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 +10 -12
- package/.pi/agents/orchestrator.md +8 -2
- package/.pi/extensions/takomi-runtime/index.ts +23 -270
- package/.pi/extensions/takomi-subagents/live-updates.ts +1 -1
- package/.pi/extensions/takomi-subagents/run-types.ts +25 -0
- package/.pi/extensions/takomi-subagents/tool-runner.ts +21 -11
- package/.pi/prompts/takomi-prompt.md +3 -0
- package/package.json +1 -1
- package/src/pi-takomi-core/orchestration.ts +1 -1
- package/src/update-check.js +12 -5
- package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +0 -62
- package/.pi/extensions/takomi-subagents/dispatch.ts +0 -318
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`, `
|
|
107
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
131
|
-
|
|
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
|
|
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 ? ` |
|
|
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
|
-
|
|
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: "
|
|
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
|
-
"
|
|
787
|
-
"
|
|
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,
|
|
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"
|
|
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
|
-
|
|
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: [{
|
|
870
|
-
|
|
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
|
|
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,
|
|
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 "./
|
|
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 {
|
|
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
|
|
165
|
-
agent:
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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.
|
|
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
|
|
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
|
|
package/src/update-check.js
CHANGED
|
@@ -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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
}
|