Merge branch 'master' into worktree/persistent-bash-str-replace-editor

This commit is contained in:
Tianyi Cui
2026-07-30 15:19:27 +08:00
1158 changed files with 47137 additions and 8419 deletions

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/pty/README.md
README.md: e54dcf64db083665f37b7dc19a7a92e21494442b
README.zh.md: e06ea496e389d329671c358fbde5c0849aa03cf8
README.zh.md: 0bb7e565a5799002ffb8b74dce12e129a7b2665e

View File

@@ -2,13 +2,13 @@
[English](README.md) | 中文
`PTY` 的全称是 **Pseudo-Terminal(伪终端)**。这项能力提供持久且限定所有者范围的终端会话,适用于需要跨工具调用保留状态或使用交互式 stdin 的工作流。PTY 是单次 bash 与文件系统工具的补充,不会取代后两者更严格的逐操作契约。
`PTY` 的全称是 **Pseudo-Terminal**(伪终端)。这项能力提供持久且限定所有者范围的终端会话,适用于需要跨工具调用保留状态或使用交互式 stdin 的工作流。PTY 是单次 bash 与文件系统工具的补充,不会取代后两者更严格的逐操作契约。
| 包 | 职责 | ctx 键 |
|---|---|---|
| [`pty`](pty/README.md)(`@deepseek-ai/dsh-pty`) | 后端注册表、品牌化 id、精确的 Agent 所有权、会话操作与等待完成的清理 | `ctx.pty` |
| [`pty`](pty/README.md)(`@deepseek-ai/dsh-pty`) | 后端注册表、品牌化 id、精确到 agent(智能体)的所有权、会话操作与等待清理完成的机制 | `ctx.pty` |
| `pty-local`(`@deepseek-ai/dsh-pty-local`) | 本地 `node-pty` 后端、就绪检测、有界终端状态、沙箱与进程会话监管 | 注册到 `ctx.pty` |
| `tool-pty`(`@deepseek-ai/dsh-tool-pty`) | 6 个面向模型的工具,并为后台发送集成通用任务 | 注册到 `ctx.tools` |
| `tool-pty`(`@deepseek-ai/dsh-tool-pty`) | 6 个面向模型的工具,以及用于后台发送的通用任务集成 | 注册到 `ctx.tools` |
| `tool-bash-persistent`(`@deepseek-ai/dsh-tool-bash-persistent`) | 一个由所有者隔离可复用 PTY shell 支撑的模型可见 `bash` | 消费 `ctx.pty`,注册到 `ctx.tools` |
设计与暂缓边界记录在[持久 PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md) 中。
设计与暂缓边界记录在[持久 PTY Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md) 中。

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/pty/pty-local/README.md
README.md: 6f243a6edf3ab8bc228cfda3f6b3b774dabadcbb
README.zh.md: c417828a443d227b5bfcdc1c788cf4ab6751b2ee
README.zh.md: 0cf2981332fa41dab22a496de5ae302066fbdba8

View File

