@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
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
* Worker-side per-task skill refresh.
|
|
3
3
|
*
|
|
4
4
|
* Polls the cheap signature endpoint; on a hash mismatch, refetches the
|
|
5
|
-
* full skill list and
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* full skill list and writes SKILL.md files to the worker's local HOME via
|
|
6
|
+
* writeSkillsToFilesystem() from skill-fs-writer.ts. This ensures newly
|
|
7
|
+
* created/approved skills land on the worker disk mid-session — no container
|
|
8
|
+
* restart required.
|
|
9
|
+
*
|
|
10
|
+
* Previously Step 3 POSTed to /api/skills/sync-filesystem, which wrote to
|
|
11
|
+
* the API server's HOME instead of the worker disk. Now Step 3 builds
|
|
12
|
+
* SkillFsEntry[] from the already-fetched skill data and writes locally.
|
|
13
|
+
* For complex skills the worker fetches bundled files via N+1 HTTP calls
|
|
14
|
+
* (acceptable for v1 — simple skills need zero extra fetches).
|
|
15
|
+
*
|
|
16
|
+
* The /api/skills/sync-filesystem endpoint is retained for single-box local
|
|
17
|
+
* dev (where API and worker share a HOME). Workers no longer call it.
|
|
9
18
|
*
|
|
10
19
|
* Transient errors are swallowed (returned as `changed: false`) so a flaky
|
|
11
20
|
* API can't churn the system prompt.
|
|
12
21
|
*/
|
|
13
22
|
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { type SkillFsEntry, writeSkillsToFilesystem } from "./skill-fs-writer";
|
|
25
|
+
|
|
14
26
|
export type SkillsRefreshContext = {
|
|
15
27
|
apiUrl: string;
|
|
16
28
|
swarmUrl: string;
|
|
@@ -27,6 +39,7 @@ export type SkillsRefreshResult = {
|
|
|
27
39
|
export async function refreshSkillsIfChanged(
|
|
28
40
|
ctx: SkillsRefreshContext,
|
|
29
41
|
lastHashRef: { current: string | null },
|
|
42
|
+
homeOverride?: string,
|
|
30
43
|
): Promise<SkillsRefreshResult> {
|
|
31
44
|
const { apiUrl, apiKey, agentId, role } = ctx;
|
|
32
45
|
const authHeaders: Record<string, string> = { "X-Agent-ID": agentId };
|
|
@@ -52,70 +65,140 @@ export async function refreshSkillsIfChanged(
|
|
|
52
65
|
return { changed: false };
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
// Step 2: full fetch
|
|
56
|
-
|
|
68
|
+
// Step 2: full fetch (only reached when hash differs or first call)
|
|
69
|
+
// Keep the full skill rows including content, id, isComplex — data is
|
|
70
|
+
// already on the wire, was previously discarded.
|
|
71
|
+
type SkillRow = {
|
|
72
|
+
id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
description: string;
|
|
75
|
+
content: string | null;
|
|
76
|
+
isComplex: boolean;
|
|
77
|
+
isEnabled: boolean;
|
|
78
|
+
isActive: boolean;
|
|
79
|
+
};
|
|
80
|
+
let skillRows: SkillRow[] = [];
|
|
57
81
|
let newHash: string | null = null;
|
|
82
|
+
let listFetchOk = false;
|
|
58
83
|
try {
|
|
59
84
|
const skillsResp = await fetch(`${apiUrl}/api/agents/${agentId}/skills`, {
|
|
60
85
|
headers: authHeaders,
|
|
61
86
|
});
|
|
62
87
|
if (skillsResp.ok) {
|
|
63
88
|
const skillsData = (await skillsResp.json()) as {
|
|
64
|
-
skills:
|
|
89
|
+
skills: SkillRow[];
|
|
65
90
|
signature?: string;
|
|
66
91
|
};
|
|
67
|
-
|
|
68
|
-
.filter((s) => s.isActive && s.isEnabled)
|
|
69
|
-
.map((s) => ({ name: s.name, description: s.description }));
|
|
92
|
+
skillRows = skillsData.skills;
|
|
70
93
|
if (typeof skillsData.signature === "string") {
|
|
71
94
|
newHash = skillsData.signature;
|
|
72
95
|
}
|
|
96
|
+
listFetchOk = true;
|
|
73
97
|
}
|
|
74
98
|
} catch {
|
|
75
|
-
//
|
|
99
|
+
// Transient network / parse error — bail out without touching the local FS
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Guard: a failed list fetch must not proceed to writeSkillsToFilesystem.
|
|
103
|
+
// An empty entries array would wipe every swarm-managed skill directory from
|
|
104
|
+
// the worker disk, which is worse than leaving the cache stale.
|
|
105
|
+
if (!listFetchOk) {
|
|
106
|
+
return { changed: false };
|
|
76
107
|
}
|
|
77
108
|
|
|
78
|
-
|
|
109
|
+
const summary = skillRows
|
|
110
|
+
.filter((s) => s.isActive && s.isEnabled)
|
|
111
|
+
.map((s) => ({ name: s.name, description: s.description }));
|
|
112
|
+
|
|
113
|
+
// Step 3: build SkillFsEntry[] and write to THIS worker's local HOME.
|
|
114
|
+
//
|
|
115
|
+
// For complex+enabled skills, fetch bundled files via N+1 HTTP calls
|
|
116
|
+
// (GET /api/skills/:id/files for manifest, then per non-binary file).
|
|
117
|
+
// Simple skills (the common case) need zero extra fetches.
|
|
79
118
|
let syncOk = false;
|
|
80
119
|
try {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
const entries: SkillFsEntry[] = [];
|
|
121
|
+
|
|
122
|
+
for (const skill of skillRows) {
|
|
123
|
+
if (!skill.isActive || !skill.isEnabled) continue;
|
|
124
|
+
|
|
125
|
+
const files: { path: string; content: string; isBinary: boolean }[] = [];
|
|
126
|
+
|
|
127
|
+
if (skill.isComplex) {
|
|
128
|
+
// Fetch manifest to know which files exist + which are binary
|
|
129
|
+
try {
|
|
130
|
+
const manifestResp = await fetch(`${apiUrl}/api/skills/${skill.id}/files`, {
|
|
131
|
+
headers: authHeaders,
|
|
132
|
+
});
|
|
133
|
+
if (manifestResp.ok) {
|
|
134
|
+
const manifestData = (await manifestResp.json()) as {
|
|
135
|
+
files: { path: string; isBinary: boolean }[];
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Fetch content for each non-binary file (N+1 — acceptable for v1)
|
|
139
|
+
for (const manifestEntry of manifestData.files) {
|
|
140
|
+
if (manifestEntry.isBinary) {
|
|
141
|
+
files.push({ path: manifestEntry.path, content: "", isBinary: true });
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const encodedPath = manifestEntry.path.split("/").map(encodeURIComponent).join("/");
|
|
146
|
+
const fileResp = await fetch(
|
|
147
|
+
`${apiUrl}/api/skills/${skill.id}/files/${encodedPath}`,
|
|
148
|
+
{ headers: authHeaders },
|
|
149
|
+
);
|
|
150
|
+
if (fileResp.ok) {
|
|
151
|
+
const fileData = (await fileResp.json()) as {
|
|
152
|
+
file: { path: string; content: string; isBinary: boolean };
|
|
153
|
+
};
|
|
154
|
+
files.push({
|
|
155
|
+
path: fileData.file.path,
|
|
156
|
+
content: fileData.file.content,
|
|
157
|
+
isBinary: fileData.file.isBinary,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// Non-fatal — skip this file
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// Non-fatal — treat as no files (will skip complex skill per writer logic)
|
|
167
|
+
}
|
|
101
168
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
169
|
+
|
|
170
|
+
entries.push({
|
|
171
|
+
id: skill.id,
|
|
172
|
+
name: skill.name,
|
|
173
|
+
content: skill.content ?? null,
|
|
174
|
+
isComplex: skill.isComplex,
|
|
175
|
+
isEnabled: skill.isEnabled,
|
|
176
|
+
isActive: skill.isActive,
|
|
177
|
+
files,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const writeResult = writeSkillsToFilesystem(entries, "all", homeOverride ?? homedir());
|
|
182
|
+
console.log(
|
|
183
|
+
`[${role}] Skills synced: ${writeResult.synced} written, ${writeResult.removed} removed`,
|
|
184
|
+
);
|
|
185
|
+
if (writeResult.errors.length > 0) {
|
|
186
|
+
console.warn(`[${role}] Skill sync errors: ${writeResult.errors.join(", ")}`);
|
|
105
187
|
}
|
|
188
|
+
syncOk = true;
|
|
106
189
|
} catch (err) {
|
|
107
190
|
console.warn(`[${role}] Skill sync failed: ${(err as Error).message}`);
|
|
108
191
|
}
|
|
109
192
|
|
|
110
|
-
if (
|
|
193
|
+
if (skillRows.length === 0 && newHash === null) {
|
|
111
194
|
return { changed: false };
|
|
112
195
|
}
|
|
113
196
|
|
|
114
|
-
// Only cache the new hash once the FS
|
|
115
|
-
// otherwise a transient
|
|
197
|
+
// Only cache the new hash once the local FS write has actually succeeded —
|
|
198
|
+
// otherwise a transient write failure would leave the cached hash matching
|
|
116
199
|
// the current signature, causing later polls to short-circuit and the
|
|
117
|
-
// disk state to stay stale
|
|
118
|
-
//
|
|
200
|
+
// disk state to stay stale forever. The next poll re-enters this code path
|
|
201
|
+
// (lastHashRef unchanged) and retries.
|
|
119
202
|
if (syncOk && newHash !== null) {
|
|
120
203
|
lastHashRef.current = newHash;
|
|
121
204
|
}
|
package/src/workflows/engine.ts
CHANGED
|
@@ -496,6 +496,7 @@ async function executeStep(
|
|
|
496
496
|
// 4. Deep-interpolate config using local context (not global ctx)
|
|
497
497
|
const { value: interpolatedValue, unresolved } = deepInterpolate(node.config, interpolationCtx);
|
|
498
498
|
const interpolatedConfig = interpolatedValue as Record<string, unknown>;
|
|
499
|
+
const executionCtx: Record<string, unknown> = { ...ctx, ...interpolationCtx };
|
|
499
500
|
|
|
500
501
|
if (unresolved.length > 0) {
|
|
501
502
|
console.warn(
|
|
@@ -524,7 +525,7 @@ async function executeStep(
|
|
|
524
525
|
result = await Promise.race([
|
|
525
526
|
executor.run({
|
|
526
527
|
config: interpolatedConfig,
|
|
527
|
-
context:
|
|
528
|
+
context: executionCtx,
|
|
528
529
|
meta,
|
|
529
530
|
}),
|
|
530
531
|
timeoutPromise(timeoutMs),
|
|
@@ -595,7 +596,7 @@ async function executeStep(
|
|
|
595
596
|
// 7. Run validation if configured
|
|
596
597
|
let validationResult: ValidationRunResult | undefined;
|
|
597
598
|
if (node.validation) {
|
|
598
|
-
validationResult = await runStepValidation(registry, node, result.output,
|
|
599
|
+
validationResult = await runStepValidation(registry, node, result.output, executionCtx, meta);
|
|
599
600
|
|
|
600
601
|
if (validationResult.outcome === "halt") {
|
|
601
602
|
const errorMsg = "Validation failed (mustPass)";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ModelTierSchema, splitLegacyModelAlias } from "../../model-tiers";
|
|
2
3
|
import { workflowContextKey } from "../../tasks/context-key";
|
|
3
4
|
import { withSiblingAwareness } from "../../tasks/sibling-awareness";
|
|
4
5
|
import type { ExecutorMeta } from "../../types";
|
|
@@ -17,6 +18,7 @@ const AgentTaskConfigSchema = z.object({
|
|
|
17
18
|
dir: z.string().min(1).optional(),
|
|
18
19
|
vcsRepo: z.string().min(1).optional(),
|
|
19
20
|
model: z.string().min(1).optional(),
|
|
21
|
+
modelTier: ModelTierSchema.optional(),
|
|
20
22
|
parentTaskId: z.string().uuid().optional(),
|
|
21
23
|
requestedByUserId: z.string().optional(),
|
|
22
24
|
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -94,7 +96,7 @@ export class AgentTaskExecutor extends BaseExecutor<
|
|
|
94
96
|
workflowRunStepId: meta.stepId,
|
|
95
97
|
dir: effectiveDir,
|
|
96
98
|
vcsRepo: effectiveVcsRepo,
|
|
97
|
-
model: config.model,
|
|
99
|
+
...splitLegacyModelAlias({ model: config.model, modelTier: config.modelTier }),
|
|
98
100
|
parentTaskId: config.parentTaskId,
|
|
99
101
|
requestedByUserId: config.requestedByUserId ?? meta.requestedByUserId,
|
|
100
102
|
outputSchema: config.outputSchema,
|