@stratusagent/gateway 0.11.3 → 0.11.5
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/README.md +5 -1
- package/dist/index.d.ts +80 -82
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +302 -208
- package/dist/index.js.map +1 -1
- package/dist/sessions.d.ts +211 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +592 -0
- package/dist/sessions.js.map +1 -0
- package/package.json +7 -7
package/dist/sessions.js
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { lstat, readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
import { agentSessionDbIn, agentStateDirIn, agentsDirIn, assertPathSafeAgentId, fleetDbIn, isSymlinkedStatePathSync, symlinkedStateDirectoryMessage, symlinkedStateFileMessage, } from '@stratusagent/state';
|
|
6
|
+
/**
|
|
7
|
+
* Sessions live one database per agent — `agents/<id>/sessions.db` — with
|
|
8
|
+
* one fleet-wide index saying which agent's store holds a given session id.
|
|
9
|
+
*
|
|
10
|
+
* The sharding is the point (step 15's layer A): a store is opened on one
|
|
11
|
+
* agent's path, so there is no query that could return another agent's
|
|
12
|
+
* conversations. The index is what the sharding costs. Session ids are
|
|
13
|
+
* caller-chosen and the control API resolves them *without* an agent —
|
|
14
|
+
* `GET /sessions/:id`, a message to an existing session — which was
|
|
15
|
+
* unambiguous only because one shared database's primary key made a
|
|
16
|
+
* duplicate id unrepresentable. The index restores that: it is where an id
|
|
17
|
+
* is claimed, and it is what a lookup with no agent in hand consults.
|
|
18
|
+
*
|
|
19
|
+
* It also carries `status`, `created_at`, and `updated_at`, because the
|
|
20
|
+
* session surface is fleet-wide in more places than the point lookup: the
|
|
21
|
+
* health counts, the roster's per-agent activity, the unfiltered listing,
|
|
22
|
+
* and the restart sweep that resumes parked approvals and fails abandoned
|
|
23
|
+
* turns. Answering those from the index rather than by fanning out over
|
|
24
|
+
* every shard is what keeps a recovery sweep from walking only whichever
|
|
25
|
+
* store happened to be open — an agent whose parked approval never
|
|
26
|
+
* resumes.
|
|
27
|
+
*/
|
|
28
|
+
const SESSIONS_TABLE = `
|
|
29
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
agent_id TEXT NOT NULL,
|
|
32
|
+
status TEXT NOT NULL,
|
|
33
|
+
body TEXT NOT NULL,
|
|
34
|
+
created_at TEXT NOT NULL,
|
|
35
|
+
updated_at TEXT NOT NULL
|
|
36
|
+
)
|
|
37
|
+
`;
|
|
38
|
+
/**
|
|
39
|
+
* The same columns minus the conversation body: the index is a routing
|
|
40
|
+
* table and a set of claimed ids, never a second copy of the transcript.
|
|
41
|
+
* Nothing about a conversation can be read out of the fleet database.
|
|
42
|
+
*/
|
|
43
|
+
const SESSION_INDEX_TABLE = `
|
|
44
|
+
CREATE TABLE IF NOT EXISTS session_index (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
agent_id TEXT NOT NULL,
|
|
47
|
+
status TEXT NOT NULL,
|
|
48
|
+
created_at TEXT NOT NULL,
|
|
49
|
+
updated_at TEXT NOT NULL
|
|
50
|
+
)
|
|
51
|
+
`;
|
|
52
|
+
/**
|
|
53
|
+
* Sessions hold complete conversations (prompts, replies, tool output,
|
|
54
|
+
* provider replay state) — owner-only, like the credentials file. The file
|
|
55
|
+
* chmods cover databases created earlier under a looser umask too;
|
|
56
|
+
* directories the stores create are born 0700.
|
|
57
|
+
*
|
|
58
|
+
* Exported because the layout migration writes the same files from
|
|
59
|
+
* `@stratusagent/state` before any store is open, and a second hand-rolled
|
|
60
|
+
* chmod loop is how one of the two ends up leaving a sidecar readable.
|
|
61
|
+
*/
|
|
62
|
+
export const tightenSqliteFile = (filePath) => {
|
|
63
|
+
// The database file is tightened FIRST: SQLite derives sidecar
|
|
64
|
+
// permissions from the main file's mode, so everything created later
|
|
65
|
+
// inherits owner-only.
|
|
66
|
+
for (const sensitive of [filePath, `${filePath}-wal`, `${filePath}-shm`, `${filePath}-journal`]) {
|
|
67
|
+
try {
|
|
68
|
+
chmodSync(sensitive, 0o600);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
// A sidecar that does not exist has nothing to tighten; anything
|
|
72
|
+
// else means session data stays readable — refuse to run over it.
|
|
73
|
+
if (error.code !== 'ENOENT') {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Whether a real file is there — the sweep's test for "this directory holds
|
|
81
|
+
* a shard".
|
|
82
|
+
*
|
|
83
|
+
* `lstat`, so a symlink is not one. `stat` follows it, which would classify
|
|
84
|
+
* a directory holding a linked `sessions.db` as an agent's and hand the
|
|
85
|
+
* sweep a store it then creates, indexes and tightens somewhere outside the
|
|
86
|
+
* home. The old layout reserved none of these names, so an operator's
|
|
87
|
+
* `agents/<something>/sessions.db` pointing elsewhere is theirs, not ours.
|
|
88
|
+
*/
|
|
89
|
+
const shardFileExists = async (filePath) => {
|
|
90
|
+
try {
|
|
91
|
+
return (await lstat(filePath)).isFile();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error.code === 'ENOENT') {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Opens the file, tightens it and its sidecars, and puts the connection in
|
|
102
|
+
* the mode every Stratus database runs in. Shared by the per-agent store,
|
|
103
|
+
* the fleet index, and the schedule store, because the posture is one rule:
|
|
104
|
+
* WAL (a DELETE journal recreates a rollback file on every write, which a
|
|
105
|
+
* one-time chmod could never cover), a busy timeout (these files have a
|
|
106
|
+
* second writer — `stratus schedules cancel` opens its own connection —
|
|
107
|
+
* and WAL serializes writers file-wide), and owner-only throughout.
|
|
108
|
+
*/
|
|
109
|
+
const openStratusDatabase = (filePath, options = {}) => {
|
|
110
|
+
const dir = path.dirname(filePath);
|
|
111
|
+
if (options.perAgent) {
|
|
112
|
+
// Never a symlink — see `isSymlinkedStatePath`, which owns that rule.
|
|
113
|
+
// Refused rather than quarantined, unlike the migration: at runtime
|
|
114
|
+
// there is no report to name it in, and a turn that cannot be stored
|
|
115
|
+
// must not read as stored.
|
|
116
|
+
//
|
|
117
|
+
// Under `perAgent` and not `ownedDirectory`, which is the distinction
|
|
118
|
+
// this comment used to describe while the code ignored it: `~/.stratus`
|
|
119
|
+
// is a symlink on plenty of real installs (a home on another disk), and
|
|
120
|
+
// the fleet index and the schedule store live directly in it.
|
|
121
|
+
if (isSymlinkedStatePathSync(dir)) {
|
|
122
|
+
throw new Error(symlinkedStateDirectoryMessage(dir));
|
|
123
|
+
}
|
|
124
|
+
// And the database itself. A link here is followed just as readily:
|
|
125
|
+
// the table is created, the rows indexed and the mode tightened in
|
|
126
|
+
// whatever it points at.
|
|
127
|
+
if (isSymlinkedStatePathSync(filePath)) {
|
|
128
|
+
throw new Error(symlinkedStateFileMessage(filePath));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
132
|
+
if (options.ownedDirectory === true || options.perAgent === true) {
|
|
133
|
+
chmodSync(dir, 0o700);
|
|
134
|
+
}
|
|
135
|
+
const db = new DatabaseSync(filePath);
|
|
136
|
+
try {
|
|
137
|
+
chmodSync(filePath, 0o600);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// A database that cannot be tightened must not be used: conversation
|
|
141
|
+
// bodies would stay readable by other local users for the daemon's
|
|
142
|
+
// whole lifetime, silently.
|
|
143
|
+
if (error.code !== 'ENOENT') {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Forced into existence here (the user_version pragma is a real page-one
|
|
148
|
+
// write) so the tightening below covers the WAL sidecars for the
|
|
149
|
+
// connection's whole lifetime.
|
|
150
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
151
|
+
db.exec('PRAGMA busy_timeout = 5000');
|
|
152
|
+
return db;
|
|
153
|
+
};
|
|
154
|
+
export class SqliteSessionStore {
|
|
155
|
+
db;
|
|
156
|
+
constructor(filePath, options = {}) {
|
|
157
|
+
this.db = openStratusDatabase(filePath, options);
|
|
158
|
+
this.db.exec(SESSIONS_TABLE);
|
|
159
|
+
this.db.exec('PRAGMA user_version = 0');
|
|
160
|
+
tightenSqliteFile(filePath);
|
|
161
|
+
}
|
|
162
|
+
write(session) {
|
|
163
|
+
this.db
|
|
164
|
+
.prepare('INSERT OR REPLACE INTO sessions (id, agent_id, status, body, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
|
|
165
|
+
.run(session.id, session.agent.id, session.status, JSON.stringify(session), session.createdAt, session.updatedAt);
|
|
166
|
+
return session;
|
|
167
|
+
}
|
|
168
|
+
async create(input) {
|
|
169
|
+
const now = new Date().toISOString();
|
|
170
|
+
return this.write({ ...input, createdAt: now, updatedAt: now });
|
|
171
|
+
}
|
|
172
|
+
async get(id) {
|
|
173
|
+
const row = this.db.prepare('SELECT body FROM sessions WHERE id = ?').get(id);
|
|
174
|
+
return row ? JSON.parse(row.body) : undefined;
|
|
175
|
+
}
|
|
176
|
+
async save(session) {
|
|
177
|
+
await this.saveReturning(session);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The same save, handing back the row as it was written.
|
|
181
|
+
*
|
|
182
|
+
* For a caller that has to mirror it: the sharded store keeps the fleet
|
|
183
|
+
* index in step, and an index holding a timestamp the store does not is
|
|
184
|
+
* drift the startup reconcile would have to clean up every boot.
|
|
185
|
+
*/
|
|
186
|
+
async saveReturning(session) {
|
|
187
|
+
return this.write({ ...session, updatedAt: new Date().toISOString() });
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Session ids in a state, oldest first — the index a restarting daemon
|
|
191
|
+
* sweeps for turns parked on a human. The `status` column carries it, so
|
|
192
|
+
* no conversation body is deserialized to answer the question.
|
|
193
|
+
*/
|
|
194
|
+
async listIdsByStatus(status) {
|
|
195
|
+
const rows = this.db
|
|
196
|
+
.prepare('SELECT id FROM sessions WHERE status = ? ORDER BY updated_at ASC')
|
|
197
|
+
.all(status);
|
|
198
|
+
return rows.map((row) => row.id);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Every row's routing and lifecycle columns, no bodies — what the
|
|
202
|
+
* startup reconcile compares this shard against the fleet index with.
|
|
203
|
+
*/
|
|
204
|
+
rows() {
|
|
205
|
+
const rows = this.db
|
|
206
|
+
.prepare('SELECT id, agent_id, status, created_at, updated_at FROM sessions')
|
|
207
|
+
.all();
|
|
208
|
+
return rows.map((row) => ({
|
|
209
|
+
id: row.id,
|
|
210
|
+
agentId: row.agent_id,
|
|
211
|
+
status: row.status,
|
|
212
|
+
createdAt: row.created_at,
|
|
213
|
+
updatedAt: row.updated_at,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
close() {
|
|
217
|
+
this.db.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Thrown when a session id another agent already holds is claimed.
|
|
222
|
+
*
|
|
223
|
+
* Sessions never cross agent identities — the dispatcher says so when it
|
|
224
|
+
* finds the conversation, and this is the same rule at the durable seam,
|
|
225
|
+
* where a shared database's primary key used to enforce it for free.
|
|
226
|
+
*/
|
|
227
|
+
export class SessionIdTakenError extends Error {
|
|
228
|
+
constructor(sessionId, heldBy, claimedBy) {
|
|
229
|
+
super(`Session ${sessionId} already belongs to agent ${heldBy}, so ${claimedBy} cannot open a conversation under that id — `
|
|
230
|
+
+ 'sessions never cross agent identities. Give the new conversation an id of its own.');
|
|
231
|
+
this.name = 'SessionIdTakenError';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The fleet's session index, in `fleet.db` beside the schedules.
|
|
236
|
+
*
|
|
237
|
+
* Its own connection rather than a table on the schedule store, for the
|
|
238
|
+
* reason the schedule store gives for its own: `stratus schedules` opens
|
|
239
|
+
* that file from another process, and WAL is what makes two connections on
|
|
240
|
+
* one file routine.
|
|
241
|
+
*/
|
|
242
|
+
export class FleetSessionIndex {
|
|
243
|
+
db;
|
|
244
|
+
constructor(filePath, options = {}) {
|
|
245
|
+
this.db = openStratusDatabase(filePath, options);
|
|
246
|
+
this.db.exec(SESSION_INDEX_TABLE);
|
|
247
|
+
tightenSqliteFile(filePath);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Claim an id for an agent, or refuse it.
|
|
251
|
+
*
|
|
252
|
+
* The claim is the create's single transactional authority: it lands
|
|
253
|
+
* before the shard write, so a crash between the two leaves a claim with
|
|
254
|
+
* no conversation — which the reconcile releases — rather than a
|
|
255
|
+
* conversation no lookup can reach. Re-claiming an id this agent already
|
|
256
|
+
* holds is genuinely a no-op: `create` on an existing id is how a caller
|
|
257
|
+
* re-opens its own session, and `save` claims every id it writes.
|
|
258
|
+
*
|
|
259
|
+
* Taking the id is all this does. It deliberately does NOT write the row
|
|
260
|
+
* it was handed, because the id it is claiming may already have a
|
|
261
|
+
* conversation: overwriting that row's status and timestamps here would
|
|
262
|
+
* announce a transition the shard write has not made yet, and a write
|
|
263
|
+
* that then fails leaves the live daemon answering from a lifecycle row
|
|
264
|
+
* no store agrees with — a pending session missing from recovery until
|
|
265
|
+
* the next restart reconciles it. The row is written by the caller,
|
|
266
|
+
* after the shard write, from what the store actually stored.
|
|
267
|
+
*/
|
|
268
|
+
claim(row) {
|
|
269
|
+
// The insert is what decides, not a read before it. `DO NOTHING` makes
|
|
270
|
+
// the row's creation the atomic step: two callers racing for an id no
|
|
271
|
+
// one holds cannot both find it free, because one of their inserts is
|
|
272
|
+
// the conflict. The read after it is then a question with an answer —
|
|
273
|
+
// who holds this id now — rather than a guess that a write could have
|
|
274
|
+
// overtaken. Nothing is overwritten on the way, so a claim that loses
|
|
275
|
+
// has not touched the winner's row.
|
|
276
|
+
this.db
|
|
277
|
+
.prepare('INSERT INTO session_index (id, agent_id, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING')
|
|
278
|
+
.run(row.id, row.agentId, row.status, row.createdAt, row.updatedAt);
|
|
279
|
+
const held = this.agentFor(row.id);
|
|
280
|
+
if (held !== undefined && held !== row.agentId) {
|
|
281
|
+
throw new SessionIdTakenError(row.id, held, row.agentId);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** Write the row as it now stands. Called after every shard write. */
|
|
285
|
+
record(row) {
|
|
286
|
+
this.db
|
|
287
|
+
.prepare('INSERT OR REPLACE INTO session_index (id, agent_id, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?)')
|
|
288
|
+
.run(row.id, row.agentId, row.status, row.createdAt, row.updatedAt);
|
|
289
|
+
}
|
|
290
|
+
/** Drop a claim — a reconcile releasing an id whose conversation never landed. */
|
|
291
|
+
release(id) {
|
|
292
|
+
this.db.prepare('DELETE FROM session_index WHERE id = ?').run(id);
|
|
293
|
+
}
|
|
294
|
+
agentFor(id) {
|
|
295
|
+
const row = this.db.prepare('SELECT agent_id FROM session_index WHERE id = ?').get(id);
|
|
296
|
+
return row?.agent_id;
|
|
297
|
+
}
|
|
298
|
+
rows() {
|
|
299
|
+
const rows = this.db
|
|
300
|
+
.prepare('SELECT id, agent_id, status, created_at, updated_at FROM session_index')
|
|
301
|
+
.all();
|
|
302
|
+
return rows.map((row) => ({
|
|
303
|
+
id: row.id,
|
|
304
|
+
agentId: row.agent_id,
|
|
305
|
+
status: row.status,
|
|
306
|
+
createdAt: row.created_at,
|
|
307
|
+
updatedAt: row.updated_at,
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
idsByStatus(status) {
|
|
311
|
+
const rows = this.db
|
|
312
|
+
.prepare('SELECT id FROM session_index WHERE status = ? ORDER BY updated_at ASC')
|
|
313
|
+
.all(status);
|
|
314
|
+
return rows.map((row) => row.id);
|
|
315
|
+
}
|
|
316
|
+
countByStatus() {
|
|
317
|
+
const rows = this.db
|
|
318
|
+
.prepare('SELECT status, COUNT(*) AS total FROM session_index GROUP BY status')
|
|
319
|
+
.all();
|
|
320
|
+
const counts = {};
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
counts[row.status] = Number(row.total);
|
|
323
|
+
}
|
|
324
|
+
return counts;
|
|
325
|
+
}
|
|
326
|
+
lastActivityByAgent() {
|
|
327
|
+
const rows = this.db
|
|
328
|
+
.prepare(`
|
|
329
|
+
SELECT agent_id,
|
|
330
|
+
MAX(updated_at) AS last_active_at,
|
|
331
|
+
SUM(CASE WHEN status IN ('running', 'pending_approval') THEN 1 ELSE 0 END) AS active_sessions
|
|
332
|
+
FROM session_index
|
|
333
|
+
GROUP BY agent_id
|
|
334
|
+
`)
|
|
335
|
+
.all();
|
|
336
|
+
const activity = {};
|
|
337
|
+
for (const row of rows) {
|
|
338
|
+
activity[row.agent_id] = {
|
|
339
|
+
lastActiveAt: row.last_active_at,
|
|
340
|
+
activeSessions: Number(row.active_sessions),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return activity;
|
|
344
|
+
}
|
|
345
|
+
list(agentId, limit) {
|
|
346
|
+
// -1 is SQLite's "no limit", so one prepared statement serves both cases
|
|
347
|
+
// rather than four.
|
|
348
|
+
const bound = limit !== undefined && Number.isInteger(limit) && limit >= 0 ? limit : -1;
|
|
349
|
+
const rows = (agentId
|
|
350
|
+
? this.db.prepare('SELECT id, agent_id, status, created_at, updated_at FROM session_index WHERE agent_id = ? ORDER BY updated_at DESC LIMIT ?').all(agentId, bound)
|
|
351
|
+
: this.db.prepare('SELECT id, agent_id, status, created_at, updated_at FROM session_index ORDER BY updated_at DESC LIMIT ?').all(bound));
|
|
352
|
+
return rows.map((row) => ({
|
|
353
|
+
id: row.id,
|
|
354
|
+
agentId: row.agent_id,
|
|
355
|
+
status: row.status,
|
|
356
|
+
createdAt: row.created_at,
|
|
357
|
+
updatedAt: row.updated_at,
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
close() {
|
|
361
|
+
this.db.close();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The fleet's session store: one SQLite database per agent, behind the
|
|
366
|
+
* `SessionStore` every runner already takes, with the fleet-wide reads the
|
|
367
|
+
* control API and the restart sweeps need answered across all of them.
|
|
368
|
+
*
|
|
369
|
+
* A runner is handed *this* — and every write goes to the store opened on
|
|
370
|
+
* its own agent's path. What the facade adds is the set of questions that
|
|
371
|
+
* were never about one agent: which agent holds an id, how many sessions
|
|
372
|
+
* are in each state, when each agent last did anything, and which ids are
|
|
373
|
+
* parked or abandoned fleet-wide.
|
|
374
|
+
*/
|
|
375
|
+
export class ShardedSessionStore {
|
|
376
|
+
stateDir;
|
|
377
|
+
ownedDirectory;
|
|
378
|
+
index;
|
|
379
|
+
shards = new Map();
|
|
380
|
+
constructor(options) {
|
|
381
|
+
this.stateDir = options.stateDir;
|
|
382
|
+
this.ownedDirectory = options.ownedDirectory ?? false;
|
|
383
|
+
this.index = new FleetSessionIndex(fleetDbIn(options.stateDir), this.ownedDirectory ? { ownedDirectory: true } : {});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* One agent's store, opened on first use and kept open.
|
|
387
|
+
*
|
|
388
|
+
* Opened lazily because a roster's agents are not all busy: a daemon
|
|
389
|
+
* with forty souls should hold descriptors for the ones that have
|
|
390
|
+
* conversations, not for every id on disk.
|
|
391
|
+
*/
|
|
392
|
+
shardFor(agentId) {
|
|
393
|
+
const existing = this.shards.get(agentId);
|
|
394
|
+
if (existing !== undefined) {
|
|
395
|
+
return existing;
|
|
396
|
+
}
|
|
397
|
+
// Always owner-only: a per-agent directory is state this repository
|
|
398
|
+
// creates, never a path an embedder pointed at something shared.
|
|
399
|
+
const shard = new SqliteSessionStore(agentSessionDbIn(this.stateDir, agentId), { perAgent: true });
|
|
400
|
+
this.shards.set(agentId, shard);
|
|
401
|
+
return shard;
|
|
402
|
+
}
|
|
403
|
+
async create(input) {
|
|
404
|
+
const agentId = input.agent.id;
|
|
405
|
+
assertPathSafeAgentId(agentId);
|
|
406
|
+
const now = new Date().toISOString();
|
|
407
|
+
// The claim first, and it is what refuses a sibling's id: the store
|
|
408
|
+
// write below is only reachable for an id this agent may have.
|
|
409
|
+
this.index.claim({ id: input.id, agentId, status: input.status, createdAt: now, updatedAt: now });
|
|
410
|
+
const session = await this.shardFor(agentId).create(input);
|
|
411
|
+
this.index.record({
|
|
412
|
+
id: session.id,
|
|
413
|
+
agentId,
|
|
414
|
+
status: session.status,
|
|
415
|
+
createdAt: session.createdAt,
|
|
416
|
+
updatedAt: session.updatedAt,
|
|
417
|
+
});
|
|
418
|
+
return session;
|
|
419
|
+
}
|
|
420
|
+
async get(id) {
|
|
421
|
+
const agentId = this.index.agentFor(id);
|
|
422
|
+
// No claim, no conversation. The index is the authority for "which
|
|
423
|
+
// agent's store holds this id" precisely so a lookup never has to
|
|
424
|
+
// guess by opening stores until one answers.
|
|
425
|
+
return agentId === undefined ? undefined : this.shardFor(agentId).get(id);
|
|
426
|
+
}
|
|
427
|
+
async save(session) {
|
|
428
|
+
const agentId = session.agent.id;
|
|
429
|
+
assertPathSafeAgentId(agentId);
|
|
430
|
+
// One id belongs to one agent, and this is the path that could change
|
|
431
|
+
// that quietly. `save` takes whatever agent the session now names: a
|
|
432
|
+
// caller handing back a session whose `agent.id` has changed would
|
|
433
|
+
// write a second copy into the new agent's shard and then repoint the
|
|
434
|
+
// index at it — the original transcript still on disk but in a store
|
|
435
|
+
// nothing resolves to, and the next start refusing to serve at all,
|
|
436
|
+
// because the reconcile finds one id in two shards and says so.
|
|
437
|
+
//
|
|
438
|
+
// The same `claim` as `create`, not a read of the index. Asking who
|
|
439
|
+
// holds the id and *then* writing the shard leaves the whole file write
|
|
440
|
+
// between the question and the answer, so two callers saving an id
|
|
441
|
+
// nobody has indexed yet — different agents, the same id — both see it
|
|
442
|
+
// free and both write. Claiming takes the id in one statement before
|
|
443
|
+
// either shard write can start, so the loser is refused here, where the
|
|
444
|
+
// error can still name the agent it belongs to, instead of at the next
|
|
445
|
+
// start where it is a daemon that will not come up.
|
|
446
|
+
this.index.claim({
|
|
447
|
+
id: session.id,
|
|
448
|
+
agentId,
|
|
449
|
+
status: session.status,
|
|
450
|
+
createdAt: session.createdAt,
|
|
451
|
+
updatedAt: session.updatedAt,
|
|
452
|
+
});
|
|
453
|
+
// The conversation is the truth and the index is derived from it, so
|
|
454
|
+
// the shard write goes first: a crash in between leaves an index row
|
|
455
|
+
// one status behind, which the next start reconciles, rather than an
|
|
456
|
+
// index pointing at a turn the store never took.
|
|
457
|
+
const written = await this.shardFor(agentId).saveReturning(session);
|
|
458
|
+
this.index.record({
|
|
459
|
+
id: written.id,
|
|
460
|
+
agentId,
|
|
461
|
+
status: written.status,
|
|
462
|
+
createdAt: written.createdAt,
|
|
463
|
+
updatedAt: written.updatedAt,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
async listIdsByStatus(status) {
|
|
467
|
+
return this.index.idsByStatus(status);
|
|
468
|
+
}
|
|
469
|
+
countByStatus() {
|
|
470
|
+
return this.index.countByStatus();
|
|
471
|
+
}
|
|
472
|
+
lastActivityByAgent() {
|
|
473
|
+
return this.index.lastActivityByAgent();
|
|
474
|
+
}
|
|
475
|
+
list(agentId, limit) {
|
|
476
|
+
return this.index.list(agentId, limit);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Put the index and the shards back in step, before anything reads
|
|
480
|
+
* either. Two durable writes stand where one atomic primary key used to,
|
|
481
|
+
* and this is what makes a crash between them resolve one way rather
|
|
482
|
+
* than leaving them to disagree:
|
|
483
|
+
*
|
|
484
|
+
* - a claim whose conversation never landed is released, freeing the id;
|
|
485
|
+
* - a conversation the index has lost — or disagrees with — is
|
|
486
|
+
* re-indexed from the shard, which is the record;
|
|
487
|
+
* - the same id in two agents' shards is refused loudly, naming both,
|
|
488
|
+
* because picking one would silently hand somebody else's conversation
|
|
489
|
+
* to whoever asks for that id next.
|
|
490
|
+
*/
|
|
491
|
+
async reconcile() {
|
|
492
|
+
const report = { released: [], reindexed: [] };
|
|
493
|
+
const owners = new Map();
|
|
494
|
+
for (const agentId of await this.shardedAgentIds()) {
|
|
495
|
+
// Read through a connection this reconcile owns, and let it go again
|
|
496
|
+
// unless the store was already open: a roster's every agent has a
|
|
497
|
+
// store on disk, and holding one descriptor per agent from start-up
|
|
498
|
+
// would make a big roster pay at rest for a sweep that runs once.
|
|
499
|
+
const cached = this.shards.get(agentId);
|
|
500
|
+
const shard = cached ?? new SqliteSessionStore(agentSessionDbIn(this.stateDir, agentId), { perAgent: true });
|
|
501
|
+
try {
|
|
502
|
+
for (const row of shard.rows()) {
|
|
503
|
+
const seen = owners.get(row.id);
|
|
504
|
+
if (seen !== undefined) {
|
|
505
|
+
throw new Error(`Session ${row.id} exists in both ${seen.agentId}'s and ${agentId}'s store. `
|
|
506
|
+
+ 'Two agents cannot share a session id; move or remove one of the two `agents/<id>/sessions.db` rows before starting.');
|
|
507
|
+
}
|
|
508
|
+
owners.set(row.id, row);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
finally {
|
|
512
|
+
if (cached === undefined) {
|
|
513
|
+
shard.close();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const indexed = new Map(this.index.rows().map((row) => [row.id, row]));
|
|
518
|
+
for (const [id] of indexed) {
|
|
519
|
+
if (!owners.has(id)) {
|
|
520
|
+
this.index.release(id);
|
|
521
|
+
report.released.push(id);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
for (const [id, row] of owners) {
|
|
525
|
+
const claim = indexed.get(id);
|
|
526
|
+
if (claim === undefined
|
|
527
|
+
|| claim.agentId !== row.agentId
|
|
528
|
+
|| claim.status !== row.status
|
|
529
|
+
|| claim.updatedAt !== row.updatedAt) {
|
|
530
|
+
this.index.record(row);
|
|
531
|
+
report.reindexed.push(id);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return report;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Which agents have a store on disk — the roster is not the answer, on
|
|
538
|
+
* purpose: the gateway keeps a missing soul's sessions when it drops the
|
|
539
|
+
* soul, and a recovery sweep that skipped those would strand exactly the
|
|
540
|
+
* conversations nobody is watching.
|
|
541
|
+
*/
|
|
542
|
+
async shardedAgentIds() {
|
|
543
|
+
let entries;
|
|
544
|
+
try {
|
|
545
|
+
entries = await readdir(agentsDirIn(this.stateDir), { withFileTypes: true });
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
if (error.code === 'ENOENT') {
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
const named = entries
|
|
554
|
+
.filter((entry) => entry.isDirectory())
|
|
555
|
+
.map((entry) => entry.name)
|
|
556
|
+
.filter((name) => {
|
|
557
|
+
try {
|
|
558
|
+
// A directory whose name cannot key a path is not an agent's —
|
|
559
|
+
// `agentStateDirIn` is what says so, and the reason it throws
|
|
560
|
+
// rather than sanitizing.
|
|
561
|
+
agentStateDirIn(this.stateDir, name);
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
// And only the ones that already hold a shard. This sweep reads what is
|
|
569
|
+
// there; opening a store *creates* the database and tightens the
|
|
570
|
+
// directory around it, so treating every well-named subdirectory as an
|
|
571
|
+
// agent means a start-up that writes `sessions.db` into whatever an
|
|
572
|
+
// operator keeps under `agents/` — a `backups/` folder, an export — and
|
|
573
|
+
// chmods it to 0700 on the way past. The old layout reserved no such
|
|
574
|
+
// names, so nothing warned them. An agent with no conversations yet has
|
|
575
|
+
// nothing to reconcile either, which is the same answer.
|
|
576
|
+
const held = [];
|
|
577
|
+
for (const name of named) {
|
|
578
|
+
if (await shardFileExists(agentSessionDbIn(this.stateDir, name))) {
|
|
579
|
+
held.push(name);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return held;
|
|
583
|
+
}
|
|
584
|
+
close() {
|
|
585
|
+
for (const shard of this.shards.values()) {
|
|
586
|
+
shard.close();
|
|
587
|
+
}
|
|
588
|
+
this.shards.clear();
|
|
589
|
+
this.index.close();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stratusagent/gateway",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
"node": ">=22.13 <23 || >=23.4"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stratusagent/
|
|
22
|
-
"@stratusagent/
|
|
23
|
-
"@stratusagent/
|
|
24
|
-
"@stratusagent/plugins": "0.11.
|
|
25
|
-
"@stratusagent/state": "0.11.
|
|
21
|
+
"@stratusagent/core": "0.11.5",
|
|
22
|
+
"@stratusagent/executor-local": "0.11.5",
|
|
23
|
+
"@stratusagent/agents": "0.11.5",
|
|
24
|
+
"@stratusagent/plugins": "0.11.5",
|
|
25
|
+
"@stratusagent/state": "0.11.5"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^24.6.0",
|
|
29
29
|
"typescript": "^5.9.3",
|
|
30
|
-
"@stratusagent/permissions": "0.11.
|
|
30
|
+
"@stratusagent/permissions": "0.11.5"
|
|
31
31
|
},
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"repository": {
|