@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.
Files changed (118) hide show
  1. package/AGENTS.md +4 -2
  2. package/BASELINE.md +55 -2
  3. package/CHANGELOG.md +321 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +18 -2
  6. package/FAQ.md +8 -4
  7. package/README.md +26 -8
  8. package/VERIFY.md +13 -10
  9. package/docs/acp-backend-rail.md +5 -1
  10. package/docs/external-mcp-host.md +8 -6
  11. package/docs/fresh-cut-policy.md +9 -1
  12. package/docs/setup-clean-host.md +24 -8
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +81 -19
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py +236 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-request.js +473 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-retire.js +125 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire-production.js +149 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire.js +158 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +2 -1
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +283 -20
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +7 -0
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -3
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +96 -16
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +5 -29
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +28 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +26 -11
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-retire-request.js +76 -0
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +67 -17
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/retirement-event.js +211 -0
  31. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +124 -10
  32. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -2
  33. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
  34. package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
  35. package/mcp/entwurf-bridge/src/index.ts +93 -19
  36. package/mcp/entwurf-bridge/test.sh +2 -2
  37. package/mcp/entwurf-bridge/tsconfig.build.json +10 -1
  38. package/package.json +4 -4
  39. package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
  40. package/pi-extensions/entwurf-control.ts +55 -20
  41. package/pi-extensions/lib/cc-retire-pidfd.py +236 -0
  42. package/pi-extensions/lib/cc-retire-request.ts +553 -0
  43. package/pi-extensions/lib/control-retire.ts +195 -0
  44. package/pi-extensions/lib/entwurf-control-rpc.ts +17 -1
  45. package/pi-extensions/lib/entwurf-v2-retire-production.ts +227 -0
  46. package/pi-extensions/lib/entwurf-v2-retire.ts +269 -0
  47. package/pi-extensions/lib/entwurf-v2-surface.ts +2 -1
  48. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +390 -36
  49. package/pi-extensions/lib/fresh-call-composition.ts +15 -0
  50. package/pi-extensions/lib/fresh-call-dispatch.ts +45 -4
  51. package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
  52. package/pi-extensions/lib/meta-session.ts +140 -16
  53. package/pi-extensions/lib/mux-fresh-call.ts +7 -27
  54. package/pi-extensions/lib/mux-launch.ts +29 -0
  55. package/pi-extensions/lib/mux-resume-call.ts +33 -12
  56. package/pi-extensions/lib/pi-retire-request.ts +111 -0
  57. package/pi-extensions/lib/resume-launch-identity.ts +104 -18
  58. package/pi-extensions/lib/retirement-event.ts +253 -0
  59. package/pi-extensions/meta-bridge-hook.ts +142 -13
  60. package/pi-extensions/meta-bridge-pi-durable.ts +56 -2
  61. package/run.sh +78 -7
  62. package/scripts/agy-bridge-config.py +4 -1
  63. package/scripts/build-bridge.sh +14 -0
  64. package/scripts/check-capability-bundle-reach.ts +135 -2
  65. package/scripts/check-entwurf-bridge-boot.ts +2 -1
  66. package/scripts/check-entwurf-fact-provider.ts +1 -0
  67. package/scripts/check-entwurf-v2-production.ts +2 -0
  68. package/scripts/check-entwurf-v2-surface.ts +7 -0
  69. package/scripts/check-gate-qualification.ts +14 -8
  70. package/scripts/check-herdr-activation.ts +2 -2
  71. package/scripts/check-herdr-placement.ts +1 -0
  72. package/scripts/check-hook-launch-topology.ts +6 -15
  73. package/scripts/check-install-container.sh +2 -2
  74. package/scripts/check-meta-doctor-oracle.sh +27 -1
  75. package/scripts/check-meta-facts.ts +8 -3
  76. package/scripts/check-meta-hook-session-switch.ts +230 -13
  77. package/scripts/check-meta-identity-consumers.ts +1 -0
  78. package/scripts/check-meta-listing.ts +1 -0
  79. package/scripts/check-meta-manifest-schema.py +1 -1
  80. package/scripts/check-meta-session.ts +31 -0
  81. package/scripts/check-meta-v3-record.ts +55 -1
  82. package/scripts/check-mux-resume-call.ts +35 -9
  83. package/scripts/check-peer-facts.ts +1 -0
  84. package/scripts/check-pi-durable-contact.ts +3 -1
  85. package/scripts/check-pi-durable-send.ts +4 -2
  86. package/scripts/check-pi-mcp-bridge.ts +1 -0
  87. package/scripts/check-resume-launch-identity.ts +62 -23
  88. package/scripts/lib/meta-hook-bundle.ts +72 -0
  89. package/scripts/meta-bridge-claude-floor.sh +13 -9
  90. package/scripts/meta-bridge-doctor.sh +17 -0
  91. package/scripts/meta-bridge-install.sh +6 -0
  92. package/scripts/meta-facts.ts +1 -1
  93. package/scripts/mutants/bridge-boot-resume.json +5 -2
  94. package/scripts/mutants/cc-retire-pidfd.json +129 -0
  95. package/scripts/mutants/cc-retire-request.json +171 -0
  96. package/scripts/mutants/control-retire.json +105 -0
  97. package/scripts/mutants/copilot-receive.json +2 -6
  98. package/scripts/mutants/entwurf-v2-retire.json +476 -0
  99. package/scripts/mutants/fresh-call-dispatch.json +37 -0
  100. package/scripts/mutants/meta-hook-session-switch.json +88 -0
  101. package/scripts/mutants/meta-identity.json +57 -0
  102. package/scripts/mutants/mux-fresh-call.json +1 -1
  103. package/scripts/mutants/mux-resume-call.json +40 -4
  104. package/scripts/mutants/pi-durable.json +79 -0
  105. package/scripts/mutants/pi-retire-request.json +72 -0
  106. package/scripts/mutants/resume-launch-identity.json +35 -8
  107. package/scripts/mutants/retirement-event.json +195 -0
  108. package/scripts/mutants/v2-surface.json +16 -0
  109. package/scripts/mutants/v2-visible-resume.json +283 -13
  110. package/scripts/probe-bridge-command.ts +1 -0
  111. package/scripts/smoke-agy-install-state.sh +2 -2
  112. package/scripts/smoke-copilot-mcp-state.sh +1 -1
  113. package/scripts/smoke-herdr-raw-install-live.sh +5 -4
  114. package/scripts/smoke-meta-install-state.sh +4 -4
  115. package/scripts/smoke-mux-lifecycle-live.ts +10 -10
  116. package/scripts/smoke-omp-mcp-state.sh +1 -1
  117. package/scripts/smoke-pi-durable-fresh-live.ts +5 -3
  118. package/scripts/smoke-pi-provider-state.sh +3 -3
