@poolzin/pool-bot 2026.2.0 → 2026.2.1

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 (230) hide show
  1. package/dist/agents/bash-tools.exec.js +76 -25
  2. package/dist/agents/cli-runner/helpers.js +9 -11
  3. package/dist/agents/identity.js +47 -7
  4. package/dist/agents/memory-search.js +25 -8
  5. package/dist/agents/model-selection.js +21 -0
  6. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  7. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  8. package/dist/agents/pi-embedded-helpers.js +1 -1
  9. package/dist/agents/pi-embedded-runner/compact.js +1 -0
  10. package/dist/agents/pi-embedded-runner/model.js +61 -2
  11. package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
  12. package/dist/agents/pi-embedded-runner/run.js +199 -46
  13. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  14. package/dist/agents/pi-embedded-subscribe.js +118 -29
  15. package/dist/agents/pi-tools.js +10 -5
  16. package/dist/agents/poolbot-tools.js +15 -10
  17. package/dist/agents/sandbox-paths.js +31 -0
  18. package/dist/agents/session-tool-result-guard.js +94 -15
  19. package/dist/agents/shell-utils.js +51 -0
  20. package/dist/agents/skills/bundled-context.js +23 -0
  21. package/dist/agents/skills/bundled-dir.js +41 -7
  22. package/dist/agents/skills-install.js +60 -23
  23. package/dist/agents/subagent-announce.js +79 -34
  24. package/dist/agents/tool-policy.conformance.js +14 -0
  25. package/dist/agents/tool-policy.js +24 -0
  26. package/dist/agents/tools/cron-tool.js +166 -19
  27. package/dist/agents/tools/discord-actions-presence.js +78 -0
  28. package/dist/agents/tools/message-tool.js +56 -2
  29. package/dist/agents/tools/sessions-history-tool.js +69 -1
  30. package/dist/agents/tools/web-search.js +211 -42
  31. package/dist/agents/usage.js +23 -1
  32. package/dist/agents/workspace-run.js +67 -0
  33. package/dist/agents/workspace-templates.js +44 -0
  34. package/dist/auto-reply/command-auth.js +121 -6
  35. package/dist/auto-reply/envelope.js +50 -72
  36. package/dist/auto-reply/reply/commands-compact.js +1 -0
  37. package/dist/auto-reply/reply/commands-context-report.js +1 -0
  38. package/dist/auto-reply/reply/commands-context.js +1 -0
  39. package/dist/auto-reply/reply/commands-models.js +107 -60
  40. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  41. package/dist/auto-reply/reply/get-reply-run.js +2 -1
  42. package/dist/auto-reply/reply/inbound-context.js +5 -1
  43. package/dist/auto-reply/reply/model-selection.js +3 -3
  44. package/dist/auto-reply/thinking.js +88 -43
  45. package/dist/browser/bridge-server.js +13 -0
  46. package/dist/browser/cdp.helpers.js +38 -24
  47. package/dist/browser/client-fetch.js +50 -7
  48. package/dist/browser/config.js +1 -10
  49. package/dist/browser/extension-relay.js +101 -40
  50. package/dist/browser/pw-ai.js +1 -1
  51. package/dist/browser/pw-session.js +143 -8
  52. package/dist/browser/pw-tools-core.interactions.js +125 -27
  53. package/dist/browser/pw-tools-core.responses.js +1 -1
  54. package/dist/browser/pw-tools-core.state.js +1 -1
  55. package/dist/browser/routes/agent.act.js +86 -41
  56. package/dist/browser/routes/dispatcher.js +4 -4
  57. package/dist/browser/screenshot.js +1 -1
  58. package/dist/browser/server.js +13 -0
  59. package/dist/build-info.json +3 -3
  60. package/dist/channels/reply-prefix.js +8 -1
  61. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  62. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  63. package/dist/cli/cron-cli/shared.js +56 -41
  64. package/dist/cli/dns-cli.js +26 -14
  65. package/dist/cli/gateway-cli/register.js +37 -19
  66. package/dist/cli/memory-cli.js +5 -5
  67. package/dist/cli/parse-bytes.js +37 -0
  68. package/dist/cli/update-cli.js +173 -52
  69. package/dist/commands/agent.js +1 -0
  70. package/dist/commands/doctor-config-flow.js +61 -5
  71. package/dist/commands/doctor-state-migrations.js +1 -1
  72. package/dist/commands/health.js +1 -1
  73. package/dist/commands/model-allowlist.js +29 -0
  74. package/dist/commands/model-picker.js +2 -1
  75. package/dist/commands/models/list.status-command.js +43 -23
  76. package/dist/commands/models/shared.js +15 -0
  77. package/dist/commands/onboard-custom.js +384 -0
  78. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  79. package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
  80. package/dist/commands/onboard-skills.js +63 -38
  81. package/dist/commands/openai-model-default.js +41 -0
  82. package/dist/config/defaults.js +3 -2
  83. package/dist/config/paths.js +136 -35
  84. package/dist/config/plugin-auto-enable.js +21 -5
  85. package/dist/config/redact-snapshot.js +153 -0
  86. package/dist/config/schema.field-metadata.js +590 -0
  87. package/dist/config/schema.js +2 -2
  88. package/dist/config/sessions/store.js +291 -23
  89. package/dist/config/zod-schema.agent-defaults.js +3 -0
  90. package/dist/config/zod-schema.agent-runtime.js +13 -2
  91. package/dist/config/zod-schema.providers-core.js +142 -0
  92. package/dist/config/zod-schema.session.js +3 -0
  93. package/dist/cron/delivery.js +57 -0
  94. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  95. package/dist/cron/isolated-agent/helpers.js +22 -5
  96. package/dist/cron/isolated-agent/run.js +171 -63
  97. package/dist/cron/isolated-agent/session.js +2 -0
  98. package/dist/cron/normalize.js +356 -28
  99. package/dist/cron/parse.js +10 -5
  100. package/dist/cron/run-log.js +35 -10
  101. package/dist/cron/schedule.js +41 -6
  102. package/dist/cron/service/jobs.js +208 -35
  103. package/dist/cron/service/ops.js +72 -16
  104. package/dist/cron/service/state.js +2 -0
  105. package/dist/cron/service/store.js +386 -14
  106. package/dist/cron/service/timer.js +390 -147
  107. package/dist/cron/session-reaper.js +86 -0
  108. package/dist/cron/store.js +23 -8
  109. package/dist/cron/validate-timestamp.js +43 -0
  110. package/dist/discord/monitor/agent-components.js +438 -0
  111. package/dist/discord/monitor/allow-list.js +28 -5
  112. package/dist/discord/monitor/gateway-registry.js +29 -0
  113. package/dist/discord/monitor/native-command.js +44 -23
  114. package/dist/discord/monitor/sender-identity.js +45 -0
  115. package/dist/discord/pluralkit.js +27 -0
  116. package/dist/discord/send.outbound.js +92 -5
  117. package/dist/discord/send.shared.js +60 -23
  118. package/dist/discord/targets.js +84 -1
  119. package/dist/entry.js +15 -9
  120. package/dist/extensionAPI.js +8 -0
  121. package/dist/gateway/control-ui.js +8 -1
  122. package/dist/gateway/hooks-mapping.js +3 -0
  123. package/dist/gateway/hooks.js +65 -0
  124. package/dist/gateway/net.js +96 -31
  125. package/dist/gateway/node-command-policy.js +50 -15
  126. package/dist/gateway/origin-check.js +56 -0
  127. package/dist/gateway/protocol/client-info.js +9 -0
  128. package/dist/gateway/protocol/index.js +9 -2
  129. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  130. package/dist/gateway/protocol/schema/cron.js +22 -10
  131. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  132. package/dist/gateway/protocol/schema/sessions.js +12 -0
  133. package/dist/gateway/server/hooks.js +1 -1
  134. package/dist/gateway/server-broadcast.js +26 -9
  135. package/dist/gateway/server-chat.js +112 -23
  136. package/dist/gateway/server-discovery-runtime.js +10 -2
  137. package/dist/gateway/server-http.js +109 -11
  138. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  139. package/dist/gateway/server-methods/agents.js +321 -2
  140. package/dist/gateway/server-methods/usage.js +559 -16
  141. package/dist/gateway/server-runtime-state.js +22 -8
  142. package/dist/gateway/server-startup-memory.js +16 -0
  143. package/dist/gateway/server.impl.js +5 -1
  144. package/dist/gateway/session-utils.fs.js +23 -25
  145. package/dist/gateway/session-utils.js +20 -10
  146. package/dist/gateway/sessions-patch.js +7 -22
  147. package/dist/gateway/test-helpers.server.js +35 -2
  148. package/dist/imessage/constants.js +2 -0
  149. package/dist/imessage/monitor/deliver.js +4 -1
  150. package/dist/imessage/monitor/monitor-provider.js +51 -1
  151. package/dist/infra/bonjour-discovery.js +131 -70
  152. package/dist/infra/control-ui-assets.js +134 -12
  153. package/dist/infra/errors.js +12 -0
  154. package/dist/infra/exec-approvals.js +266 -57
  155. package/dist/infra/format-time/format-datetime.js +79 -0
  156. package/dist/infra/format-time/format-duration.js +81 -0
  157. package/dist/infra/format-time/format-relative.js +80 -0
  158. package/dist/infra/heartbeat-runner.js +140 -49
  159. package/dist/infra/home-dir.js +54 -0
  160. package/dist/infra/net/fetch-guard.js +122 -0
  161. package/dist/infra/net/ssrf.js +65 -29
  162. package/dist/infra/outbound/abort.js +14 -0
  163. package/dist/infra/outbound/message-action-runner.js +77 -13
  164. package/dist/infra/outbound/outbound-session.js +143 -37
  165. package/dist/infra/poolbot-root.js +43 -1
  166. package/dist/infra/session-cost-usage.js +631 -41
  167. package/dist/infra/state-migrations.js +317 -47
  168. package/dist/infra/update-global.js +35 -0
  169. package/dist/infra/update-runner.js +149 -43
  170. package/dist/infra/warning-filter.js +65 -0
  171. package/dist/infra/widearea-dns.js +30 -9
  172. package/dist/logging/redact-identifier.js +12 -0
  173. package/dist/media/fetch.js +81 -58
  174. package/dist/media-understanding/apply.js +403 -3
  175. package/dist/media-understanding/attachments.js +38 -27
  176. package/dist/media-understanding/defaults.js +16 -0
  177. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  178. package/dist/media-understanding/providers/google/audio.js +24 -17
  179. package/dist/media-understanding/providers/google/video.js +24 -17
  180. package/dist/media-understanding/providers/image.js +2 -2
  181. package/dist/media-understanding/providers/index.js +4 -1
  182. package/dist/media-understanding/providers/openai/audio.js +22 -14
  183. package/dist/media-understanding/providers/shared.js +16 -11
  184. package/dist/media-understanding/providers/zai/index.js +6 -0
  185. package/dist/media-understanding/runner.js +158 -90
  186. package/dist/memory/batch-voyage.js +277 -0
  187. package/dist/memory/embeddings-voyage.js +75 -0
  188. package/dist/memory/embeddings.js +28 -16
  189. package/dist/memory/internal.js +101 -18
  190. package/dist/memory/manager.js +154 -48
  191. package/dist/memory/search-manager.js +173 -0
  192. package/dist/memory/session-files.js +9 -3
  193. package/dist/node-host/runner.js +34 -24
  194. package/dist/node-host/with-timeout.js +27 -0
  195. package/dist/plugins/commands.js +5 -1
  196. package/dist/plugins/config-state.js +86 -7
  197. package/dist/plugins/source-display.js +51 -0
  198. package/dist/process/exec.js +20 -2
  199. package/dist/routing/resolve-route.js +12 -0
  200. package/dist/routing/session-key.js +15 -0
  201. package/dist/runtime.js +2 -0
  202. package/dist/security/audit-extra.async.js +601 -0
  203. package/dist/security/audit-extra.js +2 -830
  204. package/dist/security/audit-extra.sync.js +505 -0
  205. package/dist/security/channel-metadata.js +34 -0
  206. package/dist/security/external-content.js +88 -6
  207. package/dist/security/skill-scanner.js +330 -0
  208. package/dist/sessions/session-key-utils.js +7 -0
  209. package/dist/signal/monitor/event-handler.js +80 -1
  210. package/dist/slack/monitor/media.js +85 -15
  211. package/dist/tailscale/detect.js +1 -2
  212. package/dist/telegram/bot/helpers.js +109 -28
  213. package/dist/telegram/bot-handlers.js +144 -3
  214. package/dist/telegram/bot-message-context.js +37 -10
  215. package/dist/telegram/bot-message-dispatch.js +48 -15
  216. package/dist/telegram/bot-native-commands.js +86 -29
  217. package/dist/telegram/bot.js +30 -29
  218. package/dist/telegram/model-buttons.js +163 -0
  219. package/dist/telegram/monitor.js +110 -85
  220. package/dist/telegram/send.js +129 -47
  221. package/dist/terminal/restore.js +45 -0
  222. package/dist/test-helpers/state-dir-env.js +16 -0
  223. package/dist/tts/tts.js +12 -6
  224. package/dist/tui/tui-session-actions.js +166 -54
  225. package/dist/utils/fetch-timeout.js +20 -0
  226. package/dist/utils/normalize-secret-input.js +19 -0
  227. package/dist/utils/transcript-tools.js +58 -0
  228. package/dist/utils.js +45 -14
  229. package/dist/version.js +42 -5
  230. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { resolveAgentConfig, resolveAgentDir, resolveAgentModelFallbacksOverride
2
2
  import { runCliAgent } from "../../agents/cli-runner.js";
3
3
  import { getCliSessionId, setCliSessionId } from "../../agents/cli-session.js";
4
4
  import { lookupContextTokens } from "../../agents/context.js";
5
+ import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone, } from "../../agents/date-time.js";
5
6
  import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
