@earendil-works/pi-durable 0.99.1 → 0.99.2

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 (172) hide show
  1. package/README.md +435 -46
  2. package/dist/entries.d.ts +20 -2
  3. package/dist/entries.d.ts.map +1 -1
  4. package/dist/entries.js +14 -1
  5. package/dist/entries.js.map +1 -1
  6. package/dist/env/index.d.ts +13 -38
  7. package/dist/env/index.d.ts.map +1 -1
  8. package/dist/env/index.js +2 -0
  9. package/dist/env/index.js.map +1 -1
  10. package/dist/env/node.d.ts.map +1 -1
  11. package/dist/env/node.js +51 -58
  12. package/dist/env/node.js.map +1 -1
  13. package/dist/harness/compaction.d.ts +56 -0
  14. package/dist/harness/compaction.d.ts.map +1 -0
  15. package/dist/harness/compaction.js +385 -0
  16. package/dist/harness/compaction.js.map +1 -0
  17. package/dist/harness/config.d.ts +10 -1
  18. package/dist/harness/config.d.ts.map +1 -1
  19. package/dist/harness/config.js +6 -0
  20. package/dist/harness/config.js.map +1 -1
  21. package/dist/harness/context.d.ts +8 -0
  22. package/dist/harness/context.d.ts.map +1 -1
  23. package/dist/harness/context.js +32 -21
  24. package/dist/harness/context.js.map +1 -1
  25. package/dist/harness/events.d.ts +166 -0
  26. package/dist/harness/events.d.ts.map +1 -0
  27. package/dist/harness/events.js +319 -0
  28. package/dist/harness/events.js.map +1 -0
  29. package/dist/harness/generation.d.ts +37 -6
  30. package/dist/harness/generation.d.ts.map +1 -1
  31. package/dist/harness/generation.js +350 -72
  32. package/dist/harness/generation.js.map +1 -1
  33. package/dist/harness/harness.d.ts.map +1 -1
  34. package/dist/harness/harness.js +124 -3
  35. package/dist/harness/harness.js.map +1 -1
  36. package/dist/harness/inbox.d.ts +57 -0
  37. package/dist/harness/inbox.d.ts.map +1 -0
  38. package/dist/harness/inbox.js +99 -0
  39. package/dist/harness/inbox.js.map +1 -0
  40. package/dist/harness/json.d.ts +10 -0
  41. package/dist/harness/json.d.ts.map +1 -0
  42. package/dist/harness/json.js +33 -0
  43. package/dist/harness/json.js.map +1 -0
  44. package/dist/harness/live.d.ts +66 -7
  45. package/dist/harness/live.d.ts.map +1 -1
  46. package/dist/harness/live.js +71 -7
  47. package/dist/harness/live.js.map +1 -1
  48. package/dist/harness/output.d.ts +62 -0
  49. package/dist/harness/output.d.ts.map +1 -0
  50. package/dist/harness/output.js +261 -0
  51. package/dist/harness/output.js.map +1 -0
  52. package/dist/harness/prompt.d.ts +10 -5
  53. package/dist/harness/prompt.d.ts.map +1 -1
  54. package/dist/harness/prompt.js +66 -13
  55. package/dist/harness/prompt.js.map +1 -1
  56. package/dist/harness/registry.d.ts.map +1 -1
  57. package/dist/harness/registry.js +11 -2
  58. package/dist/harness/registry.js.map +1 -1
  59. package/dist/harness/scheduler.d.ts +37 -12
  60. package/dist/harness/scheduler.d.ts.map +1 -1
  61. package/dist/harness/scheduler.js +632 -72
  62. package/dist/harness/scheduler.js.map +1 -1
  63. package/dist/harness/submissions.d.ts +12 -7
  64. package/dist/harness/submissions.d.ts.map +1 -1
  65. package/dist/harness/submissions.js +72 -33
  66. package/dist/harness/submissions.js.map +1 -1
  67. package/dist/harness/tool.d.ts +36 -0
  68. package/dist/harness/tool.d.ts.map +1 -0
  69. package/dist/harness/tool.js +412 -0
  70. package/dist/harness/tool.js.map +1 -0
  71. package/dist/harness/types.d.ts +185 -11
  72. package/dist/harness/types.d.ts.map +1 -1
  73. package/dist/harness/types.js.map +1 -1
  74. package/dist/harness/usage.d.ts +18 -0
  75. package/dist/harness/usage.d.ts.map +1 -0
  76. package/dist/harness/usage.js +57 -0
  77. package/dist/harness/usage.js.map +1 -0
  78. package/dist/harness/view.d.ts +43 -0
  79. package/dist/harness/view.d.ts.map +1 -0
  80. package/dist/harness/view.js +194 -0
  81. package/dist/harness/view.js.map +1 -0
  82. package/dist/index.d.ts +10 -4
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +6 -1
  85. package/dist/index.js.map +1 -1
  86. package/dist/session/observation.d.ts +18 -11
  87. package/dist/session/observation.d.ts.map +1 -1
  88. package/dist/session/observation.js +24 -13
  89. package/dist/session/observation.js.map +1 -1
  90. package/dist/session/session.d.ts +10 -1
  91. package/dist/session/session.d.ts.map +1 -1
  92. package/dist/session/session.js +18 -4
  93. package/dist/session/session.js.map +1 -1
  94. package/dist/session/transaction.d.ts +10 -1
  95. package/dist/session/transaction.d.ts.map +1 -1
  96. package/dist/session/transaction.js +83 -27
  97. package/dist/session/transaction.js.map +1 -1
  98. package/dist/storage/jsonl/storage.d.ts +22 -2
  99. package/dist/storage/jsonl/storage.d.ts.map +1 -1
  100. package/dist/storage/jsonl/storage.js +1 -1
  101. package/dist/storage/jsonl/storage.js.map +1 -1
  102. package/dist/storage/memory.js +1 -1
  103. package/dist/storage/memory.js.map +1 -1
  104. package/dist/storage/sqlite/migrations.js +1 -1
  105. package/dist/storage/sqlite/migrations.js.map +1 -1
  106. package/dist/testing/storage-benchmark.d.ts.map +1 -1
  107. package/dist/testing/storage-benchmark.js +0 -1
  108. package/dist/testing/storage-benchmark.js.map +1 -1
  109. package/dist/testing/storage-conformance.d.ts.map +1 -1
  110. package/dist/testing/storage-conformance.js +34 -2
  111. package/dist/testing/storage-conformance.js.map +1 -1
  112. package/dist/tools/bash.d.ts +28 -0
  113. package/dist/tools/bash.d.ts.map +1 -0
  114. package/dist/tools/bash.js +69 -0
  115. package/dist/tools/bash.js.map +1 -0
  116. package/dist/tools/edit-diff.d.ts +89 -0
  117. package/dist/tools/edit-diff.d.ts.map +1 -0
  118. package/dist/tools/edit-diff.js +386 -0
  119. package/dist/tools/edit-diff.js.map +1 -0
  120. package/dist/tools/edit.d.ts +18 -0
  121. package/dist/tools/edit.d.ts.map +1 -0
  122. package/dist/tools/edit.js +116 -0
  123. package/dist/tools/edit.js.map +1 -0
  124. package/dist/tools/env.d.ts +5 -0
  125. package/dist/tools/env.d.ts.map +1 -0
  126. package/dist/tools/env.js +7 -0
  127. package/dist/tools/env.js.map +1 -0
  128. package/dist/tools/file-mutation-queue.d.ts +5 -0
  129. package/dist/tools/file-mutation-queue.d.ts.map +1 -0
  130. package/dist/tools/file-mutation-queue.js +46 -0
  131. package/dist/tools/file-mutation-queue.js.map +1 -0
  132. package/dist/tools/image.d.ts +2 -0
  133. package/dist/tools/image.d.ts.map +1 -0
  134. package/dist/tools/image.js +92 -0
  135. package/dist/tools/image.js.map +1 -0
  136. package/dist/tools/index.d.ts +5 -0
  137. package/dist/tools/index.d.ts.map +1 -0
  138. package/dist/tools/index.js +5 -0
  139. package/dist/tools/index.js.map +1 -0
  140. package/dist/tools/path-utils.d.ts +5 -0
  141. package/dist/tools/path-utils.d.ts.map +1 -0
  142. package/dist/tools/path-utils.js +26 -0
  143. package/dist/tools/path-utils.js.map +1 -0
  144. package/dist/tools/read.d.ts +17 -0
  145. package/dist/tools/read.d.ts.map +1 -0
  146. package/dist/tools/read.js +100 -0
  147. package/dist/tools/read.js.map +1 -0
  148. package/dist/tools/write.d.ts +10 -0
  149. package/dist/tools/write.d.ts.map +1 -0
  150. package/dist/tools/write.js +30 -0
  151. package/dist/tools/write.js.map +1 -0
  152. package/dist/{env/utils/truncate.d.ts → truncate.d.ts} +1 -17
  153. package/dist/truncate.d.ts.map +1 -0
  154. package/dist/truncate.js +146 -0
  155. package/dist/truncate.js.map +1 -0
  156. package/dist/types.d.ts +86 -19
  157. package/dist/types.d.ts.map +1 -1
  158. package/dist/types.js.map +1 -1
  159. package/docs/pico-v5-handoff.md +366 -86
  160. package/docs/pico-v5.md +1373 -283
  161. package/package.json +11 -3
  162. package/dist/env/utils/adaptive-publisher.d.ts +0 -24
  163. package/dist/env/utils/adaptive-publisher.d.ts.map +0 -1
  164. package/dist/env/utils/adaptive-publisher.js +0 -79
  165. package/dist/env/utils/adaptive-publisher.js.map +0 -1
  166. package/dist/env/utils/output-capture.d.ts +0 -31
  167. package/dist/env/utils/output-capture.d.ts.map +0 -1
  168. package/dist/env/utils/output-capture.js +0 -238
  169. package/dist/env/utils/output-capture.js.map +0 -1
  170. package/dist/env/utils/truncate.d.ts.map +0 -1
  171. package/dist/env/utils/truncate.js +0 -294
  172. package/dist/env/utils/truncate.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../../src/harness/inbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGvE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAErG,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvD,2EAA2E;AAC3E,MAAM,MAAM,SAAS,GAClB;IAAE,EAAE,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAA;CAAE;AAC1F,wDAAwD;GACtD;IAAE,EAAE,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAE1D,4FAA4F;AAC5F,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAEhD,eAAO,MAAM,QAAQ,wDAQnB,CAAC;AAEH,2GAA2G;AAC3G,MAAM,MAAM,QAAQ,GAAG;IACtB,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;IACjC,4GAA4G;IAC5G,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1B,CAAC;AAEF,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzF;;;GAGG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAW/F;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAClC,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,EAAE,EAAE,WAAW,GAAG,OAAO,EACzB,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,CAAC,CAoCzB;AAED,iHAAiH;AACjH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAEtE;AAED,+EAA+E;AAC/E,wBAAsB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7G;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhG"}
