takomi 2.1.44 → 2.5.1

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 (50) hide show
  1. package/.pi/README.md +9 -8
  2. package/.pi/agents/architect.md +2 -2
  3. package/.pi/agents/designer.md +2 -2
  4. package/.pi/agents/worker.md +32 -0
  5. package/.pi/extensions/oauth-router/commands.ts +66 -35
  6. package/.pi/extensions/oauth-router/index.ts +51 -7
  7. package/.pi/extensions/oauth-router/report-ui.ts +205 -0
  8. package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +41 -3
  9. package/.pi/extensions/takomi-context-manager/diagnostics.ts +26 -0
  10. package/.pi/extensions/takomi-context-manager/index.ts +3 -2
  11. package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +50 -117
  12. package/.pi/extensions/takomi-context-manager/policy-tools.ts +93 -42
  13. package/.pi/extensions/takomi-context-manager/skill-categories.d.ts +11 -0
  14. package/.pi/extensions/takomi-context-manager/skill-categories.js +212 -0
  15. package/.pi/extensions/takomi-context-manager/skill-registry.ts +179 -8
  16. package/.pi/extensions/takomi-context-manager/skill-tools.ts +208 -103
  17. package/.pi/extensions/takomi-context-manager/tool-renderers.ts +112 -0
  18. package/.pi/extensions/takomi-context-manager/types.ts +6 -0
  19. package/.pi/extensions/takomi-runtime/command-text.ts +5 -4
  20. package/.pi/extensions/takomi-runtime/commands.ts +58 -25
  21. package/.pi/extensions/takomi-runtime/gate-provenance.ts +24 -0
  22. package/.pi/extensions/takomi-runtime/index.ts +385 -124
  23. package/.pi/extensions/takomi-runtime/model-routing-defaults.ts +262 -326
  24. package/.pi/extensions/takomi-runtime/profile.ts +9 -8
  25. package/.pi/extensions/takomi-runtime/routing-policy.ts +97 -66
  26. package/.pi/extensions/takomi-runtime/shared.ts +7 -30
  27. package/.pi/extensions/takomi-runtime/takomi-stats.js +46 -26
  28. package/.pi/extensions/takomi-runtime/tool-renderers.ts +234 -0
  29. package/.pi/extensions/takomi-runtime/workflow-catalog.ts +54 -0
  30. package/.pi/extensions/takomi-subagents/agents.ts +9 -0
  31. package/.pi/extensions/takomi-subagents/async-lifecycle.ts +401 -0
  32. package/.pi/extensions/takomi-subagents/detached-results.ts +940 -0
  33. package/.pi/extensions/takomi-subagents/index.ts +50 -1
  34. package/.pi/extensions/takomi-subagents/native-render.ts +250 -188
  35. package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +62 -47
  36. package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +11 -5
  37. package/.pi/extensions/takomi-subagents/result-heartbeat.ts +55 -0
  38. package/.pi/extensions/takomi-subagents/subagent-ux.ts +162 -0
  39. package/.pi/extensions/takomi-subagents/tool-runner.ts +198 -29
  40. package/.pi/settings.json +39 -36
  41. package/.pi/takomi/model-routing.md +288 -3
  42. package/.pi/takomi-profile.json +54 -50
  43. package/assets/.agent/skills/shared-resend-portfolio/SKILL.md +124 -0
  44. package/package.json +4 -3
  45. package/src/pi-takomi-core/orchestration.ts +39 -21
  46. package/src/pi-takomi-core/routing.ts +8 -8
  47. package/src/pi-takomi-core/types.ts +35 -5
  48. package/src/pi-takomi-core/workflows.ts +86 -45
  49. package/src/skills-catalog.js +2 -202
  50. package/src/skills-installer.js +4 -1
