@poolzin/pool-bot 2026.3.25 → 2026.3.27

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.
Files changed (271) hide show
  1. package/dist/agents/model-fallback.js +5 -4
  2. package/dist/agents/tools/common.js +16 -201
  3. package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
  4. package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
  5. package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
  6. package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
  7. package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
  8. package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
  9. package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
  10. package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
  11. package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
  12. package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
  13. package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
  14. package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
  15. package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
  16. package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
  17. package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
  18. package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
  19. package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
  20. package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
  21. package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
  22. package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
  23. package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
  24. package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
  25. package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
  26. package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
  27. package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
  28. package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
  29. package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
  30. package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
  31. package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
  32. package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
  33. package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
  34. package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
  35. package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
  36. package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
  37. package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
  38. package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
  39. package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
  40. package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
  41. package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
  42. package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
  43. package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
  44. package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
  45. package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
  46. package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
  47. package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
  48. package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
  49. package/dist/build-info.json +3 -3
  50. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  51. package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
  52. package/dist/canvas-host/a2ui/index.html +1 -307
  53. package/dist/channels/channels/directory-config.js +185 -0
  54. package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
  55. package/dist/channels/channels/discord/handle-action.js +165 -0
  56. package/dist/channels/channels/discord.js +413 -0
  57. package/dist/channels/channels/dock.js +436 -0
  58. package/dist/channels/channels/index.js +51 -0
  59. package/dist/channels/channels/plugins/outbound/discord.js +101 -0
  60. package/dist/channels/channels/whatsapp.js +17 -0
  61. package/dist/channels/plugins/types.js +1 -1
  62. package/dist/channels/run-state-machine.js +7 -0
  63. package/dist/commands/models/auth.js +47 -1
  64. package/dist/commands-subagents/action-agents.js +44 -0
  65. package/dist/commands-subagents/action-focus.js +64 -0
  66. package/dist/commands-subagents/action-help.js +4 -0
  67. package/dist/commands-subagents/action-info.js +45 -0
  68. package/dist/commands-subagents/action-kill.js +60 -0
  69. package/dist/commands-subagents/action-list.js +44 -0
  70. package/dist/commands-subagents/action-log.js +29 -0
  71. package/dist/commands-subagents/action-send.js +119 -0
  72. package/dist/commands-subagents/action-spawn.js +52 -0
  73. package/dist/commands-subagents/action-unfocus.js +30 -0
  74. package/dist/commands-subagents/shared.js +303 -0
  75. package/dist/config/config.js +1 -8
  76. package/dist/config/types.secrets.js +61 -0
  77. package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
  78. package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
  79. package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
  80. package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
  81. package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
  82. package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
  83. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
  84. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
  85. package/dist/control-ui/index.html +1 -1
  86. package/dist/gateway/method-scopes.js +9 -1
  87. package/dist/gateway/node-pending-work.js +142 -0
  88. package/dist/gateway/protocol/index.js +5 -1
  89. package/dist/gateway/protocol/schema/nodes.js +18 -0
  90. package/dist/gateway/server-methods/nodes-pending.js +96 -0
  91. package/dist/gateway/server-methods-list.js +4 -0
  92. package/dist/gateway/server-methods.js +2 -0
  93. package/dist/imessage/channel.js +253 -0
  94. package/dist/imessage/monitor/echo-cache.js +70 -0
  95. package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
  96. package/dist/imessage/monitor/reflection-guard.js +50 -0
  97. package/dist/imessage/monitor/sanitize-outbound.js +25 -0
  98. package/dist/imessage/monitor/self-chat-cache.js +75 -0
  99. package/dist/imessage/runtime.js +3 -0
  100. package/dist/infra/exec-approval-reply.js +7 -0
  101. package/dist/infra/tmp-openclaw-dir.js +84 -0
  102. package/dist/pairing/pairing-challenge.js +15 -0
  103. package/dist/plugin-sdk/account-id.d.ts +1 -0
  104. package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
  105. package/dist/plugin-sdk/allow-from.d.ts +27 -0
  106. package/dist/plugin-sdk/command-auth.d.ts +25 -0
  107. package/dist/plugin-sdk/command-auth.js +3 -1
  108. package/dist/plugin-sdk/config-paths.d.ts +6 -0
  109. package/dist/plugin-sdk/file-lock.d.ts +16 -0
  110. package/dist/plugin-sdk/index.d.ts +428 -0
  111. package/dist/plugin-sdk/index.js +237 -103
  112. package/dist/plugin-sdk/json-store.d.ts +5 -0
  113. package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
  114. package/dist/plugin-sdk/onboarding.d.ts +11 -0
  115. package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
  116. package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
  117. package/dist/plugin-sdk/status-helpers.d.ts +25 -0
  118. package/dist/plugin-sdk/temp-path.d.ts +12 -0
  119. package/dist/plugin-sdk/text-chunking.d.ts +1 -0
  120. package/dist/plugin-sdk/tool-send.d.ts +4 -0
  121. package/dist/plugin-sdk/webhook-path.d.ts +6 -0
  122. package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
  123. package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
  124. package/dist/plugin-sdk-internal/accounts.js +6 -0
  125. package/dist/plugin-sdk-internal/discord.js +23 -0
  126. package/dist/plugin-sdk-internal/imessage.js +13 -0
  127. package/dist/plugin-sdk-internal/setup.js +9 -0
  128. package/dist/plugin-sdk-internal/signal.js +13 -0
  129. package/dist/plugin-sdk-internal/slack.js +22 -0
  130. package/dist/plugin-sdk-internal/telegram.js +32 -0
  131. package/dist/plugin-sdk-internal/whatsapp.js +29 -0
  132. package/dist/routing/session-key.js +4 -185
  133. package/dist/shared/pid-alive.js +2 -61
  134. package/dist/shared/process-scoped-map.js +5 -7
  135. package/dist/signal/channel.js +264 -0
  136. package/dist/signal/monitor/access-policy.js +60 -0
  137. package/dist/signal/runtime.js +3 -0
  138. package/dist/slack/account-inspect.js +135 -0
  139. package/dist/slack/blocks-input.js +7 -38
  140. package/dist/slack/channel.js +394 -0
  141. package/dist/slack/interactive-replies.js +28 -0
  142. package/dist/slack/monitor/channel-type.js +31 -0
  143. package/dist/slack/monitor/dm-auth.js +49 -0
  144. package/dist/slack/monitor/events/interactions.modal.js +137 -0
  145. package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
  146. package/dist/slack/monitor/events/system-event-context.js +29 -0
  147. package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
  148. package/dist/slack/monitor/external-arg-menu-store.js +46 -0
  149. package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
  150. package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
  151. package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
  152. package/dist/slack/monitor/reconnect-policy.js +78 -0
  153. package/dist/slack/monitor/slash-commands.runtime.js +1 -0
  154. package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
  155. package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
  156. package/dist/slack/resolve-allowlist-common.js +36 -0
  157. package/dist/slack/runtime.js +3 -0
  158. package/dist/slack/sent-thread-cache.js +61 -0
  159. package/dist/slack/truncate.js +10 -0
  160. package/dist/telegram/account-inspect.js +175 -0
  161. package/dist/telegram/allow-from.js +10 -0
  162. package/dist/telegram/api-fetch.js +18 -0
  163. package/dist/telegram/approval-buttons.js +30 -0
  164. package/dist/telegram/audit-membership-runtime.js +61 -0
  165. package/dist/telegram/bot/delivery.replies.js +508 -0
  166. package/dist/telegram/bot/delivery.resolve-media.js +227 -0
  167. package/dist/telegram/bot/delivery.send.js +132 -0
  168. package/dist/telegram/bot/reply-threading.js +46 -0
  169. package/dist/telegram/bot-message-context.body.js +186 -0
  170. package/dist/telegram/bot-message-context.session.js +207 -0
  171. package/dist/telegram/bot-message-context.types.js +1 -0
  172. package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
  173. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  174. package/dist/telegram/bot.media.test-utils.js +81 -0
  175. package/dist/telegram/channel-actions.js +225 -0
  176. package/dist/telegram/channel.js +515 -0
  177. package/dist/telegram/conversation-route.js +107 -0
  178. package/dist/telegram/delivery.js +2 -0
  179. package/dist/telegram/delivery.replies.js +508 -0
  180. package/dist/telegram/dm-access.js +86 -0
  181. package/dist/telegram/draft-stream.test-helpers.js +62 -0
  182. package/dist/telegram/exec-approvals-handler.js +281 -0
  183. package/dist/telegram/exec-approvals.js +62 -0
  184. package/dist/telegram/forum-service-message.js +22 -0
  185. package/dist/telegram/group-config-helpers.js +10 -0
  186. package/dist/telegram/lane-delivery-state.js +19 -0
  187. package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
  188. package/dist/telegram/lane-delivery.js +2 -0
  189. package/dist/telegram/normalize.js +37 -0
  190. package/dist/telegram/onboarding.js +192 -0
  191. package/dist/telegram/outbound-adapter.js +100 -0
  192. package/dist/telegram/polling-session.js +275 -0
  193. package/dist/telegram/runtime.js +3 -0
  194. package/dist/telegram/sendchataction-401-backoff.js +71 -0
  195. package/dist/telegram/sequential-key.js +46 -0
  196. package/dist/telegram/status-issues.js +105 -0
  197. package/dist/telegram/target-writeback.js +165 -0
  198. package/dist/telegram/thread-bindings.js +560 -0
  199. package/dist/utils.js +32 -257
  200. package/dist/wizard/prompts.js +5 -5
  201. package/extensions/feishu/src/policy.ts +1 -1
  202. package/extensions/firecrawl/index.test.ts +82 -0
  203. package/extensions/firecrawl/index.ts +20 -0
  204. package/extensions/firecrawl/openclaw.plugin.json +8 -0
  205. package/extensions/firecrawl/package.json +12 -0
  206. package/extensions/firecrawl/src/config.ts +159 -0
  207. package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
  208. package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
  209. package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
  210. package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
  211. package/package.json +1 -1
  212. package/dist/.buildstamp +0 -1
  213. package/dist/acp/bindings-store.js +0 -209
  214. package/dist/acp/control-plane/runtime-cache.js +0 -54
  215. package/dist/acp/control-plane/runtime-options.js +0 -215
  216. package/dist/acp/control-plane/session-actor-queue.js +0 -36
  217. package/dist/acp/index.js +0 -2
  218. package/dist/acp/runtime/errors.js +0 -47
  219. package/dist/acp/runtime/registry.js +0 -86
  220. package/dist/acp/secret-file.js +0 -22
  221. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
  222. package/dist/agents/bash-process-registry.test-helpers.js +0 -29
  223. package/dist/agents/bash-tools.exec-approval-request.js +0 -20
  224. package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
  225. package/dist/agents/bash-tools.exec-host-node.js +0 -235
  226. package/dist/agents/checkpoint-manager.js +0 -290
  227. package/dist/agents/claude-cli-runner.js +0 -3
  228. package/dist/agents/error-classifier.js +0 -251
  229. package/dist/agents/live-model-filter.js +0 -84
  230. package/dist/agents/nvidia-models.js +0 -228
  231. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
  232. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
  233. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
  234. package/dist/agents/provider/config-loader.js +0 -76
  235. package/dist/agents/provider/index.js +0 -15
  236. package/dist/agents/provider/models-dev.js +0 -129
  237. package/dist/agents/provider/session-binding.js +0 -376
  238. package/dist/agents/queued-file-writer.js +0 -22
  239. package/dist/agents/skills/bundled-context.js +0 -23
  240. package/dist/agents/skills/security.js +0 -211
  241. package/dist/agents/skills/tools-dir.js +0 -9
  242. package/dist/agents/skills-install-download.js +0 -290
  243. package/dist/agents/skills-install-output.js +0 -30
  244. package/dist/agents/skills-install.download-test-utils.js +0 -36
  245. package/dist/agents/skills.test-helpers.js +0 -13
  246. package/dist/agents/subagent-announce-reliability.js +0 -160
  247. package/dist/agents/subagent-registry.mocks.shared.js +0 -12
  248. package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
  249. package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
  250. package/dist/agents/test-helpers/fast-core-tools.js +0 -8
  251. package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
  252. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
  253. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
  254. package/dist/agents/tool-display-common.js +0 -915
  255. package/dist/agents/tool-policy-shared.js +0 -108
  256. package/dist/agents/tool-policy.conformance.js +0 -14
  257. package/dist/agents/tool-result-truncation.js +0 -299
  258. package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
  259. package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
  260. package/dist/agents/tools/discord-actions-presence.js +0 -78
  261. package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
  262. package/dist/discord/discord-improvements.js +0 -167
  263. package/dist/discord/index.js +0 -2
  264. package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
  265. package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
  266. package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
  267. package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
  268. package/dist/whatsapp/normalize.js +0 -66
  269. package/dist/whatsapp/resolve-outbound-target.js +0 -42
  270. /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
  271. /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
