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
|
@@ -48,7 +48,7 @@ function shouldAcceptAvailableCommandsSnapshot(session, source, commands) {
|
|
|
48
48
|
if (source === "supportedCommands" && current.commands.length > 0) {
|
|
49
49
|
return {
|
|
50
50
|
accept: false,
|
|
51
|
-
reason: "supportedCommands
|
|
51
|
+
reason: "supportedCommands bootstrap cannot replace an existing snapshot",
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
if (commands.length === 0) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, handleUserDialogResponse, } from "./session_lifecycle.js";
|
|
2
|
+
export function handleInteractionCommand(command) {
|
|
3
|
+
switch (command.command) {
|
|
4
|
+
case "permission_response":
|
|
5
|
+
handlePermissionResponse(command);
|
|
6
|
+
return;
|
|
7
|
+
case "question_response":
|
|
8
|
+
handleQuestionResponse(command);
|
|
9
|
+
return;
|
|
10
|
+
case "user_dialog_response":
|
|
11
|
+
handleUserDialogResponse(command);
|
|
12
|
+
return;
|
|
13
|
+
case "elicitation_response":
|
|
14
|
+
handleElicitationResponse(command);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { getSessionMessages, listSessions, } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { currentSessionListOptions, emitSessionsList, failConnection, setSessionListingDir, slashError, writeEvent, } from "./events.js";
|
|
3
|
+
import { mapSessionMessagesToUpdates } from "./history.js";
|
|
4
|
+
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
5
|
+
import { closeAllSessions, createSession, sessions, } from "./session_lifecycle.js";
|
|
6
|
+
export async function handleLifecycleCommand(command, requestId, sdkVersionError) {
|
|
7
|
+
switch (command.command) {
|
|
8
|
+
case "initialize":
|
|
9
|
+
await initialize(command, requestId, sdkVersionError);
|
|
10
|
+
return;
|
|
11
|
+
case "create_session":
|
|
12
|
+
await create(command, requestId);
|
|
13
|
+
return;
|
|
14
|
+
case "resume_session":
|
|
15
|
+
await resume(command, requestId);
|
|
16
|
+
return;
|
|
17
|
+
case "new_session":
|
|
18
|
+
await replace(command, requestId);
|
|
19
|
+
return;
|
|
20
|
+
case "shutdown":
|
|
21
|
+
await shutdown(requestId);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function initialize(command, requestId, sdkVersionError) {
|
|
25
|
+
if (sdkVersionError) {
|
|
26
|
+
bridgeLogger.error({
|
|
27
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
28
|
+
eventName: "bridge_initialize_failed",
|
|
29
|
+
message: "bridge initialization failed due to unsupported SDK version",
|
|
30
|
+
outcome: "failure",
|
|
31
|
+
...(requestId ? { requestId } : {}),
|
|
32
|
+
fields: { error_message: sdkVersionError },
|
|
33
|
+
});
|
|
34
|
+
failConnection(sdkVersionError, requestId);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
setSessionListingDir(command.cwd);
|
|
38
|
+
writeEvent({
|
|
39
|
+
event: "initialized",
|
|
40
|
+
result: {
|
|
41
|
+
agent_name: "claude-rs-agent-bridge",
|
|
42
|
+
agent_version: "0.1.0",
|
|
43
|
+
auth_methods: [
|
|
44
|
+
{
|
|
45
|
+
id: "claude-login",
|
|
46
|
+
name: "Log in with Claude",
|
|
47
|
+
description: "Run `claude /login` in a terminal",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
capabilities: {
|
|
51
|
+
prompt_image: true,
|
|
52
|
+
prompt_embedded_context: true,
|
|
53
|
+
supports_session_listing: true,
|
|
54
|
+
supports_resume_session: true,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}, requestId);
|
|
58
|
+
await emitSessionsList(requestId);
|
|
59
|
+
}
|
|
60
|
+
async function create(command, requestId) {
|
|
61
|
+
bridgeLogger.info({
|
|
62
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
63
|
+
eventName: "session_create_requested",
|
|
64
|
+
message: "session creation requested",
|
|
65
|
+
outcome: "start",
|
|
66
|
+
...(requestId ? { requestId } : {}),
|
|
67
|
+
fields: {
|
|
68
|
+
cwd: command.cwd,
|
|
69
|
+
resume_requested: command.resume !== undefined,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
setSessionListingDir(command.cwd);
|
|
73
|
+
await createSession({
|
|
74
|
+
cwd: command.cwd,
|
|
75
|
+
resume: command.resume,
|
|
76
|
+
launchSettings: command.launch_settings,
|
|
77
|
+
connectEvent: "connected",
|
|
78
|
+
requestId,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function resume(command, requestId) {
|
|
82
|
+
bridgeLogger.info({
|
|
83
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
84
|
+
eventName: "session_resume_requested",
|
|
85
|
+
message: "session resume requested",
|
|
86
|
+
outcome: "start",
|
|
87
|
+
...(requestId ? { requestId } : {}),
|
|
88
|
+
sessionId: command.session_id,
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
const sdkSessions = await listSessions(currentSessionListOptions());
|
|
92
|
+
const matched = sdkSessions.find((entry) => entry.sessionId === command.session_id);
|
|
93
|
+
if (!matched) {
|
|
94
|
+
bridgeLogger.warn({
|
|
95
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
96
|
+
eventName: "session_resume_lookup_failed",
|
|
97
|
+
message: "session resume requested for an unknown session",
|
|
98
|
+
outcome: "failure",
|
|
99
|
+
...(requestId ? { requestId } : {}),
|
|
100
|
+
sessionId: command.session_id,
|
|
101
|
+
fields: { reason: "unknown_session" },
|
|
102
|
+
});
|
|
103
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
setSessionListingDir(matched.cwd ?? process.cwd());
|
|
107
|
+
const historyMessages = await getSessionMessages(command.session_id, matched.cwd
|
|
108
|
+
? { dir: matched.cwd, includeSystemMessages: true }
|
|
109
|
+
: { includeSystemMessages: true });
|
|
110
|
+
const resumeUpdates = mapSessionMessagesToUpdates(historyMessages);
|
|
111
|
+
const staleSessions = Array.from(sessions.values());
|
|
112
|
+
const hadActiveSession = staleSessions.length > 0;
|
|
113
|
+
bridgeLogger.info({
|
|
114
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
115
|
+
eventName: "session_resume_history_loaded",
|
|
116
|
+
message: "session resume history loaded",
|
|
117
|
+
outcome: "success",
|
|
118
|
+
...(requestId ? { requestId } : {}),
|
|
119
|
+
sessionId: command.session_id,
|
|
120
|
+
fields: {
|
|
121
|
+
history_update_count: resumeUpdates.length,
|
|
122
|
+
stale_session_count: staleSessions.length,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
await createSession({
|
|
126
|
+
cwd: matched.cwd ?? process.cwd(),
|
|
127
|
+
resume: command.session_id,
|
|
128
|
+
launchSettings: command.launch_settings,
|
|
129
|
+
...(resumeUpdates.length > 0 ? { resumeUpdates } : {}),
|
|
130
|
+
connectEvent: hadActiveSession ? "session_replaced" : "connected",
|
|
131
|
+
requestId,
|
|
132
|
+
...(hadActiveSession ? { sessionsToCloseAfterConnect: staleSessions } : {}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137
|
+
bridgeLogger.error({
|
|
138
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
139
|
+
eventName: "session_resume_failed",
|
|
140
|
+
message: "session resume failed",
|
|
141
|
+
outcome: "failure",
|
|
142
|
+
...(requestId ? { requestId } : {}),
|
|
143
|
+
sessionId: command.session_id,
|
|
144
|
+
fields: { error_message: message },
|
|
145
|
+
});
|
|
146
|
+
slashError(command.session_id, `failed to resume session: ${message}`, requestId);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function replace(command, requestId) {
|
|
150
|
+
bridgeLogger.info({
|
|
151
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
152
|
+
eventName: "session_new_requested",
|
|
153
|
+
message: "replacement session requested",
|
|
154
|
+
outcome: "start",
|
|
155
|
+
...(requestId ? { requestId } : {}),
|
|
156
|
+
fields: { cwd: command.cwd },
|
|
157
|
+
});
|
|
158
|
+
await closeAllSessions({ reason: "new_session_requested", requestId });
|
|
159
|
+
setSessionListingDir(command.cwd);
|
|
160
|
+
await createSession({
|
|
161
|
+
cwd: command.cwd,
|
|
162
|
+
launchSettings: command.launch_settings,
|
|
163
|
+
connectEvent: "session_replaced",
|
|
164
|
+
requestId,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function shutdown(requestId) {
|
|
168
|
+
bridgeLogger.info({
|
|
169
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
170
|
+
eventName: "bridge_shutdown_requested",
|
|
171
|
+
message: "bridge shutdown requested",
|
|
172
|
+
outcome: "start",
|
|
173
|
+
...(requestId ? { requestId } : {}),
|
|
174
|
+
});
|
|
175
|
+
await closeAllSessions({ reason: "bridge_shutdown_requested", requestId });
|
|
176
|
+
bridgeLogger.info({
|
|
177
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
178
|
+
eventName: "bridge_shutdown_completed",
|
|
179
|
+
message: "bridge shutdown completed",
|
|
180
|
+
outcome: "success",
|
|
181
|
+
...(requestId ? { requestId } : {}),
|
|
182
|
+
});
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { slashError } from "./events.js";
|
|
2
|
+
import { handleMcpAuthenticateCommand, handleMcpClearAuthCommand, handleMcpOauthCallbackUrlCommand, handleMcpReconnectCommand, handleMcpSetServersCommand, handleMcpStatusCommand, handleMcpToggleCommand, } from "./mcp.js";
|
|
3
|
+
import { sessionById } from "./session_lifecycle.js";
|
|
4
|
+
export async function handleMcpCommand(command, requestId) {
|
|
5
|
+
const session = sessionById(command.session_id);
|
|
6
|
+
if (!session) {
|
|
7
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
switch (command.command) {
|
|
11
|
+
case "mcp_status":
|
|
12
|
+
await handleMcpStatusCommand(session, requestId);
|
|
13
|
+
return;
|
|
14
|
+
case "mcp_reconnect":
|
|
15
|
+
await handleMcpReconnectCommand(session, command, requestId);
|
|
16
|
+
return;
|
|
17
|
+
case "mcp_toggle":
|
|
18
|
+
await handleMcpToggleCommand(session, command, requestId);
|
|
19
|
+
return;
|
|
20
|
+
case "mcp_set_servers":
|
|
21
|
+
await handleMcpSetServersCommand(session, command, requestId);
|
|
22
|
+
return;
|
|
23
|
+
case "mcp_authenticate":
|
|
24
|
+
await handleMcpAuthenticateCommand(session, command, requestId);
|
|
25
|
+
return;
|
|
26
|
+
case "mcp_clear_auth":
|
|
27
|
+
await handleMcpClearAuthCommand(session, command, requestId);
|
|
28
|
+
return;
|
|
29
|
+
case "mcp_oauth_callback_url":
|
|
30
|
+
await handleMcpOauthCallbackUrlCommand(session, command, requestId);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const LIFECYCLE_COMMANDS = new Set([
|
|
2
|
+
"initialize",
|
|
3
|
+
"create_session",
|
|
4
|
+
"resume_session",
|
|
5
|
+
"new_session",
|
|
6
|
+
"rewind",
|
|
7
|
+
"shutdown",
|
|
8
|
+
]);
|
|
9
|
+
const UNBLOCKING_COMMANDS = new Set([
|
|
10
|
+
"cancel_turn",
|
|
11
|
+
"permission_response",
|
|
12
|
+
"question_response",
|
|
13
|
+
"user_dialog_response",
|
|
14
|
+
"elicitation_response",
|
|
15
|
+
"mcp_oauth_callback_url",
|
|
16
|
+
]);
|
|
17
|
+
function commandLane(command) {
|
|
18
|
+
if (UNBLOCKING_COMMANDS.has(command.command)) {
|
|
19
|
+
return { kind: "unblocking" };
|
|
20
|
+
}
|
|
21
|
+
if (LIFECYCLE_COMMANDS.has(command.command)) {
|
|
22
|
+
return { kind: "lifecycle" };
|
|
23
|
+
}
|
|
24
|
+
if ("session_id" in command) {
|
|
25
|
+
return { kind: "session", sessionId: command.session_id };
|
|
26
|
+
}
|
|
27
|
+
return { kind: "lifecycle" };
|
|
28
|
+
}
|
|
29
|
+
function settle(task) {
|
|
30
|
+
return task.then(() => undefined, () => undefined);
|
|
31
|
+
}
|
|
32
|
+
export class BridgeCommandScheduler {
|
|
33
|
+
lifecycleTail;
|
|
34
|
+
sessionTails = new Map();
|
|
35
|
+
activeTasks = new Set();
|
|
36
|
+
shutdownState = "accepting";
|
|
37
|
+
schedule(command, task) {
|
|
38
|
+
const lane = commandLane(command);
|
|
39
|
+
if (!this.accepts(command, lane)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
let scheduledTask = task;
|
|
43
|
+
if (command.command === "shutdown") {
|
|
44
|
+
this.shutdownState = "queued";
|
|
45
|
+
scheduledTask = async () => {
|
|
46
|
+
this.shutdownState = "running";
|
|
47
|
+
try {
|
|
48
|
+
await task();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
this.shutdownState = "complete";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const operation = this.scheduleInLane(lane, scheduledTask);
|
|
56
|
+
this.track(operation);
|
|
57
|
+
return operation;
|
|
58
|
+
}
|
|
59
|
+
stopAccepting() {
|
|
60
|
+
if (this.shutdownState === "accepting") {
|
|
61
|
+
this.shutdownState = "complete";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async whenIdle() {
|
|
65
|
+
while (this.activeTasks.size > 0) {
|
|
66
|
+
await Promise.all(this.activeTasks);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
accepts(command, lane) {
|
|
70
|
+
if (this.shutdownState === "accepting") {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return (this.shutdownState === "queued" &&
|
|
74
|
+
command.command !== "shutdown" &&
|
|
75
|
+
lane.kind === "unblocking");
|
|
76
|
+
}
|
|
77
|
+
scheduleInLane(lane, task) {
|
|
78
|
+
switch (lane.kind) {
|
|
79
|
+
case "unblocking":
|
|
80
|
+
return this.start(task);
|
|
81
|
+
case "lifecycle":
|
|
82
|
+
return this.scheduleLifecycle(task);
|
|
83
|
+
case "session":
|
|
84
|
+
return this.scheduleSession(lane.sessionId, task);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
scheduleLifecycle(task) {
|
|
88
|
+
const dependencies = new Set(this.sessionTails.values());
|
|
89
|
+
if (this.lifecycleTail) {
|
|
90
|
+
dependencies.add(this.lifecycleTail);
|
|
91
|
+
}
|
|
92
|
+
const operation = dependencies.size === 0 ? this.start(task) : Promise.all(dependencies).then(task);
|
|
93
|
+
const tail = settle(operation);
|
|
94
|
+
this.lifecycleTail = tail;
|
|
95
|
+
void tail.then(() => {
|
|
96
|
+
if (this.lifecycleTail === tail) {
|
|
97
|
+
this.lifecycleTail = undefined;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return operation;
|
|
101
|
+
}
|
|
102
|
+
scheduleSession(sessionId, task) {
|
|
103
|
+
const dependencies = [];
|
|
104
|
+
if (this.lifecycleTail) {
|
|
105
|
+
dependencies.push(this.lifecycleTail);
|
|
106
|
+
}
|
|
107
|
+
const previousSessionTask = this.sessionTails.get(sessionId);
|
|
108
|
+
if (previousSessionTask) {
|
|
109
|
+
dependencies.push(previousSessionTask);
|
|
110
|
+
}
|
|
111
|
+
const operation = dependencies.length === 0 ? this.start(task) : Promise.all(dependencies).then(task);
|
|
112
|
+
const tail = settle(operation);
|
|
113
|
+
this.sessionTails.set(sessionId, tail);
|
|
114
|
+
void tail.then(() => {
|
|
115
|
+
if (this.sessionTails.get(sessionId) === tail) {
|
|
116
|
+
this.sessionTails.delete(sessionId);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return operation;
|
|
120
|
+
}
|
|
121
|
+
track(operation) {
|
|
122
|
+
const tracked = settle(operation);
|
|
123
|
+
this.activeTasks.add(tracked);
|
|
124
|
+
void tracked.then(() => {
|
|
125
|
+
this.activeTasks.delete(tracked);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
start(task) {
|
|
129
|
+
try {
|
|
130
|
+
return task();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return Promise.reject(error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { buildModeState, markModeUnavailableForSession, permissionModeFailureLooksUnsupported, refreshSupportedModesForSession, toPermissionMode, } from "./commands.js";
|
|
2
|
+
import { dispatchCancelTurnCommand } from "./command_dispatch.js";
|
|
3
|
+
import { emitFastModeUpdate } from "./error_classification.js";
|
|
4
|
+
import { emitSessionUpdate, slashError } from "./events.js";
|
|
5
|
+
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
6
|
+
import { emitCurrentModelUpdate, refreshCurrentModel, sessionById, shouldInvalidateResolvedRuntimeModel, } from "./session_lifecycle.js";
|
|
7
|
+
export async function handleSessionControlCommand(command, requestId, deps) {
|
|
8
|
+
switch (command.command) {
|
|
9
|
+
case "prompt":
|
|
10
|
+
handlePrompt(command, requestId, deps);
|
|
11
|
+
return;
|
|
12
|
+
case "cancel_turn":
|
|
13
|
+
await dispatchCancelTurnCommand(command, { requestId, sessionById, slashError });
|
|
14
|
+
return;
|
|
15
|
+
case "set_model":
|
|
16
|
+
await setModel(command, requestId);
|
|
17
|
+
return;
|
|
18
|
+
case "set_mode":
|
|
19
|
+
await setMode(command, requestId);
|
|
20
|
+
return;
|
|
21
|
+
case "set_effort":
|
|
22
|
+
await setEffort(command, requestId, deps);
|
|
23
|
+
return;
|
|
24
|
+
case "set_agent":
|
|
25
|
+
await setAgent(command, requestId, deps);
|
|
26
|
+
return;
|
|
27
|
+
case "set_fast_mode":
|
|
28
|
+
await setFastMode(command, requestId, deps);
|
|
29
|
+
return;
|
|
30
|
+
case "reload_plugins":
|
|
31
|
+
await reloadPlugins(command, requestId, deps);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function handlePrompt(command, requestId, deps) {
|
|
35
|
+
const session = requireSession(command.session_id, requestId);
|
|
36
|
+
if (!session) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const message = deps.buildPromptUserMessage(command, session.sessionId);
|
|
40
|
+
if (message) {
|
|
41
|
+
session.input.enqueue(message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function setModel(command, requestId) {
|
|
45
|
+
const session = requireSession(command.session_id, requestId);
|
|
46
|
+
if (!session) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
bridgeLogger.info({
|
|
50
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
51
|
+
eventName: "set_model_started",
|
|
52
|
+
message: "set model started",
|
|
53
|
+
outcome: "start",
|
|
54
|
+
sessionId: session.sessionId,
|
|
55
|
+
requestId,
|
|
56
|
+
fields: {
|
|
57
|
+
requested_model: command.model,
|
|
58
|
+
previous_requested_model: session.requestedModelId,
|
|
59
|
+
previous_session_model: session.model,
|
|
60
|
+
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
61
|
+
previous_current_model: session.currentModel?.resolved_id,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
const previousRequestedModel = session.requestedModelId;
|
|
66
|
+
const previousSessionModel = session.model;
|
|
67
|
+
await session.query.setModel(command.model);
|
|
68
|
+
session.requestedModelId = command.model;
|
|
69
|
+
session.model = command.model;
|
|
70
|
+
const invalidatedResolvedRuntimeModel = shouldInvalidateResolvedRuntimeModel(previousRequestedModel, previousSessionModel, command.model);
|
|
71
|
+
if (invalidatedResolvedRuntimeModel) {
|
|
72
|
+
session.resolvedRuntimeModelId = undefined;
|
|
73
|
+
}
|
|
74
|
+
const changed = refreshCurrentModel(session, true);
|
|
75
|
+
const forcedCurrentModelUpdate = !changed && emitCurrentModelUpdate(session);
|
|
76
|
+
bridgeLogger.info({
|
|
77
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
78
|
+
eventName: "set_model_succeeded",
|
|
79
|
+
message: "set model completed",
|
|
80
|
+
outcome: "success",
|
|
81
|
+
sessionId: session.sessionId,
|
|
82
|
+
requestId,
|
|
83
|
+
fields: {
|
|
84
|
+
requested_model: command.model,
|
|
85
|
+
session_model_after: session.model,
|
|
86
|
+
resolved_runtime_model_after: session.resolvedRuntimeModelId,
|
|
87
|
+
current_model_after: session.currentModel?.resolved_id,
|
|
88
|
+
current_model_display_short: session.currentModel?.display_name_short,
|
|
89
|
+
current_model_display_long: session.currentModel?.display_name_long,
|
|
90
|
+
current_model_update_emitted: changed || forcedCurrentModelUpdate,
|
|
91
|
+
current_model_update_forced: forcedCurrentModelUpdate,
|
|
92
|
+
resolved_runtime_model_invalidated: invalidatedResolvedRuntimeModel,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
refreshSupportedModesForSession(session);
|
|
96
|
+
if (session.mode) {
|
|
97
|
+
emitSessionUpdate(session.sessionId, {
|
|
98
|
+
type: "mode_state_update",
|
|
99
|
+
mode: buildModeState(session, session.mode),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
bridgeLogger.warn({
|
|
106
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
107
|
+
eventName: "set_model_failed",
|
|
108
|
+
message: "set model failed",
|
|
109
|
+
outcome: "failure",
|
|
110
|
+
sessionId: session.sessionId,
|
|
111
|
+
requestId,
|
|
112
|
+
fields: {
|
|
113
|
+
requested_model: command.model,
|
|
114
|
+
error_message: message,
|
|
115
|
+
previous_requested_model: session.requestedModelId,
|
|
116
|
+
previous_session_model: session.model,
|
|
117
|
+
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
118
|
+
previous_current_model: session.currentModel?.resolved_id,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
slashError(command.session_id, `failed to set model: ${message}`, requestId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function setMode(command, requestId) {
|
|
125
|
+
const session = requireSession(command.session_id, requestId);
|
|
126
|
+
if (!session) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const mode = toPermissionMode(command.mode);
|
|
130
|
+
if (!mode) {
|
|
131
|
+
slashError(command.session_id, `unsupported mode: ${command.mode}`, requestId);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await session.query.setPermissionMode(mode);
|
|
136
|
+
session.mode = mode;
|
|
137
|
+
refreshSupportedModesForSession(session);
|
|
138
|
+
emitSessionUpdate(session.sessionId, {
|
|
139
|
+
type: "current_mode_update",
|
|
140
|
+
current_mode_id: mode,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
+
if (permissionModeFailureLooksUnsupported(mode, message)) {
|
|
146
|
+
const changed = markModeUnavailableForSession(session, mode);
|
|
147
|
+
if (changed && session.mode) {
|
|
148
|
+
emitSessionUpdate(session.sessionId, {
|
|
149
|
+
type: "mode_state_update",
|
|
150
|
+
mode: buildModeState(session, session.mode),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
slashError(command.session_id, `failed to set mode to ${mode}: ${message}`, requestId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function setEffort(command, requestId, deps) {
|
|
158
|
+
const session = requireSession(command.session_id, requestId);
|
|
159
|
+
if (!session) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await deps.applySessionEffort(session.query, command.effort);
|
|
164
|
+
deps.emitEffortConfigOptionUpdate(session.sessionId, command.effort);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
168
|
+
slashError(command.session_id, `failed to set effort: ${message}`, requestId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function setAgent(command, requestId, deps) {
|
|
172
|
+
const session = requireSession(command.session_id, requestId);
|
|
173
|
+
if (!session) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await deps.applySessionAgent(session.query, command.agent);
|
|
178
|
+
deps.emitAgentConfigOptionUpdate(session.sessionId, command.agent);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
182
|
+
slashError(command.session_id, `failed to set agent: ${message}`, requestId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function setFastMode(command, requestId, deps) {
|
|
186
|
+
const session = requireSession(command.session_id, requestId);
|
|
187
|
+
if (!session) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
bridgeLogger.info({
|
|
191
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
192
|
+
eventName: "set_fast_mode_started",
|
|
193
|
+
message: "set fast mode started",
|
|
194
|
+
outcome: "start",
|
|
195
|
+
sessionId: session.sessionId,
|
|
196
|
+
requestId,
|
|
197
|
+
fields: {
|
|
198
|
+
requested_enabled: command.enabled,
|
|
199
|
+
previous_state: session.fastModeState,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
try {
|
|
203
|
+
const snapshot = await deps.applySessionFastMode(session.query, command.enabled);
|
|
204
|
+
const state = snapshot.state;
|
|
205
|
+
session.fastModeState = state;
|
|
206
|
+
session.fastModeDisabledReason = snapshot.disabled_reason;
|
|
207
|
+
emitFastModeUpdate(session);
|
|
208
|
+
const reportedEnabled = state !== "off";
|
|
209
|
+
if (reportedEnabled !== command.enabled) {
|
|
210
|
+
bridgeLogger.warn({
|
|
211
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
212
|
+
eventName: "set_fast_mode_mismatch",
|
|
213
|
+
message: "SDK reported a fast-mode state that did not match the request",
|
|
214
|
+
outcome: "failure",
|
|
215
|
+
sessionId: session.sessionId,
|
|
216
|
+
requestId,
|
|
217
|
+
fields: {
|
|
218
|
+
requested_enabled: command.enabled,
|
|
219
|
+
reported_state: state,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
const action = command.enabled ? "enable" : "disable";
|
|
223
|
+
slashError(command.session_id, `failed to ${action} fast mode: SDK reported state ${state}`, requestId);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
bridgeLogger.info({
|
|
227
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
228
|
+
eventName: "set_fast_mode_succeeded",
|
|
229
|
+
message: "set fast mode completed",
|
|
230
|
+
outcome: "success",
|
|
231
|
+
sessionId: session.sessionId,
|
|
232
|
+
requestId,
|
|
233
|
+
fields: {
|
|
234
|
+
requested_enabled: command.enabled,
|
|
235
|
+
reported_state: state,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
241
|
+
bridgeLogger.warn({
|
|
242
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
243
|
+
eventName: "set_fast_mode_failed",
|
|
244
|
+
message: "set fast mode failed",
|
|
245
|
+
outcome: "failure",
|
|
246
|
+
sessionId: session.sessionId,
|
|
247
|
+
requestId,
|
|
248
|
+
fields: {
|
|
249
|
+
requested_enabled: command.enabled,
|
|
250
|
+
previous_state: session.fastModeState,
|
|
251
|
+
error_message: message,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
slashError(command.session_id, `failed to set fast mode: ${message}`, requestId);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function reloadPlugins(command, requestId, deps) {
|
|
258
|
+
const session = requireSession(command.session_id, requestId);
|
|
259
|
+
if (session) {
|
|
260
|
+
await deps.handleReloadPluginsCommand(session, requestId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function requireSession(sessionId, requestId) {
|
|
264
|
+
const session = sessionById(sessionId);
|
|
265
|
+
if (!session) {
|
|
266
|
+
slashError(sessionId, `unknown session: ${sessionId}`, requestId);
|
|
267
|
+
}
|
|
268
|
+
return session;
|
|
269
|
+
}
|