agents 0.26.0 → 0.27.0

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 (233) hide show
  1. package/README.md +8 -9
  2. package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-Bhdbvf7s.d.ts} +13 -8
  3. package/dist/agent-routing.d.ts +1 -1
  4. package/dist/agent-tool-types.d.ts +1 -1
  5. package/dist/{agent-tools-YrATZJu2.d.ts → agent-tools-bJZwBMfq.d.ts} +2 -2
  6. package/dist/agent-tools.d.ts +1 -1
  7. package/dist/ai-sdk-OoGkFy3s.js +2444 -0
  8. package/dist/ai-sdk-OoGkFy3s.js.map +1 -0
  9. package/dist/apply-chunks-1FheRb9W.js +99 -0
  10. package/dist/apply-chunks-1FheRb9W.js.map +1 -0
  11. package/dist/broadcast-state-CEyI4uAK.js +69 -0
  12. package/dist/broadcast-state-CEyI4uAK.js.map +1 -0
  13. package/dist/browser/ai-sdk.d.ts +1 -1
  14. package/dist/browser/ai-sdk.js +2 -2
  15. package/dist/browser/ai-sdk.js.map +1 -1
  16. package/dist/browser/index.d.ts +1 -1
  17. package/dist/browser/tanstack-ai.d.ts +1 -1
  18. package/dist/browser/tanstack-ai.js +1 -1
  19. package/dist/{browser-DE3d-RLk.d.ts → browser-tmkcAAI3.d.ts} +2 -2
  20. package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-Bn-nzJQI.d.ts} +3 -3
  21. package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-CBqs6hqa.js} +58 -18
  22. package/dist/browser-tool-CBqs6hqa.js.map +1 -0
  23. package/dist/capability-B4WbF81e.js.map +1 -1
  24. package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-O5VbQzqg.d.ts} +9 -1
  25. package/dist/channel-Bak_6zDP.d.ts +333 -0
  26. package/dist/chat/index.d.ts +11 -9
  27. package/dist/chat/index.js +5 -194
  28. package/dist/chat/index.js.map +1 -1
  29. package/dist/chat/react.d.ts +2 -4
  30. package/dist/chat/react.js +3 -1
  31. package/dist/chat/react.js.map +1 -1
  32. package/dist/chat-sdk/index.d.ts +1 -1
  33. package/dist/chat-sdk/index.js +1 -1
  34. package/dist/cli.js +144 -0
  35. package/dist/{client-fyQrLGkD.js → client-1tEQ1Oqz.js} +27 -2
  36. package/dist/{client-fyQrLGkD.js.map → client-1tEQ1Oqz.js.map} +1 -1
  37. package/dist/{client-je3koD9Z.d.ts → client-DS3Dz9l0.d.ts} +2 -2
  38. package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
  39. package/dist/client.d.ts +1 -1
  40. package/dist/conversations-DH0ut_8n.d.ts +192 -0
  41. package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-C07C5o5Y.d.ts} +3 -3
  42. package/dist/daemon-core-Buubj-y1.js +481 -0
  43. package/dist/daemon-core-Buubj-y1.js.map +1 -0
  44. package/dist/daemon-core-CbohVPAu.d.ts +132 -0
  45. package/dist/{do-oauth-client-provider-Tmf1vgKz.d.ts → do-oauth-client-provider-DfsUcpc3.d.ts} +7 -1
  46. package/dist/{durable-object-lifecycle-HhgOMWq9.d.ts → durable-object-lifecycle-DRqp475_.d.ts} +2 -2
  47. package/dist/{channels/email.d.ts → experimental/channels/email/index.d.ts} +25 -12
  48. package/dist/{channels/email.js → experimental/channels/email/index.js} +13 -35
  49. package/dist/experimental/channels/email/index.js.map +1 -0
  50. package/dist/experimental/channels/index.d.ts +270 -0
  51. package/dist/experimental/channels/index.js +1186 -0
  52. package/dist/experimental/channels/index.js.map +1 -0
  53. package/dist/{channels/slack.d.ts → experimental/channels/slack/index.d.ts} +19 -9
  54. package/dist/experimental/channels/slack/index.js +267 -0
  55. package/dist/experimental/channels/slack/index.js.map +1 -0
  56. package/dist/{channels/telegram.d.ts → experimental/channels/telegram/index.d.ts} +19 -9
  57. package/dist/experimental/channels/telegram/index.js +198 -0
  58. package/dist/experimental/channels/telegram/index.js.map +1 -0
  59. package/dist/experimental/channels/web/ai-sdk.d.ts +55 -0
  60. package/dist/experimental/channels/web/ai-sdk.js +266 -0
  61. package/dist/experimental/channels/web/ai-sdk.js.map +1 -0
  62. package/dist/experimental/channels/web/client.d.ts +97 -0
  63. package/dist/experimental/channels/web/client.js +349 -0
  64. package/dist/experimental/channels/web/client.js.map +1 -0
  65. package/dist/experimental/channels/web/index.d.ts +189 -0
  66. package/dist/experimental/channels/web/index.js +332 -0
  67. package/dist/experimental/channels/web/index.js.map +1 -0
  68. package/dist/gateway-BDC07gYO.d.ts +100 -0
  69. package/dist/handle-zAzh0UW2.d.ts +460 -0
  70. package/dist/harness/ai-sdk/index.d.ts +175 -0
  71. package/dist/harness/ai-sdk/index.js +605 -0
  72. package/dist/harness/ai-sdk/index.js.map +1 -0
  73. package/dist/harness/container/daemon/index.d.ts +76 -0
  74. package/dist/harness/container/daemon/index.js +3 -0
  75. package/dist/harness/container/index.d.ts +740 -0
  76. package/dist/harness/container/index.js +2152 -0
  77. package/dist/harness/container/index.js.map +1 -0
  78. package/dist/harness/container/runtime/index.d.ts +144 -0
  79. package/dist/harness/container/runtime/index.js +1082 -0
  80. package/dist/harness/container/runtime/index.js.map +1 -0
  81. package/dist/harness/opencode/index.d.ts +369 -0
  82. package/dist/harness/opencode/index.js +1072 -0
  83. package/dist/harness/opencode/index.js.map +1 -0
  84. package/dist/harness/pi/index.d.ts +2 -1
  85. package/dist/harness/store/index.d.ts +24 -0
  86. package/dist/harness/store/index.js +2 -0
  87. package/dist/harness/think/index.d.ts +821 -0
  88. package/dist/harness/think/index.js +2529 -0
  89. package/dist/harness/think/index.js.map +1 -0
  90. package/dist/harness-B6B9S3rb.d.ts +203 -0
  91. package/dist/{images-iiTDSyRu.js → images-Ce9SCu3X.js} +2 -248
  92. package/dist/images-Ce9SCu3X.js.map +1 -0
  93. package/dist/{index-BreL0YrQ.d.ts → index-D0A5tbWu.d.ts} +2 -2
  94. package/dist/index-Ib_4dwOo.d.ts +200 -0
  95. package/dist/index.d.ts +5 -5
  96. package/dist/index.js +2 -2
  97. package/dist/ingress-EFbrS547.js +9 -0
  98. package/dist/ingress-EFbrS547.js.map +1 -0
  99. package/dist/input-schema-DNrpTu49.js +23 -0
  100. package/dist/input-schema-DNrpTu49.js.map +1 -0
  101. package/dist/internal-kmw70xwR.js +46 -0
  102. package/dist/internal-kmw70xwR.js.map +1 -0
  103. package/dist/internal_context.d.ts +1 -1
  104. package/dist/lifecycle/index.d.ts +3 -3
  105. package/dist/mcp/client/do-oauth-client-provider.d.ts +1 -1
  106. package/dist/mcp/client/do-oauth-client-provider.js +9 -0
  107. package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
  108. package/dist/mcp/client/index.d.ts +1 -1
  109. package/dist/mcp/client/index.js +1 -1
  110. package/dist/mcp/client/x402.d.ts +35 -4
  111. package/dist/mcp/client/x402.js +24 -3
  112. package/dist/mcp/client/x402.js.map +1 -1
  113. package/dist/mcp/index.d.ts +1 -1
  114. package/dist/mcp/index.js +2 -2
  115. package/dist/models/ai-sdk/index.d.ts +16 -14
  116. package/dist/models/ai-sdk/index.js +1 -2442
  117. package/dist/models/opencode/index.d.ts +65 -0
  118. package/dist/models/opencode/index.js +285 -0
  119. package/dist/models/opencode/index.js.map +1 -0
  120. package/dist/models/pi-ai/index.d.ts +9 -7
  121. package/dist/models/pi-ai/index.js +2 -1
  122. package/dist/models/pi-ai/index.js.map +1 -1
  123. package/dist/options-pwTNq9z6.d.ts +134 -0
  124. package/dist/protocol-BEgrJYC5.d.ts +331 -0
  125. package/dist/protocol-CukuI73M.d.ts +386 -0
  126. package/dist/protocol-cOklVBBD.js +227 -0
  127. package/dist/protocol-cOklVBBD.js.map +1 -0
  128. package/dist/{protocol-kHsr-f0X.js → protocol-eXeJxhgu.js} +2 -67
  129. package/dist/protocol-eXeJxhgu.js.map +1 -0
  130. package/dist/protocol-y2sSY584.js +148 -0
  131. package/dist/protocol-y2sSY584.js.map +1 -0
  132. package/dist/queue/index.d.ts +1 -1
  133. package/dist/react.d.ts +2 -2
  134. package/dist/routing/index.d.ts +2 -2
  135. package/dist/{scheduler-RdACNXDP.d.ts → scheduler-CrFCnv_J.d.ts} +2 -2
  136. package/dist/schedules/index.d.ts +1 -1
  137. package/dist/serializable.d.ts +1 -1
  138. package/dist/sessions/index.d.ts +11 -434
  139. package/dist/sessions/index.js +1 -2271
  140. package/dist/sessions-B9pNuxdT.js +2272 -0
  141. package/dist/sessions-B9pNuxdT.js.map +1 -0
  142. package/dist/settings-C45hDliX.js +253 -0
  143. package/dist/settings-C45hDliX.js.map +1 -0
  144. package/dist/{transport-PfFnt-Ld.d.ts → settings-NgGwF7Qt.d.ts} +14 -208
  145. package/dist/{src-BZeu-SCD.js → src-DgCbZtIR.js} +4 -4
  146. package/dist/{src-BZeu-SCD.js.map → src-DgCbZtIR.js.map} +1 -1
  147. package/dist/stall-watchdog-BpRF2uwG.js +195 -0
  148. package/dist/stall-watchdog-BpRF2uwG.js.map +1 -0
  149. package/dist/state/index.d.ts +1 -1
  150. package/dist/store-DLeqKuj3.d.ts +310 -0
  151. package/dist/store-vgcPYo8K.js +432 -0
  152. package/dist/store-vgcPYo8K.js.map +1 -0
  153. package/dist/streams/index.d.ts +1 -1
  154. package/dist/streams/index.js +1 -1
  155. package/dist/{streams-DW-UCOUc.js → streams-CITClVer.js} +16 -3
  156. package/dist/streams-CITClVer.js.map +1 -0
  157. package/dist/{streams-Cmkj9QfW.d.ts → streams-fbG61qM8.d.ts} +2 -2
  158. package/dist/sub-routing.d.ts +1 -1
  159. package/dist/tasks/index.d.ts +1 -1
  160. package/dist/tasks/index.js +1 -1
  161. package/dist/{tasks-68DESxf0.js → tasks-CkTZNaV1.js} +2 -1
  162. package/dist/tasks-CkTZNaV1.js.map +1 -0
  163. package/dist/{tasks-9YxYtByh.d.ts → tasks-PtlB4Uyn.d.ts} +2 -2
  164. package/dist/tool-3PA8xjB_.js +421 -0
  165. package/dist/tool-3PA8xjB_.js.map +1 -0
  166. package/dist/tool-BPJEbwaX.d.ts +278 -0
  167. package/dist/transport-k2mFrtL8.d.ts +200 -0
  168. package/dist/turns-DCV3ycDl.js +317 -0
  169. package/dist/turns-DCV3ycDl.js.map +1 -0
  170. package/dist/voice/client.js +1 -1
  171. package/dist/voice/index.d.ts +2 -2
  172. package/dist/voice/index.js +1 -1
  173. package/dist/websearch/index.d.ts +58 -0
  174. package/dist/websearch/index.js +2 -0
  175. package/dist/websearch/tools/ai-sdk.d.ts +77 -0
  176. package/dist/websearch/tools/ai-sdk.js +46 -0
  177. package/dist/websearch/tools/ai-sdk.js.map +1 -0
  178. package/dist/websearch/tools/pi.d.ts +82 -0
  179. package/dist/websearch/tools/pi.js +89 -0
  180. package/dist/websearch/tools/pi.js.map +1 -0
  181. package/dist/websearch/tools/tanstack-ai.d.ts +71 -0
  182. package/dist/websearch/tools/tanstack-ai.js +46 -0
  183. package/dist/websearch/tools/tanstack-ai.js.map +1 -0
  184. package/dist/websockets/index.d.ts +12 -296
  185. package/dist/websockets/index.js +1 -1
  186. package/dist/{websockets-Ci-LZFHH.js → websockets-CmENGb-Z.js} +18 -4
  187. package/dist/websockets-CmENGb-Z.js.map +1 -0
  188. package/dist/{wire-types-CrjKvDyi.d.ts → wire-types-Cv0iwjk5.d.ts} +3 -51
  189. package/dist/workflow-types.d.ts +1 -1
  190. package/dist/workflows.d.ts +1 -1
  191. package/dist/workflows.js +1 -1
  192. package/docs/browse-the-web.md +3 -0
  193. package/docs/harnesses/think.md +215 -0
  194. package/docs/index.md +2 -1
  195. package/docs/lifecycle.md +40 -0
  196. package/docs/search-the-web.md +133 -0
  197. package/package.json +117 -38
  198. package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
  199. package/dist/channel-Bnm4S7T2.d.ts +0 -491
  200. package/dist/channels/ai-sdk.d.ts +0 -46
  201. package/dist/channels/ai-sdk.js +0 -120
  202. package/dist/channels/ai-sdk.js.map +0 -1
  203. package/dist/channels/email.js.map +0 -1
  204. package/dist/channels/index.d.ts +0 -233
  205. package/dist/channels/index.js +0 -608
  206. package/dist/channels/index.js.map +0 -1
  207. package/dist/channels/slack.js +0 -614
  208. package/dist/channels/slack.js.map +0 -1
  209. package/dist/channels/tanstack-ai.d.ts +0 -39
  210. package/dist/channels/tanstack-ai.js +0 -17
  211. package/dist/channels/tanstack-ai.js.map +0 -1
  212. package/dist/channels/telegram.js +0 -427
  213. package/dist/channels/telegram.js.map +0 -1
  214. package/dist/channels/voice.d.ts +0 -45
  215. package/dist/channels/voice.js +0 -122
  216. package/dist/channels/voice.js.map +0 -1
  217. package/dist/images-iiTDSyRu.js.map +0 -1
  218. package/dist/index-BB0kqhIz.d.ts +0 -101
  219. package/dist/ingress-BfetZbMO.js +0 -83
  220. package/dist/ingress-BfetZbMO.js.map +0 -1
  221. package/dist/internal-CYlgHl1l.js +0 -59
  222. package/dist/internal-CYlgHl1l.js.map +0 -1
  223. package/dist/models/ai-sdk/index.js.map +0 -1
  224. package/dist/protocol-kHsr-f0X.js.map +0 -1
  225. package/dist/sessions/index.js.map +0 -1
  226. package/dist/streams-DW-UCOUc.js.map +0 -1
  227. package/dist/surface-bZZJqBka.js +0 -17
  228. package/dist/surface-bZZJqBka.js.map +0 -1
  229. package/dist/tasks-68DESxf0.js.map +0 -1
  230. package/dist/tool-schema-CBjGPrsQ.js +0 -31
  231. package/dist/tool-schema-CBjGPrsQ.js.map +0 -1
  232. package/dist/websockets-Ci-LZFHH.js.map +0 -1
  233. package/docs/channels.md +0 -323
