@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.
- package/openapi.json +63 -3
- package/package.json +5 -5
- package/src/be/db.ts +91 -6
- 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/modelsdev-cache.json +1222 -986
- package/src/be/seed-pricing.ts +1 -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 +5 -5
- package/src/be/skill-sync.ts +28 -179
- package/src/commands/runner.ts +124 -7
- package/src/http/api-keys.ts +42 -0
- package/src/http/mcp-bridge.ts +1 -1
- package/src/http/memory.ts +23 -24
- package/src/http/tasks.ts +10 -6
- package/src/providers/claude-adapter.ts +33 -1
- package/src/providers/claude-managed-adapter.ts +3 -0
- package/src/providers/claude-managed-models.ts +7 -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 +11 -4
- package/src/providers/pi-mono-adapter.ts +12 -2
- package/src/providers/types.ts +2 -0
- 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 +2 -2
- package/src/slack/channel-join.ts +41 -0
- package/src/tests/additive-buffer.test.ts +0 -1
- package/src/tests/api-key-tracking.test.ts +113 -0
- package/src/tests/approval-requests.test.ts +0 -6
- package/src/tests/claude-managed-setup.test.ts +0 -4
- package/src/tests/codex-pool.test.ts +2 -6
- package/src/tests/http-api-integration.test.ts +4 -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/model-control.test.ts +1 -1
- package/src/tests/reload-config.test.ts +33 -17
- package/src/tests/runner-skills-refresh.test.ts +216 -46
- package/src/tests/script-runs-http.test.ts +7 -1
- package/src/tests/scripts-runtime-secret-egress.test.ts +129 -0
- package/src/tests/seed-scripts.test.ts +13 -1
- package/src/tests/session-attach.test.ts +6 -6
- 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/structured-output.test.ts +0 -2
- package/src/tests/use-dismissible-card.test.ts +0 -4
- package/src/tools/schedules/create-schedule.ts +2 -2
- package/src/tools/schedules/update-schedule.ts +1 -1
- package/src/tools/send-task.ts +2 -2
- 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 +2 -2
- package/src/types.ts +11 -0
- package/src/utils/context-window.ts +3 -0
- package/src/utils/credentials.ts +22 -2
- package/src/utils/skill-fs-writer.ts +220 -0
- 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 (
|
|
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/
|
|
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}/
|
|
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/
|
|
441
|
-
const { status } = await put(`/api/tasks/${ids.task2}/
|
|
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
|
|
package/src/tests/memory.test.ts
CHANGED
|
@@ -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
|
|
211
|
-
//
|
|
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
|