@@ -73,8 +73,16 @@
73
73
  "claim": "V2RESUME-OBSERVE-IS-BOUNDED-WAIT",
74
74
  "title": "the observation becomes a single immediate probe, which reports every real resume as unobserved (the socket answers ~2-4s later)",
75
75
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
76
- "find": ["\t\tawait deps.sleep(intervalMs);"],
77
- "replace": ["\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"],
76
+ "find": [
77
+ "\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };",
78
+ "\t\t}",
79
+ "\t\tawait deps.sleep(intervalMs);"
80
+ ],
81
+ "replace": [
82
+ "\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };",
83
+ "\t\t}",
84
+ "\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"
85
+ ],
78
86
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
79
87
  "timeoutSeconds": 60,
80
88
  "signature": "[QK:V2RESUME-OBSERVE-IS-BOUNDED-WAIT]",
@@ -106,8 +114,8 @@
106
114
  "claim": "V2RESUME-ARGV-FROM-RECORD",
107
115
  "title": "the launcher is handed the caller's own directory instead of the record's, so the citizen returns in the wrong project",
108
116
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
109
- "find": ["\t\tconst launched = deps.launch({ cwd: identity.cwd, runtimeArgs });"],
110
- "replace": ["\t\tconst launched = deps.launch({ cwd: process.cwd(), runtimeArgs });"],
117
+ "find": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
118
+ "replace": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: process.cwd(), runtimeArgs });"],
111
119
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
112
120
  "timeoutSeconds": 60,
113
121
  "signature": "[QK:V2RESUME-ARGV-FROM-RECORD]",
@@ -117,9 +125,11 @@
117
125
  "claim": "V2RESUME-RECEIPTS-SEPARATE",
118
126
  "title": "the launch receipt absorbs the observation, so 'a window opened' and 'the citizen is back' become one claim",
119
127
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
120
- "find": ["\t\t\tlaunch: { targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle },"],
128
+ "find": [
129
+ "\t\t\tlaunch: { backend: \"pi\", targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle },"
130
+ ],
121
131
  "replace": [
122
- "\t\t\tlaunch: { targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle, kind: observation.kind } as ResumeLaunchReceipt,"
132
+ "\t\t\tlaunch: { backend: \"pi\", targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle, kind: observation.kind } as ResumeLaunchReceipt,"
123
133
  ],
124
134
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
125
135
  "timeoutSeconds": 60,
@@ -156,7 +166,8 @@
156
166
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
157
167
  "find": ["\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"],
158
168
  "replace": [
159
- "\t\t\t(deps as VisibleResumeDeps).launch({ cwd: \"/tmp\", runtimeArgs: [] });\n\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"
169
+ "\t\t\t(deps as VisibleResumeDeps).launch({ backend: \"pi\", cwd: \"/tmp\", runtimeArgs: [] });",
170
+ "\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"
160
171
  ],
161
172
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
162
173
  "timeoutSeconds": 60,
@@ -175,29 +186,29 @@
175
186
  "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
176
187
  },
