@dynamicagents/plugins 0.12.0 → 0.14.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 (132) hide show
  1. package/README.md +58 -77
  2. package/dist/browser/index.d.ts +0 -2
  3. package/dist/browser/index.js +21 -27
  4. package/dist/claude-code/cards.d.ts +55 -0
  5. package/dist/claude-code/cards.js +283 -0
  6. package/dist/claude-code/config.d.ts +28 -63
  7. package/dist/claude-code/config.js +0 -11
  8. package/dist/claude-code/egress.d.ts +8 -1
  9. package/dist/claude-code/egress.js +42 -4
  10. package/dist/claude-code/events.d.ts +106 -9
  11. package/dist/claude-code/events.js +201 -56
  12. package/dist/claude-code/github.d.ts +39 -0
  13. package/dist/claude-code/github.js +82 -0
  14. package/dist/claude-code/index.d.ts +16 -90
  15. package/dist/claude-code/index.js +14 -276
  16. package/dist/claude-code/model.d.ts +152 -0
  17. package/dist/claude-code/model.js +366 -0
  18. package/dist/claude-code/run.d.ts +230 -116
  19. package/dist/claude-code/run.js +380 -167
  20. package/dist/claude-code/session.d.ts +131 -0
  21. package/dist/claude-code/session.js +288 -0
  22. package/dist/claude-code/spec.d.ts +46 -0
  23. package/dist/claude-code/spec.js +83 -0
  24. package/dist/claude-code/verified.d.ts +15 -0
  25. package/dist/claude-code/verified.js +15 -0
  26. package/dist/computer/context.d.ts +1 -5
  27. package/dist/computer/context.js +7 -10
  28. package/dist/computer/file-lock.js +5 -4
  29. package/dist/computer/gate.d.ts +4 -4
  30. package/dist/computer/gate.js +5 -5
  31. package/dist/computer/index.d.ts +40 -258
  32. package/dist/computer/index.js +71 -324
  33. package/dist/computer/paths.d.ts +36 -16
  34. package/dist/computer/paths.js +42 -29
  35. package/dist/computer/proxy.d.ts +45 -0
  36. package/dist/computer/proxy.js +234 -0
  37. package/dist/computer/render.d.ts +1 -40
  38. package/dist/computer/render.js +2 -68
  39. package/dist/computer/tools-exec.js +12 -13
  40. package/dist/computer/tools-file.d.ts +9 -1
  41. package/dist/computer/tools-file.js +22 -82
  42. package/dist/computer/{tools-find.d.ts → tools-grep.d.ts} +1 -1
  43. package/dist/computer/tools-grep.js +129 -0
  44. package/dist/repo/checkout.d.ts +4 -5
  45. package/dist/repo/checkout.js +31 -8
  46. package/dist/repo/context.d.ts +35 -9
  47. package/dist/repo/context.js +101 -49
  48. package/dist/repo/index.d.ts +73 -28
  49. package/dist/repo/index.js +112 -96
  50. package/dist/repo/tools-checks.d.ts +3 -0
  51. package/dist/repo/tools-checks.js +112 -0
  52. package/dist/repo/tools-clone.js +1 -1
  53. package/dist/repo/tools-pr.d.ts +14 -0
  54. package/dist/repo/{tools-forge.js → tools-pr.js} +53 -36
  55. package/dist/repo/tools-review.d.ts +9 -0
  56. package/dist/repo/tools-review.js +67 -47
  57. package/dist/repo/tools-worktree.js +144 -60
  58. package/dist/repo/tools-worktrees.d.ts +12 -0
  59. package/dist/repo/tools-worktrees.js +48 -0
  60. package/dist/repo/url.d.ts +2 -2
  61. package/dist/repo/url.js +7 -7
  62. package/dist/scratch/index.d.ts +4 -4
  63. package/dist/scratch/index.js +12 -11
  64. package/dist/{computer → workspace}/advisory.d.ts +4 -4
  65. package/dist/{computer → workspace}/advisory.js +2 -2
  66. package/dist/{computer/host → workspace}/ca-trust.d.ts +8 -1
  67. package/dist/{computer/host → workspace}/ca-trust.js +12 -3
  68. package/dist/{computer/host → workspace}/container-deps.d.ts +4 -0
  69. package/dist/{computer/host → workspace}/container-deps.js +26 -1
  70. package/dist/{computer/host → workspace}/container-fault.d.ts +3 -3
  71. package/dist/{computer/host → workspace}/container-fault.js +23 -8
  72. package/dist/workspace/deps-snapshot.d.ts +110 -0
  73. package/dist/workspace/deps-snapshot.js +424 -0
  74. package/dist/workspace/exec.d.ts +94 -0
  75. package/dist/workspace/exec.js +153 -0
  76. package/dist/workspace/format.d.ts +36 -0
  77. package/dist/workspace/format.js +57 -0
  78. package/dist/{computer/host → workspace}/git-host.d.ts +21 -4
  79. package/dist/{computer/host → workspace}/git-host.js +72 -2
  80. package/dist/{computer/host → workspace}/git-identity.d.ts +1 -1
  81. package/dist/{computer/host → workspace}/git-identity.js +1 -1
  82. package/dist/workspace/index.d.ts +44 -47
  83. package/dist/workspace/index.js +52 -112
  84. package/dist/{computer/host → workspace}/install-job.d.ts +53 -4
  85. package/dist/{computer/host → workspace}/install-job.js +346 -178
  86. package/dist/{computer → workspace}/install.d.ts +17 -8
  87. package/dist/{computer → workspace}/install.js +24 -6
  88. package/dist/{computer/host/workspace.d.ts → workspace/object.d.ts} +125 -27
  89. package/dist/{computer/host/workspace.js → workspace/object.js} +341 -58
  90. package/dist/workspace/open.d.ts +95 -0
  91. package/dist/workspace/open.js +63 -0
  92. package/dist/workspace/read.d.ts +16 -0
  93. package/dist/workspace/read.js +22 -0
  94. package/dist/{computer → workspace}/shell.d.ts +5 -5
  95. package/dist/{computer → workspace}/shell.js +6 -6
  96. package/dist/{computer/host → workspace}/wake.d.ts +3 -1
  97. package/package.json +17 -37
  98. package/dist/arc-agi/analysis.d.ts +0 -322
  99. package/dist/arc-agi/analysis.js +0 -802
  100. package/dist/arc-agi/client.d.ts +0 -82
  101. package/dist/arc-agi/client.js +0 -113
  102. package/dist/arc-agi/game-tools.d.ts +0 -24
  103. package/dist/arc-agi/game-tools.js +0 -26
  104. package/dist/arc-agi/index.d.ts +0 -40
  105. package/dist/arc-agi/index.js +0 -175
  106. package/dist/arc-agi/main-agent.d.ts +0 -24
  107. package/dist/arc-agi/main-agent.js +0 -62
  108. package/dist/arc-agi/recipe.d.ts +0 -48
  109. package/dist/arc-agi/recipe.js +0 -97
  110. package/dist/arc-agi/scorecard.d.ts +0 -118
  111. package/dist/arc-agi/scorecard.js +0 -163
  112. package/dist/arc-agi/soul.d.ts +0 -29
  113. package/dist/arc-agi/soul.js +0 -79
  114. package/dist/arc-agi/store.d.ts +0 -153
  115. package/dist/arc-agi/store.js +0 -215
  116. package/dist/arc-agi/tools.d.ts +0 -36
  117. package/dist/arc-agi/tools.js +0 -574
  118. package/dist/arc-agi/types.d.ts +0 -211
  119. package/dist/arc-agi/types.js +0 -19
  120. package/dist/claude-code/recipe.d.ts +0 -53
  121. package/dist/claude-code/recipe.js +0 -100
  122. package/dist/computer/read.d.ts +0 -133
  123. package/dist/computer/read.js +0 -200
  124. package/dist/computer/tools-find.js +0 -216
  125. package/dist/recall/index.d.ts +0 -105
  126. package/dist/recall/index.js +0 -181
  127. package/dist/repo/tools-forge.d.ts +0 -4
  128. package/dist/triage/index.d.ts +0 -360
  129. package/dist/triage/index.js +0 -311
  130. /package/dist/{computer/host → workspace}/sync.d.ts +0 -0
  131. /package/dist/{computer/host → workspace}/sync.js +0 -0
  132. /package/dist/{computer/host → workspace}/wake.js +0 -0
