takomi 2.1.2 → 2.1.3

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 (49) hide show
  1. package/.pi/README.md +124 -124
  2. package/.pi/agents/architect.md +15 -15
  3. package/.pi/agents/coder.md +14 -14
  4. package/.pi/agents/designer.md +17 -17
  5. package/.pi/agents/orchestrator.md +22 -22
  6. package/.pi/agents/reviewer.md +16 -16
  7. package/.pi/extensions/oauth-router/README.md +125 -125
  8. package/.pi/extensions/oauth-router/commands.ts +380 -380
  9. package/.pi/extensions/oauth-router/config.ts +200 -200
  10. package/.pi/extensions/oauth-router/index.ts +41 -41
  11. package/.pi/extensions/oauth-router/oauth-flow.ts +154 -154
  12. package/.pi/extensions/oauth-router/oauth-store.ts +121 -121
  13. package/.pi/extensions/oauth-router/package.json +14 -14
  14. package/.pi/extensions/oauth-router/policies.ts +27 -27
  15. package/.pi/extensions/oauth-router/provider.ts +492 -492
  16. package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -98
  17. package/.pi/extensions/oauth-router/state.ts +174 -174
  18. package/.pi/extensions/oauth-router/types.ts +153 -153
  19. package/.pi/extensions/takomi-runtime/command-text.ts +130 -130
  20. package/.pi/extensions/takomi-runtime/commands.ts +179 -179
  21. package/.pi/extensions/takomi-runtime/context-panel.ts +282 -282
  22. package/.pi/extensions/takomi-runtime/index.ts +1288 -1288
  23. package/.pi/extensions/takomi-runtime/profile.ts +114 -114
  24. package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -105
  25. package/.pi/extensions/takomi-runtime/shared.ts +492 -492
  26. package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -364
  27. package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -501
  28. package/.pi/extensions/takomi-runtime/subagent-types.ts +83 -83
  29. package/.pi/extensions/takomi-runtime/ui.ts +133 -133
  30. package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -18
  31. package/.pi/extensions/takomi-subagents/agents.ts +113 -113
  32. package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -95
  33. package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -26
  34. package/.pi/extensions/takomi-subagents/dispatch.ts +215 -215
  35. package/.pi/extensions/takomi-subagents/index.ts +75 -75
  36. package/.pi/extensions/takomi-subagents/live-updates.ts +83 -83
  37. package/.pi/extensions/takomi-subagents/native-render.ts +174 -174
  38. package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -209
  39. package/.pi/themes/takomi-noir.json +81 -81
  40. package/package.json +59 -59
  41. package/src/doctor.js +87 -84
  42. package/src/pi-harness.js +355 -351
  43. package/src/pi-installer.js +193 -171
  44. package/src/pi-takomi-core/index.ts +4 -4
  45. package/src/pi-takomi-core/orchestration.ts +402 -402
  46. package/src/pi-takomi-core/routing.ts +93 -93
  47. package/src/pi-takomi-core/types.ts +173 -173
  48. package/src/pi-takomi-core/workflows.ts +299 -299
  49. package/src/skills-installer.js +101 -101
@@ -1,113 +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
- }
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
+ }
@@ -1,95 +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
- }
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
+ }
@@ -1,26 +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
- }
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
+ }