@oh-my-pi/pi-coding-agent 15.5.15 → 15.7.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 (274) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/types/capability/rule-buckets.d.ts +30 -0
  3. package/dist/types/capability/rule.d.ts +7 -0
  4. package/dist/types/cli/classify-install-target.d.ts +0 -10
  5. package/dist/types/cli/completion-gen.d.ts +80 -0
  6. package/dist/types/cli/initial-message.d.ts +1 -1
  7. package/dist/types/cli/tiny-models-cli.d.ts +9 -0
  8. package/dist/types/commands/complete.d.ts +6 -0
  9. package/dist/types/commands/completions.d.ts +13 -0
  10. package/dist/types/commands/setup.d.ts +10 -1
  11. package/dist/types/commands/tiny-models.d.ts +22 -0
  12. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  13. package/dist/types/commit/analysis/summary.d.ts +1 -1
  14. package/dist/types/commit/changelog/generate.d.ts +1 -1
  15. package/dist/types/commit/changelog/index.d.ts +2 -2
  16. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  17. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  18. package/dist/types/config/model-id-affixes.d.ts +10 -0
  19. package/dist/types/config/settings-schema.d.ts +402 -17
  20. package/dist/types/discovery/builtin-defaults.d.ts +1 -0
  21. package/dist/types/discovery/builtin-rules/index.d.ts +7 -0
  22. package/dist/types/discovery/helpers.d.ts +1 -1
  23. package/dist/types/discovery/index.d.ts +1 -0
  24. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  25. package/dist/types/edit/hashline/block-resolver.d.ts +9 -0
  26. package/dist/types/edit/hashline/index.d.ts +1 -0
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
  28. package/dist/types/eval/py/kernel.d.ts +3 -0
  29. package/dist/types/eval/py/runtime.d.ts +11 -1
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
  32. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
  33. package/dist/types/internal-urls/local-protocol.d.ts +2 -1
  34. package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
  35. package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
  36. package/dist/types/internal-urls/router.d.ts +8 -1
  37. package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
  38. package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
  39. package/dist/types/internal-urls/types.d.ts +26 -0
  40. package/dist/types/main.d.ts +1 -0
  41. package/dist/types/memory-backend/index.d.ts +1 -0
  42. package/dist/types/memory-backend/resolve.d.ts +2 -1
  43. package/dist/types/memory-backend/types.d.ts +7 -1
  44. package/dist/types/mnemosyne/backend.d.ts +4 -0
  45. package/dist/types/mnemosyne/config.d.ts +29 -0
  46. package/dist/types/mnemosyne/index.d.ts +3 -0
  47. package/dist/types/mnemosyne/state.d.ts +72 -0
  48. package/dist/types/modes/components/custom-editor.d.ts +2 -3
  49. package/dist/types/modes/components/hook-selector.d.ts +27 -0
  50. package/dist/types/modes/components/index.d.ts +2 -0
  51. package/dist/types/modes/components/segment-track.d.ts +22 -0
  52. package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
  53. package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
  54. package/dist/types/modes/components/welcome.d.ts +22 -0
  55. package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
  56. package/dist/types/modes/gradient-highlight.d.ts +23 -0
  57. package/dist/types/modes/interactive-mode.d.ts +7 -4
  58. package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
  59. package/dist/types/modes/orchestrate.d.ts +10 -0
  60. package/dist/types/modes/setup-wizard/index.d.ts +16 -0
  61. package/dist/types/modes/setup-wizard/scenes/glyph.d.ts +2 -0
  62. package/dist/types/modes/setup-wizard/scenes/outro.d.ts +2 -0
  63. package/dist/types/modes/setup-wizard/scenes/providers.d.ts +2 -0
  64. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +19 -0
  65. package/dist/types/modes/setup-wizard/scenes/splash.d.ts +11 -0
  66. package/dist/types/modes/setup-wizard/scenes/theme.d.ts +2 -0
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +43 -0
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +19 -0
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +14 -0
  70. package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
  71. package/dist/types/modes/theme/shimmer.d.ts +2 -0
  72. package/dist/types/modes/theme/theme.d.ts +11 -0
  73. package/dist/types/modes/types.d.ts +5 -1
  74. package/dist/types/modes/ultrathink.d.ts +3 -3
  75. package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
  76. package/dist/types/sdk.d.ts +3 -0
  77. package/dist/types/session/agent-session.d.ts +33 -0
  78. package/dist/types/system-prompt.d.ts +2 -0
  79. package/dist/types/task/executor.d.ts +2 -0
  80. package/dist/types/task/render.d.ts +5 -1
  81. package/dist/types/tiny/device.d.ts +78 -0
  82. package/dist/types/tiny/dtype.d.ts +85 -0
  83. package/dist/types/tiny/models.d.ts +185 -0
  84. package/dist/types/tiny/text.d.ts +19 -0
  85. package/dist/types/tiny/title-client.d.ts +32 -0
  86. package/dist/types/tiny/title-protocol.d.ts +74 -0
  87. package/dist/types/tiny/worker.d.ts +2 -0
  88. package/dist/types/tools/bash.d.ts +3 -2
  89. package/dist/types/tools/eval.d.ts +1 -1
  90. package/dist/types/tools/index.d.ts +7 -4
  91. package/dist/types/tools/memory-edit.d.ts +40 -0
  92. package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
  93. package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
  94. package/dist/types/tools/memory-render.d.ts +60 -0
  95. package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
  96. package/dist/types/tools/todo-write.d.ts +8 -0
  97. package/dist/types/tools/tool-result.d.ts +2 -0
  98. package/dist/types/tui/code-cell.d.ts +2 -0
  99. package/dist/types/tui/output-block.d.ts +17 -0
  100. package/dist/types/utils/title-generator.d.ts +3 -0
  101. package/package.json +18 -14
  102. package/scripts/build-binary.ts +1 -0
  103. package/src/capability/rule-buckets.ts +64 -0
  104. package/src/capability/rule.ts +8 -0
  105. package/src/cli/completion-gen.ts +550 -0
  106. package/src/cli/setup-cli.ts +5 -3
  107. package/src/cli/tiny-models-cli.ts +127 -0
  108. package/src/cli-commands.ts +3 -0
  109. package/src/cli.ts +9 -15
  110. package/src/commands/complete.ts +66 -0
  111. package/src/commands/completions.ts +60 -0
  112. package/src/commands/setup.ts +29 -4
  113. package/src/commands/tiny-models.ts +36 -0
  114. package/src/config/model-equivalence.ts +43 -2
  115. package/src/config/model-id-affixes.ts +64 -0
  116. package/src/config/model-registry.ts +84 -10
  117. package/src/config/settings-schema.ts +275 -15
  118. package/src/discovery/builtin-defaults.ts +39 -0
  119. package/src/discovery/builtin-rules/index.ts +48 -0
  120. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  121. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  122. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  123. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  124. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  125. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  126. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  127. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  128. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  129. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  130. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  131. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +50 -0
  132. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  133. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  134. package/src/discovery/index.ts +1 -0
  135. package/src/edit/hashline/block-resolver.ts +14 -0
  136. package/src/edit/hashline/diff.ts +9 -8
  137. package/src/edit/hashline/execute.ts +2 -1
  138. package/src/edit/hashline/index.ts +1 -0
  139. package/src/eval/__tests__/shared-executors.test.ts +36 -0
  140. package/src/eval/js/shared/local-module-loader.ts +13 -1
  141. package/src/eval/js/shared/rewrite-imports.ts +31 -26
  142. package/src/eval/py/kernel.ts +37 -15
  143. package/src/eval/py/runtime.ts +57 -28
  144. package/src/export/html/template.generated.ts +1 -1
  145. package/src/export/html/template.js +0 -12
  146. package/src/export/ttsr.ts +2 -0
  147. package/src/internal-urls/agent-protocol.ts +18 -1
  148. package/src/internal-urls/artifact-protocol.ts +19 -1
  149. package/src/internal-urls/docs-index.generated.ts +8 -7
  150. package/src/internal-urls/local-protocol.ts +14 -1
  151. package/src/internal-urls/memory-protocol.ts +6 -1
  152. package/src/internal-urls/omp-protocol.ts +5 -1
  153. package/src/internal-urls/router.ts +20 -1
  154. package/src/internal-urls/rule-protocol.ts +8 -1
  155. package/src/internal-urls/skill-protocol.ts +8 -1
  156. package/src/internal-urls/types.ts +27 -0
  157. package/src/lsp/render.ts +1 -1
  158. package/src/main.ts +18 -1
  159. package/src/mcp/oauth-flow.ts +2 -2
  160. package/src/memory-backend/index.ts +1 -0
  161. package/src/memory-backend/resolve.ts +4 -1
  162. package/src/memory-backend/types.ts +8 -1
  163. package/src/mnemosyne/backend.ts +374 -0
  164. package/src/mnemosyne/config.ts +160 -0
  165. package/src/mnemosyne/index.ts +3 -0
  166. package/src/mnemosyne/state.ts +548 -0
  167. package/src/modes/acp/acp-agent.ts +11 -6
  168. package/src/modes/components/agent-dashboard.ts +4 -4
  169. package/src/modes/components/custom-editor.ts +3 -2
  170. package/src/modes/components/diff.ts +2 -2
  171. package/src/modes/components/extensions/extension-list.ts +3 -2
  172. package/src/modes/components/footer.ts +5 -6
  173. package/src/modes/components/history-search.ts +3 -3
  174. package/src/modes/components/hook-selector.ts +92 -8
  175. package/src/modes/components/index.ts +2 -0
  176. package/src/modes/components/mcp-add-wizard.ts +3 -3
  177. package/src/modes/components/model-selector.ts +5 -4
  178. package/src/modes/components/oauth-selector.ts +3 -3
  179. package/src/modes/components/segment-track.ts +52 -0
  180. package/src/modes/components/session-observer-overlay.ts +19 -13
  181. package/src/modes/components/session-selector.ts +3 -3
  182. package/src/modes/components/settings-defs.ts +7 -0
  183. package/src/modes/components/status-line/context-thresholds.ts +11 -0
  184. package/src/modes/components/status-line/segments.ts +2 -2
  185. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  186. package/src/modes/components/tips.txt +13 -0
  187. package/src/modes/components/tool-execution.ts +72 -4
  188. package/src/modes/components/tree-selector.ts +3 -3
  189. package/src/modes/components/user-message-selector.ts +3 -3
  190. package/src/modes/components/welcome.ts +102 -43
  191. package/src/modes/controllers/command-controller.ts +16 -1
  192. package/src/modes/controllers/extension-ui-controller.ts +3 -1
  193. package/src/modes/controllers/input-controller.ts +69 -21
  194. package/src/modes/gradient-highlight.ts +70 -0
  195. package/src/modes/interactive-mode.ts +75 -114
  196. package/src/modes/internal-url-autocomplete.ts +143 -0
  197. package/src/modes/orchestrate.ts +36 -0
  198. package/src/modes/prompt-action-autocomplete.ts +12 -0
  199. package/src/modes/setup-wizard/index.ts +88 -0
  200. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  201. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  202. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  203. package/src/modes/setup-wizard/scenes/sign-in.ts +193 -0
  204. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  205. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  206. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  207. package/src/modes/setup-wizard/scenes/web-search.ts +128 -0
  208. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  209. package/src/modes/theme/shimmer.ts +5 -0
  210. package/src/modes/theme/theme.ts +44 -20
  211. package/src/modes/types.ts +6 -1
  212. package/src/modes/ultrathink.ts +9 -53
  213. package/src/modes/utils/keybinding-matchers.ts +11 -0
  214. package/src/prompts/system/memory-consolidation-system.md +8 -0
  215. package/src/prompts/system/memory-extraction-system.md +26 -0
  216. package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +6 -17
  217. package/src/prompts/system/system-prompt.md +2 -0
  218. package/src/prompts/system/tiny-title-system.md +8 -0
  219. package/src/prompts/tools/memory-edit.md +8 -0
  220. package/src/prompts/tools/read.md +4 -0
  221. package/src/prompts/tools/task.md +4 -7
  222. package/src/sdk.ts +13 -21
  223. package/src/session/agent-session.ts +128 -44
  224. package/src/slash-commands/builtin-registry.ts +18 -1
  225. package/src/system-prompt.ts +4 -0
  226. package/src/task/commands.ts +1 -5
  227. package/src/task/executor.ts +8 -0
  228. package/src/task/index.ts +2 -0
  229. package/src/task/render.ts +69 -26
  230. package/src/tiny/device.ts +117 -0
  231. package/src/tiny/dtype.ts +101 -0
  232. package/src/tiny/models.ts +218 -0
  233. package/src/tiny/text.ts +54 -0
  234. package/src/tiny/title-client.ts +395 -0
  235. package/src/tiny/title-protocol.ts +51 -0
  236. package/src/tiny/worker.ts +587 -0
  237. package/src/tools/bash.ts +74 -29
  238. package/src/tools/browser/tab-worker.ts +1 -1
  239. package/src/tools/eval.ts +9 -4
  240. package/src/tools/index.ts +17 -22
  241. package/src/tools/memory-edit.ts +59 -0
  242. package/src/tools/memory-recall.ts +100 -0
  243. package/src/tools/memory-reflect.ts +88 -0
  244. package/src/tools/memory-render.ts +185 -0
  245. package/src/tools/memory-retain.ts +91 -0
  246. package/src/tools/read.ts +1 -0
  247. package/src/tools/renderers.ts +4 -2
  248. package/src/tools/todo-write.ts +128 -29
  249. package/src/tools/tool-result.ts +8 -0
  250. package/src/tui/code-cell.ts +6 -1
  251. package/src/tui/output-block.ts +199 -38
  252. package/src/utils/title-generator.ts +115 -13
  253. package/dist/types/tools/recipe/index.d.ts +0 -46
  254. package/dist/types/tools/recipe/render.d.ts +0 -36
  255. package/dist/types/tools/recipe/runner.d.ts +0 -60
  256. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  257. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  258. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  259. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  260. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  261. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  262. package/src/prompts/tools/recipe.md +0 -16
  263. package/src/tools/hindsight-recall.ts +0 -69
  264. package/src/tools/hindsight-reflect.ts +0 -58
  265. package/src/tools/hindsight-retain.ts +0 -57
  266. package/src/tools/recipe/index.ts +0 -81
  267. package/src/tools/recipe/render.ts +0 -19
  268. package/src/tools/recipe/runner.ts +0 -219
  269. package/src/tools/recipe/runners/cargo.ts +0 -131
  270. package/src/tools/recipe/runners/index.ts +0 -8
  271. package/src/tools/recipe/runners/just.ts +0 -73
  272. package/src/tools/recipe/runners/make.ts +0 -101
  273. package/src/tools/recipe/runners/pkg.ts +0 -167
  274. package/src/tools/recipe/runners/task.ts +0 -72
