@dynamicagents/plugins 0.11.1 → 0.13.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 (134) hide show
  1. package/README.md +59 -79
  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 +53 -0
  5. package/dist/claude-code/cards.js +272 -0
  6. package/dist/claude-code/config.d.ts +73 -31
  7. package/dist/claude-code/config.js +0 -2
  8. package/dist/claude-code/credentials.d.ts +21 -9
  9. package/dist/claude-code/credentials.js +43 -9
  10. package/dist/claude-code/egress.d.ts +15 -14
  11. package/dist/claude-code/egress.js +51 -14
  12. package/dist/claude-code/events.d.ts +153 -9
  13. package/dist/claude-code/events.js +289 -58
  14. package/dist/claude-code/github.d.ts +39 -0
  15. package/dist/claude-code/github.js +82 -0
  16. package/dist/claude-code/index.d.ts +24 -80
  17. package/dist/claude-code/index.js +19 -234
  18. package/dist/claude-code/model.d.ts +152 -0
  19. package/dist/claude-code/model.js +342 -0
  20. package/dist/claude-code/run.d.ts +283 -92
  21. package/dist/claude-code/run.js +620 -114
  22. package/dist/claude-code/session.d.ts +131 -0
  23. package/dist/claude-code/session.js +285 -0
  24. package/dist/claude-code/spec.d.ts +46 -0
  25. package/dist/claude-code/spec.js +83 -0
  26. package/dist/claude-code/verified.d.ts +15 -0
  27. package/dist/claude-code/verified.js +15 -0
  28. package/dist/computer/context.d.ts +19 -0
  29. package/dist/computer/context.js +222 -0
  30. package/dist/computer/file-lock.d.ts +5 -0
  31. package/dist/computer/file-lock.js +40 -0
  32. package/dist/computer/gate.d.ts +12 -12
  33. package/dist/computer/gate.js +12 -12
  34. package/dist/computer/index.d.ts +57 -255
  35. package/dist/computer/index.js +98 -1039
  36. package/dist/computer/paths.d.ts +47 -22
  37. package/dist/computer/paths.js +64 -49
  38. package/dist/computer/proxy.d.ts +45 -0
  39. package/dist/computer/proxy.js +234 -0
  40. package/dist/computer/render.d.ts +27 -38
  41. package/dist/computer/render.js +32 -65
  42. package/dist/computer/tools-exec.d.ts +3 -0
  43. package/dist/computer/tools-exec.js +268 -0
  44. package/dist/computer/tools-file.d.ts +12 -0
  45. package/dist/computer/tools-file.js +64 -0
  46. package/dist/computer/tools-grep.d.ts +3 -0
  47. package/dist/computer/tools-grep.js +129 -0
  48. package/dist/repo/checkout.d.ts +35 -6
  49. package/dist/repo/checkout.js +64 -6
  50. package/dist/repo/context.d.ts +135 -0
  51. package/dist/repo/context.js +582 -0
  52. package/dist/repo/index.d.ts +79 -57
  53. package/dist/repo/index.js +118 -1112
  54. package/dist/repo/tools-checks.d.ts +3 -0
  55. package/dist/repo/tools-checks.js +112 -0
  56. package/dist/repo/tools-clone.d.ts +4 -0
  57. package/dist/repo/tools-clone.js +192 -0
  58. package/dist/repo/tools-pr.d.ts +14 -0
  59. package/dist/repo/tools-pr.js +196 -0
  60. package/dist/repo/tools-review.d.ts +12 -0
  61. package/dist/repo/tools-review.js +393 -0
  62. package/dist/repo/tools-worktree.d.ts +4 -0
  63. package/dist/repo/tools-worktree.js +305 -0
  64. package/dist/repo/tools-worktrees.d.ts +12 -0
  65. package/dist/repo/tools-worktrees.js +48 -0
  66. package/dist/repo/url.d.ts +2 -2
  67. package/dist/repo/url.js +7 -7
  68. package/dist/scratch/index.d.ts +4 -4
  69. package/dist/scratch/index.js +12 -11
  70. package/dist/{computer → workspace}/advisory.d.ts +33 -20
  71. package/dist/{computer → workspace}/advisory.js +32 -7
  72. package/dist/workspace/ca-trust.d.ts +95 -0
  73. package/dist/workspace/ca-trust.js +312 -0
  74. package/dist/workspace/container-deps.d.ts +54 -0
  75. package/dist/workspace/container-deps.js +224 -0
  76. package/dist/workspace/container-fault.d.ts +38 -0
  77. package/dist/workspace/container-fault.js +73 -0
  78. package/dist/workspace/exec.d.ts +94 -0
  79. package/dist/workspace/exec.js +153 -0
  80. package/dist/workspace/format.d.ts +36 -0
  81. package/dist/workspace/format.js +57 -0
  82. package/dist/workspace/git-host.d.ts +79 -0
  83. package/dist/workspace/git-host.js +226 -0
  84. package/dist/workspace/git-identity.d.ts +57 -0
  85. package/dist/workspace/git-identity.js +147 -0
  86. package/dist/workspace/index.d.ts +43 -47
  87. package/dist/workspace/index.js +52 -112
  88. package/dist/workspace/install-job.d.ts +121 -0
  89. package/dist/workspace/install-job.js +866 -0
  90. package/dist/{computer → workspace}/install.d.ts +15 -19
  91. package/dist/{computer → workspace}/install.js +8 -11
  92. package/dist/workspace/object.d.ts +502 -0
  93. package/dist/workspace/object.js +1427 -0
  94. package/dist/workspace/open.d.ts +95 -0
  95. package/dist/workspace/open.js +63 -0
  96. package/dist/workspace/read.d.ts +16 -0
  97. package/dist/workspace/read.js +22 -0
  98. package/dist/workspace/shell.d.ts +45 -0
  99. package/dist/workspace/shell.js +80 -0
  100. package/dist/workspace/sync.d.ts +111 -0
  101. package/dist/workspace/sync.js +124 -0
  102. package/dist/workspace/wake.d.ts +19 -0
  103. package/dist/workspace/wake.js +10 -0
  104. package/package.json +28 -42
  105. package/dist/arc-agi/analysis.d.ts +0 -336
  106. package/dist/arc-agi/analysis.js +0 -829
  107. package/dist/arc-agi/client.d.ts +0 -82
  108. package/dist/arc-agi/client.js +0 -113
  109. package/dist/arc-agi/game-tools.d.ts +0 -25
  110. package/dist/arc-agi/game-tools.js +0 -26
  111. package/dist/arc-agi/index.d.ts +0 -42
  112. package/dist/arc-agi/index.js +0 -175
  113. package/dist/arc-agi/main-agent.d.ts +0 -27
  114. package/dist/arc-agi/main-agent.js +0 -65
  115. package/dist/arc-agi/recipe.d.ts +0 -51
  116. package/dist/arc-agi/recipe.js +0 -100
  117. package/dist/arc-agi/scorecard.d.ts +0 -118
  118. package/dist/arc-agi/scorecard.js +0 -163
  119. package/dist/arc-agi/soul.d.ts +0 -40
  120. package/dist/arc-agi/soul.js +0 -90
  121. package/dist/arc-agi/store.d.ts +0 -154
  122. package/dist/arc-agi/store.js +0 -216
  123. package/dist/arc-agi/tools.d.ts +0 -36
  124. package/dist/arc-agi/tools.js +0 -576
  125. package/dist/arc-agi/types.d.ts +0 -212
  126. package/dist/arc-agi/types.js +0 -19
  127. package/dist/claude-code/recipe.d.ts +0 -53
  128. package/dist/claude-code/recipe.js +0 -100
  129. package/dist/computer/read.d.ts +0 -137
  130. package/dist/computer/read.js +0 -203
  131. package/dist/recall/index.d.ts +0 -99
  132. package/dist/recall/index.js +0 -171
  133. package/dist/triage/index.d.ts +0 -355
  134. package/dist/triage/index.js +0 -302
