@stratusagent/gateway 0.13.6 → 0.14.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/dist/event-intake.d.ts +141 -0
- package/dist/event-intake.d.ts.map +1 -0
- package/dist/event-intake.js +369 -0
- package/dist/event-intake.js.map +1 -0
- package/dist/events.d.ts +256 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +467 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +63 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +188 -17
- package/dist/index.js.map +1 -1
- package/dist/schedules.d.ts +41 -0
- package/dist/schedules.d.ts.map +1 -1
- package/dist/schedules.js +306 -39
- package/dist/schedules.js.map +1 -1
- package/package.json +7 -7
package/dist/events.js
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { MAX_IDEMPOTENCY_KEY_LENGTH } from '@stratusagent/core';
|
|
3
|
+
import { openFleetDatabase, SCHEDULE_SESSION_ID_PREFIX } from "./schedules.js";
|
|
4
|
+
// Event triggers' durable half (card 590, agent-plans
|
|
5
|
+
// roadmap/590-event-triggers.md): deliveries, dedup tombstones and firings,
|
|
6
|
+
// in fleet.db next to the schedules a trigger is. The listener decides what a
|
|
7
|
+
// request is and which triggers it matches; the dispatch side decides when a
|
|
8
|
+
// firing goes out. This file owns only what must survive a crash between
|
|
9
|
+
// those steps, and the keys that make a firing run at most once.
|
|
10
|
+
/** Accepted ids are remembered this long: longer than GitHub's 3-day manual redelivery. */
|
|
11
|
+
export const EVENT_DEDUP_WINDOW_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
12
|
+
/** Delivery rows listed at most: the newest this many, within the dedup window. */
|
|
13
|
+
export const EVENT_DELIVERY_ROW_CAP = 10_000;
|
|
14
|
+
/** A firing's rendered event block, and so the event text a turn sees, is at most this many bytes. */
|
|
15
|
+
export const EVENT_BLOCK_MAX_BYTES = 16 * 1_024;
|
|
16
|
+
const OPEN_STATUSES = ['pending', 'held', 'rate_limited', 'firing'];
|
|
17
|
+
const OPEN_SQL = OPEN_STATUSES.map((status) => `'${status}'`).join(', ');
|
|
18
|
+
const sha256 = (text) => createHash('sha256').update(text).digest('hex');
|
|
19
|
+
/**
|
|
20
|
+
* What sessions, holds and rate limits group by: source + subject when the
|
|
21
|
+
* event has one, so every Codex round on one PR is one conversation, and
|
|
22
|
+
* source + id when it doesn't, so subjectless events never share a bucket.
|
|
23
|
+
* JSON, so no subject can be spelled to collide with an id key.
|
|
24
|
+
*/
|
|
25
|
+
export const eventCoalescingKey = (sourceId, event) => {
|
|
26
|
+
const subject = event.attributes.subject;
|
|
27
|
+
return subject !== undefined && subject.length > 0
|
|
28
|
+
? JSON.stringify([sourceId, 'subject', subject])
|
|
29
|
+
: JSON.stringify([sourceId, 'id', event.attributes.id ?? '']);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A trigger's session for one coalescing key. In the reserved `schedule:`
|
|
33
|
+
* namespace, which the gateway refuses to dispatch into from outside, so the
|
|
34
|
+
* only turns on it are the trigger's own firings.
|
|
35
|
+
*/
|
|
36
|
+
export const eventSessionId = (triggerId, coalescingKey) => `${SCHEDULE_SESSION_ID_PREFIX}${triggerId}:event-${sha256(coalescingKey).slice(0, 32)}`;
|
|
37
|
+
/**
|
|
38
|
+
* The gateway idempotency key for one trigger's firing on one event: a
|
|
39
|
+
* repeat never starts a second turn, and a turn whose process died continues
|
|
40
|
+
* once. Hashed when over the gateway's bound, as channel-slack does, because
|
|
41
|
+
* a sender picks the event id's length.
|
|
42
|
+
*/
|
|
43
|
+
export const eventDispatchKey = (triggerId, sourceId, eventId) => {
|
|
44
|
+
const key = `event:${triggerId}:${sourceId}:${eventId}`;
|
|
45
|
+
return key.length <= MAX_IDEMPOTENCY_KEY_LENGTH ? key : `event-sha256:${sha256(key)}`;
|
|
46
|
+
};
|
|
47
|
+
const ATTRIBUTE_ORDER = ['id', 'source', 'type', 'subject', 'time'];
|
|
48
|
+
/** The longest prefix of `text` that fits in `bytes` of UTF-8, never splitting a character. */
|
|
49
|
+
const utf8Prefix = (text, bytes) => {
|
|
50
|
+
const { read } = new TextEncoder().encodeInto(text, new Uint8Array(Math.max(0, bytes)));
|
|
51
|
+
return text.slice(0, read);
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* What a firing's turn sees after the trigger's prompt. Rendered when the
|
|
55
|
+
* delivery is matched and stored with the firing, so a firing that goes out
|
|
56
|
+
* hours later needs neither the request body nor a second match. Attributes
|
|
57
|
+
* and data are JSON, so a newline in a sender's subject or comment can't pass
|
|
58
|
+
* for a line of the block. Capped at `EVENT_BLOCK_MAX_BYTES` with a note: the
|
|
59
|
+
* agent fetches the rest with its own tools (`gh` for a PR).
|
|
60
|
+
*/
|
|
61
|
+
export const renderEventBlock = (sourceId, event) => {
|
|
62
|
+
const attributes = {};
|
|
63
|
+
for (const name of ATTRIBUTE_ORDER) {
|
|
64
|
+
const value = event.attributes[name];
|
|
65
|
+
if (value !== undefined) {
|
|
66
|
+
attributes[name] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const name of Object.keys(event.attributes).sort()) {
|
|
70
|
+
if (name !== 'specversion' && !ATTRIBUTE_ORDER.includes(name)) {
|
|
71
|
+
attributes[name] = event.attributes[name];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const full = [
|
|
75
|
+
`Event from source "${sourceId}":`,
|
|
76
|
+
JSON.stringify(attributes),
|
|
77
|
+
'data:',
|
|
78
|
+
...(event.data !== undefined ? [JSON.stringify(event.data)] : []),
|
|
79
|
+
].join('\n');
|
|
80
|
+
const size = Buffer.byteLength(full);
|
|
81
|
+
if (size <= EVENT_BLOCK_MAX_BYTES) {
|
|
82
|
+
return full;
|
|
83
|
+
}
|
|
84
|
+
const note = `\n[cut at ${Math.round(EVENT_BLOCK_MAX_BYTES / 1_024)} KB of ${Math.round(size / 1_024)} KB; fetch the rest with your own tools]`;
|
|
85
|
+
return utf8Prefix(full, EVENT_BLOCK_MAX_BYTES - Buffer.byteLength(note)) + note;
|
|
86
|
+
};
|
|
87
|
+
const deliveryFrom = (row) => ({
|
|
88
|
+
seq: row.seq,
|
|
89
|
+
source: row.source,
|
|
90
|
+
eventId: row.event_id,
|
|
91
|
+
type: row.type,
|
|
92
|
+
...(row.subject !== '' ? { subject: row.subject } : {}),
|
|
93
|
+
status: row.status,
|
|
94
|
+
...(row.reason !== '' ? { reason: row.reason } : {}),
|
|
95
|
+
receivedAt: row.received_at,
|
|
96
|
+
});
|
|
97
|
+
const firingFrom = (row) => ({
|
|
98
|
+
id: row.id,
|
|
99
|
+
deliverySeq: row.delivery_seq,
|
|
100
|
+
triggerId: row.trigger_id,
|
|
101
|
+
agentId: row.agent_id,
|
|
102
|
+
source: row.source,
|
|
103
|
+
eventId: row.event_id,
|
|
104
|
+
coalescingKey: row.coalescing_key,
|
|
105
|
+
sessionId: row.session_id,
|
|
106
|
+
dispatchKey: row.dispatch_key,
|
|
107
|
+
...(row.batch_key !== '' ? { batchKey: row.batch_key } : {}),
|
|
108
|
+
status: row.status,
|
|
109
|
+
...(row.started_at !== '' ? { startedAt: row.started_at } : {}),
|
|
110
|
+
...(row.block !== null ? { block: row.block } : {}),
|
|
111
|
+
...(row.reason !== '' ? { reason: row.reason } : {}),
|
|
112
|
+
createdAt: row.created_at,
|
|
113
|
+
updatedAt: row.updated_at,
|
|
114
|
+
});
|
|
115
|
+
const minus = (iso, ms) => new Date(Date.parse(iso) - ms).toISOString();
|
|
116
|
+
/**
|
|
117
|
+
* Deliveries, dedup tombstones and firings, durable in `fleet.db`. Its own
|
|
118
|
+
* connection, like the schedule store's. `stratus events` reads through the
|
|
119
|
+
* running daemon instead of this file, because the requests refused before
|
|
120
|
+
* a row are counted only in the listener.
|
|
121
|
+
*
|
|
122
|
+
* Every method that changes more than one row does it in one transaction,
|
|
123
|
+
* and every one that moves a firing between statuses is all-or-nothing and
|
|
124
|
+
* conditional on the statuses it expects: a delivery and its firings are
|
|
125
|
+
* committed together before the sender hears 2xx, a batch is claimed whole
|
|
126
|
+
* before its dispatch, and a refused transition changes nothing.
|
|
127
|
+
*/
|
|
128
|
+
export class SqliteEventStore {
|
|
129
|
+
db;
|
|
130
|
+
constructor(filePath, options = {}) {
|
|
131
|
+
this.db = openFleetDatabase(filePath, options, `
|
|
132
|
+
CREATE TABLE IF NOT EXISTS event_deliveries (
|
|
133
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
134
|
+
source TEXT NOT NULL,
|
|
135
|
+
event_id TEXT NOT NULL,
|
|
136
|
+
type TEXT NOT NULL,
|
|
137
|
+
subject TEXT NOT NULL,
|
|
138
|
+
status TEXT NOT NULL,
|
|
139
|
+
reason TEXT NOT NULL,
|
|
140
|
+
received_at TEXT NOT NULL
|
|
141
|
+
);
|
|
142
|
+
CREATE INDEX IF NOT EXISTS event_deliveries_received ON event_deliveries (received_at);
|
|
143
|
+
CREATE TABLE IF NOT EXISTS event_seen (
|
|
144
|
+
source TEXT NOT NULL,
|
|
145
|
+
id_hash TEXT NOT NULL,
|
|
146
|
+
seen_at TEXT NOT NULL,
|
|
147
|
+
PRIMARY KEY (source, id_hash)
|
|
148
|
+
) WITHOUT ROWID;
|
|
149
|
+
CREATE TABLE IF NOT EXISTS event_firings (
|
|
150
|
+
id TEXT PRIMARY KEY,
|
|
151
|
+
delivery_seq INTEGER NOT NULL,
|
|
152
|
+
trigger_id TEXT NOT NULL,
|
|
153
|
+
agent_id TEXT NOT NULL,
|
|
154
|
+
source TEXT NOT NULL,
|
|
155
|
+
event_id TEXT NOT NULL,
|
|
156
|
+
coalescing_key TEXT NOT NULL,
|
|
157
|
+
session_id TEXT NOT NULL,
|
|
158
|
+
dispatch_key TEXT NOT NULL,
|
|
159
|
+
batch_key TEXT NOT NULL,
|
|
160
|
+
status TEXT NOT NULL,
|
|
161
|
+
started_at TEXT NOT NULL DEFAULT '',
|
|
162
|
+
block TEXT,
|
|
163
|
+
reason TEXT NOT NULL,
|
|
164
|
+
created_at TEXT NOT NULL,
|
|
165
|
+
updated_at TEXT NOT NULL
|
|
166
|
+
);
|
|
167
|
+
CREATE INDEX IF NOT EXISTS event_firings_status ON event_firings (status, created_at);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS event_firings_trigger ON event_firings (trigger_id, status, updated_at);
|
|
169
|
+
CREATE INDEX IF NOT EXISTS event_firings_batch ON event_firings (batch_key);
|
|
170
|
+
`);
|
|
171
|
+
// A table made before `started_at` existed (an unreleased build of
|
|
172
|
+
// this feature) gains it; rows claimed then count from their update.
|
|
173
|
+
const columns = this.db.prepare('PRAGMA table_info(event_firings)').all();
|
|
174
|
+
if (!columns.some((column) => column.name === 'started_at')) {
|
|
175
|
+
this.db.exec("ALTER TABLE event_firings ADD COLUMN started_at TEXT NOT NULL DEFAULT ''");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
transaction(work) {
|
|
179
|
+
// IMMEDIATE takes the write lock up front, so the dedup check and the
|
|
180
|
+
// insert it guards can't interleave with another writer's.
|
|
181
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
182
|
+
try {
|
|
183
|
+
const result = work();
|
|
184
|
+
this.db.exec('COMMIT');
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
this.db.exec('ROLLBACK');
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
insertDelivery(delivery, status, reason = '') {
|
|
193
|
+
const result = this.db
|
|
194
|
+
.prepare('INSERT INTO event_deliveries (source, event_id, type, subject, status, reason, received_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
|
|
195
|
+
.run(delivery.source, delivery.eventId, delivery.type ?? '', delivery.subject ?? '', status, reason, delivery.receivedAt);
|
|
196
|
+
return Number(result.lastInsertRowid);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Record a delivery whose signature checked out and whose body parsed:
|
|
200
|
+
* `duplicate` when its (source, id) was accepted within the dedup window,
|
|
201
|
+
* else `matched` with one `pending` firing per match, or `unmatched`. One
|
|
202
|
+
* write, made before the sender hears 2xx. Accepted deliveries leave a
|
|
203
|
+
* tombstone that outlives the row cap, so dedup never depends on it.
|
|
204
|
+
*/
|
|
205
|
+
accept(delivery) {
|
|
206
|
+
return this.transaction(() => {
|
|
207
|
+
// Insert, or revive a tombstone older than the window; zero changes
|
|
208
|
+
// means a live tombstone, so the id was seen.
|
|
209
|
+
const fresh = this.db
|
|
210
|
+
.prepare(`
|
|
211
|
+
INSERT INTO event_seen (source, id_hash, seen_at) VALUES (?, ?, ?)
|
|
212
|
+
ON CONFLICT (source, id_hash) DO UPDATE SET seen_at = excluded.seen_at WHERE seen_at < ?
|
|
213
|
+
`)
|
|
214
|
+
.run(delivery.source, sha256(delivery.eventId), delivery.receivedAt, minus(delivery.receivedAt, EVENT_DEDUP_WINDOW_MS))
|
|
215
|
+
.changes > 0;
|
|
216
|
+
if (!fresh) {
|
|
217
|
+
return { status: 'duplicate', seq: this.insertDelivery(delivery, 'duplicate'), firings: [] };
|
|
218
|
+
}
|
|
219
|
+
const status = delivery.firings.length > 0 ? 'matched' : 'unmatched';
|
|
220
|
+
const seq = this.insertDelivery(delivery, status);
|
|
221
|
+
const insert = this.db.prepare(`
|
|
222
|
+
INSERT INTO event_firings (id, delivery_seq, trigger_id, agent_id, source, event_id, coalescing_key,
|
|
223
|
+
session_id, dispatch_key, batch_key, status, block, reason, created_at, updated_at)
|
|
224
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, '', 'pending', ?, '', ?, ?)
|
|
225
|
+
`);
|
|
226
|
+
const firings = delivery.firings.map((firing) => {
|
|
227
|
+
const id = randomUUID();
|
|
228
|
+
insert.run(id, seq, firing.triggerId, firing.agentId, delivery.source, delivery.eventId, firing.coalescingKey, firing.sessionId, firing.dispatchKey, firing.block, delivery.receivedAt, delivery.receivedAt);
|
|
229
|
+
return {
|
|
230
|
+
id,
|
|
231
|
+
deliverySeq: seq,
|
|
232
|
+
triggerId: firing.triggerId,
|
|
233
|
+
agentId: firing.agentId,
|
|
234
|
+
source: delivery.source,
|
|
235
|
+
eventId: delivery.eventId,
|
|
236
|
+
coalescingKey: firing.coalescingKey,
|
|
237
|
+
sessionId: firing.sessionId,
|
|
238
|
+
dispatchKey: firing.dispatchKey,
|
|
239
|
+
status: 'pending',
|
|
240
|
+
block: firing.block,
|
|
241
|
+
createdAt: delivery.receivedAt,
|
|
242
|
+
updatedAt: delivery.receivedAt,
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
return { status, seq, firings };
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Record a signed delivery the listener refused (a form-encoded body, a
|
|
250
|
+
* malformed event). No tombstone: the sender can fix it and retry with the
|
|
251
|
+
* same id. Unsigned requests never reach here; they leave no row at all.
|
|
252
|
+
*/
|
|
253
|
+
refuse(delivery) {
|
|
254
|
+
const seq = this.insertDelivery(delivery, 'refused', delivery.reason);
|
|
255
|
+
return deliveryFrom(this.db.prepare('SELECT * FROM event_deliveries WHERE seq = ?').get(seq));
|
|
256
|
+
}
|
|
257
|
+
/** Delivery rows, newest first. */
|
|
258
|
+
deliveries(options = {}) {
|
|
259
|
+
const limit = options.limit ?? 100;
|
|
260
|
+
const rows = (options.source !== undefined
|
|
261
|
+
? this.db.prepare('SELECT * FROM event_deliveries WHERE source = ? ORDER BY seq DESC LIMIT ?').all(options.source, limit)
|
|
262
|
+
: this.db.prepare('SELECT * FROM event_deliveries ORDER BY seq DESC LIMIT ?').all(limit));
|
|
263
|
+
return rows.map(deliveryFrom);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Per source with any row: its last delivery, its last refused one, and
|
|
267
|
+
* counts since `sinceIso` (`stratus events`, `/health`). Grouped in the
|
|
268
|
+
* database, so the cost doesn't grow with the 7 days of rows kept.
|
|
269
|
+
*/
|
|
270
|
+
activity(sinceIso) {
|
|
271
|
+
const result = {};
|
|
272
|
+
const entry = (source) => (result[source] ??= { deliveries: {}, firings: {} });
|
|
273
|
+
const latest = this.db.prepare(`
|
|
274
|
+
SELECT d.* FROM event_deliveries d
|
|
275
|
+
JOIN (SELECT source, MAX(seq) AS seq FROM event_deliveries GROUP BY source) newest ON newest.seq = d.seq
|
|
276
|
+
`).all();
|
|
277
|
+
for (const row of latest) {
|
|
278
|
+
entry(row.source).lastDelivery = {
|
|
279
|
+
at: row.received_at,
|
|
280
|
+
type: row.type,
|
|
281
|
+
...(row.subject !== '' ? { subject: row.subject } : {}),
|
|
282
|
+
status: row.status,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const refused = this.db.prepare(`
|
|
286
|
+
SELECT d.* FROM event_deliveries d
|
|
287
|
+
JOIN (SELECT source, MAX(seq) AS seq FROM event_deliveries WHERE status = 'refused' GROUP BY source) newest ON newest.seq = d.seq
|
|
288
|
+
`).all();
|
|
289
|
+
for (const row of refused) {
|
|
290
|
+
entry(row.source).lastRefused = { at: row.received_at, reason: row.reason };
|
|
291
|
+
}
|
|
292
|
+
const deliveries = this.db.prepare(`
|
|
293
|
+
SELECT source, status, COUNT(*) AS n FROM event_deliveries WHERE received_at >= ? GROUP BY source, status
|
|
294
|
+
`).all(sinceIso);
|
|
295
|
+
for (const row of deliveries) {
|
|
296
|
+
entry(row.source).deliveries[row.status] = Number(row.n);
|
|
297
|
+
}
|
|
298
|
+
const firings = this.db.prepare(`
|
|
299
|
+
SELECT source, status, COUNT(*) AS n FROM event_firings
|
|
300
|
+
WHERE status IN (${OPEN_SQL}) OR updated_at >= ? GROUP BY source, status
|
|
301
|
+
`).all(sinceIso);
|
|
302
|
+
for (const row of firings) {
|
|
303
|
+
entry(row.source).firings[row.status] = Number(row.n);
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
firing(id) {
|
|
308
|
+
const row = this.db.prepare('SELECT * FROM event_firings WHERE id = ?').get(id);
|
|
309
|
+
return row ? firingFrom(row) : undefined;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Every open firing (`pending`, `held`, `rate_limited`, `firing`), oldest
|
|
313
|
+
* first, with its block and batch key: what the startup sweep recovers and
|
|
314
|
+
* what a hold releases. A `firing` row is a claim whose dispatch may or may
|
|
315
|
+
* not have reached the gateway; its batch goes out again as stored, and the
|
|
316
|
+
* idempotency key decides whether that starts or continues a turn.
|
|
317
|
+
*/
|
|
318
|
+
open() {
|
|
319
|
+
return this.db
|
|
320
|
+
.prepare(`SELECT * FROM event_firings WHERE status IN (${OPEN_SQL}) ORDER BY created_at ASC, delivery_seq ASC, rowid ASC`)
|
|
321
|
+
.all().map(firingFrom);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Run `update` on exactly `ids`, all or nothing: false, with nothing
|
|
325
|
+
* changed, unless `precheck` (inside the same transaction) passes and
|
|
326
|
+
* every row matched the update's own conditions.
|
|
327
|
+
*/
|
|
328
|
+
allOrNothing(ids, update, precheck = () => true) {
|
|
329
|
+
if (ids.length === 0 || new Set(ids).size !== ids.length) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
return this.transaction(() => {
|
|
334
|
+
if (!precheck()) {
|
|
335
|
+
throw new RefusedTransition();
|
|
336
|
+
}
|
|
337
|
+
for (const id of ids) {
|
|
338
|
+
if (update(id) === 0) {
|
|
339
|
+
throw new RefusedTransition();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
if (error instanceof RefusedTransition) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Claim firings to go out as one turn under `batchKey`, in one write
|
|
354
|
+
* before the dispatch. Either every row is free and they become a new
|
|
355
|
+
* batch, or `ids` is exactly an existing batch under exactly its key,
|
|
356
|
+
* which is how recovery resends one. A batch is fixed once claimed: no
|
|
357
|
+
* row joins it later, and it never changes key. False, with nothing
|
|
358
|
+
* changed, for anything else, or if any row is finished.
|
|
359
|
+
*
|
|
360
|
+
* With `triggerId`, also false unless that schedule row still exists, in
|
|
361
|
+
* the same transaction: `stratus schedules cancel` deletes from another
|
|
362
|
+
* process, and a trigger cancelled after the scan read it must not fire,
|
|
363
|
+
* as a timed schedule's conditional slot claim already guarantees.
|
|
364
|
+
*/
|
|
365
|
+
claim(ids, batchKey, nowIso, options = {}) {
|
|
366
|
+
const members = this.db.prepare('SELECT id FROM event_firings WHERE batch_key = ?');
|
|
367
|
+
const update = this.db.prepare(`
|
|
368
|
+
UPDATE event_firings
|
|
369
|
+
SET status = 'firing', batch_key = ?, updated_at = ?,
|
|
370
|
+
started_at = CASE WHEN started_at = '' THEN ? ELSE started_at END
|
|
371
|
+
WHERE id = ? AND status IN (${OPEN_SQL}) AND (batch_key = '' OR batch_key = ?)
|
|
372
|
+
`);
|
|
373
|
+
const wanted = new Set(ids);
|
|
374
|
+
return this.allOrNothing(ids, (id) => Number(update.run(batchKey, nowIso, nowIso, id, batchKey).changes), () => {
|
|
375
|
+
if (options.triggerId !== undefined
|
|
376
|
+
&& this.db.prepare('SELECT 1 FROM schedules WHERE id = ?').get(options.triggerId) === undefined) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
const existing = members.all(batchKey).map((row) => row.id);
|
|
380
|
+
return existing.length === 0 || (existing.length === wanted.size && existing.every((id) => wanted.has(id)));
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Hold open firings for quiet hours or paused intake (`held`) or over the
|
|
385
|
+
* trigger's hourly limit (`rate_limited`). The block and any batch key
|
|
386
|
+
* stay, so a held batch still goes out only as itself.
|
|
387
|
+
*/
|
|
388
|
+
hold(ids, status, nowIso) {
|
|
389
|
+
const update = this.db.prepare(`UPDATE event_firings SET status = ?, updated_at = ? WHERE id = ? AND status IN (${OPEN_SQL})`);
|
|
390
|
+
return this.allOrNothing(ids, (id) => Number(update.run(status, nowIso, id).changes));
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Finish claimed firings after their dispatch, clearing the block in the
|
|
394
|
+
* same write: from here the payload lives only in the session it fired.
|
|
395
|
+
* Only a `firing` row can finish, so nothing is marked fired that wasn't
|
|
396
|
+
* claimed first.
|
|
397
|
+
*/
|
|
398
|
+
finish(ids, outcome, nowIso) {
|
|
399
|
+
const reason = outcome.status === 'failed' ? outcome.reason : '';
|
|
400
|
+
const update = this.db.prepare(`
|
|
401
|
+
UPDATE event_firings SET status = ?, reason = ?, block = NULL, updated_at = ?
|
|
402
|
+
WHERE id = ? AND status = 'firing'
|
|
403
|
+
`);
|
|
404
|
+
return this.allOrNothing(ids, (id) => Number(update.run(outcome.status, reason, nowIso, id).changes));
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Retire open firings that will never go out: `superseded` by newer ones
|
|
408
|
+
* for the same key, or `expired` (held over 24 hours, or their trigger was
|
|
409
|
+
* cancelled). Clears their blocks. Returns how many were retired; a
|
|
410
|
+
* finished firing is left as it is.
|
|
411
|
+
*/
|
|
412
|
+
retire(ids, status, reason, nowIso) {
|
|
413
|
+
const update = this.db.prepare(`
|
|
414
|
+
UPDATE event_firings SET status = ?, reason = ?, block = NULL, updated_at = ?
|
|
415
|
+
WHERE id = ? AND status IN (${OPEN_SQL})
|
|
416
|
+
`);
|
|
417
|
+
return this.transaction(() => ids.reduce((count, id) => count + Number(update.run(status, reason, nowIso, id).changes), 0));
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Turns a trigger has started since `sinceIso`, for its hourly limit,
|
|
421
|
+
* counted from when each batch was first claimed (a two-hour turn doesn't
|
|
422
|
+
* hold the hour for two more): a batch running now, one that finished,
|
|
423
|
+
* and one that failed all count,
|
|
424
|
+
* since each was a turn started unattended. A failing provider would
|
|
425
|
+
* otherwise let every new event start another. A batch is one turn, so
|
|
426
|
+
* this counts batch keys, not rows; `exceptBatchKey` leaves out a batch
|
|
427
|
+
* being re-sent after a restart, which must not count against itself.
|
|
428
|
+
*/
|
|
429
|
+
turnsStartedSince(triggerId, sinceIso, exceptBatchKey = '') {
|
|
430
|
+
const row = this.db
|
|
431
|
+
.prepare(`
|
|
432
|
+
SELECT COUNT(DISTINCT batch_key) AS turns FROM event_firings
|
|
433
|
+
WHERE trigger_id = ? AND status IN ('firing', 'fired', 'failed')
|
|
434
|
+
AND (CASE WHEN started_at = '' THEN updated_at ELSE started_at END) >= ? AND batch_key != ?
|
|
435
|
+
`)
|
|
436
|
+
.get(triggerId, sinceIso, exceptBatchKey);
|
|
437
|
+
return row.turns;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Drop what the dedup window and the row cap no longer cover: tombstones
|
|
441
|
+
* and delivery rows past 7 days, delivery rows past the newest 10,000, and
|
|
442
|
+
* finished firings past 7 days. An open firing is never pruned: it holds
|
|
443
|
+
* the only copy of an event that hasn't gone out.
|
|
444
|
+
*/
|
|
445
|
+
prune(nowIso) {
|
|
446
|
+
const cutoff = minus(nowIso, EVENT_DEDUP_WINDOW_MS);
|
|
447
|
+
this.transaction(() => {
|
|
448
|
+
this.db.prepare('DELETE FROM event_seen WHERE seen_at < ?').run(cutoff);
|
|
449
|
+
this.db.prepare('DELETE FROM event_deliveries WHERE received_at < ?').run(cutoff);
|
|
450
|
+
this.db
|
|
451
|
+
.prepare('DELETE FROM event_deliveries WHERE seq <= (SELECT seq FROM event_deliveries ORDER BY seq DESC LIMIT 1 OFFSET ?)')
|
|
452
|
+
.run(EVENT_DELIVERY_ROW_CAP);
|
|
453
|
+
this.db.prepare(`DELETE FROM event_firings WHERE status NOT IN (${OPEN_SQL}) AND updated_at < ?`).run(cutoff);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
close() {
|
|
457
|
+
this.db.close();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/** Unwinds an all-or-nothing transition's transaction; never escapes the store. */
|
|
461
|
+
class RefusedTransition extends Error {
|
|
462
|
+
constructor() {
|
|
463
|
+
super('refused transition');
|
|
464
|
+
this.name = 'RefusedTransition';
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAGhE,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAmC,MAAM,gBAAgB,CAAC;AAEhH,sDAAsD;AACtD,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yEAAyE;AACzE,iEAAiE;AAEjE,2FAA2F;AAC3F,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;AAE9D,mFAAmF;AACnF,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C,sGAAsG;AACtG,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,KAAK,CAAC;AA0GhD,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,QAAQ,CAAU,CAAC;AAC7E,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEzE,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEzF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAE,KAAuC,EAAU,EAAE;IACtG,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;IACzC,OAAO,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAChD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAChD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,aAAqB,EAAU,EAAE,CACjF,GAAG,0BAA0B,GAAG,SAAS,UAAU,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AAE1F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAiB,EAAE,QAAgB,EAAE,OAAe,EAAU,EAAE;IAC/F,MAAM,GAAG,GAAG,SAAS,SAAS,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC;IACxD,OAAO,GAAG,CAAC,MAAM,IAAI,0BAA0B,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;AACxF,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;AAEpE,+FAA+F;AAC/F,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE;IACzD,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACxF,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAgB,EAAE,KAAmB,EAAU,EAAE;IAChF,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACxD,IAAI,IAAI,KAAK,aAAa,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9D,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAE,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG;QACX,sBAAsB,QAAQ,IAAI;QAClC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;QAC1B,OAAO;QACP,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,IAAI,IAAI,qBAAqB,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,aAAa,IAAI,CAAC,KAAK,CAAC,qBAAqB,GAAG,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,0CAA0C,CAAC;IAChJ,OAAO,UAAU,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;AAClF,CAAC,CAAC;AAgCF,MAAM,YAAY,GAAG,CAAC,GAAmB,EAAoB,EAAE,CAAC,CAAC;IAC/D,GAAG,EAAE,GAAG,CAAC,GAAG;IACZ,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,OAAO,EAAE,GAAG,CAAC,QAAQ;IACrB,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,GAAG,CAAC,GAAG,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,UAAU,EAAE,GAAG,CAAC,WAAW;CAC5B,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CAAC,GAAiB,EAAkB,EAAE,CAAC,CAAC;IACzD,EAAE,EAAE,GAAG,CAAC,EAAE;IACV,WAAW,EAAE,GAAG,CAAC,YAAY;IAC7B,SAAS,EAAE,GAAG,CAAC,UAAU;IACzB,OAAO,EAAE,GAAG,CAAC,QAAQ;IACrB,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,OAAO,EAAE,GAAG,CAAC,QAAQ;IACrB,aAAa,EAAE,GAAG,CAAC,cAAc;IACjC,SAAS,EAAE,GAAG,CAAC,UAAU;IACzB,WAAW,EAAE,GAAG,CAAC,YAAY;IAC7B,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,SAAS,EAAE,GAAG,CAAC,UAAU;IACzB,SAAS,EAAE,GAAG,CAAC,UAAU;CAC1B,CAAC,CAAC;AAEH,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,EAAU,EAAU,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAEhG;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAgB;IACV,EAAE,CAAe;IAElC,YAAY,QAAgB,EAAE,UAAsC,EAAE;QACpE,IAAI,CAAC,EAAE,GAAG,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuC9C,CAAC,CAAC;QACH,mEAAmE;QACnE,qEAAqE;QACrE,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC,GAAG,EAA6B,CAAC;QACrG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAEO,WAAW,CAAI,IAAa;QAClC,sEAAsE;QACtE,2DAA2D;QAC3D,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,cAAc,CACpB,QAAkG,EAClG,MAA2B,EAC3B,MAAM,GAAG,EAAE;QAEX,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE;aACnB,OAAO,CAAC,0HAA0H,CAAC;aACnI,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE,EAAE,QAAQ,CAAC,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC5H,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,QAA+B;QACpC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE;YAC3B,oEAAoE;YACpE,8CAA8C;YAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE;iBAClB,OAAO,CAAC;;;SAGR,CAAC;iBACD,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;iBACtH,OAAO,GAAG,CAAC,CAAC;YACf,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,EAAE,MAAM,EAAE,WAAoB,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YACxG,CAAC;YACD,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAkB,CAAC,CAAC,CAAC,WAAoB,CAAC;YACvF,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;OAI9B,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAkB,EAAE;gBAC9D,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;gBACxB,MAAM,CAAC,GAAG,CACR,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,EAClG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAC7F,CAAC;gBACF,OAAO;oBACL,EAAE;oBACF,WAAW,EAAE,GAAG;oBAChB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;oBACzB,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,MAAM,EAAE,SAAS;oBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,SAAS,EAAE,QAAQ,CAAC,UAAU;oBAC9B,SAAS,EAAE,QAAQ,CAAC,UAAU;iBAC/B,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;QAClC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,QAA8B;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACtE,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC,GAAG,CAAC,GAAG,CAA8B,CAAC,CAAC;IAC7H,CAAC;IAED,mCAAmC;IACnC,UAAU,CAAC,UAA+C,EAAE;QAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS;YACxC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,2EAA2E,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;YACzH,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAgC,CAAC;QAC3H,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAChC,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,QAAgB;QACvB,MAAM,MAAM,GAAwC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,CAAC,MAAc,EAAuB,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QAC5G,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG9B,CAAC,CAAC,GAAG,EAAiC,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACzB,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,YAAY,GAAG;gBAC/B,EAAE,EAAE,GAAG,CAAC,WAAW;gBACnB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,GAAG,CAAC,GAAG,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvD,MAAM,EAAE,GAAG,CAAC,MAAM;aACnB,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG/B,CAAC,CAAC,GAAG,EAAiC,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,WAAW,GAAG,EAAE,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;QAC9E,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;KAElC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAiF,CAAC;QACjG,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;yBAEX,QAAQ;KAC5B,CAAC,CAAC,GAAG,CAAC,QAAQ,CAA+E,CAAC;QAC/F,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,EAAE,CAAwC,CAAC;QACvH,OAAO,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3C,CAAC;IAED;;;;;;OAMG;IACH,IAAI;QACF,OAAQ,IAAI,CAAC,EAAE;aACZ,OAAO,CAAC,gDAAgD,QAAQ,wDAAwD,CAAC;aACzH,GAAG,EAAgC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACK,YAAY,CAAC,GAAsB,EAAE,MAA8B,EAAE,WAAW,GAAY,EAAE,CAAC,IAAI;QACzG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC;YACzD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE;gBAC3B,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;oBAChB,MAAM,IAAI,iBAAiB,EAAE,CAAC;gBAChC,CAAC;gBACD,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;oBACrB,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;wBACrB,MAAM,IAAI,iBAAiB,EAAE,CAAC;oBAChC,CAAC;gBACH,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;gBACvC,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,GAAsB,EAAE,QAAgB,EAAE,MAAc,EAAE,UAAkC,EAAE;QAClG,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC;QACpF,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;oCAIC,QAAQ;KACvC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC,YAAY,CACtB,GAAG,EACH,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAC1E,GAAG,EAAE;YACH,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;mBAC9B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;gBAClG,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,QAAQ,GAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAA2B,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvF,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9G,CAAC,CACF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,GAAsB,EAAE,MAA+B,EAAE,MAAc;QAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,mFAAmF,QAAQ,GAAG,CAAC,CAAC;QAC/H,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACxF,CAAC;IAED;;;;;OAKG;IACH,MAAM,CACJ,GAAsB,EACtB,OAAmE,EACnE,MAAc;QAEd,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG9B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACxG,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,GAAsB,EAAE,MAAgC,EAAE,MAAc,EAAE,MAAc;QAC7F,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;oCAEC,QAAQ;KACvC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9H,CAAC;IAED;;;;;;;;;OASG;IACH,iBAAiB,CAAC,SAAiB,EAAE,QAAgB,EAAE,cAAc,GAAG,EAAE;QACxE,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CAAC;;;;OAIR,CAAC;aACD,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,cAAc,CAAsB,CAAC;QACjE,OAAO,GAAG,CAAC,KAAK,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAc;QAClB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACpD,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE;YACpB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACxE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAClF,IAAI,CAAC,EAAE;iBACJ,OAAO,CAAC,iHAAiH,CAAC;iBAC1H,GAAG,CAAC,sBAAsB,CAAC,CAAC;YAC/B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kDAAkD,QAAQ,sBAAsB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF;AAED,mFAAmF;AACnF,MAAM,iBAAkB,SAAQ,KAAK;IACnC;QACE,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { EventBus, type AgentDefinition, type CredentialScope, type AlwaysMeans, type ApprovalDelegationOrigin, type ApprovalScheduleDestination, type ApprovalAnswer, type ApprovalOutcome, type ApprovalPolicy, type ImageAttachment, type ObserveEntry, type JsonObject, type Session, type ToolRisk, type TrustLevel } from '@stratusagent/core';
|
|
2
|
-
import { type ConversationReader, type ParsedSoul, type ScheduleRecord } from '@stratusagent/agents';
|
|
2
|
+
import { type ConversationReader, type ConversationCreator, type ParsedSoul, type ScheduleRecord } from '@stratusagent/agents';
|
|
3
|
+
import { type EventIntakeRequest, type EventIntakeResponse, type EventSourceConfig, type EventsSummary } from './event-intake.ts';
|
|
4
|
+
import { type EventDeliveryRow } from './events.ts';
|
|
3
5
|
import { type SchedulerLimits } from './schedules.ts';
|
|
4
6
|
import { ShardedSessionStore } from './sessions.ts';
|
|
5
7
|
export { claimHome, homeLockPath, HomeClaimedError, type HomeClaim, } from './lock.ts';
|
|
6
8
|
export { SqliteSessionStore, ShardedSessionStore, FleetSessionIndex, SessionIdTakenError, tightenSqliteFile, type SqliteSessionStoreOptions, type ShardedSessionStoreOptions, type SessionIndexRow, type SessionReconcileReport, } from './sessions.ts';
|
|
7
9
|
export { SqliteScheduleStore, createSchedulerRuntime, isScheduleSessionId, SCHEDULE_SESSION_ID_PREFIX, type ScheduleStoreLike, type SchedulerLimits, type SchedulerRuntime, type SchedulerRuntimeOptions, } from './schedules.ts';
|
|
10
|
+
export { SqliteEventStore, EVENT_BLOCK_MAX_BYTES, EVENT_DEDUP_WINDOW_MS, EVENT_DELIVERY_ROW_CAP, eventCoalescingKey, eventDispatchKey, eventSessionId, renderEventBlock, type AcceptedEventDelivery, type EventDeliveryRow, type EventDeliveryStatus, type EventFiringRow, type EventFiringStatus, type EventSourceActivity, type NewEventFiring, type RefusedEventDelivery, } from './events.ts';
|
|
11
|
+
export { createEventIntake, mapGithubDelivery, parseStandardWebhookEvent, verifyGithubSignature, verifyStandardWebhookSignature, DEFAULT_EVENT_MAX_BODY_BYTES, EVENT_SOURCE_REQUESTS_PER_MINUTE, type EventIntake, type EventIntakeCounters, type EventIntakeOptions, type EventIntakeRequest, type EventIntakeResponse, type EventSourceConfig, type EventSourceCounters, type EventSourceSummary, type EventsSummary, type EventTrigger, } from './event-intake.ts';
|
|
8
12
|
import { type OptionalModuleHost } from '@stratusagent/plugins';
|
|
9
13
|
import { type PluginsConfig, type RuntimeSelection, type StateEnvironment } from '@stratusagent/state';
|
|
10
14
|
/**
|
|
@@ -20,8 +24,9 @@ export { applySoulPins, type SoulPinContext } from '@stratusagent/state';
|
|
|
20
24
|
/**
|
|
21
25
|
* A channel adapter as the gateway sees it: started after the roster
|
|
22
26
|
* loads, stopped before the store drains. Structurally identical to
|
|
23
|
-
* @stratusagent/
|
|
24
|
-
*
|
|
27
|
+
* `ChannelAdapter` in @stratusagent/core, declared here because it
|
|
28
|
+
* predates the channel contract moving into core, when the gateway could
|
|
29
|
+
* not import it without depending on the channels package.
|
|
25
30
|
*/
|
|
26
31
|
export interface GatewayChannelAdapter {
|
|
27
32
|
name: string;
|
|
@@ -50,9 +55,8 @@ export interface GatewayChannelAdapter {
|
|
|
50
55
|
*/
|
|
51
56
|
servesWhileHeld?: boolean;
|
|
52
57
|
/**
|
|
53
|
-
* The addressable outbound seam, mirroring
|
|
54
|
-
*
|
|
55
|
-
* for the same reason the rest of this interface does. Optional: an
|
|
58
|
+
* The addressable outbound seam, mirroring `ChannelAdapter.resolveOutbound`
|
|
59
|
+
* structurally for the same reason the rest of this interface does. Optional: an
|
|
56
60
|
* adapter with no concept of a destination simply lacks it, and the
|
|
57
61
|
* gateway treats absence as "this channel cannot be spoken through".
|
|
58
62
|
* Implementations MUST reject a destination they could not deliver to,
|
|
@@ -71,7 +75,7 @@ export interface GatewayChannelAdapter {
|
|
|
71
75
|
}>;
|
|
72
76
|
/**
|
|
73
77
|
* The read side of an addressable conversation, mirroring
|
|
74
|
-
*
|
|
78
|
+
* `ChannelAdapter.readConversation`. Optional:
|
|
75
79
|
* an adapter without it cannot be read through `message.read`.
|
|
76
80
|
* Implementations MUST reject a conversation their app may not read,
|
|
77
81
|
* with a sentence for the agent.
|
|
@@ -83,14 +87,25 @@ export interface GatewayChannelAdapter {
|
|
|
83
87
|
after?: string;
|
|
84
88
|
before?: string;
|
|
85
89
|
limit: number;
|
|
90
|
+
sessionId?: string;
|
|
86
91
|
}): ReturnType<ConversationReader>;
|
|
87
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Creates a conversation with the agent's own app, mirroring
|
|
94
|
+
* `ChannelAdapter.createConversation`. Optional: an adapter without it
|
|
95
|
+
* cannot serve `channel.create`. Implementations MUST reject what they
|
|
96
|
+
* can tell is invalid before creating anything, and MUST resolve once
|
|
97
|
+
* the conversation exists, reporting what did not happen after it.
|
|
98
|
+
*/
|
|
99
|
+
createConversation?(request: Omit<Parameters<ConversationCreator>[0], 'channel'>): ReturnType<ConversationCreator>;
|
|
100
|
+
/** See `ChannelAdapter.accountFor`. */
|
|
88
101
|
accountFor?(agentId: string): string | undefined;
|
|
89
|
-
/** See
|
|
102
|
+
/** See `ChannelAdapter.conversationKindFor`. */
|
|
90
103
|
conversationKindFor?(agentId: string, conversation: string): Promise<'channel' | 'direct' | undefined>;
|
|
104
|
+
/** See `ChannelAdapter.sessionConversation`. */
|
|
105
|
+
sessionConversation?(agentId: string, sessionId: string): string | undefined;
|
|
91
106
|
/**
|
|
92
107
|
* Shows a person a form for a credential an agent asked for, mirroring
|
|
93
|
-
*
|
|
108
|
+
* `ChannelAdapter.requestCredential`. Optional:
|
|
94
109
|
* a channel without it cannot take credentials, and a request made in
|
|
95
110
|
* its conversations is refused instead of announced. Implementations
|
|
96
111
|
* MUST reject when the form reached nobody who can answer it, with a
|
|
@@ -104,10 +119,13 @@ export interface GatewayChannelAdapter {
|
|
|
104
119
|
scope: CredentialScope;
|
|
105
120
|
reason?: string;
|
|
106
121
|
replace?: boolean;
|
|
122
|
+
env?: string;
|
|
107
123
|
metadata: JsonObject;
|
|
108
124
|
}): Promise<void>;
|
|
109
|
-
/** See
|
|
125
|
+
/** See `ChannelAdapter.credentialReplaceForms`. */
|
|
110
126
|
credentialReplaceForms?: boolean;
|
|
127
|
+
/** See `ChannelAdapter.credentialEnvForms`. */
|
|
128
|
+
credentialEnvForms?: boolean;
|
|
111
129
|
/**
|
|
112
130
|
* Issues a one-time link to a form for a credential request, for the
|
|
113
131
|
* agent to pass on: what a request falls back to when its conversation
|
|
@@ -127,6 +145,8 @@ export interface GatewayChannelAdapter {
|
|
|
127
145
|
name: string;
|
|
128
146
|
scope: CredentialScope;
|
|
129
147
|
reason?: string;
|
|
148
|
+
/** The variable the agent's shell will read the key from; the page must say so. */
|
|
149
|
+
env?: string;
|
|
130
150
|
}): Promise<{
|
|
131
151
|
url: string;
|
|
132
152
|
expiresAt: string;
|
|
@@ -136,9 +156,8 @@ export interface GatewayChannelAdapter {
|
|
|
136
156
|
}
|
|
137
157
|
/**
|
|
138
158
|
* Where a session came from, as the gateway reports it. Structural for the
|
|
139
|
-
* same reason as the adapter above
|
|
140
|
-
* matching shape for adapters to consume
|
|
141
|
-
* other to agree on it.
|
|
159
|
+
* same reason as the adapter above: core's channel contract declares the
|
|
160
|
+
* matching shape (`SessionRouting`) for adapters to consume.
|
|
142
161
|
*/
|
|
143
162
|
export interface SessionRouting {
|
|
144
163
|
/** Whose app must do the talking. */
|
|
@@ -148,12 +167,12 @@ export interface SessionRouting {
|
|
|
148
167
|
/**
|
|
149
168
|
* When this agent last spoke, ISO-8601 — its newest reply's timestamp,
|
|
150
169
|
* absent when it has never produced one. Not the session's modification
|
|
151
|
-
* time, which a mid-turn save moves; see
|
|
170
|
+
* time, which a mid-turn save moves; see `SessionRouting` in @stratusagent/core.
|
|
152
171
|
*/
|
|
153
172
|
lastSpokeAt?: string;
|
|
154
|
-
/** The latest turn's text (`latestTurnReply`), when it produced any — see
|
|
173
|
+
/** The latest turn's text (`latestTurnReply`), when it produced any — see `SessionRouting` in @stratusagent/core. */
|
|
155
174
|
reply?: string;
|
|
156
|
-
/** Whether the turn the session is on is one nobody asked for (`isUnaddressedTurn`) — see
|
|
175
|
+
/** Whether the turn the session is on is one nobody asked for (`isUnaddressedTurn`) — see `SessionRouting` in @stratusagent/core. */
|
|
157
176
|
unaddressed?: boolean;
|
|
158
177
|
}
|
|
159
178
|
/**
|
|
@@ -510,6 +529,15 @@ export interface GatewayOptions {
|
|
|
510
529
|
* event, or response resets it. 0 disables. Default 120s.
|
|
511
530
|
*/
|
|
512
531
|
idleTimeoutMs?: number;
|
|
532
|
+
/**
|
|
533
|
+
* The events listener's sources and body limit (card 590), from the
|
|
534
|
+
* trusted config's `events` block. With no sources the event store is
|
|
535
|
+
* never opened, and every delivery is to an unknown source.
|
|
536
|
+
*/
|
|
537
|
+
events?: {
|
|
538
|
+
sources?: Record<string, EventSourceConfig>;
|
|
539
|
+
maxBodyBytes?: number;
|
|
540
|
+
};
|
|
513
541
|
/**
|
|
514
542
|
* Where the per-agent session stores (`agents/<id>/sessions.db`) and the
|
|
515
543
|
* fleet database (`fleet.db`, holding the schedules and the session
|
|
@@ -837,6 +865,24 @@ export interface Gateway {
|
|
|
837
865
|
* such schedule exists.
|
|
838
866
|
*/
|
|
839
867
|
cancelSchedule(scheduleId: string): boolean;
|
|
868
|
+
/**
|
|
869
|
+
* One request to the events port (card 590): verified, deduplicated,
|
|
870
|
+
* matched against event triggers and recorded, all before the answer.
|
|
871
|
+
* The listener in control-api calls this with the raw body.
|
|
872
|
+
*/
|
|
873
|
+
receiveEvent(request: EventIntakeRequest): Promise<EventIntakeResponse>;
|
|
874
|
+
/**
|
|
875
|
+
* Per configured event source: the last delivery, the last refusal and
|
|
876
|
+
* why, and the last 24 hours by status, with the requests refused before
|
|
877
|
+
* they could leave a row (`stratus events`, `/health`). No sources when
|
|
878
|
+
* none is configured.
|
|
879
|
+
*/
|
|
880
|
+
eventSummary(): EventsSummary;
|
|
881
|
+
/** Delivery rows, newest first; a row never holds the body. Empty when no source is configured. */
|
|
882
|
+
eventDeliveries(options?: {
|
|
883
|
+
source?: string;
|
|
884
|
+
limit?: number;
|
|
885
|
+
}): EventDeliveryRow[];
|
|
840
886
|
/**
|
|
841
887
|
* Every registered tool, with the risk it will actually be judged at and
|
|
842
888
|
* the package it came from.
|