@@ -1,11 +1,11 @@
1
1
  import type { WorkspaceRuntimeExecHandle, WorkspaceRuntimeExecOptions, WorkspaceRuntimeGetOptions, WorkspaceRuntimeKillOptions } from "@cloudflare/computer";
2
- import type { ProgressEvent } from "@dynamicagents/core/subtasks";
2
+ import type { NoteData } from "@dynamicagents/core/subagent";
3
3
  import { type ClaudeCodeResult, type RateLimitInfo } from "./events.js";
4
4
  import { type EffortLevel, type PermissionMode } from "./config.js";
5
5
  /**
6
- * Launching Claude Code in the workspace container, and draining it in windows.
6
+ * Launching Claude Code in the workspace container, and draining it to its end.
7
7
  *
8
- * ## One Dynamic Agents subtask is one `claude -p` session
8
+ * ## One sub-agent run is one `claude -p` session
9
9
  *
10
10
  * Not one turn, and not one tool call. The unit has to be substantial because of
11
11
  * what an invocation costs before it does anything: the harness carries an
@@ -14,19 +14,21 @@ import { type EffortLevel, type PermissionMode } from "./config.js";
14
14
  *
15
15
  * ## Detached, then re-attached — never owned by a request
16
16
  *
17
- * The run is spawned under an exec id and left running. Each chunk re-attaches,
18
- * drains for a bounded window, and returns. That shape is not a preference: a
19
- * drain owned by an RPC that returns in milliseconds gets disposed mid-command
20
- * — the same way a dependency install dies halfway through `npm ci`.
17
+ * The run is spawned under an exec id and left running, and a drain reads it to
18
+ * its exit. A drain cut short — an eviction, a deploy — does not take the
19
+ * session with it: the next one re-attaches under the same id. That shape is
20
+ * not a preference: a session owned by the request that started it dies with
21
+ * that request — the same way a dependency install dies halfway through
22
+ * `npm ci`.
21
23
  *
22
24
  * ## The cursor
23
25
  *
24
26
  * `getExec(id, { resume })` accepts `"tail"`, `"full"` **or an event sequence
25
- * number**, and the number is what this uses. Each chunk records the last `seq`
26
- * it consumed, so the next one resumes exactly there instead of replaying an
27
- * arbitrary tail. Replay still happens when a chunk dies before it can
28
- * checkpoint, which is why the progress keys stay positional (see
29
- * `toProgress`); the cursor makes replay rare, the keys make it harmless.
27
+ * number**, and the number is what this uses. The caller stores the last `seq`
28
+ * consumed, so a resumed drain starts exactly there instead of replaying an
29
+ * arbitrary tail. Replay still happens when a drain dies before the cursor is
30
+ * stored, which is why the note keys stay positional (see `toProgress`); the
31
+ * cursor makes replay rare, the keys make it harmless.
30
32
  *
31
33
  * The cursor also carries the **exec id** and the **parsed result**, and both
32
34
  * are there for reasons that only show up under concurrency or retry — see
@@ -51,20 +53,24 @@ export declare const CLAUDE_EXEC_PREFIX = "claude-code-run";
51
53
  /**
52
54
  * The exec id one session occupies.
53
55
  *
54
- * **Per subtask, not fixed**, and the difference is load-bearing. A workspace is
55
- * one Durable Object and one container, but subtasks are a flat *concurrent*
56
- * fan-out — two `claude-code` subtasks for the same caller and repository run at
57
- * the same time, against the same workspace. Under a single shared id they
58
- * would spawn over one another, each drain would attach to whichever exec won,
59
- * and `killRun` would stop somebody else's session. That is the displacement bug
60
- * the coder's install guard exists to prevent, in a new place; here the answer
61
- * is simply not to share the id.
56
+ * **Per run, not fixed**, and the difference is load-bearing. A workspace is
57
+ * one Durable Object and one container, but runs are concurrent — sessions in a
58
+ * scratchpad share their parent's container — so under a single shared id they would spawn
59
+ * over one another, each drain would attach to whichever exec won, and
60
+ * `killRun` would stop somebody else's session. That is the displacement bug the
61
+ * coder's install guard exists to prevent, in a new place; here the answer is
62
+ * simply not to share the id.
62
63
  *
63
- * Fixed *per subtask*, though, because the point is still to find it again: an
64
+ * Fixed *per run*, though, because the point is still to find it again: an
64
65
  * isolate that dies mid-drain leaves the session running in the container, and
65
- * `getExec` is how the next chunk re-attaches instead of starting a second one.
66
+ * `getExec` is how the next drain re-attaches instead of starting a second one.
66
67
  */
