@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.
Files changed (90) hide show
  1. package/README.md +1 -1
  2. package/openapi.json +276 -3
  3. package/package.json +6 -6
  4. package/plugin/skills/pages/SKILL.md +5 -2
  5. package/src/be/db.ts +416 -20
  6. package/src/be/memory/boot-reembed.ts +85 -0
  7. package/src/be/memory/constants.ts +44 -2
  8. package/src/be/memory/providers/openai-embedding.ts +15 -5
  9. package/src/be/memory/providers/sqlite-store.ts +325 -76
  10. package/src/be/memory/reranker.ts +35 -17
  11. package/src/be/memory/types.ts +43 -0
  12. package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
  13. package/src/be/migrations/085_script_runs_kind.sql +9 -0
  14. package/src/be/migrations/086_pages_default_authed.sql +64 -0
  15. package/src/be/migrations/087_skill_files.sql +19 -0
  16. package/src/be/modelsdev-cache.json +5622 -2543
  17. package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
  18. package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
  19. package/src/be/seed-scripts/catalog/compound-insights.ts +465 -0
  20. package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
  21. package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
  22. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +34 -439
  23. package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
  24. package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
  25. package/src/be/seed-scripts/index.ts +32 -4
  26. package/src/be/seed-skills/index.ts +0 -7
  27. package/src/be/skill-sync.ts +91 -7
  28. package/src/commands/runner.ts +6 -2
  29. package/src/heartbeat/templates.ts +20 -16
  30. package/src/http/index.ts +50 -7
  31. package/src/http/mcp-user.ts +23 -0
  32. package/src/http/mcp.ts +58 -0
  33. package/src/http/memory.ts +62 -0
  34. package/src/http/pages.ts +1 -1
  35. package/src/http/script-runs.ts +2 -0
  36. package/src/http/scripts.ts +39 -2
  37. package/src/http/skills.ts +225 -0
  38. package/src/providers/claude-adapter.ts +56 -24
  39. package/src/script-workflows/workflow-ctx.ts +7 -3
  40. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  41. package/src/scripts-runtime/swarm-sdk.ts +13 -0
  42. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  43. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  44. package/src/server.ts +2 -0
  45. package/src/tasks/worker-follow-up.ts +12 -0
  46. package/src/tests/claude-adapter-binary.test.ts +135 -81
  47. package/src/tests/create-page-tool.test.ts +19 -2
  48. package/src/tests/heartbeat-checklist.test.ts +36 -0
  49. package/src/tests/mcp-transport-gc.test.ts +58 -0
  50. package/src/tests/memory-e2e.test.ts +6 -6
  51. package/src/tests/memory-health-endpoint.test.ts +78 -0
  52. package/src/tests/memory-rater-e2e.test.ts +4 -5
  53. package/src/tests/memory-reranker.test.ts +135 -124
  54. package/src/tests/memory-store.test.ts +221 -1
  55. package/src/tests/memory.test.ts +13 -12
  56. package/src/tests/pages-http.test.ts +20 -2
  57. package/src/tests/pages-storage.test.ts +26 -0
  58. package/src/tests/scripts-mcp-e2e.test.ts +53 -0
  59. package/src/tests/seed-scripts.test.ts +328 -3
  60. package/src/tests/skill-files-http.test.ts +171 -0
  61. package/src/tests/skill-files.test.ts +162 -0
  62. package/src/tests/skill-get-file-tool.test.ts +110 -0
  63. package/src/tests/skill-sync.test.ts +125 -6
  64. package/src/tests/task-cascade-fail.test.ts +304 -0
  65. package/src/tools/create-page.ts +2 -2
  66. package/src/tools/skills/index.ts +1 -0
  67. package/src/tools/skills/skill-get-file.ts +80 -0
  68. package/src/tools/tool-config.ts +2 -1
  69. package/src/types.ts +20 -0
  70. package/src/utils/internal-ai/complete-structured.ts +2 -2
  71. package/templates/schedules/daily-blocker-digest/content.md +68 -54
  72. package/templates/schedules/daily-compounding-reflection/content.md +4 -4
  73. package/templates/schedules/daily-hn-briefing/content.md +5 -5
  74. package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
  75. package/templates/schedules/gtm-weekly-review/content.md +9 -9
  76. package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
  77. package/templates/skills/agentmail-sending/content.md +6 -7
  78. package/templates/skills/desloppify/content.md +8 -9
  79. package/templates/skills/jira-interaction/content.md +25 -33
  80. package/templates/skills/kapso-whatsapp/content.md +29 -30
  81. package/templates/skills/linear-interaction/content.md +8 -9
  82. package/templates/skills/profile-corruption-escalation/content.md +44 -85
  83. package/templates/skills/sprite-cli/content.md +4 -5
  84. package/templates/skills/turso-interaction/content.md +14 -17
  85. package/templates/skills/workflow-iterate/content.md +38 -391
  86. package/templates/skills/x-api-interactions/content.md +4 -6
  87. package/templates/workflows/llm-safe-release-context/config.json +13 -0
  88. package/templates/workflows/llm-safe-release-context/content.md +69 -0
  89. package/templates/skills/scheduled-task-resilience/config.json +0 -14
  90. 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"`, `"shannon"`), an absolute path,
76
- * or a whitespace-separated command string (`"bunx @dexh/shannon"`,
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 contains "shannon", claude runs inside tmux and shannon
157
- * does NOT auto-accept the dialog, so the pane hangs forever. Writing
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 credType = validateClaudeCredentials(config.env || process.env);
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 deprecated shannon path.
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/.../shannon-experimental.mdx` for the user-facing guide
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(config.env || process.env);
855
- const isShannon = claudeBinaryRaw.toLowerCase().includes("shannon");
856
- const configuredClaudeBinaryRaw = resolveClaudeBinary(config.env || process.env);
857
- if (configuredClaudeBinaryRaw.toLowerCase().includes("shannon")) {
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
- "\x1b[33m[claude]\x1b[0m CLAUDE_BINARY=shannon is deprecated; set SWARM_USE_CLAUDE_BRIDGE=true to use @desplega.ai/claude-bridge.",
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: ${claudeBinaryArgv.join(" ")} (useClaudeBridge: ${useClaudeBridge}, isShannon: ${isShannon})`,
896
+ `\x1b[2m[${config.role}]\x1b[0m Resolved claude binary: ${effectiveClaudeBinaryArgv.join(" ")} (useClaudeBridge: ${useClaudeBridge}, legacyBridgeCompat: ${isLegacyBridgeCompat})`,
865
897
  );
866
898
 
867
- // Fail fast: shannon and claude-bridge both shell out to tmux. If it's
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 ((isShannon || useClaudeBridge) && !Bun.which("tmux")) {
871
- const label = useClaudeBridge ? "SWARM_USE_CLAUDE_BRIDGE=true" : "CLAUDE_BINARY=shannon";
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
- // Shannon drives `claude` in tmux claude's per-project trust dialog
878
- // (first-run "Is this a project you trust?") hangs the pane because shannon
879
- // doesn't auto-accept it. Pre-seed `~/.claude.json` so the dialog never
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
- claudeBinaryArgv,
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
- await writeStep(label, stepType, config, "completed", result);
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
  }
@@ -115,6 +115,7 @@ export const SDK_TOOL_NAME_MAP = {
115
115
  // ── skills ──
116
116
  skill_list: "skill-list",
117
117
  skill_get: "skill-get",
118
+ skill_getFile: "skill-get-file",
118
119
  skill_search: "skill-search",
119
120
  skill_create: "skill-create",
120
121
  skill_update: "skill-update",
@@ -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, {