pantheon-opencode 1.5.0-beta.1 → 1.5.0-beta.10

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 (82) hide show
  1. package/.pantheon/code-mode/manifest.json +21 -0
  2. package/CHANGELOG.md +1026 -0
  3. package/README.md +171 -16
  4. package/README.pt-BR.md +99 -13
  5. package/bin/pantheon-init.mjs +178 -21
  6. package/package.json +23 -14
  7. package/plugin.json +1 -1
  8. package/pyproject.toml +65 -0
  9. package/scripts/code_mode_server.py +431 -55
  10. package/scripts/cost.mjs +30 -14
  11. package/scripts/doctor.mjs +178 -9
  12. package/scripts/install/cli-ui.mjs +17 -11
  13. package/scripts/install/config-migration.mjs +4 -6
  14. package/scripts/install/opencode.mjs +137 -35
  15. package/scripts/install/plugin.mjs +3 -33
  16. package/scripts/install/shared.mjs +43 -2
  17. package/scripts/install/strings.mjs +129 -0
  18. package/scripts/install-mcp.mjs +1 -0
  19. package/scripts/manifest-inventory.mjs +410 -0
  20. package/scripts/mcp_persistence_server.py +537 -128
  21. package/scripts/package-evidence.mjs +242 -0
  22. package/scripts/probe-context-rehydrate.mjs +240 -0
  23. package/scripts/probe-pantheon-cost.mjs +313 -0
  24. package/scripts/release-beta-version.mjs +56 -60
  25. package/scripts/sync-artifacts.mjs +169 -0
  26. package/scripts/sync-tui.mjs +121 -53
  27. package/scripts/test-opencode-v1-v2-sandbox.sh +367 -96
  28. package/scripts/validation-policy.mjs +7 -7
  29. package/scripts/version-check.mjs +113 -108
  30. package/scripts/versioning.mjs +230 -86
  31. package/src/agents/mnemosyne.md +1 -1
  32. package/src/agents/zeus.md +2 -2
  33. package/src/mcp/code_mode_server.py +431 -55
  34. package/src/mcp/mcp_persistence_server.py +537 -128
  35. package/src/pantheon/auto-wake.ts +1 -5
  36. package/src/pantheon/background-job-board.ts +43 -1
  37. package/src/pantheon/compaction-handoff.ts +304 -0
  38. package/src/pantheon/cost-command.ts +345 -62
  39. package/src/pantheon/delegate-manager.ts +390 -8
  40. package/src/pantheon/delegation-finalize.ts +32 -0
  41. package/src/pantheon/delegation.ts +235 -18
  42. package/src/pantheon/file-persistence-salvage.ts +64 -0
  43. package/src/pantheon/file-persistence.ts +194 -45
  44. package/src/pantheon/native-probe.ts +234 -0
  45. package/src/pantheon/native-task-status.ts +139 -0
  46. package/src/pantheon/provider-capabilities.ts +221 -0
  47. package/src/pantheon/provider-stream-tracker.ts +182 -0
  48. package/src/pantheon/shared-board.ts +37 -0
  49. package/src/pantheon/task-result-guard.ts +107 -0
  50. package/src/pantheon/token-opt.ts +146 -12
  51. package/src/pantheon/tool-ceiling.ts +159 -0
  52. package/src/pantheon/v2-events.ts +18 -1
  53. package/src/plugin-v2.ts +2 -14
  54. package/src/plugin.ts +28 -17
  55. package/src/plugins/pantheon-hooks.ts +51 -0
  56. package/src/plugins/tui/dist/server.d.ts +1 -1
  57. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  58. package/src/plugins/tui/dist/server.js +1 -1
  59. package/src/plugins/tui/dist/server.js.map +1 -1
  60. package/src/plugins/tui/dist/tui.d.ts +116 -27
  61. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  62. package/src/plugins/tui/dist/tui.js +414 -150
  63. package/src/plugins/tui/dist/tui.js.map +1 -1
  64. package/src/plugins/tui/dist/tui.tsx +447 -84
  65. package/src/plugins/tui/package-lock.json +2 -2
  66. package/src/plugins/tui/package.json +1 -1
  67. package/src/plugins/tui/src/index.tsx +447 -84
  68. package/.pantheon/code-mode/.gitkeep +0 -0
  69. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-1.json +0 -8
  70. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-10.json +0 -8
  71. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-11.json +0 -8
  72. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-2.json +0 -8
  73. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-3.json +0 -8
  74. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-4.json +0 -8
  75. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-5.json +0 -8
  76. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-6.json +0 -8
  77. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-7.json +0 -8
  78. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-8.json +0 -8
  79. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-9.json +0 -8
  80. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/heartbeat.json +0 -8
  81. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/session.json +0 -11
  82. package/src/pantheon/delegate-task-adapter.ts +0 -444
@@ -13,6 +13,11 @@
13
13
  # (config is regenerated for that version first).
14
14
  # --prompts Prompt battery via `opencode run --format json` for every
15
15
  # requested version (with --run vX) or both versions alone.
16
+ # --cost Offline pantheon_cost probe for the requested
17
+ # version (with --run vX) or both versions alone.
18
+ # The probe's synthetic checks run in explicit --fixture mode.
19
+ # --rehydrate Offline context_rehydrate + context_session_summary
20
+ # probe for the requested version (with --run vX) or both.
16
21
  # --reset Wipe the sandbox root.
17
22
  # --help Usage.
18
23
  #
@@ -25,11 +30,8 @@
25
30
  # PANTHEON_SANDBOX_MODEL Model used by init/prompts
26
31
  # (default: opencode-go/mimo-v2.5)
27
32
  # PANTHEON_PROMPT_TIMEOUT Per-prompt timeout in seconds (default: 300)
