@earendil-works/pi-durable 0.99.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +196 -74
  3. package/dist/env/index.d.ts +5 -0
  4. package/dist/env/index.d.ts.map +1 -1
  5. package/dist/env/index.js.map +1 -1
  6. package/dist/env/node.d.ts +2 -0
  7. package/dist/env/node.d.ts.map +1 -1
  8. package/dist/env/node.js +2 -0
  9. package/dist/env/node.js.map +1 -1
  10. package/dist/harness/agent.d.ts +31 -0
  11. package/dist/harness/agent.d.ts.map +1 -0
  12. package/dist/harness/agent.js +209 -0
  13. package/dist/harness/agent.js.map +1 -0
  14. package/dist/harness/compaction.d.ts.map +1 -1
  15. package/dist/harness/compaction.js +8 -11
  16. package/dist/harness/compaction.js.map +1 -1
  17. package/dist/harness/define.d.ts +18 -0
  18. package/dist/harness/define.d.ts.map +1 -0
  19. package/dist/harness/define.js +25 -0
  20. package/dist/harness/define.js.map +1 -0
  21. package/dist/harness/events.d.ts +5 -5
  22. package/dist/harness/events.d.ts.map +1 -1
  23. package/dist/harness/events.js +6 -6
  24. package/dist/harness/events.js.map +1 -1
  25. package/dist/harness/generation.d.ts +2 -4
  26. package/dist/harness/generation.d.ts.map +1 -1
  27. package/dist/harness/generation.js +39 -43
  28. package/dist/harness/generation.js.map +1 -1
  29. package/dist/harness/harness.d.ts.map +1 -1
  30. package/dist/harness/harness.js +68 -154
  31. package/dist/harness/harness.js.map +1 -1
  32. package/dist/harness/inbox.d.ts +4 -2
  33. package/dist/harness/inbox.d.ts.map +1 -1
  34. package/dist/harness/inbox.js +3 -5
  35. package/dist/harness/inbox.js.map +1 -1
  36. package/dist/harness/prompt.d.ts +1 -3
  37. package/dist/harness/prompt.d.ts.map +1 -1
  38. package/dist/harness/prompt.js +1 -11
  39. package/dist/harness/prompt.js.map +1 -1
  40. package/dist/harness/registry.d.ts +2 -3
  41. package/dist/harness/registry.d.ts.map +1 -1
  42. package/dist/harness/registry.js +66 -283
  43. package/dist/harness/registry.js.map +1 -1
  44. package/dist/harness/scheduler.d.ts +7 -2
  45. package/dist/harness/scheduler.d.ts.map +1 -1
  46. package/dist/harness/scheduler.js +41 -27
  47. package/dist/harness/scheduler.js.map +1 -1
  48. package/dist/harness/submissions.d.ts +4 -4
  49. package/dist/harness/submissions.d.ts.map +1 -1
  50. package/dist/harness/submissions.js +7 -4
  51. package/dist/harness/submissions.js.map +1 -1
  52. package/dist/harness/task-graph.d.ts +49 -0
  53. package/dist/harness/task-graph.d.ts.map +1 -0
  54. package/dist/harness/task-graph.js +163 -0
  55. package/dist/harness/task-graph.js.map +1 -0
  56. package/dist/harness/tool.d.ts +1 -1
  57. package/dist/harness/tool.d.ts.map +1 -1
  58. package/dist/harness/tool.js +9 -6
  59. package/dist/harness/tool.js.map +1 -1
  60. package/dist/harness/types.d.ts +171 -140
  61. package/dist/harness/types.d.ts.map +1 -1
  62. package/dist/harness/types.js.map +1 -1
  63. package/dist/harness/view.d.ts +1 -1
  64. package/dist/harness/view.d.ts.map +1 -1
  65. package/dist/harness/view.js +2 -7
  66. package/dist/harness/view.js.map +1 -1
  67. package/dist/index.d.ts +4 -2
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +2 -1
  70. package/dist/index.js.map +1 -1
  71. package/dist/session/session.d.ts.map +1 -1
  72. package/dist/session/session.js +5 -1
  73. package/dist/session/session.js.map +1 -1
  74. package/dist/session/transaction.d.ts +2 -2
  75. package/dist/session/transaction.d.ts.map +1 -1
  76. package/dist/session/transaction.js +7 -4
  77. package/dist/session/transaction.js.map +1 -1
  78. package/dist/storage/sqlite/database.d.ts +26 -19
  79. package/dist/storage/sqlite/database.d.ts.map +1 -1
  80. package/dist/storage/sqlite/database.js.map +1 -1
  81. package/dist/storage/sqlite/index.d.ts +1 -1
  82. package/dist/storage/sqlite/index.d.ts.map +1 -1
  83. package/dist/storage/sqlite/index.js.map +1 -1
  84. package/dist/storage/sqlite/migrations.js +6 -6
  85. package/dist/storage/sqlite/migrations.js.map +1 -1
  86. package/dist/storage/sqlite/node.d.ts +23 -7
  87. package/dist/storage/sqlite/node.d.ts.map +1 -1
  88. package/dist/storage/sqlite/node.js +129 -47
  89. package/dist/storage/sqlite/node.js.map +1 -1
  90. package/dist/storage/sqlite/storage.d.ts +12 -0
  91. package/dist/storage/sqlite/storage.d.ts.map +1 -1
  92. package/dist/storage/sqlite/storage.js +121 -129
  93. package/dist/storage/sqlite/storage.js.map +1 -1
  94. package/dist/tools/bash.d.ts +1 -1
  95. package/dist/tools/bash.d.ts.map +1 -1
  96. package/dist/tools/bash.js +3 -2
  97. package/dist/tools/bash.js.map +1 -1
  98. package/dist/tools/edit.d.ts +3 -3
  99. package/dist/tools/edit.d.ts.map +1 -1
  100. package/dist/tools/edit.js +5 -4
  101. package/dist/tools/edit.js.map +1 -1
  102. package/dist/tools/file-mutation-queue.d.ts +5 -1
  103. package/dist/tools/file-mutation-queue.d.ts.map +1 -1
  104. package/dist/tools/file-mutation-queue.js +39 -33
  105. package/dist/tools/file-mutation-queue.js.map +1 -1
  106. package/dist/tools/index.d.ts +18 -0
  107. package/dist/tools/index.d.ts.map +1 -1
  108. package/dist/tools/index.js +10 -0
  109. package/dist/tools/index.js.map +1 -1
  110. package/dist/tools/read.d.ts +3 -3
  111. package/dist/tools/read.d.ts.map +1 -1
  112. package/dist/tools/read.js +4 -3
  113. package/dist/tools/read.js.map +1 -1
  114. package/dist/tools/write.d.ts +1 -1
  115. package/dist/tools/write.d.ts.map +1 -1
  116. package/dist/tools/write.js +3 -2
  117. package/dist/tools/write.js.map +1 -1
  118. package/dist/types.d.ts +15 -4
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/types.js.map +1 -1
  121. package/package.json +25 -15
  122. package/dist/harness/config.d.ts +0 -29
  123. package/dist/harness/config.d.ts.map +0 -1
  124. package/dist/harness/config.js +0 -24
  125. package/dist/harness/config.js.map +0 -1
  126. package/docs/chord-delta-findings.md +0 -358
  127. package/docs/pico-v5-chord-usage.md +0 -373
  128. package/docs/pico-v5-handoff.md +0 -861
  129. package/docs/pico-v5.md +0 -4174
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [1.0.1] - 2026-10-03
4
+
5
+ ## [1.0.0] - 2026-10-01
6
+
7
+ ### Added
8
+
9
+ - Initial release of `@earendil-works/pi-durable`, a durable agent harness. See the [README](README.md) and the [design document](https://github.com/earendil-works/pi/blob/main/packages/durable/docs/spec.md).
package/README.md CHANGED
@@ -12,9 +12,13 @@ Built on [`@earendil-works/pi-ai`](../ai/README.md) for model access and `@earen
12
12
  - [Quick Start](#quick-start)
13
13
  - [Concepts](#concepts)
14
14
  - [Persist and Resume](#persist-and-resume)
15
+ - [Extensions](#extensions)
15
16
  - [Tools](#tools)
16
17
  - [System Prompt](#system-prompt)
17
- - [Conversation Settings](#conversation-settings)
18
+ - [Per-Conversation Agent](#per-conversation-agent)
19
+ - [Settings](#settings)
20
+ - [Environment](#environment)
21
+ - [Reload](#reload)
18
22
  - [Watching a Conversation](#watching-a-conversation)
19
23
  - [Busy Conversations](#busy-conversations)
20
24
  - [Reset and Handoff](#reset-and-handoff)
@@ -24,6 +28,7 @@ Built on [`@earendil-works/pi-ai`](../ai/README.md) for model access and `@earen
24
28
  - [More Conversations and Forks](#more-conversations-and-forks)
25
29
  - [Abort and Subagents](#abort-and-subagents)
26
30
  - [Child Tasks](#child-tasks)
31
+ - [Task Graph](#task-graph)
27
32
  - [Your Own State](#your-own-state)
28
33
  - [Usage and Cost](#usage-and-cost)
29
34
  - [Storage](#storage)
@@ -50,8 +55,7 @@ const models = createModels();
50
55
  models.setProvider(openaiProvider()); // reads OPENAI_API_KEY
51
56
 
52
57
  const harness = await Harness.open(new MemoryStorage(), { models, registry: createRegistry() }, context);
53
- const root = await harness.root(context);
54
- await root.setModel({ provider: "openai", modelId: "gpt-6-sol" }, context);
58
+ const root = await harness.root(context, { agent: { model: { provider: "openai", modelId: "gpt-6-sol" } } });
55
59
 
56
60
  const submission = await root.submit({ type: "input", content: "What is the capital of France?" }, context);
57
61
  const settled = await submission.wait(context);
@@ -65,7 +69,7 @@ await harness.close(context);
65
69
  What happened:
66
70
 
67
71
  - `Harness.open()` opens a Session over a storage backend. `MemoryStorage` keeps everything in memory.
68
- - `root()` returns the root conversation, creating it on first use. A conversation is a transcript of immutable entries.
72
+ - `root()` returns the root conversation, creating it on first use with the given agent choices. A conversation is a transcript of immutable entries.
69
73
  - `submit()` durably admits your input and returns a `Submission`. A built-in generation task calls the model and appends the answer.
70
74
  - `wait()` resolves once the input is answered (`done`) or has failed (`unanswered`, with a reason).
71
75
 
@@ -77,11 +81,13 @@ Every async call takes a Chord `Context`, which carries cancellation. `BACKGROUN
77
81
  - **Conversation**: a transcript. `root()` creates the root conversation on first use; you can create more and fork them. A `Conversation` handle holds no state; compare handles by `id`.
78
82
  - **Entry**: one immutable transcript record, such as a user message (`pi.user`), a model response (`pi.assistant`), a tool result (`pi.tool-result`), a system prompt change (`pi.system`), a reset (`pi.reset`), or your own kind. The model sees the entries from the newest reset onward.
79
83
  - **Commit**: an atomic write. `conversation.commit((tx) => ...)` can append entries, edit documents, and create tasks together; either all of it is stored or none of it.
80
- - **Document**: typed JSON state stored next to the transcript and changed in commits. Built-in ones hold each conversation's settings (`pi.conversation.config`), the running generation and tools (`pi.live`), queued submissions (`pi.inbox`), and spend (`pi.usage`).
84
+ - **Document**: typed JSON state stored next to the transcript and changed in commits. Built-in ones hold each conversation's agent choices (`pi.agent`), the running generation and tools (`pi.live`), queued submissions (`pi.inbox`), and spend (`pi.usage`).
81
85
  - **Task**: a durable state machine that saves a checkpoint at every step, so a restarted process continues from the last one. Every task has an owner: its conversation, or another task. The Harness runs answers as built-in tasks: `pi.generation` calls the model and owns the `pi.tool` tasks of its tool calls, waits for them, and hands the run to the next generation.
82
86
  - **Submission**: something you hand to a conversation, either user input or an entry to write, which you can wait for.
83
87
  - **Turn and run**: a turn is one model response and its tool calls; a run is the turns from an input to its final answer. A conversation is busy while a run is going.
84
- - **Registry**: your tools, system prompt sections, hooks, and tasks. It can change while the Harness runs; new work uses the new state.
88
+ - **Extension**: a named bundle of tools, system prompt sections, hooks, wrappers, and tasks.
89
+ - **Registry**: the extensions this process installed. It can change while the Harness runs; new work uses the new state.
90
+ - **Agent**: what a conversation runs with: model, thinking level, selected extensions, tools, instructions, and working directory. Stored per conversation as names in `pi.agent`, resolved against the registry at each use.
85
91
 
86
92
  One answered input, as entries and tasks:
87
93
 
@@ -115,65 +121,140 @@ const again = await root.submit({ type: "input", content: "Hello", requestId: "g
115
121
 
116
122
  `harness.submission(id)` reacquires a submission by ID, for example to wait for it after a restart.
117
123
 
118
- ## Tools
124
+ ## Extensions
119
125
 
120
- `@earendil-works/pi-durable/tools` provides `read`, `bash`, `edit`, and `write`. They touch files and processes only through the execution environment passed as `env`:
126
+ Code the Harness runs, other than its built-in tasks, comes in named extensions installed in a registry your process owns:
121
127
 
122
128
  ```typescript
123
- import { NodeExecutionEnv } from "@earendil-works/pi-durable/env/node";
124
- import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@earendil-works/pi-durable/tools";
129
+ import { createRegistry, defineExtension, defineTool, hook, section, ToolTask } from "@earendil-works/pi-durable";
130
+ import { CodingTools } from "@earendil-works/pi-durable/tools";
131
+
132
+ const Coding = defineExtension({
133
+ name: "coding",
134
+ sections: [section("preamble", () => "You are a concise coding assistant.", { tag: false })],
135
+ hooks: [hook(ToolTask, { beforeTool: (call) => (isDangerous(call) ? { block: "Needs approval" } : undefined) })],
136
+ });
125
137
 
126
138
  const registry = createRegistry();
127
- for (const tool of [createReadTool(), createBashTool(), createEditTool(), createWriteTool()]) registry.tools.add(tool);
128
- const env = new NodeExecutionEnv({ cwd: process.cwd() });
129
- const harness = await Harness.open(storage, { models, registry, env }, context);
139
+ registry.install(CodingTools);
140
+ registry.install(Coding);
130
141
  ```
131
142
 
132
- A new conversation activates every tool registered when it is created. Reading images is not supported yet.
143
+ An extension may bring `tools`, `sections`, `hooks`, `wraps` (decorators of a tool or section by name), and `tasks`. By default every conversation selects every installed extension, in install order. Nothing in the registry is stored; conversations store extension names.
144
+
145
+ ## Tools
146
+
147
+ `@earendil-works/pi-durable/tools` provides `read`, `write`, `edit`, and `bash`, and the `CodingTools` extension with all four. They touch files and processes only through the call's environment (see [Environment](#environment)). Reading images is not supported yet.
133
148
 
134
- Define your own tool with a TypeBox schema. `api.output()` streams running output, which becomes the result when `execute()` returns no `content`:
149
+ Define your own tool with a TypeBox schema. `defineTool()` types `args` from `parameters`, which the Harness validates before `execute()`. `api.output()` streams running output, which becomes the result when `execute()` returns no `content`:
135
150
 
136
151
  ```typescript
137
152
  import { Type } from "@earendil-works/pi-ai";
138
153
 
139
- registry.tools.add({
154
+ const count = defineTool({
140
155
  name: "count",
141
156
  description: "Count from 1 to n",
142
157
  parameters: Type.Object({ n: Type.Number() }),
143
158
  execute: async (args, api) => {
144
- for (let i = 1; i <= (args as { n: number }).n; i++) api.output(`${i}\n`);
159
+ for (let i = 1; i <= args.n; i++) api.output(`${i}\n`);
145
160
  return {};
146
161
  },
147
162
  });
163
+ registry.install(defineExtension({ name: "count", tools: [count] }));
148
164
  ```
149
165
 
150
166
  Each call runs as its own durable task. Its intent is committed before `execute()` runs. If the process dies mid-call, the tool reruns on reopen only when it is declared `replay: "safe"`; otherwise the model gets an `interrupted` error result with the output committed so far. Throwing from `execute()` gives the model an error result. A result can also return `usage`, which is added to the conversation's [usage](#usage-and-cost). It can also return `control: { terminate: true }`: when every result of the round asks for it, the run ends without another model request.
151
167
 
168
+ A later extension's tool with the same name replaces an earlier one where both are selected, and `wrapTool()` decorates whichever tool won:
169
+
170
+ ```typescript
171
+ const Venv = defineExtension({ name: "venv", tools: [createBashTool({ commandPrefix: "source .venv/bin/activate" })] });
172
+ const Timing = defineExtension({
173
+ name: "timing",
174
+ wraps: [wrapTool(createBashTool(), (bash) => ({ ...bash, execute: (args, api, ctx) => timed(() => bash.execute(args, api, ctx)) }))],
175
+ });
176
+ ```
177
+
152
178
  ## System Prompt
153
179
 
154
- The system prompt is built from named sections, rendered before each request:
180
+ The system prompt is built from the selected extensions' sections, rendered in order before each request. A section sees the resolved agent, the environment built for the request, and committed documents:
181
+
182
+ ```typescript
183
+ section("cwd", (input) => input.env?.cwd); // rendered as <cwd>\n...\n</cwd>; undefined omits it
184
+ ```
185
+
186
+ A conversation's `instructions` render last, as the section `instructions`. Sections and tool changes are stored as positional system entries in the transcript. Only what changed is sent again, which keeps provider prompt caches warm. A section that returns something different every time, such as the current time, defeats that.
187
+
188
+ ## Per-Conversation Agent
189
+
190
+ Each conversation stores what it runs with in its `pi.agent` document. `configure()` changes it in one commit; unset fields follow the host:
155
191
 
156
192
  ```typescript
157
- registry.systemPrompt.section("preamble", () => "You are a concise coding assistant.", { tag: false });
158
- registry.systemPrompt.section("cwd", () => process.cwd()); // rendered as <cwd>\n...\n</cwd>
193
+ await root.configure(
194
+ {
195
+ model: { provider: "openai", modelId: "gpt-6-sol" },
196
+ thinkingLevel: "high",
197
+ extensions: { remove: [Coding] }, // edits the host default; an array selects exactly these, in order
198
+ tools: [readTool, bashTool], // an array offers exactly these; { remove: [...] } drops some
199
+ instructions: "Only read; never edit files.",
200
+ cwd: "/work/repo",
201
+ },
202
+ context,
203
+ );
204
+ await root.configure({ tools: null }, context); // null clears a field back to the host default
205
+ const agent = await root.agent(context); // resolved: model, extensions, tools, sections, cwd
159
206
  ```
160
207
 
161
- Sections and tool changes are stored as positional system entries in the transcript. Only what changed is sent again, which keeps provider prompt caches warm. A section that returns something different every time, such as the current time, defeats that.
208
+ Extensions and tools are passed as objects and stored by name, so a stored name outlives its code: after an extension is uninstalled, conversations that select it just stop getting it until it is installed again. `createConversation()`, `fork()`, and `root()` take the same change as `agent`. A task-owned conversation, such as a subagent's, starts as a copy of its owner's conversation's agent. A fork starts with the agent its parent had at the fork entry. The model, prompt, and offered tools of a request are fixed when it is prepared; a change applies from the next request. Tool calls and hooks use the agent as their task phase resolves it, and the environment is built from the current `cwd` at each use, so a `cwd` or extension change can reach calls the model already made.
162
209
 
163
- ## Conversation Settings
210
+ ## Settings
164
211
 
165
- Each conversation has durable settings, readable and writable through the handle:
212
+ Run policy shared by every conversation is passed as `settings`. It is read at every use and never stored, so getters make it live, for example backed by a settings file:
166
213
 
167
214
  ```typescript
168
- await root.setModel({ provider: "openai", modelId: "gpt-6-sol" }, context);
169
- await root.setThinkingLevel("high", context);
170
- await root.setActiveTools(["read", "bash"], context);
171
- await root.setToolExecution("sequential", context); // default "parallel"
172
- await root.setRetryPolicy({ enabled: true, maxRetries: 3, baseDelayMs: 2000 }, context);
173
- await root.setStreamOptions({ timeoutMs: 120_000 }, context);
215
+ const harness = await Harness.open(storage, {
216
+ models,
217
+ registry,
218
+ settings: {
219
+ extensions: [CodingTools, Coding], // default selection; absent: every installed extension
220
+ stream: { timeoutMs: 120_000 },
221
+ retry: { maxRetries: 3 },
222
+ compaction: { reserveTokens: 16384 },
223
+ toolExecution: "parallel",
224
+ get followUpMode() {
225
+ return userSettings.followUpMode;
226
+ },
227
+ },
228
+ }, context);
174
229
  ```
175
230
 
176
- Changes apply when the next model request is prepared, not in the middle of one.
231
+ ## Environment
232
+
233
+ `env` builds the execution environment for each tool call, section rendering, and `runtime.env()`. It receives the conversation's ID, its agent `cwd`, and committed reads, so one function serves a directory per conversation or a container per conversation:
234
+
235
+ ```typescript
236
+ import { NodeExecutionEnv } from "@earendil-works/pi-durable/env/node";
237
+
238
+ const harness = await Harness.open(storage, {
239
+ models,
240
+ registry,
241
+ env: ({ cwd }) => new NodeExecutionEnv({ cwd: cwd ?? process.cwd() }),
242
+ }, context);
243
+ ```
244
+
245
+ A throw from `env` becomes the call's error result. Without an environment, the built-in tools fail with an error result. A fresh environment object per call is fine: `edit` and `write` serialize changes to one file by the environment's `id` and path. A custom `ExecutionEnv` sets `id` so that equal ids see the same files at the same paths, for example one id per container.
246
+
247
+ ## Reload
248
+
249
+ Installing an extension with an installed name replaces it in place, in one step:
250
+
251
+ ```typescript
252
+ registry.install(await loadCodingExtension()); // same name "coding": replaces the installed one
253
+ ```
254
+
255
+ `registry.uninstall(extension)` removes the installed extension with that name, whichever object it is.
256
+
257
+ Work that already started keeps the code it took: a running tool call finishes under its old implementation, and each task phase resolves hooks and the agent once, from the registry at the phase's start. The next phase, request, or call uses the new code. After a restart, install the same extensions again; pending tasks of an extension's `tasks` resume once it is installed.
177
258
 
178
259
  ## Watching a Conversation
179
260
 
@@ -184,7 +265,7 @@ const view = await root.viewState(context);
184
265
  view.subscribe((value) => {
185
266
  // value.entries: the active transcript
186
267
  // value.docs["pi.live"]: the running generation (streamed partial, retry, deferred) and tool calls (output, details)
187
- // value.docs["pi.inbox"], value.docs["pi.usage"], value.docs["pi.conversation.config"]
268
+ // value.docs["pi.inbox"], value.docs["pi.usage"], value.docs["pi.agent"]
188
269
  render(value);
189
270
  });
190
271
  // later: view.dispose();
@@ -220,7 +301,7 @@ await root.submit({ type: "write", entry: { kind: "app.note", data: "user opened
220
301
  - **Follow-ups** are placed when the run answers, and start the next run.
221
302
  - **Writes** append an entry without asking the model anything.
222
303
  - `await submission.abort(context)` withdraws a queued submission.
223
- - `setSteeringMode("all")` and `setFollowUpMode("all")` place every queued item at once instead of one per turn.
304
+ - The [settings](#settings) `steeringMode: "all"` and `followUpMode: "all"` place every queued item at once instead of one per turn.
224
305
 
225
306
  If a run fails, queued items stay in the inbox until the next submission places them, oldest first.
226
307
 
@@ -250,15 +331,17 @@ if (outcome.status === "completed" && outcome.result.submissionId !== undefined)
250
331
 
251
332
  The conversation keeps working while the summary is made. The summary is placed at once when the conversation is idle, otherwise at the next turn boundary. Esc (`abort()`) cancels a manual compaction.
252
333
 
253
- Generation also compacts on its own, controlled per conversation:
334
+ Generation also compacts on its own, controlled by the [settings](#settings):
254
335
 
255
336
  ```typescript
256
- await root.setCompaction({
257
- enabled: true, // automatic compaction; manual compact() always works
258
- reserveTokens: 16384, // above contextWindow - reserveTokens, the next request waits for a compaction
259
- keepRecentTokens: 20000, // roughly how much recent context stays verbatim
260
- backgroundTokens: 32768, // this far below that, a compaction starts in the background; 0 disables it
261
- }, context);
337
+ settings: {
338
+ compaction: {
339
+ enabled: true, // automatic compaction; manual compact() always works
340
+ reserveTokens: 16384, // above contextWindow - reserveTokens, the next request waits for a compaction
341
+ keepRecentTokens: 20000, // roughly how much recent context stays verbatim
342
+ backgroundTokens: 32768, // this far below that, a compaction starts in the background; 0 disables it
343
+ },
344
+ }
262
345
  ```
263
346
 
264
347
  When a provider rejects a request because the context is too long, generation compacts and retries once. A summary that would cut before the start of the current context settles as `stale` when it is placed, so when several are in flight, the furthest cut stays in effect. Summarization spend counts in `pi.usage`. A `beforeCompact` hook on `CompactionTask` can decline or supply its own summary.
@@ -273,7 +356,7 @@ For consumers that want coding-agent style events (`message_start`, `message_upd
273
356
  import { watchEvents } from "@earendil-works/pi-durable";
274
357
 
275
358
  const stream = await watchEvents(harness, root.id, context);
276
- initialize(stream.snapshot); // entries, run, in-flight generation, tools, compactions, inbox, config, usage
359
+ initialize(stream.snapshot); // entries, run, in-flight generation, tools, compactions, inbox, agent, usage
277
360
  stream.start(async (events) => {
278
361
  for (const event of events) console.log(JSON.stringify(event));
279
362
  });
@@ -283,23 +366,24 @@ Events are derived from commits, one batch per commit, and apply on top of the s
283
366
 
284
367
  ## Hooks
285
368
 
286
- Hooks let extensions observe or adjust the built-in tasks:
369
+ Hooks let extensions observe or adjust the built-in tasks, in the conversations that select them:
287
370
 
288
371
  ```typescript
289
- import { GenerationTask, ToolTask } from "@earendil-works/pi-durable";
290
-
291
- registry.hooks.add(ToolTask, {
292
- beforeTool: (call) => (call.name === "bash" ? { block: "bash is disabled here" } : undefined),
293
- });
294
- registry.hooks.add(GenerationTask, {
295
- onYield: (answer) => (needsMoreWork(answer) ? { continue: "Keep going." } : undefined),
372
+ import { GenerationTask, hook, ToolTask } from "@earendil-works/pi-durable";
373
+
374
+ const Guard = defineExtension({
375
+ name: "guard",
376
+ hooks: [
377
+ hook(ToolTask, { beforeTool: (call) => (call.name === "bash" ? { block: "bash is disabled here" } : undefined) }),
378
+ hook(GenerationTask, { onYield: (answer) => (needsMoreWork(answer) ? { continue: "Keep going." } : undefined) }),
379
+ ],
296
380
  });
297
381
  ```
298
382
 
299
383
  - **Generation:** `beforeRequest` (replace the messages of one request), `afterResponse`, `onYield` (continue the run with another user message), and `afterTools` (runs once a round's tools are done).
300
384
  - **Tools:** `beforeTool` (block or rewrite arguments) and `afterTool` (replace the result).
301
385
 
302
- Pass `{ scope: { conversationId } }` to limit a hook to one conversation.
386
+ To limit a hook to some conversations, select its extension only there, for example with `configure({ extensions: { add: [Guard] } })`.
303
387
 
304
388
  ## More Conversations and Forks
305
389
 
@@ -308,7 +392,7 @@ const other = await harness.createConversation({ ownership: { kind: "ownerless"
308
392
  const fork = await root.fork(entryId, { ownership: { kind: "ownerless" } }, context);
309
393
  ```
310
394
 
311
- A fork sees its parent's entries up to `entryId` and continues independently. It keeps the parent's settings as of that entry.
395
+ A fork sees its parent's entries up to `entryId` and continues independently. It keeps the parent's agent as of that entry. Both take `agent` and `init`, applied in the creating commit.
312
396
 
313
397
  ## Abort and Subagents
314
398
 
@@ -317,25 +401,32 @@ A fork sees its parent's entries up to `entryId` and continues independently. It
317
401
  A conversation can be **owned** by a task. A subagent tool creates its child inside `api.commit()` with `ownership: { kind: "task", taskId: api.taskId }`, then drives it through `api.conversation(id)`:
318
402
 
319
403
  ```typescript
320
- registry.tools.add({
404
+ const Subagent: Extension = defineExtension({
321
405
  name: "subagent",
322
- description: "Delegate a self-contained task to a subagent and get its answer back.",
323
- parameters: Type.Object({ task: Type.String() }),
324
- replay: "safe", // a rerun after a crash finds the same child and submission
325
- execute: async (args, api, context) => {
326
- const child = await api.commit(async (tx) => {
327
- // The ownership index remembers the child, so a rerun reuses it.
328
- const existing = (await tx.scanConversations({ ownerTaskId: api.taskId }, 1)).items[0];
329
- if (existing !== undefined) return existing.id;
330
- const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
331
- (await tx.doc(ConversationConfig, created.id)).model = { provider: "openai", modelId: "gpt-6-sol" };
332
- return created.id;
333
- }, context);
334
- await api.details({ conversationId: child }, context); // lets a UI attach to the child
335
- const request = { type: "input", content: (args as { task: string }).task, requestId: `subagent:${api.taskId}` } as const;
336
- const settled = await (await (await api.conversation(child, context))!.submit(request, context)).wait(context);
337
- return { content: [{ type: "text", text: settled.status }] };
338
- },
406
+ tools: [
407
+ defineTool({
408
+ name: "subagent",
409
+ description: "Delegate a self-contained task to a subagent and get its answer back.",
410
+ parameters: Type.Object({ task: Type.String() }),
411
+ replay: "safe", // a rerun after a crash finds the same child and submission
412
+ execute: async (args, api, context) => {
413
+ const child = await api.commit(async (tx) => {
414
+ // The ownership index remembers the child, so a rerun reuses it.
415
+ const existing = (await tx.scanConversations({ ownerTaskId: api.taskId }, 1)).items[0];
416
+ if (existing !== undefined) return existing.id;
417
+ // Starts as a copy of this conversation's agent: model, extensions, tools, cwd.
418
+ const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
419
+ // A cheaper model, and no subagents of its own.
420
+ await configure(tx, created.id, { model: haiku, extensions: { remove: [Subagent] } });
421
+ return created.id;
422
+ }, context);
423
+ await api.details({ conversationId: child }, context); // lets a UI attach to the child
424
+ const request = { type: "input", content: args.task, requestId: `subagent:${api.taskId}` } as const;
425
+ const settled = await (await (await api.conversation(child, context))!.submit(request, context)).wait(context);
426
+ return { content: [{ type: "text", text: settled.status }] };
427
+ },
428
+ }),
429
+ ],
339
430
  });
340
431
  ```
341
432
 
@@ -377,6 +468,22 @@ decide: async (task, runtime, context) => {
377
468
 
378
469
  [`24-child-tasks.ts`](test/examples/24-child-tasks.ts) runs a checkout with four payments: a declined card, a cancelled checkout, and a restart while the payments run.
379
470
 
471
+ ## Task Graph
472
+
473
+ `harness.taskGraph(context)` shows every live task of the Session as one Chord state, for a task panel or debugging. Each node has its owner edge (`owner` task, or none for a task its conversation owns), its status, whether it is `background` or abort-marked, and the conversations it owns. `harness.watchTaskGraph(context)` delivers the same value as a watch, like a conversation's `watch()`.
474
+
475
+ ```typescript
476
+ const graph = await harness.taskGraph(context);
477
+ graph.subscribe((value) => {
478
+ for (const node of Object.values(value.tasks)) {
479
+ const status = node.state.status === "waiting" ? `waiting on ${node.state.on.join(", ")}` : node.state.status;
480
+ console.log(`${node.id} ${node.kind} ${status}`, node.owner ?? `conversation ${node.conversationId}`);
481
+ }
482
+ });
483
+ ```
484
+
485
+ A task appears with the commit that creates it and leaves with the commit that makes it terminal. Statuses are the committed ones: `pending`, `running`, `waiting` (with `on` and `policy`), and `completing` (with the held outcome's status). After a restart, tasks that were `running` show as `pending` until they run again. Whether a pending task is blocked by a missing definition is not part of the graph; `harness.inspect()` reports that. The graph lists live tasks only: once a subagent's owner task is terminal, a later task in its conversation is a top-level node, and the conversation's `ConversationRecord.owner` (also in its view's `conversation`) links it to its parent. [`24-child-tasks.ts`](test/examples/24-child-tasks.ts) prints the checkout's tree while its payments run.
486
+
380
487
  ## Your Own State
381
488
 
382
489
  Documents are typed JSON objects committed together with entries. Define one, and edit it in a commit:
@@ -399,7 +506,7 @@ await root.commit(async (tx) => {
399
506
  console.log(await harness.snapshot(Todos, root.id, context));
400
507
  ```
401
508
 
402
- `harness.watchDoc()` and `harness.documentState()` observe one document like the view above. `registry.conversations.setup()` creates your documents in every new conversation, and a tool reads them through its `api`.
509
+ `harness.watchDoc()` and `harness.documentState()` observe one document like the view above. `HarnessOptions.conversationCreated(tx, conversation)` runs in every commit that creates or forks a conversation, including a tool's raw `tx.createConversation()`, so every conversation gets your documents; `init` in `createConversation()`, `fork()`, and `root()` writes per-call data in the same commit. An extension's tools, sections, and hooks read their own documents through `api` or `input.read`, and treat an absent one as its default ([`11-extension-state.ts`](test/examples/11-extension-state.ts)).
403
510
 
404
511
  ## Usage and Cost
405
512
 
@@ -417,7 +524,16 @@ const usage = await harness.usage(context); // { models: { "openai/gpt-6-sol": U
417
524
  | SQLite | `openNodeSqliteStorage(file)` from `@earendil-works/pi-durable/storage/sqlite/node` | One database file. WAL mode with `synchronous = NORMAL`: commits survive process crashes; the newest may be lost on power or host failure. |
418
525
  | JSONL | `openNodeJsonlStorage(directory, context)` from `@earendil-works/pi-durable/storage/jsonl/node` | Append-only files in one directory. Pass `{ fsync: true }` to flush before each commit marker. |
419
526
 
420
- One process owns a storage at a time; there is no cross-process locking. The portable SQLite and JSONL cores (`/storage/sqlite`, `/storage/jsonl`) run without Node APIs, for example on Bun or in Cloudflare Durable Objects, given a synchronous SQLite database or a `FileSystem` from `@earendil-works/pi-durable/env`.
527
+ One process owns a storage at a time; there is no cross-process locking. The portable SQLite and JSONL cores (`/storage/sqlite`, `/storage/jsonl`) run without Node APIs, for example on Bun or in Cloudflare Durable Objects, given an asynchronous `SqliteDatabase` facade or a `FileSystem` from `@earendil-works/pi-durable/env`.
528
+
529
+ SQLite adapters implement promise-based `exec`, `run`, `get`, `all`, `transaction`, and `close`. `run`, `get`, and `all` take SQL text plus positional bindings; adapters may cache prepared statements by SQL text. A transaction callback receives a transaction handle; all work in the transaction must use it, and the handle expires when the callback settles. Adapters must queue unrelated operations and other transactions until the transaction finishes, so calling `database` itself inside the callback never settles:
530
+
531
+ ```typescript
532
+ await database.transaction(async (transaction) => {
533
+ await transaction.exec("CREATE TABLE example (value TEXT)");
534
+ await transaction.run("INSERT INTO example (value) VALUES (?)", "stored atomically");
535
+ });
536
+ ```
421
537
 
422
538
  Custom backends can run the shared conformance suite with any Vitest- or Jest-compatible runner:
423
539
 
@@ -458,15 +574,21 @@ node --conditions=source --experimental-strip-types test/examples/14-chat.ts
458
574
  | [23-subagent-background](test/examples/23-subagent-background.ts) | Persistent subagents: spawn, steer, stop, list, answers reported back, restart-safe |
459
575
  | [24-child-tasks](test/examples/24-child-tasks.ts) | A checkout that owns and waits for four payments: failFast, abort, restart |
460
576
  | [25-compaction](test/examples/25-compaction.ts) | A long chat compacted in the background, manually, and after a context overflow |
461
- | [00](test/examples/00-conversation.ts)–[13](test/examples/13-recovery.ts) | The layers underneath: sessions, documents, forks, watches, tasks, recovery |
577
+ | [26-coding-agent](test/examples/26-coding-agent.ts) | CodingTools, live settings from a settings object, an environment that follows the conversation's directory |
578
+ | [27-plan-mode](test/examples/27-plan-mode.ts) | A read-only plan mode as an extension with its own document, switched with `configure()` |
579
+ | [28-reviewer](test/examples/28-reviewer.ts) | A reviewer conversation with its own model, extensions, tools, directory, and review loop |
580
+ | [29-sandbox-per-conversation](test/examples/29-sandbox-per-conversation.ts) | An environment per conversation, looked up from an app document |
581
+ | [30-tool-override](test/examples/30-tool-override.ts) | A same-name bash for some conversations, and a wrapper that times whichever bash won |
582
+ | [31-reload-and-restart](test/examples/31-reload-and-restart.ts) | Reloading an extension mid-call, and stored choices surviving a restart |
583
+ | [00](test/examples/00-conversation.ts)–[13](test/examples/13-recovery.ts) | The layers underneath: sessions, documents, forks, watches, the Harness, agent configuration, reload, extension state, tasks, recovery |
462
584
 
463
585
  Examples that call OpenAI need `OPENAI_API_KEY`; most use the faux provider otherwise.
464
586
 
465
587
  ## Design Documents
466
588
 
467
- - [`docs/pico-v5.md`](docs/pico-v5.md): the normative specification
468
- - [`docs/pico-v5-handoff.md`](docs/pico-v5-handoff.md): the implementation plan
469
- - [`docs/pico-v5-chord-usage.md`](docs/pico-v5-chord-usage.md): how the package uses Chord
589
+ - [`docs/spec.md`](https://github.com/earendil-works/pi/blob/main/packages/durable/docs/spec.md): the normative specification
590
+ - [`docs/pico-v5-handoff.md`](https://github.com/earendil-works/pi/blob/main/packages/durable/docs/pico-v5-handoff.md): the implementation plan
591
+ - [`docs/pico-v5-chord-usage.md`](https://github.com/earendil-works/pi/blob/main/packages/durable/docs/pico-v5-chord-usage.md): how the package uses Chord
470
592
 
471
593
  Benchmarks: `npm run bench:storage`, `npm run bench:storage:memory`, and `npm run bench:tool-output`.
472
594
 
@@ -43,6 +43,11 @@ export interface TextLineReader {
43
43
  }
44
44
  /** Portable filesystem capability. Operations return failures rather than throwing. */
45
45
  export interface FileSystem {
46
+ /**
47
+ * The file namespace: equal ids see the same files at the same paths, whatever their `cwd`. Every local Node
48
+ * environment shares one id; each container or remote host has its own.
49
+ */
50
+ readonly id: string;
46
51
  cwd: string;
47
52
  absolutePath(path: string, context: Context): Promise<Result<string, FileError>>;
48
53
  joinPath(parts: string[], context: Context): Promise<Result<string, FileError>>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,EAAE,CAAiB,KAAa;IAC/C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,GAAG,CAAiB,KAAa;IAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAiB,MAA8B;IACxE,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,MAAM,CAAC,KAAK,CAAC;IACnC,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,cAAc,CAAgC,MAA8B;IAC3F,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACrC,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;IACvD,IAAI,CAAC;QACJ,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACjC,CAAC;AACF,CAAC;AAcD,MAAM,OAAO,SAAU,SAAQ,KAAK;IAC5B,IAAI,CAAgB;IACpB,IAAI,CAAU;IAErB,YAAY,IAAmB,EAAE,OAAe,EAAE,IAAa,EAAE,KAAa;QAC7E,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAUD,MAAM,OAAO,cAAe,SAAQ,KAAK;IACjC,IAAI,CAAqB;IAChC,2GAA2G;IACpG,SAAS,CAAU;IAE1B,YAAY,IAAwB,EAAE,OAAe,EAAE,KAAa;QACnE,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\n\n/** Result of a fallible operation. Expected failures are returned instead of thrown. */\nexport type Result<TValue, TError> = { ok: true; value: TValue } | { ok: false; error: TError };\n\nexport function ok<TValue, TError>(value: TValue): Result<TValue, TError> {\n\treturn { ok: true, value };\n}\n\nexport function err<TValue, TError>(error: TError): Result<TValue, TError> {\n\treturn { ok: false, error };\n}\n\nexport function getOrThrow<TValue, TError>(result: Result<TValue, TError>): TValue {\n\tif (!result.ok) throw result.error;\n\treturn result.value;\n}\n\nexport function getOrUndefined<TValue extends object, TError>(result: Result<TValue, TError>): TValue | undefined {\n\treturn result.ok ? result.value : undefined;\n}\n\nexport function toError(error: unknown): Error {\n\tif (error instanceof Error) return error;\n\tif (typeof error === \"string\") return new Error(error);\n\ttry {\n\t\treturn new Error(JSON.stringify(error));\n\t} catch {\n\t\treturn new Error(String(error));\n\t}\n}\n\nexport type FileKind = \"file\" | \"directory\" | \"symlink\";\n\nexport type FileErrorCode =\n\t| \"aborted\"\n\t| \"not_found\"\n\t| \"permission_denied\"\n\t| \"not_directory\"\n\t| \"is_directory\"\n\t| \"invalid\"\n\t| \"not_supported\"\n\t| \"unknown\";\n\nexport class FileError extends Error {\n\tpublic code: FileErrorCode;\n\tpublic path?: string;\n\n\tconstructor(code: FileErrorCode, message: string, path?: string, cause?: Error) {\n\t\tsuper(message, cause === undefined ? undefined : { cause });\n\t\tthis.name = \"FileError\";\n\t\tthis.code = code;\n\t\tthis.path = path;\n\t}\n}\n\nexport type ExecutionErrorCode =\n\t| \"aborted\"\n\t| \"timeout\"\n\t| \"shell_unavailable\"\n\t| \"spawn_error\"\n\t| \"callback_error\"\n\t| \"unknown\";\n\nexport class ExecutionError extends Error {\n\tpublic code: ExecutionErrorCode;\n\t/** Spill file of a command that timed out or was aborted after its output crossed the spill thresholds. */\n\tpublic spillPath?: string;\n\n\tconstructor(code: ExecutionErrorCode, message: string, cause?: Error) {\n\t\tsuper(message, cause === undefined ? undefined : { cause });\n\t\tthis.name = \"ExecutionError\";\n\t\tthis.code = code;\n\t}\n}\n\nexport interface FileInfo {\n\tname: string;\n\tpath: string;\n\tkind: FileKind;\n\tsize: number;\n\tmtimeMs: number;\n}\n\nexport interface TextLine {\n\ttext: string;\n\tterminated: boolean;\n}\n\nexport interface TextLineReader {\n\treadLine(context: Context): Promise<Result<TextLine | undefined, FileError>>;\n\tclose(context: Context): Promise<void>;\n}\n\n/** Portable filesystem capability. Operations return failures rather than throwing. */\nexport interface FileSystem {\n\tcwd: string;\n\tabsolutePath(path: string, context: Context): Promise<Result<string, FileError>>;\n\tjoinPath(parts: string[], context: Context): Promise<Result<string, FileError>>;\n\treadTextFile(path: string, context: Context): Promise<Result<string, FileError>>;\n\topenTextLineReader(path: string, context: Context): Promise<Result<TextLineReader, FileError>>;\n\treadTextLines(\n\t\tpath: string,\n\t\toptions: { maxLines?: number } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<string[], FileError>>;\n\treadBinaryFile(path: string, context: Context): Promise<Result<Uint8Array, FileError>>;\n\twriteFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;\n\tappendFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;\n\t/** Truncate or extend a file to exactly `size` bytes. */\n\ttruncateFile(path: string, size: number, context: Context): Promise<Result<void, FileError>>;\n\t/** Flush file contents and metadata needed to retrieve them from an open file handle. */\n\tflushFile(path: string, context: Context): Promise<Result<void, FileError>>;\n\trenameFile(sourcePath: string, destinationPath: string, context: Context): Promise<Result<void, FileError>>;\n\tfileInfo(path: string, context: Context): Promise<Result<FileInfo, FileError>>;\n\tlistDir(path: string, context: Context): Promise<Result<FileInfo[], FileError>>;\n\tcanonicalPath(path: string, context: Context): Promise<Result<string, FileError>>;\n\texists(path: string, context: Context): Promise<Result<boolean, FileError>>;\n\tcreateDir(\n\t\tpath: string,\n\t\toptions: { recursive?: boolean } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<void, FileError>>;\n\tremove(\n\t\tpath: string,\n\t\toptions: { recursive?: boolean; force?: boolean } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<void, FileError>>;\n\tcreateTempDir(prefix: string | undefined, context: Context): Promise<Result<string, FileError>>;\n\tcreateTempFile(\n\t\toptions: { prefix?: string; suffix?: string } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<string, FileError>>;\n\tcleanup(context: Context): Promise<void>;\n}\n\n/** Spill the complete output to a temporary file once it exceeds either threshold. */\nexport interface ShellSpillOptions {\n\tafterBytes: number;\n\t/** Complete or partial lines. */\n\tafterLines: number;\n}\n\nexport interface ShellExecResult {\n\texitCode: number;\n\t/** Temporary file holding the complete raw output, when the spill thresholds were exceeded. */\n\tspillPath?: string;\n}\n\nexport interface ShellExecOptions {\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tinheritEnv?: boolean;\n\ttimeout?: number;\n\t/** Every decoded chunk of combined stdout and stderr as it arrives: raw, unbounded, and unthrottled. */\n\tonOutput?: (text: string, context: Context) => void;\n\tspill?: ShellSpillOptions;\n}\n\nexport interface Shell {\n\texec(\n\t\tcommand: string,\n\t\toptions: ShellExecOptions | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<ShellExecResult, ExecutionError>>;\n\tcleanup(context: Context): Promise<void>;\n}\n\nexport interface ExecutionEnv extends FileSystem, Shell {}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,EAAE,CAAiB,KAAa;IAC/C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,GAAG,CAAiB,KAAa;IAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAiB,MAA8B;IACxE,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,MAAM,CAAC,KAAK,CAAC;IACnC,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,cAAc,CAAgC,MAA8B;IAC3F,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACrC,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;IACvD,IAAI,CAAC;QACJ,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACjC,CAAC;AACF,CAAC;AAcD,MAAM,OAAO,SAAU,SAAQ,KAAK;IAC5B,IAAI,CAAgB;IACpB,IAAI,CAAU;IAErB,YAAY,IAAmB,EAAE,OAAe,EAAE,IAAa,EAAE,KAAa;QAC7E,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAUD,MAAM,OAAO,cAAe,SAAQ,KAAK;IACjC,IAAI,CAAqB;IAChC,2GAA2G;IACpG,SAAS,CAAU;IAE1B,YAAY,IAAwB,EAAE,OAAe,EAAE,KAAa;QACnE,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\n\n/** Result of a fallible operation. Expected failures are returned instead of thrown. */\nexport type Result<TValue, TError> = { ok: true; value: TValue } | { ok: false; error: TError };\n\nexport function ok<TValue, TError>(value: TValue): Result<TValue, TError> {\n\treturn { ok: true, value };\n}\n\nexport function err<TValue, TError>(error: TError): Result<TValue, TError> {\n\treturn { ok: false, error };\n}\n\nexport function getOrThrow<TValue, TError>(result: Result<TValue, TError>): TValue {\n\tif (!result.ok) throw result.error;\n\treturn result.value;\n}\n\nexport function getOrUndefined<TValue extends object, TError>(result: Result<TValue, TError>): TValue | undefined {\n\treturn result.ok ? result.value : undefined;\n}\n\nexport function toError(error: unknown): Error {\n\tif (error instanceof Error) return error;\n\tif (typeof error === \"string\") return new Error(error);\n\ttry {\n\t\treturn new Error(JSON.stringify(error));\n\t} catch {\n\t\treturn new Error(String(error));\n\t}\n}\n\nexport type FileKind = \"file\" | \"directory\" | \"symlink\";\n\nexport type FileErrorCode =\n\t| \"aborted\"\n\t| \"not_found\"\n\t| \"permission_denied\"\n\t| \"not_directory\"\n\t| \"is_directory\"\n\t| \"invalid\"\n\t| \"not_supported\"\n\t| \"unknown\";\n\nexport class FileError extends Error {\n\tpublic code: FileErrorCode;\n\tpublic path?: string;\n\n\tconstructor(code: FileErrorCode, message: string, path?: string, cause?: Error) {\n\t\tsuper(message, cause === undefined ? undefined : { cause });\n\t\tthis.name = \"FileError\";\n\t\tthis.code = code;\n\t\tthis.path = path;\n\t}\n}\n\nexport type ExecutionErrorCode =\n\t| \"aborted\"\n\t| \"timeout\"\n\t| \"shell_unavailable\"\n\t| \"spawn_error\"\n\t| \"callback_error\"\n\t| \"unknown\";\n\nexport class ExecutionError extends Error {\n\tpublic code: ExecutionErrorCode;\n\t/** Spill file of a command that timed out or was aborted after its output crossed the spill thresholds. */\n\tpublic spillPath?: string;\n\n\tconstructor(code: ExecutionErrorCode, message: string, cause?: Error) {\n\t\tsuper(message, cause === undefined ? undefined : { cause });\n\t\tthis.name = \"ExecutionError\";\n\t\tthis.code = code;\n\t}\n}\n\nexport interface FileInfo {\n\tname: string;\n\tpath: string;\n\tkind: FileKind;\n\tsize: number;\n\tmtimeMs: number;\n}\n\nexport interface TextLine {\n\ttext: string;\n\tterminated: boolean;\n}\n\nexport interface TextLineReader {\n\treadLine(context: Context): Promise<Result<TextLine | undefined, FileError>>;\n\tclose(context: Context): Promise<void>;\n}\n\n/** Portable filesystem capability. Operations return failures rather than throwing. */\nexport interface FileSystem {\n\t/**\n\t * The file namespace: equal ids see the same files at the same paths, whatever their `cwd`. Every local Node\n\t * environment shares one id; each container or remote host has its own.\n\t */\n\treadonly id: string;\n\tcwd: string;\n\tabsolutePath(path: string, context: Context): Promise<Result<string, FileError>>;\n\tjoinPath(parts: string[], context: Context): Promise<Result<string, FileError>>;\n\treadTextFile(path: string, context: Context): Promise<Result<string, FileError>>;\n\topenTextLineReader(path: string, context: Context): Promise<Result<TextLineReader, FileError>>;\n\treadTextLines(\n\t\tpath: string,\n\t\toptions: { maxLines?: number } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<string[], FileError>>;\n\treadBinaryFile(path: string, context: Context): Promise<Result<Uint8Array, FileError>>;\n\twriteFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;\n\tappendFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;\n\t/** Truncate or extend a file to exactly `size` bytes. */\n\ttruncateFile(path: string, size: number, context: Context): Promise<Result<void, FileError>>;\n\t/** Flush file contents and metadata needed to retrieve them from an open file handle. */\n\tflushFile(path: string, context: Context): Promise<Result<void, FileError>>;\n\trenameFile(sourcePath: string, destinationPath: string, context: Context): Promise<Result<void, FileError>>;\n\tfileInfo(path: string, context: Context): Promise<Result<FileInfo, FileError>>;\n\tlistDir(path: string, context: Context): Promise<Result<FileInfo[], FileError>>;\n\tcanonicalPath(path: string, context: Context): Promise<Result<string, FileError>>;\n\texists(path: string, context: Context): Promise<Result<boolean, FileError>>;\n\tcreateDir(\n\t\tpath: string,\n\t\toptions: { recursive?: boolean } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<void, FileError>>;\n\tremove(\n\t\tpath: string,\n\t\toptions: { recursive?: boolean; force?: boolean } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<void, FileError>>;\n\tcreateTempDir(prefix: string | undefined, context: Context): Promise<Result<string, FileError>>;\n\tcreateTempFile(\n\t\toptions: { prefix?: string; suffix?: string } | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<string, FileError>>;\n\tcleanup(context: Context): Promise<void>;\n}\n\n/** Spill the complete output to a temporary file once it exceeds either threshold. */\nexport interface ShellSpillOptions {\n\tafterBytes: number;\n\t/** Complete or partial lines. */\n\tafterLines: number;\n}\n\nexport interface ShellExecResult {\n\texitCode: number;\n\t/** Temporary file holding the complete raw output, when the spill thresholds were exceeded. */\n\tspillPath?: string;\n}\n\nexport interface ShellExecOptions {\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tinheritEnv?: boolean;\n\ttimeout?: number;\n\t/** Every decoded chunk of combined stdout and stderr as it arrives: raw, unbounded, and unthrottled. */\n\tonOutput?: (text: string, context: Context) => void;\n\tspill?: ShellSpillOptions;\n}\n\nexport interface Shell {\n\texec(\n\t\tcommand: string,\n\t\toptions: ShellExecOptions | undefined,\n\t\tcontext: Context,\n\t): Promise<Result<ShellExecResult, ExecutionError>>;\n\tcleanup(context: Context): Promise<void>;\n}\n\nexport interface ExecutionEnv extends FileSystem, Shell {}\n"]}
@@ -1,6 +1,8 @@
1
1
  import type { Context } from "@earendil-works/chord";
2
2
  import { type ExecutionEnv, ExecutionError, FileError, type FileInfo, type Result, type ShellExecOptions, type ShellExecResult, type TextLineReader } from "./index.ts";
3
3
  export declare class NodeExecutionEnv implements ExecutionEnv {
4
+ /** Every local environment sees the same files. */
5
+ readonly id: string;
4
6
  cwd: string;
5
7
  private shellPath?;
6
8
  private shellEnv?;
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/env/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/env/node.ts"],"names":[],"mappings":"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"}
package/dist/env/node.js CHANGED
@@ -378,6 +378,8 @@ class NodeTextLineReader {
378
378
  }
379
379
  }
380
380
  export class NodeExecutionEnv {
381
+ /** Every local environment sees the same files. */
382
+ id = "node:local";
381
383
  cwd;
382
384
  shellPath;
383
385
  shellEnv;