@desplega.ai/agent-swarm 1.92.2 → 1.93.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 (76) hide show
  1. package/openapi.json +63 -3
  2. package/package.json +5 -5
  3. package/src/be/db.ts +91 -6
  4. package/src/be/memory/boot-reembed.ts +0 -1
  5. package/src/be/memory/providers/sqlite-store.ts +42 -25
  6. package/src/be/memory/raters/llm-client.ts +12 -5
  7. package/src/be/memory/types.ts +3 -0
  8. package/src/be/migrations/088_script_runs_list_indexes.sql +10 -0
  9. package/src/be/migrations/089_harness_variant.sql +2 -0
  10. package/src/be/modelsdev-cache.json +1222 -986
  11. package/src/be/seed-pricing.ts +1 -0
  12. package/src/be/seed-scripts/catalog/boot-triage.inline.ts +221 -0
  13. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +457 -0
  14. package/src/be/seed-scripts/catalog/compound-insights.inline.ts +863 -0
  15. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +506 -0
  16. package/src/be/seed-scripts/index.ts +5 -5
  17. package/src/be/skill-sync.ts +28 -179
  18. package/src/commands/runner.ts +124 -7
  19. package/src/http/api-keys.ts +42 -0
  20. package/src/http/mcp-bridge.ts +1 -1
  21. package/src/http/memory.ts +23 -24
  22. package/src/http/tasks.ts +10 -6
  23. package/src/providers/claude-adapter.ts +33 -1
  24. package/src/providers/claude-managed-adapter.ts +3 -0
  25. package/src/providers/claude-managed-models.ts +7 -0
  26. package/src/providers/codex-adapter.ts +8 -1
  27. package/src/providers/codex-models.ts +1 -0
  28. package/src/providers/codex-oauth/auth-json.ts +1 -0
  29. package/src/providers/harness-version.ts +7 -0
  30. package/src/providers/opencode-adapter.ts +11 -4
  31. package/src/providers/pi-mono-adapter.ts +12 -2
  32. package/src/providers/types.ts +2 -0
  33. package/src/scripts-runtime/egress-secrets.ts +83 -0
  34. package/src/scripts-runtime/eval-harness.ts +4 -0
  35. package/src/scripts-runtime/executors/types.ts +7 -0
  36. package/src/scripts-runtime/loader.ts +2 -0
  37. package/src/server-user.ts +2 -2
  38. package/src/slack/channel-join.ts +41 -0
  39. package/src/tests/additive-buffer.test.ts +0 -1
  40. package/src/tests/api-key-tracking.test.ts +113 -0
  41. package/src/tests/approval-requests.test.ts +0 -6
  42. package/src/tests/claude-managed-setup.test.ts +0 -4
  43. package/src/tests/codex-pool.test.ts +2 -6
  44. package/src/tests/http-api-integration.test.ts +4 -6
  45. package/src/tests/memory-edges.test.ts +0 -2
  46. package/src/tests/memory-rate-endpoint.test.ts +0 -2
  47. package/src/tests/memory-rater-e2e.test.ts +0 -2
  48. package/src/tests/memory-store.test.ts +19 -1
  49. package/src/tests/memory.test.ts +51 -0
  50. package/src/tests/model-control.test.ts +1 -1
  51. package/src/tests/reload-config.test.ts +33 -17
  52. package/src/tests/runner-skills-refresh.test.ts +216 -46
  53. package/src/tests/script-runs-http.test.ts +7 -1
  54. package/src/tests/scripts-runtime-secret-egress.test.ts +129 -0
  55. package/src/tests/seed-scripts.test.ts +13 -1
  56. package/src/tests/session-attach.test.ts +6 -6
  57. package/src/tests/skill-fs-writer.test.ts +250 -0
  58. package/src/tests/slack-attachments-block.test.ts +0 -1
  59. package/src/tests/slack-blocks.test.ts +0 -1
  60. package/src/tests/slack-channel-join.test.ts +80 -0
  61. package/src/tests/slack-identity-resolution.test.ts +0 -1
  62. package/src/tests/structured-output.test.ts +0 -2
  63. package/src/tests/use-dismissible-card.test.ts +0 -4
  64. package/src/tools/schedules/create-schedule.ts +2 -2
  65. package/src/tools/schedules/update-schedule.ts +1 -1
  66. package/src/tools/send-task.ts +2 -2
  67. package/src/tools/slack-post.ts +18 -15
  68. package/src/tools/slack-read.ts +9 -11
  69. package/src/tools/slack-reply.ts +18 -15
  70. package/src/tools/slack-start-thread.ts +17 -14
  71. package/src/tools/task-action.ts +2 -2
  72. package/src/types.ts +11 -0
  73. package/src/utils/context-window.ts +3 -0
  74. package/src/utils/credentials.ts +22 -2
  75. package/src/utils/skill-fs-writer.ts +220 -0
  76. package/src/utils/skills-refresh.ts +123 -40
