@earendil-works/pi-durable 1.4.0 → 1.5.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 (270) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +195 -85
  3. package/dist/codemode/codemode.d.ts +6 -5
  4. package/dist/codemode/codemode.d.ts.map +1 -1
  5. package/dist/codemode/codemode.js +10 -49
  6. package/dist/codemode/codemode.js.map +1 -1
  7. package/dist/codemode/models.d.ts +2 -1
  8. package/dist/codemode/models.d.ts.map +1 -1
  9. package/dist/codemode/models.js +4 -4
  10. package/dist/codemode/models.js.map +1 -1
  11. package/dist/documents.d.ts +1 -1
  12. package/dist/documents.d.ts.map +1 -1
  13. package/dist/documents.js +1 -1
  14. package/dist/documents.js.map +1 -1
  15. package/dist/entries.d.ts +1 -1
  16. package/dist/entries.d.ts.map +1 -1
  17. package/dist/entries.js.map +1 -1
  18. package/dist/env/index.d.ts +13 -11
  19. package/dist/env/index.d.ts.map +1 -1
  20. package/dist/env/index.js.map +1 -1
  21. package/dist/env/node-watch.d.ts +4 -4
  22. package/dist/env/node-watch.d.ts.map +1 -1
  23. package/dist/env/node-watch.js +18 -16
  24. package/dist/env/node-watch.js.map +1 -1
  25. package/dist/env/node.d.ts +3 -3
  26. package/dist/env/node.d.ts.map +1 -1
  27. package/dist/env/node.js +1 -1
  28. package/dist/env/node.js.map +1 -1
  29. package/dist/errors.d.ts +18 -2
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js +26 -3
  32. package/dist/errors.js.map +1 -1
  33. package/dist/harness/agent.d.ts.map +1 -1
  34. package/dist/harness/agent.js +37 -3
  35. package/dist/harness/agent.js.map +1 -1
  36. package/dist/harness/compaction.d.ts +8 -3
  37. package/dist/harness/compaction.d.ts.map +1 -1
  38. package/dist/harness/compaction.js +40 -11
  39. package/dist/harness/compaction.js.map +1 -1
  40. package/dist/harness/context.d.ts +24 -8
  41. package/dist/harness/context.d.ts.map +1 -1
  42. package/dist/harness/context.js +71 -25
  43. package/dist/harness/context.js.map +1 -1
  44. package/dist/harness/define.d.ts +8 -4
  45. package/dist/harness/define.d.ts.map +1 -1
  46. package/dist/harness/define.js +4 -0
  47. package/dist/harness/define.js.map +1 -1
  48. package/dist/harness/generation.d.ts +39 -6
  49. package/dist/harness/generation.d.ts.map +1 -1
  50. package/dist/harness/generation.js +451 -100
  51. package/dist/harness/generation.js.map +1 -1
  52. package/dist/harness/harness.d.ts +3 -1
  53. package/dist/harness/harness.d.ts.map +1 -1
  54. package/dist/harness/harness.js +87 -62
  55. package/dist/harness/harness.js.map +1 -1
  56. package/dist/harness/inbox.d.ts +7 -2
  57. package/dist/harness/inbox.d.ts.map +1 -1
  58. package/dist/harness/inbox.js +5 -2
  59. package/dist/harness/inbox.js.map +1 -1
  60. package/dist/harness/json.d.ts +21 -5
  61. package/dist/harness/json.d.ts.map +1 -1
  62. package/dist/harness/json.js +23 -5
  63. package/dist/harness/json.js.map +1 -1
  64. package/dist/harness/live.d.ts +124 -17
  65. package/dist/harness/live.d.ts.map +1 -1
  66. package/dist/harness/live.js +93 -12
  67. package/dist/harness/live.js.map +1 -1
  68. package/dist/harness/partial-changes.d.ts +54 -0
  69. package/dist/harness/partial-changes.d.ts.map +1 -0
  70. package/dist/harness/partial-changes.js +163 -0
  71. package/dist/harness/partial-changes.js.map +1 -0
  72. package/dist/harness/prompt.d.ts +1 -1
  73. package/dist/harness/prompt.d.ts.map +1 -1
  74. package/dist/harness/prompt.js.map +1 -1
  75. package/dist/harness/provider.d.ts +1 -1
  76. package/dist/harness/provider.d.ts.map +1 -1
  77. package/dist/harness/provider.js.map +1 -1
  78. package/dist/harness/registry-reader.d.ts +11 -0
  79. package/dist/harness/registry-reader.d.ts.map +1 -0
  80. package/dist/harness/registry-reader.js +25 -0
  81. package/dist/harness/registry-reader.js.map +1 -0
  82. package/dist/harness/scheduler.d.ts +48 -20
  83. package/dist/harness/scheduler.d.ts.map +1 -1
  84. package/dist/harness/scheduler.js +1006 -380
  85. package/dist/harness/scheduler.js.map +1 -1
  86. package/dist/harness/submissions.d.ts +18 -11
  87. package/dist/harness/submissions.d.ts.map +1 -1
  88. package/dist/harness/submissions.js +63 -26
  89. package/dist/harness/submissions.js.map +1 -1
  90. package/dist/harness/task-graph.d.ts +14 -9
  91. package/dist/harness/task-graph.d.ts.map +1 -1
  92. package/dist/harness/task-graph.js +38 -28
  93. package/dist/harness/task-graph.js.map +1 -1
  94. package/dist/harness/telemetry.d.ts +11 -11
  95. package/dist/harness/telemetry.d.ts.map +1 -1
  96. package/dist/harness/telemetry.js +7 -7
  97. package/dist/harness/telemetry.js.map +1 -1
  98. package/dist/harness/tool.d.ts +3 -1
  99. package/dist/harness/tool.d.ts.map +1 -1
  100. package/dist/harness/tool.js +172 -56
  101. package/dist/harness/tool.js.map +1 -1
  102. package/dist/harness/transcript.d.ts +72 -0
  103. package/dist/harness/transcript.d.ts.map +1 -0
  104. package/dist/harness/transcript.js +264 -0
  105. package/dist/harness/transcript.js.map +1 -0
  106. package/dist/harness/types.d.ts +237 -61
  107. package/dist/harness/types.d.ts.map +1 -1
  108. package/dist/harness/types.js.map +1 -1
  109. package/dist/harness/usage.d.ts +2 -1
  110. package/dist/harness/usage.d.ts.map +1 -1
  111. package/dist/harness/usage.js +1 -1
  112. package/dist/harness/usage.js.map +1 -1
  113. package/dist/harness/util.d.ts +4 -2
  114. package/dist/harness/util.d.ts.map +1 -1
  115. package/dist/harness/util.js +9 -0
  116. package/dist/harness/util.js.map +1 -1
  117. package/dist/harness/view.d.ts +44 -22
  118. package/dist/harness/view.d.ts.map +1 -1
  119. package/dist/harness/view.js +130 -71
  120. package/dist/harness/view.js.map +1 -1
  121. package/dist/index.d.ts +9 -7
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +4 -3
  124. package/dist/index.js.map +1 -1
  125. package/dist/search.d.ts +19 -3
  126. package/dist/search.d.ts.map +1 -1
  127. package/dist/search.js +107 -44
  128. package/dist/search.js.map +1 -1
  129. package/dist/session/cached-storage.d.ts +72 -0
  130. package/dist/session/cached-storage.d.ts.map +1 -0
  131. package/dist/session/cached-storage.js +269 -0
  132. package/dist/session/cached-storage.js.map +1 -0
  133. package/dist/session/conversation-tails.d.ts +33 -0
  134. package/dist/session/conversation-tails.d.ts.map +1 -0
  135. package/dist/session/conversation-tails.js +101 -0
  136. package/dist/session/conversation-tails.js.map +1 -0
  137. package/dist/session/forks.d.ts +1 -1
  138. package/dist/session/forks.d.ts.map +1 -1
  139. package/dist/session/forks.js.map +1 -1
  140. package/dist/session/keys.d.ts +69 -0
  141. package/dist/session/keys.d.ts.map +1 -0
  142. package/dist/session/keys.js +161 -0
  143. package/dist/session/keys.js.map +1 -0
  144. package/dist/session/session.d.ts +169 -26
  145. package/dist/session/session.d.ts.map +1 -1
  146. package/dist/session/session.js +1029 -171
  147. package/dist/session/session.js.map +1 -1
  148. package/dist/session/transaction.d.ts +84 -5
  149. package/dist/session/transaction.d.ts.map +1 -1
  150. package/dist/session/transaction.js +227 -40
  151. package/dist/session/transaction.js.map +1 -1
  152. package/dist/session/writer.d.ts +74 -0
  153. package/dist/session/writer.d.ts.map +1 -0
  154. package/dist/session/writer.js +294 -0
  155. package/dist/session/writer.js.map +1 -0
  156. package/dist/storage/indexeddb.d.ts +12 -3
  157. package/dist/storage/indexeddb.d.ts.map +1 -1
  158. package/dist/storage/indexeddb.js +91 -41
  159. package/dist/storage/indexeddb.js.map +1 -1
  160. package/dist/storage/jsonl/index.d.ts +1 -1
  161. package/dist/storage/jsonl/index.d.ts.map +1 -1
  162. package/dist/storage/jsonl/index.js +1 -1
  163. package/dist/storage/jsonl/index.js.map +1 -1
  164. package/dist/storage/jsonl/node.d.ts +3 -3
  165. package/dist/storage/jsonl/node.d.ts.map +1 -1
  166. package/dist/storage/jsonl/node.js +2 -2
  167. package/dist/storage/jsonl/node.js.map +1 -1
  168. package/dist/storage/jsonl/storage.d.ts +46 -5
  169. package/dist/storage/jsonl/storage.d.ts.map +1 -1
  170. package/dist/storage/jsonl/storage.js +158 -43
  171. package/dist/storage/jsonl/storage.js.map +1 -1
  172. package/dist/storage/memory.d.ts +9 -3
  173. package/dist/storage/memory.d.ts.map +1 -1
  174. package/dist/storage/memory.js +28 -2
  175. package/dist/storage/memory.js.map +1 -1
  176. package/dist/storage/sqlite/commit.d.ts +52 -0
  177. package/dist/storage/sqlite/commit.d.ts.map +1 -0
  178. package/dist/storage/sqlite/commit.js +371 -0
  179. package/dist/storage/sqlite/commit.js.map +1 -0
  180. package/dist/storage/sqlite/rows.d.ts +33 -0
  181. package/dist/storage/sqlite/rows.d.ts.map +1 -0
  182. package/dist/storage/sqlite/rows.js +53 -0
  183. package/dist/storage/sqlite/rows.js.map +1 -0
  184. package/dist/storage/sqlite/statements.d.ts +13 -0
  185. package/dist/storage/sqlite/statements.d.ts.map +1 -0
  186. package/dist/storage/sqlite/statements.js +88 -0
  187. package/dist/storage/sqlite/statements.js.map +1 -0
  188. package/dist/storage/sqlite/storage.d.ts +23 -10
  189. package/dist/storage/sqlite/storage.d.ts.map +1 -1
  190. package/dist/storage/sqlite/storage.js +120 -261
  191. package/dist/storage/sqlite/storage.js.map +1 -1
  192. package/dist/storage/store/commit.d.ts +33 -0
  193. package/dist/storage/store/commit.d.ts.map +1 -0
  194. package/dist/storage/store/commit.js +286 -0
  195. package/dist/storage/store/commit.js.map +1 -0
  196. package/dist/storage/store/documents.d.ts +43 -0
  197. package/dist/storage/store/documents.d.ts.map +1 -0
  198. package/dist/storage/store/documents.js +102 -0
  199. package/dist/storage/store/documents.js.map +1 -0
  200. package/dist/storage/store/index.d.ts +2 -0
  201. package/dist/storage/store/index.d.ts.map +1 -0
  202. package/dist/storage/store/index.js +2 -0
  203. package/dist/storage/store/index.js.map +1 -0
  204. package/dist/storage/store/queries.d.ts +49 -0
  205. package/dist/storage/store/queries.d.ts.map +1 -0
  206. package/dist/storage/store/queries.js +93 -0
  207. package/dist/storage/store/queries.js.map +1 -0
  208. package/dist/storage/store/storage.d.ts +60 -0
  209. package/dist/storage/store/storage.d.ts.map +1 -0
  210. package/dist/storage/store/storage.js +438 -0
  211. package/dist/storage/store/storage.js.map +1 -0
  212. package/dist/storage/store/tables.d.ts +54 -0
  213. package/dist/storage/store/tables.d.ts.map +1 -0
  214. package/dist/storage/store/tables.js +108 -0
  215. package/dist/storage/store/tables.js.map +1 -0
  216. package/dist/testing/commit-writes.d.ts +5 -0
  217. package/dist/testing/commit-writes.d.ts.map +1 -0
  218. package/dist/testing/commit-writes.js +12 -0
  219. package/dist/testing/commit-writes.js.map +1 -0
  220. package/dist/testing/env-conformance.js +7 -7
  221. package/dist/testing/env-conformance.js.map +1 -1
  222. package/dist/testing/index.d.ts +1 -0
  223. package/dist/testing/index.d.ts.map +1 -1
  224. package/dist/testing/index.js +1 -0
  225. package/dist/testing/index.js.map +1 -1
  226. package/dist/testing/storage-benchmark.d.ts.map +1 -1
  227. package/dist/testing/storage-benchmark.js +18 -17
  228. package/dist/testing/storage-benchmark.js.map +1 -1
  229. package/dist/testing/storage-conformance.d.ts.map +1 -1
  230. package/dist/testing/storage-conformance.js +354 -69
  231. package/dist/testing/storage-conformance.js.map +1 -1
  232. package/dist/tool-search/tool-search.d.ts +8 -14
  233. package/dist/tool-search/tool-search.d.ts.map +1 -1
  234. package/dist/tool-search/tool-search.js +25 -13
  235. package/dist/tool-search/tool-search.js.map +1 -1
  236. package/dist/tools/bash.d.ts +13 -13
  237. package/dist/tools/bash.d.ts.map +1 -1
  238. package/dist/tools/bash.js +16 -15
  239. package/dist/tools/bash.js.map +1 -1
  240. package/dist/tools/edit.d.ts +7 -7
  241. package/dist/tools/edit.d.ts.map +1 -1
  242. package/dist/tools/edit.js +9 -9
  243. package/dist/tools/edit.js.map +1 -1
  244. package/dist/tools/file-mutation-queue.d.ts +1 -1
  245. package/dist/tools/file-mutation-queue.d.ts.map +1 -1
  246. package/dist/tools/file-mutation-queue.js.map +1 -1
  247. package/dist/tools/index.d.ts +32 -32
  248. package/dist/tools/path-utils.d.ts +1 -1
  249. package/dist/tools/path-utils.d.ts.map +1 -1
  250. package/dist/tools/path-utils.js.map +1 -1
  251. package/dist/tools/read.d.ts +6 -6
  252. package/dist/tools/read.d.ts.map +1 -1
  253. package/dist/tools/read.js +19 -8
  254. package/dist/tools/read.js.map +1 -1
  255. package/dist/tools/write.d.ts +4 -4
  256. package/dist/tools/write.d.ts.map +1 -1
  257. package/dist/tools/write.js +5 -5
  258. package/dist/tools/write.js.map +1 -1
  259. package/dist/types.d.ts +228 -78
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +19 -12
  263. package/dist/harness/events.d.ts +0 -186
  264. package/dist/harness/events.d.ts.map +0 -1
  265. package/dist/harness/events.js +0 -373
  266. package/dist/harness/events.js.map +0 -1
  267. package/dist/session/observation.d.ts +0 -42
  268. package/dist/session/observation.d.ts.map +0 -1
  269. package/dist/session/observation.js +0 -281
  270. package/dist/session/observation.js.map +0 -1
