@junghanacs/entwurf 0.13.0 → 0.14.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 (193) hide show
  1. package/AGENTS.md +52 -17
  2. package/BASELINE.md +60 -206
  3. package/CHANGELOG.md +64 -0
  4. package/CONTRIBUTING.md +14 -10
  5. package/DELIVERY.md +118 -261
  6. package/README.md +91 -431
  7. package/VERIFY.md +74 -95
  8. package/demo/README.md +1 -1
  9. package/demo/demo-baseline.sh +1 -3
  10. package/demo/demo.sh +2 -5
  11. package/docs/acp-backend-rail.md +236 -0
  12. package/docs/external-mcp-host.md +131 -0
  13. package/docs/fresh-cut-policy.md +99 -0
  14. package/docs/setup-clean-host.md +124 -328
  15. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
  37. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  38. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  39. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
  40. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  41. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  42. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  43. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  44. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  45. package/mcp/entwurf-bridge/src/index.ts +176 -67
  46. package/mcp/entwurf-bridge/start.sh +2 -2
  47. package/mcp/entwurf-bridge/test.sh +23 -9
  48. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  49. package/package.json +25 -11
  50. package/pi-extensions/entwurf-control.ts +220 -71
  51. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  52. package/pi-extensions/lib/acp/augment.ts +42 -3
  53. package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
  54. package/pi-extensions/lib/acp/backend.ts +366 -38
  55. package/pi-extensions/lib/acp/config.ts +1 -1
  56. package/pi-extensions/lib/acp/engraving.ts +43 -1
  57. package/pi-extensions/lib/acp/event-mapper.ts +16 -13
  58. package/pi-extensions/lib/acp/models.ts +4 -4
  59. package/pi-extensions/lib/acp/overlay.ts +11 -9
  60. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  61. package/pi-extensions/lib/entwurf-core.ts +15 -15
  62. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  63. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  64. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  65. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  66. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  67. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  68. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  69. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  70. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  71. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  72. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  73. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  74. package/pi-extensions/lib/meta-session.ts +93 -5
  75. package/pi-extensions/lib/mux-fresh-call.ts +328 -0
  76. package/pi-extensions/lib/mux-launch.ts +267 -0
  77. package/pi-extensions/lib/mux-placement.ts +387 -0
  78. package/pi-extensions/lib/mux-resume-call.ts +221 -0
  79. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  80. package/pi-extensions/lib/session-id.js +8 -5
  81. package/pi-extensions/lib/socket-discovery.ts +3 -3
  82. package/prompts/engraving.md +13 -5
  83. package/run.sh +688 -282
  84. package/scripts/agy-bridge-config.py +5 -1
  85. package/scripts/check-acp-backend-preflight.ts +1 -1
  86. package/scripts/check-acp-carrier-augment.ts +246 -10
  87. package/scripts/check-acp-cortex.ts +5 -5
  88. package/scripts/check-acp-overlay.ts +13 -3
  89. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  90. package/scripts/check-acp-stop-reason.ts +342 -0
  91. package/scripts/check-acp-stream-hooks.ts +504 -0
  92. package/scripts/check-elapsed.sh +25 -0
  93. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  94. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  95. package/scripts/check-entwurf-control-rpc.ts +4 -3
  96. package/scripts/check-entwurf-resume-args.ts +72 -70
  97. package/scripts/check-entwurf-session-identity.ts +14 -10
  98. package/scripts/check-entwurf-v2-contract.ts +34 -59
  99. package/scripts/check-entwurf-v2-decider.ts +17 -177
  100. package/scripts/check-entwurf-v2-lock.ts +5 -2
  101. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  102. package/scripts/check-entwurf-v2-production.ts +2 -91
  103. package/scripts/check-entwurf-v2-release.ts +10 -105
  104. package/scripts/check-entwurf-v2-runner.ts +4 -85
  105. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  106. package/scripts/check-entwurf-v2-send.ts +0 -28
  107. package/scripts/check-entwurf-v2-surface.ts +198 -128
  108. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  109. package/scripts/check-fresh-cut-gate.sh +1 -1
  110. package/scripts/check-gate-qualification.ts +101 -7
  111. package/scripts/check-install-container.sh +10 -2
  112. package/scripts/check-install-surface.ts +1 -1
  113. package/scripts/check-keyset-overlap.py +1 -1
  114. package/scripts/check-meta-facts.ts +249 -0
  115. package/scripts/check-meta-identity-consumers.ts +1 -1
  116. package/scripts/check-meta-session.ts +169 -0
  117. package/scripts/check-mux-launch-tmux.ts +316 -0
  118. package/scripts/check-mux-launch.ts +288 -0
  119. package/scripts/check-mux-launcher-fence.ts +264 -0
  120. package/scripts/check-mux-parent-artifact.ts +195 -0
  121. package/scripts/check-mux-placement-tmux.ts +322 -0
  122. package/scripts/check-mux-placement.ts +323 -0
  123. package/scripts/check-mux-resume-call.ts +282 -0
  124. package/scripts/check-probe-cli-shim.ts +25 -22
  125. package/scripts/check-probe-ordering.ts +121 -81
  126. package/scripts/check-release-gate-outcomes.ts +452 -0
  127. package/scripts/check-resume-launch-identity.ts +244 -0
  128. package/scripts/check-socket-discovery.ts +1 -1
  129. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  130. package/scripts/inventory-verification-surface.ts +349 -0
  131. package/scripts/lib/claude-launcher-fence.ts +322 -0
  132. package/scripts/lib/live-skip.ts +33 -0
  133. package/scripts/lib/mutation-qualify.ts +109 -3
  134. package/scripts/lib/probe-acp-turn.ts +33 -12
  135. package/scripts/lib/step-outcome.sh +88 -0
  136. package/scripts/meta-bridge-doctor.sh +6 -8
  137. package/scripts/meta-facts.ts +60 -0
  138. package/scripts/mutants/acp-augment.json +106 -0
  139. package/scripts/mutants/acp-cortex.json +2 -2
  140. package/scripts/mutants/acp-overlay.json +17 -0
  141. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  142. package/scripts/mutants/acp-stop-reason.json +80 -0
  143. package/scripts/mutants/acp-stream-hooks.json +158 -0
  144. package/scripts/mutants/bridge-boot-resume.json +45 -0
  145. package/scripts/mutants/meta-facts.json +50 -0
  146. package/scripts/mutants/meta-identity.json +36 -0
  147. package/scripts/mutants/mux-boundary.json +196 -0
  148. package/scripts/mutants/mux-fresh-call.json +185 -0
  149. package/scripts/mutants/mux-launcher-fence.json +123 -0
  150. package/scripts/mutants/mux-parent-artifact.json +39 -0
  151. package/scripts/mutants/mux-resume-call.json +148 -0
  152. package/scripts/mutants/probe-ordering.json +6 -1021
  153. package/scripts/mutants/release-gate.json +140 -0
  154. package/scripts/mutants/resume-args.json +76 -0
  155. package/scripts/mutants/resume-launch-identity.json +96 -0
  156. package/scripts/mutants/v2-surface.json +74 -23
  157. package/scripts/mutants/v2-visible-resume.json +215 -0
  158. package/scripts/new-session-id.ts +9 -4
  159. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  160. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  161. package/scripts/smoke-acp-cortex-live.ts +8 -14
  162. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  163. package/scripts/smoke-acp-mcp-live.ts +2 -2
  164. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  165. package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
  166. package/scripts/smoke-acp-overlay-live.ts +2 -2
  167. package/scripts/smoke-acp-provider-live.ts +2 -2
  168. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  169. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  170. package/scripts/smoke-acp-skill-live.ts +2 -2
  171. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  172. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  173. package/scripts/smoke-agy-native-push-live.ts +10 -20
  174. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  175. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  176. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -3
  177. package/scripts/smoke-meta-honesty.sh +1 -1
  178. package/scripts/smoke-meta-install-state.sh +4 -0
  179. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  180. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  181. package/scripts/smoke-pi-attach.ts +1 -1
  182. package/scripts/smoke-user-scope-citizen.sh +1 -1
  183. package/scripts/tsconfig.json +1 -0
  184. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  185. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  186. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  187. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  188. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  189. package/scripts/check-acp-sdk-surface.ts +0 -236
  190. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  191. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  192. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  193. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -469
