@fungi.computer/shiitake 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +454 -2
- package/dist/agar-extension.d.ts +9 -0
- package/dist/agar-extension.js +38 -0
- package/dist/agent-control.d.ts +72 -0
- package/dist/agent-control.js +431 -0
- package/dist/client-entry.d.ts +211 -0
- package/dist/client-entry.js +101 -0
- package/dist/client-wire.d.ts +199 -0
- package/dist/client-wire.js +271 -0
- package/dist/client.d.ts +439 -0
- package/dist/client.js +4 -0
- package/dist/cloudflare.d.ts +220 -0
- package/dist/cloudflare.js +531 -0
- package/dist/effect.d.ts +42 -0
- package/dist/effect.js +20 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +81 -0
- package/dist/extension-client.d.ts +38 -0
- package/dist/extension-client.js +56 -0
- package/dist/extension-contract.d.ts +73 -0
- package/dist/extension-contract.js +11 -0
- package/dist/extensions.d.ts +589 -0
- package/dist/extensions.js +263 -0
- package/dist/fleet.d.ts +297 -0
- package/dist/fleet.js +515 -0
- package/dist/hosted.d.ts +133 -0
- package/dist/hosted.js +265 -0
- package/dist/http-routes.d.ts +772 -0
- package/dist/http-routes.js +242 -0
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +13 -0
- package/dist/internal/accepted-run-executor.d.ts +23 -0
- package/dist/internal/accepted-run-executor.js +129 -0
- package/dist/internal/admission-door.d.ts +75 -0
- package/dist/internal/admission-door.js +105 -0
- package/dist/internal/agent-close.d.ts +4 -0
- package/dist/internal/agent-close.js +42 -0
- package/dist/internal/browser-sessions.d.ts +31 -0
- package/dist/internal/browser-sessions.js +173 -0
- package/dist/internal/client-boundary.d.ts +262 -0
- package/dist/internal/client-boundary.js +202 -0
- package/dist/internal/compaction/compaction.d.ts +54 -0
- package/dist/internal/compaction/compaction.js +292 -0
- package/dist/internal/compaction/session-compaction.d.ts +69 -0
- package/dist/internal/compaction/session-compaction.js +330 -0
- package/dist/internal/compaction/settings.d.ts +11 -0
- package/dist/internal/compaction/settings.js +40 -0
- package/dist/internal/compaction/utils.d.ts +45 -0
- package/dist/internal/compaction/utils.js +319 -0
- package/dist/internal/contracts.d.ts +235 -0
- package/dist/internal/contracts.js +1 -0
- package/dist/internal/coordinator.d.ts +18 -0
- package/dist/internal/coordinator.js +362 -0
- package/dist/internal/document-codec.d.ts +11 -0
- package/dist/internal/document-codec.js +39 -0
- package/dist/internal/effect-client.d.ts +13 -0
- package/dist/internal/effect-client.js +102 -0
- package/dist/internal/effect-contracts.d.ts +236 -0
- package/dist/internal/effect-contracts.js +2 -0
- package/dist/internal/extension-command-registry.d.ts +11 -0
- package/dist/internal/extension-command-registry.js +29 -0
- package/dist/internal/extension-command-tools.d.ts +17 -0
- package/dist/internal/extension-command-tools.js +50 -0
- package/dist/internal/extension-context.d.ts +25 -0
- package/dist/internal/extension-context.js +35 -0
- package/dist/internal/extension-definitions.d.ts +5 -0
- package/dist/internal/extension-definitions.js +6 -0
- package/dist/internal/extension-hook.d.ts +25 -0
- package/dist/internal/extension-hook.js +46 -0
- package/dist/internal/extension-roster.d.ts +23 -0
- package/dist/internal/extension-roster.js +228 -0
- package/dist/internal/extension-runtime.d.ts +63 -0
- package/dist/internal/extension-runtime.js +1004 -0
- package/dist/internal/failure.d.ts +2 -0
- package/dist/internal/failure.js +9 -0
- package/dist/internal/host-meta.d.ts +41 -0
- package/dist/internal/host-meta.js +39 -0
- package/dist/internal/json-value.d.ts +4 -0
- package/dist/internal/json-value.js +3 -0
- package/dist/internal/model-acquisition.d.ts +21 -0
- package/dist/internal/model-acquisition.js +98 -0
- package/dist/internal/model-preference.d.ts +21 -0
- package/dist/internal/model-preference.js +20 -0
- package/dist/internal/observation-kernel.d.ts +28 -0
- package/dist/internal/observation-kernel.js +169 -0
- package/dist/internal/promise-facade.d.ts +18 -0
- package/dist/internal/promise-facade.js +172 -0
- package/dist/internal/run-scope.d.ts +27 -0
- package/dist/internal/run-scope.js +214 -0
- package/dist/internal/runtime-coordinator.d.ts +34 -0
- package/dist/internal/runtime-coordinator.js +233 -0
- package/dist/internal/runtime-core.d.ts +19 -0
- package/dist/internal/runtime-core.js +39 -0
- package/dist/internal/runtime-reconciliation.d.ts +18 -0
- package/dist/internal/runtime-reconciliation.js +162 -0
- package/dist/internal/session-context.d.ts +11 -0
- package/dist/internal/session-context.js +67 -0
- package/dist/internal/session-status.d.ts +10 -0
- package/dist/internal/session-status.js +52 -0
- package/dist/internal/woodstock/agent-reply.d.ts +25 -0
- package/dist/internal/woodstock/agent-reply.js +67 -0
- package/dist/internal/woodstock/client-projection.d.ts +29 -0
- package/dist/internal/woodstock/client-projection.js +512 -0
- package/dist/internal/woodstock/compaction-activity.d.ts +17 -0
- package/dist/internal/woodstock/compaction-activity.js +32 -0
- package/dist/internal/woodstock/cursor.d.ts +9 -0
- package/dist/internal/woodstock/cursor.js +44 -0
- package/dist/internal/woodstock/extension-store.d.ts +72 -0
- package/dist/internal/woodstock/extension-store.js +262 -0
- package/dist/internal/woodstock/fleet-store.d.ts +32 -0
- package/dist/internal/woodstock/fleet-store.js +93 -0
- package/dist/internal/woodstock/identities.d.ts +6 -0
- package/dist/internal/woodstock/identities.js +11 -0
- package/dist/internal/woodstock/index.d.ts +170 -0
- package/dist/internal/woodstock/index.js +546 -0
- package/dist/internal/woodstock/progress.d.ts +32 -0
- package/dist/internal/woodstock/progress.js +89 -0
- package/dist/internal/woodstock/run-recovery.d.ts +8 -0
- package/dist/internal/woodstock/run-recovery.js +95 -0
- package/dist/internal/woodstock/session-attention.d.ts +16 -0
- package/dist/internal/woodstock/session-attention.js +88 -0
- package/dist/internal/woodstock/session-documents.d.ts +18 -0
- package/dist/internal/woodstock/session-documents.js +51 -0
- package/dist/internal/woodstock/session-entry.d.ts +478 -0
- package/dist/internal/woodstock/session-entry.js +484 -0
- package/dist/internal/woodstock/session-grants.d.ts +24 -0
- package/dist/internal/woodstock/session-grants.js +84 -0
- package/dist/internal/woodstock/session-inbox.d.ts +56 -0
- package/dist/internal/woodstock/session-inbox.js +307 -0
- package/dist/internal/woodstock/session-store.d.ts +432 -0
- package/dist/internal/woodstock/session-store.js +2006 -0
- package/dist/internal/woodstock/storage-admission.d.ts +14 -0
- package/dist/internal/woodstock/storage-admission.js +44 -0
- package/dist/model-preference.d.ts +20 -0
- package/dist/model-preference.js +30 -0
- package/dist/node.d.ts +64 -0
- package/dist/node.js +256 -0
- package/dist/remote-client.d.ts +11 -0
- package/dist/remote-client.js +298 -0
- package/dist/session-attention.d.ts +35 -0
- package/dist/session-attention.js +22 -0
- package/dist/session-documents.d.ts +28 -0
- package/dist/session-documents.js +1 -0
- package/dist/testing.d.ts +54 -0
- package/dist/testing.js +215 -0
- package/docs/api/README.md +26 -0
- package/docs/api/agar-extension.md +40 -0
- package/docs/api/agent-control.md +151 -0
- package/docs/api/client-entry.md +344 -0
- package/docs/api/client.md +1112 -0
- package/docs/api/cloudflare.md +500 -0
- package/docs/api/effect.md +904 -0
- package/docs/api/extensions.md +2003 -0
- package/docs/api/fleet.md +706 -0
- package/docs/api/hosted.md +382 -0
- package/docs/api/http.md +111 -0
- package/docs/api/index.md +1116 -0
- package/docs/api/model-preference.md +151 -0
- package/docs/api/node.md +142 -0
- package/docs/api/testing.md +250 -0
- package/package.json +130 -3
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { hasSessionAttention } from "./session-attention.js";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import * as Struct from "effect/Struct";
|
|
4
|
+
import { ShiitakeError } from "../../errors.js";
|
|
5
|
+
/** Rows returned by one bulk page. This bounds work, never saved Sessions. */
|
|
6
|
+
const sessionInboxPageSize = 200;
|
|
7
|
+
const closed = { onExcessProperty: "error" };
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const boundedId = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.makeFilter((value) => encoder.encode(value).byteLength <= 256)));
|
|
10
|
+
const timestamp = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
11
|
+
const tierSchema = Schema.Literals([0, 1, 2]);
|
|
12
|
+
const stateSchema = Schema.Literals([
|
|
13
|
+
"needs_you",
|
|
14
|
+
"running",
|
|
15
|
+
"errored",
|
|
16
|
+
"done",
|
|
17
|
+
]);
|
|
18
|
+
const outcomeSchema = Schema.Literals([
|
|
19
|
+
"completed",
|
|
20
|
+
"failed",
|
|
21
|
+
"cancelled",
|
|
22
|
+
"outcome_unknown",
|
|
23
|
+
"interrupted",
|
|
24
|
+
]);
|
|
25
|
+
const InboxRowSchema = Schema.Struct({
|
|
26
|
+
session_id: boundedId,
|
|
27
|
+
title: Schema.NullOr(Schema.String),
|
|
28
|
+
created_at: timestamp,
|
|
29
|
+
last_activity_at: timestamp,
|
|
30
|
+
settled_at: Schema.NullOr(timestamp),
|
|
31
|
+
state: stateSchema,
|
|
32
|
+
tier: tierSchema,
|
|
33
|
+
});
|
|
34
|
+
const decodeInboxRow = Schema.decodeUnknownResult(InboxRowSchema, closed);
|
|
35
|
+
const decodeInboxChangeRow = Schema.decodeUnknownResult(InboxRowSchema.mapFields(Struct.assign({ revision: timestamp })), closed);
|
|
36
|
+
const decodeClockRow = Schema.decodeUnknownResult(Schema.Struct({ revision: timestamp }), closed);
|
|
37
|
+
const InboxRunRowSchema = Schema.Struct({
|
|
38
|
+
outcome: Schema.NullOr(outcomeSchema),
|
|
39
|
+
});
|
|
40
|
+
const decodeInboxRunRow = Schema.decodeUnknownResult(InboxRunRowSchema, closed);
|
|
41
|
+
const PageCursorSchema = Schema.fromJsonString(Schema.Tuple([tierSchema, timestamp, boundedId]));
|
|
42
|
+
const decodePageCursor = Schema.decodeUnknownResult(PageCursorSchema);
|
|
43
|
+
const encodePageCursor = Schema.encodeSync(PageCursorSchema);
|
|
44
|
+
const inboxColumns = "session_id, title, created_at, last_activity_at, settled_at, state, tier";
|
|
45
|
+
function unavailable(message) {
|
|
46
|
+
return new ShiitakeError("store", "unavailable", message);
|
|
47
|
+
}
|
|
48
|
+
/** Queue order: waiting on a person, then live work, then settled recency. */
|
|
49
|
+
function tierOf(state) {
|
|
50
|
+
switch (state) {
|
|
51
|
+
case "needs_you":
|
|
52
|
+
return 0;
|
|
53
|
+
case "running":
|
|
54
|
+
return 1;
|
|
55
|
+
case "errored":
|
|
56
|
+
case "done":
|
|
57
|
+
return 2;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The one decision from Watchdog authority to inbox state. Live work is
|
|
62
|
+
* `running` whatever its sub-status; an idle queue reports its latest outcome.
|
|
63
|
+
* Open extension attention takes priority over both live and settled work.
|
|
64
|
+
*/
|
|
65
|
+
function inboxStateFor(head, outcome, attentionOpen) {
|
|
66
|
+
if (attentionOpen)
|
|
67
|
+
return "needs_you";
|
|
68
|
+
if (head.state !== "idle")
|
|
69
|
+
return "running";
|
|
70
|
+
switch (outcome) {
|
|
71
|
+
case "failed":
|
|
72
|
+
case "interrupted":
|
|
73
|
+
case "outcome_unknown":
|
|
74
|
+
return "errored";
|
|
75
|
+
case "completed":
|
|
76
|
+
case "cancelled":
|
|
77
|
+
case null:
|
|
78
|
+
return "done";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Create the inbox table, its keyset and revision indexes and the revision clock. */
|
|
82
|
+
export function prepareSessionInbox(owner) {
|
|
83
|
+
owner.sql.exec(`
|
|
84
|
+
CREATE TABLE IF NOT EXISTS session_inbox (
|
|
85
|
+
session_id TEXT PRIMARY KEY REFERENCES sessions(id),
|
|
86
|
+
title TEXT,
|
|
87
|
+
created_at INTEGER NOT NULL,
|
|
88
|
+
last_activity_at INTEGER NOT NULL,
|
|
89
|
+
settled_at INTEGER,
|
|
90
|
+
outcome TEXT CHECK (
|
|
91
|
+
outcome IS NULL OR outcome IN (
|
|
92
|
+
'completed', 'failed', 'cancelled', 'outcome_unknown', 'interrupted'
|
|
93
|
+
)
|
|
94
|
+
),
|
|
95
|
+
state TEXT NOT NULL
|
|
96
|
+
CHECK (state IN ('needs_you', 'running', 'errored', 'done')),
|
|
97
|
+
tier INTEGER NOT NULL CHECK (tier IN (0, 1, 2)),
|
|
98
|
+
revision INTEGER NOT NULL
|
|
99
|
+
)
|
|
100
|
+
`);
|
|
101
|
+
owner.sql.exec(`CREATE INDEX IF NOT EXISTS idx_session_inbox_order
|
|
102
|
+
ON session_inbox(tier, last_activity_at DESC, session_id)`);
|
|
103
|
+
owner.sql.exec(`CREATE INDEX IF NOT EXISTS idx_session_inbox_revision
|
|
104
|
+
ON session_inbox(revision)`);
|
|
105
|
+
owner.sql.exec(`
|
|
106
|
+
CREATE TABLE IF NOT EXISTS session_inbox_clock (
|
|
107
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
108
|
+
revision INTEGER NOT NULL
|
|
109
|
+
)
|
|
110
|
+
`);
|
|
111
|
+
owner.sql.exec("INSERT OR IGNORE INTO session_inbox_clock (singleton, revision) VALUES (1, 0)");
|
|
112
|
+
owner.sql.exec(`
|
|
113
|
+
CREATE TABLE IF NOT EXISTS session_inbox_tombstones (
|
|
114
|
+
session_id TEXT PRIMARY KEY,
|
|
115
|
+
revision INTEGER NOT NULL
|
|
116
|
+
) STRICT
|
|
117
|
+
`);
|
|
118
|
+
owner.sql.exec("CREATE INDEX IF NOT EXISTS idx_session_inbox_tombstones_revision ON session_inbox_tombstones(revision)");
|
|
119
|
+
}
|
|
120
|
+
/** Advance the owner revision stamped on the row this transaction changes. */
|
|
121
|
+
function advanceRevision(owner) {
|
|
122
|
+
owner.sql.exec("UPDATE session_inbox_clock SET revision = revision + 1 WHERE singleton = 1");
|
|
123
|
+
}
|
|
124
|
+
const currentRevision = "(SELECT revision FROM session_inbox_clock WHERE singleton = 1)";
|
|
125
|
+
/**
|
|
126
|
+
* Upsert title and recency in the caller's Session transaction. A new row
|
|
127
|
+
* starts `done`: nothing has run, and Watchdog's projector owns run state.
|
|
128
|
+
*/
|
|
129
|
+
export function touchSessionInbox(owner, facts) {
|
|
130
|
+
const initialTitle = facts.title === undefined ? "" : facts.title;
|
|
131
|
+
const replaceTitle = facts.title === undefined ? 0 : 1;
|
|
132
|
+
advanceRevision(owner);
|
|
133
|
+
owner.sql.exec(`INSERT INTO session_inbox
|
|
134
|
+
(session_id, title, created_at, last_activity_at, settled_at, outcome,
|
|
135
|
+
state, tier, revision)
|
|
136
|
+
SELECT id, ?, created_at, COALESCE(?, created_at), NULL, NULL, 'done', 2,
|
|
137
|
+
${currentRevision}
|
|
138
|
+
FROM sessions
|
|
139
|
+
WHERE id = ?
|
|
140
|
+
ON CONFLICT (session_id) DO UPDATE SET
|
|
141
|
+
title = CASE WHEN ? = 1 THEN excluded.title ELSE title END,
|
|
142
|
+
last_activity_at = MAX(last_activity_at, excluded.last_activity_at),
|
|
143
|
+
revision = excluded.revision`, initialTitle, facts.activityAt ?? null, facts.sessionId, replaceTitle);
|
|
144
|
+
}
|
|
145
|
+
/** Record a deletion in the Session owner's transaction and revision stream. */
|
|
146
|
+
export function recordSessionInboxDeletion(owner, sessionId) {
|
|
147
|
+
advanceRevision(owner);
|
|
148
|
+
owner.sql.exec(`INSERT INTO session_inbox_tombstones (session_id, revision)
|
|
149
|
+
VALUES (?, ${currentRevision})
|
|
150
|
+
ON CONFLICT (session_id) DO UPDATE SET revision = excluded.revision`, sessionId);
|
|
151
|
+
}
|
|
152
|
+
/** Drop tombstones the current member subscribers have all acknowledged. */
|
|
153
|
+
export function pruneSessionInboxTombstones(owner, through) {
|
|
154
|
+
owner.sql.exec("DELETE FROM session_inbox_tombstones WHERE revision <= ?", through);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Watchdog's same-transaction projector. Queues that are not saved Sessions
|
|
158
|
+
* have no row and are left alone.
|
|
159
|
+
*/
|
|
160
|
+
export function projectSessionRun(owner, transition, now) {
|
|
161
|
+
const raw = owner.sql
|
|
162
|
+
.exec("SELECT outcome FROM session_inbox WHERE session_id = ?", transition.queue)
|
|
163
|
+
.toArray()[0];
|
|
164
|
+
if (raw === undefined)
|
|
165
|
+
return;
|
|
166
|
+
const prior = decodeInboxRunRow(raw);
|
|
167
|
+
if (prior._tag === "Failure")
|
|
168
|
+
throw unavailable("Stored inbox row is invalid");
|
|
169
|
+
const outcome = transition.settled?.outcome ?? prior.success.outcome;
|
|
170
|
+
const state = inboxStateFor(transition.head, outcome, hasSessionAttention(owner, transition.queue));
|
|
171
|
+
advanceRevision(owner);
|
|
172
|
+
owner.sql.exec(`UPDATE session_inbox
|
|
173
|
+
SET outcome = ?, state = ?, tier = ?, revision = ${currentRevision},
|
|
174
|
+
settled_at = CASE WHEN ? = 1 THEN ? ELSE settled_at END,
|
|
175
|
+
last_activity_at = MAX(last_activity_at, ?)
|
|
176
|
+
WHERE session_id = ?`, outcome, state, tierOf(state), transition.settled === undefined ? 0 : 1, now, now, transition.queue);
|
|
177
|
+
}
|
|
178
|
+
/** Refresh attention using the current queue head in the extension transaction. */
|
|
179
|
+
export function projectSessionAttention(owner, sessionId, head) {
|
|
180
|
+
const raw = owner.sql
|
|
181
|
+
.exec("SELECT outcome FROM session_inbox WHERE session_id = ?", sessionId)
|
|
182
|
+
.toArray()[0];
|
|
183
|
+
if (raw === undefined)
|
|
184
|
+
return;
|
|
185
|
+
const prior = decodeInboxRunRow(raw);
|
|
186
|
+
if (prior._tag === "Failure")
|
|
187
|
+
throw unavailable("Stored inbox row is invalid");
|
|
188
|
+
const state = inboxStateFor(head, prior.success.outcome, hasSessionAttention(owner, sessionId));
|
|
189
|
+
advanceRevision(owner);
|
|
190
|
+
owner.sql.exec(`UPDATE session_inbox SET state = ?, tier = ?, revision = ${currentRevision} WHERE session_id = ?`, state, tierOf(state), sessionId);
|
|
191
|
+
}
|
|
192
|
+
function summaryRow(row) {
|
|
193
|
+
if (row.title === null)
|
|
194
|
+
return Object.freeze({ sessionId: row.session_id, state: "unavailable" });
|
|
195
|
+
const base = {
|
|
196
|
+
sessionId: row.session_id,
|
|
197
|
+
title: row.title,
|
|
198
|
+
createdAt: row.created_at,
|
|
199
|
+
lastActivityAt: row.last_activity_at,
|
|
200
|
+
state: row.state,
|
|
201
|
+
};
|
|
202
|
+
return Object.freeze(row.settled_at === null ? base : { ...base, settledAt: row.settled_at });
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* A bounded strict-after read of changed rows. Each Session appears once, at
|
|
206
|
+
* its latest revision, so pending delivery is bounded by Session count.
|
|
207
|
+
*/
|
|
208
|
+
export function readSessionInboxChanges(owner, after, limit) {
|
|
209
|
+
const clock = decodeClockRow(owner.sql
|
|
210
|
+
.exec("SELECT revision FROM session_inbox_clock WHERE singleton = 1")
|
|
211
|
+
.one());
|
|
212
|
+
if (clock._tag === "Failure")
|
|
213
|
+
throw unavailable("Inbox revision is invalid");
|
|
214
|
+
const rows = owner.sql
|
|
215
|
+
.exec(`SELECT ${inboxColumns}, revision FROM session_inbox
|
|
216
|
+
WHERE revision > ?
|
|
217
|
+
ORDER BY revision ASC
|
|
218
|
+
LIMIT ?`, after, limit)
|
|
219
|
+
.toArray();
|
|
220
|
+
const deleted = owner.sql
|
|
221
|
+
.exec(`SELECT session_id, revision FROM session_inbox_tombstones
|
|
222
|
+
WHERE revision > ? ORDER BY revision ASC LIMIT ?`, after, limit)
|
|
223
|
+
.toArray()
|
|
224
|
+
.map((raw) => {
|
|
225
|
+
const parsed = Schema.decodeUnknownResult(Schema.Struct({ session_id: boundedId, revision: timestamp }), closed)(raw);
|
|
226
|
+
if (parsed._tag === "Failure")
|
|
227
|
+
throw unavailable("Stored inbox tombstone is invalid");
|
|
228
|
+
return {
|
|
229
|
+
revision: parsed.success.revision,
|
|
230
|
+
delta: { sessionId: parsed.success.session_id, deleted: true },
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
const changed = rows.map((raw) => {
|
|
234
|
+
const decoded = decodeInboxChangeRow(raw);
|
|
235
|
+
if (decoded._tag === "Failure")
|
|
236
|
+
throw unavailable("Stored inbox row is invalid");
|
|
237
|
+
const { revision, ...row } = decoded.success;
|
|
238
|
+
return { revision, delta: summaryRow(row) };
|
|
239
|
+
});
|
|
240
|
+
const ordered = [...changed, ...deleted]
|
|
241
|
+
.sort((left, right) => left.revision - right.revision)
|
|
242
|
+
.slice(0, limit);
|
|
243
|
+
let through = after;
|
|
244
|
+
const deltas = [];
|
|
245
|
+
for (const change of ordered) {
|
|
246
|
+
through = change.revision;
|
|
247
|
+
deltas.push(change.delta);
|
|
248
|
+
}
|
|
249
|
+
return { rows: deltas, through, latest: clock.success.revision };
|
|
250
|
+
}
|
|
251
|
+
function parseCursor(after) {
|
|
252
|
+
const decoded = decodePageCursor(after);
|
|
253
|
+
if (decoded._tag === "Failure")
|
|
254
|
+
throw new ShiitakeError("contract", "invalid", "Session cursor is invalid");
|
|
255
|
+
return decoded.success;
|
|
256
|
+
}
|
|
257
|
+
/** Indexed lookup using the same row decoder and availability as the inbox listing. */
|
|
258
|
+
export function readSessionInboxSummary(owner, sessionId) {
|
|
259
|
+
const raw = owner.sql
|
|
260
|
+
.exec(`SELECT ${inboxColumns} FROM session_inbox WHERE session_id = ?`, sessionId)
|
|
261
|
+
.toArray()[0];
|
|
262
|
+
if (raw === undefined)
|
|
263
|
+
return undefined;
|
|
264
|
+
const decoded = decodeInboxRow(raw);
|
|
265
|
+
if (decoded._tag === "Failure")
|
|
266
|
+
throw unavailable("Stored inbox row is invalid");
|
|
267
|
+
return summaryRow(decoded.success);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* One bounded page in tier, then newest-activity order: a single indexed
|
|
271
|
+
* statement regardless of how many Sessions the Agent holds. A row whose
|
|
272
|
+
* stored facts are unreadable is its own `unavailable` row.
|
|
273
|
+
*/
|
|
274
|
+
export function readSessionInboxPage(owner, after) {
|
|
275
|
+
const cursor = after === undefined ? undefined : parseCursor(after);
|
|
276
|
+
const rows = cursor === undefined
|
|
277
|
+
? owner.sql
|
|
278
|
+
.exec(`SELECT ${inboxColumns} FROM session_inbox
|
|
279
|
+
ORDER BY tier ASC, last_activity_at DESC, session_id ASC
|
|
280
|
+
LIMIT ?`, sessionInboxPageSize)
|
|
281
|
+
.toArray()
|
|
282
|
+
: owner.sql
|
|
283
|
+
.exec(`SELECT ${inboxColumns} FROM session_inbox
|
|
284
|
+
WHERE tier > ?
|
|
285
|
+
OR (tier = ? AND last_activity_at < ?)
|
|
286
|
+
OR (tier = ? AND last_activity_at = ? AND session_id > ?)
|
|
287
|
+
ORDER BY tier ASC, last_activity_at DESC, session_id ASC
|
|
288
|
+
LIMIT ?`, cursor[0], cursor[0], cursor[1], cursor[0], cursor[1], cursor[2], sessionInboxPageSize)
|
|
289
|
+
.toArray();
|
|
290
|
+
const sessions = [];
|
|
291
|
+
let last;
|
|
292
|
+
for (const raw of rows) {
|
|
293
|
+
const decoded = decodeInboxRow(raw);
|
|
294
|
+
// Without a valid identity and position the row cannot be paged past.
|
|
295
|
+
if (decoded._tag === "Failure")
|
|
296
|
+
throw unavailable("Stored inbox row is invalid");
|
|
297
|
+
const row = decoded.success;
|
|
298
|
+
last = [row.tier, row.last_activity_at, row.session_id];
|
|
299
|
+
sessions.push(summaryRow(row));
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
sessions,
|
|
303
|
+
next: rows.length === sessionInboxPageSize && last !== undefined
|
|
304
|
+
? encodePageCursor(last)
|
|
305
|
+
: undefined,
|
|
306
|
+
};
|
|
307
|
+
}
|