@@ -0,0 +1,99 @@
1
+ import { defineDoc } from "../documents.js";
2
+ import { UserEntry } from "../entries.js";
3
+ import { ConversationConfig } from "./config.js";
4
+ export const InboxDoc = defineDoc({
5
+ kind: "pi.inbox",
6
+ version: 1,
7
+ scope: "conversation",
8
+ history: "latest",
9
+ fork: "initial",
10
+ initial: () => ({ items: [] }),
11
+ checkpointWhen: (value) => value.items.length === 0,
12
+ });
13
+ /**
14
+ * Read what a boundary needs. Table reads must precede the commit's first table write, so callers prepare the
15
+ * boundary at the start of their commit.
16
+ */
17
+ export async function prepareBoundary(tx, conversationId) {
18
+ const head = (await tx.latestHeadMarker(conversationId))?.head;
19
+ const inbox = await tx.doc(InboxDoc, conversationId);
20
+ const config = await tx.doc(ConversationConfig, conversationId);
21
+ return {
22
+ conversationId,
23
+ inbox,
24
+ steeringMode: config.steeringMode ?? "one-at-a-time",
25
+ followUpMode: config.followUpMode ?? "one-at-a-time",
26
+ head,
27
+ };
28
+ }
29
+ /**
30
+ * Place the queued items a boundary selects (spec §6): every write, the first or all steers, and at `final` the first
31
+ * or all follow-ups. A selected reset turns a `postTools` boundary into `final`. Writes are placed first and user
32
+ * items after them, each in ID order, so user items queued before a reset run in the new context. A write whose head
33
+ * targets an entry before the active range, including a range started earlier in this commit, is stale. Selected and
34
+ * stale items are removed positionally.
35
+ */
36
+ export async function applyBoundary(tx, boundary, at, now) {
37
+ const { conversationId, inbox } = boundary;
38
+ const items = inbox.items;
39
+ const reset = items.some((item) => item.mode === "write" && item.entry.head === "self");
40
+ const final = at === "final" || reset;
41
+ const pick = (mode, queueMode) => {
42
+ const indexes = items.flatMap((item, index) => (item.mode === mode ? [index] : []));
43
+ return queueMode === "all" ? indexes : indexes.slice(0, 1);
44
+ };
45
+ const writes = items.flatMap((item, index) => (item.mode === "write" ? [index] : []));
46
+ const users = [...pick("steer", boundary.steeringMode), ...(final ? pick("followUp", boundary.followUpMode) : [])];
47
+ users.sort((a, b) => a - b);
48
+ // `appendEntry()` copies the drafts' values; the items are removed only afterwards.
49
+ for (const index of writes) {
50
+ const item = items[index];
51
+ const draft = item.entry;
52
+ if (isStale(boundary, draft)) {
53
+ tx.settleSubmission(item.id, { status: "unanswered", reason: "stale" });
54
+ continue;
55
+ }
56
+ const entry = await tx.appendEntry(conversationId, draft);
57
+ if (draft.head !== undefined)
58
+ boundary.head = draft.head === "self" ? entry.id : draft.head;
59
+ tx.placeSubmission(item.id, entry.id);
60
+ }
61
+ const placed = [];
62
+ for (const index of users) {
63
+ const item = items[index];
64
+ const message = { role: "user", content: item.content, timestamp: now };
65
+ const entry = await tx.appendEntry(UserEntry, conversationId, { model: [message] });
66
+ tx.placeSubmission(item.id, entry.id);
67
+ placed.push(item.id);
68
+ }
69
+ const removed = [...writes, ...users].sort((a, b) => b - a);
70
+ for (const index of removed)
71
+ items.splice(index, 1);
72
+ return { users: placed, reset };
73
+ }
74
+ /** Whether a head write targets an entry before the active range, so placing it would bring back cut history. */
75
+ export function isStale(boundary, entry) {
76
+ return typeof entry.head === "number" && boundary.head !== undefined && entry.head < boundary.head;
77
+ }
78
+ /** Remove a withdrawn submission's item; the caller settles the submission. */
79
+ export async function removeInboxItem(tx, conversationId, id) {
80
+ const items = (await tx.doc(InboxDoc, conversationId)).items;
81
+ const index = items.findIndex((item) => item.id === id);
82
+ if (index >= 0)
83
+ items.splice(index, 1);
84
+ }
85
+ /**
86
+ * Withdraw every queued input of a conversation, as `Conversation.abort()` and abort cascades do: each settles
87
+ * `unanswered` with `aborted` and leaves the inbox; queued writes stay for later placement.
88
+ */
89
+ export async function withdrawQueuedInputs(tx, conversationId) {
90
+ const items = (await tx.doc(InboxDoc, conversationId)).items;
91
+ for (let index = items.length - 1; index >= 0; index--) {
92
+ const item = items[index];
93
+ if (item.mode === "write")
94
+ continue;
95
+ tx.settleSubmission(item.id, { status: "unanswered", reason: "aborted" });
96
+ items.splice(index, 1);
97
+ }
98
+ }
99
+ //# sourceMappingURL=inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/harness/inbox.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Draft, JsonRepresentation } from \"@earendil-works/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport { UserEntry } from \"../entries.ts\";\nimport type { ConversationId, EntryDraft, EntryId, JsonObject, SubmissionId, Tx } from \"../types.ts\";\nimport { ConversationConfig } from \"./config.ts\";\nimport type { QueueMode, UserInput } from \"./types.ts\";\n\n/** A queued submission: user input for a run, or a passive entry write. */\nexport type InboxItem =\n\t| { id: SubmissionId; mode: \"steer\" | \"followUp\"; content: JsonRepresentation<UserInput> }\n\t/** `entry` is an `EntryDraft`, stored as plain JSON. */\n\t| { id: SubmissionId; mode: \"write\"; entry: JsonObject };\n\n/** Built-in queue of one conversation's submissions waiting for a boundary, in ID order. */\nexport type InboxState = { items: InboxItem[] };\n\nexport const InboxDoc = defineDoc<InboxState>({\n\tkind: \"pi.inbox\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"latest\",\n\tfork: \"initial\",\n\tinitial: () => ({ items: [] }),\n\tcheckpointWhen: (value) => value.items.length === 0,\n});\n\n/** What a boundary reads before the commit's first table write, and the newest head it has seen so far. */\nexport type Boundary = {\n\treadonly conversationId: ConversationId;\n\treadonly inbox: Draft<InboxState>;\n\treadonly steeringMode: QueueMode;\n\treadonly followUpMode: QueueMode;\n\t/** Start of the active range, the newest head marker's `head`; advanced by heads written in this commit. */\n\thead: EntryId | undefined;\n};\n\n/** Selected user items, in ID order, and whether a `head: \"self\"` write (a reset) was placed. */\nexport type BoundaryResult = { readonly users: SubmissionId[]; readonly reset: boolean };\n\n/**\n * Read what a boundary needs. Table reads must precede the commit's first table write, so callers prepare the\n * boundary at the start of their commit.\n */\nexport async function prepareBoundary(tx: Tx, conversationId: ConversationId): Promise<Boundary> {\n\tconst head = (await tx.latestHeadMarker(conversationId))?.head;\n\tconst inbox = await tx.doc(InboxDoc, conversationId);\n\tconst config = await tx.doc(ConversationConfig, conversationId);\n\treturn {\n\t\tconversationId,\n\t\tinbox,\n\t\tsteeringMode: config.steeringMode ?? \"one-at-a-time\",\n\t\tfollowUpMode: config.followUpMode ?? \"one-at-a-time\",\n\t\thead,\n\t};\n}\n\n/**\n * Place the queued items a boundary selects (spec §6): every write, the first or all steers, and at `final` the first\n * or all follow-ups. A selected reset turns a `postTools` boundary into `final`. Writes are placed first and user\n * items after them, each in ID order, so user items queued before a reset run in the new context. A write whose head\n * targets an entry before the active range, including a range started earlier in this commit, is stale. Selected and\n * stale items are removed positionally.\n */\nexport async function applyBoundary(\n\ttx: Tx,\n\tboundary: Boundary,\n\tat: \"postTools\" | \"final\",\n\tnow: number,\n): Promise<BoundaryResult> {\n\tconst { conversationId, inbox } = boundary;\n\tconst items = inbox.items;\n\tconst reset = items.some((item) => item.mode === \"write\" && item.entry.head === \"self\");\n\tconst final = at === \"final\" || reset;\n\tconst pick = (mode: \"steer\" | \"followUp\", queueMode: QueueMode): number[] => {\n\t\tconst indexes = items.flatMap((item, index) => (item.mode === mode ? [index] : []));\n\t\treturn queueMode === \"all\" ? indexes : indexes.slice(0, 1);\n\t};\n\tconst writes = items.flatMap((item, index) => (item.mode === \"write\" ? [index] : []));\n\tconst users = [...pick(\"steer\", boundary.steeringMode), ...(final ? pick(\"followUp\", boundary.followUpMode) : [])];\n\tusers.sort((a, b) => a - b);\n\n\t// `appendEntry()` copies the drafts' values; the items are removed only afterwards.\n\tfor (const index of writes) {\n\t\tconst item = items[index] as Draft<Extract<InboxItem, { mode: \"write\" }>>;\n\t\tconst draft = item.entry as unknown as EntryDraft;\n\t\tif (isStale(boundary, draft)) {\n\t\t\ttx.settleSubmission(item.id, { status: \"unanswered\", reason: \"stale\" });\n\t\t\tcontinue;\n\t\t}\n\t\tconst entry = await tx.appendEntry(conversationId, draft);\n\t\tif (draft.head !== undefined) boundary.head = draft.head === \"self\" ? entry.id : draft.head;\n\t\ttx.placeSubmission(item.id, entry.id);\n\t}\n\tconst placed: SubmissionId[] = [];\n\tfor (const index of users) {\n\t\tconst item = items[index] as Draft<Extract<InboxItem, { mode: \"steer\" | \"followUp\" }>>;\n\t\tconst message = { role: \"user\", content: item.content as UserInput, timestamp: now } as const;\n\t\tconst entry = await tx.appendEntry(UserEntry, conversationId, { model: [message] });\n\t\ttx.placeSubmission(item.id, entry.id);\n\t\tplaced.push(item.id);\n\t}\n\tconst removed = [...writes, ...users].sort((a, b) => b - a);\n\tfor (const index of removed) items.splice(index, 1);\n\treturn { users: placed, reset };\n}\n\n/** Whether a head write targets an entry before the active range, so placing it would bring back cut history. */\nexport function isStale(boundary: Boundary, entry: EntryDraft): boolean {\n\treturn typeof entry.head === \"number\" && boundary.head !== undefined && entry.head < boundary.head;\n}\n\n/** Remove a withdrawn submission's item; the caller settles the submission. */\nexport async function removeInboxItem(tx: Tx, conversationId: ConversationId, id: SubmissionId): Promise<void> {\n\tconst items = (await tx.doc(InboxDoc, conversationId)).items;\n\tconst index = items.findIndex((item) => item.id === id);\n\tif (index >= 0) items.splice(index, 1);\n}\n\n/**\n * Withdraw every queued input of a conversation, as `Conversation.abort()` and abort cascades do: each settles\n * `unanswered` with `aborted` and leaves the inbox; queued writes stay for later placement.\n */\nexport async function withdrawQueuedInputs(tx: Tx, conversationId: ConversationId): Promise<void> {\n\tconst items = (await tx.doc(InboxDoc, conversationId)).items;\n\tfor (let index = items.length - 1; index >= 0; index--) {\n\t\tconst item = items[index]!;\n\t\tif (item.mode === \"write\") continue;\n\t\ttx.settleSubmission(item.id, { status: \"unanswered\", reason: \"aborted\" });\n\t\titems.splice(index, 1);\n\t}\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { JsonValue } from "@earendil-works/chord";
2
+ type JsonContainer = Record<string, JsonValue> | JsonValue[];
3
+ /**
4
+ * Assign `value` at `target[key]` leaf by leaf. Chord records a container assignment as one full set and only emits an
5
+ * append when a string leaf is reassigned with a longer string, so writing the partial whole would store and publish the
6
+ * complete message on every flush.
7
+ */
8
+ export declare function assignJson(target: JsonContainer, key: string | number, value: JsonValue): void;
9
+ export {};
10
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/harness/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,SAAS,EAAE,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAiB9F"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Assign `value` at `target[key]` leaf by leaf. Chord records a container assignment as one full set and only emits an
3
+ * append when a string leaf is reassigned with a longer string, so writing the partial whole would store and publish the
4
+ * complete message on every flush.
5
+ */
6
+ export function assignJson(target, key, value) {
7
+ const slots = target;
8
+ const current = slots[key];
9
+ if (isRecord(current) && isRecord(value)) {
10
+ for (const name of Object.keys(current))
11
+ if (!Object.hasOwn(value, name))
12
+ delete current[name];
13
+ for (const [name, child] of Object.entries(value))
14
+ assignJson(current, name, child);
15
+ return;
16
+ }
17
+ if (Array.isArray(current) && Array.isArray(value) && current.length <= value.length) {
18
+ const items = current;
19
+ for (let index = 0; index < value.length; index++) {
20
+ if (index < items.length)
21
+ assignJson(items, index, value[index]);
22
+ else
23
+ items.push(value[index]);
24
+ }
25
+ return;
26
+ }
27
+ if (current !== value)
28
+ slots[key] = value;
29
+ }
30
+ function isRecord(value) {
31
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32
+ }
33
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/harness/json.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,MAAqB,EAAE,GAAoB,EAAE,KAAgB;IACvF,MAAM,KAAK,GAAG,MAA4C,CAAC;IAC3D,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;gBAAE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/F,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACpF,OAAO;IACR,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACtF,MAAM,KAAK,GAAG,OAAsB,CAAC;QACrC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YACnD,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM;gBAAE,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAE,CAAC,CAAC;;gBAC7D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAE,CAAC,CAAC;QAChC,CAAC;QACD,OAAO;IACR,CAAC;IACD,IAAI,OAAO,KAAK,KAAK;QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAC3C,CAAC;AAED,SAAS,QAAQ,CAAC,KAA4B;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import type { JsonValue } from \"@earendil-works/chord\";\n\ntype JsonContainer = Record<string, JsonValue> | JsonValue[];\n\n/**\n * Assign `value` at `target[key]` leaf by leaf. Chord records a container assignment as one full set and only emits an\n * append when a string leaf is reassigned with a longer string, so writing the partial whole would store and publish the\n * complete message on every flush.\n */\nexport function assignJson(target: JsonContainer, key: string | number, value: JsonValue): void {\n\tconst slots = target as Record<string | number, JsonValue>;\n\tconst current = slots[key];\n\tif (isRecord(current) && isRecord(value)) {\n\t\tfor (const name of Object.keys(current)) if (!Object.hasOwn(value, name)) delete current[name];\n\t\tfor (const [name, child] of Object.entries(value)) assignJson(current, name, child);\n\t\treturn;\n\t}\n\tif (Array.isArray(current) && Array.isArray(value) && current.length <= value.length) {\n\t\tconst items = current as JsonValue[];\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tif (index < items.length) assignJson(items, index, value[index]!);\n\t\t\telse items.push(value[index]!);\n\t\t}\n\t\treturn;\n\t}\n\tif (current !== value) slots[key] = value;\n}\n\nfunction isRecord(value: JsonValue | undefined): value is Record<string, JsonValue> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
@@ -1,11 +1,46 @@
1
1
  import type { Draft, JsonRepresentation, JsonValue } from "@earendil-works/chord";