@@ -1,915 +0,0 @@
1
- function asRecord(args) {
2
- return args && typeof args === "object" ? args : undefined;
3
- }
4
- export function normalizeToolName(name) {
5
- return (name ?? "tool").trim();
6
- }
7
- export function defaultTitle(name) {
8
- const cleaned = name.replace(/_/g, " ").trim();
9
- if (!cleaned) {
10
- return "Tool";
11
- }
12
- return cleaned
13
- .split(/\s+/)
14
- .map((part) => part.length <= 2 && part.toUpperCase() === part
15
- ? part
16
- : `${part.at(0)?.toUpperCase() ?? ""}${part.slice(1)}`)
17
- .join(" ");
18
- }
19
- export function normalizeVerb(value) {
20
- const trimmed = value?.trim();
21
- if (!trimmed) {
22
- return undefined;
23
- }
24
- return trimmed.replace(/_/g, " ");
25
- }
26
- export function coerceDisplayValue(value, opts = {}) {
27
- const maxStringChars = opts.maxStringChars ?? 160;
28
- const maxArrayEntries = opts.maxArrayEntries ?? 3;
29
- if (value === null || value === undefined) {
30
- return undefined;
31
- }
32
- if (typeof value === "string") {
33
- const trimmed = value.trim();
34
- if (!trimmed) {
35
- return undefined;
36
- }
37
- const firstLine = trimmed.split(/\r?\n/)[0]?.trim() ?? "";
38
- if (!firstLine) {
39
- return undefined;
40
- }
41
- if (firstLine.length > maxStringChars) {
42
- return `${firstLine.slice(0, Math.max(0, maxStringChars - 3))}…`;
43
- }
44
- return firstLine;
45
- }
46
- if (typeof value === "boolean") {
47
- if (!value && !opts.includeFalse) {
48
- return undefined;
49
- }
50
- return value ? "true" : "false";
51
- }
52
- if (typeof value === "number") {
53
- if (!Number.isFinite(value)) {
54
- return opts.includeNonFinite ? String(value) : undefined;
55
- }
56
- if (value === 0 && !opts.includeZero) {
57
- return undefined;
58
- }
59
- return String(value);
60
- }
61
- if (Array.isArray(value)) {
62
- const values = value
63
- .map((item) => coerceDisplayValue(item, opts))
64
- .filter((item) => Boolean(item));
65
- if (values.length === 0) {
66
- return undefined;
67
- }
68
- const preview = values.slice(0, maxArrayEntries).join(", ");
69
- return values.length > maxArrayEntries ? `${preview}…` : preview;
70
- }
71
- return undefined;
72
- }
73
- export function lookupValueByPath(args, path) {
74
- if (!args || typeof args !== "object") {
75
- return undefined;
76
- }
77
- let current = args;
78
- for (const segment of path.split(".")) {
79
- if (!segment) {
80
- return undefined;
81
- }
82
- if (!current || typeof current !== "object") {
83
- return undefined;
84
- }
85
- const record = current;
86
- current = record[segment];
87
- }
88
- return current;
89
- }
90
- export function formatDetailKey(raw, overrides = {}) {
91
- const segments = raw.split(".").filter(Boolean);
92
- const last = segments.at(-1) ?? raw;
93
- const override = overrides[last];
94
- if (override) {
95
- return override;
96
- }
97
- const cleaned = last.replace(/_/g, " ").replace(/-/g, " ");
98
- const spaced = cleaned.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
99
- return spaced.trim().toLowerCase() || last.toLowerCase();
100
- }
101
- export function resolvePathArg(args) {
102
- const record = asRecord(args);
103
- if (!record) {
104
- return undefined;
105
- }
106
- for (const candidate of [record.path, record.file_path, record.filePath]) {
107
- if (typeof candidate !== "string") {
108
- continue;
109
- }
110
- const trimmed = candidate.trim();
111
- if (trimmed) {
112
- return trimmed;
113
- }
114
- }
115
- return undefined;
116
- }
117
- export function resolveReadDetail(args) {
118
- const record = asRecord(args);
119
- if (!record) {
120
- return undefined;
121
- }
122
- const path = resolvePathArg(record);
123
- if (!path) {
124
- return undefined;
125
- }
126
- const offsetRaw = typeof record.offset === "number" && Number.isFinite(record.offset)
127
- ? Math.floor(record.offset)
128
- : undefined;
129
- const limitRaw = typeof record.limit === "number" && Number.isFinite(record.limit)
130
- ? Math.floor(record.limit)
131
- : undefined;
132
- const offset = offsetRaw !== undefined ? Math.max(1, offsetRaw) : undefined;
133
- const limit = limitRaw !== undefined ? Math.max(1, limitRaw) : undefined;
134
- if (offset !== undefined && limit !== undefined) {
135
- const unit = limit === 1 ? "line" : "lines";
136
- return `${unit} ${offset}-${offset + limit - 1} from ${path}`;
137
- }
138
- if (offset !== undefined) {
139
- return `from line ${offset} in ${path}`;
140
- }
141
- if (limit !== undefined) {
142
- const unit = limit === 1 ? "line" : "lines";
143
- return `first ${limit} ${unit} of ${path}`;
144
- }
145
- return `from ${path}`;
146
- }
147
- export function resolveWriteDetail(toolKey, args) {
148
- const record = asRecord(args);
149
- if (!record) {
150
- return undefined;
151
- }
152
- const path = resolvePathArg(record) ?? (typeof record.url === "string" ? record.url.trim() : undefined);
153
- if (!path) {
154
- return undefined;
155
- }
156
- if (toolKey === "attach") {
157
- return `from ${path}`;
158
- }
159
- const destinationPrefix = toolKey === "edit" ? "in" : "to";
160
- const content = typeof record.content === "string"
161
- ? record.content
162
- : typeof record.newText === "string"
163
- ? record.newText
164
- : typeof record.new_string === "string"
165
- ? record.new_string
166
- : undefined;
167
- if (content && content.length > 0) {
168
- return `${destinationPrefix} ${path} (${content.length} chars)`;
169
- }
170
- return `${destinationPrefix} ${path}`;
171
- }
172
- export function resolveWebSearchDetail(args) {
173
- const record = asRecord(args);
174
- if (!record) {
175
- return undefined;
176
- }
177
- const query = typeof record.query === "string" ? record.query.trim() : undefined;
178
- const count = typeof record.count === "number" && Number.isFinite(record.count) && record.count > 0
179
- ? Math.floor(record.count)
180
- : undefined;
181
- if (!query) {
182
- return undefined;
183
- }
184
- return count !== undefined ? `for "${query}" (top ${count})` : `for "${query}"`;
185
- }
186
- export function resolveWebFetchDetail(args) {
187
- const record = asRecord(args);
188
- if (!record) {
189
- return undefined;
190
- }
191
- const url = typeof record.url === "string" ? record.url.trim() : undefined;
192
- if (!url) {
193
- return undefined;
194
- }
195
- const mode = typeof record.extractMode === "string" ? record.extractMode.trim() : undefined;
196
- const maxChars = typeof record.maxChars === "number" && Number.isFinite(record.maxChars) && record.maxChars > 0
197
- ? Math.floor(record.maxChars)
198
- : undefined;
199
- const suffix = [
200
- mode ? `mode ${mode}` : undefined,
201
- maxChars !== undefined ? `max ${maxChars} chars` : undefined,
202
- ]
203
- .filter((value) => Boolean(value))
204
- .join(", ");
205
- return suffix ? `from ${url} (${suffix})` : `from ${url}`;
206
- }
207
- function stripOuterQuotes(value) {
208
- if (!value) {
209
- return value;
210
- }
211
- const trimmed = value.trim();
212
- if (trimmed.length >= 2 &&
213
- ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
214
- (trimmed.startsWith("'") && trimmed.endsWith("'")))) {
215
- return trimmed.slice(1, -1).trim();
216
- }
217
- return trimmed;
218
- }
219
- function splitShellWords(input, maxWords = 48) {
220
- if (!input) {
221
- return [];
222
- }
223
- const words = [];
224
- let current = "";
225
- let quote;
226
- let escaped = false;
227
- for (let i = 0; i < input.length; i += 1) {
228
- const char = input[i];
229
- if (escaped) {
230
- current += char;
231
- escaped = false;
232
- continue;
233
- }
234
- if (char === "\\") {
235
- escaped = true;
236
- continue;
237
- }
238
- if (quote) {
239
- if (char === quote) {
240
- quote = undefined;
241
- }
242
- else {
243
- current += char;
244
- }
245
- continue;
246
- }
247
- if (char === '"' || char === "'") {
248
- quote = char;
249
- continue;
250
- }
251
- if (/\s/.test(char)) {
252
- if (!current) {
253
- continue;
254
- }
255
- words.push(current);
256
- if (words.length >= maxWords) {
257
- return words;
258
- }
259
- current = "";
260
- continue;
261
- }
262
- current += char;
263
- }
264
- if (current) {
265
- words.push(current);
266
- }
267
- return words;
268
- }
269
- function binaryName(token) {
270
- if (!token) {
271
- return undefined;
272
- }
273
- const cleaned = stripOuterQuotes(token) ?? token;
274
- const segment = cleaned.split(/[/]/).at(-1) ?? cleaned;
275
- return segment.trim().toLowerCase();
276
- }
277
- function optionValue(words, names) {
278
- const lookup = new Set(names);
279
- for (let i = 0; i < words.length; i += 1) {
280
- const token = words[i];
281
- if (!token) {
282
- continue;
283
- }
284
- if (lookup.has(token)) {
285
- const value = words[i + 1];
286
- if (value && !value.startsWith("-")) {
287
- return value;
288
- }
289
- continue;
290
- }
291
- for (const name of names) {
292
- if (name.startsWith("--") && token.startsWith(`${name}=`)) {
293
- return token.slice(name.length + 1);
294
- }
295
- }
296
- }
297
- return undefined;
298
- }
299
- function positionalArgs(words, from = 1, optionsWithValue = []) {
300
- const args = [];
301
- const takesValue = new Set(optionsWithValue);
302
- for (let i = from; i < words.length; i += 1) {
303
- const token = words[i];
304
- if (!token) {
305
- continue;
306
- }
307
- if (token === "--") {
308
- for (let j = i + 1; j < words.length; j += 1) {
309
- const candidate = words[j];
310
- if (candidate) {
311
- args.push(candidate);
312
- }
313
- }
314
- break;
315
- }
316
- if (token.startsWith("--")) {
317
- if (token.includes("=")) {
318
- continue;
319
- }
320
- if (takesValue.has(token)) {
321
- i += 1;
322
- }
323
- continue;
324
- }
325
- if (token.startsWith("-")) {
326
- if (takesValue.has(token)) {
327
- i += 1;
328
- }
329
- continue;
330
- }
331
- args.push(token);
332
- }
333
- return args;
334
- }
335
- function firstPositional(words, from = 1, optionsWithValue = []) {
336
- return positionalArgs(words, from, optionsWithValue)[0];
337
- }
338
- function trimLeadingEnv(words) {
339
- if (words.length === 0) {
340
- return words;
341
- }
342
- let index = 0;
343
- if (binaryName(words[0]) === "env") {
344
- index = 1;
345
- while (index < words.length) {
346
- const token = words[index];
347
- if (!token) {
348
- break;
349
- }
350
- if (token.startsWith("-")) {
351
- index += 1;
352
- continue;
353
- }
354
- if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)) {
355
- index += 1;
356
- continue;
357
- }
358
- break;
359
- }
360
- return words.slice(index);
361
- }
362
- while (index < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[index])) {
363
- index += 1;
364
- }
365
- return words.slice(index);
366
- }
367
- function unwrapShellWrapper(command) {
368
- const words = splitShellWords(command, 10);
369
- if (words.length < 3) {
370
- return command;
371
- }
372
- const bin = binaryName(words[0]);
373
- if (!(bin === "bash" || bin === "sh" || bin === "zsh" || bin === "fish")) {
374
- return command;
375
- }
376
- const flagIndex = words.findIndex((token, index) => index > 0 && (token === "-c" || token === "-lc" || token === "-ic"));
377
- if (flagIndex === -1) {
378
- return command;
379
- }
380
- const inner = words
381
- .slice(flagIndex + 1)
382
- .join(" ")
383
- .trim();
384
- return inner ? (stripOuterQuotes(inner) ?? command) : command;
385
- }
386
- function scanTopLevelChars(command, visit) {
387
- let quote;
388
- let escaped = false;
389
- for (let i = 0; i < command.length; i += 1) {
390
- const char = command[i];
391
- if (escaped) {
392
- escaped = false;
393
- continue;
394
- }
395
- if (char === "\\") {
396
- escaped = true;
397
- continue;
398
- }
399
- if (quote) {
400
- if (char === quote) {
401
- quote = undefined;
402
- }
403
- continue;
404
- }
405
- if (char === '"' || char === "'") {
406
- quote = char;
407
- continue;
408
- }
409
- if (visit(char, i) === false) {
410
- return;
411
- }
412
- }
413
- }
414
- function splitTopLevelStages(command) {
415
- const parts = [];
416
- let start = 0;
417
- scanTopLevelChars(command, (char, index) => {
418
- if (char === ";") {
419
- parts.push(command.slice(start, index));
420
- start = index + 1;
421
- return true;
422
- }
423
- if ((char === "&" || char === "|") && command[index + 1] === char) {
424
- parts.push(command.slice(start, index));
425
- start = index + 2;
426
- return true;
427
- }
428
- return true;
429
- });
430
- parts.push(command.slice(start));
431
- return parts.map((part) => part.trim()).filter((part) => part.length > 0);
432
- }
433
- function splitTopLevelPipes(command) {
434
- const parts = [];
435
- let start = 0;
436
- scanTopLevelChars(command, (char, index) => {
437
- if (char === "|" && command[index - 1] !== "|" && command[index + 1] !== "|") {
438
- parts.push(command.slice(start, index));
439
- start = index + 1;
440
- }
441
- return true;
442
- });
443
- parts.push(command.slice(start));
444
- return parts.map((part) => part.trim()).filter((part) => part.length > 0);
445
- }
446
- function parseChdirTarget(head) {
447
- const words = splitShellWords(head, 3);
448
- const bin = binaryName(words[0]);
449
- if (bin === "cd" || bin === "pushd") {
450
- return words[1] || undefined;
451
- }
452
- return undefined;
453
- }
454
- function isChdirCommand(head) {
455
- const bin = binaryName(splitShellWords(head, 2)[0]);
456
- return bin === "cd" || bin === "pushd" || bin === "popd";
457
- }
458
- function isPopdCommand(head) {
459
- return binaryName(splitShellWords(head, 2)[0]) === "popd";
460
- }
461
- function stripShellPreamble(command) {
462
- let rest = command.trim();
463
- let chdirPath;
464
- for (let i = 0; i < 4; i += 1) {
465
- // Find the first top-level separator (&&, ||, ;, \n) respecting quotes/escaping.
466
- let first;
467
- scanTopLevelChars(rest, (char, idx) => {
468
- if (char === "&" && rest[idx + 1] === "&") {
469
- first = { index: idx, length: 2 };
470
- return false;
471
- }
472
- if (char === "|" && rest[idx + 1] === "|") {
473
- first = { index: idx, length: 2, isOr: true };
474
- return false;
475
- }
476
- if (char === ";" || char === "\n") {
477
- first = { index: idx, length: 1 };
478
- return false;
479
- }
480
- });
481
- const head = (first ? rest.slice(0, first.index) : rest).trim();
482
- // cd/pushd/popd is preamble when followed by && / ; / \n, or when we already
483
- // stripped at least one preamble segment (handles chained cd's like `cd /tmp && cd /app`).
484
- // NOT for || — `cd /app || npm install` means npm runs when cd *fails*, so (in /app) is wrong.
485
- const isChdir = (first ? !first.isOr : i > 0) && isChdirCommand(head);
486
- const isPreamble = head.startsWith("set ") || head.startsWith("export ") || head.startsWith("unset ") || isChdir;
487
- if (!isPreamble) {
488
- break;
489
- }
490
- if (isChdir) {
491
- // popd returns to the previous directory, so inferred cwd from earlier
492
- // preamble steps is no longer reliable.
493
- if (isPopdCommand(head)) {
494
- chdirPath = undefined;
495
- }
496
- else {
497
- chdirPath = parseChdirTarget(head) ?? chdirPath;
498
- }
499
- }
500
- rest = first ? rest.slice(first.index + first.length).trimStart() : "";
501
- if (!rest) {
502
- break;
503
- }
504
- }
505
- return { command: rest.trim(), chdirPath };
506
- }
507
- function summarizeKnownExec(words) {
508
- if (words.length === 0) {
509
- return "run command";
510
- }
511
- const bin = binaryName(words[0]) ?? "command";
512
- if (bin === "git") {
513
- const globalWithValue = new Set([
514
- "-C",
515
- "-c",
516
- "--git-dir",
517
- "--work-tree",
518
- "--namespace",
519
- "--config-env",
520
- ]);
521
- const gitCwd = optionValue(words, ["-C"]);
522
- let sub;
523
- for (let i = 1; i < words.length; i += 1) {
524
- const token = words[i];
525
- if (!token) {
526
- continue;
527
- }
528
- if (token === "--") {
529
- sub = firstPositional(words, i + 1);
530
- break;
531
- }
532
- if (token.startsWith("--")) {
533
- if (token.includes("=")) {
534
- continue;
535
- }
536
- if (globalWithValue.has(token)) {
537
- i += 1;
538
- }
539
- continue;
540
- }
541
- if (token.startsWith("-")) {
542
- if (globalWithValue.has(token)) {
543
- i += 1;
544
- }
545
- continue;
546
- }
547
- sub = token;
548
- break;
549
- }
550
- const map = {
551
- status: "check git status",
552
- diff: "check git diff",
553
- log: "view git history",
554
- show: "show git object",
555
- branch: "list git branches",
556
- checkout: "switch git branch",
557
- switch: "switch git branch",
558
- commit: "create git commit",
559
- pull: "pull git changes",
560
- push: "push git changes",
561
- fetch: "fetch git changes",
562
- merge: "merge git changes",
563
- rebase: "rebase git branch",
564
- add: "stage git changes",
565
- restore: "restore git files",
566
- reset: "reset git state",
567
- stash: "stash git changes",
568
- };
569
- if (sub && map[sub]) {
570
- return map[sub];
571
- }
572
- if (!sub || sub.startsWith("/") || sub.startsWith("~") || sub.includes("/")) {
573
- return gitCwd ? `run git command in ${gitCwd}` : "run git command";
574
- }
575
- return `run git ${sub}`;
576
- }
577
- if (bin === "grep" || bin === "rg" || bin === "ripgrep") {
578
- const positional = positionalArgs(words, 1, [
579
- "-e",
580
- "--regexp",
581
- "-f",
582
- "--file",
583
- "-m",
584
- "--max-count",
585
- "-A",
586
- "--after-context",
587
- "-B",
588
- "--before-context",
589
- "-C",
590
- "--context",
591
- ]);
592
- const pattern = optionValue(words, ["-e", "--regexp"]) ?? positional[0];
593
- const target = positional.length > 1 ? positional.at(-1) : undefined;
594
- if (pattern) {
595
- return target ? `search "${pattern}" in ${target}` : `search "${pattern}"`;
596
- }
597
- return "search text";
598
- }
599
- if (bin === "find") {
600
- const path = words[1] && !words[1].startsWith("-") ? words[1] : ".";
601
- const name = optionValue(words, ["-name", "-iname"]);
602
- return name ? `find files named "${name}" in ${path}` : `find files in ${path}`;
603
- }
604
- if (bin === "ls") {
605
- const target = firstPositional(words, 1);
606
- return target ? `list files in ${target}` : "list files";
607
- }
608
- if (bin === "head" || bin === "tail") {
609
- const lines = optionValue(words, ["-n", "--lines"]) ??
610
- words
611
- .slice(1)
612
- .find((token) => /^-\d+$/.test(token))
613
- ?.slice(1);
614
- const positional = positionalArgs(words, 1, ["-n", "--lines"]);
615
- let target = positional.at(-1);
616
- if (target && /^\d+$/.test(target) && positional.length === 1) {
617
- target = undefined;
618
- }
619
- const side = bin === "head" ? "first" : "last";
620
- const unit = lines === "1" ? "line" : "lines";
621
- if (lines && target) {
622
- return `show ${side} ${lines} ${unit} of ${target}`;
623
- }
624
- if (lines) {
625
- return `show ${side} ${lines} ${unit}`;
626
- }
627
- if (target) {
628
- return `show ${target}`;
629
- }
630
- return `show ${bin} output`;
631
- }
632
- if (bin === "cat") {
633
- const target = firstPositional(words, 1);
634
- return target ? `show ${target}` : "show output";
635
- }
636
- if (bin === "sed") {
637
- const expression = optionValue(words, ["-e", "--expression"]);
638
- const positional = positionalArgs(words, 1, ["-e", "--expression", "-f", "--file"]);
639
- const script = expression ?? positional[0];
640
- const target = expression ? positional[0] : positional[1];
641
- if (script) {
642
- const compact = (stripOuterQuotes(script) ?? script).replace(/\s+/g, "");
643
- const range = compact.match(/^([0-9]+),([0-9]+)p$/);
644
- if (range) {
645
- return target
646
- ? `print lines ${range[1]}-${range[2]} from ${target}`
647
- : `print lines ${range[1]}-${range[2]}`;
648
- }
649
- const single = compact.match(/^([0-9]+)p$/);
650
- if (single) {
651
- return target ? `print line ${single[1]} from ${target}` : `print line ${single[1]}`;
652
- }
653
- }
654
- return target ? `run sed on ${target}` : "run sed transform";
655
- }
656
- if (bin === "printf" || bin === "echo") {
657
- return "print text";
658
- }
659
- if (bin === "cp" || bin === "mv") {
660
- const positional = positionalArgs(words, 1, ["-t", "--target-directory", "-S", "--suffix"]);
661
- const src = positional[0];
662
- const dst = positional[1];
663
- const action = bin === "cp" ? "copy" : "move";
664
- if (src && dst) {
665
- return `${action} ${src} to ${dst}`;
666
- }
667
- if (src) {
668
- return `${action} ${src}`;
669
- }
670
- return `${action} files`;
671
- }
672
- if (bin === "rm") {
673
- const target = firstPositional(words, 1);
674
- return target ? `remove ${target}` : "remove files";
675
- }
676
- if (bin === "mkdir") {
677
- const target = firstPositional(words, 1);
678
- return target ? `create folder ${target}` : "create folder";
679
- }
680
- if (bin === "touch") {
681
- const target = firstPositional(words, 1);
682
- return target ? `create file ${target}` : "create file";
683
- }
684
- if (bin === "curl" || bin === "wget") {
685
- const url = words.find((token) => /^https?:\/\//i.test(token));
686
- return url ? `fetch ${url}` : "fetch url";
687
- }
688
- if (bin === "npm" || bin === "pnpm" || bin === "yarn" || bin === "bun") {
689
- const positional = positionalArgs(words, 1, ["--prefix", "-C", "--cwd", "--config"]);
690
- const sub = positional[0] ?? "command";
691
- const map = {
692
- install: "install dependencies",
693
- test: "run tests",
694
- build: "run build",
695
- start: "start app",
696
- lint: "run lint",
697
- run: positional[1] ? `run ${positional[1]}` : "run script",
698
- };
699
- return map[sub] ?? `run ${bin} ${sub}`;
700
- }
701
- if (bin === "node" || bin === "python" || bin === "python3" || bin === "ruby" || bin === "php") {
702
- const heredoc = words.slice(1).find((token) => token.startsWith("<<"));
703
- if (heredoc) {
704
- return `run ${bin} inline script (heredoc)`;
705
- }
706
- const inline = bin === "node"
707
- ? optionValue(words, ["-e", "--eval"])
708
- : bin === "python" || bin === "python3"
709
- ? optionValue(words, ["-c"])
710
- : undefined;
711
- if (inline !== undefined) {
712
- return `run ${bin} inline script`;
713
- }
714
- const nodeOptsWithValue = ["-e", "--eval", "-m"];
715
- const otherOptsWithValue = ["-c", "-e", "--eval", "-m"];
716
- const script = firstPositional(words, 1, bin === "node" ? nodeOptsWithValue : otherOptsWithValue);
717
- if (!script) {
718
- return `run ${bin}`;
719
- }
720
- if (bin === "node") {
721
- const mode = words.includes("--check") || words.includes("-c")
722
- ? "check js syntax for"
723
- : "run node script";
724
- return `${mode} ${script}`;
725
- }
726
- return `run ${bin} ${script}`;
727
- }
728
- if (bin === "poolbot") {
729
- const sub = firstPositional(words, 1);
730
- return sub ? `run poolbot ${sub}` : "run poolbot";
731
- }
732
- const arg = firstPositional(words, 1);
733
- if (!arg || arg.length > 48) {
734
- return `run ${bin}`;
735
- }
736
- return /^[A-Za-z0-9._/-]+$/.test(arg) ? `run ${bin} ${arg}` : `run ${bin}`;
737
- }
738
- function summarizePipeline(stage) {
739
- const pipeline = splitTopLevelPipes(stage);
740
- if (pipeline.length > 1) {
741
- const first = summarizeKnownExec(trimLeadingEnv(splitShellWords(pipeline[0])));
742
- const last = summarizeKnownExec(trimLeadingEnv(splitShellWords(pipeline[pipeline.length - 1])));
743
- const extra = pipeline.length > 2 ? ` (+${pipeline.length - 2} steps)` : "";
744
- return `${first} -> ${last}${extra}`;
745
- }
746
- return summarizeKnownExec(trimLeadingEnv(splitShellWords(stage)));
747
- }
748
- function summarizeExecCommand(command) {
749
- const { command: cleaned, chdirPath } = stripShellPreamble(command);
750
- if (!cleaned) {
751
- // All segments were preamble (e.g. `cd /tmp && cd /app`) — preserve chdirPath for context.
752
- return chdirPath ? { text: "", chdirPath } : undefined;
753
- }
754
- const stages = splitTopLevelStages(cleaned);
755
- if (stages.length === 0) {
756
- return undefined;
757
- }
758
- const summaries = stages.map((stage) => summarizePipeline(stage));
759
- const text = summaries.length === 1 ? summaries[0] : summaries.join(" → ");
760
- const allGeneric = summaries.every((s) => isGenericSummary(s));
761
- return { text, chdirPath, allGeneric };
762
- }
763
- /** Known summarizer prefixes that indicate a recognized command with useful context. */
764
- const KNOWN_SUMMARY_PREFIXES = [
765
- "check git",
766
- "view git",
767
- "show git",
768
- "list git",
769
- "switch git",
770
- "create git",
771
- "pull git",
772
- "push git",
773
- "fetch git",
774
- "merge git",
775
- "rebase git",
776
- "stage git",
777
- "restore git",
778
- "reset git",
779
- "stash git",
780
- "search ",
781
- "find files",
782
- "list files",
783
- "show first",
784
- "show last",
785
- "print line",
786
- "print text",
787
- "copy ",
788
- "move ",
789
- "remove ",
790
- "create folder",
791
- "create file",
792
- "fetch http",
793
- "install dependencies",
794
- "run tests",
795
- "run build",
796
- "start app",
797
- "run lint",
798
- "run poolbot",
799
- "run node script",
800
- "run node ",
801
- "run python",
802
- "run ruby",
803
- "run php",
804
- "run sed",
805
- "run git ",
806
- "run npm ",
807
- "run pnpm ",
808
- "run yarn ",
809
- "run bun ",
810
- "check js syntax",
811
- ];
812
- /** True when the summary is generic and the raw command would be more informative. */
813
- function isGenericSummary(summary) {
814
- if (summary === "run command") {
815
- return true;
816
- }
817
- // "run <binary>" or "run <binary> <arg>" without useful context
818
- if (summary.startsWith("run ")) {
819
- return !KNOWN_SUMMARY_PREFIXES.some((prefix) => summary.startsWith(prefix));
820
- }
821
- return false;
822
- }
823
- /** Compact the raw command for display: collapse whitespace, trim long strings. */
824
- function compactRawCommand(raw, maxLength = 120) {
825
- const oneLine = raw
826
- .replace(/\s*\n\s*/g, " ")
827
- .replace(/\s{2,}/g, " ")
828
- .trim();
829
- if (oneLine.length <= maxLength) {
830
- return oneLine;
831
- }
832
- return `${oneLine.slice(0, Math.max(0, maxLength - 1))}…`;
833
- }
834
- export function resolveExecDetail(args) {
835
- const record = asRecord(args);
836
- if (!record) {
837
- return undefined;
838
- }
839
- const raw = typeof record.command === "string" ? record.command.trim() : undefined;
840
- if (!raw) {
841
- return undefined;
842
- }
843
- const unwrapped = unwrapShellWrapper(raw);
844
- const result = summarizeExecCommand(unwrapped) ?? summarizeExecCommand(raw);
845
- const summary = result?.text || "run command";
846
- const cwdRaw = typeof record.workdir === "string"
847
- ? record.workdir
848
- : typeof record.cwd === "string"
849
- ? record.cwd
850
- : undefined;
851
- // Explicit workdir takes priority; fall back to cd path extracted from the command.
852
- const cwd = cwdRaw?.trim() || result?.chdirPath || undefined;
853
- const compact = compactRawCommand(unwrapped);
854
- // When ALL stages are generic (e.g. "run jj"), use the compact raw command instead.
855
- // For mixed stages like "run cargo build → run tests", keep the summary since some parts are useful.
856
- if (result?.allGeneric !== false && isGenericSummary(summary)) {
857
- return cwd ? `${compact} (in ${cwd})` : compact;
858
- }
859
- const displaySummary = cwd ? `${summary} (in ${cwd})` : summary;
860
- // Append the raw command when the summary differs meaningfully from the command itself.
861
- if (compact && compact !== displaySummary && compact !== summary) {
862
- return `${displaySummary}\n\n\`${compact}\``;
863
- }
864
- return displaySummary;
865
- }
866
- export function resolveActionSpec(spec, action) {
867
- if (!spec || !action) {
868
- return undefined;
869
- }
870
- return spec.actions?.[action] ?? undefined;
871
- }
872
- export function resolveDetailFromKeys(args, keys, opts) {
873
- if (opts.mode === "first") {
874
- for (const key of keys) {
875
- const value = lookupValueByPath(args, key);
876
- const display = coerceDisplayValue(value, opts.coerce);
877
- if (display) {
878
- return display;
879
- }
880
- }
881
- return undefined;
882
- }
883
- const entries = [];
884
- for (const key of keys) {
885
- const value = lookupValueByPath(args, key);
886
- const display = coerceDisplayValue(value, opts.coerce);
887
- if (!display) {
888
- continue;
889
- }
890
- entries.push({ label: opts.formatKey ? opts.formatKey(key) : key, value: display });
891
- }
892
- if (entries.length === 0) {
893
- return undefined;
894
- }
895
- if (entries.length === 1) {
896
- return entries[0].value;
897
- }
898
- const seen = new Set();
899
- const unique = [];
900
- for (const entry of entries) {
901
- const token = `${entry.label}:${entry.value}`;
902
- if (seen.has(token)) {
903
- continue;
904
- }
905
- seen.add(token);
906
- unique.push(entry);
907
- }
908
- if (unique.length === 0) {
909
- return undefined;
910
- }
911
- return unique
912
- .slice(0, opts.maxEntries ?? 8)
913
- .map((entry) => `${entry.label} ${entry.value}`)
914
- .join(" · ");
915
- }