177
188
  {
178
- "claim": "V2RESUME-NOT-PI-REFUSED",
189
+ "claim": "V2RESUME-BACKEND-UNSUPPORTED-REFUSED",
179
190
  "title": "an out-of-domain backend is reported as an error instead of a named refusal, so a fine record reads as a broken one",
180
191
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
181
192
  "find": ["\t\t\tif (err instanceof ResumeBackendUnsupportedError) {"],
182
193
  "replace": ["\t\t\tif (false && err instanceof ResumeBackendUnsupportedError) {"],
183
194
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
184
195
  "timeoutSeconds": 60,
185
- "signature": "[QK:V2RESUME-NOT-PI-REFUSED]",
196
+ "signature": "[QK:V2RESUME-BACKEND-UNSUPPORTED-REFUSED]",
186
197
  "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
187
198
  },
188
199
  {
189
- "claim": "V2RESUME-NOT-PI-BY-FIELD",
200
+ "claim": "V2RESUME-BACKEND-UNSUPPORTED-BY-FIELD",
190
201
  "title": "the backend is re-derived instead of taken from the typed error, which is the second read this rail refuses",
191
202
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
192
203
  "find": [
193
- "\t\t\t\t\t\t`${targetGardenId} is a ${err.backend} citizen; only pi citizens stand a control socket up, ` +"
204
+ "\t\t\t\t\t\t`${targetGardenId} is a ${err.backend} citizen; only pi and Claude Code citizens can be reopened ` +"
194
205
  ],
195
206
  "replace": [
196
- "\t\t\t\t\t\t`${targetGardenId} is a citizen of another backend; only pi citizens stand a control socket up, ` +"
207
+ "\t\t\t\t\t\t`${targetGardenId} is a citizen of another backend; only pi and Claude Code citizens can be reopened ` +"
197
208
  ],
198
209
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
199
210
  "timeoutSeconds": 60,
200
- "signature": "[QK:V2RESUME-NOT-PI-BY-FIELD]",
211
+ "signature": "[QK:V2RESUME-BACKEND-UNSUPPORTED-BY-FIELD]",
201
212
  "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
202
213
  },
