diff --git a/api/openapi.json b/api/openapi.json index 8f7fac00..5c5ae5cb 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -4367,6 +4367,10 @@ "format": "date-time", "type": "string" }, + "creatorUserId": { + "format": "uuid", + "type": "string" + }, "disabledReason": { "nullable": true, "type": "string" @@ -4382,6 +4386,10 @@ "format": "uuid", "type": "string" }, + "model": { + "nullable": true, + "type": "string" + }, "name": { "type": "string" }, @@ -4419,6 +4427,7 @@ "required": [ "id", "issueId", + "creatorUserId", "name", "prompt", "agentId", @@ -5056,6 +5065,11 @@ "format": "uuid", "type": "string" }, + "scheduledTaskId": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "spaceId": { "format": "uuid", "type": "string" @@ -5073,7 +5087,8 @@ "issue_run.thread_changed", "project.repository_updated", "issue.conversation_changed", - "scheduled_task.artifacts_updated" + "scheduled_task.artifacts_updated", + "space.scheduled_tasks_updated" ], "type": "string" }, @@ -27024,6 +27039,9 @@ "format": "uuid", "type": "string" }, + "model": { + "type": "string" + }, "name": { "type": "string" }, @@ -27046,8 +27064,7 @@ "prompt", "agentId", "schedule", - "timezone", - "approval" + "timezone" ], "type": "object" } @@ -27487,6 +27504,9 @@ "format": "uuid", "type": "string" }, + "model": { + "type": "string" + }, "name": { "type": "string" }, @@ -27830,7 +27850,11 @@ "application/json": { "schema": { "additionalProperties": false, - "properties": {}, + "properties": { + "approval": { + "$ref": "#/components/schemas/ScheduledTaskApproval" + } + }, "type": "object" } } diff --git a/frontend/src/api/generated.schemas.ts b/frontend/src/api/generated.schemas.ts index f5259515..cf96e5ea 100644 --- a/frontend/src/api/generated.schemas.ts +++ b/frontend/src/api/generated.schemas.ts @@ -2007,11 +2007,14 @@ export interface ScheduledTaskSchedule { export interface ScheduledTask { agentId: string; createdAt: string; + creatorUserId: string; /** @nullable */ disabledReason?: string | null; enabled: boolean; id: string; issueId: string; + /** @nullable */ + model?: string | null; name: string; prompt: string; revision: number; @@ -2254,6 +2257,7 @@ export const SpaceEventType = { projectrepository_updated: 'project.repository_updated', issueconversation_changed: 'issue.conversation_changed', scheduled_taskartifacts_updated: 'scheduled_task.artifacts_updated', + spacescheduled_tasks_updated: 'space.scheduled_tasks_updated', } as const; export interface SpaceEvent { @@ -2262,6 +2266,8 @@ export interface SpaceEvent { /** @nullable */ projectId?: string | null; runId?: string; + /** @nullable */ + scheduledTaskId?: string | null; spaceId: string; type: SpaceEventType; version?: number; @@ -4250,9 +4256,10 @@ export type GetApiV1TenantsTidResourceStatus200 = { export type PostApiV1TenantsTidScheduledTasksBody = { agentId: string; - approval: ScheduledTaskApproval; + approval?: ScheduledTaskApproval; enabled?: boolean; issueId: string; + model?: string; name: string; prompt: string; schedule: ScheduledTaskSchedule; @@ -4276,6 +4283,7 @@ export type DeleteApiV1TenantsTidScheduledTasksStid200 = { export type PatchApiV1TenantsTidScheduledTasksStidBody = { agentId?: string; + model?: string; name?: string; prompt?: string; schedule?: ScheduledTaskSchedule; @@ -4299,7 +4307,9 @@ export type PostApiV1TenantsTidScheduledTasksStidDisable200 = { resource: ScheduledTask; }; -export type PostApiV1TenantsTidScheduledTasksStidEnableBody = { [key: string]: unknown }; +export type PostApiV1TenantsTidScheduledTasksStidEnableBody = { + approval?: ScheduledTaskApproval; +}; export type PostApiV1TenantsTidScheduledTasksStidEnable200 = { resource: ScheduledTask; diff --git a/frontend/src/features/scheduled-tasks/README.en.md b/frontend/src/features/scheduled-tasks/README.en.md index a5fadcaf..f008c868 100644 --- a/frontend/src/features/scheduled-tasks/README.en.md +++ b/frontend/src/features/scheduled-tasks/README.en.md @@ -38,7 +38,7 @@ resources; the module requests, renders and turns user actions into mutations. The history tests serve fixed responses through MSW and assert what renders (the verdict text and the rejection count). Do not mock internal modules. -The creation entry trusts the Cloud list response’s `acceptanceEnabled`; this module does not choose the gate, and the server independently validates writes. +The creation entry trusts the Cloud list response’s `acceptanceEnabled`; this module does not choose the gate, and the server independently validates writes. The form may select a model from the creator's default connection; its connection and credential are frozen at admission, while no selection uses the then-current default model. A disabled create is an unauthorised draft and sends no approval; a stopped task must re-check the risk disclosure before it can be enabled. Artifact lists contain metadata only. Download links target same-origin Cloud routes; each click rechecks authorization and verifies the file before serving a safe attachment. Browsers receive no storage URL, credentials or expiry; artifact content is never rendered inside the app. diff --git a/frontend/src/features/scheduled-tasks/README.md b/frontend/src/features/scheduled-tasks/README.md index c507e3a1..36ed6b95 100644 --- a/frontend/src/features/scheduled-tasks/README.md +++ b/frontend/src/features/scheduled-tasks/README.md @@ -34,7 +34,7 @@ 历史测试通过 MSW 提供固定响应,断言渲染结果(结论文案、被拒数量);不要 mock 内部模块。 -创建入口只依据 Cloud 列表响应的 `acceptanceEnabled`;本模块不决定开关,服务端仍独立验证写入。 +创建入口只依据 Cloud 列表响应的 `acceptanceEnabled`;本模块不决定开关,服务端仍独立验证写入。表单可选择创建者默认连接中的模型,实际连接/凭据会在接纳时冻结到运行;不选择时使用当时默认模型。禁用创建是无授权草稿,不发送授权;已停用任务在页面上必须重新勾选风险说明后才能启用。 产物列表只含元数据;下载链接指向 Cloud 同源路径,每次点击由服务端重新鉴权并校验文件,以安全附件返回。浏览器不取得对象存储地址、凭据或有效期,不在应用内渲染产物正文。 diff --git a/frontend/src/features/scheduled-tasks/api.ts b/frontend/src/features/scheduled-tasks/api.ts index 64a0e106..0f045b1d 100644 --- a/frontend/src/features/scheduled-tasks/api.ts +++ b/frontend/src/features/scheduled-tasks/api.ts @@ -29,6 +29,7 @@ export type ScheduledTaskDraft = { agentId: string name: string prompt: string + model: string schedule: ScheduledTaskSchedule timezone: string enabled: boolean @@ -52,6 +53,7 @@ export function useSaveScheduledTask(tid: string, existing: ScheduledTask | unde name: draft.name, prompt: draft.prompt, agentId: draft.agentId, + model: draft.model, schedule: draft.schedule, timezone: draft.timezone, } @@ -63,8 +65,8 @@ export function useSaveScheduledTask(tid: string, existing: ScheduledTask | unde : await postApiV1TenantsTidScheduledTasks(tid, { ...base, issueId: draft.issueId, - approval: { policy: 'full_auto_in_cloud_workspace' }, enabled: draft.enabled, + ...(draft.enabled ? { approval: { policy: 'full_auto_in_cloud_workspace' } } : {}), }) return result.resource }, @@ -87,9 +89,13 @@ export function useDeleteScheduledTask(tid: string) { export function useSetScheduledTaskEnabled(tid: string, task: ScheduledTask) { const queryClient = useQueryClient() return useMutation({ - mutationFn: (enabled: boolean) => + mutationFn: ({ enabled, consented }: { enabled: boolean; consented?: boolean }) => enabled - ? postApiV1TenantsTidScheduledTasksStidEnable(tid, task.id, {}) + ? postApiV1TenantsTidScheduledTasksStidEnable( + tid, + task.id, + consented ? { approval: { policy: 'full_auto_in_cloud_workspace' } } : {}, + ) : postApiV1TenantsTidScheduledTasksStidDisable(tid, task.id, {}), onSuccess: () => queryClient.invalidateQueries({ queryKey: getGetApiV1TenantsTidScheduledTasksQueryKey(tid) }), diff --git a/frontend/src/features/scheduled-tasks/scheduled-task-form.tsx b/frontend/src/features/scheduled-tasks/scheduled-task-form.tsx index 61bb57ed..797f8015 100644 --- a/frontend/src/features/scheduled-tasks/scheduled-task-form.tsx +++ b/frontend/src/features/scheduled-tasks/scheduled-task-form.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, @@ -26,10 +27,17 @@ import { useScheduledTaskIssues, type ScheduledTask, } from './api' +import { useModelConnections, useModelDefault } from '@/features/model-connections/api' const selectClass = 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs' +type ModelOption = { id: string; name: string } + +/** D5 无人值守授权的风险说明:必须显式同意后才能启用。 */ +export const CONSENT_RISK_STATEMENT = + '我已知悉并同意:定时运行在一次性隔离 Workspace 中全自动执行(非用户可见的数据边界承诺),Agent 可执行任意工具调用,消耗创建者个人模型额度,并以创建者 Git 身份提交。' + const KIND_OPTIONS: readonly { value: ScheduledTaskScheduleKind; label: string }[] = [ { value: 'daily', label: '每天' }, { value: 'weekly', label: '每周' }, @@ -179,12 +187,17 @@ function Fields({ setAgentId, prompt, setPrompt, + model, + setModel, + models, timezone, setTimezone, schedule, setSchedule, enabled, setEnabled, + consented, + setConsented, }: { isEdit: boolean issues: Issue[] @@ -197,12 +210,17 @@ function Fields({ setAgentId: (value: string) => void prompt: string setPrompt: (value: string) => void + model: string + setModel: (value: string) => void + models: ModelOption[] timezone: string setTimezone: (value: string) => void schedule: ScheduledTaskSchedule setSchedule: (value: ScheduledTaskSchedule) => void enabled: boolean setEnabled: (value: boolean) => void + consented: boolean + setConsented: (value: boolean) => void }) { return (
@@ -210,6 +228,98 @@ function Fields({ setName(e.target.value)} /> + + + +