@holon-run/agentinbox 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -16
- package/dist/src/adapters.js +38 -31
- package/dist/src/cli.js +279 -66
- package/dist/src/current_agent.js +126 -0
- package/dist/src/http.js +962 -354
- package/dist/src/service.js +94 -10
- package/dist/src/source_schema.js +2 -10
- package/dist/src/sources/feishu.js +14 -237
- package/dist/src/sources/github.js +10 -244
- package/dist/src/sources/github_ci.js +13 -12
- package/dist/src/sources/remote.js +362 -0
- package/dist/src/sources/remote_profiles.js +254 -0
- package/dist/src/store.js +119 -248
- package/dist/src/util.js +19 -3
- package/drizzle/migrations/0000_initial.sql +206 -0
- package/drizzle/migrations/0001_inbox_items_source_occurred_at_idx.sql +3 -0
- package/drizzle/migrations/meta/0001_snapshot.json +1181 -0
- package/drizzle/migrations/meta/_journal.json +20 -0
- package/drizzle/schema.ts +196 -0
- package/package.json +8 -2
- package/dist/src/matcher.js +0 -47
package/dist/src/store.js
CHANGED
|
@@ -8,7 +8,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const sql_js_1 = __importDefault(require("sql.js"));
|
|
10
10
|
const util_1 = require("./util");
|
|
11
|
-
const
|
|
11
|
+
const LEGACY_SCHEMA_VERSION = 12;
|
|
12
|
+
const DRIZZLE_MIGRATIONS_TABLE = "__drizzle_migrations";
|
|
12
13
|
function parseJson(value) {
|
|
13
14
|
if (!value) {
|
|
14
15
|
return {};
|
|
@@ -49,254 +50,89 @@ class AgentInboxStore {
|
|
|
49
50
|
this.persist();
|
|
50
51
|
}
|
|
51
52
|
migrate() {
|
|
53
|
+
const migrations = this.loadSqlMigrations();
|
|
54
|
+
const hasMigrationTable = this.tableExists(DRIZZLE_MIGRATIONS_TABLE);
|
|
55
|
+
this.ensureDrizzleMigrationsTable();
|
|
56
|
+
const applied = this.listAppliedMigrationTags();
|
|
52
57
|
const userVersion = this.userVersion();
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
if (applied.size === 0 && !hasMigrationTable && this.tableExists("sources")) {
|
|
59
|
+
if (userVersion !== 0 && userVersion !== LEGACY_SCHEMA_VERSION) {
|
|
60
|
+
throw new Error(`unsupported legacy database schema version ${userVersion} at ${this.dbPath}`);
|
|
61
|
+
}
|
|
62
|
+
this.recordAppliedMigration(migrations[0].tag);
|
|
63
|
+
applied.add(migrations[0].tag);
|
|
64
|
+
}
|
|
65
|
+
const pending = migrations.filter((migration) => !applied.has(migration.tag));
|
|
66
|
+
if (pending.length > 0) {
|
|
67
|
+
if (this.shouldBackupBeforeMigration()) {
|
|
68
|
+
this.backupDatabase();
|
|
69
|
+
}
|
|
70
|
+
for (const migration of pending) {
|
|
71
|
+
this.applyMigration(migration);
|
|
72
|
+
}
|
|
58
73
|
}
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
this.setUserVersion(migrations.length);
|
|
75
|
+
}
|
|
76
|
+
loadSqlMigrations() {
|
|
77
|
+
const migrationsDir = this.resolveMigrationsDir();
|
|
78
|
+
const files = node_fs_1.default
|
|
79
|
+
.readdirSync(migrationsDir)
|
|
80
|
+
.filter((name) => name.endsWith(".sql"))
|
|
81
|
+
.sort();
|
|
82
|
+
if (files.length === 0) {
|
|
83
|
+
throw new Error(`no SQL migrations found in ${migrationsDir}`);
|
|
61
84
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
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",
|
|
85
|
+
return files.map((name) => ({
|
|
86
|
+
tag: name.replace(/\.sql$/, ""),
|
|
87
|
+
sql: node_fs_1.default.readFileSync(node_path_1.default.join(migrationsDir, name), "utf8"),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
resolveMigrationsDir() {
|
|
91
|
+
const candidates = [
|
|
92
|
+
node_path_1.default.resolve(__dirname, "../drizzle/migrations"),
|
|
93
|
+
node_path_1.default.resolve(__dirname, "../../drizzle/migrations"),
|
|
94
|
+
node_path_1.default.resolve(process.cwd(), "drizzle/migrations"),
|
|
82
95
|
];
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.db.exec(`drop table ${table};`);
|
|
87
|
-
}
|
|
96
|
+
for (const candidate of candidates) {
|
|
97
|
+
if (node_fs_1.default.existsSync(candidate)) {
|
|
98
|
+
return candidate;
|
|
88
99
|
}
|
|
89
|
-
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`cannot locate drizzle migrations directory from ${__dirname}`);
|
|
90
102
|
}
|
|
91
|
-
|
|
103
|
+
ensureDrizzleMigrationsTable() {
|
|
92
104
|
this.db.exec(`
|
|
93
|
-
create table if not exists
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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)
|
|
105
|
+
create table if not exists ${DRIZZLE_MIGRATIONS_TABLE} (
|
|
106
|
+
id integer primary key autoincrement,
|
|
107
|
+
tag text not null unique,
|
|
108
|
+
applied_at text not null
|
|
171
109
|
);
|
|
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
110
|
`);
|
|
299
111
|
}
|
|
112
|
+
listAppliedMigrationTags() {
|
|
113
|
+
const rows = this.getAll(`select tag from ${DRIZZLE_MIGRATIONS_TABLE} order by id asc`);
|
|
114
|
+
return new Set(rows.map((row) => String(row.tag)));
|
|
115
|
+
}
|
|
116
|
+
applyMigration(migration) {
|
|
117
|
+
this.inTransaction(() => {
|
|
118
|
+
this.db.exec(migration.sql);
|
|
119
|
+
this.recordAppliedMigration(migration.tag);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
recordAppliedMigration(tag) {
|
|
123
|
+
this.db.run(`insert or ignore into ${DRIZZLE_MIGRATIONS_TABLE} (tag, applied_at) values (?, ?)`, [tag, (0, util_1.nowIso)()]);
|
|
124
|
+
}
|
|
125
|
+
shouldBackupBeforeMigration() {
|
|
126
|
+
if (!node_fs_1.default.existsSync(this.dbPath)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return this.tableExists("sources");
|
|
130
|
+
}
|
|
131
|
+
backupDatabase() {
|
|
132
|
+
const safeStamp = (0, util_1.nowIso)().replace(/[:.]/g, "-");
|
|
133
|
+
const backupPath = `${this.dbPath}.backup-${safeStamp}`;
|
|
134
|
+
node_fs_1.default.copyFileSync(this.dbPath, backupPath);
|
|
135
|
+
}
|
|
300
136
|
persist() {
|
|
301
137
|
const data = this.db.export();
|
|
302
138
|
node_fs_1.default.writeFileSync(this.dbPath, Buffer.from(data));
|
|
@@ -327,9 +163,7 @@ class AgentInboxStore {
|
|
|
327
163
|
return Boolean(row);
|
|
328
164
|
}
|
|
329
165
|
getSourceByKey(sourceType, sourceKey) {
|
|
330
|
-
const row =
|
|
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]);
|
|
166
|
+
const row = this.getOne("select * from sources where source_type = ? and source_key = ?", [sourceType, sourceKey]);
|
|
333
167
|
return row ? this.mapSource(row) : null;
|
|
334
168
|
}
|
|
335
169
|
getSource(sourceId) {
|
|
@@ -359,6 +193,24 @@ class AgentInboxStore {
|
|
|
359
193
|
const rows = this.getAll("select * from sources order by created_at asc");
|
|
360
194
|
return rows.map((row) => this.mapSource(row));
|
|
361
195
|
}
|
|
196
|
+
updateSourceDefinition(sourceId, input) {
|
|
197
|
+
const current = this.getSource(sourceId);
|
|
198
|
+
if (!current) {
|
|
199
|
+
throw new Error(`unknown source: ${sourceId}`);
|
|
200
|
+
}
|
|
201
|
+
this.db.run(`
|
|
202
|
+
update sources
|
|
203
|
+
set config_ref = ?, config_json = ?, updated_at = ?
|
|
204
|
+
where source_id = ?
|
|
205
|
+
`, [
|
|
206
|
+
Object.prototype.hasOwnProperty.call(input, "configRef") ? input.configRef ?? null : current.configRef ?? null,
|
|
207
|
+
JSON.stringify(Object.prototype.hasOwnProperty.call(input, "config") ? input.config ?? {} : current.config ?? {}),
|
|
208
|
+
(0, util_1.nowIso)(),
|
|
209
|
+
sourceId,
|
|
210
|
+
]);
|
|
211
|
+
this.persist();
|
|
212
|
+
return this.getSource(sourceId);
|
|
213
|
+
}
|
|
362
214
|
updateSourceRuntime(sourceId, input) {
|
|
363
215
|
const current = this.getSource(sourceId);
|
|
364
216
|
if (!current) {
|
|
@@ -369,13 +221,35 @@ class AgentInboxStore {
|
|
|
369
221
|
set status = ?, checkpoint = ?, updated_at = ?
|
|
370
222
|
where source_id = ?
|
|
371
223
|
`, [
|
|
372
|
-
input.status ?? current.status,
|
|
373
|
-
input.checkpoint ?? current.checkpoint ?? null,
|
|
224
|
+
Object.prototype.hasOwnProperty.call(input, "status") ? input.status ?? current.status : current.status,
|
|
225
|
+
Object.prototype.hasOwnProperty.call(input, "checkpoint") ? input.checkpoint ?? null : current.checkpoint ?? null,
|
|
374
226
|
(0, util_1.nowIso)(),
|
|
375
227
|
sourceId,
|
|
376
228
|
]);
|
|
377
229
|
this.persist();
|
|
378
230
|
}
|
|
231
|
+
deleteSource(sourceId) {
|
|
232
|
+
const source = this.getSource(sourceId);
|
|
233
|
+
if (!source) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
this.inTransaction(() => {
|
|
237
|
+
const stream = this.getStreamBySourceId(sourceId);
|
|
238
|
+
if (stream) {
|
|
239
|
+
const consumers = this.getAll("select consumer_id from consumers where stream_id = ?", [stream.streamId]);
|
|
240
|
+
for (const row of consumers) {
|
|
241
|
+
const consumerId = String(row.consumer_id);
|
|
242
|
+
this.db.run("delete from consumer_commits where consumer_id = ?", [consumerId]);
|
|
243
|
+
}
|
|
244
|
+
this.db.run("delete from consumers where stream_id = ?", [stream.streamId]);
|
|
245
|
+
this.db.run("delete from stream_events where stream_id = ?", [stream.streamId]);
|
|
246
|
+
this.db.run("delete from streams where stream_id = ?", [stream.streamId]);
|
|
247
|
+
}
|
|
248
|
+
this.db.run("delete from sources where source_id = ?", [sourceId]);
|
|
249
|
+
});
|
|
250
|
+
this.persist();
|
|
251
|
+
return source;
|
|
252
|
+
}
|
|
379
253
|
getAgent(agentId) {
|
|
380
254
|
const row = this.getOne("select * from agents where agent_id = ?", [agentId]);
|
|
381
255
|
return row ? this.mapAgent(row) : null;
|
|
@@ -1073,12 +947,9 @@ class AgentInboxStore {
|
|
|
1073
947
|
}
|
|
1074
948
|
}
|
|
1075
949
|
mapSource(row) {
|
|
1076
|
-
const sourceType = String(row.source_type) === "custom"
|
|
1077
|
-
? "local_event"
|
|
1078
|
-
: row.source_type;
|
|
1079
950
|
return {
|
|
1080
951
|
sourceId: String(row.source_id),
|
|
1081
|
-
sourceType,
|
|
952
|
+
sourceType: row.source_type,
|
|
1082
953
|
sourceKey: String(row.source_key),
|
|
1083
954
|
configRef: row.config_ref ? String(row.config_ref) : null,
|
|
1084
955
|
config: parseJson(row.config_json),
|
package/dist/src/util.js
CHANGED
|
@@ -15,13 +15,29 @@ function nowIso() {
|
|
|
15
15
|
function generateId(prefix) {
|
|
16
16
|
return `${prefix}_${node_crypto_1.default.randomUUID()}`;
|
|
17
17
|
}
|
|
18
|
-
function parseJsonArg(raw) {
|
|
18
|
+
function parseJsonArg(raw, source = "JSON argument", options) {
|
|
19
19
|
if (!raw) {
|
|
20
|
+
if (options?.requireNonEmptyObject) {
|
|
21
|
+
throw new Error(`invalid ${source}: expected a non-empty JSON object`);
|
|
22
|
+
}
|
|
20
23
|
return {};
|
|
21
24
|
}
|
|
22
|
-
|
|
25
|
+
if (options?.requireNonEmptyObject && raw.trim() === "") {
|
|
26
|
+
throw new Error(`invalid ${source}: expected a non-empty JSON object`);
|
|
27
|
+
}
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
throw new Error(`invalid ${source}: ${message}`);
|
|
35
|
+
}
|
|
23
36
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
24
|
-
throw new Error(
|
|
37
|
+
throw new Error(`expected ${source} to be a JSON object`);
|
|
38
|
+
}
|
|
39
|
+
if (options?.requireNonEmptyObject && Object.keys(parsed).length === 0) {
|
|
40
|
+
throw new Error(`invalid ${source}: expected a non-empty JSON object`);
|
|
25
41
|
}
|
|
26
42
|
return parsed;
|
|
27
43
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
create table if not exists sources (
|
|
2
|
+
source_id text primary key,
|
|
3
|
+
source_type text not null,
|
|
4
|
+
source_key text not null,
|
|
5
|
+
config_ref text,
|
|
6
|
+
config_json text not null,
|
|
7
|
+
status text not null,
|
|
8
|
+
checkpoint text,
|
|
9
|
+
created_at text not null,
|
|
10
|
+
updated_at text not null,
|
|
11
|
+
unique(source_type, source_key)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
create table if not exists agents (
|
|
15
|
+
agent_id text primary key,
|
|
16
|
+
status text not null,
|
|
17
|
+
offline_since text,
|
|
18
|
+
runtime_kind text not null,
|
|
19
|
+
runtime_session_id text,
|
|
20
|
+
created_at text not null,
|
|
21
|
+
updated_at text not null,
|
|
22
|
+
last_seen_at text not null
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
create table if not exists inboxes (
|
|
26
|
+
inbox_id text primary key,
|
|
27
|
+
owner_agent_id text not null unique,
|
|
28
|
+
created_at text not null
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
create table if not exists subscriptions (
|
|
32
|
+
subscription_id text primary key,
|
|
33
|
+
agent_id text not null,
|
|
34
|
+
source_id text not null,
|
|
35
|
+
filter_json text not null,
|
|
36
|
+
lifecycle_mode text not null,
|
|
37
|
+
expires_at text,
|
|
38
|
+
start_policy text not null,
|
|
39
|
+
start_offset integer,
|
|
40
|
+
start_time text,
|
|
41
|
+
created_at text not null
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
create table if not exists activation_targets (
|
|
45
|
+
target_id text primary key,
|
|
46
|
+
agent_id text not null,
|
|
47
|
+
kind text not null,
|
|
48
|
+
status text not null,
|
|
49
|
+
offline_since text,
|
|
50
|
+
consecutive_failures integer not null,
|
|
51
|
+
last_delivered_at text,
|
|
52
|
+
last_error text,
|
|
53
|
+
mode text not null,
|
|
54
|
+
notify_lease_ms integer not null,
|
|
55
|
+
url text,
|
|
56
|
+
runtime_kind text,
|
|
57
|
+
runtime_session_id text,
|
|
58
|
+
backend text,
|
|
59
|
+
tmux_pane_id text,
|
|
60
|
+
tty text,
|
|
61
|
+
term_program text,
|
|
62
|
+
iterm_session_id text,
|
|
63
|
+
created_at text not null,
|
|
64
|
+
updated_at text not null,
|
|
65
|
+
last_seen_at text not null
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
create table if not exists activation_dispatch_states (
|
|
69
|
+
agent_id text not null,
|
|
70
|
+
target_id text not null,
|
|
71
|
+
status text not null,
|
|
72
|
+
lease_expires_at text,
|
|
73
|
+
pending_new_item_count integer not null,
|
|
74
|
+
pending_summary text,
|
|
75
|
+
pending_subscription_ids_json text not null,
|
|
76
|
+
pending_source_ids_json text not null,
|
|
77
|
+
updated_at text not null,
|
|
78
|
+
primary key (agent_id, target_id)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
create table if not exists inbox_items (
|
|
82
|
+
item_id text primary key,
|
|
83
|
+
source_id text not null,
|
|
84
|
+
source_native_id text not null,
|
|
85
|
+
event_variant text not null,
|
|
86
|
+
inbox_id text not null,
|
|
87
|
+
occurred_at text not null,
|
|
88
|
+
metadata_json text not null,
|
|
89
|
+
raw_payload_json text not null,
|
|
90
|
+
delivery_handle_json text,
|
|
91
|
+
acked_at text,
|
|
92
|
+
unique(source_id, source_native_id, event_variant, inbox_id)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
create table if not exists activations (
|
|
96
|
+
activation_id text primary key,
|
|
97
|
+
kind text not null,
|
|
98
|
+
agent_id text not null,
|
|
99
|
+
inbox_id text not null,
|
|
100
|
+
target_id text not null,
|
|
101
|
+
target_kind text not null,
|
|
102
|
+
subscription_ids_json text not null,
|
|
103
|
+
source_ids_json text not null,
|
|
104
|
+
new_item_count integer not null,
|
|
105
|
+
summary text not null,
|
|
106
|
+
items_json text,
|
|
107
|
+
created_at text not null,
|
|
108
|
+
delivered_at text
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
create table if not exists deliveries (
|
|
112
|
+
delivery_id text primary key,
|
|
113
|
+
provider text not null,
|
|
114
|
+
surface text not null,
|
|
115
|
+
target_ref text not null,
|
|
116
|
+
thread_ref text,
|
|
117
|
+
reply_mode text,
|
|
118
|
+
kind text not null,
|
|
119
|
+
payload_json text not null,
|
|
120
|
+
status text not null,
|
|
121
|
+
created_at text not null
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
create table if not exists streams (
|
|
125
|
+
stream_id text primary key,
|
|
126
|
+
source_id text not null unique,
|
|
127
|
+
stream_key text not null unique,
|
|
128
|
+
backend text not null,
|
|
129
|
+
created_at text not null
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
create table if not exists stream_events (
|
|
133
|
+
offset integer primary key autoincrement,
|
|
134
|
+
stream_event_id text not null unique,
|
|
135
|
+
stream_id text not null,
|
|
136
|
+
source_id text not null,
|
|
137
|
+
source_native_id text not null,
|
|
138
|
+
event_variant text not null,
|
|
139
|
+
occurred_at text not null,
|
|
140
|
+
metadata_json text not null,
|
|
141
|
+
raw_payload_json text not null,
|
|
142
|
+
delivery_handle_json text,
|
|
143
|
+
created_at text not null,
|
|
144
|
+
unique(stream_id, source_native_id, event_variant)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
create table if not exists consumers (
|
|
148
|
+
consumer_id text primary key,
|
|
149
|
+
stream_id text not null,
|
|
150
|
+
subscription_id text not null unique,
|
|
151
|
+
consumer_key text not null unique,
|
|
152
|
+
start_policy text not null,
|
|
153
|
+
start_offset integer,
|
|
154
|
+
start_time text,
|
|
155
|
+
next_offset integer not null,
|
|
156
|
+
created_at text not null,
|
|
157
|
+
updated_at text not null
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
create table if not exists consumer_commits (
|
|
161
|
+
commit_id text primary key,
|
|
162
|
+
consumer_id text not null,
|
|
163
|
+
stream_id text not null,
|
|
164
|
+
committed_offset integer not null,
|
|
165
|
+
committed_at text not null
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
create unique index if not exists idx_activation_targets_terminal_tmux
|
|
169
|
+
on activation_targets(tmux_pane_id)
|
|
170
|
+
where kind = 'terminal' and tmux_pane_id is not null;
|
|
171
|
+
|
|
172
|
+
create unique index if not exists idx_activation_targets_terminal_iterm
|
|
173
|
+
on activation_targets(iterm_session_id)
|
|
174
|
+
where kind = 'terminal' and iterm_session_id is not null;
|
|
175
|
+
|
|
176
|
+
create unique index if not exists idx_activation_targets_runtime_session
|
|
177
|
+
on activation_targets(runtime_kind, runtime_session_id)
|
|
178
|
+
where kind = 'terminal' and runtime_session_id is not null;
|
|
179
|
+
|
|
180
|
+
create index if not exists idx_activation_dispatch_states_target
|
|
181
|
+
on activation_dispatch_states(target_id, lease_expires_at);
|
|
182
|
+
|
|
183
|
+
create index if not exists idx_inbox_items_acked_at
|
|
184
|
+
on inbox_items(acked_at);
|
|
185
|
+
|
|
186
|
+
create index if not exists idx_inbox_items_inbox_acked_at
|
|
187
|
+
on inbox_items(inbox_id, acked_at);
|
|
188
|
+
|
|
189
|
+
create index if not exists idx_agents_status_offline
|
|
190
|
+
on agents(status, offline_since);
|
|
191
|
+
|
|
192
|
+
create index if not exists idx_activation_targets_agent_status
|
|
193
|
+
on activation_targets(agent_id, status);
|
|
194
|
+
|
|
195
|
+
create index if not exists idx_stream_events_stream_offset
|
|
196
|
+
on stream_events(stream_id, offset);
|
|
197
|
+
|
|
198
|
+
create index if not exists idx_stream_events_stream_occurred_at
|
|
199
|
+
on stream_events(stream_id, occurred_at);
|
|
200
|
+
|
|
201
|
+
create index if not exists idx_consumers_stream
|
|
202
|
+
on consumers(stream_id);
|
|
203
|
+
|
|
204
|
+
create index if not exists idx_consumer_commits_consumer
|
|
205
|
+
on consumer_commits(consumer_id, committed_offset);
|
|
206
|
+
|