@@ -35,6 +35,7 @@ import {
35
35
  import { APP_NAME, adjustHsv, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@oh-my-pi/pi-utils";
36
36
  import chalk from "chalk";
37
37
  import { KeybindingsManager } from "../config/keybindings";
38
+ import { MODEL_ROLES, type ModelRole } from "../config/model-registry";
38
39
  import { isSettingsInitialized, Settings, settings } from "../config/settings";
39
40
  import type {
40
41
  ExtensionUIContext,
@@ -57,7 +58,7 @@ import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" wit
57
58
  import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
58
59
  type: "text",
59
60
  };
60
- import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
61
+ import type { AgentSession, AgentSessionEvent, ResolvedRoleModel } from "../session/agent-session";
61
62
  import { HistoryStorage } from "../session/history-storage";
62
63
  import type { SessionContext, SessionManager } from "../session/session-manager";
63
64
  import { getRecentSessions } from "../session/session-manager";
@@ -80,7 +81,7 @@ import { DynamicBorder } from "./components/dynamic-border";
80
81
  import type { EvalExecutionComponent } from "./components/eval-execution";
81
82
  import type { HookEditorComponent } from "./components/hook-editor";
82
83
  import type { HookInputComponent } from "./components/hook-input";
83
- import type { HookSelectorComponent } from "./components/hook-selector";
84
+ import type { HookSelectorComponent, HookSelectorSlider } from "./components/hook-selector";
84
85
  import { StatusLineComponent } from "./components/status-line";
85
86
  import type { ToolExecutionHandle } from "./components/tool-execution";
86
87
  import { WelcomeComponent, type LspServerInfo as WelcomeLspServerInfo } from "./components/welcome";
@@ -115,7 +116,14 @@ import {
115
116
  onThemeChange,
116
117
  theme,
117
118
  } from "./theme/theme";
118
- import type { CompactionQueuedMessage, InteractiveModeContext, SubmittedUserInput, TodoItem, TodoPhase } from "./types";
119
+ import type {
120
+ CompactionQueuedMessage,
121
+ InteractiveModeContext,
122
+ InteractiveModeInitOptions,
123
+ SubmittedUserInput,
124
+ TodoItem,
125
+ TodoPhase,
126
+ } from "./types";
119
127
  import { UiHelpers } from "./utils/ui-helpers";
120
128
 
121
129
  const HINT_SHIMMER_PALETTE: ShimmerPalette = {
@@ -324,8 +332,6 @@ export class InteractiveMode implements InteractiveModeContext {
324
332
  #eventBus?: EventBus;
325
333
  #eventBusUnsubscribers: Array<() => void> = [];
326
334
  #welcomeComponent?: WelcomeComponent;
327
- #todoClosingTimeout?: NodeJS.Timeout;
328
- #todoClosingState: "idle" | "playing" | "done" = "idle";
329
335
 
330
336
  constructor(
331
337
  session: AgentSession,
@@ -369,7 +375,7 @@ export class InteractiveMode implements InteractiveModeContext {
369
375
  this.ui.requestRender(true);
370
376
  };
371
377
  this.editor.onAutocompleteUpdate = () => {
372
- this.ui.requestRender();
378
+ this.ui.requestRender(false, { allowUnknownViewportMutation: true });
373
379
  };
374
380
  this.#syncEditorMaxHeight();
375
381
  this.#resizeHandler = () => {
@@ -432,7 +438,10 @@ export class InteractiveMode implements InteractiveModeContext {
432
438
  this.#observerRegistry = new SessionObserverRegistry();
433
439
  }
434
440
 
435
- async init(): Promise<void> {
441
+ playWelcomeIntro(): void {
442
+ this.#welcomeComponent?.playIntro(() => this.ui.requestRender());
443
+ }
444
+ async init(options: InteractiveModeInitOptions = {}): Promise<void> {
436
445
  if (this.isInitialized) return;
437
446
 
438
447
  this.keybindings = logger.time("InteractiveMode.init:keybindings", () => KeybindingsManager.create());
@@ -490,7 +499,9 @@ export class InteractiveMode implements InteractiveModeContext {
490
499
  this.ui.addChild(new Spacer(1));
491
500
  this.ui.addChild(this.#welcomeComponent);
492
501
  this.ui.addChild(new Spacer(1));
493
- this.#welcomeComponent.playIntro(() => this.ui.requestRender());
502
+ if (!options.suppressWelcomeIntro) {
503
+ this.playWelcomeIntro();
504
+ }
494
505
 
495
506
  // Add changelog if provided
496
507
  if (this.#changelogMarkdown) {
@@ -1035,28 +1046,7 @@ export class InteractiveMode implements InteractiveModeContext {
1035
1046
  #renderTodoList(): void {
1036
1047
  this.todoContainer.clear();
1037
1048
  const phases = this.todoPhases.filter(phase => phase.tasks.length > 0);
1038
- if (phases.length === 0) {
1039
- this.#stopTodoClosingAnimation();
1040
- this.#todoClosingState = "idle";
1041
- return;
1042
- }
1043
-
1044
- // When every visible task is completed or abandoned, fold the panel
1045
- // away with a brief celebratory animation (see
1046
- // #startTodoClosingAnimation). State machine guards against replaying
1047
- // on every re-render once the animation has finished.
1048
- const allClosed = phases.every(phase =>
1049
- phase.tasks.every(t => t.status === "completed" || t.status === "abandoned"),
1050
- );
1051
- if (allClosed) {
1052
- if (this.#todoClosingState === "done") return;
1053
- if (this.#todoClosingState === "idle") this.#startTodoClosingAnimation(phases);
1054
- return;
1055
- }
1056
- // Any open task here means the close animation is no longer applicable.
1057
- this.#stopTodoClosingAnimation();
1058
- this.#todoClosingState = "idle";
1059
-
1049
+ if (phases.length === 0) return;
1060
1050
  const indent = " ";
1061
1051
  const hook = theme.tree.hook;
1062
1052
  const lines = ["", indent + theme.bold(theme.fg("accent", "Todos"))];
@@ -1098,87 +1088,6 @@ export class InteractiveMode implements InteractiveModeContext {
1098
1088
  this.todoContainer.addChild(new Text(lines.join("\n"), 1, 0));
1099
1089
  }
1100
1090
 
1101
- /**
1102
- * Play a short "all done" close animation: a celebratory bright frame,
1103
- * a brief dim transition, then a row-by-row vertical collapse until the
1104
- * panel is empty. Triggered from #renderTodoList exactly once per
1105
- * open-to-all-closed transition; #todoClosingState gates re-entry.
1106
- *
1107
- * While playing, the animator owns the panel container; #renderTodoList
1108
- * returns early. Subsequent renders with state === "done" keep the
1109
- * panel hidden until a fresh open task flips state back to "idle".
1110
- */
1111
- #startTodoClosingAnimation(phases: TodoPhase[]): void {
1112
- this.#stopTodoClosingAnimation();
1113
- this.#todoClosingState = "playing";
1114
-
1115
- const indent = " ";
1116
- const hook = theme.tree.hook;
1117
- const snapshot: string[] = ["", `${indent}Todos ${theme.status.success}`];
1118
- for (let i = 0; i < phases.length; i++) {
1119
- const phase = phases[i];
1120
- snapshot.push(`${indent}${hook} ${formatPhaseDisplayName(phase.name, i + 1)}`);
1121
- for (let j = 0; j < phase.tasks.length; j++) {
1122
- const task = phase.tasks[j];
1123
- const mark = task.status === "abandoned" ? theme.status.aborted : theme.status.success;
1124
- const prefix = `${indent}${j === 0 ? hook : " "} `;
1125
- snapshot.push(`${prefix}${mark} ${task.content}`);
1126
- }
1127
- }
1128
-
1129
- // Frame schedule (tint, drop-from-bottom, hold-ms). Frame 0 holds long
1130
- // enough for the user to actually read the final checkmarks before the
1131
- // fade starts; later frames fade and progressively drop rows from the
1132
- // bottom for the collapse effect. Total runtime ≈ 1.4s.
1133
- const frames = [
1134
- { tint: "success" as const, drop: 0, holdMs: 900 },
1135
- { tint: "success" as const, drop: 0, holdMs: 150 },
1136
- { tint: "muted" as const, drop: 1, holdMs: 90 },
1137
- { tint: "muted" as const, drop: 2, holdMs: 90 },
1138
- { tint: "dim" as const, drop: 3, holdMs: 80 },
1139
- { tint: "dim" as const, drop: 4, holdMs: 80 },
1140
- ];
1141
-
1142
- let frameIdx = 0;
1143
- const tick = (): void => {
1144
- if (this.#todoClosingState !== "playing") return;
1145
- if (frameIdx >= frames.length) {
1146
- this.todoContainer.clear();
1147
- this.#stopTodoClosingAnimation();
1148
- this.#todoClosingState = "done";
1149
- this.ui.requestRender();
1150
- return;
1151
- }
1152
- const { tint, drop, holdMs } = frames[frameIdx];
1153
- const visibleCount = Math.max(0, snapshot.length - drop);
1154
- this.todoContainer.clear();
1155
- if (visibleCount > 0) {
1156
- const visible = snapshot.slice(0, visibleCount);
1157
- const painted = visible.map((line, idx) => {
1158
- if (idx === 1) {
1159
- // Header row gets a bold flourish on the opening tick.
1160
- const colored = theme.fg(tint, line);
1161
- return frameIdx === 0 ? theme.bold(colored) : colored;
1162
- }
1163
- return theme.fg(tint, line);
1164
- });
1165
- this.todoContainer.addChild(new Text(painted.join("\n"), 1, 0));
1166
- }
1167
- this.ui.requestRender();
1168
- frameIdx++;
1169
- this.#todoClosingTimeout = setTimeout(tick, holdMs);
1170
- };
1171
-
1172
- tick();
1173
- }
1174
-
1175
- #stopTodoClosingAnimation(): void {
1176
- if (this.#todoClosingTimeout) {
1177
- clearTimeout(this.#todoClosingTimeout);
1178
- this.#todoClosingTimeout = undefined;
1179
- }
1180
- }
1181
-
1182
1091
  async #loadTodoList(): Promise<void> {
1183
1092
  this.todoPhases = this.session.getTodoPhases();
1184
1093
  this.#renderTodoList();
@@ -1702,6 +1611,20 @@ export class InteractiveMode implements InteractiveModeContext {
1702
1611
  }
1703
1612
  }
1704
1613
 
1614
+ async #applyPlanExecutionModel(entry: ResolvedRoleModel | undefined): Promise<void> {
1615
+ if (!entry) return;
1616
+ try {
1617
+ await this.session.applyRoleModel(entry);
1618
+ this.statusLine.invalidate();
1619
+ this.updateEditorBorderColor();
1620
+ this.showStatus(`Continuing with ${entry.role}: ${entry.model.name || entry.model.id}`);
1621
+ } catch (error) {
1622
+ this.showWarning(
1623
+ `Could not switch to the ${entry.role} model: ${error instanceof Error ? error.message : String(error)}`,
1624
+ );
1625
+ }
1626
+ }
1627
+
1705
1628
  async #approvePlan(
1706
1629
  planContent: string,
1707
1630
  options: {
@@ -1710,6 +1633,7 @@ export class InteractiveMode implements InteractiveModeContext {
1710
1633
  title: string;
1711
1634
  preserveContext?: boolean;
1712
1635
  compactBeforeExecute?: boolean;
1636
+ executionModel?: ResolvedRoleModel;
1713
1637
  },
1714
1638
  ): Promise<void> {
1715
1639
  await renameApprovedPlanFile({
@@ -1791,6 +1715,8 @@ export class InteractiveMode implements InteractiveModeContext {
1791
1715
  return;
1792
1716
  }
1793
1717
 
1718
+ await this.#applyPlanExecutionModel(options.executionModel);
1719
+
1794
1720
  // Approved plans land in a fresh (or compacted) session whose first user-visible
1795
1721
  // turn is the synthetic plan-approved prompt — that path bypasses the
1796
1722
  // input-controller's title generation. Seed an auto-name from the plan title
@@ -2106,13 +2032,38 @@ export class InteractiveMode implements InteractiveModeContext {
2106
2032
  contextUsage?.percent != null
2107
2033
  ? `Approve and keep context (${contextUsage.percent.toFixed(1)}%)`
2108
2034
  : "Approve and keep context";
2035
+
2036
+ // Model-tier slider: let the operator pick which configured role model
2037
+ // (smol/default/slow/…) executes the approved plan. Left/right move it from
2038
+ // any list position. Hidden when fewer than two role models resolve — a lone
2039
+ // tier is no choice. `selectedTierIndex` tracks the live slider position.
2040
+ const cycle = this.session.getRoleModelCycle(this.session.settings.get("cycleOrder"));
2041
+ let selectedTierIndex = cycle?.currentIndex ?? 0;
2042
+ const slider: HookSelectorSlider | undefined =
2043
+ cycle && cycle.models.length > 1
2044
+ ? {
2045
+ caption: "continue with",
2046
+ index: cycle.currentIndex,
2047
+ segments: cycle.models.map(entry => ({
2048
+ label: entry.role,
2049
+ color: MODEL_ROLES[entry.role as ModelRole]?.color,
2050
+ detail: entry.model.name || entry.model.id,
2051
+ })),
2052
+ onChange: index => {
2053
+ selectedTierIndex = index;
2054
+ },
2055
+ }
2056
+ : undefined;
2057
+ const helpText = slider ? `${this.#getPlanReviewHelpText()} ◂/▸ model` : this.#getPlanReviewHelpText();
2058
+
2109
2059
  const choice = await this.showHookSelector(
2110
2060
  "Plan mode - next step",
2111
2061
  ["Approve and execute", "Approve and compact context", keepContextLabel, "Refine plan"],
2112
2062
  {
2113
- helpText: this.#getPlanReviewHelpText(),
2063
+ helpText,
2114
2064
  onExternalEditor: () => void this.#openPlanInExternalEditor(planFilePath),
2115
2065
  },
2066
+ { slider },
2116
2067
  );
2117
2068
 
2118
2069
  if (choice === "Approve and execute" || choice === "Approve and compact context" || choice === keepContextLabel) {
@@ -2123,12 +2074,21 @@ export class InteractiveMode implements InteractiveModeContext {
2123
2074
  this.showError(`Plan file not found at ${planFilePath}`);
2124
2075
  return;
2125
2076
  }
2077
+ // Capture the operator's tier choice and hand it to #approvePlan, which
2078
+ // applies it AFTER #exitPlanMode. #exitPlanMode restores
2079
+ // #planModePreviousModelState (the model from before plan mode), so
2080
+ // applying the slider choice any earlier would be silently reverted —
2081
+ // the bug that made "continue with slow" keep executing on the default
2082
+ // model. Deferred application also survives newSession()/compaction.
2083
+ const executionModel =
2084
+ cycle && selectedTierIndex !== cycle.currentIndex ? cycle.models[selectedTierIndex] : undefined;
2126
2085
  await this.#approvePlan(latestPlanContent, {
2127
2086
  planFilePath,
2128
2087
  finalPlanFilePath,
2129
2088
  title: details.title,
2130
2089
  preserveContext: choice !== "Approve and execute",
2131
2090
  compactBeforeExecute: choice === "Approve and compact context",
2091
+ executionModel,
2132
2092
  });
2133
2093
  } catch (error) {
2134
2094
  this.showError(
@@ -2311,7 +2271,7 @@ export class InteractiveMode implements InteractiveModeContext {
2311
2271
  this.ui.requestRender(true);
2312
2272
  };
2313
2273
  nextEditor.onAutocompleteUpdate = () => {
2314
- this.ui.requestRender();
2274
+ this.ui.requestRender(false, { allowUnknownViewportMutation: true });
2315
2275
  };
2316
2276
  nextEditor.setMaxHeight(this.#computeEditorMaxHeight());
2317
2277
  if (this.historyStorage) {
@@ -2904,8 +2864,9 @@ export class InteractiveMode implements InteractiveModeContext {
2904
2864
  title: string,
2905
2865
  options: string[],
2906
2866
  dialogOptions?: ExtensionUIDialogOptions,
2867
+ extra?: { slider?: HookSelectorSlider },
2907
2868
  ): Promise<string | undefined> {
2908
- return this.#extensionUiController.showHookSelector(title, options, dialogOptions);
2869
+ return this.#extensionUiController.showHookSelector(title, options, dialogOptions, extra);
2909
2870
  }
2910
2871
 
2911
2872
  hideHookSelector(): void {
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Autocomplete for internal-url schemes (skill://, rule://, omp://, local://,
3
+ * memory://, agent://, artifact://) while composing a prompt.
4
+ *
5
+ * Detection here MUST stay in sync with the generic URL-scheme trigger in the
6
+ * TUI editor (`packages/tui/src/components/editor.ts`); the editor fires the
7
+ * popup, this module decides whether there are candidates to show.
8
+ */
9
+ import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
10
+ import { InternalUrlRouter } from "../internal-urls/router";
11
+
12
+ /** Upper bound on candidates surfaced in the dropdown. */
13
+ const MAX_URL_SUGGESTIONS = 25;
14
+
15
+ /**
16
+ * A URL token ending at the cursor: a known internal scheme followed by one or
17
+ * two slashes and the partially typed host/path. The boundary/rest character
18
+ * classes mirror the editor trigger so both agree on what counts as a token.
19
+ */
20
+ const URL_TOKEN_RE = /(?:^|[\s"'`(<=])([a-z][a-z0-9+.-]*:\/{1,2}[^\s"'`()<>]*)$/i;
21
+ const SCHEME_SPLIT_RE = /^([a-z][a-z0-9+.-]*):\/{1,2}(.*)$/i;
22
+
23
+ export interface InternalUrlContext {
24
+ /** Lowercased scheme (e.g. `local`). */
25
+ scheme: string;
26
+ /** Text typed after the slashes so far (host + path); may be empty. */
27
+ query: string;
28
+ /** Exact buffer token from its boundary to the cursor (the completion prefix). */
29
+ token: string;
30
+ }
31
+
32
+ // Subsequence fuzzy match: `hum` matches `humanizer`, `lp` matches `local-plan`.
33
+ function fuzzyMatch(query: string, target: string): boolean {
34
+ if (query.length === 0) return true;
35
+ if (query.length > target.length) return false;
36
+ let q = 0;
37
+ for (let t = 0; t < target.length && q < query.length; t += 1) {
38
+ if (query[q] === target[t]) q += 1;
39
+ }
40
+ return q === query.length;
41
+ }
42
+
43
+ // Higher is better: exact > prefix > substring > scattered subsequence.
44
+ function fuzzyScore(query: string, target: string): number {
45
+ if (query.length === 0) return 1;
46
+ if (target === query) return 100;
47
+ if (target.startsWith(query)) return 80;
48
+ if (target.includes(query)) return 60;
49
+ let q = 0;
50
+ let gaps = 0;
51
+ let last = -1;
52
+ for (let t = 0; t < target.length && q < query.length; t += 1) {
53
+ if (query[q] === target[t]) {
54
+ if (last >= 0 && t - last > 1) gaps += 1;
55
+ last = t;
56
+ q += 1;
57
+ }
58
+ }
59
+ if (q !== query.length) return 0;
60
+ return Math.max(1, 40 - gaps * 5);
61
+ }
62
+
63
+ /**
64
+ * Detect a completable internal-url token immediately before the cursor.
65
+ * Returns `null` when the text is not a `scheme://` token whose scheme is
66
+ * registered with a completion-capable handler.
67
+ */
68
+ export function extractInternalUrlContext(textBeforeCursor: string): InternalUrlContext | null {
69
+ const tokenMatch = URL_TOKEN_RE.exec(textBeforeCursor);
70
+ if (!tokenMatch) return null;
71
+ const token = tokenMatch[1]!;
72
+ const parts = SCHEME_SPLIT_RE.exec(token);
73
+ if (!parts) return null;
74
+ const scheme = parts[1]!.toLowerCase();
75
+ if (!InternalUrlRouter.instance().completionSchemes().includes(scheme)) return null;
76
+ return { scheme, query: parts[2] ?? "", token };
77
+ }
78
+
79
+ /**
80
+ * Suggestions for the internal-url token ending at the cursor, or `null` when
81
+ * the text is not such a token or no candidate matches the typed query.
82
+ */
83
+ export async function getInternalUrlSuggestions(
84
+ textBeforeCursor: string,
85
+ ): Promise<{ items: AutocompleteItem[]; prefix: string } | null> {
86
+ const ctx = extractInternalUrlContext(textBeforeCursor);
87
+ if (!ctx) return null;
88
+
89
+ const candidates = await InternalUrlRouter.instance().complete(ctx.scheme, ctx.query);
90
+ if (!candidates || candidates.length === 0) return null;
91
+
92
+ const query = ctx.query.toLowerCase();
93
+ const scored: Array<{ item: AutocompleteItem; score: number }> = [];
94
+ for (const candidate of candidates) {
95
+ const target = candidate.value.toLowerCase();
96
+ if (!fuzzyMatch(query, target)) continue;
97
+ scored.push({
98
+ item: {
99
+ value: `${ctx.scheme}://${candidate.value}`,
100
+ label: candidate.label ?? candidate.value,
101
+ ...(candidate.description ? { description: candidate.description } : {}),
102
+ },
103
+ score: fuzzyScore(query, target),
104
+ });
105
+ }
106
+ if (scored.length === 0) return null;
107
+
108
+ scored.sort((a, b) => b.score - a.score);
109
+ return {
110
+ items: scored.slice(0, MAX_URL_SUGGESTIONS).map(entry => entry.item),
111
+ prefix: ctx.token,
112
+ };
113
+ }
114
+
115
+ /** Whether `prefix` (the token a completion was offered for) is an internal-url token. */
116
+ export function isInternalUrlPrefix(prefix: string): boolean {
117
+ return extractInternalUrlContext(prefix) !== null;
118
+ }
119
+
120
+ /**
121
+ * Replace the internal-url token with the selected candidate, appending a
122
+ * trailing space (matching `@` file-reference behavior) so the user can keep
123
+ * typing.
124
+ */
125
+ export function applyInternalUrlCompletion(
126
+ lines: string[],
127
+ cursorLine: number,
128
+ cursorCol: number,
129
+ item: AutocompleteItem,
130
+ prefix: string,
131
+ ): { lines: string[]; cursorLine: number; cursorCol: number } {
132
+ const currentLine = lines[cursorLine] || "";
133
+ const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
134
+ const afterCursor = currentLine.slice(cursorCol);
135
+ const insert = `${item.value} `;
136
+ const newLines = [...lines];
137
+ newLines[cursorLine] = beforePrefix + insert + afterCursor;
138
+ return {
139
+ lines: newLines,
140
+ cursorLine,
141
+ cursorCol: beforePrefix.length + insert.length,
142
+ };
143
+ }
@@ -0,0 +1,36 @@
1
+ import orchestrateNotice from "../prompts/system/orchestrate-notice.md" with { type: "text" };
2
+ import { createGradientHighlighter } from "./gradient-highlight";
3
+
4
+ /**
5
+ * "orchestrate" keyword support.
6
+ *
7
+ * Typing the standalone word in the input editor paints it with a cool
8
+ * teal→violet gradient ({@link highlightOrchestrate}); submitting a message that
9
+ * mentions it appends a hidden {@link ORCHESTRATE_NOTICE} that switches the model
10
+ * into multi-agent orchestration mode. Matching is word-bounded and
11
+ * case-insensitive, so "orchestrated"/"orchestrating" never trigger either
12
+ * behavior. Replaces the former `/orchestrate` slash command.
13
+ */
14
+
15
+ // Detection: standalone keyword, any case. Non-global so `.test` stays stateless.
16
+ const ORCHESTRATE_WORD = /\borchestrate\b/i;
17
+
18
+ /** Hidden system notice appended after a user message that mentions "orchestrate". */
19
+ export const ORCHESTRATE_NOTICE: string = orchestrateNotice.trim();
20
+
21
+ /** Whether `text` contains the standalone keyword "orchestrate" (any case). */
22
+ export function containsOrchestrate(text: string): boolean {
23
+ return ORCHESTRATE_WORD.test(text);
24
+ }
25
+
26
+ /**
27
+ * Highlight every standalone "orchestrate" in `text` for editor display with a
28
+ * cool teal→violet gradient (hue 150..280), visually distinct from ultrathink's
29
+ * full-spectrum rainbow.
30
+ */
31
+ export const highlightOrchestrate: (text: string) => string = createGradientHighlighter({
32
+ probe: /orchestrate/i,
33
+ highlight: /\borchestrate\b/gi,
34
+ stops: 14,
35
+ hue: t => 150 + t * 130,
36
+ });
@@ -8,6 +8,11 @@ import {
8
8
  import { formatKeyHints, type KeybindingsManager } from "../config/keybindings";
9
9
  import { isSettingsInitialized, settings } from "../config/settings";
10
10
  import { applyEmojiCompletion, getEmojiSuggestions, isEmojiPrefix, tryEmojiInlineReplace } from "./emoji-autocomplete";
11
+ import {
12
+ applyInternalUrlCompletion,
13
+ getInternalUrlSuggestions,
14
+ isInternalUrlPrefix,
15
+ } from "./internal-url-autocomplete";
11
16
 
12
17
  interface PromptActionDefinition {
13
18
  id: string;
@@ -128,6 +133,9 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
128
133
  }
129
134
  }
130
135
 
136
+ const urlSuggestions = await getInternalUrlSuggestions(textBeforeCursor);
137
+ if (urlSuggestions) return urlSuggestions;
138
+
131
139
  if (!isSettingsInitialized() || settings.get("emojiAutocomplete")) {
132
140
  const emojiSuggestions = getEmojiSuggestions(textBeforeCursor);
133
141
  if (emojiSuggestions) return emojiSuggestions;
@@ -170,6 +178,10 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
170
178
  };
171
179
  }
172
180
 
181
+ if (isInternalUrlPrefix(prefix)) {
182
+ return applyInternalUrlCompletion(lines, cursorLine, cursorCol, item, prefix);
183
+ }
184
+
173
185
  if (isEmojiPrefix(prefix)) {
174
186
  return applyEmojiCompletion(lines, cursorLine, cursorCol, item, prefix);
175
187
  }
@@ -0,0 +1,88 @@
1
+ import type { Settings } from "../../config/settings";
2
+ import type { InteractiveModeContext } from "../types";
3
+ import { glyphSetupScene } from "./scenes/glyph";
4
+ import { providersSetupScene } from "./scenes/providers";
5
+ import { themeSetupScene } from "./scenes/theme";
6
+ import type { SetupScene } from "./scenes/types";
7
+ import { SetupWizardComponent } from "./wizard-overlay";
8
+
9
+ export type { SetupScene, SetupSceneController, SetupSceneHost, SetupSceneResult } from "./scenes/types";
10
+
11
+ export const ALL_SCENES = [
12
+ providersSetupScene,
13
+ glyphSetupScene,
14
+ themeSetupScene,
15
+ ] as const satisfies readonly SetupScene[];
16
+
17
+ export const CURRENT_SETUP_VERSION = ALL_SCENES.reduce((max, scene) => Math.max(max, scene.minVersion), 0);
18
+
19
+ export interface SetupSceneSelectionOptions {
20
+ resuming?: boolean;
21
+ isTTY?: boolean;
22
+ skipEnv?: string;
23
+ setupWizardEnabled?: boolean;
24
+ force?: boolean;
25
+ }
26
+
27
+ function setupSkipEnvEnabled(value: string | undefined): boolean {
28
+ if (value === undefined) return false;
29
+ const normalized = value.trim().toLowerCase();
30
+ return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
31
+ }
32
+
33
+ export async function selectSetupScenes(
34
+ storedVersion: number,
35
+ scenes: readonly SetupScene[],
36
+ ctx?: InteractiveModeContext,
37
+ options: SetupSceneSelectionOptions = {},
38
+ ): Promise<SetupScene[]> {
39
+ const isTTY = options.isTTY ?? (process.stdin.isTTY && process.stdout.isTTY);
40
+ if (!isTTY) return [];
41
+ if (!options.force) {
42
+ if (options.resuming) return [];
43
+ if (setupSkipEnvEnabled(options.skipEnv ?? Bun.env.OMP_SKIP_SETUP)) return [];
44
+ if (options.setupWizardEnabled === false) return [];
45
+ }
46
+
47
+ const selected: SetupScene[] = [];
48
+ for (const scene of scenes) {
49
+ if (!options.force && scene.minVersion <= storedVersion) continue;
50
+ if (scene.shouldRun) {
51
+ if (!ctx) continue;
52
+ if (!(await scene.shouldRun(ctx))) continue;
53
+ }
54
+ selected.push(scene);
55
+ }
56
+ return selected;
57
+ }
58
+
59
+ export async function markSetupWizardComplete(
60
+ settings: Settings,
61
+ version: number = CURRENT_SETUP_VERSION,
62
+ ): Promise<void> {
63
+ settings.set("setupVersion", version);
64
+ await settings.flush();
65
+ }
66
+
67
+ export async function runSetupWizard(
68
+ ctx: InteractiveModeContext,
69
+ scenes: readonly SetupScene[] = ALL_SCENES,
70
+ ): Promise<void> {
71
+ if (scenes.length === 0) return;
72
+ const component = new SetupWizardComponent(ctx, scenes);
73
+ const overlay = ctx.ui.showOverlay(component, {
74
+ width: "100%",
75
+ maxHeight: "100%",
76
+ anchor: "top-left",
77
+ margin: 0,
78
+ });
79
+ try {
80
+ await component.run();
81
+ await markSetupWizardComplete(ctx.settings);
82
+ } finally {
83
+ component.dispose();
84
+ ctx.ui.setFocus(component);
85
+ overlay.hide();
86
+ }
87
+ ctx.playWelcomeIntro();
88
+ }