claude-code-rust 0.13.4 → 0.14.1
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/dist/bridge/account_metadata.js +1 -0
- package/agent-sdk/dist/bridge/command_dispatch.js +19 -1
- package/agent-sdk/dist/bridge/commands.js +4 -0
- package/agent-sdk/dist/bridge/message_handlers.js +39 -3
- package/agent-sdk/dist/bridge/session_lifecycle.js +18 -7
- package/agent-sdk/dist/bridge/state_parsing.js +28 -0
- package/agent-sdk/dist/bridge/tasks.js +40 -0
- package/agent-sdk/dist/bridge/tool_calls.js +46 -6
- package/agent-sdk/dist/bridge/tooling.js +53 -8
- package/agent-sdk/dist/bridge.js +20 -15
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -16,28 +16,33 @@ A native Rust terminal interface for Claude Code. Drop-in replacement for Anthro
|
|
|
16
16
|
|
|
17
17
|
Claude Code Rust replaces the stock Claude Code terminal interface with a native Rust binary built on [Ratatui](https://ratatui.rs/). It connects to the same Claude API through a local Agent SDK bridge. Core Claude Code functionality - tool calls, file editing, terminal commands, and permissions - works unchanged.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Prerequisite
|
|
20
20
|
|
|
21
|
-
-
|
|
21
|
+
- The Claude Code CLI must be installed as fallback for some SDK-unsupported features.
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### Install script (recommended, v0.14.0+)
|
|
26
|
+
|
|
27
|
+
**macOS/Linux:**
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
|
-
|
|
30
|
+
curl -fsSL https://raw.githubusercontent.com/srothgan/claude-code-rust/main/scripts/install/install.sh | sh
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
**Windows PowerShell:**
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm 'https://raw.githubusercontent.com/srothgan/claude-code-rust/main/scripts/install/install.ps1' | iex"
|
|
37
|
+
```
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
### npm (global)
|
|
34
40
|
|
|
35
41
|
```bash
|
|
36
|
-
npm config get omit
|
|
37
42
|
npm install -g claude-code-rust
|
|
38
43
|
```
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
See the [installation guide](https://srothgan.github.io/claude-code-rust/installation.html) for release pinning, custom install locations, switching install methods, uninstall, and troubleshooting.
|
|
41
46
|
|
|
42
47
|
## Usage
|
|
43
48
|
|
|
@@ -66,13 +71,15 @@ Claude Code Rust addresses these with a native terminal UI that uses diffed, dir
|
|
|
66
71
|
|
|
67
72
|
## Documentation
|
|
68
73
|
|
|
69
|
-
The manual covers installation
|
|
74
|
+
The manual covers installation with scripts and npm, plus help, slash commands, keyboard shortcuts, settings, diagnostics, troubleshooting, building from source, architecture, and the changelog:
|
|
70
75
|
|
|
71
76
|
- [Installation](https://srothgan.github.io/claude-code-rust/installation.html)
|
|
72
77
|
- [Usage](https://srothgan.github.io/claude-code-rust/usage.html)
|
|
73
78
|
- [Help](https://srothgan.github.io/claude-code-rust/help.html)
|
|
74
79
|
- [Slash commands](https://srothgan.github.io/claude-code-rust/commands.html)
|
|
75
80
|
- [Settings](https://srothgan.github.io/claude-code-rust/settings.html)
|
|
81
|
+
- [Troubleshooting](https://srothgan.github.io/claude-code-rust/troubleshooting.html)
|
|
82
|
+
- [Development](https://srothgan.github.io/claude-code-rust/development.html)
|
|
76
83
|
|
|
77
84
|
## Status
|
|
78
85
|
|
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
1
2
|
export async function dispatchCancelTurnCommand(command, deps) {
|
|
2
3
|
const session = deps.sessionById(command.session_id);
|
|
3
4
|
if (!session) {
|
|
4
5
|
deps.slashError(command.session_id, `unknown session: ${command.session_id}`, deps.requestId);
|
|
5
6
|
return;
|
|
6
7
|
}
|
|
7
|
-
await session.query.interrupt();
|
|
8
|
+
const receipt = await session.query.interrupt();
|
|
9
|
+
const stillQueued = Array.isArray(receipt?.still_queued)
|
|
10
|
+
? receipt.still_queued.filter((entry) => typeof entry === "string")
|
|
11
|
+
: [];
|
|
12
|
+
if (stillQueued.length > 0) {
|
|
13
|
+
bridgeLogger.info({
|
|
14
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
15
|
+
eventName: "interrupt_receipt_still_queued",
|
|
16
|
+
message: "interrupt receipt reported queued async messages",
|
|
17
|
+
outcome: "success",
|
|
18
|
+
sessionId: command.session_id,
|
|
19
|
+
...(deps.requestId ? { requestId: deps.requestId } : {}),
|
|
20
|
+
fields: {
|
|
21
|
+
still_queued_count: stillQueued.length,
|
|
22
|
+
still_queued: stillQueued,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
8
26
|
}
|
|
@@ -307,6 +307,7 @@ export function parseCommandEnvelope(line) {
|
|
|
307
307
|
session_id: expectString(raw, "session_id", "reload_plugins"),
|
|
308
308
|
};
|
|
309
309
|
case "mcp_status":
|
|
310
|
+
// Rust historically sends `get_mcp_snapshot`; normalize it to the bridge-internal command.
|
|
310
311
|
case "get_mcp_snapshot":
|
|
311
312
|
return {
|
|
312
313
|
command: "mcp_status",
|
|
@@ -450,6 +451,9 @@ function parseQuestionAnnotation(value) {
|
|
|
450
451
|
};
|
|
451
452
|
}
|
|
452
453
|
export function toPermissionMode(mode) {
|
|
454
|
+
if (mode === "manual") {
|
|
455
|
+
return "default";
|
|
456
|
+
}
|
|
453
457
|
if (mode === "default" ||
|
|
454
458
|
mode === "auto" ||
|
|
455
459
|
mode === "acceptEdits" ||
|
|
@@ -3,12 +3,12 @@ import { toPermissionMode, buildModeState, refreshSupportedModesForSession } fro
|
|
|
3
3
|
import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
|
|
4
4
|
import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, } from "./tooling.js";
|
|
5
5
|
import { emitToolCall, emitToolCallUpdate, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, defersTaskNotificationCompletion, toolAcceptsTaskLifecycle, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
|
|
6
|
-
import { applyTaskLifecycleState } from "./tasks.js";
|
|
6
|
+
import { applyBackgroundTasksChanged, applyTaskLifecycleState } from "./tasks.js";
|
|
7
7
|
import { linkTaskToolUse, unlinkTaskToolUse } from "./task_links.js";
|
|
8
8
|
import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
|
|
9
9
|
import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
|
|
10
10
|
import { mapInitSlashCommands, mapSdkSlashCommands, updateAvailableCommands, } from "./available_commands.js";
|
|
11
|
-
import { buildApiRetryUpdate, buildRateLimitUpdate, normalizeSettingsParseErrors, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
|
|
11
|
+
import { buildApiRetryUpdate, buildRateLimitUpdate, buildSubagentRetryUpdate, normalizeSettingsParseErrors, numberField, parseApiRetryError, parseRuntimeSessionState, } from "./state_parsing.js";
|
|
12
12
|
import { looksLikeAuthRequired } from "./auth.js";
|
|
13
13
|
import { emitCurrentModelUpdate, refreshCurrentModel, updateSessionId } from "./session_lifecycle.js";
|
|
14
14
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
@@ -39,6 +39,7 @@ function sdkCorrelationMetadata(msg) {
|
|
|
39
39
|
requestId: typeof msg.request_id === "string" ? msg.request_id : undefined,
|
|
40
40
|
subagentType: typeof msg.subagent_type === "string" ? msg.subagent_type : undefined,
|
|
41
41
|
taskDescription: typeof msg.task_description === "string" ? msg.task_description : undefined,
|
|
42
|
+
parentAgentId: typeof msg.parent_agent_id === "string" ? msg.parent_agent_id : undefined,
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
function sdkTaskMetadata(msg) {
|
|
@@ -53,12 +54,14 @@ function sdkTaskMetadata(msg) {
|
|
|
53
54
|
...(metadata.requestId ? { request_id: metadata.requestId } : {}),
|
|
54
55
|
...(metadata.subagentType ? { subagent_type: metadata.subagentType } : {}),
|
|
55
56
|
...(metadata.taskDescription ? { task_description: metadata.taskDescription } : {}),
|
|
57
|
+
...(metadata.parentAgentId ? { parent_agent_id: metadata.parentAgentId } : {}),
|
|
56
58
|
...(taskType ? { task_type: taskType } : {}),
|
|
57
59
|
...(workflowName ? { workflow_name: workflowName } : {}),
|
|
58
60
|
...(prompt ? { prompt } : {}),
|
|
59
61
|
...(outputFile ? { output_file: outputFile } : {}),
|
|
60
62
|
...(summary ? { summary } : {}),
|
|
61
63
|
...(status ? { terminal_status: status } : {}),
|
|
64
|
+
...(typeof msg.blocked === "boolean" ? { blocked: msg.blocked } : {}),
|
|
62
65
|
};
|
|
63
66
|
return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
|
|
64
67
|
}
|
|
@@ -822,6 +825,13 @@ function terminalReasonFromValue(value) {
|
|
|
822
825
|
case "hook_stopped":
|
|
823
826
|
case "tool_deferred":
|
|
824
827
|
case "max_turns":
|
|
828
|
+
case "background_requested":
|
|
829
|
+
case "api_error":
|
|
830
|
+
case "malformed_tool_use_exhausted":
|
|
831
|
+
case "budget_exhausted":
|
|
832
|
+
case "structured_output_retry_exhausted":
|
|
833
|
+
case "tool_deferred_unavailable":
|
|
834
|
+
case "turn_setup_failed":
|
|
825
835
|
case "completed":
|
|
826
836
|
return value;
|
|
827
837
|
default:
|
|
@@ -848,6 +858,10 @@ export function handleSdkMessage(session, message) {
|
|
|
848
858
|
updateAvailableCommands(session, "commands_changed", mapSdkSlashCommands(msg.commands));
|
|
849
859
|
return;
|
|
850
860
|
}
|
|
861
|
+
if (subtype === "background_tasks_changed") {
|
|
862
|
+
applyBackgroundTasksChanged(session, msg);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
851
865
|
if (subtype === "notification") {
|
|
852
866
|
const text = typeof msg.text === "string" ? msg.text : "";
|
|
853
867
|
emitSystemNoticeUpdate(session, notificationSeverity(msg.priority), text);
|
|
@@ -1145,7 +1159,29 @@ export function handleSdkMessage(session, message) {
|
|
|
1145
1159
|
},
|
|
1146
1160
|
});
|
|
1147
1161
|
if (resolvedToolUseId) {
|
|
1148
|
-
|
|
1162
|
+
const hasSubagentRetry = Object.hasOwn(msg, "subagent_retry");
|
|
1163
|
+
const subagentRetry = hasSubagentRetry ? buildSubagentRetryUpdate(msg) : null;
|
|
1164
|
+
if (hasSubagentRetry && !subagentRetry) {
|
|
1165
|
+
bridgeLogger.warn({
|
|
1166
|
+
target: LOG_TARGETS.APP_TOOL,
|
|
1167
|
+
eventName: "sdk_subagent_retry_rejected",
|
|
1168
|
+
message: "ignored malformed SDK subagent retry progress",
|
|
1169
|
+
outcome: "invalid_payload",
|
|
1170
|
+
sessionId: session.sessionId,
|
|
1171
|
+
toolCallId: resolvedToolUseId,
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
const subagentType = typeof msg.subagent_type === "string" && msg.subagent_type.trim()
|
|
1175
|
+
? msg.subagent_type.trim()
|
|
1176
|
+
: undefined;
|
|
1177
|
+
emitToolProgressUpdate(session, resolvedToolUseId, toolName, {
|
|
1178
|
+
...(subagentRetry
|
|
1179
|
+
? { subagentRetry }
|
|
1180
|
+
: hasSubagentRetry
|
|
1181
|
+
? {}
|
|
1182
|
+
: { subagentRetry: { state: "clear" } }),
|
|
1183
|
+
...(subagentType ? { subagentType } : {}),
|
|
1184
|
+
});
|
|
1149
1185
|
}
|
|
1150
1186
|
return;
|
|
1151
1187
|
}
|
|
@@ -100,23 +100,28 @@ function settingsObjectFromLaunchSettings(launchSettings) {
|
|
|
100
100
|
return launchSettings.settings;
|
|
101
101
|
}
|
|
102
102
|
function normalizedSettingsFromLaunchSettings(launchSettings) {
|
|
103
|
-
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
const settings = settingsObjectFromLaunchSettings(launchSettings) ?? {};
|
|
104
|
+
// SendFeedback queues a local draft that can only be reviewed, edited, and
|
|
105
|
+
// discarded through the native /feedback surface. Agent SDK command
|
|
106
|
+
// snapshots do not expose that command to this host, so enabling drafts
|
|
107
|
+
// would create content that claude-rs cannot safely let the user approve.
|
|
108
|
+
const hostSettings = {
|
|
109
|
+
...settings,
|
|
110
|
+
feedbackDrafts: "off",
|
|
111
|
+
};
|
|
107
112
|
const sandbox = settings.sandbox && typeof settings.sandbox === "object" && !Array.isArray(settings.sandbox)
|
|
108
113
|
? settings.sandbox
|
|
109
114
|
: undefined;
|
|
110
115
|
if (sandbox?.enabled === true && sandbox.failIfUnavailable === undefined) {
|
|
111
116
|
return {
|
|
112
|
-
...
|
|
117
|
+
...hostSettings,
|
|
113
118
|
sandbox: {
|
|
114
119
|
...sandbox,
|
|
115
120
|
failIfUnavailable: false,
|
|
116
121
|
},
|
|
117
122
|
};
|
|
118
123
|
}
|
|
119
|
-
return
|
|
124
|
+
return hostSettings;
|
|
120
125
|
}
|
|
121
126
|
export function sessionById(sessionId) {
|
|
122
127
|
return sessions.get(sessionId) ?? null;
|
|
@@ -542,6 +547,8 @@ function permissionModeFromSettingsValue(rawMode) {
|
|
|
542
547
|
return undefined;
|
|
543
548
|
}
|
|
544
549
|
switch (rawMode) {
|
|
550
|
+
case "manual":
|
|
551
|
+
return "default";
|
|
545
552
|
case "default":
|
|
546
553
|
case "auto":
|
|
547
554
|
case "acceptEdits":
|
|
@@ -610,9 +617,13 @@ export function buildQueryOptions(params) {
|
|
|
610
617
|
includePartialMessages: true,
|
|
611
618
|
promptSuggestions: true,
|
|
612
619
|
enableFileCheckpointing: true,
|
|
620
|
+
// ProposeSkills reports only a proposal count and expects a native review
|
|
621
|
+
// surface. Keep it out of this host until claude-rs can display the actual
|
|
622
|
+
// proposal input and accept/reject it without losing content.
|
|
623
|
+
disallowedTools: ["ProposeSkills"],
|
|
613
624
|
executable: "bun",
|
|
614
625
|
...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
|
|
615
|
-
|
|
626
|
+
settings,
|
|
616
627
|
...modelOption,
|
|
617
628
|
...permissionModeOptions,
|
|
618
629
|
toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
|
|
@@ -15,6 +15,34 @@ function nonNegativeNumberField(record, ...keys) {
|
|
|
15
15
|
}
|
|
16
16
|
return value;
|
|
17
17
|
}
|
|
18
|
+
function nonNegativeIntegerField(record, ...keys) {
|
|
19
|
+
const value = numberField(record, ...keys);
|
|
20
|
+
return value !== undefined && value >= 0 && Number.isInteger(value) ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
export function buildSubagentRetryUpdate(message) {
|
|
23
|
+
const retry = asRecordOrNull(message.subagent_retry);
|
|
24
|
+
if (!retry) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const attempt = nonNegativeIntegerField(retry, "attempt");
|
|
28
|
+
const maxRetries = nonNegativeIntegerField(retry, "max_retries", "maxRetries");
|
|
29
|
+
const retryDelayMs = nonNegativeIntegerField(retry, "retry_delay_ms", "retryDelayMs");
|
|
30
|
+
if (attempt === undefined || maxRetries === undefined || retryDelayMs === undefined) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const agentId = typeof retry.agent_id === "string" ? retry.agent_id.trim() : "";
|
|
34
|
+
const errorCategory = typeof retry.error_category === "string" ? retry.error_category.trim() : "";
|
|
35
|
+
const errorStatus = nonNegativeIntegerField(retry, "error_status", "errorStatus");
|
|
36
|
+
return {
|
|
37
|
+
state: "waiting",
|
|
38
|
+
...(agentId ? { agent_id: agentId } : {}),
|
|
39
|
+
attempt,
|
|
40
|
+
max_retries: maxRetries,
|
|
41
|
+
retry_delay_ms: retryDelayMs,
|
|
42
|
+
...(errorStatus !== undefined ? { error_status: errorStatus } : {}),
|
|
43
|
+
...(errorCategory ? { error_category: errorCategory } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
18
46
|
export function parseFastModeState(value) {
|
|
19
47
|
if (value === "off" || value === "cooldown" || value === "on") {
|
|
20
48
|
return value;
|
|
@@ -9,6 +9,7 @@ const TASK_TOOL_NAMES = new Set([
|
|
|
9
9
|
"TaskOutput",
|
|
10
10
|
"TaskStop",
|
|
11
11
|
]);
|
|
12
|
+
const BACKGROUND_TASK_METADATA_KEY = "sdk_background_task";
|
|
12
13
|
export function isTaskToolName(name) {
|
|
13
14
|
return TASK_TOOL_NAMES.has(name);
|
|
14
15
|
}
|
|
@@ -806,6 +807,8 @@ function lifecycleMetadata(msg) {
|
|
|
806
807
|
"summary",
|
|
807
808
|
"end_time",
|
|
808
809
|
"total_paused_ms",
|
|
810
|
+
"blocked",
|
|
811
|
+
"parent_agent_id",
|
|
809
812
|
]) {
|
|
810
813
|
copyValue(msg, key);
|
|
811
814
|
copyValue(patch, key);
|
|
@@ -857,6 +860,43 @@ export function applyTaskLifecycleState(session, subtype, msg) {
|
|
|
857
860
|
}),
|
|
858
861
|
]);
|
|
859
862
|
}
|
|
863
|
+
export function applyBackgroundTasksChanged(session, msg) {
|
|
864
|
+
const rawTasks = Array.isArray(msg.tasks) ? msg.tasks : [];
|
|
865
|
+
const nextTasks = rawTasks
|
|
866
|
+
.map((entry) => {
|
|
867
|
+
const task = asRecordOrNull(entry);
|
|
868
|
+
const taskId = nonEmptyString(task?.task_id);
|
|
869
|
+
if (!task || !taskId) {
|
|
870
|
+
return undefined;
|
|
871
|
+
}
|
|
872
|
+
const taskType = nonEmptyString(task.task_type);
|
|
873
|
+
const description = nonEmptyString(task.description);
|
|
874
|
+
return {
|
|
875
|
+
task_id: taskId,
|
|
876
|
+
subject: description ?? taskType ?? taskId,
|
|
877
|
+
description,
|
|
878
|
+
status: "in_progress",
|
|
879
|
+
blocks: [],
|
|
880
|
+
blocked_by: [],
|
|
881
|
+
metadata: {
|
|
882
|
+
[BACKGROUND_TASK_METADATA_KEY]: true,
|
|
883
|
+
...(taskType ? { task_type: taskType } : {}),
|
|
884
|
+
},
|
|
885
|
+
};
|
|
886
|
+
})
|
|
887
|
+
.filter((task) => Boolean(task));
|
|
888
|
+
const nextIds = new Set(nextTasks.map((task) => task.task_id));
|
|
889
|
+
const removedTaskIds = session.taskOrder.filter((taskId) => {
|
|
890
|
+
if (nextIds.has(taskId)) {
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
const metadata = asRecordOrNull(session.tasksById.get(taskId)?.metadata);
|
|
894
|
+
return metadata?.[BACKGROUND_TASK_METADATA_KEY] === true;
|
|
895
|
+
});
|
|
896
|
+
const tasks = nextTasks.map((task) => upsertTask(session, task));
|
|
897
|
+
const removed = removeTasks(session, removedTaskIds);
|
|
898
|
+
emitTaskStateUpdate(session, "background_tasks", tasks, removed, true);
|
|
899
|
+
}
|
|
860
900
|
export function currentTaskSnapshot(session) {
|
|
861
901
|
return orderedTasks(session);
|
|
862
902
|
}
|
|
@@ -112,10 +112,14 @@ function mergeTaskMetadata(current, update) {
|
|
|
112
112
|
if (update === undefined) {
|
|
113
113
|
return current;
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
const merged = {
|
|
116
116
|
...(current ?? {}),
|
|
117
117
|
...update,
|
|
118
118
|
};
|
|
119
|
+
if (update.subagent_retry?.state === "clear") {
|
|
120
|
+
delete merged.subagent_retry;
|
|
121
|
+
}
|
|
122
|
+
return merged;
|
|
119
123
|
}
|
|
120
124
|
function applyFieldsToBase(base, fields) {
|
|
121
125
|
if (fields.title !== undefined) {
|
|
@@ -237,6 +241,16 @@ function taskTitleContext(session, name, input) {
|
|
|
237
241
|
}
|
|
238
242
|
export function emitToolCallUpdate(session, toolUseId, fields, updateKind, sourceMessageUuid) {
|
|
239
243
|
const base = session.toolCalls.get(toolUseId);
|
|
244
|
+
const nextStatus = fields.status ?? base?.status;
|
|
245
|
+
const terminal = nextStatus === "completed" || nextStatus === "failed" || nextStatus === "killed";
|
|
246
|
+
const hasActiveRetry = base?.task_metadata?.subagent_retry?.state === "waiting" ||
|
|
247
|
+
fields.task_metadata?.subagent_retry?.state === "waiting";
|
|
248
|
+
if (terminal && hasActiveRetry) {
|
|
249
|
+
fields.task_metadata = {
|
|
250
|
+
...(fields.task_metadata ?? {}),
|
|
251
|
+
subagent_retry: { state: "clear" },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
240
254
|
logToolCallUpdateEmitted(session.sessionId, toolUseId, fields, base, updateKind);
|
|
241
255
|
emitSessionUpdate(session.sessionId, {
|
|
242
256
|
type: "tool_call_update",
|
|
@@ -324,19 +338,39 @@ export function finalizeOpenToolCalls(session, status) {
|
|
|
324
338
|
emitToolCallUpdate(session, toolUseId, { status }, "finalize");
|
|
325
339
|
}
|
|
326
340
|
}
|
|
327
|
-
export function emitToolProgressUpdate(session, toolUseId, toolName) {
|
|
328
|
-
|
|
341
|
+
export function emitToolProgressUpdate(session, toolUseId, toolName, progress = {}) {
|
|
342
|
+
let existing = session.toolCalls.get(toolUseId);
|
|
329
343
|
if (!existing) {
|
|
330
344
|
emitToolCall(session, toolUseId, toolName, {});
|
|
331
|
-
|
|
345
|
+
existing = session.toolCalls.get(toolUseId);
|
|
332
346
|
}
|
|
333
|
-
if (existing
|
|
347
|
+
if (!existing ||
|
|
334
348
|
existing.status === "completed" ||
|
|
335
349
|
existing.status === "failed" ||
|
|
336
350
|
existing.status === "killed") {
|
|
337
351
|
return;
|
|
338
352
|
}
|
|
339
|
-
|
|
353
|
+
const taskMetadata = {};
|
|
354
|
+
if (progress.subagentRetry?.state === "waiting") {
|
|
355
|
+
taskMetadata.subagent_retry = progress.subagentRetry;
|
|
356
|
+
}
|
|
357
|
+
else if (progress.subagentRetry?.state === "clear" &&
|
|
358
|
+
existing.task_metadata?.subagent_retry?.state === "waiting") {
|
|
359
|
+
taskMetadata.subagent_retry = progress.subagentRetry;
|
|
360
|
+
}
|
|
361
|
+
if (progress.subagentType && progress.subagentType !== existing.task_metadata?.subagent_type) {
|
|
362
|
+
taskMetadata.subagent_type = progress.subagentType;
|
|
363
|
+
}
|
|
364
|
+
const fields = {};
|
|
365
|
+
if (existing.status !== "in_progress") {
|
|
366
|
+
fields.status = "in_progress";
|
|
367
|
+
}
|
|
368
|
+
if (Object.keys(taskMetadata).length > 0) {
|
|
369
|
+
fields.task_metadata = taskMetadata;
|
|
370
|
+
}
|
|
371
|
+
if (Object.keys(fields).length > 0) {
|
|
372
|
+
emitToolCallUpdate(session, toolUseId, fields, "progress");
|
|
373
|
+
}
|
|
340
374
|
}
|
|
341
375
|
export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
342
376
|
const base = session.toolCalls.get(toolUseId);
|
|
@@ -423,6 +457,12 @@ function buildTaskMetadata(patch) {
|
|
|
423
457
|
if (typeof patch.status === "string" && ["completed", "failed", "killed"].includes(patch.status)) {
|
|
424
458
|
taskMetadata.terminal_status = patch.status;
|
|
425
459
|
}
|
|
460
|
+
if (typeof patch.blocked === "boolean") {
|
|
461
|
+
taskMetadata.blocked = patch.blocked;
|
|
462
|
+
}
|
|
463
|
+
if (typeof patch.parent_agent_id === "string" && patch.parent_agent_id.length > 0) {
|
|
464
|
+
taskMetadata.parent_agent_id = patch.parent_agent_id;
|
|
465
|
+
}
|
|
426
466
|
return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
|
|
427
467
|
}
|
|
428
468
|
export function taskUpdatedFields(msg) {
|
|
@@ -261,7 +261,6 @@ export function toolTitle(name, input, context = {}) {
|
|
|
261
261
|
return label ? `${ARTIFACT_TOOL_NAME}: ${label}` : ARTIFACT_TOOL_NAME;
|
|
262
262
|
}
|
|
263
263
|
if (name === SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME) {
|
|
264
|
-
// TODO: The TUI accepts this SDK tool call but does not implement an onboarding role flow yet.
|
|
265
264
|
return SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME;
|
|
266
265
|
}
|
|
267
266
|
if (name === "EnterWorktree") {
|
|
@@ -483,9 +482,19 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
483
482
|
if (toolName === "Bash") {
|
|
484
483
|
for (const candidate of candidates) {
|
|
485
484
|
const hasAssistantAutoBackgrounded = typeof candidate.assistantAutoBackgrounded === "boolean";
|
|
486
|
-
|
|
485
|
+
const timedOutAfterMs = nonNegativeInteger(candidate.timedOutAfterMs);
|
|
486
|
+
const backgroundCwdHint = nonEmptyString(candidate.backgroundCwdHint);
|
|
487
|
+
if (hasAssistantAutoBackgrounded || timedOutAfterMs !== undefined || backgroundCwdHint) {
|
|
487
488
|
const bashMetadata = {};
|
|
488
|
-
|
|
489
|
+
if (hasAssistantAutoBackgrounded) {
|
|
490
|
+
bashMetadata.assistant_auto_backgrounded = candidate.assistantAutoBackgrounded;
|
|
491
|
+
}
|
|
492
|
+
if (timedOutAfterMs !== undefined) {
|
|
493
|
+
bashMetadata.timed_out_after_ms = timedOutAfterMs;
|
|
494
|
+
}
|
|
495
|
+
if (backgroundCwdHint) {
|
|
496
|
+
bashMetadata.background_cwd_hint = backgroundCwdHint;
|
|
497
|
+
}
|
|
489
498
|
metadata.bash = bashMetadata;
|
|
490
499
|
break;
|
|
491
500
|
}
|
|
@@ -494,9 +503,11 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
494
503
|
if (toolName === "Agent" || toolName === "Task") {
|
|
495
504
|
for (const candidate of candidates) {
|
|
496
505
|
const resolvedModel = nonEmptyString(candidate.resolvedModel);
|
|
497
|
-
|
|
506
|
+
const modelsUsed = orderedNonEmptyStrings(candidate.modelsUsed);
|
|
507
|
+
if (resolvedModel || modelsUsed) {
|
|
498
508
|
const agentMetadata = {
|
|
499
|
-
resolved_model: resolvedModel,
|
|
509
|
+
...(resolvedModel ? { resolved_model: resolvedModel } : {}),
|
|
510
|
+
...(modelsUsed ? { models_used: modelsUsed } : {}),
|
|
500
511
|
};
|
|
501
512
|
metadata.agent = agentMetadata;
|
|
502
513
|
break;
|
|
@@ -737,6 +748,10 @@ function shellBackgroundMessage(record) {
|
|
|
737
748
|
if (!backgroundTaskId) {
|
|
738
749
|
return "";
|
|
739
750
|
}
|
|
751
|
+
const timedOutAfterMs = nonNegativeInteger(record.timedOutAfterMs);
|
|
752
|
+
if (timedOutAfterMs !== undefined) {
|
|
753
|
+
return `Command was auto-backgrounded after ${timedOutAfterMs.toLocaleString("en-US")} ms with ID: ${backgroundTaskId}.`;
|
|
754
|
+
}
|
|
740
755
|
if (record.assistantAutoBackgrounded === true) {
|
|
741
756
|
return `Command was auto-backgrounded by assistant mode with ID: ${backgroundTaskId}.`;
|
|
742
757
|
}
|
|
@@ -813,6 +828,9 @@ function recordNumber(record, key) {
|
|
|
813
828
|
const value = record[key];
|
|
814
829
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
815
830
|
}
|
|
831
|
+
function recordNonNegativeInteger(record, key) {
|
|
832
|
+
return nonNegativeInteger(record[key]);
|
|
833
|
+
}
|
|
816
834
|
function recordString(record, key) {
|
|
817
835
|
const value = record[key];
|
|
818
836
|
return typeof value === "string" ? value : undefined;
|
|
@@ -858,8 +876,13 @@ function globResultText(record) {
|
|
|
858
876
|
function grepResultText(record) {
|
|
859
877
|
const filenames = searchFilenames(record);
|
|
860
878
|
const content = recordString(record, "content") ?? "";
|
|
861
|
-
const
|
|
862
|
-
const
|
|
879
|
+
const hasVisibleMatches = content.trim().length > 0 || filenames.length > 0;
|
|
880
|
+
const totalFiles = recordNonNegativeInteger(record, "totalFiles");
|
|
881
|
+
const legacyNumFiles = recordNonNegativeInteger(record, "numFiles");
|
|
882
|
+
const numFiles = totalFiles ??
|
|
883
|
+
(legacyNumFiles !== 0 || !hasVisibleMatches ? legacyNumFiles : undefined) ??
|
|
884
|
+
(filenames.length > 0 ? filenames.length : undefined);
|
|
885
|
+
const numLines = recordNonNegativeInteger(record, "totalLines") ?? recordNonNegativeInteger(record, "numLines");
|
|
863
886
|
const numMatches = recordNumber(record, "numMatches");
|
|
864
887
|
const appliedLimit = recordNumber(record, "appliedLimit");
|
|
865
888
|
const appliedOffset = recordNumber(record, "appliedOffset");
|
|
@@ -879,7 +902,9 @@ function grepResultText(record) {
|
|
|
879
902
|
lines.push("No matches found");
|
|
880
903
|
}
|
|
881
904
|
const summaryParts = [];
|
|
882
|
-
|
|
905
|
+
if (numFiles !== undefined) {
|
|
906
|
+
summaryParts.push(`${numFiles} ${pluralize(numFiles, "file")}`);
|
|
907
|
+
}
|
|
883
908
|
if (numMatches !== undefined) {
|
|
884
909
|
summaryParts.push(`${numMatches} ${pluralize(numMatches, "match", "matches")}`);
|
|
885
910
|
}
|
|
@@ -938,6 +963,26 @@ function pushBooleanField(lines, label, value) {
|
|
|
938
963
|
function nonEmptyString(value) {
|
|
939
964
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
940
965
|
}
|
|
966
|
+
function nonNegativeInteger(value) {
|
|
967
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
968
|
+
? Math.trunc(value)
|
|
969
|
+
: undefined;
|
|
970
|
+
}
|
|
971
|
+
function orderedNonEmptyStrings(value) {
|
|
972
|
+
if (!Array.isArray(value)) {
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
const seen = new Set();
|
|
976
|
+
const values = [];
|
|
977
|
+
for (const item of value) {
|
|
978
|
+
const normalized = nonEmptyString(item);
|
|
979
|
+
if (normalized && !seen.has(normalized)) {
|
|
980
|
+
seen.add(normalized);
|
|
981
|
+
values.push(normalized);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return values.length > 0 ? values : undefined;
|
|
985
|
+
}
|
|
941
986
|
const CRON_MONTH_NAMES = [
|
|
942
987
|
"January",
|
|
943
988
|
"February",
|
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -108,9 +108,23 @@ export async function generatePersistedSessionTitle(query, description) {
|
|
|
108
108
|
return title;
|
|
109
109
|
}
|
|
110
110
|
export async function applySessionEffort(query, effort) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
await query.applyFlagSettings({ effortLevel: effort });
|
|
112
|
+
}
|
|
113
|
+
export function buildPromptUserMessage(command, sessionId) {
|
|
114
|
+
const content = contentFromPrompt(command);
|
|
115
|
+
if (content.length === 0) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
type: "user",
|
|
120
|
+
session_id: sessionId,
|
|
121
|
+
parent_tool_use_id: null,
|
|
122
|
+
origin: { kind: "human" },
|
|
123
|
+
message: {
|
|
124
|
+
role: "user",
|
|
125
|
+
content,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
114
128
|
}
|
|
115
129
|
export async function applySessionAgent(query, agent) {
|
|
116
130
|
const settings = { agent };
|
|
@@ -130,7 +144,7 @@ export function emitAgentConfigOptionUpdate(sessionId, agent) {
|
|
|
130
144
|
value: agent,
|
|
131
145
|
});
|
|
132
146
|
}
|
|
133
|
-
const EXPECTED_AGENT_SDK_VERSION = "0.3.
|
|
147
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.3.214";
|
|
134
148
|
const require = createRequire(import.meta.url);
|
|
135
149
|
export function resolveInstalledAgentSdkVersion() {
|
|
136
150
|
try {
|
|
@@ -662,19 +676,10 @@ async function handleCommand(command, requestId) {
|
|
|
662
676
|
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
663
677
|
return;
|
|
664
678
|
}
|
|
665
|
-
const
|
|
666
|
-
if (
|
|
679
|
+
const message = buildPromptUserMessage(command, session.sessionId);
|
|
680
|
+
if (!message) {
|
|
667
681
|
return;
|
|
668
682
|
}
|
|
669
|
-
const message = {
|
|
670
|
-
type: "user",
|
|
671
|
-
session_id: session.sessionId,
|
|
672
|
-
parent_tool_use_id: null,
|
|
673
|
-
message: {
|
|
674
|
-
role: "user",
|
|
675
|
-
content,
|
|
676
|
-
},
|
|
677
|
-
};
|
|
678
683
|
session.input.enqueue(message);
|
|
679
684
|
return;
|
|
680
685
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-rust",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
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.214"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@srothgan/claude-code-rust-darwin-arm64": "0.
|
|
40
|
-
"@srothgan/claude-code-rust-darwin-x64": "0.
|
|
41
|
-
"@srothgan/claude-code-rust-linux-x64-gnu": "0.
|
|
42
|
-
"@srothgan/claude-code-rust-linux-arm64-gnu": "0.
|
|
43
|
-
"@srothgan/claude-code-rust-win32-x64-msvc": "0.
|
|
44
|
-
"@srothgan/claude-code-rust-win32-arm64-msvc": "0.
|
|
39
|
+
"@srothgan/claude-code-rust-darwin-arm64": "0.14.1",
|
|
40
|
+
"@srothgan/claude-code-rust-darwin-x64": "0.14.1",
|
|
41
|
+
"@srothgan/claude-code-rust-linux-x64-gnu": "0.14.1",
|
|
42
|
+
"@srothgan/claude-code-rust-linux-arm64-gnu": "0.14.1",
|
|
43
|
+
"@srothgan/claude-code-rust-win32-x64-msvc": "0.14.1",
|
|
44
|
+
"@srothgan/claude-code-rust-win32-arm64-msvc": "0.14.1"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24"
|