@@ -1,11 +1,14 @@
1
- import type { AttachedReplicatedState, Context, JsonValue } from "@earendil-works/chord";
2
- import type { AssistantMessage, CacheRetention, ClientIdentity, Message, Models, ModelThinkingLevel, Static, Tool, ToolCall, ToolResultMessage, Transport, TSchema, Usage, UserMessage } from "@earendil-works/pi-ai";
1
+ import type { Context } from "@earendil-works/chord/context";
2
+ import type { JsonValue } from "@earendil-works/chord/json";
3
+ import type { WatchHandle } from "@earendil-works/chord/watch";
4
+ import type { Api, AssistantMessage, CacheRetention, ClientIdentity, Message, Model, Models, ModelThinkingLevel, ThinkingBudgets, Tool, ToolCall, ToolResultMessage, Transport, Usage, UserMessage } from "@earendil-works/pi-ai";
5
+ import type { Static } from "@earendil-works/pi-schema";
3
6
  import type { TelemetryContext } from "@earendil-works/pi-telemetry";
4
7
  import type { ExecutionEnv, ShellOutputSkip, ShellOutputWindow } from "../env/index.ts";
5
- import type { ConversationId, ConversationOwnership, ConversationRecord, Cursor, DocumentObserver, DocumentReader, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Page, ReportInfo, Session, SetTimer, SubmissionId, SubmissionRecord, Task, TaskId, TaskOptions, TaskRecord, TaskState, Tx, WatchHandle } from "../types.ts";
8
+ import type { CommitOptions, ConversationId, ConversationOwnership, ConversationRecord, Cursor, DocumentObserver, DocumentReader, Entry, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Observation, Observer, Page, Report, Session, SetTimer, SubmissionId, SubmissionRecord, Task, TaskId, TaskOptions, TaskRecord, TaskState, Tx, TypedEntry } from "../types.ts";
6
9
  import type { TaskGraph, TaskGraphWatch } from "./task-graph.ts";