@@ -6,11 +6,11 @@
## 插件(`pty-local`)
该插件注入 `pty`、`sandbox` 和 `sandboxPolicy`,然后注册所配置的后端类型(`shell`)。`danger-full-access` 会直接启动 shell;受限模式则通过 `ctx.sandbox` 包装确切的 shell argv。系统在 spawn 时解析会话的实际模式。当某个所有者存在开放的 PTY 或正在进行 spawn 时,如果配置变更会得到不同的实际模式,系统会在对应 `sandbox/mode` 事件提交前拒绝该变更。该限制绑定到确切所有者,因此即使本地提供方重新加载并保留现有会话,它仍然有效。更改模式前,请等待创建结算并关闭会话,避免以更宽权限打开的终端在权限降级后继续存在。
该插件注入 `pty`、`sandbox` 和 `sandboxPolicy`,然后注册所配置的后端类型(`shell`)。`danger-full-access` 会直接启动 shell;受限模式则通过 `ctx.sandbox` 包装确切的 shell argv。系统在 spawn 时解析会话的实际模式。当某个所有者存在开放的 PTY 或正在进行 spawn 时,如果配置变更会得到不同的实际模式,系统会在对应 `sandbox/mode` 事件提交前拒绝该变更。该限制绑定到确切所有者,因此即使本地提供方重新加载并保留现有会话,它仍然有效。更改模式前,请等待创建完成并关闭会话,避免以更宽权限打开的终端在权限降级后继续存在。
Linux 的就绪检测结合以下机制:由前台状态验证的私有 bash 提示符标记、前台进程组 syscall 检查、静默回退和绝对超时。macOS 没有 `/proc` syscall 接口,因此使用经过验证的提示符标记以及静默/超时。当可打印的提示符文本尚未到达时,即使 OSC 标记和 `PS1` 被拆到多个数据回调中,系统也不会把标记视为就绪。如果 bash 在内核发布其重新取得前台进程组的状态前打印标记,轮询会在普通静默上限之后再保留该候选状态 `handoffGraceMs`,使恰好同时发生的前台交接有机会胜出;该宽限至少要覆盖一个 `pollIntervalMs`,否则加载时即被拒绝。因此,继承 `PROMPT_COMMAND` 的交互式子进程无法持续压制推断空闲就绪,最多只能延续到绝对超时。无法识别或读取的进程状态绝不会作为精确空闲的正向信号。同样,一次 send 之前就已存在的前台进程组 stdin 等待并不代表写入后就绪:必须先观察到同一进程组脱离该等待,之后再次进入等待才能使该次 send 完成;前台进程组发生变化则构成新的证据。尚未发布的启动过程中,回退路径要求已经观察到输出;零输出静默不能发布空会话,超时则拒绝 spawn。取消操作会关闭尚未发布的 shell,并以调用方提供的确切中止原因拒绝,即使当时还无法观察其前台进程组。如果关闭失败,`PtyBackendCleanupError` 会单独保留清理失败,供注册表释放资源时处理。未完成的终端控制序列受 `maxReadBytes` 限制;超过上限后,系统会丢弃内容直到其终止符。末尾的回车会跨回调保留,使拆分的 CRLF 合并为一个换行。
Linux 的就绪检测结合以下机制:由前台状态验证的私有 bash 提示符标记、前台进程组 syscall 检查、静默回退和绝对超时。macOS 没有 `/proc` syscall 接口,因此使用经过验证的提示符标记以及静默/超时。当可打印的提示符文本尚未到达时,即使 OSC 标记和 `PS1` 被拆到多个数据回调中,系统也不会把标记视为就绪。如果 bash 在内核发布其重新取得前台进程组的状态前打印标记,轮询会在普通静默上限之后再保留该候选状态 `handoffGraceMs`,使恰好同时发生的前台交接有机会胜出;该宽限至少要覆盖一个 `pollIntervalMs`,否则加载时即被拒绝。因此,继承 `PROMPT_COMMAND` 的交互式子进程无法一直抑制推断空闲就绪直至绝对超时。无法识别或读取的进程状态绝不会作为精确空闲的正向信号。同样,一次 send 之前就已存在的前台进程组 stdin 等待并不代表写入后就绪:必须先观察到同一进程组脱离该等待,之后再次进入等待才能使该次 send 完成;前台进程组发生变化则构成新的证据。尚未发布的启动过程中,回退路径要求已经观察到输出;零输出静默不能发布空会话,超时则拒绝 spawn。取消操作会关闭尚未发布的 shell,并以调用方提供的确切中止原因拒绝,即使当时还无法观察其前台进程组。如果关闭失败,`PtyBackendCleanupError` 会单独保留清理失败,供注册表 dispose(资源释放)时处理。未完成的终端控制序列受 `maxReadBytes` 限制;超过上限后,系统会丢弃内容直到其终止符。末尾的回车会跨回调保留,使拆分的 CRLF 合并为一个换行。
取消发送时,系统会解析当前前台进程组并发送真正的 `SIGINT`;它绝不会通过写入 `\x03` 模拟中断,因此原始模式程序仍可取消。关闭操作先向后代发送 `SIGTERM` 并等待,再向已捕获的存活进程与新扫描到的后代之并集发送 `SIGKILL`,防止进程通过重新设定父进程而逃避清理。系统确认每个保留的进程身份都已消失;在 Linux 上,非执行中的僵尸进程也视为完全停稳,并会随 shell 退出而回收。如果仍有进程存活,失败结果不会缓存成永久拒绝的关闭操作;后续关闭仍会重试清理。
取消发送时,系统会解析当前前台进程组并发送真正的 `SIGINT`;它绝不会通过写入 `\x03` 模拟中断,因此,即使程序运行在 raw 模式下,也仍可取消。关闭操作先向后代发送 `SIGTERM` 并等待,再向已捕获的存活进程与新扫描到的后代之并集发送 `SIGKILL`,防止进程通过重新设定父进程而逃避清理。在停止 shell 前,系统会确认每个保留的进程身份都已消失,或者在 Linux 上已成为不再执行的僵尸进程;僵尸进程条目视为完全停稳,并会随 shell 退出而回收。如果仍有进程存活,失败结果不会缓存成永久拒绝的关闭操作;后续关闭仍会重试清理。
## 模型体验
@@ -22,15 +22,15 @@ Linux 的就绪检测结合以下机制:由前台状态验证的私有 bash
#### Token 影响
消费方返回有界的后端输出前没有影响。此包不会把保留的 PTY scrollback 放入模型历史。
消费方返回有界的后端输出前没有影响。此包(package)不会把保留的 PTY scrollback 放入模型历史。
#### KV Cache 影响
不会直接失效;提示词、schema 与追加结果由消费方负责。
不会直接使 KV Cache 失效;提示词、schema 与追加结果由消费方负责。
## 已知限制与暂缓工作
## 已知限制与暂缓事项
- 输出按行规范化;不支持全屏备用缓冲区交互。
- Linux 精确探针支持 x64 与 arm64 UAPI 表;其他架构使用提示符标记和静默/超时就绪机制。
- 如果后代进程在清理前守护化并重新设定父进程,它会脱离已捕获的进程树;清理绝不会扩大到启动器 PID 所属的整个 POSIX 会话,因为其中可能包含无关进程。
- 会话无法跨 harness 进程退出保留。
- harness 进程退出后,会话无法继续存在。

