@tt-a1i/openpi 0.1.0 → 0.1.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/SETUP.md CHANGED
@@ -9,14 +9,14 @@ pi install npm:@tt-a1i/openpi
9
9
  To inspect the current source before loading it, install directly from GitHub instead:
10
10
 
11
11
  ```sh
12
- pi install git:github.com/tt-a1i/my-pi-setup
12
+ pi install git:github.com/tt-a1i/openpi
13
13
  ```
14
14
 
15
15
  Pi installs the package dependencies automatically. Restart Pi or run `/reload` after installation.
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 binary in `~/.pi/agent/bin/`. Only when neither exists does it download an official release binary (macOS/Linux, arm64/x64, over HTTPS) into `~/.pi/agent/bin/` 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. 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
 
@@ -32,27 +32,27 @@ Pi will load the extensions, skills, and theme from their directories the next t
32
32
 
33
33
  ## Configure this package
34
34
 
35
- Use the single package-owned command. With no arguments, the current model explains the configurable areas and their impact, then 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:
36
36
 
37
37
  ```text
38
- /my-pi-setup
39
- /my-pi-setup 开启下一步预测,使用 seal/deepseek-v4-flash,关闭推理
40
- /my-pi-setup 关闭下一步预测
41
- /my-pi-setup workflow 同时跑 16 个 agent,总任务最多 256 个
42
- /my-pi-setup 显示大标题
43
- /my-pi-setup 切换 Footer 为 powerline
44
- /my-pi-setup 用 mono powerline Footer
45
- /my-pi-setup Footer 用 compact
46
- /my-pi-setup Footer 两行:cwd flex model / context cost flex git
47
- /my-pi-setup Footer 只显示 model、thinking、context、cache 和 git
48
- /my-pi-setup 关闭自定义状态栏
49
- /my-pi-setup 编辑后自动跑 npm run format
50
- /my-pi-setup 关闭 post-edit 命令
51
- /my-pi-setup 给 explorer 指定当前 Registry 中可用的模型
52
- /my-pi-setup 清除 explorer 的模型,让它继承父模型
38
+ /openpi-setup
39
+ /openpi-setup 开启下一步预测,使用 seal/deepseek-v4-flash,关闭推理
40
+ /openpi-setup 关闭下一步预测
41
+ /openpi-setup workflow 同时跑 16 个 agent,总任务最多 256 个
42
+ /openpi-setup 显示大标题
43
+ /openpi-setup 切换 Footer 为 powerline
44
+ /openpi-setup 用 mono powerline Footer
45
+ /openpi-setup Footer 用 compact
46
+ /openpi-setup Footer 两行:cwd flex model / context cost flex git
47
+ /openpi-setup Footer 只显示 model、thinking、context、cache 和 git
48
+ /openpi-setup 关闭自定义状态栏
49
+ /openpi-setup 编辑后自动跑 npm run format
50
+ /openpi-setup 关闭 post-edit 命令
51
+ /openpi-setup 给 explorer 指定当前 Registry 中可用的模型
52
+ /openpi-setup 清除 explorer 的模型,让它继承父模型
53
53
  ```
54
54
 
55
- Next-action suggestions default to off. Run `/my-pi-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. `/my-pi-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`.
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`.
56
56
 
57
57
  ## Session Goal and Tasks
58
58
 
@@ -62,7 +62,7 @@ Model callers use `get_goal`, `create_goal`, and `update_goal`. `create_goal` is
62
62
 
63
63
  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
64
 
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 `/my-pi-setup` setting or secondary judge model is required.
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.
66
66
 
67
67
  ## Other commands added by this fork
68
68
 
@@ -10,7 +10,6 @@ The upstream project is distributed under the MIT License. Its notice states
10
10
  `Copyright (c) 2026`; the complete license text is included at
11
11
  [`extensions/sessions/LICENSE.upstream`](extensions/sessions/LICENSE.upstream).
12
12
 
13
- The remainder of this repository retains its upstream licensing status; no
14
- project-wide license is asserted here. The npm package therefore declares
15
- `UNLICENSED`; publication does not grant permission to copy, modify, or
16
- redistribute code beyond rights supplied by an applicable upstream license.
13
+ OpenPI is distributed under the MIT License; see [`LICENSE`](LICENSE). Portions
14
+ identified in this notice retain their original copyright notices and license
15
+ terms. The project-wide MIT license does not replace or remove those notices.
@@ -1,6 +1,6 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="720" height="900" viewBox="0 0 720 900" role="img" aria-labelledby="title desc">
2
2
  <title id="title">OpenPI</title>
