@junghanacs/entwurf 0.17.2 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -5
- package/CHANGELOG.md +299 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +9 -5
- package/VERIFY.md +3 -3
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +46 -12
- package/docs/setup-clean-host.md +35 -3
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +9 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +11 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +66 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +10 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +8 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +25 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +6 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +38 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +64 -4
- package/mcp/entwurf-bridge/src/index.ts +16 -8
- package/package.json +10 -9
- package/pi-extensions/entwurf-control.ts +12 -2
- package/pi-extensions/lib/acp/acp-client.ts +3 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
- package/pi-extensions/lib/acp/backend.ts +3 -3
- package/pi-extensions/lib/acp/event-mapper.ts +5 -5
- package/pi-extensions/lib/entwurf-deliverability.ts +104 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +20 -6
- package/pi-extensions/lib/entwurf-facts.ts +52 -3
- package/pi-extensions/lib/entwurf-peer-observe.ts +77 -0
- package/pi-extensions/lib/entwurf-peers-render.ts +12 -1
- package/pi-extensions/lib/entwurf-v2-decider.ts +26 -8
- package/pi-extensions/lib/entwurf-v2-production.ts +32 -12
- package/pi-extensions/lib/entwurf-v2-surface.ts +6 -0
- package/pi-extensions/lib/meta-session.ts +44 -0
- package/pi-extensions/meta-bridge-hook.ts +72 -3
- package/run.sh +146 -56
- package/scripts/check-acp-usage-accounting.ts +9 -9
- package/scripts/check-bridge-delivery.ts +42 -1
- package/scripts/check-entwurf-fact-provider.ts +22 -1
- package/scripts/check-entwurf-facts.ts +17 -2
- package/scripts/check-entwurf-peers-surface.ts +20 -2
- package/scripts/check-entwurf-self-address.ts +20 -2
- package/scripts/check-entwurf-v2-decider.ts +6 -3
- package/scripts/check-entwurf-v2-matrix.ts +10 -5
- package/scripts/check-entwurf-v2-production.ts +72 -1
- package/scripts/check-gate-qualification.ts +63 -12
- package/scripts/check-meta-hook-session-switch.ts +560 -0
- package/scripts/check-release-gate-outcomes.ts +160 -3
- package/scripts/meta-bridge-doctor.sh +29 -4
- package/scripts/mutants/bridge-command-boot.json +3 -3
- package/scripts/mutants/gate-qualification.json +28 -0
- package/scripts/mutants/meta-hook-session-switch.json +223 -0
- package/scripts/mutants/pack-install.json +14 -3
- package/scripts/mutants/release-gate.json +22 -0
- package/scripts/raw-acp-compaction-measure/README.md +77 -0
- package/scripts/raw-acp-compaction-measure/probe.ts +196 -0
- package/scripts/raw-claude-session-switch/README.md +170 -0
- package/scripts/smoke-acp-cortex-live.ts +22 -2
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-v2-send-live.ts +23 -2
- package/scripts/smoke-entwurf-chain-live.ts +85 -13
- package/scripts/smoke-entwurf-v2-matrix-live.ts +23 -1
- package/scripts/smoke-mux-fresh-call-live.ts +13 -3
|
@@ -54,7 +54,15 @@ import fsp from "node:fs/promises";
|
|
|
54
54
|
import os from "node:os";
|
|
55
55
|
import path from "node:path";
|
|
56
56
|
import { fileURLToPath } from "node:url";
|
|
57
|
-
import {
|
|
57
|
+
import { resolveMailboxReceiverFacts } from "../pi-extensions/lib/entwurf-deliverability.ts";
|
|
58
|
+
import {
|
|
59
|
+
readMetaInbox,
|
|
60
|
+
readMetaReceiverMarker,
|
|
61
|
+
readMetaSenderMarker,
|
|
62
|
+
upsertMetaSession,
|
|
63
|
+
writeMetaReceiverMarker,
|
|
64
|
+
writeMetaSenderMarker,
|
|
65
|
+
} from "../pi-extensions/lib/meta-session.ts";
|
|
58
66
|
import { terminateChild } from "./lib/acp-child-cleanup.ts";
|
|
59
67
|
import { skipLive } from "./lib/live-skip.ts";
|
|
60
68
|
|
|
@@ -155,11 +163,13 @@ async function main(): Promise<void> {
|
|
|
155
163
|
const sessionsDir = path.join(world, "sessions");
|
|
156
164
|
const mailboxDir = path.join(world, "mailbox");
|
|
157
165
|
const receiversDir = path.join(world, "receivers");
|
|
158
|
-
|
|
166
|
+
const sendersDir = path.join(world, "senders");
|
|
167
|
+
for (const d of [sessionsDir, mailboxDir, receiversDir, sendersDir]) await fsp.mkdir(d, { recursive: true });
|
|
159
168
|
const worldEnv = {
|
|
160
169
|
ENTWURF_META_SESSIONS_DIR: sessionsDir,
|
|
161
170
|
ENTWURF_META_MAILBOX_DIR: mailboxDir,
|
|
162
171
|
ENTWURF_META_RECEIVERS_DIR: receiversDir,
|
|
172
|
+
ENTWURF_META_SENDERS_DIR: sendersDir,
|
|
163
173
|
};
|
|
164
174
|
// The MCP bridge prefers PI_SESSION_ID+PI_AGENT_ID over a meta-sender marker when both
|
|
165
175
|
// are present. A runner that is itself a pi --entwurf-control session (release-gate,
|
|
@@ -183,20 +193,46 @@ async function main(): Promise<void> {
|
|
|
183
193
|
dir: sessionsDir,
|
|
184
194
|
});
|
|
185
195
|
const gidD = terminus.record.gardenId;
|
|
186
|
-
writeMetaReceiverMarker({
|
|
187
|
-
gardenId: gidD,
|
|
188
|
-
backend: "claude-code",
|
|
189
|
-
nativeSessionId: terminus.record.nativeSessionId,
|
|
190
|
-
ownerPid: process.pid,
|
|
191
|
-
armProvenance: "session-start",
|
|
192
|
-
receiversDir,
|
|
193
|
-
});
|
|
194
|
-
console.error(`[smoke-entwurf-chain-live] D: ${gidD} (mailbox terminus)`);
|
|
195
|
-
|
|
196
196
|
const children: ChildProcess[] = [];
|
|
197
|
+
let terminusOwner: ChildProcess | null = null;
|
|
197
198
|
let streamB = "";
|
|
198
199
|
let streamC = "";
|
|
199
200
|
try {
|
|
201
|
+
// The receiver marker alone is no longer the whole fixture (#101): a `claude-code-cli` watch
|
|
202
|
+
// owner is deliverable only while ITS OWN sender marker still names the garden it is armed
|
|
203
|
+
// for — that join is what refuses a session which switched away in place. A receiver marker
|
|
204
|
+
// with no sender marker beside it models a state the product now treats as retired, so this
|
|
205
|
+
// smoke would fail on its own fixture instead of on the rail it exists to prove.
|
|
206
|
+
//
|
|
207
|
+
// THE OWNER IS A SPAWNED IDLE PROCESS, NOT THIS ONE, and that is specific to this smoke:
|
|
208
|
+
// hop 1 is a native `claude` child of THIS process, and the bridge resolves a sender's
|
|
209
|
+
// identity through its ancestry. A terminus marker under this pid therefore puts TWO garden
|
|
210
|
+
// citizens on one host process, which the bridge refuses outright as ambiguous sender
|
|
211
|
+
// identity — measured on oracle 2026-09-04, where hop 1 came back "refused: ambiguous sender
|
|
212
|
+
// identity (…-f34713, …-912b42)" and the chain never started. The sibling smokes keep
|
|
213
|
+
// `process.pid` because their children are pi/ACP sessions carrying their own identity, or
|
|
214
|
+
// tmux siblings that are not descendants at all.
|
|
215
|
+
terminusOwner = spawn(process.execPath, ["-e", "setTimeout(() => {}, 900000)"], { stdio: "ignore" });
|
|
216
|
+
const terminusOwnerPid = terminusOwner.pid;
|
|
217
|
+
if (typeof terminusOwnerPid !== "number") throw new Error("could not spawn the terminus owner process");
|
|
218
|
+
writeMetaReceiverMarker({
|
|
219
|
+
gardenId: gidD,
|
|
220
|
+
backend: "claude-code",
|
|
221
|
+
nativeSessionId: terminus.record.nativeSessionId,
|
|
222
|
+
ownerPid: terminusOwnerPid,
|
|
223
|
+
armProvenance: "session-start",
|
|
224
|
+
receiversDir,
|
|
225
|
+
});
|
|
226
|
+
writeMetaSenderMarker({
|
|
227
|
+
backend: "claude-code",
|
|
228
|
+
gardenId: gidD,
|
|
229
|
+
nativeSessionId: terminus.record.nativeSessionId,
|
|
230
|
+
cwd: world,
|
|
231
|
+
ownerPid: terminusOwnerPid,
|
|
232
|
+
sendersDir,
|
|
233
|
+
});
|
|
234
|
+
console.error(`[smoke-entwurf-chain-live] D: ${gidD} (mailbox terminus)`);
|
|
235
|
+
|
|
200
236
|
// ── C: the ACP Claude Sonnet citizen (spawned first so B can be told its id) ──
|
|
201
237
|
const known = new Set((await readRecords(sessionsDir)).map((r) => r.gardenId));
|
|
202
238
|
const c = spawn(
|
|
@@ -283,6 +319,21 @@ async function main(): Promise<void> {
|
|
|
283
319
|
ok("A is distinct from every other citizen", !new Set([gidB, gidC, gidD]).has(gidA as string));
|
|
284
320
|
console.error(`[smoke-entwurf-chain-live] A: ${gidA} (native Claude Code)`);
|
|
285
321
|
|
|
322
|
+
// ── the fixture must still be a deliverable citizen when the chain arrives ──
|
|
323
|
+
// The terminus is only reachable while its owner is alive AND that owner's sender marker
|
|
324
|
+
// still names it (#101). Both are fixture state this smoke owns, and both can decay while
|
|
325
|
+
// three model turns run. Asserting it here separates "the chain did not arrive" from "the
|
|
326
|
+
// target stopped being addressable", which cost a LIVE run to tell apart once.
|
|
327
|
+
const terminusFacts = resolveMailboxReceiverFacts(terminus.record, {
|
|
328
|
+
readReceiverMarker: (gardenId: string) => readMetaReceiverMarker({ gardenId, receiversDir }),
|
|
329
|
+
readSenderMarker: (backend: string, pid: number) =>
|
|
330
|
+
readMetaSenderMarker({ backend: backend as "claude-code", ownerPid: pid, sendersDir }),
|
|
331
|
+
});
|
|
332
|
+
ok(
|
|
333
|
+
"fixture: the terminus is a deliverable citizen at the moment the chain starts",
|
|
334
|
+
terminusFacts.ownerAlive && terminusFacts.watchArmed,
|
|
335
|
+
);
|
|
336
|
+
|
|
286
337
|
// ── the chain runs on its own from here; wait for the terminus ───────────
|
|
287
338
|
const boxDir = path.join(mailboxDir, gidD);
|
|
288
339
|
const until = Date.now() + CHAIN_TIMEOUT_MS;
|
|
@@ -295,7 +346,25 @@ async function main(): Promise<void> {
|
|
|
295
346
|
await sleep(POLL_MS);
|
|
296
347
|
}
|
|
297
348
|
if (msgs.length === 0) {
|
|
298
|
-
|
|
349
|
+
let ownerAlive = false;
|
|
350
|
+
try {
|
|
351
|
+
process.kill(terminusOwnerPid, 0);
|
|
352
|
+
ownerAlive = true;
|
|
353
|
+
} catch {}
|
|
354
|
+
const facts = resolveMailboxReceiverFacts(terminus.record, {
|
|
355
|
+
readReceiverMarker: (gardenId: string) => readMetaReceiverMarker({ gardenId, receiversDir }),
|
|
356
|
+
readSenderMarker: (backend: string, pid: number) =>
|
|
357
|
+
readMetaSenderMarker({ backend: backend as "claude-code", ownerPid: pid, sendersDir }),
|
|
358
|
+
});
|
|
359
|
+
console.error(
|
|
360
|
+
`[smoke-entwurf-chain-live] terminus fixture at timeout: ownerPid=${terminusOwnerPid} alive=${ownerAlive} ` +
|
|
361
|
+
`ownerAlive=${facts.ownerAlive} watchArmed=${facts.watchArmed}`,
|
|
362
|
+
);
|
|
363
|
+
// The FULL turn, not a 1500-char head: that slice cut the JSON before its `result`
|
|
364
|
+
// field, so the one line that says what A actually did — the tool outcome it was told
|
|
365
|
+
// to echo back — was the first thing this diagnostic dropped. A failure that hides its
|
|
366
|
+
// own cause costs another LIVE run to re-learn.
|
|
367
|
+
console.error(`[smoke-entwurf-chain-live] A turn output:\n${claudeOut}`);
|
|
299
368
|
console.error(`[smoke-entwurf-chain-live] B stream tail:\n${streamB.slice(-2000)}`);
|
|
300
369
|
console.error(`[smoke-entwurf-chain-live] C stream tail:\n${streamC.slice(-2000)}`);
|
|
301
370
|
}
|
|
@@ -350,6 +419,9 @@ async function main(): Promise<void> {
|
|
|
350
419
|
console.error(`[smoke-entwurf-chain-live] chain: ${gidA} → ${gidB} → ${gidC} → ${gidD}`);
|
|
351
420
|
} finally {
|
|
352
421
|
for (const ch of children) await terminateChild(ch).catch(() => undefined);
|
|
422
|
+
try {
|
|
423
|
+
terminusOwner?.kill("SIGTERM");
|
|
424
|
+
} catch {}
|
|
353
425
|
}
|
|
354
426
|
|
|
355
427
|
console.log(
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
metaRecordExistsByGardenId,
|
|
63
63
|
upsertMetaSession,
|
|
64
64
|
writeMetaReceiverMarker,
|
|
65
|
+
writeMetaSenderMarker,
|
|
65
66
|
} from "../pi-extensions/lib/meta-session.ts";
|
|
66
67
|
import { terminateChild } from "./lib/acp-child-cleanup.ts";
|
|
67
68
|
import { skipLive } from "./lib/live-skip.ts";
|
|
@@ -187,10 +188,15 @@ async function main(): Promise<void> {
|
|
|
187
188
|
const mailboxDir = path.join(tmp, "mailbox");
|
|
188
189
|
const lockDir = path.join(tmp, "locks");
|
|
189
190
|
const receiversDir = path.join(tmp, "receivers");
|
|
190
|
-
|
|
191
|
+
const sendersDir = path.join(tmp, "senders");
|
|
192
|
+
for (const d of [sessionsDir, mailboxDir, lockDir, receiversDir, sendersDir]) await fsp.mkdir(d, { recursive: true });
|
|
191
193
|
// The receiver marker is read through defaultMetaReceiversDir() (env-overridable); point it
|
|
192
194
|
// (and the store/mailbox defaults, belt-and-suspenders to the opts dirs) at the temp world.
|
|
195
|
+
// The SENDER dir joins that list for #101: deliverability now also reads the receiver owner's
|
|
196
|
+
// sender marker, and leaving this one pointed at the operator's real root would have the
|
|
197
|
+
// fixture judged against markers this smoke never wrote.
|
|
193
198
|
process.env.ENTWURF_META_RECEIVERS_DIR = receiversDir;
|
|
199
|
+
process.env.ENTWURF_META_SENDERS_DIR = sendersDir;
|
|
194
200
|
process.env.ENTWURF_META_SESSIONS_DIR = sessionsDir;
|
|
195
201
|
process.env.ENTWURF_META_MAILBOX_DIR = mailboxDir;
|
|
196
202
|
|
|
@@ -341,6 +347,14 @@ async function main(): Promise<void> {
|
|
|
341
347
|
const gid = minted.record.gardenId;
|
|
342
348
|
artifacts["C2.gid"] = gid;
|
|
343
349
|
// armed = a presence marker owned by THIS (live) process → deliverable.
|
|
350
|
+
//
|
|
351
|
+
// The receiver marker alone is no longer the whole fixture (#101): a `claude-code-cli`
|
|
352
|
+
// watch owner is deliverable only while ITS OWN sender marker still names the garden it
|
|
353
|
+
// is armed for — that join is what refuses a session which switched away in place. A
|
|
354
|
+
// receiver marker with no sender marker beside it models a state the product now treats
|
|
355
|
+
// as retired, so this smoke would fail on its own fixture instead of on the rail it
|
|
356
|
+
// exists to prove. Same shape as smoke-mux-lifecycle-live / smoke-omp-fresh-live, which
|
|
357
|
+
// already seeded both.
|
|
344
358
|
writeMetaReceiverMarker({
|
|
345
359
|
gardenId: gid,
|
|
346
360
|
backend: "claude-code",
|
|
@@ -349,6 +363,14 @@ async function main(): Promise<void> {
|
|
|
349
363
|
armProvenance: "session-start",
|
|
350
364
|
receiversDir,
|
|
351
365
|
});
|
|
366
|
+
writeMetaSenderMarker({
|
|
367
|
+
backend: "claude-code",
|
|
368
|
+
gardenId: gid,
|
|
369
|
+
nativeSessionId: minted.record.nativeSessionId,
|
|
370
|
+
cwd: tmp,
|
|
371
|
+
ownerPid: process.pid,
|
|
372
|
+
sendersDir,
|
|
373
|
+
});
|
|
352
374
|
|
|
353
375
|
const result: EntwurfV2RunResult = await runEntwurfV2(
|
|
354
376
|
{ target: gid, intent: "fire-and-forget", message: "matrix-live C2 meta-mailbox probe" },
|
|
@@ -194,7 +194,10 @@ async function main(): Promise<void> {
|
|
|
194
194
|
const callerGid = caller.record.gardenId;
|
|
195
195
|
// A record proves identity; only an ARMED receiver marker makes the mailbox deliverable —
|
|
196
196
|
// without it the callbacks would be refused as mailbox-undeliverable and this smoke would
|
|
197
|
-
// measure nothing.
|
|
197
|
+
// measure nothing. Since #101 the marker is half of that: a `claude-code-cli` watch owner
|
|
198
|
+
// is deliverable only while ITS OWN sender marker still names the garden it is armed for,
|
|
199
|
+
// which is what refuses a session that switched away in place. Both, or the fixture models
|
|
200
|
+
// a retired watch — the same shape smoke-mux-lifecycle-live already seeds.
|
|
198
201
|
meta.writeMetaReceiverMarker({
|
|
199
202
|
gardenId: callerGid,
|
|
200
203
|
backend: "claude-code",
|
|
@@ -202,9 +205,16 @@ async function main(): Promise<void> {
|
|
|
202
205
|
ownerPid: process.pid,
|
|
203
206
|
armProvenance: "session-start",
|
|
204
207
|
});
|
|
208
|
+
const callerSenderMarker = meta.writeMetaSenderMarker({
|
|
209
|
+
backend: "claude-code",
|
|
210
|
+
gardenId: callerGid,
|
|
211
|
+
nativeSessionId,
|
|
212
|
+
cwd: scratch,
|
|
213
|
+
ownerPid: process.pid,
|
|
214
|
+
});
|
|
205
215
|
ok(
|
|
206
|
-
"fixture: the caller citizen was minted INSIDE the fixture store
|
|
207
|
-
Boolean(callerGid) && caller.path.startsWith(root),
|
|
216
|
+
"fixture: the caller citizen was minted INSIDE the fixture store, with an armed mailbox and a sender marker",
|
|
217
|
+
Boolean(callerGid) && caller.path.startsWith(root) && callerSenderMarker.startsWith(root),
|
|
208
218
|
);
|
|
209
219
|
|
|
210
220
|
// ── One private tmux server per backend ──────────────────────────────────
|