@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ancienttwo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @byok-sdk/client
|
|
2
|
+
|
|
3
|
+
The local BYOK daemon. It pairs a device, durably journals tasks, connects over
|
|
4
|
+
WebSocket or long poll, dispatches to local Claude Code, Codex, or pi adapters,
|
|
5
|
+
and exposes authenticated local diagnostics/control commands.
|
|
6
|
+
|
|
7
|
+
The package installs `byok-agent` and `byok-approval-mcp` binaries. Provider
|
|
8
|
+
credentials are not read by the dispatch plane; `@byok-sdk/keys` is separate.
|
|
9
|
+
|
|
10
|
+
MIT licensed. Node.js 20 or newer; SQLite-backed journaling requires a runtime
|
|
11
|
+
with `node:sqlite` support.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { TaskOfferPayload } from '@byok-sdk/protocol';
|
|
2
|
+
import { type RuntimeAdapter, type RuntimeCapabilities, type RuntimeDetectResult, type RuntimeEnvironmentRequirements, type Session, type TaskContext } from '../../types';
|
|
3
|
+
import { type ResolvedBin } from './resolve-bin';
|
|
4
|
+
import { type ResolvedApprovalMcpBin } from './resolve-approval-mcp-bin';
|
|
5
|
+
import { type SpawnFn } from './process-client';
|
|
6
|
+
export interface ClaudeAdapterOptions {
|
|
7
|
+
/** Override bin resolution — tests substitute the fake-claude fixture script. */
|
|
8
|
+
resolveBin?: () => ResolvedBin;
|
|
9
|
+
/** Override process spawning — tests substitute a fake spawn. */
|
|
10
|
+
spawnFn?: SpawnFn;
|
|
11
|
+
/** M4 Phase 3: override `byok-approval-mcp` bin resolution — tests substitute a fixture script instead of computing a real dist path. Mirrors `resolveBin` above. */
|
|
12
|
+
resolveApprovalMcpBin?: () => ResolvedApprovalMcpBin;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Claude Code runtime adapter (`claude -p --input-format stream-json
|
|
16
|
+
* --output-format stream-json`) — the M2-a counterpart to `../pi/pi-adapter.ts`.
|
|
17
|
+
* Every behavioral claim in this file's own doc comments and its sibling
|
|
18
|
+
* modules (`events.ts`, `permission-mapping.ts`, `process-client.ts`) was
|
|
19
|
+
* empirically reproduced against the real installed `claude` 2.1.212 binary
|
|
20
|
+
* on a logged-in machine (per this task's own "do NOT trust docs over the
|
|
21
|
+
* real binary" mandate — `claude --help` was actively wrong/misleading for
|
|
22
|
+
* `--allowedTools`, see `permission-mapping.ts`) — not inferred from
|
|
23
|
+
* training-data recall or the Claude API/Agent-SDK docs, which describe a
|
|
24
|
+
* DIFFERENT product surface (the Messages API, not this CLI's headless
|
|
25
|
+
* wire format).
|
|
26
|
+
*
|
|
27
|
+
* ## The central finding: claude's headless approval model has no
|
|
28
|
+
* `needs_approval` pause, at all
|
|
29
|
+
*
|
|
30
|
+
* This is the first real use of the `needs_approval` /
|
|
31
|
+
* `Session.resolveApproval` seam any adapter in this codebase has
|
|
32
|
+
* implemented (pi never emits `needs_approval` — see `PiSession
|
|
33
|
+
* .resolveApproval`'s own doc comment) — so this finding directly informs
|
|
34
|
+
* the M2-c protocol-freeze decision on that seam.
|
|
35
|
+
*
|
|
36
|
+
* Empirically (see the M2-a report for the full live-capture evidence):
|
|
37
|
+
* spawning `claude -p` **non-interactively** with a tool call that would
|
|
38
|
+
* normally prompt a human is resolved **synchronously, before the turn
|
|
39
|
+
* continues** — there is no pause, no wait, no later resumption point:
|
|
40
|
+
*
|
|
41
|
+
* - Under `--permission-mode default` (or no flag at all — headless has no
|
|
42
|
+
* TTY to interactively ask), an unapproved tool call is immediately
|
|
43
|
+
* AUTO-DENIED with a synthesized `tool_result`
|
|
44
|
+
* (`"Claude requested permissions to write to <path>, but you haven't
|
|
45
|
+
* granted it yet."`, `is_error:true`) and the run continues normally to
|
|
46
|
+
* its own `result` frame — no hang, and nothing this adapter could ever
|
|
47
|
+
* resume later even if it wanted to.
|
|
48
|
+
* - Under a permissive `--permission-mode` (`acceptEdits`/`bypassPermissions`),
|
|
49
|
+
* the call is auto-GRANTED, again synchronously, again with nothing to
|
|
50
|
+
* pause on.
|
|
51
|
+
*
|
|
52
|
+
* There is consequently no claude stream-json frame this adapter could
|
|
53
|
+
* ever map to the protocol's `needs_approval` `AgentEvent` — the decision
|
|
54
|
+
* is always already made by the time any frame reaches this adapter at
|
|
55
|
+
* all. `resolveApproval()` below throws a descriptive error rather than
|
|
56
|
+
* silently no-op'ing, mirroring `PiSession.resolveApproval`'s own
|
|
57
|
+
* documented reasoning exactly: a caller that ever receives
|
|
58
|
+
* `task.approve`/`task.reject` for one of this adapter's tasks implies
|
|
59
|
+
* something upstream expected approval support this adapter genuinely does
|
|
60
|
+
* not have.
|
|
61
|
+
*
|
|
62
|
+
* `PermissionPolicy.mode: 'confirm'` — the policy mode whose whole point is
|
|
63
|
+
* "ask a human, then proceed" — was therefore rejected outright at
|
|
64
|
+
* `start()` through M2/M3 (fail-closed, see `permission-mapping.ts`), never
|
|
65
|
+
* silently downgraded to auto-accept or auto-deny.
|
|
66
|
+
*
|
|
67
|
+
* ## M4 Phase 3 update: a genuine out-of-band pause DOES exist — it is
|
|
68
|
+
* just invisible to everything written above
|
|
69
|
+
*
|
|
70
|
+
* `--permission-prompt-tool` (a DIFFERENT flag from `--permission-mode`,
|
|
71
|
+
* undocumented in `claude --help`'s own output on the installed 2.1.216
|
|
72
|
+
* binary but empirically confirmed accepted — an unrecognized flag is
|
|
73
|
+
* rejected outright with `error: unknown option`, this one is not) makes
|
|
74
|
+
* claude block a turn on a real MCP round-trip to a server it spawns
|
|
75
|
+
* itself, waiting for that server to answer allow/deny before continuing —
|
|
76
|
+
* genuinely pausing, for real wall-clock time (live-verified: an instant
|
|
77
|
+
* allow/deny, AND a deliberate multi-second delayed answer, both worked
|
|
78
|
+
* identically; only a permission-prompt-tool call that never answers AT
|
|
79
|
+
* ALL was found to make claude abandon the turn on its own, after roughly
|
|
80
|
+
* 1.5s — never actually reachable by this design, since the bundled
|
|
81
|
+
* `bin/byok-approval-mcp.ts` always eventually answers within its own
|
|
82
|
+
* configured ceiling).
|
|
83
|
+
*
|
|
84
|
+
* Everything above this section remains true and is NOT superseded by
|
|
85
|
+
* this: claude's own stream-json output still emits nothing while this
|
|
86
|
+
* pause is in progress — the gap between a `tool_use` frame and its
|
|
87
|
+
* `tool_result` is indistinguishable from ordinary model latency on the
|
|
88
|
+
* wire, and there is still no `needs_approval`-shaped frame this adapter's
|
|
89
|
+
* event mapper could ever produce. The pause is real, but it is invisible
|
|
90
|
+
* to `ClaudeSession.events` and to `task-runner.ts`'s `pump()` entirely —
|
|
91
|
+
* it is only ever observable from OUTSIDE this adapter's own process, by
|
|
92
|
+
* the separate MCP-server child process claude itself spawns. This is why
|
|
93
|
+
* `confirm` mode's daemon-side wiring (`task-runner.ts`'s `requestApproval`,
|
|
94
|
+
* `types.ts`'s `ApprovalChannel`) is driven from the control socket, not
|
|
95
|
+
* from any `AgentEvent` — see those files' own doc comments for the full
|
|
96
|
+
* design this finding drove. `confirm` is now SUPPORTED (see
|
|
97
|
+
* `permission-mapping.ts` and `resolveApproval()` below), still fail-closed
|
|
98
|
+
* whenever no approval channel was actually wired up for this session.
|
|
99
|
+
*
|
|
100
|
+
* ## Steering was also found unsupported (a second, related finding)
|
|
101
|
+
*
|
|
102
|
+
* Live-probed via a persistent `--input-format stream-json` process:
|
|
103
|
+
* writing a second `{"type":"user",...}` message to stdin WHILE a turn is
|
|
104
|
+
* still generating does NOT redirect that in-flight turn — it QUEUES as a
|
|
105
|
+
* separate, subsequent turn, processed only after the first one reaches
|
|
106
|
+
* its own `result`. This is genuinely useful for `followUp()` (a new turn
|
|
107
|
+
* "after [the session] has gone idle" — exactly the queued-after-result
|
|
108
|
+
* case), but it is not what `Session.steer`'s "inject steering text into a
|
|
109
|
+
* running turn (mid-stream)" contract promises. `capabilities().steer` is
|
|
110
|
+
* therefore `false`, and `steer()` throws rather than silently behaving
|
|
111
|
+
* like a queued follow-up under a name that implies live redirection.
|
|
112
|
+
*/
|
|
113
|
+
export declare class ClaudeAdapter implements RuntimeAdapter {
|
|
114
|
+
private readonly options;
|
|
115
|
+
readonly id = "claude";
|
|
116
|
+
constructor(options?: ClaudeAdapterOptions);
|
|
117
|
+
detect(): Promise<RuntimeDetectResult>;
|
|
118
|
+
capabilities(): RuntimeCapabilities;
|
|
119
|
+
/**
|
|
120
|
+
* M5: deliberate product-boundary decision, not an oversight — byok's
|
|
121
|
+
* current ToS posture for claude is login-state-only (`claude auth
|
|
122
|
+
* login`'s own OAuth session — see `probeAuthPresent` below), so this
|
|
123
|
+
* adapter declares NO credential env vars at all; env-based API-key
|
|
124
|
+
* passthrough for claude is a separate, still-pending product decision.
|
|
125
|
+
* A product that genuinely needs it can opt in locally per-device via
|
|
126
|
+
* `DaemonConfig.runtimeEnvironment.claude.allow` (`create-daemon.ts`).
|
|
127
|
+
* `baseNames` is empty too: nothing in this adapter reads a
|
|
128
|
+
* claude-specific config-discovery variable (e.g. `CLAUDE_CONFIG_DIR`)
|
|
129
|
+
* today — if a future version of this adapter starts reading one, it
|
|
130
|
+
* belongs here, not left to rely on the platform baseline alone.
|
|
131
|
+
*/
|
|
132
|
+
environmentRequirements(): RuntimeEnvironmentRequirements;
|
|
133
|
+
start(task: TaskOfferPayload, ctx: TaskContext): Promise<Session>;
|
|
134
|
+
/**
|
|
135
|
+
* `claude auth status --json` is claude's OWN non-secret login-state
|
|
136
|
+
* signal (see the credential-isolation rule on `RuntimeAdapter` in
|
|
137
|
+
* `../../types.ts`) — empirically confirmed live on this logged-in
|
|
138
|
+
* machine to report `{"loggedIn":true,"authMethod":"claude.ai",
|
|
139
|
+
* "apiProvider":"firstParty","email":"...","orgId":"...","orgName":"...",
|
|
140
|
+
* "subscriptionType":"max"}`, with no token/key material anywhere in it.
|
|
141
|
+
* This spawns the binary and parses ONLY its own reported status — it
|
|
142
|
+
* never reads `~/.claude` or any credential file itself, matching pi's
|
|
143
|
+
* `authPresent` computation being limited to environment-variable
|
|
144
|
+
* *names* (`../pi/pi-adapter.ts`'s `KNOWN_PROVIDER_ENV_VARS`), just via
|
|
145
|
+
* claude's own equivalent non-secret probe instead (claude's auth is
|
|
146
|
+
* OAuth-session-based via `claude auth login`, not primarily an env var,
|
|
147
|
+
* so pi's env-var-presence approach doesn't apply here the same way).
|
|
148
|
+
* A failed/unparseable probe (binary present but not logged in, a future
|
|
149
|
+
* claude release changing this output shape, etc.) fails closed to
|
|
150
|
+
* `false` — this never affects `present`, which is solely about whether
|
|
151
|
+
* `--version` itself succeeded.
|
|
152
|
+
*/
|
|
153
|
+
private probeAuthPresent;
|
|
154
|
+
private resolveBin;
|
|
155
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AgentEvent } from '@byok-sdk/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* A raw parsed line from `claude --output-format stream-json`. Shapes vary
|
|
4
|
+
* a lot by `type` (and, for `system`, by `subtype`) — see the doc comments
|
|
5
|
+
* on the individual mapping functions below for the concrete shapes this
|
|
6
|
+
* was empirically captured against. Kept as a loose bag rather than a full
|
|
7
|
+
* discriminated union for the same reason pi's `PiRpcMessage` is: this
|
|
8
|
+
* module only needs a handful of fields off of each frame.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClaudeStreamMessage {
|
|
11
|
+
type: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Cross-message correlation state a single {@link ClaudeSession} (in
|
|
16
|
+
* `../claude-adapter.ts`) owns for its whole lifetime.
|
|
17
|
+
*
|
|
18
|
+
* Unlike pi's `tool_execution_start`/`tool_execution_end` frames (which
|
|
19
|
+
* both carry `toolName` directly, so `../pi/events.ts` can stay a pure,
|
|
20
|
+
* stateless function), claude's Messages-API-shaped transcript splits a
|
|
21
|
+
* tool call across two DIFFERENT frame types: the `assistant` frame's
|
|
22
|
+
* `tool_use` content block carries `{id, name, input}`, but the later
|
|
23
|
+
* `user` frame's `tool_result` content block carries only `{tool_use_id,
|
|
24
|
+
* content, is_error}` — no tool name at all. Since this protocol's own
|
|
25
|
+
* `AgentEvent` schema requires `tool_result.tool: string`, this mapper has
|
|
26
|
+
* no choice but to remember `tool_use_id -> name` from the `tool_use` block
|
|
27
|
+
* and look it up when the matching `tool_result` arrives later. This is a
|
|
28
|
+
* genuine, disclosed structural difference from pi, not an arbitrary
|
|
29
|
+
* design choice — see the M2-a report for the full reasoning.
|
|
30
|
+
*/
|
|
31
|
+
export interface ToolUseCorrelation {
|
|
32
|
+
readonly toolNameByUseId: Map<string, string>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createToolUseCorrelation(): ToolUseCorrelation;
|
|
35
|
+
export interface MapClaudeMessageOptions {
|
|
36
|
+
/** `ctx.workspaceDir` for the task — used only to compute a workspace-relative `name` for a possible `artifact` AgentEvent (see `tryBuildArtifactEvent`'s doc comment). */
|
|
37
|
+
workspaceDir: string;
|
|
38
|
+
}
|
|
39
|
+
export interface MapClaudeMessageResult {
|
|
40
|
+
events: AgentEvent[];
|
|
41
|
+
/**
|
|
42
|
+
* Set when this exact frame (or, for `assistant`/`user` frames, one
|
|
43
|
+
* content block inside it) was genuinely unrecognized — a frame/subtype/
|
|
44
|
+
* block-type this adapter has never been told to expect — as opposed to
|
|
45
|
+
* routine bookkeeping this mapper deliberately ignores (see
|
|
46
|
+
* `ROUTINE_CLAUDE_SYSTEM_SUBTYPES` and the `thinking`/`redacted_thinking`
|
|
47
|
+
* cases below). The caller (`ClaudeSession`'s event iterator) is
|
|
48
|
+
* responsible for actually recording it (via
|
|
49
|
+
* `ClaudeProcessClient.recordUnmappedFrame`) — mirrors pi's
|
|
50
|
+
* `ROUTINE_PI_EVENT_TYPES` check living in `PiSession`'s iterator rather
|
|
51
|
+
* than inside `mapPiMessageToAgentEvent` itself. At most one label per
|
|
52
|
+
* call even if a frame has several unmapped things in it — sufficient
|
|
53
|
+
* for the "did this regress" self-diagnosing purpose this exists for,
|
|
54
|
+
* without needing a list.
|
|
55
|
+
*/
|
|
56
|
+
unmappedLabel?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `system` frame subtypes empirically observed on real stream-json output
|
|
60
|
+
* from the installed claude 2.1.212 binary that carry no `AgentEvent`
|
|
61
|
+
* equivalent — routine bookkeeping, deliberately ignored:
|
|
62
|
+
*
|
|
63
|
+
* - `init`: session/turn start. Carries `session_id`, `tools`, `cwd`,
|
|
64
|
+
* `permissionMode`, etc. `session_id` specifically is NOT read here —
|
|
65
|
+
* `ClaudeProcessClient.waitForInit()` (`process-client.ts`) captures it
|
|
66
|
+
* directly off the raw line as part of this adapter's own
|
|
67
|
+
* `start()`/sessionRef bookkeeping, since it's needed before any
|
|
68
|
+
* `AgentEvent` mapping is even relevant.
|
|
69
|
+
* - `hook_started` / `hook_response`: fired when the user's own Claude Code
|
|
70
|
+
* installation has configured lifecycle hooks (e.g. `SessionStart`) —
|
|
71
|
+
* machine/config-specific, not part of this protocol's surface at all.
|
|
72
|
+
* - `thinking_tokens`: periodic token-count-estimate bookkeeping emitted
|
|
73
|
+
* while the model is reasoning; no user-visible content.
|
|
74
|
+
*
|
|
75
|
+
* A `system` frame whose `subtype` is NOT in this set is treated as
|
|
76
|
+
* genuinely unmapped (see `mapClaudeMessageToAgentEvents`'s `system` case)
|
|
77
|
+
* rather than silently folded into "system frames are always routine" —
|
|
78
|
+
* this is deliberately finer-grained than lumping the whole `system` type
|
|
79
|
+
* together, so a future/unobserved subtype (e.g. something compaction- or
|
|
80
|
+
* budget-related) shows up as a one-time warning instead of disappearing
|
|
81
|
+
* the way the pi adapter's own root-cause hang (a real settle event with no
|
|
82
|
+
* mapping, silently swallowed) did before that bug was found.
|
|
83
|
+
*/
|
|
84
|
+
export declare const ROUTINE_CLAUDE_SYSTEM_SUBTYPES: ReadonlySet<string>;
|
|
85
|
+
/**
|
|
86
|
+
* Map one raw claude stream-json line to zero or more normalized
|
|
87
|
+
* `AgentEvent`s (a single `user` frame can produce two: `tool_result` plus
|
|
88
|
+
* a derived `artifact`). See the per-`type` mapping functions above for the
|
|
89
|
+
* concrete, empirically-captured shapes each branch handles.
|
|
90
|
+
*/
|
|
91
|
+
export declare function mapClaudeMessageToAgentEvents(msg: ClaudeStreamMessage, correlation: ToolUseCorrelation, options: MapClaudeMessageOptions): MapClaudeMessageResult;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { PermissionPolicy } from '@byok-sdk/protocol';
|
|
2
|
+
export interface ClaudePermissionMapping {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
/** CLI args to append to `claude -p ...`. Only meaningful when `ok` is true. */
|
|
5
|
+
args: string[];
|
|
6
|
+
/** Present when `ok` is false. */
|
|
7
|
+
reason?: string;
|
|
8
|
+
/**
|
|
9
|
+
* M4 Phase 3: set only for `policy.mode === 'confirm'` — tells
|
|
10
|
+
* `claude-adapter.ts`'s `start()` to additionally spin up the out-of-band
|
|
11
|
+
* approval channel (write the temp `--mcp-config` file pointing at
|
|
12
|
+
* `bin/byok-approval-mcp.ts`, then append `--permission-prompt-tool`) on
|
|
13
|
+
* top of the base `args` returned here. Kept out of `args` itself because
|
|
14
|
+
* generating that config is a real filesystem side effect (a temp
|
|
15
|
+
* directory + file write) that has no business happening inside this
|
|
16
|
+
* otherwise-pure, I/O-free mapping function — see `claude-adapter.ts`'s
|
|
17
|
+
* `start()` for where it actually happens.
|
|
18
|
+
*/
|
|
19
|
+
needsApprovalMcp?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Map an effective {@link PermissionPolicy} to `claude -p ...` CLI args,
|
|
23
|
+
* fail-closed. Empirically grounded against the real installed `claude`
|
|
24
|
+
* 2.1.212 binary (see the M2-a report) — every claim below was reproduced
|
|
25
|
+
* live, not inferred from `--help` text or training-data recall (`--help`
|
|
26
|
+
* text alone was actively misleading in at least one case: `--allowedTools`
|
|
27
|
+
* reads like a hard allowlist but is NOT one — see the finding below).
|
|
28
|
+
*
|
|
29
|
+
* ## Two DIFFERENT, easily-confused tool-control flags (the central finding)
|
|
30
|
+
*
|
|
31
|
+
* `claude --help` documents two separate flag families that look
|
|
32
|
+
* interchangeable but are not:
|
|
33
|
+
*
|
|
34
|
+
* - `--allowedTools` / `--disallowedTools`: a PERMISSION pre-grant/deny
|
|
35
|
+
* list. Empirically, this only affects whether a tool call needs an
|
|
36
|
+
* interactive prompt — it does NOT reliably restrict what the model can
|
|
37
|
+
* do once a broadly-permissive `--permission-mode` (acceptEdits,
|
|
38
|
+
* bypassPermissions, dontAsk) is also in effect. Confirmed two ways: (a)
|
|
39
|
+
* `--permission-mode acceptEdits --allowedTools Read` still let a Write
|
|
40
|
+
* call succeed (zero `permission_denials`) — the allowlist was silently
|
|
41
|
+
* ignored once the broad grant was in effect; (b) `--permission-mode
|
|
42
|
+
* acceptEdits --disallowedTools Write` correctly blocked the *named*
|
|
43
|
+
* `Write` tool call (`<tool_use_error>Error: No such tool available:
|
|
44
|
+
* Write...`), but the model then simply used `Bash` instead (still
|
|
45
|
+
* enabled) to write the same file — `(Bash completed with no output)`,
|
|
46
|
+
* file created anyway. A single denied tool name is not a security
|
|
47
|
+
* boundary when a general-purpose Bash tool remains available.
|
|
48
|
+
* - `--tools`: REPLACES the entire active built-in tool set (pi's own
|
|
49
|
+
* `--tools` flag works the same way — this is the one place claude and pi
|
|
50
|
+
* share an identical convention). Confirmed: `--tools Read` shrinks
|
|
51
|
+
* `system/init`'s reported `tools` array to just `['Read', ...MCP
|
|
52
|
+
* tools]`; the model then cannot even attempt Write (no tool definition
|
|
53
|
+
* to call), regardless of `--permission-mode`. `--tools ""` disables
|
|
54
|
+
* every built-in tool outright (confirmed: only the always-on MCP tool
|
|
55
|
+
* remained). This is the ONLY mechanism this mapper trusts to actually
|
|
56
|
+
* restrict the model's capability surface — never `--allowedTools`/
|
|
57
|
+
* `--disallowedTools` for that purpose.
|
|
58
|
+
*
|
|
59
|
+
* ## Mode mapping
|
|
60
|
+
*
|
|
61
|
+
* - `auto` (no `allowTools`): `--permission-mode acceptEdits`. Empirically
|
|
62
|
+
* confirmed to auto-accept BOTH file edits (Write) and Bash execution
|
|
63
|
+
* with zero `permission_denials` — broader than the interactive-mode
|
|
64
|
+
* folklore that acceptEdits only covers file edits. Verified for a
|
|
65
|
+
* benign `echo ... > file` Bash command specifically; not exhaustively
|
|
66
|
+
* verified across every possible Bash command shape.
|
|
67
|
+
* - `auto` with `allowTools` (no `denyTools`): adds `--tools
|
|
68
|
+
* <allowTools.join(',')>` (the replacive flag) on top of `acceptEdits`,
|
|
69
|
+
* mirroring pi's own `allowTools`-only branch.
|
|
70
|
+
* - `readonly`: ALWAYS `--permission-mode default` (never acceptEdits/
|
|
71
|
+
* bypassPermissions/dontAsk — see the finding above: a permissive mode
|
|
72
|
+
* defeats any restriction) plus `--tools
|
|
73
|
+
* <intersect(allowTools ?? READONLY_TOOLS, READONLY_TOOLS) - denyTools>`.
|
|
74
|
+
* An empty resulting set emits `--tools ""` explicitly — never an absent
|
|
75
|
+
* `--tools` flag, which would default to claude's full active set and
|
|
76
|
+
* silently widen a readonly request (the exact class of bug pi's own
|
|
77
|
+
* `--no-tools` fallback exists to prevent).
|
|
78
|
+
* - `plan`: `--permission-mode plan`. Empirically confirmed to never
|
|
79
|
+
* execute the requested mutating tool call against its real target — the
|
|
80
|
+
* model instead writes a plan document and stops. **Caveat, flagged for
|
|
81
|
+
* the M2-c freeze decision, not silently hidden**: it writes that plan
|
|
82
|
+
* file to `~/.claude/plans/<slug>.md` — the real user's home directory,
|
|
83
|
+
* OUTSIDE `ctx.workspaceDir` — unconditionally, regardless of cwd. This
|
|
84
|
+
* is a genuine, confirmed workspace-confinement gap specific to plan
|
|
85
|
+
* mode's own bookkeeping (the path is fixed/product-owned by Claude Code
|
|
86
|
+
* itself, not attacker/model-directed, and no destructive action runs
|
|
87
|
+
* against the actual task target) — mapped as supported rather than
|
|
88
|
+
* failed-closed because refusing would make an entire policy mode whose
|
|
89
|
+
* name and semantics match this protocol's own `plan` mode 1:1
|
|
90
|
+
* completely unusable over a relatively minor, fixed-path side effect,
|
|
91
|
+
* but this is a judgment call for a human to weigh in on, not a fact.
|
|
92
|
+
* - `confirm`: SUPPORTED as of M4 Phase 3 — `--permission-mode default`
|
|
93
|
+
* (deny-by-default baseline, same as `readonly`'s own choice above) plus
|
|
94
|
+
* `allowTools`/`denyTools` mapped exactly like `auto` does (finding F2,
|
|
95
|
+
* fixed same session as the confirm-mode support itself first shipped —
|
|
96
|
+
* see the function body below): an explicit `--tools <allowTools>` when
|
|
97
|
+
* only `allowTools` is set, and a fail-closed refusal whenever `denyTools`
|
|
98
|
+
* is non-empty, since confirm — like `auto`/`plan` and unlike
|
|
99
|
+
* `readonly` — has no bounded, known-safe base tool list to subtract
|
|
100
|
+
* from; the only trustworthy restriction mechanism (`--tools`) is
|
|
101
|
+
* replacive, not subtractive. This composes with, rather than replaces,
|
|
102
|
+
* `--permission-prompt-tool` pointed at a small bundled MCP server
|
|
103
|
+
* (`bin/byok-approval-mcp.ts`) that forwards the pending permission
|
|
104
|
+
* decision to this device's own daemon over its control socket and blocks
|
|
105
|
+
* until a human (or the daemon's own timeout) resolves it. This directly
|
|
106
|
+
* supersedes the M2-a finding that `confirm` was inexpressible: that
|
|
107
|
+
* finding was empirically true for `--permission-mode` ALONE (every
|
|
108
|
+
* decision resolved synchronously, no pause) — `--permission-prompt-tool`
|
|
109
|
+
* is a DIFFERENT flag, undocumented in `claude --help`'s output on the
|
|
110
|
+
* installed 2.1.216 binary (confirmed accepted anyway — an unrecognized
|
|
111
|
+
* flag is rejected outright with `error: unknown option`, this one is
|
|
112
|
+
* not), that genuinely pauses the turn on a real MCP round-trip while
|
|
113
|
+
* claude waits for an answer (M4 Phase 3 STEP 0: live-verified allow,
|
|
114
|
+
* deny, and multi-second-delayed-then-allow, against the real installed
|
|
115
|
+
* binary — see `../../bin/byok-approval-mcp.ts`'s own doc comment for the
|
|
116
|
+
* full empirical writeup, including the one caveat found: a permission-
|
|
117
|
+
* prompt-tool call that NEVER answers at all triggers claude's own
|
|
118
|
+
* internal abandonment of the turn after roughly 1.5s — never actually hit
|
|
119
|
+
* by this design, since `byok-approval-mcp` always eventually answers
|
|
120
|
+
* within its own configured ceiling, but disclosed rather than silently
|
|
121
|
+
* assumed away).
|
|
122
|
+
* - `denyTools` non-empty under `auto`: FAILS CLOSED. Given the
|
|
123
|
+
* `--allowedTools`/`--disallowedTools`-under-a-permissive-mode escape
|
|
124
|
+
* hatch above, the only mechanism this mapper trusts (`--tools`) is
|
|
125
|
+
* REPLACIVE, not subtractive — pi can resolve `denyTools` to an
|
|
126
|
+
* equivalent allowlist because pi's own default active tool set is
|
|
127
|
+
* fixed and known from its installed source; claude's active tool
|
|
128
|
+
* surface is NOT reliably known ahead of time (empirically, this exact
|
|
129
|
+
* dev machine's own installed build exposes a bespoke, non-vanilla tool
|
|
130
|
+
* set — see `READONLY_TOOLS`'s doc comment), so there is no reliable
|
|
131
|
+
* "default set minus these" this mapper can construct. Refusing is the
|
|
132
|
+
* fail-closed choice over guessing a set that might not match reality.
|
|
133
|
+
*
|
|
134
|
+
* `network: false` fails closed for the same reason as pi: no verified
|
|
135
|
+
* network sandbox exists for claude's Bash tool either (`claude --help`
|
|
136
|
+
* exposes no network/sandbox flag at all) — this was not independently
|
|
137
|
+
* re-verified against real network traffic the way the tool-restriction
|
|
138
|
+
* findings above were (doing so would require an actual network probe this
|
|
139
|
+
* task didn't run), but is the same conservative, precedent-consistent
|
|
140
|
+
* default pi already applies for an unverifiable constraint.
|
|
141
|
+
*/
|
|
142
|
+
export declare function mapPermissionPolicyToClaudeArgs(policy: PermissionPolicy): ClaudePermissionMapping;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import type { ClaudeStreamMessage } from './events';
|
|
3
|
+
export type SpawnFn = typeof spawn;
|
|
4
|
+
export interface ClaudeProcessClientOptions {
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
cwd: string;
|
|
8
|
+
env: NodeJS.ProcessEnv;
|
|
9
|
+
spawnFn?: SpawnFn;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* NDJSON process transport for `claude -p --input-format stream-json
|
|
13
|
+
* --output-format stream-json`.
|
|
14
|
+
*
|
|
15
|
+
* Structurally simpler than pi's `PiRpcClient` in one real way, and
|
|
16
|
+
* different (not simpler) in another:
|
|
17
|
+
*
|
|
18
|
+
* - No request/response correlation. pi's RPC mode replies to each command
|
|
19
|
+
* with a `{type:"response", id, success, ...}` — claude's stream-json has
|
|
20
|
+
* no such acknowledgement at all; writing a `{"type":"user",...}` line
|
|
21
|
+
* just starts (or queues) a turn, and the ONLY confirmation is the
|
|
22
|
+
* ordinary event stream itself (starting with a `system/init` frame).
|
|
23
|
+
* There is therefore no `pending` id->resolver map here.
|
|
24
|
+
* - `waitForInit()` exists specifically to compensate for that missing
|
|
25
|
+
* ack: pi's `start()` fails fast because a bad flag/auth error rejects
|
|
26
|
+
* the pending `send()` promise for the first command. Claude's own
|
|
27
|
+
* `AsyncQueue.end()` (used for the `events` stream) is a CLEAN,
|
|
28
|
+
* non-throwing end — a process that crashes before ever emitting a line
|
|
29
|
+
* would otherwise look, from the async-iteration protocol alone, exactly
|
|
30
|
+
* like a session that legitimately produced zero events, silently
|
|
31
|
+
* swallowing the real failure. `waitForInit()` is a dedicated promise
|
|
32
|
+
* that resolves with the real `session_id` once claude's own
|
|
33
|
+
* `system/init` frame arrives, or rejects with the same enriched
|
|
34
|
+
* exit-error `events` would otherwise swallow — this is what lets
|
|
35
|
+
* `ClaudeAdapter.start()` fail loudly and immediately for a bad
|
|
36
|
+
* `--resume` target etc., mirroring pi's own fail-fast contract with a
|
|
37
|
+
* mechanism suited to claude's ack-less protocol instead of copying pi's
|
|
38
|
+
* request/response one verbatim.
|
|
39
|
+
*
|
|
40
|
+
* Framing, stderr-ring, and unmapped-frame-tally-in-exit-error all mirror
|
|
41
|
+
* `../pi/rpc-client.ts`'s already-proven design directly (LF-delimited
|
|
42
|
+
* JSONL, `node:readline` avoided for the same U+2028/U+2029 reason pi's
|
|
43
|
+
* doc comment explains, `close` not `exit` for the same complete-stderr
|
|
44
|
+
* guarantee) — these are generic, sound patterns, not pi-specific logic,
|
|
45
|
+
* so re-implementing them independently here (rather than importing from
|
|
46
|
+
* `../pi/`) keeps this adapter fully self-contained, matching this repo's
|
|
47
|
+
* existing per-adapter isolation.
|
|
48
|
+
*/
|
|
49
|
+
export declare class ClaudeProcessClient {
|
|
50
|
+
private readonly child;
|
|
51
|
+
private buffer;
|
|
52
|
+
private readonly eventQueue;
|
|
53
|
+
private closed;
|
|
54
|
+
private exitError;
|
|
55
|
+
private readonly stderrRing;
|
|
56
|
+
private readonly unmappedFrameCounts;
|
|
57
|
+
private sessionId;
|
|
58
|
+
private initWaiter;
|
|
59
|
+
constructor(options: ClaudeProcessClientOptions);
|
|
60
|
+
/**
|
|
61
|
+
* Write a new user turn onto stdin (`--input-format stream-json`'s wire
|
|
62
|
+
* shape: `{"type":"user","message":{"role":"user","content":[{"type":
|
|
63
|
+
* "text","text":...}]}}`). Used identically for the very first turn
|
|
64
|
+
* (`ClaudeAdapter.start()`) and any later same-session turn
|
|
65
|
+
* (`ClaudeSession.followUp()`) — empirically confirmed live that claude
|
|
66
|
+
* keeps a `--input-format stream-json` process alive across multiple
|
|
67
|
+
* sequential turns on ONE persistent process/session (same `session_id`
|
|
68
|
+
* reported on each turn's own `system/init` and `result` frames), only
|
|
69
|
+
* exiting when stdin is closed or the process is killed. This is the
|
|
70
|
+
* mechanism `followUp()` relies on instead of spawning a fresh
|
|
71
|
+
* `--resume`'d process per follow-up.
|
|
72
|
+
*/
|
|
73
|
+
writeUserMessage(text: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Resolves with claude's own `session_id` once its `system/init` frame
|
|
76
|
+
* arrives (see this class's doc comment for why this exists at all).
|
|
77
|
+
* Idempotent: once resolved, further calls resolve immediately with the
|
|
78
|
+
* same id; if the process already closed before init ever arrived,
|
|
79
|
+
* every call rejects with that same exit error.
|
|
80
|
+
*/
|
|
81
|
+
waitForInit(): Promise<string>;
|
|
82
|
+
/** Every parsed stream-json line — `system/init` is consumed internally (see `waitForInit`) but is also forwarded here like any other frame, so routine-frame accounting in `ClaudeSession`'s mapper stays uniform. */
|
|
83
|
+
get events(): AsyncIterable<ClaudeStreamMessage>;
|
|
84
|
+
/**
|
|
85
|
+
* Record a claude stream-json frame/subtype/content-block label that
|
|
86
|
+
* `ClaudeSession`'s event iterator (`../claude-adapter.ts`) decided has
|
|
87
|
+
* no `AgentEvent` mapping and isn't routine bookkeeping (see
|
|
88
|
+
* `events.ts`'s `MapClaudeMessageResult.unmappedLabel` doc comment) —
|
|
89
|
+
* i.e. genuinely unexpected traffic. Mirrors pi's
|
|
90
|
+
* `PiRpcClient.recordUnmappedFrame` exactly: logs once per distinct
|
|
91
|
+
* label, folds the running tally into a later exit error for a
|
|
92
|
+
* post-mortem without separate log scraping.
|
|
93
|
+
*/
|
|
94
|
+
recordUnmappedFrame(label: string): void;
|
|
95
|
+
/** Best-effort teardown. SIGTERM on POSIX; `taskkill /T /F` on Windows to also reap child processes claude itself spawned (e.g. Bash) — mirrors pi's cross-platform `kill()` exactly. Empirically confirmed on this (POSIX) machine: a running claude process exits cleanly within ~1s of SIGTERM (observed exit code 143 = 128+SIGTERM, i.e. claude catches and handles the signal itself rather than needing a harder kill). */
|
|
96
|
+
kill(): void;
|
|
97
|
+
private onData;
|
|
98
|
+
private onLine;
|
|
99
|
+
private onStderr;
|
|
100
|
+
/** Mirrors pi's `buildExitError` exactly — stderr tail + unmapped-frame tally folded into one self-diagnosing message. */
|
|
101
|
+
private buildExitError;
|
|
102
|
+
private onClosed;
|
|
103
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface ResolvedApprovalMcpBin {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
source: 'env' | 'dist';
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve `byok-approval-mcp` — the small stdio MCP server
|
|
8
|
+
* (`bin/byok-approval-mcp.ts`) `claude`'s own `--permission-prompt-tool`
|
|
9
|
+
* spawns as ITS child process (see that file's doc comment, and
|
|
10
|
+
* `permission-mapping.ts`'s `confirm`-mode doc comment, for the full design).
|
|
11
|
+
*
|
|
12
|
+
* Unlike `resolveClaudeBin` (the end user's own separately-installed,
|
|
13
|
+
* separately-authenticated CLI, resolved via bare-name PATH lookup),
|
|
14
|
+
* `byok-approval-mcp` is a script THIS SAME `@byok-sdk/client` package ships —
|
|
15
|
+
* bare-name PATH lookup is NOT safe for it: `@byok-sdk/client` is typically a
|
|
16
|
+
* project-local dependency, so its `node_modules/.bin/byok-approval-mcp`
|
|
17
|
+
* symlink is only on PATH for processes that inherit THAT project's own
|
|
18
|
+
* shell/PATH, not reliably for a background OS service (launchd/systemd
|
|
19
|
+
* often run with a stripped-down PATH that omits project-local
|
|
20
|
+
* `node_modules/.bin` entirely — see `templates/service/**`). Resolving an
|
|
21
|
+
* ABSOLUTE path to this package's own compiled bin avoids depending on PATH
|
|
22
|
+
* at all.
|
|
23
|
+
*
|
|
24
|
+
* `BYOK_APPROVAL_MCP_BIN` overrides everything when set — the injectable
|
|
25
|
+
* seam for tests (mirrors `BYOK_CLAUDE_BIN`/`BYOK_PI_BIN`), letting a test
|
|
26
|
+
* substitute a fixture script instead of computing any real path. The
|
|
27
|
+
* override is a single command string with no separate args (tests don't
|
|
28
|
+
* need to invoke it any differently than `node <script>`); the real default
|
|
29
|
+
* below is `node <absolute-path-to-the-built-bin>`.
|
|
30
|
+
*
|
|
31
|
+
* The default computation is deliberately anchored to THIS module's own
|
|
32
|
+
* `import.meta.url`, resolved once at the real production entry point: when
|
|
33
|
+
* `@byok-sdk/client` is built (`tsup.config.ts`), this file's code ends up
|
|
34
|
+
* bundled into `dist/index.js` at the package root, with `dist/bin/
|
|
35
|
+
* byok-approval-mcp.js` as its direct sibling (same layout `byok-agent.js`
|
|
36
|
+
* already uses) — `path.join(path.dirname(fileURLToPath(import.meta.url)),
|
|
37
|
+
* 'bin', 'byok-approval-mcp.js')` is therefore correct for that one real
|
|
38
|
+
* shape. It is NOT correct for this file's own unbundled TypeScript source
|
|
39
|
+
* location (`src/adapters/claude/` is two directories deeper than `src/`),
|
|
40
|
+
* but nothing in this codebase ever reaches this fallback unbundled — every
|
|
41
|
+
* test that exercises `confirm` mode sets `BYOK_APPROVAL_MCP_BIN` explicitly
|
|
42
|
+
* (see `claude-adapter.test.ts`), exactly like `BYOK_CLAUDE_BIN` already
|
|
43
|
+
* does for the real `claude` binary.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveApprovalMcpBin(): ResolvedApprovalMcpBin;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ResolvedBin {
|
|
2
|
+
command: string;
|
|
3
|
+
source: 'env' | 'path';
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the `claude` (Claude Code) CLI executable.
|
|
7
|
+
*
|
|
8
|
+
* Unlike pi (`../pi/resolve-bin.ts`), this package does NOT bundle a
|
|
9
|
+
* matched `claude` build as an optionalDependency. Claude Code is the end
|
|
10
|
+
* user's own globally-installed, individually-authenticated CLI (`claude
|
|
11
|
+
* auth login`, tied to their Anthropic/claude.ai account) — there is
|
|
12
|
+
* nothing useful to vendor: a bundled copy could never carry the user's own
|
|
13
|
+
* login state, and the credential-isolation rule (see `../../types.ts`'s
|
|
14
|
+
* `RuntimeAdapter` doc comment — this adapter must never read, proxy, or
|
|
15
|
+
* forward `~/.claude`'s own auth storage) means this adapter has no
|
|
16
|
+
* business managing a claude install at all, only spawning whatever `claude`
|
|
17
|
+
* the user already has authenticated on their PATH.
|
|
18
|
+
*
|
|
19
|
+
* Resolution is therefore deliberately two-tier, not three like pi's:
|
|
20
|
+
* `BYOK_CLAUDE_BIN` overrides everything when set (the injectable seam for
|
|
21
|
+
* in-process tests — mirrors `BYOK_PI_BIN` and substitutes the
|
|
22
|
+
* `fake-claude.mjs` fixture ahead of a real claude install, exactly as pi's
|
|
23
|
+
* own override does), otherwise this falls back to the literal command name
|
|
24
|
+
* `claude`, resolved via the child process's own PATH lookup — there is no
|
|
25
|
+
* optionalDependency tier in between.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveClaudeBin(): ResolvedBin;
|