@tt-a1i/openpi 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +65 -28
  2. package/SETUP.md +8 -6
  3. package/extensions/ask-user/handoff.ts +5 -1
  4. package/extensions/ask-user/index.ts +44 -0
  5. package/extensions/background-terminals/index.ts +118 -29
  6. package/extensions/background-terminals/src/domain.ts +5 -1
  7. package/extensions/background-terminals/src/manager.ts +2 -1
  8. package/extensions/background-terminals/src/prompt.ts +35 -0
  9. package/extensions/background-terminals/src/result-delivery.ts +76 -3
  10. package/extensions/background-terminals/src/ui/tool-result.ts +52 -1
  11. package/extensions/capabilities/index.ts +198 -0
  12. package/extensions/context-pivot/index.ts +21 -0
  13. package/extensions/cron/index.ts +42 -15
  14. package/extensions/execution-convergence/active-evidence.ts +129 -0
  15. package/extensions/execution-convergence/index.ts +442 -0
  16. package/extensions/execution-convergence/workspace-provenance.ts +338 -0
  17. package/extensions/file-search/index.ts +8 -1
  18. package/extensions/file-search/src/binaries.ts +2 -1
  19. package/extensions/git-info/src/runtime.ts +1 -1
  20. package/extensions/goal/controller.ts +2 -1
  21. package/extensions/goal/index.ts +20 -1
  22. package/extensions/plan-mode/bash-policy.ts +219 -42
  23. package/extensions/plan-mode/index.ts +56 -19
  24. package/extensions/setup/index.ts +96 -10
  25. package/extensions/shared/child-session.ts +40 -4
  26. package/extensions/shared/editor-layers.ts +150 -0
  27. package/extensions/shared/setup-config.ts +26 -15
  28. package/extensions/shared/setup-episode-state.ts +7 -0
  29. package/extensions/shared/tool-surface.ts +435 -0
  30. package/extensions/subagents/index.ts +179 -96
  31. package/extensions/subagents/src/manager.ts +13 -11
  32. package/extensions/subagents/src/prompt.ts +7 -7
  33. package/extensions/subagents/src/ui/takeover.ts +231 -133
  34. package/extensions/subagents/src/ui/transcript.ts +252 -37
  35. package/extensions/subagents/src/ui/wait-result.ts +6 -19
  36. package/extensions/suggestions/index.ts +27 -18
  37. package/extensions/tasks/index.ts +63 -18
  38. package/extensions/ui-customization/footer.ts +65 -11
  39. package/extensions/workflows/graph-projection.ts +6 -4
  40. package/extensions/workflows/index.ts +44 -21
  41. package/extensions/workflows/invocation-ledger.ts +8 -2
  42. package/extensions/workflows/model.ts +5 -1
  43. package/extensions/workflows/prompt.ts +10 -40
  44. package/extensions/workflows/replay-safety.ts +9 -8
  45. package/package.json +10 -10
  46. package/skills/subagents/SKILL.md +7 -1
  47. package/skills/workflows/EXAMPLES.md +58 -0
  48. package/skills/workflows/REFERENCE.md +44 -0
  49. package/skills/workflows/SKILL.md +39 -0
package/README.md CHANGED
@@ -5,12 +5,12 @@
5
5
  <h1 align="center">OpenPI</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Small harness. Deep extensions. Clean context.</strong>
8
+ <strong>Pi at the core. Power on demand.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://pi.dev">Pi</a> 加一层可靠运行时:后台执行、隔离 Subagent、可恢复 Workflow、持续任务与可观测终端。<br />
13
- 不替换 Pi,不替你选模型,也不把另一套 Agent 平台塞进来。
12
+ 默认像 <a href="https://pi.dev">Pi</a> 一样轻;任务需要时,一句话展开后台执行、隔离 Subagent、可恢复 Workflow 与持续任务。<br />
13
+ 不替换 Pi,不重写 Agent loop,不让高级能力长期占据每一次对话。
14
14
  </p>
15
15
 
16
16
  <p align="center">
@@ -23,6 +23,7 @@
23
23
 
24
24
  <p align="center">
25
25
  <a href="#30-秒开始"><strong>30 秒开始</strong></a> ·
26
+ <a href="#默认轻按需强">设计</a> ·
26
27
  <a href="#openpi-解决什么">解决什么</a> ·
27
28
  <a href="#能力地图">能力地图</a> ·
28
29
  <a href="#运行模型">运行模型</a> ·
@@ -38,6 +39,27 @@
38
39
 
39
40
  ---
40
41
 
42
+ ## 默认轻,按需强
43
+
44
+ **OpenPI 最强的地方,不是工具多,而是复杂度只在值得的时候出现。**
45
+
46
+ 普通编码任务继续走 Pi 原生路径:`read`、`bash`、`edit`、`write`,完整历史、Session compaction、工具输出边界、显式 Bash timeout 与 Provider loop。OpenPI 不额外投影历史,不改写测试超时,也不向模型塞恢复提示;只保留独立的工作区删除保护。
47
+
48
+ 任务一旦需要长期进程、并行调研、隔离实现、多阶段协作或跨回合推进,高级能力仍然完整存在。用户直接提出需求,OpenPI 就在当轮加载对应能力;没用到的能力不会常驻模型工具面。
49
+
50
+ > **轻路径不缴复杂度税,重任务不缺工程能力。** 这不是一套替代 Pi 的 Agent Runtime,而是一组遵守 Pi 生命周期、Session、Provider、模型与 Trust 边界的 Pi-native 深扩展。
51
+
52
+ | 使用场景 | 模型看到什么 | OpenPI 的行为 |
53
+ | ---------------------------- | ------------------------------------------------- | ------------------------------------------------- |
54
+ | 普通编码任务 | Pi 原生 `read` / `bash` / `edit` / `write` | 默认不常驻任何 OpenPI 模型工具 |
55
+ | 用户明确要求委派或高级能力 | 仅与意图匹配的能力组 | 在当轮开始前直接加载,不要求用户记住工具名 |
56
+ | 用户主动开启 `adaptive` | 一个小型 `openpi_load_tools` 网关 | 主模型判断确有收益时,可自主加载一个能力组 |
57
+ | 后台任务或子 Agent 已经运行 | 对应的状态、等待、继续与停止工具 | 管理面随真实资源出现,资源结束后按生命周期收敛 |
58
+
59
+ 这套设计保住了两件通常很难同时拥有的东西:Pi 的清爽基本面,以及完整工程工作台的能力上限。
60
+
61
+ ---
62
+
41
63
  ## 30 秒开始
