@os-eco/overstory-cli 0.6.11 → 0.7.2
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/README.md +12 -13
- package/agents/builder.md +1 -1
- package/agents/coordinator.md +12 -11
- package/agents/lead.md +25 -24
- package/agents/monitor.md +4 -4
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +5 -5
- package/agents/supervisor.md +36 -32
- package/package.json +5 -3
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/hooks-deployer.ts +7 -90
- package/src/agents/overlay.test.ts +30 -7
- package/src/agents/overlay.ts +10 -9
- package/src/commands/agents.test.ts +5 -0
- package/src/commands/clean.test.ts +3 -0
- package/src/commands/completions.ts +1 -1
- package/src/commands/coordinator.test.ts +1 -0
- package/src/commands/coordinator.ts +34 -18
- package/src/commands/costs.test.ts +6 -1
- package/src/commands/costs.ts +13 -20
- package/src/commands/dashboard.ts +38 -138
- package/src/commands/doctor.test.ts +1 -1
- package/src/commands/doctor.ts +2 -2
- package/src/commands/ecosystem.ts +2 -1
- package/src/commands/errors.test.ts +4 -5
- package/src/commands/errors.ts +4 -62
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +12 -106
- package/src/commands/init.test.ts +1 -2
- package/src/commands/init.ts +1 -8
- package/src/commands/inspect.test.ts +14 -0
- package/src/commands/inspect.ts +10 -44
- package/src/commands/log.test.ts +14 -0
- package/src/commands/log.ts +39 -0
- package/src/commands/logs.ts +7 -63
- package/src/commands/mail.test.ts +5 -0
- package/src/commands/metrics.test.ts +2 -2
- package/src/commands/metrics.ts +3 -17
- package/src/commands/monitor.ts +30 -16
- package/src/commands/nudge.test.ts +1 -0
- package/src/commands/prime.test.ts +2 -0
- package/src/commands/prime.ts +6 -2
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +12 -135
- package/src/commands/run.test.ts +1 -0
- package/src/commands/run.ts +7 -23
- package/src/commands/sling.test.ts +68 -1
- package/src/commands/sling.ts +62 -24
- package/src/commands/status.test.ts +1 -0
- package/src/commands/status.ts +4 -17
- package/src/commands/stop.test.ts +1 -0
- package/src/commands/supervisor.ts +35 -18
- package/src/commands/trace.test.ts +6 -6
- package/src/commands/trace.ts +11 -109
- package/src/commands/worktree.test.ts +9 -0
- package/src/config.ts +39 -0
- package/src/doctor/consistency.test.ts +14 -0
- package/src/e2e/init-sling-lifecycle.test.ts +3 -5
- package/src/index.ts +2 -1
- package/src/logging/format.ts +214 -0
- package/src/logging/theme.ts +132 -0
- package/src/mail/broadcast.test.ts +1 -0
- package/src/merge/resolver.ts +23 -4
- package/src/metrics/store.test.ts +46 -0
- package/src/metrics/store.ts +11 -0
- package/src/mulch/client.test.ts +20 -0
- package/src/mulch/client.ts +312 -45
- package/src/runtimes/claude.test.ts +616 -0
- package/src/runtimes/claude.ts +218 -0
- package/src/runtimes/pi-guards.test.ts +433 -0
- package/src/runtimes/pi-guards.ts +349 -0
- package/src/runtimes/pi.test.ts +620 -0
- package/src/runtimes/pi.ts +244 -0
- package/src/runtimes/registry.test.ts +86 -0
- package/src/runtimes/registry.ts +46 -0
- package/src/runtimes/types.ts +188 -0
- package/src/schema-consistency.test.ts +1 -0
- package/src/sessions/compat.ts +1 -0
- package/src/sessions/store.test.ts +31 -0
- package/src/sessions/store.ts +37 -4
- package/src/types.ts +21 -0
- package/src/watchdog/daemon.test.ts +7 -4
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -0
- package/src/watchdog/triage.ts +14 -4
- package/src/worktree/tmux.test.ts +28 -13
- package/src/worktree/tmux.ts +14 -28
package/src/commands/feed.ts
CHANGED
|
@@ -14,97 +14,14 @@ import { createEventStore } from "../events/store.ts";
|
|
|
14
14
|
import { jsonOutput } from "../json.ts";
|
|
15
15
|
import type { ColorFn } from "../logging/color.ts";
|
|
16
16
|
import { color } from "../logging/color.ts";
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
mail_sent: { label: "MAIL>", color: color.cyan },
|
|
26
|
-
mail_received: { label: "MAIL<", color: color.cyan },
|
|
27
|
-
spawn: { label: "SPAWN", color: color.magenta },
|
|
28
|
-
error: { label: "ERROR", color: color.red },
|
|
29
|
-
custom: { label: "CUSTM", color: color.gray },
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/** Color functions assigned to agents in order of first appearance. */
|
|
33
|
-
const AGENT_COLORS: readonly ColorFn[] = [
|
|
34
|
-
color.blue,
|
|
35
|
-
color.green,
|
|
36
|
-
color.yellow,
|
|
37
|
-
color.cyan,
|
|
38
|
-
color.magenta,
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Format an absolute time from an ISO timestamp.
|
|
43
|
-
* Returns "HH:MM:SS" portion.
|
|
44
|
-
*/
|
|
45
|
-
function formatAbsoluteTime(timestamp: string): string {
|
|
46
|
-
const match = /T(\d{2}:\d{2}:\d{2})/.exec(timestamp);
|
|
47
|
-
if (match?.[1]) {
|
|
48
|
-
return match[1];
|
|
49
|
-
}
|
|
50
|
-
return timestamp;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Build a detail string for a feed event based on its type and fields.
|
|
55
|
-
*/
|
|
56
|
-
function buildEventDetail(event: StoredEvent): string {
|
|
57
|
-
const parts: string[] = [];
|
|
58
|
-
|
|
59
|
-
if (event.toolName) {
|
|
60
|
-
parts.push(`tool=${event.toolName}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (event.toolDurationMs !== null) {
|
|
64
|
-
parts.push(`${event.toolDurationMs}ms`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (event.data) {
|
|
68
|
-
try {
|
|
69
|
-
const parsed: unknown = JSON.parse(event.data);
|
|
70
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
71
|
-
const data = parsed as Record<string, unknown>;
|
|
72
|
-
for (const [key, value] of Object.entries(data)) {
|
|
73
|
-
if (value !== null && value !== undefined) {
|
|
74
|
-
const strValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
75
|
-
// Truncate long values
|
|
76
|
-
const truncated = strValue.length > 60 ? `${strValue.slice(0, 57)}...` : strValue;
|
|
77
|
-
parts.push(`${key}=${truncated}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
// data is not valid JSON; show it raw if short enough
|
|
83
|
-
if (event.data.length <= 60) {
|
|
84
|
-
parts.push(event.data);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return parts.join(" ");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Assign a stable color function to each agent based on order of first appearance.
|
|
94
|
-
*/
|
|
95
|
-
function buildAgentColorMap(events: StoredEvent[]): Map<string, ColorFn> {
|
|
96
|
-
const colorMap = new Map<string, ColorFn>();
|
|
97
|
-
for (const event of events) {
|
|
98
|
-
if (!colorMap.has(event.agentName)) {
|
|
99
|
-
const colorIndex = colorMap.size % AGENT_COLORS.length;
|
|
100
|
-
const agentColorFn = AGENT_COLORS[colorIndex];
|
|
101
|
-
if (agentColorFn !== undefined) {
|
|
102
|
-
colorMap.set(event.agentName, agentColorFn);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return colorMap;
|
|
107
|
-
}
|
|
17
|
+
import {
|
|
18
|
+
buildAgentColorMap,
|
|
19
|
+
buildEventDetail,
|
|
20
|
+
extendAgentColorMap,
|
|
21
|
+
formatAbsoluteTime,
|
|
22
|
+
} from "../logging/format.ts";
|
|
23
|
+
import { eventLabel } from "../logging/theme.ts";
|
|
24
|
+
import type { StoredEvent } from "../types.ts";
|
|
108
25
|
|
|
109
26
|
/**
|
|
110
27
|
* Print a single event in compact feed format:
|
|
@@ -115,16 +32,13 @@ function printEvent(event: StoredEvent, colorMap: Map<string, ColorFn>): void {
|
|
|
115
32
|
|
|
116
33
|
const timeStr = formatAbsoluteTime(event.createdAt);
|
|
117
34
|
|
|
118
|
-
const
|
|
119
|
-
label: event.eventType.padEnd(5),
|
|
120
|
-
color: color.gray,
|
|
121
|
-
};
|
|
35
|
+
const label = eventLabel(event.eventType);
|
|
122
36
|
|
|
123
37
|
const levelColorFn =
|
|
124
38
|
event.level === "error" ? color.red : event.level === "warn" ? color.yellow : null;
|
|
125
39
|
const applyLevel = (text: string) => (levelColorFn ? levelColorFn(text) : text);
|
|
126
40
|
|
|
127
|
-
const detail = buildEventDetail(event);
|
|
41
|
+
const detail = buildEventDetail(event, 60);
|
|
128
42
|
const detailSuffix = detail ? ` ${color.dim(detail)}` : "";
|
|
129
43
|
|
|
130
44
|
const agentColorFn = colorMap.get(event.agentName) ?? color.gray;
|
|
@@ -132,7 +46,7 @@ function printEvent(event: StoredEvent, colorMap: Map<string, ColorFn>): void {
|
|
|
132
46
|
|
|
133
47
|
w(
|
|
134
48
|
`${color.dim(timeStr)} ` +
|
|
135
|
-
`${applyLevel(
|
|
49
|
+
`${applyLevel(label.color(color.bold(label.compact)))}` +
|
|
136
50
|
`${agentLabel}${detailSuffix}\n`,
|
|
137
51
|
);
|
|
138
52
|
}
|
|
@@ -291,15 +205,7 @@ async function executeFeed(opts: FeedOpts): Promise<void> {
|
|
|
291
205
|
if (newEvents.length > 0) {
|
|
292
206
|
if (!json) {
|
|
293
207
|
// Update color map for any new agents
|
|
294
|
-
|
|
295
|
-
if (!globalColorMap.has(event.agentName)) {
|
|
296
|
-
const colorIndex = globalColorMap.size % AGENT_COLORS.length;
|
|
297
|
-
const agentColorFn = AGENT_COLORS[colorIndex];
|
|
298
|
-
if (agentColorFn !== undefined) {
|
|
299
|
-
globalColorMap.set(event.agentName, agentColorFn);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
208
|
+
extendAgentColorMap(globalColorMap, newEvents);
|
|
303
209
|
|
|
304
210
|
// Print new events
|
|
305
211
|
for (const event of newEvents) {
|
|
@@ -17,7 +17,6 @@ const AGENT_DEF_FILES = [
|
|
|
17
17
|
"reviewer.md",
|
|
18
18
|
"lead.md",
|
|
19
19
|
"merger.md",
|
|
20
|
-
"supervisor.md",
|
|
21
20
|
"coordinator.md",
|
|
22
21
|
"monitor.md",
|
|
23
22
|
];
|
|
@@ -46,7 +45,7 @@ describe("initCommand: agent-defs deployment", () => {
|
|
|
46
45
|
await cleanupTempDir(tempDir);
|
|
47
46
|
});
|
|
48
47
|
|
|
49
|
-
test("creates .overstory/agent-defs/ with all
|
|
48
|
+
test("creates .overstory/agent-defs/ with all 7 agent definition files (supervisor deprecated)", async () => {
|
|
50
49
|
await initCommand({});
|
|
51
50
|
|
|
52
51
|
const agentDefsDir = join(tempDir, ".overstory", "agent-defs");
|
package/src/commands/init.ts
CHANGED
|
@@ -241,14 +241,6 @@ function buildAgentManifest(): AgentManifest {
|
|
|
241
241
|
canSpawn: true,
|
|
242
242
|
constraints: ["read-only", "no-worktree"],
|
|
243
243
|
},
|
|
244
|
-
supervisor: {
|
|
245
|
-
file: "supervisor.md",
|
|
246
|
-
model: "opus",
|
|
247
|
-
tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Task"],
|
|
248
|
-
capabilities: ["coordinate", "supervise"],
|
|
249
|
-
canSpawn: true,
|
|
250
|
-
constraints: [],
|
|
251
|
-
},
|
|
252
244
|
monitor: {
|
|
253
245
|
file: "monitor.md",
|
|
254
246
|
model: "sonnet",
|
|
@@ -595,6 +587,7 @@ export async function initCommand(opts: InitOptions): Promise<void> {
|
|
|
595
587
|
const agentDefFiles = await readdir(overstoryAgentsDir);
|
|
596
588
|
for (const fileName of agentDefFiles) {
|
|
597
589
|
if (!fileName.endsWith(".md")) continue;
|
|
590
|
+
if (fileName === "supervisor.md") continue; // Deprecated: not deployed to new projects
|
|
598
591
|
const source = Bun.file(join(overstoryAgentsDir, fileName));
|
|
599
592
|
const content = await source.text();
|
|
600
593
|
await Bun.write(join(agentDefsTarget, fileName), content);
|
|
@@ -153,6 +153,7 @@ describe("inspectCommand", () => {
|
|
|
153
153
|
lastActivity: new Date().toISOString(),
|
|
154
154
|
escalationLevel: 0,
|
|
155
155
|
stalledSince: null,
|
|
156
|
+
transcriptPath: null,
|
|
156
157
|
});
|
|
157
158
|
store.close();
|
|
158
159
|
|
|
@@ -185,6 +186,7 @@ describe("inspectCommand", () => {
|
|
|
185
186
|
lastActivity: new Date().toISOString(),
|
|
186
187
|
escalationLevel: 0,
|
|
187
188
|
stalledSince: null,
|
|
189
|
+
transcriptPath: null,
|
|
188
190
|
});
|
|
189
191
|
store.close();
|
|
190
192
|
|
|
@@ -221,6 +223,7 @@ describe("inspectCommand", () => {
|
|
|
221
223
|
lastActivity: new Date(Date.now() - 5_000).toISOString(),
|
|
222
224
|
escalationLevel: 0,
|
|
223
225
|
stalledSince: null,
|
|
226
|
+
transcriptPath: null,
|
|
224
227
|
});
|
|
225
228
|
store.close();
|
|
226
229
|
|
|
@@ -257,6 +260,7 @@ describe("inspectCommand", () => {
|
|
|
257
260
|
lastActivity: new Date().toISOString(),
|
|
258
261
|
escalationLevel: 0,
|
|
259
262
|
stalledSince: null,
|
|
263
|
+
transcriptPath: null,
|
|
260
264
|
});
|
|
261
265
|
store.close();
|
|
262
266
|
|
|
@@ -296,6 +300,7 @@ describe("inspectCommand", () => {
|
|
|
296
300
|
lastActivity: new Date().toISOString(),
|
|
297
301
|
escalationLevel: 0,
|
|
298
302
|
stalledSince: null,
|
|
303
|
+
transcriptPath: null,
|
|
299
304
|
});
|
|
300
305
|
store.close();
|
|
301
306
|
|
|
@@ -331,6 +336,7 @@ describe("inspectCommand", () => {
|
|
|
331
336
|
lastActivity: new Date().toISOString(),
|
|
332
337
|
escalationLevel: 0,
|
|
333
338
|
stalledSince: null,
|
|
339
|
+
transcriptPath: null,
|
|
334
340
|
});
|
|
335
341
|
store.close();
|
|
336
342
|
|
|
@@ -366,6 +372,7 @@ describe("inspectCommand", () => {
|
|
|
366
372
|
lastActivity: new Date().toISOString(),
|
|
367
373
|
escalationLevel: 0,
|
|
368
374
|
stalledSince: null,
|
|
375
|
+
transcriptPath: null,
|
|
369
376
|
});
|
|
370
377
|
store.close();
|
|
371
378
|
|
|
@@ -410,6 +417,7 @@ describe("inspectCommand", () => {
|
|
|
410
417
|
lastActivity: new Date().toISOString(),
|
|
411
418
|
escalationLevel: 0,
|
|
412
419
|
stalledSince: null,
|
|
420
|
+
transcriptPath: null,
|
|
413
421
|
});
|
|
414
422
|
store.close();
|
|
415
423
|
|
|
@@ -453,6 +461,7 @@ describe("inspectCommand", () => {
|
|
|
453
461
|
lastActivity: new Date().toISOString(),
|
|
454
462
|
escalationLevel: 0,
|
|
455
463
|
stalledSince: null,
|
|
464
|
+
transcriptPath: null,
|
|
456
465
|
});
|
|
457
466
|
store.close();
|
|
458
467
|
|
|
@@ -502,6 +511,7 @@ describe("inspectCommand", () => {
|
|
|
502
511
|
lastActivity: new Date().toISOString(),
|
|
503
512
|
escalationLevel: 0,
|
|
504
513
|
stalledSince: null,
|
|
514
|
+
transcriptPath: null,
|
|
505
515
|
});
|
|
506
516
|
store.close();
|
|
507
517
|
|
|
@@ -539,6 +549,7 @@ describe("inspectCommand", () => {
|
|
|
539
549
|
lastActivity: new Date().toISOString(),
|
|
540
550
|
escalationLevel: 0,
|
|
541
551
|
stalledSince: null,
|
|
552
|
+
transcriptPath: null,
|
|
542
553
|
});
|
|
543
554
|
store.close();
|
|
544
555
|
|
|
@@ -577,6 +588,7 @@ describe("inspectCommand", () => {
|
|
|
577
588
|
lastActivity: new Date().toISOString(),
|
|
578
589
|
escalationLevel: 0,
|
|
579
590
|
stalledSince: null,
|
|
591
|
+
transcriptPath: null,
|
|
580
592
|
});
|
|
581
593
|
store.close();
|
|
582
594
|
|
|
@@ -614,6 +626,7 @@ describe("inspectCommand", () => {
|
|
|
614
626
|
lastActivity: new Date().toISOString(),
|
|
615
627
|
escalationLevel: 0,
|
|
616
628
|
stalledSince: null,
|
|
629
|
+
transcriptPath: null,
|
|
617
630
|
});
|
|
618
631
|
store.close();
|
|
619
632
|
|
|
@@ -652,6 +665,7 @@ describe("inspectCommand", () => {
|
|
|
652
665
|
lastActivity: new Date().toISOString(),
|
|
653
666
|
escalationLevel: 0,
|
|
654
667
|
stalledSince: null,
|
|
668
|
+
transcriptPath: null,
|
|
655
669
|
});
|
|
656
670
|
store.close();
|
|
657
671
|
|
package/src/commands/inspect.ts
CHANGED
|
@@ -11,45 +11,13 @@ import { loadConfig } from "../config.ts";
|
|
|
11
11
|
import { ValidationError } from "../errors.ts";
|
|
12
12
|
import { createEventStore } from "../events/store.ts";
|
|
13
13
|
import { jsonOutput } from "../json.ts";
|
|
14
|
-
import { accent
|
|
14
|
+
import { accent } from "../logging/color.ts";
|
|
15
|
+
import { formatDuration } from "../logging/format.ts";
|
|
16
|
+
import { renderHeader, separator, stateIconColored } from "../logging/theme.ts";
|
|
15
17
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
16
18
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
17
19
|
import type { AgentSession, StoredEvent, ToolStats } from "../types.ts";
|
|
18
20
|
|
|
19
|
-
/**
|
|
20
|
-
* Format a duration in ms to a human-readable string.
|
|
21
|
-
*/
|
|
22
|
-
function formatDuration(ms: number): string {
|
|
23
|
-
const seconds = Math.floor(ms / 1000);
|
|
24
|
-
if (seconds < 60) return `${seconds}s`;
|
|
25
|
-
const minutes = Math.floor(seconds / 60);
|
|
26
|
-
const remainSec = seconds % 60;
|
|
27
|
-
if (minutes < 60) return `${minutes}m ${remainSec}s`;
|
|
28
|
-
const hours = Math.floor(minutes / 60);
|
|
29
|
-
const remainMin = minutes % 60;
|
|
30
|
-
return `${hours}h ${remainMin}m`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get colored state icon based on agent state.
|
|
35
|
-
*/
|
|
36
|
-
function getStateIcon(state: AgentSession["state"]): string {
|
|
37
|
-
switch (state) {
|
|
38
|
-
case "booting":
|
|
39
|
-
return color.green("-");
|
|
40
|
-
case "working":
|
|
41
|
-
return color.cyan(">");
|
|
42
|
-
case "stalled":
|
|
43
|
-
return color.yellow("!");
|
|
44
|
-
case "completed":
|
|
45
|
-
return color.dim("x");
|
|
46
|
-
case "zombie":
|
|
47
|
-
return color.dim("x");
|
|
48
|
-
default:
|
|
49
|
-
return "?";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
21
|
/**
|
|
54
22
|
* Extract current file from most recent Edit/Write/Read tool_start event.
|
|
55
23
|
*/
|
|
@@ -253,12 +221,10 @@ export function printInspectData(data: InspectData): void {
|
|
|
253
221
|
const w = process.stdout.write.bind(process.stdout);
|
|
254
222
|
const { session } = data;
|
|
255
223
|
|
|
256
|
-
w(`\
|
|
257
|
-
w(`${"═".repeat(80)}\n\n`);
|
|
224
|
+
w(`\n${renderHeader(`Agent Inspection: ${accent(session.agentName)}`)}\n\n`);
|
|
258
225
|
|
|
259
226
|
// Agent state and metadata
|
|
260
|
-
|
|
261
|
-
w(`${stateIcon} State: ${session.state}\n`);
|
|
227
|
+
w(`${stateIconColored(session.state)} State: ${session.state}\n`);
|
|
262
228
|
w(`Last activity: ${formatDuration(data.timeSinceLastActivity)} ago\n`);
|
|
263
229
|
w(`Task: ${accent(session.taskId)}\n`);
|
|
264
230
|
w(`Capability: ${session.capability}\n`);
|
|
@@ -278,7 +244,7 @@ export function printInspectData(data: InspectData): void {
|
|
|
278
244
|
// Token usage
|
|
279
245
|
if (data.tokenUsage) {
|
|
280
246
|
w("Token Usage\n");
|
|
281
|
-
w(`${
|
|
247
|
+
w(`${separator()}\n`);
|
|
282
248
|
w(` Input: ${data.tokenUsage.inputTokens.toLocaleString()}\n`);
|
|
283
249
|
w(` Output: ${data.tokenUsage.outputTokens.toLocaleString()}\n`);
|
|
284
250
|
w(` Cache read: ${data.tokenUsage.cacheReadTokens.toLocaleString()}\n`);
|
|
@@ -295,7 +261,7 @@ export function printInspectData(data: InspectData): void {
|
|
|
295
261
|
// Tool usage statistics (top 10)
|
|
296
262
|
if (data.toolStats.length > 0) {
|
|
297
263
|
w("Tool Usage (Top 10)\n");
|
|
298
|
-
w(`${
|
|
264
|
+
w(`${separator()}\n`);
|
|
299
265
|
const top10 = data.toolStats.slice(0, 10);
|
|
300
266
|
for (const stat of top10) {
|
|
301
267
|
const avgMs = stat.avgDurationMs.toFixed(0);
|
|
@@ -308,7 +274,7 @@ export function printInspectData(data: InspectData): void {
|
|
|
308
274
|
// Recent tool calls
|
|
309
275
|
if (data.recentToolCalls.length > 0) {
|
|
310
276
|
w(`Recent Tool Calls (last ${data.recentToolCalls.length})\n`);
|
|
311
|
-
w(`${
|
|
277
|
+
w(`${separator()}\n`);
|
|
312
278
|
for (const call of data.recentToolCalls) {
|
|
313
279
|
const time = new Date(call.timestamp).toLocaleTimeString();
|
|
314
280
|
const duration = call.durationMs !== null ? `${call.durationMs}ms` : "pending";
|
|
@@ -324,9 +290,9 @@ export function printInspectData(data: InspectData): void {
|
|
|
324
290
|
// tmux output
|
|
325
291
|
if (data.tmuxOutput) {
|
|
326
292
|
w("Live Tmux Output\n");
|
|
327
|
-
w(`${
|
|
293
|
+
w(`${separator()}\n`);
|
|
328
294
|
w(`${data.tmuxOutput}\n`);
|
|
329
|
-
w(`${
|
|
295
|
+
w(`${separator()}\n`);
|
|
330
296
|
}
|
|
331
297
|
}
|
|
332
298
|
|
package/src/commands/log.test.ts
CHANGED
|
@@ -229,6 +229,7 @@ describe("logCommand", () => {
|
|
|
229
229
|
lastActivity: new Date().toISOString(),
|
|
230
230
|
escalationLevel: 0,
|
|
231
231
|
stalledSince: null,
|
|
232
|
+
transcriptPath: null,
|
|
232
233
|
};
|
|
233
234
|
const store = createSessionStore(dbPath);
|
|
234
235
|
store.upsert(session);
|
|
@@ -284,6 +285,7 @@ describe("logCommand", () => {
|
|
|
284
285
|
lastActivity: new Date().toISOString(),
|
|
285
286
|
escalationLevel: 0,
|
|
286
287
|
stalledSince: null,
|
|
288
|
+
transcriptPath: null,
|
|
287
289
|
};
|
|
288
290
|
const sessStore = createSessionStore(sessionsDbPath);
|
|
289
291
|
sessStore.upsert(session);
|
|
@@ -324,6 +326,7 @@ describe("logCommand", () => {
|
|
|
324
326
|
lastActivity: new Date(Date.now() - 60_000).toISOString(),
|
|
325
327
|
escalationLevel: 0,
|
|
326
328
|
stalledSince: null,
|
|
329
|
+
transcriptPath: null,
|
|
327
330
|
};
|
|
328
331
|
const store = createSessionStore(dbPath);
|
|
329
332
|
store.upsert(session);
|
|
@@ -363,6 +366,7 @@ describe("logCommand", () => {
|
|
|
363
366
|
lastActivity: new Date(Date.now() - 60_000).toISOString(),
|
|
364
367
|
escalationLevel: 0,
|
|
365
368
|
stalledSince: null,
|
|
369
|
+
transcriptPath: null,
|
|
366
370
|
};
|
|
367
371
|
const store = createSessionStore(dbPath);
|
|
368
372
|
store.upsert(session);
|
|
@@ -400,6 +404,7 @@ describe("logCommand", () => {
|
|
|
400
404
|
lastActivity: new Date().toISOString(),
|
|
401
405
|
escalationLevel: 0,
|
|
402
406
|
stalledSince: null,
|
|
407
|
+
transcriptPath: null,
|
|
403
408
|
});
|
|
404
409
|
sessionStoreLocal.close();
|
|
405
410
|
|
|
@@ -457,6 +462,7 @@ describe("logCommand", () => {
|
|
|
457
462
|
lastActivity: new Date().toISOString(),
|
|
458
463
|
escalationLevel: 0,
|
|
459
464
|
stalledSince: null,
|
|
465
|
+
transcriptPath: null,
|
|
460
466
|
});
|
|
461
467
|
sessionStoreLocal.close();
|
|
462
468
|
|
|
@@ -487,6 +493,7 @@ describe("logCommand", () => {
|
|
|
487
493
|
lastActivity: new Date().toISOString(),
|
|
488
494
|
escalationLevel: 0,
|
|
489
495
|
stalledSince: null,
|
|
496
|
+
transcriptPath: null,
|
|
490
497
|
});
|
|
491
498
|
sessionStoreLocal.close();
|
|
492
499
|
|
|
@@ -541,6 +548,7 @@ describe("logCommand", () => {
|
|
|
541
548
|
lastActivity: new Date().toISOString(),
|
|
542
549
|
escalationLevel: 0,
|
|
543
550
|
stalledSince: null,
|
|
551
|
+
transcriptPath: null,
|
|
544
552
|
});
|
|
545
553
|
sessionStoreLocal.close();
|
|
546
554
|
|
|
@@ -594,6 +602,7 @@ describe("logCommand", () => {
|
|
|
594
602
|
lastActivity: new Date().toISOString(),
|
|
595
603
|
escalationLevel: 0,
|
|
596
604
|
stalledSince: null,
|
|
605
|
+
transcriptPath: null,
|
|
597
606
|
};
|
|
598
607
|
const store = createSessionStore(dbPath);
|
|
599
608
|
store.upsert(session);
|
|
@@ -634,6 +643,7 @@ describe("logCommand", () => {
|
|
|
634
643
|
lastActivity: new Date().toISOString(),
|
|
635
644
|
escalationLevel: 0,
|
|
636
645
|
stalledSince: null,
|
|
646
|
+
transcriptPath: null,
|
|
637
647
|
};
|
|
638
648
|
const store = createSessionStore(dbPath);
|
|
639
649
|
store.upsert(session);
|
|
@@ -676,6 +686,7 @@ describe("logCommand", () => {
|
|
|
676
686
|
lastActivity: oldTimestamp,
|
|
677
687
|
escalationLevel: 0,
|
|
678
688
|
stalledSince: null,
|
|
689
|
+
transcriptPath: null,
|
|
679
690
|
};
|
|
680
691
|
const store = createSessionStore(dbPath);
|
|
681
692
|
store.upsert(session);
|
|
@@ -715,6 +726,7 @@ describe("logCommand", () => {
|
|
|
715
726
|
lastActivity: new Date().toISOString(),
|
|
716
727
|
escalationLevel: 0,
|
|
717
728
|
stalledSince: null,
|
|
729
|
+
transcriptPath: null,
|
|
718
730
|
};
|
|
719
731
|
const store = createSessionStore(dbPath);
|
|
720
732
|
store.upsert(session);
|
|
@@ -800,6 +812,7 @@ describe("logCommand", () => {
|
|
|
800
812
|
lastActivity: new Date().toISOString(),
|
|
801
813
|
escalationLevel: 0,
|
|
802
814
|
stalledSince: null,
|
|
815
|
+
transcriptPath: null,
|
|
803
816
|
};
|
|
804
817
|
const store = createSessionStore(dbPath);
|
|
805
818
|
store.upsert(session);
|
|
@@ -839,6 +852,7 @@ describe("logCommand", () => {
|
|
|
839
852
|
lastActivity: new Date().toISOString(),
|
|
840
853
|
escalationLevel: 0,
|
|
841
854
|
stalledSince: null,
|
|
855
|
+
transcriptPath: null,
|
|
842
856
|
};
|
|
843
857
|
const store = createSessionStore(dbPath);
|
|
844
858
|
store.upsert(session);
|
package/src/commands/log.ts
CHANGED
|
@@ -176,6 +176,23 @@ async function resolveTranscriptPath(
|
|
|
176
176
|
logsBase: string,
|
|
177
177
|
agentName: string,
|
|
178
178
|
): Promise<string | null> {
|
|
179
|
+
// Check SessionStore for a runtime-provided transcript path
|
|
180
|
+
try {
|
|
181
|
+
const { store } = openSessionStore(join(projectRoot, ".overstory"));
|
|
182
|
+
try {
|
|
183
|
+
const session = store.getByName(agentName);
|
|
184
|
+
if (session?.transcriptPath) {
|
|
185
|
+
if (await Bun.file(session.transcriptPath).exists()) {
|
|
186
|
+
return session.transcriptPath;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} finally {
|
|
190
|
+
store.close();
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
// Non-fatal: fall through to legacy resolution
|
|
194
|
+
}
|
|
195
|
+
|
|
179
196
|
// Check cached path first
|
|
180
197
|
const cachePath = join(logsBase, agentName, ".transcript-path");
|
|
181
198
|
const cacheFile = Bun.file(cachePath);
|
|
@@ -194,6 +211,17 @@ async function resolveTranscriptPath(
|
|
|
194
211
|
const directPath = join(claudeProjectsDir, projectKey, `${sessionId}.jsonl`);
|
|
195
212
|
if (await Bun.file(directPath).exists()) {
|
|
196
213
|
await Bun.write(cachePath, directPath);
|
|
214
|
+
// Save discovered path to SessionStore for future lookups
|
|
215
|
+
try {
|
|
216
|
+
const { store: writeStore } = openSessionStore(join(projectRoot, ".overstory"));
|
|
217
|
+
try {
|
|
218
|
+
writeStore.updateTranscriptPath(agentName, directPath);
|
|
219
|
+
} finally {
|
|
220
|
+
writeStore.close();
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// Non-fatal: cache write failure should not break transcript resolution
|
|
224
|
+
}
|
|
197
225
|
return directPath;
|
|
198
226
|
}
|
|
199
227
|
|
|
@@ -205,6 +233,17 @@ async function resolveTranscriptPath(
|
|
|
205
233
|
const candidate = join(claudeProjectsDir, project, `${sessionId}.jsonl`);
|
|
206
234
|
if (await Bun.file(candidate).exists()) {
|
|
207
235
|
await Bun.write(cachePath, candidate);
|
|
236
|
+
// Save discovered path to SessionStore for future lookups
|
|
237
|
+
try {
|
|
238
|
+
const { store: writeStore } = openSessionStore(join(projectRoot, ".overstory"));
|
|
239
|
+
try {
|
|
240
|
+
writeStore.updateTranscriptPath(agentName, candidate);
|
|
241
|
+
} finally {
|
|
242
|
+
writeStore.close();
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
// Non-fatal: cache write failure should not break transcript resolution
|
|
246
|
+
}
|
|
208
247
|
return candidate;
|
|
209
248
|
}
|
|
210
249
|
}
|
package/src/commands/logs.ts
CHANGED
|
@@ -14,8 +14,9 @@ import { Command } from "commander";
|
|
|
14
14
|
import { loadConfig } from "../config.ts";
|
|
15
15
|
import { ValidationError } from "../errors.ts";
|
|
16
16
|
import { jsonOutput } from "../json.ts";
|
|
17
|
-
import type { ColorFn } from "../logging/color.ts";
|
|
18
17
|
import { color } from "../logging/color.ts";
|
|
18
|
+
import { formatAbsoluteTime, formatDate, logLevelColor, logLevelLabel } from "../logging/format.ts";
|
|
19
|
+
import { renderHeader } from "../logging/theme.ts";
|
|
19
20
|
import type { LogEvent } from "../types.ts";
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -52,30 +53,6 @@ function parseRelativeTime(timeStr: string): Date {
|
|
|
52
53
|
return new Date(timeStr);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
/**
|
|
56
|
-
* Format the date portion of an ISO timestamp.
|
|
57
|
-
* Returns "YYYY-MM-DD".
|
|
58
|
-
*/
|
|
59
|
-
function formatDate(timestamp: string): string {
|
|
60
|
-
const match = /^(\d{4}-\d{2}-\d{2})/.exec(timestamp);
|
|
61
|
-
if (match?.[1]) {
|
|
62
|
-
return match[1];
|
|
63
|
-
}
|
|
64
|
-
return "";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Format an absolute time from an ISO timestamp.
|
|
69
|
-
* Returns "HH:MM:SS" portion.
|
|
70
|
-
*/
|
|
71
|
-
function formatAbsoluteTime(timestamp: string): string {
|
|
72
|
-
const match = /T(\d{2}:\d{2}:\d{2})/.exec(timestamp);
|
|
73
|
-
if (match?.[1]) {
|
|
74
|
-
return match[1];
|
|
75
|
-
}
|
|
76
|
-
return timestamp;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
56
|
/**
|
|
80
57
|
* Build a detail string for a log event based on its data.
|
|
81
58
|
*/
|
|
@@ -235,46 +212,13 @@ function filterEvents(
|
|
|
235
212
|
});
|
|
236
213
|
}
|
|
237
214
|
|
|
238
|
-
/** Resolve a log level string to a color function. */
|
|
239
|
-
function getLevelColor(level: string): ColorFn {
|
|
240
|
-
switch (level) {
|
|
241
|
-
case "debug":
|
|
242
|
-
return color.gray;
|
|
243
|
-
case "info":
|
|
244
|
-
return color.blue;
|
|
245
|
-
case "warn":
|
|
246
|
-
return color.yellow;
|
|
247
|
-
case "error":
|
|
248
|
-
return color.red;
|
|
249
|
-
default:
|
|
250
|
-
return color.gray;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/** Resolve a log level to its label string. */
|
|
255
|
-
function getLevelLabel(level: string): string {
|
|
256
|
-
switch (level) {
|
|
257
|
-
case "debug":
|
|
258
|
-
return "DBG";
|
|
259
|
-
case "info":
|
|
260
|
-
return "INF";
|
|
261
|
-
case "warn":
|
|
262
|
-
return "WRN";
|
|
263
|
-
case "error":
|
|
264
|
-
return "ERR";
|
|
265
|
-
default:
|
|
266
|
-
return String(level).slice(0, 3).toUpperCase();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
215
|
/**
|
|
271
216
|
* Print log events with ANSI colors and date separators.
|
|
272
217
|
*/
|
|
273
218
|
function printLogs(events: LogEvent[]): void {
|
|
274
219
|
const w = process.stdout.write.bind(process.stdout);
|
|
275
220
|
|
|
276
|
-
w(`${
|
|
277
|
-
w(`${"=".repeat(70)}\n`);
|
|
221
|
+
w(`${renderHeader("Logs")}\n`);
|
|
278
222
|
|
|
279
223
|
if (events.length === 0) {
|
|
280
224
|
w(`${color.dim("No log files found.")}\n`);
|
|
@@ -297,8 +241,8 @@ function printLogs(events: LogEvent[]): void {
|
|
|
297
241
|
}
|
|
298
242
|
|
|
299
243
|
const time = formatAbsoluteTime(event.timestamp);
|
|
300
|
-
const levelColorFn =
|
|
301
|
-
const levelStr =
|
|
244
|
+
const levelColorFn = logLevelColor(event.level);
|
|
245
|
+
const levelStr = logLevelLabel(event.level);
|
|
302
246
|
|
|
303
247
|
const agentLabel = event.agentName ? `[${event.agentName}]` : "[unknown]";
|
|
304
248
|
const detail = buildLogDetail(event);
|
|
@@ -373,8 +317,8 @@ async function followLogs(
|
|
|
373
317
|
|
|
374
318
|
// Print immediately
|
|
375
319
|
const time = formatAbsoluteTime(event.timestamp);
|
|
376
|
-
const levelColorFn =
|
|
377
|
-
const levelStr =
|
|
320
|
+
const levelColorFn = logLevelColor(event.level);
|
|
321
|
+
const levelStr = logLevelLabel(event.level);
|
|
378
322
|
|
|
379
323
|
const agentLabel = event.agentName ? `[${event.agentName}]` : "[unknown]";
|
|
380
324
|
const detail = buildLogDetail(event);
|