pantheon-opencode 1.4.3 → 1.5.0-beta.2
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/.pantheon/code-mode/README.md +93 -0
- package/.pantheon/code-mode/checkpoint-session.py +7 -0
- package/.pantheon/code-mode/checkpoint-session.sh +11 -0
- package/.pantheon/code-mode/checkpoint_session.py +481 -0
- package/.pantheon/code-mode/compress-inline.py +450 -0
- package/.pantheon/code-mode/eval-llm-judge.py +255 -0
- package/.pantheon/code-mode/eval-monte-carlo.py +368 -0
- package/.pantheon/code-mode/eval-run.py +197 -0
- package/.pantheon/code-mode/eval-static.py +321 -0
- package/.pantheon/code-mode/example-sync.sh +5 -0
- package/.pantheon/code-mode/session-end-save.py +186 -0
- package/.pantheon/code-mode/session-end-save.sh +18 -0
- package/.pantheon/code-mode/test-allowed.sh +5 -0
- package/.pantheon/code-mode/test-auto-continue-checkpoint.sh +179 -0
- package/.pantheon/code-mode/test-env.sh +7 -0
- package/.pantheon/code-mode/test-prlimit.sh +9 -0
- package/.pantheon/code-mode/test-timeout.sh +6 -0
- package/README.md +161 -1101
- package/README.pt-BR.md +222 -0
- package/bin/pantheon-init.mjs +30 -11
- package/opencode.json +1 -0
- package/package.json +14 -3
- package/plugin.json +15 -0
- package/scripts/_pantheon_paths.py +48 -7
- package/scripts/ci-validate-yaml.py +7 -2
- package/scripts/code_mode_server.py +33 -2
- package/scripts/cost.mjs +30 -14
- package/scripts/doctor.mjs +135 -127
- package/scripts/generate-preset-docs.mjs +127 -66
- package/scripts/generate-prompts.sh +2 -2
- package/scripts/install/cli-ui.mjs +8 -1
- package/scripts/install/config-migration.mjs +401 -0
- package/scripts/install/health-check.mjs +3 -3
- package/scripts/install/model-picker.mjs +72 -23
- package/scripts/install/opencode-version.mjs +47 -0
- package/scripts/install/opencode.mjs +261 -88
- package/scripts/install/plugin.mjs +112 -42
- package/scripts/install/shared.mjs +13 -158
- package/scripts/install-mcp.mjs +44 -232
- package/scripts/mcp_persistence_server.py +382 -16
- package/scripts/mcp_resources_server.py +144 -40
- package/scripts/memory_mcp_server.py +2 -2
- package/scripts/probe-context-rehydrate.mjs +240 -0
- package/scripts/probe-pantheon-cost.mjs +304 -0
- package/scripts/recover-zenodo-deposition.mjs +146 -0
- package/scripts/sync-tui.mjs +32 -7
- package/scripts/test-opencode-v1-v2-sandbox.sh +944 -0
- package/scripts/uninstall.mjs +298 -954
- package/scripts/validate-zenodo-release.mjs +154 -0
- package/scripts/zenodo-artifact.mjs +21 -0
- package/scripts/zenodo-release-state.mjs +53 -0
- package/src/agents/zeus.md +25 -14
- package/src/mcp/_pantheon_paths.py +48 -7
- package/src/mcp/code_mode_server.py +33 -2
- package/src/mcp/eval_store.py +1 -1
- package/src/mcp/mcp_persistence_server.py +382 -16
- package/src/mcp/mcp_resources_server.py +144 -40
- package/src/mcp/pantheon_vision_server.py +1 -1
- package/src/mcp/toon_codec.py +491 -0
- package/src/pantheon/background-job-board.ts +67 -1
- package/src/pantheon/cost-command.ts +60 -39
- package/src/pantheon/delegate-manager.ts +669 -0
- package/src/pantheon/delegation-classifier.ts +307 -0
- package/src/pantheon/delegation-enforce.ts +13 -12
- package/src/pantheon/delegation-finalize.ts +374 -0
- package/src/pantheon/delegation-notify.ts +212 -0
- package/src/pantheon/delegation-toast.ts +73 -0
- package/src/pantheon/delegation.ts +1120 -0
- package/src/pantheon/goal-loop.ts +2 -2
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/hashline/tool.ts +2 -2
- package/src/pantheon/model-command.ts +2 -1
- package/src/pantheon/permission-eval.ts +284 -0
- package/src/pantheon/step-cap.ts +110 -0
- package/src/pantheon/token-opt.ts +781 -0
- package/src/pantheon/toon-codec.ts +481 -0
- package/src/pantheon/tui-version-check.ts +1 -3
- package/src/pantheon/v2-bridge.ts +129 -0
- package/src/pantheon/v2-events.ts +131 -0
- package/src/pantheon/v2-hooks.ts +287 -0
- package/src/pantheon/v2-tool-definitions.ts +331 -0
- package/src/pantheon/zeus-delegate-with-retry.ts +406 -0
- package/src/plugin-v2/index.ts +21 -0
- package/src/plugin-v2.ts +824 -0
- package/src/plugin.ts +236 -42
- package/src/plugins/tui/dist/server.d.ts +1 -1
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/server.js +1 -1
- package/src/plugins/tui/dist/server.js.map +1 -1
- package/src/plugins/tui/dist/tui.d.ts +10 -7
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +11 -8
- package/src/plugins/tui/dist/tui.js.map +1 -1
- package/src/plugins/tui/dist/tui.tsx +22 -16
- package/src/plugins/tui/package-lock.json +51 -51
- package/src/plugins/tui/package.json +7 -7
- package/src/plugins/tui/src/index.tsx +28 -24
- package/src/skills/README.md +0 -2
- package/src/pantheon/tool-context.ts +0 -41
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-opencode-v1-v2-sandbox.sh — Pantheon global-install sandbox validator
|
|
3
|
+
# for OpenCode V1 (`opencode`) and V2 (`opencode2`) side-by-side.
|
|
4
|
+
#
|
|
5
|
+
# Validates the installed pantheon-opencode package AS A REAL USER inside an
|
|
6
|
+
# isolated sandbox (own HOME, npm prefix and venv), never the dev environment.
|
|
7
|
+
#
|
|
8
|
+
# Modes (combinable):
|
|
9
|
+
# --prepare Build tarball, install pantheon-opencode + OpenCode V1/V2
|
|
10
|
+
# binaries in the sandbox prefix, headless init, and generate
|
|
11
|
+
# the per-version project configs and sandbox run-test.sh.
|
|
12
|
+
# --run v1|v2 Base validation for one version: opencode mcp list + doctor
|
|
13
|
+
# (config is regenerated for that version first).
|
|
14
|
+
# --prompts Prompt battery via `opencode run --format json` for every
|
|
15
|
+
# requested version (with --run vX) or both versions alone.
|
|
16
|
+
# --cost Offline/ambiental 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/ambiental context_rehydrate + context_session_summary
|
|
20
|
+
# probe for the requested version (with --run vX) or both.
|
|
21
|
+
# --reset Wipe the sandbox root.
|
|
22
|
+
# --help Usage.
|
|
23
|
+
#
|
|
24
|
+
# Env overrides:
|
|
25
|
+
# PANTHEON_SANDBOX_ROOT Sandbox root (default: ~/pantheon-sandbox)
|
|
26
|
+
# OPENCODE_V1_SPEC npm spec providing the `opencode` binary
|
|
27
|
+
# (default: opencode-ai@1.18.18)
|
|
28
|
+
# OPENCODE_V2_SPEC npm spec providing the `opencode2` binary
|
|
29
|
+
# (default: @opencode-ai/cli@beta)
|
|
30
|
+
# PANTHEON_SANDBOX_MODEL Model used by init/prompts
|
|
31
|
+
# (default: opencode-go/mimo-v2.5)
|
|
32
|
+
# PANTHEON_PROMPT_TIMEOUT Per-prompt timeout in seconds (default: 300)
|
|
33
|
+
# PANTHEON_RETRY_COOLDOWN Cooldown seconds before the single FAIL retry
|
|
34
|
+
# (default: 60; retry only for real FAIL, never AMBIENTAL)
|
|
35
|
+
#
|
|
36
|
+
# Exit codes: 0 = no real failures (AMBIENTAL issues do not fail the run),
|
|
37
|
+
# 1 = real FAIL (see prompts-report.md in the sandbox root),
|
|
38
|
+
# 2 = usage error, 3 = sandbox not prepared.
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
|
|
41
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
42
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
43
|
+
|
|
44
|
+
SANDBOX_ROOT="${PANTHEON_SANDBOX_ROOT:-$HOME/pantheon-sandbox}"
|
|
45
|
+
SANDBOX_HOME="$SANDBOX_ROOT/home"
|
|
46
|
+
NPM_PREFIX="$SANDBOX_HOME/.npm-global"
|
|
47
|
+
|
|
48
|
+
OPENCODE_V1_SPEC="${OPENCODE_V1_SPEC:-opencode-ai@1.18.18}"
|
|
49
|
+
OPENCODE_V2_SPEC="${OPENCODE_V2_SPEC:-@opencode-ai/cli@beta}"
|
|
50
|
+
PANTHEON_SANDBOX_MODEL="${PANTHEON_SANDBOX_MODEL:-opencode-go/mimo-v2.5}"
|
|
51
|
+
PANTHEON_PROMPT_TIMEOUT="${PANTHEON_PROMPT_TIMEOUT:-300}"
|
|
52
|
+
|
|
53
|
+
V1_BIN="${OPENCODE_V1_BIN:-opencode}"
|
|
54
|
+
V2_BIN="${OPENCODE_V2_BIN:-opencode2}"
|
|
55
|
+
|
|
56
|
+
REPORT_FILE="$SANDBOX_ROOT/prompts-report.md"
|
|
57
|
+
COST_REPORT_FILE="$SANDBOX_ROOT/pantheon-cost-report.md"
|
|
58
|
+
REHYDRATE_REPORT_FILE="$SANDBOX_ROOT/context-rehydrate-report.md"
|
|
59
|
+
EXTRACT_PY="$SANDBOX_ROOT/.prompt-extract-json.py"
|
|
60
|
+
|
|
61
|
+
MODE_PREPARE=0
|
|
62
|
+
MODE_RESET=0
|
|
63
|
+
MODE_PROMPTS=0
|
|
64
|
+
MODE_COST=0
|
|
65
|
+
MODE_REHYDRATE=0
|
|
66
|
+
RUN_VERSION=""
|
|
67
|
+
|
|
68
|
+
usage() {
|
|
69
|
+
sed -n '2,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
70
|
+
exit 2
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
log() { printf '%s\n' "$*"; }
|
|
74
|
+
warn() { printf 'WARN: %s\n' "$*" >&2; }
|
|
75
|
+
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
|
76
|
+
|
|
77
|
+
bin_for() {
|
|
78
|
+
case "$1" in
|
|
79
|
+
v1) printf '%s' "$V1_BIN" ;;
|
|
80
|
+
v2) printf '%s' "$V2_BIN" ;;
|
|
81
|
+
*) return 1 ;;
|
|
82
|
+
esac
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
project_for() { printf '%s/project-%s' "$SANDBOX_ROOT" "$1"; }
|
|
86
|
+
|
|
87
|
+
# Resolve a binary STRICTLY inside the sandbox npm prefix — never fall back to
|
|
88
|
+
# the dev PATH, otherwise a non-prepared sandbox would silently test the host
|
|
89
|
+
# installation.
|
|
90
|
+
sandbox_bin_for() { # name → prints path; rc 1 if not inside sandbox prefix
|
|
91
|
+
local p
|
|
92
|
+
p="$(command -v "$1" 2>/dev/null || true)"
|
|
93
|
+
case "$p" in
|
|
94
|
+
"$NPM_PREFIX"/*) printf '%s' "$p" ;;
|
|
95
|
+
*) return 1 ;;
|
|
96
|
+
esac
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
sandbox_bin_v() { # v → echoes path; rc 1 if missing
|
|
100
|
+
sandbox_bin_for "$(bin_for "$1")"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# ── Sandbox environment isolation ─────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
sandbox_env() {
|
|
106
|
+
export HOME="$SANDBOX_HOME"
|
|
107
|
+
export PATH="$NPM_PREFIX/bin:$HOME/.config/opencode/.venv/bin:$PATH"
|
|
108
|
+
export npm_config_prefix="$NPM_PREFIX"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# ── Gate (b): sandbox run-test.sh with pantheon://agents content validation ──
|
|
112
|
+
|
|
113
|
+
write_run_test_sh() {
|
|
114
|
+
cat > "$SANDBOX_ROOT/run-test.sh" <<'RUNTEST'
|
|
115
|
+
#!/usr/bin/env bash
|
|
116
|
+
# Generated by pantheon-opencode scripts/test-opencode-v1-v2-sandbox.sh --prepare
|
|
117
|
+
# Validates the global install: binaries, exactly 5 connected MCPs per version,
|
|
118
|
+
# doctor 0 errors, AND the CONTENT of pantheon://agents (zeus/hermes present).
|
|
119
|
+
set -euo pipefail
|
|
120
|
+
|
|
121
|
+
SANDBOX_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
122
|
+
SANDBOX_HOME="$SANDBOX_DIR/home"
|
|
123
|
+
NPM_PREFIX="$SANDBOX_HOME/.npm-global"
|
|
124
|
+
PANTHEON_GLOBAL="$SANDBOX_HOME/.config/opencode"
|
|
125
|
+
SANDBOX_VENV="$PANTHEON_GLOBAL/.venv"
|
|
126
|
+
REPO_DIR="${PANTHEON_REPO:-$(cd "$SANDBOX_DIR/../pantheon" && pwd)}"
|
|
127
|
+
|
|
128
|
+
echo "=== Pantheon Sandbox Test ==="
|
|
129
|
+
echo "Sandbox: $SANDBOX_DIR"
|
|
130
|
+
echo "Repo: $REPO_DIR"
|
|
131
|
+
|
|
132
|
+
export HOME="$SANDBOX_HOME"
|
|
133
|
+
export PATH="$NPM_PREFIX/bin:$SANDBOX_VENV/bin:$PATH"
|
|
134
|
+
export npm_config_prefix="$NPM_PREFIX"
|
|
135
|
+
|
|
136
|
+
STATUS_FAIL=0
|
|
137
|
+
STATUS_AMBIENTAL=0
|
|
138
|
+
STATUS_NOT_TESTED=0
|
|
139
|
+
STATUS_PASS=0
|
|
140
|
+
MCP_AMBIENTAL_RE='needs authentication|unauthorized|authentication failure|api key|credentials|401|docker|econnrefused|enotfound|etimedout|econnreset|connection refused|rate.?limit|429|quota|overloaded|edgesout|npm err'
|
|
141
|
+
|
|
142
|
+
record_status() { # label status detail
|
|
143
|
+
local label="$1" status="$2" detail="$3"
|
|
144
|
+
case "$status" in
|
|
145
|
+
PASS) STATUS_PASS=$((STATUS_PASS + 1)) ;;
|
|
146
|
+
FAIL) STATUS_FAIL=$((STATUS_FAIL + 1)) ;;
|
|
147
|
+
AMBIENTAL) STATUS_AMBIENTAL=$((STATUS_AMBIENTAL + 1)) ;;
|
|
148
|
+
NOT_TESTED) STATUS_NOT_TESTED=$((STATUS_NOT_TESTED + 1)) ;;
|
|
149
|
+
esac
|
|
150
|
+
printf '%s: %s — %s\n' "$status" "$label" "$detail"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
check_binary() { # label binary
|
|
154
|
+
local label="$1" bin="$2" out rc=0
|
|
155
|
+
if ! command -v "$bin" >/dev/null 2>&1; then
|
|
156
|
+
record_status "$label" "NOT_TESTED" "binary '$bin' is not installed in the sandbox prefix"
|
|
157
|
+
return 0
|
|
158
|
+
fi
|
|
159
|
+
set +e
|
|
160
|
+
out="$("$bin" --version 2>&1)"
|
|
161
|
+
rc=$?
|
|
162
|
+
set -e
|
|
163
|
+
if [ "$rc" -eq 0 ]; then
|
|
164
|
+
record_status "$label" "PASS" "${out:-version command exited 0}"
|
|
165
|
+
elif printf '%s' "$out" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
166
|
+
record_status "$label" "AMBIENTAL" "version check unavailable (exit $rc)"
|
|
167
|
+
else
|
|
168
|
+
record_status "$label" "FAIL" "version check exited $rc"
|
|
169
|
+
fi
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
check_mcp() { # label binary project
|
|
173
|
+
local label="$1" bin="$2" project="$3" out connected_count rc=0
|
|
174
|
+
if ! command -v "$bin" >/dev/null 2>&1; then
|
|
175
|
+
record_status "$label MCP" "NOT_TESTED" "binary '$bin' is not installed in the sandbox prefix"
|
|
176
|
+
return 0
|
|
177
|
+
fi
|
|
178
|
+
if [ ! -d "$project" ]; then
|
|
179
|
+
record_status "$label MCP" "NOT_TESTED" "project directory is missing: $project"
|
|
180
|
+
return 0
|
|
181
|
+
fi
|
|
182
|
+
set +e
|
|
183
|
+
out="$(cd "$project" && "$bin" mcp list 2>&1)"
|
|
184
|
+
rc=$?
|
|
185
|
+
set -e
|
|
186
|
+
out="$(printf '%s' "$out" | sed -E $'s/\x1B\\[[0-?]*[ -/]*[@-~]//g')"
|
|
187
|
+
printf '%s\n' "$out"
|
|
188
|
+
if [ "$rc" -ne 0 ]; then
|
|
189
|
+
if printf '%s' "$out" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
190
|
+
record_status "$label MCP" "AMBIENTAL" "mcp list environmental failure (exit $rc)"
|
|
191
|
+
else
|
|
192
|
+
record_status "$label MCP" "FAIL" "mcp list exited $rc"
|
|
193
|
+
fi
|
|
194
|
+
return 0
|
|
195
|
+
fi
|
|
196
|
+
connected_count="$(printf '%s' "$out" | grep -Eic '(^|[^[:alnum:]_])connected([^[:alnum:]_]|$)')" || connected_count=0
|
|
197
|
+
if [ "$connected_count" -eq 5 ] \
|
|
198
|
+
&& ! printf '%s' "$out" | grep -Eiq 'failed|✘' \
|
|
199
|
+
&& ! printf '%s' "$out" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
200
|
+
record_status "$label MCP" "PASS" "exactly 5 connected MCPs"
|
|
201
|
+
elif printf '%s' "$out" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
202
|
+
record_status "$label MCP" "AMBIENTAL" "environmental MCP status; found $connected_count connected"
|
|
203
|
+
else
|
|
204
|
+
record_status "$label MCP" "FAIL" "expected exactly 5 connected MCPs; found $connected_count or failures reported"
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
echo "--- Binaries ---"
|
|
209
|
+
check_binary "V1 binary" opencode
|
|
210
|
+
check_binary "V2 binary" opencode2
|
|
211
|
+
|
|
212
|
+
echo "--- Building tarball ---"
|
|
213
|
+
cd "$REPO_DIR"
|
|
214
|
+
npm pack 2>/dev/null || { echo "ERROR: npm pack failed"; exit 1; }
|
|
215
|
+
TGZ=$(ls -t pantheon-opencode-*.tgz | head -1)
|
|
216
|
+
npm rm -g pantheon-opencode 2>/dev/null || true
|
|
217
|
+
npm install -g "$TGZ"
|
|
218
|
+
if ! git -C "$REPO_DIR" ls-files --error-unmatch -- "$TGZ" >/dev/null 2>&1; then rm -f -- "$TGZ"; fi
|
|
219
|
+
|
|
220
|
+
echo "--- Init (headless) ---"
|
|
221
|
+
pantheon-opencode init --headless -y
|
|
222
|
+
|
|
223
|
+
echo "--- MCP Validation ---"
|
|
224
|
+
check_mcp "V1" opencode "$SANDBOX_DIR/project-v1"
|
|
225
|
+
check_mcp "V2" opencode2 "$SANDBOX_DIR/project-v2"
|
|
226
|
+
|
|
227
|
+
echo "--- Doctor ---"
|
|
228
|
+
if command -v pantheon-opencode >/dev/null 2>&1; then
|
|
229
|
+
set +e
|
|
230
|
+
DOCTOR_OUTPUT="$(pantheon-opencode doctor --target "$SANDBOX_DIR" 2>&1)"
|
|
231
|
+
DOCTOR_RC=$?
|
|
232
|
+
set -e
|
|
233
|
+
if [ "$DOCTOR_RC" -eq 0 ]; then
|
|
234
|
+
record_status "doctor" "PASS" "exit 0"
|
|
235
|
+
elif printf '%s' "$DOCTOR_OUTPUT" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
236
|
+
record_status "doctor" "AMBIENTAL" "environmental doctor failure (exit $DOCTOR_RC)"
|
|
237
|
+
else
|
|
238
|
+
record_status "doctor" "FAIL" "doctor exited $DOCTOR_RC"
|
|
239
|
+
fi
|
|
240
|
+
else
|
|
241
|
+
record_status "doctor" "NOT_TESTED" "pantheon-opencode is not installed in the sandbox prefix"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
echo "--- Gate (b): pantheon://agents CONTENT check ---"
|
|
245
|
+
RESOURCES_SRV="$PANTHEON_GLOBAL/scripts/mcp_resources_server.py"
|
|
246
|
+
if [ ! -f "$RESOURCES_SRV" ]; then
|
|
247
|
+
record_status "pantheon://agents resource" "NOT_TESTED" "resources server not found at $RESOURCES_SRV"
|
|
248
|
+
else
|
|
249
|
+
set +e
|
|
250
|
+
AGENTS_CONTENT="$("$SANDBOX_VENV/bin/python" - "$RESOURCES_SRV" <<'PYEOF'
|
|
251
|
+
import asyncio, importlib.util, sys
|
|
252
|
+
from pathlib import Path
|
|
253
|
+
resource_path = Path(sys.argv[1]).resolve()
|
|
254
|
+
sys.path.insert(0, str(resource_path.parent))
|
|
255
|
+
spec = importlib.util.spec_from_file_location("mrs", sys.argv[1])
|
|
256
|
+
mod = importlib.util.module_from_spec(spec)
|
|
257
|
+
spec.loader.exec_module(mod)
|
|
258
|
+
print(asyncio.run(mod.list_agents()))
|
|
259
|
+
PYEOF
|
|
260
|
+
)"
|
|
261
|
+
AGENTS_RC=$?
|
|
262
|
+
set -e
|
|
263
|
+
printf '%s\n' "$AGENTS_CONTENT" | head -8
|
|
264
|
+
if [ "$AGENTS_RC" -ne 0 ]; then
|
|
265
|
+
if printf '%s' "$AGENTS_CONTENT" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
266
|
+
record_status "pantheon://agents resource" "AMBIENTAL" "resource probe environmental failure (exit $AGENTS_RC)"
|
|
267
|
+
else
|
|
268
|
+
record_status "pantheon://agents resource" "FAIL" "resource probe exited $AGENTS_RC"
|
|
269
|
+
fi
|
|
270
|
+
elif printf '%s\n' "$AGENTS_CONTENT" | grep -qi 'zeus' \
|
|
271
|
+
&& printf '%s\n' "$AGENTS_CONTENT" | grep -qi 'hermes'; then
|
|
272
|
+
record_status "pantheon://agents resource" "PASS" "pantheon://agents content includes zeus and hermes"
|
|
273
|
+
else
|
|
274
|
+
record_status "pantheon://agents resource" "FAIL" "content missing zeus/hermes (connectivity alone is not enough)"
|
|
275
|
+
fi
|
|
276
|
+
fi
|
|
277
|
+
echo ""
|
|
278
|
+
echo "=== VALIDATION COMPLETE ==="
|
|
279
|
+
if [ "$STATUS_FAIL" -gt 0 ]; then
|
|
280
|
+
FINAL_VERDICT="FAIL"
|
|
281
|
+
elif [ "$STATUS_NOT_TESTED" -gt 0 ]; then
|
|
282
|
+
FINAL_VERDICT="NOT_TESTED"
|
|
283
|
+
elif [ "$STATUS_AMBIENTAL" -gt 0 ]; then
|
|
284
|
+
FINAL_VERDICT="AMBIENTAL"
|
|
285
|
+
else
|
|
286
|
+
FINAL_VERDICT="PASS"
|
|
287
|
+
fi
|
|
288
|
+
printf 'Final verdict: %s\n' "$FINAL_VERDICT"
|
|
289
|
+
if [ "$FINAL_VERDICT" = "FAIL" ]; then
|
|
290
|
+
exit 1
|
|
291
|
+
fi
|
|
292
|
+
echo "Next: open the isolated TUI with: $SANDBOX_DIR/start-pantheon.sh"
|
|
293
|
+
RUNTEST
|
|
294
|
+
chmod +x "$SANDBOX_ROOT/run-test.sh"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# ── Reset / Prepare ───────────────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
cmd_reset() {
|
|
300
|
+
case "$SANDBOX_ROOT" in
|
|
301
|
+
"/" | "$HOME" | "$REPO_DIR" | "$REPO_DIR"/*)
|
|
302
|
+
die "refusing to reset unsafe sandbox root: $SANDBOX_ROOT" ;;
|
|
303
|
+
esac
|
|
304
|
+
log "Resetting sandbox: $SANDBOX_ROOT"
|
|
305
|
+
rm -rf "$SANDBOX_ROOT"
|
|
306
|
+
log "Sandbox removed."
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
install_binaries() {
|
|
310
|
+
log "--- Installing pantheon-opencode (from repo tarball) ---"
|
|
311
|
+
cd "$REPO_DIR"
|
|
312
|
+
npm pack 2>/dev/null || die "npm pack failed"
|
|
313
|
+
local tgz
|
|
314
|
+
tgz=$(ls -t pantheon-opencode-*.tgz | head -1)
|
|
315
|
+
npm rm -g pantheon-opencode 2>/dev/null || true
|
|
316
|
+
npm install -g "$REPO_DIR/$tgz" || die "install of $tgz failed"
|
|
317
|
+
if ! git -C "$REPO_DIR" ls-files --error-unmatch -- "$tgz" >/dev/null 2>&1; then rm -f -- "$tgz"; fi
|
|
318
|
+
log "--- Installing OpenCode V1 ($OPENCODE_V1_SPEC) ---"
|
|
319
|
+
npm install -g "$OPENCODE_V1_SPEC" \
|
|
320
|
+
|| warn "V1 binary install failed ($OPENCODE_V1_SPEC) — v1 prompts will be AMBIENTAL"
|
|
321
|
+
log "--- Installing OpenCode V2 ($OPENCODE_V2_SPEC) ---"
|
|
322
|
+
npm install -g "$OPENCODE_V2_SPEC" \
|
|
323
|
+
|| warn "V2 binary install failed ($OPENCODE_V2_SPEC) — v2 prompts will be AMBIENTAL"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
prepare_project() {
|
|
327
|
+
local v="$1" dir pan
|
|
328
|
+
dir="$(project_for "$v")"
|
|
329
|
+
mkdir -p "$dir"
|
|
330
|
+
pan="$(sandbox_bin_for "pantheon-opencode")" \
|
|
331
|
+
|| die "pantheon-opencode not installed in sandbox prefix — run $0 --prepare first"
|
|
332
|
+
log "--- Regenerating config for $v in $dir ---"
|
|
333
|
+
(cd "$dir" && "$pan" init --project --version "$v" --model "$PANTHEON_SANDBOX_MODEL" -y) \
|
|
334
|
+
|| die "init --project --version $v failed in $dir"
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
cmd_prepare() {
|
|
338
|
+
mkdir -p "$SANDBOX_HOME" "$NPM_PREFIX"
|
|
339
|
+
write_run_test_sh
|
|
340
|
+
sandbox_env
|
|
341
|
+
install_binaries
|
|
342
|
+
local pan
|
|
343
|
+
pan="$(sandbox_bin_for "pantheon-opencode")" \
|
|
344
|
+
|| die "pantheon-opencode not found in sandbox prefix after install"
|
|
345
|
+
log "--- Headless init (global venv + runtime) ---"
|
|
346
|
+
"$pan" init --headless -y --model "$PANTHEON_SANDBOX_MODEL" \
|
|
347
|
+
|| die "pantheon-opencode init --headless failed"
|
|
348
|
+
prepare_project v1
|
|
349
|
+
prepare_project v2
|
|
350
|
+
write_extract_py
|
|
351
|
+
log "Prepare complete. Sandbox: $SANDBOX_ROOT"
|
|
352
|
+
log "Next: $0 --run v1 --prompts | $0 --run v2 --prompts"
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
# ── JSON text extraction (schema-agnostic) ────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
write_extract_py() {
|
|
358
|
+
cat > "$EXTRACT_PY" <<'PYEOF'
|
|
359
|
+
"""Collect every string value from opencode run --format json output."""
|
|
360
|
+
import json
|
|
361
|
+
import sys
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def walk(obj, out):
|
|
365
|
+
if isinstance(obj, str):
|
|
366
|
+
out.append(obj)
|
|
367
|
+
elif isinstance(obj, dict):
|
|
368
|
+
for value in obj.values():
|
|
369
|
+
walk(value, out)
|
|
370
|
+
elif isinstance(obj, list):
|
|
371
|
+
for value in obj:
|
|
372
|
+
walk(value, out)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
raw = sys.stdin.read()
|
|
376
|
+
chunks = []
|
|
377
|
+
try:
|
|
378
|
+
walk(json.loads(raw), chunks)
|
|
379
|
+
except Exception:
|
|
380
|
+
for line in raw.splitlines():
|
|
381
|
+
line = line.strip()
|
|
382
|
+
if not line:
|
|
383
|
+
continue
|
|
384
|
+
try:
|
|
385
|
+
walk(json.loads(line), chunks)
|
|
386
|
+
except Exception:
|
|
387
|
+
chunks.append(line)
|
|
388
|
+
print("\n".join(chunks))
|
|
389
|
+
PYEOF
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# ── Prompt battery ────────────────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
# Known environmental failure signatures (never fail the run).
|
|
395
|
+
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'
|
|
396
|
+
MCP_AMBIENTAL_RE='needs authentication|unauthorized|authentication failure|api key|credentials|401|docker|econnrefused|enotfound|etimedout|econnreset|connection refused|rate.?limit|429|quota|overloaded|edgesout|npm err'
|
|
397
|
+
|
|
398
|
+
PROMPT_IDS=()
|
|
399
|
+
PROMPT_TEXTS=()
|
|
400
|
+
PROMPT_MARKERS=()
|
|
401
|
+
PROMPT_AMB_MARKERS=()
|
|
402
|
+
PROMPT_VERIFY=()
|
|
403
|
+
|
|
404
|
+
add_prompt() { # id marker ambiental_marker verify text
|
|
405
|
+
PROMPT_IDS+=("$1")
|
|
406
|
+
PROMPT_MARKERS+=("$2")
|
|
407
|
+
PROMPT_AMB_MARKERS+=("$3")
|
|
408
|
+
PROMPT_VERIFY+=("$4")
|
|
409
|
+
PROMPT_TEXTS+=("$5")
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
build_battery() {
|
|
413
|
+
local v="$1"
|
|
414
|
+
PROMPT_IDS=()
|
|
415
|
+
PROMPT_TEXTS=()
|
|
416
|
+
PROMPT_MARKERS=()
|
|
417
|
+
PROMPT_AMB_MARKERS=()
|
|
418
|
+
PROMPT_VERIFY=()
|
|
419
|
+
add_prompt "agents-resource" "AGENTS-OK" "AGENTS-MISSING" "" \
|
|
420
|
+
"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."
|
|
421
|
+
add_prompt "memory-store" "MEMORY-STORE-OK" "" "" \
|
|
422
|
+
"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"
|
|
423
|
+
add_prompt "memory-recall" "MEMORY-RECALL-OK" "" "" \
|
|
424
|
+
"Use the pantheon-memory memory_recall tool (or memory_search) to look up key 'pantheon-sandbox-probe-$v' in namespace 'default'. If the stored value is 'alive', reply with exactly: MEMORY-RECALL-OK"
|
|
425
|
+
add_prompt "tmp-write-read" "TMP-OK" "" \
|
|
426
|
+
"${TMPDIR:-/tmp}/pantheon-sandbox-probe-$v.txt::TMP-OK" \
|
|
427
|
+
"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"
|
|
428
|
+
add_prompt "delegation" "DELEGATION-OK" "DELEGATION-UNAVAILABLE" "" \
|
|
429
|
+
"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"
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
declare -A RESULTS
|
|
433
|
+
declare -A DETAILS
|
|
434
|
+
|
|
435
|
+
record() { # v id result detail
|
|
436
|
+
RESULTS["$1:$2"]="$3"
|
|
437
|
+
DETAILS["$1:$2"]="$4"
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
extract_text() { "$HOME/.config/opencode/.venv/bin/python" "$EXTRACT_PY" < "$1"; }
|
|
441
|
+
|
|
442
|
+
ATTEMPT_RESULT=""
|
|
443
|
+
ATTEMPT_DETAIL=""
|
|
444
|
+
|
|
445
|
+
run_prompt_attempt() { # v idx current_bin — sets ATTEMPT_RESULT / ATTEMPT_DETAIL
|
|
446
|
+
local v="$1" idx="$2" bin="$3" id marker amb_marker verify text
|
|
447
|
+
id="${PROMPT_IDS[$idx]}"
|
|
448
|
+
marker="${PROMPT_MARKERS[$idx]}"
|
|
449
|
+
amb_marker="${PROMPT_AMB_MARKERS[$idx]}"
|
|
450
|
+
verify="${PROMPT_VERIFY[$idx]}"
|
|
451
|
+
text="${PROMPT_TEXTS[$idx]}"
|
|
452
|
+
|
|
453
|
+
# Remove the probe file first so post-run verification reflects THIS run.
|
|
454
|
+
if [ -n "$verify" ]; then
|
|
455
|
+
rm -f "${verify%%::*}"
|
|
456
|
+
fi
|
|
457
|
+
|
|
458
|
+
local out_file err_file rc=0
|
|
459
|
+
out_file="$(mktemp)"
|
|
460
|
+
err_file="$(mktemp)"
|
|
461
|
+
|
|
462
|
+
set +e
|
|
463
|
+
(cd "$(project_for "$v")" \
|
|
464
|
+
&& timeout "$PANTHEON_PROMPT_TIMEOUT" "$bin" run --auto --format json "$text") \
|
|
465
|
+
> "$out_file" 2> "$err_file"
|
|
466
|
+
rc=$?
|
|
467
|
+
set -e
|
|
468
|
+
|
|
469
|
+
local extracted
|
|
470
|
+
extracted="$(extract_text "$out_file" || true)"
|
|
471
|
+
local raw
|
|
472
|
+
raw="$( { tr '\n' ' ' < "$out_file"; tr '\n' ' ' < "$err_file"; } | cut -c1-400)"
|
|
473
|
+
rm -f "$out_file" "$err_file"
|
|
474
|
+
|
|
475
|
+
if [ "$rc" -eq 124 ]; then
|
|
476
|
+
ATTEMPT_RESULT="AMBIENTAL"
|
|
477
|
+
ATTEMPT_DETAIL="timeout after ${PANTHEON_PROMPT_TIMEOUT}s (provider latency)"
|
|
478
|
+
elif [ "$rc" -ne 0 ]; then
|
|
479
|
+
if [ -n "$amb_marker" ] && printf '%s' "$extracted" | grep -q "$amb_marker"; then
|
|
480
|
+
ATTEMPT_RESULT="AMBIENTAL"
|
|
481
|
+
ATTEMPT_DETAIL="explicit $amb_marker (exit $rc)"
|
|
482
|
+
elif printf '%s' "$raw" | grep -Eiq "$AMBIENTAL_RE"; then
|
|
483
|
+
ATTEMPT_RESULT="AMBIENTAL"
|
|
484
|
+
ATTEMPT_DETAIL="environmental signature with exit $rc"
|
|
485
|
+
else
|
|
486
|
+
ATTEMPT_RESULT="FAIL"
|
|
487
|
+
ATTEMPT_DETAIL="marker $marker absent; exit $rc; snippet: $(printf '%s' "$raw" | cut -c1-160)"
|
|
488
|
+
fi
|
|
489
|
+
elif printf '%s' "$extracted" | grep -q "$marker"; then
|
|
490
|
+
ATTEMPT_RESULT="PASS"
|
|
491
|
+
ATTEMPT_DETAIL="marker $marker found"
|
|
492
|
+
elif [ -n "$verify" ] \
|
|
493
|
+
&& [ "$(cat "${verify%%::*}" 2>/dev/null)" = "${verify##*::}" ]; then
|
|
494
|
+
ATTEMPT_RESULT="PASS"
|
|
495
|
+
ATTEMPT_DETAIL="probe file verified: ${verify%%::*} contains ${verify##*::}"
|
|
496
|
+
elif printf '%s' "$raw" | grep -Eiq "$AMBIENTAL_RE"; then
|
|
497
|
+
local sig
|
|
498
|
+
sig="$(printf '%s' "$raw" | grep -Eio "$AMBIENTAL_RE" | head -1)"
|
|
499
|
+
ATTEMPT_RESULT="AMBIENTAL"
|
|
500
|
+
ATTEMPT_DETAIL="environmental signature: $sig"
|
|
501
|
+
else
|
|
502
|
+
ATTEMPT_RESULT="FAIL"
|
|
503
|
+
ATTEMPT_DETAIL="marker $marker absent; rc=$rc; snippet: $(printf '%s' "$raw" | cut -c1-160)"
|
|
504
|
+
fi
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
RETRY_COOLDOWN="${PANTHEON_RETRY_COOLDOWN:-60}"
|
|
508
|
+
RETRIES_USED=0
|
|
509
|
+
|
|
510
|
+
run_prompt() { # v idx current_bin — ONE retry after cooldown for real FAIL only
|
|
511
|
+
local v="$1" idx="$2" bin="$3" id first_detail
|
|
512
|
+
id="${PROMPT_IDS[$idx]}"
|
|
513
|
+
run_prompt_attempt "$v" "$idx" "$bin"
|
|
514
|
+
if [ "$ATTEMPT_RESULT" = "FAIL" ]; then
|
|
515
|
+
first_detail="$ATTEMPT_DETAIL"
|
|
516
|
+
log "[$v] prompt $id FAIL on first attempt — retrying once after ${RETRY_COOLDOWN}s cooldown"
|
|
517
|
+
sleep "$RETRY_COOLDOWN"
|
|
518
|
+
run_prompt_attempt "$v" "$idx" "$bin"
|
|
519
|
+
RETRIES_USED=$((RETRIES_USED + 1))
|
|
520
|
+
if [ "$ATTEMPT_RESULT" != "FAIL" ]; then
|
|
521
|
+
ATTEMPT_DETAIL="$ATTEMPT_DETAIL [retry used — first attempt: $first_detail]"
|
|
522
|
+
else
|
|
523
|
+
ATTEMPT_DETAIL="$ATTEMPT_DETAIL [FAIL persisted after retry — first attempt: $first_detail]"
|
|
524
|
+
fi
|
|
525
|
+
fi
|
|
526
|
+
record "$v" "$id" "$ATTEMPT_RESULT" "$ATTEMPT_DETAIL"
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
check_mcp_list() { # v
|
|
530
|
+
local v="$1" bin out connected_count rc=0
|
|
531
|
+
if ! bin="$(sandbox_bin_v "$v")"; then
|
|
532
|
+
record "$v" "mcp-list" "AMBIENTAL" "binary not installed in sandbox prefix"
|
|
533
|
+
return 0
|
|
534
|
+
fi
|
|
535
|
+
set +e
|
|
536
|
+
out="$(cd "$(project_for "$v")" && "$bin" mcp list 2>&1)"
|
|
537
|
+
rc=$?
|
|
538
|
+
set -e
|
|
539
|
+
out="$(printf '%s' "$out" | sed -E $'s/\x1B\\[[0-?]*[ -/]*[@-~]//g')"
|
|
540
|
+
if [ "$rc" -ne 0 ]; then
|
|
541
|
+
if printf '%s' "$out" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
542
|
+
record "$v" "mcp-list" "AMBIENTAL" "mcp list environmental failure (exit $rc)"
|
|
543
|
+
else
|
|
544
|
+
record "$v" "mcp-list" "FAIL" "opencode mcp list exited $rc"
|
|
545
|
+
fi
|
|
546
|
+
return 0
|
|
547
|
+
fi
|
|
548
|
+
connected_count="$(printf '%s' "$out" | grep -Eic '(^|[^[:alnum:]_])connected([^[:alnum:]_]|$)')" || connected_count=0
|
|
549
|
+
if [ "$connected_count" -eq 5 ] \
|
|
550
|
+
&& ! printf '%s' "$out" | grep -Eiq "failed|✘" \
|
|
551
|
+
&& ! printf '%s' "$out" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
552
|
+
record "$v" "mcp-list" "PASS" "$connected_count connected (expected exactly 5)"
|
|
553
|
+
elif printf '%s' "$out" | grep -Eiq "$MCP_AMBIENTAL_RE"; then
|
|
554
|
+
record "$v" "mcp-list" "AMBIENTAL" "environmental MCP status; found $connected_count connected"
|
|
555
|
+
else
|
|
556
|
+
record "$v" "mcp-list" "FAIL" "expected exactly 5 connected MCPs; found $connected_count or failures reported"
|
|
557
|
+
fi
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
check_doctor() { # v
|
|
561
|
+
local v="$1" pan rc=0
|
|
562
|
+
if ! pan="$(sandbox_bin_for "pantheon-opencode")"; then
|
|
563
|
+
record "$v" "doctor" "AMBIENTAL" "pantheon-opencode not installed in sandbox prefix"
|
|
564
|
+
return 0
|
|
565
|
+
fi
|
|
566
|
+
set +e
|
|
567
|
+
(cd "$(project_for "$v")" && "$pan" doctor --target "$(project_for "$v")") > /dev/null 2>&1
|
|
568
|
+
rc=$?
|
|
569
|
+
set -e
|
|
570
|
+
if [ "$rc" -eq 0 ]; then
|
|
571
|
+
record "$v" "doctor" "PASS" "exit 0"
|
|
572
|
+
else
|
|
573
|
+
record "$v" "doctor" "FAIL" "doctor exited $rc (blocking errors)"
|
|
574
|
+
fi
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
installed_package_root() {
|
|
578
|
+
local pan resolved
|
|
579
|
+
if ! pan="$(sandbox_bin_for "pantheon-opencode")"; then
|
|
580
|
+
return 1
|
|
581
|
+
fi
|
|
582
|
+
resolved="$(readlink -f "$pan" 2>/dev/null || true)"
|
|
583
|
+
[ -n "$resolved" ] || return 1
|
|
584
|
+
dirname "$(dirname "$resolved")"
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
run_cost_probe() { # v
|
|
588
|
+
local v="$1" package_root probe raw status detail
|
|
589
|
+
if ! package_root="$(installed_package_root)"; then
|
|
590
|
+
record "$v" "pantheon-cost" "NOT_TESTED" "sandbox package is not installed"
|
|
591
|
+
return 0
|
|
592
|
+
fi
|
|
593
|
+
probe="$package_root/scripts/probe-pantheon-cost.mjs"
|
|
594
|
+
if [ ! -f "$probe" ]; then
|
|
595
|
+
record "$v" "pantheon-cost" "NOT_TESTED" "installed package has no offline cost probe"
|
|
596
|
+
return 0
|
|
597
|
+
fi
|
|
598
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
599
|
+
record "$v" "pantheon-cost" "AMBIENTAL" "node runtime is not available"
|
|
600
|
+
return 0
|
|
601
|
+
fi
|
|
602
|
+
set +e
|
|
603
|
+
raw="$(node "$probe" --version "$v" --fixture --json 2>&1)"
|
|
604
|
+
local rc=$?
|
|
605
|
+
set -e
|
|
606
|
+
case "$raw" in
|
|
607
|
+
*'"status":"PASS"'*) status="PASS" ;;
|
|
608
|
+
*'"status":"NOT_TESTED"'*) status="NOT_TESTED" ;;
|
|
609
|
+
*'"status":"AMBIENTAL"'*) status="AMBIENTAL" ;;
|
|
610
|
+
*) status="FAIL" ;;
|
|
611
|
+
esac
|
|
612
|
+
detail="$(printf '%s' "$raw" | tr '\n' ' ' | cut -c1-400)"
|
|
613
|
+
if [ "$rc" -ne 0 ] && [ "$status" = "PASS" ]; then
|
|
614
|
+
status="FAIL"
|
|
615
|
+
detail="$detail (probe exit $rc)"
|
|
616
|
+
elif [ "$rc" -ne 0 ] && [ "$status" != "FAIL" ]; then
|
|
617
|
+
detail="$detail (probe exit $rc)"
|
|
618
|
+
fi
|
|
619
|
+
record "$v" "pantheon-cost" "$status" "$detail"
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
run_rehydrate_probe() { # v
|
|
623
|
+
local v="$1" package_root probe raw status detail
|
|
624
|
+
if ! package_root="$(installed_package_root)"; then
|
|
625
|
+
record "$v" "context-rehydrate" "NOT_TESTED" "sandbox package is not installed"
|
|
626
|
+
return 0
|
|
627
|
+
fi
|
|
628
|
+
probe="$package_root/scripts/probe-context-rehydrate.mjs"
|
|
629
|
+
if [ ! -f "$probe" ]; then
|
|
630
|
+
record "$v" "context-rehydrate" "NOT_TESTED" "installed package has no offline context probe"
|
|
631
|
+
return 0
|
|
632
|
+
fi
|
|
633
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
634
|
+
record "$v" "context-rehydrate" "AMBIENTAL" "node runtime is not available"
|
|
635
|
+
return 0
|
|
636
|
+
fi
|
|
637
|
+
set +e
|
|
638
|
+
raw="$(timeout 30 node "$probe" --version "$v" --json 2>&1)"
|
|
639
|
+
local rc=$?
|
|
640
|
+
set -e
|
|
641
|
+
case "$raw" in
|
|
642
|
+
*'"status":"PASS"'*) status="PASS" ;;
|
|
643
|
+
*'"status":"NOT_TESTED"'*) status="NOT_TESTED" ;;
|
|
644
|
+
*'"status":"AMBIENTAL"'*) status="AMBIENTAL" ;;
|
|
645
|
+
*) status="FAIL" ;;
|
|
646
|
+
esac
|
|
647
|
+
detail="$(printf '%s' "$raw" | tr '\n' ' ' | cut -c1-400)"
|
|
648
|
+
if [ "$rc" -eq 124 ]; then
|
|
649
|
+
status="AMBIENTAL"
|
|
650
|
+
detail="context probe timed out after 30s"
|
|
651
|
+
elif [ "$rc" -ne 0 ] && [ "$status" = "PASS" ]; then
|
|
652
|
+
# A non-zero probe exit must never be reported as PASS.
|
|
653
|
+
status="FAIL"
|
|
654
|
+
detail="$detail (probe exit $rc)"
|
|
655
|
+
elif [ "$rc" -ne 0 ] && [ "$status" != "FAIL" ]; then
|
|
656
|
+
detail="$detail (probe exit $rc)"
|
|
657
|
+
fi
|
|
658
|
+
record "$v" "context-rehydrate" "$status" "$detail"
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
run_version_prompts() { # v
|
|
662
|
+
local v="$1" bin
|
|
663
|
+
if ! bin="$(sandbox_bin_v "$v")"; then
|
|
664
|
+
log "Binary '$(bin_for "$v")' not in sandbox prefix — marking all $v prompts AMBIENTAL."
|
|
665
|
+
build_battery "$v"
|
|
666
|
+
local i
|
|
667
|
+
for i in "${!PROMPT_IDS[@]}"; do
|
|
668
|
+
record "$v" "${PROMPT_IDS[$i]}" "AMBIENTAL" "binary $(bin_for "$v") not installed in sandbox"
|
|
669
|
+
done
|
|
670
|
+
record "$v" "mcp-list" "AMBIENTAL" "binary $(bin_for "$v") not installed in sandbox"
|
|
671
|
+
record "$v" "doctor" "AMBIENTAL" "binary $(bin_for "$v") not installed in sandbox"
|
|
672
|
+
return 0
|
|
673
|
+
fi
|
|
674
|
+
prepare_project "$v"
|
|
675
|
+
build_battery "$v"
|
|
676
|
+
local i
|
|
677
|
+
for i in "${!PROMPT_IDS[@]}"; do
|
|
678
|
+
log "[$v] prompt ${PROMPT_IDS[$i]} ..."
|
|
679
|
+
run_prompt "$v" "$i" "$bin"
|
|
680
|
+
log "[$v] prompt ${PROMPT_IDS[$i]} → ${RESULTS["$v:${PROMPT_IDS[$i]}"]}"
|
|
681
|
+
done
|
|
682
|
+
check_mcp_list "$v"
|
|
683
|
+
check_doctor "$v"
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
probe_verdict() { # fail pass ambiental not_tested → one final status
|
|
687
|
+
# Verdict: PASS; Verdict: FAIL; Verdict: AMBIENTAL; Verdict: NOT_TESTED remain distinct.
|
|
688
|
+
local fail="$1" pass="$2" ambiental="$3" not_tested="$4"
|
|
689
|
+
if [ "$fail" -gt 0 ]; then
|
|
690
|
+
printf 'FAIL'
|
|
691
|
+
elif [ "$not_tested" -gt 0 ]; then
|
|
692
|
+
printf 'NOT_TESTED'
|
|
693
|
+
elif [ "$ambiental" -gt 0 ]; then
|
|
694
|
+
printf 'AMBIENTAL'
|
|
695
|
+
else
|
|
696
|
+
printf 'PASS'
|
|
697
|
+
fi
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
write_report() { # versions...
|
|
701
|
+
local versions=("$@") total_fail=0 total_pass=0 total_amb=0 total_not_tested=0
|
|
702
|
+
{
|
|
703
|
+
printf '# Pantheon Sandbox Prompts Report\n\n'
|
|
704
|
+
printf -- '- **Date:** %s\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
|
|
705
|
+
printf -- '- **Model:** %s\n' "$PANTHEON_SANDBOX_MODEL"
|
|
706
|
+
printf -- '- **Sandbox:** %s\n' "$SANDBOX_ROOT"
|
|
707
|
+
printf -- '- **V1 binary:** %s (%s) | **V2 binary:** %s (%s)\n\n' \
|
|
708
|
+
"$V1_BIN" "$OPENCODE_V1_SPEC" "$V2_BIN" "$OPENCODE_V2_SPEC"
|
|
709
|
+
local v id res
|
|
710
|
+
for v in "${versions[@]}"; do
|
|
711
|
+
printf '## Version %s (`%s`)\n\n' "$v" "$(bin_for "$v")"
|
|
712
|
+
printf '| Prompt | Result | Detail |\n|---|---|---|\n'
|
|
713
|
+
for id in "${PROMPT_IDS[@]}" mcp-list doctor; do
|
|
714
|
+
res="${RESULTS["$v:$id"]:-SKIP}"
|
|
715
|
+
case "$res" in
|
|
716
|
+
PASS) total_pass=$((total_pass + 1)) ;;
|
|
717
|
+
AMBIENTAL) total_amb=$((total_amb + 1)) ;;
|
|
718
|
+
FAIL) total_fail=$((total_fail + 1)) ;;
|
|
719
|
+
NOT_TESTED) total_not_tested=$((total_not_tested + 1)) ;;
|
|
720
|
+
esac
|
|
721
|
+
printf '| %s | %s | %s |\n' "$id" "$res" "${DETAILS["$v:$id"]:-—}"
|
|
722
|
+
done
|
|
723
|
+
printf '\n'
|
|
724
|
+
done
|
|
725
|
+
printf '## Summary\n\n'
|
|
726
|
+
printf -- '- PASS: %d\n- FAIL (real): %d\n- AMBIENTAL: %d\n- NOT_TESTED: %d\n- Retries used: %d\n\n' \
|
|
727
|
+
"$total_pass" "$total_fail" "$total_amb" "$total_not_tested" "$RETRIES_USED"
|
|
728
|
+
printf '**Verdict: %s**\n' "$(probe_verdict "$total_fail" "$total_pass" "$total_amb" "$total_not_tested")"
|
|
729
|
+
} > "$REPORT_FILE"
|
|
730
|
+
log "Report written to $REPORT_FILE"
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
write_cost_report() { # versions...
|
|
734
|
+
local versions=("$@") total_fail=0 total_pass=0 total_ambient=0 total_not_tested=0
|
|
735
|
+
{
|
|
736
|
+
printf '# Pantheon Cost Probe Report\n\n'
|
|
737
|
+
printf -- '- **Date:** %s\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
|
|
738
|
+
printf -- '- **Sandbox:** %s\n\n' "$SANDBOX_ROOT"
|
|
739
|
+
printf '| Version | Result | Detail |\n|---|---|---|\n'
|
|
740
|
+
local v result
|
|
741
|
+
for v in "${versions[@]}"; do
|
|
742
|
+
result="${RESULTS["$v:pantheon-cost"]:-NOT_TESTED}"
|
|
743
|
+
case "$result" in
|
|
744
|
+
PASS) total_pass=$((total_pass + 1)) ;;
|
|
745
|
+
FAIL) total_fail=$((total_fail + 1)) ;;
|
|
746
|
+
AMBIENTAL) total_ambient=$((total_ambient + 1)) ;;
|
|
747
|
+
NOT_TESTED) total_not_tested=$((total_not_tested + 1)) ;;
|
|
748
|
+
esac
|
|
749
|
+
printf '| %s | %s | %s |\n' "$v" "$result" "${DETAILS["$v:pantheon-cost"]:-—}"
|
|
750
|
+
done
|
|
751
|
+
printf '\n## Summary\n\n'
|
|
752
|
+
printf -- '- PASS: %d\n- FAIL (real): %d\n- AMBIENTAL: %d\n- NOT_TESTED: %d\n\n' \
|
|
753
|
+
"$total_pass" "$total_fail" "$total_ambient" "$total_not_tested"
|
|
754
|
+
printf '**Verdict: %s**\n' "$(probe_verdict "$total_fail" "$total_pass" "$total_ambient" "$total_not_tested")"
|
|
755
|
+
} > "$COST_REPORT_FILE"
|
|
756
|
+
log "Cost probe report written to $COST_REPORT_FILE"
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
write_rehydrate_report() { # versions...
|
|
760
|
+
local versions=("$@") total_fail=0 total_pass=0 total_ambient=0 total_not_tested=0
|
|
761
|
+
{
|
|
762
|
+
printf '# Pantheon Context Rehydration Probe Report\n\n'
|
|
763
|
+
printf -- '- **Date:** %s\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
|
|
764
|
+
printf -- '- **Sandbox:** %s\n\n' "$SANDBOX_ROOT"
|
|
765
|
+
printf '| Version | Probe | Result | Detail |\n|---|---|---|---|\n'
|
|
766
|
+
local v result
|
|
767
|
+
for v in "${versions[@]}"; do
|
|
768
|
+
result="${RESULTS["$v:context-rehydrate"]:-NOT_TESTED}"
|
|
769
|
+
case "$result" in
|
|
770
|
+
PASS) total_pass=$((total_pass + 1)) ;;
|
|
771
|
+
FAIL) total_fail=$((total_fail + 1)) ;;
|
|
772
|
+
AMBIENTAL) total_ambient=$((total_ambient + 1)) ;;
|
|
773
|
+
NOT_TESTED) total_not_tested=$((total_not_tested + 1)) ;;
|
|
774
|
+
esac
|
|
775
|
+
printf '| %s | context_rehydrate + context_session_summary | %s | %s |\n' \
|
|
776
|
+
"$v" "$result" "${DETAILS["$v:context-rehydrate"]:-—}"
|
|
777
|
+
done
|
|
778
|
+
printf '\n## Summary\n\n'
|
|
779
|
+
printf -- '- PASS: %d\n- FAIL (real): %d\n- AMBIENTAL: %d\n- NOT_TESTED: %d\n\n' \
|
|
780
|
+
"$total_pass" "$total_fail" "$total_ambient" "$total_not_tested"
|
|
781
|
+
printf '**Verdict: %s**\n' "$(probe_verdict "$total_fail" "$total_pass" "$total_ambient" "$total_not_tested")"
|
|
782
|
+
} > "$REHYDRATE_REPORT_FILE"
|
|
783
|
+
log "Context probe report written to $REHYDRATE_REPORT_FILE"
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
run_cost() { # versions...
|
|
787
|
+
local versions=("$@") v fail=0
|
|
788
|
+
mkdir -p "$SANDBOX_ROOT"
|
|
789
|
+
for v in "${versions[@]}"; do
|
|
790
|
+
log "[$v] pantheon_cost offline probe ..."
|
|
791
|
+
run_cost_probe "$v"
|
|
792
|
+
log "[$v] pantheon-cost → ${RESULTS["$v:pantheon-cost"]}"
|
|
793
|
+
done
|
|
794
|
+
write_cost_report "${versions[@]}"
|
|
795
|
+
for v in "${versions[@]}"; do
|
|
796
|
+
[ "${RESULTS["$v:pantheon-cost"]:-}" = "FAIL" ] && fail=$((fail + 1)) || true
|
|
797
|
+
done
|
|
798
|
+
if [ "$fail" -gt 0 ]; then
|
|
799
|
+
log "Cost probe: $fail real FAIL(s)."
|
|
800
|
+
exit 1
|
|
801
|
+
fi
|
|
802
|
+
log "Cost probe: no real failures (NOT_TESTED/AMBIENTAL is non-blocking)."
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
run_rehydrate() { # versions...
|
|
806
|
+
local versions=("$@") v fail=0
|
|
807
|
+
mkdir -p "$SANDBOX_ROOT"
|
|
808
|
+
for v in "${versions[@]}"; do
|
|
809
|
+
log "[$v] context_rehydrate/session_summary offline probe ..."
|
|
810
|
+
run_rehydrate_probe "$v"
|
|
811
|
+
log "[$v] context probe → ${RESULTS["$v:context-rehydrate"]}"
|
|
812
|
+
done
|
|
813
|
+
write_rehydrate_report "${versions[@]}"
|
|
814
|
+
for v in "${versions[@]}"; do
|
|
815
|
+
[ "${RESULTS["$v:context-rehydrate"]:-}" = "FAIL" ] && fail=$((fail + 1)) || true
|
|
816
|
+
done
|
|
817
|
+
if [ "$fail" -gt 0 ]; then
|
|
818
|
+
log "Context probe: $fail real FAIL(s)."
|
|
819
|
+
exit 1
|
|
820
|
+
fi
|
|
821
|
+
log "Context probe: no real failures (NOT_TESTED/AMBIENTAL is non-blocking)."
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
run_battery() { # versions...
|
|
825
|
+
local versions=("$@")
|
|
826
|
+
mkdir -p "$SANDBOX_ROOT"
|
|
827
|
+
write_extract_py
|
|
828
|
+
local v
|
|
829
|
+
for v in "${versions[@]}"; do
|
|
830
|
+
run_version_prompts "$v"
|
|
831
|
+
done
|
|
832
|
+
write_report "${versions[@]}"
|
|
833
|
+
local v fail=0 id
|
|
834
|
+
for v in "${versions[@]}"; do
|
|
835
|
+
for id in "${PROMPT_IDS[@]}" mcp-list doctor; do
|
|
836
|
+
[ "${RESULTS["$v:$id"]:-}" = "FAIL" ] && fail=$((fail + 1)) || true
|
|
837
|
+
done
|
|
838
|
+
done
|
|
839
|
+
if [ "$fail" -gt 0 ]; then
|
|
840
|
+
log "Prompts battery: $fail real FAIL(s)."
|
|
841
|
+
exit 1
|
|
842
|
+
fi
|
|
843
|
+
log "Prompts battery: no real failures."
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
# ── Base validation (no prompts) ──────────────────────────────────────────────
|
|
847
|
+
|
|
848
|
+
run_base() { # v
|
|
849
|
+
local v="$1" bin
|
|
850
|
+
if ! bin="$(sandbox_bin_v "$v")"; then
|
|
851
|
+
printf 'ERROR: sandbox not prepared for %s (binary '\''%s'\'' missing from sandbox prefix) — run %s --prepare first\n' \
|
|
852
|
+
"$v" "$(bin_for "$v")" "$0" >&2
|
|
853
|
+
exit 3
|
|
854
|
+
fi
|
|
855
|
+
prepare_project "$v"
|
|
856
|
+
build_battery "$v"
|
|
857
|
+
log "[$v] opencode mcp list"
|
|
858
|
+
check_mcp_list "$v"
|
|
859
|
+
log "[$v] mcp-list → ${RESULTS["$v:mcp-list"]}"
|
|
860
|
+
log "[$v] doctor"
|
|
861
|
+
check_doctor "$v"
|
|
862
|
+
log "[$v] doctor → ${RESULTS["$v:doctor"]}"
|
|
863
|
+
local fail=0
|
|
864
|
+
[ "${RESULTS["$v:mcp-list"]}" = "FAIL" ] && fail=$((fail + 1)) || true
|
|
865
|
+
[ "${RESULTS["$v:doctor"]}" = "FAIL" ] && fail=$((fail + 1)) || true
|
|
866
|
+
if [ "$fail" -gt 0 ]; then
|
|
867
|
+
log "Base validation $v: $fail real FAIL(s)."
|
|
868
|
+
exit 1
|
|
869
|
+
fi
|
|
870
|
+
log "Base validation $v: OK."
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
# ── CLI ───────────────────────────────────────────────────────────────────────
|
|
874
|
+
|
|
875
|
+
while [ $# -gt 0 ]; do
|
|
876
|
+
case "$1" in
|
|
877
|
+
--prepare) MODE_PREPARE=1 ;;
|
|
878
|
+
--reset) MODE_RESET=1 ;;
|
|
879
|
+
--prompts) MODE_PROMPTS=1 ;;
|
|
880
|
+
--cost) MODE_COST=1 ;;
|
|
881
|
+
--rehydrate) MODE_REHYDRATE=1 ;;
|
|
882
|
+
--run)
|
|
883
|
+
shift
|
|
884
|
+
case "${1:-}" in
|
|
885
|
+
v1 | v2) RUN_VERSION="$1" ;;
|
|
886
|
+
*) usage ;;
|
|
887
|
+
esac
|
|
888
|
+
;;
|
|
889
|
+
--help | -h) usage ;;
|
|
890
|
+
*) usage ;;
|
|
891
|
+
esac
|
|
892
|
+
shift
|
|
893
|
+
done
|
|
894
|
+
|
|
895
|
+
if [ "$MODE_RESET" -eq 1 ]; then
|
|
896
|
+
cmd_reset
|
|
897
|
+
exit 0
|
|
898
|
+
fi
|
|
899
|
+
|
|
900
|
+
if [ "$MODE_PREPARE" -eq 0 ] && [ -z "$RUN_VERSION" ] \
|
|
901
|
+
&& [ "$MODE_PROMPTS" -eq 0 ] && [ "$MODE_COST" -eq 0 ] \
|
|
902
|
+
&& [ "$MODE_REHYDRATE" -eq 0 ]; then
|
|
903
|
+
usage
|
|
904
|
+
fi
|
|
905
|
+
|
|
906
|
+
if [ "$MODE_PREPARE" -eq 1 ]; then
|
|
907
|
+
cmd_prepare
|
|
908
|
+
fi
|
|
909
|
+
|
|
910
|
+
if [ -n "$RUN_VERSION" ]; then
|
|
911
|
+
if [ "$MODE_PROMPTS" -eq 1 ]; then
|
|
912
|
+
sandbox_env
|
|
913
|
+
run_battery "$RUN_VERSION"
|
|
914
|
+
else
|
|
915
|
+
sandbox_env
|
|
916
|
+
run_base "$RUN_VERSION"
|
|
917
|
+
fi
|
|
918
|
+
if [ "$MODE_COST" -eq 1 ]; then
|
|
919
|
+
sandbox_env
|
|
920
|
+
run_cost "$RUN_VERSION"
|
|
921
|
+
fi
|
|
922
|
+
if [ "$MODE_REHYDRATE" -eq 1 ]; then
|
|
923
|
+
sandbox_env
|
|
924
|
+
run_rehydrate "$RUN_VERSION"
|
|
925
|
+
fi
|
|
926
|
+
elif [ "$MODE_PROMPTS" -eq 1 ]; then
|
|
927
|
+
sandbox_env
|
|
928
|
+
run_battery v1 v2
|
|
929
|
+
if [ "$MODE_COST" -eq 1 ]; then
|
|
930
|
+
run_cost v1 v2
|
|
931
|
+
fi
|
|
932
|
+
if [ "$MODE_REHYDRATE" -eq 1 ]; then
|
|
933
|
+
run_rehydrate v1 v2
|
|
934
|
+
fi
|
|
935
|
+
elif [ "$MODE_COST" -eq 1 ]; then
|
|
936
|
+
sandbox_env
|
|
937
|
+
run_cost v1 v2
|
|
938
|
+
if [ "$MODE_REHYDRATE" -eq 1 ]; then
|
|
939
|
+
run_rehydrate v1 v2
|
|
940
|
+
fi
|
|
941
|
+
elif [ "$MODE_REHYDRATE" -eq 1 ]; then
|
|
942
|
+
sandbox_env
|
|
943
|
+
run_rehydrate v1 v2
|
|
944
|
+
fi
|