takomi 2.1.26 → 2.1.28
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 +10 -0
- package/.pi/agents/architect.md +0 -1
- package/.pi/agents/coder.md +0 -1
- package/.pi/agents/designer.md +0 -1
- package/.pi/agents/orchestrator.md +0 -1
- package/.pi/agents/reviewer.md +0 -1
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +1 -1
- package/.pi/extensions/takomi-context-manager/extension-conflicts.ts +14 -3
- package/.pi/extensions/takomi-context-manager/index.ts +6 -3
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +28 -13
- package/.pi/extensions/takomi-runtime/commands.ts +24 -7
- package/.pi/extensions/takomi-runtime/context-panel.ts +583 -282
- package/.pi/extensions/takomi-runtime/index.ts +101 -6
- package/.pi/extensions/takomi-runtime/model-routing-defaults.ts +296 -0
- package/.pi/extensions/takomi-runtime/routing-policy.ts +67 -17
- package/.pi/extensions/takomi-runtime/takomi-stats.js +44 -16
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +12 -2
- package/.pi/extensions/takomi-subagents/tool-runner.ts +4 -2
- package/.pi/settings.json +18 -20
- package/README.md +34 -4
- package/package.json +4 -2
- package/src/cli.js +326 -33
- package/src/harness.js +132 -16
- package/src/pi-harness.js +27 -6
- package/src/pi-optional-features.js +195 -0
- package/src/postinstall.js +27 -0
- package/src/skills-catalog.js +245 -0
- package/src/skills-installer.js +244 -101
- package/src/skills-selection-tui.js +200 -0
- package/src/store.js +418 -240
- package/src/takomi-stats.js +44 -16
package/src/takomi-stats.js
CHANGED
|
@@ -40,6 +40,28 @@ function cost(model, input, cache, output, additiveCache = true) { const p = PRI
|
|
|
40
40
|
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)); }
|
|
41
41
|
function fmtMoney(n) { return `$${(n || 0).toFixed(n > 100 ? 0 : 2)}`; }
|
|
42
42
|
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`; }
|
|
43
|
+
const ACTIVE_GAP_THRESHOLD_MS = 15 * 60 * 1000;
|
|
44
|
+
function timestampMs(value) {
|
|
45
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
46
|
+
if (typeof value === 'string' && value) {
|
|
47
|
+
const parsed = Date.parse(value);
|
|
48
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function addTimestamp(target, value) {
|
|
53
|
+
const parsed = timestampMs(value);
|
|
54
|
+
if (parsed !== null) target.push(parsed);
|
|
55
|
+
}
|
|
56
|
+
function activeDuration(timestamps, maxGapMs = ACTIVE_GAP_THRESHOLD_MS) {
|
|
57
|
+
const sorted = [...new Set((timestamps || []).filter(Number.isFinite))].sort((a, b) => a - b);
|
|
58
|
+
let total = 0;
|
|
59
|
+
for (let i = 1; i < sorted.length; i += 1) {
|
|
60
|
+
const delta = sorted[i] - sorted[i - 1];
|
|
61
|
+
if (delta > 0 && delta <= maxGapMs) total += delta;
|
|
62
|
+
}
|
|
63
|
+
return total;
|
|
64
|
+
}
|
|
43
65
|
function parseSince(value) {
|
|
44
66
|
if (!value) return null;
|
|
45
67
|
const raw = String(value).trim().toLowerCase();
|
|
@@ -88,14 +110,20 @@ async function files(root, suffix = '.jsonl') {
|
|
|
88
110
|
await walk(root); return out;
|
|
89
111
|
}
|
|
90
112
|
|
|
113
|
+
function pushTask(taskRows, task) {
|
|
114
|
+
if (!task?.end || task.end === task.start) return;
|
|
115
|
+
task.activeMs = activeDuration(task.activityTimestamps || []);
|
|
116
|
+
taskRows.push(task);
|
|
117
|
+
}
|
|
118
|
+
|
|
91
119
|
async function scanPiSessions(root, source, events, sessionRows = [], taskRows = []) {
|
|
92
120
|
for (const file of await files(root)) {
|
|
93
121
|
let provider = 'unknown', model = 'unknown', session = path.basename(file, '.jsonl'), cwd = '', currentTask = null;
|
|
94
|
-
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 };
|
|
122
|
+
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: [] };
|
|
95
123
|
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
96
124
|
for (const line of text.split(/\r?\n/)) {
|
|
97
125
|
const obj = safeJson(line); if (!obj) continue;
|
|
98
|
-
if (obj.timestamp) { row.start ||= obj.timestamp; row.end = obj.timestamp; }
|
|
126
|
+
if (obj.timestamp) { row.start ||= obj.timestamp; row.end = obj.timestamp; addTimestamp(row.activityTimestamps, obj.timestamp); }
|
|
99
127
|
if (obj.type === 'session') { session = obj.id || session; cwd = obj.cwd || cwd; row.key = session; row.session = session; row.cwd = cwd; }
|
|
100
128
|
if (obj.type === 'model_change') { provider = obj.provider || provider; model = obj.modelId || model; }
|
|
101
129
|
if (obj.type === 'custom' && obj.customType === 'takomi-runtime-state' && obj.data) {
|
|
@@ -112,12 +140,14 @@ async function scanPiSessions(root, source, events, sessionRows = [], taskRows =
|
|
|
112
140
|
row.messages += 1;
|
|
113
141
|
const ts = obj.timestamp || msg.timestamp || '';
|
|
114
142
|
if (msg.role === 'user') {
|
|
115
|
-
|
|
143
|
+
pushTask(taskRows, currentTask);
|
|
116
144
|
row.turns += 1;
|
|
117
145
|
const textPart = (msg.content || []).find(p => p?.type === 'text')?.text || '';
|
|
118
|
-
currentTask = { source, file, session, project: projectKey(file), cwd, start: ts, end: ts, provider, model, turns: 1, toolCalls: 0, title: String(textPart).replace(/\s+/g, ' ').trim() };
|
|
146
|
+
currentTask = { source, file, session, project: projectKey(file), cwd, start: ts, end: ts, provider, model, turns: 1, toolCalls: 0, title: String(textPart).replace(/\s+/g, ' ').trim(), activityTimestamps: [] };
|
|
147
|
+
addTimestamp(currentTask.activityTimestamps, ts);
|
|
119
148
|
} else if (currentTask && ts) {
|
|
120
149
|
currentTask.end = ts;
|
|
150
|
+
addTimestamp(currentTask.activityTimestamps, ts);
|
|
121
151
|
}
|
|
122
152
|
for (const part of msg.content || []) {
|
|
123
153
|
if (!part || part.type !== 'toolCall') continue;
|
|
@@ -135,8 +165,8 @@ async function scanPiSessions(root, source, events, sessionRows = [], taskRows =
|
|
|
135
165
|
const u = msg && msg.usage;
|
|
136
166
|
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, true) });
|
|
137
167
|
}
|
|
138
|
-
|
|
139
|
-
row.activeMs =
|
|
168
|
+
pushTask(taskRows, currentTask);
|
|
169
|
+
row.activeMs = activeDuration(row.activityTimestamps);
|
|
140
170
|
if (row.messages || row.toolCalls || row.turns) sessionRows.push(row);
|
|
141
171
|
}
|
|
142
172
|
}
|
|
@@ -336,9 +366,7 @@ function sessionDuration(row) {
|
|
|
336
366
|
return row?.activeMs || 0;
|
|
337
367
|
}
|
|
338
368
|
function taskDuration(row) {
|
|
339
|
-
|
|
340
|
-
const end = Date.parse(row?.end || '');
|
|
341
|
-
return Number.isFinite(start) && Number.isFinite(end) && end >= start ? end - start : 0;
|
|
369
|
+
return row?.activeMs ?? activeDuration(row?.activityTimestamps || []);
|
|
342
370
|
}
|
|
343
371
|
function taskLabel(row, width = 34) {
|
|
344
372
|
const label = row?.title || row?.project || row?.session || 'unknown';
|
|
@@ -379,7 +407,7 @@ function renderFocusedView(stats, opts = {}) {
|
|
|
379
407
|
` ${pc.cyan(ms(sessionDuration(r)))} ${pc.dim(r.turns + ' turns')} ${pc.dim(r.toolCalls + ' tools')}`,
|
|
380
408
|
` ${pc.dim(r.file || '')}`,
|
|
381
409
|
].join('\n'), limit);
|
|
382
|
-
if (view === 'tasks-full' || view === 'task-full') return renderFullList('Longest
|
|
410
|
+
if (view === 'tasks-full' || view === 'task-full') return renderFullList('Longest Active Turns — Full Prompts', stats.topTasks || [], (r, i) => [
|
|
383
411
|
` ${pc.dim(String(i + 1).padStart(2, '0') + '.')} ${pc.cyan(ms(taskDuration(r)))} ${pc.magenta(r.toolCalls + ' tools')} ${pc.dim(dayOf(r.start))}`,
|
|
384
412
|
` ${pc.white(indentWrap(r.title || r.project || r.session || 'unknown'))}`,
|
|
385
413
|
` ${pc.dim(r.project || '')}`,
|
|
@@ -426,7 +454,7 @@ function renderFocusedView(stats, opts = {}) {
|
|
|
426
454
|
{ width: 8, align: 'right', get: r => pc.cyan(String(r.toolCalls)) },
|
|
427
455
|
{ width: 8, align: 'left', get: () => pc.dim('tools') },
|
|
428
456
|
]],
|
|
429
|
-
tasks: ['Longest
|
|
457
|
+
tasks: ['Longest Active Turns', stats.topTasks || [], [
|
|
430
458
|
{ width: 6, align: 'left', get: r => pc.dim(dayOf(r.start).slice(5)) },
|
|
431
459
|
{ width: 9, align: 'right', get: r => pc.cyan(ms(taskDuration(r))) },
|
|
432
460
|
{ width: 11, align: 'right', get: r => pc.magenta(`${r.toolCalls} tools`) },
|
|
@@ -518,9 +546,9 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
518
546
|
// ── Duration Cards ────────────────────────────────────────────────────
|
|
519
547
|
lines.push('');
|
|
520
548
|
const cards3 = [
|
|
521
|
-
statCard(longestSession ? ms(sessionDuration(longestSession)) : '-', 'Active Session', pc.cyan),
|
|
549
|
+
statCard(longestSession ? ms(sessionDuration(longestSession)) : '-', 'Top Active Session', pc.cyan),
|
|
522
550
|
statCard(longestSession ? String(longestSession.turns) : '-', 'Turns in Session', pc.cyan),
|
|
523
|
-
statCard(longestTask ? ms(taskDuration(longestTask)) : '-', 'Longest
|
|
551
|
+
statCard(longestTask ? ms(taskDuration(longestTask)) : '-', 'Longest Active Turn', pc.magenta),
|
|
524
552
|
statCard(longestTask ? String(longestTask.toolCalls) : '-', 'Tools in Task', pc.magenta),
|
|
525
553
|
statCard(stats.mostSubagentsSession ? String(stats.mostSubagentsSession.subagentCalls) : '0', 'Max Subagents', pc.blue),
|
|
526
554
|
];
|
|
@@ -530,7 +558,7 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
530
558
|
|
|
531
559
|
// ── Info line ─────────────────────────────────────────────────────────
|
|
532
560
|
lines.push('');
|
|
533
|
-
const infoText = `Peak: ${peak?.key || '-'} · ${streaks.quietDays} quiet days · ${stats.totals.events.toLocaleString()} events${stats.since ? ` · since ${stats.since}` : ''}`;
|
|
561
|
+
const infoText = `Peak: ${peak?.key || '-'} · ${streaks.quietDays} quiet days · ${stats.totals.events.toLocaleString()} events · active gaps ≤15m${stats.since ? ` · since ${stats.since}` : ''}`;
|
|
534
562
|
lines.push(center(pc.dim(infoText), W));
|
|
535
563
|
|
|
536
564
|
lines.push('');
|
|
@@ -586,10 +614,10 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
586
614
|
]));
|
|
587
615
|
}
|
|
588
616
|
|
|
589
|
-
// ── Longest
|
|
617
|
+
// ── Longest Active Turns ───────────────────────────────────────────────
|
|
590
618
|
if (stats.topTasks?.length) {
|
|
591
619
|
lines.push('');
|
|
592
|
-
lines.push(renderTable('Longest
|
|
620
|
+
lines.push(renderTable('Longest Active Turns', stats.topTasks.slice(0, 5), [
|
|
593
621
|
{ width: 6, align: 'left', get: r => pc.dim(dayOf(r.start).slice(5)) },
|
|
594
622
|
{ width: 9, align: 'right', get: r => pc.cyan(ms(taskDuration(r))) },
|
|
595
623
|
{ width: 11, align: 'right', get: r => pc.magenta(`${r.toolCalls} tools`) },
|