claude-code-rust 0.14.2 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/agent-sdk/dist/bridge/account_metadata.js +6 -2
- package/agent-sdk/dist/bridge/agents.js +9 -3
- package/agent-sdk/dist/bridge/available_commands.js +13 -3
- package/agent-sdk/dist/bridge/command_lifecycle.js +79 -4
- package/agent-sdk/dist/bridge/command_scheduler.js +7 -2
- package/agent-sdk/dist/bridge/command_session_control.js +5 -1
- package/agent-sdk/dist/bridge/command_session_data.js +225 -10
- package/agent-sdk/dist/bridge/commands.js +48 -11
- package/agent-sdk/dist/bridge/error_classification.js +5 -1
- package/agent-sdk/dist/bridge/events.js +45 -7
- package/agent-sdk/dist/bridge/history.js +33 -10
- package/agent-sdk/dist/bridge/logger.js +19 -3
- package/agent-sdk/dist/bridge/mcp.js +7 -2
- package/agent-sdk/dist/bridge/mcp_auth_adapter.js +4 -2
- package/agent-sdk/dist/bridge/mcp_metadata.js +113 -39
- package/agent-sdk/dist/bridge/mcp_monitor.js +6 -1
- package/agent-sdk/dist/bridge/message_handlers.js +336 -74
- package/agent-sdk/dist/bridge/model_metadata.js +19 -6
- package/agent-sdk/dist/bridge/permissions.js +32 -8
- package/agent-sdk/dist/bridge/session_lifecycle.js +224 -45
- package/agent-sdk/dist/bridge/state_parsing.js +23 -9
- package/agent-sdk/dist/bridge/tasks.js +62 -22
- package/agent-sdk/dist/bridge/tool_calls.js +89 -31
- package/agent-sdk/dist/bridge/tooling.js +430 -82
- package/agent-sdk/dist/bridge/user_interaction.js +45 -13
- package/agent-sdk/dist/bridge.js +200 -104
- package/package.json +8 -8
|
@@ -54,21 +54,29 @@ export async function requestExitPlanModeApproval(session, toolUseId, inputData,
|
|
|
54
54
|
return { behavior: "allow", updatedInput: inputData, toolUseID: toolUseId };
|
|
55
55
|
}
|
|
56
56
|
export function parseAskUserQuestionPrompts(inputData) {
|
|
57
|
-
const rawQuestions = Array.isArray(inputData.questions)
|
|
57
|
+
const rawQuestions = Array.isArray(inputData.questions)
|
|
58
|
+
? inputData.questions
|
|
59
|
+
: [];
|
|
58
60
|
const prompts = [];
|
|
59
61
|
for (const rawQuestion of rawQuestions) {
|
|
60
62
|
const questionRecord = asRecordOrNull(rawQuestion);
|
|
61
63
|
if (!questionRecord) {
|
|
62
64
|
continue;
|
|
63
65
|
}
|
|
64
|
-
const question = typeof questionRecord.question === "string"
|
|
66
|
+
const question = typeof questionRecord.question === "string"
|
|
67
|
+
? questionRecord.question.trim()
|
|
68
|
+
: "";
|
|
65
69
|
if (!question) {
|
|
66
70
|
continue;
|
|
67
71
|
}
|
|
68
|
-
const headerRaw = typeof questionRecord.header === "string"
|
|
72
|
+
const headerRaw = typeof questionRecord.header === "string"
|
|
73
|
+
? questionRecord.header.trim()
|
|
74
|
+
: "";
|
|
69
75
|
const header = headerRaw || `Q${prompts.length + 1}`;
|
|
70
76
|
const multiSelect = Boolean(questionRecord.multiSelect);
|
|
71
|
-
const rawOptions = Array.isArray(questionRecord.options)
|
|
77
|
+
const rawOptions = Array.isArray(questionRecord.options)
|
|
78
|
+
? questionRecord.options
|
|
79
|
+
: [];
|
|
72
80
|
const options = [];
|
|
73
81
|
for (const rawOption of rawOptions) {
|
|
74
82
|
const optionRecord = asRecordOrNull(rawOption);
|
|
@@ -76,8 +84,12 @@ export function parseAskUserQuestionPrompts(inputData) {
|
|
|
76
84
|
continue;
|
|
77
85
|
}
|
|
78
86
|
const label = typeof optionRecord.label === "string" ? optionRecord.label.trim() : "";
|
|
79
|
-
const description = typeof optionRecord.description === "string"
|
|
80
|
-
|
|
87
|
+
const description = typeof optionRecord.description === "string"
|
|
88
|
+
? optionRecord.description.trim()
|
|
89
|
+
: "";
|
|
90
|
+
const preview = typeof optionRecord.preview === "string"
|
|
91
|
+
? optionRecord.preview.trim()
|
|
92
|
+
: "";
|
|
81
93
|
if (!label) {
|
|
82
94
|
continue;
|
|
83
95
|
}
|
|
@@ -140,7 +152,9 @@ function buildQuestionRequest(promptToolCall, prompt, index, total) {
|
|
|
140
152
|
};
|
|
141
153
|
}
|
|
142
154
|
function askUserQuestionTranscript(answers) {
|
|
143
|
-
return answers
|
|
155
|
+
return answers
|
|
156
|
+
.map((entry) => `${entry.header}: ${entry.answer}\n ${entry.question}`)
|
|
157
|
+
.join("\n");
|
|
144
158
|
}
|
|
145
159
|
function askUserQuestionCompletedRawInput(prompts, answers, annotations, questionResults) {
|
|
146
160
|
return {
|
|
@@ -155,7 +169,9 @@ function askUserQuestionCompletedRawInput(prompts, answers, annotations, questio
|
|
|
155
169
|
})),
|
|
156
170
|
})),
|
|
157
171
|
answers,
|
|
158
|
-
...(Object.keys(annotations).length > 0
|
|
172
|
+
...(Object.keys(annotations).length > 0
|
|
173
|
+
? { annotations: questionAnnotationsJson(annotations) }
|
|
174
|
+
: {}),
|
|
159
175
|
question_results: questionResults,
|
|
160
176
|
};
|
|
161
177
|
}
|
|
@@ -175,7 +191,9 @@ function deriveAnnotation(selectedOptions, annotation) {
|
|
|
175
191
|
.map((option) => option.preview?.trim() ?? "")
|
|
176
192
|
.filter((previewText) => previewText.length > 0)
|
|
177
193
|
.join("\n\n");
|
|
178
|
-
const notes = annotation?.notes?.trim().length
|
|
194
|
+
const notes = annotation?.notes?.trim().length
|
|
195
|
+
? annotation.notes.trim()
|
|
196
|
+
: undefined;
|
|
179
197
|
if (!preview && !notes) {
|
|
180
198
|
return undefined;
|
|
181
199
|
}
|
|
@@ -226,13 +244,21 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
|
|
|
226
244
|
});
|
|
227
245
|
if (outcome.outcome !== "answered") {
|
|
228
246
|
setToolCallStatus(session, toolUseId, "failed", "Question cancelled");
|
|
229
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
behavior: "deny",
|
|
249
|
+
message: "Question cancelled",
|
|
250
|
+
toolUseID: toolUseId,
|
|
251
|
+
};
|
|
230
252
|
}
|
|
231
253
|
const selectedOptions = request.prompt.options.filter((option) => outcome.selected_option_ids.includes(option.option_id));
|
|
232
254
|
if (selectedOptions.length === 0 ||
|
|
233
255
|
(!prompt.multiSelect && selectedOptions.length !== 1)) {
|
|
234
256
|
setToolCallStatus(session, toolUseId, "failed", "Question answer was invalid");
|
|
235
|
-
return {
|
|
257
|
+
return {
|
|
258
|
+
behavior: "deny",
|
|
259
|
+
message: "Question answer was invalid",
|
|
260
|
+
toolUseID: toolUseId,
|
|
261
|
+
};
|
|
236
262
|
}
|
|
237
263
|
const answer = selectedOptions.map((option) => option.label).join(", ");
|
|
238
264
|
answers[prompt.question] = answer;
|
|
@@ -240,7 +266,11 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
|
|
|
240
266
|
if (annotation) {
|
|
241
267
|
annotations[prompt.question] = annotation;
|
|
242
268
|
}
|
|
243
|
-
transcript.push({
|
|
269
|
+
transcript.push({
|
|
270
|
+
header: prompt.header,
|
|
271
|
+
question: prompt.question,
|
|
272
|
+
answer,
|
|
273
|
+
});
|
|
244
274
|
questionResults.push({
|
|
245
275
|
question: prompt.question,
|
|
246
276
|
header: prompt.header,
|
|
@@ -268,7 +298,9 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
|
|
|
268
298
|
raw_output: summary,
|
|
269
299
|
content: [{ type: "content", content: { type: "text", text: summary } }],
|
|
270
300
|
...(completed
|
|
271
|
-
? {
|
|
301
|
+
? {
|
|
302
|
+
raw_input: askUserQuestionCompletedRawInput(prompts, answers, annotations, questionResults),
|
|
303
|
+
}
|
|
272
304
|
: {}),
|
|
273
305
|
};
|
|
274
306
|
emitToolCallUpdate(session, toolUseId, progressFields, "summary");
|
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -8,12 +8,12 @@ import { parseCommandEnvelope } from "./bridge/commands.js";
|
|
|
8
8
|
import { parseFastModeDisabledReason, parseFastModeState, } from "./bridge/state_parsing.js";
|
|
9
9
|
import { writeEvent, failConnection, slashError, emitRuntimeReloadCompleted, emitRuntimeReloadFailed, emitSessionUpdate, setSessionListingDir, } from "./bridge/events.js";
|
|
10
10
|
import { contentFromPrompt } from "./bridge/message_handlers.js";
|
|
11
|
-
import { sessions, sessionById, createSession, closeAllSessions, } from "./bridge/session_lifecycle.js";
|
|
11
|
+
import { sessions, sessionById, createSession, closeAllSessions, closeSessionWithLogging, detachSessionForClose, awaitSessionInitialization, commitDeferredSession, } from "./bridge/session_lifecycle.js";
|
|
12
12
|
import { mapSessionMessagesToUpdates } from "./bridge/history.js";
|
|
13
|
-
import { emitAvailableAgentsIfChanged, mapAvailableAgents } from "./bridge/agents.js";
|
|
14
|
-
import { mapSdkSlashCommands, updateAvailableCommands } from "./bridge/available_commands.js";
|
|
13
|
+
import { emitAvailableAgentsIfChanged, mapAvailableAgents, } from "./bridge/agents.js";
|
|
14
|
+
import { mapSdkSlashCommands, updateAvailableCommands, } from "./bridge/available_commands.js";
|
|
15
15
|
import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, emitReconciledMcpSnapshotFromStatuses, staleMcpAuthCandidates, } from "./bridge/mcp.js";
|
|
16
|
-
import { bridgeLogger, LOG_TARGETS, logBridgeCommandReceived } from "./bridge/logger.js";
|
|
16
|
+
import { bridgeLogger, LOG_TARGETS, logBridgeCommandReceived, } from "./bridge/logger.js";
|
|
17
17
|
import { BridgeCommandScheduler } from "./bridge/command_scheduler.js";
|
|
18
18
|
import { handleLifecycleCommand } from "./bridge/command_lifecycle.js";
|
|
19
19
|
import { handleInteractionCommand } from "./bridge/command_interactions.js";
|
|
@@ -23,7 +23,7 @@ import { handleSessionDataCommand } from "./bridge/command_session_data.js";
|
|
|
23
23
|
// Re-exports: all symbols that tests and external consumers import from bridge.js.
|
|
24
24
|
export { AsyncQueue } from "./bridge/shared.js";
|
|
25
25
|
export { asRecordOrNull } from "./bridge/shared.js";
|
|
26
|
-
export { CACHE_SPLIT_POLICY, previewKilobyteLabel } from "./bridge/cache_policy.js";
|
|
26
|
+
export { CACHE_SPLIT_POLICY, previewKilobyteLabel, } from "./bridge/cache_policy.js";
|
|
27
27
|
export { buildToolResultFields, createToolCall, isShellToolName, normalizeToolKind, normalizeToolResultText, unwrapToolUseResult, } from "./bridge/tooling.js";
|
|
28
28
|
export { looksLikeAuthRequired } from "./bridge/auth.js";
|
|
29
29
|
export { parseCommandEnvelope } from "./bridge/commands.js";
|
|
@@ -31,7 +31,7 @@ export { buildSessionListOptions } from "./bridge/events.js";
|
|
|
31
31
|
export { mapInitSlashCommands, mapSdkSlashCommands, updateAvailableCommands, } from "./bridge/available_commands.js";
|
|
32
32
|
export { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./bridge/permissions.js";
|
|
33
33
|
export { mapSessionMessagesToUpdates, mapSdkSessions, } from "./bridge/history.js";
|
|
34
|
-
export { handleSdkMessage, handleTaskSystemMessage } from "./bridge/message_handlers.js";
|
|
34
|
+
export { handleSdkMessage, handleTaskSystemMessage, } from "./bridge/message_handlers.js";
|
|
35
35
|
export { mapAvailableAgents } from "./bridge/agents.js";
|
|
36
36
|
export { buildQueryOptions, resolveClaudeCodeSpawnCommand, } from "./bridge/session_lifecycle.js";
|
|
37
37
|
export { mapAvailableModels } from "./bridge/model_metadata.js";
|
|
@@ -72,8 +72,12 @@ function userTextPartsFromSessionMessage(message) {
|
|
|
72
72
|
textBlocks.push(blockRecord.text);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
const firstText = textBlocks
|
|
76
|
-
|
|
75
|
+
const firstText = textBlocks
|
|
76
|
+
.map(normalizeRewindTargetText)
|
|
77
|
+
.find((text) => text.length > 0);
|
|
78
|
+
return firstText
|
|
79
|
+
? { firstText, inputText: textBlocks.join("\n").trim() }
|
|
80
|
+
: undefined;
|
|
77
81
|
}
|
|
78
82
|
export function rewindTargetsFromSessionMessages(messages) {
|
|
79
83
|
const targets = [];
|
|
@@ -89,24 +93,36 @@ export function rewindTargetsFromSessionMessages(messages) {
|
|
|
89
93
|
if (!uuid || !textParts) {
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
96
|
+
const plan = buildRewindConversationPlan(messages, uuid);
|
|
97
|
+
if (!plan) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
92
100
|
targets.push({
|
|
93
101
|
uuid,
|
|
94
102
|
first_text: textParts.firstText,
|
|
95
103
|
input_text: textParts.inputText,
|
|
96
104
|
index,
|
|
97
|
-
...(previousAssistantUuid
|
|
105
|
+
...(previousAssistantUuid
|
|
106
|
+
? { previous_assistant_uuid: previousAssistantUuid }
|
|
107
|
+
: {}),
|
|
108
|
+
...(plan.resumeSessionAtUuid
|
|
109
|
+
? { resume_anchor_uuid: plan.resumeSessionAtUuid }
|
|
110
|
+
: {}),
|
|
98
111
|
});
|
|
99
112
|
});
|
|
100
113
|
return targets.reverse();
|
|
101
114
|
}
|
|
102
115
|
export function canGenerateSessionTitle(query) {
|
|
103
|
-
return typeof query
|
|
116
|
+
return (typeof query
|
|
117
|
+
.generateSessionTitle === "function");
|
|
104
118
|
}
|
|
105
119
|
export async function generatePersistedSessionTitle(query, description) {
|
|
106
120
|
if (!canGenerateSessionTitle(query)) {
|
|
107
121
|
throw new Error("SDK query does not support generateSessionTitle");
|
|
108
122
|
}
|
|
109
|
-
const title = await query.generateSessionTitle(description, {
|
|
123
|
+
const title = await query.generateSessionTitle(description, {
|
|
124
|
+
persist: true,
|
|
125
|
+
});
|
|
110
126
|
if (typeof title !== "string" || title.trim().length === 0) {
|
|
111
127
|
throw new Error("SDK did not return a generated session title");
|
|
112
128
|
}
|
|
@@ -178,7 +194,7 @@ export function emitAgentConfigOptionUpdate(sessionId, agent) {
|
|
|
178
194
|
value: agent,
|
|
179
195
|
});
|
|
180
196
|
}
|
|
181
|
-
const EXPECTED_AGENT_SDK_VERSION = "0.3.
|
|
197
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.3.227";
|
|
182
198
|
const require = createRequire(import.meta.url);
|
|
183
199
|
export function resolveInstalledAgentSdkVersion() {
|
|
184
200
|
try {
|
|
@@ -226,8 +242,18 @@ export async function handleReloadPluginsCommand(session, requestId) {
|
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
244
|
function resolveRewindTarget(messages, targetUserMessageId) {
|
|
245
|
+
const seenUuids = new Set();
|
|
246
|
+
for (const message of messages) {
|
|
247
|
+
const uuid = message.uuid;
|
|
248
|
+
if (typeof uuid !== "string" ||
|
|
249
|
+
uuid.trim().length === 0 ||
|
|
250
|
+
seenUuids.has(uuid.trim())) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
seenUuids.add(uuid.trim());
|
|
254
|
+
}
|
|
229
255
|
let previousAssistantUuid;
|
|
230
|
-
let
|
|
256
|
+
let previousTextUserIndex;
|
|
231
257
|
for (let index = 0; index < messages.length; index += 1) {
|
|
232
258
|
const message = messages[index];
|
|
233
259
|
const record = message;
|
|
@@ -241,22 +267,22 @@ function resolveRewindTarget(messages, targetUserMessageId) {
|
|
|
241
267
|
continue;
|
|
242
268
|
}
|
|
243
269
|
if (uuid === targetUserMessageId) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
});
|
|
252
|
-
if (anchorIndex < 0) {
|
|
270
|
+
const isLatestTextUser = !messages
|
|
271
|
+
.slice(index + 1)
|
|
272
|
+
.some((candidate) => userTextPartsFromSessionMessage(candidate) !== undefined);
|
|
273
|
+
if (previousTextUserIndex !== undefined) {
|
|
274
|
+
const anchorRecord = messages[index - 1];
|
|
275
|
+
const resumeSessionAtUuid = typeof anchorRecord.uuid === "string" ? anchorRecord.uuid.trim() : "";
|
|
276
|
+
if (!resumeSessionAtUuid || index - 1 < previousTextUserIndex) {
|
|
253
277
|
return null;
|
|
254
278
|
}
|
|
255
279
|
return {
|
|
256
280
|
inputText: textParts.inputText,
|
|
257
281
|
previousAssistantUuid,
|
|
282
|
+
resumeSessionAtUuid,
|
|
283
|
+
...(isLatestTextUser ? { resumeDropsTurnId: uuid } : {}),
|
|
258
284
|
targetIndex: index,
|
|
259
|
-
retainedMessages: messages.slice(0,
|
|
285
|
+
retainedMessages: messages.slice(0, index),
|
|
260
286
|
};
|
|
261
287
|
}
|
|
262
288
|
return {
|
|
@@ -266,7 +292,7 @@ function resolveRewindTarget(messages, targetUserMessageId) {
|
|
|
266
292
|
};
|
|
267
293
|
}
|
|
268
294
|
if (uuid) {
|
|
269
|
-
|
|
295
|
+
previousTextUserIndex = index;
|
|
270
296
|
}
|
|
271
297
|
}
|
|
272
298
|
return null;
|
|
@@ -291,7 +317,9 @@ export function mapRewindFilesResult(result) {
|
|
|
291
317
|
can_rewind: result.canRewind,
|
|
292
318
|
...(result.error ? { error: result.error } : {}),
|
|
293
319
|
files_changed: result.filesChanged ?? [],
|
|
294
|
-
...(result.insertions !== undefined
|
|
320
|
+
...(result.insertions !== undefined
|
|
321
|
+
? { insertions: result.insertions }
|
|
322
|
+
: {}),
|
|
295
323
|
...(result.deletions !== undefined ? { deletions: result.deletions } : {}),
|
|
296
324
|
...(skippedLinks !== undefined ? { skipped_links: skippedLinks } : {}),
|
|
297
325
|
};
|
|
@@ -329,7 +357,7 @@ function requestIdFromCommandLine(line) {
|
|
|
329
357
|
return undefined;
|
|
330
358
|
}
|
|
331
359
|
}
|
|
332
|
-
async function
|
|
360
|
+
async function prepareConversationRewindCandidate(command, session, targetUserMessageId, requestId) {
|
|
333
361
|
const historyMessages = await getSessionMessages(command.session_id, {
|
|
334
362
|
dir: session.cwd,
|
|
335
363
|
includeSystemMessages: true,
|
|
@@ -371,74 +399,83 @@ async function replaceConversationForRewind(command, session, targetUserMessageI
|
|
|
371
399
|
stale_session_count: staleSessions.length,
|
|
372
400
|
},
|
|
373
401
|
});
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
402
|
+
let candidate;
|
|
403
|
+
try {
|
|
404
|
+
if (!resolved.resumeSessionAtUuid) {
|
|
405
|
+
bridgeLogger.info({
|
|
406
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
407
|
+
eventName: "rewind_first_message_branch",
|
|
408
|
+
message: "conversation rewind target is first user message; creating fresh replacement",
|
|
409
|
+
outcome: "start",
|
|
410
|
+
...(requestId ? { requestId } : {}),
|
|
411
|
+
sessionId: session.sessionId,
|
|
412
|
+
fields: {
|
|
413
|
+
target_user_message_id: targetUserMessageId,
|
|
414
|
+
history_message_count: historyMessages.length,
|
|
415
|
+
stale_session_count: staleSessions.length,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
candidate = await createSession({
|
|
419
|
+
cwd: session.cwd,
|
|
420
|
+
launchSettings: command.launch_settings,
|
|
421
|
+
connectEvent: "session_replaced",
|
|
422
|
+
requestId,
|
|
423
|
+
deferConnect: true,
|
|
424
|
+
restoredInput: resolved.inputText,
|
|
425
|
+
...(staleSessions.length > 0
|
|
426
|
+
? { sessionsToCloseAfterConnect: staleSessions }
|
|
427
|
+
: {}),
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
candidate = await createSession({
|
|
432
|
+
cwd: session.cwd,
|
|
433
|
+
resume: command.session_id,
|
|
434
|
+
resumeSessionAt: resolved.resumeSessionAtUuid,
|
|
435
|
+
resumeDropsTurn: resolved.resumeDropsTurnId,
|
|
436
|
+
forkSession: true,
|
|
437
|
+
launchSettings: command.launch_settings,
|
|
438
|
+
connectEvent: "session_replaced",
|
|
439
|
+
requestId,
|
|
440
|
+
deferConnect: true,
|
|
441
|
+
...(resumeUpdates.length > 0 ? { resumeUpdates } : {}),
|
|
442
|
+
restoredInput: resolved.inputText,
|
|
443
|
+
...(staleSessions.length > 0
|
|
444
|
+
? { sessionsToCloseAfterConnect: staleSessions }
|
|
445
|
+
: {}),
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
await awaitSessionInitialization(candidate);
|
|
397
449
|
bridgeLogger.info({
|
|
398
450
|
target: LOG_TARGETS.APP_SESSION,
|
|
399
|
-
eventName: "
|
|
400
|
-
message: "conversation rewind
|
|
451
|
+
eventName: "rewind_candidate_validated",
|
|
452
|
+
message: "conversation rewind candidate passed initialization and truncation validation",
|
|
401
453
|
outcome: "success",
|
|
402
454
|
...(requestId ? { requestId } : {}),
|
|
455
|
+
sessionId: candidate.sessionId,
|
|
403
456
|
fields: {
|
|
404
457
|
target_user_message_id: targetUserMessageId,
|
|
405
|
-
resume_session_at: "<none>",
|
|
406
|
-
|
|
407
|
-
|
|
458
|
+
resume_session_at: resolved.resumeSessionAtUuid ?? "<none>",
|
|
459
|
+
resume_drops_turn: resolved.resumeSessionAtUuid
|
|
460
|
+
? (resolved.resumeDropsTurnId ?? "<none>")
|
|
461
|
+
: "<none>",
|
|
462
|
+
retained_message_count: resolved.retainedMessages.length,
|
|
463
|
+
retained_update_count: resumeUpdates.length,
|
|
408
464
|
stale_session_count: staleSessions.length,
|
|
409
465
|
},
|
|
410
466
|
});
|
|
411
|
-
return;
|
|
467
|
+
return candidate;
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
if (candidate) {
|
|
471
|
+
detachSessionForClose(candidate);
|
|
472
|
+
await closeSessionWithLogging(candidate, {
|
|
473
|
+
reason: "rewind_candidate_rejected",
|
|
474
|
+
requestId,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
throw error;
|
|
412
478
|
}
|
|
413
|
-
const sessionsToCloseAfterConnect = staleSessions.filter((stale) => stale !== session);
|
|
414
|
-
await createSession({
|
|
415
|
-
cwd: session.cwd,
|
|
416
|
-
resume: command.session_id,
|
|
417
|
-
resumeSessionAt: resolved.previousAssistantUuid,
|
|
418
|
-
launchSettings: command.launch_settings,
|
|
419
|
-
connectEvent: "session_replaced",
|
|
420
|
-
requestId,
|
|
421
|
-
sessionsToCloseBeforeRegister: [session],
|
|
422
|
-
...(resumeUpdates.length > 0 ? { resumeUpdates } : {}),
|
|
423
|
-
restoredInput: resolved.inputText,
|
|
424
|
-
...(pendingRewindResult ? { pendingRewindResult } : {}),
|
|
425
|
-
...(sessionsToCloseAfterConnect.length > 0 ? { sessionsToCloseAfterConnect } : {}),
|
|
426
|
-
});
|
|
427
|
-
bridgeLogger.info({
|
|
428
|
-
target: LOG_TARGETS.APP_SESSION,
|
|
429
|
-
eventName: "rewind_replacement_created",
|
|
430
|
-
message: "conversation rewind replacement session created",
|
|
431
|
-
outcome: "success",
|
|
432
|
-
...(requestId ? { requestId } : {}),
|
|
433
|
-
sessionId: command.session_id,
|
|
434
|
-
fields: {
|
|
435
|
-
target_user_message_id: targetUserMessageId,
|
|
436
|
-
resume_session_at: resolved.previousAssistantUuid,
|
|
437
|
-
retained_message_count: resolved.retainedMessages.length,
|
|
438
|
-
retained_update_count: resumeUpdates.length,
|
|
439
|
-
stale_session_count: staleSessions.length,
|
|
440
|
-
},
|
|
441
|
-
});
|
|
442
479
|
}
|
|
443
480
|
async function handleRewind(command, requestId) {
|
|
444
481
|
const session = sessionById(command.session_id);
|
|
@@ -465,7 +502,8 @@ async function handleRewind(command, requestId) {
|
|
|
465
502
|
});
|
|
466
503
|
if (command.restore_mode === "conversation") {
|
|
467
504
|
try {
|
|
468
|
-
await
|
|
505
|
+
const candidate = await prepareConversationRewindCandidate(command, session, targetUserMessageId, requestId);
|
|
506
|
+
commitDeferredSession(candidate);
|
|
469
507
|
}
|
|
470
508
|
catch (error) {
|
|
471
509
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -486,18 +524,13 @@ async function handleRewind(command, requestId) {
|
|
|
486
524
|
}
|
|
487
525
|
return;
|
|
488
526
|
}
|
|
489
|
-
let
|
|
527
|
+
let dryRunResult;
|
|
490
528
|
try {
|
|
491
|
-
|
|
529
|
+
dryRunResult = await rewindFiles(session, targetUserMessageId, true);
|
|
492
530
|
if (!dryRunResult.can_rewind) {
|
|
493
531
|
slashError(command.session_id, dryRunResult.error ?? "failed to dry-run file rewind", requestId);
|
|
494
532
|
return;
|
|
495
533
|
}
|
|
496
|
-
appliedFileResult = await rewindFiles(session, targetUserMessageId, false);
|
|
497
|
-
if (!appliedFileResult.can_rewind) {
|
|
498
|
-
slashError(command.session_id, appliedFileResult.error ?? "failed to restore code", requestId);
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
534
|
}
|
|
502
535
|
catch (error) {
|
|
503
536
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -518,24 +551,61 @@ async function handleRewind(command, requestId) {
|
|
|
518
551
|
return;
|
|
519
552
|
}
|
|
520
553
|
if (command.restore_mode === "code") {
|
|
521
|
-
|
|
554
|
+
try {
|
|
555
|
+
const appliedFileResult = await rewindFiles(session, targetUserMessageId, false);
|
|
556
|
+
if (!appliedFileResult.can_rewind) {
|
|
557
|
+
slashError(command.session_id, appliedFileResult.error ?? "failed to restore code", requestId);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
emitRewindResult(session.sessionId, command.restore_mode, "success", requestId, appliedFileResult);
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
564
|
+
slashError(command.session_id, `failed to restore code: ${message}`, requestId);
|
|
565
|
+
}
|
|
522
566
|
return;
|
|
523
567
|
}
|
|
568
|
+
let candidate;
|
|
524
569
|
try {
|
|
525
|
-
await
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
570
|
+
candidate = await prepareConversationRewindCandidate(command, session, targetUserMessageId, requestId);
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
574
|
+
bridgeLogger.error({
|
|
575
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
576
|
+
eventName: "rewind_failed",
|
|
577
|
+
message: "conversation candidate failed before file restore",
|
|
578
|
+
outcome: "failure",
|
|
579
|
+
...(requestId ? { requestId } : {}),
|
|
580
|
+
sessionId: session.sessionId,
|
|
581
|
+
fields: {
|
|
582
|
+
target_user_message_id: targetUserMessageId,
|
|
583
|
+
restore_mode: command.restore_mode,
|
|
584
|
+
error_message: message,
|
|
585
|
+
},
|
|
530
586
|
});
|
|
587
|
+
slashError(command.session_id, `failed to validate conversation rewind: ${message}`, requestId);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
let appliedFileResult;
|
|
591
|
+
try {
|
|
592
|
+
appliedFileResult = await rewindFiles(session, targetUserMessageId, false);
|
|
593
|
+
if (!appliedFileResult.can_rewind) {
|
|
594
|
+
throw new Error(appliedFileResult.error ?? "failed to restore code");
|
|
595
|
+
}
|
|
531
596
|
}
|
|
532
597
|
catch (error) {
|
|
598
|
+
detachSessionForClose(candidate);
|
|
599
|
+
await closeSessionWithLogging(candidate, {
|
|
600
|
+
reason: "rewind_file_apply_failed",
|
|
601
|
+
requestId,
|
|
602
|
+
});
|
|
533
603
|
const message = error instanceof Error ? error.message : String(error);
|
|
534
604
|
bridgeLogger.error({
|
|
535
605
|
target: LOG_TARGETS.APP_SESSION,
|
|
536
606
|
eventName: "rewind_failed",
|
|
537
|
-
message: "
|
|
538
|
-
outcome: "
|
|
607
|
+
message: "file rewind failed after conversation candidate validation",
|
|
608
|
+
outcome: "failure",
|
|
539
609
|
...(requestId ? { requestId } : {}),
|
|
540
610
|
sessionId: session.sessionId,
|
|
541
611
|
fields: {
|
|
@@ -544,13 +614,34 @@ async function handleRewind(command, requestId) {
|
|
|
544
614
|
error_message: message,
|
|
545
615
|
},
|
|
546
616
|
});
|
|
547
|
-
|
|
617
|
+
slashError(command.session_id, `failed to restore code: ${message}`, requestId);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
candidate.pendingRewindResult = {
|
|
621
|
+
event: "rewind_result",
|
|
622
|
+
restore_mode: command.restore_mode,
|
|
623
|
+
status: "success",
|
|
624
|
+
file_result: appliedFileResult,
|
|
625
|
+
};
|
|
626
|
+
try {
|
|
627
|
+
commitDeferredSession(candidate);
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
detachSessionForClose(candidate);
|
|
631
|
+
await closeSessionWithLogging(candidate, {
|
|
632
|
+
reason: "rewind_candidate_failed_after_file_apply",
|
|
633
|
+
requestId,
|
|
634
|
+
});
|
|
635
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
636
|
+
emitRewindResult(session.sessionId, command.restore_mode, "partial_failure", requestId, appliedFileResult, `Code was restored, but the conversation candidate could not be committed: ${message}`);
|
|
548
637
|
}
|
|
549
638
|
}
|
|
550
639
|
async function handleCommand(command, requestId) {
|
|
551
640
|
logBridgeCommandReceived(command, requestId);
|
|
552
641
|
const sdkVersionError = agentSdkVersionCompatibilityError();
|
|
553
|
-
if (sdkVersionError &&
|
|
642
|
+
if (sdkVersionError &&
|
|
643
|
+
command.command !== "initialize" &&
|
|
644
|
+
command.command !== "shutdown") {
|
|
554
645
|
bridgeLogger.error({
|
|
555
646
|
target: LOG_TARGETS.BRIDGE_LIFECYCLE,
|
|
556
647
|
eventName: "bridge_command_rejected",
|
|
@@ -569,9 +660,12 @@ async function handleCommand(command, requestId) {
|
|
|
569
660
|
case "initialize":
|
|
570
661
|
case "create_session":
|
|
571
662
|
case "resume_session":
|
|
663
|
+
case "resume_session_at":
|
|
572
664
|
case "new_session":
|
|
573
665
|
case "shutdown":
|
|
574
|
-
await handleLifecycleCommand(command, requestId, sdkVersionError
|
|
666
|
+
await handleLifecycleCommand(command, requestId, sdkVersionError, {
|
|
667
|
+
buildRewindConversationPlan,
|
|
668
|
+
});
|
|
575
669
|
return;
|
|
576
670
|
case "prompt":
|
|
577
671
|
case "cancel_turn":
|
|
@@ -595,6 +689,7 @@ async function handleCommand(command, requestId) {
|
|
|
595
689
|
case "rename_session":
|
|
596
690
|
case "get_status_snapshot":
|
|
597
691
|
case "get_context_usage":
|
|
692
|
+
case "get_usage":
|
|
598
693
|
case "get_rewind_targets":
|
|
599
694
|
case "rewind":
|
|
600
695
|
await handleSessionDataCommand(command, requestId, {
|
|
@@ -720,6 +815,7 @@ function main() {
|
|
|
720
815
|
});
|
|
721
816
|
});
|
|
722
817
|
}
|
|
723
|
-
if (process.argv[1] &&
|
|
818
|
+
if (process.argv[1] &&
|
|
819
|
+
import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
724
820
|
main();
|
|
725
821
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-rust",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "Claude Code Rust - native Rust terminal interface for Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"LICENSE"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
36
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.227"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@srothgan/claude-code-rust-darwin-arm64": "0.14.
|
|
40
|
-
"@srothgan/claude-code-rust-darwin-x64": "0.14.
|
|
41
|
-
"@srothgan/claude-code-rust-linux-x64-gnu": "0.14.
|
|
42
|
-
"@srothgan/claude-code-rust-linux-arm64-gnu": "0.14.
|
|
43
|
-
"@srothgan/claude-code-rust-win32-x64-msvc": "0.14.
|
|
44
|
-
"@srothgan/claude-code-rust-win32-arm64-msvc": "0.14.
|
|
39
|
+
"@srothgan/claude-code-rust-darwin-arm64": "0.14.4",
|
|
40
|
+
"@srothgan/claude-code-rust-darwin-x64": "0.14.4",
|
|
41
|
+
"@srothgan/claude-code-rust-linux-x64-gnu": "0.14.4",
|
|
42
|
+
"@srothgan/claude-code-rust-linux-arm64-gnu": "0.14.4",
|
|
43
|
+
"@srothgan/claude-code-rust-win32-x64-msvc": "0.14.4",
|
|
44
|
+
"@srothgan/claude-code-rust-win32-arm64-msvc": "0.14.4"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24"
|