6
7
  import { loadModelCatalog } from "../../agents/model-catalog.js";
7
8
  import { runWithModelFallback } from "../../agents/model-fallback.js";
@@ -9,34 +10,46 @@ import { getModelRefStatus, isCliProvider, resolveAllowedModelRef, resolveConfig
9
10
  import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
10
11
  import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
11
12
  import { getSkillsSnapshotVersion } from "../../agents/skills/refresh.js";
13
+ import { runSubagentAnnounceFlow } from "../../agents/subagent-announce.js";
12
14
  import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
13
- import { hasNonzeroUsage } from "../../agents/usage.js";
15
+ import { deriveSessionTotalTokens, hasNonzeroUsage } from "../../agents/usage.js";
14
16
  import { ensureAgentWorkspace } from "../../agents/workspace.js";
15
- import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone, } from "../../agents/date-time.js";
16
- import { formatXHighModelHint, normalizeThinkLevel, normalizeVerboseLevel, supportsXHighThinking, } from "../../auto-reply/thinking.js";
17
+ import { normalizeThinkLevel, normalizeVerboseLevel, supportsXHighThinking, } from "../../auto-reply/thinking.js";
17
18
  import { createOutboundSendDeps } from "../../cli/outbound-send-deps.js";
18
- import { resolveSessionTranscriptPath, updateSessionStore } from "../../config/sessions.js";
19
+ import { resolveAgentMainSessionKey, resolveSessionTranscriptPath, updateSessionStore, } from "../../config/sessions.js";
19
20
  import { registerAgentRunContext } from "../../infra/agent-events.js";