View File

@@ -42,7 +42,7 @@ function agent(ctx: Context): Agent {
const id = SessionId('agent')
return {
id, options: {}, session: new Session(id), status: 'idle', acceptsNextStep: false, ctx,
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, cancel() {}, whenIdle: () => Promise.resolve(),
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, updateInbox: () => 'not-found', cancel() {}, whenIdle: () => Promise.resolve(),
}
}
@@ -249,7 +249,7 @@ describe('pty-local plugin shape', () => {
const ownerFiber = await ctx.plugin(() => {})
const owner: Agent = {
id: session.id, options: {}, session, status: 'idle', acceptsNextStep: false, ctx: ownerFiber.ctx,
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, cancel() {}, whenIdle: () => Promise.resolve(),
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, updateInbox: () => 'not-found', cancel() {}, whenIdle: () => Promise.resolve(),
}
ctx.agents.register(owner)
const providerFiber = await registerStubLocalBackend(ctx, () => stubLocalSession())
@@ -292,7 +292,7 @@ describe('pty-local plugin shape', () => {
const ownerFiber = await ctx.plugin(() => {})
const owner: Agent = {
id: session.id, options: {}, session, status: 'idle', acceptsNextStep: false, ctx: ownerFiber.ctx,
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, cancel() {}, whenIdle: () => Promise.resolve(),
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, updateInbox: () => 'not-found', cancel() {}, whenIdle: () => Promise.resolve(),
}
ctx.agents.register(owner)
const gate = Promise.withResolvers<undefined>()

View File

@@ -35,7 +35,7 @@ function stubAgent(ctx: Context, rawId: string): Agent {
const scope = ctx.plugin(() => {})
return {
id, options: {}, session: new Session(id), status: 'idle', acceptsNextStep: false, ctx: scope.ctx,
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, cancel() {}, whenIdle: () => Promise.resolve(),
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, updateInbox: () => 'not-found', cancel() {}, whenIdle: () => Promise.resolve(),
}
}

View File

@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
# pnpm run verify-translation-pairing --write packages/pty/pty/README.md
README.md: 0f8b8e499dc81ce91e249f44bb38c8cc1af89d3f
README.zh.md: 9158afd6a7c82db27820d94cdbebd4eaa537daab
README.zh.md: 95c6345a8077d072cd22e421d3cfbbc9bf2e89b6

View File

@@ -2,20 +2,20 @@
[English](README.md) | 中文
限定所有者范围的持久 PTY seam。`PtyService` 注册为 `ctx.pty`,生成不透明的会话 id,通过具名后端路由创建操作,将每个操作限制在完全相同的活跃 `Agent` 内,并在该 agent 或服务释放资源时等待后端完全停稳。
限定所有者范围的持久 PTY seam。`PtyService` 注册为 `ctx.pty`,生成不透明的会话 id,通过具名后端路由创建操作,将每个操作限制在完全相同的活跃 `Agent` 内,并在该 agent(智能体)或服务 dispose(资源释放)时等待后端完全停稳。
## 契约
- 后端注册一个稳定的 `type`,并返回尚未发布的 `PtyBackendSession`;失败或取消的设置过程必须清理部分资源。若清理失败,则以 `PtyBackendCleanupError` 拒绝,使注册表能在取消之后继续保留该资源。
- spawn 取消会保留调用方提供的确切中止原因。后端设置完成后,服务资源释放与所有者消失仍分别对应可供机器路由的不同失败。
- 所有者与服务的资源释放会通过服务所有的信号中止尚未发布的设置,并等待后端结算和回滚后才返回。
- 如果回滚关闭失败,或后端报告启动清理失败,资源释放生命周期会以拒绝结束,不会声称已经完全停稳。调用方触发的取消仍收到其确切原因;生命周期触发的回滚失败也会拒绝待完成的 spawn。
- 调用方取消后发生的后端清理失败仍算作所有者活动,直到所有者或服务释放资源并消费、报告该失败,避免生命周期策略把失败的清理误判为完全停稳。
- 后端注册一个稳定的 `type`,并返回尚未发布的 `PtyBackendSession`;失败或取消的设置过程必须清理部分资源。若清理失败,则以 `PtyBackendCleanupError` 拒绝,使注册表能在取消后继续保留该清理失败。
- spawn 取消会保留调用方提供的确切中止原因。后端设置完成后,服务 dispose 与所有者消失仍分别对应可供机器路由的不同失败。
- 所有者与服务的 dispose 会通过服务持有的信号中止尚未发布的设置,并等待后端结算和回滚后才返回。
- 如果回滚关闭失败,或后端报告启动清理失败,dispose 生命周期会以拒绝结束,不会声称已经完全停稳。调用方触发的取消仍收到其确切原因;生命周期触发的回滚失败也会拒绝待完成的 spawn。
- 调用方取消后发生的后端清理失败仍算作所有者活动,直到所有者或服务 dispose 并消费、报告该失败,避免生命周期策略把失败的清理误判为完全停稳。
- `hasOwnerActivity(owner)` 覆盖从尚未发布的设置到最终关闭的全过程,使生命周期策略能精确限制对应所有者,不受发布竞态影响。
- 成功的 spawn 会发布一个 `PtySessionId`。可选的 `name` 只是所有者本地的显示元数据,绝不代表权限。
- 一个会话最多接受一个活跃的发送操作。读取和信号操作可以观察该发送;在当前操作结算前,另一项发送会失败。
- `PtySendResult.waitReason` 与 `sessionStatus` 相互独立。`session_exit` 描述顶层 PTY 进程,而不是任意前台命令。
- `kill()` 与资源释放只会在后端捕获的进程树完全停稳后完成。清理失败会以拒绝结束,而非声称成功;同时它会清除匹配的后端和注册表限制,使后续关闭能够重试,且不会干扰较新的尝试。
- `kill()` 与 dispose 只会在后端捕获的进程树完全停稳后完成。清理失败会以拒绝结束,而非声称成功;同时它会清除匹配的后端和注册表限制,使后续关闭能够重试,且不会干扰较新的尝试。
该 seam 不包含 `node-pty`、沙箱、工具 schema、提示词、任务或终端渲染策略。实现负责终端机制;消费方负责模型呈现和可选的后台任务注册。
@@ -25,7 +25,7 @@
#### 模型看到的内容
没有直接可见内容。此包不注册提示词或工具;可见 schema 和结果文本由 `@deepseek-ai/dsh-tool-pty` 负责。
没有直接可见内容。此包(package)不注册提示词或工具;可见 schema 和结果文本由 `@deepseek-ai/dsh-tool-pty` 负责。
#### Token 影响
@@ -33,9 +33,9 @@
#### KV Cache 影响
不会直接失效;由具名消费方负责请求前缀变更。
不会直接失效;请求前缀变更由上述消费方负责。
## 已知限制与暂缓工作
## 已知限制与暂缓事项
- 会话只存在于进程本地,harness 重启后不会恢复。
- 系统有意不支持跨 agent 共享;未来的共享会话设计需要独立的权限契约。

View File

@@ -282,7 +282,7 @@ export class PtyService extends Service {
* @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 = 'model request'): Promise<boolean> {
async kill(owner: Agent, id: PtySessionId, reason: string = 'model request'): Promise<boolean> {
const record = this.expectOwned(owner, id)
if (record.closing !== undefined) {
await record.closing

View File

@@ -32,6 +32,7 @@ function stubAgent(ctx: Context, rawId: string): Agent {
steer: () => {},
inject: () => {},
send: () => {},
updateInbox: () => 'not-found',
cancel() {},
whenIdle: () => Promise.resolve(),
}

View File

@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
# pnpm run verify-translation-pairing --write packages/pty/tool-pty/README.md
README.md: 417cf7bd0e7881f6ffefc47f5200164464b516f6
README.zh.md: cdce3c25a6364676a6cd69c30dd877cbc8091778
README.zh.md: 72eb13957c62cbb08dcecce5971bb064d5085470

View File

@@ -2,9 +2,9 @@
[English](README.md) | 中文
基于 `ctx.pty` 提供 6 个面向模型的工具:`terminal_open`、`terminal_send`、`terminal_read`、`terminal_signal`、`terminal_close` 和 `terminal_list`。每项操作都要求提供完全相同的发起 `Agent`,因此即使模型获知另一个 agent 的 id,也无法操作其终端。
基于 `ctx.pty` 提供 6 个面向模型的工具:`terminal_open`、`terminal_send`、`terminal_read`、`terminal_signal`、`terminal_close` 和 `terminal_list`。每项操作都要求提供完全相同的发起 `Agent`,因此即使模型获知另一个 agent(智能体)的 id,也无法操作其终端。
`terminal_send(run_in_background: true)` 会复用 `ctx.tasks`;任务预检和 PTY 服务对每会话发送的独占预留都发生在返回 task id 之前。系统通过 `task_output` 收集完成结果,`task_kill` 则向前台进程组发送 `SIGINT`。前台发送使用终端调用/结果卡片。后台发送使用通用执行卡片;打开、读取、发送信号、关闭和列出操作则分别使用通用 `execute`、`read`、`execute`、`delete` 和 `read` 卡片。所有操作都不声明源位置。
`terminal_send(run_in_background: true)` 会复用 `ctx.tasks`;任务预检和 PTY 服务对每个会话的独占发送预留都发生在返回 task id 之前。系统通过 `task_output` 收集完成结果,`task_kill` 则向前台进程组发送 `SIGINT`。前台发送使用终端调用/结果卡片。后台发送使用通用执行卡片;打开、读取、发送信号、关闭和列出操作则分别使用通用 `execute`、`read`、`execute`、`delete` 和 `read` 卡片。所有操作都不声明源位置。
## 配置
@@ -13,7 +13,7 @@
| `enableRunInBackground` | `true` | 公开并接受 `run_in_background`;设为 false 时,schema 会省略该字段,并拒绝强行传入未声明的参数 |
| `maxResultBytes` | `262144` | 每个完整终端结果或 PTY 任务输出的 UTF-8 上限(最小值 `64`);在等待、会话、分页、截断和任务状态元数据全部加入后计算 |
两个值都会在加载时验证。最小结果上限可保证注册表签发的每个会话或 task id 都能出现在创建确认中。结果超过 `maxResultBytes` 时,只要空间允许,渲染会为控制元数据和截断标记预留空间;截断会保留 UTF-8 边界。每个终端定义的最终内容回调都会应用同一个上限,涵盖经过规范化的 pre-execute、around-execute 与 post-execute 策略失败、拒绝、短路、替换或阻止;结构化的多块策略结果保留其形状。
两个值都会在加载时验证。最小结果上限可保证注册表签发的每个会话或 task id 都能出现在创建确认中。结果超过 `maxResultBytes` 时,只要空间允许,渲染会为控制元数据和截断标记预留空间;截断会保留 UTF-8 边界。每个终端定义的最终内容回调都会应用同一个上限,涵盖经过规范化的 pre-execute、around-execute 与 post-execute 策略失败、拒绝、短路、替换或阻止;结构化的多块策略结果保留其结构。
## 模型体验
@@ -55,7 +55,7 @@ Use a terminal session only when work needs persistent terminal state or interac
#### 模型看到的内容
spawn 会返回 id 和有界 MOTD。发送/读取会返回有界终端文本以及就绪/历史标记。后台模式返回通用 task id。所有终端自身或策略产生的单文本结果,在经过规范化的工具或流水线错误、拒绝、短路、替换、阻止与通用任务状态文本之后,都受 `maxResultBytes` 限制。结构化的多块策略结果保留其形状。结果会保留在会话历史中直到压缩;增量任务读取不会重复已经消费的输出。编程调用方会收到带类型的会话快照、有界的提供方读取/发送 DTO、信号与关闭结果,或 `{ kind: "background", taskId }`;Native 渲染会应用上述呈现上限。
spawn 会返回 id 和有界 MOTD。发送/读取会返回有界终端文本以及就绪/历史标记。后台模式返回通用 task id。所有终端自身或策略产生的单文本结果,在经过规范化的工具或流水线错误、拒绝、短路、替换、阻止与通用任务状态文本之后,都受 `maxResultBytes` 限制。结构化的多块策略结果保留其结构。结果会保留在会话历史中直到压缩(compaction);增量任务读取不会重复已经消费的输出。编程调用方会收到带类型的会话快照、有界的提供方读取/发送 DTO、信号与关闭结果,或 `{ kind: "background", taskId }`;Native 渲染会应用上述呈现上限。
#### Token 影响
@@ -65,7 +65,7 @@ spawn 会返回 id 和有界 MOTD。发送/读取会返回有界终端文本
仅追加;新结果位于可复用请求前缀之后。
## 已知限制与暂缓工作
## 已知限制与暂缓事项
- 不公开具名按键序列、TUI、BEL、调整大小、自动启动或跨 agent 共享 schema。
- 后台模式同时依赖 `@deepseek-ai/dsh-tasks` 及其面向模型的控制接口。

View File

@@ -40,7 +40,7 @@ function agent(ctx: Context): Agent {
const id = SessionId('pty-loader-agent')
const value: Agent = {
id, options: {}, session: new Session(id), status: 'idle', acceptsNextStep: false, ctx: scope.ctx,
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, cancel() {}, whenIdle: () => Promise.resolve(),
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, updateInbox: () => 'not-found', cancel() {}, whenIdle: () => Promise.resolve(),
}
ctx.agents.register(value)
return value

View File

@@ -18,7 +18,7 @@ function fakeAgent(ctx: Context, rawId: string): Agent {
const id = SessionId(rawId)
const agent: Agent = {
id, options: {}, session: new Session(id), status: 'idle', acceptsNextStep: false, ctx: scope.ctx,
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, cancel() {}, whenIdle: () => Promise.resolve(),
followup: () => {}, steer: () => {}, inject: () => {}, send: () => {}, updateInbox: () => 'not-found', cancel() {}, whenIdle: () => Promise.resolve(),
}
ctx.agents.register(agent)
return agent