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.
@@ -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 "TodoWrite":
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
- if (name === "Bash") {
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
- const pattern = typeof input.pattern === "string" ? input.pattern : "";
60
- const path = typeof input.path === "string" ? input.path : "";
61
- if (pattern && path) {
62
- return `Glob ${pattern} in ${path}`;
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 = resultRecordCandidates(rawResult, rawContent);
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
- return {
260
- bash: bashMetadata,
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 === "TodoWrite") {
469
+ if (toolName === "WebFetch") {
267
470
  for (const candidate of candidates) {
268
- if (typeof candidate.verificationNudgeNeeded === "boolean") {
269
- return {
270
- todo_write: { verification_nudge_needed: candidate.verificationNudgeNeeded },
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 findBashResultRecord(rawResult, rawContent) {
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 bashBackgroundMessage(record) {
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 buildBashDisplayOutput(record) {
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 = bashBackgroundMessage(record);
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
- export function buildToolResultFields(isError, rawContent, base, rawResult) {
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" ? agentTitleFromAgentOutput(rawResult, rawContent) : "";
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 bashResultRecord = toolName === "Bash" ? findBashResultRecord(rawResult, rawContent) : undefined;
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 = bashResultRecord
562
- ? buildBashDisplayOutput(bashResultRecord)
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
- const outputMetadata = extractToolOutputMetadata(toolName, rawResult, rawContent);
568
- if (outputMetadata) {
569
- fields.output_metadata = outputMetadata;
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);