@junghanacs/entwurf 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -2
- package/BASELINE.md +55 -2
- package/CHANGELOG.md +321 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +18 -2
- package/FAQ.md +8 -4
- package/README.md +26 -8
- package/VERIFY.md +13 -10
- package/docs/acp-backend-rail.md +5 -1
- package/docs/external-mcp-host.md +8 -6
- package/docs/fresh-cut-policy.md +9 -1
- package/docs/setup-clean-host.md +24 -8
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +81 -19
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-request.js +473 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-retire.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire-production.js +149 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire.js +158 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +283 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +7 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +96 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +5 -29
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +28 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +26 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-retire-request.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +67 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/retirement-event.js +211 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +124 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +93 -19
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/mcp/entwurf-bridge/tsconfig.build.json +10 -1
- package/package.json +4 -4
- package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
- package/pi-extensions/entwurf-control.ts +55 -20
- package/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/pi-extensions/lib/cc-retire-request.ts +553 -0
- package/pi-extensions/lib/control-retire.ts +195 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +17 -1
- package/pi-extensions/lib/entwurf-v2-retire-production.ts +227 -0
- package/pi-extensions/lib/entwurf-v2-retire.ts +269 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +2 -1
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +390 -36
- package/pi-extensions/lib/fresh-call-composition.ts +15 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +45 -4
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/meta-session.ts +140 -16
- package/pi-extensions/lib/mux-fresh-call.ts +7 -27
- package/pi-extensions/lib/mux-launch.ts +29 -0
- package/pi-extensions/lib/mux-resume-call.ts +33 -12
- package/pi-extensions/lib/pi-retire-request.ts +111 -0
- package/pi-extensions/lib/resume-launch-identity.ts +104 -18
- package/pi-extensions/lib/retirement-event.ts +253 -0
- package/pi-extensions/meta-bridge-hook.ts +142 -13
- package/pi-extensions/meta-bridge-pi-durable.ts +56 -2
- package/run.sh +78 -7
- package/scripts/agy-bridge-config.py +4 -1
- package/scripts/build-bridge.sh +14 -0
- package/scripts/check-capability-bundle-reach.ts +135 -2
- package/scripts/check-entwurf-bridge-boot.ts +2 -1
- package/scripts/check-entwurf-fact-provider.ts +1 -0
- package/scripts/check-entwurf-v2-production.ts +2 -0
- package/scripts/check-entwurf-v2-surface.ts +7 -0
- package/scripts/check-gate-qualification.ts +14 -8
- package/scripts/check-herdr-activation.ts +2 -2
- package/scripts/check-herdr-placement.ts +1 -0
- package/scripts/check-hook-launch-topology.ts +6 -15
- package/scripts/check-install-container.sh +2 -2
- package/scripts/check-meta-doctor-oracle.sh +27 -1
- package/scripts/check-meta-facts.ts +8 -3
- package/scripts/check-meta-hook-session-switch.ts +230 -13
- package/scripts/check-meta-identity-consumers.ts +1 -0
- package/scripts/check-meta-listing.ts +1 -0
- package/scripts/check-meta-manifest-schema.py +1 -1
- package/scripts/check-meta-session.ts +31 -0
- package/scripts/check-meta-v3-record.ts +55 -1
- package/scripts/check-mux-resume-call.ts +35 -9
- package/scripts/check-peer-facts.ts +1 -0
- package/scripts/check-pi-durable-contact.ts +3 -1
- package/scripts/check-pi-durable-send.ts +4 -2
- package/scripts/check-pi-mcp-bridge.ts +1 -0
- package/scripts/check-resume-launch-identity.ts +62 -23
- package/scripts/lib/meta-hook-bundle.ts +72 -0
- package/scripts/meta-bridge-claude-floor.sh +13 -9
- package/scripts/meta-bridge-doctor.sh +17 -0
- package/scripts/meta-bridge-install.sh +6 -0
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/bridge-boot-resume.json +5 -2
- package/scripts/mutants/cc-retire-pidfd.json +129 -0
- package/scripts/mutants/cc-retire-request.json +171 -0
- package/scripts/mutants/control-retire.json +105 -0
- package/scripts/mutants/copilot-receive.json +2 -6
- package/scripts/mutants/entwurf-v2-retire.json +476 -0
- package/scripts/mutants/fresh-call-dispatch.json +37 -0
- package/scripts/mutants/meta-hook-session-switch.json +88 -0
- package/scripts/mutants/meta-identity.json +57 -0
- package/scripts/mutants/mux-fresh-call.json +1 -1
- package/scripts/mutants/mux-resume-call.json +40 -4
- package/scripts/mutants/pi-durable.json +79 -0
- package/scripts/mutants/pi-retire-request.json +72 -0
- package/scripts/mutants/resume-launch-identity.json +35 -8
- package/scripts/mutants/retirement-event.json +195 -0
- package/scripts/mutants/v2-surface.json +16 -0
- package/scripts/mutants/v2-visible-resume.json +283 -13
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-raw-install-live.sh +5 -4
- package/scripts/smoke-meta-install-state.sh +4 -4
- package/scripts/smoke-mux-lifecycle-live.ts +10 -10
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +5 -3
- package/scripts/smoke-pi-provider-state.sh +3 -3
|
@@ -53,20 +53,38 @@
|
|
|
53
53
|
* closure (build-bridge → dist), because Node REFUSES strip-types on a `.ts` below
|
|
54
54
|
* node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). The installer picks
|
|
55
55
|
* the artifact by layout (0.12.5), mirroring start.sh / store-doctor.
|
|
56
|
-
* Imports `./lib/meta-session.ts` (+ `./lib/session-id.js`);
|
|
57
|
-
* rewrites
|
|
58
|
-
* copies
|
|
56
|
+
* Imports `./lib/meta-session.ts` and `./lib/entwurf-v2-lock.ts` (+ `./lib/session-id.js`);
|
|
57
|
+
* `rewriteRelativeImportExtensions` rewrites those to `.js` in the emitted closure. The
|
|
58
|
+
* installer copies exactly those lib files alongside this file so `${CLAUDE_PLUGIN_ROOT}`
|
|
59
|
+
* self-locates them.
|
|
60
|
+
*
|
|
61
|
+
* RECORD-ARM LOCK (#147 L2 B1). Every write this hook makes to an EXISTING record happens
|
|
62
|
+
* under a per-gid lock (`defaultMetaRecordArmLocksDir`, the shared `acquireLock` primitive in
|
|
63
|
+
* its own directory — never the v2 dispatch lock dir, which visible resume holds across its
|
|
64
|
+
* observation). The garden id is learned by a READ-ONLY certified lookup; a record found
|
|
65
|
+
* absent is published create-only. On an arm-capable event the lock is held from the upsert
|
|
66
|
+
* that commits a fresh `attachIncarnation` through the receiver marker that copies it, so two
|
|
67
|
+
* normal arms (or an arm and a UserPromptSubmit) always finish as a matching record/marker
|
|
68
|
+
* pair. An arm that cannot take the lock within its bound writes NOTHING and logs ERROR: that
|
|
69
|
+
* attach is not committed, and a live native process may then exist that no record names — a
|
|
70
|
+
* boundary, not a refusal. A UserPromptSubmit that finds the lock busy skips its backfill.
|
|
59
71
|
*/
|
|
60
72
|
|
|
61
73
|
import * as fs from "node:fs";
|
|
62
74
|
import * as path from "node:path";
|
|
75
|
+
import { acquireLock, type LockClaim, releaseLock } from "./lib/entwurf-v2-lock.ts";
|
|
63
76
|
import {
|
|
64
77
|
defaultMetaMailboxDir,
|
|
78
|
+
defaultMetaRecordArmLocksDir,
|
|
65
79
|
defaultMetaSessionsDir,
|
|
80
|
+
findCertifiedIdentityByNativeSessionId,
|
|
66
81
|
isPlausibleOwnerPid,
|
|
82
|
+
type MetaIdentityMintInput,
|
|
67
83
|
type MetaReceiverArmProvenance,
|
|
84
|
+
mintAttachIncarnation,
|
|
68
85
|
readMetaSenderMarker,
|
|
69
86
|
removeMetaReceiverMarker,
|
|
87
|
+
UpsertActionMismatchError,
|
|
70
88
|
upsertMetaSession,
|
|
71
89
|
writeMetaReceiverMarker,
|
|
72
90
|
writeMetaSenderMarker,
|
|
@@ -97,8 +115,62 @@ function logLine(level: LogLevel, message: string): void {
|
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
|
|
118
|
+
/** The record-arm lock this run holds, released on every exit path (see {@link emit}). */
|
|
119
|
+
let heldArmLock: LockClaim | null = null;
|
|
120
|
+
|
|
121
|
+
/** Arm-capable events wait this long for the record-arm lock; the critical section is a few file writes. */
|
|
122
|
+
const ARM_LOCK_WAIT_MS = 2_000;
|
|
123
|
+
const ARM_LOCK_POLL_MS = 25;
|
|
124
|
+
|
|
125
|
+
function sleepSync(ms: number): void {
|
|
126
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Take the record-arm lock for `gardenId`: ONE attempt, or (arm-capable events) polled up to
|
|
131
|
+
* {@link ARM_LOCK_WAIT_MS}. A throw from the primitive (its claim could not be written) is
|
|
132
|
+
* the same answer as busy — the caller writes nothing.
|
|
133
|
+
*/
|
|
134
|
+
function takeArmLock(gardenId: string, wait: boolean): { ok: true } | { ok: false; detail: string } {
|
|
135
|
+
const dir = defaultMetaRecordArmLocksDir();
|
|
136
|
+
const deadline = Date.now() + (wait ? ARM_LOCK_WAIT_MS : 0);
|
|
137
|
+
for (;;) {
|
|
138
|
+
let detail: string;
|
|
139
|
+
try {
|
|
140
|
+
const acquired = acquireLock(gardenId, { dir });
|
|
141
|
+
if (acquired.ok) {
|
|
142
|
+
heldArmLock = acquired.claim;
|
|
143
|
+
return { ok: true };
|
|
144
|
+
}
|
|
145
|
+
detail = acquired.conflict.detail;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
detail = err instanceof Error ? err.message : String(err);
|
|
148
|
+
}
|
|
149
|
+
if (Date.now() >= deadline) return { ok: false, detail };
|
|
150
|
+
sleepSync(ARM_LOCK_POLL_MS);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function releaseArmLock(): void {
|
|
155
|
+
const claim = heldArmLock;
|
|
156
|
+
if (claim === null) return;
|
|
157
|
+
heldArmLock = null;
|
|
158
|
+
try {
|
|
159
|
+
const outcome = releaseLock(claim, { dir: defaultMetaRecordArmLocksDir() });
|
|
160
|
+
if (outcome !== "released") logLine("WARN", `record-arm lock release for ${claim.gardenId}: ${outcome}`);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// The holder pid is this short-lived hook, so a lock it failed to unlink is reclaimed
|
|
163
|
+
// by the next acquirer once this process is gone (same host + ESRCH).
|
|
164
|
+
logLine(
|
|
165
|
+
"WARN",
|
|
166
|
+
`record-arm lock release failed for ${claim.gardenId}: ${err instanceof Error ? err.message : String(err)}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
100
171
|
/** Emit a hook response on stdout and exit 0. `{}` means "did nothing, do not block startup". */
|
|
101
172
|
function emit(payload: Record<string, unknown>): never {
|
|
173
|
+
releaseArmLock();
|
|
102
174
|
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
103
175
|
process.exit(0);
|
|
104
176
|
}
|
|
@@ -174,6 +246,59 @@ function resolveMetaHookOwnerPid(): number | null {
|
|
|
174
246
|
return ownerPid;
|
|
175
247
|
}
|
|
176
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Commit this event's record write and return the garden id (#147 L2 B1). The garden id to
|
|
251
|
+
* lock is learned READ ONLY; a record found absent is published create-only (a create needs no
|
|
252
|
+
* lock: its exclusive publish cannot replace anything, and a concurrent publisher turns the
|
|
253
|
+
* create into a refusal here, never into an unlocked attach). Every write to an EXISTING
|
|
254
|
+
* record then happens under the record-arm lock, which the caller keeps through the receiver
|
|
255
|
+
* marker write (released by {@link emit}). An arm-capable event mints its binding in that same
|
|
256
|
+
* atomic upsert. Lock busy: an arm writes nothing and logs ERROR (admission failure — not a
|
|
257
|
+
* committed attach, not an armed watch); any other event skips its backfill with a WARN.
|
|
258
|
+
* Throws on a store defect, exactly as the upsert does.
|
|
259
|
+
*/
|
|
260
|
+
function commitRecord(
|
|
261
|
+
input: MetaIdentityMintInput,
|
|
262
|
+
armsWatch: boolean,
|
|
263
|
+
logContext: string,
|
|
264
|
+
): { gardenId: string; attachIncarnation: string | undefined } {
|
|
265
|
+
let existing = findCertifiedIdentityByNativeSessionId(input.nativeSessionId);
|
|
266
|
+
if (existing === null) {
|
|
267
|
+
try {
|
|
268
|
+
const created = upsertMetaSession({ input, expectAction: "create" });
|
|
269
|
+
logLine("INFO", `create record ${path.basename(created.path)} (${logContext})`);
|
|
270
|
+
// Nothing to bind and nothing older to preserve: the create is the whole write.
|
|
271
|
+
if (!armsWatch) return { gardenId: created.record.gardenId, attachIncarnation: undefined };
|
|
272
|
+
existing = created.record;
|
|
273
|
+
} catch (err) {
|
|
274
|
+
if (!(err instanceof UpsertActionMismatchError)) throw err;
|
|
275
|
+
existing = findCertifiedIdentityByNativeSessionId(input.nativeSessionId);
|
|
276
|
+
if (existing === null) throw err;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const locked = takeArmLock(existing.gardenId, armsWatch);
|
|
280
|
+
if (!locked.ok) {
|
|
281
|
+
if (armsWatch) {
|
|
282
|
+
// Admission failure: NOTHING is written, so the store keeps whatever pair it had. This
|
|
283
|
+
// attach is not committed, and a live process may now hold this session unseen.
|
|
284
|
+
logLine(
|
|
285
|
+
"ERROR",
|
|
286
|
+
`record-arm lock busy — attach NOT committed, watch not armed (garden=${existing.gardenId}, ${logContext}): ${locked.detail}`,
|
|
287
|
+
);
|
|
288
|
+
} else {
|
|
289
|
+
logLine(
|
|
290
|
+
"WARN",
|
|
291
|
+
`record-arm lock busy — backfill skipped (garden=${existing.gardenId}, ${logContext}): ${locked.detail}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
emit({});
|
|
295
|
+
}
|
|
296
|
+
const attachIncarnation = armsWatch ? mintAttachIncarnation() : undefined;
|
|
297
|
+
const result = upsertMetaSession({ input: { ...input, attachIncarnation }, expectAction: "attach" });
|
|
298
|
+
logLine("INFO", `${result.action} record ${path.basename(result.path)} (${logContext})`);
|
|
299
|
+
return { gardenId: result.record.gardenId, attachIncarnation };
|
|
300
|
+
}
|
|
301
|
+
|
|
177
302
|
function main(): void {
|
|
178
303
|
let raw: string;
|
|
179
304
|
try {
|
|
@@ -230,16 +355,16 @@ function main(): void {
|
|
|
230
355
|
emit({});
|
|
231
356
|
}
|
|
232
357
|
|
|
358
|
+
// Arm-capable events commit a fresh binding; every other event preserves it.
|
|
359
|
+
const armsWatch = armProvenanceFor(eventName) !== null;
|
|
233
360
|
let gardenId: string;
|
|
361
|
+
let attachIncarnation: string | undefined;
|
|
234
362
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
"INFO",
|
|
241
|
-
`${result.action} record ${path.basename(result.path)} (event=${eventName}, source=${source}, native=${sessionId})`,
|
|
242
|
-
);
|
|
363
|
+
({ gardenId, attachIncarnation } = commitRecord(
|
|
364
|
+
{ backend: "claude-code", nativeSessionId: sessionId, transcriptPath, cwd, model },
|
|
365
|
+
armsWatch,
|
|
366
|
+
`event=${eventName}, source=${source}, native=${sessionId}`,
|
|
367
|
+
));
|
|
243
368
|
} catch (err) {
|
|
244
369
|
// Best-effort: a broken record store must surface via the doctor, not by
|
|
245
370
|
// breaking the user's session open. Log and continue with no arm. This is
|
|
@@ -362,12 +487,16 @@ function main(): void {
|
|
|
362
487
|
nativeSessionId: sessionId,
|
|
363
488
|
ownerPid,
|
|
364
489
|
armProvenance,
|
|
490
|
+
attachIncarnation,
|
|
365
491
|
});
|
|
366
492
|
logLine("INFO", `receiver marker ${gardenId} owner=${ownerPid} arm=${eventName} source=${source}`);
|
|
367
493
|
} catch (err) {
|
|
494
|
+
// ERROR, after `INFO armed watch` so the hook-log doctor does not read a later arm as
|
|
495
|
+
// recovery: the record already carries this arm's binding, the marker does not, so the
|
|
496
|
+
// garden is undeliverable AND a later resume refuses it as unbound.
|
|
368
497
|
logLine(
|
|
369
|
-
"
|
|
370
|
-
`receiver marker write failed (event=${eventName}, garden=${gardenId}): ${err instanceof Error ? err.message : String(err)}`,
|
|
498
|
+
"ERROR",
|
|
499
|
+
`receiver marker write failed — record binding committed, marker not (event=${eventName}, garden=${gardenId}): ${err instanceof Error ? err.message : String(err)}`,
|
|
371
500
|
);
|
|
372
501
|
}
|
|
373
502
|
} else {
|
|
@@ -98,6 +98,13 @@
|
|
|
98
98
|
* operator host started outside tmux gets the bridge's named `no-tmux-context` refusal. Exposing the
|
|
99
99
|
* tool is not a caller LIVE proof.
|
|
100
100
|
*
|
|
101
|
+
* CALLER RESUME (the seventh tool, #147 L1). `entwurf_resume_call` lets this durable citizen reopen a
|
|
102
|
+
* DORMANT pi or Claude Code citizen under its own garden id (#147 L2), under the bridge's own
|
|
103
|
+
* single-`target` schema (pinned below). The bridge decides everything else from the target's record,
|
|
104
|
+
* exactly as for any other caller: other backends have no same-id resume, a live target is refused,
|
|
105
|
+
* and the window opens in the tmux session this host was launched in. Exposing the tool names no new authority and is not a caller LIVE
|
|
106
|
+
* proof; it never resumes this durable host itself.
|
|
107
|
+
*
|
|
101
108
|
* NOT HERE: crash-safe receive. `entwurf_inbox_read` archives a body before the durable tool
|
|
102
109
|
* result commits, so a crash between the two loses that body to the model; recovery settles the
|
|
103
110
|
* call as interrupted and no doorbell rings again. A drain that fails part-way has archived the
|
|
@@ -156,6 +163,8 @@ export const PI_DURABLE_TOOL_NAMES = [
|
|
|
156
163
|
"entwurf_inbox_read",
|
|
157
164
|
"entwurf_callback",
|
|
158
165
|
"entwurf_fresh_call",
|
|
166
|
+
"entwurf_resume_call",
|
|
167
|
+
"entwurf_retire_call",
|
|
159
168
|
] as const;
|
|
160
169
|
export type PiDurableToolName = (typeof PI_DURABLE_TOOL_NAMES)[number];
|
|
161
170
|
|
|
@@ -164,7 +173,7 @@ const DRAFT_07 = "http://json-schema.org/draft-07/schema#";
|
|
|
164
173
|
/**
|
|
165
174
|
* The bridge's `tools/list` inputSchemas for the exposed tools (`mcp/entwurf-bridge/src/index.ts`
|
|
166
175
|
* `entwurf_v2` 456-507, `entwurf_self`, `entwurf_peers`, `entwurf_inbox_read`, `entwurf_callback`,
|
|
167
|
-
* `entwurf_fresh_call`),
|
|
176
|
+
* `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_retire_call`),
|
|
168
177
|
* as the bridge serialises them.
|
|
169
178
|
* `attach` compares the live list against these and refuses on any difference.
|
|
170
179
|
*/
|
|
@@ -235,7 +244,7 @@ export const PI_DURABLE_TOOL_SCHEMAS: Readonly<Record<PiDurableToolName, Readonl
|
|
|
235
244
|
maxLength: 200,
|
|
236
245
|
pattern: "^[A-Za-z0-9][A-Za-z0-9._/:\\[\\]-]*$",
|
|
237
246
|
description:
|
|
238
|
-
"Required runtime model: canonical provider/model for pi, provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name.",
|
|
247
|
+
"Required runtime model: canonical provider/model for pi (a bare alias is refused), provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name. Neither pi nor pi-durable may name the openrouter provider.",
|
|
239
248
|
},
|
|
240
249
|
task: {
|
|
241
250
|
type: "string",
|
|
@@ -266,6 +275,33 @@ export const PI_DURABLE_TOOL_SCHEMAS: Readonly<Record<PiDurableToolName, Readonl
|
|
|
266
275
|
required: ["backend", "model", "task"],
|
|
267
276
|
$schema: DRAFT_07,
|
|
268
277
|
},
|
|
278
|
+
entwurf_resume_call: {
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {
|
|
281
|
+
target: {
|
|
282
|
+
type: "string",
|
|
283
|
+
minLength: 1,
|
|
284
|
+
pattern: "^\\d{8}T\\d{6}-[0-9a-f]{6}$",
|
|
285
|
+
description: "Garden id of the DORMANT pi or Claude Code citizen to reopen (discover with entwurf_peers).",
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
required: ["target"],
|
|
289
|
+
$schema: DRAFT_07,
|
|
290
|
+
},
|
|
291
|
+
entwurf_retire_call: {
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: {
|
|
294
|
+
target: {
|
|
295
|
+
type: "string",
|
|
296
|
+
minLength: 1,
|
|
297
|
+
pattern: "^\\d{8}T\\d{6}-[0-9a-f]{6}$",
|
|
298
|
+
description:
|
|
299
|
+
"Exact garden id of the Pi or Claude Code target the human operator explicitly requested to retire.",
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
required: ["target"],
|
|
303
|
+
$schema: DRAFT_07,
|
|
304
|
+
},
|
|
269
305
|
};
|
|
270
306
|
|
|
271
307
|
/** Key-order-insensitive JSON, for comparing a schema copy against the bridge's own. */
|
|
@@ -748,6 +784,24 @@ const TOOL_DESCRIPTIONS: Record<PiDurableToolName, string> = {
|
|
|
748
784
|
"(backend, model and task required; optional cwd and placement). Returns the bridge's LAUNCH receipt or its " +
|
|
749
785
|
"named refusal — not a started runtime, a delivered task or a reply; the sibling's callback names it. Opening " +
|
|
750
786
|
"beside this host needs the tmux pane this host itself was launched in.",
|
|
787
|
+
entwurf_resume_call:
|
|
788
|
+
"Reopen ONE dormant pi or Claude Code citizen under its own garden id in a visible tmux window through the " +
|
|
789
|
+
"entwurf-bridge (target required; the record supplies the session and cwd — no task, no prompt, no turn). " +
|
|
790
|
+
"Returns the bridge's LAUNCH and OBSERVATION receipts or its named refusal: a live target, another backend, " +
|
|
791
|
+
"or a Claude Code citizen not proven dormant is refused by name, and opening the window needs the tmux " +
|
|
792
|
+
"session this host itself was launched in.",
|
|
793
|
+
entwurf_retire_call:
|
|
794
|
+
"Only when the human operator explicitly requested this particular target/work: make ONE retirement attempt for that request " +
|
|
795
|
+
"through the bridge (target only, Pi or Claude Code; self retirement refuses). Development work, a role or " +
|
|
796
|
+
"a record-backed actor is NOT a grant. Use host approval where provided/configured; " +
|
|
797
|
+
"Pi and pi-durable have no built-in per-call approval prompt by default. An explicit operator request and " +
|
|
798
|
+
"agent operating policy are required; attribution and MCP annotations are hints, not grants or proof of approval. " +
|
|
799
|
+
"No autonomous cleanup, timers or automatic " +
|
|
800
|
+
"retry of unknown, cancelled or lost results. Do not manually /resume or /clear the target during the request. " +
|
|
801
|
+
"Pi shutdown-requested is only an ACK, not process exit, flush or model silence. Claude Code exited is that " +
|
|
802
|
+
"fd-pinned process only, not conversation end, children or flush. The JSON receipt keeps outcome, record and " +
|
|
803
|
+
"release separate; receipt return does not mean exit, and cancellation does not undo an action. Invoke once " +
|
|
804
|
+
"per explicitly named target, never infer a roster. This contact does not turn hints into authorization.",
|
|
751
805
|
};
|
|
752
806
|
|
|
753
807
|
/** Announce, never push: the notice names the garden and the tool, never the body. */
|
package/run.sh
CHANGED
|
@@ -224,7 +224,7 @@ Usage:
|
|
|
224
224
|
./run.sh smoke-agy-native-push-live # 봉인 8 LIVE acceptance for the native-push (agy) rail — OUT of pnpm check, needs LIVE=1 + AGY_CONVERSATION_ID (a live agy conversation). Drives the REAL antigravity adapter + register core + runEntwurfV2 (production deps): doctor-static preflight (dangling→FAIL, the ③ gate), probe route, register create/attach idempotency, fire→native-push delivered, post-send re-probe (D7 partial), bogus-conv→native-push-probe-indeterminate. Meta-store isolated to a temp dir (only the agy round-trip is real; no real-store residue). COST FENCE: open that agy conversation on gemini-3.6-flash (free account) — never a Pro tier; entwurf never selects the agy model and no assertion reads it. LIVE=1 AGY_CONVERSATION_ID=<convId> ./run.sh smoke-agy-native-push-live
|
|
225
225
|
./run.sh smoke-codex-native-push-live # on-demand LIVE acceptance for Codex native-push — needs LIVE=1 + CODEX_LIVE_THREAD_ID loaded by a visible TUI attached to the default app-server; probes the real UDS, routes through production runEntwurfV2, queues one exact-token turn without retry, re-probes, and leaves the visible TUI to show the model result
|
|
226
226
|
./run.sh smoke-codex-fresh-live # #95 RELEASE MUST: record-backed receipt fixture -> PUBLIC initial Pi fresh/callback in a session S that is NOT the app-server's -> Pi PUBLIC omitted-placement Codex fresh/exact callback, also in S -> Pi-to-Codex addressed native-push v2 -> Codex PUBLIC omitted-placement AND omitted-cwd fresh Pi/exact callback IN S beside its own TUI (lane B claim: the app-server env names another session, so S can only come from the caller's pane title; lane C claim: no cwd was asked for, so the directory can only come from the Codex caller's own record — and the Codex thread's own rollout session_meta.cwd, its pane and its record must all be the requested scratch, not the app-server's repo) -> Codex final v2 evidence. Needs LIVE=1 + ENTWURF_CODEX_APP_SERVER_PID + ENTWURF_CODEX_FRESH_MODEL + ENTWURF_CODEX_FRESH_PI_MODEL, and its ONE stable launch directory answered 'Trust' once in a plain codex -C there: codex records its direct consent per exact directory, so a directory with no answer at all opens a consent screen instead of a first turn. Only THIS gate treats that as a precondition and fails up front as codex-launch-cwd-undecided rather than timing out on a callback — an ordinary entwurf_fresh_call only prints the note and opens the window, because a human can answer the screen. The smoke PRINTS that directory as 'launch-cwd <dir>' and repeats it in the failure as a ready-to-run repair command — do not retype it from memory, and do not write it as \$TMPDIR/... (os.tmpdir() falls back to /tmp when TMPDIR is unset, so that spelling names a different directory in the filesystem root). The PID names the operator-owned app-server, which must sit in a DIFFERENT session from S; the smoke checks/prints all four coordinates, never starts/stops/guesses the app-server/session, never reads screen text, interrupts only its exact still-running Codex turn, and cleans only receipt-named window ids
|
|
227
|
-
./run.sh smoke-mux-lifecycle-live # RELEASE MUST integrated LIVE lifecycle acceptance for mux, through the REAL MCP surface — OUT of pnpm check, needs LIVE=1 and spends model turns (two pi siblings: native + recorded-ACP provider, each resumed once; one Claude Code sibling). tools/call fresh_call -> nonce callback sender envelope -> v2 control send landing in the sibling's own transcript -> resume_call REFUSED while live (window count unchanged) -> stable-handle close (pane gone, socket dead, record kept) -> dormant delivery refused honestly -> public entwurf_resume_call with LAUNCH and OBSERVATION receipts kept apart, same-gid socket alive, zero new citizens, zero lock residue, resumed pane_start_path == RECORD cwd (separate tmux query), transcript byte-identical across the resume -> v2 recall of the pre-close fact. claude-code resume refused target-
|
|
227
|
+
./run.sh smoke-mux-lifecycle-live # RELEASE MUST integrated LIVE lifecycle acceptance for mux, through the REAL MCP surface — OUT of pnpm check, needs LIVE=1 and spends model turns (two pi siblings: native + recorded-ACP provider, each resumed once; one Claude Code sibling). tools/call fresh_call -> nonce callback sender envelope -> v2 control send landing in the sibling's own transcript -> resume_call REFUSED while live (window count unchanged) -> stable-handle close (pane gone, socket dead, record kept) -> dormant delivery refused honestly -> public entwurf_resume_call with LAUNCH and OBSERVATION receipts kept apart, same-gid socket alive, zero new citizens, zero lock residue, resumed pane_start_path == RECORD cwd (separate tmux query), transcript byte-identical across the resume -> v2 recall of the pre-close fact. claude-code resume of the still-live sibling refused target-live, no window opened and no lock residue (its dormant same-id resume is deterministic-only, #147 L2). LIVE=1 ./run.sh smoke-mux-lifecycle-live
|
|
228
228
|
./run.sh check-meta-facts # deterministic gate for the meta-facts projection (#65): drives the REAL CLI — full-record join, parse-before-uniqueness, no-winner duplicates, drift/symlink/invalid-UTF-8 defects in-band, deterministic bytes, exit contract 0/2/3, dispatch+emit reachability
|
|
229
229
|
./run.sh check-herdr-plugin # deterministic gate for the #116 M2-b herdr plugin (`plugins/herdr/`): STATIC manifest shape for herdr 0.9.0 + the forbidden sections ([[startup]]/[[events]]/[[actions]]/[[link_handlers]]) asserted absent ([[build]] is check-herdr-plugin-build's subject since #116 M3-b3); BEHAVIOURAL drive of the REAL pane entry against a stub `entwurf` and stub HERDR_BIN_PATH that LOG every call — exactly one `herdr integration status` + one `entwurf peer-facts` + one `herdr agent list` per open, in that order (counted, not claimed), through ONE spawn call site; the four blocks each naming their own owner/observation time with no aggregate verdict; the D3=C fallback reading the activation ledger from the path its WRITER owns (XDG state, never the runtime's data root) and only when the phase is `active`, with ENTWURF_BIN and PATH still winning outright; the citizen axis alone skipping by name when no binary is reachable; every broken-evidence state a named red rather than a crash or an empty table, and a selected herdr atom that is `outdated`/`needs repair` as block [1]'s OWN red (`not installed` stays a reported skip); ambiguous never first-wins, diagnostics shown, activity in its own column, and zero writes to the plugin state/config dirs. No herdr binary, no Entwurf install
|
|
230
230
|
./run.sh check-herdr-runtime-bootstrap # deterministic gate for the #116 M3-b1 runtime leaf (`plugins/herdr/lib/runtime-bootstrap.mjs`): the Entwurf-owned stable active root as a REAL directory (nothing is put on PATH — the scoped wiring above names ABSOLUTE commands under it), ownership decided BEFORE the first mkdir, disk facts from lstat (a dangling symlink is not 'absent' and a link into another tree is not a directory), exact name@version + compiled entry + all three required bins present AND executable + a real `check-bridge`, the plugin-owned artifact lock coherent with the checkout, the owned npm cache and --ignore-scripts, a CERTIFIED journal (non-object/array/scalar/blank-identity/phase-contradicting-digest all refused) as the only ownership proof, all EIGHT active/staging/previous combinations named, the last good runtime never lost (torn swap AND corrupt-active beside a good backup), prior provenance carried across an install that may not finish, a failed candidate leaving the running runtime byte-identical, idempotent same-spec reinstall, a journal believed only while the disk backs it, a preflight-then-mutate inverse taking runtime last, and the refusal to record Herdr's commit as ours. NETWORK ZERO, npm ZERO — it drives a FIXTURE package, so the ACTUAL package proof lives in check-pack-install
|
|
@@ -1343,6 +1343,44 @@ check_control_send_receipt() {
|
|
|
1343
1343
|
run_vitest pi-extensions/lib/control-send-receipt.test.ts
|
|
1344
1344
|
}
|
|
1345
1345
|
|
|
1346
|
+
check_control_retire() {
|
|
1347
|
+
# Mutant execution coordinate only; the existing beside glob owns test discovery.
|
|
1348
|
+
section "Pi idle-only shutdown boundary (mutant execution coordinate)"
|
|
1349
|
+
run_vitest pi-extensions/lib/control-retire.test.ts
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
check_pi_retire_request() {
|
|
1353
|
+
# Mutant execution coordinate only; the existing beside glob owns test discovery.
|
|
1354
|
+
section "Pi retire request adapter (mutant execution coordinate)"
|
|
1355
|
+
run_vitest pi-extensions/lib/pi-retire-request.test.ts
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
check_cc_retire_pidfd() {
|
|
1359
|
+
# MUTANT EXECUTION COORDINATE, not a discovery path — check-tests-beside-behavior finds the test
|
|
1360
|
+
# like every other beside lane. This case exists because the #147 G3 CCRETIRE-* mutants name it as
|
|
1361
|
+
# gate argv; it stays in check:contracts for the same reason as check-control-send-receipt.
|
|
1362
|
+
section "Linux pidfd retire helper (mutant execution coordinate)"
|
|
1363
|
+
run_vitest pi-extensions/lib/cc-retire-pidfd.test.ts
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
check_cc_retire_request() {
|
|
1367
|
+
# Mutant execution coordinate only; the existing beside glob owns test discovery.
|
|
1368
|
+
section "Claude Code retire request adapter (mutant execution coordinate)"
|
|
1369
|
+
run_vitest pi-extensions/lib/cc-retire-request.test.ts
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
check_retirement_event() {
|
|
1373
|
+
# Mutant execution coordinate only; the existing beside glob owns test discovery.
|
|
1374
|
+
section "Retirement append leaf (mutant execution coordinate)"
|
|
1375
|
+
run_vitest pi-extensions/lib/retirement-event.test.ts
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
check_entwurf_v2_retire() {
|
|
1379
|
+
# Mutant execution coordinate only; the existing beside glob owns test discovery.
|
|
1380
|
+
section "Requested retirement dispatcher and production binding (mutant execution coordinate)"
|
|
1381
|
+
run_vitest pi-extensions/lib/entwurf-v2-retire.test.ts pi-extensions/lib/entwurf-v2-retire-production.test.ts
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1346
1384
|
check_win32_preview() {
|
|
1347
1385
|
# MUTANT EXECUTION COORDINATE, not a discovery path — the Windows preview's (#78) loader and
|
|
1348
1386
|
# platform wiring tests are found by check-tests-beside-behavior like every other beside-behaviour
|
|
@@ -1455,6 +1493,15 @@ check_acp_foreground_only() {
|
|
|
1455
1493
|
run_vitest pi-extensions/lib/acp/backend-adapter.test.ts
|
|
1456
1494
|
}
|
|
1457
1495
|
|
|
1496
|
+
check_fresh_call_provider() {
|
|
1497
|
+
# NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
|
|
1498
|
+
# MUTANT EXECUTION COORDINATE for the #76 provider claims in scripts/mutants/fresh-call-dispatch.json
|
|
1499
|
+
# (same reasoning as check_entwurf_resume_args above): pi and pi-durable refuse a model that names
|
|
1500
|
+
# the openrouter provider, and pi refuses one that names no provider, at the dispatch root.
|
|
1501
|
+
section "fresh-call provider contract (mutant execution coordinate)"
|
|
1502
|
+
run_vitest pi-extensions/lib/fresh-call-dispatch.test.ts
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1458
1505
|
check_acp_set_model_readback() {
|
|
1459
1506
|
# NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
|
|
1460
1507
|
# MUTANT EXECUTION COORDINATE for scripts/mutants/acp-set-model-readback.json (#128): the claude
|
|
@@ -1670,7 +1717,8 @@ smoke_mux_lifecycle_live() {
|
|
|
1670
1717
|
# resumed pane_start_path == the RECORD's cwd (separate tmux query, never pane text), and the
|
|
1671
1718
|
# transcript byte-identical across the resume itself -> entwurf_v2 recalling the fact from
|
|
1672
1719
|
# BEFORE the close, which is what proves the same CONVERSATION returned. Claude Code runs the
|
|
1673
|
-
# callback+mailbox half and its resume is refused as target-
|
|
1720
|
+
# callback+mailbox half and its resume, while that sibling is still live, is refused as target-live with no
|
|
1721
|
+
# window opened and no lock residue (#147 L2: Claude Code's dormant resume is certified deterministically).
|
|
1674
1722
|
# Fixture-fenced writes (XDG, four meta roots, v2 lock dir, control socket via fixture HOME);
|
|
1675
1723
|
# the runtime auth roots stay REAL and sibling transcripts remain in the real pi agent dir as
|
|
1676
1724
|
# evidence. Private tmux server per cell, bounded teardown, operator real-root entry sets
|
|
@@ -2731,7 +2779,7 @@ EOF
|
|
|
2731
2779
|
check_claude_floor_coherence() {
|
|
2732
2780
|
# #51 policy A. The Claude Code floor is a THREE-PART version, unlike the Node
|
|
2733
2781
|
# floor's major lane: the discriminator lives in the patch position (2.1.138 drops
|
|
2734
|
-
# `args`, 2.1.139 introduces the exec form, 2.1.217 is the version
|
|
2782
|
+
# `args`, 2.1.139 introduces the exec form, 2.1.217 is the version the exec form was proven on
|
|
2735
2783
|
# in a live session). So it cannot borrow check-node-floor-coherence's major-only
|
|
2736
2784
|
# comparison and needs its own binding.
|
|
2737
2785
|
#
|
|
@@ -5381,7 +5429,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
5381
5429
|
#!/usr/bin/env bash
|
|
5382
5430
|
printf '%s\n' "$*" >> "${FAKE_CLAUDE_LOG:?}"
|
|
5383
5431
|
case "$1${2:+ $2}" in
|
|
5384
|
-
"--version") echo "2.1.
|
|
5432
|
+
"--version") echo "2.1.306 (Claude Code)" ;;
|
|
5385
5433
|
"plugin validate") : ;;
|
|
5386
5434
|
"plugin uninstall") : ;;
|
|
5387
5435
|
"plugin marketplace") : ;;
|
|
@@ -5457,6 +5505,8 @@ SH
|
|
|
5457
5505
|
mkdir -p "$nm_probe/lib"
|
|
5458
5506
|
cp "$installed_hook" "$nm_probe/meta-bridge-hook.js"
|
|
5459
5507
|
cp "$stable_asm/entwurf-meta-receive/lib/meta-session.js" "$nm_probe/lib/meta-session.js"
|
|
5508
|
+
# The real installer already supplies this hook import; the node_modules probe must too.
|
|
5509
|
+
cp "$stable_asm/entwurf-meta-receive/lib/entwurf-v2-lock.js" "$nm_probe/lib/entwurf-v2-lock.js"
|
|
5460
5510
|
cp "$stable_asm/entwurf-meta-receive/lib/session-id.js" "$nm_probe/lib/session-id.js"
|
|
5461
5511
|
cp "$stable_asm/entwurf-meta-receive/entwurf-capabilities.json" "$nm_probe/entwurf-capabilities.json"
|
|
5462
5512
|
local probe_env='{"session_id":"pack-probe","transcript_path":"/tmp/x.jsonl","cwd":"/tmp","hook_event_name":"SessionStart","model":{"id":"probe"}}'
|
|
@@ -5695,7 +5745,7 @@ function finish(trimmed) {
|
|
|
5695
5745
|
// gate that exists to catch "green clone, dead consumer". Equality also catches the
|
|
5696
5746
|
// other direction the narrow-surface rule cares about: an extra or duplicated verb
|
|
5697
5747
|
// reaching hosts without a decision. Written sorted; `names` is sorted above.
|
|
5698
|
-
const EXPECT_TOOLS = ['entwurf_callback', 'entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
|
|
5748
|
+
const EXPECT_TOOLS = ['entwurf_callback', 'entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_retire_call', 'entwurf_self', 'entwurf_v2'];
|
|
5699
5749
|
if (names.length !== EXPECT_TOOLS.length || EXPECT_TOOLS.some((n, i) => names[i] !== n)) {
|
|
5700
5750
|
console.error('installed MCP tool set MISMATCH — want exactly [' + EXPECT_TOOLS.join(',') + '] got [' + names.join(',') + ']');
|
|
5701
5751
|
process.exit(1);
|
|
@@ -6257,12 +6307,12 @@ function finishOk(trimmed) {
|
|
|
6257
6307
|
}
|
|
6258
6308
|
const names = tools.map((t) => t?.name).sort();
|
|
6259
6309
|
// EXACT set, not a floor. This is the assertion behind the operator-facing claim that
|
|
6260
|
-
// `entwurf check-bridge` proves the installed bytes list the
|
|
6310
|
+
// `entwurf check-bridge` proves the installed bytes list the nine garden tools — and
|
|
6261
6311
|
// under an installed package this launcher IS the dist branch of start.sh, so a subset
|
|
6262
6312
|
// check here let an artifact missing entwurf_fresh_call / entwurf_resume_call read as a
|
|
6263
6313
|
// green bridge. Equality also refuses an undecided extra verb. Written sorted; `names`
|
|
6264
6314
|
// is sorted above.
|
|
6265
|
-
const expected = ['entwurf_callback', 'entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
|
|
6315
|
+
const expected = ['entwurf_callback', 'entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_retire_call', 'entwurf_self', 'entwurf_v2'];
|
|
6266
6316
|
if (names.length !== expected.length || expected.some((n, i) => names[i] !== n)) {
|
|
6267
6317
|
console.error(`MCP tool set MISMATCH — want exactly [${expected.join(',')}] got [${names.join(',')}]`);
|
|
6268
6318
|
process.exit(1);
|
|
@@ -7502,6 +7552,24 @@ case "$cmd" in
|
|
|
7502
7552
|
check-control-send-receipt)
|
|
7503
7553
|
check_control_send_receipt
|
|
7504
7554
|
;;
|
|
7555
|
+
check-control-retire)
|
|
7556
|
+
check_control_retire
|
|
7557
|
+
;;
|
|
7558
|
+
check-pi-retire-request)
|
|
7559
|
+
check_pi_retire_request
|
|
7560
|
+
;;
|
|
7561
|
+
check-cc-retire-pidfd)
|
|
7562
|
+
check_cc_retire_pidfd
|
|
7563
|
+
;;
|
|
7564
|
+
check-cc-retire-request)
|
|
7565
|
+
check_cc_retire_request
|
|
7566
|
+
;;
|
|
7567
|
+
check-retirement-event)
|
|
7568
|
+
check_retirement_event
|
|
7569
|
+
;;
|
|
7570
|
+
check-entwurf-v2-retire)
|
|
7571
|
+
check_entwurf_v2_retire
|
|
7572
|
+
;;
|
|
7505
7573
|
check-win32-preview)
|
|
7506
7574
|
check_win32_preview
|
|
7507
7575
|
;;
|
|
@@ -7532,6 +7600,9 @@ case "$cmd" in
|
|
|
7532
7600
|
check-acp-foreground-only)
|
|
7533
7601
|
check_acp_foreground_only
|
|
7534
7602
|
;;
|
|
7603
|
+
check-fresh-call-provider)
|
|
7604
|
+
check_fresh_call_provider
|
|
7605
|
+
;;
|
|
7535
7606
|
check-acp-set-model-readback)
|
|
7536
7607
|
check_acp_set_model_readback
|
|
7537
7608
|
;;
|
|
@@ -102,7 +102,7 @@ PERMISSION_STATE_SCHEMA_VERSION = 2
|
|
|
102
102
|
# EVERY call. The bridge was registered and two thirds of it was unusable — the same failure the
|
|
103
103
|
# doctor already names for entwurf_v2, just never checked for the other two.
|
|
104
104
|
#
|
|
105
|
-
# The
|
|
105
|
+
# The five tools deliberately NOT here, because a grant we do not need is a grant we should not take:
|
|
106
106
|
# entwurf_inbox_read — native-push has no inbox to drain. An agy citizen has no meta-mailbox
|
|
107
107
|
# directory at all (measured), so granting it would pre-approve a rail this
|
|
108
108
|
# backend does not have.
|
|
@@ -113,6 +113,9 @@ PERMISSION_STATE_SCHEMA_VERSION = 2
|
|
|
113
113
|
# refuse without a pane anchor. An agy conversation is not a tmux client,
|
|
114
114
|
# so these are not on its normal path either; the count moved from two to
|
|
115
115
|
# four when 0.14 shipped the mux verbs, and the granted set did not.
|
|
116
|
+
# entwurf_retire_call — one retirement attempt on a target a human operator explicitly named.
|
|
117
|
+
# Pre-approving it would replace that request with an installer grant, so
|
|
118
|
+
# it stays at agy's default Ask; the count moved from four to five (#147).
|
|
116
119
|
ALLOW_RULES = tuple(f"mcp({SERVER_KEY}/{tool})" for tool in ("entwurf_v2", "entwurf_peers", "entwurf_self"))
|
|
117
120
|
# Rules that MATCH one of our tools: its exact grant, the server-wide rule, and the action wildcard.
|
|
118
121
|
# Membership here is a statement about agy's matcher, not about which list the rule sits in — the
|
package/scripts/build-bridge.sh
CHANGED
|
@@ -36,3 +36,17 @@ TSC="$REPO_DIR/node_modules/.bin/tsc"
|
|
|
36
36
|
# dependent function called from its own location always resolves).
|
|
37
37
|
cp "$REPO_DIR/pi/entwurf-capabilities.json" \
|
|
38
38
|
"$REPO_DIR/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json"
|
|
39
|
+
|
|
40
|
+
# The Claude Code retire helper must TRAVEL WITH the bundle too (#147 G3).
|
|
41
|
+
#
|
|
42
|
+
# `ccRetireHelperPath()` resolves ONE fixed basename beside its own module, so the emitted
|
|
43
|
+
# dist/pi-extensions/lib/cc-retire-request.js finds the helper only if it sits next to it. The
|
|
44
|
+
# canonical bytes are pi-extensions/lib/cc-retire-pidfd.py; this is a byte copy, never an edit.
|
|
45
|
+
# A missing or non-regular source fails the build — there is no fallback location. Gated by
|
|
46
|
+
# check-capability-bundle-reach (the compiled adapter asks from where it lives, bytes compared).
|
|
47
|
+
CC_RETIRE_HELPER="$REPO_DIR/pi-extensions/lib/cc-retire-pidfd.py"
|
|
48
|
+
if [ ! -f "$CC_RETIRE_HELPER" ] || [ -L "$CC_RETIRE_HELPER" ]; then
|
|
49
|
+
echo "build-bridge: $CC_RETIRE_HELPER is missing or not a regular file" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
cp "$CC_RETIRE_HELPER" "$REPO_DIR/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py"
|