@oh-my-pi/pi-coding-agent 17.1.7 → 17.2.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 (246) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/dist/{CHANGELOG-5k4dq4g6.md → CHANGELOG-be1f2t8h.md} +127 -0
  3. package/dist/cli.js +9554 -12379
  4. package/dist/template-8vdv6xb4.js +1671 -0
  5. package/dist/template-f8wx9vfn.css +1355 -0
  6. package/dist/template-qat058wr.html +55 -0
  7. package/dist/tool-views.generated-jdfmzwmn.js +35 -0
  8. package/dist/types/advisor/advise-tool.d.ts +0 -7
  9. package/dist/types/advisor/runtime.d.ts +8 -11
  10. package/dist/types/advisor/transcript-recorder.d.ts +15 -0
  11. package/dist/types/async/job-manager.d.ts +11 -0
  12. package/dist/types/auto-thinking/classifier.d.ts +9 -2
  13. package/dist/types/cleanse/agent.d.ts +19 -0
  14. package/dist/types/cleanse/balance.d.ts +7 -0
  15. package/dist/types/cleanse/checkers.d.ts +13 -0
  16. package/dist/types/cleanse/index.d.ts +16 -0
  17. package/dist/types/cleanse/loop.d.ts +16 -0
  18. package/dist/types/cleanse/parsers.d.ts +13 -0
  19. package/dist/types/cleanse/progress.d.ts +14 -0
  20. package/dist/types/cleanse/types.d.ts +64 -0
  21. package/dist/types/cli/args.d.ts +2 -0
  22. package/dist/types/cli/session-picker.d.ts +14 -7
  23. package/dist/types/collab/guest.d.ts +21 -2
  24. package/dist/types/commands/cleanse.d.ts +23 -0
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/config/settings-schema.d.ts +63 -8
  27. package/dist/types/cursor-bridge-tools.d.ts +54 -0
  28. package/dist/types/cursor.d.ts +193 -8
  29. package/dist/types/edit/edit-clipboard.d.ts +19 -0
  30. package/dist/types/edit/hashline/diff.d.ts +9 -1
  31. package/dist/types/edit/index.d.ts +8 -1
  32. package/dist/types/edit/streaming.d.ts +8 -1
  33. package/dist/types/export/html/index.d.ts +2 -0
  34. package/dist/types/extensibility/extensions/runner.d.ts +19 -2
  35. package/dist/types/extensibility/extensions/types.d.ts +25 -1
  36. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
  37. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
  38. package/dist/types/internal-urls/local-protocol.d.ts +3 -2
  39. package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
  40. package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
  41. package/dist/types/launch/terminal-output-worker.d.ts +1 -0
  42. package/dist/types/lsp/client.d.ts +2 -0
  43. package/dist/types/main.d.ts +2 -0
  44. package/dist/types/mcp/manager.d.ts +33 -4
  45. package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
  46. package/dist/types/modes/components/read-tool-group.d.ts +12 -0
  47. package/dist/types/modes/components/session-selector.d.ts +6 -0
  48. package/dist/types/modes/components/status-line/component.d.ts +23 -0
  49. package/dist/types/modes/components/tool-execution.d.ts +16 -7
  50. package/dist/types/modes/components/usage-row.d.ts +2 -0
  51. package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
  52. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  53. package/dist/types/modes/interactive-mode.d.ts +8 -5
  54. package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
  55. package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
  56. package/dist/types/modes/theme/theme.d.ts +12 -1
  57. package/dist/types/modes/types.d.ts +2 -1
  58. package/dist/types/sdk.d.ts +1 -0
  59. package/dist/types/session/agent-session-events.d.ts +2 -0
  60. package/dist/types/session/agent-session-types.d.ts +36 -1
  61. package/dist/types/session/agent-session.d.ts +4 -2
  62. package/dist/types/session/async-job-delivery.d.ts +8 -0
  63. package/dist/types/session/claude-session-store.d.ts +13 -0
  64. package/dist/types/session/codex-session-store.d.ts +14 -0
  65. package/dist/types/session/credential-pin.d.ts +58 -0
  66. package/dist/types/session/foreign-session-import.d.ts +15 -0
  67. package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
  68. package/dist/types/session/foreign-session-store.d.ts +23 -0
  69. package/dist/types/session/model-controls.d.ts +3 -3
  70. package/dist/types/session/session-advisors.d.ts +47 -2
  71. package/dist/types/session/session-entries.d.ts +19 -1
  72. package/dist/types/session/session-handoff.d.ts +4 -1
  73. package/dist/types/session/session-loader.d.ts +2 -0
  74. package/dist/types/session/session-maintenance.d.ts +1 -1
  75. package/dist/types/session/session-manager.d.ts +21 -0
  76. package/dist/types/session/session-tools.d.ts +25 -1
  77. package/dist/types/session/turn-recovery.d.ts +10 -4
  78. package/dist/types/system-prompt.d.ts +2 -1
  79. package/dist/types/task/executor.d.ts +11 -2
  80. package/dist/types/task/isolation-ownership.d.ts +34 -0
  81. package/dist/types/task/render.d.ts +7 -0
  82. package/dist/types/thinking.d.ts +18 -6
  83. package/dist/types/tools/acp-bridge.d.ts +28 -5
  84. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
  85. package/dist/types/tools/browser/tab-worker.d.ts +1 -4
  86. package/dist/types/tools/grep.d.ts +17 -1
  87. package/dist/types/tools/hub/launch.d.ts +5 -1
  88. package/dist/types/tools/index.d.ts +4 -1
  89. package/dist/types/tools/path-utils.d.ts +19 -0
  90. package/dist/types/tools/read.d.ts +2 -0
  91. package/dist/types/tools/xdev.d.ts +2 -1
  92. package/dist/types/tui/output-block.d.ts +5 -5
  93. package/dist/types/utils/changelog.d.ts +19 -0
  94. package/dist/types/utils/git.d.ts +10 -0
  95. package/dist/types/utils/jj.d.ts +11 -4
  96. package/dist/types/web/search/types.d.ts +2 -2
  97. package/package.json +16 -12
  98. package/scripts/bundle-dist.ts +3 -1
  99. package/src/advisor/advise-tool.ts +0 -11
  100. package/src/advisor/runtime.ts +138 -24
  101. package/src/advisor/transcript-recorder.ts +56 -0
  102. package/src/async/job-manager.ts +30 -7
  103. package/src/auto-thinking/classifier.ts +48 -10
  104. package/src/cleanse/agent.ts +226 -0
  105. package/src/cleanse/balance.ts +79 -0
  106. package/src/cleanse/checkers.ts +996 -0
  107. package/src/cleanse/index.ts +190 -0
  108. package/src/cleanse/loop.ts +51 -0
  109. package/src/cleanse/parsers.ts +726 -0
  110. package/src/cleanse/progress.ts +50 -0
  111. package/src/cleanse/prompts/assignment.md +47 -0
  112. package/src/cleanse/types.ts +72 -0
  113. package/src/cli/args.ts +6 -0
  114. package/src/cli/flag-tables.ts +2 -0
  115. package/src/cli/gallery-fixtures/fs.ts +25 -0
  116. package/src/cli/session-picker.ts +34 -17
  117. package/src/cli/ttsr-cli.ts +19 -1
  118. package/src/cli/update-cli.ts +3 -0
  119. package/src/cli/usage-cli.ts +29 -4
  120. package/src/cli/worktree-cli.ts +28 -11
  121. package/src/cli-commands.ts +1 -0
  122. package/src/cli.ts +11 -1
  123. package/src/collab/guest.ts +37 -4
  124. package/src/collab/host.ts +1 -0
  125. package/src/commands/cleanse.ts +45 -0
  126. package/src/commands/launch.ts +6 -0
  127. package/src/config/model-registry.ts +103 -25
  128. package/src/config/settings-schema.ts +65 -6
  129. package/src/config/settings.ts +25 -0
  130. package/src/cursor-bridge-tools.ts +81 -0
  131. package/src/cursor.ts +463 -12
  132. package/src/discovery/claude-plugins.ts +144 -34
  133. package/src/edit/edit-clipboard.ts +23 -0
  134. package/src/edit/hashline/diff.ts +49 -10
  135. package/src/edit/hashline/execute.ts +38 -6
  136. package/src/edit/hashline/filesystem.ts +27 -3
  137. package/src/edit/index.ts +12 -2
  138. package/src/edit/renderer.ts +4 -4
  139. package/src/edit/streaming.ts +17 -2
  140. package/src/export/html/index.ts +17 -10
  141. package/src/export/html/template.js +1 -1
  142. package/src/extensibility/extensions/runner.ts +70 -0
  143. package/src/extensibility/extensions/types.ts +30 -0
  144. package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
  145. package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
  146. package/src/internal-urls/local-protocol.ts +3 -2
  147. package/src/internal-urls/mcp-protocol.ts +4 -1
  148. package/src/launch/broker.ts +14 -4
  149. package/src/launch/terminal-output-worker-client.ts +53 -0
  150. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  151. package/src/launch/terminal-output-worker.ts +23 -0
  152. package/src/lsp/client.ts +18 -3
  153. package/src/lsp/index.ts +4 -0
  154. package/src/lsp/render.ts +1 -1
  155. package/src/main.ts +127 -56
  156. package/src/mcp/manager.ts +120 -18
  157. package/src/modes/acp/acp-agent.ts +27 -18
  158. package/src/modes/acp/acp-event-mapper.ts +38 -4
  159. package/src/modes/components/chat-transcript-builder.ts +30 -11
  160. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  161. package/src/modes/components/read-tool-group.ts +159 -17
  162. package/src/modes/components/session-selector.ts +19 -3
  163. package/src/modes/components/settings-selector.ts +9 -1
  164. package/src/modes/components/status-line/component.ts +381 -54
  165. package/src/modes/components/tool-execution.ts +78 -31
  166. package/src/modes/components/usage-row.ts +10 -5
  167. package/src/modes/controllers/event-controller.ts +26 -4
  168. package/src/modes/controllers/mcp-command-controller.ts +68 -3
  169. package/src/modes/controllers/selector-controller.ts +94 -36
  170. package/src/modes/controllers/tan-command-controller.ts +14 -0
  171. package/src/modes/interactive-mode.ts +67 -64
  172. package/src/modes/prompt-action-autocomplete.ts +5 -3
  173. package/src/modes/rpc/rpc-client.ts +18 -0
  174. package/src/modes/rpc/rpc-mode.ts +16 -1
  175. package/src/modes/rpc/rpc-types.ts +11 -0
  176. package/src/modes/theme/theme.ts +151 -6
  177. package/src/modes/types.ts +2 -1
  178. package/src/modes/utils/ui-helpers.ts +38 -21
  179. package/src/prompts/goals/guided-goal-interview.md +41 -6
  180. package/src/prompts/system/auto-thinking-difficulty.md +4 -2
  181. package/src/prompts/system/plan-mode-active.md +2 -2
  182. package/src/prompts/system/system-prompt.md +3 -0
  183. package/src/prompts/system/vibe-mode-active.md +4 -1
  184. package/src/prompts/system/xdev-mount-notice.md +1 -1
  185. package/src/prompts/tools/bash.md +15 -17
  186. package/src/prompts/tools/browser.md +1 -0
  187. package/src/prompts/tools/checkpoint.md +1 -1
  188. package/src/prompts/tools/glob.md +7 -6
  189. package/src/prompts/tools/goal.md +1 -1
  190. package/src/prompts/tools/grep.md +7 -6
  191. package/src/sdk.ts +178 -15
  192. package/src/session/agent-session-events.ts +1 -0
  193. package/src/session/agent-session-types.ts +40 -1
  194. package/src/session/agent-session.ts +330 -179
  195. package/src/session/async-job-delivery.ts +8 -0
  196. package/src/session/claude-session-store.ts +426 -0
  197. package/src/session/codex-session-store.ts +673 -0
  198. package/src/session/credential-pin.ts +93 -0
  199. package/src/session/foreign-session-import.ts +52 -0
  200. package/src/session/foreign-session-jsonl.ts +29 -0
  201. package/src/session/foreign-session-store.ts +26 -0
  202. package/src/session/model-controls.ts +15 -7
  203. package/src/session/session-advisors.ts +142 -23
  204. package/src/session/session-entries.ts +21 -1
  205. package/src/session/session-handoff.ts +20 -3
  206. package/src/session/session-history-format.ts +31 -6
  207. package/src/session/session-loader.ts +25 -9
  208. package/src/session/session-maintenance.ts +59 -11
  209. package/src/session/session-manager.ts +61 -0
  210. package/src/session/session-tools.ts +107 -5
  211. package/src/session/settings-stream-fn.ts +1 -0
  212. package/src/session/turn-recovery.ts +97 -51
  213. package/src/slash-commands/builtin-registry.ts +136 -5
  214. package/src/system-prompt.ts +3 -2
  215. package/src/task/executor.ts +17 -4
  216. package/src/task/isolation-ownership.ts +106 -0
  217. package/src/task/render.ts +20 -4
  218. package/src/task/worktree.ts +8 -0
  219. package/src/thinking.ts +29 -10
  220. package/src/tools/acp-bridge.ts +52 -8
  221. package/src/tools/ask.ts +3 -3
  222. package/src/tools/ast-edit.ts +9 -2
  223. package/src/tools/browser/cmux/cmux-tab.ts +9 -14
  224. package/src/tools/browser/tab-worker.ts +12 -35
  225. package/src/tools/browser.ts +7 -2
  226. package/src/tools/checkpoint.ts +0 -13
  227. package/src/tools/gh-renderer.ts +3 -3
  228. package/src/tools/grep.ts +64 -8
  229. package/src/tools/hub/launch.ts +10 -15
  230. package/src/tools/index.ts +38 -5
  231. package/src/tools/path-utils.ts +88 -0
  232. package/src/tools/read.ts +3 -0
  233. package/src/tools/write.ts +44 -3
  234. package/src/tools/xdev.ts +39 -14
  235. package/src/tui/code-cell.ts +4 -4
  236. package/src/tui/output-block.ts +25 -8
  237. package/src/utils/changelog.ts +144 -4
  238. package/src/utils/git.ts +61 -14
  239. package/src/utils/jj.ts +19 -13
  240. package/src/utils/shell-snapshot-fn-env.sh +5 -2
  241. package/src/web/search/providers/codex.ts +34 -0
  242. package/src/web/search/render.ts +2 -2
  243. package/src/web/search/types.ts +1 -1
  244. package/dist/types/goals/guided-setup.d.ts +0 -30
  245. package/src/goals/guided-setup.ts +0 -171
  246. package/src/prompts/goals/guided-goal-system.md +0 -33