20
21
  import { deliverOutboundPayloads } from "../../infra/outbound/deliver.js";
21
22
  import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
23
+ import { logWarn } from "../../logger.js";
22
24
  import { buildAgentMainSessionKey, normalizeAgentId } from "../../routing/session-key.js";
23
25
  import { buildSafeExternalPrompt, detectSuspiciousPatterns, getHookType, isExternalHookSession, } from "../../security/external-content.js";
24
- import { logWarn } from "../../logger.js";
26
+ import { resolveCronDeliveryPlan } from "../delivery.js";
25
27
  import { resolveDeliveryTarget } from "./delivery-target.js";
26
- import { isHeartbeatOnlyResponse, pickLastNonEmptyTextFromPayloads, pickSummaryFromOutput, pickSummaryFromPayloads, resolveHeartbeatAckMaxChars, } from "./helpers.js";
28
+ import { isHeartbeatOnlyResponse, pickLastDeliverablePayload, pickLastNonEmptyTextFromPayloads, pickSummaryFromOutput, pickSummaryFromPayloads, resolveHeartbeatAckMaxChars, } from "./helpers.js";
27
29
  import { resolveCronSession } from "./session.js";
28
30
  function matchesMessagingToolDeliveryTarget(target, delivery) {
29
- if (!delivery.to || !target.to)
31
+ if (!delivery.to || !target.to) {
30
32
  return false;
33
+ }
31
34
  const channel = delivery.channel.trim().toLowerCase();
32
35
  const provider = target.provider?.trim().toLowerCase();
33
- if (provider && provider !== "message" && provider !== channel)
36
+ if (provider && provider !== "message" && provider !== channel) {
34
37
  return false;
38
+ }
35
39
  if (target.accountId && delivery.accountId && target.accountId !== delivery.accountId) {
36
40
  return false;
37
41
  }
38
42
  return target.to === delivery.to;
39
43
  }