@@ -1,11 +1,11 @@
1
1
  import type { WorkspaceRuntimeExecHandle, WorkspaceRuntimeExecOptions, WorkspaceRuntimeGetOptions, WorkspaceRuntimeKillOptions } from "@cloudflare/computer";
2
- import type { ProgressEvent } from "@dynamicagents/core/subtasks";
3
- import { type ClaudeCodeResult } from "./events.js";
4
- import { type PermissionMode } from "./config.js";
2
+ import type { NoteData } from "@dynamicagents/core/subagent";
3
+ import { type ClaudeCodeResult, type RateLimitInfo } from "./events.js";
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,20 +14,21 @@ import { 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
- * which is exactly how the dependency install used to die halfway through
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
21
22
  * `npm ci`.
22
23
  *
23
24
  * ## The cursor
24
25
  *
25
26
  * `getExec(id, { resume })` accepts `"tail"`, `"full"` **or an event sequence
26
- * number**, and the number is what this uses. Each chunk records the last `seq`
27
- * it consumed, so the next one resumes exactly there instead of replaying an
28
- * arbitrary tail. Replay still happens when a chunk dies before it can
29
- * checkpoint, which is why the progress keys stay positional (see
30
- * `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.
31
32
  *
32
33
  * The cursor also carries the **exec id** and the **parsed result**, and both
33
34
  * are there for reasons that only show up under concurrency or retry — see
@@ -52,49 +53,79 @@ export declare const CLAUDE_EXEC_PREFIX = "claude-code-run";
52
53
  /**
53
54
  * The exec id one session occupies.
54
55
  *
55
- * **Per subtask, not fixed**, and the difference is load-bearing. A workspace is
56
- * one Durable Object and one container, but subtasks are a flat *concurrent*
57
- * fan-out — two `claude-code` subtasks for the same caller and repository run at
58
- * the same time, against the same workspace. Under a single shared id they
59
- * would spawn over one another, each drain would attach to whichever exec won,
60
- * and `killRun` would stop somebody else's session. That is the displacement bug
61
- * the coder's install guard exists to prevent, in a new place; here the answer
62
- * 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.
63
63
  *
64
- * 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
65
65
  * isolate that dies mid-drain leaves the session running in the container, and
66
- * `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.
67
67
  */
68
- 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;
69
74
  /**
70
75
  * What the container is given instead of a credential.
71
76
  *
72
- * Claude Code does not validate it locally — proven in the Phase 0c spike, where
73
- * a run with this exact value succeeded and the proxy log confirmed the
74
- * container only ever sent the placeholder. The egress gateway swaps in the real
75
- * credential on the way out, so a `postinstall` script that dumps the
76
- * environment learns this and nothing else.
77
+ * Claude Code does not validate it locally: a run with this exact value
78
+ * succeeds, and the container only ever sends the placeholder. The egress
79
+ * gateway swaps in the real credential on the way out, so a `postinstall` script
80
+ * that dumps the environment learns this and nothing else.
77
81
  *
78
82
  * Shaped like a real token deliberately: something that looks obviously fake
79
83
  * invites a future reader to "fix" it by putting the real one there.
80
84
  */
81
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";
82
113
  export interface LaunchOptions {
83
- /** 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. */
84
115
  prompt: string;
85
116
  /** Where the checkout is. The session runs with this as its cwd. */
86
117
  dir: string;
87
118
  model?: string;
88
- /** Ceiling on the *outer* session's turns. Advisory; the budget gate is not. */
89
- maxTurns?: number;
90
119
  /**
91
- * Caps on Claude Code's own subagent tree.
120
+ * How hard the model thinks, per turn. Unset, the model's own default.
92
121
  *
93
- * These steer rather than enforce, and the distinction matters: the inner
94
- * tree is invisible to Dynamic Agents' scheduler and unreachable by its
95
- * cancellation sweep, so a cap it chooses to ignore has no backstop. What
96
- * actually bounds the spend is the egress gateway, which every inner call
97
- * also crosses.
122
+ * See {@link file://./config.ts ClaudeCodeConfig.effort} for what a level
123
+ * costs, and why an unrecognised one is worse here than a rejected one.
124
+ */
125
+ effort?: EffortLevel;
126
+ /**
127
+ * Caps on Claude Code's own subagent tree — advisory, for the reason
128
+ * {@link file://./config.ts ClaudeCodeConfig.maxSubagentDepth} gives.
98
129
  */
99
130
  maxSubagentDepth?: number;
100
131
  maxConcurrentSubagents?: number;
@@ -114,6 +145,63 @@ export interface LaunchOptions {
114
145
  * than trusted to the reader of that sentence.
115
146
  */
116
147
  env?: Record<string, string>;
148
+ /**
149
+ * Who this session's commits are attributed to — see
150
+ * {@link file://./config.ts ClaudeCodeConfig.author}.
151
+ */
152
+ author?: {
153
+ name: string;
154
+ email: string;
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;
117
205
  }
118
206
  export interface Launch {
119
207
  command: string;
@@ -123,37 +211,32 @@ export interface Launch {
123
211
  * Build the command and environment for one session.
124
212
  *
125
213
  * `--output-format stream-json` with `--verbose`, because the stream is the only
126
- * way to report progress before the run ends and a run legitimately lasts longer
127
- * 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
128
216
  * only the final result even in stream mode.
129
217
  *
130
- * Note what is **absent**. No `--bare`, no `--settings` override, no
131
- * `CLAUDE_CONFIG_DIR`: a cloned repository's `CLAUDE.md`, skills and hooks are
132
- * exactly the material that makes the agent good at that repository, and the
133
- * container is already an arbitrary-code-execution environment by design — the
134
- * install runs the repo's `postinstall`, the agent runs its test suite. Stripping
135
- * one door while the others stand open buys nothing and costs the agent its
136
- * context. (§4 of the design plan, cancelled 2026-08-21.)
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}.
137
227
  *
138
- * No `ANTHROPIC_BASE_URL` either, and that one is a genuine simplification over
139
- * the spike: `http-gateway` egress intercepts transparently, so the client talks
140
- * to the real hostname and the gateway sees it. Nothing has to be told to use a
141
- * proxy, which means nothing in the container can be told *not* to.
228
+ * No `ANTHROPIC_BASE_URL` either: `http-gateway` egress intercepts
229
+ * transparently, so the client talks to the real hostname and the gateway sees
230
+ * it. Nothing has to be told to use a proxy, which means nothing in the
231
+ * container can be told *not* to.
142
232
  *
143
233
  * ## `--permission-mode`, and the root guard behind it
144
234
  *
145
- * `-p` is headless. There is no terminal, so there is nobody to answer a
146
- * permission prompt — and Claude Code's headless path does not wait for one, it
147
- * **auto-denies**. Left unset the mode is `default`, and `default` gates Write,
148
- * Edit and every Bash command. A session in that state reads the repository
149
- * perfectly and cannot change one byte of it, while reporting prose that reads
150
- * like considered reluctance rather than a blocked tool. That cost this
151
- * deployment a day of "the container is fixed but nothing lands".
152
- *
153
- * So the mode is passed explicitly, and {@link DEFAULT_PERMISSION_MODE} is
154
- * `bypassPermissions` — see
155
- * {@link file://./config.ts ClaudeCodeConfig.permissionMode} for why the
156
- * permissive value is the correct default here rather than a concession.
235
+ * The mode is always passed explicitly, never left to the CLI's `default` — see
236
+ * {@link file://./config.ts ClaudeCodeConfig.permissionMode} for what an
237
+ * unset mode does to a headless session, and why
238
+ * {@link DEFAULT_PERMISSION_MODE} is the permissive value rather than a
239
+ * concession.
157
240
  *
158
241
  * **`IS_SANDBOX=1` ships in the same branch as the flag, and separating the two
159
242
  * breaks a session harder than passing no flag at all.** The container runs as
@@ -174,17 +257,32 @@ export interface Launch {
174
257
  * delete.
175
258
  */
176
259
  export declare function buildLaunch(options: LaunchOptions): Launch;
177
- /** 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. */
178
261
  export interface DrainCursor {
179
262
  /**
180
263
  * The exec id this session occupies.
181
264
  *
182
265
  * Carried rather than derived so a re-attach cannot compute a different one
183
- * 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.
184
268
  */
185
269
  execId: string;
186
- /** The last event sequence consumed; the next chunk resumes from here. */
270
+ /** The last event sequence consumed; a resumed drain starts here. */
187
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;
188
286
  /** Bytes after the last newline — an incomplete line the next read finishes. */
189
287
  carry: string;
190
288
  /** Progress notes emitted so far. The base for the positional keys. */
@@ -192,11 +290,11 @@ export interface DrainCursor {
192
290
  /**
193
291
  * The `result` line, once seen.
194
292
  *
195
- * Carried because it and the `exit` event are two separate events and a window
196
- * can end between them. Without this a run whose result arrived in the last
197
- * moments of one chunk reports a terminal outcome with **no result** in the
198
- * next — and `persistResult` converts an empty report into a failure, so a
199
- * 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.
200
298
  */
201
299
  result?: ClaudeCodeResult;
202
300
  /**
@@ -211,20 +309,37 @@ export interface DrainCursor {
211
309
  * this the caller can only report an exit code.
212
310
  *
213
311
  * Carried on the cursor rather than kept local because the death and the exit
214
- * event can land in different windows.
312
+ * event can land either side of an eviction.
215
313
  */
216
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;
217
320
  }
218
321
  /** A cursor for a session that has not started yet. */
219
322
  export declare function freshCursor(execId: string): DrainCursor;
220
323
  export type DrainOutcome = {
221
324
  done: false;
222
325
  cursor: DrainCursor;
223
- progress: ProgressEvent[];
326
+ progress: NoteData[];
327
+ /**
328
+ * The bucket as the client reported it **during this drain**, if it did.
329
+ *
330
+ * Deliberately not carried on the cursor the way `result` is. A caller
331
+ * acts on this against whichever credential is leading *now*, so a reading
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.
335
+ */
336
+ rateLimit?: RateLimitInfo;
224
337
  } | {
225
338
  done: true;
226
339
  cursor: DrainCursor;
227
- progress: ProgressEvent[];
340
+ progress: NoteData[];
341
+ /** The bucket as reported **in this drain**. See the `done: false` arm. */
342
+ rateLimit?: RateLimitInfo;
228
343
  exitCode: number;
229
344
  /** Absent when the process died without ever emitting a `result` line. */
230
345
  result?: ClaudeCodeResult;
@@ -237,41 +352,117 @@ export type DrainOutcome = {
237
352
  };
238
353
  export interface DrainOptions {
239
354
  /**
240
- * How long this chunk may block before checkpointing and yielding.
355
+ * Called with each note as it is parsed, rather than with all of them when the
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.
358
+ *
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.
241
363
  *
242
- * Must stay comfortably under the Workflow step timeout, and it is what stops
243
- * a run burning its whole chunk allowance in seconds: a drain that returned
244
- * the moment it had nothing to read would exhaust `MAX_CHUNKS_PER_BRANCH`
245
- * before the session finished thinking.
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.
246
368
  */
247
- windowMs: number;
369
+ onProgress?: (note: NoteData) => void | Promise<void>;
370
+ /**
371
+ * Called with a cursor that is safe to store, no more often than
372
+ * {@link CHECKPOINT_MIN_MS}.
373
+ *
374
+ * **Only meaningful alongside `onProgress`, and that coupling is the whole
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.
380
+ *
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.
384
+ */
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;
248
393
  now?: () => number;
249
394
  }
395
+ /** Whether a thrown value is the runtime refusing to reuse a live exec id. */
396
+ export declare function isExecBusy(err: unknown): boolean;
397
+ /**
398
+ * Whether a thrown value means the session this cursor names is gone for good.
399
+ *
400
+ * `EEXEC_LOST` is the runtime saying the container that held the execution was
401
+ * replaced. The session died with it, and no attachment will ever find it — so
402
+ * unlike `EEXEC_BUSY`, which is a live session reached the wrong way, there is
403
+ * nothing to recover and retrying only discovers that again.
404
+ */
405
+ export declare function isExecLost(err: unknown): boolean;
250
406
  /**
251
407
  * Start a session, detached.
252
408
  *
253
409
  * **Falls back to attaching when the id is already live**, which is not a
254
- * nicety. `start` spawns and then drains for minutes, so a chunk that fails
255
- * anywhere after the spawn is retried by the Workflow with no cursor to resume
256
- * from — and `@cloudflare/computer` refuses to reuse an id whose execution is
257
- * still running (`EEXEC_BUSY`). Without this the retry throws, every later retry
410
+ * nicety. `start` spawns and then drains for minutes, so a turn cut anywhere
411
+ * after the spawn is recovered with no cursor stored yet — and
412
+ * `@cloudflare/computer` refuses to reuse an id whose execution is still
413
+ * running (`EEXEC_BUSY`). Without this the recovery throws, every later one
258
414
  * throws the same way, and a perfectly healthy session in the container becomes
259
415
  * unreachable.
416
+ *
417
+ * **The sync stays on the default, and `defer` is not an option here.** A
418
+ * deferred exec skips the post-command pull entirely and settles the outcome
419
+ * from the cursor instead — and its `cancel` reads the stream to the end first,
420
+ * so stopping a drain would block until the session exits. Both halves are
421
+ * exactly what this drain is built not to do.
260
422
  */
261
423
  export declare function startRun(runtime: SessionRuntime, options: LaunchOptions & {
262
424
  execId: string;
263
425
  timeoutMs: number;
426
+ /** Stops the fallback's wait — see {@link AttachOptions.signal}. */
427
+ signal?: AbortSignal;
264
428
  }): Promise<WorkspaceRuntimeExecHandle<"utf8">>;
265
- /** Re-attach to a session this isolate did not start. */
266
- export declare function attachRun(runtime: SessionRuntime, cursor: DrainCursor): Promise<WorkspaceRuntimeExecHandle<"utf8">>;
429
+ /** The clock and the wait, injectable so a spec can drive the bound. */
430
+ export interface AttachOptions {
431
+ now?: () => number;
432
+ wait?: (ms: number) => Promise<void>;
433
+ /** Stops the wait — see `DrainOptions.signal`. */
434
+ signal?: AbortSignal;
435
+ }
267
436
  /**
268
- * Drain a session for one bounded window.
437
+ * Re-attach to a session this isolate did not start.
269
438
  *
270
- * Returns `done: false` when the window expired with the process still running —
271
- * the caller checkpoints the cursor and comes back — or `done: true` once the
272
- * stream has finished.
439
+ * **Waits out a subscriber the previous drain has not released yet**, and that
440
+ * wait is the point of this function rather than a refinement of it. The
441
+ * alternative is not "fail fast" — it is spending a recovery to discover a
442
+ * condition that clears on its own in a second, when recoveries are what a
443
+ * deploy, a severed stub and a network drop have to be covered out of. A
444
+ * deployment lost a twenty-one-minute session exactly that way — consecutive
445
+ * attach races used the retries up, and the redeploy that followed had no
446
+ * attempt left to be retried on. See {@link isExecSubscribed}.
447
+ *
448
+ * Everything else is rethrown on the first look, `EEXEC_LOST` above all: it is
449
+ * the runtime saying the container was replaced, `resume` turns it into a report
450
+ * for the model, and a retry loop would sit on it for {@link ATTACH_MAX_MS}
451
+ * learning nothing.
452
+ */
453
+ export declare function attachRun(runtime: SessionRuntime, cursor: DrainCursor, options?: AttachOptions): Promise<WorkspaceRuntimeExecHandle<"utf8">>;
454
+ /**
455
+ * Drain a session to its end.
456
+ *
457
+ * Returns `done: true` once the stream has finished, or `done: false` when the
458
+ * caller's signal stopped the drain with the process still running.
459
+ */
460
+ export declare function drainRun(handle: WorkspaceRuntimeExecHandle<"utf8">, cursor: DrainCursor, options?: DrainOptions): Promise<DrainOutcome>;
461
+ /**
462
+ * What a session reports on `stderr` when its container went away under it:
463
+ * the stream ended with no `exit` event, or a re-attach found the exec gone.
273
464
  */
274
- export declare function drainRun(handle: WorkspaceRuntimeExecHandle<"utf8">, cursor: DrainCursor, options: DrainOptions): Promise<DrainOutcome>;
465
+ export declare function containerLost(printed?: string): string;
275
466
  /**
276
467
  * Stop a session.
277
468
  *