claude-code-rust 0.12.2 → 0.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/agent-sdk/dist/bridge/commands.js +20 -0
- package/agent-sdk/dist/bridge/events.js +15 -1
- package/agent-sdk/dist/bridge/logger.js +10 -0
- package/agent-sdk/dist/bridge/message_handlers.js +172 -1
- package/agent-sdk/dist/bridge/session_lifecycle.js +37 -0
- package/agent-sdk/dist/bridge/state_parsing.js +9 -0
- package/agent-sdk/dist/bridge/tooling.js +71 -18
- package/agent-sdk/dist/bridge.js +410 -2
- package/agent-sdk/dist/bridge.test.js +650 -5
- package/package.json +9 -4
- package/scripts/jscpd-warning-summary.mjs +132 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { AsyncQueue, CACHE_SPLIT_POLICY, buildApiRetryUpdate, buildRateLimitUpdate, buildQueryOptions, canGenerateSessionTitle, generatePersistedSessionTitle, buildSessionMutationOptions, buildSessionListOptions, buildToolResultFields, createToolCall, applySessionAgent, applySessionEffort, emitAgentConfigOptionUpdate, emitEffortConfigOptionUpdate, handleTaskSystemMessage, handleSdkMessage, mapSdkAccountInfo, mapAvailableAgents, mapAvailableModels, mapSessionMessagesToUpdates, mapSdkSessions, agentSdkVersionCompatibilityError, looksLikeAuthRequired, normalizeToolResultText, parseFastModeState, parseRuntimeSessionState, parseRateLimitStatus, bridgeMcpConfigToSdk, mapMcpServerStatus, mapMcpServerStatusConfig, normalizeSettingsParseError, normalizeToolKind, parseCommandEnvelope, permissionOptionsFromSuggestions, permissionResultFromOutcome, previewKilobyteLabel, staleMcpAuthCandidates, resolveInstalledAgentSdkVersion, unwrapToolUseResult, updateAvailableCommands, handleReloadPluginsCommand, } from "./bridge.js";
|
|
3
|
+
import { AsyncQueue, CACHE_SPLIT_POLICY, buildApiRetryUpdate, buildRateLimitUpdate, buildRewindConversationPlan, buildQueryOptions, canGenerateSessionTitle, generatePersistedSessionTitle, buildSessionMutationOptions, buildSessionListOptions, buildToolResultFields, createToolCall, applySessionAgent, applySessionEffort, emitAgentConfigOptionUpdate, emitEffortConfigOptionUpdate, handleTaskSystemMessage, handleSdkMessage, isShellToolName, mapSdkAccountInfo, mapAvailableAgents, mapAvailableModels, mapSessionMessagesToUpdates, mapSdkSessions, agentSdkVersionCompatibilityError, looksLikeAuthRequired, normalizeToolResultText, parseFastModeState, parseRuntimeSessionState, parseRateLimitStatus, bridgeMcpConfigToSdk, mapMcpServerStatus, mapMcpServerStatusConfig, normalizeSettingsParseError, normalizeToolKind, parseCommandEnvelope, permissionOptionsFromSuggestions, permissionResultFromOutcome, previewKilobyteLabel, staleMcpAuthCandidates, resolveInstalledAgentSdkVersion, rewindTargetsFromSessionMessages, unwrapToolUseResult, updateAvailableCommands, handleReloadPluginsCommand, } from "./bridge.js";
|
|
4
4
|
import { availableModesForSession, buildModeState, markModeUnavailableForSession, permissionModeFailureLooksUnsupported, refreshSupportedModesForSession, } from "./bridge/commands.js";
|
|
5
5
|
import { handleMcpSetServersCommand } from "./bridge/mcp.js";
|
|
6
|
-
import { emitCurrentModelUpdate, handleUserDialogResponse, refreshCurrentModel, resolveCurrentModel, sessions, shouldInvalidateResolvedRuntimeModel, shouldEmitStartupAuthRequiredForAccount, } from "./bridge/session_lifecycle.js";
|
|
6
|
+
import { emitCurrentModelUpdate, handleUserDialogResponse, closeSessionsBeforeRegister, refreshCurrentModel, resolveCurrentModel, sessions, shouldInvalidateResolvedRuntimeModel, shouldEmitStartupAuthRequiredForAccount, } from "./bridge/session_lifecycle.js";
|
|
7
7
|
import { classifyTurnErrorKind } from "./bridge/error_classification.js";
|
|
8
8
|
import { emitToolCall, emitToolProgressUpdate, emitToolResultUpdate } from "./bridge/tool_calls.js";
|
|
9
9
|
import { linkTaskToolUse } from "./bridge/task_links.js";
|
|
10
10
|
import { requestAskUserQuestionAnswers } from "./bridge/user_interaction.js";
|
|
11
|
-
import { handleResultMessage } from "./bridge/message_handlers.js";
|
|
11
|
+
import { flushPendingWorkerShutdown, handleResultMessage } from "./bridge/message_handlers.js";
|
|
12
12
|
const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32" ? "claude-rs-bridge-node.exe" : "claude-rs-bridge-node";
|
|
13
13
|
const BRIDGE_RUNTIME_GUARD_PROMPT = `Do not terminate the Claude Rust bridge runtime process \`${BRIDGE_RUNTIME_PROCESS_NAME}\`; ` +
|
|
14
14
|
"when cleaning up development servers, only stop processes by explicit PIDs you started in this session.";
|
|
@@ -39,11 +39,34 @@ function makeSessionState() {
|
|
|
39
39
|
pendingQuestions: new Map(),
|
|
40
40
|
pendingUserDialogs: new Map(),
|
|
41
41
|
pendingElicitations: new Map(),
|
|
42
|
+
informationalDedupKeys: new Set(),
|
|
42
43
|
mcpStatusRevalidatedAt: new Map(),
|
|
43
44
|
hiddenToolUseIds: new Set(),
|
|
44
45
|
authHintSent: false,
|
|
45
46
|
};
|
|
46
47
|
}
|
|
48
|
+
test("closeSessionsBeforeRegister closes same-key stale session before replacement registration", async () => {
|
|
49
|
+
sessions.clear();
|
|
50
|
+
let staleClosed = 0;
|
|
51
|
+
const stale = makeSessionState();
|
|
52
|
+
stale.sessionId = "session-1";
|
|
53
|
+
stale.query = {
|
|
54
|
+
close: () => {
|
|
55
|
+
staleClosed += 1;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const replacement = makeSessionState();
|
|
59
|
+
replacement.sessionId = "session-1";
|
|
60
|
+
sessions.set(stale.sessionId, stale);
|
|
61
|
+
await closeSessionsBeforeRegister(replacement, [stale], "req-1");
|
|
62
|
+
assert.equal(staleClosed, 1);
|
|
63
|
+
assert.equal(sessions.has("session-1"), false);
|
|
64
|
+
sessions.set(replacement.sessionId, replacement);
|
|
65
|
+
await closeSessionsBeforeRegister(replacement, [stale], "req-2");
|
|
66
|
+
assert.equal(staleClosed, 2);
|
|
67
|
+
assert.equal(sessions.get("session-1"), replacement);
|
|
68
|
+
sessions.clear();
|
|
69
|
+
});
|
|
47
70
|
test("availableModesForSession omits conditional modes when unsupported", () => {
|
|
48
71
|
const session = makeSessionState();
|
|
49
72
|
refreshSupportedModesForSession(session);
|
|
@@ -706,6 +729,150 @@ test("parseCommandEnvelope validates get_context_usage command", () => {
|
|
|
706
729
|
session_id: "session-123",
|
|
707
730
|
});
|
|
708
731
|
});
|
|
732
|
+
test("parseCommandEnvelope validates get_rewind_targets command", () => {
|
|
733
|
+
const parsed = parseCommandEnvelope(JSON.stringify({
|
|
734
|
+
request_id: "req-rewind-targets",
|
|
735
|
+
command: "get_rewind_targets",
|
|
736
|
+
session_id: "session-123",
|
|
737
|
+
}));
|
|
738
|
+
assert.equal(parsed.requestId, "req-rewind-targets");
|
|
739
|
+
assert.deepEqual(parsed.command, {
|
|
740
|
+
command: "get_rewind_targets",
|
|
741
|
+
session_id: "session-123",
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
test("parseCommandEnvelope validates rewind command modes", () => {
|
|
745
|
+
for (const restoreMode of ["both", "conversation", "code"]) {
|
|
746
|
+
const parsed = parseCommandEnvelope(JSON.stringify({
|
|
747
|
+
request_id: "req-rewind",
|
|
748
|
+
command: "rewind",
|
|
749
|
+
session_id: "session-123",
|
|
750
|
+
target_user_message_id: "user-1",
|
|
751
|
+
restore_mode: restoreMode,
|
|
752
|
+
launch_settings: {
|
|
753
|
+
language: "German",
|
|
754
|
+
},
|
|
755
|
+
}));
|
|
756
|
+
assert.equal(parsed.requestId, "req-rewind");
|
|
757
|
+
assert.deepEqual(parsed.command, {
|
|
758
|
+
command: "rewind",
|
|
759
|
+
session_id: "session-123",
|
|
760
|
+
target_user_message_id: "user-1",
|
|
761
|
+
restore_mode: restoreMode,
|
|
762
|
+
launch_settings: { language: "German" },
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
test("parseCommandEnvelope rejects invalid rewind mode", () => {
|
|
767
|
+
assert.throws(() => parseCommandEnvelope(JSON.stringify({
|
|
768
|
+
command: "rewind",
|
|
769
|
+
session_id: "session-123",
|
|
770
|
+
target_user_message_id: "user-1",
|
|
771
|
+
restore_mode: "files",
|
|
772
|
+
})), /rewind\.restore_mode must be one of both, conversation, code/);
|
|
773
|
+
});
|
|
774
|
+
test("rewindTargetsFromSessionMessages filters user text messages with UUIDs", () => {
|
|
775
|
+
const messages = [
|
|
776
|
+
{
|
|
777
|
+
type: "user",
|
|
778
|
+
uuid: "user-1",
|
|
779
|
+
message: { role: "user", content: [{ type: "text", text: " first prompt\nline " }] },
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
type: "assistant",
|
|
783
|
+
uuid: "assistant-1",
|
|
784
|
+
message: { role: "assistant", content: [{ type: "text", text: "ignored" }] },
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
type: "user",
|
|
788
|
+
uuid: "tool-result",
|
|
789
|
+
message: { role: "user", content: [{ type: "tool_result", content: "ignored" }] },
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
type: "user",
|
|
793
|
+
uuid: "user-2",
|
|
794
|
+
message: { role: "user", content: [{ type: "text", text: "second prompt" }] },
|
|
795
|
+
},
|
|
796
|
+
];
|
|
797
|
+
assert.deepEqual(rewindTargetsFromSessionMessages(messages), [
|
|
798
|
+
{
|
|
799
|
+
uuid: "user-2",
|
|
800
|
+
first_text: "second prompt",
|
|
801
|
+
input_text: "second prompt",
|
|
802
|
+
index: 3,
|
|
803
|
+
previous_assistant_uuid: "assistant-1",
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
uuid: "user-1",
|
|
807
|
+
first_text: "first prompt line",
|
|
808
|
+
input_text: "first prompt\nline",
|
|
809
|
+
index: 0,
|
|
810
|
+
},
|
|
811
|
+
]);
|
|
812
|
+
});
|
|
813
|
+
test("buildRewindConversationPlan anchors at previous assistant message", () => {
|
|
814
|
+
const messages = [
|
|
815
|
+
{
|
|
816
|
+
type: "user",
|
|
817
|
+
uuid: "user-1",
|
|
818
|
+
message: { role: "user", content: "first prompt" },
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
type: "assistant",
|
|
822
|
+
uuid: "assistant-1",
|
|
823
|
+
message: { role: "assistant", content: [{ type: "text", text: "reply" }] },
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
type: "user",
|
|
827
|
+
uuid: "user-2",
|
|
828
|
+
message: { role: "user", content: "second prompt" },
|
|
829
|
+
},
|
|
830
|
+
];
|
|
831
|
+
const plan = buildRewindConversationPlan(messages, "user-2");
|
|
832
|
+
assert.ok(plan);
|
|
833
|
+
assert.equal(plan.inputText, "second prompt");
|
|
834
|
+
assert.equal(plan.previousAssistantUuid, "assistant-1");
|
|
835
|
+
assert.equal(plan.targetIndex, 2);
|
|
836
|
+
assert.deepEqual(plan.retainedMessages.map((message) => message.uuid), ["user-1", "assistant-1"]);
|
|
837
|
+
assert.ok(plan.resumeUpdates.length > 0);
|
|
838
|
+
});
|
|
839
|
+
test("buildRewindConversationPlan treats first user message as fresh replacement", () => {
|
|
840
|
+
const messages = [
|
|
841
|
+
{
|
|
842
|
+
type: "user",
|
|
843
|
+
uuid: "user-1",
|
|
844
|
+
message: { role: "user", content: " first prompt\nline " },
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
type: "assistant",
|
|
848
|
+
uuid: "assistant-1",
|
|
849
|
+
message: { role: "assistant", content: [{ type: "text", text: "reply" }] },
|
|
850
|
+
},
|
|
851
|
+
];
|
|
852
|
+
const plan = buildRewindConversationPlan(messages, "user-1");
|
|
853
|
+
assert.ok(plan);
|
|
854
|
+
assert.equal(plan.inputText, " first prompt\nline ");
|
|
855
|
+
assert.equal(plan.previousAssistantUuid, undefined);
|
|
856
|
+
assert.equal(plan.targetIndex, 0);
|
|
857
|
+
assert.deepEqual(plan.retainedMessages, []);
|
|
858
|
+
assert.deepEqual(plan.resumeUpdates, []);
|
|
859
|
+
});
|
|
860
|
+
test("buildRewindConversationPlan rejects stale or inconsistent targets", () => {
|
|
861
|
+
const messages = [
|
|
862
|
+
{
|
|
863
|
+
type: "user",
|
|
864
|
+
uuid: "user-1",
|
|
865
|
+
message: { role: "user", content: "first prompt" },
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
type: "user",
|
|
869
|
+
uuid: "user-2",
|
|
870
|
+
message: { role: "user", content: "second prompt" },
|
|
871
|
+
},
|
|
872
|
+
];
|
|
873
|
+
assert.equal(buildRewindConversationPlan(messages, "missing-user"), null);
|
|
874
|
+
assert.equal(buildRewindConversationPlan(messages, "user-2"), null);
|
|
875
|
+
});
|
|
709
876
|
test("staleMcpAuthCandidates selects previously connected servers that regressed to needs-auth", () => {
|
|
710
877
|
const candidates = staleMcpAuthCandidates([
|
|
711
878
|
{
|
|
@@ -817,12 +984,31 @@ test("buildQueryOptions maps launch settings into sdk query options", () => {
|
|
|
817
984
|
assert.equal("effort" in options, false);
|
|
818
985
|
assert.equal(options.agentProgressSummaries, true);
|
|
819
986
|
assert.equal(options.promptSuggestions, true);
|
|
987
|
+
assert.equal(options.enableFileCheckpointing, true);
|
|
820
988
|
assert.equal(options.sessionId, "session-1");
|
|
821
989
|
assert.deepEqual(options.settingSources, ["user", "project", "local"]);
|
|
822
990
|
assert.deepEqual(options.toolConfig, {
|
|
823
991
|
askUserQuestion: { previewFormat: "markdown" },
|
|
824
992
|
});
|
|
825
993
|
});
|
|
994
|
+
test("buildQueryOptions includes resumeSessionAt when provided", () => {
|
|
995
|
+
const input = new AsyncQueue();
|
|
996
|
+
const options = buildQueryOptions({
|
|
997
|
+
cwd: "C:/work",
|
|
998
|
+
resume: "session-1",
|
|
999
|
+
resumeSessionAt: "assistant-1",
|
|
1000
|
+
launchSettings: {},
|
|
1001
|
+
provisionalSessionId: "session-1",
|
|
1002
|
+
input,
|
|
1003
|
+
canUseTool: async () => ({ behavior: "deny", message: "not used" }),
|
|
1004
|
+
enableSdkDebug: false,
|
|
1005
|
+
enableSpawnDebug: false,
|
|
1006
|
+
sessionIdForLogs: () => "session-1",
|
|
1007
|
+
});
|
|
1008
|
+
assert.equal(options.resume, "session-1");
|
|
1009
|
+
assert.equal(options.resumeSessionAt, "assistant-1");
|
|
1010
|
+
assert.equal("sessionId" in options, false);
|
|
1011
|
+
});
|
|
826
1012
|
test("buildQueryOptions forwards settings and maps startup model and permission mode", () => {
|
|
827
1013
|
const input = new AsyncQueue();
|
|
828
1014
|
const options = buildQueryOptions({
|
|
@@ -1389,6 +1575,96 @@ test("handleSdkMessage emits transcript retraction for model_refusal_fallback",
|
|
|
1389
1575
|
]);
|
|
1390
1576
|
assert.equal(events.some((event) => event.update?.type === "system_notice_update"), false);
|
|
1391
1577
|
});
|
|
1578
|
+
test("handleSdkMessage emits warning notice for model_refusal_no_fallback with explanation", () => {
|
|
1579
|
+
const session = makeSessionState();
|
|
1580
|
+
const events = captureBridgeEvents(() => {
|
|
1581
|
+
handleSdkMessage(session, {
|
|
1582
|
+
type: "system",
|
|
1583
|
+
subtype: "model_refusal_no_fallback",
|
|
1584
|
+
original_model: "claude-opus-4-1",
|
|
1585
|
+
request_id: "req-1",
|
|
1586
|
+
api_refusal_category: "cyber",
|
|
1587
|
+
api_refusal_explanation: "policy text",
|
|
1588
|
+
refused_user_message_uuid: "user-1",
|
|
1589
|
+
content: "raw content",
|
|
1590
|
+
uuid: "refusal-notice",
|
|
1591
|
+
session_id: "session-1",
|
|
1592
|
+
});
|
|
1593
|
+
});
|
|
1594
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
1595
|
+
{
|
|
1596
|
+
type: "system_notice_update",
|
|
1597
|
+
severity: "warning",
|
|
1598
|
+
message: "Could not continue with claude-opus-4-1: model refused the request and no fallback model is configured. Reason: policy text.",
|
|
1599
|
+
},
|
|
1600
|
+
]);
|
|
1601
|
+
});
|
|
1602
|
+
test("handleSdkMessage emits warning notice for model_refusal_no_fallback with category only", () => {
|
|
1603
|
+
const session = makeSessionState();
|
|
1604
|
+
const events = captureBridgeEvents(() => {
|
|
1605
|
+
handleSdkMessage(session, {
|
|
1606
|
+
type: "system",
|
|
1607
|
+
subtype: "model_refusal_no_fallback",
|
|
1608
|
+
original_model: "claude-opus-4-1",
|
|
1609
|
+
request_id: "req-1",
|
|
1610
|
+
api_refusal_category: "cyber",
|
|
1611
|
+
uuid: "refusal-notice",
|
|
1612
|
+
session_id: "session-1",
|
|
1613
|
+
});
|
|
1614
|
+
});
|
|
1615
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
1616
|
+
{
|
|
1617
|
+
type: "system_notice_update",
|
|
1618
|
+
severity: "warning",
|
|
1619
|
+
message: "Could not continue with claude-opus-4-1: model refused the request and no fallback model is configured. Refusal category: cyber.",
|
|
1620
|
+
},
|
|
1621
|
+
]);
|
|
1622
|
+
});
|
|
1623
|
+
test("handleSdkMessage emits warning notice for model_refusal_no_fallback with content detail", () => {
|
|
1624
|
+
const session = makeSessionState();
|
|
1625
|
+
const events = captureBridgeEvents(() => {
|
|
1626
|
+
handleSdkMessage(session, {
|
|
1627
|
+
type: "system",
|
|
1628
|
+
subtype: "model_refusal_no_fallback",
|
|
1629
|
+
original_model: "claude-opus-4-1",
|
|
1630
|
+
request_id: "req-1",
|
|
1631
|
+
content: "Refused by policy!",
|
|
1632
|
+
uuid: "refusal-notice",
|
|
1633
|
+
session_id: "session-1",
|
|
1634
|
+
});
|
|
1635
|
+
});
|
|
1636
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
1637
|
+
{
|
|
1638
|
+
type: "system_notice_update",
|
|
1639
|
+
severity: "warning",
|
|
1640
|
+
message: "Could not continue with claude-opus-4-1: model refused the request and no fallback model is configured. Refused by policy!",
|
|
1641
|
+
},
|
|
1642
|
+
]);
|
|
1643
|
+
});
|
|
1644
|
+
test("handleSdkMessage emits readable model_refusal_no_fallback notice for empty metadata", () => {
|
|
1645
|
+
const session = makeSessionState();
|
|
1646
|
+
const events = captureBridgeEvents(() => {
|
|
1647
|
+
handleSdkMessage(session, {
|
|
1648
|
+
type: "system",
|
|
1649
|
+
subtype: "model_refusal_no_fallback",
|
|
1650
|
+
original_model: " ",
|
|
1651
|
+
request_id: null,
|
|
1652
|
+
api_refusal_category: " ",
|
|
1653
|
+
api_refusal_explanation: null,
|
|
1654
|
+
refused_user_message_uuid: null,
|
|
1655
|
+
content: " ",
|
|
1656
|
+
uuid: "refusal-notice",
|
|
1657
|
+
session_id: "session-1",
|
|
1658
|
+
});
|
|
1659
|
+
});
|
|
1660
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
1661
|
+
{
|
|
1662
|
+
type: "system_notice_update",
|
|
1663
|
+
severity: "warning",
|
|
1664
|
+
message: "Could not continue with the selected model: model refused the request and no fallback model is configured.",
|
|
1665
|
+
},
|
|
1666
|
+
]);
|
|
1667
|
+
});
|
|
1392
1668
|
test("handleSdkMessage emits tolerant transcript retraction for model_fallback", () => {
|
|
1393
1669
|
const session = makeSessionState();
|
|
1394
1670
|
const events = captureBridgeEvents(() => {
|
|
@@ -2889,6 +3165,7 @@ test("requestAskUserQuestionAnswers preserves previews and annotations in update
|
|
|
2889
3165
|
});
|
|
2890
3166
|
test("normalizeToolKind maps known tool names", () => {
|
|
2891
3167
|
assert.equal(normalizeToolKind("Bash"), "execute");
|
|
3168
|
+
assert.equal(normalizeToolKind("PowerShell"), "execute");
|
|
2892
3169
|
assert.equal(normalizeToolKind("Delete"), "delete");
|
|
2893
3170
|
assert.equal(normalizeToolKind("Move"), "move");
|
|
2894
3171
|
assert.equal(normalizeToolKind("EnterWorktree"), "other");
|
|
@@ -2907,12 +3184,34 @@ test("normalizeToolKind maps known tool names", () => {
|
|
|
2907
3184
|
assert.equal(normalizeToolKind("ShowOnboardingRolePicker"), "other");
|
|
2908
3185
|
assert.equal(normalizeToolKind("TaskOutput"), "other");
|
|
2909
3186
|
assert.equal(normalizeToolKind("TaskStop"), "other");
|
|
3187
|
+
assert.equal(normalizeToolKind("ReadMcpResourceDir"), "read");
|
|
2910
3188
|
assert.equal(normalizeToolKind("Task"), "think");
|
|
2911
3189
|
assert.equal(normalizeToolKind("Agent"), "think");
|
|
2912
3190
|
assert.equal(normalizeToolKind("EnterPlanMode"), "switch_mode");
|
|
2913
3191
|
assert.equal(normalizeToolKind("ExitPlanMode"), "switch_mode");
|
|
2914
3192
|
assert.equal(normalizeToolKind("TodoWrite"), normalizeToolKind("FutureUnknownTool"));
|
|
2915
3193
|
});
|
|
3194
|
+
test("isShellToolName recognizes only supported shell tools", () => {
|
|
3195
|
+
assert.equal(isShellToolName("Bash"), true);
|
|
3196
|
+
assert.equal(isShellToolName("PowerShell"), true);
|
|
3197
|
+
assert.equal(isShellToolName("Shell"), false);
|
|
3198
|
+
assert.equal(isShellToolName("bash"), false);
|
|
3199
|
+
});
|
|
3200
|
+
test("shell tool titles use input command", () => {
|
|
3201
|
+
assert.equal(createToolCall("tc-bash-title", "Bash", { command: "git status" }).title, "git status");
|
|
3202
|
+
assert.equal(createToolCall("tc-powershell-title", "PowerShell", { command: "Get-ChildItem" }).title, "Get-ChildItem");
|
|
3203
|
+
assert.equal(createToolCall("tc-powershell-empty", "PowerShell", {}).title, "Terminal");
|
|
3204
|
+
});
|
|
3205
|
+
test("ReadMcpResourceDir titles include server and URI context", () => {
|
|
3206
|
+
assert.equal(createToolCall("tc-mcp-dir-title", "ReadMcpResourceDir", {
|
|
3207
|
+
server: "docs",
|
|
3208
|
+
uri: "file://manuals/",
|
|
3209
|
+
}).title, "ReadMcpResourceDir docs file://manuals/");
|
|
3210
|
+
assert.equal(createToolCall("tc-mcp-dir-uri-title", "ReadMcpResourceDir", {
|
|
3211
|
+
uri: "file://manuals/",
|
|
3212
|
+
}).title, "ReadMcpResourceDir file://manuals/");
|
|
3213
|
+
assert.equal(createToolCall("tc-mcp-dir-fallback-title", "ReadMcpResourceDir", {}).title, "ReadMcpResourceDir");
|
|
3214
|
+
});
|
|
2916
3215
|
test("parseFastModeState accepts known values and rejects unknown values", () => {
|
|
2917
3216
|
assert.equal(parseFastModeState("off"), "off");
|
|
2918
3217
|
assert.equal(parseFastModeState("cooldown"), "cooldown");
|
|
@@ -2945,10 +3244,14 @@ test("buildRateLimitUpdate maps SDK fields to wire shape", () => {
|
|
|
2945
3244
|
overageDisabledReason: "out_of_credits",
|
|
2946
3245
|
isUsingOverage: false,
|
|
2947
3246
|
surpassedThreshold: 0.9,
|
|
3247
|
+
errorCode: "credits_required",
|
|
3248
|
+
canUserPurchaseCredits: true,
|
|
3249
|
+
hasChargeableSavedPaymentMethod: false,
|
|
2948
3250
|
});
|
|
2949
3251
|
assert.deepEqual(update, {
|
|
2950
3252
|
type: "rate_limit_update",
|
|
2951
3253
|
status: "allowed_warning",
|
|
3254
|
+
error_code: "credits_required",
|
|
2952
3255
|
resets_at: 1_741_280_000,
|
|
2953
3256
|
utilization: 0.92,
|
|
2954
3257
|
rate_limit_type: "five_hour",
|
|
@@ -2957,6 +3260,8 @@ test("buildRateLimitUpdate maps SDK fields to wire shape", () => {
|
|
|
2957
3260
|
overage_disabled_reason: "out_of_credits",
|
|
2958
3261
|
is_using_overage: false,
|
|
2959
3262
|
surpassed_threshold: 0.9,
|
|
3263
|
+
can_user_purchase_credits: true,
|
|
3264
|
+
has_chargeable_saved_payment_method: false,
|
|
2960
3265
|
});
|
|
2961
3266
|
});
|
|
2962
3267
|
test("buildRateLimitUpdate normalizes SDK overage boolean spellings", () => {
|
|
@@ -2986,6 +3291,10 @@ test("buildRateLimitUpdate rejects invalid payloads", () => {
|
|
|
2986
3291
|
status: "rejected",
|
|
2987
3292
|
overageStatus: "bad_status",
|
|
2988
3293
|
}), { type: "rate_limit_update", status: "rejected" });
|
|
3294
|
+
assert.deepEqual(buildRateLimitUpdate({ status: "rejected", errorCode: "other" }), {
|
|
3295
|
+
type: "rate_limit_update",
|
|
3296
|
+
status: "rejected",
|
|
3297
|
+
});
|
|
2989
3298
|
});
|
|
2990
3299
|
test("buildApiRetryUpdate maps SDK api_retry messages to wire shape", () => {
|
|
2991
3300
|
assert.deepEqual(buildApiRetryUpdate({
|
|
@@ -3271,6 +3580,197 @@ test("handleSdkMessage emits system notices for notifications and plugin failure
|
|
|
3271
3580
|
{ type: "system_notice_update", severity: "warning", message: "Plugin install failed acme: download failed" },
|
|
3272
3581
|
]);
|
|
3273
3582
|
});
|
|
3583
|
+
test("handleSdkMessage maps informational system messages to notices by level", () => {
|
|
3584
|
+
const session = makeSessionState();
|
|
3585
|
+
const events = captureBridgeEvents(() => {
|
|
3586
|
+
handleSdkMessage(session, {
|
|
3587
|
+
type: "system",
|
|
3588
|
+
subtype: "informational",
|
|
3589
|
+
content: " Sync ready ",
|
|
3590
|
+
level: "notice",
|
|
3591
|
+
uuid: "message-info-notice",
|
|
3592
|
+
session_id: "session-1",
|
|
3593
|
+
});
|
|
3594
|
+
handleSdkMessage(session, {
|
|
3595
|
+
type: "system",
|
|
3596
|
+
subtype: "informational",
|
|
3597
|
+
content: "Try /compact",
|
|
3598
|
+
level: "suggestion",
|
|
3599
|
+
uuid: "message-info-suggestion",
|
|
3600
|
+
session_id: "session-1",
|
|
3601
|
+
});
|
|
3602
|
+
handleSdkMessage(session, {
|
|
3603
|
+
type: "system",
|
|
3604
|
+
subtype: "informational",
|
|
3605
|
+
content: "Hook blocked continuation",
|
|
3606
|
+
level: "warning",
|
|
3607
|
+
uuid: "message-info-warning",
|
|
3608
|
+
session_id: "session-1",
|
|
3609
|
+
});
|
|
3610
|
+
});
|
|
3611
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
3612
|
+
{ type: "system_notice_update", severity: "info", message: "Sync ready" },
|
|
3613
|
+
{ type: "system_notice_update", severity: "info", message: "Suggestion: Try /compact" },
|
|
3614
|
+
{ type: "system_notice_update", severity: "warning", message: "Hook blocked continuation" },
|
|
3615
|
+
]);
|
|
3616
|
+
});
|
|
3617
|
+
test("handleSdkMessage keeps informational info log-only unless continuation is prevented", () => {
|
|
3618
|
+
const session = makeSessionState();
|
|
3619
|
+
const events = captureBridgeEvents(() => {
|
|
3620
|
+
handleSdkMessage(session, {
|
|
3621
|
+
type: "system",
|
|
3622
|
+
subtype: "informational",
|
|
3623
|
+
content: "Transcript-only progress",
|
|
3624
|
+
level: "info",
|
|
3625
|
+
uuid: "message-info-log-only",
|
|
3626
|
+
session_id: "session-1",
|
|
3627
|
+
});
|
|
3628
|
+
handleSdkMessage(session, {
|
|
3629
|
+
type: "system",
|
|
3630
|
+
subtype: "informational",
|
|
3631
|
+
content: "Stop hook denied continuation",
|
|
3632
|
+
level: "info",
|
|
3633
|
+
prevent_continuation: true,
|
|
3634
|
+
uuid: "message-info-prevented",
|
|
3635
|
+
session_id: "session-1",
|
|
3636
|
+
});
|
|
3637
|
+
});
|
|
3638
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
3639
|
+
{
|
|
3640
|
+
type: "system_notice_update",
|
|
3641
|
+
severity: "warning",
|
|
3642
|
+
message: "Stop hook denied continuation",
|
|
3643
|
+
},
|
|
3644
|
+
]);
|
|
3645
|
+
});
|
|
3646
|
+
test("handleSdkMessage deduplicates informational messages by tool use, level, and content", () => {
|
|
3647
|
+
const session = makeSessionState();
|
|
3648
|
+
const events = captureBridgeEvents(() => {
|
|
3649
|
+
handleSdkMessage(session, {
|
|
3650
|
+
type: "system",
|
|
3651
|
+
subtype: "informational",
|
|
3652
|
+
content: "Progress",
|
|
3653
|
+
level: "notice",
|
|
3654
|
+
tool_use_id: "tool-1",
|
|
3655
|
+
uuid: "message-info-1",
|
|
3656
|
+
session_id: "session-1",
|
|
3657
|
+
});
|
|
3658
|
+
handleSdkMessage(session, {
|
|
3659
|
+
type: "system",
|
|
3660
|
+
subtype: "informational",
|
|
3661
|
+
content: " Progress ",
|
|
3662
|
+
level: "notice",
|
|
3663
|
+
tool_use_id: "tool-1",
|
|
3664
|
+
uuid: "message-info-2",
|
|
3665
|
+
session_id: "session-1",
|
|
3666
|
+
});
|
|
3667
|
+
handleSdkMessage(session, {
|
|
3668
|
+
type: "system",
|
|
3669
|
+
subtype: "informational",
|
|
3670
|
+
content: "Progress updated",
|
|
3671
|
+
level: "notice",
|
|
3672
|
+
tool_use_id: "tool-1",
|
|
3673
|
+
uuid: "message-info-3",
|
|
3674
|
+
session_id: "session-1",
|
|
3675
|
+
});
|
|
3676
|
+
});
|
|
3677
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
3678
|
+
{ type: "system_notice_update", severity: "info", message: "Progress" },
|
|
3679
|
+
{ type: "system_notice_update", severity: "info", message: "Progress updated" },
|
|
3680
|
+
]);
|
|
3681
|
+
});
|
|
3682
|
+
test("handleSdkMessage does not deduplicate informational messages without a tool use id", () => {
|
|
3683
|
+
const session = makeSessionState();
|
|
3684
|
+
const events = captureBridgeEvents(() => {
|
|
3685
|
+
for (const uuid of ["message-info-1", "message-info-2"]) {
|
|
3686
|
+
handleSdkMessage(session, {
|
|
3687
|
+
type: "system",
|
|
3688
|
+
subtype: "informational",
|
|
3689
|
+
content: "Repeated global notice",
|
|
3690
|
+
level: "notice",
|
|
3691
|
+
uuid,
|
|
3692
|
+
session_id: "session-1",
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
});
|
|
3696
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
3697
|
+
{ type: "system_notice_update", severity: "info", message: "Repeated global notice" },
|
|
3698
|
+
{ type: "system_notice_update", severity: "info", message: "Repeated global notice" },
|
|
3699
|
+
]);
|
|
3700
|
+
});
|
|
3701
|
+
test("handleSdkMessage treats worker shutdown before connect as log-only", () => {
|
|
3702
|
+
const session = makeSessionState();
|
|
3703
|
+
session.connected = false;
|
|
3704
|
+
const events = captureBridgeEvents(() => {
|
|
3705
|
+
handleSdkMessage(session, {
|
|
3706
|
+
type: "system",
|
|
3707
|
+
subtype: "worker_shutting_down",
|
|
3708
|
+
reason: "host_exit",
|
|
3709
|
+
uuid: "message-worker-shutdown",
|
|
3710
|
+
session_id: "session-1",
|
|
3711
|
+
});
|
|
3712
|
+
flushPendingWorkerShutdown(session);
|
|
3713
|
+
});
|
|
3714
|
+
assert.deepEqual(events, []);
|
|
3715
|
+
assert.equal(session.pendingWorkerShutdown, undefined);
|
|
3716
|
+
});
|
|
3717
|
+
test("handleSdkMessage flushes connected worker shutdown only when stream ends", () => {
|
|
3718
|
+
const session = makeSessionState();
|
|
3719
|
+
const events = captureBridgeEvents(() => {
|
|
3720
|
+
handleSdkMessage(session, {
|
|
3721
|
+
type: "system",
|
|
3722
|
+
subtype: "worker_shutting_down",
|
|
3723
|
+
reason: "host_exit",
|
|
3724
|
+
uuid: "message-worker-shutdown",
|
|
3725
|
+
session_id: "session-1",
|
|
3726
|
+
});
|
|
3727
|
+
flushPendingWorkerShutdown(session);
|
|
3728
|
+
});
|
|
3729
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
3730
|
+
{
|
|
3731
|
+
type: "system_notice_update",
|
|
3732
|
+
severity: "warning",
|
|
3733
|
+
message: "Claude worker is shutting down: host_exit",
|
|
3734
|
+
},
|
|
3735
|
+
]);
|
|
3736
|
+
});
|
|
3737
|
+
test("handleSdkMessage cancels pending worker shutdown after later SDK activity", () => {
|
|
3738
|
+
const session = makeSessionState();
|
|
3739
|
+
const events = captureBridgeEvents(() => {
|
|
3740
|
+
handleSdkMessage(session, {
|
|
3741
|
+
type: "system",
|
|
3742
|
+
subtype: "worker_shutting_down",
|
|
3743
|
+
reason: "host_exit",
|
|
3744
|
+
uuid: "message-worker-shutdown",
|
|
3745
|
+
session_id: "session-1",
|
|
3746
|
+
});
|
|
3747
|
+
handleSdkMessage(session, {
|
|
3748
|
+
type: "system",
|
|
3749
|
+
subtype: "notification",
|
|
3750
|
+
text: "Still running",
|
|
3751
|
+
priority: "low",
|
|
3752
|
+
uuid: "message-notification",
|
|
3753
|
+
session_id: "session-1",
|
|
3754
|
+
});
|
|
3755
|
+
flushPendingWorkerShutdown(session);
|
|
3756
|
+
});
|
|
3757
|
+
assert.deepEqual(events.map((event) => event.update), [
|
|
3758
|
+
{ type: "system_notice_update", severity: "info", message: "Still running" },
|
|
3759
|
+
]);
|
|
3760
|
+
});
|
|
3761
|
+
test("handleSdkMessage ignores unknown future system subtypes", () => {
|
|
3762
|
+
const session = makeSessionState();
|
|
3763
|
+
const events = captureBridgeEvents(() => {
|
|
3764
|
+
handleSdkMessage(session, {
|
|
3765
|
+
type: "system",
|
|
3766
|
+
subtype: "future_subtype",
|
|
3767
|
+
content: "Unknown",
|
|
3768
|
+
uuid: "message-future",
|
|
3769
|
+
session_id: "session-1",
|
|
3770
|
+
});
|
|
3771
|
+
});
|
|
3772
|
+
assert.deepEqual(events, []);
|
|
3773
|
+
});
|
|
3274
3774
|
test("handleSdkMessage treats mirror errors as log-only diagnostics", () => {
|
|
3275
3775
|
const session = makeSessionState();
|
|
3276
3776
|
const events = captureBridgeEvents(() => {
|
|
@@ -4048,6 +4548,22 @@ test("buildToolResultFields ignores model-facing Bash stale read hints", () => {
|
|
|
4048
4548
|
assert.equal(fields.raw_output, "real stdout");
|
|
4049
4549
|
assert.equal(fields.output_metadata, undefined);
|
|
4050
4550
|
});
|
|
4551
|
+
test("buildToolResultFields maps PowerShell structured output like shell output", () => {
|
|
4552
|
+
const base = createToolCall("tc-powershell", "PowerShell", { command: "Get-ChildItem" });
|
|
4553
|
+
const fields = buildToolResultFields(false, {
|
|
4554
|
+
stdout: "stdout line",
|
|
4555
|
+
stderr: "stderr line",
|
|
4556
|
+
interrupted: true,
|
|
4557
|
+
}, base, {
|
|
4558
|
+
result: {
|
|
4559
|
+
stdout: "stdout line",
|
|
4560
|
+
stderr: "stderr line",
|
|
4561
|
+
interrupted: true,
|
|
4562
|
+
},
|
|
4563
|
+
});
|
|
4564
|
+
assert.equal(fields.raw_output, "stdout line\nstderr line\nCommand was aborted before completion.");
|
|
4565
|
+
assert.equal(fields.output_metadata, undefined);
|
|
4566
|
+
});
|
|
4051
4567
|
test("buildToolResultFields adds Bash auto-backgrounded metadata and message", () => {
|
|
4052
4568
|
const base = createToolCall("tc-bash-bg", "Bash", { command: "npm run watch" });
|
|
4053
4569
|
const fields = buildToolResultFields(false, {
|
|
@@ -4157,6 +4673,133 @@ test("buildToolResultFields marks ReadMcpResource error output as failed", () =>
|
|
|
4157
4673
|
},
|
|
4158
4674
|
]);
|
|
4159
4675
|
});
|
|
4676
|
+
test("buildToolResultFields renders structured ReadMcpResourceDir listings", () => {
|
|
4677
|
+
const base = createToolCall("tc-mcp-dir", "ReadMcpResourceDir", {
|
|
4678
|
+
server: "docs",
|
|
4679
|
+
uri: "file://manuals/",
|
|
4680
|
+
});
|
|
4681
|
+
const fields = buildToolResultFields(false, {
|
|
4682
|
+
resources: [
|
|
4683
|
+
{
|
|
4684
|
+
name: "guide.md",
|
|
4685
|
+
uri: "file://manuals/guide.md",
|
|
4686
|
+
mimeType: "text/markdown",
|
|
4687
|
+
},
|
|
4688
|
+
{
|
|
4689
|
+
name: "images",
|
|
4690
|
+
uri: "file://manuals/images",
|
|
4691
|
+
mimeType: "inode/directory",
|
|
4692
|
+
},
|
|
4693
|
+
{
|
|
4694
|
+
name: "readme",
|
|
4695
|
+
uri: "file://manuals/readme",
|
|
4696
|
+
},
|
|
4697
|
+
],
|
|
4698
|
+
}, base);
|
|
4699
|
+
const expected = "guide.md - file://manuals/guide.md (text/markdown)\n" +
|
|
4700
|
+
"images - file://manuals/images (directory)\n" +
|
|
4701
|
+
"readme - file://manuals/readme";
|
|
4702
|
+
assert.equal(fields.status, "completed");
|
|
4703
|
+
assert.equal(fields.raw_output, expected);
|
|
4704
|
+
assert.deepEqual(fields.content, [
|
|
4705
|
+
{
|
|
4706
|
+
type: "content",
|
|
4707
|
+
content: { type: "text", text: expected },
|
|
4708
|
+
},
|
|
4709
|
+
]);
|
|
4710
|
+
});
|
|
4711
|
+
test("buildToolResultFields renders empty ReadMcpResourceDir listings", () => {
|
|
4712
|
+
const base = createToolCall("tc-mcp-dir-empty", "ReadMcpResourceDir", {
|
|
4713
|
+
server: "docs",
|
|
4714
|
+
uri: "file://empty/",
|
|
4715
|
+
});
|
|
4716
|
+
const fields = buildToolResultFields(false, { resources: [] }, base);
|
|
4717
|
+
assert.equal(fields.status, "completed");
|
|
4718
|
+
assert.equal(fields.raw_output, "No resources found.");
|
|
4719
|
+
assert.deepEqual(fields.content, [
|
|
4720
|
+
{
|
|
4721
|
+
type: "content",
|
|
4722
|
+
content: { type: "text", text: "No resources found." },
|
|
4723
|
+
},
|
|
4724
|
+
]);
|
|
4725
|
+
});
|
|
4726
|
+
test("buildToolResultFields marks ReadMcpResourceDir error output as failed", () => {
|
|
4727
|
+
const base = createToolCall("tc-mcp-dir-error", "ReadMcpResourceDir", {
|
|
4728
|
+
server: "docs",
|
|
4729
|
+
uri: "file://missing/",
|
|
4730
|
+
});
|
|
4731
|
+
const fields = buildToolResultFields(false, {
|
|
4732
|
+
resources: [],
|
|
4733
|
+
error: "directory not found",
|
|
4734
|
+
}, base);
|
|
4735
|
+
assert.equal(fields.status, "failed");
|
|
4736
|
+
assert.equal(fields.raw_output, "Error: directory not found");
|
|
4737
|
+
assert.deepEqual(fields.content, [
|
|
4738
|
+
{
|
|
4739
|
+
type: "content",
|
|
4740
|
+
content: { type: "text", text: "Error: directory not found" },
|
|
4741
|
+
},
|
|
4742
|
+
]);
|
|
4743
|
+
});
|
|
4744
|
+
test("buildToolResultFields parses ReadMcpResourceDir transcript JSON", () => {
|
|
4745
|
+
const base = createToolCall("tc-mcp-dir-history", "ReadMcpResourceDir", {
|
|
4746
|
+
server: "docs",
|
|
4747
|
+
uri: "file://manuals/",
|
|
4748
|
+
});
|
|
4749
|
+
const transcriptJson = JSON.stringify({
|
|
4750
|
+
resources: [
|
|
4751
|
+
{
|
|
4752
|
+
name: "api.json",
|
|
4753
|
+
uri: "file://manuals/api.json",
|
|
4754
|
+
mimeType: "application/json",
|
|
4755
|
+
},
|
|
4756
|
+
],
|
|
4757
|
+
});
|
|
4758
|
+
const fields = buildToolResultFields(false, transcriptJson, base, {
|
|
4759
|
+
type: "tool_result",
|
|
4760
|
+
tool_use_id: "tc-mcp-dir-history",
|
|
4761
|
+
content: transcriptJson,
|
|
4762
|
+
});
|
|
4763
|
+
assert.equal(fields.raw_output, "api.json - file://manuals/api.json (application/json)");
|
|
4764
|
+
assert.deepEqual(fields.content, [
|
|
4765
|
+
{
|
|
4766
|
+
type: "content",
|
|
4767
|
+
content: {
|
|
4768
|
+
type: "text",
|
|
4769
|
+
text: "api.json - file://manuals/api.json (application/json)",
|
|
4770
|
+
},
|
|
4771
|
+
},
|
|
4772
|
+
]);
|
|
4773
|
+
});
|
|
4774
|
+
test("buildToolResultFields skips invalid ReadMcpResourceDir entries", () => {
|
|
4775
|
+
const base = createToolCall("tc-mcp-dir-invalid", "ReadMcpResourceDir", {
|
|
4776
|
+
server: "docs",
|
|
4777
|
+
uri: "file://manuals/",
|
|
4778
|
+
});
|
|
4779
|
+
const fields = buildToolResultFields(false, {
|
|
4780
|
+
resources: [
|
|
4781
|
+
{ name: "missing-uri" },
|
|
4782
|
+
{ uri: "file://manuals/missing-name" },
|
|
4783
|
+
null,
|
|
4784
|
+
{
|
|
4785
|
+
name: "valid.txt",
|
|
4786
|
+
uri: "file://manuals/valid.txt",
|
|
4787
|
+
mimeType: "text/plain",
|
|
4788
|
+
},
|
|
4789
|
+
],
|
|
4790
|
+
}, base);
|
|
4791
|
+
assert.equal(fields.status, "completed");
|
|
4792
|
+
assert.equal(fields.raw_output, "valid.txt - file://manuals/valid.txt (text/plain)");
|
|
4793
|
+
assert.deepEqual(fields.content, [
|
|
4794
|
+
{
|
|
4795
|
+
type: "content",
|
|
4796
|
+
content: {
|
|
4797
|
+
type: "text",
|
|
4798
|
+
text: "valid.txt - file://manuals/valid.txt (text/plain)",
|
|
4799
|
+
},
|
|
4800
|
+
},
|
|
4801
|
+
]);
|
|
4802
|
+
});
|
|
4160
4803
|
test("buildToolResultFields preserves WebFetch artifactRead only as metadata", () => {
|
|
4161
4804
|
const base = createToolCall("tc-web-fetch-artifact", "WebFetch", {
|
|
4162
4805
|
url: "https://artifact.local/dashboard",
|
|
@@ -4377,7 +5020,7 @@ test("looksLikeAuthRequired detects login hints", () => {
|
|
|
4377
5020
|
assert.equal(looksLikeAuthRequired("normal tool output"), false);
|
|
4378
5021
|
});
|
|
4379
5022
|
test("agent sdk version compatibility check matches pinned version", () => {
|
|
4380
|
-
assert.equal(resolveInstalledAgentSdkVersion(), "0.3.
|
|
5023
|
+
assert.equal(resolveInstalledAgentSdkVersion(), "0.3.193");
|
|
4381
5024
|
assert.equal(agentSdkVersionCompatibilityError(), undefined);
|
|
4382
5025
|
});
|
|
4383
5026
|
test("mapSessionMessagesToUpdates maps message content blocks", () => {
|
|
@@ -4891,13 +5534,15 @@ test("mapSdkSessions normalizes and sorts sessions", () => {
|
|
|
4891
5534
|
},
|
|
4892
5535
|
]);
|
|
4893
5536
|
});
|
|
4894
|
-
test("buildSessionListOptions
|
|
5537
|
+
test("buildSessionListOptions includes SDK-created sessions for resume listings", () => {
|
|
4895
5538
|
assert.deepEqual(buildSessionListOptions("C:/repo"), {
|
|
4896
5539
|
dir: "C:/repo",
|
|
5540
|
+
includeProgrammatic: true,
|
|
4897
5541
|
includeWorktrees: true,
|
|
4898
5542
|
limit: 50,
|
|
4899
5543
|
});
|
|
4900
5544
|
assert.deepEqual(buildSessionListOptions(undefined), {
|
|
5545
|
+
includeProgrammatic: true,
|
|
4901
5546
|
limit: 50,
|
|
4902
5547
|
});
|
|
4903
5548
|
});
|