pi-ultracode 0.3.0 → 0.3.2

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.
package/README.md CHANGED
@@ -1,433 +1,147 @@
1
- # Ultracode
1
+ # pi-ultracode
2
2
 
3
- **Ultracode** [Pi](https://github.com/badlogic/pi-mono) 的“全力执行”模式。它把 Pi 的 Agentic Loop 从单线程编码助手升级为能够运行子代理团队的编排器。
3
+ Deterministic multi-agent workflows for [Pi](https://github.com/earendil-works/pi).
4
4
 
5
- 启用 Ultracode 后,主代理会获得一条 `workflow` 工具,以及一段要求它主动使用该工具处理复杂任务的系统提示词。扩展加载时会先注册该工具,并在模型请求前的 input 与 `before_agent_start` 边界按模式校准 active tools;未启用或已暂停时,残留调用会被拒绝。每个子代理都运行在独立的内存 Pi 会话中,拥有标准编码工具、独立上下文,并可选择结构化输出、模型覆盖、Agent Type 和隔离的 git worktree。
5
+ **English** · [简体中文](https://github.com/ekil1100/pi-ultracode/blob/main/README.zh-CN.md)
6
6
 
7
- Ultracode 保留 Pi 自身的核心循环、内存模型、工具系统、扩展 API 与会话格式。它是在 Pi 之上增加编排能力,而不是替换 Pi。
7
+ `pi-ultracode` adds an opt-in high-effort mode to Pi. When a task benefits from delegation, the main agent can run a bounded JavaScript workflow with parallel subagents, isolated worktrees, structured output, durable resume, and live progress.
8
8
 
9
- ## 功能
9
+ The project combines the terminal-first agentic experience of [Claude Code](https://github.com/anthropics/claude-code) with the explicit workflow and structural-control ideas found in [Grok Build](https://github.com/xai-org/grok-build), while keeping Pi as the runtime.
10
10
 
11
- | 能力 | 说明 |
12
- | --- | --- |
13
- | `workflow` 工具 | 模型请求前按 Ultracode 状态校准;运行确定性的 JavaScript 编排脚本,并调用 `agent()`、`parallel()`、`pipeline()` 和 `workflow()`。 |
14
- | 独立子代理 | 每次 `agent()` 调用都会创建一条带标准编码工具的新 Pi 会话。 |
15
- | 并行执行 | `parallel()` 并发运行任务、保留输入顺序,并在启动前整批预留 agent slots。 |
16
- | 流水线 | `pipeline()` 让任务独立流经多个阶段,无需全局屏障。 |
17
- | 结构化输出 | 传入 JSON Schema,即可通过终止型 `structured_output` 工具获得经校验的对象。 |
18
- | Agent Type | 通过 `.pi/ultracode/agents/*.md` 定义角色提示词、工具白名单、模型和思考强度。 |
19
- | 模型覆盖 | 每次 `agent()` 调用都可指定模型模式,例如 `anthropic/claude-sonnet-4`。 |
20
- | Worktree 隔离 | 让写入型并行代理在临时 git worktree 中运行,并把改动安全合并回共享工作树。 |
21
- | 进度与详情 | 实时状态、实际模型/effort、turn/工具/token 统计,以及可流式展开的 TUI 任务详情浮层。 |
22
- | Ultracode 模式 | `/ultracode on` 启用主动编排提示词,并请求当前模型支持的最高思考强度。 |
23
- | 执行限制 | `maxAgents`(默认 128/上限 1024)、每进程且每 session/runsDir 最多 4 个活动 workflow、并发限制与嵌套深度限制。 |
24
-
25
- ## 为什么使用工作流
26
-
27
- 普通的多代理提示通常依赖模型临场决定如何分解与组合任务。Ultracode 采用另一种方式:**模型编写确定性的编排脚本,运行时执行该脚本**。
28
-
29
- 这样可以清晰表达:
30
-
31
- - 哪些任务可以并行;
32
- - 哪些阶段依赖前一阶段的结果;
33
- - 哪些代理应返回结构化数据;
34
- - 何时需要共享上下文,何时需要隔离;
35
- - 何时停止额外探索;
36
- - 失败分支应如何降级。
37
-
38
- ## 安装
11
+ ## Install
39
12
 
40
13
  ```bash
41
- pi install npm:pi-ultracode # or: pi install /path/to/pi-ultracode
14
+ pi install npm:pi-ultracode
42
15
  ```
43
16
 
44
- > **工具冲突:** pi-ultracode 注册名为 `workflow` 的工具,与 `pi-dynamic-workflows` 相同。pi-ultracode 是后者的严格超集,请先执行 `pi remove npm:pi-dynamic-workflows` 移除原型扩展。
45
-
46
- 安装后在 Pi 中运行 `/reload`。
47
-
48
- 开发仓库也可直接加载:
49
-
50
- ```bash
51
- pi -e ./extensions/ultracode.ts
52
- ```
53
-
54
- 仓库中提供两个兼容入口:
55
-
56
- - `extensions/ultracode.ts`:推荐的 Pi 扩展入口;
57
- - `src/index.ts`:兼容入口,重新导出同一个扩展。
58
-
59
- ## 快速开始
60
-
61
- 在 Pi 中启用模式:
17
+ Then reload Pi and toggle Ultracode on:
62
18
 
63
19
  ```text
64
- /ultracode on
20
+ /reload
21
+ /ultracode
65
22
  ```
66
23
 
67
- 随后给出复杂任务,例如:
68
-
69
- ```text
70
- Review the current branch, identify correctness and compatibility risks,
71
- fix the justified issues, and verify the result.
72
- ```
73
-
74
- 启用后,主代理会被要求在并行检视、迁移、审计或大范围修改明显有益时编写并运行工作流,而不是把所有工作塞进一个上下文。
75
-
76
- 也可以在启动时启用:
24
+ You can also enable it at startup:
77
25
 
78
26
  ```bash
79
27
  pi --ultracode
80
28
  ```
81
29
 
82
- 工作流运行时会内联显示实时进度,例如:
83
-
84
- ```text
85
- ◆ ▶ audit_repo (4/7 done, 2 running) · 3 cached · 203k token (141k new, 62k replayed)
86
- ✓ Survey 1/1
87
- #1 ✓ repo inventory
88
- gpt-5.6-sol • max · 15 turns · 42 tool uses · 141k token
89
- ▶ Review 3/4 · 1 running
90
- #2 ✓ auth module
91
- #3 ✓ db layer
92
- #4 ● payments module
93
- claude-sonnet-4 • high · 6 turns · 18 tool uses · 52.3k+ token · partial
94
- ▶ Verify 0/2 · 2 running
95
- ```
96
-
97
- 紧凑 token 数始终是 input + output;cache read/write 与成本只出现在任务详情中。主对话会折叠较早任务并明确显示省略数量,按 `Ctrl+O` 可展开全部,或用 `F6` 打开详情。按 `Esc` 可取消;运行中的子代理会被终止并显示为 cancelled,同时保留已有输出和部分用量。
98
-
99
- ## 命令
30
+ > `pi-ultracode` registers a tool named `workflow`. If `pi-dynamic-workflows` is installed, remove it first with `pi remove npm:pi-dynamic-workflows`.
100
31
 
101
- ```text
102
- /ultracode # toggle on/off
103
- /ultracode on # enable orchestration mode
104
- /ultracode off # disable the tool and restore the previous thinking level
105
- /ultracode status # show current status
106
- /workflows # open the interactive workflow detail overlay
107
- /workflows <runId> # open one run directly (prefix accepted)
108
- /workflows abort # abort active runs
109
- F6 # open the workflow detail overlay
110
- ```
32
+ Current releases are tested with Pi 0.84.
111
33
 
112
- `/workflows` `F6` 打开实时浮层。只有一个活动运行时会直接进入;多个活动运行会先显示选择器;没有活动运行时会打开最近完成的运行。宽终端使用任务/详情双栏,窄终端在列表与详情间切换。键盘操作:
34
+ ## Core features
113
35
 
114
- - `↑` / `↓` 选择运行或任务;`Enter` 打开;`Tab` 切换双栏焦点;`Esc` 返回或关闭;
115
- - `PageUp` / `PageDown` 滚动;`End` 恢复尾随;
116
- - `/` 搜索任务,`r` 只看运行中的任务,`a` 恢复全部,`p` 展开或收起任务 prompt。
36
+ - **Opt-in high-effort mode** requests the highest thinking level supported by the current model and restores the previous level when disabled.
37
+ - **Explicit workflows** — compose work with `agent()`, `parallel()`, `pipeline()`, and nested `workflow()` calls.
38
+ - **Independent subagents** each agent gets its own Pi session, context, tools, model selection, and optional role.
39
+ - **Parallel worktree isolation** — writing agents can work in temporary git worktrees before their patches are integrated.
40
+ - **Structured output** — validate agent results with a bounded JSON Schema subset.
41
+ - **Durable resume** — replay completed calls by stable structural identity without rerunning successful work.
42
+ - **Live observability** — inspect status, model, thinking level, turns, tool use, token usage, retries, and transcripts in the TUI.
43
+ - **Bounded execution** — lifetime agent admission, concurrency limits, strict JSON limits, worker watchdogs, and bounded cleanup.
117
44
 
118
- 每次 `agent()` 调用都是一个可独立展开的任务。详情按时间交错显示 turn、assistant 正文、工具调用、重试、上下文压缩、错误与 thinking 持续时间;thinking 正文不会被捕获。正文 delta 只保存在有界内存中并以不超过每 100ms 一次的频率刷新,消息完成后改用 Markdown 渲染。
45
+ ## Quick start
119
46
 
120
- 所有可视化和落盘内容都会移除终端控制序列并脱敏凭据。内存上限为每任务 1MiB / 5000 行、每工作流 32MiB;最终 transcript 上限为每任务 10MB(约 1MB 头部 + 9MB 尾部)、每工作流 128MB。最终清理后的 timeline 与 manifest 保存在 session 的 `ultracode-runs/` 目录,因而 `/reload` 后仍可浏览;不会复制系统 prompt、注入 skill、AGENTS 内容或父会话上下文。
121
-
122
- 状态行会显示实际生效的思考强度,例如:
47
+ Toggle Ultracode on and describe a substantial task:
123
48
 
124
49
  ```text
125
- ultracode: on · max
126
- ultracode: on · xhigh
127
- ultracode: on · high
128
- ultracode: on · off
129
- ```
130
-
131
- Ultracode 请求的是**当前模型支持的最高强度**,不是固定标签。对支持 `max` 的模型(包括 GPT-5.6 Sol)使用 `max`;能力较低的模型由 Pi 正常钳制。对不认识 `max` 的旧版 Pi,Ultracode 会自动回退到 `xhigh`。
132
-
133
- 显式覆盖始终优先:
134
-
135
- - 每次调用的模型后缀,例如 `model: "openai/gpt-5.6-sol:high"`;
136
- - Agent Type frontmatter 中的 `thinking:`;
137
- - 显式 `:off`、`:high`、`:xhigh` 或 `:max`。
138
-
139
- 模型切换或手动降低强度时,只要 Ultracode 仍启用,它就会重新请求该模型的最高可用强度。关闭或退出时,会恢复启用前的会话强度与原始默认偏好。若当前模型暂时无法表示原始强度,恢复意图会保留到后续兼容模型;中间模型的钳制结果不会覆盖原始意图。
50
+ /ultracode
140
51
 
141
- ## 工作原理
142
-
143
- 扩展注册单个 `workflow` 工具,并在 input preflight 与 `before_agent_start` 边界按 Ultracode 状态校准 active tools,同时保留其他 active tools。关闭或暂停时,`tool_call` 与实际执行入口会双重 fail-closed 拒绝残留调用。工具输入是 JavaScript 源码,其中必须包含纯字面量 `meta` 导出:
144
-
145
- ```js
146
- export const meta = {
147
- name: "review_and_fix",
148
- description: "Review independent areas, then verify the combined result",
149
- };
52
+ Review the current branch in parallel, verify every finding,
53
+ fix the justified issues, and run the relevant tests.
150
54
  ```
151
55
 
152
- 脚本可使用下列全局变量:
56
+ Pi decides whether a workflow is useful. Small tasks can still use the normal single-agent loop.
153
57
 
154
- - `agent(prompt, options)`
155
- - `parallel(thunks, options?)`
156
- - `pipeline(items, ...stages)`
157
- - `phase(title)`
158
- - `log(message)`
159
- - `workflow(nameOrRef, args)`
160
- - `args`
161
- - `cwd`
58
+ Useful commands:
162
59
 
163
- workflow 工具本身支持 `maxAgents?: integer`:默认 128,范围 1–1024。它限制同一 run 在首次执行及所有 resume 中累计获准启动的 live agent;cache replay 不重复占用额度。它不是 token budget,token/cost 仍只用于观测展示。
164
-
165
- `agent()` 的主要选项:
166
-
167
- | 选项 | 作用 |
60
+ | Command | Action |
168
61
  | --- | --- |
169
- | `label` | 进度中显示的 2–5 个词短名称。 |
170
- | `phase` | 显式指定进度阶段。 |
171
- | `schema` | 内联的有界 JSON Schema 子集(≤256 KiB、深度≤64);成功时返回经 `structured_output` 校验且≤2 MiB 的对象。 |
172
- | `model` | 按模式覆盖模型,也可带 `:off`、`:high`、`:xhigh` `:max`。 |
173
- | `agentType` | 使用内置或自定义角色。 |
174
- | `isolation: "worktree"` | 在临时 git worktree 中运行写入型代理。 |
175
-
176
- 为保证可恢复和确定性,脚本不提供 Node.js 模块加载能力,也不允许直接使用 `fs`、网络、`Date.now()`、`new Date()` 或 `Math.random()`。Worker 使用 context-realm bridge,禁用字符串/Wasm code generation,并设置 128 MiB V8 old-generation heap 上限;Intl/Temporal、ArrayBuffer、SharedArrayBuffer、typed arrays 与 WebAssembly 不向脚本开放。控制流另受循环/函数 checkpoint、host-call fuel、AST 结构限制、同步 stall watchdog 和无 host RPC 时的 idle-progress watchdog 约束。这些是确定性与 liveness guard,不是安全沙箱,也不宣称与 Grok Build 的 Rhai operation fuel 完全等价。所有 `agent()`、`parallel()`、`pipeline()` 和 `workflow()` 返回值都必须直接被 await 或 return;原生 `.then/.catch/.finally` 链、动态 method call(`value[key](...)`)和 `Promise.all/allSettled/race/any` 会在执行前被拒绝(本地异步仅保留 `new Promise(...)` 与 `Promise.resolve(...)`)。并发编排必须使用 `parallel()`/`pipeline()`,以保持 call path 可恢复且不受完成顺序影响;同 scope 的 sibling async orchestration 及未观察或仍 pending 的编排 promise 也会使 workflow 失败。
177
-
178
- 含编排调用的 helper 也属于可恢复身份的一部分,因此只支持直接声明的函数/函数变量,或声明对象/已赋给标识符的 class instance 上的静态 method call。helper alias、事后赋值 method、`this.otherMethod()` 转发、临时/awaited factory receiver 与高阶 callable 会在执行前 fail closed;请先改写为一个直接声明的 helper,再从明确的源码调用点调用它。编译器会把源码调用点、词法 loop iteration、helper caller scope、请求指纹和同请求 occurrence 组合成 durable identity,避免条件分支、循环、并行完成顺序或 resume 重试改变 cache 对应关系。
62
+ | `/ultracode` | Toggle Ultracode on or off |
63
+ | `/ultracode on` | Explicitly enable it (optional alias) |
64
+ | `/ultracode off` | Disable it and restore the previous thinking level |
65
+ | `/ultracode status` | Show the current mode and effective thinking level |
66
+ | `/workflows` or `F6` | Open the workflow browser |
67
+ | `/workflows <runId>` | Open a specific run |
68
+ | `/workflows abort` | Abort active runs |
179
69
 
180
- 资源输入输出同样有硬上限:`args` 1 MiB、schema 256 KiB/64 层、每个 agent/workflow output 2 MiB、journal 64 MiB。args/schema/output 必须是严格 JSON tree(只允许可持久化的有限 number〔不含 `-0`〕、string、boolean、null、array 与 plain object;拒绝 binary objects、accessor、稀疏数组以及循环/重复对象引用)。schema 只接受明确支持的子集:类型、object/array 结构、`enum`/`const`、`anyOf`/`allOf`、长度和数值约束及注解;未知关键字会直接拒绝。`$ref` 家族、`oneOf`、`format` 与所有 regex 关键字(包括 `pattern`/`patternProperties`)均不支持;JavaScript regex validation 无可靠执行上限。journal 在每次 append 前检查上限并校验记录语义;单进程恢复时只会修复 EOF 处的 torn JSONL 尾记录,文件中部损坏仍 fail closed。
70
+ Press `Esc` to cancel a running workflow. In Pi's fullscreen TUI, use `Ctrl+PageUp`, `Ctrl+PageDown`, and `Ctrl+End` inside workflow details.
181
71
 
182
- ### 取消与清理
72
+ ## Workflow example
183
73
 
184
- `Esc` 取消工作流时,取消信号会覆盖子代理初始化、异步 preflight 和流式执行窗口。运行器会等待进行中的 `abort()`,但 cleanup 最多等待 25 秒;即使 provider 忽略取消,workflow lease 也会在有界清理后释放。兼容旧版 Pi 时创建的临时 fallback 会话同样遵循该规则。
185
-
186
- ### 直接调用
187
-
188
- ```js
189
- export const meta = {
190
- name: "single_review",
191
- description: "Run one focused reviewer",
192
- };
193
-
194
- const result = await agent(
195
- "Inspect src/mode.ts for lifecycle bugs. Return concise findings.",
196
- { label: "mode review" },
197
- );
198
-
199
- return result;
200
- ```
201
-
202
- ### 并行检视
203
-
204
- `parallel()` 接收 thunk,而不是已经启动的 Promise。每个 panel 在任意 thunk 启动前原子预留 `options.reserveAgents ?? thunks.length` 个 agent slot;`reserveAgents` 不能小于 thunk 数。每个 thunk 保有一个独立基础 slot,其余显式预留量由 panel 共享;调用超过预留量会使整个 workflow 失败,不会临时挪用未预留的 `maxAgents` 容量。
74
+ The main agent normally writes workflows for you. Reusable workflows can also be saved under `.pi/ultracode/workflows/*.workflow.js` or `~/.pi/ultracode/workflows/*.workflow.js`.
205
75
 
206
76
  ```js
207
77
  export const meta = {
208
78
  name: "parallel_review",
209
- description: "Review independent concerns concurrently",
210
- };
211
-
212
- const tasks = [
213
- ["correctness", "Review correctness and lifecycle behavior."],
214
- ["tests", "Review test coverage and missing regressions."],
215
- ["docs", "Review README and parity documentation."],
216
- ];
217
-
218
- const results = await parallel(
219
- tasks.map(([label, prompt]) => () => agent(prompt, { label })),
220
- { reserveAgents: tasks.length },
221
- );
222
-
223
- return results.filter(Boolean);
224
- ```
225
-
226
- ### 流水线
227
-
228
- 除非后续阶段确实需要所有前置结果,否则优先使用 `pipeline()`。不同条目可独立推进:
229
-
230
- ```js
231
- export const meta = {
232
- name: "review_pipeline",
233
- description: "Inspect and verify several modules independently",
79
+ description: "Review correctness and tests in parallel",
234
80
  };
235
81
 
236
- const files = ["src/mode.ts", "src/workflow/agent-runner.ts"];
237
-
238
- const results = await pipeline(
239
- files,
240
- async (file) => agent(
241
- `Inspect ${file} and report concrete risks.`,
242
- { label: `inspect ${file}` },
82
+ const findings = await parallel([
83
+ () => agent(
84
+ "Review the current branch for correctness and lifecycle bugs.",
85
+ { label: "correctness review" },
243
86
  ),
244
- async (finding, file) => agent(
245
- `Verify this finding against ${file}:\n\n${finding}`,
246
- { label: `verify ${file}` },
87
+ () => agent(
88
+ "Review the current branch for missing or weak regression tests.",
89
+ { label: "test review" },
247
90
  ),
248
- );
91
+ ]);
249
92
 
250
- return results.filter(Boolean);
93
+ return findings.filter(Boolean);
251
94
  ```
252
95
 
253
- ### 结构化输出
254
-
255
- 不传 `schema` 时,`agent()` 返回子代理最后一段非空 assistant 文本;传入支持的 JSON Schema 子集时,则返回经校验的对象。两种返回值都必须是≤2 MiB 的严格 JSON value:
96
+ For editor IntelliSense, add:
256
97
 
257
98
  ```js
258
- export const meta = {
259
- name: "structured_audit",
260
- description: "Collect machine-readable findings",
261
- };
262
-
263
- const finding = await agent(
264
- "Audit the mode lifecycle and return one finding.",
265
- {
266
- label: "lifecycle audit",
267
- schema: {
268
- type: "object",
269
- properties: {
270
- severity: { enum: ["low", "medium", "high"] },
271
- summary: { type: "string" },
272
- },
273
- required: ["severity", "summary"],
274
- additionalProperties: false,
275
- },
276
- },
277
- );
278
-
279
- return finding;
99
+ /// <reference types="pi-ultracode/workflow" />
280
100
  ```
281
101
 
282
- 使用 schema 的代理必须调用 `structured_output`。否则该 `agent()` 分支会失败并返回 `null`;工作流本身仍可继续,因此脚本应过滤或显式处理失败分支,而不会把自由文本伪装成结构化结果。
102
+ The complete workflow contract is documented in [`types/workflow.d.ts`](types/workflow.d.ts). A larger example is available in [`examples/workflows`](examples/workflows).
283
103
 
284
- ## Agent Type
104
+ ## Roles, models, and worktrees
285
105
 
286
- 项目级角色定义放在:
106
+ Custom agent roles live in:
287
107
 
288
108
  ```text
289
109
  .pi/ultracode/agents/*.md
290
- ```
291
-
292
- 用户级角色定义放在:
293
-
294
- ```text
295
110
  ~/.pi/ultracode/agents/*.md
296
111
  ```
297
112
 
298
- 同时兼容旧的用户级目录 `~/.pi/agent/agents/*.md`。
299
-
300
- 示例:
301
-
302
- ```md
303
- ---
304
- name: reviewer
305
- description: Read-only reviewer focused on correctness and regressions
306
- tools: read, bash
307
- model: anthropic/claude-sonnet-4
308
- thinking: high
309
- ---
310
-
311
- Inspect the requested area. Do not modify files. Report only evidence-backed findings.
312
- ```
313
-
314
- 工作流中的用法:
113
+ A call can select a role, model, thinking level, or worktree isolation:
315
114
 
316
115
  ```js
317
- await agent("Review the current diff.", {
318
- label: "diff review",
319
- agentType: "reviewer",
320
- });
321
- ```
322
-
323
- 内置类型包括 `claude`、`general-purpose`、`Explore`、`Plan` 和 `code-reviewer`。
324
-
325
- Agent Type 可控制:
326
-
327
- - 角色提示词;
328
- - 工具白名单;
329
- - 默认模型;
330
- - 默认思考强度。
331
-
332
- 每次调用的选项优先于 Agent Type 默认值。
333
-
334
- ## 模型选择
335
-
336
- `model` 接受 provider/model 模式,并可带思考强度后缀:
337
-
338
- ```js
339
- await agent("Analyze this failure.", {
340
- label: "failure analysis",
341
- model: "openai/gpt-5.6-sol:max",
342
- });
343
- ```
344
-
345
- 若未指定 model,则子代理继承工作流工具创建时的 Pi 当前模型。Ultracode 启用时,默认子代理强度同样请求 `max`,并按每个代理实际选择的模型独立钳制。Ultracode 不会自动启用 GPT-5.6 的 `pro` 模式。
346
-
347
- ## Worktree 隔离
348
-
349
- 并行写入同一工作树容易产生竞态。对会修改文件的代理使用:
350
-
351
- ```js
352
- await agent("Implement the requested change and run focused tests.", {
353
- label: "implementation",
116
+ await agent("Implement and test the fix.", {
117
+ label: "implement fix",
118
+ agentType: "general-purpose",
119
+ model: "anthropic/claude-sonnet-4:high",
354
120
  isolation: "worktree",
355
121
  });
356
122
  ```
357
123
 
358
- 运行器会:
124
+ Worktree isolation requires a git repository with at least one commit. If setup fails, Ultracode logs the fallback and runs that agent in the shared working directory; avoid parallel writers in that case. A clean working tree is recommended for predictable patch integration.
359
125
 
360
- 1. 创建临时 git worktree;
361
- 2. 在隔离目录运行代理;
362
- 3. 捕获 worktree diff;
363
- 4. 检查共享工作树是否漂移;
364
- 5. 通过 `git apply --3way` 应用补丁;
365
- 6. 在失败时保存 rescue patch;
366
- 7. 成功且安全时清理临时 worktree。
126
+ ## Guardrails
367
127
 
368
- 如果 diff 捕获、rescue 保存或补丁集成异常,运行器会故障安全地保留 worktree,并在日志中给出路径,避免丢失代理改动。确认内容后可手动恢复或删除;超过保留阈值的 Ultracode worktree 会由后续运行回收。
128
+ A workflow defaults to `maxAgents: 128`, supports at most 16 concurrent agent calls, and allows one level of nested workflows. The lifetime agent limit is preserved across resumes; cached replay does not consume it again.
369
129
 
370
- 使用隔离前,共享仓库必须处于干净状态。
130
+ Workflow agent sessions retain project context and ordinary skills, but do not initialize ambient Pi extensions or expose parent orchestration tools and skills (`workflow`, `subagent`, `subagent_wait`, or `pi-subagents`). This keeps orchestration at the parent boundary and allows `pi-ultracode` and `pi-subagents` to coexist in the main session.
371
131
 
372
- ## 已保存的工作流与可恢复运行
132
+ Resume is intentionally immutable: the script, arguments, agent definitions, effective models, and call structure must still match. Changed work starts a new run.
373
133
 
374
- 可复用工作流可放在:
375
-
376
- ```text
377
- .pi/ultracode/workflows/<name>.workflow.js
378
- ~/.pi/ultracode/workflows/<name>.workflow.js
379
- ```
134
+ Token and cost data are reported for observability, not enforced as a budget. Worker and VM restrictions are determinism and liveness guards, not a security sandbox.
380
135
 
381
- 可通过工具的 `name`、`scriptPath`,或脚本内调用来运行:
136
+ ## Design inspiration
382
137
 
383
- ```js
384
- await workflow("saved_workflow_name", { target: "src" });
385
- ```
138
+ - **Claude Code** — natural-language, terminal-first agentic coding and focused delegation.
139
+ - **Grok Build** — explicit workflows, durable journals, bounded admission, and structural execution controls.
140
+ - **Pi** — the model runtime, sessions, tools, extension API, and TUI remain native Pi behavior.
386
141
 
387
- 嵌套限制为一层,且共享父运行的并发上限、`maxAgents`、当前 parallel reservations 与代理计数。由 workflow 启动的 child Pi session 会显式排除 `workflow` 工具,不能再创建一条拥有独立额度的新 workflow 链。
142
+ `pi-ultracode` is an independent Pi extension and is not affiliated with Anthropic or xAI.
388
143
 
389
- 每次运行都会把脚本和 JSONL journal 保存到:
390
-
391
- ```text
392
- <sessionDir>/ultracode-runs/<runId>.workflow.js
393
- <sessionDir>/ultracode-runs/<runId>.jsonl
394
- ```
395
-
396
- 没有 sessionDir 的独立调用使用用户目录下按 cwd 哈希隔离的 `~/.pi/ultracode-runs/<cwd-hash>/`,不会写入仓库可控制的 `.pi` symlink。run script、journal、details 和 transcript 会拒绝 symlink/non-regular artifacts,并使用 no-follow/独占或原子写入。与 Pi 和 Grok Build 一样,session 采用单进程 owner 模型;同一 Pi 进程内由 process-local lease 阻止重复运行同一 runId,但不支持两个 Pi 进程并发写同一个 session。
397
-
398
- 已有 run 可再次调用 `workflow` 并传入 `resumeFromRunId`。resume 是 immutable:顶层 script/args、实际解析到的 Agent Type 定义、effective model/thinking,以及每个 nested workflow call path 的源码/args 都必须和原 run 完全一致;args 的可观察对象键序也属于 identity。需要修改工作时必须启动新 run。成功的 agent 结果按 Worker 生成的稳定结构 call path replay,因此不受 parallel/pipeline 完成顺序影响。live admission 会在 runner side effect 前持久化,并在整个 run 生命周期累计;恢复时省略 `maxAgents` 会继承当前上限,显式值只能提高、不能降低。不存在或格式不受支持的 runId 会明确失败。
399
-
400
- ## 限制
401
-
402
- 运行时限制:
403
-
404
- - run-lifetime `maxAgents` live admission(默认 128,绝对上限 1024;cache replay 免费);
405
- - 同一进程、同一 session/runsDir 最多 4 个活动顶层 workflow;
406
- - 每个 run 最大并发 16;
407
- - 最多一层嵌套 workflow;
408
- - 10,000 次 host-call fuel、AST/checkpoint、stall 与 idle-progress 限制;
409
- - Worker V8 old-generation heap 128 MiB,并禁用 string/Wasm code generation 与二进制内存构造器;
410
- - args/schema/structured output/journal 字节与结构上限;
411
- - 取消后的 25 秒 cleanup deadline;
412
- - child session 显式排除 `workflow` 工具;
413
- - 结构化输出校验;
414
- - 静态及 context-realm 运行期确定性 guard。
415
-
416
- ## 会话与兼容性
417
-
418
- Ultracode 模式状态通过自定义 session entry 持久化。恢复、reload、fork 和 `/tree` 导航都会按当前 branch 重新读取状态;被丢弃分支中的 entry 不会错误启用模式。旧版 Pi 或旧模型会把 `max` 兼容回退为 `xhigh`,而不会把未知值静默变成 `off`。
419
-
420
- ### 0.3 迁移说明
421
-
422
- 0.3 引入 Grok Build 式结构控制:`maxAgents` 默认为 128(范围 1–1024),并在同一 run 的所有 resume 中累计 live admissions;cache replay 不重复占用。`parallel()` 会在启动前按 panel 原子预留 slots,`reserveAgents < thunks.length`、调用超过预留量或超过 `maxAgents` 都是 fatal policy failure。一个 thunk 会调用多个 agent 时,必须用 `reserveAgents` 显式覆盖全部潜在调用。resume 改为 immutable script/args,并按稳定结构 call path replay;旧的“修改脚本后最长前缀恢复”不再支持。同一进程、同一 session/runsDir 同时最多 4 个顶层 workflow,取消清理有 25 秒上限,run artifacts 拒绝 symlink。
423
-
424
- ### 0.2 迁移说明
425
-
426
- 0.2 移除了 token budget 功能:`/ultracode budget` 命令、workflow tool 的 `budget` 参数,以及 workflow 脚本中的全局 `budget` 对象都不再存在。旧的 saved workflow 如果引用 `budget.total`、`budget.remaining()` 或 `budget.spent()`,需要删除这些引用,改为显式的 agent 数量、循环条件或输入列表边界。
427
-
428
- 扩展清理只管理自己的详情浮层、status 与运行状态,不会调用 Pi 的全局 `ui.clear()`,因此不会清除其他扩展的 UI。
429
-
430
- ## 开发
144
+ ## Development
431
145
 
432
146
  ```bash
433
147
  npm ci
@@ -436,48 +150,6 @@ npm test
436
150
  npm pack --dry-run
437
151
  ```
438
152
 
439
- 项目源码位于:
440
-
441
- - `extensions/ultracode.ts`:扩展入口和 Pi 生命周期接线;
442
- - `src/mode.ts`:Ultracode 模式状态、恢复与强度管理;
443
- - `src/thinking.ts`:共享思考强度词汇与兼容性检测;
444
- - `src/workflow/`:脚本验证、运行时、子代理执行、worktree 和进度显示;
445
- - `test/`:单元、扩展和回归测试;
446
- - `docs/parity.md`:与原生 Pi 能力边界的说明。
447
-
448
- 开发依赖会安装 TypeScript 和用于测试的 Pi peer 包;通过 `pi install` 加载正式包时,Pi 仍会提供这些 peer 依赖。
449
-
450
- ## 发布
451
-
452
- npm 发布由 [`.github/workflows/publish-npm.yml`](.github/workflows/publish-npm.yml) 完成,使用 npm Trusted Publishing(OIDC),无需保存长期 `NPM_TOKEN`。
453
-
454
- 首次使用前,在 npm 的 `pi-ultracode` 包设置中添加 GitHub Actions Trusted Publisher:
455
-
456
- - Organization or user:`ekil1100`
457
- - Repository:`pi-ultracode`
458
- - Workflow filename:`publish-npm.yml`
459
- - Environment name:`npm`
460
- - Allowed action:`npm publish`
461
-
462
- 同时在 GitHub 仓库中创建名为 `npm` 的 Environment;可以按需增加审批人。正常发布无需打开 Actions 页面:
463
-
464
- ```bash
465
- npm version patch # or: minor / major / x.y.z-beta.1
466
- git push origin main --follow-tags
467
- ```
468
-
469
- `npm version` 会同步更新 `package.json` 和 `package-lock.json`,创建版本提交与 `v<version>` Tag;推送 Tag 后 CI 自动发布。稳定版本发布到 `latest`,预发布版本发布到 `next`。
470
-
471
- 需要重试或指定其他 npm dist-tag 时,也可以在 Actions 中手动运行工作流;填写完全匹配 `package.json` 的版本,且只允许从 `main` 运行。
472
-
473
- 工作流会校验 Tag 与包版本完全一致、确认版本尚未发布,并在正式发布前执行干净安装、测试和类型检查。
474
-
475
- ## 设计原则
476
-
477
- Ultracode 的目标不是“尽可能多地启动代理”,而是:
478
-
479
- > 在任务确实适合拆分、并行、独立验证或跨上下文扩展时,使用确定性编排获得更高置信度;否则继续使用 Pi 简洁直接的单代理循环。
480
-
481
- ## 许可证
153
+ ## License
482
154
 
483
155
  MIT
@@ -0,0 +1,155 @@
1
+ # pi-ultracode
2
+
3
+ 为 [Pi](https://github.com/earendil-works/pi) 提供确定性的多代理工作流。
4
+
5
+ [English](README.md) · **简体中文**
6
+
7
+ `pi-ultracode` 为 Pi 增加一个可选的高强度执行模式。当任务适合拆分时,主代理可以运行有边界的 JavaScript 工作流,使用并行子代理、隔离 worktree、结构化输出、持久恢复和实时进度。
8
+
9
+ 项目结合了 [Claude Code](https://github.com/anthropics/claude-code) 的终端 Agentic 体验,以及 [Grok Build](https://github.com/xai-org/grok-build) 的显式工作流和结构控制思路,同时继续使用 Pi 作为运行时。
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ pi install npm:pi-ultracode
15
+ ```
16
+
17
+ 然后重新加载 Pi,并用主命令切换到 Ultracode:
18
+
19
+ ```text
20
+ /reload
21
+ /ultracode
22
+ ```
23
+
24
+ 也可以在启动时直接启用:
25
+
26
+ ```bash
27
+ pi --ultracode
28
+ ```
29
+
30
+ > `pi-ultracode` 会注册名为 `workflow` 的工具。如果已安装 `pi-dynamic-workflows`,请先运行 `pi remove npm:pi-dynamic-workflows`。
31
+
32
+ 当前版本已在 Pi 0.84 上验证。
33
+
34
+ ## 核心特性
35
+
36
+ - **可选高强度模式**:请求当前模型支持的最高思考强度,关闭时恢复之前的强度。
37
+ - **显式工作流**:通过 `agent()`、`parallel()`、`pipeline()` 和嵌套 `workflow()` 组合任务。
38
+ - **独立子代理**:每个代理拥有独立的 Pi 会话、上下文、工具、模型选择和可选角色。
39
+ - **并行 worktree 隔离**:写入型代理可在临时 git worktree 中工作,再集成补丁。
40
+ - **结构化输出**:使用有边界的 JSON Schema 子集校验代理结果。
41
+ - **持久恢复**:按照稳定的结构身份复用已完成调用,不重复执行成功任务。
42
+ - **实时可观测性**:在 TUI 中查看状态、模型、思考强度、turn、工具、token、重试和 transcript。
43
+ - **有界执行**:限制累计代理数、并发、JSON 大小、Worker 运行和取消清理时间。
44
+
45
+ ## 快速开始
46
+
47
+ 用主命令开启 Ultracode,然后描述一个较复杂的任务:
48
+
49
+ ```text
50
+ /ultracode
51
+
52
+ Review the current branch in parallel, verify every finding,
53
+ fix the justified issues, and run the relevant tests.
54
+ ```
55
+
56
+ Pi 会自行判断工作流是否有帮助。小任务仍可继续使用普通的单代理循环。
57
+
58
+ 常用命令:
59
+
60
+ | 命令 | 作用 |
61
+ | --- | --- |
62
+ | `/ultracode` | 切换 Ultracode 的开启或关闭状态 |
63
+ | `/ultracode on` | 显式启用(可选别名) |
64
+ | `/ultracode off` | 关闭并恢复之前的思考强度 |
65
+ | `/ultracode status` | 查看当前模式和实际思考强度 |
66
+ | `/workflows` 或 `F6` | 打开工作流浏览器 |
67
+ | `/workflows <runId>` | 打开指定运行 |
68
+ | `/workflows abort` | 中止活动运行 |
69
+
70
+ 按 `Esc` 可取消正在运行的工作流。在 Pi 的 fullscreen TUI 中,请在工作流详情内使用 `Ctrl+PageUp`、`Ctrl+PageDown` 和 `Ctrl+End`。
71
+
72
+ ## 工作流示例
73
+
74
+ 通常由主代理自动编写工作流。需要复用时,可以保存到 `.pi/ultracode/workflows/*.workflow.js` 或 `~/.pi/ultracode/workflows/*.workflow.js`。
75
+
76
+ ```js
77
+ export const meta = {
78
+ name: "parallel_review",
79
+ description: "Review correctness and tests in parallel",
80
+ };
81
+
82
+ const findings = await parallel([
83
+ () => agent(
84
+ "Review the current branch for correctness and lifecycle bugs.",
85
+ { label: "correctness review" },
86
+ ),
87
+ () => agent(
88
+ "Review the current branch for missing or weak regression tests.",
89
+ { label: "test review" },
90
+ ),
91
+ ]);
92
+
93
+ return findings.filter(Boolean);
94
+ ```
95
+
96
+ 如需编辑器类型提示,请添加:
97
+
98
+ ```js
99
+ /// <reference types="pi-ultracode/workflow" />
100
+ ```
101
+
102
+ 完整工作流接口见 [`types/workflow.d.ts`](types/workflow.d.ts),更多示例见 [`examples/workflows`](examples/workflows)。
103
+
104
+ ## 角色、模型与 worktree
105
+
106
+ 自定义代理角色放在:
107
+
108
+ ```text
109
+ .pi/ultracode/agents/*.md
110
+ ~/.pi/ultracode/agents/*.md
111
+ ```
112
+
113
+ 每次调用都可以选择角色、模型、思考强度或 worktree 隔离:
114
+
115
+ ```js
116
+ await agent("Implement and test the fix.", {
117
+ label: "implement fix",
118
+ agentType: "general-purpose",
119
+ model: "anthropic/claude-sonnet-4:high",
120
+ isolation: "worktree",
121
+ });
122
+ ```
123
+
124
+ Worktree 隔离只能在至少有一个 commit 的 Git 仓库中使用。如果创建失败,Ultracode 会记录回退信息,并让该代理在共享工作目录中运行;此时应避免并行写入。为保证补丁集成可预测,建议保持工作区干净。
125
+
126
+ ## 执行边界
127
+
128
+ 工作流默认使用 `maxAgents: 128`,最多并发运行 16 个代理,并只允许一层嵌套工作流。累计代理上限会跨 resume 保留,缓存回放不会再次占用额度。
129
+
130
+ Workflow 子代理会保留项目上下文和普通 skills,但不会初始化环境中的 Pi 扩展,也不会暴露父级编排工具与 skill(`workflow`、`subagent`、`subagent_wait` 或 `pi-subagents`)。这样可把编排限制在父会话边界,同时允许 `pi-ultracode` 与 `pi-subagents` 在主会话中共存。
131
+
132
+ Resume 刻意保持不可变:脚本、参数、代理定义、实际模型和调用结构都必须匹配。工作内容发生变化时,应启动新的 run。
133
+
134
+ Token 和成本只用于可观测性,不作为执行预算。Worker 与 VM 限制用于确定性和存活性保护,不是安全沙箱。
135
+
136
+ ## 设计参考
137
+
138
+ - **Claude Code**:自然语言驱动、终端优先的 Agentic 编码体验和任务委派。
139
+ - **Grok Build**:显式工作流、持久 journal、有界 admission 和结构执行控制。
140
+ - **Pi**:模型运行时、会话、工具、扩展 API 和 TUI 都继续使用 Pi 原生能力。
141
+
142
+ `pi-ultracode` 是独立的 Pi 扩展,与 Anthropic 或 xAI 没有关联。
143
+
144
+ ## 开发
145
+
146
+ ```bash
147
+ npm ci
148
+ npm run typecheck
149
+ npm test
150
+ npm pack --dry-run
151
+ ```
152
+
153
+ ## 许可证
154
+
155
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ultracode",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Claude-Code-style \"ultracode\" for Pi: an effort mode that defaults to deterministic multi-agent workflow orchestration, with worktree isolation, per-agent model overrides, custom agent types, nested workflows, resumable runs, and a /workflows manager.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -53,10 +53,10 @@
53
53
  "acorn": "^8.11.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@earendil-works/pi-agent-core": "^0.83.0",
57
- "@earendil-works/pi-ai": "^0.83.0",
58
- "@earendil-works/pi-coding-agent": "^0.83.0",
59
- "@earendil-works/pi-tui": "^0.83.0",
56
+ "@earendil-works/pi-agent-core": "^0.84.0",
57
+ "@earendil-works/pi-ai": "^0.84.0",
58
+ "@earendil-works/pi-coding-agent": "^0.84.0",
59
+ "@earendil-works/pi-tui": "^0.84.0",
60
60
  "@types/node": "^24.12.4",
61
61
  "typebox": "1.3.7",
62
62
  "typescript": "^5.9.3"
package/src/prompts.ts CHANGED
@@ -64,7 +64,7 @@ export const WORKFLOW_GUIDELINES: string[] = [
64
64
  "For the workflow tool, if agent() needs machine-readable output pass an inline JSON Schema via opts.schema; agent() then returns the validated object. Use only the bounded subset: types, object/array structure, enum/const, anyOf/allOf, length/numeric constraints, and annotations. Unknown keywords, $ref variants, oneOf, format, pattern, and patternProperties are rejected. Keep schemas under 256 KiB / 64 levels and every agent/workflow output under 2 MiB. Use JSON Schema, not TypeScript or TypeBox constructors.",
65
65
  "For the workflow tool, when agent() is called WITHOUT a schema, its return value is the subagent's final assistant text (the last text the subagent produced). With a schema it returns the validated structured_output object. Prefer a schema for machine-readable results; use the text form only for prose summaries.",
66
66
  "For the workflow tool, agent opts also accept: model (override the subagent model by pattern), agentType (use a custom subagent role/system-prompt), isolation:'worktree' (run the agent in an isolated git worktree — use ONLY when agents mutate files in parallel and would conflict), and phase (assign the agent to a progress group explicitly inside parallel()/pipeline()).",
67
- "For the workflow tool, workflow(nameOrRef, args) runs a saved workflow (by name) or a scriptPath inline as a sub-step, sharing this run's concurrency, maxAgents, active reservations, and agent counter. Nesting is one level only. Child subagent sessions explicitly exclude the workflow tool and cannot start an independent recursive workflow chain.",
67
+ "For the workflow tool, workflow(nameOrRef, args) runs a saved workflow (by name) or a scriptPath inline as a sub-step, sharing this run's concurrency, maxAgents, active reservations, and agent counter. Nesting is one level only. Child subagent sessions do not load ambient extensions or expose workflow/subagent orchestration tools, so they cannot start an independent recursive workflow chain.",
68
68
  "For workflow resumeFromRunId, reuse the exact same script and args. Resume is immutable, successful calls replay by stable structural call path, and maxAgents may only stay the same or increase; changed work must start a new run.",
69
69
  "For the workflow tool, failed agent()/parallel()/pipeline() branches return null and log the failure (unless the whole run is aborted or a policy limit is hit). Check for nulls before synthesizing conclusions, and prefer a final synthesis/assertion agent that returns a compact JSON-serializable verdict.",
70
70
  "For the workflow tool, directly await or return every orchestration promise (agent/parallel/pipeline/workflow). Native .then/.catch/.finally chains and Promise.all/allSettled/race/any are rejected; use parallel() or pipeline() so call identity stays deterministic. Unobserved, pending, or native same-scope concurrent orchestration is fatal.",
@@ -14,6 +14,7 @@ import * as PiCodingAgent from "@earendil-works/pi-coding-agent";
14
14
  import {
15
15
  createAgentSession,
16
16
  createCodingTools,
17
+ DefaultResourceLoader,
17
18
  getAgentDir,
18
19
  SessionManager,
19
20
  SettingsManager,
@@ -45,6 +46,42 @@ import type { AgentTypeDef } from "./agent-types.ts";
45
46
 
46
47
  export type { ThinkingLevel } from "../thinking.ts";
47
48
 
49
+ const CHILD_ORCHESTRATION_TOOLS = ["workflow", "subagent", "subagent_wait"];
50
+ const PARENT_ONLY_CHILD_SKILLS = new Set(["pi-subagents"]);
51
+
52
+ export interface WorkflowChildResourceLoaderOptions {
53
+ cwd: string;
54
+ agentDir: string;
55
+ settingsManager?: SettingsManager;
56
+ }
57
+
58
+ /**
59
+ * Build the sealed resource view used by workflow agent sessions.
60
+ *
61
+ * Ambient extension factories execute during discovery, so filtering an already
62
+ * loaded extension result is too late: another in-process orchestrator may have
63
+ * already replaced process-global lifecycle state. Disable ambient extensions at
64
+ * the loader boundary while retaining project context and ordinary skills.
65
+ */
66
+ export async function createWorkflowChildResourceLoader(
67
+ options: WorkflowChildResourceLoaderOptions,
68
+ ): Promise<DefaultResourceLoader> {
69
+ const settingsManager = options.settingsManager
70
+ ?? SettingsManager.create(options.cwd, options.agentDir);
71
+ const loader = new DefaultResourceLoader({
72
+ cwd: options.cwd,
73
+ agentDir: options.agentDir,
74
+ settingsManager,
75
+ noExtensions: true,
76
+ skillsOverride: ({ skills, diagnostics }) => ({
77
+ skills: skills.filter((skill) => !PARENT_ONLY_CHILD_SKILLS.has(skill.name)),
78
+ diagnostics,
79
+ }),
80
+ });
81
+ await loader.reload();
82
+ return loader;
83
+ }
84
+
48
85
  /** A minimal structural view of a Pi model (avoids importing the heavy generic type). */
49
86
  export interface ModelLike {
50
87
  provider: string;
@@ -192,7 +229,10 @@ export interface WorkflowAgentRunnerOptions {
192
229
  model?: ModelLike;
193
230
  /** Default thinking level for subagents. */
194
231
  thinkingLevel?: ThinkingLevel;
195
- /** Test seam for session construction and initialization races. */
232
+ /**
233
+ * Test seam for session construction and initialization races. Custom
234
+ * factories own resource isolation; production uses the sealed Pi loader.
235
+ */
196
236
  createSession?: AgentSessionFactory;
197
237
  /** Test/compatibility seam for async ModelRuntime initialization. */
198
238
  createModelRuntime?: ModelRuntimeFactory;
@@ -243,6 +283,7 @@ export class WorkflowAgentRunner {
243
283
  private readonly defaultModel?: ModelLike;
244
284
  private readonly defaultThinking?: ThinkingLevel;
245
285
  private readonly createSession: AgentSessionFactory;
286
+ private readonly usesPiSessionFactory: boolean;
246
287
  private readonly createModelRuntime?: ModelRuntimeFactory;
247
288
  private modelRuntimePromise?: Promise<ModelRuntimeLike | undefined>;
248
289
  private readonly runtimeSupportsMaxThinking: boolean;
@@ -253,10 +294,10 @@ export class WorkflowAgentRunner {
253
294
  this.providedModelRuntime = options.modelRuntime;
254
295
  this.defaultModel = options.model;
255
296
  this.defaultThinking = options.thinkingLevel;
256
- const usesPiSessionFactory = options.createSession === undefined;
297
+ this.usesPiSessionFactory = options.createSession === undefined;
257
298
  this.createSession = options.createSession ?? (createAgentSession as unknown as AgentSessionFactory);
258
299
  this.createModelRuntime = options.createModelRuntime
259
- ?? (options.modelRuntime !== undefined || !usesPiSessionFactory ? undefined : createPiModelRuntime);
300
+ ?? (options.modelRuntime !== undefined || !this.usesPiSessionFactory ? undefined : createPiModelRuntime);
260
301
  this.runtimeSupportsMaxThinking = options.supportsMaxThinking ?? piVersionSupportsMaxThinking(PI_VERSION);
261
302
  }
262
303
 
@@ -304,22 +345,29 @@ export class WorkflowAgentRunner {
304
345
  throw error;
305
346
  }
306
347
 
307
- const createSession = (level: ThinkingLevel | undefined) => this.createSession({
308
- cwd,
309
- agentDir,
310
- sessionManager: SessionManager.inMemory(cwd),
311
- settingsManager: SettingsManager.create(cwd, agentDir),
312
- customTools,
313
- excludeTools: ["workflow"],
314
- ...(model ? { model: model as any } : {}),
315
- ...(level ? { thinkingLevel: level as any } : {}),
316
- ...(toolAllowlist ? { tools: toolAllowlist } : {}),
317
- ...(modelRuntime
318
- ? { modelRuntime }
319
- : this.modelRegistry
320
- ? { modelRegistry: this.modelRegistry as any }
321
- : {}),
322
- });
348
+ const createSession = async (level: ThinkingLevel | undefined) => {
349
+ const settingsManager = SettingsManager.create(cwd, agentDir);
350
+ const resourceLoader = this.usesPiSessionFactory
351
+ ? await createWorkflowChildResourceLoader({ cwd, agentDir, settingsManager })
352
+ : undefined;
353
+ return this.createSession({
354
+ cwd,
355
+ agentDir,
356
+ sessionManager: SessionManager.inMemory(cwd),
357
+ settingsManager,
358
+ ...(resourceLoader ? { resourceLoader } : {}),
359
+ customTools,
360
+ excludeTools: [...CHILD_ORCHESTRATION_TOOLS],
361
+ ...(model ? { model: model as any } : {}),
362
+ ...(level ? { thinkingLevel: level as any } : {}),
363
+ ...(toolAllowlist ? { tools: toolAllowlist } : {}),
364
+ ...(modelRuntime
365
+ ? { modelRuntime }
366
+ : this.modelRegistry
367
+ ? { modelRegistry: this.modelRegistry as any }
368
+ : {}),
369
+ });
370
+ };
323
371
 
324
372
  const sessionThinking = resolveSessionThinkingLevel(thinkingLevel, model);
325
373
  safeEmitTelemetry(call.onTelemetry, {
@@ -619,9 +667,27 @@ export function resolveSessionThinkingLevel(
619
667
 
620
668
  function matchExactModelIn(models: ModelLike[] | undefined, pattern: string): ModelLike | undefined {
621
669
  if (!models || !pattern.trim()) return undefined;
622
- const lower = pattern.trim().toLowerCase();
623
- return models.find((m) => `${m.provider}/${m.id}`.toLowerCase() === lower)
624
- ?? models.find((m) => m.id.toLowerCase() === lower);
670
+ const trimmed = pattern.trim();
671
+ const lower = trimmed.toLowerCase();
672
+ const canonicalMatches = models.filter((model) =>
673
+ `${model.provider}/${model.id}`.toLowerCase() === lower
674
+ );
675
+ if (canonicalMatches.length === 1) return canonicalMatches[0];
676
+ if (canonicalMatches.length > 1) throw ambiguousModelReferenceError(trimmed, canonicalMatches);
677
+
678
+ const idMatches = models.filter((model) => model.id.toLowerCase() === lower);
679
+ if (idMatches.length === 1) return idMatches[0];
680
+ if (idMatches.length > 1) throw ambiguousModelReferenceError(trimmed, idMatches);
681
+ return undefined;
682
+ }
683
+
684
+ function ambiguousModelReferenceError(pattern: string, matches: ModelLike[]): Error {
685
+ const references = [...new Set(matches.map((model) => `${model.provider}/${model.id}`))]
686
+ .sort((left, right) => left.localeCompare(right))
687
+ .join(", ");
688
+ return new Error(
689
+ `Model "${safeDisplayText(pattern, 80)}" is ambiguous across providers: ${safeDisplayText(references, 400)}. Use provider/model.`,
690
+ );
625
691
  }
626
692
 
627
693
  /** Match a model pattern against a registry list: exact provider/id, then exact id, then substring. */
@@ -24,6 +24,12 @@ const MIN_SPLIT_WIDTH = 100;
24
24
  const MAX_RENDER_FPS_INTERVAL_MS = 100;
25
25
  let overlayOpen = false;
26
26
 
27
+ type OverlayNavigationKey = "pageUp" | "pageDown" | "end";
28
+
29
+ function matchesOverlayNavigationKey(data: string, key: OverlayNavigationKey): boolean {
30
+ return matchesKey(data, key) || matchesKey(data, `ctrl+${key}`);
31
+ }
32
+
27
33
  interface TaskViewport {
28
34
  offset: number;
29
35
  follow: boolean;
@@ -318,8 +324,8 @@ export class WorkflowOverlayComponent implements Component {
318
324
  if (currentIndex >= 0) this.selectedTaskIndex = currentIndex;
319
325
  if (matchesKey(data, "up")) this.selectedTaskIndex = Math.max(0, this.selectedTaskIndex - 1);
320
326
  else if (matchesKey(data, "down")) this.selectedTaskIndex = Math.min(tasks.length - 1, this.selectedTaskIndex + 1);
321
- else if (matchesKey(data, "pageUp")) this.selectedTaskIndex = Math.max(0, this.selectedTaskIndex - 10);
322
- else if (matchesKey(data, "pageDown")) this.selectedTaskIndex = Math.min(tasks.length - 1, this.selectedTaskIndex + 10);
327
+ else if (matchesOverlayNavigationKey(data, "pageUp")) this.selectedTaskIndex = Math.max(0, this.selectedTaskIndex - 10);
328
+ else if (matchesOverlayNavigationKey(data, "pageDown")) this.selectedTaskIndex = Math.min(tasks.length - 1, this.selectedTaskIndex + 10);
323
329
  else if (matchesKey(data, "enter") || matchesKey(data, "return")) {
324
330
  if (split) this.focus = "detail";
325
331
  else {
@@ -341,13 +347,13 @@ export class WorkflowOverlayComponent implements Component {
341
347
  } else if (matchesKey(data, "down")) {
342
348
  viewport.offset++;
343
349
  viewport.follow = false;
344
- } else if (matchesKey(data, "pageUp")) {
350
+ } else if (matchesOverlayNavigationKey(data, "pageUp")) {
345
351
  viewport.offset = Math.max(0, viewport.offset - page);
346
352
  viewport.follow = false;
347
- } else if (matchesKey(data, "pageDown")) {
353
+ } else if (matchesOverlayNavigationKey(data, "pageDown")) {
348
354
  viewport.offset += page;
349
355
  viewport.follow = false;
350
- } else if (matchesKey(data, "end")) {
356
+ } else if (matchesOverlayNavigationKey(data, "end")) {
351
357
  viewport.follow = true;
352
358
  viewport.newLines = 0;
353
359
  }
@@ -456,9 +462,13 @@ export class WorkflowOverlayComponent implements Component {
456
462
  viewport.offset = Math.max(0, Math.min(viewport.offset, maxOffset));
457
463
  }
458
464
  const visible = body.slice(viewport.offset, viewport.offset + bodyHeight);
465
+ // Fullscreen owns unmodified paging keys before focused overlays receive input.
466
+ // Ctrl variants remain routed to the overlay under Pi 0.84's default bindings.
467
+ const pageKeys = this.tui.mode === "fullscreen" ? "Ctrl+PgUp/Dn" : "PgUp/Dn";
468
+ const endKey = this.tui.mode === "fullscreen" ? "Ctrl+End" : "End";
459
469
  const footer = viewport.follow
460
- ? "↑↓/PgUp scroll · End follow · p prompt · Tab tasks · Esc back"
461
- : `↓ ${viewport.newLines} new lines · End to follow`;
470
+ ? `↑↓/${pageKeys} scroll · ${endKey} follow · p prompt · Tab tasks · Esc back`
471
+ : `↓ ${viewport.newLines} new lines · ${endKey} to follow`;
462
472
  return fitHeight([...fixed, "", ...visible, this.theme.fg("dim", footer)], height);
463
473
  }
464
474