takomi 2.0.7 → 2.1.0
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 +124 -0
- package/.pi/agents/architect.md +16 -0
- package/.pi/agents/coder.md +15 -0
- package/.pi/agents/designer.md +18 -0
- package/.pi/agents/orchestrator.md +23 -0
- package/.pi/agents/reviewer.md +17 -0
- package/.pi/extensions/oauth-router/README.md +125 -0
- package/.pi/extensions/oauth-router/commands.ts +380 -0
- package/.pi/extensions/oauth-router/config.ts +200 -0
- package/.pi/extensions/oauth-router/index.ts +41 -0
- package/.pi/extensions/oauth-router/oauth-flow.ts +154 -0
- package/.pi/extensions/oauth-router/oauth-store.ts +121 -0
- package/.pi/extensions/oauth-router/package.json +14 -0
- package/.pi/extensions/oauth-router/policies.ts +27 -0
- package/.pi/extensions/oauth-router/provider.ts +492 -0
- package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -0
- package/.pi/extensions/oauth-router/state.ts +174 -0
- package/.pi/extensions/oauth-router/types.ts +153 -0
- package/.pi/extensions/takomi-runtime/command-text.ts +130 -0
- package/.pi/extensions/takomi-runtime/commands.ts +179 -0
- package/.pi/extensions/takomi-runtime/context-panel.ts +282 -0
- package/.pi/extensions/takomi-runtime/index.ts +1288 -0
- package/.pi/extensions/takomi-runtime/profile.ts +114 -0
- package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -0
- package/.pi/extensions/takomi-runtime/shared.ts +492 -0
- package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -0
- package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -0
- package/.pi/extensions/takomi-runtime/subagent-types.ts +83 -0
- package/.pi/extensions/takomi-runtime/ui.ts +133 -0
- package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -0
- package/.pi/extensions/takomi-subagents/agents.ts +113 -0
- package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -0
- package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -0
- package/.pi/extensions/takomi-subagents/dispatch.ts +215 -0
- package/.pi/extensions/takomi-subagents/index.ts +75 -0
- package/.pi/extensions/takomi-subagents/live-updates.ts +83 -0
- package/.pi/extensions/takomi-subagents/native-render.ts +174 -0
- package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -0
- package/.pi/prompts/build-prompt.md +199 -0
- package/.pi/prompts/design-prompt.md +134 -0
- package/.pi/prompts/genesis-prompt.md +133 -0
- package/.pi/prompts/orch-prompt.md +144 -0
- package/.pi/prompts/prime-prompt.md +80 -0
- package/.pi/prompts/takomi-prompt.md +96 -0
- package/.pi/prompts/vibe-primeAgent.md +97 -0
- package/.pi/prompts/vibe-spawnTask.md +133 -0
- package/.pi/prompts/vibe-syncDocs.md +100 -0
- package/.pi/themes/takomi-noir.json +81 -0
- package/README.md +28 -2
- package/assets/.agent/skills/pr-comment-fix/SKILL.md +182 -0
- package/assets/.agent/skills/takomi/SKILL.md +59 -59
- package/package.json +58 -45
- package/src/cli.js +158 -8
- package/src/doctor.js +84 -0
- package/src/pi-harness.js +351 -0
- package/src/pi-installer.js +171 -0
- package/src/pi-takomi-core/index.ts +4 -0
- package/src/pi-takomi-core/orchestration.ts +402 -0
- package/src/pi-takomi-core/routing.ts +93 -0
- package/src/pi-takomi-core/types.ts +173 -0
- package/src/pi-takomi-core/workflows.ts +299 -0
- package/src/skills-installer.js +101 -0
- package/src/utils.js +479 -447
- package/assets/.agent/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-311.pyc +0 -0
- package/assets/.agent/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-311.pyc +0 -0
- package/assets/.agent/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-311.pyc +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
|
2
|
+
import type { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import {
|
|
4
|
+
ellipsizeMiddle,
|
|
5
|
+
formatFooterNumber,
|
|
6
|
+
truncateToWidth,
|
|
7
|
+
visibleWidth,
|
|
8
|
+
} from "./shared";
|
|
9
|
+
|
|
10
|
+
interface Component {
|
|
11
|
+
render(width: number): string[];
|
|
12
|
+
invalidate(): void;
|
|
13
|
+
dispose?(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RenderTui {
|
|
17
|
+
requestRender(force?: boolean): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type RuntimeHudState = {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
autoOrch: boolean;
|
|
23
|
+
planMode: boolean;
|
|
24
|
+
role: string;
|
|
25
|
+
stage?: string;
|
|
26
|
+
workflow?: string;
|
|
27
|
+
activeSessionId?: string;
|
|
28
|
+
launchMode?: string;
|
|
29
|
+
subagentsEnabled?: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Tone = "accent" | "warning" | "success" | "error" | "muted" | "dim" | "thinkingMinimal";
|
|
33
|
+
|
|
34
|
+
function stageTone(stage?: string): Tone {
|
|
35
|
+
switch (stage) {
|
|
36
|
+
case "genesis":
|
|
37
|
+
return "thinkingMinimal";
|
|
38
|
+
case "design":
|
|
39
|
+
return "accent";
|
|
40
|
+
case "build":
|
|
41
|
+
return "warning";
|
|
42
|
+
default:
|
|
43
|
+
return "muted";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function badge(theme: Theme, label: string, tone: Tone): string {
|
|
48
|
+
return theme.fg(tone, `<${label.toUpperCase()}>`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function renderRuntimeStatus(theme: Theme, state: RuntimeHudState): string {
|
|
52
|
+
const primary = state.stage ?? state.role;
|
|
53
|
+
const stageBadge = badge(theme, primary, stageTone(state.stage));
|
|
54
|
+
const auto = state.autoOrch ? theme.fg("accent", "auto") : theme.fg("dim", "manual");
|
|
55
|
+
const gate = state.launchMode === "manual" ? theme.fg("warning", "review-gate") : theme.fg("accent", "auto-gate");
|
|
56
|
+
const plan = state.planMode ? theme.fg("warning", "plan") : theme.fg("dim", "direct");
|
|
57
|
+
const subagents = state.subagentsEnabled === false ? theme.fg("error", "subagents:off") : theme.fg("dim", "subagents:on");
|
|
58
|
+
return [theme.fg("accent", "Takomi"), stageBadge, theme.fg("dim", `role:${state.role}`), auto, gate, plan, subagents].join(" ");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function renderRuntimeWidget(theme: Theme, state: RuntimeHudState): string[] {
|
|
62
|
+
if (!state.enabled) return [];
|
|
63
|
+
const primary = state.stage ?? state.role;
|
|
64
|
+
const parts = [
|
|
65
|
+
theme.fg("accent", "Takomi"),
|
|
66
|
+
badge(theme, primary, stageTone(state.stage)),
|
|
67
|
+
theme.fg("dim", `role:${state.role}`),
|
|
68
|
+
state.autoOrch ? theme.fg("accent", "auto") : theme.fg("dim", "manual"),
|
|
69
|
+
state.launchMode === "manual" ? theme.fg("warning", "review-gate") : theme.fg("accent", "auto-gate"),
|
|
70
|
+
state.planMode ? theme.fg("warning", "plan") : theme.fg("dim", "direct"),
|
|
71
|
+
state.subagentsEnabled === false ? theme.fg("error", "subagents:off") : theme.fg("dim", "subagents:on"),
|
|
72
|
+
state.workflow ? theme.fg("dim", `wf:${state.workflow}`) : "",
|
|
73
|
+
state.activeSessionId ? theme.fg("dim", `session:${ellipsizeMiddle(state.activeSessionId, 12)}`) : "",
|
|
74
|
+
].filter(Boolean);
|
|
75
|
+
return [parts.join(" ")];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class TakomiFooterComponent implements Component {
|
|
79
|
+
private readonly unsubscribeBranchChange: () => void;
|
|
80
|
+
|
|
81
|
+
constructor(
|
|
82
|
+
private readonly tui: RenderTui,
|
|
83
|
+
private readonly theme: Theme,
|
|
84
|
+
private readonly footerData: ReadonlyFooterDataProvider,
|
|
85
|
+
private readonly ctx: ExtensionContext,
|
|
86
|
+
private readonly getState: () => RuntimeHudState,
|
|
87
|
+
) {
|
|
88
|
+
this.unsubscribeBranchChange = footerData.onBranchChange(() => {
|
|
89
|
+
this.tui.requestRender();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
dispose(): void {
|
|
94
|
+
this.unsubscribeBranchChange();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
invalidate(): void {}
|
|
98
|
+
|
|
99
|
+
render(width: number): string[] {
|
|
100
|
+
const state = this.getState();
|
|
101
|
+
let input = 0;
|
|
102
|
+
let output = 0;
|
|
103
|
+
let cost = 0;
|
|
104
|
+
|
|
105
|
+
for (const entry of this.ctx.sessionManager.getBranch()) {
|
|
106
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
107
|
+
const message = entry.message as AssistantMessage;
|
|
108
|
+
input += message.usage.input;
|
|
109
|
+
output += message.usage.output;
|
|
110
|
+
cost += message.usage.cost.total;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const cwd = this.theme.fg("dim", this.ctx.cwd);
|
|
115
|
+
const stats = this.theme.fg("dim", `up:${formatFooterNumber(input)} down:${formatFooterNumber(output)} $${cost.toFixed(3)}`);
|
|
116
|
+
const leftPad = " ".repeat(Math.max(1, width - visibleWidth(cwd) - visibleWidth(stats)));
|
|
117
|
+
const topLine = truncateToWidth(`${cwd}${leftPad}${stats}`, width);
|
|
118
|
+
|
|
119
|
+
const extensionStatuses = [...this.footerData.getExtensionStatuses().entries()]
|
|
120
|
+
.filter(([key]) => key !== "takomi-runtime")
|
|
121
|
+
.map(([, value]) => value)
|
|
122
|
+
.filter(Boolean);
|
|
123
|
+
const runtimeStatus = renderRuntimeStatus(this.theme, state);
|
|
124
|
+
const left = [runtimeStatus, ...extensionStatuses].join(this.theme.fg("dim", " | "));
|
|
125
|
+
const branch = this.footerData.getGitBranch();
|
|
126
|
+
const rightText = [this.ctx.model?.id || "no-model", branch ? `git:${branch}` : ""].filter(Boolean).join(" | ");
|
|
127
|
+
const right = this.theme.fg("dim", rightText);
|
|
128
|
+
const rightPad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right)));
|
|
129
|
+
const bottomLine = truncateToWidth(`${left}${rightPad}${right}`, width);
|
|
130
|
+
|
|
131
|
+
return [topLine, bottomLine];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TakomiAgentConfig } from "./agents";
|
|
2
|
+
|
|
3
|
+
const AGENT_ALIASES: Record<string, string[]> = {
|
|
4
|
+
general: ["orchestrator", "coder", "architect"],
|
|
5
|
+
code: ["coder"],
|
|
6
|
+
build: ["coder", "orchestrator"],
|
|
7
|
+
design: ["designer"],
|
|
8
|
+
review: ["reviewer"],
|
|
9
|
+
architecture: ["architect"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function resolveAgentName(name: string, agents: Map<string, TakomiAgentConfig>): string {
|
|
13
|
+
if (agents.has(name)) return name;
|
|
14
|
+
const lower = name.toLowerCase();
|
|
15
|
+
const exactCase = [...agents.keys()].find((agentName) => agentName.toLowerCase() === lower);
|
|
16
|
+
if (exactCase) return exactCase;
|
|
17
|
+
return AGENT_ALIASES[lower]?.find((candidate) => agents.has(candidate)) ?? name;
|
|
18
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import type { TakomiThinkingLevel } from "../../../src/pi-takomi-core";
|
|
5
|
+
|
|
6
|
+
export type TakomiAgentScope = "user" | "project" | "both";
|
|
7
|
+
|
|
8
|
+
export type TakomiAgentConfig = {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
tools?: string[];
|
|
12
|
+
model?: string;
|
|
13
|
+
fallbackModels?: string[];
|
|
14
|
+
thinking?: TakomiThinkingLevel;
|
|
15
|
+
systemPrompt: string;
|
|
16
|
+
filePath: string;
|
|
17
|
+
source: "user" | "project";
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function splitList(value?: string): string[] | undefined {
|
|
21
|
+
const parts = value
|
|
22
|
+
?.split(",")
|
|
23
|
+
.map((part) => part.trim())
|
|
24
|
+
.filter(Boolean);
|
|
25
|
+
return parts?.length ? parts : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeThinking(value?: string): TakomiThinkingLevel | undefined {
|
|
29
|
+
if (
|
|
30
|
+
value === "off"
|
|
31
|
+
|| value === "minimal"
|
|
32
|
+
|| value === "low"
|
|
33
|
+
|| value === "medium"
|
|
34
|
+
|| value === "high"
|
|
35
|
+
|| value === "xhigh"
|
|
36
|
+
) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadAgentsFromDirectory(agentsDir: string, source: "user" | "project"): TakomiAgentConfig[] {
|
|
43
|
+
if (!fs.existsSync(agentsDir)) return [];
|
|
44
|
+
|
|
45
|
+
const entries = fs.readdirSync(agentsDir, { withFileTypes: true });
|
|
46
|
+
const agents: TakomiAgentConfig[] = [];
|
|
47
|
+
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
50
|
+
const filePath = path.join(agentsDir, entry.name);
|
|
51
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
52
|
+
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
|
|
53
|
+
if (!frontmatter.name || !frontmatter.description) continue;
|
|
54
|
+
|
|
55
|
+
agents.push({
|
|
56
|
+
name: frontmatter.name,
|
|
57
|
+
description: frontmatter.description,
|
|
58
|
+
tools: splitList(frontmatter.tools),
|
|
59
|
+
model: frontmatter.model,
|
|
60
|
+
fallbackModels: splitList(frontmatter.fallbackModels ?? frontmatter.fallback_models),
|
|
61
|
+
thinking: normalizeThinking(frontmatter.thinking),
|
|
62
|
+
systemPrompt: body,
|
|
63
|
+
filePath,
|
|
64
|
+
source,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return agents;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isDirectory(target: string): boolean {
|
|
72
|
+
try {
|
|
73
|
+
return fs.statSync(target).isDirectory();
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findNearestProjectAgentsDirs(cwd: string): string[] {
|
|
80
|
+
let current = cwd;
|
|
81
|
+
while (true) {
|
|
82
|
+
const candidates = [
|
|
83
|
+
path.join(current, ".pi", "agents"),
|
|
84
|
+
path.join(current, ".agents"),
|
|
85
|
+
].filter(isDirectory);
|
|
86
|
+
if (candidates.length > 0) return candidates;
|
|
87
|
+
const parent = path.dirname(current);
|
|
88
|
+
if (parent === current) return [];
|
|
89
|
+
current = parent;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function discoverTakomiAgents(cwd: string, scope: TakomiAgentScope = "both"): TakomiAgentConfig[] {
|
|
94
|
+
const projectAgentsDirs = findNearestProjectAgentsDirs(cwd);
|
|
95
|
+
const localAgents = scope === "user"
|
|
96
|
+
? []
|
|
97
|
+
: projectAgentsDirs.flatMap((agentsDir) => loadAgentsFromDirectory(agentsDir, "project"));
|
|
98
|
+
const globalAgents = scope === "project" ? [] : loadAgentsFromDirectory(path.join(getAgentDir(), "agents"), "user");
|
|
99
|
+
const merged = new Map<string, TakomiAgentConfig>();
|
|
100
|
+
|
|
101
|
+
for (const agent of globalAgents) {
|
|
102
|
+
merged.set(agent.name, agent);
|
|
103
|
+
}
|
|
104
|
+
for (const agent of localAgents) {
|
|
105
|
+
merged.set(agent.name, agent);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return [...merged.values()];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function discoverProjectAgents(cwd: string): TakomiAgentConfig[] {
|
|
112
|
+
return discoverTakomiAgents(cwd, "both");
|
|
113
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TakomiDelegationPlan,
|
|
3
|
+
TakomiDelegationPlanTask,
|
|
4
|
+
TakomiDispatchPolicy,
|
|
5
|
+
TakomiLaunchMode,
|
|
6
|
+
TakomiProfile,
|
|
7
|
+
TakomiRole,
|
|
8
|
+
TakomiThinkingLevel,
|
|
9
|
+
TakomiWorkflowId,
|
|
10
|
+
VibeLifecycleStage,
|
|
11
|
+
} from "../../../src/pi-takomi-core";
|
|
12
|
+
import type { ChecklistInput } from "../takomi-runtime/shared";
|
|
13
|
+
|
|
14
|
+
type PlanTaskInput = {
|
|
15
|
+
id?: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
agent: string;
|
|
18
|
+
task: string;
|
|
19
|
+
role?: TakomiRole;
|
|
20
|
+
stage?: VibeLifecycleStage;
|
|
21
|
+
workflow?: TakomiWorkflowId | string;
|
|
22
|
+
model?: string;
|
|
23
|
+
fallbackModels?: string[];
|
|
24
|
+
thinking?: TakomiThinkingLevel;
|
|
25
|
+
conversationId?: string;
|
|
26
|
+
checklist?: ChecklistInput;
|
|
27
|
+
dispatchPolicy?: TakomiDispatchPolicy;
|
|
28
|
+
review?: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type PlanInput = {
|
|
32
|
+
source: TakomiDelegationPlan["source"];
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
launchMode: TakomiLaunchMode;
|
|
35
|
+
profile: TakomiProfile;
|
|
36
|
+
tasks: PlanTaskInput[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function normalizeChecklist(checklist?: ChecklistInput): TakomiDelegationPlanTask["checklist"] {
|
|
40
|
+
if (!checklist?.length) return undefined;
|
|
41
|
+
return checklist.map((item) => typeof item === "string" ? { text: item, done: false } : { text: item.text, done: item.done ?? false });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createTakomiDelegationPlan(input: PlanInput): TakomiDelegationPlan {
|
|
45
|
+
const reviewAfterImplementation = input.profile.reviewAfterImplementation ?? input.profile.review?.enabled ?? true;
|
|
46
|
+
return {
|
|
47
|
+
planId: `takomi-plan-${Date.now()}`,
|
|
48
|
+
source: input.source,
|
|
49
|
+
launchMode: input.launchMode,
|
|
50
|
+
placement: input.profile.background ? "background" : "foreground",
|
|
51
|
+
reviewAfterImplementation,
|
|
52
|
+
createdAt: new Date().toISOString(),
|
|
53
|
+
sessionId: input.sessionId,
|
|
54
|
+
tasks: input.tasks.map((task, index) => ({
|
|
55
|
+
id: task.id ?? `plan-task-${index + 1}`,
|
|
56
|
+
title: task.title ?? task.task.split(/\r?\n/).find(Boolean)?.slice(0, 80) ?? `Task ${index + 1}`,
|
|
57
|
+
agent: task.agent,
|
|
58
|
+
task: task.task,
|
|
59
|
+
role: task.role,
|
|
60
|
+
stage: task.stage,
|
|
61
|
+
workflow: task.workflow,
|
|
62
|
+
model: task.model,
|
|
63
|
+
fallbackModels: task.fallbackModels,
|
|
64
|
+
thinking: task.thinking,
|
|
65
|
+
conversationId: task.conversationId,
|
|
66
|
+
checklist: normalizeChecklist(task.checklist),
|
|
67
|
+
dispatchPolicy: task.dispatchPolicy,
|
|
68
|
+
review: task.review ?? reviewAfterImplementation,
|
|
69
|
+
status: "planned",
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function renderTakomiDelegationPlan(plan: TakomiDelegationPlan): string {
|
|
75
|
+
const lines = [
|
|
76
|
+
`Takomi delegation plan ${plan.planId}`,
|
|
77
|
+
`mode=${plan.launchMode} | placement=${plan.placement} | reviewAfterImplementation=${plan.reviewAfterImplementation ? "on" : "off"}`,
|
|
78
|
+
];
|
|
79
|
+
for (const task of plan.tasks) {
|
|
80
|
+
const checklist = task.checklist?.length ? ` | checklist=${task.checklist.filter((item) => item.done).length}/${task.checklist.length}` : "";
|
|
81
|
+
const fallback = task.fallbackModels?.length ? ` | fallbacks=${task.fallbackModels.length}` : "";
|
|
82
|
+
lines.push([
|
|
83
|
+
`${task.id}: ${task.agent}`,
|
|
84
|
+
task.model ? `model=${task.model}` : "model=default",
|
|
85
|
+
task.thinking ? `thinking=${task.thinking}` : "thinking=default",
|
|
86
|
+
task.workflow ? `workflow=${task.workflow}` : "",
|
|
87
|
+
task.review ? "review=on" : "review=off",
|
|
88
|
+
`task=${task.title}${fallback}${checklist}`,
|
|
89
|
+
].filter(Boolean).join(" | "));
|
|
90
|
+
}
|
|
91
|
+
if (plan.launchMode === "manual") {
|
|
92
|
+
lines.push("", "Manual launch mode: review or edit agent/task/model/thinking/review settings, then rerun with confirmLaunch=true. Use previewOnly=true to keep reviewing without launch.");
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TakomiDispatchInput } from "./dispatch";
|
|
2
|
+
|
|
3
|
+
export function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
4
|
+
return [...new Set(values.filter((value): value is string => Boolean(value?.trim())))];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function hasThinkingSuffix(model?: string): boolean {
|
|
8
|
+
return /:(off|minimal|low|medium|high|xhigh)$/i.test(model ?? "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildFallbackModels(input: TakomiDispatchInput): string[] {
|
|
12
|
+
return uniqueStrings([
|
|
13
|
+
input.agent.model,
|
|
14
|
+
...(input.fallbackModels ?? []),
|
|
15
|
+
...(input.agent.fallbackModels ?? []),
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildSystemPrompt(input: TakomiDispatchInput): string {
|
|
20
|
+
return [
|
|
21
|
+
input.agent.systemPrompt,
|
|
22
|
+
input.workflow ? `\nUse the ${input.workflow} workflow for this task.` : "",
|
|
23
|
+
input.skills?.length ? `\nUse these skills when relevant: ${input.skills.join(", ")}.` : "",
|
|
24
|
+
input.thinking ? `\nUse Pi thinking level '${input.thinking}' for this delegated run when the selected model supports it.` : "",
|
|
25
|
+
].filter(Boolean).join("\n");
|
|
26
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
};
|
|
57
|
+
|
|
58
|
+
export type TakomiDispatchHooks = {
|
|
59
|
+
emit?: (event: TakomiSubagentRuntimeEvent) => void;
|
|
60
|
+
onPatch?: (patch: TakomiSubagentRunPatch, runKey: string) => void | Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export async function dispatchTakomiSubagent(
|
|
64
|
+
ctx: ExtensionContext,
|
|
65
|
+
input: TakomiDispatchInput,
|
|
66
|
+
signal?: AbortSignal,
|
|
67
|
+
hooks?: TakomiDispatchHooks,
|
|
68
|
+
): Promise<TakomiDispatchResult> {
|
|
69
|
+
const subagentCwd = input.cwd ? path.resolve(input.rootCwd, input.cwd) : input.rootCwd;
|
|
70
|
+
const conversationId = input.conversationId || `${input.agent.name}-${Date.now()}`;
|
|
71
|
+
const runKey = conversationId;
|
|
72
|
+
const sessionDir = path.join(input.rootCwd, ".pi", "takomi", "subagents");
|
|
73
|
+
const sessionPath = path.join(sessionDir, `${conversationId}.jsonl`);
|
|
74
|
+
await mkdir(sessionDir, { recursive: true });
|
|
75
|
+
|
|
76
|
+
hooks?.emit?.({
|
|
77
|
+
type: "start",
|
|
78
|
+
runKey,
|
|
79
|
+
state: {
|
|
80
|
+
agent: input.agent.name,
|
|
81
|
+
taskLabel: input.taskLabel ?? input.task.split(/\r?\n/)[0]?.trim() ?? input.agent.name,
|
|
82
|
+
workflow: input.workflow,
|
|
83
|
+
stage: input.stage,
|
|
84
|
+
conversationId,
|
|
85
|
+
parentTaskId: input.parentTaskId,
|
|
86
|
+
parentRunKey: input.parentRunKey,
|
|
87
|
+
checklist: input.checklist,
|
|
88
|
+
boardTaskStatus: input.boardTaskStatus,
|
|
89
|
+
fallbackModels: input.fallbackModels,
|
|
90
|
+
thinking: input.thinking ?? input.agent.thinking,
|
|
91
|
+
summary: "Preparing delegated run.",
|
|
92
|
+
source: input.source,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const fallbackModels = buildFallbackModels(input);
|
|
97
|
+
const preflight = await runModelPreflight(ctx, subagentCwd, input.model, fallbackModels, signal);
|
|
98
|
+
const thinking = input.thinking ?? input.agent.thinking;
|
|
99
|
+
|
|
100
|
+
if (preflight.model) {
|
|
101
|
+
const patch = {
|
|
102
|
+
model: preflight.model,
|
|
103
|
+
fallbackModels,
|
|
104
|
+
thinking,
|
|
105
|
+
boardTaskStatus: input.boardTaskStatus,
|
|
106
|
+
checklist: input.checklist,
|
|
107
|
+
summary: `Model ready: ${preflight.model}${thinking ? ` (${thinking})` : ""}`,
|
|
108
|
+
};
|
|
109
|
+
hooks?.emit?.({ type: "update", runKey, patch });
|
|
110
|
+
await hooks?.onPatch?.(patch, runKey);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!preflight.model) {
|
|
114
|
+
const result = {
|
|
115
|
+
agent: input.agent.name,
|
|
116
|
+
task: input.task,
|
|
117
|
+
workflow: input.workflow,
|
|
118
|
+
model: "",
|
|
119
|
+
warning: preflight.warning,
|
|
120
|
+
thinking,
|
|
121
|
+
conversationId,
|
|
122
|
+
code: 1,
|
|
123
|
+
output: "",
|
|
124
|
+
stderr: preflight.report,
|
|
125
|
+
preflight: preflight.report,
|
|
126
|
+
};
|
|
127
|
+
hooks?.emit?.({
|
|
128
|
+
type: "block",
|
|
129
|
+
runKey,
|
|
130
|
+
patch: {
|
|
131
|
+
summary: `Subagent ${input.agent.name} blocked before launch.`,
|
|
132
|
+
boardTaskStatus: input.boardTaskStatus,
|
|
133
|
+
checklist: input.checklist,
|
|
134
|
+
fallbackModels,
|
|
135
|
+
thinking,
|
|
136
|
+
logs: [preflight.warning || "No model matched the requested run."],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const promptPath = await writeTempPrompt(input.agent.name, buildSystemPrompt(input));
|
|
143
|
+
const taskPrompt = buildTaskPrompt({
|
|
144
|
+
task: input.rerunInstructions?.trim() || input.task,
|
|
145
|
+
workflow: input.workflow,
|
|
146
|
+
skills: input.skills,
|
|
147
|
+
checklist: input.checklist,
|
|
148
|
+
stage: input.stage,
|
|
149
|
+
});
|
|
150
|
+
const args = ["--mode", "json", "--append-system-prompt", promptPath, "--session", sessionPath, taskPrompt];
|
|
151
|
+
args.unshift("--model", preflight.model);
|
|
152
|
+
if (thinking && !hasThinkingSuffix(preflight.model)) args.unshift("--thinking", thinking);
|
|
153
|
+
if (input.agent.tools?.length) args.unshift("--tools", input.agent.tools.join(","));
|
|
154
|
+
|
|
155
|
+
const result = await runPiAgentJson(subagentCwd, args, signal, {
|
|
156
|
+
onAssistantText: (text) => {
|
|
157
|
+
hooks?.emit?.({ type: "update", runKey, patch: { outputText: text, boardTaskStatus: input.boardTaskStatus, checklist: input.checklist } });
|
|
158
|
+
},
|
|
159
|
+
onEventText: (line) => {
|
|
160
|
+
hooks?.emit?.({ type: "appendLog", runKey, chunk: line });
|
|
161
|
+
},
|
|
162
|
+
onStderr: (chunk) => {
|
|
163
|
+
hooks?.emit?.({ type: "appendLog", runKey, chunk });
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const output = result.stdout.trim();
|
|
168
|
+
const dispatchResult: TakomiDispatchResult = {
|
|
169
|
+
agent: input.agent.name,
|
|
170
|
+
task: input.task,
|
|
171
|
+
workflow: input.workflow,
|
|
172
|
+
model: preflight.model,
|
|
173
|
+
warning: preflight.warning,
|
|
174
|
+
thinking,
|
|
175
|
+
conversationId,
|
|
176
|
+
code: result.code,
|
|
177
|
+
output,
|
|
178
|
+
stderr: result.stderr.trim(),
|
|
179
|
+
preflight: preflight.report,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (result.code !== 0) {
|
|
183
|
+
hooks?.emit?.({
|
|
184
|
+
type: "block",
|
|
185
|
+
runKey,
|
|
186
|
+
patch: {
|
|
187
|
+
model: preflight.model,
|
|
188
|
+
fallbackModels,
|
|
189
|
+
thinking,
|
|
190
|
+
boardTaskStatus: input.boardTaskStatus,
|
|
191
|
+
checklist: input.checklist,
|
|
192
|
+
summary: `Subagent ${input.agent.name} failed.`,
|
|
193
|
+
outputText: output || undefined,
|
|
194
|
+
logs: [result.stderr || result.stdout || "No output"],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
return dispatchResult;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
hooks?.emit?.({
|
|
201
|
+
type: "complete",
|
|
202
|
+
runKey,
|
|
203
|
+
patch: {
|
|
204
|
+
model: preflight.model,
|
|
205
|
+
fallbackModels,
|
|
206
|
+
thinking,
|
|
207
|
+
boardTaskStatus: input.boardTaskStatus,
|
|
208
|
+
checklist: input.checklist,
|
|
209
|
+
summary: output || `Subagent ${input.agent.name} run finished. Checklist-validated task completion is still a board action.`,
|
|
210
|
+
outputText: output || undefined,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return dispatchResult;
|
|
215
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { renderTakomiSubagentCall, renderTakomiSubagentResult } from "./native-render";
|
|
4
|
+
import { executeTakomiSubagentTool } from "./tool-runner";
|
|
5
|
+
|
|
6
|
+
const ChecklistItemSchema = Type.Object({
|
|
7
|
+
text: Type.String(),
|
|
8
|
+
done: Type.Optional(Type.Boolean()),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const ThinkingSchema = Type.Union([
|
|
12
|
+
Type.Literal("off"),
|
|
13
|
+
Type.Literal("minimal"),
|
|
14
|
+
Type.Literal("low"),
|
|
15
|
+
Type.Literal("medium"),
|
|
16
|
+
Type.Literal("high"),
|
|
17
|
+
Type.Literal("xhigh"),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const TaskSchema = Type.Object({
|
|
21
|
+
agent: Type.String(),
|
|
22
|
+
task: Type.String(),
|
|
23
|
+
workflow: Type.Optional(Type.String()),
|
|
24
|
+
skills: Type.Optional(Type.Array(Type.String())),
|
|
25
|
+
model: Type.Optional(Type.String()),
|
|
26
|
+
fallbackModels: Type.Optional(Type.Array(Type.String())),
|
|
27
|
+
thinking: Type.Optional(ThinkingSchema),
|
|
28
|
+
conversationId: Type.Optional(Type.String()),
|
|
29
|
+
cwd: Type.Optional(Type.String()),
|
|
30
|
+
checklist: Type.Optional(Type.Array(Type.Union([Type.String(), ChecklistItemSchema]))),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const SubagentParameters = Type.Object({
|
|
34
|
+
agent: Type.Optional(Type.String({ description: "Agent name for single execution" })),
|
|
35
|
+
task: Type.Optional(Type.String({ description: "Task for single execution" })),
|
|
36
|
+
workflow: Type.Optional(Type.String({ description: "Workflow or playbook overlay for this task" })),
|
|
37
|
+
skills: Type.Optional(Type.Array(Type.String(), { description: "Extra skills to apply during the task" })),
|
|
38
|
+
model: Type.Optional(Type.String({ description: "Optional per-run model override" })),
|
|
39
|
+
fallbackModels: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered model fallback list" })),
|
|
40
|
+
thinking: Type.Optional(ThinkingSchema),
|
|
41
|
+
conversationId: Type.Optional(Type.String({ description: "Persistent conversation id to resume the same subagent session" })),
|
|
42
|
+
cwd: Type.Optional(Type.String({ description: "Working directory override" })),
|
|
43
|
+
checklist: Type.Optional(Type.Array(Type.Union([Type.String(), ChecklistItemSchema]), { description: "Optional checklist for the subagent" })),
|
|
44
|
+
tasks: Type.Optional(Type.Array(TaskSchema, { description: "Parallel subagent tasks" })),
|
|
45
|
+
confirmLaunch: Type.Optional(Type.Boolean({ description: "Required to launch immediately in manual Takomi launch mode" })),
|
|
46
|
+
previewOnly: Type.Optional(Type.Boolean({ description: "Return the delegation plan without launching" })),
|
|
47
|
+
chain: Type.Optional(Type.Array(TaskSchema, { description: "Sequential chain of subagent tasks" })),
|
|
48
|
+
agentScope: Type.Optional(Type.Union([Type.Literal("user"), Type.Literal("project"), Type.Literal("both")])),
|
|
49
|
+
confirmProjectAgents: Type.Optional(Type.Boolean({ description: "Prompt before running project-local agents. Default: true." })),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function registerSubagentTool(pi: ExtensionAPI): void {
|
|
53
|
+
pi.registerTool({
|
|
54
|
+
name: "takomi_subagent",
|
|
55
|
+
label: "Takomi",
|
|
56
|
+
description: "Run subagents with Pi-style single, parallel, or chain modes plus Takomi lifecycle metadata.",
|
|
57
|
+
promptSnippet: "Delegate lifecycle-aware Takomi work to specialist subagents. Use single, tasks, or chain; reuse conversationId for review loops.",
|
|
58
|
+
promptGuidelines: [
|
|
59
|
+
"Use this tool during orchestration when a specialist should handle a task.",
|
|
60
|
+
"Use tasks for independent parallel work and chain for dependent handoffs with {previous}.",
|
|
61
|
+
"Use model, fallbackModels, and thinking only when deliberate; otherwise let the agent/profile defaults apply.",
|
|
62
|
+
"If review sends work back to the same agent, reuse the same conversationId for continuity.",
|
|
63
|
+
],
|
|
64
|
+
parameters: SubagentParameters,
|
|
65
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
66
|
+
return executeTakomiSubagentTool(pi, params, signal, onUpdate, ctx);
|
|
67
|
+
},
|
|
68
|
+
renderCall: renderTakomiSubagentCall,
|
|
69
|
+
renderResult: renderTakomiSubagentResult,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default function takomiSubagents(pi: ExtensionAPI) {
|
|
74
|
+
registerSubagentTool(pi);
|
|
75
|
+
}
|