@junghanacs/entwurf 0.16.0 → 0.17.1
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/AGENTS.md +5 -3
- package/CHANGELOG.md +337 -0
- package/README.md +8 -11
- package/VERIFY.md +8 -1
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +25 -14
- package/docs/setup-clean-host.md +24 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +34 -10
- package/package.json +10 -10
- package/pi-extensions/lib/acp/acp-client.ts +57 -4
- package/pi-extensions/lib/acp/backend-adapter.ts +78 -9
- package/pi-extensions/lib/acp/backend.ts +578 -18
- package/pi-extensions/lib/acp/claude-acp-launch.js +100 -0
- package/pi-extensions/lib/acp/event-mapper.ts +43 -6
- package/run.sh +129 -32
- package/scripts/check-acp-launch-namespace.ts +127 -0
- package/scripts/check-acp-prompt-lifecycle.ts +145 -2
- package/scripts/check-acp-stop-reason.ts +8 -2
- package/scripts/check-acp-usage-accounting.ts +1074 -0
- package/scripts/check-copilot-birth-hook.ts +28 -1
- package/scripts/check-gate-qualification.ts +4 -2
- package/scripts/check-omp-fresh-preflight.ts +27 -0
- package/scripts/check-setup-qualification.sh +40 -2
- package/scripts/copilot-bridge-oracle.sh +14 -6
- package/scripts/fake-copilot-vendor.sh +4 -2
- package/scripts/lib/pi-record-discovery.ts +47 -0
- package/scripts/mutants/acp-launch-namespace.json +34 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +67 -2
- package/scripts/mutants/acp-stream-hooks.json +4 -2
- package/scripts/mutants/acp-usage-accounting.json +181 -0
- package/scripts/mutants/copilot-birth.json +3 -5
- package/scripts/mutants/pack-install.json +2 -2
- package/scripts/mutants/setup-verdict.json +35 -0
- package/scripts/omp-config-xdev.py +310 -0
- package/scripts/omp-config-xdev.sh +76 -0
- package/scripts/omp-tool-surface.py +61 -10
- package/scripts/raw-acp-child-exit-measure/README.md +285 -0
- package/scripts/raw-acp-child-exit-measure/acp-turn-population.py +89 -0
- package/scripts/raw-acp-child-exit-measure/reaper-correlation.py +47 -0
- package/scripts/smoke-acp-bundled-mcp-live.ts +2 -2
- package/scripts/smoke-acp-cortex-live.ts +2 -2
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-acp-v2-send-live.ts +2 -2
- package/scripts/smoke-entwurf-v2-matrix-live.ts +60 -10
- package/scripts/smoke-mux-lifecycle-live.ts +46 -2
- package/scripts/smoke-setup-verdict.sh +48 -3
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// This module owns the `AcpConnectionLike` seam so the backend and the live
|
|
17
17
|
// smokes both drive ONE adapter — the SDK method-name mapping lives here only,
|
|
18
18
|
// and the backend's orchestration + the gate fakes stay untouched.
|
|
19
|
-
import { AGENT_METHODS, CLIENT_METHODS, client } from "@agentclientprotocol/sdk";
|
|
19
|
+
import { AGENT_METHODS, CLIENT_METHODS, client, } from "@agentclientprotocol/sdk";
|
|
20
20
|
/**
|
|
21
21
|
* Production factory — wrap the SDK 1.1 fluent `client()` into the
|
|
22
22
|
* `AcpConnectionLike` seam the backend (and the live smokes) drive.
|
|
@@ -17,10 +17,9 @@
|
|
|
17
17
|
//
|
|
18
18
|
// Fence: imported by the root program with `.js` suffixes, same as the sibling
|
|
19
19
|
// lib/acp modules — no new strip-types fence.
|
|
20
|
-
import { readFileSync } from "node:fs";
|
|
21
20
|
import { createRequire } from "node:module";
|
|
22
21
|
import { homedir } from "node:os";
|
|
23
|
-
import {
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
24
23
|
import { enrichMcpServersWithEnvelope } from "./config.js";
|
|
25
24
|
import { loadEngraving } from "./engraving.js";
|
|
26
25
|
import { CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./models.js";
|
|
@@ -39,19 +38,29 @@ function shellQuote(value) {
|
|
|
39
38
|
// claude adapter — the first implementation (the rail's reference backend)
|
|
40
39
|
// ---------------------------------------------------------------------------
|
|
41
40
|
const SUPPORTED_CLAUDE_IDS = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the claude launch — an ENTWURF-OWNED launcher, or the env override for debug.
|
|
43
|
+
* This is the single source for the claude launch spec; backend.ts holds no private copy.
|
|
44
|
+
*
|
|
45
|
+
* The default no longer names the vendor bin directly. `claude-acp-launch.js`
|
|
46
|
+
* imports it in-process; that file's header carries the reason (#72: a janitor
|
|
47
|
+
* for another harness selects `claude-agent-acp` by argv substring and SIGTERMs
|
|
48
|
+
* it by age, and the vendor's own handler erases the signal into exit 0).
|
|
49
|
+
*
|
|
50
|
+
* `CLAUDE_AGENT_ACP_COMMAND` is an EXPLICIT operator override and is deliberately
|
|
51
|
+
* NOT routed through the launcher: an operator who names their own command owns
|
|
52
|
+
* the result, including the loss of the name split and the signal observation.
|
|
53
|
+
*/
|
|
44
54
|
function resolveClaudeLaunch() {
|
|
45
55
|
const override = process.env.CLAUDE_AGENT_ACP_COMMAND?.trim();
|
|
46
56
|
if (override)
|
|
47
57
|
return { command: "bash", args: ["-lc", override] };
|
|
58
|
+
// Resolved here (not inside the launcher's own directory lookup) so a missing
|
|
59
|
+
// vendor package still fails at launch resolution, where it always failed.
|
|
48
60
|
const require = createRequire(import.meta.url);
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
if (!binPath)
|
|
53
|
-
throw new Error("@agentclientprotocol/claude-agent-acp resolved but exposes no bin entry");
|
|
54
|
-
return { command: process.execPath, args: [join(dirname(pkgJsonPath), binPath)] };
|
|
61
|
+
require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
|
|
62
|
+
const launcher = fileURLToPath(new URL("./claude-acp-launch.js", import.meta.url));
|
|
63
|
+
return { command: process.execPath, args: [launcher] };
|
|
55
64
|
}
|
|
56
65
|
export const claudeAdapter = {
|
|
57
66
|
backend: "claude",
|
|
@@ -112,6 +121,13 @@ export const claudeAdapter = {
|
|
|
112
121
|
// `_adapterSettings` here.
|
|
113
122
|
return {};
|
|
114
123
|
},
|
|
124
|
+
// MEASURED (2026-09-02, #93): claude-agent-acp's `usage_update` carries a
|
|
125
|
+
// session-cumulative ESTIMATED cost (`cost.amount = message.total_cost_usd`)
|
|
126
|
+
// and a context-occupancy scalar (`used`; both read at 0.73.0
|
|
127
|
+
// `dist/acp-agent.js:2918-2924`). Both are measured, so backend.ts seals them.
|
|
128
|
+
// The token partition is NOT declared here: ACP's only token carrier is a
|
|
129
|
+
// per-turn round-trip aggregate, which is not what pi's four fields mean.
|
|
130
|
+
sealsTurnAccounting: true,
|
|
115
131
|
};
|
|
116
132
|
// ---------------------------------------------------------------------------
|
|
117
133
|
// Registry — modelId → adapter
|
|
@@ -250,6 +266,14 @@ export const cortexAdapter = {
|
|
|
250
266
|
const settings = adapterSettings;
|
|
251
267
|
return { cortexConnection: settings?.cortexConnection ?? null };
|
|
252
268
|
},
|
|
269
|
+
// NO sealsTurnAccounting — a DELIBERATE, permanent absence, not an omission to be
|
|
270
|
+
// filled in later by symmetry with claude. Nobody has measured what cortex's
|
|
271
|
+
// ACP `usage` (and its usage_update cost, if it sends one) MEAN: whether the
|
|
272
|
+
// token counts are a turn delta or a session total, and against which price
|
|
273
|
+
// table. Until that measurement exists, the honest report is no report:
|
|
274
|
+
// backend.ts seals nothing for a backend without this flag, so cortex's
|
|
275
|
+
// emitted usage is byte-identical to what it was before #93. Declaring this
|
|
276
|
+
// with a guess would mint exactly the silent misaccounting #93 exists to end.
|
|
253
277
|
};
|
|
254
278
|
const ADAPTERS = [claudeAdapter, cortexAdapter];
|
|
255
279
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "Garden-citizen dispatch substrate and meta-bridge for Claude Code, Codex, Antigravity, and pi harnesses.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -82,23 +82,23 @@
|
|
|
82
82
|
"claudeCodeFloor": ">=2.1.217"
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@agentclientprotocol/claude-agent-acp": "0.
|
|
86
|
-
"@agentclientprotocol/sdk": "1.
|
|
85
|
+
"@agentclientprotocol/claude-agent-acp": "0.73.0",
|
|
86
|
+
"@agentclientprotocol/sdk": "1.4.0",
|
|
87
87
|
"@anthropic-ai/sdk": "0.100.1",
|
|
88
88
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
89
89
|
"zod": "^3.25.0 || ^4.0.0"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
|
-
"@earendil-works/pi-ai": ">=0.84.
|
|
93
|
-
"@earendil-works/pi-coding-agent": ">=0.84.
|
|
94
|
-
"@earendil-works/pi-tui": ">=0.84.
|
|
92
|
+
"@earendil-works/pi-ai": ">=0.84.4 <0.85",
|
|
93
|
+
"@earendil-works/pi-coding-agent": ">=0.84.4 <0.85",
|
|
94
|
+
"@earendil-works/pi-tui": ">=0.84.4 <0.85",
|
|
95
95
|
"typebox": "*"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
98
|
"@biomejs/biome": "^2.4.13",
|
|
99
|
-
"@earendil-works/pi-ai": "0.84.
|
|
100
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
101
|
-
"@earendil-works/pi-tui": "0.84.
|
|
99
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
100
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
101
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
102
102
|
"@types/node": "^24.3.0",
|
|
103
103
|
"husky": "^9.1.7",
|
|
104
104
|
"rregex": "1.13.1",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
166
166
|
"check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface",
|
|
167
167
|
"check:contracts": "./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-resume-args && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-entwurf-facts && ./run.sh check-control-socket-path && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment",
|
|
168
|
-
"check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex",
|
|
168
|
+
"check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-usage-accounting && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex",
|
|
169
169
|
"check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
|
|
170
170
|
},
|
|
171
171
|
"pi": {
|
|
@@ -17,14 +17,68 @@
|
|
|
17
17
|
// smokes both drive ONE adapter — the SDK method-name mapping lives here only,
|
|
18
18
|
// and the backend's orchestration + the gate fakes stay untouched.
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
type Usage as AcpWireUsage,
|
|
22
|
+
AGENT_METHODS,
|
|
23
|
+
CLIENT_METHODS,
|
|
24
|
+
client,
|
|
25
|
+
type Stream,
|
|
26
|
+
} from "@agentclientprotocol/sdk";
|
|
21
27
|
import type { AcpTextBlock } from "./context.js";
|
|
22
28
|
|
|
29
|
+
/**
|
|
30
|
+
* What `session/prompt` answers with — entwurf's own narrow view of the SDK's
|
|
31
|
+
* `PromptResponse`.
|
|
32
|
+
*
|
|
33
|
+
* `stopReason` stays `string | undefined` DELIBERATELY, not the SDK's closed
|
|
34
|
+
* `StopReason` union: backend.ts's verdict mapping owes an honest answer for an
|
|
35
|
+
* unknown or absent reason (it seals those as errors), and typing the field as
|
|
36
|
+
* the closed union would make that branch look unreachable.
|
|
37
|
+
*
|
|
38
|
+
* `usage` reuses the SDK's own `Usage` type rather than a hand-copy, so a field
|
|
39
|
+
* rename upstream is a typecheck failure here instead of a silent zero. It is
|
|
40
|
+
* marked `@experimental` upstream and its per-field comments say "across all
|
|
41
|
+
* turns/session" while the outer one says "for this turn" — that contradiction
|
|
42
|
+
* is why NO common code interprets this shape — and #93 then measured that the
|
|
43
|
+
* counts are a per-turn ROUND-TRIP AGGREGATE, which is not what any of pi's four
|
|
44
|
+
* `Usage` fields mean. It is read in exactly ONE place, `sealTurnUsage`, which
|
|
45
|
+
* relays it verbatim onto `usage.acp` (an accounting key, never a per-request
|
|
46
|
+
* shape) and never onto those four. A field rename upstream fails the typecheck
|
|
47
|
+
* here rather than silently zeroing the operator's cache-effect badge.
|
|
48
|
+
*/
|
|
49
|
+
/** One `_meta.quota.token_count` row (claude-agent-acp 0.73.0
|
|
50
|
+
* `dist/acp-agent.js:5750-5765`). `cachedInputTokens` is cache READS — the name
|
|
51
|
+
* differs from `usage.cachedReadTokens` because the shape is shared with
|
|
52
|
+
* codex-acp; `cachedWriteTokens` is Claude's extra sibling. */
|
|
53
|
+
export type AcpQuotaTokenCount = {
|
|
54
|
+
totalTokens?: number | null;
|
|
55
|
+
inputTokens?: number | null;
|
|
56
|
+
cachedInputTokens?: number | null;
|
|
57
|
+
cachedWriteTokens?: number | null;
|
|
58
|
+
outputTokens?: number | null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type AcpPromptResponse = {
|
|
62
|
+
stopReason?: string;
|
|
63
|
+
usage?: AcpWireUsage | null;
|
|
64
|
+
/** Vendor-private, version-pinned. `_meta` is a standard ACP extension slot
|
|
65
|
+
* whose values a client may not assume, and `quota` is NOT in
|
|
66
|
+
* claude-agent-acp's exported types — it is produced by the private
|
|
67
|
+
* `turnQuotaMeta()` (read at 0.73.0 `dist/acp-agent.js:5738-5748`). Read
|
|
68
|
+
* defensively, never structurally required, and re-measure on a pin move. */
|
|
69
|
+
_meta?: {
|
|
70
|
+
quota?: {
|
|
71
|
+
token_count?: AcpQuotaTokenCount | null;
|
|
72
|
+
model_usage?: ReadonlyArray<{ model?: string; token_count?: AcpQuotaTokenCount | null }> | null;
|
|
73
|
+
} | null;
|
|
74
|
+
} | null;
|
|
75
|
+
};
|
|
76
|
+
|
|
23
77
|
/** The subset of the ACP agent connection the backend drives (real or fake). */
|
|
24
78
|
export interface AcpConnectionLike {
|
|
25
79
|
initialize(params: unknown): Promise<unknown>;
|
|
26
80
|
newSession(params: unknown): Promise<{ sessionId?: string }>;
|
|
27
|
-
prompt(params: { sessionId: string; prompt: AcpTextBlock[] }): Promise<
|
|
81
|
+
prompt(params: { sessionId: string; prompt: AcpTextBlock[] }): Promise<AcpPromptResponse>;
|
|
28
82
|
setSessionConfigOption?(params: unknown): Promise<unknown>;
|
|
29
83
|
/**
|
|
30
84
|
* ACP `session/cancel` — the PROTOCOL way to end an in-flight prompt turn.
|
|
@@ -84,8 +138,7 @@ export function connectAcpClient(stream: Stream, handlers: AcpClientHandlers): A
|
|
|
84
138
|
initialize: (params) => agent.request(AGENT_METHODS.initialize, params as never),
|
|
85
139
|
newSession: (params) =>
|
|
86
140
|
agent.request(AGENT_METHODS.session_new, params as never) as Promise<{ sessionId?: string }>,
|
|
87
|
-
prompt: (params) =>
|
|
88
|
-
agent.request(AGENT_METHODS.session_prompt, params as never) as Promise<{ stopReason?: string }>,
|
|
141
|
+
prompt: (params) => agent.request(AGENT_METHODS.session_prompt, params as never) as Promise<AcpPromptResponse>,
|
|
89
142
|
setSessionConfigOption: (params) => agent.request(AGENT_METHODS.session_set_config_option, params as never),
|
|
90
143
|
cancel: (params) => {
|
|
91
144
|
// Notification, not a request: nothing resolves it, and the connection
|
|
@@ -18,10 +18,9 @@
|
|
|
18
18
|
// Fence: imported by the root program with `.js` suffixes, same as the sibling
|
|
19
19
|
// lib/acp modules — no new strip-types fence.
|
|
20
20
|
|
|
21
|
-
import { readFileSync } from "node:fs";
|
|
22
21
|
import { createRequire } from "node:module";
|
|
23
22
|
import { homedir } from "node:os";
|
|
24
|
-
import {
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
25
24
|
|
|
26
25
|
import type { AcpConnectionLike } from "./acp-client.js";
|
|
27
26
|
import { enrichMcpServersWithEnvelope, type ResolvedAcpConfig } from "./config.js";
|
|
@@ -132,6 +131,30 @@ export interface AcpAdapterSettingsParams {
|
|
|
132
131
|
projectPath: string;
|
|
133
132
|
}
|
|
134
133
|
|
|
134
|
+
/**
|
|
135
|
+
* NO turn-usage evidence type lives here any more, and its absence is the #93
|
|
136
|
+
* finding, not an omission.
|
|
137
|
+
*
|
|
138
|
+
* ACP reports a turn's tokens ONLY as the SUM OVER THAT TURN'S API ROUND TRIPS
|
|
139
|
+
* (measured 2026-09-02: one 21-round-trip turn summed to cacheRead 4,185,084
|
|
140
|
+
* while the context it occupied was 223,516 — the overlay ledger's 21 rows add
|
|
141
|
+
* up to the four numbers `PromptResponse.usage` reported). pi's four `Usage`
|
|
142
|
+
* fields are not that quantity: `isContextOverflow` reads `input + cacheRead`
|
|
143
|
+
* as ONE REQUEST's prompt size (read at pi-ai `dist/utils/overflow.js:132-145`)
|
|
144
|
+
* and `cache-stats.detectMiss` reads input, cache-read, and cache-write as
|
|
145
|
+
* one request's prompt shape (read at pi-coding-agent
|
|
146
|
+
* `dist/core/cache-stats.js:14-37`). Projecting the turn
|
|
147
|
+
* aggregate into them fired a false overflow that compacted a live 223k session
|
|
148
|
+
* on a 1M window, invented two phantom cache misses, and SILENCED the one real
|
|
149
|
+
* 195,177-token miss the operator needed to see.
|
|
150
|
+
*
|
|
151
|
+
* During stream handling the vendor keeps a per-message snapshot in
|
|
152
|
+
* `lastAssistantUsage`, but its `usage_update` publishes only the scalar `used`
|
|
153
|
+
* value (read at claude-agent-acp 0.73.0 `dist/acp-agent.js:3273-3297`). Until
|
|
154
|
+
* that partition is carried on the wire, NOTHING honest can go in pi's four
|
|
155
|
+
* fields, so entwurf writes none of them.
|
|
156
|
+
*/
|
|
157
|
+
|
|
135
158
|
// ---------------------------------------------------------------------------
|
|
136
159
|
// The adapter interface
|
|
137
160
|
// ---------------------------------------------------------------------------
|
|
@@ -194,6 +217,24 @@ export interface AcpBackendAdapter {
|
|
|
194
217
|
* sorted-stable primitive map (JSON.stringify determinism — no nested objects /
|
|
195
218
|
* non-deterministic order). `backend` + `nativeModelId` are added by backend.ts. */
|
|
196
219
|
configSignatureFields(adapterSettings: unknown): Record<string, unknown>;
|
|
220
|
+
|
|
221
|
+
/** Declares that THIS backend's `usage_update` cost semantics have been
|
|
222
|
+
* MEASURED, so backend.ts may seal its context occupancy and its per-turn
|
|
223
|
+
* cost from the session baseline.
|
|
224
|
+
*
|
|
225
|
+
* ABSENT means no measured semantics — permanent and static, not an omission
|
|
226
|
+
* to fill in by symmetry (cortex). backend.ts then seals NOTHING: no
|
|
227
|
+
* occupancy, no cost baseline, no diff. That is what keeps this lane's
|
|
228
|
+
* accounting out of a backend whose wire meaning nobody has measured.
|
|
229
|
+
*
|
|
230
|
+
* This is deliberately NOT a method returning token evidence. It was one
|
|
231
|
+
* until #93 measured that ACP's only token carrier is a turn aggregate that
|
|
232
|
+
* pi's four `Usage` fields cannot honestly hold (see the note above
|
|
233
|
+
* `AcpBackendAdapter`). With no token axis left to extract, presence alone
|
|
234
|
+
* carries everything the seal needs, and a flag that gates only the two axes
|
|
235
|
+
* entwurf actually reads from a NOTIFICATION cannot desync from a response
|
|
236
|
+
* shape it never inspects. */
|
|
237
|
+
sealsTurnAccounting?: true;
|
|
197
238
|
}
|
|
198
239
|
|
|
199
240
|
// ---------------------------------------------------------------------------
|
|
@@ -202,17 +243,28 @@ export interface AcpBackendAdapter {
|
|
|
202
243
|
|
|
203
244
|
const SUPPORTED_CLAUDE_IDS: ReadonlySet<string> = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);
|
|
204
245
|
|
|
205
|
-
/**
|
|
206
|
-
*
|
|
246
|
+
/**
|
|
247
|
+
* Resolve the claude launch — an ENTWURF-OWNED launcher, or the env override for debug.
|
|
248
|
+
* This is the single source for the claude launch spec; backend.ts holds no private copy.
|
|
249
|
+
*
|
|
250
|
+
* The default no longer names the vendor bin directly. `claude-acp-launch.js`
|
|
251
|
+
* imports it in-process; that file's header carries the reason (#72: a janitor
|
|
252
|
+
* for another harness selects `claude-agent-acp` by argv substring and SIGTERMs
|
|
253
|
+
* it by age, and the vendor's own handler erases the signal into exit 0).
|
|
254
|
+
*
|
|
255
|
+
* `CLAUDE_AGENT_ACP_COMMAND` is an EXPLICIT operator override and is deliberately
|
|
256
|
+
* NOT routed through the launcher: an operator who names their own command owns
|
|
257
|
+
* the result, including the loss of the name split and the signal observation.
|
|
258
|
+
*/
|
|
207
259
|
function resolveClaudeLaunch(): AcpLaunchSpec {
|
|
208
260
|
const override = process.env.CLAUDE_AGENT_ACP_COMMAND?.trim();
|
|
209
261
|
if (override) return { command: "bash", args: ["-lc", override] };
|
|
262
|
+
// Resolved here (not inside the launcher's own directory lookup) so a missing
|
|
263
|
+
// vendor package still fails at launch resolution, where it always failed.
|
|
210
264
|
const require = createRequire(import.meta.url);
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
if (!binPath) throw new Error("@agentclientprotocol/claude-agent-acp resolved but exposes no bin entry");
|
|
215
|
-
return { command: process.execPath, args: [join(dirname(pkgJsonPath), binPath)] };
|
|
265
|
+
require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
|
|
266
|
+
const launcher = fileURLToPath(new URL("./claude-acp-launch.js", import.meta.url));
|
|
267
|
+
return { command: process.execPath, args: [launcher] };
|
|
216
268
|
}
|
|
217
269
|
|
|
218
270
|
export const claudeAdapter: AcpBackendAdapter = {
|
|
@@ -287,6 +339,14 @@ export const claudeAdapter: AcpBackendAdapter = {
|
|
|
287
339
|
// `_adapterSettings` here.
|
|
288
340
|
return {};
|
|
289
341
|
},
|
|
342
|
+
|
|
343
|
+
// MEASURED (2026-09-02, #93): claude-agent-acp's `usage_update` carries a
|
|
344
|
+
// session-cumulative ESTIMATED cost (`cost.amount = message.total_cost_usd`)
|
|
345
|
+
// and a context-occupancy scalar (`used`; both read at 0.73.0
|
|
346
|
+
// `dist/acp-agent.js:2918-2924`). Both are measured, so backend.ts seals them.
|
|
347
|
+
// The token partition is NOT declared here: ACP's only token carrier is a
|
|
348
|
+
// per-turn round-trip aggregate, which is not what pi's four fields mean.
|
|
349
|
+
sealsTurnAccounting: true,
|
|
290
350
|
};
|
|
291
351
|
|
|
292
352
|
// ---------------------------------------------------------------------------
|
|
@@ -446,6 +506,15 @@ export const cortexAdapter: AcpBackendAdapter = {
|
|
|
446
506
|
const settings = adapterSettings as CortexAdapterSettings | undefined;
|
|
447
507
|
return { cortexConnection: settings?.cortexConnection ?? null };
|
|
448
508
|
},
|
|
509
|
+
|
|
510
|
+
// NO sealsTurnAccounting — a DELIBERATE, permanent absence, not an omission to be
|
|
511
|
+
// filled in later by symmetry with claude. Nobody has measured what cortex's
|
|
512
|
+
// ACP `usage` (and its usage_update cost, if it sends one) MEAN: whether the
|
|
513
|
+
// token counts are a turn delta or a session total, and against which price
|
|
514
|
+
// table. Until that measurement exists, the honest report is no report:
|
|
515
|
+
// backend.ts seals nothing for a backend without this flag, so cortex's
|
|
516
|
+
// emitted usage is byte-identical to what it was before #93. Declaring this
|
|
517
|
+
// with a guess would mint exactly the silent misaccounting #93 exists to end.
|
|
449
518
|
};
|
|
450
519
|
|
|
451
520
|
const ADAPTERS: readonly AcpBackendAdapter[] = [claudeAdapter, cortexAdapter];
|