devez-vibe 1.2.7 → 1.2.8
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/LICENSE +33 -33
- package/bin/dvz.exe +0 -0
- package/bridge/claude-agent-sdk-bridge.mjs +286 -124
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
DevezCode source code license
|
|
2
|
-
|
|
3
|
-
The MIT License below applies only to the DevezCode source code and to files
|
|
4
|
-
that are not listed in the exception below.
|
|
5
|
-
|
|
6
|
-
Exceptions:
|
|
7
|
-
|
|
8
|
-
* `Resources/Images/FileTypes/` and `Resources/Licenses/` are Microsoft Visual
|
|
9
|
-
Studio Image Library materials, subject to the included Microsoft terms.
|
|
10
|
-
* `Resources/Images/ShellPresets/` contains third-party brand assets, subject
|
|
11
|
-
to their respective owners' terms.
|
|
12
|
-
|
|
13
|
-
MIT License
|
|
14
|
-
|
|
15
|
-
Copyright (c) 2026 DevezCode contributors
|
|
16
|
-
|
|
17
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
-
in the Software without restriction, including without limitation the rights
|
|
20
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
-
furnished to do so, subject to the following conditions:
|
|
23
|
-
|
|
24
|
-
The above copyright notice and this permission notice shall be included in all
|
|
25
|
-
copies or substantial portions of the Software.
|
|
26
|
-
|
|
27
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
-
SOFTWARE.
|
|
1
|
+
DevezCode source code license
|
|
2
|
+
|
|
3
|
+
The MIT License below applies only to the DevezCode source code and to files
|
|
4
|
+
that are not listed in the exception below.
|
|
5
|
+
|
|
6
|
+
Exceptions:
|
|
7
|
+
|
|
8
|
+
* `Resources/Images/FileTypes/` and `Resources/Licenses/` are Microsoft Visual
|
|
9
|
+
Studio Image Library materials, subject to the included Microsoft terms.
|
|
10
|
+
* `Resources/Images/ShellPresets/` contains third-party brand assets, subject
|
|
11
|
+
to their respective owners' terms.
|
|
12
|
+
|
|
13
|
+
MIT License
|
|
14
|
+
|
|
15
|
+
Copyright (c) 2026 DevezCode contributors
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -497,9 +497,48 @@ 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
|
-
}
|
|
500
|
+
function emitDelta(session, method, itemId, delta) {
|
|
501
|
+
notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function isKoreanPrompt(input) {
|
|
505
|
+
const prompt = (Array.isArray(input) ? input : [])
|
|
506
|
+
.filter((item) => item?.type === "text")
|
|
507
|
+
.map((item) => String(item.text || ""))
|
|
508
|
+
.join("\n");
|
|
509
|
+
return /[\uac00-\ud7a3]/.test(prompt);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function openingNotice(input) {
|
|
513
|
+
return isKoreanPrompt(input)
|
|
514
|
+
? "요청 내용을 확인하고 필요한 작업을 진행하겠습니다."
|
|
515
|
+
: "I’ll review the request and proceed with the necessary work.";
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function normalizeProgressText(turn, text) {
|
|
519
|
+
const value = String(text || "");
|
|
520
|
+
const trimmed = value.trim();
|
|
521
|
+
if (turn?.koreanRequest
|
|
522
|
+
&& trimmed.length <= 160
|
|
523
|
+
&& !/[\uac00-\ud7a3]/.test(trimmed)
|
|
524
|
+
&& /^Now\b[^\r\n]*[.!?]?$/i.test(trimmed)) {
|
|
525
|
+
return "다음 부분을 이어서 확인하겠습니다.";
|
|
526
|
+
}
|
|
527
|
+
return value;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Claude can answer with a tool_use as its first and only content block even
|
|
531
|
+
// when the prompt asks for an opening update. Keep the visible contract stable
|
|
532
|
+
// without duplicating a real model-written update.
|
|
533
|
+
function emitOpeningNotice(session) {
|
|
534
|
+
if (!session.turn || session.turn.openingNoticeEmitted) return;
|
|
535
|
+
const text = session.turn.openingNotice;
|
|
536
|
+
const id = nextItemId(session, "opening");
|
|
537
|
+
const item = { id, type: "agentMessage", text, provider: "Claude" };
|
|
538
|
+
emitItem(session, "started", item);
|
|
539
|
+
emitItem(session, "completed", item);
|
|
540
|
+
session.turn.openingNoticeEmitted = true;
|
|
541
|
+
}
|
|
503
542
|
|
|
504
543
|
// Windows rounds larger timer delays up to the next scheduler slice. Ten
|
|
505
544
|
// milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
|
|
@@ -632,7 +671,7 @@ async function processStreamEvent(session, message) {
|
|
|
632
671
|
flushSmoothStreams(session);
|
|
633
672
|
session.streamBlocks.clear();
|
|
634
673
|
}
|
|
635
|
-
if (event.type === "content_block_start") {
|
|
674
|
+
if (event.type === "content_block_start") {
|
|
636
675
|
const block = event.content_block || {};
|
|
637
676
|
if (block.type !== "text" && block.type !== "thinking") return;
|
|
638
677
|
const id = nextItemId(session, block.type);
|
|
@@ -642,24 +681,54 @@ async function processStreamEvent(session, message) {
|
|
|
642
681
|
const smooth = block.type === "text"
|
|
643
682
|
? new SmoothTextStream((delta) => emitDelta(session, "item/agentMessage/delta", id, delta))
|
|
644
683
|
: null;
|
|
645
|
-
session.streamBlocks.set(event.index, {
|
|
684
|
+
session.streamBlocks.set(event.index, {
|
|
685
|
+
id,
|
|
686
|
+
type: block.type,
|
|
687
|
+
text: "",
|
|
688
|
+
smooth,
|
|
689
|
+
languagePending: block.type === "text" && session.turn.koreanRequest ? "" : null,
|
|
690
|
+
holdEnglishProgress: false,
|
|
691
|
+
});
|
|
646
692
|
emitItem(session, "started", item);
|
|
647
693
|
return;
|
|
648
694
|
}
|
|
649
|
-
if (event.type === "content_block_delta") {
|
|
695
|
+
if (event.type === "content_block_delta") {
|
|
650
696
|
const current = session.streamBlocks.get(event.index);
|
|
651
697
|
if (!current) return;
|
|
652
|
-
const delta = event.delta?.text || event.delta?.thinking || "";
|
|
653
|
-
if (!delta) return;
|
|
654
|
-
current.text += delta;
|
|
655
|
-
if (current.
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
698
|
+
const delta = event.delta?.text || event.delta?.thinking || "";
|
|
699
|
+
if (!delta) return;
|
|
700
|
+
current.text += delta;
|
|
701
|
+
if (current.type === "text") session.turn.sawVisibleText = true;
|
|
702
|
+
if (!current.smooth) {
|
|
703
|
+
emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (current.languagePending != null) {
|
|
707
|
+
current.languagePending += delta;
|
|
708
|
+
const probe = current.languagePending.trimStart();
|
|
709
|
+
const lower = probe.toLowerCase();
|
|
710
|
+
if (!current.holdEnglishProgress && "now".startsWith(lower)) return;
|
|
711
|
+
if (/^now(?:\s|$)/i.test(probe)) {
|
|
712
|
+
current.holdEnglishProgress = true;
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
current.smooth.push(current.languagePending);
|
|
716
|
+
current.languagePending = null;
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
current.smooth.push(delta);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
659
722
|
if (event.type === "content_block_stop") {
|
|
660
|
-
const current = session.streamBlocks.get(event.index);
|
|
661
|
-
if (!current) return;
|
|
662
|
-
|
|
723
|
+
const current = session.streamBlocks.get(event.index);
|
|
724
|
+
if (!current) return;
|
|
725
|
+
if (current.languagePending != null) {
|
|
726
|
+
const visible = normalizeProgressText(session.turn, current.languagePending);
|
|
727
|
+
current.text = visible;
|
|
728
|
+
current.smooth?.push(visible);
|
|
729
|
+
current.languagePending = null;
|
|
730
|
+
}
|
|
731
|
+
await current.smooth?.finish();
|
|
663
732
|
const item = current.type === "text"
|
|
664
733
|
? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
|
|
665
734
|
: { id: current.id, type: "reasoning", summary: [current.text] };
|
|
@@ -684,22 +753,29 @@ function processAssistant(session, message) {
|
|
|
684
753
|
message.message?.model,
|
|
685
754
|
session.model,
|
|
686
755
|
);
|
|
687
|
-
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
756
|
+
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
757
|
+
const hasToolUse = content.some((block) => block.type === "tool_use");
|
|
758
|
+
const hasVisibleText = session.turn.sawVisibleText || content.some(
|
|
759
|
+
(block) => block.type === "text" && String(block.text || "").trim(),
|
|
760
|
+
);
|
|
761
|
+
// Without partial SDK events, replay completed text before tool items so the
|
|
762
|
+
// visible order still matches the assistant content order.
|
|
763
|
+
if (!session.streamBlocks.size && !session.turn.sawStreamText) {
|
|
764
|
+
for (const block of content) {
|
|
765
|
+
if (block.type !== "text" && block.type !== "thinking") continue;
|
|
766
|
+
const id = nextItemId(session, block.type);
|
|
767
|
+
const item = block.type === "text"
|
|
768
|
+
? { id, type: "agentMessage", text: normalizeProgressText(session.turn, block.text), provider: "Claude" }
|
|
769
|
+
: { id, type: "reasoning", summary: [block.thinking || ""] };
|
|
770
|
+
emitItem(session, "started", item);
|
|
771
|
+
emitItem(session, "completed", item);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (hasToolUse && !hasVisibleText) emitOpeningNotice(session);
|
|
775
|
+
for (const block of content) {
|
|
776
|
+
if (block.type === "tool_use") processToolUse(session, block);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
703
779
|
|
|
704
780
|
function processToolUse(session, block) {
|
|
705
781
|
const name = block.name || "Tool";
|
|
@@ -762,51 +838,51 @@ function prepareTaskPlanForCreate(tasks, subject) {
|
|
|
762
838
|
if (numberedTaskIndex(subject) === 1) tasks.clear();
|
|
763
839
|
}
|
|
764
840
|
|
|
765
|
-
function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
|
|
766
|
-
const task = tasks.get(String(input.taskId));
|
|
767
|
-
if (!task) return false;
|
|
768
|
-
if (input.subject) task.subject = input.subject;
|
|
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
|
-
}
|
|
797
|
-
|
|
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;
|
|
806
|
-
}
|
|
807
|
-
task.turnId = turnId;
|
|
808
|
-
return true;
|
|
809
|
-
}
|
|
841
|
+
function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
|
|
842
|
+
const task = tasks.get(String(input.taskId));
|
|
843
|
+
if (!task) return false;
|
|
844
|
+
if (input.subject) task.subject = input.subject;
|
|
845
|
+
const status = input.status;
|
|
846
|
+
if (status === "in_progress" || status === "completed") {
|
|
847
|
+
const entries = [...tasks.values()];
|
|
848
|
+
const targetIndex = entries.indexOf(task);
|
|
849
|
+
|
|
850
|
+
// Claude occasionally closes a later pending task at the end of a turn
|
|
851
|
+
// without ever starting it. Keep the visible plan truthful and sequential:
|
|
852
|
+
// every skipped predecessor and the target itself pass through in_progress.
|
|
853
|
+
for (let index = 0; index < targetIndex; index++) {
|
|
854
|
+
const previous = entries[index];
|
|
855
|
+
if (previous.status === "completed") continue;
|
|
856
|
+
if (previous.status !== "in_progress") {
|
|
857
|
+
previous.status = "in_progress";
|
|
858
|
+
previous.turnId = turnId;
|
|
859
|
+
onIntermediate?.();
|
|
860
|
+
}
|
|
861
|
+
previous.status = "completed";
|
|
862
|
+
previous.turnId = turnId;
|
|
863
|
+
onIntermediate?.();
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
for (let index = 0; index < entries.length; index++) {
|
|
867
|
+
const other = entries[index];
|
|
868
|
+
if (other === task || other.status !== "in_progress") continue;
|
|
869
|
+
other.status = index < targetIndex ? "completed" : "pending";
|
|
870
|
+
other.turnId = turnId;
|
|
871
|
+
onIntermediate?.();
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (status === "completed" && task.status !== "in_progress" && task.status !== "completed") {
|
|
875
|
+
task.status = "in_progress";
|
|
876
|
+
task.turnId = turnId;
|
|
877
|
+
onIntermediate?.();
|
|
878
|
+
}
|
|
879
|
+
task.status = status;
|
|
880
|
+
} else if (status) {
|
|
881
|
+
task.status = status;
|
|
882
|
+
}
|
|
883
|
+
task.turnId = turnId;
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
810
886
|
|
|
811
887
|
// TaskList에는 예전 계획까지 함께 들어올 수 있다. 마지막으로 번호가 1부터
|
|
812
888
|
// 시작한 묶음만 현재 계획으로 삼되, 번호가 없는 목록은 손실 없이 그대로 둔다.
|
|
@@ -829,9 +905,9 @@ function updatePlanFromToolUse(session, name, toolUseId, input) {
|
|
|
829
905
|
status: "pending",
|
|
830
906
|
turnId,
|
|
831
907
|
});
|
|
832
|
-
} else if (name === "TaskUpdate") {
|
|
833
|
-
applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
|
|
834
|
-
}
|
|
908
|
+
} else if (name === "TaskUpdate") {
|
|
909
|
+
applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
|
|
910
|
+
}
|
|
835
911
|
emitPlan(session);
|
|
836
912
|
}
|
|
837
913
|
|
|
@@ -1335,7 +1411,7 @@ async function startPrompt(params) {
|
|
|
1335
1411
|
return runPrompt(session, params);
|
|
1336
1412
|
}
|
|
1337
1413
|
|
|
1338
|
-
async function runPrompt(session, params) {
|
|
1414
|
+
async function runPrompt(session, params) {
|
|
1339
1415
|
const id = session.id;
|
|
1340
1416
|
if (params.model) {
|
|
1341
1417
|
const model = stripClaudeModel(params.model);
|
|
@@ -1346,12 +1422,13 @@ async function runPrompt(session, params) {
|
|
|
1346
1422
|
if (effort) {
|
|
1347
1423
|
await session.query.applyFlagSettings({ effortLevel: effort });
|
|
1348
1424
|
}
|
|
1349
|
-
session.effort = effort;
|
|
1350
|
-
await applyPermissionMode(session, params.permissionMode);
|
|
1351
|
-
const
|
|
1352
|
-
session.
|
|
1353
|
-
|
|
1354
|
-
|
|
1425
|
+
session.effort = effort;
|
|
1426
|
+
await applyPermissionMode(session, params.permissionMode);
|
|
1427
|
+
const content = await inputContent(params.input, params.handoffContext);
|
|
1428
|
+
const turnId = beginTurn(session, params.input);
|
|
1429
|
+
session.queue.push({
|
|
1430
|
+
type: "user",
|
|
1431
|
+
message: { role: "user", content },
|
|
1355
1432
|
parent_tool_use_id: null,
|
|
1356
1433
|
session_id: id,
|
|
1357
1434
|
origin: { kind: "human" },
|
|
@@ -1361,9 +1438,16 @@ async function runPrompt(session, params) {
|
|
|
1361
1438
|
|
|
1362
1439
|
// A background task notification is an internal user message that starts its
|
|
1363
1440
|
// 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 = {
|
|
1441
|
+
function beginTurn(session, input = []) {
|
|
1442
|
+
const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
|
|
1443
|
+
session.turn = {
|
|
1444
|
+
id: turnId,
|
|
1445
|
+
sawStreamText: false,
|
|
1446
|
+
sawVisibleText: false,
|
|
1447
|
+
koreanRequest: isKoreanPrompt(input),
|
|
1448
|
+
openingNotice: openingNotice(input),
|
|
1449
|
+
openingNoticeEmitted: false,
|
|
1450
|
+
};
|
|
1367
1451
|
session.lastContextUsage = null;
|
|
1368
1452
|
notify("turn/started", { threadId: session.id, turn: { id: turnId } });
|
|
1369
1453
|
return turnId;
|
|
@@ -1717,33 +1801,33 @@ async function runSelfTest() {
|
|
|
1717
1801
|
}
|
|
1718
1802
|
applyTaskUpdate(restoredTasks, { taskId: "26", status: "completed" }, "resumed-turn");
|
|
1719
1803
|
applyTaskUpdate(restoredTasks, { taskId: "27", status: "in_progress" }, "resumed-turn");
|
|
1720
|
-
if (restoredTasks.size !== 6
|
|
1721
|
-
|| restoredTasks.get("25")?.status !== "completed"
|
|
1722
|
-
|| restoredTasks.get("26")?.status !== "completed"
|
|
1723
|
-
|| restoredTasks.get("27")?.status !== "in_progress"
|
|
1724
|
-
|| [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
|
|
1725
|
-
throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
|
|
1726
|
-
}
|
|
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" }],
|
|
1731
|
-
]);
|
|
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("|")}`);
|
|
1745
|
-
}
|
|
1746
|
-
const mixedPlans = new Map([
|
|
1804
|
+
if (restoredTasks.size !== 6
|
|
1805
|
+
|| restoredTasks.get("25")?.status !== "completed"
|
|
1806
|
+
|| restoredTasks.get("26")?.status !== "completed"
|
|
1807
|
+
|| restoredTasks.get("27")?.status !== "in_progress"
|
|
1808
|
+
|| [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
|
|
1809
|
+
throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
|
|
1810
|
+
}
|
|
1811
|
+
const skippedTasks = new Map([
|
|
1812
|
+
["1", { id: "1", subject: "1. 조사", status: "pending" }],
|
|
1813
|
+
["2", { id: "2", subject: "2. 분석", status: "pending" }],
|
|
1814
|
+
["3", { id: "3", subject: "3. 검증", status: "pending" }],
|
|
1815
|
+
]);
|
|
1816
|
+
const transitions = [];
|
|
1817
|
+
const snapshot = () => transitions.push([...skippedTasks.values()].map((task) => task.status).join(","));
|
|
1818
|
+
applyTaskUpdate(skippedTasks, { taskId: "3", status: "completed" }, "turn", snapshot);
|
|
1819
|
+
snapshot();
|
|
1820
|
+
if (transitions.join("|") !== [
|
|
1821
|
+
"in_progress,pending,pending",
|
|
1822
|
+
"completed,pending,pending",
|
|
1823
|
+
"completed,in_progress,pending",
|
|
1824
|
+
"completed,completed,pending",
|
|
1825
|
+
"completed,completed,in_progress",
|
|
1826
|
+
"completed,completed,completed",
|
|
1827
|
+
].join("|")) {
|
|
1828
|
+
throw new Error(`Claude skipped task transition self-test failed: ${transitions.join("|")}`);
|
|
1829
|
+
}
|
|
1830
|
+
const mixedPlans = new Map([
|
|
1747
1831
|
["old-1", { subject: "1. 이전 작업", status: "completed" }],
|
|
1748
1832
|
["old-2", { subject: "2. 이전 검증", status: "completed" }],
|
|
1749
1833
|
...[...restoredTasks],
|
|
@@ -1882,13 +1966,91 @@ async function runSelfTest() {
|
|
|
1882
1966
|
.filter(Boolean)
|
|
1883
1967
|
.map((line) => JSON.parse(line));
|
|
1884
1968
|
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
|
|
1969
|
+
if (!lifecycleMethods.includes("turn/subagents/updated")
|
|
1970
|
+
|| lifecycleMethods.filter((method) => method === "turn/started").length < 3
|
|
1971
|
+
|| !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
|
|
1972
|
+
&& event.params?.line?.kind === "error")) {
|
|
1973
|
+
throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
|
|
1974
|
+
}
|
|
1975
|
+
const openingSession = {
|
|
1976
|
+
id: "opening-self-test",
|
|
1977
|
+
model: "claude:default",
|
|
1978
|
+
models: [],
|
|
1979
|
+
turn: null,
|
|
1980
|
+
turnSequence: 1,
|
|
1981
|
+
itemSequence: 1,
|
|
1982
|
+
streamBlocks: new Map(),
|
|
1983
|
+
tools: new Map(),
|
|
1984
|
+
tasks: new Map(),
|
|
1985
|
+
subagents: new Map(),
|
|
1986
|
+
knownSubagents: new Map(),
|
|
1987
|
+
lastContextUsage: null,
|
|
1988
|
+
lastContextWindow: 0,
|
|
1989
|
+
};
|
|
1990
|
+
const openingCaptured = [];
|
|
1991
|
+
process.stdout.write = (chunk) => {
|
|
1992
|
+
openingCaptured.push(String(chunk));
|
|
1993
|
+
return true;
|
|
1994
|
+
};
|
|
1995
|
+
try {
|
|
1996
|
+
beginTurn(openingSession, [{ type: "text", text: "provider 메뉴를 수정해" }]);
|
|
1997
|
+
processAssistant(openingSession, {
|
|
1998
|
+
message: {
|
|
1999
|
+
content: [{ type: "tool_use", id: "read-1", name: "Read", input: { file_path: "src/main.rs" } }],
|
|
2000
|
+
},
|
|
2001
|
+
});
|
|
2002
|
+
} finally {
|
|
2003
|
+
process.stdout.write = stdoutWrite;
|
|
2004
|
+
}
|
|
2005
|
+
const openingEvents = openingCaptured
|
|
2006
|
+
.join("")
|
|
2007
|
+
.trim()
|
|
2008
|
+
.split("\n")
|
|
2009
|
+
.filter(Boolean)
|
|
2010
|
+
.map((line) => JSON.parse(line));
|
|
2011
|
+
const openingMessageIndex = openingEvents.findIndex((event) =>
|
|
2012
|
+
event.method === "item/completed"
|
|
2013
|
+
&& event.params?.item?.type === "agentMessage"
|
|
2014
|
+
&& event.params.item.text === "요청 내용을 확인하고 필요한 작업을 진행하겠습니다.");
|
|
2015
|
+
const openingToolIndex = openingEvents.findIndex((event) =>
|
|
2016
|
+
event.method === "item/started" && event.params?.item?.type === "dynamicToolCall");
|
|
2017
|
+
if (openingMessageIndex < 0 || openingToolIndex < 0 || openingMessageIndex > openingToolIndex) {
|
|
2018
|
+
throw new Error(`Claude opening notice order self-test failed: ${JSON.stringify(openingEvents)}`);
|
|
2019
|
+
}
|
|
2020
|
+
const languageCaptured = [];
|
|
2021
|
+
process.stdout.write = (chunk) => {
|
|
2022
|
+
languageCaptured.push(String(chunk));
|
|
2023
|
+
return true;
|
|
2024
|
+
};
|
|
2025
|
+
try {
|
|
2026
|
+
openingSession.streamBlocks.clear();
|
|
2027
|
+
await processStreamEvent(openingSession, {
|
|
2028
|
+
event: { type: "content_block_start", index: 0, content_block: { type: "text" } },
|
|
2029
|
+
});
|
|
2030
|
+
await processStreamEvent(openingSession, {
|
|
2031
|
+
event: { type: "content_block_delta", index: 0, delta: { text: "Now the tile view logic." } },
|
|
2032
|
+
});
|
|
2033
|
+
await processStreamEvent(openingSession, {
|
|
2034
|
+
event: { type: "content_block_stop", index: 0 },
|
|
2035
|
+
});
|
|
2036
|
+
} finally {
|
|
2037
|
+
process.stdout.write = stdoutWrite;
|
|
2038
|
+
}
|
|
2039
|
+
const languageEvents = languageCaptured
|
|
2040
|
+
.join("")
|
|
2041
|
+
.trim()
|
|
2042
|
+
.split("\n")
|
|
2043
|
+
.filter(Boolean)
|
|
2044
|
+
.map((line) => JSON.parse(line));
|
|
2045
|
+
const visibleLanguage = languageEvents
|
|
2046
|
+
.filter((event) => event.method === "item/agentMessage/delta")
|
|
2047
|
+
.map((event) => event.params?.delta || "")
|
|
2048
|
+
.join("");
|
|
2049
|
+
if (visibleLanguage !== "다음 부분을 이어서 확인하겠습니다."
|
|
2050
|
+
|| languageEvents.some((event) => JSON.stringify(event).includes("Now the tile view logic."))) {
|
|
2051
|
+
throw new Error(`Claude Korean progress normalization self-test failed: ${JSON.stringify(languageEvents)}`);
|
|
2052
|
+
}
|
|
2053
|
+
const smoothText = "Claude가 👨👩👧👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
|
|
1892
2054
|
const emitted = [];
|
|
1893
2055
|
const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
|
|
1894
2056
|
smooth.push(smoothText);
|