claude-code-rust 0.14.0 → 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/README.md +5 -9
- package/agent-sdk/dist/bridge/account_metadata.js +1 -0
- 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 +35 -9
- package/agent-sdk/dist/bridge/session_lifecycle.js +70 -11
- package/agent-sdk/dist/bridge/state_parsing.js +35 -0
- package/agent-sdk/dist/bridge/tool_calls.js +45 -10
- package/agent-sdk/dist/bridge/tooling.js +106 -8
- package/agent-sdk/dist/bridge.js +135 -629
- package/package.json +8 -8
|
@@ -11,7 +11,7 @@ import { isToolSearchToolName } from "./tooling.js";
|
|
|
11
11
|
import { requestExitPlanModeApproval, requestAskUserQuestionAnswers, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "./user_interaction.js";
|
|
12
12
|
import { mapAvailableAgents, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
|
|
13
13
|
import { mapSdkSlashCommands, updateAvailableCommands, } from "./available_commands.js";
|
|
14
|
-
import { emitAuthRequired,
|
|
14
|
+
import { emitAuthRequired, emitFastModeUpdate, setFastModeSnapshotIfChanged, } from "./error_classification.js";
|
|
15
15
|
import { mapAvailableModels, resolveCurrentModel, currentModelsEqual, } from "./model_metadata.js";
|
|
16
16
|
import { shouldEmitStartupAuthRequiredForAccount } from "./account_metadata.js";
|
|
17
17
|
export { mapAvailableModels, resolveCurrentModel } from "./model_metadata.js";
|
|
@@ -34,6 +34,7 @@ function permissionDisplayFromCanUseOptions(options) {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
export const sessions = new Map();
|
|
37
|
+
const pendingSessionCloseTasks = new Set();
|
|
37
38
|
const DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
|
|
38
39
|
const DEFAULT_PERMISSION_MODE = "default";
|
|
39
40
|
function isSdkElicitationContentValue(value) {
|
|
@@ -100,23 +101,28 @@ function settingsObjectFromLaunchSettings(launchSettings) {
|
|
|
100
101
|
return launchSettings.settings;
|
|
101
102
|
}
|
|
102
103
|
function normalizedSettingsFromLaunchSettings(launchSettings) {
|
|
103
|
-
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const settings = settingsObjectFromLaunchSettings(launchSettings) ?? {};
|
|
105
|
+
// SendFeedback queues a local draft that can only be reviewed, edited, and
|
|
106
|
+
// discarded through the native /feedback surface. Agent SDK command
|
|
107
|
+
// snapshots do not expose that command to this host, so enabling drafts
|
|
108
|
+
// would create content that claude-rs cannot safely let the user approve.
|
|
109
|
+
const hostSettings = {
|
|
110
|
+
...settings,
|
|
111
|
+
feedbackDrafts: "off",
|
|
112
|
+
};
|
|
107
113
|
const sandbox = settings.sandbox && typeof settings.sandbox === "object" && !Array.isArray(settings.sandbox)
|
|
108
114
|
? settings.sandbox
|
|
109
115
|
: undefined;
|
|
110
116
|
if (sandbox?.enabled === true && sandbox.failIfUnavailable === undefined) {
|
|
111
117
|
return {
|
|
112
|
-
...
|
|
118
|
+
...hostSettings,
|
|
113
119
|
sandbox: {
|
|
114
120
|
...sandbox,
|
|
115
121
|
failIfUnavailable: false,
|
|
116
122
|
},
|
|
117
123
|
};
|
|
118
124
|
}
|
|
119
|
-
return
|
|
125
|
+
return hostSettings;
|
|
120
126
|
}
|
|
121
127
|
export function sessionById(sessionId) {
|
|
122
128
|
return sessions.get(sessionId) ?? null;
|
|
@@ -129,7 +135,44 @@ export function updateSessionId(session, newSessionId) {
|
|
|
129
135
|
session.sessionId = newSessionId;
|
|
130
136
|
sessions.set(newSessionId, session);
|
|
131
137
|
}
|
|
138
|
+
export function beginSessionClose(session) {
|
|
139
|
+
session.closing = true;
|
|
140
|
+
for (const monitor of session.mcpAuthMonitors.values()) {
|
|
141
|
+
monitor.controller.abort();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function detachSessionForClose(session) {
|
|
145
|
+
beginSessionClose(session);
|
|
146
|
+
if (sessions.get(session.sessionId) === session) {
|
|
147
|
+
sessions.delete(session.sessionId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function trackSessionCloseTask(task) {
|
|
151
|
+
const ownedTask = task.catch((error) => {
|
|
152
|
+
bridgeLogger.error({
|
|
153
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
154
|
+
eventName: "session_close_task_failed",
|
|
155
|
+
message: "background session cleanup failed",
|
|
156
|
+
outcome: "failure",
|
|
157
|
+
fields: {
|
|
158
|
+
error_message: error instanceof Error ? error.message : String(error),
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
pendingSessionCloseTasks.add(ownedTask);
|
|
163
|
+
void ownedTask.then(() => {
|
|
164
|
+
pendingSessionCloseTasks.delete(ownedTask);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function waitForPendingSessionCloseTasks() {
|
|
168
|
+
while (pendingSessionCloseTasks.size > 0) {
|
|
169
|
+
await Promise.all(Array.from(pendingSessionCloseTasks));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
132
172
|
export async function closeSession(session) {
|
|
173
|
+
beginSessionClose(session);
|
|
174
|
+
const mcpAuthMonitors = Array.from(session.mcpAuthMonitors.values());
|
|
175
|
+
session.mcpAuthMonitors.clear();
|
|
133
176
|
session.input.close();
|
|
134
177
|
session.query.close();
|
|
135
178
|
for (const pending of session.pendingPermissions.values()) {
|
|
@@ -149,6 +192,11 @@ export async function closeSession(session) {
|
|
|
149
192
|
pending.resolve({ action: "cancel" });
|
|
150
193
|
}
|
|
151
194
|
session.pendingElicitations.clear();
|
|
195
|
+
await Promise.all([
|
|
196
|
+
session.initializationTask,
|
|
197
|
+
session.queryConsumerTask,
|
|
198
|
+
...mcpAuthMonitors.map((monitor) => monitor.task),
|
|
199
|
+
].filter((task) => task !== undefined));
|
|
152
200
|
}
|
|
153
201
|
export async function closeSessionWithLogging(session, options = {}) {
|
|
154
202
|
await closeSession(session);
|
|
@@ -186,6 +234,7 @@ export async function closeAllSessions(options = {}) {
|
|
|
186
234
|
reason: options.reason ?? "bulk_close",
|
|
187
235
|
requestId: options.requestId,
|
|
188
236
|
})));
|
|
237
|
+
await waitForPendingSessionCloseTasks();
|
|
189
238
|
bridgeLogger.info({
|
|
190
239
|
target: LOG_TARGETS.APP_SESSION,
|
|
191
240
|
eventName: "all_sessions_closed",
|
|
@@ -330,6 +379,7 @@ export async function createSession(params) {
|
|
|
330
379
|
query: queryHandle,
|
|
331
380
|
input,
|
|
332
381
|
connected: false,
|
|
382
|
+
closing: false,
|
|
333
383
|
connectEvent: params.connectEvent,
|
|
334
384
|
connectRequestId: params.requestId,
|
|
335
385
|
toolCalls: new Map(),
|
|
@@ -342,7 +392,9 @@ export async function createSession(params) {
|
|
|
342
392
|
pendingUserDialogs: new Map(),
|
|
343
393
|
pendingElicitations: new Map(),
|
|
344
394
|
informationalDedupKeys: new Set(),
|
|
395
|
+
knownConnectedMcpServers: new Set(),
|
|
345
396
|
mcpStatusRevalidatedAt: new Map(),
|
|
397
|
+
mcpAuthMonitors: new Map(),
|
|
346
398
|
hiddenToolUseIds: new Set(),
|
|
347
399
|
authHintSent: false,
|
|
348
400
|
...(params.resumeUpdates && params.resumeUpdates.length > 0
|
|
@@ -391,7 +443,7 @@ export async function createSession(params) {
|
|
|
391
443
|
// In stream-input mode the SDK may defer init until input arrives.
|
|
392
444
|
// Trigger initialization explicitly so the Rust UI can receive `connected`
|
|
393
445
|
// before the first user prompt.
|
|
394
|
-
|
|
446
|
+
session.initializationTask = session.query
|
|
395
447
|
.initializationResult()
|
|
396
448
|
.then(async (result) => {
|
|
397
449
|
bridgeLogger.info({
|
|
@@ -411,6 +463,7 @@ export async function createSession(params) {
|
|
|
411
463
|
const currentModelChanged = refreshCurrentModel(session);
|
|
412
464
|
const { buildModeState, refreshSupportedModesForSession } = await import("./commands.js");
|
|
413
465
|
refreshSupportedModesForSession(session);
|
|
466
|
+
const fastModeChanged = setFastModeSnapshotIfChanged(session, result.fast_mode_state, result.fast_mode_disabled_reason);
|
|
414
467
|
if (!session.connected) {
|
|
415
468
|
emitConnectEvent(session);
|
|
416
469
|
}
|
|
@@ -424,13 +477,15 @@ export async function createSession(params) {
|
|
|
424
477
|
mode: buildModeState(session, session.mode),
|
|
425
478
|
});
|
|
426
479
|
}
|
|
480
|
+
if (fastModeChanged) {
|
|
481
|
+
emitFastModeUpdate(session);
|
|
482
|
+
}
|
|
427
483
|
}
|
|
428
484
|
// Proactively detect missing auth from account info so the UI can
|
|
429
485
|
// show the login hint immediately, without waiting for the first prompt.
|
|
430
486
|
if (shouldEmitStartupAuthRequiredForAccount(result.account)) {
|
|
431
487
|
emitAuthRequired(session);
|
|
432
488
|
}
|
|
433
|
-
emitFastModeUpdateIfChanged(session, result.fast_mode_state);
|
|
434
489
|
updateAvailableCommands(session, "session_result_commands", mapSdkSlashCommands(result.commands));
|
|
435
490
|
emitAvailableAgentsIfChanged(session, mapAvailableAgents(result.agents));
|
|
436
491
|
refreshAvailableAgents(session);
|
|
@@ -452,7 +507,7 @@ export async function createSession(params) {
|
|
|
452
507
|
failConnection(`agent initialization failed: ${message}`, session.connectRequestId);
|
|
453
508
|
session.connectRequestId = undefined;
|
|
454
509
|
});
|
|
455
|
-
|
|
510
|
+
session.queryConsumerTask = (async () => {
|
|
456
511
|
try {
|
|
457
512
|
for await (const message of session.query) {
|
|
458
513
|
// Lazy import to break circular dependency at module-evaluation time.
|
|
@@ -612,9 +667,13 @@ export function buildQueryOptions(params) {
|
|
|
612
667
|
includePartialMessages: true,
|
|
613
668
|
promptSuggestions: true,
|
|
614
669
|
enableFileCheckpointing: true,
|
|
670
|
+
// ProposeSkills reports only a proposal count and expects a native review
|
|
671
|
+
// surface. Keep it out of this host until claude-rs can display the actual
|
|
672
|
+
// proposal input and accept/reject it without losing content.
|
|
673
|
+
disallowedTools: ["ProposeSkills"],
|
|
615
674
|
executable: "bun",
|
|
616
675
|
...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
|
|
617
|
-
|
|
676
|
+
settings,
|
|
618
677
|
...modelOption,
|
|
619
678
|
...permissionModeOptions,
|
|
620
679
|
toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
|
|
@@ -15,12 +15,47 @@ 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;
|
|
21
49
|
}
|
|
22
50
|
return null;
|
|
23
51
|
}
|
|
52
|
+
export function parseFastModeDisabledReason(value) {
|
|
53
|
+
if (typeof value !== "string") {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const reason = value.trim();
|
|
57
|
+
return reason.length > 0 ? reason : undefined;
|
|
58
|
+
}
|
|
24
59
|
export function parseRateLimitStatus(value) {
|
|
25
60
|
if (value === "allowed" || value === "allowed_warning" || value === "rejected") {
|
|
26
61
|
return value;
|
|
@@ -3,7 +3,7 @@ import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
|
3
3
|
import { asRecordOrNull } from "./shared.js";
|
|
4
4
|
import { applyTaskToolResult } from "./tasks.js";
|
|
5
5
|
import { activeTaskIdForToolUse, linkTaskToolUse, unlinkTaskToolUse } from "./task_links.js";
|
|
6
|
-
import { backgroundToolLaunchTaskIdFromResult, buildToolResultFields, createToolCall, } from "./tooling.js";
|
|
6
|
+
import { applyToolNonExecutionMetadata, backgroundToolLaunchTaskIdFromResult, buildToolResultFields, createToolCall, } from "./tooling.js";
|
|
7
7
|
const TOOL_SUMMARY_TOOL_NAMES = new Set(["Agent", "Task", "WebSearch", "WebFetch", "ExitPlanMode"]);
|
|
8
8
|
const TASK_LIFECYCLE_TOOL_NAMES = new Set(["Agent", "Task", "Monitor", "Workflow"]);
|
|
9
9
|
function jsonSize(value) {
|
|
@@ -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",
|
|
@@ -294,18 +308,19 @@ export function ensureToolCallVisible(session, toolUseId, toolName, input, paren
|
|
|
294
308
|
emitInitialToolCall(session, toolCall);
|
|
295
309
|
return toolCall;
|
|
296
310
|
}
|
|
297
|
-
export function emitToolResultUpdate(session, toolUseId, isError, rawContent, rawResult = rawContent, sourceMessageUuid) {
|
|
311
|
+
export function emitToolResultUpdate(session, toolUseId, isError, rawContent, rawResult = rawContent, sourceMessageUuid, nonExecutionMetadata) {
|
|
298
312
|
const base = session.toolCalls.get(toolUseId);
|
|
299
313
|
const baseToolName = toolNameFromMeta(base?.meta) ?? "";
|
|
300
314
|
const fields = buildToolResultFields(isError, rawContent, base, rawResult, taskTitleContext(session, baseToolName, asRecordOrNull(base?.raw_input) ?? {}));
|
|
301
|
-
|
|
315
|
+
applyToolNonExecutionMetadata(fields, nonExecutionMetadata);
|
|
316
|
+
if (!isError && !nonExecutionMetadata) {
|
|
302
317
|
const taskId = backgroundToolLaunchTaskIdFromResult(baseToolName, rawResult, rawContent);
|
|
303
318
|
if (taskId) {
|
|
304
319
|
linkTaskToolUse(session, taskId, toolUseId);
|
|
305
320
|
}
|
|
306
321
|
}
|
|
307
322
|
emitToolCallUpdate(session, toolUseId, fields, "result", sourceMessageUuid);
|
|
308
|
-
applyTaskToolResult(session, toolUseId, isError, rawContent, rawResult);
|
|
323
|
+
applyTaskToolResult(session, toolUseId, isError || nonExecutionMetadata !== undefined, rawContent, rawResult);
|
|
309
324
|
if (baseToolName === "Agent" || baseToolName === "Task") {
|
|
310
325
|
const taskId = activeTaskIdForToolUse(session, toolUseId);
|
|
311
326
|
if (taskId) {
|
|
@@ -324,19 +339,39 @@ export function finalizeOpenToolCalls(session, status) {
|
|
|
324
339
|
emitToolCallUpdate(session, toolUseId, { status }, "finalize");
|
|
325
340
|
}
|
|
326
341
|
}
|
|
327
|
-
export function emitToolProgressUpdate(session, toolUseId, toolName) {
|
|
328
|
-
|
|
342
|
+
export function emitToolProgressUpdate(session, toolUseId, toolName, progress = {}) {
|
|
343
|
+
let existing = session.toolCalls.get(toolUseId);
|
|
329
344
|
if (!existing) {
|
|
330
345
|
emitToolCall(session, toolUseId, toolName, {});
|
|
331
|
-
|
|
346
|
+
existing = session.toolCalls.get(toolUseId);
|
|
332
347
|
}
|
|
333
|
-
if (existing
|
|
348
|
+
if (!existing ||
|
|
334
349
|
existing.status === "completed" ||
|
|
335
350
|
existing.status === "failed" ||
|
|
336
351
|
existing.status === "killed") {
|
|
337
352
|
return;
|
|
338
353
|
}
|
|
339
|
-
|
|
354
|
+
const taskMetadata = {};
|
|
355
|
+
if (progress.subagentRetry?.state === "waiting") {
|
|
356
|
+
taskMetadata.subagent_retry = progress.subagentRetry;
|
|
357
|
+
}
|
|
358
|
+
else if (progress.subagentRetry?.state === "clear" &&
|
|
359
|
+
existing.task_metadata?.subagent_retry?.state === "waiting") {
|
|
360
|
+
taskMetadata.subagent_retry = progress.subagentRetry;
|
|
361
|
+
}
|
|
362
|
+
if (progress.subagentType && progress.subagentType !== existing.task_metadata?.subagent_type) {
|
|
363
|
+
taskMetadata.subagent_type = progress.subagentType;
|
|
364
|
+
}
|
|
365
|
+
const fields = {};
|
|
366
|
+
if (existing.status !== "in_progress") {
|
|
367
|
+
fields.status = "in_progress";
|
|
368
|
+
}
|
|
369
|
+
if (Object.keys(taskMetadata).length > 0) {
|
|
370
|
+
fields.task_metadata = taskMetadata;
|
|
371
|
+
}
|
|
372
|
+
if (Object.keys(fields).length > 0) {
|
|
373
|
+
emitToolCallUpdate(session, toolUseId, fields, "progress");
|
|
374
|
+
}
|
|
340
375
|
}
|
|
341
376
|
export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
342
377
|
const base = session.toolCalls.get(toolUseId);
|
|
@@ -482,9 +482,19 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
482
482
|
if (toolName === "Bash") {
|
|
483
483
|
for (const candidate of candidates) {
|
|
484
484
|
const hasAssistantAutoBackgrounded = typeof candidate.assistantAutoBackgrounded === "boolean";
|
|
485
|
-
|
|
485
|
+
const timedOutAfterMs = nonNegativeInteger(candidate.timedOutAfterMs);
|
|
486
|
+
const backgroundCwdHint = nonEmptyString(candidate.backgroundCwdHint);
|
|
487
|
+
if (hasAssistantAutoBackgrounded || timedOutAfterMs !== undefined || backgroundCwdHint) {
|
|
486
488
|
const bashMetadata = {};
|
|
487
|
-
|
|
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
|
+
}
|
|
488
498
|
metadata.bash = bashMetadata;
|
|
489
499
|
break;
|
|
490
500
|
}
|
|
@@ -493,9 +503,11 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
493
503
|
if (toolName === "Agent" || toolName === "Task") {
|
|
494
504
|
for (const candidate of candidates) {
|
|
495
505
|
const resolvedModel = nonEmptyString(candidate.resolvedModel);
|
|
496
|
-
|
|
506
|
+
const modelsUsed = orderedNonEmptyStrings(candidate.modelsUsed);
|
|
507
|
+
if (resolvedModel || modelsUsed) {
|
|
497
508
|
const agentMetadata = {
|
|
498
|
-
resolved_model: resolvedModel,
|
|
509
|
+
...(resolvedModel ? { resolved_model: resolvedModel } : {}),
|
|
510
|
+
...(modelsUsed ? { models_used: modelsUsed } : {}),
|
|
499
511
|
};
|
|
500
512
|
metadata.agent = agentMetadata;
|
|
501
513
|
break;
|
|
@@ -516,7 +528,59 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
516
528
|
}
|
|
517
529
|
}
|
|
518
530
|
}
|
|
519
|
-
|
|
531
|
+
if (toolName === "Skill") {
|
|
532
|
+
for (const candidate of candidates) {
|
|
533
|
+
if (candidate.background === true) {
|
|
534
|
+
metadata.skill = { background: true };
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return metadata.bash || metadata.agent || metadata.web_fetch || metadata.skill
|
|
540
|
+
? metadata
|
|
541
|
+
: undefined;
|
|
542
|
+
}
|
|
543
|
+
export function parseToolNonExecutionMetadata(value) {
|
|
544
|
+
const byToolUseId = new Map();
|
|
545
|
+
if (!Array.isArray(value)) {
|
|
546
|
+
return byToolUseId;
|
|
547
|
+
}
|
|
548
|
+
for (const entry of value) {
|
|
549
|
+
const record = asRecordOrNull(entry);
|
|
550
|
+
const id = nonEmptyString(record?.id);
|
|
551
|
+
const kind = nonEmptyString(record?.non_execution_kind);
|
|
552
|
+
if (!id || !kind || byToolUseId.has(id)) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const userFeedback = nonEmptyString(record?.user_feedback);
|
|
556
|
+
byToolUseId.set(id, {
|
|
557
|
+
kind,
|
|
558
|
+
...(userFeedback ? { user_feedback: userFeedback } : {}),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
return byToolUseId;
|
|
562
|
+
}
|
|
563
|
+
export function applyToolNonExecutionMetadata(fields, metadata) {
|
|
564
|
+
if (!metadata) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
fields.output_metadata = {
|
|
568
|
+
...(fields.output_metadata ?? {}),
|
|
569
|
+
non_execution: metadata,
|
|
570
|
+
};
|
|
571
|
+
switch (metadata.kind) {
|
|
572
|
+
case "user-rejected":
|
|
573
|
+
case "permission-rule":
|
|
574
|
+
case "automode-unavailable":
|
|
575
|
+
case "automode-parsing-error":
|
|
576
|
+
case "automode-blocked":
|
|
577
|
+
fields.status = "failed";
|
|
578
|
+
break;
|
|
579
|
+
case "cancelled":
|
|
580
|
+
case "interrupted":
|
|
581
|
+
fields.status = "killed";
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
520
584
|
}
|
|
521
585
|
export function extractText(value) {
|
|
522
586
|
if (typeof value === "string") {
|
|
@@ -736,6 +800,10 @@ function shellBackgroundMessage(record) {
|
|
|
736
800
|
if (!backgroundTaskId) {
|
|
737
801
|
return "";
|
|
738
802
|
}
|
|
803
|
+
const timedOutAfterMs = nonNegativeInteger(record.timedOutAfterMs);
|
|
804
|
+
if (timedOutAfterMs !== undefined) {
|
|
805
|
+
return `Command was auto-backgrounded after ${timedOutAfterMs.toLocaleString("en-US")} ms with ID: ${backgroundTaskId}.`;
|
|
806
|
+
}
|
|
739
807
|
if (record.assistantAutoBackgrounded === true) {
|
|
740
808
|
return `Command was auto-backgrounded by assistant mode with ID: ${backgroundTaskId}.`;
|
|
741
809
|
}
|
|
@@ -812,6 +880,9 @@ function recordNumber(record, key) {
|
|
|
812
880
|
const value = record[key];
|
|
813
881
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
814
882
|
}
|
|
883
|
+
function recordNonNegativeInteger(record, key) {
|
|
884
|
+
return nonNegativeInteger(record[key]);
|
|
885
|
+
}
|
|
815
886
|
function recordString(record, key) {
|
|
816
887
|
const value = record[key];
|
|
817
888
|
return typeof value === "string" ? value : undefined;
|
|
@@ -857,8 +928,13 @@ function globResultText(record) {
|
|
|
857
928
|
function grepResultText(record) {
|
|
858
929
|
const filenames = searchFilenames(record);
|
|
859
930
|
const content = recordString(record, "content") ?? "";
|
|
860
|
-
const
|
|
861
|
-
const
|
|
931
|
+
const hasVisibleMatches = content.trim().length > 0 || filenames.length > 0;
|
|
932
|
+
const totalFiles = recordNonNegativeInteger(record, "totalFiles");
|
|
933
|
+
const legacyNumFiles = recordNonNegativeInteger(record, "numFiles");
|
|
934
|
+
const numFiles = totalFiles ??
|
|
935
|
+
(legacyNumFiles !== 0 || !hasVisibleMatches ? legacyNumFiles : undefined) ??
|
|
936
|
+
(filenames.length > 0 ? filenames.length : undefined);
|
|
937
|
+
const numLines = recordNonNegativeInteger(record, "totalLines") ?? recordNonNegativeInteger(record, "numLines");
|
|
862
938
|
const numMatches = recordNumber(record, "numMatches");
|
|
863
939
|
const appliedLimit = recordNumber(record, "appliedLimit");
|
|
864
940
|
const appliedOffset = recordNumber(record, "appliedOffset");
|
|
@@ -878,7 +954,9 @@ function grepResultText(record) {
|
|
|
878
954
|
lines.push("No matches found");
|
|
879
955
|
}
|
|
880
956
|
const summaryParts = [];
|
|
881
|
-
|
|
957
|
+
if (numFiles !== undefined) {
|
|
958
|
+
summaryParts.push(`${numFiles} ${pluralize(numFiles, "file")}`);
|
|
959
|
+
}
|
|
882
960
|
if (numMatches !== undefined) {
|
|
883
961
|
summaryParts.push(`${numMatches} ${pluralize(numMatches, "match", "matches")}`);
|
|
884
962
|
}
|
|
@@ -937,6 +1015,26 @@ function pushBooleanField(lines, label, value) {
|
|
|
937
1015
|
function nonEmptyString(value) {
|
|
938
1016
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
939
1017
|
}
|
|
1018
|
+
function nonNegativeInteger(value) {
|
|
1019
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
1020
|
+
? Math.trunc(value)
|
|
1021
|
+
: undefined;
|
|
1022
|
+
}
|
|
1023
|
+
function orderedNonEmptyStrings(value) {
|
|
1024
|
+
if (!Array.isArray(value)) {
|
|
1025
|
+
return undefined;
|
|
1026
|
+
}
|
|
1027
|
+
const seen = new Set();
|
|
1028
|
+
const values = [];
|
|
1029
|
+
for (const item of value) {
|
|
1030
|
+
const normalized = nonEmptyString(item);
|
|
1031
|
+
if (normalized && !seen.has(normalized)) {
|
|
1032
|
+
seen.add(normalized);
|
|
1033
|
+
values.push(normalized);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return values.length > 0 ? values : undefined;
|
|
1037
|
+
}
|
|
940
1038
|
const CRON_MONTH_NAMES = [
|
|
941
1039
|
"January",
|
|
942
1040
|
"February",
|