@junghanacs/entwurf 0.23.1 → 0.25.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/BASELINE.md +1 -0
- package/CHANGELOG.md +628 -0
- package/DELIVERY.md +24 -1
- package/FAQ.md +47 -0
- package/README.md +5 -3
- package/VERIFY.md +20 -2
- package/demo/README.md +5 -2
- package/docs/acp-backend-rail.md +49 -2
- package/docs/setup-clean-host.md +8 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +38 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +19 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/callback-env.js +68 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-send-receipt.js +101 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +17 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +4 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +53 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +38 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +54 -22
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +20 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +24 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +19 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +35 -42
- package/mcp/entwurf-bridge/dist/protocol.js +0 -7
- package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +40 -3
- package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +72 -0
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +46 -5
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/package.json +11 -9
- package/pi-extensions/entwurf-control.ts +83 -154
- package/pi-extensions/lib/acp/backend.ts +16 -6
- package/pi-extensions/lib/acp/context.ts +30 -10
- package/pi-extensions/lib/acp/session-store.ts +2 -2
- package/pi-extensions/lib/callback-env.ts +78 -0
- package/pi-extensions/lib/compaction-send-guard.ts +6 -1
- package/pi-extensions/lib/control-send-receipt.ts +108 -0
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +6 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +19 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +23 -1
- package/pi-extensions/lib/entwurf-v2-send.ts +82 -6
- package/pi-extensions/lib/entwurf-v2-surface.ts +42 -3
- package/pi-extensions/lib/fresh-call-composition.ts +56 -22
- package/pi-extensions/lib/herdr-fresh-call.ts +25 -7
- package/pi-extensions/lib/herdr-placement.ts +24 -4
- package/pi-extensions/lib/mux-fresh-call.ts +23 -8
- package/pi-extensions/meta-bridge-omp.ts +35 -48
- package/protocol.js +0 -8
- package/run.sh +306 -53
- package/scripts/check-acp-backend-preflight.ts +117 -9
- package/scripts/check-acp-carrier-augment.ts +6 -4
- package/scripts/check-acp-event-mapper.ts +17 -6
- package/scripts/check-acp-prompt-builder.ts +17 -9
- package/scripts/check-acp-prompt-lifecycle.ts +23 -6
- package/scripts/check-acp-session-reuse.ts +64 -51
- package/scripts/check-acp-session-store.ts +31 -8
- package/scripts/check-acp-stop-reason.ts +20 -2
- package/scripts/check-acp-stream-hooks.ts +22 -5
- package/scripts/check-acp-usage-accounting.ts +33 -9
- package/scripts/check-entwurf-bridge-boot.ts +16 -1
- package/scripts/check-entwurf-v2-surface.ts +289 -10
- package/scripts/check-fresh-call-dispatch.ts +4 -5
- package/scripts/check-gate-qualification.ts +51 -9
- package/scripts/check-herdr-activation.ts +73 -0
- package/scripts/check-herdr-fresh-call.ts +30 -6
- package/scripts/check-herdr-placement.ts +51 -0
- package/scripts/check-herdr-plugin-build.ts +11 -4
- package/scripts/check-herdr-plugin.ts +745 -38
- package/scripts/check-herdr-sandbox.ts +14 -2
- package/scripts/check-install-container.sh +1 -1
- package/scripts/check-mux-launcher-fence.ts +37 -0
- package/scripts/check-pi-launch.ts +8 -2
- package/scripts/check-release-gate-outcomes.ts +165 -0
- package/scripts/doctor-pi-provider.ts +44 -4
- package/scripts/herdr-rails.mjs +75 -0
- package/scripts/lib/mutation-qualify.ts +1 -1
- package/scripts/meta-bridge-state.py +18 -1
- package/scripts/mutants/acp-backend-preflight.json +20 -0
- package/scripts/mutants/bridge-boot-resume.json +26 -0
- package/scripts/mutants/bridge-command-boot.json +38 -0
- package/scripts/mutants/control-send-receipt.json +235 -0
- package/scripts/mutants/herdr-activation.json +16 -0
- package/scripts/mutants/herdr-fresh-call.json +15 -4
- package/scripts/mutants/herdr-plugin-build.json +7 -11
- package/scripts/mutants/herdr-plugin.json +177 -9
- package/scripts/mutants/meta-retire.json +22 -0
- package/scripts/mutants/mux-fresh-call.json +21 -8
- package/scripts/mutants/omp-fresh.json +5 -9
- package/scripts/mutants/pack-install.json +4 -4
- package/scripts/mutants/pi-floor.json +17 -0
- package/scripts/mutants/release-gate.json +48 -0
- package/scripts/mutants/retired-sent-surface.json +32 -0
- package/scripts/mutants/v2-surface.json +7 -7
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-acp-session-reuse-live.ts +23 -5
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-codex-fresh-live.ts +94 -13
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +71 -36
- package/scripts/smoke-herdr-raw-install-live.sh +90 -5
- package/scripts/smoke-meta-install-state.sh +107 -1
- package/scripts/smoke-mux-lifecycle-live.ts +9 -2
- package/scripts/smoke-omp-fresh-live.ts +4 -2
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-provider-state.sh +108 -2
- package/scripts/smoke-setup-verdict.sh +18 -2
|
@@ -85,11 +85,12 @@
|
|
|
85
85
|
* the session the window is actually in. It still reports no `pane_current_path`, and
|
|
86
86
|
* there is no "session created" field because nothing here creates one.
|
|
87
87
|
*/
|
|
88
|
+
import { callbackEnvAssignments } from "./callback-env.js";
|
|
88
89
|
import { classifyTmuxCwd } from "./classify-tmux-cwd.js";
|
|
89
90
|
import { CODEX_CALLER_SEAT_HINT, resolveCodexCallerSeat, } from "./codex-caller-seat.js";
|
|
90
91
|
import { CODEX_CALLER_PREFLIGHT_HINT, CODEX_LAUNCH_CWD_PREFLIGHT_HINT, CODEX_PREFLIGHT_HINT, codexLaunchCwdFreshPreflight, } from "./codex-fresh-preflight.js";
|
|
91
92
|
import { COPILOT_PREFLIGHT_HINT, copilotFreshPreflight, } from "./copilot-fresh-preflight.js";
|
|
92
|
-
import { buildOmpBootstrapPayload, composeBackendArgs, composeFreshCallPrompt, FRESH_CALL_BACKENDS, FRESH_CALL_CALLBACK_TOOL, isSafeFreshCallModel, MODEL_MAX_CHARS, mintNonce, normalizeFreshCallInputs, OMP_BOOTSTRAP_FLAG, OMP_BOOTSTRAP_VERSION, TASK_MAX_CHARS, } from "./fresh-call-composition.js";
|
|
93
|
+
import { buildOmpBootstrapPayload, composeBackendArgs, composeFreshCallPrompt, FRESH_CALL_BACKENDS, FRESH_CALL_CALLBACK_TOOL, FRESH_CALL_DELIVERY_TOOL, isSafeFreshCallModel, MODEL_MAX_CHARS, mintNonce, normalizeFreshCallInputs, OMP_BOOTSTRAP_FLAG, OMP_BOOTSTRAP_VERSION, TASK_MAX_CHARS, } from "./fresh-call-composition.js";
|
|
93
94
|
import { assertLaunchTarget, LaunchPreconditionError, resolveRuntimeOnPath, } from "./mux-launch.js";
|
|
94
95
|
import { APPEND_FORMAT, assertSelector, assertTmuxOk, inspectPlacement, parseWindowFields, requireSameContext, runTmux, } from "./mux-placement.js";
|
|
95
96
|
import { resolveCodexDefaultSocketPath } from "./native-push/codex-ws-client.js";
|
|
@@ -106,7 +107,7 @@ import { classifyTmuxSessionName, resolveTmuxSessionId } from "./resolve-tmux-se
|
|
|
106
107
|
* says a sibling was opened in the operator's tmux session, and the Codex socket path. Both are
|
|
107
108
|
* arguments to the leaf rather than knowledge inside it.
|
|
108
109
|
*/
|
|
109
|
-
export { buildOmpBootstrapPayload, FRESH_CALL_BACKENDS, FRESH_CALL_CALLBACK_TOOL, isSafeFreshCallModel, MODEL_MAX_CHARS, mintNonce, OMP_BOOTSTRAP_FLAG, OMP_BOOTSTRAP_VERSION, TASK_MAX_CHARS, };
|
|
110
|
+
export { buildOmpBootstrapPayload, FRESH_CALL_BACKENDS, FRESH_CALL_CALLBACK_TOOL, FRESH_CALL_DELIVERY_TOOL, isSafeFreshCallModel, MODEL_MAX_CHARS, mintNonce, OMP_BOOTSTRAP_FLAG, OMP_BOOTSTRAP_VERSION, TASK_MAX_CHARS, };
|
|
110
111
|
/** What the tmux rail tells a sibling about where it woke up. The resume verb has no equivalent
|
|
111
112
|
* because it composes no first turn at all; a herdr rail will pass its own sentence here. */
|
|
112
113
|
export const TMUX_FRESH_CALL_OPENING_LINE = "You are a fresh visible citizen that entwurf opened in the operator's tmux session.";
|
|
@@ -200,9 +201,11 @@ export function selectFreshCallSeat(placement) {
|
|
|
200
201
|
* scrub only on the backend whose measurement surfaced it would encode the claim that the other
|
|
201
202
|
* four are immune, which is false. It costs the legitimate case nothing: a carrier is only ever
|
|
202
203
|
* authoritative when the process that owns it exported it ITSELF, and a fresh `pi` sibling does
|
|
203
|
-
* exactly that after this argv has run. This is a fixed
|
|
204
|
-
*
|
|
205
|
-
*
|
|
204
|
+
* exactly that after this argv has run. This is a fixed seam and deliberately NOT a general env
|
|
205
|
+
* carrier — an arbitrary `-e` passthrough would hand callers the environment-shaping power this
|
|
206
|
+
* rail exists to refuse. Two more `-e` assignments ride beside the scrub: the callback pair
|
|
207
|
+
* (`callback-env.ts`), launcher-computed, so the sibling's first action does not retype an
|
|
208
|
+
* address out of prose.
|
|
206
209
|
*/
|
|
207
210
|
const SCRUBBED_INHERITED_ENV = ["PI_SESSION_ID=", "PI_AGENT_ID="];
|
|
208
211
|
/** Launch argv: the leaf's detached-append shape, the identity scrub, optionally `-c` at the
|
|
@@ -215,7 +218,7 @@ const SCRUBBED_INHERITED_ENV = ["PI_SESSION_ID=", "PI_AGENT_ID="];
|
|
|
215
218
|
* name never reaches this function — the seat is resolved to a native `$id` before it is
|
|
216
219
|
* called, and `assertSelector` refuses anything that is not one. `-d` is not optional: without
|
|
217
220
|
* it a window opened into another session steals that session's focus (measured). */
|
|
218
|
-
export function buildFreshCallArgs(targetSessionId, runtimePath, backendArgs, cwd) {
|
|
221
|
+
export function buildFreshCallArgs(targetSessionId, runtimePath, backendArgs, cwd, callback) {
|
|
219
222
|
assertSelector("session", targetSessionId);
|
|
220
223
|
assertLaunchTarget(runtimePath);
|
|
221
224
|
if (cwd !== undefined) {
|
|
@@ -228,6 +231,7 @@ export function buildFreshCallArgs(targetSessionId, runtimePath, backendArgs, cw
|
|
|
228
231
|
"-d",
|
|
229
232
|
"-a",
|
|
230
233
|
...SCRUBBED_INHERITED_ENV.flatMap((assignment) => ["-e", assignment]),
|
|
234
|
+
...callbackEnvAssignments(callback).flatMap((assignment) => ["-e", assignment]),
|
|
231
235
|
"-t",
|
|
232
236
|
`${targetSessionId}:{end}`,
|
|
233
237
|
...(cwd === undefined ? [] : ["-c", cwd]),
|
|
@@ -403,7 +407,10 @@ export function freshCall(params, env = process.env, nonce = mintNonce()) {
|
|
|
403
407
|
console.error(`[fresh-call] ${unanswered}: ${launchCwd}\n` + ` ${CODEX_LAUNCH_CWD_PREFLIGHT_HINT[unanswered]}`);
|
|
404
408
|
}
|
|
405
409
|
}
|
|
406
|
-
const run = runTmux(buildFreshCallArgs(targetSessionId, runtimePath, backendArgs, cwd
|
|
410
|
+
const run = runTmux(buildFreshCallArgs(targetSessionId, runtimePath, backendArgs, cwd, {
|
|
411
|
+
target: callerGardenId,
|
|
412
|
+
nonce,
|
|
413
|
+
}), env);
|
|
407
414
|
assertTmuxOk("new-window", run);
|
|
408
415
|
let fields;
|
|
409
416
|
try {
|
|
@@ -503,9 +510,11 @@ export function renderFreshCall(result) {
|
|
|
503
510
|
`\n` +
|
|
504
511
|
`This is a LAUNCH receipt: tmux created that window and was asked to start the runtime with the model above. It does ` +
|
|
505
512
|
`NOT mean the sibling is running, that its first turn ran, or that the task was delivered.\n` +
|
|
506
|
-
`The sibling's garden id arrives separately —
|
|
507
|
-
`
|
|
508
|
-
`
|
|
513
|
+
`The sibling's garden id arrives separately — its first action is the zero-argument callback verb ` +
|
|
514
|
+
`(codex is the one arm that still sends the nonce as arguments), and the sender envelope of THAT ` +
|
|
515
|
+
`message is the address. The nonce above is the correlation tag it carries; the sibling reads it ` +
|
|
516
|
+
`from its own launch env, never from this receipt. Nothing is polling for it; if it never comes, ` +
|
|
517
|
+
`the window is visible and can be read directly.`,
|
|
509
518
|
isError: false,
|
|
510
519
|
};
|
|
511
520
|
}
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
*/
|
|
78
78
|
import * as fs from "node:fs";
|
|
79
79
|
import * as path from "node:path";
|
|
80
|
-
import { isPlausibleOwnerPid, ompMetaRootBase, ompMetaRoots,
|
|
80
|
+
import { isPlausibleOwnerPid, ompMetaRootBase, ompMetaRoots, upsertMetaSession, writeMetaSenderMarker, } from "./lib/meta-session.js";
|
|
81
81
|
/**
|
|
82
82
|
* The §3.5 host discriminator. THE one new predicate this whole lane is allowed to
|
|
83
83
|
* introduce, and it is the vendor's own top-level mode — not a heuristic over cwd,
|
|
@@ -147,7 +147,7 @@ export const OMP_BOOTSTRAP_FLAG = "entwurf-bootstrap";
|
|
|
147
147
|
/** Payload grammar version. A payload that does not say exactly this is refused rather than
|
|
148
148
|
* best-guessed: the launcher and this unit ship in the same package, so a mismatch means a
|
|
149
149
|
* STALE installed unit — the one condition `doctor-omp-bridge` exists to name out loud. */
|
|
150
|
-
export const OMP_BOOTSTRAP_VERSION =
|
|
150
|
+
export const OMP_BOOTSTRAP_VERSION = 2;
|
|
151
151
|
/**
|
|
152
152
|
* The model-facing callback tool, spelled for omp. `[측정]` the vendor's minter sanitises to
|
|
153
153
|
* `[a-z_]` and EATS the digit in `entwurf_v2` (`mcp/tool-bridge.ts:384-396`;
|
|
@@ -161,7 +161,7 @@ export const OMP_BOOTSTRAP_VERSION = 1;
|
|
|
161
161
|
* spellings are held equal by `test/omp-fresh-bootstrap.contract.test.ts`, the same shape
|
|
162
162
|
* `check-omp-fresh-preflight` already uses for the preflight's reproduced oracles.
|
|
163
163
|
*/
|
|
164
|
-
export const OMP_BOOTSTRAP_CALLBACK_TOOL = "
|
|
164
|
+
export const OMP_BOOTSTRAP_CALLBACK_TOOL = "mcp__entwurf_bridge_entwurf_callback";
|
|
165
165
|
/** Task ceiling, held equal to the launcher's `TASK_MAX_CHARS` by the same contract test. */
|
|
166
166
|
export const OMP_BOOTSTRAP_TASK_MAX_CHARS = 16000;
|
|
167
167
|
/** How long readiness may take before this bootstrap FAILS and the task is never sent.
|
|
@@ -175,12 +175,10 @@ export const OMP_BOOTSTRAP_READY_TIMEOUT_MS = 60_000;
|
|
|
175
175
|
* MCP ones (`runner.ts:907-978`). Polling the two public snapshots is the smallest honest
|
|
176
176
|
* primitive; a FIXED DELAY is forbidden because the gap is a race, not a constant. */
|
|
177
177
|
export const OMP_BOOTSTRAP_POLL_MS = 100;
|
|
178
|
-
/** The nonce the launcher mints (`mintNonce`: `mux-fresh-call-` + 12 random bytes as hex). */
|
|
179
|
-
const BOOTSTRAP_NONCE_RE = /^mux-fresh-call-[0-9a-f]{24}$/;
|
|
180
178
|
/** The wire keys, closed. An unknown key is a REFUSAL rather than an ignored extra: this
|
|
181
179
|
* flag is the one thing a caller can put arbitrary bytes into, so the decoder's job is to
|
|
182
180
|
* make "what the launcher meant" and "what this unit will act on" the same set. */
|
|
183
|
-
const BOOTSTRAP_KEYS = new Set(["v", "
|
|
181
|
+
const BOOTSTRAP_KEYS = new Set(["v", "task"]);
|
|
184
182
|
/**
|
|
185
183
|
* Read the flag value into a payload, or name why not.
|
|
186
184
|
*
|
|
@@ -212,22 +210,12 @@ export function decodeOmpBootstrapPayload(raw) {
|
|
|
212
210
|
if (!BOOTSTRAP_KEYS.has(key))
|
|
213
211
|
return { ok: false, reason: "payload-unknown-key" };
|
|
214
212
|
}
|
|
215
|
-
let target;
|
|
216
|
-
try {
|
|
217
|
-
target = requireGardenId(obj.target);
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
return { ok: false, reason: "target-invalid" };
|
|
221
|
-
}
|
|
222
|
-
const nonce = obj.nonce;
|
|
223
|
-
if (typeof nonce !== "string" || !BOOTSTRAP_NONCE_RE.test(nonce))
|
|
224
|
-
return { ok: false, reason: "nonce-invalid" };
|
|
225
213
|
const task = obj.task;
|
|
226
214
|
if (typeof task !== "string" || task.trim().length === 0)
|
|
227
215
|
return { ok: false, reason: "task-empty" };
|
|
228
216
|
if (task.length > OMP_BOOTSTRAP_TASK_MAX_CHARS)
|
|
229
217
|
return { ok: false, reason: "task-too-long" };
|
|
230
|
-
return { ok: true, value: {
|
|
218
|
+
return { ok: true, value: { task } };
|
|
231
219
|
}
|
|
232
220
|
/**
|
|
233
221
|
* Stage one's whole message: call back, and nothing else.
|
|
@@ -238,18 +226,13 @@ export function decodeOmpBootstrapPayload(raw) {
|
|
|
238
226
|
* no competing goal at all. Stage two exists so this message never has to compete with the
|
|
239
227
|
* work; adding "then do X" back into it would rebuild exactly the prompt that failed.
|
|
240
228
|
*/
|
|
241
|
-
export function buildOmpCallbackOnlyPrompt(
|
|
229
|
+
export function buildOmpCallbackOnlyPrompt() {
|
|
242
230
|
return [
|
|
243
231
|
"You are a fresh visible citizen that entwurf opened in the operator's tmux session.",
|
|
244
232
|
"",
|
|
245
|
-
`FIRST AND ONLY ACTION RIGHT NOW: call ${OMP_BOOTSTRAP_CALLBACK_TOOL} with
|
|
246
|
-
`target=${params.target}, intent=fire-and-forget, wants_reply=false, and ` +
|
|
247
|
-
`message set to exactly ${params.nonce} — that string alone, nothing added.`,
|
|
233
|
+
`FIRST AND ONLY ACTION RIGHT NOW: call ${OMP_BOOTSTRAP_CALLBACK_TOOL} with no arguments.`,
|
|
248
234
|
"That call is how the agent that opened you learns your address. Make the call before",
|
|
249
|
-
"reading files, before planning, and before answering in prose.
|
|
250
|
-
"",
|
|
251
|
-
"Do not inspect environment variables, do not call entwurf_self, and do not start an MCP",
|
|
252
|
-
"server yourself. Your own report of your identity is not the address anyone needs.",
|
|
235
|
+
"reading files, before planning, and before answering in prose. There are no parameters.",
|
|
253
236
|
"",
|
|
254
237
|
"Your actual task arrives as the NEXT user message, immediately after that call succeeds.",
|
|
255
238
|
"Do not ask for it and do not guess at it.",
|
|
@@ -521,22 +504,32 @@ export function ompCallbackToolReady(pi) {
|
|
|
521
504
|
return registered && active.includes(OMP_BOOTSTRAP_CALLBACK_TOOL);
|
|
522
505
|
}
|
|
523
506
|
/**
|
|
524
|
-
* Does this tool event name OUR callback
|
|
507
|
+
* Does this tool event name OUR callback?
|
|
525
508
|
*
|
|
526
509
|
* The event carries no server name, no session id and no original MCP tool name
|
|
527
|
-
* (`extensions/types.ts:916-1017`), so the match is built from what IS there:
|
|
528
|
-
* minted tool name
|
|
529
|
-
*
|
|
530
|
-
*
|
|
510
|
+
* (`extensions/types.ts:916-1017`), so the match is built from what IS there: exact string
|
|
511
|
+
* equality on the canonical minted tool name — a prefix or `includes` here would let a
|
|
512
|
+
* neighbouring tool release this session's task.
|
|
513
|
+
*
|
|
514
|
+
* The target/nonce half of this predicate is GONE, and that is the point of the out-of-band
|
|
515
|
+
* callback: the address rides the sibling's process env, so the verb takes no arguments and
|
|
516
|
+
* there are no argument values left to compare. What replaces the comparison is the ABSENCE
|
|
517
|
+
* of arguments — a call carrying a target or a message is a model supplying an address, which
|
|
518
|
+
* is the second address axis Hard Rule 2 refuses, and it must not release the task. Correlation
|
|
519
|
+
* to THIS bootstrap is not weakened by the removal: it is carried by the stored `toolCallId`
|
|
520
|
+
* (one session, one live tool call) plus the fact that only this session's env named the caller.
|
|
531
521
|
*/
|
|
532
|
-
function matchesCallback(event,
|
|
522
|
+
function matchesCallback(event, _payload) {
|
|
533
523
|
if (event?.toolName !== OMP_BOOTSTRAP_CALLBACK_TOOL)
|
|
534
524
|
return false;
|
|
535
525
|
const input = event?.input;
|
|
536
|
-
if (
|
|
526
|
+
if (input === undefined || input === null)
|
|
527
|
+
return true;
|
|
528
|
+
if (typeof input !== "object" || Array.isArray(input))
|
|
537
529
|
return false;
|
|
538
530
|
const args = input;
|
|
539
|
-
|
|
531
|
+
// No-arg verb: a supplied target/message is a second address axis — not our callback.
|
|
532
|
+
return args.target === undefined && args.message === undefined;
|
|
540
533
|
}
|
|
541
534
|
/**
|
|
542
535
|
* Create one bootstrap for one minted session.
|
|
@@ -580,7 +573,7 @@ export function createOmpBootstrap(opts) {
|
|
|
580
573
|
if (isFinal())
|
|
581
574
|
return;
|
|
582
575
|
phase = "failed";
|
|
583
|
-
log("WARN", `bootstrap-failed
|
|
576
|
+
log("WARN", `bootstrap-failed: ${why}; the task was NOT sent`);
|
|
584
577
|
}
|
|
585
578
|
/**
|
|
586
579
|
* Both stages send the SAME way: `pi.sendUserMessage(content)` with NO delivery option.
|
|
@@ -616,8 +609,8 @@ export function createOmpBootstrap(opts) {
|
|
|
616
609
|
// two callback prompts for one bootstrap.
|
|
617
610
|
phase = "callback-sent";
|
|
618
611
|
stopTimer();
|
|
619
|
-
log("INFO",
|
|
620
|
-
send(buildOmpCallbackOnlyPrompt(
|
|
612
|
+
log("INFO", "bootstrap-ready: callback tool live, sending callback-only prompt");
|
|
613
|
+
send(buildOmpCallbackOnlyPrompt());
|
|
621
614
|
return;
|
|
622
615
|
}
|
|
623
616
|
if (timers.now() >= deadline) {
|
|
@@ -654,7 +647,7 @@ export function createOmpBootstrap(opts) {
|
|
|
654
647
|
if (typeof id !== "string" || id.length === 0)
|
|
655
648
|
return;
|
|
656
649
|
callId = id;
|
|
657
|
-
log("INFO", `bootstrap-callback-observed
|
|
650
|
+
log("INFO", `bootstrap-callback-observed toolCallId=${id}`);
|
|
658
651
|
},
|
|
659
652
|
onToolResult(event) {
|
|
660
653
|
if (phase !== "callback-sent" || callId === null)
|
|
@@ -664,7 +657,7 @@ export function createOmpBootstrap(opts) {
|
|
|
664
657
|
if (!matchesCallback(event, payload))
|
|
665
658
|
return;
|
|
666
659
|
if (event?.isError !== false) {
|
|
667
|
-
log("WARN", `bootstrap-callback-errored
|
|
660
|
+
log("WARN", `bootstrap-callback-errored toolCallId=${callId}; the task was NOT sent`);
|
|
668
661
|
return;
|
|
669
662
|
}
|
|
670
663
|
// RELEASED, AND THE TASK IS NOT SENT HERE. This handler runs inside the callback
|
|
@@ -673,7 +666,7 @@ export function createOmpBootstrap(opts) {
|
|
|
673
666
|
// record that the callback provably succeeded; `onTurnEnd` owns the send.
|
|
674
667
|
phase = "released";
|
|
675
668
|
stopTimer();
|
|
676
|
-
log("INFO", `bootstrap-released
|
|
669
|
+
log("INFO", `bootstrap-released toolCallId=${callId}: task armed for the next turn_end`);
|
|
677
670
|
},
|
|
678
671
|
/**
|
|
679
672
|
* The stage-two boundary: the callback turn is over, so the session is at the edge
|
|
@@ -695,7 +688,7 @@ export function createOmpBootstrap(opts) {
|
|
|
695
688
|
if (phase !== "released")
|
|
696
689
|
return;
|
|
697
690
|
phase = "task-sent";
|
|
698
|
-
log("INFO",
|
|
691
|
+
log("INFO", "bootstrap-task-sent: delivering the task at the turn_end boundary");
|
|
699
692
|
send(payload.task);
|
|
700
693
|
},
|
|
701
694
|
invalidate(why) {
|
|
@@ -706,7 +699,7 @@ export function createOmpBootstrap(opts) {
|
|
|
706
699
|
if (isFinal())
|
|
707
700
|
return;
|
|
708
701
|
phase = "failed";
|
|
709
|
-
log("INFO", `bootstrap-invalidated
|
|
702
|
+
log("INFO", `bootstrap-invalidated: ${why}; the task was NOT sent`);
|
|
710
703
|
},
|
|
711
704
|
};
|
|
712
705
|
}
|
|
@@ -869,7 +862,7 @@ export function startOmpBootstrap(opts) {
|
|
|
869
862
|
const handle = createOmpBootstrap({ payload: decoded.value, pi, timers, log });
|
|
870
863
|
activeBootstrap = { sessionId: envelope.nativeSessionId, handle };
|
|
871
864
|
bootstrapConsumed = true;
|
|
872
|
-
log("INFO",
|
|
865
|
+
log("INFO", "bootstrap-armed: payload admitted, waiting for callback tool");
|
|
873
866
|
handle.start();
|
|
874
867
|
}
|
|
875
868
|
/**
|
|
@@ -20,10 +20,3 @@
|
|
|
20
20
|
* AGENTS.md section from its own pi-context augment.
|
|
21
21
|
*/
|
|
22
22
|
export const ENTWURF_PROJECT_CONTEXT_OPEN_TAG = "<project-context";
|
|
23
|
-
/**
|
|
24
|
-
* Sender-side UI echo custom message type for successful entwurf_v2 send calls.
|
|
25
|
-
* The bridge emits it, the control renderer draws it, and the provider-level
|
|
26
|
-
* context filter removes it before LLM conversion. Keep as one SSOT: a typo
|
|
27
|
-
* here otherwise becomes a silent UI/context split.
|
|
28
|
-
*/
|
|
29
|
-
export const ENTWURF_SENT_MESSAGE_TYPE = "entwurf-sent";
|
|
@@ -143,11 +143,24 @@ log(` project ${projectPath}: ${projScopeDesc}`);
|
|
|
143
143
|
log(` EFFECTIVE (${effectiveScope}): ${effectiveDesc}`);
|
|
144
144
|
// install-state ownership (user scope). absent state on a configured effective is either a
|
|
145
145
|
// pre-Task-2 install or a user-override we deliberately did not own.
|
|
146
|
+
//
|
|
147
|
+
// `command` is read beside it because it — not the bare name — is the CANONICAL managed command
|
|
148
|
+
// (#116 M3-b2). `register-pi-provider.py` writes the same string into the settings entry and into
|
|
149
|
+
// this record (:332-342, :368-369), and in plugin mode that string is an absolute bridge under the
|
|
150
|
+
// certified stable runtime, because a Herdr-plugin host has nothing entwurf on PATH. Reading it
|
|
151
|
+
// here is what lets this doctor judge drift without re-deriving the stable runtime root: the root
|
|
152
|
+
// is the writer's business, and a second derivation in this file would be a second address axis.
|
|
153
|
+
//
|
|
154
|
+
// `ownership` is the PREIMAGE classification — what stood at the key BEFORE that install — so
|
|
155
|
+
// `absent` means "we created it", not "nobody owns it". Treating it as an ownership verdict is what
|
|
156
|
+
// made every plugin-mode host red while its bridge booted and served all eight verbs.
|
|
146
157
|
let ownership;
|
|
158
|
+
let stateCommand;
|
|
147
159
|
if (existsSync(statePath)) {
|
|
148
160
|
try {
|
|
149
161
|
const st = JSON.parse(readFileSync(statePath, "utf8"));
|
|
150
162
|
ownership = typeof st.ownership === "string" ? st.ownership : undefined;
|
|
163
|
+
stateCommand = typeof st.command === "string" ? st.command : undefined;
|
|
151
164
|
log(` state: install-state present (ownership=${ownership}).`);
|
|
152
165
|
}
|
|
153
166
|
catch {
|
|
@@ -200,9 +213,33 @@ else {
|
|
|
200
213
|
hardFail = 1;
|
|
201
214
|
}
|
|
202
215
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
// Ownership classification never rounds a broken runtime up to green. It only says who may
|
|
217
|
+
// repair the entry after the independent boot verdict above.
|
|
218
|
+
//
|
|
219
|
+
// A MANAGED record is the pair (known preimage ownership, recorded command). For that pair the
|
|
220
|
+
// recorded command is canonical and the comparison runs REGARDLESS of whether the effective
|
|
221
|
+
// command happens to be the bare name. Keeping this decision inside a `!isBare` branch is how
|
|
222
|
+
// real drift escaped: a record naming the absolute plugin bridge, with the settings repointed to
|
|
223
|
+
// the bare `entwurf-bridge`, is drift — and a bare bridge that boots would otherwise carry it to
|
|
224
|
+
// green. "The effective command is spellable as our old default" is not evidence that it is the
|
|
225
|
+
// command this install wrote.
|
|
226
|
+
//
|
|
227
|
+
// Everything else — a record that names no command, or one whose ownership is a word this
|
|
228
|
+
// doctor does not know — falls through to the ORIGINAL logic below, unchanged. Repairing this
|
|
229
|
+
// defect is not a licence to migrate the state schema, to grant an amnesty to records written
|
|
230
|
+
// before `command` existed, or to let an unknown ownership word buy a managed green because a
|
|
231
|
+
// string happened to match. The state is rewritten by `./run.sh setup`, not by its reader.
|
|
232
|
+
const knownManagedOwnership = ownership === "absent" || ownership === "managed-current" || ownership === "managed-legacy";
|
|
233
|
+
if (knownManagedOwnership && stateCommand !== undefined) {
|
|
234
|
+
if (cmd !== stateCommand) {
|
|
235
|
+
log(` FAIL: state records '${stateCommand}' as the managed command (ownership=${ownership}), but the effective command is '${cmd}' — the key drifted to something entwurf never wrote.`);
|
|
236
|
+
hardFail = 1;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
log(` ok: effective command is the managed command this install recorded ('${stateCommand}', preimage ownership=${ownership}).`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (!isBare) {
|
|
206
243
|
if (ownership && ownership !== "user-override") {
|
|
207
244
|
log(` FAIL: state owns entwurf-bridge (ownership=${ownership}) but the effective command drifted to '${cmd}'.`);
|
|
208
245
|
hardFail = 1;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* herdr-rails — the ONE place the herdr rail's backend set and its per-backend model SYNTAX
|
|
3
|
+
* example are written down (#116 A7).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. Three surfaces were about to say the same two things in three
|
|
6
|
+
* dialects: `herdr-fresh-call.ts` owned the closed backend tuple, `build.mjs` narrated one
|
|
7
|
+
* model id per harness at install time, and the status pane was about to print a RAILS block.
|
|
8
|
+
* Three copies of one fact is how a rail quietly grows a fourth backend in prose that no
|
|
9
|
+
* dispatch honours. So the fact lives here and the three read it.
|
|
10
|
+
*
|
|
11
|
+
* WHY `.mjs` AND NOT `.ts`. It is read by two runtimes that cannot agree on anything else:
|
|
12
|
+
* the Herdr plugin runs `node lib/status.mjs` with no loader flags at all, and the pi
|
|
13
|
+
* extension is typechecked by `tsc`. A `.ts` leaf would be unloadable by the first; a second
|
|
14
|
+
* copy would defeat the point. `protocol.js` set this precedent for exactly the same reason
|
|
15
|
+
* (root tsconfig.json `allowJs`), and this file keeps its shape: dependency-free, no imports,
|
|
16
|
+
* no environment, no IO, so importing it can never be a side effect.
|
|
17
|
+
*
|
|
18
|
+
* WHAT THIS FILE IS NOT, and each absence is load-bearing:
|
|
19
|
+
* - NOT a model catalogue. One example per backend, chosen to show the SHAPE of the string
|
|
20
|
+
* (`provider/model` vs a bare vendor id). Adding a second example starts a list, a list
|
|
21
|
+
* invites completeness, and completeness is a catalogue Entwurf would then be expected to
|
|
22
|
+
* keep current for every vendor. `entwurf_fresh_call` takes a model string with no default
|
|
23
|
+
* and validates nothing; that is the contract, and an example must not imply otherwise.
|
|
24
|
+
* - NOT a supported-model set, a recommendation, or a validator. Nothing reads MODEL_SYNTAX_EXAMPLE
|
|
25
|
+
* to decide anything — it is display text on three surfaces.
|
|
26
|
+
* - NOT a provider, billing or quota statement. No rail here names a provider, a subscription
|
|
27
|
+
* or a cost. `#76` is about which rail a sibling may be launched on, and a syntax example
|
|
28
|
+
* that mentioned a provider would read as an answer to it. It does not answer it.
|
|
29
|
+
* - NOT a launcher, and not a claim that any binary exists. `HERDR_AGENT_KIND` (the herdr
|
|
30
|
+
* `--kind` token) stays with the rail that spends it; this leaf carries no argv.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The closed pilot set for the herdr launch rail. Herdr's own `--kind` enum is much larger and
|
|
34
|
+
* that is not evidence of support: every other backend is a named pre-mutation reject.
|
|
35
|
+
*
|
|
36
|
+
* The JSDoc cast is what keeps the TYPE half of this single source honest. Without it TypeScript
|
|
37
|
+
* infers `readonly string[]` from a `.mjs` file and `HerdrFreshCallBackend` silently widens to
|
|
38
|
+
* `string` — the tuple would still be one source at runtime while the compiler had stopped
|
|
39
|
+
* checking anything. A gate pins both halves.
|
|
40
|
+
*
|
|
41
|
+
* @type {readonly ["pi", "claude-code"]}
|
|
42
|
+
*/
|
|
43
|
+
export const HERDR_FRESH_CALL_BACKENDS = Object.freeze(["pi", "claude-code"]);
|
|
44
|
+
/**
|
|
45
|
+
* ONE model string per backend, and it is an example of SYNTAX, never of choice.
|
|
46
|
+
*
|
|
47
|
+
* `[관측: GLG, 날것 PC, 2026-09-17]` "일단 정확한 모델명을 모른다" — after a green install the first
|
|
48
|
+
* thing missing is a call that works, and the two runtimes spell a model differently enough that
|
|
49
|
+
* guessing fails: pi wants a provider-qualified path, Claude Code wants the vendor id alone.
|
|
50
|
+
* That difference is the whole content of this map.
|
|
51
|
+
*
|
|
52
|
+
* @type {Readonly<Record<"pi" | "claude-code", string>>}
|
|
53
|
+
*/
|
|
54
|
+
export const MODEL_SYNTAX_EXAMPLE = Object.freeze({
|
|
55
|
+
pi: "openai-codex/gpt-5.6-terra",
|
|
56
|
+
"claude-code": "claude-sonnet-5",
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* How a citizen on each backend is REACHED once it exists, in the operator's words. This is the
|
|
60
|
+
* rail fact the status pane's RAILS block renders and the install narration states — the same
|
|
61
|
+
* sentence, so a host cannot be told two different things about one backend.
|
|
62
|
+
*
|
|
63
|
+
* `[관측: GLG, 날것 PC, 2026-09-17]` from the operator's chair a plain `pi` looks identical to a
|
|
64
|
+
* citizen pi: the install says green, `pi` starts, and nothing is there. Citizenship is argv-gated
|
|
65
|
+
* on purpose, and that gate is invisible unless something says it out loud.
|
|
66
|
+
*
|
|
67
|
+
* @type {Readonly<Record<"pi" | "claude-code", string>>}
|
|
68
|
+
*/
|
|
69
|
+
export const RAIL_ENTRY_NOTE = Object.freeze({
|
|
70
|
+
pi: "start it as `pi --entwurf-control` to be a garden citizen — a plain `pi` loads the extension but has no garden id, no control socket and no entwurf tools",
|
|
71
|
+
"claude-code": "nothing to add — an ordinary `claude` picks up the entwurf tools through MCP",
|
|
72
|
+
});
|
|
@@ -42,6 +42,7 @@ export const EXPECTED_TOOLS = [
|
|
|
42
42
|
"entwurf_register_native",
|
|
43
43
|
"entwurf_fresh_call",
|
|
44
44
|
"entwurf_resume_call",
|
|
45
|
+
"entwurf_callback",
|
|
45
46
|
];
|
|
46
47
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
47
48
|
/** How long a probed child gets to die politely before it is killed outright. */
|
|
@@ -34,8 +34,13 @@
|
|
|
34
34
|
* - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id in a visible
|
|
35
35
|
* window; target-only, runs no turn, LAUNCH and OBSERVATION receipts stay
|
|
36
36
|
* apart.
|
|
37
|
+
* - entwurf_callback — ZERO-ARGUMENT fresh-sibling callback. Reads ENTWURF_CALLBACK_TARGET +
|
|
38
|
+
* ENTWURF_CALLBACK_NONCE from this process env (launcher-injected), validates
|
|
39
|
+
* grammar, and dispatches through the existing v2 runner. Refuses by name
|
|
40
|
+
* when env is absent/malformed or this process is a Codex-provenance bridge.
|
|
41
|
+
* No model-supplied target fallback.
|
|
37
42
|
*
|
|
38
|
-
* That list is the WHOLE public surface —
|
|
43
|
+
* That list is the WHOLE public surface — eight verbs — and `check-entwurf-bridge-boot`
|
|
39
44
|
* (G1f) holds it as an exact set on the runtime tools/list, so a verb added or dropped
|
|
40
45
|
* here without a decision is red rather than merely undocumented.
|
|
41
46
|
*
|
|
@@ -65,6 +70,7 @@ import * as process from "node:process";
|
|
|
65
70
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
66
71
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
67
72
|
import { z } from "zod";
|
|
73
|
+
import { readCallbackEnv } from "../../../pi-extensions/lib/callback-env.ts";
|
|
68
74
|
import { controlSocketPathIn, defaultControlSocketDir } from "../../../pi-extensions/lib/control-socket-path.js";
|
|
69
75
|
import { resolveMailboxReceiverFacts } from "../../../pi-extensions/lib/entwurf-deliverability.ts";
|
|
70
76
|
import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.ts";
|
|
@@ -425,7 +431,9 @@ server.tool(
|
|
|
425
431
|
"alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport " +
|
|
426
432
|
"from liveness (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge " +
|
|
427
433
|
"mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE " +
|
|
428
|
-
"outcome
|
|
434
|
+
"outcome, named per rail: a control receiver's acceptance boundary (`sent` / `queued-steer` / " +
|
|
435
|
+
"`queued-follow-up` / `accepted-unknown-boundary` — those four only), a mailbox ENQUEUE receipt, a " +
|
|
436
|
+
"native-push INJECTION, a rejection, or lock-dirty. EXISTING targets only; discover with " +
|
|
429
437
|
"entwurf_peers. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A " +
|
|
430
438
|
"citizen with NO socket liveness (liveness=unsupported) is ALSO fire-and-forget — unsupported means only " +
|
|
431
439
|
'"no control-socket probe." The decider resolves the actual rail at dispatch time: a self-fetch ' +
|
|
@@ -466,9 +474,8 @@ server.tool(
|
|
|
466
474
|
.enum(["steer", "follow_up"])
|
|
467
475
|
.optional()
|
|
468
476
|
.describe(
|
|
469
|
-
"Injection style for a CONTROL-SOCKET send only: steer (
|
|
470
|
-
"
|
|
471
|
-
"effect on those rails.",
|
|
477
|
+
"Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. " +
|
|
478
|
+
"The mailbox and native-push plans carry no mode, so it has no effect on those rails.",
|
|
472
479
|
),
|
|
473
480
|
wants_reply: z.boolean().optional().describe("Human-conversation reply hint (default false)"),
|
|
474
481
|
},
|
|
@@ -877,6 +884,40 @@ server.tool(
|
|
|
877
884
|
},
|
|
878
885
|
);
|
|
879
886
|
|
|
887
|
+
server.tool(
|
|
888
|
+
"entwurf_callback",
|
|
889
|
+
"ZERO-ARGUMENT callback for a fresh sibling this process was launched as. Reads " +
|
|
890
|
+
"ENTWURF_CALLBACK_TARGET and ENTWURF_CALLBACK_NONCE from this process environment " +
|
|
891
|
+
"(injected by the launcher next to the identity scrub), validates garden-id and nonce " +
|
|
892
|
+
"grammar, and dispatches through the existing v2 runner with intent fire-and-forget, " +
|
|
893
|
+
"message=nonce, wants_reply=false. The target is re-resolved by record/decider — env " +
|
|
894
|
+
"names the id, it does not skip dispatch. REFUSES BY NAME when the pair is absent, " +
|
|
895
|
+
"malformed, or this process is a Codex-provenance bridge (window env never reaches that " +
|
|
896
|
+
"MCP child). No arguments, no fallback to a model-supplied target.",
|
|
897
|
+
{},
|
|
898
|
+
async (_args, extra) => {
|
|
899
|
+
const read = readCallbackEnv(process.env);
|
|
900
|
+
if (!read.ok) {
|
|
901
|
+
return textErr(`entwurf_callback: ${read.reason}`);
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
const sender = await buildSendSenderEnvelope({ requestMeta: extra._meta });
|
|
905
|
+
const rendered = await runAndRenderEntwurfV2FromSurface(
|
|
906
|
+
{
|
|
907
|
+
target: read.target,
|
|
908
|
+
intent: "fire-and-forget",
|
|
909
|
+
message: read.nonce,
|
|
910
|
+
wants_reply: false,
|
|
911
|
+
},
|
|
912
|
+
{ senderProvider: () => sender },
|
|
913
|
+
);
|
|
914
|
+
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
915
|
+
} catch (err) {
|
|
916
|
+
return textErr(`entwurf_callback error: ${err instanceof Error ? err.message : String(err)}`);
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
);
|
|
920
|
+
|
|
880
921
|
// ============================================================================
|
|
881
922
|
// Main
|
|
882
923
|
// ============================================================================
|
|
@@ -55,7 +55,7 @@ names=set(raw)
|
|
|
55
55
|
# was an artifact-surface hole: a bundle missing entwurf_fresh_call / entwurf_resume_call
|
|
56
56
|
# answered tools/list and passed. Equality also refuses an undecided extra verb; the
|
|
57
57
|
# duplicate arm catches a name registered twice, which set membership cannot see.
|
|
58
|
-
expected={'entwurf_v2','entwurf_self','entwurf_peers','entwurf_inbox_read','entwurf_register_native','entwurf_fresh_call','entwurf_resume_call'}
|
|
58
|
+
expected={'entwurf_v2','entwurf_self','entwurf_peers','entwurf_inbox_read','entwurf_register_native','entwurf_fresh_call','entwurf_resume_call','entwurf_callback'}
|
|
59
59
|
legacy={'entwurf','entwurf_resume','entwurf_send'} & names
|
|
60
60
|
if len(raw)!=len(names):
|
|
61
61
|
raise SystemExit(f"duplicate tool registrations: {sorted(raw)}")
|
|
@@ -66,7 +66,7 @@ if legacy:
|
|
|
66
66
|
if names!=expected:
|
|
67
67
|
raise SystemExit(f"tool set MISMATCH — missing {sorted(expected-names)}, unexpected {sorted(names-expected)}")
|
|
68
68
|
PY
|
|
69
|
-
ok "public tool surface is EXACTLY the
|
|
69
|
+
ok "public tool surface is EXACTLY the eight garden verbs (no v1 verb, no undecided extra, no duplicate)"
|
|
70
70
|
|
|
71
71
|
SELF_JSON="$(printf '%s\n' "$OUT" | grep '"id":3' | tail -1)"
|
|
72
72
|
[[ "$SELF_JSON" == *"isError"* ]] || fail "entwurf_self without identity should be an error: $SELF_JSON"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Garden-citizen dispatch substrate and meta-bridge for pi, Claude Code, Copilot CLI, OMP, Antigravity, and Codex CLI; Claude and Snowflake Cortex Code ACP backends.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"pi/skill-plugin-example/",
|
|
68
68
|
"run.sh",
|
|
69
69
|
"AGENTS.md",
|
|
70
|
+
"FAQ.md",
|
|
70
71
|
"BASELINE.md",
|
|
71
72
|
"VERIFY.md",
|
|
72
73
|
"DELIVERY.md",
|
|
@@ -91,16 +92,17 @@
|
|
|
91
92
|
"zod": "^3.25.0 || ^4.0.0"
|
|
92
93
|
},
|
|
93
94
|
"peerDependencies": {
|
|
94
|
-
"@earendil-works/pi-ai": ">=0.
|
|
95
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
96
|
-
"@earendil-works/pi-tui": ">=0.
|
|
95
|
+
"@earendil-works/pi-ai": ">=0.87.1 <0.88",
|
|
96
|
+
"@earendil-works/pi-coding-agent": ">=0.87.1 <0.88",
|
|
97
|
+
"@earendil-works/pi-tui": ">=0.87.1 <0.88",
|
|
97
98
|
"typebox": "*"
|
|
98
99
|
},
|
|
99
100
|
"devDependencies": {
|
|
100
101
|
"@biomejs/biome": "^2.4.13",
|
|
101
|
-
"@earendil-works/pi-
|
|
102
|
-
"@earendil-works/pi-
|
|
103
|
-
"@earendil-works/pi-
|
|
102
|
+
"@earendil-works/pi-agent-core": "0.87.1",
|
|
103
|
+
"@earendil-works/pi-ai": "0.87.1",
|
|
104
|
+
"@earendil-works/pi-coding-agent": "0.87.1",
|
|
105
|
+
"@earendil-works/pi-tui": "0.87.1",
|
|
104
106
|
"@types/node": "^24.3.0",
|
|
105
107
|
"husky": "^9.1.7",
|
|
106
108
|
"rregex": "1.13.1",
|
|
@@ -174,8 +176,8 @@
|
|
|
174
176
|
"check:full": "bash scripts/check-elapsed.sh check:full check:core check:hermetic check:package",
|
|
175
177
|
"check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
|
|
176
178
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
177
|
-
"check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-tests-beside-behavior",
|
|
178
|
-
"check:contracts": "./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-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-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push",
|
|
179
|
+
"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",
|
|
180
|
+
"check:contracts": "./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push",
|
|
179
181
|
"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-codex-birth-hook && ./run.sh smoke-codex-config-state && ./run.sh smoke-codex-birth && ./run.sh check-herdr-sandbox",
|
|
180
182
|
"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-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
|
|
181
183
|
},
|