@desplega.ai/agent-swarm 1.92.2 → 1.94.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 +2 -2
- package/openapi.json +242 -3
- package/package.json +5 -5
- package/src/be/db.ts +152 -11
- package/src/be/memory/boot-reembed.ts +0 -1
- package/src/be/memory/providers/sqlite-store.ts +42 -25
- package/src/be/memory/raters/llm-client.ts +12 -5
- package/src/be/memory/types.ts +3 -0
- package/src/be/migrations/088_script_runs_list_indexes.sql +10 -0
- package/src/be/migrations/089_harness_variant.sql +2 -0
- package/src/be/migrations/090_model_tiers.sql +2 -0
- package/src/be/migrations/091_seed_swarm_operations_metrics.sql +12 -0
- package/src/be/migrations/092_metrics_dashboard_combobox_filters.sql +68 -0
- package/src/be/migrations/093_slack_message_tracking.sql +6 -0
- package/src/be/migrations/runner.ts +52 -0
- package/src/be/modelsdev-cache.json +3264 -1166
- package/src/be/scripts/boot-reembed.ts +74 -0
- package/src/be/scripts/db.ts +19 -3
- package/src/be/seed/index.ts +1 -1
- package/src/be/seed/registry.ts +2 -2
- package/src/be/seed/runner.ts +5 -5
- package/src/be/seed/types.ts +6 -1
- package/src/be/seed-pricing.ts +2 -0
- package/src/be/seed-scripts/catalog/boot-triage.inline.ts +221 -0
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +457 -0
- package/src/be/seed-scripts/catalog/compound-insights.inline.ts +863 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +506 -0
- package/src/be/seed-scripts/index.ts +8 -7
- package/src/be/skill-sync.ts +28 -179
- package/src/commands/runner.ts +197 -10
- package/src/http/api-keys.ts +42 -0
- package/src/http/index.ts +13 -2
- package/src/http/mcp-bridge.ts +1 -1
- package/src/http/memory.ts +23 -24
- package/src/http/metrics.ts +55 -6
- package/src/http/schedules.ts +16 -15
- package/src/http/script-runs.ts +7 -1
- package/src/http/scripts.ts +147 -1
- package/src/http/tasks.ts +17 -6
- package/src/model-tiers.ts +140 -0
- package/src/providers/claude-adapter.ts +33 -1
- package/src/providers/claude-managed-adapter.ts +3 -0
- package/src/providers/claude-managed-models.ts +16 -0
- package/src/providers/codex-adapter.ts +8 -1
- package/src/providers/codex-models.ts +1 -0
- package/src/providers/codex-oauth/auth-json.ts +1 -0
- package/src/providers/harness-version.ts +7 -0
- package/src/providers/opencode-adapter.ts +12 -4
- package/src/providers/pi-mono-adapter.ts +90 -8
- package/src/providers/types.ts +2 -0
- package/src/scheduler/scheduler.ts +22 -34
- package/src/scripts-runtime/egress-secrets.ts +83 -0
- package/src/scripts-runtime/eval-harness.ts +4 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/loader.ts +2 -0
- package/src/server-user.ts +8 -2
- package/src/slack/channel-join.ts +41 -0
- package/src/slack/responses.ts +39 -11
- package/src/slack/watcher.ts +121 -8
- package/src/tests/additive-buffer.test.ts +0 -1
- package/src/tests/agents-list-model-display.test.ts +13 -0
- package/src/tests/api-key-tracking.test.ts +113 -0
- package/src/tests/approval-requests.test.ts +0 -6
- package/src/tests/aws-error-classifier.test.ts +148 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -0
- package/src/tests/claude-managed-setup.test.ts +0 -4
- package/src/tests/codex-pool.test.ts +2 -6
- package/src/tests/context-window.test.ts +7 -0
- package/src/tests/http-api-integration.test.ts +23 -6
- package/src/tests/memory-edges.test.ts +0 -2
- package/src/tests/memory-rate-endpoint.test.ts +0 -2
- package/src/tests/memory-rater-e2e.test.ts +0 -2
- package/src/tests/memory-store.test.ts +19 -1
- package/src/tests/memory.test.ts +51 -0
- package/src/tests/metrics-http.test.ts +137 -3
- package/src/tests/migration-046-budgets.test.ts +33 -0
- package/src/tests/migration-runner-regressions.test.ts +69 -0
- package/src/tests/model-control.test.ts +162 -46
- package/src/tests/opencode-adapter.test.ts +9 -0
- package/src/tests/pi-mono-adapter.test.ts +319 -0
- package/src/tests/providers/pi-cost.test.ts +9 -0
- package/src/tests/reload-config.test.ts +33 -17
- package/src/tests/runner-fallback-output.test.ts +50 -0
- package/src/tests/runner-skills-refresh.test.ts +216 -46
- package/src/tests/script-runs-http.test.ts +7 -1
- package/src/tests/scripts-boot-reembed.test.ts +163 -0
- package/src/tests/scripts-embeddings.test.ts +90 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +129 -0
- package/src/tests/seed-scripts.test.ts +13 -1
- package/src/tests/seed.test.ts +26 -1
- package/src/tests/session-attach.test.ts +6 -6
- package/src/tests/session-costs-model-key-normalize.test.ts +2 -0
- package/src/tests/skill-fs-writer.test.ts +250 -0
- package/src/tests/slack-attachments-block.test.ts +0 -1
- package/src/tests/slack-blocks.test.ts +0 -1
- package/src/tests/slack-channel-join.test.ts +80 -0
- package/src/tests/slack-identity-resolution.test.ts +0 -1
- package/src/tests/slack-watcher.test.ts +66 -0
- package/src/tests/structured-output.test.ts +0 -2
- package/src/tests/use-dismissible-card.test.ts +0 -4
- package/src/tests/workflow-agent-task.test.ts +5 -2
- package/src/tests/workflow-validation-port-routing.test.ts +181 -0
- package/src/tools/memory-get.ts +11 -0
- package/src/tools/memory-search.ts +18 -0
- package/src/tools/schedules/create-schedule.ts +71 -70
- package/src/tools/schedules/update-schedule.ts +43 -31
- package/src/tools/send-task.ts +16 -5
- package/src/tools/slack-post.ts +18 -15
- package/src/tools/slack-read.ts +9 -11
- package/src/tools/slack-reply.ts +18 -15
- package/src/tools/slack-start-thread.ts +17 -14
- package/src/tools/task-action.ts +11 -3
- package/src/types.ts +40 -0
- package/src/utils/aws-error-classifier.ts +97 -0
- package/src/utils/context-window.ts +5 -0
- package/src/utils/credentials.test.ts +68 -0
- package/src/utils/credentials.ts +66 -5
- package/src/utils/pretty-print.ts +25 -10
- package/src/utils/skill-fs-writer.ts +220 -0
- package/src/utils/skills-refresh.ts +123 -40
- package/src/workflows/engine.ts +3 -2
- package/src/workflows/executors/agent-task.ts +3 -1
package/src/be/db.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Database } from "bun:sqlite";
|
|
|
2
2
|
import { parseProviderMeta } from "@/utils/provider-metadata.ts";
|
|
3
3
|
import pkg from "../../package.json";
|
|
4
4
|
import { addEyesReactionOnTaskStart } from "../github/task-reactions";
|
|
5
|
+
import { type ModelTier, parseModelTier } from "../model-tiers";
|
|
5
6
|
import { configureDbResolver } from "../prompts/resolver";
|
|
6
7
|
import type {
|
|
7
8
|
ActiveSession,
|
|
@@ -65,6 +66,7 @@ import type {
|
|
|
65
66
|
ScriptRun,
|
|
66
67
|
ScriptRunJournalEntry,
|
|
67
68
|
ScriptRunKind,
|
|
69
|
+
ScriptRunListItem,
|
|
68
70
|
ScriptRunStatus,
|
|
69
71
|
Service,
|
|
70
72
|
ServiceStatus,
|
|
@@ -999,6 +1001,8 @@ type AgentTaskRow = {
|
|
|
999
1001
|
slackThreadTs: string | null;
|
|
1000
1002
|
slackUserId: string | null;
|
|
1001
1003
|
slackReplySent: number;
|
|
1004
|
+
slackProgressMessageTs: string | null;
|
|
1005
|
+
slackTreeRootMessageTs: string | null;
|
|
1002
1006
|
vcsProvider: string | null;
|
|
1003
1007
|
vcsRepo: string | null;
|
|
1004
1008
|
vcsEventType: string | null;
|
|
@@ -1017,6 +1021,7 @@ type AgentTaskRow = {
|
|
|
1017
1021
|
parentTaskId: string | null;
|
|
1018
1022
|
claudeSessionId: string | null;
|
|
1019
1023
|
model: string | null;
|
|
1024
|
+
modelTier: string | null;
|
|
1020
1025
|
scheduleId: string | null;
|
|
1021
1026
|
workflowRunId: string | null;
|
|
1022
1027
|
workflowRunStepId: string | null;
|
|
@@ -1041,6 +1046,8 @@ type AgentTaskRow = {
|
|
|
1041
1046
|
swarmVersion: string | null;
|
|
1042
1047
|
provider: string | null;
|
|
1043
1048
|
providerMeta: string | null;
|
|
1049
|
+
harnessVariant: string | null;
|
|
1050
|
+
harnessVariantMeta: string | null;
|
|
1044
1051
|
totalCostUsd?: number | null;
|
|
1045
1052
|
};
|
|
1046
1053
|
|
|
@@ -1085,6 +1092,8 @@ function rowToAgentTask(row: AgentTaskRow): AgentTask {
|
|
|
1085
1092
|
slackThreadTs: row.slackThreadTs ?? undefined,
|
|
1086
1093
|
slackUserId: row.slackUserId ?? undefined,
|
|
1087
1094
|
slackReplySent: !!row.slackReplySent,
|
|
1095
|
+
slackProgressMessageTs: row.slackProgressMessageTs ?? undefined,
|
|
1096
|
+
slackTreeRootMessageTs: row.slackTreeRootMessageTs ?? undefined,
|
|
1088
1097
|
vcsProvider: (row.vcsProvider as "github" | "gitlab" | null) ?? undefined,
|
|
1089
1098
|
vcsRepo: row.vcsRepo ?? undefined,
|
|
1090
1099
|
vcsEventType: row.vcsEventType ?? undefined,
|
|
@@ -1102,7 +1111,8 @@ function rowToAgentTask(row: AgentTaskRow): AgentTask {
|
|
|
1102
1111
|
dir: row.dir ?? undefined,
|
|
1103
1112
|
parentTaskId: row.parentTaskId ?? undefined,
|
|
1104
1113
|
claudeSessionId: row.claudeSessionId ?? undefined,
|
|
1105
|
-
model:
|
|
1114
|
+
model: row.model ?? undefined,
|
|
1115
|
+
modelTier: parseModelTier(row.modelTier) ?? undefined,
|
|
1106
1116
|
scheduleId: row.scheduleId ?? undefined,
|
|
1107
1117
|
workflowRunId: row.workflowRunId ?? undefined,
|
|
1108
1118
|
workflowRunStepId: row.workflowRunStepId ?? undefined,
|
|
@@ -1127,6 +1137,8 @@ function rowToAgentTask(row: AgentTaskRow): AgentTask {
|
|
|
1127
1137
|
swarmVersion: row.swarmVersion ?? undefined,
|
|
1128
1138
|
provider: (row.provider as ProviderName | null) ?? undefined,
|
|
1129
1139
|
providerMeta: parseProviderMeta(row.provider as ProviderName | null, row.providerMeta),
|
|
1140
|
+
harnessVariant: row.harnessVariant ?? undefined,
|
|
1141
|
+
harnessVariantMeta: row.harnessVariantMeta ? JSON.parse(row.harnessVariantMeta) : undefined,
|
|
1130
1142
|
totalCostUsd: row.totalCostUsd ?? undefined,
|
|
1131
1143
|
};
|
|
1132
1144
|
}
|
|
@@ -1156,6 +1168,7 @@ function rowToAgentTaskSummary(row: AgentTaskRow): AgentTaskSummary {
|
|
|
1156
1168
|
parentTaskId: t.parentTaskId,
|
|
1157
1169
|
scheduleId: t.scheduleId,
|
|
1158
1170
|
model: t.model,
|
|
1171
|
+
modelTier: t.modelTier,
|
|
1159
1172
|
provider: t.provider,
|
|
1160
1173
|
requestedByUserId: t.requestedByUserId,
|
|
1161
1174
|
progress: t.progress,
|
|
@@ -1358,6 +1371,30 @@ export function markTaskSlackReplySent(taskId: string): void {
|
|
|
1358
1371
|
getDb().run(`UPDATE agent_tasks SET slackReplySent = 1 WHERE id = ?`, [taskId]);
|
|
1359
1372
|
}
|
|
1360
1373
|
|
|
1374
|
+
export function setSlackMessageTracking(
|
|
1375
|
+
taskId: string,
|
|
1376
|
+
fields: {
|
|
1377
|
+
slackProgressMessageTs?: string | null;
|
|
1378
|
+
slackTreeRootMessageTs?: string | null;
|
|
1379
|
+
},
|
|
1380
|
+
): void {
|
|
1381
|
+
const sets: string[] = [];
|
|
1382
|
+
const args: (string | null)[] = [];
|
|
1383
|
+
|
|
1384
|
+
if (Object.hasOwn(fields, "slackProgressMessageTs")) {
|
|
1385
|
+
sets.push("slackProgressMessageTs = ?");
|
|
1386
|
+
args.push(fields.slackProgressMessageTs ?? null);
|
|
1387
|
+
}
|
|
1388
|
+
if (Object.hasOwn(fields, "slackTreeRootMessageTs")) {
|
|
1389
|
+
sets.push("slackTreeRootMessageTs = ?");
|
|
1390
|
+
args.push(fields.slackTreeRootMessageTs ?? null);
|
|
1391
|
+
}
|
|
1392
|
+
if (sets.length === 0) return;
|
|
1393
|
+
|
|
1394
|
+
args.push(taskId);
|
|
1395
|
+
getDb().run(`UPDATE agent_tasks SET ${sets.join(", ")} WHERE id = ?`, args);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1361
1398
|
export function getChildTasks(parentTaskId: string): AgentTask[] {
|
|
1362
1399
|
return getDb()
|
|
1363
1400
|
.prepare<AgentTaskRow, [string]>(
|
|
@@ -1398,6 +1435,8 @@ export function updateTaskClaudeSessionId(
|
|
|
1398
1435
|
provider?: ProviderName,
|
|
1399
1436
|
providerMeta?: Record<string, unknown>,
|
|
1400
1437
|
model?: string,
|
|
1438
|
+
harnessVariant?: string,
|
|
1439
|
+
harnessVariantMeta?: Record<string, unknown>,
|
|
1401
1440
|
): AgentTask | null {
|
|
1402
1441
|
const setClauses = ["claudeSessionId = ?", "lastUpdatedAt = ?"];
|
|
1403
1442
|
const params: (string | null)[] = [claudeSessionId, new Date().toISOString()];
|
|
@@ -1414,6 +1453,14 @@ export function updateTaskClaudeSessionId(
|
|
|
1414
1453
|
setClauses.push("model = ?");
|
|
1415
1454
|
params.push(model);
|
|
1416
1455
|
}
|
|
1456
|
+
if (harnessVariant !== undefined) {
|
|
1457
|
+
setClauses.push("harnessVariant = ?");
|
|
1458
|
+
params.push(harnessVariant);
|
|
1459
|
+
}
|
|
1460
|
+
if (harnessVariantMeta !== undefined) {
|
|
1461
|
+
setClauses.push("harnessVariantMeta = ?");
|
|
1462
|
+
params.push(JSON.stringify(harnessVariantMeta));
|
|
1463
|
+
}
|
|
1417
1464
|
|
|
1418
1465
|
params.push(taskId);
|
|
1419
1466
|
|
|
@@ -2835,6 +2882,7 @@ export interface CreateTaskOptions {
|
|
|
2835
2882
|
dir?: string;
|
|
2836
2883
|
parentTaskId?: string;
|
|
2837
2884
|
model?: string;
|
|
2885
|
+
modelTier?: ModelTier;
|
|
2838
2886
|
scheduleId?: string;
|
|
2839
2887
|
workflowRunId?: string;
|
|
2840
2888
|
workflowRunStepId?: string;
|
|
@@ -3041,9 +3089,9 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
3041
3089
|
vcsProvider, vcsRepo, vcsEventType, vcsNumber, vcsCommentId, vcsAuthor, vcsUrl,
|
|
3042
3090
|
vcsInstallationId, vcsNodeId,
|
|
3043
3091
|
agentmailInboxId, agentmailMessageId, agentmailThreadId,
|
|
3044
|
-
mentionMessageId, mentionChannelId, dir, parentTaskId, model, scheduleId,
|
|
3092
|
+
mentionMessageId, mentionChannelId, dir, parentTaskId, model, modelTier, scheduleId,
|
|
3045
3093
|
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, contextKey, swarmVersion, createdAt, lastUpdatedAt, created_by, updated_by
|
|
3046
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
3094
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
3047
3095
|
)
|
|
3048
3096
|
.get(
|
|
3049
3097
|
id,
|
|
@@ -3078,6 +3126,7 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
3078
3126
|
options?.dir ?? null,
|
|
3079
3127
|
options?.parentTaskId ?? null,
|
|
3080
3128
|
options?.model ?? null,
|
|
3129
|
+
options?.modelTier ?? null,
|
|
3081
3130
|
options?.scheduleId ?? null,
|
|
3082
3131
|
options?.workflowRunId ?? null,
|
|
3083
3132
|
options?.workflowRunStepId ?? null,
|
|
@@ -5251,6 +5300,7 @@ type ScheduledTaskRow = {
|
|
|
5251
5300
|
lastErrorAt: string | null;
|
|
5252
5301
|
lastErrorMessage: string | null;
|
|
5253
5302
|
model: string | null;
|
|
5303
|
+
modelTier: string | null;
|
|
5254
5304
|
scheduleType: string;
|
|
5255
5305
|
createdAt: string;
|
|
5256
5306
|
lastUpdatedAt: string;
|
|
@@ -5291,7 +5341,8 @@ function rowToScheduledTask(row: ScheduledTaskRow): ScheduledTask {
|
|
|
5291
5341
|
consecutiveErrors: row.consecutiveErrors ?? 0,
|
|
5292
5342
|
lastErrorAt: normalizeDate(row.lastErrorAt) ?? undefined,
|
|
5293
5343
|
lastErrorMessage: row.lastErrorMessage ?? undefined,
|
|
5294
|
-
model:
|
|
5344
|
+
model: row.model ?? undefined,
|
|
5345
|
+
modelTier: parseModelTier(row.modelTier) ?? undefined,
|
|
5295
5346
|
scheduleType: row.scheduleType as "recurring" | "one_time",
|
|
5296
5347
|
createdAt: normalizeDateRequired(row.createdAt),
|
|
5297
5348
|
lastUpdatedAt: normalizeDateRequired(row.lastUpdatedAt),
|
|
@@ -5386,6 +5437,7 @@ export interface CreateScheduledTaskData {
|
|
|
5386
5437
|
createdByAgentId?: string;
|
|
5387
5438
|
timezone?: string;
|
|
5388
5439
|
model?: string;
|
|
5440
|
+
modelTier?: ModelTier;
|
|
5389
5441
|
scheduleType?: "recurring" | "one_time";
|
|
5390
5442
|
}
|
|
5391
5443
|
|
|
@@ -5398,8 +5450,8 @@ export function createScheduledTask(data: CreateScheduledTaskData): ScheduledTas
|
|
|
5398
5450
|
`INSERT INTO scheduled_tasks (
|
|
5399
5451
|
id, name, description, cronExpression, intervalMs, taskTemplate,
|
|
5400
5452
|
taskType, tags, priority, targetAgentId, enabled, nextRunAt,
|
|
5401
|
-
createdByAgentId, timezone, model, scheduleType, createdAt, lastUpdatedAt
|
|
5402
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
5453
|
+
createdByAgentId, timezone, model, modelTier, scheduleType, createdAt, lastUpdatedAt
|
|
5454
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
5403
5455
|
)
|
|
5404
5456
|
.get(
|
|
5405
5457
|
id,
|
|
@@ -5417,6 +5469,7 @@ export function createScheduledTask(data: CreateScheduledTaskData): ScheduledTas
|
|
|
5417
5469
|
data.createdByAgentId ?? null,
|
|
5418
5470
|
data.timezone ?? "UTC",
|
|
5419
5471
|
data.model ?? null,
|
|
5472
|
+
data.modelTier ?? null,
|
|
5420
5473
|
data.scheduleType ?? "recurring",
|
|
5421
5474
|
now,
|
|
5422
5475
|
now,
|
|
@@ -5444,6 +5497,7 @@ export interface UpdateScheduledTaskData {
|
|
|
5444
5497
|
lastErrorAt?: string | null;
|
|
5445
5498
|
lastErrorMessage?: string | null;
|
|
5446
5499
|
model?: string | null;
|
|
5500
|
+
modelTier?: ModelTier | null;
|
|
5447
5501
|
scheduleType?: "recurring" | "one_time";
|
|
5448
5502
|
lastUpdatedAt?: string;
|
|
5449
5503
|
}
|
|
@@ -5523,6 +5577,10 @@ export function updateScheduledTask(
|
|
|
5523
5577
|
updates.push("model = ?");
|
|
5524
5578
|
params.push(data.model);
|
|
5525
5579
|
}
|
|
5580
|
+
if (data.modelTier !== undefined) {
|
|
5581
|
+
updates.push("modelTier = ?");
|
|
5582
|
+
params.push(data.modelTier);
|
|
5583
|
+
}
|
|
5526
5584
|
if (data.scheduleType !== undefined) {
|
|
5527
5585
|
updates.push("scheduleType = ?");
|
|
5528
5586
|
params.push(data.scheduleType);
|
|
@@ -10058,6 +10116,28 @@ export function setApiKeyName(
|
|
|
10058
10116
|
return result.changes > 0;
|
|
10059
10117
|
}
|
|
10060
10118
|
|
|
10119
|
+
/**
|
|
10120
|
+
* Clear a stale rate-limit record after a successful use proves the key is healthy.
|
|
10121
|
+
*/
|
|
10122
|
+
export function clearKeyRateLimit(
|
|
10123
|
+
keyType: string,
|
|
10124
|
+
keySuffix: string,
|
|
10125
|
+
scope = "global",
|
|
10126
|
+
scopeId: string | null = null,
|
|
10127
|
+
): boolean {
|
|
10128
|
+
const now = new Date().toISOString();
|
|
10129
|
+
const effectiveScopeId = scopeId ?? "";
|
|
10130
|
+
const result = getDb()
|
|
10131
|
+
.prepare(
|
|
10132
|
+
`UPDATE api_key_status
|
|
10133
|
+
SET status = 'available', rateLimitedUntil = NULL, updatedAt = ?
|
|
10134
|
+
WHERE keyType = ? AND keySuffix = ? AND scope = ? AND scopeId = ?
|
|
10135
|
+
AND status = 'rate_limited'`,
|
|
10136
|
+
)
|
|
10137
|
+
.run(now, keyType, keySuffix, scope, effectiveScopeId);
|
|
10138
|
+
return result.changes > 0;
|
|
10139
|
+
}
|
|
10140
|
+
|
|
10061
10141
|
/**
|
|
10062
10142
|
* Get all key status records for a credential type.
|
|
10063
10143
|
*/
|
|
@@ -11574,6 +11654,22 @@ type ScriptRunRow = {
|
|
|
11574
11654
|
updated_by: string | null;
|
|
11575
11655
|
};
|
|
11576
11656
|
|
|
11657
|
+
type ScriptRunListRow = Pick<
|
|
11658
|
+
ScriptRunRow,
|
|
11659
|
+
| "id"
|
|
11660
|
+
| "agentId"
|
|
11661
|
+
| "scriptName"
|
|
11662
|
+
| "kind"
|
|
11663
|
+
| "status"
|
|
11664
|
+
| "pid"
|
|
11665
|
+
| "startedAt"
|
|
11666
|
+
| "finishedAt"
|
|
11667
|
+
| "error"
|
|
11668
|
+
| "last_heartbeat_at"
|
|
11669
|
+
| "idempotencyKey"
|
|
11670
|
+
| "requestedByUserId"
|
|
11671
|
+
>;
|
|
11672
|
+
|
|
11577
11673
|
function parseJsonColumn(value: string | null): unknown | undefined {
|
|
11578
11674
|
if (value === null) return undefined;
|
|
11579
11675
|
return JSON.parse(value);
|
|
@@ -11599,6 +11695,23 @@ function rowToScriptRun(row: ScriptRunRow): ScriptRun {
|
|
|
11599
11695
|
};
|
|
11600
11696
|
}
|
|
11601
11697
|
|
|
11698
|
+
function rowToScriptRunListItem(row: ScriptRunListRow): ScriptRunListItem {
|
|
11699
|
+
return {
|
|
11700
|
+
id: row.id,
|
|
11701
|
+
agentId: row.agentId,
|
|
11702
|
+
scriptName: row.scriptName ?? undefined,
|
|
11703
|
+
kind: row.kind as ScriptRunKind,
|
|
11704
|
+
status: row.status as ScriptRunStatus,
|
|
11705
|
+
pid: row.pid ?? undefined,
|
|
11706
|
+
startedAt: row.startedAt,
|
|
11707
|
+
finishedAt: row.finishedAt ?? undefined,
|
|
11708
|
+
error: row.error ?? undefined,
|
|
11709
|
+
lastHeartbeatAt: row.last_heartbeat_at ?? undefined,
|
|
11710
|
+
idempotencyKey: row.idempotencyKey ?? undefined,
|
|
11711
|
+
requestedByUserId: row.requestedByUserId ?? undefined,
|
|
11712
|
+
};
|
|
11713
|
+
}
|
|
11714
|
+
|
|
11602
11715
|
export function createScriptRun(data: {
|
|
11603
11716
|
id: string;
|
|
11604
11717
|
agentId: string;
|
|
@@ -11731,9 +11844,10 @@ export function getScriptRunByIdempotencyKey(idempotencyKey: string): ScriptRun
|
|
|
11731
11844
|
export function listScriptRuns(opts?: {
|
|
11732
11845
|
status?: ScriptRunStatus;
|
|
11733
11846
|
agentId?: string;
|
|
11847
|
+
scriptName?: string;
|
|
11734
11848
|
limit?: number;
|
|
11735
11849
|
offset?: number;
|
|
11736
|
-
}):
|
|
11850
|
+
}): ScriptRunListItem[] {
|
|
11737
11851
|
const conditions: string[] = [];
|
|
11738
11852
|
const params: Array<string | number> = [];
|
|
11739
11853
|
if (opts?.status) {
|
|
@@ -11744,20 +11858,43 @@ export function listScriptRuns(opts?: {
|
|
|
11744
11858
|
conditions.push("agentId = ?");
|
|
11745
11859
|
params.push(opts.agentId);
|
|
11746
11860
|
}
|
|
11861
|
+
if (opts?.scriptName) {
|
|
11862
|
+
conditions.push("scriptName = ?");
|
|
11863
|
+
params.push(opts.scriptName);
|
|
11864
|
+
}
|
|
11747
11865
|
|
|
11748
11866
|
const limit = opts?.limit ?? 50;
|
|
11749
11867
|
const offset = opts?.offset ?? 0;
|
|
11750
11868
|
params.push(limit, offset);
|
|
11751
11869
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
11752
11870
|
const rows = getDb()
|
|
11753
|
-
.prepare<
|
|
11754
|
-
`SELECT
|
|
11871
|
+
.prepare<ScriptRunListRow, Array<string | number>>(
|
|
11872
|
+
`SELECT
|
|
11873
|
+
id,
|
|
11874
|
+
agentId,
|
|
11875
|
+
scriptName,
|
|
11876
|
+
kind,
|
|
11877
|
+
status,
|
|
11878
|
+
pid,
|
|
11879
|
+
startedAt,
|
|
11880
|
+
finishedAt,
|
|
11881
|
+
error,
|
|
11882
|
+
last_heartbeat_at,
|
|
11883
|
+
idempotencyKey,
|
|
11884
|
+
requestedByUserId
|
|
11885
|
+
FROM script_runs ${where}
|
|
11886
|
+
ORDER BY startedAt DESC
|
|
11887
|
+
LIMIT ? OFFSET ?`,
|
|
11755
11888
|
)
|
|
11756
11889
|
.all(...params);
|
|
11757
|
-
return rows.map(
|
|
11890
|
+
return rows.map(rowToScriptRunListItem);
|
|
11758
11891
|
}
|
|
11759
11892
|
|
|
11760
|
-
export function countScriptRuns(opts?: {
|
|
11893
|
+
export function countScriptRuns(opts?: {
|
|
11894
|
+
status?: ScriptRunStatus;
|
|
11895
|
+
agentId?: string;
|
|
11896
|
+
scriptName?: string;
|
|
11897
|
+
}): number {
|
|
11761
11898
|
const conditions: string[] = [];
|
|
11762
11899
|
const params: string[] = [];
|
|
11763
11900
|
if (opts?.status) {
|
|
@@ -11768,6 +11905,10 @@ export function countScriptRuns(opts?: { status?: ScriptRunStatus; agentId?: str
|
|
|
11768
11905
|
conditions.push("agentId = ?");
|
|
11769
11906
|
params.push(opts.agentId);
|
|
11770
11907
|
}
|
|
11908
|
+
if (opts?.scriptName) {
|
|
11909
|
+
conditions.push("scriptName = ?");
|
|
11910
|
+
params.push(opts.scriptName);
|
|
11911
|
+
}
|
|
11771
11912
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
11772
11913
|
const row = getDb()
|
|
11773
11914
|
.prepare<{ count: number }, string[]>(`SELECT COUNT(*) AS count FROM script_runs ${where}`)
|
|
@@ -13,7 +13,6 @@ import { getEmbeddingProvider, getMemoryStore } from "./index";
|
|
|
13
13
|
|
|
14
14
|
const VECTOR_BYTES = EMBEDDING_DIMENSIONS * Float32Array.BYTES_PER_ELEMENT;
|
|
15
15
|
const BATCH_SIZE = 20;
|
|
16
|
-
const BACKFILL_KV_KEY = "memory:reembed:backfill_complete";
|
|
17
16
|
|
|
18
17
|
export async function runBootReembed(): Promise<void> {
|
|
19
18
|
const db = getDb();
|
|
@@ -488,29 +488,19 @@ export class SqliteMemoryStore implements MemoryStore {
|
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
491
|
+
private buildListWhereClause(
|
|
492
|
+
agentId: string,
|
|
493
|
+
options: MemoryListOptions,
|
|
494
|
+
): { whereClause: string; params: (Buffer | string | number | null)[] } {
|
|
495
|
+
const { scope = "all", isLead = false, ownerAgentId, source, sourcePath } = options;
|
|
495
496
|
const conditions: string[] = [];
|
|
496
|
-
const params: (string | number)[] = [];
|
|
497
|
+
const params: (Buffer | string | number | null)[] = [];
|
|
497
498
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
conditions.push("scope = 'swarm'");
|
|
504
|
-
} else {
|
|
505
|
-
conditions.push("(agentId = ? OR scope = 'swarm')");
|
|
506
|
-
params.push(agentId);
|
|
507
|
-
}
|
|
508
|
-
} else {
|
|
509
|
-
if (scope === "agent") {
|
|
510
|
-
conditions.push("scope = 'agent'");
|
|
511
|
-
} else if (scope === "swarm") {
|
|
512
|
-
conditions.push("scope = 'swarm'");
|
|
513
|
-
}
|
|
499
|
+
this.addScopeConditions(conditions, params, agentId, scope, isLead);
|
|
500
|
+
|
|
501
|
+
if (ownerAgentId) {
|
|
502
|
+
conditions.push("agentId = ?");
|
|
503
|
+
params.push(ownerAgentId);
|
|
514
504
|
}
|
|
515
505
|
|
|
516
506
|
if (source) {
|
|
@@ -518,18 +508,45 @@ export class SqliteMemoryStore implements MemoryStore {
|
|
|
518
508
|
params.push(source);
|
|
519
509
|
}
|
|
520
510
|
|
|
521
|
-
const
|
|
522
|
-
|
|
511
|
+
const sourcePathNeedle = sourcePath?.trim().toLowerCase();
|
|
512
|
+
if (sourcePathNeedle) {
|
|
513
|
+
conditions.push("instr(lower(coalesce(sourcePath, '')), ?) > 0");
|
|
514
|
+
params.push(sourcePathNeedle);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
whereClause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
|
|
519
|
+
params,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
list(agentId: string, options: MemoryListOptions = {}): AgentMemory[] {
|
|
524
|
+
const { limit = 20, offset = 0 } = options;
|
|
525
|
+
const db = getDb();
|
|
526
|
+
const { whereClause, params } = this.buildListWhereClause(agentId, options);
|
|
527
|
+
const queryParams = [...params, limit, offset];
|
|
523
528
|
|
|
524
529
|
const rows = db
|
|
525
|
-
.prepare<AgentMemoryRow, (string | number)[]>(
|
|
530
|
+
.prepare<AgentMemoryRow, (Buffer | string | number | null)[]>(
|
|
526
531
|
`SELECT * FROM agent_memory ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
527
532
|
)
|
|
528
|
-
.all(...
|
|
533
|
+
.all(...queryParams);
|
|
529
534
|
|
|
530
535
|
return rows.map(rowToAgentMemory);
|
|
531
536
|
}
|
|
532
537
|
|
|
538
|
+
count(agentId: string, options: MemoryListOptions = {}): number {
|
|
539
|
+
const db = getDb();
|
|
540
|
+
const { whereClause, params } = this.buildListWhereClause(agentId, options);
|
|
541
|
+
const row = db
|
|
542
|
+
.prepare<{ count: number }, (Buffer | string | number | null)[]>(
|
|
543
|
+
`SELECT COUNT(*) AS count FROM agent_memory ${whereClause}`,
|
|
544
|
+
)
|
|
545
|
+
.get(...params);
|
|
546
|
+
|
|
547
|
+
return row?.count ?? 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
533
550
|
isSourceProtected(source: AgentMemorySource): boolean {
|
|
534
551
|
return PROTECTED_SOURCES.has(source);
|
|
535
552
|
}
|
|
@@ -75,6 +75,13 @@ AGENT RESPONSE / SUMMARY:
|
|
|
75
75
|
|
|
76
76
|
Score 0..1.`;
|
|
77
77
|
|
|
78
|
+
const PLACEHOLDER_PREFIX = "$";
|
|
79
|
+
const QUERY_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{query}`;
|
|
80
|
+
const MEMORY_ID_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryId}`;
|
|
81
|
+
const MEMORY_NAME_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryName}`;
|
|
82
|
+
const MEMORY_CONTENT_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{memoryContent}`;
|
|
83
|
+
const RESPONSE_PLACEHOLDER = `${PLACEHOLDER_PREFIX}{response}`;
|
|
84
|
+
|
|
78
85
|
/**
|
|
79
86
|
* `claude -p --output-format json` returns a JSON envelope of the shape
|
|
80
87
|
* `{ result: string, ... }`. We parse the envelope, then JSON-parse the
|
|
@@ -83,11 +90,11 @@ Score 0..1.`;
|
|
|
83
90
|
type ClaudeCliEnvelope = { result?: unknown };
|
|
84
91
|
|
|
85
92
|
function buildPrompt(input: LlmRaterInput): string {
|
|
86
|
-
return PROMPT_TEMPLATE.replace(
|
|
87
|
-
.replace(
|
|
88
|
-
.replace(
|
|
89
|
-
.replace(
|
|
90
|
-
.replace(
|
|
93
|
+
return PROMPT_TEMPLATE.replace(QUERY_PLACEHOLDER, input.query)
|
|
94
|
+
.replace(MEMORY_ID_PLACEHOLDER, input.memory.id)
|
|
95
|
+
.replace(MEMORY_NAME_PLACEHOLDER, input.memory.name)
|
|
96
|
+
.replace(MEMORY_CONTENT_PLACEHOLDER, input.memory.content)
|
|
97
|
+
.replace(RESPONSE_PLACEHOLDER, input.response);
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
function parseScoreAndReasoning(raw: unknown): LlmRaterResult | null {
|
package/src/be/memory/types.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface MemoryStore {
|
|
|
22
22
|
peek(id: string): AgentMemory | null;
|
|
23
23
|
search(embedding: Float32Array, agentId: string, options: MemorySearchOptions): MemoryCandidate[];
|
|
24
24
|
list(agentId: string, options: MemoryListOptions): AgentMemory[];
|
|
25
|
+
count(agentId: string, options: MemoryListOptions): number;
|
|
25
26
|
isSourceProtected(source: AgentMemorySource): boolean;
|
|
26
27
|
listForCuration(
|
|
27
28
|
agentId?: string,
|
|
@@ -80,7 +81,9 @@ export interface MemoryListOptions {
|
|
|
80
81
|
limit?: number;
|
|
81
82
|
offset?: number;
|
|
82
83
|
isLead?: boolean;
|
|
84
|
+
ownerAgentId?: string;
|
|
83
85
|
source?: AgentMemorySource;
|
|
86
|
+
sourcePath?: string;
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
export interface MemoryStats {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Keep script run list pages fast as historical rows accumulate.
|
|
2
|
+
|
|
3
|
+
CREATE INDEX IF NOT EXISTS idx_script_runs_startedAt
|
|
4
|
+
ON script_runs(startedAt DESC);
|
|
5
|
+
|
|
6
|
+
CREATE INDEX IF NOT EXISTS idx_script_runs_status_startedAt
|
|
7
|
+
ON script_runs(status, startedAt DESC);
|
|
8
|
+
|
|
9
|
+
CREATE INDEX IF NOT EXISTS idx_script_runs_agentId_startedAt
|
|
10
|
+
ON script_runs(agentId, startedAt DESC);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Keep the seeded system dashboard aligned with the production swarm dashboard.
|
|
2
|
+
-- Migration 081 created the starter row; this forward migration updates both
|
|
3
|
+
-- fresh installs and existing databases without rewriting the applied seed.
|
|
4
|
+
|
|
5
|
+
UPDATE metrics
|
|
6
|
+
SET
|
|
7
|
+
title = 'Swarm operations overview',
|
|
8
|
+
description = 'A starter dashboard mixing raw SQL widgets with chart and table visualizations.',
|
|
9
|
+
definition = '{"version":1,"widgets":[{"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","columns":[{"key":"day","label":"Day"},{"key":"tasks","label":"Tasks","format":"integer"}],"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","columns":[{"key":"user","label":"User"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"cost_usd","label":"Cost","format":"currency"}],"format":"integer"}},{"id":"usage-by-model","title":"Usage by model","description":"Tasks, sessions, tokens, and total cost by model for the selected time range and agent filter.","query":{"sql":"SELECT model, COUNT(DISTINCT taskId) AS tasks, COUNT(*) AS sessions, SUM(inputTokens + outputTokens) AS tokens, ROUND(SUM(totalCostUsd), 2) AS cost_usd FROM session_costs WHERE createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) GROUP BY model ORDER BY cost_usd DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"cost_usd","columns":[{"key":"model","label":"Model"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"sessions","label":"Sessions","format":"integer"},{"key":"tokens","label":"Tokens","format":"integer"},{"key":"cost_usd","label":"Cost","format":"currency"}],"format":"currency"}},{"id":"avg-cost-per-task-by-model","title":"Avg cost per task by model","description":"Average session cost per task, grouped by model, for the selected time range and agent filter.","query":{"sql":"SELECT model, COUNT(DISTINCT taskId) AS tasks, ROUND(SUM(totalCostUsd) * 1.0 / NULLIF(COUNT(DISTINCT taskId), 0), 4) AS avg_cost_per_task FROM session_costs WHERE createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) GROUP BY model ORDER BY avg_cost_per_task DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"avg_cost_per_task","columns":[{"key":"model","label":"Model"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"avg_cost_per_task","label":"Avg cost / task","format":"currency"}],"format":"currency"}},{"id":"avg-task-time-by-model","title":"Avg task time by model","description":"Average wall-clock task duration (minutes) by model for finished tasks in the selected time range and agent filter.","query":{"sql":"SELECT model, COUNT(*) AS tasks, ROUND(AVG((julianday(finishedAt) - julianday(createdAt)) * 1440), 1) AS avg_minutes FROM agent_tasks WHERE finishedAt IS NOT NULL AND model IS NOT NULL AND createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) GROUP BY model ORDER BY tasks DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"avg_minutes","columns":[{"key":"model","label":"Model"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"avg_minutes","label":"Avg time","format":"duration"}],"format":"duration"}},{"id":"cost-per-minute-by-model","title":"Cost per minute by model","description":"Total session cost divided by active session minutes, grouped by model. Active minutes = sum of session durations.","query":{"sql":"SELECT model, ROUND(SUM(durationMs) / 60000.0, 1) AS active_minutes, ROUND(SUM(totalCostUsd), 2) AS cost_usd, ROUND(SUM(totalCostUsd) / NULLIF(SUM(durationMs) / 60000.0, 0), 4) AS cost_per_minute FROM session_costs WHERE createdAt >= datetime(''now'', ?) AND (? = '''' OR agentId = ?) AND durationMs > 0 GROUP BY model ORDER BY cost_per_minute DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"model","y":"cost_per_minute","columns":[{"key":"model","label":"Model"},{"key":"active_minutes","label":"Active minutes","format":"number"},{"key":"cost_usd","label":"Total cost","format":"currency"},{"key":"cost_per_minute","label":"Cost / min","format":"currency"}],"format":"currency"}},{"id":"cost-per-minute-by-agent","title":"Cost per minute by agent","description":"Total session cost divided by active session minutes, grouped by agent.","query":{"sql":"SELECT COALESCE(a.name, sc.agentId, ''unknown'') AS agent, ROUND(SUM(sc.durationMs) / 60000.0, 1) AS active_minutes, ROUND(SUM(sc.totalCostUsd), 2) AS cost_usd, ROUND(SUM(sc.totalCostUsd) / NULLIF(SUM(sc.durationMs) / 60000.0, 0), 4) AS cost_per_minute FROM session_costs sc LEFT JOIN agents a ON a.id = sc.agentId WHERE sc.createdAt >= datetime(''now'', ?) AND (? = '''' OR sc.agentId = ?) AND sc.durationMs > 0 GROUP BY agent ORDER BY cost_per_minute DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"bar","x":"agent","y":"cost_per_minute","columns":[{"key":"agent","label":"Agent"},{"key":"active_minutes","label":"Active minutes","format":"number"},{"key":"cost_usd","label":"Total cost","format":"currency"},{"key":"cost_per_minute","label":"Cost / min","format":"currency"}],"format":"currency"}},{"id":"agent-performance","title":"Tasks, avg time & cost by agent","description":"Finished tasks per agent with average wall-clock duration and average cost per task for the selected time range.","query":{"sql":"SELECT COALESCE(a.name, t.agentId, ''unassigned'') AS agent, COUNT(*) AS tasks, ROUND(AVG((julianday(t.finishedAt) - julianday(t.createdAt)) * 1440), 1) AS avg_minutes, ROUND(COALESCE(SUM(sc.cost), 0), 2) AS cost_usd, ROUND(COALESCE(SUM(sc.cost), 0) / COUNT(*), 4) AS avg_cost_per_task, ROUND(COALESCE(SUM(sc.cost), 0) / NULLIF(SUM(sc.dur) / 60000.0, 0), 4) AS cost_per_minute FROM agent_tasks t LEFT JOIN agents a ON a.id = t.agentId LEFT JOIN (SELECT taskId, SUM(totalCostUsd) AS cost, SUM(durationMs) AS dur FROM session_costs GROUP BY taskId) sc ON sc.taskId = t.id WHERE t.finishedAt IS NOT NULL AND t.createdAt >= datetime(''now'', ?) AND (? = '''' OR COALESCE(t.agentId, '''') = ?) GROUP BY agent ORDER BY tasks DESC","params":["{{rangeModifier}}","{{agentFilter}}","{{agentFilter}}"],"maxRows":100},"viz":{"type":"table","columns":[{"key":"agent","label":"Agent"},{"key":"tasks","label":"Tasks","format":"integer"},{"key":"avg_minutes","label":"Avg time","format":"duration"},{"key":"avg_cost_per_task","label":"Avg cost / task","format":"currency"},{"key":"cost_usd","label":"Total cost","format":"currency"},{"key":"cost_per_minute","label":"Cost / min","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"],"columns":[{"key":"day","label":"Day"},{"key":"completed","label":"Completed","format":"integer"},{"key":"failed","label":"Failed","format":"integer"}],"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"}]}}],"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":""}],"layout":{"columns":3},"refreshSeconds":60}',
|
|
10
|
+
updatedAt = CURRENT_TIMESTAMP
|
|
11
|
+
WHERE agentId = 'system'
|
|
12
|
+
AND slug = 'swarm-operations-overview';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
-- Convert the seeded system dashboard user/agent filters from free-text inputs
|
|
2
|
+
-- to dynamic select variables. The UI renders select variables with resolved
|
|
3
|
+
-- options as searchable comboboxes.
|
|
4
|
+
|
|
5
|
+
UPDATE metrics
|
|
6
|
+
SET
|
|
7
|
+
definition = json_set(
|
|
8
|
+
replace(
|
|
9
|
+
replace(
|
|
10
|
+
replace(
|
|
11
|
+
replace(
|
|
12
|
+
replace(
|
|
13
|
+
definition,
|
|
14
|
+
'? = '''' OR COALESCE(u.id, '''') = ? OR COALESCE(u.name, '''') LIKE ''%'' || ? || ''%''',
|
|
15
|
+
'? = ''all'' OR COALESCE(u.id, '''') = ? OR COALESCE(u.name, '''') LIKE ''%'' || ? || ''%'''
|
|
16
|
+
),
|
|
17
|
+
'? = '''' OR agentId = ?',
|
|
18
|
+
'? = ''all'' OR agentId = ?'
|
|
19
|
+
),
|
|
20
|
+
'? = '''' OR sc.agentId = ?',
|
|
21
|
+
'? = ''all'' OR sc.agentId = ?'
|
|
22
|
+
),
|
|
23
|
+
'? = '''' OR COALESCE(t.agentId, '''') = ?',
|
|
24
|
+
'? = ''all'' OR COALESCE(t.agentId, '''') = ?'
|
|
25
|
+
),
|
|
26
|
+
'? = '''' OR COALESCE(agentId, '''') = ?',
|
|
27
|
+
'? = ''all'' OR COALESCE(agentId, '''') = ?'
|
|
28
|
+
),
|
|
29
|
+
'$.variables',
|
|
30
|
+
json('[
|
|
31
|
+
{
|
|
32
|
+
"key": "rangeModifier",
|
|
33
|
+
"label": "Time range",
|
|
34
|
+
"type": "select",
|
|
35
|
+
"defaultValue": "-30 days",
|
|
36
|
+
"options": [
|
|
37
|
+
{ "label": "Last 7 days", "value": "-7 days" },
|
|
38
|
+
{ "label": "Last 30 days", "value": "-30 days" },
|
|
39
|
+
{ "label": "Last 90 days", "value": "-90 days" }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"key": "userFilter",
|
|
44
|
+
"label": "Requester",
|
|
45
|
+
"type": "select",
|
|
46
|
+
"defaultValue": "all",
|
|
47
|
+
"optionsQuery": {
|
|
48
|
+
"sql": "SELECT id, label FROM (SELECT ''all'' AS id, ''All requesters'' AS label, 0 AS sort_key UNION ALL SELECT id, COALESCE(NULLIF(name, ''''), email, id) AS label, 1 AS sort_key FROM users WHERE id IS NOT NULL) ORDER BY sort_key, label COLLATE NOCASE",
|
|
49
|
+
"valueKey": "id",
|
|
50
|
+
"labelKey": "label"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"key": "agentFilter",
|
|
55
|
+
"label": "Agent",
|
|
56
|
+
"type": "select",
|
|
57
|
+
"defaultValue": "all",
|
|
58
|
+
"optionsQuery": {
|
|
59
|
+
"sql": "SELECT id, label FROM (SELECT ''all'' AS id, ''All agents'' AS label, 0 AS sort_key UNION ALL SELECT id, COALESCE(NULLIF(name, ''''), id) AS label, 1 AS sort_key FROM agents WHERE id IS NOT NULL) ORDER BY sort_key, label COLLATE NOCASE",
|
|
60
|
+
"valueKey": "id",
|
|
61
|
+
"labelKey": "label"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
]')
|
|
65
|
+
),
|
|
66
|
+
updatedAt = CURRENT_TIMESTAMP
|
|
67
|
+
WHERE agentId = 'system'
|
|
68
|
+
AND slug = 'swarm-operations-overview';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Persist Slack message timestamps used by the watcher to update progress in place.
|
|
2
|
+
-- Without this, a server restart drops process-local maps and the next watcher
|
|
3
|
+
-- tick posts a duplicate progress/tree message in the same Slack thread.
|
|
4
|
+
|
|
5
|
+
ALTER TABLE agent_tasks ADD COLUMN slackProgressMessageTs TEXT;
|
|
6
|
+
ALTER TABLE agent_tasks ADD COLUMN slackTreeRootMessageTs TEXT;
|