@desplega.ai/agent-swarm 1.157.0 → 1.158.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/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
- package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
- package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
- package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
- package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
- package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
- package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
- package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
- package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
- package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
- package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
- package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
- package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
- package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
- package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
- package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
- package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
- package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
- package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
- package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
- package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
- package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
- package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
- package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
- package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
- package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
- package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
- package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
- package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
- package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
- package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
- package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
- package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
- package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
- package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
- package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
- package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
- package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
- package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
- package/dist/cli-t1ze64h3.js +811 -0
- package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
- package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
- package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
- package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
- package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
- package/dist/cli.js +29 -15
- package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
- package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
- package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
- package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
- package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
- package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
- package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
- package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
- package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
- package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
- package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
- package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
- package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
- package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
- package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
- package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
- package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
- package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
- package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
- package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
- package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
- package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
- package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
- package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
- package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
- package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
- package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
- package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
- package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
- package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
- package/openapi.json +1626 -629
- package/package.json +2 -1
- package/src/apps/catalog.generated.json +229 -0
- package/src/apps/page-validator.ts +71 -16
- package/src/apps/row-store.ts +66 -11
- package/src/apps/sync.ts +59 -17
- package/src/artifact-sdk/browser-sdk.ts +436 -2
- package/src/be/approval-sweeps.ts +109 -0
- package/src/be/boot-seeds.ts +40 -0
- package/src/be/chunking.ts +1 -1
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/approvals.ts +78 -0
- package/src/be/db/tasks/read.ts +197 -39
- package/src/be/db/tasks/write.ts +41 -9
- package/src/be/db-client.ts +107 -48
- package/src/be/db.ts +219 -76
- package/src/be/events.ts +33 -1
- package/src/be/memory/graph-expansion.ts +10 -3
- package/src/be/memory/providers/sqlite-store.ts +55 -13
- package/src/be/memory/raters/retrieval.ts +5 -2
- package/src/be/memory/reranker.ts +26 -2
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
- package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
- package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
- package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +1857 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-normalize.ts +2 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/steering.ts +53 -3
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/task-citations.ts +29 -0
- package/src/cli.tsx +14 -1
- package/src/commands/onboard/compose-generator.ts +8 -3
- package/src/commands/onboard/container-engine.ts +220 -0
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/health-check.tsx +4 -1
- package/src/commands/onboard/steps/prereq-check.tsx +50 -54
- package/src/commands/onboard/steps/start.tsx +31 -16
- package/src/commands/onboard/types.ts +7 -2
- package/src/commands/onboard.tsx +39 -4
- package/src/commands/profile-sync.ts +79 -26
- package/src/commands/runner.ts +8 -1
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +263 -103
- package/src/hooks/hook.ts +21 -12
- package/src/http/agents.ts +35 -0
- package/src/http/approval-requests.ts +136 -64
- package/src/http/apps.ts +14 -3
- package/src/http/config.ts +15 -1
- package/src/http/events.ts +62 -2
- package/src/http/index.ts +6 -4
- package/src/http/memory.ts +21 -7
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +9 -4
- package/src/http/sessions.ts +25 -18
- package/src/http/stats.ts +19 -7
- package/src/http/status.ts +11 -1
- package/src/http/tasks.ts +40 -8
- package/src/http/webhooks.ts +67 -46
- package/src/http/workflows.ts +15 -20
- package/src/linear/outbound.ts +37 -15
- package/src/otel-impl.ts +151 -1
- package/src/otel.ts +14 -3
- package/src/prompts/memories.ts +19 -2
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/modelsdev-reasoning.json +551 -0
- package/src/rbac/legacy-policy.ts +16 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/schedule-task.ts +3 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +12 -7
- package/src/slack/app.ts +35 -4
- package/src/slack/connection-state.ts +17 -0
- package/src/tasks/context-key.ts +1 -0
- package/src/tasks/worker-follow-up.ts +6 -2
- package/src/tests/agent-task-activity.test.ts +74 -0
- package/src/tests/agents-list-model-display.test.ts +6 -1
- package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
- package/src/tests/approval-requests.test.ts +395 -0
- package/src/tests/approval-sweeps-config.test.ts +47 -0
- package/src/tests/approval-sweeps.test.ts +508 -0
- package/src/tests/apps-kibo-blocks.test.ts +77 -0
- package/src/tests/apps-spike.test.ts +56 -1
- package/src/tests/apps-sync-engine.test.ts +20 -9
- package/src/tests/boot-seeds.test.ts +170 -0
- package/src/tests/cancel-approval-request-tool.test.ts +115 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/config-key-filter.test.ts +80 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-client-busy-retry.test.ts +96 -0
- package/src/tests/db-client.test.ts +60 -1
- package/src/tests/db-task-read-contract.test.ts +10 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/events-http.test.ts +103 -0
- package/src/tests/extensions-catalog.test.ts +22 -17
- package/src/tests/extensions-pre-task.test.ts +107 -0
- package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
- package/src/tests/follow-up-config-inheritance.test.ts +260 -0
- package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
- package/src/tests/heartbeat-reboot-live.test.ts +95 -0
- package/src/tests/heartbeat-stall-cas.test.ts +80 -0
- package/src/tests/heartbeat.test.ts +99 -3
- package/src/tests/hook-mcp-config.test.ts +29 -0
- package/src/tests/http-api-integration.test.ts +16 -2
- package/src/tests/kv-storage.test.ts +62 -0
- package/src/tests/linear-outbound-sync.test.ts +120 -5
- package/src/tests/list-endpoint-slimming.test.ts +91 -0
- package/src/tests/memory-graph-organic-route.test.ts +155 -0
- package/src/tests/memory-http-recall-gating.test.ts +14 -4
- package/src/tests/memory-hybrid.test.ts +161 -1
- package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
- package/src/tests/memory.test.ts +22 -0
- package/src/tests/onboard-container-engine.test.ts +240 -0
- package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
- package/src/tests/pricing-routes.test.ts +14 -0
- package/src/tests/profile-sync-warning.test.ts +8 -4
- package/src/tests/promote-draft-task-route.test.ts +19 -0
- package/src/tests/provider-registration.test.ts +296 -42
- package/src/tests/rbac-engine.test.ts +30 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/sessions-detail-batch.test.ts +250 -0
- package/src/tests/sessions.test.ts +118 -0
- package/src/tests/slack-config.test.ts +22 -0
- package/src/tests/sonnet-5-5-registry.test.ts +44 -0
- package/src/tests/status.test.ts +36 -1
- package/src/tests/swarm-diff.test.ts +316 -10
- package/src/tests/task-completion-idempotency.test.ts +2 -1
- package/src/tests/tasks-requested-by-filter.test.ts +3 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/ui-list-slim-projections.test.ts +158 -0
- package/src/tests/webhooks-task-blocked.test.ts +127 -0
- package/src/tests/workflow-approval-recovery.test.ts +203 -0
- package/src/tests/workflow-hitl-routing.test.ts +22 -1
- package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
- package/src/tests/workflow-http-v2.test.ts +99 -8
- package/src/tests/workflow-retry-validation.test.ts +56 -0
- package/src/tests/workflow-tla-races.test.ts +389 -0
- package/src/tests/workflow-validation-port-routing.test.ts +113 -1
- package/src/tools/cancel-approval-request.ts +57 -0
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/request-human-input.ts +4 -2
- package/src/tools/send-task.ts +4 -0
- package/src/tools/templates.ts +19 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/workflows/list-workflow-runs.ts +1 -0
- package/src/types.ts +38 -0
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/approval-cancel.ts +152 -0
- package/src/workflows/approval-notifications.ts +84 -0
- package/src/workflows/checkpoint.ts +10 -5
- package/src/workflows/definition.ts +21 -0
- package/src/workflows/engine.ts +152 -21
- package/src/workflows/executors/human-in-the-loop.ts +1 -1
- package/src/workflows/recovery.ts +100 -45
- package/src/workflows/resume.ts +49 -18
- package/src/workflows/retry-poller.ts +71 -21
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/github-sender-allowlist/README.md +18 -0
- package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
- package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
- package/templates/skills/apps/content.md +20 -1
- package/templates/skills/code-quality/content.md +26 -1
- package/templates/skills/pages/content.md +16 -4
- package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
- package/dist/cli-2daksqps.js +0 -378
- package/dist/rbac-roles-3gxmnb20.js +0 -37
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { generateCompose } from "../commands/onboard/compose-generator.ts";
|
|
3
|
+
import {
|
|
4
|
+
type CommandResult,
|
|
5
|
+
type CommandRunner,
|
|
6
|
+
composeCommandText,
|
|
7
|
+
engineForCommands,
|
|
8
|
+
isContainerEnginePreference,
|
|
9
|
+
resolveContainerEngine,
|
|
10
|
+
resolveContainerEnginePreference,
|
|
11
|
+
} from "../commands/onboard/container-engine.ts";
|
|
12
|
+
import { INITIAL_STATE } from "../commands/onboard/types.ts";
|
|
13
|
+
|
|
14
|
+
const ok = (stdout: string, stderr = ""): CommandResult => ({ exitCode: 0, stdout, stderr });
|
|
15
|
+
const missing: CommandResult = { exitCode: 127, stdout: "", stderr: "not found" };
|
|
16
|
+
|
|
17
|
+
/** Mocked runner keyed by the joined argv; unknown commands behave like a missing binary. */
|
|
18
|
+
function mockRunner(responses: Record<string, CommandResult>): CommandRunner & {
|
|
19
|
+
calls: string[];
|
|
20
|
+
} {
|
|
21
|
+
const calls: string[] = [];
|
|
22
|
+
const run = async (argv: string[]) => {
|
|
23
|
+
const key = argv.join(" ");
|
|
24
|
+
calls.push(key);
|
|
25
|
+
return responses[key] ?? missing;
|
|
26
|
+
};
|
|
27
|
+
return Object.assign(run, { calls });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DOCKER = {
|
|
31
|
+
"docker --version": ok("Docker version 27.3.1, build ce12230\n"),
|
|
32
|
+
"docker compose version": ok("Docker Compose version v2.29.7\n"),
|
|
33
|
+
};
|
|
34
|
+
const PODMAN = {
|
|
35
|
+
"podman --version": ok("podman version 5.2.3\n"),
|
|
36
|
+
"podman compose version": ok(
|
|
37
|
+
"docker-compose version 2.29.7\n",
|
|
38
|
+
">>>> Executing external compose provider ...\n",
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
const PODMAN_NO_PROVIDER = {
|
|
42
|
+
"podman --version": ok("podman version 5.2.3\n"),
|
|
43
|
+
"podman compose version": {
|
|
44
|
+
exitCode: 125,
|
|
45
|
+
stdout: "",
|
|
46
|
+
stderr:
|
|
47
|
+
'Error: looking up compose provider failed\n7 errors occurred:\n\t* exec: "docker-compose": executable file not found in $PATH\n',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("resolveContainerEngine", () => {
|
|
52
|
+
test("auto keeps Docker when both engines are installed", async () => {
|
|
53
|
+
const run = mockRunner({ ...DOCKER, ...PODMAN });
|
|
54
|
+
const result = await resolveContainerEngine("auto", run);
|
|
55
|
+
expect(result.ok).toBe(true);
|
|
56
|
+
expect(result.engine).toBe("docker");
|
|
57
|
+
expect(run.calls.some((c) => c.startsWith("podman"))).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("auto falls back to Podman when Docker is absent", async () => {
|
|
61
|
+
const result = await resolveContainerEngine("auto", mockRunner(PODMAN));
|
|
62
|
+
expect(result.ok).toBe(true);
|
|
63
|
+
expect(result.engine).toBe("podman");
|
|
64
|
+
if (result.ok) {
|
|
65
|
+
expect(result.probe.binary.version).toBe("podman version 5.2.3");
|
|
66
|
+
expect(result.probe.compose.version).toBe("docker-compose version 2.29.7");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("auto reports a broken Docker Compose as a Docker failure, without trying Podman", async () => {
|
|
71
|
+
const run = mockRunner({ "docker --version": DOCKER["docker --version"], ...PODMAN });
|
|
72
|
+
const result = await resolveContainerEngine("auto", run);
|
|
73
|
+
expect(result.ok).toBe(false);
|
|
74
|
+
expect(result.engine).toBe("docker");
|
|
75
|
+
if (!result.ok) expect(result.error).toBe("Docker Compose v2 not found");
|
|
76
|
+
expect(run.calls.some((c) => c.startsWith("podman"))).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("auto fails clearly when neither engine exists", async () => {
|
|
80
|
+
const result = await resolveContainerEngine("auto", mockRunner({}));
|
|
81
|
+
expect(result.ok).toBe(false);
|
|
82
|
+
expect(result.engine).toBeNull();
|
|
83
|
+
if (!result.ok) expect(result.error).toBe("Neither Docker nor Podman was found");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("explicit docker succeeds with Docker and Compose v2", async () => {
|
|
87
|
+
const result = await resolveContainerEngine("docker", mockRunner(DOCKER));
|
|
88
|
+
expect(result.ok).toBe(true);
|
|
89
|
+
expect(result.engine).toBe("docker");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("explicit docker fails when Docker is missing, even if Podman works", async () => {
|
|
93
|
+
const run = mockRunner(PODMAN);
|
|
94
|
+
const result = await resolveContainerEngine("docker", run);
|
|
95
|
+
expect(result.ok).toBe(false);
|
|
96
|
+
expect(result.engine).toBe("docker");
|
|
97
|
+
if (!result.ok) {
|
|
98
|
+
expect(result.error).toBe("Docker not found");
|
|
99
|
+
expect(result.probe?.binary.hint).toContain("docs.docker.com");
|
|
100
|
+
}
|
|
101
|
+
expect(run.calls).toEqual(["docker --version"]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("explicit podman succeeds with a Compose provider", async () => {
|
|
105
|
+
const result = await resolveContainerEngine("podman", mockRunner(PODMAN));
|
|
106
|
+
expect(result.ok).toBe(true);
|
|
107
|
+
expect(result.engine).toBe("podman");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("explicit podman fails when Podman is missing, even if Docker works", async () => {
|
|
111
|
+
const run = mockRunner(DOCKER);
|
|
112
|
+
const result = await resolveContainerEngine("podman", run);
|
|
113
|
+
expect(result.ok).toBe(false);
|
|
114
|
+
expect(result.engine).toBe("podman");
|
|
115
|
+
if (!result.ok) {
|
|
116
|
+
expect(result.error).toBe("Podman not found");
|
|
117
|
+
expect(result.probe?.binary.hint).toContain("podman.io");
|
|
118
|
+
}
|
|
119
|
+
expect(run.calls).toEqual(["podman --version"]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("explicit podman without a Compose provider prints a provider diagnostic", async () => {
|
|
123
|
+
const result = await resolveContainerEngine("podman", mockRunner(PODMAN_NO_PROVIDER));
|
|
124
|
+
expect(result.ok).toBe(false);
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
expect(result.error).toContain("podman compose has no working Compose provider");
|
|
127
|
+
expect(result.error).toContain("looking up compose provider failed");
|
|
128
|
+
expect(result.probe?.compose.hint).toContain("podman-compose");
|
|
129
|
+
expect(result.probe?.compose.hint).toContain("PODMAN_COMPOSE_PROVIDER");
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("a binary that exits non-zero is reported distinctly from a missing one", async () => {
|
|
134
|
+
const result = await resolveContainerEngine(
|
|
135
|
+
"podman",
|
|
136
|
+
mockRunner({ "podman --version": { exitCode: 1, stdout: "", stderr: "boom" } }),
|
|
137
|
+
);
|
|
138
|
+
expect(result.ok).toBe(false);
|
|
139
|
+
if (!result.ok) expect(result.error).toBe("Podman exited with non-zero status");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("container engine helpers", () => {
|
|
144
|
+
test("validates preferences", () => {
|
|
145
|
+
expect(isContainerEnginePreference("auto")).toBe(true);
|
|
146
|
+
expect(isContainerEnginePreference("docker")).toBe(true);
|
|
147
|
+
expect(isContainerEnginePreference("podman")).toBe(true);
|
|
148
|
+
expect(isContainerEnginePreference("nerdctl")).toBe(false);
|
|
149
|
+
expect(isContainerEnginePreference("")).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("auto renders commands as Docker", () => {
|
|
153
|
+
expect(engineForCommands("auto")).toBe("docker");
|
|
154
|
+
expect(composeCommandText(engineForCommands("podman"), "logs -f")).toBe(
|
|
155
|
+
"podman compose logs -f",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("resolveContainerEnginePreference", () => {
|
|
161
|
+
const ENV = "AGENT_SWARM_CONTAINER_ENGINE";
|
|
162
|
+
|
|
163
|
+
test("defaults to auto with neither flag nor env var", () => {
|
|
164
|
+
expect(resolveContainerEnginePreference(undefined, {})).toEqual({ ok: true, value: "auto" });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("an empty env var counts as unset", () => {
|
|
168
|
+
expect(resolveContainerEnginePreference(undefined, { [ENV]: "" })).toEqual({
|
|
169
|
+
ok: true,
|
|
170
|
+
value: "auto",
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("reads the env var when the flag is absent", () => {
|
|
175
|
+
for (const value of ["auto", "docker", "podman"]) {
|
|
176
|
+
expect(resolveContainerEnginePreference(undefined, { [ENV]: value })).toEqual({
|
|
177
|
+
ok: true,
|
|
178
|
+
value,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("the flag wins over the env var", () => {
|
|
184
|
+
expect(resolveContainerEnginePreference("docker", { [ENV]: "podman" })).toEqual({
|
|
185
|
+
ok: true,
|
|
186
|
+
value: "docker",
|
|
187
|
+
});
|
|
188
|
+
expect(resolveContainerEnginePreference("auto", { [ENV]: "podman" })).toEqual({
|
|
189
|
+
ok: true,
|
|
190
|
+
value: "auto",
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("a valid flag is not blocked by an invalid env var", () => {
|
|
195
|
+
expect(resolveContainerEnginePreference("podman", { [ENV]: "nerdctl" })).toEqual({
|
|
196
|
+
ok: true,
|
|
197
|
+
value: "podman",
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("an invalid env var fails and names the variable", () => {
|
|
202
|
+
expect(resolveContainerEnginePreference(undefined, { [ENV]: "nerdctl" })).toEqual({
|
|
203
|
+
ok: false,
|
|
204
|
+
error: 'Invalid AGENT_SWARM_CONTAINER_ENGINE "nerdctl". Options: auto, docker, podman',
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("an invalid flag fails even when the env var is valid", () => {
|
|
209
|
+
expect(resolveContainerEnginePreference("", { [ENV]: "podman" })).toEqual({
|
|
210
|
+
ok: false,
|
|
211
|
+
error: 'Invalid container engine "". Options: auto, docker, podman',
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("generateCompose engine output", () => {
|
|
217
|
+
const bedrockState = {
|
|
218
|
+
...INITIAL_STATE,
|
|
219
|
+
services: [{ template: "official/worker", displayName: "Worker", count: 1, role: "worker" }],
|
|
220
|
+
agentIds: { "worker-worker": "00000000-0000-4000-8000-000000000001" },
|
|
221
|
+
provider: "bedrock" as const,
|
|
222
|
+
harness: "pi" as const,
|
|
223
|
+
awsRegion: "us-east-1",
|
|
224
|
+
awsProfile: "default",
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
test("Docker output is unchanged: docker usage line, plain :ro AWS mount", () => {
|
|
228
|
+
const yaml = generateCompose(bedrockState);
|
|
229
|
+
expect(yaml).toContain("# docker compose --env-file .env up -d");
|
|
230
|
+
expect(yaml).toContain("/home/worker/.aws:ro\n");
|
|
231
|
+
expect(yaml).not.toContain(":ro,z");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("Podman output uses podman compose and a shared SELinux label on the AWS mount", () => {
|
|
235
|
+
const yaml = generateCompose({ ...bedrockState, containerEngine: "podman" });
|
|
236
|
+
expect(yaml).toContain("# Podman Compose for Agent Swarm");
|
|
237
|
+
expect(yaml).toContain("# podman compose --env-file .env up -d");
|
|
238
|
+
expect(yaml).toContain("/home/worker/.aws:ro,z\n");
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { type LogRecord, logs, SeverityNumber } from "@opentelemetry/api-logs";
|
|
3
|
+
import { MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
4
|
+
import {
|
|
5
|
+
installConsoleLogBridge,
|
|
6
|
+
startEventLoopDelayMetrics,
|
|
7
|
+
stopEventLoopDelayMetrics,
|
|
8
|
+
uninstallConsoleLogBridge,
|
|
9
|
+
} from "../otel-impl";
|
|
10
|
+
|
|
11
|
+
class CollectingReader extends MetricReader {
|
|
12
|
+
protected async onForceFlush(): Promise<void> {}
|
|
13
|
+
protected async onShutdown(): Promise<void> {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function gaugeValues(reader: CollectingReader): Promise<Record<string, number>> {
|
|
17
|
+
const { resourceMetrics } = await reader.collect();
|
|
18
|
+
const values: Record<string, number> = {};
|
|
19
|
+
for (const scope of resourceMetrics.scopeMetrics) {
|
|
20
|
+
for (const metric of scope.metrics) {
|
|
21
|
+
const point = metric.dataPoints[0];
|
|
22
|
+
if (point && typeof point.value === "number") values[metric.descriptor.name] = point.value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return values;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function blockLoop(ms: number): void {
|
|
29
|
+
const startedAt = performance.now();
|
|
30
|
+
while (performance.now() - startedAt < ms) {
|
|
31
|
+
// Busy-wait: the synchronous stall the metric exists to expose.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("event-loop delay metrics", () => {
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
stopEventLoopDelayMetrics();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("exports the worst stall since the last collection, then resets", async () => {
|
|
41
|
+
const reader = new CollectingReader();
|
|
42
|
+
const provider = new MeterProvider({ readers: [reader] });
|
|
43
|
+
startEventLoopDelayMetrics(provider.getMeter("test"));
|
|
44
|
+
|
|
45
|
+
// Let the histogram take baseline samples, then freeze the loop.
|
|
46
|
+
await Bun.sleep(50);
|
|
47
|
+
blockLoop(300);
|
|
48
|
+
await Bun.sleep(30);
|
|
49
|
+
|
|
50
|
+
const first = await gaugeValues(reader);
|
|
51
|
+
expect(first["nodejs.eventloop.delay.max"]).toBeGreaterThan(0.25);
|
|
52
|
+
expect(first["nodejs.eventloop.delay.p50"]).toBeLessThan(0.25);
|
|
53
|
+
|
|
54
|
+
// The stall was reported once; the next interval starts clean.
|
|
55
|
+
await Bun.sleep(50);
|
|
56
|
+
const second = await gaugeValues(reader);
|
|
57
|
+
expect(second["nodejs.eventloop.delay.max"]).toBeLessThan(0.25);
|
|
58
|
+
|
|
59
|
+
await provider.shutdown();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("console log bridge", () => {
|
|
64
|
+
const emitted: LogRecord[] = [];
|
|
65
|
+
const emit = mock((record: LogRecord) => {
|
|
66
|
+
emitted.push(record);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
uninstallConsoleLogBridge();
|
|
71
|
+
logs.disable();
|
|
72
|
+
emitted.length = 0;
|
|
73
|
+
emit.mockClear();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("mirrors console lines as scrubbed log records and still writes to the console", () => {
|
|
77
|
+
logs.setGlobalLoggerProvider({ getLogger: () => ({ emit }) });
|
|
78
|
+
const originalWarn = console.warn;
|
|
79
|
+
const printed: unknown[][] = [];
|
|
80
|
+
console.warn = (...args: unknown[]) => {
|
|
81
|
+
printed.push(args);
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
installConsoleLogBridge();
|
|
85
|
+
const secret = `sk-ant-api03-${"a".repeat(90)}`;
|
|
86
|
+
console.warn("[db-client] slow statement %dms token=%s", 250, secret);
|
|
87
|
+
|
|
88
|
+
expect(printed).toHaveLength(1);
|
|
89
|
+
expect(emitted).toHaveLength(1);
|
|
90
|
+
expect(emitted[0]?.severityNumber).toBe(SeverityNumber.WARN);
|
|
91
|
+
expect(emitted[0]?.severityText).toBe("WARN");
|
|
92
|
+
expect(String(emitted[0]?.body)).toContain("slow statement 250ms");
|
|
93
|
+
expect(String(emitted[0]?.body)).not.toContain(secret);
|
|
94
|
+
} finally {
|
|
95
|
+
uninstallConsoleLogBridge();
|
|
96
|
+
console.warn = originalWarn;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -252,6 +252,20 @@ describe("Phase 6 — /api/pricing REST surface", () => {
|
|
|
252
252
|
expect(res.status).toBe(400);
|
|
253
253
|
});
|
|
254
254
|
|
|
255
|
+
// `dsh` is a registered ProviderNameSchema harness provider
|
|
256
|
+
// (src/types.ts) but PricingProviderSchema forgot it, so an operator
|
|
257
|
+
// could not record a dsh price row (#1636). `acp` sets the precedent
|
|
258
|
+
// for accepting a provider here even without seeded rate rows.
|
|
259
|
+
test("POST /api/pricing/dsh/{model}/input accepts the dsh provider", async () => {
|
|
260
|
+
const res = await authedFetch(`/api/pricing/dsh/dsh-model/input`, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
body: JSON.stringify({ pricePerMillionUsd: 2.5 }),
|
|
263
|
+
});
|
|
264
|
+
expect(res.status).toBe(201);
|
|
265
|
+
const row = await res.json();
|
|
266
|
+
expect(row.provider).toBe("dsh");
|
|
267
|
+
});
|
|
268
|
+
|
|
255
269
|
test("POST 400 on invalid token class", async () => {
|
|
256
270
|
const res = await authedFetch(`/api/pricing/codex/gpt-5.3-codex/wrong-class`, {
|
|
257
271
|
method: "POST",
|
|
@@ -78,11 +78,15 @@ describe("profile sync rejection session warning", () => {
|
|
|
78
78
|
fetchImpl,
|
|
79
79
|
);
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
expect(requests[0]?.url).toContain("
|
|
84
|
-
expect(requests[0]?.url).toContain("dataField=soulMd");
|
|
81
|
+
// This mock predates latestPerDataField: the batched request is not
|
|
82
|
+
// confirmed, so the per-field lookups follow it.
|
|
83
|
+
expect(requests[0]?.url).toContain("latestPerDataField=true");
|
|
85
84
|
expect(requests[0]?.headers.get("X-Agent-ID")).toBe("agent-1");
|
|
85
|
+
expect(requests[1]?.url).toContain("event=system.profile_sync_rejected");
|
|
86
|
+
expect(requests[1]?.url).toContain("agentId=agent-1");
|
|
87
|
+
expect(requests[1]?.url).toContain("limit=1");
|
|
88
|
+
expect(requests[1]?.url).toContain("dataField=soulMd");
|
|
89
|
+
expect(requests[1]?.headers.get("X-Agent-ID")).toBe("agent-1");
|
|
86
90
|
expect(requests.at(-1)?.url).toBe("https://api.example.test/me");
|
|
87
91
|
expect(result.injected).toBeTrue();
|
|
88
92
|
expect(result.prompt).toContain("PERSISTED PROFILE SYNC REJECTION");
|
|
@@ -90,6 +90,25 @@ afterAll(async () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
describe("POST /api/tasks draft:true + /promote-draft (#1240)", () => {
|
|
93
|
+
test("a new UI session root (panel or /sessions) is source ui and assigned to the Lead", async () => {
|
|
94
|
+
// Shape the dashboard sends from both the /sessions composer and the
|
|
95
|
+
// contextual session panel: no agentId, source ui, a per-session key.
|
|
96
|
+
const created = await api("POST", "/api/tasks", {
|
|
97
|
+
task: "the chart legend is wrong",
|
|
98
|
+
source: "ui",
|
|
99
|
+
contextKey: "task:ui:workflow:w1:0b7c6e1a-5f38-4b47-9d42-1d7e7c6f0a11",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(created.status).toBe(201);
|
|
103
|
+
expect(created.body.source).toBe("ui");
|
|
104
|
+
expect(created.body.agentId).toBe(leadAgentId);
|
|
105
|
+
expect(created.body.status).toBe("pending");
|
|
106
|
+
expect(created.body.parentTaskId).toBeUndefined();
|
|
107
|
+
expect(created.body.contextKey).toBe(
|
|
108
|
+
"task:ui:workflow:w1:0b7c6e1a-5f38-4b47-9d42-1d7e7c6f0a11",
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
93
112
|
test("a UI follow-up lands on the Lead without being stamped Lead-only", async () => {
|
|
94
113
|
const worker = await createAgent({
|
|
95
114
|
name: "session-parent-worker",
|