185 lines
7.9 KiB
Markdown
185 lines
7.9 KiB
Markdown
# 持久 PTY 会话
|
|
|
|
[English](pty.md) | 中文
|
|
|
|
PTY 后端、`ctx.pty` 与面向模型的消费方共享的类型。[持久 PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md) 负责记录决策依据;本页记录来自 [`packages/pty/pty/src/types.ts`](../../packages/pty/pty/src/types.ts) 的跨包词汇。
|
|
|
|
## 标识与就绪
|
|
|
|
`PtySessionId` 是由服务铸造的品牌化 id。可选名称是拥有者本地的显示元数据;授权比较的是拥有该会话的确切 `Agent`,而不是名称或猜测的 id。
|
|
|
|
`PtyWaitReason` 说明一次发送为何返回。它与 `PtySessionStatus` 无关:一次发送可能因静默或超时而返回,但顶层 shell 仍然存活;`session_exit` 表示该 shell 已退出,而不是某个任意的前台子进程已退出。
|
|
|
|
```ts type-equiv
|
|
/** Why one interactive send returned control to its caller. */
|
|
type PtyWaitReason = 'stdin_read' | 'inferred_idle' | 'timeout' | 'session_exit'
|
|
```
|
|
|
|
```ts type-equiv
|
|
/** Top-level PTY process status, independent of a send's wait reason. */
|
|
type PtySessionStatus =
|
|
| { kind: 'running' }
|
|
| { kind: 'exited'; exitCode: number | null; signal: NodeJS.Signals | null }
|
|
```
|
|
|
|
## 后端与活跃会话
|
|
|
|
后端负责启动某种已注册类型的会话并检测其就绪状态。`PtyService` 只在初始化成功后才发布返回的会话,随后负责 id 授权与清理。无法清理部分启动资源时,后端会以 `PtyBackendCleanupError` 拒绝启动;这样,资源释放流程既能保留清理失败,也不会用它替换调用方的取消原因。后端会话拥有终端状态,并负责让已捕获的资源完全停稳。
|
|
|
|
```ts type-equiv
|
|
/** Replaceable provider for one PTY session type. */
|
|
interface PtyBackend {
|
|
/** Stable type selected by {@link PtySpawnRequest.type}. */
|
|
readonly type: string
|
|
/** Create an unpublished session or reject after cleaning partial resources; cleanup failure uses {@link PtyBackendCleanupError}. */
|
|
spawn(spec: PtyBackendSpawnSpec): Promise<PtyBackendSession>
|
|
}
|
|
```
|
|
|
|
```ts type-equiv
|
|
/** Backend-owned live session retained by {@link PtyService}. */
|
|
interface PtyBackendSession {
|
|
/** Initial bounded terminal output returned from `terminal_open`. */
|
|
readonly motd: string
|
|
/** Top-level process id when one exists. */
|
|
readonly pid?: number
|
|
/** Start one exclusive send operation. */
|
|
startSend(request: PtySendRequest): PtySendOperation
|
|
/** Read one bounded page from retained scrollback. */
|
|
read(request: PtyReadRequest): PtyReadResult
|
|
/** Signal the verified foreground process group. */
|
|
signal(signal: PtySignal): Promise<PtySignalResult>
|
|
/** Observe top-level process status. */
|
|
status(): PtySessionStatus
|
|
/** Idempotently close the captured owned process tree and await quiescence. */
|
|
close(reason: string): Promise<void>
|
|
}
|
|
```
|
|
|
|
## 发送与保留输出
|
|
|
|
一个活跃会话同时只接受一个活动发送。该操作向通用后台任务提供读取后即推进的输出游标,并向前台调用方提供最终结果。`PtyReadResult` 则为有界的会话 scrollback 单独分页。
|
|
|
|
```ts type-equiv
|
|
/** Live backend-owned send; exactly one may be active per PTY session. */
|
|
interface PtySendOperation {
|
|
/** Resolves after readiness, timeout, cancellation, or top-level process exit. */
|
|
done: Promise<PtySendResult>
|
|
/** Consume output produced since the prior call. */
|
|
readOutput(): PtySendRead
|
|
/** Request `SIGINT`; returns false after the operation settled. */
|
|
cancel(): boolean
|
|
}
|
|
```
|
|
|
|
```ts type-equiv
|
|
/** Settled result for one foreground or background send. */
|
|
interface PtySendResult {
|
|
/** Bounded rendered terminal delta remaining at settlement. */
|
|
viewport: string
|
|
/** Why the wait returned; this does not imply arbitrary child-process exit. */
|
|
waitReason: PtyWaitReason
|
|
/** Top-level session status observed at settlement. */
|
|
sessionStatus: PtySessionStatus
|
|
/** Whether output was dropped from the operation or retained scrollback. */
|
|
truncated: boolean
|
|
}
|
|
```
|
|
|
|
## 归属与持久性
|
|
|
|
`PtyService` 会将一项等待完成的清理附加到确切的拥有者作用域,拒绝其他拥有者的操作,并让会话在后端或工具插件重载期间保持存活。PTY 状态与原始字节仍局限在进程内。模型输入与有界返回输出通过现有 `tool/call`、`tool/result` 和任务结果路径持久保存,而不是重复记录 PTY 会话事件。
|
|
|
|
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
|
|
|
<a id="cordis-surface"></a>
|
|
|
|
## Cordis API
|
|
|
|
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
|
|
|
|
<a id="ctxpty--ptyservice"></a>
|
|
|
|
### `ctx.pty` — `PtyService`
|
|
|
|
In-process registry for replaceable PTY backends and exact-Agent sessions.
|
|
|
|
```ts cordis-catalog
|
|
/**
|
|
* Register one backend type for this effect scope.
|
|
* @param backend - provider with a non-empty unique type.
|
|
* @returns disposer that removes exactly this contribution.
|
|
*/
|
|
registerBackend(backend: PtyBackend): () => void
|
|
|
|
/**
|
|
* List registered backend types in registration order.
|
|
* @returns fresh backend type names.
|
|
*/
|
|
listBackends(): string[]
|
|
|
|
/**
|
|
* Create and publish one owner-scoped session after backend setup succeeds.
|
|
* @param owner - exact registered Agent that owns access and cleanup.
|
|
* @param request - backend type plus optional owner-local name and cwd.
|
|
* @param signal - cancellation of unpublished setup.
|
|
* @returns published identity, metadata, status, and MOTD.
|
|
*/
|
|
async spawn(owner: Agent, request: PtySpawnRequest, signal?: AbortSignal): Promise<PtySpawnResult>
|
|
|
|
/**
|
|
* Test whether an exact owner has a published session or unpublished spawn.
|
|
* @param owner - exact live owner to inspect.
|
|
* @returns true across the entire spawn-to-close interval, with no publication gap.
|
|
*/
|
|
hasOwnerActivity(owner: Agent): boolean
|
|
|
|
/**
|
|
* Start one exclusive interactive send.
|
|
* @param owner - exact session owner.
|
|
* @param id - target PTY identity.
|
|
* @param request - explicit text, submit behavior, and cancellation.
|
|
* @returns live operation handle for foreground await or task registration.
|
|
*/
|
|
startSend(owner: Agent, id: PtySessionId, request: PtySendRequest): PtySendOperation
|
|
|
|
/**
|
|
* Read one bounded scrollback page from an owned session.
|
|
* @param owner - exact session owner.
|
|
* @param id - target PTY identity.
|
|
* @param request - optional newest-relative offset and line count.
|
|
* @returns bounded retained text and pagination metadata.
|
|
*/
|
|
read(owner: Agent, id: PtySessionId, request: PtyReadRequest = {}): PtyReadResult
|
|
|
|
/**
|
|
* Deliver an allowed signal through an owned backend session.
|
|
* @param owner - exact session owner.
|
|
* @param id - target PTY identity.
|
|
* @param signal - allowed POSIX signal name.
|
|
* @returns delivered foreground process-group identity.
|
|
*/
|
|
signal(owner: Agent, id: PtySessionId, signal: PtySignal): Promise<PtySignalResult>
|
|
|
|
/**
|
|
* Close one owned session and remove it only after quiescent backend cleanup.
|
|
* @param owner - exact session owner.
|
|
* @param id - target PTY identity.
|
|
* @param reason - diagnostic cleanup reason.
|
|
* @returns true for a newly closed session, false when the same close is already in flight.
|
|
*/
|
|
async kill(owner: Agent, id: PtySessionId, reason: string = 'model request'): Promise<boolean>
|
|
|
|
/**
|
|
* List fresh snapshots for exactly one owner.
|
|
* @param owner - exact owner whose sessions are visible.
|
|
* @returns owner-visible snapshots in publication order.
|
|
*/
|
|
list(owner: Agent): PtySessionSnapshot[]
|
|
```
|
|
|
|
Types: [Agent](core.md)
|
|
|
|
Source: [`packages/pty/pty/src/index.ts:105`](../../packages/pty/pty/src/index.ts)
|
|
<!-- END GENERATED cordis-surface -->
|