visionclaw 0.1.219-dev.codex-issue-2805-worker-runtime.2 → 0.1.219-dev.codex-fix-2941-activity-foreground.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 (313) hide show
  1. package/dist/agent/api-proxy.d.ts +16 -0
  2. package/dist/agent/api-proxy.d.ts.map +1 -1
  3. package/dist/agent/api-proxy.js +8 -9
  4. package/dist/agent/api-proxy.js.map +1 -1
  5. package/dist/agent/browser-launcher.d.ts +0 -3
  6. package/dist/agent/browser-launcher.d.ts.map +1 -1
  7. package/dist/agent/browser-launcher.js +43 -101
  8. package/dist/agent/browser-launcher.js.map +1 -1
  9. package/dist/agent/coding-conventions.d.ts +3 -2
  10. package/dist/agent/coding-conventions.d.ts.map +1 -1
  11. package/dist/agent/coding-conventions.js +8 -7
  12. package/dist/agent/coding-conventions.js.map +1 -1
  13. package/dist/agent/command-handlers.d.ts.map +1 -1
  14. package/dist/agent/command-handlers.js +16 -4
  15. package/dist/agent/command-handlers.js.map +1 -1
  16. package/dist/agent/context-action-restart-fallback.d.ts.map +1 -1
  17. package/dist/agent/context-action-restart-fallback.js +3 -9
  18. package/dist/agent/context-action-restart-fallback.js.map +1 -1
  19. package/dist/agent/context-refresh.d.ts.map +1 -1
  20. package/dist/agent/context-refresh.js +7 -0
  21. package/dist/agent/context-refresh.js.map +1 -1
  22. package/dist/agent/context.d.ts.map +1 -1
  23. package/dist/agent/context.js +8 -6
  24. package/dist/agent/context.js.map +1 -1
  25. package/dist/agent/engine-fallback.d.ts +35 -0
  26. package/dist/agent/engine-fallback.d.ts.map +1 -1
  27. package/dist/agent/engine-fallback.js +3 -4
  28. package/dist/agent/engine-fallback.js.map +1 -1
  29. package/dist/agent/engines/browser-tool-policy.d.ts +1 -12
  30. package/dist/agent/engines/browser-tool-policy.d.ts.map +1 -1
  31. package/dist/agent/engines/browser-tool-policy.js +2 -17
  32. package/dist/agent/engines/browser-tool-policy.js.map +1 -1
  33. package/dist/agent/engines/claude/session.d.ts +0 -1
  34. package/dist/agent/engines/claude/session.d.ts.map +1 -1
  35. package/dist/agent/engines/claude/session.js +36 -21
  36. package/dist/agent/engines/claude/session.js.map +1 -1
  37. package/dist/agent/engines/pi/tools.d.ts.map +1 -1
  38. package/dist/agent/engines/pi/tools.js +10 -2
  39. package/dist/agent/engines/pi/tools.js.map +1 -1
  40. package/dist/agent/engines/pi-coding/session.d.ts +0 -3
  41. package/dist/agent/engines/pi-coding/session.d.ts.map +1 -1
  42. package/dist/agent/engines/pi-coding/session.js +22 -19
  43. package/dist/agent/engines/pi-coding/session.js.map +1 -1
  44. package/dist/agent/engines/session-types.d.ts +0 -6
  45. package/dist/agent/engines/session-types.d.ts.map +1 -1
  46. package/dist/agent/external-context/runtime.d.ts.map +1 -1
  47. package/dist/agent/external-context/runtime.js +14 -15
  48. package/dist/agent/external-context/runtime.js.map +1 -1
  49. package/dist/agent/fast-responder.d.ts +20 -4
  50. package/dist/agent/fast-responder.d.ts.map +1 -1
  51. package/dist/agent/fast-responder.js +39 -9
  52. package/dist/agent/fast-responder.js.map +1 -1
  53. package/dist/agent/interrupt-handler.d.ts +18 -4
  54. package/dist/agent/interrupt-handler.d.ts.map +1 -1
  55. package/dist/agent/interrupt-handler.js +284 -49
  56. package/dist/agent/interrupt-handler.js.map +1 -1
  57. package/dist/agent/lab-run-system-prompt.d.ts.map +1 -1
  58. package/dist/agent/lab-run-system-prompt.js +0 -3
  59. package/dist/agent/lab-run-system-prompt.js.map +1 -1
  60. package/dist/agent/lifecycle-trace.d.ts.map +1 -1
  61. package/dist/agent/lifecycle-trace.js +5 -6
  62. package/dist/agent/lifecycle-trace.js.map +1 -1
  63. package/dist/agent/loop.d.ts +1 -4
  64. package/dist/agent/loop.d.ts.map +1 -1
  65. package/dist/agent/loop.js +129 -105
  66. package/dist/agent/loop.js.map +1 -1
  67. package/dist/agent/memory-protocol.d.ts +2 -2
  68. package/dist/agent/memory-protocol.d.ts.map +1 -1
  69. package/dist/agent/memory-protocol.js +15 -3
  70. package/dist/agent/memory-protocol.js.map +1 -1
  71. package/dist/agent/memory-store.d.ts.map +1 -1
  72. package/dist/agent/memory-store.js +1 -11
  73. package/dist/agent/memory-store.js.map +1 -1
  74. package/dist/agent/model-provider.d.ts +0 -2
  75. package/dist/agent/model-provider.d.ts.map +1 -1
  76. package/dist/agent/model-provider.js +25 -31
  77. package/dist/agent/model-provider.js.map +1 -1
  78. package/dist/agent/product-identity.d.ts +4 -5
  79. package/dist/agent/product-identity.d.ts.map +1 -1
  80. package/dist/agent/product-identity.js +6 -7
  81. package/dist/agent/product-identity.js.map +1 -1
  82. package/dist/agent/public-activity.d.ts +1 -1
  83. package/dist/agent/public-activity.d.ts.map +1 -1
  84. package/dist/agent/public-activity.js.map +1 -1
  85. package/dist/agent/reply-watchdog.d.ts +3 -5
  86. package/dist/agent/reply-watchdog.d.ts.map +1 -1
  87. package/dist/agent/reply-watchdog.js +4 -6
  88. package/dist/agent/reply-watchdog.js.map +1 -1
  89. package/dist/agent/session-manager.d.ts +0 -5
  90. package/dist/agent/session-manager.d.ts.map +1 -1
  91. package/dist/agent/session-manager.js +0 -7
  92. package/dist/agent/session-manager.js.map +1 -1
  93. package/dist/agent/state.d.ts +0 -7
  94. package/dist/agent/state.d.ts.map +1 -1
  95. package/dist/agent/state.js +11 -21
  96. package/dist/agent/state.js.map +1 -1
  97. package/dist/agent/stream-handler.d.ts.map +1 -1
  98. package/dist/agent/stream-handler.js +17 -0
  99. package/dist/agent/stream-handler.js.map +1 -1
  100. package/dist/agent/system-prompt.d.ts.map +1 -1
  101. package/dist/agent/system-prompt.js +6 -10
  102. package/dist/agent/system-prompt.js.map +1 -1
  103. package/dist/agent/transcript/transcript-indexer.d.ts +28 -0
  104. package/dist/agent/transcript/transcript-indexer.d.ts.map +1 -1
  105. package/dist/agent/transcript/transcript-indexer.js +57 -58
  106. package/dist/agent/transcript/transcript-indexer.js.map +1 -1
  107. package/dist/agent/usage/usage-report-queue.d.ts.map +1 -1
  108. package/dist/agent/usage/usage-report-queue.js +52 -53
  109. package/dist/agent/usage/usage-report-queue.js.map +1 -1
  110. package/dist/agent/wake-cycle-runner.d.ts +8 -7
  111. package/dist/agent/wake-cycle-runner.d.ts.map +1 -1
  112. package/dist/agent/wake-cycle-runner.js +66 -105
  113. package/dist/agent/wake-cycle-runner.js.map +1 -1
  114. package/dist/builtin-skills/owner-manual/SKILL.md +2 -2
  115. package/dist/calendar/occurrence-ownership.d.ts.map +1 -1
  116. package/dist/calendar/occurrence-ownership.js +5 -6
  117. package/dist/calendar/occurrence-ownership.js.map +1 -1
  118. package/dist/calendar/scheduler.d.ts.map +1 -1
  119. package/dist/calendar/scheduler.js +3 -4
  120. package/dist/calendar/scheduler.js.map +1 -1
  121. package/dist/channels/interface.d.ts +2 -8
  122. package/dist/channels/interface.d.ts.map +1 -1
  123. package/dist/channels/interface.js +1 -8
  124. package/dist/channels/interface.js.map +1 -1
  125. package/dist/channels/manager.d.ts.map +1 -1
  126. package/dist/channels/manager.js +0 -2
  127. package/dist/channels/manager.js.map +1 -1
  128. package/dist/channels/qq-status.d.ts.map +1 -1
  129. package/dist/channels/qq-status.js +1 -0
  130. package/dist/channels/qq-status.js.map +1 -1
  131. package/dist/channels/queue.d.ts.map +1 -1
  132. package/dist/channels/queue.js +0 -8
  133. package/dist/channels/queue.js.map +1 -1
  134. package/dist/channels/telegram-status.d.ts.map +1 -1
  135. package/dist/channels/telegram-status.js +2 -0
  136. package/dist/channels/telegram-status.js.map +1 -1
  137. package/dist/channels/tui.d.ts +2 -2
  138. package/dist/channels/tui.d.ts.map +1 -1
  139. package/dist/channels/tui.js +2 -7
  140. package/dist/channels/tui.js.map +1 -1
  141. package/dist/config/index.d.ts.map +1 -1
  142. package/dist/config/index.js +15 -16
  143. package/dist/config/index.js.map +1 -1
  144. package/dist/config/types.d.ts +8 -16
  145. package/dist/config/types.d.ts.map +1 -1
  146. package/dist/config/types.js +24 -13
  147. package/dist/config/types.js.map +1 -1
  148. package/dist/email/email-route-store.d.ts.map +1 -1
  149. package/dist/email/email-route-store.js +5 -6
  150. package/dist/email/email-route-store.js.map +1 -1
  151. package/dist/i18n/messages.d.ts +0 -12
  152. package/dist/i18n/messages.d.ts.map +1 -1
  153. package/dist/i18n/messages.js +9 -21
  154. package/dist/i18n/messages.js.map +1 -1
  155. package/dist/logger.d.ts +0 -2
  156. package/dist/logger.d.ts.map +1 -1
  157. package/dist/logger.js +1 -19
  158. package/dist/logger.js.map +1 -1
  159. package/dist/threads/active-threads-store.d.ts.map +1 -1
  160. package/dist/threads/active-threads-store.js +27 -8
  161. package/dist/threads/active-threads-store.js.map +1 -1
  162. package/dist/threads/execution-host.d.ts.map +1 -1
  163. package/dist/threads/execution-host.js +0 -1
  164. package/dist/threads/execution-host.js.map +1 -1
  165. package/dist/threads/execution-scheduler.d.ts +0 -4
  166. package/dist/threads/execution-scheduler.d.ts.map +1 -1
  167. package/dist/threads/execution-scheduler.js +2 -38
  168. package/dist/threads/execution-scheduler.js.map +1 -1
  169. package/dist/threads/execution-store.d.ts +5 -20
  170. package/dist/threads/execution-store.d.ts.map +1 -1
  171. package/dist/threads/execution-store.js +8 -45
  172. package/dist/threads/execution-store.js.map +1 -1
  173. package/dist/threads/foreground-task.d.ts +6 -10
  174. package/dist/threads/foreground-task.d.ts.map +1 -1
  175. package/dist/threads/foreground-task.js +26 -34
  176. package/dist/threads/foreground-task.js.map +1 -1
  177. package/dist/threads/foreman-client.d.ts.map +1 -1
  178. package/dist/threads/foreman-client.js +1 -3
  179. package/dist/threads/foreman-client.js.map +1 -1
  180. package/dist/threads/foreman-execution.d.ts +12 -12
  181. package/dist/threads/foreman-execution.d.ts.map +1 -1
  182. package/dist/threads/foreman-execution.js +19 -30
  183. package/dist/threads/foreman-execution.js.map +1 -1
  184. package/dist/threads/foreman-host.d.ts +2 -17
  185. package/dist/threads/foreman-host.d.ts.map +1 -1
  186. package/dist/threads/foreman-host.js +59 -423
  187. package/dist/threads/foreman-host.js.map +1 -1
  188. package/dist/threads/foreman-protocol.d.ts +12 -109
  189. package/dist/threads/foreman-protocol.d.ts.map +1 -1
  190. package/dist/threads/foreman-protocol.js +15 -37
  191. package/dist/threads/foreman-protocol.js.map +1 -1
  192. package/dist/threads/foreman-store.d.ts +9 -36
  193. package/dist/threads/foreman-store.d.ts.map +1 -1
  194. package/dist/threads/foreman-store.js +19 -63
  195. package/dist/threads/foreman-store.js.map +1 -1
  196. package/dist/threads/generic-execution.d.ts +2 -2
  197. package/dist/threads/generic-execution.d.ts.map +1 -1
  198. package/dist/threads/generic-execution.js +6 -9
  199. package/dist/threads/generic-execution.js.map +1 -1
  200. package/dist/threads/ipc.d.ts +0 -3
  201. package/dist/threads/ipc.d.ts.map +1 -1
  202. package/dist/threads/ipc.js.map +1 -1
  203. package/dist/threads/relay-adapter.d.ts +2 -0
  204. package/dist/threads/relay-adapter.d.ts.map +1 -1
  205. package/dist/threads/relay-adapter.js +7 -1
  206. package/dist/threads/relay-adapter.js.map +1 -1
  207. package/dist/threads/supervisor.d.ts +51 -21
  208. package/dist/threads/supervisor.d.ts.map +1 -1
  209. package/dist/threads/supervisor.js +449 -160
  210. package/dist/threads/supervisor.js.map +1 -1
  211. package/dist/threads/task-contracts.d.ts +194 -0
  212. package/dist/threads/task-contracts.d.ts.map +1 -0
  213. package/dist/threads/task-contracts.js +82 -0
  214. package/dist/threads/task-contracts.js.map +1 -0
  215. package/dist/threads/task-sidecar.d.ts +145 -0
  216. package/dist/threads/task-sidecar.d.ts.map +1 -0
  217. package/dist/threads/task-sidecar.js +311 -0
  218. package/dist/threads/task-sidecar.js.map +1 -0
  219. package/dist/threads/task-store.d.ts +172 -0
  220. package/dist/threads/task-store.d.ts.map +1 -0
  221. package/dist/threads/task-store.js +599 -0
  222. package/dist/threads/task-store.js.map +1 -0
  223. package/dist/threads/thread-activity-controller.d.ts +7 -0
  224. package/dist/threads/thread-activity-controller.d.ts.map +1 -1
  225. package/dist/threads/thread-activity-controller.js +52 -2
  226. package/dist/threads/thread-activity-controller.js.map +1 -1
  227. package/dist/threads/thread-task-context.d.ts +40 -0
  228. package/dist/threads/thread-task-context.d.ts.map +1 -0
  229. package/dist/threads/thread-task-context.js +160 -0
  230. package/dist/threads/thread-task-context.js.map +1 -0
  231. package/dist/threads/thread-task-controller.d.ts +122 -0
  232. package/dist/threads/thread-task-controller.d.ts.map +1 -0
  233. package/dist/threads/thread-task-controller.js +276 -0
  234. package/dist/threads/thread-task-controller.js.map +1 -0
  235. package/dist/threads/thread-task-runtime.d.ts +350 -0
  236. package/dist/threads/thread-task-runtime.d.ts.map +1 -0
  237. package/dist/threads/thread-task-runtime.js +896 -0
  238. package/dist/threads/thread-task-runtime.js.map +1 -0
  239. package/dist/threads/worker-entry.d.ts.map +1 -1
  240. package/dist/threads/worker-entry.js +28 -29
  241. package/dist/threads/worker-entry.js.map +1 -1
  242. package/dist/threads/worker-execution-runtime.d.ts +2 -3
  243. package/dist/threads/worker-execution-runtime.d.ts.map +1 -1
  244. package/dist/threads/worker-execution-runtime.js +2 -5
  245. package/dist/threads/worker-execution-runtime.js.map +1 -1
  246. package/dist/tools/delegation.d.ts +2 -71
  247. package/dist/tools/delegation.d.ts.map +1 -1
  248. package/dist/tools/delegation.js +28 -62
  249. package/dist/tools/delegation.js.map +1 -1
  250. package/dist/tools/email.d.ts +1 -1
  251. package/dist/tools/execution-control.d.ts +0 -5
  252. package/dist/tools/execution-control.d.ts.map +1 -1
  253. package/dist/tools/execution-control.js +2 -9
  254. package/dist/tools/execution-control.js.map +1 -1
  255. package/dist/tools/index.d.ts.map +1 -1
  256. package/dist/tools/index.js +6 -2
  257. package/dist/tools/index.js.map +1 -1
  258. package/dist/tools/mcp-manager.d.ts +1 -1
  259. package/dist/tools/memory.d.ts +1 -1
  260. package/dist/tools/notify.d.ts +0 -2
  261. package/dist/tools/notify.d.ts.map +1 -1
  262. package/dist/tools/notify.js +5 -21
  263. package/dist/tools/notify.js.map +1 -1
  264. package/dist/tools/skill.d.ts +1 -1
  265. package/dist/tools/thread-tasks.d.ts +23 -0
  266. package/dist/tools/thread-tasks.d.ts.map +1 -0
  267. package/dist/tools/thread-tasks.js +122 -0
  268. package/dist/tools/thread-tasks.js.map +1 -0
  269. package/dist/tools/totp.d.ts +1 -1
  270. package/dist/tui/request-store.d.ts +1 -1
  271. package/dist/tui/request-store.d.ts.map +1 -1
  272. package/dist/tui/request-store.js +2 -2
  273. package/dist/tui/request-store.js.map +1 -1
  274. package/dist/tui/server.d.ts.map +1 -1
  275. package/dist/tui/server.js +2 -17
  276. package/dist/tui/server.js.map +1 -1
  277. package/dist/utils/restart.d.ts.map +1 -1
  278. package/dist/utils/restart.js +0 -6
  279. package/dist/utils/restart.js.map +1 -1
  280. package/dist-agent/bundle.cjs +2 -2
  281. package/package.json +1 -1
  282. package/dist/agent/runtime-context.d.ts +0 -27
  283. package/dist/agent/runtime-context.d.ts.map +0 -1
  284. package/dist/agent/runtime-context.js +0 -60
  285. package/dist/agent/runtime-context.js.map +0 -1
  286. package/dist/agent/runtime-disclosure-policy.d.ts +0 -6
  287. package/dist/agent/runtime-disclosure-policy.d.ts.map +0 -1
  288. package/dist/agent/runtime-disclosure-policy.js +0 -16
  289. package/dist/agent/runtime-disclosure-policy.js.map +0 -1
  290. package/dist/conversations/lazy-runtime.d.ts +0 -5
  291. package/dist/conversations/lazy-runtime.d.ts.map +0 -1
  292. package/dist/conversations/lazy-runtime.js +0 -12
  293. package/dist/conversations/lazy-runtime.js.map +0 -1
  294. package/dist/conversations/runtime.d.ts +0 -17
  295. package/dist/conversations/runtime.d.ts.map +0 -1
  296. package/dist/conversations/runtime.js +0 -92
  297. package/dist/conversations/runtime.js.map +0 -1
  298. package/dist/conversations/scheduler.d.ts +0 -57
  299. package/dist/conversations/scheduler.d.ts.map +0 -1
  300. package/dist/conversations/scheduler.js +0 -175
  301. package/dist/conversations/scheduler.js.map +0 -1
  302. package/dist/threads/foreman-business.d.ts +0 -9
  303. package/dist/threads/foreman-business.d.ts.map +0 -1
  304. package/dist/threads/foreman-business.js +0 -117
  305. package/dist/threads/foreman-business.js.map +0 -1
  306. package/dist/threads/foreman-delivery.d.ts +0 -5
  307. package/dist/threads/foreman-delivery.d.ts.map +0 -1
  308. package/dist/threads/foreman-delivery.js +0 -21
  309. package/dist/threads/foreman-delivery.js.map +0 -1
  310. package/dist/threads/task-workspace.d.ts +0 -8
  311. package/dist/threads/task-workspace.d.ts.map +0 -1
  312. package/dist/threads/task-workspace.js +0 -52
  313. package/dist/threads/task-workspace.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { createHash, randomUUID } from "node:crypto";