44
+ function resolveCronDeliveryBestEffort(job) {
45
+ if (typeof job.delivery?.bestEffort === "boolean") {
46
+ return job.delivery.bestEffort;
47
+ }
48
+ if (job.payload.kind === "agentTurn" && typeof job.payload.bestEffortDeliver === "boolean") {
49
+ return job.payload.bestEffortDeliver;
50
+ }
51
+ return false;
52
+ }
40
53
  export async function runCronIsolatedAgentTurn(params) {
41
54
  const defaultAgentId = resolveDefaultAgentId(params.cfg);
42
55
  const requestedAgentId = typeof params.agentId === "string" && params.agentId.trim()
@@ -109,14 +122,12 @@ export async function runCronIsolatedAgentTurn(params) {
109
122
  }
110
123
  }
111
124
  const modelOverrideRaw = params.job.payload.kind === "agentTurn" ? params.job.payload.model : undefined;
112
- if (modelOverrideRaw !== undefined) {
113
- if (typeof modelOverrideRaw !== "string") {
114
- return { status: "error", error: "invalid model: expected string" };
115
- }
125
+ const modelOverride = typeof modelOverrideRaw === "string" ? modelOverrideRaw.trim() : undefined;
126
+ if (modelOverride !== undefined && modelOverride.length > 0) {
116
127
  const resolvedOverride = resolveAllowedModelRef({
117
128
  cfg: cfgWithAgentDefaults,
118
129
  catalog: await loadCatalog(),
119
- raw: modelOverrideRaw,
130
+ raw: modelOverride,
120
131
  defaultProvider: resolvedDefault.provider,
121
132
  defaultModel: resolvedDefault.model,
122
133
  });
@@ -133,6 +144,33 @@ export async function runCronIsolatedAgentTurn(params) {
133
144
  agentId,
134
145
  nowMs: now,
135
146
  });
147
+ const runSessionId = cronSession.sessionEntry.sessionId;
148
+ const runSessionKey = baseSessionKey.startsWith("cron:")
149
+ ? `${agentSessionKey}:run:${runSessionId}`
150
+ : agentSessionKey;
151
+ const persistSessionEntry = async () => {
152
+ cronSession.store[agentSessionKey] = cronSession.sessionEntry;
153
+ if (runSessionKey !== agentSessionKey) {
154
+ cronSession.store[runSessionKey] = cronSession.sessionEntry;
155
+ }
156
+ await updateSessionStore(cronSession.storePath, (store) => {
157
+ store[agentSessionKey] = cronSession.sessionEntry;
158
+ if (runSessionKey !== agentSessionKey) {
159
+ store[runSessionKey] = cronSession.sessionEntry;
160
+ }
161
+ });
162
+ };
163
+ const withRunSession = (result) => ({
164
+ ...result,
165
+ sessionId: runSessionId,
166
+ sessionKey: runSessionKey,
167
+ });
168
+ if (!cronSession.sessionEntry.label?.trim() && baseSessionKey.startsWith("cron:")) {
169
+ const labelSuffix = typeof params.job.name === "string" && params.job.name.trim()
170
+ ? params.job.name.trim()
171
+ : params.job.id;
172
+ cronSession.sessionEntry.label = `Cron: ${labelSuffix}`;
173
+ }
136
174
  // Resolve thinking level - job thinking > hooks.gmail.thinking > agent default
137
175
  const hooksGmailThinking = isGmailHook
138
176
  ? normalizeThinkLevel(params.cfg.hooks?.gmail?.thinking)
@@ -150,20 +188,19 @@ export async function runCronIsolatedAgentTurn(params) {
150
188
  });
