Spaces:
Running
Running
| import type { | |
| ChatMessage, | |
| ParsedCommand, | |
| WorkPackage, | |
| WorkPackageOutput, | |
| WorkPackageTask, | |
| } from "./work-package-types"; | |
| import { SIMULATED_EXECUTION_DISCLAIMER } from "./work-package-types"; | |
| import type { ChatResponse } from "./board-actions"; | |
| import { | |
| buildAskGuidance, | |
| buildExecutionContent, | |
| createSpecTasks, | |
| hydrateWorkPackages, | |
| } from "./work-package-specs"; | |
| function nowIso() { | |
| return new Date().toISOString(); | |
| } | |
| function newId(prefix: string) { | |
| return `${prefix}-${crypto.randomUUID()}`; | |
| } | |
| function pickPrimaryTask(tasks: WorkPackageTask[]): WorkPackageTask | undefined { | |
| return tasks.find((t) => t.executable) ?? tasks[0]; | |
| } | |
| function buildAskResponse(args: { | |
| wp: WorkPackage; | |
| instruction: string; | |
| productIdea?: string; | |
| }) { | |
| const { wp, instruction, productIdea } = args; | |
| const focus = buildAskGuidance(wp) | |
| ? `${buildAskGuidance(wp)}` | |
| : "I would confirm the required inputs, expected outputs, and the next downstream dependency before drafting the artifact."; | |
| const intro = productIdea | |
| ? `For ${wp.shortName}, given your product idea about ${productIdea}, the objective here is: ${wp.objective}` | |
| : `For ${wp.shortName}, the objective here is: ${wp.objective}`; | |
| const questionLine = instruction | |
| ? `For your question "${instruction}", the practical interpretation is: ${focus}` | |
| : `The practical interpretation is: ${focus}`; | |
| return [intro, questionLine].join("\n\n"); | |
| } | |
| function makeSimulatedOutput(params: { | |
| wp: WorkPackage; | |
| instruction: string; | |
| productIdea?: string; | |
| sourceTaskId?: string; | |
| }): WorkPackageOutput { | |
| const { wp, instruction, productIdea, sourceTaskId } = params; | |
| const title = instruction | |
| ? `Simulated Execution: ${wp.shortName} — ${instruction}` | |
| : `Simulated Execution: ${wp.shortName}`; | |
| const generated = buildExecutionContent(wp, instruction, productIdea, sourceTaskId); | |
| return { | |
| id: newId("out"), | |
| title, | |
| type: generated.type, | |
| content: generated.content, | |
| createdAt: nowIso(), | |
| sourceTaskId: generated.sourceTaskId, | |
| executionMode: "simulated", | |
| disclaimer: SIMULATED_EXECUTION_DISCLAIMER, | |
| }; | |
| } | |
| export function runMockAgent(args: { | |
| messages: ChatMessage[]; | |
| workPackages: WorkPackage[]; | |
| selectedWorkPackageId?: string; | |
| parsedCommand?: ParsedCommand; | |
| }): ChatResponse { | |
| const { messages, workPackages, selectedWorkPackageId, parsedCommand } = args; | |
| const latestUserText = [...messages].reverse().find((message) => message.role === "user") | |
| ?.content; | |
| const productBriefFromBoard = workPackages | |
| .flatMap((workPackage) => workPackage.inputFiles) | |
| .find((input) => input.startsWith("Product brief: ")); | |
| const boardProductIdea = productBriefFromBoard?.replace(/^Product brief:\s*/, ""); | |
| const productIdea = boardProductIdea || latestUserText; | |
| const hydrationIdea = parsedCommand?.mode ? boardProductIdea : latestUserText; | |
| if (!parsedCommand?.referencedPackageName || !parsedCommand.mode) { | |
| const hydrated = hydrateWorkPackages(workPackages, latestUserText); | |
| return { | |
| assistantMessage: | |
| "Created a working draft of the IoT product package flow from your product idea. The board is now hydrated with package-specific inputs, core sections, and execution tasks, starting from CRS and carrying through service review.", | |
| boardAction: { | |
| type: "replace_all", | |
| workPackageId: null, | |
| fields: { | |
| replacementWorkPackages: hydrated, | |
| }, | |
| }, | |
| }; | |
| } | |
| const hydratedPackages = hydrateWorkPackages(workPackages, hydrationIdea); | |
| const wp = | |
| hydratedPackages.find((x) => x.id === selectedWorkPackageId) ?? | |
| hydratedPackages.find( | |
| (x) => | |
| x.title.toLowerCase() === parsedCommand.referencedPackageName?.toLowerCase() || | |
| x.shortName.toLowerCase() === | |
| parsedCommand.referencedPackageName?.toLowerCase(), | |
| ); | |
| if (!wp) { | |
| return { | |
| assistantMessage: | |
| `I couldn't find that work package in the board: "${parsedCommand.referencedPackageName}". ` + | |
| "Try using the package short name (e.g. `@SRS ...`) or click a card action to prefill the command.", | |
| boardAction: { type: "none", workPackageId: null }, | |
| }; | |
| } | |
| const instruction = (parsedCommand.instruction || "").trim(); | |
| if (parsedCommand.mode === "ask") { | |
| return { | |
| assistantMessage: buildAskResponse({ | |
| wp, | |
| instruction, | |
| productIdea: boardProductIdea || undefined, | |
| }), | |
| boardAction: { type: "none", workPackageId: null }, | |
| }; | |
| } | |
| if (parsedCommand.mode === "plan") { | |
| const plannedTasks = createSpecTasks(wp).map((task, index) => { | |
| const existing = wp.tasks[index]; | |
| return existing ? { ...existing, ...task } : task; | |
| }); | |
| return { | |
| assistantMessage: | |
| `Planned next steps for ${wp.shortName}. (Mock mode)\n\nFocus: ${ | |
| instruction || "Refine tasks into an actionable plan." | |
| }`, | |
| boardAction: { | |
| type: "update", | |
| workPackageId: wp.id, | |
| fields: { | |
| tasks: plannedTasks, | |
| status: wp.status === "done" ? "done" : "in_progress", | |
| }, | |
| }, | |
| }; | |
| } | |
| if (parsedCommand.mode === "change") { | |
| const updatedObjective = instruction | |
| ? `${wp.objective} (Change request: ${instruction})` | |
| : `${wp.objective} (Change request applied)`; | |
| return { | |
| assistantMessage: `Updated ${wp.shortName}. (Mock mode)\n- Objective updated based on your change request.`, | |
| boardAction: { | |
| type: "update", | |
| workPackageId: wp.id, | |
| fields: { | |
| objective: updatedObjective, | |
| status: wp.status === "done" ? "done" : "in_progress", | |
| }, | |
| }, | |
| }; | |
| } | |
| // execute (simulated) | |
| const primaryTask = pickPrimaryTask(wp.tasks); | |
| const output = makeSimulatedOutput({ | |
| wp, | |
| instruction, | |
| productIdea, | |
| sourceTaskId: primaryTask?.id, | |
| }); | |
| const updatedTasks = wp.tasks.map((t) => { | |
| if (!primaryTask) return t; | |
| if (t.id !== primaryTask.id) return t; | |
| return { ...t, status: "done" as const }; | |
| }); | |
| return { | |
| assistantMessage: `Simulated execution complete for ${wp.shortName}. (Mock mode)\n\n${SIMULATED_EXECUTION_DISCLAIMER}`, | |
| boardAction: { | |
| type: "update", | |
| workPackageId: wp.id, | |
| fields: { tasks: updatedTasks, outputs: [...(wp.outputs ?? []), output] }, | |
| }, | |
| }; | |
| } | |