devez-vibe 1.2.6 β 1.2.7
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/bin/dvz.exe +0 -0
- package/bridge/claude-agent-sdk-bridge.mjs +642 -586
- package/package.json +41 -41
|
@@ -435,25 +435,25 @@ async function createSession(params, resumeId) {
|
|
|
435
435
|
turnSequence: 1,
|
|
436
436
|
itemSequence: 1,
|
|
437
437
|
streamBlocks: new Map(),
|
|
438
|
-
tools: new Map(),
|
|
439
|
-
tasks: new Map(),
|
|
440
|
-
subagents: new Map(),
|
|
441
|
-
knownSubagents: new Map(),
|
|
442
|
-
lastContextUsage: null,
|
|
438
|
+
tools: new Map(),
|
|
439
|
+
tasks: new Map(),
|
|
440
|
+
subagents: new Map(),
|
|
441
|
+
knownSubagents: new Map(),
|
|
442
|
+
lastContextUsage: null,
|
|
443
443
|
lastContextWindow: 0,
|
|
444
444
|
};
|
|
445
445
|
const agentQuery = await startAgentQuery(queue, makeOptions(params, id, resumeId));
|
|
446
446
|
session.query = agentQuery;
|
|
447
447
|
sessions.set(id, session);
|
|
448
|
-
const consumer = consume(session).catch((error) => {
|
|
448
|
+
const consumer = consume(session).catch((error) => {
|
|
449
449
|
notify("error", {
|
|
450
450
|
threadId: id,
|
|
451
451
|
provider: "Claude",
|
|
452
452
|
error: { message: error instanceof Error ? error.message : String(error) },
|
|
453
|
-
willRetry: false,
|
|
454
|
-
});
|
|
455
|
-
clearSubagents(session);
|
|
456
|
-
if (session.turn) finishTurn(session, error);
|
|
453
|
+
willRetry: false,
|
|
454
|
+
});
|
|
455
|
+
clearSubagents(session);
|
|
456
|
+
if (session.turn) finishTurn(session, error);
|
|
457
457
|
});
|
|
458
458
|
session.consumer = consumer;
|
|
459
459
|
let initialization;
|
|
@@ -497,87 +497,87 @@ function emitItem(session, phase, item) {
|
|
|
497
497
|
notify(`item/${phase}`, { threadId: session.id, turnId: session.turn?.id, item });
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
function emitDelta(session, method, itemId, delta) {
|
|
501
|
-
notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Windows rounds larger timer delays up to the next scheduler slice. Ten
|
|
505
|
-
// milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
|
|
506
|
-
const SMOOTH_TEXT_INTERVAL_MS = 10;
|
|
507
|
-
const SMOOTH_TEXT_TARGET_FRAMES = 8;
|
|
508
|
-
const SMOOTH_TEXT_MAX_GRAPHEMES = 24;
|
|
509
|
-
const graphemeSegmenter = typeof Intl.Segmenter === "function"
|
|
510
|
-
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
511
|
-
: null;
|
|
512
|
-
|
|
513
|
-
function splitGraphemes(text) {
|
|
514
|
-
return graphemeSegmenter
|
|
515
|
-
? Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment)
|
|
516
|
-
: Array.from(text);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Claude can deliver a whole phrase in one SDK event. Drain roughly one visual
|
|
520
|
-
// frame's share at a time, catching up quickly when a large backlog arrives.
|
|
521
|
-
function takeSmoothTextChunk(text) {
|
|
522
|
-
const graphemes = splitGraphemes(text);
|
|
523
|
-
const size = Math.min(
|
|
524
|
-
SMOOTH_TEXT_MAX_GRAPHEMES,
|
|
525
|
-
Math.max(1, Math.ceil(graphemes.length / SMOOTH_TEXT_TARGET_FRAMES)),
|
|
526
|
-
);
|
|
527
|
-
return {
|
|
528
|
-
chunk: graphemes.slice(0, size).join(""),
|
|
529
|
-
rest: graphemes.slice(size).join(""),
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
class SmoothTextStream {
|
|
534
|
-
constructor(emit, intervalMs = SMOOTH_TEXT_INTERVAL_MS) {
|
|
535
|
-
this.emit = emit;
|
|
536
|
-
this.intervalMs = intervalMs;
|
|
537
|
-
this.pending = "";
|
|
538
|
-
this.timer = null;
|
|
539
|
-
this.waiters = [];
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
push(text) {
|
|
543
|
-
this.pending += text;
|
|
544
|
-
if (this.timer == null) this.drain();
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
drain() {
|
|
548
|
-
if (!this.pending) {
|
|
549
|
-
this.timer = null;
|
|
550
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
const { chunk, rest } = takeSmoothTextChunk(this.pending);
|
|
554
|
-
this.pending = rest;
|
|
555
|
-
this.emit(chunk);
|
|
556
|
-
if (this.pending) {
|
|
557
|
-
this.timer = setTimeout(() => this.drain(), this.intervalMs);
|
|
558
|
-
} else {
|
|
559
|
-
this.timer = null;
|
|
560
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
finish() {
|
|
565
|
-
if (!this.pending && this.timer == null) return Promise.resolve();
|
|
566
|
-
return new Promise((resolve) => this.waiters.push(resolve));
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
flush() {
|
|
570
|
-
if (this.timer != null) clearTimeout(this.timer);
|
|
571
|
-
this.timer = null;
|
|
572
|
-
if (this.pending) this.emit(this.pending);
|
|
573
|
-
this.pending = "";
|
|
574
|
-
for (const resolve of this.waiters.splice(0)) resolve();
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function flushSmoothStreams(session) {
|
|
579
|
-
for (const block of session.streamBlocks.values()) block.smooth?.flush();
|
|
580
|
-
}
|
|
500
|
+
function emitDelta(session, method, itemId, delta) {
|
|
501
|
+
notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Windows rounds larger timer delays up to the next scheduler slice. Ten
|
|
505
|
+
// milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
|
|
506
|
+
const SMOOTH_TEXT_INTERVAL_MS = 10;
|
|
507
|
+
const SMOOTH_TEXT_TARGET_FRAMES = 8;
|
|
508
|
+
const SMOOTH_TEXT_MAX_GRAPHEMES = 24;
|
|
509
|
+
const graphemeSegmenter = typeof Intl.Segmenter === "function"
|
|
510
|
+
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
511
|
+
: null;
|
|
512
|
+
|
|
513
|
+
function splitGraphemes(text) {
|
|
514
|
+
return graphemeSegmenter
|
|
515
|
+
? Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment)
|
|
516
|
+
: Array.from(text);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Claude can deliver a whole phrase in one SDK event. Drain roughly one visual
|
|
520
|
+
// frame's share at a time, catching up quickly when a large backlog arrives.
|
|
521
|
+
function takeSmoothTextChunk(text) {
|
|
522
|
+
const graphemes = splitGraphemes(text);
|
|
523
|
+
const size = Math.min(
|
|
524
|
+
SMOOTH_TEXT_MAX_GRAPHEMES,
|
|
525
|
+
Math.max(1, Math.ceil(graphemes.length / SMOOTH_TEXT_TARGET_FRAMES)),
|
|
526
|
+
);
|
|
527
|
+
return {
|
|
528
|
+
chunk: graphemes.slice(0, size).join(""),
|
|
529
|
+
rest: graphemes.slice(size).join(""),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
class SmoothTextStream {
|
|
534
|
+
constructor(emit, intervalMs = SMOOTH_TEXT_INTERVAL_MS) {
|
|
535
|
+
this.emit = emit;
|
|
536
|
+
this.intervalMs = intervalMs;
|
|
537
|
+
this.pending = "";
|
|
538
|
+
this.timer = null;
|
|
539
|
+
this.waiters = [];
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
push(text) {
|
|
543
|
+
this.pending += text;
|
|
544
|
+
if (this.timer == null) this.drain();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
drain() {
|
|
548
|
+
if (!this.pending) {
|
|
549
|
+
this.timer = null;
|
|
550
|
+
for (const resolve of this.waiters.splice(0)) resolve();
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const { chunk, rest } = takeSmoothTextChunk(this.pending);
|
|
554
|
+
this.pending = rest;
|
|
555
|
+
this.emit(chunk);
|
|
556
|
+
if (this.pending) {
|
|
557
|
+
this.timer = setTimeout(() => this.drain(), this.intervalMs);
|
|
558
|
+
} else {
|
|
559
|
+
this.timer = null;
|
|
560
|
+
for (const resolve of this.waiters.splice(0)) resolve();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
finish() {
|
|
565
|
+
if (!this.pending && this.timer == null) return Promise.resolve();
|
|
566
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
flush() {
|
|
570
|
+
if (this.timer != null) clearTimeout(this.timer);
|
|
571
|
+
this.timer = null;
|
|
572
|
+
if (this.pending) this.emit(this.pending);
|
|
573
|
+
this.pending = "";
|
|
574
|
+
for (const resolve of this.waiters.splice(0)) resolve();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function flushSmoothStreams(session) {
|
|
579
|
+
for (const block of session.streamBlocks.values()) block.smooth?.flush();
|
|
580
|
+
}
|
|
581
581
|
|
|
582
582
|
function tokenBreakdown(usage) {
|
|
583
583
|
if (!usage) return null;
|
|
@@ -625,44 +625,44 @@ function historyTokenUsage(messages, models, model) {
|
|
|
625
625
|
};
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
async function processStreamEvent(session, message) {
|
|
629
|
-
if (!session.turn || message.parent_tool_use_id) return;
|
|
630
|
-
const event = message.event || {};
|
|
631
|
-
if (event.type === "message_start") {
|
|
632
|
-
flushSmoothStreams(session);
|
|
633
|
-
session.streamBlocks.clear();
|
|
634
|
-
}
|
|
635
|
-
if (event.type === "content_block_start") {
|
|
636
|
-
const block = event.content_block || {};
|
|
637
|
-
if (block.type !== "text" && block.type !== "thinking") return;
|
|
638
|
-
const id = nextItemId(session, block.type);
|
|
628
|
+
async function processStreamEvent(session, message) {
|
|
629
|
+
if (!session.turn || message.parent_tool_use_id) return;
|
|
630
|
+
const event = message.event || {};
|
|
631
|
+
if (event.type === "message_start") {
|
|
632
|
+
flushSmoothStreams(session);
|
|
633
|
+
session.streamBlocks.clear();
|
|
634
|
+
}
|
|
635
|
+
if (event.type === "content_block_start") {
|
|
636
|
+
const block = event.content_block || {};
|
|
637
|
+
if (block.type !== "text" && block.type !== "thinking") return;
|
|
638
|
+
const id = nextItemId(session, block.type);
|
|
639
639
|
const item = block.type === "text"
|
|
640
640
|
? { id, type: "agentMessage", text: "", provider: "Claude" }
|
|
641
641
|
: { id, type: "reasoning", summary: [] };
|
|
642
|
-
const smooth = block.type === "text"
|
|
643
|
-
? new SmoothTextStream((delta) => emitDelta(session, "item/agentMessage/delta", id, delta))
|
|
644
|
-
: null;
|
|
645
|
-
session.streamBlocks.set(event.index, { id, type: block.type, text: "", smooth });
|
|
646
|
-
emitItem(session, "started", item);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
642
|
+
const smooth = block.type === "text"
|
|
643
|
+
? new SmoothTextStream((delta) => emitDelta(session, "item/agentMessage/delta", id, delta))
|
|
644
|
+
: null;
|
|
645
|
+
session.streamBlocks.set(event.index, { id, type: block.type, text: "", smooth });
|
|
646
|
+
emitItem(session, "started", item);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
649
|
if (event.type === "content_block_delta") {
|
|
650
650
|
const current = session.streamBlocks.get(event.index);
|
|
651
651
|
if (!current) return;
|
|
652
|
-
const delta = event.delta?.text || event.delta?.thinking || "";
|
|
653
|
-
if (!delta) return;
|
|
654
|
-
current.text += delta;
|
|
655
|
-
if (current.smooth) current.smooth.push(delta);
|
|
656
|
-
else emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
if (event.type === "content_block_stop") {
|
|
660
|
-
const current = session.streamBlocks.get(event.index);
|
|
661
|
-
if (!current) return;
|
|
662
|
-
await current.smooth?.finish();
|
|
663
|
-
const item = current.type === "text"
|
|
664
|
-
? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
|
|
665
|
-
: { id: current.id, type: "reasoning", summary: [current.text] };
|
|
652
|
+
const delta = event.delta?.text || event.delta?.thinking || "";
|
|
653
|
+
if (!delta) return;
|
|
654
|
+
current.text += delta;
|
|
655
|
+
if (current.smooth) current.smooth.push(delta);
|
|
656
|
+
else emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (event.type === "content_block_stop") {
|
|
660
|
+
const current = session.streamBlocks.get(event.index);
|
|
661
|
+
if (!current) return;
|
|
662
|
+
await current.smooth?.finish();
|
|
663
|
+
const item = current.type === "text"
|
|
664
|
+
? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
|
|
665
|
+
: { id: current.id, type: "reasoning", summary: [current.text] };
|
|
666
666
|
emitItem(session, "completed", item);
|
|
667
667
|
session.streamBlocks.delete(event.index);
|
|
668
668
|
}
|
|
@@ -751,52 +751,89 @@ function fileChanges(name, input) {
|
|
|
751
751
|
return [{ path, kind: { type: name === "Write" ? "add" : "update" }, diff: `@@ -0,0 +1 @@\n${additions}` }];
|
|
752
752
|
}
|
|
753
753
|
|
|
754
|
-
function numberedTaskIndex(subject) {
|
|
755
|
-
const match = String(subject || "").trim().match(/^(\d+)[.)]\s+/);
|
|
756
|
-
return match ? Number(match[1]) : null;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// Claudeμ Task idλ μΈμ
μ 체μμ λμ λλ€. μ λͺ© λ²νΈκ° λ€μ 1λΆν°
|
|
760
|
-
// μμν λλ§ μ κ³νμ΄λ©°, 3Β·4λ²λ§ κ°±μ λλ ν΄μ κΈ°μ‘΄ 1~6λ²μ μ§ν¨λ€.
|
|
761
|
-
function prepareTaskPlanForCreate(tasks, subject) {
|
|
762
|
-
if (numberedTaskIndex(subject) === 1) tasks.clear();
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function applyTaskUpdate(tasks, input, turnId) {
|
|
754
|
+
function numberedTaskIndex(subject) {
|
|
755
|
+
const match = String(subject || "").trim().match(/^(\d+)[.)]\s+/);
|
|
756
|
+
return match ? Number(match[1]) : null;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Claudeμ Task idλ μΈμ
μ 체μμ λμ λλ€. μ λͺ© λ²νΈκ° λ€μ 1λΆν°
|
|
760
|
+
// μμν λλ§ μ κ³νμ΄λ©°, 3Β·4λ²λ§ κ°±μ λλ ν΄μ κΈ°μ‘΄ 1~6λ²μ μ§ν¨λ€.
|
|
761
|
+
function prepareTaskPlanForCreate(tasks, subject) {
|
|
762
|
+
if (numberedTaskIndex(subject) === 1) tasks.clear();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
|
|
766
766
|
const task = tasks.get(String(input.taskId));
|
|
767
767
|
if (!task) return false;
|
|
768
768
|
if (input.subject) task.subject = input.subject;
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
769
|
+
const status = input.status;
|
|
770
|
+
if (status === "in_progress" || status === "completed") {
|
|
771
|
+
const entries = [...tasks.values()];
|
|
772
|
+
const targetIndex = entries.indexOf(task);
|
|
773
|
+
|
|
774
|
+
// Claude occasionally closes a later pending task at the end of a turn
|
|
775
|
+
// without ever starting it. Keep the visible plan truthful and sequential:
|
|
776
|
+
// every skipped predecessor and the target itself pass through in_progress.
|
|
777
|
+
for (let index = 0; index < targetIndex; index++) {
|
|
778
|
+
const previous = entries[index];
|
|
779
|
+
if (previous.status === "completed") continue;
|
|
780
|
+
if (previous.status !== "in_progress") {
|
|
781
|
+
previous.status = "in_progress";
|
|
782
|
+
previous.turnId = turnId;
|
|
783
|
+
onIntermediate?.();
|
|
784
|
+
}
|
|
785
|
+
previous.status = "completed";
|
|
786
|
+
previous.turnId = turnId;
|
|
787
|
+
onIntermediate?.();
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
for (let index = 0; index < entries.length; index++) {
|
|
791
|
+
const other = entries[index];
|
|
792
|
+
if (other === task || other.status !== "in_progress") continue;
|
|
793
|
+
other.status = index < targetIndex ? "completed" : "pending";
|
|
794
|
+
other.turnId = turnId;
|
|
795
|
+
onIntermediate?.();
|
|
796
|
+
}
|
|
773
797
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
798
|
+
if (status === "completed" && task.status !== "in_progress" && task.status !== "completed") {
|
|
799
|
+
task.status = "in_progress";
|
|
800
|
+
task.turnId = turnId;
|
|
801
|
+
onIntermediate?.();
|
|
802
|
+
}
|
|
803
|
+
task.status = status;
|
|
804
|
+
} else if (status) {
|
|
805
|
+
task.status = status;
|
|
781
806
|
}
|
|
782
|
-
|
|
807
|
+
task.turnId = turnId;
|
|
808
|
+
return true;
|
|
783
809
|
}
|
|
784
810
|
|
|
811
|
+
// TaskListμλ μμ κ³νκΉμ§ ν¨κ» λ€μ΄μ¬ μ μλ€. λ§μ§λ§μΌλ‘ λ²νΈκ° 1λΆν°
|
|
812
|
+
// μμν λ¬Άμλ§ νμ¬ κ³νμΌλ‘ μΌλ, λ²νΈκ° μλ λͺ©λ‘μ μμ€ μμ΄ κ·Έλλ‘ λλ€.
|
|
813
|
+
function latestTaskPlan(tasks) {
|
|
814
|
+
const entries = [...tasks.entries()];
|
|
815
|
+
let start = 0;
|
|
816
|
+
for (let index = 0; index < entries.length; index++) {
|
|
817
|
+
if (numberedTaskIndex(entries[index][1].subject) === 1) start = index;
|
|
818
|
+
}
|
|
819
|
+
return new Map(entries.slice(start));
|
|
820
|
+
}
|
|
821
|
+
|
|
785
822
|
function updatePlanFromToolUse(session, name, toolUseId, input) {
|
|
786
|
-
const turnId = session.turn?.id;
|
|
787
|
-
if (name === "TaskCreate") {
|
|
788
|
-
prepareTaskPlanForCreate(session.tasks, input.subject);
|
|
789
|
-
session.tasks.set(`pending:${toolUseId}`, {
|
|
790
|
-
id: `pending:${toolUseId}`,
|
|
791
|
-
subject: input.subject || input.description || "μμ
",
|
|
823
|
+
const turnId = session.turn?.id;
|
|
824
|
+
if (name === "TaskCreate") {
|
|
825
|
+
prepareTaskPlanForCreate(session.tasks, input.subject);
|
|
826
|
+
session.tasks.set(`pending:${toolUseId}`, {
|
|
827
|
+
id: `pending:${toolUseId}`,
|
|
828
|
+
subject: input.subject || input.description || "μμ
",
|
|
792
829
|
status: "pending",
|
|
793
|
-
turnId,
|
|
794
|
-
});
|
|
830
|
+
turnId,
|
|
831
|
+
});
|
|
795
832
|
} else if (name === "TaskUpdate") {
|
|
796
|
-
applyTaskUpdate(session.tasks, input, turnId);
|
|
833
|
+
applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
|
|
797
834
|
}
|
|
798
|
-
emitPlan(session);
|
|
799
|
-
}
|
|
835
|
+
emitPlan(session);
|
|
836
|
+
}
|
|
800
837
|
|
|
801
838
|
function updatePlanFromToolResult(session, pending, message) {
|
|
802
839
|
const value = message.tool_use_result;
|
|
@@ -824,10 +861,10 @@ function updatePlanFromToolResult(session, pending, message) {
|
|
|
824
861
|
turnId: previous.get(id)?.turnId ?? turnId,
|
|
825
862
|
});
|
|
826
863
|
}
|
|
827
|
-
session.tasks = latestTaskPlan(session.tasks);
|
|
828
|
-
emitPlan(session);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
864
|
+
session.tasks = latestTaskPlan(session.tasks);
|
|
865
|
+
emitPlan(session);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
831
868
|
|
|
832
869
|
function taskCreatedResult(structured, content) {
|
|
833
870
|
const created = structured?.task || structured;
|
|
@@ -868,78 +905,78 @@ function numberedTaskSubject(subject, index) {
|
|
|
868
905
|
// κ·Έ IDλ‘ λ¬Άμ΄ λλ©΄ μ§κΈ μ΄λ€ μμ΄μ νΈκ° λ¬΄μ¨ λꡬλ₯Ό λ리λμ§ κ·Έλλ‘ λ³΅μν μ μλ€.
|
|
869
906
|
const SUBAGENT_TOOLS = ["Agent", "Task"];
|
|
870
907
|
|
|
871
|
-
function startSubagent(session, block) {
|
|
872
|
-
const input = block.input || {};
|
|
873
|
-
session.subagents.set(block.id, {
|
|
874
|
-
id: block.id,
|
|
875
|
-
taskId: "",
|
|
876
|
-
background: false,
|
|
877
|
-
name: firstLine(input.subagent_type || input.agentType || "agent", 40),
|
|
908
|
+
function startSubagent(session, block) {
|
|
909
|
+
const input = block.input || {};
|
|
910
|
+
session.subagents.set(block.id, {
|
|
911
|
+
id: block.id,
|
|
912
|
+
taskId: "",
|
|
913
|
+
background: false,
|
|
914
|
+
name: firstLine(input.subagent_type || input.agentType || "agent", 40),
|
|
878
915
|
description: firstLine(input.description || input.prompt || "", 120),
|
|
879
916
|
tool: "",
|
|
880
917
|
startedAt: Date.now(),
|
|
881
918
|
});
|
|
882
|
-
emitSubagents(session);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
function isBackgroundSubagentResult(result) {
|
|
886
|
-
return result?.isAsync === true || result?.status === "async_launched";
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Claude Code treats an async Agent result as a launch receipt. The agent remains
|
|
890
|
-
// live until its later task-notification names the same task or tool-use id.
|
|
891
|
-
function keepBackgroundSubagent(session, toolUseId, result) {
|
|
892
|
-
if (!isBackgroundSubagentResult(result)) return false;
|
|
893
|
-
const running = session.subagents.get(toolUseId);
|
|
894
|
-
if (!running) return false;
|
|
895
|
-
running.background = true;
|
|
896
|
-
running.taskId = firstLine(result?.agentId || result?.taskId || "", 80);
|
|
897
|
-
if (running.taskId) {
|
|
898
|
-
session.knownSubagents.set(running.taskId, {
|
|
899
|
-
name: running.name,
|
|
900
|
-
description: running.description,
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
return true;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
// SendMessage can wake a completed agent from its transcript. Its new parent
|
|
907
|
-
// tool-use id owns this run, while the stable agent id lets later notifications
|
|
908
|
-
// and further resumes recover the original label.
|
|
909
|
-
function resumeBackgroundSubagent(session, toolUseId, pending, result) {
|
|
910
|
-
const taskId = firstLine(result?.resumedAgentId || "", 80);
|
|
911
|
-
if (!taskId) return false;
|
|
912
|
-
const existing = [...session.subagents.values()].find((agent) => agent.taskId === taskId);
|
|
913
|
-
if (existing) return true;
|
|
914
|
-
const known = session.knownSubagents.get(taskId);
|
|
915
|
-
const input = pending?.input || {};
|
|
916
|
-
const running = {
|
|
917
|
-
id: toolUseId,
|
|
918
|
-
taskId,
|
|
919
|
-
background: true,
|
|
920
|
-
name: known?.name || "agent",
|
|
921
|
-
description: known?.description || firstLine(input.summary || input.message || "", 120),
|
|
922
|
-
tool: "",
|
|
923
|
-
startedAt: Date.now(),
|
|
924
|
-
};
|
|
925
|
-
session.subagents.set(toolUseId, running);
|
|
926
|
-
session.knownSubagents.set(taskId, {
|
|
927
|
-
name: running.name,
|
|
928
|
-
description: running.description,
|
|
929
|
-
});
|
|
930
|
-
emitSubagents(session);
|
|
931
|
-
return true;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
function findSubagent(session, id) {
|
|
935
|
-
return session.subagents.get(id)
|
|
936
|
-
|| [...session.subagents.values()].find((agent) => agent.taskId === id);
|
|
937
|
-
}
|
|
919
|
+
emitSubagents(session);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function isBackgroundSubagentResult(result) {
|
|
923
|
+
return result?.isAsync === true || result?.status === "async_launched";
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Claude Code treats an async Agent result as a launch receipt. The agent remains
|
|
927
|
+
// live until its later task-notification names the same task or tool-use id.
|
|
928
|
+
function keepBackgroundSubagent(session, toolUseId, result) {
|
|
929
|
+
if (!isBackgroundSubagentResult(result)) return false;
|
|
930
|
+
const running = session.subagents.get(toolUseId);
|
|
931
|
+
if (!running) return false;
|
|
932
|
+
running.background = true;
|
|
933
|
+
running.taskId = firstLine(result?.agentId || result?.taskId || "", 80);
|
|
934
|
+
if (running.taskId) {
|
|
935
|
+
session.knownSubagents.set(running.taskId, {
|
|
936
|
+
name: running.name,
|
|
937
|
+
description: running.description,
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// SendMessage can wake a completed agent from its transcript. Its new parent
|
|
944
|
+
// tool-use id owns this run, while the stable agent id lets later notifications
|
|
945
|
+
// and further resumes recover the original label.
|
|
946
|
+
function resumeBackgroundSubagent(session, toolUseId, pending, result) {
|
|
947
|
+
const taskId = firstLine(result?.resumedAgentId || "", 80);
|
|
948
|
+
if (!taskId) return false;
|
|
949
|
+
const existing = [...session.subagents.values()].find((agent) => agent.taskId === taskId);
|
|
950
|
+
if (existing) return true;
|
|
951
|
+
const known = session.knownSubagents.get(taskId);
|
|
952
|
+
const input = pending?.input || {};
|
|
953
|
+
const running = {
|
|
954
|
+
id: toolUseId,
|
|
955
|
+
taskId,
|
|
956
|
+
background: true,
|
|
957
|
+
name: known?.name || "agent",
|
|
958
|
+
description: known?.description || firstLine(input.summary || input.message || "", 120),
|
|
959
|
+
tool: "",
|
|
960
|
+
startedAt: Date.now(),
|
|
961
|
+
};
|
|
962
|
+
session.subagents.set(toolUseId, running);
|
|
963
|
+
session.knownSubagents.set(taskId, {
|
|
964
|
+
name: running.name,
|
|
965
|
+
description: running.description,
|
|
966
|
+
});
|
|
967
|
+
emitSubagents(session);
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function findSubagent(session, id) {
|
|
972
|
+
return session.subagents.get(id)
|
|
973
|
+
|| [...session.subagents.values()].find((agent) => agent.taskId === id);
|
|
974
|
+
}
|
|
938
975
|
|
|
939
976
|
// μλΈμμ΄μ νΈκ° μ€μ λ‘ λ¬΄μμ νλμ§λ μμ λ©μμ§μλ§ λ¨λλ€. μ΄λμ© κΈ°λ‘μ μ¬κΈ°μ
|
|
940
977
|
// ν μ€μ© νλ €λ³΄λ΄κ³ , λͺ©λ‘ νμ μΈ νμ¬ λκ΅¬λ§ λ°λ‘ κ°±μ νλ€.
|
|
941
|
-
function recordSubagentMessage(session, message) {
|
|
942
|
-
const running = findSubagent(session, message.parent_tool_use_id);
|
|
978
|
+
function recordSubagentMessage(session, message) {
|
|
979
|
+
const running = findSubagent(session, message.parent_tool_use_id);
|
|
943
980
|
if (!running) return;
|
|
944
981
|
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
945
982
|
let toolChanged = false;
|
|
@@ -960,8 +997,8 @@ function recordSubagentMessage(session, message) {
|
|
|
960
997
|
if (toolChanged) emitSubagents(session);
|
|
961
998
|
}
|
|
962
999
|
|
|
963
|
-
function recordSubagentResult(session, message) {
|
|
964
|
-
const running = findSubagent(session, message.parent_tool_use_id);
|
|
1000
|
+
function recordSubagentResult(session, message) {
|
|
1001
|
+
const running = findSubagent(session, message.parent_tool_use_id);
|
|
965
1002
|
if (!running) return;
|
|
966
1003
|
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
967
1004
|
for (const block of content) {
|
|
@@ -997,32 +1034,32 @@ function subagentToolLabel(block) {
|
|
|
997
1034
|
return text ? `${name}(${text})` : name;
|
|
998
1035
|
}
|
|
999
1036
|
|
|
1000
|
-
function finishSubagent(session, toolUseId) {
|
|
1001
|
-
if (!session.subagents.delete(toolUseId)) return;
|
|
1002
|
-
emitSubagents(session);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
function finishSubagentTask(session, taskId) {
|
|
1006
|
-
if (!taskId) return false;
|
|
1007
|
-
const entry = [...session.subagents.entries()].find(([, agent]) => agent.taskId === taskId);
|
|
1008
|
-
if (!entry) return false;
|
|
1009
|
-
session.subagents.delete(entry[0]);
|
|
1010
|
-
emitSubagents(session);
|
|
1011
|
-
return true;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
function clearForegroundSubagents(session) {
|
|
1015
|
-
let changed = false;
|
|
1016
|
-
for (const [id, agent] of session.subagents) {
|
|
1017
|
-
if (agent.background) continue;
|
|
1018
|
-
session.subagents.delete(id);
|
|
1019
|
-
changed = true;
|
|
1020
|
-
}
|
|
1021
|
-
if (changed) emitSubagents(session);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
function clearSubagents(session) {
|
|
1025
|
-
if (!session.subagents.size) return;
|
|
1037
|
+
function finishSubagent(session, toolUseId) {
|
|
1038
|
+
if (!session.subagents.delete(toolUseId)) return;
|
|
1039
|
+
emitSubagents(session);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function finishSubagentTask(session, taskId) {
|
|
1043
|
+
if (!taskId) return false;
|
|
1044
|
+
const entry = [...session.subagents.entries()].find(([, agent]) => agent.taskId === taskId);
|
|
1045
|
+
if (!entry) return false;
|
|
1046
|
+
session.subagents.delete(entry[0]);
|
|
1047
|
+
emitSubagents(session);
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function clearForegroundSubagents(session) {
|
|
1052
|
+
let changed = false;
|
|
1053
|
+
for (const [id, agent] of session.subagents) {
|
|
1054
|
+
if (agent.background) continue;
|
|
1055
|
+
session.subagents.delete(id);
|
|
1056
|
+
changed = true;
|
|
1057
|
+
}
|
|
1058
|
+
if (changed) emitSubagents(session);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function clearSubagents(session) {
|
|
1062
|
+
if (!session.subagents.size) return;
|
|
1026
1063
|
session.subagents.clear();
|
|
1027
1064
|
emitSubagents(session);
|
|
1028
1065
|
}
|
|
@@ -1042,87 +1079,87 @@ function emitSubagents(session) {
|
|
|
1042
1079
|
tool: agent.tool,
|
|
1043
1080
|
elapsedMs: Date.now() - agent.startedAt,
|
|
1044
1081
|
})),
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
function messageTextParts(message) {
|
|
1049
|
-
const content = message.message?.content;
|
|
1050
|
-
if (typeof content === "string") return [content];
|
|
1051
|
-
if (!Array.isArray(content)) return [];
|
|
1052
|
-
return content
|
|
1053
|
-
.filter((block) => block?.type === "text" && typeof block.text === "string")
|
|
1054
|
-
.map((block) => block.text);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
function notificationTag(body, name) {
|
|
1058
|
-
const match = body.match(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`));
|
|
1059
|
-
return match?.[1]?.trim() || "";
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
function taskNotifications(message) {
|
|
1063
|
-
if (message.origin?.kind !== "task-notification"
|
|
1064
|
-
&& !messageTextParts(message).some((text) => text.includes("<task-notification>"))) {
|
|
1065
|
-
return [];
|
|
1066
|
-
}
|
|
1067
|
-
const notifications = [];
|
|
1068
|
-
for (const text of messageTextParts(message)) {
|
|
1069
|
-
for (const match of text.matchAll(/<task-notification>([\s\S]*?)<\/task-notification>/g)) {
|
|
1070
|
-
const body = match[1];
|
|
1071
|
-
notifications.push({
|
|
1072
|
-
taskId: notificationTag(body, "task-id"),
|
|
1073
|
-
toolUseId: notificationTag(body, "tool-use-id"),
|
|
1074
|
-
status: notificationTag(body, "status"),
|
|
1075
|
-
summary: notificationTag(body, "summary"),
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
return notifications;
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
function finishNotifiedSubagents(session, notifications) {
|
|
1083
|
-
for (const notification of notifications) {
|
|
1084
|
-
const byToolUse = notification.toolUseId
|
|
1085
|
-
? session.subagents.get(notification.toolUseId)
|
|
1086
|
-
: null;
|
|
1087
|
-
const running = byToolUse || (notification.taskId
|
|
1088
|
-
? [...session.subagents.values()].find((agent) => agent.taskId === notification.taskId)
|
|
1089
|
-
: null);
|
|
1090
|
-
if (!running) continue;
|
|
1091
|
-
emitSubagentLine(session, running.id, {
|
|
1092
|
-
kind: notification.status === "completed" ? "result" : "error",
|
|
1093
|
-
text: notification.summary || notification.status || "μλ£λ¨",
|
|
1094
|
-
});
|
|
1095
|
-
session.subagents.delete(running.id);
|
|
1096
|
-
emitSubagents(session);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
function processUser(session, message) {
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function messageTextParts(message) {
|
|
1086
|
+
const content = message.message?.content;
|
|
1087
|
+
if (typeof content === "string") return [content];
|
|
1088
|
+
if (!Array.isArray(content)) return [];
|
|
1089
|
+
return content
|
|
1090
|
+
.filter((block) => block?.type === "text" && typeof block.text === "string")
|
|
1091
|
+
.map((block) => block.text);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function notificationTag(body, name) {
|
|
1095
|
+
const match = body.match(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`));
|
|
1096
|
+
return match?.[1]?.trim() || "";
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function taskNotifications(message) {
|
|
1100
|
+
if (message.origin?.kind !== "task-notification"
|
|
1101
|
+
&& !messageTextParts(message).some((text) => text.includes("<task-notification>"))) {
|
|
1102
|
+
return [];
|
|
1103
|
+
}
|
|
1104
|
+
const notifications = [];
|
|
1105
|
+
for (const text of messageTextParts(message)) {
|
|
1106
|
+
for (const match of text.matchAll(/<task-notification>([\s\S]*?)<\/task-notification>/g)) {
|
|
1107
|
+
const body = match[1];
|
|
1108
|
+
notifications.push({
|
|
1109
|
+
taskId: notificationTag(body, "task-id"),
|
|
1110
|
+
toolUseId: notificationTag(body, "tool-use-id"),
|
|
1111
|
+
status: notificationTag(body, "status"),
|
|
1112
|
+
summary: notificationTag(body, "summary"),
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return notifications;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function finishNotifiedSubagents(session, notifications) {
|
|
1120
|
+
for (const notification of notifications) {
|
|
1121
|
+
const byToolUse = notification.toolUseId
|
|
1122
|
+
? session.subagents.get(notification.toolUseId)
|
|
1123
|
+
: null;
|
|
1124
|
+
const running = byToolUse || (notification.taskId
|
|
1125
|
+
? [...session.subagents.values()].find((agent) => agent.taskId === notification.taskId)
|
|
1126
|
+
: null);
|
|
1127
|
+
if (!running) continue;
|
|
1128
|
+
emitSubagentLine(session, running.id, {
|
|
1129
|
+
kind: notification.status === "completed" ? "result" : "error",
|
|
1130
|
+
text: notification.summary || notification.status || "μλ£λ¨",
|
|
1131
|
+
});
|
|
1132
|
+
session.subagents.delete(running.id);
|
|
1133
|
+
emitSubagents(session);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function processUser(session, message) {
|
|
1101
1138
|
// μμ tool_resultμ tool_use_idλ λΆλͺ¨ μΈμ
μ κ²κ³Ό λ€λ₯Έ 곡κ°μ΄λ―λ‘, λΆλͺ¨ νλ¦μ
|
|
1102
1139
|
// μμ΄κΈ° μ μ μλΈμμ΄μ νΈ κΈ°λ‘μΌλ‘ 보λΈλ€.
|
|
1103
1140
|
if (message.parent_tool_use_id) {
|
|
1104
|
-
recordSubagentResult(session, message);
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
const notifications = taskNotifications(message);
|
|
1108
|
-
if (notifications.length) {
|
|
1109
|
-
if (!session.turn) beginTurn(session);
|
|
1110
|
-
finishNotifiedSubagents(session, notifications);
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
1114
|
-
for (const block of content) {
|
|
1115
|
-
if (block.type !== "tool_result") continue;
|
|
1116
|
-
const pending = session.tools.get(block.tool_use_id);
|
|
1117
|
-
const staysInBackground = SUBAGENT_TOOLS.includes(pending?.name)
|
|
1118
|
-
&& keepBackgroundSubagent(session, block.tool_use_id, message.tool_use_result);
|
|
1119
|
-
if (!staysInBackground) finishSubagent(session, block.tool_use_id);
|
|
1120
|
-
if (pending?.name === "SendMessage") {
|
|
1121
|
-
resumeBackgroundSubagent(session, block.tool_use_id, pending, message.tool_use_result);
|
|
1122
|
-
} else if (pending?.name === "TaskStop" && message.tool_use_result?.success !== false) {
|
|
1123
|
-
finishSubagentTask(session, firstLine(pending.input?.task_id || "", 80));
|
|
1124
|
-
}
|
|
1125
|
-
if (!pending) continue;
|
|
1141
|
+
recordSubagentResult(session, message);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const notifications = taskNotifications(message);
|
|
1145
|
+
if (notifications.length) {
|
|
1146
|
+
if (!session.turn) beginTurn(session);
|
|
1147
|
+
finishNotifiedSubagents(session, notifications);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
1151
|
+
for (const block of content) {
|
|
1152
|
+
if (block.type !== "tool_result") continue;
|
|
1153
|
+
const pending = session.tools.get(block.tool_use_id);
|
|
1154
|
+
const staysInBackground = SUBAGENT_TOOLS.includes(pending?.name)
|
|
1155
|
+
&& keepBackgroundSubagent(session, block.tool_use_id, message.tool_use_result);
|
|
1156
|
+
if (!staysInBackground) finishSubagent(session, block.tool_use_id);
|
|
1157
|
+
if (pending?.name === "SendMessage") {
|
|
1158
|
+
resumeBackgroundSubagent(session, block.tool_use_id, pending, message.tool_use_result);
|
|
1159
|
+
} else if (pending?.name === "TaskStop" && message.tool_use_result?.success !== false) {
|
|
1160
|
+
finishSubagentTask(session, firstLine(pending.input?.task_id || "", 80));
|
|
1161
|
+
}
|
|
1162
|
+
if (!pending) continue;
|
|
1126
1163
|
pending.toolUseId = block.tool_use_id;
|
|
1127
1164
|
if (pending.suppressed) {
|
|
1128
1165
|
updatePlanFromToolResult(session, pending, message);
|
|
@@ -1209,10 +1246,10 @@ async function runPendingPrompt(session) {
|
|
|
1209
1246
|
}
|
|
1210
1247
|
}
|
|
1211
1248
|
|
|
1212
|
-
function finishTurn(session, error, durationMs) {
|
|
1213
|
-
if (!session.turn) return;
|
|
1214
|
-
flushSmoothStreams(session);
|
|
1215
|
-
clearForegroundSubagents(session);
|
|
1249
|
+
function finishTurn(session, error, durationMs) {
|
|
1250
|
+
if (!session.turn) return;
|
|
1251
|
+
flushSmoothStreams(session);
|
|
1252
|
+
clearForegroundSubagents(session);
|
|
1216
1253
|
const turn = { id: session.turn.id, status: error ? "failed" : "completed" };
|
|
1217
1254
|
if (error) turn.error = { message: error instanceof Error ? error.message : error.message || String(error) };
|
|
1218
1255
|
if (durationMs != null) turn.durationMs = durationMs;
|
|
@@ -1228,7 +1265,7 @@ async function consume(session) {
|
|
|
1228
1265
|
if (message.event?.type === "content_block_delta" && (message.event?.delta?.text || message.event?.delta?.thinking)) {
|
|
1229
1266
|
if (session.turn) session.turn.sawStreamText = true;
|
|
1230
1267
|
}
|
|
1231
|
-
await processStreamEvent(session, message);
|
|
1268
|
+
await processStreamEvent(session, message);
|
|
1232
1269
|
} else if (message.type === "assistant") processAssistant(session, message);
|
|
1233
1270
|
else if (message.type === "user") processUser(session, message);
|
|
1234
1271
|
else if (message.type === "result") await processResult(session, message);
|
|
@@ -1298,7 +1335,7 @@ async function startPrompt(params) {
|
|
|
1298
1335
|
return runPrompt(session, params);
|
|
1299
1336
|
}
|
|
1300
1337
|
|
|
1301
|
-
async function runPrompt(session, params) {
|
|
1338
|
+
async function runPrompt(session, params) {
|
|
1302
1339
|
const id = session.id;
|
|
1303
1340
|
if (params.model) {
|
|
1304
1341
|
const model = stripClaudeModel(params.model);
|
|
@@ -1311,7 +1348,7 @@ async function runPrompt(session, params) {
|
|
|
1311
1348
|
}
|
|
1312
1349
|
session.effort = effort;
|
|
1313
1350
|
await applyPermissionMode(session, params.permissionMode);
|
|
1314
|
-
const turnId = beginTurn(session);
|
|
1351
|
+
const turnId = beginTurn(session);
|
|
1315
1352
|
session.queue.push({
|
|
1316
1353
|
type: "user",
|
|
1317
1354
|
message: { role: "user", content: await inputContent(params.input, params.handoffContext) },
|
|
@@ -1319,18 +1356,18 @@ async function runPrompt(session, params) {
|
|
|
1319
1356
|
session_id: id,
|
|
1320
1357
|
origin: { kind: "human" },
|
|
1321
1358
|
});
|
|
1322
|
-
return { turn: { id: turnId } };
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
// A background task notification is an internal user message that starts its
|
|
1326
|
-
// own Claude response even though the host did not submit a new prompt.
|
|
1327
|
-
function beginTurn(session) {
|
|
1328
|
-
const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
|
|
1329
|
-
session.turn = { id: turnId, sawStreamText: false };
|
|
1330
|
-
session.lastContextUsage = null;
|
|
1331
|
-
notify("turn/started", { threadId: session.id, turn: { id: turnId } });
|
|
1332
|
-
return turnId;
|
|
1333
|
-
}
|
|
1359
|
+
return { turn: { id: turnId } };
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// A background task notification is an internal user message that starts its
|
|
1363
|
+
// own Claude response even though the host did not submit a new prompt.
|
|
1364
|
+
function beginTurn(session) {
|
|
1365
|
+
const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
|
|
1366
|
+
session.turn = { id: turnId, sawStreamText: false };
|
|
1367
|
+
session.lastContextUsage = null;
|
|
1368
|
+
notify("turn/started", { threadId: session.id, turn: { id: turnId } });
|
|
1369
|
+
return turnId;
|
|
1370
|
+
}
|
|
1334
1371
|
|
|
1335
1372
|
function contentBlocks(message) {
|
|
1336
1373
|
const content = message?.content;
|
|
@@ -1355,7 +1392,7 @@ function isInternalHistoryText(message, text) {
|
|
|
1355
1392
|
].includes(tag);
|
|
1356
1393
|
}
|
|
1357
1394
|
|
|
1358
|
-
function historyState(messages) {
|
|
1395
|
+
function historyState(messages) {
|
|
1359
1396
|
const turns = [];
|
|
1360
1397
|
let turn = null;
|
|
1361
1398
|
const tools = new Map();
|
|
@@ -1391,14 +1428,14 @@ function historyState(messages) {
|
|
|
1391
1428
|
if (block.type === "text") turn.items.push({ id: `${message.uuid}-text`, type: "agentMessage", text: block.text || "", provider: "Claude" });
|
|
1392
1429
|
else if (block.type === "thinking") turn.items.push({ id: `${message.uuid}-thinking`, type: "reasoning", summary: [block.thinking || ""] });
|
|
1393
1430
|
else if (block.type === "tool_use") {
|
|
1394
|
-
const pending = { name: block.name, input: block.input || {}, item: toolItem({}, block.id, block.name, block.input || {}) };
|
|
1395
|
-
tools.set(block.id, pending);
|
|
1396
|
-
if (block.name === "TaskCreate") {
|
|
1397
|
-
prepareTaskPlanForCreate(tasks, block.input?.subject);
|
|
1398
|
-
tasks.set(`pending:${block.id}`, { id: `pending:${block.id}`, subject: block.input?.subject || "μμ
", status: "pending", turnId: turn.id });
|
|
1399
|
-
} else if (block.name === "TaskUpdate") {
|
|
1400
|
-
applyTaskUpdate(tasks, block.input || {}, turn.id);
|
|
1401
|
-
} else if (!["TaskList", "AskUserQuestion"].includes(block.name)) turn.items.push(pending.item);
|
|
1431
|
+
const pending = { name: block.name, input: block.input || {}, item: toolItem({}, block.id, block.name, block.input || {}) };
|
|
1432
|
+
tools.set(block.id, pending);
|
|
1433
|
+
if (block.name === "TaskCreate") {
|
|
1434
|
+
prepareTaskPlanForCreate(tasks, block.input?.subject);
|
|
1435
|
+
tasks.set(`pending:${block.id}`, { id: `pending:${block.id}`, subject: block.input?.subject || "μμ
", status: "pending", turnId: turn.id });
|
|
1436
|
+
} else if (block.name === "TaskUpdate") {
|
|
1437
|
+
applyTaskUpdate(tasks, block.input || {}, turn.id);
|
|
1438
|
+
} else if (!["TaskList", "AskUserQuestion"].includes(block.name)) turn.items.push(pending.item);
|
|
1402
1439
|
}
|
|
1403
1440
|
}
|
|
1404
1441
|
} else if (message.type === "user") {
|
|
@@ -1415,12 +1452,12 @@ function historyState(messages) {
|
|
|
1415
1452
|
tasks.set(temporary.id, temporary);
|
|
1416
1453
|
}
|
|
1417
1454
|
} else if (pending.name === "TaskList" && Array.isArray(message.tool_use_result?.tasks)) {
|
|
1418
|
-
const known = new Map(tasks);
|
|
1419
|
-
tasks.clear();
|
|
1420
|
-
for (const task of message.tool_use_result.tasks) tasks.set(String(task.id), { id: String(task.id), subject: task.subject || "μμ
", status: task.status || "pending", turnId: known.get(String(task.id))?.turnId ?? turn.id });
|
|
1421
|
-
const current = latestTaskPlan(tasks);
|
|
1422
|
-
tasks.clear();
|
|
1423
|
-
for (const [id, task] of current) tasks.set(id, task);
|
|
1455
|
+
const known = new Map(tasks);
|
|
1456
|
+
tasks.clear();
|
|
1457
|
+
for (const task of message.tool_use_result.tasks) tasks.set(String(task.id), { id: String(task.id), subject: task.subject || "μμ
", status: task.status || "pending", turnId: known.get(String(task.id))?.turnId ?? turn.id });
|
|
1458
|
+
const current = latestTaskPlan(tasks);
|
|
1459
|
+
tasks.clear();
|
|
1460
|
+
for (const [id, task] of current) tasks.set(id, task);
|
|
1424
1461
|
} else if (pending.item) {
|
|
1425
1462
|
const output = toolOutput(block.content, message.tool_use_result);
|
|
1426
1463
|
Object.assign(pending.item, pending.item.type === "commandExecution"
|
|
@@ -1436,17 +1473,17 @@ function historyState(messages) {
|
|
|
1436
1473
|
const text = [...tasks.values()].map((task, index) => `${task.status === "completed" ? "β" : task.status === "in_progress" ? "βΈ" : "β‘"} ${numberedTaskSubject(task.subject, index)}`).join("\n");
|
|
1437
1474
|
turn.items.push({ id: "claude-plan-latest", type: "plan", text });
|
|
1438
1475
|
}
|
|
1439
|
-
return {
|
|
1440
|
-
tasks,
|
|
1441
|
-
turns: turns
|
|
1442
|
-
.filter((candidate) => !candidate.synthetic)
|
|
1443
|
-
.map(({ synthetic: _, ...candidate }) => candidate),
|
|
1444
|
-
};
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
function historyTurns(messages) {
|
|
1448
|
-
return historyState(messages).turns;
|
|
1449
|
-
}
|
|
1476
|
+
return {
|
|
1477
|
+
tasks,
|
|
1478
|
+
turns: turns
|
|
1479
|
+
.filter((candidate) => !candidate.synthetic)
|
|
1480
|
+
.map(({ synthetic: _, ...candidate }) => candidate),
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function historyTurns(messages) {
|
|
1485
|
+
return historyState(messages).turns;
|
|
1486
|
+
}
|
|
1450
1487
|
|
|
1451
1488
|
async function dispatch(method, params = {}) {
|
|
1452
1489
|
if (method === "model/list") return loadModelCatalog(params);
|
|
@@ -1471,11 +1508,11 @@ async function dispatch(method, params = {}) {
|
|
|
1471
1508
|
}
|
|
1472
1509
|
if (method === "session/resume") {
|
|
1473
1510
|
const id = liveSessionId(params.sessionId);
|
|
1474
|
-
const existing = sessions.get(id);
|
|
1475
|
-
if (existing) {
|
|
1476
|
-
const messages = await getSessionMessages(id, { dir: existing.cwd, includeSystemMessages: true });
|
|
1477
|
-
if (!existing.tasks.size) existing.tasks = historyState(messages).tasks;
|
|
1478
|
-
return {
|
|
1511
|
+
const existing = sessions.get(id);
|
|
1512
|
+
if (existing) {
|
|
1513
|
+
const messages = await getSessionMessages(id, { dir: existing.cwd, includeSystemMessages: true });
|
|
1514
|
+
if (!existing.tasks.size) existing.tasks = historyState(messages).tasks;
|
|
1515
|
+
return {
|
|
1479
1516
|
id,
|
|
1480
1517
|
thread: { id, turns: [] },
|
|
1481
1518
|
initialTurnsPage: { data: [], nextCursor: null },
|
|
@@ -1499,11 +1536,11 @@ async function dispatch(method, params = {}) {
|
|
|
1499
1536
|
model: params.model || lastModel || params.fallbackModel,
|
|
1500
1537
|
effort: params.effort || params.fallbackEffort,
|
|
1501
1538
|
}, id);
|
|
1502
|
-
const tokenUsage = historyTokenUsage(messages, session.models, session.model);
|
|
1503
|
-
// Seed the live session so the next turn keeps reporting a full context.
|
|
1504
|
-
session.lastContextUsage = tokenUsage?.last || null;
|
|
1505
|
-
session.lastContextWindow = tokenUsage?.modelContextWindow || 0;
|
|
1506
|
-
session.tasks = historyState(messages).tasks;
|
|
1539
|
+
const tokenUsage = historyTokenUsage(messages, session.models, session.model);
|
|
1540
|
+
// Seed the live session so the next turn keeps reporting a full context.
|
|
1541
|
+
session.lastContextUsage = tokenUsage?.last || null;
|
|
1542
|
+
session.lastContextWindow = tokenUsage?.modelContextWindow || 0;
|
|
1543
|
+
session.tasks = historyState(messages).tasks;
|
|
1507
1544
|
return {
|
|
1508
1545
|
id,
|
|
1509
1546
|
thread: { id, turns: [] },
|
|
@@ -1605,7 +1642,7 @@ async function dispatch(method, params = {}) {
|
|
|
1605
1642
|
throw new Error(`μ§μνμ§ μλ Claude λΈλ¦¬μ§ λ©μλ: ${method}`);
|
|
1606
1643
|
}
|
|
1607
1644
|
|
|
1608
|
-
async function runSelfTest() {
|
|
1645
|
+
async function runSelfTest() {
|
|
1609
1646
|
const user = (uuid, text) => ({
|
|
1610
1647
|
type: "user",
|
|
1611
1648
|
uuid,
|
|
@@ -1636,50 +1673,50 @@ async function runSelfTest() {
|
|
|
1636
1673
|
["say hello", "claude:claude-sonnet-5"],
|
|
1637
1674
|
["hay zzz", "claude:claude-haiku-4-5-20251001"],
|
|
1638
1675
|
];
|
|
1639
|
-
if (turns.length !== expected.length
|
|
1640
|
-
|| prompts.some((prompt, index) => prompt?.content?.[0]?.text !== expected[index][0]
|
|
1641
|
-
|| prompt.model !== expected[index][1])) {
|
|
1642
|
-
throw new Error(`Claude history self-test failed: ${JSON.stringify(turns)}`);
|
|
1643
|
-
}
|
|
1644
|
-
const taskUse = (uuid, id, name, input) => ({
|
|
1645
|
-
type: "assistant",
|
|
1646
|
-
uuid,
|
|
1647
|
-
message: {
|
|
1648
|
-
role: "assistant",
|
|
1649
|
-
model: "claude-opus-5",
|
|
1650
|
-
content: [{ type: "tool_use", id, name, input }],
|
|
1651
|
-
},
|
|
1652
|
-
});
|
|
1653
|
-
const taskResult = (uuid, id, toolUseResult, content) => ({
|
|
1654
|
-
type: "user",
|
|
1655
|
-
uuid,
|
|
1656
|
-
message: { role: "user", content: [{ type: "tool_result", tool_use_id: id, content }] },
|
|
1657
|
-
tool_use_result: toolUseResult,
|
|
1658
|
-
});
|
|
1659
|
-
const taskMessages = [user("plan", "μμ
μ μ§νν΄")];
|
|
1660
|
-
for (let index = 1; index <= 6; index++) {
|
|
1661
|
-
const id = String(24 + index);
|
|
1662
|
-
const toolId = `create-${id}`;
|
|
1663
|
-
const subject = `${index}. μμ
${index}`;
|
|
1664
|
-
taskMessages.push(
|
|
1665
|
-
taskUse(`create-use-${id}`, toolId, "TaskCreate", { subject }),
|
|
1666
|
-
taskResult(`create-result-${id}`, toolId, { task: { id, subject } }, `Task #${id} created successfully: ${subject}`),
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
taskMessages.push(
|
|
1670
|
-
taskUse("update-25-start", "update-25-start", "TaskUpdate", { taskId: "25", status: "in_progress" }),
|
|
1671
|
-
taskUse("update-25-done", "update-25-done", "TaskUpdate", { taskId: "25", status: "completed" }),
|
|
1672
|
-
taskUse("update-26-start", "update-26-start", "TaskUpdate", { taskId: "26", status: "in_progress" }),
|
|
1673
|
-
);
|
|
1674
|
-
const restoredTasks = historyState(taskMessages).tasks;
|
|
1675
|
-
if ([...restoredTasks.keys()].join(",") !== "25,26,27,28,29,30"
|
|
1676
|
-
|| restoredTasks.get("25")?.status !== "completed"
|
|
1677
|
-
|| restoredTasks.get("26")?.status !== "in_progress"
|
|
1678
|
-
|| [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
|
|
1679
|
-
throw new Error(`Claude task resume self-test failed: ${JSON.stringify([...restoredTasks])}`);
|
|
1680
|
-
}
|
|
1681
|
-
applyTaskUpdate(restoredTasks, { taskId: "26", status: "completed" }, "resumed-turn");
|
|
1682
|
-
applyTaskUpdate(restoredTasks, { taskId: "27", status: "in_progress" }, "resumed-turn");
|
|
1676
|
+
if (turns.length !== expected.length
|
|
1677
|
+
|| prompts.some((prompt, index) => prompt?.content?.[0]?.text !== expected[index][0]
|
|
1678
|
+
|| prompt.model !== expected[index][1])) {
|
|
1679
|
+
throw new Error(`Claude history self-test failed: ${JSON.stringify(turns)}`);
|
|
1680
|
+
}
|
|
1681
|
+
const taskUse = (uuid, id, name, input) => ({
|
|
1682
|
+
type: "assistant",
|
|
1683
|
+
uuid,
|
|
1684
|
+
message: {
|
|
1685
|
+
role: "assistant",
|
|
1686
|
+
model: "claude-opus-5",
|
|
1687
|
+
content: [{ type: "tool_use", id, name, input }],
|
|
1688
|
+
},
|
|
1689
|
+
});
|
|
1690
|
+
const taskResult = (uuid, id, toolUseResult, content) => ({
|
|
1691
|
+
type: "user",
|
|
1692
|
+
uuid,
|
|
1693
|
+
message: { role: "user", content: [{ type: "tool_result", tool_use_id: id, content }] },
|
|
1694
|
+
tool_use_result: toolUseResult,
|
|
1695
|
+
});
|
|
1696
|
+
const taskMessages = [user("plan", "μμ
μ μ§νν΄")];
|
|
1697
|
+
for (let index = 1; index <= 6; index++) {
|
|
1698
|
+
const id = String(24 + index);
|
|
1699
|
+
const toolId = `create-${id}`;
|
|
1700
|
+
const subject = `${index}. μμ
${index}`;
|
|
1701
|
+
taskMessages.push(
|
|
1702
|
+
taskUse(`create-use-${id}`, toolId, "TaskCreate", { subject }),
|
|
1703
|
+
taskResult(`create-result-${id}`, toolId, { task: { id, subject } }, `Task #${id} created successfully: ${subject}`),
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
taskMessages.push(
|
|
1707
|
+
taskUse("update-25-start", "update-25-start", "TaskUpdate", { taskId: "25", status: "in_progress" }),
|
|
1708
|
+
taskUse("update-25-done", "update-25-done", "TaskUpdate", { taskId: "25", status: "completed" }),
|
|
1709
|
+
taskUse("update-26-start", "update-26-start", "TaskUpdate", { taskId: "26", status: "in_progress" }),
|
|
1710
|
+
);
|
|
1711
|
+
const restoredTasks = historyState(taskMessages).tasks;
|
|
1712
|
+
if ([...restoredTasks.keys()].join(",") !== "25,26,27,28,29,30"
|
|
1713
|
+
|| restoredTasks.get("25")?.status !== "completed"
|
|
1714
|
+
|| restoredTasks.get("26")?.status !== "in_progress"
|
|
1715
|
+
|| [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
|
|
1716
|
+
throw new Error(`Claude task resume self-test failed: ${JSON.stringify([...restoredTasks])}`);
|
|
1717
|
+
}
|
|
1718
|
+
applyTaskUpdate(restoredTasks, { taskId: "26", status: "completed" }, "resumed-turn");
|
|
1719
|
+
applyTaskUpdate(restoredTasks, { taskId: "27", status: "in_progress" }, "resumed-turn");
|
|
1683
1720
|
if (restoredTasks.size !== 6
|
|
1684
1721
|
|| restoredTasks.get("25")?.status !== "completed"
|
|
1685
1722
|
|| restoredTasks.get("26")?.status !== "completed"
|
|
@@ -1687,19 +1724,38 @@ async function runSelfTest() {
|
|
|
1687
1724
|
|| [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
|
|
1688
1725
|
throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
|
|
1689
1726
|
}
|
|
1690
|
-
const
|
|
1691
|
-
["
|
|
1692
|
-
["
|
|
1693
|
-
|
|
1727
|
+
const skippedTasks = new Map([
|
|
1728
|
+
["1", { id: "1", subject: "1. μ‘°μ¬", status: "pending" }],
|
|
1729
|
+
["2", { id: "2", subject: "2. λΆμ", status: "pending" }],
|
|
1730
|
+
["3", { id: "3", subject: "3. κ²μ¦", status: "pending" }],
|
|
1694
1731
|
]);
|
|
1695
|
-
|
|
1696
|
-
|
|
1732
|
+
const transitions = [];
|
|
1733
|
+
const snapshot = () => transitions.push([...skippedTasks.values()].map((task) => task.status).join(","));
|
|
1734
|
+
applyTaskUpdate(skippedTasks, { taskId: "3", status: "completed" }, "turn", snapshot);
|
|
1735
|
+
snapshot();
|
|
1736
|
+
if (transitions.join("|") !== [
|
|
1737
|
+
"in_progress,pending,pending",
|
|
1738
|
+
"completed,pending,pending",
|
|
1739
|
+
"completed,in_progress,pending",
|
|
1740
|
+
"completed,completed,pending",
|
|
1741
|
+
"completed,completed,in_progress",
|
|
1742
|
+
"completed,completed,completed",
|
|
1743
|
+
].join("|")) {
|
|
1744
|
+
throw new Error(`Claude skipped task transition self-test failed: ${transitions.join("|")}`);
|
|
1697
1745
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1746
|
+
const mixedPlans = new Map([
|
|
1747
|
+
["old-1", { subject: "1. μ΄μ μμ
", status: "completed" }],
|
|
1748
|
+
["old-2", { subject: "2. μ΄μ κ²μ¦", status: "completed" }],
|
|
1749
|
+
...[...restoredTasks],
|
|
1750
|
+
]);
|
|
1751
|
+
if ([...latestTaskPlan(mixedPlans).keys()].join(",") !== "25,26,27,28,29,30") {
|
|
1752
|
+
throw new Error(`Claude latest task plan self-test failed: ${JSON.stringify([...mixedPlans])}`);
|
|
1753
|
+
}
|
|
1754
|
+
prepareTaskPlanForCreate(restoredTasks, "7. μΆκ° μμ
");
|
|
1755
|
+
if (restoredTasks.size !== 6) throw new Error("Claude appended task unexpectedly reset the plan");
|
|
1756
|
+
prepareTaskPlanForCreate(restoredTasks, "1. μ μμ
");
|
|
1757
|
+
if (restoredTasks.size !== 0) throw new Error("Claude new task plan did not reset the previous plan");
|
|
1758
|
+
const usage = tokenBreakdown({
|
|
1703
1759
|
input_tokens: 2,
|
|
1704
1760
|
cache_read_input_tokens: 68_000,
|
|
1705
1761
|
cache_creation_input_tokens: 500,
|
|
@@ -1713,148 +1769,148 @@ async function runSelfTest() {
|
|
|
1713
1769
|
catalogEntry({ value: "sonnet", resolvedModel: "claude-sonnet-5" }, "").contextWindow,
|
|
1714
1770
|
catalogEntry({ value: "haiku", resolvedModel: "x", contextWindow: 300_000 }, "").contextWindow,
|
|
1715
1771
|
];
|
|
1716
|
-
if (windows.join(",") !== "1000000,200000,300000") {
|
|
1717
|
-
throw new Error(`Claude context window self-test failed: ${windows.join(",")}`);
|
|
1718
|
-
}
|
|
1719
|
-
const notification = taskNotifications({
|
|
1720
|
-
origin: { kind: "task-notification" },
|
|
1721
|
-
message: {
|
|
1722
|
-
content: `<task-notification>
|
|
1723
|
-
<task-id>agent-1</task-id>
|
|
1724
|
-
<tool-use-id>toolu_1</tool-use-id>
|
|
1725
|
-
<status>completed</status>
|
|
1726
|
-
<summary>Agent "Explore" finished</summary>
|
|
1727
|
-
<result>done</result>
|
|
1728
|
-
</task-notification>`,
|
|
1729
|
-
},
|
|
1730
|
-
});
|
|
1731
|
-
if (notification.length !== 1
|
|
1732
|
-
|| notification[0].taskId !== "agent-1"
|
|
1733
|
-
|| notification[0].toolUseId !== "toolu_1"
|
|
1734
|
-
|| notification[0].status !== "completed"
|
|
1735
|
-
|| notification[0].summary !== 'Agent "Explore" finished') {
|
|
1736
|
-
throw new Error(`Claude task notification self-test failed: ${JSON.stringify(notification)}`);
|
|
1737
|
-
}
|
|
1738
|
-
const lifecycleSession = {
|
|
1739
|
-
id: "self-test-session",
|
|
1740
|
-
turn: { id: "parent-turn", sawStreamText: false },
|
|
1741
|
-
turnSequence: 1,
|
|
1742
|
-
streamBlocks: new Map(),
|
|
1743
|
-
tools: new Map([[
|
|
1744
|
-
"toolu_1",
|
|
1745
|
-
{
|
|
1746
|
-
name: "Agent",
|
|
1747
|
-
input: { subagent_type: "Explore", description: "Inspect files" },
|
|
1748
|
-
item: { id: "toolu_1", type: "collabAgentToolCall", tool: { name: "Agent", arguments: {} } },
|
|
1749
|
-
},
|
|
1750
|
-
]]),
|
|
1751
|
-
subagents: new Map([[
|
|
1752
|
-
"toolu_1",
|
|
1753
|
-
{
|
|
1754
|
-
id: "toolu_1",
|
|
1755
|
-
taskId: "",
|
|
1756
|
-
background: false,
|
|
1757
|
-
name: "Explore",
|
|
1758
|
-
description: "Inspect files",
|
|
1759
|
-
tool: "",
|
|
1760
|
-
startedAt: Date.now(),
|
|
1761
|
-
},
|
|
1762
|
-
]]),
|
|
1763
|
-
knownSubagents: new Map(),
|
|
1764
|
-
lastContextUsage: null,
|
|
1765
|
-
};
|
|
1766
|
-
const captured = [];
|
|
1767
|
-
const stdoutWrite = process.stdout.write;
|
|
1768
|
-
process.stdout.write = (chunk) => {
|
|
1769
|
-
captured.push(String(chunk));
|
|
1770
|
-
return true;
|
|
1771
|
-
};
|
|
1772
|
-
try {
|
|
1773
|
-
processUser(lifecycleSession, {
|
|
1774
|
-
message: { content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "launched" }] },
|
|
1775
|
-
tool_use_result: { isAsync: true, status: "async_launched", agentId: "agent-1" },
|
|
1776
|
-
});
|
|
1777
|
-
finishTurn(lifecycleSession, null, 1);
|
|
1778
|
-
if (!lifecycleSession.subagents.has("toolu_1") || lifecycleSession.turn !== null) {
|
|
1779
|
-
throw new Error("Claude background subagent did not survive its parent turn");
|
|
1780
|
-
}
|
|
1781
|
-
processUser(lifecycleSession, {
|
|
1782
|
-
origin: { kind: "task-notification" },
|
|
1783
|
-
message: { content: notification[0] && `<task-notification>
|
|
1784
|
-
<task-id>agent-1</task-id><tool-use-id>toolu_1</tool-use-id>
|
|
1785
|
-
<status>completed</status><summary>Agent finished</summary>
|
|
1786
|
-
</task-notification>` },
|
|
1787
|
-
});
|
|
1788
|
-
if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
|
|
1789
|
-
throw new Error("Claude task notification did not finish the agent in an automatic turn");
|
|
1790
|
-
}
|
|
1791
|
-
finishTurn(lifecycleSession, null, 1);
|
|
1792
|
-
|
|
1793
|
-
beginTurn(lifecycleSession);
|
|
1794
|
-
lifecycleSession.tools.set("toolu_2", {
|
|
1795
|
-
name: "SendMessage",
|
|
1796
|
-
input: { to: "agent-1", summary: "Continue inspection" },
|
|
1797
|
-
item: { id: "toolu_2", type: "dynamicToolCall", tool: "SendMessage", arguments: {} },
|
|
1798
|
-
});
|
|
1799
|
-
processUser(lifecycleSession, {
|
|
1800
|
-
message: { content: [{ type: "tool_result", tool_use_id: "toolu_2", content: "resumed" }] },
|
|
1801
|
-
tool_use_result: { success: true, resumedAgentId: "agent-1" },
|
|
1802
|
-
});
|
|
1803
|
-
const resumed = lifecycleSession.subagents.get("toolu_2");
|
|
1804
|
-
if (!resumed?.background || resumed.taskId !== "agent-1" || resumed.name !== "Explore") {
|
|
1805
|
-
throw new Error(`Claude resumed subagent self-test failed: ${JSON.stringify(resumed)}`);
|
|
1806
|
-
}
|
|
1807
|
-
finishTurn(lifecycleSession, null, 1);
|
|
1808
|
-
processUser(lifecycleSession, {
|
|
1809
|
-
origin: { kind: "task-notification" },
|
|
1810
|
-
message: { content: `<task-notification>
|
|
1811
|
-
<task-id>agent-1</task-id><tool-use-id>toolu_2</tool-use-id>
|
|
1812
|
-
<status>failed</status><summary>Agent failed</summary>
|
|
1813
|
-
</task-notification>` },
|
|
1814
|
-
});
|
|
1815
|
-
if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
|
|
1816
|
-
throw new Error("Claude failed task notification did not finish the resumed agent");
|
|
1817
|
-
}
|
|
1818
|
-
finishTurn(lifecycleSession, null, 1);
|
|
1819
|
-
} finally {
|
|
1820
|
-
process.stdout.write = stdoutWrite;
|
|
1821
|
-
}
|
|
1822
|
-
const lifecycleEvents = captured
|
|
1823
|
-
.join("")
|
|
1824
|
-
.trim()
|
|
1825
|
-
.split("\n")
|
|
1826
|
-
.filter(Boolean)
|
|
1827
|
-
.map((line) => JSON.parse(line));
|
|
1828
|
-
const lifecycleMethods = lifecycleEvents.map((event) => event.method);
|
|
1829
|
-
if (!lifecycleMethods.includes("turn/subagents/updated")
|
|
1830
|
-
|| lifecycleMethods.filter((method) => method === "turn/started").length < 3
|
|
1831
|
-
|| !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
|
|
1832
|
-
&& event.params?.line?.kind === "error")) {
|
|
1833
|
-
throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
|
|
1834
|
-
}
|
|
1835
|
-
const smoothText = "Claudeκ° π¨βπ©βπ§βπ¦ ν λ¬Έμ₯μ νκΊΌλ²μ 보λ΄λ λΆλλ½κ² νμν©λλ€.";
|
|
1836
|
-
const emitted = [];
|
|
1837
|
-
const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
|
|
1838
|
-
smooth.push(smoothText);
|
|
1839
|
-
await smooth.finish();
|
|
1840
|
-
if (emitted.length < 2 || emitted.join("") !== smoothText) {
|
|
1841
|
-
throw new Error(`Claude smooth stream self-test failed: ${JSON.stringify(emitted)}`);
|
|
1842
|
-
}
|
|
1843
|
-
const flushed = [];
|
|
1844
|
-
const interrupted = new SmoothTextStream((chunk) => flushed.push(chunk), 1000);
|
|
1845
|
-
interrupted.push(smoothText);
|
|
1846
|
-
interrupted.flush();
|
|
1847
|
-
await interrupted.finish();
|
|
1848
|
-
if (flushed.join("") !== smoothText) {
|
|
1849
|
-
throw new Error(`Claude smooth stream flush self-test failed: ${JSON.stringify(flushed)}`);
|
|
1850
|
-
}
|
|
1851
|
-
process.stdout.write("Claude bridge self-test passed\n");
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
if (process.argv.includes("--self-test")) {
|
|
1855
|
-
await runSelfTest();
|
|
1856
|
-
process.exit(0);
|
|
1857
|
-
}
|
|
1772
|
+
if (windows.join(",") !== "1000000,200000,300000") {
|
|
1773
|
+
throw new Error(`Claude context window self-test failed: ${windows.join(",")}`);
|
|
1774
|
+
}
|
|
1775
|
+
const notification = taskNotifications({
|
|
1776
|
+
origin: { kind: "task-notification" },
|
|
1777
|
+
message: {
|
|
1778
|
+
content: `<task-notification>
|
|
1779
|
+
<task-id>agent-1</task-id>
|
|
1780
|
+
<tool-use-id>toolu_1</tool-use-id>
|
|
1781
|
+
<status>completed</status>
|
|
1782
|
+
<summary>Agent "Explore" finished</summary>
|
|
1783
|
+
<result>done</result>
|
|
1784
|
+
</task-notification>`,
|
|
1785
|
+
},
|
|
1786
|
+
});
|
|
1787
|
+
if (notification.length !== 1
|
|
1788
|
+
|| notification[0].taskId !== "agent-1"
|
|
1789
|
+
|| notification[0].toolUseId !== "toolu_1"
|
|
1790
|
+
|| notification[0].status !== "completed"
|
|
1791
|
+
|| notification[0].summary !== 'Agent "Explore" finished') {
|
|
1792
|
+
throw new Error(`Claude task notification self-test failed: ${JSON.stringify(notification)}`);
|
|
1793
|
+
}
|
|
1794
|
+
const lifecycleSession = {
|
|
1795
|
+
id: "self-test-session",
|
|
1796
|
+
turn: { id: "parent-turn", sawStreamText: false },
|
|
1797
|
+
turnSequence: 1,
|
|
1798
|
+
streamBlocks: new Map(),
|
|
1799
|
+
tools: new Map([[
|
|
1800
|
+
"toolu_1",
|
|
1801
|
+
{
|
|
1802
|
+
name: "Agent",
|
|
1803
|
+
input: { subagent_type: "Explore", description: "Inspect files" },
|
|
1804
|
+
item: { id: "toolu_1", type: "collabAgentToolCall", tool: { name: "Agent", arguments: {} } },
|
|
1805
|
+
},
|
|
1806
|
+
]]),
|
|
1807
|
+
subagents: new Map([[
|
|
1808
|
+
"toolu_1",
|
|
1809
|
+
{
|
|
1810
|
+
id: "toolu_1",
|
|
1811
|
+
taskId: "",
|
|
1812
|
+
background: false,
|
|
1813
|
+
name: "Explore",
|
|
1814
|
+
description: "Inspect files",
|
|
1815
|
+
tool: "",
|
|
1816
|
+
startedAt: Date.now(),
|
|
1817
|
+
},
|
|
1818
|
+
]]),
|
|
1819
|
+
knownSubagents: new Map(),
|
|
1820
|
+
lastContextUsage: null,
|
|
1821
|
+
};
|
|
1822
|
+
const captured = [];
|
|
1823
|
+
const stdoutWrite = process.stdout.write;
|
|
1824
|
+
process.stdout.write = (chunk) => {
|
|
1825
|
+
captured.push(String(chunk));
|
|
1826
|
+
return true;
|
|
1827
|
+
};
|
|
1828
|
+
try {
|
|
1829
|
+
processUser(lifecycleSession, {
|
|
1830
|
+
message: { content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "launched" }] },
|
|
1831
|
+
tool_use_result: { isAsync: true, status: "async_launched", agentId: "agent-1" },
|
|
1832
|
+
});
|
|
1833
|
+
finishTurn(lifecycleSession, null, 1);
|
|
1834
|
+
if (!lifecycleSession.subagents.has("toolu_1") || lifecycleSession.turn !== null) {
|
|
1835
|
+
throw new Error("Claude background subagent did not survive its parent turn");
|
|
1836
|
+
}
|
|
1837
|
+
processUser(lifecycleSession, {
|
|
1838
|
+
origin: { kind: "task-notification" },
|
|
1839
|
+
message: { content: notification[0] && `<task-notification>
|
|
1840
|
+
<task-id>agent-1</task-id><tool-use-id>toolu_1</tool-use-id>
|
|
1841
|
+
<status>completed</status><summary>Agent finished</summary>
|
|
1842
|
+
</task-notification>` },
|
|
1843
|
+
});
|
|
1844
|
+
if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
|
|
1845
|
+
throw new Error("Claude task notification did not finish the agent in an automatic turn");
|
|
1846
|
+
}
|
|
1847
|
+
finishTurn(lifecycleSession, null, 1);
|
|
1848
|
+
|
|
1849
|
+
beginTurn(lifecycleSession);
|
|
1850
|
+
lifecycleSession.tools.set("toolu_2", {
|
|
1851
|
+
name: "SendMessage",
|
|
1852
|
+
input: { to: "agent-1", summary: "Continue inspection" },
|
|
1853
|
+
item: { id: "toolu_2", type: "dynamicToolCall", tool: "SendMessage", arguments: {} },
|
|
1854
|
+
});
|
|
1855
|
+
processUser(lifecycleSession, {
|
|
1856
|
+
message: { content: [{ type: "tool_result", tool_use_id: "toolu_2", content: "resumed" }] },
|
|
1857
|
+
tool_use_result: { success: true, resumedAgentId: "agent-1" },
|
|
1858
|
+
});
|
|
1859
|
+
const resumed = lifecycleSession.subagents.get("toolu_2");
|
|
1860
|
+
if (!resumed?.background || resumed.taskId !== "agent-1" || resumed.name !== "Explore") {
|
|
1861
|
+
throw new Error(`Claude resumed subagent self-test failed: ${JSON.stringify(resumed)}`);
|
|
1862
|
+
}
|
|
1863
|
+
finishTurn(lifecycleSession, null, 1);
|
|
1864
|
+
processUser(lifecycleSession, {
|
|
1865
|
+
origin: { kind: "task-notification" },
|
|
1866
|
+
message: { content: `<task-notification>
|
|
1867
|
+
<task-id>agent-1</task-id><tool-use-id>toolu_2</tool-use-id>
|
|
1868
|
+
<status>failed</status><summary>Agent failed</summary>
|
|
1869
|
+
</task-notification>` },
|
|
1870
|
+
});
|
|
1871
|
+
if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
|
|
1872
|
+
throw new Error("Claude failed task notification did not finish the resumed agent");
|
|
1873
|
+
}
|
|
1874
|
+
finishTurn(lifecycleSession, null, 1);
|
|
1875
|
+
} finally {
|
|
1876
|
+
process.stdout.write = stdoutWrite;
|
|
1877
|
+
}
|
|
1878
|
+
const lifecycleEvents = captured
|
|
1879
|
+
.join("")
|
|
1880
|
+
.trim()
|
|
1881
|
+
.split("\n")
|
|
1882
|
+
.filter(Boolean)
|
|
1883
|
+
.map((line) => JSON.parse(line));
|
|
1884
|
+
const lifecycleMethods = lifecycleEvents.map((event) => event.method);
|
|
1885
|
+
if (!lifecycleMethods.includes("turn/subagents/updated")
|
|
1886
|
+
|| lifecycleMethods.filter((method) => method === "turn/started").length < 3
|
|
1887
|
+
|| !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
|
|
1888
|
+
&& event.params?.line?.kind === "error")) {
|
|
1889
|
+
throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
|
|
1890
|
+
}
|
|
1891
|
+
const smoothText = "Claudeκ° π¨βπ©βπ§βπ¦ ν λ¬Έμ₯μ νκΊΌλ²μ 보λ΄λ λΆλλ½κ² νμν©λλ€.";
|
|
1892
|
+
const emitted = [];
|
|
1893
|
+
const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
|
|
1894
|
+
smooth.push(smoothText);
|
|
1895
|
+
await smooth.finish();
|
|
1896
|
+
if (emitted.length < 2 || emitted.join("") !== smoothText) {
|
|
1897
|
+
throw new Error(`Claude smooth stream self-test failed: ${JSON.stringify(emitted)}`);
|
|
1898
|
+
}
|
|
1899
|
+
const flushed = [];
|
|
1900
|
+
const interrupted = new SmoothTextStream((chunk) => flushed.push(chunk), 1000);
|
|
1901
|
+
interrupted.push(smoothText);
|
|
1902
|
+
interrupted.flush();
|
|
1903
|
+
await interrupted.finish();
|
|
1904
|
+
if (flushed.join("") !== smoothText) {
|
|
1905
|
+
throw new Error(`Claude smooth stream flush self-test failed: ${JSON.stringify(flushed)}`);
|
|
1906
|
+
}
|
|
1907
|
+
process.stdout.write("Claude bridge self-test passed\n");
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
if (process.argv.includes("--self-test")) {
|
|
1911
|
+
await runSelfTest();
|
|
1912
|
+
process.exit(0);
|
|
1913
|
+
}
|
|
1858
1914
|
|
|
1859
1915
|
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
1860
1916
|
lines.on("line", async (line) => {
|