@desplega.ai/agent-swarm 1.87.0 → 1.88.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.
Files changed (59) hide show
  1. package/README.md +2 -1
  2. package/openapi.json +13 -1
  3. package/package.json +5 -5
  4. package/src/be/db.ts +49 -7
  5. package/src/be/migrations/080_skill_system_defaults.sql +8 -0
  6. package/src/be/modelsdev-cache.json +1123 -1034
  7. package/src/be/seed/registry.ts +3 -2
  8. package/src/be/seed-skills/index.ts +172 -0
  9. package/src/cli.tsx +33 -4
  10. package/src/commands/e2b-stack-wizard.tsx +394 -0
  11. package/src/commands/e2b.ts +1352 -53
  12. package/src/commands/onboard/dashboard-url.ts +29 -0
  13. package/src/commands/onboard/steps/post-dashboard.tsx +3 -1
  14. package/src/commands/onboard.tsx +3 -1
  15. package/src/commands/runner.ts +1 -0
  16. package/src/e2b/dispatch.ts +234 -18
  17. package/src/http/memory.ts +13 -1
  18. package/src/http/skills.ts +53 -0
  19. package/src/http/webhooks.ts +75 -0
  20. package/src/integrations/kapso/client.ts +82 -0
  21. package/src/memory/automatic-task-gate.ts +47 -0
  22. package/src/prompts/base-prompt.ts +16 -1
  23. package/src/prompts/session-templates.ts +51 -0
  24. package/src/providers/claude-adapter.ts +19 -0
  25. package/src/providers/codex-adapter.ts +22 -0
  26. package/src/providers/ctx-mode-env.ts +10 -0
  27. package/src/providers/opencode-adapter.ts +50 -1
  28. package/src/slack/blocks.ts +12 -4
  29. package/src/slack/watcher.ts +3 -3
  30. package/src/telemetry.ts +14 -1
  31. package/src/templates.d.ts +4 -0
  32. package/src/tests/base-prompt.test.ts +41 -0
  33. package/src/tests/claude-adapter.test.ts +86 -1
  34. package/src/tests/codex-adapter.test.ts +89 -0
  35. package/src/tests/e2b-dispatch.test.ts +603 -11
  36. package/src/tests/http-api-integration.test.ts +113 -0
  37. package/src/tests/kapso-client.test.ts +74 -1
  38. package/src/tests/kapso-inbound.test.ts +60 -2
  39. package/src/tests/opencode-adapter.test.ts +95 -0
  40. package/src/tests/prompt-template-session.test.ts +4 -2
  41. package/src/tests/self-improvement.test.ts +89 -0
  42. package/src/tests/skill-update-scope.test.ts +88 -1
  43. package/src/tests/slack-blocks.test.ts +15 -0
  44. package/src/tests/system-default-skills.test.ts +119 -0
  45. package/src/tests/telemetry-init.test.ts +86 -0
  46. package/src/tools/skills/skill-delete.ts +14 -0
  47. package/src/tools/skills/skill-update.ts +14 -0
  48. package/src/tools/store-progress.ts +19 -5
  49. package/src/types.ts +1 -0
  50. package/templates/skills/artifacts/config.json +1 -0
  51. package/templates/skills/kv-storage/config.json +1 -0
  52. package/templates/skills/pages/config.json +1 -0
  53. package/templates/skills/scheduled-task-resilience/config.json +1 -0
  54. package/templates/skills/swarm-scripts/SKILL.md +91 -0
  55. package/templates/skills/swarm-scripts/config.json +14 -0
  56. package/templates/skills/swarm-scripts/content.md +86 -0
  57. package/templates/skills/workflow-iterate/config.json +1 -0
  58. package/templates/skills/workflow-structured-output/config.json +1 -0
  59. package/tsconfig.json +2 -1
@@ -246,6 +246,23 @@ describe("mergeMcpConfig (issue #369)", () => {
246
246
  const merged = mergeMcpConfig({ mcpServers: {} }, {}, TASK_ID);
247
247
  expect(Object.keys(merged.mcpServers)).toHaveLength(0);
248
248
  });
249
+
250
+ test("preserves a context-mode entry through the merge", () => {
251
+ const base = {
252
+ mcpServers: {
253
+ "agent-swarm": {
254
+ type: "http",
255
+ url: "http://localhost:3013/mcp",
256
+ headers: { Authorization: "Bearer KEY", "X-Agent-ID": "a1" },
257
+ },
258
+ "plugin_context-mode_context-mode": { command: "context-mode" },
259
+ },
260
+ };
261
+ const merged = mergeMcpConfig(base, null, TASK_ID);
262
+ expect(merged.mcpServers["plugin_context-mode_context-mode"]).toEqual({
263
+ command: "context-mode",
264
+ });
265
+ });
249
266
  });
250
267
 