42
64
 
43
65
  ```bash
@@ -54,7 +76,7 @@ pi install npm:@tt-a1i/openpi
54
76
  OpenPI 会把长期进程放到后台,把独立任务交给隔离 Context 的 Pi Subagent,把多阶段依赖组织成 Workflow。状态会持续显示;完整运行可从 `/ps`、`/subagents` 和 `/workflows` 检查或终止。
55
77
 
56
78
  > [!IMPORTANT]
57
- > 默认安装是安静的:不改主题、不绑定 Provider 或模型、不开启下一步预测,也不执行 post-edit 命令。OpenPI 自有偏好统一通过 `/openpi-setup` 显式配置。
79
+ > 默认安装是安静的:不改主题、不绑定 Provider 或模型、不开启下一步预测,也不执行 post-edit 命令。Capability discovery 默认 `explicit`;只有用户通过 `/openpi-setup` 选择 `adaptive` 后,模型才会常驻看到一个小型发现网关并可自主加载额外能力。
58
80
 
59
81
  ```text
60
82
  /openpi-setup
@@ -144,6 +166,8 @@ bg_start({
144
166
 
145
167
  后台进程没有 stdin。需要交互输入的命令应由用户直接运行,而不是放进后台。
146
168
 
169
+ 前台执行沿用 Pi 的 Bash 合同:`timeout` 可选且没有统一默认值,是否设置以及设置多长由模型或用户按命令语义决定。OpenPI 不再通过正则改写测试命令 timeout;确实需要有界执行时应显式传入 timeout,长期运行的 build、test、migration 或 server 可使用 Background Terminal 的生命周期能力。
170
+
147
171
  ### Pi-native Subagent:隔离 Context,不另起系统
148
172
 
149
173
  ```text
@@ -284,7 +308,7 @@ Workflow 在清理隔离 checkout 前原子保存有界 Handoff Manifest:track
284
308
 
285
309
  | 能力 | 使用方式 | 它负责什么 |
286
310
  | ------------- | --------------------------------------- | ------------------------------------------------------------------- |
287
- | Tasks | `tasks_add` / `tasks_update` / `/tasks` | 跨 Agent Run 与用户回合记录当前批次工作意图;不执行工作 |
311
+ | Tasks | `tasks_add` / `tasks_update` / `/tasks` | 逐项同步当前批次工作意图并刷新完整快照;不推断完成、不执行工作 |
288
312
  | Goal | `/goal <目标>` | 驱动一个持续到终态的自主目标;完成前要求证据审计 |
289
313
  | Plan Mode | `/plan [目标]` | 只读调研;`plan_ready` 后才准备可编辑的实施 Prompt,不自动执行 |
290
314
  | Context Pivot | `/context-pivot <下一阶段>` | Context 超过约 30K Tokens 且任务换阶段时,用自包含 Brief 替换旧噪音 |
@@ -308,7 +332,7 @@ cwd model thinking context cache cost throughput git PR
308
332
  - 支持 `powerline`、`powerline-mono`、`compact`,也支持自定义多行布局;
309
333
  - 终端变窄时按优先级隐藏次要指标,不机械截断尾部;
310
334
  - Subagent 与 Workflow 活动时自动出现,空闲时不占空间;
311
- - Bash、Write/Edit 与 Subagent 结果可独立选择 `full` 或 `compact`;
335
+ - Bash、Write/Edit 与 Subagent 结果可独立选择 `full` 或 `compact`;Subagent 的 compact 模式只显示状态摘要,不暴露原始子 Agent 正文;
312
336
  - 折叠内容用 Pi 的 `app.tools.expand` 快捷键临时展开,默认 `Ctrl+O`;
313
337
  - Git 状态本地刷新;只有显式运行 `/pr` 才查询 GitHub PR。
314
338
 
@@ -333,7 +357,7 @@ macOS/Linux arm64 与 x64 缺少二进制时,OpenPI 会从官方 Release 下
333
357
  | 终端输出 | 控制字符、方向格式符与超长内容在 ingress / render 边界清洗、限长 |
334
358
  | Shutdown | Terminal、Subagent、Workflow 都有有界取消、清理与唯一终态 |
335
359
  | 用户配置 | 单一受限 typed tool 写入;不散落扩展私有配置入口 |
336
- | 模型消费 | Suggestion 默认关闭;Subagent / Workflow 只在工具调用或用户 `/btw` 后运行 |
360
+ | 模型消费 | Suggestion 默认关闭;adaptive 仅在显式开启后允许模型自主加载能力 |
337
361
 
338
362
  可选的 [pi-intercom](https://github.com/nicobailon/pi-intercom) 只在顶层 Pi Session 加载。它使用进程级身份,而 OpenPI Child 是同一进程内的并发 Session;Child Resource Loader 会移除 pi-intercom 扩展与 Skill,避免身份串线。Replay 也不会复用其调用。
339
363
 
@@ -352,6 +376,7 @@ macOS/Linux arm64 与 x64 缺少二进制时,OpenPI 会从官方 Release 下
352
376
 
353
377
  ```text
354
378
  /openpi-setup 开启下一步预测,选择 Registry 里的轻量模型,minimal 推理
379
+ /openpi-setup 让模型在合适时自主发现并采用 OpenPI 能力
355
380
  /openpi-setup workflow 同时跑 16 个 agent,总调用最多 256
356
381
  /openpi-setup Footer 两行:cwd flex model / context cost flex git
357
382
  /openpi-setup Bash 展开,Write/Edit 保持紧凑
@@ -361,15 +386,18 @@ macOS/Linux arm64 与 x64 缺少二进制时,OpenPI 会从官方 Release 下
361
386
 
362
387
  配置保存在 `~/.pi/agent/my-pi-setup.json`,与包代码分离,升级不会覆盖。
363
388
 
389
+ 一次 `/openpi-setup` episode 最多成功写入一次;成功后配置工具立即隐藏。若随后还要修改另一项,请重新执行 `/openpi-setup <自然语言请求>`,不要让模型重调已隐藏工具,也不要绕过入口直接编辑配置文件。
390
+
364
391
  <details>
365
392
  <summary><strong>默认值</strong></summary>
366
393
 
367
394
  | 配置 | 默认值 |
368
395
  | ---------------------------- | ---------------------------------------------- |
396
+ | Capability discovery | `explicit`;`adaptive` 必须显式开启 |
369
397
  | Next-action Suggestion | 关闭;启用时显式选择 Registry 模型与 reasoning |
370
398
  | Workflow 并发 / 总调用 | 8 / 128;硬上限 64 / 1024 |
371
399
  | 大型 Header | 关闭 |
372
- | Dashboard Footer | 开启;单行 `powerline` |
400
+ | Dashboard Footer | 开启;单行 `plain` |
373
401
  | Subagent / Bash / Write/Edit | `full` / `compact` / `compact` |
374
402
  | Post-edit 命令 | 关闭;单条命令最多 500 字符 |
375
403
  | 内置角色模型 | 全部继承父模型 |
@@ -390,7 +418,7 @@ macOS/Linux arm64 与 x64 缺少二进制时,OpenPI 会从官方 Release 下
390
418
  ```bash
391
419
  git clone https://github.com/tt-a1i/openpi.git ~/work/openpi
392
420
  cd ~/work/openpi
393
- npm install
421
+ bun install --frozen-lockfile
394
422
  pi install ~/work/openpi
395
423
  ```
396
424
 
@@ -425,18 +453,23 @@ pi install npm:pi-intercom
425
453
  <details>
426
454
  <summary><strong>模型工具速查</strong></summary>
427
455
 
428
- | 工具 | 用途 |
429
- | -------------------------------------------------------------------------------------------------------- | ------------------------------ |
430
- | `bg_start`, `bg_status`, `bg_list`, `bg_watch`, `bg_kill` | 后台进程生命周期 |
431
- | `subagent_spawn`, `subagent_check`, `subagent_list`, `subagent_wait`, `subagent_send`, `subagent_cancel` | 独立子 Agent |
432
- | `workflow`, `workflow_status`, `workflow_stop` | 动态多阶段编排与运行管理 |
433
- | `tasks_add`, `tasks_update`, `tasks_list` | Session 工作项 |
434
- | `get_goal`, `create_goal`, `update_goal` | Session Goal |
435
- | `context_pivot` | Context 阶段切换 |
436
- | `ask_user`, `human_handoff` | 经复核的用户决策与用户专属操作 |
437
- | `plan_ready` | 显式完成计划,不自动开始实施 |
438
- | `fd`, `rg` | 文件发现与内容搜索 |
439
- | `configure_my_pi_setup` | 受限配置写入 |
456
+ Capability discovery 默认是 `explicit`:普通父 Session 不常驻任何 OpenPI 模型工具,首轮保持 Pi 原生 `read`、`bash`、`edit`、`write`。用户明确要求结构化搜索、Subagent、Workflow、后台进程或 Session Goal/Tasks 时,OpenPI 在 `before_agent_start` 直接加载对应能力组;明确询问 OpenPI capabilities/tools/features 时显示 `openpi_load_tools`。可通过 `/openpi-setup` 显式选择 `adaptive`:此时只让小型 `openpi_load_tools` 网关常驻,模型可在判断任务确实受益时自主加载一个能力组。该选择也授权模型启动该组内的昂贵工作,因此不作为默认值。条件句(例如 “If you delegate…”)不会被当成显式委派意图。能力组在当前 Session 内单调保持,避免反复增删工具破坏缓存。组内管理工具仍只在资源成功创建或状态确实存在后出现。Mode / Setup / Context 工具独立跟随实时状态显示和隐藏。Background、Subagent 与 Workflow 的 Skill 文件仍随包发布,但只在对应能力触发后提示读取,不常驻普通系统 Prompt。
457
+
458
+ 普通产品默认采用 Pi-native execution:保留 Pi 原生完整历史、工具输出上限、Session compaction、显式 Bash timeout 与 provider loop,不再额外做固定事务投影、成功 Bash 二次裁剪、测试 timeout 改写、重复失败硬拦或恢复/轨迹提示。OpenPI 只保留独立的工作区安全边界:阻止未授权删除 pre-existing 路径,并从实际文件状态识别本轮通过原生写入、文字重定向或 literal `mkdir -p` 创建的 scratch,避免误拦其清理。旧执行策略仅保留为受 benchmark root 门控的实验 profile,不会进入普通 Session。
459
+
460
+ | 工具 | 用途 | 可见时机 |
461
+ | -------------------------------------------------------------------------------------------------------- | ------------------------------ | -------------------------------- |
462
+ | `openpi_load_tools` | 列出或加载可选工具组 | 明确询问;或启用 `adaptive` |
463
+ | `bg_start`, `bg_status`, `bg_list`, `bg_watch`, `bg_kill` | 后台进程生命周期 | 明确意图或 adaptive;启动后展开 |
464
+ | `subagent_spawn`, `subagent_check`, `subagent_list`, `subagent_wait`, `subagent_send`, `subagent_cancel` | 独立子 Agent | 明确意图或 adaptive;创建后展开 |
465
+ | `workflow`, `workflow_status`, `workflow_stop` | 动态多阶段编排与运行管理 | 明确意图或 adaptive;运行后展开 |
466
+ | `tasks_add`, `tasks_update`, `tasks_list` | Session 工作项 | 明确意图或 adaptive;存在后展开 |
467
+ | `get_goal`, `create_goal`, `update_goal` | Session Goal | 明确意图或 adaptive;存在后展开 |
468
+ | `context_pivot` | Context 阶段切换 | Context 达到阈值时 |
469
+ | `ask_user`, `human_handoff` | 经复核的用户决策与用户专属操作 | Plan 或 Setup 进行中 |
470
+ | `plan_ready` | 显式完成计划,不自动开始实施 | Plan 调研阶段 |
471
+ | `fd`, `rg` | 文件发现与内容搜索 | 明确意图或 adaptive 加载 search |
472
+ | `configure_my_pi_setup` | 受限配置写入 | `/openpi-setup` 进行中 |
440
473
 
441
474
  </details>
442
475
 
@@ -451,14 +484,14 @@ pi install npm:pi-intercom
451
484
  <details>
452
485
  <summary><strong>安装后会自动调用额外模型吗?</strong></summary>
453
486
 
454
- 不会。Suggestion 默认关闭;只有用户显式选择模型后,完整主 Agent Run 结束时才可能增加一次小型预测调用。Subagent Workflow 也只在任务实际触发时运行。
487
+ 默认不会。Suggestion 默认关闭;Capability discovery 默认 `explicit`。如果用户显式开启 `adaptive`,网关本身不发模型请求,但主模型可以自主加载 Subagent Workflow 并启动额外模型调用;并发和 Workflow 总调用上限仍然生效。
455
488
 
456
489
  </details>
457
490
 
458
491
  <details>
459
492
  <summary><strong>Subagent 会阻塞主 Agent 吗?</strong></summary>
460
493
 
461
- `subagent_spawn` 立即返回,结束后自动回传。只有显式调用 `subagent_wait` 才会等待;它只适合下一步确实依赖结果的场景。
494
+ `subagent_spawn` 立即返回,结束后自动回传并重新唤醒主 Agent。交互会话没有其他工作时,主 Agent 应结束当前轮、让用户继续交互;“下一步依赖结果”本身不是阻塞理由。只有用户明确要求当前回复等完,或非交互自动化必须在同一次调用中返回完整结果时,才应调用 `subagent_wait`。
462
495
 
463
496
  </details>
464
497
 
@@ -497,6 +530,7 @@ Plan Mode 不猜“任意 Shell 是否只读”,只放行由已知安全零件
497
530
  ```text
498
531
  extensions/
499
532
  ├── setup/ # /openpi-setup 与受限配置工具
533
+ ├── capabilities/ # 最小能力发现入口与 Session 工具面加载
500
534
  ├── background-terminals/ # 长进程、日志、/ps
501
535
  ├── subagents/ # Pi-native Backend、角色、/subagents
502
536
  ├── workflows/ # DSL、Ledger、Graph、Replay、Artifacts
@@ -510,17 +544,20 @@ extensions/
510
544
  ├── ui-customization/ # Header、Footer、Terminal title
511
545
  └── shared/ # Child policy、配置、Worktree、终端清洗
512
546
 
513
- skills/ # Background terminal 与 Subagent 指南
547
+ skills/ # Background terminal、SubagentWorkflow 指南
514
548
  themes/ # github-dark-default
515
549
  ```
516
550
 
551
+ 开发工具链使用 Bun `1.3.14` 管理依赖和脚本,Biome 负责 TypeScript / JavaScript / JSON 格式与基础 lint;产品运行时仍是 Node,测试仍由 `node:test` 与 Vitest 执行:
552
+
517
553
  ```bash
518
- npm install
519
- npm run format:check
520
- npm run check
521
- npm test
554
+ bun install --frozen-lockfile
555
+ bun run check
556
+ bun run test
522
557
  ```
523
558
 
559
+ npm 仍用于发布包的 `pack` / clean-install 验证,因为用户通过 npm Registry 安装 OpenPI。
560
+
524
561
  测试覆盖进程树终止与竞态、Subagent 生命周期与工具边界、Workflow Sandbox / Ledger / Graph / Replay / Acceptance、Worktree 数据保全、Session 状态恢复、配置迁移和 TUI 渲染。设计记录见 [`docs/design/`](docs/design/),问题请提交到 [GitHub Issues](https://github.com/tt-a1i/openpi/issues)。
525
562
 
526
563
  ---
package/SETUP.md CHANGED
@@ -16,7 +16,7 @@ Pi installs the package dependencies automatically. Restart Pi or run `/reload`
16
16
 
17
17
  ## fd and rg tools
18
18
 
19
- The `file-search` extension registers `fd` and `rg` as model tools. No setup is normally needed: at startup it silently uses a system-installed `fd` (or `fdfind` on Debian/Ubuntu) and `rg` when available, or an existing fallback in this package's private `bin/` directory. Only when neither exists does it download an official release binary (macOS/Linux, arm64/x64, over HTTPS) into that package-local directory and show a one-time notification. If your platform is unsupported, install `fd` and `rg` with your package manager and restart Pi.
19
+ The `file-search` extension registers `fd` and `rg` as model tools. They stay outside an ordinary parent turn until the user explicitly asks to use `fd`/`rg` or structured file search, or the model loads the `search` group through `openpi_load_tools`. The gateway is shown after an explicit OpenPI-capability request, or remains visible when the user opts into adaptive discovery; child sessions may still receive `fd` and `rg` through the reviewed child-safe allowlist. No setup is normally needed: at startup it silently uses a system-installed `fd` (or `fdfind` on Debian/Ubuntu) and `rg` when available, or an existing fallback in this package's private `bin/` directory. Only when neither exists does it download an official release binary (macOS/Linux, arm64/x64, over HTTPS) into that package-local directory and show a one-time notification. If your platform is unsupported, install `fd` and `rg` with your package manager and restart Pi.
20
20
 
21
21
  ## Theme
22
22
 
@@ -28,14 +28,16 @@ Add the included theme to `~/.pi/agent/settings.json` while keeping your existin
28
28
  }
29
29
  ```
30
30
 
31
- Pi will load the extensions, skills, and theme from their directories the next time it starts.
31
+ Pi will load the extensions and theme the next time it starts. OpenPI's Background, Subagent, and Workflow Skill files remain in the package, but ordinary turns do not advertise them in the system prompt; the matching path is disclosed only after explicit capability intent or after the model loads that group through the opt-in adaptive gateway.
32
32
 
33
33
  ## Configure this package
34
34
 
35
- Use the single canonical package-owned command. `/my-pi-setup` remains a compatibility alias. With no arguments, when optional pi-intercom is absent, the interactive TUI first offers a reviewed global installation; declining changes nothing. Acceptance installs the fixed `npm:pi-intercom` source through Pi's package manager. A new private config receives `confirmSend: true` and `inboundTrigger: "replies"`; an existing preference file is never rewritten and must already define both fields. Package download failure writes no config, while uncertain activation retains the safe new config. Setup asks for `/reload` instead of loading a new broker into the running Session. The current model then explains the remaining configurable areas and uses `ask_user`: first run initializes them; later runs explain the saved state and ask whether to keep it, change one area, or review everything. With arguments, it treats the rest as a targeted natural-language request:
35
+ Use the single canonical package-owned command. `/my-pi-setup` remains a compatibility alias. With no arguments, when optional pi-intercom is absent, the interactive TUI first offers a reviewed global installation; declining changes nothing. Acceptance installs the fixed `npm:pi-intercom` source through Pi's package manager. A new private config receives `confirmSend: true` and `inboundTrigger: "replies"`; an existing preference file is never rewritten and must already define both fields. Package download failure writes no config, while uncertain activation retains the safe new config. Setup asks for `/reload` instead of loading a new broker into the running Session. The current model then explains the remaining configurable areas and uses `ask_user`: first run initializes them; later runs explain the saved state and ask whether to keep it, change one area, or review everything. With arguments, it treats the rest as a targeted natural-language request. Persist still goes through the typed `configure_my_pi_setup` tool, which is exposed only while that `/openpi-setup` episode is in flight and is hidden again afterward. One successful apply completes the episode; a later configuration change starts a new one with `/openpi-setup <request>` rather than reusing the hidden tool:
36
36
 
37
37
  ```text
38
38
  /openpi-setup
39
+ /openpi-setup 让模型在合适时自主发现并采用 OpenPI 能力
40
+ /openpi-setup 只在我明确要求时加载 OpenPI 能力
39
41
  /openpi-setup 开启下一步预测,使用 seal/deepseek-v4-flash,关闭推理
40
42
  /openpi-setup 关闭下一步预测
41
43
  /openpi-setup workflow 同时跑 16 个 agent,总任务最多 256 个
@@ -52,7 +54,7 @@ Use the single canonical package-owned command. `/my-pi-setup` remains a compati
52
54
  /openpi-setup 清除 explorer 的模型,让它继承父模型
53
55
  ```
54
56
 
55
- Next-action suggestions default to off. Run `/openpi-setup` to explicitly choose an available model and reasoning level. After a fully settled main-agent run, one suggestion may appear as dim inline text on the first row of an empty editor; reserved cells at the row end keep CJK IME preedit from overwriting it. `Right` accepts it into the editor without submitting, while any other editor input dismisses it. Suggestions are ephemeral and never enter session history or model context. Workflows default to 8 concurrent agents and 128 total agent calls per run; configurable hard maxima are 64 and 1024. The large decorative header defaults off and the custom dashboard footer defaults on with a one-line Powerline layout (`cwd model thinking context cache cost throughput |flex| git pr`). Footer presets are `powerline`, `powerline-mono`, and `compact`; style can also be set independently to `plain`, `powerline`, or `powerline-mono`. Custom layouts use a 2D `footerLines` array with at most one `flex` per row for left/right alignment. Nerd Font only affects powerline separator glyphs (``); metric text stays readable without it. Footer changes apply immediately in the active TUI session. Subagent results default to the existing full display; users who do not usually inspect implementation detail can select compact previews. Bash defaults to a folded one-line command with bounded output and a hidden-line count. Write/Edit defaults to an extra-short folded preview capped at three rendered lines including the operation header; its hidden-line hint remains inside the operation's status background. Select full independently for any category to keep it expanded. Compact views temporarily expand with `app.tools.expand` (`Ctrl+O` by default). An optional post-edit command is off by default: set one (for example `npm run format`, maximum 500 characters) and it runs once in the background after each interactive-TUI turn with successful Write/Edit operations, with failures reported as a notification. It deliberately does not guess whether arbitrary Bash commands changed files. Built-in Agent roles `explorer`, `implementer`, `reviewer`, and `advisor` are shared by `subagent_spawn.agent_type` and Workflow `agent(..., { agent_type })`; all inherit the parent model by default. `/openpi-setup` may assign a currently available Registry model to any subset; clearing one returns it to inheritance and omitted roles stay unchanged. Model precedence is explicit call > selected role-file model > setup assignment > parent inheritance; effort is explicit call > selected role > parent. A trusted project `.pi/agents/<role>.md` overrides global `~/.pi/agent/agents/<role>.md`, which overrides the complete built-in role definition; overrides are diagnosed. Role-model changes apply to the next spawn or Workflow agent call without reload. Configuration is stored privately at `~/.pi/agent/my-pi-setup.json`.
57
+ Capability discovery defaults to `explicit`, preserving the zero-resident OpenPI tool surface until the user asks for a capability. `adaptive` is an explicit opt-in that keeps only `openpi_load_tools` visible and allows the model to load a useful group on its own; because this can start Subagents, Workflows, or background processes, normal permission and configured concurrency/call limits still apply. Changing the setting updates the current Session immediately, while already loaded groups remain stable for that Session. Next-action suggestions default to off. Run `/openpi-setup` to explicitly choose an available model and reasoning level. After a fully settled main-agent run, one suggestion may appear as dim inline text on the first row of an empty editor; reserved cells at the row end keep CJK IME preedit from overwriting it. `Right` accepts it into the editor without submitting, while any other editor input dismisses it. Suggestions are ephemeral and never enter session history or model context. Workflows default to 8 concurrent agents and 128 total agent calls per run; configurable hard maxima are 64 and 1024. The large decorative header defaults off and the custom dashboard footer defaults on with a one-line Powerline layout (`cwd model thinking context cache cost throughput |flex| git pr`). Footer presets are `powerline`, `powerline-mono`, and `compact`; style can also be set independently to `plain`, `powerline`, or `powerline-mono`. Custom layouts use a 2D `footerLines` array with at most one `flex` per row for left/right alignment. Nerd Font only affects powerline separator glyphs (``); metric text stays readable without it. Footer changes apply immediately in the active TUI session. Subagent results default to the existing full display; compact mode shows only bounded status rows and keeps raw child reports behind `app.tools.expand` (`Ctrl+O` by default). Bash defaults to a folded one-line command with bounded output and a hidden-line count. Write/Edit defaults to an extra-short folded preview capped at three rendered lines including the operation header; its hidden-line hint remains inside the operation's status background. Select full independently for any category to keep it expanded. Compact views temporarily expand with `app.tools.expand`. An optional post-edit command is off by default: set one (for example `npm run format`, maximum 500 characters) and it runs once in the background after each interactive-TUI turn with successful Write/Edit operations, with failures reported as a notification. It deliberately does not guess whether arbitrary Bash commands changed files. Built-in Agent roles `explorer`, `implementer`, `reviewer`, and `advisor` are shared by `subagent_spawn.agent_type` and Workflow `agent(..., { agent_type })`; all inherit the parent model by default. `/openpi-setup` may assign a currently available Registry model to any subset; clearing one returns it to inheritance and omitted roles stay unchanged. Model precedence is explicit call > selected role-file model > setup assignment > parent inheritance; effort is explicit call > selected role > parent. A trusted project `.pi/agents/<role>.md` overrides global `~/.pi/agent/agents/<role>.md`, which overrides the complete built-in role definition; overrides are diagnosed. Role-model changes apply to the next spawn or Workflow agent call without reload. Configuration is stored privately at `~/.pi/agent/my-pi-setup.json`.
56
58
 
57
59
  ## Session Goal and Tasks
58
60
 
@@ -62,7 +64,7 @@ Model callers use `get_goal`, `create_goal`, and `update_goal`. `create_goal` is
62
64
 
63
65
  There are no normal user-facing Turn, no-progress, or wall-clock caps; a hidden 1000-continuation circuit breaker exists only to stop runaway automation. An optional `token_budget` must only be positive. Goal non-cached Assistant input-plus-output Token and elapsed-time usage are persisted; crossing the budget marks `budget_limited` and queues one wrap-up Turn. Active goals continue after reload/resume. Fork and tree navigation defer inherited active continuation until the first explicit user input; paused, blocked, and usage-limited goals remain stopped and can prompt for Resume. A v1 active/waiting goal migrates once to paused. Assistant aborts pause an active goal and Assistant errors block it. Print/json automation is inert. Footer text mirrors Codex (`Pursuing goal (…)`, resume hints, `Goal unmet`, `Goal achieved`) without showing the objective or legacy Turn counts. An achieved Footer remains visible until the next explicit interactive/RPC input, then a branch-persisted acknowledgement hides only the Footer while `/goal` retains the completed record.
64
66
 
65
- Session Tasks remain advisory multi-item work intent and do not determine Goal completion. They are scoped to the current request batch: once every item is done or dropped, the batch closes and the next `tasks_add` starts again at T1. Active items persist in a polished Claude Code-style panel above the editor; `Ctrl+Shift+T` or `/tasks hide|show|toggle` controls visibility, while `/tasks` opens the full list. No `/openpi-setup` setting or secondary judge model is required.
67
+ Session Tasks remain advisory multi-item work intent and do not determine Goal completion. They are scoped to the current request batch: once every item is done or dropped, the batch closes and the next `tasks_add` starts again at T1. The model marks a tracked item `in_progress` before starting it, records `done`, `blocked`, or `dropped` immediately after that item reaches a real outcome, and reconciles touched items before its final answer. Every add/update result returns the complete bounded current snapshot so the next item is explicit and the panel refreshes on each persisted transition. Commit, test, and authorization signals are only task-scoped evidence candidates; OpenPI never infers completion or mutates a task from those signals. Active items persist in a polished Claude Code-style panel above the editor; `Ctrl+Shift+T` or `/tasks hide|show|toggle` controls visibility, while `/tasks` opens the full list. No `/openpi-setup` setting or secondary judge model is required.
66
68
 
67
69
  ## Other commands added by this fork
68
70
 
@@ -70,5 +72,5 @@ Session Tasks remain advisory multi-item work intent and do not determine Goal c
70
72
  - `/tasks` inspects branch-scoped advisory work items.
71
73
  - `/goal ...` controls the persistent autonomous session objective.
72
74
  - `/context-pivot <next phase>` deliberately compacts a long current session into a next-phase brief. It requires at least 30,000 context tokens and is rejected below that; use the separate `/handoff` skill when work should move to a genuinely new session.
73
- - `/cron every <5m> <prompt>`, `/cron in <30s> <prompt>`, `/cron list`, and `/cron remove <id>` schedule a prompt for this session. Jobs are in-memory and session-scoped (cleared on shutdown), fire only while the session is idle, and use a duration grammar (`30s`/`5m`/`2h`, minimum 30s) rather than crontab fields, because the scheduler polls about every 30 seconds.
75
+ - `/cron every <5m> <prompt>`, `/cron in <30s> <prompt>`, `/cron list`, and `/cron remove <id>` schedule a prompt for this session. Jobs are in-memory and session-scoped (cleared on shutdown), fire only while the session is idle, and use a duration grammar (`30s`/`5m`/`2h`, minimum 30s) rather than crontab fields, because the scheduler polls about every 30 seconds. Jobs due in the same poll are delivered as one triggered turn while retaining each job's id and recurrence metadata; if that atomic delivery fails, every due job remains pending for retry.
74
76
  - `/plan [objective]` explores read-only before changing anything: while armed it blocks `edit`, `write`, mutating Bash, `subagent_send`, `workflow`, and `bg_start`, while read/grep/find/ls/fd/rg and verified read-only Git/GitHub commands stay available. It permits `subagent_spawn`, but the harness narrows every newly spawned planning child to investigation-only tools; agent types can narrow that list further, never widen it. The model must submit the complete plan through parent-only `plan_ready`; the write gate stays closed until `/plan` prepares an editable implementation prompt for the current or a fresh Session. `/plan off` cancels.
@@ -33,7 +33,11 @@ const HumanHandoffParams = Type.Object({
33
33
 
34
34
  export type HumanHandoffInput = Static<typeof HumanHandoffParams>;
35
35
  export type HumanHandoffStatus =
36
- "completed" | "unable" | "dismissed" | "cancelled" | "unavailable";
36
+ | "completed"
37
+ | "unable"
38
+ | "dismissed"
39
+ | "cancelled"
40
+ | "unavailable";
37
41
 
38
42
  export interface HumanHandoffDetails {
39
43
  status: HumanHandoffStatus;
@@ -25,6 +25,18 @@ import {
25
25
  import { Cause, Effect, Exit } from "effect";
26
26
  import { Type, type Static } from "typebox";
27
27
  import { sanitizeTerminalText } from "../shared/terminal-text.ts";
28
+ import {
29
+ PLAN_MODE_CHANNEL,
30
+ type PlanModeState,
31
+ } from "../shared/plan-mode-state.ts";
32
+ import {
33
+ OPENPI_SETUP_EPISODE_CHANNEL,
34
+ type OpenPiSetupEpisodeState,
35
+ } from "../shared/setup-episode-state.ts";
36
+ import {
37
+ OPENPI_TOOL_SURFACE,
38
+ patchOwnedTools,
39
+ } from "../shared/tool-surface.ts";
28
40
  import { createHumanHandoffToolDefinition } from "./handoff.ts";
29
41
  import {
30
42
  BRACKETED_PASTE_END,
@@ -355,6 +367,38 @@ export async function showQuestionsWithDialogs(
355
367
  }
356
368
 
357
369
  export default function askUser(pi: ExtensionAPI) {
370
+ let planning = false;
371
+ let setupActive = false;
372
+ const syncInteractionTools = () =>
373
+ patchOwnedTools(pi, "interaction", {
374
+ ...(planning || setupActive
375
+ ? { enable: OPENPI_TOOL_SURFACE.interaction.deferred }
376
+ : { disable: OPENPI_TOOL_SURFACE.interaction.deferred }),
377
+ });
378
+
379
+ pi.events.on(PLAN_MODE_CHANNEL, (state: unknown) => {
380
+ planning =
381
+ typeof state === "object" &&
382
+ state !== null &&
383
+ (state as PlanModeState).planning === true;
384
+ syncInteractionTools();
385
+ });
386
+ pi.events.on(OPENPI_SETUP_EPISODE_CHANNEL, (state: unknown) => {
387
+ setupActive =
388
+ typeof state === "object" &&
389
+ state !== null &&
390
+ (state as OpenPiSetupEpisodeState).active === true;
391
+ syncInteractionTools();
392
+ });
393
+ pi.on("session_shutdown", () => {
394
+ planning = false;
395
+ setupActive = false;
396
+ syncInteractionTools();
397
+ });
398
+ pi.on("session_start", () => {
399
+ syncInteractionTools();
400
+ });
401
+
358
402
  pi.registerTool({
359
403
  name: "ask_user",
360
404
  label: "Ask User",
@@ -28,8 +28,16 @@ import type {
28
28
  import { Type } from "typebox";
29
29
  import { loadSetupConfig } from "../shared/setup-config.ts";
30
30
  import { sanitizeTerminalText } from "../shared/terminal-text.ts";
31
+ import {
32
+ OPENPI_TOOL_SURFACE,
33
+ patchOwnedTools,
34
+ } from "../shared/tool-surface.ts";
31
35
  import type { TerminalSnapshot } from "./src/domain.ts";
32
- import { TerminalManager, type TerminalManagerShape } from "./src/manager.ts";
36
+ import {
37
+ MAX_RUNNING,
38
+ TerminalManager,
39
+ type TerminalManagerShape,
40
+ } from "./src/manager.ts";
33
41
  import {
34
42
  BG_KILL_PARAMETER_DESCRIPTIONS,
35
43
  BG_KILL_TOOL_DESCRIPTION,
@@ -45,6 +53,7 @@ import {
45
53
  buildKillReport,
46
54
  buildStartResult,
47
55
  buildStatusResult,
56
+ buildTerminalBatchResultMessage,
48
57
  buildTerminalResultMessage,
49
58
  buildWatchArmedResult,
50
59
  buildWatchMatchMessage,
@@ -52,23 +61,29 @@ import {
52
61
  } from "./src/prompt.ts";
53
62
  import {
54
63
  createDeferredResultDelivery,
64
+ createIdleResultBatcher,
65
+ hasTerminalCapacity,
55
66
  resultDeliveryOptions,
56
67
  } from "./src/result-delivery.ts";
57
- import {
58
- assertWatchableOutput,
59
- compileWatchPattern,
60
- createChunkMatcher,
61
- matchCapturedOutput,
62
- } from "./src/watch.ts";
63
68
  import {
64
69
  createTerminalRuntime,
65
70
  runTool,
66
71
  type TerminalRuntime,
67
72
  } from "./src/runtime.ts";
68
73
  import { openTerminalPicker } from "./src/ui/ps.ts";
69
- import { renderTerminalResult } from "./src/ui/tool-result.ts";
74
+ import {
75
+ renderTerminalBatchResult,
76
+ renderTerminalResult,
77
+ } from "./src/ui/tool-result.ts";
78
+ import {
79
+ assertWatchableOutput,
80
+ compileWatchPattern,
81
+ createChunkMatcher,
82
+ matchCapturedOutput,
83
+ } from "./src/watch.ts";
70
84
 
71
85
  const WIDGET_KEY = "background-terminals";
86
+ const IDLE_RESULT_BATCH_MS = 200;
72
87
 
73
88
  interface WatchToolDetails {
74
89
  id: string;
@@ -83,7 +98,16 @@ export default function (pi: ExtensionAPI) {
83
98
  let sessionContext: ExtensionContext | undefined;
84
99
  let ui: ExtensionUIContext | undefined;
85
100
  let unsubStatus: (() => void) | undefined;
101
+ let startReservations = 0;
86
102
  const resultDelivery = createDeferredResultDelivery<TerminalSnapshot>();
103
+ const hideLifecycleTools = () =>
104
+ patchOwnedTools(pi, "background", {
105
+ disable: OPENPI_TOOL_SURFACE.background.deferred,
106
+ });
107
+ const showLifecycleTools = () =>
108
+ patchOwnedTools(pi, "background", {
109
+ enable: OPENPI_TOOL_SURFACE.background.deferred,
110
+ });
87
111
  /** Active bg_watch disarm callbacks, keyed by terminal id (one per id). */
88
112
  const watchers = new Map<string, () => void>();
89
113
 
@@ -160,7 +184,9 @@ export default function (pi: ExtensionAPI) {
160
184
  customType: "background-terminal-result",
161
185
  // One message per flush, not per terminal: five processes exiting
162
186
  // together are one event to react to, not five.
163
- content: snaps.map(buildTerminalResultMessage).join("\n\n"),
187
+ content: buildTerminalBatchResultMessage(
188
+ snaps.map(buildTerminalResultMessage),
189
+ ),
164
190
  display: true,
165
191
  details:
166
192
  snaps.length === 1
@@ -198,12 +224,18 @@ export default function (pi: ExtensionAPI) {
198
224
  };
199
225
 
200
226
  const flushResults = (wake: boolean) => {
201
- const snaps = resultDelivery.drain();
202
- if (!deliverResults(snaps, wake)) {
203
- for (const snap of snaps) resultDelivery.defer(snap);
204
- }
227
+ const snaps = resultDelivery.drain(MAX_RUNNING);
228
+ if (!deliverResults(snaps, wake)) resultDelivery.restore(snaps);
205
229
  };
206
230
 
231
+ const idleResultBatcher = createIdleResultBatcher({
232
+ delayMs: IDLE_RESULT_BATCH_MS,
233
+ isIdle: () => sessionContext?.isIdle() === true,
234
+ flush: flushResults,
235
+ startTimer: (callback, delayMs) => setTimeout(callback, delayMs),
236
+ clearTimer: (timer) => clearTimeout(timer),
237
+ });
238
+
207
239
  const onSettled = (snap: TerminalSnapshot, consumed: boolean) => {
208
240
  // A settled terminal has delivered its final result and will emit no more
209
241
  // output, so any watch armed on it can never match — disarm it now instead
@@ -216,17 +248,27 @@ export default function (pi: ExtensionAPI) {
216
248
  }
217
249
  // Defer a deep-enough copy: the live snapshot's output views keep
218
250
  // mutating (late flushes) after settle.
219
- resultDelivery.defer({
251
+ const pending = resultDelivery.defer({
220
252
  ...snap,
221
253
  stdout: { ...snap.stdout },
222
254
  stderr: { ...snap.stderr },
223
255
  });
224
- // Settled while the model sits idle: it has nothing else in flight, so
225
- // this is the result it is waiting on wake it.
226
- if (sessionContext?.isIdle()) flushResults(true);
256
+ // Pending settlements remain retractable while busy, so bg_status/bg_kill
257
+ // can consume them before they are committed to context. bg_start applies
258
+ // backpressure across running + pending + reserved work, keeping this map
259
+ // bounded without dropping or prematurely queueing any result.
260
+ if (pending >= MAX_RUNNING && sessionContext?.isIdle()) {
261
+ idleResultBatcher.flushNow();
262
+ return;
263
+ }
264
+ // Give near-simultaneous idle settlements one fixed, bounded window to
265
+ // join this result. This costs one model turn for a batch instead of one
266
+ // turn per process, while preserving the immediate path after 200 ms.
267
+ if (sessionContext?.isIdle()) idleResultBatcher.schedule();
227
268
  };
228
269
 
229
270
  pi.on("session_start", (_event, ctx) => {
271
+ hideLifecycleTools();
230
272
  sessionContext = ctx;
231
273
  if (ctx.hasUI) ui = ctx.ui;
232
274
  });
@@ -236,7 +278,14 @@ export default function (pi: ExtensionAPI) {
236
278
  // double delivery is structurally impossible — whoever drains first wins.
237
279
  // These finished while the model was working on something else, so they go
238
280
  // into context without forcing a turn per stale process.
239
- pi.on("agent_settled", () => flushResults(false));
281
+ pi.on("agent_settled", () => {
282
+ idleResultBatcher.flushWithoutWake();
283
+ // A failed send is restored exactly. Drain it in bounded chunks on later
284
+ // settled turns rather than growing a single unbounded message.
285
+ if (sessionContext?.isIdle() && resultDelivery.size() > 0) {
286
+ idleResultBatcher.schedule();
287
+ }
288
+ });
240
289
 
241
290
  // /new, /resume, /fork, /reload, and quit all emit session_shutdown for
242
291
  // the old extension instance. Processes never survive a session
@@ -245,6 +294,7 @@ export default function (pi: ExtensionAPI) {
245
294
  // bounded so a wedged process cannot hang shutdown.
246
295
  pi.on("session_shutdown", async () => {
247
296
  sessionContext = undefined;
297
+ idleResultBatcher.clear();
248
298
  resultDelivery.clear();
249
299
  for (const disarm of [...watchers.values()]) disarm();
250
300
  watchers.clear();
@@ -256,6 +306,7 @@ export default function (pi: ExtensionAPI) {
256
306
  // UI may already be gone.
257
307
  }
258
308
  widgetRunning = 0;
309
+ startReservations = 0;
259
310
  ui = undefined;
260
311
  const closing = runtime;
261
312
  runtime = undefined;
@@ -309,15 +360,38 @@ export default function (pi: ExtensionAPI) {
309
360
  .replace(/\s+/g, " ")
310
361
  .trim()
311
362
  .slice(0, 80) || "terminal";
312
- const snap = await runTool(
313
- getRuntime(),
314
- manager.start({
315
- command,
316
- title,
317
- cwd,
318
- timeoutSeconds: params.timeout_seconds,
319
- }),
320
- );
363
+ const running = manager.view
364
+ .list()
365
+ .filter((entry) => entry.status === "running").length;
366
+ if (
367
+ !hasTerminalCapacity({
368
+ running,
369
+ pending: resultDelivery.size(),
370
+ reserved: startReservations,
371
+ maximum: MAX_RUNNING,
372
+ })
373
+ ) {
374
+ throw new Error(
375
+ `Max ${MAX_RUNNING} background terminals may be running or awaiting delivery. Let the current turn settle, or inspect a finished terminal with bg_status before starting another.`,
376
+ );
377
+ }
378
+ startReservations++;
379
+ let snap: TerminalSnapshot;
380
+ try {
381
+ snap = await runTool(
382
+ getRuntime(),
383
+ manager.start({
384
+ command,
385
+ title,
386
+ cwd,
387
+ timeoutSeconds: params.timeout_seconds,
388
+ }),
389
+ );
390
+ } finally {
391
+ startReservations--;
392
+ }
393
+
394
+ showLifecycleTools();
321
395
 
322
396
  return {
323
397
  content: [{ type: "text", text: buildStartResult(snap) }],
@@ -591,7 +665,24 @@ export default function (pi: ExtensionAPI) {
591
665
  status?: string;
592
666
  exitCode?: number;
593
667
  signal?: string;
668
+ results?: Array<{
669
+ id: string;
670
+ title: string;
671
+ status: string;
672
+ exitCode?: number;
673
+ signal?: string;
674
+ }>;
594
675
  };
676
+ const content =
677
+ typeof message.content === "string" ? message.content : "";
678
+ if (details.results && details.results.length > 1) {
679
+ return renderTerminalBatchResult(
680
+ content,
681
+ expanded || loadSetupConfig().ui.bashToolDisplay === "full",
682
+ theme,
683
+ details.results,
684
+ );
685
+ }
595
686
  const failed = details.status === "failed";
596
687
  const killed = details.status === "killed";
597
688
  const timedOut = details.status === "timed_out";
@@ -611,8 +702,6 @@ export default function (pi: ExtensionAPI) {
611
702
  theme.fg("accent", theme.bold(`terminal ${details.id ?? "?"}`)) +
612
703
  theme.fg("muted", ` · ${details.title ?? ""} · ${how}`);
613
704
 
614
- const content =
615
- typeof message.content === "string" ? message.content : "";
616
705
  return renderTerminalResult(
617
706
  content,
618
707
  expanded || loadSetupConfig().ui.bashToolDisplay === "full",
@@ -9,7 +9,11 @@
9
9
  import { Data } from "effect";
10
10
 
11
11
  export type TerminalStatus =
12
- "running" | "done" | "failed" | "killed" | "timed_out";
12
+ | "running"
13
+ | "done"
14
+ | "failed"
15
+ | "killed"
16
+ | "timed_out";
13
17
  // "done" = exited with code 0
14
18
  // "failed" = exited non-zero, or a spawn-level runtime error after start
15
19
  // "killed" = terminated by bg_kill, the /ps UI, or session teardown