@junghanacs/entwurf 0.41.2 → 0.50.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 +4 -2
- package/BASELINE.md +110 -1
- package/CHANGELOG.md +328 -0
- package/CONTRIBUTING.md +1 -1
- package/ENTWURF-CLI.json +1229 -0
- package/ENTWURF.json +284 -0
- package/FAQ.md +4 -4
- package/README.md +125 -184
- package/VERIFY.md +2 -2
- package/docs/external-mcp-host.md +3 -3
- package/docs/setup-clean-host.md +37 -12
- package/mcp/entwurf-bridge/dist/ENTWURF.json +284 -0
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +46 -196
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/agent-guidance.js +299 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +21 -25
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +498 -29
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +613 -33
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-resume-call.js +497 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +23 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +68 -21
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-location.js +347 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js +1132 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +7 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +18 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -60
- package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +40 -7
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +1 -27
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +1 -1
- package/mcp/entwurf-bridge/src/index.ts +149 -327
- package/mcp/entwurf-bridge/tsconfig.build.json +7 -1
- package/native/win32-x64/manifest.json +1 -1
- package/package.json +8 -4
- package/pi/pi-durable/bootstrap.mjs +179 -6
- package/pi/pi-durable/carrier/runtime.js +108 -9
- package/pi/pi-durable/carrier/session-name.js +11 -0
- package/pi/pi-durable/carrier/sessions.js +96 -20
- package/pi/pi-durable/overlay/upstream-pin.json +11 -5
- package/pi-extensions/lib/agent-guidance.ts +341 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +25 -31
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +725 -54
- package/pi-extensions/lib/fresh-call-dispatch.ts +64 -12
- package/pi-extensions/lib/herdr-fresh-call.ts +919 -40
- package/pi-extensions/lib/herdr-resume-call.ts +810 -0
- package/pi-extensions/lib/meta-session.ts +13 -8
- package/pi-extensions/lib/mux-fresh-call.ts +23 -26
- package/pi-extensions/lib/mux-resume-call.ts +95 -23
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +9 -2
- package/pi-extensions/lib/pi-durable-location.ts +446 -0
- package/pi-extensions/lib/pi-durable-owner-fence.ts +1461 -0
- package/pi-extensions/lib/pi-durable-runtime.ts +4 -0
- package/pi-extensions/lib/remote-pair-send.ts +7 -4
- package/pi-extensions/lib/resume-launch-identity.ts +31 -12
- package/pi-extensions/meta-bridge-pi-durable.ts +52 -79
- package/run.sh +417 -654
- package/scripts/build-bridge.sh +10 -0
- package/scripts/check-capability-bundle-reach.ts +86 -0
- package/scripts/check-claude-launch.ts +341 -0
- package/scripts/check-entwurf-self-address.ts +3 -3
- package/scripts/check-entwurf-v2-surface.ts +94 -98
- package/scripts/check-fresh-call-dispatch.ts +70 -9
- package/scripts/check-fresh-cut-gate.sh +23 -9
- package/scripts/check-gate-qualification.ts +24 -17
- package/scripts/check-herdr-activation.ts +301 -23
- package/scripts/check-herdr-fresh-call.ts +161 -25
- package/scripts/check-herdr-plugin-build.ts +173 -1
- package/scripts/check-herdr-plugin.ts +28 -2
- package/scripts/check-herdr-runtime-bootstrap.ts +136 -0
- package/scripts/check-install-surface.ts +1236 -6
- package/scripts/check-meta-doctor-oracle.sh +1 -1
- package/scripts/check-meta-facts.ts +20 -1
- package/scripts/check-mux-resume-call.ts +48 -3
- package/scripts/check-peer-facts.ts +39 -2
- package/scripts/cli-help.mjs +222 -0
- package/scripts/dev-bin.sh +39 -2
- package/scripts/herdr-activation.mjs +28 -6
- package/scripts/herdr-operator-link.mjs +389 -0
- package/scripts/herdr-plugin-activate.mjs +5 -0
- package/scripts/herdr-plugin-deactivate.mjs +18 -5
- package/scripts/herdr-rails.mjs +43 -7
- package/scripts/herdr-runtime.mjs +27 -0
- package/scripts/meta-bridge-fresh-cut.ts +1 -27
- package/scripts/meta-bridge-prune.ts +1 -1
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/agent-guidance.json +160 -0
- package/scripts/mutants/bridge-boot-resume.json +6 -8
- package/scripts/mutants/capability-bundle-reach.json +124 -0
- package/scripts/mutants/claude-launch.json +142 -0
- package/scripts/mutants/cli-help-source.json +420 -0
- package/scripts/mutants/control-send-receipt.json +21 -4
- package/scripts/mutants/dev-gate-dispatch.json +109 -0
- package/scripts/mutants/fresh-call-dispatch.json +38 -2
- package/scripts/mutants/fresh-cut.json +15 -0
- package/scripts/mutants/herdr-activation.json +128 -0
- package/scripts/mutants/herdr-fresh-call.json +278 -5
- package/scripts/mutants/herdr-operator-link.json +318 -0
- package/scripts/mutants/herdr-plugin-build.json +55 -0
- package/scripts/mutants/herdr-plugin.json +17 -0
- package/scripts/mutants/herdr-resume-call.json +288 -0
- package/scripts/mutants/herdr-runtime-bootstrap.json +48 -0
- package/scripts/mutants/meta-facts.json +15 -0
- package/scripts/mutants/mux-fresh-call.json +14 -3
- package/scripts/mutants/mux-resume-call.json +89 -3
- package/scripts/mutants/peer-facts.json +15 -0
- package/scripts/mutants/pi-durable.json +996 -6
- package/scripts/mutants/remote-pair-send.json +21 -2
- package/scripts/mutants/self-address.json +10 -10
- package/scripts/mutants/source-install.json +35 -0
- package/scripts/mutants/v2-surface.json +50 -64
- package/scripts/mutants/v2-visible-resume.json +266 -6
- package/scripts/peer-facts.ts +1 -1
- package/scripts/smoke-agy-install-state.sh +66 -0
- package/scripts/smoke-meta-prune.sh +23 -0
- package/scripts/smoke-omp-fresh-live.ts +12 -3
- package/scripts/smoke-omp-receive-live.ts +7 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +12 -2
- package/team.json.example +39 -0
|
@@ -360,9 +360,12 @@ export interface MetaIdentity {
|
|
|
360
360
|
* arm-capable event and writes in the SAME atomic upsert as that attach, then copies into
|
|
361
361
|
* the receiver marker it writes under the record-arm lock. A dead marker carrying the
|
|
362
362
|
* value the record still carries is evidence that no later arm-capable attach happened;
|
|
363
|
-
* one that does not carry it is not.
|
|
364
|
-
*
|
|
365
|
-
*
|
|
363
|
+
* one that does not carry it is not. The pi-durable birth (#157 B3 C4) mints it the same way
|
|
364
|
+
* under the same lock and copies it into its location evidence (`pi-durable-location.ts`)
|
|
365
|
+
* instead of a marker; there it binds a location, never a dormancy verdict. `null` means no
|
|
366
|
+
* binding was ever observed — every other backend, a pi-durable record no birth has bound,
|
|
367
|
+
* a record no arm has touched since this field existed, a create — and is never filled by
|
|
368
|
+
* inference. Every writer that does not mint PRESERVES it.
|
|
366
369
|
*/
|
|
367
370
|
attachIncarnation: string | null;
|
|
368
371
|
}
|
|
@@ -614,10 +617,10 @@ export interface MetaIdentityMintInput {
|
|
|
614
617
|
model?: string | null;
|
|
615
618
|
transcriptPath?: string | null;
|
|
616
619
|
/**
|
|
617
|
-
* The arm binding to commit with this write. ONLY the Claude Code hook
|
|
618
|
-
*
|
|
619
|
-
* writer, every UserPromptSubmit) keeps the existing value, and a
|
|
620
|
-
* `null`.
|
|
620
|
+
* The arm binding to commit with this write. ONLY the Claude Code hook (on an arm-capable
|
|
621
|
+
* event) and the pi-durable birth (#157 B3 C4) pass it, each under the record-arm lock;
|
|
622
|
+
* `undefined` (every other writer, every UserPromptSubmit) keeps the existing value, and a
|
|
623
|
+
* create without it is `null`.
|
|
621
624
|
*/
|
|
622
625
|
attachIncarnation?: string;
|
|
623
626
|
}
|
|
@@ -1471,7 +1474,9 @@ export function defaultMetaReceiversDir(): string {
|
|
|
1471
1474
|
* sandboxed receivers root sandboxes it too and no new environment carrier exists. It is
|
|
1472
1475
|
* deliberately NOT the v2 dispatch lock dir — visible resume holds that per-gid lock across
|
|
1473
1476
|
* its whole launch + observation window, and the relaunched session's own arm must not wait
|
|
1474
|
-
* on it.
|
|
1477
|
+
* on it. The Claude Code hook and the pi-durable birth (`pi-durable-location.ts`, #157 B3 C4)
|
|
1478
|
+
* take this lock, each around the upsert that mints `attachIncarnation`; nothing else reads or
|
|
1479
|
+
* writes here.
|
|
1475
1480
|
*/
|
|
1476
1481
|
export function defaultMetaRecordArmLocksDir(receiversDir: string = defaultMetaReceiversDir()): string {
|
|
1477
1482
|
return path.join(path.dirname(receiversDir), "meta-record-arm-locks");
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
*/
|
|
88
88
|
|
|
89
89
|
import { ACP_MODEL_NOT_CURATED_HINT } from "./acp/model-ids.js";
|
|
90
|
+
import { ENTWURF_FRESH_LAUNCH_REJECT_HINTS } from "./agent-guidance.ts";
|
|
90
91
|
import { callbackEnvAssignments } from "./callback-env.ts";
|
|
91
92
|
import { classifyTmuxCwd, type TmuxCwdRejectReason } from "./classify-tmux-cwd.ts";
|
|
92
93
|
import {
|
|
@@ -629,7 +630,9 @@ export function freshCall(
|
|
|
629
630
|
}
|
|
630
631
|
|
|
631
632
|
/** Why each refusal happened, in the caller's terms. A reason a caller cannot act on is a reason
|
|
632
|
-
* they will guess about.
|
|
633
|
+
* they will guess about. The static tmux launch-stage text is authored in root
|
|
634
|
+
* ENTWURF.json (#154 C1-D) and bound key by key, never spread, so a duplicate or missing reason
|
|
635
|
+
* still fails typecheck; slot-bearing, ACP and preflight-leaf hints stay code-owned. */
|
|
633
636
|
const REJECT_HINT: Record<FreshCallRejectReason, string> = {
|
|
634
637
|
// The Copilot capability reasons keep their repair text on the leaf that decides them, so
|
|
635
638
|
// the sentence an operator reads cannot drift away from the predicate that produced it.
|
|
@@ -641,34 +644,28 @@ const REJECT_HINT: Record<FreshCallRejectReason, string> = {
|
|
|
641
644
|
...CODEX_CALLER_PREFLIGHT_HINT,
|
|
642
645
|
...CODEX_CALLER_SEAT_HINT,
|
|
643
646
|
...FRESH_CALL_PROVIDER_HINT,
|
|
644
|
-
"no-tmux-context": "
|
|
645
|
-
"anchor-malformed": "
|
|
646
|
-
"anchor-unresolved": "
|
|
647
|
-
"anchor-mismatch": "
|
|
648
|
-
"caller-identity-unavailable":
|
|
649
|
-
|
|
650
|
-
"cwd-
|
|
651
|
-
|
|
652
|
-
"cwd-
|
|
653
|
-
|
|
654
|
-
"
|
|
655
|
-
|
|
656
|
-
"cwd-not-directory": "the requested cwd exists but is not a directory",
|
|
657
|
-
"tmux-session-name-invalid":
|
|
658
|
-
"the requested tmux session name is outside the shape this rail addresses (start with a letter or digit, then letters, digits, '_' or '-') — some other shapes tmux cannot resolve at all ('#' is expanded when the name is stored; '.' and ':' are its own pane/window separators inside a target; a name like '$0' loses to the session id '$0'), and the rest are declined to keep one narrow grammar, so rename the session or open one whose name fits",
|
|
659
|
-
"tmux-session-missing":
|
|
660
|
-
"no session with that exact name answers on this agent's tmux server (or that server stopped answering) — nothing was created, so open the session yourself and call again",
|
|
661
|
-
"model-empty": "model is empty after trimming; fresh calls require an explicit model",
|
|
647
|
+
"no-tmux-context": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["no-tmux-context"],
|
|
648
|
+
"anchor-malformed": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["anchor-malformed"],
|
|
649
|
+
"anchor-unresolved": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["anchor-unresolved"],
|
|
650
|
+
"anchor-mismatch": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["anchor-mismatch"],
|
|
651
|
+
"caller-identity-unavailable": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["caller-identity-unavailable"],
|
|
652
|
+
"cwd-not-absolute": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["cwd-not-absolute"],
|
|
653
|
+
"cwd-format-token": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["cwd-format-token"],
|
|
654
|
+
"cwd-missing": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["cwd-missing"],
|
|
655
|
+
"cwd-not-directory": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["cwd-not-directory"],
|
|
656
|
+
"tmux-session-name-invalid": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["tmux-session-name-invalid"],
|
|
657
|
+
"tmux-session-missing": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["tmux-session-missing"],
|
|
658
|
+
"model-empty": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["model-empty"],
|
|
662
659
|
"model-invalid": `model must be one ${MODEL_MAX_CHARS}-character argv-safe id/alias without whitespace or tmux syntax`,
|
|
663
660
|
"acp-model-not-curated": ACP_MODEL_NOT_CURATED_HINT,
|
|
664
|
-
"task-empty": "task
|
|
661
|
+
"task-empty": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["task-empty"],
|
|
665
662
|
"task-too-long": `task exceeds ${TASK_MAX_CHARS} characters`,
|
|
666
|
-
"runtime-unresolved": "
|
|
667
|
-
"runtime-not-absolute": "
|
|
668
|
-
"runtime-path-whitespace": "
|
|
669
|
-
"runtime-missing": "
|
|
670
|
-
"runtime-not-regular-file": "
|
|
671
|
-
"runtime-not-executable": "
|
|
663
|
+
"runtime-unresolved": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["runtime-unresolved"],
|
|
664
|
+
"runtime-not-absolute": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["runtime-not-absolute"],
|
|
665
|
+
"runtime-path-whitespace": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["runtime-path-whitespace"],
|
|
666
|
+
"runtime-missing": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["runtime-missing"],
|
|
667
|
+
"runtime-not-regular-file": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["runtime-not-regular-file"],
|
|
668
|
+
"runtime-not-executable": ENTWURF_FRESH_LAUNCH_REJECT_HINTS["runtime-not-executable"],
|
|
672
669
|
};
|
|
673
670
|
|
|
674
671
|
/**
|
|
@@ -23,7 +23,11 @@
|
|
|
23
23
|
* This is the tmux rail. A caller inside herdr (`HERDR_ENV=1`, herdr's own marker, read for its
|
|
24
24
|
* exact value as `fresh-call-dispatch` does) is refused by name before anything else: herdr owns
|
|
25
25
|
* placement there, and an ambient `TMUX` that happens to be set would otherwise put the window in
|
|
26
|
-
* a session the operator is not looking at.
|
|
26
|
+
* a session the operator is not looking at. Since #157 G1a the shared rail root (`fresh-call-dispatch`)
|
|
27
|
+
* hands a resume inside herdr to the separate herdr rail (`herdr-resume-call.ts`) and never reaches
|
|
28
|
+
* this leaf there;
|
|
29
|
+
* this guard stays so that a direct caller still cannot open a tmux window from inside herdr. No
|
|
30
|
+
* fallback in either direction.
|
|
27
31
|
*
|
|
28
32
|
* ── The cwd is the whole reason this module owns its own tmux argv ──
|
|
29
33
|
*
|
|
@@ -49,6 +53,7 @@
|
|
|
49
53
|
* to acceptance, not to the product's launch receipt.
|
|
50
54
|
*/
|
|
51
55
|
|
|
56
|
+
import { ENTWURF_RESUME_LAUNCH_REJECT_HINTS } from "./agent-guidance.ts";
|
|
52
57
|
import { classifyTmuxCwd, type TmuxCwdRejectReason } from "./classify-tmux-cwd.ts";
|
|
53
58
|
import {
|
|
54
59
|
assertLaunchTarget,
|
|
@@ -76,6 +81,49 @@ import {
|
|
|
76
81
|
export const RESUME_CALL_RUNTIME = { pi: "pi", "claude-code": "claude" } as const;
|
|
77
82
|
export type ResumeCallBackend = keyof typeof RESUME_CALL_RUNTIME;
|
|
78
83
|
|
|
84
|
+
/**
|
|
85
|
+
* #157 B3: a pi-durable resume reopens through the MANAGED verb, exactly as its fresh call does —
|
|
86
|
+
* `entwurf pi-durable` verifies this package's carrier and its SDK set before anything runs, and the
|
|
87
|
+
* durable app is never started bare. It is its own entry (`resumePiDurableCall`) rather than a row of
|
|
88
|
+
* the table above, because it is the one resume whose launch also carries an environment.
|
|
89
|
+
*/
|
|
90
|
+
export const RESUME_CALL_PI_DURABLE_RUNTIME = "entwurf";
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The pi-durable adopt launch projection (#157 B3, L3-A1 §1): EXACTLY these five names, each read by
|
|
94
|
+
* name and emitted in this order — the SDK's own profile input, and the standard children of ONE meta
|
|
95
|
+
* root. It is a fixed projection of two coordinates the caller derived, not an environment dictionary:
|
|
96
|
+
* no other name can be expressed through it. The launched child checks the four roots it actually
|
|
97
|
+
* received against its closed adopt context, and its raw profile against the reservation, before
|
|
98
|
+
* anything opens, so a value this module mangled would refuse there rather than open something else.
|
|
99
|
+
*/
|
|
100
|
+
export interface ResumeCallPiDurableAdoptEnv {
|
|
101
|
+
readonly PI_CODING_AGENT_DIR: string;
|
|
102
|
+
readonly ENTWURF_META_SESSIONS_DIR: string;
|
|
103
|
+
readonly ENTWURF_META_MAILBOX_DIR: string;
|
|
104
|
+
readonly ENTWURF_META_SENDERS_DIR: string;
|
|
105
|
+
readonly ENTWURF_META_RECEIVERS_DIR: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The five `NAME=value` assignments, in the fixed order, or none. Every value must be an absolute
|
|
109
|
+
* path: a relative one would resolve against whatever directory the child starts in. */
|
|
110
|
+
function adoptEnvAssignments(adoptEnv: ResumeCallPiDurableAdoptEnv | undefined): string[] {
|
|
111
|
+
if (adoptEnv === undefined) return [];
|
|
112
|
+
const pairs: readonly (readonly [string, string])[] = [
|
|
113
|
+
["PI_CODING_AGENT_DIR", adoptEnv.PI_CODING_AGENT_DIR],
|
|
114
|
+
["ENTWURF_META_SESSIONS_DIR", adoptEnv.ENTWURF_META_SESSIONS_DIR],
|
|
115
|
+
["ENTWURF_META_MAILBOX_DIR", adoptEnv.ENTWURF_META_MAILBOX_DIR],
|
|
116
|
+
["ENTWURF_META_SENDERS_DIR", adoptEnv.ENTWURF_META_SENDERS_DIR],
|
|
117
|
+
["ENTWURF_META_RECEIVERS_DIR", adoptEnv.ENTWURF_META_RECEIVERS_DIR],
|
|
118
|
+
];
|
|
119
|
+
for (const [name, value] of pairs) {
|
|
120
|
+
if (typeof value !== "string" || !value.startsWith("/")) {
|
|
121
|
+
throw new Error(`mux-resume-call: refusing to build argv with a non-absolute ${name}: ${JSON.stringify(value)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return pairs.map(([name, value]) => `${name}=${value}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
79
127
|
/** Why a resume window could not be opened. Every value is a NAMED refusal; this module has no
|
|
80
128
|
* fallback launch and no fallback directory. The cwd members come from the shared classification
|
|
81
129
|
* leaf and their string values are stable contract. */
|
|
@@ -96,7 +144,8 @@ export type ResumeCallResult = { ok: true; receipt: ResumeCallReceipt } | { ok:
|
|
|
96
144
|
|
|
97
145
|
/**
|
|
98
146
|
* Launch argv: the leaf's detached-append shape, the fixed identity scrub (`SCRUBBED_INHERITED_ENV`,
|
|
99
|
-
* the same two assignments every fresh window carries),
|
|
147
|
+
* the same two assignments every fresh window carries), for a pi-durable adoption the five fixed
|
|
148
|
+
* projection assignments after it (#157 B3), `-c`, the runtime, then the caller's flags.
|
|
100
149
|
* `--` is what keeps tmux from reading the runtime or its flags as tmux options. The scrub is not a
|
|
101
150
|
* carrier: it only empties identity a tmux server may have inherited, and a resumed Claude Code
|
|
102
151
|
* (which, unlike pi, never re-exports its own) would otherwise present a stale one to its bridge.
|
|
@@ -106,16 +155,19 @@ export function buildResumeCallArgs(
|
|
|
106
155
|
cwd: string,
|
|
107
156
|
runtimePath: string,
|
|
108
157
|
runtimeArgs: readonly string[],
|
|
158
|
+
adoptEnv?: ResumeCallPiDurableAdoptEnv,
|
|
109
159
|
): string[] {
|
|
110
160
|
assertSelector("session", placement.sessionId);
|
|
111
161
|
assertLaunchTarget(runtimePath);
|
|
112
162
|
const bad = classifyTmuxCwd(cwd);
|
|
113
163
|
if (bad) throw new Error(`mux-resume-call: refusing to build argv with an unusable cwd (${bad}): ${cwd}`);
|
|
164
|
+
const adoptAssignments = adoptEnvAssignments(adoptEnv);
|
|
114
165
|
return [
|
|
115
166
|
"new-window",
|
|
116
167
|
"-d",
|
|
117
168
|
"-a",
|
|
118
169
|
...SCRUBBED_INHERITED_ENV.flatMap((assignment) => ["-e", assignment]),
|
|
170
|
+
...adoptAssignments.flatMap((assignment) => ["-e", assignment]),
|
|
119
171
|
"-t",
|
|
120
172
|
`${placement.sessionId}:{end}`,
|
|
121
173
|
"-c",
|
|
@@ -139,6 +191,28 @@ export function buildResumeCallArgs(
|
|
|
139
191
|
export function resumeCall(
|
|
140
192
|
params: { backend: ResumeCallBackend; cwd: string; runtimeArgs: readonly string[] },
|
|
141
193
|
env: NodeJS.ProcessEnv = process.env,
|
|
194
|
+
): ResumeCallResult {
|
|
195
|
+
return openResumeWindow(RESUME_CALL_RUNTIME[params.backend], params, undefined, env);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* #157 B3: the pi-durable resume window. The same order, refusals and receipt as `resumeCall` — the
|
|
200
|
+
* runtime is the managed `entwurf`, and the window additionally carries the fixed adopt projection.
|
|
201
|
+
* `runtimeArgs` (the managed verb and its closed adopt context) is passed through untouched; this
|
|
202
|
+
* module never decodes it.
|
|
203
|
+
*/
|
|
204
|
+
export function resumePiDurableCall(
|
|
205
|
+
params: { cwd: string; runtimeArgs: readonly string[]; adoptEnv: ResumeCallPiDurableAdoptEnv },
|
|
206
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
207
|
+
): ResumeCallResult {
|
|
208
|
+
return openResumeWindow(RESUME_CALL_PI_DURABLE_RUNTIME, params, params.adoptEnv, env);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function openResumeWindow(
|
|
212
|
+
runtime: string,
|
|
213
|
+
params: { cwd: string; runtimeArgs: readonly string[] },
|
|
214
|
+
adoptEnv: ResumeCallPiDurableAdoptEnv | undefined,
|
|
215
|
+
env: NodeJS.ProcessEnv,
|
|
142
216
|
): ResumeCallResult {
|
|
143
217
|
if (env.HERDR_ENV === "1") return { ok: false, reason: "herdr-resume-unsupported" };
|
|
144
218
|
const badCwd = classifyTmuxCwd(params.cwd);
|
|
@@ -146,7 +220,7 @@ export function resumeCall(
|
|
|
146
220
|
|
|
147
221
|
let runtimePath: string;
|
|
148
222
|
try {
|
|
149
|
-
runtimePath = resolveRuntimeOnPath(
|
|
223
|
+
runtimePath = resolveRuntimeOnPath(runtime, env);
|
|
150
224
|
} catch (err) {
|
|
151
225
|
if (err instanceof LaunchPreconditionError) return { ok: false, reason: err.reason };
|
|
152
226
|
throw err;
|
|
@@ -157,7 +231,7 @@ export function resumeCall(
|
|
|
157
231
|
const placement = inspected.placement;
|
|
158
232
|
requireSameContext("resumeCall", placement, env);
|
|
159
233
|
|
|
160
|
-
const run = runTmux(buildResumeCallArgs(placement, params.cwd, runtimePath, params.runtimeArgs), env);
|
|
234
|
+
const run = runTmux(buildResumeCallArgs(placement, params.cwd, runtimePath, params.runtimeArgs, adoptEnv), env);
|
|
161
235
|
assertTmuxOk("new-window", run);
|
|
162
236
|
|
|
163
237
|
let fields: ReturnType<typeof parseWindowFields>;
|
|
@@ -187,24 +261,22 @@ export function resumeCall(
|
|
|
187
261
|
}
|
|
188
262
|
|
|
189
263
|
/** Why each refusal happened, in the caller's terms. A reason a caller cannot act on is a reason
|
|
190
|
-
* they will guess about.
|
|
264
|
+
* they will guess about. The text is authored in root ENTWURF.json (#154 C1-D); each
|
|
265
|
+
* key is bound explicitly so the reason union stays exhaustive here. */
|
|
191
266
|
export const RESUME_CALL_REJECT_HINT: Record<ResumeCallRejectReason, string> = {
|
|
192
|
-
"no-tmux-context": "
|
|
193
|
-
"anchor-malformed": "
|
|
194
|
-
"anchor-unresolved": "
|
|
195
|
-
"anchor-mismatch": "
|
|
196
|
-
"cwd-not-absolute": "
|
|
197
|
-
"cwd-format-token":
|
|
198
|
-
|
|
199
|
-
"cwd-
|
|
200
|
-
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
|
|
204
|
-
"runtime-
|
|
205
|
-
"runtime-not-
|
|
206
|
-
"runtime-
|
|
207
|
-
"runtime-missing": "nothing exists at the resolved runtime path",
|
|
208
|
-
"runtime-not-regular-file": "the resolved runtime path is not a regular file",
|
|
209
|
-
"runtime-not-executable": "the resolved runtime path is not executable",
|
|
267
|
+
"no-tmux-context": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["no-tmux-context"],
|
|
268
|
+
"anchor-malformed": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["anchor-malformed"],
|
|
269
|
+
"anchor-unresolved": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["anchor-unresolved"],
|
|
270
|
+
"anchor-mismatch": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["anchor-mismatch"],
|
|
271
|
+
"cwd-not-absolute": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["cwd-not-absolute"],
|
|
272
|
+
"cwd-format-token": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["cwd-format-token"],
|
|
273
|
+
"cwd-missing": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["cwd-missing"],
|
|
274
|
+
"cwd-not-directory": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["cwd-not-directory"],
|
|
275
|
+
"herdr-resume-unsupported": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["herdr-resume-unsupported"],
|
|
276
|
+
"runtime-unresolved": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["runtime-unresolved"],
|
|
277
|
+
"runtime-not-absolute": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["runtime-not-absolute"],
|
|
278
|
+
"runtime-path-whitespace": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["runtime-path-whitespace"],
|
|
279
|
+
"runtime-missing": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["runtime-missing"],
|
|
280
|
+
"runtime-not-regular-file": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["runtime-not-regular-file"],
|
|
281
|
+
"runtime-not-executable": ENTWURF_RESUME_LAUNCH_REJECT_HINTS["runtime-not-executable"],
|
|
210
282
|
};
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `{v:2,task}` payload, whose decoder caps `task` at 16000 characters. A framed
|
|
20
20
|
* task over that cap would open a window whose bootstrap refuses its own argv.
|
|
21
21
|
*
|
|
22
|
-
* ── Then the carrier, then the
|
|
22
|
+
* ── Then the carrier, then the five units ──
|
|
23
23
|
*
|
|
24
24
|
* carrier the package's own pi-durable carrier and its declared SDK set, verified
|
|
25
25
|
* (`pi-durable-runtime.ts`); passed in as that check's verdict.
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
* it every dispatch to the citizen is the honest `mailbox-undeliverable`.
|
|
30
30
|
* visible identity the packaged bootstrap — the one that hands the TUI the view whose conversation
|
|
31
31
|
* labels carry the garden id.
|
|
32
|
+
* owner fence the compiled owner fence in the same closure — the packaged bootstrap imports it,
|
|
33
|
+
* so without it the bootstrap cannot load. Asked here as a file this package must
|
|
34
|
+
* ship, nothing more; the carrier verdict above does not cover it.
|
|
32
35
|
*
|
|
33
36
|
* ── What this deliberately does NOT claim ──
|
|
34
37
|
*
|
|
@@ -52,7 +55,8 @@ export type PiDurablePreflightRejectReason =
|
|
|
52
55
|
| "pi-durable-birth-unit-missing"
|
|
53
56
|
| "pi-durable-mcp-hand-missing"
|
|
54
57
|
| "pi-durable-receive-unit-missing"
|
|
55
|
-
| "pi-durable-visible-identity-missing"
|
|
58
|
+
| "pi-durable-visible-identity-missing"
|
|
59
|
+
| "pi-durable-owner-fence-missing";
|
|
56
60
|
|
|
57
61
|
/** `<provider>/<model id>` at the FIRST `/`, or `null` when either side is empty. */
|
|
58
62
|
export function splitPiDurableModel(model: string): { provider: string; model: string } | null {
|
|
@@ -89,6 +93,7 @@ export function piDurableFreshPreflight(params: {
|
|
|
89
93
|
if (!existsSync(layout.bridgeEntry)) return "pi-durable-mcp-hand-missing";
|
|
90
94
|
if (!ownerJoinAdmitsDurableHost(layout.deliverability)) return "pi-durable-receive-unit-missing";
|
|
91
95
|
if (!existsSync(layout.bootstrap)) return "pi-durable-visible-identity-missing";
|
|
96
|
+
if (!existsSync(layout.ownerFence)) return "pi-durable-owner-fence-missing";
|
|
92
97
|
return null;
|
|
93
98
|
}
|
|
94
99
|
|
|
@@ -110,4 +115,6 @@ export const PI_DURABLE_PREFLIGHT_HINT: Record<
|
|
|
110
115
|
"this Entwurf package's compiled owner join does not admit pi-durable-host, so nothing could ever be delivered to the sibling — reinstall or rebuild Entwurf",
|
|
111
116
|
"pi-durable-visible-identity-missing":
|
|
112
117
|
"this Entwurf package has no packaged pi-durable bootstrap (pi/pi-durable/bootstrap.mjs), so no TUI would carry the garden id — reinstall Entwurf",
|
|
118
|
+
"pi-durable-owner-fence-missing":
|
|
119
|
+
"this Entwurf package has no compiled pi-durable owner fence (mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js), which its packaged bootstrap imports — reinstall or rebuild Entwurf",
|
|
113
120
|
};
|