3
- <desc id="desc">A mobile-readable overview of the Pi-native multi-agent runtime.</desc>
3
+ <desc id="desc">OpenPI small harness, deep extensions, clean context.</desc>
4
4
  <defs>
5
5
  <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
6
6
  <stop stop-color="#0d1117"/>
@@ -21,7 +21,7 @@
21
21
  <text x="42" y="28" fill="#a5d6ff" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="18" font-weight="700" letter-spacing="1">PI NATIVE</text>
22
22
 
23
23
  <text x="0" y="122" fill="#f0f6fc" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="66" font-weight="760" letter-spacing="-2">OpenPI</text>
24
- <text x="0" y="168" fill="#8b949e" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="24">Small harness. Real runtime.</text>
24
+ <text x="0" y="168" fill="#8b949e" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="24">Small harness. Deep extensions. Clean context.</text>
25
25
  </g>
26
26
 
27
27
  <g transform="translate(48 250)" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="23" font-weight="600">
@@ -1,6 +1,6 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-labelledby="title desc">
2
2
  <title id="title">OpenPI</title>
3
- <desc id="desc">A Pi-native runtime layer for background terminals, isolated subagents, dynamic workflows, and clean context.</desc>
3
+ <desc id="desc">OpenPI small harness, deep extensions, clean context.</desc>
4
4
 
5
5
  <defs>
6
6
  <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
@@ -34,11 +34,11 @@
34
34
  <g>
35
35
  <rect width="112" height="34" rx="17" fill="#12233d" stroke="#2f81f7"/>
36
36
  <circle cx="18" cy="17" r="5" fill="#3fb950"/>
37
- <text x="33" y="22" fill="#a5d6ff" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="13" font-weight="700" letter-spacing="1">PI NATIVE</text>
37
+ <text x="33" y="22" fill="#a5d6ff" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="14" font-weight="700" letter-spacing="0.7">PI NATIVE</text>
38
38
  </g>
39
39
 
40
40
  <text x="0" y="112" fill="#f0f6fc" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="64" font-weight="760" letter-spacing="-2">OpenPI</text>
41
- <text x="0" y="155" fill="#8b949e" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="21">Keep the harness small. Give it a real runtime.</text>
41
+ <text x="0" y="155" fill="#8b949e" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="20">Small harness. Deep extensions. Clean context.</text>
42
42
 
43
43
  <g transform="translate(0 198)" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="16">
44
44
  <path d="M5 9h14M12 2l7 7-7 7" fill="none" stroke="#58a6ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -62,14 +62,14 @@
62
62
  <circle cx="22" cy="22" r="5" fill="#f85149"/>
63
63
  <circle cx="40" cy="22" r="5" fill="#d29922"/>
64
64
  <circle cx="58" cy="22" r="5" fill="#3fb950"/>
65
- <text x="229" y="27" text-anchor="middle" fill="#7d8590" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="13">pi · main</text>
65
+ <text x="229" y="27" text-anchor="middle" fill="#7d8590" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="15">pi · main</text>
66
66
 
67
- <g font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="14">
67
+ <g font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="16">
68
68
  <text x="24" y="78" fill="#7d8590">$ pi</text>
69
69
  <rect x="24" y="92" width="410" height="44" rx="9" fill="#0d1117" stroke="#21262d"/>
70
70
  <circle cx="44" cy="114" r="5" fill="#58a6ff"/>
71
71
  <text x="59" y="119" fill="#f0f6fc">main agent</text>
72
- <text x="320" y="119" fill="#7d8590">orchestrating</text>
72
+ <text x="320" y="119" fill="#7d8590" font-size="14">orchestrating</text>
73
73
 
74
74
  <path d="M47 136V155M47 155H379" fill="none" stroke="#30363d" stroke-width="2"/>
75
75
  <path d="M90 155V169M229 155V169M379 155V169" stroke="#30363d" stroke-width="2"/>
@@ -91,18 +91,18 @@
91
91
 
92
92
  <rect x="24" y="256" width="410" height="34" rx="8" fill="#0d1117" stroke="#21262d"/>
93
93
  <text x="38" y="278" fill="#7d8590">context</text>