203
214
  {
@@ -221,6 +232,265 @@
221
232
  "timeoutSeconds": 60,
222
233
  "signature": "[QK:V2RESUME-ACP-MODEL-REFUSED]",
223
234
  "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
235
+ },
236
+ {
237
+ "claim": "V2RESUME-CC-DEAD-OWNER-LAUNCHES",
238
+ "title": "a Claude Code resume is launched as `claude --continue`, which reopens whichever session the vendor picks rather than the one the record names",
239
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
240
+ "find": ["\t\truntimeArgs: [\"--resume\", current.nativeSessionId],"],
241
+ "replace": ["\t\truntimeArgs: [\"--continue\"],"],
242
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
243
+ "timeoutSeconds": 60,
244
+ "signature": "[QK:V2RESUME-CC-DEAD-OWNER-LAUNCHES]",
245
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
246
+ },
247
+ {
248
+ "claim": "V2RESUME-CC-LIVE-REFUSED",
249
+ "title": "a live Claude Code owner stops being refused as live, so the caller is told to wait on an inconclusive probe instead of sending to the running session",
250
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
251
+ "find": ["\tif (before.owner === \"live\") {"],
252
+ "replace": ["\tif (before.owner === (\"never\" as string)) {"],
253
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
254
+ "timeoutSeconds": 60,
255
+ "signature": "[QK:V2RESUME-CC-LIVE-REFUSED]",
256
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
257
+ },
258
+ {
259
+ "claim": "V2RESUME-CC-UNCERTAIN-REFUSED",
260
+ "title": "an owner the shared rule cannot judge is treated as dormant, which can open a second process on a session that is still running",
261
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
262
+ "find": ["\tif (before.owner !== \"dead\") {"],
263
+ "replace": ["\tif (before.owner === (\"never\" as string)) {"],
264
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
265
+ "timeoutSeconds": 60,
266
+ "signature": "[QK:V2RESUME-CC-UNCERTAIN-REFUSED]",
267
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
268
+ },
269
+ {
270
+ "claim": "V2RESUME-CC-ABSENT-REFUSED",
271
+ "title": "a Claude Code citizen with no receiver marker is not refused by name, so a session nothing proves dormant falls through the dormancy proof",
272
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
273
+ "find": ["\tif (before.kind === \"absent\") {"],
274
+ "replace": ["\tif (before.kind === (\"never\" as string)) {"],
275
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
276
+ "timeoutSeconds": 60,
277
+ "signature": "[QK:V2RESUME-CC-ABSENT-REFUSED]",
278
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
279
+ },
280
+ {
281
+ "claim": "V2RESUME-CC-CORRUPT-REFUSED",
282
+ "title": "an unreadable receiver marker is not refused by name, so a marker whose owner cannot be judged falls through the dormancy proof",
283
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
284
+ "find": ["\tif (before.kind === \"corrupt\") {"],
285
+ "replace": ["\tif (before.kind === (\"never\" as string)) {"],
286
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
287
+ "timeoutSeconds": 60,
288
+ "signature": "[QK:V2RESUME-CC-CORRUPT-REFUSED]",
289
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
290
+ },
291
+ {
292
+ "claim": "V2RESUME-CC-MISMATCH-REFUSED",
293
+ "title": "a dead marker for ANOTHER native session is accepted as this session's dormancy proof",
294
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
295
+ "find": ["\t\tmarker.backend !== \"claude-code\" ||", "\t\tmarker.nativeSessionId !== identity.nativeSessionId"],
296
+ "replace": ["\t\tmarker.backend !== \"claude-code\" ||", "\t\tfalse"],
297
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
298
+ "timeoutSeconds": 60,
299
+ "signature": "[QK:V2RESUME-CC-MISMATCH-REFUSED]",
300
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
301
+ },
302
+ {
303
+ "claim": "V2RESUME-CC-OBSERVES-NEW-OWNER",
304
+ "title": "the observation accepts the previous owner's marker as the resumed session coming back",
305
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
306
+ "find": [
307
+ "\t\t\t(seen.marker.ownerPid !== previous.ownerPid || seen.marker.ownerStartKey !== previous.ownerStartKey) &&"
308
+ ],
309
+ "replace": ["\t\t\ttrue &&"],
310
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
311
+ "timeoutSeconds": 60,
312
+ "signature": "[QK:V2RESUME-CC-OBSERVES-NEW-OWNER]",
313
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
314
+ },
315
+ {
316
+ "claim": "V2RESUME-CC-RECORD-DRIFT-UNOBSERVED",
317
+ "title": "the observation stops checking that the record still names the same session with the same createdAt",
318
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
319
+ "find": ["\t\t\t\tcurrent.createdAt === identity.createdAt &&"],
320
+ "replace": ["\t\t\t\ttrue &&"],
321
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
322
+ "timeoutSeconds": 60,
323
+ "signature": "[QK:V2RESUME-CC-RECORD-DRIFT-UNOBSERVED]",
324
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
325
+ },
326
+ {
327
+ "claim": "V2RESUME-CC-MARKER-READ-RAW",
328
+ "title": "the receiver marker is read through a path that follows symlinks instead of the no-follow fd reader, so a link to another citizen's marker answers for this one",
329
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
330
+ "find": ["\t\tbytes = readStoreRecordFile(markerPath);"],
331
+ "replace": ["\t\tbytes = process.getBuiltinModule(\"node:fs\").readFileSync(markerPath, \"utf8\");"],
332
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
333
+ "timeoutSeconds": 60,
334
+ "signature": "[QK:V2RESUME-CC-MARKER-READ-RAW]",
335
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
336
+ },
337
+ {
338
+ "claim": "V2RESUME-PI-NEVER-READS-RECEIVER",
339
+ "title": "the pi path consults a Claude Code receiver marker, mixing the two backends' dormancy proofs",
340
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
341
+ "find": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
342
+ "replace": [
343
+ "\t\tdeps.inspectReceiver(targetGardenId);",
344
+ "\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"
345
+ ],
346
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
347
+ "timeoutSeconds": 60,
348
+ "signature": "[QK:V2RESUME-PI-NEVER-READS-RECEIVER]",
349
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
350
+ },
351
+ {
352
+ "claim": "V2RESUME-CC-RENDER",
353
+ "title": "the Claude Code launch receipt stops saying it asked for claude --resume, so a reader cannot tell which session the window was asked to open",
354
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
355
+ "find": [
356
+ "\t\t\t\t`LAUNCH receipt: tmux created that window and was asked to start claude --resume on the session above.\\n` +"
357
+ ],
358
+ "replace": ["\t\t\t\t`LAUNCH receipt: tmux created that window.\\n` +"],
359
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
360
+ "timeoutSeconds": 60,
361
+ "signature": "[QK:V2RESUME-CC-RENDER]",
362
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
363
+ },
364
+ {
365
+ "claim": "V2RESUME-CC-OBSERVE-HELPER-BOUNDED",
366
+ "title": "the Claude Code observation's deadline reports the previous owner as re-armed instead of unobserved",
367
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
368
+ "find": ["\t\t\treturn { kind: \"resume-unobserved\", waitedMs: deps.now() - started };"],
369
+ "replace": [
370
+ "\t\t\treturn { kind: \"receiver-armed\", ownerPid: previous.ownerPid, waitedMs: deps.now() - started };"
371
+ ],
372
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
373
+ "timeoutSeconds": 60,
374
+ "signature": "[QK:V2RESUME-CC-OBSERVE-HELPER-BOUNDED]",
375
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
376
+ },
377
+ {
378
+ "claim": "V2RESUME-CC-BINDING-REREAD",
379
+ "title": "the binding is compared against the FIRST record read, so an arm committed between that read and the dead verdict is bound by a stale value",
380
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
381
+ "find": ["\tconst current = deps.resolveIdentity(targetGardenId);"],
382
+ "replace": ["\tconst current = identity;"],
383
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
384
+ "timeoutSeconds": 60,
385
+ "signature": "[QK:V2RESUME-CC-BINDING-REREAD]",
386
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
387
+ },
388
+ {
389
+ "claim": "V2RESUME-CC-UNBOUND-LEGACY",
390
+ "title": "a record or marker with no binding (null) passes the binding check, so null == null reads as a match",
391
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
392
+ "find": ["\tif (current.attachIncarnation === null || marker.attachIncarnation === null) {"],
393
+ "replace": ["\tif (false) {"],
394
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
395
+ "timeoutSeconds": 60,
396
+ "signature": "[QK:V2RESUME-CC-UNBOUND-LEGACY]",
397
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
398
+ },
399
+ {
400
+ "claim": "V2RESUME-CC-UNBOUND-STALE",
401
+ "title": "a dead marker whose binding is older than the record's still authorises a launch",
402
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
403
+ "find": ["\tif (current.attachIncarnation !== marker.attachIncarnation) {"],
404
+ "replace": ["\tif (false) {"],
405
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
406
+ "timeoutSeconds": 60,
407
+ "signature": "[QK:V2RESUME-CC-UNBOUND-STALE]",
408
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
409
+ },
410
+ {
411
+ "claim": "V2RESUME-CC-UNBOUND-RECORD-MOVED",
412
+ "title": "a record whose createdAt changed between the two reads is still bound",
413
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
414
+ "find": ["\t\tcurrent.createdAt !== identity.createdAt"],
415
+ "replace": ["\t\tfalse"],
416
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
417
+ "timeoutSeconds": 60,
418
+ "signature": "[QK:V2RESUME-CC-UNBOUND-RECORD-MOVED]",
419
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
420
+ },
421
+ {
422
+ "claim": "V2RESUME-CC-OBSERVE-PANE-JOIN",
423
+ "title": "the observation accepts a live owner anywhere, not only the pane this call opened",
424
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
425
+ "find": ["\t\t\tString(seen.marker.ownerPid) === panePid &&"],
426
+ "replace": [""],
427
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
428
+ "timeoutSeconds": 60,
429
+ "signature": "[QK:V2RESUME-CC-OBSERVE-PANE-JOIN]",
430
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
431
+ },
432
+ {
433
+ "claim": "V2RESUME-CC-OBSERVE-BACKEND",
434
+ "title": "the observation accepts a live receiver marker of another backend",
435
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
436
+ "find": ["\t\t\tseen.marker.backend === \"claude-code\" &&"],
437
+ "replace": [""],
438
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
439
+ "timeoutSeconds": 60,
440
+ "signature": "[QK:V2RESUME-CC-OBSERVE-BACKEND]",
441
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
442
+ },
443
+ {
444
+ "claim": "V2RESUME-CC-OBSERVE-FRESH-BINDING",
445
+ "title": "the observation accepts a new owner still showing the binding the dormancy proof bound",
446
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
447
+ "find": ["\t\t\tseen.marker.attachIncarnation !== previous.attachIncarnation"],
448
+ "replace": ["\t\t\ttrue"],
449
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
450
+ "timeoutSeconds": 60,
451
+ "signature": "[QK:V2RESUME-CC-OBSERVE-FRESH-BINDING]",
452
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
453
+ },
454
+ {
455
+ "claim": "V2RESUME-CC-OBSERVE-RECORD-BINDING",
456
+ "title": "the observation stops requiring the current record to carry the marker's binding",
457
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
458
+ "find": ["\t\t\t\tcurrent.attachIncarnation === seen.marker.attachIncarnation"],
459
+ "replace": ["\t\t\t\ttrue"],
460
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
461
+ "timeoutSeconds": 60,
462
+ "signature": "[QK:V2RESUME-CC-OBSERVE-RECORD-BINDING]",
463
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
464
+ },
465
+ {
466
+ "claim": "V2RESUME-CC-OBSERVE-RECORD-UNREADABLE",
467
+ "title": "a record defect during the observation is swallowed into the bounded wait instead of stopping it as record-unreadable",
468
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
469
+ "find": [
470
+ "\t\t\t\treturn {",
471
+ "\t\t\t\t\tkind: \"record-unreadable\",",
472
+ "\t\t\t\t\tcause: err instanceof Error ? err.message : String(err),",
473
+ "\t\t\t\t\twaitedMs: deps.now() - started,",
474
+ "\t\t\t\t};"
475
+ ],
476
+ "replace": ["\t\t\t\tcurrent = identity;"],
477
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
478
+ "timeoutSeconds": 60,
479
+ "signature": "[QK:V2RESUME-CC-OBSERVE-RECORD-UNREADABLE]",
480
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
481
+ },
482
+ {
483
+ "claim": "V2RESUME-CC-MARKER-BINDING-READ",
484
+ "title": "the receiver-marker validator drops the marker's binding, so every marker reads as unbound",
485
+ "subject": "pi-extensions/lib/meta-session.ts",
486
+ "find": [
487
+ "\t\tattachIncarnation: requireAttachIncarnation(raw.attachIncarnation === undefined ? null : raw.attachIncarnation),"
488
+ ],
489
+ "replace": ["\t\tattachIncarnation: null,"],
490
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
491
+ "timeoutSeconds": 60,
492
+ "signature": "[QK:V2RESUME-CC-MARKER-BINDING-READ]",
493
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
224
494
  }
