@tt-a1i/openpi 0.1.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.
Files changed (114) hide show
  1. package/README.md +643 -0
  2. package/SETUP.md +74 -0
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/assets/openpi-package.png +0 -0
  5. package/assets/readme-hero-mobile.svg +72 -0
  6. package/assets/readme-hero.svg +118 -0
  7. package/assets/readme-runtime-mobile.svg +91 -0
  8. package/assets/readme-runtime.svg +111 -0
  9. package/extensions/ask-user/handoff.ts +205 -0
  10. package/extensions/ask-user/index.ts +1110 -0
  11. package/extensions/ask-user/limits.ts +89 -0
  12. package/extensions/ask-user/prompt.ts +76 -0
  13. package/extensions/background-terminals/index.ts +653 -0
  14. package/extensions/background-terminals/src/domain.ts +99 -0
  15. package/extensions/background-terminals/src/manager.ts +989 -0
  16. package/extensions/background-terminals/src/output.ts +84 -0
  17. package/extensions/background-terminals/src/prompt.ts +195 -0
  18. package/extensions/background-terminals/src/result-delivery.ts +43 -0
  19. package/extensions/background-terminals/src/runtime.ts +36 -0
  20. package/extensions/background-terminals/src/ui/output-view.ts +55 -0
  21. package/extensions/background-terminals/src/ui/ps.ts +642 -0
  22. package/extensions/background-terminals/src/ui/tool-result.ts +146 -0
  23. package/extensions/background-terminals/src/watch.ts +192 -0
  24. package/extensions/context-pivot/index.ts +222 -0
  25. package/extensions/copy-all/index.ts +65 -0
  26. package/extensions/cron/index.ts +173 -0
  27. package/extensions/cron/schedule.ts +127 -0
  28. package/extensions/file-mutation-display/index.ts +105 -0
  29. package/extensions/file-mutation-display/render.ts +107 -0
  30. package/extensions/file-search/index.ts +515 -0
  31. package/extensions/file-search/src/args.ts +129 -0
  32. package/extensions/file-search/src/binaries.ts +419 -0
  33. package/extensions/file-search/src/output.ts +142 -0
  34. package/extensions/file-search/src/process.ts +309 -0
  35. package/extensions/file-search/src/prompt.ts +53 -0
  36. package/extensions/git-info/index.ts +272 -0
  37. package/extensions/git-info/src/changed-files-view.ts +414 -0
  38. package/extensions/git-info/src/process.ts +107 -0
  39. package/extensions/git-info/src/refresh-coordinator.ts +13 -0
  40. package/extensions/git-info/src/runtime.ts +28 -0
  41. package/extensions/goal/controller.ts +794 -0
  42. package/extensions/goal/index.ts +521 -0
  43. package/extensions/goal/prompts.ts +122 -0
  44. package/extensions/goal/state.ts +763 -0
  45. package/extensions/goal/ui.ts +158 -0
  46. package/extensions/model-info/index.ts +234 -0
  47. package/extensions/plan-mode/bash-policy.ts +313 -0
  48. package/extensions/plan-mode/index.ts +539 -0
  49. package/extensions/post-edit/index.ts +129 -0
  50. package/extensions/sessions/LICENSE.upstream +21 -0
  51. package/extensions/sessions/git-stats.ts +226 -0
  52. package/extensions/sessions/index.ts +1092 -0
  53. package/extensions/sessions/sessions.ts +385 -0
  54. package/extensions/setup/index.ts +408 -0
  55. package/extensions/shared/activity-status.ts +65 -0
  56. package/extensions/shared/below-editor-navigation.ts +343 -0
  57. package/extensions/shared/child-session.ts +352 -0
  58. package/extensions/shared/context-utilization.ts +47 -0
  59. package/extensions/shared/dashboard-state.ts +102 -0
  60. package/extensions/shared/plan-mode-state.ts +65 -0
  61. package/extensions/shared/setup-config.ts +971 -0
  62. package/extensions/shared/subagent-roles.ts +22 -0
  63. package/extensions/shared/terminal-text.ts +38 -0
  64. package/extensions/shared/tool-call-timeout.ts +104 -0
  65. package/extensions/shared/worktree.ts +526 -0
  66. package/extensions/subagents/index.ts +1225 -0
  67. package/extensions/subagents/navigation.ts +121 -0
  68. package/extensions/subagents/src/agent-types.ts +543 -0
  69. package/extensions/subagents/src/backend.ts +63 -0
  70. package/extensions/subagents/src/backends/pi.ts +493 -0
  71. package/extensions/subagents/src/backends/stub.ts +296 -0
  72. package/extensions/subagents/src/by-the-way.ts +21 -0
  73. package/extensions/subagents/src/domain.ts +271 -0
  74. package/extensions/subagents/src/format.ts +48 -0
  75. package/extensions/subagents/src/manager.ts +769 -0
  76. package/extensions/subagents/src/prompt.ts +190 -0
  77. package/extensions/subagents/src/result-delivery.ts +20 -0
  78. package/extensions/subagents/src/runtime.ts +51 -0
  79. package/extensions/subagents/src/ui/takeover.ts +615 -0
  80. package/extensions/subagents/src/ui/transcript.ts +293 -0
  81. package/extensions/subagents/src/ui/wait-result.ts +89 -0
  82. package/extensions/suggestions/index.ts +172 -0
  83. package/extensions/suggestions/src/config.ts +12 -0
  84. package/extensions/suggestions/src/predictor.ts +147 -0
  85. package/extensions/suggestions/src/prompt.ts +20 -0
  86. package/extensions/suggestions/src/transcript.ts +233 -0
  87. package/extensions/suggestions/src/ui.ts +224 -0
  88. package/extensions/tasks/index.ts +512 -0
  89. package/extensions/tasks/tasks.ts +649 -0
  90. package/extensions/tasks/ui.ts +421 -0
  91. package/extensions/turn-time/index.ts +61 -0
  92. package/extensions/ui-customization/footer.ts +512 -0
  93. package/extensions/ui-customization/index.ts +217 -0
  94. package/extensions/workflows/acceptance.ts +298 -0
  95. package/extensions/workflows/artifacts.ts +225 -0
  96. package/extensions/workflows/controller.ts +210 -0
  97. package/extensions/workflows/dashboard.ts +1226 -0
  98. package/extensions/workflows/index.ts +1884 -0
  99. package/extensions/workflows/journal.ts +188 -0
  100. package/extensions/workflows/meta.ts +250 -0
  101. package/extensions/workflows/model.ts +423 -0
  102. package/extensions/workflows/navigation.ts +93 -0
  103. package/extensions/workflows/prompt.ts +212 -0
  104. package/extensions/workflows/replay-safety.ts +577 -0
  105. package/extensions/workflows/runner.ts +786 -0
  106. package/extensions/workflows/sandbox-child.cjs +402 -0
  107. package/extensions/workflows/sandbox.ts +397 -0
  108. package/extensions/workflows/serialization.ts +162 -0
  109. package/extensions/workflows/worktree-handoff.ts +216 -0
  110. package/package.json +87 -0
  111. package/scripts/prepare-effect-tsgo.mjs +16 -0
  112. package/skills/background-terminals/SKILL.md +30 -0
  113. package/skills/subagents/SKILL.md +15 -0
  114. package/themes/github-dark-default.json +89 -0