@@ -24,11 +24,7 @@ import {
24
24
  credentialsToAuthJson,
25
25
  } from "../providers/codex-oauth/auth-json.js";
26
26
  import { materializeCodexAuthJson } from "../providers/codex-oauth/auth-json-fs.js";
27
- import {
28
- loadAllCodexOAuthSlots,
29
- persistCodexOAuth,
30
- storeCodexOAuth,
31
- } from "../providers/codex-oauth/storage.js";
27
+ import { loadAllCodexOAuthSlots, persistCodexOAuth } from "../providers/codex-oauth/storage.js";
32
28
  import type { CodexOAuthCredentials } from "../providers/codex-oauth/types.js";
33
29
 
34
30
  // ─── Fixtures ────────────────────────────────────────────────────────────────
@@ -117,7 +113,7 @@ afterEach(() => {
117
113
  describe("Scenario 1 — 3-slot round-trip with availability filter", () => {
118
114
  it("selects from available slots [1,2] and materialises the correct creds into auth.json", async () => {
119
115
  // Mock API: three slots in config store.
120
- globalThis.fetch = async (url: string | URL | Request) => {
116
+ globalThis.fetch = async (_url: string | URL | Request) => {
121
117
  return makeConfigResponse();
122
118
  // (available-indices endpoint not called by loadAllCodexOAuthSlots)
123
119
  };
@@ -25,7 +25,6 @@ async function api(
25
25
  method: string,
26
26
  path: string,
27
27
  opts: { body?: unknown; agentId?: string; headers?: Record<string, string> } = {},
28
- // biome-ignore lint/suspicious/noExplicitAny: test helper needs flexible body type
29
28
  ): Promise<{ status: number; body: any; ok: boolean }> {
30
29
  const headers: Record<string, string> = {
31
30
  "Content-Type": "application/json",
@@ -41,7 +40,6 @@ async function api(
41
40
  });
42
41
 
43
42
  const text = await res.text();
44
- // biome-ignore lint/suspicious/noExplicitAny: body can be parsed JSON or raw text
45
43
  let body: any;
46
44
  try {
47
45
  body = JSON.parse(text);
@@ -427,9 +425,9 @@ describe("Tasks", () => {
427
425
  expect(status).toBe(404);
428
426
  });
429
427
 
430
- test("PUT /api/tasks/:id/claude-session — update session ID", async () => {
428
+ test("PUT /api/tasks/:id/session — update session ID", async () => {
431
429
  const sessionId = randomUUID();
432
- const { status, body } = await put(`/api/tasks/${ids.task2}/claude-session`, {
430
+ const { status, body } = await put(`/api/tasks/${ids.task2}/session`, {
433
431
  agentId: ids.workerAgent,
434
432
  body: { claudeSessionId: sessionId },
435
433
  });
@@ -437,8 +435,8 @@ describe("Tasks", () => {
437
435
  expect(body.claudeSessionId).toBe(sessionId);
438
436
  });
439
437
 
440
- test("PUT /api/tasks/:id/claude-session — missing fields returns 400", async () => {
441
- const { status } = await put(`/api/tasks/${ids.task2}/claude-session`, {
438
+ test("PUT /api/tasks/:id/session — missing fields returns 400", async () => {
439
+ const { status } = await put(`/api/tasks/${ids.task2}/session`, {
442
440
  body: {},
443
441
  });
444
442
  expect(status).toBe(400);
@@ -48,7 +48,6 @@ async function api(
48
48
  method: string,
49
49
  path: string,
50
50
  opts: { body?: unknown; agentId?: string } = {},
51
- // biome-ignore lint/suspicious/noExplicitAny: test helper
52
51
  ): Promise<{ status: number; body: any }> {
53
52
  const headers: Record<string, string> = {
54
53
  "Content-Type": "application/json",
@@ -61,7 +60,6 @@ async function api(
61
60
  body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
62
61
  });
63
62
  const text = await res.text();
64
- // biome-ignore lint/suspicious/noExplicitAny: body may be JSON or text
65
63
  let body: any;
66
64
  try {
67
65
  body = JSON.parse(text);
@@ -41,7 +41,6 @@ async function api(
41
41
  method: string,
42
42
  path: string,
43
43
  opts: { body?: unknown; agentId?: string } = {},
44
- // biome-ignore lint/suspicious/noExplicitAny: test helper
45
44
  ): Promise<{ status: number; body: any }> {
46
45
  const headers: Record<string, string> = {
47
46
  "Content-Type": "application/json",
@@ -54,7 +53,6 @@ async function api(
54
53
  body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
55
54
  });
56
55
  const text = await res.text();
57
- // biome-ignore lint/suspicious/noExplicitAny: body may be JSON or text
58
56
  let body: any;
59
57
  try {
60
58
  body = JSON.parse(text);
@@ -64,7 +64,6 @@ async function api(
64
64
  method: string,
65
65
  path: string,
66
66
  opts: { body?: unknown; agentId?: string; sourceTaskId?: string } = {},
67
- // biome-ignore lint/suspicious/noExplicitAny: test helper
68
67
  ): Promise<{ status: number; body: any }> {
69
68
  const headers: Record<string, string> = {
70
69
  "Content-Type": "application/json",
@@ -78,7 +77,6 @@ async function api(
78
77
  body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
79
78
  });
80
79
  const text = await res.text();
81
- // biome-ignore lint/suspicious/noExplicitAny: body may be JSON or text
82
80
  let body: any;
83
81
  try {
84
82
  body = JSON.parse(text);
@@ -1,6 +1,6 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { unlink } from "node:fs/promises";
3
- import { closeDb, createAgent, getDb, initDb } from "../be/db";
3
+ import { closeDb, createAgent, getDb, initDb, isSqliteVecAvailable } from "../be/db";
4
4
  import { serializeEmbedding } from "../be/embedding";
5
5
  import { SqliteMemoryStore } from "../be/memory/providers/sqlite-store";
6
6
 
@@ -19,6 +19,16 @@ describe("SqliteMemoryStore", () => {
19
19
  return embedding;
20
20
  }
21
21
 
22
+ function skipVecAssertionsWhenUnavailable(): boolean {
23
+ if (isSqliteVecAvailable()) return false;
24
+
25
+ const health = store.getHealth();
26
+ expect(health.sqliteVec.extensionLoaded).toBe(false);
27
+ expect(health.retrievalMode).toBe("fallback");
28
+ expect(health.reasons).toContain("sqlite_vec_extension_unavailable");
29
+ return true;
30
+ }
31
+
22
32
  beforeAll(async () => {
23
33
  for (const suffix of ["", "-wal", "-shm"]) {
24
34
  try {
@@ -221,6 +231,8 @@ describe("SqliteMemoryStore", () => {
221
231
  });
222
232
 
223
233
  test("uses sqlite-vec for 512d embeddings with scope-filter parity", () => {
234
+ if (skipVecAssertionsWhenUnavailable()) return;
235
+
224
236
  for (let i = 0; i < 6; i++) {
225
237
  const otherAgent = store.store({
226
238
  agentId: agentB,
@@ -256,6 +268,8 @@ describe("SqliteMemoryStore", () => {
256
268
 
257
269
  describe("memory_vec population", () => {
258
270
  test("populates existing embeddings on startup and reports health counts", () => {
271
+ if (skipVecAssertionsWhenUnavailable()) return;
272
+
259
273
  const raw = store.store({
260
274
  agentId: agentA,
261
275
  scope: "agent",
@@ -282,6 +296,8 @@ describe("SqliteMemoryStore", () => {
282
296
  });
283
297
 
284
298
  test("rebuilds an old non-cosine memory_vec table from agent_memory", () => {
299
+ if (skipVecAssertionsWhenUnavailable()) return;
300
+
285
301
  const raw = store.store({
286
302
  agentId: agentA,
287
303
  scope: "agent",
@@ -473,6 +489,8 @@ describe("SqliteMemoryStore", () => {
473
489
  });
474
490
 
475
491
  test("also removes corresponding vec rows", () => {
492
+ if (skipVecAssertionsWhenUnavailable()) return;
493
+
476
494
  const db = getDb();
477
495
  const emb = vector({ 0: 0.9, 100: 0.1 });
478
496
 
@@ -479,6 +479,57 @@ describe("Memory System", () => {
479
479
  const page2 = store.list(listAgent, { scope: "agent", limit: 3, offset: 3 });
480
480
  expect(page2.length).toBe(2);
481
481
  });
482
+
483
+ test("counts memories with the same filters used by list", () => {
484
+ const countAgent = "eeee0000-0000-4000-8000-000000000105";
485
+ createAgent({ id: countAgent, name: "Count Agent", isLead: false, status: "idle" });
486
+
487
+ store.store({
488
+ agentId: countAgent,
489
+ scope: "agent",
490
+ name: "Count match 1",
491
+ content: "Content",
492
+ source: "file_index",
493
+ sourcePath: "/workspace/src/MemoryPage.tsx",
494
+ });
495
+ store.store({
496
+ agentId: countAgent,
497
+ scope: "swarm",
498
+ name: "Count match 2",
499
+ content: "Content",
500
+ source: "file_index",
501
+ sourcePath: "/workspace/SRC/memory-page.tsx",
502
+ });
503
+ store.store({
504
+ agentId: countAgent,
505
+ scope: "agent",
506
+ name: "Wrong source",
507
+ content: "Content",
508
+ source: "manual",
509
+ sourcePath: "/workspace/src/MemoryPage.tsx",
510
+ });
511
+ store.store({
512
+ agentId: agentA,
513
+ scope: "agent",
514
+ name: "Wrong owner",
515
+ content: "Content",
516
+ source: "file_index",
517
+ sourcePath: "/workspace/src/MemoryPage.tsx",
518
+ });
519
+
520
+ const filters = {
521
+ scope: "all" as const,
522
+ isLead: true,
523
+ ownerAgentId: countAgent,
524
+ source: "file_index" as const,
525
+ sourcePath: "src/memory",
526
+ };
527
+
528
+ expect(store.count("", filters)).toBe(2);
529
+ expect(store.list("", { ...filters, limit: 1, offset: 0 })).toHaveLength(1);
530
+ expect(store.list("", { ...filters, limit: 1, offset: 1 })).toHaveLength(1);
531
+ expect(store.list("", { ...filters, limit: 1, offset: 2 })).toHaveLength(0);
532
+ });
482
533
  });
483
534
 
484
535
  describe("store.delete (deleteMemory)", () => {
@@ -102,7 +102,7 @@ describe("Model Control - Schedule Creation", () => {
102
102
  });
103
103
 
104
104
  test("should store all valid model values on schedules", () => {
105
- for (const model of ["haiku", "sonnet", "opus"] as const) {
105
+ for (const model of ["haiku", "sonnet", "opus", "fable"] as const) {
106
106
  const schedule = createScheduledTask({
107
107
  name: `model-schedule-all-${model}-${Date.now()}`,
108
108
  intervalMs: 60000,
@@ -14,6 +14,37 @@ import {
14
14
 
15
15
  const TEST_DB_PATH = "./test-reload-config.sqlite";
16
16
  const TEST_PORT = 13023;
17
+ const INTEGRATION_DISABLE_KEYS = [
18
+ "AGENTMAIL_DISABLE",
19
+ "GITHUB_DISABLE",
20
+ "JIRA_DISABLE",
21
+ "LINEAR_DISABLE",
22
+ "SLACK_DISABLE",
23
+ ] as const;
24
+ const originalIntegrationDisableValues = new Map<
25
+ (typeof INTEGRATION_DISABLE_KEYS)[number],
26
+ string | undefined
27
+ >();
28
+
29
+ beforeAll(() => {
30
+ for (const key of INTEGRATION_DISABLE_KEYS) {
31
+ originalIntegrationDisableValues.set(key, process.env[key]);
32
+ process.env[key] = "true";
33
+ }
34
+ _resetAutoReloadForTests();
35
+ });
36
+
37
+ afterAll(() => {
38
+ for (const key of INTEGRATION_DISABLE_KEYS) {
39
+ const originalValue = originalIntegrationDisableValues.get(key);
40
+ if (originalValue === undefined) {
41
+ delete process.env[key];
42
+ } else {
43
+ process.env[key] = originalValue;
44
+ }
45
+ }
46
+ originalIntegrationDisableValues.clear();
47
+ });
17
48
 
18
49
  function insertLegacyReservedRow(key: string, value = "legacy"): string {
19
50
  const id = crypto.randomUUID();
@@ -207,23 +238,8 @@ describe("reload-config", () => {
207
238
  });
208
239
 
209
240
  describe("auto-reload debouncer", () => {
210
- // The reload calls into stopSlackApp/startSlackApp + GH/Linear/Jira/AgentMail
211
- // init. They are no-ops without credentials, so we explicitly disable Slack
212
- // (it has its own DISABLE switch) and rely on the others being unconfigured.
213
- let originalSlackDisable: string | undefined;
214
-
215
- beforeAll(() => {
216
- originalSlackDisable = process.env.SLACK_DISABLE;
217
- process.env.SLACK_DISABLE = "true";
218
- });
219
-
220
- afterAll(() => {
221
- if (originalSlackDisable === undefined) {
222
- delete process.env.SLACK_DISABLE;
223
- } else {
224
- process.env.SLACK_DISABLE = originalSlackDisable;
225
- }
226
- });
241
+ // The reload path reinitializes every integration. Keep these tests focused
242
+ // on debounce semantics rather than local .env / CI credential side effects.
227
243
 
228
244
  beforeEach(async () => {
229
245
  // Drain any reload state that leaked from earlier test files in the full