@junghanacs/entwurf 0.52.0 → 0.53.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.
Files changed (68) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +116 -397
  3. package/CHANGELOG.md +131 -2
  4. package/CONTRIBUTING.md +4 -4
  5. package/DELIVERY.md +2 -2
  6. package/ENTWURF-CLI.json +1 -1
  7. package/README.md +7 -4
  8. package/VERIFY.md +332 -142
  9. package/demo/demo-baseline.sh +1 -1
  10. package/demo/demo.sh +1 -1
  11. package/docs/acp-backend-rail.md +248 -34
  12. package/docs/setup-clean-host.md +50 -65
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +1 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +90 -14
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +4 -9
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +15 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +9 -12
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +41 -56
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +33 -6
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -1
  23. package/package.json +4 -4
  24. package/pi/settings.reference.json +0 -1
  25. package/pi-extensions/acp-provider.ts +12 -6
  26. package/pi-extensions/lib/acp/acp-client.ts +6 -7
  27. package/pi-extensions/lib/acp/backend-adapter.ts +139 -15
  28. package/pi-extensions/lib/acp/backend.ts +261 -32
  29. package/pi-extensions/lib/acp/config.ts +4 -9
  30. package/pi-extensions/lib/acp/context.ts +17 -0
  31. package/pi-extensions/lib/acp/event-mapper.ts +236 -17
  32. package/pi-extensions/lib/acp/model-ids.js +9 -12
  33. package/pi-extensions/lib/acp/models.ts +46 -57
  34. package/pi-extensions/lib/acp/tool-surface.ts +34 -6
  35. package/pi-extensions/lib/fresh-call-composition.ts +37 -3
  36. package/pi-extensions/lib/herdr-fresh-call.ts +3 -1
  37. package/pi-extensions/lib/mux-fresh-call.ts +3 -1
  38. package/run.sh +25 -86
  39. package/scripts/check-acp-carrier-augment.ts +3 -21
  40. package/scripts/check-acp-prompt-lifecycle.ts +24 -3
  41. package/scripts/check-acp-provider-surface.ts +17 -26
  42. package/scripts/check-acp-session-reuse.ts +23 -4
  43. package/scripts/check-acp-stop-reason.ts +22 -3
  44. package/scripts/check-acp-stream-hooks.ts +24 -3
  45. package/scripts/check-acp-usage-accounting.ts +53 -24
  46. package/scripts/check-copilot-receive-arm.ts +19 -2
  47. package/scripts/check-fresh-call-dispatch.ts +9 -3
  48. package/scripts/check-gate-qualification.ts +5 -5
  49. package/scripts/check-herdr-fresh-call.ts +1 -0
  50. package/scripts/check-install-surface.ts +6 -0
  51. package/scripts/check-probe-ordering.ts +34 -24
  52. package/scripts/check-release-gate-outcomes.ts +9 -23
  53. package/scripts/lib/probe-cli-target.ts +3 -2
  54. package/scripts/mutants/acp-augment.json +0 -26
  55. package/scripts/mutants/acp-cortex.json +10 -183
  56. package/scripts/mutants/acp-foreground-only.json +11 -0
  57. package/scripts/mutants/acp-launch-namespace.json +3 -1
  58. package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
  59. package/scripts/mutants/acp-set-model-readback.json +488 -0
  60. package/scripts/mutants/mux-fresh-call.json +12 -1
  61. package/scripts/mutants/pi-floor.json +2 -2
  62. package/scripts/mutants/release-gate.json +3 -3
  63. package/scripts/raw-acp-compaction-measure/probe.ts +8 -2
  64. package/scripts/smoke-acp-cortex-live.ts +4 -408
  65. package/scripts/smoke-acp-ordering-probe-live.ts +7 -1
  66. package/scripts/smoke-acp-session-reuse-live.ts +11 -2
  67. package/scripts/smoke-herdr-raw-install-live.sh +4 -4
  68. package/scripts/check-acp-cortex.ts +0 -710
@@ -23,6 +23,494 @@
23
23
  "timeoutSeconds": 60,
24
24
  "signature": "[QK:ACP-CLAUDE-SET-MODEL-ALIAS-EXACT]",
25
25
  "signatureSource": "pi-extensions/lib/acp/backend-adapter.test.ts"
