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:
1 parent
a75200d272
commit
b17242590c
9 files changed
+64
-38
No files matched your search
@@ -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)..."
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user