@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { unlink } from "node:fs/promises";
|
|
3
|
-
import { closeDb, createAgent, initDb } from "../be/db";
|
|
3
|
+
import { closeDb, createAgent, getDb, initDb } from "../be/db";
|
|
4
|
+
import { serializeEmbedding } from "../be/embedding";
|
|
4
5
|
import { SqliteMemoryStore } from "../be/memory/providers/sqlite-store";
|
|
5
6
|
|
|
6
7
|
const TEST_DB_PATH = "./test-memory-store.sqlite";
|
|
@@ -10,6 +11,14 @@ describe("SqliteMemoryStore", () => {
|
|
|
10
11
|
const agentB = "bbbb0000-0000-4000-8000-000000000002";
|
|
11
12
|
let store: SqliteMemoryStore;
|
|
12
13
|
|
|
14
|
+
function vector(values: Record<number, number>): Float32Array {
|
|
15
|
+
const embedding = new Float32Array(512);
|
|
16
|
+
for (const [index, value] of Object.entries(values)) {
|
|
17
|
+
embedding[Number(index)] = value;
|
|
18
|
+
}
|
|
19
|
+
return embedding;
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
beforeAll(async () => {
|
|
14
23
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
15
24
|
try {
|
|
@@ -210,6 +219,95 @@ describe("SqliteMemoryStore", () => {
|
|
|
210
219
|
const agents = new Set(results.map((r) => r.agentId));
|
|
211
220
|
expect(agents.size).toBeGreaterThanOrEqual(1);
|
|
212
221
|
});
|
|
222
|
+
|
|
223
|
+
test("uses sqlite-vec for 512d embeddings with scope-filter parity", () => {
|
|
224
|
+
for (let i = 0; i < 6; i++) {
|
|
225
|
+
const otherAgent = store.store({
|
|
226
|
+
agentId: agentB,
|
|
227
|
+
scope: "agent",
|
|
228
|
+
name: `vec-other-agent-exact-${i}`,
|
|
229
|
+
content: "exact but invisible to agentA",
|
|
230
|
+
source: "manual",
|
|
231
|
+
});
|
|
232
|
+
store.updateEmbedding(otherAgent.id, vector({ 0: 1 }), "test-model");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const visible = store.store({
|
|
236
|
+
agentId: agentA,
|
|
237
|
+
scope: "agent",
|
|
238
|
+
name: "vec-agent-visible",
|
|
239
|
+
content: "visible to agentA",
|
|
240
|
+
source: "manual",
|
|
241
|
+
});
|
|
242
|
+
store.updateEmbedding(visible.id, vector({ 0: 0.8, 1: 0.2 }), "test-model");
|
|
243
|
+
|
|
244
|
+
const query = vector({ 0: 1 });
|
|
245
|
+
const results = store.search(query, agentA, { scope: "agent", limit: 5 });
|
|
246
|
+
|
|
247
|
+
expect(results[0]!.id).toBe(visible.id);
|
|
248
|
+
expect(results.every((r) => r.agentId === agentA && r.scope === "agent")).toBe(true);
|
|
249
|
+
|
|
250
|
+
const health = store.getHealth();
|
|
251
|
+
expect(health.sqliteVec.schema).toContain("distance_metric=cosine");
|
|
252
|
+
expect(health.counts.memoryVec).toBeGreaterThanOrEqual(2);
|
|
253
|
+
expect(health.retrievalMode).toBe("vec");
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("memory_vec population", () => {
|
|
258
|
+
test("populates existing embeddings on startup and reports health counts", () => {
|
|
259
|
+
const raw = store.store({
|
|
260
|
+
agentId: agentA,
|
|
261
|
+
scope: "agent",
|
|
262
|
+
name: "raw-existing-embedding",
|
|
263
|
+
content: "raw existing embedding",
|
|
264
|
+
source: "manual",
|
|
265
|
+
});
|
|
266
|
+
getDb()
|
|
267
|
+
.prepare("UPDATE agent_memory SET embedding = ?, embeddingModel = ? WHERE id = ?")
|
|
268
|
+
.run(serializeEmbedding(vector({ 2: 1 })), "test-model", raw.id);
|
|
269
|
+
getDb().prepare("DELETE FROM memory_vec WHERE memory_id = ?").run(raw.id);
|
|
270
|
+
|
|
271
|
+
const freshStore = new SqliteMemoryStore();
|
|
272
|
+
const health = freshStore.getHealth();
|
|
273
|
+
|
|
274
|
+
expect(health.counts.missingFromVec).toBe(0);
|
|
275
|
+
expect(health.sqliteVec.lastPopulate?.attempted).toBeGreaterThanOrEqual(1);
|
|
276
|
+
expect(health.sqliteVec.lastPopulate?.failed).toBe(0);
|
|
277
|
+
|
|
278
|
+
const resultIds = freshStore
|
|
279
|
+
.search(vector({ 2: 1 }), agentA, { scope: "agent", limit: 20 })
|
|
280
|
+
.map((r) => r.id);
|
|
281
|
+
expect(resultIds).toContain(raw.id);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("rebuilds an old non-cosine memory_vec table from agent_memory", () => {
|
|
285
|
+
const raw = store.store({
|
|
286
|
+
agentId: agentA,
|
|
287
|
+
scope: "agent",
|
|
288
|
+
name: "stale-schema-embedding",
|
|
289
|
+
content: "stale schema embedding",
|
|
290
|
+
source: "manual",
|
|
291
|
+
});
|
|
292
|
+
getDb()
|
|
293
|
+
.prepare("UPDATE agent_memory SET embedding = ?, embeddingModel = ? WHERE id = ?")
|
|
294
|
+
.run(serializeEmbedding(vector({ 3: 1 })), "test-model", raw.id);
|
|
295
|
+
|
|
296
|
+
getDb().run("DROP TABLE memory_vec");
|
|
297
|
+
getDb().run(`
|
|
298
|
+
CREATE VIRTUAL TABLE memory_vec USING vec0(
|
|
299
|
+
memory_id TEXT PRIMARY KEY,
|
|
300
|
+
embedding float[512]
|
|
301
|
+
)
|
|
302
|
+
`);
|
|
303
|
+
|
|
304
|
+
const freshStore = new SqliteMemoryStore();
|
|
305
|
+
const health = freshStore.getHealth();
|
|
306
|
+
|
|
307
|
+
expect(health.sqliteVec.schema).toContain("distance_metric=cosine");
|
|
308
|
+
expect(health.counts.missingFromVec).toBe(0);
|
|
309
|
+
expect(health.retrievalMode).toBe("vec");
|
|
310
|
+
});
|
|
213
311
|
});
|
|
214
312
|
|
|
215
313
|
describe("delete()", () => {
|
|
@@ -326,4 +424,126 @@ describe("SqliteMemoryStore", () => {
|
|
|
326
424
|
expect(filtered.length).toBeGreaterThan(0);
|
|
327
425
|
});
|
|
328
426
|
});
|
|
427
|
+
|
|
428
|
+
describe("purgeExpired()", () => {
|
|
429
|
+
const purgeAgent = "cccc0000-0000-4000-8000-000000000003";
|
|
430
|
+
|
|
431
|
+
beforeAll(() => {
|
|
432
|
+
try {
|
|
433
|
+
createAgent({ id: purgeAgent, name: "Purge Agent", isLead: false, status: "idle" });
|
|
434
|
+
} catch {
|
|
435
|
+
// agent may already exist from a prior run
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("deletes rows past their expiresAt and returns count", () => {
|
|
440
|
+
const db = getDb();
|
|
441
|
+
|
|
442
|
+
// Store a session_summary (3-day TTL) then backdateits expiresAt to the past
|
|
443
|
+
const mem = store.store({
|
|
444
|
+
agentId: purgeAgent,
|
|
445
|
+
scope: "agent",
|
|
446
|
+
name: "expired session",
|
|
447
|
+
content: "old session data",
|
|
448
|
+
source: "session_summary",
|
|
449
|
+
});
|
|
450
|
+
db.prepare("UPDATE agent_memory SET expiresAt = datetime('now', '-1 day') WHERE id = ?").run(
|
|
451
|
+
mem.id,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
// Store a manual memory (never expires) — should survive
|
|
455
|
+
const keeper = store.store({
|
|
456
|
+
agentId: purgeAgent,
|
|
457
|
+
scope: "agent",
|
|
458
|
+
name: "keeper",
|
|
459
|
+
content: "manual content",
|
|
460
|
+
source: "manual",
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const purged = store.purgeExpired();
|
|
464
|
+
expect(purged).toBeGreaterThanOrEqual(1);
|
|
465
|
+
|
|
466
|
+
expect(store.get(mem.id)).toBeNull();
|
|
467
|
+
expect(store.get(keeper.id)).not.toBeNull();
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("returns 0 when nothing is expired", () => {
|
|
471
|
+
const purged = store.purgeExpired();
|
|
472
|
+
expect(purged).toBe(0);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("also removes corresponding vec rows", () => {
|
|
476
|
+
const db = getDb();
|
|
477
|
+
const emb = vector({ 0: 0.9, 100: 0.1 });
|
|
478
|
+
|
|
479
|
+
const mem = store.store({
|
|
480
|
+
agentId: purgeAgent,
|
|
481
|
+
scope: "agent",
|
|
482
|
+
name: "vec-purge-test",
|
|
483
|
+
content: "will be purged",
|
|
484
|
+
source: "task_completion",
|
|
485
|
+
});
|
|
486
|
+
store.updateEmbedding(mem.id, emb, "test-model");
|
|
487
|
+
|
|
488
|
+
const vecBefore = db
|
|
489
|
+
.prepare<{ count: number }, [string]>(
|
|
490
|
+
"SELECT COUNT(*) as count FROM memory_vec WHERE memory_id = ?",
|
|
491
|
+
)
|
|
492
|
+
.get(mem.id);
|
|
493
|
+
|
|
494
|
+
// Only check vec cleanup if the row was actually inserted
|
|
495
|
+
if (vecBefore && vecBefore.count > 0) {
|
|
496
|
+
db.prepare(
|
|
497
|
+
"UPDATE agent_memory SET expiresAt = datetime('now', '-1 day') WHERE id = ?",
|
|
498
|
+
).run(mem.id);
|
|
499
|
+
|
|
500
|
+
store.purgeExpired();
|
|
501
|
+
|
|
502
|
+
const vecAfter = db
|
|
503
|
+
.prepare<{ count: number }, [string]>(
|
|
504
|
+
"SELECT COUNT(*) as count FROM memory_vec WHERE memory_id = ?",
|
|
505
|
+
)
|
|
506
|
+
.get(mem.id);
|
|
507
|
+
expect(vecAfter?.count ?? 0).toBe(0);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe("knn-k cap", () => {
|
|
513
|
+
test("search does not throw when vec table exceeds 4096 rows", () => {
|
|
514
|
+
const db = getDb();
|
|
515
|
+
const emb = vector({ 0: 1.0 });
|
|
516
|
+
const embBuffer = serializeEmbedding(emb);
|
|
517
|
+
|
|
518
|
+
// Check if vec is available — if not, skip (test is meaningful only with vec)
|
|
519
|
+
const health = store.getHealth();
|
|
520
|
+
if (health.retrievalMode !== "vec") return;
|
|
521
|
+
|
|
522
|
+
// Insert enough rows to exceed 4096 in the vec table
|
|
523
|
+
const currentCount =
|
|
524
|
+
db.prepare<{ c: number }, []>("SELECT COUNT(*) as c FROM memory_vec").get()?.c ?? 0;
|
|
525
|
+
const needed = Math.max(0, 4097 - currentCount);
|
|
526
|
+
|
|
527
|
+
for (let i = 0; i < needed; i++) {
|
|
528
|
+
const id = `knn-test-${i}-${Date.now()}`;
|
|
529
|
+
db.prepare(
|
|
530
|
+
"INSERT INTO agent_memory (id, agentId, scope, name, content, source, embedding, chunkIndex, totalChunks, tags, alpha, beta, createdAt, accessedAt) VALUES (?, ?, 'agent', ?, 'knn test', 'manual', ?, 0, 1, '[]', 1, 1, datetime('now'), datetime('now'))",
|
|
531
|
+
).run(id, agentA, `knn-${i}`, embBuffer);
|
|
532
|
+
const vecBuf = new Float32Array(emb);
|
|
533
|
+
db.prepare("INSERT INTO memory_vec (memory_id, embedding) VALUES (?, ?)").run(
|
|
534
|
+
id,
|
|
535
|
+
Buffer.from(vecBuf.buffer),
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const vecCount =
|
|
540
|
+
db.prepare<{ c: number }, []>("SELECT COUNT(*) as c FROM memory_vec").get()?.c ?? 0;
|
|
541
|
+
expect(vecCount).toBeGreaterThanOrEqual(4097);
|
|
542
|
+
|
|
543
|
+
// This should NOT throw — it should clamp k to 4096
|
|
544
|
+
const results = store.search(emb, agentA, { scope: "agent", limit: 10 });
|
|
545
|
+
expect(results).toBeDefined();
|
|
546
|
+
expect(Array.isArray(results)).toBe(true);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
329
549
|
});
|
package/src/tests/memory.test.ts
CHANGED
|
@@ -342,8 +342,9 @@ describe("Memory System", () => {
|
|
|
342
342
|
status: "idle",
|
|
343
343
|
});
|
|
344
344
|
|
|
345
|
-
// Create memories with known embeddings
|
|
346
|
-
//
|
|
345
|
+
// Create memories with known embeddings (all share a baseline component
|
|
346
|
+
// so pairwise cosine similarity stays above the MIN_SIMILARITY floor).
|
|
347
|
+
// Memory 1: agent scope for searchAgentId
|
|
347
348
|
const m1 = store.store({
|
|
348
349
|
agentId: searchAgentId,
|
|
349
350
|
scope: "agent",
|
|
@@ -351,9 +352,9 @@ describe("Memory System", () => {
|
|
|
351
352
|
content: "Agent-scoped content",
|
|
352
353
|
source: "manual",
|
|
353
354
|
});
|
|
354
|
-
store.updateEmbedding(m1.id, new Float32Array([1, 0, 0]), "test-model");
|
|
355
|
+
store.updateEmbedding(m1.id, new Float32Array([1, 0.3, 0.3]), "test-model");
|
|
355
356
|
|
|
356
|
-
// Memory 2: swarm scope
|
|
357
|
+
// Memory 2: swarm scope
|
|
357
358
|
const m2 = store.store({
|
|
358
359
|
agentId: searchAgentId,
|
|
359
360
|
scope: "swarm",
|
|
@@ -361,9 +362,9 @@ describe("Memory System", () => {
|
|
|
361
362
|
content: "Swarm-scoped content",
|
|
362
363
|
source: "file_index",
|
|
363
364
|
});
|
|
364
|
-
store.updateEmbedding(m2.id, new Float32Array([0, 1, 0]), "test-model");
|
|
365
|
+
store.updateEmbedding(m2.id, new Float32Array([0.3, 1, 0.3]), "test-model");
|
|
365
366
|
|
|
366
|
-
// Memory 3: agent scope for OTHER agent
|
|
367
|
+
// Memory 3: agent scope for OTHER agent
|
|
367
368
|
const m3 = store.store({
|
|
368
369
|
agentId: searchAgentId2,
|
|
369
370
|
scope: "agent",
|
|
@@ -371,11 +372,11 @@ describe("Memory System", () => {
|
|
|
371
372
|
content: "Other agent's private memory",
|
|
372
373
|
source: "manual",
|
|
373
374
|
});
|
|
374
|
-
store.updateEmbedding(m3.id, new Float32Array([0, 0, 1]), "test-model");
|
|
375
|
+
store.updateEmbedding(m3.id, new Float32Array([0.3, 0.3, 1]), "test-model");
|
|
375
376
|
});
|
|
376
377
|
|
|
377
378
|
test("worker sees own agent-scoped + swarm memories", () => {
|
|
378
|
-
const query = new Float32Array([1, 0, 0]); // closest to Memory 1
|
|
379
|
+
const query = new Float32Array([1, 0.3, 0.3]); // closest to Memory 1
|
|
379
380
|
const results = store.search(query, searchAgentId, { isLead: false });
|
|
380
381
|
const names = results.map((r) => r.name);
|
|
381
382
|
|
|
@@ -385,7 +386,7 @@ describe("Memory System", () => {
|
|
|
385
386
|
});
|
|
386
387
|
|
|
387
388
|
test("worker does not see other agent's agent-scoped memories", () => {
|
|
388
|
-
const query = new Float32Array([0, 0, 1]); // closest to Memory 3
|
|
389
|
+
const query = new Float32Array([0.3, 0.3, 1]); // closest to Memory 3
|
|
389
390
|
const results = store.search(query, searchAgentId, { isLead: false });
|
|
390
391
|
const names = results.map((r) => r.name);
|
|
391
392
|
|
|
@@ -393,7 +394,7 @@ describe("Memory System", () => {
|
|
|
393
394
|
});
|
|
394
395
|
|
|
395
396
|
test("lead sees ALL memories across agents", () => {
|
|
396
|
-
const query = new Float32Array([0, 0, 1]); // closest to Memory 3
|
|
397
|
+
const query = new Float32Array([0.3, 0.3, 1]); // closest to Memory 3
|
|
397
398
|
const results = store.search(query, searchAgentId, { isLead: true });
|
|
398
399
|
const names = results.map((r) => r.name);
|
|
399
400
|
|
|
@@ -403,12 +404,12 @@ describe("Memory System", () => {
|
|
|
403
404
|
});
|
|
404
405
|
|
|
405
406
|
test("results sorted by similarity (highest first)", () => {
|
|
406
|
-
const query = new Float32Array([1, 0, 0]); //
|
|
407
|
+
const query = new Float32Array([1, 0.3, 0.3]); // closest to Memory 1's embedding
|
|
407
408
|
const results = store.search(query, searchAgentId, { isLead: true });
|
|
408
409
|
|
|
409
410
|
expect(results.length).toBeGreaterThan(0);
|
|
410
411
|
expect(results[0].name).toBe("Agent Memory 1");
|
|
411
|
-
expect(results[0].similarity).
|
|
412
|
+
expect(results[0].similarity).toBeGreaterThan(0.9);
|
|
412
413
|
|
|
413
414
|
// Each subsequent result should have lower or equal similarity
|
|
414
415
|
for (let i = 1; i < results.length; i++) {
|
|
@@ -61,14 +61,13 @@ describe("Pages HTTP API", () => {
|
|
|
61
61
|
}
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
test("POST /api/pages creates
|
|
64
|
+
test("POST /api/pages creates an authed page by default and returns {id, version}", async () => {
|
|
65
65
|
const res = await fetch(`${baseUrl}/api/pages`, {
|
|
66
66
|
method: "POST",
|
|
67
67
|
headers,
|
|
68
68
|
body: JSON.stringify({
|
|
69
69
|
title: "Hello",
|
|
70
70
|
contentType: "text/html",
|
|
71
|
-
authMode: "public",
|
|
72
71
|
body: "<h1>hi</h1>",
|
|
73
72
|
}),
|
|
74
73
|
});
|
|
@@ -87,6 +86,25 @@ describe("Pages HTTP API", () => {
|
|
|
87
86
|
expect(page.agentId).toBe(agentId);
|
|
88
87
|
expect(page.slug).toBe("hello"); // auto-slug from title
|
|
89
88
|
expect(page.contentType).toBe("text/html");
|
|
89
|
+
expect(page.authMode).toBe("authed");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("POST /api/pages can explicitly opt in to public auth", async () => {
|
|
93
|
+
const res = await fetch(`${baseUrl}/api/pages`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers,
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
title: "Public",
|
|
98
|
+
contentType: "text/html",
|
|
99
|
+
authMode: "public",
|
|
100
|
+
body: "<h1>public</h1>",
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(res.status).toBe(201);
|
|
105
|
+
const json = (await res.json()) as { id: string };
|
|
106
|
+
const got = await fetch(`${baseUrl}/api/pages/${json.id}`, { headers });
|
|
107
|
+
const page = (await got.json()) as Page;
|
|
90
108
|
expect(page.authMode).toBe("public");
|
|
91
109
|
});
|
|
92
110
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
closeDb,
|
|
6
6
|
createPage,
|
|
7
7
|
deletePage,
|
|
8
|
+
getDb,
|
|
8
9
|
getPage,
|
|
9
10
|
getPageBySlug,
|
|
10
11
|
getPageVersion,
|
|
@@ -81,6 +82,31 @@ describe("pages storage CRUD", () => {
|
|
|
81
82
|
expect(getPageVersions(created.id)).toHaveLength(0);
|
|
82
83
|
});
|
|
83
84
|
|
|
85
|
+
test("createPage defaults authMode to authed when omitted", () => {
|
|
86
|
+
const agentId = makeAgentId();
|
|
87
|
+
const created = createPage({
|
|
88
|
+
agentId,
|
|
89
|
+
slug: "default-auth",
|
|
90
|
+
title: "Default Auth",
|
|
91
|
+
contentType: "text/html",
|
|
92
|
+
body: "<h1>default</h1>",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(created.authMode).toBe("authed");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("pages table defaults authMode to authed when omitted", () => {
|
|
99
|
+
const agentId = makeAgentId();
|
|
100
|
+
const row = getDb()
|
|
101
|
+
.prepare<{ authMode: string }, [string, string, string, string, string]>(
|
|
102
|
+
`INSERT INTO pages (agentId, slug, title, contentType, body)
|
|
103
|
+
VALUES (?, ?, ?, ?, ?) RETURNING authMode`,
|
|
104
|
+
)
|
|
105
|
+
.get(agentId, "sql-default-auth", "SQL Default Auth", "text/html", "<h1>default</h1>");
|
|
106
|
+
|
|
107
|
+
expect(row?.authMode).toBe("authed");
|
|
108
|
+
});
|
|
109
|
+
|
|
84
110
|
test("snapshotPage captures PRE-update content; post-update lives on parent", () => {
|
|
85
111
|
const agentId = makeAgentId();
|
|
86
112
|
const page = createPage({
|
|
@@ -232,6 +232,59 @@ describe("script_ MCP HTTP proxy tools", () => {
|
|
|
232
232
|
expect(del.structuredContent.data?.deleted).toBe(true);
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
+
test("persists a successful inline run with kind 'inline' and no journal", async () => {
|
|
236
|
+
const tools = buildToolServer();
|
|
237
|
+
const source = `export default async (args: { value: number }) => ({ doubled: args.value * 2 });`;
|
|
238
|
+
|
|
239
|
+
const run = (await tools.run.handler(
|
|
240
|
+
{ source, args: { value: 21 }, intent: "inline persist e2e" },
|
|
241
|
+
meta(workerId),
|
|
242
|
+
)) as StructuredResult<{ result: { doubled: number } }>;
|
|
243
|
+
expect(run.structuredContent.success).toBe(true);
|
|
244
|
+
expect(run.structuredContent.data?.result).toEqual({ doubled: 42 });
|
|
245
|
+
|
|
246
|
+
const listed = (await tools.listScriptRuns.handler(
|
|
247
|
+
{ limit: 10, offset: 0 },
|
|
248
|
+
meta(workerId),
|
|
249
|
+
)) as StructuredResult<{
|
|
250
|
+
runs: Array<{ id: string; kind: string; status: string }>;
|
|
251
|
+
total: number;
|
|
252
|
+
}>;
|
|
253
|
+
expect(listed.structuredContent.data?.total).toBe(1);
|
|
254
|
+
const inlineRun = listed.structuredContent.data?.runs[0];
|
|
255
|
+
expect(inlineRun?.kind).toBe("inline");
|
|
256
|
+
expect(inlineRun?.status).toBe("completed");
|
|
257
|
+
|
|
258
|
+
const detail = (await tools.getScriptRun.handler(
|
|
259
|
+
{ id: inlineRun?.id },
|
|
260
|
+
meta(workerId),
|
|
261
|
+
)) as StructuredResult<{ run: { kind: string }; journal: unknown[] }>;
|
|
262
|
+
expect(detail.structuredContent.data?.run.kind).toBe("inline");
|
|
263
|
+
expect(detail.structuredContent.data?.journal).toEqual([]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("persists a failed inline run with kind 'inline' and an error", async () => {
|
|
267
|
+
const tools = buildToolServer();
|
|
268
|
+
const source = `export default async () => { throw new Error("boom"); };`;
|
|
269
|
+
|
|
270
|
+
const run = (await tools.run.handler(
|
|
271
|
+
{ source, intent: "inline failure e2e" },
|
|
272
|
+
meta(workerId),
|
|
273
|
+
)) as StructuredResult<unknown>;
|
|
274
|
+
expect(run.structuredContent.success).toBe(true);
|
|
275
|
+
|
|
276
|
+
const listed = (await tools.listScriptRuns.handler(
|
|
277
|
+
{ limit: 10, offset: 0 },
|
|
278
|
+
meta(workerId),
|
|
279
|
+
)) as StructuredResult<{
|
|
280
|
+
runs: Array<{ kind: string; status: string; error?: string }>;
|
|
281
|
+
}>;
|
|
282
|
+
const failed = listed.structuredContent.data?.runs[0];
|
|
283
|
+
expect(failed?.kind).toBe("inline");
|
|
284
|
+
expect(failed?.status).toBe("failed");
|
|
285
|
+
expect(failed?.error).toBeTruthy();
|
|
286
|
+
});
|
|
287
|
+
|
|
235
288
|
test("stdio-style missing agent identity short-circuits clearly", async () => {
|
|
236
289
|
const tools = buildToolServer();
|
|
237
290
|
const result = (await tools.search.handler({ query: "anything" }, meta())) as StructuredResult<{
|