Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2f1da2d
docs(orchestrator): design for Python-canonical launcher rewrite
evannadeau May 13, 2026
2eb4a08
docs(orchestrator): implementation plan for Python-canonical launcher…
evannadeau May 13, 2026
4eeea44
test(orchestrator): scaffold pytest harness for launcher unit tests
evannadeau May 13, 2026
8c2e0cd
feat(orchestrator): _launcher_common.py — version guard + marketplace…
evannadeau May 13, 2026
09f384e
feat(orchestrator): _launcher_common — project-hash transform
evannadeau May 13, 2026
f465945
feat(orchestrator): _launcher_common — resolve_project_dir
evannadeau May 13, 2026
dd0128c
feat(orchestrator): _launcher_common — resolve_resume_target
evannadeau May 13, 2026
f373ef5
feat(orchestrator): _launcher_common — supersede_existing_pa
evannadeau May 13, 2026
e60ea63
feat(orchestrator): _launcher_common — make_session_name
evannadeau May 13, 2026
de29aae
feat(orchestrator): _launcher_common — setup_env
evannadeau May 13, 2026
43a1385
feat(orchestrator): _launcher_common — build_claude_args
evannadeau May 13, 2026
78b36fb
feat(orchestrator): _launcher_common — launch (terminal-spawn abstrac…
evannadeau May 13, 2026
5f281ae
feat(orchestrator): pa_start.py entry point
evannadeau May 13, 2026
89c283b
feat(orchestrator): sa_start.py entry point
evannadeau May 13, 2026
dd9a14f
feat(orchestrator): discord_start.py entry point
evannadeau May 13, 2026
5909b59
feat(orchestrator): POSIX wrappers for Python launchers
evannadeau May 13, 2026
6ef6076
feat(orchestrator): replace .ps1 launchers with thin Python wrappers
evannadeau May 13, 2026
860891a
docs(orchestrator): update install-launchers SKILL.md for Python laun…
evannadeau May 13, 2026
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
2 changes: 2 additions & 0 deletions plugins/orchestrator/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_modules/
*.db-wal
*.db-shm
.sidecar-port
__pycache__/
.pytest_cache/

Large diffs are not rendered by default.

3,309 changes: 3,309 additions & 0 deletions plugins/orchestrator/docs/plans/2026-05-13-launcher-python-canonical-plan.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions plugins/orchestrator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build": "bun build mcp/server.ts --outdir dist --target bun",
"dev": "bun run mcp/server.ts",
"test": "bun test",
"test:py": "uvx --from pytest pytest tests/launchers/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
Expand Down
13 changes: 13 additions & 0 deletions plugins/orchestrator/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[project]
name = "orchestrator-launchers"
version = "0.0.0"
description = "Python launcher implementation for the orchestrator plugin. Stdlib only — no runtime dependencies."
requires-python = ">=3.10"

[project.optional-dependencies]
dev = ["pytest>=8"]

[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests/launchers"]
addopts = ["-ra", "--strict-markers"]
195 changes: 141 additions & 54 deletions plugins/orchestrator/skills/install-launchers/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,46 @@ description: Use when setting up the orchestrator plugin in a new project (or re

## Overview

The orchestrator plugin ships canonical PowerShell launchers for spawning
Claude Code sessions wired into the agent-channel. These launchers must
live in the user's project root (not the plugin's `bin/`) because the
user invokes them from their OS terminal to spawn NEW Claude sessions -
and Claude Code's plugin `bin/` PATH only applies inside an
already-running Claude session.
The orchestrator plugin ships canonical Python launchers (with thin
platform wrappers) for spawning Claude Code sessions wired into the
agent-channel. These launchers must live in the user's project root
(not the plugin's `bin/`) because the user invokes them from their OS
terminal to spawn NEW Claude sessions - and Claude Code's plugin `bin/`
PATH only applies inside an already-running Claude session.

Three launcher kinds ship today:

| Launcher | Role | Channels attached | Tab color |
|---|---|---|---|
| `pa-start` | PrimeAgent (prime) | orchestrator | gold (#F59E0B) |
| `sa-start` | Subordinate (subordinate) | orchestrator | default |
| `discord-start` | Discord-ops (subordinate) | orchestrator + Discord | red (#DC2626) |

This skill copies SIX files into the user's CWD (three `.ps1` +
three `.bat` shims) and substitutes the marketplace slug into the
copies so they reference the right `plugin:orchestrator@<marketplace>`
for `--dangerously-load-development-channels`.
| Launcher | Role / Kind | Channels attached | Tab color | Files installed |
|---|---|---|---|---|
| `pa-start` | role=prime, kind=prime | orchestrator | gold (#F59E0B) | `pa_start.py` + `pa-start.sh` + `pa-start.ps1` + `pa-start.bat` |
| `sa-start` | role=subordinate, kind=subordinate | orchestrator | default | `sa_start.py` + `sa-start.sh` + `sa-start.ps1` + `sa-start.bat` |
| `discord-start` | role=subordinate, kind=discord-bot | orchestrator + Discord | red (#DC2626) | `discord_start.py` + `discord-start.sh` + `discord-start.ps1` + `discord-start.bat` |

All three launchers share a single canonical Python implementation
(`_launcher_common.py`), which is the 13th file installed.

This skill copies THIRTEEN files into the user's CWD (one shared
Python module + three Python entry-point modules + three POSIX
wrappers + three Windows PowerShell wrappers + three Windows batch
trampolines) and substitutes the marketplace slug into the shared
Python module so it references the right
`plugin:orchestrator@<marketplace>` for
`--dangerously-load-development-channels`.

## Prerequisites

- **Python 3.10 or newer** must be installed and on PATH.
- Linux/WSL: `sudo apt install python3` (Ubuntu 22.04+ ships 3.10+).
- macOS: `brew install python@3.12`.
- Windows: `winget install Python.Python.3.12`, the python.org
installer, OR the real Microsoft Store "Python 3.x" app (NOT the
App Execution Alias stub that just prints "Python was not found").
- The wrappers also honor `$ORCH_PYTHON` / `$env:ORCH_PYTHON` if you
want to point at a specific interpreter.

Python is already a baseline dependency of the orchestrator plugin
via `sidecar/embed_server.py` and `sidecar/requirements.txt`. This
skill does not add a new dependency.

## When to use

Expand Down Expand Up @@ -82,49 +103,102 @@ echo "Marketplace: $MARKETPLACE"
If `MARKETPLACE` is empty, ask the user for the correct marketplace slug
(visible via `/plugin marketplace list`) before proceeding.

### 4. Copy + substitute the six files

The source `.ps1` files contain the literal token `__ORCH_MARKETPLACE__`
where the orchestrator marketplace slug needs to be. Copy each file and
replace the token in-place:
### 4. Copy files into the project root

```bash
for f in pa-start.ps1 pa-start.bat sa-start.ps1 sa-start.bat discord-start.ps1 discord-start.bat; do
sed "s|__ORCH_MARKETPLACE__|$MARKETPLACE|g" "$SCRIPTS_DIR/$f" > "$PWD/$f"
echo "Installed $f"
INSTALL_DIR="$PWD"
for f in _launcher_common.py \
pa_start.py sa_start.py discord_start.py \
pa-start.sh sa-start.sh discord-start.sh \
pa-start.ps1 sa-start.ps1 discord-start.ps1 \
pa-start.bat sa-start.bat discord-start.bat; do
cp "$SCRIPTS_DIR/$f" "$INSTALL_DIR/$f"
done
chmod 755 "$INSTALL_DIR/pa-start.sh" \
"$INSTALL_DIR/sa-start.sh" \
"$INSTALL_DIR/discord-start.sh"
```

On Windows the `chmod` step is a no-op; `.sh` files are not executable
there, and `.ps1` / `.bat` files don't need an executable bit.

If any target file already exists at `$INSTALL_DIR/$f`, **ask the user
before overwriting** - they may have a local customization worth
preserving.

### 5. Substitute the marketplace slug

The placeholder `__ORCH_MARKETPLACE__` lives in `_launcher_common.py`
as a module-level constant; substitute it in that single file at copy
time:

```bash
# Linux/macOS/WSL
sed -i.bak "s|__ORCH_MARKETPLACE__|$MARKETPLACE|g" \
"$INSTALL_DIR/_launcher_common.py"
rm "$INSTALL_DIR/_launcher_common.py.bak"
```

```powershell
# Windows
(Get-Content "$InstallDir\_launcher_common.py") `
-replace '__ORCH_MARKETPLACE__', $Marketplace `
| Set-Content "$InstallDir\_launcher_common.py"
```

Verify the substitution by running the marketplace guard:

```bash
python3 -c "import sys; sys.path.insert(0, '$INSTALL_DIR'); \
import _launcher_common; _launcher_common.check_marketplace_substituted()"
```

If any target file already exists at `$PWD/$f`, **ask the user before
overwriting** - they may have a local customization worth preserving.
This is especially relevant for `discord-start.bat`, which may have
been hand-tuned for the user's existing Discord workflow.
If the placeholder wasn't substituted, the script exits 1 with an
actionable error pointing back at this install skill.

### 5. Verify the install
### 6. Verify the install

```bash
ls -la "$PWD"/{pa,sa,discord}-start.{ps1,bat}
grep -l "__ORCH_MARKETPLACE__" "$PWD"/{pa,sa,discord}-start.{ps1,bat} || echo "Substitution complete."
grep -h "plugin:orchestrator@" "$PWD"/pa-start.ps1
ls -la "$INSTALL_DIR"/_launcher_common.py \
"$INSTALL_DIR"/{pa,sa,discord}_start.py \
"$INSTALL_DIR"/{pa,sa,discord}-start.{sh,ps1,bat}
grep -l "__ORCH_MARKETPLACE__" "$INSTALL_DIR/_launcher_common.py" \
|| echo "Substitution complete."
grep -h "MARKETPLACE_PLACEHOLDER" "$INSTALL_DIR/_launcher_common.py" | head -1
```

The first grep should produce no output (the literal token is gone).
The second should print the substituted plugin reference matching the
marketplace slug.
The first listing should show 13 files. The first grep should produce
no output (the literal token is gone). The third should print the
substituted slug.

### 6. Output usage instructions
### 7. Output usage instructions

Print to terminal:

```
Installed orchestrator launchers into <PROJECT_ROOT>. Usage:
.\pa-start.bat Start a new PA (gold tab)
.\pa-start.bat -Resume <uuid-or-name> Resume an existing session as PA
.\sa-start.bat Start a new SA (default tab)
.\sa-start.bat -Name "SA-frontend" Start SA with an explicit name
.\sa-start.bat -Resume <uuid-or-name> Resume an existing session as SA
.\discord-start.bat Start a Discord-ops session (red tab,
both Discord + orchestrator channels)

POSIX (Linux/macOS/WSL):
./pa-start.sh Start a new PA (gold tab on Win)
./pa-start.sh --resume <uuid-or-name> Resume an existing session as PA
./sa-start.sh Start a new SA
./sa-start.sh --name "SA-frontend" Start SA with an explicit name
./sa-start.sh --effort max Start SA at max reasoning effort
./sa-start.sh --resume <uuid-or-name> Resume an existing session as SA
./discord-start.sh Start a Discord-ops session

Windows:
.\pa-start.bat Start a new PA (gold tab)
.\pa-start.bat -Resume <uuid-or-name> (replace -Resume with --resume
when calling the .ps1 directly)
.\sa-start.bat Start a new SA
.\discord-start.bat Start a Discord-ops session

Override the Python interpreter with $ORCH_PYTHON / $env:ORCH_PYTHON
if the default `python3` / `python.exe` resolves to the wrong one.

Use --dry-run on any launcher to print the resolved argv + env without
actually spawning Claude — useful for debugging.
```

## Quick reference
Expand All @@ -134,9 +208,10 @@ Installed orchestrator launchers into <PROJECT_ROOT>. Usage:
| 1 | Confirm `$PWD` is project root | Terminal |
| 2 | Locate scripts dir | `<base-dir>/scripts/` |
| 3 | Extract marketplace slug | `<cache-path>` after `cache/` |
| 4 | Copy + substitute `__ORCH_MARKETPLACE__` (6 files) | `$PWD/*.{ps1,bat}` |
| 5 | Verify no unsubstituted tokens remain | grep check |
| 6 | Print usage | Terminal |
| 4 | Copy 13 files + chmod the 3 .sh | `$PWD/*.{py,sh,ps1,bat}` |
| 5 | Substitute `__ORCH_MARKETPLACE__` in `_launcher_common.py` only | One sed/replace |
| 6 | Verify no unsubstituted tokens remain | grep + guard call |
| 7 | Print usage | Terminal |

## How discord-start differs

Expand All @@ -162,29 +237,41 @@ own `--channels` arg).

## Common mistakes

- **Forgetting the substitution step**: copying the raw `.ps1` files
- **Forgetting the substitution step**: copying the raw `_launcher_common.py`
with the literal `__ORCH_MARKETPLACE__` placeholder will produce
launchers that fail with "plugin not found in marketplace
'__ORCH_MARKETPLACE__'". Always run the `sed` step.
launchers that fail with the guard's "marketplace slug not
substituted" error on first run. Always run the substitution.
- **Forgetting `chmod 755` on the .sh wrappers**: POSIX shells refuse
to exec non-executable scripts. Without the chmod, users see
"Permission denied".
- **Picking the wrong cache version**: if the user has multiple
installed versions, `sort | tail -1` may not be what they want. If
uncertain, look at `/plugin` for the active version and use that
path.
- **Overwriting customized launchers silently**: if `$PWD/pa-start.ps1`
already exists, ask before clobbering. The user may have local edits.
- **Overwriting customized launchers silently**: if installed files
already exist, ask before clobbering. The user may have local edits.
- **Running outside the project root**: the skill anchors install to
`$PWD`. If the user runs it from a parent or sibling directory, the
launchers land in the wrong place. Always confirm `$PWD` in step 1.
- **Python interpreter mismatch on Windows**: if `python.exe` resolves
to the Microsoft Store App Execution Alias stub instead of a real
Python install, the wrapper's stub-detection (`Python was not
found` output match) catches it and exits 127. Install a real Python
(winget / python.org / the real MS Store Python 3.x app).

## Notes

- Re-running this skill after a `/plugin update orchestrator` is the
right way to pick up launcher improvements. The installed copies are
static; they don't auto-update with the plugin.
- The launchers themselves are project-agnostic: they use `$PWD` (or
an explicit `-ProjectDir` parameter) as the project root, set
an explicit `--project-dir` flag) as the project root, set
`ORCHESTRATOR_PROJECT_ROOT` env for the spawned MCP, and work in
any project where the orchestrator plugin is installed.
- The `__ORCH_MARKETPLACE__` placeholder makes the source scripts
portable across marketplace slugs; only the COPIED versions in each
project root are slug-specific.
portable across marketplace slugs; only the COPIED `_launcher_common.py`
in each project root is slug-specific. Entry-point and wrapper files
are identical across all installs.
- For testing the launcher logic without spawning Claude, use the
`--dry-run` flag on any entry point. It prints the JSON envelope
describing the resolved argv + env_overrides + tab_color + use_wt.
Loading