@desplega.ai/agent-swarm 1.88.0 → 1.90.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 +7 -0
- package/openapi.json +41 -1
- package/package.json +3 -2
- package/plugin/skills/composio/SKILL.md +173 -0
- package/plugin/skills/composio-gmail/SKILL.md +83 -0
- package/plugin/skills/composio-google-calendar/SKILL.md +81 -0
- package/plugin/skills/composio-google-docs/SKILL.md +71 -0
- package/src/be/db.ts +353 -2
- package/src/be/migrations/081_metrics.sql +39 -0
- package/src/be/migrations/082_user_audit_fields.sql +120 -0
- package/src/be/modelsdev-cache.json +3413 -1423
- package/src/be/seed-skills/index.ts +7 -0
- package/src/cli.tsx +18 -0
- package/src/commands/runner.ts +153 -22
- package/src/commands/x.ts +118 -0
- package/src/github/handlers.ts +40 -1
- package/src/heartbeat/heartbeat.ts +80 -12
- package/src/http/active-sessions.ts +32 -1
- package/src/http/auth.ts +36 -0
- package/src/http/core.ts +20 -16
- package/src/http/db-query.ts +20 -0
- package/src/http/index.ts +2 -0
- package/src/http/metrics.ts +447 -0
- package/src/http/operator-actor.ts +9 -0
- package/src/http/poll.ts +11 -1
- package/src/http/tasks.ts +6 -1
- package/src/http/workflows.ts +5 -1
- package/src/metrics/version.ts +26 -0
- package/src/prompts/base-prompt.ts +8 -0
- package/src/prompts/session-templates.ts +23 -0
- package/src/providers/opencode-adapter.ts +22 -6
- package/src/server.ts +10 -1
- package/src/tasks/worker-follow-up.ts +19 -1
- package/src/tests/base-prompt.test.ts +35 -0
- package/src/tests/budget-claim-gate.test.ts +26 -0
- package/src/tests/core-auth.test.ts +8 -1
- package/src/tests/events-http.test.ts +6 -2
- package/src/tests/github-handlers-cancel-config.test.ts +262 -0
- package/src/tests/heartbeat-supersede-resume.test.ts +91 -1
- package/src/tests/heartbeat.test.ts +84 -3
- package/src/tests/http-api-integration.test.ts +3 -1
- package/src/tests/metrics-http.test.ts +247 -0
- package/src/tests/opencode-adapter.test.ts +90 -30
- package/src/tests/runner-repo-autostash.test.ts +117 -0
- package/src/tests/runner-requester-profile.test.ts +25 -0
- package/src/tests/runner-skills-refresh.test.ts +1 -1
- package/src/tests/swarm-x-tool.test.ts +90 -0
- package/src/tests/system-default-skills.test.ts +3 -0
- package/src/tests/ui-logs-parser.test.ts +271 -0
- package/src/tests/user-token-rest-auth.test.ts +129 -0
- package/src/tests/workflow-async-v2.test.ts +23 -0
- package/src/tests/x-composio.test.ts +122 -0
- package/src/tools/create-metric.ts +191 -0
- package/src/tools/swarm-x.ts +116 -0
- package/src/tools/tool-config.ts +6 -0
- package/src/types.ts +120 -0
- package/src/utils/request-auth-context.ts +28 -0
- package/src/utils/skills-refresh.ts +2 -2
- package/src/workflows/engine.ts +24 -2
- package/src/workflows/executors/agent-task.ts +2 -0
- package/src/x/composio.ts +295 -0
- package/templates/skills/attio-interaction/SKILL.md +279 -0
- package/templates/skills/attio-interaction/config.json +14 -0
- package/templates/skills/attio-interaction/content.md +272 -0
package/src/be/db.ts
CHANGED
|
@@ -42,6 +42,11 @@ import type {
|
|
|
42
42
|
McpServerScope,
|
|
43
43
|
McpServerTransport,
|
|
44
44
|
McpServerWithInstallInfo,
|
|
45
|
+
Metric,
|
|
46
|
+
MetricDefinition,
|
|
47
|
+
MetricSnapshot,
|
|
48
|
+
MetricSummary,
|
|
49
|
+
MetricVersion,
|
|
45
50
|
Page,
|
|
46
51
|
PageAuthMode,
|
|
47
52
|
PageContentType,
|
|
@@ -90,6 +95,7 @@ import type {
|
|
|
90
95
|
} from "../types";
|
|
91
96
|
import { FollowUpConfigSchema, isTerminalTaskStatus } from "../types";
|
|
92
97
|
import { deriveProviderFromKeyType } from "../utils/credentials";
|
|
98
|
+
import { getCurrentRequestUserId } from "../utils/request-auth-context";
|
|
93
99
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
94
100
|
import { decryptSecret, encryptSecret, getEncryptionKey, resolveEncryptionKey } from "./crypto";
|
|
95
101
|
import { normalizeDate, normalizeDateRequired } from "./date-utils";
|
|
@@ -1272,6 +1278,31 @@ export function getPendingTaskForAgent(agentId: string): AgentTask | null {
|
|
|
1272
1278
|
return null;
|
|
1273
1279
|
}
|
|
1274
1280
|
|
|
1281
|
+
export function assignUnassignedTaskPending(taskId: string, agentId: string): AgentTask | null {
|
|
1282
|
+
const now = new Date().toISOString();
|
|
1283
|
+
const row = getDb()
|
|
1284
|
+
.prepare<AgentTaskRow, [string, string, string]>(
|
|
1285
|
+
`UPDATE agent_tasks SET agentId = ?, status = 'pending', lastUpdatedAt = ?
|
|
1286
|
+
WHERE id = ? AND status = 'unassigned' RETURNING *`,
|
|
1287
|
+
)
|
|
1288
|
+
.get(agentId, now, taskId);
|
|
1289
|
+
|
|
1290
|
+
if (row) {
|
|
1291
|
+
try {
|
|
1292
|
+
createLogEntry({
|
|
1293
|
+
eventType: "task_status_change",
|
|
1294
|
+
agentId,
|
|
1295
|
+
taskId,
|
|
1296
|
+
oldValue: "unassigned",
|
|
1297
|
+
newValue: "pending",
|
|
1298
|
+
metadata: { pendingDispatch: true },
|
|
1299
|
+
});
|
|
1300
|
+
} catch {}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
return row ? rowToAgentTask(row) : null;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1275
1306
|
export function startTask(taskId: string): AgentTask | null {
|
|
1276
1307
|
const oldTask = getTaskById(taskId);
|
|
1277
1308
|
if (!oldTask) return null;
|
|
@@ -2167,6 +2198,34 @@ export function supersedeTask(
|
|
|
2167
2198
|
return row ? rowToAgentTask(row) : null;
|
|
2168
2199
|
}
|
|
2169
2200
|
|
|
2201
|
+
export function backfillSupersedeTaskResumeTaskId(taskId: string, resumeTaskId: string): boolean {
|
|
2202
|
+
const row = getDb()
|
|
2203
|
+
.prepare<{ id: string; metadata: string | null }, [string]>(
|
|
2204
|
+
`SELECT id, metadata
|
|
2205
|
+
FROM agent_log
|
|
2206
|
+
WHERE taskId = ? AND eventType = 'task_superseded'
|
|
2207
|
+
ORDER BY createdAt DESC
|
|
2208
|
+
LIMIT 1`,
|
|
2209
|
+
)
|
|
2210
|
+
.get(taskId);
|
|
2211
|
+
if (!row) return false;
|
|
2212
|
+
|
|
2213
|
+
let metadata: Record<string, unknown> = {};
|
|
2214
|
+
if (row.metadata) {
|
|
2215
|
+
try {
|
|
2216
|
+
metadata = JSON.parse(row.metadata) as Record<string, unknown>;
|
|
2217
|
+
} catch {
|
|
2218
|
+
metadata = {};
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
metadata.resumeTaskId = resumeTaskId;
|
|
2222
|
+
|
|
2223
|
+
const result = getDb()
|
|
2224
|
+
.prepare("UPDATE agent_log SET metadata = ? WHERE id = ?")
|
|
2225
|
+
.run(JSON.stringify(metadata), row.id);
|
|
2226
|
+
return result.changes > 0;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2170
2229
|
/**
|
|
2171
2230
|
* Pause a task that is currently in progress.
|
|
2172
2231
|
* Used during graceful shutdown to allow tasks to resume after container restart.
|
|
@@ -2259,6 +2318,67 @@ export function getPausedTasksForAgent(agentId: string): AgentTask[] {
|
|
|
2259
2318
|
return rows.map(rowToAgentTask);
|
|
2260
2319
|
}
|
|
2261
2320
|
|
|
2321
|
+
export function getOrphanedInProgressTasksForAgent(
|
|
2322
|
+
agentId: string,
|
|
2323
|
+
minAgeSeconds = 60,
|
|
2324
|
+
): AgentTask[] {
|
|
2325
|
+
const cutoff = new Date(Date.now() - minAgeSeconds * 1000).toISOString();
|
|
2326
|
+
const rows = getDb()
|
|
2327
|
+
.prepare<AgentTaskRow, [string, string]>(
|
|
2328
|
+
`SELECT t.* FROM agent_tasks t
|
|
2329
|
+
LEFT JOIN active_sessions s ON s.taskId = t.id
|
|
2330
|
+
WHERE t.agentId = ?
|
|
2331
|
+
AND t.status = 'in_progress'
|
|
2332
|
+
AND t.claudeSessionId IS NULL
|
|
2333
|
+
AND t.lastUpdatedAt < ?
|
|
2334
|
+
AND s.id IS NULL
|
|
2335
|
+
AND t.finishedAt IS NULL
|
|
2336
|
+
ORDER BY t.createdAt ASC, t.rowid ASC`,
|
|
2337
|
+
)
|
|
2338
|
+
.all(agentId, cutoff);
|
|
2339
|
+
return rows.map(rowToAgentTask);
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
export function resetOrphanedInProgressTasksForAgent(
|
|
2343
|
+
agentId: string,
|
|
2344
|
+
minAgeSeconds = 60,
|
|
2345
|
+
): AgentTask[] {
|
|
2346
|
+
const cutoff = new Date(Date.now() - minAgeSeconds * 1000).toISOString();
|
|
2347
|
+
const rows = getDb()
|
|
2348
|
+
.prepare<AgentTaskRow, [string, string]>(
|
|
2349
|
+
`UPDATE agent_tasks
|
|
2350
|
+
SET status = 'pending',
|
|
2351
|
+
lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
2352
|
+
WHERE id IN (
|
|
2353
|
+
SELECT t.id FROM agent_tasks t
|
|
2354
|
+
LEFT JOIN active_sessions s ON s.taskId = t.id
|
|
2355
|
+
WHERE t.agentId = ?
|
|
2356
|
+
AND t.status = 'in_progress'
|
|
2357
|
+
AND t.claudeSessionId IS NULL
|
|
2358
|
+
AND t.lastUpdatedAt < ?
|
|
2359
|
+
AND s.id IS NULL
|
|
2360
|
+
AND t.finishedAt IS NULL
|
|
2361
|
+
)
|
|
2362
|
+
RETURNING *`,
|
|
2363
|
+
)
|
|
2364
|
+
.all(agentId, cutoff);
|
|
2365
|
+
|
|
2366
|
+
for (const row of rows) {
|
|
2367
|
+
try {
|
|
2368
|
+
createLogEntry({
|
|
2369
|
+
eventType: "task_status_change",
|
|
2370
|
+
taskId: row.id,
|
|
2371
|
+
agentId,
|
|
2372
|
+
oldValue: "in_progress",
|
|
2373
|
+
newValue: "pending",
|
|
2374
|
+
metadata: { orphanedInProgressRecovery: true },
|
|
2375
|
+
});
|
|
2376
|
+
} catch {}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
return rows.map(rowToAgentTask);
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2262
2382
|
/**
|
|
2263
2383
|
* Get recently cancelled tasks for an agent.
|
|
2264
2384
|
* Used by hooks to detect task cancellation and stop the worker loop.
|
|
@@ -2866,6 +2986,7 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2866
2986
|
}
|
|
2867
2987
|
}
|
|
2868
2988
|
|
|
2989
|
+
const auditUserId = getCurrentRequestUserId() ?? null;
|
|
2869
2990
|
const row = getDb()
|
|
2870
2991
|
.prepare<AgentTaskRow, (string | number | null)[]>(
|
|
2871
2992
|
`INSERT INTO agent_tasks (
|
|
@@ -2876,8 +2997,8 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2876
2997
|
vcsInstallationId, vcsNodeId,
|
|
2877
2998
|
agentmailInboxId, agentmailMessageId, agentmailThreadId,
|
|
2878
2999
|
mentionMessageId, mentionChannelId, dir, parentTaskId, model, scheduleId,
|
|
2879
|
-
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, contextKey, swarmVersion, createdAt, lastUpdatedAt
|
|
2880
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
3000
|
+
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, contextKey, swarmVersion, createdAt, lastUpdatedAt, created_by, updated_by
|
|
3001
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2881
3002
|
)
|
|
2882
3003
|
.get(
|
|
2883
3004
|
id,
|
|
@@ -2922,6 +3043,8 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2922
3043
|
pkg.version,
|
|
2923
3044
|
now,
|
|
2924
3045
|
now,
|
|
3046
|
+
auditUserId,
|
|
3047
|
+
auditUserId,
|
|
2925
3048
|
);
|
|
2926
3049
|
|
|
2927
3050
|
if (!row) throw new Error("Failed to create task");
|
|
@@ -7212,6 +7335,234 @@ export function getPageVersion(pageId: string, version: number): PageVersion | n
|
|
|
7212
7335
|
return row ? rowToPageVersion(row) : null;
|
|
7213
7336
|
}
|
|
7214
7337
|
|
|
7338
|
+
// ============================================================================
|
|
7339
|
+
// Metrics CRUD + version history
|
|
7340
|
+
// ----------------------------------------------------------------------------
|
|
7341
|
+
// Config-driven metrics mirror Pages: parent table `metrics` holds the current
|
|
7342
|
+
// JSON definition, and `metric_versions` holds pre-update snapshots.
|
|
7343
|
+
// ============================================================================
|
|
7344
|
+
|
|
7345
|
+
type MetricRow = {
|
|
7346
|
+
id: string;
|
|
7347
|
+
agentId: string;
|
|
7348
|
+
slug: string;
|
|
7349
|
+
title: string;
|
|
7350
|
+
description: string | null;
|
|
7351
|
+
definition: string;
|
|
7352
|
+
createdAt: string;
|
|
7353
|
+
updatedAt: string;
|
|
7354
|
+
};
|
|
7355
|
+
|
|
7356
|
+
function rowToMetric(row: MetricRow): Metric {
|
|
7357
|
+
return {
|
|
7358
|
+
id: row.id,
|
|
7359
|
+
agentId: row.agentId,
|
|
7360
|
+
slug: row.slug,
|
|
7361
|
+
title: row.title,
|
|
7362
|
+
description: row.description ?? undefined,
|
|
7363
|
+
definition: JSON.parse(row.definition) as MetricDefinition,
|
|
7364
|
+
createdAt: normalizeDateRequired(row.createdAt),
|
|
7365
|
+
updatedAt: normalizeDateRequired(row.updatedAt),
|
|
7366
|
+
};
|
|
7367
|
+
}
|
|
7368
|
+
|
|
7369
|
+
function rowToMetricSummary(row: MetricRow): MetricSummary {
|
|
7370
|
+
return {
|
|
7371
|
+
id: row.id,
|
|
7372
|
+
agentId: row.agentId,
|
|
7373
|
+
slug: row.slug,
|
|
7374
|
+
title: row.title,
|
|
7375
|
+
description: row.description ?? undefined,
|
|
7376
|
+
createdAt: normalizeDateRequired(row.createdAt),
|
|
7377
|
+
updatedAt: normalizeDateRequired(row.updatedAt),
|
|
7378
|
+
};
|
|
7379
|
+
}
|
|
7380
|
+
|
|
7381
|
+
export function createMetric(data: {
|
|
7382
|
+
agentId: string;
|
|
7383
|
+
slug: string;
|
|
7384
|
+
title: string;
|
|
7385
|
+
description?: string;
|
|
7386
|
+
definition: MetricDefinition;
|
|
7387
|
+
}): Metric {
|
|
7388
|
+
const row = getDb()
|
|
7389
|
+
.prepare<MetricRow, [string, string, string, string | null, string]>(
|
|
7390
|
+
`INSERT INTO metrics (agentId, slug, title, description, definition)
|
|
7391
|
+
VALUES (?, ?, ?, ?, ?) RETURNING *`,
|
|
7392
|
+
)
|
|
7393
|
+
.get(
|
|
7394
|
+
data.agentId,
|
|
7395
|
+
data.slug,
|
|
7396
|
+
data.title,
|
|
7397
|
+
data.description ?? null,
|
|
7398
|
+
JSON.stringify(data.definition),
|
|
7399
|
+
);
|
|
7400
|
+
if (!row) throw new Error("Failed to create metric");
|
|
7401
|
+
return rowToMetric(row);
|
|
7402
|
+
}
|
|
7403
|
+
|
|
7404
|
+
export function getMetric(id: string): Metric | null {
|
|
7405
|
+
const row = getDb().prepare<MetricRow, [string]>("SELECT * FROM metrics WHERE id = ?").get(id);
|
|
7406
|
+
return row ? rowToMetric(row) : null;
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7409
|
+
export function getMetricBySlug(agentId: string, slug: string): Metric | null {
|
|
7410
|
+
const row = getDb()
|
|
7411
|
+
.prepare<MetricRow, [string, string]>("SELECT * FROM metrics WHERE agentId = ? AND slug = ?")
|
|
7412
|
+
.get(agentId, slug);
|
|
7413
|
+
return row ? rowToMetric(row) : null;
|
|
7414
|
+
}
|
|
7415
|
+
|
|
7416
|
+
export function listMetricsByAgent(agentId: string, limit?: number, offset?: number): Metric[];
|
|
7417
|
+
export function listMetricsByAgent(
|
|
7418
|
+
agentId: string,
|
|
7419
|
+
limit: number | undefined,
|
|
7420
|
+
offset: number | undefined,
|
|
7421
|
+
opts: { slim: true },
|
|
7422
|
+
): MetricSummary[];
|
|
7423
|
+
export function listMetricsByAgent(
|
|
7424
|
+
agentId: string,
|
|
7425
|
+
limit = 100,
|
|
7426
|
+
offset = 0,
|
|
7427
|
+
opts?: { slim?: boolean },
|
|
7428
|
+
): Metric[] | MetricSummary[] {
|
|
7429
|
+
const rows = getDb()
|
|
7430
|
+
.prepare<MetricRow, [string, number, number]>(
|
|
7431
|
+
"SELECT * FROM metrics WHERE agentId = ? ORDER BY updatedAt DESC LIMIT ? OFFSET ?",
|
|
7432
|
+
)
|
|
7433
|
+
.all(agentId, limit, offset);
|
|
7434
|
+
return opts?.slim ? rows.map(rowToMetricSummary) : rows.map(rowToMetric);
|
|
7435
|
+
}
|
|
7436
|
+
|
|
7437
|
+
export function listAllMetrics(limit?: number, offset?: number): Metric[];
|
|
7438
|
+
export function listAllMetrics(
|
|
7439
|
+
limit: number | undefined,
|
|
7440
|
+
offset: number | undefined,
|
|
7441
|
+
opts: { slim: true },
|
|
7442
|
+
): MetricSummary[];
|
|
7443
|
+
export function listAllMetrics(
|
|
7444
|
+
limit = 100,
|
|
7445
|
+
offset = 0,
|
|
7446
|
+
opts?: { slim?: boolean },
|
|
7447
|
+
): Metric[] | MetricSummary[] {
|
|
7448
|
+
const rows = getDb()
|
|
7449
|
+
.prepare<MetricRow, [number, number]>(
|
|
7450
|
+
"SELECT * FROM metrics ORDER BY updatedAt DESC LIMIT ? OFFSET ?",
|
|
7451
|
+
)
|
|
7452
|
+
.all(limit, offset);
|
|
7453
|
+
return opts?.slim ? rows.map(rowToMetricSummary) : rows.map(rowToMetric);
|
|
7454
|
+
}
|
|
7455
|
+
|
|
7456
|
+
export function countAllMetrics(): number {
|
|
7457
|
+
const row = getDb().prepare<{ count: number }, []>("SELECT COUNT(*) AS count FROM metrics").get();
|
|
7458
|
+
return row?.count ?? 0;
|
|
7459
|
+
}
|
|
7460
|
+
|
|
7461
|
+
export function countMetricsByAgent(agentId: string): number {
|
|
7462
|
+
const row = getDb()
|
|
7463
|
+
.prepare<{ count: number }, [string]>("SELECT COUNT(*) AS count FROM metrics WHERE agentId = ?")
|
|
7464
|
+
.get(agentId);
|
|
7465
|
+
return row?.count ?? 0;
|
|
7466
|
+
}
|
|
7467
|
+
|
|
7468
|
+
export function updateMetric(
|
|
7469
|
+
id: string,
|
|
7470
|
+
data: {
|
|
7471
|
+
title?: string;
|
|
7472
|
+
description?: string | null;
|
|
7473
|
+
definition?: MetricDefinition;
|
|
7474
|
+
slug?: string;
|
|
7475
|
+
},
|
|
7476
|
+
): Metric | null {
|
|
7477
|
+
const updates: string[] = [];
|
|
7478
|
+
const params: (string | null)[] = [];
|
|
7479
|
+
if (data.title !== undefined) {
|
|
7480
|
+
updates.push("title = ?");
|
|
7481
|
+
params.push(data.title);
|
|
7482
|
+
}
|
|
7483
|
+
if (data.description !== undefined) {
|
|
7484
|
+
updates.push("description = ?");
|
|
7485
|
+
params.push(data.description ?? null);
|
|
7486
|
+
}
|
|
7487
|
+
if (data.definition !== undefined) {
|
|
7488
|
+
updates.push("definition = ?");
|
|
7489
|
+
params.push(JSON.stringify(data.definition));
|
|
7490
|
+
}
|
|
7491
|
+
if (data.slug !== undefined) {
|
|
7492
|
+
updates.push("slug = ?");
|
|
7493
|
+
params.push(data.slug);
|
|
7494
|
+
}
|
|
7495
|
+
if (updates.length === 0) return getMetric(id);
|
|
7496
|
+
updates.push("updatedAt = ?");
|
|
7497
|
+
params.push(new Date().toISOString());
|
|
7498
|
+
params.push(id);
|
|
7499
|
+
const row = getDb()
|
|
7500
|
+
.prepare<MetricRow, (string | null)[]>(
|
|
7501
|
+
`UPDATE metrics SET ${updates.join(", ")} WHERE id = ? RETURNING *`,
|
|
7502
|
+
)
|
|
7503
|
+
.get(...params);
|
|
7504
|
+
return row ? rowToMetric(row) : null;
|
|
7505
|
+
}
|
|
7506
|
+
|
|
7507
|
+
export function deleteMetric(id: string): boolean {
|
|
7508
|
+
const result = getDb().run("DELETE FROM metrics WHERE id = ?", [id]);
|
|
7509
|
+
return result.changes > 0;
|
|
7510
|
+
}
|
|
7511
|
+
|
|
7512
|
+
type MetricVersionRow = {
|
|
7513
|
+
id: string;
|
|
7514
|
+
metricId: string;
|
|
7515
|
+
version: number;
|
|
7516
|
+
snapshot: string;
|
|
7517
|
+
changedByAgentId: string | null;
|
|
7518
|
+
createdAt: string;
|
|
7519
|
+
};
|
|
7520
|
+
|
|
7521
|
+
function rowToMetricVersion(row: MetricVersionRow): MetricVersion {
|
|
7522
|
+
return {
|
|
7523
|
+
id: row.id,
|
|
7524
|
+
metricId: row.metricId,
|
|
7525
|
+
version: row.version,
|
|
7526
|
+
snapshot: JSON.parse(row.snapshot) as MetricSnapshot,
|
|
7527
|
+
changedByAgentId: row.changedByAgentId ?? undefined,
|
|
7528
|
+
createdAt: normalizeDateRequired(row.createdAt),
|
|
7529
|
+
};
|
|
7530
|
+
}
|
|
7531
|
+
|
|
7532
|
+
export function createMetricVersion(data: {
|
|
7533
|
+
metricId: string;
|
|
7534
|
+
version: number;
|
|
7535
|
+
snapshot: MetricSnapshot;
|
|
7536
|
+
changedByAgentId?: string;
|
|
7537
|
+
}): MetricVersion {
|
|
7538
|
+
const row = getDb()
|
|
7539
|
+
.prepare<MetricVersionRow, [string, number, string, string | null]>(
|
|
7540
|
+
`INSERT INTO metric_versions (metricId, version, snapshot, changedByAgentId)
|
|
7541
|
+
VALUES (?, ?, ?, ?) RETURNING *`,
|
|
7542
|
+
)
|
|
7543
|
+
.get(data.metricId, data.version, JSON.stringify(data.snapshot), data.changedByAgentId ?? null);
|
|
7544
|
+
if (!row) throw new Error("Failed to create metric version");
|
|
7545
|
+
return rowToMetricVersion(row);
|
|
7546
|
+
}
|
|
7547
|
+
|
|
7548
|
+
export function getMetricVersions(metricId: string): MetricVersion[] {
|
|
7549
|
+
return getDb()
|
|
7550
|
+
.prepare<MetricVersionRow, [string]>(
|
|
7551
|
+
"SELECT * FROM metric_versions WHERE metricId = ? ORDER BY version DESC",
|
|
7552
|
+
)
|
|
7553
|
+
.all(metricId)
|
|
7554
|
+
.map(rowToMetricVersion);
|
|
7555
|
+
}
|
|
7556
|
+
|
|
7557
|
+
export function getMetricVersion(metricId: string, version: number): MetricVersion | null {
|
|
7558
|
+
const row = getDb()
|
|
7559
|
+
.prepare<MetricVersionRow, [string, number]>(
|
|
7560
|
+
"SELECT * FROM metric_versions WHERE metricId = ? AND version = ?",
|
|
7561
|
+
)
|
|
7562
|
+
.get(metricId, version);
|
|
7563
|
+
return row ? rowToMetricVersion(row) : null;
|
|
7564
|
+
}
|
|
7565
|
+
|
|
7215
7566
|
// ============================================================================
|
|
7216
7567
|
// Prompt Template Operations
|
|
7217
7568
|
// ============================================================================
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- Config-driven metrics. Mirrors Pages: parent table holds the current
|
|
2
|
+
-- definition, metric_versions stores pre-update snapshots.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS metrics (
|
|
5
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
6
|
+
agentId TEXT NOT NULL,
|
|
7
|
+
slug TEXT NOT NULL,
|
|
8
|
+
title TEXT NOT NULL,
|
|
9
|
+
description TEXT,
|
|
10
|
+
definition TEXT NOT NULL,
|
|
11
|
+
createdAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
12
|
+
updatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
13
|
+
UNIQUE (agentId, slug)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_metrics_agentId ON metrics(agentId);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_metrics_updatedAt ON metrics(updatedAt DESC);
|
|
18
|
+
|
|
19
|
+
CREATE TABLE IF NOT EXISTS metric_versions (
|
|
20
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
21
|
+
metricId TEXT NOT NULL REFERENCES metrics(id) ON DELETE CASCADE,
|
|
22
|
+
version INTEGER NOT NULL,
|
|
23
|
+
snapshot TEXT NOT NULL,
|
|
24
|
+
changedByAgentId TEXT,
|
|
25
|
+
createdAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
26
|
+
UNIQUE (metricId, version)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_metric_versions_metricId ON metric_versions(metricId);
|
|
30
|
+
|
|
31
|
+
INSERT OR IGNORE INTO metrics (agentId, slug, title, description, definition)
|
|
32
|
+
VALUES
|
|
33
|
+
(
|
|
34
|
+
'system',
|
|
35
|
+
'swarm-operations-overview',
|
|
36
|
+
'Swarm operations overview',
|
|
37
|
+
'A starter dashboard mixing raw SQL widgets with chart and table visualizations.',
|
|
38
|
+
'{"version":1,"refreshSeconds":60,"layout":{"columns":2},"variables":[{"key":"rangeModifier","label":"Time range","type":"select","defaultValue":"-30 days","options":[{"label":"Last 7 days","value":"-7 days"},{"label":"Last 30 days","value":"-30 days"},{"label":"Last 90 days","value":"-90 days"}]},{"key":"userFilter","label":"Requester user ID or name","type":"text","defaultValue":""},{"key":"agentFilter","label":"Agent ID","type":"text","defaultValue":""}],"widgets":[{"id":"open-tasks","title":"Open tasks","description":"Tasks that are not terminal.","query":{"sql":"SELECT COUNT(*) AS open_tasks FROM agent_tasks WHERE status NOT IN (''completed'', ''failed'', ''cancelled'', ''superseded'')","maxRows":10},"viz":{"type":"stat","value":"open_tasks","format":"integer"}},{"id":"tasks-created-per-day","title":"Tasks created per day","description":"Daily task volume for the selected time range.","query":{"sql":"SELECT date(createdAt) AS day, COUNT(*) AS tasks FROM agent_tasks WHERE createdAt >= datetime(''now'', ?) GROUP BY day ORDER BY day","params":["{{rangeModifier}}"],"maxRows":100},"viz":{"type":"line","x":"day","y":"tasks","format":"integer","columns":[{"key":"day","label":"Day"},{"key":"tasks","label":"Tasks","format":"integer"}]}},{"id":"usage-by-user","title":"Usage by user","description":"Tasks requested and session cost by user for the selected time range, requester filter, and agent filter.","query":{"sql":"SELECT COALESCE(u.name, ''Unassigned'') AS user, COUNT(DISTINCT t.id) AS tasks, ROUND(COALESCE(SUM(sc.totalCostUsd), 0), 4) AS cost_usd FROM agent_tasks t LEFT JOIN users u ON u.id = t.requestedByUserId LEFT JOIN session_costs sc ON sc.taskId = t.id WHERE t.createdAt >= datetime(''now'', ?) AND (? = '''' OR COALESCE(u.id, '''') = ? OR COALESCE(u.name, '''') LIKE ''%'' || ? || ''%'') AND (? = '''' OR COALESCE(t.agentId, '''') = ?) GROUP BY COALESCE(u.name, ''Unassigned'') ORDER BY cost_usd DESC, tasks DESC","params":["{{rangeModifier}}","{{userFilter}}","{{userFilter}}","{{userFilter}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"user","y":"tasks","format":"integer","columns":[{"key":"user","label":"User"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"cost_usd","label":"Cost","format":"currency"}]}},{"id":"task-outcomes-by-day","title":"Task outcomes by day","description":"Completed and failed tasks for the selected time range.","query":{"sql":"SELECT date(finishedAt) AS day, SUM(CASE WHEN status = ''completed'' THEN 1 ELSE 0 END) AS completed, SUM(CASE WHEN status = ''failed'' THEN 1 ELSE 0 END) AS failed FROM agent_tasks WHERE finishedAt IS NOT NULL AND finishedAt >= datetime(''now'', ?) GROUP BY day ORDER BY day","params":["{{rangeModifier}}"],"maxRows":100},"viz":{"type":"multi-line","x":"day","series":["completed","failed"],"format":"integer","columns":[{"key":"day","label":"Day"},{"key":"completed","label":"Completed","format":"integer"},{"key":"failed","label":"Failed","format":"integer"}]}},{"id":"recent-task-outcomes","title":"Recent task outcomes","description":"Task status breakdown for tasks created in the selected time range.","query":{"sql":"SELECT status, COUNT(*) AS tasks FROM agent_tasks WHERE createdAt >= datetime(''now'', ?) GROUP BY status ORDER BY tasks DESC","params":["{{rangeModifier}}"],"maxRows":100},"viz":{"type":"table","columns":[{"key":"status","label":"Status"},{"key":"tasks","label":"Tasks","format":"integer"}]}}]}'
|
|
39
|
+
);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
-- Add nullable user audit columns to mutable/user-facing tables.
|
|
2
|
+
-- Existing rows and system-originated writes remain NULL.
|
|
3
|
+
|
|
4
|
+
ALTER TABLE agents ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
5
|
+
ALTER TABLE agents ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
6
|
+
|
|
7
|
+
ALTER TABLE channels ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
8
|
+
ALTER TABLE channels ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
9
|
+
|
|
10
|
+
ALTER TABLE agent_tasks ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
11
|
+
ALTER TABLE agent_tasks ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
12
|
+
|
|
13
|
+
ALTER TABLE channel_messages ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
14
|
+
ALTER TABLE channel_messages ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
15
|
+
|
|
16
|
+
ALTER TABLE services ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
17
|
+
ALTER TABLE services ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
18
|
+
|
|
19
|
+
ALTER TABLE inbox_messages ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
20
|
+
ALTER TABLE inbox_messages ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
21
|
+
|
|
22
|
+
ALTER TABLE scheduled_tasks ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
23
|
+
ALTER TABLE scheduled_tasks ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
24
|
+
|
|
25
|
+
ALTER TABLE swarm_config ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
26
|
+
ALTER TABLE swarm_config ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
27
|
+
|
|
28
|
+
ALTER TABLE swarm_repos ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
29
|
+
ALTER TABLE swarm_repos ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
30
|
+
|
|
31
|
+
ALTER TABLE agent_memory ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
32
|
+
ALTER TABLE agent_memory ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
33
|
+
|
|
34
|
+
ALTER TABLE agentmail_inbox_mappings ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
35
|
+
ALTER TABLE agentmail_inbox_mappings ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
36
|
+
|
|
37
|
+
ALTER TABLE workflows ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
38
|
+
ALTER TABLE workflows ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
39
|
+
|
|
40
|
+
ALTER TABLE workflow_runs ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
41
|
+
ALTER TABLE workflow_runs ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
42
|
+
|
|
43
|
+
ALTER TABLE workflow_run_steps ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
44
|
+
ALTER TABLE workflow_run_steps ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
45
|
+
|
|
46
|
+
ALTER TABLE workflow_versions ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
47
|
+
ALTER TABLE workflow_versions ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
48
|
+
|
|
49
|
+
ALTER TABLE prompt_templates ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
50
|
+
ALTER TABLE prompt_templates ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
51
|
+
|
|
52
|
+
ALTER TABLE prompt_template_history ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
53
|
+
ALTER TABLE prompt_template_history ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
54
|
+
|
|
55
|
+
ALTER TABLE approval_requests ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
56
|
+
ALTER TABLE approval_requests ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
57
|
+
|
|
58
|
+
ALTER TABLE mcp_servers ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
59
|
+
ALTER TABLE mcp_servers ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
60
|
+
|
|
61
|
+
ALTER TABLE mcp_oauth_tokens ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
62
|
+
ALTER TABLE mcp_oauth_tokens ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
63
|
+
|
|
64
|
+
ALTER TABLE mcp_oauth_pending ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
65
|
+
ALTER TABLE mcp_oauth_pending ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
66
|
+
|
|
67
|
+
ALTER TABLE task_templates ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
68
|
+
ALTER TABLE task_templates ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
69
|
+
|
|
70
|
+
ALTER TABLE pages ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
71
|
+
ALTER TABLE pages ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
72
|
+
|
|
73
|
+
ALTER TABLE page_versions ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
74
|
+
ALTER TABLE page_versions ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
75
|
+
|
|
76
|
+
ALTER TABLE kv_entries ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
77
|
+
ALTER TABLE kv_entries ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
78
|
+
|
|
79
|
+
ALTER TABLE scripts ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
80
|
+
ALTER TABLE scripts ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
81
|
+
|
|
82
|
+
ALTER TABLE script_versions ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
83
|
+
ALTER TABLE script_versions ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
84
|
+
|
|
85
|
+
ALTER TABLE skills ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
86
|
+
ALTER TABLE skills ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
87
|
+
|
|
88
|
+
ALTER TABLE users ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
89
|
+
ALTER TABLE users ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
90
|
+
|
|
91
|
+
ALTER TABLE user_external_ids ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
92
|
+
ALTER TABLE user_external_ids ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
93
|
+
|
|
94
|
+
ALTER TABLE user_tokens ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
95
|
+
ALTER TABLE user_tokens ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
96
|
+
|
|
97
|
+
ALTER TABLE task_attachments ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
98
|
+
ALTER TABLE task_attachments ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
99
|
+
|
|
100
|
+
ALTER TABLE budgets ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
101
|
+
ALTER TABLE budgets ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
102
|
+
|
|
103
|
+
ALTER TABLE pricing ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
104
|
+
ALTER TABLE pricing ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
105
|
+
|
|
106
|
+
ALTER TABLE inbox_item_state ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
107
|
+
ALTER TABLE inbox_item_state ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
108
|
+
|
|
109
|
+
ALTER TABLE metrics ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
110
|
+
ALTER TABLE metrics ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
111
|
+
|
|
112
|
+
ALTER TABLE metric_versions ADD COLUMN created_by TEXT REFERENCES users(id);
|
|
113
|
+
ALTER TABLE metric_versions ADD COLUMN updated_by TEXT REFERENCES users(id);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_agent_tasks_created_by ON agent_tasks(created_by) WHERE created_by IS NOT NULL;
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_agent_tasks_updated_by ON agent_tasks(updated_by) WHERE updated_by IS NOT NULL;
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_created_by ON workflows(created_by) WHERE created_by IS NOT NULL;
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_pages_created_by ON pages(created_by) WHERE created_by IS NOT NULL;
|
|
119
|
+
CREATE INDEX IF NOT EXISTS idx_scripts_created_by ON scripts(created_by) WHERE created_by IS NOT NULL;
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_skills_created_by ON skills(created_by) WHERE created_by IS NOT NULL;
|