task: rename agent-pi → client-slack tmux session + restore on restart

- Add `client-` prefix to sandbox-processes.md naming table for
  external-surface clients bridging an in-sandbox agent.
- Tighten the `agent-` example off interactive CLIs; document that
  claude/codex/opencode are foreground tools, not tmux sessions.
- entrypoint.sh: restore client-slack on container start when
  SLACK_APP_TOKEN + SLACK_BOT_TOKEN are present and `pi` is installed.
- Propagate rename through wizard code, READMEs, and docs.
- Fold in half-finished harness-pi rename from #302.

Closes #344

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryaneggzandClaude Opus 4.7 committed 2026-05-23 20:39:51 +00:00
1 parent a75200d272
commit b17242590c
9 files changed
+64 -38

No files matched your search

+23
View File
@@ -208,6 +208,29 @@ if [ -f "$HARNESS/scripts/cron-runtime.ts" ] && command -v tmux &>/dev/null; the
fi
fi
# ─── Restore client-slack session if Slack is configured ──────────────
# Tokens live in .devcontainer/.env (Compose KEY=value format). When both
# SLACK_APP_TOKEN and SLACK_BOT_TOKEN are set AND `pi` is installed for
# the sandbox user, restore the session that `oh config slack` created.
# Sourcing happens inside the tmux command (same pattern the wizard uses)
# so the child `pi` process inherits the exported vars.
SLACK_ENV="$HARNESS/.devcontainer/.env"
if [ -f "$SLACK_ENV" ] && command -v tmux &>/dev/null; then
# shellcheck disable=SC1090
SLACK_APP_TOKEN=$(grep -E '^SLACK_APP_TOKEN=' "$SLACK_ENV" | tail -1 | cut -d= -f2-)
SLACK_BOT_TOKEN=$(grep -E '^SLACK_BOT_TOKEN=' "$SLACK_ENV" | tail -1 | cut -d= -f2-)
if [ -n "$SLACK_APP_TOKEN" ] && [ -n "$SLACK_BOT_TOKEN" ] \
&& gosu sandbox bash -lc 'command -v pi' &>/dev/null; then
if ! gosu sandbox tmux has-session -t client-slack 2>/dev/null; then
gosu sandbox tmux new-session -d -s client-slack \
"bash -c 'set -a; source $SLACK_ENV; set +a; pi 2>&1 | tee /tmp/client-slack.log'"
echo "[entrypoint] client-slack tmux session started (Slack bridge)"
else
echo "[entrypoint] client-slack tmux session already running — skipping"
fi
fi
fi
# ─── Optional: agent-browser (opt-in via INSTALL_AGENT_BROWSER=true) ──
if [ "${INSTALL_AGENT_BROWSER:-false}" = "true" ] && ! command -v agent-browser &>/dev/null; then
echo "[entrypoint] Installing agent-browser (INSTALL_AGENT_BROWSER=true)..."
+3 -3
View File
@@ -74,8 +74,8 @@ SLACK_ALLOW_USERS=U789
```bash
make shell
set -a; source /home/sandbox/harness/.devcontainer/.env; set +a
tmux new-session -d -s agent-pi 'pi 2>&1 | tee /tmp/agent-pi.log'
tmux attach -t agent-pi
tmux new-session -d -s client-slack 'pi 2>&1 | tee /tmp/client-slack.log'
tmux attach -t client-slack
```
`set -a` is needed because `.devcontainer/.env` is Compose-formatted (no `export` prefix); without it, sourced vars stay in the shell but aren't inherited by `pi`. The tmux session keeps `pi` alive across SSH/VS Code disconnects (see [`context/rules/sandbox-processes.md`](../../context/rules/sandbox-processes.md)).
@@ -86,7 +86,7 @@ SLACK_ALLOW_USERS=U789
```bash
env | grep SLACK # confirm vars are exported
```
Then DM the bot or `@mention` it from an allow-listed user. Watch the round-trip in `tmux attach -t agent-pi` or `tail -f /tmp/agent-pi.log`.
Then DM the bot or `@mention` it from an allow-listed user. Watch the round-trip in `tmux attach -t client-slack` or `tail -f /tmp/client-slack.log`.
## Build and Test
+1
View File
@@ -20,6 +20,7 @@ Update policy and release automation live in [`.claude/rules/git.md`](.claude/ru
- `README.md`, `docs/installation.md`, and `docs/quickstart.md` restructured to surface fork-and-clone and clone-and-own as first-class install paths alongside the upstream one-liner; `scripts/install.sh` local-run detection is now the documented bootstrap for self-hosters ([#331](https://github.com/ryaneggz/open-harness/issues/331)).
- Skill `LICENSE` files updated to `Copyright (c) 2026 Mifune Dev (mifune.dev)` to match upstream correction.
- Re-synced `context-audit` to the portable upstream form ([mifunedev/skills#6](https://github.com/mifunedev/skills/pull/6)): removed the top-level `argument-hint` frontmatter key, repinned `.mifune/skills.lock` (`commit` `f95011fe`, checksum `02dd6324`), and added the previously-missing `LICENSE` for folder parity ([#339](https://github.com/ryaneggz/open-harness/issues/339)).
- Renamed the Slack-bridge tmux session from `agent-pi` to `client-slack` and introduced a `client-` prefix in `context/rules/sandbox-processes.md` for external-surface clients (Slack, Discord, web UIs) that bridge an in-sandbox agent. `.devcontainer/entrypoint.sh` now restores the `client-slack` session on container start whenever `SLACK_APP_TOKEN` and `SLACK_BOT_TOKEN` are present in `.devcontainer/.env` and `pi` is installed, so the Slack bridge survives `docker compose restart` without manual relaunch. Rename propagated through `apps/oh/src/config/slack.ts` (`oh config slack` wizard), `.pi/extensions/slack/README.md`, `docs/integrations/slack.md`, `docs/connecting.md`, `docs/intro.md`, and `docs/harnesses/overview.md` (the latter folding in the half-finished `harness-pi` rename from #302).
### Fixed
- Sandbox image installs `pi-coding-agent` under the sandbox user's npm prefix (`/home/sandbox/.local`) and places that bin directory before `/usr/bin`, allowing `pi update` to self-update without sudo on fresh builds.
### Removed
+18 -18
View File
@@ -10,7 +10,7 @@ import {
hasSession, killSession, newSession, capturePane, isInstalled as tmuxAvailable,
} from "../lib/tmux.js";
const AGENT_PI_SESSION = "agent-pi";
const CLIENT_SLACK_SESSION = "client-slack";
const CONNECTED_MARKER = "connected and listening";
const POLL_INTERVAL_MS = 1000;
const MAX_POLLS = 15;
@@ -143,37 +143,37 @@ async function promptAllowlist(env: Record<string, string | undefined>): Promise
return result;
}
async function relaunchAgentPi(envPath: string): Promise<void> {
async function relaunchClientSlack(envPath: string): Promise<void> {
if (!tmuxAvailable()) {
warn("tmux not found in PATH — skipping agent-pi restart. Restart it manually after this exits.");
warn("tmux not found in PATH — skipping client-slack restart. Restart it manually after this exits.");
return;
}
const wasRunning = hasSession(AGENT_PI_SESSION);
const wasRunning = hasSession(CLIENT_SLACK_SESSION);
if (wasRunning) {
info(`Killing existing tmux session "${AGENT_PI_SESSION}"…`);
killSession(AGENT_PI_SESSION);
info(`Killing existing tmux session "${CLIENT_SLACK_SESSION}"…`);
killSession(CLIENT_SLACK_SESSION);
}
info(`Starting tmux session "${AGENT_PI_SESSION}"…`);
info(`Starting tmux session "${CLIENT_SLACK_SESSION}"…`);
// The session sources .env via `set -a` so child pi inherits the new vars.
const cmd = `bash -c 'set -a; source ${envPath}; set +a; pi 2>&1 | tee /tmp/agent-pi.log'`;
newSession(AGENT_PI_SESSION, cmd);
const cmd = `bash -c 'set -a; source ${envPath}; set +a; pi 2>&1 | tee /tmp/client-slack.log'`;
newSession(CLIENT_SLACK_SESSION, cmd);
info("Validating Slack connection (up to 15s)…");
for (let i = 0; i < MAX_POLLS; i++) {
const out = capturePane(AGENT_PI_SESSION);
const out = capturePane(CLIENT_SLACK_SESSION);
if (out.includes(CONNECTED_MARKER)) {
ok("Slack extension connected.");
return;
}
if (/Run error|Missing env/.test(out)) {
fail("Slack extension reported an error. Check `tmux attach -t agent-pi`.");
fail("Slack extension reported an error. Check `tmux attach -t client-slack`.");
return;
}
await new Promise<void>((r) => setTimeout(r, POLL_INTERVAL_MS));
process.stdout.write(".");
}
process.stdout.write("\n");
warn("Did not see 'connected and listening' within 15s. Check `tmux attach -t agent-pi`.");
warn("Did not see 'connected and listening' within 15s. Check `tmux attach -t client-slack`.");
}
export async function runSlack(): Promise<number> {
@@ -231,20 +231,20 @@ export async function runSlack(): Promise<number> {
return 1;
}
info("Next step: start the Slack bridge by (re)launching the agent-pi tmux session.");
info("This kills any existing agent-pi session, then starts pi with the new tokens;");
info("Next step: start the Slack bridge by (re)launching the client-slack tmux session.");
info("This kills any existing client-slack session, then starts pi with the new tokens;");
info("pi loads the slack extension on boot and opens the Socket Mode connection.");
if (await confirm("Start the Slack bridge now?")) {
await relaunchAgentPi(envPath);
await relaunchClientSlack(envPath);
info("");
info("Slack bridge is live. Test it: DM your bot or @mention it in an allow-listed channel.");
info("Tail the live log: tmux attach -t agent-pi (detach: Ctrl-b d)");
info("Tail the live log: tmux attach -t client-slack (detach: Ctrl-b d)");
} else {
info("Skipped. Start the bridge manually when ready:");
info(" tmux kill-session -t agent-pi 2>/dev/null; \\");
info(" tmux kill-session -t client-slack 2>/dev/null; \\");
info(` set -a; source ${envPath}; set +a; \\`);
info(" tmux new-session -d -s agent-pi 'pi 2>&1 | tee /tmp/agent-pi.log'");
info(" tmux new-session -d -s client-slack 'pi 2>&1 | tee /tmp/client-slack.log'");
}
return 0;
+2 -1
View File
@@ -18,7 +18,8 @@ Format: `<category>-<identifier>` (kebab-case inside each segment).
|----------|---------|---------|
| `app-` | `app-docs`, `app-api` | User dev servers |
| `expose-public-` | `expose-public-3000` | Cloudflare quick tunnels (auto-created) |
| `agent-` | `agent-claude`, `agent-pi` | AI agent processes |
| `agent-` | `agent-watcher`, `agent-batch` | Headless / long-running agent processes. Interactive CLIs (`claude`, `codex`, `opencode`) are normally foreground in a terminal or VS Code, not detached in tmux. |
| `client-` | `client-slack`, `client-discord` | External-surface clients that bridge an in-sandbox agent to a third-party UI |
| `heartbeat-` | `heartbeat-daily-sync` | Scheduled heartbeat tasks |
Reserved prefix: `system-`. Do not use for user apps.
+4 -3
View File
@@ -93,7 +93,8 @@ All long-running processes inside the sandbox run in named tmux sessions. The na
| Category | Example | Purpose |
|----------|---------|---------|
| `agent-` | `agent-claude`, `agent-pi` | AI agent processes |
| `client-` | `client-slack`, `client-discord` | External-surface clients bridging an in-sandbox agent |
| `agent-` | `agent-watcher`, `agent-batch` | Headless / long-running agent processes (interactive CLIs are foreground, not tmux) |
| `app-` | `app-docs`, `app-api` | Dev servers |
For the full convention see [`context/rules/sandbox-processes.md`](https://github.com/ryaneggz/open-harness/blob/development/context/rules/sandbox-processes.md).
@@ -113,12 +114,12 @@ Follow Option B (or C for a remote host). The Ports panel in VSCode shows forwar
oh config slack
```
The interactive wizard collects your Slack tokens, validates them, writes `.devcontainer/.env`, and restarts the `agent-pi` tmux session in Socket Mode. For the full wizard walkthrough see [Integrations → Slack](/docs/integrations/slack).
The interactive wizard collects your Slack tokens, validates them, writes `.devcontainer/.env`, and restarts the `client-slack` tmux session in Socket Mode. For the full wizard walkthrough see [Integrations → Slack](/docs/integrations/slack).
After the wizard completes, verify the bridge is live:
```bash
tmux capture-pane -t agent-pi -p | grep -i 'connected\|listening'
tmux capture-pane -t client-slack -p | grep -i 'connected\|listening'
```
### Step 3 — Launch T3 Code
+2 -2
View File
@@ -54,11 +54,11 @@ Each runs in its own named tmux session per [`context/rules/sandbox-processes.md
### Pi+Slack
The Pi agent with the Slack bridge loaded. The wizard writes `.devcontainer/.env`, launches the `harness-pi` session, and waits for the Socket Mode connection to come up:
The Pi agent with the Slack bridge loaded. The wizard writes `.devcontainer/.env`, launches the `client-slack` session, and waits for the Socket Mode connection to come up:
```bash
oh config slack # interactive wizard
tmux attach -t harness-pi # watch the live log
tmux attach -t client-slack # watch the live log
```
Talk to the agent from Slack (DM or `@mention` in an allow-listed channel). Full setup: [Slack integration](../integrations/slack.md).
+10 -10
View File
@@ -69,7 +69,7 @@ Keep both values ready for the next step.
Inside the sandbox, run the wizard. It prompts for the two tokens, asks
which allowlist mode to use (users / channels / both), validates IDs,
writes `.devcontainer/.env` atomically, and offers to restart the
`agent-pi` tmux session.
`client-slack` tmux session.
```bash
make shell # enter the sandbox
@@ -84,7 +84,7 @@ The wizard:
- Writes only the Slack keys; unrelated entries (`GH_TOKEN`, `TZ`, etc.)
are preserved.
- **Starts the Slack bridge for you** — at the end, it kills any
existing `agent-pi` tmux session and launches a fresh one running
existing `client-slack` tmux session and launches a fresh one running
`pi` with the new env sourced. `pi` loads the Slack extension on boot
and opens the Socket Mode connection. The wizard polls for the
`"connected and listening"` log line and prints `✓ Slack bridge is live`
@@ -99,7 +99,7 @@ If `oh` isn't available (older sandbox image, or you'd rather edit by
hand), the manual procedure still works:
<details>
<summary>Hand-edit <code>.devcontainer/.env</code> + relaunch <code>agent-pi</code></summary>
<summary>Hand-edit <code>.devcontainer/.env</code> + relaunch <code>client-slack</code></summary>
`.devcontainer/.env` uses Docker Compose `KEY=value` format — no `export`
prefix. This file is gitignored, so your tokens will not be committed.
@@ -126,11 +126,11 @@ vars. This is the most common manual-mode failure.
```bash
set -a; source /home/sandbox/harness/.devcontainer/.env; set +a
tmux new-session -d -s agent-pi 'pi 2>&1 | tee /tmp/agent-pi.log'
tmux attach -t agent-pi
tmux new-session -d -s client-slack 'pi 2>&1 | tee /tmp/client-slack.log'
tmux attach -t client-slack
```
Detach with `Ctrl-b d`. The session name `agent-pi` follows the `agent-`
Detach with `Ctrl-b d`. The session name `client-slack` follows the `client-`
prefix convention in
[`context/rules/sandbox-processes.md`](https://github.com/ryaneggz/open-harness/blob/development/context/rules/sandbox-processes.md).
@@ -151,7 +151,7 @@ the env (before attaching to tmux).
2. **Socket Mode connected** (the real connectivity check):
```bash
tmux capture-pane -t agent-pi -p | grep -i 'socket\|connected\|listening'
tmux capture-pane -t client-slack -p | grep -i 'socket\|connected\|listening'
```
Look for lines indicating the Socket Mode client connected or is listening.
Note: `curl https://slack.com/api/auth.test` only validates the bot token
@@ -161,17 +161,17 @@ the env (before attaching to tmux).
3. **Round-trip test:**
From an allow-listed user account, DM the bot or `@mention` it in an
allow-listed channel. Watch `tmux attach -t agent-pi` — you should see the
allow-listed channel. Watch `tmux attach -t client-slack` — you should see the
inbound event logged and the agent's reply posted back to Slack.
## 7. Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| Bot doesn't respond; allowlist is set | `pi` did not inherit the vars — `set -a` ran in a different shell than `tmux new-session` | Kill the session (`tmux kill-session -t agent-pi`), run `set -a; source …; set +a`, then relaunch `tmux new-session` in the same shell |
| Bot doesn't respond; allowlist is set | `pi` did not inherit the vars — `set -a` ran in a different shell than `tmux new-session` | Kill the session (`tmux kill-session -t client-slack`), run `set -a; source …; set +a`, then relaunch `tmux new-session` in the same shell |
| Bot doesn't respond; no allowlist set | Deny-default: both `SLACK_ALLOW_CHANNELS` and `SLACK_ALLOW_USERS` are unset | Add at least one allowlist var to `.devcontainer/.env` and relaunch |
| `invalid_auth` or `not_authed` in log | `xapp-` and `xoxb-` tokens are swapped | `SLACK_APP_TOKEN` must be the `xapp-` token; `SLACK_BOT_TOKEN` must be the `xoxb-` token — correct in `.devcontainer/.env` and relaunch |
| Socket Mode crashes / reconnects in a loop | Network interruption or token revoked | `tmux kill-session -t agent-pi`, verify tokens in `.devcontainer/.env`, then relaunch |
| Socket Mode crashes / reconnects in a loop | Network interruption or token revoked | `tmux kill-session -t client-slack`, verify tokens in `.devcontainer/.env`, then relaunch |
| Bot is in allow-list but channel messages ignored | Bot is not a member of the channel | In Slack, type `/invite @OpenHarness` in the target channel |
## 8. Architecture Pointer
+1 -1
View File
@@ -39,7 +39,7 @@ flowchart TB
subgraph sandbox["Sandbox container — default workspace"]
Orch{{"<b>Orchestrator</b><br/>Claude @ project root<br/>git · lifecycle · file edits"}}
Tmux["tmux sessions<br/>agent-pi · agent-t3code · app-docs · system-cron"]
Tmux["tmux sessions<br/>client-slack · agent-t3code · app-docs · system-cron"]
Sock(["docker.sock"])
end