2
2
  import type { AssistantMessage } from "@earendil-works/pi-ai";
3
3
  import type { Transaction } from "../session/transaction.ts";
4
- import type { SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from "../types.ts";
4
+ import type { EntryId, SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from "../types.ts";
5
5
  import type { SchedulerOutcome } from "./scheduler.ts";
6
- /** Built-in live conversation state: run control and presentation of the current generation. */
6
+ import type { CompactionReason, CompactionResult, ToolDiagnostic } from "./types.ts";
7
+ /** Presentation of one tool call of the current round. */
8
+ export type ToolSlot = {
9
+ callId: string;
10
+ name: string;
11
+ /**
12
+ * Absent for a call not started yet (sequential round) and for a call its request did not offer, which starts `done`
13
+ * with the `entry` generation wrote.
14
+ */
15
+ taskId?: TaskId;
16
+ status: "pending" | "running" | "done";
17
+ /** Retained running output and what the bounds dropped. */
18
+ output?: string;
19
+ droppedBytes?: number;
20
+ droppedLines?: number;
21
+ /** Last `details()` value. */
22
+ details?: JsonValue;
23
+ /** Diagnostics recorded through `api.diagnostic()`. */
24
+ diagnostics?: ToolDiagnostic[];
25
+ /** Result entry once done; absent when the tool task faulted or was orphaned. */
26
+ entry?: EntryId;
27
+ };
28
+ /** Presentation of one live compaction task (spec §8.7). */
29
+ export type CompactionStatus = {
30
+ taskId: TaskId<CompactionResult>;
31
+ reason: CompactionReason;
32
+ /** Whether a generation waits for it: a compaction the generation owns. */
33
+ blocking: boolean;
34
+ attempt: number;
35
+ /** Durable backoff before the next summarization attempt. */
36
+ retry?: {
37
+ at: number;
38
+ error: string;
39
+ };
40
+ };
41
+ /** Built-in live conversation state: run control and presentation of the current generation and tool round. */
7
42
  export type LiveState = {
8
- /** Run control: the task responsible for the run and its placed inputs; present exactly while busy. */
43
+ /** Run control: the task that settles the run's inputs, and those inputs; present exactly while busy. */
9
44
  run?: {
10
45
  taskId: TaskId;
11
46
  inputs: SubmissionId[];
@@ -25,16 +60,40 @@ export type LiveState = {
25
60
  pollAt: number;
26
61
  };
27
62
  };
63
+ /** The current tool round in call order, from the tool-calling answer until the generation's `tools` phase ends it. */
64
+ tools?: ToolSlot[];
65
+ /** Live compaction tasks in task ID order; absent when none. */
66
+ compactions?: CompactionStatus[];
28
67
  };
29
68
  export declare const LiveDoc: import("../types.ts").ConversationDocToken<LiveState>;
30
69
  /**
31
- * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation`, whose
32
- * presentation belongs to the ending task.
70
+ * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation` and `tools`,
71
+ * whose presentation belongs to the ending run.
33
72
  */
34
73
  export declare function endRun(tx: Tx, live: Draft<LiveState>, taskId: TaskId, settlement: SubmissionSettlement): void;
74
+ /** Add the status of a compaction task created in this commit; statuses stay in task ID order. */
75
+ export declare function addCompactionStatus(live: Draft<LiveState>, status: CompactionStatus): void;
76
+ /** The status of compaction task `taskId`, if listed. */
77
+ export declare function compactionStatus(live: Draft<LiveState>, taskId: TaskId): Draft<CompactionStatus> | undefined;
78
+ /** Remove the status of compaction task `taskId`, and the list once empty. */
79
+ export declare function removeCompactionStatus(live: Draft<LiveState>, taskId: TaskId): void;
80
+ /** The slot of tool task `taskId` in the current round, if the round still lists it. */
81
+ export declare function toolSlot(live: Draft<LiveState>, taskId: TaskId): Draft<ToolSlot> | undefined;
82
+ /** Mark a slot done: the result entry, if any, now carries its running output, details, and diagnostics. */
83
+ export declare function finishSlot(slot: Draft<ToolSlot>, entry: EntryId | undefined): void;
84
+ /** Remove what a tool published while running; its result entry or a rerun replaces it. */
85
+ export declare function clearProgress(slot: Draft<ToolSlot>): void;
35
86
  /**
36
- * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). Ignores non-run task
37
- * kinds so it never creates `pi.live` elsewhere. Committed partials are discarded without a transcript entry.
87
+ * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). A run task ends its
88
+ * run; a tool task's slot is marked done without an entry, and context derivation synthesizes the missing result; a
89
+ * compaction task's status is removed.
90
+ * Ignores other kinds so it never creates `pi.live` elsewhere. The scheduler calls this without knowing task kinds;
91
+ * the Harness passes it in (spec §5.4).
92
+ * REMINDER: a committed generation partial becomes an aborted assistant entry here, exactly as in the generation abort
93
+ * handler, so the transcript keeps what the model produced and `pi.usage` counts its spend. The scheduler's commit has
94
+ * no task scope, so that entry has no `byTaskId`. Faults come from task bugs
95
+ * or malformed provider data (a non-JSON value in a response), or a commit the Storage rejected without effect; an
96
+ * uncertain storage failure poisons the Session instead and writes no outcome.
38
97
  */
39
98
  export declare function settleSchedulerOutcome(tx: Transaction, record: TaskRecord<JsonValue, JsonValue, JsonValue>, outcome: SchedulerOutcome): Promise<void>;
40
99
  //# sourceMappingURL=live.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/harness/live.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,gGAAgG;AAChG,MAAM,MAAM,SAAS,GAAG;IACvB,uGAAuG;IACvG,GAAG,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IACjD,sDAAsD;IACtD,UAAU,CAAC,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,6DAA6D;QAC7D,OAAO,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;QAC/C,+CAA+C;QAC/C,KAAK,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACtC,oDAAoD;QACpD,QAAQ,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAC9B,CAAC;CACF,CAAC;AAEF,eAAO,MAAM,OAAO,uDAUlB,CAAC;AAKH;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,oBAAoB,GAAG,IAAI,CAM7G;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC3C,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,EACnD,OAAO,EAAE,gBAAgB,GACvB,OAAO,CAAC,IAAI,CAAC,CAYf"}
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/harness/live.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { defineDoc } from "../documents.js";
2
+ import { convertPartial } from "./generation.js";
2
3
  export const LiveDoc = defineDoc({
3
4
  kind: "pi.live",
4
5
  version: 1,
@@ -6,15 +7,19 @@ export const LiveDoc = defineDoc({
6
7
  history: "latest",
7
8
  fork: "initial",
8
9
  initial: () => ({}),
9
- // A complete base whenever nothing is in flight, so the delta chain spans at most one generation, including its
10
- // retries and deferred polls, or one tool round.
11
- checkpointWhen: (value) => value.generation === undefined,
10
+ // REMINDER: a complete base whenever nothing runs (spec §8.2): no generation and no running tool slot. That holds
11
+ // while idle, in the commit handing a generation over to its tool round, and between tools, so the delta chain
12
+ // spans at most one generation or the overlapping execution of one round's tools. A slot holds output only while
13
+ // running, so every base is small. Do not add a delta-count bound; the tool output benchmark checks this rule.
14
+ checkpointWhen: (value) => value.generation === undefined && !(value.tools ?? []).some((slot) => slot.status === "running"),
12
15
  });
13
16
  /** Built-in task kinds that can own `pi.live.run`. */
14
17
  const RUN_TASK_KINDS = new Set(["pi.generation"]);
18
+ const TOOL_TASK_KIND = "pi.tool";
19
+ const COMPACTION_TASK_KIND = "pi.compaction";
15
20
  /**
16
- * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation`, whose
17
- * presentation belongs to the ending task.
21
+ * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation` and `tools`,
22
+ * whose presentation belongs to the ending run.
18
23
  */
19
24
  export function endRun(tx, live, taskId, settlement) {
20
25
  if (live.run?.taskId === taskId) {
@@ -23,17 +28,76 @@ export function endRun(tx, live, taskId, settlement) {
23
28
  delete live.run;
24
29
  }
25
30
  delete live.generation;
31
+ delete live.tools;
32
+ }
33
+ /** Add the status of a compaction task created in this commit; statuses stay in task ID order. */
34
+ export function addCompactionStatus(live, status) {
35
+ live.compactions ??= [];
36
+ live.compactions.push(status);
37
+ }
38
+ /** The status of compaction task `taskId`, if listed. */
39
+ export function compactionStatus(live, taskId) {
40
+ return live.compactions?.find((status) => status.taskId === taskId);
41
+ }
42
+ /** Remove the status of compaction task `taskId`, and the list once empty. */
43
+ export function removeCompactionStatus(live, taskId) {
44
+ const statuses = live.compactions;
45
+ if (statuses === undefined)
46
+ return;
47
+ const index = statuses.findIndex((status) => status.taskId === taskId);
48
+ if (index >= 0)
49
+ statuses.splice(index, 1);
50
+ if (statuses.length === 0)
51
+ delete live.compactions;
52
+ }
53
+ /** The slot of tool task `taskId` in the current round, if the round still lists it. */
54
+ export function toolSlot(live, taskId) {
55
+ return live.tools?.find((slot) => slot.taskId === taskId);
56
+ }
57
+ /** Mark a slot done: the result entry, if any, now carries its running output, details, and diagnostics. */
58
+ export function finishSlot(slot, entry) {
59
+ slot.status = "done";
60
+ if (entry !== undefined)
61
+ slot.entry = entry;
62
+ clearProgress(slot);
63
+ }
64
+ /** Remove what a tool published while running; its result entry or a rerun replaces it. */
65
+ export function clearProgress(slot) {
66
+ delete slot.output;
67
+ delete slot.droppedBytes;
68
+ delete slot.droppedLines;
69
+ delete slot.details;
70
+ delete slot.diagnostics;
26
71
  }
27
72
  /**
28
- * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). Ignores non-run task
29
- * kinds so it never creates `pi.live` elsewhere. Committed partials are discarded without a transcript entry.
73
+ * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). A run task ends its
74
+ * run; a tool task's slot is marked done without an entry, and context derivation synthesizes the missing result; a
75
+ * compaction task's status is removed.
76
+ * Ignores other kinds so it never creates `pi.live` elsewhere. The scheduler calls this without knowing task kinds;
77
+ * the Harness passes it in (spec §5.4).
78
+ * REMINDER: a committed generation partial becomes an aborted assistant entry here, exactly as in the generation abort
79
+ * handler, so the transcript keeps what the model produced and `pi.usage` counts its spend. The scheduler's commit has
80
+ * no task scope, so that entry has no `byTaskId`. Faults come from task bugs
81
+ * or malformed provider data (a non-JSON value in a response), or a commit the Storage rejected without effect; an
82
+ * uncertain storage failure poisons the Session instead and writes no outcome.
30
83
  */
31
84
  export async function settleSchedulerOutcome(tx, record, outcome) {
85
+ if (record.kind === TOOL_TASK_KIND) {
86
+ const slot = toolSlot(await tx.doc(LiveDoc, record.conversationId), record.id);
87
+ if (slot !== undefined)
88
+ finishSlot(slot, undefined);
89
+ return;
90
+ }
91
+ if (record.kind === COMPACTION_TASK_KIND) {
92
+ removeCompactionStatus(await tx.doc(LiveDoc, record.conversationId), record.id);
93
+ return;
94
+ }
32
95
  if (!RUN_TASK_KINDS.has(record.kind))
33
96
  return;
34
97
  const live = await tx.doc(LiveDoc, record.conversationId);
35
98
  if (live.run?.taskId !== record.id)
36
99
  return;
100
+ await convertPartial(tx, live, record.conversationId);
37
101
  endRun(tx, live, record.id, outcome.status === "faulted"
38
102
  ? { status: "unanswered", reason: "faulted", detail: outcome.error.message }
39
103
  : { status: "unanswered", reason: outcome.reason });
@@ -1 +1 @@
1
- {"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/harness/live.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAqB5C,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAY;IAC3C,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,cAAc;IACrB,OAAO,EAAE,QAAQ;IACjB,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;IACnB,gHAAgH;IAChH,iDAAiD;IACjD,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS;CACzD,CAAC,CAAC;AAEH,sDAAsD;AACtD,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;AAEvE;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,EAAM,EAAE,IAAsB,EAAE,MAAc,EAAE,UAAgC;IACtG,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;QACjC,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC,GAAG,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC,UAAU,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,EAAe,EACf,MAAmD,EACnD,OAAyB;IAEzB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,OAAO;IAC7C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;IAC1D,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,EAAE;QAAE,OAAO;IAC3C,MAAM,CACL,EAAE,EACF,IAAI,EACJ,MAAM,CAAC,EAAE,EACT,OAAO,CAAC,MAAM,KAAK,SAAS;QAC3B,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE;QAC5E,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CACnD,CAAC;AACH,CAAC","sourcesContent":["import type { Draft, JsonRepresentation, JsonValue } from \"@earendil-works/chord\";\nimport type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { defineDoc } from \"../documents.ts\";\nimport type { Transaction } from \"../session/transaction.ts\";\nimport type { SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from \"../types.ts\";\nimport type { SchedulerOutcome } from \"./scheduler.ts\";\n\n/** Built-in live conversation state: run control and presentation of the current generation. */\nexport type LiveState = {\n\t/** Run control: the task responsible for the run and its placed inputs; present exactly while busy. */\n\trun?: { taskId: TaskId; inputs: SubmissionId[] };\n\t/** Presentation of the current generation attempt. */\n\tgeneration?: {\n\t\tattempt: number;\n\t\t/** Committed throttled partial of the in-flight response. */\n\t\tmessage?: JsonRepresentation<AssistantMessage>;\n\t\t/** Durable backoff before the next attempt. */\n\t\tretry?: { at: number; error: string };\n\t\t/** Provider-side deferred response being polled. */\n\t\tdeferred?: { pollAt: number };\n\t};\n};\n\nexport const LiveDoc = defineDoc<LiveState>({\n\tkind: \"pi.live\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"latest\",\n\tfork: \"initial\",\n\tinitial: () => ({}),\n\t// A complete base whenever nothing is in flight, so the delta chain spans at most one generation, including its\n\t// retries and deferred polls, or one tool round.\n\tcheckpointWhen: (value) => value.generation === undefined,\n});\n\n/** Built-in task kinds that can own `pi.live.run`. */\nconst RUN_TASK_KINDS: ReadonlySet<string> = new Set([\"pi.generation\"]);\n\n/**\n * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation`, whose\n * presentation belongs to the ending task.\n */\nexport function endRun(tx: Tx, live: Draft<LiveState>, taskId: TaskId, settlement: SubmissionSettlement): void {\n\tif (live.run?.taskId === taskId) {\n\t\tfor (const id of live.run.inputs) tx.settleSubmission(id, settlement);\n\t\tdelete live.run;\n\t}\n\tdelete live.generation;\n}\n\n/**\n * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). Ignores non-run task\n * kinds so it never creates `pi.live` elsewhere. Committed partials are discarded without a transcript entry.\n */\nexport async function settleSchedulerOutcome(\n\ttx: Transaction,\n\trecord: TaskRecord<JsonValue, JsonValue, JsonValue>,\n\toutcome: SchedulerOutcome,\n): Promise<void> {\n\tif (!RUN_TASK_KINDS.has(record.kind)) return;\n\tconst live = await tx.doc(LiveDoc, record.conversationId);\n\tif (live.run?.taskId !== record.id) return;\n\tendRun(\n\t\ttx,\n\t\tlive,\n\t\trecord.id,\n\t\toutcome.status === \"faulted\"\n\t\t\t? { status: \"unanswered\", reason: \"faulted\", detail: outcome.error.message }\n\t\t\t: { status: \"unanswered\", reason: outcome.reason },\n\t);\n}\n"]}
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/harness/live.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Draft, JsonRepresentation, JsonValue } from \"@earendil-works/chord\";\nimport type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { defineDoc } from \"../documents.ts\";\nimport type { Transaction } from \"../session/transaction.ts\";\nimport type { EntryId, SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from \"../types.ts\";\nimport { convertPartial } from \"./generation.ts\";\nimport type { SchedulerOutcome } from \"./scheduler.ts\";\nimport type { CompactionReason, CompactionResult, ToolDiagnostic } from \"./types.ts\";\n\n/** Presentation of one tool call of the current round. */\nexport type ToolSlot = {\n\tcallId: string;\n\tname: string;\n\t/**\n\t * Absent for a call not started yet (sequential round) and for a call its request did not offer, which starts `done`\n\t * with the `entry` generation wrote.\n\t */\n\ttaskId?: TaskId;\n\tstatus: \"pending\" | \"running\" | \"done\";\n\t/** Retained running output and what the bounds dropped. */\n\toutput?: string;\n\tdroppedBytes?: number;\n\tdroppedLines?: number;\n\t/** Last `details()` value. */\n\tdetails?: JsonValue;\n\t/** Diagnostics recorded through `api.diagnostic()`. */\n\tdiagnostics?: ToolDiagnostic[];\n\t/** Result entry once done; absent when the tool task faulted or was orphaned. */\n\tentry?: EntryId;\n};\n\n/** Presentation of one live compaction task (spec §8.7). */\nexport type CompactionStatus = {\n\ttaskId: TaskId<CompactionResult>;\n\treason: CompactionReason;\n\t/** Whether a generation waits for it: a compaction the generation owns. */\n\tblocking: boolean;\n\tattempt: number;\n\t/** Durable backoff before the next summarization attempt. */\n\tretry?: { at: number; error: string };\n};\n\n/** Built-in live conversation state: run control and presentation of the current generation and tool round. */\nexport type LiveState = {\n\t/** Run control: the task that settles the run's inputs, and those inputs; present exactly while busy. */\n\trun?: { taskId: TaskId; inputs: SubmissionId[] };\n\t/** Presentation of the current generation attempt. */\n\tgeneration?: {\n\t\tattempt: number;\n\t\t/** Committed throttled partial of the in-flight response. */\n\t\tmessage?: JsonRepresentation<AssistantMessage>;\n\t\t/** Durable backoff before the next attempt. */\n\t\tretry?: { at: number; error: string };\n\t\t/** Provider-side deferred response being polled. */\n\t\tdeferred?: { pollAt: number };\n\t};\n\t/** The current tool round in call order, from the tool-calling answer until the generation's `tools` phase ends it. */\n\ttools?: ToolSlot[];\n\t/** Live compaction tasks in task ID order; absent when none. */\n\tcompactions?: CompactionStatus[];\n};\n\nexport const LiveDoc = defineDoc<LiveState>({\n\tkind: \"pi.live\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"latest\",\n\tfork: \"initial\",\n\tinitial: () => ({}),\n\t// REMINDER: a complete base whenever nothing runs (spec §8.2): no generation and no running tool slot. That holds\n\t// while idle, in the commit handing a generation over to its tool round, and between tools, so the delta chain\n\t// spans at most one generation or the overlapping execution of one round's tools. A slot holds output only while\n\t// running, so every base is small. Do not add a delta-count bound; the tool output benchmark checks this rule.\n\tcheckpointWhen: (value) =>\n\t\tvalue.generation === undefined && !(value.tools ?? []).some((slot) => slot.status === \"running\"),\n});\n\n/** Built-in task kinds that can own `pi.live.run`. */\nconst RUN_TASK_KINDS: ReadonlySet<string> = new Set([\"pi.generation\"]);\nconst TOOL_TASK_KIND = \"pi.tool\";\nconst COMPACTION_TASK_KIND = \"pi.compaction\";\n\n/**\n * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation` and `tools`,\n * whose presentation belongs to the ending run.\n */\nexport function endRun(tx: Tx, live: Draft<LiveState>, taskId: TaskId, settlement: SubmissionSettlement): void {\n\tif (live.run?.taskId === taskId) {\n\t\tfor (const id of live.run.inputs) tx.settleSubmission(id, settlement);\n\t\tdelete live.run;\n\t}\n\tdelete live.generation;\n\tdelete live.tools;\n}\n\n/** Add the status of a compaction task created in this commit; statuses stay in task ID order. */\nexport function addCompactionStatus(live: Draft<LiveState>, status: CompactionStatus): void {\n\tlive.compactions ??= [];\n\tlive.compactions.push(status);\n}\n\n/** The status of compaction task `taskId`, if listed. */\nexport function compactionStatus(live: Draft<LiveState>, taskId: TaskId): Draft<CompactionStatus> | undefined {\n\treturn live.compactions?.find((status) => status.taskId === taskId);\n}\n\n/** Remove the status of compaction task `taskId`, and the list once empty. */\nexport function removeCompactionStatus(live: Draft<LiveState>, taskId: TaskId): void {\n\tconst statuses = live.compactions;\n\tif (statuses === undefined) return;\n\tconst index = statuses.findIndex((status) => status.taskId === taskId);\n\tif (index >= 0) statuses.splice(index, 1);\n\tif (statuses.length === 0) delete live.compactions;\n}\n\n/** The slot of tool task `taskId` in the current round, if the round still lists it. */\nexport function toolSlot(live: Draft<LiveState>, taskId: TaskId): Draft<ToolSlot> | undefined {\n\treturn live.tools?.find((slot) => slot.taskId === taskId);\n}\n\n/** Mark a slot done: the result entry, if any, now carries its running output, details, and diagnostics. */\nexport function finishSlot(slot: Draft<ToolSlot>, entry: EntryId | undefined): void {\n\tslot.status = \"done\";\n\tif (entry !== undefined) slot.entry = entry;\n\tclearProgress(slot);\n}\n\n/** Remove what a tool published while running; its result entry or a rerun replaces it. */\nexport function clearProgress(slot: Draft<ToolSlot>): void {\n\tdelete slot.output;\n\tdelete slot.droppedBytes;\n\tdelete slot.droppedLines;\n\tdelete slot.details;\n\tdelete slot.diagnostics;\n}\n\n/**\n * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). A run task ends its\n * run; a tool task's slot is marked done without an entry, and context derivation synthesizes the missing result; a\n * compaction task's status is removed.\n * Ignores other kinds so it never creates `pi.live` elsewhere. The scheduler calls this without knowing task kinds;\n * the Harness passes it in (spec §5.4).\n * REMINDER: a committed generation partial becomes an aborted assistant entry here, exactly as in the generation abort\n * handler, so the transcript keeps what the model produced and `pi.usage` counts its spend. The scheduler's commit has\n * no task scope, so that entry has no `byTaskId`. Faults come from task bugs\n * or malformed provider data (a non-JSON value in a response), or a commit the Storage rejected without effect; an\n * uncertain storage failure poisons the Session instead and writes no outcome.\n */\nexport async function settleSchedulerOutcome(\n\ttx: Transaction,\n\trecord: TaskRecord<JsonValue, JsonValue, JsonValue>,\n\toutcome: SchedulerOutcome,\n): Promise<void> {\n\tif (record.kind === TOOL_TASK_KIND) {\n\t\tconst slot = toolSlot(await tx.doc(LiveDoc, record.conversationId), record.id);\n\t\tif (slot !== undefined) finishSlot(slot, undefined);\n\t\treturn;\n\t}\n\tif (record.kind === COMPACTION_TASK_KIND) {\n\t\tremoveCompactionStatus(await tx.doc(LiveDoc, record.conversationId), record.id);\n\t\treturn;\n\t}\n\tif (!RUN_TASK_KINDS.has(record.kind)) return;\n\tconst live = await tx.doc(LiveDoc, record.conversationId);\n\tif (live.run?.taskId !== record.id) return;\n\tawait convertPartial(tx, live, record.conversationId);\n\tendRun(\n\t\ttx,\n\t\tlive,\n\t\trecord.id,\n\t\toutcome.status === \"faulted\"\n\t\t\t? { status: \"unanswered\", reason: \"faulted\", detail: outcome.error.message }\n\t\t\t: { status: \"unanswered\", reason: outcome.reason },\n\t);\n}\n"]}
@@ -0,0 +1,62 @@
1
+ /** Retention limits of one tool's output. */
2
+ export type OutputLimits = {
3
+ readonly maxBytes: number;
4
+ readonly maxLines: number;
5
+ readonly retain: "head" | "tail";
6
+ };
7
+ /** Retained output and what the limits dropped. */
8
+ export type BoundedOutput = {
9
+ readonly text: string;
10
+ readonly droppedBytes: number;
11
+ readonly droppedLines: number;
12
+ };
13
+ /** An exact slice of the input within the limits, and what it left out. */
14
+ export type OutputSlice = {
15
+ readonly text: string;
16
+ readonly bytes: number;
17
+ readonly droppedBytes: number;
18
+ readonly droppedLines: number;
19
+ };
20
+ /** Remove control characters that break display and transcripts; tabs and newlines stay. */
21
+ export declare function sanitizeOutput(text: string): string;
22
+ /**
23
+ * Bound `text` to whole lines within the limits: the first lines for `head`, the last lines for `tail`. The result is an
24
+ * exact slice, trailing newline included. A single line longer than `maxBytes` is cut at the byte limit on a character
25
+ * boundary.
26
+ */
27
+ export declare function boundOutput(text: string, limits: OutputLimits): OutputSlice;
28
+ /** The last character boundary at or before `index`. */
29
+ export declare function characterEnd(bytes: Uint8Array, index: number): number;
30
+ /**
31
+ * Bounded running output of one tool call. Accepting a chunk costs time proportional to the chunk: head retention stops
32
+ * storing once the window is full, and tail retention drops stored text the window no longer needs when it snapshots.
33
+ * Counts of the whole stream are kept so the dropped totals stay exact.
34
+ */
35
+ export declare class OutputBuffer {
36
+ #private;
37
+ constructor(limits: OutputLimits);
38
+ /** Bytes currently held; bounded by the limits plus one chunk. */
39
+ get storedBytes(): number;
40
+ /** Accept a chunk; returns whether anything was accepted. */
41
+ push(chunk: string | Uint8Array): boolean;
42
+ /** Flush an incomplete trailing character as a replacement character; call when the stream ends. */
43
+ end(): void;
44
+ /** Retained, sanitized output and what the limits dropped from the whole stream. */
45
+ snapshot(): BoundedOutput;
46
+ }
47
+ /**
48
+ * Adaptive progress commits, like the environment's shell output capture: the first change after an idle period
49
+ * commits at once; each commit then delays the next by at least 100 ms and by its written size at 100 KiB/s. At most
50
+ * one commit is in flight; changes made meanwhile coalesce into the next one.
51
+ */
52
+ export declare class Progress {
53
+ #private;
54
+ constructor(write: () => Promise<number>, onError: (error: unknown) => void);
55
+ /** Schedule a commit. */
56
+ mark(): void;
57
+ /** Schedule a commit; the promise settles with the commit that includes this change. */
58
+ markAndWait(): Promise<void>;
59
+ /** Stop committing and wait for the commit in flight; returns the waiters the final commit must settle. */
60
+ stop(): Promise<PromiseWithResolvers<void>[]>;
61
+ }
62
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/harness/output.ts"],"names":[],"mappings":"AAEA,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAEtH,mDAAmD;AACnD,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpH,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B,CAAC;AAOF,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,CAU3E;AA0CD,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIrE;AAgBD;;;;GAIG;AACH,qBAAa,YAAY;;IAYxB,YAAY,MAAM,EAAE,YAAY,EAE/B;IAED,kEAAkE;IAClE,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,6DAA6D;IAC7D,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAKxC;IAED,oGAAoG;IACpG,GAAG,IAAI,IAAI,CAEV;IAgCD,oFAAoF;IACpF,QAAQ,IAAI,aAAa,CAmBxB;CACD;AAiBD;;;;GAIG;AACH,qBAAa,QAAQ;;IAUpB,YAAY,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EAG1E;IAED,yBAAyB;IACzB,IAAI,IAAI,IAAI,CAGX;IAED,wFAAwF;IACxF,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;IAED,2GAA2G;IACrG,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,CAMlD;CAmCD"}