Commit Graph

68 Commits

Author SHA1 Message Date
nntrivi2001 fcfebe44f1 feat(ai-tools): Settings → AI Tools, Default/Other lane picker, per-run bypass switch (#40 A1)
Settings → Dashboard gains an AI Tools section (installed tools with
Enabled/Disabled and Set default, available ones with a homepage link,
Refresh). useAiTools shares one catalog copy across the app and replaces
LANE_TOOL_LABEL. The Add-lane modal picks Default (<tool>) or Other…; the
router row reads Native (<tool>) and disables AI Router profiles for tools
that cannot use one. The run form gives catalog tools resume and a red
per-run Bypass permissions switch (Codex too). Claude Code / Codex settings
tabs hide while that tool is disabled. The Workspace probes each lane tool's
CLI fresh.

Docs: CLAUDE.md tool/bypass rule, AGENTS.md, LANES.md, API.md, CLI.md,
server/README.md, OpenAPI fragment + regenerated openapi.yaml.
2026-09-29 08:21:05 +07:00
nntrivi2001 887dc4640e chore(release): bump version to 1.8.12 2026-09-28 17:49:38 +07:00
nntrivi2001 3c238bc5c4 chore(release): bump version to 1.8.11 2026-09-28 10:26:56 +07:00
nntrivi2001 952e8584f0 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.
2026-09-25 17:30:28 +07:00
nntrivi2001 3f6e903c22 chore(release): bump version to 1.8.10 2026-09-25 13:37:46 +07:00
nntrivi2001 52392962fb chore(release): bump version to 1.8.9 2026-09-25 11:41:53 +07:00
nntrivi2001 478418f602 fix(macos): scrub parent-session env, survive node upgrades and reboots, store lanes by real path (#22)
What broke (code audit, untested on a Mac):
- Without systemd the server is always spawned from inside a Claude Code
  session, so it inherited CLAUDECODE, CLAUDE_CODE_SESSION_ID,
  CLAUDE_PLUGIN_ROOT, ... and tmux handed them to every lane pane (a codex
  pane even resolved as `claude` in `agenthub`'s harness check).
- The ~/.local/bin/agenthub launcher pinned a versioned node path that
  `brew upgrade`/nvm deletes, leaving the CLI dead until the next bootstrap.
- A discovery entry left over from before a reboot could name a recycled pid,
  so the bootstrap's fast path believed a dashboard was live and
  stopDashboard could signal an unrelated process.
- Lanes were stored under the typed cwd; macOS reports /private/tmp and the
  on-disk case, so sessions never bound to a /tmp or differently-cased lane.

What changed:
- server/index.js deletes a DENYLIST of parent-session variables at startup;
  user config (CLAUDE_CONFIG_DIR, CLAUDE_CODE_USE_BEDROCK, ANTHROPIC_*) stays.
- linkCli's launcher falls back to `node` on PATH when the pinned one is gone.
- liveServers ignores entries whose startedAt predates this boot (60 s margin).
- createLane and POST /api/lanes/ensure store/look up fs.realpathSync.native
  of an existing cwd; the liveness reaper also tries the realpath. Existing
  rows are untouched.
- session-liveness gains an exec seam so the darwin ps+lsof branch is tested.
- plugin-doctor names ~/.zshrc on darwin; INSTALL.md gains a macOS section
  with the Mac verification checklist, mirrored in both READMEs and PLUGINS.md.
No launchd, no macOptionIsMeta, engines stays >=20.
2026-09-25 11:23:59 +07:00
nntrivi2001 aff79a787c feat(lanes,git): publish a repo with no remote, and read a repo with no commits
What broke: a lane on a fresh `git init` repo had no working git surface.
`statusCounts` ran `rev-parse --short HEAD` unguarded, so `gitFacts` threw
and `GET /api/lanes/:id/git` answered `{available:false}` — the Changes
panel showed no header, and `commit`/`switch` 500'd through `isDirty`.
Add lane in Worktree mode accepted such a repo and left a `failed` row
behind, and a repo with commits but no remote had a permanently disabled
Push with no way to give it one short of a terminal.

What changed:
- worktree.js: an unborn HEAD is a normal state. `statusCounts` reports
  `head: null`; `gitFacts` reads the branch via `symbolic-ref`, an empty
  subject, and a new `unborn` field.
- git-ops.js: an 11th write, `publish` — `remote add origin -- <url>` then
  `push -u origin HEAD`, only with a commit (ENOCOMMITS) and NO remote at
  all (EREMOTEEXISTS, so it never re-points one), the url allowlisted by
  `validateRemoteUrl` (https without userinfo, ssh://, scp-like; refuses
  local paths, file://, ext::, fd::, git://, http://, leading `-`:
  EBADURL), and `origin` removed again when the push fails.
- POST /api/lanes/worktree answers 400 EEMPTYREPO for a repo with no
  commits instead of creating a row that can only fail.
- LaneGitBar: unborn repos show "no commits yet" and a commit-first hint;
  no remote offers Publish… with an inline URL box; a remote but no
  upstream relabels Push as "Publish branch". AddLaneModal disables Add
  on an empty repo and offers "Use as a Repo lane".
- The two tests that used an unborn repo to force a provisioning failure
  now use a `feat` ref clash, since that repo is refused up front.
- CLAUDE.md rule reworded to "the boring eleven" with publish's guards;
  docs/API.md, docs/LANES.md, ARCHITECTURE.md, server/README.md, openapi.
2026-09-25 11:21:30 +07:00
nntrivi2001 605ca9d65e chore(release): bump version to 1.8.8 2026-09-24 17:08:18 +07:00
nntrivi2001 804b193ac7 chore(release): bump version to 1.8.7 2026-09-24 15:54:09 +07:00
nntrivi2001 4e0b869dd3 chore(release): bump version to 1.8.6 2026-09-24 14:40:32 +07:00
nntrivi2001 cfe832383c feat(lanes): stage/undo one hunk at a time, sticky review file headers
Review and the single-file view gain per-hunk Stage and Undo
(stage-hunk / discard-hunk: the hunk is named by its @@ line and
re-matched against a fresh diff, then git apply --cached / -R; undo
needs confirm: true and two clicks). Each review file header stays
pinned while its diff scrolls and carries the file's own
stage/unstage/discard.
2026-09-24 14:40:31 +07:00
nntrivi2001 7bc4594b94 fix(lanes,git): harden the lane git surface and fix client races in the Changes panel
Server (git-diff.js, git-ops.js, worktree.js, routes/lanes.js):
- Every lane git call runs with GIT_LITERAL_PATHSPECS=1 and -z porcelain, so a
  file name is a file name, never a glob, and odd names parse correctly.
- A lane that is a subdirectory of its repo sees and acts on ONLY its own
  paths (reads, stage/unstage/discard/commit, and gitFacts.files).
- Any file named settings.local.json (the lane's router API key) is refused as
  403 ESECRET on disk (symlinks resolved) and at every committed rev.
- commit refuses with 400 ESTAGEDOUTSIDE while anything outside the lane is
  staged (checked with --no-renames, so a move into the lane is caught).
- push resolves its remote (upstream → only remote → origin), else 400
  ENOREMOTE; unstaging a rename unstages both halves.
- Git writes serialize on the repo's git dir, not the lane id, so two lanes on
  one index cannot collide on index.lock.
- git refusing an action (a numeric exit status) answers 409 EGIT with git's
  stderr instead of a 500 carrying a numeric code; one shared parseNumstat.

Client:
- Changes panel: no stale answer from a previous lane or an older quick-open
  query repaints the panel; discard disarms on lane switch; Enter on a file
  path opens it instead of folding; bounded tree expand-all.
- Large diffs (> 500 rows) render only the visible rows, with stable width
  and in-sync split columns; mirrored split scroll is clamped.
- Git bar: no "in sync" without counts, a clear no-remote message, branch list
  re-read after a switch, and no previous-lane facts after a lane switch.
- Tooltips no longer flicker or close on unrelated scrolls.
2026-09-24 10:30:25 +07:00
nntrivi2001 6153d50fa8 chore(release): bump version to 1.8.5 2026-09-24 10:30:25 +07:00
nntrivi2001 6353e88c9e chore(release): bump version to 1.8.4 2026-09-24 09:46:52 +07:00
nntrivi2001 6cf8a82889 feat(ui): Appearance settings — dark/light/system mode and named themes; fix Add lane silently doing nothing (#42)
- Settings → Appearance: mode Dark / Light / System (follows the OS live) and
  a named theme remembered per mode — dark: Default, Darcula, Apple Dark;
  light: Default, Apple Light, IntelliJ Light, Canvas. Each theme is a
  CSS-variable override block keyed by `data-theme` on <html>; the sidebar
  quick toggle gains System and swaps between the two remembered themes.
- An inline boot script in index.html applies the stored mode and theme
  before first paint, so a reload no longer flashes the default colours.
- Diff +/- rows are easier to tell apart on every theme: a /20 status tint,
  a 3px status bar and a bold coloured marker; the changed-span emphasis no
  longer vanishes on light themes.
- #42: the Add-lane modal (Repo mode / "Current branch") asks
  POST /api/lanes/ensure with `exact: true`. Without it a folder nested inside
  an existing lane resolved to that parent lane with created:false, so the
  modal closed and no new card appeared. When the folder really already has a
  lane, the modal now says which one instead of closing.

Closes #42
2026-09-24 09:44:42 +07:00
nntrivi2001 2ea2184372 chore(release): bump version to 1.8.3 2026-09-23 16:46:40 +07:00
nntrivi2001 94ee2dc79c chore(release): bump version to 1.8.2 2026-09-23 13:23:40 +07:00
nntrivi2001 1b561c22d9 feat(lanes,ui): per-lane git surface — Changes panel, VS Code-style diff, branch-aware Add lane, redesigned tooltips (#34, #35)
- Changes card beside lane detail with five modes: Files tree, overview,
  Review (whole diff, per-file collapse), single file, commit history.
- LaneDiffView: unified / side-by-side with independent scroll on both axes
  and mirrored scrollLeft, expand/collapse context, intra-line emphasis.
- Staged-only commit box; git actions (fetch/pull/push/stage/unstage/
  discard/commit/switch/branch-create/branch-delete) guarded in git-ops.js.
- Server: git-diff.js + git-ops.js behind GET /api/lanes/:id/git/* and
  POST /api/lanes/:id/git/:action; gitFacts gains upstream/ahead/behind.
- Add lane: new/existing branch, current branch, worktree list, prefill
  from the git bar's "New lane here"; /api/run/cwds gains a repo kind.
- GlobalTooltip upgrades every native title into a themed anchored bubble;
  chart/info popovers use theme tokens.
2026-09-23 09:18:17 +07:00
nntrivi2001 32a8887419 chore(release): bump version to 1.7.0 2026-09-21 17:07:08 +07:00
nntrivi2001 f77049c238 feat(run,ui): make the lane console usable — terminal focus, live-exit recovery, auto permission mode, model aliases
Terminal (issue #33):
- `convertEol` is gone from xterm. It rewrote every bare LF as CR+LF, so a
  tmux pane that scrolled (tmux emits a bare LF and keeps drawing at the
  column it was on) had the rest of that draw land at column 0, stamping a
  line's first characters into the left margin of the rows below — the torn
  left edge that only showed up once content scrolled.
- The attach carries the client's real size (`?cols=&rows=`, `sizeFromQuery`
  in `server/websocket.js`, bounded because it reaches a TIOCSWINSZ ioctl).
  `attach()` spawned `tmux attach-session` at a hardcoded 80x24 on every
  attach, and tmux sizes the window to the newest client, so `claude` redrew
  its whole UI at 80 columns until the first `resize` message landed.
- The terminal takes the keyboard on mount, so typing reaches tmux without
  clicking the pane first.
- A pane that ends (double Ctrl+C, `exit`, a crash) reports itself through a
  new `onExit` prop and `LaneConsolePane` runs `newRun()`, so the console
  returns to the setup form instead of leaving a dead pane behind a
  "New run" button.

Pane environment:
- `server/lib/desktop-env.js` forwards the graphical session's DISPLAY /
  WAYLAND_DISPLAY / XAUTHORITY / XDG_RUNTIME_DIR (from
  `systemctl --user show-environment`, this process's own values winning)
  into the pane. The server normally runs as a systemd user service with none
  of them, so `claude` there could not read the clipboard and answered "no
  image found in clipboard" on every image paste.

Run setup:
- `auto` is a permission mode and is now the default everywhere — allowlists,
  the `--permission-mode` fallback, the select, and the pane's initial state.
- The model picker sends the CLI's aliases (`fable`/`opus`/`sonnet`/`haiku`)
  instead of pinned ids. `claude --model` resolves an alias to the latest
  model itself, so a release like Fable 5.1 needs no edit here; pricing is
  unaffected because it keys off the resolved id the transcript reports.
- **Create/update readme file** moved from the Add-lane modal into the lane's
  Tools menu: creating a lane and writing its docs are separate decisions,
  and the second is worth re-running long after the first.
- The Workspace's no-lane state is a real banner; the lane strip (add tile
  included) renders only once a lane exists.

Refs #32, #33.

Tests: npm run test:server (1828 pass), client vitest (558 pass).
2026-09-21 17:03:27 +07:00
nntrivi2001 64017c6ca5 feat(run,notifications,profiles): stop sending prompts nobody typed, and fix notification + profile delivery
Run console:
- Picking a pipeline no longer pre-writes anything into the prompt box
  (`promptPrefill` in `LaneConsolePane.tsx` is gone — it wrote the driving
  slash command on a claude lane and a stage checklist on a codex one).
  Whatever sits in that box is sent as a real prompt the moment the run
  starts, so the box is the user's alone now.
- Resuming a session sends NO prompt at all (`handleStart` in `RunSetup.tsx`):
  the resumed session carries its own transcript, and leftover text in the box
  fired an extra turn into it.

Notifications:
- `client/src/lib/notify.ts` is now the ONE sender, used by both
  `useNotifications` and Settings' test button, and it reports where the
  notification landed: `POST /api/push/send` answering `{native:false,pushed:0}`
  means it went NOWHERE, so the client falls back to a local notification
  instead of counting the 200 as delivered.
- A notification carries the lane that raised it (`/workspace?lane=<id>`), and
  Workspace selects that lane on arrival.

Profiles:
- `server/lib/cc-profiles.js` reads and writes the macOS login Keychain item
  `Claude Code-credentials`, where the `claude` CLI actually keeps
  `claudeAiOauth` on darwin — a switch used to rewrite a file nothing reads.
  The file stays the fallback, is kept in step only when it already exists, and
  `AGENTHUB_CC_KEYCHAIN=0` pins it (tests set it).

Tests: npm run test:server (1820 pass), client vitest (553 pass).
2026-09-21 15:15:22 +07:00
nntrivi2001 9156781d75 chore(release): bump version to 1.6.17 2026-09-18 16:53:56 +07:00
nntrivi2001 59b3ba9e39 chore(release): bump version to 1.6.14 2026-09-18 10:40:24 +07:00
nntrivi2001 e217314248 chore(release): bump version to 1.6.13 2026-09-18 10:22:49 +07:00
nntrivi2001 4227a97ab1 chore(release): bump version to 1.6.12 2026-09-18 09:45:50 +07:00
nntrivi2001 75a2de60b2 chore(release): bump version to 1.6.11 2026-09-17 23:20:19 +07:00
nntrivi2001 470f9909b4 chore(release): bump version to 1.6.10 2026-09-17 22:22:15 +07:00
nntrivi2001 5ba9e07d78 feat(cc-config,lanes): per-profile quota, and three fixes that made a write look lost
Closes #19, #20, #21, #27.

#19 — a saved auth profile now reports how much of its account's quota is
already spent, which is the only number that decides which one is worth
switching to. Nothing under CLAUDE_HOME records it, so it comes from the same
endpoint the CLI's own /usage reads, answered for whichever account's OAuth
token is presented. It is a POST and a button, never a poll: every profile that
is not the active one is normally expired, so answering means refreshing it
first, and a refresh ROTATES the stored refresh token. The rotated pair is
written straight back — dropping it would leave the profile holding a
credential Anthropic has already retired, i.e. an account needing a fresh
login. The token is read, put in an Authorization header, and never returned,
logged or echoed; what leaves the server is two percentages and two reset
times.

#20 — a settings patch that changes nothing wrote nothing but still took a
timestamped backup, so opening the config panel quietly grew a backup
directory inside every lane. patchSettings now compares the rendered output
with what is already on disk and returns early, fixed once where all callers
route through. A lane's git exclude also picks up .claude/.cc-config-backups/
beside settings.local.json.

#21 — the router apply route was the only mutating lane route that never
broadcast its lane. The write reached disk and the column, but the client's
cached row kept the old router_profile forever: the picker looked right until
the user switched lanes and back, at which point the effect re-read the stale
row and flipped the selection to the previous profile. One broadcastLane, plus
a lane-switch guard so an apply that resolves after the user has moved on no
longer paints its result onto whichever lane is now on screen.

#27 — Add-lane in worktree mode left the dialog up after a clean setup with a
dead confirm button and no obvious way out. It closes itself when nothing
failed, and when something did, the summary stays and the confirm becomes a
real Close.
2026-09-17 22:20:32 +07:00
nntrivi2001 b0e8368c1d chore(release): bump version to 1.6.9 2026-09-17 16:16:39 +07:00
nntrivi2001 686b80bd99 fix(lanes,reload): count a pipeline run by declarations, and stop the two reload commands merging
Closes #26, #25.

`runs` is the denominator every pass rate on the Workflows page is read
against, and any backwards move opened a new one. Stage detection bounces by
design — one feature reads `implement → tests → implement` as the session
edits, runs the suite and edits again — so a single feature was recorded as
five or six runs, and a lane in constant use reported a handful of passes
spread across dozens of phantom runs. A backwards move now opens a run only
when it is DECLARED; a backwards detection continues the run in flight.
Reaching the final node still closes the run for either, since work after
`done` is new work however it was observed. Replayed over this repo's own lane
the rule takes 26 recorded runs down to the 5 features that actually shipped
through it. The panel itself is unchanged: passes are still declarations only.

`/reload-plugins` and `/reload-skills` were typed back to back. Claude Code
opens its slash-command menu the moment `/` is typed and the Enter that follows
lands on that menu, so the second command was appended to the line still in the
composer and the pane submitted `/reload-plugins/reload-skills` — which Claude
Code runs as `/reload-plugins` with an argument, never reloading skills, and
leaving a stray prompt turn in the scrollback. Reproduced and fixed against a
live pane: SETTLE_MS (1s) between the two sends, which makes
`reloadOpenSessions` async rather than block the event loop, and the same pause
in the detached update-all worker.
2026-09-17 16:16:31 +07:00
nntrivi2001 02ee46c862 chore(release): bump version to 1.6.8 2026-09-17 15:09:30 +07:00
nntrivi2001 5bd1c0323f feat(lanes): reorder and rename lane cards, and show each file's line delta
The lane strip's tiles are now drag-reorderable (Alt+←/→ is the keyboard
path), and a lane's name can be set in place from the detail header. The
"Lane 23 ·" prefix is gone from both — the bare `#23` stays on the tile,
because that number is how the CLI addresses a lane.

Card order is a per-viewer preference, not a lane fact: it lives in
localStorage (`agenthub.workspace.laneOrder`) beside the split-view layout
and writes nothing server-side, so `GET /api/lanes` and `agenthub lanes`
keep answering in id order. Renaming is the opposite — it is PATCH
/api/lanes/:id with {title}, the one lane column a user now types into
directly.

That made a patch value reachable by hand for the first time, so
`bindable` now rejects any non-scalar for every PATCHABLE column with
EBADFIELD, and the route answers 400 for that and for the
SQLITE_CONSTRAINT family, rather than handing better-sqlite3 a datatype
throw and reporting 500 on plain bad input.

The uncommitted-files tooltip became a real panel. `GET /api/lanes/:id/git`
now returns `files` as {path, status, added, deleted} records — deltas from
one `git diff --numstat HEAD`, null where git has none to report (untracked,
binary, renamed) — and the card renders them portalled to the body, so the
Workspace's own scroll box cannot clip the list.
2026-09-17 15:07:37 +07:00
nntrivi2001 b5ca531800 chore(release): bump version to 1.6.7 2026-09-17 13:21:34 +07:00
nntrivi2001 af2ad63a18 feat(lanes): name the files behind a lane's uncommitted counts
GET /api/lanes/:id/git now returns `files`, the raw `git status --porcelain`
lines the dirty/untracked counts were already parsed from, capped at 20 so a
lane mid-`npm install` cannot ship thousands of paths through a polled
endpoint. The lane card hovers them as the tooltip on its
"N modified · N untracked" text, appending "… +N" when the cap bit.

No new subprocess and no new endpoint — statusCounts was counting those lines
and throwing them away.
2026-09-17 13:19:07 +07:00
nntrivi2001 2fe1dcd46f chore(release): bump version to 1.6.6 2026-09-17 11:39:00 +07:00
nntrivi2001 229002cc2b chore(release): bump version to 1.6.5 2026-09-17 10:02:12 +07:00
nntrivi2001 5ab674f134 chore(release): bump version to 1.6.4 2026-09-16 11:28:18 +07:00
nntrivi2001 fb3796f064 chore(release): bump version to 1.6.3 2026-09-15 14:55:23 +07:00
nntrivi2001 6e685ce1c3 chore(release): bump version to 1.6.2 2026-09-15 11:01:07 +07:00
nntrivi2001 713e9fe58b chore(release): bump version to 1.6.1 2026-09-14 16:33:46 +07:00
nntrivi2001 e895c0e8eb chore(release): bump version to 1.6.0 2026-09-14 16:07:05 +07:00
nntrivi2001 fc7b58f15b chore(release): bump version to 1.5.10 2026-09-14 13:10:21 +07:00
nntrivi2001 91af3dca86 chore(release): bump version to 1.5.9 2026-09-14 11:22:11 +07:00
nntrivi2001 b7e81af0b6 chore(release): bump version to 1.5.8 2026-09-11 16:48:00 +07:00
nntrivi2001 49a041b1dc chore(release): bump version to 1.5.7 2026-09-11 16:32:50 +07:00
nntrivi2001 0335e0e6bc chore(release): bump version to 1.5.6 2026-09-11 15:59:33 +07:00
nntrivi2001 3ed88062d6 chore(release): bump version to 1.5.5 2026-09-11 15:35:35 +07:00
nntrivi2001 7000a9c5ce chore(release): bump version to 1.5.4 2026-09-10 23:21:45 +07:00
nntrivi2001 67c948bbe2 chore(release): bump version to 1.5.3 2026-09-10 17:09:07 +07:00