28
- # PANTHEON_RETRY_COOLDOWN Cooldown seconds before the single FAIL retry
29
- # (default: 60; retry only for real FAIL, never AMBIENTAL)
30
- #
31
- # Exit codes: 0 = no real failures (AMBIENTAL issues do not fail the run),
32
- # 1 = real FAIL (see prompts-report.md in the sandbox root),
33
+ # Exit codes: 0 = every required check returned explicit PASS,
34
+ # 1 = any failure or untested check (see reports in the sandbox root),
33
35
  # 2 = usage error, 3 = sandbox not prepared.
34
36
  set -euo pipefail
35
37
 
@@ -49,11 +51,15 @@ V1_BIN="${OPENCODE_V1_BIN:-opencode}"
49
51
  V2_BIN="${OPENCODE_V2_BIN:-opencode2}"
50
52
 
51
53
  REPORT_FILE="$SANDBOX_ROOT/prompts-report.md"
54
+ COST_REPORT_FILE="$SANDBOX_ROOT/pantheon-cost-report.md"
55
+ REHYDRATE_REPORT_FILE="$SANDBOX_ROOT/context-rehydrate-report.md"
52
56
  EXTRACT_PY="$SANDBOX_ROOT/.prompt-extract-json.py"
53
57
 
54
58
  MODE_PREPARE=0
55
59
  MODE_RESET=0
56
60
  MODE_PROMPTS=0
61
+ MODE_COST=0
62
+ MODE_REHYDRATE=0
57
63
  RUN_VERSION=""
58
64
 
59
65
  usage() {
@@ -105,8 +111,8 @@ write_run_test_sh() {
105
111
  cat > "$SANDBOX_ROOT/run-test.sh" <<'RUNTEST'
106
112
  #!/usr/bin/env bash
107
113
  # Generated by pantheon-opencode scripts/test-opencode-v1-v2-sandbox.sh --prepare
108
- # Validates the global install: binaries, 5/5 MCP connectivity, doctor 0 errors,
109
- # AND the CONTENT of the pantheon://agents resource (zeus/hermes present).
114
+ # Validates the global install: binaries, exactly 5 connected MCPs per version,
115
+ # doctor 0 errors, AND the CONTENT of pantheon://agents (zeus/hermes present).
110
116
  set -euo pipefail
111
117
 
112
118
  SANDBOX_DIR="$(cd "$(dirname "$0")" && pwd)"
@@ -124,9 +130,68 @@ export HOME="$SANDBOX_HOME"
124
130
  export PATH="$NPM_PREFIX/bin:$SANDBOX_VENV/bin:$PATH"
125
131
  export npm_config_prefix="$NPM_PREFIX"
126
132
 
133
+ STATUS_FAIL=0
134
+ STATUS_PASS=0
135
+
136
+ record_status() { # label status detail
137
+ local label="$1" status="$2" detail="$3"
138
+ case "$status" in
139
+ PASS) STATUS_PASS=$((STATUS_PASS + 1)) ;;
140
+ FAIL) STATUS_FAIL=$((STATUS_FAIL + 1)) ;;
141
+ *) STATUS_FAIL=$((STATUS_FAIL + 1)); status="FAIL" ;;
142
+ esac
143
+ printf '%s: %s — %s\n' "$status" "$label" "$detail"
144
+ }
145
+
146
+ check_binary() { # label binary
147
+ local label="$1" bin="$2" out rc=0
148
+ if ! command -v "$bin" >/dev/null 2>&1; then
149
+ record_status "$label" "FAIL" "binary '$bin' is not installed in the sandbox prefix"
150
+ return 0
151
+ fi
152
+ set +e
153
+ out="$("$bin" --version 2>&1)"
154
+ rc=$?
155
+ set -e
156
+ if [ "$rc" -eq 0 ]; then
157
+ record_status "$label" "PASS" "${out:-version command exited 0}"
158
+ else
159
+ record_status "$label" "FAIL" "version check exited $rc"
160
+ fi
161
+ }
162
+
163
+ check_mcp() { # label binary project
164
+ local label="$1" bin="$2" project="$3" out connected_count rc=0
165
+ if ! command -v "$bin" >/dev/null 2>&1; then
166
+ record_status "$label MCP" "FAIL" "binary '$bin' is not installed in the sandbox prefix"
167
+ return 0
168
+ fi
169
+ if [ ! -d "$project" ]; then
170
+ record_status "$label MCP" "FAIL" "project directory is missing: $project"
171
+ return 0
172
+ fi
173
+ set +e
174
+ out="$(cd "$project" && "$bin" mcp list 2>&1)"
175
+ rc=$?
176
+ set -e
177
+ out="$(printf '%s' "$out" | sed -E $'s/\x1B\\[[0-?]*[ -/]*[@-~]//g')"
178
+ printf '%s\n' "$out"
179
+ if [ "$rc" -ne 0 ]; then
180
+ record_status "$label MCP" "FAIL" "mcp list exited $rc"
181
+ return 0
182
+ fi
183
+ connected_count="$(printf '%s' "$out" | grep -Eic '(^|[^[:alnum:]_])connected([^[:alnum:]_]|$)')" || connected_count=0
184
+ if [ "$connected_count" -eq 5 ] \
185
+ && ! printf '%s' "$out" | grep -Eiq 'failed|✘'; then
186
+ record_status "$label MCP" "PASS" "exactly 5 connected MCPs"
187
+ else
188
+ record_status "$label MCP" "FAIL" "expected exactly 5 connected MCPs; found $connected_count or failures reported"
189
+ fi
190
+ }
191
+
127
192
  echo "--- Binaries ---"
128
- opencode --version
129
- opencode2 --version
193
+ check_binary "V1 binary" opencode
194
+ check_binary "V2 binary" opencode2
130
195
 
131
196
  echo "--- Building tarball ---"
