@holon-run/agentinbox 0.1.4 → 0.3.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/README.md +1 -0
- package/dist/src/adapters.js +33 -2
- package/dist/src/cli.js +173 -14
- package/dist/src/current_agent.js +1 -0
- package/dist/src/http.js +242 -8
- package/dist/src/runtime_gate.js +464 -0
- package/dist/src/service.js +1114 -23
- package/dist/src/source_resolution.js +100 -0
- package/dist/src/source_schema.js +23 -1
- package/dist/src/sources/github.js +73 -1
- package/dist/src/sources/remote.js +24 -0
- package/dist/src/sources/remote_profiles.js +133 -0
- package/dist/src/store.js +234 -9
- package/dist/src/terminal.js +12 -4
- package/drizzle/migrations/0002_subscription_cleanup_policy.sql +45 -0
- package/drizzle/migrations/0003_subscription_lifecycle_retirements.sql +14 -0
- package/drizzle/migrations/0004_source_idle_states.sql +10 -0
- package/drizzle/migrations/0005_timers.sql +22 -0
- package/drizzle/migrations/0006_terminal_runtime_pid.sql +1 -0
- package/drizzle/schema.ts +45 -2
- package/package.json +1 -1
package/dist/src/store.js
CHANGED
|
@@ -16,6 +16,17 @@ function parseJson(value) {
|
|
|
16
16
|
}
|
|
17
17
|
return JSON.parse(value);
|
|
18
18
|
}
|
|
19
|
+
function earlierLifecycleRetirement(left, right) {
|
|
20
|
+
const leftAt = Date.parse(left.retireAt);
|
|
21
|
+
const rightAt = Date.parse(right.retireAt);
|
|
22
|
+
if (!Number.isNaN(leftAt) && !Number.isNaN(rightAt) && leftAt <= rightAt) {
|
|
23
|
+
return {
|
|
24
|
+
...left,
|
|
25
|
+
updatedAt: right.updatedAt,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return right;
|
|
29
|
+
}
|
|
19
30
|
class AgentInboxStore {
|
|
20
31
|
dbPath;
|
|
21
32
|
db;
|
|
@@ -193,6 +204,38 @@ class AgentInboxStore {
|
|
|
193
204
|
const rows = this.getAll("select * from sources order by created_at asc");
|
|
194
205
|
return rows.map((row) => this.mapSource(row));
|
|
195
206
|
}
|
|
207
|
+
getSourceIdleState(sourceId) {
|
|
208
|
+
const row = this.getOne("select * from source_idle_states where source_id = ?", [sourceId]);
|
|
209
|
+
return row ? this.mapSourceIdleState(row) : null;
|
|
210
|
+
}
|
|
211
|
+
listSourceIdleStatesDue(cutoffIso) {
|
|
212
|
+
const rows = this.getAll("select * from source_idle_states where auto_pause_at <= ? and auto_paused_at is null order by auto_pause_at asc", [cutoffIso]);
|
|
213
|
+
return rows.map((row) => this.mapSourceIdleState(row));
|
|
214
|
+
}
|
|
215
|
+
upsertSourceIdleState(idleState) {
|
|
216
|
+
this.db.run(`
|
|
217
|
+
insert into source_idle_states (
|
|
218
|
+
source_id, idle_since, auto_pause_at, auto_paused_at, updated_at
|
|
219
|
+
) values (?, ?, ?, ?, ?)
|
|
220
|
+
on conflict(source_id) do update set
|
|
221
|
+
idle_since = excluded.idle_since,
|
|
222
|
+
auto_pause_at = excluded.auto_pause_at,
|
|
223
|
+
auto_paused_at = excluded.auto_paused_at,
|
|
224
|
+
updated_at = excluded.updated_at
|
|
225
|
+
`, [
|
|
226
|
+
idleState.sourceId,
|
|
227
|
+
idleState.idleSince,
|
|
228
|
+
idleState.autoPauseAt,
|
|
229
|
+
idleState.autoPausedAt ?? null,
|
|
230
|
+
idleState.updatedAt,
|
|
231
|
+
]);
|
|
232
|
+
this.persist();
|
|
233
|
+
return this.getSourceIdleState(idleState.sourceId);
|
|
234
|
+
}
|
|
235
|
+
deleteSourceIdleState(sourceId) {
|
|
236
|
+
this.db.run("delete from source_idle_states where source_id = ?", [sourceId]);
|
|
237
|
+
this.persist();
|
|
238
|
+
}
|
|
196
239
|
updateSourceDefinition(sourceId, input) {
|
|
197
240
|
const current = this.getSource(sourceId);
|
|
198
241
|
if (!current) {
|
|
@@ -245,6 +288,7 @@ class AgentInboxStore {
|
|
|
245
288
|
this.db.run("delete from stream_events where stream_id = ?", [stream.streamId]);
|
|
246
289
|
this.db.run("delete from streams where stream_id = ?", [stream.streamId]);
|
|
247
290
|
}
|
|
291
|
+
this.db.run("delete from source_idle_states where source_id = ?", [sourceId]);
|
|
248
292
|
this.db.run("delete from sources where source_id = ?", [sourceId]);
|
|
249
293
|
});
|
|
250
294
|
this.persist();
|
|
@@ -319,15 +363,15 @@ class AgentInboxStore {
|
|
|
319
363
|
insertSubscription(subscription) {
|
|
320
364
|
this.db.run(`
|
|
321
365
|
insert into subscriptions (
|
|
322
|
-
subscription_id, agent_id, source_id, filter_json,
|
|
366
|
+
subscription_id, agent_id, source_id, filter_json, tracked_resource_ref, cleanup_policy_json, start_policy, start_offset, start_time, created_at
|
|
323
367
|
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
324
368
|
`, [
|
|
325
369
|
subscription.subscriptionId,
|
|
326
370
|
subscription.agentId,
|
|
327
371
|
subscription.sourceId,
|
|
328
372
|
JSON.stringify(subscription.filter),
|
|
329
|
-
subscription.
|
|
330
|
-
subscription.
|
|
373
|
+
subscription.trackedResourceRef ?? null,
|
|
374
|
+
JSON.stringify(subscription.cleanupPolicy),
|
|
331
375
|
subscription.startPolicy,
|
|
332
376
|
subscription.startOffset ?? null,
|
|
333
377
|
subscription.startTime ?? null,
|
|
@@ -352,10 +396,63 @@ class AgentInboxStore {
|
|
|
352
396
|
if (!subscription) {
|
|
353
397
|
return null;
|
|
354
398
|
}
|
|
355
|
-
this.
|
|
399
|
+
this.inTransaction(() => {
|
|
400
|
+
this.db.run("delete from consumer_commits where consumer_id in (select consumer_id from consumers where subscription_id = ?)", [subscriptionId]);
|
|
401
|
+
this.db.run("delete from consumers where subscription_id = ?", [subscriptionId]);
|
|
402
|
+
this.db.run("delete from subscription_lifecycle_retirements where subscription_id = ?", [subscriptionId]);
|
|
403
|
+
this.db.run("delete from subscriptions where subscription_id = ?", [subscriptionId]);
|
|
404
|
+
});
|
|
356
405
|
this.persist();
|
|
357
406
|
return subscription;
|
|
358
407
|
}
|
|
408
|
+
getSubscriptionLifecycleRetirement(subscriptionId) {
|
|
409
|
+
const row = this.getOne("select * from subscription_lifecycle_retirements where subscription_id = ?", [subscriptionId]);
|
|
410
|
+
return row ? this.mapSubscriptionLifecycleRetirement(row) : null;
|
|
411
|
+
}
|
|
412
|
+
listSubscriptionLifecycleRetirements() {
|
|
413
|
+
const rows = this.getAll("select * from subscription_lifecycle_retirements order by retire_at asc, created_at asc");
|
|
414
|
+
return rows.map((row) => this.mapSubscriptionLifecycleRetirement(row));
|
|
415
|
+
}
|
|
416
|
+
listSubscriptionLifecycleRetirementsDue(cutoffIso) {
|
|
417
|
+
const rows = this.getAll("select * from subscription_lifecycle_retirements where retire_at <= ? order by retire_at asc, created_at asc", [cutoffIso]);
|
|
418
|
+
return rows.map((row) => this.mapSubscriptionLifecycleRetirement(row));
|
|
419
|
+
}
|
|
420
|
+
upsertSubscriptionLifecycleRetirement(retirement) {
|
|
421
|
+
const existing = this.getSubscriptionLifecycleRetirement(retirement.subscriptionId);
|
|
422
|
+
const next = existing
|
|
423
|
+
? earlierLifecycleRetirement(existing, retirement)
|
|
424
|
+
: retirement;
|
|
425
|
+
this.db.run(`
|
|
426
|
+
insert into subscription_lifecycle_retirements (
|
|
427
|
+
subscription_id, source_id, tracked_resource_ref, retire_at, terminal_state, terminal_result, terminal_occurred_at, created_at, updated_at
|
|
428
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
429
|
+
on conflict(subscription_id) do update set
|
|
430
|
+
source_id = excluded.source_id,
|
|
431
|
+
tracked_resource_ref = excluded.tracked_resource_ref,
|
|
432
|
+
retire_at = excluded.retire_at,
|
|
433
|
+
terminal_state = excluded.terminal_state,
|
|
434
|
+
terminal_result = excluded.terminal_result,
|
|
435
|
+
terminal_occurred_at = excluded.terminal_occurred_at,
|
|
436
|
+
created_at = excluded.created_at,
|
|
437
|
+
updated_at = excluded.updated_at
|
|
438
|
+
`, [
|
|
439
|
+
next.subscriptionId,
|
|
440
|
+
next.sourceId,
|
|
441
|
+
next.trackedResourceRef,
|
|
442
|
+
next.retireAt,
|
|
443
|
+
next.terminalState ?? null,
|
|
444
|
+
next.terminalResult ?? null,
|
|
445
|
+
next.terminalOccurredAt ?? null,
|
|
446
|
+
next.createdAt,
|
|
447
|
+
next.updatedAt,
|
|
448
|
+
]);
|
|
449
|
+
this.persist();
|
|
450
|
+
return this.getSubscriptionLifecycleRetirement(retirement.subscriptionId);
|
|
451
|
+
}
|
|
452
|
+
deleteSubscriptionLifecycleRetirement(subscriptionId) {
|
|
453
|
+
this.db.run("delete from subscription_lifecycle_retirements where subscription_id = ?", [subscriptionId]);
|
|
454
|
+
this.persist();
|
|
455
|
+
}
|
|
359
456
|
getActivationTarget(targetId) {
|
|
360
457
|
const row = this.getOne("select * from activation_targets where target_id = ?", [targetId]);
|
|
361
458
|
return row ? this.mapActivationTarget(row) : null;
|
|
@@ -408,9 +505,9 @@ class AgentInboxStore {
|
|
|
408
505
|
this.db.run(`
|
|
409
506
|
insert into activation_targets (
|
|
410
507
|
target_id, agent_id, kind, status, offline_since, consecutive_failures, last_delivered_at, last_error, mode, notify_lease_ms,
|
|
411
|
-
runtime_kind, runtime_session_id, backend, tmux_pane_id, tty, term_program, iterm_session_id,
|
|
508
|
+
runtime_kind, runtime_session_id, runtime_pid, backend, tmux_pane_id, tty, term_program, iterm_session_id,
|
|
412
509
|
created_at, updated_at, last_seen_at
|
|
413
|
-
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
510
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
414
511
|
`, [
|
|
415
512
|
target.targetId,
|
|
416
513
|
target.agentId,
|
|
@@ -424,6 +521,7 @@ class AgentInboxStore {
|
|
|
424
521
|
target.notifyLeaseMs,
|
|
425
522
|
target.runtimeKind,
|
|
426
523
|
target.runtimeSessionId ?? null,
|
|
524
|
+
target.runtimePid ?? null,
|
|
427
525
|
target.backend,
|
|
428
526
|
target.tmuxPaneId ?? null,
|
|
429
527
|
target.tty ?? null,
|
|
@@ -440,11 +538,12 @@ class AgentInboxStore {
|
|
|
440
538
|
this.db.run(`
|
|
441
539
|
update activation_targets
|
|
442
540
|
set status = 'active', offline_since = null, last_error = null, consecutive_failures = 0,
|
|
443
|
-
runtime_kind = ?, runtime_session_id = ?, tmux_pane_id = ?, tty = ?, term_program = ?, iterm_session_id = ?, updated_at = ?, last_seen_at = ?
|
|
541
|
+
runtime_kind = ?, runtime_session_id = ?, runtime_pid = ?, tmux_pane_id = ?, tty = ?, term_program = ?, iterm_session_id = ?, updated_at = ?, last_seen_at = ?
|
|
444
542
|
where target_id = ? and kind = 'terminal'
|
|
445
543
|
`, [
|
|
446
544
|
input.runtimeKind,
|
|
447
545
|
input.runtimeSessionId ?? null,
|
|
546
|
+
input.runtimePid ?? null,
|
|
448
547
|
input.tmuxPaneId ?? null,
|
|
449
548
|
input.tty ?? null,
|
|
450
549
|
input.termProgram ?? null,
|
|
@@ -507,6 +606,9 @@ class AgentInboxStore {
|
|
|
507
606
|
]);
|
|
508
607
|
this.db.run("delete from consumers where subscription_id = ?", [subscription.subscriptionId]);
|
|
509
608
|
}
|
|
609
|
+
this.db.run("delete from subscription_lifecycle_retirements where subscription_id in (select subscription_id from subscriptions where agent_id = ?)", [agentId]);
|
|
610
|
+
this.db.run("delete from source_idle_states where source_id in (select distinct source_id from subscriptions where agent_id = ?)", [agentId]);
|
|
611
|
+
this.db.run("delete from timers where agent_id = ?", [agentId]);
|
|
510
612
|
this.db.run("delete from subscriptions where agent_id = ?", [agentId]);
|
|
511
613
|
this.db.run("delete from activation_dispatch_states where agent_id = ?", [agentId]);
|
|
512
614
|
this.db.run("delete from activation_targets where agent_id = ?", [agentId]);
|
|
@@ -532,6 +634,86 @@ class AgentInboxStore {
|
|
|
532
634
|
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]);
|
|
533
635
|
return rows.map((row) => this.mapAgent(row));
|
|
534
636
|
}
|
|
637
|
+
insertTimer(timer) {
|
|
638
|
+
this.db.run(`
|
|
639
|
+
insert into timers (
|
|
640
|
+
schedule_id, agent_id, status, mode, at, interval_ms, cron_expr, timezone,
|
|
641
|
+
message, sender, next_fire_at, last_fired_at, created_at, updated_at
|
|
642
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
643
|
+
`, [
|
|
644
|
+
timer.scheduleId,
|
|
645
|
+
timer.agentId,
|
|
646
|
+
timer.status,
|
|
647
|
+
timer.mode,
|
|
648
|
+
timer.at ?? null,
|
|
649
|
+
timer.intervalMs ?? null,
|
|
650
|
+
timer.cronExpr ?? null,
|
|
651
|
+
timer.timezone,
|
|
652
|
+
timer.message,
|
|
653
|
+
timer.sender ?? null,
|
|
654
|
+
timer.nextFireAt ?? null,
|
|
655
|
+
timer.lastFiredAt ?? null,
|
|
656
|
+
timer.createdAt,
|
|
657
|
+
timer.updatedAt,
|
|
658
|
+
]);
|
|
659
|
+
this.persist();
|
|
660
|
+
}
|
|
661
|
+
getTimer(scheduleId) {
|
|
662
|
+
const row = this.getOne("select * from timers where schedule_id = ?", [scheduleId]);
|
|
663
|
+
return row ? this.mapTimer(row) : null;
|
|
664
|
+
}
|
|
665
|
+
listTimers() {
|
|
666
|
+
const rows = this.getAll("select * from timers order by created_at asc");
|
|
667
|
+
return rows.map((row) => this.mapTimer(row));
|
|
668
|
+
}
|
|
669
|
+
listTimersForAgent(agentId) {
|
|
670
|
+
const rows = this.getAll("select * from timers where agent_id = ? order by created_at asc", [agentId]);
|
|
671
|
+
return rows.map((row) => this.mapTimer(row));
|
|
672
|
+
}
|
|
673
|
+
listDueTimers(cutoffIso) {
|
|
674
|
+
const rows = this.getAll("select * from timers where status = 'active' and next_fire_at is not null and next_fire_at <= ? order by next_fire_at asc, schedule_id asc", [cutoffIso]);
|
|
675
|
+
return rows.map((row) => this.mapTimer(row));
|
|
676
|
+
}
|
|
677
|
+
getNearestActiveTimer() {
|
|
678
|
+
const row = this.getOne("select * from timers where status = 'active' and next_fire_at is not null order by next_fire_at asc, schedule_id asc limit 1");
|
|
679
|
+
return row ? this.mapTimer(row) : null;
|
|
680
|
+
}
|
|
681
|
+
updateTimer(scheduleId, input) {
|
|
682
|
+
const current = this.getTimer(scheduleId);
|
|
683
|
+
if (!current) {
|
|
684
|
+
throw new Error(`unknown timer: ${scheduleId}`);
|
|
685
|
+
}
|
|
686
|
+
this.db.run(`
|
|
687
|
+
update timers
|
|
688
|
+
set status = ?, mode = ?, at = ?, interval_ms = ?, cron_expr = ?, timezone = ?,
|
|
689
|
+
message = ?, sender = ?, next_fire_at = ?, last_fired_at = ?, updated_at = ?
|
|
690
|
+
where schedule_id = ?
|
|
691
|
+
`, [
|
|
692
|
+
input.status ?? current.status,
|
|
693
|
+
input.mode ?? current.mode,
|
|
694
|
+
input.at !== undefined ? input.at ?? null : current.at ?? null,
|
|
695
|
+
input.intervalMs !== undefined ? input.intervalMs ?? null : current.intervalMs ?? null,
|
|
696
|
+
input.cronExpr !== undefined ? input.cronExpr ?? null : current.cronExpr ?? null,
|
|
697
|
+
input.timezone ?? current.timezone,
|
|
698
|
+
input.message ?? current.message,
|
|
699
|
+
input.sender !== undefined ? input.sender ?? null : current.sender ?? null,
|
|
700
|
+
input.nextFireAt !== undefined ? input.nextFireAt ?? null : current.nextFireAt ?? null,
|
|
701
|
+
input.lastFiredAt !== undefined ? input.lastFiredAt ?? null : current.lastFiredAt ?? null,
|
|
702
|
+
input.updatedAt ?? current.updatedAt,
|
|
703
|
+
scheduleId,
|
|
704
|
+
]);
|
|
705
|
+
this.persist();
|
|
706
|
+
return this.getTimer(scheduleId);
|
|
707
|
+
}
|
|
708
|
+
deleteTimer(scheduleId) {
|
|
709
|
+
const current = this.getTimer(scheduleId);
|
|
710
|
+
if (!current) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
this.db.run("delete from timers where schedule_id = ?", [scheduleId]);
|
|
714
|
+
this.persist();
|
|
715
|
+
return current;
|
|
716
|
+
}
|
|
535
717
|
getActivationDispatchState(agentId, targetId) {
|
|
536
718
|
const row = this.getOne("select * from activation_dispatch_states where agent_id = ? and target_id = ?", [agentId, targetId]);
|
|
537
719
|
return row ? this.mapActivationDispatchState(row) : null;
|
|
@@ -895,6 +1077,8 @@ class AgentInboxStore {
|
|
|
895
1077
|
agents: this.count("agents"),
|
|
896
1078
|
offlineAgents: this.count("agents where status = 'offline'"),
|
|
897
1079
|
subscriptions: this.count("subscriptions"),
|
|
1080
|
+
subscriptionLifecycleRetirements: this.count("subscription_lifecycle_retirements"),
|
|
1081
|
+
sourceIdleStates: this.count("source_idle_states"),
|
|
898
1082
|
inboxes: this.count("inboxes"),
|
|
899
1083
|
activationTargets: this.count("activation_targets"),
|
|
900
1084
|
offlineActivationTargets: this.count("activation_targets where status = 'offline'"),
|
|
@@ -959,6 +1143,15 @@ class AgentInboxStore {
|
|
|
959
1143
|
updatedAt: String(row.updated_at),
|
|
960
1144
|
};
|
|
961
1145
|
}
|
|
1146
|
+
mapSourceIdleState(row) {
|
|
1147
|
+
return {
|
|
1148
|
+
sourceId: String(row.source_id),
|
|
1149
|
+
idleSince: String(row.idle_since),
|
|
1150
|
+
autoPauseAt: String(row.auto_pause_at),
|
|
1151
|
+
autoPausedAt: row.auto_paused_at ? String(row.auto_paused_at) : null,
|
|
1152
|
+
updatedAt: String(row.updated_at),
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
962
1155
|
mapAgent(row) {
|
|
963
1156
|
return {
|
|
964
1157
|
agentId: String(row.agent_id),
|
|
@@ -984,14 +1177,27 @@ class AgentInboxStore {
|
|
|
984
1177
|
agentId: String(row.agent_id),
|
|
985
1178
|
sourceId: String(row.source_id),
|
|
986
1179
|
filter: parseJson(row.filter_json),
|
|
987
|
-
|
|
988
|
-
|
|
1180
|
+
trackedResourceRef: row.tracked_resource_ref ? String(row.tracked_resource_ref) : null,
|
|
1181
|
+
cleanupPolicy: parseJson(row.cleanup_policy_json),
|
|
989
1182
|
startPolicy: row.start_policy,
|
|
990
1183
|
startOffset: row.start_offset != null ? Number(row.start_offset) : null,
|
|
991
1184
|
startTime: row.start_time ? String(row.start_time) : null,
|
|
992
1185
|
createdAt: String(row.created_at),
|
|
993
1186
|
};
|
|
994
1187
|
}
|
|
1188
|
+
mapSubscriptionLifecycleRetirement(row) {
|
|
1189
|
+
return {
|
|
1190
|
+
subscriptionId: String(row.subscription_id),
|
|
1191
|
+
sourceId: String(row.source_id),
|
|
1192
|
+
trackedResourceRef: String(row.tracked_resource_ref),
|
|
1193
|
+
retireAt: String(row.retire_at),
|
|
1194
|
+
terminalState: row.terminal_state ? String(row.terminal_state) : null,
|
|
1195
|
+
terminalResult: row.terminal_result ? String(row.terminal_result) : null,
|
|
1196
|
+
terminalOccurredAt: row.terminal_occurred_at ? String(row.terminal_occurred_at) : null,
|
|
1197
|
+
createdAt: String(row.created_at),
|
|
1198
|
+
updatedAt: String(row.updated_at),
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
995
1201
|
mapActivationTarget(row) {
|
|
996
1202
|
if (String(row.kind) === "webhook") {
|
|
997
1203
|
const url = typeof row.url === "string" && row.url.trim().length > 0 ? row.url.trim() : null;
|
|
@@ -1028,6 +1234,7 @@ class AgentInboxStore {
|
|
|
1028
1234
|
notifyLeaseMs: Number(row.notify_lease_ms),
|
|
1029
1235
|
runtimeKind: (row.runtime_kind ? String(row.runtime_kind) : "unknown"),
|
|
1030
1236
|
runtimeSessionId: row.runtime_session_id ? String(row.runtime_session_id) : null,
|
|
1237
|
+
runtimePid: row.runtime_pid == null ? null : Number(row.runtime_pid),
|
|
1031
1238
|
backend: row.backend,
|
|
1032
1239
|
tmuxPaneId: row.tmux_pane_id ? String(row.tmux_pane_id) : null,
|
|
1033
1240
|
tty: row.tty ? String(row.tty) : null,
|
|
@@ -1051,6 +1258,24 @@ class AgentInboxStore {
|
|
|
1051
1258
|
updatedAt: String(row.updated_at),
|
|
1052
1259
|
};
|
|
1053
1260
|
}
|
|
1261
|
+
mapTimer(row) {
|
|
1262
|
+
return {
|
|
1263
|
+
scheduleId: String(row.schedule_id),
|
|
1264
|
+
agentId: String(row.agent_id),
|
|
1265
|
+
status: String(row.status),
|
|
1266
|
+
mode: String(row.mode),
|
|
1267
|
+
at: row.at ? String(row.at) : null,
|
|
1268
|
+
intervalMs: row.interval_ms == null ? null : Number(row.interval_ms),
|
|
1269
|
+
cronExpr: row.cron_expr ? String(row.cron_expr) : null,
|
|
1270
|
+
timezone: String(row.timezone),
|
|
1271
|
+
message: String(row.message),
|
|
1272
|
+
sender: row.sender ? String(row.sender) : null,
|
|
1273
|
+
nextFireAt: row.next_fire_at ? String(row.next_fire_at) : null,
|
|
1274
|
+
lastFiredAt: row.last_fired_at ? String(row.last_fired_at) : null,
|
|
1275
|
+
createdAt: String(row.created_at),
|
|
1276
|
+
updatedAt: String(row.updated_at),
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1054
1279
|
mapInboxItem(row) {
|
|
1055
1280
|
return {
|
|
1056
1281
|
itemId: String(row.item_id),
|
package/dist/src/terminal.js
CHANGED
|
@@ -24,6 +24,7 @@ function detectTerminalContext(env = process.env) {
|
|
|
24
24
|
return {
|
|
25
25
|
runtimeKind: runtime.runtimeKind,
|
|
26
26
|
runtimeSessionId: runtime.runtimeSessionId,
|
|
27
|
+
runtimePid: runtime.runtimePid,
|
|
27
28
|
backend: "tmux",
|
|
28
29
|
tmuxPaneId,
|
|
29
30
|
tty,
|
|
@@ -35,6 +36,7 @@ function detectTerminalContext(env = process.env) {
|
|
|
35
36
|
return {
|
|
36
37
|
runtimeKind: runtime.runtimeKind,
|
|
37
38
|
runtimeSessionId: runtime.runtimeSessionId,
|
|
39
|
+
runtimePid: runtime.runtimePid,
|
|
38
40
|
backend: "iterm2",
|
|
39
41
|
tty,
|
|
40
42
|
termProgram,
|
|
@@ -77,7 +79,8 @@ class TerminalDispatcher {
|
|
|
77
79
|
if (!target.tmuxPaneId) {
|
|
78
80
|
throw new Error(`tmux terminal target ${target.targetId} is missing tmuxPaneId`);
|
|
79
81
|
}
|
|
80
|
-
await this.execAsync("tmux", ["send-keys", "-t", target.tmuxPaneId,
|
|
82
|
+
await this.execAsync("tmux", ["send-keys", "-t", target.tmuxPaneId, "-l", prompt]);
|
|
83
|
+
await this.execAsync("tmux", ["send-keys", "-t", target.tmuxPaneId, "-l", "\r"]);
|
|
81
84
|
return;
|
|
82
85
|
}
|
|
83
86
|
if (target.backend === "iterm2") {
|
|
@@ -164,15 +167,18 @@ function detectRuntimeContext(env) {
|
|
|
164
167
|
return {
|
|
165
168
|
runtimeKind: "codex",
|
|
166
169
|
runtimeSessionId: codexThreadId,
|
|
170
|
+
runtimePid: findNamedProcessInAncestry((name) => name === "codex"),
|
|
167
171
|
};
|
|
168
172
|
}
|
|
169
173
|
const claudeSessionId = normalizeOptionalString(env.CLAUDE_CODE_SESSION_ID
|
|
170
174
|
?? env.CLAUDECODE_SESSION_ID
|
|
171
175
|
?? env.CLAUDE_SESSION_ID);
|
|
172
176
|
if (claudeSessionId) {
|
|
177
|
+
const claudePid = findNamedProcessInAncestry((name) => name.includes("claude"));
|
|
173
178
|
return {
|
|
174
179
|
runtimeKind: "claude_code",
|
|
175
180
|
runtimeSessionId: claudeSessionId,
|
|
181
|
+
runtimePid: claudePid,
|
|
176
182
|
};
|
|
177
183
|
}
|
|
178
184
|
// Fallback: try to detect Claude Code from filesystem
|
|
@@ -183,12 +189,13 @@ function detectRuntimeContext(env) {
|
|
|
183
189
|
return {
|
|
184
190
|
runtimeKind: "unknown",
|
|
185
191
|
runtimeSessionId: null,
|
|
192
|
+
runtimePid: null,
|
|
186
193
|
};
|
|
187
194
|
}
|
|
188
195
|
function detectClaudeCodeFromFileSystem(env) {
|
|
189
196
|
try {
|
|
190
197
|
// Walk up the process tree to find a claude process
|
|
191
|
-
const claudePid =
|
|
198
|
+
const claudePid = findNamedProcessInAncestry((name) => name.includes("claude"));
|
|
192
199
|
if (!claudePid) {
|
|
193
200
|
return null;
|
|
194
201
|
}
|
|
@@ -215,6 +222,7 @@ function detectClaudeCodeFromFileSystem(env) {
|
|
|
215
222
|
return {
|
|
216
223
|
runtimeKind: "claude_code",
|
|
217
224
|
runtimeSessionId: sessionData.sessionId,
|
|
225
|
+
runtimePid: claudePid,
|
|
218
226
|
};
|
|
219
227
|
}
|
|
220
228
|
catch {
|
|
@@ -222,13 +230,13 @@ function detectClaudeCodeFromFileSystem(env) {
|
|
|
222
230
|
return null;
|
|
223
231
|
}
|
|
224
232
|
}
|
|
225
|
-
function
|
|
233
|
+
function findNamedProcessInAncestry(matcher) {
|
|
226
234
|
let currentPid = process.ppid;
|
|
227
235
|
const maxIterations = 10; // Prevent infinite loops
|
|
228
236
|
let iterations = 0;
|
|
229
237
|
while (currentPid && iterations < maxIterations) {
|
|
230
238
|
const processName = tryGetProcessName(currentPid);
|
|
231
|
-
if (processName
|
|
239
|
+
if (processName && matcher(processName)) {
|
|
232
240
|
return currentPid;
|
|
233
241
|
}
|
|
234
242
|
// Get parent of current process
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
create table subscriptions__new (
|
|
2
|
+
subscription_id text primary key,
|
|
3
|
+
agent_id text not null,
|
|
4
|
+
source_id text not null,
|
|
5
|
+
filter_json text not null,
|
|
6
|
+
tracked_resource_ref text,
|
|
7
|
+
cleanup_policy_json text not null,
|
|
8
|
+
start_policy text not null,
|
|
9
|
+
start_offset integer,
|
|
10
|
+
start_time text,
|
|
11
|
+
created_at text not null
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
insert into subscriptions__new (
|
|
15
|
+
subscription_id,
|
|
16
|
+
agent_id,
|
|
17
|
+
source_id,
|
|
18
|
+
filter_json,
|
|
19
|
+
tracked_resource_ref,
|
|
20
|
+
cleanup_policy_json,
|
|
21
|
+
start_policy,
|
|
22
|
+
start_offset,
|
|
23
|
+
start_time,
|
|
24
|
+
created_at
|
|
25
|
+
)
|
|
26
|
+
select
|
|
27
|
+
subscription_id,
|
|
28
|
+
agent_id,
|
|
29
|
+
source_id,
|
|
30
|
+
filter_json,
|
|
31
|
+
null as tracked_resource_ref,
|
|
32
|
+
case
|
|
33
|
+
when expires_at is not null then json_object('mode', 'at', 'at', expires_at)
|
|
34
|
+
when lifecycle_mode = 'temporary' then json_object('mode', 'manual')
|
|
35
|
+
else json_object('mode', 'manual')
|
|
36
|
+
end as cleanup_policy_json,
|
|
37
|
+
start_policy,
|
|
38
|
+
start_offset,
|
|
39
|
+
start_time,
|
|
40
|
+
created_at
|
|
41
|
+
from subscriptions;
|
|
42
|
+
|
|
43
|
+
drop table subscriptions;
|
|
44
|
+
|
|
45
|
+
alter table subscriptions__new rename to subscriptions;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
create table if not exists subscription_lifecycle_retirements (
|
|
2
|
+
subscription_id text primary key,
|
|
3
|
+
source_id text not null,
|
|
4
|
+
tracked_resource_ref text not null,
|
|
5
|
+
retire_at text not null,
|
|
6
|
+
terminal_state text,
|
|
7
|
+
terminal_result text,
|
|
8
|
+
terminal_occurred_at text,
|
|
9
|
+
created_at text not null,
|
|
10
|
+
updated_at text not null
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
create index if not exists idx_subscription_lifecycle_retirements_retire_at
|
|
14
|
+
on subscription_lifecycle_retirements(retire_at);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
create table if not exists source_idle_states (
|
|
2
|
+
source_id text primary key not null,
|
|
3
|
+
idle_since text not null,
|
|
4
|
+
auto_pause_at text not null,
|
|
5
|
+
auto_paused_at text,
|
|
6
|
+
updated_at text not null
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
create index if not exists idx_source_idle_states_auto_pause_at
|
|
10
|
+
on source_idle_states(auto_pause_at);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
create table if not exists timers (
|
|
2
|
+
schedule_id text primary key not null,
|
|
3
|
+
agent_id text not null,
|
|
4
|
+
status text not null,
|
|
5
|
+
mode text not null,
|
|
6
|
+
at text,
|
|
7
|
+
interval_ms integer,
|
|
8
|
+
cron_expr text,
|
|
9
|
+
timezone text not null,
|
|
10
|
+
message text not null,
|
|
11
|
+
sender text,
|
|
12
|
+
next_fire_at text,
|
|
13
|
+
last_fired_at text,
|
|
14
|
+
created_at text not null,
|
|
15
|
+
updated_at text not null
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
create index if not exists idx_timers_agent_next_fire_at
|
|
19
|
+
on timers(agent_id, next_fire_at);
|
|
20
|
+
|
|
21
|
+
create index if not exists idx_timers_next_fire_at
|
|
22
|
+
on timers(next_fire_at);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
alter table activation_targets add column runtime_pid integer;
|
package/drizzle/schema.ts
CHANGED
|
@@ -14,6 +14,36 @@ export const sources = sqliteTable("sources", {
|
|
|
14
14
|
sourceTypeKey: uniqueIndex("idx_sources_type_key").on(table.sourceType, table.sourceKey),
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
+
export const sourceIdleStates = sqliteTable("source_idle_states", {
|
|
18
|
+
sourceId: text("source_id").primaryKey(),
|
|
19
|
+
idleSince: text("idle_since").notNull(),
|
|
20
|
+
autoPauseAt: text("auto_pause_at").notNull(),
|
|
21
|
+
autoPausedAt: text("auto_paused_at"),
|
|
22
|
+
updatedAt: text("updated_at").notNull(),
|
|
23
|
+
}, (table) => ({
|
|
24
|
+
autoPauseAtIdx: index("idx_source_idle_states_auto_pause_at").on(table.autoPauseAt),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
export const timers = sqliteTable("timers", {
|
|
28
|
+
scheduleId: text("schedule_id").primaryKey(),
|
|
29
|
+
agentId: text("agent_id").notNull(),
|
|
30
|
+
status: text("status").notNull(),
|
|
31
|
+
mode: text("mode").notNull(),
|
|
32
|
+
at: text("at"),
|
|
33
|
+
intervalMs: integer("interval_ms"),
|
|
34
|
+
cronExpr: text("cron_expr"),
|
|
35
|
+
timezone: text("timezone").notNull(),
|
|
36
|
+
message: text("message").notNull(),
|
|
37
|
+
sender: text("sender"),
|
|
38
|
+
nextFireAt: text("next_fire_at"),
|
|
39
|
+
lastFiredAt: text("last_fired_at"),
|
|
40
|
+
createdAt: text("created_at").notNull(),
|
|
41
|
+
updatedAt: text("updated_at").notNull(),
|
|
42
|
+
}, (table) => ({
|
|
43
|
+
agentNextFireIdx: index("idx_timers_agent_next_fire_at").on(table.agentId, table.nextFireAt),
|
|
44
|
+
nextFireIdx: index("idx_timers_next_fire_at").on(table.nextFireAt),
|
|
45
|
+
}));
|
|
46
|
+
|
|
17
47
|
export const agents = sqliteTable("agents", {
|
|
18
48
|
agentId: text("agent_id").primaryKey(),
|
|
19
49
|
status: text("status").notNull(),
|
|
@@ -40,14 +70,26 @@ export const subscriptions = sqliteTable("subscriptions", {
|
|
|
40
70
|
agentId: text("agent_id").notNull(),
|
|
41
71
|
sourceId: text("source_id").notNull(),
|
|
42
72
|
filterJson: text("filter_json").notNull(),
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
trackedResourceRef: text("tracked_resource_ref"),
|
|
74
|
+
cleanupPolicyJson: text("cleanup_policy_json").notNull(),
|
|
45
75
|
startPolicy: text("start_policy").notNull(),
|
|
46
76
|
startOffset: integer("start_offset"),
|
|
47
77
|
startTime: text("start_time"),
|
|
48
78
|
createdAt: text("created_at").notNull(),
|
|
49
79
|
});
|
|
50
80
|
|
|
81
|
+
export const subscriptionLifecycleRetirements = sqliteTable("subscription_lifecycle_retirements", {
|
|
82
|
+
subscriptionId: text("subscription_id").primaryKey(),
|
|
83
|
+
sourceId: text("source_id").notNull(),
|
|
84
|
+
trackedResourceRef: text("tracked_resource_ref").notNull(),
|
|
85
|
+
retireAt: text("retire_at").notNull(),
|
|
86
|
+
terminalState: text("terminal_state"),
|
|
87
|
+
terminalResult: text("terminal_result"),
|
|
88
|
+
terminalOccurredAt: text("terminal_occurred_at"),
|
|
89
|
+
createdAt: text("created_at").notNull(),
|
|
90
|
+
updatedAt: text("updated_at").notNull(),
|
|
91
|
+
});
|
|
92
|
+
|
|
51
93
|
export const activationTargets = sqliteTable("activation_targets", {
|
|
52
94
|
targetId: text("target_id").primaryKey(),
|
|
53
95
|
agentId: text("agent_id").notNull(),
|
|
@@ -62,6 +104,7 @@ export const activationTargets = sqliteTable("activation_targets", {
|
|
|
62
104
|
url: text("url"),
|
|
63
105
|
runtimeKind: text("runtime_kind"),
|
|
64
106
|
runtimeSessionId: text("runtime_session_id"),
|
|
107
|
+
runtimePid: integer("runtime_pid"),
|
|
65
108
|
backend: text("backend"),
|
|
66
109
|
tmuxPaneId: text("tmux_pane_id"),
|
|
67
110
|
tty: text("tty"),
|