225
495
  ]
226
496
  }
@@ -67,6 +67,7 @@ export const EXPECTED_TOOLS = [
67
67
  "entwurf_register_native",
68
68
  "entwurf_fresh_call",
69
69
  "entwurf_resume_call",
70
+ "entwurf_retire_call",
70
71
  "entwurf_callback",
71
72
  ] as const;
72
73
  const DEFAULT_TIMEOUT_MS = 10_000;
@@ -67,7 +67,7 @@ write_mcp_fake() { # $1 = path
67
67
  while IFS= read -r line; do
68
68
  case "$line" in
69
69
  *'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
70
- *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
70
+ *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
71
71
  esac
72
72
  done
73
73
  FAKE
@@ -89,7 +89,7 @@ if [ "${REVIEW_ENV:-}" = "fail" ]; then echo configured-env-failure >&2; exit 24
89
89
  while IFS= read -r line; do
90
90
  case "$line" in
91
91
  *'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
92
- *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
92
+ *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
93
93
  esac
94
94
  done
95
95
  FAKE
@@ -26,7 +26,7 @@ write_mcp_fake() {
26
26
  while IFS= read -r line; do
27
27
  case "$line" in
28
28
  *'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
29
- *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
29
+ *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
30
30
  esac
31
31
  done
32
32
  FAKE
@@ -490,8 +490,8 @@ for (const name of ["entwurf", "entwurf-bridge", "entwurf-statusline"]) {
490
490
  }
491
491
 
492
492
  // The REAL subcommand, from the installed bin, under node_modules — which is the branch of
493
- // start.sh that runs the prebuilt dist. Its own oracle is an EXACT set (`EXPECT_TOOLS` in run.sh check-bridge), so a
494
- // zero exit here is the artifact listing all eight garden verbs and no ninth.
493
+ // start.sh that runs the prebuilt dist. Its own oracle is an EXACT set (`expected` in run.sh `validate_entwurf_bridge`, which check-bridge runs), so a
494
+ // zero exit here is the artifact listing all nine garden verbs and no tenth.
495
495
  const run = spawnSync(path.join(binDir, "entwurf"), ["check-bridge"], { encoding: "utf8" });
496
496
  const said = `${run.stdout ?? ""}${run.stderr ?? ""}`;
497
497
  // [a-z0-9_] and not [a-z_]: the first draft of this line printed `entwurf_v` for entwurf_v2
@@ -502,7 +502,7 @@ const verbs = (said.match(/entwurf_[a-z0-9_]+/g) ?? []).filter((v, i, a) => a.in
502
502
  // A SECOND, INDEPENDENT COPY OF THE SET, ON PURPOSE (A5 D1). The exit code alone used to be
503
503
  // the oracle here, which left the printed list as decoration a parser bug could quietly
504
504
  // corrupt — and did, see the regex note just above. The package under test carries the
505
- // expectation of run.sh:5187 in its own bytes, so reading the answer from there would let
505
+ // `validate_entwurf_bridge` expectation of run.sh in its own bytes, so reading the answer from there would let
506
506
  // the artifact grade itself. The literal below is a copy that must AGREE with the shipped
507
507
  // one; the two disagreeing is the signal, and a verb genuinely added upstream reddens this
508
508
  // smoke until somebody says so here too.
@@ -513,6 +513,7 @@ const EXPECTED_VERBS = [
513
513
  "entwurf_peers",
514
514
  "entwurf_register_native",
515
515
  "entwurf_resume_call",
516
+ "entwurf_retire_call",
516
517
  "entwurf_self",
517
518
  "entwurf_v2",
518
519
  ];
@@ -520,7 +521,7 @@ console.log(` VERBS ${verbs.join(",") || "<none>"}`);
520
521
  if (run.status === 0) ok(`entwurf check-bridge exit 0 from the installed bin (${verbs.length} verbs listed)`);
521
522
  else no(`entwurf check-bridge exit ${run.status}: ${said.trim().slice(0, 400)}`);
522
523
  JSON.stringify(verbs) === JSON.stringify(EXPECTED_VERBS)
523
- ? ok(`the receipt names EXACTLY the eight garden verbs (${EXPECTED_VERBS.length}), parsed from what the installed bin answered`)
524
+ ? ok(`the receipt names EXACTLY the nine garden verbs (${EXPECTED_VERBS.length}), parsed from what the installed bin answered`)
524
525
  : no(`verb set mismatch — want ${JSON.stringify(EXPECTED_VERBS)} got ${JSON.stringify(verbs)}`);
525
526
  process.exit(bad);
526
527
  ' "$ACTIVE" "$REGISTRY" "$PLUGIN_ID" || fail=1
@@ -46,7 +46,7 @@ cat > "$INS_BIN/claude" <<'SH'
46
46
  #!/usr/bin/env bash
47
47
  printf '%s\n' "$*" >> "$FAKE_CLAUDE_LOG"
48
48
  case "$1${2:+ $2}" in
49
- "--version") echo "2.1.217 (Claude Code)" ;;
49
+ "--version") echo "2.1.306 (Claude Code)" ;;
50
50
  "plugin list") printf '%s\n' "entwurf-meta-receive@meta-bridge-local" " Status: enabled" ;;
