takomi 2.1.29 → 2.1.30
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/agents/coder.md +6 -0
- package/.pi/agents/orchestrator.md +16 -0
- package/.pi/extensions/oauth-router/README.md +4 -2
- package/.pi/extensions/oauth-router/commands.ts +37 -9
- package/.pi/extensions/oauth-router/config.ts +7 -1
- package/.pi/extensions/oauth-router/index.ts +1 -1
- package/.pi/extensions/oauth-router/oauth-flow.ts +71 -22
- package/.pi/extensions/oauth-router/provider.ts +112 -12
- package/.pi/extensions/oauth-router/state.ts +12 -1
- package/.pi/extensions/oauth-router/types.ts +15 -2
- package/.pi/extensions/takomi-runtime/context-panel.ts +130 -27
- package/.pi/extensions/takomi-runtime/index.ts +2 -0
- package/.pi/extensions/takomi-runtime/takomi-stats.js +679 -679
- package/.pi/prompts/build-prompt.md +15 -0
- package/.pi/prompts/genesis-prompt.md +8 -0
- package/.pi/prompts/takomi-prompt.md +16 -0
- package/package.json +1 -1
- package/src/takomi-stats.js +749 -679
|
@@ -53,6 +53,8 @@ export type ContextPanelState = {
|
|
|
53
53
|
toolUses: ToolUseCount;
|
|
54
54
|
sessionStart: number;
|
|
55
55
|
activeMs: number;
|
|
56
|
+
activityIntervals: ActivityInterval[];
|
|
57
|
+
pendingToolStarts: Record<string, number>;
|
|
56
58
|
lastActivityAt: number;
|
|
57
59
|
lastToolAt: number;
|
|
58
60
|
runtime: ContextRuntimeState;
|
|
@@ -76,6 +78,11 @@ type BoardCountsCacheEntry = {
|
|
|
76
78
|
counts?: BoardCounts;
|
|
77
79
|
};
|
|
78
80
|
|
|
81
|
+
type ActivityInterval = {
|
|
82
|
+
start: number;
|
|
83
|
+
end: number;
|
|
84
|
+
};
|
|
85
|
+
|
|
79
86
|
const BOARD_COUNTS_CACHE_MS = 1_000;
|
|
80
87
|
const boardCountsCache = new Map<string, BoardCountsCacheEntry>();
|
|
81
88
|
|
|
@@ -85,6 +92,8 @@ function createEmptyState(sessionStart = Date.now(), runtime: ContextRuntimeStat
|
|
|
85
92
|
toolUses: {},
|
|
86
93
|
sessionStart,
|
|
87
94
|
activeMs: 0,
|
|
95
|
+
activityIntervals: [],
|
|
96
|
+
pendingToolStarts: {},
|
|
88
97
|
lastActivityAt: sessionStart,
|
|
89
98
|
lastToolAt: 0,
|
|
90
99
|
runtime,
|
|
@@ -173,14 +182,28 @@ function toTimestampMs(entry: { timestamp?: unknown; message?: { timestamp?: unk
|
|
|
173
182
|
return undefined;
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
function
|
|
177
|
-
const sorted =
|
|
178
|
-
|
|
185
|
+
function mergeIntervals(intervals: ActivityInterval[]): ActivityInterval[] {
|
|
186
|
+
const sorted = intervals
|
|
187
|
+
.filter((interval) => Number.isFinite(interval.start) && Number.isFinite(interval.end) && interval.end > interval.start)
|
|
188
|
+
.map((interval) => ({ start: interval.start, end: interval.end }))
|
|
189
|
+
.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
190
|
+
if (sorted.length === 0) return [];
|
|
191
|
+
|
|
192
|
+
const merged: ActivityInterval[] = [sorted[0]];
|
|
179
193
|
for (let i = 1; i < sorted.length; i += 1) {
|
|
180
|
-
const
|
|
181
|
-
|
|
194
|
+
const current = sorted[i];
|
|
195
|
+
const previous = merged[merged.length - 1];
|
|
196
|
+
if (current.start <= previous.end) {
|
|
197
|
+
previous.end = Math.max(previous.end, current.end);
|
|
198
|
+
} else {
|
|
199
|
+
merged.push({ ...current });
|
|
200
|
+
}
|
|
182
201
|
}
|
|
183
|
-
return
|
|
202
|
+
return merged;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function calculateActiveMs(intervals: ActivityInterval[]): number {
|
|
206
|
+
return mergeIntervals(intervals).reduce((total, interval) => total + (interval.end - interval.start), 0);
|
|
184
207
|
}
|
|
185
208
|
|
|
186
209
|
function formatDisplayPath(filePath: string, cwd?: string): string {
|
|
@@ -290,7 +313,10 @@ class ContextPanelComponent implements Component {
|
|
|
290
313
|
|
|
291
314
|
if (ctx) {
|
|
292
315
|
const age = formatDuration(Date.now() - state.sessionStart);
|
|
293
|
-
const
|
|
316
|
+
const pendingStarts = Object.values(state.pendingToolStarts ?? {});
|
|
317
|
+
const pendingStart = pendingStarts.length > 0 ? Math.min(...pendingStarts) : undefined;
|
|
318
|
+
const activeMs = pendingStart !== undefined ? state.activeMs + Math.max(0, Date.now() - pendingStart) : state.activeMs;
|
|
319
|
+
const active = formatDuration(activeMs);
|
|
294
320
|
lines.push(`${pad}${theme.fg("dim", "Age:")} ${theme.fg("muted", age)}`);
|
|
295
321
|
lines.push(`${pad}${theme.fg("dim", "Active:")} ${theme.fg("muted", active)}`);
|
|
296
322
|
|
|
@@ -376,6 +402,7 @@ export class TakomiContextPanel {
|
|
|
376
402
|
private overlayHandle?: OverlayHandle;
|
|
377
403
|
private requestRender?: () => void;
|
|
378
404
|
private lastCtx?: ExtensionContext;
|
|
405
|
+
private readonly toolStartTimes = new Map<string, number>();
|
|
379
406
|
|
|
380
407
|
getState(): ContextPanelState {
|
|
381
408
|
return this.state;
|
|
@@ -390,6 +417,33 @@ export class TakomiContextPanel {
|
|
|
390
417
|
this.requestRender?.();
|
|
391
418
|
}
|
|
392
419
|
|
|
420
|
+
private recomputeActiveMs(): void {
|
|
421
|
+
this.state.activeMs = calculateActiveMs(this.state.activityIntervals);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private addActivityInterval(start: number, end: number): void {
|
|
425
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return;
|
|
426
|
+
this.state.activityIntervals.push({ start, end });
|
|
427
|
+
this.recomputeActiveMs();
|
|
428
|
+
this.state.lastActivityAt = Math.max(this.state.lastActivityAt, end);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private noteActivity(timestamp = Date.now(), allowLongGap = false): void {
|
|
432
|
+
const previous = this.state.lastActivityAt;
|
|
433
|
+
const delta = timestamp - previous;
|
|
434
|
+
if (delta > 0 && (allowLongGap || delta <= ACTIVE_GAP_THRESHOLD_MS)) {
|
|
435
|
+
this.addActivityInterval(previous, timestamp);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
this.state.lastActivityAt = Math.max(previous, timestamp);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private noteToolWindow(toolCallId: string | undefined, start: number, end: number): void {
|
|
442
|
+
this.noteActivity(start);
|
|
443
|
+
this.addActivityInterval(start, end);
|
|
444
|
+
if (toolCallId) delete this.state.pendingToolStarts[toolCallId];
|
|
445
|
+
}
|
|
446
|
+
|
|
393
447
|
trackFileChange(change: FileChange): void {
|
|
394
448
|
this.state.fileChanges.push({ ...change, timestamp: change.timestamp || Date.now() });
|
|
395
449
|
this.state.lastToolAt = Date.now();
|
|
@@ -397,21 +451,39 @@ export class TakomiContextPanel {
|
|
|
397
451
|
}
|
|
398
452
|
|
|
399
453
|
trackActivity(timestamp = Date.now()): void {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
454
|
+
this.noteActivity(timestamp);
|
|
455
|
+
this.requestRender?.();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
trackToolStart(toolCallId: string | undefined, timestamp = Date.now()): void {
|
|
459
|
+
if (toolCallId) {
|
|
460
|
+
this.toolStartTimes.set(toolCallId, timestamp);
|
|
461
|
+
this.state.pendingToolStarts[toolCallId] = timestamp;
|
|
462
|
+
}
|
|
463
|
+
this.noteActivity(timestamp);
|
|
464
|
+
this.requestRender?.();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
trackToolEnd(toolCallId: string | undefined, timestamp = Date.now()): void {
|
|
468
|
+
const start = toolCallId ? this.toolStartTimes.get(toolCallId) : undefined;
|
|
469
|
+
if (start !== undefined) {
|
|
470
|
+
this.noteToolWindow(toolCallId, start, timestamp);
|
|
471
|
+
if (toolCallId) this.toolStartTimes.delete(toolCallId);
|
|
472
|
+
} else {
|
|
473
|
+
if (toolCallId) delete this.state.pendingToolStarts[toolCallId];
|
|
474
|
+
this.noteActivity(timestamp);
|
|
475
|
+
}
|
|
404
476
|
this.requestRender?.();
|
|
405
477
|
}
|
|
406
478
|
|
|
407
479
|
trackToolUse(toolName: string): void {
|
|
408
480
|
this.state.toolUses[toolName] = (this.state.toolUses[toolName] ?? 0) + 1;
|
|
409
481
|
this.state.lastToolAt = Date.now();
|
|
410
|
-
this.trackActivity(this.state.lastToolAt);
|
|
411
482
|
}
|
|
412
483
|
|
|
413
484
|
resetSession(): void {
|
|
414
485
|
this.state = createEmptyState(Date.now(), this.state.runtime);
|
|
486
|
+
this.toolStartTimes.clear();
|
|
415
487
|
this.requestRender?.();
|
|
416
488
|
}
|
|
417
489
|
|
|
@@ -438,36 +510,65 @@ export class TakomiContextPanel {
|
|
|
438
510
|
timestamp?: number;
|
|
439
511
|
};
|
|
440
512
|
}>;
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
513
|
+
const next = createEmptyState(Date.now(), this.state.runtime);
|
|
514
|
+
const intervals: ActivityInterval[] = [];
|
|
515
|
+
const toolCalls = new Map<string, { name: string; input: ToolCallInput; startedAt: number }>();
|
|
516
|
+
let lastActivityPoint: number | undefined;
|
|
517
|
+
let firstSessionTs: number | undefined;
|
|
518
|
+
this.toolStartTimes.clear();
|
|
447
519
|
|
|
448
520
|
for (const entry of branch) {
|
|
521
|
+
const ts = toTimestampMs(entry);
|
|
449
522
|
const message = entry.message;
|
|
450
|
-
if (
|
|
523
|
+
if (ts !== undefined) firstSessionTs = firstSessionTs === undefined ? ts : Math.min(firstSessionTs, ts);
|
|
524
|
+
if (!message || ts === undefined) continue;
|
|
525
|
+
|
|
451
526
|
if (message.role === "assistant" && Array.isArray(message.content)) {
|
|
527
|
+
let sawToolCall = false;
|
|
452
528
|
for (const block of message.content) {
|
|
453
529
|
const record = asRecord(block);
|
|
454
530
|
if (record.type !== "toolCall") continue;
|
|
455
531
|
const id = typeof record.id === "string" ? record.id : undefined;
|
|
456
532
|
const name = typeof record.name === "string" ? record.name : undefined;
|
|
457
533
|
if (!id || !name) continue;
|
|
458
|
-
|
|
534
|
+
sawToolCall = true;
|
|
535
|
+
toolCalls.set(id, { name, input: asRecord(record.arguments), startedAt: ts });
|
|
536
|
+
this.toolStartTimes.set(id, ts);
|
|
459
537
|
}
|
|
538
|
+
if (lastActivityPoint !== undefined && ts > lastActivityPoint && ts - lastActivityPoint <= ACTIVE_GAP_THRESHOLD_MS) {
|
|
539
|
+
intervals.push({ start: lastActivityPoint, end: ts });
|
|
540
|
+
}
|
|
541
|
+
lastActivityPoint = ts;
|
|
542
|
+
if (!sawToolCall) continue;
|
|
460
543
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
544
|
+
|
|
545
|
+
if (message.role === "toolResult" && message.toolName) {
|
|
546
|
+
next.toolUses[message.toolName] = (next.toolUses[message.toolName] ?? 0) + 1;
|
|
547
|
+
next.lastToolAt = ts;
|
|
548
|
+
const toolCall = message.toolCallId ? toolCalls.get(message.toolCallId) : undefined;
|
|
549
|
+
if (toolCall) {
|
|
550
|
+
intervals.push({ start: toolCall.startedAt, end: ts });
|
|
551
|
+
const change = changeFromTool(message.toolName, toolCall.input);
|
|
552
|
+
if (change) next.fileChanges.push({ ...change, timestamp: ts });
|
|
553
|
+
if (message.toolCallId) toolCalls.delete(message.toolCallId);
|
|
554
|
+
} else if (lastActivityPoint !== undefined && ts > lastActivityPoint && ts - lastActivityPoint <= ACTIVE_GAP_THRESHOLD_MS) {
|
|
555
|
+
intervals.push({ start: lastActivityPoint, end: ts });
|
|
556
|
+
}
|
|
557
|
+
lastActivityPoint = ts;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (lastActivityPoint !== undefined && ts > lastActivityPoint && ts - lastActivityPoint <= ACTIVE_GAP_THRESHOLD_MS) {
|
|
562
|
+
intervals.push({ start: lastActivityPoint, end: ts });
|
|
468
563
|
}
|
|
564
|
+
lastActivityPoint = ts;
|
|
469
565
|
}
|
|
470
566
|
|
|
567
|
+
next.sessionStart = firstSessionTs ?? next.sessionStart;
|
|
568
|
+
next.activityIntervals = intervals;
|
|
569
|
+
next.activeMs = calculateActiveMs(intervals);
|
|
570
|
+
next.lastActivityAt = lastActivityPoint ?? next.sessionStart;
|
|
571
|
+
|
|
471
572
|
this.state = next;
|
|
472
573
|
this.requestRender?.();
|
|
473
574
|
}
|
|
@@ -529,6 +630,7 @@ export function wireContextPanel(pi: ExtensionAPI, panel: TakomiContextPanel): v
|
|
|
529
630
|
const pendingWriteActions = new Map<string, "M" | "+">();
|
|
530
631
|
|
|
531
632
|
pi.on("tool_call", (event, ctx) => {
|
|
633
|
+
panel.trackToolStart(event.toolCallId, Date.now());
|
|
532
634
|
if (event.toolName !== "write") return;
|
|
533
635
|
const input = asRecord(event.input);
|
|
534
636
|
const filePath = normalizeToolPath(input);
|
|
@@ -546,6 +648,7 @@ export function wireContextPanel(pi: ExtensionAPI, panel: TakomiContextPanel): v
|
|
|
546
648
|
pi.on("tool_result", (event) => {
|
|
547
649
|
const toolName = event.toolName;
|
|
548
650
|
panel.trackToolUse(toolName);
|
|
651
|
+
panel.trackToolEnd(event.toolCallId, Date.now());
|
|
549
652
|
|
|
550
653
|
const input = asRecord(event.input);
|
|
551
654
|
const actionOverride = toolName === "write" ? pendingWriteActions.get(event.toolCallId) : undefined;
|
|
@@ -891,6 +891,8 @@ export default function takomiRuntime(pi: ExtensionAPI) {
|
|
|
891
891
|
"Session IDs must use the canonical timestamp format orch-YYYYMMDD-HHMMSS. Use the same sessionId for the authored docs folder and the board JSON state.",
|
|
892
892
|
"For high-quality orchestration sessions, provide sessionId, masterPlanMarkdown, and taskMarkdown values that match the authored session folder. If you already wrote docs/tasks/orchestrator-sessions/<id>, call this tool with sessionId=<id>; do not create a second session id.",
|
|
893
893
|
"JSON fields should carry IDs/status/roles/workflow/dependencies/checklists for tracking, not replace expressive markdown.",
|
|
894
|
+
"Do not use expand_stage as a placeholder generator. For Design/Build expansions, provide full taskMarkdown or complete objective/scope/definitionOfDone/expectedArtifacts/instructions for every task.",
|
|
895
|
+
"If a task packet would render with Scope/Definition Of Done/Expected Artifacts as None specified, repair it before launching subagents.",
|
|
894
896
|
"A new session should normally begin Genesis-first, then expand Design and Build into as many tasks as the scope actually needs.",
|
|
895
897
|
"If the request is small enough, do not force orchestration just because the tool exists.",
|
|
896
898
|
"If a reviewed task needs more work, reuse the task conversationId when you call takomi_subagent again, then update the board with the new result.",
|