@junghanacs/entwurf 0.41.0 → 0.41.2
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 +1 -1
- package/BASELINE.md +7 -5
- package/CHANGELOG.md +426 -0
- package/DELIVERY.md +37 -20
- package/README.md +83 -81
- package/VERIFY.md +10 -10
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/external-mcp-host.md +1 -1
- package/docs/setup-clean-host.md +95 -29
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
- package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
- package/mcp/entwurf-bridge/src/index.ts +14 -11
- package/native/win32-x64/entwurf_win.node +0 -0
- package/native/win32-x64/manifest.json +2 -2
- package/package.json +15 -14
- package/pi/pi-durable/carrier/tui.js +18 -3
- package/pi/pi-durable/overlay/upstream-pin.json +12 -12
- package/pi-extensions/lib/acp/backend-adapter.ts +5 -3
- package/pi-extensions/lib/acp/backend.ts +12 -9
- package/pi-extensions/lib/entwurf-self-address.ts +13 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
- package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
- package/pi-extensions/lib/fresh-call-composition.ts +21 -10
- package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
- package/pi-extensions/lib/meta-sender-identity.ts +4 -0
- package/pi-extensions/lib/mux-fresh-call.ts +6 -4
- package/pi-extensions/lib/native-push/register.ts +17 -2
- package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
- package/pi-extensions/lib/remote-pair-receive.ts +27 -19
- package/pi-extensions/lib/remote-pair-send.ts +117 -30
- package/pi-extensions/meta-bridge-omp.ts +69 -7
- package/pi-extensions/meta-bridge-pi-durable.ts +3 -3
- package/run.sh +218 -49
- package/scripts/check-agy-sender-identity.ts +6 -4
- package/scripts/check-entwurf-self-address.ts +34 -3
- package/scripts/check-entwurf-v2-contract.ts +24 -2
- package/scripts/check-entwurf-v2-production.ts +45 -31
- package/scripts/check-gate-qualification.ts +11 -9
- package/scripts/check-native-push-adapter.ts +4 -0
- package/scripts/check-native-push-register.ts +32 -82
- package/scripts/check-remote-pair-installed.ts +2 -2
- package/scripts/dev-bin.sh +112 -1
- package/scripts/lib/chain-terminus-publication.ts +58 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +33 -2
- package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
- package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
- package/scripts/mutants/chain-terminus-publication.json +17 -0
- package/scripts/mutants/codex-native.json +98 -0
- package/scripts/mutants/control-send-receipt.json +3 -3
- package/scripts/mutants/mux-fresh-call.json +16 -1
- package/scripts/mutants/native-push-paused.json +17 -0
- package/scripts/mutants/omp-fresh.json +13 -2
- package/scripts/mutants/pack-install.json +50 -4
- package/scripts/mutants/pi-durable.json +60 -0
- package/scripts/mutants/pi-floor.json +5 -5
- package/scripts/mutants/remote-pair-display.json +3 -3
- package/scripts/mutants/remote-pair-receive.json +41 -11
- package/scripts/mutants/remote-pair-roundtrip.json +67 -7
- package/scripts/mutants/remote-pair-send.json +156 -17
- package/scripts/mutants/remote-pair-transport.json +4 -4
- package/scripts/mutants/source-install.json +66 -0
- package/scripts/remote-pair-receive.ts +12 -12
- package/scripts/smoke-agy-install-state.sh +121 -0
- package/scripts/smoke-agy-native-push-live.ts +13 -0
- package/scripts/smoke-codex-fresh-live.ts +52 -28
- package/scripts/smoke-entwurf-chain-live.ts +7 -9
- package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
- package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
- package/scripts/smoke-herdr-raw-install-live.sh +10 -3
- package/scripts/smoke-mux-lifecycle-live.ts +140 -8
- package/scripts/smoke-omp-fresh-live.ts +5 -4
- package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
- package/scripts/smoke-setup-verdict.sh +27 -2
package/docs/setup-clean-host.md
CHANGED
|
@@ -23,18 +23,22 @@ evidence split, and its three states must not collapse into "macOS is supported"
|
|
|
23
23
|
|
|
24
24
|
## Requirements
|
|
25
25
|
|
|
26
|
+
This table is the published `@junghanacs/entwurf@0.41.1`'s, which ships the Pi 1.4.0 SDK set.
|
|
27
|
+
The previous package, `0.41.0`, ships the Pi 1.3.0 SDK set and requires Pi 1.3.x; the setup guide
|
|
28
|
+
inside that tarball is its contract.
|
|
29
|
+
|
|
26
30
|
| Component | Requirement | Needed for |
|
|
27
31
|
|---|---|---|
|
|
28
32
|
| Node | **`>=24.0.0`** | package and bridge runtime |
|
|
29
33
|
| npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
|
|
30
34
|
| Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
|
|
31
35
|
| entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
|
|
32
|
-
| pi | optional-by-presence, `>=1.
|
|
33
|
-
| Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.
|
|
36
|
+
| pi | optional-by-presence, `>=1.4.0 <1.5` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
|
|
37
|
+
| Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.4.0 pi SDK set as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
|
|
34
38
|
| Claude Code | optional, **`>=2.1.306`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
|
|
35
39
|
| GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
|
|
36
40
|
| OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
|
|
37
|
-
| Antigravity `agy` | optional, operator-installed and authenticated | native
|
|
41
|
+
| Antigravity `agy` | optional, operator-installed and authenticated | legacy citizen; native push paused (#152) |
|
|
38
42
|
| OpenAI Codex CLI | optional-by-presence, operator-installed and authenticated; detected composes all four operator-owned units (birth/MCP/status-line/terminal-title) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires the operator-owned app-server, started with `entwurf codex-app-server` in a tmux session of the operator's choosing (#95 D1 retired the fixed `codex` home) | native-push and visible fresh, supported in 0.21.0 |
|
|
39
43
|
| Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
|
|
40
44
|
|
|
@@ -79,11 +83,14 @@ node --version
|
|
|
79
83
|
npm --version
|
|
80
84
|
```
|
|
81
85
|
|
|
86
|
+
The two npm forms below install the **published** package, `0.41.1` (Pi 1.4.x, the Pi 1.4.0 SDK
|
|
87
|
+
set).
|
|
88
|
+
|
|
82
89
|
Global install is simplest when native harnesses should find stable bins from every
|
|
83
90
|
working directory:
|
|
84
91
|
|
|
85
92
|
```bash
|
|
86
|
-
npm install -g @junghanacs/entwurf
|
|
93
|
+
npm install -g @junghanacs/entwurf@0.41.1
|
|
87
94
|
entwurf --help
|
|
88
95
|
entwurf setup /path/to/your-project
|
|
89
96
|
entwurf check-bridge
|
|
@@ -94,7 +101,7 @@ A project-local installation is also supported:
|
|
|
94
101
|
```bash
|
|
95
102
|
mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
|
|
96
103
|
npm init -y
|
|
97
|
-
npm install --save-dev @junghanacs/entwurf
|
|
104
|
+
npm install --save-dev @junghanacs/entwurf@0.41.1
|
|
98
105
|
npx entwurf setup .
|
|
99
106
|
npx entwurf check-bridge
|
|
100
107
|
```
|
|
@@ -104,7 +111,7 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
|
|
|
104
111
|
same scope when upgrading and checking; native process restarts are separate (§7).
|
|
105
112
|
|
|
106
113
|
`check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
|
|
107
|
-
lists the nine garden tools (0.41.
|
|
114
|
+
lists the nine garden tools (0.41.1); it does not prove a backend model turn, native hook or retirement permission.
|
|
108
115
|
|
|
109
116
|
Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
|
|
110
117
|
Cortex, and their authentication remain operator-owned optional prerequisites for the
|
|
@@ -119,11 +126,19 @@ with a nonzero setup result):
|
|
|
119
126
|
```bash
|
|
120
127
|
git clone https://github.com/junghan0611/entwurf ~/repos/gh/entwurf
|
|
121
128
|
cd ~/repos/gh/entwurf
|
|
129
|
+
git checkout "$CANDIDATE_REF" # set it first to the commit you mean to run; a bare clone gives the default branch
|
|
122
130
|
./run.sh setup /path/to/consumer-project
|
|
123
131
|
```
|
|
124
132
|
|
|
133
|
+
Set `CANDIDATE_REF` explicitly to a published branch or tag that carries the commit you mean;
|
|
134
|
+
a bare clone selects the default branch, not a promised candidate. A validation branch is not a
|
|
135
|
+
release acceptance. A checkout that already holds the intended commit skips the clone and the
|
|
136
|
+
`git checkout`.
|
|
137
|
+
|
|
125
138
|
This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
|
|
126
|
-
link is foreign, outside PATH, or shadowed by another command
|
|
139
|
+
link is foreign, outside PATH, or shadowed by another command, or if another root still answers to
|
|
140
|
+
a package bin name in PATH or in the global bin dir `pnpm bin -g` / `npm prefix -g` report (it
|
|
141
|
+
removes nothing). It detects and wires
|
|
127
142
|
pi/Claude/agy/Copilot/OMP/Codex by presence and prints a computed per-component
|
|
128
143
|
PASS/SKIP/FAIL summary — a detected harness that cannot be completed makes setup exit nonzero.
|
|
129
144
|
A detected `copilot` composes all four native units, and a detected `omp` composes its own four.
|
|
@@ -207,10 +222,12 @@ physical-Darwin receipt. `entwurf install` may write `.pi/settings.json`
|
|
|
207
222
|
on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
|
|
208
223
|
`setup` names the pi row FAIL with the §1 Darwin wording.
|
|
209
224
|
|
|
210
|
-
Install the exact release floor, then wire the project
|
|
225
|
+
Install the exact release floor, then wire the project. The previous 0.41.0 instead takes the
|
|
226
|
+
Pi 1.3.x its own setup guide names. A source checkout passes its checkout path to `-e` in place of
|
|
227
|
+
the global package root:
|
|
211
228
|
|
|
212
229
|
```bash
|
|
213
|
-
npm install -g @earendil-works/pi-coding-agent@1.
|
|
230
|
+
npm install -g @earendil-works/pi-coding-agent@1.4.0
|
|
214
231
|
pi --version
|
|
215
232
|
|
|
216
233
|
cd ~/entwurf-smoke
|
|
@@ -218,7 +235,7 @@ entwurf install .
|
|
|
218
235
|
pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
|
|
219
236
|
```
|
|
220
237
|
|
|
221
|
-
The supported range is `>=1.
|
|
238
|
+
The supported range is `>=1.4.0 <1.5`. It is a hard minimum: installing this
|
|
222
239
|
release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
|
|
223
240
|
minor. A host using only the external MCP bridge can skip pi until it needs a
|
|
224
241
|
control socket; no delivery rail launches a pi process.
|
|
@@ -240,14 +257,14 @@ A plain `pi` can load the package without becoming a citizen.
|
|
|
240
257
|
In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
|
|
241
258
|
live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
|
|
242
259
|
message. These are harness tools, not shell subcommands; pi exposes them through
|
|
243
|
-
Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.
|
|
260
|
+
Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.1 bridge has nine tools;
|
|
244
261
|
pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
|
|
245
262
|
The added `entwurf_retire_call` is only for one target/action explicitly requested by the
|
|
246
263
|
human operator, never autonomous cleanup. Where a host provides or is configured with an approval
|
|
247
264
|
step, that step applies as well; Pi and pi-durable 1.3.0 have no built-in approval prompt by
|
|
248
265
|
default. The configured Claude ACP child is also [set to run without per-call permission prompts](./acp-backend-rail.md#configured-claude-acp-permissions); ACP retirement remains unmeasured.
|
|
249
266
|
Tool availability, annotations and caller identity are not a grant.
|
|
250
|
-
These
|
|
267
|
+
These counts do not claim that an older installed package or running host has rolled
|
|
251
268
|
forward; see [applying new source to a running host](#applying-new-source-to-a-running-host).
|
|
252
269
|
Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
|
|
253
270
|
control address (a Unix socket; a named pipe on the experimental native-Windows Pi
|
|
@@ -322,7 +339,7 @@ by `entwurf pi` or by registering ordinary Pi's adapter.
|
|
|
322
339
|
What the Entwurf package supplies:
|
|
323
340
|
|
|
324
341
|
1. **The pi SDK set** — `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
|
|
325
|
-
pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.
|
|
342
|
+
pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.4.0`, plus
|
|
326
343
|
`proper-lockfile`, as Entwurf's own production dependencies (declared once in
|
|
327
344
|
`pi/pi-durable/overlay/upstream-pin.json` `sdk`). Model data rides the published
|
|
328
345
|
`pi-ai`; there is no catalog copy.
|
|
@@ -335,7 +352,7 @@ What the Entwurf package supplies:
|
|
|
335
352
|
(`pi/pi-durable/carrier-resolver.mjs`), which maps them to the published package's
|
|
336
353
|
own `dist/` as installed — never through a source checkout.
|
|
337
354
|
|
|
338
|
-
The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.
|
|
355
|
+
The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.4.0/packages/durable/README.md)
|
|
339
356
|
and the [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
|
|
340
357
|
(last published at v1.1.0) describe what the carrier is. Running the bare upstream app does not attach Entwurf's
|
|
341
358
|
garden contact. Entwurf does not rebuild the controller/model/auth/storage system or
|
|
@@ -420,7 +437,7 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
|
|
|
420
437
|
manifest field; it is a known constraint of supplying both from one package, accepted for 0.32,
|
|
421
438
|
not a claim that every load path is harmless.
|
|
422
439
|
|
|
423
|
-
The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.
|
|
440
|
+
The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.4.0 <1.5`): a host Pi is
|
|
424
441
|
not needed to run pi-durable, and a host inside the range need not be the same copy as the
|
|
425
442
|
carrier's. Native Pi owns credentials, model settings and durable SQLite sessions; keep them
|
|
426
443
|
in place rather than copying credentials or migrating ordinary Pi transcripts. Native
|
|
@@ -431,6 +448,29 @@ to the app's own model resolution; the checkout contact/send/receive gates run t
|
|
|
431
448
|
with scripted S plus native H; Pi ↔ pi-durable LIVE on this supply surface is a separate release
|
|
432
449
|
proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
|
|
433
450
|
|
|
451
|
+
### Moving durable sessions from the Pi 1.3 SDK to 1.4
|
|
452
|
+
|
|
453
|
+
0.41.1 is published; moving a live host onto it remains a separate operator decision for each
|
|
454
|
+
host. When you do:
|
|
455
|
+
|
|
456
|
+
1. **Confirm the old owner is gone.** Close the 0.41 (SDK 1.3) durable session normally and check
|
|
457
|
+
that its process has exited. A stale session lock alone is not proof.
|
|
458
|
+
2. **Back up first.** Before the first 1.4 open, copy the target session directory whole
|
|
459
|
+
(`session.sqlite` with any `-wal`/`-shm`) as one consistent set.
|
|
460
|
+
3. **Choose the session by name.** Reopen with the `--resume` picker and pick the entry you mean;
|
|
461
|
+
do not use `--continue` on a host with more than one session.
|
|
462
|
+
4. **Package downgrade is not data rollback.** The first 1.4 open migrates SQLite storage to
|
|
463
|
+
schema 2, which SDK 1.3 (Entwurf 0.41) refuses to open. Reinstalling the old package does not
|
|
464
|
+
undo that. Restoring the backup does, and work done after the 1.4 open is not in the backup.
|
|
465
|
+
5. **Inspect parked work before retrying.** On its first open, 1.4 counts a task that SDK 1.3 left
|
|
466
|
+
running as one restart, and after repeated cut-offs it parks the task (`restart_limit`). Read
|
|
467
|
+
what the task already did before you resend anything: recovery of a parked model turn is not
|
|
468
|
+
measured, and its side effects or outcome may be unknown. The existing conversation abort is
|
|
469
|
+
cancellation, not retry; it need not affect background tasks.
|
|
470
|
+
|
|
471
|
+
The measurements behind these points, and what they do not cover, are in the
|
|
472
|
+
[#148 checkpoint](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6339048361).
|
|
473
|
+
|
|
434
474
|
### Maintainer: re-emitting the carrier
|
|
435
475
|
|
|
436
476
|
Only a maintainer changing the pi-durable app runs `scripts/emit-pi-durable-carrier.ts`. Its
|
|
@@ -458,31 +498,45 @@ re-emitted with `--write`, and its generated digest is reviewed like any other c
|
|
|
458
498
|
|
|
459
499
|
## 2c. Optional remote pair between two hosts (#107)
|
|
460
500
|
|
|
461
|
-
This lets
|
|
501
|
+
This lets declared pairs of existing citizens on two or more hosts reach each other over SSH that you already run. Each pair is one route; one host's file may carry several (#149). [DELIVERY](../DELIVERY.md#remote-pairs-107-149-declared-pairs-across-hosts) owns the contract. The machine boundary is set by a person, and nothing here is automatic.
|
|
462
502
|
|
|
463
|
-
1. **Connect the hosts yourself.** Use a private network (Tailscale or similar is recommended) and SSH keys and authorization you already trust. From each host, `ssh -o BatchMode=yes <alias> true` must succeed non-interactively, and the other host's non-interactive `command -v entwurf` must resolve to the installation you intend. Entwurf edits no SSH config, key, known-hosts file or network setting. Whatever SSH itself writes under your own policy, such as host-key updates, stays yours.
|
|
503
|
+
1. **Connect the hosts yourself.** Use a private network (Tailscale or similar is recommended) and SSH keys and authorization you already trust. From each host, `ssh -o BatchMode=yes <alias> true` must succeed non-interactively, and the other host's non-interactive `command -v entwurf` must resolve to the installation you intend. Entwurf edits no SSH config, key, known-hosts file or network setting. Whatever SSH itself writes under your own policy, such as host-key updates, stays yours. Everything that can log in to an account is inside that account's trust: if two other hosts' keys reach the same account, either can claim any garden id that account's file declares.
|
|
464
504
|
2. **Run the accepted bytes on both hosts, in the processes that matter.** See [applying new source to a running host](#applying-new-source-to-a-running-host). A pulled checkout or a green setup line does not, by itself, show what a running bridge loaded.
|
|
465
505
|
3. **Write one file on each host,** at `remotePairOptInPath()`. That is the pi agent dir unless `ENTWURF_META_SESSIONS_DIR` moves the meta-record store. Check that the sending bridge's environment and the SSH receive environment resolve the same path. Use mode `0600` and never overwrite a file you did not inspect:
|
|
466
506
|
|
|
467
507
|
```json
|
|
468
508
|
{
|
|
469
|
-
"
|
|
470
|
-
"
|
|
471
|
-
|
|
472
|
-
|
|
509
|
+
"version": 2,
|
|
510
|
+
"routes": [
|
|
511
|
+
{
|
|
512
|
+
"localGardenId": "<this host's garden id that talks to it>",
|
|
513
|
+
"remoteGardenId": "<the other host's garden id>",
|
|
514
|
+
"ssh": "<your existing alias for the other host>",
|
|
515
|
+
"remotePairFile": "<absolute path of the other host's own file>"
|
|
516
|
+
}
|
|
517
|
+
]
|
|
473
518
|
}
|
|
474
519
|
```
|
|
475
520
|
|
|
476
|
-
The two files
|
|
521
|
+
Add one route per pair. One local citizen may appear in several routes, and so may one remote citizen or one alias. The file is refused if it repeats a pair, uses one garden id as both local and remote, reaches one remote garden id through two aliases or files, or gives one alias two different files. For each route, the other host's file needs the mirror route: there, this route's `remoteGardenId` is the `localGardenId`. That host's `remotePairFile` must spell this path as it is normalized, not through a symlinked alias.
|
|
477
522
|
|
|
478
523
|
To check a file, read it with the repository's internal parser from a source checkout. Use the same agent/store-root settings the bridge runs with. This is a read-only dev-clone check: there is no installed operator validation verb, and it performs no send and no model input.
|
|
479
524
|
|
|
480
525
|
```bash
|
|
481
|
-
node --experimental-strip-types -e 'import("./pi-extensions/lib/remote-pair-send.ts").then(m=>console.log(m.readRemotePairOptIn(m.remotePairOptInPath())))'
|
|
526
|
+
node --experimental-strip-types -e 'import("./pi-extensions/lib/remote-pair-send.ts").then(m=>console.log(JSON.stringify(m.readRemotePairOptIn(m.remotePairOptInPath()),null,2)))'
|
|
482
527
|
```
|
|
483
528
|
|
|
484
529
|
Do not validate the configuration by sending a test message.
|
|
485
|
-
4. **Use it only between siblings you have open.** The measured self-fetch pair must be active and armed, and the target rail's own admission still applies. Delivery never starts, resumes or wakes an OFF citizen. To an active, armed one it rings the normal doorbell, which may start a turn at once in the receiving TUI. Remove the file to switch
|
|
530
|
+
4. **Use it only between siblings you have open.** The measured self-fetch pair must be active and armed, and the target rail's own admission still applies. Delivery never starts, resumes or wakes an OFF citizen. To an active, armed one it rings the normal doorbell, which may start a turn at once in the receiving TUI. Remove the file to switch every route off; there is nothing to stop or reconnect.
|
|
531
|
+
|
|
532
|
+
**Converting a file from 0.41 or earlier.** The earlier versionless file (the four keys alone) is refused by name. It is not read as one route, and it is not read as off. Convert each host by hand, one host at a time:
|
|
533
|
+
|
|
534
|
+
1. Inspect the existing file.
|
|
535
|
+
2. Install the new bytes on that host.
|
|
536
|
+
3. Restart the processes whose bridge you use. A bridge that is already running keeps the code it loaded.
|
|
537
|
+
4. Rewrite the file as `{"version":2,"routes":[<the old object>]}`, mode `0600`.
|
|
538
|
+
|
|
539
|
+
While the bytes and the file disagree, **every** `entwurf_v2` call of that host's bridges is refused, local targets included, because the file is read before the target is classified. That applies either way: new code with the old file, or old code with the new file. Keep that window short, and do not hide it with a fallback. The other host can stay on its own release and file until it converts. The claim, SSH argv, exit codes and receipt did not change, so the two hosts should still interoperate, but that mixed state has not been measured, and neither has any exchange over the new file.
|
|
486
540
|
|
|
487
541
|
## 3. Optional Claude Code native citizen
|
|
488
542
|
|
|
@@ -647,7 +701,7 @@ as a note rather than a fault.
|
|
|
647
701
|
|
|
648
702
|
**entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
|
|
649
703
|
presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
|
|
650
|
-
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.
|
|
704
|
+
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.4.0 <1.5`) and Claude Code
|
|
651
705
|
(`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
|
|
652
706
|
decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
|
|
653
707
|
earned one because an older binary validates the exec manifest, drops `args` at runtime, and
|
|
@@ -899,9 +953,14 @@ mechanism, because both the anchor and the env fallback would answer the same ro
|
|
|
899
953
|
|
|
900
954
|
## 5. Optional Antigravity native citizen
|
|
901
955
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
not a
|
|
956
|
+
Native-push send on this rail is paused (#152, below). Its Linux certification is
|
|
957
|
+
history: the last live bidirectional round trip was on agy 1.1.0 (2026-07-14), and it
|
|
958
|
+
is not a current certification. What remains is the legacy identity surface — install,
|
|
959
|
+
birth, statusline and sender identity. While the pause holds, a release accepts agy
|
|
960
|
+
through the deterministic pause cell and the install/sender gates only; checking these
|
|
961
|
+
surfaces in a fresh conversation is an optional diagnostic, not a release certification of
|
|
962
|
+
the installed host. On macOS it is NOT CERTIFIED — pending physical host. The installers may write on Darwin; a green install is not a native-push
|
|
963
|
+
receipt.
|
|
905
964
|
|
|
906
965
|
Install the three independently owned surfaces:
|
|
907
966
|
|
|
@@ -920,9 +979,16 @@ owns its subtree; the hook owns one `PreInvocation` entry. Unrelated settings ar
|
|
|
920
979
|
preserved. A fresh conversation initially may show `🪛 ? agy`; the first invocation
|
|
921
980
|
births the record by native `conversationId`, after which the garden id appears.
|
|
922
981
|
|
|
923
|
-
|
|
982
|
+
Native push to agy is paused (#152): on the external path entwurf measured, the agy
|
|
983
|
+
language server refused `agentapi` calls for a missing CSRF token (a first-party token
|
|
984
|
+
path is unmeasured), so `entwurf_v2` refuses an agy target as
|
|
985
|
+
`native-push-paused`, `entwurf_self` reports `replyable: false`, and
|
|
986
|
+
`entwurf_register_native` refuses. The citizen keeps its garden id and statusline. While
|
|
987
|
+
the pause holds, the LIVE smoke exits 2 with `[entwurf:paused]` before it reads the
|
|
988
|
+
conversation id, which is not a PASS, and no conversation needs to be opened for it:
|
|
924
989
|
|
|
925
990
|
```bash
|
|
991
|
+
# exits 2 [entwurf:paused] while #152 holds — not a PASS, not a SKIP
|
|
926
992
|
LIVE=1 AGY_CONVERSATION_ID=<id> entwurf smoke-agy-native-push-live
|
|
927
993
|
```
|
|
928
994
|
|
|
@@ -77,7 +77,7 @@ import { resolveMailboxReceiverFacts } from "../../../pi-extensions/lib/entwurf-
|
|
|
77
77
|
import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.js";
|
|
78
78
|
import { ENTWURF_PEERS_RENDER_LIMIT, renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-render.js";
|
|
79
79
|
import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
|
|
80
|
-
import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
|
|
80
|
+
import { nativePushPaused, nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
|
|
81
81
|
import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.js";
|
|
82
82
|
import { retireCitizen } from "../../../pi-extensions/lib/entwurf-v2-retire.js";
|
|
83
83
|
import { makeProductionRetireDeps } from "../../../pi-extensions/lib/entwurf-v2-retire-production.js";
|
|
@@ -235,6 +235,7 @@ async function buildMetaSenderEnvelope(identity, cwd, trustedMarker) {
|
|
|
235
235
|
metaDeliveryDomain,
|
|
236
236
|
recordBacked: true,
|
|
237
237
|
probeAlive: await probeNativeSenderAlive(identity),
|
|
238
|
+
nativePushPaused: nativePushPaused(identity.backend),
|
|
238
239
|
}
|
|
239
240
|
: metaDeliveryDomain === "self-fetch"
|
|
240
241
|
? (() => {
|
|
@@ -402,7 +403,7 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
|
|
|
402
403
|
"mailbox only while deliverable, native-push direct injection only while its adapter probe is alive, " +
|
|
403
404
|
"or a THIRD RESULT: rejection when neither holds. An inactive self-fetch citizen is mailbox-undeliverable; " +
|
|
404
405
|
"native-push has NO mailbox: dead → native-push-target-dead; indeterminate → " +
|
|
405
|
-
"native-push-probe-indeterminate. " +
|
|
406
|
+
"native-push-probe-indeterminate; agy (paused, #152) → native-push-paused, no probe. " +
|
|
406
407
|
"DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported " +
|
|
407
408
|
"— same receiver rule as the mailbox, no active drainer means no delivery. " +
|
|
408
409
|
"The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child " +
|
|
@@ -417,7 +418,8 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
|
|
|
417
418
|
.describe("fire-and-forget = send/reply/hand-off to a LIVE socket target or to any citizen with no " +
|
|
418
419
|
"socket liveness — the decider picks its rail, and a rail can also REJECT (self-fetch → " +
|
|
419
420
|
"mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, " +
|
|
420
|
-
"dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate
|
|
421
|
+
"dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate; a paused " +
|
|
422
|
+
"backend — agy, #152 — rejects as native-push-paused before any probe); set " +
|
|
421
423
|
"wants_reply for an answer. This is the ONLY intent: owned-outcome, which resumed a dormant " +
|
|
422
424
|
"citizen by launching a hidden background child, was withdrawn under the visible-first rule. " +
|
|
423
425
|
"It is not selectable, not deprecated-but-tolerated; a dormant citizen is currently " +
|
|
@@ -438,11 +440,11 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
|
|
|
438
440
|
// Resolved ONCE so the dispatch-moment timestamp is fixed and the control RPC sender
|
|
439
441
|
// + the mailbox body sender share one envelope. No replyability gate (see above).
|
|
440
442
|
const sender = await buildSendSenderEnvelope({ requestMeta: extra._meta });
|
|
441
|
-
// #107: the operator's remote-pair opt-in is read per send. Absent = off and the local
|
|
442
|
-
// surface below runs exactly as before; a declared target goes over
|
|
443
|
+
// #107/#149: the operator's remote-pair opt-in is read per send. Absent = off and the local
|
|
444
|
+
// surface below runs exactly as before; a declared target goes over its one exact route.
|
|
443
445
|
const rendered = await runEntwurfV2WithRemoteRoute({ target, intent, message, mode, wants_reply }, {
|
|
444
446
|
senderProvider: () => sender,
|
|
445
|
-
|
|
447
|
+
optIn: readRemotePairOptIn(remotePairOptInPath()),
|
|
446
448
|
// The decider's canonical socket dir AND this bridge's ENTWURF_DIR: a trace in either is local.
|
|
447
449
|
localTrace: makeLocalTraceFact({
|
|
448
450
|
sessionsDir: defaultMetaSessionsDir(),
|
|
@@ -546,7 +548,7 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
|
|
|
546
548
|
"or a REJECT when neither holds: mailbox-undeliverable for a self-fetch citizen whose inbox " +
|
|
547
549
|
"nobody drains, and on the native-push probe dead → native-push-target-dead vs " +
|
|
548
550
|
"indeterminate → native-push-probe-indeterminate, kept apart because an unestablished probe " +
|
|
549
|
-
"is not a departed host. So " +
|
|
551
|
+
"is not a departed host; an agy target rejects as native-push-paused (#152) without being probed. So " +
|
|
550
552
|
"`unsupported` does not promise reachability either: a record whose backend has no adapter on " +
|
|
551
553
|
"this lane resolves to that reject. " +
|
|
552
554
|
"Note: this is the *active* world and it is facts-only — it creates nothing. Pass an " +
|
|
@@ -616,8 +618,9 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
|
|
|
616
618
|
"must be LIVE: it is probed first, and a dead/indeterminate probe is refused (no garden id is " +
|
|
617
619
|
"minted for a pointer that does not resolve to a real host). Re-registering the same " +
|
|
618
620
|
"conversation attaches to the SAME garden id and refreshes the cwd. Only 'antigravity' is " +
|
|
619
|
-
"registerable on this lane
|
|
620
|
-
"
|
|
621
|
+
"registerable on this lane, and antigravity native-push is PAUSED (#152): this verb refuses it by " +
|
|
622
|
+
"name before any probe and mints no garden id. No mailbox receiver marker is written — native-push " +
|
|
623
|
+
"has no idle-wake watch; the returned garden id is the reply handle.", {
|
|
621
624
|
backend: z
|
|
622
625
|
.enum(["antigravity"])
|
|
623
626
|
.describe("The native backend hosting the conversation. Only 'antigravity' — codex is a separate lane."),
|
|
@@ -659,8 +662,8 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
|
|
|
659
662
|
"herdr workspace, pi and claude-code " +
|
|
660
663
|
"only; everywhere else it opens in the operator's tmux with all six backends (pi, " +
|
|
661
664
|
"claude-code, copilot, omp, codex, pi-durable). No fallback: an incomplete herdr context is refused by name, " +
|
|
662
|
-
"never by a tmux window you cannot see from herdr.
|
|
663
|
-
"
|
|
665
|
+
"never by a tmux window you cannot see from herdr. If proceeding, the sibling calls back before its task with a nonce; " +
|
|
666
|
+
"the sender envelope names its garden id — that is how you learn the " +
|
|
664
667
|
"address of a thing that did not exist a moment ago. This returns a LAUNCH receipt (the owner's " +
|
|
665
668
|
"coordinates plus that nonce): it does NOT mean the runtime started, the first turn ran, or " +
|
|
666
669
|
"the task was delivered. Those coordinates are a VIEW, never an address — a herdr tab/pane id can change under " +
|
|
@@ -75,6 +75,16 @@ export function computeSelfAddressability(facts) {
|
|
|
75
75
|
// default fail-closed row).
|
|
76
76
|
switch (facts.metaDeliveryDomain) {
|
|
77
77
|
case "native-push": {
|
|
78
|
+
// #152 policy, decided BEFORE the pure record+probe predicate and kept out of it:
|
|
79
|
+
// only a record-backed citizen can be "paused" (a record-less one keeps its own
|
|
80
|
+
// reason), and no probe was run for this answer, so none is reported.
|
|
81
|
+
if (facts.recordBacked === true && facts.nativePushPaused === true) {
|
|
82
|
+
return {
|
|
83
|
+
replyable: false,
|
|
84
|
+
socketState: "none",
|
|
85
|
+
reason: "native-push unreachable — native-push for this backend is paused (#152); no probe ran for this answer",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
78
88
|
const push = nativePushDeliverable({ recordBacked: facts.recordBacked, probeAlive: facts.probeAlive });
|
|
79
89
|
return {
|
|
80
90
|
replyable: push.deliverable,
|
|
@@ -119,6 +119,24 @@ export const NATIVE_PUSH_BACKENDS = ["antigravity", "codex"];
|
|
|
119
119
|
export function nativePushSupported(backend) {
|
|
120
120
|
return NATIVE_PUSH_BACKENDS.includes(backend);
|
|
121
121
|
}
|
|
122
|
+
// ── Paused native-push backends (#152) ─────────────────────────────────────
|
|
123
|
+
// A native-push backend whose SEND is paused by product policy. On the external send path
|
|
124
|
+
// entwurf measured, Antigravity's language server refused the `agentapi` call with
|
|
125
|
+
// `Unauthenticated: missing CSRF token` (agy 1.2.16 in #128; agy 1.4.6 again for #152).
|
|
126
|
+
// Whether a first-party own-tool shell holds a token entwurf could legitimately present is
|
|
127
|
+
// UNMEASURED, not ruled out. So instead of probing a port that refused on that path and
|
|
128
|
+
// reporting it as an inconclusive liveness, the three public entries that would reach the agy LS —
|
|
129
|
+
// v2 dispatch, a sender's own `replyable`, and `entwurf_register_native` — refuse by name
|
|
130
|
+
// BEFORE any probe. This is a fixed constant, not a flag: no env, config or runtime toggle
|
|
131
|
+
// can lift it. The backend stays in NATIVE_PUSH_BACKENDS, so existing agy records, the V3
|
|
132
|
+
// reader and the peers listing are unchanged; only the native-push traffic stops.
|
|
133
|
+
// The one probe that is NOT behind this guard is the fresh-cut quiescence check
|
|
134
|
+
// (scripts/meta-bridge-fresh-cut.ts), which protects an operator-named archive and must keep
|
|
135
|
+
// failing closed rather than read the pause as a departed host.
|
|
136
|
+
export const NATIVE_PUSH_PAUSED_BACKENDS = ["antigravity"];
|
|
137
|
+
export function nativePushPaused(backend) {
|
|
138
|
+
return NATIVE_PUSH_PAUSED_BACKENDS.includes(backend);
|
|
139
|
+
}
|
|
122
140
|
/**
|
|
123
141
|
* Compose the 4-value FACT liveness from a backend and its socket probe.
|
|
124
142
|
* Out-of-domain backend → `unsupported` (NOT dead/indeterminate, R1). An
|
|
@@ -148,18 +166,20 @@ export const ENTWURF_V2_REJECT_REASONS = [
|
|
|
148
166
|
"mailbox-undeliverable", // F-mailbox: fire-and-forget to an unsupported citizen whose mailbox is not deliverable (fail-closed; future pi-backend non-drainable mailbox)
|
|
149
167
|
"native-push-target-dead", // 봉인 1: fire-and-forget to a native-push (agy) target whose adapter probe found NO live conversation. Post-probe; observedLiveness = dead.
|
|
150
168
|
"native-push-probe-indeterminate", // 봉인 1: fire-and-forget to a native-push target whose adapter probe was inconclusive (agy alive but no port served the conv, or a probe error). Post-probe; observedLiveness = indeterminate. Never coerced to dead.
|
|
169
|
+
"native-push-paused", // #152: fire-and-forget to a native-push backend whose send is paused by product policy (NATIVE_PUSH_PAUSED_BACKENDS — antigravity). Pre-probe, observedLiveness = null: nothing was probed, so no liveness is claimed — never coerced to dead or indeterminate.
|
|
151
170
|
"bad-target", // R2: absent/typo garden-id (no existing citizen); fresh creation out of v2 scope
|
|
152
171
|
"record-less-socket", // #50 C4: a gid-shaped non-symlink control socket exists but NO meta-record claims it. The record is the sole address authority (목표 ②), so a bare socket is not an addressable citizen — it is a diagnostic state, refused for EVERY intent before any lock/probe (pre-probe, observedLiveness=null). NOT `bad-target`: something real answers to this gid, and mislabeling it absent would hide the very state the reject exists to surface.
|
|
153
172
|
"target-locked", // R5 pre-claim for bucket B F2 per-gid lockfile conflict
|
|
154
173
|
"target-address-conflict", // F3: a quarantined citizen (garden-id-socket-conflict / symlinked socket) — the gid resolves to two different receivers (record vs socket), so dispatch refuses to pick. The ONLY in-band honest channel for a dispatch-level identity-split (the listing diagnostic channel is not visible to a v2 caller, who only gets a receipt). Pre-resolver, like bad-target/target-locked — NOT a RESOLVER_REJECT_REASONS member.
|
|
155
174
|
];
|
|
156
175
|
// ── Pre-probe reject reasons (?6 — observedLiveness = null) ────────────────
|
|
157
|
-
// These
|
|
176
|
+
// These five rejects are decided BEFORE any liveness probe runs, so there is no
|
|
158
177
|
// honest 4-value FactLiveness to stamp: `bad-target` (no citizen/backend),
|
|
159
178
|
// `target-locked` (5a lock conflict, before lstat/connect), `target-address-conflict`
|
|
160
179
|
// (address-subject conflict → probing is forbidden), `record-less-socket` (#50 C4:
|
|
161
180
|
// no record ⇒ not a citizen ⇒ no in-domain probe ever runs — the presence hint is
|
|
162
|
-
// a single lstat, not a liveness measurement)
|
|
181
|
+
// a single lstat, not a liveness measurement), `native-push-paused` (#152: the backend's
|
|
182
|
+
// native-push send is paused by policy, so its adapter is never probed). `indeterminate` means an
|
|
163
183
|
// in-domain probe was inconclusive (≠ "not looked yet"); `unsupported` means the
|
|
164
184
|
// backend has no predicate (≠ "pre-probe"). So a pre-probe reject's
|
|
165
185
|
// observedLiveness is `null`, NOT one of the four values. Every OTHER reject —
|
|
@@ -176,6 +196,7 @@ export const PRE_PROBE_REJECT_REASONS = [
|
|
|
176
196
|
"target-locked",
|
|
177
197
|
"target-address-conflict",
|
|
178
198
|
"record-less-socket",
|
|
199
|
+
"native-push-paused",
|
|
179
200
|
];
|
|
180
201
|
export function isPreProbeReject(reason) {
|
|
181
202
|
return PRE_PROBE_REJECT_REASONS.includes(reason);
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* be bypassed.
|
|
45
45
|
*/
|
|
46
46
|
import { isOutOfSocketDomainGardenIdConflict } from "./entwurf-facts.js";
|
|
47
|
-
import { isLivenessSupported, makeRejectReceipt, nativePushSupported, resolveDispatch, resolveNativePushDispatch, } from "./entwurf-v2-contract.js";
|
|
47
|
+
import { isLivenessSupported, makeRejectReceipt, nativePushPaused, nativePushSupported, resolveDispatch, resolveNativePushDispatch, } from "./entwurf-v2-contract.js";
|
|
48
48
|
import { defaultMetaMailboxDir, defaultMetaSessionsDir, metaCapabilityFor, } from "./meta-session.js";
|
|
49
49
|
import { isValidSessionId } from "./session-id.js";
|
|
50
50
|
import { controlSocketPath, mapInspectionToLiveness } from "./socket-discovery.js";
|
|
@@ -121,6 +121,12 @@ export async function decideDispatch(input, deps) {
|
|
|
121
121
|
// branch is LOCK-FREE (봉인 4): the pi in-domain lock closes a socket TOCTOU, but a
|
|
122
122
|
// volatile probe route has no lock meaning (a duplicate-send idempotency is a D8 future).
|
|
123
123
|
if (nativePushSupported(identity.backend)) {
|
|
124
|
+
// #152: a paused native-push backend is refused by name BEFORE its adapter is probed —
|
|
125
|
+
// on the measured external path that language server refused the call (missing CSRF token),
|
|
126
|
+
// and a refusal must not be reported as a measured liveness. Pre-probe, observedLiveness null.
|
|
127
|
+
if (nativePushPaused(identity.backend)) {
|
|
128
|
+
return reject(makeRejectReceipt("native-push-paused", null));
|
|
129
|
+
}
|
|
124
130
|
const probe = await deps.nativePushProbe(identity);
|
|
125
131
|
const receipt = resolveNativePushDispatch(input.intent, probe.status);
|
|
126
132
|
if (!receipt.ok)
|
|
@@ -74,6 +74,9 @@ export function actionableRejectHint(reason) {
|
|
|
74
74
|
// Native host up, but nothing served this conversation/thread — inconclusive, not a
|
|
75
75
|
// hard dead. Backend-neutral for the same reason as the dead cell above.
|
|
76
76
|
return "the native host (harness process or app-server) is up, but nothing served this conversation/thread, so the probe is INCONCLUSIVE — not a measured death. Retry once it is loaded, or verify the conversation/thread id.";
|
|
77
|
+
case "native-push-paused":
|
|
78
|
+
// #152: a policy refusal, not a liveness verdict — this dispatch probed and sent nothing.
|
|
79
|
+
return "native-push for this backend is paused (#152): its language server refused entwurf's measured external calls for a missing CSRF token, so this dispatch probed and sent nothing. The citizen and its record are kept; reach it from inside its own harness instead.";
|
|
77
80
|
case "record-less-socket":
|
|
78
81
|
// #50 C4: name the true cause AND the fix — a bare socket is a diagnostic
|
|
79
82
|
// state, not an addressable citizen (the record is the address).
|
|
@@ -63,7 +63,7 @@ export const FRESH_CALL_CALLBACK_TOOL = {
|
|
|
63
63
|
copilot: "entwurf-bridge-entwurf_callback",
|
|
64
64
|
omp: "mcp__entwurf_bridge_entwurf_callback",
|
|
65
65
|
// Codex's tool process is the operator app-server, not the pane: the no-arg
|
|
66
|
-
// verb refuses there by name.
|
|
66
|
+
// verb refuses there by name. Its callback stays the delivery verb with args.
|
|
67
67
|
codex: "mcp__entwurf_bridge__entwurf_v2",
|
|
68
68
|
// The durable contact registers the bridge verbs as its OWN tools under their bare names, and the
|
|
69
69
|
// app offers a tool to the model by its registered name (`durable/harness/registry.ts:99`
|
|
@@ -348,8 +348,8 @@ export function composeBackendArgs(backend, composition, model, resolveCodexSock
|
|
|
348
348
|
* PLACEMENT SENTENCE. The first line names where the sibling was opened, and only the RAIL knows
|
|
349
349
|
* that — "the operator's tmux session" is true of a `new-window` launch and false of a herdr
|
|
350
350
|
* pane. It is therefore supplied by the caller rather than composed here. Everything BELOW that
|
|
351
|
-
* line is rail-independent and stays the contract: the callback
|
|
352
|
-
*
|
|
351
|
+
* line is rail-independent and stays the contract: the callback comes before the task, an optional
|
|
352
|
+
* read-only check of the caller may come before the callback, and the steering is stated as fact.
|
|
353
353
|
*/
|
|
354
354
|
/**
|
|
355
355
|
* The first-turn framing, WITHOUT the task.
|
|
@@ -361,8 +361,13 @@ export function composeBackendArgs(backend, composition, model, resolveCodexSock
|
|
|
361
361
|
* the halves separate is what lets the framing stay plain prose on both rails while the operator's
|
|
362
362
|
* task text keeps every byte it was written with.
|
|
363
363
|
*
|
|
364
|
-
* Order is the contract: the callback
|
|
365
|
-
*
|
|
364
|
+
* Order is the contract: the callback comes BEFORE THE TASK, so a sibling that gets stuck in the
|
|
365
|
+
* task has already told the caller who it is. It is not the literal first tool call. `[GLG 직접,
|
|
366
|
+
* 2026-10-24]` checking the caller and then calling back is the right order, and the framing used
|
|
367
|
+
* to say both "FIRST ACTION … call back" and "you can corroborate the caller first" — two
|
|
368
|
+
* sentences that cannot both be obeyed. What may come before the callback is the one read-only
|
|
369
|
+
* check offered below (and, where a dialect needs it, loading the tools); reading files, running
|
|
370
|
+
* commands or starting the task may not.
|
|
366
371
|
*
|
|
367
372
|
* WHAT THIS SAYS AND WHY IT STOPPED SAYING THE REST (#116). Until 2026-09-17 this framing carried
|
|
368
373
|
* three prohibitions, including "Do not inspect environment variables, do not call entwurf_self".
|
|
@@ -390,7 +395,7 @@ export function composeFreshCallFraming(params) {
|
|
|
390
395
|
}
|
|
391
396
|
const firstAction = params.backend === "codex"
|
|
392
397
|
? [
|
|
393
|
-
`
|
|
398
|
+
`BEFORE THE TASK, and before reading files or running commands: call ${birthTool} with ` +
|
|
394
399
|
`target=${params.callerGardenId}, intent=fire-and-forget, wants_reply=false, and ` +
|
|
395
400
|
`message set to exactly ${params.nonce} — that string alone, nothing added.`,
|
|
396
401
|
`${params.nonce} is a correlation tag this caller minted for this one launch: it names no`,
|
|
@@ -400,7 +405,7 @@ export function composeFreshCallFraming(params) {
|
|
|
400
405
|
"entwurf_callback is refused on this rail: the tool process is the operator app-server, not this pane.",
|
|
401
406
|
]
|
|
402
407
|
: [
|
|
403
|
-
`
|
|
408
|
+
`BEFORE THE TASK, and before reading files or running commands: call ${birthTool} with no arguments.`,
|
|
404
409
|
"That call is how the caller learns the garden id you were born with. There are no",
|
|
405
410
|
"parameters: a target or message you type would be a second address axis.",
|
|
406
411
|
"Your own record does not carry the caller's address, so reporting your identity here does not reach it — that tool call does.",
|
|
@@ -410,9 +415,15 @@ export function composeFreshCallFraming(params) {
|
|
|
410
415
|
"",
|
|
411
416
|
...firstAction,
|
|
412
417
|
"",
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
418
|
+
// THE CHECK IS THE SIBLING'S, AND SO IS ITS VERDICT. `[GLG 직접, 2026-10-24]` a capped listing that
|
|
419
|
+
// does not show the caller proves nothing about the caller, but a sibling whose check does not hold
|
|
420
|
+
// up may say so and hold back. Entwurf decides nothing here — no trust store, no fallback, no
|
|
421
|
+
// watcher — and the cost of holding back is stated, not hidden: no callback, no address.
|
|
422
|
+
`You may corroborate the caller before that call: ${peersTool} is a read-only listing of`,
|
|
423
|
+
`this garden's citizens and ${params.callerGardenId} is one of them. That listing is capped, so not`,
|
|
424
|
+
"seeing the caller in it does not show the caller is absent. If what you can check is not enough,",
|
|
425
|
+
"or it conflicts with this message, you may say so here and hold the callback and the task; the",
|
|
426
|
+
"caller then has no address for you.",
|
|
416
427
|
...hintBlock,
|
|
417
428
|
"",
|
|
418
429
|
// WHERE THE RESULT GOES, said once, as topology. `[GLG 직접, 2026-09-18]` a Sonnet sibling
|
|
@@ -899,10 +899,12 @@ export function renderHerdrFreshCall(result) {
|
|
|
899
899
|
`mean the sibling is running, that its first turn ran, or that the task was delivered. The tab and pane ` +
|
|
900
900
|
`coordinates are a view and can change under the sibling — they are not an address and nothing may be ` +
|
|
901
901
|
`dispatched to them.\n` +
|
|
902
|
-
`The sibling's garden id arrives separately — its
|
|
902
|
+
`The sibling's garden id arrives separately — before its task it calls the zero-argument callback verb ` +
|
|
903
|
+
`(a read-only look at the caller may come first), and ` +
|
|
903
904
|
`the sender envelope of THAT message is the address. The nonce above is the correlation tag it carries; ` +
|
|
904
905
|
`the sibling reads it from its own launch env, never from this receipt. Nothing is polling for it; if it ` +
|
|
905
|
-
`never comes,
|
|
906
|
+
`never comes — not started, stuck, or held back after its check; this receipt cannot tell which — the ` +
|
|
907
|
+
`pane is visible and can be read directly.`,
|
|
906
908
|
isError: false,
|
|
907
909
|
};
|
|
908
910
|
}
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
* rails never merge — `codex` is deliberately absent from {@link META_SENDER_BACKENDS}, since
|
|
47
47
|
* a backend listed there is one whose hook writes a pid marker, and codex birth writes none.
|
|
48
48
|
*/
|
|
49
|
+
import { nativePushPaused } from "./entwurf-v2-contract.js";
|
|
49
50
|
import { defaultMetaSessionsDir, describe, isPlausibleOwnerPid, listAllMetaIdentities, makeStoreRecordReader, metaRecordExistsByGardenId, parentPid, readActiveStoreEntries, readMetaIdentityByGardenId, readMetaSenderMarker, } from "./meta-session.js";
|
|
50
51
|
import { resolveNativePushAdapter } from "./native-push/adapter.js";
|
|
51
52
|
/** Every native backend that mints a garden-id from its own hook and writes a sender marker.
|
|
@@ -193,6 +194,10 @@ export function resolveTrustedMetaSenderIdentity(opts = {}) {
|
|
|
193
194
|
* exactly the Crash-Don't-Warn shape this lane exists to remove. Let them propagate.
|
|
194
195
|
*/
|
|
195
196
|
export async function probeNativeSenderAlive(identity, deps = {}) {
|
|
197
|
+
// #152: a paused native-push backend cannot receive a reply, so its `replyable` is false by
|
|
198
|
+
// POLICY — no adapter is resolved or probed. This is not a liveness verdict about the citizen.
|
|
199
|
+
if (nativePushPaused(identity.backend))
|
|
200
|
+
return false;
|
|
196
201
|
const resolveAdapter = deps.resolveAdapter ?? resolveNativePushAdapter;
|
|
197
202
|
const probe = await resolveAdapter(identity.backend).probe(identity.nativeSessionId);
|
|
198
203
|
return probe.status === "alive";
|
|
@@ -514,11 +514,13 @@ export function renderFreshCall(result) {
|
|
|
514
514
|
`\n` +
|
|
515
515
|
`This is a LAUNCH receipt: tmux created that window and was asked to start the runtime with the model above. It does ` +
|
|
516
516
|
`NOT mean the sibling is running, that its first turn ran, or that the task was delivered.\n` +
|
|
517
|
-
`The sibling's garden id arrives separately — its
|
|
518
|
-
`(codex is the one arm that still sends the nonce as arguments
|
|
517
|
+
`The sibling's garden id arrives separately — before its task it calls the zero-argument callback verb ` +
|
|
518
|
+
`(codex is the one arm that still sends the nonce as arguments; a read-only look at the caller may come ` +
|
|
519
|
+
`first), and the sender envelope of THAT ` +
|
|
519
520
|
`message is the address. The nonce above is the correlation tag it carries; the sibling reads it ` +
|
|
520
|
-
`from its own launch env, never from this receipt. Nothing is polling for it; if it never comes
|
|
521
|
-
`the window is visible
|
|
521
|
+
`from its own launch env, never from this receipt. Nothing is polling for it; if it never comes — ` +
|
|
522
|
+
`not started, stuck, or held back after its check; this receipt cannot tell which — the window is visible ` +
|
|
523
|
+
`and can be read directly.`,
|
|
522
524
|
isError: false,
|
|
523
525
|
};
|
|
524
526
|
}
|