51
51
  "mcp get") printf '%s\n' "Scope: User config" "Status: Connected" ;;
52
52
  *) : ;;
@@ -95,7 +95,7 @@ case "$1${2:+ $2}" in
95
95
  # Keep writing well past the pipe buffer: a reader that leaves early SIGPIPEs us.
96
96
  cat "$FAKE_MCP_TAIL"
97
97
  exit $? ;;
98
- "--version") echo "2.1.217 (Claude Code)" ;;
98
+ "--version") echo "2.1.306 (Claude Code)" ;;
99
99
  "plugin list") printf '%s\n' "entwurf-meta-receive@meta-bridge-local" " Status: enabled" ;;
100
100
  *) : ;;
101
101
  esac
@@ -710,7 +710,7 @@ FAKE_BIN="$TMP/fake-bin"; mkdir -p "$FAKE_BIN"
710
710
  cat > "$FAKE_BIN/claude" <<'SH'
711
711
  #!/usr/bin/env bash
712
712
  printf '%s\n' "$*" >> "$FAKE_CLAUDE_LOG"
713
- [ "$1" = "--version" ] && echo "2.1.217 (Claude Code)"
713
+ [ "$1" = "--version" ] && echo "2.1.306 (Claude Code)"
714
714
  exit 0
715
715
  SH
