@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
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { randomBytes } from "node:crypto";
|
|
26
|
+
import { PROVIDER_ID, SUPPORTED_ANTHROPIC_MODEL_IDS } from "./acp/model-ids.js";
|
|
26
27
|
|
|
27
28
|
/** The backends this rail can open. Fixed set, not a profile — a further one is a decision,
|
|
28
29
|
* not a config entry. `copilot` was added by #82 RAIL 9, `omp` by #87 Bundle C, `codex`
|
|
@@ -150,6 +151,34 @@ export const FRESH_CALL_TOOL_LOAD_HINT: Record<FreshCallBackend, readonly string
|
|
|
150
151
|
"pi-durable": [],
|
|
151
152
|
};
|
|
152
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Which dialect table spells the bridge tools for THIS launch. The backend picks the runtime; the
|
|
156
|
+
* model can pick a different tool surface underneath it.
|
|
157
|
+
*
|
|
158
|
+
* `[측정 2026-11-13, D1]` a pi sibling opened on an ACP Claude model (`entwurf/claude-*`) does not
|
|
159
|
+
* see Pi's MCP names: its turns run inside the Claude Agent SDK, which mints the bridge tools as
|
|
160
|
+
* `mcp__entwurf-bridge__*` (the ACP Sonnet interview of 2026-11-13, n=1, called them by the schema
|
|
161
|
+
* it was given, not by the pi spelling its framing named). So the NAMES follow the claude-code
|
|
162
|
+
* table. The load hint stays the pi row (none): in that n=1 the bridge tools were directly callable
|
|
163
|
+
* without loading. Whether ToolSearch exists on this surface, or deferral can occur with more
|
|
164
|
+
* declared servers (`acp/tool-surface.ts` names the SDK's deferred tools), is unmeasured — not a
|
|
165
|
+
* claim that it cannot.
|
|
166
|
+
*
|
|
167
|
+
* Only an exact curated Anthropic id switches. A thinking suffix (`:high`) or fuzzy alias never
|
|
168
|
+
* reaches here through the public verb: the dispatch root refuses any `entwurf/` id that is not
|
|
169
|
+
* exactly curated (`isUncuratedEntwurfPiModel`, #128), and the test pins that alignment. Retired
|
|
170
|
+
* Cortex ids are refused at the dispatch root before framing. The ACP id list arrives from
|
|
171
|
+
* `acp/model-ids.js`, an import-free module.
|
|
172
|
+
*/
|
|
173
|
+
export function freshCallToolDialect(backend: FreshCallBackend, model: string): FreshCallBackend {
|
|
174
|
+
if (backend !== "pi") return backend;
|
|
175
|
+
const trimmed = model.trim();
|
|
176
|
+
const slash = trimmed.indexOf("/");
|
|
177
|
+
if (slash < 0 || trimmed.slice(0, slash) !== PROVIDER_ID) return backend;
|
|
178
|
+
const id = trimmed.slice(slash + 1);
|
|
179
|
+
return (SUPPORTED_ANTHROPIC_MODEL_IDS as readonly string[]).includes(id) ? "claude-code" : backend;
|
|
180
|
+
}
|
|
181
|
+
|
|
153
182
|
/**
|
|
154
183
|
* What a launch has to say, in the two shapes the six backends need. pi, claude-code, copilot
|
|
155
184
|
* and codex are handed the first-turn PROMPT as an argument; pi-durable is handed that same
|
|
@@ -413,15 +442,19 @@ export function composeBackendArgs(
|
|
|
413
442
|
*/
|
|
414
443
|
export function composeFreshCallFraming(params: {
|
|
415
444
|
backend: FreshCallBackend;
|
|
445
|
+
/** The launch model, as the caller wrote it. It chooses the tool-name dialect
|
|
446
|
+
* (`freshCallToolDialect`) and is never echoed into the framing. */
|
|
447
|
+
model: string;
|
|
416
448
|
callerGardenId: string;
|
|
417
449
|
nonce: string;
|
|
418
450
|
/** The rail's own first sentence — see `PLACEMENT SENTENCE` above. Empty is a crash, not a
|
|
419
451
|
* default: a sibling told nothing about where it is would be told something false by silence. */
|
|
420
452
|
openingLine: string;
|
|
421
453
|
}): string[] {
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
const
|
|
454
|
+
const dialect = freshCallToolDialect(params.backend, params.model);
|
|
455
|
+
const birthTool = FRESH_CALL_CALLBACK_TOOL[dialect];
|
|
456
|
+
const deliveryTool = FRESH_CALL_DELIVERY_TOOL[dialect];
|
|
457
|
+
const peersTool = FRESH_CALL_PEERS_TOOL[dialect];
|
|
425
458
|
// The hint is a per-backend TEMPLATE so the two tool dialects stay the single source of their
|
|
426
459
|
// own spelling here too — a hint that hard-coded Claude's name would go stale the day a dialect
|
|
427
460
|
// moves, and it would go stale silently, in the one sentence a stuck child depends on.
|
|
@@ -490,6 +523,7 @@ export function composeFreshCallFraming(params: {
|
|
|
490
523
|
*/
|
|
491
524
|
export function composeFreshCallPrompt(params: {
|
|
492
525
|
backend: FreshCallBackend;
|
|
526
|
+
model: string;
|
|
493
527
|
task: string;
|
|
494
528
|
callerGardenId: string;
|
|
495
529
|
nonce: string;
|
|
@@ -962,6 +962,7 @@ export async function herdrFreshCall(
|
|
|
962
962
|
|
|
963
963
|
const framing = composeFreshCallFraming({
|
|
964
964
|
backend,
|
|
965
|
+
model,
|
|
965
966
|
callerGardenId,
|
|
966
967
|
nonce,
|
|
967
968
|
openingLine: HERDR_FRESH_CALL_OPENING_LINE,
|
|
@@ -1796,6 +1797,7 @@ async function herdrLayoutFreshCall(
|
|
|
1796
1797
|
|
|
1797
1798
|
const prompt = composeFreshCallPrompt({
|
|
1798
1799
|
backend: "pi-durable",
|
|
1800
|
+
model,
|
|
1799
1801
|
task,
|
|
1800
1802
|
callerGardenId,
|
|
1801
1803
|
nonce,
|
|
@@ -1876,7 +1878,7 @@ export const HERDR_REJECT_HINT: Record<
|
|
|
1876
1878
|
// #128: decided at the dispatch root (`fresh-call-dispatch.ts`), worded here because this rail's
|
|
1877
1879
|
// import fence admits no ACP module. The curated list itself lives in `acp/model-ids.js`.
|
|
1878
1880
|
"acp-model-not-curated":
|
|
1879
|
-
"an entwurf model must be one exact curated ACP id
|
|
1881
|
+
"an entwurf model must be one exact curated ACP id; Cortex ACP is disabled. Pi may otherwise pick another row by substring, so nothing was opened or aliased",
|
|
1880
1882
|
"task-empty": "a sibling opened with nothing to do is a window nobody asked for",
|
|
1881
1883
|
"task-too-long": `the task is over the ${TASK_MAX_CHARS}-character interface bound this verb shares with entwurf_v2`,
|
|
1882
1884
|
"herdr-context-missing":
|
|
@@ -79,7 +79,7 @@ export function formatMetaMailboxBody(sender: MailboxSenderEnvelope, message: st
|
|
|
79
79
|
// SENDING host, so it is neither abbreviated against this host's HOME nor shown as a local path.
|
|
80
80
|
const remote = !replyable ? sender.remote : undefined;
|
|
81
81
|
const sessionLine = replyable
|
|
82
|
-
? `${sender.sessionId} (${kind}replyable — reply
|
|
82
|
+
? `${sender.sessionId} (${kind}replyable — when a reply is requested, send the result via entwurf_v2 (intent=fire-and-forget) to the requested recipient, defaulting to this sessionId; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment)`
|
|
83
83
|
: remote
|
|
84
84
|
? `${sender.sessionId} (remote via ${remote.via}; ${remote.replyRoute === "declared" ? "reply route declared, not probed" : "no reply route declared"})`
|
|
85
85
|
: isMeta
|
|
@@ -214,6 +214,7 @@ export function buildBackendArgs(
|
|
|
214
214
|
/** The first turn for THIS rail: the neutral framing under this rail's placement sentence. */
|
|
215
215
|
export function buildFreshCallPrompt(params: {
|
|
216
216
|
backend: FreshCallBackend;
|
|
217
|
+
model: string;
|
|
217
218
|
task: string;
|
|
218
219
|
callerGardenId: string;
|
|
219
220
|
nonce: string;
|
|
@@ -510,7 +511,7 @@ export function freshCall(
|
|
|
510
511
|
const runtime = layout === null ? null : checkPiDurableRuntime(layout);
|
|
511
512
|
const missing = piDurableFreshPreflight({
|
|
512
513
|
model,
|
|
513
|
-
firstInput: buildFreshCallPrompt({ backend: params.backend, task, callerGardenId, nonce }),
|
|
514
|
+
firstInput: buildFreshCallPrompt({ backend: params.backend, model, task, callerGardenId, nonce }),
|
|
514
515
|
layout,
|
|
515
516
|
runtime: runtime === null || runtime.ok ? null : runtime.reason,
|
|
516
517
|
});
|
|
@@ -549,6 +550,7 @@ export function freshCall(
|
|
|
549
550
|
const composition: FreshCallComposition = {
|
|
550
551
|
prompt: buildFreshCallPrompt({
|
|
551
552
|
backend: params.backend,
|
|
553
|
+
model,
|
|
552
554
|
task,
|
|
553
555
|
callerGardenId,
|
|
554
556
|
nonce,
|
|
@@ -349,7 +349,7 @@ export interface PiDurableRoots {
|
|
|
349
349
|
// ---------------------------------------------------------------------------
|
|
350
350
|
// Durable surface, typed NARROWLY and locally — read at pi-durable 1.0.2 @cd32f77 (#129,
|
|
351
351
|
// `harness/types.ts`; historical coordinates, not re-anchored here). The current supply is the
|
|
352
|
-
// carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.5.
|
|
352
|
+
// carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.5.2 SDK set
|
|
353
353
|
// `pi/pi-durable/overlay/upstream-pin.json` names (origin `abe508e`). `[read 2026-10-13]` at
|
|
354
354
|
// v1.2.0 one shape typed below moved: `ToolExecutionResult.content` is now `output`, beside the new
|
|
355
355
|
// `structuredOutput` (`harness/types.ts`, its CHANGELOG's breaking changes). An omitted `output`
|
package/run.sh
CHANGED
|
@@ -1156,8 +1156,10 @@ check_control_send_receipt() {
|
|
|
1156
1156
|
# test titles `run_vitest` emits. Narrow file filter, same reason as check-entwurf-v2-send.
|
|
1157
1157
|
# It stays inside check:contracts even though the discovery door also runs this file — see
|
|
1158
1158
|
# check-release-gate-outcomes: a gate a mutant names must be reachable outside qualification.
|
|
1159
|
-
|
|
1160
|
-
|
|
1159
|
+
# #159 adds the three delivery-input seams here as a shared mutant coordinate; discovery remains
|
|
1160
|
+
# the beside glob. This reuses the existing gate instead of adding a second help/catalog surface.
|
|
1161
|
+
section "control-socket acceptance and received guidance (mutant execution coordinate)"
|
|
1162
|
+
run_vitest pi-extensions/lib/control-send-receipt.test.ts pi-extensions/entwurf-control.test.ts pi-extensions/lib/entwurf-v2-mailbox.test.ts pi-extensions/lib/entwurf-v2-native-push.test.ts
|
|
1161
1163
|
}
|
|
1162
1164
|
|
|
1163
1165
|
check_control_retire() {
|
|
@@ -1342,9 +1344,13 @@ check_fresh_call_provider() {
|
|
|
1342
1344
|
check_acp_set_model_readback() {
|
|
1343
1345
|
# NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
|
|
1344
1346
|
# MUTANT EXECUTION COORDINATE for scripts/mutants/acp-set-model-readback.json (#128): the claude
|
|
1345
|
-
# adapter refuses a set-model answer that does not name the requested model, before the prompt
|
|
1346
|
-
|
|
1347
|
-
|
|
1347
|
+
# adapter refuses a set-model answer that does not name the requested model, before the prompt;
|
|
1348
|
+
# and (#142) pi's thinking level reaches Claude effort, set and read back before the prompt;
|
|
1349
|
+
# and (#142) what a cancel leaves: the operator notice and the aborted-turn status in a rebuild;
|
|
1350
|
+
# and (#163) Pi's summary request as a one-shot tool-less turn, and the child closed on compaction;
|
|
1351
|
+
# and (#163) the display-only tool title, output preview and compaction lines in the event mapper.
|
|
1352
|
+
section "claude ACP set-model + effort readback (mutant execution coordinate)"
|
|
1353
|
+
run_vitest pi-extensions/lib/acp/backend-adapter.test.ts pi-extensions/lib/acp/backend.thinking.test.ts pi-extensions/lib/acp/context.test.ts pi-extensions/lib/acp/cortex-admission.test.ts pi-extensions/lib/acp/backend.summary-turn.test.ts pi-extensions/lib/acp/event-mapper.test.ts
|
|
1348
1354
|
}
|
|
1349
1355
|
|
|
1350
1356
|
check_acp_vendor_rejection() {
|
|
@@ -1796,38 +1802,8 @@ smoke_acp_rgg_live() {
|
|
|
1796
1802
|
}
|
|
1797
1803
|
|
|
1798
1804
|
smoke_acp_cortex_live() {
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
# `pnpm check` AND OUTSIDE the claude-only aggregate LIVE floor (capability
|
|
1802
|
-
# dignity, invariant #7): a host with no cortex install / no Snowflake auth must
|
|
1803
|
-
# not redden a release for a backend it does not run, so this is deliberately
|
|
1804
|
-
# absent from release-gate's 11-smoke ACP acceptance floor. That is a WIRING
|
|
1805
|
-
# decision, not a lower evidence bar — a cut that SHIPS cortex still owes a
|
|
1806
|
-
# deliberate run of this smoke, and the aggregate gate's silence is not a pass
|
|
1807
|
-
# (ACP rail Cortex verification boundary). Drives one real
|
|
1808
|
-
# cortex ACP turn through the entwurf provider path (outbound entwurf_v2 +
|
|
1809
|
-
# dual-HOME overlay facts + process-group reclaim — CP2).
|
|
1810
|
-
# HONEST-SKIP (protocol exit 97, never 0) when LIVE!=1 OR `cortex` is not on PATH OR no
|
|
1811
|
-
# connection is pinned — the live turn needs `cortex` installed with the
|
|
1812
|
-
# operator's own web-login auth already present (there is no `cortex auth`
|
|
1813
|
-
# subcommand — CP0 D6), reached through the overlay's narrow credential
|
|
1814
|
-
# symlinks — narrow in WHICH paths are reachable (D5), not read-only: a symlink
|
|
1815
|
-
# carries no write protection (AGENTS §ACP Plugin Boundary: entwurf never
|
|
1816
|
-
# provides/proxies the Snowflake credential). The connection must ride the adapter seam
|
|
1817
|
-
# (ENTWURF_ACP_CORTEX_CONNECTION) because the dual-HOME overlay denies the
|
|
1818
|
-
# operator settings.json where a default would live. PR #40 called this
|
|
1819
|
-
# `smoke-cortex`; the canonical family name is smoke-acp-cortex-live.
|
|
1820
|
-
# LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live
|
|
1821
|
-
entwurf_require_step_outcome
|
|
1822
|
-
if [ "${LIVE:-}" != "1" ]; then
|
|
1823
|
-
echo "[entwurf:skip] smoke-acp-cortex-live — set LIVE=1 (+ ENTWURF_ACP_CORTEX_CONNECTION) to run."
|
|
1824
|
-
return "$ENTWURF_STEP_SKIP_EXIT"
|
|
1825
|
-
fi
|
|
1826
|
-
if ! command -v cortex >/dev/null 2>&1; then
|
|
1827
|
-
echo "[entwurf:skip] smoke-acp-cortex-live — cortex not on PATH (install Snowflake Cortex Code and complete its own login flow)."
|
|
1828
|
-
return "$ENTWURF_STEP_SKIP_EXIT"
|
|
1829
|
-
fi
|
|
1830
|
-
run_ts scripts/smoke-acp-cortex-live.ts
|
|
1805
|
+
echo "[entwurf:cortex-acp-disabled] Cortex ACP is not supported; no live probe was started." >&2
|
|
1806
|
+
return 2
|
|
1831
1807
|
}
|
|
1832
1808
|
|
|
1833
1809
|
smoke_entwurf_v2_matrix_live() {
|
|
@@ -2502,15 +2478,16 @@ assert.deepEqual(floorGaps, [],
|
|
|
2502
2478
|
// The published-scope regions' own oracle (see PUBLISHED-SCOPE REGIONS above). Checked after the
|
|
2503
2479
|
// four surfaces so a stranded closure row is still attributed to the claim above.
|
|
2504
2480
|
// PUBLISHED_ARTIFACT is the one published pair a region may name, copied from the artifact, not from
|
|
2505
|
-
// this checkout.
|
|
2506
|
-
//
|
|
2507
|
-
//
|
|
2481
|
+
// this checkout. Read 2026-11-11 from package/package.json in the preserved registry-accepted
|
|
2482
|
+
// 0.51.0 release TGZ (TGZ SHA256 04fcce26d0a6dec8b8bb9a7f14a843eee0defb313aedafa34080600853d36e4d;
|
|
2483
|
+
// raw manifest SHA256 b5043ba8edde6f2b9d13052a98c3cd808a3b30f1023da41b22ee022470a96120).
|
|
2484
|
+
// Its ten @earendil-works production dependencies are exact 1.5.0, independent of this checkout.
|
|
2508
2485
|
const PUBLISHED_ARTIFACT = {
|
|
2509
|
-
version: '0.
|
|
2486
|
+
version: '0.51.0',
|
|
2510
2487
|
dependencies: {
|
|
2511
|
-
'@earendil-works/chord': '1.
|
|
2512
|
-
'@earendil-works/pi-codemode': '1.
|
|
2513
|
-
'@earendil-works/pi-mcp': '1.
|
|
2488
|
+
'@earendil-works/chord': '1.5.0', '@earendil-works/pi-agent-core': '1.5.0', '@earendil-works/pi-ai': '1.5.0',
|
|
2489
|
+
'@earendil-works/pi-codemode': '1.5.0', '@earendil-works/pi-coding-agent': '1.5.0', '@earendil-works/pi-durable': '1.5.0',
|
|
2490
|
+
'@earendil-works/pi-mcp': '1.5.0', '@earendil-works/pi-schema': '1.5.0', '@earendil-works/pi-telemetry': '1.5.0', '@earendil-works/pi-tui': '1.5.0',
|
|
2514
2491
|
},
|
|
2515
2492
|
};
|
|
2516
2493
|
const publishedSdk = [...new Set(Object.values(PUBLISHED_ARTIFACT.dependencies))];
|
|
@@ -3372,13 +3349,11 @@ check_acp_provider_surface() {
|
|
|
3372
3349
|
# Deterministic gate for the ACP provider registration surface. Loads the REAL
|
|
3373
3350
|
# provider lib modules AND compiles + drives the REAL entry against a fake pi,
|
|
3374
3351
|
# asserting: one surface name, the no-auth sentinel shape, full
|
|
3375
|
-
# ProviderModelConfig rows on the curated
|
|
3376
|
-
# set
|
|
3377
|
-
# [QK:CORTEX-PROVIDER-EXACT-ROW-SURFACE], the claim that catches an entry which
|
|
3378
|
-
# silently drops a whole backend), and that streamSimple is the real
|
|
3352
|
+
# ProviderModelConfig rows on the curated Claude anchor, the exact Claude-only
|
|
3353
|
+
# set [QK:ACP-PROVIDER-CLAUDE-ONLY], and that streamSimple is the real
|
|
3379
3354
|
# streamShellAcp backend (checked BY NAME, never invoked — invoking spawns an
|
|
3380
3355
|
# ACP child). Pure, no pi runtime, no API.
|
|
3381
|
-
section "ACP provider surface (registration +
|
|
3356
|
+
section "ACP provider surface (registration + Claude-only model set)"
|
|
3382
3357
|
run_ts scripts/check-acp-provider-surface.ts
|
|
3383
3358
|
}
|
|
3384
3359
|
|
|
@@ -3706,30 +3681,10 @@ check_acp_carrier_augment() {
|
|
|
3706
3681
|
}
|
|
3707
3682
|
|
|
3708
3683
|
check_acp_cortex() {
|
|
3709
|
-
#
|
|
3710
|
-
#
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
# + the dual-HOME overlay (overlay.ts); the 결합 규칙 requires the gate to land
|
|
3714
|
-
# WITH it, extending the check-acp-* family, so cortex's whole
|
|
3715
|
-
# deterministic axis lives here. Locks (each Cortex-audit D-pinned): the GLG-decided
|
|
3716
|
-
# 4-row curation rides real registry bases and the `cortex-` prefix routes to
|
|
3717
|
-
# cortexAdapter; launch is `cortex acp serve` with NO -m — the model is
|
|
3718
|
-
# enforced per turn via session/set_config_option with the native id (E);
|
|
3719
|
-
# CORTEX_HOME presence (empty included) refuses the spawn (D3); the dual-HOME
|
|
3720
|
-
# overlay isolates HOME+SNOWFLAKE_HOME per session key (P0-1: scope =
|
|
3721
|
-
# backend-passed resolveSessionKey, never ambient), passes through only
|
|
3722
|
-
# connections.toml / optional config.toml / credential_cache (D5/F — never the
|
|
3723
|
-
# whole cache, never skills; symlink-through, no copies — AGENTS §ACP Plugin
|
|
3724
|
-
# Boundary), authors autoUpdate:false (D4), and projects envelope-enriched
|
|
3725
|
-
# explicit servers into cortex/mcp.json with the real HOME restored on
|
|
3726
|
-
# entwurf-bridge alone (D9/D10), non-stdio failing loud; the
|
|
3727
|
-
# CORTEX_ACP_COMMAND override single-quotes shell-metachar connection tokens.
|
|
3728
|
-
# The gate self-manages the tsc-emit layer for the .js-suffixed backend-adapter
|
|
3729
|
-
# imports; no auth, no spawn, no live cortex needed. Kill-qualified via the
|
|
3730
|
-
# `acp-cortex` mutant lane under check-gate-qualification.
|
|
3731
|
-
section "ACP cortex backend (Snowflake Cortex Code — 1st non-claude adapter)"
|
|
3732
|
-
run_ts scripts/check-acp-cortex.ts
|
|
3684
|
+
# The old positive Cortex adapter contract is retired. Exercise the production
|
|
3685
|
+
# admission and direct adapter refusal without launching a vendor process.
|
|
3686
|
+
section "ACP Cortex disabled admission"
|
|
3687
|
+
run_vitest pi-extensions/lib/acp/cortex-admission.test.ts
|
|
3733
3688
|
}
|
|
3734
3689
|
|
|
3735
3690
|
check_pack() {
|
|
@@ -4129,7 +4084,7 @@ check_pack_win32_carrier() {
|
|
|
4129
4084
|
# which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
|
|
4130
4085
|
# below so a future suffix change cannot pass vacuously.)
|
|
4131
4086
|
pack_install_leaked_pi() {
|
|
4132
|
-
grep '^@earendil-works+' | grep -Ev '@1\.5\.
|
|
4087
|
+
grep '^@earendil-works+' | grep -Ev '@1\.5\.2(_|$)' || true
|
|
4133
4088
|
}
|
|
4134
4089
|
|
|
4135
4090
|
# Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
|
|
@@ -4146,18 +4101,18 @@ check_pack_pin_matcher() {
|
|
|
4146
4101
|
# Cell 1 — the version boundary. Every measured peer-hash shape is a fixture: the
|
|
4147
4102
|
# 0.84.4-era `_@modelcontextprotocol+sdk@…`, the `_ws@8.21.3` observed on a real
|
|
4148
4103
|
# 0.85.1 tree, and `[측정 2026-09-20]` the combined suffix a 0.86.0 install carries.
|
|
4149
|
-
# None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`1.5.
|
|
4104
|
+
# None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`1.5.2-beta.1`,
|
|
4150
4105
|
# the prerelease shape that an unanchored match would bless) and an off-pin version.
|
|
4151
4106
|
matcher_probe=$(printf '%s\n' \
|
|
4152
|
-
'@earendil-works+pi-ai@1.5.
|
|
4153
|
-
'@earendil-works+pi-ai@1.5.
|
|
4154
|
-
'@earendil-works+pi-ai@1.5.
|
|
4155
|
-
'@earendil-works+pi-ai@1.5.
|
|
4156
|
-
'@earendil-works+pi-ai@1.5.
|
|
4107
|
+
'@earendil-works+pi-ai@1.5.2' \
|
|
4108
|
+
'@earendil-works+pi-ai@1.5.2_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
|
|
4109
|
+
'@earendil-works+pi-ai@1.5.2_ws@8.21.3' \
|
|
4110
|
+
'@earendil-works+pi-ai@1.5.2_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
|
|
4111
|
+
'@earendil-works+pi-ai@1.5.2-beta.1' \
|
|
4157
4112
|
'@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
|
|
4158
|
-
if [ "$matcher_probe" != '@earendil-works+pi-ai@1.5.
|
|
4113
|
+
if [ "$matcher_probe" != '@earendil-works+pi-ai@1.5.2-beta.1
|
|
4159
4114
|
@earendil-works+pi-agent-core@0.87.1' ]; then
|
|
4160
|
-
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.5.
|
|
4115
|
+
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.5.2-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.5.2 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
|
|
4161
4116
|
return 1
|
|
4162
4117
|
fi
|
|
4163
4118
|
|
|
@@ -4165,9 +4120,9 @@ check_pack_pin_matcher() {
|
|
|
4165
4120
|
# (0.85.0 onward, still true at 0.99.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
|
|
4166
4121
|
# back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
|
|
4167
4122
|
matcher_probe=$(printf '%s\n' \
|
|
4168
|
-
'@earendil-works+chord@1.5.
|
|
4123
|
+
'@earendil-works+chord@1.5.2' \
|
|
4169
4124
|
'@earendil-works+chord@0.87.1' \
|
|
4170
|
-
'@earendil-works+pi-ai@1.5.
|
|
4125
|
+
'@earendil-works+pi-ai@1.5.2' | pack_install_leaked_pi)
|
|
4171
4126
|
if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
|
|
4172
4127
|
fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it was a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and pi-protocol — `[측정 2026-09-20]` at 0.86.0, re-measured `[측정 2026-09-22]` at 0.87.0, `[측정 2026-09-23]` at 0.87.1 and `[측정 2026-09-30]` at 0.99.1, where pi-coding-agent adds pi-codemode and pi-mcp beside them; `[측정 2026-10-13]` at 1.2.0 pi-durable alone declares it) — got: ${matcher_probe:-<nothing leaked>}"
|
|
4173
4128
|
return 1
|
|
@@ -5089,16 +5044,16 @@ _check_pack_install_impl() {
|
|
|
5089
5044
|
local install_log
|
|
5090
5045
|
install_log=$(cd "$tmp" && pnpm add \
|
|
5091
5046
|
"$tgz_path" \
|
|
5092
|
-
"@earendil-works/pi-ai@1.5.
|
|
5093
|
-
"@earendil-works/pi-coding-agent@1.5.
|
|
5094
|
-
"@earendil-works/pi-tui@1.5.
|
|
5095
|
-
"@earendil-works/pi-agent-core@1.5.
|
|
5096
|
-
"@earendil-works/pi-telemetry@1.5.
|
|
5097
|
-
"@earendil-works/chord@1.5.
|
|
5098
|
-
"@earendil-works/pi-codemode@1.5.
|
|
5099
|
-
"@earendil-works/pi-mcp@1.5.
|
|
5100
|
-
"@earendil-works/pi-durable@1.5.
|
|
5101
|
-
"@earendil-works/pi-schema@1.5.
|
|
5047
|
+
"@earendil-works/pi-ai@1.5.2" \
|
|
5048
|
+
"@earendil-works/pi-coding-agent@1.5.2" \
|
|
5049
|
+
"@earendil-works/pi-tui@1.5.2" \
|
|
5050
|
+
"@earendil-works/pi-agent-core@1.5.2" \
|
|
5051
|
+
"@earendil-works/pi-telemetry@1.5.2" \
|
|
5052
|
+
"@earendil-works/chord@1.5.2" \
|
|
5053
|
+
"@earendil-works/pi-codemode@1.5.2" \
|
|
5054
|
+
"@earendil-works/pi-mcp@1.5.2" \
|
|
5055
|
+
"@earendil-works/pi-durable@1.5.2" \
|
|
5056
|
+
"@earendil-works/pi-schema@1.5.2" \
|
|
5102
5057
|
--ignore-workspace --ignore-scripts 2>&1) || {
|
|
5103
5058
|
fail "[check-pack-install] pnpm add failed:"
|
|
5104
5059
|
echo "$install_log" | tail -10 | sed 's/^/ /' >&2
|
|
@@ -5107,17 +5062,17 @@ _check_pack_install_impl() {
|
|
|
5107
5062
|
|
|
5108
5063
|
# A pin is a wish until the resolved tree is read back. Assert it: EVERY
|
|
5109
5064
|
# @earendil-works package present — direct or transitive, top level or nested,
|
|
5110
|
-
# chord included — must be the pinned
|
|
5065
|
+
# chord included — must be the pinned 1.5.2. Anything else means an unpinned caret
|
|
5111
5066
|
# floated and the rest of this gate would be exercising a runtime nobody verified,
|
|
5112
|
-
# while still printing
|
|
5067
|
+
# while still printing a pinned runtime. Fail loud instead of proving the wrong floor.
|
|
5113
5068
|
local leaked_pi
|
|
5114
5069
|
leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
|
|
5115
5070
|
if [ -n "$leaked_pi" ]; then
|
|
5116
|
-
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.5.
|
|
5071
|
+
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.5.2):"
|
|
5117
5072
|
printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
|
|
5118
5073
|
return 1
|
|
5119
5074
|
fi
|
|
5120
|
-
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.5.
|
|
5075
|
+
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.5.2 (chord, pi-durable and pi-schema included)"
|
|
5121
5076
|
|
|
5122
5077
|
# Resolve the installed package.json and confirm pi.extensions
|
|
5123
5078
|
# arrived intact. If pi.extensions is empty or missing, the
|
|
@@ -5280,17 +5235,9 @@ _check_pack_install_impl() {
|
|
|
5280
5235
|
echo "$loader_out" | tail -10 | sed 's/^/ /' >&2
|
|
5281
5236
|
return 1
|
|
5282
5237
|
fi
|
|
5283
|
-
# Verify the EXACT
|
|
5284
|
-
#
|
|
5285
|
-
#
|
|
5286
|
-
# Why exact-set and not `grep -q <id>`: the cortex ids CONTAIN the claude ids
|
|
5287
|
-
# as substrings (`cortex-claude-sonnet-5` matches a bare `claude-sonnet-5`
|
|
5288
|
-
# grep), so a substring probe could go green on a surface that dropped every
|
|
5289
|
-
# unprefixed Claude row. And an anchor-only probe cannot see a MISSING cortex
|
|
5290
|
-
# row at all — that is exactly the installed-consumer gap this assertion
|
|
5291
|
-
# closes: the source-side sets are pinned by check-acp-cortex /
|
|
5292
|
-
# check-acp-provider-surface, but only THIS gate proves the ids survive the
|
|
5293
|
-
# pack → install → pi-loader path into a real consumer's model list.
|
|
5238
|
+
# Verify the EXACT supported surface the installed package registers: four
|
|
5239
|
+
# Claude rows, no retired Cortex row. A substring probe cannot prove absence.
|
|
5240
|
+
# This gate joins the source admission to pack → install → real Pi loader.
|
|
5294
5241
|
#
|
|
5295
5242
|
# Parse rows whose FIRST field is the provider `entwurf` (pi prints a
|
|
5296
5243
|
# `provider model context …` table; the header row's first field is
|
|
@@ -5299,14 +5246,11 @@ _check_pack_install_impl() {
|
|
|
5299
5246
|
loader_ids=$(awk '$1 == "entwurf" { print $2 }' <<<"$loader_out" | LC_ALL=C sort)
|
|
5300
5247
|
loader_expected=$(printf '%s\n' \
|
|
5301
5248
|
claude-fable-5-1 \
|
|
5249
|
+
claude-fable-5-2 \
|
|
5302
5250
|
claude-opus-5-5 \
|
|
5303
|
-
claude-sonnet-5-5
|
|
5304
|
-
cortex-auto \
|
|
5305
|
-
cortex-claude-opus-5 \
|
|
5306
|
-
cortex-claude-sonnet-5 \
|
|
5307
|
-
cortex-openai-gpt-5.4 | LC_ALL=C sort)
|
|
5251
|
+
claude-sonnet-5-5 | LC_ALL=C sort)
|
|
5308
5252
|
if [ "$loader_ids" != "$loader_expected" ]; then
|
|
5309
|
-
fail "[check-pack-install] installed provider model set drifted — expected exactly
|
|
5253
|
+
fail "[check-pack-install] installed provider model set drifted — expected exactly 4 Claude ids and no Cortex ids:"
|
|
5310
5254
|
echo " expected:" >&2
|
|
5311
5255
|
echo "$loader_expected" | sed 's/^/ /' >&2
|
|
5312
5256
|
echo " actual (entwurf rows):" >&2
|
|
@@ -5318,7 +5262,7 @@ _check_pack_install_impl() {
|
|
|
5318
5262
|
# Name the set on SUCCESS too, not only in the failure branch — same principle as
|
|
5319
5263
|
# the pinned-pi version assert above: a gate that cannot name what it proved has
|
|
5320
5264
|
# proved nothing, and a row COUNT without the ids cannot be audited from a CI log.
|
|
5321
|
-
echo "[check-pack-install] pi loader smoke pass (entwurf registered; exact curated set:
|
|
5265
|
+
echo "[check-pack-install] pi loader smoke pass (entwurf registered; exact curated set: 4 Claude ids, no Cortex):"
|
|
5322
5266
|
echo "$loader_ids" | sed 's/^/ /'
|
|
5323
5267
|
|
|
5324
5268
|
# npm-managed neutral install regression — the README's PRIMARY install path is
|
|
@@ -7629,10 +7573,8 @@ release_gate() {
|
|
|
7629
7573
|
# green cut said nothing about them. Under the P1 outcome protocol wiring
|
|
7630
7574
|
# them costs nothing on a host that cannot run them — they SKIP honestly and
|
|
7631
7575
|
# `--cut` names the missing prerequisite — while a host that CAN run them now
|
|
7632
|
-
# must. Cortex is
|
|
7633
|
-
#
|
|
7634
|
-
# that account; it stays a required DIRECT call for a Cortex-rail cut instead
|
|
7635
|
-
# (VERIFY 「Cortex is an on-demand axis」). Same for `smoke-acp-long-turn-live`,
|
|
7576
|
+
# must. Cortex ACP is disabled; its former direct smoke now refuses without
|
|
7577
|
+
# starting a probe. The excluded `smoke-acp-long-turn-live`,
|
|
7636
7578
|
# `smoke-agy-native-push-live` and `smoke-acp-ordering-probe-live`: >12min by
|
|
7637
7579
|
# construction, no aggregate-owned agy conversation id, and an opt-in instrument
|
|
7638
7580
|
# rather than acceptance. check-release-gate-outcomes pins that every LIVE smoke
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// Pure + temp-dir fs, no spawn/child — IN pnpm check.
|
|
16
16
|
|
|
17
17
|
import { strict as assert } from "node:assert";
|
|
18
|
-
import { mkdtempSync,
|
|
18
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
@@ -379,23 +379,6 @@ function ctxWith(firstUser: string) {
|
|
|
379
379
|
"[QK:CARRIER-FRAME-NAMES-CLAUDE-CARRIER] the claude frame must name the real carrier AND scope it to the engraving — " +
|
|
380
380
|
`saying only "this is not your system prompt" leaves the model to guess what the system prompt then is. Got: ${JSON.stringify(claudeAug.slice(0, 500))}`,
|
|
381
381
|
);
|
|
382
|
-
assert.ok(
|
|
383
|
-
cortexAug.includes("carries no system-prompt carrier at all") &&
|
|
384
|
-
!cortexAug.includes("does have a system-prompt carrier"),
|
|
385
|
-
"[QK:CARRIER-FRAME-DENIES-CORTEX-CARRIER] the cortex frame must deny the carrier — cortex's buildSessionMeta returns " +
|
|
386
|
-
`undefined so no _meta is sent; claiming a tiny carrier there would be a lie. Got: ${JSON.stringify(cortexAug.slice(0, 500))}`,
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
// The rail difference is not a doc claim — it is in the adapter source.
|
|
390
|
-
const adapterSrc = readFileSync(join(REPO_DIR, "pi-extensions/lib/acp/backend-adapter.ts"), "utf8");
|
|
391
|
-
const cortexAt = adapterSrc.indexOf('backend: "cortex"');
|
|
392
|
-
assert.ok(cortexAt > 0, "backend-adapter.ts still declares the cortex adapter");
|
|
393
|
-
assert.match(
|
|
394
|
-
adapterSrc.slice(cortexAt),
|
|
395
|
-
/buildSessionMeta\(\)\s*\{\s*return undefined;/,
|
|
396
|
-
"[QK:CARRIER-RAIL-DIFF-IS-SOURCE-PINNED] cortex's buildSessionMeta must still return undefined — the source fact the " +
|
|
397
|
-
"cortex frame's 'no carrier at all' sentence rests on. If this ever grows a carrier, the frame becomes a lie.",
|
|
398
|
-
);
|
|
399
382
|
|
|
400
383
|
// DISJOINT SURFACES: the thing the model is told is its system prompt (the
|
|
401
384
|
// engraving) must not contain the narrative it is told is user text. If the
|
|
@@ -559,9 +542,8 @@ console.log(
|
|
|
559
542
|
"signature change (stable carrier → stable signature); augment: prepended on `new` only (reuse delta has none), " +
|
|
560
543
|
"wire-only so it never enters contextMessageSignatures, entwurf cwd/AGENTS.md de-dup (present → drop only that " +
|
|
561
544
|
"section, home kept; absent → kept), day-granularity date, 50KB truncation marker, shipped AGENTS budget; " +
|
|
562
|
-
"provenance:
|
|
563
|
-
"tiny engraving-only carrier
|
|
564
|
-
"undefined), and carrier/augment stay disjoint; A-join: the carrier opens its own block after the SDK's fixed " +
|
|
545
|
+
"provenance: the augment is first-user-message text and not the system prompt, Claude names its " +
|
|
546
|
+
"tiny engraving-only carrier, and carrier/augment stay disjoint; A-join: the carrier opens its own block after the SDK's fixed " +
|
|
565
547
|
"sentence, the boundary is a loader constant (not the template's whitespace, which trim eats) and does not " +
|
|
566
548
|
"resurrect an opted-out carrier, the shipped carrier stays under the tiny budget, and the meta hop sends the " +
|
|
567
549
|
"loader string byte-for-byte so the wire and the reuse signature cannot desynchronize",
|
|
@@ -41,7 +41,21 @@ import { normalizeContext } from "@earendil-works/pi-ai";
|
|
|
41
41
|
import { isRetryableAssistantError } from "@earendil-works/pi-ai/compat";
|
|
42
42
|
import { assertOverlayInSandbox, sandboxAcpGateHome } from "./lib/acp-gate-home.ts";
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// #142: a Claude turn carries pi's thinking level, which the backend sets as effort and reads back
|
|
45
|
+
// before the prompt. This fixture runs every turn at one fixed level on rows carrying the curated
|
|
46
|
+
// level map, and its fake driver reads back whatever config option it was asked to set. The effort
|
|
47
|
+
// contract itself is certified in pi-extensions/lib/acp/backend.thinking.test.ts.
|
|
48
|
+
const FIXTURE_THINKING_LEVEL = "medium";
|
|
49
|
+
const FIXTURE_THINKING_LEVEL_MAP = {
|
|
50
|
+
off: null,
|
|
51
|
+
minimal: null,
|
|
52
|
+
low: "low",
|
|
53
|
+
medium: "medium",
|
|
54
|
+
high: "high",
|
|
55
|
+
xhigh: "xhigh",
|
|
56
|
+
max: "max",
|
|
57
|
+
};
|
|
58
|
+
const sonnet = { id: "claude-sonnet-5-5", thinkingLevelMap: FIXTURE_THINKING_LEVEL_MAP } as unknown as Model<Api>;
|
|
45
59
|
|
|
46
60
|
type Stream = AsyncIterable<AssistantMessageEvent>;
|
|
47
61
|
|
|
@@ -152,7 +166,7 @@ function makeHarness(recordDir: string) {
|
|
|
152
166
|
const makeConnection = (handlers: any) => ({
|
|
153
167
|
initialize: async () => ({ agentCapabilities: {} }),
|
|
154
168
|
newSession: async () => ({ sessionId: "ACP-1" }),
|
|
155
|
-
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id:
|
|
169
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: p.configId, currentValue: p.value }] }),
|
|
156
170
|
prompt: ({ sessionId }: any) => {
|
|
157
171
|
promptCount++;
|
|
158
172
|
notifier = (update) => handlers.sessionUpdate({ update, sessionId });
|
|
@@ -287,7 +301,14 @@ try {
|
|
|
287
301
|
const promptsOut = resolve(TMP_EMIT, "pi-extensions/lib/acp/prompts");
|
|
288
302
|
mkdirSync(promptsOut, { recursive: true });
|
|
289
303
|
copyFileSync("pi-extensions/lib/acp/prompts/engraving.md", resolve(promptsOut, "engraving.md"));
|
|
290
|
-
const
|
|
304
|
+
const backendModule = (await import(
|
|
305
|
+
pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend.js")).href
|
|
306
|
+
)) as any;
|
|
307
|
+
const backend = {
|
|
308
|
+
...backendModule,
|
|
309
|
+
streamAcpTurn: (model: unknown, context: unknown, options: Record<string, unknown> | undefined, deps: unknown) =>
|
|
310
|
+
backendModule.streamAcpTurn(model, context, { reasoning: FIXTURE_THINKING_LEVEL, ...options }, deps),
|
|
311
|
+
};
|
|
291
312
|
await assertOverlayInSandbox(TMP_EMIT, gateHome);
|
|
292
313
|
|
|
293
314
|
// ----------------------------------------------------------------------
|
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
ENTWURF_ACP_NO_AUTH_SENTINEL,
|
|
36
36
|
PROVIDER_ID,
|
|
37
37
|
SUPPORTED_ANTHROPIC_MODEL_IDS,
|
|
38
|
-
SUPPORTED_CORTEX_MODEL_IDS,
|
|
39
38
|
} from "../pi-extensions/lib/acp/models.ts";
|
|
40
39
|
|
|
41
40
|
// ---------------------------------------------------------------------------
|
|
@@ -103,10 +102,16 @@ try {
|
|
|
103
102
|
const mod = await import(entryUrl);
|
|
104
103
|
|
|
105
104
|
const calls: Array<{ id: string; cfg: CapturedCfg }> = [];
|
|
105
|
+
const events: string[] = [];
|
|
106
106
|
const fakePi = {
|
|
107
107
|
registerProvider(id: string, cfg: CapturedCfg) {
|
|
108
108
|
calls.push({ id, cfg });
|
|
109
109
|
},
|
|
110
|
+
// #163: the entry closes a session's Claude child on compaction. Its behaviour is proven in
|
|
111
|
+
// pi-extensions/lib/acp/backend.summary-turn.test.ts; here only the registration count.
|
|
112
|
+
on(event: string) {
|
|
113
|
+
events.push(event);
|
|
114
|
+
},
|
|
110
115
|
};
|
|
111
116
|
|
|
112
117
|
// Drive the REAL default export twice — the second call must be a no-op.
|
|
@@ -118,6 +123,7 @@ try {
|
|
|
118
123
|
1,
|
|
119
124
|
`entry must register exactly once across two calls (idempotency) — got ${calls.length}`,
|
|
120
125
|
);
|
|
126
|
+
assert.deepEqual(events, ["session_compact"], `entry must subscribe to session_compact exactly once — got ${events}`);
|
|
121
127
|
const cap = calls[0];
|
|
122
128
|
assert.equal(cap.id, PROVIDER_ID, `entry registered the wrong provider id: ${cap.id}`);
|
|
123
129
|
assert.equal(cap.cfg.apiKey, ENTWURF_ACP_NO_AUTH_SENTINEL, "entry apiKey is not the no-auth sentinel");
|
|
@@ -127,33 +133,18 @@ try {
|
|
|
127
133
|
assert.ok(capIds.includes(want), `entry model surface missing ${want} (got: ${capIds.join(", ") || "none"})`);
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
// Set comparison (sorted, exact) — not `includes`: a substring/anchor probe
|
|
139
|
-
// cannot see a row that VANISHED, and the cortex ids contain the claude ids as
|
|
140
|
-
// substrings. Expected is derived from the two source id constants so there is
|
|
141
|
-
// one SSOT; the literal 7 below is the independent floor that catches a
|
|
142
|
-
// curation list that SHRANK — expected derives from the same constants, so a
|
|
143
|
-
// dropped row moves expected and captured together and the set comparison
|
|
144
|
-
// stays green. That is why the count is asserted separately and carries its
|
|
145
|
-
// own claim: the cortex list has `CORTEX-CURATED-FOUR-ROWS` for exactly this
|
|
146
|
-
// failure, and the claude list needs its own oracle rather than borrowing it.
|
|
147
|
-
const expectedIds = [...SUPPORTED_ANTHROPIC_MODEL_IDS, ...SUPPORTED_CORTEX_MODEL_IDS].slice().sort();
|
|
148
|
-
assert.equal(
|
|
149
|
-
expectedIds.length,
|
|
150
|
-
7,
|
|
151
|
-
`[QK:CLAUDE-CURATED-THREE-ROWS] curated id constants must total 7 rows (claude 3 + cortex 4) — got ${expectedIds.length}: ${expectedIds.join(", ")}`,
|
|
136
|
+
// Independent literal oracle: the compiled provider exposes the four Claude
|
|
137
|
+
// rows and no retired Cortex row, even if a source list drifts with the SUT.
|
|
138
|
+
const expectedIds = ["claude-fable-5-1", "claude-fable-5-2", "claude-opus-5-5", "claude-sonnet-5-5"];
|
|
139
|
+
assert.deepEqual(
|
|
140
|
+
[...SUPPORTED_ANTHROPIC_MODEL_IDS].sort(),
|
|
141
|
+
expectedIds,
|
|
142
|
+
"[QK:CLAUDE-CURATED-FOUR-ROWS] the four Claude IDs are an independent literal oracle",
|
|
152
143
|
);
|
|
153
144
|
assert.deepEqual(
|
|
154
145
|
capIds.slice().sort(),
|
|
155
146
|
expectedIds,
|
|
156
|
-
`[QK:
|
|
147
|
+
`[QK:ACP-PROVIDER-CLAUDE-ONLY] compiled provider must expose exactly the four Claude rows — got: ${capIds.join(", ") || "none"}`,
|
|
157
148
|
);
|
|
158
149
|
assert.equal(typeof cap.cfg.streamSimple, "function", "entry streamSimple must be a function");
|
|
159
150
|
// Regression guard: the entry must wire the REAL backend (streamShellAcp),
|
|
@@ -200,7 +191,7 @@ assert.ok(!/backend-stub/.test(entrySrc), "entry must not import the removed S0
|
|
|
200
191
|
|
|
201
192
|
console.log(
|
|
202
193
|
`[check-acp-provider-surface] ok — compiled entry registers ${PROVIDER_ID} once (idempotent) with the no-auth ` +
|
|
203
|
-
`sentinel + the EXACT curated set
|
|
204
|
-
`${CURATED_ANCHOR_MODEL_ID}
|
|
194
|
+
`sentinel + the EXACT Claude-only curated set (${SUPPORTED_ANTHROPIC_MODEL_IDS.length} incl. ` +
|
|
195
|
+
`${CURATED_ANCHOR_MODEL_ID}) + real streamShellAcp backend (S2c); ` +
|
|
205
196
|
`claude lib surface verified live (${models.length} model(s))`,
|
|
206
197
|
);
|