@@ -0,0 +1,408 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { StringEnum } from "@earendil-works/pi-ai";
3
+ import { Type } from "typebox";
4
+ import {
5
+ SUBAGENT_ROLE_NAMES,
6
+ type SubagentRoleModel,
7
+ type SubagentRoleModels,
8
+ } from "../shared/subagent-roles.ts";
9
+ import {
10
+ applyFooterConfig,
11
+ DETAIL_DISPLAYS,
12
+ FOOTER_ITEMS,
13
+ FOOTER_LAYOUT_ITEMS,
14
+ FOOTER_PRESETS,
15
+ FOOTER_STYLES,
16
+ formatSetupConfig,
17
+ hasSavedSetupConfig,
18
+ loadSetupConfig,
19
+ updateSetupConfig,
20
+ MAX_WORKFLOW_AGENT_CALLS,
21
+ MAX_WORKFLOW_CONCURRENCY,
22
+ POST_EDIT_COMMAND_MAX_CHARS,
23
+ REASONING_LEVELS,
24
+ SETUP_CONFIG_CHANGED_CHANNEL,
25
+ type FooterLayoutItem,
26
+ type FooterPreset,
27
+ type FooterStyle,
28
+ type MyPiSetupConfig,
29
+ } from "../shared/setup-config.ts";
30
+
31
+ const subagentRoleModelValueSchema = Type.Union([
32
+ Type.Object(
33
+ {
34
+ provider: Type.String({ description: "Available Pi provider id." }),
35
+ model: Type.String({ description: "Available Pi model id." }),
36
+ },
37
+ { additionalProperties: false },
38
+ ),
39
+ Type.Null(),
40
+ ]);
41
+
42
+ export const SUBAGENT_ROLE_MODELS_SCHEMA = Type.Partial(
43
+ Type.Record(
44
+ Type.Union(SUBAGENT_ROLE_NAMES.map((role) => Type.Literal(role))),
45
+ subagentRoleModelValueSchema,
46
+ ),
47
+ {
48
+ additionalProperties: false,
49
+ description:
50
+ "Partial built-in-role model assignments shared by subagent_spawn and workflow agent_type. Each value is an available {provider, model}; null clears that role to inherit the parent model, and omitted roles are preserved.",
51
+ },
52
+ );
53
+
54
+ export function applySubagentRoleModelUpdates(
55
+ current: SubagentRoleModels,
56
+ updates:
57
+ | Partial<
58
+ Record<(typeof SUBAGENT_ROLE_NAMES)[number], SubagentRoleModel | null>
59
+ >
60
+ | undefined,
61
+ findModel: (
62
+ provider: string,
63
+ model: string,
64
+ ) => { readonly provider: string; readonly id: string } | undefined,
65
+ ) {
66
+ if (!updates) return current;
67
+
68
+ const roleModels = { ...current };
69
+ for (const role of SUBAGENT_ROLE_NAMES) {
70
+ const update = updates[role];
71
+ if (update === undefined) continue;
72
+ if (update === null) {
73
+ delete roleModels[role];
74
+ continue;
75
+ }
76
+ const resolved = findModel(update.provider, update.model);
77
+ if (!resolved) {
78
+ throw new Error(
79
+ `Unknown configured subagent role model for ${role}: ${update.provider}/${update.model}`,
80
+ );
81
+ }
82
+ roleModels[role] = { provider: resolved.provider, model: resolved.id };
83
+ }
84
+ return roleModels;
85
+ }
86
+
87
+ export function buildInteractiveSetupPrompt(options: {
88
+ currentConfiguration: string;
89
+ currentModel: string;
90
+ currentThinking: string;
91
+ savedConfigExists: boolean;
92
+ }) {
93
+ const configurationState = options.savedConfigExists
94
+ ? [
95
+ "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 Next-action suggestions, Workflow limits, UI/Footer, result detail display, Post-edit, Agent role models, or review everything.",
96
+ "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.",
97
+ ]
98
+ : [
99
+ "This is the first setup. Explain the available choices and their impact in the user's language, then collect the initial preferences.",
100
+ "Prefer one ask_user call with up to three independent questions covering Next-action suggestions, Workflow limits, and UI/Footer/result display. Explain that Post-edit defaults off; keep it off unless the user opts in, then ask only for the command. Explain that built-in Agent roles used by subagent_spawn and workflow agent_type inherit the parent model unless the user assigns an available model to a role.",
101
+ ];
102
+
103
+ return [
104
+ "Guide me through configuring the installed my-pi-setup package interactively.",
105
+ "",
106
+ "Current configuration:",
107
+ options.currentConfiguration,
108
+ `Current Pi model: ${options.currentModel}`,
109
+ `Current Pi thinking level: ${options.currentThinking}`,
110
+ `Saved configuration exists: ${options.savedConfigExists ? "yes" : "no"}`,
111
+ "",
112
+ ...configurationState,
113
+ "",
114
+ "Before asking, briefly explain what can be configured and the practical impact:",
115
+ "- 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.",
116
+ "- 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.",
117
+ "- UI: the large header costs vertical space; the custom footer is a declarative dashboard. Presets: powerline (default one-line ANSI256 blocks), powerline-mono (one-line high-contrast gray powerline), and compact (one-line plain text). Style can also be set independently: plain, powerline, powerline-mono. Custom lines are a 2D layout of cwd/model/thinking/context/cache/cost/throughput/git/pr plus at most one flex per line for left/right alignment. Nerd Font only affects powerline separator glyphs; text stays readable without it. Changes apply immediately in the active TUI session.",
118
+ "- Operational activity for Subagents, Workflows, and background terminals is core status and always remains visible whenever the custom footer is enabled.",
119
+ "- 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
+ "- 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
+ "- 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.",
122
+ "",
123
+ "Natural-language configuration examples the user might ask for:",
124
+ '- "switch footer to powerline" → ui_footer_preset=powerline',
125
+ '- "use mono powerline" → ui_footer_preset=powerline-mono',
126
+ '- "compact footer" → ui_footer_preset=compact',
127
+ '- "two custom lines: cwd flex model / context cost flex git" → ui_footer_lines=[["cwd","flex","model"],["context","cost","flex","git"]]',
128
+ '- "run npm run format after Write/Edit turns" → post_edit_command="npm run format"',
129
+ '- "turn off post-edit" → post_edit_command=""',
130
+ '- "make explorer use my available fast model" → subagent_role_models={explorer:{provider:"…",model:"…"}}',
131
+ '- "make explorer inherit again" → subagent_role_models={explorer:null}',
132
+ "",
133
+ "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.",
134
+ ];
135
+ }
136
+
137
+ export default function myPiSetup(pi: ExtensionAPI) {
138
+ pi.registerTool({
139
+ name: "configure_my_pi_setup",
140
+ label: "Configure My Pi Setup",
141
+ description:
142
+ "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
+ parameters: Type.Object({
144
+ suggestions_enabled: Type.Optional(
145
+ Type.Boolean({
146
+ description:
147
+ "Whether model-generated next-action ghost suggestions are enabled. Omit to preserve the current value.",
148
+ }),
149
+ ),
150
+ suggestion_provider: Type.Optional(
151
+ Type.String({ description: "Configured Pi provider id." }),
152
+ ),
153
+ suggestion_model: Type.Optional(
154
+ Type.String({ description: "Configured Pi model id." }),
155
+ ),
156
+ suggestion_reasoning: Type.Optional(
157
+ StringEnum(REASONING_LEVELS, {
158
+ description: "Reasoning level for the suggestion model.",
159
+ }),
160
+ ),
161
+ workflow_concurrency: Type.Optional(
162
+ Type.Integer({
163
+ minimum: 1,
164
+ maximum: MAX_WORKFLOW_CONCURRENCY,
165
+ description:
166
+ "Maximum simultaneously running agents in each workflow (default 8, hard maximum 64). Omit to preserve the current value.",
167
+ }),
168
+ ),
169
+ workflow_max_agent_calls: Type.Optional(
170
+ Type.Integer({
171
+ minimum: 1,
172
+ maximum: MAX_WORKFLOW_AGENT_CALLS,
173
+ description:
174
+ "Maximum total agent() calls in each workflow (default 128, hard maximum 1024). Omit to preserve the current value.",
175
+ }),
176
+ ),
177
+ ui_show_header: Type.Optional(
178
+ Type.Boolean({
179
+ description:
180
+ "Whether to show the large decorative Pi header. Defaults to false; omit to preserve the current value.",
181
+ }),
182
+ ),
183
+ ui_custom_footer: Type.Optional(
184
+ Type.Boolean({
185
+ description:
186
+ "Whether to replace Pi's footer with the package dashboard footer. Defaults to true; omit to preserve the current value.",
187
+ }),
188
+ ),
189
+ ui_footer_preset: Type.Optional(
190
+ StringEnum(FOOTER_PRESETS, {
191
+ description:
192
+ "Convenient footer preset applied first: powerline (one-line ANSI256 blocks), powerline-mono (one-line gray powerline), compact (one-line plain text). Style/lines overrides still win after the preset. Omit to preserve the current layout unless other footer fields are set.",
193
+ }),
194
+ ),
195
+ ui_footer_style: Type.Optional(
196
+ StringEnum(FOOTER_STYLES, {
197
+ description:
198
+ "Footer visual style: plain (Pi theme separators), powerline (ANSI256 colored blocks with  seams), powerline-mono (high-contrast gray powerline). Nerd Font improves separator glyphs only. Omit to preserve the current style (or the preset's style when a preset is applied).",
199
+ }),
200
+ ),
201
+ ui_footer_lines: Type.Optional(
202
+ Type.Array(
203
+ Type.Array(StringEnum(FOOTER_LAYOUT_ITEMS), { minItems: 1 }),
204
+ {
205
+ minItems: 1,
206
+ description:
207
+ "Declarative multi-line footer layout. Each row is an ordered list of metrics (cwd, model, thinking, context, cache, cost, throughput, git, pr) plus at most one flex for left/right alignment. Unknown/duplicate metrics are dropped; empty result falls back to the default. Cannot be combined with ui_footer_items.",
208
+ },
209
+ ),
210
+ ),
211
+ ui_footer_items: Type.Optional(
212
+ Type.Array(StringEnum(FOOTER_ITEMS), {
213
+ minItems: 1,
214
+ uniqueItems: true,
215
+ description:
216
+ "Legacy flat footer metric selection. Mapped onto the default one-line skeleton (metrics not listed are hidden). Prefer ui_footer_lines for custom multi-line layouts. Cannot be combined with ui_footer_lines. Operational activity remains visible whenever the custom footer is enabled. Omit to preserve the current selection.",
217
+ }),
218
+ ),
219
+ subagent_result_display: Type.Optional(
220
+ StringEnum(DETAIL_DISPLAYS, {
221
+ description:
222
+ "How completed Subagent results render by default: full preserves complete output; compact shows a bounded preview that can be expanded with app.tools.expand. Omit to preserve the current value.",
223
+ }),
224
+ ),
225
+ bash_tool_display: Type.Optional(
226
+ StringEnum(DETAIL_DISPLAYS, {
227
+ description:
228
+ "How Bash commands and output render by default: compact keeps a one-line command plus a bounded output preview with a hidden-line count and expands with app.tools.expand; full keeps every command expanded. Omit to preserve the current value.",
229
+ }),
230
+ ),
231
+ file_mutation_display: Type.Optional(
232
+ StringEnum(DETAIL_DISPLAYS, {
233
+ description:
234
+ "How Write/Edit content and diffs render by default: compact shows a Claude Code-style folded preview with a hidden-line count and expands with app.tools.expand; full keeps every operation expanded. Omit to preserve the current value.",
235
+ }),
236
+ ),
237
+ subagent_role_models: Type.Optional(SUBAGENT_ROLE_MODELS_SCHEMA),
238
+ post_edit_command: Type.Optional(
239
+ Type.String({
240
+ maxLength: POST_EDIT_COMMAND_MAX_CHARS,
241
+ description:
242
+ '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.',
243
+ }),
244
+ ),
245
+ }),
246
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
247
+ const modelFields = [
248
+ params.suggestion_provider,
249
+ params.suggestion_model,
250
+ params.suggestion_reasoning,
251
+ ];
252
+ const supplied = modelFields.filter(
253
+ (value) => value !== undefined,
254
+ ).length;
255
+ if (supplied !== 0 && supplied !== modelFields.length) {
256
+ throw new Error(
257
+ "suggestion_provider, suggestion_model, and suggestion_reasoning must be provided together, or all omitted.",
258
+ );
259
+ }
260
+
261
+ const buildConfig = (current: MyPiSetupConfig) => {
262
+ let model = current.suggestions.model;
263
+ if (params.suggestion_provider && params.suggestion_model) {
264
+ const resolved = ctx.modelRegistry.find(
265
+ params.suggestion_provider,
266
+ params.suggestion_model,
267
+ );
268
+ if (!resolved) {
269
+ throw new Error(
270
+ `Unknown configured model: ${params.suggestion_provider}/${params.suggestion_model}`,
271
+ );
272
+ }
273
+ model = {
274
+ provider: resolved.provider,
275
+ model: resolved.id,
276
+ reasoning: params.suggestion_reasoning!,
277
+ };
278
+ }
279
+ const suggestionsEnabled =
280
+ params.suggestions_enabled ??
281
+ (params.suggestion_provider ? true : current.suggestions.enabled);
282
+ if (suggestionsEnabled && !model) {
283
+ throw new Error(
284
+ "Next-action suggestions require suggestion_provider, suggestion_model, and suggestion_reasoning.",
285
+ );
286
+ }
287
+
288
+ const footer = applyFooterConfig(
289
+ {
290
+ footerStyle: current.ui.footerStyle,
291
+ footerLines: current.ui.footerLines,
292
+ },
293
+ {
294
+ ...(params.ui_footer_preset !== undefined
295
+ ? { preset: params.ui_footer_preset as FooterPreset }
296
+ : {}),
297
+ ...(params.ui_footer_style !== undefined
298
+ ? { style: params.ui_footer_style as FooterStyle }
299
+ : {}),
300
+ ...(params.ui_footer_lines !== undefined
301
+ ? {
302
+ lines: params.ui_footer_lines as FooterLayoutItem[][],
303
+ }
304
+ : {}),
305
+ ...(params.ui_footer_items !== undefined
306
+ ? { items: params.ui_footer_items }
307
+ : {}),
308
+ },
309
+ );
310
+
311
+ const config: MyPiSetupConfig = {
312
+ suggestions: {
313
+ enabled: suggestionsEnabled,
314
+ ...(model ? { model } : {}),
315
+ },
316
+ workflows: {
317
+ concurrency:
318
+ params.workflow_concurrency ?? current.workflows.concurrency,
319
+ maxAgentCalls:
320
+ params.workflow_max_agent_calls ??
321
+ current.workflows.maxAgentCalls,
322
+ },
323
+ ui: {
324
+ showHeader: params.ui_show_header ?? current.ui.showHeader,
325
+ customFooter: params.ui_custom_footer ?? current.ui.customFooter,
326
+ ...footer,
327
+ subagentResultDisplay:
328
+ params.subagent_result_display ??
329
+ current.ui.subagentResultDisplay,
330
+ bashToolDisplay:
331
+ params.bash_tool_display ?? current.ui.bashToolDisplay,
332
+ fileMutationDisplay:
333
+ params.file_mutation_display ?? current.ui.fileMutationDisplay,
334
+ },
335
+ postEdit: {
336
+ command:
337
+ params.post_edit_command !== undefined
338
+ ? params.post_edit_command.trim()
339
+ : current.postEdit.command,
340
+ },
341
+ subagents: {
342
+ roleModels: applySubagentRoleModelUpdates(
343
+ current.subagents.roleModels,
344
+ params.subagent_role_models,
345
+ (provider, modelId) => ctx.modelRegistry.find(provider, modelId),
346
+ ),
347
+ },
348
+ };
349
+ return config;
350
+ };
351
+
352
+ // Patch the document as it is on disk now, not as it was when this call
353
+ // started, and report any stored value that was normalized or migrated.
354
+ const { config, replaced } = await updateSetupConfig(buildConfig);
355
+ pi.events.emit(SETUP_CONFIG_CHANGED_CHANNEL, config);
356
+ const text = formatSetupConfig(config);
357
+ const note =
358
+ replaced.length > 0
359
+ ? ` Normalized or migrated stored values: ${replaced.join(", ")}.`
360
+ : "";
361
+ if (ctx.hasUI) ctx.ui.notify(`${text}${note}`, "info");
362
+ return {
363
+ content: [
364
+ { type: "text", text: `Updated my Pi setup. ${text}${note}` },
365
+ ],
366
+ details: config,
367
+ };
368
+ },
369
+ });
370
+
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();
382
+
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
+ });
401
+
402
+ pi.sendUserMessage(
403
+ prompt.join("\n"),
404
+ ctx.isIdle() ? undefined : { deliverAs: "followUp" },
405
+ );
406
+ },
407
+ });
408
+ }
@@ -0,0 +1,65 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ type Theme = ExtensionContext["ui"]["theme"];
4
+
5
+ export interface ActivityCounts {
6
+ running: number;
7
+ done: number;
8
+ failed: number;
9
+ }
10
+
11
+ const SQUARE = "■";
12
+
13
+ /**
14
+ * Settled work is an unread notice, not a session tally: `done`/`failed` stay
15
+ * visible until the user's next explicit request acknowledges them, while
16
+ * running work is always reported. `acknowledgedAt` is the timestamp of that
17
+ * last explicit request. A settle in that same millisecond, or one carrying no
18
+ * timestamp, stays unread rather than being silently swallowed.
19
+ */
20
+ export function unreadActivityCounts(
21
+ items: readonly {
22
+ readonly status: "running" | "done" | "error";
23
+ readonly settledAt?: number;
24
+ }[],
25
+ acknowledgedAt: number,
26
+ ): ActivityCounts {
27
+ let running = 0;
28
+ let done = 0;
29
+ let failed = 0;
30
+ for (const item of items) {
31
+ if (item.status === "running") {
32
+ running += 1;
33
+ continue;
34
+ }
35
+ if (item.settledAt !== undefined && item.settledAt < acknowledgedAt)
36
+ continue;
37
+ if (item.status === "error") failed += 1;
38
+ else done += 1;
39
+ }
40
+ return { running, done, failed };
41
+ }
42
+
43
+ export function hasActivity(counts: ActivityCounts) {
44
+ return counts.running + counts.done + counts.failed > 0;
45
+ }
46
+
47
+ export function formatActivityStatus(
48
+ theme: Theme,
49
+ label: "subagents" | "workflows",
50
+ counts: ActivityCounts,
51
+ ) {
52
+ const parts: string[] = [];
53
+ if (counts.running > 0) {
54
+ parts.push(theme.fg("warning", `${SQUARE} ${counts.running} running`));
55
+ }
56
+ if (counts.done > 0) {
57
+ parts.push(theme.fg("success", `${SQUARE} ${counts.done} done`));
58
+ }
59
+ if (counts.failed > 0) {
60
+ parts.push(theme.fg("error", `${SQUARE} ${counts.failed} failed`));
61
+ }
62
+ parts.push(theme.fg("accent", `/${label}`) + theme.fg("dim", " to view"));
63
+
64
+ return `${theme.fg("muted", `${label}:`)} ${parts.join(theme.fg("dim", " · "))}`;
65
+ }