7
10
  import type { UsageState } from "./usage.ts";
8
- import type { ConversationView } from "./view.ts";
11
+ import type { ConversationView, ConversationWindow } from "./view.ts";
9
12
  /** Provider and model ID resolved through pi-ai `Models`. */
10
13
  export type ModelRef = {
11
14
  readonly provider: string;
@@ -20,13 +23,23 @@ export type InputEntry = {
20
23
  readonly kind: string;
21
24
  readonly data?: JsonValue;
22
25
  };
23
- /** Host submission: user input that may start a run, or a passive entry write. */
26
+ /**
27
+ * Host submission: user input that may start a run, a passive entry write, or a continue: a run without a new user
28
+ * message, for an idle conversation whose context the model still owes an answer (it ends in a user message or tool
29
+ * results, for example after a run that ended without an answer).
30
+ */
24
31
  export type SubmissionDraft = {
25
32
  readonly requestId?: string;
26
33
  } & ({
27
34
  readonly type: "input";
28
35
  readonly content: UserInput;
29
36
  readonly whenBusy?: "steer" | "followUp" | "reject";
37
+ /**
38
+ * `reject`: an idle conversation (no run) rejects the input with `ConversationIdle` and writes nothing, so the input
39
+ * never starts a run of its own. With `whenBusy: "steer"` it steers the current run or is refused; a run that
40
+ * answered and waits for its `afterRun` tasks is still busy, and such an input queues and starts the next run.
41
+ */
42
+ readonly whenIdle?: "reject";
30
43
  /** Place the input as this entry; default `pi.user` without data. The kind must not start with `pi.`. */
31
44
  readonly entry?: InputEntry;
32
45
  } | {
@@ -34,6 +47,20 @@ export type SubmissionDraft = {
34
47
  readonly entry: EntryDraft;
35
48
  readonly content?: never;
36
49
  readonly whenBusy?: never;
50
+ readonly whenIdle?: never;
51
+ } | {
52
+ /**
53
+ * Start a run on the context as it is, without a user message. Only when idle: a busy conversation rejects it with
54
+ * `ConversationBusy`, and it never queues. Items kept in `pi.inbox` are placed first and join the run. A context
55
+ * with nothing to answer (empty, or ending in an answer, also a text answer cut off at its output limit) ends the
56
+ * run without a request, `unanswered` with `nothing_to_answer`. A cut-off answer with tool calls is continued:
57
+ * its calls never ran, and the context ends in their error results (`missing_result`).
58
+ */
59
+ readonly type: "continue";
60
+ readonly content?: never;
61
+ readonly entry?: never;
62
+ readonly whenBusy?: never;
63
+ readonly whenIdle?: never;
37
64
  });
38
65
  export type InputSubmissionDraft = Extract<SubmissionDraft, {
39
66
  readonly type: "input";
@@ -77,12 +104,16 @@ export type ConversationAbortOptions = {
77
104
  /** Hook handler map declared by a task definition. */
78
105
  export type HooksOf<K> = K extends Task<infer _I, infer _S, infer _R, infer H> ? H : never;
79
106
  /**
80
- * Invocation-bound conversation operations for tasks and tools. Rejects after the invocation ends; passive entries are
81
- * written with ordinary transaction writes instead.
107
+ * Invocation-bound conversation operations for tasks and tools. Rejects after the invocation ends. `submit` takes any
108
+ * submission, in both forms of `Conversation.submit()`: a subagent's conversation is an ordinary one, so a tool may also
109
+ * continue it, and a task may admit a submission in the same commit as its own writes (`runtime.commit()`); entries
110
+ * the admission appends there carry the task's `byTaskId`. The transaction form also rejects once the invocation is
111
+ * aborted.
82
112
  */
83
113
  export interface ConversationHandle {
84
114
  readonly id: ConversationId;
85
- submit(submission: InputSubmissionDraft, context: Context): Promise<Submission>;
115
+ submit(submission: SubmissionDraft, context: Context): Promise<Submission>;
116
+ submit(tx: Tx, submission: SubmissionDraft): Promise<SubmissionId>;
86
117
  /** `Conversation.abort()`: withdraw queued inputs, abort the ordinary ownership scope, and wait until it is idle. */
87
118
  abort(context: Context, options?: ConversationAbortOptions): Promise<void>;
88
119
  /** Resolve when the conversation's ordinary ownership scope has no live non-background task. */
@@ -93,6 +124,10 @@ export type ToolControl = {
93
124
  readonly addTools?: readonly string[];
94
125
  /** End the run without another model request when every result of the round sets it to `true`; `false` is a no-op. */
95
126
  readonly terminate?: boolean;
127
+ /**
128
+ * Start a new context with this text as its user message, as `reset(handoff)` writes, and continue the run there.
129
+ * The last handoff in call order wins.
130
+ */
96
131
  readonly handoff?: string;
97
132
  };
98
133
  /** Remark about a call for the model and the UI, such as truncation or a spill path; never part of the tool's data. */
@@ -189,7 +224,8 @@ export interface ToolExecutionApi<TDetails extends JsonValue = JsonValue> extend
189
224
  diagnostic(diagnostic: ToolDiagnostic): void;
190
225
  /** Replace running details; the last value becomes the result details when the result omits `details`. */
191
226
  details(value: TDetails, context: Context): Promise<void>;
192
- commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context): Promise<T>;
227
+ /** A commit of this call's task; `options` may defer storing it (`CommitPersistence`). */
228
+ commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context, options?: CommitOptions): Promise<T>;
193
229
  memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;
194
230
  memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;
195
231
  /**
@@ -235,11 +271,18 @@ export interface ToolExecutionApi<TDetails extends JsonValue = JsonValue> extend
235
271
  readonly progress?: boolean;
236
272
  }): Promise<NestedToolExecutionResult>;
237
273
  }
274
+ /**
275
+ * The arguments type of a tool with parameter schema `TParameters`: pi-schema's `Static<TParameters>` (the builders'
276
+ * type, TypeBox's for the common TypeBox kinds, else `unknown`), and `any` for `ToolRegistration<any>`.
277
+ */
278
+ export type ToolArgs<TParameters> = IsAny<TParameters> extends true ? any : Static<TParameters>;
279
+ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
238
280
  /**
239
281
  * Executable tool registered in a registry. Only pi-ai `Tool` fields enter the transcript. `args` are typed by
240
- * `parameters`, which the Harness validates them against before `execute()`; `defineTool()` infers both generics.
282
+ * `parameters` (any JSON Schema object; see {@link ToolArgs}), which the Harness validates them against before
283
+ * `execute()`; `defineTool()` infers both generics.
241
284
  */
242
- export type ToolRegistration<TParameters extends TSchema = TSchema, TDetails extends JsonValue = JsonValue> = Tool<TParameters> & {
285
+ export type ToolRegistration<TParameters extends object = object, TDetails extends JsonValue = JsonValue> = Tool<TParameters> & {
243
286
  /** Whether an interrupted execution may rerun on recovery. Default `unsafe`. */
244
287
  readonly replay?: "safe" | "unsafe";
245
288
  /** Default: the settings' `toolExecution`. One sequential call makes its whole round sequential. */
@@ -249,12 +292,12 @@ export type ToolRegistration<TParameters extends TSchema = TSchema, TDetails ext
249
292
  * pure and must not mutate `args`: it runs again when a call is retried before its intent is recorded. Its result is
250
293
  * still validated against `parameters`.
251
294
  */
252
- prepareArguments?(args: unknown): Static<TParameters>;
295
+ prepareArguments?(args: unknown): ToolArgs<TParameters>;
253
296
  /**
254
297
  * Schema of `structuredOutput`, which programs that call the tool receive. Omitted: they receive the result's bounded
255
298
  * output, success or error, as `NestedToolExecutionResult.structuredOutput` describes.
256
299
  */
257
- readonly structuredOutputSchema?: TSchema;
300
+ readonly structuredOutputSchema?: object;
258
301
  /**
259
302
  * Who may call the tool: the model, other tools through `executeTool()`, or both (default). A code mode tool is
260
303
  * `["model"]`, so scripts cannot start scripts; tools behind code mode, such as MCP tools, can be `["tools"]`, so the
@@ -271,7 +314,7 @@ export type ToolRegistration<TParameters extends TSchema = TSchema, TDetails ext
271
314
  readonly maxLines?: number;
272
315
  readonly retain?: "head" | "tail";
273
316
  };
274
- execute(args: Static<TParameters>, api: ToolExecutionApi<TDetails>, context: Context): Promise<ToolExecutionResult<TDetails>>;
317
+ execute(args: ToolArgs<TParameters>, api: ToolExecutionApi<TDetails>, context: Context): Promise<ToolExecutionResult<TDetails>>;
275
318
  };
276
319
  /** Input to system prompt section rendering for one request preparation. */
277
320
  export type PromptInput<Tool extends ToolRegistration = ToolRegistration> = {
@@ -377,6 +420,17 @@ export type AgentState = {
377
420
  instructions?: string;
378
421
  /** Directory within the environment's file system, passed to `HarnessOptions.env`. */
379
422
  cwd?: string;
423
+ /**
424
+ * Tool rounds one run takes at most (`pi.live.run.turns`): the round that reaches it runs its tools, then the run ends
425
+ * `unanswered` with `turn_limit`. An integer, `1` or more; unset, no limit. Task-owned children and forks inherit it.
426
+ */
427
+ maxTurns?: number;
428
+ /**
429
+ * This conversation's compaction settings over the harness's, field by field: its `modelOverrides` entry, then the
430
+ * harness's, then its own fields, then the harness-wide policy. Its `enabled: false` also beats the harness's model
431
+ * override. Task-owned children and forks inherit it.
432
+ */
433
+ compaction?: CompactionSettings;
380
434
  };
381
435
  /** A change to `pi.agent`: a given field replaces the stored one, `null` clears it, `undefined` changes nothing. */
382
436
  export type AgentChange = {
@@ -396,6 +450,10 @@ export type AgentChange = {
396
450
  } | null;
397
451
  readonly instructions?: string | null;
398
452
  readonly cwd?: string | null;
453
+ /** `configure()` throws a `RangeError` unless it is an integer `1` or more (or `null`). */
454
+ readonly maxTurns?: number | null;
455
+ /** Replaces the stored settings as a whole; `configure()` throws a `RangeError` unless every token count is an integer `0` or more. */
456
+ readonly compaction?: CompactionSettings | null;
399
457
  };
400
458
  /** A conversation's agent resolved against a registry snapshot and the settings. */
401
459
  export type Agent<Tool extends ToolRegistration = ToolRegistration> = {
@@ -412,6 +470,13 @@ export type Agent<Tool extends ToolRegistration = ToolRegistration> = {
412
470
  readonly sections: readonly PromptSection<Tool>[];
413
471
  readonly instructions?: string;
414
472
  readonly cwd?: string;
473
+ /**
474
+ * Tool rounds one run takes at most (`pi.live.run.turns`): the round that reaches it runs its tools, then the run ends
475
+ * `unanswered` with `turn_limit`. An integer, `1` or more; unset, no limit. Task-owned children and forks inherit it.
476
+ */
477
+ readonly maxTurns?: number;
478
+ /** This conversation's compaction settings over the harness's (`AgentState.compaction`). */
479
+ readonly compaction?: CompactionSettings;
415
480
  };
416
481
  /**
417
482
  * Runs inside the creating commit, after the creation hook and the `agent` change. The conversation creation is already
@@ -428,6 +493,10 @@ export type ConversationCreateOptions = {
428
493
  export type ConversationStreamOptions = {
429
494
  transport?: Transport;
430
495
  timeoutMs?: number;
496
+ /** WebSocket connection/open handshake timeout; stream idleness uses `timeoutMs`. */
497
+ websocketConnectTimeoutMs?: number;
498
+ /** Custom token budgets for thinking levels on token-based providers. */
499
+ thinkingBudgets?: ThinkingBudgets;
431
500
  /** Provider/SDK retries inside one request attempt. */
432
501
  maxRetries?: number;
433
502
  maxRetryDelayMs?: number;
@@ -457,21 +526,29 @@ export type CompactionPolicy = {
457
526
  backgroundTokens: number;
458
527
  };
459
528
  /**
460
- * Harness-wide compaction settings. `modelOverrides` is keyed by exact `"provider/modelId"`; each field it sets
461
- * replaces the harness-wide one for that model, so small- and large-window models can share a harness.
529
+ * Compaction settings of the harness (`HarnessSettings.compaction`) or of one conversation (`AgentState.compaction`).
530
+ * `modelOverrides` is keyed by exact `"provider/modelId"`; each field it sets replaces the general one for that model,
531
+ * so small- and large-window models can share a harness.
462
532
  */
463
533
  export type CompactionSettings = Partial<CompactionPolicy> & {
464
534
  readonly modelOverrides?: Readonly<Record<string, Partial<CompactionPolicy>>>;
465
535
  };
466
536
  /**
467
- * How often running progress is committed. Each progress commit is a storage write; a host whose storage is remote can
468
- * commit less often, so live answers and tool output appear in larger steps.
537
+ * How often running progress is committed, and how long it may wait in memory before it is stored. Progress commits are
538
+ * deferred (`CommitPersistence`): watches see each at once, and Storage gets them together, at the run's next phase
539
+ * boundary or after `persistWithinMs`. A host can commit less often, so live answers and tool output appear in larger steps.
469
540
  */
470
541
  export type ProgressPolicy = {
471
542
  /** Minimum pause between commits of the answer being generated. */
472
543
  partialIntervalMs: number;
473
544
  /** Minimum pause between commits of running tool output; large commits also pause in proportion to their size. */
474
545
  outputIntervalMs: number;
546
+ /**
547
+ * How long these commits, and other deferred commits without their own `withinMs`, may stay in memory before they are
548
+ * stored (`CommitPersistence`); a crash loses at most about this much of an answer or of tool output. Default 1,000;
549
+ * `Infinity` stores them only at the next commit that reads or writes their document and at close.
550
+ */
551
+ persistWithinMs: number;
475
552
  };
476
553
  /** Why a compaction runs: `compact()`, a threshold in generation preparation, or a context overflow. */
477
554
  export type CompactionReason = "manual" | "threshold" | "overflow";
@@ -500,25 +577,32 @@ export type HarnessSettings = {
500
577
  */
501
578
  readonly contextRetentionMs?: number;
502
579
  /**
503
- * How long a loaded document that no watch or document state observes stays in memory after its last read or write;
580
+ * How long a loaded document that no watch observes stays in memory after its last read or write;
504
581
  * the next access loads it from Storage. Observed documents always stay. Default ten minutes; `0` drops one right
505
582
  * after use; `Infinity` keeps them. Anything else that is not a number of milliseconds means the default.
506
583
  */
507
584
  readonly documentRetentionMs?: number;
508
585
  /**
509
- * What a run that fails (`model_error`, `no_model`) does with the inputs queued behind it: `keep` leaves them
510
- * queued until the next input or queued write, `continue` starts the next run with them, as an answer would, and
511
- * `withdraw` settles them `unanswered` with reason `run_failed`. Default `keep`.
586
+ * What a run that fails (`model_error`, `no_model`, `interrupted`, `restart_limit`, `nothing_to_answer`,
587
+ * `turn_limit`) does with the inputs queued behind it: `keep` leaves them queued until the next input or queued write, `continue` starts the
588
+ * next run with them, as an answer would, and `withdraw` settles them `unanswered` with reason `run_failed`. Default
589
+ * `keep`.
512
590
  */
513
591
  readonly afterFailedRun?: AfterFailedRun;
514
592
  /**
515
593
  * How many times in a row a task may be cut off by a crash, eviction, or reset (a close does not count) without
516
- * changing its state before it is parked as blocked (`restart_limit`) instead of run again; `Harness.retryTask()`
517
- * runs it again. Default 5; `Infinity` never parks.
594
+ * changing its state before it ends instead of running again: a tool call with an error result, a compaction failed
595
+ * into `pi.live.compactionFailure`, a run with `pi.live.runFailure` reason `restart_limit`, any other task `faulted`
596
+ * with detail `{ reason: "restart_limit" }`. A generation's request or poll the host died during is a failed attempt
597
+ * under `retry` first; the limit bounds it only when charging that attempt keeps being cut off. Default 5;
598
+ * `Infinity` never ends a task for it.
518
599
  */
519
600
  readonly restartLimit?: number;
520
601
  };
521
- /** What a failed run does with the inputs queued behind it (`HarnessSettings.afterFailedRun`). */
602
+ /**
603
+ * What a failed run does with the inputs queued behind it (`HarnessSettings.afterFailedRun`): a run that ends with
604
+ * `model_error`, `no_model`, `interrupted`, `restart_limit`, `nothing_to_answer`, or `turn_limit`.
605
+ */
522
606
  export type AfterFailedRun = "keep" | "continue" | "withdraw";
523
607
  /** Resolved settings: every field over its built-in default, object fields merged. */
524
608
  export type Settings = {
@@ -587,18 +671,27 @@ export type HarnessOptions<Tool extends ToolRegistration = ToolRegistration> = {
587
671
  * Wakes the process for sleeping tasks instead of a `setTimer` timer. Without it, while scheduling runs, a timer that
588
672
  * keeps the process alive resumes sleeping tasks when they are due. With it, the Harness arms no timer for them: it
589
673
  * calls `onWake` after open and whenever `Harness.wake()` changes, and the host arms its own wakeup, such as a
590
- * Durable Object alarm, and calls `resume()` when it fires. Called off the Session line, after the commit that
591
- * changed the value, so a host arms a recovery wakeup before calls that can start work. It may call `wake()` and
592
- * `resume()`, and must not block. A throw is reported, and the value is offered again at the next change.
674
+ * Durable Object alarm, and calls `resume()` when it fires. Called in a microtask the Session line never waits for,
675
+ * after the commit that changed the value was adopted, so a host arms a recovery wakeup before calls that can start
676
+ * work. It may call `wake()` and `resume()`, and must not block. A throw is reported, and the value is offered again
677
+ * at the next change.
593
678
  */
594
679
  readonly onWake?: (wake: Wake) => void;
595
680
  /**
596
681
  * Receives failures that do not fail the calling operation, such as a throwing hook or listener, a task that faulted,
597
- * and the error that fails the Session, once. `info` says what happened: a host can tell a failed Session
598
- * (`session_failed`, often a platform reset) from a faulted task (`task_faulted`) or a parked one (`task_parked`).
599
- * Its own throw or rejection is dropped.
682
+ * and the error that fails the Session, once: one `Report`, the `error` and what happened (`kind` and the fields of
683
+ * its `ReportInfo`). A host can tell a failed Session (`storage_failed`, often a platform reset) from a faulted task
684
+ * (`task_faulted`). Its own throw or rejection is dropped.
600
685
  */
601
- readonly onReport?: (error: unknown, info: ReportInfo) => void;
686
+ readonly onReport?: (report: Report) => void;
687
+ /**
688
+ * Open for reading only, as a viewer of a session that another process drives, for example a synced copy on a device
689
+ * that only shows it. Open recovers nothing: a `running` task stays as committed. Every commit with writes rejects
690
+ * with `ReadOnlySession` before it reaches Storage, and so does `resume()` and every call that enables scheduling;
691
+ * the Harness stays usable for reads and watches. `wake()` is always inactive, and `onWake` is never called. Reopen
692
+ * without it to write.
693
+ */
694
+ readonly readOnly?: boolean;
602
695
  };
603
696
  /**
604
697
  * When a Session next needs a running process (`Harness.wake()`). `active`: some task can run now or is running, or a
@@ -619,7 +712,7 @@ export type TaskInspection = {
619
712
  }
620
713
  /** The next scheduling pass reserves it; `migrates` when its definition is newer and has `migrate`. */
621
714
  | {
622
- readonly kind: "ready";
715
+ readonly kind: "pending";
623
716
  readonly migrates: boolean;
624
717
  }
625
718
  /**
@@ -639,13 +732,10 @@ export type TaskInspection = {
639
732
  | {
640
733
  readonly kind: "completing";
641
734
  }
642
- /**
643
- * Cannot run here: no registered definition can take it (aborting it settles it as `orphaned`), or it is parked by
644
- * the restart limit (`retryTask()` runs it again).
645
- */
735
+ /** Cannot run here: no registered definition can take it (aborting it settles it as `orphaned`). */
646
736
  | {
647
737
  readonly kind: "blocked";
648
- readonly reason: "missing_task" | "task_too_old" | "migration_failed" | "restart_limit";
738
+ readonly reason: "missing_task" | "task_too_old" | "migration_failed";
649
739
  readonly error?: unknown;
650
740
  };
651
741
  };
@@ -675,12 +765,25 @@ export interface Conversation {
675
765
  /** `configure()` in its own commit. */
676
766
  configure(change: AgentChange, context: Context): Promise<void>;
677
767
  /**
678
- * Durably admit user input or a passive entry write. A busy conversation, or one with queued items, queues input and
679
- * writes that affect model context in `pi.inbox`; `whenBusy: "reject"` rejects input with `ConversationBusy` instead
680
- * and writes nothing. A write of only `kind` and `data` (no `model`, `head`, or `edits`) is appended at once,
681
- * even mid-run, as it changes nothing the model sees.
768
+ * Durably admit user input, a passive entry write, or a continue. A busy conversation, or one with queued items,
769
+ * queues input and writes that affect model context in `pi.inbox`; `whenBusy: "reject"` rejects input with
770
+ * `ConversationBusy` instead and writes nothing. An idle conversation rejects `whenIdle: "reject"` input with
771
+ * `ConversationIdle` and writes nothing. A write of only `kind` and `data` (no `model`, `head`, or `edits`)
772
+ * is appended at once, even mid-run, as it changes nothing the model sees. A continue starts a run on the context as
773
+ * it is, after placing queued items; a busy conversation rejects it with `ConversationBusy`, as it never queues.
682
774
  */
683
775
  submit(submission: SubmissionDraft, context: Context): Promise<Submission>;
776
+ /**
777
+ * Admit a submission inside the caller's commit `tx` (`Harness.commit()`, `Conversation.commit()`, a task's
778
+ * `runtime.commit()`), with the same rules, and return its ID: the admission is stored with that commit, or not at all
779
+ * when the callback throws later or the commit fails. Call it before the commit's first table write (`appendEntry()`,
780
+ * `createTask()`, `createSubmission()`, another admission): otherwise it throws `ReadAfterWrite`, on every path.
781
+ * Documents may be read and written before and after it. A rejection (`ConversationBusy`, `ConversationIdle`) throws
782
+ * before any write, so the callback may catch it. Do not call `Submission` or other Harness reads inside the callback:
783
+ * they wait for the commit holding the line. Its `pi.durable.submit` span covers the admission only, so its `result`
784
+ * is provisional. Throws for a transaction of another Session.
785
+ */
786
+ submit(tx: Tx, submission: SubmissionDraft): Promise<SubmissionId>;
684
787
  /**
685
788
  * Admit a write of a `pi.reset` entry that starts a new context, carrying `handoff` as a user message when given.
686
789
  * Resolves after admission; while busy, it is placed at the next boundary.
@@ -691,8 +794,8 @@ export interface Conversation {
691
794
  * summary through a write submission: at once when idle, otherwise at the next boundary (spec §8.7).
692
795
  */
693
796
  compact(instructions: string | undefined, context: Context): Promise<TaskId<CompactionResult>>;
694
- /** Session commit whose `tx.createTask()` defaults to this conversation. */
695
- commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context): Promise<T>;
797
+ /** Session commit whose `tx.createTask()` defaults to this conversation; `options` as for `Session.commit()`. */
798
+ commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context, options?: CommitOptions): Promise<T>;
696
799
  /**
697
800
  * Committed raw active transcript and model context. With `at`, the context as of that visible entry: the same view
698
801
  * `fork(at)` would start with, without creating a conversation.
@@ -713,18 +816,21 @@ export interface Conversation {
713
816
  * owned, transitively, by its non-background tasks.
714
817
  */
715
818
  waitForIdle(context: Context): Promise<void>;
716
- /** The structural view (spec §9.3) as a disposable read-only Chord state. */
717
- viewState(context: Context): Promise<AttachedReplicatedState<ConversationView>>;
718
- /** The structural view as a serialized exact-frame watch with bounded pending frames. */
719
- watch(context: Context): Promise<ConversationWatch>;
819
+ /**
820
+ * The structural view (spec §9.3) as a serialized watch; commits made while its listener runs arrive folded. Its
821
+ * `entries` are a window over the transcript, as `window` says: the newest 50 by default, then appends, dropping the
822
+ * oldest past 100. Built on the same mount as `Harness.observeConversation()`.
823
+ */
824
+ watch(context: Context, window?: ConversationWindow): Promise<ConversationWatch>;
720
825
  }
721
826
  export type ConversationWatch = WatchHandle<ConversationView>;
722
827
  /** Durable agent harness over one Session. */
723
828
  export interface Harness extends Session {
724
829
  /**
725
- * Enable task scheduling. Idempotent; throws after close. Calls that ask for progress enable it too:
726
- * `Conversation.submit()`, `Conversation.compact()`, `Conversation.abort()`, `Submission.wait()`, `waitForTask()`,
727
- * `Harness.waitForIdle()`, and `Conversation.waitForIdle()`. Read-only viewers never do.
830
+ * Enable task scheduling. Idempotent; throws after close, and `ReadOnlySession` on a read-only Harness. Calls that
831
+ * ask for progress enable it too: `Conversation.submit()`, `Conversation.compact()`, `Conversation.abort()`,
832
+ * `Submission.wait()`, `waitForTask()`, `Harness.waitForIdle()`, and `Conversation.waitForIdle()`. Read-only viewers
833
+ * never do.
728
834
  */
729
835
  resume(): void;
730
836
  /**
@@ -753,23 +859,35 @@ export interface Harness extends Session {
753
859
  * definition cannot take it settles as `orphaned` instead.
754
860
  */
755
861
  abortTask(id: TaskId, context: Context): Promise<"marked" | "terminal">;
756
- /**
757
- * Run a task parked by `settings.restartLimit` again: clears its restart count and enables scheduling. Does nothing
758
- * for a task that is not parked; rejects for an unknown one.
759
- */
760
- retryTask(id: TaskId, context: Context): Promise<void>;
761
862
  /** Resolve with the terminal receipt; cancelling `context` cancels only this wait. */
762
863
  waitForTask<R>(id: TaskId<R>, context: Context): Promise<SettledTask<R>>;
763
864
  /** Resolve when the ordinary ownership scope of every ownerless conversation has no live non-background task. */
764
865
  waitForIdle(context: Context): Promise<void>;
765
866
  /** Session total: every conversation's `pi.usage` summed. */
766
867
  usage(context: Context): Promise<UsageState>;
767
- /** Every live task with its owner edge, status, and owned conversations (spec §9.5), as a disposable Chord state. */
768
- taskGraph(context: Context): Promise<AttachedReplicatedState<TaskGraph>>;
769
- /** The task graph as a serialized exact-frame watch with bounded pending frames. */
868
+ /**
869
+ * Every live task with its owner edge, status, and owned conversations (spec §9.4), as a serialized watch; commits
870
+ * made while its listener runs arrive folded.
871
+ */
770
872
  watchTaskGraph(context: Context): Promise<TaskGraphWatch>;
873
+ /**
874
+ * Attach a synchronous `Observer` to a conversation's structural view (spec §9.3), atomically on the Session line,
875
+ * for adapters: `advance` gets each next revision with its operations as memory adopts the commit, inside Storage's
876
+ * `adopt` for a stored one and before it is stored for a deferred one, so it only records it. `window` as for
877
+ * `Conversation.watch()`; each observer has its own. `context` governs only the attachment.
878
+ */
879
+ observeConversation(id: ConversationId, observer: Observer<ConversationView>, context: Context, window?: ConversationWindow): Promise<Observation<ConversationView>>;
880
+ /**
881
+ * Attach a synchronous `Observer` to the task graph (spec §9.4), as `observeConversation()` does to a view. A
882
+ * `blocked` reason that changes without a commit (a registry change, a failed migration) advances it on the Session
883
+ * line outside any commit and `adopt`, with the background context.
884
+ */
885
+ observeTaskGraph(observer: Observer<TaskGraph>, context: Context): Promise<Observation<TaskGraph>>;
771
886
  }
772
- /** What a hook may use: committed reads and the asking task's memos, which hooks and the task share. */
887
+ /**
888
+ * What a hook may use: committed reads and the asking task's memos, which hooks and the task share. Hooks run off the
889
+ * Session line, so reads see what is committed when they run; a hook rerun after a crash may see more.
890
+ */
773
891
  export interface HookApi extends DocumentReader {
774
892
  readonly taskId: TaskId;
775
893
  readonly conversationId: ConversationId;
@@ -777,6 +895,12 @@ export interface HookApi extends DocumentReader {
777
895
  readonly models: Models;
778
896
  memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;
779
897
  memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;
898
+ /** Committed entry visible from the task's conversation, as `TaskRuntime.entry()`. */
899
+ entry(id: EntryId, context: Context): Promise<EntryRecord | undefined>;
900
+ /** Undefined when the entry is absent, not visible, or has another kind. */
901
+ entry<D extends JsonValue>(token: Entry<D>, id: EntryId, context: Context): Promise<TypedEntry<D> | undefined>;
902
+ /** Committed raw history of the task's conversation, as `TaskRuntime.entries()`. */
903
+ entries(query: Omit<EntryQuery, "conversationId">, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<EntryRecord, Cursor>>;
780
904
  }
781
905
  export type HookResult<T> = T | undefined | Promise<T | undefined>;
782
906
  /** Hooks of the built-in generation task. */
@@ -787,6 +911,17 @@ export interface GenerationHooks {
787
911
  }, api: HookApi, context: Context): HookResult<{
788
912
  readonly messages: readonly Message[];
789
913
  }>;
914
+ /**
915
+ * After pi-ai builds the provider request body, before it is sent; the result replaces the body for that request only.
916
+ * Runs again whenever pi-ai rebuilds the body, such as a retry within one attempt. The body is in the provider API's
917
+ * own format (`request.model.api`), so a hook checks the API before changing it.
918
+ */
919
+ beforeProviderRequest(request: {
920
+ readonly model: Model<Api>;
921
+ readonly payload: unknown;
922
+ }, api: HookApi, context: Context): HookResult<{
923
+ readonly payload: unknown;
924
+ }>;
790
925
  /** Every terminal provider message, before classification. */
791
926
  afterResponse(message: AssistantMessage, api: HookApi, context: Context): void | Promise<void>;
792
927
  /** A final answer; the first `continue` appends a user message and continues the run. */
@@ -795,7 +930,36 @@ export interface GenerationHooks {
795
930
  }>;
796
931
  /** After every tool of the round is terminal; `results` are the round's result entries in call order. */
797
932
  afterTools(assistant: EntryId, results: readonly EntryId[], api: HookApi, context: Context): void | Promise<void>;
933
+ /**
934
+ * After a run answered, its answer committed and its inputs settled `done`: a final answer, or a `terminate` round;
935
+ * not when an `onYield` continuation is taken, and never after a failure or an abort. The requested tasks of every
936
+ * hook are created together as children of the generation, which keeps the run, and the conversation busy, until
937
+ * they are terminal; then queued items are placed and the run ends (spec §8.3). A crash before they are created asks
938
+ * again; once created, never again for this run.
939
+ */
940
+ afterRun(run: RunSummary, api: HookApi, context: Context): HookResult<{
941
+ readonly tasks: readonly RunEndTask[];
942
+ }>;
798
943
  }
944
+ /** How a run ended, for `afterRun` hooks. Read the run's history with `api.entries({ minEntryId: first, maxEntryId: last })`. */
945
+ export type RunSummary = {
946
+ /** A final answer, or a tool round whose every call asked to `terminate`. */
947
+ readonly reason: "answer" | "terminate";
948
+ /**
949
+ * The run's first entry (`LiveRun.first`): the first one the boundary that started it placed, for a continue the failed
950
+ * run's it continues, otherwise its first tool-calling answer; the answer itself for a run that has none of these.
951
+ */
952
+ readonly first: EntryId;
953
+ /** The answer, or the terminating round's tool-calling answer, whose results follow it. */
954
+ readonly answer: EntryId;
955
+ /** The run's last entry: the answer; for a `terminate` round its newest result, or the reset of its handoff. */
956
+ readonly last: EntryId;
957
+ };
958
+ /** A task an `afterRun` hook asks for, built by `runEndTask()`. */
959
+ export type RunEndTask = {
960
+ readonly task: AnyTask;
961
+ readonly input: JsonValue;
962
+ };
799
963
  /** A call as tool hooks see it; `parent` is set for a nested call, one a running tool made through `executeTool()`. */
800
964
  export type ToolHookCall = ToolCall & {
801
965
  readonly parent?: {
@@ -810,7 +974,18 @@ export interface ToolHooks {
810
974
  readonly arguments?: JsonObject;
811
975
  readonly block?: string;
812
976
  }>;
813
- /** After execution, before the result is committed; replaces the result. */
977
+ /**
978
+ * Before the result is committed; replaces the result. Runs on every result the tool task writes: an executed
979
+ * call's, and the error results the Harness writes itself, told apart by their diagnostic code: a tool that does not
980
+ * resolve (`tool_unavailable`), invalid arguments (`invalid_arguments`, with `call.arguments` as the model sent
981
+ * them), a block (`blocked`), and, rebuilt from the call's partial output, an interruption (`interrupted`), the
982
+ * restart limit (`restart_limit`), and an abort (`aborted`, `abandoned`). Once the task was cut off more than
983
+ * `settings.restartLimit` times the hooks no longer run, since they may be what cuts it off, nor in the last attempt
984
+ * the scheduler gives an abort handler (restarts at the limit minus one). A result's `structuredOutput` is dropped
985
+ * when the Harness wrote it (a nested call a hook turns into a success gets the bounded output as a schema-less tool's
986
+ * does), and its `control` applies only to a call that completes (not to an interrupted, cut-off, or aborted one).
987
+ * An abort's hooks run inside the abort handler, which cannot be aborted: a slow hook delays `Conversation.abort()`.
988
+ */
814
989
  afterTool(call: ToolHookCall, result: ToolExecutionResult, api: HookApi, context: Context): HookResult<ToolExecutionResult>;
815
990
  }
816
991
  /** Hooks of the built-in compaction task. */
@@ -832,4 +1007,5 @@ export interface CompactionHooks {
832
1007
  readonly summary: string;
833
1008
  }>;
834
1009
  }
1010
+ export {};
835
1011
  //# sourceMappingURL=types.d.ts.map