94
- <rect x="104" y="268" width="166" height="10" rx="5" fill="#21262d"/>
95
- <rect x="104" y="268" width="61" height="10" rx="5" fill="#2f81f7"/>
94
+ <rect x="115" y="268" width="155" height="10" rx="5" fill="#21262d"/>
95
+ <rect x="115" y="268" width="57" height="10" rx="5" fill="#2f81f7"/>
96
96
  <text x="285" y="278" fill="#c9d1d9">37%</text>
97
97
  <text x="338" y="278" fill="#3fb950">settled</text>
98
98
 
99
99
  <path d="M24 308H434" stroke="#21262d"/>
100
100
  <rect x="24" y="318" width="410" height="18" rx="4" fill="#0b1720"/>
101
- <text x="34" y="332" fill="#39c5cf" font-size="13">run tests and open the PR</text>
101
+ <text x="34" y="332" fill="#39c5cf" font-size="15">run tests and open the PR</text>
102
102
  </g>
103
103
  </g>
104
104
 
105
- <g transform="translate(70 444)" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="13" font-weight="600" letter-spacing="0.4">
105
+ <g transform="translate(70 444)" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="14" font-weight="600" letter-spacing="0.25">
106
106
  <text fill="#58a6ff">BACKGROUND</text>
107
107
  <circle cx="110" cy="-4" r="2" fill="#30363d"/>
108
108
  <text x="126" fill="#3fb950">SUBAGENTS</text>
@@ -1,4 +1,7 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionCommandContext,
4
+ } from "@earendil-works/pi-coding-agent";
2
5
  import { StringEnum } from "@earendil-works/pi-ai";
3
6
  import { Type } from "typebox";
