takomi 2.1.44 → 2.1.45
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 +4 -3
- package/.pi/extensions/oauth-router/commands.ts +66 -35
- package/.pi/extensions/oauth-router/index.ts +51 -7
- package/.pi/extensions/oauth-router/report-ui.ts +205 -0
- package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +41 -3
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +26 -0
- package/.pi/extensions/takomi-context-manager/index.ts +3 -2
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +36 -15
- package/.pi/extensions/takomi-context-manager/policy-tools.ts +93 -42
- package/.pi/extensions/takomi-context-manager/skill-categories.d.ts +11 -0
- package/.pi/extensions/takomi-context-manager/skill-categories.js +212 -0
- package/.pi/extensions/takomi-context-manager/skill-registry.ts +179 -8
- package/.pi/extensions/takomi-context-manager/skill-tools.ts +208 -103
- package/.pi/extensions/takomi-context-manager/tool-renderers.ts +112 -0
- package/.pi/extensions/takomi-context-manager/types.ts +6 -0
- package/.pi/extensions/takomi-runtime/commands.ts +45 -12
- package/.pi/extensions/takomi-runtime/gate-provenance.ts +24 -0
- package/.pi/extensions/takomi-runtime/index.ts +111 -42
- package/.pi/extensions/takomi-runtime/routing-policy.ts +14 -2
- package/.pi/extensions/takomi-runtime/takomi-stats.js +46 -26
- package/.pi/extensions/takomi-runtime/tool-renderers.ts +234 -0
- package/.pi/extensions/takomi-runtime/workflow-catalog.ts +54 -0
- package/.pi/extensions/takomi-subagents/agents.ts +4 -0
- package/.pi/extensions/takomi-subagents/async-lifecycle.ts +401 -0
- package/.pi/extensions/takomi-subagents/detached-results.ts +940 -0
- package/.pi/extensions/takomi-subagents/index.ts +46 -1
- package/.pi/extensions/takomi-subagents/native-render.ts +250 -188
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +55 -46
- package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +11 -5
- package/.pi/extensions/takomi-subagents/result-heartbeat.ts +55 -0
- package/.pi/extensions/takomi-subagents/subagent-ux.ts +162 -0
- package/.pi/extensions/takomi-subagents/tool-runner.ts +158 -28
- package/.pi/takomi/model-routing.md +282 -3
- package/assets/.agent/skills/shared-resend-portfolio/SKILL.md +124 -0
- package/package.json +4 -3
- package/src/pi-takomi-core/types.ts +10 -0
- package/src/pi-takomi-core/workflows.ts +86 -45
- package/src/skills-catalog.js +2 -202
- package/src/skills-installer.js +4 -1
|
@@ -15,29 +15,47 @@ const pc = {
|
|
|
15
15
|
magenta: ansi(35, 39),
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
// USD per million tokens: input, cache read, output, cache write.
|
|
19
|
+
// OAuth-router's configured catalog is merged over these fallbacks at runtime,
|
|
20
|
+
// keeping Stats aligned with custom/new models without another hard-coded edit.
|
|
18
21
|
const PRICES = {
|
|
19
|
-
'gpt-5.
|
|
20
|
-
'gpt-5.
|
|
21
|
-
'gpt-5.
|
|
22
|
-
'gpt-5.
|
|
23
|
-
'gpt-5.
|
|
24
|
-
'gpt-5.
|
|
25
|
-
'gpt-5-
|
|
26
|
-
'gpt-5.
|
|
27
|
-
'gpt-5.
|
|
28
|
-
'gpt-5': [1.25, 0.125, 10.00],
|
|
29
|
-
'gpt-5
|
|
30
|
-
'gpt-
|
|
31
|
-
'gpt-
|
|
32
|
-
'
|
|
33
|
-
'
|
|
22
|
+
'gpt-5.6-luna': [1.00, 0.10, 6.00, 1.25],
|
|
23
|
+
'gpt-5.6-sol': [5.00, 0.50, 30.00, 6.25],
|
|
24
|
+
'gpt-5.6-terra': [2.50, 0.25, 15.00, 3.125],
|
|
25
|
+
'gpt-5.5': [5.00, 0.50, 30.00, 6.25],
|
|
26
|
+
'gpt-5.4': [2.50, 0.25, 15.00, 3.125],
|
|
27
|
+
'gpt-5.4-mini': [0.75, 0.075, 4.50, 0.9375],
|
|
28
|
+
'gpt-5.4-nano': [0.20, 0.02, 1.25, 0.25],
|
|
29
|
+
'gpt-5.3-codex': [2.50, 0.25, 15.00, 3.125],
|
|
30
|
+
'gpt-5.2-codex': [1.75, 0.175, 14.00, 2.1875],
|
|
31
|
+
'gpt-5-codex': [1.25, 0.125, 10.00, 1.5625],
|
|
32
|
+
'gpt-5.2': [1.75, 0.175, 14.00, 2.1875],
|
|
33
|
+
'gpt-5.1': [1.25, 0.125, 10.00, 1.5625],
|
|
34
|
+
'gpt-5': [1.25, 0.125, 10.00, 1.5625],
|
|
35
|
+
'gpt-5-mini': [0.25, 0.025, 2.00, 0.3125],
|
|
36
|
+
'gpt-4.1': [2.00, 0.50, 8.00, 2.00],
|
|
37
|
+
'gpt-4o': [2.50, 1.25, 10.00, 2.50],
|
|
38
|
+
'o4-mini': [1.10, 0.275, 4.40, 1.10],
|
|
39
|
+
'claude-sonnet-4-6': [3.00, 0.30, 15.00, 3.75],
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
async function exists(target) { try { await fs.access(target); return true; } catch { return false; } }
|
|
37
43
|
function safeJson(line) { try { return JSON.parse(line); } catch { return null; } }
|
|
38
44
|
function dayOf(ts) { return typeof ts === 'string' && ts.length >= 10 ? ts.slice(0, 10) : 'unknown'; }
|
|
39
|
-
function add(map, key, patch) { const row = map.get(key) || { key, input: 0, cache: 0, output: 0, total: 0, cost: 0, events: 0 }; for (const [k,v] of Object.entries(patch)) row[k] = (row[k] || 0) + (Number(v) || 0); if (!Object.prototype.hasOwnProperty.call(patch, 'events')) row.events += 1; map.set(key, row); }
|
|
40
|
-
function cost(model, input, cache, output,
|
|
45
|
+
function add(map, key, patch) { const row = map.get(key) || { key, input: 0, cache: 0, cacheWrite: 0, output: 0, total: 0, cost: 0, events: 0 }; for (const [k,v] of Object.entries(patch)) row[k] = (row[k] || 0) + (Number(v) || 0); if (!Object.prototype.hasOwnProperty.call(patch, 'events')) row.events += 1; map.set(key, row); }
|
|
46
|
+
function cost(model, input, cache, output, cacheWrite = 0, prices = PRICES) { const p = prices[model]; if (!p) return 0; return (input*p[0] + cache*p[1] + output*p[2] + cacheWrite*(p[3] ?? p[0])) / 1_000_000; }
|
|
47
|
+
async function loadPrices(home) {
|
|
48
|
+
const prices = { ...PRICES };
|
|
49
|
+
const configPath = path.join(home, '.pi', 'agent', 'oauth-router', 'config.json');
|
|
50
|
+
try {
|
|
51
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
52
|
+
for (const model of config.models || []) {
|
|
53
|
+
if (!model?.id || !model?.cost) continue;
|
|
54
|
+
prices[model.id] = [model.cost.input || 0, model.cost.cacheRead || 0, model.cost.output || 0, model.cost.cacheWrite ?? model.cost.input ?? 0];
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
return prices;
|
|
58
|
+
}
|
|
41
59
|
function fmtTokens(n) { if (n >= 1e9) return `${(n/1e9).toFixed(2)}B`; if (n >= 1e6) return `${(n/1e6).toFixed(1)}M`; if (n >= 1e3) return `${(n/1e3).toFixed(1)}K`; return String(Math.round(n || 0)); }
|
|
42
60
|
function fmtMoney(n) { return `$${(n || 0).toFixed(n > 100 ? 0 : 2)}`; }
|
|
43
61
|
function ms(n) { if (!n) return '-'; const s = Math.round(n/1000); if (s < 60) return `${s}s`; const m = Math.floor(s/60); if (m < 60) return `${m}m ${s%60}s`; const h = Math.floor(m/60); return `${h}h ${m%60}m`; }
|
|
@@ -117,7 +135,7 @@ function pushTask(taskRows, task) {
|
|
|
117
135
|
taskRows.push(task);
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
async function scanPiSessions(root, source, events, sessionRows = [], taskRows = []) {
|
|
138
|
+
async function scanPiSessions(root, source, events, sessionRows = [], taskRows = [], prices = PRICES) {
|
|
121
139
|
for (const file of await files(root)) {
|
|
122
140
|
let provider = 'unknown', model = 'unknown', session = path.basename(file, '.jsonl'), cwd = '', currentTask = null;
|
|
123
141
|
const row = { key: session, session, source, file, project: projectKey(file), cwd, start: '', end: '', turns: 0, messages: 0, toolCalls: 0, subagentCalls: 0, roles: new Map(), stages: new Map(), workflows: new Map(), activeMs: 0, activityTimestamps: [] };
|
|
@@ -170,7 +188,7 @@ async function scanPiSessions(root, source, events, sessionRows = [], taskRows =
|
|
|
170
188
|
}
|
|
171
189
|
}
|
|
172
190
|
const u = msg && msg.usage;
|
|
173
|
-
if (u) events.push({ source, file, timestamp: obj.timestamp, day: dayOf(obj.timestamp), session, provider, model, project: projectKey(file), kind: 'usage', input: +u.input||0, cache: +u.cacheRead||0, output: +u.output||0, total: +u.totalTokens||0, cost: cost(model, +u.input||0, +u.cacheRead||0, +u.output||0,
|
|
191
|
+
if (u) events.push({ source, file, timestamp: obj.timestamp, day: dayOf(obj.timestamp), session, provider, model, project: projectKey(file), kind: 'usage', input: +u.input||0, cache: +u.cacheRead||0, cacheWrite: +u.cacheWrite||0, output: +u.output||0, total: +u.totalTokens||0, cost: cost(model, +u.input||0, +u.cacheRead||0, +u.output||0, +u.cacheWrite||0, prices) });
|
|
174
192
|
}
|
|
175
193
|
} catch {
|
|
176
194
|
continue;
|
|
@@ -193,18 +211,19 @@ export async function collectTakomiStats(opts = {}) {
|
|
|
193
211
|
const home = opts.home || os.homedir();
|
|
194
212
|
const cwd = opts.cwd || process.cwd();
|
|
195
213
|
const rawEvents = [], rawSessions = [], rawTasks = [];
|
|
214
|
+
const prices = await loadPrices(home);
|
|
196
215
|
const globalSessions = path.resolve(path.join(home, '.pi', 'agent', 'sessions'));
|
|
197
216
|
const projectSessions = path.resolve(path.join(cwd, '.pi', 'agent', 'sessions'));
|
|
198
|
-
await scanPiSessions(globalSessions, 'pi-global', rawEvents, rawSessions, rawTasks);
|
|
199
|
-
if (projectSessions !== globalSessions) await scanPiSessions(projectSessions, 'pi-project', rawEvents, rawSessions, rawTasks);
|
|
200
|
-
await scanPiSessions(path.join(cwd, '.pi', 'takomi'), 'takomi-project', rawEvents, rawSessions, rawTasks);
|
|
217
|
+
await scanPiSessions(globalSessions, 'pi-global', rawEvents, rawSessions, rawTasks, prices);
|
|
218
|
+
if (projectSessions !== globalSessions) await scanPiSessions(projectSessions, 'pi-project', rawEvents, rawSessions, rawTasks, prices);
|
|
219
|
+
await scanPiSessions(path.join(cwd, '.pi', 'takomi'), 'takomi-project', rawEvents, rawSessions, rawTasks, prices);
|
|
201
220
|
const sinceDay = parseSince(opts.since);
|
|
202
221
|
const events = rawEvents.filter(e => !sinceDay || e.day >= sinceDay);
|
|
203
222
|
const sessionRows = rawSessions.filter(s => !sinceDay || dayOf(s.end || s.start) >= sinceDay);
|
|
204
223
|
const taskRows = rawTasks.filter(t => !sinceDay || dayOf(t.end || t.start) >= sinceDay);
|
|
205
224
|
const runs = await scanRunHistory(path.join(home, '.pi', 'agent', 'run-history.jsonl'));
|
|
206
225
|
const byDay = new Map(), byModel = new Map(), bySource = new Map(), byProject = new Map(), byTool = new Map(), byRole = new Map(), byStage = new Map(), byWorkflow = new Map();
|
|
207
|
-
let totals = { input: 0, cache: 0, output: 0, total: 0, cost: 0, events: events.filter(e => e.kind === 'usage').length, toolCalls: 0, turns: 0 };
|
|
226
|
+
let totals = { input: 0, cache: 0, cacheWrite: 0, output: 0, total: 0, cost: 0, events: events.filter(e => e.kind === 'usage').length, toolCalls: 0, turns: 0 };
|
|
208
227
|
for (const s of sessionRows) { totals.toolCalls += s.toolCalls; totals.turns += s.turns; }
|
|
209
228
|
for (const e of events) {
|
|
210
229
|
if (e.kind === 'tool') { add(byTool, e.tool || 'unknown', { total: 0, events: 1 }); continue; }
|
|
@@ -214,7 +233,7 @@ export async function collectTakomiStats(opts = {}) {
|
|
|
214
233
|
add(byWorkflow, e.workflow || 'unknown', { total: 0, events: 1 });
|
|
215
234
|
continue;
|
|
216
235
|
}
|
|
217
|
-
totals.input += e.input; totals.cache += e.cache; totals.output += e.output; totals.total += e.total; totals.cost += e.cost;
|
|
236
|
+
totals.input += e.input; totals.cache += e.cache; totals.cacheWrite += e.cacheWrite || 0; totals.output += e.output; totals.total += e.total; totals.cost += e.cost;
|
|
218
237
|
add(byDay, e.day, e); add(byModel, e.model, e); add(bySource, e.source, e); add(byProject, e.project, e);
|
|
219
238
|
}
|
|
220
239
|
const byAgent = new Map(); let longestRun = null;
|
|
@@ -509,7 +528,8 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
509
528
|
// ── Stat Cards Row 1 ─────────────────────────────────────────────────
|
|
510
529
|
const cards1 = [
|
|
511
530
|
statCard(fmtTokens(stats.totals.total), 'Lifetime Tokens'),
|
|
512
|
-
statCard(fmtTokens(stats.totals.cache), 'Cache
|
|
531
|
+
statCard(fmtTokens(stats.totals.cache), 'Cache Reads'),
|
|
532
|
+
statCard(fmtTokens(stats.totals.cacheWrite), 'Cache Writes'),
|
|
513
533
|
statCard(fmtMoney(stats.totals.cost), 'Est. Cost'),
|
|
514
534
|
statCard(String(stats.sessions), 'Sessions'),
|
|
515
535
|
statCard(String(stats.totals.turns), 'Main Turns'),
|
|
@@ -677,7 +697,7 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
677
697
|
lines.push('');
|
|
678
698
|
lines.push(' ' + pc.dim(hrule(W - 4)));
|
|
679
699
|
lines.push(' ' + pc.dim('Privacy: metadata only · no raw prompts or transcripts'));
|
|
680
|
-
lines.push(' ' + pc.dim('Costs
|
|
700
|
+
lines.push(' ' + pc.dim('Costs use the oauth-router catalog when available; cache reads and writes are priced separately.'));
|
|
681
701
|
lines.push('');
|
|
682
702
|
|
|
683
703
|
return lines.join('\n');
|
|
@@ -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";
|
|
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
|
+
}
|
|
@@ -12,6 +12,7 @@ export type TakomiAgentConfig = {
|
|
|
12
12
|
model?: string;
|
|
13
13
|
fallbackModels?: string[];
|
|
14
14
|
thinking?: TakomiThinkingLevel;
|
|
15
|
+
defaultContext?: "fresh" | "fork";
|
|
15
16
|
systemPrompt: string;
|
|
16
17
|
filePath: string;
|
|
17
18
|
source: "user" | "project";
|
|
@@ -59,6 +60,9 @@ function loadAgentsFromDirectory(agentsDir: string, source: "user" | "project"):
|
|
|
59
60
|
model: frontmatter.model,
|
|
60
61
|
fallbackModels: splitList(frontmatter.fallbackModels ?? frontmatter.fallback_models),
|
|
61
62
|
thinking: normalizeThinking(frontmatter.thinking),
|
|
63
|
+
defaultContext: frontmatter.defaultContext === "fork" || frontmatter.defaultContext === "fresh"
|
|
64
|
+
? frontmatter.defaultContext
|
|
65
|
+
: undefined,
|
|
62
66
|
systemPrompt: body,
|
|
63
67
|
filePath,
|
|
64
68
|
source,
|