@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/http/index.ts
CHANGED
|
@@ -458,10 +458,12 @@ try {
|
|
|
458
458
|
// Seed the built-in entity catalog (scripts today; more kinds later) so
|
|
459
459
|
// `script-search` & co. return useful hits from a fresh DB. Idempotent and
|
|
460
460
|
// version-aware: a pristine entity updates when its source changes, a
|
|
461
|
-
// user-modified one is preserved.
|
|
461
|
+
// user-modified one is preserved. Script embeddings are deferred to a
|
|
462
|
+
// post-listen backfill so boot doesn't block on embedding provider calls.
|
|
463
|
+
// See src/be/seed for the framework.
|
|
462
464
|
try {
|
|
463
465
|
const { runAllSeeders } = await import("../be/seed");
|
|
464
|
-
await runAllSeeders();
|
|
466
|
+
await runAllSeeders({ scriptEmbeddingMode: "skip" });
|
|
465
467
|
} catch (err) {
|
|
466
468
|
console.error("[startup] Failed to seed built-in entities:", err);
|
|
467
469
|
}
|
|
@@ -565,6 +567,15 @@ httpServer
|
|
|
565
567
|
.catch((err) => {
|
|
566
568
|
console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
|
|
567
569
|
});
|
|
570
|
+
|
|
571
|
+
// Background backfill: embed any scripts that were seeded without embeddings
|
|
572
|
+
// (scriptEmbeddingMode: "skip" during boot). Non-blocking, idempotent, no-op
|
|
573
|
+
// when every non-scratch script already has an embedding.
|
|
574
|
+
import("../be/scripts/boot-reembed")
|
|
575
|
+
.then(({ runBootReembedScripts }) => runBootReembedScripts())
|
|
576
|
+
.catch((err) => {
|
|
577
|
+
console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
|
|
578
|
+
});
|
|
568
579
|
})
|
|
569
580
|
.on("error", (err) => {
|
|
570
581
|
console.error("HTTP Server Error:", err);
|
package/src/http/mcp-bridge.ts
CHANGED
package/src/http/memory.ts
CHANGED
|
@@ -406,6 +406,7 @@ export async function handleMemory(
|
|
|
406
406
|
|
|
407
407
|
const { query, agentId, scope, source, sourcePath, limit, offset } = parsed.body;
|
|
408
408
|
const store = getMemoryStore();
|
|
409
|
+
const pageLimit = Math.min(limit, 100);
|
|
409
410
|
const pathNeedle = sourcePath?.trim().toLowerCase();
|
|
410
411
|
const matchesPath = (p: string | null) =>
|
|
411
412
|
!pathNeedle || (p?.toLowerCase().includes(pathNeedle) ?? false);
|
|
@@ -416,11 +417,14 @@ export async function handleMemory(
|
|
|
416
417
|
const queryEmbedding = await provider.embed(query.trim());
|
|
417
418
|
|
|
418
419
|
if (!queryEmbedding) {
|
|
419
|
-
json(res, { results: [], total: 0, mode: "semantic" });
|
|
420
|
+
json(res, { results: [], total: 0, limit: pageLimit, offset, mode: "semantic" });
|
|
420
421
|
return true;
|
|
421
422
|
}
|
|
422
423
|
|
|
423
|
-
const candidateLimit = Math.min(
|
|
424
|
+
const candidateLimit = Math.min(
|
|
425
|
+
4096,
|
|
426
|
+
Math.max(offset + pageLimit, pageLimit) * CANDIDATE_SET_MULTIPLIER,
|
|
427
|
+
);
|
|
424
428
|
let candidates = store.search(queryEmbedding, agentId ?? "", {
|
|
425
429
|
scope,
|
|
426
430
|
limit: candidateLimit,
|
|
@@ -433,10 +437,11 @@ export async function handleMemory(
|
|
|
433
437
|
if (pathNeedle) {
|
|
434
438
|
candidates = candidates.filter((c) => matchesPath(c.sourcePath));
|
|
435
439
|
}
|
|
436
|
-
const ranked = rerank(candidates, { limit:
|
|
440
|
+
const ranked = rerank(candidates, { limit: candidates.length });
|
|
441
|
+
const page = ranked.slice(offset, offset + pageLimit);
|
|
437
442
|
|
|
438
443
|
json(res, {
|
|
439
|
-
results:
|
|
444
|
+
results: page.map((r) => ({
|
|
440
445
|
id: r.id,
|
|
441
446
|
name: r.name,
|
|
442
447
|
content: r.content,
|
|
@@ -457,33 +462,25 @@ export async function handleMemory(
|
|
|
457
462
|
totalChunks: r.totalChunks,
|
|
458
463
|
tags: r.tags,
|
|
459
464
|
})),
|
|
460
|
-
total:
|
|
465
|
+
total: candidates.length,
|
|
466
|
+
limit: pageLimit,
|
|
467
|
+
offset,
|
|
461
468
|
mode: "semantic",
|
|
462
469
|
});
|
|
463
470
|
return true;
|
|
464
471
|
}
|
|
465
472
|
|
|
466
|
-
|
|
467
|
-
// page isn't gutted by the in-memory filter.
|
|
468
|
-
const fetchLimit = pathNeedle
|
|
469
|
-
? Math.min(500, Math.max(limit * 10, 100))
|
|
470
|
-
: Math.min(limit, 100);
|
|
471
|
-
let rows = store.list(agentId ?? "", {
|
|
473
|
+
const listOptions = {
|
|
472
474
|
scope,
|
|
473
|
-
limit:
|
|
475
|
+
limit: pageLimit,
|
|
474
476
|
offset,
|
|
475
477
|
isLead: true,
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
if (pathNeedle) {
|
|
484
|
-
rows = rows.filter((r) => matchesPath(r.sourcePath));
|
|
485
|
-
}
|
|
486
|
-
rows = rows.slice(0, Math.min(limit, 100));
|
|
478
|
+
ownerAgentId: agentId,
|
|
479
|
+
source,
|
|
480
|
+
sourcePath: pathNeedle,
|
|
481
|
+
};
|
|
482
|
+
const rows = store.list(agentId ?? "", listOptions);
|
|
483
|
+
const total = store.count(agentId ?? "", listOptions);
|
|
487
484
|
|
|
488
485
|
json(res, {
|
|
489
486
|
results: rows.map((r) => ({
|
|
@@ -504,7 +501,9 @@ export async function handleMemory(
|
|
|
504
501
|
totalChunks: r.totalChunks,
|
|
505
502
|
tags: r.tags,
|
|
506
503
|
})),
|
|
507
|
-
total
|
|
504
|
+
total,
|
|
505
|
+
limit: pageLimit,
|
|
506
|
+
offset,
|
|
508
507
|
mode: "list",
|
|
509
508
|
});
|
|
510
509
|
} catch (err) {
|
package/src/http/metrics.ts
CHANGED
|
@@ -48,12 +48,48 @@ function slugify(input: string): string {
|
|
|
48
48
|
|
|
49
49
|
function validateMetricDefinition(definition: unknown) {
|
|
50
50
|
const parsed = MetricDefinitionSchema.parse(definition);
|
|
51
|
+
for (const variable of parsed.variables ?? []) {
|
|
52
|
+
if (variable.optionsQuery) {
|
|
53
|
+
assertSelectOnlyQuery(variable.optionsQuery.sql);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
51
56
|
for (const widget of parsed.widgets) {
|
|
52
57
|
assertSelectOnlyQuery(widget.query.sql);
|
|
53
58
|
}
|
|
54
59
|
return parsed;
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
function resolveVariableOptionValues(variable: MetricVariable) {
|
|
63
|
+
if (!variable.optionsQuery) return variable.options ?? [];
|
|
64
|
+
assertSelectOnlyQuery(variable.optionsQuery.sql);
|
|
65
|
+
const result = executeReadOnlyQuery(variable.optionsQuery.sql, [], HARD_METRIC_MAX_ROWS);
|
|
66
|
+
return result.rows.map((row) => {
|
|
67
|
+
const record = Object.fromEntries(
|
|
68
|
+
result.columns.map((column, index) => [column, row[index] as MetricParam]),
|
|
69
|
+
);
|
|
70
|
+
const value = record[variable.optionsQuery!.valueKey];
|
|
71
|
+
const labelKey = variable.optionsQuery!.labelKey ?? variable.optionsQuery!.valueKey;
|
|
72
|
+
const label = record[labelKey] ?? value;
|
|
73
|
+
return {
|
|
74
|
+
label: label == null ? "" : String(label),
|
|
75
|
+
value: value == null ? null : value,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveVariableOptions(metric: Metric) {
|
|
81
|
+
const optionsByKey: Record<string, Array<{ label: string; value: MetricParam }>> = {};
|
|
82
|
+
const variables = (metric.definition.variables ?? []).map((variable) => {
|
|
83
|
+
if (!variable.optionsQuery) {
|
|
84
|
+
return variable;
|
|
85
|
+
}
|
|
86
|
+
const options = resolveVariableOptionValues(variable);
|
|
87
|
+
optionsByKey[variable.key] = options;
|
|
88
|
+
return { ...variable, options };
|
|
89
|
+
});
|
|
90
|
+
return { variables, optionsByKey };
|
|
91
|
+
}
|
|
92
|
+
|
|
57
93
|
function coerceVariableValue(variable: MetricVariable, raw: unknown): MetricParam {
|
|
58
94
|
if (raw == null || raw === "") {
|
|
59
95
|
return variable.defaultValue ?? null;
|
|
@@ -71,12 +107,21 @@ function coerceVariableValue(variable: MetricVariable, raw: unknown): MetricPara
|
|
|
71
107
|
return String(raw);
|
|
72
108
|
}
|
|
73
109
|
|
|
74
|
-
function resolveMetricVariables(
|
|
110
|
+
function resolveMetricVariables(
|
|
111
|
+
metric: Metric,
|
|
112
|
+
provided: Record<string, unknown>,
|
|
113
|
+
dynamicOptionsByKey: Record<string, Array<{ label: string; value: MetricParam }>> = {},
|
|
114
|
+
) {
|
|
75
115
|
const values: Record<string, MetricParam> = {};
|
|
76
116
|
for (const variable of metric.definition.variables ?? []) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
117
|
+
const options = dynamicOptionsByKey[variable.key] ?? variable.options;
|
|
118
|
+
const raw = provided[variable.key];
|
|
119
|
+
const value =
|
|
120
|
+
(raw == null || raw === "") && variable.defaultValue === undefined && options?.length
|
|
121
|
+
? options[0]!.value
|
|
122
|
+
: coerceVariableValue(variable, raw);
|
|
123
|
+
if (options?.length) {
|
|
124
|
+
const allowed = options.some((option) => option.value === value);
|
|
80
125
|
if (!allowed) {
|
|
81
126
|
throw new Error(`Metric variable "${variable.key}" must match one of its options`);
|
|
82
127
|
}
|
|
@@ -125,10 +170,14 @@ function runMetricWidget(widget: MetricWidget, variables: Record<string, MetricP
|
|
|
125
170
|
}
|
|
126
171
|
|
|
127
172
|
function runMetric(metric: Metric, providedVariables: Record<string, unknown> = {}) {
|
|
128
|
-
const
|
|
173
|
+
const resolved = resolveVariableOptions(metric);
|
|
174
|
+
const variables = resolveMetricVariables(metric, providedVariables, resolved.optionsByKey);
|
|
129
175
|
const widgets = metric.definition.widgets.map((widget) => runMetricWidget(widget, variables));
|
|
130
176
|
return {
|
|
131
|
-
metric
|
|
177
|
+
metric: {
|
|
178
|
+
...metric,
|
|
179
|
+
definition: { ...metric.definition, variables: resolved.variables },
|
|
180
|
+
},
|
|
132
181
|
variables,
|
|
133
182
|
widgets,
|
|
134
183
|
// Kept as the first widget result for older callers during the PR cycle.
|
package/src/http/schedules.ts
CHANGED
|
@@ -12,9 +12,8 @@ import {
|
|
|
12
12
|
updateScheduledTask,
|
|
13
13
|
} from "../be/db";
|
|
14
14
|
import { mergeScheduleTiming, validateRecurringTiming } from "../be/schedules/validate";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
15
|
+
import { ModelTierSchema, splitLegacyModelAlias } from "../model-tiers";
|
|
16
|
+
import { calculateNextRun, createStandaloneScheduleTask } from "../scheduler/scheduler";
|
|
18
17
|
import { getExecutorRegistry } from "../workflows";
|
|
19
18
|
import { handleScheduleTrigger } from "../workflows/triggers";
|
|
20
19
|
import { route } from "./route-def";
|
|
@@ -41,6 +40,7 @@ const createSchedule = route({
|
|
|
41
40
|
enabled: z.boolean().optional(),
|
|
42
41
|
timezone: z.string().optional(),
|
|
43
42
|
model: z.string().optional(),
|
|
43
|
+
modelTier: ModelTierSchema.optional(),
|
|
44
44
|
scheduleType: z.enum(["recurring", "one_time"]).optional(),
|
|
45
45
|
delayMs: z.number().int().optional(),
|
|
46
46
|
runAt: z.string().optional(),
|
|
@@ -126,6 +126,7 @@ const updateSchedule = route({
|
|
|
126
126
|
enabled: z.boolean().optional(),
|
|
127
127
|
timezone: z.string().optional(),
|
|
128
128
|
model: z.string().optional(),
|
|
129
|
+
modelTier: ModelTierSchema.nullable().optional(),
|
|
129
130
|
nextRunAt: z.string().nullable().optional(),
|
|
130
131
|
}),
|
|
131
132
|
responses: {
|
|
@@ -270,7 +271,7 @@ export async function handleSchedules(
|
|
|
270
271
|
enabled: body.enabled,
|
|
271
272
|
nextRunAt,
|
|
272
273
|
timezone: body.timezone,
|
|
273
|
-
model: body.model,
|
|
274
|
+
...splitLegacyModelAlias({ model: body.model, modelTier: body.modelTier }),
|
|
274
275
|
scheduleType: body.scheduleType,
|
|
275
276
|
});
|
|
276
277
|
|
|
@@ -333,17 +334,7 @@ export async function handleSchedules(
|
|
|
333
334
|
const now = new Date().toISOString();
|
|
334
335
|
|
|
335
336
|
const task = getDb().transaction(() => {
|
|
336
|
-
const createdTask =
|
|
337
|
-
creatorAgentId: schedule.createdByAgentId,
|
|
338
|
-
taskType: schedule.taskType,
|
|
339
|
-
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, "manual-run"],
|
|
340
|
-
priority: schedule.priority,
|
|
341
|
-
agentId: schedule.targetAgentId,
|
|
342
|
-
model: schedule.model,
|
|
343
|
-
scheduleId: schedule.id,
|
|
344
|
-
source: "schedule",
|
|
345
|
-
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
346
|
-
});
|
|
337
|
+
const createdTask = createStandaloneScheduleTask(schedule, ["manual-run"]);
|
|
347
338
|
|
|
348
339
|
if (schedule.scheduleType === "one_time") {
|
|
349
340
|
updateScheduledTask(schedule.id, {
|
|
@@ -388,6 +379,16 @@ export async function handleSchedules(
|
|
|
388
379
|
const parsed = await updateSchedule.parse(req, res, pathSegments, queryParams);
|
|
389
380
|
if (!parsed) return true;
|
|
390
381
|
const body = parsed.body as Record<string, unknown>;
|
|
382
|
+
if (parsed.body.model !== undefined || parsed.body.modelTier !== undefined) {
|
|
383
|
+
const normalizedModel = splitLegacyModelAlias({
|
|
384
|
+
model: parsed.body.model,
|
|
385
|
+
modelTier: parsed.body.modelTier,
|
|
386
|
+
});
|
|
387
|
+
if (parsed.body.model !== undefined) body.model = normalizedModel.model ?? null;
|
|
388
|
+
if (parsed.body.modelTier !== undefined || normalizedModel.modelTier) {
|
|
389
|
+
body.modelTier = normalizedModel.modelTier ?? null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
391
392
|
|
|
392
393
|
const existing = getScheduledTaskById(parsed.params.id);
|
|
393
394
|
if (!existing) {
|
package/src/http/script-runs.ts
CHANGED
|
@@ -51,6 +51,7 @@ const createScriptRunBodySchema = z.object({
|
|
|
51
51
|
const listScriptRunsQuerySchema = z.object({
|
|
52
52
|
status: ScriptRunStatusSchema.optional(),
|
|
53
53
|
agentId: z.string().optional(),
|
|
54
|
+
scriptName: z.string().optional(),
|
|
54
55
|
limit: z.coerce.number().int().min(1).max(500).optional(),
|
|
55
56
|
offset: z.coerce.number().int().min(0).optional(),
|
|
56
57
|
});
|
|
@@ -367,12 +368,17 @@ export async function handleScriptRuns(
|
|
|
367
368
|
const opts = {
|
|
368
369
|
status: parsed.query.status,
|
|
369
370
|
agentId: parsed.query.agentId,
|
|
371
|
+
scriptName: parsed.query.scriptName,
|
|
370
372
|
limit: parsed.query.limit ?? 50,
|
|
371
373
|
offset: parsed.query.offset ?? 0,
|
|
372
374
|
};
|
|
373
375
|
json(res, {
|
|
374
376
|
runs: listScriptRuns(opts),
|
|
375
|
-
total: countScriptRuns({
|
|
377
|
+
total: countScriptRuns({
|
|
378
|
+
status: opts.status,
|
|
379
|
+
agentId: opts.agentId,
|
|
380
|
+
scriptName: opts.scriptName,
|
|
381
|
+
}),
|
|
376
382
|
});
|
|
377
383
|
return true;
|
|
378
384
|
}
|
package/src/http/scripts.ts
CHANGED
|
@@ -2,14 +2,23 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAgentById, recordInlineScriptRun, upsertKv } from "../be/db";
|
|
4
4
|
import { createEvent } from "../be/events";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
deleteScript,
|
|
7
|
+
getScript,
|
|
8
|
+
getScriptById,
|
|
9
|
+
listScripts,
|
|
10
|
+
listScriptVersions,
|
|
11
|
+
upsertScriptByName,
|
|
12
|
+
} from "../be/scripts/db";
|
|
6
13
|
import { searchScripts } from "../be/scripts/embeddings";
|
|
7
14
|
import { extractArgsJsonSchema } from "../be/scripts/extract-schema";
|
|
8
15
|
import { SCRIPT_SDK_TYPES, SCRIPT_STDLIB_TYPES, typecheckScript } from "../be/scripts/typecheck";
|
|
9
16
|
import { extractScriptSignature } from "../scripts-runtime/extract-signature";
|
|
10
17
|
import { runScript } from "../scripts-runtime/loader";
|
|
11
18
|
import {
|
|
19
|
+
type ScriptDetail,
|
|
12
20
|
ScriptFsModeSchema,
|
|
21
|
+
type ScriptListItem,
|
|
13
22
|
type ScriptRecord,
|
|
14
23
|
type ScriptScope,
|
|
15
24
|
ScriptScopeSchema,
|
|
@@ -52,6 +61,11 @@ const searchBodySchema = z.object({
|
|
|
52
61
|
const nameParamsSchema = z.object({ name: scriptNameSchema });
|
|
53
62
|
const scopeQuerySchema = z.object({ scope: ScriptScopeSchema.default("agent") });
|
|
54
63
|
const optionalScopeQuerySchema = z.object({ scope: ScriptScopeSchema.optional() });
|
|
64
|
+
const idParamsSchema = z.object({ id: z.string().uuid() });
|
|
65
|
+
const listScriptsQuerySchema = z.object({
|
|
66
|
+
scope: ScriptScopeSchema.optional(),
|
|
67
|
+
includeScratch: z.enum(["true", "false"]).optional(),
|
|
68
|
+
});
|
|
55
69
|
|
|
56
70
|
const upsertRoute = route({
|
|
57
71
|
method: "post",
|
|
@@ -133,6 +147,74 @@ const typesRoute = route({
|
|
|
133
147
|
},
|
|
134
148
|
});
|
|
135
149
|
|
|
150
|
+
// ── Dashboard read routes ──
|
|
151
|
+
// The worker-facing routes above resolve scripts relative to the calling agent
|
|
152
|
+
// and therefore requireAgent (X-Agent-ID). The routes below are cross-scope
|
|
153
|
+
// admin reads for the dashboard: API-key auth only, no agent identity — the
|
|
154
|
+
// same model as /api/script-runs.
|
|
155
|
+
|
|
156
|
+
const listScriptsRoute = route({
|
|
157
|
+
method: "get",
|
|
158
|
+
path: "/api/scripts",
|
|
159
|
+
pattern: ["api", "scripts"],
|
|
160
|
+
operationId: "scripts_list",
|
|
161
|
+
summary: "List saved scripts",
|
|
162
|
+
description:
|
|
163
|
+
"Dashboard read: lean projection without source. Scratch scripts are excluded unless includeScratch=true.",
|
|
164
|
+
tags: ["Scripts"],
|
|
165
|
+
query: listScriptsQuerySchema,
|
|
166
|
+
responses: {
|
|
167
|
+
200: { description: "Saved scripts" },
|
|
168
|
+
400: { description: "Validation error" },
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Declared (and matched) BEFORE the by-id route: the by-id pattern
|
|
173
|
+
// ["api", "scripts", null] matches any single segment, so the literal
|
|
174
|
+
// "type-defs" segment must win first.
|
|
175
|
+
const typeDefsRoute = route({
|
|
176
|
+
method: "get",
|
|
177
|
+
path: "/api/scripts/type-defs",
|
|
178
|
+
pattern: ["api", "scripts", "type-defs"],
|
|
179
|
+
operationId: "scripts_type_defs",
|
|
180
|
+
summary: "Get script SDK and stdlib type definitions",
|
|
181
|
+
description: "Static .d.ts blobs for editor integration (e.g. Monaco extraLibs). Cacheable.",
|
|
182
|
+
tags: ["Scripts"],
|
|
183
|
+
responses: {
|
|
184
|
+
200: { description: "SDK and stdlib type definition blobs" },
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const getScriptByIdRoute = route({
|
|
189
|
+
method: "get",
|
|
190
|
+
path: "/api/scripts/{id}",
|
|
191
|
+
pattern: ["api", "scripts", null],
|
|
192
|
+
operationId: "scripts_get",
|
|
193
|
+
summary: "Get a saved script by id",
|
|
194
|
+
description: "Dashboard read: full record including source and parsed signature.",
|
|
195
|
+
tags: ["Scripts"],
|
|
196
|
+
params: idParamsSchema,
|
|
197
|
+
responses: {
|
|
198
|
+
200: { description: "Script detail" },
|
|
199
|
+
404: { description: "Script not found" },
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const listVersionsRoute = route({
|
|
204
|
+
method: "get",
|
|
205
|
+
path: "/api/scripts/{id}/versions",
|
|
206
|
+
pattern: ["api", "scripts", null, "versions"],
|
|
207
|
+
operationId: "scripts_versions",
|
|
208
|
+
summary: "List versions of a saved script",
|
|
209
|
+
description: "Dashboard read: version history, newest first.",
|
|
210
|
+
tags: ["Scripts"],
|
|
211
|
+
params: idParamsSchema,
|
|
212
|
+
responses: {
|
|
213
|
+
200: { description: "Script versions" },
|
|
214
|
+
404: { description: "Script not found" },
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
136
218
|
function requireAgent(res: ServerResponse, agentId: string | undefined) {
|
|
137
219
|
if (!agentId) {
|
|
138
220
|
jsonError(res, "X-Agent-ID required for scripts API", 400);
|
|
@@ -413,6 +495,70 @@ export async function handleScripts(
|
|
|
413
495
|
return true;
|
|
414
496
|
}
|
|
415
497
|
|
|
498
|
+
// ── Dashboard reads (no requireAgent — API-key auth only, like /api/script-runs) ──
|
|
499
|
+
|
|
500
|
+
if (listScriptsRoute.match(req.method, pathSegments)) {
|
|
501
|
+
const parsed = await listScriptsRoute.parse(req, res, pathSegments, queryParams);
|
|
502
|
+
if (!parsed) return true;
|
|
503
|
+
const scripts: ScriptListItem[] = listScripts({
|
|
504
|
+
scope: parsed.query.scope,
|
|
505
|
+
includeScratch: parsed.query.includeScratch === "true",
|
|
506
|
+
}).map((script) => ({
|
|
507
|
+
id: script.id,
|
|
508
|
+
name: script.name,
|
|
509
|
+
scope: script.scope,
|
|
510
|
+
scopeId: script.scopeId,
|
|
511
|
+
description: script.description,
|
|
512
|
+
intent: script.intent,
|
|
513
|
+
version: script.version,
|
|
514
|
+
isScratch: script.isScratch,
|
|
515
|
+
typeChecked: script.typeChecked,
|
|
516
|
+
fsMode: script.fsMode,
|
|
517
|
+
createdByAgentId: script.createdByAgentId,
|
|
518
|
+
createdAt: script.createdAt,
|
|
519
|
+
updatedAt: script.updatedAt,
|
|
520
|
+
}));
|
|
521
|
+
json(res, { scripts });
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Must be matched before getScriptByIdRoute — its ["api", "scripts", null]
|
|
526
|
+
// pattern would otherwise swallow the literal "type-defs" segment.
|
|
527
|
+
if (typeDefsRoute.match(req.method, pathSegments)) {
|
|
528
|
+
json(res, { sdkTypes: SCRIPT_SDK_TYPES, stdlibTypes: SCRIPT_STDLIB_TYPES });
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (getScriptByIdRoute.match(req.method, pathSegments)) {
|
|
533
|
+
const parsed = await getScriptByIdRoute.parse(req, res, pathSegments, queryParams);
|
|
534
|
+
if (!parsed) return true;
|
|
535
|
+
const script = getScriptById(parsed.params.id);
|
|
536
|
+
if (!script) {
|
|
537
|
+
jsonError(res, "Script not found", 404);
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
// `source` is author-supplied TS (same trust surface as script_runs.source,
|
|
541
|
+
// already served raw by GET /api/script-runs/{id}) — no env/secret material.
|
|
542
|
+
const detail: ScriptDetail = {
|
|
543
|
+
...script,
|
|
544
|
+
signature: JSON.parse(script.signatureJson) as unknown,
|
|
545
|
+
argsJsonSchema: script.argsJsonSchema ? (JSON.parse(script.argsJsonSchema) as unknown) : null,
|
|
546
|
+
};
|
|
547
|
+
json(res, { script: detail });
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (listVersionsRoute.match(req.method, pathSegments)) {
|
|
552
|
+
const parsed = await listVersionsRoute.parse(req, res, pathSegments, queryParams);
|
|
553
|
+
if (!parsed) return true;
|
|
554
|
+
if (!getScriptById(parsed.params.id)) {
|
|
555
|
+
jsonError(res, "Script not found", 404);
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
json(res, { versions: listScriptVersions(parsed.params.id) });
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
|
|
416
562
|
if (typesRoute.match(req.method, pathSegments)) {
|
|
417
563
|
const parsed = await typesRoute.parse(req, res, pathSegments, queryParams);
|
|
418
564
|
if (!parsed) return true;
|
package/src/http/tasks.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
updateTaskProgress,
|
|
24
24
|
updateTaskVcs,
|
|
25
25
|
} from "../be/db";
|
|
26
|
+
import { ModelTierSchema, splitLegacyModelAlias } from "../model-tiers";
|
|
26
27
|
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
27
28
|
import { createResumeFollowUp, createWorkerTaskFollowUp } from "../tasks/worker-follow-up";
|
|
28
29
|
import { telemetry } from "../telemetry";
|
|
@@ -91,6 +92,8 @@ const createTask = route({
|
|
|
91
92
|
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
92
93
|
contextKey: z.string().optional(),
|
|
93
94
|
requestedByUserId: z.string().optional(),
|
|
95
|
+
model: z.string().optional(),
|
|
96
|
+
modelTier: ModelTierSchema.optional(),
|
|
94
97
|
}),
|
|
95
98
|
responses: {
|
|
96
99
|
201: { description: "Task created" },
|
|
@@ -98,11 +101,11 @@ const createTask = route({
|
|
|
98
101
|
},
|
|
99
102
|
});
|
|
100
103
|
|
|
101
|
-
const
|
|
104
|
+
const updateSession = route({
|
|
102
105
|
method: "put",
|
|
103
|
-
path: "/api/tasks/{id}/
|
|
104
|
-
pattern: ["api", "tasks", null, "
|
|
105
|
-
summary: "Update
|
|
106
|
+
path: "/api/tasks/{id}/session",
|
|
107
|
+
pattern: ["api", "tasks", null, "session"],
|
|
108
|
+
summary: "Update provider session ID and harness metadata for a task",
|
|
106
109
|
tags: ["Tasks"],
|
|
107
110
|
params: z.object({ id: z.string() }),
|
|
108
111
|
body: z.union([
|
|
@@ -121,6 +124,8 @@ const updateClaudeSession = route({
|
|
|
121
124
|
provider: ProviderNameSchema.exclude(["devin"]).optional(),
|
|
122
125
|
model: z.string().optional(),
|
|
123
126
|
providerMeta: z.object({}).optional(),
|
|
127
|
+
harnessVariant: z.string().optional(),
|
|
128
|
+
harnessVariantMeta: z.record(z.string(), z.unknown()).optional(),
|
|
124
129
|
}),
|
|
125
130
|
]),
|
|
126
131
|
responses: {
|
|
@@ -393,6 +398,10 @@ export async function handleTasks(
|
|
|
393
398
|
outputSchema: parsed.body.outputSchema || undefined,
|
|
394
399
|
contextKey: parsed.body.contextKey || undefined,
|
|
395
400
|
requestedByUserId,
|
|
401
|
+
...splitLegacyModelAlias({
|
|
402
|
+
model: parsed.body.model,
|
|
403
|
+
modelTier: parsed.body.modelTier,
|
|
404
|
+
}),
|
|
396
405
|
});
|
|
397
406
|
|
|
398
407
|
ensure({
|
|
@@ -427,8 +436,8 @@ export async function handleTasks(
|
|
|
427
436
|
return true;
|
|
428
437
|
}
|
|
429
438
|
|
|
430
|
-
if (
|
|
431
|
-
const parsed = await
|
|
439
|
+
if (updateSession.match(req.method, pathSegments)) {
|
|
440
|
+
const parsed = await updateSession.parse(req, res, pathSegments, queryParams);
|
|
432
441
|
if (!parsed) return true;
|
|
433
442
|
const task = updateTaskClaudeSessionId(
|
|
434
443
|
parsed.params.id,
|
|
@@ -436,6 +445,8 @@ export async function handleTasks(
|
|
|
436
445
|
parsed.body.provider,
|
|
437
446
|
parsed.body.providerMeta,
|
|
438
447
|
parsed.body.model,
|
|
448
|
+
"harnessVariant" in parsed.body ? parsed.body.harnessVariant : undefined,
|
|
449
|
+
"harnessVariantMeta" in parsed.body ? parsed.body.harnessVariantMeta : undefined,
|
|
439
450
|
);
|
|
440
451
|
if (!task) {
|
|
441
452
|
jsonError(res, "Task not found", 404);
|