@desplega.ai/agent-swarm 1.92.0 → 1.92.2
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 +1 -1
- package/openapi.json +276 -3
- package/package.json +6 -6
- package/plugin/skills/pages/SKILL.md +5 -2
- package/src/be/db.ts +416 -20
- package/src/be/memory/boot-reembed.ts +85 -0
- package/src/be/memory/constants.ts +44 -2
- package/src/be/memory/providers/openai-embedding.ts +15 -5
- package/src/be/memory/providers/sqlite-store.ts +325 -76
- package/src/be/memory/reranker.ts +35 -17
- package/src/be/memory/types.ts +43 -0
- package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
- package/src/be/migrations/085_script_runs_kind.sql +9 -0
- package/src/be/migrations/086_pages_default_authed.sql +64 -0
- package/src/be/migrations/087_skill_files.sql +19 -0
- package/src/be/modelsdev-cache.json +5622 -2543
- package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
- package/src/be/seed-scripts/catalog/compound-insights.ts +465 -0
- package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
- package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +34 -439
- package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
- package/src/be/seed-scripts/index.ts +32 -4
- package/src/be/seed-skills/index.ts +0 -7
- package/src/be/skill-sync.ts +91 -7
- package/src/commands/runner.ts +6 -2
- package/src/heartbeat/templates.ts +20 -16
- package/src/http/index.ts +50 -7
- package/src/http/mcp-user.ts +23 -0
- package/src/http/mcp.ts +58 -0
- package/src/http/memory.ts +62 -0
- package/src/http/pages.ts +1 -1
- package/src/http/script-runs.ts +2 -0
- package/src/http/scripts.ts +39 -2
- package/src/http/skills.ts +225 -0
- package/src/providers/claude-adapter.ts +56 -24
- package/src/script-workflows/workflow-ctx.ts +7 -3
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +13 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +2 -0
- package/src/tasks/worker-follow-up.ts +12 -0
- package/src/tests/claude-adapter-binary.test.ts +135 -81
- package/src/tests/create-page-tool.test.ts +19 -2
- package/src/tests/heartbeat-checklist.test.ts +36 -0
- package/src/tests/mcp-transport-gc.test.ts +58 -0
- package/src/tests/memory-e2e.test.ts +6 -6
- package/src/tests/memory-health-endpoint.test.ts +78 -0
- package/src/tests/memory-rater-e2e.test.ts +4 -5
- package/src/tests/memory-reranker.test.ts +135 -124
- package/src/tests/memory-store.test.ts +221 -1
- package/src/tests/memory.test.ts +13 -12
- package/src/tests/pages-http.test.ts +20 -2
- package/src/tests/pages-storage.test.ts +26 -0
- package/src/tests/scripts-mcp-e2e.test.ts +53 -0
- package/src/tests/seed-scripts.test.ts +328 -3
- package/src/tests/skill-files-http.test.ts +171 -0
- package/src/tests/skill-files.test.ts +162 -0
- package/src/tests/skill-get-file-tool.test.ts +110 -0
- package/src/tests/skill-sync.test.ts +125 -6
- package/src/tests/task-cascade-fail.test.ts +304 -0
- package/src/tools/create-page.ts +2 -2
- package/src/tools/skills/index.ts +1 -0
- package/src/tools/skills/skill-get-file.ts +80 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +20 -0
- package/src/utils/internal-ai/complete-structured.ts +2 -2
- package/templates/schedules/daily-blocker-digest/content.md +68 -54
- package/templates/schedules/daily-compounding-reflection/content.md +4 -4
- package/templates/schedules/daily-hn-briefing/content.md +5 -5
- package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
- package/templates/schedules/gtm-weekly-review/content.md +9 -9
- package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
- package/templates/skills/agentmail-sending/content.md +6 -7
- package/templates/skills/desloppify/content.md +8 -9
- package/templates/skills/jira-interaction/content.md +25 -33
- package/templates/skills/kapso-whatsapp/content.md +29 -30
- package/templates/skills/linear-interaction/content.md +8 -9
- package/templates/skills/profile-corruption-escalation/content.md +44 -85
- package/templates/skills/sprite-cli/content.md +4 -5
- package/templates/skills/turso-interaction/content.md +14 -17
- package/templates/skills/workflow-iterate/content.md +38 -391
- package/templates/skills/x-api-interactions/content.md +4 -6
- package/templates/workflows/llm-safe-release-context/config.json +13 -0
- package/templates/workflows/llm-safe-release-context/content.md +69 -0
- package/templates/skills/scheduled-task-resilience/config.json +0 -14
- package/templates/skills/scheduled-task-resilience/content.md +0 -95
|
@@ -72,9 +72,8 @@ async function cleanupTaskFile(pid: number): Promise<void> {
|
|
|
72
72
|
/**
|
|
73
73
|
* Parse `CLAUDE_BINARY` into argv prefix tokens.
|
|
74
74
|
*
|
|
75
|
-
* Accepts a single binary name (`"claude"
|
|
76
|
-
*
|
|
77
|
-
* `"npx -y @dexh/shannon"`). Trim + split on `/\s+/`. No shell parsing, no
|
|
75
|
+
* Accepts a single binary name (`"claude"`), an absolute path, or a
|
|
76
|
+
* whitespace-separated command string. Trim + split on `/\s+/`. No shell parsing, no
|
|
78
77
|
* quote handling — keep it tiny and predictable. Empty / missing → `["claude"]`.
|
|
79
78
|
*
|
|
80
79
|
* Exported for unit testing.
|
|
@@ -109,6 +108,15 @@ export function resolveClaudeBinary(
|
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
const CLAUDE_BRIDGE_BINARY = "claude-bridge";
|
|
111
|
+
const CLAUDE_BRIDGE_LOCAL_AUTH_ARG = "--desplega-local-auth";
|
|
112
|
+
const LEGACY_CLAUDE_BRIDGE_COMPAT_BINARY = "shan" + "non";
|
|
113
|
+
const CLAUDE_BRIDGE_LOCAL_AUTH_ENV_VARS = [
|
|
114
|
+
"ANTHROPIC_API_KEY",
|
|
115
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
116
|
+
"ANTHROPIC_BASE_URL",
|
|
117
|
+
"ANTHROPIC_CUSTOM_HEADERS",
|
|
118
|
+
"ANTHROPIC_MODEL",
|
|
119
|
+
] as const;
|
|
112
120
|
|
|
113
121
|
/**
|
|
114
122
|
* Parse a boolean env toggle. Only true/1 enable and false/0 disable; unset
|
|
@@ -147,14 +155,33 @@ function resolveClaudeBinaryArgv(
|
|
|
147
155
|
return { raw, argv: parseClaudeBinary(raw), useClaudeBridge };
|
|
148
156
|
}
|
|
149
157
|
|
|
158
|
+
function isLegacyClaudeBridgeCompatBinary(raw: string): boolean {
|
|
159
|
+
return raw.toLowerCase().includes(LEGACY_CLAUDE_BRIDGE_COMPAT_BINARY);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function withClaudeBridgeAuthArgs(
|
|
163
|
+
argv: readonly string[],
|
|
164
|
+
sourceEnv: Record<string, string | undefined>,
|
|
165
|
+
): string[] {
|
|
166
|
+
if (sourceEnv.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
167
|
+
return [...argv];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (CLAUDE_BRIDGE_LOCAL_AUTH_ENV_VARS.some((name) => sourceEnv[name])) {
|
|
171
|
+
return [...argv, CLAUDE_BRIDGE_LOCAL_AUTH_ARG];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return [...argv];
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
/**
|
|
151
178
|
* Pre-seed `~/.claude.json` so the per-project trust-dialog ("Quick safety
|
|
152
179
|
* check: Is this a project you trust?") doesn't block on first run.
|
|
153
180
|
*
|
|
154
181
|
* Mirrors the onboarding-skip hack in `Dockerfile.worker` (which writes
|
|
155
182
|
* `hasCompletedOnboarding` and `bypassPermissionsModeAccepted`). When the
|
|
156
|
-
* resolved binary
|
|
157
|
-
*
|
|
183
|
+
* resolved binary runs interactive claude inside tmux, claude does NOT
|
|
184
|
+
* reliably auto-accept the dialog, so the pane can hang forever. Writing
|
|
158
185
|
* `projects[cwd].hasTrustDialogAccepted = true` (and `hasCompletedProjectOnboarding`)
|
|
159
186
|
* tells claude-code the cwd is pre-trusted.
|
|
160
187
|
*
|
|
@@ -832,54 +859,59 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
832
859
|
|
|
833
860
|
const model = config.model || "opus";
|
|
834
861
|
|
|
835
|
-
const
|
|
862
|
+
const sourceEnv = config.env || process.env;
|
|
863
|
+
const credType = validateClaudeCredentials(sourceEnv);
|
|
836
864
|
console.log(`\x1b[2m[claude]\x1b[0m Using credential: ${credType}`);
|
|
837
865
|
|
|
838
866
|
// Resolve the argv prefix. Same flags (`-p`, `--model`, ...) work across
|
|
839
867
|
// alternates; only argv[0..n] changes. Prefer SWARM_USE_CLAUDE_BRIDGE=true
|
|
840
868
|
// for the Desplega-owned bridge. CLAUDE_BINARY remains as the low-level
|
|
841
|
-
// override for custom binaries and the
|
|
869
|
+
// override for custom binaries and the legacy third-party bridge path.
|
|
842
870
|
//
|
|
843
871
|
// `config.env` carries the swarm_config overlay (resolved repo > agent > global
|
|
844
872
|
// by `fetchResolvedEnv` in src/commands/runner.ts), so operators can flip
|
|
845
873
|
// a worker's binary via `set-config CLAUDE_BINARY=...` without a restart.
|
|
846
874
|
// Falls back to process.env, then "claude". See `resolveClaudeBinary` above.
|
|
847
875
|
//
|
|
848
|
-
// See `docs-site/.../
|
|
876
|
+
// See `docs-site/.../claude-bridge-experimental.mdx` for the user-facing guide
|
|
849
877
|
// and `runbooks/harness-providers.md` for engineering notes.
|
|
850
878
|
const {
|
|
851
879
|
raw: claudeBinaryRaw,
|
|
852
880
|
argv: claudeBinaryArgv,
|
|
853
881
|
useClaudeBridge,
|
|
854
|
-
} = resolveClaudeBinaryArgv(
|
|
855
|
-
const
|
|
856
|
-
const
|
|
857
|
-
|
|
882
|
+
} = resolveClaudeBinaryArgv(sourceEnv);
|
|
883
|
+
const isLegacyBridgeCompat = isLegacyClaudeBridgeCompatBinary(claudeBinaryRaw);
|
|
884
|
+
const effectiveClaudeBinaryArgv = useClaudeBridge
|
|
885
|
+
? withClaudeBridgeAuthArgs(claudeBinaryArgv, sourceEnv)
|
|
886
|
+
: claudeBinaryArgv;
|
|
887
|
+
const isInteractiveTmuxClaude = isLegacyBridgeCompat || useClaudeBridge;
|
|
888
|
+
const configuredClaudeBinaryRaw = resolveClaudeBinary(sourceEnv);
|
|
889
|
+
if (isLegacyClaudeBridgeCompatBinary(configuredClaudeBinaryRaw)) {
|
|
858
890
|
console.warn(
|
|
859
|
-
|
|
891
|
+
`\x1b[33m[claude]\x1b[0m CLAUDE_BINARY=${LEGACY_CLAUDE_BRIDGE_COMPAT_BINARY} is deprecated; set SWARM_USE_CLAUDE_BRIDGE=true to use @desplega.ai/claude-bridge.`,
|
|
860
892
|
);
|
|
861
893
|
}
|
|
862
894
|
|
|
863
895
|
console.log(
|
|
864
|
-
`\x1b[2m[${config.role}]\x1b[0m Resolved claude binary: ${
|
|
896
|
+
`\x1b[2m[${config.role}]\x1b[0m Resolved claude binary: ${effectiveClaudeBinaryArgv.join(" ")} (useClaudeBridge: ${useClaudeBridge}, legacyBridgeCompat: ${isLegacyBridgeCompat})`,
|
|
865
897
|
);
|
|
866
898
|
|
|
867
|
-
// Fail fast:
|
|
899
|
+
// Fail fast: claude-bridge and its legacy compatibility path both shell
|
|
900
|
+
// out to tmux. If it's
|
|
868
901
|
// missing, surface a clear error here rather than letting startup fail
|
|
869
902
|
// opaquely.
|
|
870
|
-
if (
|
|
871
|
-
const label = useClaudeBridge
|
|
903
|
+
if (isInteractiveTmuxClaude && !Bun.which("tmux")) {
|
|
904
|
+
const label = useClaudeBridge
|
|
905
|
+
? "SWARM_USE_CLAUDE_BRIDGE=true"
|
|
906
|
+
: `CLAUDE_BINARY=${LEGACY_CLAUDE_BRIDGE_COMPAT_BINARY}`;
|
|
872
907
|
throw new Error(
|
|
873
908
|
`${label} requires 'tmux' on PATH (install via apt/brew). See runbooks/harness-providers.md.`,
|
|
874
909
|
);
|
|
875
910
|
}
|
|
876
911
|
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
|
|
880
|
-
// prompts. Idempotent; no-op when already trusted. Engineering rationale:
|
|
881
|
-
// `runbooks/harness-providers.md` § "Trust-dialog pre-seed".
|
|
882
|
-
if (isShannon) {
|
|
912
|
+
// Claude Bridge and its legacy compatibility path drive interactive
|
|
913
|
+
// `claude` in tmux, where the first-run trust dialog can block startup.
|
|
914
|
+
if (isInteractiveTmuxClaude) {
|
|
883
915
|
try {
|
|
884
916
|
await preseedClaudeTrustDialog(config.cwd);
|
|
885
917
|
} catch (err) {
|
|
@@ -944,7 +976,7 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
944
976
|
taskFilePath,
|
|
945
977
|
taskFilePid,
|
|
946
978
|
sessionMcpConfig,
|
|
947
|
-
|
|
979
|
+
effectiveClaudeBinaryArgv,
|
|
948
980
|
systemPromptFile,
|
|
949
981
|
);
|
|
950
982
|
}
|
|
@@ -124,10 +124,11 @@ export function buildWorkflowCtx(input: {
|
|
|
124
124
|
status: "completed" | "failed",
|
|
125
125
|
result?: unknown,
|
|
126
126
|
error?: string,
|
|
127
|
+
durationMs?: number,
|
|
127
128
|
): Promise<void> {
|
|
128
129
|
const body = (await fetchJson(`/api/internal/script-runs/${input.runId}/steps`, {
|
|
129
130
|
method: "POST",
|
|
130
|
-
body: JSON.stringify({ stepKey: label, stepType, config, status, result, error }),
|
|
131
|
+
body: JSON.stringify({ stepKey: label, stepType, config, status, result, error, durationMs }),
|
|
131
132
|
})) as StepWriteResponse;
|
|
132
133
|
if (!("ok" in body)) throw new Error(`Failed to write journal step ${label}`);
|
|
133
134
|
}
|
|
@@ -140,13 +141,16 @@ export function buildWorkflowCtx(input: {
|
|
|
140
141
|
): Promise<unknown> {
|
|
141
142
|
const replayed = await completedStep(label);
|
|
142
143
|
if (replayed.found) return replayed.result;
|
|
144
|
+
const startedAt = Date.now();
|
|
143
145
|
try {
|
|
144
146
|
const result = await execute();
|
|
145
|
-
|
|
147
|
+
const durationMs = Date.now() - startedAt;
|
|
148
|
+
await writeStep(label, stepType, config, "completed", result, undefined, durationMs);
|
|
146
149
|
return result;
|
|
147
150
|
} catch (err) {
|
|
151
|
+
const durationMs = Date.now() - startedAt;
|
|
148
152
|
const error = err instanceof Error ? err.message : String(err);
|
|
149
|
-
await writeStep(label, stepType, config, "failed", undefined, error);
|
|
153
|
+
await writeStep(label, stepType, config, "failed", undefined, error, durationMs);
|
|
150
154
|
throw err;
|
|
151
155
|
}
|
|
152
156
|
}
|
|
@@ -290,6 +290,19 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
|
|
|
290
290
|
if (!id) throw new Error("skill_get requires string `id`");
|
|
291
291
|
return { method: "GET", path: `/api/skills/${encodeURIComponent(id)}` };
|
|
292
292
|
}
|
|
293
|
+
case "skill_getFile": {
|
|
294
|
+
const skillId = typeof body.skillId === "string" ? body.skillId : undefined;
|
|
295
|
+
const path = typeof body.path === "string" ? body.path : undefined;
|
|
296
|
+
if (!skillId) throw new Error("skill_getFile requires string `skillId`");
|
|
297
|
+
if (!path) throw new Error("skill_getFile requires string `path`");
|
|
298
|
+
return {
|
|
299
|
+
method: "GET",
|
|
300
|
+
path: `/api/skills/${encodeURIComponent(skillId)}/files/${path
|
|
301
|
+
.split("/")
|
|
302
|
+
.map(encodeURIComponent)
|
|
303
|
+
.join("/")}`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
293
306
|
case "skill_search":
|
|
294
307
|
return { method: "POST", path: "/api/skills/search", body };
|
|
295
308
|
case "skill_delete": {
|
|
@@ -298,6 +298,7 @@ declare module "swarm-sdk" {
|
|
|
298
298
|
includeBuiltin?: boolean;
|
|
299
299
|
}): Promise<unknown>;
|
|
300
300
|
skill_get(args: { id: string }): Promise<unknown>;
|
|
301
|
+
skill_getFile(args: { skillId: string; path: string }): Promise<unknown>;
|
|
301
302
|
skill_search(args: { query: string; limit?: number }): Promise<unknown>;
|
|
302
303
|
skill_create(args: Record<string, unknown>): Promise<unknown>;
|
|
303
304
|
skill_update(args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -280,6 +280,7 @@ declare module "swarm-sdk" {
|
|
|
280
280
|
includeBuiltin?: boolean;
|
|
281
281
|
}): Promise<unknown>;
|
|
282
282
|
skill_get(args: { id: string }): Promise<unknown>;
|
|
283
|
+
skill_getFile(args: { skillId: string; path: string }): Promise<unknown>;
|
|
283
284
|
skill_search(args: { query: string; limit?: number }): Promise<unknown>;
|
|
284
285
|
skill_create(args: Record<string, unknown>): Promise<unknown>;
|
|
285
286
|
skill_update(args: Record<string, unknown>): Promise<unknown>;
|
package/src/server.ts
CHANGED
|
@@ -86,6 +86,7 @@ import { registerSendTaskTool } from "./tools/send-task";
|
|
|
86
86
|
import {
|
|
87
87
|
registerSkillCreateTool,
|
|
88
88
|
registerSkillDeleteTool,
|
|
89
|
+
registerSkillGetFileTool,
|
|
89
90
|
registerSkillGetTool,
|
|
90
91
|
registerSkillInstallRemoteTool,
|
|
91
92
|
registerSkillInstallTool,
|
|
@@ -329,6 +330,7 @@ export function createServer() {
|
|
|
329
330
|
registerSkillUpdateTool(server);
|
|
330
331
|
registerSkillDeleteTool(server);
|
|
331
332
|
registerSkillGetTool(server);
|
|
333
|
+
registerSkillGetFileTool(server);
|
|
332
334
|
registerSkillListTool(server);
|
|
333
335
|
registerSkillSearchTool(server);
|
|
334
336
|
registerSkillInstallTool(server);
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createTaskExtended,
|
|
3
3
|
getActiveTaskCount,
|
|
4
4
|
getAgentById,
|
|
5
|
+
getDependentTasks,
|
|
5
6
|
getLeadAgent,
|
|
6
7
|
getTaskAttachments,
|
|
7
8
|
getTaskById,
|
|
@@ -115,6 +116,17 @@ export function createWorkerTaskFollowUp(args: {
|
|
|
115
116
|
task_id: task.id,
|
|
116
117
|
});
|
|
117
118
|
followUpDescription = failedResult.text;
|
|
119
|
+
|
|
120
|
+
// Enrich with cascade info: list dependents that were cascade-failed.
|
|
121
|
+
const cascadedDeps = getDependentTasks(task.id, { includeTerminal: true }).filter(
|
|
122
|
+
(t) => t.status === "failed" && t.failureReason?.includes("Blocked dependency"),
|
|
123
|
+
);
|
|
124
|
+
if (cascadedDeps.length > 0) {
|
|
125
|
+
const depLines = cascadedDeps.map(
|
|
126
|
+
(d) => `- ${d.id.slice(0, 8)} — "${d.task.slice(0, 100)}" (${d.failureReason})`,
|
|
127
|
+
);
|
|
128
|
+
followUpDescription += `\n\n⚠️ Cascade impact: ${cascadedDeps.length} dependent task(s) were also failed because they depend on this task:\n${depLines.join("\n")}`;
|
|
129
|
+
}
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
return createTaskExtended(followUpDescription, {
|