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