@@ -0,0 +1,215 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "v2-visible-resume",
4
+ "mutants": [
5
+ {
6
+ "claim": "V2RESUME-LIVE-REFUSED",
7
+ "title": "the live check inverts, so a running citizen gets a second window on its own transcript",
8
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
9
+ "find": ["\t\tif (mapped.liveness === \"alive\") {"],
10
+ "replace": ["\t\tif (mapped.liveness === \"dead\") {"],
11
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
12
+ "timeoutSeconds": 60,
13
+ "signature": "[QK:V2RESUME-LIVE-REFUSED]",
14
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
15
+ },
16
+ {
17
+ "claim": "V2RESUME-INDETERMINATE-NO-START",
18
+ "title": "an inconclusive probe stops being a refusal, so a citizen that may be running is resumed anyway",
19
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
20
+ "find": ["\t\tif (mapped.liveness === \"indeterminate\") {"],
21
+ "replace": ["\t\tif (mapped.liveness === \"alive\") {"],
22
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
23
+ "timeoutSeconds": 60,
24
+ "signature": "[QK:V2RESUME-INDETERMINATE-NO-START]",
25
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
26
+ },
27
+ {
28
+ "claim": "V2RESUME-CONFLICT-NO-START",
29
+ "title": "a corrupt control-socket address stops being a refusal and a citizen is stood up behind it",
30
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
31
+ "find": ["\t\tif (\"addressConflict\" in mapped) {"],
32
+ "replace": ["\t\tif (\"addressConflictNever\" in mapped) {"],
33
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
34
+ "timeoutSeconds": 60,
35
+ "signature": "[QK:V2RESUME-CONFLICT-NO-START]",
36
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
37
+ },
38
+ {
39
+ "claim": "V2RESUME-LOCKED-NO-RESOLVE",
40
+ "title": "a lock conflict is read as success, so the record is opened behind another holder's lock",
41
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
42
+ "find": ["\tif (!acquired.ok) {"],
43
+ "replace": ["\tif (!acquired.ok && acquired.conflict.reason !== \"target-locked\") {"],
44
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
45
+ "timeoutSeconds": 60,
46
+ "signature": "[QK:V2RESUME-LOCKED-NO-RESOLVE]",
47
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
48
+ },
49
+ {
50
+ "claim": "V2RESUME-NO-TRANSCRIPT-FAILS-LOUD",
51
+ "title": "the leaf's cause-rich throw is swallowed into a placeholder identity, so an unresumable citizen opens a window anyway",
52
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
53
+ "find": [
54
+ "\t\tlet identity: LaunchIdentity;",
55
+ "\t\ttry {",
56
+ "\t\t\tidentity = deps.resolveIdentity(targetGardenId);",
57
+ "\t\t} catch (err) {"
58
+ ],
59
+ "replace": [
60
+ "\t\tlet identity: LaunchIdentity;",
61
+ "\t\ttry {",
62
+ "\t\t\tidentity = deps.resolveIdentity(targetGardenId);",
63
+ "\t\t} catch (err) {",
64
+ "\t\t\tif (!(err instanceof ResumeBackendUnsupportedError))",
65
+ "\t\t\t\treturn { ok: false, reason: \"target-invalid\", detail: String(err) };"
66
+ ],
67
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
68
+ "timeoutSeconds": 60,
69
+ "signature": "[QK:V2RESUME-NO-TRANSCRIPT-FAILS-LOUD]",
70
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
71
+ },
72
+ {
73
+ "claim": "V2RESUME-OBSERVE-IS-BOUNDED-WAIT",
74
+ "title": "the observation becomes a single immediate probe, which reports every real resume as unobserved (the socket answers ~2-4s later)",
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 };"],
78
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
79
+ "timeoutSeconds": 60,
80
+ "signature": "[QK:V2RESUME-OBSERVE-IS-BOUNDED-WAIT]",
81
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
82
+ },
83
+ {
84
+ "claim": "V2RESUME-UNOBSERVED-IS-REAL",
85
+ "title": "a timeout claims the socket answered, turning the one fact that says the citizen is back into a guess",
86
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
87
+ "find": ["\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"],
88
+ "replace": ["\t\t\treturn { kind: \"socket-alive\", socketPath, waitedMs: deps.now() - started };"],
89
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
90
+ "timeoutSeconds": 60,
91
+ "signature": "[QK:V2RESUME-UNOBSERVED-IS-REAL]",
92
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
93
+ },
94
+ {
95
+ "claim": "V2RESUME-RELEASE-FAILURE-LOUD",
96
+ "title": "a release that did not release is folded into a green receipt, leaving a held lock nobody is told about",
97
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
98
+ "find": ["\t\tif (outcome !== \"released\") {"],
99
+ "replace": ["\t\tif (outcome !== \"released\" && outcome !== \"not-owned\" && outcome !== \"absent\") {"],
100
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
101
+ "timeoutSeconds": 60,
102
+ "signature": "[QK:V2RESUME-RELEASE-FAILURE-LOUD]",
103
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
104
+ },
105
+ {
106
+ "claim": "V2RESUME-ARGV-FROM-RECORD",
107
+ "title": "the launcher is handed the caller's own directory instead of the record's, so the citizen returns in the wrong project",
108
+ "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 });"],
111
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
112
+ "timeoutSeconds": 60,
113
+ "signature": "[QK:V2RESUME-ARGV-FROM-RECORD]",
114
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
115
+ },
116
+ {
117
+ "claim": "V2RESUME-RECEIPTS-SEPARATE",
118
+ "title": "the launch receipt absorbs the observation, so 'a window opened' and 'the citizen is back' become one claim",
119
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
120
+ "find": ["\t\t\tlaunch: { targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle },"],
121
+ "replace": [
122
+ "\t\t\tlaunch: { targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle, kind: observation.kind } as ResumeLaunchReceipt,"
123
+ ],
124
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
125
+ "timeoutSeconds": 60,
126
+ "signature": "[QK:V2RESUME-RECEIPTS-SEPARATE]",
127
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
128
+ },
129
+ {
130
+ "claim": "V2RESUME-RENDER-SEPARATES",
131
+ "title": "the rendered text merges the two receipts into one sentence, so a reader cannot tell which fact is missing",
132
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
133
+ "find": [
134
+ "\t\t\t`LAUNCH receipt: tmux created that window and was asked to start pi on the transcript above.\\n` +"
135
+ ],
136
+ "replace": ["\t\t\t`Resume complete.\\n` +"],
137
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
138
+ "timeoutSeconds": 60,
139
+ "signature": "[QK:V2RESUME-RENDER-SEPARATES]",
140
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
141
+ },
142
+ {
143
+ "claim": "V2RESUME-IDENTITY-BEFORE-WINDOW",
144
+ "title": "the liveness preflight runs BEFORE the record is read, so dormancy is judged for a citizen whose transcript has not been proven resumable yet",
145
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
146
+ "find": ["\t\tlet identity: LaunchIdentity;"],
147
+ "replace": ["\t\tawait deps.inspectSocket(targetGardenId);", "\t\tlet identity: LaunchIdentity;"],
148
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
149
+ "timeoutSeconds": 60,
150
+ "signature": "[QK:V2RESUME-IDENTITY-BEFORE-WINDOW]",
151
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
152
+ },
153
+ {
154
+ "claim": "V2RESUME-TIMEOUT-RELEASES",
155
+ "title": "the timeout path launches a second time — the relaunch this rail refuses, hidden inside what reads as one bounded wait",
156
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
157
+ "find": ["\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"],
158
+ "replace": [
159
+ "\t\t\t(deps as VisibleResumeDeps).launch({ cwd: \"/tmp\", runtimeArgs: [] });\n\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"
160
+ ],
161
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
162
+ "timeoutSeconds": 60,
163
+ "signature": "[QK:V2RESUME-TIMEOUT-RELEASES]",
164
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
165
+ },
166
+ {
167
+ "claim": "V2RESUME-LOCK-BEFORE-LIVENESS",
168
+ "title": "the lock stops being taken first, so a competing resume can pass its own dead check while this one is launching",
169
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
170
+ "find": ["\tconst acquired = deps.acquireLock(targetGardenId);"],
171
+ "replace": ["\tawait deps.inspectSocket(targetGardenId);\n\tconst acquired = deps.acquireLock(targetGardenId);"],
172
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
173
+ "timeoutSeconds": 60,
174
+ "signature": "[QK:V2RESUME-LOCK-BEFORE-LIVENESS]",
175
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
176
+ },
177
+ {
178
+ "claim": "V2RESUME-NOT-PI-REFUSED",
179
+ "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
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
181
+ "find": ["\t\t\tif (err instanceof ResumeBackendUnsupportedError) {"],
182
+ "replace": ["\t\t\tif (false && err instanceof ResumeBackendUnsupportedError) {"],
183
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
184
+ "timeoutSeconds": 60,
185
+ "signature": "[QK:V2RESUME-NOT-PI-REFUSED]",
186
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
187
+ },
188
+ {
189
+ "claim": "V2RESUME-NOT-PI-BY-FIELD",
190
+ "title": "the backend is re-derived instead of taken from the typed error, which is the second read this rail refuses",
191
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
192
+ "find": [
193
+ "\t\t\t\t\t\t`${targetGardenId} is a ${err.backend} citizen; only pi citizens stand a control socket up, ` +"
194
+ ],
195
+ "replace": [
196
+ "\t\t\t\t\t\t`${targetGardenId} is a citizen of another backend; only pi citizens stand a control socket up, ` +"
197
+ ],
198
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
199
+ "timeoutSeconds": 60,
200
+ "signature": "[QK:V2RESUME-NOT-PI-BY-FIELD]",
201
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
202
+ },
203
+ {
204
+ "claim": "V2RESUME-SHARED-ID-GRAMMAR",
205
+ "title": "the address grammar is re-implemented locally, where it can drift from the record, the lock path and the socket filename it becomes",
206
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
207
+ "find": ["\tif (!isValidSessionId(targetGardenId)) {"],
208
+ "replace": ["\tif (!/^\\d{8}T\\d{6}-[0-9a-f]{6}$/.test(targetGardenId)) {"],
209
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
210
+ "timeoutSeconds": 60,
211
+ "signature": "[QK:V2RESUME-SHARED-ID-GRAMMAR]",
212
+ "signatureSource": "scripts/check-entwurf-v2-visible-resume.ts"
213
+ }
214
+ ]
215
+ }
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * new-session-id — print one fresh garden-native sessionId and exit.
3
3
  *