132
197
  cd "$REPO_DIR"
@@ -134,42 +199,70 @@ npm pack 2>/dev/null || { echo "ERROR: npm pack failed"; exit 1; }
134
199
  TGZ=$(ls -t pantheon-opencode-*.tgz | head -1)
135
200
  npm rm -g pantheon-opencode 2>/dev/null || true
136
201
  npm install -g "$TGZ"
202
+ if ! git -C "$REPO_DIR" ls-files --error-unmatch -- "$TGZ" >/dev/null 2>&1; then rm -f -- "$TGZ"; fi
137
203
 
138
204
  echo "--- Init (headless) ---"
139
205
  pantheon-opencode init --headless -y
140
206
 
141
- echo "--- MCP List (V1) ---"
142
- opencode mcp list
143
-
144
- echo "--- MCP List (V2) ---"
145
- opencode2 mcp list
207
+ echo "--- MCP Validation ---"
208
+ check_mcp "V1" opencode "$SANDBOX_DIR/project-v1"
209
+ check_mcp "V2" opencode2 "$SANDBOX_DIR/project-v2"
146
210
 
147
211
  echo "--- Doctor ---"
148
- pantheon-opencode doctor --target "$SANDBOX_DIR"
212
+ if command -v pantheon-opencode >/dev/null 2>&1; then
213
+ set +e
214
+ DOCTOR_OUTPUT="$(pantheon-opencode doctor --target "$SANDBOX_DIR" 2>&1)"
215
+ DOCTOR_RC=$?
216
+ set -e
217
+ if [ "$DOCTOR_RC" -eq 0 ]; then
218
+ record_status "doctor" "PASS" "exit 0"
219
+ else
220
+ record_status "doctor" "FAIL" "doctor exited $DOCTOR_RC"
221
+ fi
222
+ else
223
+ record_status "doctor" "FAIL" "pantheon-opencode is not installed in the sandbox prefix"
224
+ fi
149
225
 
150
226
  echo "--- Gate (b): pantheon://agents CONTENT check ---"
151
227
  RESOURCES_SRV="$PANTHEON_GLOBAL/scripts/mcp_resources_server.py"
152
228
  if [ ! -f "$RESOURCES_SRV" ]; then
153
- echo "ERROR: resources server not found at $RESOURCES_SRV"; exit 1
154
- fi
155
- AGENTS_CONTENT="$("$SANDBOX_VENV/bin/python" - "$RESOURCES_SRV" <<'PYEOF'
229
+ record_status "pantheon://agents resource" "FAIL" "resources server not found at $RESOURCES_SRV"
230
+ else
231
+ set +e
232
+ AGENTS_CONTENT="$("$SANDBOX_VENV/bin/python" - "$RESOURCES_SRV" <<'PYEOF'
156
233
  import asyncio, importlib.util, sys
234
+ from pathlib import Path
235
+ resource_path = Path(sys.argv[1]).resolve()
236
+ sys.path.insert(0, str(resource_path.parent))
157
237
  spec = importlib.util.spec_from_file_location("mrs", sys.argv[1])
158
238
  mod = importlib.util.module_from_spec(spec)
159
239
  spec.loader.exec_module(mod)
160
240
  print(asyncio.run(mod.list_agents()))
161
241
  PYEOF
162
242
  )"
163
- printf '%s\n' "$AGENTS_CONTENT" | head -8
164
- if printf '%s\n' "$AGENTS_CONTENT" | grep -qi 'zeus' \
165
- && printf '%s\n' "$AGENTS_CONTENT" | grep -qi 'hermes'; then
166
- echo "OK: pantheon://agents content includes zeus and hermes"
167
- else
168
- echo "ERROR: pantheon://agents content missing zeus/hermes (connectivity alone is not enough)"; exit 1
243
+ AGENTS_RC=$?
244
+ set -e
245
+ printf '%s\n' "$AGENTS_CONTENT" | head -8
246
+ if [ "$AGENTS_RC" -ne 0 ]; then
247
+ record_status "pantheon://agents resource" "FAIL" "resource probe exited $AGENTS_RC"
248
+ elif printf '%s\n' "$AGENTS_CONTENT" | grep -qi 'zeus' \
249
+ && printf '%s\n' "$AGENTS_CONTENT" | grep -qi 'hermes'; then
250
+ record_status "pantheon://agents resource" "PASS" "pantheon://agents content includes zeus and hermes"
251
+ else
252
+ record_status "pantheon://agents resource" "FAIL" "content missing zeus/hermes (connectivity alone is not enough)"
253
+ fi
169
254
  fi
170
-
171
255
  echo ""
172
256
  echo "=== VALIDATION COMPLETE ==="
257
+ if [ "$STATUS_FAIL" -gt 0 ] || [ "$STATUS_PASS" -eq 0 ]; then
258
+ FINAL_VERDICT="FAIL"
259
+ else
260
+ FINAL_VERDICT="PASS"
261
+ fi
262
+ printf 'Final verdict: %s\n' "$FINAL_VERDICT"
263
+ if [ "$FINAL_VERDICT" != "PASS" ]; then
264
+ exit 1
265
+ fi
173
266
  echo "Next: open the isolated TUI with: $SANDBOX_DIR/start-pantheon.sh"
174
267
  RUNTEST
175
268
  chmod +x "$SANDBOX_ROOT/run-test.sh"
