@junghanacs/entwurf 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +52 -17
- package/BASELINE.md +60 -206
- package/CHANGELOG.md +64 -0
- package/CONTRIBUTING.md +14 -10
- package/DELIVERY.md +118 -261
- package/README.md +91 -431
- package/VERIFY.md +74 -95
- package/demo/README.md +1 -1
- package/demo/demo-baseline.sh +1 -3
- package/demo/demo.sh +2 -5
- package/docs/acp-backend-rail.md +236 -0
- package/docs/external-mcp-host.md +131 -0
- package/docs/fresh-cut-policy.md +99 -0
- package/docs/setup-clean-host.md +124 -328
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
- 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 +11 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
- package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
- package/mcp/entwurf-bridge/src/index.ts +176 -67
- package/mcp/entwurf-bridge/start.sh +2 -2
- package/mcp/entwurf-bridge/test.sh +23 -9
- package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
- package/package.json +25 -11
- package/pi-extensions/entwurf-control.ts +220 -71
- package/pi-extensions/lib/acp/acp-client.ts +22 -0
- package/pi-extensions/lib/acp/augment.ts +42 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
- package/pi-extensions/lib/acp/backend.ts +366 -38
- package/pi-extensions/lib/acp/config.ts +1 -1
- package/pi-extensions/lib/acp/engraving.ts +43 -1
- package/pi-extensions/lib/acp/event-mapper.ts +16 -13
- package/pi-extensions/lib/acp/models.ts +4 -4
- package/pi-extensions/lib/acp/overlay.ts +11 -9
- package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
- package/pi-extensions/lib/entwurf-core.ts +15 -15
- package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
- package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
- package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
- package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
- package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
- package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
- package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
- package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
- package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
- package/pi-extensions/lib/meta-session.ts +93 -5
- package/pi-extensions/lib/mux-fresh-call.ts +328 -0
- package/pi-extensions/lib/mux-launch.ts +267 -0
- package/pi-extensions/lib/mux-placement.ts +387 -0
- package/pi-extensions/lib/mux-resume-call.ts +221 -0
- package/pi-extensions/lib/resume-launch-identity.ts +162 -0
- package/pi-extensions/lib/session-id.js +8 -5
- package/pi-extensions/lib/socket-discovery.ts +3 -3
- package/prompts/engraving.md +13 -5
- package/run.sh +688 -282
- package/scripts/agy-bridge-config.py +5 -1
- package/scripts/check-acp-backend-preflight.ts +1 -1
- package/scripts/check-acp-carrier-augment.ts +246 -10
- package/scripts/check-acp-cortex.ts +5 -5
- package/scripts/check-acp-overlay.ts +13 -3
- package/scripts/check-acp-prompt-lifecycle.ts +565 -0
- package/scripts/check-acp-stop-reason.ts +342 -0
- package/scripts/check-acp-stream-hooks.ts +504 -0
- package/scripts/check-elapsed.sh +25 -0
- package/scripts/check-entwurf-bridge-boot.ts +51 -4
- package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
- package/scripts/check-entwurf-control-rpc.ts +4 -3
- package/scripts/check-entwurf-resume-args.ts +72 -70
- package/scripts/check-entwurf-session-identity.ts +14 -10
- package/scripts/check-entwurf-v2-contract.ts +34 -59
- package/scripts/check-entwurf-v2-decider.ts +17 -177
- package/scripts/check-entwurf-v2-lock.ts +5 -2
- package/scripts/check-entwurf-v2-matrix.ts +3 -53
- package/scripts/check-entwurf-v2-production.ts +2 -91
- package/scripts/check-entwurf-v2-release.ts +10 -105
- package/scripts/check-entwurf-v2-runner.ts +4 -85
- package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
- package/scripts/check-entwurf-v2-send.ts +0 -28
- package/scripts/check-entwurf-v2-surface.ts +198 -128
- package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
- package/scripts/check-fresh-cut-gate.sh +1 -1
- package/scripts/check-gate-qualification.ts +101 -7
- package/scripts/check-install-container.sh +10 -2
- package/scripts/check-install-surface.ts +1 -1
- package/scripts/check-keyset-overlap.py +1 -1
- package/scripts/check-meta-facts.ts +249 -0
- package/scripts/check-meta-identity-consumers.ts +1 -1
- package/scripts/check-meta-session.ts +169 -0
- package/scripts/check-mux-launch-tmux.ts +316 -0
- package/scripts/check-mux-launch.ts +288 -0
- package/scripts/check-mux-launcher-fence.ts +264 -0
- package/scripts/check-mux-parent-artifact.ts +195 -0
- package/scripts/check-mux-placement-tmux.ts +322 -0
- package/scripts/check-mux-placement.ts +323 -0
- package/scripts/check-mux-resume-call.ts +282 -0
- package/scripts/check-probe-cli-shim.ts +25 -22
- package/scripts/check-probe-ordering.ts +121 -81
- package/scripts/check-release-gate-outcomes.ts +452 -0
- package/scripts/check-resume-launch-identity.ts +244 -0
- package/scripts/check-socket-discovery.ts +1 -1
- package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
- package/scripts/inventory-verification-surface.ts +349 -0
- package/scripts/lib/claude-launcher-fence.ts +322 -0
- package/scripts/lib/live-skip.ts +33 -0
- package/scripts/lib/mutation-qualify.ts +109 -3
- package/scripts/lib/probe-acp-turn.ts +33 -12
- package/scripts/lib/step-outcome.sh +88 -0
- package/scripts/meta-bridge-doctor.sh +6 -8
- package/scripts/meta-facts.ts +60 -0
- package/scripts/mutants/acp-augment.json +106 -0
- package/scripts/mutants/acp-cortex.json +2 -2
- package/scripts/mutants/acp-overlay.json +17 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
- package/scripts/mutants/acp-stop-reason.json +80 -0
- package/scripts/mutants/acp-stream-hooks.json +158 -0
- package/scripts/mutants/bridge-boot-resume.json +45 -0
- package/scripts/mutants/meta-facts.json +50 -0
- package/scripts/mutants/meta-identity.json +36 -0
- package/scripts/mutants/mux-boundary.json +196 -0
- package/scripts/mutants/mux-fresh-call.json +185 -0
- package/scripts/mutants/mux-launcher-fence.json +123 -0
- package/scripts/mutants/mux-parent-artifact.json +39 -0
- package/scripts/mutants/mux-resume-call.json +148 -0
- package/scripts/mutants/probe-ordering.json +6 -1021
- package/scripts/mutants/release-gate.json +140 -0
- package/scripts/mutants/resume-args.json +76 -0
- package/scripts/mutants/resume-launch-identity.json +96 -0
- package/scripts/mutants/v2-surface.json +74 -23
- package/scripts/mutants/v2-visible-resume.json +215 -0
- package/scripts/new-session-id.ts +9 -4
- 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 +8 -14
- 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 +2 -2
- 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 +10 -20
- 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 +3 -3
- package/scripts/smoke-meta-honesty.sh +1 -1
- package/scripts/smoke-meta-install-state.sh +4 -0
- package/scripts/smoke-mux-fresh-call-live.ts +365 -0
- package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
- package/scripts/smoke-pi-attach.ts +1 -1
- package/scripts/smoke-user-scope-citizen.sh +1 -1
- package/scripts/tsconfig.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
- package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
- package/scripts/check-acp-sdk-surface.ts +0 -236
- package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
- package/scripts/check-entwurf-v2-spawn.ts +0 -399
- package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -469
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
* - Register the canonical `entwurf_v2` dispatch tool for existing garden citizens.
|
|
26
26
|
* - Expose `entwurf_peers` facts for operator inspection (#50 C4: the socket-scan
|
|
27
27
|
* `/entwurf-sessions` command is gone — the record listing is the only surface).
|
|
28
|
-
* - Maintain the resident control socket used by v2 live-send
|
|
28
|
+
* - Maintain the resident control socket used by the v2 live-send path.
|
|
29
29
|
* - Attach this pi session to its meta-record at session_start (#50 C2) and key
|
|
30
30
|
* the control socket on the record's gardenId.
|
|
31
31
|
*
|
|
32
32
|
* Send-is-throw still applies at the control-socket protocol layer: a `send` RPC
|
|
33
33
|
* ack confirms the receiver enqueued the message (`message_processed` semantics)
|
|
34
34
|
* and does not wait for a peer turn result. Public v1 send surfaces were removed;
|
|
35
|
-
* callers use `entwurf_v2`, whose decider chooses send /
|
|
35
|
+
* callers use `entwurf_v2`, whose decider chooses control-socket send / mailbox / native-push.
|
|
36
36
|
*
|
|
37
37
|
* Usage:
|
|
38
38
|
* pi --entwurf-control (no id injection: pi owns its id, the record owns
|
|
@@ -758,8 +758,9 @@ async function handleCommand(
|
|
|
758
758
|
// (entwurf-bridge entwurf_v2, the pi-native entwurf_v2 senderProvider via buildLocalSenderEnvelope)
|
|
759
759
|
// pass the envelope structurally and never touch the message body — the
|
|
760
760
|
// canonical XML-style payload is the shared formatSenderInfoBlock SSOT
|
|
761
|
-
// (#50 C3: the
|
|
762
|
-
//
|
|
761
|
+
// (#50 C3: this is the ONE renderer since the visible-first cut removed the
|
|
762
|
+
// dormant spawn-resume rail that used to append the same block to a resume
|
|
763
|
+
// prompt — a future VISIBLE resume must render through it, not beside it).
|
|
763
764
|
const senderInfoBlock = sender ? formatSenderInfoBlock(sender, wantsReply) : "";
|
|
764
765
|
|
|
765
766
|
const mode = command.mode ?? "steer";
|
|
@@ -938,7 +939,8 @@ function updateStatus(ctx: ExtensionContext | null, enabled: boolean, gardenId:
|
|
|
938
939
|
// is GONE with the id it mirrored. A name was a second place the address lived; the
|
|
939
940
|
// record is the only one now, so there is nothing left to keep in sync and nothing
|
|
940
941
|
// to crash over. (The dormant-resume authorization that leaned on the `entwurf` tag
|
|
941
|
-
// moved to record existence
|
|
942
|
+
// moved to record existence, and that resume rail has since been withdrawn entirely;
|
|
943
|
+
// the record-authoritative remainder lives in resume-launch-identity.ts.)
|
|
942
944
|
|
|
943
945
|
function updateSessionEnv(ctx: ExtensionContext | null, enabled: boolean, gardenId: string | null): void {
|
|
944
946
|
if (!enabled || !gardenId) {
|
|
@@ -985,7 +987,7 @@ function getStringFlagFromArgv(flagName: string): string | undefined {
|
|
|
985
987
|
}
|
|
986
988
|
|
|
987
989
|
// `--emacs-agent-socket <name>` exports PI_EMACS_AGENT_SOCKET so this session's
|
|
988
|
-
// own Bash/emacsclient calls (and any
|
|
990
|
+
// own Bash/emacsclient calls (and any child process that inherits this env)
|
|
989
991
|
// target the right Emacs server socket — e.g. `emacsclient -s "$PI_EMACS_AGENT_SOCKET"`.
|
|
990
992
|
// v2-only revival: the original ACP path injected this into the ACP child's spawn
|
|
991
993
|
// env (acp-bridge.ts); with no ACP child on this branch, the consumer IS this pi
|
|
@@ -1042,6 +1044,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1042
1044
|
if (shouldRegisterControlTools(pi)) {
|
|
1043
1045
|
registerListSessionsTool(pi);
|
|
1044
1046
|
registerEntwurfV2Tool(pi);
|
|
1047
|
+
registerFreshCallTool(pi);
|
|
1048
|
+
registerResumeCallTool(pi);
|
|
1045
1049
|
}
|
|
1046
1050
|
|
|
1047
1051
|
// The in-process mint refusals (`/new`, `/fork`, `/clone`, RPC new_session) are
|
|
@@ -1258,23 +1262,19 @@ interface EntwurfV2SurfaceModule {
|
|
|
1258
1262
|
runAndRenderEntwurfV2FromSurface(
|
|
1259
1263
|
params: {
|
|
1260
1264
|
target: string;
|
|
1261
|
-
intent: "fire-and-forget"
|
|
1265
|
+
intent: "fire-and-forget";
|
|
1262
1266
|
mode?: "steer" | "follow_up";
|
|
1263
1267
|
wants_reply?: boolean;
|
|
1264
1268
|
message: string;
|
|
1265
1269
|
},
|
|
1266
|
-
opts: {
|
|
1267
|
-
senderProvider: () => SenderEnvelope | undefined;
|
|
1268
|
-
agentDir?: string;
|
|
1269
|
-
prefixRoots?: readonly string[];
|
|
1270
|
-
},
|
|
1270
|
+
opts: { senderProvider: () => SenderEnvelope | undefined },
|
|
1271
1271
|
): Promise<{ text: string; isError: boolean }>;
|
|
1272
1272
|
}
|
|
1273
1273
|
|
|
1274
1274
|
function registerEntwurfV2Tool(pi: ExtensionAPI): void {
|
|
1275
1275
|
const entwurfV2Parameters = Type.Object({
|
|
1276
1276
|
target: Type.String({ description: "Target garden id (use entwurf_peers to discover)" }),
|
|
1277
|
-
intent: StringEnum(["fire-and-forget"
|
|
1277
|
+
intent: StringEnum(["fire-and-forget"] as const, {
|
|
1278
1278
|
description:
|
|
1279
1279
|
"fire-and-forget = send/reply/hand-off to a LIVE socket target (currently backend pi) or to any " +
|
|
1280
1280
|
"citizen with no socket liveness — the decider picks that citizen's rail, and a rail can also " +
|
|
@@ -1284,10 +1284,10 @@ function registerEntwurfV2Tool(pi: ExtensionAPI): void {
|
|
|
1284
1284
|
"dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate (two rejects, " +
|
|
1285
1285
|
"not one). " +
|
|
1286
1286
|
"Set wants_reply for an answer. " +
|
|
1287
|
-
"
|
|
1288
|
-
"
|
|
1289
|
-
"
|
|
1290
|
-
"
|
|
1287
|
+
"This is the ONLY intent. The second one, owned-outcome, resumed a dormant citizen by launching " +
|
|
1288
|
+
"a hidden background child and was withdrawn under the visible-first rule; it is not selectable, " +
|
|
1289
|
+
"not deprecated-but-tolerated, and a dormant citizen is currently unreachable by this verb and " +
|
|
1290
|
+
"rejects as dormant-fire-forget-unsupported.",
|
|
1291
1291
|
}),
|
|
1292
1292
|
message: Type.String({
|
|
1293
1293
|
description:
|
|
@@ -1298,7 +1298,7 @@ function registerEntwurfV2Tool(pi: ExtensionAPI): void {
|
|
|
1298
1298
|
StringEnum(["steer", "follow_up"] as const, {
|
|
1299
1299
|
description:
|
|
1300
1300
|
"Injection style for a CONTROL-SOCKET send only: steer (immediate) or follow_up (after task). " +
|
|
1301
|
-
"The mailbox
|
|
1301
|
+
"The mailbox and native-push plans carry no mode, so it has no effect on those rails.",
|
|
1302
1302
|
}),
|
|
1303
1303
|
),
|
|
1304
1304
|
wants_reply: Type.Optional(Type.Boolean({ description: "Human-conversation reply hint (default false)" })),
|
|
@@ -1306,7 +1306,7 @@ function registerEntwurfV2Tool(pi: ExtensionAPI): void {
|
|
|
1306
1306
|
|
|
1307
1307
|
type EntwurfV2Params = {
|
|
1308
1308
|
target: string;
|
|
1309
|
-
intent: "fire-and-forget"
|
|
1309
|
+
intent: "fire-and-forget";
|
|
1310
1310
|
message: string;
|
|
1311
1311
|
mode?: "steer" | "follow_up";
|
|
1312
1312
|
wants_reply?: boolean;
|
|
@@ -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
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
picks
|
|
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; deliverable self-fetch citizen → meta-bridge mailbox;
|
|
1328
|
+
probe-alive native-push citizen → direct injection into its conversation) and reports ONE outcome
|
|
1329
|
+
(delivered / rejected / delivered-but-lock-dirty). EXISTING targets only; discover with entwurf_peers.
|
|
1330
|
+
A peer entwurf_peers shows as liveness=alive → fire-and-forget. A citizen with NO socket liveness
|
|
1331
|
+
(liveness=unsupported) is ALSO fire-and-forget — unsupported means only "no control-socket probe" — and the
|
|
1332
|
+
decider picks its own rail: a self-fetch backend (e.g. Claude Code) gets the mailbox, a native-push backend
|
|
1333
|
+
(e.g. Antigravity) gets direct injection and has NO mailbox at all. THERE IS A THIRD RESULT: the mailbox
|
|
1334
|
+
delivers only to a DELIVERABLE citizen, so a terminated session, or a backend with no adapter here (e.g.
|
|
1335
|
+
codex), is mailbox-undeliverable, not queued for an inbox nobody drains. The native-push probe is 3-valued:
|
|
1336
|
+
alive → injected; dead → native-push-target-dead; indeterminate → native-push-probe-indeterminate
|
|
1337
|
+
(unestablished ≠ gone). DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets
|
|
1338
|
+
dormant-fire-forget-unsupported — same receiver rule as the mailbox, no active drainer means no
|
|
1339
|
+
delivery. The intent that used to answer there, owned-outcome, resumed it by
|
|
1340
|
+
launching a hidden background child; it was withdrawn under the visible-first rule, so re-open the session
|
|
1341
|
+
yourself and dispatch again. LOCK: taken for a control-socket-DOMAIN dispatch. The mailbox and native-push
|
|
1342
|
+
rails are lock-free — deliverability and the adapter probe guard them. mode applies to a CONTROL-SOCKET
|
|
1343
|
+
send only; other plans carry no mode. wants_reply rides every rail. message caps at 16000 chars; send an
|
|
1344
|
+
artifact path + digest for more.`,
|
|
1374
1345
|
parameters: entwurfV2Parameters,
|
|
1375
1346
|
async execute(
|
|
1376
1347
|
_toolCallId: string,
|
|
@@ -1410,8 +1381,10 @@ The decider — not this surface — chooses the transport.`,
|
|
|
1410
1381
|
mode: params.mode,
|
|
1411
1382
|
wants_reply: params.wants_reply,
|
|
1412
1383
|
},
|
|
1413
|
-
//
|
|
1414
|
-
//
|
|
1384
|
+
// No trust-preflight inputs are passed, and none exist to pass: the preflight on this
|
|
1385
|
+
// path guarded the resume verdict, so it left with `owned-outcome`. `senderProvider`
|
|
1386
|
+
// is the whole options surface now — do NOT re-add an `ENTWURF_PREFIX_ROOTS` fallback
|
|
1387
|
+
// here without a verdict that reads it (nothing on the dispatch path does).
|
|
1415
1388
|
{ senderProvider },
|
|
1416
1389
|
);
|
|
1417
1390
|
return {
|
|
@@ -1482,7 +1455,7 @@ function registerListSessionsTool(pi: ExtensionAPI): void {
|
|
|
1482
1455
|
name: "entwurf_peers",
|
|
1483
1456
|
label: "List Garden Citizens",
|
|
1484
1457
|
description:
|
|
1485
|
-
"List the entwurf fact surface: garden citizens from meta-records (including active self-fetch meta receivers such as claude-code) with liveness, plus diagnostics. The record is the sole address axis (#50 C4) — a control socket no record claims surfaces as a record-less-socket diagnostic, never a peer row. Pair with entwurf_v2 to address a peer by garden id; this surface reports facts, never per-row routing verbs.",
|
|
1458
|
+
"List the entwurf fact surface: garden citizens from meta-records (including active self-fetch meta receivers such as claude-code) with liveness, plus diagnostics. The record is the sole address axis (#50 C4) — a control socket no record claims surfaces as a record-less-socket diagnostic, never a peer row. Pair with entwurf_v2 to address a peer by garden id; this surface reports facts, never per-row routing verbs. A `dead` row is a REPORTED FACT and nothing more: that citizen is dormant and is currently unreachable by any verb, so listing it grants no action — appearing here is not an invitation to dispatch. It is facts-only and creates nothing: to open a NEW sibling use entwurf_fresh_call.",
|
|
1486
1459
|
parameters: Type.Object({}),
|
|
1487
1460
|
async execute(
|
|
1488
1461
|
_toolCallId: string,
|
|
@@ -1507,3 +1480,179 @@ function registerListSessionsTool(pi: ExtensionAPI): void {
|
|
|
1507
1480
|
},
|
|
1508
1481
|
});
|
|
1509
1482
|
}
|
|
1483
|
+
|
|
1484
|
+
// ============================================================================
|
|
1485
|
+
// Tool: entwurf_fresh_call
|
|
1486
|
+
// ============================================================================
|
|
1487
|
+
|
|
1488
|
+
const MUX_FRESH_CALL_MODULE = "./lib/mux-fresh-call.ts";
|
|
1489
|
+
|
|
1490
|
+
interface MuxFreshCallModule {
|
|
1491
|
+
freshCall(
|
|
1492
|
+
params: { backend: "pi" | "claude-code"; model: string; task: string; callerGardenId: string | null },
|
|
1493
|
+
env?: NodeJS.ProcessEnv,
|
|
1494
|
+
): { ok: boolean };
|
|
1495
|
+
renderFreshCall(result: { ok: boolean }): { text: string; isError: boolean };
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* The caller identity for this surface is the RESIDENT's garden address — the one the record
|
|
1500
|
+
* minted at session start and keyed the control socket on. It is read from this extension's own
|
|
1501
|
+
* closure, never from a tool parameter and never from `process.env`.
|
|
1502
|
+
*
|
|
1503
|
+
* A caller that could pass an id could pass a WRONG one, and that value would become the address
|
|
1504
|
+
* the sibling calls back to — docs/mux-launch-rail.md §6-b keeps the measured incident. So there
|
|
1505
|
+
* is no parameter to be wrong with.
|
|
1506
|
+
*
|
|
1507
|
+
* A null resident id refuses LOUDLY rather than falling back: without an address to call back to,
|
|
1508
|
+
* the launch would be a window with no way home.
|
|
1509
|
+
*/
|
|
1510
|
+
function registerFreshCallTool(pi: ExtensionAPI): void {
|
|
1511
|
+
// Same TS2589 workaround as registerEntwurfV2Tool — see the comment block there.
|
|
1512
|
+
const registerTool = pi.registerTool as (def: any) => void;
|
|
1513
|
+
registerTool({
|
|
1514
|
+
name: "entwurf_fresh_call",
|
|
1515
|
+
label: "Open Fresh Sibling",
|
|
1516
|
+
description: `Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task. Two fixed
|
|
1517
|
+
backends only: pi, claude-code. The sibling's FIRST action is a callback to you carrying a nonce, and the
|
|
1518
|
+
sender envelope of that callback is its garden id — that is how you learn the address of something that did
|
|
1519
|
+
not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing else:
|
|
1520
|
+
it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls for the
|
|
1521
|
+
callback; if it never arrives the window is visible and can be read directly. For EXISTING citizens use
|
|
1522
|
+
entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and passed to the
|
|
1523
|
+
chosen runtime CLI (provider/model for pi; model id/alias for Claude Code); there are no arbitrary
|
|
1524
|
+
command/cwd/env knobs. Do not put secrets in the task — model and task argv are visible to same-user
|
|
1525
|
+
processes on this host.`,
|
|
1526
|
+
parameters: Type.Object({
|
|
1527
|
+
backend: StringEnum(["pi", "claude-code"], {
|
|
1528
|
+
description: "Which fixed runtime to open. Only these two; there is no arbitrary command.",
|
|
1529
|
+
}),
|
|
1530
|
+
model: Type.String({
|
|
1531
|
+
minLength: 1,
|
|
1532
|
+
maxLength: 200,
|
|
1533
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._/:\\[\\]-]*$",
|
|
1534
|
+
description: "Required runtime model: canonical provider/model for pi, or a Claude Code model id/alias.",
|
|
1535
|
+
}),
|
|
1536
|
+
task: Type.String({
|
|
1537
|
+
minLength: 1,
|
|
1538
|
+
maxLength: 16000,
|
|
1539
|
+
description:
|
|
1540
|
+
"What the sibling should do after it calls you back. Plain instructions; no secrets (see the tool description).",
|
|
1541
|
+
}),
|
|
1542
|
+
}),
|
|
1543
|
+
async execute(
|
|
1544
|
+
_toolCallId: string,
|
|
1545
|
+
params: { backend: "pi" | "claude-code"; model: string; task: string },
|
|
1546
|
+
_signal: AbortSignal | undefined,
|
|
1547
|
+
_onUpdate: unknown,
|
|
1548
|
+
_ctx: ExtensionContext,
|
|
1549
|
+
) {
|
|
1550
|
+
try {
|
|
1551
|
+
const mux = (await import(MUX_FRESH_CALL_MODULE)) as unknown as MuxFreshCallModule;
|
|
1552
|
+
const result = mux.freshCall({
|
|
1553
|
+
backend: params.backend,
|
|
1554
|
+
model: params.model,
|
|
1555
|
+
task: params.task,
|
|
1556
|
+
callerGardenId: residentGardenId,
|
|
1557
|
+
});
|
|
1558
|
+
const rendered = mux.renderFreshCall(result);
|
|
1559
|
+
return {
|
|
1560
|
+
content: [{ type: "text", text: rendered.text }],
|
|
1561
|
+
isError: rendered.isError,
|
|
1562
|
+
details: { isError: rendered.isError },
|
|
1563
|
+
};
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1566
|
+
return {
|
|
1567
|
+
content: [{ type: "text", text: `entwurf_fresh_call error: ${msg}` }],
|
|
1568
|
+
isError: true,
|
|
1569
|
+
details: { error: msg },
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
},
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// ============================================================================
|
|
1577
|
+
// Tool: entwurf_resume_call
|
|
1578
|
+
// ============================================================================
|
|
1579
|
+
|
|
1580
|
+
const MUX_RESUME_CALL_MODULE = "./lib/mux-resume-call.ts";
|
|
1581
|
+
const V2_VISIBLE_RESUME_MODULE = "./lib/entwurf-v2-visible-resume.ts";
|
|
1582
|
+
|
|
1583
|
+
interface MuxResumeCallModule {
|
|
1584
|
+
resumeCall(
|
|
1585
|
+
params: { cwd: string; runtimeArgs: readonly string[] },
|
|
1586
|
+
env?: NodeJS.ProcessEnv,
|
|
1587
|
+
): { ok: true; receipt: Record<string, string> } | { ok: false; reason: string };
|
|
1588
|
+
RESUME_CALL_REJECT_HINT: Record<string, string>;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
interface VisibleResumeModule {
|
|
1592
|
+
visibleResume(target: string, deps: unknown): Promise<{ ok: boolean }>;
|
|
1593
|
+
makeVisibleResumeDeps(launch: unknown): unknown;
|
|
1594
|
+
renderVisibleResume(result: { ok: boolean }): { text: string; isError: boolean };
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* Unlike `entwurf_fresh_call`, this tool needs NO caller identity: a resume names an existing
|
|
1599
|
+
* citizen, so the address is the parameter rather than something the sibling has to report back.
|
|
1600
|
+
* What it needs instead is the per-gid lock, which is why the composition lives on the v2 side of
|
|
1601
|
+
* the fence and only the LAUNCH is handed across (docs/mux-launch-rail.md §11).
|
|
1602
|
+
*/
|
|
1603
|
+
function registerResumeCallTool(pi: ExtensionAPI): void {
|
|
1604
|
+
// Same TS2589 workaround as registerEntwurfV2Tool — see the comment block there.
|
|
1605
|
+
const registerTool = pi.registerTool as (def: any) => void;
|
|
1606
|
+
registerTool({
|
|
1607
|
+
name: "entwurf_resume_call",
|
|
1608
|
+
label: "Resume Dormant Citizen",
|
|
1609
|
+
description: `Reopen ONE DORMANT pi citizen under its OWN garden id, in a visible window in the operator's own tmux
|
|
1610
|
+
session. The record supplies everything — which transcript, which model, which provider, which cwd — so the only
|
|
1611
|
+
input is the target id: there is no model override, no task, and no prompt. This runs NO turn: the window comes
|
|
1612
|
+
back with the conversation and waits, and talking to it is still entwurf_v2 fire-and-forget on the socket this
|
|
1613
|
+
call stands up. You get TWO receipts and they mean different things: a LAUNCH receipt (tmux made a window and was
|
|
1614
|
+
asked to start pi) and an OBSERVATION receipt (the control socket answered under the same id, or
|
|
1615
|
+
resume-unobserved). Unobserved is a real outcome, not an error to retry — the window is visible, so read it. A
|
|
1616
|
+
citizen that is already LIVE is refused: address it with entwurf_v2 instead. Only pi citizens have a same-id
|
|
1617
|
+
resume, because only they stand a control socket up.`,
|
|
1618
|
+
parameters: Type.Object({
|
|
1619
|
+
target: Type.String({
|
|
1620
|
+
minLength: 1,
|
|
1621
|
+
pattern: "^\\d{8}T\\d{6}-[0-9a-f]{6}$",
|
|
1622
|
+
description: "Garden id of the DORMANT pi citizen to reopen (discover with entwurf_peers).",
|
|
1623
|
+
}),
|
|
1624
|
+
}),
|
|
1625
|
+
async execute(
|
|
1626
|
+
_toolCallId: string,
|
|
1627
|
+
params: { target: string },
|
|
1628
|
+
_signal: AbortSignal | undefined,
|
|
1629
|
+
_onUpdate: unknown,
|
|
1630
|
+
_ctx: ExtensionContext,
|
|
1631
|
+
) {
|
|
1632
|
+
try {
|
|
1633
|
+
const mux = (await import(MUX_RESUME_CALL_MODULE)) as unknown as MuxResumeCallModule;
|
|
1634
|
+
const v2 = (await import(V2_VISIBLE_RESUME_MODULE)) as unknown as VisibleResumeModule;
|
|
1635
|
+
const launch = (input: { cwd: string; runtimeArgs: readonly string[] }) => {
|
|
1636
|
+
const launched = mux.resumeCall(input);
|
|
1637
|
+
return launched.ok
|
|
1638
|
+
? { ok: true, handle: launched.receipt }
|
|
1639
|
+
: { ok: false, reason: launched.reason, hint: mux.RESUME_CALL_REJECT_HINT[launched.reason] };
|
|
1640
|
+
};
|
|
1641
|
+
const result = await v2.visibleResume(params.target, v2.makeVisibleResumeDeps(launch));
|
|
1642
|
+
const rendered = v2.renderVisibleResume(result);
|
|
1643
|
+
return {
|
|
1644
|
+
content: [{ type: "text", text: rendered.text }],
|
|
1645
|
+
isError: rendered.isError,
|
|
1646
|
+
details: { isError: rendered.isError },
|
|
1647
|
+
};
|
|
1648
|
+
} catch (err) {
|
|
1649
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1650
|
+
return {
|
|
1651
|
+
content: [{ type: "text", text: `entwurf_resume_call error: ${msg}` }],
|
|
1652
|
+
isError: true,
|
|
1653
|
+
details: { error: msg },
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
},
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
@@ -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
|
|
@@ -36,10 +36,10 @@ 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
|
-
//
|
|
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
41
|
// backend (cortex, the first — precisely: SYSTEM-PROMPT-carrier-less, since
|
|
42
|
-
// cortex does read `_meta` for an unpromoted caller-session-id seam
|
|
42
|
+
// cortex does read `_meta` for an unpromoted caller-session-id seam)
|
|
43
43
|
// has nowhere to put it — so its operator engraving
|
|
44
44
|
// rides HERE, prepended as the LEADING section of the first-user augment. It
|
|
45
45
|
// travels on the WIRE only (new-only, never the config signature), so an
|
|
@@ -48,6 +48,13 @@ const MAX_AUGMENT_BYTES = 50 * 1024;
|
|
|
48
48
|
// carrier feeds bridgeConfigSignature.
|
|
49
49
|
const CARRIER_LESS_BACKENDS: ReadonlySet<string> = new Set(["cortex"]);
|
|
50
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
|
+
|
|
51
58
|
// The env var an operator points at a personal engraving file. Shared surface
|
|
52
59
|
// with the claude carrier (engraving.ts) BY NAME, but read INLINE here (not
|
|
53
60
|
// imported): augment.ts is loaded raw by the strip-types carrier-augment gate,
|
|
@@ -116,6 +123,38 @@ export function buildPiContextAugment(params: PiContextAugmentParams): string {
|
|
|
116
123
|
if (engraving) sections.push(engraving);
|
|
117
124
|
}
|
|
118
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
|
+
|
|
119
158
|
// Bridge identity + caller-side sibling stance. The closing line is a
|
|
120
159
|
// caller-perspective statement: when THIS session throws an entwurf, the peer
|
|
121
160
|
// on the other end is a sibling, not a worker.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// ACP backend adapter rail — the PRODUCT seam by which a curated model id selects
|
|
2
|
-
// which ACP backend (claude / future backend / …) drives a turn. See
|
|
2
|
+
// which ACP backend (claude / future backend / …) drives a turn. See the adapter contract in
|
|
3
|
+
// docs/acp-backend-rail.md.
|
|
3
4
|
//
|
|
4
5
|
// This seam is DISTINCT from `AcpTurnDeps` (backend.ts), which is the test/runtime
|
|
5
|
-
// seam (fake spawn/connection/clock for the gates).
|
|
6
|
-
//
|
|
6
|
+
// seam (fake spawn/connection/clock for the gates). Merging them would make a
|
|
7
|
+
// fake-deps fixture look like
|
|
7
8
|
// a fake backend and force the adapter to carry clock/sessionDir/createConnection.
|
|
8
9
|
// The wiring is `defaultDeps(adapter)` — the turn loop in backend.ts stays
|
|
9
10
|
// backend-invariant; only these per-backend functions change with `adapter`.
|
|
@@ -217,7 +218,7 @@ function resolveClaudeLaunch(): AcpLaunchSpec {
|
|
|
217
218
|
export const claudeAdapter: AcpBackendAdapter = {
|
|
218
219
|
backend: "claude",
|
|
219
220
|
|
|
220
|
-
// Claude owns its UNPREFIXED curated ids only (
|
|
221
|
+
// Claude owns its UNPREFIXED curated ids only (rail “Adapter contract”). The native id
|
|
221
222
|
// equals the curated id — claude carries no curation prefix to strip.
|
|
222
223
|
routeModel(modelId) {
|
|
223
224
|
return SUPPORTED_CLAUDE_IDS.has(modelId) ? { nativeModelId: modelId } : undefined;
|
|
@@ -298,13 +299,13 @@ export const claudeAdapter: AcpBackendAdapter = {
|
|
|
298
299
|
* adapters claim one id. */
|
|
299
300
|
// ---------------------------------------------------------------------------
|
|
300
301
|
// cortex adapter — Snowflake Cortex Code, the first non-claude backend on the
|
|
301
|
-
// rail (docs/acp-backend-rail.md
|
|
302
|
+
// rail (docs/acp-backend-rail.md, “Shipped adapters”). It adds ZERO to the common layer:
|
|
302
303
|
// everything cortex-specific lives here + models.ts + overlay.ts + the gates.
|
|
303
304
|
// ---------------------------------------------------------------------------
|
|
304
305
|
|
|
305
306
|
const SUPPORTED_CORTEX_IDS: ReadonlySet<string> = new Set(SUPPORTED_CORTEX_MODEL_IDS);
|
|
306
307
|
|
|
307
|
-
/** Cortex's OWN settings
|
|
308
|
+
/** Cortex's OWN settings: a Snowflake connection name, or null. Opaque to
|
|
308
309
|
* config.ts / backend.ts — only cortexAdapter reads it (casting back). */
|
|
309
310
|
export interface CortexAdapterSettings {
|
|
310
311
|
cortexConnection: string | null;
|
|
@@ -318,7 +319,7 @@ export const CORTEX_CONNECTION_ENV = "ENTWURF_ACP_CORTEX_CONNECTION";
|
|
|
318
319
|
export const cortexAdapter: AcpBackendAdapter = {
|
|
319
320
|
backend: "cortex",
|
|
320
321
|
|
|
321
|
-
// Cortex owns the reserved `cortex-` prefix (
|
|
322
|
+
// Cortex owns the reserved `cortex-` prefix (rail “Adapter contract”). routeModel strips it to the
|
|
322
323
|
// native id: `cortex-auto` → "auto", `cortex-claude-sonnet-5` → "claude-sonnet-5".
|
|
323
324
|
routeModel(modelId) {
|
|
324
325
|
if (!SUPPORTED_CORTEX_IDS.has(modelId)) return undefined;
|
|
@@ -406,7 +407,7 @@ export const cortexAdapter: AcpBackendAdapter = {
|
|
|
406
407
|
return { envOverrides: { HOME: overlay.home, SNOWFLAKE_HOME: overlay.snowflakeHome } };
|
|
407
408
|
},
|
|
408
409
|
|
|
409
|
-
// System-prompt-carrier-less (
|
|
410
|
+
// System-prompt-carrier-less (ACP rail “Cortex Code audit”): Cortex ACP exposes no
|
|
410
411
|
// `_meta.systemPrompt` and has no developer_instructions / GEMINI_SYSTEM_MD
|
|
411
412
|
// equivalent. (It does READ `_meta` — a caller-session-id seam, measured but
|
|
412
413
|
// unexplored and deliberately not part of this contract.) loadCarrier returns
|
|
@@ -438,7 +439,7 @@ export const cortexAdapter: AcpBackendAdapter = {
|
|
|
438
439
|
await setConfig.call(connection, { sessionId: acpSessionId, configId: "model", value: nativeModelId });
|
|
439
440
|
},
|
|
440
441
|
|
|
441
|
-
// A connection change must invalidate a reused session (
|
|
442
|
+
// A connection change must invalidate a reused session (rail: Adapter contract). Flat,
|
|
442
443
|
// sorted-stable primitive map; reads ONLY the opaque adapterSettings. `backend`
|
|
443
444
|
// + `nativeModelId` are added by backend.ts.
|
|
444
445
|
configSignatureFields(adapterSettings) {
|
|
@@ -452,7 +453,7 @@ const ADAPTERS: readonly AcpBackendAdapter[] = [claudeAdapter, cortexAdapter];
|
|
|
452
453
|
/**
|
|
453
454
|
* Resolve the backend adapter that owns `modelId`.
|
|
454
455
|
*
|
|
455
|
-
*
|
|
456
|
+
* Routing fail-fast contract (rail “Adapter contract”):
|
|
456
457
|
* - 0 matches → throw (unknown model — no silent default).
|
|
457
458
|
* - 2+ matches → throw (prefix collision — a startup-visible registry bug).
|
|
458
459
|
*
|
|
@@ -476,7 +477,7 @@ export function resolveAcpBackendAdapter(modelId: string): { adapter: AcpBackend
|
|
|
476
477
|
}
|
|
477
478
|
|
|
478
479
|
/** Every curated model row across all registered adapters — for provider registration.
|
|
479
|
-
*
|
|
480
|
+
* Registration-time fail-fast (rail “Adapter contract”): every curated id must route to EXACTLY
|
|
480
481
|
* one adapter and no id may be duplicated across adapters. Catching it here means a
|
|
481
482
|
* prefix-collision / duplicate surfaces at provider registration, not mid-turn. */
|
|
482
483
|
export function allCuratedModels(): AcpModelRow[] {
|