1
+ import { randomUUID } from "node:crypto";
2
2
  import { DEFAULT_THREAD_ID } from "../channels/interface.js";
3
3
  import { isDeliveryTarget } from "../channels/delivery-target.js";
4
4
  import { taskContinuationRouting } from "../agent/reply-target.js";
@@ -6,10 +6,7 @@ import { resolveWorkspaceCwd } from "../agent/workspace-cwd.js";
6
6
  import { t } from "../i18n/index.js";
7
7
  import { logger } from "../logger.js";
8
8
  import { ExecutionCommandConflictError, isTerminalExecutionState, } from "./execution-store.js";
9
- import { DEFAULT_LAB_RUN_BUDGET } from "./execution-snapshot.js";
10
- import { prepareTaskWorkspace } from "./task-workspace.js";
11
- import { publicTaskText, taskDeliveryMode } from "./foreman-delivery.js";
12
- import { CancelArgsSchema, DelegateArgsSchema, DelegateBatchArgsSchema, FOREMAN_EVENT_META_KEY, FOREMAN_SCOPE, InputArgsSchema, StatusArgsSchema, } from "./foreman-protocol.js";
9
+ import { CancelArgsSchema, DelegateArgsSchema, FOREMAN_EVENT_META_KEY, FOREMAN_SCOPE, InputArgsSchema, StatusArgsSchema, } from "./foreman-protocol.js";
13
10
  /**
14
11
  * The Foreman host (#2805 stage 3), in the main process.
15
12
  *
@@ -42,23 +39,6 @@ function textOf(value, fallback = "") {
42
39
  function clip(text, max) {
43
40
  return text.length <= max ? text : `${text.slice(0, max)}…`;
44
41
  }
45
- function identity(...parts) {
46
- return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
47
- }
48
- function taskIdFor(conversation, dispatch, key) {
49
- return `task-${identity(conversation, dispatch, key)}`;
50
- }
51
- function remainingBudget(limits, attempts) {
52
- const used = (field) => attempts.reduce((sum, attempt) => {
53
- const value = (attempt.usage ?? attempt.result?.usage)?.[field];
54
- return sum + (typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0);
55
- }, 0);
56
- return {
57
- maxModelAttempts: limits.maxModelAttempts - used("modelAttempts"),
58
- maxToolCalls: limits.maxToolCalls - used("toolCalls"),
59
- maxDurationMs: limits.maxDurationMs - used("elapsedMs"),
60
- };
61
- }
62
42
  function describeState(record) {
63
43
  switch (record.state) {
64
44
  case "queued": return "queued, waiting for a worker slot";
@@ -77,13 +57,10 @@ function fallbackKey(obligation) {
77
57
  return "foreman.fallback.succeeded";
78
58
  if (obligation.status === "cancelled")
79
59
  return "foreman.fallback.cancelled";
80
- if (obligation.status === "partial")
81
- return "foreman.fallback.partial";
82
60
  return "foreman.fallback.failed";
83
61
  }
84
62
  export class ForemanHost {
85
63
  deps;
86
- pendingBatches = new Map();
87
64
  timer = null;
88
65
  sweeping = false;
89
66
  now;
@@ -104,33 +81,13 @@ export class ForemanHost {
104
81
  clearInterval(this.timer);
105
82
  this.timer = null;
106
83
  }
107
- /** The supervisor supplies executor identity; model arguments never select an account. */
108
- async readBusiness(executionId, tool, args, workspace) {
109
- const record = this.deps.executions.get(executionId);
110
- const dispatch = record ? this.ownedDispatch(record.caller.ref, record.taskId) : null;
111
- if (record?.caller.kind !== "general" || record.state !== "running" || !record.payload.businessRead || !dispatch?.metadata.businessRead
112
- || !this.deps.canReadBusiness?.(dispatch.threadId, dispatch.replyTarget) || !this.deps.readBusiness) {
113
- return fail("This execution is not authorized to read the owner's workspace");
114
- }
115
- if (record.payload.workspaceMode === "read_only" && tool === "manage_drive"
116
- && typeof args === "object" && args !== null && "action" in args && args.action === "download_file") {
117
- return fail("A read-only Task cannot download files into its workspace");
118
- }
119
- const stillAuthorized = () => {
120
- const current = this.deps.executions.get(executionId);
121
- return current?.state === "running" && current.workerInstanceId === record.workerInstanceId
122
- && this.deps.canReadBusiness?.(dispatch.threadId, dispatch.replyTarget) === true;
123
- };
124
- const result = await this.deps.readBusiness(tool, args, workspace, stillAuthorized);
125
- return stillAuthorized() ? result : fail("The execution stopped or its business-read permission was revoked");
126
- }
127
- async handle(threadId, command) {
84
+ handle(threadId, command) {
128
85
  try {
129
- return await this.run(threadId, command);
86
+ return Promise.resolve(this.run(threadId, command));
130
87
  }
131
88
  catch (error) {
132
89
  logger.warn(`[foreman] ${command.type} from thread=${threadId} failed: ${errorMessage(error)}`);
133
- return fail(errorMessage(error));
90
+ return Promise.resolve(fail(errorMessage(error)));
134
91
  }
135
92
  }
136
93
  // ----------------------------------------------------------- delivery
@@ -147,25 +104,11 @@ export class ForemanHost {
147
104
  return Promise.resolve(true);
148
105
  const threadId = caller.ref;
149
106
  const dispatch = this.ownedDispatch(threadId, event.taskId);
150
- if (dispatch?.metadata.parentTaskId && event.type === "result") {
151
- return this.deliverParentResult(threadId, dispatch.metadata.parentTaskId);
152
- }
153
- // A retried Task may still have an older result/question in the outbox.
154
- // A transport replay must not revive that superseded attempt's delivery.
155
- const latest = dispatch && !dispatch.metadata.children?.length ? this.latest(dispatch.taskId) : null;
156
- if (latest && latest.executionId !== event.executionId)
157
- return Promise.resolve(true);
158
- if (event.type === "result" && this.deps.store.isUserCancelled(event.taskId))
159
- return Promise.resolve(true);
160
107
  const userCancelled = event.type === "result" && event.payload.status === "cancelled"
161
108
  && (event.payload.userInitiated === true || this.deps.store.isUserCancelled(event.taskId));
162
- const publicText = publicTaskText(event);
163
- const direct = Boolean(dispatch?.replyTarget && !userCancelled && publicText !== null && taskDeliveryMode(event) === "direct"
164
- && !dispatch.metadata.children?.length);
165
109
  // Queue first, then open the obligation: both are idempotent per event, so
166
110
  // a crash in between is repaired by the outbox's redelivery.
167
- if (!direct)
168
- this.deps.enqueueToGeneral(threadId, this.eventMessage(event, threadId, dispatch, dispatch !== null && !userCancelled));
111
+ this.deps.enqueueToGeneral(threadId, this.eventMessage(event, threadId, dispatch, dispatch !== null && !userCancelled));
169
112
  if (dispatch) {
170
113
  this.deps.store.open({
171
114
  taskId: event.taskId,
@@ -174,36 +117,23 @@ export class ForemanHost {
174
117
  kind: event.type,
175
118
  status: event.type === "question" ? "question" : String(event.payload.status),
176
119
  userInitiated: userCancelled,
177
- publicText,
178
- deliveryMode: direct ? "direct" : "review",
179
120
  });
180
121
  }
181
- return direct ? this.sweep().then(() => true) : Promise.resolve(true);
122
+ return Promise.resolve(true);
182
123
  }
183
- /** Deliver the public event content for every obligation that is due. */
124
+ /** Send the fixed notice for every obligation that is due. Safe to call at any time. */
184
125
  async sweep() {
185
126
  if (this.sweeping)
186
127
  return;
187
128
  this.sweeping = true;
188
129
  try {
189
- for (const cancel of this.deps.store.groupCancels()) {
190
- const parent = this.deps.store.dispatch(cancel.taskId);
191
- if (parent)
192
- this.cancelChildren(parent, cancel.reason, cancel.byUser);
193
- }
194
130
  for (const obligation of this.deps.store.due()) {
195
131
  const dispatch = this.deps.store.dispatch(obligation.taskId);
196
132
  if (!dispatch?.replyTarget) {
197
133
  this.deps.store.fallbackFailed(obligation.taskId, obligation.eventId, "No reply target was recorded for this task");
198
134
  continue;
199
135
  }
200
- const legacyEvent = obligation.publicText === null
201
- ? this.allEvents(obligation.executionId).find((event) => event.eventId === obligation.eventId) : undefined;
202
- const publicText = obligation.publicText ?? (legacyEvent ? publicTaskText(legacyEvent) : null);
203
- const text = obligation.deliveryMode === "direct" && publicText ? `${dispatch.title}\n\n${publicText}`
204
- : obligation.deliveryMode === "review" && obligation.kind === "result"
205
- ? t("foreman.fallback.review", this.deps.locale(), dispatch.title)
206
- : [t(fallbackKey(obligation), this.deps.locale(), dispatch.title), publicText].filter(Boolean).join("\n\n");
136
+ const text = t(fallbackKey(obligation), this.deps.locale(), dispatch.title);
207
137
  let result;
208
138
  try {
209
139
  result = await this.deps.deliver(dispatch.replyTarget, text, dispatch.threadId);
@@ -212,11 +142,8 @@ export class ForemanHost {
212
142
  result = { ok: false, errorMessage: errorMessage(error) };
213
143
  }
214
144
  if (result.ok) {
215
- if (obligation.deliveryMode === "direct")
216
- this.deps.store.markDelivered(obligation.taskId, obligation.kind === "result" ? "completion" : undefined, obligation.eventId);
217
- else
218
- this.deps.store.fallbackSent(obligation.taskId, obligation.eventId);
219
- logger.system(`[foreman] Sent ${obligation.deliveryMode === "direct" ? "direct delivery" : "fallback notice"} for task=${obligation.taskId} event=${obligation.eventId} (${obligation.kind})`);
145
+ this.deps.store.fallbackSent(obligation.taskId, obligation.eventId);
146
+ logger.system(`[foreman] Sent the delivery fallback for task=${obligation.taskId} event=${obligation.eventId} (${obligation.kind})`);
220
147
  continue;
221
148
  }
222
149
  const after = this.deps.store.fallbackFailed(obligation.taskId, obligation.eventId, result.errorMessage);
@@ -237,25 +164,11 @@ export class ForemanHost {
237
164
  // ----------------------------------------------------------- commands
238
165
  run(threadId, command) {
239
166
  switch (command.type) {
240
- case "business_read": return fail("Business reads require an admitted execution connection");
241
- case "delegate": return this.deps.allowDelegation?.() === false ? fail("New Task delegation is disabled") : this.delegate(threadId, command);
242
- case "delegate_batch": return this.deps.allowDelegation?.() === false ? fail("New Task delegation is disabled") : this.delegateBatch(threadId, command);
167
+ case "delegate": return this.delegate(threadId, command);
243
168
  case "status": return this.status(threadId, command.args);
244
169
  case "input": return this.input(threadId, command.args);
245
170
  case "cancel": return this.cancel(threadId, command.args);
246
- case "cancel_all": {
247
- const roots = this.deps.store.allForThread(threadId).filter((task) => !task.metadata.parentTaskId && this.isOpen(task));
248
- const results = roots.map((task) => this.cancel(threadId, { task_id: task.taskId, requested_by_user: true, reason: "User stopped this conversation's tasks" }));
249
- return {
250
- ok: results.every((result) => result.ok),
251
- text: roots.length > 0 ? [
252
- t("foreman.cancel_all", this.deps.locale(), String(roots.length)),
253
- ...roots.slice(0, 10).map((task) => `- ${task.title}`),
254
- ...results.filter((result) => !result.ok).map((result) => result.text),
255
- ].join("\n") : "",
256
- };
257
- }
258
- case "delivered": return this.delivered(threadId, command.taskId, command.purpose, command.eventId);
171
+ case "delivered": return this.delivered(threadId, command.taskId, command.purpose);
259
172
  case "turn_ended": return this.turnEnded(threadId, command.events);
260
173
  case "summary": return { ok: true, text: this.summary(threadId) };
261
174
  }
@@ -271,177 +184,15 @@ export class ForemanHost {
271
184
  latest(taskId) {
272
185
  return this.deps.executions.latestForTask(taskId, FOREMAN_SCOPE);
273
186
  }
274
- childRecords(parent) {
275
- return (parent.metadata.children ?? []).map((taskId) => ({ taskId, record: this.latest(taskId) }));
276
- }
277
- parentState(parent) {
278
- const children = this.childRecords(parent);
279
- if (children.every((child) => child.record === null))
280
- return "not_started";
281
- if (children.some((child) => child.record === null || !isTerminalExecutionState(child.record.state))) {
282
- if (children.some((child) => child.record?.state === "running"))
283
- return "running";
284
- if (children.some((child) => child.record?.state === "cancel_requested"))
285
- return "cancel_requested";
286
- if (children.some((child) => child.record?.state === "waiting_input"))
287
- return "waiting_input";
288
- return "queued";
289
- }
290
- if (children.every((child) => child.record?.state === "succeeded"))
291
- return "succeeded";
292
- if (children.every((child) => child.record?.state === "cancelled") || this.deps.store.groupCancels().some((item) => item.taskId === parent.taskId))
293
- return "cancelled";
294
- if (children.some((child) => child.record?.state === "succeeded"))
295
- return "partial";
296
- return "failed";
297
- }
298
- deliverParentResult(threadId, taskId) {
299
- const parent = this.ownedDispatch(threadId, taskId);
300
- if (!parent?.metadata.children?.length)
301
- return Promise.resolve(false);
302
- const children = this.childRecords(parent);
303
- if (children.some((child) => !child.record || !isTerminalExecutionState(child.record.state)))
304
- return Promise.resolve(true);
305
- const records = children.map((child) => child.record).filter((record) => record !== null);
306
- // The set of attempts is the result version. A late event from an old
307
- // attempt cannot create a second delivery for the current result version.
308
- const eventId = `evt-task-${identity(taskId, ...records.map((record) => record.executionId))}`;
309
- const status = this.parentState(parent);
310
- const summary = records.map((record) => {
311
- const childId = record.taskId;
312
- const title = this.deps.store.dispatch(childId)?.title ?? childId;
313
- return `${childId} "${title}": ${record.state}. ${clip(record.result?.summary ?? record.result?.error?.message ?? "", 400)}`;
314
- }).join("\n");
315
- const event = {
316
- protocolVersion: 1, eventId, seq: 0, taskId, executionId: taskId, type: "result",
317
- createdAt: records.map((record) => record.updatedAt).sort().at(-1) ?? this.now().toISOString(),
318
- payload: {
319
- status, summary,
320
- data: { userMessage: children.map(({ taskId: childId, record }) => {
321
- const message = publicTaskText({ type: "result", payload: { data: record?.result?.data } });
322
- const title = this.deps.store.dispatch(childId)?.title ?? childId;
323
- return message ? `${title}\n${message}` : null;
324
- }).filter(Boolean).join("\n\n") },
325
- artifactRefs: records.flatMap((record) => record.result?.artifactRefs ?? []).slice(0, 30),
326
- userInitiated: status === "cancelled" && this.deps.store.isUserCancelled(taskId),
327
- },
328
- };
329
- return this.deliverEvent(event, { kind: "general", ref: threadId });
330
- }
331
- cancelChildren(parent, reason, byUser) {
332
- for (const { taskId, record } of this.childRecords(parent)) {
333
- if (!record || isTerminalExecutionState(record.state))
334
- continue;
335
- if (byUser)
336
- this.deps.store.markUserCancelled(taskId);
337
- this.deps.store.withdraw(taskId);
338
- this.deps.executions.cancel(this.envelope(parent.threadId, `cancel-${identity(parent.taskId, record.executionId, reason, String(byUser))}`), {
339
- executionId: record.executionId, reason, userInitiated: byUser,
340
- });
341
- }
342
- }
343
- delegateBatch(threadId, command) {
344
- const parsed = DelegateBatchArgsSchema.safeParse(command.args);
345
- if (!parsed.success)
346
- return Promise.resolve(invalid(parsed.error));
347
- const key = identity(threadId, command.dispatchKey, parsed.data.title);
348
- const specification = JSON.stringify(parsed.data);
349
- const pending = this.pendingBatches.get(key);
350
- if (pending)
351
- return pending.specification === specification ? pending.result : Promise.resolve(fail("Batch identity is already being prepared with different content"));
352
- const result = this.prepareBatch(threadId, command);
353
- this.pendingBatches.set(key, { specification, result });
354
- void result.finally(() => { this.pendingBatches.delete(key); }).catch(() => { });
355
- return result;
356
- }
357
- async prepareBatch(threadId, command) {
358
- const parsed = DelegateBatchArgsSchema.safeParse(command.args);
359
- if (!parsed.success)
360
- return invalid(parsed.error);
361
- const args = parsed.data;
362
- if (new Set(args.tasks.map((task) => task.key)).size !== args.tasks.length)
363
- return fail("Task keys must be unique within a batch");
364
- const atomic = this.deps.executions.atomic;
365
- if (!atomic)
366
- return fail("This execution host does not support atomic task batches");
367
- const rootId = taskIdFor(threadId, command.dispatchKey, `batch:${args.title}`);
368
- const specification = identity(JSON.stringify(args));
369
- const previous = this.ownedDispatch(threadId, rootId);
370
- if (previous && previous.metadata.specification !== specification)
371
- return fail("This batch key was already used with different tasks; use a distinct title for new work");
372
- const ids = args.tasks.map((task) => taskIdFor(threadId, command.dispatchKey, `${rootId}:${task.key}`));
373
- const commits = new Map();
374
- // Filesystem preparation happens before the synchronous acceptance transaction.
375
- // No worker starts until every Task and command has been committed.
376
- const workspaces = [];
377
- const contexts = [];
378
- for (const [index, task] of args.tasks.entries()) {
379
- const existing = this.ownedDispatch(threadId, ids[index]);
380
- if (existing?.metadata.specification && existing.metadata.specification !== identity(specification, task.key))
381
- throw new Error(`Task key ${task.key} was already used with different content`);
382
- const source = task.context_task_id ? this.ownedDispatch(threadId, task.context_task_id) : null;
383
- const sourceRecord = source ? (existing?.metadata.contextExecutionId
384
- ? this.deps.executions.get(existing.metadata.contextExecutionId) : this.latest(source.taskId)) : null;
385
- if (task.context_task_id && (!sourceRecord || sourceRecord.taskId !== source?.taskId || !isTerminalExecutionState(sourceRecord.state))) {
386
- return fail("context_task_id must name a completed execution Task from this conversation");
387
- }
388
- contexts.push(sourceRecord?.executionId);
389
- const directory = task.project_dir ?? sourceRecord?.payload.workspaceRef ?? source?.workspace;
390
- workspaces.push(existing?.workspace ?? (directory
391
- ? await prepareTaskWorkspace(directory, ids[index], task.workspace_mode, commits)
392
- : undefined));
393
- }
394
- const parent = args.result_mode === "combined" && args.tasks.length > 1;
395
- const foreground = this.ownedDispatch(threadId, ids[0])?.metadata.foregroundCalls
396
- ?? Math.max(0, Math.trunc(command.foreground.toolCallsUsed));
397
- const budget = DEFAULT_LAB_RUN_BUDGET;
398
- return atomic(() => this.deps.store.atomic(() => {
399
- if (parent)
400
- this.deps.store.recordDispatch({
401
- taskId: rootId, threadId, title: args.title, replyTarget: isDeliveryTarget(command.replyTarget)
402
- ? command.replyTarget : threadId === DEFAULT_THREAD_ID ? this.deps.ownerTarget() : null,
403
- sender: command.sender ?? "", metadata: { children: ids, specification, sourceKey: command.dispatchKey },
404
- });
405
- for (const [index, task] of args.tasks.entries()) {
406
- const result = this.delegate(threadId, {
407
- type: "delegate", args: { ...task, project_dir: workspaces[index], workspace_mode: task.workspace_mode === "isolated" ? "shared" : task.workspace_mode },
408
- foreground: { taskId: ids[index], toolCallsUsed: index === 0 ? foreground : 0 },
409
- ...(parent ? { parentTaskId: rootId } : {}),
410
- preparedWorkspace: Boolean(workspaces[index] && task.workspace_mode === "isolated"),
411
- contextExecutionId: contexts[index],
412
- specification: identity(specification, task.key),
413
- sourceKey: command.dispatchKey,
414
- budget, replyTarget: command.replyTarget, sender: command.sender,
415
- });
416
- if (!result.ok)
417
- throw new Error(result.text);
418
- }
419
- return {
420
- ok: true, acceptedTaskId: parent ? rootId : ids[0], acceptedTaskIds: ids,
421
- text: `Accepted ${ids.length} Task(s)${parent ? ` under parent ${rootId} for one combined delivery` : " with independent delivery"}: ${args.tasks.map((task, i) => `${task.key}=${ids[i]}`).join(", ")}. Each Task has its own budget and runs when slots are available. You may delegate more independent work, coordinate existing Tasks, or reply. Do not repeat work assigned to these Tasks.`,
422
- };
423
- }));
424
- }
425
187
  delegate(threadId, command) {
426
- if (command.args && typeof command.args === "object" && "worker" in command.args && command.args.worker !== null && command.args.worker !== undefined) {
427
- return fail("Worker aliases are no longer used. Select a completed context_task_id, or send input to the existing Task.");
428
- }
429
188
  const parsed = DelegateArgsSchema.safeParse(command.args);
430
189
  if (!parsed.success)
431
190
  return invalid(parsed.error);
432
191
  const args = parsed.data;
433
192
  let retryOf = null;
434
- const retryDispatchId = args.retry_task_id && command.dispatchKey ? identity(threadId, command.dispatchKey) : undefined;
435
- const retrySpecification = retryDispatchId ? identity(JSON.stringify(args)) : undefined;
436
193
  if (args.retry_task_id) {
437
194
  if (!this.ownedDispatch(threadId, args.retry_task_id))
438
195
  return fail(`task ${args.retry_task_id} was not delegated from this conversation`);
439
- const replay = retryDispatchId ? this.deps.executions.attemptsForTask?.(args.retry_task_id, FOREMAN_SCOPE)
440
- .find((attempt) => attempt.payload.retryDispatchId === retryDispatchId) : undefined;
441
- if (replay)
442
- return replay.payload.retrySpecification === retrySpecification
443
- ? { ok: true, acceptedTaskId: replay.taskId, text: `Retry already accepted for task ${replay.taskId}, execution ${replay.executionId}: ${describeState(replay)}.` }
444
- : fail("This retry was already accepted with different content; use background_task_input or a new user instruction");
445
196
  retryOf = this.latest(args.retry_task_id);
446
197
  if (!retryOf)
447
198
  return fail(`task ${args.retry_task_id} has no earlier run to retry`);
@@ -449,34 +200,13 @@ export class ForemanHost {
449
200
  return fail(`task ${args.retry_task_id} is still ${describeState(retryOf)}; send changes with background_task_input instead`);
450
201
  }
451
202
  }
452
- const taskId = args.retry_task_id ?? (command.dispatchKey
453
- ? taskIdFor(threadId, command.dispatchKey, args.key ?? identity(JSON.stringify(args)))
454
- : command.foreground.taskId);
455
- const oldDispatch = this.ownedDispatch(threadId, taskId);
456
- if (command.parentTaskId && !this.ownedDispatch(threadId, command.parentTaskId)?.metadata.children?.includes(taskId)) {
457
- return fail("The parent Task did not admit this child");
458
- }
459
- const previous = args.context_task_id ? this.ownedDispatch(threadId, args.context_task_id) : null;
460
- const contextExecutionId = command.contextExecutionId ?? oldDispatch?.metadata.contextExecutionId;
461
- const previousRecord = previous ? (contextExecutionId
462
- ? this.deps.executions.get(contextExecutionId) : this.latest(previous.taskId)) : null;
463
- if (args.context_task_id) {
464
- if (args.retry_task_id)
465
- return fail("Use either context_task_id for new work or retry_task_id for another attempt, not both");
466
- if (!previousRecord || previousRecord.taskId !== previous?.taskId)
467
- return fail("context_task_id must name an execution Task from this conversation");
468
- if (!isTerminalExecutionState(previousRecord.state))
469
- return fail("That Task is unfinished; use background_task_input to continue it");
470
- }
203
+ const taskId = args.retry_task_id ?? command.foreground.taskId;
204
+ const previous = args.worker ? this.deps.store.lastForWorker(threadId, args.worker) : null;
471
205
  let workspace;
472
- if (retryOf && args.workspace_mode && (retryOf.payload.workspaceMode === "read_only") !== (args.workspace_mode === "read_only")) {
473
- return fail("A retry keeps its workspace access. Delegate a new Task to change read-only versus writable access");
474
- }
475
- const workspaceMode = retryOf?.payload.workspaceMode ?? (command.preparedWorkspace ? "isolated" : args.workspace_mode ?? "shared");
476
- const requestedDir = args.project_dir ?? retryOf?.payload.workspaceRef ?? oldDispatch?.workspace ?? previousRecord?.payload.workspaceRef ?? previous?.workspace ?? undefined;
206
+ const requestedDir = args.project_dir ?? previous?.workspace ?? undefined;
477
207
  if (requestedDir) {
478
208
  try {
479
- workspace = resolveWorkspaceCwd(requestedDir, { writable: workspaceMode !== "read_only" });
209
+ workspace = resolveWorkspaceCwd(requestedDir, { writable: true });
480
210
  }
481
211
  catch (error) {
482
212
  return fail(`project_dir: ${errorMessage(error)}. Nothing was delegated.`);
@@ -493,65 +223,41 @@ export class ForemanHost {
493
223
  title: args.title,
494
224
  replyTarget,
495
225
  sender: typeof command.sender === "string" ? command.sender : "",
226
+ worker: args.worker ?? null,
496
227
  workspace: workspace ?? null,
497
- metadata: {
498
- sourceKey: command.sourceKey ?? command.dispatchKey ?? (command.parentTaskId ? this.deps.store.dispatch(command.parentTaskId)?.metadata.sourceKey : undefined),
499
- contextTaskId: previous?.taskId,
500
- contextExecutionId: previousRecord?.executionId,
501
- businessRead: this.deps.canReadBusiness?.(threadId, replyTarget) ?? false,
502
- ...(command.parentTaskId ? { parentTaskId: command.parentTaskId } : {}),
503
- budget: command.budget ?? DEFAULT_LAB_RUN_BUDGET,
504
- foregroundCalls: command.foreground.toolCallsUsed,
505
- preparedWorkspace: command.preparedWorkspace,
506
- specification: command.specification,
507
- },
508
228
  });
509
229
  const context = [
510
230
  args.context,
511
- retryOf?.result?.summary ? `Previous attempt result: ${clip(retryOf.result.summary, 4_000)}` : undefined,
512
- retryOf?.payload.contextSummary,
513
231
  dispatch.sender ? `Requested by: ${dispatch.sender}` : undefined,
514
- previous && previous.taskId !== taskId ? this.previousWork(previous, previousRecord) : undefined,
232
+ previous && previous.taskId !== taskId ? this.previousWork(previous) : undefined,
515
233
  ].filter((part) => typeof part === "string" && part.length > 0);
516
- const toolCallsUsed = retryOf ? command.foreground.toolCallsUsed : dispatch.metadata.foregroundCalls ?? command.foreground.toolCallsUsed;
234
+ const toolCallsUsed = command.foreground.toolCallsUsed;
517
235
  const payload = {
518
236
  goal: args.goal,
519
- requirements: [...new Set([...(retryOf?.payload.requirements ?? []), ...args.requirements])],
520
- ...((args.expected_output ?? retryOf?.payload.expectedOutput) ? { expectedOutput: args.expected_output ?? retryOf?.payload.expectedOutput } : {}),
237
+ requirements: args.requirements,
238
+ ...(args.expected_output ? { expectedOutput: args.expected_output } : {}),
521
239
  ...(context.length > 0 ? { contextSummary: clip(context.join("\n\n"), 20_000) } : {}),
522
240
  checkpoint: {
523
241
  done: args.done,
524
242
  remaining: args.remaining,
525
243
  decisions: [],
526
244
  blockers: [],
527
- artifactRefs: [...new Set([...(previousRecord?.result?.artifactRefs ?? []), ...args.artifacts])],
245
+ artifactRefs: args.artifacts,
528
246
  uncertainOperations: args.uncertain_operations,
529
247
  },
530
248
  title: dispatch.title,
531
- businessRead: dispatch.metadata.businessRead === true,
532
249
  foregroundToolCallsUsed: Number.isInteger(toolCallsUsed) && toolCallsUsed >= 0 ? toolCallsUsed : 0,
533
250
  ...(workspace ? { workspaceRef: workspace } : {}),
534
- workspaceMode,
535
- ...(dispatch.metadata.parentTaskId ? { parentTaskId: dispatch.metadata.parentTaskId } : {}),
536
- ...((command.preparedWorkspace || (dispatch.metadata.preparedWorkspace && workspace === dispatch.workspace)
537
- || (previousRecord?.payload.preparedWorkspace && workspace === previousRecord.payload.workspaceRef)
538
- || (retryOf?.payload.preparedWorkspace && workspace === retryOf.payload.workspaceRef)) ? { preparedWorkspace: true } : {}),
539
- ...(retryOf ? { priorExecutionId: retryOf.executionId } : {}),
540
- ...(retryDispatchId ? { retryDispatchId, retrySpecification } : {}),
541
251
  };
542
252
  // One command per handoff (or per retry of one run): a repeated call
543
253
  // returns the original acceptance instead of starting a second worker.
544
254
  const commandId = retryOf ? `retry-${taskId}-${retryOf.executionId}` : `delegate-${taskId}`;
545
- const budget = remainingBudget(dispatch.metadata.budget ?? DEFAULT_LAB_RUN_BUDGET, retryOf ? this.deps.executions.attemptsForTask?.(taskId, FOREMAN_SCOPE) ?? [retryOf] : []);
546
- if (budget.maxModelAttempts < 1 || budget.maxToolCalls <= toolCallsUsed || budget.maxDurationMs < 1)
547
- return fail(`Task ${taskId} has exhausted its cumulative budget`);
548
255
  let outcome;
549
256
  try {
550
257
  outcome = this.deps.executions.submit(this.envelope(threadId, commandId), {
551
258
  taskId,
552
259
  ...(retryOf ? { retryOf: retryOf.executionId } : {}),
553
260
  payload,
554
- budget,
555
261
  });
556
262
  }
557
263
  catch (error) {
@@ -569,11 +275,6 @@ export class ForemanHost {
569
275
  // The previous result is superseded: whatever it still owed the user moves to the new run.
570
276
  this.deps.store.withdraw(taskId);
571
277
  this.deps.store.clearUserCancelled(taskId);
572
- if (dispatch.metadata.parentTaskId) {
573
- this.deps.store.withdraw(dispatch.metadata.parentTaskId);
574
- this.deps.store.clearUserCancelled(dispatch.metadata.parentTaskId);
575
- this.deps.store.clearGroupCancel(dispatch.metadata.parentTaskId);
576
- }
577
278
  }
578
279
  logger.system(`[foreman] Delegated task=${taskId} execution=${outcome.value.executionId} from thread=${threadId}${retryOf ? ` (retry of ${retryOf.executionId})` : ""}`);
579
280
  return {
@@ -581,14 +282,15 @@ export class ForemanHost {
581
282
  acceptedTaskId: taskId,
582
283
  text: [
583
284
  `Delegated as background task ${taskId} ("${dispatch.title}"), execution ${outcome.value.executionId}, ${outcome.value.state === "queued" ? "queued: it starts when a worker slot is free" : outcome.value.state}.`,
584
- "Do not repeat this Task's work here. You may delegate more independent Tasks, coordinate existing work, or reply to the user.",
285
+ "Stop working on this task here. Tell the user it is running in the background (unless you already did), then call finish.",
585
286
  "Its question or result arrives later as a [Background task update] message.",
586
287
  ].join(" "),
587
288
  };
588
289
  }
589
- /** Explicit Task context survives process replacement without a worker identity. */
590
- previousWork(previous, record) {
591
- const lines = [`Referenced Task: ${previous.taskId} "${previous.title}" (${record ? describeState(record) : "not started"})`];
290
+ /** What the same logical worker did last time, so follow-up work starts informed. */
291
+ previousWork(previous) {
292
+ const record = this.latest(previous.taskId);
293
+ const lines = [`Previous task of this worker: ${previous.taskId} "${previous.title}" (${record ? describeState(record) : "not started"})`];
592
294
  if (record?.result?.summary)
593
295
  lines.push(`Its result: ${clip(record.result.summary, 4_000)}`);
594
296
  if (record?.result?.artifactRefs?.length)
@@ -605,9 +307,7 @@ export class ForemanHost {
605
307
  ? { ok: true, text: this.describeTask(dispatch, true) }
606
308
  : fail(`no background task ${parsed.data.task_id} in this conversation`);
607
309
  }
608
- const roots = this.deps.store.allForThread(threadId).filter((dispatch) => !dispatch.metadata.parentTaskId);
609
- const active = roots.filter((dispatch) => this.isOpen(dispatch));
610
- const dispatches = [...active, ...roots.filter((dispatch) => !this.isOpen(dispatch)).slice(0, Math.max(0, 20 - active.length))];
310
+ const dispatches = this.deps.store.dispatchesForThread(threadId, 20);
611
311
  if (dispatches.length === 0)
612
312
  return { ok: true, text: "No background tasks in this conversation." };
613
313
  return { ok: true, text: dispatches.map((dispatch) => this.describeTask(dispatch, false)).join("\n") };
@@ -617,11 +317,8 @@ export class ForemanHost {
617
317
  if (!parsed.success)
618
318
  return invalid(parsed.error);
619
319
  const { task_id: taskId, text } = parsed.data;
620
- const dispatch = this.ownedDispatch(threadId, taskId);
621
- if (!dispatch)
320
+ if (!this.ownedDispatch(threadId, taskId))
622
321
  return fail(`no background task ${taskId} in this conversation`);
623
- if (dispatch.metadata.children?.length)
624
- return fail(`Send the answer or requirement to a child Task: ${dispatch.metadata.children.join(", ")}. Each child owns its input cursor.`);
625
322
  const record = this.latest(taskId);
626
323
  if (!record)
627
324
  return fail(`task ${taskId} has not started`);
@@ -656,25 +353,10 @@ export class ForemanHost {
656
353
  if (!parsed.success)
657
354
  return invalid(parsed.error);
658
355
  const { task_id: taskId, requested_by_user: byUser, reason } = parsed.data;
659
- const dispatch = this.ownedDispatch(threadId, taskId);
660
- if (!dispatch)
356
+ if (!this.ownedDispatch(threadId, taskId))
661
357
  return fail(`no background task ${taskId} in this conversation`);
662
- if (dispatch.metadata.children?.length) {
663
- if (!this.isOpen(dispatch))
664
- return { ok: true, text: `Task ${taskId} has already finished (${this.parentState(dispatch)}).` };
665
- if (byUser)
666
- this.deps.store.markUserCancelled(taskId);
667
- this.deps.store.withdraw(taskId);
668
- this.deps.store.requestGroupCancel(taskId, reason ?? "Parent task cancelled", byUser);
669
- this.cancelChildren(dispatch, reason ?? "Parent task cancelled", byUser);
670
- return { ok: true, text: `Cancel requested for all unfinished children of ${taskId}. Running children must confirm their stop before the parent is cancelled.` };
671
- }
672
358
  const record = this.latest(taskId);
673
359
  if (!record || isTerminalExecutionState(record.state)) {
674
- if (byUser) {
675
- this.deps.store.markUserCancelled(taskId);
676
- this.deps.store.withdraw(taskId);
677
- }
678
360
  return { ok: true, text: `Task ${taskId} is not running (${record ? record.state : "never started"}); nothing to cancel.` };
679
361
  }
680
362
  // Recorded first: the cancelled result that follows owes the user nothing.
@@ -695,13 +377,10 @@ export class ForemanHost {
695
377
  : `Cancel requested for task ${taskId}. The worker has not confirmed it stopped yet; do not tell the user it has stopped. The task is reported cancelled only after it does.`,
696
378
  };
697
379
  }
698
- delivered(threadId, taskId, purpose, eventId) {
699
- const dispatch = this.ownedDispatch(threadId, taskId);
700
- if (!dispatch)
380
+ delivered(threadId, taskId, purpose) {
381
+ if (!this.ownedDispatch(threadId, taskId))
701
382
  return { ok: false, text: `no background task ${taskId} in this conversation` };
702
- if (dispatch.metadata.children?.length && !eventId)
703
- return fail("A parent Task delivery must identify its result event/version");
704
- const satisfied = this.deps.store.markDelivered(taskId, purpose === "completion" ? "completion" : undefined, eventId);
383
+ const satisfied = this.deps.store.markDelivered(taskId, purpose === "completion" ? "completion" : undefined);
705
384
  return { ok: true, text: String(satisfied) };
706
385
  }
707
386
  /** General's turn that handled these events ended: whatever it did not deliver is due now. */
@@ -721,13 +400,13 @@ export class ForemanHost {
721
400
  }
722
401
  /** Open tasks of this conversation, for General's wake context. Empty when none. */
723
402
  summary(threadId) {
724
- const roots = this.deps.store.allForThread(threadId).filter((dispatch) => !dispatch.metadata.parentTaskId);
725
- const open = roots.filter((dispatch) => this.isOpen(dispatch));
726
- const recent = roots.filter((dispatch) => {
403
+ const open = this.deps.store.dispatchesForThread(threadId, 20).filter((dispatch) => {
404
+ const record = this.latest(dispatch.taskId);
727
405
  const delivery = this.deps.store.latest(dispatch.taskId);
728
- return !this.isOpen(dispatch) && delivery?.deliveryMode === "direct" && delivery.state === "delivered";
729
- }).slice(0, 5);
730
- if (open.length === 0 && recent.length === 0)
406
+ return (record !== null && !isTerminalExecutionState(record.state))
407
+ || delivery?.state === "pending" || delivery?.state === "delivery_failed";
408
+ });
409
+ if (open.length === 0)
731
410
  return "";
732
411
  let text = [
733
412
  "## Background Tasks",
@@ -741,30 +420,15 @@ export class ForemanHost {
741
420
  }
742
421
  text += `\n${entry}`;
743
422
  }
744
- for (const dispatch of recent) {
745
- const entry = `\n- Already delivered directly: ${dispatch.taskId} "${dispatch.title}": ${clip(this.deps.store.latest(dispatch.taskId)?.publicText ?? "", 1_000)}. Do not send it again.`;
746
- if (text.length + entry.length <= SUMMARY_CHAR_BUDGET)
747
- text += entry;
748
- }
749
423
  return text;
750
424
  }
751
425
  describeTask(dispatch, detail) {
752
- if (dispatch.metadata.children?.length) {
753
- const children = dispatch.metadata.children.map((id) => this.deps.store.dispatch(id))
754
- .filter((item) => item !== null);
755
- const delivery = this.deps.store.latest(dispatch.taskId);
756
- return [
757
- `- ${dispatch.taskId} "${dispatch.title}": ${this.parentState(dispatch)} (${children.length} child Tasks)`,
758
- ...children.map((child) => ` ${this.describeTask(child, detail)}`),
759
- ...(delivery ? [` parent delivery: ${delivery.state} (event ${delivery.eventId})`] : []),
760
- ].join("\n");
761
- }
762
426
  const record = this.latest(dispatch.taskId);
763
427
  const lines = [`- ${dispatch.taskId} "${dispatch.title}": ${record ? describeState(record) : "not started"}`];
764
- if (dispatch.metadata.contextTaskId)
765
- lines.push(` context from Task: ${dispatch.metadata.contextTaskId}`);
428
+ if (dispatch.worker)
429
+ lines.push(` worker: ${dispatch.worker}`);
766
430
  if (record) {
767
- const events = detail || record.state === "waiting_input" ? this.allEvents(record.executionId) : [];
431
+ const events = detail || record.state === "waiting_input" ? this.deps.executions.eventsAfter(record.executionId, 0) : [];
768
432
  const newestFirst = [...events].reverse();
769
433
  if (record.state === "waiting_input" && record.openQuestionId) {
770
434
  const question = newestFirst.find((event) => event.type === "question" && event.payload.questionId === record.openQuestionId);
@@ -784,9 +448,6 @@ export class ForemanHost {
784
448
  const result = record.result;
785
449
  if (result?.summary)
786
450
  lines.push(` result: ${clip(result.summary, detail ? 4_000 : 400)}`);
787
- const prepared = detail ? publicTaskText({ type: "result", payload: { data: result?.data } }) : null;
788
- if (prepared)
789
- lines.push(` prepared answer: ${prepared}`);
790
451
  if (result?.error)
791
452
  lines.push(` error: ${result.error.code}: ${clip(result.error.message, 1_000)}`);
792
453
  if (result?.artifactRefs?.length)
@@ -794,42 +455,15 @@ export class ForemanHost {
794
455
  }
795
456
  const delivery = this.deps.store.latest(dispatch.taskId);
796
457
  if (delivery && delivery.state !== "delivered" && delivery.state !== "withdrawn") {
797
- lines.push(` delivery to the user: ${delivery.state}${delivery.lastError ? ` (${delivery.lastError})` : ""} (event ${delivery.eventId})`);
798
- if (delivery.deliveryMode === "review" && delivery.state === "fallback_sent")
799
- lines.push(" Only a status notice was sent; the result still requires General to review and deliver it.");
458
+ lines.push(` delivery to the user: ${delivery.state}${delivery.lastError ? ` (${delivery.lastError})` : ""}`);
800
459
  }
801
460
  return lines.join("\n");
802
461
  }
803
- isOpen(dispatch) {
804
- const record = this.latest(dispatch.taskId);
805
- const workOpen = dispatch.metadata.children?.length
806
- ? this.childRecords(dispatch).some((child) => child.record !== null && !isTerminalExecutionState(child.record.state))
807
- : record !== null && !isTerminalExecutionState(record.state);
808
- const delivery = this.deps.store.latest(dispatch.taskId);
809
- return workOpen || delivery?.state === "pending" || delivery?.state === "delivery_failed"
810
- || (delivery?.state === "fallback_sent" && delivery.deliveryMode === "review" && delivery.kind === "result");
811
- }
812
- allEvents(executionId) {
813
- const events = [];
814
- let after = 0;
815
- for (;;) {
816
- const page = this.deps.executions.eventsAfter(executionId, after);
817
- const last = page.at(-1)?.seq ?? after;
818
- if (last <= after)
819
- break;
820
- events.push(...page);
821
- after = last;
822
- if (page.length < 500)
823
- break;
824
- }
825
- return events;
826
- }
827
462
  /** The system message that brings a question or result to the delegating thread. */
828
463
  eventMessage(event, threadId, dispatch, obligationOpens) {
829
464
  const payload = event.payload;
830
465
  const title = dispatch?.title ?? event.taskId;
831
- const taskQuote = `task_id="${event.taskId}"`;
832
- const quote = `${taskQuote}, event_id="${event.eventId}"`;
466
+ const quote = `task_id="${event.taskId}"`;
833
467
  const lines = [`[Background task update] ${event.type === "question" ? "Question from" : "Result of"} background task ${event.taskId} "${title}"`];
834
468
  if (dispatch?.sender)
835
469
  lines.push(`Requested by: ${dispatch.sender}`);
@@ -838,16 +472,13 @@ export class ForemanHost {
838
472
  if (Array.isArray(payload.choices) && payload.choices.length > 0) {
839
473
  lines.push(`Choices: ${payload.choices.map(String).join(" / ")}`);
840
474
  }
841
- lines.push(`If you can answer from what you know, answer with background_task_input(${taskQuote}, question_id="${textOf(payload.questionId)}"). Otherwise ask the requester with notify_user(${quote}) and pass their answer on. Do not do the task's work here.`);
475
+ lines.push(`If you can answer from what you know, answer with background_task_input(${quote}). Otherwise ask the requester with notify_user(${quote}) and pass their answer on. Do not do the task's work here.`);
842
476
  }
843
477
  else {
844
478
  const status = String(payload.status);
845
479
  lines.push(`Status: ${status}`);
846
480
  if (typeof payload.summary === "string")
847
481
  lines.push(`Summary: ${clip(payload.summary, 8_000)}`);
848
- const preparedReply = publicTaskText(event);
849
- if (preparedReply)
850
- lines.push(`Prepared reply for the requester (review before sending): ${preparedReply}`);
851
482
  const error = payload.error;
852
483
  if (error && typeof error === "object") {
853
484
  const { code, message } = error;
@@ -856,15 +487,12 @@ export class ForemanHost {
856
487
  if (Array.isArray(payload.artifactRefs) && payload.artifactRefs.length > 0) {
857
488
  lines.push(`Artifacts: ${payload.artifactRefs.slice(0, 30).map(String).join(", ")}`);
858
489
  }
859
- if (status === "cancelled" && !obligationOpens) {
860
- lines.push("The requester asked to stop this task; nothing more is needed unless they asked to hear back.");
861
- }
862
- else if (dispatch?.metadata.children?.length) {
863
- lines.push(`Review the child results together. Deliver the overall result with notify_user(${quote}, purpose="completion"). Retry only children that need further work using their child task_id; do not repeat successful children. The parent does not occupy a worker.`);
864
- }
865
- else if (status === "succeeded") {
490
+ if (status === "succeeded") {
866
491
  lines.push(`Check the result against the request before you deliver it. If it holds up, tell the requester with notify_user(${quote}, purpose="completion") in your own words, and send any drafts it contains with the right tool. If it falls short, run it again with delegate_task(retry_task_id="${event.taskId}") and say what must change.`);
867
492
  }
493
+ else if (status === "cancelled" && !obligationOpens) {
494
+ lines.push("The requester asked to stop this task; nothing more is needed unless they asked to hear back.");
495
+ }
868
496
  else {
869
497
  lines.push(`Tell the requester what happened with notify_user(${quote}, purpose="completion"), or run it again with delegate_task(retry_task_id="${event.taskId}") if a retry can succeed.`);
870
498
  }
@@ -883,7 +511,15 @@ export class ForemanHost {
883
511
  attachments: [],
884
512
  timestamp: event.createdAt,
885
513
  ...routing,
886
- meta: { ...routing.meta, [FOREMAN_EVENT_META_KEY]: { taskId: event.taskId, eventId: event.eventId } },
514
+ meta: {
515
+ ...routing.meta,
516
+ [FOREMAN_EVENT_META_KEY]: {
517
+ taskId: event.taskId,
518
+ eventId: event.eventId,
519
+ kind: event.type,
520
+ ...(event.type === "result" ? { status: String(payload.status) } : {}),
521
+ },
522
+ },
887
523
  ...(threadId !== DEFAULT_THREAD_ID ? { threadId } : {}),
888
524
  };
889
525
  }