@@ -0,0 +1,234 @@
1
+ import { getMarkdownTheme, keyHint } from "@earendil-works/pi-coding-agent";
2
+ import { Container, Markdown, Text } from "@earendil-works/pi-tui";
3
+
4
+ type Theme = {
5
+ fg(color: string, text: string): string;
6
+ bold(text: string): string;
7
+ };
8
+
9
+ type ToolResult = {
10
+ content?: string | Array<{ type?: string; text?: string }>;
11
+ details?: unknown;
12
+ isError?: boolean;
13
+ };
14
+
15
+ type ResultOptions = { expanded: boolean; isError?: boolean };
16
+ type CardStatus = "success" | "warning" | "error" | "pending";
17
+
18
+ type ModeDetails = {
19
+ mode?: string;
20
+ source?: string;
21
+ reason?: string;
22
+ role?: string;
23
+ stage?: string;
24
+ workflow?: string;
25
+ };
26
+
27
+ type RoutingDetails = {
28
+ result?: { detectedDefaults?: string[] };
29
+ preview?: { scope?: string };
30
+ };
31
+
32
+ type WorkflowDetails = { id?: string; title?: string; purpose?: string };
33
+
34
+ type BoardError = {
35
+ code: string;
36
+ message: string;
37
+ severity: "warning" | "error";
38
+ };
39
+
40
+ type BoardDetails = {
41
+ sessionId?: string;
42
+ taskId?: string;
43
+ task?: { id?: string; status?: string };
44
+ tasks?: unknown[];
45
+ lifecycle?: unknown;
46
+ error?: BoardError;
47
+ };
48
+
49
+ type BoardArgs = {
50
+ action?: "init_session" | "expand_stage" | "show_workflows" | "show_session" | "update_task" | "replace_master_plan";
51
+ sessionId?: string;
52
+ taskId?: string;
53
+ stage?: string;
54
+ };
55
+
56
+ // Tool output is model-facing data. Sanitize only at the TUI boundary so
57
+ // ANSI, OSC, and C0 controls cannot alter terminal presentation.
58
+ function sanitizePresentation(value: string): string {
59
+ return value
60
+ .replace(/\x1B\][\s\S]*?(?:\x07|\x1B\\)/g, "") // OSC (BEL or ST terminated)
61
+ .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "") // CSI
62
+ .replace(/[\x00-\x08\x0B-\x1F\x7F-\x9F]/g, "");
63
+ }
64
+
65
+ function resultText(result: ToolResult): string {
66
+ const text = typeof result.content === "string"
67
+ ? result.content
68
+ : result.content?.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n") ?? "";
69
+ return sanitizePresentation(text);
70
+ }
71
+
72
+ function bounded(value: string | undefined, limit = 36): string {
73
+ const text = sanitizePresentation(value ?? "").replace(/\s+/g, " ").trim();
74
+ return text.length > limit ? `${text.slice(0, Math.max(1, limit - 1))}…` : text;
75
+ }
76
+
77
+ function status(result: ToolResult, options: ResultOptions, details?: { error?: BoardError }): CardStatus {
78
+ if (details?.error?.severity) return details.error.severity;
79
+ return result.isError || options.isError ? "error" : "success";
80
+ }
81
+
82
+ function renderToolCall(name: string, target: string | undefined, theme: Theme): Text {
83
+ return new Text(
84
+ `${theme.fg("toolTitle", theme.bold(`${name} `))}${target ? theme.fg("accent", bounded(target)) : ""}`,
85
+ 0,
86
+ 0,
87
+ );
88
+ }
89
+
90
+ function renderCompactCard(options: {
91
+ status: CardStatus;
92
+ title: string;
93
+ summary: string;
94
+ metadata: string;
95
+ }, theme: Theme): Text {
96
+ const presentation = {
97
+ success: ["✓", "success"],
98
+ warning: ["⚠", "warning"],
99
+ error: ["✗", "error"],
100
+ pending: ["…", "muted"],
101
+ } as const;
102
+ const [icon, color] = presentation[options.status];
103
+ return new Text([
104
+ `${theme.fg(color, icon)} ${theme.fg("accent", theme.bold(bounded(options.title)))} ${theme.fg("muted", bounded(options.summary, 72))}`,
105
+ theme.fg("dim", `${bounded(options.metadata, 64)} · ${keyHint("app.tools.expand", "view details")}`),
106
+ ].join("\n"), 0, 0);
107
+ }
108
+
109
+ function renderExpandedCard(options: {
110
+ status: CardStatus;
111
+ title: string;
112
+ summary: string;
113
+ metadata: string[];
114
+ markdown: string;
115
+ }, theme: Theme): Container {
116
+ const presentation = {
117
+ success: ["✓", "success"],
118
+ warning: ["⚠", "warning"],
119
+ error: ["✗", "error"],
120
+ pending: ["…", "muted"],
121
+ } as const;
122
+ const [icon, color] = presentation[options.status];
123
+ const container = new Container();
124
+ container.addChild(new Text(
125
+ `${theme.fg(color, icon)} ${theme.fg("accent", theme.bold(sanitizePresentation(options.title)))} ${theme.fg("muted", sanitizePresentation(options.summary))}`,
126
+ 0,
127
+ 0,
128
+ ));
129
+ for (const detail of options.metadata) container.addChild(new Text(theme.fg("dim", sanitizePresentation(detail)), 0, 0));
130
+ container.addChild(new Text(theme.fg("dim", keyHint("app.tools.expand", "collapse")), 0, 0));
131
+ container.addChild(new Markdown(sanitizePresentation(options.markdown), 0, 1, getMarkdownTheme()));
132
+ return container;
133
+ }
134
+
135
+ export function renderTakomiModeCall(args: { mode?: string } | undefined, theme: Theme): Text {
136
+ return renderToolCall("takomi_mode", args?.mode ?? "mode", theme);
137
+ }
138
+
139
+ export function renderTakomiModeResult(result: ToolResult, options: ResultOptions, theme: Theme): Container | Text {
140
+ const details = result.details as ModeDetails | undefined;
141
+ const primary = details?.stage ?? details?.role ?? details?.mode ?? "mode";
142
+ const source = details?.source ?? "model";
143
+ const metadata = [source, details?.workflow ? `wf:${details.workflow}` : "footer updated"].join(" · ");
144
+ if (!options.expanded) {
145
+ return renderCompactCard({ status: status(result, options), title: "Takomi mode", summary: `${source}:${primary}`, metadata }, theme);
146
+ }
147
+ return renderExpandedCard({
148
+ status: status(result, options),
149
+ title: "Takomi mode",
150
+ summary: `${source}:${primary}`,
151
+ metadata: [metadata],
152
+ markdown: resultText(result),
153
+ }, theme);
154
+ }
155
+
156
+ export function renderTakomiRoutingCall(args: { scope?: string } | undefined, theme: Theme): Text {
157
+ return renderToolCall("takomi_apply_routing_policy", args?.scope ?? "global", theme);
158
+ }
159
+
160
+ export function renderTakomiRoutingResult(result: ToolResult, options: ResultOptions, theme: Theme): Container | Text {
161
+ const details = result.details as RoutingDetails | undefined;
162
+ const scope = details?.preview?.scope ?? "global";
163
+ const detectedCount = details?.result?.detectedDefaults?.length ?? 0;
164
+ const summary = status(result, options) === "error" ? "policy was not saved" : `${scope} policy saved`;
165
+ const metadata = `${detectedCount} routing defaults detected`;
166
+ if (!options.expanded) return renderCompactCard({ status: status(result, options), title: "Takomi routing", summary, metadata }, theme);
167
+ return renderExpandedCard({
168
+ status: status(result, options),
169
+ title: "Takomi routing",
170
+ summary,
171
+ metadata: [`Scope: ${scope}`, metadata],
172
+ markdown: resultText(result),
173
+ }, theme);
174
+ }
175
+
176
+ export function renderTakomiWorkflowCall(args: { workflow?: string } | undefined, theme: Theme): Text {
177
+ return renderToolCall("takomi_workflow", args?.workflow ?? "library", theme);
178
+ }
179
+
180
+ export function renderTakomiWorkflowResult(result: ToolResult, options: ResultOptions, theme: Theme): Container | Text {
181
+ const details = result.details as WorkflowDetails | undefined;
182
+ const selected = details?.id;
183
+ const title = selected ? "Takomi workflow" : "Workflow library";
184
+ const summary = selected ? (details?.title ?? selected) : "embedded lifecycle playbooks";
185
+ const metadata = selected ? bounded(selected) : "genesis · design · build";
186
+ if (!options.expanded) return renderCompactCard({ status: status(result, options), title, summary, metadata }, theme);
187
+ return renderExpandedCard({ status: status(result, options), title, summary, metadata: [metadata], markdown: resultText(result) }, theme);
188
+ }
189
+
190
+ function boardPresentation(args: BoardArgs, details: BoardDetails | undefined): { title: string; summary: string; metadata: string } {
191
+ switch (args.action) {
192
+ case "show_workflows":
193
+ return { title: "Workflow library", summary: "available lifecycle playbooks", metadata: "genesis · design · build" };
194
+ case "show_session":
195
+ return { title: "Takomi session", summary: "session loaded", metadata: `session:${bounded(args.sessionId ?? details?.sessionId, 24)}` };
196
+ case "update_task":
197
+ return {
198
+ title: `Task ${bounded(args.taskId ?? details?.taskId ?? details?.task?.id, 24) || "update"}`,
199
+ summary: details?.task?.status ? `status: ${details.task.status}` : "task updated",
200
+ metadata: `session:${bounded(args.sessionId ?? details?.sessionId, 24)}`,
201
+ };
202
+ case "expand_stage":
203
+ return {
204
+ title: `${args.stage ?? "Lifecycle"} stage`,
205
+ summary: "stage expanded",
206
+ metadata: `session:${bounded(args.sessionId ?? details?.sessionId, 24)}`,
207
+ };
208
+ default:
209
+ return {
210
+ title: "Takomi board",
211
+ summary: "orchestrator session created",
212
+ metadata: `session:${bounded(details?.sessionId ?? args.sessionId, 24)}`,
213
+ };
214
+ }
215
+ }
216
+
217
+ export function renderTakomiBoardCall(args: BoardArgs | undefined, theme: Theme): Text {
218
+ return renderToolCall("takomi_board", args?.action ?? "init_session", theme);
219
+ }
220
+
221
+ export function renderTakomiBoardResult(result: ToolResult, options: ResultOptions, theme: Theme, args?: BoardArgs): Container | Text {
222
+ const details = result.details as BoardDetails | undefined;
223
+ const presentation = boardPresentation(args ?? {}, details);
224
+ const cardStatus = status(result, options, details);
225
+ const summary = cardStatus === "success" ? presentation.summary : details?.error?.message ?? "action needs attention";
226
+ if (!options.expanded) return renderCompactCard({ status: cardStatus, title: presentation.title, summary, metadata: presentation.metadata }, theme);
227
+ return renderExpandedCard({
228
+ status: cardStatus,
229
+ title: presentation.title,
230
+ summary,
231
+ metadata: [presentation.metadata],
232
+ markdown: resultText(result),
233
+ }, theme);
234
+ }
@@ -0,0 +1,54 @@
1
+ import {
2
+ getWorkflowDefinition,
3
+ listWorkflowCatalog,
4
+ type TakomiWorkflowId,
5
+ type WorkflowCatalogEntry,
6
+ type WorkflowDefinition,
7
+ } from "../../../src/pi-takomi-core";
8
+
9
+ type WorkflowCatalogPayload = {
10
+ content: Array<{ type: "text"; text: string }>;
11
+ details: { workflows: readonly WorkflowCatalogEntry[] };
12
+ };
13
+
14
+ type WorkflowPlaybookPayload = {
15
+ content: Array<{ type: "text"; text: string }>;
16
+ details: WorkflowDefinition & { workflows: readonly WorkflowCatalogEntry[] };
17
+ };
18
+
19
+ function formatCatalogEntry(workflow: WorkflowCatalogEntry): string {
20
+ return `${workflow.id} (${workflow.stage}) — ${workflow.name}: ${workflow.description} [${workflow.availability}]`;
21
+ }
22
+
23
+ /** Discovery/load adapter for the direct workflow API. */
24
+ export function discoverWorkflowPlaybooks(workflowId?: TakomiWorkflowId): WorkflowPlaybookPayload | WorkflowCatalogPayload {
25
+ if (workflowId) {
26
+ const workflow = getWorkflowDefinition(workflowId);
27
+ return {
28
+ content: [{ type: "text", text: `${workflow.title}\n\n${workflow.playbook}` }],
29
+ details: { ...workflow, workflows: listWorkflowCatalog() },
30
+ };
31
+ }
32
+
33
+ const workflows = listWorkflowCatalog();
34
+ return {
35
+ content: [{ type: "text", text: workflows.map(formatCatalogEntry).join("\n") }],
36
+ details: { workflows },
37
+ };
38
+ }
39
+
40
+ /** Lifecycle-board adapter; intentionally catalog-oriented and playbook-free. */
41
+ export function showWorkflowCatalogForBoard(): WorkflowCatalogPayload {
42
+ const workflows = listWorkflowCatalog();
43
+ return {
44
+ content: [{
45
+ type: "text",
46
+ text: [
47
+ "Takomi lifecycle workflow catalog",
48
+ "",
49
+ ...workflows.map((workflow) => `- ${formatCatalogEntry(workflow)}`),
50
+ ].join("\n"),
51
+ }],
52
+ details: { workflows },
53
+ };
54
+ }
@@ -5,6 +5,9 @@ import type { TakomiThinkingLevel } from "../../../src/pi-takomi-core";
5
5
 