67
- export declare function execIdFor(subtaskId: string | number): string;
68
+ export declare function execIdFor(runId: string): string;
69
+ /**
70
+ * The id a run's follow-up turn runs under — its own, because the session's exec
71
+ * has already finished under {@link execIdFor} and an id names one process.
72
+ */
73
+ export declare function followUpExecIdFor(runId: string): string;
68
74
  /**
69
75
  * What the container is given instead of a credential.
70
76
  *
@@ -77,8 +83,35 @@ export declare function execIdFor(subtaskId: string | number): string;
77
83
  * invites a future reader to "fix" it by putting the real one there.
78
84
  */
79
85
  export declare const CREDENTIAL_PLACEHOLDER: string;
86
+ /**
87
+ * Where every session keeps Claude Code's own state — its `CLAUDE_CONFIG_DIR`.
88
+ *
89
+ * The whole config directory moves, created if absent: the transcripts under
90
+ * `projects/<cwd-with-/-as-->/<session-id>.jsonl`, the session index,
91
+ * `.claude.json` and the auto-memory directory. None of it is a credential.
92
+ *
93
+ * This is what decides how long a session stays resumable. The CLI's own
94
+ * `$HOME/.claude` is on the container's disk, so a conversation there dies with
95
+ * the container — which a deploy, a rollout and the workspace's own idle timer
96
+ * all end. Under the workspace mount it is the Durable Object's storage, and the
97
+ * conversation outlives the container that held it; a later run can continue it
98
+ * from that same workspace object, and from no other.
99
+ *
100
+ * **Fixed, and never a host's to choose.** A transcript is found only where it
101
+ * was written, so every second place a session could write one is a resume that
102
+ * cannot find it. `env` cannot move it either: {@link buildLaunch} refuses the
103
+ * key.
104
+ *
105
+ * The directory is named {@link file://../computer/paths.ts SESSION_STATE_DIR},
106
+ * which is the name the file tools' walk steps over: a transcript records every
107
+ * line the session read, so a walk that descended into it would answer the
108
+ * parent's `grep` for a line of source with the session that quoted it. The
109
+ * name is written out here rather than imported, because `/claude-code` does
110
+ * not reach `/computer` — a spec holds the two to the same spelling.
111
+ */
112
+ export declare const SESSION_CONFIG_DIR = "/workspace/.claude-sessions";
80
113
  export interface LaunchOptions {
81
- /** The subtask's prompt — the whole of what this session is asked to do. */
114
+ /** The run's prompt — the whole of what this session is asked to do. */
82
115
  prompt: string;
83
116
  /** Where the checkout is. The session runs with this as its cwd. */
84
117
  dir: string;
@@ -120,6 +153,55 @@ export interface LaunchOptions {
120
153
  name: string;
121
154
  email: string;
122
155
  };
156
+ /**
157
+ * A JSON Schema the session's answer must match, as `--json-schema`. The CLI
158
+ * gives the model a `StructuredOutput` tool and holds the run until it is
159
+ * called, and the result line carries the call's input as
160
+ * `structured_output` — see {@link file://./events.ts ClaudeCodeResult}.
161
+ */
162
+ jsonSchema?: Record<string, unknown>;
163
+ /**
164
+ * A Claude Code session id to continue — the handle an earlier session left on
165
+ * its {@link DrainCursor}, or reported on its `result` line.
166
+ *
167
+ * {@link LaunchOptions.prompt} is then the **next user turn** rather than a new
168
+ * task: the conversation is already in the session's context, so a brief that
169
+ * restates it pays for it twice.
170
+ *
171
+ * Not scoped to the working directory — a session recorded against one cwd
172
+ * resumes under another and goes on appending to the original transcript — so
173
+ * what decides whether this works is where the transcript is, which is
174
+ * {@link SESSION_CONFIG_DIR} in the workspace the session ran in.
175
+ *
176
+ * **An id whose transcript is not there is not a failed session.** The CLI
177
+ * prints `No conversation found with session ID`, emits a `result` line
178
+ * carrying that as {@link file://./events.ts ClaudeCodeResult.errors}, and
179
+ * exits in milliseconds; its client still makes its own startup requests, but
180
+ * nothing is inferred and nothing is billed. The honest report is "not
181
+ * resumable", and the retry is the same delegation without this.
182
+ *
183
+ * **An empty id is refused rather than dropped.** That retry is launching
184
+ * without this option, not passing it empty.
185
+ */
186
+ resume?: string;
187
+ /**
188
+ * Fork the resumed conversation: continue it under a **new** session id,
189
+ * leaving the original transcript whole and resumable.
190
+ *
191
+ * What a change of mode wants — a planning session, run under `plan`,
192
+ * continued under the mode that writes — so that the plan's own conversation
193
+ * can still be resumed for an edit afterwards. Continuing in the same mode
194
+ * takes no fork: one conversation accumulating is what "continue" means.
195
+ *
196
+ * Refused without {@link LaunchOptions.resume}: on its own the flag has no
197
+ * conversation to fork, and a caller that set it meant to resume one.
198
+ */
199
+ fork?: boolean;
200
+ /**
201
+ * Present GitHub's placeholder as `GH_TOKEN` and as git's credential, for a
202
+ * gateway holding the real token — see {@link file://./github.ts}.
203
+ */
204
+ github?: boolean;
123
205
  }