@@ -195,12 +288,11 @@ install_binaries() {
195
288
  tgz=$(ls -t pantheon-opencode-*.tgz | head -1)
196
289
  npm rm -g pantheon-opencode 2>/dev/null || true
197
290
  npm install -g "$REPO_DIR/$tgz" || die "install of $tgz failed"
291
+ if ! git -C "$REPO_DIR" ls-files --error-unmatch -- "$tgz" >/dev/null 2>&1; then rm -f -- "$tgz"; fi
198
292
  log "--- Installing OpenCode V1 ($OPENCODE_V1_SPEC) ---"
199
- npm install -g "$OPENCODE_V1_SPEC" \
200
- || warn "V1 binary install failed ($OPENCODE_V1_SPEC) — v1 prompts will be AMBIENTAL"
293
+ npm install -g "$OPENCODE_V1_SPEC" || die "install of $OPENCODE_V1_SPEC failed"
201
294
  log "--- Installing OpenCode V2 ($OPENCODE_V2_SPEC) ---"
202
- npm install -g "$OPENCODE_V2_SPEC" \
203
- || warn "V2 binary install failed ($OPENCODE_V2_SPEC) — v2 prompts will be AMBIENTAL"
295
+ npm install -g "$OPENCODE_V2_SPEC" || die "install of $OPENCODE_V2_SPEC failed"
204
296
  }
205
297
 
