oh-my-opencode 4.9.0 → 4.9.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/.agents/skills/opencode-qa/SKILL.md +1 -0
- package/.agents/skills/opencode-qa/scripts/lib/fake-openai-branches.mjs +39 -0
- package/.agents/skills/opencode-qa/scripts/lib/fake-openai-events.mjs +106 -0
- package/.agents/skills/opencode-qa/scripts/lib/fake-openai-server.mjs +117 -0
- package/.agents/skills/opencode-qa/scripts/serve-wake-split-probe.sh +716 -0
- package/dist/cli/doctor/checks/dependencies.d.ts +2 -2
- package/dist/cli/index.js +31742 -31476
- package/dist/cli-node/index.js +31742 -31476
- package/dist/config/schema/experimental.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
- package/dist/index.js +3067 -1344
- package/dist/oh-my-opencode.schema.json +3 -0
- package/dist/shared/internal-initiator-marker.d.ts +7 -0
- package/dist/shared/live-server-route.d.ts +24 -0
- package/dist/shared/module-resolution-failure.d.ts +7 -0
- package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +1 -0
- package/dist/testing/create-plugin-module.d.ts +4 -0
- package/package.json +12 -12
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/hooks.json +16 -16
- package/packages/omo-codex/plugin/package.json +1 -1
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# serve-wake-split-probe.sh
|
|
3
|
+
# Serve-topology wake runner-split QA harness.
|
|
4
|
+
#
|
|
5
|
+
# Proves whether omo's plugin-origin promptAsync (parent-wake bg notifications)
|
|
6
|
+
# forks a second concurrent LLM runner in opencode serve topology (REPRODUCED)
|
|
7
|
+
# or routes correctly through the live listener (FIXED).
|
|
8
|
+
#
|
|
9
|
+
# Two assertion modes:
|
|
10
|
+
# --expect reproduced exit 0 if stops>1 OR children>1 OR mechanism arm true
|
|
11
|
+
# --expect fixed exit 0 if children==1 AND stops==1
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# serve-wake-split-probe.sh [--expect reproduced|fixed] [--evidence-dir DIR]
|
|
15
|
+
# [--self-test] [--help]
|
|
16
|
+
#
|
|
17
|
+
# Env:
|
|
18
|
+
# OMO_SANDBOX_OMO_CONFIG JSON string; when set, deep-merged over the base
|
|
19
|
+
# agent overrides (env keys win) and written to
|
|
20
|
+
# $XDG_CONFIG_HOME/opencode/oh-my-openagent.json
|
|
21
|
+
# before the server starts (flag-disabled control).
|
|
22
|
+
# FAKE_OPENAI_PORT Force the fake-LLM to bind a specific port
|
|
23
|
+
# (default: random). Port 1 triggers a startup
|
|
24
|
+
# failure test used by the self-test failure path.
|
|
25
|
+
#
|
|
26
|
+
# Exit codes:
|
|
27
|
+
# 0 expectation met (or --self-test OK)
|
|
28
|
+
# 1 expectation NOT met, or internal harness error
|
|
29
|
+
# 2 usage / bad arguments
|
|
30
|
+
|
|
31
|
+
set -uo pipefail
|
|
32
|
+
|
|
33
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
|
+
. "$SCRIPT_DIR/lib/common.sh"
|
|
35
|
+
|
|
36
|
+
# ---- defaults ----------------------------------------------------------------
|
|
37
|
+
EXPECT_MODE="" # reproduced | fixed
|
|
38
|
+
EVIDENCE_DIR=""
|
|
39
|
+
SELF_TEST=0
|
|
40
|
+
FAKE_SERVER_PID=""
|
|
41
|
+
FAKE_SERVER_PORT=""
|
|
42
|
+
FAKE_LLM_LOG="" # set after evidence dir is known
|
|
43
|
+
|
|
44
|
+
# ---- argument parsing --------------------------------------------------------
|
|
45
|
+
while [ $# -gt 0 ]; do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--expect)
|
|
48
|
+
EXPECT_MODE="$2"
|
|
49
|
+
shift 2
|
|
50
|
+
;;
|
|
51
|
+
--evidence-dir)
|
|
52
|
+
EVIDENCE_DIR="$2"
|
|
53
|
+
shift 2
|
|
54
|
+
;;
|
|
55
|
+
--self-test)
|
|
56
|
+
SELF_TEST=1
|
|
57
|
+
shift
|
|
58
|
+
;;
|
|
59
|
+
-h|--help)
|
|
60
|
+
sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
61
|
+
exit 0
|
|
62
|
+
;;
|
|
63
|
+
*)
|
|
64
|
+
printf 'unknown option: %s\n' "$1" >&2
|
|
65
|
+
exit 2
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
if [ "$SELF_TEST" -eq 0 ] && [ -z "$EXPECT_MODE" ]; then
|
|
71
|
+
EXPECT_MODE="reproduced"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if [ -n "$EXPECT_MODE" ] && [ "$EXPECT_MODE" != "reproduced" ] && [ "$EXPECT_MODE" != "fixed" ]; then
|
|
75
|
+
printf 'error: --expect must be reproduced or fixed\n' >&2
|
|
76
|
+
exit 2
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# ---- helpers -----------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
swsp_log() { printf '%s\n' "$*" >&2; }
|
|
82
|
+
swsp_info() { printf '[swsp] %s\n' "$*" >&2; }
|
|
83
|
+
|
|
84
|
+
# Start the fake-LLM server; sets FAKE_SERVER_PID + FAKE_SERVER_PORT.
|
|
85
|
+
swsp_start_fake_llm() {
|
|
86
|
+
local log_file="$1"
|
|
87
|
+
local port_file
|
|
88
|
+
port_file="$(mktemp -t swsp-port.XXXXXX)"
|
|
89
|
+
OQA_TMPDIRS+=("$port_file")
|
|
90
|
+
|
|
91
|
+
FAKE_LLM_LOG="$log_file" FAKE_OPENAI_PORT="${FAKE_OPENAI_PORT:-0}" \
|
|
92
|
+
bun run --bun "$SCRIPT_DIR/lib/fake-openai-server.mjs" >"$port_file.stdout" 2>&1 &
|
|
93
|
+
FAKE_SERVER_PID=$!
|
|
94
|
+
disown "$FAKE_SERVER_PID" 2>/dev/null || true
|
|
95
|
+
|
|
96
|
+
# Poll for the port line (fake-openai listening on <port>)
|
|
97
|
+
local deadline
|
|
98
|
+
deadline=$(( $(date +%s) + 10 ))
|
|
99
|
+
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
100
|
+
if grep -q "^fake-openai listening on " "$port_file.stdout" 2>/dev/null; then
|
|
101
|
+
FAKE_SERVER_PORT="$(grep "^fake-openai listening on " "$port_file.stdout" | head -1 | awk '{print $NF}')"
|
|
102
|
+
break
|
|
103
|
+
fi
|
|
104
|
+
if ! kill -0 "$FAKE_SERVER_PID" 2>/dev/null; then
|
|
105
|
+
swsp_log "FAIL: fake-openai server process died immediately"
|
|
106
|
+
cat "$port_file.stdout" >&2 2>/dev/null || true
|
|
107
|
+
return 1
|
|
108
|
+
fi
|
|
109
|
+
sleep 0.3
|
|
110
|
+
done
|
|
111
|
+
OQA_TMPDIRS+=("$port_file.stdout")
|
|
112
|
+
|
|
113
|
+
if [ -z "$FAKE_SERVER_PORT" ]; then
|
|
114
|
+
swsp_log "FAIL: fake-openai server did not report port within 10s"
|
|
115
|
+
cat "$port_file.stdout" >&2 2>/dev/null || true
|
|
116
|
+
kill "$FAKE_SERVER_PID" 2>/dev/null || true
|
|
117
|
+
return 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Verify it's up
|
|
121
|
+
local hdeadline=0
|
|
122
|
+
hdeadline=$(( $(date +%s) + 5 ))
|
|
123
|
+
while [ "$(date +%s)" -lt "$hdeadline" ]; do
|
|
124
|
+
if curl -sf "http://127.0.0.1:${FAKE_SERVER_PORT}/health" >/dev/null 2>&1; then
|
|
125
|
+
swsp_info "fake-openai listening on port $FAKE_SERVER_PORT"
|
|
126
|
+
return 0
|
|
127
|
+
fi
|
|
128
|
+
sleep 0.2
|
|
129
|
+
done
|
|
130
|
+
swsp_log "FAIL: fake-openai /health did not respond within 5s on port $FAKE_SERVER_PORT"
|
|
131
|
+
kill "$FAKE_SERVER_PID" 2>/dev/null || true
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
swsp_stop_fake_llm() {
|
|
136
|
+
if [ -n "$FAKE_SERVER_PID" ]; then
|
|
137
|
+
kill "$FAKE_SERVER_PID" 2>/dev/null || true
|
|
138
|
+
sleep 0.3
|
|
139
|
+
kill -0 "$FAKE_SERVER_PID" 2>/dev/null && kill -9 "$FAKE_SERVER_PID" 2>/dev/null || true
|
|
140
|
+
FAKE_SERVER_PID=""
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Write the sandbox omo config: base agent overrides (explore/librarian -> the
|
|
145
|
+
# fake provider, required for child model resolution) deep-merged with
|
|
146
|
+
# OMO_SANDBOX_OMO_CONFIG when set (jq '.[0] * .[1]'; env keys win).
|
|
147
|
+
# Args: sandbox_config_dir
|
|
148
|
+
swsp_write_omo_config() {
|
|
149
|
+
local cfg_dir="$1"
|
|
150
|
+
local omo_cfg="$cfg_dir/opencode/oh-my-openagent.json"
|
|
151
|
+
local base='{"agents":{"explore":{"model":"openai/gpt-fake"},"librarian":{"model":"openai/gpt-fake"}}}'
|
|
152
|
+
|
|
153
|
+
mkdir -p "$cfg_dir/opencode"
|
|
154
|
+
if [ -n "${OMO_SANDBOX_OMO_CONFIG:-}" ]; then
|
|
155
|
+
if ! printf '%s\n%s\n' "$base" "$OMO_SANDBOX_OMO_CONFIG" | jq -s '.[0] * .[1]' >"$omo_cfg" 2>/dev/null; then
|
|
156
|
+
swsp_log "FAIL: OMO_SANDBOX_OMO_CONFIG is not valid JSON"
|
|
157
|
+
return 1
|
|
158
|
+
fi
|
|
159
|
+
swsp_info "wrote merged OMO_SANDBOX_OMO_CONFIG to $omo_cfg"
|
|
160
|
+
else
|
|
161
|
+
printf '%s\n' "$base" >"$omo_cfg"
|
|
162
|
+
swsp_info "wrote agent overrides to $omo_cfg"
|
|
163
|
+
fi
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Write the sandbox opencode.jsonc with the fake provider + local plugin.
|
|
167
|
+
# Args: sandbox_config_dir fake_port
|
|
168
|
+
swsp_write_opencode_config() {
|
|
169
|
+
local cfg_dir="$1"
|
|
170
|
+
local fake_port="$2"
|
|
171
|
+
local repo_root
|
|
172
|
+
repo_root="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
|
173
|
+
|
|
174
|
+
mkdir -p "$cfg_dir/opencode"
|
|
175
|
+
cat >"$cfg_dir/opencode/opencode.jsonc" <<JSONC
|
|
176
|
+
{
|
|
177
|
+
"plugin": ["file://${repo_root}/packages/omo-opencode/src/index.ts"],
|
|
178
|
+
"model": "openai/gpt-fake",
|
|
179
|
+
"provider": {
|
|
180
|
+
"openai": {
|
|
181
|
+
"options": {
|
|
182
|
+
"apiKey": "fake-key",
|
|
183
|
+
"baseURL": "http://127.0.0.1:${fake_port}/v1",
|
|
184
|
+
"timeout": 30000
|
|
185
|
+
},
|
|
186
|
+
"models": {
|
|
187
|
+
"gpt-fake": {
|
|
188
|
+
"tool_call": true,
|
|
189
|
+
"limit": {
|
|
190
|
+
"context": 200000,
|
|
191
|
+
"output": 8192
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"permission": {
|
|
198
|
+
"bash": "allow",
|
|
199
|
+
"call_omo_agent": "allow"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
JSONC
|
|
203
|
+
swsp_info "opencode.jsonc written to $cfg_dir/opencode/opencode.jsonc"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# Poll the sandbox DB for children/stops on a message matching a LIKE pattern.
|
|
207
|
+
# Args: db_path like_pattern timeout_s
|
|
208
|
+
# Outputs: "<children> <stops>" on stdout
|
|
209
|
+
swsp_poll_db_metrics() {
|
|
210
|
+
local db="$1"
|
|
211
|
+
local like_pat="$2"
|
|
212
|
+
local timeout_s="${3:-90}"
|
|
213
|
+
local deadline
|
|
214
|
+
local metrics_query="
|
|
215
|
+
WITH target AS (
|
|
216
|
+
SELECT m.id AS user_id, m.session_id
|
|
217
|
+
FROM message m
|
|
218
|
+
JOIN part p ON p.message_id = m.id
|
|
219
|
+
WHERE json_extract(m.data, '\$.role') = 'user'
|
|
220
|
+
AND json_extract(p.data, '\$.type') = 'text'
|
|
221
|
+
AND json_extract(p.data, '\$.text') LIKE '${like_pat}'
|
|
222
|
+
),
|
|
223
|
+
counts AS (
|
|
224
|
+
SELECT
|
|
225
|
+
count(a.id) AS children,
|
|
226
|
+
sum(CASE WHEN json_extract(a.data, '\$.finish') = 'stop' THEN 1 ELSE 0 END) AS stops
|
|
227
|
+
FROM target t
|
|
228
|
+
LEFT JOIN message a
|
|
229
|
+
ON a.session_id = t.session_id
|
|
230
|
+
AND json_extract(a.data, '\$.parentID') = t.user_id
|
|
231
|
+
GROUP BY t.user_id
|
|
232
|
+
)
|
|
233
|
+
SELECT printf('%d %d',
|
|
234
|
+
coalesce((SELECT max(children) FROM counts), 0),
|
|
235
|
+
coalesce((SELECT max(stops) FROM counts), 0)
|
|
236
|
+
);
|
|
237
|
+
"
|
|
238
|
+
|
|
239
|
+
deadline=$(( $(date +%s) + timeout_s ))
|
|
240
|
+
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
241
|
+
if [ ! -f "$db" ]; then
|
|
242
|
+
sleep 0.5
|
|
243
|
+
continue
|
|
244
|
+
fi
|
|
245
|
+
local result
|
|
246
|
+
result="$(sqlite3 "$db" "$metrics_query" 2>/dev/null)" || true
|
|
247
|
+
|
|
248
|
+
local children stops
|
|
249
|
+
children="$(printf '%s' "$result" | awk '{print $1}')"
|
|
250
|
+
stops="$(printf '%s' "$result" | awk '{print $2}')"
|
|
251
|
+
|
|
252
|
+
# Return once we have at least 1 stop (parent session finished)
|
|
253
|
+
if [ -n "$stops" ] && [ "${stops:-0}" -ge 1 ] 2>/dev/null; then
|
|
254
|
+
printf '%s %s' "$children" "$stops"
|
|
255
|
+
return 0
|
|
256
|
+
fi
|
|
257
|
+
sleep 0.5
|
|
258
|
+
done
|
|
259
|
+
|
|
260
|
+
# Return whatever we have on timeout
|
|
261
|
+
local result
|
|
262
|
+
result="$(sqlite3 "$db" "$metrics_query" 2>/dev/null)" || true
|
|
263
|
+
printf '%s' "${result:-0 0}"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Wait until a session is no longer in the server's active status map.
|
|
267
|
+
# Args: server_url pass session_id timeout_s
|
|
268
|
+
swsp_wait_session_idle() {
|
|
269
|
+
local url="$1" pass="$2" ses_id="$3" timeout_s="${4:-120}"
|
|
270
|
+
local deadline
|
|
271
|
+
deadline=$(( $(date +%s) + timeout_s ))
|
|
272
|
+
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
273
|
+
local status_json
|
|
274
|
+
status_json="$(curl -sf -u "opencode:${pass}" "${url}/session/status" 2>/dev/null)" || true
|
|
275
|
+
if [ -z "$status_json" ] || ! printf '%s' "$status_json" | grep -q "$ses_id" 2>/dev/null; then
|
|
276
|
+
return 0
|
|
277
|
+
fi
|
|
278
|
+
sleep 0.5
|
|
279
|
+
done
|
|
280
|
+
swsp_log "WARNING: session $ses_id did not go idle within ${timeout_s}s; proceeding with current DB state"
|
|
281
|
+
return 0
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Count plugin_inits from the omo log since a byte offset, filtered to sandbox dir.
|
|
285
|
+
# Args: log_offset sandbox_dir
|
|
286
|
+
swsp_count_plugin_inits() {
|
|
287
|
+
local offset="$1"
|
|
288
|
+
local sandbox_dir="$2"
|
|
289
|
+
local log_path="${TMPDIR:-/tmp}/oh-my-opencode.log"
|
|
290
|
+
if [ ! -f "$log_path" ]; then
|
|
291
|
+
printf '0'
|
|
292
|
+
return 0
|
|
293
|
+
fi
|
|
294
|
+
# tail from byte offset
|
|
295
|
+
tail -c "+$((offset + 1))" "$log_path" 2>/dev/null \
|
|
296
|
+
| grep "ENTRY - plugin loading" \
|
|
297
|
+
| grep -c "$sandbox_dir" 2>/dev/null \
|
|
298
|
+
|| printf '0'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# Detect WAKE_DISPATCHED_DURING_PARENT_TURN:
|
|
302
|
+
# true iff the omo log (since offset) contains a [prompt-async-gate] promptAsync dispatching
|
|
303
|
+
# line with source containing "parent-wake", AND that line's timestamp is within the
|
|
304
|
+
# parent-hold window (between branch=parent-hold line and next non-wake completion).
|
|
305
|
+
# Args: log_offset fake_llm_log sandbox_dir
|
|
306
|
+
swsp_detect_wake_during_parent() {
|
|
307
|
+
local offset="$1"
|
|
308
|
+
local fake_log="$2"
|
|
309
|
+
local sandbox_dir="$3"
|
|
310
|
+
local omo_log="${TMPDIR:-/tmp}/oh-my-opencode.log"
|
|
311
|
+
|
|
312
|
+
# Find parent-hold timestamp from fake-llm.log
|
|
313
|
+
local hold_ts
|
|
314
|
+
hold_ts="$(grep "branch=parent-hold" "$fake_log" 2>/dev/null | head -1 | grep -o '\[.*\]' | tr -d '[]')" || true
|
|
315
|
+
|
|
316
|
+
if [ -z "$hold_ts" ]; then
|
|
317
|
+
# parent-hold never fired — cannot determine
|
|
318
|
+
printf 'false'
|
|
319
|
+
return 0
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# Check for gate dispatch log line with parent-wake source since offset
|
|
323
|
+
local dispatch_line
|
|
324
|
+
dispatch_line="$(tail -c "+$((offset + 1))" "$omo_log" 2>/dev/null \
|
|
325
|
+
| grep "promptAsync dispatching" \
|
|
326
|
+
| grep -i "parent-wake\|background-agent-parent-wake" \
|
|
327
|
+
| head -1)" || true
|
|
328
|
+
|
|
329
|
+
if [ -z "$dispatch_line" ]; then
|
|
330
|
+
printf 'false'
|
|
331
|
+
return 0
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# Extract timestamp from dispatch line (ISO 8601 in brackets or as prefix)
|
|
335
|
+
local dispatch_ts
|
|
336
|
+
dispatch_ts="$(printf '%s' "$dispatch_line" | grep -o '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]' | head -1)" || true
|
|
337
|
+
|
|
338
|
+
if [ -z "$dispatch_ts" ]; then
|
|
339
|
+
# Can't compare timestamps — fall back to presence check
|
|
340
|
+
printf 'true'
|
|
341
|
+
return 0
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
# Simple lexicographic timestamp compare (ISO 8601 sorts correctly)
|
|
345
|
+
# The hold_ts is the start of the hold window; if dispatch happened after it, signal is true
|
|
346
|
+
if [ "$dispatch_ts" \> "$hold_ts" ] || [ "$dispatch_ts" = "$hold_ts" ]; then
|
|
347
|
+
printf 'true'
|
|
348
|
+
else
|
|
349
|
+
printf 'false'
|
|
350
|
+
fi
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
# Verify branch-count guard: all required branches fired.
|
|
354
|
+
# Returns 0 if OK, 1 if any required branch missing (also sets RESULT=HARNESS_ERROR).
|
|
355
|
+
swsp_check_branch_counts() {
|
|
356
|
+
local fake_log="$1"
|
|
357
|
+
local ptc pc cc wc
|
|
358
|
+
ptc="$(grep -c "branch=parent-tool-call" "$fake_log" 2>/dev/null | tr -d '[:space:]')"; ptc="${ptc:-0}"
|
|
359
|
+
pc="$(grep -c "branch=parent-hold" "$fake_log" 2>/dev/null | tr -d '[:space:]')"; pc="${pc:-0}"
|
|
360
|
+
cc="$(grep -c "branch=child" "$fake_log" 2>/dev/null | tr -d '[:space:]')"; cc="${cc:-0}"
|
|
361
|
+
wc="$(grep -c "branch=wake" "$fake_log" 2>/dev/null | tr -d '[:space:]')"; wc="${wc:-0}"
|
|
362
|
+
ptc="${ptc%%[!0-9]*}"; pc="${pc%%[!0-9]*}"; cc="${cc%%[!0-9]*}"; wc="${wc%%[!0-9]*}"
|
|
363
|
+
ptc="${ptc:-0}"; pc="${pc:-0}"; cc="${cc:-0}"; wc="${wc:-0}"
|
|
364
|
+
|
|
365
|
+
swsp_info "branch counts: parent-tool-call=$ptc parent-hold=$pc child=$cc wake=$wc"
|
|
366
|
+
|
|
367
|
+
if [ "$ptc" -lt 1 ] || [ "$pc" -lt 1 ] || [ "$cc" -lt 1 ] || [ "$wc" -lt 1 ]; then
|
|
368
|
+
printf 'RESULT=HARNESS_ERROR branch_counts parent-tool-call=%s parent-hold=%s child=%s wake=%s\n' \
|
|
369
|
+
"$ptc" "$pc" "$cc" "$wc"
|
|
370
|
+
return 1
|
|
371
|
+
fi
|
|
372
|
+
return 0
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
# ---- self-test ---------------------------------------------------------------
|
|
376
|
+
swsp_self_test() {
|
|
377
|
+
swsp_info "running self-test..."
|
|
378
|
+
local fails=0
|
|
379
|
+
|
|
380
|
+
# Deps
|
|
381
|
+
oqa_require opencode sqlite3 curl jq bun || { swsp_log "FAIL: missing dependencies"; fails=$((fails+1)); }
|
|
382
|
+
|
|
383
|
+
# Start fake-LLM
|
|
384
|
+
local st_log
|
|
385
|
+
st_log="$(mktemp -t swsp-st-llm.XXXXXX)"
|
|
386
|
+
OQA_TMPDIRS+=("$st_log")
|
|
387
|
+
|
|
388
|
+
if ! swsp_start_fake_llm "$st_log"; then
|
|
389
|
+
swsp_log "FAIL: fake-LLM did not start (port=${FAKE_OPENAI_PORT:-dynamic})"
|
|
390
|
+
fails=$((fails+1))
|
|
391
|
+
else
|
|
392
|
+
swsp_info "fake-LLM started on port $FAKE_SERVER_PORT"
|
|
393
|
+
|
|
394
|
+
# Health check
|
|
395
|
+
if curl -sf "http://127.0.0.1:${FAKE_SERVER_PORT}/health" >/dev/null 2>&1; then
|
|
396
|
+
swsp_info "PASS: fake-LLM /health 200"
|
|
397
|
+
else
|
|
398
|
+
swsp_log "FAIL: fake-LLM /health did not return 200"
|
|
399
|
+
fails=$((fails+1))
|
|
400
|
+
fi
|
|
401
|
+
fi
|
|
402
|
+
|
|
403
|
+
# Sandbox + opencode serve
|
|
404
|
+
if ! oqa_start_server; then
|
|
405
|
+
swsp_log "FAIL: opencode serve did not start"
|
|
406
|
+
swsp_stop_fake_llm
|
|
407
|
+
fails=$((fails+1))
|
|
408
|
+
else
|
|
409
|
+
swsp_info "PASS: opencode serve started at $OQA_SERVER_URL"
|
|
410
|
+
|
|
411
|
+
# /global/health check
|
|
412
|
+
local health_code
|
|
413
|
+
health_code="$(curl -so /dev/null -w "%{http_code}" -u "opencode:${OQA_SERVER_PASS}" \
|
|
414
|
+
"${OQA_SERVER_URL}/global/health" 2>/dev/null)" || true
|
|
415
|
+
if [ "$health_code" = "200" ]; then
|
|
416
|
+
swsp_info "PASS: /global/health 200"
|
|
417
|
+
else
|
|
418
|
+
swsp_log "FAIL: /global/health returned $health_code"
|
|
419
|
+
fails=$((fails+1))
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
# OMO_SANDBOX_OMO_CONFIG env contract assertion: merge keeps base overrides
|
|
423
|
+
local omo_cfg_path="$XDG_CONFIG_HOME/opencode/oh-my-openagent.json"
|
|
424
|
+
OMO_SANDBOX_OMO_CONFIG='{"_probe":true}' swsp_write_omo_config "$XDG_CONFIG_HOME"
|
|
425
|
+
local probe_val explore_model
|
|
426
|
+
probe_val="$(jq -r '._probe' "$omo_cfg_path" 2>/dev/null)"
|
|
427
|
+
explore_model="$(jq -r '.agents.explore.model' "$omo_cfg_path" 2>/dev/null)"
|
|
428
|
+
if [ "$probe_val" = "true" ] && [ "$explore_model" = "openai/gpt-fake" ]; then
|
|
429
|
+
swsp_info "PASS: OMO_SANDBOX_OMO_CONFIG merge assertion (env key + base overrides both present)"
|
|
430
|
+
else
|
|
431
|
+
swsp_log "FAIL: omo config merge wrong: _probe='$probe_val' explore_model='$explore_model'"
|
|
432
|
+
fails=$((fails+1))
|
|
433
|
+
fi
|
|
434
|
+
fi
|
|
435
|
+
|
|
436
|
+
swsp_stop_fake_llm
|
|
437
|
+
|
|
438
|
+
# Orphan check
|
|
439
|
+
local orphan_count
|
|
440
|
+
orphan_count="$(pgrep -f "fake-openai-server" 2>/dev/null | wc -l | tr -d ' ')" || orphan_count=0
|
|
441
|
+
if [ "${orphan_count:-0}" -eq 0 ]; then
|
|
442
|
+
swsp_info "PASS: no orphan fake-openai-server processes"
|
|
443
|
+
else
|
|
444
|
+
swsp_log "FAIL: $orphan_count orphan fake-openai-server process(es) remain"
|
|
445
|
+
pkill -f "fake-openai-server" 2>/dev/null || true
|
|
446
|
+
fails=$((fails+1))
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
if [ "$fails" -eq 0 ]; then
|
|
450
|
+
printf 'SELF-TEST OK\n'
|
|
451
|
+
return 0
|
|
452
|
+
fi
|
|
453
|
+
printf 'SELF-TEST FAILED (%d failure(s))\n' "$fails" >&2
|
|
454
|
+
return 1
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
# ---- main probe run ----------------------------------------------------------
|
|
458
|
+
swsp_run_probe() {
|
|
459
|
+
local evidence_dir="${EVIDENCE_DIR:-$(mktemp -d -t swsp-evidence.XXXXXX)}"
|
|
460
|
+
mkdir -p "$evidence_dir"
|
|
461
|
+
OQA_TMPDIRS+=("$evidence_dir") 2>/dev/null || true # only auto-clean if we created it
|
|
462
|
+
|
|
463
|
+
# Override: if caller gave --evidence-dir, don't delete it
|
|
464
|
+
if [ -n "$EVIDENCE_DIR" ]; then
|
|
465
|
+
# Remove from cleanup list (last element we added)
|
|
466
|
+
unset 'OQA_TMPDIRS[${#OQA_TMPDIRS[@]}-1]' 2>/dev/null || true
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
swsp_info "evidence dir: $evidence_dir"
|
|
470
|
+
|
|
471
|
+
local fake_llm_log="$evidence_dir/fake-llm.log"
|
|
472
|
+
local harness_log="$evidence_dir/harness.log"
|
|
473
|
+
local serve_stdout="$evidence_dir/opencode-serve.stdout"
|
|
474
|
+
local serve_stderr="$evidence_dir/opencode-serve.stderr"
|
|
475
|
+
|
|
476
|
+
# Step 1: Record real-DB session count (read-only)
|
|
477
|
+
local real_db_path real_db_count_before
|
|
478
|
+
real_db_path="$(opencode db path 2>/dev/null | head -1 || echo "")"
|
|
479
|
+
if [ -n "$real_db_path" ] && [ -f "$real_db_path" ]; then
|
|
480
|
+
real_db_count_before="$(sqlite3 "$real_db_path" 'SELECT count(*) FROM session' 2>/dev/null || echo "0")"
|
|
481
|
+
else
|
|
482
|
+
real_db_count_before="0"
|
|
483
|
+
real_db_path="(not found)"
|
|
484
|
+
fi
|
|
485
|
+
swsp_info "real DB session count before: $real_db_count_before"
|
|
486
|
+
printf 'real_db=%s before=%s\n' "$real_db_path" "$real_db_count_before" >"$evidence_dir/isolation-receipt.txt"
|
|
487
|
+
|
|
488
|
+
# Capture omo log byte offset
|
|
489
|
+
local omo_log="${TMPDIR:-/tmp}/oh-my-opencode.log"
|
|
490
|
+
local omo_log_offset
|
|
491
|
+
if [ -f "$omo_log" ]; then
|
|
492
|
+
omo_log_offset="$(wc -c <"$omo_log" 2>/dev/null | tr -d ' ')" || omo_log_offset=0
|
|
493
|
+
else
|
|
494
|
+
omo_log_offset=0
|
|
495
|
+
fi
|
|
496
|
+
|
|
497
|
+
# Step 2: Start fake-openai server
|
|
498
|
+
swsp_info "starting fake-openai server..."
|
|
499
|
+
if ! swsp_start_fake_llm "$fake_llm_log"; then
|
|
500
|
+
swsp_log "HARNESS_ERROR: fake-openai server failed to start"
|
|
501
|
+
printf 'RESULT=HARNESS_ERROR fake_llm_start_failed\n' | tee -a "$harness_log"
|
|
502
|
+
return 1
|
|
503
|
+
fi
|
|
504
|
+
swsp_info "fake-openai on port $FAKE_SERVER_PORT"
|
|
505
|
+
printf 'fake_llm_port=%s\n' "$FAKE_SERVER_PORT" >>"$evidence_dir/isolation-receipt.txt"
|
|
506
|
+
|
|
507
|
+
# Step 3: Create isolated sandbox and write opencode.jsonc
|
|
508
|
+
# oqa_mk_isolated_xdg sets XDG_CONFIG_HOME, XDG_DATA_HOME, etc.
|
|
509
|
+
oqa_mk_isolated_xdg
|
|
510
|
+
swsp_info "sandbox: $OQA_XDG_ROOT"
|
|
511
|
+
|
|
512
|
+
swsp_write_omo_config "$XDG_CONFIG_HOME"
|
|
513
|
+
|
|
514
|
+
swsp_write_opencode_config "$XDG_CONFIG_HOME" "$FAKE_SERVER_PORT"
|
|
515
|
+
local sandbox_db="$XDG_DATA_HOME/opencode/opencode.db"
|
|
516
|
+
|
|
517
|
+
# Step 4: Start opencode serve (using oqa_start_server internals but with our config)
|
|
518
|
+
# oqa_start_server includes another oqa_mk_isolated_xdg call which would reset XDG vars.
|
|
519
|
+
# Instead, start server directly using the already-set XDG vars.
|
|
520
|
+
swsp_info "starting opencode serve..."
|
|
521
|
+
local port pass
|
|
522
|
+
port="$(oqa_free_port)"
|
|
523
|
+
pass="oqa-${RANDOM}${RANDOM}"
|
|
524
|
+
|
|
525
|
+
OPENCODE_SERVER_PASSWORD="$pass" opencode serve --port "$port" --hostname 127.0.0.1 \
|
|
526
|
+
>"$serve_stdout" 2>"$serve_stderr" &
|
|
527
|
+
OQA_SERVER_PID=$!
|
|
528
|
+
disown "$OQA_SERVER_PID" 2>/dev/null || true
|
|
529
|
+
export OQA_SERVER_PORT="$port"
|
|
530
|
+
export OQA_SERVER_PASS="$pass"
|
|
531
|
+
export OQA_SERVER_URL="http://127.0.0.1:$port"
|
|
532
|
+
|
|
533
|
+
if ! oqa_wait_http "$OQA_SERVER_URL/global/health" "opencode:$pass" 30; then
|
|
534
|
+
swsp_log "HARNESS_ERROR: opencode serve failed to start"
|
|
535
|
+
cat "$serve_stderr" >&2 2>/dev/null || true
|
|
536
|
+
printf 'RESULT=HARNESS_ERROR opencode_serve_start_failed\n' | tee -a "$harness_log"
|
|
537
|
+
swsp_stop_fake_llm
|
|
538
|
+
return 1
|
|
539
|
+
fi
|
|
540
|
+
swsp_info "opencode serve ready at $OQA_SERVER_URL"
|
|
541
|
+
|
|
542
|
+
# Encode the working directory for use in URL
|
|
543
|
+
local enc_dir
|
|
544
|
+
enc_dir="$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=""))' "$OQA_PROJ" 2>/dev/null \
|
|
545
|
+
|| printf '%s' "$OQA_PROJ" | sed 's|/|%2F|g')"
|
|
546
|
+
|
|
547
|
+
# Step 5: Create a session
|
|
548
|
+
swsp_info "creating session..."
|
|
549
|
+
local ses_response ses_id
|
|
550
|
+
ses_response="$(curl -sS -u "opencode:${pass}" \
|
|
551
|
+
-X POST "${OQA_SERVER_URL}/session?directory=${enc_dir}" \
|
|
552
|
+
-H 'content-type: application/json' \
|
|
553
|
+
-d '{"title":"wake split probe"}' 2>/dev/null)" || ses_response=""
|
|
554
|
+
|
|
555
|
+
ses_id="$(printf '%s' "$ses_response" | jq -r '.id // .sessionID // empty' 2>/dev/null)" || ses_id=""
|
|
556
|
+
|
|
557
|
+
if [ -z "$ses_id" ]; then
|
|
558
|
+
swsp_log "HARNESS_ERROR: could not create session (response: $ses_response)"
|
|
559
|
+
printf 'RESULT=HARNESS_ERROR session_create_failed\n' | tee -a "$harness_log"
|
|
560
|
+
swsp_stop_fake_llm
|
|
561
|
+
return 1
|
|
562
|
+
fi
|
|
563
|
+
swsp_info "session: $ses_id"
|
|
564
|
+
printf 'session_id=%s\n' "$ses_id" >>"$evidence_dir/isolation-receipt.txt"
|
|
565
|
+
|
|
566
|
+
# Step 6: Send the split probe prompt
|
|
567
|
+
swsp_info "sending split probe prompt..."
|
|
568
|
+
local prompt_response
|
|
569
|
+
prompt_response="$(curl -sS -u "opencode:${pass}" \
|
|
570
|
+
-X POST "${OQA_SERVER_URL}/session/${ses_id}/prompt_async?directory=${enc_dir}" \
|
|
571
|
+
-H 'content-type: application/json' \
|
|
572
|
+
-d '{"parts":[{"type":"text","text":"Run the split probe: call call_omo_agent exactly once as instructed, then run the bash hold command."}]}' \
|
|
573
|
+
2>/dev/null)" || prompt_response=""
|
|
574
|
+
swsp_info "prompt_async response: $prompt_response"
|
|
575
|
+
|
|
576
|
+
# Step 7: Poll sandbox DB for children/stops; also wait for session idle
|
|
577
|
+
swsp_info "polling DB for wake-split metrics (up to 120s)..."
|
|
578
|
+
local metrics
|
|
579
|
+
metrics="$(swsp_poll_db_metrics "$sandbox_db" '%[BACKGROUND TASK%' 120)"
|
|
580
|
+
local children stops
|
|
581
|
+
children="$(printf '%s' "$metrics" | awk '{print $1}')"
|
|
582
|
+
stops="$(printf '%s' "$metrics" | awk '{print $2}')"
|
|
583
|
+
children="${children:-0}"
|
|
584
|
+
stops="${stops:-0}"
|
|
585
|
+
swsp_info "DB metrics: children=$children stops=$stops"
|
|
586
|
+
|
|
587
|
+
# Wait for parent session to go idle
|
|
588
|
+
swsp_info "waiting for parent session to go idle..."
|
|
589
|
+
swsp_wait_session_idle "$OQA_SERVER_URL" "$pass" "$ses_id" 60
|
|
590
|
+
|
|
591
|
+
# Re-read metrics after idle
|
|
592
|
+
metrics="$(swsp_poll_db_metrics "$sandbox_db" '%[BACKGROUND TASK%' 10)"
|
|
593
|
+
children="$(printf '%s' "$metrics" | awk '{print $1}')"
|
|
594
|
+
stops="$(printf '%s' "$metrics" | awk '{print $2}')"
|
|
595
|
+
children="${children:-0}"
|
|
596
|
+
stops="${stops:-0}"
|
|
597
|
+
swsp_info "final DB metrics: children=$children stops=$stops"
|
|
598
|
+
printf 'children=%s stops=%s\n' "$children" "$stops" >"$evidence_dir/marker-metrics.txt"
|
|
599
|
+
|
|
600
|
+
# Step 8: Plugin-init count
|
|
601
|
+
local plugin_inits
|
|
602
|
+
plugin_inits="$(swsp_count_plugin_inits "$omo_log_offset" "$OQA_PROJ")"
|
|
603
|
+
plugin_inits="${plugin_inits:-0}"
|
|
604
|
+
swsp_info "plugin_inits: $plugin_inits"
|
|
605
|
+
printf '%s\n' "$plugin_inits" >"$evidence_dir/plugin-init-count.txt"
|
|
606
|
+
|
|
607
|
+
# Step 9: Route provenance
|
|
608
|
+
local route_prov=""
|
|
609
|
+
if [ -f "$omo_log" ]; then
|
|
610
|
+
route_prov="$(tail -c "+$((omo_log_offset + 1))" "$omo_log" 2>/dev/null \
|
|
611
|
+
| grep -E "live-server-route" || true)"
|
|
612
|
+
fi
|
|
613
|
+
printf '%s\n' "$route_prov" >"$evidence_dir/route-provenance.log"
|
|
614
|
+
swsp_info "route-provenance lines: $(printf '%s' "$route_prov" | wc -l | tr -d ' ')"
|
|
615
|
+
|
|
616
|
+
# WAKE_DISPATCHED_DURING_PARENT_TURN mechanism signal
|
|
617
|
+
local wake_during_parent
|
|
618
|
+
wake_during_parent="$(swsp_detect_wake_during_parent "$omo_log_offset" "$fake_llm_log" "$OQA_PROJ")"
|
|
619
|
+
swsp_info "WAKE_DISPATCHED_DURING_PARENT_TURN=$wake_during_parent"
|
|
620
|
+
|
|
621
|
+
# Step 10: Branch-count guard
|
|
622
|
+
if ! swsp_check_branch_counts "$fake_llm_log" >&2; then
|
|
623
|
+
# Branch counts not met — HARNESS_ERROR
|
|
624
|
+
local ptc pc cc wc
|
|
625
|
+
ptc="$(grep -c "branch=parent-tool-call" "$fake_llm_log" 2>/dev/null || printf '0')"
|
|
626
|
+
pc="$(grep -c "branch=parent-hold" "$fake_llm_log" 2>/dev/null || printf '0')"
|
|
627
|
+
cc="$(grep -c "branch=child" "$fake_llm_log" 2>/dev/null || printf '0')"
|
|
628
|
+
wc="$(grep -c "branch=wake" "$fake_llm_log" 2>/dev/null || printf '0')"
|
|
629
|
+
local verdict_line
|
|
630
|
+
verdict_line="RESULT=HARNESS_ERROR children=${children} stops=${stops} plugin_inits=${plugin_inits} WAKE_DISPATCHED_DURING_PARENT_TURN=${wake_during_parent} branch_counts=parent-tool-call:${ptc},parent-hold:${pc},child:${cc},wake:${wc}"
|
|
631
|
+
printf '%s\n' "$verdict_line" | tee -a "$harness_log"
|
|
632
|
+
swsp_stop_fake_llm
|
|
633
|
+
return 1
|
|
634
|
+
fi
|
|
635
|
+
|
|
636
|
+
# Step 11: Determine verdict
|
|
637
|
+
local result="INCONCLUSIVE"
|
|
638
|
+
local exit_code=1
|
|
639
|
+
|
|
640
|
+
# Arm 1: SQLite evidence (stops>1 or children>1)
|
|
641
|
+
if [ "${stops:-0}" -gt 1 ] || [ "${children:-0}" -gt 1 ] 2>/dev/null; then
|
|
642
|
+
result="REPRODUCED"
|
|
643
|
+
fi
|
|
644
|
+
|
|
645
|
+
# Arm 2: Mechanism signal (wake dispatched during parent turn + in-process path)
|
|
646
|
+
# in-process path: no live-server-route dispatch line for this wake
|
|
647
|
+
local has_live_dispatch=false
|
|
648
|
+
if printf '%s' "$route_prov" | grep -q "dispatch via live listener" 2>/dev/null; then
|
|
649
|
+
has_live_dispatch=true
|
|
650
|
+
fi
|
|
651
|
+
if [ "$wake_during_parent" = "true" ] && [ "$has_live_dispatch" = "false" ]; then
|
|
652
|
+
result="REPRODUCED"
|
|
653
|
+
fi
|
|
654
|
+
|
|
655
|
+
# FIXED: exactly 1 child and 1 stop, and neither REPRODUCED arm held
|
|
656
|
+
if [ "$result" = "INCONCLUSIVE" ] && [ "${children:-0}" -eq 1 ] && [ "${stops:-0}" -eq 1 ] 2>/dev/null; then
|
|
657
|
+
result="FIXED"
|
|
658
|
+
fi
|
|
659
|
+
|
|
660
|
+
local verdict_line
|
|
661
|
+
verdict_line="RESULT=${result} children=${children} stops=${stops} plugin_inits=${plugin_inits} WAKE_DISPATCHED_DURING_PARENT_TURN=${wake_during_parent}"
|
|
662
|
+
printf '%s\n' "$verdict_line" | tee -a "$harness_log"
|
|
663
|
+
|
|
664
|
+
# Determine exit code based on expected mode
|
|
665
|
+
if [ -n "$EXPECT_MODE" ]; then
|
|
666
|
+
if [ "$EXPECT_MODE" = "reproduced" ] && [ "$result" = "REPRODUCED" ]; then
|
|
667
|
+
exit_code=0
|
|
668
|
+
elif [ "$EXPECT_MODE" = "fixed" ] && [ "$result" = "FIXED" ]; then
|
|
669
|
+
exit_code=0
|
|
670
|
+
else
|
|
671
|
+
exit_code=1
|
|
672
|
+
fi
|
|
673
|
+
else
|
|
674
|
+
exit_code=0 # no expectation: just report
|
|
675
|
+
fi
|
|
676
|
+
|
|
677
|
+
# Step 12: Isolation receipt — verify real DB count unchanged
|
|
678
|
+
local real_db_count_after=""
|
|
679
|
+
if [ -n "$real_db_path" ] && [ "$real_db_path" != "(not found)" ] && [ -f "$real_db_path" ]; then
|
|
680
|
+
real_db_count_after="$(sqlite3 "$real_db_path" 'SELECT count(*) FROM session' 2>/dev/null || echo "0")"
|
|
681
|
+
else
|
|
682
|
+
real_db_count_after="$real_db_count_before"
|
|
683
|
+
fi
|
|
684
|
+
printf 'after=%s unchanged=%s\n' \
|
|
685
|
+
"$real_db_count_after" \
|
|
686
|
+
"$([ "$real_db_count_after" = "$real_db_count_before" ] && echo yes || echo NO)" \
|
|
687
|
+
>>"$evidence_dir/isolation-receipt.txt"
|
|
688
|
+
swsp_info "isolation: real DB before=$real_db_count_before after=$real_db_count_after"
|
|
689
|
+
|
|
690
|
+
# Preserve the sandbox DB for post-hoc inspection before the trap removes it
|
|
691
|
+
sqlite3 "$sandbox_db" ".backup '$evidence_dir/sandbox-opencode.db'" 2>/dev/null || true
|
|
692
|
+
|
|
693
|
+
# Cleanup receipt
|
|
694
|
+
swsp_stop_fake_llm
|
|
695
|
+
printf 'fake_llm=stopped opencode_serve=stopping\n' >"$evidence_dir/cleanup-receipt.txt"
|
|
696
|
+
|
|
697
|
+
# Orphan check
|
|
698
|
+
local orphan_count
|
|
699
|
+
orphan_count="$(pgrep -f "fake-openai-server" 2>/dev/null | wc -l | tr -d ' ')" || orphan_count=0
|
|
700
|
+
if [ "${orphan_count:-0}" -gt 0 ]; then
|
|
701
|
+
swsp_log "WARNING: $orphan_count orphan fake-openai-server process(es); killing"
|
|
702
|
+
pkill -f "fake-openai-server" 2>/dev/null || true
|
|
703
|
+
fi
|
|
704
|
+
printf 'orphan_fake_llm=%s\n' "${orphan_count:-0}" >>"$evidence_dir/cleanup-receipt.txt"
|
|
705
|
+
|
|
706
|
+
return "$exit_code"
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
# ---- dispatch ----------------------------------------------------------------
|
|
710
|
+
if [ "$SELF_TEST" -eq 1 ]; then
|
|
711
|
+
swsp_self_test
|
|
712
|
+
exit $?
|
|
713
|
+
fi
|
|
714
|
+
|
|
715
|
+
swsp_run_probe
|
|
716
|
+
exit $?
|