claude-code-rust 0.9.0 → 0.11.0
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 +16 -9
- package/agent-sdk/README.md +1 -1
- package/agent-sdk/dist/bridge/commands.js +71 -2
- package/agent-sdk/dist/bridge/events.js +127 -12
- package/agent-sdk/dist/bridge/history.js +9 -4
- package/agent-sdk/dist/bridge/logger.js +253 -0
- package/agent-sdk/dist/bridge/mcp.js +81 -5
- package/agent-sdk/dist/bridge/message_handlers.js +318 -67
- package/agent-sdk/dist/bridge/session_lifecycle.js +774 -58
- package/agent-sdk/dist/bridge/shared.js +0 -7
- package/agent-sdk/dist/bridge/state_parsing.js +61 -0
- package/agent-sdk/dist/bridge/tool_calls.js +269 -66
- package/agent-sdk/dist/bridge/tooling.js +60 -27
- package/agent-sdk/dist/bridge/user_interaction.js +34 -23
- package/agent-sdk/dist/bridge.js +406 -42
- package/agent-sdk/dist/bridge.test.js +765 -23
- package/package.json +2 -2
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -4,14 +4,16 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { getSessionMessages, listSessions, renameSession, } from "@anthropic-ai/claude-agent-sdk";
|
|
7
|
-
import { parseCommandEnvelope, toPermissionMode } from "./bridge/commands.js";
|
|
8
|
-
import { writeEvent, failConnection, slashError, emitSessionUpdate, emitSessionsList, currentSessionListOptions, setSessionListingDir, } from "./bridge/events.js";
|
|
9
|
-
import {
|
|
10
|
-
import { sessions, sessionById, createSession, closeAllSessions, handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, } from "./bridge/session_lifecycle.js";
|
|
7
|
+
import { buildModeState, markModeUnavailableForSession, parseCommandEnvelope, permissionModeFailureLooksUnsupported, refreshSupportedModesForSession, toPermissionMode, } from "./bridge/commands.js";
|
|
8
|
+
import { writeEvent, failConnection, slashError, emitRuntimeReloadCompleted, emitRuntimeReloadFailed, emitSessionUpdate, emitSessionsList, currentSessionListOptions, setSessionListingDir, } from "./bridge/events.js";
|
|
9
|
+
import { contentFromPrompt } from "./bridge/message_handlers.js";
|
|
10
|
+
import { sessions, sessionById, createSession, closeAllSessions, handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, emitCurrentModelUpdate, refreshCurrentModel, shouldInvalidateResolvedRuntimeModel, } from "./bridge/session_lifecycle.js";
|
|
11
11
|
import { mapSessionMessagesToUpdates } from "./bridge/history.js";
|
|
12
|
+
import { emitAvailableAgentsIfChanged, mapAvailableAgents } from "./bridge/agents.js";
|
|
12
13
|
import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, handleMcpAuthenticateCommand, handleMcpClearAuthCommand, handleMcpOauthCallbackUrlCommand, handleMcpReconnectCommand, handleMcpSetServersCommand, handleMcpStatusCommand, handleMcpToggleCommand, staleMcpAuthCandidates, } from "./bridge/mcp.js";
|
|
14
|
+
import { bridgeLogger, LOG_TARGETS, logBridgeCommandReceived } from "./bridge/logger.js";
|
|
13
15
|
// Re-exports: all symbols that tests and external consumers import from bridge.js.
|
|
14
|
-
export { AsyncQueue
|
|
16
|
+
export { AsyncQueue } from "./bridge/shared.js";
|
|
15
17
|
export { asRecordOrNull } from "./bridge/shared.js";
|
|
16
18
|
export { CACHE_SPLIT_POLICY, previewKilobyteLabel } from "./bridge/cache_policy.js";
|
|
17
19
|
export { buildToolResultFields, createToolCall, normalizeToolKind, normalizeToolResultText, unwrapToolUseResult, } from "./bridge/tooling.js";
|
|
@@ -20,10 +22,10 @@ export { parseCommandEnvelope } from "./bridge/commands.js";
|
|
|
20
22
|
export { buildSessionListOptions } from "./bridge/events.js";
|
|
21
23
|
export { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./bridge/permissions.js";
|
|
22
24
|
export { mapSessionMessagesToUpdates, mapSdkSessions, } from "./bridge/history.js";
|
|
23
|
-
export { handleTaskSystemMessage } from "./bridge/message_handlers.js";
|
|
25
|
+
export { handleSdkMessage, handleTaskSystemMessage } from "./bridge/message_handlers.js";
|
|
24
26
|
export { mapAvailableAgents } from "./bridge/agents.js";
|
|
25
|
-
export { buildQueryOptions, mapAvailableModels } from "./bridge/session_lifecycle.js";
|
|
26
|
-
export { parseFastModeState, parseRateLimitStatus, buildRateLimitUpdate, } from "./bridge/state_parsing.js";
|
|
27
|
+
export { attachRequestUserDialogInterceptor, buildQueryOptions, mapAvailableModels, } from "./bridge/session_lifecycle.js";
|
|
28
|
+
export { parseFastModeState, parseRateLimitStatus, parseRuntimeSessionState, parseApiRetryError, buildRateLimitUpdate, buildApiRetryUpdate, normalizeSettingsParseError, normalizeSettingsParseErrors, } from "./bridge/state_parsing.js";
|
|
27
29
|
export { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, staleMcpAuthCandidates };
|
|
28
30
|
export function buildSessionMutationOptions(cwd) {
|
|
29
31
|
return cwd ? { dir: cwd } : undefined;
|
|
@@ -41,7 +43,7 @@ export async function generatePersistedSessionTitle(query, description) {
|
|
|
41
43
|
}
|
|
42
44
|
return title;
|
|
43
45
|
}
|
|
44
|
-
const EXPECTED_AGENT_SDK_VERSION = "0.2.
|
|
46
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.2.104";
|
|
45
47
|
const require = createRequire(import.meta.url);
|
|
46
48
|
export function resolveInstalledAgentSdkVersion() {
|
|
47
49
|
try {
|
|
@@ -67,14 +69,34 @@ export function agentSdkVersionCompatibilityError() {
|
|
|
67
69
|
`found ${installed}.`);
|
|
68
70
|
}
|
|
69
71
|
async function handleCommand(command, requestId) {
|
|
72
|
+
logBridgeCommandReceived(command, requestId);
|
|
70
73
|
const sdkVersionError = agentSdkVersionCompatibilityError();
|
|
71
74
|
if (sdkVersionError && command.command !== "initialize" && command.command !== "shutdown") {
|
|
75
|
+
bridgeLogger.error({
|
|
76
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
77
|
+
eventName: "bridge_command_rejected",
|
|
78
|
+
message: "bridge command rejected due to unsupported SDK version",
|
|
79
|
+
outcome: "failure",
|
|
80
|
+
...(requestId ? { requestId } : {}),
|
|
81
|
+
fields: {
|
|
82
|
+
bridge_command: command.command,
|
|
83
|
+
error_message: sdkVersionError,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
72
86
|
failConnection(sdkVersionError, requestId);
|
|
73
87
|
return;
|
|
74
88
|
}
|
|
75
89
|
switch (command.command) {
|
|
76
90
|
case "initialize":
|
|
77
91
|
if (sdkVersionError) {
|
|
92
|
+
bridgeLogger.error({
|
|
93
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
94
|
+
eventName: "bridge_initialize_failed",
|
|
95
|
+
message: "bridge initialization failed due to unsupported SDK version",
|
|
96
|
+
outcome: "failure",
|
|
97
|
+
...(requestId ? { requestId } : {}),
|
|
98
|
+
fields: { error_message: sdkVersionError },
|
|
99
|
+
});
|
|
78
100
|
failConnection(sdkVersionError, requestId);
|
|
79
101
|
return;
|
|
80
102
|
}
|
|
@@ -92,7 +114,7 @@ async function handleCommand(command, requestId) {
|
|
|
92
114
|
},
|
|
93
115
|
],
|
|
94
116
|
capabilities: {
|
|
95
|
-
prompt_image:
|
|
117
|
+
prompt_image: true,
|
|
96
118
|
prompt_embedded_context: true,
|
|
97
119
|
supports_session_listing: true,
|
|
98
120
|
supports_resume_session: true,
|
|
@@ -102,6 +124,17 @@ async function handleCommand(command, requestId) {
|
|
|
102
124
|
await emitSessionsList(requestId);
|
|
103
125
|
return;
|
|
104
126
|
case "create_session":
|
|
127
|
+
bridgeLogger.info({
|
|
128
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
129
|
+
eventName: "session_create_requested",
|
|
130
|
+
message: "session creation requested",
|
|
131
|
+
outcome: "start",
|
|
132
|
+
...(requestId ? { requestId } : {}),
|
|
133
|
+
fields: {
|
|
134
|
+
cwd: command.cwd,
|
|
135
|
+
resume_requested: command.resume !== undefined,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
105
138
|
setSessionListingDir(command.cwd);
|
|
106
139
|
await createSession({
|
|
107
140
|
cwd: command.cwd,
|
|
@@ -112,10 +145,27 @@ async function handleCommand(command, requestId) {
|
|
|
112
145
|
});
|
|
113
146
|
return;
|
|
114
147
|
case "resume_session": {
|
|
148
|
+
bridgeLogger.info({
|
|
149
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
150
|
+
eventName: "session_resume_requested",
|
|
151
|
+
message: "session resume requested",
|
|
152
|
+
outcome: "start",
|
|
153
|
+
...(requestId ? { requestId } : {}),
|
|
154
|
+
sessionId: command.session_id,
|
|
155
|
+
});
|
|
115
156
|
try {
|
|
116
157
|
const sdkSessions = await listSessions(currentSessionListOptions());
|
|
117
158
|
const matched = sdkSessions.find((entry) => entry.sessionId === command.session_id);
|
|
118
159
|
if (!matched) {
|
|
160
|
+
bridgeLogger.warn({
|
|
161
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
162
|
+
eventName: "session_resume_lookup_failed",
|
|
163
|
+
message: "session resume requested for an unknown session",
|
|
164
|
+
outcome: "failure",
|
|
165
|
+
...(requestId ? { requestId } : {}),
|
|
166
|
+
sessionId: command.session_id,
|
|
167
|
+
fields: { reason: "unknown_session" },
|
|
168
|
+
});
|
|
119
169
|
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
120
170
|
return;
|
|
121
171
|
}
|
|
@@ -124,6 +174,18 @@ async function handleCommand(command, requestId) {
|
|
|
124
174
|
const resumeUpdates = mapSessionMessagesToUpdates(historyMessages);
|
|
125
175
|
const staleSessions = Array.from(sessions.values());
|
|
126
176
|
const hadActiveSession = staleSessions.length > 0;
|
|
177
|
+
bridgeLogger.info({
|
|
178
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
179
|
+
eventName: "session_resume_history_loaded",
|
|
180
|
+
message: "session resume history loaded",
|
|
181
|
+
outcome: "success",
|
|
182
|
+
...(requestId ? { requestId } : {}),
|
|
183
|
+
sessionId: command.session_id,
|
|
184
|
+
fields: {
|
|
185
|
+
history_update_count: resumeUpdates.length,
|
|
186
|
+
stale_session_count: staleSessions.length,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
127
189
|
await createSession({
|
|
128
190
|
cwd: matched.cwd ?? process.cwd(),
|
|
129
191
|
resume: command.session_id,
|
|
@@ -136,12 +198,29 @@ async function handleCommand(command, requestId) {
|
|
|
136
198
|
}
|
|
137
199
|
catch (error) {
|
|
138
200
|
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
bridgeLogger.error({
|
|
202
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
203
|
+
eventName: "session_resume_failed",
|
|
204
|
+
message: "session resume failed",
|
|
205
|
+
outcome: "failure",
|
|
206
|
+
...(requestId ? { requestId } : {}),
|
|
207
|
+
sessionId: command.session_id,
|
|
208
|
+
fields: { error_message: message },
|
|
209
|
+
});
|
|
139
210
|
slashError(command.session_id, `failed to resume session: ${message}`, requestId);
|
|
140
211
|
}
|
|
141
212
|
return;
|
|
142
213
|
}
|
|
143
214
|
case "new_session":
|
|
144
|
-
|
|
215
|
+
bridgeLogger.info({
|
|
216
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
217
|
+
eventName: "session_new_requested",
|
|
218
|
+
message: "replacement session requested",
|
|
219
|
+
outcome: "start",
|
|
220
|
+
...(requestId ? { requestId } : {}),
|
|
221
|
+
fields: { cwd: command.cwd },
|
|
222
|
+
});
|
|
223
|
+
await closeAllSessions({ reason: "new_session_requested", requestId });
|
|
145
224
|
setSessionListingDir(command.cwd);
|
|
146
225
|
await createSession({
|
|
147
226
|
cwd: command.cwd,
|
|
@@ -156,19 +235,20 @@ async function handleCommand(command, requestId) {
|
|
|
156
235
|
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
157
236
|
return;
|
|
158
237
|
}
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
238
|
+
const content = contentFromPrompt(command);
|
|
239
|
+
if (content.length === 0) {
|
|
161
240
|
return;
|
|
162
241
|
}
|
|
163
|
-
|
|
242
|
+
const message = {
|
|
164
243
|
type: "user",
|
|
165
244
|
session_id: session.sessionId,
|
|
166
245
|
parent_tool_use_id: null,
|
|
167
246
|
message: {
|
|
168
247
|
role: "user",
|
|
169
|
-
content
|
|
248
|
+
content,
|
|
170
249
|
},
|
|
171
|
-
}
|
|
250
|
+
};
|
|
251
|
+
session.input.enqueue(message);
|
|
172
252
|
return;
|
|
173
253
|
}
|
|
174
254
|
case "cancel_turn": {
|
|
@@ -186,13 +266,80 @@ async function handleCommand(command, requestId) {
|
|
|
186
266
|
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
187
267
|
return;
|
|
188
268
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
269
|
+
bridgeLogger.info({
|
|
270
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
271
|
+
eventName: "set_model_started",
|
|
272
|
+
message: "set model started",
|
|
273
|
+
outcome: "start",
|
|
274
|
+
sessionId: session.sessionId,
|
|
275
|
+
requestId,
|
|
276
|
+
fields: {
|
|
277
|
+
requested_model: command.model,
|
|
278
|
+
previous_requested_model: session.requestedModelId,
|
|
279
|
+
previous_session_model: session.model,
|
|
280
|
+
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
281
|
+
previous_current_model: session.currentModel?.resolved_id,
|
|
282
|
+
},
|
|
195
283
|
});
|
|
284
|
+
try {
|
|
285
|
+
const previousRequestedModel = session.requestedModelId;
|
|
286
|
+
const previousSessionModel = session.model;
|
|
287
|
+
await session.query.setModel(command.model);
|
|
288
|
+
session.requestedModelId = command.model;
|
|
289
|
+
session.model = command.model;
|
|
290
|
+
const invalidatedResolvedRuntimeModel = shouldInvalidateResolvedRuntimeModel(previousRequestedModel, previousSessionModel, command.model);
|
|
291
|
+
if (invalidatedResolvedRuntimeModel) {
|
|
292
|
+
session.resolvedRuntimeModelId = undefined;
|
|
293
|
+
}
|
|
294
|
+
const changed = refreshCurrentModel(session, true);
|
|
295
|
+
const forcedCurrentModelUpdate = !changed && emitCurrentModelUpdate(session);
|
|
296
|
+
bridgeLogger.info({
|
|
297
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
298
|
+
eventName: "set_model_succeeded",
|
|
299
|
+
message: "set model completed",
|
|
300
|
+
outcome: "success",
|
|
301
|
+
sessionId: session.sessionId,
|
|
302
|
+
requestId,
|
|
303
|
+
fields: {
|
|
304
|
+
requested_model: command.model,
|
|
305
|
+
session_model_after: session.model,
|
|
306
|
+
resolved_runtime_model_after: session.resolvedRuntimeModelId,
|
|
307
|
+
current_model_after: session.currentModel?.resolved_id,
|
|
308
|
+
current_model_display_short: session.currentModel?.display_name_short,
|
|
309
|
+
current_model_display_long: session.currentModel?.display_name_long,
|
|
310
|
+
current_model_update_emitted: changed || forcedCurrentModelUpdate,
|
|
311
|
+
current_model_update_forced: forcedCurrentModelUpdate,
|
|
312
|
+
resolved_runtime_model_invalidated: invalidatedResolvedRuntimeModel,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
refreshSupportedModesForSession(session);
|
|
316
|
+
if (session.mode) {
|
|
317
|
+
emitSessionUpdate(session.sessionId, {
|
|
318
|
+
type: "mode_state_update",
|
|
319
|
+
mode: buildModeState(session, session.mode),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
325
|
+
bridgeLogger.warn({
|
|
326
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
327
|
+
eventName: "set_model_failed",
|
|
328
|
+
message: "set model failed",
|
|
329
|
+
outcome: "failure",
|
|
330
|
+
sessionId: session.sessionId,
|
|
331
|
+
requestId,
|
|
332
|
+
fields: {
|
|
333
|
+
requested_model: command.model,
|
|
334
|
+
error_message: message,
|
|
335
|
+
previous_requested_model: session.requestedModelId,
|
|
336
|
+
previous_session_model: session.model,
|
|
337
|
+
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
338
|
+
previous_current_model: session.currentModel?.resolved_id,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
slashError(command.session_id, `failed to set model: ${message}`, requestId);
|
|
342
|
+
}
|
|
196
343
|
return;
|
|
197
344
|
}
|
|
198
345
|
case "set_mode": {
|
|
@@ -206,12 +353,28 @@ async function handleCommand(command, requestId) {
|
|
|
206
353
|
slashError(command.session_id, `unsupported mode: ${command.mode}`, requestId);
|
|
207
354
|
return;
|
|
208
355
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
356
|
+
try {
|
|
357
|
+
await session.query.setPermissionMode(mode);
|
|
358
|
+
session.mode = mode;
|
|
359
|
+
refreshSupportedModesForSession(session);
|
|
360
|
+
emitSessionUpdate(session.sessionId, {
|
|
361
|
+
type: "current_mode_update",
|
|
362
|
+
current_mode_id: mode,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
367
|
+
if (permissionModeFailureLooksUnsupported(mode, message)) {
|
|
368
|
+
const changed = markModeUnavailableForSession(session, mode);
|
|
369
|
+
if (changed && session.mode) {
|
|
370
|
+
emitSessionUpdate(session.sessionId, {
|
|
371
|
+
type: "mode_state_update",
|
|
372
|
+
mode: buildModeState(session, session.mode),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
slashError(command.session_id, `failed to set mode to ${mode}: ${message}`, requestId);
|
|
377
|
+
}
|
|
215
378
|
return;
|
|
216
379
|
}
|
|
217
380
|
case "generate_session_title": {
|
|
@@ -254,18 +417,144 @@ async function handleCommand(command, requestId) {
|
|
|
254
417
|
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
255
418
|
return;
|
|
256
419
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
420
|
+
try {
|
|
421
|
+
const account = await session.query.accountInfo();
|
|
422
|
+
bridgeLogger.info({
|
|
423
|
+
target: LOG_TARGETS.APP_AUTH,
|
|
424
|
+
eventName: "status_snapshot_emitted",
|
|
425
|
+
message: "status snapshot emitted",
|
|
426
|
+
outcome: "success",
|
|
427
|
+
...(requestId ? { requestId } : {}),
|
|
428
|
+
sessionId: session.sessionId,
|
|
429
|
+
fields: {
|
|
430
|
+
has_email: typeof account.email === "string" && account.email.trim().length > 0,
|
|
431
|
+
has_organization: account.organization !== undefined,
|
|
432
|
+
subscription_type: account.subscriptionType,
|
|
433
|
+
token_source: account.tokenSource,
|
|
434
|
+
api_key_source: account.apiKeySource,
|
|
435
|
+
api_provider: account.apiProvider,
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
writeEvent({
|
|
439
|
+
event: "status_snapshot",
|
|
440
|
+
session_id: session.sessionId,
|
|
441
|
+
account: {
|
|
442
|
+
email: account.email,
|
|
443
|
+
organization: account.organization,
|
|
444
|
+
subscription_type: account.subscriptionType,
|
|
445
|
+
token_source: account.tokenSource,
|
|
446
|
+
api_key_source: account.apiKeySource,
|
|
447
|
+
api_provider: account.apiProvider,
|
|
448
|
+
},
|
|
449
|
+
}, requestId);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
453
|
+
bridgeLogger.warn({
|
|
454
|
+
target: LOG_TARGETS.APP_AUTH,
|
|
455
|
+
eventName: "status_snapshot_failed",
|
|
456
|
+
message: "failed to build status snapshot",
|
|
457
|
+
outcome: "failure",
|
|
458
|
+
...(requestId ? { requestId } : {}),
|
|
459
|
+
sessionId: session.sessionId,
|
|
460
|
+
fields: { error_message: message },
|
|
461
|
+
});
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
case "get_context_usage": {
|
|
467
|
+
const session = sessionById(command.session_id);
|
|
468
|
+
if (!session) {
|
|
469
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
const usage = await session.query.getContextUsage();
|
|
474
|
+
if (typeof usage.model === "string" && usage.model.trim().length > 0) {
|
|
475
|
+
session.resolvedRuntimeModelId = usage.model.trim();
|
|
476
|
+
refreshCurrentModel(session, true);
|
|
477
|
+
}
|
|
478
|
+
const rawPercentage = typeof usage.percentage === "number" ? usage.percentage : undefined;
|
|
479
|
+
const normalizedPercentage = rawPercentage === undefined || !Number.isFinite(rawPercentage)
|
|
480
|
+
? undefined
|
|
481
|
+
: Math.max(0, Math.min(100, Math.round(rawPercentage)));
|
|
482
|
+
bridgeLogger.debug({
|
|
483
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
484
|
+
eventName: "context_usage_succeeded",
|
|
485
|
+
message: "session context usage received from SDK",
|
|
486
|
+
outcome: "success",
|
|
487
|
+
...(requestId ? { requestId } : {}),
|
|
488
|
+
sessionId: session.sessionId,
|
|
489
|
+
fields: {
|
|
490
|
+
raw_percentage: rawPercentage,
|
|
491
|
+
normalized_percentage: normalizedPercentage,
|
|
492
|
+
total_tokens: typeof usage.totalTokens === "number" ? usage.totalTokens : undefined,
|
|
493
|
+
max_tokens: typeof usage.maxTokens === "number" ? usage.maxTokens : undefined,
|
|
494
|
+
raw_max_tokens: typeof usage.rawMaxTokens === "number" ? usage.rawMaxTokens : undefined,
|
|
495
|
+
model: typeof usage.model === "string" ? usage.model : undefined,
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
writeEvent({
|
|
499
|
+
event: "context_usage",
|
|
500
|
+
session_id: session.sessionId,
|
|
501
|
+
...(normalizedPercentage !== undefined ? { percentage: normalizedPercentage } : {}),
|
|
502
|
+
}, requestId);
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
506
|
+
bridgeLogger.warn({
|
|
507
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
508
|
+
eventName: "context_usage_failed",
|
|
509
|
+
message: "failed to get session context usage",
|
|
510
|
+
outcome: "failure",
|
|
511
|
+
...(requestId ? { requestId } : {}),
|
|
512
|
+
sessionId: session.sessionId,
|
|
513
|
+
fields: { error_message: message },
|
|
514
|
+
});
|
|
515
|
+
writeEvent({
|
|
516
|
+
event: "context_usage",
|
|
517
|
+
session_id: session.sessionId,
|
|
518
|
+
}, requestId);
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
case "reload_plugins": {
|
|
523
|
+
const session = sessionById(command.session_id);
|
|
524
|
+
if (!session) {
|
|
525
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const result = await session.query.reloadPlugins();
|
|
530
|
+
const commands = Array.isArray(result.commands)
|
|
531
|
+
? result.commands.map((entry) => ({
|
|
532
|
+
name: entry.name,
|
|
533
|
+
description: entry.description ?? "",
|
|
534
|
+
input_hint: entry.argumentHint ?? undefined,
|
|
535
|
+
}))
|
|
536
|
+
: [];
|
|
537
|
+
emitSessionUpdate(session.sessionId, {
|
|
538
|
+
type: "available_commands_update",
|
|
539
|
+
commands,
|
|
540
|
+
});
|
|
541
|
+
emitAvailableAgentsIfChanged(session, mapAvailableAgents(result.agents));
|
|
542
|
+
await handleMcpStatusCommand(session, requestId);
|
|
543
|
+
emitRuntimeReloadCompleted(session.sessionId, requestId);
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
547
|
+
bridgeLogger.warn({
|
|
548
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
549
|
+
eventName: "reload_plugins_failed",
|
|
550
|
+
message: "failed to reload session plugins",
|
|
551
|
+
outcome: "failure",
|
|
552
|
+
...(requestId ? { requestId } : {}),
|
|
553
|
+
sessionId: session.sessionId,
|
|
554
|
+
fields: { error_message: message },
|
|
555
|
+
});
|
|
556
|
+
emitRuntimeReloadFailed(session.sessionId, message, requestId);
|
|
557
|
+
}
|
|
269
558
|
return;
|
|
270
559
|
}
|
|
271
560
|
case "mcp_status": {
|
|
@@ -341,13 +630,46 @@ async function handleCommand(command, requestId) {
|
|
|
341
630
|
handleElicitationResponse(command);
|
|
342
631
|
return;
|
|
343
632
|
case "shutdown":
|
|
344
|
-
|
|
633
|
+
bridgeLogger.info({
|
|
634
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
635
|
+
eventName: "bridge_shutdown_requested",
|
|
636
|
+
message: "bridge shutdown requested",
|
|
637
|
+
outcome: "start",
|
|
638
|
+
...(requestId ? { requestId } : {}),
|
|
639
|
+
});
|
|
640
|
+
await closeAllSessions({ reason: "bridge_shutdown_requested", requestId });
|
|
641
|
+
bridgeLogger.info({
|
|
642
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
643
|
+
eventName: "bridge_shutdown_completed",
|
|
644
|
+
message: "bridge shutdown completed",
|
|
645
|
+
outcome: "success",
|
|
646
|
+
...(requestId ? { requestId } : {}),
|
|
647
|
+
});
|
|
345
648
|
process.exit(0);
|
|
649
|
+
return;
|
|
346
650
|
default:
|
|
651
|
+
bridgeLogger.error({
|
|
652
|
+
target: LOG_TARGETS.BRIDGE_PROTOCOL,
|
|
653
|
+
eventName: "bridge_command_rejected",
|
|
654
|
+
message: "received unsupported bridge command",
|
|
655
|
+
outcome: "failure",
|
|
656
|
+
...(requestId ? { requestId } : {}),
|
|
657
|
+
fields: {
|
|
658
|
+
bridge_command: command.command ?? "unknown",
|
|
659
|
+
reason: "unsupported_command",
|
|
660
|
+
},
|
|
661
|
+
});
|
|
347
662
|
failConnection(`unhandled command: ${command.command ?? "unknown"}`, requestId);
|
|
348
663
|
}
|
|
349
664
|
}
|
|
350
665
|
function main() {
|
|
666
|
+
bridgeLogger.info({
|
|
667
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
668
|
+
eventName: "bridge_process_started",
|
|
669
|
+
message: "bridge process started",
|
|
670
|
+
outcome: "start",
|
|
671
|
+
fields: { pid: process.pid },
|
|
672
|
+
});
|
|
351
673
|
const rl = readline.createInterface({
|
|
352
674
|
input: process.stdin,
|
|
353
675
|
crlfDelay: Number.POSITIVE_INFINITY,
|
|
@@ -363,6 +685,18 @@ function main() {
|
|
|
363
685
|
}
|
|
364
686
|
catch (error) {
|
|
365
687
|
const message = error instanceof Error ? error.message : String(error);
|
|
688
|
+
bridgeLogger.error({
|
|
689
|
+
target: LOG_TARGETS.BRIDGE_PROTOCOL,
|
|
690
|
+
eventName: "bridge_command_decode_failed",
|
|
691
|
+
message: "failed to decode bridge command envelope",
|
|
692
|
+
outcome: "failure",
|
|
693
|
+
sizeBytes: Buffer.byteLength(line),
|
|
694
|
+
fields: {
|
|
695
|
+
preview: line.slice(0, 240),
|
|
696
|
+
preview_chars: Math.min(line.length, 240),
|
|
697
|
+
error_message: message,
|
|
698
|
+
},
|
|
699
|
+
});
|
|
366
700
|
failConnection(`invalid command envelope: ${message}`);
|
|
367
701
|
return;
|
|
368
702
|
}
|
|
@@ -371,12 +705,42 @@ function main() {
|
|
|
371
705
|
}
|
|
372
706
|
catch (error) {
|
|
373
707
|
const message = error instanceof Error ? error.message : String(error);
|
|
708
|
+
bridgeLogger.error({
|
|
709
|
+
target: LOG_TARGETS.BRIDGE_PROTOCOL,
|
|
710
|
+
eventName: "bridge_command_failed",
|
|
711
|
+
message: "bridge command handler failed",
|
|
712
|
+
outcome: "failure",
|
|
713
|
+
...(parsed.requestId ? { requestId: parsed.requestId } : {}),
|
|
714
|
+
...(parsed.command.command === "create_session" || parsed.command.command === "new_session"
|
|
715
|
+
? {}
|
|
716
|
+
: "session_id" in parsed.command
|
|
717
|
+
? { sessionId: parsed.command.session_id }
|
|
718
|
+
: {}),
|
|
719
|
+
fields: {
|
|
720
|
+
bridge_command: parsed.command.command,
|
|
721
|
+
error_message: message,
|
|
722
|
+
},
|
|
723
|
+
});
|
|
374
724
|
failConnection(`bridge command failed (${parsed.command.command}): ${message}`, parsed.requestId);
|
|
375
725
|
}
|
|
376
726
|
})();
|
|
377
727
|
});
|
|
378
728
|
rl.on("close", () => {
|
|
379
|
-
|
|
729
|
+
bridgeLogger.info({
|
|
730
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
731
|
+
eventName: "bridge_input_closed",
|
|
732
|
+
message: "bridge stdin closed",
|
|
733
|
+
outcome: "success",
|
|
734
|
+
});
|
|
735
|
+
void closeAllSessions({ reason: "bridge_stdin_closed" }).finally(() => {
|
|
736
|
+
bridgeLogger.info({
|
|
737
|
+
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
738
|
+
eventName: "bridge_shutdown_completed",
|
|
739
|
+
message: "bridge shutdown completed after stdin close",
|
|
740
|
+
outcome: "success",
|
|
741
|
+
});
|
|
742
|
+
process.exit(0);
|
|
743
|
+
});
|
|
380
744
|
});
|
|
381
745
|
}
|
|
382
746
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|