@bridge_gpt/mcp-server 0.2.62 → 0.2.66
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 +66 -15
- package/build/agent-launchers/claude-executor-adapter.js +145 -20
- package/build/agent-launchers/executor-adapter.js +81 -2
- package/build/agents.generated.js +10 -1
- package/build/bounded-wait.js +32 -1
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +7 -6
- package/build/conductor/bridge-api-client.js +565 -32
- package/build/conductor/bring-up-facts.js +3 -1
- package/build/conductor/doctor.js +23 -14
- package/build/conductor/epic-child-continuation.js +351 -0
- package/build/conductor/epic-implementer-cli.js +154 -17
- package/build/conductor/git-hooks.js +529 -118
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/hook-runtime-qualification.js +340 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness-sections.js +5 -4
- package/build/conductor/readiness.js +45 -12
- package/build/conductor/recovery-cli.js +859 -27
- package/build/conductor/recovery-operations.js +574 -5
- package/build/conductor/repair-contract.js +69 -3
- package/build/conductor-bundle-artifacts.js +1 -0
- package/build/credential-store.js +52 -13
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +3 -2
- package/build/doctor.js +147 -21
- package/build/drive-epic-key.js +12 -0
- package/build/drive-epic.js +1537 -125
- package/build/epic-coherence-input-cli.js +137 -0
- package/build/epic-coherence-input.js +920 -0
- package/build/epic-coherence-input.test-fixtures.js +175 -0
- package/build/epic-implementer-bin.js +41 -33
- package/build/epic-implementer-bundle-cli.js +14 -1
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +58 -19
- package/build/epic-run-status.js +5 -0
- package/build/epic-shared/bridge-client.js +46 -4
- package/build/executor/cli.js +39 -0
- package/build/executor/completion-contract.js +423 -0
- package/build/executor/completion-supervision.js +291 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/credentials.js +3 -1
- package/build/executor/deps.js +65 -1
- package/build/executor/env.js +12 -0
- package/build/executor/heartbeat.js +13 -3
- package/build/executor/http-client.js +91 -4
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +61 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +1550 -55
- package/build/executor/job-types.js +9 -6
- package/build/executor/preflight.js +189 -1
- package/build/executor/process-containment.js +756 -0
- package/build/executor/process.js +235 -5
- package/build/executor/prompt-spec.js +366 -1
- package/build/executor/provider-checks.js +69 -0
- package/build/executor/rebase-finalization.js +351 -0
- package/build/executor/reconcile-overlap-job.js +77 -84
- package/build/executor/required-verification.js +313 -0
- package/build/executor/required-work-ledger.js +1121 -0
- package/build/executor/results.js +142 -0
- package/build/executor/resume-pre-spawn.js +87 -4
- package/build/executor/runner.js +168 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/test-declaration.js +20 -0
- package/build/executor/types.js +13 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +70 -34
- package/build/executor/worker-finalization.js +168 -26
- package/build/executor/worker-guard-hook.js +75 -14
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/executor/worktree.js +13 -0
- package/build/executor-launch.js +658 -0
- package/build/index.js +594 -1152
- package/build/init.js +107 -4
- package/build/install-bridge-conductor.js +231 -41
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/merge-pull-request.js +1 -1
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +9 -8
- package/build/plan-discovery.js +137 -0
- package/build/plan-epic-implementer-eligibility.js +2 -2
- package/build/plane/cli.js +37 -3
- package/build/plane/member-logs.js +3 -0
- package/build/plane/member-roster.js +7 -1
- package/build/plane/shutdown.js +59 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +146 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/scripts/executor-protocol-contract-driver.js +13 -3
- package/build/setup-epic.js +208 -125
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +5 -4
- package/build/worktree-core.js +6 -5
- package/docs/CONDUCTOR.md +116 -2
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +2 -1
- package/pipelines/implement-ticket.json +1 -1
package/README.md
CHANGED
|
@@ -252,9 +252,13 @@ workflows. Bridge-spawned Codex workers then select it automatically, including
|
|
|
252
252
|
the fresh implementation session after review. Pass `--codex-profile=off` to
|
|
253
253
|
decline; the installer leaves any existing profile content intact. On package
|
|
254
254
|
upgrade, the next Bridge-spawned worker refreshes an older, unmodified managed
|
|
255
|
-
profile before launch.
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
profile before launch. The profile preapproves review-decision capture, the
|
|
256
|
+
fixed reviewed-document publisher, and the implementation phase-result record,
|
|
257
|
+
so those required Bridge writes do not enter Codex Auto-review. The general
|
|
258
|
+
`attachment` tool stays on automatic review because it also supports deletion
|
|
259
|
+
and replacement. If a profile already contains user-authored or conflicting
|
|
260
|
+
content, the installer leaves it untouched and reports that workers cannot use
|
|
261
|
+
it. Run `doctor --agent codex` to
|
|
258
262
|
inspect the current status. To use the profile in a Codex session you open
|
|
259
263
|
yourself, run `codex --profile bridge`. Bridge `--auto` controls recipe gates;
|
|
260
264
|
Codex MCP tool approvals are a separate setting. The profile does not change
|
|
@@ -369,7 +373,7 @@ later boot.
|
|
|
369
373
|
"mcpServers": {
|
|
370
374
|
"bridge": {
|
|
371
375
|
"command": "npx",
|
|
372
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
376
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
|
|
373
377
|
"env": {
|
|
374
378
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
375
379
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -391,7 +395,7 @@ later boot.
|
|
|
391
395
|
"bridge": {
|
|
392
396
|
"type": "stdio",
|
|
393
397
|
"command": "npx",
|
|
394
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
398
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
|
|
395
399
|
"env": {
|
|
396
400
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
397
401
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -413,7 +417,7 @@ later boot.
|
|
|
413
417
|
"bridge": {
|
|
414
418
|
"type": "stdio",
|
|
415
419
|
"command": "npx",
|
|
416
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
420
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
|
|
417
421
|
"env": {
|
|
418
422
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
419
423
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -436,7 +440,7 @@ Windsurf only supports global MCP configuration.
|
|
|
436
440
|
"mcpServers": {
|
|
437
441
|
"bridge": {
|
|
438
442
|
"command": "npx",
|
|
439
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
443
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
|
|
440
444
|
"env": {
|
|
441
445
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
442
446
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -455,7 +459,7 @@ Windsurf only supports global MCP configuration.
|
|
|
455
459
|
```toml
|
|
456
460
|
[mcp_servers.bridge]
|
|
457
461
|
command = "npx"
|
|
458
|
-
args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
462
|
+
args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"]
|
|
459
463
|
|
|
460
464
|
[mcp_servers.bridge.env]
|
|
461
465
|
BAPI_BASE_URL = "https://bridgegpt-api.com"
|
|
@@ -893,7 +897,7 @@ Because tier 2 outranks tier 3, a stale `SFCC_HOSTNAME` left in the environment
|
|
|
893
897
|
"env": { "BRIDGE_MCP_PROFILE": "sfcc" }
|
|
894
898
|
```
|
|
895
899
|
|
|
896
|
-
`sfcc` gives the eight read tools
|
|
900
|
+
`sfcc` gives the two diagnostics, the eight read tools, and `sfcc_log_query`. For the nine destructive write tools as well, use `"sfcc,sfcc-write"`; `full` expands to every group and is therefore write-capable. Without any of these, no SFCC tool is registered — not even the diagnostics.
|
|
897
901
|
|
|
898
902
|
**4. Verify and grant OCAPI access.** With `sfcc` selected in step 3 (both diagnostics live in that profile), ask your agent to run `sfcc_setup_status` (expect all checks ✓), then `check_permissions`. If it reports HTTP 401/403, it prints the exact OCAPI Settings JSON to paste in Business Manager (**Administration → Site Development → Open Commerce API Settings → Data API** tab); save it there and re-run. Restart the MCP client after any credential or env change — a running session does not pick them up.
|
|
899
903
|
|
|
@@ -1049,7 +1053,7 @@ Each `KEY` must match `[A-Z]+-[0-9]+` (e.g., `BAPI-248`). The CLI creates/switch
|
|
|
1049
1053
|
npx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248
|
|
1050
1054
|
```
|
|
1051
1055
|
|
|
1052
|
-
**Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket's `difficulty` (1-2 → cheap, 3-6 → basic, 7-10 → premium) and may inject `--model` at the spawn boundary. The Python backend returns only the coarse tier (`GET /jira/tickets/{KEY}/model-tier`, computing + caching difficulty on demand). Claude and Cursor map that tier through their agent registry aliases; Cursor validates against `cursor-agent --list-models`. Routing is gated per repo by `difficulty_model_routing_enabled` (default **ON**). Claude/Cursor may use `difficulty_model_tier_overrides` (tier → alias) and retain their premium fallback behavior on an unknown tier or transient failure. When an alias cannot be validated, the CLI omits `--model` and launches the agent default. `--dry-run` resolves routing read-only to preview the command without creating worktrees or opening tabs.
|
|
1056
|
+
**Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket's `difficulty` (1-2 → cheap, 3-6 → basic, 7-10 → premium) and may inject `--model` at the spawn boundary. Under the repo's `quality` optimization posture (also the fail-safe default), premium starts one step earlier, at difficulty 6. The Python backend returns only the coarse tier (`GET /jira/tickets/{KEY}/model-tier`, computing + caching difficulty on demand). Claude and Cursor map that tier through their agent registry aliases; Cursor validates against `cursor-agent --list-models`. Routing is gated per repo by `difficulty_model_routing_enabled` (default **ON**). Claude/Cursor may use `difficulty_model_tier_overrides` (tier → alias) and retain their premium fallback behavior on an unknown tier or transient failure. When an alias cannot be validated, the CLI omits `--model` and launches the agent default. `--dry-run` resolves routing read-only to preview the command without creating worktrees or opening tabs.
|
|
1053
1057
|
|
|
1054
1058
|
For Codex, `GET /jira/codex-model-catalog?repo_name=...` is the sole tier-to-model authority. Bridge derives its three bare CLI IDs and SHA-256 mapping revision at request time from `MODEL_REGISTRY`; the launcher fetches and validates that catalog once per invocation. Repository `difficulty_model_tier_overrides` do not apply to Codex. A failed access, config, tier, catalog, revision, or alias check omits `--model` and launches the configured Codex default. The catalog expresses **Bridge provider policy only**: it cannot confirm that the local Codex login or subscription can use a model. The CLI has no supported non-interactive entitlement probe, so routing emits that limitation as a diagnostic and never uses Bridge's OpenAI API key as proof of local access.
|
|
1055
1059
|
|
|
@@ -1128,6 +1132,51 @@ including every error path, ever offers you two paths.
|
|
|
1128
1132
|
the strategy and the posture (see below). With none of them, a multi-ticket epic
|
|
1129
1133
|
runs on `epic/<KEY>`, unattended.
|
|
1130
1134
|
|
|
1135
|
+
**Finding the plan from the epic key (BAPI-1285).** `--plan-file` is optional.
|
|
1136
|
+
Without it, `drive-epic` searches `{docs_dir}/epic-plans/*/epic-plan.meta.json` for a
|
|
1137
|
+
file whose `epic_key` is exactly this epic (`docs_dir` is `BAPI_DOCS_DIR`, else
|
|
1138
|
+
`docs/tmp`, resolved against the Git top level; an absolute `BAPI_DOCS_DIR` is used
|
|
1139
|
+
as given). Exactly one match with a sibling `epic-plan.dag.json` is used as if you
|
|
1140
|
+
had passed `--plan-file`, and its path is printed before anything is created. Zero
|
|
1141
|
+
matches, two or more, or an unusable meta file **stop** with the paths found and
|
|
1142
|
+
create nothing, and a folder name, a child ticket key, or
|
|
1143
|
+
`sibling-ticket-manifest.json` is never used to guess. A supplied `--plan-file`
|
|
1144
|
+
always wins and discovery does not run.
|
|
1145
|
+
|
|
1146
|
+
The meta file is `{"epic_key": "<KEY>"}`, written by `emit-epic-implementer-bundle
|
|
1147
|
+
finalize` beside the DAG (its own atomic write, reported as `meta_path` in
|
|
1148
|
+
`--json`). It is separate from `epic-plan.dag.json` on purpose: the DAG is sent to
|
|
1149
|
+
the server, which rejects unknown fields. A plan finalized before this change has no
|
|
1150
|
+
meta file, so it is reported as not found — pass its DAG with `--plan-file`.
|
|
1151
|
+
|
|
1152
|
+
**A live run with no plan.** If the epic already has a live run and no plan was
|
|
1153
|
+
supplied or found, `drive-epic` creates nothing. It makes sure that run has an
|
|
1154
|
+
executor: it reuses a verified live one (never starting a second), starts one when
|
|
1155
|
+
the old one is gone, starts none for a durable launch profile with a confirmed
|
|
1156
|
+
repo-wide executor, and starts none when this checkout's plane is bound to that
|
|
1157
|
+
run. An epic with several active runs (`live-run-ambiguous`), or a run that cannot
|
|
1158
|
+
be read (`live-run-unknown`), is refused by name.
|
|
1159
|
+
|
|
1160
|
+
**Preview with `--dry-run`.** `drive-epic <EPIC> --dry-run` is read-only. It reads
|
|
1161
|
+
the target, readiness, the live run, and the plan, runs the bootstrap's own dry-run
|
|
1162
|
+
validation when a plan is available, and reports the decision a real invocation
|
|
1163
|
+
would make — `create`, `attach`, `refuse`, or `stop` (exit `0` for the first two,
|
|
1164
|
+
`1` for the others). It never creates a run, cuts a branch, starts a plane,
|
|
1165
|
+
requests a lane, or starts or signals an executor. With `--json` (valid only with
|
|
1166
|
+
`--dry-run`) stdout carries exactly one JSON document with `epic_key`, `target`,
|
|
1167
|
+
`readiness`, `live_run`, `plan`, `summary`, `decision`, and `refusal`. The
|
|
1168
|
+
readiness `reason_id` and `remedy` are the strings the real refusal prints. The
|
|
1169
|
+
webhook URL, credentials, and policy contents are never included. The preview does
|
|
1170
|
+
probes only a selected loopback plane after a network readiness failure and an
|
|
1171
|
+
available plan. A refused port plus passing local preflight yields a conditional
|
|
1172
|
+
`create` with optional `cold_start` (`plane_endpoint`, `will_start_plane: true`,
|
|
1173
|
+
`live_run_checked: false`), `live_run: null`, and partial offline setup validation.
|
|
1174
|
+
The real launch attempts guarded startup, then rechecks run existence and server
|
|
1175
|
+
validation; the preview does not guarantee creation. Remote targets, non-refused
|
|
1176
|
+
port probes, and ordinary production previews keep their existing refusal or
|
|
1177
|
+
create behavior. `target.source` is optional and appears only for selected
|
|
1178
|
+
profiles (`profile` or `profile-env`).
|
|
1179
|
+
|
|
1131
1180
|
**The composed bring-up is two-phase (BAPI-1102).** With `--plan-file` on a
|
|
1132
1181
|
repository whose readiness is missing only the runtime facts, one invocation runs,
|
|
1133
1182
|
in order: the **control plane** (Bridge API server + reconciler, each gated on its
|
|
@@ -1209,8 +1258,10 @@ default, or, when it declares none, as an explicit recorded `no_ci_gate` mode.
|
|
|
1209
1258
|
> combining `--attended` with a file that declares `posture: "unattended"` is a named
|
|
1210
1259
|
> refusal rather than a silent winner.
|
|
1211
1260
|
|
|
1212
|
-
An unattended run has two
|
|
1213
|
-
(`unattended_epic_implementer_allowed
|
|
1261
|
+
An unattended run has two **repository** prerequisites — consent
|
|
1262
|
+
(`unattended_epic_implementer_allowed`, ON by default; the repository owner can switch
|
|
1263
|
+
"Allow unattended Epic Implementer runs" off and on in the Bridge admin panel's Setup
|
|
1264
|
+
page) and a verified notify webhook default — plus
|
|
1214
1265
|
confirmed default-branch review/CI workflows. Each is refused by name **before any
|
|
1215
1266
|
run row is created** (`target_not_allowed`, `webhook_unverified`,
|
|
1216
1267
|
`repository_readiness_unconfirmed`), and `drive-epic` readiness and `doctor` both
|
|
@@ -1417,7 +1468,7 @@ If a custom pipeline has the same key as a built-in pipeline, the custom version
|
|
|
1417
1468
|
| `BAPI_BASE_URL` (`executor` subcommand) | **Yes** | _(none)_ | The `executor` deliberately has **no** production fallback — it refuses to start rather than guess a target |
|
|
1418
1469
|
| `BAPI_REPO_NAME` | Yes | _(none)_ | Jira project/repository identifier configured in Bridge API |
|
|
1419
1470
|
| `BAPI_API_KEY` | No | _(none)_ | A Bridge credential **is** required; this environment variable is only the first place the server looks for it. When it is unset the server resolves the credential from the user-scoped store (`~/.config/bridge/credentials.json`, target `bapi:<repo>`), which is why generated MCP registrations are secret-free |
|
|
1420
|
-
| `BAPI_PROJECT_ROOT` | No | _(see fallback order)_ | Absolute path to project root. Anchors `BAPI_DOCS_DIR` and `BAPI_PIPELINES_DIR` resolution. Resolved once, in order: `BAPI_PROJECT_ROOT` → the connected client's MCP `roots/list` → `CLAUDE_PROJECT_DIR` → `process.cwd()`. Several paths *write* it into a generated registration (`--init`, host-config provisioning, the worktree `mcp-invoke` shim) — that is provenance, not a runtime default |
|
|
1471
|
+
| `BAPI_PROJECT_ROOT` | No | _(see fallback order)_ | Absolute path to project root. Anchors `BAPI_DOCS_DIR` and `BAPI_PIPELINES_DIR` resolution. Resolved once, in order: `BAPI_PROJECT_ROOT` → the connected client's MCP `roots/list` → `CLAUDE_PROJECT_DIR` → `process.cwd()`. The explicit value is validated: it is used only when it exists, is a folder, and contains `.git` (a folder, or a worktree's `.git` file) or `.bridge/config`. Otherwise it is ignored and the next source is used, so a path committed from another machine is harmless. The later sources are not validated. The server logs the chosen root and its source to stderr once (for example `Bridge MCP: project root /home/b/app (from roots/list); BAPI_PROJECT_ROOT /Users/a/app ignored: does not exist`), and `doctor` reports whether each configured value is valid. The check proves the path looks like a project, not that it is *this* project. Several paths *write* it into a generated registration (`--init`, `install`, `upgrade`, host-config provisioning, the worktree `mcp-invoke` shim) — that is provenance, not a runtime default; a git-tracked config keeps its existing value. What each host passes at start: see the per-host table in `docs/claude/cli-install-architecture.md` (§5a) |
|
|
1421
1472
|
| `SFCC_HOSTNAME` | No | _(none)_ | SFCC sandbox hostname. Part of the environment credential tier — `SFCC_HOSTNAME`, `SFCC_CLIENT_ID`, and `SFCC_CLIENT_SECRET` must **all three** be set for that tier to apply, and a complete tier takes precedence over `dw.json` |
|
|
1422
1473
|
| `SFCC_CLIENT_ID` | No | _(none)_ | Account Manager API client id. See `SFCC_HOSTNAME` — all three are needed together. Also required on its own when a tool is called with an explicit dotted `instance` |
|
|
1423
1474
|
| `SFCC_CLIENT_SECRET` | No | _(none)_ | Account Manager API client secret. See `SFCC_HOSTNAME` — all three are needed together. Never sent to Bridge; it goes only to the Account Manager token endpoint |
|
|
@@ -1691,7 +1742,7 @@ The full surface, for when you need the complete enumeration. Day-to-day, use [U
|
|
|
1691
1742
|
|
|
1692
1743
|
### MCP tools
|
|
1693
1744
|
|
|
1694
|
-
The authoritative tool catalog covers **
|
|
1745
|
+
The authoritative tool catalog covers **78 tools** (enumerated below). What's actually registered in a session depends on `BRIDGE_MCP_PROFILE`: the default `core` profile loads a trimmed subset, and the conductor/pipeline-authoring/SFCC tools are added only under their respective profiles (see [Environment Variables](#environment-variables)). Async AI tools follow a request/get pattern: call the `request_*` tool to kick off generation, then the matching `get_*` tool to retrieve the result (or pass `wait_for_result: true` to poll automatically).
|
|
1695
1746
|
|
|
1696
1747
|
- **Connectivity & identity** — `ping` (its JSON also carries `docs_dir`, `role`, and `customer_type`)
|
|
1697
1748
|
- **Team & access** — `invite_member` (admin-only; mints a scoped access key for a teammate on an already-configured project — the plaintext key is shown exactly once)
|
|
@@ -1704,7 +1755,7 @@ The authoritative tool catalog covers **77 tools** (enumerated below). What's ac
|
|
|
1704
1755
|
- **Jira status** — `update_jira_status` (pass `status: "auto"` to resolve the configured post-PR status server-side)
|
|
1705
1756
|
- **Repository & CI** — `parse_repository` (`action`: `start`, `status`), `regenerate_directory_map`, `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, `wait_for_ci_checks` (the last two are hidden until `ci_check_config` resolves, and share one visibility gate)
|
|
1706
1757
|
- **Pipelines & automation** — `get_pipeline_recipe` (returns a fully resolved recipe the agent executes step-by-step)
|
|
1707
|
-
- **Config** — `get_project_standards`, `config_field` (operations: `get`, `update`, `list`)
|
|
1758
|
+
- **Config** — `get_project_standards`, `config_field` (operations: `get`, `update`, `list`), `model_thresholds` (actions: `get`, `set`, `clear`; the only way to set personal thresholds)
|
|
1708
1759
|
|
|
1709
1760
|
### Bundled pipelines
|
|
1710
1761
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isModelTier, isValidModelAlias, resolveModelAlias } from "../agent-registry.js";
|
|
2
1
|
import { evaluateClaudeMcpShadowingPolicy, inspectClaudeUserConfigForMcpShadowing, resolveClaudeUserConfigPath, } from "../claude-user-config-doctor.js";
|
|
3
2
|
import { runDenyEnforcementPreflight } from "../conductor/deny-enforcement-preflight.js";
|
|
4
3
|
import { buildExecutorBaseWorkerEnv } from "../executor/env.js";
|
|
@@ -7,7 +6,8 @@ import { provisionExecutorDenyLayer } from "../executor/permissions.js";
|
|
|
7
6
|
import { WORKER_GUARD_ROOT_FLAG } from "../executor/worker-guard-hook.js";
|
|
8
7
|
import { SUPPORTED_EXECUTOR_PLATFORMS, evaluateExecutorPlatform } from "../executor/platform.js";
|
|
9
8
|
import { normalizeMcpServerNames } from "../executor/observation.js";
|
|
10
|
-
import { supported } from "./executor-adapter.js";
|
|
9
|
+
import { COMPLETION_EVIDENCE_TRANSPORT, resolveExecutorModelAlias, supported, } from "./executor-adapter.js";
|
|
10
|
+
import { SUPPORTED_COMPLETION_RUNTIME, boundedIdentifier, } from "../executor/completion-contract.js";
|
|
11
11
|
import { MCP_SERVER_NAME } from "../mcp-identity.js";
|
|
12
12
|
/** Stable identity of this reference implementation. */
|
|
13
13
|
export const CLAUDE_ADAPTER_ID = "claude-reference";
|
|
@@ -158,21 +158,12 @@ export function classifyClaudeAuthFailure(stdoutExcerpt) {
|
|
|
158
158
|
* when it passes both registry alias validation AND the spec's static allowlist;
|
|
159
159
|
* anything else falls through to tier resolution, and an unusable result is null
|
|
160
160
|
* (meaning "omit `--model`") rather than an unvalidated passthrough.
|
|
161
|
+
*
|
|
162
|
+
* BAPI-1343: the rule now lives in the agent-neutral
|
|
163
|
+
* {@link resolveExecutorModelAlias}; this export delegates with no behavior change.
|
|
161
164
|
*/
|
|
162
165
|
export function resolveClaudeModelAlias(spec, payload) {
|
|
163
|
-
|
|
164
|
-
const directRaw = p.model_alias;
|
|
165
|
-
if (typeof directRaw === "string" && directRaw.trim().length > 0) {
|
|
166
|
-
const direct = directRaw.trim();
|
|
167
|
-
const allowed = !spec.staticModelAliasAllowlist || spec.staticModelAliasAllowlist.includes(direct);
|
|
168
|
-
if (isValidModelAlias(direct) && allowed) {
|
|
169
|
-
return direct;
|
|
170
|
-
}
|
|
171
|
-
// An invalid/disallowed direct alias falls through to the tier path — never
|
|
172
|
-
// returned as-is.
|
|
173
|
-
}
|
|
174
|
-
const tier = isModelTier(p.model_tier) ? p.model_tier : null;
|
|
175
|
-
return resolveModelAlias(spec, tier, null);
|
|
166
|
+
return resolveExecutorModelAlias(spec, payload);
|
|
176
167
|
}
|
|
177
168
|
// ---------------------------------------------------------------------------
|
|
178
169
|
// Spawn shape
|
|
@@ -210,8 +201,17 @@ export function resolveClaudeModelAlias(spec, payload) {
|
|
|
210
201
|
* Every entry is pushed SEPARATELY and never joined into a command string, so a
|
|
211
202
|
* prompt or path containing a space or a shell metacharacter is just a value.
|
|
212
203
|
*/
|
|
213
|
-
export function buildClaudeArgv(prompt, alias, mcpConfigPath, posture) {
|
|
214
|
-
|
|
204
|
+
export function buildClaudeArgv(prompt, alias, mcpConfigPath, posture, options = {}) {
|
|
205
|
+
// BAPI-1301 (D4a): the held-stdin form keeps `-p` (headless print mode) but
|
|
206
|
+
// puts NO prompt on argv — it arrives as the first stream-json user message on
|
|
207
|
+
// a held-open stdin, which is what lets the executor keep the worker alive
|
|
208
|
+
// until its required background work resolves. Everything after the input
|
|
209
|
+
// flags — output format, model, exactly one permission posture, strict MCP
|
|
210
|
+
// loading — is identical to the argv form. The default is byte-identical to the
|
|
211
|
+
// pre-BAPI-1301 golden shape.
|
|
212
|
+
const argv = options.promptTransport === "stdin-stream-json"
|
|
213
|
+
? ["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"]
|
|
214
|
+
: ["-p", prompt, "--output-format", "stream-json", "--verbose"];
|
|
215
215
|
if (alias) {
|
|
216
216
|
argv.push("--model", alias);
|
|
217
217
|
}
|
|
@@ -219,6 +219,101 @@ export function buildClaudeArgv(prompt, alias, mcpConfigPath, posture) {
|
|
|
219
219
|
argv.push("--strict-mcp-config", "--mcp-config", mcpConfigPath);
|
|
220
220
|
return argv;
|
|
221
221
|
}
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Held-stdin stream-json messages (BAPI-1301, D4a)
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
/**
|
|
226
|
+
* One stream-json user message as the exact NDJSON line written to the held
|
|
227
|
+
* stdin: `{"type":"user","message":{"role":"user","content":<text>}}\n`.
|
|
228
|
+
* `JSON.stringify` escapes every newline inside `text`, so one message is always
|
|
229
|
+
* exactly one line.
|
|
230
|
+
*/
|
|
231
|
+
function claudeStdinUserLine(text) {
|
|
232
|
+
return `${JSON.stringify({ type: "user", message: { role: "user", content: text } })}\n`;
|
|
233
|
+
}
|
|
234
|
+
/** The prompt as the first held-stdin user message. */
|
|
235
|
+
export function buildClaudeStdinUserMessage(prompt) {
|
|
236
|
+
return claudeStdinUserLine(prompt);
|
|
237
|
+
}
|
|
238
|
+
/** At most this many task ids are named in a nudge. */
|
|
239
|
+
const NUDGE_MAX_TASK_IDS = 16;
|
|
240
|
+
/**
|
|
241
|
+
* The ONE optional-watcher nudge (BAPI-1301, D4a), sent when the worker produced
|
|
242
|
+
* its result while background tasks are still live.
|
|
243
|
+
*
|
|
244
|
+
* The wording is the safety property. It asks the worker to WAIT for tasks it
|
|
245
|
+
* needs and to stop ONLY an optional watcher it no longer needs — it never asks
|
|
246
|
+
* the worker to cancel, kill, or abandon required work to finish sooner, because
|
|
247
|
+
* a nudge that could talk a worker out of required work would turn the
|
|
248
|
+
* supervision meant to guarantee completion into a way to skip it. Task ids are
|
|
249
|
+
* passed through {@link boundedIdentifier}; anything that is not a safe opaque
|
|
250
|
+
* identifier is dropped rather than echoed.
|
|
251
|
+
*/
|
|
252
|
+
export function buildClaudeWatcherNudgeMessage(liveTaskIds) {
|
|
253
|
+
const ids = liveTaskIds
|
|
254
|
+
.map((id) => boundedIdentifier(id))
|
|
255
|
+
.filter((id) => id !== null)
|
|
256
|
+
.slice(0, NUDGE_MAX_TASK_IDS);
|
|
257
|
+
const named = ids.length > 0 ? ` (task ids: ${ids.join(", ")})` : "";
|
|
258
|
+
return claudeStdinUserLine(`Your result was received, but background work you started is still running${named}. ` +
|
|
259
|
+
"If that work is part of this task, wait for it to finish and then report its outcome. " +
|
|
260
|
+
"If a task is only an optional watcher you no longer need (for example a monitor or a " +
|
|
261
|
+
"background log tail), you may stop that watcher. Do not cancel, kill, or abandon any " +
|
|
262
|
+
"required work in order to finish sooner.");
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Runtime identity probe (BAPI-1301, D4a)
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
/** Bound on the `--version` identity probe. */
|
|
268
|
+
export const CLAUDE_RUNTIME_PROBE_TIMEOUT_MS = 15_000;
|
|
269
|
+
/** Placeholder for a version the probe could not recognize — never matches. */
|
|
270
|
+
export const UNRECOGNIZED_CLI_VERSION = "unrecognized";
|
|
271
|
+
/**
|
|
272
|
+
* Parse `claude --version` output of the exact measured form
|
|
273
|
+
* `2.1.282 (Claude Code)` into `2.1.282`. Anything else is null — the raw text
|
|
274
|
+
* is never carried, because it would travel into logs and evidence.
|
|
275
|
+
*/
|
|
276
|
+
export function parseClaudeCliVersion(stdout) {
|
|
277
|
+
const firstLine = (typeof stdout === "string" ? stdout : "").trim().split("\n")[0]?.trim() ?? "";
|
|
278
|
+
const match = /^(\d{1,4}\.\d{1,4}\.\d{1,6}) \(Claude Code\)$/.exec(firstLine);
|
|
279
|
+
return match ? match[1] : null;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The completion platform id for a host: `darwin` + `arm64` is `darwin-arm64`.
|
|
283
|
+
* Every other pair is rendered as `<os>-<arch>` over a closed character set, so
|
|
284
|
+
* it is bounded and can never equal the approved id by accident.
|
|
285
|
+
*/
|
|
286
|
+
export function completionPlatformId(platform, arch) {
|
|
287
|
+
const clean = (value) => (typeof value === "string" ? value : "").toLowerCase().replace(/[^a-z0-9_]/g, "").slice(0, 16) ||
|
|
288
|
+
"unknown";
|
|
289
|
+
return `${clean(platform)}-${clean(arch)}`;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Probe the installed Claude runtime's completion identity through the INJECTED
|
|
293
|
+
* command runner (the adapter never spawns on its own authority). Resolves null
|
|
294
|
+
* when the probe could not run or exited non-zero; otherwise an identity whose
|
|
295
|
+
* `cli_version` is the parsed version or {@link UNRECOGNIZED_CLI_VERSION}. The
|
|
296
|
+
* event schema is the one this adapter's lifecycle parser speaks. Never throws.
|
|
297
|
+
*/
|
|
298
|
+
export async function probeClaudeCompletionRuntime(command, deps) {
|
|
299
|
+
let output;
|
|
300
|
+
try {
|
|
301
|
+
output = await deps.runCommand(command, ["--version"], {
|
|
302
|
+
timeoutMs: deps.timeoutMs ?? CLAUDE_RUNTIME_PROBE_TIMEOUT_MS,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
if (!output || output.exitCode !== 0)
|
|
309
|
+
return null;
|
|
310
|
+
return {
|
|
311
|
+
adapter: SUPPORTED_COMPLETION_RUNTIME.adapter,
|
|
312
|
+
cli_version: parseClaudeCliVersion(output.stdout) ?? UNRECOGNIZED_CLI_VERSION,
|
|
313
|
+
platform: completionPlatformId(deps.platform ?? process.platform, deps.arch ?? process.arch),
|
|
314
|
+
event_schema: SUPPORTED_COMPLETION_RUNTIME.event_schema,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
222
317
|
/**
|
|
223
318
|
* The single permission form for one posture — the whole policy→argv mapping,
|
|
224
319
|
* in one exhaustive place.
|
|
@@ -306,9 +401,10 @@ function shellQuoteHookPath(value) {
|
|
|
306
401
|
* where the executor's env deny-list can guarantee an ambient value never
|
|
307
402
|
* substitutes for the job's own.
|
|
308
403
|
*/
|
|
309
|
-
export function buildWorkerGuardHookCommand(binPath, execPath, worktreePath) {
|
|
404
|
+
export function buildWorkerGuardHookCommand(binPath, execPath, worktreePath, correctionIntentVersion) {
|
|
310
405
|
return (`${shellQuoteHookPath(execPath)} ${shellQuoteHookPath(binPath)} ` +
|
|
311
|
-
`${WORKER_GUARD_ROOT_FLAG} ${shellQuoteHookPath(worktreePath)}`
|
|
406
|
+
`${WORKER_GUARD_ROOT_FLAG} ${shellQuoteHookPath(worktreePath)}` +
|
|
407
|
+
(correctionIntentVersion === 1 ? " --correction-intent-version 1" : ""));
|
|
312
408
|
}
|
|
313
409
|
// ---------------------------------------------------------------------------
|
|
314
410
|
// The adapter factory
|
|
@@ -345,9 +441,24 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
345
441
|
if (input.workerBranch !== undefined) {
|
|
346
442
|
envOptions.workerBranch = input.workerBranch;
|
|
347
443
|
}
|
|
444
|
+
if (input.correctionIntentVersion !== undefined) {
|
|
445
|
+
envOptions.correctionIntentVersion = input.correctionIntentVersion;
|
|
446
|
+
}
|
|
348
447
|
if (input.declaredTouchedFiles !== undefined) {
|
|
349
448
|
envOptions.declaredTouchedFiles = input.declaredTouchedFiles;
|
|
350
449
|
}
|
|
450
|
+
// BAPI-1301 (D4a): the held-stdin transport is opted into per spawn. The
|
|
451
|
+
// argv form returns exactly the pre-BAPI-1301 three-key shape.
|
|
452
|
+
if (input.promptTransport === "stdin-stream-json") {
|
|
453
|
+
return {
|
|
454
|
+
executable: spec.command,
|
|
455
|
+
argv: buildClaudeArgv(input.prompt, input.modelAlias, input.mcpConfigPath, input.posture, {
|
|
456
|
+
promptTransport: "stdin-stream-json",
|
|
457
|
+
}),
|
|
458
|
+
env: buildClaudeWorkerEnv(input.parentEnv, envOptions),
|
|
459
|
+
stdinMessages: { initial: buildClaudeStdinUserMessage(input.prompt) },
|
|
460
|
+
};
|
|
461
|
+
}
|
|
351
462
|
return {
|
|
352
463
|
executable: spec.command,
|
|
353
464
|
argv: buildClaudeArgv(input.prompt, input.modelAlias, input.mcpConfigPath, input.posture),
|
|
@@ -405,7 +516,7 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
405
516
|
}
|
|
406
517
|
const options = {
|
|
407
518
|
baseBranch: input.baseBranch,
|
|
408
|
-
workerGuardHookCommand: buildWorkerGuardHookCommand(input.workerGuardHookBinPath, deps.execPath ?? process.execPath, input.worktreePath),
|
|
519
|
+
workerGuardHookCommand: buildWorkerGuardHookCommand(input.workerGuardHookBinPath, deps.execPath ?? process.execPath, input.worktreePath, input.correctionIntentVersion),
|
|
409
520
|
};
|
|
410
521
|
if (input.workerBranch !== undefined) {
|
|
411
522
|
options.workerBranch = input.workerBranch;
|
|
@@ -470,6 +581,19 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
470
581
|
}
|
|
471
582
|
},
|
|
472
583
|
};
|
|
584
|
+
/**
|
|
585
|
+
* BAPI-1301 (D4a) — declared for exactly the measured identity (Claude Code
|
|
586
|
+
* 2.1.282, macOS arm64, the lifecycle event schema this adapter parses).
|
|
587
|
+
* Declaring it is not qualifying: preflight probes the installed runtime and
|
|
588
|
+
* advertises `completion_evidence.v1` only on an exact match, so a future CLI
|
|
589
|
+
* version or another platform stays unsupported until it is measured.
|
|
590
|
+
*/
|
|
591
|
+
const completionEvidence = {
|
|
592
|
+
runtime: { ...SUPPORTED_COMPLETION_RUNTIME },
|
|
593
|
+
transport: COMPLETION_EVIDENCE_TRANSPORT,
|
|
594
|
+
probeRuntime: (probeDeps) => probeClaudeCompletionRuntime(spec.command, probeDeps),
|
|
595
|
+
buildWatcherNudge: buildClaudeWatcherNudgeMessage,
|
|
596
|
+
};
|
|
473
597
|
return {
|
|
474
598
|
identity: {
|
|
475
599
|
agentId: spec.name,
|
|
@@ -488,5 +612,6 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
488
612
|
redaction: supported(redaction),
|
|
489
613
|
lifecycle: supported(lifecycle),
|
|
490
614
|
advisoryMcpInspection: supported(advisoryMcpInspection),
|
|
615
|
+
completionEvidence: supported(completionEvidence),
|
|
491
616
|
};
|
|
492
617
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isModelTier, isValidModelAlias, resolveModelAlias } from "../agent-registry.js";
|
|
1
2
|
/** Construct a supported capability. */
|
|
2
3
|
export function supported(value) {
|
|
3
4
|
return { supported: true, value };
|
|
@@ -8,6 +9,68 @@ export function unsupported(reason, strategyId) {
|
|
|
8
9
|
? { supported: false, reason }
|
|
9
10
|
: { supported: false, reason, strategyId };
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Agent-neutral payload -> model alias rule shared by every executor adapter
|
|
14
|
+
* (BAPI-1343).
|
|
15
|
+
*
|
|
16
|
+
* A direct `model_alias` is accepted only when it passes registry alias
|
|
17
|
+
* validation AND the spec's static allowlist. Otherwise a stamped `model_tier`
|
|
18
|
+
* maps through {@link resolveModelAlias} with no repository override. Anything
|
|
19
|
+
* unusable returns null, meaning "omit the model flag". There is deliberately no
|
|
20
|
+
* job-type branch: the server decides whether to stamp a tier, and every stamped
|
|
21
|
+
* payload takes the same path.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveExecutorModelAlias(spec, payload) {
|
|
24
|
+
const p = payload && typeof payload === "object" ? payload : {};
|
|
25
|
+
const directRaw = p.model_alias;
|
|
26
|
+
if (typeof directRaw === "string" && directRaw.trim().length > 0) {
|
|
27
|
+
const direct = directRaw.trim();
|
|
28
|
+
const allowed = !spec.staticModelAliasAllowlist || spec.staticModelAliasAllowlist.includes(direct);
|
|
29
|
+
if (isValidModelAlias(direct) && allowed) {
|
|
30
|
+
return direct;
|
|
31
|
+
}
|
|
32
|
+
// An invalid/disallowed direct alias falls through to the tier path — never
|
|
33
|
+
// returned as-is.
|
|
34
|
+
}
|
|
35
|
+
const tier = isModelTier(p.model_tier) ? p.model_tier : null;
|
|
36
|
+
return resolveModelAlias(spec, tier, null);
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Completion evidence (job-specific, non-required) — BAPI-1301 (D4a)
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/** The one held-stdin transport a completion-evidence adapter can declare. */
|
|
42
|
+
export const COMPLETION_EVIDENCE_TRANSPORT = "held-stdin-stream-json";
|
|
43
|
+
/**
|
|
44
|
+
* Validate an OPTIONAL completion-evidence declaration (BAPI-1301).
|
|
45
|
+
*
|
|
46
|
+
* Returns the usable capability or a bounded reason it cannot be used. Never a
|
|
47
|
+
* refusal of the adapter: an absent, unsupported, or malformed declaration only
|
|
48
|
+
* means the executor does not advertise `completion_evidence.v1`.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveCompletionEvidenceCapability(adapter) {
|
|
51
|
+
const declared = adapter.completionEvidence;
|
|
52
|
+
if (declared === undefined || declared === null) {
|
|
53
|
+
return { supported: false, reason: "adapter declares no completion-evidence capability" };
|
|
54
|
+
}
|
|
55
|
+
if (declared.supported !== true) {
|
|
56
|
+
const reason = typeof declared.reason === "string" && declared.reason.length > 0
|
|
57
|
+
? declared.reason.slice(0, 200)
|
|
58
|
+
: "completion-evidence capability is declared unsupported";
|
|
59
|
+
return { supported: false, reason };
|
|
60
|
+
}
|
|
61
|
+
const value = declared.value;
|
|
62
|
+
const runtime = value?.runtime;
|
|
63
|
+
const wellFormedRuntime = !!runtime &&
|
|
64
|
+
["adapter", "cli_version", "platform", "event_schema"].every((key) => typeof runtime[key] === "string" && runtime[key].length > 0);
|
|
65
|
+
if (!value ||
|
|
66
|
+
!wellFormedRuntime ||
|
|
67
|
+
value.transport !== COMPLETION_EVIDENCE_TRANSPORT ||
|
|
68
|
+
typeof value.probeRuntime !== "function" ||
|
|
69
|
+
typeof value.buildWatcherNudge !== "function") {
|
|
70
|
+
return { supported: false, reason: "completion-evidence capability declaration is malformed" };
|
|
71
|
+
}
|
|
72
|
+
return { supported: true, value: value };
|
|
73
|
+
}
|
|
11
74
|
/**
|
|
12
75
|
* The capability keys an adapter MUST supply as supported.
|
|
13
76
|
*
|
|
@@ -93,10 +156,26 @@ export function validateExecutorAdapterCapabilities(adapter) {
|
|
|
93
156
|
reason: null,
|
|
94
157
|
});
|
|
95
158
|
}
|
|
159
|
+
// BAPI-1301 (D4a): the job-specific completion-evidence capability is
|
|
160
|
+
// validated ONLY when declared, and its outcome never joins `refusals` — an
|
|
161
|
+
// adapter that cannot supervise required work is still a complete adapter for
|
|
162
|
+
// every other job. The key is added only when declared, so an adapter without
|
|
163
|
+
// it validates to exactly the pre-BAPI-1301 shape.
|
|
164
|
+
const optional = {};
|
|
165
|
+
if (adapter.completionEvidence !== undefined && adapter.completionEvidence !== null) {
|
|
166
|
+
const resolved = resolveCompletionEvidenceCapability(adapter);
|
|
167
|
+
optional.optionalCapabilities = [
|
|
168
|
+
{
|
|
169
|
+
capability: "completionEvidence",
|
|
170
|
+
supported: resolved.supported,
|
|
171
|
+
reason: resolved.supported ? null : resolved.reason,
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
}
|
|
96
175
|
if (refusals.length > 0) {
|
|
97
|
-
return { ok: false, identity, refusals, capabilities };
|
|
176
|
+
return { ok: false, identity, refusals, capabilities, ...optional };
|
|
98
177
|
}
|
|
99
|
-
return { ok: true, identity, capabilities };
|
|
178
|
+
return { ok: true, identity, capabilities, ...optional };
|
|
100
179
|
}
|
|
101
180
|
/** Render a bounded, secret-free summary of why an adapter was refused. */
|
|
102
181
|
export function formatAdapterRefusal(identity, refusals) {
|