@junghanacs/entwurf 0.21.0 → 0.23.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.
Files changed (122) hide show
  1. package/AGENTS.md +3 -2
  2. package/BASELINE.md +3 -1
  3. package/CHANGELOG.md +658 -0
  4. package/DELIVERY.md +167 -29
  5. package/README.md +104 -14
  6. package/VERIFY.md +73 -11
  7. package/docs/external-mcp-host.md +16 -6
  8. package/docs/setup-clean-host.md +73 -22
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +54 -31
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +289 -57
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +171 -263
  26. package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
  27. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
  28. package/mcp/entwurf-bridge/src/index.ts +65 -31
  29. package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
  30. package/package.json +4 -4
  31. package/pi-extensions/entwurf-control.ts +91 -52
  32. package/pi-extensions/lib/codex-caller-seat.ts +204 -0
  33. package/pi-extensions/lib/codex-declaration.js +612 -0
  34. package/pi-extensions/lib/codex-fresh-preflight.ts +325 -52
  35. package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
  36. package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
  37. package/pi-extensions/lib/entwurf-facts.ts +14 -1
  38. package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
  39. package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
  40. package/pi-extensions/lib/entwurf-self-address.ts +33 -0
  41. package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
  42. package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
  43. package/pi-extensions/lib/fresh-call-composition.ts +493 -0
  44. package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
  45. package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
  46. package/pi-extensions/lib/herdr-placement.ts +276 -0
  47. package/pi-extensions/lib/mux-fresh-call.ts +257 -304
  48. package/run.sh +522 -10
  49. package/scripts/check-codex-app-server-launch.ts +445 -0
  50. package/scripts/check-codex-birth-hook.ts +187 -0
  51. package/scripts/check-entwurf-control-rpc.ts +138 -0
  52. package/scripts/check-entwurf-fact-provider.ts +4 -2
  53. package/scripts/check-entwurf-facts.ts +12 -6
  54. package/scripts/check-entwurf-peers-surface.ts +3 -1
  55. package/scripts/check-entwurf-self-address.ts +68 -0
  56. package/scripts/check-entwurf-v2-production.ts +42 -1
  57. package/scripts/check-entwurf-v2-send.ts +26 -7
  58. package/scripts/check-fresh-call-dispatch.ts +301 -0
  59. package/scripts/check-gate-qualification.ts +19 -5
  60. package/scripts/check-herdr-activation.ts +1293 -0
  61. package/scripts/check-herdr-fresh-call.ts +1250 -0
  62. package/scripts/check-herdr-placement.ts +383 -0
  63. package/scripts/check-herdr-plugin-build.ts +842 -0
  64. package/scripts/check-herdr-plugin-profile.ts +371 -0
  65. package/scripts/check-herdr-plugin.ts +523 -0
  66. package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
  67. package/scripts/check-herdr-sandbox.ts +456 -0
  68. package/scripts/check-herdr-supply.ts +184 -0
  69. package/scripts/check-mux-launch-tmux.ts +331 -35
  70. package/scripts/check-mux-launcher-fence.ts +138 -0
  71. package/scripts/check-peer-facts.ts +331 -0
  72. package/scripts/check-release-gate-outcomes.ts +12 -0
  73. package/scripts/check-typing-call-fence.ts +296 -0
  74. package/scripts/codex-app-server-launch.sh +275 -0
  75. package/scripts/codex-birth-doctor.sh +159 -16
  76. package/scripts/codex-birth-install.sh +178 -72
  77. package/scripts/codex-birth-uninstall.sh +162 -7
  78. package/scripts/codex-socket-path.ts +33 -0
  79. package/scripts/codex-terminal-title-config.py +500 -0
  80. package/scripts/codex_toml_io.py +121 -0
  81. package/scripts/fixtures/herdr-supply.json +29 -0
  82. package/scripts/herdr-activation.mjs +536 -0
  83. package/scripts/herdr-plugin-activate.mjs +270 -0
  84. package/scripts/herdr-plugin-deactivate.mjs +193 -0
  85. package/scripts/herdr-runtime.mjs +1203 -0
  86. package/scripts/install-herdr-ci.sh +96 -0
  87. package/scripts/lib/codex-fresh-live-protocol.ts +13 -5
  88. package/scripts/lib/codex-fresh-source-receipts.ts +29 -2
  89. package/scripts/lib/tree-digest.ts +97 -0
  90. package/scripts/meta-bridge-install.sh +19 -2
  91. package/scripts/meta-bridge-state.py +167 -11
  92. package/scripts/mutants/codex-app-server-launch.json +157 -0
  93. package/scripts/mutants/codex-caller-seat.json +334 -0
  94. package/scripts/mutants/codex-native.json +195 -29
  95. package/scripts/mutants/control-socket-disconnect.json +57 -0
  96. package/scripts/mutants/fresh-call-dispatch.json +154 -0
  97. package/scripts/mutants/herdr-activation.json +305 -0
  98. package/scripts/mutants/herdr-fresh-call.json +448 -0
  99. package/scripts/mutants/herdr-placement.json +161 -0
  100. package/scripts/mutants/herdr-plugin-build.json +160 -0
  101. package/scripts/mutants/herdr-plugin-profile.json +202 -0
  102. package/scripts/mutants/herdr-plugin.json +133 -0
  103. package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
  104. package/scripts/mutants/herdr-supply.json +133 -0
  105. package/scripts/mutants/meta-hook-session-switch.json +2 -2
  106. package/scripts/mutants/mux-fresh-call.json +184 -25
  107. package/scripts/mutants/mux-launcher-fence.json +13 -0
  108. package/scripts/mutants/omp-fresh.json +4 -4
  109. package/scripts/mutants/peer-facts.json +98 -0
  110. package/scripts/mutants/self-address.json +33 -0
  111. package/scripts/mutants/typing-call-fence.json +17 -0
  112. package/scripts/mutants/v2-surface.json +22 -0
  113. package/scripts/peer-facts.ts +120 -0
  114. package/scripts/register-pi-package.py +8 -0
  115. package/scripts/register-pi-provider.py +88 -12
  116. package/scripts/smoke-codex-birth.sh +281 -12
  117. package/scripts/smoke-codex-config-state.sh +192 -3
  118. package/scripts/smoke-codex-fresh-live.ts +277 -37
  119. package/scripts/smoke-entwurf-chain-live.ts +50 -0
  120. package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
  121. package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
  122. package/scripts/smoke-setup-verdict.sh +13 -11