124
206
  export interface Launch {
125
207
  command: string;
@@ -129,17 +211,19 @@ export interface Launch {
129
211
  * Build the command and environment for one session.
130
212
  *
131
213
  * `--output-format stream-json` with `--verbose`, because the stream is the only
132
- * way to report progress before the run ends and a run legitimately lasts longer
133
- * than any single chunk. `--verbose` is required: without it Claude Code emits
214
+ * way to report progress before the run ends, and a run lasts for minutes.
215
+ * `--verbose` is required: without it Claude Code emits
134
216
  * only the final result even in stream mode.
135
217
  *
136
- * Note what is **absent**. No `--bare`, no `--settings` override, no
137
- * `CLAUDE_CONFIG_DIR`: a cloned repository's `CLAUDE.md`, skills and hooks are
138
- * exactly the material that makes the agent good at that repository, and the
139
- * container is already an arbitrary-code-execution environment by design — the
140
- * install runs the repo's `postinstall`, the agent runs its test suite. Stripping
141
- * one door while the others stand open buys nothing and costs the agent its
142
- * context.
218
+ * Note what is **absent**. No `--bare` and no `--settings` override: a cloned
219
+ * repository's `CLAUDE.md`, skills and hooks are exactly the material that makes
220
+ * the agent good at that repository, and the container is already an
221
+ * arbitrary-code-execution environment by design — the install runs the repo's
222
+ * `postinstall`, the agent runs its test suite. Stripping one door while the
223
+ * others stand open buys nothing and costs the agent its context.
224
+ * `CLAUDE_CONFIG_DIR` is not that kind of flag, and every session gets it: it
225
+ * moves where the *client* keeps its own state, touching nothing a repository
226
+ * ships — see {@link SESSION_CONFIG_DIR}.
143
227
  *
144
228
  * No `ANTHROPIC_BASE_URL` either: `http-gateway` egress intercepts
145
229
  * transparently, so the client talks to the real hostname and the gateway sees
@@ -173,17 +257,32 @@ export interface Launch {
173
257
  * delete.
174
258
  */
175
259
  export declare function buildLaunch(options: LaunchOptions): Launch;
176
- /** Where a drain got to. Persisted between chunks by the caller. */
260
+ /** Where a drain got to. Stored by the caller, so a cut-short drain resumes. */
177
261
  export interface DrainCursor {
178
262
  /**
179
263
  * The exec id this session occupies.
180
264
  *
181
265
  * Carried rather than derived so a re-attach cannot compute a different one
182
- * from the subtask id it happens to have in hand.
266
+ * from the run id it happens to have in hand — and because a run's follow-up
267
+ * is a second exec, and the cursor says which of the two it names.
183
268
  */
184
269
  execId: string;
185
- /** The last event sequence consumed; the next chunk resumes from here. */
270
+ /** The last event sequence consumed; a resumed drain starts here. */
186
271
  seq: number;
272
+ /**
273
+ * The Claude Code session id, from the `init` line — the handle that continues
274
+ * this conversation later.
275
+ *
276
+ * Carried here rather than read off the `result` line alone because the runs
277
+ * that most need continuing are the ones that never reach a result: a session
278
+ * stopped by its time limit, a cancel or a container rollout reports no result
279
+ * at all, and without this the conversation it got half-way through would be
280
+ * unreachable. The `init` line arrives in the first seconds, so a session has a
281
+ * handle almost from the start — which is why {@link drainRun} offers the
282
+ * cursor the moment the id appears instead of waiting out
283
+ * {@link CHECKPOINT_MIN_MS}.
284
+ */
285
+ sessionId?: string;
187
286
  /** Bytes after the last newline — an incomplete line the next read finishes. */
188
287
  carry: string;
189
288
  /** Progress notes emitted so far. The base for the positional keys. */
@@ -191,11 +290,11 @@ export interface DrainCursor {
191
290
  /**
192
291
  * The `result` line, once seen.
193
292
  *
194
- * Carried because it and the `exit` event are two separate events and a window
195
- * can end between them. Without this a run whose result arrived in the last
196
- * moments of one chunk reports a terminal outcome with **no result** in the
197
- * next — and `persistResult` converts an empty report into a failure, so a
198
- * successful session would be recorded as a failed one.
293
+ * Carried because it and the `exit` event are two separate events and a
294
+ * drain can be cut short between them. Without this a run whose result
295
+ * arrived just before an eviction reports a terminal outcome with **no
296
+ * result** after it resumes — and a successful session would be reported as
297
+ * one that died without a word.
199
298
  */
200
299
  result?: ClaudeCodeResult;
201
300
  /**
@@ -210,31 +309,36 @@ export interface DrainCursor {
210
309
  * this the caller can only report an exit code.
211
310
  *
212
311
  * Carried on the cursor rather than kept local because the death and the exit
213
- * event can land in different windows.
312
+ * event can land either side of an eviction.
214
313
  */
215
314
  stderr?: string;
315
+ /**
316
+ * When the pending thinking block began streaming, on this drain's clock —
317
+ * see `ParsedStream.thinkingFrom` in `./events.ts`.
318
+ */
319
+ thinkingFrom?: number;
216
320
  }
217
321
  /** A cursor for a session that has not started yet. */
218
322
  export declare function freshCursor(execId: string): DrainCursor;
219
323
  export type DrainOutcome = {
220
324
  done: false;
221
325
  cursor: DrainCursor;
222
- progress: ProgressEvent[];
326
+ progress: NoteData[];
223
327
  /**
224
- * The bucket as the client reported it **during this window**, if it did.
328
+ * The bucket as the client reported it **during this drain**, if it did.
225
329
  *
226
330
  * Deliberately not carried on the cursor the way `result` is. A caller
227
331
  * acts on this against whichever credential is leading *now*, so a reading
228
- * replayed on every later chunk would let one window's observation retire
229
- * a credential that was not even in use when it was taken. Absent means
230
- * "this window learned nothing", which is the honest answer.
332
+ * replayed on every resumed drain would let one old observation retire a
333
+ * credential that was not even in use when it was taken. Absent means
334
+ * "this drain learned nothing", which is the honest answer.
231
335
  */
232
336
  rateLimit?: RateLimitInfo;
233
337
  } | {
234
338
  done: true;
235
339
  cursor: DrainCursor;
236
- progress: ProgressEvent[];
237
- /** The bucket as reported **in this window**. See the `done: false` arm. */
340
+ progress: NoteData[];
341
+ /** The bucket as reported **in this drain**. See the `done: false` arm. */
238
342
  rateLimit?: RateLimitInfo;
239
343
  exitCode: number;
240
344
  /** Absent when the process died without ever emitting a `result` line. */
@@ -247,125 +351,135 @@ export type DrainOutcome = {
247
351
  stderr?: string;
248
352
  };
249
353
  export interface DrainOptions {
250
- /**
251
- * How long this chunk may block before checkpointing and yielding.
252
- *
253
- * **This is the coder path's soft limit**, justified the same way core's
254
- * `CHUNK_SOFT_MS` is — by the step timeout — but checked as a deadline on
255
- * reading the session's stdout stream rather than between turns, because a
256
- * `claude -p` session runs its own loop inside the container and there are no
257
- * core-visible turns to stop between. The stream is the only synchronisation
258
- * point there is. {@link file://./config.ts ClaudeCodeConfig.windowMs} holds
259
- * how the default is sized against that timeout, what the headroom it leaves
260
- * is for, and why that headroom is an expectation rather than a guarantee.
261
- *
262
- * It is also what stops a run burning its whole chunk allowance in seconds: a
263
- * drain that returned the moment it had nothing to read would exhaust
264
- * `MAX_CHUNKS_PER_BRANCH` before the session finished thinking.
265
- *
266
- * **It is not the reporting interval**, and reading it as one is the mistake
267
- * {@link DrainOptions.onProgress} exists to remove: a session that finishes
268
- * inside a single window reaches no boundary at all, so a caller with nothing
269
- * but the outcome learns everything at once, once the work is over.
270
- */
271
- windowMs: number;
272
354
  /**
273
355
  * Called with each note as it is parsed, rather than with all of them when the
274
- * window ends.
356
+ * drain ends — a session runs for minutes, and a caller with nothing but the
357
+ * outcome would learn everything at once, once the work is over.
275
358
  *
276
- * Notes are still returned on the outcome as well, so a caller that posts from
277
- * here must drop what it is handed back, or the same note is posted twice. The
278
- * keys are positional, so the gatekeeper would dedupe it, but paying for the
279
- * second post to be discarded is not a plan.
359
+ * Notes are still returned on the outcome as well, so a caller that files them
360
+ * from here must drop what it is handed back, or the same note is filed twice.
361
+ * The keys are positional, so the transcript would dedupe it, but paying for
362
+ * the second one to be discarded is not a plan.
280
363
  *
281
- * **Never awaited inside the read loop.** A post is a signed round trip to the
282
- * gatekeeper — measured at ~700 ms — and awaiting one per note would stall
283
- * reading the container's stream for as long as the session is talkative.
284
- * Calls are chained instead, so they stay in order, and the chain is settled
285
- * before the drain returns.
364
+ * **Never awaited inside the read loop.** Calls are chained instead, so they
365
+ * stay in order without stalling the read of the container's stream for as
366
+ * long as the session is talkative, and the chain is settled before the drain
367
+ * returns.
286
368
  */
287
- onProgress?: (event: ProgressEvent) => void | Promise<void>;
369
+ onProgress?: (note: NoteData) => void | Promise<void>;
288
370
  /**
289
- * Called with a cursor that is safe to persist, no more often than
371
+ * Called with a cursor that is safe to store, no more often than
290
372
  * {@link CHECKPOINT_MIN_MS}.
291
373
  *
292
374
  * **Only meaningful alongside `onProgress`, and that coupling is the whole
293
- * point.** A caller normally commits the cursor after the drain returns,
294
- * because a cursor written ahead of consuming events would skip events a retry
295
- * never saw. A cursor offered here names a position whose notes have *already
296
- * been handed to the sink*, so resuming from it loses nothing a person saw —
297
- * which is only true because the sink posted them.
375
+ * point.** A cursor names a position whose notes have *already been handed to
376
+ * the sink* — it rides the same chain, after them — so resuming from it loses
377
+ * nothing the parent was sent. A note the sink **rejects** ends the
378
+ * checkpoints for the rest of the drain, so no stored cursor passes it: a
379
+ * drain cut after that resumes from before it, and files it again.
298
380
  *
299
- * Without it, a chunk that dies mid-window resumes from the last committed
300
- * position, which is wherever the previous window ended. One production run
301
- * lost six and a half minutes of a session that way and re-derived it by
302
- * replaying the whole stream.
381
+ * Without it, a drain that dies mid-stream resumes from the start of the
382
+ * session. One production run lost six and a half minutes of a session that
383
+ * way and re-derived it by replaying the whole stream.
303
384
  */
304
385
  onCheckpoint?: (cursor: DrainCursor) => void | Promise<void>;
386
+ /**
387
+ * Stops the drain now: the cursor comes back as `done: false`, and the
388
+ * session goes on running — stopping *it* is the caller's, with
389
+ * {@link killRun}. Not honoured once the process has exited, because the read
390
+ * to the end carries the filesystem sync; see the loop in {@link drainRun}.
391
+ */
392
+ signal?: AbortSignal;
305
393
  now?: () => number;
306
394
  }
395
+ /** Whether a thrown value is the runtime refusing to reuse a live exec id. */
396
+ export declare function isExecBusy(err: unknown): boolean;
307
397
  /**
308
398
  * Whether a thrown value means the session this cursor names is gone for good.
309
399
  *
310
400
  * `EEXEC_LOST` is the runtime saying the container that held the execution was
311
401
  * replaced. The session died with it, and no attachment will ever find it — so
312
402
  * unlike `EEXEC_BUSY`, which is a live session reached the wrong way, there is
313
- * nothing to recover and retrying only spends chunks discovering that again.
403
+ * nothing to recover and retrying only discovers that again.
314
404
  */
315
405
  export declare function isExecLost(err: unknown): boolean;
406
+ /**
407
+ * {@link attachRun} giving up on a subscriber that was never released. Unlike
408
+ * a transient failure, nothing retries it: the run fails, and its host stops
409
+ * the session.
410
+ */
411
+ export declare class AttachGivenUpError extends Error {
412
+ name: string;
413
+ }
316
414
  /**
317
415
  * Start a session, detached.
318
416
  *
319
417
  * **Falls back to attaching when the id is already live**, which is not a
320
- * nicety. `start` spawns and then drains for minutes, so a chunk that fails
321
- * anywhere after the spawn is retried by the Workflow with no cursor to resume
322
- * from — and `@cloudflare/computer` refuses to reuse an id whose execution is
323
- * still running (`EEXEC_BUSY`). Without this the retry throws, every later retry
418
+ * nicety. `start` spawns and then drains for minutes, so a turn cut anywhere
419
+ * after the spawn is recovered with no cursor stored yet — and
420
+ * `@cloudflare/computer` refuses to reuse an id whose execution is still
421
+ * running (`EEXEC_BUSY`). Without this the recovery throws, every later one
324
422
  * throws the same way, and a perfectly healthy session in the container becomes
325
423
  * unreachable.
326
424
  *
327
425
  * **The sync stays on the default, and `defer` is not an option here.** A
328
426
  * deferred exec skips the post-command pull entirely and settles the outcome
329
427
  * from the cursor instead — and its `cancel` reads the stream to the end first,
330
- * so ending a window would block until the session exits rather than yielding
331
- * the chunk. Both halves are exactly what this drain is built not to do.
428
+ * so stopping a drain would block until the session exits. Both halves are
429
+ * exactly what this drain is built not to do.
332
430
  */
333
431
  export declare function startRun(runtime: SessionRuntime, options: LaunchOptions & {
334
432
  execId: string;
335
433
  timeoutMs: number;
434
+ /** Stops the fallback's wait — see {@link AttachOptions.signal}. */
435
+ signal?: AbortSignal;
336
436
  }): Promise<WorkspaceRuntimeExecHandle<"utf8">>;
337
- /** The clock and the wait, injectable so a spec can drive the bound. */
338
437
  export interface AttachOptions {
438
+ /**
439
+ * How long to wait out a subscriber that has not been released: the
440
+ * session's own `timeoutMs`.
441
+ *
442
+ * Giving up fails the run, and a failed run is stopped by its host — so a
443
+ * shorter bound kills a healthy session, and past this one the session is
444
+ * over anyway. How long a release actually took is logged when the attach
445
+ * lands.
446
+ */
447
+ maxWaitMs: number;
448
+ /** The clock and the wait, injectable so a spec can drive the bound. */
339
449
  now?: () => number;
340
450
  wait?: (ms: number) => Promise<void>;
451
+ /** Stops the wait — see `DrainOptions.signal`. */
452
+ signal?: AbortSignal;
341
453
  }
342
454
  /**
343
455
  * Re-attach to a session this isolate did not start.
344
456
  *
345
- * **Waits out a subscriber the previous window has not released yet**, and that
457
+ * **Waits out a subscriber the previous drain has not released yet**, and that
346
458
  * wait is the point of this function rather than a refinement of it. The
347
- * alternative is not "fail fast" — it is spending a Workflow retry to discover a
348
- * condition that clears on its own in a second, when a chunk step's retries are
349
- * what a deploy, a severed stub and a network drop have to be covered out of.
350
- * Core sizes that budget and says why; what matters here is that this must not
351
- * be charged to it. A deployment lost a twenty-one-minute session exactly that
352
- * way — consecutive attach races used the retries up, and the redeploy that
353
- * followed had no attempt left to be retried on. See {@link isExecSubscribed}.
459
+ * alternative is not "fail fast" — it is spending a recovery to discover a
460
+ * condition that clears on its own in a second, when recoveries are what a
461
+ * deploy, a severed stub and a network drop have to be covered out of. A
462
+ * deployment lost a twenty-one-minute session exactly that way — consecutive
463
+ * attach races used the retries up, and the redeploy that followed had no
464
+ * attempt left to be retried on. See {@link isExecSubscribed}.
354
465
  *
355
466
  * Everything else is rethrown on the first look, `EEXEC_LOST` above all: it is
356
467
  * the runtime saying the container was replaced, `resume` turns it into a report
357
- * for the model, and a retry loop would sit on it for {@link ATTACH_MAX_MS}
358
- * learning nothing.
468
+ * for the model, and a retry loop would sit on it learning nothing.
359
469
  */
360
- export declare function attachRun(runtime: SessionRuntime, cursor: DrainCursor, options?: AttachOptions): Promise<WorkspaceRuntimeExecHandle<"utf8">>;
470
+ export declare function attachRun(runtime: SessionRuntime, cursor: DrainCursor, options: AttachOptions): Promise<WorkspaceRuntimeExecHandle<"utf8">>;
361
471
  /**
362
- * Drain a session for one bounded window.
472
+ * Drain a session to its end.
363
473
  *
364
- * Returns `done: false` when the window expired with the process still running —
365
- * the caller checkpoints the cursor and comes back — or `done: true` once the
366
- * stream has finished.
474
+ * Returns `done: true` once the stream has finished, or `done: false` when the
475
+ * caller's signal stopped the drain with the process still running.
476
+ */
477
+ export declare function drainRun(handle: WorkspaceRuntimeExecHandle<"utf8">, cursor: DrainCursor, options?: DrainOptions): Promise<DrainOutcome>;
478
+ /**
479
+ * What a session reports on `stderr` when its container went away under it:
480
+ * the stream ended with no `exit` event, or a re-attach found the exec gone.
367
481
  */
368
- export declare function drainRun(handle: WorkspaceRuntimeExecHandle<"utf8">, cursor: DrainCursor, options: DrainOptions): Promise<DrainOutcome>;
482
+ export declare function containerLost(printed?: string): string;
369
483
  /**
370
484
  * Stop a session.
371
485
  *