@earendil-works/pi-durable 0.99.1 → 1.0.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 (203) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +557 -46
  3. package/dist/entries.d.ts +20 -2
  4. package/dist/entries.d.ts.map +1 -1
  5. package/dist/entries.js +14 -1
  6. package/dist/entries.js.map +1 -1
  7. package/dist/env/index.d.ts +18 -38
  8. package/dist/env/index.d.ts.map +1 -1
  9. package/dist/env/index.js +2 -0
  10. package/dist/env/index.js.map +1 -1
  11. package/dist/env/node.d.ts +2 -0
  12. package/dist/env/node.d.ts.map +1 -1
  13. package/dist/env/node.js +53 -58
  14. package/dist/env/node.js.map +1 -1
  15. package/dist/harness/agent.d.ts +31 -0
  16. package/dist/harness/agent.d.ts.map +1 -0
  17. package/dist/harness/agent.js +209 -0
  18. package/dist/harness/agent.js.map +1 -0
  19. package/dist/harness/compaction.d.ts +56 -0
  20. package/dist/harness/compaction.d.ts.map +1 -0
  21. package/dist/harness/compaction.js +382 -0
  22. package/dist/harness/compaction.js.map +1 -0
  23. package/dist/harness/context.d.ts +8 -0
  24. package/dist/harness/context.d.ts.map +1 -1
  25. package/dist/harness/context.js +32 -21
  26. package/dist/harness/context.js.map +1 -1
  27. package/dist/harness/define.d.ts +18 -0
  28. package/dist/harness/define.d.ts.map +1 -0
  29. package/dist/harness/define.js +25 -0
  30. package/dist/harness/define.js.map +1 -0
  31. package/dist/harness/events.d.ts +166 -0
  32. package/dist/harness/events.d.ts.map +1 -0
  33. package/dist/harness/events.js +319 -0
  34. package/dist/harness/events.js.map +1 -0
  35. package/dist/harness/generation.d.ts +36 -7
  36. package/dist/harness/generation.d.ts.map +1 -1
  37. package/dist/harness/generation.js +356 -82
  38. package/dist/harness/generation.js.map +1 -1
  39. package/dist/harness/harness.d.ts.map +1 -1
  40. package/dist/harness/harness.js +141 -106
  41. package/dist/harness/harness.js.map +1 -1
  42. package/dist/harness/inbox.d.ts +59 -0
  43. package/dist/harness/inbox.d.ts.map +1 -0
  44. package/dist/harness/inbox.js +97 -0
  45. package/dist/harness/inbox.js.map +1 -0
  46. package/dist/harness/json.d.ts +10 -0
  47. package/dist/harness/json.d.ts.map +1 -0
  48. package/dist/harness/json.js +33 -0
  49. package/dist/harness/json.js.map +1 -0
  50. package/dist/harness/live.d.ts +66 -7
  51. package/dist/harness/live.d.ts.map +1 -1
  52. package/dist/harness/live.js +71 -7
  53. package/dist/harness/live.js.map +1 -1
  54. package/dist/harness/output.d.ts +62 -0
  55. package/dist/harness/output.d.ts.map +1 -0
  56. package/dist/harness/output.js +261 -0
  57. package/dist/harness/output.js.map +1 -0
  58. package/dist/harness/prompt.d.ts +9 -6
  59. package/dist/harness/prompt.d.ts.map +1 -1
  60. package/dist/harness/prompt.js +57 -14
  61. package/dist/harness/prompt.js.map +1 -1
  62. package/dist/harness/registry.d.ts +2 -3
  63. package/dist/harness/registry.d.ts.map +1 -1
  64. package/dist/harness/registry.js +69 -277
  65. package/dist/harness/registry.js.map +1 -1
  66. package/dist/harness/scheduler.d.ts +42 -12
  67. package/dist/harness/scheduler.d.ts.map +1 -1
  68. package/dist/harness/scheduler.js +649 -75
  69. package/dist/harness/scheduler.js.map +1 -1
  70. package/dist/harness/submissions.d.ts +14 -9
  71. package/dist/harness/submissions.d.ts.map +1 -1
  72. package/dist/harness/submissions.js +76 -34
  73. package/dist/harness/submissions.js.map +1 -1
  74. package/dist/harness/task-graph.d.ts +49 -0
  75. package/dist/harness/task-graph.d.ts.map +1 -0
  76. package/dist/harness/task-graph.js +163 -0
  77. package/dist/harness/task-graph.js.map +1 -0
  78. package/dist/harness/tool.d.ts +36 -0
  79. package/dist/harness/tool.d.ts.map +1 -0
  80. package/dist/harness/tool.js +415 -0
  81. package/dist/harness/tool.js.map +1 -0
  82. package/dist/harness/types.d.ts +330 -125
  83. package/dist/harness/types.d.ts.map +1 -1
  84. package/dist/harness/types.js.map +1 -1
  85. package/dist/harness/usage.d.ts +18 -0
  86. package/dist/harness/usage.d.ts.map +1 -0
  87. package/dist/harness/usage.js +57 -0
  88. package/dist/harness/usage.js.map +1 -0
  89. package/dist/harness/view.d.ts +43 -0
  90. package/dist/harness/view.d.ts.map +1 -0
  91. package/dist/harness/view.js +189 -0
  92. package/dist/harness/view.js.map +1 -0
  93. package/dist/index.d.ts +13 -5
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +8 -2
  96. package/dist/index.js.map +1 -1
  97. package/dist/session/observation.d.ts +18 -11
  98. package/dist/session/observation.d.ts.map +1 -1
  99. package/dist/session/observation.js +24 -13
  100. package/dist/session/observation.js.map +1 -1
  101. package/dist/session/session.d.ts +10 -1
  102. package/dist/session/session.d.ts.map +1 -1
  103. package/dist/session/session.js +23 -5
  104. package/dist/session/session.js.map +1 -1
  105. package/dist/session/transaction.d.ts +12 -3
  106. package/dist/session/transaction.d.ts.map +1 -1
  107. package/dist/session/transaction.js +90 -31
  108. package/dist/session/transaction.js.map +1 -1
  109. package/dist/storage/jsonl/storage.d.ts +22 -2
  110. package/dist/storage/jsonl/storage.d.ts.map +1 -1
  111. package/dist/storage/jsonl/storage.js +1 -1
  112. package/dist/storage/jsonl/storage.js.map +1 -1
  113. package/dist/storage/memory.js +1 -1
  114. package/dist/storage/memory.js.map +1 -1
  115. package/dist/storage/sqlite/database.d.ts +26 -19
  116. package/dist/storage/sqlite/database.d.ts.map +1 -1
  117. package/dist/storage/sqlite/database.js.map +1 -1
  118. package/dist/storage/sqlite/index.d.ts +1 -1
  119. package/dist/storage/sqlite/index.d.ts.map +1 -1
  120. package/dist/storage/sqlite/index.js.map +1 -1
  121. package/dist/storage/sqlite/migrations.js +7 -7
  122. package/dist/storage/sqlite/migrations.js.map +1 -1
  123. package/dist/storage/sqlite/node.d.ts +23 -7
  124. package/dist/storage/sqlite/node.d.ts.map +1 -1
  125. package/dist/storage/sqlite/node.js +129 -47
  126. package/dist/storage/sqlite/node.js.map +1 -1
  127. package/dist/storage/sqlite/storage.d.ts +12 -0
  128. package/dist/storage/sqlite/storage.d.ts.map +1 -1
  129. package/dist/storage/sqlite/storage.js +121 -129
  130. package/dist/storage/sqlite/storage.js.map +1 -1
  131. package/dist/testing/storage-benchmark.d.ts.map +1 -1
  132. package/dist/testing/storage-benchmark.js +0 -1
  133. package/dist/testing/storage-benchmark.js.map +1 -1
  134. package/dist/testing/storage-conformance.d.ts.map +1 -1
  135. package/dist/testing/storage-conformance.js +34 -2
  136. package/dist/testing/storage-conformance.js.map +1 -1
  137. package/dist/tools/bash.d.ts +28 -0
  138. package/dist/tools/bash.d.ts.map +1 -0
  139. package/dist/tools/bash.js +70 -0
  140. package/dist/tools/bash.js.map +1 -0
  141. package/dist/tools/edit-diff.d.ts +89 -0
  142. package/dist/tools/edit-diff.d.ts.map +1 -0
  143. package/dist/tools/edit-diff.js +386 -0
  144. package/dist/tools/edit-diff.js.map +1 -0
  145. package/dist/tools/edit.d.ts +18 -0
  146. package/dist/tools/edit.d.ts.map +1 -0
  147. package/dist/tools/edit.js +117 -0
  148. package/dist/tools/edit.js.map +1 -0
  149. package/dist/tools/env.d.ts +5 -0
  150. package/dist/tools/env.d.ts.map +1 -0
  151. package/dist/tools/env.js +7 -0
  152. package/dist/tools/env.js.map +1 -0
  153. package/dist/tools/file-mutation-queue.d.ts +9 -0
  154. package/dist/tools/file-mutation-queue.d.ts.map +1 -0
  155. package/dist/tools/file-mutation-queue.js +52 -0
  156. package/dist/tools/file-mutation-queue.js.map +1 -0
  157. package/dist/tools/image.d.ts +2 -0
  158. package/dist/tools/image.d.ts.map +1 -0
  159. package/dist/tools/image.js +92 -0
  160. package/dist/tools/image.js.map +1 -0
  161. package/dist/tools/index.d.ts +23 -0
  162. package/dist/tools/index.d.ts.map +1 -0
  163. package/dist/tools/index.js +15 -0
  164. package/dist/tools/index.js.map +1 -0
  165. package/dist/tools/path-utils.d.ts +5 -0
  166. package/dist/tools/path-utils.d.ts.map +1 -0
  167. package/dist/tools/path-utils.js +26 -0
  168. package/dist/tools/path-utils.js.map +1 -0
  169. package/dist/tools/read.d.ts +17 -0
  170. package/dist/tools/read.d.ts.map +1 -0
  171. package/dist/tools/read.js +101 -0
  172. package/dist/tools/read.js.map +1 -0
  173. package/dist/tools/write.d.ts +10 -0
  174. package/dist/tools/write.d.ts.map +1 -0
  175. package/dist/tools/write.js +31 -0
  176. package/dist/tools/write.js.map +1 -0
  177. package/dist/{env/utils/truncate.d.ts → truncate.d.ts} +1 -17
  178. package/dist/truncate.d.ts.map +1 -0
  179. package/dist/truncate.js +146 -0
  180. package/dist/truncate.js.map +1 -0
  181. package/dist/types.d.ts +97 -19
  182. package/dist/types.d.ts.map +1 -1
  183. package/dist/types.js.map +1 -1
  184. package/package.json +32 -14
  185. package/dist/env/utils/adaptive-publisher.d.ts +0 -24
  186. package/dist/env/utils/adaptive-publisher.d.ts.map +0 -1
  187. package/dist/env/utils/adaptive-publisher.js +0 -79
  188. package/dist/env/utils/adaptive-publisher.js.map +0 -1
  189. package/dist/env/utils/output-capture.d.ts +0 -31
  190. package/dist/env/utils/output-capture.d.ts.map +0 -1
  191. package/dist/env/utils/output-capture.js +0 -238
  192. package/dist/env/utils/output-capture.js.map +0 -1
  193. package/dist/env/utils/truncate.d.ts.map +0 -1
  194. package/dist/env/utils/truncate.js +0 -294
  195. package/dist/env/utils/truncate.js.map +0 -1
  196. package/dist/harness/config.d.ts +0 -20
  197. package/dist/harness/config.d.ts.map +0 -1
  198. package/dist/harness/config.js +0 -18
  199. package/dist/harness/config.js.map +0 -1
  200. package/docs/chord-delta-findings.md +0 -358
  201. package/docs/pico-v5-chord-usage.md +0 -373
  202. package/docs/pico-v5-handoff.md +0 -581
  203. package/docs/pico-v5.md +0 -3084