4
7
  import {
@@ -6,6 +9,13 @@ import {
6
9
  type SubagentRoleModel,
7
10
  type SubagentRoleModels,
8
11
  } from "../shared/subagent-roles.ts";
12
+ import { sanitizeTerminalText } from "../shared/terminal-text.ts";
13
+ import {
14
+ formatPiIntercomStatus,
15
+ inspectPiIntercom,
16
+ installPiIntercom,
17
+ type PiIntercomStatus,
18
+ } from "./intercom.ts";
9
19
  import {
10
20
  applyFooterConfig,
11
21
  DETAIL_DISPLAYS,
@@ -101,7 +111,7 @@ export function buildInteractiveSetupPrompt(options: {
101
111
  ];
102
112
 
103
113
  return [
104
- "Guide me through configuring the installed my-pi-setup package interactively.",
114
+ "Guide me through configuring the installed OpenPI package interactively.",
105
115
  "",
106
116
  "Current configuration:",
107
117
  options.currentConfiguration,
@@ -119,6 +129,7 @@ export function buildInteractiveSetupPrompt(options: {
119
129
  "- 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.",
120
130
  "- Result detail display: Subagent results, Bash operations, and Write/Edit operations can each default to full (always expanded) or compact (Claude Code-style folded preview with a hidden-line count). Compact output can still be temporarily expanded with the configured app.tools.expand key (Ctrl+O by default). Bash and Write/Edit default to compact. Recommend compact for users who do not usually inspect implementation details.",
121
131
  "- 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.",
132
+ "- 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.",
122
133
  "",
123
134
  "Natural-language configuration examples the user might ask for:",
124
135
  '- "switch footer to powerline" → ui_footer_preset=powerline',
@@ -134,10 +145,96 @@ export function buildInteractiveSetupPrompt(options: {
134
145
  ];
135
146
  }
136
147
 
137
- export default function myPiSetup(pi: ExtensionAPI) {
148
+ const safeSetupNotice = (value: unknown, maximum = 500) =>
149
+ sanitizeTerminalText(value instanceof Error ? value.message : String(value))
150
+ .replace(/\s+/gu, " ")
151
+ .trim()
152
+ .slice(0, maximum);
153
+
154
+ export function shouldOfferPiIntercom(options: {
155
+ readonly request: string;
156
+ readonly status: PiIntercomStatus;
157
+ readonly mode: ExtensionCommandContext["mode"];
158
+ readonly idle: boolean;
159
+ }) {
160
+ return (
161
+ !options.request &&
162
+ !options.status.active &&
163
+ !options.status.installed &&
164
+ !options.status.diagnostic &&
165
+ options.mode === "tui" &&
166
+ options.idle
167
+ );
168
+ }
169
+
170
+ async function maybeOfferPiIntercom(
171
+ ctx: ExtensionCommandContext,
172
+ status: PiIntercomStatus,
173
+ request: string,
174
+ ) {
175
+ if (
176
+ !shouldOfferPiIntercom({
177
+ request,
178
+ status,
179
+ mode: ctx.mode,
180
+ idle: ctx.isIdle(),
181
+ })
182
+ ) {
183
+ return status;
184
+ }
185
+
186
+ const accepted = await ctx.ui.confirm(
187
+ status.configured
188
+ ? "Repair optional pi-intercom integration?"
189
+ : "Install optional pi-intercom integration?",
190
+ [
191
+ "pi-intercom enables cross-session messaging through a local IPC broker.",
192
+ "Like every Pi package, it runs with full system access.",
193
+ "OpenPI will install npm:pi-intercom globally. A new private config gets safe defaults; an existing preference file is never rewritten and must already define both fields:",
194
+ "• confirmSend: true",
195
+ '• inboundTrigger: "replies"',
196
+ "It remains parent-only and activates after /reload.",
197
+ ].join("\n"),
198
+ );
199
+ if (!accepted) return status;
200
+
201
+ ctx.ui.setWorkingMessage("Installing optional pi-intercom integration...");
202
+ try {
203
+ await installPiIntercom({
204
+ cwd: ctx.cwd,
205
+ onProgress: (event) =>
206
+ ctx.ui.setWorkingMessage(
207
+ safeSetupNotice(
208
+ event.message ?? "Installing optional pi-intercom integration...",
209
+ 200,
210
+ ),
211
+ ),
212
+ });
213
+ const installed = inspectPiIntercom({
214
+ cwd: ctx.cwd,
215
+ active: false,
216
+ });
217
+ const next = { ...installed, reloadRequired: true };
218
+ ctx.ui.notify(
219
+ "pi-intercom installed with existing preferences preserved or a new safe config created. Run /reload after setup to activate it.",
220
+ "info",
221
+ );
222
+ return next;
223
+ } catch (error) {
224
+ ctx.ui.notify(
225
+ `pi-intercom was not enabled: ${safeSetupNotice(error)}`,
226
+ "error",
227
+ );
228
+ return inspectPiIntercom({ cwd: ctx.cwd, active: false });
229
+ } finally {
230
+ ctx.ui.setWorkingMessage();
231
+ }
232
+ }
233
+
234
+ export default function openPiSetup(pi: ExtensionAPI) {
138
235
  pi.registerTool({
139
236
  name: "configure_my_pi_setup",
140
- label: "Configure My Pi Setup",
237
+ label: "Configure OpenPI",
141
238
  description:
142
239
  "Apply a user-requested configuration change for this Pi setup. Configures next-action suggestions, workflow fan-out, UI/Footer (presets, style, multi-line layout), result detail display, optional Post-edit, and built-in Agent-role model assignments shared by subagent_spawn and workflow agent_type. Role models must be available in the Pi registry; null clears a role back to parent-model inheritance. Footer examples: powerline preset, powerline-mono, compact, or custom ui_footer_lines with flex. Preserve current values for settings the user did not ask to change. Changes apply immediately to an active TUI footer.",
143
240
  parameters: Type.Object({
@@ -361,48 +458,61 @@ export default function myPiSetup(pi: ExtensionAPI) {
361
458
  if (ctx.hasUI) ctx.ui.notify(`${text}${note}`, "info");
362
459
  return {
363
460
  content: [
364
- { type: "text", text: `Updated my Pi setup. ${text}${note}` },
461
+ { type: "text", text: `Updated OpenPI setup. ${text}${note}` },
365
462
  ],
366
463
  details: config,
367
464
  };
368
465
  },
369
466
  });
370
467
 
371
- pi.registerCommand("my-pi-setup", {
372
- description:
373
- "View or change this package's configuration in natural language",
374
- handler: async (args, ctx) => {
375
- const request = args.trim();
376
- const currentConfiguration = formatSetupConfig(loadSetupConfig());
377
- const savedConfigExists = hasSavedSetupConfig();
378
- const currentModel = ctx.model
379
- ? `${ctx.model.provider}/${ctx.model.id}`
380
- : "unavailable";
381
- const currentThinking = pi.getThinkingLevel();
468
+ const setupHandler = async (args: string, ctx: ExtensionCommandContext) => {
469
+ const request = args.trim();
470
+ let intercomStatus = inspectPiIntercom({
471
+ cwd: ctx.cwd,
472
+ active: pi.getAllTools().some(({ name }) => name === "intercom"),
473
+ });
474
+ intercomStatus = await maybeOfferPiIntercom(ctx, intercomStatus, request);
382
475
 
383
- const prompt = request
384
- ? [
385
- "Configure the installed my-pi-setup package according to this request:",
386
- request,
387
- "",
388
- "Current configuration:",
389
- currentConfiguration,
390
- "",
391
- "Footer tips: presets are powerline, powerline-mono, compact; style is plain/powerline/powerline-mono; custom layouts use ui_footer_lines (2D enum arrays with optional flex). Do not use ui_footer_items together with ui_footer_lines. Built-in Agent role models (explorer, implementer, reviewer, advisor) are shared by subagent_spawn and workflow agent_type; they inherit the parent unless assigned an available registry model, and clearing an assignment restores inheritance. Custom agent-type files still override built-in role definitions. Nerd Font only affects powerline separator glyphs. Changes apply immediately in the active TUI session.",
392
- "",
393
- "Use configure_my_pi_setup to apply only the requested changes and preserve everything else. Interpret model names from the available Pi registry. Do not edit configuration files directly.",
394
- ]
395
- : buildInteractiveSetupPrompt({
396
- currentConfiguration,
397
- currentModel,
398
- currentThinking,
399
- savedConfigExists,
400
- });
476
+ const currentConfiguration = formatSetupConfig(loadSetupConfig(), [
477
+ formatPiIntercomStatus(intercomStatus),
478
+ ]);
479
+ const savedConfigExists = hasSavedSetupConfig();
480
+ const currentModel = ctx.model
481
+ ? `${ctx.model.provider}/${ctx.model.id}`
482
+ : "unavailable";
483
+ const currentThinking = pi.getThinkingLevel();
401
484
 
402
- pi.sendUserMessage(
403
- prompt.join("\n"),
404
- ctx.isIdle() ? undefined : { deliverAs: "followUp" },
405
- );
406
- },
485
+ const prompt = request
486
+ ? [
487
+ "Configure the installed OpenPI package according to this request:",
488
+ request,
489
+ "",
490
+ "Current configuration:",
491
+ currentConfiguration,
492
+ "",
493
+ "Footer tips: presets are powerline, powerline-mono, compact; style is plain/powerline/powerline-mono; custom layouts use ui_footer_lines (2D enum arrays with optional flex). Do not use ui_footer_items together with ui_footer_lines. Built-in Agent role models (explorer, implementer, reviewer, advisor) are shared by subagent_spawn and workflow agent_type; they inherit the parent unless assigned an available registry model, and clearing an assignment restores inheritance. Custom agent-type files still override built-in role definitions. Nerd Font only affects powerline separator glyphs. Changes apply immediately in the active TUI session. Intercom installation is handled only by the native setup confirmation; do not install packages or edit its config yourself.",
494
+ "",
495
+ "Use configure_my_pi_setup to apply only the requested OpenPI-owned changes and preserve everything else. Interpret model names from the available Pi registry. Do not edit configuration files directly.",
496
+ ]
497
+ : buildInteractiveSetupPrompt({
498
+ currentConfiguration,
499
+ currentModel,
500
+ currentThinking,
501
+ savedConfigExists,
502
+ });
503
+
504
+ pi.sendUserMessage(
505
+ prompt.join("\n"),
506
+ ctx.isIdle() ? undefined : { deliverAs: "followUp" },
507
+ );
508
+ };
509
+
510
+ pi.registerCommand("openpi-setup", {
511
+ description: "View or change OpenPI configuration in natural language",
512
+ handler: setupHandler,
513
+ });
514
+ pi.registerCommand("my-pi-setup", {
515
+ description: "Legacy alias — use /openpi-setup",
516
+ handler: setupHandler,
407
517
  });
408
518
  }
@@ -0,0 +1,130 @@
1
+ const { randomUUID } = require("node:crypto");
2
+ const {
3
+ constants,
4
+ closeSync,
5
+ fstatSync,
6
+ openSync,
7
+ readFileSync,
8
+ renameSync,
9
+ statSync,
10
+ unlinkSync,
11
+ writeFileSync,
12
+ } = require("node:fs");
13
+
14
+ const [operation, expectedDev, expectedIno, name, encoded] =
15
+ process.argv.slice(2);
16
+ const allowedNames = new Set([
17
+ "config.json",
18
+ "config.json.openpi-install.lock",
19
+ ]);
20
+ const fail = (code, message) => {
21
+ process.stderr.write(`OPENPI:${code}:${message}\n`);
22
+ process.exit(1);
23
+ };
24
+ const identityMatches = () => {
25
+ const current = statSync(".", { bigint: true });
26
+ return (
27
+ current.isDirectory() &&
28
+ String(current.dev) === expectedDev &&
29
+ String(current.ino) === expectedIno
30
+ );
31
+ };
32
+
33
+ if (!allowedNames.has(name)) fail("INPUT", "unsupported file name");
34
+ if (!identityMatches()) fail("IDENTITY", "directory identity mismatch");
35
+
36
+ const payload = Buffer.from(encoded ?? "", "base64");
37
+ if (payload.length > 8_192) fail("INPUT", "payload too large");
38
+
39
+ if (operation === "create") {
40
+ let fd;
41
+ try {
42
+ fd = openSync(
43
+ name,
44
+ constants.O_WRONLY |
45
+ constants.O_CREAT |
46
+ constants.O_EXCL |
47
+ (constants.O_NOFOLLOW ?? 0),
48
+ 0o600,
49
+ );
50
+ } catch (error) {
51
+ if (error && error.code === "EEXIST") fail("EEXIST", "file exists");
52
+ fail("IO", error instanceof Error ? error.message : String(error));
53
+ }
54
+ try {
55
+ const metadata = fstatSync(fd);
56
+ if (!metadata.isFile()) fail("TYPE", "created path is not a regular file");
57
+ writeFileSync(fd, payload);
58
+ } finally {
59
+ closeSync(fd);
60
+ }
61
+ if (!identityMatches()) fail("IDENTITY", "directory identity changed");
62
+ process.exit(0);
63
+ }
64
+
65
+ if (operation === "remove-owned") {
66
+ // Atomically move the current lock to a unique claim before inspecting it.
67
+ // A replacement lock can then appear at `name`, but cleanup never unlinks
68
+ // that pathname: it removes only inode-pinned, randomly named claims.
69
+ const claim = `.${name}.release.${process.pid}.${randomUUID()}`;
70
+ const markerClaim = `${claim}.marker`;
71
+ const releaseToken = Buffer.from(`release:${process.pid}:${randomUUID()}\n`);
72
+ try {
73
+ renameSync(name, claim);
74
+ } catch (error) {
75
+ fail("IO", error instanceof Error ? error.message : String(error));
76
+ }
77
+
78
+ let markerCreated = false;
79
+ let markerFd;
80
+ try {
81
+ markerFd = openSync(
82
+ name,
83
+ constants.O_WRONLY |
84
+ constants.O_CREAT |
85
+ constants.O_EXCL |
86
+ (constants.O_NOFOLLOW ?? 0),
87
+ 0o600,
88
+ );
89
+ writeFileSync(markerFd, releaseToken);
90
+ markerCreated = true;
91
+ } catch (error) {
92
+ if (!error || error.code !== "EEXIST") {
93
+ fail("IO", error instanceof Error ? error.message : String(error));
94
+ }
95
+ } finally {
96
+ if (markerFd !== undefined) closeSync(markerFd);
97
+ }
98
+
99
+ let claimFd;
100
+ try {
101
+ claimFd = openSync(claim, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
102
+ if (!fstatSync(claimFd).isFile())
103
+ fail("TYPE", "claim is not a regular file");
104
+ if (!readFileSync(claimFd).equals(payload))
105
+ fail("OWNER", "owner token mismatch");
106
+ } finally {
107
+ if (claimFd !== undefined) closeSync(claimFd);
108
+ }
109
+ unlinkSync(claim);
110
+
111
+ if (markerCreated) {
112
+ renameSync(name, markerClaim);
113
+ let releaseFd;
114
+ try {
115
+ releaseFd = openSync(
116
+ markerClaim,
117
+ constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),
118
+ );
119
+ if (!readFileSync(releaseFd).equals(releaseToken))
120
+ fail("OWNER", "release marker ownership mismatch");
121
+ } finally {
122
+ if (releaseFd !== undefined) closeSync(releaseFd);
123
+ }
124
+ unlinkSync(markerClaim);
125
+ }
126
+ if (!identityMatches()) fail("IDENTITY", "directory identity changed");
127
+ process.exit(0);
128
+ }
129
+
130
+ fail("INPUT", "unsupported operation");