26
+ },
27
+ {
28
+ "claim": "ACP-CLAUDE-THINKING-MENU",
29
+ "title": "the curated Claude rows register no thinkingLevelMap — pi's menu offers off/minimal the driver cannot carry and hides xhigh/max it can",
30
+ "subject": "pi-extensions/lib/acp/models.ts",
31
+ "find": ["\t\tthinkingLevelMap: claudeAcpThinkingLevelMap(m.thinkingLevelMap),"],
32
+ "replace": [""],
33
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
34
+ "timeoutSeconds": 60,
35
+ "signature": "[QK:ACP-CLAUDE-THINKING-MENU]",
36
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
37
+ },
38
+ {
39
+ "claim": "ACP-CLAUDE-THINKING-REFUSE-UNMAPPED",
40
+ "title": "any mapped string passes as an effort — a map value the driver does not offer reaches the wire instead of a named refusal",
41
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
42
+ "find": ["\tif (typeof value === \"string\" && CLAUDE_EFFORT_VALUE_SET.has(value)) return value;"],
43
+ "replace": ["\tif (typeof value === \"string\") return value;"],
44
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
45
+ "timeoutSeconds": 60,
46
+ "signature": "[QK:ACP-CLAUDE-THINKING-REFUSE-UNMAPPED]",
47
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
48
+ },
49
+ {
50
+ "claim": "ACP-CLAUDE-EFFORT-READBACK",
51
+ "title": "the set-effort answer is no longer read back — the turn runs at whatever effort the driver kept while pi shows the requested level",
52
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
53
+ "find": ["\t\tconfirmClaudeEffortAnswer(answer, modelId, value);"],
54
+ "replace": ["\t\tvoid answer;"],
55
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
56
+ "timeoutSeconds": 60,
57
+ "signature": "[QK:ACP-CLAUDE-EFFORT-READBACK]",
58
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
59
+ },
60
+ {
61
+ "claim": "ACP-THINKING-NEW-SET",
62
+ "title": "a new session never sets effort — the first turn runs at the vendor default whatever level pi selected",
63
+ "subject": "pi-extensions/lib/acp/backend.ts",
64
+ "find": [
65
+ "\t\t\t\t\tadapter.enforceThinking({ connection, acpSessionId, modelId: model.id, value: thinkingValue }),"
66
+ ],
67
+ "replace": ["\t\t\t\t\tPromise.resolve(),"],
68
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
69
+ "timeoutSeconds": 60,
70
+ "signature": "[QK:ACP-THINKING-NEW-SET]",
71
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
72
+ },
73
+ {
74
+ "claim": "ACP-THINKING-REUSE-SET",
75
+ "title": "a reused session never re-sets a changed level — the operator's new level is shown but the live conversation keeps the old effort",
76
+ "subject": "pi-extensions/lib/acp/backend.ts",
77
+ "find": [
78
+ "\t\t\tif (thinkingValue !== undefined && adapter.enforceThinking && session.thinkingValue !== thinkingValue) {"
79
+ ],
80
+ "replace": [
81
+ "\t\t\tif (thinkingValue !== undefined && adapter.enforceThinking && session.thinkingValue === undefined) {"
82
+ ],
83
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
84
+ "timeoutSeconds": 60,
85
+ "signature": "[QK:ACP-THINKING-REUSE-SET]",
86
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
87
+ },
88
+ {
89
+ "claim": "ACP-THINKING-READBACK-NOTICE",
90
+ "title": "a new session sets effort without telling the operator — the read-back level is invisible in pi, so a changed menu level and the driver's answer cannot be compared",
91
+ "subject": "pi-extensions/lib/acp/backend.ts",
92
+ "find": [
93
+ "\t\t\t\tsession.thinkingValue = thinkingValue;\n\t\t\t\t// Visible like the model lock: the operator sees the effort the driver read back.\n\t\t\t\tpushAcpLifecycleNotice(state, `effort ${thinkingValue} read back`);"
94
+ ],
95
+ "replace": ["\t\t\t\tsession.thinkingValue = thinkingValue;"],
96
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
97
+ "timeoutSeconds": 60,
98
+ "signature": "[QK:ACP-THINKING-READBACK-NOTICE]",
99
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
100
+ },
101
+ {
102
+ "claim": "ACP-THINKING-REUSE-ABORT",
103
+ "title": "an abort while a reuse turn waits for the effort answer is not heard — ESC settles only when the driver answers or the 30s bootstrap timeout fires",
104
+ "subject": "pi-extensions/lib/acp/backend.ts",
105
+ "find": ["\tif (!signal) return p;"],
106
+ "replace": ["\treturn p;"],
107
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
108
+ "timeoutSeconds": 120,
109
+ "signature": "[QK:ACP-THINKING-REUSE-ABORT]",
110
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
111
+ },
112
+ {
113
+ "claim": "ACP-CANCEL-NOTICE-REUSE-KEPT",
114
+ "title": "a reuse turn the agent cancelled keeps its live session without telling the operator — they cannot tell whether to continue or start over",
115
+ "subject": "pi-extensions/lib/acp/backend.ts",
116
+ "find": ["\t\t\t\tpushAcpLifecycleNotice(state, ACP_CANCEL_KEPT_NOTICE);"],
117
+ "replace": [""],
118
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
119
+ "timeoutSeconds": 60,
120
+ "signature": "[QK:ACP-CANCEL-NOTICE-REUSE-KEPT]",
121
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
122
+ },
123
+ {
124
+ "claim": "ACP-CANCEL-NOTICE-NEW-CLOSED",
125
+ "title": "a cancelled first turn claims the live session was kept although it was closed — the next message silently opens a new conversation",
126
+ "subject": "pi-extensions/lib/acp/backend.ts",
127
+ "find": ["\t\t\t\tpushAcpLifecycleNotice(state, retain ? ACP_CANCEL_KEPT_NOTICE : ACP_CANCEL_CLOSED_NOTICE);"],
128
+ "replace": ["\t\t\t\tpushAcpLifecycleNotice(state, ACP_CANCEL_KEPT_NOTICE);"],
129
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
130
+ "timeoutSeconds": 60,
131
+ "signature": "[QK:ACP-CANCEL-NOTICE-NEW-CLOSED]",
132
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
133
+ },
134
+ {
135
+ "claim": "ACP-THINKING-REUSE-ABORT-LATE",
136
+ "title": "an abort that arrives while a reuse turn waits for the effort answer is never heard — only an abort raised before the wait is honoured, so a real ESC waits for the driver or the 30s bound",
137
+ "subject": "pi-extensions/lib/acp/backend.ts",
138
+ "find": [
139
+ "\t\tonAbort = () => reject(new Error(\"entwurf: aborted by the operator before the prompt was sent\"));\n\t\tif (signal.aborted) onAbort();\n\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });"
140
+ ],
141
+ "replace": [
142
+ "\t\tonAbort = () => reject(new Error(\"entwurf: aborted by the operator before the prompt was sent\"));\n\t\tif (signal.aborted) onAbort();\n\t\telse void signal;"
143
+ ],
144
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
145
+ "timeoutSeconds": 90,
146
+ "signature": "[QK:ACP-THINKING-REUSE-ABORT-LATE]",
147
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
148
+ },
149
+ {
150
+ "claim": "ACP-REBUILD-ABORTED-STATUS",
151
+ "title": "a rebuilt session reads an aborted turn that left text as a finished one — the vendor's rejection text alone makes a started command look like it never ran",
152
+ "subject": "pi-extensions/lib/acp/context.ts",
153
+ "find": [
154
+ "\t\t\t\treturn text ? `Assistant: ${text}\\n${ABORTED_TURN_STATUS}` : `Assistant: ${ABORTED_TURN_STATUS}`;"
155
+ ],
156
+ "replace": ["\t\t\t\treturn text ? `Assistant: ${text}` : `Assistant: ${ABORTED_TURN_STATUS}`;"],
157
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
158
+ "timeoutSeconds": 60,
159
+ "signature": "[QK:ACP-REBUILD-ABORTED-STATUS]",
160
+ "signatureSource": "pi-extensions/lib/acp/context.test.ts"
161
+ },
162
+ {
163
+ "claim": "ACP-REBUILD-ABORTED-EMPTY",
164
+ "title": "an aborted turn that left no text vanishes from the rebuilt transcript — the new session never learns a turn was cut",
165
+ "subject": "pi-extensions/lib/acp/context.ts",
166
+ "find": [
167
+ "\t\t\t\treturn text ? `Assistant: ${text}\\n${ABORTED_TURN_STATUS}` : `Assistant: ${ABORTED_TURN_STATUS}`;"
168
+ ],
169
+ "replace": ["\t\t\t\treturn text ? `Assistant: ${text}\\n${ABORTED_TURN_STATUS}` : undefined;"],
170
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
171
+ "timeoutSeconds": 60,
172
+ "signature": "[QK:ACP-REBUILD-ABORTED-EMPTY]",
173
+ "signatureSource": "pi-extensions/lib/acp/context.test.ts"
174
+ },
175
+ {
176
+ "claim": "ACP-REUSE-LATE-UPDATE-PRE-PROMPT",
177
+ "title": "a reuse turn routes the live session's updates to its stream before its own prompt is sent — a late update of the previous, cancelled turn arriving during the effort set is shown and sealed as this turn's answer",
178
+ "subject": "pi-extensions/lib/acp/backend.ts",
179
+ "find": ["\t\t\tpushAcpLifecycleNotice(state, \"reusing live session\");\n\t\t\tsession.busy = true;"],
180
+ "replace": [
181
+ "\t\t\tpushAcpLifecycleNotice(state, \"reusing live session\");\n\t\t\tsession.busy = true;\n\t\t\tsession.activePromptHandler = makePromptHandler(session);"
182
+ ],
183
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
184
+ "timeoutSeconds": 60,
185
+ "signature": "[QK:ACP-REUSE-LATE-UPDATE-PRE-PROMPT]",
186
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
187
+ },
188
+ {
189
+ "claim": "ACP-ROWS-TEXT-ONLY-INPUT",
190
+ "title": "the ACP rows advertise image input again — pi accepts an attachment the rail only ever sends as an `[image omitted]` marker",
191
+ "subject": "pi-extensions/lib/acp/models.ts",
192
+ "find": ["const ACP_ROW_INPUT: (\"text\" | \"image\")[] = [\"text\"];"],
193
+ "replace": ["const ACP_ROW_INPUT: (\"text\" | \"image\")[] = [\"text\", \"image\"];"],
194
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
195
+ "timeoutSeconds": 60,
196
+ "signature": "[QK:ACP-ROWS-TEXT-ONLY-INPUT]",
197
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
198
+ },
199
+ {
200
+ "claim": "ACP-CORTEX-DISABLED",
201
+ "title": "a retired Cortex model loses its named refusal at the turn resolver",
202
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
203
+ "find": ["if (modelId.toLowerCase().startsWith(CORTEX_MODEL_PREFIX)) {"],
204
+ "replace": ["if (false && modelId.toLowerCase().startsWith(CORTEX_MODEL_PREFIX)) {"],
205
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
206
+ "timeoutSeconds": 60,
207
+ "signature": "[QK:ACP-CORTEX-DISABLED]",
208
+ "signatureSource": "pi-extensions/lib/acp/cortex-admission.test.ts"
209
+ },
210
+ {
211
+ "claim": "ACP-CORTEX-LEGACY-NOT-CURATED",
212
+ "title": "retired Cortex ids become accepted by the exact launch admission again",
213
+ "subject": "pi-extensions/lib/acp/model-ids.js",
214
+ "find": ["const CURATED_ACP_MODEL_IDS = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);"],
215
+ "replace": [
216
+ "const CURATED_ACP_MODEL_IDS = new Set([...SUPPORTED_ANTHROPIC_MODEL_IDS, ...LEGACY_CORTEX_MODEL_IDS]);"
217
+ ],
218
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
219
+ "timeoutSeconds": 60,
220
+ "signature": "[QK:ACP-CORTEX-LEGACY-NOT-CURATED]",
221
+ "signatureSource": "pi-extensions/lib/acp/cortex-admission.test.ts"
222
+ },
223
+ {
224
+ "claim": "ACP-CORTEX-DIRECT-DISABLED",
225
+ "title": "the exported retired adapter can launch Cortex again",
226
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
227
+ "find": ["\tresolveLaunch: cortexDisabled,"],
228
+ "replace": ["\tresolveLaunch: retiredCortexAdapter.resolveLaunch,"],
229
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
230
+ "timeoutSeconds": 60,
231
+ "signature": "[QK:ACP-CORTEX-DIRECT-DISABLED]",
232
+ "signatureSource": "pi-extensions/lib/acp/cortex-admission.test.ts"
233
+ },
234
+ {
235
+ "claim": "ACP-CORTEX-CONFIG-DISABLED",
236
+ "title": "an explicit Cortex backend selection reaches Claude instead of the named disabled refusal",
237
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
238
+ "find": ["if (backend?.toLowerCase() === \"cortex\") cortexDisabled();"],
239
+ "replace": ["if (false && backend?.toLowerCase() === \"cortex\") cortexDisabled();"],
240
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
241
+ "timeoutSeconds": 60,
242
+ "signature": "[QK:ACP-CORTEX-CONFIG-DISABLED]",
243
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
244
+ },
245
+ {
246
+ "claim": "ACP-CORTEX-TURN-DISABLED",
247
+ "title": "the production turn resolver falls back to Claude instead of refusing a Cortex model",
248
+ "subject": "pi-extensions/lib/acp/backend.ts",
249
+ "find": ["({ adapter, nativeModelId } = resolveAcpBackendAdapter(model.id));"],
250
+ "replace": ["({ adapter, nativeModelId } = resolveAcpBackendAdapter(\"claude-opus-5-5\"));"],
251
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
252
+ "timeoutSeconds": 60,
253
+ "signature": "[QK:ACP-CORTEX-TURN-DISABLED]",
254
+ "signatureSource": "pi-extensions/lib/acp/backend.thinking.test.ts"
255
+ },
256
+ {
257
+ "claim": "ACP-SUMMARY-TURN-TOOLLESS",
258
+ "title": "the summary turn keeps the operator's tool surface — Pi's tool-less summary request reaches a Claude child that can still run Read/Bash/Edit/Write while summarizing",
259
+ "subject": "pi-extensions/lib/acp/backend.ts",
260
+ "find": ["\t\t\ttools: [],"],
261
+ "replace": ["\t\t\ttools: config.tools,"],
262
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
263
+ "timeoutSeconds": 60,
264
+ "signature": "[QK:ACP-SUMMARY-TURN-TOOLLESS]",
265
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
266
+ },
267
+ {
268
+ "claim": "ACP-SUMMARY-INSTRUCTIONS-RELAYED",
269
+ "title": "Pi's summarization instructions are dropped — the system message never reaches the wire, so Claude answers the bare conversation instead of writing Pi's checkpoint summary",
270
+ "subject": "pi-extensions/lib/acp/backend.ts",
271
+ "find": ["const instructions = getCurrentSystemPrompt(context.messages).trim();"],
272
+ "replace": ["const instructions = \"\";"],
273
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
274
+ "timeoutSeconds": 60,
275
+ "signature": "[QK:ACP-SUMMARY-INSTRUCTIONS-RELAYED]",
276
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
277
+ },
278
+ {
279
+ "claim": "ACP-SUMMARY-TURN-NO-NOTICE",
280
+ "title": "the summary turn emits lifecycle and tool notices — `[acp:]`/`[tool:]` lines join the text Pi stores as compaction.summary, which is what gets embedded",
281
+ "subject": "pi-extensions/lib/acp/backend.ts",
282
+ "find": ["state.quiet = true;"],
283
+ "replace": ["state.quiet = false;"],
284
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
285
+ "timeoutSeconds": 60,
286
+ "signature": "[QK:ACP-SUMMARY-TURN-NO-NOTICE]",
287
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
288
+ },
289
+ {
290
+ "claim": "ACP-SUMMARY-TURN-NOT-RETAINED",
291
+ "title": "the summary child is retained under a process-scoped policy — every /compact leaves one more idle Claude process behind under a key nobody reuses",
292
+ "subject": "pi-extensions/lib/acp/backend.ts",
293
+ "find": ["lifecyclePolicy: \"turn-scoped\","],
294
+ "replace": ["lifecyclePolicy: \"process-scoped\","],
295
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
296
+ "timeoutSeconds": 60,
297
+ "signature": "[QK:ACP-SUMMARY-TURN-NOT-RETAINED]",
298
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
299
+ },
300
+ {
301
+ "claim": "ACP-SUMMARY-REQUIRES-CACHE-RETENTION",
302
+ "title": "an empty tool surface alone admits the summary path — an ordinary turn with every tool excluded silently runs tool-less instead of refusing the unhonourable exclusion",
303
+ "subject": "pi-extensions/lib/acp/tool-surface.ts",
304
+ "find": ["return activeToolNames.length === 0 && cacheRetention === \"none\";"],
305
+ "replace": ["return activeToolNames.length === 0;"],
306
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
307
+ "timeoutSeconds": 60,
308
+ "signature": "[QK:ACP-SUMMARY-REQUIRES-CACHE-RETENTION]",
309
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
310
+ },
311
+ {
312
+ "claim": "ACP-COMPACT-CLOSES-CHILD",
313
+ "title": "a compaction leaves the conversation's Claude child running — the pre-compaction context stays resident until the next turn's prefix break, against GLG's close-right-after decision",
314
+ "subject": "pi-extensions/acp-provider.ts",
315
+ "find": ["closeRetainedAcpSession(ctx.sessionManager.getSessionId());"],
316
+ "replace": ["void ctx;"],
317
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
318
+ "timeoutSeconds": 60,
319
+ "signature": "[QK:ACP-COMPACT-CLOSES-CHILD]",
320
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
321
+ },
322
+ {
323
+ "claim": "ACP-SUMMARY-TURN-NO-SETTING-SOURCES",
324
+ "title": "the summary child inherits the operator's settingSources — CLAUDE.md, hooks and plugins from disk reach a turn that should carry only the carrier and Pi's instructions",
325
+ "subject": "pi-extensions/lib/acp/backend.ts",
326
+ "find": ["\t\t\tsettingSources: [],"],
327
+ "replace": ["\t\t\tsettingSources: config.settingSources,"],
328
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
329
+ "timeoutSeconds": 60,
330
+ "signature": "[QK:ACP-SUMMARY-TURN-NO-SETTING-SOURCES]",
331
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
332
+ },
333
+ {
334
+ "claim": "ACP-SUMMARY-VENDOR-CANCEL-NOT-KEPT",
335
+ "title": "a summary the backend cancelled without an abort from Pi is sealed as aborted — Pi keeps aborted text, so an incomplete summary becomes the session's checkpoint",
336
+ "subject": "pi-extensions/lib/acp/backend.ts",
337
+ "find": ["if (summaryTurnActive && verdict.stopReason === \"aborted\" && !signal?.aborted) {"],
338
+ "replace": ["if (false) {"],
339
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
340
+ "timeoutSeconds": 60,
341
+ "signature": "[QK:ACP-SUMMARY-VENDOR-CANCEL-NOT-KEPT]",
342
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
343
+ },
344
+ {
345
+ "claim": "ACP-SUMMARY-REQUIRES-EMPTY-SURFACE",
346
+ "title": "cacheRetention none alone admits the summary path — a request that declares tools loses its tools, MCP and augment and runs as a tool-less one-shot",
347
+ "subject": "pi-extensions/lib/acp/tool-surface.ts",
348
+ "find": ["return activeToolNames.length === 0 && cacheRetention === \"none\";"],
349
+ "replace": ["return cacheRetention === \"none\";"],
350
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
351
+ "timeoutSeconds": 60,
352
+ "signature": "[QK:ACP-SUMMARY-REQUIRES-EMPTY-SURFACE]",
353
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
354
+ },
355
+ {
356
+ "claim": "ACP-COMPACT-SPARES-TURN-IN-FLIGHT",
357
+ "title": "a compaction closes a session whose turn is still running — the reused child is killed under the prompt it is answering",
358
+ "subject": "pi-extensions/lib/acp/backend.ts",
359
+ "find": ["\tif (!session || session.busy || inFlightKeys.has(key)) return false;"],
360
+ "replace": ["\tif (!session) return false;"],
361
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
362
+ "timeoutSeconds": 60,
363
+ "signature": "[QK:ACP-COMPACT-SPARES-TURN-IN-FLIGHT]",
364
+ "signatureSource": "pi-extensions/lib/acp/backend.summary-turn.test.ts"
365
+ },
366
+ {
367
+ "claim": "ACP-TOOL-TITLE-SHOWN",
368
+ "title": "a call's concrete title is never shown while it runs — the operator reads 'Terminal' until the command has finished",
369
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
370
+ "find": [
371
+ "\t\tpushDisplayOnlyNotice(state, `\\n[tool:running] ${sanitizeNoticeFragment(title, NOTICE_TITLE_MAX)}\\n`);"
372
+ ],
373
+ "replace": ["\t\tvoid title;"],
374
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
375
+ "timeoutSeconds": 60,
376
+ "signature": "[QK:ACP-TOOL-TITLE-SHOWN]",
377
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
378
+ },
379
+ {
380
+ "claim": "ACP-TOOL-TITLE-ONCE",
381
+ "title": "an update carrying both a new title and a progress beat prints two running lines for one event",
382
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
383
+ "find": ["\t\telapsed === undefined &&\n"],
384
+ "replace": [""],
385
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
386
+ "timeoutSeconds": 60,
387
+ "signature": "[QK:ACP-TOOL-TITLE-ONCE]",
388
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
389
+ },
390
+ {
391
+ "claim": "ACP-TOOL-TITLE-REPEAT-SILENT",
392
+ "title": "every title update prints a running line — a repeated title shows the same command again",
393
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
394
+ "find": [
395
+ "\t\tsanitizeNoticeFragment(update.title, NOTICE_TITLE_MAX) !== sanitizeNoticeFragment(previous.title, NOTICE_TITLE_MAX)"
396
+ ],
397
+ "replace": ["\t\ttrue"],
398
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
399
+ "timeoutSeconds": 60,
400
+ "signature": "[QK:ACP-TOOL-TITLE-REPEAT-SILENT]",
401
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
402
+ },
403
+ {
404
+ "claim": "ACP-TOOL-OUTPUT-PREVIEW",
405
+ "title": "a finished tool shows no output preview — the operator sees only the one-line summary, as before",
406
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
407
+ "find": ["\t\t\tconst preview = previewOf(summary, failed);"],
408
+ "replace": ["\t\t\tconst preview = undefined as string | undefined;"],
409
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
410
+ "timeoutSeconds": 60,
411
+ "signature": "[QK:ACP-TOOL-OUTPUT-PREVIEW]",
412
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
413
+ },
414
+ {
415
+ "claim": "ACP-TOOL-OUTPUT-FAILURE-TAIL",
416
+ "title": "a failure previews the head of its output — the stderr and exit reason at the end are cut off",
417
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
418
+ "find": ["\tconst shown = failed ? lines.slice(-PREVIEW_MAX_LINES) : lines.slice(0, PREVIEW_MAX_LINES);"],
419
+ "replace": ["\tconst shown = lines.slice(0, PREVIEW_MAX_LINES);"],
420
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
421
+ "timeoutSeconds": 60,
422
+ "signature": "[QK:ACP-TOOL-OUTPUT-FAILURE-TAIL]",
423
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
424
+ },
425
+ {
426
+ "claim": "ACP-TOOL-OUTPUT-BOUNDED",
427
+ "title": "a preview is bounded by lines only — a few very long lines put kilobytes of output on screen",
428
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
429
+ "find": ["\treturn clipBytes(shown.join(\"\\n\"), PREVIEW_MAX_BYTES, failed);"],
430
+ "replace": ["\treturn shown.join(\"\\n\");"],
431
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
432
+ "timeoutSeconds": 60,
433
+ "signature": "[QK:ACP-TOOL-OUTPUT-BOUNDED]",
434
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
435
+ },
436
+ {
437
+ "claim": "ACP-COMPACTION-LINE",
438
+ "title": "the backend's own compaction shows no numbers — only the bare start/done tool pair",
439
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
440
+ "find": ["\tpushAcpLifecycleNotice(state, parts.join(\" \"));"],
441
+ "replace": ["\tvoid parts;"],
442
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
443
+ "timeoutSeconds": 60,
444
+ "signature": "[QK:ACP-COMPACTION-LINE]",
445
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
446
+ },
447
+ {
448
+ "claim": "ACP-COMPACTION-ONCE",
449
+ "title": "an enriching terminal update prints a second compaction line for the same compaction",
450
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
451
+ "find": ["\tif (observed.compactionShown) return;"],
452
+ "replace": ["\tif (observed.compactionShown === null) return;"],
453
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
454
+ "timeoutSeconds": 60,
455
+ "signature": "[QK:ACP-COMPACTION-ONCE]",
456
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
457
+ },
458
+ {
459
+ "claim": "ACP-COMPACTION-FAILED",
460
+ "title": "a failed compaction reads like a successful one — its line never says failed",
461
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
462
+ "find": [
463
+ "\tif (facts.error !== undefined && facts.error !== null && facts.error !== \"\") parts.push(\"failed\");"
464
+ ],
465
+ "replace": ["\tvoid facts.error;"],
466
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
467
+ "timeoutSeconds": 60,
468
+ "signature": "[QK:ACP-COMPACTION-FAILED]",
469
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
470
+ },
471
+ {
472
+ "claim": "ACP-NOTICE-NO-TERMINAL-CONTROL",
473
+ "title": "tool output and titles reach the terminal raw — an OSC 52 in a command's output writes the operator's clipboard, an OSC 0 retitles the window",
474
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
475
+ "find": ["\treturn text.replace(ANSI_SEQUENCE, \"\").replace(CONTROL_CHARS, \"\");"],
476
+ "replace": ["\treturn text;"],
477
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
478
+ "timeoutSeconds": 60,
479
+ "signature": "[QK:ACP-NOTICE-NO-TERMINAL-CONTROL]",
480
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
481
+ },
482
+ {
483
+ "claim": "ACP-NOTICE-ANSI-WHOLE",
484
+ "title": "only the escape byte is dropped — each sequence's parameters and payload (a clipboard base64, a colour code) stay on screen as text",
485
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
486
+ "find": ["\treturn text.replace(ANSI_SEQUENCE, \"\").replace(CONTROL_CHARS, \"\");"],
487
+ "replace": ["\treturn text.replace(CONTROL_CHARS, \"\");"],
488
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
489
+ "timeoutSeconds": 60,
490
+ "signature": "[QK:ACP-NOTICE-ANSI-WHOLE]",
491
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
492
+ },
493
+ {
494
+ "claim": "ACP-TOOL-OUTPUT-INDENT-KEPT",
495
+ "title": "a preview collapses each line's whitespace — a stack trace, YAML or diff loses its indentation",
496
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
497
+ "find": ["\t\t.map((line) => sanitizePreviewLine(line));"],
498
+ "replace": ["\t\t.map((line) => sanitizeNoticeFragment(line, PREVIEW_MAX_BYTES));"],
499
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
500
+ "timeoutSeconds": 60,
501
+ "signature": "[QK:ACP-TOOL-OUTPUT-INDENT-KEPT]",
502
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
503
+ },
504
+ {
505
+ "claim": "ACP-PREVIEW-MATCHES-PI",
506
+ "title": "a preview keeps characters Pi drops from tool output — U+FFF9..U+FFFB, which break width measurement, survive",
507
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
508
+ "find": ["/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x9F\\uFFF9-\\uFFFB]/g;"],
509
+ "replace": ["/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x9F]/g;"],
510
+ "gate": ["bash", "run.sh", "check-acp-set-model-readback"],
511
+ "timeoutSeconds": 60,
512
+ "signature": "[QK:ACP-PREVIEW-MATCHES-PI]",
513
+ "signatureSource": "pi-extensions/lib/acp/event-mapper.test.ts"
26
514
  }
