claude-code-rust 0.12.0 → 0.12.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 +10 -6
- package/agent-sdk/README.md +1 -1
- package/agent-sdk/dist/bridge/account_metadata.js +44 -0
- package/agent-sdk/dist/bridge/available_commands.js +129 -0
- package/agent-sdk/dist/bridge/commands.js +58 -36
- package/agent-sdk/dist/bridge/error_classification.js +20 -7
- package/agent-sdk/dist/bridge/events.js +18 -0
- package/agent-sdk/dist/bridge/history.js +183 -11
- package/agent-sdk/dist/bridge/logger.js +3 -0
- package/agent-sdk/dist/bridge/mcp.js +49 -79
- package/agent-sdk/dist/bridge/mcp_metadata.js +369 -0
- package/agent-sdk/dist/bridge/message_handlers.js +401 -57
- package/agent-sdk/dist/bridge/model_metadata.js +228 -0
- package/agent-sdk/dist/bridge/session_lifecycle.js +197 -326
- package/agent-sdk/dist/bridge/state_parsing.js +7 -1
- package/agent-sdk/dist/bridge/task_links.js +34 -0
- package/agent-sdk/dist/bridge/tasks.js +862 -0
- package/agent-sdk/dist/bridge/tool_calls.js +88 -31
- package/agent-sdk/dist/bridge/tooling.js +1262 -32
- package/agent-sdk/dist/bridge.js +95 -43
- package/agent-sdk/dist/bridge.test.js +3680 -269
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { asRecordOrNull } from "./shared.js";
|
|
2
2
|
import { CACHE_SPLIT_POLICY, previewKilobyteLabel } from "./cache_policy.js";
|
|
3
|
+
import { isTaskToolName, taskToolResultText, taskToolTitle, taskUpdateSucceeded, } from "./tasks.js";
|
|
3
4
|
export const TOOL_RESULT_TYPES = new Set([
|
|
4
5
|
"tool_result",
|
|
5
6
|
"tool_search_tool_result",
|
|
@@ -10,6 +11,22 @@ export const TOOL_RESULT_TYPES = new Set([
|
|
|
10
11
|
"text_editor_code_execution_tool_result",
|
|
11
12
|
"mcp_tool_result",
|
|
12
13
|
]);
|
|
14
|
+
const CRON_TOOL_NAMES = new Set(["CronCreate", "CronDelete", "CronList"]);
|
|
15
|
+
const CRON_LIST_DIVIDER = "__cron_list_job_divider__";
|
|
16
|
+
const SCHEDULE_WAKEUP_TOOL_NAME = "ScheduleWakeup";
|
|
17
|
+
const PUSH_NOTIFICATION_TOOL_NAME = "PushNotification";
|
|
18
|
+
const REMOTE_TRIGGER_TOOL_NAME = "RemoteTrigger";
|
|
19
|
+
const ENTER_PLAN_MODE_TOOL_NAME = "EnterPlanMode";
|
|
20
|
+
const REPL_TOOL_NAME = "REPL";
|
|
21
|
+
const MONITOR_TOOL_NAME = "Monitor";
|
|
22
|
+
const WORKFLOW_TOOL_NAME = "Workflow";
|
|
23
|
+
const PROJECTS_TOOL_NAME = "Projects";
|
|
24
|
+
const ARTIFACT_TOOL_NAME = "Artifact";
|
|
25
|
+
const SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME = "ShowOnboardingRolePicker";
|
|
26
|
+
const SEARCH_OUTPUT_MODES = new Set(["content", "files_with_matches", "count"]);
|
|
27
|
+
function isCronToolName(name) {
|
|
28
|
+
return CRON_TOOL_NAMES.has(name);
|
|
29
|
+
}
|
|
13
30
|
export function isToolSearchToolName(name) {
|
|
14
31
|
const normalized = name.replace(/[\s_-]+/g, "").toLowerCase();
|
|
15
32
|
return normalized === "toolsearch" || normalized === "toolsearchtool";
|
|
@@ -20,6 +37,103 @@ export function isToolSearchToolResultType(blockType) {
|
|
|
20
37
|
export function isToolUseBlockType(blockType) {
|
|
21
38
|
return blockType === "tool_use" || blockType === "server_tool_use" || blockType === "mcp_tool_use";
|
|
22
39
|
}
|
|
40
|
+
function inputString(input, key) {
|
|
41
|
+
return typeof input[key] === "string" ? input[key].trim() : "";
|
|
42
|
+
}
|
|
43
|
+
function inputNumber(input, key) {
|
|
44
|
+
const value = input[key];
|
|
45
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
46
|
+
}
|
|
47
|
+
function inputBoolean(input, key) {
|
|
48
|
+
return typeof input[key] === "boolean" ? input[key] : undefined;
|
|
49
|
+
}
|
|
50
|
+
function isAgentLikeToolName(name) {
|
|
51
|
+
return name === "Agent" || name === "Task";
|
|
52
|
+
}
|
|
53
|
+
function agentInputTitle(name, input) {
|
|
54
|
+
if (!isAgentLikeToolName(name)) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const agentName = nonEmptyString(input.name);
|
|
58
|
+
if (agentName) {
|
|
59
|
+
return `${name}: ${agentName}`;
|
|
60
|
+
}
|
|
61
|
+
const subagentType = nonEmptyString(input.subagent_type);
|
|
62
|
+
return subagentType ? `${name}: ${subagentType}` : undefined;
|
|
63
|
+
}
|
|
64
|
+
function searchModeLabel(value) {
|
|
65
|
+
if (typeof value !== "string" || !SEARCH_OUTPUT_MODES.has(value)) {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
switch (value) {
|
|
69
|
+
case "files_with_matches":
|
|
70
|
+
return "files";
|
|
71
|
+
case "content":
|
|
72
|
+
return "content";
|
|
73
|
+
case "count":
|
|
74
|
+
return "count";
|
|
75
|
+
default:
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function grepContextValue(input) {
|
|
80
|
+
return (inputNumber(input, "context") ??
|
|
81
|
+
inputNumber(input, "-C") ??
|
|
82
|
+
inputNumber(input, "-A") ??
|
|
83
|
+
inputNumber(input, "-B"));
|
|
84
|
+
}
|
|
85
|
+
function formatGlobTitle(input) {
|
|
86
|
+
const pattern = inputString(input, "pattern");
|
|
87
|
+
const path = inputString(input, "path");
|
|
88
|
+
if (pattern && path) {
|
|
89
|
+
return `Glob ${pattern} in ${path}`;
|
|
90
|
+
}
|
|
91
|
+
if (pattern) {
|
|
92
|
+
return `Glob ${pattern}`;
|
|
93
|
+
}
|
|
94
|
+
if (path) {
|
|
95
|
+
return `Glob ${path}`;
|
|
96
|
+
}
|
|
97
|
+
return "Glob";
|
|
98
|
+
}
|
|
99
|
+
function formatGrepTitle(input) {
|
|
100
|
+
const pattern = inputString(input, "pattern");
|
|
101
|
+
const path = inputString(input, "path");
|
|
102
|
+
const glob = inputString(input, "glob");
|
|
103
|
+
const fileType = inputString(input, "type");
|
|
104
|
+
const outputMode = searchModeLabel(input.output_mode);
|
|
105
|
+
const headLimit = inputNumber(input, "head_limit");
|
|
106
|
+
const offset = inputNumber(input, "offset");
|
|
107
|
+
const context = grepContextValue(input);
|
|
108
|
+
const flags = [];
|
|
109
|
+
if (glob) {
|
|
110
|
+
flags.push(`glob ${glob}`);
|
|
111
|
+
}
|
|
112
|
+
if (fileType) {
|
|
113
|
+
flags.push(`type ${fileType}`);
|
|
114
|
+
}
|
|
115
|
+
if (outputMode) {
|
|
116
|
+
flags.push(outputMode);
|
|
117
|
+
}
|
|
118
|
+
if (inputBoolean(input, "-i") === true) {
|
|
119
|
+
flags.push("case-insensitive");
|
|
120
|
+
}
|
|
121
|
+
if (context !== undefined) {
|
|
122
|
+
flags.push(`context ${context}`);
|
|
123
|
+
}
|
|
124
|
+
if (headLimit !== undefined) {
|
|
125
|
+
flags.push(`limit ${headLimit}`);
|
|
126
|
+
}
|
|
127
|
+
if (offset !== undefined && offset > 0) {
|
|
128
|
+
flags.push(`offset ${offset}`);
|
|
129
|
+
}
|
|
130
|
+
if (inputBoolean(input, "multiline") === true) {
|
|
131
|
+
flags.push("multiline");
|
|
132
|
+
}
|
|
133
|
+
const base = pattern ? `Grep ${pattern}` : "Grep";
|
|
134
|
+
const scoped = path ? `${base} in ${path}` : base;
|
|
135
|
+
return flags.length > 0 ? `${scoped} (${flags.join(", ")})` : scoped;
|
|
136
|
+
}
|
|
23
137
|
export function normalizeToolKind(name) {
|
|
24
138
|
switch (name) {
|
|
25
139
|
case "Bash":
|
|
@@ -39,34 +153,51 @@ export function normalizeToolKind(name) {
|
|
|
39
153
|
return "search";
|
|
40
154
|
case "WebFetch":
|
|
41
155
|
return "fetch";
|
|
42
|
-
case "
|
|
156
|
+
case "TaskCreate":
|
|
157
|
+
case "TaskUpdate":
|
|
158
|
+
case "TaskGet":
|
|
159
|
+
case "TaskList":
|
|
160
|
+
case "TaskOutput":
|
|
161
|
+
case "TaskStop":
|
|
162
|
+
case "CronCreate":
|
|
163
|
+
case "CronDelete":
|
|
164
|
+
case "CronList":
|
|
165
|
+
case "ScheduleWakeup":
|
|
166
|
+
case "PushNotification":
|
|
167
|
+
case "RemoteTrigger":
|
|
168
|
+
case "EnterWorktree":
|
|
169
|
+
case "ExitWorktree":
|
|
170
|
+
case "REPL":
|
|
171
|
+
case "Monitor":
|
|
172
|
+
case "Workflow":
|
|
173
|
+
case "Projects":
|
|
174
|
+
case "Artifact":
|
|
175
|
+
case "ShowOnboardingRolePicker":
|
|
43
176
|
return "other";
|
|
44
177
|
case "Task":
|
|
45
178
|
case "Agent":
|
|
46
179
|
return "think";
|
|
180
|
+
case "EnterPlanMode":
|
|
47
181
|
case "ExitPlanMode":
|
|
48
182
|
return "switch_mode";
|
|
49
183
|
default:
|
|
50
184
|
return "think";
|
|
51
185
|
}
|
|
52
186
|
}
|
|
53
|
-
export function toolTitle(name, input) {
|
|
187
|
+
export function toolTitle(name, input, context = {}) {
|
|
188
|
+
const agentTitle = agentInputTitle(name, input);
|
|
189
|
+
if (agentTitle) {
|
|
190
|
+
return agentTitle;
|
|
191
|
+
}
|
|
54
192
|
if (name === "Bash") {
|
|
55
193
|
const command = typeof input.command === "string" ? input.command : "";
|
|
56
194
|
return command || "Terminal";
|
|
57
195
|
}
|
|
58
196
|
if (name === "Glob") {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
if (pattern) {
|
|
65
|
-
return `Glob ${pattern}`;
|
|
66
|
-
}
|
|
67
|
-
if (path) {
|
|
68
|
-
return `Glob ${path}`;
|
|
69
|
-
}
|
|
197
|
+
return formatGlobTitle(input);
|
|
198
|
+
}
|
|
199
|
+
if (name === "Grep") {
|
|
200
|
+
return formatGrepTitle(input);
|
|
70
201
|
}
|
|
71
202
|
if (name === "WebFetch") {
|
|
72
203
|
const url = typeof input.url === "string" ? input.url : "";
|
|
@@ -80,6 +211,56 @@ export function toolTitle(name, input) {
|
|
|
80
211
|
return `WebSearch ${query}`;
|
|
81
212
|
}
|
|
82
213
|
}
|
|
214
|
+
const taskTitle = taskToolTitle(name, input, context);
|
|
215
|
+
if (taskTitle) {
|
|
216
|
+
return taskTitle;
|
|
217
|
+
}
|
|
218
|
+
if (isCronToolName(name)) {
|
|
219
|
+
return name;
|
|
220
|
+
}
|
|
221
|
+
if (name === SCHEDULE_WAKEUP_TOOL_NAME) {
|
|
222
|
+
return name;
|
|
223
|
+
}
|
|
224
|
+
if (name === PUSH_NOTIFICATION_TOOL_NAME) {
|
|
225
|
+
return name;
|
|
226
|
+
}
|
|
227
|
+
if (name === REMOTE_TRIGGER_TOOL_NAME) {
|
|
228
|
+
const action = typeof input.action === "string" ? input.action.trim() : "";
|
|
229
|
+
return action ? `${REMOTE_TRIGGER_TOOL_NAME}: ${action}` : REMOTE_TRIGGER_TOOL_NAME;
|
|
230
|
+
}
|
|
231
|
+
if (name === ENTER_PLAN_MODE_TOOL_NAME) {
|
|
232
|
+
return name;
|
|
233
|
+
}
|
|
234
|
+
if (name === REPL_TOOL_NAME) {
|
|
235
|
+
const code = typeof input.code === "string" ? input.code.trim() : "";
|
|
236
|
+
return code ? `REPL: ${code}` : REPL_TOOL_NAME;
|
|
237
|
+
}
|
|
238
|
+
if (name === MONITOR_TOOL_NAME) {
|
|
239
|
+
const description = nonEmptyString(input.description);
|
|
240
|
+
return description ? `${MONITOR_TOOL_NAME}: ${description}` : MONITOR_TOOL_NAME;
|
|
241
|
+
}
|
|
242
|
+
if (name === WORKFLOW_TOOL_NAME) {
|
|
243
|
+
const workflowName = nonEmptyString(input.name);
|
|
244
|
+
return workflowName ? `${WORKFLOW_TOOL_NAME}: ${workflowName}` : WORKFLOW_TOOL_NAME;
|
|
245
|
+
}
|
|
246
|
+
if (name === PROJECTS_TOOL_NAME) {
|
|
247
|
+
return formatProjectsTitle(input);
|
|
248
|
+
}
|
|
249
|
+
if (name === ARTIFACT_TOOL_NAME) {
|
|
250
|
+
const label = nonEmptyString(input.label) ?? nonEmptyString(input.file_path);
|
|
251
|
+
return label ? `${ARTIFACT_TOOL_NAME}: ${label}` : ARTIFACT_TOOL_NAME;
|
|
252
|
+
}
|
|
253
|
+
if (name === SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME) {
|
|
254
|
+
// TODO: The TUI accepts this SDK tool call but does not implement an onboarding role flow yet.
|
|
255
|
+
return SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME;
|
|
256
|
+
}
|
|
257
|
+
if (name === "EnterWorktree") {
|
|
258
|
+
const worktreeName = typeof input.name === "string" ? input.name.trim() : "";
|
|
259
|
+
return worktreeName || "EnterWorktree";
|
|
260
|
+
}
|
|
261
|
+
if (name === "ExitWorktree") {
|
|
262
|
+
return "ExitWorktree";
|
|
263
|
+
}
|
|
83
264
|
if ((name === "Read" || name === "Write" || name === "Edit") && typeof input.file_path === "string") {
|
|
84
265
|
return `${name} ${input.file_path}`;
|
|
85
266
|
}
|
|
@@ -95,6 +276,13 @@ export function toolTitle(name, input) {
|
|
|
95
276
|
}
|
|
96
277
|
return name;
|
|
97
278
|
}
|
|
279
|
+
function formatProjectsTitle(input) {
|
|
280
|
+
const method = nonEmptyString(input.method);
|
|
281
|
+
const action = method?.startsWith("project_") ? method.slice("project_".length) : method;
|
|
282
|
+
const suffix = nonEmptyString(input.path) ?? nonEmptyString(input.query);
|
|
283
|
+
const base = action ? `${PROJECTS_TOOL_NAME}: ${action}` : PROJECTS_TOOL_NAME;
|
|
284
|
+
return suffix ? `${base} ${suffix}` : base;
|
|
285
|
+
}
|
|
98
286
|
function editDiffContent(name, input) {
|
|
99
287
|
const filePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
100
288
|
if (!filePath) {
|
|
@@ -117,10 +305,10 @@ function editDiffContent(name, input) {
|
|
|
117
305
|
}
|
|
118
306
|
return [];
|
|
119
307
|
}
|
|
120
|
-
export function createToolCall(toolUseId, name, input, parentToolUseId = null) {
|
|
308
|
+
export function createToolCall(toolUseId, name, input, parentToolUseId = null, titleContext = {}) {
|
|
121
309
|
return {
|
|
122
310
|
tool_call_id: toolUseId,
|
|
123
|
-
title: toolTitle(name, input),
|
|
311
|
+
title: toolTitle(name, input, titleContext),
|
|
124
312
|
kind: normalizeToolKind(name),
|
|
125
313
|
status: "pending",
|
|
126
314
|
content: editDiffContent(name, input),
|
|
@@ -249,30 +437,46 @@ function mcpResourceContentFromResult(rawResult, rawContent) {
|
|
|
249
437
|
return [];
|
|
250
438
|
}
|
|
251
439
|
function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
252
|
-
const candidates =
|
|
440
|
+
const candidates = collectResultCandidates(rawResult, rawContent);
|
|
441
|
+
const metadata = {};
|
|
253
442
|
if (toolName === "Bash") {
|
|
254
443
|
for (const candidate of candidates) {
|
|
255
444
|
const hasAssistantAutoBackgrounded = typeof candidate.assistantAutoBackgrounded === "boolean";
|
|
256
445
|
if (hasAssistantAutoBackgrounded) {
|
|
257
446
|
const bashMetadata = {};
|
|
258
447
|
bashMetadata.assistant_auto_backgrounded = candidate.assistantAutoBackgrounded;
|
|
259
|
-
|
|
260
|
-
|
|
448
|
+
metadata.bash = bashMetadata;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (toolName === "Agent" || toolName === "Task") {
|
|
454
|
+
for (const candidate of candidates) {
|
|
455
|
+
const resolvedModel = nonEmptyString(candidate.resolvedModel);
|
|
456
|
+
if (resolvedModel) {
|
|
457
|
+
const agentMetadata = {
|
|
458
|
+
resolved_model: resolvedModel,
|
|
261
459
|
};
|
|
460
|
+
metadata.agent = agentMetadata;
|
|
461
|
+
break;
|
|
262
462
|
}
|
|
263
463
|
}
|
|
264
|
-
return undefined;
|
|
265
464
|
}
|
|
266
|
-
if (toolName === "
|
|
465
|
+
if (toolName === "WebFetch") {
|
|
267
466
|
for (const candidate of candidates) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
467
|
+
const artifactRead = asRecordOrNull(candidate.artifactRead);
|
|
468
|
+
const slug = nonEmptyString(artifactRead?.slug);
|
|
469
|
+
const ver = nonEmptyString(artifactRead?.ver);
|
|
470
|
+
if (slug && ver) {
|
|
471
|
+
const webFetchMetadata = {
|
|
472
|
+
artifact_read: { slug, ver },
|
|
271
473
|
};
|
|
474
|
+
metadata.web_fetch = webFetchMetadata;
|
|
475
|
+
break;
|
|
272
476
|
}
|
|
273
477
|
}
|
|
274
478
|
}
|
|
275
|
-
return undefined;
|
|
479
|
+
return metadata.bash || metadata.agent || metadata.web_fetch ? metadata : undefined;
|
|
276
480
|
}
|
|
277
481
|
export function extractText(value) {
|
|
278
482
|
if (typeof value === "string") {
|
|
@@ -532,41 +736,1067 @@ function fileUnchangedResultText(rawResult, rawContent) {
|
|
|
532
736
|
}
|
|
533
737
|
return "";
|
|
534
738
|
}
|
|
535
|
-
function agentTitleFromAgentOutput(rawResult, rawContent) {
|
|
739
|
+
function agentTitleFromAgentOutput(rawResult, rawContent, base) {
|
|
740
|
+
const inputAgentName = nonEmptyString(asRecordOrNull(base?.raw_input)?.name);
|
|
741
|
+
if (inputAgentName) {
|
|
742
|
+
return "";
|
|
743
|
+
}
|
|
536
744
|
for (const candidate of resultRecordCandidates(rawResult, rawContent)) {
|
|
537
745
|
const agentType = typeof candidate.agentType === "string" ? candidate.agentType.trim() : "";
|
|
538
746
|
if (agentType) {
|
|
539
|
-
return agentType
|
|
747
|
+
return `Agent: ${agentType}`;
|
|
540
748
|
}
|
|
541
749
|
}
|
|
542
750
|
return "";
|
|
543
751
|
}
|
|
544
|
-
|
|
752
|
+
function firstSearchRecord(toolName, rawResult, rawContent) {
|
|
753
|
+
if (toolName !== "Glob" && toolName !== "Grep") {
|
|
754
|
+
return undefined;
|
|
755
|
+
}
|
|
756
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
757
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
758
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
759
|
+
}
|
|
760
|
+
return candidates.find((candidate) => {
|
|
761
|
+
if (toolName === "Glob") {
|
|
762
|
+
return Array.isArray(candidate.filenames) || "numFiles" in candidate || "truncated" in candidate;
|
|
763
|
+
}
|
|
764
|
+
return (Array.isArray(candidate.filenames) ||
|
|
765
|
+
"numFiles" in candidate ||
|
|
766
|
+
"content" in candidate ||
|
|
767
|
+
"numLines" in candidate ||
|
|
768
|
+
"numMatches" in candidate);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
function recordNumber(record, key) {
|
|
772
|
+
const value = record[key];
|
|
773
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
774
|
+
}
|
|
775
|
+
function recordString(record, key) {
|
|
776
|
+
const value = record[key];
|
|
777
|
+
return typeof value === "string" ? value : undefined;
|
|
778
|
+
}
|
|
779
|
+
function searchFilenames(record) {
|
|
780
|
+
return Array.isArray(record.filenames)
|
|
781
|
+
? record.filenames.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
782
|
+
: [];
|
|
783
|
+
}
|
|
784
|
+
function pluralize(count, singular, plural = `${singular}s`) {
|
|
785
|
+
return count === 1 ? singular : plural;
|
|
786
|
+
}
|
|
787
|
+
function truncateList(values, limit) {
|
|
788
|
+
if (values.length <= limit) {
|
|
789
|
+
return { visible: values, hidden: 0 };
|
|
790
|
+
}
|
|
791
|
+
return { visible: values.slice(0, limit), hidden: values.length - limit };
|
|
792
|
+
}
|
|
793
|
+
function globResultText(record) {
|
|
794
|
+
const filenames = searchFilenames(record);
|
|
795
|
+
const numFiles = recordNumber(record, "numFiles") ?? filenames.length;
|
|
796
|
+
const truncated = record.truncated === true;
|
|
797
|
+
const lines = [];
|
|
798
|
+
if (numFiles === 0 && filenames.length === 0) {
|
|
799
|
+
lines.push("No files found");
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
lines.push(`${numFiles} ${pluralize(numFiles, "file")} found${truncated ? " (truncated)" : ""}`);
|
|
803
|
+
}
|
|
804
|
+
if (filenames.length > 0) {
|
|
805
|
+
const { visible, hidden } = truncateList(filenames, 20);
|
|
806
|
+
lines.push(...visible);
|
|
807
|
+
if (hidden > 0) {
|
|
808
|
+
lines.push(`... ${hidden} more ${pluralize(hidden, "file")} hidden`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const durationMs = recordNumber(record, "durationMs");
|
|
812
|
+
if (durationMs !== undefined) {
|
|
813
|
+
lines.push(`Duration: ${durationMs}ms`);
|
|
814
|
+
}
|
|
815
|
+
return lines.join("\n");
|
|
816
|
+
}
|
|
817
|
+
function grepResultText(record) {
|
|
818
|
+
const filenames = searchFilenames(record);
|
|
819
|
+
const content = recordString(record, "content") ?? "";
|
|
820
|
+
const numFiles = recordNumber(record, "numFiles") ?? filenames.length;
|
|
821
|
+
const numLines = recordNumber(record, "numLines");
|
|
822
|
+
const numMatches = recordNumber(record, "numMatches");
|
|
823
|
+
const appliedLimit = recordNumber(record, "appliedLimit");
|
|
824
|
+
const appliedOffset = recordNumber(record, "appliedOffset");
|
|
825
|
+
const mode = searchModeLabel(record.mode) || "files";
|
|
826
|
+
const lines = [];
|
|
827
|
+
if (content.trim().length > 0) {
|
|
828
|
+
lines.push(content);
|
|
829
|
+
}
|
|
830
|
+
else if (filenames.length > 0) {
|
|
831
|
+
const { visible, hidden } = truncateList(filenames, 20);
|
|
832
|
+
lines.push(...visible);
|
|
833
|
+
if (hidden > 0) {
|
|
834
|
+
lines.push(`... ${hidden} more ${pluralize(hidden, "file")} hidden`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
lines.push("No matches found");
|
|
839
|
+
}
|
|
840
|
+
const summaryParts = [];
|
|
841
|
+
summaryParts.push(`${numFiles} ${pluralize(numFiles, "file")}`);
|
|
842
|
+
if (numMatches !== undefined) {
|
|
843
|
+
summaryParts.push(`${numMatches} ${pluralize(numMatches, "match", "matches")}`);
|
|
844
|
+
}
|
|
845
|
+
if (numLines !== undefined) {
|
|
846
|
+
summaryParts.push(`${numLines} ${pluralize(numLines, "line")}`);
|
|
847
|
+
}
|
|
848
|
+
summaryParts.push(`mode ${mode}`);
|
|
849
|
+
if (appliedLimit !== undefined) {
|
|
850
|
+
summaryParts.push(`limit ${appliedLimit}`);
|
|
851
|
+
}
|
|
852
|
+
if (appliedOffset !== undefined && appliedOffset > 0) {
|
|
853
|
+
summaryParts.push(`offset ${appliedOffset}`);
|
|
854
|
+
}
|
|
855
|
+
if (summaryParts.length > 0) {
|
|
856
|
+
lines.push(`Summary: ${summaryParts.join(", ")}`);
|
|
857
|
+
}
|
|
858
|
+
return lines.join("\n");
|
|
859
|
+
}
|
|
860
|
+
function searchResultText(toolName, rawResult, rawContent) {
|
|
861
|
+
const record = firstSearchRecord(toolName, rawResult, rawContent);
|
|
862
|
+
if (!record) {
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
865
|
+
return toolName === "Glob" ? globResultText(record) : grepResultText(record);
|
|
866
|
+
}
|
|
867
|
+
function worktreeResultFields(toolName, rawResult, rawContent) {
|
|
868
|
+
if (toolName !== "EnterWorktree" && toolName !== "ExitWorktree") {
|
|
869
|
+
return undefined;
|
|
870
|
+
}
|
|
871
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
872
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
873
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
874
|
+
}
|
|
875
|
+
for (const candidate of candidates) {
|
|
876
|
+
const branch = typeof candidate.worktreeBranch === "string" ? candidate.worktreeBranch.trim() : "";
|
|
877
|
+
const path = typeof candidate.worktreePath === "string" ? candidate.worktreePath.trim() : "";
|
|
878
|
+
const output = branch ? `Branch: ${branch}` : path ? `Path: ${path}` : "";
|
|
879
|
+
const isStructuredWorktreeOutput = "message" in candidate ||
|
|
880
|
+
"worktreeBranch" in candidate ||
|
|
881
|
+
"worktreePath" in candidate ||
|
|
882
|
+
"originalCwd" in candidate;
|
|
883
|
+
if (output || isStructuredWorktreeOutput) {
|
|
884
|
+
return output ? { output } : {};
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return undefined;
|
|
888
|
+
}
|
|
889
|
+
function booleanLabel(value) {
|
|
890
|
+
return value ? "yes" : "no";
|
|
891
|
+
}
|
|
892
|
+
function pushBooleanField(lines, label, value) {
|
|
893
|
+
if (typeof value === "boolean") {
|
|
894
|
+
lines.push(`${label}: ${booleanLabel(value)}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function nonEmptyString(value) {
|
|
898
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
899
|
+
}
|
|
900
|
+
const CRON_MONTH_NAMES = [
|
|
901
|
+
"January",
|
|
902
|
+
"February",
|
|
903
|
+
"March",
|
|
904
|
+
"April",
|
|
905
|
+
"May",
|
|
906
|
+
"June",
|
|
907
|
+
"July",
|
|
908
|
+
"August",
|
|
909
|
+
"September",
|
|
910
|
+
"October",
|
|
911
|
+
"November",
|
|
912
|
+
"December",
|
|
913
|
+
];
|
|
914
|
+
const CRON_WEEKDAY_NAMES = [
|
|
915
|
+
"Sunday",
|
|
916
|
+
"Monday",
|
|
917
|
+
"Tuesday",
|
|
918
|
+
"Wednesday",
|
|
919
|
+
"Thursday",
|
|
920
|
+
"Friday",
|
|
921
|
+
"Saturday",
|
|
922
|
+
];
|
|
923
|
+
const CRON_MONTH_ALIASES = new Map(["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"].map((name, index) => [name, index + 1]));
|
|
924
|
+
const CRON_WEEKDAY_ALIASES = new Map(["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"].map((name, index) => [name, index]));
|
|
925
|
+
function parseCronValue(value, min, max, aliases) {
|
|
926
|
+
const normalized = value.trim().toUpperCase();
|
|
927
|
+
const aliased = aliases?.get(normalized);
|
|
928
|
+
const parsed = aliased ?? Number(normalized);
|
|
929
|
+
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
932
|
+
return parsed;
|
|
933
|
+
}
|
|
934
|
+
function parseCronField(rawField, min, max, aliases) {
|
|
935
|
+
const raw = rawField.trim();
|
|
936
|
+
if (raw === "*") {
|
|
937
|
+
return { kind: "any", raw };
|
|
938
|
+
}
|
|
939
|
+
const stepMatch = raw.match(/^\*\/(\d+)$/);
|
|
940
|
+
if (stepMatch) {
|
|
941
|
+
const step = Number(stepMatch[1]);
|
|
942
|
+
return Number.isInteger(step) && step > 0 ? { kind: "step", raw, step } : { kind: "unsupported", raw };
|
|
943
|
+
}
|
|
944
|
+
if (raw.includes(",")) {
|
|
945
|
+
const values = raw
|
|
946
|
+
.split(",")
|
|
947
|
+
.map((part) => parseCronValue(part, min, max, aliases))
|
|
948
|
+
.filter((value) => value !== undefined);
|
|
949
|
+
return values.length === raw.split(",").length ? { kind: "list", raw, values } : { kind: "unsupported", raw };
|
|
950
|
+
}
|
|
951
|
+
const rangeMatch = raw.match(/^([^/-]+)-([^/-]+)$/);
|
|
952
|
+
if (rangeMatch) {
|
|
953
|
+
const start = parseCronValue(rangeMatch[1], min, max, aliases);
|
|
954
|
+
const end = parseCronValue(rangeMatch[2], min, max, aliases);
|
|
955
|
+
return start !== undefined && end !== undefined && start <= end
|
|
956
|
+
? { kind: "range", raw, start, end }
|
|
957
|
+
: { kind: "unsupported", raw };
|
|
958
|
+
}
|
|
959
|
+
const value = parseCronValue(raw, min, max, aliases);
|
|
960
|
+
return value !== undefined ? { kind: "single", raw, value } : { kind: "unsupported", raw };
|
|
961
|
+
}
|
|
962
|
+
function isCronAny(field) {
|
|
963
|
+
return field.kind === "any";
|
|
964
|
+
}
|
|
965
|
+
function isCronUnsupported(...fields) {
|
|
966
|
+
return fields.some((field) => field.kind === "unsupported");
|
|
967
|
+
}
|
|
968
|
+
function padCronNumber(value) {
|
|
969
|
+
return value.toString().padStart(2, "0");
|
|
970
|
+
}
|
|
971
|
+
function cronTime(hour, minute) {
|
|
972
|
+
if (hour.kind !== "single" || minute.kind !== "single") {
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
return `${padCronNumber(hour.value)}:${padCronNumber(minute.value)}`;
|
|
976
|
+
}
|
|
977
|
+
function pluralUnit(value, unit) {
|
|
978
|
+
return value === 1 ? unit : `${unit}s`;
|
|
979
|
+
}
|
|
980
|
+
function joinEnglishList(values) {
|
|
981
|
+
if (values.length <= 2) {
|
|
982
|
+
return values.join(" and ");
|
|
983
|
+
}
|
|
984
|
+
return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
|
|
985
|
+
}
|
|
986
|
+
function weekdayName(value) {
|
|
987
|
+
const normalized = value === 7 ? 0 : value;
|
|
988
|
+
return CRON_WEEKDAY_NAMES[normalized];
|
|
989
|
+
}
|
|
990
|
+
function weekdayDescription(field) {
|
|
991
|
+
if (field.kind === "single") {
|
|
992
|
+
return weekdayName(field.value);
|
|
993
|
+
}
|
|
994
|
+
if (field.kind === "range" && field.start === 1 && field.end === 5) {
|
|
995
|
+
return "weekday";
|
|
996
|
+
}
|
|
997
|
+
if (field.kind === "range" && field.start === 0 && field.end === 6) {
|
|
998
|
+
return "day";
|
|
999
|
+
}
|
|
1000
|
+
if (field.kind === "list") {
|
|
1001
|
+
const normalized = [...new Set(field.values.map((value) => (value === 7 ? 0 : value)))].sort((left, right) => left - right);
|
|
1002
|
+
if (normalized.length === 2 && normalized[0] === 0 && normalized[1] === 6) {
|
|
1003
|
+
return "weekend day";
|
|
1004
|
+
}
|
|
1005
|
+
const names = normalized.map(weekdayName);
|
|
1006
|
+
return names.every((name) => name !== undefined) ? joinEnglishList(names) : undefined;
|
|
1007
|
+
}
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
function monthName(value) {
|
|
1011
|
+
return CRON_MONTH_NAMES[value - 1];
|
|
1012
|
+
}
|
|
1013
|
+
function hourlyScheduleText(minute) {
|
|
1014
|
+
if (minute.kind !== "single") {
|
|
1015
|
+
return undefined;
|
|
1016
|
+
}
|
|
1017
|
+
return minute.value === 0 ? "Every hour on the hour" : `Every hour at minute ${padCronNumber(minute.value)}`;
|
|
1018
|
+
}
|
|
1019
|
+
function cronScheduleFromExpression(cron) {
|
|
1020
|
+
const parts = cron.trim().split(/\s+/);
|
|
1021
|
+
if (parts.length !== 5) {
|
|
1022
|
+
return undefined;
|
|
1023
|
+
}
|
|
1024
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek] = [
|
|
1025
|
+
parseCronField(parts[0], 0, 59),
|
|
1026
|
+
parseCronField(parts[1], 0, 23),
|
|
1027
|
+
parseCronField(parts[2], 1, 31),
|
|
1028
|
+
parseCronField(parts[3], 1, 12, CRON_MONTH_ALIASES),
|
|
1029
|
+
parseCronField(parts[4], 0, 7, CRON_WEEKDAY_ALIASES),
|
|
1030
|
+
];
|
|
1031
|
+
if (isCronUnsupported(minute, hour, dayOfMonth, month, dayOfWeek)) {
|
|
1032
|
+
return undefined;
|
|
1033
|
+
}
|
|
1034
|
+
const everyDay = isCronAny(dayOfMonth) && isCronAny(month) && isCronAny(dayOfWeek);
|
|
1035
|
+
if (everyDay && minute.kind === "any" && hour.kind === "any") {
|
|
1036
|
+
return "Every minute";
|
|
1037
|
+
}
|
|
1038
|
+
if (everyDay && minute.kind === "step" && hour.kind === "any") {
|
|
1039
|
+
return `Every ${minute.step} ${pluralUnit(minute.step, "minute")}`;
|
|
1040
|
+
}
|
|
1041
|
+
if (everyDay && minute.kind === "single" && hour.kind === "any") {
|
|
1042
|
+
return hourlyScheduleText(minute);
|
|
1043
|
+
}
|
|
1044
|
+
if (everyDay && minute.kind === "single" && hour.kind === "step") {
|
|
1045
|
+
const suffix = minute.value === 0 ? "on the hour" : `at minute ${padCronNumber(minute.value)}`;
|
|
1046
|
+
return `Every ${hour.step} ${pluralUnit(hour.step, "hour")} ${suffix}`;
|
|
1047
|
+
}
|
|
1048
|
+
const time = cronTime(hour, minute);
|
|
1049
|
+
if (!time) {
|
|
1050
|
+
return undefined;
|
|
1051
|
+
}
|
|
1052
|
+
if (everyDay) {
|
|
1053
|
+
return `Every day at ${time}`;
|
|
1054
|
+
}
|
|
1055
|
+
if (isCronAny(dayOfMonth) && isCronAny(month) && !isCronAny(dayOfWeek)) {
|
|
1056
|
+
const weekday = weekdayDescription(dayOfWeek);
|
|
1057
|
+
return weekday ? `Every ${weekday} at ${time}` : undefined;
|
|
1058
|
+
}
|
|
1059
|
+
if (isCronAny(month) && isCronAny(dayOfWeek)) {
|
|
1060
|
+
if (dayOfMonth.kind === "single") {
|
|
1061
|
+
return `Every month on day ${dayOfMonth.value} at ${time}`;
|
|
1062
|
+
}
|
|
1063
|
+
if (dayOfMonth.kind === "step") {
|
|
1064
|
+
return `Every ${dayOfMonth.step} ${pluralUnit(dayOfMonth.step, "day")} at ${time}`;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (dayOfMonth.kind === "single" && isCronAny(dayOfWeek)) {
|
|
1068
|
+
if (month.kind === "single") {
|
|
1069
|
+
const monthLabel = monthName(month.value);
|
|
1070
|
+
return monthLabel ? `Every ${monthLabel} ${dayOfMonth.value} at ${time}` : undefined;
|
|
1071
|
+
}
|
|
1072
|
+
if (month.kind === "step") {
|
|
1073
|
+
return `Every ${month.step} ${pluralUnit(month.step, "month")} on day ${dayOfMonth.value} at ${time}`;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (isCronAny(dayOfMonth) && month.kind === "single" && isCronAny(dayOfWeek)) {
|
|
1077
|
+
const monthLabel = monthName(month.value);
|
|
1078
|
+
return monthLabel ? `Every day in ${monthLabel} at ${time}` : undefined;
|
|
1079
|
+
}
|
|
1080
|
+
return undefined;
|
|
1081
|
+
}
|
|
1082
|
+
function normalizeHumanSchedule(value) {
|
|
1083
|
+
const text = nonEmptyString(value);
|
|
1084
|
+
if (!text) {
|
|
1085
|
+
return undefined;
|
|
1086
|
+
}
|
|
1087
|
+
const hourlyMinute = text.match(/^Every hour at :(\d{1,2})$/i);
|
|
1088
|
+
if (hourlyMinute) {
|
|
1089
|
+
const minute = Number(hourlyMinute[1]);
|
|
1090
|
+
if (Number.isInteger(minute) && minute >= 0 && minute <= 59) {
|
|
1091
|
+
return hourlyScheduleText({ kind: "single", raw: hourlyMinute[1], value: minute });
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return text;
|
|
1095
|
+
}
|
|
1096
|
+
function readableCronSchedule(cron, humanSchedule) {
|
|
1097
|
+
const cronText = nonEmptyString(cron);
|
|
1098
|
+
if (cronText) {
|
|
1099
|
+
const derived = cronScheduleFromExpression(cronText);
|
|
1100
|
+
if (derived) {
|
|
1101
|
+
return derived;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return normalizeHumanSchedule(humanSchedule);
|
|
1105
|
+
}
|
|
1106
|
+
function cronCreateResultText(candidate, rawInput) {
|
|
1107
|
+
const input = asRecordOrNull(rawInput);
|
|
1108
|
+
const schedule = readableCronSchedule(input?.cron, candidate.humanSchedule);
|
|
1109
|
+
if (typeof candidate.id !== "string" ||
|
|
1110
|
+
typeof candidate.recurring !== "boolean" ||
|
|
1111
|
+
!schedule) {
|
|
1112
|
+
return undefined;
|
|
1113
|
+
}
|
|
1114
|
+
const lines = [
|
|
1115
|
+
`Schedule ID: ${candidate.id}`,
|
|
1116
|
+
`Schedule: ${schedule}`,
|
|
1117
|
+
`Recurring: ${booleanLabel(candidate.recurring)}`,
|
|
1118
|
+
];
|
|
1119
|
+
pushBooleanField(lines, "Durable", candidate.durable);
|
|
1120
|
+
return lines.join("\n");
|
|
1121
|
+
}
|
|
1122
|
+
function cronDeleteResultText(candidate) {
|
|
1123
|
+
return typeof candidate.id === "string" ? `Schedule ID: ${candidate.id}` : undefined;
|
|
1124
|
+
}
|
|
1125
|
+
function cronListResultText(candidate) {
|
|
1126
|
+
if (!Array.isArray(candidate.jobs)) {
|
|
1127
|
+
return undefined;
|
|
1128
|
+
}
|
|
1129
|
+
const jobs = candidate.jobs.map(asRecordOrNull).filter((job) => job !== null);
|
|
1130
|
+
if (jobs.length === 0) {
|
|
1131
|
+
return "Jobs: none";
|
|
1132
|
+
}
|
|
1133
|
+
const lines = [];
|
|
1134
|
+
if (jobs.length === 1) {
|
|
1135
|
+
const [job] = jobs;
|
|
1136
|
+
if (typeof job.id === "string") {
|
|
1137
|
+
lines.push(`Schedule ID: ${job.id}`);
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof job.cron === "string" && job.cron.trim()) {
|
|
1140
|
+
lines.push(`Cron: ${job.cron.trim()}`);
|
|
1141
|
+
}
|
|
1142
|
+
const schedule = readableCronSchedule(job.cron, job.humanSchedule);
|
|
1143
|
+
if (schedule) {
|
|
1144
|
+
lines.push(`Schedule: ${schedule}`);
|
|
1145
|
+
}
|
|
1146
|
+
if (typeof job.prompt === "string") {
|
|
1147
|
+
lines.push(`Prompt: ${job.prompt}`);
|
|
1148
|
+
}
|
|
1149
|
+
pushBooleanField(lines, "Recurring", job.recurring);
|
|
1150
|
+
pushBooleanField(lines, "Durable", job.durable);
|
|
1151
|
+
}
|
|
1152
|
+
if (jobs.length > 1) {
|
|
1153
|
+
for (const [index, job] of jobs.entries()) {
|
|
1154
|
+
if (typeof job.id === "string") {
|
|
1155
|
+
lines.push(`Schedule ID: ${job.id}`);
|
|
1156
|
+
}
|
|
1157
|
+
const schedule = readableCronSchedule(job.cron, job.humanSchedule);
|
|
1158
|
+
if (schedule) {
|
|
1159
|
+
lines.push(`Schedule: ${schedule}`);
|
|
1160
|
+
}
|
|
1161
|
+
else if (typeof job.cron === "string" && job.cron.trim()) {
|
|
1162
|
+
lines.push(`Cron: ${job.cron.trim()}`);
|
|
1163
|
+
}
|
|
1164
|
+
if (typeof job.prompt === "string") {
|
|
1165
|
+
lines.push(`Prompt: ${job.prompt}`);
|
|
1166
|
+
}
|
|
1167
|
+
if (index < jobs.length - 1) {
|
|
1168
|
+
lines.push(CRON_LIST_DIVIDER);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return lines.length > 0 ? lines.join("\n") : "Jobs: none";
|
|
1173
|
+
}
|
|
1174
|
+
function cronResultText(toolName, rawResult, rawContent, rawInput) {
|
|
1175
|
+
if (!isCronToolName(toolName)) {
|
|
1176
|
+
return undefined;
|
|
1177
|
+
}
|
|
1178
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
1179
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
1180
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
1181
|
+
}
|
|
1182
|
+
for (const candidate of candidates) {
|
|
1183
|
+
const output = toolName === "CronCreate"
|
|
1184
|
+
? cronCreateResultText(candidate, rawInput)
|
|
1185
|
+
: toolName === "CronDelete"
|
|
1186
|
+
? cronDeleteResultText(candidate)
|
|
1187
|
+
: cronListResultText(candidate);
|
|
1188
|
+
if (output !== undefined) {
|
|
1189
|
+
return output;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return undefined;
|
|
1193
|
+
}
|
|
1194
|
+
function formatDurationSeconds(seconds) {
|
|
1195
|
+
const rounded = Math.max(0, Math.trunc(seconds));
|
|
1196
|
+
const hours = Math.floor(rounded / 3600);
|
|
1197
|
+
const minutes = Math.floor((rounded % 3600) / 60);
|
|
1198
|
+
const remainingSeconds = rounded % 60;
|
|
1199
|
+
const parts = [];
|
|
1200
|
+
if (hours > 0) {
|
|
1201
|
+
parts.push(`${hours}h`);
|
|
1202
|
+
}
|
|
1203
|
+
if (minutes > 0) {
|
|
1204
|
+
parts.push(`${minutes}m`);
|
|
1205
|
+
}
|
|
1206
|
+
if (remainingSeconds > 0 || parts.length === 0) {
|
|
1207
|
+
parts.push(`${remainingSeconds}s`);
|
|
1208
|
+
}
|
|
1209
|
+
return parts.join(" ");
|
|
1210
|
+
}
|
|
1211
|
+
function formatDurationMilliseconds(milliseconds) {
|
|
1212
|
+
const rounded = Math.max(0, Math.trunc(milliseconds));
|
|
1213
|
+
if (rounded < 1000) {
|
|
1214
|
+
return `${rounded}ms`;
|
|
1215
|
+
}
|
|
1216
|
+
return formatDurationSeconds(rounded / 1000);
|
|
1217
|
+
}
|
|
1218
|
+
function formatLocalTimestamp(epochMs) {
|
|
1219
|
+
if (!Number.isFinite(epochMs)) {
|
|
1220
|
+
return undefined;
|
|
1221
|
+
}
|
|
1222
|
+
const date = new Date(epochMs);
|
|
1223
|
+
if (!Number.isFinite(date.getTime())) {
|
|
1224
|
+
return undefined;
|
|
1225
|
+
}
|
|
1226
|
+
const year = date.getFullYear().toString().padStart(4, "0");
|
|
1227
|
+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
1228
|
+
const day = date.getDate().toString().padStart(2, "0");
|
|
1229
|
+
const hour = date.getHours().toString().padStart(2, "0");
|
|
1230
|
+
const minute = date.getMinutes().toString().padStart(2, "0");
|
|
1231
|
+
const second = date.getSeconds().toString().padStart(2, "0");
|
|
1232
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second} local`;
|
|
1233
|
+
}
|
|
1234
|
+
function formatIsoTimestamp(value) {
|
|
1235
|
+
const raw = nonEmptyString(value);
|
|
1236
|
+
if (!raw) {
|
|
1237
|
+
return undefined;
|
|
1238
|
+
}
|
|
1239
|
+
const parsed = Date.parse(raw);
|
|
1240
|
+
return Number.isFinite(parsed) ? formatLocalTimestamp(parsed) : raw;
|
|
1241
|
+
}
|
|
1242
|
+
function scheduleWakeupResultText(toolName, rawResult, rawContent) {
|
|
1243
|
+
if (toolName !== SCHEDULE_WAKEUP_TOOL_NAME) {
|
|
1244
|
+
return undefined;
|
|
1245
|
+
}
|
|
1246
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
1247
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
1248
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
1249
|
+
}
|
|
1250
|
+
for (const candidate of candidates) {
|
|
1251
|
+
const scheduledFor = typeof candidate.scheduledFor === "number"
|
|
1252
|
+
? formatLocalTimestamp(candidate.scheduledFor)
|
|
1253
|
+
: undefined;
|
|
1254
|
+
const clampedDelaySeconds = typeof candidate.clampedDelaySeconds === "number"
|
|
1255
|
+
? formatDurationSeconds(candidate.clampedDelaySeconds)
|
|
1256
|
+
: undefined;
|
|
1257
|
+
if (!scheduledFor || !clampedDelaySeconds || typeof candidate.wasClamped !== "boolean") {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
return [
|
|
1261
|
+
`Scheduled for: ${scheduledFor}`,
|
|
1262
|
+
`Actual delay: ${clampedDelaySeconds}`,
|
|
1263
|
+
`Clamped: ${booleanLabel(candidate.wasClamped)}`,
|
|
1264
|
+
].join("\n");
|
|
1265
|
+
}
|
|
1266
|
+
return undefined;
|
|
1267
|
+
}
|
|
1268
|
+
function pushNotificationDisabledReason(value) {
|
|
1269
|
+
switch (value) {
|
|
1270
|
+
case "config_off":
|
|
1271
|
+
return "notifications disabled";
|
|
1272
|
+
case "user_present":
|
|
1273
|
+
return "user present";
|
|
1274
|
+
case "no_transport":
|
|
1275
|
+
return "no notification transport";
|
|
1276
|
+
default:
|
|
1277
|
+
return undefined;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function pushNotificationResultText(toolName, rawResult, rawContent, rawInput) {
|
|
1281
|
+
if (toolName !== PUSH_NOTIFICATION_TOOL_NAME) {
|
|
1282
|
+
return undefined;
|
|
1283
|
+
}
|
|
1284
|
+
const inputMessage = nonEmptyString(asRecordOrNull(rawInput)?.message);
|
|
1285
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
1286
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
1287
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
1288
|
+
}
|
|
1289
|
+
for (const candidate of candidates) {
|
|
1290
|
+
const isStructuredPushOutput = "message" in candidate ||
|
|
1291
|
+
"pushSent" in candidate ||
|
|
1292
|
+
"localSent" in candidate ||
|
|
1293
|
+
"disabledReason" in candidate ||
|
|
1294
|
+
"idleSec" in candidate ||
|
|
1295
|
+
"hasFocus" in candidate ||
|
|
1296
|
+
"sentAt" in candidate;
|
|
1297
|
+
if (!isStructuredPushOutput) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
const lines = [];
|
|
1301
|
+
const outputMessage = nonEmptyString(candidate.message);
|
|
1302
|
+
if (outputMessage && outputMessage !== inputMessage) {
|
|
1303
|
+
lines.push(`Result: ${outputMessage}`);
|
|
1304
|
+
}
|
|
1305
|
+
pushBooleanField(lines, "Push sent", candidate.pushSent);
|
|
1306
|
+
pushBooleanField(lines, "Local sent", candidate.localSent);
|
|
1307
|
+
const disabledReason = pushNotificationDisabledReason(candidate.disabledReason);
|
|
1308
|
+
if (disabledReason) {
|
|
1309
|
+
lines.push(`Disabled reason: ${disabledReason}`);
|
|
1310
|
+
}
|
|
1311
|
+
if (typeof candidate.idleSec === "number" && Number.isFinite(candidate.idleSec)) {
|
|
1312
|
+
lines.push(`Idle time: ${formatDurationSeconds(candidate.idleSec)}`);
|
|
1313
|
+
}
|
|
1314
|
+
pushBooleanField(lines, "App focused", candidate.hasFocus);
|
|
1315
|
+
const sentAt = formatIsoTimestamp(candidate.sentAt);
|
|
1316
|
+
if (sentAt) {
|
|
1317
|
+
lines.push(`Sent at: ${sentAt}`);
|
|
1318
|
+
}
|
|
1319
|
+
return lines.join("\n");
|
|
1320
|
+
}
|
|
1321
|
+
return undefined;
|
|
1322
|
+
}
|
|
1323
|
+
function compactJson(value) {
|
|
1324
|
+
if (value === undefined || value === null) {
|
|
1325
|
+
return undefined;
|
|
1326
|
+
}
|
|
1327
|
+
if (typeof value === "string") {
|
|
1328
|
+
return value.trim() ? value : undefined;
|
|
1329
|
+
}
|
|
1330
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
1331
|
+
return undefined;
|
|
1332
|
+
}
|
|
1333
|
+
const record = asRecordOrNull(value);
|
|
1334
|
+
if (record && Object.keys(record).length === 0) {
|
|
1335
|
+
return undefined;
|
|
1336
|
+
}
|
|
1337
|
+
try {
|
|
1338
|
+
return JSON.stringify(value);
|
|
1339
|
+
}
|
|
1340
|
+
catch {
|
|
1341
|
+
return undefined;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
function compactParsedJsonString(value) {
|
|
1345
|
+
const trimmed = value.trim();
|
|
1346
|
+
if (!trimmed) {
|
|
1347
|
+
return undefined;
|
|
1348
|
+
}
|
|
1349
|
+
try {
|
|
1350
|
+
return JSON.stringify(JSON.parse(trimmed));
|
|
1351
|
+
}
|
|
1352
|
+
catch {
|
|
1353
|
+
return trimmed;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function remoteTriggerResultFields(toolName, rawResult, rawContent) {
|
|
1357
|
+
if (toolName !== REMOTE_TRIGGER_TOOL_NAME) {
|
|
1358
|
+
return undefined;
|
|
1359
|
+
}
|
|
1360
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
1361
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
1362
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
1363
|
+
}
|
|
1364
|
+
for (const candidate of candidates) {
|
|
1365
|
+
if (typeof candidate.status !== "number" || typeof candidate.json !== "string") {
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
const lines = [`Status: ${candidate.status}`];
|
|
1369
|
+
const summary = nonEmptyString(candidate.summary);
|
|
1370
|
+
if (summary) {
|
|
1371
|
+
lines.push(`Summary: ${summary}`);
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1374
|
+
const response = compactParsedJsonString(candidate.json);
|
|
1375
|
+
if (response) {
|
|
1376
|
+
lines.push(`Response: ${response}`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return { output: lines.join("\n"), failed: candidate.status >= 400 };
|
|
1380
|
+
}
|
|
1381
|
+
return undefined;
|
|
1382
|
+
}
|
|
1383
|
+
function hasConcreteReplField(record) {
|
|
1384
|
+
return ("code" in record ||
|
|
1385
|
+
"error" in record ||
|
|
1386
|
+
"stdout" in record ||
|
|
1387
|
+
"stderr" in record ||
|
|
1388
|
+
"registeredTools" in record ||
|
|
1389
|
+
"images" in record ||
|
|
1390
|
+
"documents" in record);
|
|
1391
|
+
}
|
|
1392
|
+
function isReplWrapperRecord(record) {
|
|
1393
|
+
const nestedResult = asRecordOrNull(record.result);
|
|
1394
|
+
return Boolean(nestedResult && hasConcreteReplField(nestedResult));
|
|
1395
|
+
}
|
|
1396
|
+
function isReplOutputRecord(record) {
|
|
1397
|
+
if (isReplWrapperRecord(record)) {
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
return hasConcreteReplField(record) || "result" in record;
|
|
1401
|
+
}
|
|
1402
|
+
function replResultFields(toolName, rawResult, rawContent) {
|
|
1403
|
+
if (toolName !== REPL_TOOL_NAME) {
|
|
1404
|
+
return undefined;
|
|
1405
|
+
}
|
|
1406
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
1407
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
1408
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
1409
|
+
}
|
|
1410
|
+
for (const candidate of candidates) {
|
|
1411
|
+
if (!isReplOutputRecord(candidate)) {
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
const lines = [];
|
|
1415
|
+
let failed = false;
|
|
1416
|
+
if ("error" in candidate) {
|
|
1417
|
+
failed = true;
|
|
1418
|
+
const error = compactJson(candidate.error);
|
|
1419
|
+
if (error) {
|
|
1420
|
+
lines.push(`Error: ${error}`);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const stdout = typeof candidate.stdout === "string" ? candidate.stdout : "";
|
|
1424
|
+
if (stdout) {
|
|
1425
|
+
lines.push(`Stdout: ${stdout}`);
|
|
1426
|
+
}
|
|
1427
|
+
const stderr = typeof candidate.stderr === "string" ? candidate.stderr : "";
|
|
1428
|
+
if (stderr) {
|
|
1429
|
+
lines.push(`Stderr: ${stderr}`);
|
|
1430
|
+
}
|
|
1431
|
+
const result = compactJson(candidate.result);
|
|
1432
|
+
if (result) {
|
|
1433
|
+
lines.push(`Result: ${result}`);
|
|
1434
|
+
}
|
|
1435
|
+
if (Array.isArray(candidate.registeredTools)) {
|
|
1436
|
+
const registeredTools = candidate.registeredTools.filter((tool) => typeof tool === "string" && tool.trim().length > 0);
|
|
1437
|
+
if (registeredTools.length > 0) {
|
|
1438
|
+
lines.push(`Registered tools: ${registeredTools.join(", ")}`);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (Array.isArray(candidate.images) && candidate.images.length > 0) {
|
|
1442
|
+
lines.push(`Images: ${candidate.images.length}`);
|
|
1443
|
+
}
|
|
1444
|
+
if (Array.isArray(candidate.documents) && candidate.documents.length > 0) {
|
|
1445
|
+
lines.push(`Documents: ${candidate.documents.length}`);
|
|
1446
|
+
}
|
|
1447
|
+
return { output: lines.length > 0 ? lines.join("\n") : undefined, failed };
|
|
1448
|
+
}
|
|
1449
|
+
return undefined;
|
|
1450
|
+
}
|
|
1451
|
+
function collectResultCandidates(rawResult, rawContent) {
|
|
1452
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
1453
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
1454
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
1455
|
+
}
|
|
1456
|
+
return candidates;
|
|
1457
|
+
}
|
|
1458
|
+
function monitorResultFields(toolName, rawResult, rawContent) {
|
|
1459
|
+
if (toolName !== MONITOR_TOOL_NAME) {
|
|
1460
|
+
return undefined;
|
|
1461
|
+
}
|
|
1462
|
+
for (const candidate of collectResultCandidates(rawResult, rawContent)) {
|
|
1463
|
+
const taskId = nonEmptyString(candidate.taskId);
|
|
1464
|
+
const timeoutMs = typeof candidate.timeoutMs === "number" && Number.isFinite(candidate.timeoutMs)
|
|
1465
|
+
? Math.max(0, Math.trunc(candidate.timeoutMs))
|
|
1466
|
+
: undefined;
|
|
1467
|
+
const persistent = typeof candidate.persistent === "boolean"
|
|
1468
|
+
? candidate.persistent
|
|
1469
|
+
: timeoutMs === 0
|
|
1470
|
+
? true
|
|
1471
|
+
: timeoutMs !== undefined
|
|
1472
|
+
? false
|
|
1473
|
+
: undefined;
|
|
1474
|
+
const isStructuredMonitorOutput = taskId !== undefined || timeoutMs !== undefined || persistent !== undefined;
|
|
1475
|
+
if (!isStructuredMonitorOutput) {
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
const lines = [];
|
|
1479
|
+
if (taskId) {
|
|
1480
|
+
lines.push(`Task ID: ${taskId}`);
|
|
1481
|
+
}
|
|
1482
|
+
if (persistent !== undefined) {
|
|
1483
|
+
lines.push(`Persistent: ${booleanLabel(persistent)}`);
|
|
1484
|
+
}
|
|
1485
|
+
if (timeoutMs !== undefined && persistent !== true) {
|
|
1486
|
+
lines.push(`Timeout: ${formatDurationMilliseconds(timeoutMs)}`);
|
|
1487
|
+
}
|
|
1488
|
+
return {
|
|
1489
|
+
output: lines.length > 0 ? lines.join("\n") : undefined,
|
|
1490
|
+
taskId,
|
|
1491
|
+
failed: false,
|
|
1492
|
+
keepRunning: Boolean(taskId),
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
return undefined;
|
|
1496
|
+
}
|
|
1497
|
+
function workflowStatusLabel(value) {
|
|
1498
|
+
switch (value) {
|
|
1499
|
+
case "async_launched":
|
|
1500
|
+
return "async launched";
|
|
1501
|
+
case "remote_launched":
|
|
1502
|
+
return "remote launched";
|
|
1503
|
+
default:
|
|
1504
|
+
return nonEmptyString(value);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
function workflowResultFields(toolName, rawResult, rawContent) {
|
|
1508
|
+
if (toolName !== WORKFLOW_TOOL_NAME) {
|
|
1509
|
+
return undefined;
|
|
1510
|
+
}
|
|
1511
|
+
for (const candidate of collectResultCandidates(rawResult, rawContent)) {
|
|
1512
|
+
const taskId = nonEmptyString(candidate.taskId);
|
|
1513
|
+
const status = workflowStatusLabel(candidate.status);
|
|
1514
|
+
const error = nonEmptyString(candidate.error);
|
|
1515
|
+
const taskType = nonEmptyString(candidate.taskType);
|
|
1516
|
+
const workflowName = nonEmptyString(candidate.workflowName);
|
|
1517
|
+
const isStructuredWorkflowOutput = status !== undefined ||
|
|
1518
|
+
taskId !== undefined ||
|
|
1519
|
+
taskType !== undefined ||
|
|
1520
|
+
workflowName !== undefined ||
|
|
1521
|
+
"runId" in candidate ||
|
|
1522
|
+
"summary" in candidate ||
|
|
1523
|
+
"transcriptDir" in candidate ||
|
|
1524
|
+
"scriptPath" in candidate ||
|
|
1525
|
+
"sessionUrl" in candidate ||
|
|
1526
|
+
"warning" in candidate ||
|
|
1527
|
+
"error" in candidate;
|
|
1528
|
+
if (!isStructuredWorkflowOutput) {
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
const lines = [];
|
|
1532
|
+
if (status) {
|
|
1533
|
+
lines.push(`Status: ${status}`);
|
|
1534
|
+
}
|
|
1535
|
+
if (taskId) {
|
|
1536
|
+
lines.push(`Task ID: ${taskId}`);
|
|
1537
|
+
}
|
|
1538
|
+
if (taskType) {
|
|
1539
|
+
lines.push(`Task type: ${taskType}`);
|
|
1540
|
+
}
|
|
1541
|
+
if (workflowName) {
|
|
1542
|
+
lines.push(`Workflow name: ${workflowName}`);
|
|
1543
|
+
}
|
|
1544
|
+
const runId = nonEmptyString(candidate.runId);
|
|
1545
|
+
if (runId) {
|
|
1546
|
+
lines.push(`Run ID: ${runId}`);
|
|
1547
|
+
}
|
|
1548
|
+
const summary = nonEmptyString(candidate.summary);
|
|
1549
|
+
if (summary) {
|
|
1550
|
+
lines.push(`Summary: ${summary}`);
|
|
1551
|
+
}
|
|
1552
|
+
const transcriptDir = nonEmptyString(candidate.transcriptDir);
|
|
1553
|
+
if (transcriptDir) {
|
|
1554
|
+
lines.push(`Transcript dir: ${transcriptDir}`);
|
|
1555
|
+
}
|
|
1556
|
+
const scriptPath = nonEmptyString(candidate.scriptPath);
|
|
1557
|
+
if (scriptPath) {
|
|
1558
|
+
lines.push(`Script path: ${scriptPath}`);
|
|
1559
|
+
}
|
|
1560
|
+
const sessionUrl = nonEmptyString(candidate.sessionUrl);
|
|
1561
|
+
if (sessionUrl) {
|
|
1562
|
+
lines.push(`Session URL: ${sessionUrl}`);
|
|
1563
|
+
}
|
|
1564
|
+
const warning = nonEmptyString(candidate.warning);
|
|
1565
|
+
if (warning) {
|
|
1566
|
+
lines.push(`Warning: ${warning}`);
|
|
1567
|
+
}
|
|
1568
|
+
if (error) {
|
|
1569
|
+
lines.push(`Error: ${error}`);
|
|
1570
|
+
}
|
|
1571
|
+
return {
|
|
1572
|
+
output: lines.length > 0 ? lines.join("\n") : undefined,
|
|
1573
|
+
taskId,
|
|
1574
|
+
failed: Boolean(error),
|
|
1575
|
+
keepRunning: Boolean(taskId && !error),
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
return undefined;
|
|
1579
|
+
}
|
|
1580
|
+
function backgroundLaunchResultFields(toolName, rawResult, rawContent) {
|
|
1581
|
+
return (monitorResultFields(toolName, rawResult, rawContent) ??
|
|
1582
|
+
workflowResultFields(toolName, rawResult, rawContent));
|
|
1583
|
+
}
|
|
1584
|
+
export function backgroundToolLaunchTaskIdFromResult(toolName, rawResult, rawContent) {
|
|
1585
|
+
const result = backgroundLaunchResultFields(toolName, rawResult, rawContent);
|
|
1586
|
+
return result?.keepRunning ? result.taskId : undefined;
|
|
1587
|
+
}
|
|
1588
|
+
function enterPlanModeStructuredOutputHandled(toolName, rawResult, rawContent) {
|
|
1589
|
+
if (toolName !== ENTER_PLAN_MODE_TOOL_NAME) {
|
|
1590
|
+
return false;
|
|
1591
|
+
}
|
|
1592
|
+
const candidates = resultRecordCandidates(rawResult, rawContent);
|
|
1593
|
+
for (const parsed of [parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
|
|
1594
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
1595
|
+
}
|
|
1596
|
+
for (const candidate of candidates) {
|
|
1597
|
+
if (typeof candidate.message === "string") {
|
|
1598
|
+
return true;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1603
|
+
function readMcpResourceErrorText(toolName, rawResult, rawContent) {
|
|
1604
|
+
if (toolName !== "ReadMcpResource") {
|
|
1605
|
+
return undefined;
|
|
1606
|
+
}
|
|
1607
|
+
for (const candidate of collectResultCandidates(rawResult, rawContent)) {
|
|
1608
|
+
const error = nonEmptyString(candidate.error);
|
|
1609
|
+
if (error) {
|
|
1610
|
+
return `Error: ${error}`;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return undefined;
|
|
1614
|
+
}
|
|
1615
|
+
function webFetchResultText(toolName, rawResult, rawContent) {
|
|
1616
|
+
if (toolName !== "WebFetch") {
|
|
1617
|
+
return undefined;
|
|
1618
|
+
}
|
|
1619
|
+
for (const candidate of collectResultCandidates(rawResult, rawContent)) {
|
|
1620
|
+
const isStructuredWebFetchOutput = "result" in candidate ||
|
|
1621
|
+
"url" in candidate ||
|
|
1622
|
+
"code" in candidate ||
|
|
1623
|
+
"codeText" in candidate ||
|
|
1624
|
+
"bytes" in candidate ||
|
|
1625
|
+
"durationMs" in candidate ||
|
|
1626
|
+
"artifactRead" in candidate;
|
|
1627
|
+
if (!isStructuredWebFetchOutput) {
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
const result = nonEmptyString(candidate.result);
|
|
1631
|
+
if (result) {
|
|
1632
|
+
return result;
|
|
1633
|
+
}
|
|
1634
|
+
const lines = [];
|
|
1635
|
+
const url = nonEmptyString(candidate.url);
|
|
1636
|
+
if (url) {
|
|
1637
|
+
lines.push(`URL: ${url}`);
|
|
1638
|
+
}
|
|
1639
|
+
if (typeof candidate.code === "number" && Number.isFinite(candidate.code)) {
|
|
1640
|
+
const codeText = nonEmptyString(candidate.codeText);
|
|
1641
|
+
lines.push(`Status: ${candidate.code}${codeText ? ` ${codeText}` : ""}`);
|
|
1642
|
+
}
|
|
1643
|
+
if (typeof candidate.bytes === "number" && Number.isFinite(candidate.bytes)) {
|
|
1644
|
+
lines.push(`Bytes: ${Math.max(0, Math.trunc(candidate.bytes))}`);
|
|
1645
|
+
}
|
|
1646
|
+
if (typeof candidate.durationMs === "number" && Number.isFinite(candidate.durationMs)) {
|
|
1647
|
+
lines.push(`Duration: ${Math.max(0, Math.trunc(candidate.durationMs))}ms`);
|
|
1648
|
+
}
|
|
1649
|
+
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
1650
|
+
}
|
|
1651
|
+
return undefined;
|
|
1652
|
+
}
|
|
1653
|
+
export function buildToolResultFields(isError, rawContent, base, rawResult, _context = {}) {
|
|
545
1654
|
const toolName = resolveToolName(base);
|
|
546
1655
|
const fields = {
|
|
547
1656
|
status: isError ? "failed" : "completed",
|
|
548
1657
|
};
|
|
1658
|
+
const outputMetadata = extractToolOutputMetadata(toolName, rawResult, rawContent);
|
|
1659
|
+
if (outputMetadata) {
|
|
1660
|
+
fields.output_metadata = outputMetadata;
|
|
1661
|
+
}
|
|
549
1662
|
const fileUnchangedText = !isError && toolName === "Read" ? fileUnchangedResultText(rawResult, rawContent) : "";
|
|
550
1663
|
if (fileUnchangedText) {
|
|
551
1664
|
fields.raw_output = fileUnchangedText;
|
|
552
1665
|
fields.content = [{ type: "content", content: { type: "text", text: fileUnchangedText } }];
|
|
553
1666
|
return fields;
|
|
554
1667
|
}
|
|
555
|
-
const agentTitle = !isError && toolName === "Agent"
|
|
1668
|
+
const agentTitle = !isError && toolName === "Agent"
|
|
1669
|
+
? agentTitleFromAgentOutput(rawResult, rawContent, base)
|
|
1670
|
+
: "";
|
|
556
1671
|
if (agentTitle) {
|
|
557
1672
|
fields.title = agentTitle;
|
|
558
1673
|
}
|
|
1674
|
+
const readMcpResourceError = readMcpResourceErrorText(toolName, rawResult, rawContent);
|
|
1675
|
+
if (readMcpResourceError) {
|
|
1676
|
+
fields.status = "failed";
|
|
1677
|
+
fields.raw_output = readMcpResourceError;
|
|
1678
|
+
fields.content = [{ type: "content", content: { type: "text", text: readMcpResourceError } }];
|
|
1679
|
+
return fields;
|
|
1680
|
+
}
|
|
1681
|
+
const searchOutput = !isError ? searchResultText(toolName, rawResult, rawContent) : undefined;
|
|
1682
|
+
if (searchOutput !== undefined) {
|
|
1683
|
+
fields.raw_output = searchOutput;
|
|
1684
|
+
fields.content = [{ type: "content", content: { type: "text", text: searchOutput } }];
|
|
1685
|
+
return fields;
|
|
1686
|
+
}
|
|
1687
|
+
const webFetchOutput = !isError ? webFetchResultText(toolName, rawResult, rawContent) : undefined;
|
|
1688
|
+
if (webFetchOutput !== undefined) {
|
|
1689
|
+
fields.raw_output = webFetchOutput;
|
|
1690
|
+
fields.content = [{ type: "content", content: { type: "text", text: webFetchOutput } }];
|
|
1691
|
+
return fields;
|
|
1692
|
+
}
|
|
1693
|
+
const worktreeOutput = !isError
|
|
1694
|
+
? worktreeResultFields(toolName, rawResult, rawContent)
|
|
1695
|
+
: undefined;
|
|
1696
|
+
if (worktreeOutput) {
|
|
1697
|
+
if (worktreeOutput.output) {
|
|
1698
|
+
fields.raw_output = worktreeOutput.output;
|
|
1699
|
+
fields.content = [
|
|
1700
|
+
{ type: "content", content: { type: "text", text: worktreeOutput.output } },
|
|
1701
|
+
];
|
|
1702
|
+
}
|
|
1703
|
+
return fields;
|
|
1704
|
+
}
|
|
1705
|
+
const cronOutput = !isError
|
|
1706
|
+
? cronResultText(toolName, rawResult, rawContent, base?.raw_input)
|
|
1707
|
+
: undefined;
|
|
1708
|
+
if (cronOutput !== undefined) {
|
|
1709
|
+
fields.raw_output = cronOutput;
|
|
1710
|
+
fields.content = [{ type: "content", content: { type: "text", text: cronOutput } }];
|
|
1711
|
+
return fields;
|
|
1712
|
+
}
|
|
1713
|
+
const scheduleWakeupOutput = !isError
|
|
1714
|
+
? scheduleWakeupResultText(toolName, rawResult, rawContent)
|
|
1715
|
+
: undefined;
|
|
1716
|
+
if (scheduleWakeupOutput !== undefined) {
|
|
1717
|
+
fields.raw_output = scheduleWakeupOutput;
|
|
1718
|
+
fields.content = [
|
|
1719
|
+
{ type: "content", content: { type: "text", text: scheduleWakeupOutput } },
|
|
1720
|
+
];
|
|
1721
|
+
return fields;
|
|
1722
|
+
}
|
|
1723
|
+
const pushNotificationOutput = !isError
|
|
1724
|
+
? pushNotificationResultText(toolName, rawResult, rawContent, base?.raw_input)
|
|
1725
|
+
: undefined;
|
|
1726
|
+
if (pushNotificationOutput !== undefined) {
|
|
1727
|
+
fields.raw_output = pushNotificationOutput;
|
|
1728
|
+
fields.content = [
|
|
1729
|
+
{ type: "content", content: { type: "text", text: pushNotificationOutput } },
|
|
1730
|
+
];
|
|
1731
|
+
return fields;
|
|
1732
|
+
}
|
|
1733
|
+
if (!isError &&
|
|
1734
|
+
enterPlanModeStructuredOutputHandled(toolName, rawResult, rawContent)) {
|
|
1735
|
+
return fields;
|
|
1736
|
+
}
|
|
1737
|
+
const remoteTriggerOutput = remoteTriggerResultFields(toolName, rawResult, rawContent);
|
|
1738
|
+
if (remoteTriggerOutput !== undefined) {
|
|
1739
|
+
if (remoteTriggerOutput.failed) {
|
|
1740
|
+
fields.status = "failed";
|
|
1741
|
+
}
|
|
1742
|
+
if (remoteTriggerOutput.output) {
|
|
1743
|
+
fields.raw_output = remoteTriggerOutput.output;
|
|
1744
|
+
fields.content = [
|
|
1745
|
+
{ type: "content", content: { type: "text", text: remoteTriggerOutput.output } },
|
|
1746
|
+
];
|
|
1747
|
+
}
|
|
1748
|
+
return fields;
|
|
1749
|
+
}
|
|
1750
|
+
const replOutput = replResultFields(toolName, rawResult, rawContent);
|
|
1751
|
+
if (replOutput !== undefined) {
|
|
1752
|
+
if (replOutput.failed) {
|
|
1753
|
+
fields.status = "failed";
|
|
1754
|
+
}
|
|
1755
|
+
if (replOutput.output) {
|
|
1756
|
+
fields.raw_output = replOutput.output;
|
|
1757
|
+
fields.content = [
|
|
1758
|
+
{ type: "content", content: { type: "text", text: replOutput.output } },
|
|
1759
|
+
];
|
|
1760
|
+
}
|
|
1761
|
+
return fields;
|
|
1762
|
+
}
|
|
1763
|
+
const backgroundLaunchOutput = !isError
|
|
1764
|
+
? backgroundLaunchResultFields(toolName, rawResult, rawContent)
|
|
1765
|
+
: undefined;
|
|
1766
|
+
if (backgroundLaunchOutput !== undefined) {
|
|
1767
|
+
fields.status = backgroundLaunchOutput.failed
|
|
1768
|
+
? "failed"
|
|
1769
|
+
: backgroundLaunchOutput.keepRunning
|
|
1770
|
+
? "in_progress"
|
|
1771
|
+
: "completed";
|
|
1772
|
+
if (backgroundLaunchOutput.output) {
|
|
1773
|
+
fields.raw_output = backgroundLaunchOutput.output;
|
|
1774
|
+
fields.content = [
|
|
1775
|
+
{ type: "content", content: { type: "text", text: backgroundLaunchOutput.output } },
|
|
1776
|
+
];
|
|
1777
|
+
}
|
|
1778
|
+
return fields;
|
|
1779
|
+
}
|
|
559
1780
|
const bashResultRecord = toolName === "Bash" ? findBashResultRecord(rawResult, rawContent) : undefined;
|
|
560
1781
|
const normalizedRawOutput = normalizeToolResultText(rawContent, isError);
|
|
561
1782
|
const rawOutput = bashResultRecord
|
|
562
1783
|
? buildBashDisplayOutput(bashResultRecord)
|
|
563
1784
|
: normalizedRawOutput || JSON.stringify(rawContent);
|
|
564
|
-
if (rawOutput) {
|
|
1785
|
+
if (rawOutput && !(isTaskToolName(toolName) && !isError)) {
|
|
565
1786
|
fields.raw_output = rawOutput;
|
|
566
1787
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1788
|
+
if (!isError && isTaskToolName(toolName)) {
|
|
1789
|
+
if (toolName === "TaskUpdate" && taskUpdateSucceeded(rawResult, rawContent) === false) {
|
|
1790
|
+
fields.status = "failed";
|
|
1791
|
+
}
|
|
1792
|
+
const taskOutput = taskToolResultText(toolName, rawResult, rawContent, base?.raw_input);
|
|
1793
|
+
if (taskOutput) {
|
|
1794
|
+
fields.content = [{ type: "content", content: { type: "text", text: taskOutput } }];
|
|
1795
|
+
return fields;
|
|
1796
|
+
}
|
|
1797
|
+
if (toolName === "TaskCreate" || toolName === "TaskUpdate" || toolName === "TaskOutput" || toolName === "TaskStop") {
|
|
1798
|
+
return fields;
|
|
1799
|
+
}
|
|
570
1800
|
}
|
|
571
1801
|
if (!isError && toolName === "Write") {
|
|
572
1802
|
const structuredDiff = writeDiffFromResult(rawContent);
|