@junghanacs/entwurf 0.40.0 → 0.41.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 +4 -2
- package/BASELINE.md +55 -2
- package/CHANGELOG.md +562 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +18 -2
- package/FAQ.md +8 -4
- package/README.md +60 -45
- package/VERIFY.md +14 -11
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +6 -2
- package/docs/external-mcp-host.md +8 -6
- package/docs/fresh-cut-policy.md +9 -1
- package/docs/setup-clean-host.md +76 -20
- 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 +15 -14
- package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
- package/pi/pi-durable/carrier/tui.js +18 -3
- package/pi/pi-durable/overlay/upstream-pin.json +12 -12
- package/pi-extensions/entwurf-control.ts +55 -20
- package/pi-extensions/lib/acp/backend-adapter.ts +5 -3
- package/pi-extensions/lib/acp/backend.ts +12 -9
- 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 +57 -3
- package/run.sh +123 -34
- 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/codex-fresh-live-protocol.ts +3 -2
- 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/control-send-receipt.json +1 -1
- 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/pack-install.json +4 -4
- package/scripts/mutants/pi-durable.json +139 -0
- package/scripts/mutants/pi-floor.json +5 -5
- 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-codex-fresh-live.ts +2 -1
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-raw-install-live.sh +15 -7
- 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.1",
|
|
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",
|
|
@@ -89,27 +89,28 @@
|
|
|
89
89
|
"CHANGELOG.md",
|
|
90
90
|
"!**/__pycache__",
|
|
91
91
|
"!**/*.pyc",
|
|
92
|
-
"!pi-extensions/**/*.test.ts"
|
|
92
|
+
"!pi-extensions/**/*.test.ts",
|
|
93
|
+
"!pi-extensions/**/fixtures/**"
|
|
93
94
|
],
|
|
94
95
|
"engines": {
|
|
95
96
|
"node": ">=24.0.0"
|
|
96
97
|
},
|
|
97
98
|
"entwurf": {
|
|
98
|
-
"claudeCodeFloor": ">=2.1.
|
|
99
|
+
"claudeCodeFloor": ">=2.1.306"
|
|
99
100
|
},
|
|
100
101
|
"dependencies": {
|
|
101
102
|
"@agentclientprotocol/claude-agent-acp": "0.90.0",
|
|
102
103
|
"@agentclientprotocol/sdk": "1.8.0",
|
|
103
104
|
"@anthropic-ai/sdk": "0.100.1",
|
|
104
|
-
"@earendil-works/chord": "1.
|
|
105
|
-
"@earendil-works/pi-agent-core": "1.
|
|
106
|
-
"@earendil-works/pi-ai": "1.
|
|
107
|
-
"@earendil-works/pi-codemode": "1.
|
|
108
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
109
|
-
"@earendil-works/pi-durable": "1.
|
|
110
|
-
"@earendil-works/pi-mcp": "1.
|
|
111
|
-
"@earendil-works/pi-telemetry": "1.
|
|
112
|
-
"@earendil-works/pi-tui": "1.
|
|
105
|
+
"@earendil-works/chord": "1.4.0",
|
|
106
|
+
"@earendil-works/pi-agent-core": "1.4.0",
|
|
107
|
+
"@earendil-works/pi-ai": "1.4.0",
|
|
108
|
+
"@earendil-works/pi-codemode": "1.4.0",
|
|
109
|
+
"@earendil-works/pi-coding-agent": "1.4.0",
|
|
110
|
+
"@earendil-works/pi-durable": "1.4.0",
|
|
111
|
+
"@earendil-works/pi-mcp": "1.4.0",
|
|
112
|
+
"@earendil-works/pi-telemetry": "1.4.0",
|
|
113
|
+
"@earendil-works/pi-tui": "1.4.0",
|
|
113
114
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
114
115
|
"proper-lockfile": "4.1.2",
|
|
115
116
|
"smol-toml": "^1.8.0",
|
|
@@ -196,7 +197,7 @@
|
|
|
196
197
|
"check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
|
|
197
198
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
198
199
|
"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",
|
|
200
|
+
"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
201
|
"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
202
|
"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
203
|
},
|
|
@@ -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
|
|
@@ -219,6 +219,7 @@ export class TaskBadge {
|
|
|
219
219
|
...tally("aborting", (node) => node.abortRequested),
|
|
220
220
|
...tally("completing", (node) => !node.abortRequested && node.state.status === "completing"),
|
|
221
221
|
...tally("waiting", (node) => !node.abortRequested && node.state.status === "waiting"),
|
|
222
|
+
...tally("sleeping", (node) => !node.abortRequested && node.state.status === "sleeping"),
|
|
222
223
|
...tally("pending", (node) => !node.abortRequested && node.state.status === "pending"),
|
|
223
224
|
].join(", ");
|
|
224
225
|
}
|
|
@@ -621,19 +622,33 @@ class DurableTui {
|
|
|
621
622
|
}
|
|
622
623
|
}
|
|
623
624
|
|
|
624
|
-
|
|
625
|
+
/** A task's line in the tasks panel: a sleeping task says when it wakes, a blocked one why it cannot run. */
|
|
626
|
+
export function describeTask(node ) {
|
|
625
627
|
const state = node.state;
|
|
626
628
|
const status =
|
|
627
629
|
state.status === "waiting"
|
|
628
630
|
? `waiting on ${state.on.join(", ")}`
|
|
629
631
|
: state.status === "completing"
|
|
630
632
|
? `completing (${state.outcome})`
|
|
631
|
-
:
|
|
632
|
-
|
|
633
|
+
: state.status === "sleeping"
|
|
634
|
+
? `sleeping ${state.phase} until ${localTime(state.until)}`
|
|
635
|
+
: `${state.status} ${state.phase}`;
|
|
636
|
+
const flags = [
|
|
637
|
+
node.background ? "background" : "",
|
|
638
|
+
node.abortRequested ? "aborting" : "",
|
|
639
|
+
node.blocked !== undefined ? `blocked:${node.blocked}` : "",
|
|
640
|
+
].filter(Boolean);
|
|
633
641
|
const owned = node.conversations.length > 0 ? ` owns conversation ${node.conversations.join(", ")}` : "";
|
|
634
642
|
return `${node.kind} #${node.id}: ${status}${flags.length > 0 ? ` [${flags.join(", ")}]` : ""}${owned}`;
|
|
635
643
|
}
|
|
636
644
|
|
|
645
|
+
/** Epoch milliseconds as local wall-clock time, `YYYY-MM-DD HH:MM:SS`. */
|
|
646
|
+
function localTime(epoch ) {
|
|
647
|
+
const at = new Date(epoch);
|
|
648
|
+
const pad = (n ) => String(n).padStart(2, "0");
|
|
649
|
+
return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}`;
|
|
650
|
+
}
|
|
651
|
+
|
|
637
652
|
function userText(content ) {
|
|
638
653
|
if (typeof content === "string") return content;
|
|
639
654
|
return content
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
{
|
|
77
77
|
"name": "tui.js",
|
|
78
78
|
"source": "pi/pi-durable/src/experimental/durable/tui.ts",
|
|
79
|
-
"blob": "
|
|
80
|
-
"sha256": "
|
|
79
|
+
"blob": "405600f6ba1558bf6bf22d144afdcf3f1295c282",
|
|
80
|
+
"sha256": "d4bf4e2861bde917e1dcdea6f063bdc3e101ab500107a9c5fab0e3ce97fb2c4a"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"name": "harness-setup.js",
|
|
@@ -132,17 +132,17 @@
|
|
|
132
132
|
]
|
|
133
133
|
},
|
|
134
134
|
"sdk": {
|
|
135
|
-
"comment": "The published library set the carrier runs against, derived from the 1.
|
|
135
|
+
"comment": "The published library set the carrier runs against, derived from the 1.4.0 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare. direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.4.0 pins). Each is an exact production dependency in package.json. Since 1.3.0 the members' own edges are exact too (upstream #10865), and the installed physical edges are still what must equal these versions.",
|
|
136
136
|
"members": {
|
|
137
|
-
"@earendil-works/chord": "1.
|
|
138
|
-
"@earendil-works/pi-agent-core": "1.
|
|
139
|
-
"@earendil-works/pi-ai": "1.
|
|
140
|
-
"@earendil-works/pi-codemode": "1.
|
|
141
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
142
|
-
"@earendil-works/pi-durable": "1.
|
|
143
|
-
"@earendil-works/pi-mcp": "1.
|
|
144
|
-
"@earendil-works/pi-telemetry": "1.
|
|
145
|
-
"@earendil-works/pi-tui": "1.
|
|
137
|
+
"@earendil-works/chord": "1.4.0",
|
|
138
|
+
"@earendil-works/pi-agent-core": "1.4.0",
|
|
139
|
+
"@earendil-works/pi-ai": "1.4.0",
|
|
140
|
+
"@earendil-works/pi-codemode": "1.4.0",
|
|
141
|
+
"@earendil-works/pi-coding-agent": "1.4.0",
|
|
142
|
+
"@earendil-works/pi-durable": "1.4.0",
|
|
143
|
+
"@earendil-works/pi-mcp": "1.4.0",
|
|
144
|
+
"@earendil-works/pi-telemetry": "1.4.0",
|
|
145
|
+
"@earendil-works/pi-tui": "1.4.0"
|
|
146
146
|
},
|
|
147
147
|
"direct": {
|
|
148
148
|
"proper-lockfile": "4.1.2"
|
|
@@ -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
|
|
|
@@ -239,9 +239,11 @@ export interface AcpBackendAdapter {
|
|
|
239
239
|
|
|
240
240
|
/** OPTIONAL capability (#127): recognise a typed vendor rejection of `session/prompt`
|
|
241
241
|
* whose raw text must not become pi's `errorMessage`. pi decides a whole-prompt replay
|
|
242
|
-
* from `errorMessage` alone, and a vendor's "Internal error"
|
|
243
|
-
*
|
|
244
|
-
* from a cold session.
|
|
242
|
+
* from `errorMessage` alone, and a vendor's "Internal error" reads as transient
|
|
243
|
+
* (pi-ai's `internal.?error` term), so a turn whose tools already ran would be resent
|
|
244
|
+
* from a cold session. Through pi-ai 1.3 a digit run inside the ids it carries (`…8503…`)
|
|
245
|
+
* read as an HTTP status too; pi-ai 1.4 bounds the status terms (upstream #11057), so
|
|
246
|
+
* the ids no longer trigger it — the "Internal error" text alone still does. A recognised rejection returns a fixed, authored verdict for
|
|
245
247
|
* `errorMessage`; backend.ts keeps the raw rejection as a pi diagnostic instead.
|
|
246
248
|
*
|
|
247
249
|
* ABSENT means every failure of this backend keeps the verbatim-first `errorMessage`.
|
|
@@ -668,13 +668,15 @@ async function settleChildEnd(latch: ChildEndLatch, ms: number): Promise<void> {
|
|
|
668
668
|
* may appear here, or a dead child would put a full cold prompt replay back on
|
|
669
669
|
* the table — with the tool side effects this turn already produced.
|
|
670
670
|
*
|
|
671
|
-
* That pattern is a
|
|
672
|
-
*
|
|
673
|
-
* "502", "503", "504"
|
|
674
|
-
* CHILD_END_SETTLE_MS made this line say "within 500ms", which pi read as an
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
671
|
+
* That pattern is a substring alternation (`new RegExp(terms.join("|"), "i")` — NO word
|
|
672
|
+
* boundaries on its word terms). Through pi-ai 1.3 its HTTP status terms ("429", "500",
|
|
673
|
+
* "502", "503", "504", "520", "524") were bare substrings too, and rendering
|
|
674
|
+
* CHILD_END_SETTLE_MS made this line say "within 500ms", which pi read as an HTTP 500 and
|
|
675
|
+
* classified as transient. pi-ai 1.4 bounds only those status terms (upstream #11057), so
|
|
676
|
+
* "500ms" no longer matches; the word terms above are still unbounded. The settle bound
|
|
677
|
+
* stays deliberately NOT interpolated: naming the window in prose keeps a later change of
|
|
678
|
+
* that constant, or of the classifier, from silently re-arming the replay — do not
|
|
679
|
+
* "improve" this by putting the number back; the bound belongs in the code and the gate.
|
|
678
680
|
*
|
|
679
681
|
* The three endings are kept DISTINCT on purpose (issue #72 Done-when): an
|
|
680
682
|
* exit code, a signal, and "we waited and it never said" are three different
|
|
@@ -1468,8 +1470,9 @@ export function streamAcpTurn(
|
|
|
1468
1470
|
// the turn-scoped full-transcript-replay cause.
|
|
1469
1471
|
const hint = aborted ? undefined : actionableAcpBackendHint(full);
|
|
1470
1472
|
// #127: pi's retry and overflow classifiers read `errorMessage` ALONE, and a
|
|
1471
|
-
// vendor's "Internal error"
|
|
1472
|
-
//
|
|
1473
|
+
// vendor's "Internal error" reads as transient — a replay of a turn whose tools
|
|
1474
|
+
// already ran. (Through pi-ai 1.3 a digit run in its ids/stderr did too; 1.4 bounds
|
|
1475
|
+
// the status terms, upstream #11057.) A rejection the adapter names gets
|
|
1473
1476
|
// its fixed verdict there; every raw part rides one diagnostic, appended after any
|
|
1474
1477
|
// existing ones. An abort keeps the abort path.
|
|
1475
1478
|
const rejection = aborted ? undefined : adapter?.readPromptRejection?.(err);
|