oh-my-opencode-dashboard 0.0.1

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.
@@ -0,0 +1,707 @@
1
+ import * as fs from "node:fs"
2
+ import * as os from "node:os"
3
+ import * as path from "node:path"
4
+ import { describe, expect, it } from "vitest"
5
+ import { deriveBackgroundTasks } from "./background-tasks"
6
+ import { getStorageRoots } from "./session"
7
+
8
+ function mkStorageRoot(): string {
9
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "omo-storage-"))
10
+ fs.mkdirSync(path.join(root, "session"), { recursive: true })
11
+ fs.mkdirSync(path.join(root, "message"), { recursive: true })
12
+ fs.mkdirSync(path.join(root, "part"), { recursive: true })
13
+ return root
14
+ }
15
+
16
+ describe("deriveBackgroundTasks", () => {
17
+ it("extracts delegate_task background calls and correlates child sessions", () => {
18
+ const storageRoot = mkStorageRoot()
19
+ const storage = getStorageRoots(storageRoot)
20
+ const mainSessionId = "ses_main"
21
+
22
+ // Main session message + tool part
23
+ const msgDir = path.join(storage.message, mainSessionId)
24
+ fs.mkdirSync(msgDir, { recursive: true })
25
+ const messageID = "msg_1"
26
+ fs.writeFileSync(
27
+ path.join(msgDir, `${messageID}.json`),
28
+ JSON.stringify({
29
+ id: messageID,
30
+ sessionID: mainSessionId,
31
+ role: "assistant",
32
+ time: { created: 1000 },
33
+ }),
34
+ "utf8"
35
+ )
36
+ const partDir = path.join(storage.part, messageID)
37
+ fs.mkdirSync(partDir, { recursive: true })
38
+ fs.writeFileSync(
39
+ path.join(partDir, "part_1.json"),
40
+ JSON.stringify({
41
+ id: "part_1",
42
+ sessionID: mainSessionId,
43
+ messageID,
44
+ type: "tool",
45
+ callID: "call_1",
46
+ tool: "delegate_task",
47
+ state: {
48
+ status: "completed",
49
+ input: {
50
+ run_in_background: true,
51
+ description: "Scan repo",
52
+ subagent_type: "explore",
53
+ prompt: "SECRET",
54
+ },
55
+ },
56
+ }),
57
+ "utf8"
58
+ )
59
+
60
+ // Child session metadata that should be correlated
61
+ const projectID = "proj"
62
+ const sessDir = path.join(storage.session, projectID)
63
+ fs.mkdirSync(sessDir, { recursive: true })
64
+ fs.writeFileSync(
65
+ path.join(sessDir, "ses_child.json"),
66
+ JSON.stringify({
67
+ id: "ses_child",
68
+ projectID,
69
+ directory: "/tmp/project",
70
+ title: "Background: Scan repo",
71
+ parentID: mainSessionId,
72
+ time: { created: 1500, updated: 1500 },
73
+ }),
74
+ "utf8"
75
+ )
76
+
77
+ // Background session message with a tool call
78
+ const childMsgDir = path.join(storage.message, "ses_child")
79
+ fs.mkdirSync(childMsgDir, { recursive: true })
80
+ const childMsgId = "msg_child"
81
+ fs.writeFileSync(
82
+ path.join(childMsgDir, `${childMsgId}.json`),
83
+ JSON.stringify({
84
+ id: childMsgId,
85
+ sessionID: "ses_child",
86
+ role: "assistant",
87
+ time: { created: 2000 },
88
+ }),
89
+ "utf8"
90
+ )
91
+ const childPartDir = path.join(storage.part, childMsgId)
92
+ fs.mkdirSync(childPartDir, { recursive: true })
93
+ fs.writeFileSync(
94
+ path.join(childPartDir, "part_1.json"),
95
+ JSON.stringify({
96
+ id: "part_1",
97
+ sessionID: "ses_child",
98
+ messageID: childMsgId,
99
+ type: "tool",
100
+ callID: "call_x",
101
+ tool: "grep",
102
+ state: { status: "completed", input: {} },
103
+ }),
104
+ "utf8"
105
+ )
106
+
107
+ const rows = deriveBackgroundTasks({ storage, mainSessionId, nowMs: 3000 })
108
+ expect(rows.length).toBe(1)
109
+ expect(rows[0].description).toBe("Scan repo")
110
+ expect(rows[0].agent).toBe("explore")
111
+ expect(rows[0].sessionId).toBe("ses_child")
112
+ expect(rows[0].toolCalls).toBe(1)
113
+ expect(rows[0].lastTool).toBe("grep")
114
+ expect(rows[0].timeline).toBe("1970-01-01T00:00:01Z: 2s")
115
+
116
+ const completed = deriveBackgroundTasks({ storage, mainSessionId, nowMs: 20_000 })
117
+ expect(completed.length).toBe(1)
118
+ expect(completed[0].status).toBe("completed")
119
+ expect(completed[0].timeline).toBe("1970-01-01T00:00:01Z: 1s")
120
+
121
+ // Ensure no sensitive keys leak
122
+ expect((rows[0] as unknown as Record<string, unknown>).prompt).toBeUndefined()
123
+ expect((rows[0] as unknown as Record<string, unknown>).input).toBeUndefined()
124
+ expect((rows[0] as unknown as Record<string, unknown>).state).toBeUndefined()
125
+ })
126
+
127
+ it("selects deterministic background session when multiple candidates have identical created timestamps", () => {
128
+ const storageRoot = mkStorageRoot()
129
+ const storage = getStorageRoots(storageRoot)
130
+ const mainSessionId = "ses_main"
131
+
132
+ const msgDir = path.join(storage.message, mainSessionId)
133
+ fs.mkdirSync(msgDir, { recursive: true })
134
+ const messageID = "msg_1"
135
+ fs.writeFileSync(
136
+ path.join(msgDir, `${messageID}.json`),
137
+ JSON.stringify({
138
+ id: messageID,
139
+ sessionID: mainSessionId,
140
+ role: "assistant",
141
+ time: { created: 1000 },
142
+ }),
143
+ "utf8"
144
+ )
145
+ const partDir = path.join(storage.part, messageID)
146
+ fs.mkdirSync(partDir, { recursive: true })
147
+ fs.writeFileSync(
148
+ path.join(partDir, "part_tie.json"),
149
+ JSON.stringify({
150
+ id: "part_tie",
151
+ sessionID: mainSessionId,
152
+ messageID,
153
+ type: "tool",
154
+ callID: "call_tie",
155
+ tool: "delegate_task",
156
+ state: {
157
+ status: "completed",
158
+ input: {
159
+ run_in_background: true,
160
+ description: "Tie-break test",
161
+ subagent_type: "explore",
162
+ },
163
+ },
164
+ }),
165
+ "utf8"
166
+ )
167
+
168
+ const projectID = "proj"
169
+ const sessDir = path.join(storage.session, projectID)
170
+ fs.mkdirSync(sessDir, { recursive: true })
171
+
172
+ const sharedTimestamp = 1500
173
+ fs.writeFileSync(
174
+ path.join(sessDir, "ses_zzz.json"),
175
+ JSON.stringify({
176
+ id: "ses_zzz",
177
+ projectID,
178
+ directory: "/tmp/project",
179
+ title: "Background: Tie-break test",
180
+ parentID: mainSessionId,
181
+ time: { created: sharedTimestamp, updated: sharedTimestamp },
182
+ }),
183
+ "utf8"
184
+ )
185
+ fs.writeFileSync(
186
+ path.join(sessDir, "ses_aaa.json"),
187
+ JSON.stringify({
188
+ id: "ses_aaa",
189
+ projectID,
190
+ directory: "/tmp/project",
191
+ title: "Background: Tie-break test",
192
+ parentID: mainSessionId,
193
+ time: { created: sharedTimestamp, updated: sharedTimestamp },
194
+ }),
195
+ "utf8"
196
+ )
197
+
198
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
199
+ expect(rows.length).toBe(1)
200
+ expect(rows[0].sessionId).toBe("ses_aaa")
201
+ expect(rows[0].description).toBe("Tie-break test")
202
+ expect(rows[0].agent).toBe("explore")
203
+ })
204
+
205
+ it("includes sync delegate_task rows when run_in_background is false", () => {
206
+ const storageRoot = mkStorageRoot()
207
+ const storage = getStorageRoots(storageRoot)
208
+ const mainSessionId = "ses_main"
209
+
210
+ // Main session message + sync tool part
211
+ const msgDir = path.join(storage.message, mainSessionId)
212
+ fs.mkdirSync(msgDir, { recursive: true })
213
+ const messageID = "msg_1"
214
+ fs.writeFileSync(
215
+ path.join(msgDir, `${messageID}.json`),
216
+ JSON.stringify({
217
+ id: messageID,
218
+ sessionID: mainSessionId,
219
+ role: "assistant",
220
+ time: { created: 1000 },
221
+ }),
222
+ "utf8"
223
+ )
224
+ const partDir = path.join(storage.part, messageID)
225
+ fs.mkdirSync(partDir, { recursive: true })
226
+ fs.writeFileSync(
227
+ path.join(partDir, "part_sync.json"),
228
+ JSON.stringify({
229
+ id: "part_sync",
230
+ sessionID: mainSessionId,
231
+ messageID,
232
+ type: "tool",
233
+ callID: "call_sync",
234
+ tool: "delegate_task",
235
+ state: {
236
+ status: "completed",
237
+ input: {
238
+ run_in_background: false,
239
+ description: "Quick analysis",
240
+ category: "quick",
241
+ prompt: "SHOULD NOT APPEAR",
242
+ },
243
+ },
244
+ }),
245
+ "utf8"
246
+ )
247
+
248
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
249
+ expect(rows.length).toBe(1)
250
+ expect(rows[0].description).toBe("Quick analysis")
251
+ expect(rows[0].agent).toBe("sisyphus-junior (quick)")
252
+ expect(rows[0].sessionId).toBe(null) // No background session for sync tasks
253
+ expect(rows[0].toolCalls).toBe(null) // No background session stats for sync tasks
254
+ expect(rows[0].lastTool).toBe(null)
255
+ expect(rows[0].status).toBe("queued") // Should show queued for unlinked sync tasks
256
+
257
+ // Ensure no sensitive keys leak
258
+ expect((rows[0] as unknown as Record<string, unknown>).prompt).toBeUndefined()
259
+ expect((rows[0] as unknown as Record<string, unknown>).input).toBeUndefined()
260
+ expect((rows[0] as unknown as Record<string, unknown>).state).toBeUndefined()
261
+ })
262
+
263
+ it("selects deterministic task session when multiple candidates have identical created timestamps", () => {
264
+ const storageRoot = mkStorageRoot()
265
+ const storage = getStorageRoots(storageRoot)
266
+ const mainSessionId = "ses_main"
267
+
268
+ const msgDir = path.join(storage.message, mainSessionId)
269
+ fs.mkdirSync(msgDir, { recursive: true })
270
+ const messageID = "msg_1"
271
+ fs.writeFileSync(
272
+ path.join(msgDir, `${messageID}.json`),
273
+ JSON.stringify({
274
+ id: messageID,
275
+ sessionID: mainSessionId,
276
+ role: "assistant",
277
+ time: { created: 1000 },
278
+ }),
279
+ "utf8"
280
+ )
281
+ const partDir = path.join(storage.part, messageID)
282
+ fs.mkdirSync(partDir, { recursive: true })
283
+ fs.writeFileSync(
284
+ path.join(partDir, "part_tie_task.json"),
285
+ JSON.stringify({
286
+ id: "part_tie_task",
287
+ sessionID: mainSessionId,
288
+ messageID,
289
+ type: "tool",
290
+ callID: "call_tie_task",
291
+ tool: "delegate_task",
292
+ state: {
293
+ status: "completed",
294
+ input: {
295
+ run_in_background: false,
296
+ description: "Task tie-break test",
297
+ category: "quick",
298
+ },
299
+ },
300
+ }),
301
+ "utf8"
302
+ )
303
+
304
+ const projectID = "proj"
305
+ const sessDir = path.join(storage.session, projectID)
306
+ fs.mkdirSync(sessDir, { recursive: true })
307
+
308
+ const sharedTimestamp = 1500
309
+ fs.writeFileSync(
310
+ path.join(sessDir, "ses_task_zzz.json"),
311
+ JSON.stringify({
312
+ id: "ses_task_zzz",
313
+ projectID,
314
+ directory: "/tmp/project",
315
+ title: "Task: Task tie-break test",
316
+ parentID: mainSessionId,
317
+ time: { created: sharedTimestamp, updated: sharedTimestamp },
318
+ }),
319
+ "utf8"
320
+ )
321
+ fs.writeFileSync(
322
+ path.join(sessDir, "ses_task_aaa.json"),
323
+ JSON.stringify({
324
+ id: "ses_task_aaa",
325
+ projectID,
326
+ directory: "/tmp/project",
327
+ title: "Task: Task tie-break test",
328
+ parentID: mainSessionId,
329
+ time: { created: sharedTimestamp, updated: sharedTimestamp },
330
+ }),
331
+ "utf8"
332
+ )
333
+
334
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
335
+ expect(rows.length).toBe(1)
336
+ expect(rows[0].sessionId).toBe("ses_task_aaa")
337
+ expect(rows[0].description).toBe("Task tie-break test")
338
+ expect(rows[0].agent).toBe("sisyphus-junior (quick)")
339
+ })
340
+
341
+ it("links sync delegate_task rows to Task sessions when available", () => {
342
+ const storageRoot = mkStorageRoot()
343
+ const storage = getStorageRoots(storageRoot)
344
+ const mainSessionId = "ses_main"
345
+
346
+ // Main session message + sync tool part
347
+ const msgDir = path.join(storage.message, mainSessionId)
348
+ fs.mkdirSync(msgDir, { recursive: true })
349
+ const messageID = "msg_1"
350
+ fs.writeFileSync(
351
+ path.join(msgDir, `${messageID}.json`),
352
+ JSON.stringify({
353
+ id: messageID,
354
+ sessionID: mainSessionId,
355
+ role: "assistant",
356
+ time: { created: 1000 },
357
+ }),
358
+ "utf8"
359
+ )
360
+ const partDir = path.join(storage.part, messageID)
361
+ fs.mkdirSync(partDir, { recursive: true })
362
+ fs.writeFileSync(
363
+ path.join(partDir, "part_sync.json"),
364
+ JSON.stringify({
365
+ id: "part_sync",
366
+ sessionID: mainSessionId,
367
+ messageID,
368
+ type: "tool",
369
+ callID: "call_sync",
370
+ tool: "delegate_task",
371
+ state: {
372
+ status: "completed",
373
+ input: {
374
+ run_in_background: false,
375
+ description: "Quick analysis",
376
+ category: "quick",
377
+ prompt: "SHOULD NOT APPEAR",
378
+ },
379
+ },
380
+ }),
381
+ "utf8"
382
+ )
383
+
384
+ // Child session metadata with Task: title that should be correlated
385
+ const projectID = "proj"
386
+ const sessDir = path.join(storage.session, projectID)
387
+ fs.mkdirSync(sessDir, { recursive: true })
388
+ fs.writeFileSync(
389
+ path.join(sessDir, "ses_task.json"),
390
+ JSON.stringify({
391
+ id: "ses_task",
392
+ projectID,
393
+ directory: "/tmp/project",
394
+ title: "Task: Quick analysis",
395
+ parentID: mainSessionId,
396
+ time: { created: 1050, updated: 1050 },
397
+ }),
398
+ "utf8"
399
+ )
400
+
401
+ // Task session message with a tool call
402
+ const taskMsgDir = path.join(storage.message, "ses_task")
403
+ fs.mkdirSync(taskMsgDir, { recursive: true })
404
+ const taskMsgId = "msg_task"
405
+ fs.writeFileSync(
406
+ path.join(taskMsgDir, `${taskMsgId}.json`),
407
+ JSON.stringify({
408
+ id: taskMsgId,
409
+ sessionID: "ses_task",
410
+ role: "assistant",
411
+ time: { created: 1100 },
412
+ }),
413
+ "utf8"
414
+ )
415
+ const taskPartDir = path.join(storage.part, taskMsgId)
416
+ fs.mkdirSync(taskPartDir, { recursive: true })
417
+ fs.writeFileSync(
418
+ path.join(taskPartDir, "part_1.json"),
419
+ JSON.stringify({
420
+ id: "part_1",
421
+ sessionID: "ses_task",
422
+ messageID: taskMsgId,
423
+ type: "tool",
424
+ callID: "call_x",
425
+ tool: "read",
426
+ state: { status: "completed", input: {} },
427
+ }),
428
+ "utf8"
429
+ )
430
+
431
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
432
+ expect(rows.length).toBe(1)
433
+ expect(rows[0].description).toBe("Quick analysis")
434
+ expect(rows[0].agent).toBe("sisyphus-junior (quick)")
435
+ expect(rows[0].sessionId).toBe("ses_task") // Should be linked to Task session
436
+ expect(rows[0].toolCalls).toBe(1)
437
+ expect(rows[0].lastTool).toBe("read")
438
+ expect(rows[0].status).toBe("completed") // Should be completed since Task session has tool calls
439
+
440
+ // Ensure no sensitive keys leak
441
+ expect((rows[0] as unknown as Record<string, unknown>).prompt).toBeUndefined()
442
+ expect((rows[0] as unknown as Record<string, unknown>).input).toBeUndefined()
443
+ expect((rows[0] as unknown as Record<string, unknown>).state).toBeUndefined()
444
+ })
445
+
446
+ it("links sync delegate_task rows to resumed session when resume is specified", () => {
447
+ const storageRoot = mkStorageRoot()
448
+ const storage = getStorageRoots(storageRoot)
449
+ const mainSessionId = "ses_main"
450
+ const resumedSessionId = "ses_resumed"
451
+
452
+ // Main session message + sync tool part with resume
453
+ const msgDir = path.join(storage.message, mainSessionId)
454
+ fs.mkdirSync(msgDir, { recursive: true })
455
+ const messageID = "msg_1"
456
+ fs.writeFileSync(
457
+ path.join(msgDir, `${messageID}.json`),
458
+ JSON.stringify({
459
+ id: messageID,
460
+ sessionID: mainSessionId,
461
+ role: "assistant",
462
+ time: { created: 1000 },
463
+ }),
464
+ "utf8"
465
+ )
466
+ const partDir = path.join(storage.part, messageID)
467
+ fs.mkdirSync(partDir, { recursive: true })
468
+ fs.writeFileSync(
469
+ path.join(partDir, "part_resume.json"),
470
+ JSON.stringify({
471
+ id: "part_resume",
472
+ sessionID: mainSessionId,
473
+ messageID,
474
+ type: "tool",
475
+ callID: "call_resume",
476
+ tool: "delegate_task",
477
+ state: {
478
+ status: "completed",
479
+ input: {
480
+ run_in_background: false,
481
+ description: "Resume work",
482
+ category: "quick",
483
+ resume: resumedSessionId,
484
+ prompt: "SHOULD NOT APPEAR",
485
+ },
486
+ },
487
+ }),
488
+ "utf8"
489
+ )
490
+
491
+ // Resumed session message + tool call
492
+ const resumedMsgDir = path.join(storage.message, resumedSessionId)
493
+ fs.mkdirSync(resumedMsgDir, { recursive: true })
494
+ const resumedMsgId = "msg_resumed"
495
+ fs.writeFileSync(
496
+ path.join(resumedMsgDir, `${resumedMsgId}.json`),
497
+ JSON.stringify({
498
+ id: resumedMsgId,
499
+ sessionID: resumedSessionId,
500
+ role: "assistant",
501
+ time: { created: 500 },
502
+ }),
503
+ "utf8"
504
+ )
505
+ const resumedPartDir = path.join(storage.part, resumedMsgId)
506
+ fs.mkdirSync(resumedPartDir, { recursive: true })
507
+ fs.writeFileSync(
508
+ path.join(resumedPartDir, "part_1.json"),
509
+ JSON.stringify({
510
+ id: "part_1",
511
+ sessionID: resumedSessionId,
512
+ messageID: resumedMsgId,
513
+ type: "tool",
514
+ callID: "call_grep",
515
+ tool: "grep",
516
+ state: { status: "completed", input: {} },
517
+ }),
518
+ "utf8"
519
+ )
520
+
521
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
522
+ expect(rows.length).toBe(1)
523
+ expect(rows[0].description).toBe("Resume work")
524
+ expect(rows[0].agent).toBe("sisyphus-junior (quick)")
525
+ expect(rows[0].sessionId).toBe(resumedSessionId) // Should be linked to resumed session
526
+ expect(rows[0].toolCalls).toBe(1)
527
+ expect(rows[0].lastTool).toBe("grep")
528
+ expect(rows[0].status).toBe("completed") // Should be completed since resumed session has tool calls
529
+
530
+ // Ensure no sensitive keys leak
531
+ expect((rows[0] as unknown as Record<string, unknown>).prompt).toBeUndefined()
532
+ expect((rows[0] as unknown as Record<string, unknown>).input).toBeUndefined()
533
+ expect((rows[0] as unknown as Record<string, unknown>).state).toBeUndefined()
534
+ })
535
+
536
+ it("falls back to title-based matching when resume session does not exist", () => {
537
+ const storageRoot = mkStorageRoot()
538
+ const storage = getStorageRoots(storageRoot)
539
+ const mainSessionId = "ses_main"
540
+ const nonExistentResumeId = "ses_nonexistent"
541
+
542
+ // Main session message + sync tool part with non-existent resume
543
+ const msgDir = path.join(storage.message, mainSessionId)
544
+ fs.mkdirSync(msgDir, { recursive: true })
545
+ const messageID = "msg_1"
546
+ fs.writeFileSync(
547
+ path.join(msgDir, `${messageID}.json`),
548
+ JSON.stringify({
549
+ id: messageID,
550
+ sessionID: mainSessionId,
551
+ role: "assistant",
552
+ time: { created: 1000 },
553
+ }),
554
+ "utf8"
555
+ )
556
+ const partDir = path.join(storage.part, messageID)
557
+ fs.mkdirSync(partDir, { recursive: true })
558
+ fs.writeFileSync(
559
+ path.join(partDir, "part_fallback.json"),
560
+ JSON.stringify({
561
+ id: "part_fallback",
562
+ sessionID: mainSessionId,
563
+ messageID,
564
+ type: "tool",
565
+ callID: "call_fallback",
566
+ tool: "delegate_task",
567
+ state: {
568
+ status: "completed",
569
+ input: {
570
+ run_in_background: false,
571
+ description: "Fallback task",
572
+ category: "quick",
573
+ resume: nonExistentResumeId,
574
+ prompt: "SHOULD NOT APPEAR",
575
+ },
576
+ },
577
+ }),
578
+ "utf8"
579
+ )
580
+
581
+ // Child session metadata with Task: title that should be used as fallback
582
+ const projectID = "proj"
583
+ const sessDir = path.join(storage.session, projectID)
584
+ fs.mkdirSync(sessDir, { recursive: true })
585
+ fs.writeFileSync(
586
+ path.join(sessDir, "ses_task.json"),
587
+ JSON.stringify({
588
+ id: "ses_task",
589
+ projectID,
590
+ directory: "/tmp/project",
591
+ title: "Task: Fallback task",
592
+ parentID: mainSessionId,
593
+ time: { created: 1050, updated: 1050 },
594
+ }),
595
+ "utf8"
596
+ )
597
+
598
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
599
+ expect(rows.length).toBe(1)
600
+ expect(rows[0].description).toBe("Fallback task")
601
+ expect(rows[0].sessionId).toBe("ses_task") // Should fallback to Task session
602
+ expect(rows[0].toolCalls).toBe(0) // No tool calls in fallback session
603
+ expect(rows[0].lastTool).toBe(null)
604
+ expect(rows[0].status).toBe("unknown") // Should be unknown since session exists but no tool calls
605
+ })
606
+
607
+ it("links sync delegate_task rows to Background sessions when forced-to-background but waited", () => {
608
+ const storageRoot = mkStorageRoot()
609
+ const storage = getStorageRoots(storageRoot)
610
+ const mainSessionId = "ses_main"
611
+
612
+ const msgDir = path.join(storage.message, mainSessionId)
613
+ fs.mkdirSync(msgDir, { recursive: true })
614
+ const messageID = "msg_1"
615
+ fs.writeFileSync(
616
+ path.join(msgDir, `${messageID}.json`),
617
+ JSON.stringify({
618
+ id: messageID,
619
+ sessionID: mainSessionId,
620
+ role: "assistant",
621
+ time: { created: 1000 },
622
+ }),
623
+ "utf8"
624
+ )
625
+ const partDir = path.join(storage.part, messageID)
626
+ fs.mkdirSync(partDir, { recursive: true })
627
+ fs.writeFileSync(
628
+ path.join(partDir, "part_forced.json"),
629
+ JSON.stringify({
630
+ id: "part_forced",
631
+ sessionID: mainSessionId,
632
+ messageID,
633
+ type: "tool",
634
+ callID: "call_forced",
635
+ tool: "delegate_task",
636
+ state: {
637
+ status: "completed",
638
+ input: {
639
+ run_in_background: false,
640
+ description: "Forced background task",
641
+ category: "quick",
642
+ prompt: "SHOULD NOT APPEAR",
643
+ },
644
+ },
645
+ }),
646
+ "utf8"
647
+ )
648
+
649
+ const projectID = "proj"
650
+ const sessDir = path.join(storage.session, projectID)
651
+ fs.mkdirSync(sessDir, { recursive: true })
652
+ fs.writeFileSync(
653
+ path.join(sessDir, "ses_background.json"),
654
+ JSON.stringify({
655
+ id: "ses_background",
656
+ projectID,
657
+ directory: "/tmp/project",
658
+ title: "Background: Forced background task",
659
+ parentID: mainSessionId,
660
+ time: { created: 1050, updated: 1050 },
661
+ }),
662
+ "utf8"
663
+ )
664
+
665
+ const backgroundMsgDir = path.join(storage.message, "ses_background")
666
+ fs.mkdirSync(backgroundMsgDir, { recursive: true })
667
+ const backgroundMsgId = "msg_background"
668
+ fs.writeFileSync(
669
+ path.join(backgroundMsgDir, `${backgroundMsgId}.json`),
670
+ JSON.stringify({
671
+ id: backgroundMsgId,
672
+ sessionID: "ses_background",
673
+ role: "assistant",
674
+ time: { created: 1100 },
675
+ }),
676
+ "utf8"
677
+ )
678
+ const backgroundPartDir = path.join(storage.part, backgroundMsgId)
679
+ fs.mkdirSync(backgroundPartDir, { recursive: true })
680
+ fs.writeFileSync(
681
+ path.join(backgroundPartDir, "part_1.json"),
682
+ JSON.stringify({
683
+ id: "part_1",
684
+ sessionID: "ses_background",
685
+ messageID: backgroundMsgId,
686
+ type: "tool",
687
+ callID: "call_x",
688
+ tool: "bash",
689
+ state: { status: "completed", input: {} },
690
+ }),
691
+ "utf8"
692
+ )
693
+
694
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
695
+ expect(rows.length).toBe(1)
696
+ expect(rows[0].description).toBe("Forced background task")
697
+ expect(rows[0].agent).toBe("sisyphus-junior (quick)")
698
+ expect(rows[0].sessionId).toBe("ses_background")
699
+ expect(rows[0].toolCalls).toBe(1)
700
+ expect(rows[0].lastTool).toBe("bash")
701
+ expect(rows[0].status).toBe("completed")
702
+
703
+ expect((rows[0] as unknown as Record<string, unknown>).prompt).toBeUndefined()
704
+ expect((rows[0] as unknown as Record<string, unknown>).input).toBeUndefined()
705
+ expect((rows[0] as unknown as Record<string, unknown>).state).toBeUndefined()
706
+ })
707
+ })