@earendil-works/pi-durable 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +227 -0
- package/README.md +287 -82
- package/dist/codemode/codemode.d.ts +4 -4
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +21 -16
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.d.ts.map +1 -1
- package/dist/codemode/models.js +6 -4
- package/dist/codemode/models.js.map +1 -1
- package/dist/documents.d.ts +1 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js.map +1 -1
- package/dist/env/index.d.ts +12 -10
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node-watch.d.ts +3 -3
- package/dist/env/node-watch.d.ts.map +1 -1
- package/dist/env/node-watch.js +18 -16
- package/dist/env/node-watch.js.map +1 -1
- package/dist/env/node.d.ts +2 -2
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +12 -4
- package/dist/env/node.js.map +1 -1
- package/dist/errors.d.ts +18 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -3
- package/dist/errors.js.map +1 -1
- package/dist/harness/agent.d.ts +13 -2
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +48 -6
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts +3 -1
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +38 -6
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +23 -7
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +71 -25
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/define.d.ts +7 -3
- package/dist/harness/define.d.ts.map +1 -1
- package/dist/harness/define.js +4 -0
- package/dist/harness/define.js.map +1 -1
- package/dist/harness/generation.d.ts +38 -5
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +497 -108
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +7 -2
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +147 -57
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +7 -3
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +7 -4
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/json.d.ts +20 -4
- package/dist/harness/json.d.ts.map +1 -1
- package/dist/harness/json.js +23 -5
- package/dist/harness/json.js.map +1 -1
- package/dist/harness/live.d.ts +122 -16
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +93 -12
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/partial-changes.d.ts +54 -0
- package/dist/harness/partial-changes.d.ts.map +1 -0
- package/dist/harness/partial-changes.js +162 -0
- package/dist/harness/partial-changes.js.map +1 -0
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +1 -1
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/registry-reader.d.ts +11 -0
- package/dist/harness/registry-reader.d.ts.map +1 -0
- package/dist/harness/registry-reader.js +25 -0
- package/dist/harness/registry-reader.js.map +1 -0
- package/dist/harness/scheduler.d.ts +86 -14
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +1459 -369
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +23 -11
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +104 -36
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +28 -8
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +85 -36
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +307 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +136 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/tool.d.ts +2 -0
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +223 -61
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/transcript.d.ts +72 -0
- package/dist/harness/transcript.d.ts.map +1 -0
- package/dist/harness/transcript.js +264 -0
- package/dist/harness/transcript.js.map +1 -0
- package/dist/harness/types.d.ts +302 -48
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/util.d.ts +7 -1
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +13 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/harness/view.d.ts +43 -22
- package/dist/harness/view.d.ts.map +1 -1
- package/dist/harness/view.js +130 -71
- package/dist/harness/view.js.map +1 -1
- package/dist/index.d.ts +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/search.d.ts +19 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +107 -44
- package/dist/search.js.map +1 -1
- package/dist/session/cached-storage.d.ts +71 -0
- package/dist/session/cached-storage.d.ts.map +1 -0
- package/dist/session/cached-storage.js +269 -0
- package/dist/session/cached-storage.js.map +1 -0
- package/dist/session/conversation-tails.d.ts +33 -0
- package/dist/session/conversation-tails.d.ts.map +1 -0
- package/dist/session/conversation-tails.js +101 -0
- package/dist/session/conversation-tails.js.map +1 -0
- package/dist/session/keys.d.ts +68 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +161 -0
- package/dist/session/keys.js.map +1 -0
- package/dist/session/session.d.ts +181 -29
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +1073 -183
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +86 -4
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +250 -38
- package/dist/session/transaction.js.map +1 -1
- package/dist/session/writer.d.ts +74 -0
- package/dist/session/writer.d.ts.map +1 -0
- package/dist/session/writer.js +294 -0
- package/dist/session/writer.js.map +1 -0
- package/dist/storage/indexeddb.d.ts +68 -0
- package/dist/storage/indexeddb.d.ts.map +1 -0
- package/dist/storage/indexeddb.js +881 -0
- package/dist/storage/indexeddb.js.map +1 -0
- package/dist/storage/jsonl/storage.d.ts +30 -2
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +88 -24
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.d.ts +7 -2
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +29 -3
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/cloudflare.d.ts +2 -51
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -1
- package/dist/storage/sqlite/cloudflare.js +2 -133
- package/dist/storage/sqlite/cloudflare.js.map +1 -1
- package/dist/storage/sqlite/database.d.ts +1 -36
- package/dist/storage/sqlite/database.d.ts.map +1 -1
- package/dist/storage/sqlite/database.js.map +1 -1
- package/dist/storage/sqlite/index.d.ts +1 -1
- package/dist/storage/sqlite/index.d.ts.map +1 -1
- package/dist/storage/sqlite/index.js.map +1 -1
- package/dist/storage/sqlite/migrations.d.ts +2 -5
- package/dist/storage/sqlite/migrations.d.ts.map +1 -1
- package/dist/storage/sqlite/migrations.js +28 -28
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/node.d.ts +2 -37
- package/dist/storage/sqlite/node.d.ts.map +1 -1
- package/dist/storage/sqlite/node.js +3 -174
- package/dist/storage/sqlite/node.js.map +1 -1
- package/dist/storage/sqlite/schema.d.ts +1 -3
- package/dist/storage/sqlite/schema.d.ts.map +1 -1
- package/dist/storage/sqlite/schema.js +2 -9
- package/dist/storage/sqlite/schema.js.map +1 -1
- package/dist/storage/sqlite/storage.d.ts +15 -3
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +110 -36
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/storage/store/commit.d.ts +33 -0
- package/dist/storage/store/commit.d.ts.map +1 -0
- package/dist/storage/store/commit.js +286 -0
- package/dist/storage/store/commit.js.map +1 -0
- package/dist/storage/store/documents.d.ts +43 -0
- package/dist/storage/store/documents.d.ts.map +1 -0
- package/dist/storage/store/documents.js +102 -0
- package/dist/storage/store/documents.js.map +1 -0
- package/dist/storage/store/index.d.ts +2 -0
- package/dist/storage/store/index.d.ts.map +1 -0
- package/dist/storage/store/index.js +2 -0
- package/dist/storage/store/index.js.map +1 -0
- package/dist/storage/store/queries.d.ts +49 -0
- package/dist/storage/store/queries.d.ts.map +1 -0
- package/dist/storage/store/queries.js +93 -0
- package/dist/storage/store/queries.js.map +1 -0
- package/dist/storage/store/storage.d.ts +59 -0
- package/dist/storage/store/storage.d.ts.map +1 -0
- package/dist/storage/store/storage.js +438 -0
- package/dist/storage/store/storage.js.map +1 -0
- package/dist/storage/store/tables.d.ts +54 -0
- package/dist/storage/store/tables.d.ts.map +1 -0
- package/dist/storage/store/tables.js +108 -0
- package/dist/storage/store/tables.js.map +1 -0
- package/dist/testing/commit-writes.d.ts +5 -0
- package/dist/testing/commit-writes.d.ts.map +1 -0
- package/dist/testing/commit-writes.js +12 -0
- package/dist/testing/commit-writes.js.map +1 -0
- package/dist/testing/env-conformance.js +7 -7
- package/dist/testing/env-conformance.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +18 -17
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +369 -67
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/timers.d.ts +21 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +72 -0
- package/dist/timers.js.map +1 -0
- package/dist/tool-search/tool-search.d.ts +6 -12
- package/dist/tool-search/tool-search.d.ts.map +1 -1
- package/dist/tool-search/tool-search.js +7 -7
- package/dist/tool-search/tool-search.js.map +1 -1
- package/dist/tools/bash.d.ts +12 -12
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +16 -15
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +7 -7
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -9
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +32 -32
- package/dist/tools/read.d.ts +6 -6
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +19 -8
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +4 -4
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +5 -5
- package/dist/tools/write.js.map +1 -1
- package/dist/types.d.ts +331 -64
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +27 -10
- package/dist/harness/events.d.ts +0 -186
- package/dist/harness/events.d.ts.map +0 -1
- package/dist/harness/events.js +0 -373
- package/dist/harness/events.js.map +0 -1
- package/dist/session/observation.d.ts +0 -42
- package/dist/session/observation.d.ts.map +0 -1
- package/dist/session/observation.js +0 -281
- package/dist/session/observation.js.map +0 -1
package/dist/harness/inbox.js
CHANGED
|
@@ -45,6 +45,7 @@ export async function applyBoundary(tx, boundary, at, now) {
|
|
|
45
45
|
const users = [...pick("steer", boundary.steeringMode), ...(final ? pick("followUp", boundary.followUpMode) : [])];
|
|
46
46
|
users.sort((a, b) => a - b);
|
|
47
47
|
// `appendEntry()` copies the drafts' values; the items are removed only afterwards.
|
|
48
|
+
let first;
|
|
48
49
|
for (const index of writes) {
|
|
49
50
|
const item = items[index];
|
|
50
51
|
const draft = item.entry;
|
|
@@ -56,6 +57,7 @@ export async function applyBoundary(tx, boundary, at, now) {
|
|
|
56
57
|
if (draft.head !== undefined)
|
|
57
58
|
boundary.head = draft.head === "self" ? entry.id : draft.head;
|
|
58
59
|
tx.placeSubmission(item.id, entry.id);
|
|
60
|
+
first ??= entry.id;
|
|
59
61
|
}
|
|
60
62
|
const placed = [];
|
|
61
63
|
for (const index of users) {
|
|
@@ -63,11 +65,12 @@ export async function applyBoundary(tx, boundary, at, now) {
|
|
|
63
65
|
const entry = await appendInput(tx, conversationId, item.content, item.entry, now);
|
|
64
66
|
tx.placeSubmission(item.id, entry.id);
|
|
65
67
|
placed.push(item.id);
|
|
68
|
+
first ??= entry.id;
|
|
66
69
|
}
|
|
67
70
|
const removed = [...writes, ...users].sort((a, b) => b - a);
|
|
68
71
|
for (const index of removed)
|
|
69
72
|
items.splice(index, 1);
|
|
70
|
-
return { users: placed, reset };
|
|
73
|
+
return { users: placed, first, reset };
|
|
71
74
|
}
|
|
72
75
|
/** Append the entry an input is placed as: `entry`'s kind and data, or `pi.user`, with the user message of `content`. */
|
|
73
76
|
export function appendInput(tx, conversationId, content, entry, now) {
|
|
@@ -105,15 +108,15 @@ export async function removeInboxItem(tx, conversationId, id) {
|
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
107
110
|
* Withdraw every queued input of a conversation, as `Conversation.abort()` and abort cascades do: each settles
|
|
108
|
-
* `unanswered` with `aborted` and leaves the inbox; queued writes stay for later placement.
|
|
111
|
+
* `unanswered` with `reason` (default `aborted`) and leaves the inbox; queued writes stay for later placement.
|
|
109
112
|
*/
|
|
110
|
-
export async function withdrawQueuedInputs(tx, conversationId) {
|
|
113
|
+
export async function withdrawQueuedInputs(tx, conversationId, reason = "aborted") {
|
|
111
114
|
const items = (await tx.doc(InboxDoc, conversationId)).items;
|
|
112
115
|
for (let index = items.length - 1; index >= 0; index--) {
|
|
113
116
|
const item = items[index];
|
|
114
117
|
if (item.mode === "write")
|
|
115
118
|
continue;
|
|
116
|
-
tx.settleSubmission(item.id, { status: "unanswered", reason
|
|
119
|
+
tx.settleSubmission(item.id, { status: "unanswered", reason });
|
|
117
120
|
items.splice(index, 1);
|
|
118
121
|
}
|
|
119
122
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/harness/inbox.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyJson, type Draft, type JsonRepresentation } from \"@earendil-works/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport { UserEntry } from \"../entries.ts\";\nimport type { ConversationId, EntryDraft, EntryId, EntryRecord, JsonObject, SubmissionId, Tx } from \"../types.ts\";\nimport type { InputEntry, QueueMode, Settings, UserInput } from \"./types.ts\";\n\n/** A queued submission: user input for a run, with the entry it is placed as when not `pi.user`, or an entry write. */\nexport type InboxItem =\n\t| {\n\t\t\tid: SubmissionId;\n\t\t\tmode: \"steer\" | \"followUp\";\n\t\t\tcontent: JsonRepresentation<UserInput>;\n\t\t\tentry?: JsonRepresentation<InputEntry>;\n\t }\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/** The settings a boundary reads, on the Session line. */\nexport type QueueModes = Pick<Settings, \"steeringMode\" | \"followUpMode\">;\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, modes: QueueModes): Promise<Boundary> {\n\tconst head = (await tx.latestHeadMarker(conversationId))?.head;\n\tconst inbox = await tx.doc(InboxDoc, conversationId);\n\treturn {\n\t\tconversationId,\n\t\tinbox,\n\t\tsteeringMode: modes.steeringMode,\n\t\tfollowUpMode: modes.followUpMode,\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 = hasQueuedReset(items);\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 entry = await appendInput(tx, conversationId, item.content as UserInput, item.entry as InputEntry, now);\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/** Append the entry an input is placed as: `entry`'s kind and data, or `pi.user`, with the user message of `content`. */\nexport function appendInput(\n\ttx: Tx,\n\tconversationId: ConversationId,\n\tcontent: UserInput,\n\tentry: InputEntry | undefined,\n\tnow: number,\n): Promise<EntryRecord> {\n\tconst message = { role: \"user\", content, timestamp: now } as const;\n\tif (entry === undefined) return tx.appendEntry(UserEntry, conversationId, { model: [message] });\n\treturn tx.appendEntry(conversationId, { ...pickInputEntry(entry), model: [message] });\n}\n\n/** The kind and data of an input's entry as strict JSON, without other fields an untyped caller might pass. */\nexport function pickInputEntry({ kind, data }: InputEntry): JsonRepresentation<InputEntry> {\n\tconst picked = data === undefined ? { kind } : { kind, data };\n\treturn copyJson(picked, { omitUndefinedProperties: true }) as JsonRepresentation<InputEntry>;\n}\n\n/**\n * Whether a write is display-only: it has no `model`, `head`, or `edits` (present counts, even if empty), so it changes\n * nothing the model sees and needs no boundary.\n */\nexport function isDisplayOnly(entry: EntryDraft): boolean {\n\treturn entry.model === undefined && entry.head === undefined && entry.edits === undefined;\n}\n\n/** Whether a reset (`head: \"self\"` write) is queued; entries appended before it leave the active range when it lands. */\nexport function hasQueuedReset(items: readonly InboxItem[]): boolean {\n\treturn items.some((item) => item.mode === \"write\" && item.entry.head === \"self\");\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"]}
|
|
1
|
+
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/harness/inbox.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyJson, type Draft, type JsonRepresentation } from \"@earendil-works/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport { UserEntry } from \"../entries.ts\";\nimport type { ConversationId, EntryDraft, EntryId, EntryRecord, JsonObject, SubmissionId, Tx } from \"../types.ts\";\nimport type { InputEntry, QueueMode, Settings, UserInput } from \"./types.ts\";\n\n/** A queued submission: user input for a run, with the entry it is placed as when not `pi.user`, or an entry write. */\nexport type InboxItem =\n\t| {\n\t\t\tid: SubmissionId;\n\t\t\tmode: \"steer\" | \"followUp\";\n\t\t\tcontent: JsonRepresentation<UserInput>;\n\t\t\tentry?: JsonRepresentation<InputEntry>;\n\t }\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/** The settings a boundary reads, on the Session line. */\nexport type QueueModes = Pick<Settings, \"steeringMode\" | \"followUpMode\">;\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/**\n * Selected user items, in ID order, the first entry the boundary placed, a write's or a user item's (a run it starts\n * begins there, `LiveRun.first`), and whether a `head: \"self\"` write (a reset) was placed.\n */\nexport type BoundaryResult = {\n\treadonly users: SubmissionId[];\n\treadonly first: EntryId | undefined;\n\treadonly reset: boolean;\n};\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, modes: QueueModes): Promise<Boundary> {\n\tconst head = (await tx.latestHeadMarker(conversationId))?.head;\n\tconst inbox = await tx.doc(InboxDoc, conversationId);\n\treturn {\n\t\tconversationId,\n\t\tinbox,\n\t\tsteeringMode: modes.steeringMode,\n\t\tfollowUpMode: modes.followUpMode,\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 = hasQueuedReset(items);\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\tlet first: EntryId | undefined;\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\tfirst ??= 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 entry = await appendInput(tx, conversationId, item.content as UserInput, item.entry as InputEntry, now);\n\t\ttx.placeSubmission(item.id, entry.id);\n\t\tplaced.push(item.id);\n\t\tfirst ??= entry.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, first, reset };\n}\n\n/** Append the entry an input is placed as: `entry`'s kind and data, or `pi.user`, with the user message of `content`. */\nexport function appendInput(\n\ttx: Tx,\n\tconversationId: ConversationId,\n\tcontent: UserInput,\n\tentry: InputEntry | undefined,\n\tnow: number,\n): Promise<EntryRecord> {\n\tconst message = { role: \"user\", content, timestamp: now } as const;\n\tif (entry === undefined) return tx.appendEntry(UserEntry, conversationId, { model: [message] });\n\treturn tx.appendEntry(conversationId, { ...pickInputEntry(entry), model: [message] });\n}\n\n/** The kind and data of an input's entry as strict JSON, without other fields an untyped caller might pass. */\nexport function pickInputEntry({ kind, data }: InputEntry): JsonRepresentation<InputEntry> {\n\tconst picked = data === undefined ? { kind } : { kind, data };\n\treturn copyJson(picked, { omitUndefinedProperties: true }) as JsonRepresentation<InputEntry>;\n}\n\n/**\n * Whether a write is display-only: it has no `model`, `head`, or `edits` (present counts, even if empty), so it changes\n * nothing the model sees and needs no boundary.\n */\nexport function isDisplayOnly(entry: EntryDraft): boolean {\n\treturn entry.model === undefined && entry.head === undefined && entry.edits === undefined;\n}\n\n/** Whether a reset (`head: \"self\"` write) is queued; entries appended before it leave the active range when it lands. */\nexport function hasQueuedReset(items: readonly InboxItem[]): boolean {\n\treturn items.some((item) => item.mode === \"write\" && item.entry.head === \"self\");\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 `reason` (default `aborted`) and leaves the inbox; queued writes stay for later placement.\n */\nexport async function withdrawQueuedInputs(tx: Tx, conversationId: ConversationId, reason = \"aborted\"): 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 });\n\t\titems.splice(index, 1);\n\t}\n}\n"]}
|
package/dist/harness/json.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import type { JsonValue } from "@earendil-works/chord";
|
|
2
|
-
|
|
2
|
+
/** How `assignJson()` writes one value instead of assigning it leaf by leaf. */
|
|
3
|
+
export type AssignPlan =
|
|
4
|
+
/** The string grew by `text` since it was last assigned: append it, when the lengths agree. */
|
|
5
|
+
{
|
|
6
|
+
readonly kind: "append";
|
|
7
|
+
readonly text: string;
|
|
8
|
+
}
|
|
9
|
+
/** Leave the value as it is; the caller writes it. */
|
|
10
|
+
| {
|
|
11
|
+
readonly kind: "keep";
|
|
12
|
+
}
|
|
13
|
+
/** Plans for some members or items of a container. */
|
|
14
|
+
| {
|
|
15
|
+
readonly kind: "fields";
|
|
16
|
+
readonly fields: ReadonlyMap<string | number, AssignPlan>;
|
|
17
|
+
};
|
|
3
18
|
/**
|
|
4
19
|
* Assign `value` at `target[key]` leaf by leaf. Chord records a container assignment as one full set and only emits an
|
|
5
20
|
* 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.
|
|
21
|
+
* complete message on every flush. That append costs a compare of the whole string, though; where `plan` says what a
|
|
22
|
+
* string member grew by, and the committed string plus that text has the new string's length, the text is appended
|
|
23
|
+
* with `appendText()` instead.
|
|
7
24
|
*/
|
|
8
|
-
export declare function assignJson
|
|
9
|
-
export {};
|
|
25
|
+
export declare function assignJson<T extends object, K extends keyof T & (string | number)>(target: T, key: K, value: T[K] & JsonValue, plan?: AssignPlan): void;
|
|
10
26
|
//# sourceMappingURL=json.d.ts.map
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/harness/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD,gFAAgF;AAChF,MAAM,MAAM,UAAU;AACrB,+FAA+F;AAC7F;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACpD,sDAAsD;GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC3B,sDAAsD;GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,GAAG,MAAM,EAAE,UAAU,CAAC,CAAA;CAAE,CAAC;AAE1F;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EACjF,MAAM,EAAE,CAAC,EACT,GAAG,EAAE,CAAC,EACN,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,EACvB,IAAI,CAAC,EAAE,UAAU,GACf,IAAI,CAiCN"}
|
package/dist/harness/json.js
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
|
+
import { appendText } from "@earendil-works/chord/delta";
|
|
1
2
|
/**
|
|
2
3
|
* Assign `value` at `target[key]` leaf by leaf. Chord records a container assignment as one full set and only emits an
|
|
3
4
|
* 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
|
+
* complete message on every flush. That append costs a compare of the whole string, though; where `plan` says what a
|
|
6
|
+
* string member grew by, and the committed string plus that text has the new string's length, the text is appended
|
|
7
|
+
* with `appendText()` instead.
|
|
5
8
|
*/
|
|
6
|
-
export function assignJson(target, key, value) {
|
|
9
|
+
export function assignJson(target, key, value, plan) {
|
|
10
|
+
if (plan?.kind === "keep")
|
|
11
|
+
return;
|
|
12
|
+
// Every target is a document container: JSON members or items, whatever its declared type names them.
|
|
7
13
|
const slots = target;
|
|
8
|
-
|
|
14
|
+
// A `__proto__` member the target lacks reads through to Object.prototype; merging into that would pollute every
|
|
15
|
+
// object in the process. Other inherited members are functions, which are not merged into.
|
|
16
|
+
const current = key === "__proto__" && !Object.hasOwn(slots, key) ? undefined : slots[key];
|
|
17
|
+
const fields = plan?.kind === "fields" ? plan.fields : undefined;
|
|
9
18
|
if (isRecord(current) && isRecord(value)) {
|
|
10
19
|
for (const name of Object.keys(current))
|
|
11
20
|
if (!Object.hasOwn(value, name))
|
|
12
21
|
delete current[name];
|
|
13
22
|
for (const [name, child] of Object.entries(value))
|
|
14
|
-
assignJson(current, name, child);
|
|
23
|
+
assignJson(current, name, child, fields?.get(name));
|
|
15
24
|
return;
|
|
16
25
|
}
|
|
17
26
|
if (Array.isArray(current) && Array.isArray(value) && current.length <= value.length) {
|
|
18
27
|
const items = current;
|
|
19
28
|
for (let index = 0; index < value.length; index++) {
|
|
20
29
|
if (index < items.length)
|
|
21
|
-
assignJson(items, index, value[index]);
|
|
30
|
+
assignJson(items, index, value[index], fields?.get(index));
|
|
22
31
|
else
|
|
23
32
|
items.push(value[index]);
|
|
24
33
|
}
|
|
25
34
|
return;
|
|
26
35
|
}
|
|
36
|
+
if (plan?.kind === "append" &&
|
|
37
|
+
typeof current === "string" &&
|
|
38
|
+
typeof value === "string" &&
|
|
39
|
+
!Array.isArray(target) &&
|
|
40
|
+
current.length + plan.text.length === value.length) {
|
|
41
|
+
// The cast names what the checks above established: `target[key]` is a string.
|
|
42
|
+
appendText(target, String(key), plan.text);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
27
45
|
if (current !== value)
|
|
28
46
|
slots[key] = value;
|
|
29
47
|
}
|
package/dist/harness/json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/harness/json.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/harness/json.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAc,MAAM,6BAA6B,CAAC;AAWrE;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACzB,MAAS,EACT,GAAM,EACN,KAAuB,EACvB,IAAiB;IAEjB,IAAI,IAAI,EAAE,IAAI,KAAK,MAAM;QAAE,OAAO;IAClC,sGAAsG;IACtG,MAAM,KAAK,GAAG,MAA4C,CAAC;IAC3D,iHAAiH;IACjH,2FAA2F;IAC3F,MAAM,OAAO,GAA0B,GAAG,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClH,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,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,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACvG,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,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;gBACjF,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAE,CAAC,CAAC;QAChC,CAAC;QACD,OAAO;IACR,CAAC;IACD,IACC,IAAI,EAAE,IAAI,KAAK,QAAQ;QACvB,OAAO,OAAO,KAAK,QAAQ;QAC3B,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EACjD,CAAC;QACF,+EAA+E;QAC/E,UAAU,CAAC,MAAuC,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5E,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\";\nimport { appendText, type Draft } from \"@earendil-works/chord/delta\";\n\n/** How `assignJson()` writes one value instead of assigning it leaf by leaf. */\nexport type AssignPlan =\n\t/** The string grew by `text` since it was last assigned: append it, when the lengths agree. */\n\t| { readonly kind: \"append\"; readonly text: string }\n\t/** Leave the value as it is; the caller writes it. */\n\t| { readonly kind: \"keep\" }\n\t/** Plans for some members or items of a container. */\n\t| { readonly kind: \"fields\"; readonly fields: ReadonlyMap<string | number, AssignPlan> };\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. That append costs a compare of the whole string, though; where `plan` says what a\n * string member grew by, and the committed string plus that text has the new string's length, the text is appended\n * with `appendText()` instead.\n */\nexport function assignJson<T extends object, K extends keyof T & (string | number)>(\n\ttarget: T,\n\tkey: K,\n\tvalue: T[K] & JsonValue,\n\tplan?: AssignPlan,\n): void {\n\tif (plan?.kind === \"keep\") return;\n\t// Every target is a document container: JSON members or items, whatever its declared type names them.\n\tconst slots = target as Record<string | number, JsonValue>;\n\t// A `__proto__` member the target lacks reads through to Object.prototype; merging into that would pollute every\n\t// object in the process. Other inherited members are functions, which are not merged into.\n\tconst current: JsonValue | undefined = key === \"__proto__\" && !Object.hasOwn(slots, key) ? undefined : slots[key];\n\tconst fields = plan?.kind === \"fields\" ? plan.fields : undefined;\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, fields?.get(name));\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]!, fields?.get(index));\n\t\t\telse items.push(value[index]!);\n\t\t}\n\t\treturn;\n\t}\n\tif (\n\t\tplan?.kind === \"append\" &&\n\t\ttypeof current === \"string\" &&\n\t\ttypeof value === \"string\" &&\n\t\t!Array.isArray(target) &&\n\t\tcurrent.length + plan.text.length === value.length\n\t) {\n\t\t// The cast names what the checks above established: `target[key]` is a string.\n\t\tappendText(target as Draft<Record<string, string>>, String(key), plan.text);\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"]}
|
package/dist/harness/live.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Draft, JsonRepresentation, JsonValue } from "@earendil-works/chord";
|
|
2
2
|
import type { AssistantMessage, Usage } from "@earendil-works/pi-ai";
|
|
3
3
|
import type { Transaction } from "../session/transaction.ts";
|
|
4
|
-
import type { EntryId, JsonObject, SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from "../types.ts";
|
|
4
|
+
import type { CommitOptions, EntryId, JsonObject, SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from "../types.ts";
|
|
5
5
|
import type { SchedulerOutcome } from "./scheduler.ts";
|
|
6
6
|
import type { CompactionReason, CompactionResult, ToolDiagnostic } from "./types.ts";
|
|
7
7
|
/** What a running tool call publishes, shared by model-issued and nested calls. */
|
|
@@ -20,17 +20,16 @@ type SlotProgress = {
|
|
|
20
20
|
};
|
|
21
21
|
/** Presentation of one tool call of the current round. */
|
|
22
22
|
export type ToolSlot = SlotProgress & {
|
|
23
|
-
/**
|
|
24
|
-
* Absent for a call not started yet (sequential round) and for a call its request did not offer, which starts `done`
|
|
25
|
-
* with the `entry` generation wrote.
|
|
26
|
-
*/
|
|
23
|
+
/** Absent for a call not started yet (sequential round). */
|
|
27
24
|
taskId?: TaskId;
|
|
25
|
+
/** A call to a tool its request did not offer: its task settles `tool_unavailable` without running anything. */
|
|
26
|
+
unoffered?: true;
|
|
28
27
|
/** Result entry once done; absent when the tool task faulted or was orphaned. */
|
|
29
28
|
entry?: EntryId;
|
|
30
29
|
};
|
|
31
30
|
/**
|
|
32
31
|
* Presentation of one nested call: a call a running tool made through `executeTool()`. `callId` is
|
|
33
|
-
* `<parentCallId>/<key>`, the call's ID
|
|
32
|
+
* `<parentCallId>/<key>`, the call's ID, unique because keys contain no `/`. The Harness relates
|
|
34
33
|
* slots by task ID: `taskId` is the call's tool task, `parentTaskId` the calling one, model-issued or nested.
|
|
35
34
|
*/
|
|
36
35
|
export type NestedToolSlot = SlotProgress & {
|
|
@@ -55,7 +54,8 @@ export type NestedToolSummary = {
|
|
|
55
54
|
/** Presentation of one live compaction task (spec §8.7). */
|
|
56
55
|
export type CompactionStatus = {
|
|
57
56
|
taskId: TaskId<CompactionResult>;
|
|
58
|
-
|
|
57
|
+
/** What started it: `compact()`, a threshold, or a context overflow. */
|
|
58
|
+
trigger: CompactionReason;
|
|
59
59
|
/** Whether a generation waits for it: a compaction the generation owns. */
|
|
60
60
|
blocking: boolean;
|
|
61
61
|
attempt: number;
|
|
@@ -68,17 +68,71 @@ export type CompactionStatus = {
|
|
|
68
68
|
/** The newest compaction that failed since a compaction last produced a summary (spec §8.7). */
|
|
69
69
|
export type CompactionFailure = {
|
|
70
70
|
taskId: TaskId<CompactionResult>;
|
|
71
|
-
|
|
71
|
+
/** What started the compaction that failed. */
|
|
72
|
+
trigger: CompactionReason;
|
|
72
73
|
/** The failed or faulted outcome's error message, or why the scheduler orphaned the task. */
|
|
73
74
|
error: string;
|
|
74
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Why a run ended without an answer:
|
|
78
|
+
* - `model_error`: the model's response failed, after any retries, or the compaction a request needed failed;
|
|
79
|
+
* - `no_model`: no model is configured, or the configured one is not available;
|
|
80
|
+
* - `aborted`: the run was aborted (`Conversation.abort()`, `abortTask()`); `detail` says when its abort handler was
|
|
81
|
+
* cut off `settings.restartLimit` times and did not finish;
|
|
82
|
+
* - `reset`: a queued reset started a new context at the post-tools boundary, before an answer (a tool's handoff
|
|
83
|
+
* continues the run);
|
|
84
|
+
* - `faulted`: the run's task threw where it cannot recover, or storage rejected its commit;
|
|
85
|
+
* - `orphaned`: the run's task was aborted while no registered definition could take it;
|
|
86
|
+
* - `interrupted`: the host stopped while the model was answering, more often than `settings.retry` retries;
|
|
87
|
+
* - `restart_limit`: the run's generation was cut off `settings.restartLimit` times in a row; a request or poll it was
|
|
88
|
+
* cut off during counts as a failed attempt first, so this ends one whose charge itself keeps failing;
|
|
89
|
+
* - `nothing_to_answer`: the context did not end in a user message or tool results (empty, or ending in an answer), so
|
|
90
|
+
* no request was sent, as for a continue after an answer;
|
|
91
|
+
* - `turn_limit`: a tool round reached the conversation's `maxTurns`; a continue resumes on its results.
|
|
92
|
+
*/
|
|
93
|
+
export type RunFailureReason = "model_error" | "no_model" | "aborted" | "reset" | "faulted" | "orphaned" | "interrupted" | "restart_limit" | "nothing_to_answer" | "turn_limit";
|
|
94
|
+
/** The newest run that ended without an answer (spec §8.2). */
|
|
95
|
+
export type RunFailure = {
|
|
96
|
+
/** The task that ended the run: the run's task at its end. */
|
|
97
|
+
taskId: TaskId;
|
|
98
|
+
/** The run's inputs, settled `unanswered`. */
|
|
99
|
+
inputs: SubmissionId[];
|
|
100
|
+
reason: RunFailureReason;
|
|
101
|
+
/**
|
|
102
|
+
* `model_error`: the error text; `no_model`: which model is missing; `faulted`: the error message; `orphaned`: why
|
|
103
|
+
* the task could not run (`missing_task`, `task_too_old`, `migration_failed`); `interrupted` and `restart_limit`: what
|
|
104
|
+
* was cut off, and how often; `turn_limit`: the tool rounds the run took, `"1 turn"` or `"N turns"`.
|
|
105
|
+
*/
|
|
106
|
+
detail?: string;
|
|
107
|
+
/** The failed run's `run.first`, so a continue's `afterRun` hooks see its history too. */
|
|
108
|
+
first?: EntryId;
|
|
109
|
+
};
|
|
110
|
+
/** Run control (spec §8.2). */
|
|
111
|
+
export type LiveRun = {
|
|
112
|
+
/** The task that settles the run's inputs: its generation, handed from one to the next. */
|
|
113
|
+
taskId: TaskId;
|
|
114
|
+
/** The run's placed inputs, settled when it ends; `[]` once it answered and runs only its `afterRun` tasks. */
|
|
115
|
+
inputs: SubmissionId[];
|
|
116
|
+
/** Tool rounds so far, which `AgentState.maxTurns` bounds; an `onYield` continuation starts the count over. */
|
|
117
|
+
turns: number;
|
|
118
|
+
/**
|
|
119
|
+
* The run's first entry: the first one the boundary that started it placed, a continue's the failed run's it
|
|
120
|
+
* continues; otherwise its first tool-calling answer. A run started before it was recorded may lack it; `afterRun`
|
|
121
|
+
* hooks then get the answer as the first entry.
|
|
122
|
+
*/
|
|
123
|
+
first?: EntryId;
|
|
124
|
+
/**
|
|
125
|
+
* Present once the run answered and its generation waits for the tasks `afterRun` hooks asked for, listed once
|
|
126
|
+
* created: queued items stay queued until they are terminal, and the run then ends without a failure.
|
|
127
|
+
*/
|
|
128
|
+
afterRun?: {
|
|
129
|
+
tasks?: TaskId[];
|
|
130
|
+
};
|
|
131
|
+
};
|
|
75
132
|
/** Built-in live conversation state: run control and presentation of the current generation and tool round. */
|
|
76
133
|
export type LiveState = {
|
|
77
|
-
/** Run control
|
|
78
|
-
run?:
|
|
79
|
-
taskId: TaskId;
|
|
80
|
-
inputs: SubmissionId[];
|
|
81
|
-
};
|
|
134
|
+
/** Run control; present exactly while busy, also while a run that answered runs its `afterRun` tasks. */
|
|
135
|
+
run?: LiveRun;
|
|
82
136
|
/** Presentation of the current generation attempt. */
|
|
83
137
|
generation?: {
|
|
84
138
|
attempt: number;
|
|
@@ -108,13 +162,65 @@ export type LiveState = {
|
|
|
108
162
|
* removes it; one that is aborted or finds nothing to compact leaves it.
|
|
109
163
|
*/
|
|
110
164
|
compactionFailure?: CompactionFailure;
|
|
165
|
+
/**
|
|
166
|
+
* Why the newest run ended without an answer, so a client can show it after the run ended. Set in the commit that
|
|
167
|
+
* ends the run; removed when a later commit starts a run. A run started in the same commit, by
|
|
168
|
+
* `afterFailedRun: "continue"` or after a reset, keeps it.
|
|
169
|
+
*/
|
|
170
|
+
runFailure?: RunFailure;
|
|
111
171
|
};
|
|
112
172
|
export declare const LiveDoc: import("../types.ts").ConversationDocToken<LiveState>;
|
|
113
173
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
174
|
+
* How the progress throttles commit a partial answer and running tool output into `pi.live`: deferred, so storage gets
|
|
175
|
+
* them together, at the next commit that reads or writes `pi.live` or after `settings.progress.persistWithinMs`. Every
|
|
176
|
+
* commit that consumes or clears streamed fields reads `pi.live`, so it stores them with its own changes (spec §8.2).
|
|
177
|
+
*/
|
|
178
|
+
export declare const PROGRESS_COMMIT: CommitOptions;
|
|
179
|
+
/**
|
|
180
|
+
* Built-in task kinds that end themselves once cut off `settings.restartLimit` times (`TaskRecord.restarts`); the
|
|
181
|
+
* scheduler faults a task of any other kind instead (spec §5.4).
|
|
182
|
+
*/
|
|
183
|
+
export declare const LIMITS_RESTARTS: ReadonlySet<string>;
|
|
184
|
+
/** Whether `task` was cut off `settings.restartLimit` times in a row, so its phase ends it instead of running. */
|
|
185
|
+
export declare function cutOffTooOften(task: {
|
|
186
|
+
readonly restarts?: number;
|
|
187
|
+
}, runtime: {
|
|
188
|
+
readonly settings: {
|
|
189
|
+
readonly restartLimit: number;
|
|
190
|
+
};
|
|
191
|
+
}): boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Whether a task at the limit may still do the cleanup it does when it ends, such as converting a partial: only at the
|
|
194
|
+
* limit. Past it, that cleanup may be what cut the task off, so the task ends without it.
|
|
195
|
+
*/
|
|
196
|
+
export declare function salvageable(task: {
|
|
197
|
+
readonly restarts?: number;
|
|
198
|
+
}, runtime: {
|
|
199
|
+
readonly settings: {
|
|
200
|
+
readonly restartLimit: number;
|
|
201
|
+
};
|
|
202
|
+
}): boolean;
|
|
203
|
+
/** How often a task was cut off, for a message: "once", or "3 times in a row". */
|
|
204
|
+
export declare function cutOffCount(task: {
|
|
205
|
+
readonly restarts?: number;
|
|
206
|
+
}): string;
|
|
207
|
+
/** How a run ends: with an answer, or without one, settling its inputs `unanswered` and recording why. */
|
|
208
|
+
export type RunEnd = {
|
|
209
|
+
readonly status: "done";
|
|
210
|
+
readonly answer: EntryId;
|
|
211
|
+
} | {
|
|
212
|
+
readonly status: "unanswered";
|
|
213
|
+
readonly reason: RunFailureReason;
|
|
214
|
+
readonly detail?: string;
|
|
215
|
+
/** The inputs' settlement; default `unanswered` with `reason` and `detail`. */
|
|
216
|
+
readonly settlement?: SubmissionSettlement;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* End the run owned by `taskId`: settle each of its inputs, remove `run`, and, without an answer, record
|
|
220
|
+
* `runFailure`. A run that answered already, and runs only its `afterRun` tasks, records no failure, however it ends.
|
|
221
|
+
* Always removes `generation`, `tools`, and `nestedTools`, whose presentation belongs to the ending run.
|
|
116
222
|
*/
|
|
117
|
-
export declare function endRun(tx: Tx, live: Draft<LiveState>, taskId: TaskId,
|
|
223
|
+
export declare function endRun(tx: Tx, live: Draft<LiveState>, taskId: TaskId, end: RunEnd): void;
|
|
118
224
|
/** Add the status of a compaction task created in this commit; statuses stay in task ID order. */
|
|
119
225
|
export declare function addCompactionStatus(live: Draft<LiveState>, status: CompactionStatus): void;
|
|
120
226
|
/** The status of compaction task `taskId`, if listed. */
|
|
@@ -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,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/harness/live.ts"],"names":[],"mappings":"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"}
|
package/dist/harness/live.js
CHANGED
|
@@ -2,11 +2,24 @@ import { defineDoc } from "../documents.js";
|
|
|
2
2
|
import { convertPartial } from "./generation.js";
|
|
3
3
|
export const LiveDoc = defineDoc({
|
|
4
4
|
kind: "pi.live",
|
|
5
|
-
version:
|
|
5
|
+
version: 3,
|
|
6
6
|
scope: "conversation",
|
|
7
7
|
history: "latest",
|
|
8
8
|
fork: "initial",
|
|
9
9
|
initial: () => ({}),
|
|
10
|
+
// Version 1 named a compaction's trigger `reason`, in `compactions` and `compactionFailure`. Versions 1 and 2 (as
|
|
11
|
+
// builds before `run.turns` stored it) may lack `run.turns`: a run in flight across the upgrade counts its turns
|
|
12
|
+
// from 0. Version 2 already names the trigger `trigger`, so renaming leaves it alone.
|
|
13
|
+
migrate: (value) => {
|
|
14
|
+
const live = value;
|
|
15
|
+
const { run, compactions, compactionFailure } = live;
|
|
16
|
+
return {
|
|
17
|
+
...value,
|
|
18
|
+
...(run === undefined ? {} : { run: { ...run, turns: typeof run.turns === "number" ? run.turns : 0 } }),
|
|
19
|
+
...(compactions === undefined ? {} : { compactions: compactions.map(renameReason) }),
|
|
20
|
+
...(compactionFailure === undefined ? {} : { compactionFailure: renameReason(compactionFailure) }),
|
|
21
|
+
};
|
|
22
|
+
},
|
|
10
23
|
// REMINDER: a complete base whenever nothing runs (spec §8.2): no generation and no running tool slot. That holds
|
|
11
24
|
// while idle, in the commit handing a generation over to its tool round, and between tools, so the delta chain
|
|
12
25
|
// spans at most one generation or the overlapping execution of one round's tools. A slot holds output only while
|
|
@@ -16,18 +29,72 @@ export const LiveDoc = defineDoc({
|
|
|
16
29
|
!(value.tools ?? []).some((slot) => slot.status === "running") &&
|
|
17
30
|
!(value.nestedTools ?? []).some((slot) => slot.status === "running"),
|
|
18
31
|
});
|
|
32
|
+
/** A copy of a version 1 compaction status or failure with `reason` renamed `trigger`, its keys in their order. */
|
|
33
|
+
function renameReason(value) {
|
|
34
|
+
return Object.fromEntries(Object.entries(value).map(([key, field]) => [key === "reason" ? "trigger" : key, field]));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* How the progress throttles commit a partial answer and running tool output into `pi.live`: deferred, so storage gets
|
|
38
|
+
* them together, at the next commit that reads or writes `pi.live` or after `settings.progress.persistWithinMs`. Every
|
|
39
|
+
* commit that consumes or clears streamed fields reads `pi.live`, so it stores them with its own changes (spec §8.2).
|
|
40
|
+
*/
|
|
41
|
+
export const PROGRESS_COMMIT = { persistence: { kind: "deferred" } };
|
|
19
42
|
/** Built-in task kinds that can own `pi.live.run`. */
|
|
20
43
|
const RUN_TASK_KINDS = new Set(["pi.generation"]);
|
|
21
44
|
const TOOL_TASK_KIND = "pi.tool";
|
|
22
45
|
const COMPACTION_TASK_KIND = "pi.compaction";
|
|
23
46
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
47
|
+
* Built-in task kinds that end themselves once cut off `settings.restartLimit` times (`TaskRecord.restarts`); the
|
|
48
|
+
* scheduler faults a task of any other kind instead (spec §5.4).
|
|
49
|
+
*/
|
|
50
|
+
export const LIMITS_RESTARTS = new Set([...RUN_TASK_KINDS, TOOL_TASK_KIND, COMPACTION_TASK_KIND]);
|
|
51
|
+
/** Whether `task` was cut off `settings.restartLimit` times in a row, so its phase ends it instead of running. */
|
|
52
|
+
export function cutOffTooOften(task, runtime) {
|
|
53
|
+
// The settings are read only for a task that was cut off at all, so the normal path resolves nothing more.
|
|
54
|
+
return task.restarts !== undefined && task.restarts >= runtime.settings.restartLimit;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether a task at the limit may still do the cleanup it does when it ends, such as converting a partial: only at the
|
|
58
|
+
* limit. Past it, that cleanup may be what cut the task off, so the task ends without it.
|
|
26
59
|
*/
|
|
27
|
-
export function
|
|
60
|
+
export function salvageable(task, runtime) {
|
|
61
|
+
return (task.restarts ?? 0) <= runtime.settings.restartLimit;
|
|
62
|
+
}
|
|
63
|
+
/** How often a task was cut off, for a message: "once", or "3 times in a row". */
|
|
64
|
+
export function cutOffCount(task) {
|
|
65
|
+
const restarts = task.restarts ?? 0;
|
|
66
|
+
return restarts === 1 ? "once" : `${restarts} times in a row`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* End the run owned by `taskId`: settle each of its inputs, remove `run`, and, without an answer, record
|
|
70
|
+
* `runFailure`. A run that answered already, and runs only its `afterRun` tasks, records no failure, however it ends.
|
|
71
|
+
* Always removes `generation`, `tools`, and `nestedTools`, whose presentation belongs to the ending run.
|
|
72
|
+
*/
|
|
73
|
+
export function endRun(tx, live, taskId, end) {
|
|
28
74
|
if (live.run?.taskId === taskId) {
|
|
29
|
-
|
|
30
|
-
|
|
75
|
+
const inputs = [...live.run.inputs];
|
|
76
|
+
const { first } = live.run;
|
|
77
|
+
if (end.status === "done") {
|
|
78
|
+
for (const id of inputs)
|
|
79
|
+
tx.settleSubmission(id, { status: "done", answer: end.answer });
|
|
80
|
+
}
|
|
81
|
+
else if (live.run.afterRun === undefined) {
|
|
82
|
+
const { reason, detail } = end;
|
|
83
|
+
const settlement = end.settlement ?? {
|
|
84
|
+
status: "unanswered",
|
|
85
|
+
reason,
|
|
86
|
+
...(detail === undefined ? {} : { detail }),
|
|
87
|
+
};
|
|
88
|
+
for (const id of inputs)
|
|
89
|
+
tx.settleSubmission(id, settlement);
|
|
90
|
+
live.runFailure = {
|
|
91
|
+
taskId,
|
|
92
|
+
inputs,
|
|
93
|
+
reason,
|
|
94
|
+
...(detail === undefined ? {} : { detail }),
|
|
95
|
+
...(first === undefined ? {} : { first }),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
31
98
|
delete live.run;
|
|
32
99
|
}
|
|
33
100
|
delete live.generation;
|
|
@@ -58,7 +125,7 @@ export function removeCompactionStatus(live, taskId) {
|
|
|
58
125
|
export function failCompactionStatus(live, taskId, error) {
|
|
59
126
|
const status = compactionStatus(live, taskId);
|
|
60
127
|
if (status !== undefined)
|
|
61
|
-
live.compactionFailure = { taskId: status.taskId,
|
|
128
|
+
live.compactionFailure = { taskId: status.taskId, trigger: status.trigger, error };
|
|
62
129
|
removeCompactionStatus(live, taskId);
|
|
63
130
|
}
|
|
64
131
|
/**
|
|
@@ -139,9 +206,16 @@ export async function settleSchedulerOutcome(tx, record, outcome) {
|
|
|
139
206
|
removeNestedSlots(live, record.id);
|
|
140
207
|
return;
|
|
141
208
|
}
|
|
209
|
+
// Only an abort handler is faulted for the limit here (the built-in phases end themselves): the task was aborted,
|
|
210
|
+
// and keeps that meaning. Its run ends `aborted`, with the limit as detail, and an aborted compaction is no failure.
|
|
211
|
+
const detail = outcome.status === "faulted" ? outcome.error.detail : undefined;
|
|
212
|
+
const cutOff = typeof detail === "object" && detail !== null && "reason" in detail && detail.reason === "restart_limit";
|
|
142
213
|
if (record.kind === COMPACTION_TASK_KIND) {
|
|
143
|
-
const
|
|
144
|
-
|
|
214
|
+
const live = await tx.doc(LiveDoc, record.conversationId);
|
|
215
|
+
if (cutOff)
|
|
216
|
+
removeCompactionStatus(live, record.id);
|
|
217
|
+
else
|
|
218
|
+
failCompactionStatus(live, record.id, outcome.status === "faulted" ? outcome.error.message : outcome.reason);
|
|
145
219
|
return;
|
|
146
220
|
}
|
|
147
221
|
if (!RUN_TASK_KINDS.has(record.kind))
|
|
@@ -149,9 +223,16 @@ export async function settleSchedulerOutcome(tx, record, outcome) {
|
|
|
149
223
|
const live = await tx.doc(LiveDoc, record.conversationId);
|
|
150
224
|
if (live.run?.taskId !== record.id)
|
|
151
225
|
return;
|
|
152
|
-
|
|
226
|
+
// Cut off, the abort handler may have died converting the partial: the run ends without it.
|
|
227
|
+
if (!cutOff)
|
|
228
|
+
await convertPartial(tx, live, record.conversationId);
|
|
153
229
|
endRun(tx, live, record.id, outcome.status === "faulted"
|
|
154
|
-
? { status: "unanswered", reason: "faulted", detail: outcome.error.message }
|
|
155
|
-
: {
|
|
230
|
+
? { status: "unanswered", reason: cutOff ? "aborted" : "faulted", detail: outcome.error.message }
|
|
231
|
+
: {
|
|
232
|
+
status: "unanswered",
|
|
233
|
+
reason: "orphaned",
|
|
234
|
+
detail: outcome.reason,
|
|
235
|
+
settlement: { status: "unanswered", reason: outcome.reason },
|
|
236
|
+
});
|
|
156
237
|
}
|
|
157
238
|
//# sourceMappingURL=live.js.map
|