takomi 2.1.11 → 2.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.pi/README.md CHANGED
@@ -103,18 +103,18 @@ So when working on packaging, agents should distinguish between:
103
103
  - Orchestrator sessions run in hybrid mode:
104
104
  - human-readable docs live under `docs/tasks/orchestrator-sessions/<sessionId>/`
105
105
  - machine state lives under `.pi/takomi/orchestrator/<sessionId>.json`
106
- - Task packets can carry `workflow`, `skills`, `preferredModel`, `fallbackModels`, `preferredThinking`, `dispatchPolicy`, `conversationId`, and `checklist` metadata.
107
- - Board redispatch and direct subagent calls now build a `TakomiDelegationPlan` before launch. In auto mode the plan launches immediately; in manual mode the plan is returned for review until `confirmLaunch=true` is supplied.
106
+ - Task packets can carry `workflow`, `skills`, `preferredModel`, `fallbackModels`, `preferredThinking`, `executionHint`, `conversationId`, and `checklist` metadata.
107
+ - Direct `takomi_subagent` calls build a `TakomiDelegationPlan` before launch. In auto mode the plan launches immediately; in manual mode the plan is returned for review until `confirmLaunch=true` is supplied.
108
108
  - The subagent tool supports `conversationId`, so reviewed work can be sent back to the same agent for continuation instead of restarting from scratch.
109
109
  - The subagent tool supports Pi-style single, parallel `tasks`, and sequential `chain` modes.
110
110
  - The subagent tool supports `agentScope` values of `user`, `project`, and `both`; project-local agents require confirmation by default.
111
111
  - The subagent tool also supports per-run `workflow`, `skills`, `model`, `fallbackModels`, `thinking`, and `checklist` overrides.
112
- - Board redispatch and direct `takomi_subagent` calls now share one launch path, so model preflight, thinking, fallback behavior, default prompts, and persisted session files stay aligned.
113
- - Pi's default `subagent` tool remains owned by the user-level/default subagent extension to avoid tool-name conflicts; Takomi uses `takomi_subagent` as the preferred lifecycle-aware interface and renders it with the native Pi-style result surface.
114
- - Treat raw `subagent` usage as advanced/internal. Normal Takomi lifecycle work should go through `takomi_subagent` or `takomi_board`.
112
+ - `takomi_board` records session/state/markdown artifacts only; it does not run subagents.
113
+ - Pi's default `subagent` tool remains owned by the user-level/default subagent extension to avoid tool-name conflicts; Takomi uses `takomi_subagent` as the preferred lifecycle-aware execution interface and renders it with the native Pi-style result surface.
114
+ - Treat raw `subagent` usage as advanced/internal. Normal Takomi lifecycle execution should go through `takomi_subagent`, then `takomi_board update_task` should record the outcome.
115
115
  - Active Takomi subagent work now streams through the native Pi-style result UI instead of Takomi's older below-editor stack.
116
116
  - Use Pi's native result expansion, `Alt+T`, or `/takomi subagents expand` to inspect detailed subagent output.
117
- - Takomi still tracks active runs internally for status, review continuity, and board synchronization, but it no longer opens a custom subagent fullscreen overlay.
117
+ - Takomi still tracks active runs internally for status and review continuity, but board synchronization is explicit: run with `takomi_subagent`, then update the board.
118
118
  - `takomi-context-manager` reduces prompt bloat by replacing the always-on skill description dump with a names-only Skill Index plus progressive `skill_manifest`/`skill_load` tools.
119
119
  - `takomi-context-manager` treats `/takomi routing` as the source of truth for model-routing policy via `.pi/settings.json -> takomi.modelRoutingPolicyFile`.
120
120
  - `takomi-context-manager` gates `takomi_subagent` when model-routing context has not been loaded, provides the routing policy, and tells the agent to retry.
@@ -122,11 +122,9 @@ So when working on packaging, agents should distinguish between:
122
122
  - `takomi-context-manager` detects known duplicate global/project Takomi extension paths in `context_report` to help diagnose tool registration conflicts.
123
123
  - `takomi_board` can:
124
124
  - create a Genesis-first lifecycle session by default
125
+ - preserve authored `master_plan.md` and task packet markdown
125
126
  - expand a lifecycle stage into additional tasks
126
- - update task status and notes
127
- - update checklist progress
128
- - dispatch approved tasks as single, parallel, or chain subagent run groups
127
+ - update task status, notes, and checklist progress
129
128
  - rewrite JSON machine state
130
- - regenerate task docs into `pending/`, `in-progress/`, `completed/`, and `blocked/`
131
- - redispatch a task to the same agent with the same `conversationId`
132
- - use `review_and_redispatch` for a cleaner review loop
129
+ - keep task docs organized in `pending/`, `in-progress/`, `completed/`, and `blocked/`
130
+ - `takomi_board` intentionally cannot dispatch or redispatch agents. Use `takomi_subagent` for single, parallel, or chain execution, then call `takomi_board update_task` with the result.
@@ -49,7 +49,13 @@ Map sequential vs parallel work explicitly.
49
49
  ## Phase 3: Session Initialization
50
50
  For broad work, create or update an orchestration session using markdown-first authorship.
51
51
 
52
- Do **not** let JSON/tool fields generate the human plan by themselves. First author the session docs naturally, then register them with `takomi_board` using `masterPlanMarkdown` and each task's `taskMarkdown`.
52
+ Do **not** let JSON/tool fields generate the human plan by themselves. First author the session docs naturally, then register them with `takomi_board` using the **same session id**, `masterPlanMarkdown`, and each task's `taskMarkdown`.
53
+
54
+ `takomi_board` only tracks session/state/markdown artifacts. Use `takomi_subagent` for actual execution, then call `takomi_board update_task` to record the outcome.
55
+
56
+ Session IDs must follow the canonical timestamp format: `orch-YYYYMMDD-HHMMSS` (example: `orch-20260515-161526`). Use this exact ID for both the markdown folder and the board JSON state.
57
+
58
+ If you already wrote `docs/tasks/orchestrator-sessions/<sessionId>/master_plan.md`, call `takomi_board` with `sessionId: "<sessionId>"`. Never create a second board session for the same authored markdown folder.
53
59
 
54
60
  ### Master Plan Shape
55
61
  Create `docs/tasks/orchestrator-sessions/<sessionId>/master_plan.md` with:
@@ -81,7 +87,7 @@ Each task packet should include:
81
87
  - `## Constraints`
82
88
  - optional `## Dependencies`, `## Verification`, or `## Handoff Notes` when useful
83
89
 
84
- Task packets should be self-contained enough for a subagent to execute without guessing, but scoped enough to review.
90
+ Task packets should be self-contained enough for a subagent to execute without guessing, but scoped enough to review. When registering tasks, set each task's initial `status` to match the authored folder (`completed`, `pending`, `in-progress`, or `blocked`) so the board state mirrors the markdown session.
85
91
 
86
92
  Keep human-readable markdown meaningful; keep JSON as tracking/continuity metadata.
87
93
 