151
189
  }
152
190
  if (thinkLevel === "xhigh" && !supportsXHighThinking(provider, model)) {
153
- throw new Error(`Thinking level "xhigh" is only supported for ${formatXHighModelHint()}.`);
191
+ logWarn(`[cron:${params.job.id}] Thinking level "xhigh" is not supported for ${provider}/${model}; downgrading to "high".`);
192
+ thinkLevel = "high";
154
193
  }
155
194
  const timeoutMs = resolveAgentTimeoutMs({
156
195
  cfg: cfgWithAgentDefaults,
157
196
  overrideSeconds: params.job.payload.kind === "agentTurn" ? params.job.payload.timeoutSeconds : undefined,
158
197
  });
159
198
  const agentPayload = params.job.payload.kind === "agentTurn" ? params.job.payload : null;
160
- const deliveryMode = agentPayload?.deliver === true ? "explicit" : agentPayload?.deliver === false ? "off" : "auto";
161
- const hasExplicitTarget = Boolean(agentPayload?.to && agentPayload.to.trim());
162
- const deliveryRequested = deliveryMode === "explicit" || (deliveryMode === "auto" && hasExplicitTarget);
163
- const bestEffortDeliver = agentPayload?.bestEffortDeliver === true;
199
+ const deliveryPlan = resolveCronDeliveryPlan(params.job);
200
+ const deliveryRequested = deliveryPlan.requested;
164
201
  const resolvedDelivery = await resolveDeliveryTarget(cfgWithAgentDefaults, agentId, {
165
- channel: agentPayload?.channel ?? "last",
166
- to: agentPayload?.to,
202
+ channel: deliveryPlan.channel ?? "last",
203
+ to: deliveryPlan.to,
167
204
  });
