@junghanacs/entwurf 0.51.0 → 0.53.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 +1 -1
- package/BASELINE.md +116 -397
- package/CHANGELOG.md +191 -2
- package/CONTRIBUTING.md +4 -4
- package/DELIVERY.md +2 -2
- package/ENTWURF-CLI.json +1 -1
- package/ENTWURF.json +10 -4
- package/README.md +11 -8
- package/VERIFY.md +333 -143
- package/demo/README.md +1 -1
- package/demo/demo-baseline.sh +1 -1
- package/demo/demo.sh +1 -1
- package/docs/acp-backend-rail.md +248 -34
- package/docs/setup-clean-host.md +75 -80
- package/mcp/entwurf-bridge/dist/ENTWURF.json +10 -4
- 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 +90 -14
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +4 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +15 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +9 -12
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +41 -56
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +33 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +4 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -1
- package/package.json +14 -14
- package/pi/pi-durable/carrier/harness-setup.js +1 -1
- package/pi/pi-durable/carrier/subagent.js +1 -1
- package/pi/pi-durable/overlay/upstream-pin.json +15 -15
- package/pi/settings.reference.json +0 -1
- package/pi-extensions/acp-provider.ts +12 -6
- package/pi-extensions/lib/acp/acp-client.ts +6 -7
- package/pi-extensions/lib/acp/backend-adapter.ts +139 -15
- package/pi-extensions/lib/acp/backend.ts +261 -32
- package/pi-extensions/lib/acp/config.ts +4 -9
- package/pi-extensions/lib/acp/context.ts +17 -0
- package/pi-extensions/lib/acp/event-mapper.ts +236 -17
- package/pi-extensions/lib/acp/model-ids.js +9 -12
- package/pi-extensions/lib/acp/models.ts +46 -57
- package/pi-extensions/lib/acp/tool-surface.ts +34 -6
- package/pi-extensions/lib/entwurf-control-rpc.ts +5 -1
- package/pi-extensions/lib/fresh-call-composition.ts +37 -3
- package/pi-extensions/lib/herdr-fresh-call.ts +3 -1
- package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
- package/pi-extensions/lib/mux-fresh-call.ts +3 -1
- package/pi-extensions/meta-bridge-pi-durable.ts +1 -1
- package/run.sh +62 -120
- package/scripts/check-acp-carrier-augment.ts +3 -21
- package/scripts/check-acp-prompt-lifecycle.ts +24 -3
- package/scripts/check-acp-provider-surface.ts +17 -26
- package/scripts/check-acp-session-reuse.ts +23 -4
- package/scripts/check-acp-stop-reason.ts +22 -3
- package/scripts/check-acp-stream-hooks.ts +24 -3
- package/scripts/check-acp-usage-accounting.ts +53 -24
- package/scripts/check-copilot-receive-arm.ts +19 -2
- package/scripts/check-entwurf-v2-surface.ts +7 -21
- package/scripts/check-fresh-call-dispatch.ts +9 -3
- package/scripts/check-gate-qualification.ts +6 -6
- package/scripts/check-herdr-fresh-call.ts +1 -0
- package/scripts/check-install-surface.ts +6 -0
- package/scripts/check-pi-durable-send.ts +4 -1
- package/scripts/check-probe-ordering.ts +34 -24
- package/scripts/check-release-gate-outcomes.ts +9 -23
- package/scripts/lib/codex-fresh-source-receipts.ts +12 -6
- package/scripts/lib/probe-cli-target.ts +3 -2
- package/scripts/mutants/acp-augment.json +0 -26
- package/scripts/mutants/acp-cortex.json +10 -183
- package/scripts/mutants/acp-foreground-only.json +11 -0
- package/scripts/mutants/acp-launch-namespace.json +3 -1
- package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
- package/scripts/mutants/acp-set-model-readback.json +488 -0
- package/scripts/mutants/agent-guidance.json +2 -2
- package/scripts/mutants/codex-native.json +2 -2
- package/scripts/mutants/control-send-receipt.json +47 -7
- package/scripts/mutants/mux-fresh-call.json +12 -1
- package/scripts/mutants/pack-install.json +4 -4
- package/scripts/mutants/pi-floor.json +8 -10
- package/scripts/mutants/release-gate.json +3 -3
- package/scripts/mutants/v2-surface.json +4 -6
- package/scripts/raw-acp-compaction-measure/probe.ts +8 -2
- package/scripts/smoke-acp-cortex-live.ts +4 -408
- package/scripts/smoke-acp-ordering-probe-live.ts +7 -1
- package/scripts/smoke-acp-session-reuse-live.ts +11 -2
- package/scripts/smoke-acp-v2-send-live.ts +1 -1
- package/scripts/smoke-herdr-raw-install-live.sh +4 -4
- package/scripts/check-acp-cortex.ts +0 -710
|
@@ -52,8 +52,22 @@ function tctx(messages: Context["messages"]): TranscriptContext {
|
|
|
52
52
|
return normalizeContext({ tools: PI_DECLARED_TOOLS, messages });
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// #142: a Claude turn carries pi's thinking level, which the backend sets as effort and reads back
|
|
56
|
+
// before the prompt. This fixture runs every turn at one fixed level on rows carrying the curated
|
|
57
|
+
// level map, and its fake driver reads back whatever config option it was asked to set. The effort
|
|
58
|
+
// contract itself is certified in pi-extensions/lib/acp/backend.thinking.test.ts.
|
|
59
|
+
const FIXTURE_THINKING_LEVEL = "medium";
|
|
60
|
+
const FIXTURE_THINKING_LEVEL_MAP = {
|
|
61
|
+
off: null,
|
|
62
|
+
minimal: null,
|
|
63
|
+
low: "low",
|
|
64
|
+
medium: "medium",
|
|
65
|
+
high: "high",
|
|
66
|
+
xhigh: "xhigh",
|
|
67
|
+
max: "max",
|
|
68
|
+
};
|
|
69
|
+
const sonnet = { id: "claude-sonnet-5-5", thinkingLevelMap: FIXTURE_THINKING_LEVEL_MAP } as unknown as Model<Api>;
|
|
70
|
+
const opus = { id: "claude-opus-5-5", thinkingLevelMap: FIXTURE_THINKING_LEVEL_MAP } as unknown as Model<Api>;
|
|
57
71
|
|
|
58
72
|
type Stream = AsyncIterable<AssistantMessageEvent> & {
|
|
59
73
|
result: () => Promise<{ stopReason: string; errorMessage?: string }>;
|
|
@@ -155,7 +169,7 @@ function makeHarness(recordDir: string, config: any = DEFAULT_RESOLVED_CONFIG, e
|
|
|
155
169
|
newSessionSeq++;
|
|
156
170
|
return { sessionId: `ACP-${newSessionSeq}` };
|
|
157
171
|
},
|
|
158
|
-
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id:
|
|
172
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: p.configId, currentValue: p.value }] }),
|
|
159
173
|
prompt: async ({ sessionId, prompt }: any) => {
|
|
160
174
|
promptCalls.push({ sessionId, text: prompt.map((b: { text: string }) => b.text).join("\n") });
|
|
161
175
|
if (block) await block;
|
|
@@ -282,7 +296,12 @@ try {
|
|
|
282
296
|
copyFileSync("pi-extensions/lib/acp/prompts/engraving.md", resolve(promptsOut, "engraving.md"));
|
|
283
297
|
const backendUrl = pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend.js")).href;
|
|
284
298
|
const storeUrl = pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/session-store.js")).href;
|
|
285
|
-
const
|
|
299
|
+
const backendModule = (await import(backendUrl)) as any;
|
|
300
|
+
const backend = {
|
|
301
|
+
...backendModule,
|
|
302
|
+
streamAcpTurn: (model: unknown, context: unknown, options: Record<string, unknown> | undefined, deps: unknown) =>
|
|
303
|
+
backendModule.streamAcpTurn(model, context, { reasoning: FIXTURE_THINKING_LEVEL, ...options }, deps),
|
|
304
|
+
};
|
|
286
305
|
await assertOverlayInSandbox(TMP_EMIT, gateHome);
|
|
287
306
|
const store = (await import(storeUrl)) as any;
|
|
288
307
|
const configUrl = pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/config.js")).href;
|
|
@@ -56,7 +56,21 @@ import type { Api, AssistantMessageEvent, Model } from "@earendil-works/pi-ai";
|
|
|
56
56
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
57
57
|
import { assertOverlayInSandbox, sandboxAcpGateHome } from "./lib/acp-gate-home.ts";
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// #142: a Claude turn carries pi's thinking level, which the backend sets as effort and reads back
|
|
60
|
+
// before the prompt. This fixture runs every turn at one fixed level on rows carrying the curated
|
|
61
|
+
// level map, and its fake driver reads back whatever config option it was asked to set. The effort
|
|
62
|
+
// contract itself is certified in pi-extensions/lib/acp/backend.thinking.test.ts.
|
|
63
|
+
const FIXTURE_THINKING_LEVEL = "medium";
|
|
64
|
+
const FIXTURE_THINKING_LEVEL_MAP = {
|
|
65
|
+
off: null,
|
|
66
|
+
minimal: null,
|
|
67
|
+
low: "low",
|
|
68
|
+
medium: "medium",
|
|
69
|
+
high: "high",
|
|
70
|
+
xhigh: "xhigh",
|
|
71
|
+
max: "max",
|
|
72
|
+
};
|
|
73
|
+
const sonnet = { id: "claude-sonnet-5-5", thinkingLevelMap: FIXTURE_THINKING_LEVEL_MAP } as unknown as Model<Api>;
|
|
60
74
|
|
|
61
75
|
type Stream = AsyncIterable<AssistantMessageEvent> & {
|
|
62
76
|
result: () => Promise<{ stopReason: string; errorMessage?: string }>;
|
|
@@ -123,7 +137,7 @@ function makeHarness(recordDir: string, wireReason: string | null) {
|
|
|
123
137
|
createConnection: (_child: any, handlers: any) => ({
|
|
124
138
|
initialize: async () => ({ agentCapabilities: {} }),
|
|
125
139
|
newSession: async () => ({ sessionId: "ACP-1" }),
|
|
126
|
-
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id:
|
|
140
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: p.configId, currentValue: p.value }] }),
|
|
127
141
|
prompt: async ({ sessionId }: any) => {
|
|
128
142
|
await handlers.sessionUpdate({
|
|
129
143
|
update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: "PARTIAL" } },
|
|
@@ -249,7 +263,12 @@ try {
|
|
|
249
263
|
|
|
250
264
|
const backendUrl = pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend.js")).href;
|
|
251
265
|
const eventUrl = pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/event-mapper.js")).href;
|
|
252
|
-
const
|
|
266
|
+
const backendModule = (await import(backendUrl)) as any;
|
|
267
|
+
const backend = {
|
|
268
|
+
...backendModule,
|
|
269
|
+
streamAcpTurn: (model: unknown, context: unknown, options: Record<string, unknown> | undefined, deps: unknown) =>
|
|
270
|
+
backendModule.streamAcpTurn(model, context, { reasoning: FIXTURE_THINKING_LEVEL, ...options }, deps),
|
|
271
|
+
};
|
|
253
272
|
await assertOverlayInSandbox(TMP_EMIT, gateHome);
|
|
254
273
|
const eventMod = (await import(eventUrl)) as any;
|
|
255
274
|
|
|
@@ -36,7 +36,21 @@ import type { Api, AssistantMessageEvent, Message, Model, TranscriptContext } fr
|
|
|
36
36
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
37
37
|
import { assertOverlayInSandbox, sandboxAcpGateHome } from "./lib/acp-gate-home.ts";
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
// #142: a Claude turn carries pi's thinking level, which the backend sets as effort and reads back
|
|
40
|
+
// before the prompt. This fixture runs every turn at one fixed level on rows carrying the curated
|
|
41
|
+
// level map, and its fake driver reads back whatever config option it was asked to set. The effort
|
|
42
|
+
// contract itself is certified in pi-extensions/lib/acp/backend.thinking.test.ts.
|
|
43
|
+
const FIXTURE_THINKING_LEVEL = "medium";
|
|
44
|
+
const FIXTURE_THINKING_LEVEL_MAP = {
|
|
45
|
+
off: null,
|
|
46
|
+
minimal: null,
|
|
47
|
+
low: "low",
|
|
48
|
+
medium: "medium",
|
|
49
|
+
high: "high",
|
|
50
|
+
xhigh: "xhigh",
|
|
51
|
+
max: "max",
|
|
52
|
+
};
|
|
53
|
+
const sonnet = { id: "claude-sonnet-5-5", thinkingLevelMap: FIXTURE_THINKING_LEVEL_MAP } as unknown as Model<Api>;
|
|
40
54
|
|
|
41
55
|
type Stream = AsyncIterable<AssistantMessageEvent>;
|
|
42
56
|
|
|
@@ -94,7 +108,7 @@ function makeHarness(recordDir: string) {
|
|
|
94
108
|
const makeConnection = (_handlers: any) => ({
|
|
95
109
|
initialize: async () => ({ agentCapabilities: {} }),
|
|
96
110
|
newSession: async () => ({ sessionId: "ACP-1" }),
|
|
97
|
-
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id:
|
|
111
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: p.configId, currentValue: p.value }] }),
|
|
98
112
|
prompt: async (params: any) => {
|
|
99
113
|
// Deep-cloned: the capture must be the bytes that crossed the seam, not a
|
|
100
114
|
// live reference a later mutation could rewrite.
|
|
@@ -195,7 +209,14 @@ try {
|
|
|
195
209
|
const promptsOut = resolve(TMP_EMIT, "pi-extensions/lib/acp/prompts");
|
|
196
210
|
mkdirSync(promptsOut, { recursive: true });
|
|
197
211
|
copyFileSync("pi-extensions/lib/acp/prompts/engraving.md", resolve(promptsOut, "engraving.md"));
|
|
198
|
-
const
|
|
212
|
+
const backendModule = (await import(
|
|
213
|
+
pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend.js")).href
|
|
214
|
+
)) as any;
|
|
215
|
+
const backend = {
|
|
216
|
+
...backendModule,
|
|
217
|
+
streamAcpTurn: (model: unknown, context: unknown, options: Record<string, unknown> | undefined, deps: unknown) =>
|
|
218
|
+
backendModule.streamAcpTurn(model, context, { reasoning: FIXTURE_THINKING_LEVEL, ...options }, deps),
|
|
219
|
+
};
|
|
199
220
|
await assertOverlayInSandbox(TMP_EMIT, gateHome);
|
|
200
221
|
|
|
201
222
|
// ----------------------------------------------------------------------
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// totalTokens is CONTEXT OCCUPANCY, not a turn total, and is never overwritten
|
|
28
28
|
// with the turn aggregate — pi reads that field as the context gauge.
|
|
29
29
|
// a backend with no measured semantics (no sealsTurnAccounting) is sealed NOT AT
|
|
30
|
-
// ALL:
|
|
30
|
+
// ALL: the no-capability fixture preserves the mapper's output.
|
|
31
31
|
//
|
|
32
32
|
// ORACLES, each independent of the subject:
|
|
33
33
|
// - the cost sum: THIS FILE constructs the cumulative series and asserts the
|
|
@@ -62,8 +62,21 @@ import { isContextOverflow, normalizeContext } from "@earendil-works/pi-ai";
|
|
|
62
62
|
import { calculateContextTokens } from "@earendil-works/pi-coding-agent";
|
|
63
63
|
import { assertOverlayInSandbox, sandboxAcpGateHome } from "./lib/acp-gate-home.ts";
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// #142: a Claude turn carries pi's thinking level, which the backend sets as effort and reads back
|
|
66
|
+
// before the prompt. This fixture runs every turn at one fixed level on rows carrying the curated
|
|
67
|
+
// level map, and its fake driver reads back whatever config option it was asked to set. The effort
|
|
68
|
+
// contract itself is certified in pi-extensions/lib/acp/backend.thinking.test.ts.
|
|
69
|
+
const FIXTURE_THINKING_LEVEL = "medium";
|
|
70
|
+
const FIXTURE_THINKING_LEVEL_MAP = {
|
|
71
|
+
off: null,
|
|
72
|
+
minimal: null,
|
|
73
|
+
low: "low",
|
|
74
|
+
medium: "medium",
|
|
75
|
+
high: "high",
|
|
76
|
+
xhigh: "xhigh",
|
|
77
|
+
max: "max",
|
|
78
|
+
};
|
|
79
|
+
const claude = { id: "claude-sonnet-5-5", thinkingLevelMap: FIXTURE_THINKING_LEVEL_MAP } as unknown as Model<Api>;
|
|
67
80
|
|
|
68
81
|
type Stream = AsyncIterable<AssistantMessageEvent>;
|
|
69
82
|
|
|
@@ -161,7 +174,7 @@ function makeHarness(recordDir: string, scripts: TurnScript[]) {
|
|
|
161
174
|
newSessionCalls++;
|
|
162
175
|
return { sessionId: "ACP-1" };
|
|
163
176
|
},
|
|
164
|
-
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id:
|
|
177
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: p.configId, currentValue: p.value }] }),
|
|
165
178
|
prompt: async ({ sessionId }: any) => {
|
|
166
179
|
const script = scripts[turnIndex++] ?? {};
|
|
167
180
|
const update: Record<string, unknown> = { sessionUpdate: "usage_update" };
|
|
@@ -305,7 +318,14 @@ try {
|
|
|
305
318
|
const promptsOut = resolve(TMP_EMIT, "pi-extensions/lib/acp/prompts");
|
|
306
319
|
mkdirSync(promptsOut, { recursive: true });
|
|
307
320
|
copyFileSync("pi-extensions/lib/acp/prompts/engraving.md", resolve(promptsOut, "engraving.md"));
|
|
308
|
-
const
|
|
321
|
+
const backendModule = (await import(
|
|
322
|
+
pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend.js")).href
|
|
323
|
+
)) as any;
|
|
324
|
+
const backend = {
|
|
325
|
+
...backendModule,
|
|
326
|
+
streamAcpTurn: (model: unknown, context: unknown, options: Record<string, unknown> | undefined, deps: unknown) =>
|
|
327
|
+
backendModule.streamAcpTurn(model, context, { reasoning: FIXTURE_THINKING_LEVEL, ...options }, deps),
|
|
328
|
+
};
|
|
309
329
|
await assertOverlayInSandbox(TMP_EMIT, gateHome);
|
|
310
330
|
|
|
311
331
|
// ----------------------------------------------------------------------
|
|
@@ -915,13 +935,10 @@ try {
|
|
|
915
935
|
// ----------------------------------------------------------------------
|
|
916
936
|
// CELL 5 — a backend with no MEASURED semantics is sealed NOT AT ALL.
|
|
917
937
|
//
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
//
|
|
921
|
-
//
|
|
922
|
-
// turn", the field comments say "across all turns/session". So cortex must
|
|
923
|
-
// keep exactly its pre-#93 output: the coarse mapper assignment, and no token
|
|
924
|
-
// projection, no baseline, no diff.
|
|
938
|
+
// The common loop must not invent accounting semantics for an adapter that
|
|
939
|
+
// declares none. Cortex is disabled, so this isolated fixture temporarily
|
|
940
|
+
// removes the capability from the compiled Claude adapter; it changes no
|
|
941
|
+
// production routing and restores the capability before the next cell.
|
|
925
942
|
// ----------------------------------------------------------------------
|
|
926
943
|
// TWO turns, and the second one is what carries the claim: on turn ONE a
|
|
927
944
|
// diff-against-an-empty-baseline and the raw cumulative are the SAME number, so
|
|
@@ -946,18 +963,30 @@ try {
|
|
|
946
963
|
occupancyTokens: 140_000,
|
|
947
964
|
},
|
|
948
965
|
]);
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
await collect(
|
|
952
|
-
backend.streamAcpTurn(
|
|
953
|
-
cortex,
|
|
954
|
-
reuseCtx("cortex NONCE-1", "cortex NONCE-2"),
|
|
955
|
-
{ sessionId: "usage-E" },
|
|
956
|
-
h.deps,
|
|
957
|
-
) as Stream,
|
|
958
|
-
),
|
|
966
|
+
const { claudeAdapter } = await import(
|
|
967
|
+
pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend-adapter.js")).href
|
|
959
968
|
);
|
|
960
|
-
|
|
969
|
+
const oldCapability = claudeAdapter.sealsTurnAccounting;
|
|
970
|
+
claudeAdapter.sealsTurnAccounting = false;
|
|
971
|
+
let msg: AssistantMessage;
|
|
972
|
+
try {
|
|
973
|
+
await collect(
|
|
974
|
+
backend.streamAcpTurn(claude, userCtx("fixture NONCE-1"), { sessionId: "usage-E" }, h.deps) as Stream,
|
|
975
|
+
);
|
|
976
|
+
msg = sealedMessage(
|
|
977
|
+
await collect(
|
|
978
|
+
backend.streamAcpTurn(
|
|
979
|
+
claude,
|
|
980
|
+
reuseCtx("fixture NONCE-1", "fixture NONCE-2"),
|
|
981
|
+
{ sessionId: "usage-E" },
|
|
982
|
+
h.deps,
|
|
983
|
+
) as Stream,
|
|
984
|
+
),
|
|
985
|
+
);
|
|
986
|
+
} finally {
|
|
987
|
+
claudeAdapter.sealsTurnAccounting = oldCapability;
|
|
988
|
+
}
|
|
989
|
+
assert.equal(h.children.length, 1, "both synthetic no-accounting turns ran on ONE reused child");
|
|
961
990
|
assert.deepEqual(
|
|
962
991
|
{
|
|
963
992
|
input: msg.usage.input,
|
|
@@ -1094,7 +1123,7 @@ console.log(
|
|
|
1094
1123
|
"a reused session; a turn with no cost notification attributes $0 while HOLDING the baseline so the session sum " +
|
|
1095
1124
|
"reproduces the backend's own cumulative ESTIMATE exactly (agreement with that carrier, never with a bill); a DECREASING total rebaselines, attributes $0 and says so to the operator with both numbers; " +
|
|
1096
1125
|
"totalTokens stays CONTEXT OCCUPANCY (verified through pi's own calculateContextTokens) and is carried forward " +
|
|
1097
|
-
"when a turn reports none;
|
|
1126
|
+
"when a turn reports none; an injected no-accounting capability fixture is sealed not at all; and a next turn " +
|
|
1098
1127
|
"started from INSIDE the previous turn's terminal event still reuses the one session, so the cost baseline " +
|
|
1099
1128
|
"survives the turn boundary",
|
|
1100
1129
|
);
|
|
@@ -705,12 +705,29 @@ ok(
|
|
|
705
705
|
"python3",
|
|
706
706
|
[nonDumpable, path.join(root, "node_modules", "@github", "copilot", "npm-loader.js")],
|
|
707
707
|
{
|
|
708
|
-
stdio: "ignore",
|
|
708
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
709
709
|
},
|
|
710
710
|
);
|
|
711
711
|
running.push(opaque);
|
|
712
712
|
try {
|
|
713
|
-
|
|
713
|
+
// Wait for the child's own "ready" (written after prctl succeeded), not a fixed delay: on a
|
|
714
|
+
// loaded host 400 ms was not enough for python to start and drop dumpability, so the
|
|
715
|
+
// environ was still readable and this cell failed for a timing reason (0.53.0 release gate).
|
|
716
|
+
await new Promise<void>((resolve, reject) => {
|
|
717
|
+
let seen = "";
|
|
718
|
+
const timer = setTimeout(() => reject(new Error("non-dumpable fixture never reported ready")), 15_000);
|
|
719
|
+
opaque.stderr?.on("data", (b: Buffer) => {
|
|
720
|
+
seen += b.toString();
|
|
721
|
+
if (seen.includes("ready")) {
|
|
722
|
+
clearTimeout(timer);
|
|
723
|
+
resolve();
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
opaque.once("exit", (code) => {
|
|
727
|
+
clearTimeout(timer);
|
|
728
|
+
reject(new Error(`non-dumpable fixture exited early (${code})`));
|
|
729
|
+
});
|
|
730
|
+
});
|
|
714
731
|
let environReadable = true;
|
|
715
732
|
try {
|
|
716
733
|
readFileSync(`/proc/${opaque.pid}/environ`);
|
|
@@ -92,7 +92,7 @@ function assertRailSemantics(tag: string, longDescRaw: string, intentDescRaw: st
|
|
|
92
92
|
);
|
|
93
93
|
ok(
|
|
94
94
|
`${tag} — long description names the THIRD result for an unsupported citizen`,
|
|
95
|
-
/mailbox-undeliverable/.test(longDesc) && /THIRD RESULT/.test(longDesc),
|
|
95
|
+
/mailbox-undeliverable/.test(longDesc) && /target without a live socket, the THIRD RESULT/.test(longDesc),
|
|
96
96
|
);
|
|
97
97
|
ok(
|
|
98
98
|
`${tag} — intent param names a rail's reject, not only its allow cell`,
|
|
@@ -139,30 +139,16 @@ function assertRailSemantics(tag: string, longDescRaw: string, intentDescRaw: st
|
|
|
139
139
|
* whose subject (two distinct OWNED rejects) left with the `owned-outcome` intent.
|
|
140
140
|
*
|
|
141
141
|
* After the visible-first cut a dormant socket-domain citizen rejects delivery by v2, and
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* an operator who read last week's docs with no way to find out what happened;
|
|
147
|
-
* - say it was `withdrawn`, ADJACENTLY to that name. Presence alone is not enough: naming a
|
|
148
|
-
* retired intent without the retirement verb beside it reads as an offer, which is exactly
|
|
149
|
-
* the "advertised as selectable" failure this claim exists to forbid;
|
|
150
|
-
* - carry NEITHER retired advertisement form (`owned-outcome = …` enum styling, or the old
|
|
151
|
-
* "DORMANT socket-domain citizen … never auto-converted" sentence that described picking it).
|
|
152
|
-
*
|
|
153
|
-
* ORDER IS LOAD-BEARING, and not for readability. `ok` throws on the FIRST failure, and the kill
|
|
154
|
-
* signature is read off the failing line — so the `[QK:…]` label must sit on the assertion this
|
|
155
|
-
* claim's mutant reaches FIRST. That mutant drops the reason AND the withdrawal in one edit; with
|
|
156
|
-
* the reason check leading, an UNLABELLED line would throw first and the kill would read as
|
|
157
|
-
* WRONG-REASON. The adjacency check therefore goes first and carries the only token; the other two
|
|
158
|
-
* run unlabelled behind it — still real assertions, just not this claim's signature.
|
|
142
|
+
* both caller-facing strings must independently name the actual dormant rejection and say that
|
|
143
|
+
* delivery never launches or resumes a process. Neither may advertise the retired offer. History
|
|
144
|
+
* belongs in the issue and literal amendment, not in the current caller contract. The labelled
|
|
145
|
+
* assertion comes first because qualification reads the first failing line as the kill signature.
|
|
159
146
|
*/
|
|
160
147
|
function assertDormantHonesty(tag: string, rawText: string): void {
|
|
161
148
|
const text = rawText;
|
|
162
|
-
const at = text.indexOf("owned-outcome");
|
|
163
149
|
ok(
|
|
164
|
-
`${tag} —
|
|
165
|
-
|
|
150
|
+
`${tag} — delivery never launches or resumes a process and names the dormant rejection [QK:V2SURF-DORMANT-HONESTY]`,
|
|
151
|
+
/This delivery verb never launches or resumes a process/.test(text) && /dormant-fire-forget-unsupported/.test(text),
|
|
166
152
|
);
|
|
167
153
|
ok(`${tag} — names the reason a dormant target actually returns`, /dormant-fire-forget-unsupported/.test(text));
|
|
168
154
|
ok(
|
|
@@ -150,7 +150,10 @@ async function main(): Promise<void> {
|
|
|
150
150
|
const tmuxRetired = await dispatchFreshCall(pi(" entwurf/claude-opus-5 "), {}, recordingSpawn().spawn);
|
|
151
151
|
const current = recordingSpawn();
|
|
152
152
|
const herdrCurrent = await dispatchFreshCall(pi("entwurf/claude-opus-5-5"), HERDR_ENV, current.spawn);
|
|
153
|
-
const
|
|
153
|
+
const cortexSpawn = recordingSpawn();
|
|
154
|
+
const cortex = await dispatchFreshCall(pi("entwurf/cortex-claude-sonnet-5"), {}, cortexSpawn.spawn);
|
|
155
|
+
const cortexHerdrSpawn = recordingSpawn();
|
|
156
|
+
const cortexHerdr = await dispatchFreshCall(pi("entwurf/cortex-auto"), HERDR_ENV, cortexHerdrSpawn.spawn);
|
|
154
157
|
const otherBackend = await dispatchFreshCall(
|
|
155
158
|
{ ...REQUEST, backend: "claude-code", model: "entwurf/claude-sonnet-5" },
|
|
156
159
|
{},
|
|
@@ -158,7 +161,7 @@ async function main(): Promise<void> {
|
|
|
158
161
|
);
|
|
159
162
|
const reasonOf = (d: Awaited<ReturnType<typeof dispatchFreshCall>>) => (d.result.ok ? null : d.result.reason);
|
|
160
163
|
ok(
|
|
161
|
-
"[QK:FCD-ACP-MODEL-EXACT] a pi sibling under the entwurf provider
|
|
164
|
+
"[QK:FCD-ACP-MODEL-EXACT] a pi sibling under the entwurf provider names an exact supported Claude id: retired Claude and Cortex ids refuse before launch, while a current Claude id passes and another backend is not judged",
|
|
162
165
|
herdrRetired.rail === "herdr" &&
|
|
163
166
|
reasonOf(herdrRetired) === "acp-model-not-curated" &&
|
|
164
167
|
herdrSpawn.calls.length === 0 &&
|
|
@@ -166,7 +169,10 @@ async function main(): Promise<void> {
|
|
|
166
169
|
reasonOf(tmuxRetired) === "acp-model-not-curated" &&
|
|
167
170
|
reasonOf(herdrCurrent) !== "acp-model-not-curated" &&
|
|
168
171
|
current.calls.length > 0 &&
|
|
169
|
-
reasonOf(cortex)
|
|
172
|
+
reasonOf(cortex) === "acp-model-not-curated" &&
|
|
173
|
+
cortexSpawn.calls.length === 0 &&
|
|
174
|
+
reasonOf(cortexHerdr) === "acp-model-not-curated" &&
|
|
175
|
+
cortexHerdrSpawn.calls.length === 0 &&
|
|
170
176
|
reasonOf(otherBackend) !== "acp-model-not-curated" &&
|
|
171
177
|
renderDispatchedFreshCall(herdrRetired).text.includes("exact curated ACP id") &&
|
|
172
178
|
renderDispatchedFreshCall(tmuxRetired).text.includes("exact curated ACP id"),
|
|
@@ -3435,13 +3435,13 @@ let manifestCount: number;
|
|
|
3435
3435
|
// Extend this inventory and the manifests TOGETHER, never silently.
|
|
3436
3436
|
const EXPECTED_LANE_MUTANTS: Record<string, number> = {
|
|
3437
3437
|
"acp-backend-preflight": 1,
|
|
3438
|
-
"acp-augment":
|
|
3439
|
-
"acp-cortex":
|
|
3440
|
-
"acp-foreground-only":
|
|
3438
|
+
"acp-augment": 8,
|
|
3439
|
+
"acp-cortex": 3,
|
|
3440
|
+
"acp-foreground-only": 2,
|
|
3441
3441
|
"acp-launch-namespace": 2,
|
|
3442
3442
|
"acp-overlay": 1,
|
|
3443
3443
|
"acp-prompt-lifecycle": 15,
|
|
3444
|
-
"acp-set-model-readback":
|
|
3444
|
+
"acp-set-model-readback": 44,
|
|
3445
3445
|
"acp-stop-reason": 7,
|
|
3446
3446
|
"acp-stream-hooks": 10,
|
|
3447
3447
|
"acp-usage-accounting": 12,
|
|
@@ -3460,7 +3460,7 @@ let manifestCount: number;
|
|
|
3460
3460
|
"pi-retire-request": 6,
|
|
3461
3461
|
"retirement-event": 16,
|
|
3462
3462
|
"control-retire": 9,
|
|
3463
|
-
"control-send-receipt":
|
|
3463
|
+
"control-send-receipt": 23,
|
|
3464
3464
|
"codex-native": 95,
|
|
3465
3465
|
"native-push-paused": 1,
|
|
3466
3466
|
"chain-terminus-publication": 1,
|
|
@@ -3500,7 +3500,7 @@ let manifestCount: number;
|
|
|
3500
3500
|
"meta-identity": 9,
|
|
3501
3501
|
"meta-retire": 4,
|
|
3502
3502
|
"mux-boundary": 16,
|
|
3503
|
-
"mux-fresh-call":
|
|
3503
|
+
"mux-fresh-call": 68,
|
|
3504
3504
|
"mux-launcher-fence": 8,
|
|
3505
3505
|
"mux-parent-artifact": 3,
|
|
3506
3506
|
"pack-install": 9,
|
|
@@ -413,6 +413,7 @@ async function main(): Promise<void> {
|
|
|
413
413
|
// bytes somebody wrote — while the framing gets its own claim about surviving the fold verbatim.
|
|
414
414
|
const FRAMING = composeFreshCallFraming({
|
|
415
415
|
backend: "claude-code",
|
|
416
|
+
model: "claude-sonnet-5-5",
|
|
416
417
|
callerGardenId: "20260101T010101-aaaaaa",
|
|
417
418
|
nonce: "mux-fresh-call-deadbeefdeadbeefdeadbeef",
|
|
418
419
|
openingLine: HERDR_FRESH_CALL_OPENING_LINE,
|
|
@@ -516,6 +516,12 @@ const operatorCmds = [...targets].filter(([cmd, ts]) => !isDevGate(cmd) && ts.le
|
|
|
516
516
|
l.replace(/^ {2}\.\/run\.sh (\S.*?)( +)# /, (_m, syn, gap) => ` entwurf ${syn}${gap} # `);
|
|
517
517
|
const toolCountWord = ({ 9: "nine" } as Record<number, string>)[EXPECTED_TOOLS.length];
|
|
518
518
|
const literalAmendments: { old: string; next: string; receipt: string }[] = [
|
|
519
|
+
{
|
|
520
|
+
old: "claude-native-resume; Cortex stays a documented on-demand direct call",
|
|
521
|
+
next: "claude-native-resume; Cortex ACP is disabled",
|
|
522
|
+
receipt:
|
|
523
|
+
"run.sh smoke_acp_cortex_live refuses with cortex-acp-disabled before a probe; the other MUST axes are unchanged",
|
|
524
|
+
},
|
|
519
525
|
{
|
|
520
526
|
old: "EXACTLY the eight shipped garden verbs",
|
|
521
527
|
next: `EXACTLY the ${toolCountWord} shipped garden verbs`,
|
|
@@ -900,7 +900,10 @@ async function cell(): Promise<void> {
|
|
|
900
900
|
hostReceiver.ownerKind === "pi-durable-host";
|
|
901
901
|
ok(
|
|
902
902
|
`[QK:${QK}-SENDER-REPLYABILITY] the host's receiver marker is armed and owned by the spawned host, and the landed body says what that predicts: replyable (armed ${hostArmed})`,
|
|
903
|
-
hostArmed &&
|
|
903
|
+
hostArmed &&
|
|
904
|
+
body.includes(
|
|
905
|
+
`session: ${hostGid} (meta-session, replyable — when a reply is requested, send the result via entwurf_v2`,
|
|
906
|
+
),
|
|
904
907
|
);
|
|
905
908
|
const hostRecords = fs
|
|
906
909
|
.readdirSync(dirs.sessions)
|
|
@@ -1892,20 +1892,24 @@ function intervention(
|
|
|
1892
1892
|
// a mutant subject (§11-7-c), so kill-power here is the synthetic
|
|
1893
1893
|
// negatives, not a manifest entry.
|
|
1894
1894
|
{
|
|
1895
|
-
|
|
1895
|
+
// claude-agent-acp 0.103.0+ split the resolver out of the agent: `claudeCliPath` lives in
|
|
1896
|
+
// `dist/claude-cli.js` (`cliSrc`) and the query-options literal in `dist/v1/acp-agent.js`
|
|
1897
|
+
// (`acpSrc`), which now passes `await claudeCliPath()` alone — the env check moved inside
|
|
1898
|
+
// the resolver, so the verbatim-override premise is judged there.
|
|
1899
|
+
const inspect = (cliSrc: string, acpSrc: string, sdkSrc: string): string[] => {
|
|
1896
1900
|
const violations: string[] = [];
|
|
1897
|
-
if (!
|
|
1898
|
-
violations.push("claudeCliPath export missing from
|
|
1901
|
+
if (!cliSrc.includes("export async function claudeCliPath()")) {
|
|
1902
|
+
violations.push("claudeCliPath export missing from claude-cli.js");
|
|
1899
1903
|
}
|
|
1900
|
-
if (!/if \(process\.env\.CLAUDE_CODE_EXECUTABLE\) \{\s*return process\.env\.CLAUDE_CODE_EXECUTABLE;/.test(
|
|
1904
|
+
if (!/if \(process\.env\.CLAUDE_CODE_EXECUTABLE\) \{\s*return process\.env\.CLAUDE_CODE_EXECUTABLE;/.test(cliSrc)) {
|
|
1901
1905
|
violations.push(
|
|
1902
1906
|
"claudeCliPath no longer returns the ambient override VERBATIM — the refusal precondition's premise moved",
|
|
1903
1907
|
);
|
|
1904
1908
|
}
|
|
1905
|
-
const overrideLine = "pathToClaudeCodeExecutable:
|
|
1909
|
+
const overrideLine = "pathToClaudeCodeExecutable: await claudeCliPath(),";
|
|
1906
1910
|
const iOverride = acpSrc.indexOf(overrideLine);
|
|
1907
1911
|
if (iOverride === -1) {
|
|
1908
|
-
violations.push("the
|
|
1912
|
+
violations.push("the claudeCliPath resolution at the query options literal is gone");
|
|
1909
1913
|
} else {
|
|
1910
1914
|
const iSpread = acpSrc.indexOf("...userProvidedOptions,");
|
|
1911
1915
|
if (iSpread === -1 || iSpread > iOverride) {
|
|
@@ -1944,49 +1948,55 @@ function intervention(
|
|
|
1944
1948
|
|
|
1945
1949
|
// Synthetic fixtures FIRST — an inspector that cannot see the defect it
|
|
1946
1950
|
// exists for is no inspector.
|
|
1947
|
-
const
|
|
1951
|
+
const goodCli =
|
|
1948
1952
|
"export async function claudeCliPath() {\n" +
|
|
1949
|
-
" if (process.env.CLAUDE_CODE_EXECUTABLE) {\n return process.env.CLAUDE_CODE_EXECUTABLE;\n }\n}\n"
|
|
1953
|
+
" if (process.env.CLAUDE_CODE_EXECUTABLE) {\n return process.env.CLAUDE_CODE_EXECUTABLE;\n }\n}\n";
|
|
1954
|
+
const goodAcp =
|
|
1950
1955
|
"const options = {\n ...userProvidedOptions,\n" +
|
|
1951
|
-
" pathToClaudeCodeExecutable:
|
|
1956
|
+
" pathToClaudeCodeExecutable: await claudeCliPath(),\n};\n";
|
|
1952
1957
|
const goodSdk = `x=![".js",".mjs",".tsx",".ts",".jsx"].some((r)=>e.endsWith(r));y=Cs()?"bun":"node";spawnLocalProcess(e){let{command:t,args:r,cwd:n,env:o,signal:i}=e,s=cxe(t,r,{cwd:n,stdio:["pipe","pipe","pipe"],signal:i,env:o,windowsHide:!0})}`;
|
|
1953
|
-
assert.deepEqual(inspect(goodAcp, goodSdk), [], "inspector passes the correct synthetic fixture");
|
|
1958
|
+
assert.deepEqual(inspect(goodCli, goodAcp, goodSdk), [], "inspector passes the correct synthetic fixture");
|
|
1954
1959
|
const invertedAcp = goodAcp.replace(
|
|
1955
|
-
" ...userProvidedOptions,\n pathToClaudeCodeExecutable:
|
|
1956
|
-
" pathToClaudeCodeExecutable:
|
|
1960
|
+
" ...userProvidedOptions,\n pathToClaudeCodeExecutable: await claudeCliPath(),\n",
|
|
1961
|
+
" pathToClaudeCodeExecutable: await claudeCliPath(),\n ...userProvidedOptions,\n",
|
|
1957
1962
|
);
|
|
1958
1963
|
assert.ok(
|
|
1959
|
-
inspect(invertedAcp, goodSdk).some((v) => v.includes("key-order")),
|
|
1964
|
+
inspect(goodCli, invertedAcp, goodSdk).some((v) => v.includes("key-order")),
|
|
1960
1965
|
"inspector catches userProvidedOptions inverted to win over the explicit override key",
|
|
1961
1966
|
);
|
|
1962
1967
|
assert.ok(
|
|
1963
|
-
inspect(
|
|
1964
|
-
(
|
|
1965
|
-
|
|
1968
|
+
inspect(
|
|
1969
|
+
goodCli.replace("export async function claudeCliPath()", "async function claudeCliPath()"),
|
|
1970
|
+
goodAcp,
|
|
1971
|
+
goodSdk,
|
|
1972
|
+
).some((v) => v.includes("export missing")),
|
|
1966
1973
|
"inspector catches the claudeCliPath export disappearing",
|
|
1967
1974
|
);
|
|
1968
1975
|
assert.ok(
|
|
1969
|
-
inspect(goodAcp, goodSdk.replace('".mjs",', "")).some((v) => v.includes("discriminator")),
|
|
1976
|
+
inspect(goodCli, goodAcp, goodSdk.replace('".mjs",', "")).some((v) => v.includes("discriminator")),
|
|
1970
1977
|
"inspector catches the SDK suffix list drifting from the pinned SDK_SCRIPT_SUFFIXES",
|
|
1971
1978
|
);
|
|
1972
1979
|
// The no-shell negatives must be caught IN THE LEAF WINDOW, in both the
|
|
1973
1980
|
// minified and the plain spelling — the global one-spelling absence check
|
|
1974
1981
|
// this replaced passed them all.
|
|
1975
1982
|
assert.ok(
|
|
1976
|
-
inspect(goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:!0")).some((v) =>
|
|
1983
|
+
inspect(goodCli, goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:!0")).some((v) =>
|
|
1977
1984
|
v.includes("no-shell"),
|
|
1978
1985
|
) &&
|
|
1979
|
-
inspect(goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:true")).some((v) =>
|
|
1986
|
+
inspect(goodCli, goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:true")).some((v) =>
|
|
1980
1987
|
v.includes("no-shell"),
|
|
1981
1988
|
) &&
|
|
1982
|
-
inspect(goodAcp, goodSdk.replace("spawnLocalProcess(", "spawnElsewhere(")).some((v) =>
|
|
1989
|
+
inspect(goodCli, goodAcp, goodSdk.replace("spawnLocalProcess(", "spawnElsewhere(")).some((v) =>
|
|
1990
|
+
v.includes("anchor"),
|
|
1991
|
+
),
|
|
1983
1992
|
"inspector catches shell:!0 AND shell:true inside the spawn leaf, and the leaf disappearing entirely",
|
|
1984
1993
|
);
|
|
1985
1994
|
|
|
1986
1995
|
// Installed dists — the actual §11-7-c load-bearing assumptions.
|
|
1987
1996
|
const rootRequire = createRequire(resolve(REPO_ROOT, "package.json"));
|
|
1988
1997
|
const acpPkgJson = rootRequire.resolve("@agentclientprotocol/claude-agent-acp/package.json");
|
|
1989
|
-
const
|
|
1998
|
+
const cliDist = readFileSync(join(dirname(acpPkgJson), "dist", "claude-cli.js"), "utf8");
|
|
1999
|
+
const acpDist = readFileSync(join(dirname(acpPkgJson), "dist", "v1", "acp-agent.js"), "utf8");
|
|
1990
2000
|
const acpRequire = createRequire(acpPkgJson);
|
|
1991
2001
|
const sdkEntry = acpRequire.resolve("@anthropic-ai/claude-agent-sdk");
|
|
1992
2002
|
let sdkDist = readFileSync(sdkEntry, "utf8");
|
|
@@ -1995,9 +2005,9 @@ function intervention(
|
|
|
1995
2005
|
if (existsSync(sibling)) sdkDist = readFileSync(sibling, "utf8");
|
|
1996
2006
|
}
|
|
1997
2007
|
assert.deepEqual(
|
|
1998
|
-
inspect(acpDist, sdkDist),
|
|
2008
|
+
inspect(cliDist, acpDist, sdkDist),
|
|
1999
2009
|
[],
|
|
2000
|
-
"installed acp-agent.js + sdk.mjs still carry every §11-7-c launch-semantics assumption (claudeCliPath verbatim env, override key order, suffix discriminator, node|bun choice, piped no-shell spawn)",
|
|
2010
|
+
"installed claude-cli.js + v1/acp-agent.js + sdk.mjs still carry every §11-7-c launch-semantics assumption (claudeCliPath verbatim env, override key order, suffix discriminator, node|bun choice, piped no-shell spawn)",
|
|
2001
2011
|
);
|
|
2002
2012
|
}
|
|
2003
2013
|
|
|
@@ -2050,7 +2060,7 @@ function intervention(
|
|
|
2050
2060
|
);
|
|
2051
2061
|
assert.ok(
|
|
2052
2062
|
RUNNER_SRC.includes("CLI_TARGET = await resolveProbeCliTarget({") &&
|
|
2053
|
-
RUNNER_SRC.includes('"
|
|
2063
|
+
RUNNER_SRC.includes('join(dirname(pkgJson), "dist", "claude-cli.js")') &&
|
|
2054
2064
|
RUNNER_SRC.includes("`precondition-${err.reason}`"),
|
|
2055
2065
|
"the runner resolves the target through upstream claudeCliPath BEFORE any run, and a precondition refusal writes a NAMED classification on the artifact (not stderr alone)",
|
|
2056
2066
|
);
|