agent-relay-server 0.15.1 → 0.16.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/package.json +1 -1
- package/src/automations.ts +17 -17
- package/src/bus-outbox.ts +5 -5
- package/src/bus.ts +1 -1
- package/src/commands-db.ts +5 -5
- package/src/config-store.ts +12 -12
- package/src/db.ts +289 -226
- package/src/insights-db.ts +6 -6
- package/src/lifecycle-manager.ts +3 -3
- package/src/maintenance.ts +25 -6
- package/src/memory-sqlite-broker.ts +12 -12
- package/src/provider-catalog-store.ts +2 -2
- package/src/recipe-db.ts +9 -9
- package/src/security.ts +1 -1
- package/src/token-db.ts +10 -10
package/src/insights-db.ts
CHANGED
|
@@ -77,7 +77,7 @@ export function recordObservation(input: RecordObservationInput): InsightObserva
|
|
|
77
77
|
const now = input.createdAt ?? Date.now();
|
|
78
78
|
|
|
79
79
|
const result = getDb()
|
|
80
|
-
.
|
|
80
|
+
.query(
|
|
81
81
|
`INSERT INTO insights_observations (session_id, agent_id, project, signal, value, outcome, source, created_at)
|
|
82
82
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
83
83
|
)
|
|
@@ -95,7 +95,7 @@ export function recordObservation(input: RecordObservationInput): InsightObserva
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
export function getObservation(id: number): InsightObservation | null {
|
|
98
|
-
const row = getDb().
|
|
98
|
+
const row = getDb().query("SELECT * FROM insights_observations WHERE id = ?").get(id) as ObservationRow | undefined;
|
|
99
99
|
return row ? rowToObservation(row) : null;
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -124,7 +124,7 @@ export function listObservations(query: ListObservationsQuery = {}): InsightObse
|
|
|
124
124
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
125
125
|
const limit = Math.min(Math.max(query.limit ?? 200, 1), 1000);
|
|
126
126
|
const rows = getDb()
|
|
127
|
-
.
|
|
127
|
+
.query(`SELECT * FROM insights_observations ${where} ORDER BY created_at DESC, id DESC LIMIT ?`)
|
|
128
128
|
.all(...args, limit) as ObservationRow[];
|
|
129
129
|
return rows.map(rowToObservation);
|
|
130
130
|
}
|
|
@@ -139,7 +139,7 @@ export function getObservationStats(signal?: string): InsightsStats[] {
|
|
|
139
139
|
const signalFilter = signal ? "WHERE signal = ?" : "";
|
|
140
140
|
const args = signal ? [signal] : [];
|
|
141
141
|
const perProject = getDb()
|
|
142
|
-
.
|
|
142
|
+
.query(
|
|
143
143
|
`SELECT signal, project,
|
|
144
144
|
COUNT(*) AS count,
|
|
145
145
|
AVG(CAST(json_extract(value, '$.ratio') AS REAL)) AS avg_ratio,
|
|
@@ -152,7 +152,7 @@ export function getObservationStats(signal?: string): InsightsStats[] {
|
|
|
152
152
|
.all(...args) as Array<{ signal: string; project: string; count: number; avg_ratio: number | null; last_at: number | null }>;
|
|
153
153
|
|
|
154
154
|
const global = getDb()
|
|
155
|
-
.
|
|
155
|
+
.query(
|
|
156
156
|
`SELECT signal,
|
|
157
157
|
COUNT(*) AS count,
|
|
158
158
|
AVG(CAST(json_extract(value, '$.ratio') AS REAL)) AS avg_ratio,
|
|
@@ -173,7 +173,7 @@ export function getObservationStats(signal?: string): InsightsStats[] {
|
|
|
173
173
|
/** Distinct projects that have produced at least one observation — feeds per-project baselines. */
|
|
174
174
|
export function listObservationProjects(): string[] {
|
|
175
175
|
const rows = getDb()
|
|
176
|
-
.
|
|
176
|
+
.query("SELECT DISTINCT project FROM insights_observations ORDER BY project ASC")
|
|
177
177
|
.all() as Array<{ project: string }>;
|
|
178
178
|
return rows.map((r) => r.project);
|
|
179
179
|
}
|
package/src/lifecycle-manager.ts
CHANGED
|
@@ -472,7 +472,7 @@ export class LifecycleManager {
|
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
private lastActivityAt(agentId: string): number {
|
|
475
|
-
const row = getDb().
|
|
475
|
+
const row = getDb().query(`
|
|
476
476
|
SELECT max(created_at) AS at
|
|
477
477
|
FROM messages
|
|
478
478
|
WHERE from_agent = ? OR to_target = ? OR resolved_to_agent = ?
|
|
@@ -481,12 +481,12 @@ export class LifecycleManager {
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
private hasQueuedMessages(policyName: string): boolean {
|
|
484
|
-
const row = getDb().
|
|
484
|
+
const row = getDb().query("SELECT 1 FROM messages WHERE to_target = ? AND delivery_status = 'queued' LIMIT 1").get(`policy:${policyName}`);
|
|
485
485
|
return Boolean(row);
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
private hasActiveCommand(target: string, type: string): boolean {
|
|
489
|
-
const row = getDb().
|
|
489
|
+
const row = getDb().query("SELECT 1 FROM commands WHERE target = ? AND type = ? AND status IN ('pending', 'accepted', 'running') LIMIT 1").get(target, type);
|
|
490
490
|
return Boolean(row);
|
|
491
491
|
}
|
|
492
492
|
|
package/src/maintenance.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
releaseExpiredClaims,
|
|
27
27
|
releaseExpiredMergeLeases,
|
|
28
28
|
releaseOrphanedTasks,
|
|
29
|
+
runDbMaintenance,
|
|
29
30
|
sendMessage,
|
|
30
31
|
sweepArtifacts,
|
|
31
32
|
updateWorkspaceStatus,
|
|
@@ -56,6 +57,11 @@ const OUTBOX_RETENTION_MS = Number(process.env.AGENT_RELAY_OUTBOX_RETENTION_MS)
|
|
|
56
57
|
const TOKEN_RECORD_RETENTION_SECONDS = Number(process.env.AGENT_RELAY_TOKEN_RECORD_RETENTION_SECONDS) || 7 * 24 * 60 * 60;
|
|
57
58
|
const CONFLICT_SCAN_INTERVAL_MS = Number(process.env.AGENT_RELAY_CONFLICT_SCAN_INTERVAL_MS) || 2 * 60 * 1000;
|
|
58
59
|
const WORKSPACE_RETENTION_MS = Number(process.env.AGENT_RELAY_WORKSPACE_RETENTION_MS) || DAY_MS;
|
|
60
|
+
const DB_MAINTENANCE_INTERVAL_MS = Number(process.env.AGENT_RELAY_DB_MAINTENANCE_INTERVAL_MS) || DAY_MS;
|
|
61
|
+
// VACUUM rewrites the whole file (auto_vacuum is off), so run it sparingly.
|
|
62
|
+
// Default: every 7th db-maintenance pass (~weekly at the default daily interval).
|
|
63
|
+
const DB_VACUUM_EVERY = Number(process.env.AGENT_RELAY_DB_VACUUM_EVERY) || 7;
|
|
64
|
+
let dbMaintenanceRuns = 0;
|
|
59
65
|
const WORKSPACE_REVIEW_TTL_MS = Number(process.env.AGENT_RELAY_WORKSPACE_REVIEW_TTL_MS) || 3 * DAY_MS;
|
|
60
66
|
const WORKSPACE_GC_INTERVAL_MS = Number(process.env.AGENT_RELAY_WORKSPACE_GC_INTERVAL_MS) || 60 * 60 * 1000;
|
|
61
67
|
// Deterministic auto-land (Layer 0): merge clean fast-forwards with no human in
|
|
@@ -305,6 +311,19 @@ const definitions: MaintenanceJobDefinition[] = [
|
|
|
305
311
|
return { prunedMessages: pruneOldMessages(retentionDays * DAY_MS) };
|
|
306
312
|
},
|
|
307
313
|
},
|
|
314
|
+
{
|
|
315
|
+
id: "db-maintenance",
|
|
316
|
+
title: "Database maintenance",
|
|
317
|
+
description: "Refresh planner stats (ANALYZE + optimize), truncate the WAL, and periodically VACUUM to reclaim space.",
|
|
318
|
+
intervalMs: DB_MAINTENANCE_INTERVAL_MS,
|
|
319
|
+
runOnStart: false,
|
|
320
|
+
timeoutMs: 5 * 60 * 1000,
|
|
321
|
+
handler() {
|
|
322
|
+
dbMaintenanceRuns += 1;
|
|
323
|
+
const vacuum = DB_VACUUM_EVERY > 0 && dbMaintenanceRuns % DB_VACUUM_EVERY === 0;
|
|
324
|
+
return runDbMaintenance({ vacuum });
|
|
325
|
+
},
|
|
326
|
+
},
|
|
308
327
|
{
|
|
309
328
|
id: "bus-outbox-prune",
|
|
310
329
|
title: "Bus outbox prune",
|
|
@@ -867,7 +886,7 @@ export function startMaintenanceScheduler(): void {
|
|
|
867
886
|
|
|
868
887
|
export function listMaintenanceJobs(): MaintenanceJob[] {
|
|
869
888
|
ensureMaintenanceJobs();
|
|
870
|
-
const rows = getDb().
|
|
889
|
+
const rows = getDb().query("SELECT * FROM maintenance_jobs ORDER BY title COLLATE NOCASE ASC").all() as MaintenanceJobRow[];
|
|
871
890
|
const now = Date.now();
|
|
872
891
|
return rows.map((row) => rowToJob(row, now));
|
|
873
892
|
}
|
|
@@ -895,7 +914,7 @@ async function runMaintenanceJobsNow(ids: string[]): Promise<MaintenanceJobRun[]
|
|
|
895
914
|
async function runDueMaintenanceJobs(): Promise<void> {
|
|
896
915
|
ensureMaintenanceJobs();
|
|
897
916
|
const now = Date.now();
|
|
898
|
-
const rows = getDb().
|
|
917
|
+
const rows = getDb().query(`
|
|
899
918
|
SELECT id FROM maintenance_jobs
|
|
900
919
|
WHERE enabled = 1
|
|
901
920
|
AND next_run_at IS NOT NULL
|
|
@@ -914,7 +933,7 @@ async function runDueMaintenanceJobs(): Promise<void> {
|
|
|
914
933
|
function ensureMaintenanceJobs(): void {
|
|
915
934
|
const db = getDb();
|
|
916
935
|
const now = Date.now();
|
|
917
|
-
const stmt = db.
|
|
936
|
+
const stmt = db.query(`
|
|
918
937
|
INSERT INTO maintenance_jobs (id, title, description, interval_ms, timeout_ms, enabled, run_on_start, next_run_at, updated_at)
|
|
919
938
|
VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?)
|
|
920
939
|
ON CONFLICT(id) DO UPDATE SET
|
|
@@ -946,7 +965,7 @@ async function runJob(definition: MaintenanceJobDefinition, options: { force?: b
|
|
|
946
965
|
const owner = `${process.pid}-${randomUUID()}`;
|
|
947
966
|
const timeoutMs = definition.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
948
967
|
const leaseUntil = startedAt + timeoutMs + 5_000;
|
|
949
|
-
const claim = db.
|
|
968
|
+
const claim = db.query(`
|
|
950
969
|
UPDATE maintenance_jobs
|
|
951
970
|
SET lease_owner = ?, lease_until = ?, last_status = 'running', updated_at = ?
|
|
952
971
|
WHERE id = ?
|
|
@@ -963,7 +982,7 @@ async function runJob(definition: MaintenanceJobDefinition, options: { force?: b
|
|
|
963
982
|
const result = await withTimeout(Promise.resolve(definition.handler()), timeoutMs);
|
|
964
983
|
const finishedAt = Date.now();
|
|
965
984
|
const durationMs = finishedAt - startedAt;
|
|
966
|
-
db.
|
|
985
|
+
db.query(`
|
|
967
986
|
UPDATE maintenance_jobs
|
|
968
987
|
SET last_run_at = ?, next_run_at = ?, last_duration_ms = ?, last_status = 'succeeded',
|
|
969
988
|
last_error = NULL, last_result = ?, consecutive_failures = 0,
|
|
@@ -975,7 +994,7 @@ async function runJob(definition: MaintenanceJobDefinition, options: { force?: b
|
|
|
975
994
|
const finishedAt = Date.now();
|
|
976
995
|
const durationMs = finishedAt - startedAt;
|
|
977
996
|
const message = error instanceof Error ? error.message : String(error);
|
|
978
|
-
db.
|
|
997
|
+
db.query(`
|
|
979
998
|
UPDATE maintenance_jobs
|
|
980
999
|
SET last_run_at = ?, next_run_at = ?, last_duration_ms = ?, last_status = 'failed',
|
|
981
1000
|
last_error = ?, consecutive_failures = consecutive_failures + 1,
|
|
@@ -86,10 +86,10 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
86
86
|
expiresAt: input.ttlMs === undefined ? undefined : now + input.ttlMs,
|
|
87
87
|
}, { now });
|
|
88
88
|
const contentHash = memoryContentHash(memory);
|
|
89
|
-
const existing = this.db.
|
|
89
|
+
const existing = this.db.query("SELECT * FROM memories WHERE content_hash = ? ORDER BY updated_at DESC LIMIT 1").get(contentHash) as MemoryRow | undefined;
|
|
90
90
|
if (existing) return rowToMemory(existing);
|
|
91
91
|
|
|
92
|
-
this.db.
|
|
92
|
+
this.db.query(`
|
|
93
93
|
INSERT INTO memories (
|
|
94
94
|
id, type, scope, title, content, tags, visibility, sensitivity, confidence, redaction_state,
|
|
95
95
|
relevance_score, source_agent, source_task, created_by, content_hash, metadata, access_count,
|
|
@@ -124,7 +124,7 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
async get(id: string, _ctx: MemoryBrokerContext): Promise<Memory | null> {
|
|
127
|
-
const row = this.db.
|
|
127
|
+
const row = this.db.query("SELECT * FROM memories WHERE id = ?").get(id) as MemoryRow | undefined;
|
|
128
128
|
return row ? rowToMemory(row) : null;
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -156,7 +156,7 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
156
156
|
}, { now: ctx.now });
|
|
157
157
|
const contentHash = memoryContentHash(next);
|
|
158
158
|
|
|
159
|
-
this.db.
|
|
159
|
+
this.db.query(`
|
|
160
160
|
UPDATE memories SET
|
|
161
161
|
title = ?,
|
|
162
162
|
content = ?,
|
|
@@ -191,12 +191,12 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
async delete(id: string, _ctx: MemoryBrokerContext): Promise<void> {
|
|
194
|
-
this.db.
|
|
194
|
+
this.db.query("DELETE FROM memories WHERE id = ?").run(id);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
async stats(ctx: MemoryBrokerContext): Promise<MemoryStats> {
|
|
198
198
|
const memories = filterMemoriesForQuery(
|
|
199
|
-
(this.db.
|
|
199
|
+
(this.db.query("SELECT * FROM memories").all() as MemoryRow[]).map(rowToMemory),
|
|
200
200
|
{},
|
|
201
201
|
ctx,
|
|
202
202
|
);
|
|
@@ -243,12 +243,12 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
243
243
|
|
|
244
244
|
async markInjected(agentId: string, memoryIds: string[], ctx: MemoryBrokerContext): Promise<void> {
|
|
245
245
|
const uniqueIds = [...new Set(memoryIds)];
|
|
246
|
-
const insert = this.db.
|
|
246
|
+
const insert = this.db.query(`
|
|
247
247
|
INSERT INTO agent_active_memories (agent_id, memory_id, loaded_at)
|
|
248
248
|
VALUES (?, ?, ?)
|
|
249
249
|
ON CONFLICT(agent_id, memory_id) DO UPDATE SET loaded_at = excluded.loaded_at
|
|
250
250
|
`);
|
|
251
|
-
const touch = this.db.
|
|
251
|
+
const touch = this.db.query("UPDATE memories SET access_count = access_count + 1, last_accessed_at = ? WHERE id = ?");
|
|
252
252
|
const tx = this.db.transaction(() => {
|
|
253
253
|
for (const memoryId of uniqueIds) {
|
|
254
254
|
insert.run(agentId, memoryId, ctx.now);
|
|
@@ -259,11 +259,11 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
async clearActive(agentId: string, _reason: ActiveMemoryClearReason, _ctx: MemoryBrokerContext): Promise<void> {
|
|
262
|
-
this.db.
|
|
262
|
+
this.db.query("DELETE FROM agent_active_memories WHERE agent_id = ?").run(agentId);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
async listActive(agentId: string, ctx: MemoryBrokerContext): Promise<Memory[]> {
|
|
266
|
-
const rows = this.db.
|
|
266
|
+
const rows = this.db.query(`
|
|
267
267
|
SELECT memories.*
|
|
268
268
|
FROM agent_active_memories
|
|
269
269
|
JOIN memories ON memories.id = agent_active_memories.memory_id
|
|
@@ -274,7 +274,7 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
listActiveMemoryIds(agentId: string): string[] {
|
|
277
|
-
return (this.db.
|
|
277
|
+
return (this.db.query("SELECT memory_id FROM agent_active_memories WHERE agent_id = ? ORDER BY loaded_at DESC").all(agentId) as Array<{ memory_id: string }>).map((row) => row.memory_id);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
private queryRows(query: MemoryQuery): MemoryRow[] {
|
|
@@ -293,7 +293,7 @@ export class SqliteMemoryBroker implements MemoryBroker {
|
|
|
293
293
|
params.push(query.visibility);
|
|
294
294
|
}
|
|
295
295
|
sql += " ORDER BY relevance_score DESC, updated_at DESC";
|
|
296
|
-
return this.db.
|
|
296
|
+
return this.db.query(sql).all(...params) as MemoryRow[];
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
private get db(): Database {
|
|
@@ -36,7 +36,7 @@ interface ProviderModelOverrideInput {
|
|
|
36
36
|
|
|
37
37
|
function listProviderModelOverrides(): ProviderModelOverride[] {
|
|
38
38
|
const rows = getDb()
|
|
39
|
-
.
|
|
39
|
+
.query("SELECT * FROM provider_model_overrides ORDER BY provider ASC, alias ASC")
|
|
40
40
|
.all() as ProviderModelOverrideRow[];
|
|
41
41
|
return rows.map(rowToOverride).filter((entry): entry is ProviderModelOverride => Boolean(entry));
|
|
42
42
|
}
|
|
@@ -44,7 +44,7 @@ function listProviderModelOverrides(): ProviderModelOverride[] {
|
|
|
44
44
|
export function upsertProviderModelOverride(input: ProviderModelOverrideInput): ProviderModelOverride {
|
|
45
45
|
const normalized = normalizeOverrideInput(input);
|
|
46
46
|
const now = Date.now();
|
|
47
|
-
getDb().
|
|
47
|
+
getDb().query(`
|
|
48
48
|
INSERT INTO provider_model_overrides (provider, alias, entry, deleted, updated_at, updated_by)
|
|
49
49
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
50
50
|
ON CONFLICT(provider, alias) DO UPDATE SET
|
package/src/recipe-db.ts
CHANGED
|
@@ -53,7 +53,7 @@ export function validateRecipeArtifactRefs(refs: AttachmentRef[]): void {
|
|
|
53
53
|
export function createRecipeInstance(input: CreateRecipeInstanceInput): RecipeInstance {
|
|
54
54
|
const now = Date.now();
|
|
55
55
|
const id = randomUUID();
|
|
56
|
-
getDb().
|
|
56
|
+
getDb().query(`
|
|
57
57
|
INSERT INTO recipe_instances (id, recipe_name, recipe_source, cwd, orchestrator_id, status, started_by, started_at)
|
|
58
58
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
59
59
|
`).run(id, input.recipeName, input.recipeSource, input.cwd, input.orchestratorId, "starting", input.startedBy, now);
|
|
@@ -81,13 +81,13 @@ export function linkRecipeArtifacts(instanceId: string, refs: AttachmentRef[], c
|
|
|
81
81
|
|
|
82
82
|
export function listRecipeInstances(status?: RecipeInstanceStatus): RecipeInstance[] {
|
|
83
83
|
const rows = status
|
|
84
|
-
? getDb().
|
|
85
|
-
: getDb().
|
|
84
|
+
? getDb().query("SELECT * FROM recipe_instances WHERE status = ? ORDER BY started_at DESC").all(status)
|
|
85
|
+
: getDb().query("SELECT * FROM recipe_instances ORDER BY started_at DESC").all();
|
|
86
86
|
return (rows as RecipeInstanceRow[]).map(rowToRecipeInstance);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export function getRecipeInstance(id: string): RecipeInstance | null {
|
|
90
|
-
const row = getDb().
|
|
90
|
+
const row = getDb().query("SELECT * FROM recipe_instances WHERE id = ?").get(id) as RecipeInstanceRow | undefined;
|
|
91
91
|
return row ? rowToRecipeInstance(row) : null;
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -97,7 +97,7 @@ export function updateRecipeInstance(
|
|
|
97
97
|
): RecipeInstance | null {
|
|
98
98
|
const existing = getRecipeInstance(id);
|
|
99
99
|
if (!existing) return null;
|
|
100
|
-
getDb().
|
|
100
|
+
getDb().query(`
|
|
101
101
|
UPDATE recipe_instances
|
|
102
102
|
SET status = ?, error = ?, stopped_at = ?
|
|
103
103
|
WHERE id = ?
|
|
@@ -111,7 +111,7 @@ export function updateRecipeInstance(
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export function upsertRecipeAgentInstance(input: UpsertRecipeAgentInstanceInput): RecipeAgentInstance {
|
|
114
|
-
getDb().
|
|
114
|
+
getDb().query(`
|
|
115
115
|
INSERT INTO recipe_agent_instances (instance_id, role, agent_id, provider, status, idx)
|
|
116
116
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
117
117
|
ON CONFLICT(instance_id, role, agent_id) DO UPDATE SET
|
|
@@ -128,7 +128,7 @@ export function updateRecipeAgentInstance(
|
|
|
128
128
|
agentId: string,
|
|
129
129
|
status: RecipeAgentStatus,
|
|
130
130
|
): RecipeAgentInstance | null {
|
|
131
|
-
const changed = getDb().
|
|
131
|
+
const changed = getDb().query(`
|
|
132
132
|
UPDATE recipe_agent_instances
|
|
133
133
|
SET status = ?
|
|
134
134
|
WHERE instance_id = ? AND agent_id = ?
|
|
@@ -143,7 +143,7 @@ export function replaceRecipeAgentInstanceAgentId(
|
|
|
143
143
|
nextAgentId: string,
|
|
144
144
|
status: RecipeAgentStatus,
|
|
145
145
|
): RecipeAgentInstance | null {
|
|
146
|
-
const changed = getDb().
|
|
146
|
+
const changed = getDb().query(`
|
|
147
147
|
UPDATE recipe_agent_instances
|
|
148
148
|
SET agent_id = ?, status = ?
|
|
149
149
|
WHERE instance_id = ? AND agent_id = ?
|
|
@@ -153,7 +153,7 @@ export function replaceRecipeAgentInstanceAgentId(
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
function listRecipeAgentInstances(instanceId: string): RecipeAgentInstance[] {
|
|
156
|
-
const rows = getDb().
|
|
156
|
+
const rows = getDb().query(`
|
|
157
157
|
SELECT * FROM recipe_agent_instances
|
|
158
158
|
WHERE instance_id = ?
|
|
159
159
|
ORDER BY role ASC, idx ASC, agent_id ASC
|
package/src/security.ts
CHANGED
|
@@ -453,7 +453,7 @@ function isTokenConstraints(value: unknown): value is TokenConstraints {
|
|
|
453
453
|
|
|
454
454
|
function isTokenRevoked(jti: string): boolean {
|
|
455
455
|
try {
|
|
456
|
-
const row = getDb().
|
|
456
|
+
const row = getDb().query("SELECT revoked_at FROM tokens WHERE jti = ?").get(jti) as { revoked_at?: number | null } | undefined;
|
|
457
457
|
return Boolean(row?.revoked_at);
|
|
458
458
|
} catch {
|
|
459
459
|
return false;
|
package/src/token-db.ts
CHANGED
|
@@ -143,7 +143,7 @@ export function createToken(input: {
|
|
|
143
143
|
jti,
|
|
144
144
|
};
|
|
145
145
|
const token = signComponentToken(payload);
|
|
146
|
-
getDb().
|
|
146
|
+
getDb().query(`
|
|
147
147
|
INSERT INTO tokens (jti, sub, role, scope, constraints, profile_id, issued_at, expires_at, created_by)
|
|
148
148
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
149
149
|
`).run(jti, input.sub, role, JSON.stringify(scope), constraints ? JSON.stringify(constraints) : null, profile?.id ?? null, now, expiresAt ?? null, input.createdBy ?? null);
|
|
@@ -151,17 +151,17 @@ export function createToken(input: {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
export function listTokens(): TokenRecord[] {
|
|
154
|
-
const rows = getDb().
|
|
154
|
+
const rows = getDb().query("SELECT * FROM tokens ORDER BY issued_at DESC").all() as TokenRow[];
|
|
155
155
|
return rows.map(rowToToken);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
export function getToken(jti: string): TokenRecord | null {
|
|
159
|
-
const row = getDb().
|
|
159
|
+
const row = getDb().query("SELECT * FROM tokens WHERE jti = ?").get(jti) as TokenRow | undefined;
|
|
160
160
|
return row ? rowToToken(row) : null;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
export function revokeToken(jti: string): boolean {
|
|
164
|
-
return getDb().
|
|
164
|
+
return getDb().query("UPDATE tokens SET revoked_at = ? WHERE jti = ? AND revoked_at IS NULL").run(Math.floor(Date.now() / 1000), jti).changes > 0;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
export function renewToken(input: {
|
|
@@ -192,7 +192,7 @@ export function pruneExpiredTokenRecords(input: {
|
|
|
192
192
|
const retentionSeconds = input.retentionSeconds ?? 7 * 24 * 60 * 60;
|
|
193
193
|
const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
194
194
|
const cutoff = nowSeconds - retentionSeconds;
|
|
195
|
-
const rows = getDb().
|
|
195
|
+
const rows = getDb().query(`
|
|
196
196
|
DELETE FROM tokens
|
|
197
197
|
WHERE (expires_at IS NOT NULL AND expires_at <= ?)
|
|
198
198
|
OR (revoked_at IS NOT NULL AND revoked_at <= ?)
|
|
@@ -203,13 +203,13 @@ export function pruneExpiredTokenRecords(input: {
|
|
|
203
203
|
|
|
204
204
|
export function listTokenProfiles(): TokenProfile[] {
|
|
205
205
|
ensureBuiltInTokenProfiles();
|
|
206
|
-
const rows = getDb().
|
|
206
|
+
const rows = getDb().query("SELECT * FROM token_profiles ORDER BY built_in DESC, name COLLATE NOCASE ASC").all() as TokenProfileRow[];
|
|
207
207
|
return rows.map(rowToProfile);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
export function getTokenProfile(id: string): TokenProfile | null {
|
|
211
211
|
ensureBuiltInTokenProfiles();
|
|
212
|
-
const row = getDb().
|
|
212
|
+
const row = getDb().query("SELECT * FROM token_profiles WHERE id = ?").get(id) as TokenProfileRow | undefined;
|
|
213
213
|
return row ? rowToProfile(row) : null;
|
|
214
214
|
}
|
|
215
215
|
|
|
@@ -219,7 +219,7 @@ export function upsertTokenProfile(input: CreateTokenProfileInput): TokenProfile
|
|
|
219
219
|
const id = input.id ?? slugify(input.name);
|
|
220
220
|
const existing = getTokenProfile(id);
|
|
221
221
|
if (existing?.builtIn) throw new Error("built-in token profiles cannot be modified");
|
|
222
|
-
getDb().
|
|
222
|
+
getDb().query(`
|
|
223
223
|
INSERT INTO token_profiles (id, name, description, role, scope, constraints, ttl_seconds, built_in, created_at, updated_at, created_by)
|
|
224
224
|
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?)
|
|
225
225
|
ON CONFLICT(id) DO UPDATE SET
|
|
@@ -268,12 +268,12 @@ export function updateTokenProfile(id: string, patch: UpdateTokenProfileInput):
|
|
|
268
268
|
|
|
269
269
|
export function deleteTokenProfile(id: string): boolean {
|
|
270
270
|
ensureBuiltInTokenProfiles();
|
|
271
|
-
return getDb().
|
|
271
|
+
return getDb().query("DELETE FROM token_profiles WHERE id = ? AND built_in = 0").run(id).changes > 0;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
function ensureBuiltInTokenProfiles(): void {
|
|
275
275
|
const now = Date.now();
|
|
276
|
-
const stmt = getDb().
|
|
276
|
+
const stmt = getDb().query(`
|
|
277
277
|
INSERT INTO token_profiles (id, name, description, role, scope, constraints, ttl_seconds, built_in, created_at, updated_at, created_by)
|
|
278
278
|
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)
|
|
279
279
|
ON CONFLICT(id) DO UPDATE SET
|