diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-create-request.ts b/apps/presentation/dashboard/src/features/personal-workspace/goal-create-request.ts index d7e13ae233..3b10663f5b 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-create-request.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-create-request.ts @@ -18,7 +18,7 @@ export function goalCreateContent(input: GoalContent, t: WorkspaceTranslate) { /** One preview builder for explicit forms and conversational drafts. No effects. */ export function goalCreateRequest(input: GoalContent & { - agentId: string; permission: string; contextGoalId: string | null; operationId?: string; + workspaceRef?: string; agentId: string; permission: string; contextGoalId: string | null; operationId?: string; }, t: WorkspaceTranslate): WorkspaceActionPreviewRequest { const content = goalCreateContent(input, t); return { @@ -28,7 +28,7 @@ export function goalCreateRequest(input: GoalContent & { normalizedParameters: { ...content, goal_id: `goal-${input.operationId ?? crypto.randomUUID()}`, agent_id: input.agentId, permission: input.permission, - workspace_ref: "current", heartbeat: { enabled: false, cadence: "1d", timezone: "Asia/Shanghai" }, + workspace_ref: input.workspaceRef ?? "current", heartbeat: { enabled: false, cadence: "1d", timezone: "Asia/Shanghai" }, stop_condition: "goal_complete", }, }; diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs index 65b693d157..c276f88654 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs @@ -385,6 +385,13 @@ for (const contextGoalId of [null, "existing-research-goal"]) { assert.equal(request.normalizedParameters.goal_id, "goal-new-research"); assert.notEqual(request.normalizedParameters.goal_id, contextGoalId); } +const explicitWorkspace = goalCreateRequest({ + objective: "Inspect the second project", completion: "A checked report", boundary: "Read only", + agentId: "research-agent", permission: "read_only", contextGoalId: "first-project", + workspaceRef: "second-project", operationId: "explicit-workspace", +}, (key) => key); +assert.equal(explicitWorkspace.normalizedParameters.workspace_ref, "second-project"); +assert.equal(explicitWorkspace.context.goal_id, "first-project", "Conversation and workspace remain separate"); assert.match(page, /t\("proposal\.workspace\.current"\)/, "Create Goal localizes its execution workspace explanation"); assert.match(i18n, /Current local workspace \(no Repository bound\)/, "English workspace copy explains that no repository is bound"); assert.match(i18n, /当前本地工作区(未绑定 Repository)/, "Chinese workspace copy explains that no repository is bound"); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx index 262dd21cdd..9aeae52278 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx @@ -2243,7 +2243,14 @@ export function PersonalWorkspacePage({ {composerSuggestions} {!composerCollapsed ? <> - {actionDraft ? setActionDraft(null)} onPreview={(request) => createPreview(request)} /> : null} + {actionDraft ? setActionDraft(null)} onPreview={async (request) => { + const proposal = await createPreview(request, { select: false }); + if (proposal.gate?.kind === "workspace_unavailable") { + throw new Error(proposal.gate.summary); + } + if (isCurrentConversation()) setSelection({ item: proposal, kind: "proposal" }); + return proposal; + }} /> : null} {imageAttachments.length ?
{imageAttachments.map((attachment) => (
{attachment.name} diff --git a/apps/presentation/dashboard/src/features/personal-workspace/workspace-action-form.tsx b/apps/presentation/dashboard/src/features/personal-workspace/workspace-action-form.tsx index c3a79f13cc..751749bb5b 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/workspace-action-form.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/workspace-action-form.tsx @@ -1,7 +1,7 @@ import { goalCreateContent, goalCreateRequest } from "./goal-create-request"; import { useEffect, useId, useRef, useState } from "react"; import { useWorkspaceI18n } from "./i18n"; -import type { WorkspaceActionPreviewRequest } from "./personal-workspace-model"; +import type { WorkspaceActionPreviewRequest, WorkspaceGoal } from "./personal-workspace-model"; // Action identity comes from an explicit control, never from the text being edited. export type WorkspaceActionDraft = { @@ -22,8 +22,9 @@ function boundedLength(value: string) { return Array.from(value.split(/\s+/u).filter(Boolean).join(" ")).length; } -export function WorkspaceActionForm({ draft, onClose, onPreview }: { +export function WorkspaceActionForm({ draft, workspaceGoals = [], onClose, onPreview }: { draft: WorkspaceActionDraft; + workspaceGoals?: WorkspaceGoal[]; onClose: () => void; onPreview: (request: WorkspaceActionPreviewRequest) => Promise; }) { @@ -37,6 +38,7 @@ export function WorkspaceActionForm({ draft, onClose, onPreview }: { const [objective, setObjective] = useState(draft.text ?? ""); const [completion, setCompletion] = useState(draft.completionCriteria ?? ""); const [boundary, setBoundary] = useState(draft.executionBoundary ?? ""); + const [workspaceRef, setWorkspaceRef] = useState("current"); const [permission, setPermission] = useState(draft.permission ?? "workspace_write_on_confirmation"); const [interval, setInterval] = useState(draft.kind === "heartbeat" ? "1" : "2"); const [unit, setUnit] = useState(draft.kind === "heartbeat" ? "d" : "h"); @@ -65,7 +67,7 @@ export function WorkspaceActionForm({ draft, onClose, onPreview }: { setError(""); try { if (draft.kind === "goal") { - await onPreview(goalCreateRequest({ objective, completion, boundary, permission, + await onPreview(goalCreateRequest({ objective, completion, boundary, permission, workspaceRef, agentId: draft.agentId, contextGoalId: draft.goalId }, t)); onClose(); return; @@ -103,6 +105,11 @@ export function WorkspaceActionForm({ draft, onClose, onPreview }: { {draft.kind === "goal" ? <>