@tt-a1i/openpi 0.2.0 → 0.3.1
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 +28 -6
- package/SETUP.md +1 -1
- package/extensions/capabilities/index.ts +4 -1
- package/extensions/plan-mode/bash-policy.ts +219 -42
- package/extensions/plan-mode/index.ts +44 -19
- package/extensions/setup/index.ts +3 -3
- package/extensions/shared/editor-layers.ts +150 -0
- package/extensions/shared/setup-config.ts +4 -15
- package/extensions/subagents/index.ts +174 -124
- package/extensions/subagents/src/prompt.ts +6 -6
- package/extensions/subagents/src/result-delivery.ts +50 -5
- package/extensions/subagents/src/ui/takeover.ts +231 -133
- package/extensions/subagents/src/ui/transcript.ts +252 -37
- package/extensions/subagents/src/ui/wait-result.ts +6 -19
- package/extensions/suggestions/index.ts +27 -18
- package/extensions/tasks/index.ts +24 -6
- package/extensions/ui-customization/footer.ts +59 -10
- package/extensions/workflows/index.ts +28 -20
- package/package.json +1 -1
- package/skills/subagents/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<h1 align="center">OpenPI</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
8
|
+
<strong>Pi at the core. Power on demand.</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
|
|
13
|
-
不替换 Pi
|
|
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
|
|
@@ -310,7 +332,7 @@ cwd model thinking context cache cost throughput git PR
|
|
|
310
332
|
- 支持 `powerline`、`powerline-mono`、`compact`,也支持自定义多行布局;
|
|
311
333
|
- 终端变窄时按优先级隐藏次要指标,不机械截断尾部;
|
|
312
334
|
- Subagent 与 Workflow 活动时自动出现,空闲时不占空间;
|
|
313
|
-
- Bash、Write/Edit 与 Subagent 结果可独立选择 `full` 或 `compact`;
|
|
335
|
+
- Bash、Write/Edit 与 Subagent 结果可独立选择 `full` 或 `compact`;Subagent 的 compact 模式只显示状态摘要,不暴露原始子 Agent 正文;
|
|
314
336
|
- 折叠内容用 Pi 的 `app.tools.expand` 快捷键临时展开,默认 `Ctrl+O`;
|
|
315
337
|
- Git 状态本地刷新;只有显式运行 `/pr` 才查询 GitHub PR。
|
|
316
338
|
|
|
@@ -375,7 +397,7 @@ macOS/Linux arm64 与 x64 缺少二进制时,OpenPI 会从官方 Release 下
|
|
|
375
397
|
| Next-action Suggestion | 关闭;启用时显式选择 Registry 模型与 reasoning |
|
|
376
398
|
| Workflow 并发 / 总调用 | 8 / 128;硬上限 64 / 1024 |
|
|
377
399
|
| 大型 Header | 关闭 |
|
|
378
|
-
| Dashboard Footer | 开启;单行 `
|
|
400
|
+
| Dashboard Footer | 开启;单行 `plain` |
|
|
379
401
|
| Subagent / Bash / Write/Edit | `full` / `compact` / `compact` |
|
|
380
402
|
| Post-edit 命令 | 关闭;单条命令最多 500 字符 |
|
|
381
403
|
| 内置角色模型 | 全部继承父模型 |
|
|
@@ -469,7 +491,7 @@ Capability discovery 默认是 `explicit`:普通父 Session 不常驻任何 Op
|
|
|
469
491
|
<details>
|
|
470
492
|
<summary><strong>Subagent 会阻塞主 Agent 吗?</strong></summary>
|
|
471
493
|
|
|
472
|
-
`subagent_spawn`
|
|
494
|
+
`subagent_spawn` 立即返回,结束后自动回传并重新唤醒主 Agent。交互会话没有其他工作时,主 Agent 应结束当前轮、让用户继续交互;“下一步依赖结果”本身不是阻塞理由。只有用户明确要求当前回复等完,或非交互自动化必须在同一次调用中返回完整结果时,才应调用 `subagent_wait`。
|
|
473
495
|
|
|
474
496
|
</details>
|
|
475
497
|
|
package/SETUP.md
CHANGED
|
@@ -54,7 +54,7 @@ Use the single canonical package-owned command. `/my-pi-setup` remains a compati
|
|
|
54
54
|
/openpi-setup 清除 explorer 的模型,让它继承父模型
|
|
55
55
|
```
|
|
56
56
|
|
|
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;
|
|
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`.
|
|
58
58
|
|
|
59
59
|
## Session Goal and Tasks
|
|
60
60
|
|
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
resetOpenPiToolSurface,
|
|
18
18
|
} from "../shared/tool-surface.ts";
|
|
19
19
|
|
|
20
|
-
const CapabilitySchema = Type.
|
|
20
|
+
const CapabilitySchema = Type.Unsafe<OpenPiCapability>({
|
|
21
|
+
type: "string",
|
|
22
|
+
enum: OPENPI_CAPABILITY_NAMES,
|
|
23
|
+
});
|
|
21
24
|
|
|
22
25
|
const OpenPiLoadToolsParameters = Type.Object({
|
|
23
26
|
groups: Type.Optional(
|
|
@@ -25,24 +25,22 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* This module does not parse shell or admit shell composition. Its small
|
|
29
|
+
* tokenizer only recognizes words and quoted literal spans. `$`, backticks
|
|
30
|
+
* and `\` are refused everywhere; shell metacharacters are refused outside
|
|
31
|
+
* quotes. Globs are also refused outside quotes because the shell would expand
|
|
32
|
+
* them before the allowlisted program sees them, while a quoted glob is a
|
|
33
|
+
* literal pattern interpreted by that read-only program itself.
|
|
34
34
|
*/
|
|
35
|
-
const
|
|
35
|
+
const UNQUOTED_SHELL_METACHARACTERS = /[;&|<>(){}\n\r#]/;
|
|
36
|
+
const EXPANSION_CHARACTERS = /[$`\\]/;
|
|
37
|
+
const UNQUOTED_GLOB_CHARACTERS = new Set(["*", "?", "[", "]"]);
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
* Tilde expansion
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* Tilde expansion is refused only at the start of an unquoted word. `HEAD~3`
|
|
41
|
+
* is ordinary revision syntax and must survive, while `~/notes` and
|
|
42
|
+
* `~user/x` resolve to a path this module never sees.
|
|
41
43
|
*/
|
|
42
|
-
const TILDE_EXPANSION = /(^|\s)~/;
|
|
43
|
-
|
|
44
|
-
/** Quotes hide word boundaries from the tokenizer below, so they are refused too. */
|
|
45
|
-
const QUOTES = /["']/;
|
|
46
44
|
|
|
47
45
|
/**
|
|
48
46
|
* Read-only git subcommands. Absent on purpose: `config`, `stash`, `tag`,
|
|
@@ -194,6 +192,94 @@ const GH_FLAGS = new Set([
|
|
|
194
192
|
"--comments",
|
|
195
193
|
]);
|
|
196
194
|
|
|
195
|
+
const RG_FLAGS = new Set([
|
|
196
|
+
"-n",
|
|
197
|
+
"--line-number",
|
|
198
|
+
"-i",
|
|
199
|
+
"--ignore-case",
|
|
200
|
+
"-l",
|
|
201
|
+
"--files-with-matches",
|
|
202
|
+
"-c",
|
|
203
|
+
"--count",
|
|
204
|
+
"-w",
|
|
205
|
+
"-F",
|
|
206
|
+
"--fixed-strings",
|
|
207
|
+
"-e",
|
|
208
|
+
"--regexp",
|
|
209
|
+
"-g",
|
|
210
|
+
"--glob",
|
|
211
|
+
"-t",
|
|
212
|
+
"--type",
|
|
213
|
+
"--files",
|
|
214
|
+
"--hidden",
|
|
215
|
+
"--no-ignore",
|
|
216
|
+
"-A",
|
|
217
|
+
"-B",
|
|
218
|
+
"-C",
|
|
219
|
+
"--after-context",
|
|
220
|
+
"--before-context",
|
|
221
|
+
"--context",
|
|
222
|
+
"-m",
|
|
223
|
+
"--max-count",
|
|
224
|
+
"-o",
|
|
225
|
+
"--only-matching",
|
|
226
|
+
"--sort",
|
|
227
|
+
"--json",
|
|
228
|
+
"--color",
|
|
229
|
+
"-H",
|
|
230
|
+
"-N",
|
|
231
|
+
"--no-filename",
|
|
232
|
+
"-v",
|
|
233
|
+
"--invert-match",
|
|
234
|
+
"-u",
|
|
235
|
+
"-uu",
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
const FD_FLAGS = new Set([
|
|
239
|
+
"-e",
|
|
240
|
+
"--extension",
|
|
241
|
+
"-t",
|
|
242
|
+
"--type",
|
|
243
|
+
"-d",
|
|
244
|
+
"--max-depth",
|
|
245
|
+
"--min-depth",
|
|
246
|
+
"-H",
|
|
247
|
+
"--hidden",
|
|
248
|
+
"-I",
|
|
249
|
+
"--no-ignore",
|
|
250
|
+
"-g",
|
|
251
|
+
"--glob",
|
|
252
|
+
"-F",
|
|
253
|
+
"--fixed-strings",
|
|
254
|
+
"-p",
|
|
255
|
+
"--full-path",
|
|
256
|
+
"-a",
|
|
257
|
+
"--absolute-path",
|
|
258
|
+
"-l",
|
|
259
|
+
"--list-details",
|
|
260
|
+
"--color",
|
|
261
|
+
"-0",
|
|
262
|
+
"-S",
|
|
263
|
+
"--size",
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
const LS_FLAGS = new Set([
|
|
267
|
+
"-l",
|
|
268
|
+
"-a",
|
|
269
|
+
"-A",
|
|
270
|
+
"-h",
|
|
271
|
+
"-t",
|
|
272
|
+
"-r",
|
|
273
|
+
"-R",
|
|
274
|
+
"-d",
|
|
275
|
+
"-1",
|
|
276
|
+
"-S",
|
|
277
|
+
"-F",
|
|
278
|
+
"--color",
|
|
279
|
+
]);
|
|
280
|
+
const WC_FLAGS = new Set(["-l", "-w", "-c", "-m"]);
|
|
281
|
+
const HEAD_TAIL_FLAGS = new Set(["-n", "-c", "--lines", "--bytes"]);
|
|
282
|
+
|
|
197
283
|
/** `-5`, `-20`: git's count shorthand, which is a number rather than a flag. */
|
|
198
284
|
const NUMERIC_SHORTHAND = /^-\d+$/;
|
|
199
285
|
|
|
@@ -208,6 +294,83 @@ const refuse = (reason: string): BashPlanDecision => ({
|
|
|
208
294
|
reason,
|
|
209
295
|
});
|
|
210
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Tokenize words without pretending to be a shell parser. Quoted spans are
|
|
299
|
+
* removed and become literal text; no expansion or command composition is
|
|
300
|
+
* supported. The validation happens while tokenizing so an unquoted glob can
|
|
301
|
+
* never be mistaken for a literal program argument.
|
|
302
|
+
*/
|
|
303
|
+
function tokenize(command: string) {
|
|
304
|
+
const words: string[] = [];
|
|
305
|
+
let word = "";
|
|
306
|
+
let inWord = false;
|
|
307
|
+
let quote: "'" | '"' | undefined;
|
|
308
|
+
let wordStartsUnquoted = false;
|
|
309
|
+
|
|
310
|
+
for (const character of command) {
|
|
311
|
+
if (quote) {
|
|
312
|
+
if (character === quote) {
|
|
313
|
+
quote = undefined;
|
|
314
|
+
} else if (EXPANSION_CHARACTERS.test(character)) {
|
|
315
|
+
return refuse(
|
|
316
|
+
`plan mode rejected expansion character ${JSON.stringify(character)} — remove expansion syntax and pass literal arguments instead`,
|
|
317
|
+
);
|
|
318
|
+
} else {
|
|
319
|
+
word += character;
|
|
320
|
+
}
|
|
321
|
+
inWord = true;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (EXPANSION_CHARACTERS.test(character)) {
|
|
326
|
+
return refuse(
|
|
327
|
+
`plan mode rejected expansion character ${JSON.stringify(character)} — remove expansion syntax and pass literal arguments instead`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (UNQUOTED_SHELL_METACHARACTERS.test(character)) {
|
|
331
|
+
return refuse(
|
|
332
|
+
`plan mode rejected unquoted shell metacharacter ${JSON.stringify(character)} — run a single plain command without shell composition`,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
if (UNQUOTED_GLOB_CHARACTERS.has(character)) {
|
|
336
|
+
return refuse(
|
|
337
|
+
"plan mode will not run an unquoted glob because the shell would expand it — quote it instead, e.g. --glob '*.ts'",
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (character === "'" || character === '"') {
|
|
341
|
+
quote = character;
|
|
342
|
+
inWord = true;
|
|
343
|
+
if (!word) wordStartsUnquoted = false;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (/\s/.test(character)) {
|
|
347
|
+
if (inWord) {
|
|
348
|
+
words.push(word);
|
|
349
|
+
word = "";
|
|
350
|
+
inWord = false;
|
|
351
|
+
wordStartsUnquoted = false;
|
|
352
|
+
}
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (!inWord) wordStartsUnquoted = true;
|
|
356
|
+
if (wordStartsUnquoted && word.length === 0 && character === "~") {
|
|
357
|
+
return refuse(
|
|
358
|
+
"plan mode does not run tilde-expanded paths — use a path relative to the project instead",
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
word += character;
|
|
362
|
+
inWord = true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (quote) {
|
|
366
|
+
return refuse(
|
|
367
|
+
`plan mode rejected an unterminated ${quote === "'" ? "single" : "double"} quote — close the quote or pass a literal argument instead`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (inWord) words.push(word);
|
|
371
|
+
return { allowed: true as const, words };
|
|
372
|
+
}
|
|
373
|
+
|
|
211
374
|
/** Split `--flag=value` into the flag part the allowlists are keyed on. */
|
|
212
375
|
function flagName(word: string) {
|
|
213
376
|
const eq = word.indexOf("=");
|
|
@@ -223,16 +386,27 @@ function scanArguments(
|
|
|
223
386
|
words: readonly string[],
|
|
224
387
|
allowed: ReadonlySet<string>,
|
|
225
388
|
program: string,
|
|
389
|
+
/**
|
|
390
|
+
* Whether `-la` may stand for `-l -a`. Only the file-inspection programs opt
|
|
391
|
+
* in: git and gh keep their historical one-flag-per-word rule, so widening
|
|
392
|
+
* the tokenizer cannot quietly widen their surface too.
|
|
393
|
+
*/
|
|
394
|
+
allowShortClusters = false,
|
|
226
395
|
): BashPlanDecision {
|
|
227
396
|
for (const word of words) {
|
|
228
397
|
if (word === "--") break;
|
|
229
398
|
if (!word.startsWith("-")) continue;
|
|
230
399
|
if (NUMERIC_SHORTHAND.test(word)) continue;
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
|
|
400
|
+
if (allowed.has(flagName(word))) continue;
|
|
401
|
+
const isAllowedCluster =
|
|
402
|
+
allowShortClusters &&
|
|
403
|
+
!word.startsWith("--") &&
|
|
404
|
+
word.length > 2 &&
|
|
405
|
+
[...word.slice(1)].every((character) => allowed.has(`-${character}`));
|
|
406
|
+
if (isAllowedCluster) continue;
|
|
407
|
+
return refuse(
|
|
408
|
+
`plan mode does not recognize "${word}" as a read-only ${program} option — use only the allowlisted ${program} flags`,
|
|
409
|
+
);
|
|
236
410
|
}
|
|
237
411
|
return { allowed: true };
|
|
238
412
|
}
|
|
@@ -249,21 +423,10 @@ export function planBashDecision(command: unknown): BashPlanDecision {
|
|
|
249
423
|
const text = command.trim();
|
|
250
424
|
if (!text) return refuse("plan mode received an empty command");
|
|
251
425
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
if (QUOTES.test(text)) {
|
|
258
|
-
return refuse("plan mode only runs unquoted commands while planning");
|
|
259
|
-
}
|
|
260
|
-
if (TILDE_EXPANSION.test(text)) {
|
|
261
|
-
return refuse(
|
|
262
|
-
"plan mode does not run commands with `~` paths — give a path relative to the project instead",
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const [program, ...rest] = text.split(/\s+/);
|
|
426
|
+
const tokenized = tokenize(text);
|
|
427
|
+
if (!("words" in tokenized)) return tokenized;
|
|
428
|
+
const [program, ...rest] = tokenized.words;
|
|
429
|
+
if (!program) return refuse("plan mode received an empty command");
|
|
267
430
|
|
|
268
431
|
/*
|
|
269
432
|
* The subcommand must be the FIRST word, never "the first word that is not a
|
|
@@ -300,14 +463,28 @@ export function planBashDecision(command: unknown): BashPlanDecision {
|
|
|
300
463
|
return scanArguments(args, GH_FLAGS, "gh");
|
|
301
464
|
}
|
|
302
465
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
466
|
+
const readOnlyPrograms = new Map([
|
|
467
|
+
["rg", RG_FLAGS],
|
|
468
|
+
["fd", FD_FLAGS],
|
|
469
|
+
["ls", LS_FLAGS],
|
|
470
|
+
["wc", WC_FLAGS],
|
|
471
|
+
["head", HEAD_TAIL_FLAGS],
|
|
472
|
+
["tail", HEAD_TAIL_FLAGS],
|
|
473
|
+
]);
|
|
474
|
+
const flags = readOnlyPrograms.get(program);
|
|
475
|
+
if (flags) {
|
|
476
|
+
if (
|
|
477
|
+
program === "tail" &&
|
|
478
|
+
rest.some((word) => word === "-f" || word === "--follow")
|
|
479
|
+
) {
|
|
480
|
+
return refuse(
|
|
481
|
+
'plan mode refuses "tail -f/--follow" because it can block forever — use a finite tail command instead',
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
return scanArguments(rest, flags, program, true);
|
|
485
|
+
}
|
|
486
|
+
|
|
310
487
|
return refuse(
|
|
311
|
-
`plan mode
|
|
488
|
+
`plan mode does not allow "${program}" — use read-only git and gh investigation commands; available commands are git, gh, rg, fd, ls, wc, head or tail, plus the read/grep/fd tools`,
|
|
312
489
|
);
|
|
313
490
|
}
|
|
@@ -142,8 +142,12 @@ export const PLAN_READY_ACTIONS = {
|
|
|
142
142
|
continue: "Continue planning",
|
|
143
143
|
current: "Implement in this session",
|
|
144
144
|
fresh: "Start a fresh session",
|
|
145
|
+
off: "Turn plan mode off",
|
|
145
146
|
} as const;
|
|
146
147
|
|
|
148
|
+
/** Menu label for the same effect as `/plan done`. */
|
|
149
|
+
const FINALIZE_NOW = "Finalize now";
|
|
150
|
+
|
|
147
151
|
export function buildPlanImplementationPrompt(plan: string) {
|
|
148
152
|
return [
|
|
149
153
|
"Implement the approved plan below. Re-check the repository state before editing, follow the project instructions, and verify the finished change.",
|
|
@@ -257,11 +261,7 @@ export default function planMode(pi: ExtensionAPI) {
|
|
|
257
261
|
if (!ctx.hasUI) return;
|
|
258
262
|
ctx.ui.setStatus(
|
|
259
263
|
"plan-mode",
|
|
260
|
-
readyPlan
|
|
261
|
-
? "plan mode · ready"
|
|
262
|
-
: planning
|
|
263
|
-
? "plan mode · read-only"
|
|
264
|
-
: undefined,
|
|
264
|
+
readyPlan ? "plan ready" : planning ? "plan mode" : undefined,
|
|
265
265
|
);
|
|
266
266
|
};
|
|
267
267
|
|
|
@@ -371,9 +371,25 @@ export default function planMode(pi: ExtensionAPI) {
|
|
|
371
371
|
implementHere(ctx);
|
|
372
372
|
} else if (choice === PLAN_READY_ACTIONS.fresh) {
|
|
373
373
|
await implementFresh(ctx);
|
|
374
|
+
} else if (choice === PLAN_READY_ACTIONS.off) {
|
|
375
|
+
clearPlan(ctx);
|
|
376
|
+
ctx.ui.notify("Plan mode is off.", "info");
|
|
374
377
|
}
|
|
375
378
|
};
|
|
376
379
|
|
|
380
|
+
const requestPlanFinalization = () => {
|
|
381
|
+
pi.sendMessage(
|
|
382
|
+
{
|
|
383
|
+
customType: "plan-finalize-requested",
|
|
384
|
+
content:
|
|
385
|
+
"Finalize the plan now. Resolve any remaining material ambiguity with ask_user; otherwise call plan_ready alone with the complete implementation-ready Markdown plan. Do not implement it.",
|
|
386
|
+
display: true,
|
|
387
|
+
details: {},
|
|
388
|
+
},
|
|
389
|
+
{ deliverAs: "followUp", triggerTurn: true },
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
|
|
377
393
|
pi.registerTool({
|
|
378
394
|
name: "plan_ready",
|
|
379
395
|
label: "Plan Ready",
|
|
@@ -462,16 +478,7 @@ export default function planMode(pi: ExtensionAPI) {
|
|
|
462
478
|
ctx.ui.notify("Plan mode is not active.", "warning");
|
|
463
479
|
return;
|
|
464
480
|
}
|
|
465
|
-
|
|
466
|
-
{
|
|
467
|
-
customType: "plan-finalize-requested",
|
|
468
|
-
content:
|
|
469
|
-
"Finalize the plan now. Resolve any remaining material ambiguity with ask_user; otherwise call plan_ready alone with the complete implementation-ready Markdown plan. Do not implement it.",
|
|
470
|
-
display: true,
|
|
471
|
-
details: {},
|
|
472
|
-
},
|
|
473
|
-
{ deliverAs: "followUp", triggerTurn: true },
|
|
474
|
-
);
|
|
481
|
+
requestPlanFinalization();
|
|
475
482
|
return;
|
|
476
483
|
}
|
|
477
484
|
|
|
@@ -481,10 +488,28 @@ export default function planMode(pi: ExtensionAPI) {
|
|
|
481
488
|
}
|
|
482
489
|
|
|
483
490
|
if (planning) {
|
|
484
|
-
ctx.
|
|
485
|
-
|
|
486
|
-
|
|
491
|
+
if (!ctx.hasUI) {
|
|
492
|
+
ctx.ui.notify(
|
|
493
|
+
"Plan mode is already active. `/plan done` requests completion; `/plan off` cancels.",
|
|
494
|
+
"info",
|
|
495
|
+
);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const choice = await ctx.ui.select(
|
|
499
|
+
"Plan Mode — choose what happens next",
|
|
500
|
+
[PLAN_READY_ACTIONS.continue, FINALIZE_NOW, PLAN_READY_ACTIONS.off],
|
|
487
501
|
);
|
|
502
|
+
if (choice === PLAN_READY_ACTIONS.continue) {
|
|
503
|
+
ctx.ui.notify(
|
|
504
|
+
"Plan mode is already active. `/plan done` requests completion; `/plan off` cancels.",
|
|
505
|
+
"info",
|
|
506
|
+
);
|
|
507
|
+
} else if (choice === FINALIZE_NOW) {
|
|
508
|
+
requestPlanFinalization();
|
|
509
|
+
} else if (choice === PLAN_READY_ACTIONS.off) {
|
|
510
|
+
clearPlan(ctx);
|
|
511
|
+
ctx.ui.notify("Plan mode is off.", "info");
|
|
512
|
+
}
|
|
488
513
|
return;
|
|
489
514
|
}
|
|
490
515
|
|
|
@@ -510,7 +535,7 @@ export default function planMode(pi: ExtensionAPI) {
|
|
|
510
535
|
return {
|
|
511
536
|
block: true as const,
|
|
512
537
|
reason:
|
|
513
|
-
"The plan is ready and the write gate remains closed. Wait for the user to choose the next action with `/plan`; do not call more tools.",
|
|
538
|
+
"The plan is ready and the write gate remains closed. Wait for the user to choose the next action with `/plan` or turn it off with `/plan off`; do not call more tools.",
|
|
514
539
|
};
|
|
515
540
|
}
|
|
516
541
|
const batchDecision =
|
|
@@ -132,10 +132,10 @@ export function buildInteractiveSetupPrompt(options: {
|
|
|
132
132
|
"- Capability discovery: explicit is the safe default and keeps OpenPI model tools absent until the user asks for a capability. adaptive is opt-in and keeps only the small openpi_load_tools gateway visible, allowing the model to load Subagents, Workflows, background terminals, structured search, or Session tracking when it judges them useful. Loaded groups remain session-stable, and normal permission, concurrency, and workflow limits still apply.",
|
|
133
133
|
"- Next-action suggestions: disabled, or model-generated after a fully settled main-agent run. A suggestion appears 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 without submitting, and any other editor input dismisses it. Enabling requires an available provider/model and reasoning level and adds one small model call per settled run.",
|
|
134
134
|
"- Workflow fan-out: concurrency controls simultaneous agents and resource pressure; max agent calls controls the total capacity of one workflow. Valid ranges are 1-64 and 1-1024.",
|
|
135
|
-
"- UI: the large header costs vertical space; the custom footer is a declarative dashboard. Presets: powerline (
|
|
135
|
+
"- UI: the large header costs vertical space; the custom footer is a declarative dashboard. Presets: powerline (one-line ANSI256 blocks), powerline-mono (one-line high-contrast gray powerline), and compact (one-line plain text); the default is plain with cwd/git/pr on the left and model/context/cost on the right. Style can also be set independently: plain, powerline, powerline-mono. Custom lines are a 2D layout of cwd/model/thinking/context/cache/cost/throughput/git/pr plus at most one flex per line for left/right alignment. Nerd Font only affects powerline separator glyphs; text stays readable without it. Changes apply immediately in the active TUI session.",
|
|
136
136
|
"- Operational activity for Subagents, Workflows, and background terminals is core status and always remains visible whenever the custom footer is enabled.",
|
|
137
137
|
"- Post-edit command: one optional shell command (maximum 500 characters) run in the background after a turn with successful Write/Edit operations (e.g. `npm run format`). Off by default, interactive TUI sessions only, failures surface as a notification. This is a single command, not an event-hook system.",
|
|
138
|
-
"- Result detail display: Subagent results, Bash operations, and Write/Edit operations can each default to full
|
|
138
|
+
"- Result detail display: Subagent results, Bash operations, and Write/Edit operations can each default to full or compact. Compact Subagent results show only bounded status rows and keep raw child reports behind app.tools.expand; compact Bash and Write/Edit operations use folded previews. Ctrl+O expands compact output by default. Bash and Write/Edit default to compact. Recommend compact for users who do not usually inspect implementation details.",
|
|
139
139
|
"- Agent role models: built-in explorer, implementer, reviewer, and advisor roles are shared by subagent_spawn and workflow agent_type, and inherit the parent model by default. Assign only an available registry model to an individual role when needed; clearing that role returns it to inheritance. Custom agent-type files still override a built-in role's complete definition.",
|
|
140
140
|
"- Intercom: optional cross-session messaging is installed only after a native setup confirmation. It stays parent-only; Direct/Workflow children and Replay cannot use it. The status above is informational for this model-guided step—do not install packages or edit its config yourself.",
|
|
141
141
|
"",
|
|
@@ -395,7 +395,7 @@ export default function openPiSetup(pi: ExtensionAPI) {
|
|
|
395
395
|
subagent_result_display: Type.Optional(
|
|
396
396
|
StringEnum(DETAIL_DISPLAYS, {
|
|
397
397
|
description:
|
|
398
|
-
"How completed Subagent results render by default: full
|
|
398
|
+
"How completed Subagent results render by default: full shows complete output; compact shows only bounded status rows while app.tools.expand reveals the full child report. Omit to preserve the current value.",
|
|
399
399
|
}),
|
|
400
400
|
),
|
|
401
401
|
bash_tool_display: Type.Optional(
|