@katyella/legio 0.1.0
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/CHANGELOG.md +422 -0
- package/LICENSE +21 -0
- package/README.md +555 -0
- package/agents/builder.md +141 -0
- package/agents/coordinator.md +351 -0
- package/agents/cto.md +196 -0
- package/agents/gateway.md +276 -0
- package/agents/lead.md +281 -0
- package/agents/merger.md +156 -0
- package/agents/monitor.md +212 -0
- package/agents/reviewer.md +142 -0
- package/agents/scout.md +131 -0
- package/agents/supervisor.md +416 -0
- package/bin/legio.mjs +38 -0
- package/package.json +77 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +102 -0
- package/src/agents/hooks-deployer.test.ts +1820 -0
- package/src/agents/hooks-deployer.ts +574 -0
- package/src/agents/identity.test.ts +614 -0
- package/src/agents/identity.ts +385 -0
- package/src/agents/lifecycle.test.ts +202 -0
- package/src/agents/lifecycle.ts +184 -0
- package/src/agents/manifest.test.ts +558 -0
- package/src/agents/manifest.ts +297 -0
- package/src/agents/overlay.test.ts +592 -0
- package/src/agents/overlay.ts +316 -0
- package/src/beads/client.test.ts +210 -0
- package/src/beads/client.ts +227 -0
- package/src/beads/molecules.test.ts +320 -0
- package/src/beads/molecules.ts +209 -0
- package/src/commands/agents.test.ts +325 -0
- package/src/commands/agents.ts +286 -0
- package/src/commands/clean.test.ts +730 -0
- package/src/commands/clean.ts +653 -0
- package/src/commands/completions.test.ts +346 -0
- package/src/commands/completions.ts +950 -0
- package/src/commands/coordinator.test.ts +1524 -0
- package/src/commands/coordinator.ts +880 -0
- package/src/commands/costs.test.ts +1015 -0
- package/src/commands/costs.ts +473 -0
- package/src/commands/dashboard.test.ts +94 -0
- package/src/commands/dashboard.ts +607 -0
- package/src/commands/doctor.test.ts +295 -0
- package/src/commands/doctor.ts +213 -0
- package/src/commands/down.test.ts +308 -0
- package/src/commands/down.ts +124 -0
- package/src/commands/errors.test.ts +648 -0
- package/src/commands/errors.ts +255 -0
- package/src/commands/feed.test.ts +579 -0
- package/src/commands/feed.ts +368 -0
- package/src/commands/gateway.test.ts +698 -0
- package/src/commands/gateway.ts +419 -0
- package/src/commands/group.test.ts +262 -0
- package/src/commands/group.ts +539 -0
- package/src/commands/hooks.test.ts +292 -0
- package/src/commands/hooks.ts +210 -0
- package/src/commands/init.test.ts +211 -0
- package/src/commands/init.ts +622 -0
- package/src/commands/inspect.test.ts +670 -0
- package/src/commands/inspect.ts +455 -0
- package/src/commands/log.test.ts +1556 -0
- package/src/commands/log.ts +752 -0
- package/src/commands/logs.test.ts +379 -0
- package/src/commands/logs.ts +544 -0
- package/src/commands/mail.test.ts +1726 -0
- package/src/commands/mail.ts +926 -0
- package/src/commands/merge.test.ts +676 -0
- package/src/commands/merge.ts +374 -0
- package/src/commands/metrics.test.ts +444 -0
- package/src/commands/metrics.ts +150 -0
- package/src/commands/monitor.test.ts +151 -0
- package/src/commands/monitor.ts +394 -0
- package/src/commands/nudge.test.ts +230 -0
- package/src/commands/nudge.ts +373 -0
- package/src/commands/prime.test.ts +467 -0
- package/src/commands/prime.ts +386 -0
- package/src/commands/replay.test.ts +742 -0
- package/src/commands/replay.ts +367 -0
- package/src/commands/run.test.ts +443 -0
- package/src/commands/run.ts +365 -0
- package/src/commands/server.test.ts +626 -0
- package/src/commands/server.ts +298 -0
- package/src/commands/sling.test.ts +810 -0
- package/src/commands/sling.ts +700 -0
- package/src/commands/spec.test.ts +206 -0
- package/src/commands/spec.ts +171 -0
- package/src/commands/status.test.ts +276 -0
- package/src/commands/status.ts +339 -0
- package/src/commands/stop.test.ts +357 -0
- package/src/commands/stop.ts +119 -0
- package/src/commands/supervisor.test.ts +186 -0
- package/src/commands/supervisor.ts +544 -0
- package/src/commands/trace.test.ts +746 -0
- package/src/commands/trace.ts +332 -0
- package/src/commands/up.test.ts +597 -0
- package/src/commands/up.ts +275 -0
- package/src/commands/watch.test.ts +152 -0
- package/src/commands/watch.ts +238 -0
- package/src/commands/worktree.test.ts +648 -0
- package/src/commands/worktree.ts +266 -0
- package/src/config.test.ts +496 -0
- package/src/config.ts +616 -0
- package/src/doctor/agents.test.ts +448 -0
- package/src/doctor/agents.ts +396 -0
- package/src/doctor/config-check.test.ts +184 -0
- package/src/doctor/config-check.ts +185 -0
- package/src/doctor/consistency.test.ts +645 -0
- package/src/doctor/consistency.ts +294 -0
- package/src/doctor/databases.test.ts +284 -0
- package/src/doctor/databases.ts +211 -0
- package/src/doctor/dependencies.test.ts +150 -0
- package/src/doctor/dependencies.ts +179 -0
- package/src/doctor/logs.test.ts +244 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +210 -0
- package/src/doctor/merge-queue.ts +144 -0
- package/src/doctor/structure.test.ts +285 -0
- package/src/doctor/structure.ts +195 -0
- package/src/doctor/types.ts +37 -0
- package/src/doctor/version.test.ts +130 -0
- package/src/doctor/version.ts +131 -0
- package/src/e2e/chat-flow.test.ts +346 -0
- package/src/e2e/init-sling-lifecycle.test.ts +288 -0
- package/src/errors.test.ts +21 -0
- package/src/errors.ts +246 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +344 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/global-setup.ts +14 -0
- package/src/index.ts +339 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/logging/color.test.ts +118 -0
- package/src/logging/color.ts +71 -0
- package/src/logging/logger.test.ts +812 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +258 -0
- package/src/logging/reporter.ts +109 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/mail/broadcast.test.ts +203 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +873 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +815 -0
- package/src/mail/store.ts +402 -0
- package/src/merge/queue.test.ts +449 -0
- package/src/merge/queue.ts +262 -0
- package/src/merge/resolver.test.ts +1453 -0
- package/src/merge/resolver.ts +759 -0
- package/src/metrics/store.test.ts +1167 -0
- package/src/metrics/store.ts +511 -0
- package/src/metrics/summary.test.ts +397 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +643 -0
- package/src/metrics/transcript.ts +351 -0
- package/src/mulch/client.test.ts +547 -0
- package/src/mulch/client.ts +416 -0
- package/src/server/audit-store.test.ts +384 -0
- package/src/server/audit-store.ts +257 -0
- package/src/server/headless.test.ts +180 -0
- package/src/server/headless.ts +151 -0
- package/src/server/index.test.ts +241 -0
- package/src/server/index.ts +317 -0
- package/src/server/public/app.js +187 -0
- package/src/server/public/apple-touch-icon.png +0 -0
- package/src/server/public/components/agent-badge.js +37 -0
- package/src/server/public/components/data-table.js +114 -0
- package/src/server/public/components/gateway-chat.js +256 -0
- package/src/server/public/components/issue-card.js +96 -0
- package/src/server/public/components/layout.js +88 -0
- package/src/server/public/components/message-bubble.js +120 -0
- package/src/server/public/components/stat-card.js +26 -0
- package/src/server/public/components/terminal-panel.js +140 -0
- package/src/server/public/favicon-16.png +0 -0
- package/src/server/public/favicon-32.png +0 -0
- package/src/server/public/favicon.ico +0 -0
- package/src/server/public/favicon.png +0 -0
- package/src/server/public/index.html +64 -0
- package/src/server/public/lib/api.js +35 -0
- package/src/server/public/lib/markdown.js +8 -0
- package/src/server/public/lib/preact-setup.js +8 -0
- package/src/server/public/lib/state.js +99 -0
- package/src/server/public/lib/utils.js +309 -0
- package/src/server/public/lib/ws.js +79 -0
- package/src/server/public/views/chat.js +983 -0
- package/src/server/public/views/costs.js +692 -0
- package/src/server/public/views/dashboard.js +781 -0
- package/src/server/public/views/gateway-chat.js +622 -0
- package/src/server/public/views/inspect.js +399 -0
- package/src/server/public/views/issues.js +470 -0
- package/src/server/public/views/setup.js +94 -0
- package/src/server/public/views/task-detail.js +422 -0
- package/src/server/routes.test.ts +3816 -0
- package/src/server/routes.ts +1964 -0
- package/src/server/websocket.test.ts +288 -0
- package/src/server/websocket.ts +196 -0
- package/src/sessions/compat.test.ts +109 -0
- package/src/sessions/compat.ts +17 -0
- package/src/sessions/store.test.ts +969 -0
- package/src/sessions/store.ts +480 -0
- package/src/test-helpers.test.ts +97 -0
- package/src/test-helpers.ts +143 -0
- package/src/types.ts +708 -0
- package/src/watchdog/daemon.test.ts +1233 -0
- package/src/watchdog/daemon.ts +533 -0
- package/src/watchdog/health.test.ts +371 -0
- package/src/watchdog/health.ts +248 -0
- package/src/watchdog/triage.test.ts +162 -0
- package/src/watchdog/triage.ts +193 -0
- package/src/worktree/manager.test.ts +444 -0
- package/src/worktree/manager.ts +224 -0
- package/src/worktree/tmux.test.ts +1238 -0
- package/src/worktree/tmux.ts +644 -0
- package/templates/CLAUDE.md.tmpl +89 -0
- package/templates/hooks.json.tmpl +132 -0
- package/templates/overlay.md.tmpl +79 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `legio replay` command.
|
|
3
|
+
*
|
|
4
|
+
* Uses real better-sqlite3 (temp files) to test the replay command end-to-end.
|
|
5
|
+
* Captures process.stdout.write to verify output formatting.
|
|
6
|
+
*
|
|
7
|
+
* Real implementations used for: filesystem (temp dirs), SQLite (EventStore).
|
|
8
|
+
* No mocks needed -- all dependencies are cheap and local.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
15
|
+
import { ValidationError } from "../errors.ts";
|
|
16
|
+
import { createEventStore } from "../events/store.ts";
|
|
17
|
+
import type { InsertEvent } from "../types.ts";
|
|
18
|
+
import { replayCommand } from "./replay.ts";
|
|
19
|
+
|
|
20
|
+
/** Helper to create an InsertEvent with sensible defaults. */
|
|
21
|
+
function makeEvent(overrides: Partial<InsertEvent> = {}): InsertEvent {
|
|
22
|
+
return {
|
|
23
|
+
runId: "run-001",
|
|
24
|
+
agentName: "builder-1",
|
|
25
|
+
sessionId: "sess-abc",
|
|
26
|
+
eventType: "tool_start",
|
|
27
|
+
toolName: "Read",
|
|
28
|
+
toolArgs: '{"file": "src/index.ts"}',
|
|
29
|
+
toolDurationMs: null,
|
|
30
|
+
level: "info",
|
|
31
|
+
data: null,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("replayCommand", () => {
|
|
37
|
+
let chunks: string[];
|
|
38
|
+
let originalWrite: typeof process.stdout.write;
|
|
39
|
+
let tempDir: string;
|
|
40
|
+
let originalCwd: string;
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
// Spy on stdout
|
|
44
|
+
chunks = [];
|
|
45
|
+
originalWrite = process.stdout.write;
|
|
46
|
+
process.stdout.write = ((chunk: string) => {
|
|
47
|
+
chunks.push(chunk);
|
|
48
|
+
return true;
|
|
49
|
+
}) as typeof process.stdout.write;
|
|
50
|
+
|
|
51
|
+
// Create temp dir with .legio/config.yaml structure
|
|
52
|
+
tempDir = await mkdtemp(join(tmpdir(), "replay-test-"));
|
|
53
|
+
const legioDir = join(tempDir, ".legio");
|
|
54
|
+
await mkdir(legioDir, { recursive: true });
|
|
55
|
+
await writeFile(
|
|
56
|
+
join(legioDir, "config.yaml"),
|
|
57
|
+
`project:\n name: test\n root: ${tempDir}\n canonicalBranch: main\n`,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Change to temp dir so loadConfig() works
|
|
61
|
+
originalCwd = process.cwd();
|
|
62
|
+
process.chdir(tempDir);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(async () => {
|
|
66
|
+
process.stdout.write = originalWrite;
|
|
67
|
+
process.chdir(originalCwd);
|
|
68
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function output(): string {
|
|
72
|
+
return chunks.join("");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// === Help flag ===
|
|
76
|
+
|
|
77
|
+
describe("help flag", () => {
|
|
78
|
+
test("--help shows help text", async () => {
|
|
79
|
+
await replayCommand(["--help"]);
|
|
80
|
+
const out = output();
|
|
81
|
+
|
|
82
|
+
expect(out).toContain("legio replay");
|
|
83
|
+
expect(out).toContain("--run");
|
|
84
|
+
expect(out).toContain("--agent");
|
|
85
|
+
expect(out).toContain("--json");
|
|
86
|
+
expect(out).toContain("--since");
|
|
87
|
+
expect(out).toContain("--until");
|
|
88
|
+
expect(out).toContain("--limit");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("-h shows help text", async () => {
|
|
92
|
+
await replayCommand(["-h"]);
|
|
93
|
+
const out = output();
|
|
94
|
+
|
|
95
|
+
expect(out).toContain("legio replay");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// === Argument parsing ===
|
|
100
|
+
|
|
101
|
+
describe("argument parsing", () => {
|
|
102
|
+
test("--limit with non-numeric value throws ValidationError", async () => {
|
|
103
|
+
await expect(replayCommand(["--limit", "abc"])).rejects.toThrow(ValidationError);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("--limit with zero throws ValidationError", async () => {
|
|
107
|
+
await expect(replayCommand(["--limit", "0"])).rejects.toThrow(ValidationError);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("--limit with negative value throws ValidationError", async () => {
|
|
111
|
+
await expect(replayCommand(["--limit", "-5"])).rejects.toThrow(ValidationError);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("--since with invalid timestamp throws ValidationError", async () => {
|
|
115
|
+
await expect(replayCommand(["--since", "not-a-date"])).rejects.toThrow(ValidationError);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("--until with invalid timestamp throws ValidationError", async () => {
|
|
119
|
+
await expect(replayCommand(["--until", "not-a-date"])).rejects.toThrow(ValidationError);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// === Missing events.db (graceful handling) ===
|
|
124
|
+
|
|
125
|
+
describe("missing events.db", () => {
|
|
126
|
+
test("text mode outputs friendly message when no events.db exists", async () => {
|
|
127
|
+
await replayCommand([]);
|
|
128
|
+
const out = output();
|
|
129
|
+
|
|
130
|
+
expect(out).toBe("No events data yet.\n");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("JSON mode outputs empty array when no events.db exists", async () => {
|
|
134
|
+
await replayCommand(["--json"]);
|
|
135
|
+
const out = output();
|
|
136
|
+
|
|
137
|
+
expect(out).toBe("[]\n");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// === JSON output mode ===
|
|
142
|
+
|
|
143
|
+
describe("JSON output mode", () => {
|
|
144
|
+
test("outputs valid JSON array with events", async () => {
|
|
145
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
146
|
+
const store = createEventStore(dbPath);
|
|
147
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "session_start" }));
|
|
148
|
+
store.insert(makeEvent({ agentName: "scout-1", eventType: "tool_start" }));
|
|
149
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "session_end" }));
|
|
150
|
+
store.close();
|
|
151
|
+
|
|
152
|
+
await replayCommand(["--run", "run-001", "--json"]);
|
|
153
|
+
const out = output();
|
|
154
|
+
|
|
155
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
156
|
+
expect(parsed).toHaveLength(3);
|
|
157
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("JSON output includes expected fields", async () => {
|
|
161
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
162
|
+
const store = createEventStore(dbPath);
|
|
163
|
+
store.insert(
|
|
164
|
+
makeEvent({
|
|
165
|
+
agentName: "builder-1",
|
|
166
|
+
eventType: "tool_start",
|
|
167
|
+
toolName: "Bash",
|
|
168
|
+
level: "info",
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
store.close();
|
|
172
|
+
|
|
173
|
+
await replayCommand(["--run", "run-001", "--json"]);
|
|
174
|
+
const out = output();
|
|
175
|
+
|
|
176
|
+
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
177
|
+
expect(parsed).toHaveLength(1);
|
|
178
|
+
const event = parsed[0];
|
|
179
|
+
expect(event).toBeDefined();
|
|
180
|
+
expect(event?.agentName).toBe("builder-1");
|
|
181
|
+
expect(event?.eventType).toBe("tool_start");
|
|
182
|
+
expect(event?.toolName).toBe("Bash");
|
|
183
|
+
expect(event?.level).toBe("info");
|
|
184
|
+
expect(event?.createdAt).toBeTruthy();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("JSON output returns empty array when no events match run", async () => {
|
|
188
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
189
|
+
const store = createEventStore(dbPath);
|
|
190
|
+
store.insert(makeEvent({ runId: "run-other" }));
|
|
191
|
+
store.close();
|
|
192
|
+
|
|
193
|
+
await replayCommand(["--run", "run-001", "--json"]);
|
|
194
|
+
const out = output();
|
|
195
|
+
|
|
196
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
197
|
+
expect(parsed).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// === Human output format ===
|
|
202
|
+
|
|
203
|
+
describe("human output format", () => {
|
|
204
|
+
test("shows Replay header", async () => {
|
|
205
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
206
|
+
const store = createEventStore(dbPath);
|
|
207
|
+
store.insert(makeEvent());
|
|
208
|
+
store.close();
|
|
209
|
+
|
|
210
|
+
await replayCommand(["--run", "run-001"]);
|
|
211
|
+
const out = output();
|
|
212
|
+
|
|
213
|
+
expect(out).toContain("Replay");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("shows separator line", async () => {
|
|
217
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
218
|
+
const store = createEventStore(dbPath);
|
|
219
|
+
store.insert(makeEvent());
|
|
220
|
+
store.close();
|
|
221
|
+
|
|
222
|
+
await replayCommand(["--run", "run-001"]);
|
|
223
|
+
const out = output();
|
|
224
|
+
|
|
225
|
+
expect(out).toContain("=".repeat(70));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("shows event count", async () => {
|
|
229
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
230
|
+
const store = createEventStore(dbPath);
|
|
231
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "session_start" }));
|
|
232
|
+
store.insert(makeEvent({ agentName: "scout-1", eventType: "tool_start" }));
|
|
233
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "session_end" }));
|
|
234
|
+
store.close();
|
|
235
|
+
|
|
236
|
+
await replayCommand(["--run", "run-001"]);
|
|
237
|
+
const out = output();
|
|
238
|
+
|
|
239
|
+
expect(out).toContain("3 events");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("shows singular event count", async () => {
|
|
243
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
244
|
+
const store = createEventStore(dbPath);
|
|
245
|
+
store.insert(makeEvent());
|
|
246
|
+
store.close();
|
|
247
|
+
|
|
248
|
+
await replayCommand(["--run", "run-001"]);
|
|
249
|
+
const out = output();
|
|
250
|
+
|
|
251
|
+
expect(out).toContain("1 event");
|
|
252
|
+
expect(out).not.toMatch(/1 events/);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("no events shows 'No events found' message", async () => {
|
|
256
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
257
|
+
const store = createEventStore(dbPath);
|
|
258
|
+
store.insert(makeEvent({ runId: "run-other" }));
|
|
259
|
+
store.close();
|
|
260
|
+
|
|
261
|
+
await replayCommand(["--run", "run-001"]);
|
|
262
|
+
const out = output();
|
|
263
|
+
|
|
264
|
+
expect(out).toContain("No events found");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("agent name is shown in brackets for every event", async () => {
|
|
268
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
269
|
+
const store = createEventStore(dbPath);
|
|
270
|
+
store.insert(makeEvent({ agentName: "builder-1" }));
|
|
271
|
+
store.insert(makeEvent({ agentName: "scout-1" }));
|
|
272
|
+
store.close();
|
|
273
|
+
|
|
274
|
+
await replayCommand(["--run", "run-001"]);
|
|
275
|
+
const out = output();
|
|
276
|
+
|
|
277
|
+
expect(out).toContain("[builder-1]");
|
|
278
|
+
expect(out).toContain("[scout-1]");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("event type labels are shown", async () => {
|
|
282
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
283
|
+
const store = createEventStore(dbPath);
|
|
284
|
+
store.insert(makeEvent({ eventType: "session_start" }));
|
|
285
|
+
store.insert(makeEvent({ eventType: "tool_start" }));
|
|
286
|
+
store.insert(makeEvent({ eventType: "error", level: "error" }));
|
|
287
|
+
store.close();
|
|
288
|
+
|
|
289
|
+
await replayCommand(["--run", "run-001"]);
|
|
290
|
+
const out = output();
|
|
291
|
+
|
|
292
|
+
expect(out).toContain("SESSION +");
|
|
293
|
+
expect(out).toContain("TOOL START");
|
|
294
|
+
expect(out).toContain("ERROR");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("tool name is shown in detail", async () => {
|
|
298
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
299
|
+
const store = createEventStore(dbPath);
|
|
300
|
+
store.insert(makeEvent({ eventType: "tool_start", toolName: "Bash" }));
|
|
301
|
+
store.close();
|
|
302
|
+
|
|
303
|
+
await replayCommand(["--run", "run-001"]);
|
|
304
|
+
const out = output();
|
|
305
|
+
|
|
306
|
+
expect(out).toContain("tool=Bash");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("date separator appears in timeline", async () => {
|
|
310
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
311
|
+
const store = createEventStore(dbPath);
|
|
312
|
+
store.insert(makeEvent());
|
|
313
|
+
store.close();
|
|
314
|
+
|
|
315
|
+
await replayCommand(["--run", "run-001"]);
|
|
316
|
+
const out = output();
|
|
317
|
+
|
|
318
|
+
expect(out).toMatch(/---\s+\d{4}-\d{2}-\d{2}\s+---/);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// === --run filter ===
|
|
323
|
+
|
|
324
|
+
describe("--run filter", () => {
|
|
325
|
+
test("filters events by run ID", async () => {
|
|
326
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
327
|
+
const store = createEventStore(dbPath);
|
|
328
|
+
store.insert(makeEvent({ runId: "run-001", agentName: "builder-1" }));
|
|
329
|
+
store.insert(makeEvent({ runId: "run-002", agentName: "scout-1" }));
|
|
330
|
+
store.insert(makeEvent({ runId: "run-001", agentName: "builder-2" }));
|
|
331
|
+
store.close();
|
|
332
|
+
|
|
333
|
+
await replayCommand(["--run", "run-001", "--json"]);
|
|
334
|
+
const out = output();
|
|
335
|
+
|
|
336
|
+
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
337
|
+
expect(parsed).toHaveLength(2);
|
|
338
|
+
for (const event of parsed) {
|
|
339
|
+
expect(event.runId).toBe("run-001");
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("--run with --since filters both", async () => {
|
|
344
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
345
|
+
const store = createEventStore(dbPath);
|
|
346
|
+
store.insert(makeEvent({ runId: "run-001" }));
|
|
347
|
+
store.close();
|
|
348
|
+
|
|
349
|
+
// Future since should return no events
|
|
350
|
+
await replayCommand(["--run", "run-001", "--json", "--since", "2099-01-01T00:00:00Z"]);
|
|
351
|
+
const out = output();
|
|
352
|
+
|
|
353
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
354
|
+
expect(parsed).toEqual([]);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// === --agent filter ===
|
|
359
|
+
|
|
360
|
+
describe("--agent filter", () => {
|
|
361
|
+
test("single --agent filters to one agent", async () => {
|
|
362
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
363
|
+
const store = createEventStore(dbPath);
|
|
364
|
+
store.insert(makeEvent({ agentName: "builder-1" }));
|
|
365
|
+
store.insert(makeEvent({ agentName: "scout-1" }));
|
|
366
|
+
store.insert(makeEvent({ agentName: "builder-1" }));
|
|
367
|
+
store.close();
|
|
368
|
+
|
|
369
|
+
await replayCommand(["--agent", "builder-1", "--json"]);
|
|
370
|
+
const out = output();
|
|
371
|
+
|
|
372
|
+
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
373
|
+
expect(parsed).toHaveLength(2);
|
|
374
|
+
for (const event of parsed) {
|
|
375
|
+
expect(event.agentName).toBe("builder-1");
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("multiple --agent flags merge events from all agents", async () => {
|
|
380
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
381
|
+
const store = createEventStore(dbPath);
|
|
382
|
+
store.insert(makeEvent({ agentName: "builder-1" }));
|
|
383
|
+
store.insert(makeEvent({ agentName: "scout-1" }));
|
|
384
|
+
store.insert(makeEvent({ agentName: "reviewer-1" }));
|
|
385
|
+
store.insert(makeEvent({ agentName: "builder-1" }));
|
|
386
|
+
store.close();
|
|
387
|
+
|
|
388
|
+
await replayCommand(["--agent", "builder-1", "--agent", "scout-1", "--json"]);
|
|
389
|
+
const out = output();
|
|
390
|
+
|
|
391
|
+
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
392
|
+
expect(parsed).toHaveLength(3);
|
|
393
|
+
const agents = new Set(parsed.map((e) => e.agentName));
|
|
394
|
+
expect(agents.has("builder-1")).toBe(true);
|
|
395
|
+
expect(agents.has("scout-1")).toBe(true);
|
|
396
|
+
expect(agents.has("reviewer-1")).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("--agent events are sorted chronologically", async () => {
|
|
400
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
401
|
+
const store = createEventStore(dbPath);
|
|
402
|
+
// Insert in order; they get sequential timestamps from SQLite
|
|
403
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "session_start" }));
|
|
404
|
+
store.insert(makeEvent({ agentName: "scout-1", eventType: "tool_start" }));
|
|
405
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "tool_end" }));
|
|
406
|
+
store.close();
|
|
407
|
+
|
|
408
|
+
await replayCommand(["--agent", "builder-1", "--agent", "scout-1", "--json"]);
|
|
409
|
+
const out = output();
|
|
410
|
+
|
|
411
|
+
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
412
|
+
expect(parsed).toHaveLength(3);
|
|
413
|
+
// They should be in chronological order
|
|
414
|
+
for (let i = 1; i < parsed.length; i++) {
|
|
415
|
+
const prev = parsed[i - 1];
|
|
416
|
+
const curr = parsed[i];
|
|
417
|
+
if (prev && curr) {
|
|
418
|
+
expect(
|
|
419
|
+
(prev.createdAt as string).localeCompare(curr.createdAt as string),
|
|
420
|
+
).toBeLessThanOrEqual(0);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("--agent with no matching events returns empty", async () => {
|
|
426
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
427
|
+
const store = createEventStore(dbPath);
|
|
428
|
+
store.insert(makeEvent({ agentName: "other-agent" }));
|
|
429
|
+
store.close();
|
|
430
|
+
|
|
431
|
+
await replayCommand(["--agent", "nonexistent", "--json"]);
|
|
432
|
+
const out = output();
|
|
433
|
+
|
|
434
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
435
|
+
expect(parsed).toEqual([]);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// === Default behavior (no --run or --agent) ===
|
|
440
|
+
|
|
441
|
+
describe("default behavior", () => {
|
|
442
|
+
test("uses current-run.txt when it exists", async () => {
|
|
443
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
444
|
+
const store = createEventStore(dbPath);
|
|
445
|
+
store.insert(makeEvent({ runId: "run-from-file", agentName: "builder-1" }));
|
|
446
|
+
store.insert(makeEvent({ runId: "run-other", agentName: "scout-1" }));
|
|
447
|
+
store.close();
|
|
448
|
+
|
|
449
|
+
// Write current-run.txt
|
|
450
|
+
await writeFile(join(tempDir, ".legio", "current-run.txt"), "run-from-file\n");
|
|
451
|
+
|
|
452
|
+
await replayCommand(["--json"]);
|
|
453
|
+
const out = output();
|
|
454
|
+
|
|
455
|
+
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
456
|
+
expect(parsed).toHaveLength(1);
|
|
457
|
+
expect(parsed[0]?.runId).toBe("run-from-file");
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("falls back to 24h timeline when no current-run.txt", async () => {
|
|
461
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
462
|
+
const store = createEventStore(dbPath);
|
|
463
|
+
// These events were just inserted, so they're within the last 24h
|
|
464
|
+
store.insert(makeEvent({ agentName: "builder-1", runId: null }));
|
|
465
|
+
store.insert(makeEvent({ agentName: "scout-1", runId: null }));
|
|
466
|
+
store.close();
|
|
467
|
+
|
|
468
|
+
await replayCommand(["--json"]);
|
|
469
|
+
const out = output();
|
|
470
|
+
|
|
471
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
472
|
+
expect(parsed).toHaveLength(2);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("falls back to timeline when current-run.txt is empty", async () => {
|
|
476
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
477
|
+
const store = createEventStore(dbPath);
|
|
478
|
+
store.insert(makeEvent({ agentName: "builder-1", runId: null }));
|
|
479
|
+
store.close();
|
|
480
|
+
|
|
481
|
+
await writeFile(join(tempDir, ".legio", "current-run.txt"), "");
|
|
482
|
+
|
|
483
|
+
await replayCommand(["--json"]);
|
|
484
|
+
const out = output();
|
|
485
|
+
|
|
486
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
487
|
+
expect(parsed).toHaveLength(1);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// === --limit flag ===
|
|
492
|
+
|
|
493
|
+
describe("--limit flag", () => {
|
|
494
|
+
test("limits the number of events returned", async () => {
|
|
495
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
496
|
+
const store = createEventStore(dbPath);
|
|
497
|
+
for (let i = 0; i < 10; i++) {
|
|
498
|
+
store.insert(makeEvent());
|
|
499
|
+
}
|
|
500
|
+
store.close();
|
|
501
|
+
|
|
502
|
+
await replayCommand(["--run", "run-001", "--json", "--limit", "3"]);
|
|
503
|
+
const out = output();
|
|
504
|
+
|
|
505
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
506
|
+
expect(parsed).toHaveLength(3);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("default limit is 200", async () => {
|
|
510
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
511
|
+
const store = createEventStore(dbPath);
|
|
512
|
+
for (let i = 0; i < 220; i++) {
|
|
513
|
+
store.insert(makeEvent());
|
|
514
|
+
}
|
|
515
|
+
store.close();
|
|
516
|
+
|
|
517
|
+
await replayCommand(["--run", "run-001", "--json"]);
|
|
518
|
+
const out = output();
|
|
519
|
+
|
|
520
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
521
|
+
expect(parsed).toHaveLength(200);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("--limit applies to merged --agent queries", async () => {
|
|
525
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
526
|
+
const store = createEventStore(dbPath);
|
|
527
|
+
for (let i = 0; i < 5; i++) {
|
|
528
|
+
store.insert(makeEvent({ agentName: "builder-1" }));
|
|
529
|
+
store.insert(makeEvent({ agentName: "scout-1" }));
|
|
530
|
+
}
|
|
531
|
+
store.close();
|
|
532
|
+
|
|
533
|
+
await replayCommand(["--agent", "builder-1", "--agent", "scout-1", "--json", "--limit", "4"]);
|
|
534
|
+
const out = output();
|
|
535
|
+
|
|
536
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
537
|
+
expect(parsed).toHaveLength(4);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// === --since and --until flags ===
|
|
542
|
+
|
|
543
|
+
describe("--since and --until flags", () => {
|
|
544
|
+
test("--since filters events after a timestamp", async () => {
|
|
545
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
546
|
+
const store = createEventStore(dbPath);
|
|
547
|
+
store.insert(makeEvent());
|
|
548
|
+
store.close();
|
|
549
|
+
|
|
550
|
+
// A future timestamp should return no events
|
|
551
|
+
await replayCommand(["--run", "run-001", "--json", "--since", "2099-01-01T00:00:00Z"]);
|
|
552
|
+
const out = output();
|
|
553
|
+
|
|
554
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
555
|
+
expect(parsed).toEqual([]);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test("--since with past timestamp returns all events", async () => {
|
|
559
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
560
|
+
const store = createEventStore(dbPath);
|
|
561
|
+
store.insert(makeEvent());
|
|
562
|
+
store.insert(makeEvent({ agentName: "scout-1" }));
|
|
563
|
+
store.close();
|
|
564
|
+
|
|
565
|
+
await replayCommand(["--run", "run-001", "--json", "--since", "2020-01-01T00:00:00Z"]);
|
|
566
|
+
const out = output();
|
|
567
|
+
|
|
568
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
569
|
+
expect(parsed).toHaveLength(2);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("--until with past timestamp returns no events", async () => {
|
|
573
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
574
|
+
const store = createEventStore(dbPath);
|
|
575
|
+
store.insert(makeEvent());
|
|
576
|
+
store.close();
|
|
577
|
+
|
|
578
|
+
await replayCommand(["--run", "run-001", "--json", "--until", "2000-01-01T00:00:00Z"]);
|
|
579
|
+
const out = output();
|
|
580
|
+
|
|
581
|
+
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
582
|
+
expect(parsed).toEqual([]);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("--since causes absolute timestamps in text mode", async () => {
|
|
586
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
587
|
+
const store = createEventStore(dbPath);
|
|
588
|
+
store.insert(makeEvent());
|
|
589
|
+
store.close();
|
|
590
|
+
|
|
591
|
+
await replayCommand(["--run", "run-001", "--since", "2020-01-01T00:00:00Z"]);
|
|
592
|
+
const out = output();
|
|
593
|
+
|
|
594
|
+
// Absolute timestamps show HH:MM:SS format
|
|
595
|
+
expect(out).toMatch(/\d{2}:\d{2}:\d{2}/);
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// === Interleaving order ===
|
|
600
|
+
|
|
601
|
+
describe("interleaving order", () => {
|
|
602
|
+
test("events from different agents are interleaved chronologically", async () => {
|
|
603
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
604
|
+
const store = createEventStore(dbPath);
|
|
605
|
+
// Insert in alternating order; SQLite timestamps are sequential
|
|
606
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "session_start" }));
|
|
607
|
+
store.insert(makeEvent({ agentName: "scout-1", eventType: "session_start" }));
|
|
608
|
+
store.insert(makeEvent({ agentName: "builder-1", eventType: "tool_start" }));
|
|
609
|
+
store.insert(makeEvent({ agentName: "scout-1", eventType: "tool_end" }));
|
|
610
|
+
store.close();
|
|
611
|
+
|
|
612
|
+
await replayCommand(["--run", "run-001", "--json"]);
|
|
613
|
+
const out = output();
|
|
614
|
+
|
|
615
|
+
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
616
|
+
expect(parsed).toHaveLength(4);
|
|
617
|
+
// Verify chronological order
|
|
618
|
+
for (let i = 1; i < parsed.length; i++) {
|
|
619
|
+
const prev = parsed[i - 1];
|
|
620
|
+
const curr = parsed[i];
|
|
621
|
+
if (prev && curr) {
|
|
622
|
+
expect(
|
|
623
|
+
(prev.createdAt as string).localeCompare(curr.createdAt as string),
|
|
624
|
+
).toBeLessThanOrEqual(0);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Verify interleaving: agents should alternate
|
|
628
|
+
expect(parsed[0]?.agentName).toBe("builder-1");
|
|
629
|
+
expect(parsed[1]?.agentName).toBe("scout-1");
|
|
630
|
+
expect(parsed[2]?.agentName).toBe("builder-1");
|
|
631
|
+
expect(parsed[3]?.agentName).toBe("scout-1");
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// === Agent color assignment ===
|
|
636
|
+
|
|
637
|
+
describe("agent color assignment", () => {
|
|
638
|
+
test("different agents get color-labeled brackets in human output", async () => {
|
|
639
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
640
|
+
const store = createEventStore(dbPath);
|
|
641
|
+
store.insert(makeEvent({ agentName: "builder-1" }));
|
|
642
|
+
store.insert(makeEvent({ agentName: "scout-1" }));
|
|
643
|
+
store.insert(makeEvent({ agentName: "reviewer-1" }));
|
|
644
|
+
store.close();
|
|
645
|
+
|
|
646
|
+
await replayCommand(["--run", "run-001"]);
|
|
647
|
+
const out = output();
|
|
648
|
+
|
|
649
|
+
// All three agents should appear in brackets
|
|
650
|
+
expect(out).toContain("[builder-1]");
|
|
651
|
+
expect(out).toContain("[scout-1]");
|
|
652
|
+
expect(out).toContain("[reviewer-1]");
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// === Edge cases ===
|
|
657
|
+
|
|
658
|
+
describe("edge cases", () => {
|
|
659
|
+
test("handles event with all null optional fields", async () => {
|
|
660
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
661
|
+
const store = createEventStore(dbPath);
|
|
662
|
+
store.insert(
|
|
663
|
+
makeEvent({
|
|
664
|
+
agentName: "builder-1",
|
|
665
|
+
eventType: "session_start",
|
|
666
|
+
runId: "run-001",
|
|
667
|
+
sessionId: null,
|
|
668
|
+
toolName: null,
|
|
669
|
+
toolArgs: null,
|
|
670
|
+
toolDurationMs: null,
|
|
671
|
+
data: null,
|
|
672
|
+
}),
|
|
673
|
+
);
|
|
674
|
+
store.close();
|
|
675
|
+
|
|
676
|
+
// Should not throw
|
|
677
|
+
await replayCommand(["--run", "run-001"]);
|
|
678
|
+
const out = output();
|
|
679
|
+
|
|
680
|
+
expect(out).toContain("Replay");
|
|
681
|
+
expect(out).toContain("1 event");
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test("all event types have labeled output", async () => {
|
|
685
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
686
|
+
const store = createEventStore(dbPath);
|
|
687
|
+
const eventTypes = [
|
|
688
|
+
"tool_start",
|
|
689
|
+
"tool_end",
|
|
690
|
+
"session_start",
|
|
691
|
+
"session_end",
|
|
692
|
+
"mail_sent",
|
|
693
|
+
"mail_received",
|
|
694
|
+
"spawn",
|
|
695
|
+
"error",
|
|
696
|
+
"custom",
|
|
697
|
+
] as const;
|
|
698
|
+
for (const eventType of eventTypes) {
|
|
699
|
+
store.insert(
|
|
700
|
+
makeEvent({
|
|
701
|
+
eventType,
|
|
702
|
+
level: eventType === "error" ? "error" : "info",
|
|
703
|
+
}),
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
store.close();
|
|
707
|
+
|
|
708
|
+
await replayCommand(["--run", "run-001"]);
|
|
709
|
+
const out = output();
|
|
710
|
+
|
|
711
|
+
expect(out).toContain("TOOL START");
|
|
712
|
+
expect(out).toContain("TOOL END");
|
|
713
|
+
expect(out).toContain("SESSION +");
|
|
714
|
+
expect(out).toContain("SESSION -");
|
|
715
|
+
expect(out).toContain("MAIL SENT");
|
|
716
|
+
expect(out).toContain("MAIL RECV");
|
|
717
|
+
expect(out).toContain("SPAWN");
|
|
718
|
+
expect(out).toContain("ERROR");
|
|
719
|
+
expect(out).toContain("CUSTOM");
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test("long data values are truncated", async () => {
|
|
723
|
+
const dbPath = join(tempDir, ".legio", "events.db");
|
|
724
|
+
const store = createEventStore(dbPath);
|
|
725
|
+
const longValue = "x".repeat(200);
|
|
726
|
+
store.insert(
|
|
727
|
+
makeEvent({
|
|
728
|
+
eventType: "custom",
|
|
729
|
+
toolName: null,
|
|
730
|
+
data: JSON.stringify({ message: longValue }),
|
|
731
|
+
}),
|
|
732
|
+
);
|
|
733
|
+
store.close();
|
|
734
|
+
|
|
735
|
+
await replayCommand(["--run", "run-001"]);
|
|
736
|
+
const out = output();
|
|
737
|
+
|
|
738
|
+
expect(out).not.toContain(longValue);
|
|
739
|
+
expect(out).toContain("...");
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
});
|