168
205
  const userTimezone = resolveUserTimezone(params.cfg.agents?.defaults?.userTimezone);
169
206
  const userTimeFormat = resolveUserTimeFormat(params.cfg.agents?.defaults?.timeFormat);
@@ -182,8 +219,7 @@ export async function runCronIsolatedAgentTurn(params) {
182
219
  const suspiciousPatterns = detectSuspiciousPatterns(params.message);
183
220
  if (suspiciousPatterns.length > 0) {
184
221
  logWarn(`[security] Suspicious patterns detected in external hook content ` +
185
- `(session=${baseSessionKey}, patterns=${suspiciousPatterns.length}): ` +
186
- `${suspiciousPatterns.slice(0, 3).join(", ")}`);
222
+ `(session=${baseSessionKey}, patterns=${suspiciousPatterns.length}): ${suspiciousPatterns.slice(0, 3).join(", ")}`);
187
223
  }
188
224
  }
189
225
  if (shouldWrapExternal) {
@@ -202,6 +238,10 @@ export async function runCronIsolatedAgentTurn(params) {
202
238
  // Internal/trusted source - use original format
203
239
  commandBody = `${base}\n${timeLine}`.trim();
204
240
  }
241
+ if (deliveryRequested) {
242
+ commandBody =
243
+ `${commandBody}\n\nReturn your summary as plain text; it will be delivered automatically. If the task explicitly calls for messaging a specific external recipient, note who/where it should go instead of sending it yourself.`.trim();
244
+ }
205
245
  const existingSnapshot = cronSession.sessionEntry.skillsSnapshot;
206
246
  const skillsSnapshotVersion = getSkillsSnapshotVersion(workspaceDir);
207
247
  const needsSkillsSnapshot = !existingSnapshot || existingSnapshot.version !== skillsSnapshotVersion;
@@ -218,20 +258,16 @@ export async function runCronIsolatedAgentTurn(params) {
218
258
  updatedAt: Date.now(),
219
259
  skillsSnapshot,
220
260
  };
221
- cronSession.store[agentSessionKey] = cronSession.sessionEntry;
222
- await updateSessionStore(cronSession.storePath, (store) => {
223
- store[agentSessionKey] = cronSession.sessionEntry;
224
- });
261
+ await persistSessionEntry();
225
262
  }
226
263
  // Persist systemSent before the run, mirroring the inbound auto-reply behavior.
227
264
  cronSession.sessionEntry.systemSent = true;
228
- cronSession.store[agentSessionKey] = cronSession.sessionEntry;
229
- await updateSessionStore(cronSession.storePath, (store) => {
230
- store[agentSessionKey] = cronSession.sessionEntry;
231
- });
265
+ await persistSessionEntry();
232
266
  let runResult;
233
267
  let fallbackProvider = provider;
234
268
  let fallbackModel = model;
