File size: 6,387 Bytes
3f76ff4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837e3ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3f76ff4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837e3ac
 
 
3f76ff4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837e3ac
3f76ff4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837e3ac
 
 
 
 
3f76ff4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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] },
    },
  };
}