6
6
  export type TakomiAgentScope = "user" | "project" | "both";
7
7
 
8
+ export const TAKOMI_PUBLIC_AGENT_NAMES = ["architect", "designer", "coder", "worker", "reviewer", "orchestrator"] as const;
9
+ const TAKOMI_PUBLIC_AGENT_SET = new Set<string>(TAKOMI_PUBLIC_AGENT_NAMES);
10
+
8
11
  export type TakomiAgentConfig = {
9
12
  name: string;
10
13
  description: string;
@@ -12,6 +15,7 @@ export type TakomiAgentConfig = {
12
15
  model?: string;
13
16
  fallbackModels?: string[];
14
17
  thinking?: TakomiThinkingLevel;
18
+ defaultContext?: "fresh" | "fork";
15
19
  systemPrompt: string;
16
20
  filePath: string;
17
21
  source: "user" | "project";
@@ -52,6 +56,8 @@ function loadAgentsFromDirectory(agentsDir: string, source: "user" | "project"):
52
56
  const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
53
57
  if (!frontmatter.name || !frontmatter.description) continue;
54
58
 
59
+ if (!TAKOMI_PUBLIC_AGENT_SET.has(frontmatter.name)) continue;
60
+
55
61
  agents.push({
56
62
  name: frontmatter.name,
57
63
  description: frontmatter.description,
@@ -59,6 +65,9 @@ function loadAgentsFromDirectory(agentsDir: string, source: "user" | "project"):
59
65
  model: frontmatter.model,
60
66
  fallbackModels: splitList(frontmatter.fallbackModels ?? frontmatter.fallback_models),
61
67
  thinking: normalizeThinking(frontmatter.thinking),
68
+ defaultContext: frontmatter.defaultContext === "fork" || frontmatter.defaultContext === "fresh"
69
+ ? frontmatter.defaultContext
70
+ : undefined,
62
71
  systemPrompt: body,
63
72
  filePath,
64
73
  source,