@opencode-trace/plugin 0.0.4 → 0.0.5
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 +4 -11
- package/dist/__tests__/tracer.test.d.ts +2 -0
- package/dist/__tests__/tracer.test.d.ts.map +1 -0
- package/dist/__tests__/tracer.test.js +276 -0
- package/dist/__tests__/tracer.test.js.map +1 -0
- package/dist/integration.test.js +508 -11
- package/dist/integration.test.js.map +1 -1
- package/dist/plugin-instance.d.ts +31 -17
- package/dist/plugin-instance.d.ts.map +1 -1
- package/dist/plugin-instance.js +249 -78
- package/dist/plugin-instance.js.map +1 -1
- package/dist/plugin-instance.test.js +672 -25
- package/dist/plugin-instance.test.js.map +1 -1
- package/dist/trace.d.ts.map +1 -1
- package/dist/trace.js +209 -54
- package/dist/trace.js.map +1 -1
- package/dist/trace.test.js +630 -47
- package/dist/trace.test.js.map +1 -1
- package/dist/tracer.d.ts +20 -0
- package/dist/tracer.d.ts.map +1 -0
- package/dist/tracer.js +12 -0
- package/dist/tracer.js.map +1 -0
- package/dist/write-queue.d.ts +27 -2
- package/dist/write-queue.d.ts.map +1 -1
- package/dist/write-queue.js +99 -14
- package/dist/write-queue.js.map +1 -1
- package/dist/write-queue.test.js +373 -6
- package/dist/write-queue.test.js.map +1 -1
- package/package.json +11 -4
- package/dist/state-queue.d.ts +0 -14
- package/dist/state-queue.d.ts.map +0 -1
- package/dist/state-queue.js +0 -44
- package/dist/state-queue.js.map +0 -1
- package/dist/state-queue.test.d.ts +0 -2
- package/dist/state-queue.test.d.ts.map +0 -1
- package/dist/state-queue.test.js +0 -99
- package/dist/state-queue.test.js.map +0 -1
package/dist/trace.test.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import { mkdtempSync, rmSync, existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync, } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { logger } from "@opencode-trace/core";
|
|
5
6
|
import entrypoint, { _resetForTesting } from "./trace.js";
|
|
6
7
|
vi.mock("node:os", async (importOriginal) => {
|
|
7
8
|
const original = await importOriginal();
|
|
@@ -26,13 +27,12 @@ async function waitForFile(filePath, timeoutMs = 5000) {
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
catch {
|
|
30
|
-
}
|
|
30
|
+
catch { }
|
|
31
31
|
}
|
|
32
32
|
if (Date.now() - startTime > timeoutMs) {
|
|
33
33
|
throw new Error(`Timeout waiting for valid file ${filePath} after ${timeoutMs}ms`);
|
|
34
34
|
}
|
|
35
|
-
await new Promise(r => setTimeout(r, 10));
|
|
35
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
let testDir;
|
|
@@ -100,8 +100,8 @@ describe("Plugin - event hook 处理 session.created", () => {
|
|
|
100
100
|
const traceDir = join(testDir, ".opencode-trace");
|
|
101
101
|
const sessionDir = join(traceDir, sessionId);
|
|
102
102
|
expect(existsSync(sessionDir)).toBe(true);
|
|
103
|
-
const
|
|
104
|
-
expect(existsSync(
|
|
103
|
+
const configPath = join(traceDir, "config.json");
|
|
104
|
+
expect(existsSync(configPath)).toBe(true);
|
|
105
105
|
const metaPath = join(sessionDir, "metadata.json");
|
|
106
106
|
expect(existsSync(metaPath)).toBe(true);
|
|
107
107
|
const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
@@ -162,9 +162,13 @@ describe("tracedFetch stream integration", () => {
|
|
|
162
162
|
const mockFetch = vi.fn();
|
|
163
163
|
const originalFetch = globalThis.fetch;
|
|
164
164
|
globalThis.fetch = mockFetch;
|
|
165
|
-
const chunks = [
|
|
165
|
+
const chunks = [
|
|
166
|
+
'data: {"content": "Hello"}\n',
|
|
167
|
+
'data: {"content": "World"}\n',
|
|
168
|
+
"data: [DONE]\n",
|
|
169
|
+
];
|
|
166
170
|
const encoder = new TextEncoder();
|
|
167
|
-
const streamChunks = chunks.map(c => encoder.encode(c));
|
|
171
|
+
const streamChunks = chunks.map((c) => encoder.encode(c));
|
|
168
172
|
const mockStream = new ReadableStream({
|
|
169
173
|
start(controller) {
|
|
170
174
|
for (const chunk of streamChunks) {
|
|
@@ -208,7 +212,10 @@ describe("tracedFetch stream integration", () => {
|
|
|
208
212
|
"content-type": "application/json",
|
|
209
213
|
"x-opencode-session": sessionId,
|
|
210
214
|
},
|
|
211
|
-
body: JSON.stringify({
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
stream: true,
|
|
217
|
+
messages: [{ role: "user", content: "test" }],
|
|
218
|
+
}),
|
|
212
219
|
});
|
|
213
220
|
const response = await globalThis.fetch(streamRequest);
|
|
214
221
|
expect(response.__latencyMeta).toBeDefined();
|
|
@@ -278,7 +285,10 @@ describe("tracedFetch stream integration", () => {
|
|
|
278
285
|
"content-type": "application/json",
|
|
279
286
|
"x-opencode-session": sessionId,
|
|
280
287
|
},
|
|
281
|
-
body: JSON.stringify({
|
|
288
|
+
body: JSON.stringify({
|
|
289
|
+
stream: false,
|
|
290
|
+
messages: [{ role: "user", content: "test" }],
|
|
291
|
+
}),
|
|
282
292
|
});
|
|
283
293
|
const response = await globalThis.fetch(request);
|
|
284
294
|
expect(response.__latencyMeta).toBeUndefined();
|
|
@@ -324,8 +334,8 @@ describe("Plugin - tool.execute.after hook 处理 Task 工具", () => {
|
|
|
324
334
|
metadata: { session_id: subSessionId },
|
|
325
335
|
});
|
|
326
336
|
const traceDir = join(testDir, ".opencode-trace");
|
|
327
|
-
const
|
|
328
|
-
expect(existsSync(
|
|
337
|
+
const configPath = join(traceDir, "config.json");
|
|
338
|
+
expect(existsSync(configPath)).toBe(true);
|
|
329
339
|
});
|
|
330
340
|
test("tool.execute.after hook 对非 Task 工具不记录 sub session", async () => {
|
|
331
341
|
const hooks = await entrypoint.server({
|
|
@@ -364,12 +374,12 @@ describe("Plugin - tool.execute.after hook 处理 Task 工具", () => {
|
|
|
364
374
|
metadata: {},
|
|
365
375
|
});
|
|
366
376
|
const traceDir = join(testDir, ".opencode-trace");
|
|
367
|
-
const
|
|
368
|
-
expect(existsSync(
|
|
377
|
+
const configPath = join(traceDir, "config.json");
|
|
378
|
+
expect(existsSync(configPath)).toBe(true);
|
|
369
379
|
});
|
|
370
380
|
});
|
|
371
381
|
describe("Plugin - global/local mode", () => {
|
|
372
|
-
test("
|
|
382
|
+
test("trace_on tool enables session scope", async () => {
|
|
373
383
|
const hooks = await entrypoint.server({
|
|
374
384
|
client: {},
|
|
375
385
|
project: {},
|
|
@@ -379,7 +389,7 @@ describe("Plugin - global/local mode", () => {
|
|
|
379
389
|
serverUrl: new URL("http://localhost"),
|
|
380
390
|
$: {},
|
|
381
391
|
});
|
|
382
|
-
const sessionId = "test-session-
|
|
392
|
+
const sessionId = "test-session-on";
|
|
383
393
|
await hooks.event({
|
|
384
394
|
event: {
|
|
385
395
|
type: "session.created",
|
|
@@ -395,8 +405,10 @@ describe("Plugin - global/local mode", () => {
|
|
|
395
405
|
},
|
|
396
406
|
},
|
|
397
407
|
});
|
|
398
|
-
const result = await hooks.tool.
|
|
399
|
-
|
|
408
|
+
const result = await hooks.tool.trace_on.execute({}, {
|
|
409
|
+
sessionID: sessionId,
|
|
410
|
+
});
|
|
411
|
+
expect(result).toContain("Trace enabled for session");
|
|
400
412
|
const globalDir = join(testDir, ".opencode-trace");
|
|
401
413
|
const sessionDir = join(globalDir, sessionId);
|
|
402
414
|
await hooks.event({
|
|
@@ -414,10 +426,10 @@ describe("Plugin - global/local mode", () => {
|
|
|
414
426
|
},
|
|
415
427
|
},
|
|
416
428
|
});
|
|
417
|
-
await new Promise(r => setTimeout(r, 100));
|
|
429
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
418
430
|
expect(existsSync(sessionDir)).toBe(true);
|
|
419
431
|
});
|
|
420
|
-
test("
|
|
432
|
+
test("trace_off tool disables session scope", async () => {
|
|
421
433
|
const hooks = await entrypoint.server({
|
|
422
434
|
client: {},
|
|
423
435
|
project: {},
|
|
@@ -427,7 +439,7 @@ describe("Plugin - global/local mode", () => {
|
|
|
427
439
|
serverUrl: new URL("http://localhost"),
|
|
428
440
|
$: {},
|
|
429
441
|
});
|
|
430
|
-
const sessionId = "test-session-
|
|
442
|
+
const sessionId = "test-session-off";
|
|
431
443
|
await hooks.event({
|
|
432
444
|
event: {
|
|
433
445
|
type: "session.created",
|
|
@@ -443,29 +455,12 @@ describe("Plugin - global/local mode", () => {
|
|
|
443
455
|
},
|
|
444
456
|
},
|
|
445
457
|
});
|
|
446
|
-
const result = await hooks.tool.
|
|
447
|
-
|
|
448
|
-
const localDir = join(testDir, ".opencode-trace");
|
|
449
|
-
const sessionDir = join(localDir, sessionId);
|
|
450
|
-
await hooks.event({
|
|
451
|
-
event: {
|
|
452
|
-
type: "session.updated",
|
|
453
|
-
properties: {
|
|
454
|
-
info: {
|
|
455
|
-
id: sessionId,
|
|
456
|
-
projectID: "test-project",
|
|
457
|
-
directory: testDir,
|
|
458
|
-
title: "Test Session Updated",
|
|
459
|
-
version: "1.0",
|
|
460
|
-
time: { created: Date.now(), updated: Date.now() },
|
|
461
|
-
},
|
|
462
|
-
},
|
|
463
|
-
},
|
|
458
|
+
const result = await hooks.tool.trace_off.execute({}, {
|
|
459
|
+
sessionID: sessionId,
|
|
464
460
|
});
|
|
465
|
-
|
|
466
|
-
expect(existsSync(sessionDir)).toBe(true);
|
|
461
|
+
expect(result).toContain("Trace disabled for session");
|
|
467
462
|
});
|
|
468
|
-
test("
|
|
463
|
+
test("trace_status tool shows current status", async () => {
|
|
469
464
|
const hooks = await entrypoint.server({
|
|
470
465
|
client: {},
|
|
471
466
|
project: {},
|
|
@@ -491,11 +486,599 @@ describe("Plugin - global/local mode", () => {
|
|
|
491
486
|
},
|
|
492
487
|
},
|
|
493
488
|
});
|
|
494
|
-
const result = await hooks.tool.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
expect(
|
|
498
|
-
expect(
|
|
489
|
+
const result = await hooks.tool.trace_status.execute({}, {
|
|
490
|
+
sessionID: sessionId,
|
|
491
|
+
});
|
|
492
|
+
expect(result).toContain("Trace Status");
|
|
493
|
+
expect(result).toContain("Global");
|
|
494
|
+
expect(result).toContain("Local");
|
|
495
|
+
expect(result).toContain("Session");
|
|
496
|
+
expect(result).toContain("Storage");
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
async function setupPluginWithMockClient(testDir) {
|
|
500
|
+
_resetForTesting();
|
|
501
|
+
const mockPrompt = vi.fn().mockResolvedValue({});
|
|
502
|
+
const hooks = await entrypoint.server({
|
|
503
|
+
client: { session: { prompt: mockPrompt } },
|
|
504
|
+
project: {},
|
|
505
|
+
directory: testDir,
|
|
506
|
+
worktree: testDir,
|
|
507
|
+
experimental_workspace: { register: vi.fn() },
|
|
508
|
+
serverUrl: new URL("http://localhost"),
|
|
509
|
+
$: {},
|
|
510
|
+
});
|
|
511
|
+
return { hooks, mockPrompt };
|
|
512
|
+
}
|
|
513
|
+
async function runTraceCommand(hooks, mockPrompt, args, sessionId = "test-slash-session") {
|
|
514
|
+
const output = { parts: [{ type: "text", text: "original" }] };
|
|
515
|
+
let error = null;
|
|
516
|
+
try {
|
|
517
|
+
await hooks["command.execute.before"]({
|
|
518
|
+
command: "trace",
|
|
519
|
+
sessionID: sessionId,
|
|
520
|
+
arguments: args,
|
|
521
|
+
}, output);
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
error = err;
|
|
525
|
+
}
|
|
526
|
+
expect(error).toBeTruthy();
|
|
527
|
+
expect(error.message).toBe("__TRACE_HANDLED__");
|
|
528
|
+
expect(output.parts.length).toBe(0);
|
|
529
|
+
if (mockPrompt.mock.calls.length > 0) {
|
|
530
|
+
const call = mockPrompt.mock.calls[0][0];
|
|
531
|
+
return { text: call.body.parts[0].text, outputParts: output.parts };
|
|
532
|
+
}
|
|
533
|
+
return { text: null, outputParts: output.parts };
|
|
534
|
+
}
|
|
535
|
+
async function createSessionViaEvent(hooks, sessionId, testDir, title = "Slash Test Session", parentID) {
|
|
536
|
+
await hooks.event({
|
|
537
|
+
event: {
|
|
538
|
+
type: "session.created",
|
|
539
|
+
properties: {
|
|
540
|
+
info: {
|
|
541
|
+
id: sessionId,
|
|
542
|
+
projectID: "test-project",
|
|
543
|
+
directory: testDir,
|
|
544
|
+
title,
|
|
545
|
+
parentID,
|
|
546
|
+
version: "1.0",
|
|
547
|
+
time: { created: Date.now(), updated: Date.now() },
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
function readConfig(testDir) {
|
|
554
|
+
const configPath = join(testDir, ".opencode-trace", "config.json");
|
|
555
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
556
|
+
}
|
|
557
|
+
function readSessionMetadata(testDir, sessionId) {
|
|
558
|
+
const metaPath = join(testDir, ".opencode-trace", sessionId, "metadata.json");
|
|
559
|
+
if (!existsSync(metaPath))
|
|
560
|
+
return null;
|
|
561
|
+
return JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
562
|
+
}
|
|
563
|
+
describe("Plugin - /trace on (slash command)", () => {
|
|
564
|
+
test("/trace on with no flags enables global scope by default", async () => {
|
|
565
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
566
|
+
const sessionId = "slash-on-default";
|
|
567
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
568
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on", sessionId);
|
|
569
|
+
expect(text).toContain("Trace enabled");
|
|
570
|
+
expect(text).toContain("global");
|
|
571
|
+
const config = readConfig(testDir);
|
|
572
|
+
expect(config.global_trace_enabled).toBe(true);
|
|
573
|
+
});
|
|
574
|
+
test("/trace on -g explicitly enables global scope", async () => {
|
|
575
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
576
|
+
const sessionId = "slash-on-g";
|
|
577
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
578
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -g", sessionId);
|
|
579
|
+
expect(text).toContain("Trace enabled");
|
|
580
|
+
expect(text).toContain("global");
|
|
581
|
+
const config = readConfig(testDir);
|
|
582
|
+
expect(config.global_trace_enabled).toBe(true);
|
|
583
|
+
});
|
|
584
|
+
test("/trace on --global (long form) enables global scope", async () => {
|
|
585
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
586
|
+
const sessionId = "slash-on-long-global";
|
|
587
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
588
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on --global", sessionId);
|
|
589
|
+
expect(text).toContain("Trace enabled");
|
|
590
|
+
expect(text).toContain("global");
|
|
591
|
+
const config = readConfig(testDir);
|
|
592
|
+
expect(config.global_trace_enabled).toBe(true);
|
|
593
|
+
});
|
|
594
|
+
test("/trace on -l enables local scope", async () => {
|
|
595
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
596
|
+
const sessionId = "slash-on-l";
|
|
597
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
598
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -l", sessionId);
|
|
599
|
+
expect(text).toContain("Trace enabled");
|
|
600
|
+
expect(text).toContain("local");
|
|
601
|
+
const config = readConfig(testDir);
|
|
602
|
+
expect(config.global_trace_enabled).toBe(true);
|
|
603
|
+
});
|
|
604
|
+
test("/trace on -s enables session scope", async () => {
|
|
605
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
606
|
+
const sessionId = "slash-on-s";
|
|
607
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
608
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -s", sessionId);
|
|
609
|
+
expect(text).toContain("Trace enabled");
|
|
610
|
+
expect(text).toContain("session");
|
|
611
|
+
const meta = readSessionMetadata(testDir, sessionId);
|
|
612
|
+
expect(meta).toBeTruthy();
|
|
613
|
+
expect(meta.trace_enabled).toBe(true);
|
|
614
|
+
});
|
|
615
|
+
test("/trace on -d local sets storage preference to local", async () => {
|
|
616
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
617
|
+
const sessionId = "slash-on-d-local";
|
|
618
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
619
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -d local", sessionId);
|
|
620
|
+
expect(text).toContain("Trace enabled");
|
|
621
|
+
expect(text).toContain("storage: local");
|
|
622
|
+
const config = readConfig(testDir);
|
|
623
|
+
expect(config.storage_preference).toBe("local");
|
|
624
|
+
expect(config.global_trace_enabled).toBe(true);
|
|
625
|
+
});
|
|
626
|
+
test("/trace on -d global sets storage preference to global", async () => {
|
|
627
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
628
|
+
const sessionId = "slash-on-d-global";
|
|
629
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
630
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -d global", sessionId);
|
|
631
|
+
expect(text).toContain("Trace enabled");
|
|
632
|
+
const config = readConfig(testDir);
|
|
633
|
+
expect(config.storage_preference).toBe("global");
|
|
634
|
+
});
|
|
635
|
+
test("/trace on -g -l enables both global and local scopes", async () => {
|
|
636
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
637
|
+
const sessionId = "slash-on-gl";
|
|
638
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
639
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -g -l", sessionId);
|
|
640
|
+
expect(text).toContain("Trace enabled");
|
|
641
|
+
expect(text).toContain("global");
|
|
642
|
+
expect(text).toContain("local");
|
|
643
|
+
const config = readConfig(testDir);
|
|
644
|
+
expect(config.global_trace_enabled).toBe(true);
|
|
645
|
+
});
|
|
646
|
+
test("/trace on -g -l -s enables all three scopes", async () => {
|
|
647
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
648
|
+
const sessionId = "slash-on-gls";
|
|
649
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
650
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -g -l -s", sessionId);
|
|
651
|
+
expect(text).toContain("Trace enabled");
|
|
652
|
+
expect(text).toContain("global");
|
|
653
|
+
expect(text).toContain("local");
|
|
654
|
+
expect(text).toContain("session");
|
|
655
|
+
const meta = readSessionMetadata(testDir, sessionId);
|
|
656
|
+
expect(meta.trace_enabled).toBe(true);
|
|
657
|
+
});
|
|
658
|
+
test("/trace on -s -d local enables session and sets local storage", async () => {
|
|
659
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
660
|
+
const sessionId = "slash-on-s-d-local";
|
|
661
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
662
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "on -s -d local", sessionId);
|
|
663
|
+
expect(text).toContain("Trace enabled");
|
|
664
|
+
expect(text).toContain("session");
|
|
665
|
+
expect(text).toContain("storage: local");
|
|
666
|
+
const meta = readSessionMetadata(testDir, sessionId);
|
|
667
|
+
expect(meta.trace_enabled).toBe(true);
|
|
668
|
+
expect(meta.storage_preference).toBe("local");
|
|
669
|
+
});
|
|
670
|
+
test("/trace enable (alias) works the same as /trace on", async () => {
|
|
671
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
672
|
+
const sessionId = "slash-enable-alias";
|
|
673
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
674
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "enable", sessionId);
|
|
675
|
+
expect(text).toContain("Trace enabled");
|
|
676
|
+
expect(text).toContain("global");
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
describe("Plugin - /trace off (slash command)", () => {
|
|
680
|
+
test("/trace off with no flags disables global scope by default", async () => {
|
|
681
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
682
|
+
const sessionId = "slash-off-default";
|
|
683
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
684
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "off", sessionId);
|
|
685
|
+
expect(text).toContain("Trace disabled");
|
|
686
|
+
expect(text).toContain("global");
|
|
687
|
+
const config = readConfig(testDir);
|
|
688
|
+
expect(config.global_trace_enabled).toBe(false);
|
|
689
|
+
});
|
|
690
|
+
test("/trace off -g explicitly disables global scope", async () => {
|
|
691
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
692
|
+
const sessionId = "slash-off-g";
|
|
693
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
694
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "off -g", sessionId);
|
|
695
|
+
expect(text).toContain("Trace disabled");
|
|
696
|
+
expect(text).toContain("global");
|
|
697
|
+
const config = readConfig(testDir);
|
|
698
|
+
expect(config.global_trace_enabled).toBe(false);
|
|
699
|
+
});
|
|
700
|
+
test("/trace off -l disables local scope", async () => {
|
|
701
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
702
|
+
const sessionId = "slash-off-l";
|
|
703
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
704
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "off -l", sessionId);
|
|
705
|
+
expect(text).toContain("Trace disabled");
|
|
706
|
+
expect(text).toContain("local");
|
|
707
|
+
const config = readConfig(testDir);
|
|
708
|
+
expect(config.global_trace_enabled).toBe(false);
|
|
709
|
+
});
|
|
710
|
+
test("/trace off -s disables session scope", async () => {
|
|
711
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
712
|
+
const sessionId = "slash-off-s";
|
|
713
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
714
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "off -s", sessionId);
|
|
715
|
+
expect(text).toContain("Trace disabled");
|
|
716
|
+
expect(text).toContain("session");
|
|
717
|
+
const meta = readSessionMetadata(testDir, sessionId);
|
|
718
|
+
expect(meta.trace_enabled).toBe(false);
|
|
719
|
+
});
|
|
720
|
+
test("/trace off -g -l -s disables all three scopes", async () => {
|
|
721
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
722
|
+
const sessionId = "slash-off-all";
|
|
723
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
724
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "off -g -l -s", sessionId);
|
|
725
|
+
expect(text).toContain("Trace disabled");
|
|
726
|
+
expect(text).toContain("global");
|
|
727
|
+
expect(text).toContain("local");
|
|
728
|
+
expect(text).toContain("session");
|
|
729
|
+
const config = readConfig(testDir);
|
|
730
|
+
expect(config.global_trace_enabled).toBe(false);
|
|
731
|
+
const meta = readSessionMetadata(testDir, sessionId);
|
|
732
|
+
expect(meta.trace_enabled).toBe(false);
|
|
733
|
+
});
|
|
734
|
+
test("/trace disable (alias) works the same as /trace off", async () => {
|
|
735
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
736
|
+
const sessionId = "slash-disable-alias";
|
|
737
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
738
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "disable", sessionId);
|
|
739
|
+
expect(text).toContain("Trace disabled");
|
|
740
|
+
expect(text).toContain("global");
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
describe("Plugin - /trace status (slash command)", () => {
|
|
744
|
+
test("/trace status with no flags shows full status", async () => {
|
|
745
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
746
|
+
const sessionId = "slash-status-default";
|
|
747
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
748
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "status", sessionId);
|
|
749
|
+
expect(text).toContain("Trace Status");
|
|
750
|
+
expect(text).toContain("Global");
|
|
751
|
+
expect(text).toContain("Local");
|
|
752
|
+
expect(text).toContain("Session");
|
|
753
|
+
expect(text).toContain("Storage");
|
|
754
|
+
expect(text).toContain("Effective");
|
|
755
|
+
});
|
|
756
|
+
test("/trace status -g shows status with global flag", async () => {
|
|
757
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
758
|
+
const sessionId = "slash-status-g";
|
|
759
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
760
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "status -g", sessionId);
|
|
761
|
+
expect(text).toContain("Trace Status");
|
|
762
|
+
});
|
|
763
|
+
test("/trace status -l shows status with local flag", async () => {
|
|
764
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
765
|
+
const sessionId = "slash-status-l";
|
|
766
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
767
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "status -l", sessionId);
|
|
768
|
+
expect(text).toContain("Trace Status");
|
|
769
|
+
});
|
|
770
|
+
test("/trace status -s shows status with session flag", async () => {
|
|
771
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
772
|
+
const sessionId = "slash-status-s";
|
|
773
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
774
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "status -s", sessionId);
|
|
775
|
+
expect(text).toContain("Trace Status");
|
|
776
|
+
expect(text).toContain(`Session : ON`);
|
|
777
|
+
});
|
|
778
|
+
test("/trace status reflects ON state after /trace on -g", async () => {
|
|
779
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
780
|
+
const sessionId = "slash-status-after-on";
|
|
781
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
782
|
+
await runTraceCommand(hooks, mockPrompt, "on -g", sessionId);
|
|
783
|
+
mockPrompt.mockClear();
|
|
784
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "status", sessionId);
|
|
785
|
+
expect(text).toContain("Trace Status");
|
|
786
|
+
expect(text).toContain("Global : ON");
|
|
787
|
+
expect(text).toContain("Effective: RECORDING");
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
describe("Plugin - /trace help and unknown commands", () => {
|
|
791
|
+
test("/trace help shows help text", async () => {
|
|
792
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
793
|
+
const sessionId = "slash-help";
|
|
794
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
795
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "help", sessionId);
|
|
796
|
+
expect(text).toContain("Usage: /trace");
|
|
797
|
+
expect(text).toContain("Commands:");
|
|
798
|
+
expect(text).toContain("on");
|
|
799
|
+
expect(text).toContain("off");
|
|
800
|
+
expect(text).toContain("status");
|
|
801
|
+
expect(text).toContain("-g");
|
|
802
|
+
expect(text).toContain("-l");
|
|
803
|
+
expect(text).toContain("-s");
|
|
804
|
+
expect(text).toContain("-d");
|
|
805
|
+
});
|
|
806
|
+
test("/trace with no arguments also shows help", async () => {
|
|
807
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
808
|
+
const sessionId = "slash-no-args";
|
|
809
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
810
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "", sessionId);
|
|
811
|
+
expect(text).toContain("Usage: /trace");
|
|
812
|
+
});
|
|
813
|
+
test("/trace foo (unknown subcommand) returns error message without crashing", async () => {
|
|
814
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
815
|
+
const sessionId = "slash-unknown";
|
|
816
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
817
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "foo", sessionId);
|
|
818
|
+
expect(text).toContain("Unknown command: foo");
|
|
819
|
+
expect(text).toContain("/trace on");
|
|
820
|
+
expect(text).toContain("/trace off");
|
|
821
|
+
expect(text).toContain("/trace status");
|
|
822
|
+
});
|
|
823
|
+
test("/trace with mixed-case ON is normalized", async () => {
|
|
824
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
825
|
+
const sessionId = "slash-mixed-case";
|
|
826
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
827
|
+
const { text } = await runTraceCommand(hooks, mockPrompt, "ON", sessionId);
|
|
828
|
+
expect(text).toContain("Trace enabled");
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
describe("Plugin - slash command guards", () => {
|
|
832
|
+
test("non-trace commands are ignored (no prompt sent, no throw)", async () => {
|
|
833
|
+
const { hooks, mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
834
|
+
const sessionId = "slash-other-cmd";
|
|
835
|
+
await createSessionViaEvent(hooks, sessionId, testDir);
|
|
836
|
+
const output = { parts: [{ type: "text", text: "original" }] };
|
|
837
|
+
let error = null;
|
|
838
|
+
try {
|
|
839
|
+
await hooks["command.execute.before"]({
|
|
840
|
+
command: "help",
|
|
841
|
+
sessionID: sessionId,
|
|
842
|
+
arguments: "",
|
|
843
|
+
}, output);
|
|
844
|
+
}
|
|
845
|
+
catch (err) {
|
|
846
|
+
error = err;
|
|
847
|
+
}
|
|
848
|
+
expect(error).toBeNull();
|
|
849
|
+
expect(mockPrompt).not.toHaveBeenCalled();
|
|
850
|
+
expect(output.parts.length).toBe(1);
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
describe("Plugin - tool.execute.after parentID linking", () => {
|
|
854
|
+
test("tool.execute.after with task tool records sub-session under parent's subSessions", async () => {
|
|
855
|
+
const { hooks, mockPrompt: _mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
856
|
+
void _mockPrompt;
|
|
857
|
+
const parentSessionId = "parent-link-test";
|
|
858
|
+
const subSessionId = "sub-link-test";
|
|
859
|
+
await createSessionViaEvent(hooks, parentSessionId, testDir, "Parent");
|
|
860
|
+
await hooks["tool.execute.after"]({
|
|
861
|
+
tool: "task",
|
|
862
|
+
sessionID: parentSessionId,
|
|
863
|
+
callID: "call-789",
|
|
864
|
+
args: { description: "do work", prompt: "do work" },
|
|
865
|
+
}, {
|
|
866
|
+
title: "Task done",
|
|
867
|
+
output: "completed",
|
|
868
|
+
metadata: { session_id: subSessionId },
|
|
869
|
+
});
|
|
870
|
+
const parentMeta = readSessionMetadata(testDir, parentSessionId);
|
|
871
|
+
expect(parentMeta).toBeTruthy();
|
|
872
|
+
expect(parentMeta.subSessions).toBeDefined();
|
|
873
|
+
expect(parentMeta.subSessions).toContain(subSessionId);
|
|
874
|
+
});
|
|
875
|
+
test("tool.execute.after ignores task output without session_id metadata", async () => {
|
|
876
|
+
const { hooks, mockPrompt: _mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
877
|
+
void _mockPrompt;
|
|
878
|
+
const parentSessionId = "parent-link-no-sid";
|
|
879
|
+
await createSessionViaEvent(hooks, parentSessionId, testDir, "Parent");
|
|
880
|
+
await hooks["tool.execute.after"]({
|
|
881
|
+
tool: "task",
|
|
882
|
+
sessionID: parentSessionId,
|
|
883
|
+
callID: "call-no-sid",
|
|
884
|
+
args: { description: "test" },
|
|
885
|
+
}, {
|
|
886
|
+
title: "Task done",
|
|
887
|
+
output: "completed",
|
|
888
|
+
metadata: {},
|
|
889
|
+
});
|
|
890
|
+
const parentMeta = readSessionMetadata(testDir, parentSessionId);
|
|
891
|
+
if (parentMeta.subSessions) {
|
|
892
|
+
expect(parentMeta.subSessions).not.toContain("phantom-sub");
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
test("event hook with parentID records sub-session in parent metadata", async () => {
|
|
896
|
+
const { hooks, mockPrompt: _mockPrompt } = await setupPluginWithMockClient(testDir);
|
|
897
|
+
void _mockPrompt;
|
|
898
|
+
const parentSessionId = "parent-event-link";
|
|
899
|
+
const childSessionId = "child-event-link";
|
|
900
|
+
await createSessionViaEvent(hooks, parentSessionId, testDir, "Parent");
|
|
901
|
+
await createSessionViaEvent(hooks, childSessionId, testDir, "Child", parentSessionId);
|
|
902
|
+
const parentMeta = readSessionMetadata(testDir, parentSessionId);
|
|
903
|
+
expect(parentMeta).toBeTruthy();
|
|
904
|
+
expect(parentMeta.subSessions).toBeDefined();
|
|
905
|
+
expect(parentMeta.subSessions).toContain(childSessionId);
|
|
906
|
+
const childMeta = readSessionMetadata(testDir, childSessionId);
|
|
907
|
+
expect(childMeta).toBeTruthy();
|
|
908
|
+
expect(childMeta.parentID).toBe(parentSessionId);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
describe("Plugin - new hooks wiring (chat.message, chat.params, tool.execute.before)", () => {
|
|
912
|
+
test("plugin returns chat.message, chat.params, and tool.execute.before hooks as functions", async () => {
|
|
913
|
+
const hooks = await entrypoint.server({
|
|
914
|
+
client: {},
|
|
915
|
+
project: {},
|
|
916
|
+
directory: testDir,
|
|
917
|
+
worktree: testDir,
|
|
918
|
+
experimental_workspace: { register: vi.fn() },
|
|
919
|
+
serverUrl: new URL("http://localhost"),
|
|
920
|
+
$: {},
|
|
921
|
+
});
|
|
922
|
+
expect(typeof hooks["chat.message"]).toBe("function");
|
|
923
|
+
expect(typeof hooks["chat.params"]).toBe("function");
|
|
924
|
+
expect(typeof hooks["tool.execute.before"]).toBe("function");
|
|
925
|
+
});
|
|
926
|
+
test("chat.message hook logs via logger.info and does not throw", async () => {
|
|
927
|
+
const infoSpy = vi
|
|
928
|
+
.spyOn(logger, "info")
|
|
929
|
+
.mockImplementation((() => logger));
|
|
930
|
+
try {
|
|
931
|
+
const hooks = await entrypoint.server({
|
|
932
|
+
client: {},
|
|
933
|
+
project: {},
|
|
934
|
+
directory: testDir,
|
|
935
|
+
worktree: testDir,
|
|
936
|
+
experimental_workspace: { register: vi.fn() },
|
|
937
|
+
serverUrl: new URL("http://localhost"),
|
|
938
|
+
$: {},
|
|
939
|
+
});
|
|
940
|
+
const sessionId = "chat-message-test";
|
|
941
|
+
await createSessionViaEvent(hooks, sessionId, testDir, "ChatMsg");
|
|
942
|
+
await expect(hooks["chat.message"]({
|
|
943
|
+
sessionID: sessionId,
|
|
944
|
+
messageID: "msg-1",
|
|
945
|
+
agent: "build",
|
|
946
|
+
}, { message: {}, parts: [] })).resolves.toBeUndefined();
|
|
947
|
+
const chatMessageCalls = infoSpy.mock.calls.filter((c) => c[0] === "chat.message");
|
|
948
|
+
expect(chatMessageCalls.length).toBeGreaterThan(0);
|
|
949
|
+
const payload = chatMessageCalls[0][1];
|
|
950
|
+
expect(payload.sessionID).toBe(sessionId);
|
|
951
|
+
expect(payload.messageID).toBe("msg-1");
|
|
952
|
+
expect(payload.agent).toBe("build");
|
|
953
|
+
}
|
|
954
|
+
finally {
|
|
955
|
+
infoSpy.mockRestore();
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
test("chat.params hook logs via logger.info and does not throw", async () => {
|
|
959
|
+
const infoSpy = vi
|
|
960
|
+
.spyOn(logger, "info")
|
|
961
|
+
.mockImplementation((() => logger));
|
|
962
|
+
try {
|
|
963
|
+
const hooks = await entrypoint.server({
|
|
964
|
+
client: {},
|
|
965
|
+
project: {},
|
|
966
|
+
directory: testDir,
|
|
967
|
+
worktree: testDir,
|
|
968
|
+
experimental_workspace: { register: vi.fn() },
|
|
969
|
+
serverUrl: new URL("http://localhost"),
|
|
970
|
+
$: {},
|
|
971
|
+
});
|
|
972
|
+
const sessionId = "chat-params-test";
|
|
973
|
+
await createSessionViaEvent(hooks, sessionId, testDir, "ChatParams");
|
|
974
|
+
await expect(hooks["chat.params"]({
|
|
975
|
+
sessionID: sessionId,
|
|
976
|
+
agent: "build",
|
|
977
|
+
model: { providerID: "anthropic", modelID: "claude-3-5-sonnet" },
|
|
978
|
+
provider: { source: "config", info: {}, options: {} },
|
|
979
|
+
message: {},
|
|
980
|
+
}, {
|
|
981
|
+
temperature: 0.7,
|
|
982
|
+
topP: 0.9,
|
|
983
|
+
topK: 40,
|
|
984
|
+
maxOutputTokens: 100,
|
|
985
|
+
options: {},
|
|
986
|
+
})).resolves.toBeUndefined();
|
|
987
|
+
const chatParamsCalls = infoSpy.mock.calls.filter((c) => c[0] === "chat.params");
|
|
988
|
+
expect(chatParamsCalls.length).toBeGreaterThan(0);
|
|
989
|
+
const payload = chatParamsCalls[0][1];
|
|
990
|
+
expect(payload.sessionID).toBe(sessionId);
|
|
991
|
+
expect(payload.agent).toBe("build");
|
|
992
|
+
}
|
|
993
|
+
finally {
|
|
994
|
+
infoSpy.mockRestore();
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
test("tool.execute.before hook logs via logger.info and does not throw", async () => {
|
|
998
|
+
const infoSpy = vi
|
|
999
|
+
.spyOn(logger, "info")
|
|
1000
|
+
.mockImplementation((() => logger));
|
|
1001
|
+
try {
|
|
1002
|
+
const hooks = await entrypoint.server({
|
|
1003
|
+
client: {},
|
|
1004
|
+
project: {},
|
|
1005
|
+
directory: testDir,
|
|
1006
|
+
worktree: testDir,
|
|
1007
|
+
experimental_workspace: { register: vi.fn() },
|
|
1008
|
+
serverUrl: new URL("http://localhost"),
|
|
1009
|
+
$: {},
|
|
1010
|
+
});
|
|
1011
|
+
const sessionId = "tool-before-test";
|
|
1012
|
+
await createSessionViaEvent(hooks, sessionId, testDir, "ToolBefore");
|
|
1013
|
+
await expect(hooks["tool.execute.before"]({ tool: "bash", sessionID: sessionId, callID: "call-1" }, { args: { command: "ls" } })).resolves.toBeUndefined();
|
|
1014
|
+
const toolBeforeCalls = infoSpy.mock.calls.filter((c) => c[0] === "tool.execute.before");
|
|
1015
|
+
expect(toolBeforeCalls.length).toBeGreaterThan(0);
|
|
1016
|
+
const payload = toolBeforeCalls[0][1];
|
|
1017
|
+
expect(payload.sessionID).toBe(sessionId);
|
|
1018
|
+
expect(payload.callID).toBe("call-1");
|
|
1019
|
+
expect(payload.tool).toBe("bash");
|
|
1020
|
+
}
|
|
1021
|
+
finally {
|
|
1022
|
+
infoSpy.mockRestore();
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
describe("Plugin - tool.execute.after parentID eager propagation", () => {
|
|
1027
|
+
test("eagerly writes child session's parentID via updateSessionMetadata when task tool completes", async () => {
|
|
1028
|
+
const { hooks } = await setupPluginWithMockClient(testDir);
|
|
1029
|
+
const parentSessionId = "parent-eager-pid";
|
|
1030
|
+
const childSessionId = "child-eager-pid";
|
|
1031
|
+
await createSessionViaEvent(hooks, parentSessionId, testDir, "Parent");
|
|
1032
|
+
await hooks["tool.execute.after"]({
|
|
1033
|
+
tool: "task",
|
|
1034
|
+
sessionID: parentSessionId,
|
|
1035
|
+
callID: "call-eager",
|
|
1036
|
+
args: { description: "sub-work", prompt: "do it" },
|
|
1037
|
+
}, {
|
|
1038
|
+
title: "Task done",
|
|
1039
|
+
output: "completed",
|
|
1040
|
+
metadata: { session_id: childSessionId },
|
|
1041
|
+
});
|
|
1042
|
+
const childMeta = readSessionMetadata(testDir, childSessionId);
|
|
1043
|
+
expect(childMeta).toBeTruthy();
|
|
1044
|
+
expect(childMeta.parentID).toBe(parentSessionId);
|
|
1045
|
+
const parentMeta = readSessionMetadata(testDir, parentSessionId);
|
|
1046
|
+
expect(parentMeta.subSessions).toContain(childSessionId);
|
|
1047
|
+
});
|
|
1048
|
+
test("does not eagerly write parentID for non-task tools", async () => {
|
|
1049
|
+
const { hooks } = await setupPluginWithMockClient(testDir);
|
|
1050
|
+
const sessionId = "non-task-eager";
|
|
1051
|
+
await createSessionViaEvent(hooks, sessionId, testDir, "Parent");
|
|
1052
|
+
const ghostChild = "ghost-child-eager";
|
|
1053
|
+
await hooks["tool.execute.after"]({
|
|
1054
|
+
tool: "read",
|
|
1055
|
+
sessionID: sessionId,
|
|
1056
|
+
callID: "call-read",
|
|
1057
|
+
args: { filePath: "/foo" },
|
|
1058
|
+
}, {
|
|
1059
|
+
title: "Read done",
|
|
1060
|
+
output: "contents",
|
|
1061
|
+
metadata: { session_id: ghostChild },
|
|
1062
|
+
});
|
|
1063
|
+
const ghostMeta = readSessionMetadata(testDir, ghostChild);
|
|
1064
|
+
if (ghostMeta !== null) {
|
|
1065
|
+
expect(ghostMeta.parentID).toBeUndefined();
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
test("does not throw when task metadata has no session_id", async () => {
|
|
1069
|
+
const { hooks } = await setupPluginWithMockClient(testDir);
|
|
1070
|
+
const sessionId = "task-no-sid-eager";
|
|
1071
|
+
await createSessionViaEvent(hooks, sessionId, testDir, "Parent");
|
|
1072
|
+
await expect(hooks["tool.execute.after"]({
|
|
1073
|
+
tool: "task",
|
|
1074
|
+
sessionID: sessionId,
|
|
1075
|
+
callID: "call-no-sid-eager",
|
|
1076
|
+
args: {},
|
|
1077
|
+
}, {
|
|
1078
|
+
title: "Task done",
|
|
1079
|
+
output: "completed",
|
|
1080
|
+
metadata: {},
|
|
1081
|
+
})).resolves.toBeUndefined();
|
|
499
1082
|
});
|
|
500
1083
|
});
|
|
501
1084
|
//# sourceMappingURL=trace.test.js.map
|