@nbardy/oompa 0.7.2 → 0.7.3
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/README.md +6 -4
- package/agentnet/src/agentnet/agent.clj +9 -8
- package/agentnet/src/agentnet/cli.clj +268 -77
- package/agentnet/src/agentnet/cli.clj.bak +1384 -0
- package/agentnet/src/agentnet/core.clj +17 -2
- package/agentnet/src/agentnet/harness.clj +69 -36
- package/agentnet/src/agentnet/runs.clj +5 -3
- package/agentnet/src/agentnet/tasks.clj +6 -0
- package/agentnet/src/agentnet/worker.clj +676 -226
- package/bin/oompa.js +5 -1
- package/config/prompts/_task_header.md +2 -2
- package/oompa.example.json +4 -4
- package/package.json +5 -3
- package/scripts/README.md +6 -0
- package/scripts/__pycache__/stream_bridge.cpython-314.pyc +0 -0
- package/scripts/copy-repo-code.sh +110 -0
- package/scripts/install-babashka.js +97 -0
- package/scripts/test-harness-resume.sh +229 -0
package/bin/oompa.js
CHANGED
|
@@ -15,7 +15,11 @@ if (!fs.existsSync(swarmScript) || !fs.existsSync(classpath)) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// Try to use the locally bundled bb if it exists (from postinstall)
|
|
19
|
+
const localBb = path.join(__dirname, "bb");
|
|
20
|
+
const bbPath = fs.existsSync(localBb) ? localBb : "bb";
|
|
21
|
+
|
|
22
|
+
const result = spawnSync(bbPath, ["--classpath", classpath, swarmScript, ...argv], {
|
|
19
23
|
stdio: "inherit",
|
|
20
24
|
cwd: process.cwd(),
|
|
21
25
|
env: { ...process.env, OOMPA_PACKAGE_ROOT: packageRoot }
|
|
@@ -33,6 +33,6 @@ Always read/write queue files via `{{TASKS_ROOT}}/...` (not hard-coded local `ta
|
|
|
33
33
|
|
|
34
34
|
- **`CLAIM(id, ...)`** — Claim one or more pending tasks. Batch related tasks together.
|
|
35
35
|
- **`COMPLETE_AND_READY_FOR_MERGE`** — Your work is done and ready for review. Framework reviews, merges, and marks your claimed tasks complete.
|
|
36
|
-
- **`
|
|
36
|
+
- **`NEEDS_FOLLOWUP`** — Last resort continuation signal. Use only if you tried hard and still cannot produce a merge-ready artifact from the current context. Explain the remaining work after the signal. The framework will keep your claimed tasks and resume you with a sharper follow-up prompt. This is not success.
|
|
37
37
|
|
|
38
|
-
One signal per output. Claim before working.
|
|
38
|
+
One signal per output. Claim before working. Do not output `DONE`.
|
package/oompa.example.json
CHANGED
|
@@ -3,28 +3,28 @@
|
|
|
3
3
|
{
|
|
4
4
|
"model": "claude:opus",
|
|
5
5
|
"prompt": ["config/prompts/planner.md"],
|
|
6
|
-
"
|
|
6
|
+
"max_cycle": 5,
|
|
7
7
|
"count": 1,
|
|
8
8
|
"wait_between": 60
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"model": "codex:gpt-5.3-codex:medium",
|
|
12
12
|
"prompt": ["config/prompts/executor.md"],
|
|
13
|
-
"
|
|
13
|
+
"max_cycle": 10,
|
|
14
14
|
"count": 2,
|
|
15
15
|
"can_plan": false
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"model": "opencode:opencode/kimi-k2.5-free",
|
|
19
19
|
"prompt": ["config/prompts/executor.md"],
|
|
20
|
-
"
|
|
20
|
+
"max_cycle": 10,
|
|
21
21
|
"count": 1,
|
|
22
22
|
"can_plan": false
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"model": "gemini:gemini-3-pro-preview",
|
|
26
26
|
"prompt": ["config/prompts/executor.md"],
|
|
27
|
-
"
|
|
27
|
+
"max_cycle": 10,
|
|
28
28
|
"count": 1,
|
|
29
29
|
"can_plan": false
|
|
30
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbardy/oompa",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Git-worktree multi-agent swarm orchestrator for Codex, Claude, and Opencode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
"agentnet/src/",
|
|
15
15
|
"config/prompts/",
|
|
16
16
|
"oompa.example.json",
|
|
17
|
-
"README.md"
|
|
17
|
+
"README.md",
|
|
18
|
+
"scripts/"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|
|
20
21
|
"check": "node bin/oompa.js check",
|
|
21
22
|
"help": "node bin/oompa.js help",
|
|
22
|
-
"pack:dry": "npm pack --dry-run"
|
|
23
|
+
"pack:dry": "npm pack --dry-run",
|
|
24
|
+
"postinstall": "node scripts/install-babashka.js"
|
|
23
25
|
},
|
|
24
26
|
"engines": {
|
|
25
27
|
"node": ">=18"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Scripts
|
|
2
|
+
|
|
3
|
+
Reserved for helper shell scripts (smoke tests, lint wiring, etc.).
|
|
4
|
+
Populate as the orchestrator matures.
|
|
5
|
+
|
|
6
|
+
- `copy-repo-code.sh`: copies implementation code files by default (or all tracked text files via `--all-files`) with filename headers to macOS clipboard (`pbcopy`).
|
|
Binary file
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copy tracked code files in this repo to clipboard with file headers.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./scripts/copy-repo-code.sh
|
|
6
|
+
# ./scripts/copy-repo-code.sh --all-files
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat <<'EOF'
|
|
12
|
+
Usage:
|
|
13
|
+
./scripts/copy-repo-code.sh
|
|
14
|
+
./scripts/copy-repo-code.sh --all-files
|
|
15
|
+
|
|
16
|
+
Default mode:
|
|
17
|
+
Copies implementation-oriented files only (src, scripts, bins, tests, and
|
|
18
|
+
top-level shell/babashka entrypoints).
|
|
19
|
+
|
|
20
|
+
--all-files:
|
|
21
|
+
Copies all tracked text files in the repository.
|
|
22
|
+
EOF
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
mode="implementation"
|
|
26
|
+
if [ "${1:-}" = "--all-files" ]; then
|
|
27
|
+
mode="all"
|
|
28
|
+
shift
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
|
32
|
+
usage
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if [ "$#" -gt 0 ]; then
|
|
37
|
+
usage >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
42
|
+
echo "Error: git is required." >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if ! command -v pbcopy >/dev/null 2>&1; then
|
|
47
|
+
echo "Error: pbcopy is not available on this system." >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
52
|
+
repo_root=$(git -C "$script_dir" rev-parse --show-toplevel 2>/dev/null || true)
|
|
53
|
+
if [ -z "$repo_root" ]; then
|
|
54
|
+
echo "Error: could not determine git repo root." >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
tmp_file=$(mktemp)
|
|
59
|
+
cleanup() {
|
|
60
|
+
rm -f "$tmp_file"
|
|
61
|
+
}
|
|
62
|
+
trap cleanup EXIT
|
|
63
|
+
|
|
64
|
+
copied=0
|
|
65
|
+
skipped=0
|
|
66
|
+
|
|
67
|
+
if [ "$mode" = "all" ]; then
|
|
68
|
+
file_source_cmd=(git -C "$repo_root" ls-files -z)
|
|
69
|
+
else
|
|
70
|
+
file_source_cmd=(
|
|
71
|
+
git -C "$repo_root" ls-files -z --
|
|
72
|
+
"src/**"
|
|
73
|
+
"agentnet/src/**"
|
|
74
|
+
"bin/**"
|
|
75
|
+
"scripts/**"
|
|
76
|
+
"tests/**"
|
|
77
|
+
"agentnet/test/**"
|
|
78
|
+
"*.sh"
|
|
79
|
+
"*.bb"
|
|
80
|
+
"bb.edn"
|
|
81
|
+
"package.json"
|
|
82
|
+
)
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
while IFS= read -r -d '' file; do
|
|
86
|
+
full_path="$repo_root/$file"
|
|
87
|
+
[ -f "$full_path" ] || continue
|
|
88
|
+
|
|
89
|
+
# Skip files that look binary.
|
|
90
|
+
if ! grep -Iq . "$full_path"; then
|
|
91
|
+
skipped=$((skipped + 1))
|
|
92
|
+
continue
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
copied=$((copied + 1))
|
|
96
|
+
{
|
|
97
|
+
printf '===== FILE: %s =====\n' "$file"
|
|
98
|
+
cat "$full_path"
|
|
99
|
+
printf '\n\n'
|
|
100
|
+
} >> "$tmp_file"
|
|
101
|
+
done < <("${file_source_cmd[@]}")
|
|
102
|
+
|
|
103
|
+
if [ "$copied" -eq 0 ]; then
|
|
104
|
+
echo "No tracked text files found to copy." >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
pbcopy < "$tmp_file"
|
|
109
|
+
bytes=$(wc -c < "$tmp_file" | tr -d '[:space:]')
|
|
110
|
+
echo "Copied $copied files ($bytes bytes) to clipboard in '$mode' mode. Skipped $skipped binary files."
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const BB_VERSION = '1.3.191'; // A stable release version
|
|
10
|
+
|
|
11
|
+
// Map Node.js os/arch to Babashka release asset names
|
|
12
|
+
const platforms = {
|
|
13
|
+
darwin: {
|
|
14
|
+
arm64: `babashka-${BB_VERSION}-macos-aarch64.tar.gz`,
|
|
15
|
+
x64: `babashka-${BB_VERSION}-macos-amd64.tar.gz`,
|
|
16
|
+
},
|
|
17
|
+
linux: {
|
|
18
|
+
arm64: `babashka-${BB_VERSION}-linux-aarch64-static.tar.gz`,
|
|
19
|
+
x64: `babashka-${BB_VERSION}-linux-amd64-static.tar.gz`,
|
|
20
|
+
},
|
|
21
|
+
// We can add Windows later if needed, but it requires zip extraction
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
async function downloadBabashka() {
|
|
25
|
+
const osType = os.platform();
|
|
26
|
+
const arch = os.arch();
|
|
27
|
+
|
|
28
|
+
// If already installed globally and accessible, we can skip downloading!
|
|
29
|
+
try {
|
|
30
|
+
const existing = execSync('which bb', { encoding: 'utf-8' }).trim();
|
|
31
|
+
if (existing) {
|
|
32
|
+
console.log(`[oompa] System Babashka found at ${existing}. Skipping download.`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Not found, continue with download
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const assetName = platforms[osType]?.[arch];
|
|
40
|
+
if (!assetName) {
|
|
41
|
+
console.warn(`[oompa] Automatic Babashka download not supported for ${osType} ${arch}.`);
|
|
42
|
+
console.warn('[oompa] Please install manually: https://github.com/babashka/babashka#installation');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const downloadUrl = `https://github.com/babashka/babashka/releases/download/v${BB_VERSION}/${assetName}`;
|
|
47
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
48
|
+
const tempTar = path.join(binDir, 'bb.tar.gz');
|
|
49
|
+
const bbDest = path.join(binDir, 'bb');
|
|
50
|
+
|
|
51
|
+
if (!fs.existsSync(binDir)) {
|
|
52
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`[oompa] Downloading Babashka (native engine) from ${downloadUrl}...`);
|
|
56
|
+
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
// Handle GitHub's redirects (302)
|
|
59
|
+
https.get(downloadUrl, (res) => {
|
|
60
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
61
|
+
https.get(res.headers.location, (redirectRes) => {
|
|
62
|
+
const fileStream = fs.createWriteStream(tempTar);
|
|
63
|
+
redirectRes.pipe(fileStream);
|
|
64
|
+
fileStream.on('finish', () => {
|
|
65
|
+
fileStream.close();
|
|
66
|
+
extract(tempTar, binDir, bbDest, resolve, reject);
|
|
67
|
+
});
|
|
68
|
+
}).on('error', reject);
|
|
69
|
+
} else {
|
|
70
|
+
reject(new Error(`Failed to download: ${res.statusCode}`));
|
|
71
|
+
}
|
|
72
|
+
}).on('error', reject);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function extract(tarPath, destDir, bbDest, resolve, reject) {
|
|
77
|
+
console.log('[oompa] Extracting native binary...');
|
|
78
|
+
try {
|
|
79
|
+
execSync(`tar -xzf ${tarPath} -C ${destDir}`);
|
|
80
|
+
fs.unlinkSync(tarPath); // cleanup
|
|
81
|
+
|
|
82
|
+
if (fs.existsSync(bbDest)) {
|
|
83
|
+
fs.chmodSync(bbDest, 0o755); // Make it executable
|
|
84
|
+
console.log('[oompa] ✅ Native engine installed successfully!');
|
|
85
|
+
resolve();
|
|
86
|
+
} else {
|
|
87
|
+
reject(new Error('Extraction failed: bb binary not found.'));
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
reject(e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
downloadBabashka().catch((err) => {
|
|
95
|
+
console.error(`[oompa] Failed to install Babashka natively: ${err.message}`);
|
|
96
|
+
console.warn('[oompa] Please install manually: https://github.com/babashka/babashka#installation');
|
|
97
|
+
});
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-harness-resume.sh — Smoke test session create + resume for each harness.
|
|
3
|
+
#
|
|
4
|
+
# For each available harness CLI:
|
|
5
|
+
# 1. Create a session: "Remember the word FOOBAR"
|
|
6
|
+
# 2. Resume that session: "What word were you supposed to remember?"
|
|
7
|
+
# 3. Check the response contains FOOBAR
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/test-harness-resume.sh # test all available
|
|
11
|
+
# ./scripts/test-harness-resume.sh claude # test one harness
|
|
12
|
+
#
|
|
13
|
+
# Requires the CLI binaries to be on PATH.
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
PASS=0
|
|
18
|
+
FAIL=0
|
|
19
|
+
SKIP=0
|
|
20
|
+
|
|
21
|
+
# Unset CLAUDECODE so we can run nested claude sessions
|
|
22
|
+
unset CLAUDECODE 2>/dev/null || true
|
|
23
|
+
|
|
24
|
+
green() { printf "\033[32m%s\033[0m\n" "$*"; }
|
|
25
|
+
red() { printf "\033[31m%s\033[0m\n" "$*"; }
|
|
26
|
+
yellow() { printf "\033[33m%s\033[0m\n" "$*"; }
|
|
27
|
+
|
|
28
|
+
check_binary() {
|
|
29
|
+
command -v "$1" >/dev/null 2>&1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ── Claude ────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
test_claude() {
|
|
35
|
+
local uuid
|
|
36
|
+
uuid=$(python3 -c "import uuid; print(str(uuid.uuid4()))")
|
|
37
|
+
|
|
38
|
+
echo " Session ID: $uuid"
|
|
39
|
+
|
|
40
|
+
# Step 1: Create session
|
|
41
|
+
echo " Creating session..."
|
|
42
|
+
local create_out
|
|
43
|
+
create_out=$(claude --dangerously-skip-permissions \
|
|
44
|
+
--session-id "$uuid" \
|
|
45
|
+
-p "Remember the word FOOBAR. Say only: OK, remembered." 2>&1)
|
|
46
|
+
local create_exit=$?
|
|
47
|
+
|
|
48
|
+
if [ $create_exit -ne 0 ]; then
|
|
49
|
+
red " FAIL: create exited $create_exit"
|
|
50
|
+
echo " Output: $create_out"
|
|
51
|
+
return 1
|
|
52
|
+
fi
|
|
53
|
+
echo " Create output: $create_out"
|
|
54
|
+
|
|
55
|
+
# Step 2: Resume session
|
|
56
|
+
echo " Resuming session..."
|
|
57
|
+
local resume_out
|
|
58
|
+
resume_out=$(claude --dangerously-skip-permissions \
|
|
59
|
+
--resume "$uuid" \
|
|
60
|
+
-p "What word were you supposed to remember? Say only the word." 2>&1)
|
|
61
|
+
local resume_exit=$?
|
|
62
|
+
|
|
63
|
+
if [ $resume_exit -ne 0 ]; then
|
|
64
|
+
red " FAIL: resume exited $resume_exit"
|
|
65
|
+
echo " Output: $resume_out"
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
echo " Resume output: $resume_out"
|
|
69
|
+
|
|
70
|
+
# Step 3: Check for FOOBAR
|
|
71
|
+
if echo "$resume_out" | grep -qi "FOOBAR"; then
|
|
72
|
+
green " PASS: resume recalled FOOBAR"
|
|
73
|
+
return 0
|
|
74
|
+
else
|
|
75
|
+
red " FAIL: resume did not recall FOOBAR"
|
|
76
|
+
return 1
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ── Opencode ──────────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
test_opencode() {
|
|
83
|
+
# Step 1: Create session, extract session-id from NDJSON
|
|
84
|
+
echo " Creating session..."
|
|
85
|
+
local create_out
|
|
86
|
+
create_out=$(opencode run --format json --print-logs --log-level WARN \
|
|
87
|
+
"Remember the word FOOBAR. Say only: OK, remembered." 2>&1)
|
|
88
|
+
local create_exit=$?
|
|
89
|
+
|
|
90
|
+
if [ $create_exit -ne 0 ]; then
|
|
91
|
+
red " FAIL: create exited $create_exit"
|
|
92
|
+
return 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Extract session ID from NDJSON
|
|
96
|
+
local sid
|
|
97
|
+
sid=$(echo "$create_out" | grep -o '"sessionI[Dd]":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
98
|
+
if [ -z "$sid" ]; then
|
|
99
|
+
# Try alternate pattern
|
|
100
|
+
sid=$(echo "$create_out" | grep -oE 'ses_[A-Za-z0-9]+' | head -1)
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [ -z "$sid" ]; then
|
|
104
|
+
red " FAIL: could not extract session ID"
|
|
105
|
+
echo " Output: $create_out"
|
|
106
|
+
return 1
|
|
107
|
+
fi
|
|
108
|
+
echo " Session ID: $sid"
|
|
109
|
+
|
|
110
|
+
# Step 2: Resume
|
|
111
|
+
echo " Resuming session..."
|
|
112
|
+
local resume_out
|
|
113
|
+
resume_out=$(opencode run --format json --print-logs --log-level WARN \
|
|
114
|
+
-s "$sid" --continue \
|
|
115
|
+
"What word were you supposed to remember? Say only the word." 2>&1)
|
|
116
|
+
local resume_exit=$?
|
|
117
|
+
|
|
118
|
+
if [ $resume_exit -ne 0 ]; then
|
|
119
|
+
red " FAIL: resume exited $resume_exit"
|
|
120
|
+
return 1
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
if echo "$resume_out" | grep -qi "FOOBAR"; then
|
|
124
|
+
green " PASS: resume recalled FOOBAR"
|
|
125
|
+
return 0
|
|
126
|
+
else
|
|
127
|
+
red " FAIL: resume did not recall FOOBAR"
|
|
128
|
+
echo " Output: $resume_out"
|
|
129
|
+
return 1
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# ── Gemini ────────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
test_gemini() {
|
|
136
|
+
# Gemini uses implicit sessions (by cwd) and --resume latest
|
|
137
|
+
local tmpdir
|
|
138
|
+
tmpdir=$(mktemp -d)
|
|
139
|
+
|
|
140
|
+
echo " Working dir: $tmpdir"
|
|
141
|
+
|
|
142
|
+
# Step 1: Create session
|
|
143
|
+
echo " Creating session..."
|
|
144
|
+
local create_out
|
|
145
|
+
create_out=$(cd "$tmpdir" && gemini --yolo \
|
|
146
|
+
-p "Remember the word FOOBAR. Say only: OK, remembered." 2>&1)
|
|
147
|
+
local create_exit=$?
|
|
148
|
+
|
|
149
|
+
if [ $create_exit -ne 0 ]; then
|
|
150
|
+
red " FAIL: create exited $create_exit"
|
|
151
|
+
rm -rf "$tmpdir"
|
|
152
|
+
return 1
|
|
153
|
+
fi
|
|
154
|
+
echo " Create output: $create_out"
|
|
155
|
+
|
|
156
|
+
# Step 2: Resume
|
|
157
|
+
echo " Resuming session..."
|
|
158
|
+
local resume_out
|
|
159
|
+
resume_out=$(cd "$tmpdir" && gemini --yolo --resume latest \
|
|
160
|
+
-p "What word were you supposed to remember? Say only the word." 2>&1)
|
|
161
|
+
local resume_exit=$?
|
|
162
|
+
|
|
163
|
+
rm -rf "$tmpdir"
|
|
164
|
+
|
|
165
|
+
if [ $resume_exit -ne 0 ]; then
|
|
166
|
+
red " FAIL: resume exited $resume_exit"
|
|
167
|
+
return 1
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
if echo "$resume_out" | grep -qi "FOOBAR"; then
|
|
171
|
+
green " PASS: resume recalled FOOBAR"
|
|
172
|
+
return 0
|
|
173
|
+
else
|
|
174
|
+
red " FAIL: resume did not recall FOOBAR"
|
|
175
|
+
echo " Output: $resume_out"
|
|
176
|
+
return 1
|
|
177
|
+
fi
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# ── Codex ─────────────────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
test_codex() {
|
|
183
|
+
# Codex has resume-fn: nil — resume is not supported.
|
|
184
|
+
# This is a known limitation, not a silent skip. Fail so it stays visible.
|
|
185
|
+
red " FAIL: codex does not support session resume (resume-fn is nil in registry)"
|
|
186
|
+
return 1
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# ── Runner ────────────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
run_test() {
|
|
192
|
+
local harness=$1
|
|
193
|
+
echo ""
|
|
194
|
+
echo "=== Testing $harness ==="
|
|
195
|
+
|
|
196
|
+
if ! check_binary "$harness"; then
|
|
197
|
+
yellow " SKIP: $harness not found on PATH"
|
|
198
|
+
SKIP=$((SKIP + 1))
|
|
199
|
+
return
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
local result=0
|
|
203
|
+
"test_$harness" || result=$?
|
|
204
|
+
|
|
205
|
+
if [ $result -eq 0 ]; then
|
|
206
|
+
PASS=$((PASS + 1))
|
|
207
|
+
else
|
|
208
|
+
FAIL=$((FAIL + 1))
|
|
209
|
+
fi
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
HARNESSES="${1:-claude codex opencode gemini}"
|
|
215
|
+
|
|
216
|
+
echo "Harness Resume Smoke Tests"
|
|
217
|
+
echo "=========================="
|
|
218
|
+
|
|
219
|
+
for h in $HARNESSES; do
|
|
220
|
+
run_test "$h"
|
|
221
|
+
done
|
|
222
|
+
|
|
223
|
+
echo ""
|
|
224
|
+
echo "=========================="
|
|
225
|
+
echo "Results: $(green "$PASS pass"), $(red "$FAIL fail"), $(yellow "$SKIP skip")"
|
|
226
|
+
|
|
227
|
+
if [ $FAIL -gt 0 ]; then
|
|
228
|
+
exit 1
|
|
229
|
+
fi
|