27
515
  ]
28
516
  }
@@ -659,13 +659,24 @@
659
659
  "claim": "FRESHCOMP-PEERS-TOOL-DIALECT",
660
660
  "title": "the corroboration the framing offers goes back to one bare spelling, so four of the five backends are offered a tool name their own session does not expose",
661
661
  "subject": "pi-extensions/lib/fresh-call-composition.ts",
662
- "find": ["\tconst peersTool = FRESH_CALL_PEERS_TOOL[params.backend];"],
662
+ "find": ["\tconst peersTool = FRESH_CALL_PEERS_TOOL[dialect];"],
663
663
  "replace": ["\tconst peersTool = \"entwurf_peers\";"],
664
664
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
665
665
  "timeoutSeconds": 120,
666
666
  "signature": "[QK:FRESHCOMP-PEERS-TOOL-DIALECT]",
667
667
  "signatureSource": "test/fresh-call-composition.contract.test.ts"
668
668
  },
669
+ {
670
+ "claim": "FRESHCOMP-ACP-CLAUDE-TOOL-DIALECT",
671
+ "title": "the framing goes back to keying tool names on the backend alone, so a pi sibling opened on an ACP Claude model is told Pi's underscore spelling its Claude tool surface does not expose",
672
+ "subject": "pi-extensions/lib/fresh-call-composition.ts",
673
+ "find": ["\tconst dialect = freshCallToolDialect(params.backend, params.model);"],
674
+ "replace": ["\tconst dialect = params.backend;"],
675
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
676
+ "timeoutSeconds": 120,
677
+ "signature": "[QK:FRESHCOMP-ACP-CLAUDE-TOOL-DIALECT]",
678
+ "signatureSource": "test/fresh-call-composition.contract.test.ts"
679
+ },
669
680
  {
670
681
  "claim": "FRESHCOMP-RESULT-GOES-TO-CALLER",
671
682
  "title": "the first turn stops naming where the result goes, so a sibling finishes its task, prints the answer in its own window, and the caller never learns it is done",
@@ -44,10 +44,10 @@
44
44
  "title": "a published-scope region in an operator doc names a package the frozen published manifest does not record, so the 1.5.0 instructions inside it would be held to a pairing no artifact ships — the doc can no longer prove which published package its install steps belong to",
45
45
  "subject": "docs/setup-clean-host.md",
46
46
  "find": [
47
- "| pi | optional-by-presence — published 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->"
47
+ "| pi | optional-by-presence — current 0.52.0: `>=1.5.2 <1.6`; historical 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->"
48
48
  ],
49
49
  "replace": [
50
- "| pi | optional-by-presence — published 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.5.0 -->"
50
+ "| pi | optional-by-presence — current 0.52.0: `>=1.5.2 <1.6`; historical 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.5.0 -->"
51
51
  ],
52
52
  "gate": ["bash", "run.sh", "check-dep-versions"],
53
53
  "timeoutSeconds": 300,
@@ -62,14 +62,14 @@
62
62
  },