@@ -1,6 +1,10 @@
1
- import type { Context, JsonValue } from "@earendil-works/chord";
2
- import type { CacheRetention, Message, Models, ModelThinkingLevel, Tool, ToolResultMessage, Transport, UserMessage } from "@earendil-works/pi-ai";
3
- import type { ConversationId, ConversationOwnership, ConversationRecord, Cursor, DocumentObserver, DocumentReader, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Page, Session, SubmissionId, SubmissionRecord, Task, TaskId, TaskOptions, TaskRecord, TaskState, Tx } from "../types.ts";
1
+ import type { AttachedReplicatedState, Context, JsonValue } from "@earendil-works/chord";
2
+ import type { AssistantMessage, CacheRetention, Message, Models, ModelThinkingLevel, Static, Tool, ToolCall, ToolResultMessage, Transport, TSchema, Usage, UserMessage } from "@earendil-works/pi-ai";
3
+ import type { ExecutionEnv } from "../env/index.ts";
4
+ import type { ConversationId, ConversationOwnership, ConversationRecord, Cursor, DocumentObserver, DocumentReader, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Page, Session, SubmissionId, SubmissionRecord, Task, TaskId, TaskOptions, TaskRecord, TaskState, Tx, WatchHandle } from "../types.ts";
5
+ import type { TaskGraph, TaskGraphWatch } from "./task-graph.ts";
6
+ import type { UsageState } from "./usage.ts";
7
+ import type { ConversationView } from "./view.ts";
4
8
  /** Provider and model ID resolved through pi-ai `Models`. */
