diff --git a/docs/api.md b/docs/api.md index abe3bcf..bee4cbb 100644 --- a/docs/api.md +++ b/docs/api.md @@ -94,7 +94,7 @@ when the gateway cannot find its parent, or the parent already shows a request. for a moment after a thread changes. `activity` is the newest step the agent has in progress. `kind` is `command`, `edit`, `search`, -`web`, `subagent`, `thinking` or `tool`, and `text` is the command line, the file name (without its +`web`, `subagent`, `thinking` or `tool`, and `text` is the command's first line, the file name (without its directory), the search, the subagent's title or the tool's name, trimmed to about 80 characters. Monitors are left out, since they run for hours. It is null when no step is in progress. The gateway rereads a running thread at most every 5 seconds, so it can lag that much. @@ -136,7 +136,8 @@ GET /v1/threads/:hostId/:threadId/history?cursor= 200 { "messages": [Message], "earlierCursor": "..." | null } ``` -The 20 messages before the ones the watch has, oldest first. Pass the returned `earlierCursor` for +The messages before the ones the watch has, oldest first: 20 at a time at first, then one T3 history +page at a time, which can hold more. Pass the returned `earlierCursor` for the page before that. It is null at the start of the thread. Cursors are opaque. ``` diff --git a/gateway/src/views.ts b/gateway/src/views.ts index f230454..4b58821 100644 --- a/gateway/src/views.ts +++ b/gateway/src/views.ts @@ -93,7 +93,10 @@ function windowCursor(start: number, bounded: BoundedThread): string | null { return bounded.hasMoreHistory && bounded.historyCursor ? `h${bounded.historyCursor}` : null; } -/** Up to 20 messages before `cursor`. T3 pages without messages are skipped, three at most. */ +/** + * The messages before `cursor`: 20 from the bounded window, or one T3 page, which can hold more. + * T3 pages without messages are skipped, three at most. + */ export async function loadHistory(host: T3Host, thread: ThreadShell, cursor: string): Promise { if (cursor.startsWith("w")) { const end = Number(cursor.slice(1)); @@ -120,7 +123,7 @@ export async function loadHistory(host: T3Host, thread: ThreadShell, cursor: str createdAt: item.startedAt ?? item.updatedAt ?? "", }), ); - if (messages.length > 0) return { messages: messages.slice(-MESSAGES_PER_THREAD), earlierCursor: next && `h${next}` }; + if (messages.length > 0) return { messages, earlierCursor: next && `h${next}` }; } return { messages: [], earlierCursor: next && `h${next}` }; } @@ -139,7 +142,9 @@ export function currentActivity(turnItems: TurnItem[]): Activity | null { function describeStep(item: TurnItem): Omit | null { switch (item.type) { case "command_execution": - return item.monitor || typeof item.input !== "string" ? null : { kind: "command", text: item.input }; + return item.monitor || typeof item.input !== "string" + ? null + : { kind: "command", text: item.input.split("\n").find((line) => line.trim()) ?? item.input }; case "file_change": return item.fileName ? { kind: "edit", text: item.fileName.split("/").pop()! } : null; case "file_search": diff --git a/gateway/test/views.test.ts b/gateway/test/views.test.ts index 85b3498..c1d1bd4 100644 --- a/gateway/test/views.test.ts +++ b/gateway/test/views.test.ts @@ -239,11 +239,11 @@ describe("currentActivity", () => { expect( currentActivity([ item("file_change", "running", { fileName: "gateway/src/host.ts" }), - item("command_execution", "running", { input: "bun test" }), + item("command_execution", "running", { input: "\nbun test \\\n --bail" }), item("command_execution", "running", { input: "tail -f log", monitor: true }), item("assistant_message", "running"), ]), - ).toEqual({ kind: "command", text: "bun test", startedAt: "2026-11-01T09:00:00.000Z" }); + ).toEqual({ kind: "command", text: "bun test \\", startedAt: "2026-11-01T09:00:00.000Z" }); expect(currentActivity([item("file_change", "running", { fileName: "gateway/src/host.ts" })])?.text).toBe("host.ts"); });