@holon-run/agentinbox 0.1.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/LICENSE +178 -0
- package/README.md +168 -0
- package/dist/src/adapters.js +111 -0
- package/dist/src/backend.js +175 -0
- package/dist/src/cli.js +620 -0
- package/dist/src/client.js +279 -0
- package/dist/src/control_server.js +93 -0
- package/dist/src/daemon.js +246 -0
- package/dist/src/filter.js +167 -0
- package/dist/src/http.js +408 -0
- package/dist/src/matcher.js +47 -0
- package/dist/src/model.js +2 -0
- package/dist/src/paths.js +141 -0
- package/dist/src/service.js +1338 -0
- package/dist/src/source_schema.js +150 -0
- package/dist/src/sources/feishu.js +567 -0
- package/dist/src/sources/github.js +485 -0
- package/dist/src/sources/github_ci.js +372 -0
- package/dist/src/store.js +1271 -0
- package/dist/src/terminal.js +301 -0
- package/dist/src/util.js +36 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AgentInboxStore = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const sql_js_1 = __importDefault(require("sql.js"));
|
|
10
|
+
const util_1 = require("./util");
|
|
11
|
+
const SCHEMA_VERSION = 11;
|
|
12
|
+
function parseJson(value) {
|
|
13
|
+
if (!value) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
return JSON.parse(value);
|
|
17
|
+
}
|
|
18
|
+
class AgentInboxStore {
|
|
19
|
+
dbPath;
|
|
20
|
+
db;
|
|
21
|
+
static sqlPromise = null;
|
|
22
|
+
constructor(dbPath, db) {
|
|
23
|
+
this.dbPath = dbPath;
|
|
24
|
+
this.db = db;
|
|
25
|
+
}
|
|
26
|
+
static async open(dbPath) {
|
|
27
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(dbPath), { recursive: true });
|
|
28
|
+
const SQL = await this.loadSqlJs();
|
|
29
|
+
const db = node_fs_1.default.existsSync(dbPath)
|
|
30
|
+
? new SQL.Database(node_fs_1.default.readFileSync(dbPath))
|
|
31
|
+
: new SQL.Database();
|
|
32
|
+
const store = new AgentInboxStore(dbPath, db);
|
|
33
|
+
store.migrate();
|
|
34
|
+
store.persist();
|
|
35
|
+
return store;
|
|
36
|
+
}
|
|
37
|
+
static async loadSqlJs() {
|
|
38
|
+
if (!this.sqlPromise) {
|
|
39
|
+
this.sqlPromise = (0, sql_js_1.default)({
|
|
40
|
+
locateFile: (file) => require.resolve(`sql.js/dist/${file}`),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return this.sqlPromise;
|
|
44
|
+
}
|
|
45
|
+
close() {
|
|
46
|
+
this.db.close();
|
|
47
|
+
}
|
|
48
|
+
save() {
|
|
49
|
+
this.persist();
|
|
50
|
+
}
|
|
51
|
+
migrate() {
|
|
52
|
+
const userVersion = this.userVersion();
|
|
53
|
+
if (userVersion === 0) {
|
|
54
|
+
this.dropKnownTables();
|
|
55
|
+
this.createCurrentSchema();
|
|
56
|
+
this.setUserVersion(SCHEMA_VERSION);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (userVersion !== SCHEMA_VERSION) {
|
|
60
|
+
throw new Error(`unsupported database schema version ${userVersion}; delete ${this.dbPath} to recreate`);
|
|
61
|
+
}
|
|
62
|
+
this.createCurrentSchema();
|
|
63
|
+
}
|
|
64
|
+
dropKnownTables() {
|
|
65
|
+
const tables = [
|
|
66
|
+
"consumer_commits",
|
|
67
|
+
"consumers",
|
|
68
|
+
"stream_events",
|
|
69
|
+
"streams",
|
|
70
|
+
"deliveries",
|
|
71
|
+
"activations",
|
|
72
|
+
"inbox_items",
|
|
73
|
+
"activation_dispatch_states",
|
|
74
|
+
"activation_targets",
|
|
75
|
+
"subscriptions",
|
|
76
|
+
"inboxes",
|
|
77
|
+
"agents",
|
|
78
|
+
"sources",
|
|
79
|
+
"terminal_dispatch_states",
|
|
80
|
+
"terminal_targets",
|
|
81
|
+
"interests",
|
|
82
|
+
];
|
|
83
|
+
this.inTransaction(() => {
|
|
84
|
+
for (const table of tables) {
|
|
85
|
+
if (this.tableExists(table)) {
|
|
86
|
+
this.db.exec(`drop table ${table};`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
createCurrentSchema() {
|
|
92
|
+
this.db.exec(`
|
|
93
|
+
create table if not exists sources (
|
|
94
|
+
source_id text primary key,
|
|
95
|
+
source_type text not null,
|
|
96
|
+
source_key text not null,
|
|
97
|
+
config_ref text,
|
|
98
|
+
config_json text not null,
|
|
99
|
+
status text not null,
|
|
100
|
+
checkpoint text,
|
|
101
|
+
created_at text not null,
|
|
102
|
+
updated_at text not null,
|
|
103
|
+
unique(source_type, source_key)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
create table if not exists agents (
|
|
107
|
+
agent_id text primary key,
|
|
108
|
+
status text not null,
|
|
109
|
+
offline_since text,
|
|
110
|
+
runtime_kind text not null,
|
|
111
|
+
runtime_session_id text,
|
|
112
|
+
created_at text not null,
|
|
113
|
+
updated_at text not null,
|
|
114
|
+
last_seen_at text not null
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
create table if not exists inboxes (
|
|
118
|
+
inbox_id text primary key,
|
|
119
|
+
owner_agent_id text not null unique,
|
|
120
|
+
created_at text not null
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
create table if not exists subscriptions (
|
|
124
|
+
subscription_id text primary key,
|
|
125
|
+
agent_id text not null,
|
|
126
|
+
source_id text not null,
|
|
127
|
+
filter_json text not null,
|
|
128
|
+
lifecycle_mode text not null,
|
|
129
|
+
expires_at text,
|
|
130
|
+
start_policy text not null,
|
|
131
|
+
start_offset integer,
|
|
132
|
+
start_time text,
|
|
133
|
+
created_at text not null
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
create table if not exists activation_targets (
|
|
137
|
+
target_id text primary key,
|
|
138
|
+
agent_id text not null,
|
|
139
|
+
kind text not null,
|
|
140
|
+
status text not null,
|
|
141
|
+
offline_since text,
|
|
142
|
+
consecutive_failures integer not null,
|
|
143
|
+
last_delivered_at text,
|
|
144
|
+
last_error text,
|
|
145
|
+
mode text not null,
|
|
146
|
+
notify_lease_ms integer not null,
|
|
147
|
+
url text,
|
|
148
|
+
runtime_kind text,
|
|
149
|
+
runtime_session_id text,
|
|
150
|
+
backend text,
|
|
151
|
+
tmux_pane_id text,
|
|
152
|
+
tty text,
|
|
153
|
+
term_program text,
|
|
154
|
+
iterm_session_id text,
|
|
155
|
+
created_at text not null,
|
|
156
|
+
updated_at text not null,
|
|
157
|
+
last_seen_at text not null
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
create table if not exists activation_dispatch_states (
|
|
161
|
+
agent_id text not null,
|
|
162
|
+
target_id text not null,
|
|
163
|
+
status text not null,
|
|
164
|
+
lease_expires_at text,
|
|
165
|
+
pending_new_item_count integer not null,
|
|
166
|
+
pending_summary text,
|
|
167
|
+
pending_subscription_ids_json text not null,
|
|
168
|
+
pending_source_ids_json text not null,
|
|
169
|
+
updated_at text not null,
|
|
170
|
+
primary key (agent_id, target_id)
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
create table if not exists inbox_items (
|
|
174
|
+
item_id text primary key,
|
|
175
|
+
source_id text not null,
|
|
176
|
+
source_native_id text not null,
|
|
177
|
+
event_variant text not null,
|
|
178
|
+
inbox_id text not null,
|
|
179
|
+
occurred_at text not null,
|
|
180
|
+
metadata_json text not null,
|
|
181
|
+
raw_payload_json text not null,
|
|
182
|
+
delivery_handle_json text,
|
|
183
|
+
acked_at text,
|
|
184
|
+
unique(source_id, source_native_id, event_variant, inbox_id)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
create table if not exists activations (
|
|
188
|
+
activation_id text primary key,
|
|
189
|
+
kind text not null,
|
|
190
|
+
agent_id text not null,
|
|
191
|
+
inbox_id text not null,
|
|
192
|
+
target_id text not null,
|
|
193
|
+
target_kind text not null,
|
|
194
|
+
subscription_ids_json text not null,
|
|
195
|
+
source_ids_json text not null,
|
|
196
|
+
new_item_count integer not null,
|
|
197
|
+
summary text not null,
|
|
198
|
+
items_json text,
|
|
199
|
+
created_at text not null,
|
|
200
|
+
delivered_at text
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
create table if not exists deliveries (
|
|
204
|
+
delivery_id text primary key,
|
|
205
|
+
provider text not null,
|
|
206
|
+
surface text not null,
|
|
207
|
+
target_ref text not null,
|
|
208
|
+
thread_ref text,
|
|
209
|
+
reply_mode text,
|
|
210
|
+
kind text not null,
|
|
211
|
+
payload_json text not null,
|
|
212
|
+
status text not null,
|
|
213
|
+
created_at text not null
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
create table if not exists streams (
|
|
217
|
+
stream_id text primary key,
|
|
218
|
+
source_id text not null unique,
|
|
219
|
+
stream_key text not null unique,
|
|
220
|
+
backend text not null,
|
|
221
|
+
created_at text not null
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
create table if not exists stream_events (
|
|
225
|
+
offset integer primary key autoincrement,
|
|
226
|
+
stream_event_id text not null unique,
|
|
227
|
+
stream_id text not null,
|
|
228
|
+
source_id text not null,
|
|
229
|
+
source_native_id text not null,
|
|
230
|
+
event_variant text not null,
|
|
231
|
+
occurred_at text not null,
|
|
232
|
+
metadata_json text not null,
|
|
233
|
+
raw_payload_json text not null,
|
|
234
|
+
delivery_handle_json text,
|
|
235
|
+
created_at text not null,
|
|
236
|
+
unique(stream_id, source_native_id, event_variant)
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
create table if not exists consumers (
|
|
240
|
+
consumer_id text primary key,
|
|
241
|
+
stream_id text not null,
|
|
242
|
+
subscription_id text not null unique,
|
|
243
|
+
consumer_key text not null unique,
|
|
244
|
+
start_policy text not null,
|
|
245
|
+
start_offset integer,
|
|
246
|
+
start_time text,
|
|
247
|
+
next_offset integer not null,
|
|
248
|
+
created_at text not null,
|
|
249
|
+
updated_at text not null
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
create table if not exists consumer_commits (
|
|
253
|
+
commit_id text primary key,
|
|
254
|
+
consumer_id text not null,
|
|
255
|
+
stream_id text not null,
|
|
256
|
+
committed_offset integer not null,
|
|
257
|
+
committed_at text not null
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
create unique index if not exists idx_activation_targets_terminal_tmux
|
|
261
|
+
on activation_targets(tmux_pane_id)
|
|
262
|
+
where kind = 'terminal' and tmux_pane_id is not null;
|
|
263
|
+
|
|
264
|
+
create unique index if not exists idx_activation_targets_terminal_iterm
|
|
265
|
+
on activation_targets(iterm_session_id)
|
|
266
|
+
where kind = 'terminal' and iterm_session_id is not null;
|
|
267
|
+
|
|
268
|
+
create unique index if not exists idx_activation_targets_runtime_session
|
|
269
|
+
on activation_targets(runtime_kind, runtime_session_id)
|
|
270
|
+
where kind = 'terminal' and runtime_session_id is not null;
|
|
271
|
+
|
|
272
|
+
create index if not exists idx_activation_dispatch_states_target
|
|
273
|
+
on activation_dispatch_states(target_id, lease_expires_at);
|
|
274
|
+
|
|
275
|
+
create index if not exists idx_inbox_items_acked_at
|
|
276
|
+
on inbox_items(acked_at);
|
|
277
|
+
|
|
278
|
+
create index if not exists idx_inbox_items_inbox_acked_at
|
|
279
|
+
on inbox_items(inbox_id, acked_at);
|
|
280
|
+
|
|
281
|
+
create index if not exists idx_agents_status_offline
|
|
282
|
+
on agents(status, offline_since);
|
|
283
|
+
|
|
284
|
+
create index if not exists idx_activation_targets_agent_status
|
|
285
|
+
on activation_targets(agent_id, status);
|
|
286
|
+
|
|
287
|
+
create index if not exists idx_stream_events_stream_offset
|
|
288
|
+
on stream_events(stream_id, offset);
|
|
289
|
+
|
|
290
|
+
create index if not exists idx_stream_events_stream_occurred_at
|
|
291
|
+
on stream_events(stream_id, occurred_at);
|
|
292
|
+
|
|
293
|
+
create index if not exists idx_consumers_stream
|
|
294
|
+
on consumers(stream_id);
|
|
295
|
+
|
|
296
|
+
create index if not exists idx_consumer_commits_consumer
|
|
297
|
+
on consumer_commits(consumer_id, committed_offset);
|
|
298
|
+
`);
|
|
299
|
+
}
|
|
300
|
+
persist() {
|
|
301
|
+
const data = this.db.export();
|
|
302
|
+
node_fs_1.default.writeFileSync(this.dbPath, Buffer.from(data));
|
|
303
|
+
}
|
|
304
|
+
inTransaction(fn) {
|
|
305
|
+
this.db.exec("begin");
|
|
306
|
+
try {
|
|
307
|
+
fn();
|
|
308
|
+
this.db.exec("commit");
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
this.db.exec("rollback");
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
userVersion() {
|
|
316
|
+
const row = this.getOne("pragma user_version;");
|
|
317
|
+
if (!row) {
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
return Number(row.user_version ?? 0);
|
|
321
|
+
}
|
|
322
|
+
setUserVersion(version) {
|
|
323
|
+
this.db.exec(`pragma user_version = ${version};`);
|
|
324
|
+
}
|
|
325
|
+
tableExists(name) {
|
|
326
|
+
const row = this.getOne("select name from sqlite_master where type = 'table' and name = ?", [name]);
|
|
327
|
+
return Boolean(row);
|
|
328
|
+
}
|
|
329
|
+
getSourceByKey(sourceType, sourceKey) {
|
|
330
|
+
const row = sourceType === "local_event"
|
|
331
|
+
? this.getOne("select * from sources where source_type in (?, ?) and source_key = ? order by case when source_type = ? then 0 else 1 end limit 1", ["local_event", "custom", sourceKey, "local_event"])
|
|
332
|
+
: this.getOne("select * from sources where source_type = ? and source_key = ?", [sourceType, sourceKey]);
|
|
333
|
+
return row ? this.mapSource(row) : null;
|
|
334
|
+
}
|
|
335
|
+
getSource(sourceId) {
|
|
336
|
+
const row = this.getOne("select * from sources where source_id = ?", [sourceId]);
|
|
337
|
+
return row ? this.mapSource(row) : null;
|
|
338
|
+
}
|
|
339
|
+
insertSource(source) {
|
|
340
|
+
this.db.run(`
|
|
341
|
+
insert into sources (
|
|
342
|
+
source_id, source_type, source_key, config_ref, config_json,
|
|
343
|
+
status, checkpoint, created_at, updated_at
|
|
344
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
345
|
+
`, [
|
|
346
|
+
source.sourceId,
|
|
347
|
+
source.sourceType,
|
|
348
|
+
source.sourceKey,
|
|
349
|
+
source.configRef ?? null,
|
|
350
|
+
JSON.stringify(source.config ?? {}),
|
|
351
|
+
source.status,
|
|
352
|
+
source.checkpoint ?? null,
|
|
353
|
+
source.createdAt,
|
|
354
|
+
source.updatedAt,
|
|
355
|
+
]);
|
|
356
|
+
this.persist();
|
|
357
|
+
}
|
|
358
|
+
listSources() {
|
|
359
|
+
const rows = this.getAll("select * from sources order by created_at asc");
|
|
360
|
+
return rows.map((row) => this.mapSource(row));
|
|
361
|
+
}
|
|
362
|
+
updateSourceRuntime(sourceId, input) {
|
|
363
|
+
const current = this.getSource(sourceId);
|
|
364
|
+
if (!current) {
|
|
365
|
+
throw new Error(`unknown source: ${sourceId}`);
|
|
366
|
+
}
|
|
367
|
+
this.db.run(`
|
|
368
|
+
update sources
|
|
369
|
+
set status = ?, checkpoint = ?, updated_at = ?
|
|
370
|
+
where source_id = ?
|
|
371
|
+
`, [
|
|
372
|
+
input.status ?? current.status,
|
|
373
|
+
input.checkpoint ?? current.checkpoint ?? null,
|
|
374
|
+
(0, util_1.nowIso)(),
|
|
375
|
+
sourceId,
|
|
376
|
+
]);
|
|
377
|
+
this.persist();
|
|
378
|
+
}
|
|
379
|
+
getAgent(agentId) {
|
|
380
|
+
const row = this.getOne("select * from agents where agent_id = ?", [agentId]);
|
|
381
|
+
return row ? this.mapAgent(row) : null;
|
|
382
|
+
}
|
|
383
|
+
insertAgent(agent) {
|
|
384
|
+
this.db.run(`
|
|
385
|
+
insert into agents (
|
|
386
|
+
agent_id, status, offline_since, runtime_kind, runtime_session_id, created_at, updated_at, last_seen_at
|
|
387
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
388
|
+
`, [
|
|
389
|
+
agent.agentId,
|
|
390
|
+
agent.status,
|
|
391
|
+
agent.offlineSince ?? null,
|
|
392
|
+
agent.runtimeKind,
|
|
393
|
+
agent.runtimeSessionId ?? null,
|
|
394
|
+
agent.createdAt,
|
|
395
|
+
agent.updatedAt,
|
|
396
|
+
agent.lastSeenAt,
|
|
397
|
+
]);
|
|
398
|
+
this.persist();
|
|
399
|
+
}
|
|
400
|
+
updateAgent(agentId, input) {
|
|
401
|
+
const current = this.getAgent(agentId);
|
|
402
|
+
if (!current) {
|
|
403
|
+
throw new Error(`unknown agent: ${agentId}`);
|
|
404
|
+
}
|
|
405
|
+
this.db.run(`
|
|
406
|
+
update agents
|
|
407
|
+
set status = ?, offline_since = ?, runtime_kind = ?, runtime_session_id = ?, updated_at = ?, last_seen_at = ?
|
|
408
|
+
where agent_id = ?
|
|
409
|
+
`, [
|
|
410
|
+
input.status ?? current.status,
|
|
411
|
+
input.offlineSince !== undefined ? input.offlineSince : current.offlineSince ?? null,
|
|
412
|
+
input.runtimeKind,
|
|
413
|
+
input.runtimeSessionId ?? null,
|
|
414
|
+
input.updatedAt,
|
|
415
|
+
input.lastSeenAt,
|
|
416
|
+
agentId,
|
|
417
|
+
]);
|
|
418
|
+
this.persist();
|
|
419
|
+
return this.getAgent(agentId);
|
|
420
|
+
}
|
|
421
|
+
listAgents() {
|
|
422
|
+
const rows = this.getAll("select * from agents order by created_at asc");
|
|
423
|
+
return rows.map((row) => this.mapAgent(row));
|
|
424
|
+
}
|
|
425
|
+
getInbox(inboxId) {
|
|
426
|
+
const row = this.getOne("select * from inboxes where inbox_id = ?", [inboxId]);
|
|
427
|
+
return row ? this.mapInbox(row) : null;
|
|
428
|
+
}
|
|
429
|
+
getInboxByAgentId(agentId) {
|
|
430
|
+
const row = this.getOne("select * from inboxes where owner_agent_id = ?", [agentId]);
|
|
431
|
+
return row ? this.mapInbox(row) : null;
|
|
432
|
+
}
|
|
433
|
+
insertInbox(inbox) {
|
|
434
|
+
this.db.run("insert into inboxes (inbox_id, owner_agent_id, created_at) values (?, ?, ?)", [inbox.inboxId, inbox.ownerAgentId, inbox.createdAt]);
|
|
435
|
+
this.persist();
|
|
436
|
+
}
|
|
437
|
+
listInboxes() {
|
|
438
|
+
const rows = this.getAll("select * from inboxes order by created_at asc");
|
|
439
|
+
return rows.map((row) => this.mapInbox(row));
|
|
440
|
+
}
|
|
441
|
+
getSubscription(subscriptionId) {
|
|
442
|
+
const row = this.getOne("select * from subscriptions where subscription_id = ?", [subscriptionId]);
|
|
443
|
+
return row ? this.mapSubscription(row) : null;
|
|
444
|
+
}
|
|
445
|
+
insertSubscription(subscription) {
|
|
446
|
+
this.db.run(`
|
|
447
|
+
insert into subscriptions (
|
|
448
|
+
subscription_id, agent_id, source_id, filter_json, lifecycle_mode, expires_at, start_policy, start_offset, start_time, created_at
|
|
449
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
450
|
+
`, [
|
|
451
|
+
subscription.subscriptionId,
|
|
452
|
+
subscription.agentId,
|
|
453
|
+
subscription.sourceId,
|
|
454
|
+
JSON.stringify(subscription.filter),
|
|
455
|
+
subscription.lifecycleMode,
|
|
456
|
+
subscription.expiresAt ?? null,
|
|
457
|
+
subscription.startPolicy,
|
|
458
|
+
subscription.startOffset ?? null,
|
|
459
|
+
subscription.startTime ?? null,
|
|
460
|
+
subscription.createdAt,
|
|
461
|
+
]);
|
|
462
|
+
this.persist();
|
|
463
|
+
}
|
|
464
|
+
listSubscriptions() {
|
|
465
|
+
const rows = this.getAll("select * from subscriptions order by created_at asc");
|
|
466
|
+
return rows.map((row) => this.mapSubscription(row));
|
|
467
|
+
}
|
|
468
|
+
listSubscriptionsForSource(sourceId) {
|
|
469
|
+
const rows = this.getAll("select * from subscriptions where source_id = ? order by created_at asc", [sourceId]);
|
|
470
|
+
return rows.map((row) => this.mapSubscription(row));
|
|
471
|
+
}
|
|
472
|
+
listSubscriptionsForAgent(agentId) {
|
|
473
|
+
const rows = this.getAll("select * from subscriptions where agent_id = ? order by created_at asc", [agentId]);
|
|
474
|
+
return rows.map((row) => this.mapSubscription(row));
|
|
475
|
+
}
|
|
476
|
+
deleteSubscription(subscriptionId) {
|
|
477
|
+
const subscription = this.getSubscription(subscriptionId);
|
|
478
|
+
if (!subscription) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
this.db.run("delete from subscriptions where subscription_id = ?", [subscriptionId]);
|
|
482
|
+
this.persist();
|
|
483
|
+
return subscription;
|
|
484
|
+
}
|
|
485
|
+
getActivationTarget(targetId) {
|
|
486
|
+
const row = this.getOne("select * from activation_targets where target_id = ?", [targetId]);
|
|
487
|
+
return row ? this.mapActivationTarget(row) : null;
|
|
488
|
+
}
|
|
489
|
+
getTerminalActivationTargetByTmuxPaneId(tmuxPaneId) {
|
|
490
|
+
const row = this.getOne("select * from activation_targets where kind = 'terminal' and tmux_pane_id = ?", [tmuxPaneId]);
|
|
491
|
+
const target = row ? this.mapActivationTarget(row) : null;
|
|
492
|
+
return target?.kind === "terminal" ? target : null;
|
|
493
|
+
}
|
|
494
|
+
getTerminalActivationTargetByItermSessionId(itermSessionId) {
|
|
495
|
+
const row = this.getOne("select * from activation_targets where kind = 'terminal' and iterm_session_id = ?", [itermSessionId]);
|
|
496
|
+
const target = row ? this.mapActivationTarget(row) : null;
|
|
497
|
+
return target?.kind === "terminal" ? target : null;
|
|
498
|
+
}
|
|
499
|
+
getTerminalActivationTargetByTty(tty) {
|
|
500
|
+
const row = this.getOne("select * from activation_targets where kind = 'terminal' and tty = ?", [tty]);
|
|
501
|
+
const target = row ? this.mapActivationTarget(row) : null;
|
|
502
|
+
return target?.kind === "terminal" ? target : null;
|
|
503
|
+
}
|
|
504
|
+
getTerminalActivationTargetByRuntimeSession(runtimeKind, runtimeSessionId) {
|
|
505
|
+
const row = this.getOne("select * from activation_targets where kind = 'terminal' and runtime_kind = ? and runtime_session_id = ?", [runtimeKind, runtimeSessionId]);
|
|
506
|
+
const target = row ? this.mapActivationTarget(row) : null;
|
|
507
|
+
return target?.kind === "terminal" ? target : null;
|
|
508
|
+
}
|
|
509
|
+
insertActivationTarget(target) {
|
|
510
|
+
if (target.kind === "webhook") {
|
|
511
|
+
this.db.run(`
|
|
512
|
+
insert into activation_targets (
|
|
513
|
+
target_id, agent_id, kind, status, offline_since, consecutive_failures, last_delivered_at, last_error,
|
|
514
|
+
mode, notify_lease_ms, url, created_at, updated_at, last_seen_at
|
|
515
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
516
|
+
`, [
|
|
517
|
+
target.targetId,
|
|
518
|
+
target.agentId,
|
|
519
|
+
target.kind,
|
|
520
|
+
target.status,
|
|
521
|
+
target.offlineSince ?? null,
|
|
522
|
+
target.consecutiveFailures,
|
|
523
|
+
target.lastDeliveredAt ?? null,
|
|
524
|
+
target.lastError ?? null,
|
|
525
|
+
target.mode,
|
|
526
|
+
target.notifyLeaseMs,
|
|
527
|
+
target.url,
|
|
528
|
+
target.createdAt,
|
|
529
|
+
target.updatedAt,
|
|
530
|
+
target.lastSeenAt,
|
|
531
|
+
]);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
this.db.run(`
|
|
535
|
+
insert into activation_targets (
|
|
536
|
+
target_id, agent_id, kind, status, offline_since, consecutive_failures, last_delivered_at, last_error, mode, notify_lease_ms,
|
|
537
|
+
runtime_kind, runtime_session_id, backend, tmux_pane_id, tty, term_program, iterm_session_id,
|
|
538
|
+
created_at, updated_at, last_seen_at
|
|
539
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
540
|
+
`, [
|
|
541
|
+
target.targetId,
|
|
542
|
+
target.agentId,
|
|
543
|
+
target.kind,
|
|
544
|
+
target.status,
|
|
545
|
+
target.offlineSince ?? null,
|
|
546
|
+
target.consecutiveFailures,
|
|
547
|
+
target.lastDeliveredAt ?? null,
|
|
548
|
+
target.lastError ?? null,
|
|
549
|
+
target.mode,
|
|
550
|
+
target.notifyLeaseMs,
|
|
551
|
+
target.runtimeKind,
|
|
552
|
+
target.runtimeSessionId ?? null,
|
|
553
|
+
target.backend,
|
|
554
|
+
target.tmuxPaneId ?? null,
|
|
555
|
+
target.tty ?? null,
|
|
556
|
+
target.termProgram ?? null,
|
|
557
|
+
target.itermSessionId ?? null,
|
|
558
|
+
target.createdAt,
|
|
559
|
+
target.updatedAt,
|
|
560
|
+
target.lastSeenAt,
|
|
561
|
+
]);
|
|
562
|
+
}
|
|
563
|
+
this.persist();
|
|
564
|
+
}
|
|
565
|
+
updateTerminalActivationTargetHeartbeat(targetId, input) {
|
|
566
|
+
this.db.run(`
|
|
567
|
+
update activation_targets
|
|
568
|
+
set status = 'active', offline_since = null, last_error = null, consecutive_failures = 0,
|
|
569
|
+
runtime_kind = ?, runtime_session_id = ?, tmux_pane_id = ?, tty = ?, term_program = ?, iterm_session_id = ?, updated_at = ?, last_seen_at = ?
|
|
570
|
+
where target_id = ? and kind = 'terminal'
|
|
571
|
+
`, [
|
|
572
|
+
input.runtimeKind,
|
|
573
|
+
input.runtimeSessionId ?? null,
|
|
574
|
+
input.tmuxPaneId ?? null,
|
|
575
|
+
input.tty ?? null,
|
|
576
|
+
input.termProgram ?? null,
|
|
577
|
+
input.itermSessionId ?? null,
|
|
578
|
+
input.updatedAt,
|
|
579
|
+
input.lastSeenAt,
|
|
580
|
+
targetId,
|
|
581
|
+
]);
|
|
582
|
+
this.persist();
|
|
583
|
+
const target = this.getActivationTarget(targetId);
|
|
584
|
+
if (!target || target.kind !== "terminal") {
|
|
585
|
+
throw new Error(`unknown terminal activation target: ${targetId}`);
|
|
586
|
+
}
|
|
587
|
+
return target;
|
|
588
|
+
}
|
|
589
|
+
updateActivationTargetRuntime(targetId, input) {
|
|
590
|
+
const current = this.getActivationTarget(targetId);
|
|
591
|
+
if (!current) {
|
|
592
|
+
throw new Error(`unknown activation target: ${targetId}`);
|
|
593
|
+
}
|
|
594
|
+
this.db.run(`
|
|
595
|
+
update activation_targets
|
|
596
|
+
set status = ?, offline_since = ?, consecutive_failures = ?, last_delivered_at = ?, last_error = ?, updated_at = ?, last_seen_at = ?
|
|
597
|
+
where target_id = ?
|
|
598
|
+
`, [
|
|
599
|
+
input.status ?? current.status,
|
|
600
|
+
input.offlineSince !== undefined ? input.offlineSince : current.offlineSince ?? null,
|
|
601
|
+
input.consecutiveFailures ?? current.consecutiveFailures,
|
|
602
|
+
input.lastDeliveredAt !== undefined ? input.lastDeliveredAt : current.lastDeliveredAt ?? null,
|
|
603
|
+
input.lastError !== undefined ? input.lastError : current.lastError ?? null,
|
|
604
|
+
input.updatedAt,
|
|
605
|
+
input.lastSeenAt ?? current.lastSeenAt,
|
|
606
|
+
targetId,
|
|
607
|
+
]);
|
|
608
|
+
this.persist();
|
|
609
|
+
return this.getActivationTarget(targetId);
|
|
610
|
+
}
|
|
611
|
+
listActivationTargets() {
|
|
612
|
+
const rows = this.getAll("select * from activation_targets order by created_at asc");
|
|
613
|
+
return rows.map((row) => this.mapActivationTarget(row));
|
|
614
|
+
}
|
|
615
|
+
listActivationTargetsForAgent(agentId) {
|
|
616
|
+
const rows = this.getAll("select * from activation_targets where agent_id = ? order by created_at asc", [agentId]);
|
|
617
|
+
return rows.map((row) => this.mapActivationTarget(row));
|
|
618
|
+
}
|
|
619
|
+
deleteActivationTarget(agentId, targetId) {
|
|
620
|
+
this.inTransaction(() => {
|
|
621
|
+
this.db.run("delete from activation_dispatch_states where agent_id = ? and target_id = ?", [agentId, targetId]);
|
|
622
|
+
this.db.run("delete from activation_targets where agent_id = ? and target_id = ?", [agentId, targetId]);
|
|
623
|
+
});
|
|
624
|
+
this.persist();
|
|
625
|
+
}
|
|
626
|
+
deleteAgent(agentId, options) {
|
|
627
|
+
const inbox = this.getInboxByAgentId(agentId);
|
|
628
|
+
const subscriptions = this.listSubscriptionsForAgent(agentId);
|
|
629
|
+
this.inTransaction(() => {
|
|
630
|
+
for (const subscription of subscriptions) {
|
|
631
|
+
this.db.run("delete from consumer_commits where consumer_id in (select consumer_id from consumers where subscription_id = ?)", [
|
|
632
|
+
subscription.subscriptionId,
|
|
633
|
+
]);
|
|
634
|
+
this.db.run("delete from consumers where subscription_id = ?", [subscription.subscriptionId]);
|
|
635
|
+
}
|
|
636
|
+
this.db.run("delete from subscriptions where agent_id = ?", [agentId]);
|
|
637
|
+
this.db.run("delete from activation_dispatch_states where agent_id = ?", [agentId]);
|
|
638
|
+
this.db.run("delete from activation_targets where agent_id = ?", [agentId]);
|
|
639
|
+
if (inbox) {
|
|
640
|
+
this.db.run("delete from inbox_items where inbox_id = ?", [inbox.inboxId]);
|
|
641
|
+
this.db.run("delete from activations where agent_id = ? or inbox_id = ?", [agentId, inbox.inboxId]);
|
|
642
|
+
this.db.run("delete from inboxes where inbox_id = ?", [inbox.inboxId]);
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
this.db.run("delete from activations where agent_id = ?", [agentId]);
|
|
646
|
+
}
|
|
647
|
+
this.db.run("delete from agents where agent_id = ?", [agentId]);
|
|
648
|
+
});
|
|
649
|
+
if (options?.persist !== false) {
|
|
650
|
+
this.persist();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
countActiveActivationTargetsForAgent(agentId) {
|
|
654
|
+
const row = this.getOne("select count(*) as count from activation_targets where agent_id = ? and status = 'active'", [agentId]);
|
|
655
|
+
return Number(row?.count ?? 0);
|
|
656
|
+
}
|
|
657
|
+
listOfflineAgentsOlderThan(cutoffIso) {
|
|
658
|
+
const rows = this.getAll("select * from agents where status = 'offline' and offline_since is not null and offline_since <= ? order by offline_since asc", [cutoffIso]);
|
|
659
|
+
return rows.map((row) => this.mapAgent(row));
|
|
660
|
+
}
|
|
661
|
+
getActivationDispatchState(agentId, targetId) {
|
|
662
|
+
const row = this.getOne("select * from activation_dispatch_states where agent_id = ? and target_id = ?", [agentId, targetId]);
|
|
663
|
+
return row ? this.mapActivationDispatchState(row) : null;
|
|
664
|
+
}
|
|
665
|
+
listActivationDispatchStates() {
|
|
666
|
+
const rows = this.getAll("select * from activation_dispatch_states order by updated_at asc");
|
|
667
|
+
return rows.map((row) => this.mapActivationDispatchState(row));
|
|
668
|
+
}
|
|
669
|
+
listActivationDispatchStatesForAgent(agentId) {
|
|
670
|
+
const rows = this.getAll("select * from activation_dispatch_states where agent_id = ? order by updated_at asc", [agentId]);
|
|
671
|
+
return rows.map((row) => this.mapActivationDispatchState(row));
|
|
672
|
+
}
|
|
673
|
+
upsertActivationDispatchState(state) {
|
|
674
|
+
this.db.run(`
|
|
675
|
+
insert into activation_dispatch_states (
|
|
676
|
+
agent_id, target_id, status, lease_expires_at, pending_new_item_count, pending_summary,
|
|
677
|
+
pending_subscription_ids_json, pending_source_ids_json, updated_at
|
|
678
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
679
|
+
on conflict(agent_id, target_id) do update set
|
|
680
|
+
status = excluded.status,
|
|
681
|
+
lease_expires_at = excluded.lease_expires_at,
|
|
682
|
+
pending_new_item_count = excluded.pending_new_item_count,
|
|
683
|
+
pending_summary = excluded.pending_summary,
|
|
684
|
+
pending_subscription_ids_json = excluded.pending_subscription_ids_json,
|
|
685
|
+
pending_source_ids_json = excluded.pending_source_ids_json,
|
|
686
|
+
updated_at = excluded.updated_at
|
|
687
|
+
`, [
|
|
688
|
+
state.agentId,
|
|
689
|
+
state.targetId,
|
|
690
|
+
state.status,
|
|
691
|
+
state.leaseExpiresAt ?? null,
|
|
692
|
+
state.pendingNewItemCount,
|
|
693
|
+
state.pendingSummary ?? null,
|
|
694
|
+
JSON.stringify(state.pendingSubscriptionIds),
|
|
695
|
+
JSON.stringify(state.pendingSourceIds),
|
|
696
|
+
state.updatedAt,
|
|
697
|
+
]);
|
|
698
|
+
this.persist();
|
|
699
|
+
}
|
|
700
|
+
deleteActivationDispatchState(agentId, targetId) {
|
|
701
|
+
this.db.run("delete from activation_dispatch_states where agent_id = ? and target_id = ?", [agentId, targetId]);
|
|
702
|
+
this.persist();
|
|
703
|
+
}
|
|
704
|
+
insertInboxItem(item) {
|
|
705
|
+
const before = this.changes();
|
|
706
|
+
this.db.run(`
|
|
707
|
+
insert or ignore into inbox_items (
|
|
708
|
+
item_id, source_id, source_native_id, event_variant, inbox_id, occurred_at,
|
|
709
|
+
metadata_json, raw_payload_json, delivery_handle_json, acked_at
|
|
710
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
711
|
+
`, [
|
|
712
|
+
item.itemId,
|
|
713
|
+
item.sourceId,
|
|
714
|
+
item.sourceNativeId,
|
|
715
|
+
item.eventVariant,
|
|
716
|
+
item.inboxId,
|
|
717
|
+
item.occurredAt,
|
|
718
|
+
JSON.stringify(item.metadata),
|
|
719
|
+
JSON.stringify(item.rawPayload),
|
|
720
|
+
item.deliveryHandle ? JSON.stringify(item.deliveryHandle) : null,
|
|
721
|
+
item.ackedAt ?? null,
|
|
722
|
+
]);
|
|
723
|
+
const inserted = this.changes() > before;
|
|
724
|
+
if (inserted) {
|
|
725
|
+
this.persist();
|
|
726
|
+
}
|
|
727
|
+
return inserted;
|
|
728
|
+
}
|
|
729
|
+
listInboxItems(inboxId, options) {
|
|
730
|
+
const includeAcked = options?.includeAcked ?? false;
|
|
731
|
+
const filters = ["inbox_id = ?"];
|
|
732
|
+
const params = [inboxId];
|
|
733
|
+
if (!includeAcked) {
|
|
734
|
+
filters.push("acked_at is null");
|
|
735
|
+
}
|
|
736
|
+
if (options?.afterItemId) {
|
|
737
|
+
const anchor = this.getOne("select occurred_at, item_id from inbox_items where inbox_id = ? and item_id = ?", [inboxId, options.afterItemId]);
|
|
738
|
+
if (!anchor) {
|
|
739
|
+
throw new Error(`unknown inbox item: ${options.afterItemId}`);
|
|
740
|
+
}
|
|
741
|
+
filters.push("((occurred_at > ?) or (occurred_at = ? and item_id > ?))");
|
|
742
|
+
params.push(String(anchor.occurred_at), String(anchor.occurred_at), String(anchor.item_id));
|
|
743
|
+
}
|
|
744
|
+
const rows = this.getAll(`select * from inbox_items where ${filters.join(" and ")} order by occurred_at asc, item_id asc`, params);
|
|
745
|
+
return rows.map((row) => this.mapInboxItem(row));
|
|
746
|
+
}
|
|
747
|
+
ackItems(inboxId, itemIds, ackedAt) {
|
|
748
|
+
let changes = 0;
|
|
749
|
+
for (const itemId of itemIds) {
|
|
750
|
+
this.db.run(`
|
|
751
|
+
update inbox_items
|
|
752
|
+
set acked_at = ?
|
|
753
|
+
where inbox_id = ? and item_id = ? and acked_at is null
|
|
754
|
+
`, [ackedAt, inboxId, itemId]);
|
|
755
|
+
changes += this.changes();
|
|
756
|
+
}
|
|
757
|
+
if (changes > 0) {
|
|
758
|
+
this.persist();
|
|
759
|
+
}
|
|
760
|
+
return changes;
|
|
761
|
+
}
|
|
762
|
+
ackItemsThrough(inboxId, itemId, ackedAt) {
|
|
763
|
+
const anchor = this.getOne("select occurred_at, item_id from inbox_items where inbox_id = ? and item_id = ?", [inboxId, itemId]);
|
|
764
|
+
if (!anchor) {
|
|
765
|
+
throw new Error(`unknown inbox item: ${itemId}`);
|
|
766
|
+
}
|
|
767
|
+
this.db.run(`
|
|
768
|
+
update inbox_items
|
|
769
|
+
set acked_at = ?
|
|
770
|
+
where inbox_id = ?
|
|
771
|
+
and acked_at is null
|
|
772
|
+
and ((occurred_at < ?) or (occurred_at = ? and item_id <= ?))
|
|
773
|
+
`, [ackedAt, inboxId, String(anchor.occurred_at), String(anchor.occurred_at), String(anchor.item_id)]);
|
|
774
|
+
const changes = this.changes();
|
|
775
|
+
if (changes > 0) {
|
|
776
|
+
this.persist();
|
|
777
|
+
}
|
|
778
|
+
return changes;
|
|
779
|
+
}
|
|
780
|
+
deleteAckedInboxItems(inboxId, olderThan) {
|
|
781
|
+
this.db.run(`
|
|
782
|
+
delete from inbox_items
|
|
783
|
+
where inbox_id = ?
|
|
784
|
+
and acked_at is not null
|
|
785
|
+
and acked_at < ?
|
|
786
|
+
`, [inboxId, olderThan]);
|
|
787
|
+
const changes = this.changes();
|
|
788
|
+
if (changes > 0) {
|
|
789
|
+
this.persist();
|
|
790
|
+
}
|
|
791
|
+
return changes;
|
|
792
|
+
}
|
|
793
|
+
deleteAckedInboxItemsGlobal(olderThan) {
|
|
794
|
+
this.db.run(`
|
|
795
|
+
delete from inbox_items
|
|
796
|
+
where acked_at is not null
|
|
797
|
+
and acked_at < ?
|
|
798
|
+
`, [olderThan]);
|
|
799
|
+
const changes = this.changes();
|
|
800
|
+
if (changes > 0) {
|
|
801
|
+
this.persist();
|
|
802
|
+
}
|
|
803
|
+
return changes;
|
|
804
|
+
}
|
|
805
|
+
countInboxItems(inboxId, includeAcked) {
|
|
806
|
+
const row = this.getOne(`
|
|
807
|
+
select count(*) as count
|
|
808
|
+
from inbox_items
|
|
809
|
+
where inbox_id = ?
|
|
810
|
+
${includeAcked ? "" : "and acked_at is null"}
|
|
811
|
+
`, [inboxId]);
|
|
812
|
+
return Number(row?.count ?? 0);
|
|
813
|
+
}
|
|
814
|
+
insertActivation(activation) {
|
|
815
|
+
this.db.run(`
|
|
816
|
+
insert into activations (
|
|
817
|
+
activation_id, kind, agent_id, inbox_id, target_id, target_kind,
|
|
818
|
+
subscription_ids_json, source_ids_json, new_item_count, summary, items_json, created_at, delivered_at
|
|
819
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
820
|
+
`, [
|
|
821
|
+
activation.activationId,
|
|
822
|
+
activation.kind,
|
|
823
|
+
activation.agentId,
|
|
824
|
+
activation.inboxId,
|
|
825
|
+
activation.targetId,
|
|
826
|
+
activation.targetKind,
|
|
827
|
+
JSON.stringify(activation.subscriptionIds),
|
|
828
|
+
JSON.stringify(activation.sourceIds),
|
|
829
|
+
activation.newItemCount,
|
|
830
|
+
activation.summary,
|
|
831
|
+
activation.items ? JSON.stringify(activation.items) : null,
|
|
832
|
+
activation.createdAt,
|
|
833
|
+
activation.deliveredAt ?? null,
|
|
834
|
+
]);
|
|
835
|
+
this.persist();
|
|
836
|
+
}
|
|
837
|
+
listActivations() {
|
|
838
|
+
const rows = this.getAll("select * from activations order by created_at desc");
|
|
839
|
+
return rows.map((row) => this.mapActivation(row));
|
|
840
|
+
}
|
|
841
|
+
insertDelivery(delivery) {
|
|
842
|
+
this.db.run(`
|
|
843
|
+
insert into deliveries (
|
|
844
|
+
delivery_id, provider, surface, target_ref, thread_ref, reply_mode,
|
|
845
|
+
kind, payload_json, status, created_at
|
|
846
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
847
|
+
`, [
|
|
848
|
+
delivery.deliveryId,
|
|
849
|
+
delivery.provider,
|
|
850
|
+
delivery.surface,
|
|
851
|
+
delivery.targetRef,
|
|
852
|
+
delivery.threadRef ?? null,
|
|
853
|
+
delivery.replyMode ?? null,
|
|
854
|
+
delivery.kind,
|
|
855
|
+
JSON.stringify(delivery.payload),
|
|
856
|
+
delivery.status,
|
|
857
|
+
delivery.createdAt,
|
|
858
|
+
]);
|
|
859
|
+
this.persist();
|
|
860
|
+
}
|
|
861
|
+
listDeliveries() {
|
|
862
|
+
const rows = this.getAll("select * from deliveries order by created_at desc");
|
|
863
|
+
return rows.map((row) => this.mapDelivery(row));
|
|
864
|
+
}
|
|
865
|
+
insertStream(stream) {
|
|
866
|
+
this.db.run("insert into streams (stream_id, source_id, stream_key, backend, created_at) values (?, ?, ?, ?, ?)", [stream.streamId, stream.sourceId, stream.streamKey, stream.backend, stream.createdAt]);
|
|
867
|
+
this.persist();
|
|
868
|
+
}
|
|
869
|
+
getStream(streamId) {
|
|
870
|
+
const row = this.getOne("select * from streams where stream_id = ?", [streamId]);
|
|
871
|
+
return row ? this.mapStream(row) : null;
|
|
872
|
+
}
|
|
873
|
+
listStreams() {
|
|
874
|
+
const rows = this.getAll("select * from streams order by created_at asc");
|
|
875
|
+
return rows.map((row) => this.mapStream(row));
|
|
876
|
+
}
|
|
877
|
+
getStreamBySourceId(sourceId) {
|
|
878
|
+
const row = this.getOne("select * from streams where source_id = ?", [sourceId]);
|
|
879
|
+
return row ? this.mapStream(row) : null;
|
|
880
|
+
}
|
|
881
|
+
insertStreamEvent(streamId, event) {
|
|
882
|
+
const before = this.changes();
|
|
883
|
+
this.db.run(`
|
|
884
|
+
insert or ignore into stream_events (
|
|
885
|
+
stream_event_id, stream_id, source_id, source_native_id, event_variant,
|
|
886
|
+
occurred_at, metadata_json, raw_payload_json, delivery_handle_json, created_at
|
|
887
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
888
|
+
`, [
|
|
889
|
+
(0, util_1.generateId)("strevt"),
|
|
890
|
+
streamId,
|
|
891
|
+
event.sourceId,
|
|
892
|
+
event.sourceNativeId,
|
|
893
|
+
event.eventVariant,
|
|
894
|
+
event.occurredAt ?? (0, util_1.nowIso)(),
|
|
895
|
+
JSON.stringify(event.metadata ?? {}),
|
|
896
|
+
JSON.stringify(event.rawPayload ?? {}),
|
|
897
|
+
event.deliveryHandle ? JSON.stringify(event.deliveryHandle) : null,
|
|
898
|
+
(0, util_1.nowIso)(),
|
|
899
|
+
]);
|
|
900
|
+
const inserted = this.changes() > before;
|
|
901
|
+
if (inserted) {
|
|
902
|
+
this.persist();
|
|
903
|
+
return {
|
|
904
|
+
appended: 1,
|
|
905
|
+
deduped: 0,
|
|
906
|
+
lastOffset: this.lastInsertRowId(),
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
return { appended: 0, deduped: 1, lastOffset: null };
|
|
910
|
+
}
|
|
911
|
+
readStreamEvents(streamId, nextOffset, limit) {
|
|
912
|
+
const rows = this.getAll(`
|
|
913
|
+
select * from stream_events
|
|
914
|
+
where stream_id = ? and offset >= ?
|
|
915
|
+
order by offset asc
|
|
916
|
+
limit ?
|
|
917
|
+
`, [streamId, nextOffset, limit]);
|
|
918
|
+
return rows.map((row) => this.mapStreamEvent(row));
|
|
919
|
+
}
|
|
920
|
+
getStreamStats(streamId) {
|
|
921
|
+
const row = this.getOne(`
|
|
922
|
+
select count(*) as event_count, max(offset) as high_watermark_offset
|
|
923
|
+
from stream_events
|
|
924
|
+
where stream_id = ?
|
|
925
|
+
`, [streamId]);
|
|
926
|
+
return {
|
|
927
|
+
streamId,
|
|
928
|
+
eventCount: Number(row?.event_count ?? 0),
|
|
929
|
+
highWatermarkOffset: row?.high_watermark_offset != null ? Number(row.high_watermark_offset) : null,
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
findOffsetAtOrAfter(streamId, occurredAt) {
|
|
933
|
+
const row = this.getOne(`
|
|
934
|
+
select offset
|
|
935
|
+
from stream_events
|
|
936
|
+
where stream_id = ? and occurred_at >= ?
|
|
937
|
+
order by occurred_at asc, offset asc
|
|
938
|
+
limit 1
|
|
939
|
+
`, [streamId, occurredAt]);
|
|
940
|
+
return row?.offset != null ? Number(row.offset) : null;
|
|
941
|
+
}
|
|
942
|
+
countPendingEvents(streamId, nextOffset) {
|
|
943
|
+
const row = this.getOne("select count(*) as count from stream_events where stream_id = ? and offset >= ?", [streamId, nextOffset]);
|
|
944
|
+
return Number(row?.count ?? 0);
|
|
945
|
+
}
|
|
946
|
+
insertConsumer(consumer) {
|
|
947
|
+
this.db.run(`
|
|
948
|
+
insert into consumers (
|
|
949
|
+
consumer_id, stream_id, subscription_id, consumer_key, start_policy,
|
|
950
|
+
start_offset, start_time, next_offset, created_at, updated_at
|
|
951
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
952
|
+
`, [
|
|
953
|
+
consumer.consumerId,
|
|
954
|
+
consumer.streamId,
|
|
955
|
+
consumer.subscriptionId,
|
|
956
|
+
consumer.consumerKey,
|
|
957
|
+
consumer.startPolicy,
|
|
958
|
+
consumer.startOffset ?? null,
|
|
959
|
+
consumer.startTime ?? null,
|
|
960
|
+
consumer.nextOffset,
|
|
961
|
+
consumer.createdAt,
|
|
962
|
+
consumer.updatedAt,
|
|
963
|
+
]);
|
|
964
|
+
this.persist();
|
|
965
|
+
}
|
|
966
|
+
getConsumer(consumerId) {
|
|
967
|
+
const row = this.getOne("select * from consumers where consumer_id = ?", [consumerId]);
|
|
968
|
+
return row ? this.mapConsumer(row) : null;
|
|
969
|
+
}
|
|
970
|
+
getConsumerBySubscriptionId(subscriptionId) {
|
|
971
|
+
const row = this.getOne("select * from consumers where subscription_id = ?", [subscriptionId]);
|
|
972
|
+
return row ? this.mapConsumer(row) : null;
|
|
973
|
+
}
|
|
974
|
+
listConsumers() {
|
|
975
|
+
const rows = this.getAll("select * from consumers order by created_at asc");
|
|
976
|
+
return rows.map((row) => this.mapConsumer(row));
|
|
977
|
+
}
|
|
978
|
+
deleteConsumer(consumerId) {
|
|
979
|
+
this.inTransaction(() => {
|
|
980
|
+
this.db.run("delete from consumer_commits where consumer_id = ?", [consumerId]);
|
|
981
|
+
this.db.run("delete from consumers where consumer_id = ?", [consumerId]);
|
|
982
|
+
});
|
|
983
|
+
this.persist();
|
|
984
|
+
}
|
|
985
|
+
updateConsumerOffset(consumerId, nextOffset, committedOffset) {
|
|
986
|
+
const consumer = this.getConsumer(consumerId);
|
|
987
|
+
if (!consumer) {
|
|
988
|
+
throw new Error(`unknown consumer: ${consumerId}`);
|
|
989
|
+
}
|
|
990
|
+
const updatedAt = (0, util_1.nowIso)();
|
|
991
|
+
this.inTransaction(() => {
|
|
992
|
+
this.db.run("update consumers set next_offset = ?, updated_at = ? where consumer_id = ?", [nextOffset, updatedAt, consumerId]);
|
|
993
|
+
this.db.run(`
|
|
994
|
+
insert into consumer_commits (
|
|
995
|
+
commit_id, consumer_id, stream_id, committed_offset, committed_at
|
|
996
|
+
) values (?, ?, ?, ?, ?)
|
|
997
|
+
`, [(0, util_1.generateId)("commit"), consumerId, consumer.streamId, committedOffset, updatedAt]);
|
|
998
|
+
});
|
|
999
|
+
this.persist();
|
|
1000
|
+
return this.getConsumer(consumerId);
|
|
1001
|
+
}
|
|
1002
|
+
resetConsumer(consumerId, input) {
|
|
1003
|
+
this.db.run(`
|
|
1004
|
+
update consumers
|
|
1005
|
+
set start_policy = ?, start_offset = ?, start_time = ?, next_offset = ?, updated_at = ?
|
|
1006
|
+
where consumer_id = ?
|
|
1007
|
+
`, [
|
|
1008
|
+
input.startPolicy,
|
|
1009
|
+
input.startOffset ?? null,
|
|
1010
|
+
input.startTime ?? null,
|
|
1011
|
+
input.nextOffset,
|
|
1012
|
+
(0, util_1.nowIso)(),
|
|
1013
|
+
consumerId,
|
|
1014
|
+
]);
|
|
1015
|
+
this.persist();
|
|
1016
|
+
return this.getConsumer(consumerId);
|
|
1017
|
+
}
|
|
1018
|
+
getCounts() {
|
|
1019
|
+
return {
|
|
1020
|
+
sources: this.count("sources"),
|
|
1021
|
+
agents: this.count("agents"),
|
|
1022
|
+
offlineAgents: this.count("agents where status = 'offline'"),
|
|
1023
|
+
subscriptions: this.count("subscriptions"),
|
|
1024
|
+
inboxes: this.count("inboxes"),
|
|
1025
|
+
activationTargets: this.count("activation_targets"),
|
|
1026
|
+
offlineActivationTargets: this.count("activation_targets where status = 'offline'"),
|
|
1027
|
+
activationDispatchStates: this.count("activation_dispatch_states"),
|
|
1028
|
+
streamEvents: this.count("stream_events"),
|
|
1029
|
+
consumers: this.count("consumers"),
|
|
1030
|
+
inboxItems: this.count("inbox_items"),
|
|
1031
|
+
pendingInboxItems: this.count("inbox_items where acked_at is null"),
|
|
1032
|
+
activations: this.count("activations"),
|
|
1033
|
+
deliveries: this.count("deliveries"),
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
count(tableOrClause) {
|
|
1037
|
+
const row = this.getOne(`select count(*) as count from ${tableOrClause}`);
|
|
1038
|
+
return row ? Number(row.count) : 0;
|
|
1039
|
+
}
|
|
1040
|
+
changes() {
|
|
1041
|
+
const row = this.getOne("select changes() as count");
|
|
1042
|
+
return row ? Number(row.count) : 0;
|
|
1043
|
+
}
|
|
1044
|
+
lastInsertRowId() {
|
|
1045
|
+
const row = this.getOne("select last_insert_rowid() as rowid");
|
|
1046
|
+
return Number(row?.rowid ?? 0);
|
|
1047
|
+
}
|
|
1048
|
+
getOne(sql, params = []) {
|
|
1049
|
+
const statement = this.db.prepare(sql);
|
|
1050
|
+
try {
|
|
1051
|
+
statement.bind(params);
|
|
1052
|
+
if (!statement.step()) {
|
|
1053
|
+
return undefined;
|
|
1054
|
+
}
|
|
1055
|
+
return statement.getAsObject();
|
|
1056
|
+
}
|
|
1057
|
+
finally {
|
|
1058
|
+
statement.free();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
getAll(sql, params = []) {
|
|
1062
|
+
const statement = this.db.prepare(sql);
|
|
1063
|
+
try {
|
|
1064
|
+
statement.bind(params);
|
|
1065
|
+
const rows = [];
|
|
1066
|
+
while (statement.step()) {
|
|
1067
|
+
rows.push(statement.getAsObject());
|
|
1068
|
+
}
|
|
1069
|
+
return rows;
|
|
1070
|
+
}
|
|
1071
|
+
finally {
|
|
1072
|
+
statement.free();
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
mapSource(row) {
|
|
1076
|
+
const sourceType = String(row.source_type) === "custom"
|
|
1077
|
+
? "local_event"
|
|
1078
|
+
: row.source_type;
|
|
1079
|
+
return {
|
|
1080
|
+
sourceId: String(row.source_id),
|
|
1081
|
+
sourceType,
|
|
1082
|
+
sourceKey: String(row.source_key),
|
|
1083
|
+
configRef: row.config_ref ? String(row.config_ref) : null,
|
|
1084
|
+
config: parseJson(row.config_json),
|
|
1085
|
+
status: row.status,
|
|
1086
|
+
checkpoint: row.checkpoint ? String(row.checkpoint) : null,
|
|
1087
|
+
createdAt: String(row.created_at),
|
|
1088
|
+
updatedAt: String(row.updated_at),
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
mapAgent(row) {
|
|
1092
|
+
return {
|
|
1093
|
+
agentId: String(row.agent_id),
|
|
1094
|
+
status: row.status,
|
|
1095
|
+
offlineSince: row.offline_since ? String(row.offline_since) : null,
|
|
1096
|
+
runtimeKind: row.runtime_kind,
|
|
1097
|
+
runtimeSessionId: row.runtime_session_id ? String(row.runtime_session_id) : null,
|
|
1098
|
+
createdAt: String(row.created_at),
|
|
1099
|
+
updatedAt: String(row.updated_at),
|
|
1100
|
+
lastSeenAt: String(row.last_seen_at),
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
mapInbox(row) {
|
|
1104
|
+
return {
|
|
1105
|
+
inboxId: String(row.inbox_id),
|
|
1106
|
+
ownerAgentId: String(row.owner_agent_id),
|
|
1107
|
+
createdAt: String(row.created_at),
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
mapSubscription(row) {
|
|
1111
|
+
return {
|
|
1112
|
+
subscriptionId: String(row.subscription_id),
|
|
1113
|
+
agentId: String(row.agent_id),
|
|
1114
|
+
sourceId: String(row.source_id),
|
|
1115
|
+
filter: parseJson(row.filter_json),
|
|
1116
|
+
lifecycleMode: row.lifecycle_mode,
|
|
1117
|
+
expiresAt: row.expires_at ? String(row.expires_at) : null,
|
|
1118
|
+
startPolicy: row.start_policy,
|
|
1119
|
+
startOffset: row.start_offset != null ? Number(row.start_offset) : null,
|
|
1120
|
+
startTime: row.start_time ? String(row.start_time) : null,
|
|
1121
|
+
createdAt: String(row.created_at),
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
mapActivationTarget(row) {
|
|
1125
|
+
if (String(row.kind) === "webhook") {
|
|
1126
|
+
const url = typeof row.url === "string" && row.url.trim().length > 0 ? row.url.trim() : null;
|
|
1127
|
+
if (!url) {
|
|
1128
|
+
throw new Error(`invalid webhook activation target: ${String(row.target_id)}`);
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
targetId: String(row.target_id),
|
|
1132
|
+
agentId: String(row.agent_id),
|
|
1133
|
+
kind: "webhook",
|
|
1134
|
+
status: row.status,
|
|
1135
|
+
offlineSince: row.offline_since ? String(row.offline_since) : null,
|
|
1136
|
+
consecutiveFailures: Number(row.consecutive_failures ?? 0),
|
|
1137
|
+
lastDeliveredAt: row.last_delivered_at ? String(row.last_delivered_at) : null,
|
|
1138
|
+
lastError: row.last_error ? String(row.last_error) : null,
|
|
1139
|
+
mode: row.mode,
|
|
1140
|
+
url,
|
|
1141
|
+
notifyLeaseMs: Number(row.notify_lease_ms),
|
|
1142
|
+
createdAt: String(row.created_at),
|
|
1143
|
+
updatedAt: String(row.updated_at),
|
|
1144
|
+
lastSeenAt: String(row.last_seen_at),
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
return {
|
|
1148
|
+
targetId: String(row.target_id),
|
|
1149
|
+
agentId: String(row.agent_id),
|
|
1150
|
+
kind: "terminal",
|
|
1151
|
+
status: row.status,
|
|
1152
|
+
offlineSince: row.offline_since ? String(row.offline_since) : null,
|
|
1153
|
+
consecutiveFailures: Number(row.consecutive_failures ?? 0),
|
|
1154
|
+
lastDeliveredAt: row.last_delivered_at ? String(row.last_delivered_at) : null,
|
|
1155
|
+
lastError: row.last_error ? String(row.last_error) : null,
|
|
1156
|
+
mode: row.mode,
|
|
1157
|
+
notifyLeaseMs: Number(row.notify_lease_ms),
|
|
1158
|
+
runtimeKind: (row.runtime_kind ? String(row.runtime_kind) : "unknown"),
|
|
1159
|
+
runtimeSessionId: row.runtime_session_id ? String(row.runtime_session_id) : null,
|
|
1160
|
+
backend: row.backend,
|
|
1161
|
+
tmuxPaneId: row.tmux_pane_id ? String(row.tmux_pane_id) : null,
|
|
1162
|
+
tty: row.tty ? String(row.tty) : null,
|
|
1163
|
+
termProgram: row.term_program ? String(row.term_program) : null,
|
|
1164
|
+
itermSessionId: row.iterm_session_id ? String(row.iterm_session_id) : null,
|
|
1165
|
+
createdAt: String(row.created_at),
|
|
1166
|
+
updatedAt: String(row.updated_at),
|
|
1167
|
+
lastSeenAt: String(row.last_seen_at),
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
mapActivationDispatchState(row) {
|
|
1171
|
+
return {
|
|
1172
|
+
agentId: String(row.agent_id),
|
|
1173
|
+
targetId: String(row.target_id),
|
|
1174
|
+
status: row.status,
|
|
1175
|
+
leaseExpiresAt: row.lease_expires_at ? String(row.lease_expires_at) : null,
|
|
1176
|
+
pendingNewItemCount: Number(row.pending_new_item_count),
|
|
1177
|
+
pendingSummary: row.pending_summary ? String(row.pending_summary) : null,
|
|
1178
|
+
pendingSubscriptionIds: parseJson(row.pending_subscription_ids_json),
|
|
1179
|
+
pendingSourceIds: parseJson(row.pending_source_ids_json),
|
|
1180
|
+
updatedAt: String(row.updated_at),
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
mapInboxItem(row) {
|
|
1184
|
+
return {
|
|
1185
|
+
itemId: String(row.item_id),
|
|
1186
|
+
sourceId: String(row.source_id),
|
|
1187
|
+
sourceNativeId: String(row.source_native_id),
|
|
1188
|
+
eventVariant: String(row.event_variant),
|
|
1189
|
+
inboxId: String(row.inbox_id),
|
|
1190
|
+
occurredAt: String(row.occurred_at),
|
|
1191
|
+
metadata: parseJson(row.metadata_json),
|
|
1192
|
+
rawPayload: parseJson(row.raw_payload_json),
|
|
1193
|
+
deliveryHandle: row.delivery_handle_json
|
|
1194
|
+
? parseJson(row.delivery_handle_json)
|
|
1195
|
+
: null,
|
|
1196
|
+
ackedAt: row.acked_at ? String(row.acked_at) : null,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
mapActivation(row) {
|
|
1200
|
+
return {
|
|
1201
|
+
kind: "agentinbox.activation",
|
|
1202
|
+
activationId: String(row.activation_id),
|
|
1203
|
+
agentId: String(row.agent_id),
|
|
1204
|
+
inboxId: String(row.inbox_id),
|
|
1205
|
+
targetId: String(row.target_id),
|
|
1206
|
+
targetKind: row.target_kind,
|
|
1207
|
+
subscriptionIds: parseJson(row.subscription_ids_json),
|
|
1208
|
+
sourceIds: parseJson(row.source_ids_json),
|
|
1209
|
+
newItemCount: Number(row.new_item_count),
|
|
1210
|
+
summary: String(row.summary),
|
|
1211
|
+
items: row.items_json ? parseJson(row.items_json) : undefined,
|
|
1212
|
+
createdAt: String(row.created_at),
|
|
1213
|
+
deliveredAt: row.delivered_at ? String(row.delivered_at) : null,
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
mapDelivery(row) {
|
|
1217
|
+
return {
|
|
1218
|
+
deliveryId: String(row.delivery_id),
|
|
1219
|
+
provider: String(row.provider),
|
|
1220
|
+
surface: String(row.surface),
|
|
1221
|
+
targetRef: String(row.target_ref),
|
|
1222
|
+
threadRef: row.thread_ref ? String(row.thread_ref) : null,
|
|
1223
|
+
replyMode: row.reply_mode ? String(row.reply_mode) : null,
|
|
1224
|
+
kind: String(row.kind),
|
|
1225
|
+
payload: parseJson(row.payload_json),
|
|
1226
|
+
status: row.status,
|
|
1227
|
+
createdAt: String(row.created_at),
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
mapStream(row) {
|
|
1231
|
+
return {
|
|
1232
|
+
streamId: String(row.stream_id),
|
|
1233
|
+
sourceId: String(row.source_id),
|
|
1234
|
+
streamKey: String(row.stream_key),
|
|
1235
|
+
backend: String(row.backend),
|
|
1236
|
+
createdAt: String(row.created_at),
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
mapStreamEvent(row) {
|
|
1240
|
+
return {
|
|
1241
|
+
offset: Number(row.offset),
|
|
1242
|
+
streamEventId: String(row.stream_event_id),
|
|
1243
|
+
streamId: String(row.stream_id),
|
|
1244
|
+
sourceId: String(row.source_id),
|
|
1245
|
+
sourceNativeId: String(row.source_native_id),
|
|
1246
|
+
eventVariant: String(row.event_variant),
|
|
1247
|
+
occurredAt: String(row.occurred_at),
|
|
1248
|
+
metadata: parseJson(row.metadata_json),
|
|
1249
|
+
rawPayload: parseJson(row.raw_payload_json),
|
|
1250
|
+
deliveryHandle: row.delivery_handle_json
|
|
1251
|
+
? parseJson(row.delivery_handle_json)
|
|
1252
|
+
: null,
|
|
1253
|
+
createdAt: String(row.created_at),
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
mapConsumer(row) {
|
|
1257
|
+
return {
|
|
1258
|
+
consumerId: String(row.consumer_id),
|
|
1259
|
+
streamId: String(row.stream_id),
|
|
1260
|
+
subscriptionId: String(row.subscription_id),
|
|
1261
|
+
consumerKey: String(row.consumer_key),
|
|
1262
|
+
nextOffset: Number(row.next_offset),
|
|
1263
|
+
startPolicy: row.start_policy,
|
|
1264
|
+
startOffset: row.start_offset != null ? Number(row.start_offset) : null,
|
|
1265
|
+
startTime: row.start_time ? String(row.start_time) : null,
|
|
1266
|
+
createdAt: String(row.created_at),
|
|
1267
|
+
updatedAt: String(row.updated_at),
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
exports.AgentInboxStore = AgentInboxStore;
|