206
298
  prepare_project() {
@@ -271,19 +363,14 @@ PYEOF
271
363
 
272
364
  # ── Prompt battery ────────────────────────────────────────────────────────────
273
365
 
274
- # Known environmental failure signatures (never fail the run).
275
- AMBIENTAL_RE='bifrost|unauthorized|authentication failure|api key|credentials|401|docker|econnrefused|enotfound|etimedout|econnreset|connection refused|rate.?limit|429|quota|overloaded|edgesout|npm err'
276
-
277
366
  PROMPT_IDS=()
278
367
  PROMPT_TEXTS=()
279
368
  PROMPT_MARKERS=()
280
- PROMPT_AMB_MARKERS=()
281
369
  PROMPT_VERIFY=()
282
370
 
283
- add_prompt() { # id marker ambiental_marker verify text
371
+ add_prompt() { # id marker ignored verify text
284
372
  PROMPT_IDS+=("$1")
285
373
  PROMPT_MARKERS+=("$2")
286
- PROMPT_AMB_MARKERS+=("$3")
287
374
  PROMPT_VERIFY+=("$4")
288
375
  PROMPT_TEXTS+=("$5")
289
376
  }
@@ -293,9 +380,8 @@ build_battery() {
293
380
  PROMPT_IDS=()
294
381
  PROMPT_TEXTS=()
295
382
  PROMPT_MARKERS=()
296
- PROMPT_AMB_MARKERS=()
297
383
  PROMPT_VERIFY=()
298
- add_prompt "agents-resource" "AGENTS-OK" "AGENTS-MISSING" "" \
384
+ add_prompt "agents-resource" "AGENTS-OK" "" "" \
299
385
  "List your MCP resources and read the resource pantheon://agents. If the returned agent list includes an agent named 'zeus' and one named 'hermes', reply with exactly: AGENTS-OK. Otherwise reply with exactly: AGENTS-MISSING."
300
386
  add_prompt "memory-store" "MEMORY-STORE-OK" "" "" \
301
387
  "Use the pantheon-memory memory_store tool to store this exact entry: namespace 'default', key 'pantheon-sandbox-probe-$v', value 'alive'. Then reply with exactly: MEMORY-STORE-OK"
@@ -304,7 +390,7 @@ build_battery() {
304
390
  add_prompt "tmp-write-read" "TMP-OK" "" \
305
391
  "${TMPDIR:-/tmp}/pantheon-sandbox-probe-$v.txt::TMP-OK" \
306
392
  "Using the bash tool, run a python3 -c command that writes the text TMP-OK to the file ${TMPDIR:-/tmp}/pantheon-sandbox-probe-$v.txt (overwrite it), then run another python3 -c command to read that file back. Do not use any other tool or command to write or read the file — only python3 -c. If what you read back is exactly TMP-OK, reply with exactly: TMP-OK"
307
- add_prompt "delegation" "DELEGATION-OK" "DELEGATION-UNAVAILABLE" "" \
393
+ add_prompt "delegation" "DELEGATION-OK" "" "" \
308
394
  "Delegate a tiny task to the @talos agent via the task tool: ask it to reply with the single word PONG. If the delegated response contains PONG, reply with exactly: DELEGATION-OK. If you cannot delegate to agents in this context, reply with exactly: DELEGATION-UNAVAILABLE"
309
395
  }
310
396
 
@@ -322,10 +408,9 @@ ATTEMPT_RESULT=""
322
408
  ATTEMPT_DETAIL=""
323
409
 
324
410
  run_prompt_attempt() { # v idx current_bin — sets ATTEMPT_RESULT / ATTEMPT_DETAIL
325
- local v="$1" idx="$2" bin="$3" id marker amb_marker verify text
411
+ local v="$1" idx="$2" bin="$3" id marker verify text
326
412
  id="${PROMPT_IDS[$idx]}"
327
413
  marker="${PROMPT_MARKERS[$idx]}"
328
- amb_marker="${PROMPT_AMB_MARKERS[$idx]}"
329
414
  verify="${PROMPT_VERIFY[$idx]}"
330
415
  text="${PROMPT_TEXTS[$idx]}"
331
416
 
@@ -352,75 +437,59 @@ run_prompt_attempt() { # v idx current_bin — sets ATTEMPT_RESULT / ATTEMPT_DET
352
437
  rm -f "$out_file" "$err_file"
353
438
 
354
439
  if [ "$rc" -eq 124 ]; then
355
- ATTEMPT_RESULT="AMBIENTAL"
356
- ATTEMPT_DETAIL="timeout after ${PANTHEON_PROMPT_TIMEOUT}s (provider latency)"
440
+ ATTEMPT_RESULT="FAIL"
441
+ ATTEMPT_DETAIL="timeout after ${PANTHEON_PROMPT_TIMEOUT}s"
442
+ elif [ "$rc" -ne 0 ]; then
443
+ ATTEMPT_RESULT="FAIL"
444
+ ATTEMPT_DETAIL="marker $marker absent; exit $rc; snippet: $(printf '%s' "$raw" | cut -c1-160)"
357
445
  elif printf '%s' "$extracted" | grep -q "$marker"; then
358
446
  ATTEMPT_RESULT="PASS"
359
447
  ATTEMPT_DETAIL="marker $marker found"
360
- elif [ -n "$amb_marker" ] && printf '%s' "$extracted" | grep -q "$amb_marker"; then
361
- ATTEMPT_RESULT="AMBIENTAL"
362
- ATTEMPT_DETAIL="explicit $amb_marker"
363
448
  elif [ -n "$verify" ] \
364
449
  && [ "$(cat "${verify%%::*}" 2>/dev/null)" = "${verify##*::}" ]; then
365
450
  ATTEMPT_RESULT="PASS"
366
451
  ATTEMPT_DETAIL="probe file verified: ${verify%%::*} contains ${verify##*::}"
367
- elif printf '%s' "$raw" | grep -Eiq "$AMBIENTAL_RE"; then
368
- local sig
369
- sig="$(printf '%s' "$raw" | grep -Eio "$AMBIENTAL_RE" | head -1)"
370
- ATTEMPT_RESULT="AMBIENTAL"
371
- ATTEMPT_DETAIL="environmental signature: $sig"
372
452
  else
373
453
  ATTEMPT_RESULT="FAIL"
374
454
  ATTEMPT_DETAIL="marker $marker absent; rc=$rc; snippet: $(printf '%s' "$raw" | cut -c1-160)"
375
455
  fi
376
456
  }
377
457
 
378
- RETRY_COOLDOWN="${PANTHEON_RETRY_COOLDOWN:-60}"
379
- RETRIES_USED=0
380
-
381
- run_prompt() { # v idx current_bin — ONE retry after cooldown for real FAIL only
382
- local v="$1" idx="$2" bin="$3" id first_detail
458
+ run_prompt() { # v idx current_bin — exactly one attempt
459
+ local v="$1" idx="$2" bin="$3" id
383
460
  id="${PROMPT_IDS[$idx]}"
384
461
  run_prompt_attempt "$v" "$idx" "$bin"
385
- if [ "$ATTEMPT_RESULT" = "FAIL" ]; then
386
- first_detail="$ATTEMPT_DETAIL"
387
- log "[$v] prompt $id FAIL on first attempt — retrying once after ${RETRY_COOLDOWN}s cooldown"
388
- sleep "$RETRY_COOLDOWN"
389
- run_prompt_attempt "$v" "$idx" "$bin"
390
- RETRIES_USED=$((RETRIES_USED + 1))
391
- if [ "$ATTEMPT_RESULT" != "FAIL" ]; then
392
- ATTEMPT_DETAIL="$ATTEMPT_DETAIL [retry used — first attempt: $first_detail]"
393
- else
394
- ATTEMPT_DETAIL="$ATTEMPT_DETAIL [FAIL persisted after retry — first attempt: $first_detail]"
395
- fi
396
- fi
397
462
  record "$v" "$id" "$ATTEMPT_RESULT" "$ATTEMPT_DETAIL"
398
463
  }
399
464
 
400
465
  check_mcp_list() { # v
401
- local v="$1" bin out rc=0
466
+ local v="$1" bin out connected_count rc=0
402
467
  if ! bin="$(sandbox_bin_v "$v")"; then
403
- record "$v" "mcp-list" "AMBIENTAL" "binary not installed in sandbox prefix"
468
+ record "$v" "mcp-list" "FAIL" "binary not installed in sandbox prefix"
404
469
  return 0
405
470
  fi
406
471
  set +e
407
472
  out="$(cd "$(project_for "$v")" && "$bin" mcp list 2>&1)"
408
473
  rc=$?
409
474
  set -e
475
+ out="$(printf '%s' "$out" | sed -E $'s/\x1B\\[[0-?]*[ -/]*[@-~]//g')"
410
476
  if [ "$rc" -ne 0 ]; then
411
477
  record "$v" "mcp-list" "FAIL" "opencode mcp list exited $rc"
412
- elif printf '%s' "$out" | grep -qi "connected" \
478
+ return 0
479
+ fi
480
+ connected_count="$(printf '%s' "$out" | grep -Eic '(^|[^[:alnum:]_])connected([^[:alnum:]_]|$)')" || connected_count=0
481
+ if [ "$connected_count" -eq 5 ] \
413
482
  && ! printf '%s' "$out" | grep -Eiq "failed|✘"; then
414
- record "$v" "mcp-list" "PASS" "$(printf '%s' "$out" | grep -ci 'connected') connected"
483
+ record "$v" "mcp-list" "PASS" "$connected_count connected (expected exactly 5)"
415
484
  else
416
- record "$v" "mcp-list" "FAIL" "no connected MCPs or failures reported"
485
+ record "$v" "mcp-list" "FAIL" "expected exactly 5 connected MCPs; found $connected_count or failures reported"
417
486
  fi
418
487
  }
419
488
 
420
489
  check_doctor() { # v
421
490
  local v="$1" pan rc=0
422
491
  if ! pan="$(sandbox_bin_for "pantheon-opencode")"; then
423
- record "$v" "doctor" "AMBIENTAL" "pantheon-opencode not installed in sandbox prefix"
492
+ record "$v" "doctor" "FAIL" "pantheon-opencode not installed in sandbox prefix"
424
493
  return 0
425
494
  fi
426
495
  set +e
@@ -434,17 +503,101 @@ check_doctor() { # v
434
503
  fi
435
504
  }
436
505
 
506
+ installed_package_root() {
507
+ local pan resolved
508
+ if ! pan="$(sandbox_bin_for "pantheon-opencode")"; then
509
+ return 1
510
+ fi
511
+ resolved="$(readlink -f "$pan" 2>/dev/null || true)"
512
+ [ -n "$resolved" ] || return 1
513
+ dirname "$(dirname "$resolved")"
514
+ }
515
+
516
+ run_cost_probe() { # v
517
+ local v="$1" package_root probe raw status detail
518
+ if ! package_root="$(installed_package_root)"; then
519
+ record "$v" "pantheon-cost" "FAIL" "sandbox package is not installed"
520
+ return 0
521
+ fi
522
+ probe="$package_root/scripts/probe-pantheon-cost.mjs"
523
+ if [ ! -f "$probe" ]; then
524
+ record "$v" "pantheon-cost" "FAIL" "installed package has no offline cost probe"
525
+ return 0
526
+ fi
527
+ if ! command -v node >/dev/null 2>&1; then
528
+ record "$v" "pantheon-cost" "FAIL" "node runtime is not available"
529
+ return 0
530
+ fi
531
+ set +e
532
+ raw="$(node "$probe" --version "$v" --fixture --json 2>&1)"
533
+ local rc=$?
534
+ set -e
535
+ case "$raw" in
536
+ *'"status":"PASS"'*) status="PASS" ;;
537
+ *'"status":"NOT_TESTED"'*) status="FAIL" ;;
538
+ *'"status":"AMBIENTAL"'*) status="FAIL" ;;
539
+ *) status="FAIL" ;;
540
+ esac
541
+ detail="$(printf '%s' "$raw" | tr '\n' ' ' | cut -c1-400)"
542
+ if [ "$rc" -ne 0 ] && [ "$status" = "PASS" ]; then
543
+ status="FAIL"
544
+ detail="$detail (probe exit $rc)"
545
+ elif [ "$rc" -ne 0 ] && [ "$status" != "FAIL" ]; then
546
+ detail="$detail (probe exit $rc)"
547
+ fi
548
+ record "$v" "pantheon-cost" "$status" "$detail"
549
+ }
550
+
551
+ run_rehydrate_probe() { # v
552
+ local v="$1" package_root probe raw status detail
553
+ if ! package_root="$(installed_package_root)"; then
554
+ record "$v" "context-rehydrate" "FAIL" "sandbox package is not installed"
555
+ return 0
556
+ fi
557
+ probe="$package_root/scripts/probe-context-rehydrate.mjs"
558
+ if [ ! -f "$probe" ]; then
559
+ record "$v" "context-rehydrate" "FAIL" "installed package has no offline context probe"
560
+ return 0
561
+ fi
562
+ if ! command -v node >/dev/null 2>&1; then
563
+ record "$v" "context-rehydrate" "FAIL" "node runtime is not available"
564
+ return 0
565
+ fi
566
+ set +e
567
+ raw="$(timeout 30 node "$probe" --version "$v" --json 2>&1)"
568
+ local rc=$?
569
+ set -e
570
+ case "$raw" in
571
+ *'"status":"PASS"'*) status="PASS" ;;
572
+ *'"status":"NOT_TESTED"'*) status="FAIL" ;;
573
+ *'"status":"AMBIENTAL"'*) status="FAIL" ;;
574
+ *) status="FAIL" ;;
575
+ esac
576
+ detail="$(printf '%s' "$raw" | tr '\n' ' ' | cut -c1-400)"
577
+ if [ "$rc" -eq 124 ]; then
578
+ status="FAIL"
579
+ detail="context probe timed out after 30s"
580
+ elif [ "$rc" -ne 0 ] && [ "$status" = "PASS" ]; then
581
+ # A non-zero probe exit must never be reported as PASS.
582
+ status="FAIL"
583
+ detail="$detail (probe exit $rc)"
584
+ elif [ "$rc" -ne 0 ] && [ "$status" != "FAIL" ]; then
585
+ detail="$detail (probe exit $rc)"
586
+ fi
587
+ record "$v" "context-rehydrate" "$status" "$detail"
588
+ }
589
+
437
590
  run_version_prompts() { # v
438
591
  local v="$1" bin
439
592
  if ! bin="$(sandbox_bin_v "$v")"; then
440
- log "Binary '$(bin_for "$v")' not in sandbox prefix — marking all $v prompts AMBIENTAL."
593
+ log "Binary '$(bin_for "$v")' not in sandbox prefix — failing all $v checks."
441
594
  build_battery "$v"
442
595
  local i
443
596
  for i in "${!PROMPT_IDS[@]}"; do
444
- record "$v" "${PROMPT_IDS[$i]}" "AMBIENTAL" "binary $(bin_for "$v") not installed in sandbox"
597
+ record "$v" "${PROMPT_IDS[$i]}" "FAIL" "binary $(bin_for "$v") not installed in sandbox"
445
598
  done
446
- record "$v" "mcp-list" "AMBIENTAL" "binary $(bin_for "$v") not installed in sandbox"
447
- record "$v" "doctor" "AMBIENTAL" "binary $(bin_for "$v") not installed in sandbox"
599
+ record "$v" "mcp-list" "FAIL" "binary $(bin_for "$v") not installed in sandbox"
600
+ record "$v" "doctor" "FAIL" "binary $(bin_for "$v") not installed in sandbox"
448
601
  return 0
449
602
  fi
450
603
  prepare_project "$v"
@@ -459,8 +612,17 @@ run_version_prompts() { # v
459
612
  check_doctor "$v"
460
613
  }
461
614
 
615
+ probe_verdict() { # pass total → PASS when every required check was explicitly PASS
616
+ local pass="$1" total="$2"
617
+ if [ "$pass" -eq "$total" ] && [ "$pass" -gt 0 ]; then
618
+ printf 'PASS'
619
+ else
620
+ printf 'FAIL'
621
+ fi
622
+ }
623
+
462
624
  write_report() { # versions...
463
- local versions=("$@") total_fail=0 total_pass=0 total_amb=0
625
+ local versions=("$@") total_fail=0 total_pass=0
464
626
  {
465
627
  printf '# Pantheon Sandbox Prompts Report\n\n'
466
628
  printf -- '- **Date:** %s\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
@@ -475,26 +637,108 @@ write_report() { # versions...
475
637
  for id in "${PROMPT_IDS[@]}" mcp-list doctor; do
476
638
  res="${RESULTS["$v:$id"]:-SKIP}"
477
639
  case "$res" in
478
- PASS) total_pass=$((total_pass + 1)) ;;
479
- AMBIENTAL) total_amb=$((total_amb + 1)) ;;
480
- FAIL) total_fail=$((total_fail + 1)) ;;
640
+ PASS) total_pass=$((total_pass + 1)) ;;
641
+ *) total_fail=$((total_fail + 1)) ;;
481
642
  esac
