@junghanacs/entwurf 0.12.10 → 0.13.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 +6 -3
- package/BASELINE.md +56 -163
- package/CHANGELOG.md +35 -0
- package/CONTRIBUTING.md +4 -2
- package/DELIVERY.md +117 -261
- package/README.md +74 -399
- package/VERIFY.md +59 -85
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +231 -0
- package/docs/external-mcp-host.md +132 -0
- package/docs/fresh-cut-policy.md +99 -0
- package/docs/setup-clean-host.md +123 -328
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +21 -48
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +155 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
- package/mcp/entwurf-bridge/src/index.ts +21 -48
- package/package.json +14 -9
- package/pi/settings.reference.json +1 -1
- package/pi-extensions/acp-provider.ts +20 -10
- package/pi-extensions/entwurf-control.ts +20 -49
- package/pi-extensions/lib/acp/acp-client.ts +22 -0
- package/pi-extensions/lib/acp/augment.ts +99 -2
- package/pi-extensions/lib/acp/backend-adapter.ts +190 -14
- package/pi-extensions/lib/acp/backend.ts +310 -37
- package/pi-extensions/lib/acp/config.ts +19 -5
- package/pi-extensions/lib/acp/engraving.ts +46 -2
- package/pi-extensions/lib/acp/event-mapper.ts +26 -16
- package/pi-extensions/lib/acp/models.ts +69 -7
- package/pi-extensions/lib/acp/overlay.ts +234 -5
- package/pi-extensions/lib/acp/tool-surface.ts +12 -4
- package/prompts/engraving.md +13 -5
- package/run.sh +386 -119
- package/scripts/check-acp-carrier-augment.ts +246 -10
- package/scripts/check-acp-cortex.ts +668 -0
- package/scripts/check-acp-prompt-lifecycle.ts +565 -0
- package/scripts/check-acp-provider-surface.ts +50 -6
- package/scripts/check-acp-sdk-surface.ts +60 -21
- package/scripts/check-acp-session-reuse.ts +64 -1
- package/scripts/check-acp-stop-reason.ts +342 -0
- package/scripts/check-entwurf-v2-surface.ts +41 -0
- package/scripts/check-gate-qualification.ts +7 -2
- package/scripts/check-probe-cli-shim.ts +879 -0
- package/scripts/check-probe-ordering.ts +2482 -0
- package/scripts/check-release-gate-outcomes.ts +332 -0
- package/scripts/check-shell-quote.ts +4 -4
- package/scripts/fixtures/probe-cli-shim +20 -0
- package/scripts/fixtures/probe-mcp-server.ts +168 -12
- package/scripts/lib/live-skip.ts +33 -0
- package/scripts/lib/probe-acp-turn.ts +228 -0
- package/scripts/lib/probe-cli-shim.ts +464 -0
- package/scripts/lib/probe-cli-target.ts +165 -0
- package/scripts/lib/probe-event-log.ts +383 -0
- package/scripts/lib/probe-verdict.ts +1213 -0
- package/scripts/lib/step-outcome.sh +88 -0
- package/scripts/mutants/acp-augment.json +106 -0
- package/scripts/mutants/acp-cortex.json +196 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
- package/scripts/mutants/acp-stop-reason.json +80 -0
- package/scripts/mutants/probe-ordering.json +1054 -0
- package/scripts/mutants/release-gate.json +105 -0
- package/scripts/mutants/v2-surface.json +26 -15
- package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
- package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
- package/scripts/smoke-acp-cortex-live.ts +386 -0
- package/scripts/smoke-acp-long-turn-live.ts +185 -0
- package/scripts/smoke-acp-mcp-live.ts +2 -2
- package/scripts/smoke-acp-memory-containment-live.ts +2 -2
- package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
- package/scripts/smoke-acp-overlay-live.ts +2 -2
- package/scripts/smoke-acp-provider-live.ts +2 -2
- package/scripts/smoke-acp-raw-turn-live.ts +3 -3
- package/scripts/smoke-acp-session-reuse-live.ts +2 -2
- package/scripts/smoke-acp-skill-live.ts +2 -2
- package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
- package/scripts/smoke-acp-v2-send-live.ts +4 -3
- package/scripts/smoke-agy-native-push-live.ts +4 -3
- package/scripts/smoke-claude-native-resume-live.sh +13 -3
- package/scripts/smoke-entwurf-chain-live.ts +352 -0
- package/scripts/smoke-entwurf-v2-matrix-live.ts +2 -2
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +2 -4
- package/scripts/smoke-meta-install-state.sh +4 -0
|
@@ -10,10 +10,18 @@
|
|
|
10
10
|
// if pi has excluded a built-in the backend will still expose, the declared
|
|
11
11
|
// surface diverges from the actual one — we fail fast rather than lie.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// The module's TWO jobs have different backend scopes — do not collapse them:
|
|
14
|
+
// - `buildClaudeSessionMeta` is claude-only by CONSTRUCTION, with exactly one
|
|
15
|
+
// caller (`claudeAdapter.buildSessionMeta`). Cortex exposes no
|
|
16
|
+
// `_meta.systemPrompt` and no tools/permission declaration surface (native
|
|
17
|
+
// tools + its own overlay mcp.json projection), so its adapter returns
|
|
18
|
+
// `undefined` and that function is never reached for a cortex turn.
|
|
19
|
+
// - `assertExcludeToolsHonored` is BACKEND-INVARIANT: backend.ts runs it for
|
|
20
|
+
// every adapter before any spawn, passing `backend: adapter.backend` and the
|
|
21
|
+
// resolved `config.tools`. A cortex turn DOES enter this module through it.
|
|
22
|
+
// That split is why the `backend` field exists: the honest-divergence logic stays
|
|
23
|
+
// explicit and the matrix gate can exercise both the "claude narrows via tools"
|
|
24
|
+
// and "native always exposes" branches.
|
|
17
25
|
//
|
|
18
26
|
// Carrier guard (NEXT §S2-scout 핀1): `buildClaudeSessionMeta` only attaches
|
|
19
27
|
// `_meta.systemPrompt` when a caller passes one. The shipped S2d path passes the
|
|
@@ -344,54 +344,27 @@ function abbreviateHomeMcp(cwd: string): string {
|
|
|
344
344
|
// sender replyability).
|
|
345
345
|
server.tool(
|
|
346
346
|
"entwurf_v2",
|
|
347
|
-
"CANONICAL DELIVERY SURFACE for garden ids
|
|
348
|
-
"
|
|
349
|
-
"
|
|
350
|
-
"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"
|
|
354
|
-
"
|
|
355
|
-
"
|
|
356
|
-
"
|
|
357
|
-
"
|
|
358
|
-
"
|
|
359
|
-
"
|
|
360
|
-
"
|
|
361
|
-
"
|
|
362
|
-
"
|
|
363
|
-
"
|
|
364
|
-
"
|
|
365
|
-
"
|
|
366
|
-
"
|
|
367
|
-
"
|
|
368
|
-
"wants_reply:true if you need an answer (wants_reply is NOT owned-outcome). Replies to a citizen " +
|
|
369
|
-
"with NO socket liveness (liveness=unsupported) are ALSO fire-and-forget, and the decider picks " +
|
|
370
|
-
"that citizen's own rail: a self-fetch backend (e.g. Claude Code) gets the meta-bridge mailbox, " +
|
|
371
|
-
"while a native-push backend (e.g. Antigravity) gets direct injection into its live conversation " +
|
|
372
|
-
"and has NO mailbox at all — do not assume mailbox semantics for every unsupported citizen. A " +
|
|
373
|
-
"native-push target IS measured by its own adapter probe, and that probe is THREE-valued, so " +
|
|
374
|
-
"the send is never silently queued: alive → injected; dead → rejected as " +
|
|
375
|
-
"native-push-target-dead; indeterminate → rejected as native-push-probe-indeterminate. " +
|
|
376
|
-
"Those two rejects stay distinct because 'we could not establish it' is not 'it is gone' — " +
|
|
377
|
-
"collapsing them reports a guess as a fact. THERE IS A " +
|
|
378
|
-
"THIRD RESULT, so do not read liveness=unsupported as 'reachable by some rail': the mailbox path " +
|
|
379
|
-
"delivers only to a DELIVERABLE citizen (a self-fetch backend whose receiver is live and armed), " +
|
|
380
|
-
"so a terminated Claude Code session — and any record whose backend has no adapter on this lane, " +
|
|
381
|
-
"e.g. codex — is rejected as mailbox-undeliverable rather than queued into an inbox nobody drains. " +
|
|
382
|
-
"unsupported means only 'this backend has no control-socket probe'. " +
|
|
383
|
-
"owned-outcome is ONLY for waking a DORMANT socket-domain citizen (spawn-bg resume, currently " +
|
|
384
|
-
"backend pi); on a live target it is rejected as owned-live-no-autosend. Neither self-fetch nor " +
|
|
385
|
-
"native-push has resume authority, but they reject under DIFFERENT reasons — self-fetch as " +
|
|
386
|
-
"backend-liveness-unsupported, native-push as native-push-no-resume-authority. " +
|
|
387
|
-
"It is NEVER auto-converted — so pick the right intent up front. " +
|
|
388
|
-
"mode applies to a CONTROL-SOCKET send only — it is the injection style for a live pi turn, and " +
|
|
389
|
-
"the mailbox, native-push, and spawn-bg plans carry no mode at all, so setting it for those " +
|
|
390
|
-
"targets changes nothing (a native-push send IS live and still ignores it). wants_reply rides " +
|
|
391
|
-
"every rail. Use entwurf_peers to discover targets. " +
|
|
392
|
-
"Payload guidance: message hard cap 16000 chars. For larger reviews/logs, write an " +
|
|
393
|
-
"artifact and dispatch its path plus a short digest; avoid multi-part sends because " +
|
|
394
|
-
"mailbox doorbells may coalesce.",
|
|
347
|
+
"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id " +
|
|
348
|
+
"alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport " +
|
|
349
|
+
"from liveness (live socket citizen → control-socket send; dormant socket citizen → spawn-bg resume; " +
|
|
350
|
+
"deliverable self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection " +
|
|
351
|
+
"into its conversation) and reports ONE outcome (delivered / rejected / lock-retained / " +
|
|
352
|
+
"delivered-but-lock-dirty). EXISTING targets only; discover with entwurf_peers. INTENT — picking wrong is " +
|
|
353
|
+
"rejected, never auto-converted. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A " +
|
|
354
|
+
"citizen with NO socket liveness (liveness=unsupported) is ALSO fire-and-forget — unsupported means only " +
|
|
355
|
+
'"no control-socket probe" — and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) ' +
|
|
356
|
+
"gets the mailbox, a native-push backend (e.g. Antigravity) gets direct injection and has NO mailbox at " +
|
|
357
|
+
"all. THERE IS A THIRD RESULT: the mailbox delivers only to a DELIVERABLE citizen, so a terminated " +
|
|
358
|
+
"session, or a backend with no adapter here (e.g. codex), is mailbox-undeliverable, not queued for an " +
|
|
359
|
+
"inbox nobody drains. The native-push probe is 3-valued: alive → injected; dead → " +
|
|
360
|
+
"native-push-target-dead; indeterminate → native-push-probe-indeterminate (unestablished ≠ gone). " +
|
|
361
|
+
"owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY — live target → " +
|
|
362
|
+
"owned-live-no-autosend, self-fetch → backend-liveness-unsupported, native-push → " +
|
|
363
|
+
"native-push-no-resume-authority. LOCK: taken for a control-socket-DOMAIN dispatch — the live send AND " +
|
|
364
|
+
"the dormant cell's spawn-bg resume, a separate transport that still runs under that domain's lock. The " +
|
|
365
|
+
"mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode " +
|
|
366
|
+
"applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message " +
|
|
367
|
+
"caps at 16000 chars; send an artifact path + digest for more.",
|
|
395
368
|
{
|
|
396
369
|
target: z.string().min(1).describe("Target garden id (use entwurf_peers to discover)"),
|
|
397
370
|
intent: z
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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": {
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"demo/demo.sh",
|
|
43
43
|
"demo/demo-baseline.sh",
|
|
44
44
|
"docs/setup-clean-host.md",
|
|
45
|
+
"docs/external-mcp-host.md",
|
|
46
|
+
"docs/fresh-cut-policy.md",
|
|
47
|
+
"docs/acp-backend-rail.md",
|
|
45
48
|
"docs/assets/entwurf-hero.jpg",
|
|
46
49
|
"docs/assets/entwurf-demo.gif",
|
|
47
50
|
"docs/assets/entwurf-doomemacs.gif",
|
|
@@ -71,23 +74,23 @@
|
|
|
71
74
|
"claudeCodeFloor": ">=2.1.217"
|
|
72
75
|
},
|
|
73
76
|
"dependencies": {
|
|
74
|
-
"@agentclientprotocol/claude-agent-acp": "0.
|
|
77
|
+
"@agentclientprotocol/claude-agent-acp": "0.64.0",
|
|
75
78
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
76
79
|
"@anthropic-ai/sdk": "0.100.1",
|
|
77
80
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
78
81
|
"zod": "^3.25.0 || ^4.0.0"
|
|
79
82
|
},
|
|
80
83
|
"peerDependencies": {
|
|
81
|
-
"@earendil-works/pi-ai": ">=0.
|
|
82
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
83
|
-
"@earendil-works/pi-tui": ">=0.
|
|
84
|
+
"@earendil-works/pi-ai": ">=0.83.0 <0.84",
|
|
85
|
+
"@earendil-works/pi-coding-agent": ">=0.83.0 <0.84",
|
|
86
|
+
"@earendil-works/pi-tui": ">=0.83.0 <0.84",
|
|
84
87
|
"typebox": "*"
|
|
85
88
|
},
|
|
86
89
|
"devDependencies": {
|
|
87
90
|
"@biomejs/biome": "^2.4.13",
|
|
88
|
-
"@earendil-works/pi-ai": "0.
|
|
89
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
90
|
-
"@earendil-works/pi-tui": "0.
|
|
91
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
92
|
+
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
93
|
+
"@earendil-works/pi-tui": "0.83.0",
|
|
91
94
|
"@types/node": "^24.3.0",
|
|
92
95
|
"husky": "^9.1.7",
|
|
93
96
|
"typescript": "^6.0.2"
|
|
@@ -121,6 +124,8 @@
|
|
|
121
124
|
"check-acp-carrier-augment": "./run.sh check-acp-carrier-augment",
|
|
122
125
|
"check-agy-permission-matrix": "./run.sh check-agy-permission-matrix",
|
|
123
126
|
"check-gate-qualification": "./run.sh check-gate-qualification",
|
|
127
|
+
"check-probe-ordering": "./run.sh check-probe-ordering",
|
|
128
|
+
"check-probe-cli-shim": "./run.sh check-probe-cli-shim",
|
|
124
129
|
"check-pack": "./run.sh check-pack",
|
|
125
130
|
"check-pack-install": "./run.sh check-pack-install",
|
|
126
131
|
"check-install-container": "./run.sh check-install-container",
|
|
@@ -130,7 +135,7 @@
|
|
|
130
135
|
"format": "biome check --write .",
|
|
131
136
|
"check-meta-manifest-schema": "./run.sh check-meta-manifest-schema",
|
|
132
137
|
"check-package-source-routing": "./run.sh check-package-source-routing",
|
|
133
|
-
"check": "pnpm lint && pnpm typecheck && ./run.sh check-shell-quote && ./run.sh check-install-surface && ./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-capability-bundle-reach && ./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-hook-launch-topology && ./run.sh check-meta-identity-consumers && ./run.sh check-meta-capability-source && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh smoke-meta-install-state && ./run.sh check-meta-doctor-oracle && ./run.sh smoke-agy-install-state && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./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-package-source-routing && ./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-install-preflight && ./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-control-rpc && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-boot && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-v2-spawn && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-spawn-production && ./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-agy-sender-identity && ./run.sh check-auth-boundary && ./run.sh check-acp-provider-surface && ./run.sh check-acp-sdk-surface && ./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-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-acp-carrier-augment && ./run.sh check-gate-qualification && ./run.sh check-pack"
|
|
138
|
+
"check": "pnpm lint && pnpm typecheck && ./run.sh check-shell-quote && ./run.sh check-install-surface && ./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-capability-bundle-reach && ./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-hook-launch-topology && ./run.sh check-meta-identity-consumers && ./run.sh check-meta-capability-source && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh smoke-meta-install-state && ./run.sh check-meta-doctor-oracle && ./run.sh smoke-agy-install-state && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./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-package-source-routing && ./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-install-preflight && ./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-control-rpc && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-boot && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-v2-spawn && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-spawn-production && ./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-agy-sender-identity && ./run.sh check-auth-boundary && ./run.sh check-acp-provider-surface && ./run.sh check-acp-sdk-surface && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-acp-carrier-augment && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex && ./run.sh check-gate-qualification && ./run.sh check-pack"
|
|
134
139
|
},
|
|
135
140
|
"pi": {
|
|
136
141
|
"extensions": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"WebFetch",
|
|
33
33
|
"WebSearch"
|
|
34
34
|
],
|
|
35
|
-
"
|
|
35
|
+
"cortexConnection": "MY_SNOWFLAKE_CONNECTION",
|
|
36
36
|
"mcpServers": {
|
|
37
37
|
"entwurf-bridge": {
|
|
38
38
|
"command": "/path/to/entwurf/mcp/entwurf-bridge/start.sh",
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This is the pi-extension entry point that registers `entwurf` as a pi
|
|
4
4
|
// session provider/model. It is intentionally THIN: it stands up the provider
|
|
5
|
-
// surface (curated
|
|
6
|
-
// the real ACP backend (lib/acp/backend.ts —
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// surface (every registered adapter's curated rows + the no-auth sentinel) and
|
|
6
|
+
// wires streamSimple to the real ACP backend (lib/acp/backend.ts — a
|
|
7
|
+
// spawn-per-turn ACP child, `claude-agent-acp` or `cortex acp serve` depending
|
|
8
|
+
// on which adapter the model id routes to). It does NOT build a
|
|
9
|
+
// socket/peers/citizen protocol or touch the v2 core — socket-citizenship is
|
|
10
|
+
// supplied by the host `--entwurf-control` pi session (AGENTS §ACP Plugin
|
|
11
|
+
// Boundary).
|
|
12
|
+
//
|
|
13
|
+
// The model set comes from `allCuratedModels()` — the union across the adapter
|
|
14
|
+
// registry (backend-adapter.ts), NOT a per-backend list spelled here. Adding a
|
|
15
|
+
// backend therefore never edits this file. check-acp-provider-surface pins that
|
|
16
|
+
// the compiled entry really registers the EXACT union
|
|
17
|
+
// ([QK:CORTEX-PROVIDER-SIX-ROW-SURFACE]).
|
|
10
18
|
//
|
|
11
19
|
// Fence: this entry rides the emit-capable root tsconfig (it is not in the root
|
|
12
20
|
// `exclude` list); its lib modules are imported with `.js` suffixes (the root
|
|
@@ -49,14 +57,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
49
57
|
apiKey: ENTWURF_ACP_NO_AUTH_SENTINEL,
|
|
50
58
|
api: "entwurf",
|
|
51
59
|
models: allCuratedModels(),
|
|
52
|
-
// S2c: real ACP backend. Spawn-per-turn
|
|
53
|
-
//
|
|
54
|
-
// provider path is open. Backend auth still
|
|
55
|
-
//
|
|
60
|
+
// S2c: real ACP backend. Spawn-per-turn ACP child + event mapping
|
|
61
|
+
// (lib/acp/backend.ts); the routed adapter decides WHICH child. The S0
|
|
62
|
+
// fail-loud stub is gone — the provider path is open. Backend auth still
|
|
63
|
+
// belongs to the operator's own backend CLI child (no-auth sentinel above);
|
|
64
|
+
// this plugin only orchestrates.
|
|
56
65
|
streamSimple: streamShellAcp,
|
|
57
66
|
});
|
|
58
67
|
|
|
59
|
-
// Mark only AFTER a successful registration. If
|
|
68
|
+
// Mark only AFTER a successful registration. If allCuratedModels() (which
|
|
69
|
+
// fail-fasts on an unowned/duplicate curated id, and whose claude rows carry a
|
|
60
70
|
// fail-loud anchor check) or registerProvider throws, the runtime is not left
|
|
61
71
|
// poisoned with a "registered" marker — a retry can register cleanly.
|
|
62
72
|
markRegisteredOnRuntime(pi);
|
|
@@ -1322,55 +1322,26 @@ function registerEntwurfV2Tool(pi: ExtensionAPI): void {
|
|
|
1322
1322
|
registerTool({
|
|
1323
1323
|
name: "entwurf_v2",
|
|
1324
1324
|
label: "Dispatch (v2)",
|
|
1325
|
-
description: `CANONICAL
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
self-fetch
|
|
1329
|
-
and
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
-
|
|
1343
|
-
-
|
|
1344
|
-
|
|
1345
|
-
- mode: steer or follow_up for a CONTROL-SOCKET send (optional). The mailbox, native-push, and
|
|
1346
|
-
spawn-bg plans carry no mode, so it has no effect on those rails — a native-push send IS live and
|
|
1347
|
-
still ignores it.
|
|
1348
|
-
- wants_reply: reply hint; it rides every rail (optional, default false).
|
|
1349
|
-
|
|
1350
|
-
CHOOSING INTENT (picking wrong is rejected, never auto-fixed): to message / reply / hand off a peer
|
|
1351
|
-
that entwurf_peers shows as liveness=alive (a live socket citizen, currently backend pi), use intent:
|
|
1352
|
-
fire-and-forget — it routes to the live control-socket; set wants_reply:true if you need an answer
|
|
1353
|
-
(wants_reply is NOT owned-outcome). Replies to a citizen with NO socket liveness
|
|
1354
|
-
(liveness=unsupported) are ALSO fire-and-forget, and the decider picks that citizen's own rail: a
|
|
1355
|
-
self-fetch backend (e.g. Claude Code) gets the meta-bridge mailbox, while a native-push backend
|
|
1356
|
-
(e.g. Antigravity) gets direct injection into its live conversation and has NO mailbox at all — do
|
|
1357
|
-
not assume mailbox semantics for every unsupported citizen. A native-push target IS measured by its
|
|
1358
|
-
own adapter probe, and that probe is THREE-valued, so the send is never silently queued: alive →
|
|
1359
|
-
injected; dead → rejected as native-push-target-dead; indeterminate → rejected as
|
|
1360
|
-
native-push-probe-indeterminate. Those last two stay separate on purpose — "we could not establish
|
|
1361
|
-
it" is not "it is gone". THERE IS A THIRD RESULT, so do not read
|
|
1362
|
-
liveness=unsupported as "reachable by some rail": the mailbox path delivers only to a DELIVERABLE
|
|
1363
|
-
citizen (a self-fetch backend whose receiver is live and armed), so a terminated Claude Code session —
|
|
1364
|
-
and any record whose backend has no adapter on this lane, e.g. codex — is rejected as
|
|
1365
|
-
mailbox-undeliverable rather than queued into an inbox nobody drains. unsupported means only "this
|
|
1366
|
-
backend has no control-socket probe".
|
|
1367
|
-
owned-outcome is ONLY for waking a DORMANT socket-domain citizen (spawn-bg resume, currently backend
|
|
1368
|
-
pi); on a live target it is rejected as owned-live-no-autosend. Neither self-fetch nor native-push
|
|
1369
|
-
has resume authority, but they reject under DIFFERENT reasons — self-fetch as
|
|
1370
|
-
backend-liveness-unsupported, native-push as native-push-no-resume-authority.
|
|
1371
|
-
It is NEVER auto-converted — so pick the right intent up front.
|
|
1372
|
-
|
|
1373
|
-
The decider — not this surface — chooses the transport.`,
|
|
1325
|
+
description: `CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id alone
|
|
1326
|
+
does not say which rail that citizen answers on. Give target + intent; the decider picks transport from
|
|
1327
|
+
liveness (live socket citizen → control-socket send; dormant socket citizen → spawn-bg resume; deliverable
|
|
1328
|
+
self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection into its
|
|
1329
|
+
conversation) and reports ONE outcome (delivered / rejected / lock-retained / delivered-but-lock-dirty).
|
|
1330
|
+
EXISTING targets only; discover with entwurf_peers. INTENT — picking wrong is rejected, never
|
|
1331
|
+
auto-converted. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A citizen with NO socket
|
|
1332
|
+
liveness (liveness=unsupported) is ALSO fire-and-forget — unsupported means only "no control-socket probe" —
|
|
1333
|
+
and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) gets the mailbox, a native-push
|
|
1334
|
+
backend (e.g. Antigravity) gets direct injection and has NO mailbox at all. THERE IS A THIRD RESULT: the
|
|
1335
|
+
mailbox delivers only to a DELIVERABLE citizen, so a terminated session, or a backend with no adapter here
|
|
1336
|
+
(e.g. codex), is mailbox-undeliverable, not queued for an inbox nobody drains. The native-push probe is
|
|
1337
|
+
3-valued: alive → injected; dead → native-push-target-dead; indeterminate → native-push-probe-indeterminate
|
|
1338
|
+
(unestablished ≠ gone). owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY — live
|
|
1339
|
+
target → owned-live-no-autosend, self-fetch → backend-liveness-unsupported, native-push →
|
|
1340
|
+
native-push-no-resume-authority. LOCK: taken for a control-socket-DOMAIN dispatch — the live send AND the
|
|
1341
|
+
dormant cell's spawn-bg resume, a separate transport that still runs under that domain's lock. The mailbox
|
|
1342
|
+
and native-push rails are lock-free — deliverability and the adapter probe guard them. mode applies to a
|
|
1343
|
+
CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message caps at 16000
|
|
1344
|
+
chars; send an artifact path + digest for more.`,
|
|
1374
1345
|
parameters: entwurfV2Parameters,
|
|
1375
1346
|
async execute(
|
|
1376
1347
|
_toolCallId: string,
|
|
@@ -26,6 +26,17 @@ export interface AcpConnectionLike {
|
|
|
26
26
|
newSession(params: unknown): Promise<{ sessionId?: string }>;
|
|
27
27
|
prompt(params: { sessionId: string; prompt: AcpTextBlock[] }): Promise<{ stopReason?: string }>;
|
|
28
28
|
setSessionConfigOption?(params: unknown): Promise<unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* ACP `session/cancel` — the PROTOCOL way to end an in-flight prompt turn.
|
|
31
|
+
*
|
|
32
|
+
* The spec requires the agent to answer the pending `session/prompt` with
|
|
33
|
+
* `stopReason: "cancelled"` after this notification, so a user abort ends the
|
|
34
|
+
* turn as a protocol event (backend.ts maps cancelled → aborted) instead of a
|
|
35
|
+
* signal race. Fire-and-forget by contract: it is a JSON-RPC notification, so
|
|
36
|
+
* there is nothing to await and a send failure on an already-closed connection
|
|
37
|
+
* must not mask the abort the caller is executing.
|
|
38
|
+
*/
|
|
39
|
+
cancel?(params: { sessionId: string }): void;
|
|
29
40
|
/**
|
|
30
41
|
* Closes the underlying SDK connection before child process teardown. With
|
|
31
42
|
* the fluent SDK connection this is load-bearing: otherwise a successful
|
|
@@ -76,6 +87,17 @@ export function connectAcpClient(stream: Stream, handlers: AcpClientHandlers): A
|
|
|
76
87
|
prompt: (params) =>
|
|
77
88
|
agent.request(AGENT_METHODS.session_prompt, params as never) as Promise<{ stopReason?: string }>,
|
|
78
89
|
setSessionConfigOption: (params) => agent.request(AGENT_METHODS.session_set_config_option, params as never),
|
|
90
|
+
cancel: (params) => {
|
|
91
|
+
// Notification, not a request: nothing resolves it, and the connection
|
|
92
|
+
// may already be closing when the operator aborts. Swallow both the sync
|
|
93
|
+
// throw and the rejected send — the caller's abort path continues either
|
|
94
|
+
// way (it escalates to teardown after a bounded grace).
|
|
95
|
+
try {
|
|
96
|
+
void Promise.resolve(agent.notify(AGENT_METHODS.session_cancel, params as never)).catch(() => {});
|
|
97
|
+
} catch {
|
|
98
|
+
// connection already closed — the abort path escalates on its own.
|
|
99
|
+
}
|
|
100
|
+
},
|
|
79
101
|
close: (error) => {
|
|
80
102
|
// Best-effort by contract (see AcpConnectionLike.close): a teardown-path
|
|
81
103
|
// close that threw would mask the turn's real error and skip the child
|
|
@@ -30,14 +30,71 @@
|
|
|
30
30
|
|
|
31
31
|
import { existsSync, readFileSync } from "node:fs";
|
|
32
32
|
import { homedir } from "node:os";
|
|
33
|
-
import { join } from "node:path";
|
|
33
|
+
import { join, resolve } from "node:path";
|
|
34
34
|
import { ENTWURF_PROJECT_CONTEXT_OPEN_TAG } from "../../../protocol.js";
|
|
35
35
|
import type { AcpTextBlock } from "./context.js";
|
|
36
36
|
|
|
37
37
|
const MAX_AUGMENT_BYTES = 50 * 1024;
|
|
38
38
|
|
|
39
|
+
// Carrier-less backends expose no `_meta.systemPrompt` (docs/acp-backend-rail.md,
|
|
40
|
+
// “Cortex Code audit”). claude folds the operator engraving into that carrier; a carrier-less
|
|
41
|
+
// backend (cortex, the first — precisely: SYSTEM-PROMPT-carrier-less, since
|
|
42
|
+
// cortex does read `_meta` for an unpromoted caller-session-id seam)
|
|
43
|
+
// has nowhere to put it — so its operator engraving
|
|
44
|
+
// rides HERE, prepended as the LEADING section of the first-user augment. It
|
|
45
|
+
// travels on the WIRE only (new-only, never the config signature), so an
|
|
46
|
+
// engraving change applies to the NEXT new cortex session rather than
|
|
47
|
+
// invalidating a live reused one — the documented asymmetry vs claude, where the
|
|
48
|
+
// carrier feeds bridgeConfigSignature.
|
|
49
|
+
const CARRIER_LESS_BACKENDS: ReadonlySet<string> = new Set(["cortex"]);
|
|
50
|
+
|
|
51
|
+
// The one sentence both rails owe the model: WHAT this block is and what it is
|
|
52
|
+
// NOT. Shared so neither rail's frame can quietly lose it (and so the gate has a
|
|
53
|
+
// single anchor to mutate). Everything after it is rail-specific, because the
|
|
54
|
+
// rails really do differ — see the frame in buildPiContextAugment.
|
|
55
|
+
const PROVENANCE_LEAD =
|
|
56
|
+
"This block is prepended to the FIRST USER MESSAGE of this session. It is not your system prompt.";
|
|
57
|
+
|
|
58
|
+
// The env var an operator points at a personal engraving file. Shared surface
|
|
59
|
+
// with the claude carrier (engraving.ts) BY NAME, but read INLINE here (not
|
|
60
|
+
// imported): augment.ts is loaded raw by the strip-types carrier-augment gate,
|
|
61
|
+
// which cannot resolve a `.js` VALUE import of the sibling `.ts` engraving
|
|
62
|
+
// module (augment.ts only value-imports real `.js` files). So the carrier-less
|
|
63
|
+
// override read is self-contained. Unlike engraving.ts's shipped-default path,
|
|
64
|
+
// this ONLY ever reads the operator OVERRIDE file: the shipped claude default
|
|
65
|
+
// (a preset-replacement / auto-memory lever) is meaningless for a non-claude
|
|
66
|
+
// backend and is never injected into a carrier-less augment.
|
|
67
|
+
const ENGRAVING_OVERRIDE_ENV = "ENTWURF_ACP_ENGRAVING_PATH";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Render the operator engraving OVERRIDE for a carrier-less backend, or null
|
|
71
|
+
* when unset / empty / whitespace / unreadable. Pure w.r.t. (override file
|
|
72
|
+
* contents, backend, sorted mcpServerNames). Mirrors engraving.ts's `{{backend}}`
|
|
73
|
+
* / `{{mcp_servers}}` token substitution so the operator's file behaves the same
|
|
74
|
+
* on the augment as it would on the claude carrier.
|
|
75
|
+
*/
|
|
76
|
+
function loadCarrierlessOperatorEngraving(backend: string, mcpServerNames: readonly string[]): string | null {
|
|
77
|
+
const envPath = process.env[ENGRAVING_OVERRIDE_ENV]?.trim();
|
|
78
|
+
if (!envPath) return null;
|
|
79
|
+
let source: string;
|
|
80
|
+
try {
|
|
81
|
+
source = readFileSync(resolve(envPath), "utf8");
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const names = [...mcpServerNames].sort();
|
|
86
|
+
const mcpList = names.length > 0 ? names.join(", ") : "(none registered)";
|
|
87
|
+
const rendered = source
|
|
88
|
+
.replace(/\{\{backend\}\}/g, backend)
|
|
89
|
+
.replace(/\{\{mcp_servers\}\}/g, mcpList)
|
|
90
|
+
.trim();
|
|
91
|
+
return rendered.length > 0 ? rendered : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
39
94
|
export interface PiContextAugmentParams {
|
|
40
|
-
/**
|
|
95
|
+
/** The routed backend ("claude" | "cortex"). Load-bearing, not decorative: the
|
|
96
|
+
* narrative names it AND `CARRIER_LESS_BACKENDS` keys off it to decide whether
|
|
97
|
+
* the operator engraving leads the augment. */
|
|
41
98
|
backend: string;
|
|
42
99
|
cwd: string;
|
|
43
100
|
mcpServerNames: readonly string[];
|
|
@@ -58,6 +115,46 @@ export function buildPiContextAugment(params: PiContextAugmentParams): string {
|
|
|
58
115
|
|
|
59
116
|
const sections: string[] = [];
|
|
60
117
|
|
|
118
|
+
// System-prompt-carrier-less backend (cortex): the operator engraving leads the augment,
|
|
119
|
+
// since there is no `_meta.systemPrompt` carrier to hold it. null when no
|
|
120
|
+
// override is configured — the shipped claude default is never injected here.
|
|
121
|
+
if (CARRIER_LESS_BACKENDS.has(params.backend)) {
|
|
122
|
+
const engraving = loadCarrierlessOperatorEngraving(params.backend, params.mcpServerNames);
|
|
123
|
+
if (engraving) sections.push(engraving);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// PROVENANCE FRAME — leads the bridge narrative, and it is not decoration.
|
|
127
|
+
//
|
|
128
|
+
// Measured 2026-07-30: asked where its instructions came from, the Claude ACP
|
|
129
|
+
// model asserted that the two paragraphs below were its system prompt. They
|
|
130
|
+
// are not — they are text prepended to its FIRST USER MESSAGE. Nothing in the
|
|
131
|
+
// block said otherwise, and a model has no other way to tell: on the wire a
|
|
132
|
+
// long first user message and a system prompt read the same. So the block now
|
|
133
|
+
// states its own provenance, per rail, because the rails genuinely differ
|
|
134
|
+
// (backend-adapter.ts: claude's buildSessionMeta carries `_meta.systemPrompt`,
|
|
135
|
+
// cortex's returns undefined so no `_meta` key is sent at all).
|
|
136
|
+
//
|
|
137
|
+
// Honest about scope: this makes the boundary STATEABLE, not enforced. It
|
|
138
|
+
// tells the model what it cannot otherwise observe; it does not stop a model
|
|
139
|
+
// from guessing anyway.
|
|
140
|
+
sections.push(
|
|
141
|
+
CARRIER_LESS_BACKENDS.has(params.backend)
|
|
142
|
+
? [
|
|
143
|
+
"# entwurf: where this text comes from",
|
|
144
|
+
"",
|
|
145
|
+
PROVENANCE_LEAD,
|
|
146
|
+
`The ${params.backend} rail carries no system-prompt carrier at all — entwurf sends no \`_meta.systemPrompt\`, so everything entwurf tells you, including any operator engraving above, arrives here as user-message text.`,
|
|
147
|
+
"If you are asked what your system prompt says, do not quote this block as one.",
|
|
148
|
+
].join("\n")
|
|
149
|
+
: [
|
|
150
|
+
"# entwurf: where this text comes from",
|
|
151
|
+
"",
|
|
152
|
+
PROVENANCE_LEAD,
|
|
153
|
+
`The ${params.backend} rail does have a system-prompt carrier (\`_meta.systemPrompt\`), and entwurf keeps it deliberately tiny: it carries the operator engraving only — never this narrative, never AGENTS.md.`,
|
|
154
|
+
"So: system prompt = the short engraving; everything below = first-user-message text. If you are asked what your system prompt says, do not quote this block as one.",
|
|
155
|
+
].join("\n"),
|
|
156
|
+
);
|
|
157
|
+
|
|
61
158
|
// Bridge identity + caller-side sibling stance. The closing line is a
|
|
62
159
|
// caller-perspective statement: when THIS session throws an entwurf, the peer
|
|
63
160
|
// on the other end is a sibling, not a worker.
|