4
- * The garden launcher (operator alias / wrapper) calls this so every
5
- * `--entwurf-control` session is born a garden citizen:
4
+ * NOT a launcher contract. #50 C2 retired the injection form
5
+ * (`pi --session-id "$(./run.sh new-session-id)" --entwurf-control …`): pi mints its
6
+ * own session id (a `uuidv7` is normal), `session_start` attaches that session to its
7
+ * meta-record, and the RECORD mints the garden id and keys the control socket on it.
8
+ * Injecting a garden-shaped id gives a session two address-shaped strings of which only
9
+ * one is an address. Launch is plain `pi --entwurf-control` (README §Garden launcher).
6
10
  *
7
- * pi --session-id "$(./run.sh new-session-id)" --entwurf-control \
8
- * --emacs-agent-socket server
11
+ * What survives is this: the operator-facing generator for the same id grammar the
12
+ * record layer itself uses — one fresh id on stdout, for a caller that needs to mint a
13
+ * token deliberately, never as a launch argument.
9
14
  *
10
15
  * The id is `generateSessionId()` from entwurf-core — the single SSOT for the
11
16
  * locked `YYYYMMDDTHHMMSS-[0-9a-f]{6}` grammar. Do NOT reimplement the format in
@@ -52,6 +52,7 @@ import * as readline from "node:readline";
52
52
  import { fileURLToPath } from "node:url";
