takomi 2.0.6 → 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.
Files changed (76) hide show
  1. package/.pi/README.md +124 -0
  2. package/.pi/agents/architect.md +16 -0
  3. package/.pi/agents/coder.md +15 -0
  4. package/.pi/agents/designer.md +18 -0
  5. package/.pi/agents/orchestrator.md +23 -0
  6. package/.pi/agents/reviewer.md +17 -0
  7. package/.pi/extensions/oauth-router/README.md +125 -0
  8. package/.pi/extensions/oauth-router/commands.ts +380 -0
  9. package/.pi/extensions/oauth-router/config.ts +200 -0
  10. package/.pi/extensions/oauth-router/index.ts +41 -0
  11. package/.pi/extensions/oauth-router/oauth-flow.ts +154 -0
  12. package/.pi/extensions/oauth-router/oauth-store.ts +121 -0
  13. package/.pi/extensions/oauth-router/package.json +14 -0
  14. package/.pi/extensions/oauth-router/policies.ts +27 -0
  15. package/.pi/extensions/oauth-router/provider.ts +492 -0
  16. package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -0
  17. package/.pi/extensions/oauth-router/state.ts +174 -0
  18. package/.pi/extensions/oauth-router/types.ts +153 -0
  19. package/.pi/extensions/takomi-runtime/command-text.ts +130 -0
  20. package/.pi/extensions/takomi-runtime/commands.ts +179 -0
  21. package/.pi/extensions/takomi-runtime/context-panel.ts +282 -0
  22. package/.pi/extensions/takomi-runtime/index.ts +1288 -0
  23. package/.pi/extensions/takomi-runtime/profile.ts +114 -0
  24. package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -0
  25. package/.pi/extensions/takomi-runtime/shared.ts +492 -0
  26. package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -0
  27. package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -0
  28. package/.pi/extensions/takomi-runtime/subagent-types.ts +83 -0
  29. package/.pi/extensions/takomi-runtime/ui.ts +133 -0
  30. package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -0
  31. package/.pi/extensions/takomi-subagents/agents.ts +113 -0
  32. package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -0
  33. package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -0
  34. package/.pi/extensions/takomi-subagents/dispatch.ts +215 -0
  35. package/.pi/extensions/takomi-subagents/index.ts +75 -0
  36. package/.pi/extensions/takomi-subagents/live-updates.ts +83 -0
  37. package/.pi/extensions/takomi-subagents/native-render.ts +174 -0
  38. package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -0
  39. package/.pi/prompts/build-prompt.md +199 -0
  40. package/.pi/prompts/design-prompt.md +134 -0
  41. package/.pi/prompts/genesis-prompt.md +133 -0
  42. package/.pi/prompts/orch-prompt.md +144 -0
  43. package/.pi/prompts/prime-prompt.md +80 -0
  44. package/.pi/prompts/takomi-prompt.md +96 -0
  45. package/.pi/prompts/vibe-primeAgent.md +97 -0
  46. package/.pi/prompts/vibe-spawnTask.md +133 -0
  47. package/.pi/prompts/vibe-syncDocs.md +100 -0
  48. package/.pi/themes/takomi-noir.json +81 -0
  49. package/README.md +61 -27
  50. package/assets/.agent/skills/21st-dev-components/21st-handoff.md +146 -0
  51. package/assets/.agent/skills/21st-dev-components/SKILL.md +198 -0
  52. package/assets/.agent/skills/21st-dev-components/references/categories.md +91 -0
  53. package/assets/.agent/skills/21st-dev-components/references/manual-handoff-template.md +79 -0
  54. package/assets/.agent/skills/21st-dev-components/references/section-detection-rubric.md +59 -0
  55. package/assets/.agent/skills/21st-dev-components/scripts/_shared.mjs +304 -0
  56. package/assets/.agent/skills/21st-dev-components/scripts/build-manual-handoff-template.mjs +115 -0
  57. package/assets/.agent/skills/21st-dev-components/scripts/fetch-21st-source.mjs +65 -0
  58. package/assets/.agent/skills/21st-dev-components/scripts/resolve-21st-component.mjs +115 -0
  59. package/assets/.agent/skills/pr-comment-fix/SKILL.md +182 -0
  60. package/assets/.agent/skills/takomi/SKILL.md +59 -59
  61. package/package.json +58 -41
  62. package/src/cli.js +165 -15
  63. package/src/doctor.js +84 -0
  64. package/src/harness.js +13 -0
  65. package/src/pi-harness.js +351 -0
  66. package/src/pi-installer.js +171 -0
  67. package/src/pi-takomi-core/index.ts +4 -0
  68. package/src/pi-takomi-core/orchestration.ts +402 -0
  69. package/src/pi-takomi-core/routing.ts +93 -0
  70. package/src/pi-takomi-core/types.ts +173 -0
  71. package/src/pi-takomi-core/workflows.ts +299 -0
  72. package/src/skills-installer.js +101 -0
  73. package/src/utils.js +479 -447
  74. package/assets/.agent/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-311.pyc +0 -0
  75. package/assets/.agent/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-311.pyc +0 -0
  76. package/assets/.agent/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-311.pyc +0 -0
