toolroll 0.9.63 → 0.9.65
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/CHANGELOG.md +51 -0
- package/README.md +15 -5
- package/dist/acp-agents.d.ts +43 -0
- package/dist/acp-agents.js +62 -0
- package/dist/acp-attest.d.ts +44 -0
- package/dist/acp-attest.js +269 -0
- package/dist/acp-check.d.ts +28 -0
- package/dist/acp-check.js +410 -0
- package/dist/acp-client.d.ts +163 -0
- package/dist/acp-client.js +738 -0
- package/dist/acp-inspect.d.ts +23 -0
- package/dist/acp-inspect.js +109 -0
- package/dist/agent-board.d.ts +19 -6
- package/dist/agent-board.js +30 -9
- package/dist/agent-fence.d.ts +50 -2
- package/dist/agent-fence.js +157 -10
- package/dist/agent-pulse.js +9 -2
- package/dist/agent-state-words.d.ts +19 -0
- package/dist/agent-state-words.js +20 -0
- package/dist/agent-state.d.ts +65 -0
- package/dist/agent-state.js +229 -0
- package/dist/agentconfig.js +36 -13
- package/dist/agents-board.d.ts +34 -2
- package/dist/agents-board.js +110 -1
- package/dist/approval-rules-ui.js +2 -2
- package/dist/assignment-delivery.d.ts +14 -0
- package/dist/assignment-delivery.js +13 -0
- package/dist/assignment.js +13 -0
- package/dist/attest.js +7 -0
- package/dist/backup-ui.js +2 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +48 -0
- package/dist/browser/terminal.css +37 -0
- package/dist/browser/terminal.js +117 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +42 -38
- package/dist/browser-shell.js +6 -3
- package/dist/browser-workspace.d.ts +235 -1
- package/dist/browser-workspace.js +60 -0
- package/dist/builder.d.ts +6 -1
- package/dist/builder.js +34 -13
- package/dist/chat-actions.d.ts +13 -1
- package/dist/chat-actions.js +197 -15
- package/dist/chat-ask.d.ts +4 -0
- package/dist/chat-ask.js +14 -0
- package/dist/chat-channel.js +20 -0
- package/dist/chat-continuity.js +5 -4
- package/dist/chat-decide.d.ts +0 -14
- package/dist/chat-decide.js +4 -1
- package/dist/chat-delivery.d.ts +9 -0
- package/dist/chat-delivery.js +45 -6
- package/dist/chat-messages.d.ts +8 -0
- package/dist/chat-messages.js +36 -4
- package/dist/check-levels-ui.js +2 -2
- package/dist/checkout-proof.d.ts +28 -0
- package/dist/checkout-proof.js +62 -0
- package/dist/claim.d.ts +12 -0
- package/dist/claim.js +19 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +4 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +21 -2
- package/dist/coding-ui.js +6 -4
- package/dist/console-instance.d.ts +124 -0
- package/dist/console-instance.js +337 -0
- package/dist/contracts/chat-actions.d.ts +16 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/chat-content.d.ts +19 -0
- package/dist/contracts/chat-content.js +6 -0
- package/dist/contracts/console-api.d.ts +12 -4
- package/dist/contracts/console-api.js +12 -4
- package/dist/contracts/flow-card.d.ts +4 -0
- package/dist/contracts/flow-card.js +3 -0
- package/dist/contracts/flow.d.ts +46 -0
- package/dist/contracts/flow.js +11 -2
- package/dist/contracts/lead-tools.d.ts +92 -0
- package/dist/contracts/lead-tools.js +13 -0
- package/dist/contracts/provider.d.ts +11 -1
- package/dist/contracts/provider.js +1 -1
- package/dist/contracts/route.d.ts +76 -1
- package/dist/contracts/route.js +1 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/control-setup.d.ts +1 -1
- package/dist/control-ui.js +2 -2
- package/dist/converse.d.ts +16 -0
- package/dist/converse.js +32 -4
- package/dist/demo-chat.js +4 -4
- package/dist/demo.js +2 -2
- package/dist/desktop-update-gate.js +6 -0
- package/dist/desktop-update.d.ts +1 -1
- package/dist/discord-chat.js +4 -1
- package/dist/doctor.js +1 -1
- package/dist/egress-bridge.d.ts +17 -0
- package/dist/egress-bridge.js +111 -0
- package/dist/egress.d.ts +119 -0
- package/dist/egress.js +423 -0
- package/dist/exec.d.ts +78 -0
- package/dist/exec.js +367 -9
- package/dist/export-ui.js +2 -2
- package/dist/flow-engine.js +24 -5
- package/dist/flow-gallery-ui.js +2 -2
- package/dist/flow-gallery.js +27 -8
- package/dist/flow-share.js +16 -5
- package/dist/flow-triggers.d.ts +77 -8
- package/dist/flow-triggers.js +517 -70
- package/dist/flows-cli.js +8 -2
- package/dist/flows-ui.d.ts +0 -2
- package/dist/flows-ui.js +52 -4
- package/dist/flows.d.ts +4 -0
- package/dist/flows.js +9 -1
- package/dist/html.d.ts +9 -0
- package/dist/html.js +11 -0
- package/dist/invoke.d.ts +17 -0
- package/dist/invoke.js +217 -21
- package/dist/keys.d.ts +18 -14
- package/dist/keys.js +5 -0
- package/dist/lead-capabilities.js +3 -2
- package/dist/lead-contract.js +2 -0
- package/dist/lead-doors.d.ts +2 -0
- package/dist/lead-doors.js +7 -0
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +58 -2
- package/dist/lead-progress.js +1 -0
- package/dist/lead-queue-runtime.d.ts +46 -0
- package/dist/lead-queue-runtime.js +191 -0
- package/dist/lead-queue.d.ts +104 -0
- package/dist/lead-queue.js +187 -0
- package/dist/lead-sweep.d.ts +67 -0
- package/dist/lead-sweep.js +99 -0
- package/dist/lead-tools.d.ts +22 -0
- package/dist/lead-tools.js +152 -1
- package/dist/lead.d.ts +13 -5
- package/dist/lead.js +89 -29
- package/dist/limits-ui.d.ts +8 -1
- package/dist/limits-ui.js +26 -0
- package/dist/live-bus.d.ts +8 -0
- package/dist/live-bus.js +40 -4
- package/dist/live-detail.d.ts +27 -0
- package/dist/live-detail.js +57 -0
- package/dist/live-notes.d.ts +20 -0
- package/dist/live-notes.js +47 -0
- package/dist/live-records.d.ts +140 -0
- package/dist/live-records.js +935 -0
- package/dist/live-terminal.d.ts +258 -0
- package/dist/live-terminal.js +649 -0
- package/dist/live.d.ts +38 -27
- package/dist/live.js +142 -124
- package/dist/mcp-oauth.d.ts +6 -3
- package/dist/mcp-oauth.js +8 -4
- package/dist/memory-pass.js +2 -2
- package/dist/mobile-viewport.d.ts +5 -1
- package/dist/mobile-viewport.js +18 -2
- package/dist/model-catalog.js +4 -0
- package/dist/monitoring-ui.js +2 -2
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +11 -4
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +275 -17
- package/dist/passwordless.d.ts +23 -0
- package/dist/passwordless.js +26 -0
- package/dist/peek-cli.d.ts +12 -4
- package/dist/peek-cli.js +19 -11
- package/dist/phase-routing.js +10 -3
- package/dist/planner-source.d.ts +3 -0
- package/dist/planner-source.js +14 -0
- package/dist/planner.d.ts +2 -1
- package/dist/planner.js +7 -1
- package/dist/policy-ui.js +2 -2
- package/dist/policy.d.ts +1 -20
- package/dist/policy.js +29 -2
- package/dist/project-delete-ui.d.ts +12 -0
- package/dist/project-delete-ui.js +41 -9
- package/dist/project-delete.js +2 -2
- package/dist/project-memory.d.ts +3 -1
- package/dist/project-memory.js +57 -25
- package/dist/project-tools.d.ts +5 -3
- package/dist/project-tools.js +12 -9
- package/dist/provider-auth.d.ts +4 -3
- package/dist/provider-auth.js +5 -4
- package/dist/provider-connection.d.ts +2 -1
- package/dist/provider-connection.js +5 -0
- package/dist/provider.d.ts +25 -2
- package/dist/provider.js +63 -9
- package/dist/pull-request-ui.js +2 -2
- package/dist/push.js +1 -1
- package/dist/quick-start.d.ts +24 -0
- package/dist/quick-start.js +127 -0
- package/dist/release.d.ts +3 -0
- package/dist/release.js +14 -8
- package/dist/request-budget-ui.js +2 -2
- package/dist/result-review.d.ts +1 -1
- package/dist/result-review.js +9 -3
- package/dist/retention-ui.js +2 -2
- package/dist/retention.js +10 -1
- package/dist/run-efficiency.d.ts +73 -0
- package/dist/run-efficiency.js +228 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.js +22 -1
- package/dist/scope.d.ts +170 -5
- package/dist/scope.js +251 -31
- package/dist/scout-net.d.ts +4 -1
- package/dist/scout-net.js +37 -12
- package/dist/scout.d.ts +20 -6
- package/dist/scout.js +27 -3
- package/dist/serve.js +247 -40
- package/dist/server/chat.d.ts +1 -1
- package/dist/server/chat.js +122 -31
- package/dist/server/chrome.d.ts +6 -1
- package/dist/server/chrome.js +18 -7
- package/dist/server/console.css +52 -38
- package/dist/server/flows.js +38 -8
- package/dist/server/guards.d.ts +1 -0
- package/dist/server/guards.js +23 -9
- package/dist/server/http.d.ts +7 -0
- package/dist/server/http.js +3 -2
- package/dist/server/live.d.ts +34 -1
- package/dist/server/live.js +39 -17
- package/dist/server/pages-assets.js +5 -0
- package/dist/server/pages.js +90 -13
- package/dist/server/people-tokens.d.ts +3 -0
- package/dist/server/people-tokens.js +55 -17
- package/dist/server/remote-hooks.js +12 -6
- package/dist/server/remote.d.ts +2 -1
- package/dist/server/remote.js +14 -5
- package/dist/server/render-chat.d.ts +12 -3
- package/dist/server/render-chat.js +47 -16
- package/dist/server/render-pages.d.ts +4 -1
- package/dist/server/render-pages.js +5 -4
- package/dist/server/render-people.d.ts +3 -2
- package/dist/server/render-people.js +3 -2
- package/dist/server/render-results.d.ts +18 -2
- package/dist/server/render-results.js +37 -28
- package/dist/server/render-settings.js +6 -5
- package/dist/server/render-tasks.d.ts +35 -10
- package/dist/server/render-tasks.js +110 -27
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +2 -1
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +46 -5
- package/dist/server/runtime.d.ts +33 -2
- package/dist/server/session.d.ts +25 -1
- package/dist/server/session.js +18 -4
- package/dist/server/settings.js +121 -10
- package/dist/server/sweeps.d.ts +4 -0
- package/dist/server/sweeps.js +58 -0
- package/dist/server/task-views.d.ts +2 -1
- package/dist/server/task-views.js +96 -6
- package/dist/server/tasks.js +320 -74
- package/dist/server/terminals.d.ts +31 -0
- package/dist/server/terminals.js +365 -0
- package/dist/setup-guide.js +3 -2
- package/dist/slack-chat.js +4 -1
- package/dist/spend-ui.js +3 -3
- package/dist/spend.js +2 -1
- package/dist/steer-capabilities.d.ts +32 -0
- package/dist/steer-capabilities.js +60 -0
- package/dist/steer-controls.d.ts +14 -0
- package/dist/steer-controls.js +36 -0
- package/dist/steer-status.js +2 -2
- package/dist/storage-ui.js +4 -4
- package/dist/store-baseline.d.ts +1 -1
- package/dist/store-baseline.js +1 -1
- package/dist/store.d.ts +430 -15
- package/dist/store.js +1041 -52
- package/dist/subagent-desk.js +1 -1
- package/dist/summary.js +3 -0
- package/dist/task-source.d.ts +58 -0
- package/dist/task-source.js +93 -0
- package/dist/team-leads.js +1 -1
- package/dist/teams-chat.js +4 -1
- package/dist/telegram-lead.d.ts +3 -0
- package/dist/telegram-lead.js +44 -7
- package/dist/telegram.d.ts +11 -2
- package/dist/telegram.js +51 -17
- package/dist/terminal-catalog.d.ts +11 -0
- package/dist/terminal-catalog.js +129 -0
- package/dist/text-limits.d.ts +2 -0
- package/dist/text-limits.js +2 -0
- package/dist/tool-broker.d.ts +2 -0
- package/dist/tool-broker.js +1 -0
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +13 -3
- package/dist/tools-ui.js +2 -2
- package/dist/webhook-limits.d.ts +23 -0
- package/dist/webhook-limits.js +30 -0
- package/dist/webhook-readers.d.ts +56 -0
- package/dist/webhook-readers.js +138 -0
- package/dist/webhook-signing.d.ts +27 -0
- package/dist/webhook-signing.js +119 -0
- package/dist/work-index.d.ts +29 -0
- package/dist/work-index.js +16 -2
- package/dist/workspace-revision.js +3 -1
- package/package.json +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.65 — 2026-10-22
|
|
4
|
+
|
|
5
|
+
0.9.64 was tagged but never published: a macOS check failed on Node 24.
|
|
6
|
+
0.9.65 is the first published release with everything listed under 0.9.64
|
|
7
|
+
below, plus:
|
|
8
|
+
|
|
9
|
+
- **The release check's upgrade test** skips a tagged release that never
|
|
10
|
+
reached npm, instead of failing to install it.
|
|
11
|
+
- **A Linux build's network bridge always fits a socket path.** When the
|
|
12
|
+
plane's own folder is too deep for one, the bridge goes in the system temp
|
|
13
|
+
folder, which a confined Linux build never sees (its `/tmp` is its own).
|
|
14
|
+
|
|
15
|
+
## 0.9.64 — 2026-10-22
|
|
16
|
+
|
|
17
|
+
**Upgrading:** builds now reach only the network their approval allows
|
|
18
|
+
(below). On a Mac, a project whose tests start a local server needs
|
|
19
|
+
**Settings → Project → Network → local services** turned on. A machine with
|
|
20
|
+
no OS sandbox (Windows, Linux without bubblewrap) no longer runs builds
|
|
21
|
+
unless an instance operator allows it (`toolroll config unconfined-agents`).
|
|
22
|
+
|
|
23
|
+
- **Builds reach only their approved network.** Each approved task seals
|
|
24
|
+
what its build may reach: the agent's provider, the package registries
|
|
25
|
+
the project uses, and hosts you add in **Settings → Project → Network**
|
|
26
|
+
(or `toolroll project network`). The OS sandbox refuses everything else,
|
|
27
|
+
name lookups included; a result's **Network** row shows what the build
|
|
28
|
+
connected to and what was refused.
|
|
29
|
+
- **Agents you can watch.** A light cockpit with each task's agent lanes,
|
|
30
|
+
the live conversation in time order, and real terminals for running
|
|
31
|
+
builds.
|
|
32
|
+
- **ACP agents.** OpenCode, Cursor, Cline, Qwen and Goose can build, each
|
|
33
|
+
checked on this machine before it takes work.
|
|
34
|
+
- **Passwordless mode.** Signing in is the only password; credential
|
|
35
|
+
changes still ask for it.
|
|
36
|
+
- **One page per task and agent**, with Activity, Result and Plan tabs and
|
|
37
|
+
one ⋯ menu.
|
|
38
|
+
- **Lead sweeps.** One card and one yes start many isolated tasks, tracked
|
|
39
|
+
in one table.
|
|
40
|
+
- **The lead queue.** Messages sent while the lead answers wait their turn.
|
|
41
|
+
- **Lead tools on demand**, with prompt caching: smaller, cheaper turns.
|
|
42
|
+
- **How it ran.** Every result shows time, turns, tokens and cache use.
|
|
43
|
+
- **Signed webhooks become tasks.**
|
|
44
|
+
- **Phone keyboard.** Chat and text boxes stay above it; Return writes a
|
|
45
|
+
new line on a phone.
|
|
46
|
+
- **A calmer UI**: one button style, theme tokens, status glyphs and a Crew
|
|
47
|
+
rail.
|
|
48
|
+
- **`toolroll release` finishes**: a release build is recorded as a commit of
|
|
49
|
+
its own holding exactly the candidate's files; the release now accepts it
|
|
50
|
+
by its files and deploys to it, instead of stopping at the check.
|
|
51
|
+
- **Release check** sizes itself to the machine; deploys keep hidden table
|
|
52
|
+
ids and find dependencies behind macOS's temp-folder link.
|
|
53
|
+
|
|
3
54
|
## 0.9.63 — 2026-10-13
|
|
4
55
|
|
|
5
56
|
- **Scheduled flows run on their own.** Sign a flow once with limits
|
package/README.md
CHANGED
|
@@ -415,7 +415,7 @@ do not loosen when you leave the room:
|
|
|
415
415
|
| guess at a judgement call | it parks a typed decision — recap, options with reversibility, recommendation, evidence — and the other eleven tasks keep going |
|
|
416
416
|
|
|
417
417
|
The whole claim is executable: one test,
|
|
418
|
-
[`src/unattended.test.ts`](src/unattended.test.ts), queues twelve tasks,
|
|
418
|
+
[`src/unattended.slow.test.ts`](src/unattended.slow.test.ts), queues twelve tasks,
|
|
419
419
|
walks away, and comes back to pull requests.
|
|
420
420
|
|
|
421
421
|
The name comes from a captain's night orders — the written standing instructions left for the officer of the watch: *proceed on this course without me, and wake me under exactly these conditions.* That is the product, and it is not about the hour: it is for **long-running work that outlasts your attention** — an afternoon of errands, a weekend, or yes, a night.
|
|
@@ -581,9 +581,19 @@ and launch tools remain available.
|
|
|
581
581
|
|
|
582
582
|
| | Auto | Full access |
|
|
583
583
|
|---|---|---|
|
|
584
|
-
| **Codex / OpenRouter** (any OS) | Codex's workspace sandbox (workspace writes, no network) with the fence | Codex's sandbox widened to write anywhere with network
|
|
585
|
-
| **Claude** | macOS: the agent runs inside a sandbox that denies the fence. Linux
|
|
586
|
-
|
|
584
|
+
| **Codex / OpenRouter** (any OS) | Codex's workspace sandbox (workspace writes, no network) with the fence | Codex's sandbox widened to write anywhere, with the fence; its commands reach the network only when the project's network is open |
|
|
585
|
+
| **Claude, Gemini, ACP agents** | macOS: the agent runs inside a Seatbelt sandbox that denies the fence. Linux: inside bubblewrap. Neither (Windows, or Linux without bubblewrap): the build doesn't start, unless an instance operator allows unconfined agents | same |
|
|
586
|
+
|
|
587
|
+
**The network.** Each approved scope says what its build may reach: the
|
|
588
|
+
agent's provider, the package registries the project uses (npm, PyPI,
|
|
589
|
+
crates.io, the Go proxy, RubyGems, Maven), and any hosts an approver added
|
|
590
|
+
with `toolroll project network --repo <p> --allow api.example.com`. Every
|
|
591
|
+
connection goes through the run's own proxy, which admits only those hosts;
|
|
592
|
+
the sandbox refuses everything else, name lookups included. On a Mac a build
|
|
593
|
+
reaches no local service but its own proxy and tools: a project whose tests
|
|
594
|
+
start a local server sets `--local any` (every local service on that Mac then
|
|
595
|
+
becomes reachable). A result's **Network** row says what the build connected
|
|
596
|
+
to and what was refused.
|
|
587
597
|
|
|
588
598
|
Each attempt records how it was fenced. Two limits to know: in **API-key
|
|
589
599
|
mode** an agent's own provider key is in its environment, so its shell can
|
|
@@ -1129,7 +1139,7 @@ doubling backoff, three-strike stalls a person exits with `task requeue` —
|
|
|
1129
1139
|
and every provider spawn is stamped before it spends, so cost is measured,
|
|
1130
1140
|
never asserted. CI on published PRs is watched as episodes that never call
|
|
1131
1141
|
silence green. The whole unattended stretch is one test,
|
|
1132
|
-
[`src/unattended.test.ts`](src/unattended.test.ts): queue twelve, walk
|
|
1142
|
+
[`src/unattended.slow.test.ts`](src/unattended.slow.test.ts): queue twelve, walk
|
|
1133
1143
|
away, come back to PRs. Architecture: [`docs/DESIGN.md`](docs/DESIGN.md);
|
|
1134
1144
|
the item-by-item ledger: [`docs/PROGRESS.md`](docs/PROGRESS.md).
|
|
1135
1145
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coding agents Toolroll runs over the Agent Client Protocol
|
|
3
|
+
* (docs/plans/acp-adapter.md). Each is its own provider id — readiness,
|
|
4
|
+
* sign-in pauses, routing and spend stay per agent — but they share one
|
|
5
|
+
* transport (acp-client.ts), and a row here is the only place an agent is
|
|
6
|
+
* described.
|
|
7
|
+
*
|
|
8
|
+
* Nothing in a row is trusted to be true of the binary on this machine: an
|
|
9
|
+
* agent runs only after its conformance check passed against that exact
|
|
10
|
+
* binary (attestation), and the check is what proves the argv, the name it
|
|
11
|
+
* gives in `initialize`, and the models it offers. Until then it is listed
|
|
12
|
+
* and refused.
|
|
13
|
+
*/
|
|
14
|
+
import type { ProviderId } from "./contracts/provider.js";
|
|
15
|
+
export declare const ACP_PROVIDER_IDS: readonly ["opencode", "cursor", "cline", "qwen", "goose"];
|
|
16
|
+
export type AcpProviderId = (typeof ACP_PROVIDER_IDS)[number];
|
|
17
|
+
/** The providers with their own adapter and key: everything that is not an ACP agent. */
|
|
18
|
+
export type KeyedProviderId = Exclude<ProviderId, AcpProviderId>;
|
|
19
|
+
export type AcpAgent = {
|
|
20
|
+
/** How people know it. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** The command looked up on PATH. */
|
|
23
|
+
binary: string;
|
|
24
|
+
/** What starts its ACP server on stdio. */
|
|
25
|
+
argv: readonly string[];
|
|
26
|
+
/** What the operator runs to sign in. */
|
|
27
|
+
signIn: string;
|
|
28
|
+
/** Folders under the home directory that hold its own configuration and sign-in (fenced like other agents' homes). */
|
|
29
|
+
home: readonly string[];
|
|
30
|
+
/** What still reaches a run beside the brief: its own configuration surfaces, in words. */
|
|
31
|
+
configSurface: readonly string[];
|
|
32
|
+
};
|
|
33
|
+
export declare const ACP_AGENTS: Readonly<Record<AcpProviderId, AcpAgent>>;
|
|
34
|
+
export declare function isAcpProvider(provider: string): provider is AcpProviderId;
|
|
35
|
+
/** Whether a profile (or anything carrying a provider) is an ACP agent's: narrows the union. */
|
|
36
|
+
export declare function isAcpProfile<P extends {
|
|
37
|
+
provider: string;
|
|
38
|
+
}>(profile: P): profile is Extract<P, {
|
|
39
|
+
provider: AcpProviderId;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function isKeyedProvider(provider: ProviderId): provider is KeyedProviderId;
|
|
42
|
+
/** Build a per-provider record whose ACP entries all come from one function of the agent's row. */
|
|
43
|
+
export declare function withAcp<K extends KeyedProviderId, V>(keyed: Record<K, V>, acp: (agent: AcpAgent, provider: AcpProviderId) => V): Record<K | AcpProviderId, V>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const ACP_PROVIDER_IDS = ["opencode", "cursor", "cline", "qwen", "goose"];
|
|
2
|
+
export const ACP_AGENTS = {
|
|
3
|
+
opencode: {
|
|
4
|
+
name: "OpenCode",
|
|
5
|
+
binary: "opencode",
|
|
6
|
+
// --pure: "run without external plugins" (opencode 1.18 `acp --help`).
|
|
7
|
+
argv: ["acp", "--pure"],
|
|
8
|
+
signIn: "opencode auth login",
|
|
9
|
+
home: [".config/opencode", ".local/share/opencode"],
|
|
10
|
+
configSurface: ["~/.config/opencode (models, providers, permissions)", "repository AGENTS.md and opencode.json", "its own sign-ins (opencode auth login)"],
|
|
11
|
+
},
|
|
12
|
+
cursor: {
|
|
13
|
+
name: "Cursor",
|
|
14
|
+
binary: "cursor-agent",
|
|
15
|
+
argv: ["acp"],
|
|
16
|
+
signIn: "cursor-agent login",
|
|
17
|
+
home: [".cursor"],
|
|
18
|
+
configSurface: ["~/.cursor (rules, MCP servers)", "repository .cursor/rules and AGENTS.md", "its own sign-in"],
|
|
19
|
+
},
|
|
20
|
+
cline: {
|
|
21
|
+
name: "Cline",
|
|
22
|
+
binary: "cline",
|
|
23
|
+
argv: ["--acp"],
|
|
24
|
+
signIn: "cline auth",
|
|
25
|
+
home: [".cline"],
|
|
26
|
+
configSurface: ["~/.cline (settings, MCP servers)", "repository .clinerules", "its own sign-in"],
|
|
27
|
+
},
|
|
28
|
+
qwen: {
|
|
29
|
+
name: "Qwen Code",
|
|
30
|
+
binary: "qwen",
|
|
31
|
+
argv: ["--acp"],
|
|
32
|
+
signIn: "qwen (then /auth)",
|
|
33
|
+
home: [".qwen"],
|
|
34
|
+
configSurface: ["~/.qwen/settings.json (MCP servers, model settings)", "QWEN.md (global and repository)", "its own sign-in"],
|
|
35
|
+
},
|
|
36
|
+
goose: {
|
|
37
|
+
name: "Goose",
|
|
38
|
+
binary: "goose",
|
|
39
|
+
argv: ["acp"],
|
|
40
|
+
signIn: "goose configure",
|
|
41
|
+
home: [".config/goose"],
|
|
42
|
+
configSurface: ["~/.config/goose (providers, extensions)", "repository .goosehints", "its own sign-in"],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
const ACP = new Set(ACP_PROVIDER_IDS);
|
|
46
|
+
export function isAcpProvider(provider) {
|
|
47
|
+
return ACP.has(provider);
|
|
48
|
+
}
|
|
49
|
+
/** Whether a profile (or anything carrying a provider) is an ACP agent's: narrows the union. */
|
|
50
|
+
export function isAcpProfile(profile) {
|
|
51
|
+
return ACP.has(profile.provider);
|
|
52
|
+
}
|
|
53
|
+
export function isKeyedProvider(provider) {
|
|
54
|
+
return !ACP.has(provider);
|
|
55
|
+
}
|
|
56
|
+
/** Build a per-provider record whose ACP entries all come from one function of the agent's row. */
|
|
57
|
+
export function withAcp(keyed, acp) {
|
|
58
|
+
const out = { ...keyed };
|
|
59
|
+
for (const provider of ACP_PROVIDER_IDS)
|
|
60
|
+
out[provider] = acp(ACP_AGENTS[provider], provider);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** What makes an interpreter load code beside the script it was given: never passed to an ACP launch. */
|
|
2
|
+
export declare const INTERPRETER_INJECTION_ENV: readonly string[];
|
|
3
|
+
/** Bumped whenever the conformance probe changes what it proves: every attestation made under another version is void. */
|
|
4
|
+
export declare const ACP_CONFORMANCE_VERSION = 2;
|
|
5
|
+
export type FileBinding = {
|
|
6
|
+
role: "launcher" | "interpreter" | "package" | "entry";
|
|
7
|
+
path: string;
|
|
8
|
+
sha256: string;
|
|
9
|
+
dev: number;
|
|
10
|
+
ino: number;
|
|
11
|
+
ctimeMs: number;
|
|
12
|
+
size: number;
|
|
13
|
+
};
|
|
14
|
+
/** How the attested binary is started, and every file that start depends on. */
|
|
15
|
+
export type AcpLaunch = {
|
|
16
|
+
/** What is executed: the launcher itself, or the pinned interpreter. */
|
|
17
|
+
file: string;
|
|
18
|
+
/** Arguments before the agent's own ACP argv: a script's shebang arguments and the script itself. */
|
|
19
|
+
prefix: string[];
|
|
20
|
+
bindings: FileBinding[];
|
|
21
|
+
};
|
|
22
|
+
/** sha256 of a regular file, read without following a final symlink. */
|
|
23
|
+
export declare function hashFile(path: string): string | null;
|
|
24
|
+
/** The interpreter and its arguments a shebang line names — `#!/abs/interp [arg]` or `#!/usr/bin/env [-S] name [args]` — or null. */
|
|
25
|
+
export declare function parseShebang(line: string, path: string): {
|
|
26
|
+
interpreter: string;
|
|
27
|
+
args: string[];
|
|
28
|
+
} | null;
|
|
29
|
+
/**
|
|
30
|
+
* How to launch a binary found at `launcher` (a realpath), with every file bound — or why it can't be. `path` is the
|
|
31
|
+
* PATH a `#!/usr/bin/env name` shebang is resolved on, once, here.
|
|
32
|
+
*/
|
|
33
|
+
export declare function launchOf(launcher: string, path: string): AcpLaunch | {
|
|
34
|
+
problem: string;
|
|
35
|
+
};
|
|
36
|
+
/** Whether every bound file is still the one that passed: same stat, or on any change the same sha256. */
|
|
37
|
+
export declare function verifyLaunch(launch: AcpLaunch): {
|
|
38
|
+
ok: true;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
problem: string;
|
|
42
|
+
};
|
|
43
|
+
/** A stored launch, read strictly: anything this code never writes reads as null. */
|
|
44
|
+
export declare function launchFromJson(json: string): AcpLaunch | null;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An ACP agent's admission (docs/plans/acp-adapter.md § Admission): it runs only on a passed conformance check of the
|
|
3
|
+
* exact binary on this runner, recorded as an attestation. Absence is denial.
|
|
4
|
+
*
|
|
5
|
+
* The record says how to launch that binary — a native launcher directly, a script through the interpreter its
|
|
6
|
+
* shebang named, resolved once at the check and pinned (the child's PATH never chooses it) — and binds every file
|
|
7
|
+
* that launch depends on: the launcher, the interpreter, and for a package its package.json and entry. Each binding
|
|
8
|
+
* keeps the file's stat tuple and sha256. At spawn, after all asynchronous setup, every binding is checked again: a
|
|
9
|
+
* changed stat is re-hashed, and the spawn refuses unless every hash still matches.
|
|
10
|
+
*
|
|
11
|
+
* What it proves, exactly: the bytes of those files are the bytes that passed the check, as of the re-check
|
|
12
|
+
* immediately before spawn. Not an exclusive lock on the paths: a writer racing the narrow window between that re-check
|
|
13
|
+
* and exec is not excluded (Node has no exec-by-descriptor), the same residual the version attestation states. Nor the
|
|
14
|
+
* agent's other files (a bundle's chunks, its plugins): those are its configuration surface. The environment
|
|
15
|
+
* variables that make an interpreter load other code are shed at every ACP launch (INTERPRETER_INJECTION_ENV).
|
|
16
|
+
*/
|
|
17
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, realpathSync, statSync } from "node:fs";
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { delimiter, dirname, isAbsolute, join } from "node:path";
|
|
20
|
+
/** What makes an interpreter load code beside the script it was given: never passed to an ACP launch. */
|
|
21
|
+
export const INTERPRETER_INJECTION_ENV = [
|
|
22
|
+
"NODE_OPTIONS", "NODE_PATH", "NODE_REPL_EXTERNAL_MODULE", "PYTHONPATH", "PYTHONSTARTUP", "PYTHONHOME", "PYTHONINSPECT",
|
|
23
|
+
"RUBYOPT", "RUBYLIB", "PERL5OPT", "PERL5LIB", "PERLLIB", "BUN_OPTIONS", "DENO_DIR", "LD_PRELOAD", "LD_LIBRARY_PATH",
|
|
24
|
+
"DYLD_INSERT_LIBRARIES", "DYLD_LIBRARY_PATH", "DYLD_FRAMEWORK_PATH",
|
|
25
|
+
];
|
|
26
|
+
/** Bumped whenever the conformance probe changes what it proves: every attestation made under another version is void. */
|
|
27
|
+
export const ACP_CONFORMANCE_VERSION = 2;
|
|
28
|
+
const READ_CAP = 64 * 1024;
|
|
29
|
+
const HASH_CHUNK = 1024 * 1024;
|
|
30
|
+
/** sha256 of a regular file, read without following a final symlink. */
|
|
31
|
+
export function hashFile(path) {
|
|
32
|
+
let fd = null;
|
|
33
|
+
try {
|
|
34
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | (constants.O_NONBLOCK ?? 0));
|
|
35
|
+
if (!fstatSync(fd).isFile())
|
|
36
|
+
return null;
|
|
37
|
+
const hash = createHash("sha256");
|
|
38
|
+
const buffer = Buffer.alloc(HASH_CHUNK);
|
|
39
|
+
for (;;) {
|
|
40
|
+
const read = readSync(fd, buffer, 0, HASH_CHUNK, null);
|
|
41
|
+
if (read === 0)
|
|
42
|
+
break;
|
|
43
|
+
hash.update(buffer.subarray(0, read));
|
|
44
|
+
}
|
|
45
|
+
return hash.digest("hex");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
if (fd !== null)
|
|
52
|
+
closeSync(fd);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function head(path, cap) {
|
|
56
|
+
let fd = null;
|
|
57
|
+
try {
|
|
58
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | (constants.O_NONBLOCK ?? 0));
|
|
59
|
+
if (!fstatSync(fd).isFile())
|
|
60
|
+
return null;
|
|
61
|
+
const buffer = Buffer.alloc(cap);
|
|
62
|
+
return buffer.subarray(0, readSync(fd, buffer, 0, cap, 0));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (fd !== null)
|
|
69
|
+
closeSync(fd);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function bind(role, path) {
|
|
73
|
+
try {
|
|
74
|
+
const stat = statSync(path);
|
|
75
|
+
if (!stat.isFile())
|
|
76
|
+
return null;
|
|
77
|
+
const sha256 = hashFile(path);
|
|
78
|
+
if (sha256 === null)
|
|
79
|
+
return null;
|
|
80
|
+
return { role, path, sha256, dev: stat.dev, ino: stat.ino, ctimeMs: stat.ctimeMs, size: stat.size };
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function isNative(bytes) {
|
|
87
|
+
if (bytes.length < 4)
|
|
88
|
+
return false;
|
|
89
|
+
const magic = bytes.readUInt32BE(0);
|
|
90
|
+
return magic === 0x7f454c46 || [0xfeedface, 0xfeedfacf, 0xcefaedfe, 0xcffaedfe, 0xcafebabe, 0xbebafeca].includes(magic);
|
|
91
|
+
}
|
|
92
|
+
/** A name found on a PATH, by realpath; only absolute PATH entries count. */
|
|
93
|
+
function onPath(name, path) {
|
|
94
|
+
for (const folder of path.split(delimiter)) {
|
|
95
|
+
if (folder === "" || !isAbsolute(folder))
|
|
96
|
+
continue;
|
|
97
|
+
try {
|
|
98
|
+
const real = realpathSync(join(folder, name));
|
|
99
|
+
const stat = statSync(real);
|
|
100
|
+
if (stat.isFile() && (stat.mode & 0o111) !== 0)
|
|
101
|
+
return real;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/** The interpreter and its arguments a shebang line names — `#!/abs/interp [arg]` or `#!/usr/bin/env [-S] name [args]` — or null. */
|
|
110
|
+
export function parseShebang(line, path) {
|
|
111
|
+
if (!line.startsWith("#!"))
|
|
112
|
+
return null;
|
|
113
|
+
const words = line.slice(2).trim().split(/\s+/).filter(one => one !== "");
|
|
114
|
+
const [first, ...rest] = words;
|
|
115
|
+
if (first === undefined || !isAbsolute(first))
|
|
116
|
+
return null;
|
|
117
|
+
if (first === "/usr/bin/env" || first === "/bin/env") {
|
|
118
|
+
const named = rest[0] === "-S" ? rest.slice(1) : rest;
|
|
119
|
+
const [name, ...args] = named;
|
|
120
|
+
if (name === undefined || name.includes("/") || name.startsWith("-") || name.includes("="))
|
|
121
|
+
return null;
|
|
122
|
+
const interpreter = onPath(name, path);
|
|
123
|
+
return interpreter === null ? null : { interpreter, args };
|
|
124
|
+
}
|
|
125
|
+
if (rest.length > 1)
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
return { interpreter: realpathSync(first), args: rest };
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** The nearest package.json above a script and the file its `bin`/`main` names, when that is not the launcher. */
|
|
135
|
+
function packageBindings(launcher) {
|
|
136
|
+
let folder = dirname(launcher);
|
|
137
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
138
|
+
const file = join(folder, "package.json");
|
|
139
|
+
const bytes = head(file, READ_CAP);
|
|
140
|
+
if (bytes !== null) {
|
|
141
|
+
let parsed;
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const pkg = bind("package", file);
|
|
149
|
+
if (pkg === null)
|
|
150
|
+
return null;
|
|
151
|
+
const bins = typeof parsed["bin"] === "string" ? [parsed["bin"]] : parsed["bin"] !== null && typeof parsed["bin"] === "object" ? Object.values(parsed["bin"]) : [];
|
|
152
|
+
const entries = [...bins, parsed["main"]].filter((one) => typeof one === "string");
|
|
153
|
+
const out = [pkg];
|
|
154
|
+
for (const entry of new Set(entries)) {
|
|
155
|
+
let real;
|
|
156
|
+
try {
|
|
157
|
+
real = realpathSync(join(folder, entry));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (real === launcher)
|
|
163
|
+
continue;
|
|
164
|
+
const bound = bind("entry", real);
|
|
165
|
+
if (bound !== null)
|
|
166
|
+
out.push(bound);
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
const up = dirname(folder);
|
|
171
|
+
if (up === folder)
|
|
172
|
+
break;
|
|
173
|
+
folder = up;
|
|
174
|
+
}
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* How to launch a binary found at `launcher` (a realpath), with every file bound — or why it can't be. `path` is the
|
|
179
|
+
* PATH a `#!/usr/bin/env name` shebang is resolved on, once, here.
|
|
180
|
+
*/
|
|
181
|
+
export function launchOf(launcher, path) {
|
|
182
|
+
const bytes = head(launcher, 4096);
|
|
183
|
+
if (bytes === null)
|
|
184
|
+
return { problem: `${launcher} can't be read` };
|
|
185
|
+
const self = bind("launcher", launcher);
|
|
186
|
+
if (self === null)
|
|
187
|
+
return { problem: `${launcher} can't be read` };
|
|
188
|
+
if (isNative(bytes))
|
|
189
|
+
return { file: launcher, prefix: [], bindings: [self] };
|
|
190
|
+
const first = bytes.toString("utf8").split("\n")[0] ?? "";
|
|
191
|
+
const shebang = parseShebang(first, path);
|
|
192
|
+
if (shebang === null)
|
|
193
|
+
return { problem: `${launcher} is neither a native program nor a script whose interpreter can be pinned (unsupported-launcher)` };
|
|
194
|
+
const interpreterBytes = head(shebang.interpreter, 4096);
|
|
195
|
+
if (interpreterBytes === null || !isNative(interpreterBytes))
|
|
196
|
+
return { problem: `${shebang.interpreter}, which runs ${launcher}, is not a native program (unsupported-launcher)` };
|
|
197
|
+
const interpreter = bind("interpreter", shebang.interpreter);
|
|
198
|
+
const pkg = packageBindings(launcher);
|
|
199
|
+
if (interpreter === null || pkg === null)
|
|
200
|
+
return { problem: `the files ${launcher} runs from can't all be read` };
|
|
201
|
+
return { file: shebang.interpreter, prefix: [...shebang.args, launcher], bindings: [self, interpreter, ...pkg] };
|
|
202
|
+
}
|
|
203
|
+
/** Whether every bound file is still the one that passed: same stat, or on any change the same sha256. */
|
|
204
|
+
export function verifyLaunch(launch) {
|
|
205
|
+
for (const binding of launch.bindings) {
|
|
206
|
+
let stat;
|
|
207
|
+
try {
|
|
208
|
+
if (lstatSync(binding.path).isSymbolicLink())
|
|
209
|
+
return { ok: false, problem: `${binding.path} became a link since the check` };
|
|
210
|
+
stat = statSync(binding.path);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return { ok: false, problem: `${binding.path} is gone since the check` };
|
|
214
|
+
}
|
|
215
|
+
if (stat.dev === binding.dev && stat.ino === binding.ino && stat.ctimeMs === binding.ctimeMs && stat.size === binding.size)
|
|
216
|
+
continue;
|
|
217
|
+
if (hashFile(binding.path) !== binding.sha256)
|
|
218
|
+
return { ok: false, problem: `${binding.path} changed since the check — check the agent again` };
|
|
219
|
+
}
|
|
220
|
+
return { ok: true };
|
|
221
|
+
}
|
|
222
|
+
/** A stored launch, read strictly: anything this code never writes reads as null. */
|
|
223
|
+
export function launchFromJson(json) {
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = JSON.parse(json);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
232
|
+
return null;
|
|
233
|
+
const value = parsed;
|
|
234
|
+
if (typeof value["file"] !== "string" || !isAbsolute(value["file"]))
|
|
235
|
+
return null;
|
|
236
|
+
if (!Array.isArray(value["prefix"]) || !value["prefix"].every(one => typeof one === "string"))
|
|
237
|
+
return null;
|
|
238
|
+
if (!Array.isArray(value["bindings"]) || value["bindings"].length === 0)
|
|
239
|
+
return null;
|
|
240
|
+
const bindings = [];
|
|
241
|
+
for (const one of value["bindings"]) {
|
|
242
|
+
if (one === null || typeof one !== "object")
|
|
243
|
+
return null;
|
|
244
|
+
const b = one;
|
|
245
|
+
if (!["launcher", "interpreter", "package", "entry"].includes(String(b["role"])))
|
|
246
|
+
return null;
|
|
247
|
+
if (typeof b["path"] !== "string" || !isAbsolute(b["path"]) || typeof b["sha256"] !== "string" || !/^[0-9a-f]{64}$/.test(b["sha256"]))
|
|
248
|
+
return null;
|
|
249
|
+
if (![b["dev"], b["ino"], b["ctimeMs"], b["size"]].every(n => typeof n === "number" && Number.isFinite(n)))
|
|
250
|
+
return null;
|
|
251
|
+
bindings.push({ role: b["role"], path: b["path"], sha256: b["sha256"], dev: b["dev"], ino: b["ino"], ctimeMs: b["ctimeMs"], size: b["size"] });
|
|
252
|
+
}
|
|
253
|
+
if (bindings[0]?.role !== "launcher")
|
|
254
|
+
return null;
|
|
255
|
+
// What is executed is a bound file — the launcher itself, or the bound interpreter with the launcher as its script
|
|
256
|
+
// — and nothing a stored row could point elsewhere.
|
|
257
|
+
const launcher = bindings[0].path;
|
|
258
|
+
const prefix = value["prefix"];
|
|
259
|
+
if (value["file"] === launcher) {
|
|
260
|
+
if (prefix.length !== 0)
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
const interpreter = bindings.find(one => one.role === "interpreter");
|
|
265
|
+
if (interpreter === undefined || interpreter.path !== value["file"] || prefix[prefix.length - 1] !== launcher)
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return { file: value["file"], prefix, bindings };
|
|
269
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ProviderRunner } from "./provider.js";
|
|
2
|
+
import { type Store } from "./store.js";
|
|
3
|
+
import type { WorktreePool } from "./worktree.js";
|
|
4
|
+
export declare const CHECK_PROMPTS: {
|
|
5
|
+
readonly ok: "Create a file named done.txt in the current folder containing exactly the word ok. Then stop. Do nothing else.";
|
|
6
|
+
readonly stop: "Run the shell command `sleep 120` and wait for it to finish.";
|
|
7
|
+
};
|
|
8
|
+
export type AcpCheckJob = {
|
|
9
|
+
store: Store;
|
|
10
|
+
runner: string;
|
|
11
|
+
token: string;
|
|
12
|
+
/** The watch running it: its project and its lease's owner. */
|
|
13
|
+
watch: {
|
|
14
|
+
repo: string;
|
|
15
|
+
owner: string;
|
|
16
|
+
};
|
|
17
|
+
pool: WorktreePool;
|
|
18
|
+
clock?: () => Date;
|
|
19
|
+
keyHome?: string;
|
|
20
|
+
/** Tests only: a stand-in agent runner. */
|
|
21
|
+
agent?: ProviderRunner;
|
|
22
|
+
/** Tests only: a PATH to find the agent on. */
|
|
23
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
24
|
+
progress?: (line: string) => void;
|
|
25
|
+
};
|
|
26
|
+
/** Close what no live watch holds any more, let go of what finished checks still hold, then run this runner's next
|
|
27
|
+
* check, if one is waiting. */
|
|
28
|
+
export declare function runAcpChecks(job: AcpCheckJob): Promise<void>;
|