@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
@@ -24,6 +24,8 @@ export interface ReadToolDetails {
24
24
  method?: string;
25
25
  notes?: string[];
26
26
  meta?: OutputMeta;
27
+ /** Full on-disk byte size recorded before applying a file range. */
28
+ fileSize?: number;
27
29
  /** Raw text + start line for user-visible TUI rendering, set when content is text-like.
28
30
  * Mirrors the same lines the model receives but without hashline/line-number prefixes,
29
31
  * so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
@@ -103,10 +103,11 @@ export declare function resolveMountedXdevTool(state: XdevState, name: string):
103
103
  export declare function resolveMountedXdevExecutable(state: XdevState, name: string): Tool | undefined;
104
104
  /** Mounted tools in presentation order, resolved from the canonical map. */
105
105
  export declare function listXdevTools(state: XdevState): Tool[];
106
- /** `{name, summary}` pairs for prompt templates and `/tools` display. */
106
+ /** `{name, summary, dynamic}` triples for prompt templates and `/tools` display. */
107
107
  export declare function xdevEntries(state: XdevState): Array<{
108
108
  name: string;
109
109
  summary: string;
110
+ dynamic: boolean;
110
111
  }>;
111
112
  /** `read xd://` listing with one device per line. */
112
113
  export declare function xdevListing(state: XdevState): string;
@@ -16,6 +16,7 @@ export interface OutputBlockOptions {
16
16
  width: number;
17
17
  applyBg?: boolean;
18
18
  contentPaddingLeft?: number;
19
+ contentPaddingRight?: number;
19
20
  /** Override the state-derived border color. Used for muted "legacy" tool
20
21
  * frames that should not visually compete with framed-output tools. */
21
22
  borderColor?: ThemeColor;
@@ -28,12 +29,11 @@ export declare function markFramedBlockComponent<T extends Component>(component:
28
29
  export declare function isFramedBlockComponent(component: Component): boolean;
29
30
  /**
30
31
  * Inner content width that {@link renderOutputBlock} wraps its body to, for a
31
- * given outer `width`: both vertical borders (1 cell each) plus the left
32
- * content padding. Renderers that size a tail window MUST budget visual rows
33
- * against this, not the outer width otherwise the block re-wraps their lines
34
- * into more rows than they counted and the box overflows its intended height.
32
+ * given outer `width`: both vertical borders plus symmetric content padding.
33
+ * An explicit left padding of zero keeps legacy flush blocks flush on both
34
+ * sides unless a right padding is provided separately.
35
35
  */
36
- export declare function outputBlockContentWidth(width: number, contentPaddingLeft?: number): number;
36
+ export declare function outputBlockContentWidth(width: number, contentPaddingLeft?: number, contentPaddingRight?: number): number;
37
37
  export declare function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[];
38
38
  /**
39
39
  * Cached wrapper around `renderOutputBlock`.
@@ -1,3 +1,4 @@
1
+ import type { SettingValue } from "../config/settings.js";
1
2
  export interface ChangelogEntry {
2
3
  major: number;
3
4
  minor: number;
@@ -21,7 +22,13 @@ export interface StartupChangelogSelection {
21
22
  persistCurrentVersion: boolean;
22
23
  truncated: boolean;
23
24
  selectedEntries: number;
25
+ totalUnseenEntries: number;
26
+ latestVersion: string | undefined;
27
+ changeCount: number;
28
+ categoryCounts: Record<string, number>;
24
29
  }
30
+ /** Format the compact, deterministic startup update notice. */
31
+ export declare function formatStartupChangelogSummary(selection: StartupChangelogSelection): string;
25
32
  /**
26
33
  * Parse changelog entries from omp's package asset when available, falling back
27
34
  * to the copy embedded in compiled binaries.
@@ -55,6 +62,18 @@ export declare function renderChangelogEntries(entries: ChangelogEntry[], option
55
62
  * Select bounded release notes for interactive startup.
56
63
  */
57
64
  export declare function selectStartupChangelog(entries: ChangelogEntry[], lastVersion: string | undefined, currentVersion: string): StartupChangelogSelection;
65
+ /**
66
+ * Resolve and persist the automatic startup changelog decision.
67
+ *
68
+ * Hidden mode advances the marker only for an upgrade, so downgrades do not
69
+ * erase knowledge of a newer version the user has already seen.
70
+ */
71
+ export declare function resolveStartupChangelogForDisplay(options: {
72
+ mode: SettingValue<"startup.changelogMode">;
73
+ currentVersion: string;
74
+ changelogPath?: string;
75
+ agentDir?: string;
76
+ }): Promise<StartupChangelogSelection | undefined>;
58
77
  export { getChangelogPath } from "../config.js";
59
78
  /**
60
79
  * Last omp version whose changelog the user has seen. Stored as a plain-text
@@ -147,6 +147,14 @@ export declare const GIT_COMMAND_TIMEOUT_MS: number;
147
147
  export declare const GIT_NETWORK_TIMEOUT_MS: number;
148
148
  /** Maximum captured stdout or stderr bytes retained from git and gh subprocesses. */
149
149
  export declare const GIT_COMMAND_OUTPUT_LIMIT_BYTES: number;
150
+ /**
151
+ * Deadline for synchronous git plumbing commands launched via
152
+ * {@link gitSpawnSyncText}. These run on the render path (e.g. reftable HEAD
153
+ * resolution), so the deadline is short: a command that has not exited by then
154
+ * is killed and reported as {@link GIT_COMMAND_TIMEOUT_EXIT_CODE} so the caller
155
+ * degrades instead of freezing the UI indefinitely.
156
+ */
157
+ export declare const GIT_SPAWN_SYNC_TIMEOUT_MS = 5000;
150
158
  interface CommandOptions {
151
159
  readonly env?: Record<string, string | undefined>;
152
160
  readonly maxOutputBytes?: number;
@@ -271,6 +279,8 @@ export declare const log: {
271
279
  export declare const revList: {
272
280
  /** Commits in `base..head`, oldest first. */
273
281
  range(cwd: string, base: string, head: string, signal?: AbortSignal): Promise<string[]>;
282
+ /** Commits reachable from `ref` that touched `file`, newest first, capped at `limit`. */
283
+ touching(cwd: string, ref: string, file: string, limit: number, signal?: AbortSignal): Promise<string[]>;
274
284
  };
275
285
  export declare const branch: {
276
286
  /** Current branch name, or null if detached/unavailable. */
@@ -16,14 +16,21 @@ export interface JjRepository {
16
16
  storeDir: string;
17
17
  }
18
18
  /** Options for `jj diff` invocations. */
19
- export interface DiffOptions {
19
+ export interface DiffOptions extends JjCommandOptions {
20
20
  /** Optional file paths to restrict the diff with `-- <files>`. */
21
21
  readonly files?: readonly string[];
22
22
  /** Return only changed file names instead of Git-format diff text. */
23
23
  readonly nameOnly?: boolean;
24
- /** Optional abort signal passed to the spawned `jj` process. */
24
+ }
25
+ /** Options for a bounded `jj` subprocess query. */
26
+ export interface JjCommandOptions {
27
+ /** Optional cancellation signal for the subprocess. */
25
28
  readonly signal?: AbortSignal;
29
+ /** Deadline in milliseconds. Defaults to {@link JJ_COMMAND_TIMEOUT_MS}. */
30
+ readonly timeoutMs?: number;
26
31
  }
32
+ /** Default finite deadline for local jj subprocesses. */
33
+ export declare const JJ_COMMAND_TIMEOUT_MS = 5000;
27
34
  /** Error thrown when a checked `jj` command exits non-zero. */
28
35
  export declare class JjCommandError extends Error {
29
36
  /** Arguments passed after the common `jj --no-pager --color=never` prefix. */
@@ -46,7 +53,7 @@ export declare const workingCopy: {
46
53
  * Label `@` with its nearest bookmark, falling back to its short change ID.
47
54
  * Returns `null` when `jj` is unavailable or the query fails.
48
55
  */
49
- label(cwd: string, signal?: AbortSignal): Promise<string | null>;
56
+ label(cwd: string, options?: JjCommandOptions): Promise<string | null>;
50
57
  /** Parse working-copy label query output. */
51
58
  parseLabel: typeof parseWorkingCopyLabel;
52
59
  };
@@ -56,7 +63,7 @@ export declare const status: {
56
63
  * Count changes in `@` relative to its parent using the Git status shape.
57
64
  * Jujutsu has no index, so `staged` is always zero.
58
65
  */
59
- summary(cwd: string, signal?: AbortSignal): Promise<git.GitStatusSummary | null>;
66
+ summary(cwd: string, options?: JjCommandOptions): Promise<git.GitStatusSummary | null>;
60
67
  /** Parse `jj diff --summary` output into status counts. */
61
68
  parse: typeof parseStatusSummary;
62
69
  };
@@ -34,7 +34,7 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
34
34
  }, {
35
35
  readonly value: "exa";
36
36
  readonly label: "Exa";
37
- readonly description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP";
37
+ readonly description: "API via /login exa or EXA_API_KEY; explicit keyless fallback via MCP";
38
38
  }, {
39
39
  readonly value: "tinyfish";
40
40
  readonly label: "TinyFish";
@@ -136,7 +136,7 @@ export declare const SEARCH_PROVIDER_CHOICES: ({
136
136
  } | {
137
137
  readonly value: "exa";
138
138
  readonly label: "Exa";
139
- readonly description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP";
139
+ readonly description: "API via /login exa or EXA_API_KEY; explicit keyless fallback via MCP";
140
140
  } | {
141
141
  readonly value: "tinyfish";
142
142
  readonly label: "TinyFish";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "17.1.7",
4
+ "version": "17.2.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -52,17 +52,17 @@
52
52
  "@agentclientprotocol/sdk": "1.2.1",
53
53
  "@babel/parser": "^7.29.7",
54
54
  "@mozilla/readability": "^0.6.0",
55
- "@oh-my-pi/hashline": "17.1.7",
56
- "@oh-my-pi/omp-stats": "17.1.7",
57
- "@oh-my-pi/pi-agent-core": "17.1.7",
58
- "@oh-my-pi/pi-ai": "17.1.7",
59
- "@oh-my-pi/pi-catalog": "17.1.7",
60
- "@oh-my-pi/pi-mnemopi": "17.1.7",
61
- "@oh-my-pi/pi-natives": "17.1.7",
62
- "@oh-my-pi/pi-tui": "17.1.7",
63
- "@oh-my-pi/pi-utils": "17.1.7",
64
- "@oh-my-pi/pi-wire": "17.1.7",
65
- "@oh-my-pi/snapcompact": "17.1.7",
55
+ "@oh-my-pi/hashline": "17.2.0",
56
+ "@oh-my-pi/omp-stats": "17.2.0",
57
+ "@oh-my-pi/pi-agent-core": "17.2.0",
58
+ "@oh-my-pi/pi-ai": "17.2.0",
59
+ "@oh-my-pi/pi-catalog": "17.2.0",
60
+ "@oh-my-pi/pi-mnemopi": "17.2.0",
61
+ "@oh-my-pi/pi-natives": "17.2.0",
62
+ "@oh-my-pi/pi-tui": "17.2.0",
63
+ "@oh-my-pi/pi-utils": "17.2.0",
64
+ "@oh-my-pi/pi-wire": "17.2.0",
65
+ "@oh-my-pi/snapcompact": "17.2.0",
66
66
  "@opentelemetry/api": "^1.9.1",
67
67
  "@opentelemetry/api-logs": "^0.220.0",
68
68
  "@opentelemetry/context-async-hooks": "^2.9.0",
@@ -107,6 +107,10 @@
107
107
  "dist/cli.js",
108
108
  "dist/CHANGELOG-*.md",
109
109
  "dist/*.node",
110
+ "dist/template-*.css",
111
+ "dist/template-*.html",
112
+ "dist/template-*.js",
113
+ "dist/tool-views.generated-*.js",
110
114
  "scripts",
111
115
  "examples",
112
116
  "README.md",
@@ -9,6 +9,7 @@ const packageDir = path.join(import.meta.dir, "..");
9
9
  const outDir = path.join(packageDir, "dist");
10
10
  const cliPath = path.join(outDir, "cli.js");
11
11
  const shebang = "#!/usr/bin/env bun\n";
12
+ const legacyHtmlExportAssetPattern = /^(?:template-[^.]+\.(?:css|html|js)|tool-views\.generated-[^.]+\.js)$/;
12
13
 
13
14
  // Native / optional / platform-specific deps are loaded from installed files.
14
15
  // `omp-legacy-pi-modules` exists only in compiled binaries via the build plugin;
@@ -80,7 +81,8 @@ async function cleanBundleOutputs(): Promise<void> {
80
81
  entry === "cli.js" ||
81
82
  entry.endsWith(".node") ||
82
83
  entry.endsWith(".js.map") ||
83
- (entry.startsWith("CHANGELOG-") && entry.endsWith(".md")),
84
+ (entry.startsWith("CHANGELOG-") && entry.endsWith(".md")) ||
85
+ legacyHtmlExportAssetPattern.test(entry),
84
86
  )
85
87
  .map(entry => fs.rm(path.join(outDir, entry), { force: true })),
86
88
  );
@@ -75,17 +75,6 @@ export function isInterruptingSeverity(severity: AdvisorSeverity | undefined): b
75
75
  return severity === "concern" || severity === "blocker";
76
76
  }
77
77
 
78
- /**
79
- * Append a staleness caveat to an advisor note when newer primary turns arrived
80
- * after the reviewed transcript window (i.e. `hasFreshBacklog` is true on the
81
- * advisor runtime at delivery time). Pure function — no session coupling — so it
82
- * can be unit-tested in isolation and called from `AgentSession#routeAdvice`.
83
- */
84
- export function annotateForStaleness(note: string, hasFreshBacklog: boolean): string {
85
- if (!hasFreshBacklog) return note;
86
- return `${note}\n\n_(Note: newer primary turns arrived after this reviewed window — verify this still applies.)_`;
87
- }
88
-
89
78
  /** How an advisor note is routed to the primary. */
90
79
  export type AdvisorDeliveryChannel = "aside" | "steer" | "preserve";
91
80
  /** Half-open turn-count fence for the post-interrupt cooldown. */
@@ -2,6 +2,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import { estimateTokens } from "@oh-my-pi/pi-agent-core/compaction";
3
3
  import type { AssistantMessage, ImageContent, TextContent } from "@oh-my-pi/pi-ai";
4
4
  import * as AIError from "@oh-my-pi/pi-ai/error";
5
+ import { raceWithSignal } from "@oh-my-pi/pi-ai/utils/abort";
5
6
  import { type CursorExecResolvedCarrier, kCursorExecResolved } from "@oh-my-pi/pi-ai/utils/block-symbols";
6
7
  import { logger } from "@oh-my-pi/pi-utils";
7
8
  import { obfuscateToolArguments, type SecretObfuscator } from "../secrets/obfuscator";
@@ -47,7 +48,7 @@ export interface AdvisorRuntimeHost {
47
48
  * recovery path must never replay the full primary transcript.
48
49
  * Optional: hosts that omit it get no proactive maintenance.
49
50
  */
50
- maintainContext?(incomingTokens: number): Promise<boolean>;
51
+ maintainContext?(incomingTokens: number, signal: AbortSignal): Promise<boolean>;
51
52
  /**
52
53
  * Called immediately before each `agent.prompt(batch)` cycle. Lets the host
53
54
  * clear per-update advisor state — currently the one-advise-per-update gate
@@ -67,6 +68,7 @@ export interface AdvisorRuntimeHost {
67
68
  onTurnError?(
68
69
  error: unknown,
69
70
  failedMessages: readonly AgentMessage[],
71
+ signal: AbortSignal,
70
72
  ): Promise<boolean | undefined> | boolean | undefined;
71
73
  /** Called after a successful advisor turn so the host can finish fallback lifecycle reporting. */
72
74
  onTurnSuccess?(): Promise<void> | void;
@@ -76,6 +78,8 @@ export interface AdvisorRuntimeHost {
76
78
  * recovery (credential switch, fallback chain) declined. Cleared only by
77
79
  * an explicit reset (`/new`, config rebuild, session restart). */
78
80
  notifyQuotaExhausted?(): void;
81
+ /** Stable identity for the live advisor model. Used to restore full transcript rendering after a model switch. */
82
+ getModelIdentity?(): string;
79
83
  }
80
84
 
81
85
  /**
@@ -228,7 +232,6 @@ const MAX_COALESCE_ROUNDS = 3;
228
232
  const MAX_QUARANTINE_RETRIES = 2;
229
233
 
230
234
  const ADVISOR_RENDER_OPTIONS = {
231
- includeThinking: true,
232
235
  includeToolIntent: true,
233
236
  watchedRoles: true,
234
237
  expandPrimaryContext: true,
@@ -285,11 +288,17 @@ export class AdvisorRuntime {
285
288
  #advisorRegexSecretValues = new Set<string>();
286
289
  #pending: PendingDelta[] = [];
287
290
  #busy = false;
291
+ #sessionTransitionPaused = false;
292
+ #promptInFlight: Promise<void> | undefined;
293
+ #iterationAbort: AbortController | undefined;
288
294
  #backlog = 0;
289
295
  #consecutiveFailures = 0;
290
296
  #failureNotified = false;
291
297
  /** Consecutive quarantined turns since the last success/reset (issue #6661). */
292
298
  #consecutiveQuarantines = 0;
299
+ /** Whether primary reasoning is included in advisor deltas for the current model. */
300
+ #includeThinking = true;
301
+ #modelIdentity: string | undefined;
293
302
  /** Completed 3-failure backlog-drop cycles since the last success/reset. */
294
303
  #droppedBacklogs = 0;
295
304
  /**
@@ -340,18 +349,6 @@ export class AdvisorRuntime {
340
349
  return this.#halted;
341
350
  }
342
351
 
343
- /**
344
- * True when `#pending` is non-empty while the drain loop is busy — i.e., newer
345
- * primary turns arrived after the current batch's transcript window was fixed
346
- * but before the advisor model finished processing it. The delivery path uses
347
- * this to annotate advice that was generated without seeing those newer turns.
348
- * Can be true during `agent.prompt()`, a `maintainContext` await, or a retry
349
- * sleep — any time `#drain` is busy and a concurrent `onTurnEnd` pushed.
350
- */
351
- get hasFreshBacklog(): boolean {
352
- return this.#pending.length > 0;
353
- }
354
-
355
352
  /**
356
353
  * Called after each primary turn ends. Renders the incremental delta and
357
354
  * queues it for the advisor model.
@@ -440,6 +437,7 @@ export class AdvisorRuntime {
440
437
  }
441
438
 
442
439
  dispose(): void {
440
+ this.#iterationAbort?.abort("advisor disposed");
443
441
  this.disposed = true;
444
442
  this.#epoch++;
445
443
  this.#pending = [];
@@ -502,6 +500,31 @@ export class AdvisorRuntime {
502
500
  });
503
501
  }
504
502
 
503
+ /** Stop new advisor work and wait only for the active prompt's recorder-visible events. */
504
+ pauseForSessionTransition(): Promise<void> {
505
+ if (!this.#sessionTransitionPaused) {
506
+ this.#sessionTransitionPaused = true;
507
+ this.#wakeAllWaiters();
508
+ this.#iterationAbort?.abort("advisor session transition");
509
+ try {
510
+ this.agent.abort("advisor session transition");
511
+ } catch {}
512
+ }
513
+ return (
514
+ this.#promptInFlight?.then(
515
+ () => {},
516
+ () => {},
517
+ ) ?? Promise.resolve()
518
+ );
519
+ }
520
+
521
+ /** Continue queued work after a session transition rolls back or preserves the conversation. */
522
+ resumeAfterSessionTransition(): void {
523
+ if (!this.#sessionTransitionPaused) return;
524
+ this.#sessionTransitionPaused = false;
525
+ if (!this.#quotaExhausted && !this.#halted) void this.#drain();
526
+ }
527
+
505
528
  /**
506
529
  * Re-prime the advisor after a history rewrite (compaction, session
507
530
  * switch/resume, branch). Clears the advisor's own (non-persisted) context
@@ -510,7 +533,9 @@ export class AdvisorRuntime {
510
533
  * leaving it blind to everything before the rewrite.
511
534
  */
512
535
  reset(): void {
536
+ this.#iterationAbort?.abort("advisor reset");
513
537
  this.#epoch++;
538
+ this.#sessionTransitionPaused = false;
514
539
  this.#quotaExhausted = false;
515
540
  this.#halted = false;
516
541
  this.#failing = false;
@@ -542,13 +567,23 @@ export class AdvisorRuntime {
542
567
  this.#wakeAllWaiters();
543
568
  }
544
569
 
570
+ #syncModelIdentity(): void {
571
+ const identity = this.host.getModelIdentity?.();
572
+ if (identity === undefined || identity === this.#modelIdentity) return;
573
+ this.#modelIdentity = identity;
574
+ this.#includeThinking = true;
575
+ }
576
+
545
577
  #formatRawDelta(rawMessages: AgentMessage[], wip = false): string | null {
546
578
  const delta = rawMessages
547
579
  .filter(message => !(message.role === "custom" && message.customType === "advisor"))
548
580
  .map(message => this.#dedupContextMessage(message));
549
581
  if (delta.length === 0) return null;
550
582
  const obfuscator = this.host.obfuscator;
551
- let md = formatSessionHistoryMarkdown(delta, ADVISOR_RENDER_OPTIONS);
583
+ let md = formatSessionHistoryMarkdown(delta, {
584
+ ...ADVISOR_RENDER_OPTIONS,
585
+ includeThinking: this.#includeThinking,
586
+ });
552
587
  if (!md.trim()) return null;
553
588
  if (obfuscator?.hasSecrets()) {
554
589
  let discoveredNewRegexSecretValue = false;
@@ -582,7 +617,7 @@ export class AdvisorRuntime {
582
617
  ? obfuscateAdvisorMessage(obfuscator, message, this.#advisorRegexSecretValues)
583
618
  : message,
584
619
  ),
585
- ADVISOR_RENDER_OPTIONS,
620
+ { ...ADVISOR_RENDER_OPTIONS, includeThinking: this.#includeThinking },
586
621
  );
587
622
  md = obfuscator.obfuscate(md, this.#advisorRegexSecretValues);
588
623
  }
@@ -708,6 +743,7 @@ export class AdvisorRuntime {
708
743
  epoch: number,
709
744
  initial: PendingDelta[],
710
745
  recoveringOverflow: boolean,
746
+ signal: AbortSignal,
711
747
  ): Promise<{
712
748
  batch: string | null;
713
749
  rawMessages: AgentMessage[];
@@ -724,11 +760,12 @@ export class AdvisorRuntime {
724
760
  let wip = initial.at(-1)?.wip ?? false;
725
761
 
726
762
  for (let round = 0; round < MAX_COALESCE_ROUNDS; round++) {
763
+ if (this.#sessionTransitionPaused) break;
727
764
  if (this.host.maintainContext) {
728
765
  const incomingTokens = estimateTokens({ role: "user", content: batchText, timestamp: Date.now() });
729
766
  let shouldResetContext = false;
730
767
  try {
731
- shouldResetContext = await this.host.maintainContext(incomingTokens);
768
+ shouldResetContext = await this.host.maintainContext(incomingTokens, signal);
732
769
  } catch (err) {
733
770
  logger.debug("advisor context maintenance failed", { err: String(err) });
734
771
  }
@@ -745,6 +782,7 @@ export class AdvisorRuntime {
745
782
  // remain queued and ship as their own subsequent batch.
746
783
  if (round > 0) {
747
784
  const lateItems = this.#pending.splice(0);
785
+ initial.push(...lateItems);
748
786
  turns += lateItems.reduce((sum, b) => sum + b.turns, 0);
749
787
  if (lateItems.length > 0) {
750
788
  wip = lateItems.at(-1)!.wip;
@@ -781,6 +819,7 @@ export class AdvisorRuntime {
781
819
  // update WIP state, and re-check the maintenance budget.
782
820
  const late = this.#pending.splice(0);
783
821
  if (late.length === 0) break;
822
+ initial.push(...late);
784
823
  batchText = [batchText, ...late.map(b => b.text)].join("\n\n");
785
824
  rawMessages = rawMessages.concat(late.flatMap(b => b.rawMessages));
786
825
  turns += late.reduce((sum, b) => sum + b.turns, 0);
@@ -814,10 +853,12 @@ export class AdvisorRuntime {
814
853
  }
815
854
 
816
855
  async #drain(): Promise<void> {
817
- if (this.#busy) return;
856
+ if (this.#busy || this.#sessionTransitionPaused) return;
818
857
  this.#busy = true;
819
858
  try {
820
- while (!this.disposed && this.#pending.length) {
859
+ this.#syncModelIdentity();
860
+ while (!this.disposed && !this.#sessionTransitionPaused && this.#pending.length) {
861
+ this.#syncModelIdentity();
821
862
  let popped: PendingDelta[];
822
863
  if (this.#pending[0]?.overflowRecovery) {
823
864
  const recovery = this.#pending.shift();
@@ -826,6 +867,8 @@ export class AdvisorRuntime {
826
867
  } else {
827
868
  popped = this.#pending.splice(0);
828
869
  }
870
+ const iterationAbort = new AbortController();
871
+ this.#iterationAbort = iterationAbort;
829
872
  const epoch = this.#epoch;
830
873
  for (const delta of popped) {
831
874
  if (delta.renderRevision === this.#renderRevision) continue;
@@ -834,10 +877,19 @@ export class AdvisorRuntime {
834
877
  delta.renderRevision = this.#renderRevision;
835
878
  }
836
879
  const recoveringOverflow = popped.some(delta => delta.overflowRecovery === true);
837
- const result = await this.#collectAndMaintainBatch(epoch, popped, recoveringOverflow);
880
+ const result = await this.#collectAndMaintainBatch(
881
+ epoch,
882
+ popped,
883
+ recoveringOverflow,
884
+ iterationAbort.signal,
885
+ );
838
886
 
839
887
  // Epoch was invalidated during batch collection; restart the loop.
840
888
  if (result === null) continue;
889
+ if (this.#sessionTransitionPaused) {
890
+ this.#pending.unshift(...popped);
891
+ continue;
892
+ }
841
893
 
842
894
  const { batch, rawMessages, finalTurns, wip, resetContext } = result;
843
895
 
@@ -859,7 +911,13 @@ export class AdvisorRuntime {
859
911
  // Reset the host's per-update advisor state (one-advise-per-update
860
912
  // gate) before each model cycle so the new batch starts fresh.
861
913
  this.host.beginAdvisorUpdate?.();
862
- await this.agent.prompt(batch);
914
+ const prompt = this.agent.prompt(batch);
915
+ this.#promptInFlight = prompt;
916
+ try {
917
+ await prompt;
918
+ } finally {
919
+ if (this.#promptInFlight === prompt) this.#promptInFlight = undefined;
920
+ }
863
921
  // Agent.#runLoop catches provider/stream failures internally and
864
922
  // resolves prompt() cleanly with stopReason: "error". Treat that
865
923
  // as a failed turn so endpoint rejections trip the retry path.
@@ -880,12 +938,17 @@ export class AdvisorRuntime {
880
938
  this.#consecutiveQuarantines = 0;
881
939
  if (this.host.onTurnSuccess) {
882
940
  try {
883
- await this.host.onTurnSuccess();
941
+ await raceWithSignal(Promise.resolve(this.host.onTurnSuccess()), iterationAbort.signal);
884
942
  } catch (hookErr) {
885
943
  logger.debug("advisor onTurnSuccess hook failed", { err: String(hookErr) });
886
944
  }
887
945
  }
888
946
  } catch (err) {
947
+ if (this.#sessionTransitionPaused) {
948
+ this.#rollbackFailedTurn(messageSnapshot);
949
+ this.#pending.unshift(...popped);
950
+ continue;
951
+ }
889
952
  // reset()/dispose() aborts the in-flight prompt; treat it as a
890
953
  // reset, not a transient failure — drop the stale batch.
891
954
  if (this.#epoch !== epoch) continue;
@@ -897,6 +960,9 @@ export class AdvisorRuntime {
897
960
  this.#wakeAllWaiters();
898
961
  const failedMessages = this.agent.state.messages.slice(messageSnapshot);
899
962
  const terminalFailure = this.#terminalAssistantFailure(messageSnapshot);
963
+ const classifierRefusal =
964
+ (terminalFailure !== undefined && isClassifierRefusal(terminalFailure)) ||
965
+ AIError.is(AIError.classify(err), AIError.Flag.ContentBlocked);
900
966
  const terminalFailureId =
901
967
  terminalFailure === undefined ? undefined : AIError.classifyMessage(terminalFailure);
902
968
  const contextOverflow =
@@ -912,12 +978,43 @@ export class AdvisorRuntime {
912
978
  AIError.is(terminalFailureId, AIError.Flag.ContextOverflow);
913
979
  this.#rollbackFailedTurn(messageSnapshot);
914
980
  logger.debug("advisor turn failed", { err: String(err) });
981
+ if (classifierRefusal) {
982
+ if (this.#includeThinking) {
983
+ this.#includeThinking = false;
984
+ const strippedBatch = this.#formatRawDelta(rawMessages, wip);
985
+ if (strippedBatch) {
986
+ this.#pending.unshift({
987
+ text: strippedBatch,
988
+ rawMessages,
989
+ renderRevision: this.#renderRevision,
990
+ turns: finalTurns,
991
+ wip,
992
+ overflowRecovery: recoveringOverflow || undefined,
993
+ });
994
+ logger.debug("advisor refusal recovered by stripping primary reasoning");
995
+ continue;
996
+ }
997
+ }
998
+ this.#notifyFailureOnce(err);
999
+ this.#clearSeenContext();
1000
+ this.#backlog = Math.max(0, this.#backlog - finalTurns);
1001
+ this.#notifyWaiters();
1002
+ continue;
1003
+ }
915
1004
  let recovered = false;
916
1005
  try {
917
- recovered = (await this.host.onTurnError?.(err, failedMessages)) === true;
1006
+ recovered =
1007
+ (await raceWithSignal(
1008
+ Promise.resolve(this.host.onTurnError?.(err, failedMessages, iterationAbort.signal)),
1009
+ iterationAbort.signal,
1010
+ )) === true;
918
1011
  } catch (hookErr) {
919
1012
  logger.debug("advisor onTurnError hook failed", { err: String(hookErr) });
920
1013
  }
1014
+ if (this.#sessionTransitionPaused) {
1015
+ this.#pending.unshift(...popped);
1016
+ continue;
1017
+ }
921
1018
  if (err instanceof AdvisorOutputQuarantinedError) {
922
1019
  // A quarantine discards the advisor's whole turn before dispatch, so
923
1020
  // its advice never reaches the primary. One re-prime is allowed to
@@ -1032,7 +1129,15 @@ export class AdvisorRuntime {
1032
1129
  wip,
1033
1130
  overflowRecovery: recoveringOverflow || undefined,
1034
1131
  });
1035
- await Bun.sleep(this.retryDelayMs);
1132
+ if (this.retryDelayMs <= 0) {
1133
+ await Bun.sleep(0);
1134
+ } else {
1135
+ try {
1136
+ await raceWithSignal(Bun.sleep(this.retryDelayMs), iterationAbort.signal);
1137
+ } catch (sleepError) {
1138
+ if (!iterationAbort.signal.aborted) throw sleepError;
1139
+ }
1140
+ }
1036
1141
  }
1037
1142
  }
1038
1143
  }
@@ -1043,11 +1148,20 @@ export class AdvisorRuntime {
1043
1148
  }
1044
1149
  }
1045
1150
  } finally {
1151
+ this.#iterationAbort = undefined;
1046
1152
  this.#busy = false;
1047
1153
  }
1048
1154
  }
1049
1155
  }
1050
1156
 
1157
+ /** Mirrors turn recovery's refusal classification and retains AIError's provider-neutral content-block fallback. */
1158
+ function isClassifierRefusal(message: AssistantMessage): boolean {
1159
+ if (message.stopReason !== "error") return false;
1160
+ const stopType = message.stopDetails?.type;
1161
+ if (stopType === "refusal" || stopType === "sensitive") return true;
1162
+ return AIError.is(AIError.classifyMessage(message), AIError.Flag.ContentBlocked);
1163
+ }
1164
+
1051
1165
  /**
1052
1166
  * The only malformed advisor turn shape: the prompt resolved but produced no
1053
1167
  * assistant response at all. Everything an assistant message carries — advice,