agent-relay-server 0.10.7 → 0.10.8
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/package.json +1 -1
- package/public/index.html +49 -47
- package/src/config-store.ts +380 -0
- package/src/db.ts +223 -5
- package/src/index.ts +2 -0
- package/src/lifecycle-manager.ts +369 -0
- package/src/routes.ts +407 -8
- package/src/sse.ts +26 -0
package/src/db.ts
CHANGED
|
@@ -83,6 +83,11 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
83
83
|
claimed_at INTEGER,
|
|
84
84
|
claim_expires_at INTEGER,
|
|
85
85
|
idempotency_key TEXT,
|
|
86
|
+
delivery_status TEXT NOT NULL DEFAULT 'pending',
|
|
87
|
+
delivery_attempts INTEGER NOT NULL DEFAULT 0,
|
|
88
|
+
queued_at INTEGER,
|
|
89
|
+
max_age_seconds INTEGER,
|
|
90
|
+
resolved_to_agent TEXT,
|
|
86
91
|
payload TEXT NOT NULL DEFAULT '{}',
|
|
87
92
|
meta TEXT NOT NULL DEFAULT '{}',
|
|
88
93
|
read_by TEXT NOT NULL DEFAULT '[]',
|
|
@@ -93,6 +98,48 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
93
98
|
CREATE INDEX IF NOT EXISTS idx_msg_created ON messages(created_at);
|
|
94
99
|
CREATE INDEX IF NOT EXISTS idx_msg_channel ON messages(channel);
|
|
95
100
|
|
|
101
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
102
|
+
namespace TEXT NOT NULL,
|
|
103
|
+
key TEXT NOT NULL,
|
|
104
|
+
value TEXT NOT NULL,
|
|
105
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
106
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
107
|
+
updated_by TEXT,
|
|
108
|
+
PRIMARY KEY (namespace, key)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE TABLE IF NOT EXISTS config_history (
|
|
112
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
113
|
+
namespace TEXT NOT NULL,
|
|
114
|
+
key TEXT NOT NULL,
|
|
115
|
+
value TEXT NOT NULL,
|
|
116
|
+
version INTEGER NOT NULL,
|
|
117
|
+
changed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
118
|
+
changed_by TEXT
|
|
119
|
+
);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_config_history_key ON config_history(namespace, key, version);
|
|
121
|
+
|
|
122
|
+
CREATE TABLE IF NOT EXISTS managed_agent_state (
|
|
123
|
+
policy_name TEXT PRIMARY KEY,
|
|
124
|
+
status TEXT NOT NULL,
|
|
125
|
+
agent_id TEXT,
|
|
126
|
+
orchestrator_id TEXT NOT NULL,
|
|
127
|
+
provider TEXT NOT NULL,
|
|
128
|
+
tmux_session TEXT,
|
|
129
|
+
spawn_request_id TEXT,
|
|
130
|
+
last_spawn_at INTEGER,
|
|
131
|
+
last_stop_at INTEGER,
|
|
132
|
+
healthy_since INTEGER,
|
|
133
|
+
restart_count INTEGER NOT NULL DEFAULT 0,
|
|
134
|
+
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
135
|
+
backoff_until INTEGER,
|
|
136
|
+
last_error TEXT,
|
|
137
|
+
updated_at INTEGER NOT NULL
|
|
138
|
+
);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_managed_agent_state_status ON managed_agent_state(status);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_managed_agent_state_agent ON managed_agent_state(agent_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_managed_agent_state_spawn_request ON managed_agent_state(policy_name, spawn_request_id);
|
|
142
|
+
|
|
96
143
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
97
144
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
145
|
source TEXT NOT NULL,
|
|
@@ -380,11 +427,28 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
380
427
|
if (!colNames.includes("idempotency_key")) {
|
|
381
428
|
db.run("ALTER TABLE messages ADD COLUMN idempotency_key TEXT");
|
|
382
429
|
}
|
|
430
|
+
if (!colNames.includes("delivery_status")) {
|
|
431
|
+
db.run("ALTER TABLE messages ADD COLUMN delivery_status TEXT NOT NULL DEFAULT 'pending'");
|
|
432
|
+
}
|
|
433
|
+
if (!colNames.includes("delivery_attempts")) {
|
|
434
|
+
db.run("ALTER TABLE messages ADD COLUMN delivery_attempts INTEGER NOT NULL DEFAULT 0");
|
|
435
|
+
}
|
|
436
|
+
if (!colNames.includes("queued_at")) {
|
|
437
|
+
db.run("ALTER TABLE messages ADD COLUMN queued_at INTEGER");
|
|
438
|
+
}
|
|
439
|
+
if (!colNames.includes("max_age_seconds")) {
|
|
440
|
+
db.run("ALTER TABLE messages ADD COLUMN max_age_seconds INTEGER");
|
|
441
|
+
}
|
|
442
|
+
if (!colNames.includes("resolved_to_agent")) {
|
|
443
|
+
db.run("ALTER TABLE messages ADD COLUMN resolved_to_agent TEXT");
|
|
444
|
+
}
|
|
383
445
|
db.prepare(
|
|
384
446
|
"UPDATE messages SET claim_expires_at = coalesce(claimed_at, ?) + ? WHERE claimed_by IS NOT NULL AND claim_expires_at IS NULL",
|
|
385
447
|
).run(Date.now(), CLAIM_LEASE_MS);
|
|
386
448
|
db.run("CREATE INDEX IF NOT EXISTS idx_msg_thread ON messages(thread_id)");
|
|
387
449
|
db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_msg_idempotency ON messages(from_agent, idempotency_key) WHERE idempotency_key IS NOT NULL");
|
|
450
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_msg_delivery_status ON messages(delivery_status)");
|
|
451
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_msg_resolved_to_agent ON messages(resolved_to_agent)");
|
|
388
452
|
|
|
389
453
|
const channelBindingsSql = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'channel_bindings'").get() as { sql?: string } | undefined;
|
|
390
454
|
if (channelBindingsSql?.sql?.includes("UNIQUE(channel_id, conversation_key)")) {
|
|
@@ -626,6 +690,11 @@ function rowToMessage(row: any): Message {
|
|
|
626
690
|
claimedAt: row.claimed_at ?? undefined,
|
|
627
691
|
claimExpiresAt: row.claim_expires_at ?? undefined,
|
|
628
692
|
idempotencyKey: row.idempotency_key ?? undefined,
|
|
693
|
+
deliveryStatus: row.delivery_status ?? "pending",
|
|
694
|
+
deliveryAttempts: row.delivery_attempts ?? 0,
|
|
695
|
+
queuedAt: row.queued_at ?? undefined,
|
|
696
|
+
maxAgeSeconds: row.max_age_seconds ?? undefined,
|
|
697
|
+
resolvedToAgent: row.resolved_to_agent ?? undefined,
|
|
629
698
|
payload: parseJson(row.payload ?? "{}", {}),
|
|
630
699
|
meta: parseJson(row.meta, {}),
|
|
631
700
|
readBy: parseJson(row.read_by_agents ?? "[]", []),
|
|
@@ -763,6 +832,7 @@ function bindingTargetToLegacyTarget(target: ChannelRouteTarget): string {
|
|
|
763
832
|
if (target.type === "broadcast") return "broadcast";
|
|
764
833
|
if (target.type === "orchestrator") return `orchestrator:${target.id}`;
|
|
765
834
|
if (target.type === "pool") return `pool:${target.id}`;
|
|
835
|
+
if (target.type === "policy") return `policy:${target.id}`;
|
|
766
836
|
return "";
|
|
767
837
|
}
|
|
768
838
|
|
|
@@ -801,6 +871,11 @@ function channelTargetMatches(target: ChannelRouteTarget, channelId?: string): A
|
|
|
801
871
|
const agent = getAgent(target.id);
|
|
802
872
|
return agent ? [agent] : [];
|
|
803
873
|
}
|
|
874
|
+
if (target.type === "policy") {
|
|
875
|
+
const agentId = runningAgentForPolicy(target.id);
|
|
876
|
+
const agent = agentId ? getAgent(agentId) : null;
|
|
877
|
+
return agent ? [agent] : [];
|
|
878
|
+
}
|
|
804
879
|
const channelCandidates = candidates.filter((agent) => agentCanServeChannel(agent, channelId));
|
|
805
880
|
if (target.type === "pool") return poolSelectorMatches(target.id, channelCandidates);
|
|
806
881
|
if (target.type === "label") return channelCandidates.filter((agent) => agent.label === target.id);
|
|
@@ -867,7 +942,7 @@ function channelTargetHealth(binding: ChannelBinding, now: number = Date.now()):
|
|
|
867
942
|
return { status: "ok", detail: `Pool ${targetLabel}: claimed by ${binding.poolAgentId}`, target, matches: snapshots };
|
|
868
943
|
}
|
|
869
944
|
|
|
870
|
-
if (target.type === "agent" || target.type === "orchestrator") {
|
|
945
|
+
if (target.type === "agent" || target.type === "orchestrator" || target.type === "policy") {
|
|
871
946
|
const agent = matches[0];
|
|
872
947
|
if (!agent) {
|
|
873
948
|
return { status: "error", detail: `Target ${targetLabel} is not registered`, target, matches: [] };
|
|
@@ -2137,6 +2212,56 @@ function findMessageByIdempotencyKey(from: string, key: string): Message | null
|
|
|
2137
2212
|
return row ? rowToMessage(row) : null;
|
|
2138
2213
|
}
|
|
2139
2214
|
|
|
2215
|
+
function policyNameFromTarget(target: string): string | null {
|
|
2216
|
+
if (!target.startsWith("policy:")) return null;
|
|
2217
|
+
const name = target.slice("policy:".length).trim();
|
|
2218
|
+
return name || null;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
function spawnPolicyExists(policyName: string): boolean {
|
|
2222
|
+
const row = db.prepare("SELECT 1 FROM config WHERE namespace = 'spawn-policy' AND key = ?").get(policyName);
|
|
2223
|
+
return Boolean(row);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function runningAgentForPolicy(policyName: string): string | null {
|
|
2227
|
+
const row = db.prepare(`
|
|
2228
|
+
SELECT agent_id
|
|
2229
|
+
FROM managed_agent_state
|
|
2230
|
+
WHERE policy_name = ? AND status = 'running' AND agent_id IS NOT NULL
|
|
2231
|
+
`).get(policyName) as { agent_id?: string } | undefined;
|
|
2232
|
+
if (!row?.agent_id) return null;
|
|
2233
|
+
const agent = getAgent(row.agent_id);
|
|
2234
|
+
if (!agent || agent.status === "offline") return null;
|
|
2235
|
+
return row.agent_id;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
function queueDepthLimit(target: string): number {
|
|
2239
|
+
const row = db.prepare("SELECT value FROM config WHERE namespace = 'system' AND key = 'message-queue'").get() as { value?: string } | undefined;
|
|
2240
|
+
const parsed = row?.value ? parseJson<Record<string, unknown>>(row.value, {}) : {};
|
|
2241
|
+
const perTarget = parsed?.maxDepthPerTarget;
|
|
2242
|
+
if (typeof perTarget === "number" && Number.isSafeInteger(perTarget) && perTarget > 0) return perTarget;
|
|
2243
|
+
const targetLimits = parsed?.targetLimits;
|
|
2244
|
+
if (targetLimits && typeof targetLimits === "object" && !Array.isArray(targetLimits)) {
|
|
2245
|
+
const value = (targetLimits as Record<string, unknown>)[target];
|
|
2246
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
|
|
2247
|
+
}
|
|
2248
|
+
return 100;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
function enforceQueueLimit(target: string): void {
|
|
2252
|
+
const limit = queueDepthLimit(target);
|
|
2253
|
+
db.prepare(`
|
|
2254
|
+
UPDATE messages
|
|
2255
|
+
SET delivery_status = 'failed'
|
|
2256
|
+
WHERE id IN (
|
|
2257
|
+
SELECT id FROM messages
|
|
2258
|
+
WHERE to_target = ? AND delivery_status = 'queued'
|
|
2259
|
+
ORDER BY queued_at DESC, id DESC
|
|
2260
|
+
LIMIT -1 OFFSET ?
|
|
2261
|
+
)
|
|
2262
|
+
`).run(target, limit);
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2140
2265
|
function isDeliveryAgent(agent: AgentCard): boolean {
|
|
2141
2266
|
return agent.status !== "offline" &&
|
|
2142
2267
|
agent.id !== "user" &&
|
|
@@ -2231,12 +2356,35 @@ export function sendMessageWithResult(input: SendMessageInput): { message: Messa
|
|
|
2231
2356
|
}
|
|
2232
2357
|
|
|
2233
2358
|
const insert = db.prepare(`
|
|
2234
|
-
INSERT INTO messages (
|
|
2235
|
-
|
|
2359
|
+
INSERT INTO messages (
|
|
2360
|
+
from_agent, to_target, kind, channel, subject, body, thread_id, reply_to, claimable,
|
|
2361
|
+
idempotency_key, delivery_status, queued_at, max_age_seconds, resolved_to_agent,
|
|
2362
|
+
payload, meta, created_at
|
|
2363
|
+
)
|
|
2364
|
+
VALUES (
|
|
2365
|
+
$from, $to, $kind, $channel, $subject, $body, $threadId, $replyTo, $claimable,
|
|
2366
|
+
$idempotencyKey, $deliveryStatus, $queuedAt, $maxAgeSeconds, $resolvedToAgent,
|
|
2367
|
+
$payload, $meta, $now
|
|
2368
|
+
)
|
|
2236
2369
|
`);
|
|
2237
2370
|
const setSelfThread = db.prepare("UPDATE messages SET thread_id = ? WHERE id = ?");
|
|
2238
2371
|
const claimable = shouldStoreClaimable(input);
|
|
2239
2372
|
const kind = inferMessageKind(input);
|
|
2373
|
+
const policyName = policyNameFromTarget(input.to);
|
|
2374
|
+
let deliveryStatus: Message["deliveryStatus"] = "pending";
|
|
2375
|
+
let queuedAt: number | null = null;
|
|
2376
|
+
let maxAgeSeconds = input.maxAgeSeconds ?? null;
|
|
2377
|
+
let resolvedToAgent: string | null = null;
|
|
2378
|
+
|
|
2379
|
+
if (policyName) {
|
|
2380
|
+
if (!spawnPolicyExists(policyName)) throw new ValidationError(`spawn policy ${policyName} not found`);
|
|
2381
|
+
resolvedToAgent = runningAgentForPolicy(policyName);
|
|
2382
|
+
if (!resolvedToAgent) {
|
|
2383
|
+
deliveryStatus = "queued";
|
|
2384
|
+
queuedAt = now;
|
|
2385
|
+
maxAgeSeconds = maxAgeSeconds ?? 86_400;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2240
2388
|
|
|
2241
2389
|
const id = db.transaction(() => {
|
|
2242
2390
|
const result = insert.run({
|
|
@@ -2250,12 +2398,17 @@ export function sendMessageWithResult(input: SendMessageInput): { message: Messa
|
|
|
2250
2398
|
$replyTo: input.replyTo ?? null,
|
|
2251
2399
|
$claimable: claimable ? 1 : 0,
|
|
2252
2400
|
$idempotencyKey: input.idempotencyKey ?? null,
|
|
2401
|
+
$deliveryStatus: deliveryStatus,
|
|
2402
|
+
$queuedAt: queuedAt,
|
|
2403
|
+
$maxAgeSeconds: maxAgeSeconds,
|
|
2404
|
+
$resolvedToAgent: resolvedToAgent,
|
|
2253
2405
|
$payload: JSON.stringify(input.payload ?? {}),
|
|
2254
2406
|
$meta: JSON.stringify(input.meta ?? {}),
|
|
2255
2407
|
$now: now,
|
|
2256
2408
|
});
|
|
2257
2409
|
const newId = Number(result.lastInsertRowid);
|
|
2258
2410
|
if (threadId === null) setSelfThread.run(newId, newId);
|
|
2411
|
+
if (policyName && deliveryStatus === "queued") enforceQueueLimit(`policy:${policyName}`);
|
|
2259
2412
|
return newId;
|
|
2260
2413
|
})();
|
|
2261
2414
|
|
|
@@ -2375,6 +2528,65 @@ export function getMessage(id: number): Message | null {
|
|
|
2375
2528
|
return row ? rowToMessage(row) : null;
|
|
2376
2529
|
}
|
|
2377
2530
|
|
|
2531
|
+
export function getMessageDeliveryStatus(id: number): Pick<Message, "id" | "to" | "deliveryStatus" | "deliveryAttempts" | "queuedAt" | "maxAgeSeconds" | "resolvedToAgent"> | null {
|
|
2532
|
+
const message = getMessage(id);
|
|
2533
|
+
if (!message) return null;
|
|
2534
|
+
return {
|
|
2535
|
+
id: message.id,
|
|
2536
|
+
to: message.to,
|
|
2537
|
+
deliveryStatus: message.deliveryStatus,
|
|
2538
|
+
deliveryAttempts: message.deliveryAttempts,
|
|
2539
|
+
queuedAt: message.queuedAt,
|
|
2540
|
+
maxAgeSeconds: message.maxAgeSeconds,
|
|
2541
|
+
resolvedToAgent: message.resolvedToAgent,
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
export function listQueuedMessages(target: string, limit = 100): Message[] {
|
|
2546
|
+
const safeLimit = Math.min(Math.max(limit, 1), 500);
|
|
2547
|
+
return (db.prepare(`
|
|
2548
|
+
${MSG_SELECT}
|
|
2549
|
+
WHERE m.to_target = ? AND m.delivery_status = 'queued'
|
|
2550
|
+
ORDER BY m.queued_at ASC, m.id ASC
|
|
2551
|
+
LIMIT ?
|
|
2552
|
+
`).all(target, safeLimit) as any[]).map(rowToMessage);
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
export function resolveQueuedPolicyMessages(policyName: string, agentId: string): Message[] {
|
|
2556
|
+
const target = `policy:${policyName}`;
|
|
2557
|
+
const rows = db.prepare(`
|
|
2558
|
+
SELECT id FROM messages
|
|
2559
|
+
WHERE to_target = ? AND delivery_status = 'queued'
|
|
2560
|
+
ORDER BY queued_at ASC, id ASC
|
|
2561
|
+
`).all(target) as Array<{ id: number }>;
|
|
2562
|
+
if (rows.length === 0) return [];
|
|
2563
|
+
const ids = rows.map((row) => row.id);
|
|
2564
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
2565
|
+
db.prepare(`
|
|
2566
|
+
UPDATE messages
|
|
2567
|
+
SET delivery_status = 'pending',
|
|
2568
|
+
resolved_to_agent = ?,
|
|
2569
|
+
delivery_attempts = delivery_attempts + 1
|
|
2570
|
+
WHERE id IN (${placeholders})
|
|
2571
|
+
`).run(agentId, ...ids);
|
|
2572
|
+
return ids.map((id) => getMessage(id)).filter((message): message is Message => Boolean(message));
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
export function expireQueuedMessages(now: number = Date.now()): Message[] {
|
|
2576
|
+
const rows = db.prepare(`
|
|
2577
|
+
SELECT id FROM messages
|
|
2578
|
+
WHERE delivery_status = 'queued'
|
|
2579
|
+
AND queued_at IS NOT NULL
|
|
2580
|
+
AND coalesce(max_age_seconds, 86400) >= 0
|
|
2581
|
+
AND queued_at + (coalesce(max_age_seconds, 86400) * 1000) <= ?
|
|
2582
|
+
`).all(now) as Array<{ id: number }>;
|
|
2583
|
+
if (rows.length === 0) return [];
|
|
2584
|
+
const ids = rows.map((row) => row.id);
|
|
2585
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
2586
|
+
db.prepare(`UPDATE messages SET delivery_status = 'failed' WHERE id IN (${placeholders})`).run(...ids);
|
|
2587
|
+
return ids.map((id) => getMessage(id)).filter((message): message is Message => Boolean(message));
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2378
2590
|
export function listRecentMessages(limit: number = 100, since?: number, channel?: string): Message[] {
|
|
2379
2591
|
const conditions: string[] = [];
|
|
2380
2592
|
const params: any[] = [];
|
|
@@ -2409,8 +2621,8 @@ export function pollMessages(query: PollQuery): Message[] {
|
|
|
2409
2621
|
// Channel agents also accept legacy bare provider targets (for example
|
|
2410
2622
|
// "telegram") so older clients keep working after canonical IDs became
|
|
2411
2623
|
// provider:account ("telegram:default").
|
|
2412
|
-
const targetClauses = ["to_target = ?", "to_target = 'broadcast'"];
|
|
2413
|
-
params.push(query.for);
|
|
2624
|
+
const targetClauses = ["to_target = ?", "resolved_to_agent = ?", "to_target = 'broadcast'"];
|
|
2625
|
+
params.push(query.for, query.for);
|
|
2414
2626
|
|
|
2415
2627
|
for (const tag of agentTags) {
|
|
2416
2628
|
targetClauses.push("to_target = ?");
|
|
@@ -2429,6 +2641,7 @@ export function pollMessages(query: PollQuery): Message[] {
|
|
|
2429
2641
|
params.push(target);
|
|
2430
2642
|
}
|
|
2431
2643
|
conditions.push(`(${targetClauses.join(" OR ")})`);
|
|
2644
|
+
conditions.push("delivery_status != 'queued' AND delivery_status != 'failed'");
|
|
2432
2645
|
|
|
2433
2646
|
// Hide active claims held by someone else, but let expired claims surface so
|
|
2434
2647
|
// another matching agent can recover stuck work.
|
|
@@ -2468,6 +2681,11 @@ export function markRead(messageId: number, agentId: string): boolean {
|
|
|
2468
2681
|
db.prepare(
|
|
2469
2682
|
"INSERT OR IGNORE INTO message_reads (message_id, agent_id, read_at) VALUES (?, ?, ?)"
|
|
2470
2683
|
).run(messageId, agentId, Date.now());
|
|
2684
|
+
db.prepare(`
|
|
2685
|
+
UPDATE messages
|
|
2686
|
+
SET delivery_status = 'delivered'
|
|
2687
|
+
WHERE id = ? AND (to_target = ? OR resolved_to_agent = ? OR to_target = 'broadcast' OR to_target LIKE 'tag:%' OR to_target LIKE 'cap:%' OR to_target LIKE 'label:%')
|
|
2688
|
+
`).run(messageId, agentId, agentId);
|
|
2471
2689
|
return true;
|
|
2472
2690
|
}
|
|
2473
2691
|
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { expireCommands } from "./commands-db";
|
|
|
8
8
|
import { applyCommandToRecipe } from "./recipe-runner";
|
|
9
9
|
import { emitRelayEvent } from "./events";
|
|
10
10
|
import { reconcileAutomationRuns, runDueAutomations, type AutomationDispatchResult, type AutomationReconcileResult } from "./automations";
|
|
11
|
+
import { getLifecycleManager } from "./lifecycle-manager";
|
|
11
12
|
import { resolve, sep } from "path";
|
|
12
13
|
import {
|
|
13
14
|
REAP_INTERVAL_MS,
|
|
@@ -48,6 +49,7 @@ function startServer(): void {
|
|
|
48
49
|
|
|
49
50
|
assertSafeNetworkConfig(HOST);
|
|
50
51
|
initDb(DB_PATH);
|
|
52
|
+
getLifecycleManager().start();
|
|
51
53
|
|
|
52
54
|
setInterval(() => {
|
|
53
55
|
const released = releaseExpiredClaims();
|