claude-code-rust 0.14.0 → 0.14.2
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 +5 -9
- package/agent-sdk/dist/bridge/account_metadata.js +1 -0
- package/agent-sdk/dist/bridge/available_commands.js +1 -1
- package/agent-sdk/dist/bridge/command_interactions.js +16 -0
- package/agent-sdk/dist/bridge/command_lifecycle.js +184 -0
- package/agent-sdk/dist/bridge/command_mcp.js +32 -0
- package/agent-sdk/dist/bridge/command_scheduler.js +136 -0
- package/agent-sdk/dist/bridge/command_session_control.js +269 -0
- package/agent-sdk/dist/bridge/command_session_data.js +203 -0
- package/agent-sdk/dist/bridge/commands.js +6 -0
- package/agent-sdk/dist/bridge/error_classification.js +24 -7
- package/agent-sdk/dist/bridge/events.js +42 -9
- package/agent-sdk/dist/bridge/history.js +7 -3
- package/agent-sdk/dist/bridge/logger.js +2 -0
- package/agent-sdk/dist/bridge/mcp.js +90 -80
- package/agent-sdk/dist/bridge/mcp_auth_adapter.js +67 -0
- package/agent-sdk/dist/bridge/mcp_monitor.js +59 -0
- package/agent-sdk/dist/bridge/message_handlers.js +35 -9
- package/agent-sdk/dist/bridge/session_lifecycle.js +70 -11
- package/agent-sdk/dist/bridge/state_parsing.js +35 -0
- package/agent-sdk/dist/bridge/tool_calls.js +45 -10
- package/agent-sdk/dist/bridge/tooling.js +106 -8
- package/agent-sdk/dist/bridge.js +135 -629
- package/package.json +8 -8
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -3,18 +3,23 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
-
import { getSessionMessages
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
|
|
7
|
+
import { parseCommandEnvelope } from "./bridge/commands.js";
|
|
8
|
+
import { parseFastModeDisabledReason, parseFastModeState, } from "./bridge/state_parsing.js";
|
|
9
|
+
import { writeEvent, failConnection, slashError, emitRuntimeReloadCompleted, emitRuntimeReloadFailed, emitSessionUpdate, setSessionListingDir, } from "./bridge/events.js";
|
|
9
10
|
import { contentFromPrompt } from "./bridge/message_handlers.js";
|
|
10
|
-
import { sessions, sessionById, createSession, closeAllSessions,
|
|
11
|
+
import { sessions, sessionById, createSession, closeAllSessions, } from "./bridge/session_lifecycle.js";
|
|
11
12
|
import { mapSessionMessagesToUpdates } from "./bridge/history.js";
|
|
12
13
|
import { emitAvailableAgentsIfChanged, mapAvailableAgents } from "./bridge/agents.js";
|
|
13
14
|
import { mapSdkSlashCommands, updateAvailableCommands } from "./bridge/available_commands.js";
|
|
14
|
-
import {
|
|
15
|
-
import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, emitReconciledMcpSnapshotFromStatuses, handleMcpAuthenticateCommand, handleMcpClearAuthCommand, handleMcpOauthCallbackUrlCommand, handleMcpReconnectCommand, handleMcpSetServersCommand, handleMcpStatusCommand, handleMcpToggleCommand, staleMcpAuthCandidates, } from "./bridge/mcp.js";
|
|
15
|
+
import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, emitReconciledMcpSnapshotFromStatuses, staleMcpAuthCandidates, } from "./bridge/mcp.js";
|
|
16
16
|
import { bridgeLogger, LOG_TARGETS, logBridgeCommandReceived } from "./bridge/logger.js";
|
|
17
|
-
import {
|
|
17
|
+
import { BridgeCommandScheduler } from "./bridge/command_scheduler.js";
|
|
18
|
+
import { handleLifecycleCommand } from "./bridge/command_lifecycle.js";
|
|
19
|
+
import { handleInteractionCommand } from "./bridge/command_interactions.js";
|
|
20
|
+
import { handleMcpCommand } from "./bridge/command_mcp.js";
|
|
21
|
+
import { handleSessionControlCommand } from "./bridge/command_session_control.js";
|
|
22
|
+
import { handleSessionDataCommand } from "./bridge/command_session_data.js";
|
|
18
23
|
// Re-exports: all symbols that tests and external consumers import from bridge.js.
|
|
19
24
|
export { AsyncQueue } from "./bridge/shared.js";
|
|
20
25
|
export { asRecordOrNull } from "./bridge/shared.js";
|
|
@@ -108,9 +113,52 @@ export async function generatePersistedSessionTitle(query, description) {
|
|
|
108
113
|
return title;
|
|
109
114
|
}
|
|
110
115
|
export async function applySessionEffort(query, effort) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
await query.applyFlagSettings({ effortLevel: effort });
|
|
117
|
+
}
|
|
118
|
+
export async function applySessionFastMode(query, enabled) {
|
|
119
|
+
try {
|
|
120
|
+
await query.applyFlagSettings({ fastMode: enabled });
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
124
|
+
throw new Error(`SDK rejected the fast-mode change: ${message}`);
|
|
125
|
+
}
|
|
126
|
+
let result;
|
|
127
|
+
try {
|
|
128
|
+
result = await query.reinitialize();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
+
throw new Error(`SDK accepted the fast-mode change but state verification failed: ${message}`);
|
|
133
|
+
}
|
|
134
|
+
const state = parseFastModeState(result.fast_mode_state);
|
|
135
|
+
if (state) {
|
|
136
|
+
const disabledReason = parseFastModeDisabledReason(result.fast_mode_disabled_reason);
|
|
137
|
+
return {
|
|
138
|
+
state,
|
|
139
|
+
...(disabledReason ? { disabled_reason: disabledReason } : {}),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (!enabled && result.fast_mode_state === undefined) {
|
|
143
|
+
return { state: "off" };
|
|
144
|
+
}
|
|
145
|
+
throw new Error("SDK accepted the fast-mode change but did not report its resulting state");
|
|
146
|
+
}
|
|
147
|
+
export function buildPromptUserMessage(command, sessionId) {
|
|
148
|
+
const content = contentFromPrompt(command);
|
|
149
|
+
if (content.length === 0) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
type: "user",
|
|
154
|
+
session_id: sessionId,
|
|
155
|
+
parent_tool_use_id: null,
|
|
156
|
+
origin: { kind: "human" },
|
|
157
|
+
message: {
|
|
158
|
+
role: "user",
|
|
159
|
+
content,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
114
162
|
}
|
|
115
163
|
export async function applySessionAgent(query, agent) {
|
|
116
164
|
const settings = { agent };
|
|
@@ -130,7 +178,7 @@ export function emitAgentConfigOptionUpdate(sessionId, agent) {
|
|
|
130
178
|
value: agent,
|
|
131
179
|
});
|
|
132
180
|
}
|
|
133
|
-
const EXPECTED_AGENT_SDK_VERSION = "0.3.
|
|
181
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.3.220";
|
|
134
182
|
const require = createRequire(import.meta.url);
|
|
135
183
|
export function resolveInstalledAgentSdkVersion() {
|
|
136
184
|
try {
|
|
@@ -233,13 +281,19 @@ export function buildRewindConversationPlan(messages, targetUserMessageId) {
|
|
|
233
281
|
resumeUpdates: mapSessionMessagesToUpdates(resolved.retainedMessages),
|
|
234
282
|
};
|
|
235
283
|
}
|
|
236
|
-
function mapRewindFilesResult(result) {
|
|
284
|
+
export function mapRewindFilesResult(result) {
|
|
285
|
+
const skippedLinks = Number.isFinite(result.skippedLinks) &&
|
|
286
|
+
Number.isInteger(result.skippedLinks) &&
|
|
287
|
+
(result.skippedLinks ?? -1) >= 0
|
|
288
|
+
? result.skippedLinks
|
|
289
|
+
: undefined;
|
|
237
290
|
return {
|
|
238
291
|
can_rewind: result.canRewind,
|
|
239
292
|
...(result.error ? { error: result.error } : {}),
|
|
240
293
|
files_changed: result.filesChanged ?? [],
|
|
241
294
|
...(result.insertions !== undefined ? { insertions: result.insertions } : {}),
|
|
242
295
|
...(result.deletions !== undefined ? { deletions: result.deletions } : {}),
|
|
296
|
+
...(skippedLinks !== undefined ? { skipped_links: skippedLinks } : {}),
|
|
243
297
|
};
|
|
244
298
|
}
|
|
245
299
|
async function rewindFiles(session, targetUserMessageId, dryRun) {
|
|
@@ -513,608 +567,57 @@ async function handleCommand(command, requestId) {
|
|
|
513
567
|
}
|
|
514
568
|
switch (command.command) {
|
|
515
569
|
case "initialize":
|
|
516
|
-
if (sdkVersionError) {
|
|
517
|
-
bridgeLogger.error({
|
|
518
|
-
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
519
|
-
eventName: "bridge_initialize_failed",
|
|
520
|
-
message: "bridge initialization failed due to unsupported SDK version",
|
|
521
|
-
outcome: "failure",
|
|
522
|
-
...(requestId ? { requestId } : {}),
|
|
523
|
-
fields: { error_message: sdkVersionError },
|
|
524
|
-
});
|
|
525
|
-
failConnection(sdkVersionError, requestId);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
setSessionListingDir(command.cwd);
|
|
529
|
-
writeEvent({
|
|
530
|
-
event: "initialized",
|
|
531
|
-
result: {
|
|
532
|
-
agent_name: "claude-rs-agent-bridge",
|
|
533
|
-
agent_version: "0.1.0",
|
|
534
|
-
auth_methods: [
|
|
535
|
-
{
|
|
536
|
-
id: "claude-login",
|
|
537
|
-
name: "Log in with Claude",
|
|
538
|
-
description: "Run `claude /login` in a terminal",
|
|
539
|
-
},
|
|
540
|
-
],
|
|
541
|
-
capabilities: {
|
|
542
|
-
prompt_image: true,
|
|
543
|
-
prompt_embedded_context: true,
|
|
544
|
-
supports_session_listing: true,
|
|
545
|
-
supports_resume_session: true,
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
}, requestId);
|
|
549
|
-
await emitSessionsList(requestId);
|
|
550
|
-
return;
|
|
551
570
|
case "create_session":
|
|
552
|
-
|
|
553
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
554
|
-
eventName: "session_create_requested",
|
|
555
|
-
message: "session creation requested",
|
|
556
|
-
outcome: "start",
|
|
557
|
-
...(requestId ? { requestId } : {}),
|
|
558
|
-
fields: {
|
|
559
|
-
cwd: command.cwd,
|
|
560
|
-
resume_requested: command.resume !== undefined,
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
setSessionListingDir(command.cwd);
|
|
564
|
-
await createSession({
|
|
565
|
-
cwd: command.cwd,
|
|
566
|
-
resume: command.resume,
|
|
567
|
-
launchSettings: command.launch_settings,
|
|
568
|
-
connectEvent: "connected",
|
|
569
|
-
requestId,
|
|
570
|
-
});
|
|
571
|
-
return;
|
|
572
|
-
case "resume_session": {
|
|
573
|
-
bridgeLogger.info({
|
|
574
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
575
|
-
eventName: "session_resume_requested",
|
|
576
|
-
message: "session resume requested",
|
|
577
|
-
outcome: "start",
|
|
578
|
-
...(requestId ? { requestId } : {}),
|
|
579
|
-
sessionId: command.session_id,
|
|
580
|
-
});
|
|
581
|
-
try {
|
|
582
|
-
const sdkSessions = await listSessions(currentSessionListOptions());
|
|
583
|
-
const matched = sdkSessions.find((entry) => entry.sessionId === command.session_id);
|
|
584
|
-
if (!matched) {
|
|
585
|
-
bridgeLogger.warn({
|
|
586
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
587
|
-
eventName: "session_resume_lookup_failed",
|
|
588
|
-
message: "session resume requested for an unknown session",
|
|
589
|
-
outcome: "failure",
|
|
590
|
-
...(requestId ? { requestId } : {}),
|
|
591
|
-
sessionId: command.session_id,
|
|
592
|
-
fields: { reason: "unknown_session" },
|
|
593
|
-
});
|
|
594
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
setSessionListingDir(matched.cwd ?? process.cwd());
|
|
598
|
-
const historyMessages = await getSessionMessages(command.session_id, matched.cwd
|
|
599
|
-
? { dir: matched.cwd, includeSystemMessages: true }
|
|
600
|
-
: { includeSystemMessages: true });
|
|
601
|
-
const resumeUpdates = mapSessionMessagesToUpdates(historyMessages);
|
|
602
|
-
const staleSessions = Array.from(sessions.values());
|
|
603
|
-
const hadActiveSession = staleSessions.length > 0;
|
|
604
|
-
bridgeLogger.info({
|
|
605
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
606
|
-
eventName: "session_resume_history_loaded",
|
|
607
|
-
message: "session resume history loaded",
|
|
608
|
-
outcome: "success",
|
|
609
|
-
...(requestId ? { requestId } : {}),
|
|
610
|
-
sessionId: command.session_id,
|
|
611
|
-
fields: {
|
|
612
|
-
history_update_count: resumeUpdates.length,
|
|
613
|
-
stale_session_count: staleSessions.length,
|
|
614
|
-
},
|
|
615
|
-
});
|
|
616
|
-
await createSession({
|
|
617
|
-
cwd: matched.cwd ?? process.cwd(),
|
|
618
|
-
resume: command.session_id,
|
|
619
|
-
launchSettings: command.launch_settings,
|
|
620
|
-
...(resumeUpdates.length > 0 ? { resumeUpdates } : {}),
|
|
621
|
-
connectEvent: hadActiveSession ? "session_replaced" : "connected",
|
|
622
|
-
requestId,
|
|
623
|
-
...(hadActiveSession ? { sessionsToCloseAfterConnect: staleSessions } : {}),
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
catch (error) {
|
|
627
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
628
|
-
bridgeLogger.error({
|
|
629
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
630
|
-
eventName: "session_resume_failed",
|
|
631
|
-
message: "session resume failed",
|
|
632
|
-
outcome: "failure",
|
|
633
|
-
...(requestId ? { requestId } : {}),
|
|
634
|
-
sessionId: command.session_id,
|
|
635
|
-
fields: { error_message: message },
|
|
636
|
-
});
|
|
637
|
-
slashError(command.session_id, `failed to resume session: ${message}`, requestId);
|
|
638
|
-
}
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
571
|
+
case "resume_session":
|
|
641
572
|
case "new_session":
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (!session) {
|
|
662
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const content = contentFromPrompt(command);
|
|
666
|
-
if (content.length === 0) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const message = {
|
|
670
|
-
type: "user",
|
|
671
|
-
session_id: session.sessionId,
|
|
672
|
-
parent_tool_use_id: null,
|
|
673
|
-
message: {
|
|
674
|
-
role: "user",
|
|
675
|
-
content,
|
|
676
|
-
},
|
|
677
|
-
};
|
|
678
|
-
session.input.enqueue(message);
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
case "cancel_turn": {
|
|
682
|
-
await dispatchCancelTurnCommand(command, { requestId, sessionById, slashError });
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
case "set_model": {
|
|
686
|
-
const session = sessionById(command.session_id);
|
|
687
|
-
if (!session) {
|
|
688
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
bridgeLogger.info({
|
|
692
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
693
|
-
eventName: "set_model_started",
|
|
694
|
-
message: "set model started",
|
|
695
|
-
outcome: "start",
|
|
696
|
-
sessionId: session.sessionId,
|
|
697
|
-
requestId,
|
|
698
|
-
fields: {
|
|
699
|
-
requested_model: command.model,
|
|
700
|
-
previous_requested_model: session.requestedModelId,
|
|
701
|
-
previous_session_model: session.model,
|
|
702
|
-
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
703
|
-
previous_current_model: session.currentModel?.resolved_id,
|
|
704
|
-
},
|
|
573
|
+
case "shutdown":
|
|
574
|
+
await handleLifecycleCommand(command, requestId, sdkVersionError);
|
|
575
|
+
return;
|
|
576
|
+
case "prompt":
|
|
577
|
+
case "cancel_turn":
|
|
578
|
+
case "set_model":
|
|
579
|
+
case "set_mode":
|
|
580
|
+
case "set_effort":
|
|
581
|
+
case "set_agent":
|
|
582
|
+
case "set_fast_mode":
|
|
583
|
+
case "reload_plugins":
|
|
584
|
+
await handleSessionControlCommand(command, requestId, {
|
|
585
|
+
buildPromptUserMessage,
|
|
586
|
+
applySessionEffort,
|
|
587
|
+
applySessionAgent,
|
|
588
|
+
applySessionFastMode,
|
|
589
|
+
emitEffortConfigOptionUpdate,
|
|
590
|
+
emitAgentConfigOptionUpdate,
|
|
591
|
+
handleReloadPluginsCommand,
|
|
705
592
|
});
|
|
706
|
-
try {
|
|
707
|
-
const previousRequestedModel = session.requestedModelId;
|
|
708
|
-
const previousSessionModel = session.model;
|
|
709
|
-
await session.query.setModel(command.model);
|
|
710
|
-
session.requestedModelId = command.model;
|
|
711
|
-
session.model = command.model;
|
|
712
|
-
const invalidatedResolvedRuntimeModel = shouldInvalidateResolvedRuntimeModel(previousRequestedModel, previousSessionModel, command.model);
|
|
713
|
-
if (invalidatedResolvedRuntimeModel) {
|
|
714
|
-
session.resolvedRuntimeModelId = undefined;
|
|
715
|
-
}
|
|
716
|
-
const changed = refreshCurrentModel(session, true);
|
|
717
|
-
const forcedCurrentModelUpdate = !changed && emitCurrentModelUpdate(session);
|
|
718
|
-
bridgeLogger.info({
|
|
719
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
720
|
-
eventName: "set_model_succeeded",
|
|
721
|
-
message: "set model completed",
|
|
722
|
-
outcome: "success",
|
|
723
|
-
sessionId: session.sessionId,
|
|
724
|
-
requestId,
|
|
725
|
-
fields: {
|
|
726
|
-
requested_model: command.model,
|
|
727
|
-
session_model_after: session.model,
|
|
728
|
-
resolved_runtime_model_after: session.resolvedRuntimeModelId,
|
|
729
|
-
current_model_after: session.currentModel?.resolved_id,
|
|
730
|
-
current_model_display_short: session.currentModel?.display_name_short,
|
|
731
|
-
current_model_display_long: session.currentModel?.display_name_long,
|
|
732
|
-
current_model_update_emitted: changed || forcedCurrentModelUpdate,
|
|
733
|
-
current_model_update_forced: forcedCurrentModelUpdate,
|
|
734
|
-
resolved_runtime_model_invalidated: invalidatedResolvedRuntimeModel,
|
|
735
|
-
},
|
|
736
|
-
});
|
|
737
|
-
refreshSupportedModesForSession(session);
|
|
738
|
-
if (session.mode) {
|
|
739
|
-
emitSessionUpdate(session.sessionId, {
|
|
740
|
-
type: "mode_state_update",
|
|
741
|
-
mode: buildModeState(session, session.mode),
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
catch (error) {
|
|
746
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
747
|
-
bridgeLogger.warn({
|
|
748
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
749
|
-
eventName: "set_model_failed",
|
|
750
|
-
message: "set model failed",
|
|
751
|
-
outcome: "failure",
|
|
752
|
-
sessionId: session.sessionId,
|
|
753
|
-
requestId,
|
|
754
|
-
fields: {
|
|
755
|
-
requested_model: command.model,
|
|
756
|
-
error_message: message,
|
|
757
|
-
previous_requested_model: session.requestedModelId,
|
|
758
|
-
previous_session_model: session.model,
|
|
759
|
-
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
760
|
-
previous_current_model: session.currentModel?.resolved_id,
|
|
761
|
-
},
|
|
762
|
-
});
|
|
763
|
-
slashError(command.session_id, `failed to set model: ${message}`, requestId);
|
|
764
|
-
}
|
|
765
593
|
return;
|
|
766
|
-
|
|
767
|
-
case "
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
const mode = toPermissionMode(command.mode);
|
|
774
|
-
if (!mode) {
|
|
775
|
-
slashError(command.session_id, `unsupported mode: ${command.mode}`, requestId);
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
try {
|
|
779
|
-
await session.query.setPermissionMode(mode);
|
|
780
|
-
session.mode = mode;
|
|
781
|
-
refreshSupportedModesForSession(session);
|
|
782
|
-
emitSessionUpdate(session.sessionId, {
|
|
783
|
-
type: "current_mode_update",
|
|
784
|
-
current_mode_id: mode,
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
catch (error) {
|
|
788
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
789
|
-
if (permissionModeFailureLooksUnsupported(mode, message)) {
|
|
790
|
-
const changed = markModeUnavailableForSession(session, mode);
|
|
791
|
-
if (changed && session.mode) {
|
|
792
|
-
emitSessionUpdate(session.sessionId, {
|
|
793
|
-
type: "mode_state_update",
|
|
794
|
-
mode: buildModeState(session, session.mode),
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
slashError(command.session_id, `failed to set mode to ${mode}: ${message}`, requestId);
|
|
799
|
-
}
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
case "set_effort": {
|
|
803
|
-
const session = sessionById(command.session_id);
|
|
804
|
-
if (!session) {
|
|
805
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
try {
|
|
809
|
-
await applySessionEffort(session.query, command.effort);
|
|
810
|
-
emitEffortConfigOptionUpdate(session.sessionId, command.effort);
|
|
811
|
-
}
|
|
812
|
-
catch (error) {
|
|
813
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
814
|
-
slashError(command.session_id, `failed to set effort: ${message}`, requestId);
|
|
815
|
-
}
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
case "set_agent": {
|
|
819
|
-
const session = sessionById(command.session_id);
|
|
820
|
-
if (!session) {
|
|
821
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
try {
|
|
825
|
-
await applySessionAgent(session.query, command.agent);
|
|
826
|
-
emitAgentConfigOptionUpdate(session.sessionId, command.agent);
|
|
827
|
-
}
|
|
828
|
-
catch (error) {
|
|
829
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
830
|
-
slashError(command.session_id, `failed to set agent: ${message}`, requestId);
|
|
831
|
-
}
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
case "generate_session_title": {
|
|
835
|
-
const session = sessionById(command.session_id);
|
|
836
|
-
if (!session) {
|
|
837
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
try {
|
|
841
|
-
await generatePersistedSessionTitle(session.query, command.description);
|
|
842
|
-
setSessionListingDir(session.cwd);
|
|
843
|
-
await emitSessionsList(requestId);
|
|
844
|
-
}
|
|
845
|
-
catch (error) {
|
|
846
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
847
|
-
slashError(command.session_id, `failed to generate session title: ${message}`, requestId);
|
|
848
|
-
}
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
case "rename_session": {
|
|
852
|
-
const session = sessionById(command.session_id);
|
|
853
|
-
if (!session) {
|
|
854
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
try {
|
|
858
|
-
await renameSession(command.session_id, command.title, buildSessionMutationOptions(session.cwd));
|
|
859
|
-
setSessionListingDir(session.cwd);
|
|
860
|
-
await emitSessionsList(requestId);
|
|
861
|
-
}
|
|
862
|
-
catch (error) {
|
|
863
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
864
|
-
slashError(command.session_id, `failed to rename session: ${message}`, requestId);
|
|
865
|
-
}
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
case "get_status_snapshot": {
|
|
869
|
-
const session = sessionById(command.session_id);
|
|
870
|
-
if (!session) {
|
|
871
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
try {
|
|
875
|
-
const account = await session.query.accountInfo();
|
|
876
|
-
bridgeLogger.info({
|
|
877
|
-
target: LOG_TARGETS.APP_AUTH,
|
|
878
|
-
eventName: "status_snapshot_emitted",
|
|
879
|
-
message: "status snapshot emitted",
|
|
880
|
-
outcome: "success",
|
|
881
|
-
...(requestId ? { requestId } : {}),
|
|
882
|
-
sessionId: session.sessionId,
|
|
883
|
-
fields: {
|
|
884
|
-
has_email: typeof account.email === "string" && account.email.trim().length > 0,
|
|
885
|
-
has_organization: account.organization !== undefined,
|
|
886
|
-
subscription_type: account.subscriptionType,
|
|
887
|
-
token_source: account.tokenSource,
|
|
888
|
-
api_key_source: account.apiKeySource,
|
|
889
|
-
api_provider: account.apiProvider,
|
|
890
|
-
},
|
|
891
|
-
});
|
|
892
|
-
writeEvent({
|
|
893
|
-
event: "status_snapshot",
|
|
894
|
-
session_id: session.sessionId,
|
|
895
|
-
account: mapSdkAccountInfo(account),
|
|
896
|
-
}, requestId);
|
|
897
|
-
}
|
|
898
|
-
catch (error) {
|
|
899
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
900
|
-
bridgeLogger.warn({
|
|
901
|
-
target: LOG_TARGETS.APP_AUTH,
|
|
902
|
-
eventName: "status_snapshot_failed",
|
|
903
|
-
message: "failed to build status snapshot",
|
|
904
|
-
outcome: "failure",
|
|
905
|
-
...(requestId ? { requestId } : {}),
|
|
906
|
-
sessionId: session.sessionId,
|
|
907
|
-
fields: { error_message: message },
|
|
908
|
-
});
|
|
909
|
-
throw error;
|
|
910
|
-
}
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
case "get_context_usage": {
|
|
914
|
-
const session = sessionById(command.session_id);
|
|
915
|
-
if (!session) {
|
|
916
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
try {
|
|
920
|
-
const usage = await session.query.getContextUsage();
|
|
921
|
-
if (typeof usage.model === "string" && usage.model.trim().length > 0) {
|
|
922
|
-
session.resolvedRuntimeModelId = usage.model.trim();
|
|
923
|
-
refreshCurrentModel(session, true);
|
|
924
|
-
}
|
|
925
|
-
const rawPercentage = typeof usage.percentage === "number" ? usage.percentage : undefined;
|
|
926
|
-
const normalizedPercentage = rawPercentage === undefined || !Number.isFinite(rawPercentage)
|
|
927
|
-
? undefined
|
|
928
|
-
: Math.max(0, Math.min(100, Math.round(rawPercentage)));
|
|
929
|
-
bridgeLogger.debug({
|
|
930
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
931
|
-
eventName: "context_usage_succeeded",
|
|
932
|
-
message: "session context usage received from SDK",
|
|
933
|
-
outcome: "success",
|
|
934
|
-
...(requestId ? { requestId } : {}),
|
|
935
|
-
sessionId: session.sessionId,
|
|
936
|
-
fields: {
|
|
937
|
-
raw_percentage: rawPercentage,
|
|
938
|
-
normalized_percentage: normalizedPercentage,
|
|
939
|
-
total_tokens: typeof usage.totalTokens === "number" ? usage.totalTokens : undefined,
|
|
940
|
-
max_tokens: typeof usage.maxTokens === "number" ? usage.maxTokens : undefined,
|
|
941
|
-
raw_max_tokens: typeof usage.rawMaxTokens === "number" ? usage.rawMaxTokens : undefined,
|
|
942
|
-
model: typeof usage.model === "string" ? usage.model : undefined,
|
|
943
|
-
},
|
|
944
|
-
});
|
|
945
|
-
writeEvent({
|
|
946
|
-
event: "context_usage",
|
|
947
|
-
session_id: session.sessionId,
|
|
948
|
-
...(normalizedPercentage !== undefined ? { percentage: normalizedPercentage } : {}),
|
|
949
|
-
}, requestId);
|
|
950
|
-
}
|
|
951
|
-
catch (error) {
|
|
952
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
953
|
-
bridgeLogger.warn({
|
|
954
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
955
|
-
eventName: "context_usage_failed",
|
|
956
|
-
message: "failed to get session context usage",
|
|
957
|
-
outcome: "failure",
|
|
958
|
-
...(requestId ? { requestId } : {}),
|
|
959
|
-
sessionId: session.sessionId,
|
|
960
|
-
fields: { error_message: message },
|
|
961
|
-
});
|
|
962
|
-
writeEvent({
|
|
963
|
-
event: "context_usage",
|
|
964
|
-
session_id: session.sessionId,
|
|
965
|
-
}, requestId);
|
|
966
|
-
}
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
case "get_rewind_targets": {
|
|
970
|
-
const session = sessionById(command.session_id);
|
|
971
|
-
if (!session) {
|
|
972
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
973
|
-
return;
|
|
974
|
-
}
|
|
975
|
-
try {
|
|
976
|
-
const historyMessages = await getSessionMessages(command.session_id, {
|
|
977
|
-
dir: session.cwd,
|
|
978
|
-
includeSystemMessages: true,
|
|
979
|
-
});
|
|
980
|
-
const targets = rewindTargetsFromSessionMessages(historyMessages);
|
|
981
|
-
bridgeLogger.info({
|
|
982
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
983
|
-
eventName: "rewind_targets_loaded",
|
|
984
|
-
message: "rewind targets loaded from session history",
|
|
985
|
-
outcome: "success",
|
|
986
|
-
...(requestId ? { requestId } : {}),
|
|
987
|
-
sessionId: session.sessionId,
|
|
988
|
-
fields: {
|
|
989
|
-
history_message_count: historyMessages.length,
|
|
990
|
-
target_count: targets.length,
|
|
991
|
-
},
|
|
992
|
-
});
|
|
993
|
-
writeEvent({
|
|
994
|
-
event: "rewind_targets",
|
|
995
|
-
session_id: session.sessionId,
|
|
996
|
-
targets,
|
|
997
|
-
}, requestId);
|
|
998
|
-
}
|
|
999
|
-
catch (error) {
|
|
1000
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1001
|
-
bridgeLogger.warn({
|
|
1002
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
1003
|
-
eventName: "rewind_targets_failed",
|
|
1004
|
-
message: "failed to load rewind targets",
|
|
1005
|
-
outcome: "failure",
|
|
1006
|
-
...(requestId ? { requestId } : {}),
|
|
1007
|
-
sessionId: session.sessionId,
|
|
1008
|
-
fields: { error_message: message },
|
|
1009
|
-
});
|
|
1010
|
-
slashError(command.session_id, `failed to load rewind targets: ${message}`, requestId);
|
|
1011
|
-
}
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
594
|
+
case "generate_session_title":
|
|
595
|
+
case "rename_session":
|
|
596
|
+
case "get_status_snapshot":
|
|
597
|
+
case "get_context_usage":
|
|
598
|
+
case "get_rewind_targets":
|
|
1014
599
|
case "rewind":
|
|
1015
|
-
await
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
await handleReloadPluginsCommand(session, requestId);
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
|
-
case "mcp_status": {
|
|
1027
|
-
const session = sessionById(command.session_id);
|
|
1028
|
-
if (!session) {
|
|
1029
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
1030
|
-
return;
|
|
1031
|
-
}
|
|
1032
|
-
await handleMcpStatusCommand(session, requestId);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
case "mcp_reconnect": {
|
|
1036
|
-
const session = sessionById(command.session_id);
|
|
1037
|
-
if (!session) {
|
|
1038
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
await handleMcpReconnectCommand(session, command, requestId);
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
case "mcp_toggle": {
|
|
1045
|
-
const session = sessionById(command.session_id);
|
|
1046
|
-
if (!session) {
|
|
1047
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
await handleMcpToggleCommand(session, command, requestId);
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
case "mcp_set_servers": {
|
|
1054
|
-
const session = sessionById(command.session_id);
|
|
1055
|
-
if (!session) {
|
|
1056
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
await handleMcpSetServersCommand(session, command, requestId);
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
case "mcp_authenticate": {
|
|
1063
|
-
const session = sessionById(command.session_id);
|
|
1064
|
-
if (!session) {
|
|
1065
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
await handleMcpAuthenticateCommand(session, command, requestId);
|
|
600
|
+
await handleSessionDataCommand(command, requestId, {
|
|
601
|
+
generatePersistedSessionTitle,
|
|
602
|
+
buildSessionMutationOptions,
|
|
603
|
+
rewindTargetsFromSessionMessages,
|
|
604
|
+
handleRewind,
|
|
605
|
+
});
|
|
1069
606
|
return;
|
|
1070
|
-
|
|
1071
|
-
case "
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
await
|
|
607
|
+
case "mcp_status":
|
|
608
|
+
case "mcp_reconnect":
|
|
609
|
+
case "mcp_toggle":
|
|
610
|
+
case "mcp_set_servers":
|
|
611
|
+
case "mcp_authenticate":
|
|
612
|
+
case "mcp_clear_auth":
|
|
613
|
+
case "mcp_oauth_callback_url":
|
|
614
|
+
await handleMcpCommand(command, requestId);
|
|
1078
615
|
return;
|
|
1079
|
-
}
|
|
1080
|
-
case "mcp_oauth_callback_url": {
|
|
1081
|
-
const session = sessionById(command.session_id);
|
|
1082
|
-
if (!session) {
|
|
1083
|
-
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
await handleMcpOauthCallbackUrlCommand(session, command, requestId);
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
616
|
case "permission_response":
|
|
1090
|
-
handlePermissionResponse(command);
|
|
1091
|
-
return;
|
|
1092
617
|
case "question_response":
|
|
1093
|
-
handleQuestionResponse(command);
|
|
1094
|
-
return;
|
|
1095
618
|
case "user_dialog_response":
|
|
1096
|
-
handleUserDialogResponse(command);
|
|
1097
|
-
return;
|
|
1098
619
|
case "elicitation_response":
|
|
1099
|
-
|
|
1100
|
-
return;
|
|
1101
|
-
case "shutdown":
|
|
1102
|
-
bridgeLogger.info({
|
|
1103
|
-
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
1104
|
-
eventName: "bridge_shutdown_requested",
|
|
1105
|
-
message: "bridge shutdown requested",
|
|
1106
|
-
outcome: "start",
|
|
1107
|
-
...(requestId ? { requestId } : {}),
|
|
1108
|
-
});
|
|
1109
|
-
await closeAllSessions({ reason: "bridge_shutdown_requested", requestId });
|
|
1110
|
-
bridgeLogger.info({
|
|
1111
|
-
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
1112
|
-
eventName: "bridge_shutdown_completed",
|
|
1113
|
-
message: "bridge shutdown completed",
|
|
1114
|
-
outcome: "success",
|
|
1115
|
-
...(requestId ? { requestId } : {}),
|
|
1116
|
-
});
|
|
1117
|
-
process.exit(0);
|
|
620
|
+
handleInteractionCommand(command);
|
|
1118
621
|
return;
|
|
1119
622
|
default:
|
|
1120
623
|
bridgeLogger.error({
|
|
@@ -1143,34 +646,35 @@ function main() {
|
|
|
1143
646
|
input: process.stdin,
|
|
1144
647
|
crlfDelay: Number.POSITIVE_INFINITY,
|
|
1145
648
|
});
|
|
649
|
+
const commandScheduler = new BridgeCommandScheduler();
|
|
1146
650
|
rl.on("line", (line) => {
|
|
1147
651
|
if (line.trim().length === 0) {
|
|
1148
652
|
return;
|
|
1149
653
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
654
|
+
let parsed;
|
|
655
|
+
try {
|
|
656
|
+
parsed = parseCommandEnvelope(line);
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
660
|
+
const requestId = requestIdFromCommandLine(line);
|
|
661
|
+
bridgeLogger.error({
|
|
662
|
+
target: LOG_TARGETS.BRIDGE_PROTOCOL,
|
|
663
|
+
eventName: "bridge_command_decode_failed",
|
|
664
|
+
message: "failed to decode bridge command envelope",
|
|
665
|
+
outcome: "failure",
|
|
666
|
+
...(requestId ? { requestId } : {}),
|
|
667
|
+
sizeBytes: Buffer.byteLength(line),
|
|
668
|
+
fields: {
|
|
669
|
+
preview: line.slice(0, 240),
|
|
670
|
+
preview_chars: Math.min(line.length, 240),
|
|
671
|
+
error_message: message,
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
failConnection(`invalid command envelope: ${message}`, requestId);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
commandScheduler.schedule(parsed.command, async () => {
|
|
1174
678
|
try {
|
|
1175
679
|
await handleCommand(parsed.command, parsed.requestId);
|
|
1176
680
|
}
|
|
@@ -1182,7 +686,8 @@ function main() {
|
|
|
1182
686
|
message: "bridge command handler failed",
|
|
1183
687
|
outcome: "failure",
|
|
1184
688
|
...(parsed.requestId ? { requestId: parsed.requestId } : {}),
|
|
1185
|
-
...(parsed.command.command === "create_session" ||
|
|
689
|
+
...(parsed.command.command === "create_session" ||
|
|
690
|
+
parsed.command.command === "new_session"
|
|
1186
691
|
? {}
|
|
1187
692
|
: "session_id" in parsed.command
|
|
1188
693
|
? { sessionId: parsed.command.session_id }
|
|
@@ -1194,9 +699,10 @@ function main() {
|
|
|
1194
699
|
});
|
|
1195
700
|
failConnection(`bridge command failed (${parsed.command.command}): ${message}`, parsed.requestId);
|
|
1196
701
|
}
|
|
1197
|
-
})
|
|
702
|
+
});
|
|
1198
703
|
});
|
|
1199
704
|
rl.on("close", () => {
|
|
705
|
+
commandScheduler.stopAccepting();
|
|
1200
706
|
bridgeLogger.info({
|
|
1201
707
|
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
1202
708
|
eventName: "bridge_input_closed",
|