@tt-a1i/openpi 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -46
- package/SETUP.md +29 -7
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +155 -0
- package/extensions/ai-providers/LICENSE.upstream +23 -0
- package/extensions/ai-providers/README.md +59 -0
- package/extensions/ai-providers/antigravity/credentials.ts +52 -0
- package/extensions/ai-providers/antigravity/discovery.ts +130 -0
- package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
- package/extensions/ai-providers/antigravity/models.ts +84 -0
- package/extensions/ai-providers/antigravity/oauth.ts +700 -0
- package/extensions/ai-providers/antigravity/provider.ts +1116 -0
- package/extensions/ai-providers/antigravity/routing.ts +340 -0
- package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
- package/extensions/ai-providers/cursor/constants.ts +5 -0
- package/extensions/ai-providers/cursor/credentials.ts +14 -0
- package/extensions/ai-providers/cursor/discovery.ts +291 -0
- package/extensions/ai-providers/cursor/input-images.ts +106 -0
- package/extensions/ai-providers/cursor/models.ts +45 -0
- package/extensions/ai-providers/cursor/oauth.ts +263 -0
- package/extensions/ai-providers/cursor/proto.ts +1064 -0
- package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
- package/extensions/ai-providers/cursor/provider.ts +1175 -0
- package/extensions/ai-providers/cursor/proxy.ts +213 -0
- package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
- package/extensions/ai-providers/index.ts +86 -0
- package/extensions/ai-providers/oauth-adapter.ts +81 -0
- package/extensions/ai-providers/usage.ts +10 -0
- package/extensions/background-terminals/index.ts +38 -3
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +484 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +47 -24
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +12 -2
- package/extensions/file-mutation-display/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +65 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +82 -6
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +153 -86
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +138 -130
- package/extensions/shared/activity-status.ts +30 -0
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +457 -21
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +83 -34
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +313 -62
- package/extensions/subagents/navigation.ts +34 -5
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +450 -70
- package/extensions/subagents/src/domain.ts +21 -1
- package/extensions/subagents/src/manager.ts +39 -2
- package/extensions/subagents/src/prompt.ts +49 -7
- package/extensions/subagents/src/result-artifact.ts +36 -0
- package/extensions/subagents/src/result-delivery.ts +39 -14
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/ui-customization/footer.ts +16 -45
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +257 -0
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +459 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +175 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +501 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +79 -5
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +70 -16
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +214 -76
- package/extensions/workflows/retention.ts +599 -0
- package/extensions/workflows/runner.ts +389 -345
- package/extensions/workflows/sandbox-child.cjs +25 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +34 -14
- package/skills/subagents/REFERENCE.md +190 -0
- package/skills/subagents/SKILL.md +2 -1
- package/skills/workflows/REFERENCE.md +6 -4
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +664 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +1069 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +467 -0
- package/web/runtime/pi-runtime.ts +1206 -0
- package/web/runtime/types.ts +102 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1700 -0
- package/web/ui/index.html +142 -0
- package/web/ui/styles.css +680 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -303
|
@@ -162,6 +162,30 @@ export function filterSessionInfos(
|
|
|
162
162
|
);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Mirror pi-tui SelectList's centered viewport so background stats work is
|
|
167
|
+
* limited to rows the picker can actually render. Keep the formula locked by
|
|
168
|
+
* tests because SelectList does not expose its visible range.
|
|
169
|
+
*/
|
|
170
|
+
export function selectSessionStatsWindow(
|
|
171
|
+
sessions: readonly SessionInfoLike[],
|
|
172
|
+
selectedPath: string,
|
|
173
|
+
maxVisible: number,
|
|
174
|
+
) {
|
|
175
|
+
if (sessions.length === 0) return [];
|
|
176
|
+
const visible = Math.max(1, Math.min(maxVisible, sessions.length));
|
|
177
|
+
const found = sessions.findIndex((session) => session.path === selectedPath);
|
|
178
|
+
const selectedIndex = found >= 0 ? found : 0;
|
|
179
|
+
const startIndex = Math.max(
|
|
180
|
+
0,
|
|
181
|
+
Math.min(
|
|
182
|
+
selectedIndex - Math.floor(visible / 2),
|
|
183
|
+
sessions.length - visible,
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
return sessions.slice(startIndex, startIndex + visible);
|
|
187
|
+
}
|
|
188
|
+
|
|
165
189
|
export function getSessionPaneLayout(width: number): SessionPaneLayout {
|
|
166
190
|
if (width < 80) {
|
|
167
191
|
return { mode: "single", listWidth: width, previewWidth: 0 };
|
|
@@ -340,12 +364,21 @@ function messageToBlocks(message: PreviewMessageLike): PreviewBlock[] {
|
|
|
340
364
|
export function buildSessionPreview(
|
|
341
365
|
session: SessionInfoLike,
|
|
342
366
|
messages: PreviewMessageLike[],
|
|
343
|
-
options: {
|
|
367
|
+
options: {
|
|
368
|
+
maxMessages?: number;
|
|
369
|
+
totalMessages?: number;
|
|
370
|
+
truncatedBytes?: number;
|
|
371
|
+
} = {},
|
|
344
372
|
): SessionPreview {
|
|
345
373
|
const maxMessages = options.maxMessages ?? 80;
|
|
346
374
|
const blocks: PreviewBlock[] = [];
|
|
347
|
-
const
|
|
348
|
-
|
|
375
|
+
const visibleMessages =
|
|
376
|
+
messages.length > maxMessages ? messages.slice(-maxMessages) : messages;
|
|
377
|
+
const totalMessages = Math.max(
|
|
378
|
+
visibleMessages.length,
|
|
379
|
+
options.totalMessages ?? messages.length,
|
|
380
|
+
);
|
|
381
|
+
const omitted = Math.max(0, totalMessages - visibleMessages.length);
|
|
349
382
|
|
|
350
383
|
if (omitted > 0) {
|
|
351
384
|
blocks.push({
|
|
@@ -353,12 +386,18 @@ export function buildSessionPreview(
|
|
|
353
386
|
text: `… ${omitted} earlier messages omitted`,
|
|
354
387
|
});
|
|
355
388
|
}
|
|
389
|
+
if ((options.truncatedBytes ?? 0) > 0) {
|
|
390
|
+
blocks.push({
|
|
391
|
+
kind: "notice",
|
|
392
|
+
text: `… ${options.truncatedBytes} bytes of preview content omitted`,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
356
395
|
|
|
357
396
|
for (const message of visibleMessages) {
|
|
358
397
|
blocks.push(...messageToBlocks(message));
|
|
359
398
|
}
|
|
360
399
|
|
|
361
|
-
const messageCount = session.messageCount ??
|
|
400
|
+
const messageCount = session.messageCount ?? totalMessages;
|
|
362
401
|
return {
|
|
363
402
|
title: buildSessionLabel(session),
|
|
364
403
|
subtitle: `${formatTimestamp(session.modified)} · ${messageCount} messages · ${cleanDisplayLine(session.cwd)}`,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ExtensionAPI,
|
|
3
3
|
ExtensionCommandContext,
|
|
4
|
+
ExtensionContext,
|
|
4
5
|
} from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
6
7
|
import { Type } from "typebox";
|
|
@@ -9,18 +10,15 @@ import {
|
|
|
9
10
|
type SubagentRoleModel,
|
|
10
11
|
type SubagentRoleModels,
|
|
11
12
|
} from "../shared/subagent-roles.ts";
|
|
12
|
-
import { sanitizeTerminalText } from "../shared/terminal-text.ts";
|
|
13
13
|
import {
|
|
14
14
|
OPENPI_SETUP_EPISODE_CHANNEL,
|
|
15
15
|
type OpenPiSetupEpisodeState,
|
|
16
16
|
} from "../shared/setup-episode-state.ts";
|
|
17
|
-
import { patchOwnedTools } from "../shared/tool-surface.ts";
|
|
18
17
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from "./intercom.ts";
|
|
18
|
+
isOwnedToolActive,
|
|
19
|
+
isOwnedToolAvailable,
|
|
20
|
+
patchOwnedTools,
|
|
21
|
+
} from "../shared/tool-surface.ts";
|
|
24
22
|
import {
|
|
25
23
|
applyFooterConfig,
|
|
26
24
|
CAPABILITY_DISCOVERY_MODES,
|
|
@@ -38,11 +36,13 @@ import {
|
|
|
38
36
|
POST_EDIT_COMMAND_MAX_CHARS,
|
|
39
37
|
REASONING_LEVELS,
|
|
40
38
|
SETUP_CONFIG_CHANGED_CHANNEL,
|
|
39
|
+
WEB_THEMES,
|
|
41
40
|
type FooterLayoutItem,
|
|
42
41
|
type CapabilityDiscoveryMode,
|
|
43
42
|
type FooterPreset,
|
|
44
43
|
type FooterStyle,
|
|
45
44
|
type MyPiSetupConfig,
|
|
45
|
+
type WebTheme,
|
|
46
46
|
} from "../shared/setup-config.ts";
|
|
47
47
|
|
|
48
48
|
const subagentRoleModelValueSchema = Type.Union([
|
|
@@ -109,7 +109,7 @@ export function buildInteractiveSetupPrompt(options: {
|
|
|
109
109
|
}) {
|
|
110
110
|
const configurationState = options.savedConfigExists
|
|
111
111
|
? [
|
|
112
|
-
"This package has already been configured. Explain the current settings in the user's language, then ask whether they want to keep them or change Capability discovery, Next-action suggestions, Workflow limits, UI/Footer, result detail display, Post-edit, Agent role models, or review everything.",
|
|
112
|
+
"This package has already been configured. Explain the current settings in the user's language, then ask whether they want to keep them or change Capability discovery, Next-action suggestions, Workflow limits, UI theme/Footer, result detail display, Post-edit, Agent role models, or review everything.",
|
|
113
113
|
"If the user keeps the current settings, do not call configure_my_pi_setup. If they choose a category, ask only the follow-up needed for that category.",
|
|
114
114
|
]
|
|
115
115
|
: [
|
|
@@ -132,16 +132,16 @@ 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 (one-line ANSI256 blocks), powerline-mono (one-line high-contrast gray powerline), and compact (one-line plain text); the default is plain with model/context on the left and git/pr/cwd 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. Footer metrics use Codicon outline glyphs for model, context, and directory; a Nerd Font renders them as designed while the text stays readable without it. Changes apply immediately in the active TUI session.",
|
|
135
|
+
"- UI: the Web theme is system (default), light, or dark and is projected from this canonical configuration without browser-local overrides. 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 model/context on the left and git/pr/cwd 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. Footer metrics use Codicon outline glyphs for model, context, and directory; a Nerd Font renders them as designed while the text stays readable without it. Changes apply immediately in the active TUI session; Web theme changes apply on its next canonical snapshot.",
|
|
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 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 one-line semantic activity summaries. Read, grep, find, and ls use the same compact activity-row projection. Ctrl+O restores Pi's native full arguments, output, errors, diffs, and timing.
|
|
138
|
+
"- Result detail display: Subagent results, Bash operations, and Write/Edit operations can each default to full or compact; all three default to 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 one-line semantic activity summaries. Read, grep, find, and ls use the same compact activity-row projection. Ctrl+O restores Pi's native full arguments, output, errors, diffs, and timing. Recommend compact for users who scan activity first and inspect evidence on demand.",
|
|
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
|
-
"- 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
140
|
"",
|
|
142
141
|
"Natural-language configuration examples the user might ask for:",
|
|
143
142
|
'- "let the model discover OpenPI capabilities when useful" → capability_discovery=adaptive',
|
|
144
143
|
'- "only use OpenPI capabilities when I ask" → capability_discovery=explicit',
|
|
144
|
+
'- "use dark theme in OpenPI Web" → ui_web_theme=dark',
|
|
145
145
|
'- "switch footer to powerline" → ui_footer_preset=powerline',
|
|
146
146
|
'- "use mono powerline" → ui_footer_preset=powerline-mono',
|
|
147
147
|
'- "compact footer" → ui_footer_preset=compact',
|
|
@@ -151,16 +151,10 @@ export function buildInteractiveSetupPrompt(options: {
|
|
|
151
151
|
'- "make explorer use my available fast model" → subagent_role_models={explorer:{provider:"…",model:"…"}}',
|
|
152
152
|
'- "make explorer inherit again" → subagent_role_models={explorer:null}',
|
|
153
153
|
"",
|
|
154
|
-
"Use ask_user for the decision instead of merely printing instructions. Put the recommended choice first. Do not change configuration until the choices are clear. Then call configure_my_pi_setup at most once with the final requested changes, preserving everything else. Do not edit configuration files directly.",
|
|
154
|
+
"configure_my_pi_setup is available only for this setup run. If the run settles without a successful apply, the writer is hidden and a later change requires /openpi-setup <request>. Use ask_user for the decision instead of merely printing instructions. Put the recommended choice first. Do not change configuration until the choices are clear. Then call configure_my_pi_setup at most once with the final requested changes, preserving everything else. Do not edit configuration files directly.",
|
|
155
155
|
];
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
const safeSetupNotice = (value: unknown, maximum = 500) =>
|
|
159
|
-
sanitizeTerminalText(value instanceof Error ? value.message : String(value))
|
|
160
|
-
.replace(/\s+/gu, " ")
|
|
161
|
-
.trim()
|
|
162
|
-
.slice(0, maximum);
|
|
163
|
-
|
|
164
158
|
export function buildSetupSuccessText(
|
|
165
159
|
currentConfiguration: string,
|
|
166
160
|
normalizationNote = "",
|
|
@@ -171,87 +165,12 @@ export function buildSetupSuccessText(
|
|
|
171
165
|
].join(" ");
|
|
172
166
|
}
|
|
173
167
|
|
|
174
|
-
export function
|
|
175
|
-
|
|
176
|
-
readonly status: PiIntercomStatus;
|
|
177
|
-
readonly mode: ExtensionCommandContext["mode"];
|
|
178
|
-
readonly idle: boolean;
|
|
179
|
-
}) {
|
|
180
|
-
return (
|
|
181
|
-
!options.request &&
|
|
182
|
-
!options.status.active &&
|
|
183
|
-
!options.status.installed &&
|
|
184
|
-
!options.status.diagnostic &&
|
|
185
|
-
options.mode === "tui" &&
|
|
186
|
-
options.idle
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function maybeOfferPiIntercom(
|
|
191
|
-
ctx: ExtensionCommandContext,
|
|
192
|
-
status: PiIntercomStatus,
|
|
193
|
-
request: string,
|
|
194
|
-
) {
|
|
195
|
-
if (
|
|
196
|
-
!shouldOfferPiIntercom({
|
|
197
|
-
request,
|
|
198
|
-
status,
|
|
199
|
-
mode: ctx.mode,
|
|
200
|
-
idle: ctx.isIdle(),
|
|
201
|
-
})
|
|
202
|
-
) {
|
|
203
|
-
return status;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const accepted = await ctx.ui.confirm(
|
|
207
|
-
status.configured
|
|
208
|
-
? "Repair optional pi-intercom integration?"
|
|
209
|
-
: "Install optional pi-intercom integration?",
|
|
210
|
-
[
|
|
211
|
-
"pi-intercom enables cross-session messaging through a local IPC broker.",
|
|
212
|
-
"Like every Pi package, it runs with full system access.",
|
|
213
|
-
"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:",
|
|
214
|
-
"• confirmSend: true",
|
|
215
|
-
'• inboundTrigger: "replies"',
|
|
216
|
-
"It remains parent-only and activates after /reload.",
|
|
217
|
-
].join("\n"),
|
|
218
|
-
);
|
|
219
|
-
if (!accepted) return status;
|
|
220
|
-
|
|
221
|
-
ctx.ui.setWorkingMessage("Installing optional pi-intercom integration...");
|
|
222
|
-
try {
|
|
223
|
-
await installPiIntercom({
|
|
224
|
-
cwd: ctx.cwd,
|
|
225
|
-
onProgress: (event) =>
|
|
226
|
-
ctx.ui.setWorkingMessage(
|
|
227
|
-
safeSetupNotice(
|
|
228
|
-
event.message ?? "Installing optional pi-intercom integration...",
|
|
229
|
-
200,
|
|
230
|
-
),
|
|
231
|
-
),
|
|
232
|
-
});
|
|
233
|
-
const installed = inspectPiIntercom({
|
|
234
|
-
cwd: ctx.cwd,
|
|
235
|
-
active: false,
|
|
236
|
-
});
|
|
237
|
-
const next = { ...installed, reloadRequired: true };
|
|
238
|
-
ctx.ui.notify(
|
|
239
|
-
"pi-intercom installed with existing preferences preserved or a new safe config created. Run /reload after setup to activate it.",
|
|
240
|
-
"info",
|
|
241
|
-
);
|
|
242
|
-
return next;
|
|
243
|
-
} catch (error) {
|
|
244
|
-
ctx.ui.notify(
|
|
245
|
-
`pi-intercom was not enabled: ${safeSetupNotice(error)}`,
|
|
246
|
-
"error",
|
|
247
|
-
);
|
|
248
|
-
return inspectPiIntercom({ cwd: ctx.cwd, active: false });
|
|
249
|
-
} finally {
|
|
250
|
-
ctx.ui.setWorkingMessage();
|
|
251
|
-
}
|
|
168
|
+
export function buildSetupNoopClosureText() {
|
|
169
|
+
return "No configuration update was confirmed in this setup run. The configuration writer is now hidden. To make a configuration change, run /openpi-setup <request>; do not edit configuration files directly.";
|
|
252
170
|
}
|
|
253
171
|
|
|
254
172
|
export const CONFIGURE_MY_PI_SETUP_TOOL_NAME = "configure_my_pi_setup";
|
|
173
|
+
const SETUP_REQUEST_CUSTOM_TYPE = "openpi-setup-request";
|
|
255
174
|
|
|
256
175
|
type SetupEpisode = "idle" | "armed" | "active";
|
|
257
176
|
|
|
@@ -259,6 +178,7 @@ function showConfigureTool(pi: ExtensionAPI) {
|
|
|
259
178
|
patchOwnedTools(pi, "setup", {
|
|
260
179
|
enable: [CONFIGURE_MY_PI_SETUP_TOOL_NAME],
|
|
261
180
|
});
|
|
181
|
+
return isOwnedToolActive(pi, "setup", CONFIGURE_MY_PI_SETUP_TOOL_NAME);
|
|
262
182
|
}
|
|
263
183
|
|
|
264
184
|
function hideConfigureTool(pi: ExtensionAPI) {
|
|
@@ -269,47 +189,130 @@ function hideConfigureTool(pi: ExtensionAPI) {
|
|
|
269
189
|
|
|
270
190
|
export default function openPiSetup(pi: ExtensionAPI) {
|
|
271
191
|
let episode: SetupEpisode = "idle";
|
|
192
|
+
let claimedToolCallId: string | undefined;
|
|
193
|
+
let blockedMatchingClaimCount = 0;
|
|
194
|
+
let requestSequence = 0;
|
|
195
|
+
const pendingRequests: Array<{ requestId: string; prompt: string }> = [];
|
|
272
196
|
const publishEpisode = () =>
|
|
273
197
|
pi.events.emit(OPENPI_SETUP_EPISODE_CHANNEL, {
|
|
274
|
-
active: episode
|
|
198
|
+
active: episode === "active",
|
|
275
199
|
} satisfies OpenPiSetupEpisodeState);
|
|
276
200
|
|
|
277
|
-
const
|
|
201
|
+
const hideActiveWriter = () => {
|
|
202
|
+
claimedToolCallId = undefined;
|
|
203
|
+
blockedMatchingClaimCount = 0;
|
|
204
|
+
episode = pendingRequests.length > 0 ? "armed" : "idle";
|
|
205
|
+
hideConfigureTool(pi);
|
|
206
|
+
publishEpisode();
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const resetEpisode = () => {
|
|
210
|
+
claimedToolCallId = undefined;
|
|
211
|
+
blockedMatchingClaimCount = 0;
|
|
212
|
+
pendingRequests.length = 0;
|
|
278
213
|
episode = "idle";
|
|
279
214
|
hideConfigureTool(pi);
|
|
280
215
|
publishEpisode();
|
|
281
216
|
};
|
|
282
217
|
|
|
283
218
|
pi.on("session_start", () => {
|
|
284
|
-
|
|
219
|
+
resetEpisode();
|
|
285
220
|
});
|
|
286
221
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
222
|
+
const dispatchNextRequest = (ctx: ExtensionContext) => {
|
|
223
|
+
const request = pendingRequests.shift();
|
|
224
|
+
if (!request) return;
|
|
225
|
+
if (!showConfigureTool(pi)) {
|
|
226
|
+
resetEpisode();
|
|
227
|
+
if (ctx.hasUI) {
|
|
228
|
+
ctx.ui.notify(
|
|
229
|
+
"OpenPI lost ownership of its configuration writer before setup started.",
|
|
230
|
+
"error",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
pi.sendMessage({
|
|
234
|
+
customType: "openpi-setup-closed",
|
|
235
|
+
content:
|
|
236
|
+
"OpenPI could not activate its owned configuration writer, so setup was not started. Check for duplicate or mismatched OpenPI extension sources, then retry with /openpi-setup <request>. Do not edit configuration files directly.",
|
|
237
|
+
display: true,
|
|
238
|
+
details: { reason: "writer_activation_failed" },
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
claimedToolCallId = undefined;
|
|
243
|
+
blockedMatchingClaimCount = 0;
|
|
244
|
+
episode = "active";
|
|
245
|
+
publishEpisode();
|
|
246
|
+
pi.sendMessage(
|
|
247
|
+
{
|
|
248
|
+
customType: SETUP_REQUEST_CUSTOM_TYPE,
|
|
249
|
+
content: request.prompt,
|
|
250
|
+
display: true,
|
|
251
|
+
details: { requestId: request.requestId },
|
|
252
|
+
},
|
|
253
|
+
{ triggerTurn: true },
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
pi.on("tool_call", (event) => {
|
|
258
|
+
if (event.toolName !== CONFIGURE_MY_PI_SETUP_TOOL_NAME) return;
|
|
259
|
+
if (
|
|
260
|
+
episode !== "active" ||
|
|
261
|
+
!isOwnedToolActive(pi, "setup", CONFIGURE_MY_PI_SETUP_TOOL_NAME)
|
|
262
|
+
) {
|
|
263
|
+
return {
|
|
264
|
+
block: true as const,
|
|
265
|
+
reason:
|
|
266
|
+
"The OpenPI configuration writer is not active for this setup episode. Run /openpi-setup <request> to start a new setup episode.",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (claimedToolCallId !== undefined) {
|
|
270
|
+
if (claimedToolCallId === event.toolCallId) {
|
|
271
|
+
blockedMatchingClaimCount += 1;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
block: true as const,
|
|
275
|
+
reason:
|
|
276
|
+
"This setup episode already admitted one configure_my_pi_setup call. Wait for its result before retrying.",
|
|
277
|
+
};
|
|
291
278
|
}
|
|
279
|
+
claimedToolCallId = event.toolCallId;
|
|
292
280
|
});
|
|
293
281
|
|
|
294
282
|
pi.on("tool_execution_end", (event) => {
|
|
295
283
|
if (
|
|
296
|
-
episode
|
|
297
|
-
event.toolName
|
|
298
|
-
|
|
299
|
-
)
|
|
300
|
-
|
|
284
|
+
episode !== "active" ||
|
|
285
|
+
event.toolName !== CONFIGURE_MY_PI_SETUP_TOOL_NAME ||
|
|
286
|
+
event.toolCallId !== claimedToolCallId
|
|
287
|
+
)
|
|
288
|
+
return;
|
|
289
|
+
if (!event.isError) {
|
|
290
|
+
hideActiveWriter();
|
|
291
|
+
} else if (blockedMatchingClaimCount > 0) {
|
|
292
|
+
blockedMatchingClaimCount -= 1;
|
|
293
|
+
} else {
|
|
294
|
+
claimedToolCallId = undefined;
|
|
301
295
|
}
|
|
302
296
|
});
|
|
303
297
|
|
|
304
|
-
pi.on("agent_settled", () => {
|
|
305
|
-
if (episode === "active")
|
|
298
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
299
|
+
if (episode === "active") {
|
|
300
|
+
hideActiveWriter();
|
|
301
|
+
pi.sendMessage({
|
|
302
|
+
customType: "openpi-setup-closed",
|
|
303
|
+
content: buildSetupNoopClosureText(),
|
|
304
|
+
display: true,
|
|
305
|
+
details: { reason: "settled_without_successful_apply" },
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
if (pendingRequests.length > 0) dispatchNextRequest(ctx);
|
|
306
309
|
});
|
|
307
310
|
|
|
308
311
|
pi.registerTool({
|
|
309
312
|
name: "configure_my_pi_setup",
|
|
310
313
|
label: "Configure OpenPI",
|
|
311
314
|
description:
|
|
312
|
-
"Apply a user-requested configuration change for this Pi setup. Configures capability discovery (explicit or opt-in adaptive), 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 the capability gateway and active TUI footer.",
|
|
315
|
+
"Apply a user-requested configuration change for this Pi setup. Configures capability discovery (explicit or opt-in adaptive), next-action suggestions, workflow fan-out, the canonical OpenPI Web theme, 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 the capability gateway and active TUI footer; Web observes theme changes through canonical snapshots.",
|
|
313
316
|
parameters: Type.Object({
|
|
314
317
|
capability_discovery: Type.Optional(
|
|
315
318
|
StringEnum(CAPABILITY_DISCOVERY_MODES, {
|
|
@@ -356,6 +359,12 @@ export default function openPiSetup(pi: ExtensionAPI) {
|
|
|
356
359
|
"Whether to show the large decorative Pi header. Defaults to false; omit to preserve the current value.",
|
|
357
360
|
}),
|
|
358
361
|
),
|
|
362
|
+
ui_web_theme: Type.Optional(
|
|
363
|
+
StringEnum(WEB_THEMES, {
|
|
364
|
+
description:
|
|
365
|
+
"Canonical OpenPI Web theme: system follows the browser/OS color scheme, light and dark force that appearance. Stored in package setup rather than browser storage. Omit to preserve the current value.",
|
|
366
|
+
}),
|
|
367
|
+
),
|
|
359
368
|
ui_custom_footer: Type.Optional(
|
|
360
369
|
Type.Boolean({
|
|
361
370
|
description:
|
|
@@ -415,7 +424,7 @@ export default function openPiSetup(pi: ExtensionAPI) {
|
|
|
415
424
|
Type.String({
|
|
416
425
|
maxLength: POST_EDIT_COMMAND_MAX_CHARS,
|
|
417
426
|
description:
|
|
418
|
-
'A single shell command (maximum 500 characters) to run in the background after a turn with successful Write/Edit operations, e.g. "npm run format". Runs once per changed turn, not per edit, and only in an interactive TUI session. Set to an empty string to turn it off. Omit to preserve the current value.',
|
|
427
|
+
'A single shell command (maximum 500 characters) to run in the background after a turn with successful Write/Edit operations, e.g. "npm run format". Set a non-empty command only when the current user\'s /openpi-setup request explicitly asks to configure Post-edit; do not infer one while changing another setting. Runs once per changed turn, not per edit, and only in an interactive TUI session. Set to an empty string to turn it off. Omit to preserve the current value.',
|
|
419
428
|
}),
|
|
420
429
|
),
|
|
421
430
|
}),
|
|
@@ -503,6 +512,9 @@ export default function openPiSetup(pi: ExtensionAPI) {
|
|
|
503
512
|
current.workflows.maxAgentCalls,
|
|
504
513
|
},
|
|
505
514
|
ui: {
|
|
515
|
+
webTheme:
|
|
516
|
+
(params.ui_web_theme as WebTheme | undefined) ??
|
|
517
|
+
current.ui.webTheme,
|
|
506
518
|
showHeader: params.ui_show_header ?? current.ui.showHeader,
|
|
507
519
|
customFooter: params.ui_custom_footer ?? current.ui.customFooter,
|
|
508
520
|
...footer,
|
|
@@ -550,15 +562,7 @@ export default function openPiSetup(pi: ExtensionAPI) {
|
|
|
550
562
|
|
|
551
563
|
const setupHandler = async (args: string, ctx: ExtensionCommandContext) => {
|
|
552
564
|
const request = args.trim();
|
|
553
|
-
|
|
554
|
-
cwd: ctx.cwd,
|
|
555
|
-
active: pi.getAllTools().some(({ name }) => name === "intercom"),
|
|
556
|
-
});
|
|
557
|
-
intercomStatus = await maybeOfferPiIntercom(ctx, intercomStatus, request);
|
|
558
|
-
|
|
559
|
-
const currentConfiguration = formatSetupConfig(loadSetupConfig(), [
|
|
560
|
-
formatPiIntercomStatus(intercomStatus),
|
|
561
|
-
]);
|
|
565
|
+
const currentConfiguration = formatSetupConfig(loadSetupConfig());
|
|
562
566
|
const savedConfigExists = hasSavedSetupConfig();
|
|
563
567
|
const currentModel = ctx.model
|
|
564
568
|
? `${ctx.model.provider}/${ctx.model.id}`
|
|
@@ -573,9 +577,9 @@ export default function openPiSetup(pi: ExtensionAPI) {
|
|
|
573
577
|
"Current configuration:",
|
|
574
578
|
currentConfiguration,
|
|
575
579
|
"",
|
|
576
|
-
"Capability discovery is explicit by default; adaptive is an opt-in that keeps only openpi_load_tools visible so the model may load useful groups. 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. A Nerd Font renders Footer Codicons and powerline seams as designed; text stays readable without it. Changes apply immediately in the active TUI session.
|
|
580
|
+
"Capability discovery is explicit by default; adaptive is an opt-in that keeps only openpi_load_tools visible so the model may load useful groups. 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. A Nerd Font renders Footer Codicons and powerline seams as designed; text stays readable without it. Changes apply immediately in the active TUI session.",
|
|
577
581
|
"",
|
|
578
|
-
"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.",
|
|
582
|
+
"configure_my_pi_setup is available only for this setup run. If the run settles without a successful apply, the writer is hidden and a later change requires /openpi-setup <request>. 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.",
|
|
579
583
|
]
|
|
580
584
|
: buildInteractiveSetupPrompt({
|
|
581
585
|
currentConfiguration,
|
|
@@ -584,13 +588,17 @@ export default function openPiSetup(pi: ExtensionAPI) {
|
|
|
584
588
|
savedConfigExists,
|
|
585
589
|
});
|
|
586
590
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
591
|
+
if (!isOwnedToolAvailable(pi, "setup", CONFIGURE_MY_PI_SETUP_TOOL_NAME)) {
|
|
592
|
+
resetEpisode();
|
|
593
|
+
const message =
|
|
594
|
+
"OpenPI could not find its owned configuration writer. Setup was not started; check for duplicate or mismatched OpenPI extension sources.";
|
|
595
|
+
if (ctx.hasUI) ctx.ui.notify(message, "error");
|
|
596
|
+
throw new Error(message);
|
|
597
|
+
}
|
|
598
|
+
const requestId = `setup-${++requestSequence}`;
|
|
599
|
+
pendingRequests.push({ requestId, prompt: prompt.join("\n") });
|
|
600
|
+
if (episode === "idle") episode = "armed";
|
|
601
|
+
if (ctx.isIdle() && episode === "armed") dispatchNextRequest(ctx);
|
|
594
602
|
};
|
|
595
603
|
|
|
596
604
|
pi.registerCommand("openpi-setup", {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
4
|
+
type StatusUI = Pick<ExtensionContext["ui"], "setStatus">;
|
|
4
5
|
|
|
5
6
|
export interface ActivityCounts {
|
|
6
7
|
running: number;
|
|
@@ -42,6 +43,35 @@ export function hasActivity(counts: ActivityCounts) {
|
|
|
42
43
|
return counts.running + counts.done + counts.failed > 0;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Pi's `setStatus` requests a global render unconditionally, without diffing
|
|
48
|
+
* the text, so rewriting the same value repaints the whole TUI for nothing.
|
|
49
|
+
* Managers notify on every child event (streaming text, tools, usage), and in
|
|
50
|
+
* the TUI the footer line stays `undefined` throughout, so the vast majority of
|
|
51
|
+
* those writes are no-ops. Keep the last written value and forward only real
|
|
52
|
+
* changes. The `ui` identity is part of the comparison: a new session hands out
|
|
53
|
+
* a new object whose footer starts empty, so the first write there must land.
|
|
54
|
+
*/
|
|
55
|
+
export function createStatusWriter(key: string) {
|
|
56
|
+
let lastUI: StatusUI | undefined;
|
|
57
|
+
let lastText: string | undefined;
|
|
58
|
+
return {
|
|
59
|
+
/** Returns whether the write reached Pi. */
|
|
60
|
+
write(ui: StatusUI, text: string | undefined) {
|
|
61
|
+
if (ui === lastUI && text === lastText) return false;
|
|
62
|
+
lastUI = ui;
|
|
63
|
+
lastText = text;
|
|
64
|
+
ui.setStatus(key, text);
|
|
65
|
+
return true;
|
|
66
|
+
},
|
|
67
|
+
/** Forget the cached value so the next write always lands. */
|
|
68
|
+
reset() {
|
|
69
|
+
lastUI = undefined;
|
|
70
|
+
lastText = undefined;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
45
75
|
export function formatActivityStatus(
|
|
46
76
|
theme: Theme,
|
|
47
77
|
label: "subagents" | "workflows",
|