269
+ const runStartedAt = Date.now();
270
+ let runEndedAt = runStartedAt;
235
271
  try {
236
272
  const sessionFile = resolveSessionTranscriptPath(cronSession.sessionEntry.sessionId, agentId);
237
273
  const resolvedVerboseLevel = normalizeVerboseLevel(cronSession.sessionEntry.verboseLevel) ??
@@ -289,9 +325,10 @@ export async function runCronIsolatedAgentTurn(params) {
289
325
  runResult = fallbackResult.result;
290
326
  fallbackProvider = fallbackResult.provider;
291
327
  fallbackModel = fallbackResult.model;
328
+ runEndedAt = Date.now();
292
329
  }
293
330
  catch (err) {
294
- return { status: "error", error: String(err) };
331
+ return withRunSession({ status: "error", error: String(err) });
295
332
  }
296
333
  const payloads = runResult.payloads ?? [];
297
334
  // Update token+model fields in the session store.
@@ -312,25 +349,34 @@ export async function runCronIsolatedAgentTurn(params) {
312
349
  if (hasNonzeroUsage(usage)) {
313
350
  const input = usage.input ?? 0;
314
351
  const output = usage.output ?? 0;
315
- const promptTokens = input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
316
352
  cronSession.sessionEntry.inputTokens = input;
317
353
  cronSession.sessionEntry.outputTokens = output;
318
354
  cronSession.sessionEntry.totalTokens =
319
- promptTokens > 0 ? promptTokens : (usage.total ?? input);
355
+ deriveSessionTotalTokens({
356
+ usage,
357
+ contextTokens,
358
+ }) ?? input;
320
359
  }
321
- cronSession.store[agentSessionKey] = cronSession.sessionEntry;
322
- await updateSessionStore(cronSession.storePath, (store) => {
323
- store[agentSessionKey] = cronSession.sessionEntry;
324
- });
360
+ await persistSessionEntry();
325
361
  }
326
362
  const firstText = payloads[0]?.text ?? "";
327
363
  const summary = pickSummaryFromPayloads(payloads) ?? pickSummaryFromOutput(firstText);
328
364
  const outputText = pickLastNonEmptyTextFromPayloads(payloads);
365
+ const synthesizedText = outputText?.trim() || summary?.trim() || undefined;
366
+ const deliveryPayload = pickLastDeliverablePayload(payloads);
367
+ const deliveryPayloads = deliveryPayload !== undefined
368
+ ? [deliveryPayload]
369
+ : synthesizedText
370
+ ? [{ text: synthesizedText }]
371
+ : [];
372
+ const deliveryPayloadHasStructuredContent = Boolean(deliveryPayload?.mediaUrl) ||
373
+ (deliveryPayload?.mediaUrls?.length ?? 0) > 0 ||
374
+ Object.keys(deliveryPayload?.channelData ?? {}).length > 0;
375
+ const deliveryBestEffort = resolveCronDeliveryBestEffort(params.job);
329
376
  // Skip delivery for heartbeat-only responses (HEARTBEAT_OK with no real content).
330
377
  const ackMaxChars = resolveHeartbeatAckMaxChars(agentCfg);
331
378
  const skipHeartbeatDelivery = deliveryRequested && isHeartbeatOnlyResponse(payloads, ackMaxChars);
332
379
  const skipMessagingToolDelivery = deliveryRequested &&
333
- deliveryMode === "auto" &&
334
380
  runResult.didSendViaMessagingTool === true &&
335
381
  (runResult.messagingToolSentTargets ?? []).some((target) => matchesMessagingToolDeliveryTarget(target, {
336
382
  channel: resolvedDelivery.channel,
@@ -338,39 +384,101 @@ export async function runCronIsolatedAgentTurn(params) {
338
384
  accountId: resolvedDelivery.accountId,
339
385
  }));
340
386
  if (deliveryRequested && !skipHeartbeatDelivery && !skipMessagingToolDelivery) {
387
+ if (resolvedDelivery.error) {
388
+ if (!deliveryBestEffort) {
389
+ return withRunSession({
390
+ status: "error",
391
+ error: resolvedDelivery.error.message,
392
+ summary,
393
+ outputText,
394
+ });
395
+ }
396
+ logWarn(`[cron:${params.job.id}] ${resolvedDelivery.error.message}`);
397
+ return withRunSession({ status: "ok", summary, outputText });
398
+ }
341
399
  if (!resolvedDelivery.to) {
342
- const reason = resolvedDelivery.error?.message ?? "Cron delivery requires a recipient (--to).";
343
- if (!bestEffortDeliver) {
344
- return {
400
+ const message = "cron delivery target is missing";
401
+ if (!deliveryBestEffort) {
402
+ return withRunSession({
345
403
  status: "error",
404
+ error: message,
346
405
  summary,
347
406
  outputText,
348
- error: reason,
349
- };
407
+ });
350
408
  }
351
- return {
352
- status: "skipped",
353
- summary: `Delivery skipped (${reason}).`,
354
- outputText,
355
- };
409
+ logWarn(`[cron:${params.job.id}] ${message}`);
410
+ return withRunSession({ status: "ok", summary, outputText });
356
411
  }
357
- try {
358
- await deliverOutboundPayloads({
359
- cfg: cfgWithAgentDefaults,
360
- channel: resolvedDelivery.channel,
361
- to: resolvedDelivery.to,
362
- accountId: resolvedDelivery.accountId,
363
- payloads,
364
- bestEffort: bestEffortDeliver,
365
- deps: createOutboundSendDeps(params.deps),
366
- });
412
+ // Shared subagent announce flow is text-based; keep direct outbound delivery
413
+ // for media/channel payloads so structured content is preserved.
414
+ if (deliveryPayloadHasStructuredContent) {
415
+ try {
416
+ await deliverOutboundPayloads({
417
+ cfg: cfgWithAgentDefaults,
418
+ channel: resolvedDelivery.channel,
419
+ to: resolvedDelivery.to,
420
+ accountId: resolvedDelivery.accountId,
421
+ threadId: resolvedDelivery.threadId,
422
+ payloads: deliveryPayloads,
423
+ bestEffort: deliveryBestEffort,
424
+ deps: createOutboundSendDeps(params.deps),
425
+ });
426
+ }
427
+ catch (err) {
428
+ if (!deliveryBestEffort) {
429
+ return withRunSession({ status: "error", summary, outputText, error: String(err) });
430
+ }
431
+ }
367
432
  }
368
- catch (err) {
369
- if (!bestEffortDeliver) {
370
- return { status: "error", summary, outputText, error: String(err) };
433
+ else if (synthesizedText) {
434
+ const announceSessionKey = resolveAgentMainSessionKey({
435
+ cfg: params.cfg,
436
+ agentId,
437
+ });
438
+ const taskLabel = typeof params.job.name === "string" && params.job.name.trim()
439
+ ? params.job.name.trim()
440
+ : `cron:${params.job.id}`;
441
+ try {
442
+ const didAnnounce = await runSubagentAnnounceFlow({
443
+ childSessionKey: runSessionKey,
444
+ childRunId: `${params.job.id}:${runSessionId}`,
445
+ requesterSessionKey: announceSessionKey,
446
+ requesterOrigin: {
447
+ channel: resolvedDelivery.channel,
448
+ to: resolvedDelivery.to,
449
+ accountId: resolvedDelivery.accountId,
450
+ threadId: resolvedDelivery.threadId,
451
+ },
452
+ requesterDisplayKey: announceSessionKey,
453
+ task: taskLabel,
454
+ timeoutMs,
455
+ cleanup: "keep",
456
+ roundOneReply: synthesizedText,
457
+ waitForCompletion: false,
458
+ startedAt: runStartedAt,
459
+ endedAt: runEndedAt,
460
+ outcome: { status: "ok" },
461
+ });
462
+ if (!didAnnounce) {
463
+ const message = "cron announce delivery failed";
464
+ if (!deliveryBestEffort) {
465
+ return withRunSession({
466
+ status: "error",
467
+ summary,
468
+ outputText,
469
+ error: message,
470
+ });
471
+ }
472
+ logWarn(`[cron:${params.job.id}] ${message}`);
473
+ }
474
+ }
475
+ catch (err) {
476
+ if (!deliveryBestEffort) {
477
+ return withRunSession({ status: "error", summary, outputText, error: String(err) });
478
+ }
479
+ logWarn(`[cron:${params.job.id}] ${String(err)}`);
371
480
  }
372
- return { status: "ok", summary, outputText };
373
481
  }
374
482
  }
375
- return { status: "ok", summary, outputText };
483
+ return withRunSession({ status: "ok", summary, outputText });
376
484
  }
@@ -21,6 +21,8 @@ export function resolveCronSession(params) {
21
21
  lastChannel: entry?.lastChannel,
22
22
  lastTo: entry?.lastTo,
23
23
  lastAccountId: entry?.lastAccountId,
24
+ label: entry?.label,
25
+ displayName: entry?.displayName,
24
26
  skillsSnapshot: entry?.skillsSnapshot,
25
27
  };
26
28
  return { storePath, store, sessionEntry, systemSent, isNewSession: true };