482
643
  printf '| %s | %s | %s |\n' "$id" "$res" "${DETAILS["$v:$id"]:-—}"
483
644
  done
484
645
  printf '\n'
485
646
  done
486
647
  printf '## Summary\n\n'
487
- printf -- '- PASS: %d\n- FAIL (real): %d\n- AMBIENTAL: %d\n- Retries used: %d\n\n' \
488
- "$total_pass" "$total_fail" "$total_amb" "$RETRIES_USED"
489
- if [ "$total_fail" -eq 0 ]; then
490
- printf '**Verdict: PASS** — no real failures (AMBIENTAL issues do not block).\n'
491
- else
492
- printf '**Verdict: FAIL** — real failures detected (see table above).\n'
493
- fi
648
+ printf -- '- PASS: %d\n- Not PASS (blocking): %d\n\n' \
649
+ "$total_pass" "$total_fail"
650
+ printf '**Verdict: %s**\n' "$(probe_verdict "$total_pass" "$((total_pass + total_fail))")"
494
651
  } > "$REPORT_FILE"
495
652
  log "Report written to $REPORT_FILE"
496
653
  }
497
654
 
655
+ write_cost_report() { # versions...
656
+ local versions=("$@") total_fail=0 total_pass=0
657
+ {
658
+ printf '# Pantheon Cost Probe Report\n\n'
659
+ printf -- '- **Date:** %s\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
660
+ printf -- '- **Sandbox:** %s\n\n' "$SANDBOX_ROOT"
661
+ printf '| Version | Result | Detail |\n|---|---|---|\n'
662
+ local v result
663
+ for v in "${versions[@]}"; do
664
+ result="${RESULTS["$v:pantheon-cost"]:-NOT_TESTED}"
665
+ case "$result" in
666
+ PASS) total_pass=$((total_pass + 1)) ;;
667
+ *) total_fail=$((total_fail + 1)) ;;
668
+ esac
669
+ printf '| %s | %s | %s |\n' "$v" "$result" "${DETAILS["$v:pantheon-cost"]:-—}"
670
+ done
671
+ printf '\n## Summary\n\n'
672
+ printf -- '- PASS: %d\n- Not PASS (blocking): %d\n\n' \
673
+ "$total_pass" "$total_fail"
674
+ printf '**Verdict: %s**\n' "$(probe_verdict "$total_pass" "$((total_pass + total_fail))")"
675
+ } > "$COST_REPORT_FILE"
676
+ log "Cost probe report written to $COST_REPORT_FILE"
677
+ }
678
+
679
+ write_rehydrate_report() { # versions...
680
+ local versions=("$@") total_fail=0 total_pass=0
681
+ {
682
+ printf '# Pantheon Context Rehydration Probe Report\n\n'
683
+ printf -- '- **Date:** %s\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
684
+ printf -- '- **Sandbox:** %s\n\n' "$SANDBOX_ROOT"
685
+ printf '| Version | Probe | Result | Detail |\n|---|---|---|---|\n'
686
+ local v result
687
+ for v in "${versions[@]}"; do
688
+ result="${RESULTS["$v:context-rehydrate"]:-NOT_TESTED}"
689
+ case "$result" in
690
+ PASS) total_pass=$((total_pass + 1)) ;;
691
+ *) total_fail=$((total_fail + 1)) ;;
692
+ esac
693
+ printf '| %s | context_rehydrate + context_session_summary | %s | %s |\n' \
694
+ "$v" "$result" "${DETAILS["$v:context-rehydrate"]:-—}"
695
+ done
696
+ printf '\n## Summary\n\n'
697
+ printf -- '- PASS: %d\n- Not PASS (blocking): %d\n\n' \
698
+ "$total_pass" "$total_fail"
699
+ printf '**Verdict: %s**\n' "$(probe_verdict "$total_pass" "$((total_pass + total_fail))")"
700
+ } > "$REHYDRATE_REPORT_FILE"
701
+ log "Context probe report written to $REHYDRATE_REPORT_FILE"
702
+ }
703
+
704
+ run_cost() { # versions...
705
+ local versions=("$@") v fail=0
706
+ mkdir -p "$SANDBOX_ROOT"
707
+ for v in "${versions[@]}"; do
708
+ log "[$v] pantheon_cost offline probe ..."
709
+ run_cost_probe "$v"
710
+ log "[$v] pantheon-cost → ${RESULTS["$v:pantheon-cost"]}"
711
+ done
712
+ write_cost_report "${versions[@]}"
713
+ for v in "${versions[@]}"; do
714
+ [ "${RESULTS["$v:pantheon-cost"]:-}" = "PASS" ] || fail=$((fail + 1))
715
+ done
716
+ if [ "$fail" -gt 0 ]; then
717
+ log "Cost probe: $fail check(s) without explicit PASS."
718
+ exit 1
719
+ fi
720
+ log "Cost probe: PASS."
721
+ }
722
+
723
+ run_rehydrate() { # versions...
724
+ local versions=("$@") v fail=0
725
+ mkdir -p "$SANDBOX_ROOT"
726
+ for v in "${versions[@]}"; do
727
+ log "[$v] context_rehydrate/session_summary offline probe ..."
728
+ run_rehydrate_probe "$v"
729
+ log "[$v] context probe → ${RESULTS["$v:context-rehydrate"]}"
730
+ done
731
+ write_rehydrate_report "${versions[@]}"
732
+ for v in "${versions[@]}"; do
733
+ [ "${RESULTS["$v:context-rehydrate"]:-}" = "PASS" ] || fail=$((fail + 1))
734
+ done
735
+ if [ "$fail" -gt 0 ]; then
736
+ log "Context probe: $fail check(s) without explicit PASS."
737
+ exit 1
738
+ fi
739
+ log "Context probe: PASS."
740
+ }
741
+
498
742
  run_battery() { # versions...
499
743
  local versions=("$@")
500
744
  mkdir -p "$SANDBOX_ROOT"
@@ -507,14 +751,14 @@ run_battery() { # versions...
507
751
  local v fail=0 id
508
752
  for v in "${versions[@]}"; do
509
753
  for id in "${PROMPT_IDS[@]}" mcp-list doctor; do
510
- [ "${RESULTS["$v:$id"]:-}" = "FAIL" ] && fail=$((fail + 1)) || true
754
+ [ "${RESULTS["$v:$id"]:-}" = "PASS" ] || fail=$((fail + 1))
511
755
  done
512
756
  done
513
757
  if [ "$fail" -gt 0 ]; then
514
- log "Prompts battery: $fail real FAIL(s)."
758
+ log "Prompts battery: $fail check(s) without explicit PASS."
515
759
  exit 1
516
760
  fi
517
- log "Prompts battery: no real failures."
761
+ log "Prompts battery: PASS."
518
762
  }
519
763
 
520
764
  # ── Base validation (no prompts) ──────────────────────────────────────────────
@@ -535,13 +779,13 @@ run_base() { # v
535
779
  check_doctor "$v"
536
780
  log "[$v] doctor → ${RESULTS["$v:doctor"]}"
537
781
  local fail=0
538
- [ "${RESULTS["$v:mcp-list"]}" = "FAIL" ] && fail=$((fail + 1)) || true
539
- [ "${RESULTS["$v:doctor"]}" = "FAIL" ] && fail=$((fail + 1)) || true
782
+ [ "${RESULTS["$v:mcp-list"]}" = "PASS" ] || fail=$((fail + 1))
783
+ [ "${RESULTS["$v:doctor"]}" = "PASS" ] || fail=$((fail + 1))
540
784
  if [ "$fail" -gt 0 ]; then
541
- log "Base validation $v: $fail real FAIL(s)."
785
+ log "Base validation $v: $fail check(s) without explicit PASS."
542
786
  exit 1
543
787
  fi
544
- log "Base validation $v: OK."
788
+ log "Base validation $v: PASS."
545
789
  }
546
790
 
547
791
  # ── CLI ───────────────────────────────────────────────────────────────────────
@@ -551,6 +795,8 @@ while [ $# -gt 0 ]; do
551
795
  --prepare) MODE_PREPARE=1 ;;
552
796
  --reset) MODE_RESET=1 ;;
