feat: per-change-block hunk actions (#50), codex lanes without a pipeline, macOS LaunchAgent
- git (#50): stage-hunk/discard-hunk take an optional `block` — one run of consecutive +/- lines inside an @@ hunk. `onlyBlock` (server/lib/git-diff.js) cuts git's fresh hunk down to it, folding the other blocks into the side being checked; LaneDiffView hangs one review bar per block (unified + split) and its arrows walk blocks. - git exclude: entries AgentHub writes for a lane go to the COMMON git dir with the lane's path prefix (server/lib/git-exclude.js, used by router-apply and lane-mcp) — a linked worktree's own info/exclude is never read by git, and a subdir lane has no .git. MCP sync no longer fails with ENOTDIR on a worktree lane or creates a stray .git in a non-repo lane. - codex lanes carry no pipeline: codex-ingest mirrors only idle/running, codex-nudge is removed, agents install writes nothing for codex, the Workspace hides stage/pipeline/feature pickers/PipelineMap for them, and every server start clears leftover codex stage data and the old AGENTS.md stage block. - Changes panel: a whole file opens for reading and enters the editor on a click on its text (caret at the clicked line), not automatically. - macOS: LaunchAgent start-at-login (scripts/launchd-service.js, picked by scripts/service-manager.js). - Adds the AgentHub introduction slides and their image-embedding script.
This commit is contained in:
+2
-2
@@ -586,8 +586,8 @@ cannot collide on `index.lock`.
|
||||
| `stage` | `git add -- <paths>` | `{"paths": ["..."]}` |
|
||||
| `unstage` | `git restore --staged -- <paths>` (a staged rename also passes its original path, so both halves unstage) | `{"paths": ["..."]}` |
|
||||
| `discard` | `git restore --source=HEAD --staged --worktree` (tracked) / `git clean -fd` (untracked) | `{"paths": ["..."], "confirm": true}` |
|
||||
| `stage-hunk` | `git apply --cached` of ONE hunk of `git diff -U<context> HEAD -- <path>` | `{"path": "...", "hunk": "@@ -1,2 +1,3 @@", "context": 3}` |
|
||||
| `discard-hunk` | `git apply -R` of that one hunk, off the working tree | `{"path": "...", "hunk": "@@ ... @@", "context": 3, "confirm": true}` |
|
||||
| `stage-hunk` | `git apply --cached` of ONE hunk of `git diff -U<context> HEAD -- <path>` — or, with `block`, of only that hunk's `block`-th run of +/- lines (the other runs folded into context, as an edited `git add -p` hunk) | `{"path": "...", "hunk": "@@ -1,2 +1,3 @@", "block": 0, "context": 3}` |
|
||||
| `discard-hunk` | `git apply -R` of that one hunk (or that one `block` of it), off the working tree | `{"path": "...", "hunk": "@@ ... @@", "block": 0, "context": 3, "confirm": true}` |
|
||||
| `commit` | `git add -A` then `git commit -m <message>`, or the index as it stands with `only: "staged"` | `{"message": "...", "only": "staged"}` |
|
||||
| `switch` | `git switch [-c] <branch>` | `{"branch": "...", "create": false}` |
|
||||
| `branch-create` | `git branch <branch> [<base>]` — never switches | `{"branch": "...", "base": "..."}` |
|
||||
|
||||
+1
-1
@@ -253,7 +253,7 @@ DASHBOARD_DB_PATH=/var/lib/agenthub/dashboard.db
|
||||
|
||||
# Ingestion polls (0 disables either one)
|
||||
DASHBOARD_WORKFLOW_POLL_MS=12000 # Workflow-tool run journals
|
||||
DASHBOARD_CODEX_POLL_MS=15000 # Codex rollout journals → codex lane stage detection
|
||||
DASHBOARD_CODEX_POLL_MS=15000 # Codex rollout journals → codex lane running/idle
|
||||
CODEX_HOME= # where Codex keeps sessions/ (default ~/.codex)
|
||||
|
||||
# Logging
|
||||
|
||||
+47
-116
@@ -96,7 +96,7 @@ Both modes' path field has a **Browse** button next to it, opening a small folde
|
||||
A lane is driven by a tool: **Claude** (default) or **Codex**, chosen in the "+ Add lane" modal and immutable once the lane exists. A lane's tool determines which CLI is spawned when a run starts.
|
||||
|
||||
- **`claude` (default)** — `claude` binary is invoked; the session's hooks, messages, and tool calls flow through the dashboard. Claude lanes render Sessions, Analytics, cost, and stage-detection badges; liveness is tracked by the Claude Code session.
|
||||
- **`codex` (experimental)** — `codex` binary is invoked instead. The Run Setup panel shows fields for model, sandbox, and approval mode instead of the Claude session picker, and offers "Resume last Codex run" instead of picking a session. Resume uses `codex resume --last`. Codex lanes render stage-detection badges too, fed from Codex's own rollout journals rather than hooks (see "Stage detection"), but no Sessions/Analytics/cost. (The console pre-fills nothing into the prompt box, for either tool.)
|
||||
- **`codex` (experimental)** — `codex` binary is invoked instead. The Run Setup panel shows fields for model, sandbox, and approval mode instead of the Claude session picker, and offers "Resume last Codex run" instead of picking a session. Resume uses `codex resume --last`. Codex lanes carry **no pipeline**: no stage chip, no time-on-stage, no pipeline or feature picker and no PipelineMap, and nothing infers or advances their stage. They also get no Sessions/Analytics/cost. (The console pre-fills nothing into the prompt box, for either tool.)
|
||||
|
||||
### How Codex arguments map
|
||||
|
||||
@@ -116,39 +116,17 @@ Codex does not emit Claude Code hooks (sessions, agent events, tool-execution tr
|
||||
- **Sessions** and **Analytics** tabs — no session history or aggregated statistics
|
||||
- **Cost** surface — no per-run or per-model pricing data
|
||||
|
||||
**These are not bugs; they reflect Codex's architecture.** The stage you declare via `agenthub stage` (or that the driving agent calls itself) still works; liveness is tracked by the lane's tmux pane (if the stack is up) and `dashboard_runs` table (recording every invocation). Cost tracking for Codex runs would require parsing Codex's own logs, a separate design not yet implemented.
|
||||
**These are not bugs; they reflect Codex's architecture.** Liveness is tracked by the lane's tmux pane (if the stack is up) and `dashboard_runs` table (recording every invocation). Cost tracking for Codex runs would require parsing Codex's own logs, a separate design not yet implemented.
|
||||
|
||||
**Stage detection is not in that list.** It does work for a codex lane, from
|
||||
Codex's own on-disk rollout journals rather than from hooks, and unlike a claude
|
||||
lane a written detection there also **advances the lane's declared `stage`** —
|
||||
see "Stage detection" below. What a codex lane cannot get for free is
|
||||
*evidence*: an auto-advanced node renders `passed-no-evidence` (amber), never
|
||||
`done`, because only the session itself can say what it actually finished. The
|
||||
run console never pre-fills a Claude slash command into a codex pane (Codex
|
||||
would read `/agenthub:ship-feature-lane` as prose), so a codex session declares
|
||||
evidence only when something tells it to. Three things do:
|
||||
|
||||
- **An opening prompt checklist.** The Run Setup panel pre-fills a codex pane's
|
||||
first prompt with a plain-text list of `agenthub stage <stage> --tool codex`
|
||||
calls to make, one per node of the lane's own pipeline template (built from
|
||||
`Lane.pipeline_nodes`, so it always matches whatever template is in use) —
|
||||
the same up-front priming a Claude lane gets from its pre-filled
|
||||
`/agenthub:ship-feature-lane` skill command, just as plain text since Codex has
|
||||
no slash commands. See `LaneConsolePane.tsx`'s `promptPrefill`.
|
||||
- **An `AGENTS.md` block.** `agenthub lanes agents install` on a codex lane writes
|
||||
a marker-delimited `<!-- agenthub:stage:start -->` section into
|
||||
`<lane cwd>/AGENTS.md` — the one instruction channel Codex actually reads,
|
||||
since it never loads `.claude/agents/`. Appended, never overwriting your own
|
||||
prose, and a reinstall replaces exactly that block. (On a `claude` lane the
|
||||
same command still copies the two agent templates instead.)
|
||||
- **A missing-evidence nudge.** `server/lib/codex-nudge.js` sweeps every 60s
|
||||
(`DASHBOARD_CODEX_NUDGE_MS` to change, `0` to disable) for a codex lane whose
|
||||
current stage has been held for at least two minutes with no evidence
|
||||
recorded, and types a one-line `[agenthub] ... run: agenthub stage ... --evidence
|
||||
"..."` reminder straight into that lane's live tmux pane (`tmux.sendText`,
|
||||
skipped if the pane is at a bare shell prompt). It never writes `stage`,
|
||||
`stages` or `detected_stage` itself — the evidence that results is a real
|
||||
declaration the session made.
|
||||
**Codex lanes carry no pipeline.** Nothing infers their stage, nothing types
|
||||
a stage reminder into their pane, and `agenthub lanes agents install` installs
|
||||
nothing on them (`installed: []`). The one thing AgentHub reads from Codex is
|
||||
its running/idle status — see "Stage detection" for the journal poll that
|
||||
mirrors it. Every server start also wipes what older builds left behind on a
|
||||
codex lane — its stage columns, stage log (`lane_stage_events`) and feature
|
||||
records (`lane_features`) — and takes the old
|
||||
`<!-- agenthub:stage:start -->` block back out of its `AGENTS.md` (deleting
|
||||
the file if nothing else is in it).
|
||||
|
||||
## Destructive lane actions
|
||||
|
||||
@@ -652,13 +630,18 @@ only one that answers "show me the code" rather than "show me the change"):
|
||||
one green with `U` (`A` added, `D` deleted and struck through, `R`
|
||||
renamed), and a folder holding any of them gets an amber dot. The colours
|
||||
come with the tree read, so **Refresh** re-reads them.
|
||||
Opening a file entry shows the whole file (`/git/file`). Its **Edit** button
|
||||
(any range but a commit) turns that view into a text editor: **Save** (or
|
||||
Opening a file entry shows the whole file (`/git/file`) for reading, at every
|
||||
panel width. **Clicking its text** (or Enter on it; any range but a commit)
|
||||
turns that view into a text editor with the caret on the clicked line and
|
||||
the scroll position kept — a click that only ends a drag-selection does not,
|
||||
so copying never drops you into the editor. **Cancel** returns to reading.
|
||||
From a diff, which has no whole text to click, an **Edit** button does the
|
||||
same once the file has loaded. **Save** (or
|
||||
Ctrl/Cmd+S) is `PUT /api/lanes/:id/git/file`, which writes only over the
|
||||
exact version that was opened — a file the agent changed meanwhile answers
|
||||
`409 ESTALE` and the draft stays for a retry — and keeps the file's CRLF
|
||||
line endings and final newline. A file the editor cannot take (binary, or
|
||||
over the 1 MB view cap) says so where the Edit button would be.
|
||||
over the 1 MB view cap) says so in the file's header.
|
||||
- **Overview** — every changed file with its `+N −M`. Polls every 15s, because
|
||||
it is the cheap shape and the one you leave open while an agent works. Each
|
||||
row carries its own **stage** / **unstage** / **discard** buttons.
|
||||
@@ -670,14 +653,18 @@ only one that answers "show me the code" rather than "show me the change"):
|
||||
a file that appears later is open by default; a lane switch clears it.
|
||||
Each file's header stays **pinned** at the top while its diff scrolls past,
|
||||
and carries the same stage / unstage / discard buttons as its overview row.
|
||||
On a file with unstaged changes every hunk gets an editor-style **review
|
||||
bar**, hung just under its last changed line and pinned to the visible
|
||||
right edge: `^`/`v` to walk the hunks (each brings the next/previous `@@`
|
||||
line to the top of the ONE scroller, below the sticky file header — a
|
||||
On a file with unstaged changes every **change block** — each run of
|
||||
consecutive `+`/`-` lines, not each `@@` hunk: git merges edits up to six
|
||||
lines apart into one hunk, and one bar for all of them acted on every edit
|
||||
at once (issue #50) — gets an editor-style **review bar**, hung just under
|
||||
its last changed line and pinned to the visible right edge: `^`/`v` to walk
|
||||
the blocks (each brings the next/previous block's first line to the top of
|
||||
the ONE scroller, below the sticky file header — a
|
||||
review diff has no scroll box of its own unless it is large enough to
|
||||
window, above 500 rows), `n of N`, **Undo**
|
||||
(`discard-hunk` — asks twice, like discard) and **Keep** (`stage-hunk`).
|
||||
The single-file view offers the same bar.
|
||||
(`discard-hunk` — asks twice, like discard) and **Keep** (`stage-hunk`),
|
||||
both sent with the hunk's `@@` line plus the block's index, so they touch
|
||||
that block alone. The single-file view offers the same bar.
|
||||
- **One file** — clicking a file's path in any list opens just that file.
|
||||
**All files** returns to the list it was opened from — the same mode, at the
|
||||
same scroll offset — because reading a change-set is a walk down a long list
|
||||
@@ -857,7 +844,7 @@ one feature's work reads `implement → tests → implement → tests` as the se
|
||||
edits, runs the suite, and edits again — and when any backwards move opened a
|
||||
run, a single feature was recorded as five or six of them. A real pass ends
|
||||
where the session says it ends: a backwards move only starts a new run when it
|
||||
is declared (`agenthub stage`, or a codex lane's ingest-driven declaration). A
|
||||
is declared (`agenthub stage`). A
|
||||
detection that moves backwards continues the run in flight. Reaching the last
|
||||
node still closes the run for both — work after `done` is new work by anyone's
|
||||
reading. On this repo's own lane the rule took a 26-run history down to 5, which
|
||||
@@ -1164,72 +1151,23 @@ also **infers** a stage from the tool events every hook already delivers
|
||||
un-instrumented sessions that never call `agenthub stage` — it fills in a lane's
|
||||
pipeline map without requiring every skill to be rewritten.
|
||||
|
||||
Two event sources feed the identical matcher, one per tool:
|
||||
|
||||
| Lane `tool` | Source | Cadence |
|
||||
| --- | --- | --- |
|
||||
| `claude` | hooks — `touchLaneFromHook` in `server/routes/hooks.js` | every hook, live |
|
||||
| `codex` | Codex's own rollout journals on disk — `server/lib/codex-ingest.js` | poll, `DASHBOARD_CODEX_POLL_MS` (default 15000; `0` disables) |
|
||||
The matcher is fed by hooks — `touchLaneFromHook` in `server/routes/hooks.js`,
|
||||
on every hook, live — so it only ever sees `claude` lanes.
|
||||
|
||||
Codex has no hook mechanism at all, so `touchLaneFromHook` is pinned to
|
||||
`"claude"` — a hook must never touch a `codex` lane sharing the same directory.
|
||||
`codex-ingest.js` is the codex-side equivalent: it tails
|
||||
`$CODEX_HOME/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl` (`$CODEX_HOME` defaults
|
||||
to `~/.codex`), reads the thread's `cwd` from the `session_meta` on each file's
|
||||
first line, and maps Codex's tool calls onto the same `{tool_name, tool_input}`
|
||||
shape a Claude hook delivers:
|
||||
|
||||
| Codex tool call | Mapped to |
|
||||
| --- | --- |
|
||||
| `exec` / `exec_command` / `shell` / `local_shell` | `Bash` with the extracted `cmd` |
|
||||
| any of the above carrying an `apply_patch` heredoc | one `Write` (for `*** Add File:`) or `Edit` (`Update`/`Delete`) per touched file |
|
||||
| `update_plan` | `TaskCreate` |
|
||||
| `spawn_agent` | `Agent` with the task name as `subagent_type` |
|
||||
| anything else (messages, reasoning, web search, tool OUTPUT) | ignored |
|
||||
|
||||
Everything past that point is shared: the same templates, the same
|
||||
forward-only/declared-wins rules, the same amber-never-green boundary. Three
|
||||
codex-only details:
|
||||
|
||||
- **A written detection also advances the lane's declared `stage`.** This is
|
||||
the one place in AgentHub where an inference moves the blue `current` ring, and
|
||||
it exists because nothing else can: a codex session has no hook and rarely
|
||||
calls `agenthub stage` on its own, so without it the card sits at `idle` forever
|
||||
while the amber badge is the only thing that ever moves. It is gated on
|
||||
`recordDetection` having *written*, so it inherits that function's
|
||||
forward-only, declared-wins and TTL guards unchanged — a detection behind the
|
||||
declared stage still advances nothing. The `setStage` call passes **no
|
||||
evidence**, so every node it moves through renders `passed-no-evidence`
|
||||
(amber), never `done`: a detection sees a tool call, never its outcome. And
|
||||
it passes `keepDetection: true`, the sole exception to `setStage`'s clearing
|
||||
of the detection columns — here the detection is the *reason* for the
|
||||
declaration, so the card keeps showing which command caused it rather than
|
||||
looking like a claim the agent made. The last node of every template carries
|
||||
no `detect` rules, so this can never auto-declare a lane `done`.
|
||||
|
||||
- Only bytes appended since the last pass are read. On the first sight of a
|
||||
journal only its recent tail is read, and nothing at all when that file is
|
||||
already idle — replaying a thread that finished days ago would paint stages
|
||||
for work nobody is doing. "Idle" means the journal's mtime is more than two
|
||||
minutes old **or in the future**: a file that claims to be from the future
|
||||
(clock stepped back, NTP correction, a restore that preserved timestamps)
|
||||
makes a raw `now - mtime` negative, which would otherwise read as "written
|
||||
milliseconds ago" and replay a finished thread as live work.
|
||||
- The same pass mirrors the lane's `status` between `idle` and `running` from
|
||||
the journal's mtime, under the identical guard hook ingestion uses (only a
|
||||
lane with no `run_id`, and only from `idle`/`running` — never from
|
||||
`provisioning` or `failed`). It writes nothing else: not `stage`, not
|
||||
`notes`, not `slot`/`ports`.
|
||||
|
||||
**A codex lane's *evidence* is a separate matter.** Auto-advance moves the ring
|
||||
but can never supply evidence, so every node a codex lane passes through stays
|
||||
amber until the session itself runs `agenthub stage <node> --evidence "..."`. AgentHub
|
||||
cannot inject an instruction into a CLI it merely spawns, so an `AGENTS.md` in
|
||||
the working copy is the only lever — `agenthub lanes agents install` writes the
|
||||
marker-delimited block for you (see "Installing the QC/gate agents"). Note that
|
||||
from inside a codex session `agenthub stage` needs to know which lane it is: a pane
|
||||
the dashboard started carries `AGENTHUB_LANE_ID`, and anywhere else `--tool codex`
|
||||
disambiguates (see "Which lane a CLI command addresses" below).
|
||||
Codex lanes carry no pipeline, so nothing detects or advances their stage.
|
||||
`server/lib/codex-ingest.js` only mirrors a codex lane's `status` between
|
||||
`idle` and `running`: every `DASHBOARD_CODEX_POLL_MS` (default 15000; `0`
|
||||
disables) it lists `$CODEX_HOME/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl`
|
||||
(`$CODEX_HOME` defaults to `~/.codex`), reads each thread's `cwd` from the
|
||||
`session_meta` on the file's first line, and calls the owning lane `running`
|
||||
while that journal's mtime is under two minutes old — and `idle` once it is
|
||||
older **or in the future** (a clock stepped back makes a raw `now - mtime`
|
||||
negative, which would otherwise read as live). It uses the identical guard hook
|
||||
ingestion uses (only a lane with no `run_id`, and only from `idle`/`running` —
|
||||
never from `provisioning` or `failed`) and writes nothing else: not `stage`,
|
||||
not `notes`, not `slot`/`ports`.
|
||||
|
||||
### The evidence boundary
|
||||
|
||||
@@ -1796,16 +1734,9 @@ agenthub lanes agents install
|
||||
|
||||
Writes both agent definitions (from the repo's own `agents/`, the same files the plugin ships) into `<lane>/.claude/agents/` and adds that directory to the lane's local `.git/info/exclude` (never the tracked `.gitignore` — this is a per-clone runtime concern, not an app-repo change). Idempotent and never automatic, the same shape as `agenthub lanes proof-link` — a session installs the agents explicitly, once, before a lane's first run through Stage 6/7 (or after a AgentHub upgrade ships updated templates — reinstalling overwrites, it doesn't merge).
|
||||
|
||||
**On a `codex` lane the same command installs something else.** Codex never
|
||||
reads `.claude/agents/`, so copying agent definitions there would be dead
|
||||
files. Instead it writes the stage-declaration block into
|
||||
`<lane>/AGENTS.md` — the project doc Codex *does* load from the working
|
||||
directory — delimited by `<!-- agenthub:stage:start -->` / `<!-- agenthub:stage:end -->`
|
||||
so a reinstall replaces exactly that block and nothing else. The file is
|
||||
appended to when it already exists (your own prose is preserved) and created
|
||||
when it does not; `installed` comes back as `["AGENTS.md"]`. This is the lever
|
||||
that gets a codex session to supply the *evidence* auto-advance cannot — see
|
||||
"Stage detection".
|
||||
**On a `codex` lane the same command installs nothing** (`installed: []`, and
|
||||
the Tools menu hides it): Codex never reads `.claude/agents/`, and codex lanes
|
||||
carry no pipeline to drive.
|
||||
|
||||
**Credentials are not embedded.** Unlike Shipyard's original per-lane agent generation, these templates carry no QA account credentials — this repo has no seeded-QA-account system yet. `qc-local` degrades gracefully: if it hits a login page with no credentials block present, it reports `LOCAL-QC: FAIL — login required, no seed-account mechanism configured for this profile` rather than guessing.
|
||||
|
||||
|
||||
+20
-9
@@ -106,9 +106,8 @@ root holds all four (`qc-local`, `senior-gate-reviewer`, `backend-reviewer`,
|
||||
`.claude/agents/` — the highest-priority agent source — which is what makes the
|
||||
bare `subagent_type: qc-local` in the pipeline skill resolve there. One source
|
||||
file, two ways to reach it; the skill names the bare form first and falls back
|
||||
to the `agenthub:` form. On a `codex` lane the same command installs the
|
||||
`<!-- agenthub:stage:start -->` block in the lane's `AGENTS.md` instead — Codex
|
||||
never reads `.claude/agents/`.
|
||||
to the `agenthub:` form. On a `codex` lane the same command installs nothing —
|
||||
Codex never reads `.claude/agents/`, and codex lanes carry no pipeline.
|
||||
|
||||
Because the plugin ships the hooks itself, `npm run install-hooks` is not needed
|
||||
for plugin users — and must not be run alongside it. Events carry no id, so two
|
||||
@@ -137,10 +136,11 @@ waits on an install:
|
||||
7. **CLI** — write the `~/.local/bin/agenthub` launcher, never clobbering a `agenthub` the bootstrap did not write. It pins the node that ran the bootstrap and falls back to `node` on `PATH` when that binary is gone (a Homebrew Cellar or nvm path deleted by an upgrade).
|
||||
8. **UI build** — `client/` is copied into the runtime dir and built there (`npm ci --include=dev`, falling back to `npm install --include=dev`, then `npm run build`), landing in `runtime/client-dist` — the same directory the server serves from. `--include=dev` is required, not tidy: the entire build toolchain (`vite`, `typescript`, `@vitejs/plugin-react`, `tailwindcss`, `@types/*`) is a devDependency, and npm reads an inherited `NODE_ENV=production` as `--omit=dev`. Every bootstrap descended from the running server carries that env — the self-update chain, and any `SessionStart` fired by a `claude` inside a tmux pane the server started — so without the flag the install drops `vite` and the build dies on missing types. Skipped when a bundle for the current version already exists; a failure here does not fail the bootstrap (the API still works, and `/agenthub-open` can retry) but does mean `/run` 404s until it's fixed — and when a bundle from the PREVIOUS version is still on disk the failure is quieter still: the dashboard keeps serving the old UI against the new API, visible only as the Settings page's `UI build vX (rebuild client to match)` line.
|
||||
9. **Server** — on Linux with `systemd --user` available, install/refresh a
|
||||
`agenthub.service` unit (see [Auto-start on boot](#auto-start-on-boot)) instead
|
||||
`agenthub.service` unit — on macOS with a GUI login, the
|
||||
`io.smartgift.agenthub` LaunchAgent (see [Auto-start on boot](#auto-start-on-boot)) — instead
|
||||
of a bare spawn, so the server is supervised and comes back after a reboot
|
||||
with no Claude Code session involved. Everywhere else (macOS, Windows, or
|
||||
no systemd), spawn `server/index.js` detached, with `NODE_PATH` at the
|
||||
with no Claude Code session involved. Everywhere else (Windows, no
|
||||
systemd, a Mac reached only over SSH), spawn `server/index.js` detached, with `NODE_PATH` at the
|
||||
runtime `node_modules` and `DASHBOARD_CLIENT_DIST` at the runtime
|
||||
`client-dist` — scoped to this session, same as before this fell back to
|
||||
systemd.
|
||||
@@ -233,15 +233,26 @@ anyone logs in; when that needs elevated rights, it's skipped silently (check
|
||||
`agenthub.service` unit not written by this bootstrap is left untouched, and the
|
||||
server falls back to the old per-session detached spawn instead.
|
||||
|
||||
On macOS, Windows, or a Linux box without systemd, nothing changes: the
|
||||
On macOS the same steps write a LaunchAgent instead
|
||||
(`~/Library/LaunchAgents/io.smartgift.agenthub.plist`, `scripts/launchd-service.js`;
|
||||
`scripts/service-manager.js` picks the one for the platform, and both export the
|
||||
same API). `KeepAlive` plays `Restart=always`, `AbandonProcessGroup` keeps the
|
||||
lane panes' tmux server alive across a restart, output goes to
|
||||
`~/Library/Logs/agenthub.log`, and the hand-over restart is `launchctl bootout`
|
||||
+ `bootstrap` (a `kickstart` would keep the old plist). If that bootstrap fails
|
||||
and no server is left running, the bootstrap starts one detached. A LaunchAgent
|
||||
needs the user's GUI login; without one (SSH only) `launchctl print gui/<uid>`
|
||||
fails and the server falls back to the detached spawn below.
|
||||
|
||||
On Windows, a Linux box without systemd, or a Mac with no GUI login, the
|
||||
server is a detached spawn started fresh on the next `SessionStart` after a
|
||||
reboot, same as always. That spawn inherits the Claude Code session's env, so
|
||||
`server/index.js` deletes the parent-session variables (`CLAUDECODE`,
|
||||
`CLAUDE_CODE_SESSION_ID`, `CLAUDE_CODE_MESSAGING_*`, `CLAUDE_PLUGIN_ROOT`, …)
|
||||
before anything else runs — otherwise tmux hands them to every lane pane. It is
|
||||
a denylist: user configuration such as `CLAUDE_CONFIG_DIR`,
|
||||
`CLAUDE_CODE_USE_BEDROCK` and `ANTHROPIC_*` passes through. There is no
|
||||
launchd agent; see [INSTALL.md § macOS](../INSTALL.md#macos).
|
||||
`CLAUDE_CODE_USE_BEDROCK` and `ANTHROPIC_*` passes through. See
|
||||
[INSTALL.md § macOS](../INSTALL.md#macos).
|
||||
|
||||
### Who may stop the server
|
||||
|
||||
|
||||
Reference in New Issue
Block a user