@junghanacs/entwurf 0.13.1 → 0.14.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 +59 -15
- package/BASELINE.md +3 -3
- package/CHANGELOG.md +74 -0
- package/CONTRIBUTING.md +13 -9
- package/DELIVERY.md +7 -6
- package/README.md +37 -28
- 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 +155 -28
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +8 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/classify-tmux-cwd.js +47 -0
- 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 +300 -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 +141 -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 +173 -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 +227 -40
- package/pi-extensions/lib/acp/backend.ts +71 -12
- package/pi-extensions/lib/acp/overlay.ts +8 -6
- package/pi-extensions/lib/classify-tmux-cwd.ts +50 -0
- 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 +381 -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 +189 -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 +99 -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 +283 -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-bridge-state.py +75 -1
- 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/meta-retire.json +47 -0
- package/scripts/mutants/mux-boundary.json +196 -0
- package/scripts/mutants/mux-fresh-call.json +229 -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-chain-live.ts +11 -3
- package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
- package/scripts/smoke-meta-honesty.sh +1 -1
- package/scripts/smoke-meta-install-state.sh +169 -3
- 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
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Currently exposed tools (scope is deliberately narrow — anything that can live
|
|
12
12
|
* as a local skill should live as a skill, not here):
|
|
13
13
|
* - entwurf_v2 — canonical delivery surface for existing garden citizens; the decider
|
|
14
|
-
* chooses live control-socket send /
|
|
14
|
+
* chooses live control-socket send / meta-mailbox enqueue / native-push.
|
|
15
15
|
* - entwurf_peers — entwurf fact surface: garden citizens (meta-records) with liveness +
|
|
16
16
|
* diagnostics (#50 C4: record-less sockets surface THERE, never as identity).
|
|
17
17
|
* Brain = pi-extensions/lib/entwurf-fact-provider (listEntwurfFacts) +
|
|
@@ -24,6 +24,19 @@
|
|
|
24
24
|
* garden record may call this — so the surface is "drain the inbox you were
|
|
25
25
|
* pointed at", not "drain your own". A rung doorbell is a wake attempt;
|
|
26
26
|
* this read is the receipt.
|
|
27
|
+
* - entwurf_register_native — explicit/manual fallback binding an ALREADY-RUNNING native
|
|
28
|
+
* conversation (antigravity) to a garden id. Never a spawn.
|
|
29
|
+
* - entwurf_fresh_call — open ONE fresh visible sibling in the operator's own tmux session,
|
|
30
|
+
* optionally at ONE literal requested cwd (cross-repo fresh, #73);
|
|
31
|
+
* returns a LAUNCH receipt only, and the new address arrives later as the
|
|
32
|
+
* sender envelope of the sibling's nonce callback.
|
|
33
|
+
* - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id in a visible
|
|
34
|
+
* window; target-only, runs no turn, LAUNCH and OBSERVATION receipts stay
|
|
35
|
+
* apart.
|
|
36
|
+
*
|
|
37
|
+
* That list is the WHOLE public surface — seven verbs — and `check-entwurf-bridge-boot`
|
|
38
|
+
* (G1f) holds it as an exact set on the runtime tools/list, so a verb added or dropped
|
|
39
|
+
* here without a decision is red rather than merely undocumented.
|
|
27
40
|
*
|
|
28
41
|
* Removed from this v2-only surface: legacy MCP `entwurf`, `entwurf_resume`, and
|
|
29
42
|
* `entwurf_send`. Use `entwurf_v2` for delivery to existing garden citizens.
|
|
@@ -57,8 +70,11 @@ import { renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-ren
|
|
|
57
70
|
import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
|
|
58
71
|
import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
|
|
59
72
|
import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.js";
|
|
73
|
+
import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
|
|
60
74
|
import { probeNativeSenderAlive, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
|
|
61
75
|
import { defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, } from "../../../pi-extensions/lib/meta-session.js";
|
|
76
|
+
import { freshCall, renderFreshCall } from "../../../pi-extensions/lib/mux-fresh-call.js";
|
|
77
|
+
import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
|
|
62
78
|
import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
|
|
63
79
|
const HOME = os.homedir();
|
|
64
80
|
// Directory SOURCE is this adapter's own policy — the bridge honours an explicit
|
|
@@ -286,13 +302,12 @@ function abbreviateHomeMcp(cwd) {
|
|
|
286
302
|
}
|
|
287
303
|
// entwurf_v2 — the unified v2 dispatch verb (0.11 step 5d-3b). It hands the
|
|
288
304
|
// target + intent to the 5b decider, which chooses the transport (live
|
|
289
|
-
// control-socket send /
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
// native-push branches carry `lock: null` (entwurf-v2-decider.ts). It runs
|
|
305
|
+
// control-socket send / meta-mailbox enqueue / native-push direct injection) and
|
|
306
|
+
// reports one outcome. The per-target lock is NOT taken by every rail: the decider
|
|
307
|
+
// locks only a control-socket-domain dispatch; the mailbox and native-push branches
|
|
308
|
+
// carry `lock: null` (entwurf-v2-decider.ts). It runs
|
|
294
309
|
// IN-PROCESS here (the same production runner pi-native uses) — NOT a delegating
|
|
295
|
-
// RPC — so control, mailbox
|
|
310
|
+
// RPC — so control, mailbox and native-push all flow through
|
|
296
311
|
// `runEntwurfV2`. The sender envelope is
|
|
297
312
|
// `buildSendSenderEnvelope()` verbatim (origin/replyable as resolved) — v2 does
|
|
298
313
|
// NOT gate on replyability (a `wants_reply` from an external/non-replyable caller
|
|
@@ -300,11 +315,10 @@ function abbreviateHomeMcp(cwd) {
|
|
|
300
315
|
// sender replyability).
|
|
301
316
|
server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id " +
|
|
302
317
|
"alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport " +
|
|
303
|
-
"from liveness (live socket citizen → control-socket send;
|
|
304
|
-
"
|
|
305
|
-
"
|
|
306
|
-
"
|
|
307
|
-
"rejected, never auto-converted. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A " +
|
|
318
|
+
"from liveness (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge " +
|
|
319
|
+
"mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE " +
|
|
320
|
+
"outcome (delivered / rejected / delivered-but-lock-dirty). EXISTING targets only; discover with " +
|
|
321
|
+
"entwurf_peers. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A " +
|
|
308
322
|
"citizen with NO socket liveness (liveness=unsupported) is ALSO fire-and-forget — unsupported means only " +
|
|
309
323
|
'"no control-socket probe" — and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) ' +
|
|
310
324
|
"gets the mailbox, a native-push backend (e.g. Antigravity) gets direct injection and has NO mailbox at " +
|
|
@@ -312,24 +326,25 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
|
|
|
312
326
|
"session, or a backend with no adapter here (e.g. codex), is mailbox-undeliverable, not queued for an " +
|
|
313
327
|
"inbox nobody drains. The native-push probe is 3-valued: alive → injected; dead → " +
|
|
314
328
|
"native-push-target-dead; indeterminate → native-push-probe-indeterminate (unestablished ≠ gone). " +
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
329
|
+
"DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported " +
|
|
330
|
+
"— same receiver rule as the mailbox, no active drainer means no delivery. " +
|
|
331
|
+
"The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child " +
|
|
332
|
+
"and was withdrawn under the visible-first rule — re-open the session yourself, then dispatch again. " +
|
|
333
|
+
"LOCK: taken for a control-socket-DOMAIN dispatch. The " +
|
|
319
334
|
"mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode " +
|
|
320
335
|
"applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message " +
|
|
321
336
|
"caps at 16000 chars; send an artifact path + digest for more.", {
|
|
322
337
|
target: z.string().min(1).describe("Target garden id (use entwurf_peers to discover)"),
|
|
323
338
|
intent: z
|
|
324
|
-
.enum(["fire-and-forget"
|
|
339
|
+
.enum(["fire-and-forget"])
|
|
325
340
|
.describe("fire-and-forget = send/reply/hand-off to a LIVE socket target or to any citizen with no " +
|
|
326
341
|
"socket liveness — the decider picks its rail, and a rail can also REJECT (self-fetch → " +
|
|
327
342
|
"mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, " +
|
|
328
343
|
"dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate); set " +
|
|
329
|
-
"wants_reply for an answer. owned-outcome
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
"
|
|
344
|
+
"wants_reply for an answer. This is the ONLY intent: owned-outcome, which resumed a dormant " +
|
|
345
|
+
"citizen by launching a hidden background child, was withdrawn under the visible-first rule. " +
|
|
346
|
+
"It is not selectable, not deprecated-but-tolerated; a dormant citizen is currently " +
|
|
347
|
+
"unreachable by this verb and rejects as dormant-fire-forget-unsupported."),
|
|
333
348
|
message: z
|
|
334
349
|
.string()
|
|
335
350
|
.min(1)
|
|
@@ -339,8 +354,8 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
|
|
|
339
354
|
.enum(["steer", "follow_up"])
|
|
340
355
|
.optional()
|
|
341
356
|
.describe("Injection style for a CONTROL-SOCKET send only: steer (interrupt the current turn) or " +
|
|
342
|
-
"follow_up (queue after it). The mailbox
|
|
343
|
-
"
|
|
357
|
+
"follow_up (queue after it). The mailbox and native-push plans carry no mode, so it has no " +
|
|
358
|
+
"effect on those rails."),
|
|
344
359
|
wants_reply: z.boolean().optional().describe("Human-conversation reply hint (default false)"),
|
|
345
360
|
}, async ({ target, intent, message, mode, wants_reply }) => {
|
|
346
361
|
try {
|
|
@@ -348,8 +363,9 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
|
|
|
348
363
|
// + the mailbox body sender share one envelope. No replyability gate (see above).
|
|
349
364
|
const sender = await buildSendSenderEnvelope();
|
|
350
365
|
const rendered = await runAndRenderEntwurfV2FromSurface({ target, intent, message, mode, wants_reply },
|
|
351
|
-
//
|
|
352
|
-
//
|
|
366
|
+
// No trust-preflight inputs are passed, and none exist to pass: the preflight on this
|
|
367
|
+
// path guarded the resume verdict and left with `owned-outcome`. `senderProvider` is
|
|
368
|
+
// the whole options surface now (see the pi-native surface for the same note).
|
|
353
369
|
{ senderProvider: () => sender });
|
|
354
370
|
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
355
371
|
}
|
|
@@ -432,7 +448,9 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
|
|
|
432
448
|
"`unsupported` for a backend with no control-socket probe such as claude-code); the dispatch " +
|
|
433
449
|
"decision (send vs resume) is computed LATER by the entwurf_v2 contract from that liveness, " +
|
|
434
450
|
"not here — this surface carries no per-row routing field, so do not read a transport off a " +
|
|
435
|
-
"row.
|
|
451
|
+
"row. A `dead` row is a REPORTED FACT and nothing more: that citizen is dormant and is " +
|
|
452
|
+
"currently unreachable by any verb, so listing it grants no action — appearing here is not " +
|
|
453
|
+
"an invitation to dispatch. In particular `unsupported` does NOT mean mailbox: it means this backend has no " +
|
|
436
454
|
"control-socket probe. Which rail it answers on is a capability the decider resolves at dispatch " +
|
|
437
455
|
"time, and there are THREE possible answers, not two — a self-fetch mailbox (only while that " +
|
|
438
456
|
"mailbox is deliverable), native-push direct injection (only while its adapter probe is alive), " +
|
|
@@ -442,8 +460,8 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
|
|
|
442
460
|
"is not a departed host. So " +
|
|
443
461
|
"`unsupported` does not promise reachability either: a record whose backend has no adapter on " +
|
|
444
462
|
"this lane resolves to that reject. " +
|
|
445
|
-
"Note: this is the *active* world
|
|
446
|
-
"existing garden id to entwurf_v2.", {}, async () => {
|
|
463
|
+
"Note: this is the *active* world and it is facts-only — it creates nothing. Pass an " +
|
|
464
|
+
"existing garden id to entwurf_v2; to open a NEW sibling use entwurf_fresh_call.", {}, async () => {
|
|
447
465
|
try {
|
|
448
466
|
// Meta-store axis: entries WITH their kind (ENOENT = fresh install = empty; any
|
|
449
467
|
// other readdir failure is a real error, not a silent empty). The name-only
|
|
@@ -535,6 +553,115 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
|
|
|
535
553
|
return textErr(`entwurf_register_native error: ${err instanceof Error ? err.message : String(err)}`);
|
|
536
554
|
}
|
|
537
555
|
});
|
|
556
|
+
// The caller identity for THIS surface comes from the same authoritative resolution
|
|
557
|
+
// `entwurf_self` uses — BOTH of its certified paths: the pi carrier this child inherited
|
|
558
|
+
// (PI_SESSION_ID + PI_AGENT_ID, which a record minted at birth), or a trusted meta-sender marker
|
|
559
|
+
// the native host's own hook wrote. Neither is a guess and neither is deprecated here.
|
|
560
|
+
//
|
|
561
|
+
// What is refused is a caller-supplied id. The measured failure mode is exactly that: asked for
|
|
562
|
+
// its own garden id, a fresh cell answered with its uuidv7 `PI_SESSION_ID` value read out of the
|
|
563
|
+
// environment by an MCP server it had spawned itself — confidently, and wrong. A sibling launched
|
|
564
|
+
// against that answer would call home to a garden id nobody holds.
|
|
565
|
+
server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task. Two fixed " +
|
|
566
|
+
"backends only: pi, claude-code. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
|
|
567
|
+
"sender envelope of that callback is its garden id — that is how you learn the address of something that " +
|
|
568
|
+
"did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
|
|
569
|
+
"else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
|
|
570
|
+
"for the callback; if it never arrives the window is visible and can be read directly. For EXISTING " +
|
|
571
|
+
"citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
|
|
572
|
+
"is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code). An optional " +
|
|
573
|
+
"cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume " +
|
|
574
|
+
"for a dormant record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. " +
|
|
575
|
+
"There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
|
|
576
|
+
"same-user processes on this host. Requires that this agent itself runs " +
|
|
577
|
+
"inside tmux: without a pane anchor there is no session to open a sibling beside.", {
|
|
578
|
+
backend: z
|
|
579
|
+
.enum(["pi", "claude-code"])
|
|
580
|
+
.describe("Which fixed runtime to open. Only these two; there is no arbitrary command."),
|
|
581
|
+
model: z
|
|
582
|
+
.string()
|
|
583
|
+
.min(1)
|
|
584
|
+
.max(200)
|
|
585
|
+
// The `\[` below is USELESS TO JS AND LOAD-BEARING TO THE HOST. zod emits this source
|
|
586
|
+
// text verbatim as the JSON Schema `pattern`, and the host's tool-schema validator is a
|
|
587
|
+
// Rust regex engine that rejects an unescaped `[` inside a character class as "unclosed
|
|
588
|
+
// character class" — a 400 on tools/list that stops EVERY Claude session from opening
|
|
589
|
+
// (7-M-fix). Biome's safe fix removes it; do not accept that fix. The suppression must
|
|
590
|
+
// stay on the line DIRECTLY above the expression, or it attaches to nothing and reads
|
|
591
|
+
// as an unused suppression while the escape goes back to being fixable.
|
|
592
|
+
// biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
|
|
593
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
|
|
594
|
+
.describe("Required runtime model: canonical provider/model for pi, or a Claude Code model id/alias."),
|
|
595
|
+
task: z
|
|
596
|
+
.string()
|
|
597
|
+
.min(1)
|
|
598
|
+
.max(16000)
|
|
599
|
+
.describe("What the sibling should do after it calls you back. Plain instructions; no secrets (see the tool description)."),
|
|
600
|
+
cwd: z
|
|
601
|
+
.string()
|
|
602
|
+
.optional()
|
|
603
|
+
.describe("Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \"\" to start in this agent's own cwd. Taken exactly as given — no trim, no realpath, no project-name resolution; '#' is refused (tmux format expansion). The receipt echoes what was REQUESTED, never an observation."),
|
|
604
|
+
}, async ({ backend, model, task, cwd }) => {
|
|
605
|
+
let callerGardenId = null;
|
|
606
|
+
try {
|
|
607
|
+
const self = await buildAuthoritativeSelfEnvelope();
|
|
608
|
+
callerGardenId = self.envelope.sessionId;
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
// ONE error is a legitimate answer here: this host has no authoritative identity at all
|
|
612
|
+
// (no pi carrier inherited, no trusted marker written), so the sibling would have nowhere
|
|
613
|
+
// to call home. That normalises to the named refusal.
|
|
614
|
+
//
|
|
615
|
+
// Everything else — a corrupt record, an unreadable store, a broken marker — keeps its
|
|
616
|
+
// own diagnosis and fails loud. Folding those into "you are anonymous" would relabel a
|
|
617
|
+
// store defect as a wiring choice, and the operator would go looking in the wrong place.
|
|
618
|
+
if (!(err instanceof EntwurfEnvelopeWiringError))
|
|
619
|
+
throw err;
|
|
620
|
+
callerGardenId = null;
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
const rendered = renderFreshCall(freshCall({ backend, model, task, cwd, callerGardenId }));
|
|
624
|
+
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
return textErr(`entwurf_fresh_call error: ${err instanceof Error ? err.message : String(err)}`);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
// The composition root for a visible resume: this surface is the only layer that may know BOTH
|
|
631
|
+
// halves, so it is where the mux launcher is handed to the v2 composition. `entwurf-v2-visible-
|
|
632
|
+
// resume` cannot open a window on its own and `mux-resume-call` cannot read a record — that is
|
|
633
|
+
// the import fence in docs/mux-launch-rail.md §11, expressed as a function argument.
|
|
634
|
+
const visibleResumeLaunch = (input) => {
|
|
635
|
+
const launched = resumeCall({ cwd: input.cwd, runtimeArgs: input.runtimeArgs });
|
|
636
|
+
return launched.ok
|
|
637
|
+
? { ok: true, handle: launched.receipt }
|
|
638
|
+
: { ok: false, reason: launched.reason, hint: RESUME_CALL_REJECT_HINT[launched.reason] };
|
|
639
|
+
};
|
|
640
|
+
server.tool("entwurf_resume_call", "Reopen ONE DORMANT pi citizen under its OWN garden id, in a visible window in the operator's own tmux " +
|
|
641
|
+
"session. The record supplies everything — which transcript, which model, which provider, which cwd — so " +
|
|
642
|
+
"the only input is the target id: there is no model override, no task, and no prompt. This runs NO turn: " +
|
|
643
|
+
"the window comes back with the conversation and waits, and talking to it is still entwurf_v2 " +
|
|
644
|
+
"fire-and-forget on the socket this call stands up. You get TWO receipts and they mean different things: a " +
|
|
645
|
+
"LAUNCH receipt (tmux made a window and was asked to start pi) and an OBSERVATION receipt (the control " +
|
|
646
|
+
"socket answered under the same id, or resume-unobserved). Unobserved is a real outcome, not an error to " +
|
|
647
|
+
"retry — the window is visible, so read it. A citizen that is already LIVE is refused: address it with " +
|
|
648
|
+
"entwurf_v2 instead. Only pi citizens have a same-id resume, because only they stand a control socket up. " +
|
|
649
|
+
"Requires that this agent itself runs inside tmux.", {
|
|
650
|
+
target: z
|
|
651
|
+
.string()
|
|
652
|
+
.min(1)
|
|
653
|
+
.regex(/^\d{8}T\d{6}-[0-9a-f]{6}$/)
|
|
654
|
+
.describe("Garden id of the DORMANT pi citizen to reopen (discover with entwurf_peers)."),
|
|
655
|
+
}, async ({ target }) => {
|
|
656
|
+
try {
|
|
657
|
+
const result = await visibleResume(target, makeVisibleResumeDeps(visibleResumeLaunch));
|
|
658
|
+
const rendered = renderVisibleResume(result);
|
|
659
|
+
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
538
665
|
// ============================================================================
|
|
539
666
|
// Main
|
|
540
667
|
// ============================================================================
|
|
@@ -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)
|
|
@@ -86,9 +86,11 @@ export const OVERLAY_EMPTY_DIRS = new Set(["projects", "sessions"]);
|
|
|
86
86
|
export const OVERLAY_BINARY_OWNED = new Set([".claude.json", "backups", "settings.json"]);
|
|
87
87
|
/**
|
|
88
88
|
* Minimal overlay settings.json. Only fields with a reason to pin:
|
|
89
|
-
* - `permissions.defaultMode: "
|
|
90
|
-
*
|
|
91
|
-
*
|
|
89
|
+
* - `permissions.defaultMode: "bypassPermissions"` is deliberate unattended
|
|
90
|
+
* ACP operation: a tool call must never suspend a model turn on an interactive
|
|
91
|
+
* permission prompt. This does not widen the callable surface — explicit
|
|
92
|
+
* `tools`/`disallowedTools` still shape it, and `permissionAllow` still rides
|
|
93
|
+
* the inline Claude settings — or bypass backend authentication.
|
|
92
94
|
* - `autoMemoryEnabled: false` — SDK opt-out for auto-memory (defense in
|
|
93
95
|
* depth; the tiny non-empty engraving/preset replacement is the primary
|
|
94
96
|
* write-containment lever for Claude ACP).
|
|
@@ -97,7 +99,7 @@ export const OVERLAY_BINARY_OWNED = new Set([".claude.json", "backups", "setting
|
|
|
97
99
|
*/
|
|
98
100
|
export function overlaySettingsJson() {
|
|
99
101
|
return `${JSON.stringify({
|
|
100
|
-
permissions: { defaultMode: "
|
|
102
|
+
permissions: { defaultMode: "bypassPermissions" },
|
|
101
103
|
autoMemoryEnabled: false,
|
|
102
104
|
hooks: {},
|
|
103
105
|
}, null, 2)}\n`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* classify-tmux-cwd — the ONE classification of a start directory that is about to be handed
|
|
3
|
+
* to tmux as a `-c` value. Shared leaf of the resume and fresh launch compositions; it owns
|
|
4
|
+
* the classification and NOTHING else — no hints (each consumer phrases its own: resume says
|
|
5
|
+
* "recorded cwd", fresh says "requested cwd"), no argv, no tmux, no fallback directory.
|
|
6
|
+
*
|
|
7
|
+
* Every rule below is a MEASURED tmux 3.6a behaviour (2026-08-06, private server), and each
|
|
8
|
+
* one is a way a launch would look successful while being wrong:
|
|
9
|
+
*
|
|
10
|
+
* 1. a NONEXISTENT `-c` is silent. tmux exits 0, opens the window, and the child falls back
|
|
11
|
+
* to `$HOME`. A launch whose directory has been deleted would therefore open a visible
|
|
12
|
+
* window in the wrong project and look successful. Nothing downstream can catch that:
|
|
13
|
+
* the launch receipt would be perfectly well-formed.
|
|
14
|
+
* 2. `-c` is FORMAT-EXPANDED. `#{pane_id}` inside the value silently rewrote the path
|
|
15
|
+
* (`<dir>/#{pane_id}` → `<dir>/%0`), and a `#(…)` value was observed running its
|
|
16
|
+
* command. A path is data; tmux reads it as a format. So `#` is refused outright.
|
|
17
|
+
* 3. whitespace is SAFE — argv is an array and nothing re-splits. A dir named `with space`
|
|
18
|
+
* arrived intact. So there is no quoting grammar here, and none is owed.
|
|
19
|
+
*
|
|
20
|
+
* That is the entire defence: one existence check and one character. No escaping layer, no
|
|
21
|
+
* sanitiser, no trim, no realpath/symlink policy — a symlinked project dir is a normal thing
|
|
22
|
+
* to work in, and a value is classified exactly as given.
|
|
23
|
+
*/
|
|
24
|
+
import { statSync } from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
/**
|
|
27
|
+
* Classify a candidate start directory. Split into separate reasons rather than one because
|
|
28
|
+
* the operator's next move differs: an absolute-path bug is a caller defect, a missing
|
|
29
|
+
* directory is a moved/deleted project, and a `#` is a path tmux would rewrite under us.
|
|
30
|
+
*/
|
|
31
|
+
export function classifyTmuxCwd(cwd) {
|
|
32
|
+
if (!path.isAbsolute(cwd))
|
|
33
|
+
return "cwd-not-absolute";
|
|
34
|
+
// tmux expands formats inside the `-c` VALUE. `#{…}` rewrote the path silently and `#(…)`
|
|
35
|
+
// was observed executing; neither is something to escape our way out of.
|
|
36
|
+
if (cwd.includes("#"))
|
|
37
|
+
return "cwd-format-token";
|
|
38
|
+
let st;
|
|
39
|
+
try {
|
|
40
|
+
st = statSync(cwd);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// tmux would NOT report this — it opens the window and lands the child in $HOME.
|
|
44
|
+
return "cwd-missing";
|
|
45
|
+
}
|
|
46
|
+
return st.isDirectory() ? null : "cwd-not-directory";
|
|
47
|
+
}
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
import * as net from "node:net";
|
|
17
17
|
/**
|
|
18
18
|
* THE `<sender_info>` synthesis — the one place the sender envelope becomes
|
|
19
|
-
* message text.
|
|
19
|
+
* message text. ONE consumer since the visible-first cut (#50 C3):
|
|
20
20
|
* - the live socket rail's RECEIVER (entwurf-control handleCommand("send")
|
|
21
|
-
* appends it to the delivered customMessage)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* appends it to the delivered customMessage).
|
|
22
|
+
*
|
|
23
|
+
* The second consumer was the dormant spawn-resume rail's SENDER, which appended
|
|
24
|
+
* the same shape to a resume prompt so a resumed citizen woke knowing who called.
|
|
25
|
+
* That rail is gone. The shape stays single-sourced here so a future VISIBLE
|
|
26
|
+
* resume renders an identical envelope rather than inventing a second one.
|
|
25
27
|
* `wants_reply` is emitted only when explicitly true — an unset/false marker
|
|
26
28
|
* renders nothing (etiquette marker, not transport contract).
|
|
27
29
|
*/
|
|
@@ -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
|
|
@@ -113,9 +112,9 @@ export function normalizeCodexEntwurfModelForAcp(model) {
|
|
|
113
112
|
// The Entwurf Target Registry is GONE (#50 C3). `pi/entwurf-targets.json` and its
|
|
114
113
|
// reader chain (loadEntwurfTargets / resolveEntwurfTarget / EntwurfRegistryError +
|
|
115
114
|
// the ~/.pi/agent symlink machinery) were the v1 "narrow door" for spawn-model
|
|
116
|
-
// policy — but v2 never spawns from a model tuple: entwurf_v2
|
|
117
|
-
// already-identified record-backed citizen, and the model axis is the
|
|
118
|
-
// own (Identity Preservation Rule). The last readers were the RT-dead
|
|
115
|
+
// policy — but v2 never spawns at all, from a model tuple or otherwise: entwurf_v2
|
|
116
|
+
// addresses an already-identified record-backed citizen, and the model axis is the
|
|
117
|
+
// citizen's own (Identity Preservation Rule). The last readers were the RT-dead
|
|
119
118
|
// buildSessionName mirror and the v1 spawn guard, both swept with this cut.
|
|
120
119
|
// Bridge-extension routing for provider=entwurf survives below (getRegistryRouting
|
|
121
120
|
// ← scripts/resolve-acp-bridge.ts) and takes a caller-supplied target — no file.
|
|
@@ -1,53 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* entwurf-resume-args — the SINGLE source of truth for the `pi` argv
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
2
|
+
* entwurf-resume-args — the SINGLE source of truth for the `pi` argv that reopens a dormant
|
|
3
|
+
* citizen, MEASURED against the runtime rather than inherited from the removed transport.
|
|
4
|
+
*
|
|
5
|
+
* ── What changed, and why the old shape was not reusable ──
|
|
6
|
+
*
|
|
7
|
+
* Until the visible-first cut this builder emitted `--mode json -p … <prompt>`: a headless JSON
|
|
8
|
+
* child running one prompt-as-turn, which is what `spawn-bg`'s detached `defaultSpawnChild`
|
|
9
|
+
* needed. The design notes carried that argv forward as "visibility-neutral, reuse verbatim".
|
|
10
|
+
* It is not. Measured 2026-08-06 (private tmux + fixture, `pi --help`: `--print, -p` =
|
|
11
|
+
* *non-interactive mode*): dropped into a tmux window, the headless prefix produces a JSON
|
|
12
|
+
* stream and a turn, not a window an operator can type into. The visible dialect is the one
|
|
13
|
+
* `mux-fresh-call` already measured for a fresh sibling — no `--mode`, no `-p`.
|
|
14
|
+
*
|
|
15
|
+
* So the shipped posture is now one shape, and it is the only one:
|
|
16
|
+
*
|
|
17
|
+
* pi --entwurf-control [-e <bridge> …] --session <file> [--provider <p>] --model <m>
|
|
18
|
+
*
|
|
19
|
+
* There is deliberately NO prompt and no `launchArgs`. A resume opens the window; talking to the
|
|
20
|
+
* citizen afterwards is `entwurf_v2`'s job on the socket this launch stands up. That split is
|
|
21
|
+
* what makes the resumed turn free: measured, a promptless resume left the transcript
|
|
22
|
+
* byte-identical (1666 → 1666) and started no model turn at all.
|
|
23
|
+
*
|
|
24
|
+
* The one-shot `legacy` variant (removed 2026-07-27) and the headless prefix (removed here) are
|
|
25
|
+
* not kept behind a variant flag for compatibility. An exported branch no product path takes is
|
|
26
|
+
* exactly what let this module's prose claim a live consumer for months.
|
|
27
|
+
*
|
|
28
|
+
* Provider/model identity is the caller's authority (`resolveResumeLaunchIdentity`) — this
|
|
29
|
+
* builder only LAYS OUT argv, it never resolves identity. `explicitExtensionArgs` is preserved
|
|
30
|
+
* verbatim: a recorded `provider=entwurf` resume needs the bridge re-injected or pi cannot
|
|
31
|
+
* resolve the provider (#29). Measured on that axis: the resolver emits
|
|
32
|
+
* `["-e", "<bridge>"]`, and the argv carries it exactly once, between `--entwurf-control` and
|
|
33
|
+
* `--session`.
|
|
34
|
+
*
|
|
35
|
+
* The runtime path itself is NOT here. Resolving `pi` on PATH and proving it launchable belongs
|
|
36
|
+
* to the mux lane (`mux-launch`), so this module emits the flags after the runtime and nothing
|
|
37
|
+
* more — which is also why it stays import-free and can be read by both tsconfigs.
|
|
27
38
|
*/
|
|
28
39
|
/**
|
|
29
|
-
* Build the `pi`
|
|
30
|
-
* child, prompt-as-turn); then the resident posture; then
|
|
31
|
-
* `[…ext args] --session <file> [--provider <p>] --model <m> <prompt>`.
|
|
40
|
+
* Build the `pi` flags for a VISIBLE resume, in the measured order.
|
|
32
41
|
*
|
|
33
42
|
* Invariants the gate pins:
|
|
34
|
-
* -
|
|
35
|
-
*
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
43
|
+
* - `--entwurf-control` is FIRST: the resumed session must stand its control socket up, or
|
|
44
|
+
* the transcript comes back with no address and the citizen is still unreachable.
|
|
45
|
+
* - no `--mode`, no `-p`, no positional prompt — the window is interactive and no turn runs.
|
|
46
|
+
* - `explicitExtensionArgs` appears exactly once, after the control flag and before
|
|
47
|
+
* `--session`.
|
|
48
|
+
* - provider is emitted only when recorded; `--model <m>` is the tail.
|
|
38
49
|
*/
|
|
39
50
|
export function buildResumePiArgs(input) {
|
|
40
|
-
const args = ["--
|
|
41
|
-
// Resident citizen: stand the control socket up (A1) and keep extensions loaded. The
|
|
42
|
-
// keep-alive the removed one-shot launcher had to avoid is precisely the goal here —
|
|
43
|
-
// the resumed session must stay addressable. `--approve`/launchArgs ride along.
|
|
44
|
-
args.push("--entwurf-control");
|
|
45
|
-
args.push(...(input.launchArgs ?? []));
|
|
46
|
-
// Suffix — the fixed identity layout.
|
|
51
|
+
const args = ["--entwurf-control"];
|
|
47
52
|
args.push(...input.explicitExtensionArgs);
|
|
48
53
|
args.push("--session", input.sessionFile);
|
|
49
54
|
if (input.provider)
|
|
50
55
|
args.push("--provider", input.provider);
|
|
51
|
-
args.push("--model", input.model
|
|
56
|
+
args.push("--model", input.model);
|
|
52
57
|
return args;
|
|
53
58
|
}
|