@@ -0,0 +1,2152 @@
1
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../../classPrivateFieldGet2-DZBYAB34.js";
2
+ import { t as _classPrivateMethodInitSpec } from "../../classPrivateMethodInitSpec-qMjJ6sHQ.js";
3
+ import { t as LifecycleCapability } from "../../capability-B4WbF81e.js";
4
+ import "../../lifecycle-CFu6OV03.js";
5
+ import { n as openHarnessStore } from "../../store-vgcPYo8K.js";
6
+ import { d as parseDaemonMessage, i as CONTAINER_HEALTH_PATH, l as chunkEntries, o as CONTAINER_SESSION_PATH, r as CONTAINER_ENV, s as CONTAINER_TOKEN_HEADER } from "../../protocol-cOklVBBD.js";
7
+ import { WorkerEntrypoint } from "cloudflare:workers";
8
+ //#region src/harness/container/link.ts
9
+ /**
10
+ * The object's side of one session socket: dial the daemon through the
11
+ * container's TCP port, read its `hello`, then hand every later frame to
12
+ * the harness. Failures to reach the daemon are values, so the harness can
13
+ * decide between retrying and giving up.
14
+ */
15
+ const HELLO_TIMEOUT_MS = 1e4;
16
+ const CONNECT_TIMEOUT_MS = 1e4;
17
+ const HEALTH_TIMEOUT_MS = 2e3;
18
+ function unreachable(message) {
19
+ return {
20
+ _tag: "err",
21
+ error: {
22
+ _tag: "unreachable",
23
+ message
24
+ }
25
+ };
26
+ }
27
+ function errorText$1(error) {
28
+ return error instanceof Error ? error.message : String(error);
29
+ }
30
+ /**
31
+ * Wait until the daemon answers its health check.
32
+ *
33
+ * @param port - The container's TCP port.
34
+ * @param timeoutMs - How long to keep trying.
35
+ * @param running - Whether the container still runs; waiting stops early
36
+ * once it does not.
37
+ * @returns `ok`, or `unreachable` after the timeout or once it stops.
38
+ */
39
+ async function waitHealthy(port, timeoutMs, running) {
40
+ const deadline = Date.now() + timeoutMs;
41
+ let delay = 250;
42
+ let last = "no answer";
43
+ for (;;) {
44
+ try {
45
+ const response = await port.fetch(`http://container${CONTAINER_HEALTH_PATH}`, { signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS) });
46
+ await response.body?.cancel();
47
+ if (response.ok) return {
48
+ _tag: "ok",
49
+ value: void 0
50
+ };
51
+ last = `health check answered ${response.status}`;
52
+ } catch (error) {
53
+ last = errorText$1(error);
54
+ }
55
+ if (!running()) return unreachable("the container stopped");
56
+ if (Date.now() + delay > deadline) return unreachable(last);
57
+ await new Promise((resolve) => setTimeout(resolve, delay));
58
+ delay = Math.min(delay * 2, 2e3);
59
+ }
60
+ }
61
+ var _socket = /* @__PURE__ */ new WeakMap();
62
+ var _open = /* @__PURE__ */ new WeakMap();
63
+ /** One open session socket. */
64
+ var DaemonLink = class DaemonLink {
65
+ constructor(socket) {
66
+ _classPrivateFieldInitSpec(this, _socket, void 0);
67
+ _classPrivateFieldInitSpec(this, _open, true);
68
+ _classPrivateFieldSet2(_socket, this, socket);
69
+ }
70
+ /**
71
+ * Dial the daemon for one session and wait for its `hello`.
72
+ *
73
+ * @param port - The container's TCP port.
74
+ * @param session - The session id.
75
+ * @param token - The shared secret the container was started with.
76
+ * @param handlers - Called for frames after `hello`, and on close.
77
+ * @returns The link and the greeting, or why it failed.
78
+ */
79
+ static async open(port, session, token, handlers) {
80
+ let response;
81
+ try {
82
+ response = await port.fetch(`http://container${CONTAINER_SESSION_PATH}${encodeURIComponent(session)}`, {
83
+ headers: {
84
+ Upgrade: "websocket",
85
+ [CONTAINER_TOKEN_HEADER]: token
86
+ },
87
+ signal: AbortSignal.timeout(CONNECT_TIMEOUT_MS)
88
+ });
89
+ } catch (error) {
90
+ return unreachable(errorText$1(error));
91
+ }
92
+ const socket = response.webSocket;
93
+ if (!socket) {
94
+ await response.body?.cancel();
95
+ return unreachable(`upgrade refused with ${response.status}`);
96
+ }
97
+ socket.accept();
98
+ const link = new DaemonLink(socket);
99
+ return new Promise((resolve) => {
100
+ let greeted = false;
101
+ const timer = setTimeout(() => {
102
+ if (greeted) return;
103
+ link.close();
104
+ resolve(unreachable("no hello from the daemon"));
105
+ }, HELLO_TIMEOUT_MS);
106
+ socket.addEventListener("message", (event) => {
107
+ const message = parseDaemonMessage(typeof event.data === "string" ? event.data : new TextDecoder().decode(event.data));
108
+ if (!message) return;
109
+ if (!greeted) {
110
+ if (message.type !== "hello") return;
111
+ greeted = true;
112
+ clearTimeout(timer);
113
+ if (message.protocol !== 1) {
114
+ link.close();
115
+ resolve({
116
+ _tag: "err",
117
+ error: {
118
+ _tag: "protocol",
119
+ message: `daemon speaks protocol ${message.protocol}, the harness 1`
120
+ }
121
+ });
122
+ return;
123
+ }
124
+ resolve({
125
+ _tag: "ok",
126
+ value: {
127
+ link,
128
+ hello: message
129
+ }
130
+ });
131
+ return;
132
+ }
133
+ if (message.type === "frame") handlers.frame(message.seq, message.frame);
134
+ else if (message.type === "caught-up") handlers.caughtUp();
135
+ });
136
+ const onClose = () => {
137
+ if (!_classPrivateFieldGet2(_open, link)) return;
138
+ _classPrivateFieldSet2(_open, link, false);
139
+ clearTimeout(timer);
140
+ if (!greeted) resolve(unreachable("socket closed before hello"));
141
+ else handlers.closed();
142
+ };
143
+ socket.addEventListener("close", onClose);
144
+ socket.addEventListener("error", onClose);
145
+ });
146
+ }
147
+ /** Whether the socket is still open. */
148
+ get open() {
149
+ return _classPrivateFieldGet2(_open, this);
150
+ }
151
+ /**
152
+ * Send one message. Returns false when the socket has closed.
153
+ *
154
+ * @param message - The message.
155
+ * @returns Whether it was handed to the socket.
156
+ */
157
+ send(message) {
158
+ if (!_classPrivateFieldGet2(_open, this)) return false;
159
+ try {
160
+ _classPrivateFieldGet2(_socket, this).send(JSON.stringify(message));
161
+ return true;
162
+ } catch {
163
+ return false;
164
+ }
165
+ }
166
+ /** Close the socket. Does not call `closed`. */
167
+ close() {
168
+ if (!_classPrivateFieldGet2(_open, this)) return;
169
+ _classPrivateFieldSet2(_open, this, false);
170
+ try {
171
+ _classPrivateFieldGet2(_socket, this).close(1e3, "closed by the harness");
172
+ } catch {}
173
+ }
174
+ };
175
+ //#endregion
176
+ //#region src/harness/container/daemon-source.generated.ts
177
+ /** The harness daemon, bundled: one Node ES module. */
178
+ const DAEMON_SOURCE = "var $=\"/healthz\",M=\"/sessions/\",z=\"x-cf-harness-token\",x={token:\"CF_HARNESS_TOKEN\",runtimeId:\"CF_HARNESS_RUNTIME_ID\",port:\"CF_HARNESS_PORT\"},K=4001,W=4003,xe=256*1024;function B(n){return typeof n==\"object\"&&n!==null&&!Array.isArray(n)}function k(n){return typeof n==\"string\"}function E(n){return typeof n==\"number\"&&Number.isSafeInteger(n)&&n>=0}function A(n){if(n===null)return!0;switch(typeof n){case\"string\":case\"boolean\":return!0;case\"number\":return Number.isFinite(n);case\"object\":return Array.isArray(n)?n.every(A):Object.values(n).every(A);default:return!1}}function be(n){return k(n)?!0:Array.isArray(n)&&n.every(e=>B(e)&&(e.type===\"text\"&&k(e.text)||e.type===\"image\"&&k(e.mediaType)&&k(e.data)))}function U(n){return B(n)&&(n.model===void 0||k(n.model))&&(n.options===void 0||A(n.options))}function Ie(n){try{return JSON.parse(n)}catch{return}}function G(n){let e=Ie(n);if(B(e))switch(e.type){case\"restore\":return E(e.generation)&&Array.isArray(e.entries)&&e.entries.every(A)?{type:\"restore\",generation:e.generation,entries:e.entries}:void 0;case\"open\":return E(e.generation)&&U(e.settings)&&Array.isArray(e.restore)&&e.restore.every(A)?{type:\"open\",generation:e.generation,settings:e.settings,restore:e.restore}:void 0;case\"replay\":return E(e.after)?{type:\"replay\",after:e.after}:void 0;case\"prompt\":return k(e.operationId)&&be(e.input)&&(e.whenBusy===\"followUp\"||e.whenBusy===\"steer\")?{type:\"prompt\",operationId:e.operationId,input:e.input,whenBusy:e.whenBusy}:void 0;case\"abort\":return e.operationId===void 0?{type:\"abort\"}:k(e.operationId)?{type:\"abort\",operationId:e.operationId}:void 0;case\"configure\":return U(e.settings)?{type:\"configure\",settings:e.settings}:void 0;case\"ack\":return E(e.seq)?{type:\"ack\",seq:e.seq}:void 0;default:return}}function _(n){return typeof n==\"string\"?n:n.map(e=>e.type===\"text\"?e.text:\"\").join(`\n`)}function Y(n){let e=[],t=[],r=0;for(let o of n){let s=JSON.stringify(o).length;t.length>0&&r+s>xe&&(e.push(t),t=[],r=0),t.push(o),r+=s}return t.length>0&&e.push(t),e}var X=1e3,T=class{#e;#n;#s;#i=new Map;constructor(e){this.#e=e.adapter,this.#n=e.runtimeId,this.#s=e.maxKeptFrames??1e4}connect(e,t){let r=this.#i.get(e);return r||(r=new J(e,this.#e,this.#n,this.#s),this.#i.set(e,r)),r.attach(t)}async close(){await Promise.all([...this.#i.values()].map(e=>e.shutdown()))}},J=class{#e;#n;#s;#i;#a=0;#r=[];#t=new Map;#l=[];#o;#d;#y;#u;#f=!1;#h=Promise.resolve();constructor(e,t,r,o){this.#e=e,this.#n=t,this.#s=r,this.#i=o}attach(e){let t=this.#u;return this.#u=e,this.#f=!1,t?.close(K,\"replaced by a newer connection\"),this.#c(e,this.#w()),{receive:r=>{if(this.#u!==e)return;let o=G(r);if(!o){this.#c(e,{type:\"error\",message:\"unparseable message\"});return}this.#h=this.#h.then(()=>this.#C(o)).catch(s=>{this.#c(e,{type:\"error\",message:Z(s)})})},closed:()=>{this.#u===e&&(this.#u=void 0,this.#f=!1)}}}async shutdown(){this.#o?.controller.abort();let e=this.#d;this.#d=void 0,await e?.session.close().catch(()=>{})}#w(){let e=[];for(let t of this.#t.values()){let r=t.state;e.push(r.tag===\"settled\"?{operationId:t.id,status:\"settled\",outcome:r.outcome}:{operationId:t.id,status:r.tag})}return{type:\"hello\",protocol:1,runtimeId:this.#s,session:this.#e,adapter:{id:this.#n.id,version:this.#n.version,capabilities:[...this.#n.capabilities??[]]},open:this.#d?.generation??null,lastSeq:this.#a,operations:e}}async#C(e){switch(e.type){case\"restore\":this.#y?.generation!==e.generation&&(this.#y={generation:e.generation,entries:[]}),this.#y.entries.push(...e.entries);return;case\"open\":await this.#x(e);return;case\"replay\":this.#b(e.after);return;case\"prompt\":await this.#I(e.operationId,e.input,e.whenBusy);return;case\"abort\":this.#S(e.operationId);return;case\"configure\":await this.#d?.session.configure?.(e.settings);return;case\"ack\":this.#r=this.#r.filter(t=>t.seq>e.seq);return}}async#x(e){let t=this.#y;if(this.#y=void 0,this.#d?.generation===e.generation)return;let r=t?.generation===e.generation?[...t.entries,...e.restore]:e.restore;this.#d&&(this.#o?.controller.abort(),await this.#d.session.close().catch(()=>{}),this.#d=void 0);let o=await this.#n.open({session:this.#e,settings:e.settings,restore:r,emit:s=>this.#p({kind:\"event\",operationId:this.#o?.id??null,event:s}),persist:s=>{for(let i of Y(s))this.#p({kind:\"persist\",entries:i})},log:(s,i)=>this.#p({kind:\"event\",operationId:this.#o?.id??null,event:{type:\"log\",level:s,message:i}})});this.#d={generation:e.generation,session:o},this.#g()}#b(e){let t=this.#u;if(t){for(let r of this.#r)r.seq>e&&this.#c(t,{type:\"frame\",seq:r.seq,frame:r.frame});this.#c(t,{type:\"caught-up\",lastSeq:this.#a}),this.#f=!0}}async#I(e,t,r){if(this.#t.has(e))return;let o={id:e,input:t,whenBusy:r,state:{tag:\"queued\"}};this.#t.set(e,o),this.#A();let s=this.#o,i=this.#d?.session.steer;if(r===\"steer\"&&s&&i&&await i.call(this.#d?.session,t)&&this.#o===s){o.state={tag:\"running\"},s.folded.push(e),this.#p({kind:\"start\",operationId:e});return}this.#l.push(e),this.#g()}#S(e){let t=e===void 0?[...this.#t.keys()]:this.#t.has(e)?[e]:[];for(let r of t){let o=this.#t.get(r);if(o)if(o.state.tag===\"queued\"){let s=this.#l.indexOf(r);s>=0&&this.#l.splice(s,1),this.#m(o,{status:\"unanswered\",reason:\"aborted\"})}else o.state.tag===\"running\"&&this.#o?.controller.abort()}}#g(){let e=this.#d;if(this.#o||!e)return;let t=this.#l.shift();if(t===void 0)return;let r=this.#t.get(t);if(!r||r.state.tag!==\"queued\"){this.#g();return}let o={id:t,folded:[],controller:new AbortController};this.#o=o,r.state={tag:\"running\"},this.#p({kind:\"start\",operationId:t}),this.#k(e,r,o)}async#k(e,t,r){let o;try{o=await e.session.run({operationId:t.id,input:t.input,signal:r.controller.signal}),r.controller.signal.aborted&&o.status===\"done\"&&(o={status:\"unanswered\",reason:\"aborted\"})}catch(s){o={status:\"unanswered\",reason:r.controller.signal.aborted?\"aborted\":Z(s)}}this.#o===r&&(this.#o=void 0),this.#m(t,o);for(let s of r.folded){let i=this.#t.get(s);i&&this.#m(i,o)}this.#g()}#m(e,t){e.state.tag!==\"settled\"&&(e.state={tag:\"settled\",outcome:t},this.#p({kind:\"settle\",operationId:e.id,outcome:t}))}#p(e){this.#a+=1;let t=this.#a;this.#r.push({seq:t,frame:e}),this.#r.length>this.#i&&this.#r.splice(0,this.#r.length-this.#i);let r=this.#u;r&&this.#f&&this.#c(r,{type:\"frame\",seq:t,frame:e})}#c(e,t){try{e.send(JSON.stringify(t))}catch{}}#A(){if(!(this.#t.size<=X))for(let[e,t]of this.#t){if(this.#t.size<=X)return;t.state.tag===\"settled\"&&this.#t.delete(e)}}};function Z(n){return n instanceof Error?n.message:String(n)}var Q={id:\"echo\",version:\"1\",capabilities:[\"steer\"],open(n){let e=n.restore.length,t=[];return{async run(r){let o=_(r.input),s=/^slow (\\d+) ?(.*)$/s.exec(o);s&&await ke(Number(s[1]),r.signal);let i=/^fail ?(.*)$/s.exec(o);if(i)return{status:\"unanswered\",reason:i[1]||\"failed\"};e+=1;let d=s?s[2]??\"\":o,a=t.splice(0).map(u=>` + ${u}`).join(\"\"),l=`echo: ${d}${a} (turn ${e})`,c=`${r.operationId}:assistant`;return n.emit({type:\"text-delta\",messageId:c,delta:l}),n.emit({type:\"message\",message:{id:c,role:\"assistant\",parts:[{type:\"text\",text:l}],operationId:r.operationId,createdAt:Date.now()}}),n.persist([{turn:e,text:d}]),{status:\"done\",text:l}},steer(r){return t.push(_(r)),!0},async close(){}}}};function ke(n,e){return new Promise((t,r)=>{if(e.aborted){r(new Error(\"aborted\"));return}let o=setTimeout(()=>{e.removeEventListener(\"abort\",s),t()},n),s=()=>{clearTimeout(o),r(new Error(\"aborted\"))};e.addEventListener(\"abort\",s,{once:!0})})}import{spawn as Ne}from\"node:child_process\";import{createHash as De}from\"node:crypto\";import{constants as Me}from\"node:fs\";import{chown as re,copyFile as Be,mkdir as oe,readdir as se,readlink as Je,symlink as Re}from\"node:fs/promises\";import{homedir as Le}from\"node:os\";import{join as O,resolve as L,sep as Ve}from\"node:path\";import{createInterface as Fe}from\"node:readline\";import{mkdir as Ae,open as P,readdir as _e,rm as ve,stat as ee}from\"node:fs/promises\";import{dirname as Oe,join as Ee,relative as Te,resolve as te,sep as ne}from\"node:path\";function w(n){return typeof n==\"object\"&&n!==null&&!Array.isArray(n)}function v(n){if(Array.isArray(n))return n.filter(e=>typeof e==\"string\")}var R=128*1024;function Pe(n){return w(n)&&n.kind===\"file\"&&typeof n.path==\"string\"&&typeof n.offset==\"number\"&&typeof n.data==\"string\"}var N=class{#e;#n;#s=new Map;constructor(e,t){this.#e=te(e),this.#n=t}async restore(e){for(let t of this.#n){let r=this.#t(t);r!==void 0&&r!==this.#e&&await ve(r,{recursive:!0,force:!0})}for(let t of e){if(!Pe(t))continue;let r=this.#t(t.path);if(r===void 0)continue;await Ae(Oe(r),{recursive:!0});let o=t.offset===0?await P(r,\"w\"):await P(r,\"r+\").catch(()=>P(r,\"w\"));try{t.offset>0&&await o.truncate(t.offset),await o.write(Buffer.from(t.data,\"base64\"),0,void 0,t.offset)}finally{await o.close()}}for(let t of await this.#r()){let r=await ee(t);this.#s.set(t,{size:r.size,mtimeMs:r.mtimeMs})}}async collect(){let e=[];for(let t of await this.#r()){let r=await ee(t).catch(()=>{});if(!r)continue;let o=this.#s.get(t);if(o&&o.size===r.size&&o.mtimeMs===r.mtimeMs)continue;let s=o&&r.size>o.size?o.size:0,i=await this.#a(t,s,r.size),d=[];for(let a=0;a<i.length;a+=R)d.push(i.subarray(a,a+R));d.length===0&&d.push(i),d.forEach((a,l)=>{e.push(this.#i(t,s+l*R,a))}),this.#s.set(t,{size:s+i.length,mtimeMs:r.mtimeMs})}return e}#i(e,t,r){return{kind:\"file\",path:Te(this.#e,e).split(ne).join(\"/\"),offset:t,data:r.toString(\"base64\")}}async#a(e,t,r){let o=await P(e,\"r\");try{let s=Buffer.alloc(Math.max(0,r-t)),{bytesRead:i}=await o.read(s,0,s.length,t);return s.subarray(0,i)}finally{await o.close()}}async#r(){let e=[],t=async r=>{let o=await _e(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){let i=Ee(r,s.name);s.isDirectory()?await t(i):s.isFile()&&e.push(i)}};for(let r of this.#n){let o=this.#t(r);o!==void 0&&await t(o)}return e.sort()}#t(e){let t=te(this.#e,e);return t===this.#e||t.startsWith(this.#e+ne)?t:void 0}};var He=5e3,je=4096;function qe(n){let e={};for(let t of n){if(!w(t)||t.kind!==\"state\")continue;let r=t.values;if(w(r))for(let[o,s]of Object.entries(r))typeof s==\"string\"&&(e[o]=s)}return e}async function ie(n,e){await re(n,e.uid,e.gid);let t=await se(n,{withFileTypes:!0}).catch(()=>[]);for(let r of t){let o=O(n,r.name);r.isDirectory()?await ie(o,e):await re(o,e.uid,e.gid).catch(()=>{})}}async function Ue(n,e,t){let r=L(t),o=async(s,i)=>{let d=await se(s,{withFileTypes:!0}).catch(()=>[]);for(let a of d){let l=O(s,a.name),c=L(l);if(c===r||c.startsWith(r+Ve))continue;let u=O(i,a.name);a.isDirectory()?(await oe(u,{recursive:!0}),await o(l,u)):a.isSymbolicLink()?await Re(await Je(l),u).catch(()=>{}):a.isFile()&&await Be(l,u,Me.COPYFILE_EXCL).catch(()=>{})}};await o(L(n),e)}function $e(n,e){return O(n,De(\"sha256\").update(e).digest(\"hex\").slice(0,32))}var V=class{#e;#n;#s;#i;#a;#r;#t=Promise.resolve();constructor(e,t,r,o){this.#e=e,this.#n=t,this.#s=r,this.#i=o,this.#a=qe(t.restore),this.#r=t.settings}async run(e){let t={operationId:e.operationId,prompt:_(e.input),settings:this.#r,state:this.#a},r=this.#e.command(t),o=this.#e.parser(t),[s,...i]=r.argv;if(s===void 0)throw new Error(\"The CLI command is empty\");let d=this.#e.user,a=Ne(s,i,{cwd:this.#e.cwd??\"/workspace\",env:{...process.env,...r.env,HOME:this.#s},stdio:[\"pipe\",\"pipe\",\"pipe\"],...d?{uid:d.uid,gid:d.gid}:{}});a.stdin.on(\"error\",()=>{}),a.stdin.end(r.stdin??\"\");let l=\"\";a.stderr.setEncoding(\"utf8\"),a.stderr.on(\"data\",p=>{l=(l+p).slice(-je)});let c;Fe({input:a.stdout}).on(\"line\",p=>{if(p.trim()===\"\")return;let y;try{y=o.line(p)}catch(h){this.#n.log(\"warn\",`unreadable CLI output: ${String(h)}`);return}for(let h of y.events??[])this.#n.emit(h);y.state&&this.#l(y.state),y.outcome&&(c=y.outcome)});let b=()=>{a.kill(\"SIGINT\"),setTimeout(()=>a.kill(\"SIGKILL\"),He).unref()};e.signal.addEventListener(\"abort\",b,{once:!0});let m=setInterval(()=>{this.#o()},this.#e.syncIntervalMs??5e3),f=await new Promise(p=>{a.on(\"error\",y=>{l+=`\n${y.message}`,p(null)}),a.on(\"close\",y=>p(y))});return clearInterval(m),e.signal.removeEventListener(\"abort\",b),await this.#o(),e.signal.aborted?{status:\"unanswered\",reason:\"aborted\"}:c??o.end({code:f,stderr:l})}configure(e){this.#r=e}async close(){await this.#t}#l(e){Object.entries(e).some(([r,o])=>this.#a[r]!==o)&&(this.#a={...this.#a,...e},this.#n.persist([{kind:\"state\",values:{...e}}]))}#o(){return this.#t=this.#t.then(async()=>{try{let e=await this.#i.collect();e.length>0&&this.#n.persist(e)}catch(e){this.#n.log(\"warn\",`state sync failed: ${String(e)}`)}}),this.#t}};function ae(n){let e=n.homes??O(Le(),\".harness\",\"sessions\");return{id:n.id,version:n.version??\"1\",capabilities:[\"resume\",\"fork\",\"model\"],async open(t){let r=$e(e,t.session);await oe(r,{recursive:!0}),n.skeleton&&await Ue(n.skeleton,r,e);let o=new N(r,n.stateDirs??[]);return await o.restore(t.restore),n.user&&await ie(r,n.user),new V(n,t,r,o)}}}function de(n){if(n===void 0)return null;try{return JSON.parse(JSON.stringify(n))}catch{return String(n)}}function ze(n){if(typeof n==\"string\")return n;if(Array.isArray(n)){let e=n.map(t=>typeof t==\"object\"&&t!==null&&\"type\"in t&&t.type===\"text\"&&\"text\"in t&&typeof t.text==\"string\"?t.text:void 0).filter(t=>t!==void 0);if(e.length===n.length)return e.join(`\n`)}return de(n)}var D=class{#e=new Map;#n=new Map;#s=new Set;#i;#a;constructor(e=Date.now){this.#a=e}project(e,t){if(e.parent_tool_use_id)return[];switch(e.type){case\"stream_event\":return this.#r(e);case\"assistant\":return this.#t(e,t);case\"user\":return this.#l(e);case\"result\":return this.#o(e);default:return[]}}outcome(e,t){if(t)return{status:\"unanswered\",reason:\"aborted\"};if(e.subtype===\"success\"&&e.is_error!==!0)return{status:\"done\",text:e.result??\"\"};let r=e.errors?.join(\"; \")||e.result;return{status:\"unanswered\",reason:[e.subtype&&e.subtype!==\"success\"?e.subtype:\"\",r].filter(s=>s).join(\": \")||\"error\"}}#r(e){let t=e.event;if(!t)return[];if(t.type===\"message_start\")return this.#i=t.message?.id,[];let r=this.#i;return t.type!==\"content_block_delta\"||!r||!t.delta?[]:t.delta.type===\"text_delta\"&&t.delta.text?[{type:\"text-delta\",messageId:r,delta:t.delta.text}]:t.delta.type===\"thinking_delta\"&&t.delta.thinking?[{type:\"reasoning-delta\",messageId:r,delta:t.delta.thinking}]:[]}#t(e,t){let r=e.message?.id,o=e.message?.content;if(!r||!Array.isArray(o))return[];if(e.uuid!==void 0){if(this.#s.has(e.uuid))return[];this.#s.add(e.uuid)}let s=this.#e.get(r);s||(s={id:r,operationId:t,createdAt:this.#a(),parts:[]},this.#e.set(r,s));for(let i of o)i.type===\"text\"&&i.text?s.parts.push({type:\"text\",text:i.text}):i.type===\"thinking\"&&i.thinking?s.parts.push({type:\"reasoning\",text:i.thinking}):i.type===\"tool_use\"&&i.id&&(s.parts.push({type:\"tool\",toolCallId:i.id,toolName:i.name??\"tool\",input:de(i.input),state:\"running\"}),this.#n.set(i.id,r));return[{type:\"message\",message:this.#d(s)}]}#l(e){let t=e.message?.content;if(!Array.isArray(t))return[];let r=new Set;for(let o of t){if(o.type!==\"tool_result\"||!o.tool_use_id)continue;let s=this.#n.get(o.tool_use_id),i=s===void 0?void 0:this.#e.get(s);i&&(i.parts=i.parts.map(d=>d.type===\"tool\"&&d.toolCallId===o.tool_use_id?{...d,state:o.is_error?\"error\":\"done\",output:ze(o.content)}:d),r.add(i))}return[...r].map(o=>({type:\"message\",message:this.#d(o)}))}#o(e){return[{type:\"usage\",...e.usage?.input_tokens===void 0?{}:{inputTokens:e.usage.input_tokens},...e.usage?.output_tokens===void 0?{}:{outputTokens:e.usage.output_tokens},...e.total_cost_usd===void 0?{}:{costUsd:e.total_cost_usd}}]}#d(e){return{id:e.id,role:\"assistant\",parts:[...e.parts],...e.operationId===void 0?{}:{operationId:e.operationId},createdAt:e.createdAt}}};function Ke(n){if(!w(n))return{};let e=v(n.allowedTools),t=v(n.disallowedTools);return{...typeof n.appendSystemPrompt==\"string\"?{appendSystemPrompt:n.appendSystemPrompt}:{},...e?{allowedTools:e}:{},...t?{disallowedTools:t}:{},...typeof n.maxTurns==\"number\"?{maxTurns:n.maxTurns}:{}}}function We(n){let e=JSON.parse(n);if(!(typeof e!=\"object\"||e===null||!(\"type\"in e)))return e}var le={id:\"claude-code\",stateDirs:[\".claude/projects\"],command(n){let e=Ke(n.settings.options),t=[\"claude\",\"-p\",\"--output-format\",\"stream-json\",\"--verbose\",\"--include-partial-messages\",\"--dangerously-skip-permissions\"];return n.settings.model&&t.push(\"--model\",n.settings.model),n.state.sessionId&&t.push(\"--resume\",n.state.sessionId),e.appendSystemPrompt&&t.push(\"--append-system-prompt\",e.appendSystemPrompt),e.allowedTools?.length&&t.push(\"--allowedTools\",e.allowedTools.join(\",\")),e.disallowedTools?.length&&t.push(\"--disallowedTools\",e.disallowedTools.join(\",\")),e.maxTurns!==void 0&&t.push(\"--max-turns\",String(e.maxTurns)),{argv:t,stdin:n.prompt,env:{DISABLE_AUTOUPDATER:\"1\"}}},parser(n){let e=new D;return{line(t){let r=We(t);return r?{events:e.project(r,n.operationId),...r.session_id?{state:{sessionId:r.session_id}}:{},...r.type===\"result\"?{outcome:e.outcome(r,!1)}:{}}:{}},end({code:t,stderr:r}){return{status:\"unanswered\",reason:`claude exited with ${t??\"a signal\"}: ${r.trim()||\"no output\"}`}}}}};function Ge(n){let e=JSON.parse(n);if(!(typeof e!=\"object\"||e===null))return e}function Ye(n){return n===\"completed\"?\"done\":n===\"failed\"||n===\"declined\"?\"error\":\"running\"}function Xe(n){let e=n.id??\"item\",t=Ye(n.status);switch(n.type){case\"agent_message\":return{type:\"text\",text:n.text??\"\"};case\"reasoning\":return{type:\"reasoning\",text:n.text??\"\"};case\"command_execution\":return{type:\"tool\",toolCallId:e,toolName:\"shell\",input:{command:n.command??\"\"},state:t===\"done\"&&n.exit_code!==0&&n.exit_code!=null?\"error\":t,...n.aggregated_output?{output:n.aggregated_output}:{}};case\"file_change\":return{type:\"tool\",toolCallId:e,toolName:\"file_change\",input:n.changes??null,state:t};case\"mcp_tool_call\":return{type:\"tool\",toolCallId:e,toolName:`${n.server??\"mcp\"}.${n.tool??\"tool\"}`,input:n.arguments??null,state:t,...n.result!==void 0||n.error!==void 0?{output:n.result??n.error??null}:{}};case\"web_search\":return{type:\"tool\",toolCallId:e,toolName:\"web_search\",input:{query:n.query??\"\"},state:t};case\"todo_list\":return{type:\"tool\",toolCallId:e,toolName:\"todo_list\",input:n.items??null,state:t};default:return}}function Ze(n){if(!w(n))return{};let e=v(n.config);return e?{config:e}:{}}var ue={id:\"codex\",stateDirs:[\".codex/sessions\"],command(n){let e=[\"codex\",\"exec\"];n.state.threadId&&e.push(\"resume\",n.state.threadId),e.push(\"--json\",\"--skip-git-repo-check\",\"--dangerously-bypass-approvals-and-sandbox\");let t=process.env.OPENAI_BASE_URL;t&&e.push(\"-c\",\"model_provider=gateway\",\"-c\",`model_providers.gateway={name=\"AI Gateway\",base_url=${JSON.stringify(t)},env_key=\"OPENAI_API_KEY\",wire_api=\"responses\"}`);for(let r of Ze(n.settings.options).config??[])e.push(\"-c\",r);return n.settings.model&&e.push(\"-m\",n.settings.model),e.push(\"-\"),{argv:e,stdin:n.prompt}},parser(n){let e=`${n.operationId}:assistant`,t=Date.now(),r=new Map,o=\"\",s,i=()=>({type:\"message\",message:{id:e,role:\"assistant\",parts:[...r.values()],operationId:n.operationId,createdAt:t}});return{line(d){let a=Ge(d);if(!a)return{};switch(a.type){case\"thread.started\":return a.thread_id?{state:{threadId:a.thread_id}}:{};case\"item.started\":case\"item.updated\":case\"item.completed\":{let l=a.item;if(!l)return{};if(l.type===\"error\")return{events:[{type:\"log\",level:\"warn\",message:l.message??\"error\"}]};let c=Xe(l);return c?(r.set(l.id??String(r.size),c),c.type===\"text\"&&a.type===\"item.completed\"&&(o=c.text),{events:[i()]}):{}}case\"turn.completed\":return{events:[{type:\"usage\",...a.usage?.input_tokens===void 0?{}:{inputTokens:a.usage.input_tokens},...a.usage?.output_tokens===void 0?{}:{outputTokens:a.usage.output_tokens}}],outcome:{status:\"done\",text:o}};case\"turn.failed\":return{outcome:{status:\"unanswered\",reason:a.error?.message??\"turn failed\"}};case\"error\":return s=a.message,{events:[{type:\"log\",level:\"warn\",message:a.message??\"error\"}]};default:return{}}},end({code:d,stderr:a}){return{status:\"unanswered\",reason:`codex exited with ${d??\"a signal\"}: ${s??(a.trim()||\"no output\")}`}}}}};import{timingSafeEqual as st}from\"node:crypto\";import{createServer as it}from\"node:http\";import{createHash as Qe}from\"node:crypto\";var et=\"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\",pe=64*1024*1024,tt=0,ce=1,nt=2,F=8,rt=9,ye=10;function ot(n,e){let t=e.length,r=t<126?Buffer.from([128|n,t]):t<65536?Buffer.from([128|n,126,t>>8,t&255]):Buffer.concat([Buffer.from([128|n,127]),(()=>{let o=Buffer.alloc(8);return o.writeBigUInt64BE(BigInt(t)),o})()]);return Buffer.concat([r,e])}function H(n,e,t,r){let o=n.headers[\"sec-websocket-key\"];if(typeof o!=\"string\"||n.headers.upgrade?.toLowerCase()!==\"websocket\"){e.destroy();return}let s=Qe(\"sha1\").update(o+et).digest(\"base64\");e.write(`HTTP/1.1 101 Switching Protocols\\r\nUpgrade: websocket\\r\nConnection: Upgrade\\r\nSec-WebSocket-Accept: ${s}\\r\n\\r\n`);let i=!0,d=!1,a=()=>{d||(d=!0,i=!1,r.closed())},l=(p,y)=>{i&&e.write(ot(p,y))},c={send:p=>l(ce,Buffer.from(p,\"utf8\")),close:(p,y)=>{if(!i)return;let h=Buffer.alloc(2+Buffer.byteLength(y));h.writeUInt16BE(p),h.write(y,2),l(F,h),i=!1,e.end()}},u=t.length>0?Buffer.from(t):Buffer.alloc(0),b=[],m=0,f=()=>{for(;;){if(u.length<2)return;let p=u[0]??0,y=u[1]??0,h=(p&128)!==0,we=p&15,q=(y&128)!==0,I=y&127,C=2;if(I===126){if(u.length<4)return;I=u.readUInt16BE(2),C=4}else if(I===127){if(u.length<10)return;let g=u.readBigUInt64BE(2);if(g>BigInt(pe)){c.close(1009,\"message too big\");return}I=Number(g),C=10}let Ce=C;if(q&&(C+=4),u.length<C+I)return;let S=Buffer.from(u.subarray(C,C+I));if(q)for(let g=0;g<S.length;g++)S[g]=(S[g]??0)^(u[Ce+g%4]??0);switch(u=u.subarray(C+I),we){case ce:case nt:case tt:{if(b.push(S),m+=S.length,m>pe){c.close(1009,\"message too big\");return}if(h){let g=Buffer.concat(b).toString(\"utf8\");b=[],m=0,r.message(g)}break}case rt:l(ye,S);break;case ye:break;case F:i&&(l(F,S.subarray(0,2)),i=!1),e.end(),a();return;default:c.close(1002,\"unsupported opcode\");return}}};return e.on(\"data\",p=>{u=u.length===0?p:Buffer.concat([u,p]),f()}),e.on(\"close\",a),e.on(\"error\",a),u.length>0&&queueMicrotask(f),c}function at(n,e){if(e===void 0)return!1;let t=Buffer.from(n),r=Buffer.from(e);return t.length===r.length&&st(t,r)}async function dt(n){let e=new T({adapter:n.adapter,runtimeId:n.runtimeId}),t=new Set,r=it((i,d)=>{if(i.url===$){d.writeHead(200,{\"content-type\":\"text/plain\"}),d.end(\"ok\");return}d.writeHead(404),d.end()});r.on(\"upgrade\",(i,d,a)=>{let l=new URL(i.url??\"/\",\"http://container\");if(!l.pathname.startsWith(M)){d.destroy();return}let c=decodeURIComponent(l.pathname.slice(M.length)),u=i.headers[z];if(!at(n.token,Array.isArray(u)?u[0]:u)||c.length===0){H(i,d,a,{message:()=>{},closed:()=>{}})?.close(W,\"unauthorized\");return}let m,f=H(i,d,a,{message:p=>m?.receive(p),closed:()=>{f&&t.delete(f),m?.closed()}});f&&(t.add(f),m=e.connect(c,f))}),await new Promise(i=>{r.listen(n.port,n.host??\"0.0.0.0\",i)});let o=r.address();return{port:typeof o==\"object\"&&o?o.port:0,async close(){for(let i of t)i.close(1001,\"shutting down\");await new Promise(i=>r.close(()=>i())),await e.close()}}}async function fe(n,e=process.env){let t=e[x.token],r=e[x.runtimeId],o=Number(e[x.port]??\"8080\");if(!t||!r||!Number.isInteger(o)||o<=0)throw new Error(`${x.token}, ${x.runtimeId} and ${x.port} must be set; ContainerHarness sets them`);delete e[x.token];let s=await dt({adapter:n,runtimeId:r,token:t,port:o});console.log(`harness-daemon: ${n.id} on ${s.port} (${r})`);let i=()=>{s.close().finally(()=>process.exit(0))};return process.once(\"SIGTERM\",i),process.once(\"SIGINT\",i),s}var lt={\"claude-code\":le,codex:ue};function ut(n){let e=n?/^(\\d+):(\\d+)$/.exec(n):null;return e?{uid:Number(e[1]),gid:Number(e[2])}:void 0}var j=process.env.CF_HARNESS_ADAPTER??\"claude-code\",ge=ut(process.env.CF_HARNESS_USER),me=lt[j],he=j===\"echo\"?Q:me?ae({...me,...ge?{user:ge,homes:\"/home/agent/.harness/sessions\",skeleton:\"/home/agent\"}:{}}):void 0;he||(console.error(`harness-daemon: unknown CF_HARNESS_ADAPTER ${j}`),process.exit(1));await fe(he).catch(n=>{console.error(`harness-daemon: ${String(n)}`),process.exit(1)});\n";
179
+ /** A hash of `DAEMON_SOURCE`, to tell daemon builds apart. */
180
+ const DAEMON_VERSION = "d45c6a8182ef9070";
181
+ //#endregion
182
+ //#region src/harness/container/managed-image.ts
183
+ /**
184
+ * Containers built at runtime from `cloudflare/debian-trixie`, with no
185
+ * image of your own.
186
+ *
187
+ * The first start of a setup runs its steps with `exec()` (create the
188
+ * unprivileged user, install the CLI), writes the bundled daemon, and
189
+ * snapshots the filesystem. Later starts restore the snapshot and are
190
+ * ready as soon as the daemon boots. A snapshot is keyed by the image, the
191
+ * steps, and the daemon build, so changing any of them sets up afresh.
192
+ *
193
+ * The container's entrypoint waits for the daemon to be written and then
194
+ * runs it, so the same entrypoint serves a fresh container (where setup
195
+ * writes the daemon) and a restored one (where it is already there).
196
+ */
197
+ /** The managed system image: Debian Trixie with Node.js. */
198
+ const MANAGED_IMAGE = "cloudflare/debian-trixie";
199
+ /** The unprivileged user CLIs run as, `uid:gid`. */
200
+ const AGENT_USER = "10001:10001";
201
+ const DAEMON_DIR = "/opt/harness";
202
+ /** Waits for setup to write the daemon, then becomes it. */
203
+ const MANAGED_ENTRYPOINT = [
204
+ "sh",
205
+ "-c",
206
+ `while [ ! -f ${DAEMON_DIR}/ready ]; do sleep 0.1; done; exec node ${DAEMON_DIR}/daemon.mjs`
207
+ ];
208
+ /** The agent user's home, which every session home starts as a copy of. */
209
+ const AGENT_HOME = "/home/agent";
210
+ /** Steps every managed agent needs before its own. */
211
+ const BASE_STEPS = [{
212
+ name: "create the agent user and workspace",
213
+ command: [
214
+ "sh",
215
+ "-c",
216
+ `id -u agent >/dev/null 2>&1 || useradd --create-home --uid 10001 --shell /bin/sh agent; mkdir -p /workspace ${DAEMON_DIR} && chown 10001:10001 /workspace`
217
+ ]
218
+ }, {
219
+ name: "install git",
220
+ command: [
221
+ "sh",
222
+ "-c",
223
+ "command -v git >/dev/null || (apt-get update -qq && apt-get install -y -qq --no-install-recommends git ca-certificates)"
224
+ ]
225
+ }];
226
+ /** How long one setup step may run. */
227
+ const STEP_TIMEOUT_MS = 10 * 6e4;
228
+ /**
229
+ * The key a snapshot of this setup is stored under.
230
+ *
231
+ * @param steps - The setup steps.
232
+ * @returns A stable key for the image, the steps, and the daemon build.
233
+ */
234
+ async function setupKey(steps) {
235
+ const text = JSON.stringify({
236
+ image: MANAGED_IMAGE,
237
+ steps: steps.map((step) => [step.user ?? "root", ...step.command]),
238
+ daemon: DAEMON_VERSION
239
+ });
240
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
241
+ return [...new Uint8Array(digest)].slice(0, 16).map((byte) => byte.toString(16).padStart(2, "0")).join("");
242
+ }
243
+ async function run(container, step, stdin) {
244
+ const asAgent = step.user === "agent";
245
+ const process = await container.exec([...step.command], {
246
+ env: {
247
+ HOME: asAgent ? AGENT_HOME : "/root",
248
+ DEBIAN_FRONTEND: "noninteractive"
249
+ },
250
+ ...asAgent ? {
251
+ user: AGENT_USER,
252
+ cwd: AGENT_HOME
253
+ } : {},
254
+ stderr: "combined",
255
+ ...stdin === void 0 ? {} : { stdin: new Blob([stdin]).stream() }
256
+ });
257
+ let exited = false;
258
+ process.exitCode.then(() => exited = true, () => exited = true);
259
+ const timer = setTimeout(() => {
260
+ if (!exited) process.kill(9);
261
+ }, STEP_TIMEOUT_MS);
262
+ try {
263
+ const output = await process.output();
264
+ if (output.exitCode === 0) return void 0;
265
+ const text = new TextDecoder().decode(output.stdout).slice(-2e3);
266
+ return {
267
+ _tag: "setup",
268
+ message: `${step.name} exited with ${output.exitCode}: ${text.trim()}`
269
+ };
270
+ } finally {
271
+ clearTimeout(timer);
272
+ }
273
+ }
274
+ /**
275
+ * Set a fresh managed container up: run the steps, install the daemon, and
276
+ * snapshot the result. A failed snapshot is not an error: the container
277
+ * still works, and the next start sets up again.
278
+ *
279
+ * @param container - The running container, started from `MANAGED_IMAGE`
280
+ * with `MANAGED_ENTRYPOINT`.
281
+ * @param steps - The agent's setup, after `BASE_STEPS`.
282
+ * @param key - The setup's key, from `setupKey`.
283
+ * @returns The snapshot to store, `undefined` when none was taken, or why
284
+ * setup failed.
285
+ */
286
+ async function setUp(container, steps, key) {
287
+ for (const step of [...BASE_STEPS, ...steps]) {
288
+ const failed = await run(container, step).catch((error) => ({
289
+ _tag: "setup",
290
+ message: `${step.name} failed: ${error instanceof Error ? error.message : String(error)}`
291
+ }));
292
+ if (failed) return {
293
+ _tag: "err",
294
+ error: failed
295
+ };
296
+ }
297
+ const installed = await run(container, {
298
+ name: "install the daemon",
299
+ command: [
300
+ "sh",
301
+ "-c",
302
+ `cat > ${DAEMON_DIR}/daemon.mjs && touch ${DAEMON_DIR}/ready`
303
+ ]
304
+ }, DAEMON_SOURCE);
305
+ if (installed) return {
306
+ _tag: "err",
307
+ error: installed
308
+ };
309
+ try {
310
+ return {
311
+ _tag: "ok",
312
+ snapshot: {
313
+ key,
314
+ snapshot: { id: (await container.snapshotContainer({ name: `harness-${key}` })).id }
315
+ }
316
+ };
317
+ } catch {
318
+ return {
319
+ _tag: "ok",
320
+ snapshot: void 0
321
+ };
322
+ }
323
+ }
324
+ //#endregion
325
+ //#region src/harness/container/harness.ts
326
+ /** The root session's id. */
327
+ const ROOT_SESSION = "root";
328
+ /** Default `idleTimeoutMs`: the container stops five minutes after its last work. */
329
+ const IDLE_TIMEOUT_MS = 5 * 6e4;
330
+ /** Default `startTimeoutMs`: how long a new container may take to answer. */
331
+ const START_TIMEOUT_MS = 6e4;
332
+ /** How long a container that already runs may take to answer before it is replaced. */
333
+ const ADOPT_TIMEOUT_MS = 1e4;
334
+ /** Default `port`: where the daemon listens in the container. */
335
+ const DAEMON_PORT = 8080;
336
+ /**
337
+ * Longest one wake waits on the container. It waits inside an alarm
338
+ * invocation, which has a 15 minute wall-time limit, so a longer run is
339
+ * waited on across several alarms.
340
+ */
341
+ const WAIT_BUDGET_MS = 10 * 6e4;
342
+ /**
343
+ * The wake job's heartbeat while it waits. If the object is evicted, the
344
+ * job is still due and its alarm restarts the object, which reattaches.
345
+ */
346
+ const HEARTBEAT_MS = 3e4;
347
+ /** Consecutive failed starts before a session's open work is given up. */
348
+ const MAX_START_FAILURES = 5;
349
+ /** How often a wait checks that the container still runs. */
350
+ const CONTAINER_CHECK_MS = 2e3;
351
+ /** How often `wait()` re-reads the store while it waits. */
352
+ const SETTLE_POLL_MS = 1e3;
353
+ /** Frames between acknowledgements, so the daemon can forget them. */
354
+ const ACK_EVERY = 32;
355
+ const WAKE_FN = "wake";
356
+ const IDLE_FN = "idle";
357
+ const IDLE_JOB = "container-idle";
358
+ const RUNTIME_KEY = "container-harness:runtime";
359
+ const SNAPSHOT_KEY = "container-harness:snapshot";
360
+ const WORKSPACE_KEY = "container-harness:workspace";
361
+ /**
362
+ * How much longer than `idleTimeoutMs` the platform keeps an inactive
363
+ * object's container. The harness's own idle stop runs first, so it can
364
+ * snapshot the workspace before the container goes.
365
+ */
366
+ const IDLE_GRACE_MS = 2 * 6e4;
367
+ /** The longest inactivity timeout the platform accepts. */
368
+ const MAX_INACTIVITY_MS = 360 * 6e4;
369
+ /**
370
+ * Snapshots live 30 days from their last restore. One unused for nearly
371
+ * that long is treated as gone rather than tried.
372
+ */
373
+ const SNAPSHOT_STALE_MS = 696 * 60 * 6e4;
374
+ /**
375
+ * The platform does not say why a start failed. A snapshot that fails this
376
+ * many starts in a row is skipped (not deleted) in favour of the next
377
+ * source: whether that one works decides whether the snapshot was at fault.
378
+ */
379
+ const SKIP_AFTER_FAILURES = 2;
380
+ /** Snapshot sources, most preferred first. */
381
+ const SNAPSHOT_SOURCES = [{
382
+ from: "workspace",
383
+ storageKey: WORKSPACE_KEY
384
+ }, {
385
+ from: "snapshot",
386
+ storageKey: SNAPSHOT_KEY
387
+ }];
388
+ const MESSAGES = "messages";
389
+ const ENGINE = "engine";
390
+ const OPEN = ["queued", "running"];
391
+ function wakeJobId(session) {
392
+ return `container-wake:${session}`;
393
+ }
394
+ function parseWakePayload(payload) {
395
+ if (typeof payload !== "object" || payload === null || !("session" in payload) || typeof payload.session !== "string") return;
396
+ const failures = "failures" in payload && typeof payload.failures === "number" ? payload.failures : 0;
397
+ return {
398
+ session: payload.session,
399
+ failures
400
+ };
401
+ }
402
+ function isRecord(value) {
403
+ return typeof value === "object" && value !== null && !Array.isArray(value);
404
+ }
405
+ function parseSessionState(value) {
406
+ const fields = isRecord(value) ? value : {};
407
+ const cursor = fields.cursor;
408
+ return {
409
+ model: typeof fields.model === "string" ? fields.model : void 0,
410
+ cursor: isRecord(cursor) && typeof cursor.runtimeId === "string" && typeof cursor.seq === "number" ? {
411
+ runtimeId: cursor.runtimeId,
412
+ seq: cursor.seq
413
+ } : null,
414
+ generation: typeof fields.generation === "number" ? fields.generation : 1,
415
+ messagesFrom: typeof fields.messagesFrom === "number" ? fields.messagesFrom : 0,
416
+ handoff: typeof fields.handoff === "string" ? fields.handoff : void 0
417
+ };
418
+ }
419
+ function sessionStateJson(state) {
420
+ return {
421
+ ...state.model === void 0 ? {} : { model: state.model },
422
+ cursor: state.cursor,
423
+ generation: state.generation,
424
+ messagesFrom: state.messagesFrom,
425
+ ...state.handoff === void 0 ? {} : { handoff: state.handoff }
426
+ };
427
+ }
428
+ function parseMeta(value) {
429
+ const fields = isRecord(value) ? value : {};
430
+ return {
431
+ whenBusy: fields.whenBusy === "steer" ? "steer" : "followUp",
432
+ delivered: typeof fields.delivered === "string" ? fields.delivered : null
433
+ };
434
+ }
435
+ function metaJson(meta) {
436
+ return {
437
+ whenBusy: meta.whenBusy,
438
+ delivered: meta.delivered
439
+ };
440
+ }
441
+ function parseInput(value) {
442
+ if (typeof value === "string") return value;
443
+ return value;
444
+ }
445
+ function inputJson(input) {
446
+ return input;
447
+ }
448
+ function parseMessage(value) {
449
+ return value;
450
+ }
451
+ function messageJson(message) {
452
+ return message;
453
+ }
454
+ function userParts(input) {
455
+ if (typeof input === "string") return [{
456
+ type: "text",
457
+ text: input
458
+ }];
459
+ return input.map((part) => part.type === "text" ? {
460
+ type: "text",
461
+ text: part.text
462
+ } : {
463
+ type: "image",
464
+ mediaType: part.mediaType,
465
+ data: part.data
466
+ });
467
+ }
468
+ function withHandoff(input, handoff) {
469
+ const note = {
470
+ type: "text",
471
+ text: handoff
472
+ };
473
+ if (typeof input === "string") return [note, {
474
+ type: "text",
475
+ text: input
476
+ }];
477
+ return [note, ...input];
478
+ }
479
+ function errorText(error) {
480
+ return error instanceof Error ? error.message : String(error);
481
+ }
482
+ var _options = /* @__PURE__ */ new WeakMap();
483
+ var _idleTimeoutMs = /* @__PURE__ */ new WeakMap();
484
+ var _startTimeoutMs = /* @__PURE__ */ new WeakMap();
485
+ var _waitBudgetMs = /* @__PURE__ */ new WeakMap();
486
+ var _heartbeatMs = /* @__PURE__ */ new WeakMap();
487
+ var _retryBaseMs = /* @__PURE__ */ new WeakMap();
488
+ var _idleStopMs = /* @__PURE__ */ new WeakMap();
489
+ var _store = /* @__PURE__ */ new WeakMap();
490
+ var _runtime = /* @__PURE__ */ new WeakMap();
491
+ var _starting = /* @__PURE__ */ new WeakMap();
492
+ var _timeoutSet = /* @__PURE__ */ new WeakMap();
493
+ var _attached = /* @__PURE__ */ new WeakMap();
494
+ var _attaching = /* @__PURE__ */ new WeakMap();
495
+ var _waits = /* @__PURE__ */ new WeakMap();
496
+ var _settleWaiters = /* @__PURE__ */ new WeakMap();
497
+ var _changeWaiters = /* @__PURE__ */ new WeakMap();
498
+ var _listeners = /* @__PURE__ */ new WeakMap();
499
+ var _ContainerHarness_brand = /* @__PURE__ */ new WeakSet();
500
+ /**
501
+ * An agent CLI running in a Cloudflare Container, driven from a Durable
502
+ * Object, behind the same interface as `PiHarness`: `harness.prompt()`,
503
+ * `harness.sessions`, `harness.session(id)`.
504
+ *
505
+ * The harness does not know which agent runs in the container. A preset
506
+ * (`claudeCode()`, `codex()`) says what to install and which adapter the
507
+ * daemon runs; the harness builds the container from
508
+ * `cloudflare/debian-trixie` on first use, installs the bundled daemon, and
509
+ * snapshots the result, so there is no image to build. Credentials stay
510
+ * outside: the CLI calls a placeholder host, and `ContainerEgress` adds the
511
+ * real key. Any adapter that produces `ContainerEvent`s works, including
512
+ * one in an image of your own (`containerAgent()`).
513
+ *
514
+ * The object owns the durable record, in a `HarnessStore`: sessions,
515
+ * operations, the transcript, and the adapter's resume state. The container
516
+ * is disposable. It is started on demand with `ctx.container.start()`, kept
517
+ * for `idleTimeoutMs` after its last work, and then stopped. The next
518
+ * prompt starts a new container and hands the adapter everything it
519
+ * persisted, so the agent resumes its own session there.
520
+ *
521
+ * Each session has one Lifecycle wake job. It runs while the session has
522
+ * open operations: it starts or reattaches to the container, reconciles
523
+ * what the container knows with what the object stored, delivers queued
524
+ * operations, and waits for them, rescheduling itself as a heartbeat. An
525
+ * eviction mid-run leaves the job due, so its alarm restarts the object,
526
+ * which reattaches and replays the frames it missed from its cursor.
527
+ *
528
+ * Recovery, by what was lost:
529
+ *
530
+ * - **The object** (eviction, deploy): the container keeps running for
531
+ * `idleTimeoutMs`. The wake reattaches and replays from the cursor.
532
+ * - **The socket**: the same, on the next heartbeat.
533
+ * - **The container** (idle stop, crash, platform restart): a new one
534
+ * answers with a different runtime id. The session is reopened in it
535
+ * from the persisted resume state; queued operations run there; an
536
+ * operation that was running is settled `container_lost` or run again,
537
+ * per `onContainerLost`.
538
+ *
539
+ * @experimental The API may change before it stabilizes.
540
+ */
541
+ var ContainerHarness = class extends LifecycleCapability {
542
+ /**
543
+ * @param options - The container, the agent, and session defaults.
544
+ */
545
+ constructor(options) {
546
+ super("container-harness");
547
+ _classPrivateMethodInitSpec(this, _ContainerHarness_brand);
548
+ _classPrivateFieldInitSpec(this, _options, void 0);
549
+ _classPrivateFieldInitSpec(this, _idleTimeoutMs, void 0);
550
+ _classPrivateFieldInitSpec(this, _startTimeoutMs, void 0);
551
+ _classPrivateFieldInitSpec(this, _waitBudgetMs, WAIT_BUDGET_MS);
552
+ _classPrivateFieldInitSpec(this, _heartbeatMs, HEARTBEAT_MS);
553
+ _classPrivateFieldInitSpec(this, _retryBaseMs, 1e3);
554
+ _classPrivateFieldInitSpec(this, _idleStopMs, void 0);
555
+ _classPrivateFieldInitSpec(this, _store, void 0);
556
+ _classPrivateFieldInitSpec(this, _runtime, void 0);
557
+ _classPrivateFieldInitSpec(this, _starting, void 0);
558
+ _classPrivateFieldInitSpec(this, _timeoutSet, false);
559
+ _classPrivateFieldInitSpec(this, _attached, /* @__PURE__ */ new Map());
560
+ _classPrivateFieldInitSpec(this, _attaching, /* @__PURE__ */ new Map());
561
+ _classPrivateFieldInitSpec(this, _waits, /* @__PURE__ */ new Map());
562
+ _classPrivateFieldInitSpec(this, _settleWaiters, /* @__PURE__ */ new Map());
563
+ _classPrivateFieldInitSpec(this, _changeWaiters, /* @__PURE__ */ new Map());
564
+ _classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Map());
565
+ const idle = options.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
566
+ if (!Number.isFinite(idle) || idle <= 0 || idle > MAX_INACTIVITY_MS - IDLE_GRACE_MS) throw new Error("ContainerHarness idleTimeoutMs must be positive and at most six hours less two minutes");
567
+ for (const route of options.agent.egress) if (!URL.canParse(route.upstream) || new URL(route.upstream).protocol !== "https:") throw new Error(`ContainerHarness: egress upstream for ${route.host} must be an https URL, got ${JSON.stringify(route.upstream)}`);
568
+ if (options.agent.egress.length > 0 && options.egress === void 0) throw new Error("ContainerHarness: the agent's credentials need `egress: ctx.exports.ContainerEgress`, and `export { ContainerEgress } from \"agents/harness/container\"` in your main module");
569
+ _classPrivateFieldSet2(_options, this, options);
570
+ _classPrivateFieldSet2(_idleTimeoutMs, this, idle);
571
+ _classPrivateFieldSet2(_startTimeoutMs, this, options.startTimeoutMs ?? START_TIMEOUT_MS);
572
+ _classPrivateFieldSet2(_idleStopMs, this, idle);
573
+ this.sessions = new ContainerSessions(this);
574
+ }
575
+ /** Reopen the store, re-arm the container's idle timeout, and wake open sessions. */
576
+ async onStart(_context) {
577
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
578
+ const container = _classPrivateFieldGet2(_options, this).container;
579
+ if (container.running) {
580
+ await container.setInactivityTimeout(_assertClassBrand(_ContainerHarness_brand, this, _inactivityTimeoutMs).call(this));
581
+ _classPrivateFieldSet2(_timeoutSet, this, true);
582
+ }
583
+ const open = store.operations({ status: OPEN });
584
+ const sessions = new Set(open.map((operation) => operation.session));
585
+ for (const session of sessions) if (!this.lifecycle.jobs.get(wakeJobId(session))) await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session);
586
+ if (sessions.size === 0 && container.running) await _assertClassBrand(_ContainerHarness_brand, this, _scheduleIdle).call(this);
587
+ }
588
+ /** Dispatch the harness's jobs: one wake per session, and the idle stop. */
589
+ async onJob(context) {
590
+ if (context.job.fn === IDLE_FN) return _assertClassBrand(_ContainerHarness_brand, this, _idleStep).call(this);
591
+ if (context.job.fn !== WAKE_FN) return;
592
+ const wake = parseWakePayload(context.job.payload);
593
+ if (wake !== void 0) return _assertClassBrand(_ContainerHarness_brand, this, _wakeStep).call(this, wake);
594
+ }
595
+ /** Close the session sockets. Durable state and the container are untouched. */
596
+ async dispose() {
597
+ for (const session of [..._classPrivateFieldGet2(_attached, this).keys()]) _assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
598
+ }
599
+ /**
600
+ * A handle on one session. No I/O until you call it.
601
+ *
602
+ * @param id - The session. Default: the root session.
603
+ * @returns The handle.
604
+ */
605
+ session(id = ROOT_SESSION) {
606
+ return new ContainerSession(this, id);
607
+ }
608
+ /**
609
+ * Submit a prompt and wait for its answer.
610
+ *
611
+ * @param input - The prompt.
612
+ * @param options - Session, idempotency key, and busy behaviour.
613
+ * @returns The result, with the session's transcript after it.
614
+ */
615
+ prompt(input, options = {}) {
616
+ return this.session(options.session).prompt(input, options);
617
+ }
618
+ /**
619
+ * Durably submit a prompt. Resolves before the agent runs.
620
+ *
621
+ * @param input - The prompt.
622
+ * @param options - Session, idempotency key, and busy behaviour.
623
+ * @returns The receipt.
624
+ */
625
+ submit(input, options = {}) {
626
+ return this.session(options.session).submit(input, options);
627
+ }
628
+ /**
629
+ * Stop one operation, or everything running in a session.
630
+ *
631
+ * @param options - The session, and optionally one operation.
632
+ * @returns Whether anything was stopped.
633
+ */
634
+ abort(options = {}) {
635
+ return this.session(options.session).abort(options.operationId);
636
+ }
637
+ /**
638
+ * Wait for an operation to settle.
639
+ *
640
+ * @param operationId - The operation.
641
+ * @param options - The session, and a signal that stops only the wait.
642
+ * @returns How it ended.
643
+ */
644
+ wait(operationId, options = {}) {
645
+ return this.session(options.session).wait(operationId, options.signal);
646
+ }
647
+ /**
648
+ * The session's active transcript.
649
+ *
650
+ * @param options - The session.
651
+ * @returns Its messages since the newest reset.
652
+ */
653
+ messages(options = {}) {
654
+ return this.session(options.session).messages();
655
+ }
656
+ /**
657
+ * Submissions that have not settled yet, oldest first.
658
+ *
659
+ * @param options - Narrow to one session. Default: every session.
660
+ * @returns The open operations.
661
+ */
662
+ async pending(options = {}) {
663
+ return (await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this)).operations({
664
+ ...options.session === void 0 ? {} : { session: options.session },
665
+ status: OPEN
666
+ }).map((operation) => ({
667
+ operationId: operation.id,
668
+ session: operation.session,
669
+ status: operation.status === "running" ? "running" : "queued"
670
+ }));
671
+ }
672
+ /**
673
+ * The container, as the harness sees it.
674
+ *
675
+ * @returns Whether it runs, and the id the harness started it with.
676
+ */
677
+ async container() {
678
+ const runtime = await _assertClassBrand(_ContainerHarness_brand, this, _runtimeRecord).call(this);
679
+ return {
680
+ running: _classPrivateFieldGet2(_options, this).container.running,
681
+ runtimeId: runtime?.runtimeId
682
+ };
683
+ }
684
+ /**
685
+ * Stop the container now. Sessions survive: the next prompt starts a new
686
+ * container and resumes them in it. Running operations are reconciled as
687
+ * if the container had died.
688
+ */
689
+ async stop() {
690
+ await this.dispose();
691
+ const container = _classPrivateFieldGet2(_options, this).container;
692
+ if (container.running) {
693
+ await _assertClassBrand(_ContainerHarness_brand, this, _saveWorkspace).call(this, container);
694
+ await container.destroy();
695
+ }
696
+ _assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
697
+ type: "container",
698
+ status: "stopped"
699
+ });
700
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
701
+ const sessions = new Set(store.operations({ status: OPEN }).map((operation) => operation.session));
702
+ for (const session of sessions) await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session);
703
+ }
704
+ /** @internal */
705
+ async store() {
706
+ return _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
707
+ }
708
+ /** @internal Create a session with the defaults, or fork one. */
709
+ async createSession(id, from) {
710
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
711
+ store.transaction(() => {
712
+ if (from === void 0) {
713
+ store.createSession({
714
+ id,
715
+ state: sessionStateJson(_assertClassBrand(_ContainerHarness_brand, this, _initial).call(this))
716
+ });
717
+ return;
718
+ }
719
+ const parent = store.session(from);
720
+ if (!parent) throw new Error(`Unknown container session ${from}`);
721
+ const parentState = parseSessionState(parent.state);
722
+ store.createSession({
723
+ id,
724
+ parent: from,
725
+ state: sessionStateJson({
726
+ ..._assertClassBrand(_ContainerHarness_brand, this, _initial).call(this),
727
+ model: parentState.model
728
+ })
729
+ });
730
+ store.append(id, MESSAGES, store.read(from, MESSAGES, { after: parentState.messagesFrom }).map((entry) => entry.id === void 0 ? { data: entry.data } : {
731
+ id: entry.id,
732
+ data: entry.data
733
+ }));
734
+ store.copy(from, id, ENGINE);
735
+ });
736
+ }
737
+ /** @internal */
738
+ async enqueue(session, input, options) {
739
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
740
+ _assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session);
741
+ const operationId = options.operationId ?? crypto.randomUUID();
742
+ const meta = {
743
+ whenBusy: options.whenBusy ?? "followUp",
744
+ delivered: null
745
+ };
746
+ const { accepted } = store.enqueue({
747
+ session,
748
+ id: operationId,
749
+ input: inputJson(input),
750
+ meta: metaJson(meta)
751
+ });
752
+ if (accepted) _assertClassBrand(_ContainerHarness_brand, this, _deliver).call(this, store, session);
753
+ await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session);
754
+ return {
755
+ operationId,
756
+ session,
757
+ accepted
758
+ };
759
+ }
760
+ /** @internal Withdraw or abort one operation, or all of a session's. */
761
+ async withdraw(session, operationId) {
762
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
763
+ const targets = operationId === void 0 ? store.operations({
764
+ session,
765
+ status: OPEN
766
+ }) : [store.operation(session, operationId)].filter((operation) => operation !== void 0 && (operation.status === "queued" || operation.status === "running"));
767
+ if (targets.length === 0) return operationId === void 0;
768
+ const attached = _classPrivateFieldGet2(_attached, this).get(session);
769
+ if (operationId === void 0) attached?.link.send({ type: "abort" });
770
+ else if (targets.some((each) => parseMeta(each.meta).delivered !== null)) attached?.link.send({
771
+ type: "abort",
772
+ operationId
773
+ });
774
+ for (const operation of targets) _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operation.id, {
775
+ status: "unanswered",
776
+ reason: "aborted"
777
+ });
778
+ return true;
779
+ }
780
+ /** @internal Wait for an operation to settle. */
781
+ async settled(session, operationId, signal) {
782
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
783
+ for (;;) {
784
+ const operation = store.operation(session, operationId);
785
+ if (!operation) return {
786
+ operationId,
787
+ session,
788
+ status: "unanswered",
789
+ reason: "not_found"
790
+ };
791
+ const result = resultOf(operation);
792
+ if (result) return result;
793
+ if (signal?.aborted) throw signal.reason;
794
+ await _assertClassBrand(_ContainerHarness_brand, this, _until).call(this, _classPrivateFieldGet2(_settleWaiters, this), `${session}\0${operationId}`, signal, SETTLE_POLL_MS);
795
+ }
796
+ }
797
+ /** @internal The active transcript. */
798
+ async transcript(session) {
799
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
800
+ const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
801
+ return store.read(session, MESSAGES, { after: state.messagesFrom }).map((entry) => parseMessage(entry.data));
802
+ }
803
+ /** @internal */
804
+ async busy(session) {
805
+ return (await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this)).operations({
806
+ session,
807
+ status: OPEN
808
+ }).length > 0;
809
+ }
810
+ /** @internal Start a new context: a new agent session, without history. */
811
+ async resetSession(session, handoff) {
812
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
813
+ if (store.operations({
814
+ session,
815
+ status: OPEN
816
+ }).length > 0) throw new Error(`Container session ${session} is busy; abort it first`);
817
+ store.transaction(() => {
818
+ const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
819
+ store.clear(session, ENGINE);
820
+ store.setSessionState(session, sessionStateJson({
821
+ ...state,
822
+ generation: state.generation + 1,
823
+ messagesFrom: store.end(session, MESSAGES),
824
+ handoff
825
+ }));
826
+ });
827
+ _assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
828
+ }
829
+ /** @internal */
830
+ async setModel(session, model) {
831
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
832
+ const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
833
+ store.setSessionState(session, sessionStateJson({
834
+ ...state,
835
+ model
836
+ }));
837
+ _classPrivateFieldGet2(_attached, this).get(session)?.link.send({
838
+ type: "configure",
839
+ settings: _assertClassBrand(_ContainerHarness_brand, this, _settings).call(this, {
840
+ ...state,
841
+ model
842
+ })
843
+ });
844
+ }
845
+ /** @internal */
846
+ async events(session) {
847
+ let buffered = [];
848
+ let listener;
849
+ let stopped = false;
850
+ const relay = (events) => {
851
+ if (buffered) buffered.push(...events);
852
+ else listener?.(events);
853
+ };
854
+ let set = _classPrivateFieldGet2(_listeners, this).get(session);
855
+ if (!set) {
856
+ set = /* @__PURE__ */ new Set();
857
+ _classPrivateFieldGet2(_listeners, this).set(session, set);
858
+ }
859
+ set.add(relay);
860
+ let snapshot;
861
+ try {
862
+ snapshot = {
863
+ session,
864
+ messages: await this.transcript(session),
865
+ pending: await this.pending({ session }),
866
+ busy: await this.busy(session)
867
+ };
868
+ } catch (error) {
869
+ set.delete(relay);
870
+ throw error;
871
+ }
872
+ return {
873
+ snapshot,
874
+ start: (next) => {
875
+ if (listener || stopped) throw new Error("The event stream has already started");
876
+ listener = next;
877
+ const held = buffered ?? [];
878
+ buffered = void 0;
879
+ if (held.length > 0) next(held);
880
+ },
881
+ stop: async () => {
882
+ stopped = true;
883
+ buffered = void 0;
884
+ listener = void 0;
885
+ _classPrivateFieldGet2(_listeners, this).get(session)?.delete(relay);
886
+ }
887
+ };
888
+ }
889
+ };
890
+ /** Schedule the session's wake job now, or pull it forward. */
891
+ function _wake(session, time = Date.now(), failures = 0) {
892
+ const payload = {
893
+ session,
894
+ failures
895
+ };
896
+ return this.lifecycle.jobs.push({
897
+ id: wakeJobId(session),
898
+ fn: WAKE_FN,
899
+ time,
900
+ payload,
901
+ singleflight: true,
902
+ recoveryLoop: true
903
+ });
904
+ }
905
+ /**
906
+ * One run of a session's wake job. It never blocks on the container: it
907
+ * hands the drive (start or attach, reconcile, deliver, wait) to the
908
+ * alarm's tracked work and returns a heartbeat. It completes when the
909
+ * session has no open operations.
910
+ */
911
+ async function _wakeStep(wake) {
912
+ const { session } = wake;
913
+ const heartbeat = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_heartbeatMs, this) };
914
+ if (_classPrivateFieldGet2(_waits, this).has(session)) return heartbeat;
915
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
916
+ if (!store.session(session)) return void 0;
917
+ if (store.operations({
918
+ session,
919
+ status: OPEN
920
+ }).length === 0) {
921
+ _assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
922
+ return;
923
+ }
924
+ const drive = _assertClassBrand(_ContainerHarness_brand, this, _drive).call(this, store, wake).catch((error) => {
925
+ const detail = {
926
+ session,
927
+ error: errorText(error)
928
+ };
929
+ console.error("[container-harness] drive failed", detail);
930
+ this.lifecycle.events.emit("container:drive_error", detail);
931
+ return {
932
+ at: Date.now() + _classPrivateFieldGet2(_retryBaseMs, this),
933
+ failures: wake.failures
934
+ };
935
+ }).then((next) => {
936
+ _classPrivateFieldGet2(_waits, this).delete(session);
937
+ _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, next.at, next.failures);
938
+ });
939
+ _classPrivateFieldGet2(_waits, this).set(session, drive);
940
+ this.lifecycle.trackAlarmWork(drive);
941
+ return heartbeat;
942
+ }
943
+ /**
944
+ * Attach, deliver, and wait while the session has open work and a live
945
+ * socket. Resolves with when the wake should look again.
946
+ */
947
+ async function _drive(store, wake) {
948
+ const { session } = wake;
949
+ const failures = wake.failures + 1;
950
+ await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, Date.now() + _classPrivateFieldGet2(_heartbeatMs, this), failures);
951
+ const attached = await _assertClassBrand(_ContainerHarness_brand, this, _attach).call(this, session);
952
+ if (attached._tag === "err" && attached.error._tag === "retry") {
953
+ await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, Date.now() + _classPrivateFieldGet2(_heartbeatMs, this), wake.failures);
954
+ return {
955
+ at: Date.now(),
956
+ failures: wake.failures
957
+ };
958
+ }
959
+ if (attached._tag === "err") {
960
+ const detail = {
961
+ session,
962
+ failures,
963
+ error: attached.error.message
964
+ };
965
+ console.warn("[container-harness] attach failed", detail);
966
+ this.lifecycle.events.emit("container:attach_error", detail);
967
+ if (failures < MAX_START_FAILURES) {
968
+ const backoff = Math.min(_classPrivateFieldGet2(_retryBaseMs, this) * 2 ** (failures - 1), 3e4);
969
+ return {
970
+ at: Date.now() + backoff,
971
+ failures
972
+ };
973
+ }
974
+ await _assertClassBrand(_ContainerHarness_brand, this, _clearAllFailures).call(this);
975
+ for (const operation of store.operations({
976
+ session,
977
+ status: OPEN
978
+ })) _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operation.id, {
979
+ status: "unanswered",
980
+ reason: "container_unavailable"
981
+ });
982
+ return {
983
+ at: Date.now(),
984
+ failures: 0
985
+ };
986
+ }
987
+ await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, Date.now() + _classPrivateFieldGet2(_heartbeatMs, this), 0);
988
+ _assertClassBrand(_ContainerHarness_brand, this, _deliver).call(this, store, session);
989
+ await _assertClassBrand(_ContainerHarness_brand, this, _waitForWork).call(this, store, session);
990
+ return {
991
+ at: Date.now(),
992
+ failures: 0
993
+ };
994
+ }
995
+ /**
996
+ * Resolves when the session has no open work, its socket closed, the
997
+ * container stopped, or the budget ran out.
998
+ */
999
+ async function _waitForWork(store, session) {
1000
+ const budget = new AbortController();
1001
+ const timer = setTimeout(() => budget.abort(), _classPrivateFieldGet2(_waitBudgetMs, this));
1002
+ const watch = setInterval(() => {
1003
+ if (!_classPrivateFieldGet2(_options, this).container.running) _assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
1004
+ }, CONTAINER_CHECK_MS);
1005
+ try {
1006
+ while (!budget.signal.aborted && _classPrivateFieldGet2(_attached, this).get(session)?.link.open === true && store.operations({
1007
+ session,
1008
+ status: OPEN
1009
+ }).length > 0) await _assertClassBrand(_ContainerHarness_brand, this, _until).call(this, _classPrivateFieldGet2(_changeWaiters, this), session, budget.signal);
1010
+ } catch {} finally {
1011
+ clearTimeout(timer);
1012
+ clearInterval(watch);
1013
+ }
1014
+ }
1015
+ /** The platform's inactivity timeout: past the harness's own idle stop. */
1016
+ function _inactivityTimeoutMs() {
1017
+ return _classPrivateFieldGet2(_idleTimeoutMs, this) + IDLE_GRACE_MS;
1018
+ }
1019
+ /** The idle stop: destroy the container once nothing has used it for `idleTimeoutMs`. */
1020
+ async function _idleStep() {
1021
+ if ((await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this)).operations({ status: OPEN }).length > 0) return void 0;
1022
+ const container = _classPrivateFieldGet2(_options, this).container;
1023
+ if (!container.running) return void 0;
1024
+ await this.dispose();
1025
+ await _assertClassBrand(_ContainerHarness_brand, this, _saveWorkspace).call(this, container);
1026
+ await container.destroy();
1027
+ _assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
1028
+ type: "container",
1029
+ status: "stopped"
1030
+ });
1031
+ }
1032
+ async function _scheduleIdle() {
1033
+ await this.lifecycle.jobs.push({
1034
+ id: IDLE_JOB,
1035
+ fn: IDLE_FN,
1036
+ time: Date.now() + _classPrivateFieldGet2(_idleStopMs, this)
1037
+ });
1038
+ }
1039
+ async function _runtimeRecord() {
1040
+ if (_classPrivateFieldGet2(_runtime, this)) return _classPrivateFieldGet2(_runtime, this);
1041
+ await this.lifecycle.ready();
1042
+ const stored = await this.lifecycle.storage.get(RUNTIME_KEY);
1043
+ _classPrivateFieldSet2(_runtime, this, stored);
1044
+ return stored;
1045
+ }
1046
+ /** The running container, started if it is not. Single-flight. */
1047
+ function _ensureRuntime() {
1048
+ _classPrivateFieldGet2(_starting, this) ?? _classPrivateFieldSet2(_starting, this, _assertClassBrand(_ContainerHarness_brand, this, _doEnsureRuntime).call(this).finally(() => {
1049
+ _classPrivateFieldSet2(_starting, this, void 0);
1050
+ }));
1051
+ return _classPrivateFieldGet2(_starting, this);
1052
+ }
1053
+ async function _doEnsureRuntime() {
1054
+ const container = _classPrivateFieldGet2(_options, this).container;
1055
+ const known = await _assertClassBrand(_ContainerHarness_brand, this, _runtimeRecord).call(this);
1056
+ if (container.running && known) {
1057
+ if (!_classPrivateFieldGet2(_timeoutSet, this)) {
1058
+ await container.setInactivityTimeout(_assertClassBrand(_ContainerHarness_brand, this, _inactivityTimeoutMs).call(this));
1059
+ _classPrivateFieldSet2(_timeoutSet, this, true);
1060
+ }
1061
+ if ((await waitHealthy(_assertClassBrand(_ContainerHarness_brand, this, _port).call(this), Math.min(_classPrivateFieldGet2(_startTimeoutMs, this), ADOPT_TIMEOUT_MS), () => container.running))._tag === "ok") return {
1062
+ _tag: "ok",
1063
+ value: known
1064
+ };
1065
+ await container.destroy().catch(() => void 0);
1066
+ } else if (container.running) await container.destroy().catch(() => void 0);
1067
+ return _assertClassBrand(_ContainerHarness_brand, this, _launch).call(this, container);
1068
+ }
1069
+ /**
1070
+ * The daemon's port on the container that runs now. Taken fresh each
1071
+ * time: a port taken before `start()` stays bound to no instance.
1072
+ */
1073
+ function _port() {
1074
+ return _classPrivateFieldGet2(_options, this).container.getTcpPort(_classPrivateFieldGet2(_options, this).port ?? DAEMON_PORT);
1075
+ }
1076
+ /**
1077
+ * Start the agent's container and route its egress. A managed image is
1078
+ * restored from this setup's snapshot when there is one, and set up (and
1079
+ * snapshotted) when there is not.
1080
+ */
1081
+ async function _start(container, runtime) {
1082
+ const agent = _classPrivateFieldGet2(_options, this).agent;
1083
+ const image = agent.image;
1084
+ const env = {
1085
+ ...agent.env,
1086
+ CF_HARNESS_ADAPTER: agent.adapter,
1087
+ [CONTAINER_ENV.token]: runtime.token,
1088
+ [CONTAINER_ENV.runtimeId]: runtime.runtimeId,
1089
+ [CONTAINER_ENV.port]: String(_classPrivateFieldGet2(_options, this).port ?? DAEMON_PORT)
1090
+ };
1091
+ const base = {
1092
+ enableInternet: _classPrivateFieldGet2(_options, this).enableInternet ?? true,
1093
+ instance: _classPrivateFieldGet2(_options, this).instance ?? "standard-1"
1094
+ };
1095
+ let from = "image";
1096
+ let key;
1097
+ let options;
1098
+ const baseKey = await _assertClassBrand(_ContainerHarness_brand, this, _baseKey).call(this);
1099
+ const workspace = await _assertClassBrand(_ContainerHarness_brand, this, _usableSnapshot).call(this, WORKSPACE_KEY, baseKey);
1100
+ if (image.kind === "custom") if (workspace) {
1101
+ from = "workspace";
1102
+ options = {
1103
+ ...base,
1104
+ env,
1105
+ containerSnapshot: workspace.snapshot
1106
+ };
1107
+ } else options = {
1108
+ ...base,
1109
+ env,
1110
+ image: image.image
1111
+ };
1112
+ else {
1113
+ key = baseKey;
1114
+ const stored = await _assertClassBrand(_ContainerHarness_brand, this, _usableSnapshot).call(this, SNAPSHOT_KEY, key);
1115
+ const managed = {
1116
+ ...base,
1117
+ env: {
1118
+ ...env,
1119
+ CF_HARNESS_USER: AGENT_USER
1120
+ },
1121
+ entrypoint: [...MANAGED_ENTRYPOINT]
1122
+ };
1123
+ if (workspace) {
1124
+ from = "workspace";
1125
+ options = {
1126
+ ...managed,
1127
+ containerSnapshot: workspace.snapshot
1128
+ };
1129
+ } else if (stored) {
1130
+ from = "snapshot";
1131
+ options = {
1132
+ ...managed,
1133
+ containerSnapshot: stored.snapshot
1134
+ };
1135
+ } else {
1136
+ from = "setup";
1137
+ options = {
1138
+ ...managed,
1139
+ image: MANAGED_IMAGE
1140
+ };
1141
+ }
1142
+ }
1143
+ try {
1144
+ container.start(options);
1145
+ } catch (error) {
1146
+ await _assertClassBrand(_ContainerHarness_brand, this, _snapshotFailed).call(this, from);
1147
+ return {
1148
+ _tag: "err",
1149
+ error: {
1150
+ _tag: "unreachable",
1151
+ message: errorText(error)
1152
+ }
1153
+ };
1154
+ }
1155
+ try {
1156
+ await container.setInactivityTimeout(_assertClassBrand(_ContainerHarness_brand, this, _inactivityTimeoutMs).call(this));
1157
+ _classPrivateFieldSet2(_timeoutSet, this, true);
1158
+ const egress = _classPrivateFieldGet2(_options, this).egress;
1159
+ if (egress && agent.egress.length > 0) {
1160
+ const binding = egress({ props: { routes: agent.egress } });
1161
+ for (const route of agent.egress) await container.interceptOutboundHttp(route.host, binding);
1162
+ }
1163
+ } catch (error) {
1164
+ return {
1165
+ _tag: "err",
1166
+ error: {
1167
+ _tag: "unreachable",
1168
+ message: errorText(error)
1169
+ }
1170
+ };
1171
+ }
1172
+ if (from === "setup" && image.kind === "managed" && key !== void 0) {
1173
+ const setup = await setUp(container, image.setup, key);
1174
+ if (setup._tag === "err") return {
1175
+ _tag: "err",
1176
+ error: {
1177
+ _tag: "unreachable",
1178
+ message: setup.error.message
1179
+ }
1180
+ };
1181
+ if (setup.snapshot) {
1182
+ const record = {
1183
+ ...setup.snapshot,
1184
+ usedAt: Date.now()
1185
+ };
1186
+ await this.lifecycle.storage.put(SNAPSHOT_KEY, record);
1187
+ }
1188
+ }
1189
+ return {
1190
+ _tag: "ok",
1191
+ value: from
1192
+ };
1193
+ }
1194
+ /**
1195
+ * The stored snapshot for `key`, unless it is for another setup, too old
1196
+ * to restore (deleted), or failing (skipped for now, kept).
1197
+ */
1198
+ async function _usableSnapshot(storageKey, key) {
1199
+ const record = await this.lifecycle.storage.get(storageKey);
1200
+ if (!record || record.key !== key) return void 0;
1201
+ if (Date.now() - (record.usedAt ?? Date.now()) > SNAPSHOT_STALE_MS) {
1202
+ await this.lifecycle.storage.delete(storageKey);
1203
+ return;
1204
+ }
1205
+ if (record.probe) return record;
1206
+ if ((record.failures?.count ?? 0) >= SKIP_AFTER_FAILURES) return void 0;
1207
+ return record;
1208
+ }
1209
+ /** Index of a start source in `SNAPSHOT_SOURCES`; the image or setup is last. */
1210
+ function _rank(from) {
1211
+ const index = SNAPSHOT_SOURCES.findIndex((source) => source.from === from);
1212
+ return index === -1 ? SNAPSHOT_SOURCES.length : index;
1213
+ }
1214
+ /**
1215
+ * A start failed. Count it against the snapshot it came from. A
1216
+ * preferred snapshot being skipped is not to blame when its fallback
1217
+ * fails too (no capacity, say): its count is cleared, so it is tried
1218
+ * again.
1219
+ */
1220
+ async function _snapshotFailed(from) {
1221
+ const rank = _assertClassBrand(_ContainerHarness_brand, this, _rank).call(this, from);
1222
+ for (const [index, source] of SNAPSHOT_SOURCES.entries()) {
1223
+ const record = await this.lifecycle.storage.get(source.storageKey);
1224
+ if (!record) continue;
1225
+ if (index === rank && record.probe) {
1226
+ console.warn(`[container-harness] dropping the ${source.from} snapshot: it no longer starts`);
1227
+ await this.lifecycle.storage.delete(source.storageKey);
1228
+ } else if (index === rank) {
1229
+ const now = Date.now();
1230
+ const failed = {
1231
+ ...record,
1232
+ failures: {
1233
+ count: (record.failures?.count ?? 0) + 1,
1234
+ since: record.failures?.since ?? now
1235
+ }
1236
+ };
1237
+ await this.lifecycle.storage.put(source.storageKey, failed);
1238
+ } else if (index < rank && record.failures) await _assertClassBrand(_ContainerHarness_brand, this, _clearFailures).call(this, source.storageKey, record);
1239
+ }
1240
+ }
1241
+ /**
1242
+ * A start came up. Its snapshot's lifetime restarts and its failures
1243
+ * clear. A preferred snapshot that was being skipped is not proven
1244
+ * broken by this (the failures may have been an outage that has just
1245
+ * ended): it is marked to be tried once more, and the caller stops this
1246
+ * container to do so. Returns whether it should.
1247
+ */
1248
+ async function _snapshotWorked(from) {
1249
+ const rank = _assertClassBrand(_ContainerHarness_brand, this, _rank).call(this, from);
1250
+ let retry = false;
1251
+ for (const [index, source] of SNAPSHOT_SOURCES.entries()) {
1252
+ const record = await this.lifecycle.storage.get(source.storageKey);
1253
+ if (!record) continue;
1254
+ if (index === rank) {
1255
+ const worked = {
1256
+ key: record.key,
1257
+ snapshot: record.snapshot,
1258
+ usedAt: Date.now()
1259
+ };
1260
+ await this.lifecycle.storage.put(source.storageKey, worked);
1261
+ } else if (index < rank && !record.probe && (record.failures?.count ?? 0) >= SKIP_AFTER_FAILURES) {
1262
+ const probe = {
1263
+ key: record.key,
1264
+ snapshot: record.snapshot,
1265
+ ...record.usedAt === void 0 ? {} : { usedAt: record.usedAt },
1266
+ probe: true
1267
+ };
1268
+ await this.lifecycle.storage.put(source.storageKey, probe);
1269
+ retry = true;
1270
+ }
1271
+ }
1272
+ return retry;
1273
+ }
1274
+ /** Forget every snapshot's failures: nothing could be concluded from them. */
1275
+ async function _clearAllFailures() {
1276
+ for (const source of SNAPSHOT_SOURCES) {
1277
+ const record = await this.lifecycle.storage.get(source.storageKey);
1278
+ if (record?.failures || record?.probe) await _assertClassBrand(_ContainerHarness_brand, this, _clearFailures).call(this, source.storageKey, record);
1279
+ }
1280
+ }
1281
+ async function _clearFailures(storageKey, record) {
1282
+ const cleared = {
1283
+ key: record.key,
1284
+ snapshot: record.snapshot,
1285
+ ...record.usedAt === void 0 ? {} : { usedAt: record.usedAt }
1286
+ };
1287
+ await this.lifecycle.storage.put(storageKey, cleared);
1288
+ }
1289
+ /** What a container's filesystem is built from: the setup, or the image. */
1290
+ async function _baseKey() {
1291
+ const image = _classPrivateFieldGet2(_options, this).agent.image;
1292
+ return image.kind === "managed" ? setupKey(image.setup) : `image:${image.image}`;
1293
+ }
1294
+ /**
1295
+ * Snapshot the container's filesystem before the harness stops it, so the
1296
+ * next container starts with the workspace as this one left it. A failed
1297
+ * snapshot costs the workspace edits, never the stop.
1298
+ */
1299
+ async function _saveWorkspace(container) {
1300
+ try {
1301
+ const snapshot = await container.snapshotContainer({ name: `workspace-${Date.now()}` });
1302
+ const saved = {
1303
+ key: await _assertClassBrand(_ContainerHarness_brand, this, _baseKey).call(this),
1304
+ snapshot: { id: snapshot.id },
1305
+ usedAt: Date.now()
1306
+ };
1307
+ await this.lifecycle.storage.put(WORKSPACE_KEY, saved);
1308
+ } catch (error) {
1309
+ await this.lifecycle.storage.delete(WORKSPACE_KEY);
1310
+ console.warn("[container-harness] workspace snapshot failed; the next container starts without this workspace", { error: errorText(error) });
1311
+ }
1312
+ }
1313
+ async function _launch(container) {
1314
+ const runtime = {
1315
+ runtimeId: crypto.randomUUID(),
1316
+ token: `${crypto.randomUUID()}${crypto.randomUUID()}`.replaceAll("-", "")
1317
+ };
1318
+ await this.lifecycle.storage.put(RUNTIME_KEY, runtime);
1319
+ _classPrivateFieldSet2(_runtime, this, runtime);
1320
+ _assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
1321
+ type: "container",
1322
+ status: "starting"
1323
+ });
1324
+ const started = await _assertClassBrand(_ContainerHarness_brand, this, _start).call(this, container, runtime);
1325
+ if (started._tag === "err") {
1326
+ await container.destroy().catch(() => void 0);
1327
+ return started;
1328
+ }
1329
+ const healthy = await waitHealthy(_assertClassBrand(_ContainerHarness_brand, this, _port).call(this), _classPrivateFieldGet2(_startTimeoutMs, this), () => container.running);
1330
+ if (healthy._tag === "err") {
1331
+ await _assertClassBrand(_ContainerHarness_brand, this, _snapshotFailed).call(this, started.value);
1332
+ await container.destroy().catch(() => void 0);
1333
+ return healthy;
1334
+ }
1335
+ if (await _assertClassBrand(_ContainerHarness_brand, this, _snapshotWorked).call(this, started.value)) {
1336
+ await container.destroy().catch(() => void 0);
1337
+ return {
1338
+ _tag: "err",
1339
+ error: {
1340
+ _tag: "retry",
1341
+ message: "retrying a snapshot skipped after failed starts"
1342
+ }
1343
+ };
1344
+ }
1345
+ _assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
1346
+ type: "container",
1347
+ status: "ready"
1348
+ });
1349
+ return {
1350
+ _tag: "ok",
1351
+ value: runtime
1352
+ };
1353
+ }
1354
+ /** The session's socket, opened and reconciled if it is not. Single-flight. */
1355
+ function _attach(session) {
1356
+ const attached = _classPrivateFieldGet2(_attached, this).get(session);
1357
+ if (attached?.link.open) return Promise.resolve({
1358
+ _tag: "ok",
1359
+ value: attached
1360
+ });
1361
+ let attaching = _classPrivateFieldGet2(_attaching, this).get(session);
1362
+ if (!attaching) {
1363
+ attaching = _assertClassBrand(_ContainerHarness_brand, this, _doAttach).call(this, session).finally(() => {
1364
+ _classPrivateFieldGet2(_attaching, this).delete(session);
1365
+ });
1366
+ _classPrivateFieldGet2(_attaching, this).set(session, attaching);
1367
+ }
1368
+ return attaching;
1369
+ }
1370
+ async function _doAttach(session) {
1371
+ const runtime = await _assertClassBrand(_ContainerHarness_brand, this, _ensureRuntime).call(this);
1372
+ if (runtime._tag === "err") return runtime;
1373
+ const port = _assertClassBrand(_ContainerHarness_brand, this, _port).call(this);
1374
+ let ready;
1375
+ const early = [];
1376
+ let caughtUpEarly = false;
1377
+ const opened = await DaemonLink.open(port, session, runtime.value.token, {
1378
+ frame: (seq, frame) => {
1379
+ if (ready) _assertClassBrand(_ContainerHarness_brand, this, _onFrame).call(this, session, ready, seq, frame);
1380
+ else early.push({
1381
+ seq,
1382
+ frame
1383
+ });
1384
+ },
1385
+ caughtUp: () => {
1386
+ if (ready) _assertClassBrand(_ContainerHarness_brand, this, _onCaughtUp).call(this, session, ready);
1387
+ else caughtUpEarly = true;
1388
+ },
1389
+ closed: () => _assertClassBrand(_ContainerHarness_brand, this, _onClosed).call(this, session, ready)
1390
+ });
1391
+ if (opened._tag === "err") return opened;
1392
+ const { link, hello } = opened.value;
1393
+ const attached = {
1394
+ link,
1395
+ runtimeId: hello.runtimeId,
1396
+ sinceAck: 0,
1397
+ settledThere: []
1398
+ };
1399
+ if (hello.runtimeId !== runtime.value.runtimeId) {
1400
+ const adopted = {
1401
+ ...runtime.value,
1402
+ runtimeId: hello.runtimeId
1403
+ };
1404
+ await this.lifecycle.storage.put(RUNTIME_KEY, adopted);
1405
+ _classPrivateFieldSet2(_runtime, this, adopted);
1406
+ }
1407
+ const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
1408
+ _assertClassBrand(_ContainerHarness_brand, this, _reconcile).call(this, store, session, attached, hello);
1409
+ _classPrivateFieldGet2(_attached, this).get(session)?.link.close();
1410
+ _classPrivateFieldGet2(_attached, this).set(session, attached);
1411
+ ready = attached;
1412
+ for (const each of early) _assertClassBrand(_ContainerHarness_brand, this, _onFrame).call(this, session, attached, each.seq, each.frame);
1413
+ if (caughtUpEarly) _assertClassBrand(_ContainerHarness_brand, this, _onCaughtUp).call(this, session, attached);
1414
+ return {
1415
+ _tag: "ok",
1416
+ value: attached
1417
+ };
1418
+ }
1419
+ /**
1420
+ * The replay is complete. Operations the container reported settled in
1421
+ * `hello` whose frames it no longer kept are settled from the report now,
1422
+ * after every frame it did keep, so their messages land first.
1423
+ */
1424
+ function _onCaughtUp(session, attached) {
1425
+ const store = _classPrivateFieldGet2(_store, this);
1426
+ if (!store) return;
1427
+ for (const { operationId, outcome } of attached.settledThere.splice(0)) _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operationId, outcome);
1428
+ }
1429
+ /**
1430
+ * Make the container's view of a session and the object's agree, before
1431
+ * anything is delivered: open the adapter session if the container does
1432
+ * not hold it, settle what the container already settled, decide what to
1433
+ * do with work a lost container took with it, abort what the container
1434
+ * still runs but the object has given up on, and replay missed frames.
1435
+ */
1436
+ function _reconcile(store, session, attached, hello) {
1437
+ const { link } = attached;
1438
+ let state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
1439
+ const sameRuntime = state.cursor?.runtimeId === hello.runtimeId;
1440
+ let lostAny = false;
1441
+ if (hello.open !== state.generation) {
1442
+ const groups = chunkEntries(store.read(session, ENGINE).map((entry) => entry.data));
1443
+ const last = groups.pop() ?? [];
1444
+ for (const entries of groups) link.send({
1445
+ type: "restore",
1446
+ generation: state.generation,
1447
+ entries
1448
+ });
1449
+ link.send({
1450
+ type: "open",
1451
+ generation: state.generation,
1452
+ settings: _assertClassBrand(_ContainerHarness_brand, this, _settings).call(this, state),
1453
+ restore: last
1454
+ });
1455
+ }
1456
+ const known = new Map(hello.operations.map((operation) => [operation.operationId, operation]));
1457
+ for (const operation of store.operations({
1458
+ session,
1459
+ status: OPEN
1460
+ })) {
1461
+ const there = known.get(operation.id);
1462
+ if (there?.status === "settled") {
1463
+ attached.settledThere.push({
1464
+ operationId: operation.id,
1465
+ outcome: there.outcome
1466
+ });
1467
+ continue;
1468
+ }
1469
+ if (there) continue;
1470
+ const meta = parseMeta(operation.meta);
1471
+ if (!(operation.status === "running" && meta.delivered !== hello.runtimeId)) {
1472
+ if (meta.delivered !== null) store.setOperationMeta(session, operation.id, metaJson({
1473
+ ...meta,
1474
+ delivered: null
1475
+ }));
1476
+ continue;
1477
+ }
1478
+ lostAny = true;
1479
+ if (_classPrivateFieldGet2(_options, this).onContainerLost === "retry") store.transaction(() => {
1480
+ store.requeue(session, operation.id);
1481
+ store.setOperationMeta(session, operation.id, metaJson({
1482
+ ...meta,
1483
+ delivered: null
1484
+ }));
1485
+ });
1486
+ else _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operation.id, {
1487
+ status: "unanswered",
1488
+ reason: "container_lost"
1489
+ });
1490
+ }
1491
+ for (const there of hello.operations) {
1492
+ if (there.status === "settled") continue;
1493
+ const operation = store.operation(session, there.operationId);
1494
+ if (!operation || operation.status === "done" || operation.status === "unanswered") link.send({
1495
+ type: "abort",
1496
+ operationId: there.operationId
1497
+ });
1498
+ }
1499
+ if (lostAny) _assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, [{
1500
+ type: "container",
1501
+ status: "lost"
1502
+ }]);
1503
+ if (!sameRuntime) {
1504
+ state = {
1505
+ ...state,
1506
+ cursor: {
1507
+ runtimeId: hello.runtimeId,
1508
+ seq: 0
1509
+ }
1510
+ };
1511
+ store.setSessionState(session, sessionStateJson(state));
1512
+ }
1513
+ link.send({
1514
+ type: "replay",
1515
+ after: state.cursor?.seq ?? 0
1516
+ });
1517
+ }
1518
+ /** Hand every undelivered open operation of the session to the container. */
1519
+ function _deliver(store, session) {
1520
+ const attached = _classPrivateFieldGet2(_attached, this).get(session);
1521
+ if (!attached?.link.open) return;
1522
+ for (const operation of store.operations({
1523
+ session,
1524
+ status: OPEN
1525
+ })) {
1526
+ const meta = parseMeta(operation.meta);
1527
+ if (meta.delivered === attached.runtimeId) continue;
1528
+ let input = parseInput(operation.input);
1529
+ const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
1530
+ if (state.handoff !== void 0) input = withHandoff(input, state.handoff);
1531
+ if (!attached.link.send({
1532
+ type: "prompt",
1533
+ operationId: operation.id,
1534
+ input,
1535
+ whenBusy: meta.whenBusy
1536
+ })) return;
1537
+ store.transaction(() => {
1538
+ store.setOperationMeta(session, operation.id, metaJson({
1539
+ ...meta,
1540
+ delivered: attached.runtimeId
1541
+ }));
1542
+ if (state.handoff !== void 0) store.setSessionState(session, sessionStateJson({
1543
+ ...state,
1544
+ handoff: void 0
1545
+ }));
1546
+ });
1547
+ }
1548
+ }
1549
+ /** Apply one frame from the container. */
1550
+ function _onFrame(session, attached, seq, frame) {
1551
+ const store = _classPrivateFieldGet2(_store, this);
1552
+ if (!store) return;
1553
+ const events = [];
1554
+ let settled;
1555
+ store.transaction(() => {
1556
+ const record = store.session(session);
1557
+ if (!record) return;
1558
+ let state = parseSessionState(record.state);
1559
+ if (state.cursor?.runtimeId === attached.runtimeId && seq <= state.cursor.seq) return;
1560
+ switch (frame.kind) {
1561
+ case "start":
1562
+ if (store.start(session, frame.operationId)) {
1563
+ const operation = store.operation(session, frame.operationId);
1564
+ if (operation) {
1565
+ const message = {
1566
+ id: `${operation.id}:user`,
1567
+ role: "user",
1568
+ parts: userParts(parseInput(operation.input)),
1569
+ operationId: operation.id,
1570
+ createdAt: operation.createdAt
1571
+ };
1572
+ store.append(session, MESSAGES, [{
1573
+ id: message.id,
1574
+ data: messageJson(message)
1575
+ }]);
1576
+ events.push({
1577
+ type: "message",
1578
+ message,
1579
+ operationId: operation.id
1580
+ });
1581
+ }
1582
+ events.push({
1583
+ type: "operation-start",
1584
+ operationId: frame.operationId
1585
+ });
1586
+ }
1587
+ break;
1588
+ case "event":
1589
+ events.push({
1590
+ ...frame.event,
1591
+ operationId: frame.operationId
1592
+ });
1593
+ if (frame.event.type === "message") store.append(session, MESSAGES, [{
1594
+ id: frame.event.message.id,
1595
+ data: messageJson(frame.event.message)
1596
+ }]);
1597
+ break;
1598
+ case "persist":
1599
+ store.append(session, ENGINE, frame.entries.map((data) => ({ data })));
1600
+ break;
1601
+ case "settle":
1602
+ settled = _assertClassBrand(_ContainerHarness_brand, this, _settleInStore).call(this, store, session, frame.operationId, frame.outcome);
1603
+ break;
1604
+ }
1605
+ state = {
1606
+ ...state,
1607
+ cursor: {
1608
+ runtimeId: attached.runtimeId,
1609
+ seq
1610
+ }
1611
+ };
1612
+ store.setSessionState(session, sessionStateJson(state));
1613
+ });
1614
+ if (settled) events.push({
1615
+ type: "operation-end",
1616
+ result: settled
1617
+ });
1618
+ _assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, events);
1619
+ attached.sinceAck += 1;
1620
+ if (settled || attached.sinceAck >= ACK_EVERY) {
1621
+ attached.sinceAck = 0;
1622
+ attached.link.send({
1623
+ type: "ack",
1624
+ seq
1625
+ });
1626
+ }
1627
+ if (settled) _assertClassBrand(_ContainerHarness_brand, this, _afterSettle).call(this, store, session, settled);
1628
+ }
1629
+ function _onClosed(session, attached) {
1630
+ if (attached && _classPrivateFieldGet2(_attached, this).get(session) === attached) _classPrivateFieldGet2(_attached, this).delete(session);
1631
+ _assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_changeWaiters, this), session);
1632
+ }
1633
+ function _detach(session) {
1634
+ const attached = _classPrivateFieldGet2(_attached, this).get(session);
1635
+ _classPrivateFieldGet2(_attached, this).delete(session);
1636
+ attached?.link.close();
1637
+ _assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_changeWaiters, this), session);
1638
+ }
1639
+ function _settleInStore(store, session, operationId, outcome) {
1640
+ if (!store.settle(session, operationId, outcome.status === "done" ? {
1641
+ status: "done",
1642
+ result: { text: outcome.text }
1643
+ } : outcome)) return void 0;
1644
+ const operation = store.operation(session, operationId);
1645
+ return operation ? resultOf(operation) : void 0;
1646
+ }
1647
+ /** Settle outside a frame, and tell everyone. */
1648
+ function _settle(store, session, operationId, outcome) {
1649
+ const result = _assertClassBrand(_ContainerHarness_brand, this, _settleInStore).call(this, store, session, operationId, outcome);
1650
+ if (!result) return;
1651
+ _assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, [{
1652
+ type: "operation-end",
1653
+ result
1654
+ }]);
1655
+ _assertClassBrand(_ContainerHarness_brand, this, _afterSettle).call(this, store, session, result);
1656
+ }
1657
+ function _afterSettle(store, session, result) {
1658
+ _assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_settleWaiters, this), `${session}\0${result.operationId}`);
1659
+ _assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_changeWaiters, this), session);
1660
+ if (store.operations({ status: OPEN }).length === 0) _assertClassBrand(_ContainerHarness_brand, this, _scheduleIdle).call(this).catch(() => void 0);
1661
+ }
1662
+ async function _openStore() {
1663
+ if (_classPrivateFieldGet2(_store, this)) return _classPrivateFieldGet2(_store, this);
1664
+ await this.lifecycle.ready();
1665
+ if (_classPrivateFieldGet2(_store, this)) return _classPrivateFieldGet2(_store, this);
1666
+ const store = openHarnessStore(this.lifecycle.storage, { prefix: _classPrivateFieldGet2(_options, this).storePrefix ?? "container_" });
1667
+ store.createSession({
1668
+ id: ROOT_SESSION,
1669
+ state: sessionStateJson(_assertClassBrand(_ContainerHarness_brand, this, _initial).call(this))
1670
+ });
1671
+ _classPrivateFieldSet2(_store, this, store);
1672
+ return store;
1673
+ }
1674
+ function _initial() {
1675
+ return {
1676
+ model: _classPrivateFieldGet2(_options, this).defaults?.model ?? _classPrivateFieldGet2(_options, this).agent.model,
1677
+ cursor: null,
1678
+ generation: 1,
1679
+ messagesFrom: 0,
1680
+ handoff: void 0
1681
+ };
1682
+ }
1683
+ function _settings(state) {
1684
+ const options = _classPrivateFieldGet2(_options, this).defaults?.options ?? _classPrivateFieldGet2(_options, this).agent.options;
1685
+ return {
1686
+ ...state.model === void 0 ? {} : { model: state.model },
1687
+ ...options === void 0 ? {} : { options }
1688
+ };
1689
+ }
1690
+ function _requireSession(store, session) {
1691
+ const record = store.session(session);
1692
+ if (!record) throw new Error(`Unknown container session ${session}`);
1693
+ return record;
1694
+ }
1695
+ function _emit(session, events) {
1696
+ if (events.length === 0) return;
1697
+ const listeners = _classPrivateFieldGet2(_listeners, this).get(session);
1698
+ if (!listeners) return;
1699
+ for (const listener of listeners) try {
1700
+ listener(events);
1701
+ } catch {}
1702
+ }
1703
+ function _broadcast(event) {
1704
+ for (const session of _classPrivateFieldGet2(_listeners, this).keys()) _assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, [event]);
1705
+ }
1706
+ function _notify(waiters, key) {
1707
+ const set = waiters.get(key);
1708
+ waiters.delete(key);
1709
+ if (set) for (const wake of set) wake();
1710
+ }
1711
+ /**
1712
+ * Resolve on the next notify for `key`, after `pollMs` if given, or
1713
+ * reject when `signal` aborts.
1714
+ *
1715
+ * The poll matters for waits in a request: the notify comes from the
1716
+ * alarm that drives the container, a different request context, and a
1717
+ * request whose only pending work is a promise another context resolves
1718
+ * is cancelled by the runtime as hung. A timer of its own keeps it alive,
1719
+ * and the caller re-reads the store when it fires.
1720
+ */
1721
+ function _until(waiters, key, signal, pollMs) {
1722
+ return new Promise((resolve, reject) => {
1723
+ let set = waiters.get(key);
1724
+ if (!set) {
1725
+ set = /* @__PURE__ */ new Set();
1726
+ waiters.set(key, set);
1727
+ }
1728
+ let timer;
1729
+ const done = () => {
1730
+ clearTimeout(timer);
1731
+ set?.delete(wake);
1732
+ signal?.removeEventListener("abort", onAbort);
1733
+ };
1734
+ const onAbort = () => {
1735
+ done();
1736
+ reject(signal?.reason);
1737
+ };
1738
+ const wake = () => {
1739
+ done();
1740
+ resolve();
1741
+ };
1742
+ if (signal?.aborted) {
1743
+ onAbort();
1744
+ return;
1745
+ }
1746
+ signal?.addEventListener("abort", onAbort, { once: true });
1747
+ set.add(wake);
1748
+ if (pollMs !== void 0) timer = setTimeout(wake, pollMs);
1749
+ });
1750
+ }
1751
+ function resultOf(operation) {
1752
+ const base = {
1753
+ operationId: operation.id,
1754
+ session: operation.session
1755
+ };
1756
+ if (operation.status === "done") {
1757
+ const result = operation.result;
1758
+ const text = isRecord(result) && typeof result.text === "string" ? result.text : "";
1759
+ return {
1760
+ ...base,
1761
+ status: "done",
1762
+ text
1763
+ };
1764
+ }
1765
+ if (operation.status === "unanswered") return {
1766
+ ...base,
1767
+ status: "unanswered",
1768
+ reason: operation.reason ?? "unanswered"
1769
+ };
1770
+ }
1771
+ var _harness = /* @__PURE__ */ new WeakMap();
1772
+ /**
1773
+ * One session, addressed through the harness.
1774
+ *
1775
+ * @experimental The API may change before it stabilizes.
1776
+ */
1777
+ var ContainerSession = class {
1778
+ /**
1779
+ * @param harness - The harness.
1780
+ * @param id - The session id.
1781
+ */
1782
+ constructor(harness, id) {
1783
+ _classPrivateFieldInitSpec(this, _harness, void 0);
1784
+ _classPrivateFieldSet2(_harness, this, harness);
1785
+ this.id = id;
1786
+ }
1787
+ /**
1788
+ * Durably submit a prompt. Resolves before the agent runs.
1789
+ *
1790
+ * @param input - The prompt.
1791
+ * @param options - Idempotency key and busy behaviour.
1792
+ * @returns The receipt.
1793
+ */
1794
+ submit(input, options = {}) {
1795
+ return _classPrivateFieldGet2(_harness, this).enqueue(this.id, input, options);
1796
+ }
1797
+ /**
1798
+ * Submit and wait for the answer and the updated transcript.
1799
+ *
1800
+ * @param input - The prompt.
1801
+ * @param options - Idempotency key and busy behaviour.
1802
+ * @returns The result, with the transcript after it.
1803
+ */
1804
+ async prompt(input, options = {}) {
1805
+ const receipt = await this.submit(input, options);
1806
+ return {
1807
+ ...await this.wait(receipt.operationId),
1808
+ messages: await this.messages()
1809
+ };
1810
+ }
1811
+ /**
1812
+ * Join the running turn, when the adapter can steer; otherwise queue.
1813
+ *
1814
+ * @param input - The prompt.
1815
+ * @param options - Idempotency key.
1816
+ * @returns The receipt.
1817
+ */
1818
+ steer(input, options = {}) {
1819
+ return this.submit(input, {
1820
+ ...options,
1821
+ whenBusy: "steer"
1822
+ });
1823
+ }
1824
+ /**
1825
+ * Wait for an operation to settle. Aborting `signal` stops only the wait.
1826
+ *
1827
+ * @param operationId - The operation.
1828
+ * @param signal - Stops the wait.
1829
+ * @returns How it ended.
1830
+ */
1831
+ wait(operationId, signal) {
1832
+ return _classPrivateFieldGet2(_harness, this).settled(this.id, operationId, signal);
1833
+ }
1834
+ /**
1835
+ * Withdraw or abort one operation, or, with no id, every open operation
1836
+ * of the session. Aborted operations settle `unanswered` with `aborted`.
1837
+ *
1838
+ * @param operationId - The operation. Default: all of them.
1839
+ * @returns Whether anything was open to abort (always true with no id).
1840
+ */
1841
+ abort(operationId) {
1842
+ return _classPrivateFieldGet2(_harness, this).withdraw(this.id, operationId);
1843
+ }
1844
+ /**
1845
+ * Start a new context: a new agent session without the old history,
1846
+ * optionally seeded with a handoff note prepended to the next prompt.
1847
+ * Throws while the session is busy.
1848
+ *
1849
+ * @param handoff - The note.
1850
+ */
1851
+ reset(handoff) {
1852
+ return _classPrivateFieldGet2(_harness, this).resetSession(this.id, handoff);
1853
+ }
1854
+ /**
1855
+ * Change this session's model, by the adapter's model name.
1856
+ *
1857
+ * @param model - The model.
1858
+ */
1859
+ setModel(model) {
1860
+ return _classPrivateFieldGet2(_harness, this).setModel(this.id, model);
1861
+ }
1862
+ /**
1863
+ * The active transcript, since the newest reset.
1864
+ *
1865
+ * @returns The messages, oldest first.
1866
+ */
1867
+ messages() {
1868
+ return _classPrivateFieldGet2(_harness, this).transcript(this.id);
1869
+ }
1870
+ /**
1871
+ * This session's events: a snapshot, then live batches.
1872
+ *
1873
+ * @returns The stream.
1874
+ */
1875
+ events() {
1876
+ return _classPrivateFieldGet2(_harness, this).events(this.id);
1877
+ }
1878
+ /**
1879
+ * Whether the session has open operations.
1880
+ *
1881
+ * @returns True while anything is queued or running.
1882
+ */
1883
+ busy() {
1884
+ return _classPrivateFieldGet2(_harness, this).busy(this.id);
1885
+ }
1886
+ };
1887
+ var _harness2 = /* @__PURE__ */ new WeakMap();
1888
+ /**
1889
+ * Every session in this object.
1890
+ *
1891
+ * @experimental The API may change before it stabilizes.
1892
+ */
1893
+ var ContainerSessions = class {
1894
+ /**
1895
+ * @param harness - The harness.
1896
+ */
1897
+ constructor(harness) {
1898
+ _classPrivateFieldInitSpec(this, _harness2, void 0);
1899
+ _classPrivateFieldSet2(_harness2, this, harness);
1900
+ }
1901
+ /**
1902
+ * A handle on one session.
1903
+ *
1904
+ * @param id - The session id.
1905
+ * @returns The handle.
1906
+ */
1907
+ get(id) {
1908
+ return _classPrivateFieldGet2(_harness2, this).session(id);
1909
+ }
1910
+ /**
1911
+ * A new top-level session, with the harness's defaults.
1912
+ *
1913
+ * @returns The new session.
1914
+ */
1915
+ async create() {
1916
+ const id = crypto.randomUUID();
1917
+ await _classPrivateFieldGet2(_harness2, this).createSession(id);
1918
+ return _classPrivateFieldGet2(_harness2, this).session(id);
1919
+ }
1920
+ /**
1921
+ * A new session that continues `from`'s history as a branch.
1922
+ *
1923
+ * @param from - The session to fork.
1924
+ * @returns The new session.
1925
+ */
1926
+ async fork(from) {
1927
+ const id = crypto.randomUUID();
1928
+ await _classPrivateFieldGet2(_harness2, this).createSession(id, from);
1929
+ return _classPrivateFieldGet2(_harness2, this).session(id);
1930
+ }
1931
+ /**
1932
+ * Every session, oldest first.
1933
+ *
1934
+ * @returns Each session's id, parent, and whether it is busy.
1935
+ */
1936
+ async list() {
1937
+ const store = await _classPrivateFieldGet2(_harness2, this).store();
1938
+ const busy = new Set(store.operations({ status: OPEN }).map((operation) => operation.session));
1939
+ return store.sessions().map((record) => ({
1940
+ id: record.id,
1941
+ ...record.parent === void 0 ? {} : { parent: record.parent },
1942
+ busy: busy.has(record.id)
1943
+ }));
1944
+ }
1945
+ };
1946
+ //#endregion
1947
+ //#region src/harness/container/agents.ts
1948
+ /** What the CLI sends instead of a key; egress replaces it. */
1949
+ const PLACEHOLDER_KEY = "harness-egress";
1950
+ const CREDENTIAL_HEADERS = [
1951
+ "authorization",
1952
+ "x-api-key",
1953
+ "cf-aig-authorization"
1954
+ ];
1955
+ function npmInstall(name, pkg) {
1956
+ return {
1957
+ name: `install ${name}`,
1958
+ command: [
1959
+ "npm",
1960
+ "install",
1961
+ "--global",
1962
+ "--no-fund",
1963
+ "--no-audit",
1964
+ pkg
1965
+ ]
1966
+ };
1967
+ }
1968
+ /** The egress route for one provider. */
1969
+ function route(host, connection, provider) {
1970
+ return {
1971
+ host,
1972
+ upstream: connection.baseUrl ?? provider.baseUrl,
1973
+ headers: {
1974
+ ...connection.apiKey === void 0 ? {} : provider.keyHeader(connection.apiKey),
1975
+ ...connection.headers
1976
+ },
1977
+ strip: CREDENTIAL_HEADERS
1978
+ };
1979
+ }
1980
+ /**
1981
+ * Claude Code, installed at runtime on `cloudflare/debian-trixie`.
1982
+ *
1983
+ * @param options - Its provider connection (`apiKey`, `baseUrl`,
1984
+ * `headers`) and settings.
1985
+ * @returns The agent, for `ContainerHarness`'s `agent`.
1986
+ *
1987
+ * @experimental The API may change before it stabilizes.
1988
+ */
1989
+ function claudeCode(options) {
1990
+ const host = "anthropic.harness.internal";
1991
+ return {
1992
+ adapter: "claude-code",
1993
+ image: {
1994
+ kind: "managed",
1995
+ setup: [npmInstall("Claude Code", `@anthropic-ai/claude-code@${options.version ?? "2.1.289"}`), ...options.setup ?? []]
1996
+ },
1997
+ env: {
1998
+ ANTHROPIC_BASE_URL: `http://${host}`,
1999
+ ANTHROPIC_API_KEY: PLACEHOLDER_KEY,
2000
+ DISABLE_AUTOUPDATER: "1"
2001
+ },
2002
+ egress: [route(host, options, {
2003
+ baseUrl: "https://api.anthropic.com",
2004
+ keyHeader: (key) => ({ "x-api-key": key })
2005
+ })],
2006
+ model: options.model ?? "claude-sonnet-4-5",
2007
+ options: {
2008
+ ...options.appendSystemPrompt === void 0 ? {} : { appendSystemPrompt: options.appendSystemPrompt },
2009
+ ...options.allowedTools === void 0 ? {} : { allowedTools: [...options.allowedTools] },
2010
+ ...options.disallowedTools === void 0 ? {} : { disallowedTools: [...options.disallowedTools] },
2011
+ ...options.maxTurns === void 0 ? {} : { maxTurns: options.maxTurns }
2012
+ }
2013
+ };
2014
+ }
2015
+ /**
2016
+ * Codex, installed at runtime on `cloudflare/debian-trixie`.
2017
+ *
2018
+ * @param options - Its provider connection (`apiKey`, `baseUrl`,
2019
+ * `headers`) and settings.
2020
+ * @returns The agent, for `ContainerHarness`'s `agent`.
2021
+ *
2022
+ * @experimental The API may change before it stabilizes.
2023
+ */
2024
+ function codex(options) {
2025
+ const host = "openai.harness.internal";
2026
+ return {
2027
+ adapter: "codex",
2028
+ image: {
2029
+ kind: "managed",
2030
+ setup: [npmInstall("Codex", `@openai/codex@${options.version ?? "0.157.0"}`), ...options.setup ?? []]
2031
+ },
2032
+ env: {
2033
+ OPENAI_BASE_URL: `http://${host}`,
2034
+ OPENAI_API_KEY: PLACEHOLDER_KEY
2035
+ },
2036
+ egress: [route(host, options, {
2037
+ baseUrl: "https://api.openai.com/v1",
2038
+ keyHeader: (key) => ({ authorization: `Bearer ${key}` })
2039
+ })],
2040
+ model: options.model ?? "gpt-5.1",
2041
+ options: options.config === void 0 ? {} : { config: [...options.config] }
2042
+ };
2043
+ }
2044
+ /**
2045
+ * An agent in an image you built, whose entrypoint serves the daemon
2046
+ * (`serveFromEnv` from `agents/harness/container/runtime`).
2047
+ *
2048
+ * @param options - The image, the adapter, and its environment.
2049
+ * @returns The agent, for `ContainerHarness`'s `agent`.
2050
+ *
2051
+ * @experimental The API may change before it stabilizes.
2052
+ */
2053
+ function containerAgent(options) {
2054
+ return {
2055
+ adapter: options.adapter,
2056
+ image: {
2057
+ kind: "custom",
2058
+ image: options.image
2059
+ },
2060
+ env: options.env ?? {},
2061
+ egress: options.egress ?? [],
2062
+ ...options.model === void 0 ? {} : { model: options.model },
2063
+ ...options.options === void 0 ? {} : { options: options.options }
2064
+ };
2065
+ }
2066
+ /**
2067
+ * The echo adapter on `cloudflare/debian-trixie`: no CLI, no model, no
2068
+ * credentials. For smoke tests of the whole path.
2069
+ *
2070
+ * @returns The agent, for `ContainerHarness`'s `agent`.
2071
+ *
2072
+ * @experimental The API may change before it stabilizes.
2073
+ */
2074
+ function echoAgent() {
2075
+ return {
2076
+ adapter: "echo",
2077
+ image: {
2078
+ kind: "managed",
2079
+ setup: []
2080
+ },
2081
+ env: {},
2082
+ egress: []
2083
+ };
2084
+ }
2085
+ //#endregion
2086
+ //#region src/harness/container/egress.ts
2087
+ /**
2088
+ * Credentials that never enter the container.
2089
+ *
2090
+ * The CLI in the container is pointed at a placeholder host over plain
2091
+ * HTTP (`http://anthropic.harness.internal`) with a placeholder key. The
2092
+ * harness intercepts that host with `ctx.container.interceptOutboundHttp()`
2093
+ * and hands each request to `ContainerEgress`, a `WorkerEntrypoint` running
2094
+ * in your Worker, which drops the placeholder credentials, adds the real
2095
+ * ones, and forwards the request to the real upstream (an AI Gateway, or
2096
+ * the provider). The container can run any command it likes and still has
2097
+ * nothing to steal.
2098
+ *
2099
+ * Plain HTTP inside the container means no certificate to trust: the
2100
+ * connection to the intercept never leaves Cloudflare, and the request to
2101
+ * the upstream is HTTPS.
2102
+ */
2103
+ /**
2104
+ * Forward one intercepted request to its route's upstream.
2105
+ *
2106
+ * @param request - The request the container made.
2107
+ * @param routes - The routes, by placeholder host.
2108
+ * @param fetcher - How to make the upstream request. Default `fetch`.
2109
+ * @returns The upstream's response, or 403 for a host without a route.
2110
+ */
2111
+ async function forwardEgress(request, routes, fetcher = fetch) {
2112
+ const url = new URL(request.url);
2113
+ const route = routes.find((each) => each.host === url.hostname);
2114
+ if (!route) return new Response(`No egress route for ${url.hostname}`, { status: 403 });
2115
+ const target = `${route.upstream.replace(/\/+$/, "")}${url.pathname}${url.search}`;
2116
+ const headers = new Headers(request.headers);
2117
+ headers.delete("host");
2118
+ for (const name of route.strip) headers.delete(name);
2119
+ for (const [name, value] of Object.entries(route.headers)) headers.set(name, value);
2120
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
2121
+ return fetcher(target, {
2122
+ method: request.method,
2123
+ headers,
2124
+ ...hasBody ? { body: request.body } : {},
2125
+ redirect: "manual"
2126
+ });
2127
+ }
2128
+ /**
2129
+ * The egress for a `ContainerHarness`'s credentials. Export it from your
2130
+ * Worker's main module so `ctx.exports.ContainerEgress` exists:
2131
+ *
2132
+ * ```ts
2133
+ * export { ContainerEgress } from "agents/harness/container";
2134
+ * ```
2135
+ *
2136
+ * @experimental The API may change before it stabilizes.
2137
+ */
2138
+ var ContainerEgress = class extends WorkerEntrypoint {
2139
+ /**
2140
+ * Forward one request the container made.
2141
+ *
2142
+ * @param request - The intercepted request.
2143
+ * @returns The upstream's response.
2144
+ */
2145
+ fetch(request) {
2146
+ return forwardEgress(request, this.ctx.props.routes);
2147
+ }
2148
+ };
2149
+ //#endregion
2150
+ export { ContainerEgress, ContainerHarness, ContainerSession, ContainerSessions, ROOT_SESSION, claudeCode, codex, containerAgent, echoAgent };
2151
+
2152
+ //# sourceMappingURL=index.js.map