5
9
  export type ModelRef = {
6
10
  readonly provider: string;
@@ -51,13 +55,27 @@ export type AnyTask = {
51
55
  readonly hooks?: object;
52
56
  };
53
57
  };
58
+ /** Options of `Conversation.abort()`. */
59
+ export type ConversationAbortOptions = {
60
+ /**
61
+ * Cross background boundaries: mark every live task reached ignoring the background flag when the abort is admitted,
62
+ * withdraw the queued inputs of every conversation reached, and wait until those tasks are terminal and the
63
+ * conversation is ordinarily idle. Background work created afterwards is neither marked nor awaited.
64
+ */
65
+ readonly background?: boolean;
66
+ };
54
67
  /** Hook handler map declared by a task definition. */
55
68
  export type HooksOf<K> = K extends Task<infer _I, infer _S, infer _R, infer H> ? H : never;
56
- /** Invocation-bound conversation operations available to tools. */
69
+ /**
70
+ * Invocation-bound conversation operations for tasks and tools. Rejects after the invocation ends; passive entries are
71
+ * written with ordinary transaction writes instead.
72
+ */
57
73
  export interface ConversationHandle {
58
74
  readonly id: ConversationId;
59
75
  submit(submission: InputSubmissionDraft, context: Context): Promise<Submission>;
60
- abort(context: Context): Promise<void>;
76
+ /** `Conversation.abort()`: withdraw queued inputs, abort the ordinary ownership scope, and wait until it is idle. */
77
+ abort(context: Context, options?: ConversationAbortOptions): Promise<void>;
78
+ /** Resolve when the conversation's ordinary ownership scope has no live non-background task. */
61
79
  waitForIdle(context: Context): Promise<void>;
62
80
  }
63
81
  /** Post-tools controls requested by a tool result. */
@@ -66,21 +84,48 @@ export type ToolControl = {
66
84
  readonly terminate?: true;
67
85
  readonly handoff?: string;
68
86
  };
