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