@@ -78,7 +78,6 @@ import type {
78
78
  import type { CompactOptions } from "../extensibility/extensions/types";
79
79
  import type { Skill } from "../extensibility/skills";
80
80
  import { loadSlashCommands } from "../extensibility/slash-commands";
81
- import { type GuidedGoalMessage, newGuidedGoalSessionId, runGuidedGoalTurn } from "../goals/guided-setup";
82
81
  import type { Goal, GoalModeState } from "../goals/state";
83
82
  import { resolveLocalUrlToPath } from "../internal-urls";
84
83
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
@@ -91,6 +90,7 @@ import {
91
90
  } from "../mcp/startup-events";
92
91
  import { humanizePlanTitle, type PlanApprovalDetails, resolvePlanTitle } from "../plan-mode/approved-plan";
93
92
  import { resolvePlanModelTransition } from "../plan-mode/model-transition";
93
+ import guidedGoalInterviewPrompt from "../prompts/goals/guided-goal-interview.md" with { type: "text" };
94
94
  import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
95
95
  import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
96
96
  type: "text",
@@ -103,6 +103,7 @@ import {
103
103
  SHUTDOWN_CONSOLIDATE_BUDGET_MS,
104
104
  } from "../session/agent-session";
105
105
  import type { CompactMode } from "../session/compact-modes";
106
+ import type { ForeignSessionSource } from "../session/foreign-session-store";
106
107
  import { HistoryStorage } from "../session/history-storage";
107
108
  import type { SessionContext } from "../session/session-context";
108
109
  import { getRecentSessions } from "../session/session-listing";
@@ -127,6 +128,7 @@ import {
127
128
  } from "../tools/todo";
128
129
  import { vocalizer } from "../tts/vocalizer";
129
130
  import { renderTreeList } from "../tui/tree-list";
131
+ import { formatStartupChangelogSummary, type StartupChangelogSelection } from "../utils/changelog";
130
132
  import { copyToClipboard } from "../utils/clipboard";
131
133
  import type { EventBus } from "../utils/event-bus";
132
134
  import { getEditorCommand, openInEditor } from "../utils/external-editor";
@@ -148,6 +150,7 @@ import {
148
150
  import type { AssistantMessageComponent } from "./components/assistant-message";
149
151
  import type { BashExecutionComponent } from "./components/bash-execution";
150
152
  import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
153
+ import { CodexResetFireworksController } from "./components/codex-reset-fireworks";
151
154
  import { CustomEditor } from "./components/custom-editor";
152
155
  import { DynamicBorder } from "./components/dynamic-border";
153
156
  import { ErrorBannerComponent } from "./components/error-banner";
@@ -202,6 +205,7 @@ import {
202
205
  onTerminalAppearanceChange,
203
206
  onThemeChange,
204
207
  setMarkdownMermaidRendering,
208
+ startMacOSAppearanceReprobeFallback,
205
209
  theme,
206
210
  } from "./theme/theme";
207
211
  import type {
@@ -554,7 +558,7 @@ export class InteractiveMode implements InteractiveModeContext {
554
558
  #cleanupUnsubscribe?: () => void;
555
559
  #signalTeardown?: SessionTeardown;
556
560
  readonly #version: string;
557
- readonly #changelogMarkdown: string | undefined;
561
+ readonly #startupChangelog: StartupChangelogSelection | undefined;
558
562
  #planModePreviousTools: string[] | undefined;
559
563
  #goalModePreviousTools: string[] | undefined;
560
564
  #vibeModePreviousTools: string[] | undefined;
@@ -580,6 +584,7 @@ export class InteractiveMode implements InteractiveModeContext {
580
584
  mcpManager?: MCPManager;
581
585
  readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
582
586
 
587
+ readonly #codexResetFireworksController: CodexResetFireworksController;
583
588
  readonly #btwController: BtwController;
584
589
  readonly #tanCommandController: TanCommandController;
585
590
  readonly #omfgController: OmfgController;
@@ -662,7 +667,7 @@ export class InteractiveMode implements InteractiveModeContext {
662
667
  constructor(
663
668
  session: AgentSession,
664
669
  version: string,
665
- changelogMarkdown: string | undefined = undefined,
670
+ startupChangelog: StartupChangelogSelection | undefined = undefined,
666
671
  setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
667
672
  lspServers: LspStartupServerInfo[] | undefined = undefined,
668
673
  mcpManager?: MCPManager,
@@ -674,7 +679,7 @@ export class InteractiveMode implements InteractiveModeContext {
674
679
  this.keybindings = KeybindingsManager.inMemory();
675
680
  this.agent = session.agent;
676
681
  this.#version = version;
677
- this.#changelogMarkdown = changelogMarkdown;
682
+ this.#startupChangelog = startupChangelog;
678
683
  this.#toolUiContextSetter = setToolUIContext;
679
684
  this.lspServers = lspServers;
680
685
  this.mcpManager = mcpManager;
@@ -750,6 +755,10 @@ export class InteractiveMode implements InteractiveModeContext {
750
755
  this.editorContainer.addChild(this.editor);
751
756
  this.statusLine = new StatusLineComponent(session);
752
757
  this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
758
+ this.#codexResetFireworksController = new CodexResetFireworksController(this);
759
+ this.statusLine.setCodexResetFireworksHandler(event => {
760
+ this.#codexResetFireworksController.show(event);
761
+ });
753
762
  // Vibe worker tok/s aggregator — keeps the status-line render layer off
754
763
  // the heavy vibe/task dependency graph. The director is often idle while
755
764
  // workers stream, so without this the tok/s badge would show a stale
@@ -941,19 +950,20 @@ export class InteractiveMode implements InteractiveModeContext {
941
950
  }
942
951
 
943
952
  // Add changelog if provided
944
- if (this.#changelogMarkdown) {
953
+ if (this.#startupChangelog && settings.get("startup.changelogMode") !== "hidden") {
945
954
  this.ui.addChild(new DynamicBorder());
946
- if (settings.get("collapseChangelog")) {
947
- const versionMatch = this.#changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
948
- const latestVersion = versionMatch ? versionMatch[1] : this.#version;
949
- const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
950
- this.ui.addChild(new Text(condensedText, 1, 0));
955
+ this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
956
+ this.ui.addChild(new Spacer(1));
957
+ if (settings.get("startup.changelogMode") === "summary") {
958
+ const summary = formatStartupChangelogSummary(this.#startupChangelog).replace(
959
+ /\/changelog(?: full)?/g,
960
+ command => theme.bold(command),
961
+ );
962
+ this.ui.addChild(new Text(summary, 1, 0));
951
963
  } else {
952
- this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
953
- this.ui.addChild(new Spacer(1));
954
- this.ui.addChild(new Markdown(this.#changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
955
- this.ui.addChild(new Spacer(1));
964
+ this.ui.addChild(new Markdown(this.#startupChangelog.markdown?.trim() ?? "", 1, 0, getMarkdownTheme()));
956
965
  }
966
+ this.ui.addChild(new Spacer(1));
957
967
  this.ui.addChild(new DynamicBorder());
958
968
  }
959
969
  }
@@ -995,6 +1005,10 @@ export class InteractiveMode implements InteractiveModeContext {
995
1005
  // Load initial todos
996
1006
  await this.#loadTodoList();
997
1007
 
1008
+ if (process.platform === "darwin" && TERMINAL.id === "wezterm" && !isInsideTerminalMultiplexer()) {
1009
+ this.#eventBusUnsubscribers.push(startMacOSAppearanceReprobeFallback(this.ui.terminal));
1010
+ }
1011
+
998
1012
  // Start the UI. Cold `omp` launch opts into clearing on the first paint so
999
1013
  // the initial welcome frame does not append over the previous run's scrollback.
1000
1014
  this.ui.start({ clearScrollback: options.clearInitialTerminalHistory === true });
@@ -1273,8 +1287,7 @@ export class InteractiveMode implements InteractiveModeContext {
1273
1287
  await this.refreshSkillState();
1274
1288
  await this.refreshSlashCommandState(newCwd);
1275
1289
  setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
1276
- this.statusLine.invalidate();
1277
- this.ui.requestRender();
1290
+ this.statusLine.applyCwdChange();
1278
1291
  }
1279
1292
 
1280
1293
  async getUserInput(): Promise<SubmittedUserInput> {
@@ -3277,9 +3290,10 @@ export class InteractiveMode implements InteractiveModeContext {
3277
3290
 
3278
3291
  /**
3279
3292
  * `/vibe` toggle. Entering installs the ephemeral vibe tools, strips the
3280
- * active toolset down to `read` plus those tools, and injects the director
3281
- * context. Exiting unregisters them, restores the previous toolset, and kills
3282
- * every worker session so workers cannot outlive the mode that directs them.
3293
+ * active toolset down to `read`, optional parent-owned `todo`, plus those
3294
+ * tools, and injects the director context. Exiting unregisters them, restores
3295
+ * the previous toolset, and kills every worker session so workers cannot
3296
+ * outlive the mode that directs them.
3283
3297
  */
3284
3298
  async handleVibeModeCommand(initialPrompt?: string): Promise<void> {
3285
3299
  if (this.vibeModeEnabled) {
@@ -3317,7 +3331,9 @@ export class InteractiveMode implements InteractiveModeContext {
3317
3331
  const ownerScope = vibeRegistry.ownerScope(this.#vibeParentSession());
3318
3332
  vibeRegistry.activateScope(ownerScope);
3319
3333
  const previousTools = this.session.getEnabledToolNames();
3320
- await this.session.activateVibeTools(["read"]);
3334
+ const vibeBaseTools = ["read"];
3335
+ if (this.session.hasBuiltInTool("todo")) vibeBaseTools.push("todo");
3336
+ await this.session.activateVibeTools(vibeBaseTools);
3321
3337
  this.#vibeModePreviousTools = previousTools;
3322
3338
  this.#vibeModeOwnerScope = ownerScope;
3323
3339
  this.vibeModeEnabled = true;
@@ -3330,7 +3346,9 @@ export class InteractiveMode implements InteractiveModeContext {
3330
3346
  }
3331
3347
  this.#updateVibeModeStatus();
3332
3348
  if (options?.persistModeChange !== false) this.sessionManager.appendModeChange("vibe");
3333
- this.showStatus("Vibe mode enabled. You direct fast/good worker sessions; toolset is read + vibe tools.");
3349
+ this.showStatus(
3350
+ "Vibe mode enabled. You direct fast/good worker sessions; toolset is read + optional parent Todo + vibe tools.",
3351
+ );
3334
3352
  }
3335
3353
 
3336
3354
  async #exitVibeMode(): Promise<void> {
@@ -3434,6 +3452,10 @@ export class InteractiveMode implements InteractiveModeContext {
3434
3452
  this.showWarning("Exit plan mode first.");
3435
3453
  return;
3436
3454
  }
3455
+ if (this.vibeModeEnabled) {
3456
+ this.showWarning("Exit vibe mode first.");
3457
+ return;
3458
+ }
3437
3459
  if (!this.session.settings.get("goal.enabled")) {
3438
3460
  this.showWarning("Goal mode is disabled. Enable it in settings (goal.enabled).");
3439
3461
  return;
@@ -3447,51 +3469,31 @@ export class InteractiveMode implements InteractiveModeContext {
3447
3469
  return;
3448
3470
  }
3449
3471
 
3450
- const initial = rest?.trim()
3451
- ? rest.trim()
3452
- : (await this.showHookEditor("Guided goal", undefined, undefined, { promptStyle: true }))?.trim();
3453
- if (!initial) return;
3454
-
3455
- const messages: GuidedGoalMessage[] = [{ role: "user", content: initial }];
3456
- let latestDraftObjective: string | undefined;
3457
- // One Codex side session for the whole interview: every follow-up turn
3458
- // reuses it so a multi-question interview shares a single websocket-only
3459
- // Codex socket instead of leaking one per turn (#5471 review).
3460
- const guidedGoalSessionId = newGuidedGoalSessionId(this.session);
3461
- for (let turn = 0; turn < 6; turn++) {
3462
- const result = await runGuidedGoalTurn(this.session, { messages, sideSessionId: guidedGoalSessionId });
3463
- if (result.objective?.trim()) latestDraftObjective = result.objective.trim();
3464
- if (result.kind === "question") {
3465
- messages.push({ role: "assistant", content: result.question });
3466
- const answer = (
3467
- await this.showHookEditor(result.question, undefined, undefined, { promptStyle: true })
3468
- )?.trim();
3469
- if (!answer) return;
3470
- messages.push({ role: "user", content: answer });
3471
- continue;
3472
- }
3473
-
3474
- const finalObjective = (
3475
- await this.showHookEditor("Review guided goal", result.objective, undefined, { promptStyle: true })
3476
- )?.trim();
3477
- if (!finalObjective) return;
3478
- await this.#startGoalFromObjective(finalObjective);
3479
- return;
3472
+ // Expose the goal tool for the interview so the agent can finish by
3473
+ // calling `goal create`. Record the pre-interview toolset first: the
3474
+ // tool-driven create flips goalModeEnabled via `goal_updated`, and the
3475
+ // eventual goal exit restores this set (dropping the goal tool again).
3476
+ const enabledTools = this.session.getEnabledToolNames();
3477
+ this.#goalModePreviousTools = enabledTools.filter(name => name !== "goal");
3478
+ if (!enabledTools.includes("goal")) {
3479
+ await this.session.setActiveToolsByName([...enabledTools, "goal"]);
3480
3480
  }
3481
3481
 
3482
- // Hit the turn cap without an explicit `ready`. Rather than discard the whole interview,
3483
- // salvage the latest non-empty model objective draft seen on any earlier turn. A final
3484
- // question turn may omit `objective`; that must not erase a usable draft.
3485
- if (latestDraftObjective) {
3486
- const finalObjective = (
3487
- await this.showHookEditor("Review guided goal", latestDraftObjective, undefined, { promptStyle: true })
3488
- )?.trim();
3489
- if (finalObjective) {
3490
- await this.#startGoalFromObjective(finalObjective);
3491
- return;
3482
+ // The interview is a normal conversation: the kickoff rides in as a
3483
+ // hidden developer message, the agent asks its questions as regular
3484
+ // assistant turns, and the user answers in the ordinary editor. Queue
3485
+ // behind an in-flight run instead of aborting it.
3486
+ const kickoff = prompt.render(guidedGoalInterviewPrompt, { initial: rest?.trim() || undefined });
3487
+ if (this.session.isStreaming) {
3488
+ await this.session.followUp(kickoff, undefined, { synthetic: true });
3489
+ } else {
3490
+ try {
3491
+ await this.session.prompt(kickoff, { synthetic: true });
3492
+ } catch (error) {
3493
+ if (!(error instanceof AgentBusyError)) throw error;
3494
+ await this.session.followUp(kickoff, undefined, { synthetic: true });
3492
3495
  }
3493
3496
  }
3494
- this.showWarning("Guided goal setup needs more detail. Run /guided-goal again with a narrower objective.");
3495
3497
  } catch (error) {
3496
3498
  this.showError(error instanceof Error ? error.message : String(error));
3497
3499
  }
@@ -3953,6 +3955,7 @@ export class InteractiveMode implements InteractiveModeContext {
3953
3955
  this.#agentRegistryUnsubscribe = undefined;
3954
3956
  this.#agentRegistrySubscriptionTarget = undefined;
3955
3957
  this.#eventController.dispose();
3958
+ this.#codexResetFireworksController.dispose();
3956
3959
  this.statusLine.dispose();
3957
3960
  if (this.#resizeHandler) {
3958
3961
  process.stdout.removeListener("resize", this.#resizeHandler);
@@ -4692,8 +4695,8 @@ export class InteractiveMode implements InteractiveModeContext {
4692
4695
  this.#selectorController.showTreeSelector();
4693
4696
  }
4694
4697
 
4695
- showSessionSelector(): void {
4696
- this.#selectorController.showSessionSelector();
4698
+ showSessionSelector(source?: ForeignSessionSource): void {
4699
+ void this.#selectorController.showSessionSelector(source);
4697
4700
  }
4698
4701
 
4699
4702
  async handleResumeSession(sessionPath: string): Promise<void> {
@@ -158,9 +158,11 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
158
158
  if (command && (!("allowArgs" in command) || command.allowArgs !== false)) {
159
159
  const argumentSuggestions = await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol);
160
160
  if (argumentSuggestions) return argumentSuggestions;
161
- // No slash-argument completion for this input: fall through to
162
- // internal-url completion only. `#` prompt-action tokens stay
163
- // literal text inside slash command arguments.
161
+ // No slash-argument completion for this input: preserve numeric
162
+ // GitHub references and internal URLs while keeping prompt-action
163
+ // tokens such as `#copy` literal.
164
+ const githubRefSuggestions = getGithubRefSuggestions(textBeforeCursor);
165
+ if (githubRefSuggestions) return githubRefSuggestions;
164
166
  return getInternalUrlSuggestions(textBeforeCursor, this.#basePath);
165
167
  }
166
168
  }
@@ -128,6 +128,7 @@ const sessionEventTypes = new Set<AgentSessionEvent["type"]>([
128
128
  "irc_message",
129
129
  "notice",
130
130
  "thinking_level_changed",
131
+ "model_changed",
131
132
  "goal_updated",
132
133
  ]);
133
134
 
@@ -597,6 +598,23 @@ export class RpcClient {
597
598
  */
598
599
  async getState(): Promise<RpcSessionState> {
599
600
  const response = await this.#send({ type: "get_state" });
601
+ const state = this.#getData<RpcSessionState>(response);
602
+ return {
603
+ ...state,
604
+ fastModeEnabled: state.fastModeEnabled === true,
605
+ fastModeActive: state.fastModeActive === true,
606
+ tokensPerSecond:
607
+ typeof state.tokensPerSecond === "number" && Number.isFinite(state.tokensPerSecond)
608
+ ? state.tokensPerSecond
609
+ : null,
610
+ };
611
+ }
612
+
613
+ /**
614
+ * Enable or disable fast mode for the active model family.
615
+ */
616
+ async setFastMode(enabled: boolean): Promise<{ enabled: boolean; active: boolean }> {
617
+ const response = await this.#send({ type: "set_fast_mode", enabled });
600
618
  return this.#getData(response);
601
619
  }
602
620
 
@@ -32,6 +32,7 @@ import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins
32
32
  import { buildAvailableSlashCommands } from "../../slash-commands/available-commands";
33
33
  import { defaultLoadModeForToolName } from "../../tools/essential-tools";
34
34
  import type { EventBus } from "../../utils/event-bus";
35
+ import { calculateTokensPerSecond } from "../../utils/token-rate";
35
36
  import { initializeExtensions } from "../runtime-init";
36
37
  import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
37
38
  import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
@@ -1084,9 +1085,12 @@ export async function runRpcMode(
1084
1085
  sessionId: session.sessionId,
1085
1086
  sessionName: session.sessionName,
1086
1087
  autoCompactionEnabled: session.autoCompactionEnabled,
1087
- messageCount: session.messages.length,
1088
1088
  queuedMessageCount: session.queuedMessageCount,
1089
1089
  todoPhases: session.getTodoPhases(),
1090
+ fastModeEnabled: session.isFastModeEnabled(),
1091
+ tokensPerSecond: calculateTokensPerSecond(session.messages, session.isStreaming),
1092
+ fastModeActive: session.isFastModeActive(),
1093
+ messageCount: session.messages.length,
1090
1094
  systemPrompt: session.systemPrompt,
1091
1095
  dumpTools: session.agent.state.tools.map(tool => ({
1092
1096
  name: tool.name,
@@ -1099,6 +1103,17 @@ export async function runRpcMode(
1099
1103
  return success(id, "get_state", state);
1100
1104
  }
1101
1105
 
1106
+ case "set_fast_mode": {
1107
+ const supported = session.setFastMode(command.enabled);
1108
+ if (command.enabled && !supported) {
1109
+ return error(id, "set_fast_mode", "Fast mode is unavailable for the current model.");
1110
+ }
1111
+ return success(id, "set_fast_mode", {
1112
+ enabled: session.isFastModeEnabled(),
1113
+ active: session.isFastModeActive(),
1114
+ });
1115
+ }
1116
+
1102
1117
  case "get_available_commands": {
1103
1118
  return success(id, "get_available_commands", { commands: await getAvailableCommands() });
1104
1119
  }
@@ -39,6 +39,7 @@ export type RpcCommand =
39
39
 
40
40
  // State
41
41
  | { id?: string; type: "get_state" }
42
+ | { id?: string; type: "set_fast_mode"; enabled: boolean }
42
43
  | { id?: string; type: "get_available_commands" }
43
44
  | { id?: string; type: "set_todos"; phases: TodoPhase[] }
44
45
  | { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
@@ -107,6 +108,9 @@ export interface RpcSessionState {
107
108
  sessionId: string;
108
109
  sessionName?: string;
109
110
  autoCompactionEnabled: boolean;
111
+ fastModeEnabled: boolean;
112
+ fastModeActive: boolean;
113
+ tokensPerSecond: number | null;
110
114
  messageCount: number;
111
115
  queuedMessageCount: number;
112
116
  todoPhases: TodoPhase[];
@@ -209,6 +213,13 @@ export type RpcResponse =
209
213
 
210
214
  // State
211
215
  | { id?: string; type: "response"; command: "get_state"; success: true; data: RpcSessionState }
216
+ | {
217
+ id?: string;
218
+ type: "response";
219
+ command: "set_fast_mode";
220
+ success: true;
221
+ data: { enabled: boolean; active: boolean };
222
+ }
212
223
  | {
213
224
  id?: string;
214
225
  type: "response";
@@ -9,7 +9,15 @@ import {
9
9
  highlightCode as nativeHighlightCode,
10
10
  supportsLanguage as nativeSupportsLanguage,
11
11
  } from "@oh-my-pi/pi-natives";
12
- import type { EditorTheme, MarkdownTheme, SelectListTheme, SettingsListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
12
+ import type {
13
+ EditorTheme,
14
+ MarkdownTheme,
15
+ SelectListTheme,
16
+ SettingsListTheme,
17
+ SymbolTheme,
18
+ Terminal,
19
+ TerminalAppearance,
20
+ } from "@oh-my-pi/pi-tui";
13
21
  import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
14
22
  import { type } from "arktype";
15
23
  import chalk from "chalk";
@@ -2493,28 +2501,165 @@ async function startThemeWatcher(): Promise<void> {
2493
2501
  }
2494
2502
 
2495
2503
  /**
2496
- * Shared logic for re-evaluating the auto-detected theme.
2497
- * Called from SIGWINCH, terminal appearance change handler, and macOS fallback observer.
2504
+ * Load and apply an already-resolved auto-theme name.
2498
2505
  */
2499
- function reevaluateAutoTheme(debugLabel: string, event: ThemeChangeEvent = {}): void {
2500
- if (!autoDetectedTheme) return;
2501
- const resolved = getDefaultTheme();
2506
+ function applyResolvedAutoTheme(resolved: string, debugLabel: string, event: ThemeChangeEvent): void {
2502
2507
  if (resolved === currentThemeName) return;
2503
2508
  currentThemeName = resolved;
2509
+ const requestId = ++themeLoadRequestId;
2504
2510
  loadTheme(resolved, getCurrentThemeOptions())
2505
2511
  .then(loadedTheme => {
2512
+ if (requestId !== themeLoadRequestId) return;
2506
2513
  theme = loadedTheme;
2507
2514
  notifyThemeChange(event);
2508
2515
  })
2509
2516
  .catch(err => {
2517
+ if (requestId !== themeLoadRequestId) return;
2510
2518
  logger.debug(`Theme switch on ${debugLabel} failed`, { error: String(err) });
2511
2519
  });
2512
2520
  }
2513
2521
 
2522
+ /**
2523
+ * Shared logic for re-evaluating the auto-detected theme.
2524
+ * An explicit appearance is provisional input and does not alter terminal-reported state.
2525
+ */
2526
+ function reevaluateAutoTheme(debugLabel: string, event: ThemeChangeEvent = {}, appearance?: "dark" | "light"): void {
2527
+ if (!autoDetectedTheme) return;
2528
+ const resolved =
2529
+ appearance === undefined ? getDefaultTheme() : appearance === "dark" ? autoDarkTheme : autoLightTheme;
2530
+ applyResolvedAutoTheme(resolved, debugLabel, event);
2531
+ }
2532
+
2514
2533
  // ============================================================================
2515
2534
  // macOS Appearance Fallback Observer
2516
2535
  // ============================================================================
2517
2536
 
2537
+ type MacOSAppearanceReprobeTerminal = Pick<
2538
+ Terminal,
2539
+ "appearance" | "onAppearanceChange" | "onAppearanceReport" | "onPrivateModeReport" | "refreshAppearance"
2540
+ >;
2541
+
2542
+ const MACOS_APPEARANCE_REPROBE_DELAYS_MS = [25, 50, 100, 250, 500, 1000] as const;
2543
+ const MACOS_APPEARANCE_RECONCILE_DELAY_MS = 1100;
2544
+
2545
+ /**
2546
+ * Fall back to native macOS appearance notifications when the terminal
2547
+ * explicitly confirms that Mode 2031 notifications are unsupported.
2548
+ *
2549
+ * Native notifications provisionally repaint from the host appearance and
2550
+ * synchronously trigger an OSC 11 probe, followed by a bounded burst of six
2551
+ * retries. A changed terminal classification cancels the sequence; otherwise
2552
+ * a confirmed terminal classification is restored at the validation deadline.
2553
+ */
2554
+ export function startMacOSAppearanceReprobeFallback(terminal: MacOSAppearanceReprobeTerminal): () => void {
2555
+ let disposed = false;
2556
+ let observerStartAttempted = false;
2557
+ let observer: MacAppearanceObserver | undefined;
2558
+ let probeGeneration = 0;
2559
+ let probeSequenceActive = false;
2560
+ let probeBaseline: TerminalAppearance | undefined;
2561
+ let probeResponseConfirmed = false;
2562
+ const probeTimers = new Set<Timer>();
2563
+ let reconciliationTimer: Timer | undefined;
2564
+
2565
+ const cancelProbeSequence = (): void => {
2566
+ probeGeneration++;
2567
+ probeSequenceActive = false;
2568
+ probeBaseline = undefined;
2569
+ probeResponseConfirmed = false;
2570
+ if (reconciliationTimer) {
2571
+ clearTimeout(reconciliationTimer);
2572
+ reconciliationTimer = undefined;
2573
+ }
2574
+ for (const timer of probeTimers) {
2575
+ clearTimeout(timer);
2576
+ }
2577
+ probeTimers.clear();
2578
+ };
2579
+
2580
+ const scheduleProbeSequence = (): void => {
2581
+ cancelProbeSequence();
2582
+ if (disposed || !autoDetectedTheme) return;
2583
+
2584
+ probeSequenceActive = true;
2585
+ probeBaseline = terminal.appearance;
2586
+ probeResponseConfirmed = false;
2587
+ const generation = probeGeneration;
2588
+ terminal.refreshAppearance?.();
2589
+ if (disposed || generation !== probeGeneration || !autoDetectedTheme) return;
2590
+ for (const delay of MACOS_APPEARANCE_REPROBE_DELAYS_MS) {
2591
+ const timer = setTimeout(() => {
2592
+ probeTimers.delete(timer);
2593
+ if (disposed || generation !== probeGeneration) return;
2594
+ if (!autoDetectedTheme) {
2595
+ cancelProbeSequence();
2596
+ return;
2597
+ }
2598
+ terminal.refreshAppearance?.();
2599
+ }, delay);
2600
+ timer.unref?.();
2601
+ probeTimers.add(timer);
2602
+ }
2603
+ reconciliationTimer = setTimeout(() => {
2604
+ reconciliationTimer = undefined;
2605
+ if (disposed || generation !== probeGeneration) return;
2606
+ const appearance = probeResponseConfirmed ? terminal.appearance : undefined;
2607
+ cancelProbeSequence();
2608
+ if (!autoDetectedTheme || !appearance) return;
2609
+ reevaluateAutoTheme("macOS appearance reconciliation", {}, appearance);
2610
+ }, MACOS_APPEARANCE_RECONCILE_DELAY_MS);
2611
+ reconciliationTimer.unref?.();
2612
+ };
2613
+
2614
+ const unsubscribeAppearanceReport = terminal.onAppearanceReport?.(() => {
2615
+ if (disposed || !probeSequenceActive) return;
2616
+ probeResponseConfirmed = true;
2617
+ });
2618
+
2619
+ terminal.onAppearanceChange(appearance => {
2620
+ if (disposed || !probeSequenceActive || appearance === probeBaseline) return;
2621
+ cancelProbeSequence();
2622
+ });
2623
+
2624
+ terminal.onPrivateModeReport?.((mode, supported, confirmed) => {
2625
+ if (disposed || observerStartAttempted || mode !== 2031 || supported || confirmed !== true) {
2626
+ return;
2627
+ }
2628
+
2629
+ observerStartAttempted = true;
2630
+ try {
2631
+ observer = MacAppearanceObserver.start((err, appearance) => {
2632
+ if (disposed) return;
2633
+ if (err) {
2634
+ cancelProbeSequence();
2635
+ return;
2636
+ }
2637
+ if (appearance === "dark" || appearance === "light") {
2638
+ reevaluateAutoTheme("macOS provisional appearance", {}, appearance);
2639
+ }
2640
+ scheduleProbeSequence();
2641
+ });
2642
+ } catch (err) {
2643
+ logger.warn("Failed to start macOS appearance reprobe observer", { err });
2644
+ }
2645
+ });
2646
+
2647
+ return () => {
2648
+ if (disposed) return;
2649
+ disposed = true;
2650
+ cancelProbeSequence();
2651
+ if (unsubscribeAppearanceReport) unsubscribeAppearanceReport();
2652
+ const activeObserver = observer;
2653
+ observer = undefined;
2654
+ if (!activeObserver) return;
2655
+ try {
2656
+ activeObserver.stop();
2657
+ } catch (err) {
2658
+ logger.debug("Failed to stop macOS appearance reprobe observer", { err });
2659
+ }
2660
+ };
2661
+ }
2662
+
2518
2663
  var macObserver: { stop(): void } | undefined;
2519
2664
 
2520
2665
  function startMacAppearanceObserver(): void {
@@ -20,6 +20,7 @@ import type { MCPManager } from "../mcp";
20
20
  import type { PlanApprovalDetails } from "../plan-mode/approved-plan";
21
21
  import type { AgentSession } from "../session/agent-session";
22
22
  import type { CompactMode } from "../session/compact-modes";
23
+ import type { ForeignSessionSource } from "../session/foreign-session-store";
23
24
  import type { HistoryStorage } from "../session/history-storage";
24
25
  import type { SessionContext } from "../session/session-context";
25
26
  import type { SessionManager } from "../session/session-manager";
@@ -384,7 +385,7 @@ export interface InteractiveModeContext {
384
385
  showUserMessageSelector(): void;
385
386
  showCopySelector(): void;
386
387
  showTreeSelector(): void;
387
- showSessionSelector(): void;
388
+ showSessionSelector(source?: ForeignSessionSource): void;
388
389
  handleResumeSession(sessionPath: string): Promise<void>;
389
390
  handleSessionDeleteCommand(): Promise<void>;
390
391
  showOAuthSelector(mode: "login" | "logout", providerId?: string): Promise<void>;