69
- export type ToolExecutionResult = {
87
+ /** Remark about a call for the model and the UI, such as truncation or a spill path; never part of the tool's data. */
88
+ export type ToolDiagnostic = {
89
+ readonly severity: "info" | "warn" | "error";
90
+ readonly message: string;
91
+ readonly code?: string;
92
+ };
93
+ export type ToolExecutionResult<TDetails extends JsonValue = JsonValue> = {
94
+ /** Omitted: the retained `output()` text becomes the content. */
70
95
  readonly content?: ToolResultMessage["content"];
71
96
  readonly isError?: boolean;
72
- readonly details?: JsonValue;
97
+ /** Omitted: the last `details()` value becomes the details. */
98
+ readonly details?: TDetails;
99
+ /** Added after those recorded through `api.diagnostic()`. */
100
+ readonly diagnostics?: readonly ToolDiagnostic[];
101
+ /** Spend of the execution itself, such as a model call; stored on the result and in `pi.usage.tools`. */
102
+ readonly usage?: Usage;
73
103
  readonly control?: ToolControl;
74
104
  };
75
- /** Operations available to one tool invocation. */
76
- export interface ToolExecutionApi extends DocumentObserver, DocumentReader {
105
+ /** Whether the tools of one round run at once or one after another in call order. */
106
+ export type ToolExecutionMode = "parallel" | "sequential";
107
+ /** How many queued items of one mode a boundary places: the first, or all of them. */
108
+ export type QueueMode = "all" | "one-at-a-time";
109
+ /**
110
+ * Operations available to one tool invocation. A plain object, so a wrapper can spread it. Every operation rejects after
111
+ * the invocation ends.
112
+ */
113
+ export interface ToolExecutionApi<TDetails extends JsonValue = JsonValue> extends DocumentObserver, DocumentReader {
77
114
  readonly taskId: TaskId;
78
115
  readonly conversationId: ConversationId;
79
116
  readonly callId: string;
117
+ /** The tool task's phase snapshot. */
118
+ readonly registry: RegistrySnapshot;
119
+ /** The calling conversation's agent, as the tool task's phase resolved it. */
120
+ agent(context: Context): Promise<Agent>;
121
+ /** Built by `HarnessOptions.env` for this call; `undefined` without an environment. */
122
+ readonly env: ExecutionEnv | undefined;
80
123
  /** Append running output; it becomes the result content when the result omits `content`. */
81
124
  output(chunk: string | Uint8Array): void;
125
+ /** Record a model-visible remark about this call. */
126
+ diagnostic(diagnostic: ToolDiagnostic): void;
82
127
  /** Replace running details; the last value becomes the result details when the result omits `details`. */
83
- details(value: JsonValue, context: Context): Promise<void>;
128
+ details(value: TDetails, context: Context): Promise<void>;
84
129
  commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context): Promise<T>;
85
130
  memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;
86
131
  memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;
@@ -89,84 +134,90 @@ export interface ToolExecutionApi extends DocumentObserver, DocumentReader {
89
134
  }, R, H extends object>(task: Task<I, S, R, H>, input: I, options: Omit<TaskOptions, "conversationId">, context: Context): Promise<TaskId<R>>;
90
135
  getTask<R>(id: TaskId<R>, context: Context): Promise<TaskRecord<JsonValue, JsonValue, R> | undefined>;
91
136
  waitForTask<R>(id: TaskId<R>, context: Context): Promise<SettledTask<R>>;
137
+ /** Invocation-bound handle of an existing conversation, such as one this tool created in `commit()`. */
92
138
  conversation(id: ConversationId, context: Context): Promise<ConversationHandle | undefined>;
93
139
  }
94
- /** Executable tool registered in a registry. Only pi-ai `Tool` fields enter the transcript. */
95
- export type ToolRegistration = Tool & {
140
+ /**
141
+ * Executable tool registered in a registry. Only pi-ai `Tool` fields enter the transcript. `args` are typed by
142
+ * `parameters`, which the Harness validates them against before `execute()`; `defineTool()` infers both generics.
143
+ */
144
+ export type ToolRegistration<TParameters extends TSchema = TSchema, TDetails extends JsonValue = JsonValue> = Tool<TParameters> & {
96
145
  /** Whether an interrupted execution may rerun on recovery. Default `unsafe`. */
97
146
  readonly replay?: "safe" | "unsafe";
147
+ /** Default: the settings' `toolExecution`. One sequential call makes its whole round sequential. */
148
+ readonly executionMode?: ToolExecutionMode;
149
+ /**
150
+ * Repair arguments models commonly get wrong before validation, such as a JSON string where an array belongs. Must be
151
+ * pure and must not mutate `args`: it runs again when a call is retried before its intent is recorded. Its result is
152
+ * still validated against `parameters`.
153
+ */
154
+ prepareArguments?(args: unknown): Static<TParameters>;
98
155
  readonly outputLimits?: {
99
156
  readonly maxBytes?: number;
100
157
  readonly maxLines?: number;
101
158
  readonly retain?: "head" | "tail";
102
159
  };
103
- execute(args: JsonValue, api: ToolExecutionApi, context: Context): Promise<ToolExecutionResult>;
104
- };
105
- /** Token for removing registrations. Work already running keeps using what it started with. */
106
- export interface Registration {
107
- /** Idempotent; removes exactly the registrations this token covers. */
108
- dispose(): void;
109
- }
110
- /** Pure decorator; returns a new tool with the same name and never mutates its input. */
111
- export type ToolWrapper<Tool extends ToolRegistration> = (tool: Tool) => Tool;
112
- /** Conversation selection for a scoped hook registration. */
113
- export type HookScope = {
114
- readonly conversationId: ConversationId;
115
- /** Also match conversations owned, transitively, by tasks of this conversation. */
116
- readonly subtree?: boolean;
160
+ execute(args: Static<TParameters>, api: ToolExecutionApi<TDetails>, context: Context): Promise<ToolExecutionResult<TDetails>>;
117
161
  };
118
162
  /** Input to system prompt section rendering for one request preparation. */
119
- export type PromptInput<Tool extends ToolRegistration> = {
163
+ export type PromptInput<Tool extends ToolRegistration = ToolRegistration> = {
120
164
  readonly conversationId: ConversationId;
121
- /** Active and registered tools in configured order, as offered in this request. */
122
- readonly tools: readonly Tool[];
165
+ /** The request's resolution; `agent.tools` are the tools offered in this request. */
166
+ readonly agent: Agent<Tool>;
167
+ /** Built by `HarnessOptions.env` for this preparation; `undefined` without an environment. */
168
+ readonly env: ExecutionEnv | undefined;
123
169
  /** Sections already in effect after replaying the active transcript. */
124
170
  readonly shown: Readonly<Record<string, string>>;
125
- readonly model?: ModelRef;
126
- readonly thinkingLevel: ModelThinkingLevel;
127
171
  /** Committed document reads. */
128
172
  readonly read: DocumentReader;
129
173
  };
130
- /** One registered system prompt section; sections render in registry order before each request. */
131
- export type PromptSection<Tool extends ToolRegistration> = {
174
+ /** One system prompt section; the agent's sections render in order before each request. */
175
+ export type PromptSection<Tool extends ToolRegistration = ToolRegistration> = {
132
176
  readonly key: string;
133
177
  render(input: PromptInput<Tool>, context: Context): string | undefined | Promise<string | undefined>;
134
178
  /** Default true: wrap the text as `<key>\n...\n</key>`. */
135
179
  readonly tag?: boolean;
136
180
  };
137
- /** Pure decorator; returns a new section with the same key and never mutates its input. */
138
- export type PromptSectionWrapper<Tool extends ToolRegistration> = (section: PromptSection<Tool>) => PromptSection<Tool>;
139
- /** One registered hook handler map for task `K`. */
140
- export type HookRegistration<K extends AnyTask> = {
141
- readonly handlers: Partial<HooksOf<K>>;
142
- readonly scope?: HookScope;
181
+ /** Built by `hook()`; matches tasks by name. */
182
+ export type HookRegistration = {
183
+ readonly task: string;
184
+ readonly handlers: object;
143
185
  };
144
- /** Wrapper composition failure found while building a snapshot. */
145
- export type RegistryFailure = {
146
- readonly kind: "tool" | "section";
147
- /** Tool name or section key. */
148
- readonly name: string;
149
- readonly error: unknown;
186
+ /** Built by `wrapTool()` and `wrapSection()`; targets a tool name or a section key. Wrappers are pure. */
187
+ export type Wrap<Tool extends ToolRegistration = ToolRegistration> = {
188
+ readonly tool: string;
189
+ wrap(tool: Tool): Tool;
190
+ } | {
191
+ readonly section: string;
192
+ wrap(section: PromptSection<Tool>): PromptSection<Tool>;
150
193
  };
194
+ /** Named bundle of code; installed in a registry and selected by conversations by name. */
195
+ export interface Extension<Tool extends ToolRegistration = ToolRegistration> {
196
+ readonly name: string;
197
+ readonly tools?: readonly Tool[];
198
+ readonly sections?: readonly PromptSection<Tool>[];
199
+ readonly hooks?: readonly HookRegistration[];
200
+ /** Apply where this extension is selected, in order. */
201
+ readonly wraps?: readonly Wrap<Tool>[];
202
+ /** Resolved by name for every task, whichever conversations select this extension. */
203
+ readonly tasks?: readonly AnyTask[];
204
+ }
151
205
  /** Immutable view of one published registry state. */
152
206
  export interface RegistrySnapshot<Tool extends ToolRegistration = ToolRegistration> {
153
- /** Composed tools in registry order; a tool whose wrapper failed is absent. */
154
- tools(): readonly Tool[];
155
- tool(name: string): Tool | undefined;
156
- /** Base tool names in registry order, including tools whose wrappers fail. */
157
- toolNames(): readonly string[];
158
- task(name: string): AnyTask | undefined;
159
- /** Hooks registered for tasks with `task`'s name, in registry order. */
160
- hooks<K extends AnyTask>(task: K): readonly HookRegistration<K>[];
161
- /** Composed sections in registry order; a section whose wrapper failed is absent. */
162
- sections(): readonly PromptSection<Tool>[];
163
- /** Wrapper failures of this state. */
164
- failures(): readonly RegistryFailure[];
165
- /** Conversation setups in registry order, the built-in `pi` setup first. */
166
- conversationSetups(): readonly {
167
- readonly key: string;
168
- readonly setup: ConversationSetup;
207
+ installed(): readonly Extension<Tool>[];
208
+ extension(name: string): Extension<Tool> | undefined;
209
+ /** Every installed tool with its extension, in install order. Names may repeat across extensions. */
210
+ tools(): readonly {
211
+ readonly extension: Extension<Tool>;
212
+ readonly tool: Tool;
213
+ }[];
214
+ sections(): readonly {
215
+ readonly extension: Extension<Tool>;
216
+ readonly section: PromptSection<Tool>;
169
217
  }[];
218
+ /** Built-in and installed task definitions. */
219
+ tasks(): readonly AnyTask[];
220
+ task(name: string): AnyTask | undefined;
170
221
  }
171
222
  /** Read side of a registry consumed by a Harness. */
172
223
  export interface RegistryReader<Tool extends ToolRegistration = ToolRegistration> {
@@ -175,54 +226,66 @@ export interface RegistryReader<Tool extends ToolRegistration = ToolRegistration
175
226
  /** Called synchronously after every publication; wakes the scheduler to reconsider blocked tasks. */
176
227
  subscribe(listener: () => void): () => void;
177
228
  }
178
- /** Application-owned registry of tools, hooks, tasks, and the system prompt. */
229
+ /** Application-owned registry of extensions. */
179
230
  export interface Registry<Tool extends ToolRegistration = ToolRegistration> extends RegistryReader<Tool> {
180
- readonly tools: {
181
- add(tool: Tool): Registration;
182
- /** `key` identifies the wrapper: it orders wrappers of one tool and keeps its position on re-registration. */
183
- wrap(name: string, key: string, wrapper: ToolWrapper<Tool>): Registration;
184
- /** Composed tools of the current state. */
185
- list(): readonly Tool[];
186
- };
187
- readonly hooks: {
188
- add<K extends AnyTask>(task: K, handlers: Partial<HooksOf<K>>, options?: {
189
- readonly scope?: HookScope;
190
- readonly key?: string;
191
- }): Registration;
192
- };
193
- readonly tasks: {
194
- add(task: AnyTask): Registration;
195
- list(): readonly AnyTask[];
196
- };
197
- readonly conversations: {
198
- /** `key` orders setups and keeps its position on re-registration. */
199
- setup(key: string, setup: ConversationSetup): Registration;
231
+ /** Install `extension`, or replace the installed extension with its name in place. Publishes at once. */
232
+ install(extension: Extension<Tool>): void;
233
+ /** Remove the installed extension with `extension.name`, whichever object it is. A later install appends. */
234
+ uninstall(extension: Extension): void;
235
+ }
236
+ /** Stored choices of one conversation; names, not objects. Unset fields follow the host. */
237
+ export type AgentState = {
238
+ model?: ModelRef;
239
+ thinkingLevel?: ModelThinkingLevel;
240
+ /** An array selects exactly these extensions, in order. An object edits the host default selection. */
241
+ extensions?: string[] | {
242
+ add?: string[];
243
+ remove?: string[];
200
244
  };
201
- readonly systemPrompt: {
202
- section(key: string, render: PromptSection<Tool>["render"], options?: {
203
- readonly tag?: boolean;
204
- }): Registration;
205
- wrap(key: string, wrapperKey: string, wrapper: PromptSectionWrapper<Tool>): Registration;
206
- /** Composed sections of the current state. */
207
- sections(): readonly PromptSection<Tool>[];
245
+ /** Filters the selected extensions' tools. An array offers exactly these, in order. */
246
+ tools?: string[] | {
247
+ remove: string[];
208
248
  };
209
- /** Stage registrations and disposals made synchronously by `register`, then publish them at once. Cannot nest. */
210
- batch(register: () => void): Registration;
211
- }
212
- /**
213
- * Stages the documents every new conversation gets. Runs inside every Harness commit that creates or forks a
214
- * conversation, including raw `Tx` creation, after fork copies and before host `init`. Table reads throw, since the
215
- * conversation write came first; a fork (`conversation.parent`) already holds its copied documents. A throw fails the
216
- * creating commit.
217
- */
218
- export type ConversationSetup = (tx: Tx, conversation: ConversationRecord, registry: RegistrySnapshot) => void | Promise<void>;
249
+ /** Rendered after every extension section, as the section `instructions`. */
250
+ instructions?: string;
251
+ /** Directory within the environment's file system, passed to `HarnessOptions.env`. */
252
+ cwd?: string;
253
+ };
254
+ /** A change to `pi.agent`: a given field replaces the stored one, `null` clears it, `undefined` changes nothing. */
255
+ export type AgentChange = {
256
+ readonly model?: ModelRef | null;
257
+ readonly thinkingLevel?: ModelThinkingLevel | null;
258
+ readonly extensions?: readonly Extension[] | {
259
+ readonly add?: readonly Extension[];
260
+ readonly remove?: readonly Extension[];
261
+ } | null;
262
+ readonly tools?: readonly ToolRegistration[] | {
263
+ readonly remove: readonly ToolRegistration[];
264
+ } | null;
265
+ readonly instructions?: string | null;
266
+ readonly cwd?: string | null;
267
+ };
268
+ /** A conversation's agent resolved against a registry snapshot and the settings. */
269
+ export type Agent<Tool extends ToolRegistration = ToolRegistration> = {
270
+ readonly model?: ModelRef;
271
+ readonly thinkingLevel: ModelThinkingLevel;
272
+ readonly extensions: readonly Extension<Tool>[];
273
+ /** The tools a request offers, in order. */
274
+ readonly tools: readonly Tool[];
275
+ /** Extension sections, then `instructions` when set. */
276
+ readonly sections: readonly PromptSection<Tool>[];
277
+ readonly instructions?: string;
278
+ readonly cwd?: string;
279
+ };
219
280
  /**
220
- * Runs inside the creating commit, after the conversation and its configuration exist. The conversation creation is
221
- * already a table write, so table reads here throw `ReadAfterWrite`; document access remains available.
281
+ * Runs inside the creating commit, after the creation hook and the `agent` change. The conversation creation is already
282
+ * a table write, so table reads here throw `ReadAfterWrite`; document access remains available.
222
283
  */
223
284
  export type ConversationInit = (tx: Tx, conversationId: ConversationId) => void | Promise<void>;
224
285
  export type ConversationCreateOptions = {
225
286
  readonly ownership: ConversationOwnership;
287
+ /** Applied in the creating commit after the creation hook's copy, before `init`. */
288
+ readonly agent?: AgentChange;
226
289
  readonly init?: ConversationInit;
227
290
  };
228
291
  /** Curated pi-ai request options; absent fields use pi-ai defaults. */
@@ -246,10 +309,69 @@ export type ConversationRetryPolicy = {
246
309
  baseDelayMs: number;
247
310
  maxAgentDelayMs?: number;
248
311
  };
312
+ /** Automatic compaction thresholds (spec §8.7); manual compaction ignores `enabled`. */
313
+ export type CompactionPolicy = {
314
+ /** Threshold and overflow compaction. */
315
+ enabled: boolean;
316
+ /** Room kept free for the answer: generation blocks to compact above `contextWindow - reserveTokens`. */
317
+ reserveTokens: number;
318
+ /** Approximate size of the recent context a summary keeps verbatim. */
319
+ keepRecentTokens: number;
320
+ /** Background compaction starts `backgroundTokens` below the blocking threshold; `0` disables it. */
321
+ backgroundTokens: number;
322
+ };
323
+ /** Why a compaction runs: `compact()`, a threshold in generation preparation, or a context overflow. */
324
+ export type CompactionReason = "manual" | "threshold" | "overflow";
325
+ /**
326
+ * `entryId` of a blocking compaction's summary, or the `submissionId` of a conversation-owned compaction's summary
327
+ * write; both absent when nothing was compacted.
328
+ */
329
+ export type CompactionResult = {
330
+ entryId?: EntryId;
331
+ submissionId?: SubmissionId;
332
+ };
333
+ /** Harness-wide run policy. Read at every resolution and never copied; getters are fine. Synchronous: some readers run on the Session line. */
334
+ export type HarnessSettings = {
335
+ /** Default extension selection; absent: every installed extension, in install order. */
336
+ readonly extensions?: readonly Extension[];
337
+ readonly stream?: ConversationStreamOptions;
338
+ readonly retry?: Partial<ConversationRetryPolicy>;
339
+ readonly compaction?: Partial<CompactionPolicy>;
340
+ readonly toolExecution?: ToolExecutionMode;
341
+ readonly steeringMode?: QueueMode;
342
+ readonly followUpMode?: QueueMode;
343
+ };
344
+ /** Resolved settings: every field over its built-in default, object fields merged. */
345
+ export type Settings = {
346
+ /** Absent: every installed extension, in install order. */
347
+ readonly extensions?: readonly Extension[];
348
+ readonly stream: ConversationStreamOptions;
349
+ readonly retry: ConversationRetryPolicy;
350
+ readonly compaction: CompactionPolicy;
351
+ readonly toolExecution: ToolExecutionMode;
352
+ readonly steeringMode: QueueMode;
353
+ readonly followUpMode: QueueMode;
354
+ };
355
+ /** What `HarnessOptions.env` builds an environment for. */
356
+ export type EnvTarget = {
357
+ readonly conversationId: ConversationId;
358
+ /** The conversation's agent `cwd`. */
359
+ readonly cwd?: string;
360
+ readonly read: DocumentReader;
361
+ };
249
362
  export type HarnessOptions<Tool extends ToolRegistration = ToolRegistration> = {
250
363
  /** pi-ai model access used by generation. */
251
364
  readonly models: Models;
252
365
  readonly registry: RegistryReader<Tool>;
366
+ readonly settings?: HarnessSettings;
367
+ /** Builds a conversation's environment at each use. Never called on the Session line; may be async. */
368
+ readonly env?: (target: EnvTarget, context: Context) => ExecutionEnv | undefined | Promise<ExecutionEnv | undefined>;
369
+ /**
370
+ * Runs in every commit that creates or forks a conversation, raw `tx.createConversation()` included, after the
371
+ * built-in `pi.*` documents and before the conveniences apply `agent` and run `init`. A fork already has its copies.
372
+ * Table reads throw `ReadAfterWrite`, as in `init`; a throw fails the creating commit.
373
+ */
374
+ readonly conversationCreated?: (tx: Tx, conversation: ConversationRecord) => void | Promise<void>;
253
375
  readonly now?: () => number;
254
376
  /** Receives extension failures that do not fail the calling operation. Must not throw. */
255
377
  readonly onReport?: (error: unknown) => void;
@@ -265,11 +387,18 @@ export type TaskInspection = {
265
387
  readonly kind: "ready";
266
388
  readonly migrates: boolean;
267
389
  }
268
- /** Pending until these tasks are terminal. */
390
+ /**
391
+ * Waits for these live tasks: the live part of its `on`, or, when abort-marked, its live ordinary owned work, which
392
+ * must end before its abort handler starts.
393
+ */
269
394
  | {
270
395
  readonly kind: "waiting";
271
396
  readonly on: readonly TaskId[];
272
397
  }
398
+ /** Outcome held until its ordinary owned work drains. */
399
+ | {
400
+ readonly kind: "completing";
401
+ }
273
402
  /** No registered definition can take it; aborting it settles it as `orphaned`. */
274
403
  | {
275
404
  readonly kind: "blocked";
@@ -283,8 +412,6 @@ export type HarnessInspection = {
283
412
  readonly tasks: readonly TaskInspection[];
284
413
  /** Queued and placed submissions, in ID order. */
285
414
  readonly submissions: readonly SubmissionRecord[];
286
- /** Wrapper failures of the current registry snapshot. */
287
- readonly registry: readonly RegistryFailure[];
288
415
  };
289
416
  /** Raw active transcript and derived model context. */
290
417
  export type ContextView = {
@@ -292,54 +419,72 @@ export type ContextView = {
292
419
  readonly head: EntryRecord | undefined;
293
420
  /** Raw active entries: the head marker followed by non-head entries from its head through the tail. */
294
421
  readonly entries: readonly EntryRecord[];
422
+ /** Per entry of `entries`, its model messages after edits and excluded stop reasons, before tool result ordering. */
423
+ readonly contributions: readonly (readonly Message[])[];
295
424
  /** Model context for the next provider request. */
296
425
  readonly messages: readonly Message[];
297
426
  };
298
427
  /** Stateless handle for one conversation, bound to the Harness that returned it. Compare handles by `id`. */
299
428
  export interface Conversation {
300
429
  readonly id: ConversationId;
301
- getModel(context: Context): Promise<ModelRef | undefined>;
302
- setModel(model: ModelRef | undefined, context: Context): Promise<void>;
303
- getThinkingLevel(context: Context): Promise<ModelThinkingLevel>;
304
- setThinkingLevel(level: ModelThinkingLevel, context: Context): Promise<void>;
305
- getActiveTools(context: Context): Promise<readonly string[]>;
306
- setActiveTools(names: readonly string[], context: Context): Promise<void>;
307
- /** `{}` when unset. */
308
- getStreamOptions(context: Context): Promise<ConversationStreamOptions>;
309
- setStreamOptions(options: ConversationStreamOptions, context: Context): Promise<void>;
310
- /** The default policy when unset. */
311
- getRetryPolicy(context: Context): Promise<ConversationRetryPolicy>;
312
- /** `undefined` removes the configured policy. */
313
- setRetryPolicy(policy: ConversationRetryPolicy | undefined, context: Context): Promise<void>;
430
+ /** Resolved with the current registry snapshot and settings. */
431
+ agent(context: Context): Promise<Agent>;
432
+ /** `configure()` in its own commit. */
433
+ configure(change: AgentChange, context: Context): Promise<void>;
314
434
  /**
315
- * Durably admit user input or a passive entry write. Until the inbox exists, a busy conversation rejects every
316
- * submission with `ConversationBusy`.
435
+ * Durably admit user input or a passive entry write. A busy conversation, or one with queued items, queues it in
436
+ * `pi.inbox`; `whenBusy: "reject"` rejects with `ConversationBusy` instead and writes nothing.
317
437
  */
318
438
  submit(submission: SubmissionDraft, context: Context): Promise<Submission>;
439
+ /**
440
+ * Admit a write of a `pi.reset` entry that starts a new context, carrying `handoff` as a user message when given.
441
+ * Resolves after admission; while busy, it is placed at the next boundary.
442
+ */
443
+ reset(handoff: string | undefined, context: Context): Promise<void>;
444
+ /**
445
+ * Admit a manual compaction task and return its ID. It summarizes while the conversation keeps working and places its
446
+ * summary through a write submission: at once when idle, otherwise at the next boundary (spec §8.7).
447
+ */
448
+ compact(instructions: string | undefined, context: Context): Promise<TaskId<CompactionResult>>;
319
449
  /** Session commit whose `tx.createTask()` defaults to this conversation. */
320
450
  commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context): Promise<T>;
321
451
  context(context: Context): Promise<ContextView>;
322
452
  /** Newest-first fork-aware history of this conversation. */
323
453
  entries(query: Omit<EntryQuery, "conversationId">, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<EntryRecord, Cursor>>;
324
454
  fork(at: EntryId, options: ConversationCreateOptions, context: Context): Promise<Conversation>;
325
- /** Resolve when no live non-background task belongs to this conversation. */
455
+ /**
456
+ * Withdraw queued inputs (queued writes stay), mark every live non-background task of the ordinary ownership scope,
457
+ * signal them, and resolve once the scope is idle. Background subtrees survive unless `background` is set.
458
+ */
459
+ abort(context: Context, options?: ConversationAbortOptions): Promise<void>;
460
+ /**
461
+ * Resolve when the ordinary ownership scope has no live non-background task: this conversation and the conversations
462
+ * owned, transitively, by its non-background tasks.
463
+ */
326
464
  waitForIdle(context: Context): Promise<void>;
465
+ /** The structural view (spec §9.3) as a disposable read-only Chord state. */
466
+ viewState(context: Context): Promise<AttachedReplicatedState<ConversationView>>;
467
+ /** The structural view as a serialized exact-frame watch with bounded pending frames. */
468
+ watch(context: Context): Promise<ConversationWatch>;
327
469
  }
470
+ export type ConversationWatch = WatchHandle<ConversationView>;
328
471
  /** Durable agent harness over one Session. */
329
472
  export interface Harness extends Session {
330
473
  /**
331
- * Enable task scheduling. Idempotent; throws after close. Calls that wait for progress (`Conversation.submit()`,
332
- * `Submission.wait()`, `waitForTask()`, `waitForIdle()`) enable it too.
474
+ * Enable task scheduling. Idempotent; throws after close. Calls that ask for progress enable it too:
475
+ * `Conversation.submit()`, `Conversation.compact()`, `Conversation.abort()`, `Submission.wait()`, `waitForTask()`,
476
+ * `Harness.waitForIdle()`, and `Conversation.waitForIdle()`. Read-only viewers never do.
333
477
  */
334
478
  resume(): void;
335
- /** Return the reserved root conversation, creating it with `init` in one commit when absent. */
479
+ /** Return the reserved root conversation, creating it with `agent` and `init` in one commit when absent. */
336
480
  root(context: Context, options?: {
481
+ readonly agent?: AgentChange;
337
482
  readonly init?: ConversationInit;
338
483
  }): Promise<Conversation>;
339
484
  conversation(id: ConversationId, context: Context): Promise<Conversation | undefined>;
340
485
  createConversation(options: ConversationCreateOptions, context: Context): Promise<Conversation>;
341
486
  getTask<R>(id: TaskId<R>, context: Context): Promise<TaskRecord<JsonValue, JsonValue, R> | undefined>;
342
- /** Live tasks, unsettled submissions, and registry failures. Writes nothing and runs no task code. */
487
+ /** Live tasks and unsettled submissions. Writes nothing and runs no task code. */
343
488
  inspect(context: Context): Promise<HarnessInspection>;
344
489
  /** Reacquire a submission, for example after reopen. */
345
490
  submission(id: SubmissionId, context: Context): Promise<Submission | undefined>;
@@ -352,7 +497,67 @@ export interface Harness extends Session {
352
497
  abortTask(id: TaskId, context: Context): Promise<"marked" | "terminal">;
353
498
  /** Resolve with the terminal receipt; cancelling `context` cancels only this wait. */
354
499
  waitForTask<R>(id: TaskId<R>, context: Context): Promise<SettledTask<R>>;
355
- /** Resolve when no live non-background task exists. */
500
+ /** Resolve when the ordinary ownership scope of every ownerless conversation has no live non-background task. */
356
501
  waitForIdle(context: Context): Promise<void>;
502
+ /** Session total: every conversation's `pi.usage` summed. */
503
+ usage(context: Context): Promise<UsageState>;
504
+ /** Every live task with its owner edge, status, and owned conversations (spec §9.5), as a disposable Chord state. */
505
+ taskGraph(context: Context): Promise<AttachedReplicatedState<TaskGraph>>;
506
+ /** The task graph as a serialized exact-frame watch with bounded pending frames. */
507
+ watchTaskGraph(context: Context): Promise<TaskGraphWatch>;
508
+ }
509
+ /** What a hook may use: committed reads and the asking task's memos, which hooks and the task share. */
510
+ export interface HookApi extends DocumentReader {
511
+ readonly taskId: TaskId;
512
+ readonly conversationId: ConversationId;
513
+ memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;
514
+ memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;
515
+ }
516
+ export type HookResult<T> = T | undefined | Promise<T | undefined>;
517
+ /** Hooks of the built-in generation task. */
518
+ export interface GenerationHooks {
519
+ /** Before every request attempt, including recovery; the result is used for that request only. */
520
+ beforeRequest(request: {
521
+ readonly messages: readonly Message[];
522
+ }, api: HookApi, context: Context): HookResult<{
523
+ readonly messages: readonly Message[];
524
+ }>;
525
+ /** Every terminal provider message, before classification. */
526
+ afterResponse(message: AssistantMessage, api: HookApi, context: Context): void | Promise<void>;
527
+ /** A final answer; the first `continue` appends a user message and continues the run. */
528
+ onYield(answer: AssistantMessage, api: HookApi, context: Context): HookResult<{
529
+ readonly continue: UserInput;
530
+ }>;
531
+ /** After every tool of the round is terminal; `results` are the round's result entries in call order. */
532
+ afterTools(assistant: EntryId, results: readonly EntryId[], api: HookApi, context: Context): void | Promise<void>;
533
+ }
534
+ /** Hooks of the built-in tool task. */
535
+ export interface ToolHooks {
536
+ /** Before intent; the first `block` wins, otherwise `arguments` replace the call's arguments. A throw blocks. */
537
+ beforeTool(call: ToolCall, api: HookApi, context: Context): HookResult<{
538
+ readonly arguments?: JsonObject;
539
+ readonly block?: string;
540
+ }>;
541
+ /** After execution, before the result entry; replaces the result. */
542
+ afterTool(call: ToolCall, result: ToolExecutionResult, api: HookApi, context: Context): HookResult<ToolExecutionResult>;
543
+ }
544
+ /** Hooks of the built-in compaction task. */
545
+ export interface CompactionHooks {
546
+ /**
547
+ * After range selection, before summarizing; the first decision wins. `entries` are the active entries the summary
548
+ * replaces, the head marker first, and `messages` their model context, the summarizer's source; `firstKept` is the
549
+ * first entry kept verbatim.
550
+ */
551
+ beforeCompact(compaction: {
552
+ readonly reason: CompactionReason;
553
+ readonly entries: readonly EntryRecord[];
554
+ readonly messages: readonly Message[];
555
+ readonly firstKept: EntryId;
556
+ readonly instructions?: string;
557
+ }, api: HookApi, context: Context): HookResult<{
558
+ readonly decline: true;
559
+ } | {
560
+ readonly summary: string;
561
+ }>;
357
562
  }
358
563
  //# sourceMappingURL=types.d.ts.map