@byok-sdk/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/adapters/claude/claude-adapter.d.ts +155 -0
- package/dist/adapters/claude/events.d.ts +91 -0
- package/dist/adapters/claude/permission-mapping.d.ts +142 -0
- package/dist/adapters/claude/process-client.d.ts +103 -0
- package/dist/adapters/claude/resolve-approval-mcp-bin.d.ts +45 -0
- package/dist/adapters/claude/resolve-bin.d.ts +27 -0
- package/dist/adapters/codex/codex-adapter.d.ts +97 -0
- package/dist/adapters/codex/events.d.ts +41 -0
- package/dist/adapters/codex/permission-mapping.d.ts +107 -0
- package/dist/adapters/codex/process-runner.d.ts +86 -0
- package/dist/adapters/codex/resolve-bin.d.ts +25 -0
- package/dist/adapters/pi/events.d.ts +58 -0
- package/dist/adapters/pi/permission-mapping.d.ts +46 -0
- package/dist/adapters/pi/pi-adapter.d.ts +29 -0
- package/dist/adapters/pi/resolve-bin.d.ts +44 -0
- package/dist/adapters/pi/rpc-client.d.ts +101 -0
- package/dist/bin/approval-mcp-server.d.ts +69 -0
- package/dist/bin/audit-log.d.ts +212 -0
- package/dist/bin/byok-agent.d.ts +2 -0
- package/dist/bin/byok-agent.js +12347 -0
- package/dist/bin/byok-agent.js.map +1 -0
- package/dist/bin/byok-approval-mcp.d.ts +2 -0
- package/dist/bin/byok-approval-mcp.js +498 -0
- package/dist/bin/byok-approval-mcp.js.map +1 -0
- package/dist/bin/commands/approvals.d.ts +31 -0
- package/dist/bin/commands/approve-reject.d.ts +29 -0
- package/dist/bin/commands/doctor.d.ts +15 -0
- package/dist/bin/commands/pair.d.ts +7 -0
- package/dist/bin/commands/runtimes.d.ts +7 -0
- package/dist/bin/commands/service.d.ts +54 -0
- package/dist/bin/commands/start.d.ts +21 -0
- package/dist/bin/commands/status.d.ts +25 -0
- package/dist/bin/commands/support-bundle.d.ts +7 -0
- package/dist/bin/commands/tasks.d.ts +27 -0
- package/dist/bin/commands/unpair.d.ts +124 -0
- package/dist/bin/commands/workspaces.d.ts +16 -0
- package/dist/bin/config.d.ts +41 -0
- package/dist/bin/control-client.d.ts +50 -0
- package/dist/bin/format.d.ts +67 -0
- package/dist/bin/runtime-probe.d.ts +34 -0
- package/dist/bin/tasks-view.d.ts +49 -0
- package/dist/daemon/approvals.d.ts +87 -0
- package/dist/daemon/auth-manager.d.ts +52 -0
- package/dist/daemon/blob-client.d.ts +22 -0
- package/dist/daemon/connection-manager.d.ts +502 -0
- package/dist/daemon/control-protocol.d.ts +299 -0
- package/dist/daemon/control-server.d.ts +61 -0
- package/dist/daemon/create-daemon.d.ts +364 -0
- package/dist/daemon/cursor-store.d.ts +30 -0
- package/dist/daemon/daemon-owner.d.ts +24 -0
- package/dist/daemon/deterministic-jitter.d.ts +20 -0
- package/dist/daemon/device-keys.d.ts +45 -0
- package/dist/daemon/device-proof-signer.d.ts +41 -0
- package/dist/daemon/environment.d.ts +96 -0
- package/dist/daemon/git-workspace-store.d.ts +49 -0
- package/dist/daemon/git-workspace.d.ts +80 -0
- package/dist/daemon/http-client.d.ts +8 -0
- package/dist/daemon/journal/journal.d.ts +342 -0
- package/dist/daemon/journal/sqlite-journal.d.ts +173 -0
- package/dist/daemon/journal/sqlite-support.d.ts +96 -0
- package/dist/daemon/journal/storage-policy.d.ts +368 -0
- package/dist/daemon/long-poll-transport.d.ts +142 -0
- package/dist/daemon/observer.d.ts +345 -0
- package/dist/daemon/operational-health.d.ts +87 -0
- package/dist/daemon/policy.d.ts +16 -0
- package/dist/daemon/progress-batcher.d.ts +31 -0
- package/dist/daemon/runtime-capabilities.d.ts +36 -0
- package/dist/daemon/session-workspace-store.d.ts +75 -0
- package/dist/daemon/store.d.ts +54 -0
- package/dist/daemon/task-runner.d.ts +862 -0
- package/dist/daemon/truth-memory-client.d.ts +94 -0
- package/dist/daemon/url.d.ts +60 -0
- package/dist/daemon/ws-transport.d.ts +110 -0
- package/dist/diagnostics/diagnostics.d.ts +114 -0
- package/dist/diagnostics/support-bundle.d.ts +81 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +10056 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/create-service-lifecycle.d.ts +59 -0
- package/dist/lifecycle/exec-runner.d.ts +141 -0
- package/dist/lifecycle/launchd.d.ts +55 -0
- package/dist/lifecycle/service-types.d.ts +177 -0
- package/dist/lifecycle/systemd.d.ts +46 -0
- package/dist/lifecycle/winsw.d.ts +64 -0
- package/dist/types.d.ts +178 -0
- package/dist/util/async-queue.d.ts +45 -0
- package/dist/util/atomic-write.d.ts +86 -0
- package/dist/util/secure-dir.d.ts +124 -0
- package/package.json +66 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import type { RuntimeId } from '@byok-sdk/protocol';
|
|
2
|
+
import type { PermissionPolicy } from '@byok-sdk/protocol';
|
|
3
|
+
import type { RuntimeAdapter, GitWorkspaceConfig } from '../types';
|
|
4
|
+
import type { BackoffOptions, LivenessOptions } from './ws-transport';
|
|
5
|
+
import { type OperationalHealthSnapshot } from './operational-health';
|
|
6
|
+
import { type DaemonEventListener, type DaemonTaskInfo, type Unsubscribe } from './observer';
|
|
7
|
+
import { GitWorkspaceManager } from './git-workspace';
|
|
8
|
+
import { GitWorkspaceStore } from './git-workspace-store';
|
|
9
|
+
import { type DeviceRecord } from './store';
|
|
10
|
+
import { type LocalTaskJournal } from './journal/journal';
|
|
11
|
+
import { type JournalOpenFaultSeam } from './journal/sqlite-support';
|
|
12
|
+
import { LocalStoragePressureEngine, type LocalStoragePolicyInput } from './journal/storage-policy';
|
|
13
|
+
import type { ProgressBatcherOptions } from './progress-batcher';
|
|
14
|
+
/**
|
|
15
|
+
* Optional white-label product display info — purely opaque passthrough
|
|
16
|
+
* (never interpreted, validated, or rendered by the daemon itself). Carried
|
|
17
|
+
* through to `DaemonStatus.branding` (see `status()` below) so a downstream
|
|
18
|
+
* CLI or audit log can render/stamp product identity without the daemon
|
|
19
|
+
* needing to know anything about presentation. Deliberately a small,
|
|
20
|
+
* open-ish shape rather than an exhaustive theming schema — add fields here
|
|
21
|
+
* only as concrete consumers (CLI UX, audit log) need them.
|
|
22
|
+
*/
|
|
23
|
+
export interface DaemonBranding {
|
|
24
|
+
/** Product/company name for banners, prompts, audit log entries, etc. */
|
|
25
|
+
displayName?: string;
|
|
26
|
+
/** Support/help URL surfaced alongside branding. */
|
|
27
|
+
supportUrl?: string;
|
|
28
|
+
/** Brand accent color (any CSS-color-like string — hex, name, etc.); not parsed or validated here. */
|
|
29
|
+
accent?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* S3b (L-002): opt-in durable local journal for hosted deployments
|
|
33
|
+
* (architecture §12.7.2).
|
|
34
|
+
*
|
|
35
|
+
* Explicit and off by default, because it changes what an ack MEANS. With no
|
|
36
|
+
* journal configured (the self-hosted default this SDK has always shipped),
|
|
37
|
+
* the daemon's redelivery cursor advances on handler success exactly as
|
|
38
|
+
* before. With one configured, every inbound envelope is committed and fsynced
|
|
39
|
+
* to `<storeDir>/daemon.db` BEFORE the handler resolves — and since
|
|
40
|
+
* `ConnectionManager` only advances the cursor once the handler resolves
|
|
41
|
+
* (`connection-manager.ts`), acking before committing stops being something
|
|
42
|
+
* this daemon can express, rather than something it is careful not to do.
|
|
43
|
+
*
|
|
44
|
+
* Turning this on when the runtime has no working `node:sqlite` is a typed
|
|
45
|
+
* construction failure, not a downgrade to a file journal — see
|
|
46
|
+
* `JournalUnavailableError`. That refusal is the point (§12.7.2): a journal
|
|
47
|
+
* that does not fsync loses tasks only under power-cut timings, so it passes
|
|
48
|
+
* every test anyone will ever run against it.
|
|
49
|
+
*/
|
|
50
|
+
export interface HostedJournalConfig {
|
|
51
|
+
/**
|
|
52
|
+
* The backend. Only `sqlite` today, and spelled out rather than implied by
|
|
53
|
+
* the section's presence so adding a second one later is an additive change
|
|
54
|
+
* to a closed set instead of a re-interpretation of an existing config.
|
|
55
|
+
*/
|
|
56
|
+
mode: 'sqlite';
|
|
57
|
+
/**
|
|
58
|
+
* The tenant every journal row on this device is scoped to (§12.7.2's
|
|
59
|
+
* minimum fact set opens with tenant/product/device). Required: a hosted
|
|
60
|
+
* daemon that cannot say which tenant its durable evidence belongs to has
|
|
61
|
+
* evidence nobody can act on.
|
|
62
|
+
*/
|
|
63
|
+
tenantId: string;
|
|
64
|
+
/** Bound on waiting for the journal's write lock, ms. Defaults to the journal's own bound. */
|
|
65
|
+
busyTimeoutMs?: number;
|
|
66
|
+
/** Per-record byte bound. Defaults to the journal's own bound; oversized records are refused, never truncated. */
|
|
67
|
+
maxRecordBytes?: number;
|
|
68
|
+
/**
|
|
69
|
+
* S3b (L-003): opt-in local storage policy — §12.7.2.1's watermarks,
|
|
70
|
+
* classified GC, and bounded WAL compaction. See `LocalStoragePolicyInput`
|
|
71
|
+
* (`journal/storage-policy.ts`); `maxStoreBytes` and `minFreeBytes` are the
|
|
72
|
+
* only two a host must supply.
|
|
73
|
+
*
|
|
74
|
+
* Optional WITHIN hosted journal mode, and off by default there too: a
|
|
75
|
+
* journal without a policy is a daemon that records durably and never
|
|
76
|
+
* declines, which is exactly the right behaviour on a host that manages its
|
|
77
|
+
* own disk. Setting it is what turns local storage into an admission-control
|
|
78
|
+
* input — hard pressure declines new offers (retryably) while terminal
|
|
79
|
+
* flush, delete, export and recovery keep running, and `emergency` refuses
|
|
80
|
+
* to ack at all rather than acking a row it cannot durably record.
|
|
81
|
+
*/
|
|
82
|
+
storagePolicy?: LocalStoragePolicyInput;
|
|
83
|
+
}
|
|
84
|
+
export interface DaemonConfig {
|
|
85
|
+
productName: string;
|
|
86
|
+
productId: string;
|
|
87
|
+
serverUrl: string;
|
|
88
|
+
deviceName?: string;
|
|
89
|
+
workspaceRoot: string;
|
|
90
|
+
/** Disabled by default. Enables local-only Git checkpoint repositories. */
|
|
91
|
+
gitWorkspace?: GitWorkspaceConfig;
|
|
92
|
+
/** Disabled by default. Enables the durable local task journal — see {@link HostedJournalConfig}. */
|
|
93
|
+
hostedJournal?: HostedJournalConfig;
|
|
94
|
+
/**
|
|
95
|
+
* Restricts which runtimes this daemon will ever use — enforced in two
|
|
96
|
+
* places that must stay consistent: `createDaemon` (this file) builds its
|
|
97
|
+
* bundled adapter set FROM this list (unset = all three bundled adapters
|
|
98
|
+
* — pi, claude, codex; set = exactly the listed runtime ids, unknown ids
|
|
99
|
+
* ignored — see `buildDefaultAdapters`), and `TaskRunner.pickAdapter`
|
|
100
|
+
* (`task-runner.ts`) separately fail-closed-rejects any `task.offer`
|
|
101
|
+
* naming a runtime outside this list regardless of which adapters were
|
|
102
|
+
* constructed. `createDaemonWithAdapters` callers supply their own
|
|
103
|
+
* `adapters` array directly, so for them this field is enforcement-only,
|
|
104
|
+
* unchanged from M1/M2.
|
|
105
|
+
*/
|
|
106
|
+
runtimeAllowlist?: string[];
|
|
107
|
+
/**
|
|
108
|
+
* M5 batch-3 (workstream 1): explicit auto-select priority order for
|
|
109
|
+
* `TaskRunner.pickAdapter`'s no-explicit-runtime branch (`task-runner.ts`)
|
|
110
|
+
* — tried in listed order; the first candidate that is both PRESENT
|
|
111
|
+
* (`adapter.detect()`) and CAPABLE (declares the offer's
|
|
112
|
+
* `PermissionPolicy.mode` in its own `capabilities().permissionModes` —
|
|
113
|
+
* see `adapterSupportsMode`) wins. Unset defaults to
|
|
114
|
+
* `DEFAULT_RUNTIME_PREFERENCE` (`task-runner.ts`): `['claude', 'codex',
|
|
115
|
+
* 'pi']` — pi LAST, deliberately.
|
|
116
|
+
*
|
|
117
|
+
* Product decision: pi is this SDK's FALLBACK runtime, not its default.
|
|
118
|
+
* Before this field existed, the auto-select path had no notion of
|
|
119
|
+
* priority at all — it simply walked `deps.adapters` in whatever order
|
|
120
|
+
* `buildDefaultAdapters`/the embedder constructed them, which for
|
|
121
|
+
* `createDaemon`'s bundled set meant `ALL_RUNTIME_IDS`'s construction
|
|
122
|
+
* order (`['pi', 'claude', 'codex']`) doubled as the de-facto selection
|
|
123
|
+
* priority — silently making pi the default winner whenever it was
|
|
124
|
+
* present, for no better reason than being listed first in an array never
|
|
125
|
+
* meant to encode a priority. This field makes the real priority an
|
|
126
|
+
* explicit, independently-configurable decision instead of an accident of
|
|
127
|
+
* construction order — see `ALL_RUNTIME_IDS`'s own doc comment below for
|
|
128
|
+
* the construction-vs-selection-order split this introduces.
|
|
129
|
+
*
|
|
130
|
+
* Only affects the auto-select (no `task.offer.runtime`) path — an offer
|
|
131
|
+
* naming an explicit runtime is unaffected (unchanged semantics: that
|
|
132
|
+
* exact adapter is used, or the offer is declined; never a fallback
|
|
133
|
+
* substitution). Independent of `runtimeAllowlist` above (which restricts
|
|
134
|
+
* WHICH runtimes are eligible at all): this only orders the attempt
|
|
135
|
+
* sequence among whatever that allowlist, if set, already let through.
|
|
136
|
+
*/
|
|
137
|
+
runtimePreference?: RuntimeId[];
|
|
138
|
+
/**
|
|
139
|
+
* The device operator's configured policy CEILING — every `task.offer`'s
|
|
140
|
+
* own policy is merged against this and fail-closed-rejected if it asks
|
|
141
|
+
* for more latitude than this allows (`daemon/policy.ts`'s
|
|
142
|
+
* `computeEffectivePolicy`).
|
|
143
|
+
*
|
|
144
|
+
* M5 batch-3 (workstream 1): `workspaceRoot` set on THIS ceiling is still
|
|
145
|
+
* merged into the effective policy handed to an adapter as
|
|
146
|
+
* `ctx.policy.workspaceRoot` (`computeEffectivePolicy` is unchanged) — but
|
|
147
|
+
* no bundled adapter (pi/claude/codex) actually reads or enforces it;
|
|
148
|
+
* every adapter derives its real confinement from `ctx.workspaceDir` (the
|
|
149
|
+
* daemon-created per-task directory) instead — see docs/security.md's
|
|
150
|
+
* "Workspace confinement is a convention, not a sandbox" section. Setting
|
|
151
|
+
* it here is therefore silently inert rather than actively dangerous by
|
|
152
|
+
* itself (an OFFER independently asking for its OWN `workspaceRoot` is a
|
|
153
|
+
* separate, fail-closed-declined case — see `TaskRunner.handleOffer` —
|
|
154
|
+
* precisely because THAT looks like a live security control when it
|
|
155
|
+
* isn't). `start()` below logs a loud, one-time `console.warn` whenever
|
|
156
|
+
* this ceiling sets `workspaceRoot`, so an operator who configured it
|
|
157
|
+
* expecting real enforcement finds out immediately instead of trusting a
|
|
158
|
+
* control nothing honors.
|
|
159
|
+
*/
|
|
160
|
+
permissionDefaults?: PermissionPolicy;
|
|
161
|
+
storeDir?: string;
|
|
162
|
+
/** Optional white-label branding — see `DaemonBranding`. Carried through verbatim to `status().branding`. */
|
|
163
|
+
branding?: DaemonBranding;
|
|
164
|
+
/**
|
|
165
|
+
* M5: per-device, per-runtime escape hatch into the environment allowlist
|
|
166
|
+
* `task-runner.ts` builds each task's spawn environment from
|
|
167
|
+
* (`daemon/environment.ts`'s `buildRuntimeEnv`) — keyed by runtime id
|
|
168
|
+
* (`'pi' | 'claude' | 'codex'`, though not typed that narrowly here since
|
|
169
|
+
* an id with no matching adapter is simply never looked up). `allow`
|
|
170
|
+
* entries are exact variable names or `*`-suffixed prefixes, merged in
|
|
171
|
+
* alongside that runtime adapter's own declared
|
|
172
|
+
* `environmentRequirements()` — this can never override the hard
|
|
173
|
+
* `BYOK_*` deny (see `environment.ts`'s own doc comment).
|
|
174
|
+
*/
|
|
175
|
+
runtimeEnvironment?: Record<string, {
|
|
176
|
+
allow?: string[];
|
|
177
|
+
}>;
|
|
178
|
+
/**
|
|
179
|
+
* M5: explicit escape hatch for `url.ts`'s `assertServerUrlAllowed` — see
|
|
180
|
+
* that function's own doc comment for the full allow/deny rule. Default
|
|
181
|
+
* (unset/`false`): a `serverUrl` using plaintext `ws:`/`http:` is only
|
|
182
|
+
* accepted when its host is loopback (`localhost`/`*.localhost`,
|
|
183
|
+
* `127.0.0.0/8`, `::1`); anything else over plaintext throws a typed
|
|
184
|
+
* `InsecureServerUrlError` from `pair()`/`start()` below, BEFORE any
|
|
185
|
+
* network call is attempted, rather than silently sending the pairing
|
|
186
|
+
* code / device credentials to a remote host in the clear. Set this
|
|
187
|
+
* `true` only when you have deliberately decided to accept that risk
|
|
188
|
+
* (e.g. a trusted private network with no TLS terminator in front of the
|
|
189
|
+
* server) — doing so also logs a loud `console.warn` (see
|
|
190
|
+
* `checkServerUrl`, this file) every time it actually changes the
|
|
191
|
+
* outcome. Never overrides an unsupported scheme (anything other than
|
|
192
|
+
* `http:`/`https:`/`ws:`/`wss:`), which is refused unconditionally.
|
|
193
|
+
*/
|
|
194
|
+
dangerouslyAllowInsecureRemote?: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* M5 batch-3 (workstream 2): caps accumulated (approximate,
|
|
197
|
+
* serialized-event-length) agent-event output bytes this daemon will
|
|
198
|
+
* tolerate for a single task before tearing it down as a resource-limit
|
|
199
|
+
* violation (`task.fail`, `retryable: false`, reason prefixed
|
|
200
|
+
* `resource limit exceeded: maxTaskOutputBytes` — see
|
|
201
|
+
* `MAX_OUTPUT_BYTES_EXCEEDED_REASON_PREFIX`, `task-runner.ts`) — see
|
|
202
|
+
* `TaskRunner.pump`'s own doc comment for exactly what's counted and what
|
|
203
|
+
* isn't. Default {@link DEFAULT_MAX_TASK_OUTPUT_BYTES} (64 MiB) when
|
|
204
|
+
* unset.
|
|
205
|
+
*
|
|
206
|
+
* `0` or a negative number is a config validation error, thrown
|
|
207
|
+
* synchronously from `createDaemonWithAdapters`/`createDaemon` — NOT a
|
|
208
|
+
* supported way to disable the cap. Pass `Number.POSITIVE_INFINITY`
|
|
209
|
+
* explicitly instead to opt out of enforcement altogether.
|
|
210
|
+
*/
|
|
211
|
+
maxTaskOutputBytes?: number;
|
|
212
|
+
/**
|
|
213
|
+
* M5 batch-3 (workstream 2): deadline bound on the graceful-shutdown
|
|
214
|
+
* sequence's own wait for `TaskRunner.shutdownActiveTasks` to finish
|
|
215
|
+
* interrupting/failing every active task before this daemon proceeds to
|
|
216
|
+
* actually stop regardless — see `runShutdownSequence`'s own doc comment
|
|
217
|
+
* for the full sequence this bounds, and for why every graceful-shutdown
|
|
218
|
+
* path (`stop()`, `unpair()`, the control socket's `shutdown` RPC) now
|
|
219
|
+
* shares it. Default 10s (`SHUTDOWN_TASK_TEARDOWN_DEADLINE_MS`) — the same
|
|
220
|
+
* bound the control-socket shutdown path already used before this field
|
|
221
|
+
* existed.
|
|
222
|
+
*/
|
|
223
|
+
shutdownGraceMs?: number;
|
|
224
|
+
}
|
|
225
|
+
export interface DaemonStatus {
|
|
226
|
+
paired: boolean;
|
|
227
|
+
connected: boolean;
|
|
228
|
+
/** True once the connection has fallen back to long-poll (protocol §8) — transport info only (finding F6): long-poll is a full transport, so work still proceeds normally while this holds; outbound envelopes POST to /byok/messages instead of going out over WS. */
|
|
229
|
+
degraded: boolean;
|
|
230
|
+
/** True once the server has revoked this device (401 on challenge/token, protocol §6.3). The only recourse is calling `pair()` again — the daemon does not keep retrying on its own. */
|
|
231
|
+
revoked: boolean;
|
|
232
|
+
deviceId?: string;
|
|
233
|
+
activeTaskCount: number;
|
|
234
|
+
/** Passthrough of `DaemonConfig.branding` — `undefined` when the product configured none. See `DaemonBranding`. */
|
|
235
|
+
branding?: DaemonBranding;
|
|
236
|
+
/** Local lifecycle/retry budget, separate from transport fallback state. */
|
|
237
|
+
operationalHealth: OperationalHealthSnapshot;
|
|
238
|
+
}
|
|
239
|
+
export interface Daemon {
|
|
240
|
+
pair(pairingCode: string): Promise<DeviceRecord>;
|
|
241
|
+
start(): Promise<void>;
|
|
242
|
+
stop(): Promise<void>;
|
|
243
|
+
status(): DaemonStatus;
|
|
244
|
+
/**
|
|
245
|
+
* M3-2a: local observability — subscribe to live `DaemonEvent`s (task
|
|
246
|
+
* feed, connection/pairing state changes, runtime-detection results) as
|
|
247
|
+
* they happen on THIS daemon, no SaaS-side polling required. Returns an
|
|
248
|
+
* unsubscribe function; a listener that throws is caught and logged, never
|
|
249
|
+
* propagated (see `observer.ts`). Purely additive: emitting these never
|
|
250
|
+
* changes `status()` or any existing wire/task behavior.
|
|
251
|
+
*/
|
|
252
|
+
subscribe(listener: DaemonEventListener): Unsubscribe;
|
|
253
|
+
/** M3-2a: current locally-known tasks and their derived state/summary (for a `tasks` CLI subcommand) — reflects only what this daemon has observed since it started; see `observer.ts`'s `DaemonObserver.tasks`. */
|
|
254
|
+
tasks(): DaemonTaskInfo[];
|
|
255
|
+
/**
|
|
256
|
+
* M3-2a: clears this device's persisted identity/credentials and
|
|
257
|
+
* disconnects — the next `start()` throws until `pair()` is called again.
|
|
258
|
+
* Safe to call at any point in the daemon's lifecycle (never paired,
|
|
259
|
+
* paired-but-not-started, or running).
|
|
260
|
+
*/
|
|
261
|
+
unpair(): Promise<void>;
|
|
262
|
+
/**
|
|
263
|
+
* M3-2a: locally resolve a task currently paused on `needs_approval` —
|
|
264
|
+
* drives the exact same code path a server-sent `task.approve` does
|
|
265
|
+
* (`TaskRunner.handleApprove`), invoked directly instead of over the wire.
|
|
266
|
+
* Honest-but-currently-unexercised: none of the three bundled adapters
|
|
267
|
+
* (pi/claude/codex) ever actually pauses for approval — each one's
|
|
268
|
+
* `resolveApproval` throws unconditionally (see `toRuntimeInfoCapabilities`'s
|
|
269
|
+
* doc comment below) — so calling this against a task on this daemon today
|
|
270
|
+
* always fails that task with a clear reason instead of resuming it,
|
|
271
|
+
* exactly as an honest, doing-nothing-magic implementation should. Ready
|
|
272
|
+
* for the day a runtime adapter implements real interactive approval, with
|
|
273
|
+
* no further changes needed here. A no-op (resolves immediately) for a
|
|
274
|
+
* `taskId` this daemon doesn't currently have active.
|
|
275
|
+
*/
|
|
276
|
+
approve(taskId: string): Promise<void>;
|
|
277
|
+
/** M3-2a: same as {@link approve} but rejects — see that method's doc comment. */
|
|
278
|
+
reject(taskId: string, reason?: string): Promise<void>;
|
|
279
|
+
}
|
|
280
|
+
/** Internal seam so tests can substitute stub adapters / faster backoff+batch+liveness+long-poll timing. `createDaemonWithAdapters` (which takes this) is also the real entry point for products supplying a hand-built adapter set `createDaemon` can't construct on its own — e.g. custom adapter options, or an adapter that REPLACES a bundled runtime's implementation under the same id. Honest limit: an adapter id outside `pi`/`claude`/`codex` cannot pass wire validation today — `RuntimeIdSchema` (`@byok-sdk/protocol`) is a closed `z.enum(['pi', 'claude', 'codex'])`, and `isRuntimeId` filtering below (see `detectRuntimes`) drops any detected adapter outside that set before it ever reaches a wire-visible field. A genuinely fourth/namespaced runtime id is a future protocol change, not something this seam enables today. */
|
|
281
|
+
export interface DaemonOverrides {
|
|
282
|
+
backoff?: BackoffOptions;
|
|
283
|
+
batch?: ProgressBatcherOptions;
|
|
284
|
+
liveness?: LivenessOptions;
|
|
285
|
+
/** M4 Phase 3: overrides `TaskRunner`'s default out-of-band approval wait (`DEFAULT_APPROVAL_TIMEOUT_MS`, 10 minutes) before an unanswered `requestApproval` force-resolves as a fail-closed rejection. */
|
|
286
|
+
approvalTimeoutMs?: number;
|
|
287
|
+
/** Finding F5: overrides for the control-socket shutdown path's own bounded waits — see `TaskRunner.shutdownTask`'s and `ConnectionManager.stop`'s own doc comments. Both default to 5s; neither affects an ordinary (non-shutdown-RPC) `daemon.stop()` call. */
|
|
288
|
+
shutdown?: {
|
|
289
|
+
/** Bound on how long `shutdownTask` waits for a single task's own `session.interrupt()` before giving up on it specifically and reporting `task.fail` anyway. Default `DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS`. */
|
|
290
|
+
taskInterruptTimeoutMs?: number;
|
|
291
|
+
/** Bound on how long the control-socket shutdown path waits for the outbox to actually drain before closing the connection. Default `DEFAULT_SHUTDOWN_OUTBOX_DRAIN_TIMEOUT_MS`. */
|
|
292
|
+
outboxDrainTimeoutMs?: number;
|
|
293
|
+
};
|
|
294
|
+
longPoll?: {
|
|
295
|
+
/** Consecutive never-acked WS connect failures before falling back to long-poll. Default 3. */
|
|
296
|
+
wsFailureThreshold?: number;
|
|
297
|
+
/** While long-polling, how often to retry establishing WS. Default 5 minutes. */
|
|
298
|
+
wsRetryIntervalMs?: number;
|
|
299
|
+
/** Backoff between failed long-poll HTTP attempts. Default 2s. */
|
|
300
|
+
retryDelayMs?: number;
|
|
301
|
+
/** Minimum delay before the next long-poll request after an empty (no-events) response — avoids busy-looping against a server that responds instantly. Default 250ms. */
|
|
302
|
+
idleDelayMs?: number;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Test/product injection seam for the local Git workspace boundary. The
|
|
306
|
+
* supplied manager/store are used only when `config.gitWorkspace` is enabled;
|
|
307
|
+
* an absent Git config therefore cannot accidentally activate either object.
|
|
308
|
+
*/
|
|
309
|
+
gitWorkspace?: {
|
|
310
|
+
manager?: GitWorkspaceManager;
|
|
311
|
+
store?: GitWorkspaceStore;
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* S3b: injection seam for the durable local journal, mirroring
|
|
315
|
+
* `gitWorkspace` above — used only when `config.hostedJournal` is set, so an
|
|
316
|
+
* absent journal config cannot accidentally activate a supplied object. Also
|
|
317
|
+
* the host-injected-implementation path §12.7.2 allows: a backend other than
|
|
318
|
+
* `SqliteLocalTaskJournal` is acceptable if it meets the same durability
|
|
319
|
+
* contract (a resolved `appendEnvelope` means fsynced), and unacceptable
|
|
320
|
+
* otherwise — this seam does not verify that, the host asserting it does.
|
|
321
|
+
*
|
|
322
|
+
* A journal supplied here is NOT closed by `stop()`; whoever injected it
|
|
323
|
+
* owns its lifetime.
|
|
324
|
+
*/
|
|
325
|
+
hostedJournal?: {
|
|
326
|
+
journal?: LocalTaskJournal;
|
|
327
|
+
/** Test-only post-open SQLite initialization fault seam. */
|
|
328
|
+
openFaults?: JournalOpenFaultSeam;
|
|
329
|
+
/**
|
|
330
|
+
* S3b (L-003): injection seam for the storage pressure engine, same rule
|
|
331
|
+
* as `journal` above — used only when `config.hostedJournal` is set, and
|
|
332
|
+
* NOT started or stopped by this daemon. Whoever injects one owns its
|
|
333
|
+
* cadence, which is what lets the disk-pressure matrix drive `tick()` by
|
|
334
|
+
* hand with fake usage/free providers and no timer at all.
|
|
335
|
+
*
|
|
336
|
+
* An injected engine still answers both questions the daemon asks it
|
|
337
|
+
* (admission under hard pressure, ack-critical refusal under emergency)
|
|
338
|
+
* and still backs the `storage` section of the control-socket status.
|
|
339
|
+
*/
|
|
340
|
+
pressureEngine?: LocalStoragePressureEngine;
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
export declare function createDaemonWithAdapters(config: DaemonConfig, adapters: RuntimeAdapter[], overrides?: DaemonOverrides): Daemon;
|
|
344
|
+
/**
|
|
345
|
+
* Public white-label entry point (M0-M3): the "5-line launcher" — a product
|
|
346
|
+
* only needs a `DaemonConfig`, no hand-built adapter list. The bundled
|
|
347
|
+
* adapter set is built from `config.runtimeAllowlist` (see
|
|
348
|
+
* `buildDefaultAdapters` and that field's own doc comment for the exact
|
|
349
|
+
* unset-vs-set contract); with no `runtimeAllowlist` configured, the default
|
|
350
|
+
* is ALL THREE bundled adapters (pi, claude, codex), not pi alone: M0/M1
|
|
351
|
+
* hard-wired pi-only unconditionally, which meant any product wanting
|
|
352
|
+
* claude/codex had to drop to `createDaemonWithAdapters` and hand-build
|
|
353
|
+
* adapters just to get a runtime that was already built into this SDK.
|
|
354
|
+
* `detectRuntimes` only ever advertises what's actually present on the
|
|
355
|
+
* device (protocol §10 gap #4), so constructing an adapter for a runtime the
|
|
356
|
+
* device doesn't have costs one quick failed `--version` probe at `start()`
|
|
357
|
+
* and is otherwise invisible — there's no reason to withhold it by default.
|
|
358
|
+
* Products that DO want to restrict to a subset set `runtimeAllowlist`
|
|
359
|
+
* (also independently enforced at task-pick time — see
|
|
360
|
+
* `TaskRunnerDeps.runtimeAllowlist` / `TaskRunner.pickAdapter`); products
|
|
361
|
+
* needing something this can't build (custom adapter options, a fourth
|
|
362
|
+
* in-house runtime, test stubs) use `createDaemonWithAdapters` directly.
|
|
363
|
+
*/
|
|
364
|
+
export declare function createDaemon(config: DaemonConfig): Daemon;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persists the highest processed server->daemon envelope `seq` per
|
|
3
|
+
* (server, device) pair (protocol §9, at-least-once redelivery), so a
|
|
4
|
+
* restarted daemon can send an accurate `conn.hello.cursor` and the server
|
|
5
|
+
* can skip re-delivering envelopes it already knows were handled. Keyed by a
|
|
6
|
+
* hash of `serverUrl` + `deviceId` together (not just `serverUrl`) so
|
|
7
|
+
* filenames stay filesystem-safe regardless of scheme/port/path.
|
|
8
|
+
*
|
|
9
|
+
* Finding F5 (stale cursor across re-pair): `POST /byok/pair` always mints a
|
|
10
|
+
* brand new `deviceId` (see `packages/server/src/http.ts`), including on a
|
|
11
|
+
* re-pair against the same `serverUrl` (e.g. recovering from revocation,
|
|
12
|
+
* protocol §6.3). A cursor keyed by `serverUrl` alone would hand the fresh
|
|
13
|
+
* device's very first connection a stale, unrelated cursor value left over
|
|
14
|
+
* from whatever device previously used this URL — the new device's own
|
|
15
|
+
* server-side outbox starts its `seq` counter back at 1, so that stale
|
|
16
|
+
* cursor would make the server's redelivery filter (`seq > cursor`) throw
|
|
17
|
+
* away every legitimate envelope sent to it. Keying by the pair means a new
|
|
18
|
+
* deviceId always starts with a genuinely fresh (absent) cursor entry;
|
|
19
|
+
* `clear()` additionally lets `create-daemon.ts`'s `pair()` proactively wipe
|
|
20
|
+
* the previous device's entry for this `serverUrl` as a hygiene measure.
|
|
21
|
+
*/
|
|
22
|
+
export declare class CursorStore {
|
|
23
|
+
private readonly storeDir;
|
|
24
|
+
constructor(storeDir: string);
|
|
25
|
+
private fileFor;
|
|
26
|
+
load(serverUrl: string, deviceId: string): Promise<number | undefined>;
|
|
27
|
+
save(serverUrl: string, deviceId: string, cursor: number): Promise<void>;
|
|
28
|
+
/** Remove any persisted cursor for (serverUrl, deviceId) — a no-op if none exists. Called from `pair()` (finding F5) so a device that's about to be replaced never leaves a cursor a future, unrelated device could somehow inherit. */
|
|
29
|
+
clear(serverUrl: string, deviceId: string): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const DAEMON_OWNER_FILENAME = "daemon-owner.json";
|
|
2
|
+
interface OwnerRecord {
|
|
3
|
+
version: 2;
|
|
4
|
+
pid: number;
|
|
5
|
+
nonce: string;
|
|
6
|
+
role: 'daemon' | 'doctor';
|
|
7
|
+
acquiredAt: string;
|
|
8
|
+
processStartedAt: string;
|
|
9
|
+
livenessPort: number;
|
|
10
|
+
}
|
|
11
|
+
export interface DaemonOwnerLease {
|
|
12
|
+
release(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare class DaemonOwnerActiveError extends Error {
|
|
15
|
+
readonly role: OwnerRecord['role'] | 'unknown';
|
|
16
|
+
constructor(role: OwnerRecord['role'] | 'unknown');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Cross-process, fail-closed ownership for store mutations that must never
|
|
20
|
+
* race a daemon. A separate exclusive reclaim file serializes stale-owner recovery so two
|
|
21
|
+
* starters cannot accidentally rename each other's newly-acquired lease.
|
|
22
|
+
*/
|
|
23
|
+
export declare function acquireDaemonOwner(storeDir: string, role: OwnerRecord['role'], clock?: () => Date): Promise<DaemonOwnerLease>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type JitterDomain = 'reconnect' | 'upload' | 'maintenance';
|
|
2
|
+
export interface DeterministicJitterInput {
|
|
3
|
+
seed: string;
|
|
4
|
+
domain: JitterDomain;
|
|
5
|
+
sequence: number;
|
|
6
|
+
baseMs: number;
|
|
7
|
+
ratio?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Stable, domain-separated fleet jitter. The same identity/domain/sequence
|
|
11
|
+
* always produces the same integer delay, while a different domain cannot
|
|
12
|
+
* accidentally reuse the same hash stream. There is deliberately no random
|
|
13
|
+
* fallback: callers must have loaded the device identity before constructing
|
|
14
|
+
* an automatic retry loop.
|
|
15
|
+
*/
|
|
16
|
+
export declare function deterministicJitterMs(input: DeterministicJitterInput): number;
|
|
17
|
+
export interface FleetJitter {
|
|
18
|
+
delay(domain: JitterDomain, sequence: number, baseMs: number): number;
|
|
19
|
+
}
|
|
20
|
+
export declare function createFleetJitter(productId: string, deviceId: string): FleetJitter;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type KeyObject } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Device identity keypair (protocol §6): Ed25519, generated once on first
|
|
4
|
+
* pair and reused across re-pairs (§6.3 — "a fresh device keypair is not
|
|
5
|
+
* required" when re-pairing after revocation). The private key never
|
|
6
|
+
* leaves this device; only the public key ever goes over the wire
|
|
7
|
+
* (`PairRequest.devicePublicKey`).
|
|
8
|
+
*
|
|
9
|
+
* Encoding choice: an OKP (Ed25519) key's JWK `x` member is defined by
|
|
10
|
+
* RFC 8037 as the raw 32-byte public key, base64url-encoded with no
|
|
11
|
+
* padding — exactly "Ed25519 public key, base64url-encoded" per
|
|
12
|
+
* docs/protocol.md §6.1, and avoids shipping an ASN.1/SPKI wrapper the
|
|
13
|
+
* server would otherwise have to strip. Signatures are the raw 64-byte
|
|
14
|
+
* Ed25519 signature (`crypto.sign(null, data, privateKey)` — Ed25519 needs
|
|
15
|
+
* no digest algorithm), also base64url-encoded.
|
|
16
|
+
*/
|
|
17
|
+
export interface DeviceKeyPair {
|
|
18
|
+
publicKeyBase64Url: string;
|
|
19
|
+
privateKey: KeyObject;
|
|
20
|
+
}
|
|
21
|
+
export declare function generateDeviceKeyPair(): DeviceKeyPair;
|
|
22
|
+
/** PKCS8 PEM — the on-disk storage form (0600 file under storeDir). OS keychain integration is deferred (tracked as a future roadmap item, not promised for any specific milestone). */
|
|
23
|
+
export declare function exportPrivateKeyPem(privateKey: KeyObject): string;
|
|
24
|
+
export declare function importPrivateKeyPem(pem: string): KeyObject;
|
|
25
|
+
/**
|
|
26
|
+
* S1 (GAP-004): the domain-separation prefix this device signs along with a
|
|
27
|
+
* challenge nonce, byte-identical to the server's own
|
|
28
|
+
* `NONCE_SIGNING_DOMAIN` (`packages/server/src/auth.ts`). The device key is a
|
|
29
|
+
* long-lived identity key that later planes will also sign structured
|
|
30
|
+
* messages with; tagging the domain is what stops a signature made for one of
|
|
31
|
+
* those from being replayable as a token-renewal credential.
|
|
32
|
+
*
|
|
33
|
+
* Not shared through a package: the two ends agree on a wire constant, and
|
|
34
|
+
* inventing a dependency between server and client to hold one string literal
|
|
35
|
+
* would couple them far harder than the literal does.
|
|
36
|
+
*/
|
|
37
|
+
export declare const NONCE_SIGNING_DOMAIN = "byok-nonce-v1\n";
|
|
38
|
+
/**
|
|
39
|
+
* Sign a challenge nonce with the device private key: the signed message is
|
|
40
|
+
* {@link NONCE_SIGNING_DOMAIN} followed by `nonce` (UTF-8), and the result is
|
|
41
|
+
* the raw 64-byte Ed25519 signature, base64url-encoded (protocol §6.2). A
|
|
42
|
+
* server on the domain-separated contract rejects the undomained form, so
|
|
43
|
+
* there is no variant of this that omits the prefix.
|
|
44
|
+
*/
|
|
45
|
+
export declare function signNonce(privateKey: KeyObject, nonce: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type DeviceProofEnvelopeV1 } from '@byok-sdk/core';
|
|
2
|
+
import type { DeviceStore } from './store';
|
|
3
|
+
export interface DeviceProofRequest {
|
|
4
|
+
readonly method: string;
|
|
5
|
+
/** Exact origin-relative path, including the query string when present. */
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly operation: string;
|
|
8
|
+
readonly resource: string;
|
|
9
|
+
readonly requestId: string;
|
|
10
|
+
readonly body: Uint8Array;
|
|
11
|
+
readonly issuedAt?: string;
|
|
12
|
+
readonly expiresAt?: string;
|
|
13
|
+
readonly nonce?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Local signing seam consumed by the truth client. It never exposes key bytes. */
|
|
16
|
+
export interface DeviceProofSigner {
|
|
17
|
+
sign(request: DeviceProofRequest): Promise<DeviceProofEnvelopeV1>;
|
|
18
|
+
}
|
|
19
|
+
export interface StoredDeviceProofSignerOptions {
|
|
20
|
+
readonly store: Pick<DeviceStore, 'load'>;
|
|
21
|
+
/** Explicit host configuration. Pairing/bearer state is never mined for tenant identity. */
|
|
22
|
+
readonly tenantId: string;
|
|
23
|
+
readonly productId: string;
|
|
24
|
+
readonly keyId: string;
|
|
25
|
+
readonly keyEpoch: number;
|
|
26
|
+
readonly clock?: () => Date;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Signs request-bound S6 proofs with the paired device identity key.
|
|
30
|
+
*
|
|
31
|
+
* The store is read for every signature rather than cached: clearing the paired
|
|
32
|
+
* record immediately removes local signing authority. Canonicalization is
|
|
33
|
+
* imported from `@byok-sdk/core`, the one frozen byte authority; this module only
|
|
34
|
+
* supplies the Node Ed25519 operation.
|
|
35
|
+
*/
|
|
36
|
+
export declare class StoredDeviceProofSigner implements DeviceProofSigner {
|
|
37
|
+
#private;
|
|
38
|
+
private readonly options;
|
|
39
|
+
constructor(options: StoredDeviceProofSignerOptions);
|
|
40
|
+
sign(request: DeviceProofRequest): Promise<DeviceProofEnvelopeV1>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M5: per-runtime environment allowlist for spawned agent child processes.
|
|
3
|
+
*
|
|
4
|
+
* Before this module existed, `task-runner.ts` built every task's
|
|
5
|
+
* `TaskContext.env` as `process.env` verbatim — the daemon's OWN full
|
|
6
|
+
* environment, unfiltered, handed to whichever runtime CLI (`pi`/`claude`/
|
|
7
|
+
* `codex`) `pickAdapter` selected. Any credential-shaped variable sitting in
|
|
8
|
+
* the daemon's own environment for a completely unrelated reason (an
|
|
9
|
+
* `AWS_SECRET_ACCESS_KEY`, `DATABASE_URL`, `GITHUB_TOKEN` set for the
|
|
10
|
+
* daemon's OWN deployment, nothing to do with any coding-agent runtime) was
|
|
11
|
+
* therefore inherited by every single spawned agent process — a
|
|
12
|
+
* credential-leak gap, not a deliberate design choice.
|
|
13
|
+
*
|
|
14
|
+
* {@link buildRuntimeEnv} replaces that blanket passthrough with an explicit
|
|
15
|
+
* allowlist, built fresh per task from three layers:
|
|
16
|
+
*
|
|
17
|
+
* 1. A small, always-included platform baseline ({@link BASE_PLATFORM_ALLOWLIST}
|
|
18
|
+
* / {@link WINDOWS_BASE_ALLOWLIST}) — the bare minimum any CLI needs to
|
|
19
|
+
* resolve its own binaries/libraries, find a home/temp directory, and
|
|
20
|
+
* behave sanely in a non-interactive shell.
|
|
21
|
+
* 2. Whatever ADDITIONAL names the *specific* runtime adapter about to be
|
|
22
|
+
* spawned declares it actually needs
|
|
23
|
+
* (`RuntimeAdapter.environmentRequirements()` — see `../types.ts`). An
|
|
24
|
+
* adapter that declares nothing at all (doesn't implement the optional
|
|
25
|
+
* method) gets the platform baseline ONLY — fail-closed by construction,
|
|
26
|
+
* not by an extra check here.
|
|
27
|
+
* 3. A per-device, per-runtime operator override (`DaemonConfig
|
|
28
|
+
* .runtimeEnvironment` — see `create-daemon.ts`) — a local escape hatch
|
|
29
|
+
* for a product/operator that knows it needs one more variable forwarded
|
|
30
|
+
* to one specific runtime on this one device.
|
|
31
|
+
*
|
|
32
|
+
* One hard, unconditional deny always wins over all three layers above,
|
|
33
|
+
* including the operator's own override: `BYOK_*`, this SDK's own
|
|
34
|
+
* control-plane variables, must never reach a spawned agent process — see
|
|
35
|
+
* {@link HARD_DENY_PATTERNS}.
|
|
36
|
+
*
|
|
37
|
+
* Every name in every list may be an exact match or a `*`-suffixed prefix
|
|
38
|
+
* (e.g. `'LC_*'` matches `LC_ALL`, `LC_CTYPE`, ...).
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* What one runtime adapter declares it needs beyond the always-included
|
|
42
|
+
* platform baseline. Returned from the optional
|
|
43
|
+
* `RuntimeAdapter.environmentRequirements()` method (`../types.ts`).
|
|
44
|
+
*/
|
|
45
|
+
export interface RuntimeEnvironmentRequirements {
|
|
46
|
+
/**
|
|
47
|
+
* Extra non-secret, config-discovery-shaped variable names this runtime's
|
|
48
|
+
* own CLI reads (e.g. a `<RUNTIME>_CONFIG_DIR`-style override) — anything
|
|
49
|
+
* that isn't itself a credential. Optional: most adapters need nothing
|
|
50
|
+
* beyond the platform baseline.
|
|
51
|
+
*/
|
|
52
|
+
baseNames?: readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* Credential/auth variable names this runtime's own CLI reads to
|
|
55
|
+
* authenticate (e.g. a provider API key). Kept as its own field (distinct
|
|
56
|
+
* from `baseNames`) so a product's own security review can reason about
|
|
57
|
+
* "what credential-shaped names does this runtime get" as a single,
|
|
58
|
+
* explicit list per adapter — see e.g. the pi adapter's
|
|
59
|
+
* `KNOWN_PROVIDER_ENV_VARS`.
|
|
60
|
+
*/
|
|
61
|
+
credentialNames?: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
/** Inputs to {@link buildRuntimeEnv}. */
|
|
64
|
+
export interface BuildRuntimeEnvOptions {
|
|
65
|
+
/**
|
|
66
|
+
* The daemon's own ambient environment (normally `process.env`). Never
|
|
67
|
+
* mutated — every returned variable is copied into a fresh object.
|
|
68
|
+
*/
|
|
69
|
+
ambient: NodeJS.ProcessEnv;
|
|
70
|
+
/**
|
|
71
|
+
* The selected runtime adapter's own declared requirements —
|
|
72
|
+
* `undefined` (no `environmentRequirements()` implementation on that
|
|
73
|
+
* adapter) means "platform baseline only," fail-closed.
|
|
74
|
+
*/
|
|
75
|
+
requirements?: RuntimeEnvironmentRequirements;
|
|
76
|
+
/**
|
|
77
|
+
* This device's own operator-configured escape hatch for this one runtime
|
|
78
|
+
* (`DaemonConfig.runtimeEnvironment?.[adapterId]?.allow`) — merged in like
|
|
79
|
+
* any other allowlist entry, still subject to the hard deny below.
|
|
80
|
+
*/
|
|
81
|
+
locallyAllowedNames?: readonly string[];
|
|
82
|
+
/**
|
|
83
|
+
* Test seam: which platform's extra base vars to include
|
|
84
|
+
* ({@link WINDOWS_BASE_ALLOWLIST} vs none) — defaults to `process.platform`
|
|
85
|
+
* so callers never have to think about it, while still letting a test
|
|
86
|
+
* exercise the win32 branch deterministically on any host OS.
|
|
87
|
+
*/
|
|
88
|
+
platform?: NodeJS.Platform;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build the environment one specific runtime's spawned child process should
|
|
92
|
+
* actually receive — a fresh object, never `options.ambient` itself and
|
|
93
|
+
* never mutated in place. See this module's own doc comment for the full
|
|
94
|
+
* allow/deny model.
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildRuntimeEnv(options: BuildRuntimeEnvOptions): Record<string, string>;
|