Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
},
"metadata": {
"description": "Pragmatic multi-agent orchestration: independent review and steerable delegation across Codex, Claude Code, OpenCode, Grok Build, Gemini, and Devin CLI \u2014 for any agent that supports the Agent Skills standard.",
"version": "2.0.0-beta.2"
"version": "2.0.0-beta.3"
},
"plugins": [
{
"name": "pragmatic-orchestration",
"description": "Multi-agent orchestration for coding agents: run external agent CLIs (Codex, Claude Code, OpenCode, Grok Build, Gemini, Devin CLI) as independent reviewers, stateful repository researchers, or steerable single-agent implementers via the `porch` CLI Also ships remote-agents: dedicated remote Linux/Windows agent hosts (SSH-over-SSM, headless desktop, work bridge).",
"version": "2.0.0-beta.2",
"version": "2.0.0-beta.3",
"author": {
"name": "CodeAlive-AI",
"email": "contact@codealive.ai"
Expand Down Expand Up @@ -61,5 +61,5 @@
]
}
],
"version": "2.0.0-beta.2"
"version": "2.0.0-beta.3"
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "pragmatic-orchestration",
"description": "Multi-agent orchestration for coding agents: run external agent CLIs (Codex, Claude Code, OpenCode, Grok Build, Gemini, Devin CLI) as independent reviewers, stateful repository researchers, or steerable single-agent implementers via the `porch` CLI. Review ask/code modes (read-only), delegate mode (full-access, steerable, detachable), session-history search, and subscription quota checks Ships a second skill, remote-agents, for provisioning and operating dedicated remote Linux/Windows agent hosts.",
"version": "2.0.0-beta.2",
"version": "2.0.0-beta.3",
"author": {
"name": "CodeAlive-AI",
"email": "contact@codealive.ai"
Expand Down
29 changes: 25 additions & 4 deletions skills/pragmatic-orchestration/scripts/lib/backend_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ unset _RESOLVE_ARGS
if [[ -n "$RESOLVED_JSON" ]]; then
eval "$(
RESOLVED_JSON="$RESOLVED_JSON" python3 - <<'PY'
import json, os, shlex
import json, os, shlex, sys
# Native Windows Python must not add CR characters to shell assignments.
sys.stdout.reconfigure(newline='\n')
d = json.loads(os.environ["RESOLVED_JSON"])
def emit(k, v):
print(f"{k}={shlex.quote('' if v is None else str(v))}")
Expand Down Expand Up @@ -293,9 +295,28 @@ if [[ -z "$BIN" ]]; then
echo "Error: unknown backend '$BACKEND' for agent $AGENT_ID" >&2
exit $EXIT_CONFIG_ERROR
fi
if ! command -v "$BIN" &>/dev/null && [[ ! -x "$BIN" ]]; then
echo "Error: backend CLI not found: $BIN (backend=$BACKEND)" >&2
exit $EXIT_CONFIG_ERROR
bin_usable() {
command -v "$1" &>/dev/null || [[ -x "$1" ]]
}
if ! bin_usable "$BIN"; then
# Native Windows Python can resolve an npm .CMD shim that MSYS bash cannot
# execute. Use its extensionless sibling, keeping the resolved directory
# (and any explicit binary override) authoritative.
_bin_fallback=""
if [[ "$BIN" == *[/\\]* && "$BIN" == *.[cC][mM][dD] ]]; then
_bin_fallback="${BIN%.*}"
_bin_fallback="${_bin_fallback//\\//}"
if command -v cygpath &>/dev/null; then
_bin_fallback="$(cygpath -u "$_bin_fallback" 2>/dev/null)" || _bin_fallback=""
fi
fi
if [[ -n "$_bin_fallback" && -x "$_bin_fallback" ]]; then
BIN="$_bin_fallback"
else
echo "Error: backend CLI not found: $BIN (backend=$BACKEND)" >&2
exit $EXIT_CONFIG_ERROR
fi
unset _bin_fallback
fi

# Codex model alias + default efforts are already applied by backend_contract
Expand Down
87 changes: 87 additions & 0 deletions skills/pragmatic-orchestration/scripts/tests/platform_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import os
import pkgutil
import shutil
import subprocess
import sys
import tempfile
Expand Down Expand Up @@ -46,6 +47,92 @@ def test_all_modules_import(self):
importlib.import_module(module.name)
importlib.import_module("terminal_guard")

def test_backend_shell_assignments_are_lf_only_and_preserve_values(self):
bash = shutil.which("bash")
if bash is None:
self.skipTest("backend shell assignments require Bash")
# Exercise the runner's actual emitter with native Windows-style
# stdout on every platform; text=True would hide the CRLF regression.
source = (LIB / "backend_run.sh").read_text(encoding="utf-8")
marker = 'RESOLVED_JSON="$RESOLVED_JSON" python3 - <<\'PY\'\n'
self.assertEqual(source.count(marker), 1)
emitter = source.split(marker, 1)[1].split("\nPY\n", 1)[0]
resolved = {
"backend": "codex-cli",
"model": "model 'quoted' $HOME ü",
"effort": "high",
"label": "Windows test",
"role": "analyst",
"binary": r"C:\Users\Test User ü\npm\codex.CMD",
"review_instructions": "Use 'quotes' and $HOME literally.\nSecond line.",
"access_class": "readonly",
}
result = subprocess.run(
[sys.executable, "-c",
"import sys\nsys.stdout.reconfigure(newline='\\r\\n')\n" + emitter],
env=dict(self.env, RESOLVED_JSON=json.dumps(resolved)),
capture_output=True, timeout=15,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertNotIn(b"\r", result.stdout)
shell = subprocess.run(
[bash, "--noprofile", "--norc", "-c",
'set -eu; eval "$(cat)"; printf "%s\\0" '
'"$BACKEND" "$MODEL" "$EFFORT" "$LABEL" "$ROLE_ID" '
'"$BIN" "$REVIEW_INSTRUCTIONS" "$ACCESS_POLICY"'],
input=result.stdout, env=self.env, capture_output=True, timeout=15,
)
self.assertEqual(shell.returncode, 0, shell.stderr)
self.assertEqual(shell.stdout.decode("utf-8").split("\0"),
[*resolved.values(), ""])

def test_backend_cmd_shim_uses_only_its_extensionless_sibling(self):
bash = shutil.which("bash")
if bash is None:
self.skipTest("backend shim resolution requires Bash")
source = (LIB / "backend_run.sh").read_text(encoding="utf-8")
marker = "bin_usable() {\n"
self.assertEqual(source.count(marker), 1)
resolver = marker + source.split(marker, 1)[1].split(
"\n# Codex model alias", 1)[0]
launcher = self.root / "shimcodex"
launcher.write_bytes(b"#!/bin/sh\nprintf '%s\\n' 'CMD_SHIM_OK'\n")
launcher.chmod(0o755)
shim = launcher.with_suffix(".CMD")
shim.write_bytes(b"@echo off\r\n")
shim.chmod(0o644)
missing = self.root / "missing" / shim.name
missing.parent.mkdir()
missing.write_bytes(b"@echo off\r\n")
missing.chmod(0o644)
script = 'set -eu\nEXIT_CONFIG_ERROR=4\nBACKEND=codex-cli\n' + resolver + '\n"$BIN"\n'
# A same-named working launcher on PATH must not replace an explicitly
# selected installation whose extensionless sibling is missing.
env = dict(self.env, PATH=str(self.root) + os.pathsep + self.env["PATH"])
for name, binary, expected_rc in (
("cmd sibling", shim, 0),
("missing sibling", missing, 4),
("non-cmd extension", launcher.with_suffix(".exe"), 4),
):
with self.subTest(name=name):
if os.name == "nt" and binary.suffix == ".CMD":
# Keep native Python's drive/backslash spelling so the
# production resolver exercises cygpath under Git Bash.
binary_path = shutil.which(str(binary))
self.assertIsNotNone(binary_path)
else:
binary_path = str(binary).replace("/", "\\")
result = subprocess.run(
[bash, "--noprofile", "--norc", "-c", script],
env=dict(env, BIN=binary_path), capture_output=True, timeout=15,
)
self.assertEqual(result.returncode, expected_rc, result.stderr)
if expected_rc == 0:
self.assertEqual(result.stdout, b"CMD_SHIM_OK\n")
else:
self.assertIn(b"backend CLI not found", result.stderr)
self.assertNotIn(b"CMD_SHIM_OK", result.stdout)

def test_steer_guidance_after_flags_and_root_option(self):
from steer import control
for tail in (["guidance text", "--mode", "queue"],
Expand Down
59 changes: 59 additions & 0 deletions skills/pragmatic-orchestration/scripts/tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,65 @@ xml=$(CLAUDE_MODEL="claude-runtime" CLAUDE_EFFORT="max" \
assert_contains "ask XML uses resolved model" "$xml" 'model="claude-runtime"'
assert_contains "ask XML uses resolved effort" "$xml" 'effort="max"'

echo "=== Windows .cmd shim binary resolution ==="
# On Windows the contract resolves binaries with Python's shutil.which, which
# walks PATHEXT and returns the `foo.CMD` sibling spelled with backslashes. npm
# writes that shim without the executable bit, so bash can neither `command -v`
# the path nor exec-test it, and every npm-installed backend was rejected with a
# false "backend CLI not found". Reproduced here on any platform by pointing the
# override at such a path; the extensionless launcher is the usable one.
SHIM_DIR="$TMP/cmd-shim"
mkdir -p "$SHIM_DIR"
cp "$FAKES/fake-codex" "$SHIM_DIR/shimcodex"
chmod +x "$SHIM_DIR/shimcodex"
printf '@echo off\r\n' > "$SHIM_DIR/shimcodex.cmd"
chmod 644 "$SHIM_DIR/shimcodex.cmd"

export PORCH_RUN_DIR="$TMP/run-ask-cmd-shim"
mkdir -p "$PORCH_RUN_DIR"
set +e
out=$(PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="$SHIM_DIR\\shimcodex.CMD" \
"$PORCH" review ask --progress none -a codex "review this" \
2>"$TMP/ask-cmd-shim.err")
set -e
assert_contains "windows .cmd path falls back to extensionless launcher" "$out" "FAKE_CODEX_OK"
assert_not_contains "windows .cmd path is not reported missing" \
"$(cat "$TMP/ask-cmd-shim.err")" "backend CLI not found"

# A bare name that resolves to nothing must still fail; the fallback only
# applies to values that look like a resolved path.
set +e
PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="definitely-absent-cli" \
"$PORCH" review ask --progress none -a codex "review this" \
>"$TMP/ask-absent-bin.out" 2>"$TMP/ask-absent-bin.err"
absent_bin_rc=$?
set -e
assert_eq "missing bare binary still fails" "$absent_bin_rc" "3"

# A missing .CMD path must not pick a different installation from PATH.
set +e
PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="$TMP/missing/shimcodex.CMD" \
PORCH_RUN_DIR="$TMP/run-ask-missing-cmd" \
"$PORCH" review ask --progress none -a codex "review this" \
>"$TMP/ask-missing-cmd.out" 2>"$TMP/ask-missing-cmd.err"
missing_cmd_rc=$?
set -e
assert_eq "missing .CMD sibling fails instead of searching PATH" "$missing_cmd_rc" "3"
assert_not_contains "missing .CMD sibling does not run another CLI" \
"$(cat "$TMP/ask-missing-cmd.out")" "FAKE_CODEX_OK"

# Other extensions do not imply an npm shim, even when a sibling exists.
set +e
PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="$SHIM_DIR/shimcodex.exe" \
PORCH_RUN_DIR="$TMP/run-ask-missing-exe" \
"$PORCH" review ask --progress none -a codex "review this" \
>"$TMP/ask-missing-exe.out" 2>"$TMP/ask-missing-exe.err"
missing_exe_rc=$?
set -e
assert_eq "missing non-.CMD binary still fails" "$missing_exe_rc" "3"
assert_not_contains "missing non-.CMD binary does not run sibling" \
"$(cat "$TMP/ask-missing-exe.out")" "FAKE_CODEX_OK"

echo "=== Delegate with fake ==="
export PORCH_RUN_DIR="$TMP/run-del"
mkdir -p "$PORCH_RUN_DIR"
Expand Down
Loading