@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
|
@@ -1,12 +1,214 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
+
import { delimiter, join } from "node:path";
|
|
5
6
|
import { buildPricingSeedRows } from "../be/seed-pricing";
|
|
6
|
-
import {
|
|
7
|
-
|
|
7
|
+
import {
|
|
8
|
+
CREDENTIAL_PROVIDER_CHECKERS,
|
|
9
|
+
validateProviderCredentials,
|
|
10
|
+
} from "../commands/provider-credentials";
|
|
11
|
+
import { createProviderAdapter } from "../providers";
|
|
12
|
+
import { PricingProviderSchema, type ProviderName, ProviderNameSchema } from "../types";
|
|
13
|
+
import { CHILD_PROCESS_TEST_BUDGET_MS, runChild } from "./test-proc";
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Every `ProviderNameSchema` member must reach every mandatory touch point, or
|
|
17
|
+
* be exempted there with a one-line reason (#1559). A failure names the
|
|
18
|
+
* provider and the file to edit.
|
|
19
|
+
*
|
|
20
|
+
* Some lists are subsets or supersets by design (PricingProviderSchema also
|
|
21
|
+
* accepts `gemini`; the local-harness lists skip cloud providers), so each
|
|
22
|
+
* touch point only asserts that every provider is present or consciously
|
|
23
|
+
* exempted. An exemption for a provider that is actually present fails as
|
|
24
|
+
* stale, so the map cannot rot.
|
|
25
|
+
*
|
|
26
|
+
* Touch points that are already compile-bound to one of these lists are not
|
|
27
|
+
* repeated here: HARNESS_LABEL and ICON_BY_HARNESS (`Record<ProviderName, …>`
|
|
28
|
+
* over the UI's PROVIDER_NAMES), ROUTING_PREFIXES_BY_PROVIDER
|
|
29
|
+
* (`Record<PricingProvider, …>`), the session-costs `provider` field (reuses
|
|
30
|
+
* PricingProviderSchema), DEFAULT_MODEL_TIER_MAP and PROVIDER_STEER_CAPABILITIES
|
|
31
|
+
* (`Record<ProviderName, …>`; the factory switch is covered by
|
|
32
|
+
* provider-steering-capabilities.test.ts).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const repoRoot = join(import.meta.dir, "..", "..");
|
|
36
|
+
|
|
37
|
+
async function readRepoFile(relPath: string): Promise<string> {
|
|
38
|
+
return Bun.file(join(repoRoot, relPath)).text();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The quoted members of the first array/enum literal matched by `pattern`. */
|
|
42
|
+
async function literalMembers(relPath: string, pattern: RegExp): Promise<string[]> {
|
|
43
|
+
const match = (await readRepoFile(relPath)).match(pattern);
|
|
44
|
+
if (!match?.[1]) {
|
|
45
|
+
throw new Error(`Could not find ${pattern} in ${relPath}. Did the declaration move?`);
|
|
46
|
+
}
|
|
47
|
+
return [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1] as string);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The provider names listed after `Supported:` in an unknown-provider error. */
|
|
51
|
+
function supportedList(message: string): string[] {
|
|
52
|
+
const list = message.split("Supported:")[1];
|
|
53
|
+
if (list === undefined) throw new Error(`No "Supported:" list in: ${message}`);
|
|
54
|
+
return list
|
|
55
|
+
.replace(/\.\s*$/, "")
|
|
56
|
+
.split(",")
|
|
57
|
+
.map((name) => name.trim());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const modelsDevFixture = {
|
|
61
|
+
anthropic: { models: { "claude-test": { cost: { input: 1, output: 2 } } } },
|
|
62
|
+
openai: { models: { "gpt-test": { cost: { input: 1, output: 2 } } } },
|
|
63
|
+
openrouter: { models: { "test/model": { cost: { input: 1, output: 2 } } } },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
interface TouchPoint {
|
|
67
|
+
/** The file a new provider must be added to. */
|
|
68
|
+
file: string;
|
|
69
|
+
/** What inside that file. */
|
|
70
|
+
what: string;
|
|
71
|
+
members: () => Promise<readonly string[]>;
|
|
72
|
+
/** Providers deliberately absent here, each with a one-line reason. */
|
|
73
|
+
exempt: Partial<Record<ProviderName, string>>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const TOUCH_POINTS: TouchPoint[] = [
|
|
77
|
+
{
|
|
78
|
+
file: "src/commands/provider-credentials.ts",
|
|
79
|
+
what: "CREDENTIAL_PROVIDER_CHECKERS (and the SupportedProvider union it is keyed by)",
|
|
80
|
+
members: async () => Object.keys(CREDENTIAL_PROVIDER_CHECKERS),
|
|
81
|
+
exempt: {},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
file: "src/commands/provider-credentials.ts",
|
|
85
|
+
what: "the validateProviderCredentials switch (dashboard Test connection)",
|
|
86
|
+
members: async () => {
|
|
87
|
+
const source = await readRepoFile("src/commands/provider-credentials.ts");
|
|
88
|
+
const start = source.indexOf("export async function validateProviderCredentials(");
|
|
89
|
+
const end = source.indexOf("\n}\n", start);
|
|
90
|
+
if (start === -1 || end === -1) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"Could not find validateProviderCredentials in src/commands/provider-credentials.ts.",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return [...source.slice(start, end).matchAll(/case "([^"]+)":/g)].map((m) => m[1] as string);
|
|
96
|
+
},
|
|
97
|
+
exempt: {},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
file: "src/commands/provider-credentials.ts",
|
|
101
|
+
what: "the validateProviderCredentials unknown-provider error message",
|
|
102
|
+
members: async () => {
|
|
103
|
+
const result = await validateProviderCredentials("__unregistered_provider__");
|
|
104
|
+
return supportedList(result.ok ? "" : result.error);
|
|
105
|
+
},
|
|
106
|
+
exempt: {},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
file: "src/providers/index.ts",
|
|
110
|
+
what: "the createProviderAdapter unknown-provider error message",
|
|
111
|
+
members: async () => {
|
|
112
|
+
try {
|
|
113
|
+
await createProviderAdapter("__unregistered_provider__");
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return supportedList(String(error));
|
|
116
|
+
}
|
|
117
|
+
throw new Error("createProviderAdapter accepted an unregistered provider.");
|
|
118
|
+
},
|
|
119
|
+
exempt: {},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
file: "src/types.ts",
|
|
123
|
+
what: "PricingProviderSchema (#1636; the session-costs provider field reuses it)",
|
|
124
|
+
members: async () => PricingProviderSchema.options,
|
|
125
|
+
exempt: {},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
file: "src/be/seed-pricing.ts",
|
|
129
|
+
what: "the provider rows buildPricingSeedRows emits",
|
|
130
|
+
members: async () => [
|
|
131
|
+
...new Set(buildPricingSeedRows(modelsDevFixture).map((row) => row.provider)),
|
|
132
|
+
],
|
|
133
|
+
exempt: {
|
|
134
|
+
acp: "A generic ACP target owns its own billing and the adapter reports totalCostUsd: 0.",
|
|
135
|
+
dsh: "The dsh adapter reports no cost data today, so there is nothing to price.",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
file: "apps/ui/src/api/types.ts",
|
|
140
|
+
what: "PROVIDER_NAMES (#1637; HARNESS_LABEL and ICON_BY_HARNESS are typed against it)",
|
|
141
|
+
members: () =>
|
|
142
|
+
literalMembers("apps/ui/src/api/types.ts", /export const PROVIDER_NAMES = \[([^\]]*)\]/),
|
|
143
|
+
exempt: {},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
file: "src/http/agents.ts",
|
|
147
|
+
what: "LocalHarnessProviderSchema (PATCH /api/agents/{id}/runtime)",
|
|
148
|
+
members: () =>
|
|
149
|
+
literalMembers(
|
|
150
|
+
"src/http/agents.ts",
|
|
151
|
+
/const LocalHarnessProviderSchema = z\.enum\(\[([^\]]*)\]/,
|
|
152
|
+
),
|
|
153
|
+
exempt: {
|
|
154
|
+
"claude-managed":
|
|
155
|
+
"Cloud harness: runs in Anthropic's sandbox, no local runtime to configure.",
|
|
156
|
+
devin: "Cloud harness: runs on Devin's API, no local runtime to configure.",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
file: "apps/ui/src/lib/agent-runtime-models.ts",
|
|
161
|
+
what: "LOCAL_HARNESSES (dashboard runtime harness picker)",
|
|
162
|
+
members: () =>
|
|
163
|
+
literalMembers(
|
|
164
|
+
"apps/ui/src/lib/agent-runtime-models.ts",
|
|
165
|
+
/export const LOCAL_HARNESSES[^=]*=\s*\[([^\]]*)\]/,
|
|
166
|
+
),
|
|
167
|
+
exempt: {
|
|
168
|
+
"claude-managed":
|
|
169
|
+
"Cloud harness: runs in Anthropic's sandbox, no local runtime to configure.",
|
|
170
|
+
devin: "Cloud harness: runs on Devin's API, no local runtime to configure.",
|
|
171
|
+
dsh: "KNOWN GAP (#1559): the server's LocalHarnessProviderSchema accepts dsh but the picker does not offer it yet.",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
describe("provider registration synchronization", () => {
|
|
177
|
+
for (const touchPoint of TOUCH_POINTS) {
|
|
178
|
+
describe(`${touchPoint.file}: ${touchPoint.what}`, () => {
|
|
179
|
+
test("the extractor finds the list and every exemption is still needed", async () => {
|
|
180
|
+
const members = await touchPoint.members();
|
|
181
|
+
expect(members.length).toBeGreaterThan(0);
|
|
182
|
+
const stale = Object.keys(touchPoint.exempt).filter((provider) =>
|
|
183
|
+
members.includes(provider),
|
|
184
|
+
);
|
|
185
|
+
if (stale.length > 0) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Stale exemption in ${touchPoint.file} (${touchPoint.what}): ${stale.join(", ")} is now present. Remove it from the exempt map in src/tests/provider-registration.test.ts.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
for (const provider of ProviderNameSchema.options) {
|
|
193
|
+
if (touchPoint.exempt[provider]) continue;
|
|
194
|
+
test(`registers ${provider}`, async () => {
|
|
195
|
+
const members = await touchPoint.members();
|
|
196
|
+
if (!members.includes(provider)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Provider "${provider}" is missing from ${touchPoint.what} in ${touchPoint.file}. Add it there, or exempt it for this touch point in src/tests/provider-registration.test.ts with a one-line reason.`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ─── docker-entrypoint.sh ────────────────────────────────────────────────────
|
|
208
|
+
// Bash chains a TS type cannot reach, so these run the real blocks in a bash
|
|
209
|
+
// subprocess and check which branch each provider lands in.
|
|
210
|
+
|
|
211
|
+
const entrypoint = await readRepoFile("docker-entrypoint.sh");
|
|
10
212
|
|
|
11
213
|
const bootstrapStart = 'if [ "$HARNESS_PROVIDER" = "pi" ]; then';
|
|
12
214
|
const bootstrapEnd = "# ---- Verify provider binary is reachable ----";
|
|
@@ -23,20 +225,23 @@ if (isolatedBootstrapBlock === bootstrapBlock) {
|
|
|
23
225
|
throw new Error(`Could not isolate entrypoint assignment: ${codexHomeAssignment}`);
|
|
24
226
|
}
|
|
25
227
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
228
|
+
/**
|
|
229
|
+
* The line each provider's credential-bootstrap branch prints with no
|
|
230
|
+
* credentials set, or why it has no branch. Typed over every provider, so a
|
|
231
|
+
* new one does not compile until it is listed here.
|
|
232
|
+
*/
|
|
233
|
+
const BOOTSTRAP: Record<ProviderName, { expect: string } | { exempt: string }> = {
|
|
234
|
+
claude: { expect: "Warning: claude provider has no credentials yet" },
|
|
235
|
+
dsh: { expect: "Warning: dsh provider has no credentials yet" },
|
|
236
|
+
pi: { expect: "Warning: pi provider has no credentials yet" },
|
|
237
|
+
opencode: { expect: "Warning: opencode provider has no credentials yet" },
|
|
238
|
+
"claude-managed": { expect: "Warning: claude-managed provider missing:" },
|
|
239
|
+
devin: { expect: "Warning: devin provider missing DEVIN_API_KEY / DEVIN_ORG_ID" },
|
|
240
|
+
codex: { expect: "Warning: codex provider has no auth.json yet" },
|
|
241
|
+
acp: {
|
|
242
|
+
exempt:
|
|
243
|
+
"KNOWN GAP (#1559): no acp branch, so it falls into the claude else branch and prints the claude warning.",
|
|
244
|
+
},
|
|
40
245
|
};
|
|
41
246
|
|
|
42
247
|
async function runCredentialBootstrap(provider: string): Promise<{
|
|
@@ -65,32 +270,81 @@ async function runCredentialBootstrap(provider: string): Promise<{
|
|
|
65
270
|
}
|
|
66
271
|
}
|
|
67
272
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
}
|
|
273
|
+
function extractMarkedBlock(name: string): string {
|
|
274
|
+
const begin = entrypoint.indexOf(`# BEGIN ${name}`);
|
|
275
|
+
const end = entrypoint.indexOf(`# END ${name}`, begin);
|
|
276
|
+
if (begin === -1 || end === -1) {
|
|
277
|
+
throw new Error(`Could not find the # BEGIN/END ${name} markers in docker-entrypoint.sh.`);
|
|
278
|
+
}
|
|
279
|
+
return entrypoint.slice(begin, end);
|
|
280
|
+
}
|
|
74
281
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
282
|
+
/** Resolvable no-op stubs for every binary the verify chain may look up. */
|
|
283
|
+
function makeBinaryStubDir(): string {
|
|
284
|
+
const dir = mkdtempSync(join(tmpdir(), "provider-registration-bin-"));
|
|
285
|
+
for (const name of ["claude", "codex", "opencode", "dsh"]) {
|
|
286
|
+
const file = join(dir, name);
|
|
287
|
+
writeFileSync(file, "#!/bin/sh\nexit 0\n");
|
|
288
|
+
chmodSync(file, 0o755);
|
|
289
|
+
}
|
|
290
|
+
return dir;
|
|
291
|
+
}
|
|
84
292
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
293
|
+
describe.skipIf(process.platform === "win32")("docker-entrypoint.sh provider chains", () => {
|
|
294
|
+
for (const provider of ProviderNameSchema.options) {
|
|
295
|
+
const entry = BOOTSTRAP[provider] as (typeof BOOTSTRAP)[ProviderName] | undefined;
|
|
296
|
+
if (entry && !("expect" in entry)) {
|
|
297
|
+
test(`credential bootstrap exemption for ${provider} is still needed`, async () => {
|
|
90
298
|
const result = await runCredentialBootstrap(provider);
|
|
91
|
-
|
|
92
|
-
|
|
299
|
+
if (result.stdout.includes(`Warning: ${provider} provider`)) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Stale exemption: docker-entrypoint.sh now has a ${provider} credential-bootstrap branch. Replace its BOOTSTRAP exemption in src/tests/provider-registration.test.ts with the warning it prints.`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
test(`credential bootstrap has a ${provider} branch`, async () => {
|
|
308
|
+
if (!entry) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`Provider "${provider}" is missing from BOOTSTRAP in src/tests/provider-registration.test.ts. Add its docker-entrypoint.sh credential warning, or an exemption with a one-line reason.`,
|
|
311
|
+
);
|
|
93
312
|
}
|
|
94
|
-
|
|
95
|
-
|
|
313
|
+
const result = await runCredentialBootstrap(provider);
|
|
314
|
+
expect(result.exitCode).toBe(0);
|
|
315
|
+
if (!result.stdout.includes(entry.expect)) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Provider "${provider}" has no credential-bootstrap branch in docker-entrypoint.sh (expected "${entry.expect}", got "${result.stdout}"). Add an elif, or exempt it in BOOTSTRAP with a one-line reason.`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// #1463 class: a provider with no branch of its own falls through to the
|
|
324
|
+
// catch-all and gets the claude CLI checked instead of its own binary.
|
|
325
|
+
const verifyChain = extractMarkedBlock("verify_provider_binary");
|
|
326
|
+
for (const provider of ProviderNameSchema.options) {
|
|
327
|
+
test(
|
|
328
|
+
`verify_provider_binary does not route ${provider} through another provider's check`,
|
|
329
|
+
async () => {
|
|
330
|
+
const stubDir = makeBinaryStubDir();
|
|
331
|
+
const result = await runChild(["bash", "-c", verifyChain], {
|
|
332
|
+
env: {
|
|
333
|
+
PATH: [stubDir, "/usr/bin", "/bin"].join(delimiter),
|
|
334
|
+
HARNESS_PROVIDER: provider,
|
|
335
|
+
ACP_TARGET: "opencode",
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
await rm(stubDir, { recursive: true, force: true });
|
|
339
|
+
expect(result.exitCode).toBe(0);
|
|
340
|
+
const checkedClaude = result.stdout.includes("Claude CLI:");
|
|
341
|
+
if (checkedClaude !== (provider === "claude")) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`Provider "${provider}" ${checkedClaude ? "falls through to the claude CLI check" : "skips the claude CLI check"} in docker-entrypoint.sh verify_provider_binary. Give it its own elif, or add it to the final \`!= "pi"\` guard if it has no binary.`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
96
350
|
});
|
|
@@ -97,6 +97,8 @@ const LEAD_OR_TASK_CREATOR_VERBS: PermissionVerb[] = ["task.cancel.any", "task.s
|
|
|
97
97
|
|
|
98
98
|
const LEAD_OR_OWN_NAMESPACE_VERBS: PermissionVerb[] = ["kv.write.any"];
|
|
99
99
|
|
|
100
|
+
const HUMAN_OR_LEAD_OR_RESOURCE_OWNER_VERBS: PermissionVerb[] = ["approval.cancel.any"];
|
|
101
|
+
|
|
100
102
|
const ANY_AUTHENTICATED_VERBS: PermissionVerb[] = [
|
|
101
103
|
"app.manage",
|
|
102
104
|
"app.use",
|
|
@@ -171,6 +173,7 @@ describe("verb-group partition", () => {
|
|
|
171
173
|
...LEAD_OR_RESOURCE_OWNER_VERBS,
|
|
172
174
|
...LEAD_OR_TASK_CREATOR_VERBS,
|
|
173
175
|
...LEAD_OR_OWN_NAMESPACE_VERBS,
|
|
176
|
+
...HUMAN_OR_LEAD_OR_RESOURCE_OWNER_VERBS,
|
|
174
177
|
...ANY_AUTHENTICATED_VERBS,
|
|
175
178
|
...REQUESTER_OWNS_TASK_VERBS,
|
|
176
179
|
...COMPOSITE_VERBS,
|
|
@@ -314,6 +317,33 @@ describe("lead-or-resource-owner verbs", () => {
|
|
|
314
317
|
});
|
|
315
318
|
});
|
|
316
319
|
|
|
320
|
+
describe("human-or-lead-or-resource-owner verbs (approval.cancel.any)", () => {
|
|
321
|
+
const expected: Expected = {
|
|
322
|
+
lead: true,
|
|
323
|
+
worker: false,
|
|
324
|
+
ownerWorker: true,
|
|
325
|
+
creatorWorker: false,
|
|
326
|
+
userRequester: true,
|
|
327
|
+
foreignUser: true,
|
|
328
|
+
operator: true,
|
|
329
|
+
};
|
|
330
|
+
for (const verb of HUMAN_OR_LEAD_OR_RESOURCE_OWNER_VERBS) {
|
|
331
|
+
test(`${verb}: humans, lead, or owner allowed`, () => {
|
|
332
|
+
expectDecisions(verb, OWNED_RESOURCE, expected);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
test("ownerless resource denies non-lead agents", () => {
|
|
337
|
+
const decision = can({
|
|
338
|
+
principal: PRINCIPALS.ownerWorker,
|
|
339
|
+
verb: "approval.cancel.any",
|
|
340
|
+
resource: { kind: "owned", ownerAgentId: null },
|
|
341
|
+
source: "mcp",
|
|
342
|
+
});
|
|
343
|
+
expect(decision.allow).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
317
347
|
describe("lead-or-task-creator verbs (task.cancel.any)", () => {
|
|
318
348
|
const expected: Expected = {
|
|
319
349
|
lead: true,
|
|
@@ -30,6 +30,14 @@ describe("reasoningCapability — cache-sourced levels", () => {
|
|
|
30
30
|
"xhigh",
|
|
31
31
|
]);
|
|
32
32
|
});
|
|
33
|
+
test("Claude Sonnet 5.5 exposes supported CLI effort levels", () => {
|
|
34
|
+
expect(reasoningCapability("claude", "claude-sonnet-5-5").levels).toEqual([
|
|
35
|
+
"low",
|
|
36
|
+
"medium",
|
|
37
|
+
"high",
|
|
38
|
+
"xhigh",
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
33
41
|
test("claude claude-opus-4-8: levels come from reasoning_options.effort, not the fallback", () => {
|
|
34
42
|
const cap = reasoningCapability("claude", "claude-opus-4-8");
|
|
35
43
|
expect(cap.supported).toBe(true);
|
|
@@ -578,6 +578,13 @@ describe("script connections", () => {
|
|
|
578
578
|
// 164 indexes agent_tasks columns and alters api_key_status, neither of
|
|
579
579
|
// which this migration-112-only fixture creates.
|
|
580
580
|
markMigrationApplied(database, "164_usage_cover_index_and_key_plans.sql");
|
|
581
|
+
markMigrationApplied(database, "166_approval_request_auto_cancellation.sql");
|
|
582
|
+
// 171 alters memory_retrieval, which this migration-112-only fixture does not create.
|
|
583
|
+
markMigrationApplied(database, "171_memory_retrieval_relevance.sql");
|
|
584
|
+
// 175 indexes workflow_runs, which this migration-112-only fixture does not create.
|
|
585
|
+
markMigrationApplied(database, "175_workflow_runs_list_index.sql");
|
|
586
|
+
// 177 indexes events, which this migration-112-only fixture does not create.
|
|
587
|
+
markMigrationApplied(database, "177_events_event_agent_created_index.sql");
|
|
581
588
|
|
|
582
589
|
const id = crypto.randomUUID();
|
|
583
590
|
const now = new Date().toISOString();
|
|
@@ -98,6 +98,45 @@ describe("script SDK allowlist", () => {
|
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
test("approval_cancel posts to the cancel route with the reason", async () => {
|
|
102
|
+
let request: { method: string; path: string; body: unknown } | undefined;
|
|
103
|
+
const httpServer = Bun.serve({
|
|
104
|
+
port: 0,
|
|
105
|
+
async fetch(req) {
|
|
106
|
+
request = {
|
|
107
|
+
method: req.method,
|
|
108
|
+
path: new URL(req.url).pathname,
|
|
109
|
+
body: await req.json(),
|
|
110
|
+
};
|
|
111
|
+
return Response.json({ alreadyCancelled: false, runCancelled: false });
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const config = new SwarmConfig({
|
|
115
|
+
system: {
|
|
116
|
+
apiKey: { value: "sdk-test-key", isSecret: true },
|
|
117
|
+
agentId: { value: "sdk-test-agent", isSecret: false },
|
|
118
|
+
mcpBaseUrl: { value: `http://127.0.0.1:${httpServer.port}`, isSecret: false },
|
|
119
|
+
},
|
|
120
|
+
user: {},
|
|
121
|
+
});
|
|
122
|
+
const sdk = createSwarmSdk(config);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const requestId = crypto.randomUUID();
|
|
126
|
+
await sdk.approval_cancel({ requestId, reason: "done" });
|
|
127
|
+
expect(request).toEqual({
|
|
128
|
+
method: "POST",
|
|
129
|
+
path: `/api/approval-requests/${requestId}/cancel`,
|
|
130
|
+
body: { reason: "done" },
|
|
131
|
+
});
|
|
132
|
+
await expect(sdk.approval_cancel({} as { requestId: string })).rejects.toThrow(
|
|
133
|
+
"approval_cancel requires string `requestId`",
|
|
134
|
+
);
|
|
135
|
+
} finally {
|
|
136
|
+
httpServer.stop(true);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
101
140
|
test("schedule_list maps includeFull to the full HTTP field and keeps the default slim", async () => {
|
|
102
141
|
const requestUrls: URL[] = [];
|
|
103
142
|
const httpServer = Bun.serve({
|
|
@@ -137,6 +137,7 @@ describe("normalizeModelKey()", () => {
|
|
|
137
137
|
expect(normalizeModelKey("claude", "claude-opus-5")).toBe("claude-opus-5");
|
|
138
138
|
expect(normalizeModelKey("claude", "claude-fable-5")).toBe("claude-fable-5");
|
|
139
139
|
expect(normalizeModelKey("claude", "claude-mythos-5")).toBe("claude-mythos-5");
|
|
140
|
+
expect(normalizeModelKey("claude", "claude-sonnet-5-5")).toBe("claude-sonnet-5-5");
|
|
140
141
|
expect(normalizeModelKey("claude", "claude-sonnet-5")).toBe("claude-sonnet-5");
|
|
141
142
|
});
|
|
142
143
|
|