@@ -33,10 +33,6 @@ import {
33
33
  type TakomiWorkflowId,
34
34
  type VibeLifecycleStage,
35
35
  } from "../../../src/pi-takomi-core";
36
- import { discoverProjectAgents, type TakomiAgentConfig } from "../takomi-subagents/agents";
37
- import { dispatchTakomiSubagent, type TakomiDispatchResult } from "../takomi-subagents/dispatch";
38
- import { createTakomiDelegationPlan, renderTakomiDelegationPlan } from "../takomi-subagents/delegation-plan";
39
- import { executeTakomiSubagentTool } from "../takomi-subagents/tool-runner";
40
36
  import {
41
37
  renderRuntimeStatus,
42
38
  renderRuntimeWidget,
@@ -192,7 +188,7 @@ async function loadRolePrompt(cwd: string, role: TakomiRole): Promise<string> {
192
188
  cleaned,
193
189
  ].join("\n\n");
194
190
  }
195
- } catch {}
191
+ } catch { }
196
192
  }
197
193
 
198
194
  return fallbackRolePrompt(role);
@@ -242,7 +238,7 @@ async function loadWorkflowPrompt(cwd: string, workflow: TakomiWorkflowId): Prom
242
238
  const raw = await readFile(candidate, "utf8");
243
239
  const cleaned = stripTemplateOnlyRequestPlaceholder(stripPromptFrontmatter(raw));
244
240
  if (cleaned) return cleaned;
245
- } catch {}
241
+ } catch { }
246
242
  }
247
243
 
248
244
  return undefined;
@@ -278,11 +274,7 @@ function shouldAutoRoute(text: string): boolean {
278
274
  }
279
275
 
280
276
  function buildTaskRows(tasks: OrchestratorTask[]): string {
281
- return tasks.map((task) => `${task.id}: ${task.stage ?? "-"} | ${task.title} [${task.status}] -> ${task.preferredAgent ?? task.role}${task.conversationId ? ` (${task.conversationId})` : ""}${task.workflow ? ` | workflow=${task.workflow}` : ""}${task.preferredModel ? ` | model=${task.preferredModel}` : ""}${task.preferredThinking ? ` | thinking=${task.preferredThinking}` : ""}${task.dispatchPolicy ? ` | dispatch=${task.dispatchPolicy}` : ""}${task.skills?.length ? ` | skills=${task.skills.join(",")}` : ""}`).join("\n");
282
- }
283
-
284
- function resolveTaskAgent(task: OrchestratorTask): string {
285
- return task.preferredAgent ?? (task.role === "code" ? "coder" : task.role === "design" ? "designer" : task.role === "architect" ? "architect" : task.role === "review" ? "reviewer" : "orchestrator");
277
+ return tasks.map((task) => `${task.id}: ${task.stage ?? "-"} | ${task.title} [${task.status}] -> ${task.preferredAgent ?? task.role}${task.conversationId ? ` (${task.conversationId})` : ""}${task.workflow ? ` | workflow=${task.workflow}` : ""}${task.preferredModel ? ` | model=${task.preferredModel}` : ""}${task.preferredThinking ? ` | thinking=${task.preferredThinking}` : ""}${task.dispatchPolicy ? ` | execution=${task.dispatchPolicy}` : ""}${task.skills?.length ? ` | skills=${task.skills.join(",")}` : ""}`).join("\n");
286
278
  }
287
279
 