63
63
  {
64
64
  "claim": "WRAPPER-SKIP-IS-PROTOCOL-EXIT",
65
- "title": "a run.sh smoke wrapper returns 0 on a missing prerequisite — the second skip surface, where cortex/matrix/spawn decline before their .ts is ever reached",
65
+ "title": "the matrix smoke wrapper returns 0 on a missing prerequisite before its .ts is reached",
66
66
  "subject": "run.sh",
67
67
  "find": [
68
- " echo \"[entwurf:skip] smoke-acp-cortex-live — set LIVE=1 (+ ENTWURF_ACP_CORTEX_CONNECTION) to run.\"",
68
+ " echo \"[entwurf:skip] smoke-entwurf-v2-matrix-live — set LIVE=1 to run (spawns a real pi --entwurf-control + opens a real socket).\"",
69
69
  " return \"$ENTWURF_STEP_SKIP_EXIT\""
70
70
  ],
71
71
  "replace": [
72
- " echo \"[entwurf:skip] smoke-acp-cortex-live — set LIVE=1 (+ ENTWURF_ACP_CORTEX_CONNECTION) to run.\"",
72
+ " echo \"[entwurf:skip] smoke-entwurf-v2-matrix-live — set LIVE=1 to run (spawns a real pi --entwurf-control + opens a real socket).\"",
73
73
  " return 0"
74
74
  ],
75
75
  "gate": ["bash", "run.sh", "check-release-gate-outcomes"],
@@ -18,10 +18,11 @@
18
18
  // It asserts nothing and blocks nothing. The receipt goes in README.md next to it.
19
19
 
20
20
  import { type ChildProcessByStdio, spawn } from "node:child_process";
21
+ import { readFileSync } from "node:fs";
21
22
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
22
23
  import { createRequire } from "node:module";
23
24
  import { tmpdir } from "node:os";
24
- import { join } from "node:path";
25
+ import { dirname, join } from "node:path";
25
26
  import { Readable, Writable } from "node:stream";
26
27
  import { ndJsonStream, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
27
28
  import { connectAcpClient } from "../../pi-extensions/lib/acp/acp-client.ts";
@@ -47,7 +48,12 @@ function withTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
47
48
  }
48
49
 
49
50
  const require = createRequire(import.meta.url);
50
- const bin = require.resolve("@agentclientprotocol/claude-agent-acp/dist/index.js");
51
+ // 0.103.0 exports only `.` and `./package.json`; take the bin path from the manifest.
52
+ const acpPkgJson = require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
53
+ const bin = join(
54
+ dirname(acpPkgJson),
55
+ (JSON.parse(readFileSync(acpPkgJson, "utf8")) as { bin: Record<string, string> }).bin["claude-agent-acp"],
56
+ );
51
57
 
52
58
  const scratch = await mkdtemp(join(tmpdir(), "raw-acp-compaction-"));
53
59
  const child: ChildProcessByStdio<Writable, Readable, Readable> = spawn(process.execPath, [bin], {