553
797
  --prompts) MODE_PROMPTS=1 ;;
798
+ --cost) MODE_COST=1 ;;
799
+ --rehydrate) MODE_REHYDRATE=1 ;;
554
800
  --run)
555
801
  shift
556
802
  case "${1:-}" in
@@ -569,7 +815,9 @@ if [ "$MODE_RESET" -eq 1 ]; then
569
815
  exit 0
570
816
  fi
571
817
 
572
- if [ "$MODE_PREPARE" -eq 0 ] && [ -z "$RUN_VERSION" ] && [ "$MODE_PROMPTS" -eq 0 ]; then
818
+ if [ "$MODE_PREPARE" -eq 0 ] && [ -z "$RUN_VERSION" ] \
819
+ && [ "$MODE_PROMPTS" -eq 0 ] && [ "$MODE_COST" -eq 0 ] \
820
+ && [ "$MODE_REHYDRATE" -eq 0 ]; then
573
821
  usage
574
822
  fi
575
823
 
@@ -585,7 +833,30 @@ if [ -n "$RUN_VERSION" ]; then
585
833
  sandbox_env
586
834
  run_base "$RUN_VERSION"
587
835
  fi
836
+ if [ "$MODE_COST" -eq 1 ]; then
837
+ sandbox_env
838
+ run_cost "$RUN_VERSION"
839
+ fi
840
+ if [ "$MODE_REHYDRATE" -eq 1 ]; then
841
+ sandbox_env
842
+ run_rehydrate "$RUN_VERSION"
843
+ fi
588
844
  elif [ "$MODE_PROMPTS" -eq 1 ]; then
589
845
  sandbox_env
590
846
  run_battery v1 v2
847
+ if [ "$MODE_COST" -eq 1 ]; then
848
+ run_cost v1 v2
849
+ fi
850
+ if [ "$MODE_REHYDRATE" -eq 1 ]; then
851
+ run_rehydrate v1 v2
852
+ fi
853
+ elif [ "$MODE_COST" -eq 1 ]; then
854
+ sandbox_env
855
+ run_cost v1 v2
856
+ if [ "$MODE_REHYDRATE" -eq 1 ]; then
857
+ run_rehydrate v1 v2
858
+ fi
859
+ elif [ "$MODE_REHYDRATE" -eq 1 ]; then
860
+ sandbox_env
861
+ run_rehydrate v1 v2
591
862
  fi