288
280
  function appendTaskNote(existing: string | undefined, heading: string, body?: string): string {
@@ -469,6 +461,7 @@ type IncomingTask = {
469
461
  id?: string;
470
462
  title: string;
471
463
  taskMarkdown?: string;
464
+ status?: OrchestratorTaskStatus;
472
465
  role: TakomiRole;
473
466
  stage?: VibeLifecycleStage;
474
467
  workflow?: TakomiWorkflowId;
@@ -478,6 +471,7 @@ type IncomingTask = {
478
471
  preferredModelHint?: string;
479
472
  preferredThinking?: TakomiThinkingLevel;
480
473
  fallbackModels?: string[];
474
+ executionHint?: TakomiDispatchPolicy;
481
475
  dispatchPolicy?: TakomiDispatchPolicy;
482
476
  skills?: string[];
483
477
  checklist?: Array<string | { text: string; done?: boolean }>;
@@ -518,7 +512,8 @@ async function materializeTasksFromInput(
518
512
  preferredModelHint: [task.preferredModelHint, resolvedModel.warning].filter(Boolean).join(" ").trim() || undefined,
519
513
  preferredThinking: task.preferredThinking ?? defaults.thinking,
520
514
  fallbackModels: fallbackModels.length ? fallbackModels : undefined,
521
- dispatchPolicy: task.dispatchPolicy ?? defaults.dispatchPolicy,
515
+ status: task.status ?? "pending",
516
+ dispatchPolicy: task.executionHint ?? task.dispatchPolicy ?? defaults.dispatchPolicy,
522
517
  skills: task.skills,
523
518
  checklist: (task.checklist ?? []).map((item) => typeof item === "string" ? { text: item } : item),
524
519
  objective: task.objective,
@@ -569,7 +564,7 @@ async function refreshUi(ctx: ExtensionContext, state: TakomiState) {
569
564
  if (!ctx.hasUI) return;
570
565
  ctx.ui.setTitle("Takomi");
571
566
  ctx.ui.setHeader((_tui, theme) => ({
572
- invalidate() {},
567
+ invalidate() { },
573
568
  render() {
574
569
  return renderTakomiHeader(theme);
575
570
  },
@@ -780,34 +775,25 @@ export default function takomiRuntime(pi: ExtensionAPI) {
780
775
  name: "takomi_board",
781
776
  label: "Takomi Board",
782
777
  description: "Create and manage lifecycle-aware Takomi orchestration session artifacts.",
783
- promptSnippet: "Track/delegate a Genesis -> Design -> Build orchestration session after the core plan has been authored in markdown.",
778
+ promptSnippet: "Register or update Takomi session/state/markdown artifacts; subagent execution happens elsewhere.",
784
779
  promptGuidelines: [
785
780
  "Use this when you need a concrete orchestrator session directory and task artifacts on disk.",
786
- "Do not use takomi_board as a substitute for authoring PRD, issue, design, build, master-plan, or task markdown. Write the human-facing markdown first, then use this tool to register/preserve it for state tracking and dispatch.",
787
- "For high-quality orchestration sessions, provide masterPlanMarkdown and taskMarkdown values that contain the authored plan/task packets; JSON fields should carry IDs/status/roles/workflow/dependencies/checklists for tracking, not replace expressive markdown.",
781
+ "takomi_board never runs subagents. Author the human-facing markdown first, use takomi_subagent for execution, then return here with takomi_board update_task to record the outcome.",
782
+ "Session IDs must use the canonical timestamp format orch-YYYYMMDD-HHMMSS. Use the same sessionId for the authored docs folder and the board JSON state.",
783
+ "For high-quality orchestration sessions, provide sessionId, masterPlanMarkdown, and taskMarkdown values that match the authored session folder. If you already wrote docs/tasks/orchestrator-sessions/<id>, call this tool with sessionId=<id>; do not create a second session id.",
784
+ "JSON fields should carry IDs/status/roles/workflow/dependencies/checklists for tracking, not replace expressive markdown.",
788
785
  "A new session should normally begin Genesis-first, then expand Design and Build into as many tasks as the scope actually needs.",
789
786
  "If the request is small enough, do not force orchestration just because the tool exists.",
790
- "If a reviewed task needs more work, keep or reuse its conversationId so the same subagent can continue it.",
787
+ "If a reviewed task needs more work, reuse the task conversationId when you call takomi_subagent again, then update the board with the new result.",
791
788
  ],
792
789
  parameters: Type.Object({
793
- action: StringEnum(["init_session", "expand_stage", "show_workflows", "show_session", "update_task", "redispatch_task", "review_and_redispatch", "dispatch_tasks"] as const),
790
+ action: StringEnum(["init_session", "expand_stage", "show_workflows", "show_session", "update_task"] as const),
794
791
  title: Type.Optional(Type.String()),
795
792
  sessionId: Type.Optional(Type.String()),
796
793
  taskId: Type.Optional(Type.String()),
797
- taskIds: Type.Optional(Type.Array(Type.String())),
798
- dispatchMode: Type.Optional(StringEnum(["single", "parallel", "chain"] as const)),
799
- agentScope: Type.Optional(StringEnum(["user", "project", "both"] as const)),
800
- confirmProjectAgents: Type.Optional(Type.Boolean()),
801
794
  stage: Type.Optional(StringEnum(["genesis", "design", "build"] as const)),
802
795
  status: Type.Optional(StringEnum(["pending", "in-progress", "completed", "blocked"] as const)),
803
796
  notes: Type.Optional(Type.String()),
804
- rerunInstructions: Type.Optional(Type.String()),
805
- confirmLaunch: Type.Optional(Type.Boolean()),
806
- previewOnly: Type.Optional(Type.Boolean()),
807
- preferredAgent: Type.Optional(Type.String()),
808
- preferredModel: Type.Optional(Type.String()),
809
- preferredThinking: Type.Optional(ThinkingSchema),
810
- includeReview: Type.Optional(Type.Boolean()),
811
797
  checklist: Type.Optional(Type.Array(Type.Union([
812
798
  Type.String(),
813
799
  Type.Object({ text: Type.String(), done: Type.Optional(Type.Boolean()) }),
@@ -822,6 +808,7 @@ export default function takomiRuntime(pi: ExtensionAPI) {
822
808
  id: Type.Optional(Type.String()),
823
809
  title: Type.String(),
824
810
  taskMarkdown: Type.Optional(Type.String()),
811
+ status: Type.Optional(StringEnum(["pending", "in-progress", "completed", "blocked"] as const)),
825
812
  role: StringEnum(["general", "orchestrator", "architect", "design", "code", "review"] as const),
826
813
  stage: Type.Optional(StringEnum(["genesis", "design", "build"] as const)),
827
814
  workflow: Type.Optional(StringEnum(["vibe-genesis", "vibe-design", "vibe-build"] as const)),
@@ -831,7 +818,7 @@ export default function takomiRuntime(pi: ExtensionAPI) {
831
818
  preferredModelHint: Type.Optional(Type.String()),
832
819
  preferredThinking: Type.Optional(ThinkingSchema),
833
820
  fallbackModels: Type.Optional(Type.Array(Type.String())),
834
- dispatchPolicy: Type.Optional(StringEnum(["direct", "subagent", "review-first"] as const)),
821
+ executionHint: Type.Optional(StringEnum(["direct", "subagent", "review-first"] as const)),
835
822
  skills: Type.Optional(Type.Array(Type.String())),
836
823
  checklist: Type.Optional(Type.Array(Type.Union([
837
824
  Type.String(),
@@ -866,8 +853,10 @@ export default function takomiRuntime(pi: ExtensionAPI) {
866
853
  readFile(paths.stateFile, "utf8").catch(() => "{}"),
867
854
  ]);
868
855
  return {
869
- content: [{ type: "text", text: `${masterPlan}\n\n---\n\nMachine state\n\n\
870
- ${stateJson}` }],
856
+ content: [{
857
+ type: "text", text: `${masterPlan}\n\n---\n\nMachine state\n\n\
858
+ ${stateJson}`
859
+ }],
871
860
  details: { paths, state: normalizeSessionState({ sessionId: params.sessionId, title: "Takomi Session", ...(JSON.parse(stateJson) as Partial<OrchestratorSessionState>) }) },
872
861
  };
873
862
  }
@@ -931,242 +920,6 @@ ${stateJson}` }],
931
920
  };
932
921
  }
933
922
 
934
- if (params.action === "dispatch_tasks") {
935
- if (!state.subagentsEnabled) {
936
- return {
937
- content: [{ type: "text", text: "Takomi subagents are disabled. Use /takomi subagents on before dispatching board tasks." }],
938
- details: { sessionId: params.sessionId, taskIds: params.taskIds },
939
- isError: true,
940
- };
941
- }
942
- if (!params.sessionId || !params.taskIds?.length) {
943
- return { content: [{ type: "text", text: "sessionId and taskIds are required for dispatch_tasks" }], details: {}, isError: true };
944
- }
945
- const { state: sessionState } = await loadSessionState(ctx.cwd, params.sessionId);
946
- const selectedTasks = params.taskIds.map((id) => sessionState.tasks.find((task) => task.id === id));
947
- if (selectedTasks.some((task) => !task)) {
948
- return {
949
- content: [{ type: "text", text: `Some taskIds were not found in session ${params.sessionId}.` }],
950
- details: { requestedTaskIds: params.taskIds, availableTaskIds: sessionState.tasks.map((task) => task.id) },
951
- isError: true,
952
- };
953
- }
954
- const boardTasks = selectedTasks as OrchestratorTask[];
955
- const mode = params.dispatchMode ?? (boardTasks.length === 1 ? "single" : "parallel");
956
- const toolTasks = boardTasks.map((task) => ({
957
- agent: params.preferredAgent ?? resolveTaskAgent(task),
958
- task: task.notes || task.title,
959
- workflow: task.workflow,
960
- skills: task.skills,
961
- model: params.preferredModel ?? task.preferredModel,
962
- fallbackModels: task.fallbackModels,
963
- thinking: params.preferredThinking ?? task.preferredThinking,
964
- conversationId: task.conversationId ?? task.id,
965
- checklist: task.checklist,
966
- }));
967
- if (params.previewOnly || ((state.launchMode ?? activeProfile.launchMode) === "manual" && !params.confirmLaunch)) {
968
- return executeTakomiSubagentTool(pi, {
969
- ...(mode === "chain" ? { chain: toolTasks } : mode === "parallel" ? { tasks: toolTasks } : toolTasks[0]),
970
- previewOnly: true,
971
- agentScope: params.agentScope ?? "both",
972
- confirmProjectAgents: params.confirmProjectAgents,
973
- }, _signal, _onUpdate, ctx);
974
- }
975
- for (const task of boardTasks) task.status = "in-progress";
976
- let nextState = buildSessionState(sessionState.sessionId, sessionState.title, sessionState.tasks, new Date(), {
977
- sessionIntent: sessionState.sessionIntent,
978
- lifecycle: sessionState.lifecycle,
979
- });
980
- const paths = await syncTaskArtifacts(ctx.cwd, nextState);
981
- const toolResult = await executeTakomiSubagentTool(pi, {
982
- ...(mode === "chain" ? { chain: toolTasks } : mode === "parallel" ? { tasks: toolTasks } : toolTasks[0]),
983
- confirmLaunch: true,
984
- agentScope: params.agentScope ?? "both",
985
- confirmProjectAgents: params.confirmProjectAgents,
986
- }, _signal, _onUpdate, ctx);
987
- const results = (toolResult.details as { results?: TakomiDispatchResult[] }).results ?? [];
988
- for (let index = 0; index < boardTasks.length; index++) {
989
- const result = results[index];
990
- const task = boardTasks[index];
991
- if (!result) continue;
992
- task.conversationId = result.conversationId;
993
- task.notes = appendTaskNote(task.notes, "Model preflight", result.preflight);
994
- task.notes = appendTaskNote(task.notes, "Last dispatch output", result.output || result.stderr);
995
- if (result.code !== 0) task.status = "blocked";
996
- if (result.model) task.preferredModel = result.model;
997
- if (result.thinking) task.preferredThinking = result.thinking;
998
- }
999
- nextState = buildSessionState(sessionState.sessionId, sessionState.title, sessionState.tasks, new Date(), {
1000
- sessionIntent: sessionState.sessionIntent,
1001
- lifecycle: sessionState.lifecycle,
1002
- });
1003
- await syncTaskArtifacts(ctx.cwd, nextState);
1004
- return {
1005
- content: toolResult.content,
1006
- details: { ...toolResult.details, sessionId: params.sessionId, paths, lifecycle: nextState.lifecycle, mode },
1007
- isError: results.some((result) => result.code !== 0) || undefined,
1008
- };
1009
- }
1010
-
1011
- if (params.action === "redispatch_task" || params.action === "review_and_redispatch") {
1012
- if (!state.subagentsEnabled) {
1013
- return {
1014
- content: [{ type: "text", text: "Takomi subagents are disabled. Use /takomi subagents on before redispatching." }],
1015
- details: { sessionId: params.sessionId, taskId: params.taskId },
1016
- isError: true,
1017
- };
1018
- }
1019
- if (!params.sessionId || !params.taskId) {
1020
- return { content: [{ type: "text", text: "sessionId and taskId are required for redispatch_task" }], details: {}, isError: true };
1021
- }
1022
- const { state: sessionState } = await loadSessionState(ctx.cwd, params.sessionId);
1023
- const task = sessionState.tasks.find((item) => item.id === params.taskId);
1024
- if (!task) {
1025
- return { content: [{ type: "text", text: `Task ${params.taskId} not found in session ${params.sessionId}` }], details: {}, isError: true };
1026
- }
1027
- const draftChecklist = resolveChecklistState(task.checklist, params.checklist, params.checklistUpdates);
1028
- const agentName = params.preferredAgent ?? resolveTaskAgent(task);
1029
- const draftModel = params.preferredModel ?? task.preferredModel;
1030
- const draftThinking = params.preferredThinking ?? task.preferredThinking;
1031
- const conversationId = task.conversationId ?? task.id;
1032
- const launchMode = state.launchMode ?? activeProfile.launchMode ?? "auto";
1033
- const plan = createTakomiDelegationPlan({
1034
- source: "runtime-board",
1035
- sessionId: params.sessionId,
1036
- launchMode,
1037
- profile: activeProfile,
1038
- tasks: [{
1039
- id: task.id,
1040
- title: task.title,
1041
- agent: agentName,
1042
- task: params.rerunInstructions ?? task.notes ?? task.title,
1043
- role: task.role,
1044
- stage: task.stage,
1045
- workflow: task.workflow,
1046
- model: draftModel,
1047
- fallbackModels: task.fallbackModels,
1048
- thinking: draftThinking,
1049
- conversationId,
1050
- checklist: draftChecklist,
1051
- dispatchPolicy: task.dispatchPolicy,
1052
- review: params.includeReview,
1053
- }],
1054
- });
1055
- if (params.previewOnly || (launchMode === "manual" && !params.confirmLaunch)) {
1056
- return {
1057
- content: [{ type: "text", text: renderTakomiDelegationPlan(plan) }],
1058
- details: { sessionId: params.sessionId, taskId: task.id, plan },
1059
- };
1060
- }
1061
-
1062
- const agents: TakomiAgentConfig[] = discoverProjectAgents(ctx.cwd);
1063
- const config = agents.find((agent: TakomiAgentConfig) => agent.name === agentName);
1064
- if (!config) {
1065
- return { content: [{ type: "text", text: `Preferred agent '${agentName}' not found.` }], details: { availableAgents: agents.map((agent: TakomiAgentConfig) => agent.name) }, isError: true };
1066
- }
1067
-
1068
- task.status = "in-progress";
1069
- task.checklist = draftChecklist;
1070
- task.preferredAgent = agentName;
1071
- task.preferredModel = draftModel;
1072
- task.preferredThinking = draftThinking;
1073
- if (params.action === "review_and_redispatch") {
1074
- task.notes = appendTaskNote(task.notes, "Review feedback", params.notes);
1075
- } else if (params.notes) {
1076
- task.notes = params.notes;
1077
- }
1078
- let nextState = buildSessionState(
1079
- sessionState.sessionId,
1080
- sessionState.title,
1081
- sessionState.tasks,
1082
- new Date(),
1083
- {
1084
- sessionIntent: sessionState.sessionIntent,
1085
- lifecycle: sessionState.lifecycle,
1086
- },
1087
- );
1088
- const paths = await syncTaskArtifacts(ctx.cwd, nextState);
1089
- task.conversationId = conversationId;
1090
- const runKey = conversationId;
1091
- const parentRunKey = task.parentTaskId
1092
- ? (() => {
1093
- const parentTask = sessionState.tasks.find((item) => item.id === task.parentTaskId);
1094
- if (parentTask) return parentTask.conversationId ?? parentTask.id;
1095
- return subagentController.getKnownParentRunKey(task.parentTaskId);
1096
- })()
1097
- : undefined;
1098
-
1099
- const result = await dispatchTakomiSubagent(ctx, {
1100
- agent: config,
1101
- task: task.notes || task.title,
1102
- rootCwd: ctx.cwd,
1103
- workflow: task.workflow,
1104
- skills: task.skills,
1105
- model: task.preferredModel,
1106
- fallbackModels: task.fallbackModels,
1107
- thinking: task.preferredThinking,
1108
- conversationId,
1109
- checklist: task.checklist,
1110
- stage: task.stage,
1111
- taskLabel: `${task.id} - ${task.title}`,
1112
- parentTaskId: task.parentTaskId,
1113
- parentRunKey,
1114
- boardTaskStatus: task.status,
1115
- source: "runtime-board",
1116
- rerunInstructions: params.rerunInstructions,
1117
- }, _signal, {
1118
- emit: (event) => {
1119
- void applySubagentRuntimeEvent(event, ctx);
1120
- },
1121
- });
1122
-
1123
- task.notes = appendTaskNote(task.notes, "Model preflight", result.preflight);
1124
- if (result.model) task.preferredModel = result.model;
1125
- if (result.warning) {
1126
- task.notes = appendTaskNote(task.notes, "Model fallback", result.warning);
1127
- if (ctx.hasUI) ctx.ui.notify(result.warning, "warning");
1128
- }
1129
- if (result.thinking) task.preferredThinking = result.thinking;
1130
-
1131
- if (result.code !== 0) {
1132
- task.status = "blocked";
1133
- task.notes = appendTaskNote(task.notes, "Redispatch failure", result.stderr || result.output);
1134
- nextState = buildSessionState(
1135
- sessionState.sessionId,
1136
- sessionState.title,
1137
- sessionState.tasks,
1138
- new Date(),
1139
- {
1140
- sessionIntent: sessionState.sessionIntent,
1141
- lifecycle: sessionState.lifecycle,
1142
- },
1143
- );
1144
- await syncTaskArtifacts(ctx.cwd, nextState);
1145
- return {
1146
- content: [{ type: "text", text: `Redispatch failed for task ${task.id}.\n\n${result.stderr || result.output || "No output"}` }],
1147
- details: { sessionId: params.sessionId, task, paths, result },
1148
- isError: true,
1149
- };
1150
- }
1151
-
1152
- task.notes = appendTaskNote(task.notes, "Last redispatch output", result.output);
1153
- nextState = buildSessionState(
1154
- sessionState.sessionId,
1155
- sessionState.title,
1156
- sessionState.tasks,
1157
- new Date(),
1158
- {
1159
- sessionIntent: sessionState.sessionIntent,
1160
- lifecycle: sessionState.lifecycle,
1161
- },
1162
- );
1163
- await syncTaskArtifacts(ctx.cwd, nextState);
1164
- return {
1165
- content: [{ type: "text", text: `${result.preflight}\n\n${result.output || `Redispatched task ${task.id} to ${agentName}.`}` }],
1166
- details: { sessionId: params.sessionId, task, paths, lifecycle: nextState.lifecycle, agent: agentName, conversationId: task.conversationId, action: params.action, result },
1167
- };
1168
- }
1169
-
1170
923
  if (params.action === "expand_stage") {
1171
924
  if (!params.sessionId || !params.stage || !params.tasks?.length) {
1172
925
  return { content: [{ type: "text", text: "sessionId, stage, and at least one task are required for expand_stage" }], details: {}, isError: true };
@@ -1359,7 +1112,7 @@ ${stateJson}` }],
1359
1112
  modelPreflightContext,
1360
1113
  `Execution mode: ${route.executionMode}. Session recommendation: ${route.sessionRecommendation}.`,
1361
1114
  `Takomi execution gate: ${effectiveState.launchMode === "manual" ? "review" : "auto"}. In review gate mode, show the delegation plan before launching and return to the user after each task with results, verification guidance, and the recommended next step.`,
1362
- !effectiveState.subagentsEnabled ? "Takomi subagents are disabled for this session. Do not call takomi_subagent, subagent, or redispatch board tasks until the user enables subagents." : "",
1115
+ !effectiveState.subagentsEnabled ? "Takomi subagents are disabled for this session. Do not call takomi_subagent or subagent until the user enables subagents." : "",
1363
1116
  !genesisExists ? "Project foundation is missing or incomplete. Do not skip Genesis unless the user explicitly waives it." : "",
1364
1117
  "Takomi is the default orchestration mindset here. Do not wait for the literal phrase 'use Takomi' before applying lifecycle judgment.",
1365
1118
  "Task fan-out is flexible. Do not force exactly three tasks; decompose Genesis, Design, and Build work to fit the actual scope.",
@@ -1368,7 +1121,7 @@ ${stateJson}` }],
1368
1121
  "Before any Takomi subagent dispatch or model override, use the injected Pi model-registry context and project routing policy. Prefer provider-qualified model IDs. Do not run `pi --list-models` unless the registry context is missing or the user asks for a visible diagnostic.",
1369
1122
  "When useful, state the current Takomi stage and the recommended next stage.",
1370
1123
  effectiveState.stage === "build"
1371
- ? "For build orchestration, it is valid to dispatch tasks to specialist subagents, review them, and send fixes back to the same agent by reusing its conversation id."
1124
+ ? "For build orchestration, use takomi_subagent to dispatch work to specialist subagents, then record the result on takomi_board; reuse the same conversation id when sending fixes back to the agent."
1372
1125
  : "",
1373
1126
  ].filter(Boolean);
1374
1127
 
@@ -1,6 +1,6 @@
1
1
  import type { TakomiThinkingLevel } from "../../../src/pi-takomi-core";
2
2
  import type { TakomiSubagentRuntimeEvent } from "../takomi-runtime/subagent-types";
3
- import type { TakomiDispatchResult } from "./dispatch";
3
+ import type { TakomiDispatchResult } from "./run-types";
4
4
 
5
5
  type ToolUpdate = (partial: {
6
6
  content: Array<{ type: "text"; text: string }>;
@@ -0,0 +1,25 @@
1
+ import type { TakomiThinkingLevel } from "../../../src/pi-takomi-core";
2
+
3
+ export type TakomiDispatchResult = {
4
+ agent: string;
5
+ task: string;
6
+ workflow?: string;
7
+ model?: string;
8
+ warning?: string;
9
+ thinking?: TakomiThinkingLevel;
10
+ conversationId: string;
11
+ code: number;
12
+ output: string;
13
+ stderr: string;
14
+ preflight: string;
15
+ startedAt?: number;
16
+ endedAt?: number;
17
+ lastActivityAt?: number;
18
+ currentTool?: string;
19
+ currentToolArgs?: string;
20
+ currentToolStartedAt?: number;
21
+ recentTools?: Array<{ tool: string; args: string; endMs: number }>;
22
+ recentOutput?: string[];
23
+ toolCount?: number;
24
+ sessionFile?: string;
25
+ };
@@ -9,7 +9,8 @@ import {
9
9
  import { resolveAgentName } from "./agent-aliases";
10
10
  import { discoverTakomiAgents, type TakomiAgentConfig, type TakomiAgentScope } from "./agents";
11
11
  import { createTakomiDelegationPlan, renderTakomiDelegationPlan } from "./delegation-plan";
12
- import { dispatchTakomiSubagent, type TakomiDispatchResult } from "./dispatch";
12
+ import type { TakomiDispatchResult } from "./run-types";
13
+ import { createTakomiPiSubagentsEngine } from "./pi-subagents-engine";
13
14
  import { createTakomiLiveUpdateBridge } from "./live-updates";
14
15
 
15
16
  type ChecklistItem = string | { text: string; done?: boolean };
@@ -110,6 +111,7 @@ export async function executeTakomiSubagentTool(
110
111
  onUpdate: ToolUpdate | undefined,
111
112
  ctx: ExtensionContext,
112
113
  ) {
114
+ const engine = createTakomiPiSubagentsEngine(pi);
113
115
  const rootCwd = params.cwd ? path.resolve(ctx.cwd, params.cwd) : ctx.cwd;
114
116
  const profile = await loadTakomiProfile(rootCwd);
115
117
  const agentScope = params.agentScope ?? "both";
@@ -161,10 +163,9 @@ export async function executeTakomiSubagentTool(
161
163
  const runOne = async (item: TakomiSubagentToolTask, index: number, previousOutput = "") => {
162
164
  const config = byName.get(item.agent);
163
165
  if (!config) throw new Error(`Unknown subagent '${item.agent}'. Available: ${agents.map((agent) => `${agent.name} (${agent.source})`).join(", ") || "none"}`);
164
- const result = await dispatchTakomiSubagent(ctx, {
165
- agent: config,
166
+ const raw: any = await engine.execute(`takomi-tool-${index + 1}`, {
167
+ agent: item.agent,
166
168
  task: item.task.replaceAll("{previous}", previousOutput),
167
- rootCwd,
168
169
  cwd: item.cwd,
169
170
  workflow: item.workflow,
170
171
  skills: item.skills,
@@ -173,13 +174,22 @@ export async function executeTakomiSubagentTool(
173
174
  thinking: item.thinking,
174
175
  conversationId: item.conversationId,
175
176
  checklist: item.checklist,
176
- source: "takomi-tool",
177
- }, signal, {
178
- emit: (event) => {
179
- emitRuntimeSubagentEvent(pi, event);
180
- live.event(index, event);
181
- },
182
- });
177
+ }, signal, undefined as any, ctx);
178
+ const single = raw?.details?.results?.[0] ?? {};
179
+ const result: TakomiDispatchResult = {
180
+ agent: single.agent ?? config.name,
181
+ task: item.task,
182
+ workflow: item.workflow,
183
+ model: single.model ?? item.model,
184
+ warning: single.warning,
185
+ thinking: item.thinking,
186
+ conversationId: single.conversationId ?? item.conversationId ?? `${config.name}-${Date.now()}`,
187
+ code: single.exitCode ?? 1,
188
+ output: single.messages?.map((message: any) => message?.content?.map((part: any) => part?.type === "text" ? part.text : "").filter(Boolean).join("\n")).filter(Boolean).join("\n\n") ?? "",
189
+ stderr: single.stderr ?? "",
190
+ preflight: raw?.content?.[0]?.text ?? "",
191
+ sessionFile: single.sessionFile,
192
+ };
183
193
  live.finish(index, result);
184
194
  return result;
185
195
  };
@@ -16,6 +16,9 @@ Always-on Takomi behavior.
16
16
  - Do not blend architecture, design, and implementation sloppily.
17
17
  - When the right path is clear, make a recommendation instead of hedging.
18
18
  - For broad work, Genesis may create the orchestration session that carries work into later stages.
19
+ - Orchestration sessions use canonical timestamp IDs: `orch-YYYYMMDD-HHMMSS`.
20
+ - Orchestration sessions are markdown-first: author `master_plan.md` and task packets first, then call `takomi_board` with the same `sessionId`, `masterPlanMarkdown`, task `taskMarkdown`, and matching task statuses. Do not create a second board session for already-authored session docs.
21
+ - `takomi_board` never runs subagents. Use `takomi_subagent` for execution, then come back to `takomi_board update_task` to record results.
19
22
 
20
23
  ## Shared Mode Pattern
21
24
  - Load context before acting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takomi",
3
- "version": "2.1.11",
3
+ "version": "2.1.12",
4
4
  "description": "🎯 Stop wrestling with AI. Start building with purpose. The artisan's toolkit for agent workflows, Codex skills, and original Takomi capabilities like 21st.dev integration.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -316,7 +316,7 @@ export function renderMasterPlan(sessionOrId: OrchestratorSessionState | string,
316
316
  "- Human-readable task docs live in this session folder.",
317
317
  "- Machine state lives in `.pi/takomi/orchestrator/<sessionId>.json`.",
318
318
  "- Sessions follow the Genesis -> Design -> Build lifecycle, but each stage may stay compact or expand into more tasks.",
319
- "- Rich runtime metadata such as conversation continuity, model overrides, and dispatch behavior remains in JSON rather than cluttering the markdown surface.",
319
+ "- Rich runtime metadata such as conversation continuity, model overrides, and execution hints remains in JSON rather than cluttering the markdown surface.",
320
320
  ].join("\n");
321
321
  }
322
322
 
@@ -130,11 +130,18 @@ export async function printTakomiUpdateStatus(currentVersion) {
130
130
 
131
131
  export function upgradeTakomiPackage() {
132
132
  console.log(pc.cyan('Updating Takomi from npm...\n'));
133
- const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
134
- const result = spawnSync(command, ['install', '-g', 'takomi@latest'], {
135
- stdio: 'inherit',
136
- shell: process.platform === 'win32',
137
- });
133
+
134
+ // Avoid `shell: true` here. Newer Node versions emit DEP0190 when args are
135
+ // passed alongside `shell: true`, because they are concatenated rather than
136
+ // escaped. On Windows, invoke the npm cmd shim through cmd.exe explicitly.
137
+ const result = process.platform === 'win32'
138
+ ? spawnSync('cmd.exe', ['/d', '/s', '/c', 'npm.cmd', 'install', '-g', 'takomi@latest'], {
139
+ stdio: 'inherit',
140
+ })
141
+ : spawnSync('npm', ['install', '-g', 'takomi@latest'], {
142
+ stdio: 'inherit',
143
+ });
144
+
138
145
  if (result.status === 0) {
139
146
  console.log(pc.green('\nTakomi updated. Run `takomi --version` to confirm.'));
140
147
  return 0;
@@ -1,62 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import type { TakomiDispatchInput } from "./dispatch";
4
-
5
- export function uniqueStrings(values: Array<string | undefined>): string[] {
6
- return [...new Set(values.filter((value): value is string => Boolean(value?.trim())))];
7
- }
8
-
9
- export function hasThinkingSuffix(model?: string): boolean {
10
- return /:(off|minimal|low|medium|high|xhigh)$/i.test(model ?? "");
11
- }
12
-
13
- export function buildFallbackModels(input: TakomiDispatchInput): string[] {
14
- return uniqueStrings([
15
- input.agent.model,
16
- ...(input.fallbackModels ?? []),
17
- ...(input.agent.fallbackModels ?? []),
18
- ]);
19
- }
20
-
21
- function buildPrimeStub(input: TakomiDispatchInput): string {
22
- const checklist = [
23
- "Prime context before work: inspect the repo docs and current task state.",
24
- "Follow the role prompt and assigned workflow.",
25
- "Use repo context as the source of truth; do not rely on optional skills as a prerequisite.",
26
- ];
27
- if (input.workflow) checklist.push(`This task uses the ${input.workflow} workflow.`);
28
- return [`Takomi subagent preflight:`, ...checklist].join("\n");
29
- }
30
-
31
- function stripPromptFrontmatter(content: string): string {
32
- return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
33
- }
34
-
35
- function workflowPromptFile(workflow?: string): string | undefined {
36
- if (workflow === "vibe-genesis") return "genesis-prompt.md";
37
- if (workflow === "vibe-design") return "design-prompt.md";
38
- if (workflow === "vibe-build") return "build-prompt.md";
39
- return undefined;
40
- }
41
-
42
- function loadWorkflowPrompt(input: TakomiDispatchInput): string | undefined {
43
- const fileName = workflowPromptFile(input.workflow);
44
- if (!fileName) return undefined;
45
- const promptPath = path.join(input.rootCwd, ".pi", "prompts", fileName);
46
- try {
47
- const prompt = stripPromptFrontmatter(fs.readFileSync(promptPath, "utf8"));
48
- return prompt ? [`Full assigned workflow loaded from ${path.relative(input.rootCwd, promptPath)}:`, prompt].join("\n\n") : undefined;
49
- } catch {
50
- return undefined;
51
- }
52
- }
53
-
54
- export function buildSystemPrompt(input: TakomiDispatchInput): string {
55
- return [
56
- input.agent.systemPrompt,
57
- buildPrimeStub(input),
58
- loadWorkflowPrompt(input) ?? (input.workflow ? `\nUse the ${input.workflow} workflow for this task.` : ""),
59
- input.skills?.length ? `\nOptional skill/context overlays for this task: ${input.skills.join(", ")}. Use them only if available and genuinely helpful; otherwise rely on the harness workflow and repo context.` : "",
60
- input.thinking ? `\nUse Pi thinking level '${input.thinking}' for this delegated run when the selected model supports it.` : "",
61
- ].filter(Boolean).join("\n");
62
- }
@@ -1,318 +0,0 @@
1
- import { mkdir } from "node:fs/promises";
2
- import path from "node:path";
3
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
4
- import type { TakomiThinkingLevel } from "../../../src/pi-takomi-core";
5
- import {
6
- buildTaskPrompt,
7
- runModelPreflight,
8
- runPiAgentJson,
9
- writeTempPrompt,
10
- type ChecklistInput,
11
- } from "../takomi-runtime/shared";
12
- import type {
13
- TakomiSubagentRuntimeEvent,
14
- TakomiSubagentRunPatch,
15
- } from "../takomi-runtime/subagent-types";
16
- import type { TakomiAgentConfig } from "./agents";
17
- import {
18
- buildFallbackModels,
19
- buildSystemPrompt,
20
- hasThinkingSuffix,
21
- } from "./dispatch-helpers";
22
-
23
- export type TakomiDispatchInput = {
24
- agent: TakomiAgentConfig;
25
- task: string;
26
- rootCwd: string;
27
- cwd?: string;
28
- workflow?: string;
29
- skills?: string[];
30
- model?: string;
31
- fallbackModels?: string[];
32
- thinking?: TakomiThinkingLevel;
33
- conversationId?: string;
34
- checklist?: ChecklistInput;
35
- stage?: string;
36
- taskLabel?: string;
37
- parentTaskId?: string;
38
- parentRunKey?: string;
39
- boardTaskStatus?: "pending" | "in-progress" | "completed" | "blocked";
40
- source: "runtime-board" | "takomi-tool";
41
- rerunInstructions?: string;
42
- };
43
-
44
- export type TakomiDispatchResult = {
45
- agent: string;
46
- task: string;
47
- workflow?: string;
48
- model?: string;
49
- warning?: string;
50
- thinking?: TakomiThinkingLevel;
51
- conversationId: string;
52
- code: number;
53
- output: string;
54
- stderr: string;
55
- preflight: string;
56
- startedAt?: number;
57
- endedAt?: number;
58
- lastActivityAt?: number;
59
- currentTool?: string;
60
- currentToolArgs?: string;
61
- currentToolStartedAt?: number;
62
- recentTools?: Array<{ tool: string; args: string; endMs: number }>;
63
- recentOutput?: string[];
64
- toolCount?: number;
65
- sessionFile?: string;
66
- };
67
-
68
- export type TakomiDispatchHooks = {
69
- emit?: (event: TakomiSubagentRuntimeEvent) => void;
70
- onPatch?: (patch: TakomiSubagentRunPatch, runKey: string) => void | Promise<void>;
71
- };
72
-
73
- export async function dispatchTakomiSubagent(
74
- ctx: ExtensionContext,
75
- input: TakomiDispatchInput,
76
- signal?: AbortSignal,
77
- hooks?: TakomiDispatchHooks,
78
- ): Promise<TakomiDispatchResult> {
79
- const subagentCwd = input.cwd ? path.resolve(input.rootCwd, input.cwd) : input.rootCwd;
80
- const conversationId = input.conversationId || `${input.agent.name}-${Date.now()}`;
81
- const runKey = conversationId;
82
- const sessionDir = path.join(input.rootCwd, ".pi", "takomi", "subagents");
83
- const sessionPath = path.join(sessionDir, `${conversationId}.jsonl`);
84
- await mkdir(sessionDir, { recursive: true });
85
- const startedAt = Date.now();
86
- let lastActivityAt = startedAt;
87
- let currentTool: string | undefined;
88
- let currentToolArgs: string | undefined;
89
- let currentToolStartedAt: number | undefined;
90
- let toolCount = 0;
91
- let recentTools: Array<{ tool: string; args: string; endMs: number }> = [];
92
- const activeToolInvocations = new Map<string, { tool: string; args: string; startedAt: number }>();
93
- let recentOutput: string[] = [];
94
-
95
- const setCurrentToolFromActive = () => {
96
- const latest = [...activeToolInvocations.values()].at(-1);
97
- currentTool = latest?.tool;
98
- currentToolArgs = latest?.args;
99
- currentToolStartedAt = latest?.startedAt;
100
- };
101
-
102
- const appendRecentOutput = (text: string) => {
103
- const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
104
- if (!lines.length) return;
105
- recentOutput.push(...lines);
106
- if (recentOutput.length > 8) recentOutput.splice(0, recentOutput.length - 8);
107
- lastActivityAt = Date.now();
108
- };
109
-
110
- hooks?.emit?.({
111
- type: "start",
112
- runKey,
113
- state: {
114
- agent: input.agent.name,
115
- taskLabel: input.taskLabel ?? input.task.split(/\r?\n/)[0]?.trim() ?? input.agent.name,
116
- workflow: input.workflow,
117
- stage: input.stage,
118
- conversationId,
119
- parentTaskId: input.parentTaskId,
120
- parentRunKey: input.parentRunKey,
121
- checklist: input.checklist,
122
- boardTaskStatus: input.boardTaskStatus,
123
- fallbackModels: input.fallbackModels,
124
- thinking: input.thinking ?? input.agent.thinking,
125
- summary: "Preparing delegated run.",
126
- source: input.source,
127
- },
128
- });
129
-
130
- const fallbackModels = buildFallbackModels(input);
131
- const preflight = await runModelPreflight(ctx, subagentCwd, input.model, fallbackModels, signal);
132
- const thinking = input.thinking ?? input.agent.thinking;
133
-
134
- if (preflight.model) {
135
- const patch = {
136
- model: preflight.model,
137
- fallbackModels,
138
- thinking,
139
- boardTaskStatus: input.boardTaskStatus,
140
- checklist: input.checklist,
141
- summary: `Model ready: ${preflight.model}${thinking ? ` (${thinking})` : ""}`,
142
- };
143
- hooks?.emit?.({ type: "update", runKey, patch });
144
- await hooks?.onPatch?.(patch, runKey);
145
- }
146
-
147
- if (!preflight.model) {
148
- const result = {
149
- agent: input.agent.name,
150
- task: input.task,
151
- workflow: input.workflow,
152
- model: "",
153
- warning: preflight.warning,
154
- thinking,
155
- conversationId,
156
- code: 1,
157
- output: "",
158
- stderr: preflight.report,
159
- preflight: preflight.report,
160
- startedAt,
161
- endedAt: Date.now(),
162
- lastActivityAt,
163
- recentTools,
164
- recentOutput,
165
- toolCount,
166
- sessionFile: sessionPath,
167
- };
168
- hooks?.emit?.({
169
- type: "block",
170
- runKey,
171
- patch: {
172
- summary: `Subagent ${input.agent.name} blocked before launch.`,
173
- boardTaskStatus: input.boardTaskStatus,
174
- checklist: input.checklist,
175
- fallbackModels,
176
- thinking,
177
- logs: [preflight.warning || "No model matched the requested run."],
178
- },
179
- });
180
- return result;
181
- }
182
-
183
- const promptPath = await writeTempPrompt(input.agent.name, buildSystemPrompt(input));
184
- const taskPrompt = buildTaskPrompt({
185
- task: input.rerunInstructions?.trim() || input.task,
186
- workflow: input.workflow,
187
- skills: input.skills,
188
- checklist: input.checklist,
189
- stage: input.stage,
190
- });
191
- const args = ["--mode", "json", "--append-system-prompt", promptPath, "--session", sessionPath, taskPrompt];
192
- args.unshift("--model", preflight.model);
193
- if (thinking && !hasThinkingSuffix(preflight.model)) args.unshift("--thinking", thinking);
194
- if (input.agent.tools?.length) args.unshift("--tools", input.agent.tools.join(","));
195
-
196
- const result = await runPiAgentJson(subagentCwd, args, signal, {
197
- onAssistantText: (text) => {
198
- appendRecentOutput(text);
199
- hooks?.emit?.({
200
- type: "update",
201
- runKey,
202
- patch: {
203
- outputText: text,
204
- recentOutput,
205
- currentTool,
206
- currentToolArgs,
207
- currentToolStartedAt,
208
- recentTools,
209
- toolCount,
210
- boardTaskStatus: input.boardTaskStatus,
211
- checklist: input.checklist,
212
- },
213
- });
214
- },
215
- onEventText: (line) => {
216
- appendRecentOutput(line);
217
- hooks?.emit?.({ type: "appendLog", runKey, chunk: line });
218
- hooks?.emit?.({ type: "update", runKey, patch: { recentOutput, boardTaskStatus: input.boardTaskStatus, checklist: input.checklist } });
219
- },
220
- onToolEvent: (event) => {
221
- lastActivityAt = Date.now();
222
- const invocationId = event.invocationId ?? event.toolName;
223
- if (event.type === "start") {
224
- activeToolInvocations.set(invocationId, {
225
- tool: event.toolName,
226
- args: event.args ?? "",
227
- startedAt: Date.now(),
228
- });
229
- toolCount += 1;
230
- setCurrentToolFromActive();
231
- } else if (event.type === "end") {
232
- const active = activeToolInvocations.get(invocationId);
233
- recentTools.push({ tool: event.toolName, args: active?.args ?? "", endMs: Date.now() });
234
- if (recentTools.length > 8) recentTools.splice(0, recentTools.length - 8);
235
- activeToolInvocations.delete(invocationId);
236
- setCurrentToolFromActive();
237
- }
238
- hooks?.emit?.({
239
- type: "update",
240
- runKey,
241
- patch: {
242
- currentTool,
243
- currentToolArgs,
244
- currentToolStartedAt,
245
- recentTools,
246
- recentOutput,
247
- toolCount,
248
- boardTaskStatus: input.boardTaskStatus,
249
- checklist: input.checklist,
250
- },
251
- });
252
- },
253
- onStderr: (chunk) => {
254
- appendRecentOutput(chunk);
255
- hooks?.emit?.({ type: "appendLog", runKey, chunk });
256
- hooks?.emit?.({ type: "update", runKey, patch: { recentOutput, boardTaskStatus: input.boardTaskStatus, checklist: input.checklist } });
257
- },
258
- });
259
-
260
- const output = result.stdout.trim();
261
- const dispatchResult: TakomiDispatchResult = {
262
- agent: input.agent.name,
263
- task: input.task,
264
- workflow: input.workflow,
265
- model: preflight.model,
266
- warning: preflight.warning,
267
- thinking,
268
- conversationId,
269
- code: result.code,
270
- output,
271
- stderr: result.stderr.trim(),
272
- preflight: preflight.report,
273
- startedAt,
274
- endedAt: Date.now(),
275
- lastActivityAt,
276
- currentTool,
277
- currentToolArgs,
278
- currentToolStartedAt,
279
- recentTools,
280
- recentOutput,
281
- toolCount,
282
- sessionFile: sessionPath,
283
- };
284
-
285
- if (result.code !== 0) {
286
- hooks?.emit?.({
287
- type: "block",
288
- runKey,
289
- patch: {
290
- model: preflight.model,
291
- fallbackModels,
292
- thinking,
293
- boardTaskStatus: input.boardTaskStatus,
294
- checklist: input.checklist,
295
- summary: `Subagent ${input.agent.name} failed.`,
296
- outputText: output || undefined,
297
- logs: [result.stderr || result.stdout || "No output"],
298
- },
299
- });
300
- return dispatchResult;
301
- }
302
-
303
- hooks?.emit?.({
304
- type: "complete",
305
- runKey,
306
- patch: {
307
- model: preflight.model,
308
- fallbackModels,
309
- thinking,
310
- boardTaskStatus: input.boardTaskStatus,
311
- checklist: input.checklist,
312
- summary: output || `Subagent ${input.agent.name} run finished. Checklist-validated task completion is still a board action.`,
313
- outputText: output || undefined,
314
- },
315
- });
316
-
317
- return dispatchResult;
318
- }