716
716
  chmod +x "$FAKE_BIN/claude"
@@ -1027,7 +1027,7 @@ if [ "${1:-} ${2:-} ${3:-}" = "plugin list --json" ]; then
1027
1027
  exit 0
1028
1028
  fi
1029
1029
  case "$1${2:+ $2}" in
1030
- "--version") echo "2.1.217 (Claude Code)" ;;
1030
+ "--version") echo "2.1.306 (Claude Code)" ;;
1031
1031
  "plugin list") printf '%s\n' "entwurf-meta-receive@meta-bridge-local" " Status: enabled" ;;
1032
1032
  "mcp get") printf '%s\n' "Scope: User config" "Status: ✔ Connected" ;;
1033
1033
  *) : ;;
@@ -34,11 +34,11 @@
34
34
  * re-injects a bridge extension (#29), and a resume that silently lost its history would look
35
35
  * identical from the socket axis.
36
36
  *
37
- * Claude Code runs the same first half and then STOPS where its capability stops: callback id,
38
- * mailbox delivery, mailbox read receipt — and a resume that is REFUSED as `target-not-pi`, with
39
- * no window opened and no lock residue after the per-gid coordination lock is released. Only pi
40
- * stands a control socket up, so only pi has a same-id
41
- * resume; the refusal is the proof, not an omission.
37
+ * Claude Code runs the same first half: callback id, mailbox delivery, mailbox read receipt — and a
38
+ * resume of that still-LIVE sibling that is REFUSED as `target-live` (its receiver marker names a
39
+ * live owner), with no window opened and no lock residue after the per-gid coordination lock is
40
+ * released. Claude Code's DORMANT same-id resume (#147 L2) is certified deterministically; its LIVE
41
+ * close→resume leg is not in this smoke yet, and this cell does not stand in for it.
42
42
  *
43
43
  * ── Isolate the WRITES, keep the runtimes real ──
44
44
  *
@@ -1035,20 +1035,20 @@ async function main(): Promise<void> {
1035
1035
  `--- response ---\n${ccRead.text}`,
1036
1036
  );
1037
1037
 
1038
- // No resume is invented for a backend that never stands a control socket up. The refusal is
1039
- // the proof: it opens no window, and its per-gid coordination lock leaves no residue.
1038
+ // A live Claude Code sibling is not reopened: its receiver marker names a live owner. The refusal
1039
+ // is the proof: it opens no window, and its per-gid coordination lock leaves no residue.
1040
1040
  const ccWindowsBefore = windowCount(cc.socket, cc.env);
1041
1041
  const ccResume = await ccBridge.call("entwurf_resume_call", { target: ccCitizen }, RESUME_CALL_TIMEOUT_MS);
1042
1042
  ok(
1043
- "claude-code: entwurf_resume_call is REFUSED as target-not-pi — a capability boundary named as itself, not an error about a record that is perfectly fine",
1044
- ccResume.isError && ccResume.text.includes("target-not-pi"),
1043
+ "claude-code: entwurf_resume_call of the still-live sibling is REFUSED as target-live — its receiver marker names a live owner, so a second window would put two processes on one session",
1044
+ ccResume.isError && ccResume.text.includes("target-live"),
1045
1045
  `--- response ---\n${ccResume.text}`,
1046
1046
  );
1047
1047
  ok(
1048
1048
  "claude-code: that refusal opened no window and left no lock behind",
1049
1049
  windowCount(cc.socket, cc.env) === ccWindowsBefore && lockResidue(cellRoots(REAL_HOME), ccCitizen).length === 0,
1050
1050
  );
1051
- receipts["claude-code/5-resume-refused-not-pi"] = ccResume.text;
1051
+ receipts["claude-code/5-resume-refused-live"] = ccResume.text;
1052
1052
  }
