@junghanacs/entwurf 0.13.1 → 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 +48 -15
- package/BASELINE.md +3 -3
- package/CHANGELOG.md +45 -0
- package/CONTRIBUTING.md +13 -9
- package/DELIVERY.md +7 -6
- package/README.md +27 -27
- package/VERIFY.md +22 -14
- 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 +9 -4
- package/docs/external-mcp-host.md +4 -5
- package/docs/setup-clean-host.md +8 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +148 -28
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +8 -6
- 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 +164 -28
- 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 +22 -11
- package/pi-extensions/entwurf-control.ts +218 -40
- package/pi-extensions/lib/acp/backend.ts +71 -12
- package/pi-extensions/lib/acp/overlay.ts +8 -6
- 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/run.sh +471 -201
- package/scripts/agy-bridge-config.py +5 -1
- package/scripts/check-acp-backend-preflight.ts +1 -1
- package/scripts/check-acp-overlay.ts +13 -3
- 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 +157 -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 +98 -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 +84 -76
- package/scripts/check-release-gate-outcomes.ts +127 -7
- 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/mutation-qualify.ts +109 -3
- package/scripts/meta-bridge-doctor.sh +6 -8
- package/scripts/meta-facts.ts +60 -0
- package/scripts/mutants/acp-overlay.json +17 -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 +0 -1037
- package/scripts/mutants/release-gate.json +35 -0
- package/scripts/mutants/resume-args.json +76 -0
- package/scripts/mutants/resume-launch-identity.json +96 -0
- package/scripts/mutants/v2-surface.json +58 -18
- package/scripts/mutants/v2-visible-resume.json +215 -0
- package/scripts/new-session-id.ts +9 -4
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-agy-native-push-live.ts +6 -17
- package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
- package/scripts/smoke-meta-honesty.sh +1 -1
- 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 -275
- 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 -467
|
@@ -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;
|
|
@@ -1324,24 +1324,24 @@ function registerEntwurfV2Tool(pi: ExtensionAPI): void {
|
|
|
1324
1324
|
label: "Dispatch (v2)",
|
|
1325
1325
|
description: `CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id alone
|
|
1326
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;
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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.`,
|
|
1345
1345
|
parameters: entwurfV2Parameters,
|
|
1346
1346
|
async execute(
|
|
1347
1347
|
_toolCallId: string,
|
|
@@ -1381,8 +1381,10 @@ chars; send an artifact path + digest for more.`,
|
|
|
1381
1381
|
mode: params.mode,
|
|
1382
1382
|
wants_reply: params.wants_reply,
|
|
1383
1383
|
},
|
|
1384
|
-
//
|
|
1385
|
-
//
|
|
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).
|
|
1386
1388
|
{ senderProvider },
|
|
1387
1389
|
);
|
|
1388
1390
|
return {
|
|
@@ -1453,7 +1455,7 @@ function registerListSessionsTool(pi: ExtensionAPI): void {
|
|
|
1453
1455
|
name: "entwurf_peers",
|
|
1454
1456
|
label: "List Garden Citizens",
|
|
1455
1457
|
description:
|
|
1456
|
-
"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.",
|
|
1457
1459
|
parameters: Type.Object({}),
|
|
1458
1460
|
async execute(
|
|
1459
1461
|
_toolCallId: string,
|
|
@@ -1478,3 +1480,179 @@ function registerListSessionsTool(pi: ExtensionAPI): void {
|
|
|
1478
1480
|
},
|
|
1479
1481
|
});
|
|
1480
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
|
+
}
|
|
@@ -602,6 +602,58 @@ export function actionableAcpBackendHint(message: string): string | undefined {
|
|
|
602
602
|
].join("\n");
|
|
603
603
|
}
|
|
604
604
|
|
|
605
|
+
/**
|
|
606
|
+
* pi 0.84 streamSimple hook contract (#63; upstream pi-mono #7372 → doc-only PR
|
|
607
|
+
* #7576): implementations must invoke `options.onPayload` before sending the
|
|
608
|
+
* provider request and use any returned replacement.
|
|
609
|
+
*
|
|
610
|
+
* On this rail the provider request is the ACP `session/prompt` params — so the
|
|
611
|
+
* hook receives the EXACT `{sessionId, prompt}` object after the wire content is
|
|
612
|
+
* fully built (augment / reuse delta included) and immediately before
|
|
613
|
+
* `connection.prompt`. That is the truthful analogue of the built-in providers'
|
|
614
|
+
* post-build/pre-send boundary. Replacement is honored fail-closed, never
|
|
615
|
+
* silently: it must be a non-null, non-array object, keep the bootstrapped
|
|
616
|
+
* `sessionId`, and carry a non-empty prompt array — prompt rewriting is
|
|
617
|
+
* upstream-granted power, but entwurf cannot truthfully deliver to an ACP
|
|
618
|
+
* session it did not open, and an emptied prompt would undo this rail's own
|
|
619
|
+
* non-empty-prompt invariant; either refuses the turn before the wire.
|
|
620
|
+
*
|
|
621
|
+
* `options.onResponse` is an EXPLICIT LOCAL NON-HTTP EXEMPTION and is never
|
|
622
|
+
* invoked anywhere on this rail: pi hard-types it as HTTP `{status, headers}`
|
|
623
|
+
* (and the `after_provider_response` extension event re-emits exactly that), and
|
|
624
|
+
* ACP's terminal result arrives only AFTER the session-update body was already
|
|
625
|
+
* consumed — both the shape and the "before consuming its body" ordering are
|
|
626
|
+
* unmappable, so any call would fabricate HTTP evidence that does not exist.
|
|
627
|
+
* The absence is pinned behaviorally by check-acp-stream-hooks.
|
|
628
|
+
*/
|
|
629
|
+
async function applyProviderPayloadHook<T extends { sessionId: string }>(
|
|
630
|
+
options: SimpleStreamOptions | undefined,
|
|
631
|
+
params: T,
|
|
632
|
+
model: Model<Api>,
|
|
633
|
+
): Promise<T> {
|
|
634
|
+
const onPayload = options?.onPayload;
|
|
635
|
+
if (!onPayload) return params;
|
|
636
|
+
const replacement = await onPayload(params, model);
|
|
637
|
+
if (replacement === undefined) return params;
|
|
638
|
+
if (typeof replacement !== "object" || replacement === null || Array.isArray(replacement)) {
|
|
639
|
+
throw new Error(
|
|
640
|
+
"entwurf: before_provider_request returned a non-object replacement — the ACP prompt payload must stay a non-null, non-array object",
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if ((replacement as { sessionId?: unknown }).sessionId !== params.sessionId) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
"entwurf: before_provider_request changed the ACP sessionId — entwurf cannot truthfully deliver to a session it did not bootstrap; prompt rewriting is allowed, session identity is not",
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
const replacementPrompt = (replacement as { prompt?: unknown }).prompt;
|
|
649
|
+
if (!Array.isArray(replacementPrompt) || replacementPrompt.length === 0) {
|
|
650
|
+
throw new Error(
|
|
651
|
+
"entwurf: before_provider_request returned a replacement without a non-empty prompt array — an empty ACP prompt cannot be sent",
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
return replacement as T;
|
|
655
|
+
}
|
|
656
|
+
|
|
605
657
|
/**
|
|
606
658
|
* streamSimple for the entwurf provider. Returns the event stream
|
|
607
659
|
* synchronously and drives the ACP turn on a microtask.
|
|
@@ -1038,17 +1090,22 @@ export function streamAcpTurn(
|
|
|
1038
1090
|
// prompt could still sync-reject before the wire write; the next visible
|
|
1039
1091
|
// event after this is the backend's own first token / tool notice.
|
|
1040
1092
|
pushAcpLifecycleNotice(state, "sending prompt");
|
|
1093
|
+
// #63: the pi streamSimple payload hook sees the EXACT wire params and may
|
|
1094
|
+
// replace them (fail-closed integrity inside the helper). It runs while the
|
|
1095
|
+
// bootstrap abort listener is still armed; the recheck below keeps an abort
|
|
1096
|
+
// raised during a slow handler ahead of the wire write.
|
|
1097
|
+
const wireParams = await applyProviderPayloadHook(options, { sessionId: acpSessionId, prompt }, model);
|
|
1098
|
+
if (signal?.aborted) throw new Error("aborted during payload hook");
|
|
1041
1099
|
// Hand the abort window over to the prompt driver: from here on an abort
|
|
1042
1100
|
// is a protocol `session/cancel` first, teardown only after the grace.
|
|
1043
1101
|
if (signal && onAbort) {
|
|
1044
1102
|
signal.removeEventListener("abort", onAbort);
|
|
1045
1103
|
onAbort = undefined;
|
|
1046
1104
|
}
|
|
1047
|
-
const promptResult = await awaitAcpPromptTurn(
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
);
|
|
1105
|
+
const promptResult = await awaitAcpPromptTurn(session, wireParams, {
|
|
1106
|
+
signal,
|
|
1107
|
+
graceMs: deps.abortGraceMs ?? ABORT_CANCEL_GRACE_MS,
|
|
1108
|
+
});
|
|
1052
1109
|
|
|
1053
1110
|
session.activePromptHandler = undefined;
|
|
1054
1111
|
session.busy = false;
|
|
@@ -1109,13 +1166,15 @@ export function streamAcpTurn(
|
|
|
1109
1166
|
|
|
1110
1167
|
// S2f visibility: about to send the delta to the resident child.
|
|
1111
1168
|
pushAcpLifecycleNotice(state, "sending prompt");
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1169
|
+
// #63: same hook boundary as a new turn — the reuse delta is the wire
|
|
1170
|
+
// params here. The recheck keeps an abort raised during a slow handler
|
|
1171
|
+
// ahead of the wire write; the prompt driver then owns the abort surface.
|
|
1172
|
+
const wireParams = await applyProviderPayloadHook(options, { sessionId: session.acpSessionId, prompt }, model);
|
|
1173
|
+
if (signal?.aborted) throw new Error("aborted during payload hook");
|
|
1174
|
+
const promptResult = await awaitAcpPromptTurn(session, wireParams, {
|
|
1175
|
+
signal,
|
|
1176
|
+
graceMs: deps.abortGraceMs ?? ABORT_CANCEL_GRACE_MS,
|
|
1177
|
+
});
|
|
1119
1178
|
|
|
1120
1179
|
session.activePromptHandler = undefined;
|
|
1121
1180
|
session.busy = false;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// claude-agent-acp's SettingsManager loads the operator's `~/.claude/settings.json`
|
|
4
4
|
// DIRECTLY (CLAUDE_CONFIG_DIR is the only knob that redirects that read). So the
|
|
5
|
-
// operator's native `permissions.defaultMode` (
|
|
6
|
-
// per-cwd memory/projects state would otherwise leak into entwurf ACP
|
|
5
|
+
// operator's native `permissions.defaultMode` (whatever its current value), hooks,
|
|
6
|
+
// plugins, and per-cwd memory/projects state would otherwise leak into entwurf ACP
|
|
7
7
|
// sessions. The overlay redirects SettingsManager at a pi-owned directory whose
|
|
8
8
|
// `settings.json` WE author (minimal, `hooks:{}`), while keeping exactly the
|
|
9
9
|
// operator entries a backend needs (credentials, caches, built-in skills)
|
|
@@ -105,9 +105,11 @@ export const OVERLAY_BINARY_OWNED: ReadonlySet<string> = new Set([".claude.json"
|
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
107
|
* Minimal overlay settings.json. Only fields with a reason to pin:
|
|
108
|
-
* - `permissions.defaultMode: "
|
|
109
|
-
*
|
|
110
|
-
*
|
|
108
|
+
* - `permissions.defaultMode: "bypassPermissions"` is deliberate unattended
|
|
109
|
+
* ACP operation: a tool call must never suspend a model turn on an interactive
|
|
110
|
+
* permission prompt. This does not widen the callable surface — explicit
|
|
111
|
+
* `tools`/`disallowedTools` still shape it, and `permissionAllow` still rides
|
|
112
|
+
* the inline Claude settings — or bypass backend authentication.
|
|
111
113
|
* - `autoMemoryEnabled: false` — SDK opt-out for auto-memory (defense in
|
|
112
114
|
* depth; the tiny non-empty engraving/preset replacement is the primary
|
|
113
115
|
* write-containment lever for Claude ACP).
|
|
@@ -117,7 +119,7 @@ export const OVERLAY_BINARY_OWNED: ReadonlySet<string> = new Set([".claude.json"
|
|
|
117
119
|
export function overlaySettingsJson(): string {
|
|
118
120
|
return `${JSON.stringify(
|
|
119
121
|
{
|
|
120
|
-
permissions: { defaultMode: "
|
|
122
|
+
permissions: { defaultMode: "bypassPermissions" },
|
|
121
123
|
autoMemoryEnabled: false,
|
|
122
124
|
hooks: {},
|
|
123
125
|
},
|
|
@@ -37,12 +37,14 @@ export interface SenderEnvelope {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* THE `<sender_info>` synthesis — the one place the sender envelope becomes
|
|
40
|
-
* message text.
|
|
40
|
+
* message text. ONE consumer since the visible-first cut (#50 C3):
|
|
41
41
|
* - the live socket rail's RECEIVER (entwurf-control handleCommand("send")
|
|
42
|
-
* appends it to the delivered customMessage)
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* appends it to the delivered customMessage).
|
|
43
|
+
*
|
|
44
|
+
* The second consumer was the dormant spawn-resume rail's SENDER, which appended
|
|
45
|
+
* the same shape to a resume prompt so a resumed citizen woke knowing who called.
|
|
46
|
+
* That rail is gone. The shape stays single-sourced here so a future VISIBLE
|
|
47
|
+
* resume renders an identical envelope rather than inventing a second one.
|
|
46
48
|
* `wants_reply` is emitted only when explicitly true — an unset/false marker
|
|
47
49
|
* renders nothing (etiquette marker, not transport contract).
|
|
48
50
|
*/
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* entwurf-core — sync entwurf execution, host-agnostic.
|
|
3
3
|
*
|
|
4
|
-
* DIRECT importers, MEASURED from the import graph (2026-
|
|
4
|
+
* DIRECT importers, RE-MEASURED from the import graph (2026-08-06) — not from memory:
|
|
5
5
|
* - pi-extensions/entwurf-control.ts (the pi adapter)
|
|
6
|
-
* - pi-extensions/lib/
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* - pi-extensions/lib/resume-launch-identity.ts (record-authoritative launch identity,
|
|
7
|
+
* itself consumer-zero since the visible-first cut — see its header)
|
|
8
|
+
* - plus gates/smokes (check-shell-quote, check-package-source-routing,
|
|
9
|
+
* check-entwurf-session-identity, new-session-id, resolve-acp-bridge)
|
|
10
10
|
*
|
|
11
|
-
* The MCP bridge
|
|
12
|
-
*
|
|
13
|
-
* entwurf-v2-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* runtime, only for spawn-bg production.
|
|
11
|
+
* The MCP bridge does NOT reach this module on any path, and that changed with the cut:
|
|
12
|
+
* its one edge ran `index.ts → entwurf-v2-surface.ts → entwurf-v2-production.ts →
|
|
13
|
+
* entwurf-v2-spawn-production.ts → entwurf-core.ts`, and the spawn-production link was
|
|
14
|
+
* deleted with the `spawn-bg` transport. Do not restate the old "transitive consumer,
|
|
15
|
+
* for spawn-bg production" line; re-measure before claiming either way.
|
|
17
16
|
*
|
|
18
17
|
* NOT consumers on any path: `pi-extensions/entwurf.ts` (the v1 pi native tool surface) and
|
|
19
18
|
* `pi-extensions/lib/entwurf-async.ts` were REMOVED in the 0.12 cutover. Do not reintroduce
|
|
@@ -131,9 +130,9 @@ export function normalizeCodexEntwurfModelForAcp(model?: string): string | undef
|
|
|
131
130
|
// The Entwurf Target Registry is GONE (#50 C3). `pi/entwurf-targets.json` and its
|
|
132
131
|
// reader chain (loadEntwurfTargets / resolveEntwurfTarget / EntwurfRegistryError +
|
|
133
132
|
// the ~/.pi/agent symlink machinery) were the v1 "narrow door" for spawn-model
|
|
134
|
-
// policy — but v2 never spawns from a model tuple: entwurf_v2
|
|
135
|
-
// already-identified record-backed citizen, and the model axis is the
|
|
136
|
-
// own (Identity Preservation Rule). The last readers were the RT-dead
|
|
133
|
+
// policy — but v2 never spawns at all, from a model tuple or otherwise: entwurf_v2
|
|
134
|
+
// addresses an already-identified record-backed citizen, and the model axis is the
|
|
135
|
+
// citizen's own (Identity Preservation Rule). The last readers were the RT-dead
|
|
137
136
|
// buildSessionName mirror and the v1 spawn guard, both swept with this cut.
|
|
138
137
|
// Bridge-extension routing for provider=entwurf survives below (getRegistryRouting
|
|
139
138
|
// ← scripts/resolve-acp-bridge.ts) and takes a caller-supplied target — no file.
|
|
@@ -211,7 +210,8 @@ const SESSION_READ_CHUNK_BYTES = 64 * 1024;
|
|
|
211
210
|
* old name-mirror integrity check and the `requireEntwurf` name-tag
|
|
212
211
|
* authorization are gone (#50 C3): resume authorization is record existence,
|
|
213
212
|
* and transcript integrity is the caller's header-id ↔ record.nativeSessionId
|
|
214
|
-
* check (
|
|
213
|
+
* check (resume-launch-identity.resolveResumeLaunchIdentity — the leaf that
|
|
214
|
+
* outlived the spawn-bg factory it used to sit in).
|
|
215
215
|
*/
|
|
216
216
|
export interface RecordedSessionIdentity {
|
|
217
217
|
/** JSONL header `id` (pi's own session id — the record's `nativeSessionId`). */
|