@@ -0,0 +1,174 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { STATE_PATH, writeJsonFile } from "./config.ts";
3
+ import type { RouterAccountState, RouterRuntimeState, RoutingPolicyName } from "./types.ts";
4
+
5
+ const DEFAULT_STATE: RouterRuntimeState = {
6
+ version: 1,
7
+ policy: "round-robin",
8
+ rrCursor: 0,
9
+ weightedCursor: 0,
10
+ accounts: {},
11
+ };
12
+
13
+ function normalizeAccountState(accountId: string, input?: Partial<RouterAccountState>): RouterAccountState {
14
+ return {
15
+ accountId,
16
+ authHealth: input?.authHealth === "invalid" ? "invalid" : "ok",
17
+ cooldownUntil: Number.isFinite(input?.cooldownUntil) ? input?.cooldownUntil : undefined,
18
+ penaltyUntil: Number.isFinite(input?.penaltyUntil) ? input?.penaltyUntil : undefined,
19
+ lastUsedAt: Number.isFinite(input?.lastUsedAt) ? input?.lastUsedAt : undefined,
20
+ lastTriedAt: Number.isFinite(input?.lastTriedAt) ? input?.lastTriedAt : undefined,
21
+ lastModel: typeof input?.lastModel === "string" ? input.lastModel : undefined,
22
+ lastStatus: Number.isFinite(input?.lastStatus) ? input?.lastStatus : undefined,
23
+ lastError: typeof input?.lastError === "string" ? input.lastError : undefined,
24
+ failures: Number.isFinite(input?.failures) ? input!.failures! : 0,
25
+ rateLimitCount: Number.isFinite(input?.rateLimitCount) ? input!.rateLimitCount! : 0,
26
+ authFailureCount: Number.isFinite(input?.authFailureCount) ? input!.authFailureCount! : 0,
27
+ successCount: Number.isFinite(input?.successCount) ? input!.successCount! : 0,
28
+ };
29
+ }
30
+
31
+ export class RouterStateStore {
32
+ private data: RouterRuntimeState;
33
+
34
+ constructor(initialPolicy: RoutingPolicyName) {
35
+ this.data = this.load(initialPolicy);
36
+ }
37
+
38
+ private load(initialPolicy: RoutingPolicyName): RouterRuntimeState {
39
+ if (!existsSync(STATE_PATH)) {
40
+ const initial = { ...DEFAULT_STATE, policy: initialPolicy } satisfies RouterRuntimeState;
41
+ writeJsonFile(STATE_PATH, initial, true);
42
+ return initial;
43
+ }
44
+
45
+ try {
46
+ const parsed = JSON.parse(readFileSync(STATE_PATH, "utf8")) as Partial<RouterRuntimeState>;
47
+ const accounts = Object.fromEntries(
48
+ Object.entries(parsed.accounts ?? {}).map(([accountId, state]) => [accountId, normalizeAccountState(accountId, state)]),
49
+ );
50
+ return {
51
+ version: 1,
52
+ policy:
53
+ parsed.policy === "weighted-round-robin" || parsed.policy === "round-robin" ? parsed.policy : initialPolicy,
54
+ rrCursor: Number.isFinite(parsed.rrCursor) ? parsed.rrCursor : 0,
55
+ weightedCursor: Number.isFinite(parsed.weightedCursor) ? parsed.weightedCursor : 0,
56
+ accounts,
57
+ };
58
+ } catch {
59
+ const initial = { ...DEFAULT_STATE, policy: initialPolicy } satisfies RouterRuntimeState;
60
+ writeJsonFile(STATE_PATH, initial, true);
61
+ return initial;
62
+ }
63
+ }
64
+
65
+ private save() {
66
+ writeJsonFile(STATE_PATH, this.data, true);
67
+ }
68
+
69
+ snapshot(): RouterRuntimeState {
70
+ return JSON.parse(JSON.stringify(this.data)) as RouterRuntimeState;
71
+ }
72
+
73
+ getPolicy(defaultPolicy: RoutingPolicyName): RoutingPolicyName {
74
+ if (this.data.policy !== "round-robin" && this.data.policy !== "weighted-round-robin") {
75
+ this.data.policy = defaultPolicy;
76
+ this.save();
77
+ }
78
+ return this.data.policy;
79
+ }
80
+
81
+ setPolicy(policy: RoutingPolicyName) {
82
+ this.data.policy = policy;
83
+ this.save();
84
+ }
85
+
86
+ getCursor(policy: RoutingPolicyName): number {
87
+ return policy === "weighted-round-robin" ? this.data.weightedCursor : this.data.rrCursor;
88
+ }
89
+
90
+ advanceCursor(policy: RoutingPolicyName, next: number) {
91
+ if (policy === "weighted-round-robin") {
92
+ this.data.weightedCursor = next;
93
+ } else {
94
+ this.data.rrCursor = next;
95
+ }
96
+ this.save();
97
+ }
98
+
99
+ ensureAccount(accountId: string): RouterAccountState {
100
+ if (!this.data.accounts[accountId]) {
101
+ this.data.accounts[accountId] = normalizeAccountState(accountId);
102
+ this.save();
103
+ }
104
+ return this.data.accounts[accountId]!;
105
+ }
106
+
107
+ pruneAccountIds(validIds: string[]) {
108
+ const valid = new Set(validIds);
109
+ for (const id of Object.keys(this.data.accounts)) {
110
+ if (!valid.has(id)) delete this.data.accounts[id];
111
+ }
112
+ this.save();
113
+ }
114
+
115
+ markAttempt(accountId: string, modelId: string) {
116
+ const account = this.ensureAccount(accountId);
117
+ account.lastTriedAt = Date.now();
118
+ account.lastModel = modelId;
119
+ this.save();
120
+ }
121
+
122
+ markSuccess(accountId: string, status?: number) {
123
+ const account = this.ensureAccount(accountId);
124
+ account.authHealth = "ok";
125
+ account.lastUsedAt = Date.now();
126
+ account.lastStatus = status;
127
+ account.lastError = undefined;
128
+ account.penaltyUntil = undefined;
129
+ account.failures = 0;
130
+ account.successCount += 1;
131
+ this.save();
132
+ }
133
+
134
+ markRateLimit(accountId: string, retryAfterMs: number, status = 429, message = "Rate limited") {
135
+ const account = this.ensureAccount(accountId);
136
+ const now = Date.now();
137
+ account.lastStatus = status;
138
+ account.lastError = message;
139
+ account.cooldownUntil = now + Math.max(1_000, retryAfterMs);
140
+ account.failures += 1;
141
+ account.rateLimitCount += 1;
142
+ this.save();
143
+ }
144
+
145
+ markAuthFailure(accountId: string, status = 401, message = "Authentication failed") {
146
+ const account = this.ensureAccount(accountId);
147
+ account.authHealth = "invalid";
148
+ account.lastStatus = status;
149
+ account.lastError = message;
150
+ account.failures += 1;
151
+ account.authFailureCount += 1;
152
+ account.cooldownUntil = undefined;
153
+ account.penaltyUntil = undefined;
154
+ this.save();
155
+ }
156
+
157
+ markTransientFailure(accountId: string, penaltyMs: number, status = 500, message = "Transient upstream failure") {
158
+ const account = this.ensureAccount(accountId);
159
+ account.lastStatus = status;
160
+ account.lastError = message;
161
+ account.penaltyUntil = Date.now() + Math.max(1_000, penaltyMs);
162
+ account.failures += 1;
163
+ this.save();
164
+ }
165
+
166
+ clearHealth(accountId: string) {
167
+ const account = this.ensureAccount(accountId);
168
+ account.authHealth = "ok";
169
+ account.cooldownUntil = undefined;
170
+ account.penaltyUntil = undefined;
171
+ account.lastError = undefined;
172
+ this.save();
173
+ }
174
+ }
@@ -0,0 +1,153 @@
1
+ import type { Api, AssistantMessage, Context, Model, SimpleStreamOptions } from "@mariozechner/pi-ai";
2
+
3
+ export type RouterUpstreamApi = "openai-completions" | "openai-responses" | "openai-codex-responses";
4
+ export type RouterAuthMode = "oauth" | "api-key";
5
+ export type RoutingPolicyName = "round-robin" | "weighted-round-robin";
6
+ export type AuthHealth = "ok" | "invalid";
7
+
8
+ export interface RouterModelConfig {
9
+ id: string;
10
+ name: string;
11
+ reasoning: boolean;
12
+ input: Array<"text" | "image">;
13
+ cost: {
14
+ input: number;
15
+ output: number;
16
+ cacheRead: number;
17
+ cacheWrite: number;
18
+ };
19
+ contextWindow: number;
20
+ maxTokens: number;
21
+ compat?: Record<string, unknown>;
22
+ route?: {
23
+ upstreamIds?: string[];
24
+ };
25
+ }
26
+
27
+ export interface RouterUpstreamConfig {
28
+ id: string;
29
+ label: string;
30
+ description?: string;
31
+ baseUrl: string;
32
+ api: RouterUpstreamApi;
33
+ authMode: RouterAuthMode;
34
+ oauthProviderId?: string;
35
+ enabled: boolean;
36
+ modelIds: string[];
37
+ headers?: Record<string, string>;
38
+ }
39
+
40
+ export interface RouterConfig {
41
+ version: 1;
42
+ providerName: "oauth-router";
43
+ policy: RoutingPolicyName;
44
+ tokenRefreshSkewMs: number;
45
+ rateLimitCooldownMs: number;
46
+ transientPenaltyMs: number;
47
+ models: RouterModelConfig[];
48
+ upstreams: RouterUpstreamConfig[];
49
+ }
50
+
51
+ export interface StoredRouterAccount {
52
+ id: string;
53
+ label: string;
54
+ provider: string;
55
+ upstreamId: string;
56
+ access: string;
57
+ refresh: string;
58
+ expires: number;
59
+ enabled: boolean;
60
+ weight: number;
61
+ createdAt: number;
62
+ updatedAt: number;
63
+ meta?: Record<string, unknown>;
64
+ }
65
+
66
+ export interface RouterCredentialStore {
67
+ version: 1;
68
+ accounts: StoredRouterAccount[];
69
+ }
70
+
71
+ export interface RouterAccountState {
72
+ accountId: string;
73
+ authHealth: AuthHealth;
74
+ cooldownUntil?: number;
75
+ penaltyUntil?: number;
76
+ lastUsedAt?: number;
77
+ lastTriedAt?: number;
78
+ lastModel?: string;
79
+ lastStatus?: number;
80
+ lastError?: string;
81
+ failures: number;
82
+ rateLimitCount: number;
83
+ authFailureCount: number;
84
+ successCount: number;
85
+ }
86
+
87
+ export interface RouterRuntimeState {
88
+ version: 1;
89
+ policy: RoutingPolicyName;
90
+ rrCursor: number;
91
+ weightedCursor: number;
92
+ accounts: Record<string, RouterAccountState>;
93
+ }
94
+
95
+ export interface RouterStatusRow {
96
+ id: string;
97
+ label: string;
98
+ upstream: string;
99
+ provider: string;
100
+ enabled: boolean;
101
+ weight: number;
102
+ authHealth: AuthHealth;
103
+ cooldownUntil?: number;
104
+ penaltyUntil?: number;
105
+ lastUsedAt?: number;
106
+ lastStatus?: number;
107
+ failures: number;
108
+ rateLimitCount: number;
109
+ authFailureCount: number;
110
+ successCount: number;
111
+ expires: number;
112
+ }
113
+
114
+ export interface EligibleAccount {
115
+ account: StoredRouterAccount;
116
+ upstream: RouterUpstreamConfig;
117
+ modelConfig: RouterModelConfig;
118
+ state: RouterAccountState;
119
+ }
120
+
121
+ export interface DelegateSelection {
122
+ account: StoredRouterAccount;
123
+ upstream: RouterUpstreamConfig;
124
+ modelConfig: RouterModelConfig;
125
+ apiKey: string;
126
+ headers: Record<string, string>;
127
+ delegatedModel: Model<Api>;
128
+ }
129
+
130
+ export interface FailureClassification {
131
+ kind: "rate-limit" | "auth" | "transient" | "fatal";
132
+ status?: number;
133
+ retryAfterMs?: number;
134
+ message: string;
135
+ }
136
+
137
+ export interface StreamAttemptResult {
138
+ completed: boolean;
139
+ emittedMeaningfulOutput: boolean;
140
+ failure?: FailureClassification;
141
+ }
142
+
143
+ export interface RouterStreamInput {
144
+ model: Model<Api>;
145
+ context: Context;
146
+ options?: SimpleStreamOptions;
147
+ }
148
+
149
+ export interface RouterErrorMessageInput {
150
+ model: Model<Api>;
151
+ message: string;
152
+ stopReason?: AssistantMessage["stopReason"];
153
+ }
@@ -0,0 +1,130 @@
1
+ import type { VibeLifecycleStage } from "../../../src/pi-takomi-core";
2
+ import type { TakomiRuntimeCommandState } from "./commands";
3
+ import type { TakomiSubagentController } from "./subagent-types";
4
+
5
+ export type TakomiCompletion = {
6
+ value: string;
7
+ label: string;
8
+ description: string;
9
+ };
10
+
11
+ const ROOT_COMPLETIONS: TakomiCompletion[] = [
12
+ { value: "genesis", label: "genesis", description: "Run the Genesis planning stage" },
13
+ { value: "design", label: "design", description: "Run UI/UX design from approved scope" },
14
+ { value: "build", label: "build", description: "Implement against the agreed UI" },
15
+ { value: "plan", label: "plan", description: "Create or update the orchestration plan" },
16
+ { value: "mode", label: "mode", description: "Set direct, orchestrate, or review mode" },
17
+ { value: "gate", label: "gate", description: "Set auto or review-gated execution" },
18
+ { value: "subagents", label: "subagents", description: "Control subagent usage and view" },
19
+ { value: "routing", label: "routing", description: "Install/update Takomi model routing policy" },
20
+ ];
21
+
22
+ const SUBCOMMAND_COMPLETIONS: Record<string, TakomiCompletion[]> = {
23
+ mode: [
24
+ { value: "direct", label: "direct", description: "Handle work directly unless delegation is explicit" },
25
+ { value: "orchestrate", label: "orchestrate", description: "Plan and delegate broad work" },
26
+ { value: "review", label: "review", description: "Inspect outputs and route fixes" },
27
+ ],
28
+ gate: [
29
+ { value: "review", label: "review", description: "Return to the user after each task" },
30
+ { value: "auto", label: "auto", description: "Continue approved plans automatically" },
31
+ ],
32
+ subagents: [
33
+ { value: "status", label: "status", description: "Show active subagents" },
34
+ { value: "on", label: "on", description: "Allow subagent delegation" },
35
+ { value: "off", label: "off", description: "Disable subagent delegation" },
36
+ { value: "expand", label: "expand", description: "Expand native tool results" },
37
+ { value: "collapse", label: "collapse", description: "Collapse native tool results" },
38
+ { value: "toggle", label: "toggle", description: "Toggle native tool result expansion" },
39
+ ],
40
+ };
41
+
42
+ const SUBCOMMAND_ALIASES: Record<string, string> = {
43
+ subagent: "subagents",
44
+ };
45
+
46
+ function normalizeSubcommand(value: string): string {
47
+ return SUBCOMMAND_ALIASES[value] ?? value;
48
+ }
49
+
50
+ function matchesToken(completion: TakomiCompletion, token: string): boolean {
51
+ const lowered = token.toLowerCase();
52
+ return !lowered || completion.value.toLowerCase().startsWith(lowered) || completion.label.toLowerCase().startsWith(lowered);
53
+ }
54
+
55
+ function withArgumentPrefix(parent: string, completions: TakomiCompletion[], token: string): TakomiCompletion[] {
56
+ return completions
57
+ .filter((completion) => matchesToken(completion, token))
58
+ .map((completion) => ({
59
+ ...completion,
60
+ value: `${parent} ${completion.value}`,
61
+ }));
62
+ }
63
+
64
+ export function commandHelp(): string {
65
+ return [
66
+ "Takomi commands:",
67
+ "/takomi genesis [prompt]",
68
+ "/takomi design [prompt]",
69
+ "/takomi build [prompt]",
70
+ "/takomi plan [title]",
71
+ "/takomi mode <direct|orchestrate|review>",
72
+ "/takomi gate <auto|review>",
73
+ "/takomi subagents <on|off|status|expand|collapse|toggle>",
74
+ "/takomi routing <policy text>",
75
+ "/takomi-status",
76
+ "/takomi-reset",
77
+ ].join("\n");
78
+ }
79
+
80
+ export function workflowPrompt(stage: VibeLifecycleStage, suppliedPrompt?: string): string {
81
+ const suffix = suppliedPrompt?.trim() ? `\n\nPrompt: ${suppliedPrompt.trim()}` : "";
82
+ const lines = {
83
+ genesis: [
84
+ "Takomi Genesis is active.",
85
+ "Use existing markdown/project context when no extra prompt is supplied.",
86
+ "Strictly follow the Genesis playbook: clarify scope, create or update planning docs, and do not implement code yet.",
87
+ ],
88
+ design: [
89
+ "Takomi Design is active.",
90
+ "Focus on UI/UX only: agreed screens, visual direction, interaction states, mockups, and build constraints.",
91
+ "Do not drift into route/API architecture unless the user explicitly asks.",
92
+ ],
93
+ build: [
94
+ "Takomi Build is active.",
95
+ "Implement against the approved UI and continuously cross-check code against design artifacts and acceptance criteria.",
96
+ ],
97
+ }[stage];
98
+ return [...lines, suffix].filter(Boolean).join("\n");
99
+ }
100
+
101
+ export function statusText(state: TakomiRuntimeCommandState, subagentController: TakomiSubagentController): string {
102
+ return [
103
+ `Takomi: ${state.enabled ? "on" : "off"}`,
104
+ `Mode: ${state.role}`,
105
+ `Stage: ${state.stage ?? "-"}`,
106
+ `Workflow: ${state.workflow ?? "-"}`,
107
+ `Plan bias: ${state.planMode ? "on" : "off"}`,
108
+ `Execution gate: ${state.launchMode === "manual" ? "review" : "auto"}`,
109
+ `Subagents: ${state.subagentsEnabled ? "on" : "off"}`,
110
+ state.activeSessionId ? `Session: ${state.activeSessionId}` : "",
111
+ "",
112
+ subagentController.getStatusSummary(),
113
+ ].filter(Boolean).join("\n");
114
+ }
115
+
116
+ export function completions(argumentPrefix: string): TakomiCompletion[] {
117
+ const input = argumentPrefix.trimStart();
118
+ const parts = input.split(/\s+/).filter(Boolean);
119
+ const hasTrailingSpace = /\s$/.test(input);
120
+
121
+ if (parts.length === 0) return ROOT_COMPLETIONS;
122
+ if (parts.length === 1 && !hasTrailingSpace) {
123
+ return ROOT_COMPLETIONS.filter((completion) => matchesToken(completion, parts[0]));
124
+ }
125
+
126
+ const parent = normalizeSubcommand(parts[0]);
127
+ const nested = SUBCOMMAND_COMPLETIONS[parent] ?? [];
128
+ const token = hasTrailingSpace ? "" : parts[parts.length - 1] ?? "";
129
+ return withArgumentPrefix(parent, nested, token);
130
+ }
@@ -0,0 +1,179 @@
1
+ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+ import type {
3
+ TakomiLaunchMode,
4
+ TakomiRole,
5
+ TakomiWorkflowId,
6
+ VibeLifecycleStage,
7
+ } from "../../../src/pi-takomi-core";
8
+ import { commandHelp, completions, statusText, workflowPrompt } from "./command-text";
9
+ import type { TakomiSubagentController } from "./subagent-types";
10
+ import { installTakomiRoutingPolicy } from "./routing-policy";
11
+
12
+ export type TakomiRuntimeCommandState = {
13
+ enabled: boolean;
14
+ autoOrch: boolean;
15
+ launchMode: TakomiLaunchMode;
16
+ planMode: boolean;
17
+ role: TakomiRole;
18
+ stage?: VibeLifecycleStage;
19
+ workflow?: TakomiWorkflowId;
20
+ activeSessionId?: string;
21
+ subagentsEnabled: boolean;
22
+ };
23
+
24
+ type RegisterTakomiCommandOptions = {
25
+ getState(): TakomiRuntimeCommandState;
26
+ updateState(ctx: ExtensionContext, mutator: () => void, message?: string | (() => string)): Promise<void>;
27
+ resetRuntime(ctx: ExtensionCommandContext): Promise<void>;
28
+ setStageAndWorkflow(stage: VibeLifecycleStage, options?: { preserveRole?: boolean }): void;
29
+ createPlanSession(ctx: ExtensionCommandContext, title?: string): Promise<string>;
30
+ hasGenesisArtifacts(cwd: string): Promise<boolean>;
31
+ subagentController: TakomiSubagentController;
32
+ };
33
+
34
+ export function registerTakomiCommands(pi: ExtensionAPI, options: RegisterTakomiCommandOptions): void {
35
+ async function handleStage(ctx: ExtensionCommandContext, stage: VibeLifecycleStage, prompt?: string): Promise<void> {
36
+ await options.updateState(ctx, () => {
37
+ options.getState().enabled = true;
38
+ options.setStageAndWorkflow(stage, { preserveRole: stage === "genesis" && options.getState().role === "orchestrator" });
39
+ options.getState().planMode = stage !== "build";
40
+ }, workflowPrompt(stage, prompt));
41
+ }
42
+
43
+ async function handleMode(ctx: ExtensionCommandContext, mode?: string): Promise<void> {
44
+ if (mode !== "direct" && mode !== "orchestrate" && mode !== "review") {
45
+ ctx.ui.notify("Usage: /takomi mode <direct|orchestrate|review>", "warning");
46
+ return;
47
+ }
48
+ const hasGenesis = await options.hasGenesisArtifacts(ctx.cwd);
49
+ await options.updateState(ctx, () => {
50
+ const state = options.getState();
51
+ state.enabled = true;
52
+ if (mode === "direct") {
53
+ state.autoOrch = false;
54
+ state.planMode = false;
55
+ state.role = "general";
56
+ } else if (mode === "orchestrate") {
57
+ state.autoOrch = true;
58
+ state.planMode = true;
59
+ state.role = "orchestrator";
60
+ state.stage = hasGenesis ? "build" : "genesis";
61
+ state.workflow = hasGenesis ? "vibe-build" : "vibe-genesis";
62
+ } else {
63
+ state.autoOrch = false;
64
+ state.planMode = true;
65
+ state.launchMode = "manual";
66
+ state.role = "review";
67
+ }
68
+ }, () => `Takomi mode set to ${mode}`);
69
+ }
70
+
71
+ async function handleGate(ctx: ExtensionCommandContext, gate?: string): Promise<void> {
72
+ if (gate !== "auto" && gate !== "review") {
73
+ ctx.ui.notify("Usage: /takomi gate <auto|review>", "warning");
74
+ return;
75
+ }
76
+ await options.updateState(ctx, () => {
77
+ const state = options.getState();
78
+ state.enabled = true;
79
+ state.launchMode = gate === "review" ? "manual" : "auto";
80
+ state.autoOrch = gate === "auto";
81
+ }, () => `Takomi execution gate set to ${gate}`);
82
+ }
83
+
84
+ async function handleRouting(ctx: ExtensionCommandContext, body?: string): Promise<void> {
85
+ if (!body?.trim()) {
86
+ ctx.ui.notify("Usage: /takomi routing <policy text>\nTip: paste the policy after the command, or say: Update Takomi routing logic: \"\"\"...\"\"\"", "warning");
87
+ return;
88
+ }
89
+ try {
90
+ const result = await installTakomiRoutingPolicy(ctx.cwd, body);
91
+ const detected = result.detectedDefaults.length ? `\n\nDetected defaults:\n- ${result.detectedDefaults.join("\n- ")}` : "\n\nNo model names were auto-detected; saved policy only.";
92
+ ctx.ui.notify(`Takomi routing policy updated.\n\nPolicy: ${result.policyPath}\nSettings: ${result.settingsPath}${detected}`, "info");
93
+ } catch (error) {
94
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
95
+ }
96
+ }
97
+
98
+ async function handleSubagents(ctx: ExtensionCommandContext, action?: string): Promise<void> {
99
+ const controller = options.subagentController;
100
+ if (action === "on" || action === "off") {
101
+ await options.updateState(ctx, () => {
102
+ options.getState().subagentsEnabled = action === "on";
103
+ }, `Takomi subagents ${action}`);
104
+ return;
105
+ }
106
+ if (action === "status" || !action) {
107
+ ctx.ui.notify(statusText(options.getState(), controller), controller.hasRuns() ? "info" : "warning");
108
+ return;
109
+ }
110
+ if (action === "expand" || action === "fullscreen") {
111
+ ctx.ui.setToolsExpanded(true);
112
+ ctx.ui.notify("Expanded native tool results for Takomi subagent output.", "info");
113
+ return;
114
+ }
115
+ if (action === "collapse") {
116
+ ctx.ui.setToolsExpanded(false);
117
+ ctx.ui.notify("Collapsed native tool results.", "info");
118
+ return;
119
+ }
120
+ if (action === "toggle") {
121
+ const expanded = !ctx.ui.getToolsExpanded();
122
+ ctx.ui.setToolsExpanded(expanded);
123
+ ctx.ui.notify(`${expanded ? "Expanded" : "Collapsed"} native tool results.`, "info");
124
+ return;
125
+ }
126
+ if (action === "next" || action === "prev") {
127
+ ctx.ui.notify("Takomi now uses Pi's native inline result UI. Subagent navigation is handled by the transcript/tool results.", "info");
128
+ return;
129
+ }
130
+ ctx.ui.notify("Usage: /takomi subagents <on|off|status|expand|collapse|toggle>", "warning");
131
+ }
132
+
133
+ pi.registerCommand("takomi", {
134
+ description: "Takomi lifecycle entrypoint",
135
+ getArgumentCompletions: completions,
136
+ handler: async (args, ctx) => {
137
+ const [subcommand = "", ...rest] = args.trim().split(/\s+/).filter(Boolean);
138
+ const tail = rest.join(" ");
139
+ if (!subcommand) {
140
+ await options.updateState(ctx, () => {
141
+ options.getState().enabled = true;
142
+ }, commandHelp());
143
+ return;
144
+ }
145
+ if (subcommand === "genesis") return handleStage(ctx, "genesis", tail);
146
+ if (subcommand === "design") return handleStage(ctx, "design", tail);
147
+ if (subcommand === "build") return handleStage(ctx, "build", tail);
148
+ if (subcommand === "plan" || subcommand === "kickoff" || subcommand === "lifecycle" || subcommand === "autoorch") {
149
+ const summary = await options.createPlanSession(ctx, tail || undefined);
150
+ ctx.ui.notify(summary, "info");
151
+ return;
152
+ }
153
+ if (subcommand === "mode") return handleMode(ctx, rest[0]);
154
+ if (subcommand === "gate") return handleGate(ctx, rest[0]);
155
+ if (subcommand === "routing" || subcommand === "route" || subcommand === "models") return handleRouting(ctx, tail);
156
+ if (subcommand === "subagents" || subcommand === "subagent") return handleSubagents(ctx, rest[0]);
157
+ if (subcommand === "status") {
158
+ ctx.ui.notify(statusText(options.getState(), options.subagentController), "info");
159
+ return;
160
+ }
161
+ if (subcommand === "reset") return options.resetRuntime(ctx);
162
+ ctx.ui.notify(commandHelp(), "warning");
163
+ },
164
+ });
165
+
166
+ pi.registerCommand("takomi-status", {
167
+ description: "Show Takomi lifecycle, gate, session, and subagent status",
168
+ handler: async (_args, ctx) => {
169
+ ctx.ui.notify(statusText(options.getState(), options.subagentController), "info");
170
+ },
171
+ });
172
+
173
+ pi.registerCommand("takomi-reset", {
174
+ description: "Reset Takomi runtime state to defaults",
175
+ handler: async (_args, ctx) => {
176
+ await options.resetRuntime(ctx);
177
+ },
178
+ });
179
+ }