@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.
- package/AGENTS.md +52 -17
- package/BASELINE.md +60 -206
- package/CHANGELOG.md +64 -0
- package/CONTRIBUTING.md +14 -10
- package/DELIVERY.md +118 -261
- package/README.md +91 -431
- package/VERIFY.md +74 -95
- package/demo/README.md +1 -1
- package/demo/demo-baseline.sh +1 -3
- package/demo/demo.sh +2 -5
- package/docs/acp-backend-rail.md +236 -0
- package/docs/external-mcp-host.md +131 -0
- package/docs/fresh-cut-policy.md +99 -0
- package/docs/setup-clean-host.md +124 -328
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
- package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
- package/mcp/entwurf-bridge/src/index.ts +176 -67
- package/mcp/entwurf-bridge/start.sh +2 -2
- package/mcp/entwurf-bridge/test.sh +23 -9
- package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
- package/package.json +25 -11
- package/pi-extensions/entwurf-control.ts +220 -71
- package/pi-extensions/lib/acp/acp-client.ts +22 -0
- package/pi-extensions/lib/acp/augment.ts +42 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
- package/pi-extensions/lib/acp/backend.ts +366 -38
- package/pi-extensions/lib/acp/config.ts +1 -1
- package/pi-extensions/lib/acp/engraving.ts +43 -1
- package/pi-extensions/lib/acp/event-mapper.ts +16 -13
- package/pi-extensions/lib/acp/models.ts +4 -4
- package/pi-extensions/lib/acp/overlay.ts +11 -9
- package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
- package/pi-extensions/lib/entwurf-core.ts +15 -15
- package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
- package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
- package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
- package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
- package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
- package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
- package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
- package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
- package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
- package/pi-extensions/lib/meta-session.ts +93 -5
- package/pi-extensions/lib/mux-fresh-call.ts +328 -0
- package/pi-extensions/lib/mux-launch.ts +267 -0
- package/pi-extensions/lib/mux-placement.ts +387 -0
- package/pi-extensions/lib/mux-resume-call.ts +221 -0
- package/pi-extensions/lib/resume-launch-identity.ts +162 -0
- package/pi-extensions/lib/session-id.js +8 -5
- package/pi-extensions/lib/socket-discovery.ts +3 -3
- package/prompts/engraving.md +13 -5
- package/run.sh +688 -282
- package/scripts/agy-bridge-config.py +5 -1
- package/scripts/check-acp-backend-preflight.ts +1 -1
- package/scripts/check-acp-carrier-augment.ts +246 -10
- package/scripts/check-acp-cortex.ts +5 -5
- package/scripts/check-acp-overlay.ts +13 -3
- package/scripts/check-acp-prompt-lifecycle.ts +565 -0
- package/scripts/check-acp-stop-reason.ts +342 -0
- package/scripts/check-acp-stream-hooks.ts +504 -0
- package/scripts/check-elapsed.sh +25 -0
- package/scripts/check-entwurf-bridge-boot.ts +51 -4
- package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
- package/scripts/check-entwurf-control-rpc.ts +4 -3
- package/scripts/check-entwurf-resume-args.ts +72 -70
- package/scripts/check-entwurf-session-identity.ts +14 -10
- package/scripts/check-entwurf-v2-contract.ts +34 -59
- package/scripts/check-entwurf-v2-decider.ts +17 -177
- package/scripts/check-entwurf-v2-lock.ts +5 -2
- package/scripts/check-entwurf-v2-matrix.ts +3 -53
- package/scripts/check-entwurf-v2-production.ts +2 -91
- package/scripts/check-entwurf-v2-release.ts +10 -105
- package/scripts/check-entwurf-v2-runner.ts +4 -85
- package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
- package/scripts/check-entwurf-v2-send.ts +0 -28
- package/scripts/check-entwurf-v2-surface.ts +198 -128
- package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
- package/scripts/check-fresh-cut-gate.sh +1 -1
- package/scripts/check-gate-qualification.ts +101 -7
- package/scripts/check-install-container.sh +10 -2
- package/scripts/check-install-surface.ts +1 -1
- package/scripts/check-keyset-overlap.py +1 -1
- package/scripts/check-meta-facts.ts +249 -0
- package/scripts/check-meta-identity-consumers.ts +1 -1
- package/scripts/check-meta-session.ts +169 -0
- package/scripts/check-mux-launch-tmux.ts +316 -0
- package/scripts/check-mux-launch.ts +288 -0
- package/scripts/check-mux-launcher-fence.ts +264 -0
- package/scripts/check-mux-parent-artifact.ts +195 -0
- package/scripts/check-mux-placement-tmux.ts +322 -0
- package/scripts/check-mux-placement.ts +323 -0
- package/scripts/check-mux-resume-call.ts +282 -0
- package/scripts/check-probe-cli-shim.ts +25 -22
- package/scripts/check-probe-ordering.ts +121 -81
- package/scripts/check-release-gate-outcomes.ts +452 -0
- package/scripts/check-resume-launch-identity.ts +244 -0
- package/scripts/check-socket-discovery.ts +1 -1
- package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
- package/scripts/inventory-verification-surface.ts +349 -0
- package/scripts/lib/claude-launcher-fence.ts +322 -0
- package/scripts/lib/live-skip.ts +33 -0
- package/scripts/lib/mutation-qualify.ts +109 -3
- package/scripts/lib/probe-acp-turn.ts +33 -12
- package/scripts/lib/step-outcome.sh +88 -0
- package/scripts/meta-bridge-doctor.sh +6 -8
- package/scripts/meta-facts.ts +60 -0
- package/scripts/mutants/acp-augment.json +106 -0
- package/scripts/mutants/acp-cortex.json +2 -2
- package/scripts/mutants/acp-overlay.json +17 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
- package/scripts/mutants/acp-stop-reason.json +80 -0
- package/scripts/mutants/acp-stream-hooks.json +158 -0
- package/scripts/mutants/bridge-boot-resume.json +45 -0
- package/scripts/mutants/meta-facts.json +50 -0
- package/scripts/mutants/meta-identity.json +36 -0
- package/scripts/mutants/mux-boundary.json +196 -0
- package/scripts/mutants/mux-fresh-call.json +185 -0
- package/scripts/mutants/mux-launcher-fence.json +123 -0
- package/scripts/mutants/mux-parent-artifact.json +39 -0
- package/scripts/mutants/mux-resume-call.json +148 -0
- package/scripts/mutants/probe-ordering.json +6 -1021
- package/scripts/mutants/release-gate.json +140 -0
- package/scripts/mutants/resume-args.json +76 -0
- package/scripts/mutants/resume-launch-identity.json +96 -0
- package/scripts/mutants/v2-surface.json +74 -23
- package/scripts/mutants/v2-visible-resume.json +215 -0
- package/scripts/new-session-id.ts +9 -4
- package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
- package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
- package/scripts/smoke-acp-cortex-live.ts +8 -14
- package/scripts/smoke-acp-long-turn-live.ts +185 -0
- package/scripts/smoke-acp-mcp-live.ts +2 -2
- package/scripts/smoke-acp-memory-containment-live.ts +2 -2
- package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
- package/scripts/smoke-acp-overlay-live.ts +2 -2
- package/scripts/smoke-acp-provider-live.ts +2 -2
- package/scripts/smoke-acp-raw-turn-live.ts +3 -3
- package/scripts/smoke-acp-session-reuse-live.ts +2 -2
- package/scripts/smoke-acp-skill-live.ts +2 -2
- package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
- package/scripts/smoke-acp-v2-send-live.ts +4 -3
- package/scripts/smoke-agy-native-push-live.ts +10 -20
- package/scripts/smoke-claude-native-resume-live.sh +13 -3
- package/scripts/smoke-entwurf-chain-live.ts +352 -0
- package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -3
- package/scripts/smoke-meta-honesty.sh +1 -1
- package/scripts/smoke-meta-install-state.sh +4 -0
- package/scripts/smoke-mux-fresh-call-live.ts +365 -0
- package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
- package/scripts/smoke-pi-attach.ts +1 -1
- package/scripts/smoke-user-scope-citizen.sh +1 -1
- package/scripts/tsconfig.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
- package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
- package/scripts/check-acp-sdk-surface.ts +0 -236
- package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
- package/scripts/check-entwurf-v2-spawn.ts +0 -399
- package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
- 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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
164
|
-
"
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
"
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|