@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/LICENSE +21 -0
- package/README.md +281 -390
- package/SETUP.md +20 -20
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/assets/readme-hero-mobile.svg +2 -2
- package/assets/readme-hero.svg +10 -10
- package/extensions/setup/index.ts +149 -39
- package/extensions/setup/intercom-fs-helper.cjs +130 -0
- package/extensions/setup/intercom.ts +603 -0
- package/extensions/shared/child-session.ts +2 -1
- package/extensions/shared/setup-config.ts +5 -1
- package/extensions/subagents/index.ts +1 -1
- package/extensions/workflows/artifacts.ts +6 -1
- package/extensions/workflows/dashboard.ts +138 -27
- package/extensions/workflows/graph-projection.ts +238 -0
- package/extensions/workflows/handoff.ts +194 -0
- package/extensions/workflows/index.ts +242 -55
- package/extensions/workflows/invocation-ledger.ts +362 -0
- package/extensions/workflows/model.ts +52 -0
- package/extensions/workflows/operator.ts +131 -0
- package/extensions/workflows/prompt.ts +8 -6
- package/extensions/workflows/runner.ts +10 -2
- package/extensions/workflows/sandbox.ts +5 -0
- package/package.json +6 -6
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/
|
|
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
|
|
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
|
|
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
|
-
/
|
|
39
|
-
/
|
|
40
|
-
/
|
|
41
|
-
/
|
|
42
|
-
/
|
|
43
|
-
/
|
|
44
|
-
/
|
|
45
|
-
/
|
|
46
|
-
/
|
|
47
|
-
/
|
|
48
|
-
/
|
|
49
|
-
/
|
|
50
|
-
/
|
|
51
|
-
/
|
|
52
|
-
/
|
|
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 `/
|
|
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 `/
|
|
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
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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">
|
|
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.
|
|
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">
|
package/assets/readme-hero.svg
CHANGED
|
@@ -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">
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
95
|
-
<rect x="
|
|
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="
|
|
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="
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
461
|
+
{ type: "text", text: `Updated OpenPI setup. ${text}${note}` },
|
|
365
462
|
],
|
|
366
463
|
details: config,
|
|
367
464
|
};
|
|
368
465
|
},
|
|
369
466
|
});
|
|
370
467
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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");
|