claude-code-rust 0.12.0 → 0.12.2

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