@blade-hq/agent-kit 1.0.14 → 1.0.16
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/dist/{SkillStatusBar-cYkuNAmR.d.ts → SkillStatusBar-DskumalP.d.ts} +19 -7
- package/dist/{blade-client-CGhtHnQN.d.ts → blade-client-KZiBuBHI.d.ts} +4 -1
- package/dist/{chunk-4K25SSZ4.js → chunk-J3PQU7BQ.js} +3 -3
- package/dist/{chunk-DXDLZXDP.js → chunk-JHUSL3JZ.js} +720 -626
- package/dist/chunk-JHUSL3JZ.js.map +1 -0
- package/dist/{chunk-CVCQSRVP.js → chunk-OMIVYL5D.js} +4 -4
- package/dist/chunk-OMIVYL5D.js.map +1 -0
- package/dist/{chunk-3UY7SD3P.js → chunk-QEYZZDLM.js} +1091 -11
- package/dist/chunk-QEYZZDLM.js.map +1 -0
- package/dist/{chunk-CM3H6ZNV.js → chunk-U4KVB5OB.js} +2 -2
- package/dist/{chunk-DN4CKDDQ.js → chunk-V74REX3Q.js} +2 -1
- package/dist/chunk-V74REX3Q.js.map +1 -0
- package/dist/client/index.d.ts +445 -2
- package/dist/client/index.js +1 -1
- package/dist/react/api/vibe-coding.d.ts +3 -3
- package/dist/react/api/vibe-coding.js +2 -2
- package/dist/react/components/chat/index.d.ts +4 -4
- package/dist/react/components/chat/index.js +7 -5
- package/dist/react/components/plan/index.js +4 -4
- package/dist/react/components/session/index.d.ts +1 -1
- package/dist/react/components/session/index.js +3 -3
- package/dist/react/components/workspace/index.d.ts +3 -3
- package/dist/react/components/workspace/index.js +3 -3
- package/dist/react/index.d.ts +22 -11
- package/dist/react/index.js +11 -6
- package/dist/react/index.js.map +1 -1
- package/dist/{session-o8bAQoXK.d.ts → session-CT8Y2KHr.d.ts} +2 -0
- package/dist/{sessions-BDau9Lk7.d.ts → sessions-BX8qHBN3.d.ts} +2 -2
- package/package.json +1 -1
- package/dist/chunk-3UY7SD3P.js.map +0 -1
- package/dist/chunk-CVCQSRVP.js.map +0 -1
- package/dist/chunk-DN4CKDDQ.js.map +0 -1
- package/dist/chunk-DXDLZXDP.js.map +0 -1
- /package/dist/{chunk-4K25SSZ4.js.map → chunk-J3PQU7BQ.js.map} +0 -0
- /package/dist/{chunk-CM3H6ZNV.js.map → chunk-U4KVB5OB.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-J3XVFPOV.js";
|
|
4
4
|
import {
|
|
5
5
|
BladeClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-V74REX3Q.js";
|
|
7
7
|
import {
|
|
8
8
|
createClientActions,
|
|
9
9
|
useCardStateStore
|
|
@@ -571,6 +571,1059 @@ function isUiMeta(value) {
|
|
|
571
571
|
return true;
|
|
572
572
|
}
|
|
573
573
|
|
|
574
|
+
// src/react/projection/helpers.ts
|
|
575
|
+
function upsertToolCall(state, toolCallId, toolName, displayName, args) {
|
|
576
|
+
if (!toolCallId) return;
|
|
577
|
+
let found = false;
|
|
578
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
579
|
+
if (state.toolCalls[i].id !== toolCallId) continue;
|
|
580
|
+
const tc = state.toolCalls[i];
|
|
581
|
+
state.toolCalls[i] = {
|
|
582
|
+
...tc,
|
|
583
|
+
tool_name: toolName || tc.tool_name,
|
|
584
|
+
display_name: displayName || tc.display_name,
|
|
585
|
+
arguments: args || tc.arguments
|
|
586
|
+
};
|
|
587
|
+
found = true;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
if (!found) {
|
|
591
|
+
state.toolCalls.push({
|
|
592
|
+
id: toolCallId,
|
|
593
|
+
tool_name: toolName,
|
|
594
|
+
display_name: displayName,
|
|
595
|
+
arguments: args,
|
|
596
|
+
status: "pending"
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
let blockFound = false;
|
|
600
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
601
|
+
const b = state.blocks[i];
|
|
602
|
+
if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
|
|
603
|
+
state.blocks[i] = {
|
|
604
|
+
...b,
|
|
605
|
+
content: args || b.content,
|
|
606
|
+
tool_name: toolName || b.tool_name,
|
|
607
|
+
display_name: displayName || b.display_name
|
|
608
|
+
};
|
|
609
|
+
blockFound = true;
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
if (!blockFound) {
|
|
613
|
+
state.blocks.push({
|
|
614
|
+
type: "tool_use",
|
|
615
|
+
content: args,
|
|
616
|
+
tool_call_id: toolCallId,
|
|
617
|
+
tool_name: toolName || null,
|
|
618
|
+
display_name: displayName || null
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function applyToolResult(state, toolCallId, result, status, durationMs, sourceLoop) {
|
|
623
|
+
let toolName = "";
|
|
624
|
+
let displayName = "";
|
|
625
|
+
let finalResult = result;
|
|
626
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
627
|
+
const tc = state.toolCalls[i];
|
|
628
|
+
if (tc.id !== toolCallId) continue;
|
|
629
|
+
if (typeof result === "string") {
|
|
630
|
+
finalResult = maybeEnrichAskUserResult(tc.tool_name, result, sourceLoop);
|
|
631
|
+
}
|
|
632
|
+
const update = { status, result: finalResult };
|
|
633
|
+
if (durationMs != null) update.duration_ms = durationMs;
|
|
634
|
+
state.toolCalls[i] = { ...tc, ...update };
|
|
635
|
+
toolName = tc.tool_name;
|
|
636
|
+
displayName = tc.display_name;
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
state.blocks.push({
|
|
640
|
+
type: "tool_result",
|
|
641
|
+
content: finalResult,
|
|
642
|
+
tool_call_id: toolCallId,
|
|
643
|
+
tool_name: toolName || null,
|
|
644
|
+
display_name: displayName || null
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
function appendTextBlock(state, blockType, content) {
|
|
648
|
+
if (content == null || content === "") return;
|
|
649
|
+
const text = String(content);
|
|
650
|
+
const lastBlock = state.blocks[state.blocks.length - 1];
|
|
651
|
+
if (lastBlock && lastBlock.type === blockType && typeof lastBlock.content === "string") {
|
|
652
|
+
state.blocks[state.blocks.length - 1] = {
|
|
653
|
+
...lastBlock,
|
|
654
|
+
content: lastBlock.content + text
|
|
655
|
+
};
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
state.blocks.push({ type: blockType, content: text });
|
|
659
|
+
}
|
|
660
|
+
function appendToolCallArguments(state, toolCallId, delta) {
|
|
661
|
+
let newArgs = "";
|
|
662
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
663
|
+
const tc = state.toolCalls[i];
|
|
664
|
+
if (tc.id === toolCallId) {
|
|
665
|
+
newArgs = tc.arguments + delta;
|
|
666
|
+
state.toolCalls[i] = { ...tc, arguments: newArgs };
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (!newArgs) return;
|
|
671
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
672
|
+
const b = state.blocks[i];
|
|
673
|
+
if (b.type === "tool_use" && b.tool_call_id === toolCallId) {
|
|
674
|
+
state.blocks[i] = { ...b, content: newArgs };
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function maybeEnrichAskUserResult(toolName, result, sourceLoop) {
|
|
680
|
+
if (!sourceLoop) return result;
|
|
681
|
+
const normalized = toolName.includes(":") ? toolName.split(":").pop() : toolName;
|
|
682
|
+
if (normalized !== "AskUserQuestion") return result;
|
|
683
|
+
try {
|
|
684
|
+
const payload = JSON.parse(result);
|
|
685
|
+
if (typeof payload !== "object" || payload === null) return result;
|
|
686
|
+
payload.source_loop = sourceLoop;
|
|
687
|
+
return JSON.stringify(payload);
|
|
688
|
+
} catch {
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function buildSystemNotificationTurn(sequence, turnId, loopId, notificationType, status, title, detail, metadata) {
|
|
693
|
+
return {
|
|
694
|
+
id: turnId,
|
|
695
|
+
sequence,
|
|
696
|
+
turn_id: turnId,
|
|
697
|
+
loop_id: loopId,
|
|
698
|
+
kind: "message",
|
|
699
|
+
role: "system",
|
|
700
|
+
status: "completed",
|
|
701
|
+
blocks: [
|
|
702
|
+
{
|
|
703
|
+
type: "system_notification",
|
|
704
|
+
content: {
|
|
705
|
+
notification_type: notificationType,
|
|
706
|
+
status,
|
|
707
|
+
title,
|
|
708
|
+
detail,
|
|
709
|
+
metadata
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
],
|
|
713
|
+
tool_calls: [],
|
|
714
|
+
model: null,
|
|
715
|
+
usage: null,
|
|
716
|
+
duration_ms: 0
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function buildSystemNotificationBlock(notificationType, status, title, detail, metadata) {
|
|
720
|
+
return {
|
|
721
|
+
type: "system_notification",
|
|
722
|
+
content: {
|
|
723
|
+
notification_type: notificationType,
|
|
724
|
+
status,
|
|
725
|
+
title,
|
|
726
|
+
detail,
|
|
727
|
+
metadata
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function buildMarkerTurn(sequence, turnId, loopId, blockType, content) {
|
|
732
|
+
return {
|
|
733
|
+
id: turnId,
|
|
734
|
+
sequence,
|
|
735
|
+
turn_id: turnId,
|
|
736
|
+
loop_id: loopId,
|
|
737
|
+
kind: "message",
|
|
738
|
+
role: "assistant",
|
|
739
|
+
status: "completed",
|
|
740
|
+
blocks: [{ type: blockType, content }],
|
|
741
|
+
tool_calls: [],
|
|
742
|
+
model: null,
|
|
743
|
+
usage: null,
|
|
744
|
+
duration_ms: 0
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
function buildCompactionTurn(sequence, compactionId, loopId, status, data) {
|
|
748
|
+
const tokensBefore = asInt(data.tokens_before);
|
|
749
|
+
const tokensAfter = asInt(data.tokens_after);
|
|
750
|
+
let savedRatio = null;
|
|
751
|
+
if (tokensBefore && tokensBefore > 0 && tokensAfter != null) {
|
|
752
|
+
savedRatio = 1 - tokensAfter / tokensBefore;
|
|
753
|
+
} else {
|
|
754
|
+
savedRatio = asFloat(data.saved_ratio);
|
|
755
|
+
}
|
|
756
|
+
let archivedCount = asInt(data.archived_count);
|
|
757
|
+
if (archivedCount == null && Array.isArray(data.archived_files)) {
|
|
758
|
+
archivedCount = data.archived_files.length;
|
|
759
|
+
}
|
|
760
|
+
const content = data.content;
|
|
761
|
+
const summaryFull = compactionSummaryFull(content);
|
|
762
|
+
const summaryPreview = summaryFull ? summaryFull.split("\n").map((l) => l.trim()).filter(Boolean).join(" ").slice(0, 200) : null;
|
|
763
|
+
return {
|
|
764
|
+
id: `compaction:${compactionId}`,
|
|
765
|
+
sequence,
|
|
766
|
+
turn_id: `compaction:${compactionId}`,
|
|
767
|
+
loop_id: loopId,
|
|
768
|
+
kind: "compaction",
|
|
769
|
+
role: "system",
|
|
770
|
+
status,
|
|
771
|
+
blocks: [],
|
|
772
|
+
tool_calls: [],
|
|
773
|
+
model: null,
|
|
774
|
+
usage: null,
|
|
775
|
+
duration_ms: 0,
|
|
776
|
+
compaction_id: compactionId,
|
|
777
|
+
summary_preview: summaryPreview,
|
|
778
|
+
summary_full: summaryFull,
|
|
779
|
+
archived_count: archivedCount,
|
|
780
|
+
tokens_before: tokensBefore,
|
|
781
|
+
tokens_after: tokensAfter,
|
|
782
|
+
saved_ratio: savedRatio,
|
|
783
|
+
trigger: asTrigger(data.trigger),
|
|
784
|
+
failure_reason: typeof data.reason === "string" && data.reason ? data.reason : null,
|
|
785
|
+
fallback_applied: typeof data.fallback_applied === "boolean" ? data.fallback_applied : null
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function compactionSummaryFull(content) {
|
|
789
|
+
const text = String(content ?? "");
|
|
790
|
+
if (!text) return null;
|
|
791
|
+
const cleaned = text.replace("<compaction-summary>", "").replace("</compaction-summary>", "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
792
|
+
return cleaned || null;
|
|
793
|
+
}
|
|
794
|
+
function asInt(value) {
|
|
795
|
+
if (typeof value === "boolean") return null;
|
|
796
|
+
if (typeof value === "number") return Math.trunc(value);
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
function asFloat(value) {
|
|
800
|
+
if (typeof value === "boolean") return null;
|
|
801
|
+
if (typeof value === "number") return value;
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
function asTrigger(value) {
|
|
805
|
+
if (value === "auto" || value === "manual" || value === "forced_retry") return value;
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
function setToolCallStatus(turn, toolCallId, status, result, pendingQuestionRef) {
|
|
809
|
+
if (!turn || !toolCallId) return;
|
|
810
|
+
const toolCalls = "toolCalls" in turn ? turn.toolCalls : turn.tool_calls;
|
|
811
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
812
|
+
if (toolCalls[i].id !== toolCallId) continue;
|
|
813
|
+
toolCalls[i] = {
|
|
814
|
+
...toolCalls[i],
|
|
815
|
+
status,
|
|
816
|
+
result,
|
|
817
|
+
pending_question_ref: pendingQuestionRef
|
|
818
|
+
};
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function applyAskUserPauseToTurn(turn, pauseToolData, toolCallId, childLoopName, description, displayNameResolver) {
|
|
823
|
+
if (!turn || !toolCallId) return;
|
|
824
|
+
const sourceLoop = childLoopName ? { loop_name: childLoopName, description } : null;
|
|
825
|
+
const toolName = String(pauseToolData.name ?? "AskUserQuestion");
|
|
826
|
+
const args = JSON.stringify(pauseToolData.arguments ?? {});
|
|
827
|
+
const result = askUserResultFromPausePayload(pauseToolData, sourceLoop);
|
|
828
|
+
const isTurnState = "toolCalls" in turn;
|
|
829
|
+
const toolCalls = isTurnState ? turn.toolCalls : turn.tool_calls;
|
|
830
|
+
const blocks = turn.blocks;
|
|
831
|
+
const displayName = displayNameResolver(toolName);
|
|
832
|
+
let found = false;
|
|
833
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
834
|
+
if (toolCalls[i].id !== toolCallId) continue;
|
|
835
|
+
toolCalls[i] = {
|
|
836
|
+
...toolCalls[i],
|
|
837
|
+
tool_name: toolName || toolCalls[i].tool_name,
|
|
838
|
+
display_name: displayName || toolCalls[i].display_name,
|
|
839
|
+
arguments: args || toolCalls[i].arguments
|
|
840
|
+
};
|
|
841
|
+
found = true;
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
if (!found) {
|
|
845
|
+
toolCalls.push({
|
|
846
|
+
id: toolCallId,
|
|
847
|
+
tool_name: toolName,
|
|
848
|
+
display_name: displayName,
|
|
849
|
+
arguments: args,
|
|
850
|
+
status: "pending"
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
let blockFound = false;
|
|
854
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
855
|
+
if (blocks[i].type !== "tool_use" || blocks[i].tool_call_id !== toolCallId) continue;
|
|
856
|
+
blocks[i] = { ...blocks[i], content: args, tool_name: toolName, display_name: displayName };
|
|
857
|
+
blockFound = true;
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
860
|
+
if (!blockFound) {
|
|
861
|
+
blocks.push({
|
|
862
|
+
type: "tool_use",
|
|
863
|
+
content: args,
|
|
864
|
+
tool_call_id: toolCallId,
|
|
865
|
+
tool_name: toolName,
|
|
866
|
+
display_name: displayName
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
setToolCallStatus(turn, toolCallId, "awaiting_answer", result, null);
|
|
870
|
+
let resultBlockFound = false;
|
|
871
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
872
|
+
if (blocks[i].type !== "tool_result" || blocks[i].tool_call_id !== toolCallId) continue;
|
|
873
|
+
blocks[i] = { ...blocks[i], content: result };
|
|
874
|
+
resultBlockFound = true;
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
if (!resultBlockFound) {
|
|
878
|
+
blocks.push({
|
|
879
|
+
type: "tool_result",
|
|
880
|
+
content: result,
|
|
881
|
+
tool_call_id: toolCallId,
|
|
882
|
+
tool_name: toolName,
|
|
883
|
+
display_name: displayName
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function applyChildPauseToParent(parentTurn, parentToolCallId, childLoopName, childToolCallId, description, displayNameResolver) {
|
|
888
|
+
if (!parentTurn || !parentToolCallId) return;
|
|
889
|
+
const toolCalls = "toolCalls" in parentTurn ? parentTurn.toolCalls : parentTurn.tool_calls;
|
|
890
|
+
const blocks = parentTurn.blocks;
|
|
891
|
+
const displayName = displayNameResolver("Agent");
|
|
892
|
+
let found = false;
|
|
893
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
894
|
+
if (toolCalls[i].id !== parentToolCallId) continue;
|
|
895
|
+
toolCalls[i] = {
|
|
896
|
+
...toolCalls[i],
|
|
897
|
+
tool_name: "Agent",
|
|
898
|
+
display_name: displayName || toolCalls[i].display_name
|
|
899
|
+
};
|
|
900
|
+
found = true;
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
if (!found) {
|
|
904
|
+
toolCalls.push({
|
|
905
|
+
id: parentToolCallId,
|
|
906
|
+
tool_name: "Agent",
|
|
907
|
+
display_name: displayName,
|
|
908
|
+
arguments: "",
|
|
909
|
+
status: "pending"
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
let blockFound = false;
|
|
913
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
914
|
+
if (blocks[i].type !== "tool_use" || blocks[i].tool_call_id !== parentToolCallId) continue;
|
|
915
|
+
blockFound = true;
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
if (!blockFound) {
|
|
919
|
+
blocks.push({
|
|
920
|
+
type: "tool_use",
|
|
921
|
+
content: "",
|
|
922
|
+
tool_call_id: parentToolCallId,
|
|
923
|
+
tool_name: "Agent",
|
|
924
|
+
display_name: displayName
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
setToolCallStatus(parentTurn, parentToolCallId, "awaiting_answer", null, {
|
|
928
|
+
child_loop_name: childLoopName,
|
|
929
|
+
child_tool_call_id: childToolCallId,
|
|
930
|
+
description: description || null
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
function askUserResultFromPausePayload(pauseToolData, sourceLoop) {
|
|
934
|
+
const args = typeof pauseToolData.arguments === "object" && pauseToolData.arguments !== null ? pauseToolData.arguments : {};
|
|
935
|
+
const payload = { questions: args.questions ?? [] };
|
|
936
|
+
if (sourceLoop) payload.source_loop = sourceLoop;
|
|
937
|
+
return JSON.stringify(payload);
|
|
938
|
+
}
|
|
939
|
+
function sourceLoopFor(loopId, loopDescriptions) {
|
|
940
|
+
if (loopId === "root") return null;
|
|
941
|
+
const description = loopDescriptions.get(loopId);
|
|
942
|
+
if (!description) return null;
|
|
943
|
+
return { loop_name: loopId, description };
|
|
944
|
+
}
|
|
945
|
+
function normalizeChildPause(pausePayload) {
|
|
946
|
+
const rawPtd = pausePayload.pause_tool_data;
|
|
947
|
+
const pauseToolData = typeof rawPtd === "object" && rawPtd !== null ? { ...rawPtd } : { ...pausePayload };
|
|
948
|
+
let sourceLoop = pauseToolData.source_loop;
|
|
949
|
+
if (typeof sourceLoop !== "object" || sourceLoop === null) {
|
|
950
|
+
sourceLoop = pausePayload.source_loop;
|
|
951
|
+
}
|
|
952
|
+
const childLoopName = String(pausePayload.child_loop_name ?? "") || (typeof sourceLoop === "object" && sourceLoop ? String(sourceLoop.name ?? "") : "");
|
|
953
|
+
const childToolCallId = String(
|
|
954
|
+
pausePayload.child_pause_tool_call_id ?? pauseToolData.tool_call_id ?? ""
|
|
955
|
+
);
|
|
956
|
+
const description = typeof sourceLoop === "object" && sourceLoop ? String(sourceLoop.description ?? "") : "";
|
|
957
|
+
if (childLoopName && description) {
|
|
958
|
+
pauseToolData.source_loop = { name: childLoopName, description };
|
|
959
|
+
}
|
|
960
|
+
return { pauseToolData, childLoopName, childToolCallId, description };
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// src/react/schemas/message.ts
|
|
964
|
+
function inferToolStatus(resultStr) {
|
|
965
|
+
if (resultStr.includes('"Cancelled by user"')) return "cancelled";
|
|
966
|
+
try {
|
|
967
|
+
const parsed = JSON.parse(resultStr);
|
|
968
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
969
|
+
if ("error" in parsed && parsed.error) return "error";
|
|
970
|
+
const nested = parsed.result;
|
|
971
|
+
if (typeof nested === "object" && nested !== null && "error" in nested && nested.error) {
|
|
972
|
+
return "error";
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
} catch {
|
|
976
|
+
}
|
|
977
|
+
return "done";
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/react/projection/state.ts
|
|
981
|
+
function createTurnState(turnId, loopId, opts = {}) {
|
|
982
|
+
return {
|
|
983
|
+
turnId,
|
|
984
|
+
loopId,
|
|
985
|
+
role: "assistant",
|
|
986
|
+
model: opts.model ?? null,
|
|
987
|
+
startedAt: Date.now(),
|
|
988
|
+
contextWindow: opts.contextWindow ?? 0,
|
|
989
|
+
blocks: [],
|
|
990
|
+
toolCalls: [],
|
|
991
|
+
usage: null,
|
|
992
|
+
memoryRefs: opts.memoryRefs ?? null,
|
|
993
|
+
parentForkToolCallId: null
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function snapshot(state, sequence, status) {
|
|
997
|
+
return {
|
|
998
|
+
id: state.turnId,
|
|
999
|
+
sequence,
|
|
1000
|
+
turn_id: state.turnId,
|
|
1001
|
+
loop_id: state.loopId,
|
|
1002
|
+
kind: "message",
|
|
1003
|
+
role: state.role,
|
|
1004
|
+
status,
|
|
1005
|
+
blocks: [...state.blocks],
|
|
1006
|
+
tool_calls: [...state.toolCalls],
|
|
1007
|
+
model: state.model,
|
|
1008
|
+
usage: state.usage ? { ...state.usage } : null,
|
|
1009
|
+
duration_ms: Math.max(0, Date.now() - state.startedAt),
|
|
1010
|
+
started_at: new Date(state.startedAt).toISOString(),
|
|
1011
|
+
context_window: state.contextWindow,
|
|
1012
|
+
memory_refs: state.memoryRefs,
|
|
1013
|
+
parent_fork_tool_call_id: state.parentForkToolCallId
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
var PAUSE_TOOL_NAMES = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
|
|
1017
|
+
function toolStatusFromResult(result) {
|
|
1018
|
+
const text = typeof result === "string" ? result : Array.isArray(result) ? result.filter(
|
|
1019
|
+
(p) => typeof p === "object" && p !== null && p.type === "text"
|
|
1020
|
+
).map((p) => p.text ?? "").join("\n") : String(result ?? "");
|
|
1021
|
+
return inferToolStatus(text);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/react/projection/builder.ts
|
|
1025
|
+
var ClientProjectionBuilder = class {
|
|
1026
|
+
activeTurns = /* @__PURE__ */ new Map();
|
|
1027
|
+
lastTurnIds = /* @__PURE__ */ new Map();
|
|
1028
|
+
latestTurns = /* @__PURE__ */ new Map();
|
|
1029
|
+
loopDescriptions = /* @__PURE__ */ new Map();
|
|
1030
|
+
pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1031
|
+
pendingMemoryRefs = /* @__PURE__ */ new Map();
|
|
1032
|
+
activeCompactions = /* @__PURE__ */ new Map();
|
|
1033
|
+
seq = 0;
|
|
1034
|
+
syntheticCounter = 0;
|
|
1035
|
+
displayNameResolver;
|
|
1036
|
+
constructor(displayNameResolver) {
|
|
1037
|
+
this.displayNameResolver = displayNameResolver ?? ((n) => n);
|
|
1038
|
+
}
|
|
1039
|
+
processBatch(events) {
|
|
1040
|
+
const lastUpsertByTurn = /* @__PURE__ */ new Map();
|
|
1041
|
+
const nonUpserts = [];
|
|
1042
|
+
for (const event of events) {
|
|
1043
|
+
const updates = this.processEvent(event);
|
|
1044
|
+
if (!updates) continue;
|
|
1045
|
+
for (const u of updates) {
|
|
1046
|
+
if (u.kind === "upsert") {
|
|
1047
|
+
lastUpsertByTurn.set(u.turn.turn_id, u);
|
|
1048
|
+
} else {
|
|
1049
|
+
nonUpserts.push(u);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return [...nonUpserts, ...lastUpsertByTurn.values()];
|
|
1054
|
+
}
|
|
1055
|
+
processEvent(event) {
|
|
1056
|
+
const payload = event.payload;
|
|
1057
|
+
const loopId = String(payload.loop_name ?? "root");
|
|
1058
|
+
switch (event.type) {
|
|
1059
|
+
case "memory:inject:done":
|
|
1060
|
+
return this.onMemoryInject(loopId, payload);
|
|
1061
|
+
case "memory:inject:none":
|
|
1062
|
+
return null;
|
|
1063
|
+
case "loop:turn":
|
|
1064
|
+
return this.onLoopTurn(loopId, payload);
|
|
1065
|
+
case "mode:change":
|
|
1066
|
+
return this.onModeChange(loopId, payload);
|
|
1067
|
+
case "plan:status:update":
|
|
1068
|
+
return this.onPlanStatusUpdate(loopId, payload);
|
|
1069
|
+
case "workspace:changed":
|
|
1070
|
+
return this.onWorkspaceChanged(loopId, payload);
|
|
1071
|
+
case "agent:start":
|
|
1072
|
+
return this.onAgentStart(loopId, payload);
|
|
1073
|
+
case "bg:started":
|
|
1074
|
+
return this.onBgStarted(loopId, payload);
|
|
1075
|
+
case "bg:tasks_completed":
|
|
1076
|
+
return this.onBgTasksCompleted(loopId, payload);
|
|
1077
|
+
case "loop:ask_user_answer":
|
|
1078
|
+
return this.onAskUserAnswer(loopId, payload);
|
|
1079
|
+
case "user:message":
|
|
1080
|
+
return this.onUserMessage(loopId, payload);
|
|
1081
|
+
}
|
|
1082
|
+
if (event.type.startsWith("compaction:"))
|
|
1083
|
+
return this.onCompaction(event.type, loopId, payload);
|
|
1084
|
+
const state = this.activeTurns.get(loopId);
|
|
1085
|
+
if (!state) {
|
|
1086
|
+
if (event.type === "chat:end") {
|
|
1087
|
+
this.pendingMemoryRefs.delete(loopId);
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
if (event.type === "agent:end") return this.onAgentEnd(null, loopId, payload);
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
switch (event.type) {
|
|
1094
|
+
case "llm:thinking:delta":
|
|
1095
|
+
return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
|
|
1096
|
+
case "llm:content:delta":
|
|
1097
|
+
return this.onDelta(state, "text", "", String(payload.content ?? ""));
|
|
1098
|
+
case "llm:tool_call:created":
|
|
1099
|
+
return this.onToolCallCreated(state, payload);
|
|
1100
|
+
case "llm:response:done":
|
|
1101
|
+
return this.onResponseDone(state, loopId, payload);
|
|
1102
|
+
case "tool:result:done":
|
|
1103
|
+
return this.onToolResultDone(state, loopId, payload);
|
|
1104
|
+
case "loop:tool_ui":
|
|
1105
|
+
return this.onToolUi(state, payload);
|
|
1106
|
+
case "loop:tool_bridge":
|
|
1107
|
+
return this.onToolBridge(state, payload);
|
|
1108
|
+
case "tool:approval:required":
|
|
1109
|
+
return this.onToolApprovalRequired(state, payload);
|
|
1110
|
+
case "loop:child_pause":
|
|
1111
|
+
return this.onChildPause(state, loopId, payload);
|
|
1112
|
+
case "chat:end":
|
|
1113
|
+
return this.onChatEnd(state, loopId, payload);
|
|
1114
|
+
case "agent:end":
|
|
1115
|
+
return this.onAgentEnd(state, loopId, payload);
|
|
1116
|
+
default:
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
reset() {
|
|
1121
|
+
this.activeTurns.clear();
|
|
1122
|
+
this.lastTurnIds.clear();
|
|
1123
|
+
this.latestTurns.clear();
|
|
1124
|
+
this.loopDescriptions.clear();
|
|
1125
|
+
this.pendingChildPauses.clear();
|
|
1126
|
+
this.pendingMemoryRefs.clear();
|
|
1127
|
+
this.activeCompactions.clear();
|
|
1128
|
+
this.seq = 0;
|
|
1129
|
+
this.syntheticCounter = 0;
|
|
1130
|
+
}
|
|
1131
|
+
seedSequence(maxSeq) {
|
|
1132
|
+
if (maxSeq > this.seq) {
|
|
1133
|
+
this.seq = maxSeq;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
// --- Event handlers ---
|
|
1137
|
+
onMemoryInject(loopId, payload) {
|
|
1138
|
+
const refs = payload.memory_refs;
|
|
1139
|
+
const turnId = String(payload.turn_id ?? "");
|
|
1140
|
+
if (Array.isArray(refs) && refs.length > 0) {
|
|
1141
|
+
this.pendingMemoryRefs.set(loopId, {
|
|
1142
|
+
turnId,
|
|
1143
|
+
refs: refs.map((r4) => ({
|
|
1144
|
+
id: Number(r4.id),
|
|
1145
|
+
content_preview: String(r4.content_preview ?? ""),
|
|
1146
|
+
skill_name: r4.skill_name != null ? String(r4.skill_name) : null
|
|
1147
|
+
}))
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
onLoopTurn(loopId, payload) {
|
|
1153
|
+
let turnId = String(payload.turn_id ?? this.nextTurnId(`turn:${loopId}`));
|
|
1154
|
+
let memoryRefs = null;
|
|
1155
|
+
const pending = this.pendingMemoryRefs.get(loopId);
|
|
1156
|
+
if (pending) {
|
|
1157
|
+
this.pendingMemoryRefs.delete(loopId);
|
|
1158
|
+
if (pending.turnId) turnId = pending.turnId;
|
|
1159
|
+
memoryRefs = pending.refs;
|
|
1160
|
+
}
|
|
1161
|
+
const rawCw = payload.context_window;
|
|
1162
|
+
const contextWindow = typeof rawCw === "number" && rawCw > 0 ? Math.trunc(rawCw) : 0;
|
|
1163
|
+
const state = createTurnState(turnId, loopId, {
|
|
1164
|
+
model: optStr(payload.model),
|
|
1165
|
+
contextWindow,
|
|
1166
|
+
memoryRefs
|
|
1167
|
+
});
|
|
1168
|
+
this.activeTurns.set(loopId, state);
|
|
1169
|
+
this.lastTurnIds.set(loopId, turnId);
|
|
1170
|
+
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1171
|
+
this.latestTurns.set(loopId, turn);
|
|
1172
|
+
return [{ kind: "upsert", turn }];
|
|
1173
|
+
}
|
|
1174
|
+
onModeChange(loopId, payload) {
|
|
1175
|
+
const content = {};
|
|
1176
|
+
if (payload.from != null && payload.from !== "") content.from = String(payload.from);
|
|
1177
|
+
if (payload.to != null && payload.to !== "") content.to = String(payload.to);
|
|
1178
|
+
const turn = buildMarkerTurn(
|
|
1179
|
+
this.nextSeq(),
|
|
1180
|
+
this.nextTurnId("mode_change"),
|
|
1181
|
+
loopId,
|
|
1182
|
+
"mode_change",
|
|
1183
|
+
content
|
|
1184
|
+
);
|
|
1185
|
+
return [{ kind: "upsert", turn }];
|
|
1186
|
+
}
|
|
1187
|
+
onPlanStatusUpdate(loopId, payload) {
|
|
1188
|
+
let statuses = payload.statuses;
|
|
1189
|
+
if (typeof statuses !== "object" || statuses === null) {
|
|
1190
|
+
statuses = payload._statuses ?? {};
|
|
1191
|
+
}
|
|
1192
|
+
const turn = buildMarkerTurn(
|
|
1193
|
+
this.nextSeq(),
|
|
1194
|
+
this.nextTurnId("plan_status"),
|
|
1195
|
+
loopId,
|
|
1196
|
+
"plan_status",
|
|
1197
|
+
{ plan_yaml: payload.plan_yaml ?? "", statuses }
|
|
1198
|
+
);
|
|
1199
|
+
return [{ kind: "upsert", turn }];
|
|
1200
|
+
}
|
|
1201
|
+
onWorkspaceChanged(loopId, payload) {
|
|
1202
|
+
const updates = [
|
|
1203
|
+
{
|
|
1204
|
+
kind: "workspace_changed",
|
|
1205
|
+
loopId,
|
|
1206
|
+
toolCallId: optStr(payload.tool_call_id) ?? void 0,
|
|
1207
|
+
toolName: toolNameFromPayload(payload) ?? void 0
|
|
1208
|
+
}
|
|
1209
|
+
];
|
|
1210
|
+
const state = this.activeTurns.get(loopId);
|
|
1211
|
+
if (state) {
|
|
1212
|
+
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1213
|
+
this.latestTurns.set(loopId, turn);
|
|
1214
|
+
this.lastTurnIds.set(loopId, turn.turn_id);
|
|
1215
|
+
updates.push({ kind: "upsert", turn });
|
|
1216
|
+
}
|
|
1217
|
+
return updates;
|
|
1218
|
+
}
|
|
1219
|
+
onAgentStart(loopId, payload) {
|
|
1220
|
+
const description = optStr(payload.description);
|
|
1221
|
+
if (description) this.loopDescriptions.set(loopId, description);
|
|
1222
|
+
const turn = buildSystemNotificationTurn(
|
|
1223
|
+
this.nextSeq(),
|
|
1224
|
+
this.nextTurnId("agent:start"),
|
|
1225
|
+
loopId,
|
|
1226
|
+
"agent:start",
|
|
1227
|
+
"running",
|
|
1228
|
+
`Agent: ${payload.description ?? payload.skill_id ?? "agent"}`,
|
|
1229
|
+
optStr(payload.prompt),
|
|
1230
|
+
{
|
|
1231
|
+
skill_id: optStr(payload.skill_id),
|
|
1232
|
+
description: optStr(payload.description),
|
|
1233
|
+
parent_fork_tool_call_id: optStr(payload.parent_fork_tool_call_id)
|
|
1234
|
+
}
|
|
1235
|
+
);
|
|
1236
|
+
return [{ kind: "upsert", turn }];
|
|
1237
|
+
}
|
|
1238
|
+
onBgStarted(loopId, payload) {
|
|
1239
|
+
const taskId = optStr(payload.task_id) ?? "";
|
|
1240
|
+
const command = optStr(payload.command);
|
|
1241
|
+
const description = optStr(payload.description);
|
|
1242
|
+
const turn = buildSystemNotificationTurn(
|
|
1243
|
+
this.nextSeq(),
|
|
1244
|
+
this.nextTurnId("bg:started"),
|
|
1245
|
+
loopId,
|
|
1246
|
+
"bg:started",
|
|
1247
|
+
"running",
|
|
1248
|
+
description ?? `Background: ${taskId || "task"}`,
|
|
1249
|
+
command,
|
|
1250
|
+
{ task_id: taskId, command, description }
|
|
1251
|
+
);
|
|
1252
|
+
return [{ kind: "upsert", turn }];
|
|
1253
|
+
}
|
|
1254
|
+
onBgTasksCompleted(loopId, payload) {
|
|
1255
|
+
const taskIds = Array.isArray(payload.task_ids) ? payload.task_ids : [];
|
|
1256
|
+
const count = typeof payload.count === "number" ? payload.count : taskIds.length;
|
|
1257
|
+
const detail = taskIds.map(String).join(", ");
|
|
1258
|
+
const turn = buildSystemNotificationTurn(
|
|
1259
|
+
this.nextSeq(),
|
|
1260
|
+
this.nextTurnId("bg:tasks_completed"),
|
|
1261
|
+
loopId,
|
|
1262
|
+
"bg:tasks_completed",
|
|
1263
|
+
"done",
|
|
1264
|
+
`Background tasks completed (${count})`,
|
|
1265
|
+
detail || null,
|
|
1266
|
+
{ task_ids: taskIds, count }
|
|
1267
|
+
);
|
|
1268
|
+
return [{ kind: "upsert", turn }];
|
|
1269
|
+
}
|
|
1270
|
+
onAskUserAnswer(loopId, payload) {
|
|
1271
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1272
|
+
const pending = this.pendingChildPauses.get(toolCallId);
|
|
1273
|
+
const parentLoopId = pending?.parentLoopName;
|
|
1274
|
+
if (pending) {
|
|
1275
|
+
this.pendingChildPauses.delete(toolCallId);
|
|
1276
|
+
const childTurn = this.activeTurns.get(pending.childLoopName) ?? this.latestTurns.get(pending.childLoopName);
|
|
1277
|
+
setToolCallStatus(childTurn, pending.childToolCallId, "done", null, null);
|
|
1278
|
+
const parentTurn = this.activeTurns.get(pending.parentLoopName) ?? this.latestTurns.get(pending.parentLoopName);
|
|
1279
|
+
setToolCallStatus(parentTurn, pending.parentToolCallId, "done", null, null);
|
|
1280
|
+
}
|
|
1281
|
+
const directTurn = this.activeTurns.get(loopId) ?? this.latestTurns.get(loopId);
|
|
1282
|
+
setToolCallStatus(directTurn, toolCallId, "done", null, null);
|
|
1283
|
+
return this.collectLoopUpdates(loopId, parentLoopId);
|
|
1284
|
+
}
|
|
1285
|
+
onUserMessage(_loopId, payload) {
|
|
1286
|
+
const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
|
|
1287
|
+
const displayContent = payload.display_content ?? payload.content ?? "";
|
|
1288
|
+
const turn = {
|
|
1289
|
+
id: entryId,
|
|
1290
|
+
sequence: this.nextSeq(),
|
|
1291
|
+
turn_id: entryId,
|
|
1292
|
+
loop_id: "root",
|
|
1293
|
+
kind: "message",
|
|
1294
|
+
role: "user",
|
|
1295
|
+
status: "completed",
|
|
1296
|
+
blocks: [{ type: "text", content: displayContent }],
|
|
1297
|
+
tool_calls: [],
|
|
1298
|
+
model: null,
|
|
1299
|
+
usage: null,
|
|
1300
|
+
duration_ms: 0
|
|
1301
|
+
};
|
|
1302
|
+
return [{ kind: "upsert", turn }];
|
|
1303
|
+
}
|
|
1304
|
+
// --- Delta / streaming events ---
|
|
1305
|
+
onDelta(state, blockType, _toolCallId, content) {
|
|
1306
|
+
if (!content) return null;
|
|
1307
|
+
appendTextBlock(state, blockType, content);
|
|
1308
|
+
return this.syncPatch(state);
|
|
1309
|
+
}
|
|
1310
|
+
onToolCallCreated(state, payload) {
|
|
1311
|
+
if ("arguments_delta" in payload) {
|
|
1312
|
+
const delta = String(payload.arguments_delta ?? "");
|
|
1313
|
+
const toolCallId2 = String(payload.id ?? "");
|
|
1314
|
+
if (delta && toolCallId2) {
|
|
1315
|
+
appendToolCallArguments(state, toolCallId2, delta);
|
|
1316
|
+
return this.syncPatch(state);
|
|
1317
|
+
}
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
const toolCallId = String(payload.id ?? "");
|
|
1321
|
+
const toolName = toolNameFromPayload(payload);
|
|
1322
|
+
const displayName = String(payload.display_name ?? "") || this.displayNameResolver(toolName);
|
|
1323
|
+
const args = toolArgsFromPayload(payload);
|
|
1324
|
+
upsertToolCall(state, toolCallId, toolName, displayName, args);
|
|
1325
|
+
return this.syncPatch(state);
|
|
1326
|
+
}
|
|
1327
|
+
onResponseDone(state, loopId, payload) {
|
|
1328
|
+
const model = optStr(payload.model);
|
|
1329
|
+
if (model) state.model = model;
|
|
1330
|
+
if (typeof payload.usage === "object" && payload.usage !== null) {
|
|
1331
|
+
state.usage = { ...payload.usage };
|
|
1332
|
+
}
|
|
1333
|
+
const toolCalls = payload.tool_calls;
|
|
1334
|
+
if (Array.isArray(toolCalls)) {
|
|
1335
|
+
for (const raw of toolCalls) {
|
|
1336
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
1337
|
+
const tc = raw;
|
|
1338
|
+
const fn = tc.function ?? {};
|
|
1339
|
+
const toolName = String(fn.name ?? "");
|
|
1340
|
+
const displayName = String(tc.display_name ?? "") || this.displayNameResolver(toolName);
|
|
1341
|
+
upsertToolCall(
|
|
1342
|
+
state,
|
|
1343
|
+
String(tc.id ?? ""),
|
|
1344
|
+
toolName,
|
|
1345
|
+
displayName,
|
|
1346
|
+
String(fn.arguments ?? "")
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (state.toolCalls.length === 0) {
|
|
1351
|
+
return this.finalize(loopId, "completed");
|
|
1352
|
+
}
|
|
1353
|
+
return this.syncPatch(state);
|
|
1354
|
+
}
|
|
1355
|
+
onToolResultDone(state, loopId, payload) {
|
|
1356
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1357
|
+
const content = payload.content ?? "";
|
|
1358
|
+
const status = toolStatusFromResult(content);
|
|
1359
|
+
const rawDuration = payload.duration_ms;
|
|
1360
|
+
const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
|
|
1361
|
+
const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
|
|
1362
|
+
applyToolResult(state, toolCallId, content, status, durationMs, sl);
|
|
1363
|
+
if (this.shouldFinalizeAfterToolResults(state)) {
|
|
1364
|
+
return this.finalize(loopId, "completed");
|
|
1365
|
+
}
|
|
1366
|
+
return this.syncPatch(state);
|
|
1367
|
+
}
|
|
1368
|
+
onToolUi(state, payload) {
|
|
1369
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1370
|
+
const ui = payload.ui;
|
|
1371
|
+
if (toolCallId && typeof ui === "object" && ui !== null) {
|
|
1372
|
+
state.blocks.push({
|
|
1373
|
+
type: "tool_ui",
|
|
1374
|
+
content: ui,
|
|
1375
|
+
tool_call_id: toolCallId
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
return this.syncPatch(state);
|
|
1379
|
+
}
|
|
1380
|
+
onToolBridge(state, payload) {
|
|
1381
|
+
const bridge = payload.bridge;
|
|
1382
|
+
if (typeof bridge !== "object" || bridge === null) return null;
|
|
1383
|
+
state.blocks.push({
|
|
1384
|
+
type: "tool_bridge",
|
|
1385
|
+
content: bridge,
|
|
1386
|
+
tool_call_id: String(payload.tool_call_id ?? "") || null
|
|
1387
|
+
});
|
|
1388
|
+
return this.syncPatch(state);
|
|
1389
|
+
}
|
|
1390
|
+
onToolApprovalRequired(state, payload) {
|
|
1391
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1392
|
+
const toolName = String(payload.tool_name ?? "");
|
|
1393
|
+
const displayName = this.displayNameResolver(toolName);
|
|
1394
|
+
upsertToolCall(state, toolCallId, toolName, displayName, "");
|
|
1395
|
+
return this.syncPatch(state);
|
|
1396
|
+
}
|
|
1397
|
+
onChildPause(state, loopId, payload) {
|
|
1398
|
+
const pauseToolData = payload.pause_tool_data;
|
|
1399
|
+
if (typeof pauseToolData !== "object" || pauseToolData === null)
|
|
1400
|
+
return this.syncPatch(state);
|
|
1401
|
+
const ptd = pauseToolData;
|
|
1402
|
+
const sourceLoop = ptd.source_loop;
|
|
1403
|
+
if (typeof sourceLoop === "object" && sourceLoop) {
|
|
1404
|
+
const slName = String(sourceLoop.name ?? "");
|
|
1405
|
+
const slDesc = optStr(sourceLoop.description);
|
|
1406
|
+
if (slName && slDesc) this.loopDescriptions.set(slName, slDesc);
|
|
1407
|
+
}
|
|
1408
|
+
const { childLoopName, childToolCallId, description } = normalizeChildPause(payload);
|
|
1409
|
+
if (!childToolCallId) return this.syncPatch(state);
|
|
1410
|
+
const existing = this.pendingChildPauses.get(childToolCallId);
|
|
1411
|
+
const parentLoopName = String(
|
|
1412
|
+
payload.parent_loop_name ?? existing?.parentLoopName ?? "root"
|
|
1413
|
+
);
|
|
1414
|
+
const parentToolCallId = String(
|
|
1415
|
+
payload.parent_fork_tool_call_id ?? existing?.parentToolCallId ?? ""
|
|
1416
|
+
);
|
|
1417
|
+
const resolvedChildLoopName = String(payload.child_loop_name ?? "") || existing?.childLoopName || childLoopName || loopId;
|
|
1418
|
+
this.pendingChildPauses.set(childToolCallId, {
|
|
1419
|
+
parentLoopName,
|
|
1420
|
+
parentToolCallId,
|
|
1421
|
+
childLoopName: resolvedChildLoopName,
|
|
1422
|
+
childToolCallId,
|
|
1423
|
+
description: description || existing?.description || ""
|
|
1424
|
+
});
|
|
1425
|
+
applyAskUserPauseToTurn(
|
|
1426
|
+
state,
|
|
1427
|
+
ptd,
|
|
1428
|
+
childToolCallId,
|
|
1429
|
+
resolvedChildLoopName,
|
|
1430
|
+
this.pendingChildPauses.get(childToolCallId).description,
|
|
1431
|
+
this.displayNameResolver
|
|
1432
|
+
);
|
|
1433
|
+
const parentTurn = this.activeTurns.get(parentLoopName) ?? this.latestTurns.get(parentLoopName);
|
|
1434
|
+
if (parentTurn) {
|
|
1435
|
+
applyChildPauseToParent(
|
|
1436
|
+
parentTurn,
|
|
1437
|
+
parentToolCallId,
|
|
1438
|
+
resolvedChildLoopName,
|
|
1439
|
+
childToolCallId,
|
|
1440
|
+
this.pendingChildPauses.get(childToolCallId).description,
|
|
1441
|
+
this.displayNameResolver
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
return this.collectLoopUpdates(loopId, parentLoopName);
|
|
1445
|
+
}
|
|
1446
|
+
onChatEnd(state, loopId, payload) {
|
|
1447
|
+
const status = String(payload.status ?? "completed");
|
|
1448
|
+
const pauseToolData = payload.pause_tool_data;
|
|
1449
|
+
if (typeof pauseToolData === "object" && pauseToolData !== null) {
|
|
1450
|
+
const ptd = pauseToolData;
|
|
1451
|
+
const pauseToolName = String(ptd.name ?? "");
|
|
1452
|
+
const normalized = pauseToolName.includes(":") ? pauseToolName.split(":").pop() : pauseToolName;
|
|
1453
|
+
const sourceLoop = ptd.source_loop;
|
|
1454
|
+
if (typeof sourceLoop === "object" && sourceLoop) {
|
|
1455
|
+
const slName = String(sourceLoop.name ?? "");
|
|
1456
|
+
const slDesc = optStr(sourceLoop.description);
|
|
1457
|
+
if (slName && slDesc) this.loopDescriptions.set(slName, slDesc);
|
|
1458
|
+
if (status === "paused" && slName) {
|
|
1459
|
+
applyChildPauseToParent(
|
|
1460
|
+
state,
|
|
1461
|
+
String(ptd.parent_fork_tool_call_id ?? ""),
|
|
1462
|
+
slName,
|
|
1463
|
+
String(ptd.tool_call_id ?? ""),
|
|
1464
|
+
slDesc ?? "",
|
|
1465
|
+
this.displayNameResolver
|
|
1466
|
+
);
|
|
1467
|
+
const childTurn = this.activeTurns.get(slName);
|
|
1468
|
+
if (childTurn) {
|
|
1469
|
+
applyAskUserPauseToTurn(
|
|
1470
|
+
childTurn,
|
|
1471
|
+
ptd,
|
|
1472
|
+
String(ptd.tool_call_id ?? ""),
|
|
1473
|
+
slName,
|
|
1474
|
+
slDesc ?? "",
|
|
1475
|
+
this.displayNameResolver
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
} else if (status === "paused" && normalized === "AskUserQuestion") {
|
|
1480
|
+
applyAskUserPauseToTurn(
|
|
1481
|
+
state,
|
|
1482
|
+
ptd,
|
|
1483
|
+
String(ptd.tool_call_id ?? ""),
|
|
1484
|
+
"",
|
|
1485
|
+
"",
|
|
1486
|
+
this.displayNameResolver
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
const finalStatus = { completed: "completed", paused: "paused", failed: "failed", interrupted: "interrupted" }[status];
|
|
1491
|
+
if (!finalStatus) return null;
|
|
1492
|
+
if (!this.activeTurns.has(loopId)) {
|
|
1493
|
+
this.pendingMemoryRefs.delete(loopId);
|
|
1494
|
+
}
|
|
1495
|
+
return this.finalize(loopId, finalStatus);
|
|
1496
|
+
}
|
|
1497
|
+
onAgentEnd(state, loopId, payload) {
|
|
1498
|
+
const ok = payload.ok !== false;
|
|
1499
|
+
const status = ok ? "done" : "error";
|
|
1500
|
+
const title = `Agent: ${payload.description ?? payload.skill_id ?? "agent"}`;
|
|
1501
|
+
const detail = optStr(payload.error);
|
|
1502
|
+
const metadata = {
|
|
1503
|
+
skill_id: optStr(payload.skill_id),
|
|
1504
|
+
description: optStr(payload.description),
|
|
1505
|
+
parent_fork_tool_call_id: optStr(payload.parent_fork_tool_call_id),
|
|
1506
|
+
ok
|
|
1507
|
+
};
|
|
1508
|
+
if (state) {
|
|
1509
|
+
state.blocks.push(buildSystemNotificationBlock("agent:end", status, title, detail, metadata));
|
|
1510
|
+
return this.finalize(loopId, ok ? "completed" : "failed");
|
|
1511
|
+
}
|
|
1512
|
+
const turn = buildSystemNotificationTurn(
|
|
1513
|
+
this.nextSeq(),
|
|
1514
|
+
this.nextTurnId("agent:end"),
|
|
1515
|
+
loopId,
|
|
1516
|
+
"agent:end",
|
|
1517
|
+
status,
|
|
1518
|
+
title,
|
|
1519
|
+
detail,
|
|
1520
|
+
metadata
|
|
1521
|
+
);
|
|
1522
|
+
return [{ kind: "upsert", turn }];
|
|
1523
|
+
}
|
|
1524
|
+
onCompaction(eventType, loopId, payload) {
|
|
1525
|
+
const compactionId = String(payload.compaction_id ?? "");
|
|
1526
|
+
if (!compactionId) return null;
|
|
1527
|
+
const current = {
|
|
1528
|
+
...this.activeCompactions.get(compactionId) ?? {},
|
|
1529
|
+
...payload,
|
|
1530
|
+
loop_id: loopId
|
|
1531
|
+
};
|
|
1532
|
+
let status;
|
|
1533
|
+
switch (eventType) {
|
|
1534
|
+
case "compaction:start":
|
|
1535
|
+
status = "streaming";
|
|
1536
|
+
this.activeCompactions.set(compactionId, current);
|
|
1537
|
+
break;
|
|
1538
|
+
case "compaction:end":
|
|
1539
|
+
status = "completed";
|
|
1540
|
+
this.activeCompactions.delete(compactionId);
|
|
1541
|
+
break;
|
|
1542
|
+
case "compaction:abort":
|
|
1543
|
+
status = "interrupted";
|
|
1544
|
+
this.activeCompactions.delete(compactionId);
|
|
1545
|
+
break;
|
|
1546
|
+
case "compaction:error":
|
|
1547
|
+
status = "failed";
|
|
1548
|
+
this.activeCompactions.delete(compactionId);
|
|
1549
|
+
break;
|
|
1550
|
+
default:
|
|
1551
|
+
status = "streaming";
|
|
1552
|
+
this.activeCompactions.set(compactionId, current);
|
|
1553
|
+
}
|
|
1554
|
+
const turn = buildCompactionTurn(this.nextSeq(), compactionId, loopId, status, current);
|
|
1555
|
+
return [{ kind: "upsert", turn }];
|
|
1556
|
+
}
|
|
1557
|
+
// --- Internal helpers ---
|
|
1558
|
+
syncPatch(state) {
|
|
1559
|
+
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1560
|
+
this.latestTurns.set(state.loopId, turn);
|
|
1561
|
+
this.lastTurnIds.set(state.loopId, turn.turn_id);
|
|
1562
|
+
return [{ kind: "upsert", turn }];
|
|
1563
|
+
}
|
|
1564
|
+
finalize(loopId, status) {
|
|
1565
|
+
const state = this.activeTurns.get(loopId);
|
|
1566
|
+
if (!state) return null;
|
|
1567
|
+
this.activeTurns.delete(loopId);
|
|
1568
|
+
const turn = snapshot(state, this.nextSeq(), status);
|
|
1569
|
+
this.lastTurnIds.set(loopId, turn.turn_id);
|
|
1570
|
+
this.latestTurns.set(loopId, turn);
|
|
1571
|
+
return [{ kind: "upsert", turn }];
|
|
1572
|
+
}
|
|
1573
|
+
shouldFinalizeAfterToolResults(state) {
|
|
1574
|
+
if (state.toolCalls.length === 0) return true;
|
|
1575
|
+
if (state.toolCalls.some((tc) => tc.status === "pending")) return false;
|
|
1576
|
+
return !state.toolCalls.some((tc) => {
|
|
1577
|
+
const normalized = tc.tool_name.includes(":") ? tc.tool_name.split(":").pop() : tc.tool_name;
|
|
1578
|
+
return PAUSE_TOOL_NAMES.has(normalized);
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
collectLoopUpdates(...loopIds) {
|
|
1582
|
+
const updates = [];
|
|
1583
|
+
for (const loopId of loopIds) {
|
|
1584
|
+
if (!loopId) continue;
|
|
1585
|
+
const state = this.activeTurns.get(loopId);
|
|
1586
|
+
if (state) {
|
|
1587
|
+
updates.push(...this.syncPatch(state));
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
const proj = this.latestTurns.get(loopId);
|
|
1591
|
+
if (proj) {
|
|
1592
|
+
const updated = { ...proj, sequence: this.nextSeq() };
|
|
1593
|
+
this.latestTurns.set(loopId, updated);
|
|
1594
|
+
updates.push({ kind: "upsert", turn: updated });
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return updates;
|
|
1598
|
+
}
|
|
1599
|
+
nextSeq() {
|
|
1600
|
+
return ++this.seq;
|
|
1601
|
+
}
|
|
1602
|
+
nextTurnId(prefix) {
|
|
1603
|
+
this.syntheticCounter++;
|
|
1604
|
+
const hex = Math.random().toString(16).slice(2, 10);
|
|
1605
|
+
return `${prefix}:${this.syntheticCounter}:${hex}`;
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
function optStr(value) {
|
|
1609
|
+
if (value == null || value === "") return null;
|
|
1610
|
+
return String(value);
|
|
1611
|
+
}
|
|
1612
|
+
function toolNameFromPayload(payload) {
|
|
1613
|
+
const fn = payload.function;
|
|
1614
|
+
if (typeof fn === "object" && fn !== null) {
|
|
1615
|
+
return String(fn.name ?? "");
|
|
1616
|
+
}
|
|
1617
|
+
return String(payload.tool_name ?? "");
|
|
1618
|
+
}
|
|
1619
|
+
function toolArgsFromPayload(payload) {
|
|
1620
|
+
const fn = payload.function;
|
|
1621
|
+
if (typeof fn === "object" && fn !== null) {
|
|
1622
|
+
return String(fn.arguments ?? "");
|
|
1623
|
+
}
|
|
1624
|
+
return "";
|
|
1625
|
+
}
|
|
1626
|
+
|
|
574
1627
|
// src/react/stores/auth-store.ts
|
|
575
1628
|
import { create as create6 } from "zustand";
|
|
576
1629
|
import { createJSONStorage, persist } from "zustand/middleware";
|
|
@@ -1624,7 +2677,7 @@ var useSessionStore = create5()((set, get) => ({
|
|
|
1624
2677
|
rewindDrafts: {},
|
|
1625
2678
|
errorMessages: {},
|
|
1626
2679
|
_freshSessions: /* @__PURE__ */ new Set(),
|
|
1627
|
-
|
|
2680
|
+
fetchAllSessions: async () => {
|
|
1628
2681
|
set({ loading: true });
|
|
1629
2682
|
try {
|
|
1630
2683
|
const currentState = get();
|
|
@@ -1692,7 +2745,7 @@ var useSessionStore = create5()((set, get) => ({
|
|
|
1692
2745
|
});
|
|
1693
2746
|
get().setActiveSession(session_id);
|
|
1694
2747
|
invalidateHomeSidebarSessions();
|
|
1695
|
-
get().
|
|
2748
|
+
get().fetchAllSessions().catch(() => {
|
|
1696
2749
|
});
|
|
1697
2750
|
return session_id;
|
|
1698
2751
|
},
|
|
@@ -1762,7 +2815,7 @@ var useSessionStore = create5()((set, get) => ({
|
|
|
1762
2815
|
await deleteSession(id);
|
|
1763
2816
|
invalidateHomeSidebarSessions();
|
|
1764
2817
|
const wasActive = get().activeSessionId === id;
|
|
1765
|
-
await get().
|
|
2818
|
+
await get().fetchAllSessions();
|
|
1766
2819
|
if (!wasActive) {
|
|
1767
2820
|
return;
|
|
1768
2821
|
}
|
|
@@ -1907,8 +2960,6 @@ function finishAuth(set, payload) {
|
|
|
1907
2960
|
error: null
|
|
1908
2961
|
});
|
|
1909
2962
|
agentSocket?.reconnect();
|
|
1910
|
-
useSessionStore.getState().fetchSessions().catch(() => {
|
|
1911
|
-
});
|
|
1912
2963
|
}
|
|
1913
2964
|
function finishCookieHydration(set, payload) {
|
|
1914
2965
|
set({
|
|
@@ -1919,8 +2970,6 @@ function finishCookieHydration(set, payload) {
|
|
|
1919
2970
|
error: null
|
|
1920
2971
|
});
|
|
1921
2972
|
agentSocket?.reconnect();
|
|
1922
|
-
useSessionStore.getState().fetchSessions().catch(() => {
|
|
1923
|
-
});
|
|
1924
2973
|
}
|
|
1925
2974
|
function toUser(info) {
|
|
1926
2975
|
return {
|
|
@@ -2393,6 +3442,7 @@ var AgentSocket = class {
|
|
|
2393
3442
|
boardProjectRefCounts = /* @__PURE__ */ new Map();
|
|
2394
3443
|
patchFlushHandle = null;
|
|
2395
3444
|
patchFlushCancel = null;
|
|
3445
|
+
projectionBuilder = new ClientProjectionBuilder();
|
|
2396
3446
|
_ensureConnected() {
|
|
2397
3447
|
if (this.socket.connected || this.socket.active) {
|
|
2398
3448
|
return;
|
|
@@ -2499,6 +3549,33 @@ var AgentSocket = class {
|
|
|
2499
3549
|
if (!sessionId) return;
|
|
2500
3550
|
applyTurn(sessionId, data);
|
|
2501
3551
|
});
|
|
3552
|
+
s.on("turn:events", (data) => {
|
|
3553
|
+
const sessionId = this._resolveSessionId(data);
|
|
3554
|
+
if (!sessionId || !data.events?.length) return;
|
|
3555
|
+
if (!useChatStore.getState().isStreaming[sessionId]) {
|
|
3556
|
+
useSessionStore.getState().updateSessionStatus(sessionId, "running");
|
|
3557
|
+
useChatStore.getState().setStreaming(sessionId, true);
|
|
3558
|
+
}
|
|
3559
|
+
const existingTurns = useChatStore.getState().turns[sessionId];
|
|
3560
|
+
if (existingTurns?.length) {
|
|
3561
|
+
this.projectionBuilder.seedSequence(
|
|
3562
|
+
Math.max(...existingTurns.map((t) => t.sequence))
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
const updates = this.projectionBuilder.processBatch(data.events);
|
|
3566
|
+
for (const update of updates) {
|
|
3567
|
+
if (update.kind === "upsert") {
|
|
3568
|
+
useChatStore.getState().upsertTurn(sessionId, update.turn);
|
|
3569
|
+
const mode = extractModeFromBlocks3(update.turn.blocks);
|
|
3570
|
+
if (mode === "planning" || mode === "executing") {
|
|
3571
|
+
useSessionStore.getState().setMode(sessionId, mode);
|
|
3572
|
+
}
|
|
3573
|
+
this._applyProjectionSideEffects(sessionId, update.turn);
|
|
3574
|
+
} else if (update.kind === "workspace_changed") {
|
|
3575
|
+
notifyWorkspaceFilesChanged(sessionId);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
});
|
|
2502
3579
|
s.on("chat:start", (data) => {
|
|
2503
3580
|
const sessionId = this._resolveSessionId(data);
|
|
2504
3581
|
if (!sessionId) return;
|
|
@@ -2599,6 +3676,7 @@ var AgentSocket = class {
|
|
|
2599
3676
|
if (!sessionId) return;
|
|
2600
3677
|
const patch = {};
|
|
2601
3678
|
if (typeof data.intent === "string") patch.intent = data.intent;
|
|
3679
|
+
if (data.status) patch.status = data.status;
|
|
2602
3680
|
if ("model" in data) patch.model = data.model;
|
|
2603
3681
|
if ("bound_skill_id" in data) patch.bound_skill_id = data.bound_skill_id;
|
|
2604
3682
|
if ("replay_state" in data) patch.replay_state = data.replay_state;
|
|
@@ -2917,6 +3995,7 @@ var AgentSocket = class {
|
|
|
2917
3995
|
this.deliveredBridgeKeys.clear();
|
|
2918
3996
|
useUiStore.getState().clearArtifacts();
|
|
2919
3997
|
useCardStateStore.getState().clearAllStates();
|
|
3998
|
+
this.projectionBuilder.reset();
|
|
2920
3999
|
}
|
|
2921
4000
|
if (this.joinedSessions.has(sessionId)) {
|
|
2922
4001
|
this._refreshRunningState(sessionId);
|
|
@@ -2955,9 +4034,9 @@ var AgentSocket = class {
|
|
|
2955
4034
|
};
|
|
2956
4035
|
}
|
|
2957
4036
|
/**
|
|
2958
|
-
* 订阅 session:updated
|
|
4037
|
+
* 订阅 session:updated(智能体意图、状态等会话属性变更)。全局 handler 只会
|
|
2959
4038
|
* patch 已在 useSessionStore 列表里的会话;看板任务这类不在列表里的
|
|
2960
|
-
*
|
|
4039
|
+
* 会话,由调用方通过本方法自行消费属性更新。
|
|
2961
4040
|
*/
|
|
2962
4041
|
onSessionUpdated(handler) {
|
|
2963
4042
|
this.socket.on("session:updated", handler);
|
|
@@ -3087,6 +4166,7 @@ var AgentSocket = class {
|
|
|
3087
4166
|
mode,
|
|
3088
4167
|
askuser_answer: askuserAnswer,
|
|
3089
4168
|
model: extras?.model,
|
|
4169
|
+
thinking_override: extras?.thinkingOverride,
|
|
3090
4170
|
whatif: extras?.whatif,
|
|
3091
4171
|
replay_decision: extras?.replay_decision
|
|
3092
4172
|
};
|
|
@@ -3402,4 +4482,4 @@ export {
|
|
|
3402
4482
|
bootstrapBladeClient,
|
|
3403
4483
|
getBootstrappedClient
|
|
3404
4484
|
};
|
|
3405
|
-
//# sourceMappingURL=chunk-
|
|
4485
|
+
//# sourceMappingURL=chunk-QEYZZDLM.js.map
|