@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
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, retire on request
|
|
3
|
+
"version": "0.50.0",
|
|
4
|
+
"description": "Visible sibling sessions across independent agent harnesses: launch in Herdr or tmux, retire on request, visibly reopen supported sessions under the same id, 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",
|
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
],
|
|
40
40
|
"type": "module",
|
|
41
41
|
"files": [
|
|
42
|
+
"ENTWURF.json",
|
|
43
|
+
"ENTWURF-CLI.json",
|
|
44
|
+
"team.json.example",
|
|
42
45
|
"protocol.js",
|
|
43
46
|
"pi-extensions/",
|
|
44
47
|
"native/win32-x64/",
|
|
@@ -158,6 +161,7 @@
|
|
|
158
161
|
"check-copilot-statusline": "./run.sh check-copilot-statusline",
|
|
159
162
|
"check-copilot-launch": "./run.sh check-copilot-launch",
|
|
160
163
|
"check-pi-launch": "./run.sh check-pi-launch",
|
|
164
|
+
"check-claude-launch": "./run.sh check-claude-launch",
|
|
161
165
|
"smoke-copilot-statusline-state": "./run.sh smoke-copilot-statusline-state",
|
|
162
166
|
"smoke-copilot-mcp-state": "./run.sh smoke-copilot-mcp-state",
|
|
163
167
|
"smoke-omp-bridge-state": "./run.sh smoke-omp-bridge-state",
|
|
@@ -197,8 +201,8 @@
|
|
|
197
201
|
"check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
|
|
198
202
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
199
203
|
"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 && ./run.sh check-chain-terminus-publication",
|
|
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-native-push-paused && ./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 && ./run.sh check-remote-pair-beside",
|
|
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",
|
|
204
|
+
"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-native-push-paused && ./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-agent-guidance-beside && ./run.sh check-pi-durable-beside && ./run.sh check-pi-durable-carrier && ./run.sh check-pi-durable-native-module && ./run.sh check-remote-pair-beside",
|
|
205
|
+
"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-claude-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",
|
|
202
206
|
"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-argv-matcher && ./run.sh check-pack-consumer-roots && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
|
|
203
207
|
},
|
|
204
208
|
"pi": {
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
// fails and closes itself retires that doorbell at once;
|
|
15
15
|
// - a launcher's first input, when given, is admitted to the root ONCE, after that birth;
|
|
16
16
|
// - the TUI is handed the native view with the garden id on every conversation label
|
|
17
|
-
// (`withGardenIdentity`)
|
|
17
|
+
// (`withGardenIdentity`);
|
|
18
|
+
// - every open takes the owner fence (#157 B3) — built once here from the compiled closure — inside
|
|
19
|
+
// the session directory before its native lease; a refusal opens nothing.
|
|
18
20
|
// Nothing of the native registry, controller or TUI is rebuilt here.
|
|
19
21
|
//
|
|
20
22
|
// The durable app itself is this package's CARRIER (./carrier/, emitted from Entwurf's owned source
|
|
@@ -27,6 +29,8 @@
|
|
|
27
29
|
// <entwurf>/pi/pi-durable/bootstrap.mjs \
|
|
28
30
|
// --provider <provider> --model <model id> --width task-wide [--entwurf-bootstrap <json>]
|
|
29
31
|
// ... bootstrap.mjs --continue
|
|
32
|
+
// ... bootstrap.mjs --resume
|
|
33
|
+
// ... bootstrap.mjs --session <native id>
|
|
30
34
|
//
|
|
31
35
|
// Explicit means the native API, not a vendor argv: upstream's durable main takes only
|
|
32
36
|
// `--continue`, so `--provider`/`--model` reach pi's own `findInitialAgentModel` through the
|
|
@@ -45,9 +49,17 @@
|
|
|
45
49
|
// address rides the process env only.
|
|
46
50
|
// `--continue` opens the newest session for this directory as upstream does (not a chosen
|
|
47
51
|
// record), so every flag that only a new session can honour is refused beside it by name.
|
|
52
|
+
// `--session <native id>` (#157 G3 B-L1) reopens exactly that native session of this directory as it
|
|
53
|
+
// was saved, through the owned runtime's `session` option, with no interactive session picker or
|
|
54
|
+
// picker-specific TTY check; the normal TUI still runs. The id is the native session directory name, never a garden id: a native CLI selector, not an address. It is
|
|
55
|
+
// held to the owned `isSessionName` grammar before operator-native module evaluation, TUI load, open
|
|
56
|
+
// or birth; that scope is lexical (the selection stays under this cwd's session root by name), not
|
|
57
|
+
// physical symlink confinement. A well-formed id this directory does not hold is the runtime's own
|
|
58
|
+
// refusal, after the TUI module loads and before any TUI runs. `--continue`, `--resume` and
|
|
59
|
+
// `--session` are mutually exclusive, and every new-session flag is refused beside each of them.
|
|
48
60
|
//
|
|
49
|
-
// `--native-module <absolute file>` (#130 P2) names ONE operator module, for a new
|
|
50
|
-
// session. It is an ordinary ES module: its own top-level evaluation (top-level await included) is
|
|
61
|
+
// `--native-module <absolute file>` (#130 P2) names ONE operator module, for a new, a continued, a
|
|
62
|
+
// resumed or a named session. It is an ordinary ES module: its own top-level evaluation (top-level await included) is
|
|
51
63
|
// its initialization, and its default export is a native durable Extension, handed to `openDurable`
|
|
52
64
|
// by reference after the contact. Initialization runs before the TUI or the runtime is imported —
|
|
53
65
|
// the runtime creates the provider runtime before it installs any extension (carrier/runtime.js
|
|
@@ -68,16 +80,28 @@
|
|
|
68
80
|
// REPLACE the first extension or keep both tools. A refusal at module k leaves modules before it
|
|
69
81
|
// evaluated: nothing is rolled back. No watch, no recursion, nothing persisted.
|
|
70
82
|
//
|
|
83
|
+
// `--entwurf-adopt <json>` (#157 B3 I-3) opens the one native session a parent reserved for this process. The
|
|
84
|
+
// payload is closed: exactly `{v:1, gardenId, metaRoot, sourceGeneration}`, and any other key or value is refused. It
|
|
85
|
+
// is exclusive with `--continue`, `--resume`, `--session` and every new-session flag. The garden record under
|
|
86
|
+
// `metaRoot` is the address. Its native session id is the session, held to the same grammar as `--session`.
|
|
87
|
+
// Before anything loads, the four `ENTWURF_META_*_DIR` values this process actually received must be `metaRoot`'s
|
|
88
|
+
// standard children (the launch's fixed projection). The contact's roots are those four, whatever a seam passes.
|
|
89
|
+
// The open takes the adoption fence of that context, which re-reads the record and its location inside its own
|
|
90
|
+
// holds. The selection read here is never handed to the fence as evidence.
|
|
91
|
+
//
|
|
71
92
|
// Without that resolver the carrier's first pi-coding-agent internal import fails by name (an
|
|
72
93
|
// unsupported entwurf-pi-dist: URL scheme), and that failure is the honest one.
|
|
73
94
|
import { readdirSync, realpathSync, statSync } from "node:fs";
|
|
74
95
|
import { isAbsolute, join } from "node:path";
|
|
75
96
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
97
|
+
import { readAddressableMetaIdentity } from "../../mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js";
|
|
98
|
+
import { createPiDurableOwnerFence } from "../../mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js";
|
|
76
99
|
import { decodeOmpBootstrapPayload } from "../../mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js";
|
|
77
100
|
import {
|
|
78
101
|
closeAll,
|
|
79
102
|
createPiDurableContact,
|
|
80
103
|
failAfterCleanup,
|
|
104
|
+
PI_DURABLE_BACKEND,
|
|
81
105
|
PI_DURABLE_EXTENSION_NAME,
|
|
82
106
|
PI_DURABLE_TOOL_NAMES,
|
|
83
107
|
piDurableGardenIds,
|
|
@@ -87,6 +111,14 @@ import {
|
|
|
87
111
|
const RUNTIME = "./carrier/runtime.js";
|
|
88
112
|
const TUI = "./carrier/tui.js";
|
|
89
113
|
const SESSIONS = "./carrier/sessions.js";
|
|
114
|
+
/** The session-name grammar alone: no imports and no I/O, so reading it loads nothing an operator module may prepare. */
|
|
115
|
+
const SESSION_NAME = "./carrier/session-name.js";
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The owner fence every ordinary open of this process hands the runtime (#157 B3); an adoption hands its own (below).
|
|
119
|
+
* Building it reads and writes nothing.
|
|
120
|
+
*/
|
|
121
|
+
const OWNER_FENCE = createPiDurableOwnerFence();
|
|
90
122
|
|
|
91
123
|
/** The entwurf-bridge of THIS package closure: the one the contact spawns as its MCP hand. */
|
|
92
124
|
export const PACKAGED_BRIDGE_ENTRY = fileURLToPath(
|
|
@@ -102,9 +134,20 @@ const VALUE_FLAGS = [
|
|
|
102
134
|
"--entwurf-bootstrap",
|
|
103
135
|
"--native-module",
|
|
104
136
|
"--native-module-dir",
|
|
137
|
+
"--session",
|
|
138
|
+
"--entwurf-adopt",
|
|
105
139
|
];
|
|
106
140
|
/** Flags only a NEW session can honour. */
|
|
107
141
|
const FRESH_ONLY_FLAGS = ["--provider", "--model", "--width", "--entwurf-bootstrap"];
|
|
142
|
+
/** The closed adopt context's keys, sorted: nothing else rides it (#157 B3 I-3). */
|
|
143
|
+
const ADOPT_KEYS = ["gardenId", "metaRoot", "sourceGeneration", "v"];
|
|
144
|
+
/** Each contact root of an adoption: the env name its launch carries it in, and its standard child of `metaRoot`. */
|
|
145
|
+
const ADOPT_ROOTS = {
|
|
146
|
+
sessionsDir: ["ENTWURF_META_SESSIONS_DIR", "meta-sessions"],
|
|
147
|
+
mailboxDir: ["ENTWURF_META_MAILBOX_DIR", "meta-mailbox"],
|
|
148
|
+
sendersDir: ["ENTWURF_META_SENDERS_DIR", "meta-senders"],
|
|
149
|
+
receiversDir: ["ENTWURF_META_RECEIVERS_DIR", "meta-receivers"],
|
|
150
|
+
};
|
|
108
151
|
|
|
109
152
|
/**
|
|
110
153
|
* Extension names the durable app installs itself — the contact, then the carrier's own
|
|
@@ -124,6 +167,68 @@ function refuse(reason, detail) {
|
|
|
124
167
|
|
|
125
168
|
const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0;
|
|
126
169
|
|
|
170
|
+
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* `--entwurf-adopt`'s closed context, decoded before anything loads. The key set and `v` are this grammar's own.
|
|
174
|
+
* The garden id, the generation and the absolute root are the adoption fence's checks, run here by building the
|
|
175
|
+
* fence, which reads and writes nothing.
|
|
176
|
+
*/
|
|
177
|
+
function decodeAdoptContext(text) {
|
|
178
|
+
let value;
|
|
179
|
+
try {
|
|
180
|
+
value = JSON.parse(text);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw Object.assign(refuse("adopt-context-invalid", "--entwurf-adopt is not JSON"), { cause: error });
|
|
183
|
+
}
|
|
184
|
+
if (
|
|
185
|
+
typeof value !== "object" ||
|
|
186
|
+
value === null ||
|
|
187
|
+
Array.isArray(value) ||
|
|
188
|
+
Object.keys(value).sort().join(",") !== ADOPT_KEYS.join(",")
|
|
189
|
+
) {
|
|
190
|
+
throw refuse("adopt-context-invalid", "--entwurf-adopt is exactly {v, gardenId, metaRoot, sourceGeneration}");
|
|
191
|
+
}
|
|
192
|
+
if (value.v !== 1) throw refuse("adopt-context-invalid", "--entwurf-adopt is version 1");
|
|
193
|
+
const { gardenId, metaRoot, sourceGeneration } = value;
|
|
194
|
+
let fence;
|
|
195
|
+
try {
|
|
196
|
+
fence = createPiDurableOwnerFence({ adoption: { gardenId, metaRoot, sourceGeneration } });
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw Object.assign(refuse("adopt-context-invalid", messageOf(error)), { cause: error });
|
|
199
|
+
}
|
|
200
|
+
return { gardenId, metaRoot, sourceGeneration, fence };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** An adoption's contact roots: the standard children of its `metaRoot`, the shape the parent's reserve required. */
|
|
204
|
+
function adoptRoots(metaRoot) {
|
|
205
|
+
return Object.fromEntries(Object.entries(ADOPT_ROOTS).map(([key, [, child]]) => [key, join(metaRoot, child)]));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The four meta roots this process actually received must be the context's; no ambient root stands in for them. */
|
|
209
|
+
function requireAdoptRootEnv(metaRoot) {
|
|
210
|
+
const mismatched = Object.values(ADOPT_ROOTS)
|
|
211
|
+
.filter(([name, child]) => process.env[name] !== join(metaRoot, child))
|
|
212
|
+
.map(([name]) => name);
|
|
213
|
+
if (mismatched.length > 0) {
|
|
214
|
+
throw refuse("adopt-root-mismatch", `${mismatched.join(", ")} must be the standard children of ${metaRoot}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The native session the garden record names. The record is the address, read through the addressable reader. */
|
|
219
|
+
function adoptedSession({ gardenId, metaRoot }) {
|
|
220
|
+
let record;
|
|
221
|
+
try {
|
|
222
|
+
record = readAddressableMetaIdentity(gardenId, join(metaRoot, ADOPT_ROOTS.sessionsDir[1]));
|
|
223
|
+
} catch (error) {
|
|
224
|
+
throw Object.assign(refuse("adopt-record-unaddressable", `${gardenId}: ${messageOf(error)}`), { cause: error });
|
|
225
|
+
}
|
|
226
|
+
if (record.backend !== PI_DURABLE_BACKEND) {
|
|
227
|
+
throw refuse("adopt-record-not-pi-durable", `${gardenId} is a ${record.backend} record`);
|
|
228
|
+
}
|
|
229
|
+
return record.nativeSessionId;
|
|
230
|
+
}
|
|
231
|
+
|
|
127
232
|
/** The identity carriers present now: names and values, compared in memory and never printed. */
|
|
128
233
|
function identitySnapshot() {
|
|
129
234
|
return Object.fromEntries(
|
|
@@ -291,6 +396,8 @@ async function loadNativeModules(files) {
|
|
|
291
396
|
* checkout-only gates, which drive a private bundle) passes another one — never the launch argv.
|
|
292
397
|
* `extensions` are the operator's native extensions (`--native-module` or `--native-module-dir`),
|
|
293
398
|
* installed after the contact, by reference.
|
|
399
|
+
* `adopt` is a decoded `--entwurf-adopt` context (#157 B3 I-3). Its fence admits the open, in place of the ordinary
|
|
400
|
+
* one, and its `metaRoot`'s four standard roots are the contact's, spread last so no seam replaces them.
|
|
294
401
|
*
|
|
295
402
|
* @param {{
|
|
296
403
|
* bridgeEntry?: string,
|
|
@@ -301,7 +408,8 @@ async function loadNativeModules(files) {
|
|
|
301
408
|
* firstInput?: string,
|
|
302
409
|
* extensions?: readonly object[],
|
|
303
410
|
* contact?: Record<string, unknown>,
|
|
304
|
-
*
|
|
411
|
+
* adopt?: { gardenId: string, metaRoot: string, sourceGeneration: string, fence: object },
|
|
412
|
+
* open?: (options: { cwd: string, continueSession: boolean, session?: string, extensions: readonly object[], model?: { provider: string, model: string }, fence: object }) => Promise<any>,
|
|
305
413
|
* }} options
|
|
306
414
|
* @returns {Promise<{ durable: any, contact: any, attachment: { gardenId: string } & Record<string, any>, firstInput?: any, close(): Promise<void> }>}
|
|
307
415
|
* (stated, because the compiled closure carries no declarations: a cleanup path that rethrows
|
|
@@ -316,6 +424,7 @@ export async function openDurableCitizen({
|
|
|
316
424
|
firstInput,
|
|
317
425
|
extensions = [],
|
|
318
426
|
contact: contactOptions = {},
|
|
427
|
+
adopt,
|
|
319
428
|
open,
|
|
320
429
|
}) {
|
|
321
430
|
if (continueSession && (model !== undefined || firstInput !== undefined)) {
|
|
@@ -337,7 +446,12 @@ export async function openDurableCitizen({
|
|
|
337
446
|
throw refuse("first-input-empty", "a first input is a non-empty task");
|
|
338
447
|
}
|
|
339
448
|
const openDurable = open ?? (await import(RUNTIME)).openDurable;
|
|
340
|
-
const
|
|
449
|
+
const fence = adopt === undefined ? OWNER_FENCE : adopt.fence;
|
|
450
|
+
const contact = createPiDurableContact({
|
|
451
|
+
...contactOptions,
|
|
452
|
+
bridgeEntry,
|
|
453
|
+
...(adopt === undefined ? {} : adoptRoots(adopt.metaRoot)),
|
|
454
|
+
});
|
|
341
455
|
let durable;
|
|
342
456
|
try {
|
|
343
457
|
durable = await openDurable({
|
|
@@ -346,6 +460,7 @@ export async function openDurableCitizen({
|
|
|
346
460
|
...(session === undefined ? {} : { session }),
|
|
347
461
|
extensions: [contact.extension, ...extensions],
|
|
348
462
|
...(model === undefined ? {} : { model }),
|
|
463
|
+
fence,
|
|
349
464
|
});
|
|
350
465
|
} catch (error) {
|
|
351
466
|
contact.fail(error);
|
|
@@ -426,7 +541,42 @@ function parseArgs(argv) {
|
|
|
426
541
|
"--resume chooses a session and --continue opens the newest one; give one of them",
|
|
427
542
|
);
|
|
428
543
|
}
|
|
544
|
+
const session = values.get("--session");
|
|
545
|
+
if (session !== undefined && continueSession) {
|
|
546
|
+
throw refuse(
|
|
547
|
+
"session-continue-exclusive",
|
|
548
|
+
"--session names the session to reopen and --continue opens the newest one; give one of them",
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (session !== undefined && resume) {
|
|
552
|
+
throw refuse(
|
|
553
|
+
"session-resume-exclusive",
|
|
554
|
+
"--session names the session to reopen and --resume chooses one from a list; give one of them",
|
|
555
|
+
);
|
|
556
|
+
}
|
|
429
557
|
const fresh = FRESH_ONLY_FLAGS.filter((flag) => values.has(flag));
|
|
558
|
+
const adoptArg = values.get("--entwurf-adopt");
|
|
559
|
+
if (adoptArg !== undefined) {
|
|
560
|
+
const others = [
|
|
561
|
+
...(continueSession ? ["--continue"] : []),
|
|
562
|
+
...(resume ? ["--resume"] : []),
|
|
563
|
+
...(session === undefined ? [] : ["--session"]),
|
|
564
|
+
...fresh,
|
|
565
|
+
];
|
|
566
|
+
if (others.length > 0) {
|
|
567
|
+
throw refuse(
|
|
568
|
+
"adopt-exclusive",
|
|
569
|
+
`${others.join(", ")} cannot be given with --entwurf-adopt: an adoption opens exactly the reserved session`,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const adopt = adoptArg === undefined ? undefined : decodeAdoptContext(adoptArg);
|
|
574
|
+
if (session !== undefined && fresh.length > 0) {
|
|
575
|
+
throw refuse(
|
|
576
|
+
"fresh-only-argument-with-session",
|
|
577
|
+
`${fresh.join(", ")} apply only to a new session; --session reopens the named session as it was saved`,
|
|
578
|
+
);
|
|
579
|
+
}
|
|
430
580
|
if (resume && fresh.length > 0) {
|
|
431
581
|
throw refuse(
|
|
432
582
|
"fresh-only-argument-with-resume",
|
|
@@ -455,6 +605,8 @@ function parseArgs(argv) {
|
|
|
455
605
|
: {};
|
|
456
606
|
if (continueSession) return { continueSession, ...nativeModule };
|
|
457
607
|
if (resume) return { resume, ...nativeModule };
|
|
608
|
+
if (session !== undefined) return { session, ...nativeModule };
|
|
609
|
+
if (adopt !== undefined) return { adopt, ...nativeModule };
|
|
458
610
|
const provider = values.get("--provider");
|
|
459
611
|
if (provider === undefined) {
|
|
460
612
|
throw refuse(
|
|
@@ -543,8 +695,29 @@ async function chooseSession(cwd, sessionsDir, pick) {
|
|
|
543
695
|
* }} [seams]
|
|
544
696
|
*/
|
|
545
697
|
export async function main(argv, { loadTui = () => import(TUI), open, contact, bridgeEntry, pick } = {}) {
|
|
546
|
-
// Every argument refusal is decided here, before any module, the TUI or
|
|
698
|
+
// Every argument refusal but the `--session` grammar is decided here, before any module, the TUI or
|
|
699
|
+
// the app loads.
|
|
547
700
|
const { nativeModules = [], resume = false, ...args } = parseArgs(argv);
|
|
701
|
+
// `--entwurf-adopt` (#157 B3 I-3): the roots this process received are checked against the context before
|
|
702
|
+
// anything else is read. The session is the one the garden record names, under the `--session` grammar below.
|
|
703
|
+
if (args.adopt !== undefined) {
|
|
704
|
+
requireAdoptRootEnv(args.adopt.metaRoot);
|
|
705
|
+
args.session = adoptedSession(args.adopt);
|
|
706
|
+
}
|
|
707
|
+
// `--session` is held to the owned session-name grammar the runtime selects by, before operator-native
|
|
708
|
+
// module evaluation, TUI load, open or birth. Only the import-free grammar file is read for it — never
|
|
709
|
+
// the session store, whose SDK config import fixes package state from the environment an operator
|
|
710
|
+
// module may still prepare. The shape is lexical — it names a directory under this cwd's session root
|
|
711
|
+
// by name only, which is not physical symlink confinement.
|
|
712
|
+
if (args.session !== undefined) {
|
|
713
|
+
const { isSessionName } = await import(SESSION_NAME);
|
|
714
|
+
if (!isSessionName(args.session)) {
|
|
715
|
+
throw refuse(
|
|
716
|
+
"session-id-malformed",
|
|
717
|
+
`${JSON.stringify(args.session)} is not a durable session id (<13-digit ms>-<uuid>)`,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
548
721
|
// The caller's directory, fixed before any operator code runs and handed to the app explicitly.
|
|
549
722
|
const cwd = process.cwd();
|
|
550
723
|
const extensions = await loadNativeModules(nativeModules);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
clampThinkingLevel,
|
|
5
|
+
cleanupSessionResources,
|
|
6
|
+
getSupportedThinkingLevels,
|
|
7
|
+
|
|
8
|
+
} from "@earendil-works/pi-ai";
|
|
4
9
|
import {
|
|
5
10
|
|
|
6
11
|
|
|
@@ -26,7 +31,7 @@ import {
|
|
|
26
31
|
ExecutionEnvs,
|
|
27
32
|
findInitialAgentModel,
|
|
28
33
|
} from "./harness-setup.js";
|
|
29
|
-
import {
|
|
34
|
+
import { selectSession } from "./sessions.js";
|
|
30
35
|
import { Subagent } from "./subagent.js";
|
|
31
36
|
|
|
32
37
|
const context = BACKGROUND_CONTEXT;
|
|
@@ -96,6 +101,8 @@ const context = BACKGROUND_CONTEXT;
|
|
|
96
101
|
|
|
97
102
|
|
|
98
103
|
|
|
104
|
+
|
|
105
|
+
|
|
99
106
|
|
|
100
107
|
|
|
101
108
|
|
|
@@ -144,7 +151,16 @@ function titleOf(entry ) {
|
|
|
144
151
|
return { title: text.replace(/\s+/g, " ").trim() };
|
|
145
152
|
}
|
|
146
153
|
|
|
147
|
-
|
|
154
|
+
/** What failed, for an owner end's `cause`. */
|
|
155
|
+
function causeOf(error ) {
|
|
156
|
+
return error instanceof Error ? error.message : String(error);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function openDurable(options ) {
|
|
160
|
+
// The type requires it; a JS caller is refused here, before the session root is created.
|
|
161
|
+
if (options.fence === undefined) {
|
|
162
|
+
throw new Error("fence-required: an owned durable open takes the owner fence before its native lease");
|
|
163
|
+
}
|
|
148
164
|
if (options.model !== undefined && (options.continueSession || options.session !== undefined)) {
|
|
149
165
|
throw new Error("An explicit model applies only to a new session, not to a continued one");
|
|
150
166
|
}
|
|
@@ -152,12 +168,16 @@ export async function openDurable(options = {})
|
|
|
152
168
|
throw new Error("A chosen session and the newest one are two different sessions; name one");
|
|
153
169
|
}
|
|
154
170
|
const location = await selectSession(
|
|
171
|
+
options.fence,
|
|
155
172
|
options.cwd ?? process.cwd(),
|
|
156
173
|
options.continueSession ?? false,
|
|
157
174
|
options.session,
|
|
158
175
|
);
|
|
176
|
+
const ticket = location.ticket;
|
|
159
177
|
const envs = new ExecutionEnvs(location.cwd);
|
|
160
178
|
let harness ;
|
|
179
|
+
// The storage handle Harness.open is handed: if Harness.open itself rejects, this is what was opened.
|
|
180
|
+
let backendHandle ;
|
|
161
181
|
try {
|
|
162
182
|
const modelRuntime = await ModelRuntime.create();
|
|
163
183
|
const settingsManager = SettingsManager.create(location.cwd);
|
|
@@ -169,8 +189,12 @@ export async function openDurable(options = {})
|
|
|
169
189
|
|
|
170
190
|
const pendingReports = [];
|
|
171
191
|
let report = (error) => pendingReports.push(error);
|
|
192
|
+
// Recorded BEFORE the call: once the open is attempted, no later outcome proves that nothing was opened.
|
|
193
|
+
ticket.storageOpenAttempted = true;
|
|
194
|
+
const backend = await openNodeSqliteStorage(location.database);
|
|
195
|
+
backendHandle = backend;
|
|
172
196
|
harness = await Harness.open(
|
|
173
|
-
|
|
197
|
+
backend,
|
|
174
198
|
{
|
|
175
199
|
models: modelRuntime,
|
|
176
200
|
registry,
|
|
@@ -180,6 +204,7 @@ export async function openDurable(options = {})
|
|
|
180
204
|
},
|
|
181
205
|
context,
|
|
182
206
|
);
|
|
207
|
+
await ticket.storageOpened();
|
|
183
208
|
const initial = location.created
|
|
184
209
|
? await findInitialAgentModel(settingsManager, modelRuntime, options.model)
|
|
185
210
|
: undefined;
|
|
@@ -439,20 +464,94 @@ export async function openDurable(options = {})
|
|
|
439
464
|
unsubscribeCommits();
|
|
440
465
|
conversation.dispose();
|
|
441
466
|
closeTasks();
|
|
467
|
+
// The resource end the fence records (#157 B3). Storage is `closed` only when the first actual close
|
|
468
|
+
// resolved, no failure was seen before it, and the Session's own terminal result is not `failed`.
|
|
469
|
+
const failures = [];
|
|
470
|
+
let storage = "failed";
|
|
471
|
+
let envCleanup ;
|
|
472
|
+
let lease = "released";
|
|
442
473
|
try {
|
|
443
474
|
// Close writes no outcome: a running turn resumes with --continue.
|
|
444
475
|
await opened.close(context);
|
|
445
|
-
await
|
|
476
|
+
const ended = await opened.closed;
|
|
477
|
+
if (state.failure === undefined && ended.reason !== "failed") storage = "closed";
|
|
478
|
+
try {
|
|
479
|
+
await envs.cleanup(context);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
envCleanup = "failed";
|
|
482
|
+
failures.push(error);
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
failures.push(error);
|
|
446
486
|
} finally {
|
|
447
|
-
|
|
487
|
+
// The provider connections the turns left open for reuse: pi-ai keeps a Codex WebSocket per provider
|
|
488
|
+
// session on a five-minute idle timer, and pi's AgentSession releases them on dispose. Without the same
|
|
489
|
+
// release here a closed host stays alive until that timer (#157 S3 LIVE-R1). No id: the provider session
|
|
490
|
+
// ids are docs of the storage this process alone opened, and a host process holds this one Session.
|
|
491
|
+
try {
|
|
492
|
+
cleanupSessionResources();
|
|
493
|
+
} catch (error) {
|
|
494
|
+
failures.push(error);
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
await location.release();
|
|
498
|
+
} catch (error) {
|
|
499
|
+
lease = "failed";
|
|
500
|
+
failures.push(error);
|
|
501
|
+
}
|
|
448
502
|
}
|
|
503
|
+
const failure = failures[0] ?? (storage === "closed" ? undefined : "the session failed before close");
|
|
504
|
+
const record = {
|
|
505
|
+
storage,
|
|
506
|
+
lease,
|
|
507
|
+
...(envCleanup === undefined ? {} : { envCleanup }),
|
|
508
|
+
...(failure === undefined ? {} : { cause: causeOf(failure) }),
|
|
509
|
+
};
|
|
510
|
+
// An env cleanup failure is the caller's error, not the resource's: the end stays `closed`.
|
|
511
|
+
try {
|
|
512
|
+
if (storage === "closed" && lease === "released") await ticket.closed(record);
|
|
513
|
+
else await ticket.closeFailed(record);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
failures.push(error);
|
|
516
|
+
}
|
|
517
|
+
if (failures.length === 1) throw failures[0];
|
|
518
|
+
if (failures.length > 1) throw new AggregateError(failures, "The durable session did not close cleanly");
|
|
449
519
|
})();
|
|
450
520
|
return closing;
|
|
451
521
|
},
|
|
452
522
|
};
|
|
453
523
|
} catch (error) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
524
|
+
// An init failure (#157 B3). Only a storage open that was never attempted, with the lease verifiably released,
|
|
525
|
+
// ends `closed`; once attempted, neither a settled Harness close nor a released lease proves the resource ended.
|
|
526
|
+
let storage = ticket.storageOpenAttempted ? "unknown" : "never-opened";
|
|
527
|
+
// The init error first, then every cleanup failure: none is dropped.
|
|
528
|
+
const failures = [error];
|
|
529
|
+
// The first close of what was opened: the Harness once it opened, else the storage handle Harness.open was
|
|
530
|
+
// handed and rejected. A close that settles is no verified close here; one that rejects is a failed storage.
|
|
531
|
+
const closable = harness ?? backendHandle;
|
|
532
|
+
if (closable !== undefined) {
|
|
533
|
+
try {
|
|
534
|
+
await closable.close(context);
|
|
535
|
+
} catch (closeError) {
|
|
536
|
+
storage = "failed";
|
|
537
|
+
failures.push(closeError);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
let lease = "released";
|
|
541
|
+
try {
|
|
542
|
+
await location.release();
|
|
543
|
+
} catch (releaseError) {
|
|
544
|
+
lease = "failed";
|
|
545
|
+
failures.push(releaseError);
|
|
546
|
+
}
|
|
547
|
+
const ending = { storage, lease, cause: failures.map(causeOf).join("; ") };
|
|
548
|
+
try {
|
|
549
|
+
if (storage === "never-opened" && lease === "released") await ticket.closed(ending);
|
|
550
|
+
else await ticket.closeFailed(ending);
|
|
551
|
+
} catch (fenceError) {
|
|
552
|
+
failures.push(fenceError);
|
|
553
|
+
}
|
|
554
|
+
if (failures.length === 1) throw error;
|
|
555
|
+
throw new AggregateError(failures, `${causeOf(error)}; its cleanup or owner end failed too`);
|
|
457
556
|
}
|
|
458
557
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The durable session name grammar (#157 G3 B-L1), alone in its own file: no imports and no I/O, so the
|
|
3
|
+
* bootstrap can hold a `--session` value to it before an operator module prepares the environment, without
|
|
4
|
+
* loading the session store, the SDK config or a lock library first. sessions.ts selects by this same predicate.
|
|
5
|
+
*/
|
|
6
|
+
const SESSION_NAME = /^\d{13}-[0-9a-f-]{36}$/u;
|
|
7
|
+
|
|
8
|
+
/** Whether `name` has the exact shape of a session directory name: `<13-digit ms>-<uuid>`. */
|
|
9
|
+
export function isSessionName(name ) {
|
|
10
|
+
return SESSION_NAME.test(name);
|
|
11
|
+
}
|