Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
23f7e99
chore: auto-commit agent work (cfd631a8-766)
jaeko44 Mar 26, 2026
ce7f882
fix(tui): accept ink enter sequence in session detail
jaeko44 Mar 26, 2026
db3dfa0
fix(ci): restore workflow markers and detail regressions
jaeko44 Mar 26, 2026
71a286b
Merge origin/main into task/cfd631a87666-feat-tui-session-detail-moda…
jaeko44 Mar 26, 2026
6af339b
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 Mar 26, 2026
e955e37
Merge remote-tracking branch 'origin/main' into HEAD
jaeko44 Mar 26, 2026
4c5db8b
Merge remote-tracking branch 'origin/main' into HEAD
jaeko44 Mar 26, 2026
3c65239
bosun: commit remaining changes for PR #445
jaeko44 Mar 30, 2026
1c8e54d
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 Mar 30, 2026
d935182
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 Mar 30, 2026
67b529b
fix: resolve CI failure in workflow and tui checks
jaeko44 Mar 30, 2026
223ccc1
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 Mar 30, 2026
a10a7da
fix: resolve merge conflicts, fix TUI session detail modal rendering …
Copilot Mar 30, 2026
fd6eae0
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 Mar 30, 2026
c5843b9
fix: resolve merge conflicts and fix TUI session detail modal tests
Copilot Mar 30, 2026
702a104
Merge remote-tracking branch 'origin/task/cfd631a87666-feat-tui-sessi…
Copilot Mar 30, 2026
22ceb8a
Apply 3 code review suggestions
jaeko44 Mar 30, 2026
a4798c9
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 Mar 30, 2026
38f3165
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 Mar 30, 2026
e6e7591
fix: resolve CI failure in tui screens tests
jaeko44 Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions infra/worktree-recovery-state.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,7 @@ function buildNextWorktreeRecoveryState(currentState, event) {
if (normalizedEvent.outcome === "healthy_noop") {
return {
...nextState,
health:
state.health === "recovered"
? "recovered"
: (state.failureStreak > 0 ? state.health : "healthy"),
health: state.health === "healthy" ? "healthy" : state.health,
lastHealthyAt: normalizedEvent.timestamp,
};
}
Expand Down
25 changes: 23 additions & 2 deletions tests/tui/fixtures.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ export const sessionDetailFixture = {
ok: true,
session: {
...sessionsFixture[0],
branch: "task/cfd631a87666-feat-tui-session-detail-modal-full-session-drill",
provider: "openai",
model: "gpt-5.4",
tokensIn: 2300,
tokensOut: 1100,
runtimeMs: 30000,
turns: Array.from({ length: 18 }, (_, index) => ({
id: `turn-${index + 1}`,
number: index + 1,
timestamp: `2026-03-23T00:00:${String(index).padStart(2, "0")}.000Z`,
tokenDelta: 50 + index,
durationMs: 1000 + (index * 250),
lastToolCall: index % 2 === 0 ? "shell.exec" : "assistant.message",
})),
messages: [
{ timestamp: "2026-03-23T00:00:00.000Z", role: "user", content: "Start debugging" },
{ timestamp: "2026-03-23T00:00:05.000Z", role: "assistant", content: "Loaded the relevant logs" },
Expand All @@ -77,9 +91,15 @@ export const sessionDiffFixture = {
ok: true,
summary: "2 files changed",
diff: {
formatted: [
"diff --git a/src/app.mjs b/src/app.mjs",
"--- a/src/app.mjs",
"+++ b/src/app.mjs",
...Array.from({ length: 45 }, (_, index) => index % 3 === 0 ? `+added line ${index}` : index % 3 === 1 ? `-removed line ${index}` : ` context line ${index}`),
].join("\\n"),
files: [
{ filename: "src/app.mjs", additions: 8, deletions: 2 },
{ filename: "tests/app.test.mjs", additions: 4, deletions: 0 },
{ filename: "src/app.mjs", additions: 8, deletions: 2, patch: "+added line\n-removed line\n context" },
{ filename: "tests/app.test.mjs", additions: 4, deletions: 0, patch: "+test line" },
],
},
};
Expand Down Expand Up @@ -130,3 +150,4 @@ export function createMockWsClient() {
},
};
}

117 changes: 112 additions & 5 deletions tests/tui/screens.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,117 @@ describe("tui screen rendering", () => {
await view.unmount();
});

it("opens a session detail modal on enter and renders timeline, diff, and logs", async () => {
const bridge = createMockBridge();
const view = await renderInk(
React.createElement(AgentsScreen, {
wsBridge: bridge,
host: "127.0.0.1",
port: 3080,
sessions: sessionsFixture,
stats: monitorStatsFixture,
}),
{ columns: 220, rows: 60 },
);

await waitFor(() => view.text().includes("Backoff queue (1)"));
await view.press("`r", 80);
await waitFor(() => view.text().includes("Session Detail"));

expect(view.text()).toContain("Task ID");
expect(view.text()).toContain("Turn Timeline");
expect(view.text()).toContain("Latest Diff");
expect(view.text()).toContain("Stdout");
expect(view.text()).toContain("[S]teer");

await view.unmount();
});

it("sends a steer message from session detail and shows confirmation", async () => {
const bridge = createMockBridge();
const view = await renderInk(
React.createElement(AgentsScreen, {
wsBridge: bridge,
host: "127.0.0.1",
port: 3080,
sessions: sessionsFixture,
stats: monitorStatsFixture,
}),
{ columns: 220, rows: 60 },
);

await waitFor(() => view.text().includes("Backoff queue (1)"));
await view.press("`r", 80);
await waitFor(() => view.text().includes("Session Detail"));

await view.press("s", 40);
await waitFor(() => view.text().includes("Steer message:"));
await view.press("Please continue with focused logging", 50);
await view.press("`r", 100);

await waitFor(() => view.text().includes("Steer sent ✓"));
expect(globalThis.fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/sessions/session-active-1/message?workspace=all"),
expect.objectContaining({ method: "POST" }),
);

await view.unmount();
});

it("scrolls the turn timeline independently inside session detail", async () => {
const bridge = createMockBridge();
const view = await renderInk(
React.createElement(AgentsScreen, {
wsBridge: bridge,
host: "127.0.0.1",
port: 3080,
sessions: sessionsFixture,
stats: monitorStatsFixture,
}),
{ columns: 220, rows: 60 },
);

await waitFor(() => view.text().includes("Backoff queue (1)"));
await view.press("`r", 80);
await waitFor(() => view.text().includes("| 2026-03-23 00:00:00.000 |"));
expect(view.text()).not.toContain("| 2026-03-23 00:00:17.000 |");

await view.press("\u001b[6~", 80);
await waitFor(() => view.text().includes("| 2026-03-23 00:00:17.000 |"));

await view.unmount();
});

it("streams live stdout into the right panel while detail is open", async () => {
const bridge = createMockBridge();
const view = await renderInk(
React.createElement(AgentsScreen, {
wsBridge: bridge,
host: "127.0.0.1",
port: 3080,
sessions: sessionsFixture,
stats: monitorStatsFixture,
}),
{ columns: 220, rows: 60 },
);

await waitFor(() => view.text().includes("Backoff queue (1)"));
await view.press("`r", 80);
await waitFor(() => view.text().includes("Session Detail"));

bridge.emit("logs:stream", {
logType: "stdout",
raw: "Steer message accepted by running session",
line: "Steer message accepted by running session",
level: "info",
timestamp: "2026-03-23T00:00:31.000Z",
filePath: "",
});

await waitFor(() => view.text().includes("00:00:31"));
await view.unmount();
});

it("navigates app tabs with numeric key input", async () => {
const bridge = createMockBridge();
const view = await renderInk(
Expand Down Expand Up @@ -180,6 +291,7 @@ describe("tui screen rendering", () => {

await view.press("?");
await view.press(" ", 40);
await waitFor(() => view.text().includes("? Help"));

await view.unmount();
});
Expand Down Expand Up @@ -216,8 +328,3 @@ describe("tui screen rendering", () => {
await view.unmount();
});
});





Loading
Loading