53
53
  import { fetchControlSocketRuntimeInfo, formatRuntimeModel } from "../pi-extensions/lib/entwurf-control-rpc.ts";
54
54
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
55
+ import { skipLive } from "./lib/live-skip.ts";
55
56
  import { waitForPiRecord } from "./lib/pi-record-discovery.ts";
56
57
 
57
58
  const ACP_PROVIDER = "entwurf";
@@ -160,10 +161,10 @@ function driveSelfTurn(child: ChildProcess, prompt: string): Promise<TurnCapture
160
161
 
161
162
  async function main(): Promise<void> {
162
163
  if (process.env.LIVE !== "1") {
163
- console.log(
164
- "[smoke-acp-bundled-mcp-live] skipped — set LIVE=1 to run (spawns a real pi --entwurf-control resident + drives one model turn).",
164
+ skipLive(
165
+ "smoke-acp-bundled-mcp-live",
166
+ "set LIVE=1 to run (spawns a real pi --entwurf-control resident + drives one model turn).",
165
167
  );
166
- return;
167
168
  }
168
169
 
169
170
  const tmp = os.tmpdir();
@@ -45,6 +45,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
45
45
  import { tmpdir } from "node:os";
46
46
  import { dirname, join, resolve } from "node:path";
47
47
  import { fileURLToPath } from "node:url";
48
+ import { skipLive } from "./lib/live-skip.ts";
48
49
 
49
50
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
50
51
  const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
@@ -57,8 +58,7 @@ function fail(msg: string): never {
57
58
  }
58
59
 
59
60
  if (process.env.LIVE !== "1") {
60
- console.error("[smoke-acp-carrier-augment-live] skipped — set LIVE=1 to run the real pi provider turn.");
61
- process.exit(0);
61
+ skipLive("smoke-acp-carrier-augment-live", "set LIVE=1 to run the real pi provider turn.");
62
62
  }
63
63
 
64
64
  // Billing-reclassification canaries (핀1). The empty carrier must NOT trip metered
@@ -5,7 +5,7 @@
5
5
  //
6
6
  // LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live
7
7
  //
8
- // WHY this exact axis. The CP0 audit (docs/acp-backend-rail.md §11-8) measured
8
+ // WHY this exact axis. The CP0 audit (docs/acp-backend-rail.md, Cortex D1–D10) measured
9
9
  // three things no offline gate can prove:
10
10
  // - D9/D10: cortex ignores the wire `mcpServers` param, so the bundled
11
11
  // entwurf-bridge reaches a cortex session ONLY through the overlay-private
@@ -32,7 +32,7 @@
32
32
  // LIVE-only and OUTSIDE the claude-only AGGREGATE release floor (capability
33
33
  // dignity): a host that runs no cortex must not redden a claude release. That is
34
34
  // a wiring decision — shipping cortex still owes a deliberate run of this smoke,
35
- // and the aggregate gate's silence is not a pass (rail §11-8 tail).
35
+ // and the aggregate gate's silence is not a pass (ACP rail verification boundary).
36
36
  // Honest skip when LIVE!=1, when `cortex` is not on PATH, or when
37
37
  // ENTWURF_ACP_CORTEX_CONNECTION is unset — the overlay denies the operator's
38
38
  // cortex settings.json (where a default connection would live), so the
@@ -53,6 +53,7 @@ import * as readline from "node:readline";
53
53
  import { fileURLToPath } from "node:url";
54
54
  import { upsertMetaSession, writeMetaReceiverMarker } from "../pi-extensions/lib/meta-session.ts";
55
55
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
56
+ import { skipLive } from "./lib/live-skip.ts";
56
57
  import { waitForPiRecord } from "./lib/pi-record-discovery.ts";
57
58
 
58
59
  const ACP_PROVIDER = "entwurf";
@@ -189,24 +190,17 @@ function driveTurn(child: ChildProcess, prompt: string): Promise<TurnCapture> {
189
190
 
190
191
  async function main(): Promise<void> {
191
192
  if (process.env.LIVE !== "1") {
192
- console.log(
193
- "[smoke-acp-cortex-live] skipped — set LIVE=1 to run (spawns a real cortex ACP resident + one model turn).",
194
- );
195
- return;
193
+ skipLive("smoke-acp-cortex-live", "set LIVE=1 to run (spawns a real cortex ACP resident + one model turn).");
196
194
  }
197
195
  const connection = process.env.ENTWURF_ACP_CORTEX_CONNECTION?.trim();
198
196
  if (!connection) {
199
- console.log(
200
- "[smoke-acp-cortex-live] skipped — set ENTWURF_ACP_CORTEX_CONNECTION=<snowflake connection> (the dual-HOME " +
201
- "overlay denies the operator cortex settings.json, so the connection must ride the adapter's own seam).",
197
+ skipLive(
198
+ "smoke-acp-cortex-live",
199
+ "set ENTWURF_ACP_CORTEX_CONNECTION=<snowflake connection> (the dual-HOME overlay denies the operator cortex settings.json, so the connection must ride the adapter's own seam).",
202
200
  );
203
- return;
204
201
  }
205
202
  if ("CORTEX_HOME" in process.env) {
206
- console.log(
207
- "[smoke-acp-cortex-live] skipped — unset CORTEX_HOME first (the adapter refuses its presence, CP0 D3).",
208
- );
209
- return;
203
+ skipLive("smoke-acp-cortex-live", "unset CORTEX_HOME first (the adapter refuses its presence, CP0 D3).");
210
204
  }
211
205
 
212
206
  // One temp root holding the whole garden this smoke can see: store, mailbox
@@ -0,0 +1,185 @@
1
+ // 0.13.1 — LIVE acceptance for the prompt-lifecycle contract. OUT of `pnpm check`.
2
+ //
3
+ // LIVE=1 ./run.sh smoke-acp-long-turn-live
4
+ //
5
+ // THE CLAIM THIS PAYS FOR. `check-acp-prompt-lifecycle` proves against fakes
6
+ // that a running prompt has no wall clock, and `check-probe-ordering` proves the
7
+ // source carries none. Neither can prove the whole stack — pi's runner, its
8
+ // retry policy, the real adapter, and a real model — lets a turn run past the
9
+ // number we deleted. This smoke does exactly that and nothing else: it drives
10
+ // ONE real turn whose tool work is deliberately longer than the retired 600s
11
+ // cutoff, and requires it to finish as ONE turn.
12
+ //
13
+ // Two facts are asserted together, because the old defect was their PRODUCT:
14
+ //
15
+ // survives the reply carries the nonce and the turn took LONGER than the
16
+ // retired cutoff — a fixed wall clock would have killed it.
17
+ // not replayed
18
+ // the persisted transcript contains exactly ONE bootstrap notice
19
+ // ("[acp: preparing claude session]"). The old failure mode
20
+ // replayed the same full prompt from a COLD ACP session up to
21
+ // `retry.maxRetries` times, so every replay re-announced that
22
+ // bootstrap. One occurrence is the direct evidence that the answer
23
+ // came from the original prompt.
24
+ //
25
+ // The work is split into several tool calls under the backend's own per-command
26
+ // tool cap rather than one huge wait, which is also the realistic shape of the
27
+ // turns that were being killed (continuous tool activity, no output for minutes).
28
+ //
29
+ // WHY THE WORK IS NOT `sleep` (measured 2026-07-30, first LIVE attempt). Asking
30
+ // for `sleep 240` produced a 22.9s turn with no nonce: the Claude backend's own
31
+ // Bash tool refuses a FOREGROUND `sleep` ("Foreground `sleep` is blocked; use
32
+ // Monitor with an until-loop"), so the model detached it, reported "Command
33
+ // running in background", said it would wait, and ended the turn. The tool work
34
+ // never happened, so nothing about the wall clock was measured. The blocking
35
+ // command below waits the same wall time without invoking `sleep`, which is the
36
+ // only reason the shape changed — the claim is unchanged.
37
+
38
+ import { strict as assert } from "node:assert";
39
+ import { spawnSync } from "node:child_process";
40
+ import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
41
+ import { tmpdir } from "node:os";
42
+ import { dirname, join, resolve } from "node:path";
43
+ import { fileURLToPath } from "node:url";
44
+ import { skipLive } from "./lib/live-skip.ts";
45
+
46
+ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
47
+ const MODEL = process.env.ENTWURF_ACP_LONG_TURN_MODEL?.trim() || "claude-sonnet-5";
48
+ const PROVIDER = "entwurf";
49
+
50
+ /** The cutoff this cut removed. The turn must outlive it. */
51
+ const RETIRED_CUTOFF_MS = 600_000;
52
+ /** Seconds per tool call — each stays under the backend's own per-command cap. */
53
+ const SLEEP_SECONDS = Number(process.env.ENTWURF_ACP_LONG_TURN_SLEEP_SECONDS) || 240;
54
+ /** How many sequential waits to ask for (3 × 240s = 720s > the retired cutoff). */
55
+ const SLEEP_ROUNDS = Number(process.env.ENTWURF_ACP_LONG_TURN_ROUNDS) || 3;
56
+ /** Harness bound for the WHOLE smoke — an observation horizon, not a turn contract. */
57
+ const SMOKE_HORIZON_MS = Number(process.env.ENTWURF_ACP_LONG_TURN_HORIZON_MS) || 2_400_000;
58
+
59
+ function fail(msg: string): never {
60
+ console.error(`[smoke-acp-long-turn-live] FAIL: ${msg}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ if (process.env.LIVE !== "1") {
65
+ skipLive("smoke-acp-long-turn-live", "set LIVE=1 to run the real long ACP turn.");
66
+ }
67
+
68
+ const nonce = `${process.pid.toString(36)}${Date.now().toString(36)}`;
69
+ const expected = `LONGTURN_OK_${nonce}`;
70
+ const sessionId = `long-turn-${nonce}`;
71
+
72
+ const scratch = mkdtempSync(join(tmpdir(), "entwurf-long-turn-"));
73
+ const sessionDir = mkdtempSync(join(tmpdir(), "entwurf-long-turn-sessions-"));
74
+ const startedAt = Date.now();
75
+ try {
76
+ // Blocks for SLEEP_SECONDS in the FOREGROUND without invoking `sleep` (see the
77
+ // header): select() with only a timeout is a plain wait, and the echo gives
78
+ // each round a visible completion marker in the transcript.
79
+ const waitCommand = `python3 -c "import select; select.select([], [], [], ${SLEEP_SECONDS})" && echo ROUND_COMPLETE`;
80
+
81
+ const prompt =
82
+ `Run exactly ${SLEEP_ROUNDS} shell commands with your shell tool, one at a time, waiting for each to print ` +
83
+ `ROUND_COMPLETE before starting the next. Each command is exactly: ${waitCommand}\n\n` +
84
+ `Each one blocks for about ${SLEEP_SECONDS} seconds, so give the shell tool a per-command limit of at least ` +
85
+ `${(SLEEP_SECONDS + 60) * 1000} ms. Run every command in the FOREGROUND and wait for its output — do not ` +
86
+ "detach it, do not run it in the background, do not poll for it, do not shorten it, and do not run them in " +
87
+ `parallel. When all ${SLEEP_ROUNDS} have printed ROUND_COMPLETE, reply with exactly ${expected} and nothing else.`;
88
+
89
+ const args = [
90
+ "--no-extensions",
91
+ "-e",
92
+ REPO_ROOT,
93
+ "--mode",
94
+ "text",
95
+ "--session-id",
96
+ sessionId,
97
+ "--session-dir",
98
+ sessionDir,
99
+ "-p",
100
+ "--approve",
101
+ "--provider",
102
+ PROVIDER,
103
+ "--model",
104
+ MODEL,
105
+ prompt,
106
+ ];
107
+
108
+ console.error(`[smoke-acp-long-turn-live] repo: ${REPO_ROOT}`);
109
+ console.error(`[smoke-acp-long-turn-live] cwd: ${scratch}`);
110
+ console.error(`[smoke-acp-long-turn-live] model: ${PROVIDER}/${MODEL}`);
111
+ console.error(`[smoke-acp-long-turn-live] work: ${SLEEP_ROUNDS} × ${SLEEP_SECONDS}s foreground wait`);
112
+ console.error(`[smoke-acp-long-turn-live] expects: ${expected} after > ${RETIRED_CUTOFF_MS}ms`);
113
+
114
+ const res = spawnSync("pi", args, { cwd: scratch, encoding: "utf8", timeout: SMOKE_HORIZON_MS, env: process.env });
115
+ const elapsed = Date.now() - startedAt;
116
+
117
+ const stdout = res.stdout ?? "";
118
+ const stderr = res.stderr ?? "";
119
+ const combined = `${stdout}\n${stderr}`;
120
+ const tail = combined.split("\n").slice(-30).join("\n");
121
+
122
+ if (res.error) {
123
+ console.error(`[smoke-acp-long-turn-live] output tail:\n${tail}`);
124
+ fail(`pi spawn failed after ${elapsed}ms: ${res.error.message}`);
125
+ }
126
+ if (res.status !== 0) {
127
+ console.error(`[smoke-acp-long-turn-live] output tail:\n${tail}`);
128
+ fail(`pi exited ${res.status} (signal=${res.signal ?? "none"}) after ${elapsed}ms`);
129
+ }
130
+
131
+ assert.ok(
132
+ stdout.includes(expected),
133
+ `the long turn did not deliver its nonce ${expected} after ${elapsed}ms (stdout tail: ${JSON.stringify(stdout.slice(-400))})`,
134
+ );
135
+ assert.ok(
136
+ elapsed > RETIRED_CUTOFF_MS,
137
+ `the turn finished in ${elapsed}ms — under the retired ${RETIRED_CUTOFF_MS}ms cutoff, so it proves nothing. ` +
138
+ "Raise ENTWURF_ACP_LONG_TURN_ROUNDS / _SLEEP_SECONDS until the tool work outlasts the cutoff.",
139
+ );
140
+
141
+ // No cold replay: exactly one bootstrap in the persisted transcript.
142
+ const jsonl = readdirSync(sessionDir, { recursive: true })
143
+ .filter((f): f is string => typeof f === "string" && f.endsWith(".jsonl"))
144
+ .map((f) => readFileSync(join(sessionDir, f), "utf8"))
145
+ .join("\n");
146
+ assert.ok(jsonl.length > 0, `no session JSONL under ${sessionDir} — cannot judge replay`);
147
+ const bootstraps = jsonl.split("[acp: preparing claude session]").length - 1;
148
+ assert.equal(
149
+ bootstraps,
150
+ 1,
151
+ `the transcript shows ${bootstraps} cold ACP bootstraps — a long turn must be answered by its ORIGINAL prompt, ` +
152
+ "not replayed from a fresh session (that replay is what turned one long turn into four)",
153
+ );
154
+ // The DEFECT's own signatures, not the word "timeout" anywhere: the model
155
+ // narrates its shell tool's per-command limit in this transcript, so a bare
156
+ // /timeout/i would fail an otherwise perfect run on the assistant's own prose.
157
+ // These three strings are what the old pairing actually emitted — the retired
158
+ // cutoff's message, and pi's retry-exhaustion wording.
159
+ const defectMarkers = [
160
+ /prompt timed out after/i,
161
+ /Aborted after \d+ retry attempts?/i,
162
+ /\bretry attempt \d+/i,
163
+ ].filter((re) => re.test(combined));
164
+ assert.equal(
165
+ defectMarkers.length,
166
+ 0,
167
+ `the run reported the retired cutoff or a pi retry — neither may happen to a healthy long turn ` +
168
+ `(matched ${defectMarkers.join(", ")}; tail: ${JSON.stringify(tail)})`,
169
+ );
170
+
171
+ console.log("[smoke-acp-long-turn-live] PASS — a real ACP turn outlived the retired wall clock and answered once");
172
+ console.log(` model: ${PROVIDER}/${MODEL}`);
173
+ console.log(` elapsed: ${elapsed}ms (> retired cutoff ${RETIRED_CUTOFF_MS}ms)`);
174
+ console.log(` nonce: ${expected} present in the assistant reply`);
175
+ console.log(" replay: exactly 1 cold ACP bootstrap in the persisted transcript");
176
+ console.log(" retry: no retry / timeout reported anywhere in the run");
177
+ } finally {
178
+ for (const d of [scratch, sessionDir]) {
179
+ try {
180
+ rmSync(d, { recursive: true, force: true });
181
+ } catch {
182
+ // best-effort cleanup
183
+ }
184
+ }
185
+ }
@@ -26,6 +26,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
26
26
  import { tmpdir } from "node:os";
27
27
  import { dirname, join, resolve } from "node:path";
28
28
  import { fileURLToPath } from "node:url";
29
+ import { skipLive } from "./lib/live-skip.ts";
29
30
 
30
31
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
31
32
  const PROBE_SERVER = join(REPO_ROOT, "scripts", "fixtures", "probe-mcp-server.ts");
@@ -39,8 +40,7 @@ function fail(msg: string): never {
39
40
  }
40
41
 
41
42
  if (process.env.LIVE !== "1") {
42
- console.error("[smoke-acp-mcp-live] skipped — set LIVE=1 to run the real pi provider turn.");
43
- process.exit(0);
43
+ skipLive("smoke-acp-mcp-live", "set LIVE=1 to run the real pi provider turn.");
44
44
  }
45
45
 
46
46
  const STUB_PATTERN = /AcpBackendNotImplementedError|not implemented in S0/i;
@@ -68,6 +68,7 @@ import {
68
68
  DEFAULT_CLAUDE_TOOLS,
69
69
  } from "../pi-extensions/lib/acp/tool-surface.ts";
70
70
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
71
+ import { skipLive } from "./lib/live-skip.ts";
71
72
 
72
73
  const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_MEMORY_MODEL ?? "claude-sonnet-5";
73
74
  const ALLOW_PATH_FALLBACK = process.env.ENTWURF_ACP_MEMORY_ALLOW_PATH_FALLBACK === "1";
@@ -109,8 +110,7 @@ async function listMemoryFiles(dir: string): Promise<string[]> {
109
110
  }
110
111
 
111
112
  if (process.env.LIVE !== "1") {
112
- console.error("[smoke-acp-memory-containment-live] skipped — set LIVE=1 to run the real memory-containment turn.");
113
- process.exit(0);
113
+ skipLive("smoke-acp-memory-containment-live", "set LIVE=1 to run the real memory-containment turn.");
114
114
  }
115
115
 
116
116
  function resolveLaunch(): { command: string; args: string[]; source: string; acceptance: boolean } {
@@ -50,6 +50,7 @@ import { type AcpClientHandlers, connectAcpClient } from "../pi-extensions/lib/a
50
50
  import type { AcpBackendAdapter } from "../pi-extensions/lib/acp/backend-adapter.ts";
51
51
  import type { ResolvedAcpConfig } from "../pi-extensions/lib/acp/config.ts";
52
52
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
53
+ import { skipLive } from "./lib/live-skip.ts";
53
54
  import { driveProbeTurn, type ProbeMcpEnricher, ProbePhaseError } from "./lib/probe-acp-turn.ts";
54
55
  import {
55
56
  AMBIENT_OVERRIDE_ENV,
@@ -103,8 +104,7 @@ function fail(msg: string): never {
103
104
  }
104
105
 
105
106
  if (process.env.LIVE !== "1") {
106
- console.error("[smoke-acp-ordering-probe-live] skipped — set LIVE=1 to run the real paired probe.");
107
- process.exit(0);
107
+ skipLive("smoke-acp-ordering-probe-live", "set LIVE=1 to run the real paired probe.");
108
108
  }
109
109
 
110
110
  for (const [label, d] of [
@@ -55,6 +55,7 @@ import {
55
55
  DEFAULT_CLAUDE_TOOLS,
56
56
  } from "../pi-extensions/lib/acp/tool-surface.ts";
57
57
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
58
+ import { skipLive } from "./lib/live-skip.ts";
58
59
 
59
60
  const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_OVERLAY_MODEL ?? "claude-sonnet-5";
60
61
  const ALLOW_PATH_FALLBACK = process.env.ENTWURF_ACP_OVERLAY_ALLOW_PATH_FALLBACK === "1";
@@ -76,8 +77,7 @@ function withTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
76
77
  }
77
78
 
78
79
  if (process.env.LIVE !== "1") {
79
- console.error("[smoke-acp-overlay-live] skipped — set LIVE=1 to run the real overlay ACP turn.");
80
- process.exit(0);
80
+ skipLive("smoke-acp-overlay-live", "set LIVE=1 to run the real overlay ACP turn.");
81
81
  }
82
82
 
83
83
  function resolveLaunch(): { command: string; args: string[]; source: string; acceptance: boolean } {
@@ -26,6 +26,7 @@ import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
26
26
  import { tmpdir } from "node:os";
27
27
  import { dirname, join, resolve } from "node:path";
28
28
  import { fileURLToPath } from "node:url";
29
+ import { skipLive } from "./lib/live-skip.ts";
29
30
 
30
31
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
31
32
  const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
@@ -38,8 +39,7 @@ function fail(msg: string): never {
38
39
  }
39
40
 
40
41
  if (process.env.LIVE !== "1") {
41
- console.error("[smoke-acp-provider-live] skipped — set LIVE=1 to run the real pi provider turn.");
42
- process.exit(0);
42
+ skipLive("smoke-acp-provider-live", "set LIVE=1 to run the real pi provider turn.");
43
43
  }
44
44
 
45
45
  // A nonce makes the expected reply unique so a stray "OK" elsewhere in the
@@ -3,7 +3,7 @@
3
3
  // LIVE=1 ./run.sh smoke-acp-raw-turn-live
4
4
  //
5
5
  // What this proves (and ONLY this): the pinned Claude ACP adapter
6
- // (@agentclientprotocol/claude-agent-acp@0.62.0) spawns, speaks the ACP wire
6
+ // (@agentclientprotocol/claude-agent-acp@0.66.0) spawns, speaks the ACP wire
7
7
  // protocol over stdio NDJSON, and returns one real model turn. It is the
8
8
  // bytes-flow proof that the S2a dep surface is not just installable but
9
9
  // actually drivable — before any provider/overlay/streamSimple code (S2b+).
@@ -34,6 +34,7 @@ import { Readable, Writable } from "node:stream";
34
34
  import { ndJsonStream, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
35
35
  import { connectAcpClient } from "../pi-extensions/lib/acp/acp-client.ts";
36
36
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
37
+ import { skipLive } from "./lib/live-skip.ts";
37
38
 
38
39
  const REQUESTED_MODEL_ID = process.env.ENTWURF_ACP_RAW_TURN_MODEL ?? "claude-sonnet-5";
39
40
  const ALLOW_PATH_FALLBACK = process.env.ENTWURF_ACP_RAW_TURN_ALLOW_PATH_FALLBACK === "1";
@@ -59,8 +60,7 @@ function withTimeout<T>(label: string, p: Promise<T>, ms: number): Promise<T> {
59
60
  }
60
61
 
61
62
  if (process.env.LIVE !== "1") {
62
- console.error("[smoke-acp-raw-turn-live] skipped — set LIVE=1 to run the real ACP turn.");
63
- process.exit(0);
63
+ skipLive("smoke-acp-raw-turn-live", "set LIVE=1 to run the real ACP turn.");
64
64
  }
65
65
 
66
66
  // ---------------------------------------------------------------------------