feat(terminal-controller): read Agent terminal screens synchronously with an explicit drain
This commit is contained in:
1 parent
eaf6df53e2
commit
336ee0d7fd
12 files changed
+87
-65
No files matched your search
@@ -283,7 +283,7 @@ async openForAgent(agent: Agent, request: AgentTerminalRequest, signal: AbortSig
|
||||
* @param agent - Session owner.
|
||||
* @param id - terminal identity.
|
||||
* @param request - page position and byte bound.
|
||||
* @returns rendered screen text.
|
||||
* @returns rendered screen text, including output received before the call.
|
||||
*/
|
||||
readScreen(agent: Agent, id: WebTerminalId, request: TerminalScreenRequest): Promise<TerminalScreenPage>
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ async openForAgent(agent: Agent, request: AgentTerminalRequest, signal: AbortSig
|
||||
* @param agent - Session owner.
|
||||
* @param id - terminal identity.
|
||||
* @param request - page position and byte bound.
|
||||
* @returns rendered screen text.
|
||||
* @returns rendered screen text, including output received before the call.
|
||||
*/
|
||||
readScreen(agent: Agent, id: WebTerminalId, request: TerminalScreenRequest): Promise<TerminalScreenPage>
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
en: 5c966c6397e001ce
|
||||
zh: d08ec72cc3a6b8b2
|
||||
/deepseek-ai-dsh-api-terminal-controller/use-this-package/agent-terminals:
|
||||
en: 6b0f71340d6c174c
|
||||
zh: 1170c4340ee2225a
|
||||
en: 189fb90d90a8fa3b
|
||||
zh: 0948cf928d94c0af
|
||||
/deepseek-ai-dsh-api-terminal-controller/understand-the-implementation:
|
||||
en: 6eab922e2fec84d5
|
||||
zh: 8264367fdf878b98
|
||||
en: 128e0ddd93d59fa4
|
||||
zh: e5408d260b51e3fe
|
||||
/deepseek-ai-dsh-api-terminal-controller/further-exploration:
|
||||
en: b1b5d2a2bfb8cc6a
|
||||
zh: aa0ea568aa63fda5
|
||||
|
||||
@@ -50,7 +50,7 @@ An open tab in any connected window retains its terminal, including hidden tabs
|
||||
|
||||
Host code opens a terminal for a Session's Agent with `openForAgent(agent, request, signal)`. The caller supplies the sandbox policy it resolved for the Session: `danger-full-access` launches the shell directly, confined modes wrap it through the execution environment's `ctx.sandbox` provider, and a missing provider fails before allocation. Confined POSIX shells write history to `/tmp/dsh-agent-history-<id>` because the person's history file is outside the writable roots. `preferredShells` names executables tried among discovered shells before the default. The terminal records `origin: 'agent'`, `control: 'agent'` and its `sandboxMode`; it counts toward `maxTerminals` and, while the Agent controls it, toward `maxAgentTerminals`.
|
||||
|
||||
The returned `AgentTerminal` writes input, renders the screen as text, marks a screen position to render later output, waits for output or state changes, inspects and signals the foreground process group, and closes the terminal. Agent input neither needs nor takes Web input control. The first browser write marked `typed` passes control to the person; afterwards writes, signals and closing fail with `AgentTerminalError` code `user-control`, while reads continue. `readScreen` and `inspectActivity` read any terminal of the Agent's Session, including terminals the person opened.
|
||||
The returned `AgentTerminal` writes input, renders the screen as text, marks a screen position to render later output, waits for output or state changes, inspects and signals the foreground process group, and closes the terminal. Its screen reads and marks are synchronous and see output applied so far; `drain` waits until output received before the call is applied. Agent input neither needs nor takes Web input control. The first browser write marked `typed` passes control to the person; afterwards writes, signals and closing fail with `AgentTerminalError` code `user-control`, while reads continue. The controller's `readScreen` drains before rendering, and it and `inspectActivity` read any terminal of the Agent's Session, including terminals the person opened.
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## Understand the implementation
|
||||
@@ -60,7 +60,7 @@ The returned `AgentTerminal` writes input, renders the screen as text, marks a s
|
||||
|
||||
The Host uses `ctx.subprocess.spawnTerminal` with `TERM=xterm-256color`; it never launches a desktop terminal application. Streaming UTF-8 decoding preserves split characters and leading BOMs, and replaces incomplete trailing bytes at EOF. The duplex `attach` Remote stream carries output and ordered `write`/`resize` commands on the Gateway mux. Each command has a consecutive attachment-local sequence; an `ack` confirms provider acceptance or reports control refusal. Snapshots and state frames carry the receiving attachment's `writable` flag. Headless xterm and its serializer produce each opening screen after all preceding output writes, then monotone output sequences identify subsequent frames. Slow followers fail explicitly; a new attachment restores the current screen.
|
||||
|
||||
Agent terminals reuse the same allocation, rollback and retention paths with a Host-minted identity. Agent input is queued with browser input and output but bypasses attachment control. Screen reads render the headless buffer after queued output: wrapped rows are joined, the alternate screen is read as displayed, and pages keep the newest bytes within the caller's bound. Screen marks use emulator markers, so trimmed scrollback reports truncation. A change revision advances after each applied output chunk and state publication, and `changed` waits on it. The Client marks a write `typed` only within a short window after keyboard, paste, IME, drop, wheel, pointer or touch input, because xterm.js reports terminal-protocol replies through the same data event.
|
||||
Agent terminals reuse the same allocation, rollback and retention paths with a Host-minted identity. Agent input is queued with browser input and output but bypasses attachment control. Screen reads render the headless buffer: wrapped rows are joined, the alternate screen is read as displayed, and pages keep the newest bytes within the caller's bound. Screen marks use emulator markers, so trimmed scrollback reports truncation. A change revision advances after each applied output chunk and state publication, and `changed` waits on it. The Client marks a write `typed` only within a short window after keyboard, paste, IME, drop, wheel, pointer or touch input, because xterm.js reports terminal-protocol replies through the same data event.
|
||||
|
||||
The latest attachment owns input and resize. Detachment releases input control without killing the process. Explicit close awaits process cleanup and final output; cleanup failure retains the resource for retry. The Session remembers closed identities and rejects their delayed or repeated creation, including creation already in progress when close arrives. A new terminal uses a new identity. Pending allocations remain owned even if cancellation and cleanup both fail. Session owner disposal and controller disposal also terminate owned processes. Changing the Session’s sandbox mode leaves user terminals running with the same permissions. Input or resize refused after control transfer or process exit leaves the output attachment intact and disables input; rejected input is not replayed. Provider completion disables input before output drainage finishes, and late input failures cannot discard a gracefully finished follower's final frames.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Shell 探测结果首先列出执行环境声明的默认 shell。仅当 provide
|
||||
|
||||
Host 代码通过 `openForAgent(agent, request, signal)` 为 Session 的 Agent 打开终端。调用方传入它为该 Session 解析出的沙箱策略:`danger-full-access` 直接启动 shell,受限模式通过执行环境的 `ctx.sandbox` provider 包裹 shell,缺少 provider 时在分配前失败。受限的 POSIX shell 把历史写到 `/tmp/dsh-agent-history-<id>`,因为人的历史文件不在可写目录内。`preferredShells` 列出在已探测 shell 中优先尝试的可执行文件,之后才用默认 shell。终端记录 `origin: 'agent'`、`control: 'agent'` 和 `sandboxMode`;它计入 `maxTerminals`,在 Agent 控制期间也计入 `maxAgentTerminals`。
|
||||
|
||||
返回的 `AgentTerminal` 可以写入输入、把屏幕渲染为文本、标记屏幕位置以便之后渲染新输出、等待输出或状态变化、查询并向前台进程组发信号,以及关闭终端。Agent 输入既不需要也不抢占 Web 的输入控制权。浏览器第一次发出标记为 `typed` 的写入后,控制权转给人;此后写入、发信号和关闭都会以 `AgentTerminalError` 的 `user-control` 代码失败,读取不受影响。`readScreen` 和 `inspectActivity` 可读取 Agent 所在 Session 的任意终端,包括人自己打开的终端。
|
||||
返回的 `AgentTerminal` 可以写入输入、把屏幕渲染为文本、标记屏幕位置以便之后渲染新输出、等待输出或状态变化、查询并向前台进程组发信号,以及关闭终端。它的读屏和标记是同步的,只看到已处理的输出;`drain` 会等到调用前收到的输出处理完。Agent 输入既不需要也不抢占 Web 的输入控制权。浏览器第一次发出标记为 `typed` 的写入后,控制权转给人;此后写入、发信号和关闭都会以 `AgentTerminalError` 的 `user-control` 代码失败,读取不受影响。控制器的 `readScreen` 会先等输出处理完再渲染;它和 `inspectActivity` 可读取 Agent 所在 Session 的任意终端,包括人自己打开的终端。
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## 了解实现
|
||||
@@ -60,7 +60,7 @@ Host 代码通过 `openForAgent(agent, request, signal)` 为 Session 的 Agent
|
||||
|
||||
Host 通过 `ctx.subprocess.spawnTerminal` 创建 `TERM=xterm-256color` 的终端,不启动桌面终端应用。流式 UTF-8 解码保留跨块字符和开头的 BOM,并在 EOF 将不完整的尾部字节替换为替代字符。双工 `attach` Remote 流通过 Gateway mux 传输输出和有序的 `write`/`resize` 命令。每条命令使用附件内连续递增的序号;`ack` 确认提供者已接受操作,或报告控制权拒绝。快照和状态帧携带接收附件的 `writable` 标记。Headless xterm 和序列化 addon 在此前输出写入后生成初始屏幕,后续增量携带单调序号。过慢的订阅者明确失败;重新连接恢复当前屏幕。
|
||||
|
||||
Agent 终端复用同一套分配、回滚和保留流程,标识由 Host 生成。Agent 输入与浏览器输入和输出一起排队,但不经过附件的输入控制权。读屏在已排队的输出处理完后渲染 headless 缓冲区:软换行的行会拼接,备用屏按当前显示读取,分页在调用方给出的字节上限内保留最新内容。屏幕标记使用模拟器的 marker,因此滚动缓冲区截掉标记行时会报告截断。每处理一段输出或发布一次状态,变更版本号就加一,`changed` 据此等待。Client 只在键盘、粘贴、输入法、拖放、滚轮、指针或触摸操作之后的短时间窗口内把写入标记为 `typed`,因为 xterm.js 通过同一个数据事件报告终端协议应答。
|
||||
Agent 终端复用同一套分配、回滚和保留流程,标识由 Host 生成。Agent 输入与浏览器输入和输出一起排队,但不经过附件的输入控制权。读屏渲染 headless 缓冲区:软换行的行会拼接,备用屏按当前显示读取,分页在调用方给出的字节上限内保留最新内容。屏幕标记使用模拟器的 marker,因此滚动缓冲区截掉标记行时会报告截断。每处理一段输出或发布一次状态,变更版本号就加一,`changed` 据此等待。Client 只在键盘、粘贴、输入法、拖放、滚轮、指针或触摸操作之后的短时间窗口内把写入标记为 `typed`,因为 xterm.js 通过同一个数据事件报告终端协议应答。
|
||||
|
||||
最新连接持有输入和尺寸控制权。断开连接只释放输入权,不结束进程。显式关闭等待进程清理和最后输出;清理失败时保留资源以便重试。Session 记住已关闭的标识并拒绝迟到或重复的创建请求,包括关闭到达时仍在进行的创建。新终端使用新标识。取消创建且清理失败时,已分配的进程仍有所有者。Session owner 和 controller 卸载也会终止所拥有的进程。改变 Session 的沙箱模式时,用户终端继续以原有权限运行。控制权转移或进程退出后被拒绝的输入和尺寸请求保留输出连接并禁用输入,不重发被拒绝的输入。提供者完成时即禁用输入,无须等待输出排空;迟到的输入失败不能丢弃已正常结束的订阅者所保留的尾部帧。
|
||||
|
||||
|
||||
@@ -69,17 +69,25 @@ export class AgentTerminal {
|
||||
write(data: string): Promise<void> { return this.terminal.agentWrite(data) }
|
||||
|
||||
/**
|
||||
* Render a newest-relative page of the screen.
|
||||
* @param request - page position and byte bound.
|
||||
* @returns rendered text.
|
||||
* Wait until output received before the call is applied to the screen.
|
||||
* @returns after queued output and input settle.
|
||||
*/
|
||||
read(request: TerminalScreenRequest): Promise<TerminalScreenPage> { return this.terminal.readScreen(request) }
|
||||
drain(): Promise<void> { return this.terminal.drain() }
|
||||
|
||||
/**
|
||||
* Mark the cursor row so later output can be rendered from it.
|
||||
* @returns the screen mark.
|
||||
* Render a newest-relative page of the screen as applied so far.
|
||||
* @param request - page position and byte bound.
|
||||
* @returns rendered text.
|
||||
* @throws AgentTerminalError `unavailable` after the terminal is closed.
|
||||
*/
|
||||
mark(): Promise<TerminalScreenMark> { return this.terminal.markScreen() }
|
||||
read(request: TerminalScreenRequest): TerminalScreenPage { return this.terminal.readScreen(request) }
|
||||
|
||||
/**
|
||||
* Mark the cursor row of the screen as applied so far, so later output can be rendered from it.
|
||||
* @returns the screen mark.
|
||||
* @throws AgentTerminalError `unavailable` after the terminal is closed.
|
||||
*/
|
||||
mark(): TerminalScreenMark { return this.terminal.markScreen() }
|
||||
|
||||
/**
|
||||
* Inspect the foreground process group.
|
||||
|
||||
@@ -226,10 +226,11 @@ export class TerminalController extends TypertRemoteService {
|
||||
* @param agent - Session owner.
|
||||
* @param id - terminal identity.
|
||||
* @param request - page position and byte bound.
|
||||
* @returns rendered screen text.
|
||||
* @returns rendered screen text, including output received before the call.
|
||||
*/
|
||||
readScreen(agent: Agent, id: WebTerminalId, request: TerminalScreenRequest): Promise<TerminalScreenPage> {
|
||||
return this.terminal(agent, id).readScreen(request)
|
||||
const terminal = this.terminal(agent, id)
|
||||
return terminal.drain().then(() => terminal.readScreen(request))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -255,41 +255,45 @@ export class BrowserTerminal {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a newest-relative page after all received output is applied.
|
||||
* Wait until output received before the call, and input queued before it, are applied.
|
||||
* @returns after the queued work settles, whether or not it failed.
|
||||
*/
|
||||
async drain(): Promise<void> {
|
||||
await this.enqueue(() => undefined)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a newest-relative page of the screen as applied so far; {@link drain} first to include received output.
|
||||
* @param request - page position and byte bound.
|
||||
* @returns rendered text; alternate-screen applications return their current screen.
|
||||
* @throws AgentTerminalError after the screen is released.
|
||||
*/
|
||||
readScreen(request: TerminalScreenRequest): Promise<TerminalScreenPage> {
|
||||
return this.enqueue(() => {
|
||||
this.requireScreen()
|
||||
const buffer = this.screen.buffer.active
|
||||
return pageLines(renderLines(buffer, 0, lastContentRow(buffer) + 1), request)
|
||||
})
|
||||
readScreen(request: TerminalScreenRequest): TerminalScreenPage {
|
||||
this.requireScreen()
|
||||
const buffer = this.screen.buffer.active
|
||||
return pageLines(renderLines(buffer, 0, lastContentRow(buffer) + 1), request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the cursor row after all received output is applied.
|
||||
* Mark the cursor row of the screen as applied so far; {@link drain} first to place it after received output.
|
||||
* @returns a mark whose lines include the marked row; a change of active buffer renders the whole new buffer.
|
||||
* @throws AgentTerminalError after the screen is released.
|
||||
*/
|
||||
markScreen(): Promise<TerminalScreenMark> {
|
||||
return this.enqueue(() => {
|
||||
this.requireScreen()
|
||||
const marked = this.screen.buffer.active.type
|
||||
const marker = this.screen.registerMarker(0)
|
||||
return {
|
||||
lines: () => this.enqueue(() => {
|
||||
this.requireScreen()
|
||||
const active = this.screen.buffer.active
|
||||
const end = lastContentRow(active) + 1
|
||||
if (active.type !== marked || marker === undefined) return { lines: renderLines(active, 0, end), truncated: false }
|
||||
if (marker.isDisposed) return { lines: renderLines(active, 0, end), truncated: true }
|
||||
return { lines: renderLines(active, marker.line, end), truncated: false }
|
||||
}),
|
||||
dispose: () => { marker?.dispose() },
|
||||
}
|
||||
})
|
||||
markScreen(): TerminalScreenMark {
|
||||
this.requireScreen()
|
||||
const marked = this.screen.buffer.active.type
|
||||
const marker = this.screen.registerMarker(0)
|
||||
return {
|
||||
lines: () => {
|
||||
this.requireScreen()
|
||||
const active = this.screen.buffer.active
|
||||
const end = lastContentRow(active) + 1
|
||||
if (active.type !== marked || marker === undefined) return { lines: renderLines(active, 0, end), truncated: false }
|
||||
if (marker.isDisposed) return { lines: renderLines(active, 0, end), truncated: true }
|
||||
return { lines: renderLines(active, marker.line, end), truncated: false }
|
||||
},
|
||||
dispose: () => { marker?.dispose() },
|
||||
}
|
||||
}
|
||||
|
||||
private requireAgentControl(): void {
|
||||
|
||||
@@ -95,10 +95,11 @@ export interface TerminalScreenPage {
|
||||
/** A screen position that follows scrollback trimming, used to render output written after it. */
|
||||
export interface TerminalScreenMark {
|
||||
/**
|
||||
* Render logical lines from the marked row through the last content row, after all received output is applied.
|
||||
* Render logical lines from the marked row through the last content row of the screen as applied so far.
|
||||
* @returns the lines; `truncated` means scrollback trimming discarded the marked row and rendering starts at the oldest row.
|
||||
* @throws AgentTerminalError `unavailable` after the screen is released.
|
||||
*/
|
||||
lines(): Promise<{ readonly lines: readonly string[]; readonly truncated: boolean }>
|
||||
lines(): { readonly lines: readonly string[]; readonly truncated: boolean }
|
||||
/** Release the emulator marker. */
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
@@ -848,10 +848,11 @@ describe('Agent terminals', () => {
|
||||
expect(handle.write).toHaveBeenCalledExactlyOnceWith('echo ready\r')
|
||||
expect(await terminal.signal('SIGINT')).toBe(123)
|
||||
expect(await terminal.inspectForeground()).toBeUndefined()
|
||||
const mark = await terminal.mark()
|
||||
expect(await mark.lines()).toEqual({ lines: [''], truncated: false })
|
||||
await terminal.drain()
|
||||
const mark = terminal.mark()
|
||||
expect(mark.lines()).toEqual({ lines: [''], truncated: false })
|
||||
mark.dispose()
|
||||
expect(await terminal.read({ offset: 0, count: 10, maxBytes: 100 })).toMatchObject({ text: '', totalLines: 1 })
|
||||
expect(terminal.read({ offset: 0, count: 10, maxBytes: 100 })).toMatchObject({ text: '', totalLines: 1 })
|
||||
const revision = terminal.revision
|
||||
const changed = terminal.changed(revision, signal())
|
||||
controller.rename(agent, terminal.id, 'server')
|
||||
@@ -920,7 +921,7 @@ it.skipIf(process.platform === 'win32')('runs a command typed by the agent in a
|
||||
title: 'agent', cols: 80, rows: 24, policy: { mode: 'danger-full-access', workspaceRoot: cwd }, preferredShells: [],
|
||||
}, signal())
|
||||
await terminal.write('echo agent-$((40 + 2))\r')
|
||||
await expect.poll(async () => (await terminal.read({ offset: 0, count: 20, maxBytes: 4000 })).text, { timeout: 10_000 }).toContain('agent-42')
|
||||
await expect.poll(() => terminal.read({ offset: 0, count: 20, maxBytes: 4000 }).text, { timeout: 10_000 }).toContain('agent-42')
|
||||
await terminal.close()
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
|
||||
@@ -368,14 +368,17 @@ describe('agent access', () => {
|
||||
expect(handle.write).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renders pages and marked output after queued output is applied', async () => {
|
||||
it('renders pages and marked output once draining applies queued output', async () => {
|
||||
const { terminal, output } = fixture(agentInfo)
|
||||
output.write(Buffer.from('$ earlier\r\n'))
|
||||
await expect.poll(() => terminal.revision).toBeGreaterThan(0)
|
||||
const mark = await terminal.markScreen()
|
||||
await terminal.drain()
|
||||
const mark = terminal.markScreen()
|
||||
output.write(Buffer.from('$ make\r\nprogress 10%\rprogress 100%\r\n$ '))
|
||||
await expect.poll(async () => (await mark.lines()).lines).toEqual(['$ make', 'progress 100%', '$'])
|
||||
expect(await terminal.readScreen({ offset: 0, count: 2, maxBytes: 1000 })).toEqual({ text: 'progress 100%\n$', totalLines: 4, lineBegin: 0, lineEnd: 2, truncated: false })
|
||||
await expect.poll(() => terminal.revision).toBeGreaterThan(1)
|
||||
await terminal.drain()
|
||||
expect(mark.lines().lines).toEqual(['$ make', 'progress 100%', '$'])
|
||||
expect(terminal.readScreen({ offset: 0, count: 2, maxBytes: 1000 })).toEqual({ text: 'progress 100%\n$', totalLines: 4, lineBegin: 0, lineEnd: 2, truncated: false })
|
||||
mark.dispose()
|
||||
})
|
||||
|
||||
@@ -383,9 +386,9 @@ describe('agent access', () => {
|
||||
const { terminal, output } = fixture(agentInfo)
|
||||
output.write(Buffer.from('$ vim notes\r\n'))
|
||||
await expect.poll(() => terminal.revision).toBeGreaterThan(0)
|
||||
const mark = await terminal.markScreen()
|
||||
const mark = terminal.markScreen()
|
||||
output.write(Buffer.from('\x1b[?1049h\x1b[Hediting notes'))
|
||||
await expect.poll(async () => await mark.lines()).toEqual({ lines: ['editing notes'], truncated: false })
|
||||
await expect.poll(() => mark.lines()).toEqual({ lines: ['editing notes'], truncated: false })
|
||||
})
|
||||
|
||||
it('reports foreground process facts without taking control', async () => {
|
||||
@@ -397,10 +400,11 @@ describe('agent access', () => {
|
||||
|
||||
it('reports a mark trimmed out of scrollback as truncated', async () => {
|
||||
const { terminal, output } = fixture(agentInfo)
|
||||
const mark = await terminal.markScreen()
|
||||
const mark = terminal.markScreen()
|
||||
output.write(Buffer.from(Array.from({ length: 200 }, (_, index) => `line ${index}`).join('\r\n')))
|
||||
await expect.poll(async () => (await mark.lines()).truncated).toBe(true)
|
||||
expect((await mark.lines()).lines.at(-1)).toBe('line 199')
|
||||
await expect.poll(() => mark.lines().truncated).toBe(true)
|
||||
await terminal.drain()
|
||||
expect(mark.lines().lines.at(-1)).toBe('line 199')
|
||||
})
|
||||
|
||||
it('wakes revision waiters on output and state changes and honors cancellation', async () => {
|
||||
@@ -420,8 +424,11 @@ describe('agent access', () => {
|
||||
|
||||
it('refuses screen reads after the screen is released', async () => {
|
||||
const { terminal } = fixture(agentInfo)
|
||||
const mark = terminal.markScreen()
|
||||
await terminal.close()
|
||||
await expect(terminal.readScreen({ offset: 0, count: 10, maxBytes: 100 })).rejects.toMatchObject({ code: 'unavailable' })
|
||||
await expect(terminal.markScreen()).rejects.toMatchObject({ code: 'unavailable' })
|
||||
await terminal.drain()
|
||||
expect(() => terminal.readScreen({ offset: 0, count: 10, maxBytes: 100 })).toThrow(expect.objectContaining({ code: 'unavailable' }))
|
||||
expect(() => terminal.markScreen()).toThrow(expect.objectContaining({ code: 'unavailable' }))
|
||||
expect(() => mark.lines()).toThrow(expect.objectContaining({ code: 'unavailable' }))
|
||||
})
|
||||
})
|
||||
@@ -3398,7 +3398,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
signature: 'readScreen(agent: Agent, id: WebTerminalId, request: TerminalScreenRequest): Promise<TerminalScreenPage>',
|
||||
description: 'Render a page of any terminal in the Agent\'s Session, including terminals the person opened.',
|
||||
parameters: [{ name: 'agent', description: 'Session owner.' }, { name: 'id', description: 'terminal identity.' }, { name: 'request', description: 'page position and byte bound.' }],
|
||||
returns: 'rendered screen text.',
|
||||
returns: 'rendered screen text, including output received before the call.',
|
||||
},
|
||||
{
|
||||
signature: 'inspectActivity(agent: Agent, id: WebTerminalId): Promise<\'idle\' | \'busy\' | \'unknown\'>',
|
||||
@@ -4979,7 +4979,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'AgentTerminal',
|
||||
declaration: 'export class AgentTerminal {\n constructor(private readonly terminal: BrowserTerminal, private readonly closeTerminal: () => Promise<void>);\n get id(): WebTerminalId;\n get info(): WebTerminalInfo;\n get revision(): number;\n changed(after: number, signal: AbortSignal): Promise<number>;\n write(data: string): Promise<void>;\n read(request: TerminalScreenRequest): Promise<TerminalScreenPage>;\n mark(): Promise<TerminalScreenMark>;\n inspectForeground(): Promise<SubprocessTerminalForeground | undefined>;\n signal(signal: SubprocessTerminalSignal): Promise<number>;\n async close(): Promise<void>;\n}',
|
||||
declaration: 'export class AgentTerminal {\n constructor(private readonly terminal: BrowserTerminal, private readonly closeTerminal: () => Promise<void>);\n get id(): WebTerminalId;\n get info(): WebTerminalInfo;\n get revision(): number;\n changed(after: number, signal: AbortSignal): Promise<number>;\n write(data: string): Promise<void>;\n drain(): Promise<void>;\n read(request: TerminalScreenRequest): TerminalScreenPage;\n mark(): TerminalScreenMark;\n inspectForeground(): Promise<SubprocessTerminalForeground | undefined>;\n signal(signal: SubprocessTerminalSignal): Promise<number>;\n async close(): Promise<void>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AgentTerminalRequest',
|
||||
@@ -5191,7 +5191,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'BrowserTerminal',
|
||||
declaration: 'export class BrowserTerminal {\n constructor(private readonly handle: SubprocessTerminalHandle, public info: WebTerminalInfo, scrollback: number, private readonly maxBufferedBytes: number);\n monitor(policy: TerminalRetentionPolicy, closing: () => void, closed: () => void, failed: (error: unknown) => void): void;\n retain(signal: AbortSignal): AsyncIterable<TerminalRetentionFrame>;\n async *attach(input: AsyncIterable<TerminalInput>, limits: Pick<TerminalEnvironment, \'maxInputBytes\' | \'maxCols\' | \'maxRows\'>, signal: AbortSignal, inputWindow?: RemoteUplinkWindow): AsyncIterable<TerminalFrame>;\n onStateChange(listener: () => void): void;\n async inspectActivity(): Promise<SubprocessTerminalActivity[\'state\']>;\n get revision(): number;\n async changed(after: number, signal: AbortSignal): Promise<number>;\n agentWrite(data: string): Promise<void>;\n agentSignal(signal: SubprocessTerminalSignal): Promise<number>;\n inspectForeground(): Promise<SubprocessTerminalForeground | undefined>;\n readScreen(request: TerminalScreenRequest): Promise<TerminalScreenPage>;\n markScreen(): Promise<TerminalScreenMark>;\n rename(title: string): void;\n close(): Promise<void>;\n dispose(): Promise<void>;\n}',
|
||||
declaration: 'export class BrowserTerminal {\n constructor(private readonly handle: SubprocessTerminalHandle, public info: WebTerminalInfo, scrollback: number, private readonly maxBufferedBytes: number);\n monitor(policy: TerminalRetentionPolicy, closing: () => void, closed: () => void, failed: (error: unknown) => void): void;\n retain(signal: AbortSignal): AsyncIterable<TerminalRetentionFrame>;\n async *attach(input: AsyncIterable<TerminalInput>, limits: Pick<TerminalEnvironment, \'maxInputBytes\' | \'maxCols\' | \'maxRows\'>, signal: AbortSignal, inputWindow?: RemoteUplinkWindow): AsyncIterable<TerminalFrame>;\n onStateChange(listener: () => void): void;\n async inspectActivity(): Promise<SubprocessTerminalActivity[\'state\']>;\n get revision(): number;\n async changed(after: number, signal: AbortSignal): Promise<number>;\n agentWrite(data: string): Promise<void>;\n agentSignal(signal: SubprocessTerminalSignal): Promise<number>;\n inspectForeground(): Promise<SubprocessTerminalForeground | undefined>;\n async drain(): Promise<void>;\n readScreen(request: TerminalScreenRequest): TerminalScreenPage;\n markScreen(): TerminalScreenMark;\n rename(title: string): void;\n close(): Promise<void>;\n dispose(): Promise<void>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'BrowserUseProviderName',
|
||||
@@ -8447,7 +8447,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'TerminalScreenMark',
|
||||
declaration: 'export interface TerminalScreenMark {\n lines(): Promise<{\n readonly lines: readonly string[];\n readonly truncated: boolean;\n }>;\n dispose(): void;\n}',
|
||||
declaration: 'export interface TerminalScreenMark {\n lines(): {\n readonly lines: readonly string[];\n readonly truncated: boolean;\n };\n dispose(): void;\n}',
|
||||
},
|
||||
{
|
||||
name: 'TerminalScreenPage',
|
||||
|
||||
Reference in new issue
Block a user