@ai-devkit/agent-manager 0.41.0 → 0.42.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.
Files changed (101) hide show
  1. package/dist/AgentManager.d.ts.map +1 -1
  2. package/dist/AgentManager.js +25 -9
  3. package/dist/AgentManager.js.map +1 -1
  4. package/dist/__tests__/fixtures.test.js.map +1 -1
  5. package/dist/__tests__/harnesses/devin/DevinAdapter.test.js.map +1 -1
  6. package/dist/__tests__/harnesses/devin/DevinSessionParser.test.js.map +1 -1
  7. package/dist/__tests__/harnesses/muse/MuseAdapter.test.js +247 -0
  8. package/dist/__tests__/harnesses/muse/MuseAdapter.test.js.map +1 -0
  9. package/dist/__tests__/harnesses/muse/MuseAgentMapper.test.js +81 -0
  10. package/dist/__tests__/harnesses/muse/MuseAgentMapper.test.js.map +1 -0
  11. package/dist/__tests__/harnesses/muse/MuseSessionLocator.test.js +185 -0
  12. package/dist/__tests__/harnesses/muse/MuseSessionLocator.test.js.map +1 -0
  13. package/dist/__tests__/harnesses/muse/MuseSessionParser.test.js +328 -0
  14. package/dist/__tests__/harnesses/muse/MuseSessionParser.test.js.map +1 -0
  15. package/dist/__tests__/harnesses/opencode/OpenCodeSessionParser.test.js.map +1 -1
  16. package/dist/__tests__/harnesses/readiness/AgentReadiness.test.js +42 -1
  17. package/dist/__tests__/harnesses/readiness/AgentReadiness.test.js.map +1 -1
  18. package/dist/__tests__/harnesses/runtimeProfiles.test.js +12 -0
  19. package/dist/__tests__/harnesses/runtimeProfiles.test.js.map +1 -1
  20. package/dist/__tests__/utils/process.test.js +71 -1
  21. package/dist/__tests__/utils/process.test.js.map +1 -1
  22. package/dist/adapters/AgentAdapter.d.ts +8 -1
  23. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  24. package/dist/adapters/AgentAdapter.js +2 -1
  25. package/dist/adapters/AgentAdapter.js.map +1 -1
  26. package/dist/fixtures/capture.d.ts.map +1 -1
  27. package/dist/fixtures/capture.js +5 -4
  28. package/dist/fixtures/capture.js.map +1 -1
  29. package/dist/harnesses/devin/DevinAdapter.d.ts.map +1 -1
  30. package/dist/harnesses/devin/DevinAdapter.js.map +1 -1
  31. package/dist/harnesses/devin/DevinAgentMapper.d.ts.map +1 -1
  32. package/dist/harnesses/devin/DevinAgentMapper.js.map +1 -1
  33. package/dist/harnesses/devin/DevinSessionParser.d.ts.map +1 -1
  34. package/dist/harnesses/devin/DevinSessionParser.js.map +1 -1
  35. package/dist/harnesses/index.d.ts.map +1 -1
  36. package/dist/harnesses/index.js +3 -1
  37. package/dist/harnesses/index.js.map +1 -1
  38. package/dist/harnesses/muse/MuseAdapter.d.ts +33 -0
  39. package/dist/harnesses/muse/MuseAdapter.d.ts.map +1 -0
  40. package/dist/harnesses/muse/MuseAdapter.js +122 -0
  41. package/dist/harnesses/muse/MuseAdapter.js.map +1 -0
  42. package/dist/harnesses/muse/MuseAgentMapper.d.ts +15 -0
  43. package/dist/harnesses/muse/MuseAgentMapper.d.ts.map +1 -0
  44. package/dist/harnesses/muse/MuseAgentMapper.js +29 -0
  45. package/dist/harnesses/muse/MuseAgentMapper.js.map +1 -0
  46. package/dist/harnesses/muse/MuseSessionLocator.d.ts +61 -0
  47. package/dist/harnesses/muse/MuseSessionLocator.d.ts.map +1 -0
  48. package/dist/harnesses/muse/MuseSessionLocator.js +233 -0
  49. package/dist/harnesses/muse/MuseSessionLocator.js.map +1 -0
  50. package/dist/harnesses/muse/MuseSessionParser.d.ts +77 -0
  51. package/dist/harnesses/muse/MuseSessionParser.d.ts.map +1 -0
  52. package/dist/harnesses/muse/MuseSessionParser.js +350 -0
  53. package/dist/harnesses/muse/MuseSessionParser.js.map +1 -0
  54. package/dist/harnesses/muse/readiness.d.ts +3 -0
  55. package/dist/harnesses/muse/readiness.d.ts.map +1 -0
  56. package/dist/harnesses/muse/readiness.js +37 -0
  57. package/dist/harnesses/muse/readiness.js.map +1 -0
  58. package/dist/harnesses/readiness/AgentReadiness.d.ts.map +1 -1
  59. package/dist/harnesses/readiness/AgentReadiness.js +3 -1
  60. package/dist/harnesses/readiness/AgentReadiness.js.map +1 -1
  61. package/dist/harnesses/readiness/types.d.ts +1 -1
  62. package/dist/harnesses/readiness/types.d.ts.map +1 -1
  63. package/dist/harnesses/runtimeProfiles.d.ts.map +1 -1
  64. package/dist/harnesses/runtimeProfiles.js +2 -1
  65. package/dist/harnesses/runtimeProfiles.js.map +1 -1
  66. package/dist/harnesses/shared.d.ts +1 -1
  67. package/dist/harnesses/shared.d.ts.map +1 -1
  68. package/dist/harnesses/shared.js +6 -5
  69. package/dist/harnesses/shared.js.map +1 -1
  70. package/dist/utils/process.d.ts +23 -1
  71. package/dist/utils/process.d.ts.map +1 -1
  72. package/dist/utils/process.js +44 -8
  73. package/dist/utils/process.js.map +1 -1
  74. package/package.json +1 -1
  75. package/src/AgentManager.ts +34 -8
  76. package/src/__tests__/fixtures.test.ts +15 -17
  77. package/src/__tests__/harnesses/devin/DevinAdapter.test.ts +1 -3
  78. package/src/__tests__/harnesses/devin/DevinSessionParser.test.ts +3 -9
  79. package/src/__tests__/harnesses/muse/MuseAdapter.test.ts +197 -0
  80. package/src/__tests__/harnesses/muse/MuseAgentMapper.test.ts +83 -0
  81. package/src/__tests__/harnesses/muse/MuseSessionLocator.test.ts +177 -0
  82. package/src/__tests__/harnesses/muse/MuseSessionParser.test.ts +279 -0
  83. package/src/__tests__/harnesses/opencode/OpenCodeSessionParser.test.ts +3 -2
  84. package/src/__tests__/harnesses/readiness/AgentReadiness.test.ts +36 -0
  85. package/src/__tests__/harnesses/runtimeProfiles.test.ts +13 -0
  86. package/src/__tests__/utils/process.test.ts +30 -0
  87. package/src/adapters/AgentAdapter.ts +9 -0
  88. package/src/fixtures/capture.ts +32 -17
  89. package/src/harnesses/devin/DevinAdapter.ts +3 -15
  90. package/src/harnesses/devin/DevinAgentMapper.ts +1 -5
  91. package/src/harnesses/devin/DevinSessionParser.ts +3 -12
  92. package/src/harnesses/index.ts +2 -0
  93. package/src/harnesses/muse/MuseAdapter.ts +157 -0
  94. package/src/harnesses/muse/MuseAgentMapper.ts +33 -0
  95. package/src/harnesses/muse/MuseSessionLocator.ts +286 -0
  96. package/src/harnesses/muse/MuseSessionParser.ts +435 -0
  97. package/src/harnesses/muse/readiness.ts +38 -0
  98. package/src/harnesses/readiness/AgentReadiness.ts +2 -0
  99. package/src/harnesses/runtimeProfiles.ts +1 -0
  100. package/src/harnesses/shared.ts +10 -5
  101. package/src/utils/process.ts +61 -9
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Tests for harnesses/muse/MuseAdapter.ts — detection through the real
3
+ * locator/parser/mapper against a fake Muse home (no mocks).
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import * as os from "os";
8
+ import * as path from "path";
9
+ import { AgentStatus } from "../../../adapters/AgentAdapter.js";
10
+ import type { ProcessInfo } from "../../../adapters/AgentAdapter.js";
11
+ import { MuseAdapter } from "../../../harnesses/muse/MuseAdapter.js";
12
+
13
+ const SID = "01a12618-d918-7b63-91c8-4e896326eb1a";
14
+ const PROJECT = "codeaholicguy/Code/ai-devkit";
15
+
16
+ function frame(inner: unknown): string {
17
+ return JSON.stringify(inner);
18
+ }
19
+
20
+ function intent(text: string, recordedAtUs: number): unknown {
21
+ return {
22
+ schema_version: 1,
23
+ stream: { kind: "session", id: SID },
24
+ sequence: 2,
25
+ recorded_at: recordedAtUs,
26
+ record_type: "event",
27
+ payload_type: "runtime.user_intent.accepted",
28
+ payload: { refill_blocks: [{ kind: "text", text }] },
29
+ };
30
+ }
31
+
32
+ describe("MuseAdapter", () => {
33
+ let home: string;
34
+ let adapter: MuseAdapter;
35
+
36
+ beforeEach(() => {
37
+ home = fs.mkdtempSync(path.join(os.tmpdir(), "muse-adapter-test-"));
38
+ adapter = new MuseAdapter({ homeDir: home });
39
+ });
40
+
41
+ afterEach(() => {
42
+ fs.rmSync(home, { recursive: true, force: true });
43
+ });
44
+
45
+ function writeTranscript(lines: string[]): string {
46
+ const dir = path.join(home, ".local", "share", "muse", "sessions", "2026/10/10", SID);
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ const filePath = path.join(dir, "session.jsonl");
49
+ fs.writeFileSync(filePath, lines.join("\n"));
50
+ return filePath;
51
+ }
52
+
53
+ function writeRuntimeFile(pid: number): void {
54
+ const dir = path.join(home, ".local", "share", "muse", "runtime", "muse", "sessions");
55
+ fs.mkdirSync(dir, { recursive: true });
56
+ fs.writeFileSync(
57
+ path.join(dir, `${SID}.json`),
58
+ JSON.stringify({ schema_version: 1, session_id: SID, process_generation_hint: `pid=${pid}` }),
59
+ );
60
+ }
61
+
62
+ function proc(overrides: Partial<ProcessInfo> = {}): ProcessInfo {
63
+ return {
64
+ pid: 16174,
65
+ command: "/Users/tester/.local/bin/muse-bin-1.4.4-R5419.1",
66
+ cwd: `/Users/tester/${PROJECT}`,
67
+ tty: "ttys001",
68
+ startTime: new Date(),
69
+ ...overrides,
70
+ };
71
+ }
72
+
73
+ it("handles versioned launcher commands and rejects others", () => {
74
+ expect(adapter.canHandle(proc())).toBe(true);
75
+ expect(adapter.canHandle(proc({ command: "/usr/local/bin/muse" }))).toBe(true);
76
+ expect(adapter.canHandle(proc({ command: "/usr/local/bin/claude" }))).toBe(false);
77
+ expect(adapter.canHandle(proc({ command: "/usr/bin/museums" }))).toBe(false);
78
+ });
79
+
80
+ it("detects a live session through the runtime file", async () => {
81
+ const filePath = writeTranscript([
82
+ frame({
83
+ schema_version: 1,
84
+ stream: { kind: "session", id: SID },
85
+ sequence: 1,
86
+ recorded_at: 1791640525332173,
87
+ record_type: "event",
88
+ payload_type: "runtime.session.metadata",
89
+ payload: {
90
+ kind: "metadata",
91
+ record: { workspace_root: `/Users/tester/${PROJECT}` },
92
+ },
93
+ }),
94
+ frame(intent("hello", 1791640525332174)),
95
+ ]);
96
+ writeRuntimeFile(16174);
97
+ const start = new Date(fs.statSync(filePath).birthtimeMs);
98
+
99
+ const agents = await adapter.detectAgents({ processes: [proc({ startTime: start })] });
100
+ expect(agents).toHaveLength(1);
101
+ expect(agents[0]).toMatchObject({
102
+ type: "muse",
103
+ pid: 16174,
104
+ sessionId: SID,
105
+ projectPath: `/Users/tester/${PROJECT}`,
106
+ summary: "hello",
107
+ });
108
+ expect(agents[0].sessionFilePath).toBe(filePath);
109
+ });
110
+
111
+ it("matches a process by cwd and birthtime without a runtime file", async () => {
112
+ const filePath = writeTranscript([
113
+ frame({
114
+ schema_version: 1,
115
+ stream: { kind: "session", id: SID },
116
+ sequence: 1,
117
+ recorded_at: 1791640525332173,
118
+ record_type: "event",
119
+ payload_type: "runtime.session.metadata",
120
+ payload: {
121
+ kind: "metadata",
122
+ record: { workspace_root: `/Users/tester/${PROJECT}` },
123
+ },
124
+ }),
125
+ frame(intent("legacy hello", 1791640525332174)),
126
+ ]);
127
+ const start = new Date(fs.statSync(filePath).birthtimeMs);
128
+
129
+ const agents = await adapter.detectAgents({
130
+ processes: [proc({ pid: 7777, startTime: start })],
131
+ });
132
+ expect(agents).toHaveLength(1);
133
+ expect(agents[0]).toMatchObject({
134
+ type: "muse",
135
+ pid: 7777,
136
+ sessionId: SID,
137
+ summary: "legacy hello",
138
+ });
139
+ });
140
+
141
+ it("skips transcripts with no conversation signal in history", async () => {
142
+ writeTranscript([
143
+ frame({
144
+ schema_version: 1,
145
+ stream: { kind: "session", id: SID },
146
+ sequence: 1,
147
+ recorded_at: 1791640525332173,
148
+ record_type: "event",
149
+ payload_type: "runtime.session.metadata",
150
+ payload: { kind: "metadata", record: { workspace_root: "/Users/tester/empty" } },
151
+ }),
152
+ ]);
153
+ expect(await adapter.listSessions()).toEqual([]);
154
+ });
155
+
156
+ it("reports an unmatched process as a running placeholder", async () => {
157
+ const agents = await adapter.detectAgents({ processes: [proc({ pid: 4242 })] });
158
+ expect(agents).toHaveLength(1);
159
+ expect(agents[0]).toMatchObject({
160
+ type: "muse",
161
+ status: AgentStatus.RUNNING,
162
+ sessionId: "pid-4242",
163
+ });
164
+ });
165
+
166
+ it("lists historical sessions with strict cwd filtering", async () => {
167
+ writeTranscript([
168
+ frame({
169
+ schema_version: 1,
170
+ stream: { kind: "session", id: SID },
171
+ sequence: 1,
172
+ recorded_at: 1791640525332173,
173
+ record_type: "event",
174
+ payload_type: "runtime.session.metadata",
175
+ payload: {
176
+ kind: "metadata",
177
+ record: { workspace_root: `/Users/tester/${PROJECT}` },
178
+ },
179
+ }),
180
+ frame(intent("hello", 1791640525332174)),
181
+ ]);
182
+
183
+ const all = await adapter.listSessions();
184
+ expect(all).toHaveLength(1);
185
+ expect(all[0]).toMatchObject({
186
+ type: "muse",
187
+ sessionId: SID,
188
+ cwd: `/Users/tester/${PROJECT}`,
189
+ firstUserMessage: "hello",
190
+ });
191
+
192
+ expect(await adapter.listSessions({ cwd: "/elsewhere" })).toEqual([]);
193
+ const found = await adapter.findSessionsById(SID);
194
+ expect(found.map((s) => s.sessionId)).toEqual([SID]);
195
+ expect(await adapter.findSessionsById("00000000-0000-0000-0000-000000000000")).toEqual([]);
196
+ });
197
+ });
@@ -0,0 +1,83 @@
1
+ import { AgentStatus } from "../../../adapters/AgentAdapter.js";
2
+ import type { ProcessInfo } from "../../../adapters/AgentAdapter.js";
3
+ import type { SessionFile } from "../../../utils/session.js";
4
+ import { MuseAgentMapper } from "../../../harnesses/muse/MuseAgentMapper.js";
5
+ import type { MuseSession } from "../../../harnesses/muse/MuseSessionParser.js";
6
+
7
+ const SID = "01a12618-d918-7b63-91c8-4e896326eb1a";
8
+
9
+ function session(overrides: Partial<MuseSession> = {}): MuseSession {
10
+ return {
11
+ sessionId: SID,
12
+ projectPath: "/Users/tester/proj",
13
+ sessionStart: new Date("2026-10-10T13:55:25.332Z"),
14
+ lastActive: new Date("2026-10-10T14:00:00.000Z"),
15
+ lastSignal: "assistant",
16
+ lastUserMessage: "what is this repo about",
17
+ firstUserMessage: "what is this repo about",
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ function sessionFile(): SessionFile {
23
+ return {
24
+ sessionId: SID,
25
+ filePath: `/home/tester/.local/share/muse/sessions/2026/10/10/${SID}/session.jsonl`,
26
+ projectDir: `/home/tester/.local/share/muse/sessions/2026/10/10/${SID}`,
27
+ birthtimeMs: 1791640525332,
28
+ resolvedCwd: "/Users/tester/proj",
29
+ };
30
+ }
31
+
32
+ function proc(): ProcessInfo {
33
+ return {
34
+ pid: 16174,
35
+ command: "/Users/tester/.local/bin/muse-bin-1.4.4-R5419.1",
36
+ cwd: "/Users/tester/proj",
37
+ tty: "ttys001",
38
+ };
39
+ }
40
+
41
+ describe("MuseAgentMapper", () => {
42
+ const mapper = new MuseAgentMapper();
43
+
44
+ it("maps a session to a project-named agent", () => {
45
+ const agent = mapper.mapSessionToAgent({
46
+ session: session(),
47
+ processInfo: proc(),
48
+ sessionFile: sessionFile(),
49
+ });
50
+ expect(agent).toMatchObject({
51
+ name: "proj-16174",
52
+ type: "muse",
53
+ status: AgentStatus.WAITING,
54
+ summary: "what is this repo about",
55
+ pid: 16174,
56
+ projectPath: "/Users/tester/proj",
57
+ sessionId: SID,
58
+ });
59
+ expect(agent.lastActive).toEqual(new Date("2026-10-10T14:00:00.000Z"));
60
+ expect(agent.sessionFilePath).toContain("session.jsonl");
61
+ });
62
+
63
+ it("falls back to a started summary and process cwd without workspace root", () => {
64
+ const agent = mapper.mapSessionToAgent({
65
+ session: session({ projectPath: "", lastUserMessage: undefined, lastSignal: undefined }),
66
+ processInfo: proc(),
67
+ sessionFile: { ...sessionFile(), resolvedCwd: "" },
68
+ });
69
+ expect(agent.summary).toBe("Session started");
70
+ expect(agent.projectPath).toBe("/Users/tester/proj");
71
+ expect(agent.status).toBe(AgentStatus.UNKNOWN);
72
+ });
73
+
74
+ it("maps an unmatched process to a running placeholder", () => {
75
+ const agent = mapper.mapProcessOnlyAgent(proc());
76
+ expect(agent).toMatchObject({
77
+ type: "muse",
78
+ status: AgentStatus.RUNNING,
79
+ pid: 16174,
80
+ sessionId: "pid-16174",
81
+ });
82
+ });
83
+ });
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Tests for harnesses/muse/MuseSessionLocator.ts — runtime-file matching,
3
+ * dated-archive discovery, and session lookup by id.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import * as os from "os";
8
+ import * as path from "path";
9
+ import type { ProcessInfo } from "../../../adapters/AgentAdapter.js";
10
+ import { MuseSessionLocator } from "../../../harnesses/muse/MuseSessionLocator.js";
11
+
12
+ const SID = "01a12618-d918-7b63-91c8-4e896326eb1a";
13
+ const OTHER_SID = "2288dbd2-d703-444d-afce-59e40a8ad8b1";
14
+
15
+ function frame(inner: unknown): string {
16
+ return JSON.stringify(inner);
17
+ }
18
+
19
+ function intent(text: string, recordedAtUs: number): unknown {
20
+ return {
21
+ schema_version: 1,
22
+ stream: { kind: "session", id: SID },
23
+ sequence: 2,
24
+ recorded_at: recordedAtUs,
25
+ record_type: "event",
26
+ payload_type: "runtime.user_intent.accepted",
27
+ payload: { refill_blocks: [{ kind: "text", text }] },
28
+ };
29
+ }
30
+
31
+ function metadata(workspaceRoot: string, recordedAtUs: number): unknown {
32
+ return {
33
+ schema_version: 1,
34
+ stream: { kind: "session", id: SID },
35
+ sequence: 1,
36
+ recorded_at: recordedAtUs,
37
+ record_type: "event",
38
+ payload_type: "runtime.session.metadata",
39
+ payload: { kind: "metadata", record: { workspace_root: workspaceRoot } },
40
+ };
41
+ }
42
+
43
+ function proc(pid: number, cwd: string, startTime: Date): ProcessInfo {
44
+ return {
45
+ pid,
46
+ command: `/Users/tester/.local/bin/muse-bin-1.4.4-R5419.1`,
47
+ cwd,
48
+ tty: "ttys001",
49
+ startTime,
50
+ };
51
+ }
52
+
53
+ describe("MuseSessionLocator", () => {
54
+ let home: string;
55
+ let locator: MuseSessionLocator;
56
+
57
+ beforeEach(() => {
58
+ home = fs.mkdtempSync(path.join(os.tmpdir(), "muse-locator-test-"));
59
+ locator = new MuseSessionLocator({ homeDir: home });
60
+ });
61
+
62
+ afterEach(() => {
63
+ fs.rmSync(home, { recursive: true, force: true });
64
+ });
65
+
66
+ function writeTranscript(sessionId: string, dayDir: string, lines: string[]): string {
67
+ const dir = path.join(home, ".local", "share", "muse", "sessions", dayDir, sessionId);
68
+ fs.mkdirSync(dir, { recursive: true });
69
+ const filePath = path.join(dir, "session.jsonl");
70
+ fs.writeFileSync(filePath, lines.join("\n"));
71
+ return filePath;
72
+ }
73
+
74
+ function writeRuntimeFile(sessionId: string, pid: number): string {
75
+ const dir = path.join(home, ".local", "share", "muse", "runtime", "muse", "sessions");
76
+ fs.mkdirSync(dir, { recursive: true });
77
+ const filePath = path.join(dir, `${sessionId}.jsonl`.replace(".jsonl", ".json"));
78
+ fs.writeFileSync(
79
+ filePath,
80
+ JSON.stringify({
81
+ schema_version: 1,
82
+ session_id: sessionId,
83
+ process_generation_hint: `pid=${pid}`,
84
+ }),
85
+ );
86
+ return filePath;
87
+ }
88
+
89
+ it("matches a live process to its transcript via the runtime file", () => {
90
+ const now = Date.now();
91
+ const filePath = writeTranscript(SID, "2026/10/10", [
92
+ frame(metadata("/Users/tester/proj", now * 1000)),
93
+ frame(intent("hello", now * 1000)),
94
+ ]);
95
+ writeRuntimeFile(SID, 16174);
96
+ // Align file birthtime with process start for the staleness guard.
97
+ const start = new Date(fs.statSync(filePath).birthtimeMs);
98
+
99
+ const { direct } = locator.matchRunningProcesses([proc(16174, "/Users/tester/proj", start)]);
100
+ expect(direct).toHaveLength(1);
101
+ expect(direct[0].process.pid).toBe(16174);
102
+ expect(direct[0].sessionFile.filePath).toBe(filePath);
103
+ expect(direct[0].sessionFile.sessionId).toBe(SID);
104
+ });
105
+
106
+ it("rejects runtime matches when the pid was recycled", () => {
107
+ writeTranscript(SID, "2026/10/10", [frame(intent("hello", 1791640525332173))]);
108
+ writeRuntimeFile(SID, 16174);
109
+
110
+ // Process started an hour after the session file was born: recycled pid.
111
+ const { direct, fallback } = locator.matchRunningProcesses([
112
+ proc(16174, "/Users/tester/proj", new Date(Date.now() + 3600_000)),
113
+ ]);
114
+ expect(direct).toHaveLength(0);
115
+ expect(fallback.map((p) => p.pid)).toContain(16174);
116
+ });
117
+
118
+ it("ignores malformed runtime files (processes fall through to legacy)", () => {
119
+ writeTranscript(SID, "2026/10/10", [frame(intent("hello", 1791640525332173))]);
120
+ const dir = path.join(home, ".local", "share", "muse", "runtime", "muse", "sessions");
121
+ fs.mkdirSync(dir, { recursive: true });
122
+ fs.writeFileSync(path.join(dir, `${SID}.json`), "not json");
123
+ fs.writeFileSync(
124
+ path.join(dir, "other.json"),
125
+ JSON.stringify({ session_id: "other", process_generation_hint: "nonsense" }),
126
+ );
127
+
128
+ // Non-muse processes never reach the locator: the adapter pre-filters via canHandle.
129
+ const { direct, fallback } = locator.matchRunningProcesses([
130
+ proc(16174, "/Users/tester/proj", new Date()),
131
+ ]);
132
+ expect(direct).toHaveLength(0);
133
+ expect(fallback.map((p) => p.pid)).toEqual([16174]);
134
+ });
135
+
136
+ it("discovers historical sessions across dated dirs, skipping noise", () => {
137
+ writeTranscript(SID, "2026/10/10", [frame(intent("hello", 1791640525332173))]);
138
+ writeTranscript(OTHER_SID, "2026/10/09", [frame(intent("world", 1791640525332173))]);
139
+ // Noise: subagent-style nested dir without session.jsonl, stray files.
140
+ const noise = path.join(
141
+ home,
142
+ ".local",
143
+ "share",
144
+ "muse",
145
+ "sessions",
146
+ "2026/10/10",
147
+ "not-a-uuid",
148
+ );
149
+ fs.mkdirSync(noise, { recursive: true });
150
+ fs.writeFileSync(path.join(noise, "notes.txt"), "hi");
151
+
152
+ const found = locator.discoverHistoricalSessionFiles();
153
+ expect(found.map((f) => f.filePath).sort()).toEqual(
154
+ [
155
+ path.join(
156
+ home,
157
+ ".local",
158
+ "share",
159
+ "muse",
160
+ "sessions",
161
+ "2026/10/09",
162
+ OTHER_SID,
163
+ "session.jsonl",
164
+ ),
165
+ path.join(home, ".local", "share", "muse", "sessions", "2026/10/10", SID, "session.jsonl"),
166
+ ].sort(),
167
+ );
168
+ });
169
+
170
+ it("finds a session file by id without enumerating everything", () => {
171
+ const filePath = writeTranscript(SID, "2026/10/10", [frame(intent("hello", 1791640525332173))]);
172
+ const found = locator.findHistoricalSessionFilesById(SID);
173
+ expect(found).toEqual([{ filePath, defaultCwd: "" }]);
174
+ expect(locator.findHistoricalSessionFilesById("nope")).toEqual([]);
175
+ expect(locator.findHistoricalSessionFilesById("../evil")).toEqual([]);
176
+ });
177
+ });