@@ -0,0 +1,213 @@
1
+ /**
2
+ * herdr-placement — the PURE half of the placement evidence axis (#116 S1).
3
+ *
4
+ * WHAT THIS IS FOR. A citizen opened inside herdr is visible in one of herdr's panes.
5
+ * That pane is an EPHEMERAL VIEW, never an address and never a liveness receipt
6
+ * (AGENTS Hard Rule 16, `docs/mux-launch-rail.md` §7). It earns a column in
7
+ * `entwurf_peers` for one reason: on a herdr host the operator's own question is
8
+ * "which pane is that one in", and answering it by hand means reading two listings
9
+ * side by side.
10
+ *
11
+ * WHY THIS JOIN IS EXACT EVIDENCE AND NOT A GUESS. §7 admitted exactly two forms of
12
+ * exact placement evidence (our own launch receipt, and a peer's verifiable
13
+ * self-report) and named the guesses it refuses: window title, cwd match, and time
14
+ * proximity. This is a THIRD form and it belongs to neither the receipts nor the
15
+ * guesses: herdr reports the native session id its OWN OFFICIAL integration told it,
16
+ * and `nativeSessionId` is unique across the whole meta-store (Hard Rule 7). Two
17
+ * independently-owned facts meet on one unique key. Nothing here narrows candidates
18
+ * by cwd, reads a title, or prefers the nearest timestamp. §7 was amended in the same
19
+ * change that added this module; if the two ever disagree, §7 is the owning document.
20
+ *
21
+ * THE TWO AXES ARE NOT EQUALLY EXACT, AND THE DIFFERENCE IS NAMED. On the claude axis
22
+ * the reported value IS the key, byte for byte. On the pi axis it is a session FILE
23
+ * PATH, and the key is recovered from that filename by a strict conversion measured on
24
+ * pi 0.85.1 — a VENDOR FLOOR, not a key equality. Calling both "a unique-key join"
25
+ * would hide which one can drift when a vendor renames a file. The conversion is
26
+ * deliberately strict at both ends so that drift lands as a missed join, never a wrong
27
+ * one, and a listing that declined to read anything says so (see `declinedReports`).
28
+ *
29
+ * ONE READ, NO WATCHER. The observer performs exactly ONE `pane list` read for a
30
+ * whole listing and never retries. herdr publishes no event that says "this pane's
31
+ * `agent_session` is now settled" — measured on herdr 0.9.0: `pane.updated` is the
32
+ * only event whose payload carries `agent_session` and plugin `[[events]]` refuses
33
+ * that name, `events.subscribe` offers three pane kinds and none of them carry it,
34
+ * and `pane.agent_detected` arrives BEFORE the session reference exists. A retry loop
35
+ * around that gap is a discovery watcher, which §7 forbids by name. So a citizen
36
+ * whose pane has not reported yet reads `none` on this pass and is simply asked again
37
+ * next time.
38
+ *
39
+ * FACTS, NOT VERBS. Nothing here returns a transport, a target, or a suggestion. A
40
+ * pane id must never reach dispatch: `entwurf_v2` resolves its rail from the record
41
+ * and a live probe, and a placement that is one server restart from being false has
42
+ * no business in that decision.
43
+ */
44
+ export const UNOBSERVED_PLACEMENT = { kind: "unobserved" };
45
+ export const NO_PLACEMENT = { kind: "none" };
46
+ export const AMBIGUOUS_PLACEMENT = { kind: "ambiguous" };
47
+ /**
48
+ * The OFFICIAL integration reports this join accepts, and nothing else.
49
+ *
50
+ * WHY THIS TABLE EXISTS. herdr accepts a session reference from any integration,
51
+ * including one a user wrote (`integrations.mdx:65-90 @ c77af189`), and the row
52
+ * carries who reported it in `agent_session.agent` / `.source`. Reading only
53
+ * `kind`/`value` would have let a third party's report stand in for the placement
54
+ * owner's own — and "the placement owner reported it" is the entire reason
55
+ * `docs/mux-launch-rail.md` §7 admits this as exact evidence. So the triple is
56
+ * pinned: source, agent, AND the shape that source is measured to emit. A row that
57
+ * misses any leg carries no key.
58
+ *
59
+ * `backend` is the meta-record backend this report may speak for. It is checked at
60
+ * resolve time so a claude-reported pane can never be handed to a pi citizen whose
61
+ * native id happens to collide.
62
+ */
63
+ const OFFICIAL_REPORTS = {
64
+ "herdr:claude": { agent: "claude", kind: "id", backend: "claude-code" },
65
+ "herdr:pi": { agent: "pi", kind: "path", backend: "pi" },
66
+ };
67
+ /**
68
+ * A pi session filename, measured 2026-09-14 on pi 0.85.1:
69
+ *
70
+ * 2026-09-14T05-17-03-979Z_01a09e58-f06a-70e8-b14a-1f0f0c7f7c7d.jsonl
71
+ * └────────── start stamp ─────────┘ └──────── nativeSessionId ────────┘
72
+ *
73
+ * WHY THIS IS PINNED HERE AND NOT INFERRED. The uuid is the join key and it lives in
74
+ * a VENDOR filename, so this rule depends on pi's naming and would break silently if
75
+ * pi changed it. **Vendor floor: pi 0.85.1, measured 2026-09-14** — whether that layout
76
+ * is a vendor contract or a convention is NOT measured, so this is the one place in the
77
+ * axis that can drift under us. It is therefore strict on both ends — the name must end
78
+ * in `.jsonl` and the tail after the last `_` must be a well-formed uuid — and a name
79
+ * that fails either test yields no key at all rather than a substring that happens to
80
+ * look plausible. Drift then lands as a DECLINED report (the citizen reads `unobserved`,
81
+ * not `none`), never as a wrong join onto someone else's pane.
82
+ */
83
+ const PI_SESSION_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
84
+ const PI_SESSION_SUFFIX = ".jsonl";
85
+ export function piNativeSessionIdFromPath(value) {
86
+ if (!value.endsWith(PI_SESSION_SUFFIX))
87
+ return null;
88
+ const stem = value.slice(0, -PI_SESSION_SUFFIX.length);
89
+ const base = stem.slice(stem.lastIndexOf("/") + 1);
90
+ const cut = base.lastIndexOf("_");
91
+ if (cut < 0)
92
+ return null;
93
+ const candidate = base.slice(cut + 1);
94
+ return PI_SESSION_UUID.test(candidate) ? candidate : null;
95
+ }
96
+ export function joinKeyOf(row) {
97
+ if (row.sessionSource === null)
98
+ return null;
99
+ const official = OFFICIAL_REPORTS[row.sessionSource];
100
+ // An unmeasured source — a herdr version we have not run, or a third-party
101
+ // integration. Declined on purpose: coercing it would invent a rule, and the
102
+ // whole exact-evidence argument rests on WHO reported.
103
+ if (official === undefined)
104
+ return null;
105
+ if (row.agent !== official.agent)
106
+ return null;
107
+ if (row.sessionKind !== official.kind)
108
+ return null;
109
+ if (row.sessionValue === null || row.sessionValue.length === 0)
110
+ return null;
111
+ const nativeSessionId = official.kind === "id" ? row.sessionValue : piNativeSessionIdFromPath(row.sessionValue);
112
+ return nativeSessionId === null ? null : { nativeSessionId, backend: official.backend };
113
+ }
114
+ export function buildPlacementIndex(rows) {
115
+ const byNativeSessionId = new Map();
116
+ const ambiguous = new Set();
117
+ let declinedReports = 0;
118
+ for (const row of rows) {
119
+ const entry = joinKeyOf(row);
120
+ if (entry === null) {
121
+ // Only a row that actually CARRIED a report counts as declined.
122
+ if (row.sessionSource !== null || row.sessionValue !== null)
123
+ declinedReports++;
124
+ continue;
125
+ }
126
+ const key = entry.nativeSessionId;
127
+ if (ambiguous.has(key))
128
+ continue;
129
+ const seen = byNativeSessionId.get(key);
130
+ if (seen !== undefined && seen.paneId !== row.paneId) {
131
+ byNativeSessionId.delete(key);
132
+ ambiguous.add(key);
133
+ continue;
134
+ }
135
+ byNativeSessionId.set(key, { ...entry, paneId: row.paneId });
136
+ }
137
+ return { byNativeSessionId, ambiguous, declinedReports };
138
+ }
139
+ /**
140
+ * Parse what `herdr pane list` printed. Returns `null` — "nobody looked" — for any
141
+ * payload this does not recognise, because a herdr that answered in a shape we cannot
142
+ * read has told us nothing, and reporting `none` for every citizen on that basis
143
+ * would be a fabricated measurement. A row missing `pane_id` is the one hard error:
144
+ * the whole payload is unreadable rather than partially trusted.
145
+ */
146
+ export function parseHerdrPaneList(stdout) {
147
+ let raw;
148
+ try {
149
+ raw = JSON.parse(stdout);
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ if (typeof raw !== "object" || raw === null)
155
+ return null;
156
+ const result = raw.result;
157
+ if (typeof result !== "object" || result === null)
158
+ return null;
159
+ const panes = result.panes;
160
+ if (!Array.isArray(panes))
161
+ return null;
162
+ const rows = [];
163
+ for (const pane of panes) {
164
+ if (typeof pane !== "object" || pane === null)
165
+ return null;
166
+ const paneId = pane.pane_id;
167
+ if (typeof paneId !== "string" || paneId.length === 0)
168
+ return null;
169
+ const session = pane.agent_session;
170
+ if (typeof session !== "object" || session === null) {
171
+ rows.push({ paneId, agent: null, sessionSource: null, sessionKind: null, sessionValue: null });
172
+ continue;
173
+ }
174
+ const agent = session.agent;
175
+ const source = session.source;
176
+ const kind = session.kind;
177
+ const value = session.value;
178
+ rows.push({
179
+ paneId,
180
+ agent: typeof agent === "string" ? agent : null,
181
+ sessionSource: typeof source === "string" ? source : null,
182
+ sessionKind: kind === "id" || kind === "path" ? kind : null,
183
+ sessionValue: typeof value === "string" ? value : null,
184
+ });
185
+ }
186
+ return rows;
187
+ }
188
+ /**
189
+ * The whole decision, for one citizen. A `null` index is the no-herdr host and every
190
+ * citizen on it reads `unobserved` — which is why this takes the index rather than a
191
+ * reader: the read happened once, above, and this stays pure.
192
+ */
193
+ export function resolvePlacement(index, identity) {
194
+ if (index === null)
195
+ return UNOBSERVED_PLACEMENT;
196
+ if (index.ambiguous.has(identity.nativeSessionId))
197
+ return AMBIGUOUS_PLACEMENT;
198
+ const entry = index.byNativeSessionId.get(identity.nativeSessionId);
199
+ if (entry !== undefined) {
200
+ // The reporting integration must speak for THIS citizen's backend. A pi
201
+ // integration reporting an id that collides with a claude-code record is a
202
+ // contradiction, not a placement, and we decline to claim either way.
203
+ return entry.backend === identity.backend ? { kind: "herdr-pane", paneId: entry.paneId } : UNOBSERVED_PLACEMENT;
204
+ }
205
+ // No join. `none` is only honest when the read was COMPLETE — if any report was
206
+ // declined, the thing we did not read might have been this one.
207
+ return index.declinedReports > 0 ? UNOBSERVED_PLACEMENT : NO_PLACEMENT;
208
+ }
209
+ /** Render one observation as the listing's column value. `herdr <pane>` says WHERE it
210
+ * was seen and by WHOM, so a reader never mistakes the pane id for one of ours. */
211
+ export function renderPlacement(placement) {
212
+ return placement.kind === "herdr-pane" ? `herdr ${placement.paneId}` : placement.kind;
213
+ }