@junghanacs/entwurf 0.40.0 → 0.41.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 +55 -2
- package/CHANGELOG.md +321 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +18 -2
- package/FAQ.md +8 -4
- package/README.md +26 -8
- package/VERIFY.md +13 -10
- package/docs/acp-backend-rail.md +5 -1
- package/docs/external-mcp-host.md +8 -6
- package/docs/fresh-cut-policy.md +9 -1
- package/docs/setup-clean-host.md +24 -8
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +81 -19
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-request.js +473 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-retire.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire-production.js +149 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire.js +158 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +283 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +7 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +96 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +5 -29
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +28 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +26 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-retire-request.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +67 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/retirement-event.js +211 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +124 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +93 -19
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/mcp/entwurf-bridge/tsconfig.build.json +10 -1
- package/package.json +4 -4
- package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
- package/pi-extensions/entwurf-control.ts +55 -20
- package/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/pi-extensions/lib/cc-retire-request.ts +553 -0
- package/pi-extensions/lib/control-retire.ts +195 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +17 -1
- package/pi-extensions/lib/entwurf-v2-retire-production.ts +227 -0
- package/pi-extensions/lib/entwurf-v2-retire.ts +269 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +2 -1
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +390 -36
- package/pi-extensions/lib/fresh-call-composition.ts +15 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +45 -4
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/meta-session.ts +140 -16
- package/pi-extensions/lib/mux-fresh-call.ts +7 -27
- package/pi-extensions/lib/mux-launch.ts +29 -0
- package/pi-extensions/lib/mux-resume-call.ts +33 -12
- package/pi-extensions/lib/pi-retire-request.ts +111 -0
- package/pi-extensions/lib/resume-launch-identity.ts +104 -18
- package/pi-extensions/lib/retirement-event.ts +253 -0
- package/pi-extensions/meta-bridge-hook.ts +142 -13
- package/pi-extensions/meta-bridge-pi-durable.ts +56 -2
- package/run.sh +78 -7
- package/scripts/agy-bridge-config.py +4 -1
- package/scripts/build-bridge.sh +14 -0
- package/scripts/check-capability-bundle-reach.ts +135 -2
- package/scripts/check-entwurf-bridge-boot.ts +2 -1
- package/scripts/check-entwurf-fact-provider.ts +1 -0
- package/scripts/check-entwurf-v2-production.ts +2 -0
- package/scripts/check-entwurf-v2-surface.ts +7 -0
- package/scripts/check-gate-qualification.ts +14 -8
- package/scripts/check-herdr-activation.ts +2 -2
- package/scripts/check-herdr-placement.ts +1 -0
- package/scripts/check-hook-launch-topology.ts +6 -15
- package/scripts/check-install-container.sh +2 -2
- package/scripts/check-meta-doctor-oracle.sh +27 -1
- package/scripts/check-meta-facts.ts +8 -3
- package/scripts/check-meta-hook-session-switch.ts +230 -13
- package/scripts/check-meta-identity-consumers.ts +1 -0
- package/scripts/check-meta-listing.ts +1 -0
- package/scripts/check-meta-manifest-schema.py +1 -1
- package/scripts/check-meta-session.ts +31 -0
- package/scripts/check-meta-v3-record.ts +55 -1
- package/scripts/check-mux-resume-call.ts +35 -9
- package/scripts/check-peer-facts.ts +1 -0
- package/scripts/check-pi-durable-contact.ts +3 -1
- package/scripts/check-pi-durable-send.ts +4 -2
- package/scripts/check-pi-mcp-bridge.ts +1 -0
- package/scripts/check-resume-launch-identity.ts +62 -23
- package/scripts/lib/meta-hook-bundle.ts +72 -0
- package/scripts/meta-bridge-claude-floor.sh +13 -9
- package/scripts/meta-bridge-doctor.sh +17 -0
- package/scripts/meta-bridge-install.sh +6 -0
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/bridge-boot-resume.json +5 -2
- package/scripts/mutants/cc-retire-pidfd.json +129 -0
- package/scripts/mutants/cc-retire-request.json +171 -0
- package/scripts/mutants/control-retire.json +105 -0
- package/scripts/mutants/copilot-receive.json +2 -6
- package/scripts/mutants/entwurf-v2-retire.json +476 -0
- package/scripts/mutants/fresh-call-dispatch.json +37 -0
- package/scripts/mutants/meta-hook-session-switch.json +88 -0
- package/scripts/mutants/meta-identity.json +57 -0
- package/scripts/mutants/mux-fresh-call.json +1 -1
- package/scripts/mutants/mux-resume-call.json +40 -4
- package/scripts/mutants/pi-durable.json +79 -0
- package/scripts/mutants/pi-retire-request.json +72 -0
- package/scripts/mutants/resume-launch-identity.json +35 -8
- package/scripts/mutants/retirement-event.json +195 -0
- package/scripts/mutants/v2-surface.json +16 -0
- package/scripts/mutants/v2-visible-resume.json +283 -13
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-raw-install-live.sh +5 -4
- package/scripts/smoke-meta-install-state.sh +4 -4
- package/scripts/smoke-mux-lifecycle-live.ts +10 -10
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +5 -3
- package/scripts/smoke-pi-provider-state.sh +3 -3
|
@@ -31,16 +31,18 @@
|
|
|
31
31
|
* seat, #105; nothing is ever created), optionally at ONE literal requested
|
|
32
32
|
* cwd (cross-repo fresh, #73); returns a LAUNCH receipt only, and the new
|
|
33
33
|
* address arrives later as the sender envelope of the sibling's nonce callback.
|
|
34
|
-
* - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id
|
|
35
|
-
* window; target-only,
|
|
36
|
-
* apart.
|
|
34
|
+
* - entwurf_resume_call — reopen ONE DORMANT pi or Claude Code citizen under its OWN garden id
|
|
35
|
+
* in a visible window; target-only, submits no prompt, LAUNCH and OBSERVATION
|
|
36
|
+
* receipts stay apart, the caller is attributed but grants nothing.
|
|
37
|
+
* - entwurf_retire_call — one operator-requested target, one retirement attempt; actor is not
|
|
38
|
+
* permission, and action / append-record / lock-release receipts stay separate.
|
|
37
39
|
* - entwurf_callback — ZERO-ARGUMENT fresh-sibling callback. Reads ENTWURF_CALLBACK_TARGET +
|
|
38
40
|
* ENTWURF_CALLBACK_NONCE from this process env (launcher-injected), validates
|
|
39
41
|
* grammar, and dispatches through the existing v2 runner. Refuses by name
|
|
40
42
|
* when env is absent/malformed or this process is a Codex-provenance bridge.
|
|
41
43
|
* No model-supplied target fallback.
|
|
42
44
|
*
|
|
43
|
-
* That list is the WHOLE public surface —
|
|
45
|
+
* That list is the WHOLE public surface — nine verbs — and `check-entwurf-bridge-boot`
|
|
44
46
|
* (G1f) holds it as an exact set on the runtime tools/list, so a verb added or dropped
|
|
45
47
|
* here without a decision is red rather than merely undocumented.
|
|
46
48
|
*
|
|
@@ -78,6 +80,8 @@ import { ENTWURF_PEERS_RENDER_LIMIT, renderEntwurfPeers } from "../../../pi-exte
|
|
|
78
80
|
import { computeSelfAddressability, type MetaDeliveryDomain } from "../../../pi-extensions/lib/entwurf-self-address.ts";
|
|
79
81
|
import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.ts";
|
|
80
82
|
import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.ts";
|
|
83
|
+
import { retireCitizen } from "../../../pi-extensions/lib/entwurf-v2-retire.ts";
|
|
84
|
+
import { makeProductionRetireDeps } from "../../../pi-extensions/lib/entwurf-v2-retire-production.ts";
|
|
81
85
|
import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.ts";
|
|
82
86
|
import {
|
|
83
87
|
makeVisibleResumeDeps,
|
|
@@ -818,7 +822,7 @@ server.tool(
|
|
|
818
822
|
// biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
|
|
819
823
|
.regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
|
|
820
824
|
.describe(
|
|
821
|
-
"Required runtime model: canonical provider/model for pi, provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name.",
|
|
825
|
+
"Required runtime model: canonical provider/model for pi (a bare alias is refused), provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name. Neither pi nor pi-durable may name the openrouter provider.",
|
|
822
826
|
),
|
|
823
827
|
task: z
|
|
824
828
|
.string()
|
|
@@ -904,7 +908,7 @@ server.tool(
|
|
|
904
908
|
// resume` cannot open a window on its own and `mux-resume-call` cannot read a record — that is
|
|
905
909
|
// the import fence in docs/mux-launch-rail.md §11, expressed as a function argument.
|
|
906
910
|
const visibleResumeLaunch: VisibleResumeDeps["launch"] = (input) => {
|
|
907
|
-
const launched = resumeCall({ cwd: input.cwd, runtimeArgs: input.runtimeArgs });
|
|
911
|
+
const launched = resumeCall({ backend: input.backend, cwd: input.cwd, runtimeArgs: input.runtimeArgs });
|
|
908
912
|
return launched.ok
|
|
909
913
|
? { ok: true, handle: launched.receipt }
|
|
910
914
|
: { ok: false, reason: launched.reason, hint: RESUME_CALL_REJECT_HINT[launched.reason] };
|
|
@@ -912,27 +916,45 @@ const visibleResumeLaunch: VisibleResumeDeps["launch"] = (input) => {
|
|
|
912
916
|
|
|
913
917
|
server.tool(
|
|
914
918
|
"entwurf_resume_call",
|
|
915
|
-
"Reopen ONE DORMANT pi citizen under its OWN garden id, in a visible window in the operator's
|
|
916
|
-
"session. The record supplies everything — which
|
|
917
|
-
"the only input is the target id: there is no model override, no task, and no
|
|
918
|
-
"
|
|
919
|
-
"
|
|
920
|
-
"
|
|
921
|
-
"
|
|
922
|
-
"
|
|
923
|
-
"
|
|
924
|
-
"
|
|
919
|
+
"Reopen ONE DORMANT pi or Claude Code citizen under its OWN garden id, in a visible window in the operator's " +
|
|
920
|
+
"own tmux session. The record supplies everything — which session, which cwd, and for pi which transcript, " +
|
|
921
|
+
"model and provider — so the only input is the target id: there is no model override, no task, and no " +
|
|
922
|
+
"prompt, and a Claude Code session comes back as `claude --resume <its session id>` with none of the model " +
|
|
923
|
+
"or permission flags it was once started with. This verb submits NO prompt; what the runtime itself does " +
|
|
924
|
+
"after it comes back (for Claude Code, its hook re-arms the doorbell and unread mail may then wake a turn) " +
|
|
925
|
+
"is not measured, and talking to it is still entwurf_v2 fire-and-forget. You get TWO receipts and they mean " +
|
|
926
|
+
"different things: a LAUNCH receipt (tmux made a window and was asked to start the runtime) and an " +
|
|
927
|
+
"OBSERVATION receipt (pi: the control socket answered under the same id; Claude Code: a NEW live process in " +
|
|
928
|
+
"the window this call opened re-armed its receiver marker for the same session with a fresh arm binding the " +
|
|
929
|
+
"record also carries; or resume-unobserved; or record-unreadable with its cause). Unobserved is a real " +
|
|
930
|
+
"outcome, not an error to retry — the window is visible, so read it. A citizen that is already LIVE is " +
|
|
931
|
+
"refused: address it with entwurf_v2 instead. A Claude Code citizen is reopened only when the last validated " +
|
|
932
|
+
"owner the Entwurf hook armed for its session is proven gone AND that arm is still the record's last one; " +
|
|
933
|
+
"no marker, an unreadable or mismatched one, an inconclusive owner or an unbound one is refused, and a " +
|
|
934
|
+
"process holding the session without a committed hook attach is invisible to that proof. Other backends " +
|
|
935
|
+
"have no same-id resume. Requires that this agent itself runs inside tmux (inside herdr it is refused).",
|
|
925
936
|
{
|
|
926
937
|
target: z
|
|
927
938
|
.string()
|
|
928
939
|
.min(1)
|
|
929
940
|
.regex(/^\d{8}T\d{6}-[0-9a-f]{6}$/)
|
|
930
|
-
.describe("Garden id of the DORMANT pi citizen to reopen (discover with entwurf_peers)."),
|
|
941
|
+
.describe("Garden id of the DORMANT pi or Claude Code citizen to reopen (discover with entwurf_peers)."),
|
|
931
942
|
},
|
|
932
|
-
async ({ target }) => {
|
|
943
|
+
async ({ target }, extra) => {
|
|
944
|
+
// Who asked, from the same authoritative caller identity fresh_call reads. Attribution only:
|
|
945
|
+
// an anonymous caller is reported as unattributed, never refused, and a record-backed one is
|
|
946
|
+
// granted nothing the target's record and live facts do not already decide.
|
|
947
|
+
let actor: string | null = null;
|
|
948
|
+
try {
|
|
949
|
+
actor = (await buildAuthoritativeSelfEnvelope({ requestMeta: extra._meta })).envelope.sessionId;
|
|
950
|
+
} catch (err) {
|
|
951
|
+
if (!(err instanceof EntwurfEnvelopeWiringError)) {
|
|
952
|
+
return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
933
955
|
try {
|
|
934
956
|
const result = await visibleResume(target, makeVisibleResumeDeps(visibleResumeLaunch));
|
|
935
|
-
const rendered = renderVisibleResume(result);
|
|
957
|
+
const rendered = renderVisibleResume(result, { actor });
|
|
936
958
|
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
937
959
|
} catch (err) {
|
|
938
960
|
return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -940,6 +962,58 @@ server.tool(
|
|
|
940
962
|
},
|
|
941
963
|
);
|
|
942
964
|
|
|
965
|
+
server.tool(
|
|
966
|
+
"entwurf_retire_call",
|
|
967
|
+
"Only when the human operator explicitly requested this particular target/work: make ONE retirement attempt for that request. " +
|
|
968
|
+
"Development work, a role or a record-backed actor is NOT a grant. Use host approval where provided/configured; " +
|
|
969
|
+
"Pi and pi-durable have no built-in per-call approval prompt by default. An explicit operator request and " +
|
|
970
|
+
"agent operating policy are required; attribution and MCP annotations are hints, not grants or proof of approval. " +
|
|
971
|
+
"Never use for autonomous cleanup or timers. Never automatically retry an unknown, cancelled or lost result. " +
|
|
972
|
+
"Target must be an existing Pi or Claude Code garden id; self retirement refuses. Pi shutdown-requested is " +
|
|
973
|
+
"only an ACK, not process exit, flush or model silence. Claude Code exited means that fd-pinned process only, " +
|
|
974
|
+
"not conversation end, children or flush. Do not manually /resume or /clear the target during the request. " +
|
|
975
|
+
"The JSON receipt preserves outcome, record and release separately; returning a receipt does not mean the " +
|
|
976
|
+
"target exited. Cancellation can suppress the wire response without undoing an action. For plural explicit " +
|
|
977
|
+
"requests, invoke once for each named target; never infer a roster or expand the request.",
|
|
978
|
+
{
|
|
979
|
+
target: z
|
|
980
|
+
.string()
|
|
981
|
+
.min(1)
|
|
982
|
+
.regex(/^\d{8}T\d{6}-[0-9a-f]{6}$/)
|
|
983
|
+
.describe("Exact garden id of the Pi or Claude Code target the human operator explicitly requested to retire."),
|
|
984
|
+
},
|
|
985
|
+
{ readOnlyHint: false, destructiveHint: true, idempotentHint: false },
|
|
986
|
+
async ({ target }, extra) => {
|
|
987
|
+
try {
|
|
988
|
+
const receipt = await retireCitizen(
|
|
989
|
+
target,
|
|
990
|
+
makeProductionRetireDeps({
|
|
991
|
+
resolveCaller: async () => (await resolveAuthoritativeSender({ requestMeta: extra._meta }))?.envelope ?? null,
|
|
992
|
+
signal: extra.signal,
|
|
993
|
+
}),
|
|
994
|
+
);
|
|
995
|
+
// isError reports uncertainty/faults, NOT a common Pi-ACK/CC-exit success boundary.
|
|
996
|
+
// Named refusals are completed decisions. Always preserve the full three-axis JSON.
|
|
997
|
+
const outcome = receipt.outcome;
|
|
998
|
+
const uncertain =
|
|
999
|
+
outcome.kind === "unknown" ||
|
|
1000
|
+
outcome.kind === "preflight-error" ||
|
|
1001
|
+
(outcome.kind === "pi" && outcome.result.boundary === "unknown") ||
|
|
1002
|
+
(outcome.kind === "claude-code" && outcome.result.outcome === "unknown");
|
|
1003
|
+
const recordFault =
|
|
1004
|
+
receipt.record.status === "requested-append-error" || receipt.record.status === "outcome-append-error";
|
|
1005
|
+
const releaseFault = receipt.release.status !== "released" && receipt.release.status !== "not-acquired";
|
|
1006
|
+
const json = JSON.stringify(receipt);
|
|
1007
|
+
return uncertain || recordFault || releaseFault ? textErr(json) : textOk(json);
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
return textErr(
|
|
1010
|
+
`entwurf_retire_call error: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
1011
|
+
"The outcome may be unknown; this does not prove no action. Do not automatically retry.",
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
);
|
|
1016
|
+
|
|
943
1017
|
server.tool(
|
|
944
1018
|
"entwurf_callback",
|
|
945
1019
|
"ZERO-ARGUMENT callback for a fresh sibling this process was launched as. Reads " +
|
|
@@ -55,7 +55,7 @@ names=set(raw)
|
|
|
55
55
|
# was an artifact-surface hole: a bundle missing entwurf_fresh_call / entwurf_resume_call
|
|
56
56
|
# answered tools/list and passed. Equality also refuses an undecided extra verb; the
|
|
57
57
|
# duplicate arm catches a name registered twice, which set membership cannot see.
|
|
58
|
-
expected={'entwurf_v2','entwurf_self','entwurf_peers','entwurf_inbox_read','entwurf_register_native','entwurf_fresh_call','entwurf_resume_call','entwurf_callback'}
|
|
58
|
+
expected={'entwurf_v2','entwurf_self','entwurf_peers','entwurf_inbox_read','entwurf_register_native','entwurf_fresh_call','entwurf_resume_call','entwurf_retire_call','entwurf_callback'}
|
|
59
59
|
legacy={'entwurf','entwurf_resume','entwurf_send'} & names
|
|
60
60
|
if len(raw)!=len(names):
|
|
61
61
|
raise SystemExit(f"duplicate tool registrations: {sorted(raw)}")
|
|
@@ -66,7 +66,7 @@ if legacy:
|
|
|
66
66
|
if names!=expected:
|
|
67
67
|
raise SystemExit(f"tool set MISMATCH — missing {sorted(expected-names)}, unexpected {sorted(names-expected)}")
|
|
68
68
|
PY
|
|
69
|
-
ok "public tool surface is EXACTLY the
|
|
69
|
+
ok "public tool surface is EXACTLY the nine garden verbs (no v1 verb, no undecided extra, no duplicate)"
|
|
70
70
|
|
|
71
71
|
SELF_JSON="$(printf '%s\n' "$OUT" | grep '"id":3' | tail -1)"
|
|
72
72
|
[[ "$SELF_JSON" == *"isError"* ]] || fail "entwurf_self without identity should be an error: $SELF_JSON"
|
|
@@ -147,6 +147,12 @@
|
|
|
147
147
|
// remote-pair-send, entwurf-v2-surface, meta-session) are ALREADY emitted for the bridge's own
|
|
148
148
|
// entwurf_v2 handler, so this adds ONE leaf JS and ZERO new deps. It is never in index.ts's boot
|
|
149
149
|
// closure.
|
|
150
|
+
// WHY cc-retire-request rides THIS build (#147 G3): the core-side Claude Code retire adapter
|
|
151
|
+
// must run from the compiled bridge as well as from source. It was first supplied as a private
|
|
152
|
+
// entry; the #147 public retirement factory now reaches it too. Keep the named entry for the
|
|
153
|
+
// standalone compiled adapter consumer proof. Its helper is not TS: the
|
|
154
|
+
// build script byte-copies pi-extensions/lib/cc-retire-pidfd.py beside the emitted adapter.
|
|
155
|
+
// Its repo imports (meta-session, entwurf-v2-visible-resume) are ALREADY emitted for the bridge.
|
|
150
156
|
"include": [
|
|
151
157
|
"./src/index.ts",
|
|
152
158
|
"../../protocol.js",
|
|
@@ -168,6 +174,9 @@
|
|
|
168
174
|
"../../pi-extensions/meta-bridge-hook-codex.ts",
|
|
169
175
|
"../../pi-extensions/meta-bridge-omp.ts",
|
|
170
176
|
"../../pi-extensions/meta-bridge-receive-omp.ts",
|
|
171
|
-
"../../pi-extensions/meta-bridge-pi-durable.ts"
|
|
177
|
+
"../../pi-extensions/meta-bridge-pi-durable.ts",
|
|
178
|
+
"../../pi-extensions/lib/cc-retire-request.ts",
|
|
179
|
+
// Keep the standalone Pi adapter consumer entry; the public factory also reaches it now.
|
|
180
|
+
"../../pi-extensions/lib/pi-retire-request.ts"
|
|
172
181
|
]
|
|
173
182
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Visible sibling sessions across independent agent harnesses: launch in Herdr or tmux, reopen pi in tmux, and deliver messages by garden id; native bridges and Claude/Cortex ACP adapters.",
|
|
3
|
+
"version": "0.41.0",
|
|
4
|
+
"description": "Visible sibling sessions across independent agent harnesses: launch in Herdr or tmux, retire on request and reopen pi or Claude Code in tmux, and deliver messages by garden id; native bridges and Claude/Cortex ACP adapters.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Junghan Kim",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"node": ">=24.0.0"
|
|
96
96
|
},
|
|
97
97
|
"entwurf": {
|
|
98
|
-
"claudeCodeFloor": ">=2.1.
|
|
98
|
+
"claudeCodeFloor": ">=2.1.306"
|
|
99
99
|
},
|
|
100
100
|
"dependencies": {
|
|
101
101
|
"@agentclientprotocol/claude-agent-acp": "0.90.0",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
"check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
|
|
197
197
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
198
198
|
"check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-pi-queue-oracle && ./run.sh check-tests-beside-behavior && ./run.sh check-remote-pair-installed-audit",
|
|
199
|
-
"check:contracts": "./run.sh check-demo-reproducibility && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-win32-preview && ./run.sh check-pack-win32-carrier && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-set-model-readback && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-pi-durable-beside && ./run.sh check-pi-durable-carrier && ./run.sh check-pi-durable-native-module",
|
|
199
|
+
"check:contracts": "./run.sh check-demo-reproducibility && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-control-retire && ./run.sh check-pi-retire-request && ./run.sh check-cc-retire-pidfd && ./run.sh check-cc-retire-request && ./run.sh check-retirement-event && ./run.sh check-entwurf-v2-retire && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-win32-preview && ./run.sh check-pack-win32-carrier && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-fresh-call-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-set-model-readback && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-pi-durable-beside && ./run.sh check-pi-durable-carrier && ./run.sh check-pi-durable-native-module",
|
|
200
200
|
"check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-meta-hook-session-switch && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-pi-launch && ./run.sh check-codex-app-server-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-birth-hook && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-peer-facts && ./run.sh check-herdr-plugin && ./run.sh check-herdr-plugin-profile && ./run.sh check-herdr-plugin-build && ./run.sh check-herdr-runtime-bootstrap && ./run.sh check-herdr-activation && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-usage-accounting && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-typing-call-fence && ./run.sh check-gate-manifests && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex && ./run.sh check-codex-bridge-identity && ./run.sh check-pi-bridge-sender && ./run.sh check-pi-mcp-register && ./run.sh check-pi-mcp-bridge && ./run.sh check-codex-birth-hook && ./run.sh smoke-codex-config-state && ./run.sh smoke-codex-birth && ./run.sh check-herdr-sandbox",
|
|
201
201
|
"check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-pack-consumer-roots && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
|
|
202
202
|
},
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# WHY THIS EXISTS (measured, #51 B/B2, 2026-07-22 — not inferred)
|
|
5
5
|
#
|
|
6
|
-
# Claude Code
|
|
6
|
+
# Claude Code 2.1.217 and later support the shell-less EXEC form: `command` is the
|
|
7
7
|
# executable and `args` is argv, each element substituted as a plain string, with
|
|
8
8
|
# no shell anywhere on the path. Measured at 2.1.217: a hook declared that way
|
|
9
9
|
# receives every `args` element verbatim (a literal `${HOME}` arrives LITERAL —
|
|
@@ -47,8 +47,8 @@ if [ "$#" -eq 0 ]; then
|
|
|
47
47
|
still reports the hook as `exit_code: 0, outcome: success` (measured on 2.1.138),
|
|
48
48
|
so nothing upstream will ever tell you.
|
|
49
49
|
|
|
50
|
-
entwurf supports Claude Code >= 2.1.
|
|
51
|
-
|
|
50
|
+
entwurf supports Claude Code >= 2.1.306 (the exec form was verified end to end at
|
|
51
|
+
2.1.217; same-id resume needs the newer CLI). There is no shell-form fallback.
|
|
52
52
|
|
|
53
53
|
Fix: update Claude Code, then re-run
|
|
54
54
|
./run.sh install-meta-bridge && ./run.sh doctor-meta-bridge
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
* - { type: "get_info" }
|
|
60
60
|
* - { type: "clear", summarize?: boolean }
|
|
61
61
|
* - { type: "abort" }
|
|
62
|
+
* - { type: "retire", gardenId: "...", nativeSessionId: "..." } (internal, idle TUI only)
|
|
62
63
|
* Responses are JSON objects with { type: "response", command, success, data?, error? }
|
|
63
64
|
* (No event channel — the turn_end subscribe surface was removed with the
|
|
64
65
|
* Send-is-throw cleanup; see note above.)
|
|
@@ -79,6 +80,16 @@ import {
|
|
|
79
80
|
noteCompactionBefore,
|
|
80
81
|
noteCompactionTerminal,
|
|
81
82
|
} from "./lib/compaction-send-guard.js";
|
|
83
|
+
import {
|
|
84
|
+
acceptControlConnection,
|
|
85
|
+
type ControlBinding,
|
|
86
|
+
closeControlConnections,
|
|
87
|
+
endRetireResponse,
|
|
88
|
+
invalidateControlBinding,
|
|
89
|
+
requestControlRetire,
|
|
90
|
+
retireResponseEnvelope,
|
|
91
|
+
startOrRebindControlServer,
|
|
92
|
+
} from "./lib/control-retire.js";
|
|
82
93
|
import { sendReceiptBoundary } from "./lib/control-send-receipt.js";
|
|
83
94
|
import { CONTROL_SOCKET_SUFFIX, defaultControlSocketDir } from "./lib/control-socket-path.js";
|
|
84
95
|
import {
|
|
@@ -121,6 +132,10 @@ interface SocketState {
|
|
|
121
132
|
server: net.Server | null;
|
|
122
133
|
socketPath: string | null;
|
|
123
134
|
context: ExtensionContext | null;
|
|
135
|
+
binding: ControlBinding | null;
|
|
136
|
+
closing: boolean;
|
|
137
|
+
retireRequested: boolean;
|
|
138
|
+
connections: Set<net.Socket>;
|
|
124
139
|
compaction: CompactionGuard;
|
|
125
140
|
}
|
|
126
141
|
|
|
@@ -485,12 +500,27 @@ async function handleCommand(
|
|
|
485
500
|
state: SocketState,
|
|
486
501
|
command: RpcCommand,
|
|
487
502
|
socket: net.Socket,
|
|
503
|
+
serverBinding: ControlBinding | null,
|
|
488
504
|
): Promise<void> {
|
|
489
505
|
const id = "id" in command && typeof command.id === "string" ? command.id : undefined;
|
|
490
506
|
const respond = (success: boolean, commandName: string, data?: unknown, error?: string) => {
|
|
491
507
|
writeResponse(socket, { type: "response", command: commandName, success, data, error, id });
|
|
492
508
|
};
|
|
493
509
|
|
|
510
|
+
// The public core checks caller/self, freshness and target admission, not an operator grant.
|
|
511
|
+
// Attribution is not authorization: an explicit operator request and agent operating policy
|
|
512
|
+
// are required; use host approval where provided/configured. No async gap exists between
|
|
513
|
+
// the receiver's final same-session checks and the SDK void call.
|
|
514
|
+
if (command.type === "retire") {
|
|
515
|
+
const result = requestControlRetire(state, serverBinding, residentGardenId, command);
|
|
516
|
+
endRetireResponse(socket, retireResponseEnvelope(result, id));
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (state.closing || state.retireRequested) {
|
|
521
|
+
respond(false, command.type, undefined, "target-closing");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
494
524
|
const ctx = state.context;
|
|
495
525
|
if (!ctx) {
|
|
496
526
|
respond(false, command.type, undefined, "Session not ready");
|
|
@@ -690,6 +720,7 @@ async function handleCommand(
|
|
|
690
720
|
|
|
691
721
|
async function createServer(pi: ExtensionAPI, state: SocketState, socketPath: string): Promise<net.Server> {
|
|
692
722
|
const server = net.createServer((socket) => {
|
|
723
|
+
const serverBinding = acceptControlConnection(state, socket);
|
|
693
724
|
// FIRST — before setEncoding, before the data handler, and before any response can be
|
|
694
725
|
// written back. The policy itself (and why this process died without it) lives with the
|
|
695
726
|
// rest of the wire protocol in lib/entwurf-control-rpc.ts.
|
|
@@ -729,7 +760,7 @@ async function createServer(pi: ExtensionAPI, state: SocketState, socketPath: st
|
|
|
729
760
|
// here would serialize subsequent commands on the same socket and
|
|
730
761
|
// tangle teardown ordering during socket close.
|
|
731
762
|
const commandName = parsed.command?.type ?? "unknown";
|
|
732
|
-
void handleCommand(pi, state, parsed.command!, socket).catch((error) => {
|
|
763
|
+
void handleCommand(pi, state, parsed.command!, socket, serverBinding).catch((error) => {
|
|
733
764
|
const message = error instanceof Error ? error.message : String(error);
|
|
734
765
|
writeResponse(socket, {
|
|
735
766
|
type: "response",
|
|
@@ -762,24 +793,21 @@ async function startControlServer(
|
|
|
762
793
|
state: SocketState,
|
|
763
794
|
ctx: ExtensionContext,
|
|
764
795
|
socketPath: string,
|
|
796
|
+
gardenId: string,
|
|
765
797
|
): Promise<void> {
|
|
766
|
-
await
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
await removeSocket(socketPath);
|
|
776
|
-
|
|
777
|
-
state.context = ctx;
|
|
778
|
-
state.socketPath = socketPath;
|
|
779
|
-
state.server = await createServer(pi, state, socketPath);
|
|
798
|
+
await startOrRebindControlServer(state, ctx, socketPath, gardenId, {
|
|
799
|
+
prepare: async () => {
|
|
800
|
+
await ensureControlDir();
|
|
801
|
+
await gcStaleSockets();
|
|
802
|
+
},
|
|
803
|
+
stop: () => stopControlServer(state),
|
|
804
|
+
remove: removeSocket,
|
|
805
|
+
create: (address) => createServer(pi, state, address),
|
|
806
|
+
});
|
|
780
807
|
}
|
|
781
808
|
|
|
782
809
|
async function stopControlServer(state: SocketState): Promise<void> {
|
|
810
|
+
invalidateControlBinding(state);
|
|
783
811
|
if (!state.server) {
|
|
784
812
|
await removeSocket(state.socketPath);
|
|
785
813
|
state.socketPath = null;
|
|
@@ -788,7 +816,7 @@ async function stopControlServer(state: SocketState): Promise<void> {
|
|
|
788
816
|
|
|
789
817
|
const socketPath = state.socketPath;
|
|
790
818
|
state.socketPath = null;
|
|
791
|
-
await
|
|
819
|
+
await closeControlConnections(state.server, state.connections);
|
|
792
820
|
state.server = null;
|
|
793
821
|
await removeSocket(socketPath);
|
|
794
822
|
}
|
|
@@ -958,6 +986,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
958
986
|
server: null,
|
|
959
987
|
socketPath: null,
|
|
960
988
|
context: null,
|
|
989
|
+
binding: null,
|
|
990
|
+
// Before session_start, commands refuse target-closing rather than Session not ready.
|
|
991
|
+
closing: true,
|
|
992
|
+
retireRequested: false,
|
|
993
|
+
connections: new Set(),
|
|
961
994
|
compaction: createCompactionGuard(),
|
|
962
995
|
};
|
|
963
996
|
|
|
@@ -1022,7 +1055,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1022
1055
|
// naming the fresh-cut command, control server refused. There is no mixed
|
|
1023
1056
|
// store to warn about; the store is either clean or this session has no
|
|
1024
1057
|
// address.
|
|
1025
|
-
await startControlServer(pi, state, ctx, birth.socketPath);
|
|
1058
|
+
await startControlServer(pi, state, ctx, birth.socketPath, birth.gardenId);
|
|
1026
1059
|
// Identity before UI (#125): the carrier env and the MCP registration that carries the
|
|
1027
1060
|
// born gardenId are both made before any status rendering, so a visual failure can never
|
|
1028
1061
|
// leave a born citizen without its identity carrier or its verbs.
|
|
@@ -1082,10 +1115,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1082
1115
|
// simply rebinds to the new address. There is no id to police at the pre-event.
|
|
1083
1116
|
|
|
1084
1117
|
pi.on("session_shutdown", async () => {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
updateSessionEnv(state.context, false, null);
|
|
1118
|
+
const previousContext = state.context;
|
|
1119
|
+
invalidateControlBinding(state);
|
|
1088
1120
|
residentGardenId = null;
|
|
1121
|
+
noteCompactionTerminal(state.compaction);
|
|
1122
|
+
updateStatus(previousContext, false, null);
|
|
1123
|
+
updateSessionEnv(previousContext, false, null);
|
|
1089
1124
|
await stopControlServer(state);
|
|
1090
1125
|
});
|
|
1091
1126
|
|