@os-eco/overstory-cli 0.7.0 → 0.7.3
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 +7 -6
- package/agents/builder.md +1 -1
- package/agents/coordinator.md +12 -11
- package/agents/lead.md +6 -6
- 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 +1 -1
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/hooks-deployer.test.ts +6 -5
- package/src/agents/hooks-deployer.ts +7 -90
- package/src/agents/identity.test.ts +3 -2
- package/src/agents/manifest.test.ts +4 -3
- package/src/agents/overlay.test.ts +10 -9
- package/src/agents/overlay.ts +5 -5
- package/src/commands/agents.test.ts +10 -4
- package/src/commands/clean.test.ts +3 -0
- package/src/commands/completions.test.ts +8 -5
- package/src/commands/completions.ts +38 -2
- package/src/commands/coordinator.test.ts +1 -0
- package/src/commands/coordinator.ts +15 -11
- package/src/commands/costs.test.ts +9 -3
- package/src/commands/dashboard.test.ts +265 -6
- package/src/commands/dashboard.ts +367 -64
- package/src/commands/doctor.test.ts +3 -2
- package/src/commands/errors.test.ts +3 -2
- package/src/commands/feed.test.ts +3 -2
- package/src/commands/feed.ts +2 -29
- package/src/commands/init.test.ts +1 -2
- package/src/commands/init.ts +1 -8
- package/src/commands/inspect.test.ts +17 -2
- package/src/commands/log.test.ts +262 -8
- package/src/commands/log.ts +232 -110
- package/src/commands/logs.test.ts +3 -2
- package/src/commands/mail.test.ts +8 -2
- package/src/commands/metrics.test.ts +4 -3
- package/src/commands/monitor.ts +15 -11
- package/src/commands/nudge.test.ts +4 -2
- package/src/commands/prime.test.ts +4 -2
- package/src/commands/prime.ts +6 -2
- package/src/commands/replay.test.ts +3 -2
- package/src/commands/run.test.ts +3 -1
- package/src/commands/sling.test.ts +142 -1
- package/src/commands/sling.ts +145 -24
- package/src/commands/status.test.ts +9 -8
- package/src/commands/stop.test.ts +1 -0
- package/src/commands/supervisor.ts +19 -12
- package/src/commands/trace.test.ts +4 -2
- package/src/commands/watch.test.ts +3 -2
- package/src/commands/worktree.test.ts +9 -0
- package/src/config.test.ts +3 -3
- package/src/config.ts +29 -0
- package/src/doctor/agents.test.ts +3 -2
- package/src/doctor/consistency.test.ts +14 -0
- package/src/doctor/logs.test.ts +3 -2
- package/src/doctor/structure.test.ts +3 -2
- package/src/e2e/init-sling-lifecycle.test.ts +3 -5
- package/src/index.ts +3 -1
- package/src/logging/color.ts +1 -1
- package/src/logging/format.test.ts +110 -0
- package/src/logging/format.ts +42 -1
- package/src/logging/logger.test.ts +3 -2
- package/src/mail/broadcast.test.ts +1 -0
- package/src/mail/client.test.ts +3 -2
- package/src/mail/store.test.ts +3 -2
- package/src/merge/queue.test.ts +3 -2
- package/src/merge/resolver.test.ts +39 -0
- package/src/merge/resolver.ts +24 -5
- package/src/mulch/client.test.ts +63 -2
- package/src/mulch/client.ts +62 -1
- package/src/runtimes/claude.test.ts +5 -4
- package/src/runtimes/pi-guards.test.ts +457 -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 +33 -0
- package/src/runtimes/registry.ts +15 -2
- package/src/runtimes/types.ts +63 -0
- package/src/schema-consistency.test.ts +5 -2
- package/src/sessions/compat.test.ts +3 -2
- package/src/sessions/compat.ts +1 -0
- package/src/sessions/store.test.ts +34 -2
- package/src/sessions/store.ts +37 -4
- package/src/test-helpers.ts +20 -1
- package/src/types.ts +17 -0
- package/src/watchdog/daemon.test.ts +11 -7
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -0
- package/src/watchdog/triage.test.ts +3 -2
- package/src/watchdog/triage.ts +14 -4
package/src/commands/log.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp, readdir,
|
|
2
|
+
import { mkdir, mkdtemp, readdir, stat } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { ValidationError } from "../errors.ts";
|
|
@@ -9,8 +9,9 @@ import { createMailStore } from "../mail/store.ts";
|
|
|
9
9
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
10
10
|
import type { MulchClient } from "../mulch/client.ts";
|
|
11
11
|
import { createRunStore, createSessionStore } from "../sessions/store.ts";
|
|
12
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
12
13
|
import type { AgentSession, MulchLearnResult, StoredEvent } from "../types.ts";
|
|
13
|
-
import { autoRecordExpertise, logCommand } from "./log.ts";
|
|
14
|
+
import { appendOutcomeToAppliedRecords, autoRecordExpertise, logCommand } from "./log.ts";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Tests for `overstory log` command.
|
|
@@ -50,7 +51,7 @@ describe("logCommand", () => {
|
|
|
50
51
|
afterEach(async () => {
|
|
51
52
|
process.stdout.write = originalWrite;
|
|
52
53
|
process.chdir(originalCwd);
|
|
53
|
-
await
|
|
54
|
+
await cleanupTempDir(tempDir);
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
function output(): string {
|
|
@@ -58,18 +59,28 @@ describe("logCommand", () => {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* Fake MulchClient for testing autoRecordExpertise.
|
|
62
|
-
* Only learn() and
|
|
62
|
+
* Fake MulchClient for testing autoRecordExpertise and appendOutcomeToAppliedRecords.
|
|
63
|
+
* Only learn(), record(), and appendOutcome() are implemented — other methods are stubs.
|
|
63
64
|
* Justified: we are testing orchestration logic, not the mulch CLI itself.
|
|
64
65
|
*/
|
|
65
66
|
function createFakeMulchClient(
|
|
66
67
|
learnResult: MulchLearnResult,
|
|
67
|
-
opts?: { recordShouldFail?: boolean },
|
|
68
|
+
opts?: { recordShouldFail?: boolean; appendOutcomeShouldFail?: boolean },
|
|
68
69
|
): {
|
|
69
70
|
client: MulchClient;
|
|
70
71
|
recordCalls: Array<{ domain: string; options: Record<string, unknown> }>;
|
|
72
|
+
appendOutcomeCalls: Array<{
|
|
73
|
+
domain: string;
|
|
74
|
+
id: string;
|
|
75
|
+
outcome: Record<string, unknown>;
|
|
76
|
+
}>;
|
|
71
77
|
} {
|
|
72
78
|
const recordCalls: Array<{ domain: string; options: Record<string, unknown> }> = [];
|
|
79
|
+
const appendOutcomeCalls: Array<{
|
|
80
|
+
domain: string;
|
|
81
|
+
id: string;
|
|
82
|
+
outcome: Record<string, unknown>;
|
|
83
|
+
}> = [];
|
|
73
84
|
const client = {
|
|
74
85
|
async learn() {
|
|
75
86
|
return learnResult;
|
|
@@ -80,8 +91,14 @@ describe("logCommand", () => {
|
|
|
80
91
|
}
|
|
81
92
|
recordCalls.push({ domain, options });
|
|
82
93
|
},
|
|
94
|
+
async appendOutcome(domain: string, id: string, outcome: Record<string, unknown>) {
|
|
95
|
+
if (opts?.appendOutcomeShouldFail) {
|
|
96
|
+
throw new Error("mulch appendOutcome failed");
|
|
97
|
+
}
|
|
98
|
+
appendOutcomeCalls.push({ domain, id, outcome });
|
|
99
|
+
},
|
|
83
100
|
} as unknown as MulchClient;
|
|
84
|
-
return { client, recordCalls };
|
|
101
|
+
return { client, recordCalls, appendOutcomeCalls };
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
test("--help flag shows help text", async () => {
|
|
@@ -229,6 +246,7 @@ describe("logCommand", () => {
|
|
|
229
246
|
lastActivity: new Date().toISOString(),
|
|
230
247
|
escalationLevel: 0,
|
|
231
248
|
stalledSince: null,
|
|
249
|
+
transcriptPath: null,
|
|
232
250
|
};
|
|
233
251
|
const store = createSessionStore(dbPath);
|
|
234
252
|
store.upsert(session);
|
|
@@ -284,6 +302,7 @@ describe("logCommand", () => {
|
|
|
284
302
|
lastActivity: new Date().toISOString(),
|
|
285
303
|
escalationLevel: 0,
|
|
286
304
|
stalledSince: null,
|
|
305
|
+
transcriptPath: null,
|
|
287
306
|
};
|
|
288
307
|
const sessStore = createSessionStore(sessionsDbPath);
|
|
289
308
|
sessStore.upsert(session);
|
|
@@ -324,6 +343,7 @@ describe("logCommand", () => {
|
|
|
324
343
|
lastActivity: new Date(Date.now() - 60_000).toISOString(),
|
|
325
344
|
escalationLevel: 0,
|
|
326
345
|
stalledSince: null,
|
|
346
|
+
transcriptPath: null,
|
|
327
347
|
};
|
|
328
348
|
const store = createSessionStore(dbPath);
|
|
329
349
|
store.upsert(session);
|
|
@@ -363,6 +383,7 @@ describe("logCommand", () => {
|
|
|
363
383
|
lastActivity: new Date(Date.now() - 60_000).toISOString(),
|
|
364
384
|
escalationLevel: 0,
|
|
365
385
|
stalledSince: null,
|
|
386
|
+
transcriptPath: null,
|
|
366
387
|
};
|
|
367
388
|
const store = createSessionStore(dbPath);
|
|
368
389
|
store.upsert(session);
|
|
@@ -400,6 +421,7 @@ describe("logCommand", () => {
|
|
|
400
421
|
lastActivity: new Date().toISOString(),
|
|
401
422
|
escalationLevel: 0,
|
|
402
423
|
stalledSince: null,
|
|
424
|
+
transcriptPath: null,
|
|
403
425
|
});
|
|
404
426
|
sessionStoreLocal.close();
|
|
405
427
|
|
|
@@ -457,6 +479,7 @@ describe("logCommand", () => {
|
|
|
457
479
|
lastActivity: new Date().toISOString(),
|
|
458
480
|
escalationLevel: 0,
|
|
459
481
|
stalledSince: null,
|
|
482
|
+
transcriptPath: null,
|
|
460
483
|
});
|
|
461
484
|
sessionStoreLocal.close();
|
|
462
485
|
|
|
@@ -487,6 +510,7 @@ describe("logCommand", () => {
|
|
|
487
510
|
lastActivity: new Date().toISOString(),
|
|
488
511
|
escalationLevel: 0,
|
|
489
512
|
stalledSince: null,
|
|
513
|
+
transcriptPath: null,
|
|
490
514
|
});
|
|
491
515
|
sessionStoreLocal.close();
|
|
492
516
|
|
|
@@ -541,6 +565,7 @@ describe("logCommand", () => {
|
|
|
541
565
|
lastActivity: new Date().toISOString(),
|
|
542
566
|
escalationLevel: 0,
|
|
543
567
|
stalledSince: null,
|
|
568
|
+
transcriptPath: null,
|
|
544
569
|
});
|
|
545
570
|
sessionStoreLocal.close();
|
|
546
571
|
|
|
@@ -594,6 +619,7 @@ describe("logCommand", () => {
|
|
|
594
619
|
lastActivity: new Date().toISOString(),
|
|
595
620
|
escalationLevel: 0,
|
|
596
621
|
stalledSince: null,
|
|
622
|
+
transcriptPath: null,
|
|
597
623
|
};
|
|
598
624
|
const store = createSessionStore(dbPath);
|
|
599
625
|
store.upsert(session);
|
|
@@ -634,6 +660,7 @@ describe("logCommand", () => {
|
|
|
634
660
|
lastActivity: new Date().toISOString(),
|
|
635
661
|
escalationLevel: 0,
|
|
636
662
|
stalledSince: null,
|
|
663
|
+
transcriptPath: null,
|
|
637
664
|
};
|
|
638
665
|
const store = createSessionStore(dbPath);
|
|
639
666
|
store.upsert(session);
|
|
@@ -676,6 +703,7 @@ describe("logCommand", () => {
|
|
|
676
703
|
lastActivity: oldTimestamp,
|
|
677
704
|
escalationLevel: 0,
|
|
678
705
|
stalledSince: null,
|
|
706
|
+
transcriptPath: null,
|
|
679
707
|
};
|
|
680
708
|
const store = createSessionStore(dbPath);
|
|
681
709
|
store.upsert(session);
|
|
@@ -715,6 +743,7 @@ describe("logCommand", () => {
|
|
|
715
743
|
lastActivity: new Date().toISOString(),
|
|
716
744
|
escalationLevel: 0,
|
|
717
745
|
stalledSince: null,
|
|
746
|
+
transcriptPath: null,
|
|
718
747
|
};
|
|
719
748
|
const store = createSessionStore(dbPath);
|
|
720
749
|
store.upsert(session);
|
|
@@ -773,6 +802,54 @@ describe("logCommand", () => {
|
|
|
773
802
|
expect(eventsContent).toContain("unknown");
|
|
774
803
|
});
|
|
775
804
|
|
|
805
|
+
test("tool-start writes to EventStore without --stdin flag (Pi runtime path)", async () => {
|
|
806
|
+
await logCommand(["tool-start", "--agent", "pi-agent", "--tool-name", "Read"]);
|
|
807
|
+
|
|
808
|
+
const eventsDbPath = join(tempDir, ".overstory", "events.db");
|
|
809
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
810
|
+
const events = eventStore.getByAgent("pi-agent");
|
|
811
|
+
eventStore.close();
|
|
812
|
+
|
|
813
|
+
expect(events).toHaveLength(1);
|
|
814
|
+
expect(events[0]?.eventType).toBe("tool_start");
|
|
815
|
+
expect(events[0]?.toolName).toBe("Read");
|
|
816
|
+
expect(events[0]?.sessionId).toBeNull();
|
|
817
|
+
expect(events[0]?.agentName).toBe("pi-agent");
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("tool-end writes to EventStore without --stdin flag (Pi runtime path)", async () => {
|
|
821
|
+
await logCommand(["tool-start", "--agent", "pi-end-agent", "--tool-name", "Write"]);
|
|
822
|
+
await logCommand(["tool-end", "--agent", "pi-end-agent", "--tool-name", "Write"]);
|
|
823
|
+
|
|
824
|
+
const eventsDbPath = join(tempDir, ".overstory", "events.db");
|
|
825
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
826
|
+
const events = eventStore.getByAgent("pi-end-agent");
|
|
827
|
+
eventStore.close();
|
|
828
|
+
|
|
829
|
+
expect(events).toHaveLength(2);
|
|
830
|
+
const startEv = events.find((e) => e.eventType === "tool_start");
|
|
831
|
+
const endEv = events.find((e) => e.eventType === "tool_end");
|
|
832
|
+
expect(startEv).toBeDefined();
|
|
833
|
+
expect(endEv).toBeDefined();
|
|
834
|
+
expect(startEv?.toolName).toBe("Write");
|
|
835
|
+
expect(endEv?.toolName).toBe("Write");
|
|
836
|
+
expect(startEv?.sessionId).toBeNull();
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("session-end writes to EventStore without --stdin flag (Pi runtime path)", async () => {
|
|
840
|
+
await logCommand(["session-end", "--agent", "pi-session-agent"]);
|
|
841
|
+
|
|
842
|
+
const eventsDbPath = join(tempDir, ".overstory", "events.db");
|
|
843
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
844
|
+
const events = eventStore.getByAgent("pi-session-agent");
|
|
845
|
+
eventStore.close();
|
|
846
|
+
|
|
847
|
+
expect(events).toHaveLength(1);
|
|
848
|
+
expect(events[0]?.eventType).toBe("session_end");
|
|
849
|
+
expect(events[0]?.sessionId).toBeNull();
|
|
850
|
+
expect(events[0]?.agentName).toBe("pi-session-agent");
|
|
851
|
+
});
|
|
852
|
+
|
|
776
853
|
test("--help includes --stdin option in output", async () => {
|
|
777
854
|
await logCommand(["--help"]);
|
|
778
855
|
const out = output();
|
|
@@ -800,6 +877,7 @@ describe("logCommand", () => {
|
|
|
800
877
|
lastActivity: new Date().toISOString(),
|
|
801
878
|
escalationLevel: 0,
|
|
802
879
|
stalledSince: null,
|
|
880
|
+
transcriptPath: null,
|
|
803
881
|
};
|
|
804
882
|
const store = createSessionStore(dbPath);
|
|
805
883
|
store.upsert(session);
|
|
@@ -839,6 +917,7 @@ describe("logCommand", () => {
|
|
|
839
917
|
lastActivity: new Date().toISOString(),
|
|
840
918
|
escalationLevel: 0,
|
|
841
919
|
stalledSince: null,
|
|
920
|
+
transcriptPath: null,
|
|
842
921
|
};
|
|
843
922
|
const store = createSessionStore(dbPath);
|
|
844
923
|
store.upsert(session);
|
|
@@ -1168,7 +1247,7 @@ describe("logCommand --stdin integration", () => {
|
|
|
1168
1247
|
});
|
|
1169
1248
|
|
|
1170
1249
|
afterEach(async () => {
|
|
1171
|
-
await
|
|
1250
|
+
await cleanupTempDir(tempDir);
|
|
1172
1251
|
});
|
|
1173
1252
|
|
|
1174
1253
|
/**
|
|
@@ -1373,6 +1452,7 @@ try {
|
|
|
1373
1452
|
const scriptPath = join(tempDir, "_run-log-empty.ts");
|
|
1374
1453
|
const scriptContent = `
|
|
1375
1454
|
import { logCommand } from "${join(import.meta.dir, "log.ts").replace(/\\/g, "/")}";
|
|
1455
|
+
|
|
1376
1456
|
try {
|
|
1377
1457
|
await logCommand(["tool-start", "--agent", "empty-stdin-agent", "--stdin"]);
|
|
1378
1458
|
} catch (e) {
|
|
@@ -1447,3 +1527,177 @@ try {
|
|
|
1447
1527
|
// tool_result is not stored in EventStore (filtered out), but tool_name was parsed correctly
|
|
1448
1528
|
});
|
|
1449
1529
|
});
|
|
1530
|
+
|
|
1531
|
+
describe("appendOutcomeToAppliedRecords", () => {
|
|
1532
|
+
let tempDir: string;
|
|
1533
|
+
|
|
1534
|
+
/** Minimal fake MulchClient for appendOutcomeToAppliedRecords tests. */
|
|
1535
|
+
function makeOutcomeClient(opts?: { appendOutcomeShouldFail?: boolean }): {
|
|
1536
|
+
client: MulchClient;
|
|
1537
|
+
appendOutcomeCalls: Array<{ domain: string; id: string; outcome: Record<string, unknown> }>;
|
|
1538
|
+
} {
|
|
1539
|
+
const appendOutcomeCalls: Array<{
|
|
1540
|
+
domain: string;
|
|
1541
|
+
id: string;
|
|
1542
|
+
outcome: Record<string, unknown>;
|
|
1543
|
+
}> = [];
|
|
1544
|
+
const client = {
|
|
1545
|
+
async appendOutcome(domain: string, id: string, outcome: Record<string, unknown>) {
|
|
1546
|
+
if (opts?.appendOutcomeShouldFail) throw new Error("mulch appendOutcome failed");
|
|
1547
|
+
appendOutcomeCalls.push({ domain, id, outcome });
|
|
1548
|
+
},
|
|
1549
|
+
} as unknown as MulchClient;
|
|
1550
|
+
return { client, appendOutcomeCalls };
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
beforeEach(async () => {
|
|
1554
|
+
tempDir = await mkdtemp(join(tmpdir(), "outcome-test-"));
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
afterEach(async () => {
|
|
1558
|
+
await cleanupTempDir(tempDir);
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
test("returns 0 when applied-records.json does not exist (backward compat)", async () => {
|
|
1562
|
+
const { client } = makeOutcomeClient();
|
|
1563
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1564
|
+
mulchClient: client,
|
|
1565
|
+
agentName: "test-agent",
|
|
1566
|
+
capability: "builder",
|
|
1567
|
+
taskId: "bead-001",
|
|
1568
|
+
projectRoot: tempDir,
|
|
1569
|
+
});
|
|
1570
|
+
expect(count).toBe(0);
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
test("returns 0 when records array is empty", async () => {
|
|
1574
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1575
|
+
await mkdir(agentDir, { recursive: true });
|
|
1576
|
+
await Bun.write(
|
|
1577
|
+
join(agentDir, "applied-records.json"),
|
|
1578
|
+
JSON.stringify({
|
|
1579
|
+
taskId: "bead-001",
|
|
1580
|
+
agentName: "test-agent",
|
|
1581
|
+
capability: "builder",
|
|
1582
|
+
records: [],
|
|
1583
|
+
}),
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
const { client } = makeOutcomeClient();
|
|
1587
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1588
|
+
mulchClient: client,
|
|
1589
|
+
agentName: "test-agent",
|
|
1590
|
+
capability: "builder",
|
|
1591
|
+
taskId: "bead-001",
|
|
1592
|
+
projectRoot: tempDir,
|
|
1593
|
+
});
|
|
1594
|
+
expect(count).toBe(0);
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1597
|
+
test("calls appendOutcome for each record and returns count", async () => {
|
|
1598
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1599
|
+
await mkdir(agentDir, { recursive: true });
|
|
1600
|
+
const records = [
|
|
1601
|
+
{ id: "mx-aaa111", domain: "agents" },
|
|
1602
|
+
{ id: "mx-bbb222", domain: "typescript" },
|
|
1603
|
+
];
|
|
1604
|
+
await Bun.write(
|
|
1605
|
+
join(agentDir, "applied-records.json"),
|
|
1606
|
+
JSON.stringify({
|
|
1607
|
+
taskId: "bead-001",
|
|
1608
|
+
agentName: "test-agent",
|
|
1609
|
+
capability: "builder",
|
|
1610
|
+
records,
|
|
1611
|
+
}),
|
|
1612
|
+
);
|
|
1613
|
+
|
|
1614
|
+
const { client, appendOutcomeCalls } = makeOutcomeClient();
|
|
1615
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1616
|
+
mulchClient: client,
|
|
1617
|
+
agentName: "test-agent",
|
|
1618
|
+
capability: "builder",
|
|
1619
|
+
taskId: "bead-001",
|
|
1620
|
+
projectRoot: tempDir,
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
expect(count).toBe(2);
|
|
1624
|
+
expect(appendOutcomeCalls).toHaveLength(2);
|
|
1625
|
+
expect(appendOutcomeCalls[0]).toMatchObject({ id: "mx-aaa111", domain: "agents" });
|
|
1626
|
+
expect(appendOutcomeCalls[1]).toMatchObject({ id: "mx-bbb222", domain: "typescript" });
|
|
1627
|
+
expect(appendOutcomeCalls[0]?.outcome).toMatchObject({
|
|
1628
|
+
status: "success",
|
|
1629
|
+
agent: "test-agent",
|
|
1630
|
+
});
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
test("cleans up applied-records.json after processing", async () => {
|
|
1634
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1635
|
+
await mkdir(agentDir, { recursive: true });
|
|
1636
|
+
const appliedPath = join(agentDir, "applied-records.json");
|
|
1637
|
+
await Bun.write(
|
|
1638
|
+
appliedPath,
|
|
1639
|
+
JSON.stringify({
|
|
1640
|
+
taskId: "bead-001",
|
|
1641
|
+
agentName: "test-agent",
|
|
1642
|
+
capability: "builder",
|
|
1643
|
+
records: [{ id: "mx-abc123", domain: "agents" }],
|
|
1644
|
+
}),
|
|
1645
|
+
);
|
|
1646
|
+
|
|
1647
|
+
const { client } = makeOutcomeClient();
|
|
1648
|
+
await appendOutcomeToAppliedRecords({
|
|
1649
|
+
mulchClient: client,
|
|
1650
|
+
agentName: "test-agent",
|
|
1651
|
+
capability: "builder",
|
|
1652
|
+
taskId: "bead-001",
|
|
1653
|
+
projectRoot: tempDir,
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
expect(await Bun.file(appliedPath).exists()).toBe(false);
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
test("continues when individual appendOutcome calls fail (non-fatal per record)", async () => {
|
|
1660
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1661
|
+
await mkdir(agentDir, { recursive: true });
|
|
1662
|
+
const records = [
|
|
1663
|
+
{ id: "mx-fail111", domain: "agents" },
|
|
1664
|
+
{ id: "mx-fail222", domain: "typescript" },
|
|
1665
|
+
];
|
|
1666
|
+
await Bun.write(
|
|
1667
|
+
join(agentDir, "applied-records.json"),
|
|
1668
|
+
JSON.stringify({
|
|
1669
|
+
taskId: "bead-002",
|
|
1670
|
+
agentName: "test-agent",
|
|
1671
|
+
capability: "builder",
|
|
1672
|
+
records,
|
|
1673
|
+
}),
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
// appendOutcomeShouldFail=true makes all calls throw — should return 0 but not throw
|
|
1677
|
+
const { client } = makeOutcomeClient({ appendOutcomeShouldFail: true });
|
|
1678
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1679
|
+
mulchClient: client,
|
|
1680
|
+
agentName: "test-agent",
|
|
1681
|
+
capability: "builder",
|
|
1682
|
+
taskId: "bead-002",
|
|
1683
|
+
projectRoot: tempDir,
|
|
1684
|
+
});
|
|
1685
|
+
expect(count).toBe(0);
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
test("returns 0 for malformed JSON", async () => {
|
|
1689
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1690
|
+
await mkdir(agentDir, { recursive: true });
|
|
1691
|
+
await Bun.write(join(agentDir, "applied-records.json"), "not-valid-json{{{");
|
|
1692
|
+
|
|
1693
|
+
const { client } = makeOutcomeClient();
|
|
1694
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1695
|
+
mulchClient: client,
|
|
1696
|
+
agentName: "test-agent",
|
|
1697
|
+
capability: "builder",
|
|
1698
|
+
taskId: null,
|
|
1699
|
+
projectRoot: tempDir,
|
|
1700
|
+
});
|
|
1701
|
+
expect(count).toBe(0);
|
|
1702
|
+
});
|
|
1703
|
+
});
|