1053
1053
 
1054
1054
  await runClaudeCell();
@@ -53,7 +53,7 @@ cat > "$SB/bin/entwurf-bridge" <<'FAKE'
53
53
  while IFS= read -r line; do
54
54
  case "$line" in
55
55
  *'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
56
- *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
56
+ *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
57
57
  esac
58
58
  done
59
59
  FAKE
@@ -24,7 +24,7 @@
24
24
  * Pi its own JSONL toolCall/toolResult rows and `entwurf-message` custom rows.
25
25
  * pd its NATIVE store, read from a consistent snapshot taken with node:sqlite's online
26
26
  * `backup` while the host is live (a raw copy of a live WAL set is not consistent):
27
- * the `pi.system` entries' `toolsAdded` (the actual offering — task-wide means the six
27
+ * the `pi.system` entries' `toolsAdded` (the actual offering — task-wide means the eight
28
28
  * contact tools AND the native tools, no filter), the generation checkpoint's
29
29
  * `request.model` and the assistant messages' provider/model (the actual model), the
30
30
  * contact tool calls, and the doorbell submission. The live database and its transcript
@@ -74,7 +74,7 @@ const DIST_ENTRY = path.join(REPO, "mcp/entwurf-bridge/dist/mcp/entwurf-bridge/s
74
74
  const PD_MODEL = process.env.ENTWURF_PI_DURABLE_FRESH_MODEL?.trim() || "openai-codex/gpt-6.1-sol";
75
75
  const PI_MODEL = process.env.ENTWURF_PI_DURABLE_FRESH_PI_MODEL?.trim() || "openai-codex/gpt-6.1-sol";
76
76
  const WIDTH = "task-wide";
77
- /** Independent of the adapter's own constant: the six contact tools step 9 expects the host to offer. */
77
+ /** Independent of the adapter's own constant: the eight contact tools step 9 expects the host to offer. */
78
78
  const CONTACT_TOOLS = [
79
79
  "entwurf_self",
80
80
  "entwurf_peers",
@@ -82,6 +82,8 @@ const CONTACT_TOOLS = [
82
82
  "entwurf_inbox_read",
83
83
  "entwurf_callback",
84
84
  "entwurf_fresh_call",
85
+ "entwurf_resume_call",
86
+ "entwurf_retire_call",
85
87
  ];
86
88
  /** The native app's own tools; task-wide = no filter, so they must be offered beside the contact. */
87
89
  const NATIVE_TOOLS = ["bash", "read", "edit", "write", "subagent"];
@@ -584,7 +586,7 @@ const rootId = (roots[0] as { id: number }).id;
584
586
  const view = pdRootView(nativeEntries, rootId);
585
587
  receipts["6-native-offering"] = view.offering.join(" ") || "(no pi.system offering)";
586
588
  ok(
587
- "ACTUAL OFFERING (root pi.system, toolsAdded/toolsRemoved applied): the six contact tools AND the native tools — task-wide, no filter",
589
+ "ACTUAL OFFERING (root pi.system, toolsAdded/toolsRemoved applied): the eight contact tools AND the native tools — task-wide, no filter",
588
590
  CONTACT_TOOLS.every((t) => view.offering.includes(t)) && NATIVE_TOOLS.every((t) => view.offering.includes(t)),
589
591
  `offered: ${view.offering.join(", ") || "(none)"}`,
590
592
  );
@@ -67,7 +67,7 @@ write_mcp_fake() { # $1 = path
67
67
  while IFS= read -r line; do
68
68
  case "$line" in
69
69
  *'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
70
- *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
70
+ *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
71
71
  esac
72
72
  done
73
73
  FAKE
@@ -93,7 +93,7 @@ write_stale_fake() { # $1 = path
93
93
  while IFS= read -r line; do
94
94
  case "$line" in
95
95
  *'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"stale-bridge","version":"0"}}}' ;;
96
- *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
96
+ *'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
97
97
  esac
98
98
  done
99
99
  FAKE
@@ -289,7 +289,7 @@ rm -f "$STATE"
289
289
  # `ownership` field in that state is the PREIMAGE classification — what stood at the key BEFORE this
290
290
  # install — so `absent` means "we created it" and `managed-current` means "we rewrote our own", and
291
291
  # NEITHER means "unowned". A doctor whose only non-drift shape is the bare bin therefore calls every
292
- # plugin-mode host red while the bridge it points at boots and serves all eight verbs. The canonical
292
+ # plugin-mode host red while the bridge it points at boots and serves all nine verbs. The canonical
293
293
  # command is the one the state RECORDS; the stable runtime root is the writer's business and is
294
294
  # deliberately not re-derived here.
295
295
  PLUGIN_BRIDGE="$SB/runtime/active/node_modules/.bin/entwurf-bridge"