feat(lanes): split panes on one lane, in-place file editing, hunk review bar
- Split view: every pane shows the selected lane, each in its own tmux session (`slot` 0-3 on POST /api/lanes/:id/start; slot 0 stays the lane's `run_id`, slots 1-3 are `agenthub-lane-<id>-<slot>`). A pane's Active runs hides what the other panes show; stop/reset/remove kill every slot. No per-pane lane picker any more. - Whole-file editor: PUT /api/lanes/:id/git/file (lane-file-write.js) with the read path's guards, existing text files only, and a baseSha stale check (409 ESTALE). A file it cannot take says why. - Per-hunk review bar under each hunk's last changed line: prev/next, "n of N", Undo, Keep. Review diffs no longer nest their own scroll box (unless windowed), so navigation moves the one real scroller. - Files tree lists untracked-not-ignored files and colours entries by git status (M/U/A/D/R), folders holding changes get a dot. - Changes panel: Unstage all on the single commit/Stage-all row; sizes Narrow (30%) / Medium / Full screen, no 70/30. - Lane card: the needs-you banner is gone (the strip tile keeps it).
This commit is contained in:
+21
-4
@@ -21,6 +21,10 @@
|
||||
*
|
||||
* Spawns `claude` or `codex` in the tmux pane based on the tool parameter.
|
||||
*
|
||||
* A lane can hold up to four sessions, one per Workspace split pane: pane 0 is
|
||||
* `agenthub-lane-<laneId>` (the lane's own `run_id`), panes 1–3 are
|
||||
* `agenthub-lane-<laneId>-<slot>`.
|
||||
*
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -35,7 +39,9 @@ try {
|
||||
/* db-less environment, skip persistence */
|
||||
}
|
||||
|
||||
const RUN_ID_RE = /^agenthub-lane-(\d+)$/;
|
||||
const RUN_ID_RE = /^agenthub-lane-(\d+)(?:-([1-3]))?$/;
|
||||
/** Highest pane slot a lane may use (the Workspace's 4-pane grid is 0–3). */
|
||||
const MAX_PANE_SLOT = 3;
|
||||
const EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
||||
// `auto` is the CLI's own default-ish mode (`claude --permission-mode auto`):
|
||||
// it asks only for what its classifier reads as risky. `default` is kept for
|
||||
@@ -55,8 +61,13 @@ const CODEX_APPROVALS = new Set(["on-request", "never"]);
|
||||
// Codex has no `--effort` flag; reasoning effort is a config override.
|
||||
const CODEX_EFFORTS = new Set(["low", "medium", "high", "xhigh"]);
|
||||
|
||||
function runIdForLane(laneId) {
|
||||
return `agenthub-lane-${laneId}`;
|
||||
function runIdForLane(laneId, slot = 0) {
|
||||
return slot ? `agenthub-lane-${laneId}-${slot}` : `agenthub-lane-${laneId}`;
|
||||
}
|
||||
|
||||
function slotFromRunId(id) {
|
||||
const m = typeof id === "string" ? id.match(RUN_ID_RE) : null;
|
||||
return m ? Number(m[2] || 0) : null;
|
||||
}
|
||||
|
||||
function laneIdFromRunId(id) {
|
||||
@@ -149,6 +160,7 @@ function buildArgv(args) {
|
||||
function spawnRun(args) {
|
||||
const {
|
||||
laneId,
|
||||
slot = 0,
|
||||
cwd,
|
||||
tool = "claude",
|
||||
model,
|
||||
@@ -164,6 +176,9 @@ function spawnRun(args) {
|
||||
if (typeof laneId !== "number" || !Number.isInteger(laneId)) {
|
||||
throw makeErr("EBADLANE", "laneId must be an integer");
|
||||
}
|
||||
if (!Number.isInteger(slot) || slot < 0 || slot > MAX_PANE_SLOT) {
|
||||
throw makeErr("EBADSLOT", `slot must be an integer 0–${MAX_PANE_SLOT}`);
|
||||
}
|
||||
if (typeof cwd !== "string" || !cwd) {
|
||||
throw makeErr("EBADCWD", "cwd is required");
|
||||
}
|
||||
@@ -215,7 +230,7 @@ function spawnRun(args) {
|
||||
);
|
||||
}
|
||||
|
||||
const id = runIdForLane(laneId);
|
||||
const id = runIdForLane(laneId, slot);
|
||||
const startedAt = Date.now();
|
||||
const argv = buildArgv({
|
||||
tool,
|
||||
@@ -323,6 +338,7 @@ function publicRun(id) {
|
||||
return {
|
||||
id,
|
||||
laneId,
|
||||
slot: slotFromRunId(id),
|
||||
status: live ? "running" : "gone",
|
||||
tool: row?.tool || "claude",
|
||||
cwd: row?.cwd || null,
|
||||
@@ -353,5 +369,6 @@ module.exports = {
|
||||
listRuns,
|
||||
laneIdFromRunId,
|
||||
runIdForLane,
|
||||
MAX_PANE_SLOT,
|
||||
SHELL_COMMANDS,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user