251
268
  describe("createSessionMcpConfig", () => {
@@ -323,7 +340,13 @@ describe("createSessionMcpConfig", () => {
323
340
  const written = await readWritten(path!);
324
341
  expect(written.mcpServers["agent-swarm"]).toBeDefined();
325
342
  expect(written.mcpServers.Datadog).toBeDefined();
326
- expect(Object.keys(written.mcpServers).sort()).toEqual(["Datadog", "agent-swarm"]);
343
+ // context-mode is injected by default (see CONTEXT_MODE_DISABLED gate); the
344
+ // two differently-named .mcp.json servers still merge alongside it.
345
+ expect(Object.keys(written.mcpServers).sort()).toEqual([
346
+ "Datadog",
347
+ "agent-swarm",
348
+ "plugin_context-mode_context-mode",
349
+ ]);
327
350
  });
328
351
 
329
352
  test("ancestor wins over repo-local on agent-swarm key conflict", async () => {
@@ -402,4 +425,66 @@ describe("createSessionMcpConfig", () => {
402
425
  const written = await readWritten(path!);
403
426
  expect(written.mcpServers["from-api"]).toBeDefined();
404
427
  });
428
+
429
+ test("includes context-mode entry when CONTEXT_MODE_DISABLED is unset", async () => {
430
+ const prev = process.env.CONTEXT_MODE_DISABLED;
431
+ delete process.env.CONTEXT_MODE_DISABLED;
432
+ try {
433
+ await writeFile(
434
+ join(sandbox, ".mcp.json"),
435
+ JSON.stringify({
436
+ mcpServers: {
437
+ "agent-swarm": {
438
+ type: "http",
439
+ url: "http://swarm/mcp",
440
+ headers: { Authorization: "Bearer SWARM", "X-Agent-ID": "a1" },
441
+ },
442
+ },
443
+ }),
444
+ );
445
+ const cwd = join(sandbox, "repos", "foo");
446
+ await mkdir(cwd, { recursive: true });
447
+
448
+ const path = await createSessionMcpConfig(cwd, "task-ctx-on");
449
+ const written = await readWritten(path!);
450
+ expect(written.mcpServers["plugin_context-mode_context-mode"]).toEqual({
451
+ command: "context-mode",
452
+ });
453
+ // Coexists with the swarm entry.
454
+ expect(written.mcpServers["agent-swarm"]).toBeDefined();
455
+ } finally {
456
+ if (prev === undefined) delete process.env.CONTEXT_MODE_DISABLED;
457
+ else process.env.CONTEXT_MODE_DISABLED = prev;
458
+ }
459
+ });
460
+
461
+ test("excludes context-mode entry when CONTEXT_MODE_DISABLED='true'", async () => {
462
+ const prev = process.env.CONTEXT_MODE_DISABLED;
463
+ process.env.CONTEXT_MODE_DISABLED = "true";
464
+ try {
465
+ await writeFile(
466
+ join(sandbox, ".mcp.json"),
467
+ JSON.stringify({
468
+ mcpServers: {
469
+ "agent-swarm": {
470
+ type: "http",
471
+ url: "http://swarm/mcp",
472
+ headers: { Authorization: "Bearer SWARM", "X-Agent-ID": "a1" },
473
+ },
474
+ },
475
+ }),
476
+ );
477
+ const cwd = join(sandbox, "repos", "foo");
478
+ await mkdir(cwd, { recursive: true });
479
+
480
+ const path = await createSessionMcpConfig(cwd, "task-ctx-off");
481
+ const written = await readWritten(path!);
482
+ expect(written.mcpServers["plugin_context-mode_context-mode"]).toBeUndefined();
483
+ // The swarm entry is still present.
484
+ expect(written.mcpServers["agent-swarm"]).toBeDefined();
485
+ } finally {
486
+ if (prev === undefined) delete process.env.CONTEXT_MODE_DISABLED;
487
+ else process.env.CONTEXT_MODE_DISABLED = prev;
488
+ }
489
+ });
405
490
  });
@@ -872,9 +872,21 @@ describe("computeCodexCostUsd", () => {
872
872
  describe("buildCodexConfig", () => {
873
873
  // Save and restore the global fetch so we don't leak mocks between tests.
874
874
  const originalFetch = globalThis.fetch;
875
+ // These tests assert the EXACT set of mcp_servers keys, which is only the
876
+ // installed-server merge logic. Disable the always-on context-mode entry so
877
+ // those exact-key assertions stay valid; a dedicated block below verifies
878
+ // the context-mode + features behavior. Save/restore the env to avoid leaks.
879
+ let prevContextModeDisabled: string | undefined;
880
+
881
+ beforeEach(() => {
882
+ prevContextModeDisabled = process.env.CONTEXT_MODE_DISABLED;
883
+ process.env.CONTEXT_MODE_DISABLED = "true";
884
+ });
875
885
 
876
886
  afterEach(() => {
877
887
  globalThis.fetch = originalFetch;
888
+ if (prevContextModeDisabled === undefined) delete process.env.CONTEXT_MODE_DISABLED;
889
+ else process.env.CONTEXT_MODE_DISABLED = prevContextModeDisabled;
878
890
  });
879
891
 
880
892
  // Helper: build a ProviderSessionConfig pointed at a mock endpoint.
@@ -1098,6 +1110,83 @@ describe("buildCodexConfig", () => {
1098
1110
  });
1099
1111
  });
1100
1112
 
1113
+ // ─── Phase 3: buildCodexConfig — context-mode MCP + hook feature flags ───────
1114
+
1115
+ describe("buildCodexConfig — context-mode + features", () => {
1116
+ const originalFetch = globalThis.fetch;
1117
+ // Explicitly own CONTEXT_MODE_DISABLED here. Save the ambient value up front
1118
+ // and restore it after every test so we never leak the mutation to siblings.
1119
+ let prevContextModeDisabled: string | undefined;
1120
+
1121
+ beforeEach(() => {
1122
+ prevContextModeDisabled = process.env.CONTEXT_MODE_DISABLED;
1123
+ });
1124
+
1125
+ afterEach(() => {
1126
+ globalThis.fetch = originalFetch;
1127
+ if (prevContextModeDisabled === undefined) delete process.env.CONTEXT_MODE_DISABLED;
1128
+ else process.env.CONTEXT_MODE_DISABLED = prevContextModeDisabled;
1129
+ });
1130
+
1131
+ function cfg(overrides: Partial<ProviderSessionConfig> = {}): ProviderSessionConfig {
1132
+ return {
1133
+ prompt: "hello",
1134
+ systemPrompt: "",
1135
+ model: "gpt-5.4",
1136
+ role: "worker",
1137
+ agentId: "agent-mcp-test",
1138
+ taskId: "task-mcp-test",
1139
+ apiUrl: "http://test.invalid",
1140
+ apiKey: "test-key",
1141
+ cwd: "",
1142
+ logFile: `/tmp/codex-ctx-test-${Date.now()}-${Math.random().toString(36).slice(2)}.log`,
1143
+ ...overrides,
1144
+ };
1145
+ }
1146
+
1147
+ function stubFetch(body: unknown, status = 200): typeof globalThis.fetch {
1148
+ return async (): Promise<Response> => {
1149
+ return new Response(JSON.stringify(body), {
1150
+ status,
1151
+ headers: { "Content-Type": "application/json" },
1152
+ });
1153
+ };
1154
+ }
1155
+
1156
+ test("includes the 'context-mode' mcp_servers entry by default", async () => {
1157
+ delete process.env.CONTEXT_MODE_DISABLED;
1158
+ globalThis.fetch = stubFetch({ servers: [], total: 0 });
1159
+ const merged = await buildCodexConfig(cfg(), "gpt-5.4", () => {});
1160
+ const mcp = merged.mcp_servers as Record<string, Record<string, unknown>>;
1161
+
1162
+ expect(Object.keys(mcp).sort()).toEqual(["agent-swarm", "context-mode"]);
1163
+ expect(mcp["context-mode"]?.command).toBe("context-mode");
1164
+ expect(mcp["context-mode"]?.enabled).toBe(true);
1165
+ expect(mcp["context-mode"]?.startup_timeout_sec).toBe(30);
1166
+ expect(mcp["context-mode"]?.tool_timeout_sec).toBe(120);
1167
+ });
1168
+
1169
+ test("excludes the 'context-mode' entry when CONTEXT_MODE_DISABLED=true", async () => {
1170
+ process.env.CONTEXT_MODE_DISABLED = "true";
1171
+ globalThis.fetch = stubFetch({ servers: [], total: 0 });
1172
+ const merged = await buildCodexConfig(cfg(), "gpt-5.4", () => {});
1173
+ const mcp = merged.mcp_servers as Record<string, Record<string, unknown>>;
1174
+
1175
+ expect(Object.keys(mcp)).toEqual(["agent-swarm"]);
1176
+ expect(mcp["context-mode"]).toBeUndefined();
1177
+ });
1178
+
1179
+ test("sets features.hooks and features.plugin_hooks to true", async () => {
1180
+ delete process.env.CONTEXT_MODE_DISABLED;
1181
+ globalThis.fetch = stubFetch({ servers: [], total: 0 });
1182
+ const merged = await buildCodexConfig(cfg(), "gpt-5.4", () => {});
1183
+
1184
+ const features = merged.features as Record<string, unknown>;
1185
+ expect(features.hooks).toBe(true);
1186
+ expect(features.plugin_hooks).toBe(true);
1187
+ });
1188
+ });
1189
+
1101
1190
  // ─────────────────────────────────────────────────────────────────────────────
1102
1191
  // Phase 3 — session-end summarization
1103
1192
  // ─────────────────────────────────────────────────────────────────────────────