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/dist/egress.js
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a build may reach on the network (docs/plans/agent-baseline-hardening.md, arc 1).
|
|
3
|
+
*
|
|
4
|
+
* A run's network is part of its sealed execution profile: the hosts it may reach are resolved once, when the scope
|
|
5
|
+
* is written (the provider's own hosts, the package registries the project's own checkout names,
|
|
6
|
+
* and the project's added hosts), and that snapshot is what the approval binds and the launch enforces. Nothing at
|
|
7
|
+
* launch re-derives it from settings changed since or from files the agent wrote.
|
|
8
|
+
*
|
|
9
|
+
* The launch enforces it at the operating system (agent-fence.ts: Seatbelt's network rules on macOS, a network
|
|
10
|
+
* namespace on Linux) and reaches the world through one proxy of its own (startEgressProxy), which admits only the
|
|
11
|
+
* snapshot's hosts. The proxy never decrypts TLS: it restricts where a build connects, not what it sends there.
|
|
12
|
+
*/
|
|
13
|
+
import { createServer, request as httpRequest } from "node:http";
|
|
14
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
15
|
+
import { isIP } from "node:net";
|
|
16
|
+
import { domainToASCII } from "node:url";
|
|
17
|
+
import { readdirSync } from "node:fs";
|
|
18
|
+
import { basename, join } from "node:path";
|
|
19
|
+
import { connectFirst, isPublicAddress } from "./scout-net.js";
|
|
20
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
21
|
+
/** The policy's shape version: a worker enforces the versions it knows and refuses any other. */
|
|
22
|
+
export const NETWORK_POLICY_VERSION = 1;
|
|
23
|
+
export const PROJECT_NETWORK_DEFAULT = { mode: "allowlist", hosts: [], loopback: "proxy-only" };
|
|
24
|
+
/** At most this many hosts a project adds (a list longer than this is a different policy: open). */
|
|
25
|
+
export const MAX_NETWORK_HOSTS = 200;
|
|
26
|
+
/** At most this many in a sealed snapshot: the project's, plus its provider's and every registry's. */
|
|
27
|
+
export const MAX_SNAPSHOT_HOSTS = MAX_NETWORK_HOSTS + 100;
|
|
28
|
+
/**
|
|
29
|
+
* A host as a policy names it: lowercase ASCII (an international name in its punycode form), no trailing dot, at
|
|
30
|
+
* least two labels, each 1–63 letters, digits or hyphens; or `*.` and such a name, for every name under it. Never an
|
|
31
|
+
* IP address, a port, a path or a bare top-level name. Null when it is none of these.
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeHost(input) {
|
|
34
|
+
const trimmed = input.trim().toLowerCase().replace(/\.$/, "");
|
|
35
|
+
const wildcard = trimmed.startsWith("*.");
|
|
36
|
+
const bare = wildcard ? trimmed.slice(2) : trimmed;
|
|
37
|
+
if (bare === "" || bare.length > 253 || isIP(bare.replace(/^\[|\]$/g, "")) !== 0)
|
|
38
|
+
return null;
|
|
39
|
+
// Nothing of a URL's other parts: the converter below would quietly cut a name at a slash or a colon.
|
|
40
|
+
if (/[\s/\\:@?#%[\]*,;]/.test(bare))
|
|
41
|
+
return null;
|
|
42
|
+
const ascii = domainToASCII(bare);
|
|
43
|
+
if (ascii === "" || ascii !== ascii.toLowerCase())
|
|
44
|
+
return null;
|
|
45
|
+
const labels = ascii.split(".");
|
|
46
|
+
if (labels.length < 2)
|
|
47
|
+
return null;
|
|
48
|
+
if (!labels.every(label => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)))
|
|
49
|
+
return null;
|
|
50
|
+
// A name whose last label is all digits reads as an address to too many resolvers.
|
|
51
|
+
if (/^\d+$/.test(labels.at(-1)))
|
|
52
|
+
return null;
|
|
53
|
+
return wildcard ? `*.${ascii}` : ascii;
|
|
54
|
+
}
|
|
55
|
+
/** Whether `host` (as a client named it) is one a policy's list admits: an exact name, or a name under a `*.` entry. */
|
|
56
|
+
export function hostAllowed(host, hosts) {
|
|
57
|
+
const name = normalizeHost(host);
|
|
58
|
+
if (name === null || name.startsWith("*."))
|
|
59
|
+
return false;
|
|
60
|
+
return hosts.some(entry => entry === name || (entry.startsWith("*.") && name.endsWith(entry.slice(1))));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Each provider's own hosts (catalog version 1): its model API and the sign-in refresh its CLI needs. Codex starts
|
|
64
|
+
* its harness outside its command sandbox, so its hosts matter only to what its commands may reach. An ACP agent
|
|
65
|
+
* talks to whatever model service it was set up with; its row names the hosts its own service uses.
|
|
66
|
+
*/
|
|
67
|
+
export const PROVIDER_HOSTS = {
|
|
68
|
+
claude: ["api.anthropic.com", "console.anthropic.com", "platform.claude.com"],
|
|
69
|
+
codex: ["api.openai.com", "chatgpt.com", "auth.openai.com"],
|
|
70
|
+
openrouter: ["openrouter.ai"],
|
|
71
|
+
gemini: ["generativelanguage.googleapis.com", "cloudcode-pa.googleapis.com", "oauth2.googleapis.com"],
|
|
72
|
+
opencode: ["opencode.ai", "*.opencode.ai", "models.dev"],
|
|
73
|
+
cursor: ["*.cursor.sh", "*.cursor.com"],
|
|
74
|
+
cline: ["api.cline.bot"],
|
|
75
|
+
qwen: ["chat.qwen.ai", "dashscope.aliyuncs.com", "dashscope-intl.aliyuncs.com"],
|
|
76
|
+
// Goose talks to whatever model service it is set up with: a project using it adds that service's host.
|
|
77
|
+
goose: [],
|
|
78
|
+
};
|
|
79
|
+
export const REGISTRY_HOSTS = {
|
|
80
|
+
npm: ["registry.npmjs.org", "registry.yarnpkg.com", "repo.yarnpkg.com", "nodejs.org"],
|
|
81
|
+
python: ["pypi.org", "files.pythonhosted.org"],
|
|
82
|
+
rust: ["crates.io", "index.crates.io", "static.crates.io"],
|
|
83
|
+
go: ["proxy.golang.org", "sum.golang.org"],
|
|
84
|
+
ruby: ["rubygems.org", "index.rubygems.org"],
|
|
85
|
+
jvm: ["repo.maven.apache.org", "repo1.maven.org", "plugins.gradle.org", "services.gradle.org", "downloads.gradle.org"],
|
|
86
|
+
};
|
|
87
|
+
/** The files that name each ecosystem, by file name wherever they are in the tree. */
|
|
88
|
+
const ECOSYSTEM_FILES = {
|
|
89
|
+
"package.json": "npm", "package-lock.json": "npm", "npm-shrinkwrap.json": "npm", "yarn.lock": "npm", "pnpm-lock.yaml": "npm", "bun.lock": "npm", "bun.lockb": "npm",
|
|
90
|
+
"pyproject.toml": "python", "requirements.txt": "python", "pipfile": "python", "pipfile.lock": "python", "poetry.lock": "python", "uv.lock": "python", "setup.py": "python", "setup.cfg": "python",
|
|
91
|
+
"cargo.toml": "rust", "cargo.lock": "rust",
|
|
92
|
+
"go.mod": "go", "go.sum": "go",
|
|
93
|
+
"gemfile": "ruby", "gemfile.lock": "ruby",
|
|
94
|
+
"pom.xml": "jvm", "build.gradle": "jvm", "build.gradle.kts": "jvm", "settings.gradle": "jvm", "settings.gradle.kts": "jvm",
|
|
95
|
+
};
|
|
96
|
+
/** The ecosystems a list of repository paths names (requirements-*.txt counts as Python). */
|
|
97
|
+
export function ecosystemsOf(paths) {
|
|
98
|
+
const found = new Set();
|
|
99
|
+
for (const path of paths) {
|
|
100
|
+
const name = basename(path).toLowerCase();
|
|
101
|
+
const one = ECOSYSTEM_FILES[name] ?? (/^requirements[\w.-]*\.txt$/.test(name) ? "python" : undefined);
|
|
102
|
+
if (one !== undefined)
|
|
103
|
+
found.add(one);
|
|
104
|
+
}
|
|
105
|
+
return [...found].sort();
|
|
106
|
+
}
|
|
107
|
+
/** How deep and how wide the walk for ecosystem files goes (a monorepo names its ecosystems near its root). */
|
|
108
|
+
const WALK_DEPTH = 3, WALK_LIMIT = 5_000;
|
|
109
|
+
const WALK_SKIP = new Set(["node_modules", "vendor", "target", "dist", "build", "venv", "__pycache__"]);
|
|
110
|
+
/**
|
|
111
|
+
* The ecosystems the project's own checkout names: its files a few folders deep, read straight from the folder the
|
|
112
|
+
* plane registered (which no build can write: agent-fence.ts taskBoundary), never a build's worktree. No process is
|
|
113
|
+
* started and nothing in the repository runs. An unreadable folder names none: its builds reach the provider and the
|
|
114
|
+
* project's own hosts only.
|
|
115
|
+
*/
|
|
116
|
+
export function repositoryEcosystems(repo) {
|
|
117
|
+
const paths = [];
|
|
118
|
+
const walk = (folder, depth) => {
|
|
119
|
+
let entries;
|
|
120
|
+
try {
|
|
121
|
+
entries = readdirSync(folder, { withFileTypes: true });
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (paths.length >= WALK_LIMIT)
|
|
128
|
+
return;
|
|
129
|
+
if (entry.isFile())
|
|
130
|
+
paths.push(join(folder, entry.name));
|
|
131
|
+
else if (entry.isDirectory() && depth < WALK_DEPTH && !entry.name.startsWith(".") && !WALK_SKIP.has(entry.name))
|
|
132
|
+
walk(join(folder, entry.name), depth + 1);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
walk(repo, 0);
|
|
136
|
+
return ecosystemsOf(paths);
|
|
137
|
+
}
|
|
138
|
+
/** The network a new scope is written with: the project's setting, its provider's hosts and its ecosystems' registries. */
|
|
139
|
+
export function resolveNetwork(provider, project, ecosystems) {
|
|
140
|
+
if (project.mode === "open")
|
|
141
|
+
return { version: NETWORK_POLICY_VERSION, mode: "open", hosts: [], loopback: project.loopback };
|
|
142
|
+
const hosts = new Set([
|
|
143
|
+
...(PROVIDER_HOSTS[provider] ?? []),
|
|
144
|
+
...ecosystems.flatMap(one => REGISTRY_HOSTS[one]),
|
|
145
|
+
...project.hosts,
|
|
146
|
+
]);
|
|
147
|
+
return { version: NETWORK_POLICY_VERSION, mode: "allowlist", hosts: [...hosts].sort(), loopback: project.loopback };
|
|
148
|
+
}
|
|
149
|
+
/** The network of a launch nothing sealed one for (a planner, a check): its provider's own hosts and nothing else. */
|
|
150
|
+
export function providerOnlyNetwork(provider) {
|
|
151
|
+
return { version: NETWORK_POLICY_VERSION, mode: "allowlist", hosts: [...new Set(PROVIDER_HOSTS[provider] ?? [])].sort(), loopback: "proxy-only" };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The network an approval sealed before network policies existed runs under: a deliberate narrowing (provider hosts
|
|
155
|
+
* and the repository's registries, nothing the project added since, local services of this Mac out of reach). Never
|
|
156
|
+
* widened by a setting changed after it was approved.
|
|
157
|
+
*/
|
|
158
|
+
export function legacyNetwork(provider, ecosystems) {
|
|
159
|
+
return { ...resolveNetwork(provider, { mode: "allowlist", hosts: [], loopback: "proxy-only" }, ecosystems) };
|
|
160
|
+
}
|
|
161
|
+
/** Strict re-hydration (scope.ts profileFromJson): exactly the keys this code writes, every host normalized, or null. */
|
|
162
|
+
export function networkFromValue(value) {
|
|
163
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
164
|
+
return null;
|
|
165
|
+
const v = value;
|
|
166
|
+
const keys = Object.keys(v);
|
|
167
|
+
if (keys.length !== 4 || !["version", "mode", "hosts", "loopback"].every(key => keys.includes(key)))
|
|
168
|
+
return null;
|
|
169
|
+
if (v["version"] !== NETWORK_POLICY_VERSION)
|
|
170
|
+
return null;
|
|
171
|
+
if (v["mode"] !== "allowlist" && v["mode"] !== "open")
|
|
172
|
+
return null;
|
|
173
|
+
if (v["loopback"] !== "any" && v["loopback"] !== "proxy-only")
|
|
174
|
+
return null;
|
|
175
|
+
if (!Array.isArray(v["hosts"]) || v["hosts"].length > MAX_SNAPSHOT_HOSTS)
|
|
176
|
+
return null;
|
|
177
|
+
const hosts = [];
|
|
178
|
+
for (const one of v["hosts"]) {
|
|
179
|
+
if (typeof one !== "string" || normalizeHost(one) !== one)
|
|
180
|
+
return null;
|
|
181
|
+
hosts.push(one);
|
|
182
|
+
}
|
|
183
|
+
if (v["mode"] === "open" && hosts.length > 0)
|
|
184
|
+
return null;
|
|
185
|
+
if (new Set(hosts).size !== hosts.length || hosts.some((one, index) => index > 0 && hosts[index - 1] >= one))
|
|
186
|
+
return null;
|
|
187
|
+
return { version: NETWORK_POLICY_VERSION, mode: v["mode"], hosts, loopback: v["loopback"] };
|
|
188
|
+
}
|
|
189
|
+
/** A project setting as saved (store.ts project_network), or null when it isn't one this code wrote. */
|
|
190
|
+
export function projectNetworkFromValue(value) {
|
|
191
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
192
|
+
return null;
|
|
193
|
+
const v = value;
|
|
194
|
+
if (Object.keys(v).length !== 3 || (v["mode"] !== "allowlist" && v["mode"] !== "open") || (v["loopback"] !== "any" && v["loopback"] !== "proxy-only"))
|
|
195
|
+
return null;
|
|
196
|
+
if (!Array.isArray(v["hosts"]) || v["hosts"].length > MAX_NETWORK_HOSTS)
|
|
197
|
+
return null;
|
|
198
|
+
const hosts = v["hosts"].map(one => typeof one === "string" ? normalizeHost(one) : null);
|
|
199
|
+
if (hosts.some(one => one === null))
|
|
200
|
+
return null;
|
|
201
|
+
return { mode: v["mode"], hosts: [...new Set(hosts)].sort(), loopback: v["loopback"] };
|
|
202
|
+
}
|
|
203
|
+
/** The network in plain words, for an approval card and a run's record. */
|
|
204
|
+
export function networkWords(network) {
|
|
205
|
+
if (network === null)
|
|
206
|
+
return "Network: approved before network rules (provider and package registries only)";
|
|
207
|
+
if (network.mode === "open")
|
|
208
|
+
return `Network: open — any host${network.loopback === "proxy-only" ? "" : ", and every local service on this Mac"}`;
|
|
209
|
+
const registries = Object.entries(REGISTRY_HOSTS)
|
|
210
|
+
.filter(([, hosts]) => hosts.every(one => network.hosts.includes(one))).map(([name]) => REGISTRY_NAMES[name]);
|
|
211
|
+
const known = new Set([...Object.values(REGISTRY_HOSTS).flat(), ...Object.values(PROVIDER_HOSTS).flat()]);
|
|
212
|
+
const added = network.hosts.filter(one => !known.has(one));
|
|
213
|
+
return `Network: the agent's provider${registries.length === 0 ? "" : `, ${registries.join(", ")}`}${added.length === 0 ? "" : `, and ${added.join(", ")}`}`
|
|
214
|
+
+ (network.loopback === "any" ? "; and every local service on this Mac" : "");
|
|
215
|
+
}
|
|
216
|
+
const REGISTRY_NAMES = { npm: "npm", python: "PyPI", rust: "crates.io", go: "the Go proxy", ruby: "RubyGems", jvm: "Maven and Gradle" };
|
|
217
|
+
const HOST_LIMIT = 50;
|
|
218
|
+
/** A stored record read back strictly: anything this code didn't write reads as none. */
|
|
219
|
+
export function runNetworkFromValue(value) {
|
|
220
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
221
|
+
return null;
|
|
222
|
+
const v = value;
|
|
223
|
+
if ((v["mode"] !== "open" && v["mode"] !== "allowlist") || v["phase"] !== "agent")
|
|
224
|
+
return null;
|
|
225
|
+
const count = (n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
|
|
226
|
+
const out = { mode: v["mode"], phase: "agent" };
|
|
227
|
+
if (v["via"] === true)
|
|
228
|
+
out.via = true;
|
|
229
|
+
if (v["hosts"] !== undefined) {
|
|
230
|
+
if (!count(v["hosts"]))
|
|
231
|
+
return null;
|
|
232
|
+
out.hosts = v["hosts"];
|
|
233
|
+
}
|
|
234
|
+
if (v["loopback"] !== undefined) {
|
|
235
|
+
if (v["loopback"] !== "any" && v["loopback"] !== "proxy-only")
|
|
236
|
+
return null;
|
|
237
|
+
out.loopback = v["loopback"];
|
|
238
|
+
}
|
|
239
|
+
if (v["counts"] !== undefined) {
|
|
240
|
+
const c = v["counts"];
|
|
241
|
+
if (c === null || typeof c !== "object" || !count(c["connections"]) || !count(c["refused"]) || !count(c["otherHosts"]) || !Array.isArray(c["hosts"]) || c["hosts"].length > HOST_LIMIT)
|
|
242
|
+
return null;
|
|
243
|
+
const hosts = [];
|
|
244
|
+
for (const one of c["hosts"]) {
|
|
245
|
+
const h = one;
|
|
246
|
+
if (h === null || typeof h !== "object" || typeof h["host"] !== "string" || h["host"].length > 300 || !count(h["connections"]) || !count(h["refused"]))
|
|
247
|
+
return null;
|
|
248
|
+
hosts.push({ host: h["host"], connections: h["connections"], refused: h["refused"] });
|
|
249
|
+
}
|
|
250
|
+
out.counts = { connections: c["connections"], refused: c["refused"], hosts, otherHosts: c["otherHosts"] };
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
/** Every proxy setting a program might read, both cases, so none falls back to a direct connection the OS refuses. */
|
|
255
|
+
export function proxyEnv(proxy, loopback) {
|
|
256
|
+
const local = loopback === "proxy-only" ? "" : "localhost,127.0.0.1,::1";
|
|
257
|
+
return {
|
|
258
|
+
HTTPS_PROXY: proxy.url, https_proxy: proxy.url, HTTP_PROXY: proxy.url, http_proxy: proxy.url, ALL_PROXY: proxy.url, all_proxy: proxy.url,
|
|
259
|
+
NO_PROXY: local, no_proxy: local,
|
|
260
|
+
// Node's own fetch reads the proxy variables only when told to (Node 24 and later; earlier versions ignore it).
|
|
261
|
+
NODE_USE_ENV_PROXY: "1",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Start one run's proxy on loopback. Only a client presenting this run's credentials is served (another run's agent
|
|
266
|
+
* on the same machine can't borrow it). CONNECT goes only to port 443 and plain HTTP only to port 80, only to a host
|
|
267
|
+
* the run's list admits (matched by name before it is resolved), only to public addresses (the proxy resolves the
|
|
268
|
+
* name itself and connects to exactly the addresses it checked). A plain HTTP request's Host must be the URL's host.
|
|
269
|
+
*/
|
|
270
|
+
export async function startEgressProxy(options) {
|
|
271
|
+
const addressAllowed = options.addressAllowed ?? isPublicAddress;
|
|
272
|
+
const upstreamPort = options.upstreamPort ?? ((port) => port);
|
|
273
|
+
const lookup = options.lookup ?? (async (host) => (await dnsLookup(host, { all: true, verbatim: true })).map(one => one.address));
|
|
274
|
+
const secret = randomBytes(24).toString("base64url");
|
|
275
|
+
const expected = Buffer.from(`Basic ${Buffer.from(`run:${secret}`).toString("base64")}`);
|
|
276
|
+
const sockets = new Set();
|
|
277
|
+
const byHost = new Map();
|
|
278
|
+
let connections = 0, refused = 0, otherHosts = 0;
|
|
279
|
+
const count = (host, allowed, why) => {
|
|
280
|
+
if (allowed)
|
|
281
|
+
connections++;
|
|
282
|
+
else
|
|
283
|
+
refused++;
|
|
284
|
+
const key = normalizeHost(host) ?? "(not a host name)";
|
|
285
|
+
let entry = byHost.get(key);
|
|
286
|
+
if (entry === undefined && byHost.size >= HOST_LIMIT) {
|
|
287
|
+
otherHosts++;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (entry === undefined) {
|
|
291
|
+
entry = { connections: 0, refused: 0 };
|
|
292
|
+
byHost.set(key, entry);
|
|
293
|
+
}
|
|
294
|
+
if (allowed)
|
|
295
|
+
entry.connections++;
|
|
296
|
+
else
|
|
297
|
+
entry.refused++;
|
|
298
|
+
try {
|
|
299
|
+
options.decided?.(key, allowed, why);
|
|
300
|
+
}
|
|
301
|
+
catch { /* a listener's trouble is not the build's */ }
|
|
302
|
+
};
|
|
303
|
+
const authorized = (incoming) => {
|
|
304
|
+
const given = Buffer.from(String(incoming.headers["proxy-authorization"] ?? ""));
|
|
305
|
+
return given.length === expected.length && timingSafeEqual(given, expected);
|
|
306
|
+
};
|
|
307
|
+
/** The addresses a request may reach, or why it may not. */
|
|
308
|
+
const admit = async (host, port, wantPort) => {
|
|
309
|
+
if (port !== wantPort)
|
|
310
|
+
return { ok: false, why: `port ${port} is not allowed` };
|
|
311
|
+
if (!hostAllowed(host, options.hosts))
|
|
312
|
+
return { ok: false, why: "not an approved host" };
|
|
313
|
+
let addresses;
|
|
314
|
+
try {
|
|
315
|
+
addresses = await lookup(normalizeHost(host));
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return { ok: false, why: "the host does not resolve" };
|
|
319
|
+
}
|
|
320
|
+
if (addresses.length === 0)
|
|
321
|
+
return { ok: false, why: "the host does not resolve" };
|
|
322
|
+
if (!addresses.every(addressAllowed))
|
|
323
|
+
return { ok: false, why: "not a public internet address" };
|
|
324
|
+
return { ok: true, addresses };
|
|
325
|
+
};
|
|
326
|
+
const server = createServer((incoming, outgoing) => {
|
|
327
|
+
void (async () => {
|
|
328
|
+
if (!authorized(incoming)) {
|
|
329
|
+
outgoing.writeHead(407, { "proxy-authenticate": 'Basic realm="toolroll"' }).end();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
let url;
|
|
333
|
+
try {
|
|
334
|
+
url = new URL(incoming.url ?? "");
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
outgoing.writeHead(400).end();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const hostHeader = String(incoming.headers.host ?? "").toLowerCase();
|
|
341
|
+
const sameHost = hostHeader === url.host.toLowerCase() || hostHeader === url.hostname.toLowerCase() || (url.port === "" && hostHeader === `${url.hostname.toLowerCase()}:80`);
|
|
342
|
+
if (url.protocol !== "http:" || url.username !== "" || url.password !== "" || !sameHost) {
|
|
343
|
+
count(url.hostname, false, "not a plain web request for its own host");
|
|
344
|
+
outgoing.writeHead(403, { "content-type": "text/plain" }).end("Toolroll: this build may reach only its approved hosts.");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const reached = await admit(url.hostname, Number(url.port || 80), 80);
|
|
348
|
+
if (!reached.ok) {
|
|
349
|
+
count(url.hostname, false, reached.why);
|
|
350
|
+
outgoing.writeHead(403, { "content-type": "text/plain" }).end(`Toolroll: ${url.hostname} is not one of this build's approved hosts.`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const upstream = await connectFirst(reached.addresses, upstreamPort(80));
|
|
354
|
+
if (upstream === null) {
|
|
355
|
+
outgoing.writeHead(502).end();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
count(url.hostname, true, "approved");
|
|
359
|
+
sockets.add(upstream);
|
|
360
|
+
upstream.on("close", () => sockets.delete(upstream));
|
|
361
|
+
const headers = { ...incoming.headers };
|
|
362
|
+
delete headers["proxy-connection"];
|
|
363
|
+
delete headers["proxy-authorization"];
|
|
364
|
+
const forward = httpRequest({ createConnection: () => upstream, method: incoming.method, path: `${url.pathname}${url.search}`, headers, setHost: false }, answer => {
|
|
365
|
+
outgoing.writeHead(answer.statusCode ?? 502, answer.headers);
|
|
366
|
+
answer.pipe(outgoing);
|
|
367
|
+
});
|
|
368
|
+
forward.on("error", () => { if (!outgoing.headersSent)
|
|
369
|
+
outgoing.writeHead(502); outgoing.end(); });
|
|
370
|
+
incoming.pipe(forward);
|
|
371
|
+
})();
|
|
372
|
+
});
|
|
373
|
+
server.on("connect", (incoming, client, head) => {
|
|
374
|
+
sockets.add(client);
|
|
375
|
+
client.on("close", () => sockets.delete(client));
|
|
376
|
+
client.on("error", () => client.destroy());
|
|
377
|
+
void (async () => {
|
|
378
|
+
if (!authorized(incoming)) {
|
|
379
|
+
client.end('HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="toolroll"\r\nContent-Length: 0\r\n\r\n');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const match = /^([^:[\]\s]+):(\d{1,5})$/.exec(incoming.url ?? "");
|
|
383
|
+
const reached = match === null ? { ok: false, why: "not a host name and port" } : await admit(match[1], Number(match[2]), 443);
|
|
384
|
+
if (!reached.ok) {
|
|
385
|
+
count(match?.[1] ?? String(incoming.url ?? ""), false, reached.why);
|
|
386
|
+
client.end("HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const upstream = await connectFirst(reached.addresses, upstreamPort(443));
|
|
390
|
+
if (upstream === null) {
|
|
391
|
+
if (!client.destroyed)
|
|
392
|
+
client.end("HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
count(match[1], true, "approved");
|
|
396
|
+
sockets.add(upstream);
|
|
397
|
+
upstream.on("close", () => { sockets.delete(upstream); client.destroy(); });
|
|
398
|
+
upstream.on("error", () => client.destroy());
|
|
399
|
+
client.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
400
|
+
if (head.length > 0)
|
|
401
|
+
upstream.write(head);
|
|
402
|
+
upstream.pipe(client);
|
|
403
|
+
client.pipe(upstream);
|
|
404
|
+
})();
|
|
405
|
+
});
|
|
406
|
+
server.on("connection", socket => { sockets.add(socket); socket.on("close", () => sockets.delete(socket)); });
|
|
407
|
+
await new Promise((done, fail) => {
|
|
408
|
+
server.once("error", fail);
|
|
409
|
+
server.listen(0, "127.0.0.1", () => { server.off("error", fail); done(); });
|
|
410
|
+
});
|
|
411
|
+
const address = server.address();
|
|
412
|
+
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
413
|
+
return {
|
|
414
|
+
url: `http://run:${secret}@127.0.0.1:${port}`,
|
|
415
|
+
port,
|
|
416
|
+
counts: () => ({ connections, refused, hosts: [...byHost].map(([host, one]) => ({ host, ...one })).sort((a, b) => b.connections + b.refused - (a.connections + a.refused) || a.host.localeCompare(b.host)), otherHosts }),
|
|
417
|
+
close: () => new Promise(done => {
|
|
418
|
+
for (const socket of sockets)
|
|
419
|
+
socket.destroy();
|
|
420
|
+
server.close(() => done());
|
|
421
|
+
}),
|
|
422
|
+
};
|
|
423
|
+
}
|
package/dist/exec.d.ts
CHANGED
|
@@ -46,8 +46,26 @@ export type RunOptions = ProcessTreeObserver & {
|
|
|
46
46
|
fence?: readonly string[];
|
|
47
47
|
/** What the fence gives back beside those paths (agent-fence.ts FenceGrants). */
|
|
48
48
|
fenceGrants?: FenceGrants;
|
|
49
|
+
/** `required`: this spawn runs code a build controls, so it runs inside the OS fence or not at all (never unwrapped
|
|
50
|
+
* because no backend applies here). The caller passes the fence it must run in. */
|
|
51
|
+
confinement?: "required";
|
|
49
52
|
/** UTF-8 input for JSONL or buffered process-group transports; closed after delivery. */
|
|
50
53
|
stdin?: string;
|
|
54
|
+
/** One ACP conversation (runAcpSession only): the brief, the sealed model, and the agent the attestation names. */
|
|
55
|
+
acp?: AcpRun;
|
|
56
|
+
/** The conformance check's own admission (the gateway only; set by the check job and nothing else): this run is the
|
|
57
|
+
* request's current step, admitted by the request instead of an attestation. */
|
|
58
|
+
acpCheck?: {
|
|
59
|
+
requestId: number;
|
|
60
|
+
step: 1 | 2;
|
|
61
|
+
/** Step 1: the plane's isolation probes — a canary file in the fenced state, and a canary value the launching
|
|
62
|
+
* environment carries under a key every ACP launch sheds. */
|
|
63
|
+
isolation?: {
|
|
64
|
+
file: string;
|
|
65
|
+
fileCanary: string;
|
|
66
|
+
envValue: string;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
51
69
|
/**
|
|
52
70
|
* Absolute wall-clock ceiling. Keep this for bounded commands and repair
|
|
53
71
|
* turns. Long-running provider sessions use idleTimeoutMs instead: useful
|
|
@@ -164,6 +182,19 @@ export type RunOptions = ProcessTreeObserver & {
|
|
|
164
182
|
export declare class ContainmentRefusal extends Error {
|
|
165
183
|
readonly name = "ContainmentRefusal";
|
|
166
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* The command as it runs inside the agent fence (sandbox-exec or bubblewrap wrapping it), or as it is when it has no
|
|
187
|
+
* fence. A spawn whose confinement is required and that no backend here can wrap is refused (ContainmentRefusal):
|
|
188
|
+
* nothing runs, and nothing falls back to running it unwrapped.
|
|
189
|
+
*/
|
|
190
|
+
export declare function confined(file: string, args: readonly string[], bag: {
|
|
191
|
+
fence?: readonly string[] | undefined;
|
|
192
|
+
fenceGrants?: FenceGrants | undefined;
|
|
193
|
+
confinement?: "required" | undefined;
|
|
194
|
+
}): {
|
|
195
|
+
file: string;
|
|
196
|
+
args: string[];
|
|
197
|
+
};
|
|
167
198
|
/** Run IDs are local to a database. Connections to the same file share
|
|
168
199
|
* custody; separate files and separate in-memory databases never do. */
|
|
169
200
|
export declare function runOwnerTag(store: Store, runId: number): string;
|
|
@@ -249,6 +280,53 @@ export declare function runStreamJsonl(file: string, args: readonly string[], op
|
|
|
249
280
|
* semantics match `runStreamJsonl`.
|
|
250
281
|
*/
|
|
251
282
|
export declare function runGeminiStreamJsonl(file: string, args: readonly string[], options?: RunOptions): Promise<ExecResult>;
|
|
283
|
+
/** What runAcpSession needs beyond a process: the conversation's terms (acp-client.ts). */
|
|
284
|
+
export type AcpRun = {
|
|
285
|
+
prompt: string;
|
|
286
|
+
model: string;
|
|
287
|
+
/** The agent's name and version as its attestation recorded them; the handshake must say the same. */
|
|
288
|
+
expect: {
|
|
289
|
+
name: string;
|
|
290
|
+
version: string;
|
|
291
|
+
} | null;
|
|
292
|
+
/** Handed the conversation's controls once it starts (the conformance probe cancels with it). */
|
|
293
|
+
onControl?: (control: AcpControl) => void;
|
|
294
|
+
/** Each handshake step's deadline (ACP_HANDSHAKE_MS unless a test shortens it). */
|
|
295
|
+
handshakeMs?: number;
|
|
296
|
+
};
|
|
297
|
+
/** What a caller (the conformance check) may do with a running ACP conversation. */
|
|
298
|
+
export type AcpControl = {
|
|
299
|
+
/** Ask the agent to stop the prompt in flight; unanswered within `deadlineMs`, the agent is stopped and the
|
|
300
|
+
* conversation fails (a check's cancel must be answered in time). */
|
|
301
|
+
cancel: (deadlineMs?: number) => void;
|
|
302
|
+
/** A fresh, plane-side reading, now, of what the agent itself started and is still running: the live (never zombie)
|
|
303
|
+
* processes beneath the agent's own process — below any wrapper the fence put around it — with their process
|
|
304
|
+
* groups. Null when it can't be read here (Windows). */
|
|
305
|
+
liveDescendants: () => {
|
|
306
|
+
pid: number;
|
|
307
|
+
group: number;
|
|
308
|
+
}[] | null;
|
|
309
|
+
};
|
|
310
|
+
/** How long each shutdown step waits: session/close's answer, then exit after stdin closes, then exit after SIGTERM. */
|
|
311
|
+
export declare const ACP_SHUTDOWN_MS: {
|
|
312
|
+
readonly close: 2000;
|
|
313
|
+
readonly eof: 5000;
|
|
314
|
+
readonly term: 5000;
|
|
315
|
+
};
|
|
316
|
+
/** How long each handshake step (initialize, session/new, choosing the model) may take before the agent is stopped. */
|
|
317
|
+
export declare const ACP_HANDSHAKE_MS = 30000;
|
|
318
|
+
/**
|
|
319
|
+
* One ACP build conversation over the agent's stdio (docs/plans/acp-adapter.md § Gateway, runner, custody). The
|
|
320
|
+
* conversation itself is acp-client.ts; this owns the process: the same contained spawn, ownership, descendant
|
|
321
|
+
* observation, container settlement and watchdog every other runner uses, with both clocks at once.
|
|
322
|
+
*
|
|
323
|
+
* After the prompt is answered it shuts the agent down in bounded steps — session/close (when offered), stdin EOF,
|
|
324
|
+
* SIGTERM to the group, then the forced kill — and returns only when the process has settled, so the gateway's
|
|
325
|
+
* witness records the real exit. A protocol failure kills at once. A stop, from the gateway's stop watch, kills at
|
|
326
|
+
* any step. The result's stdout is the conversation's retained record, never the agent's stream; its code is 0 only
|
|
327
|
+
* when the prompt was answered end_turn and the agent then exited cleanly or on our own signal.
|
|
328
|
+
*/
|
|
329
|
+
export declare function runAcpSession(file: string, args: readonly string[], options?: RunOptions): Promise<ExecResult>;
|
|
252
330
|
/**
|
|
253
331
|
* A live session's account so far (one main result per turn, checked on Claude Code 2.1.290): the latest result's
|
|
254
332
|
* session, ending, words and cost — `total_cost_usd` is already the session's running total — with the usage token
|