@oh-my-pi/pi-coding-agent 17.1.8 → 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 (180) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-be1f2t8h.md} +92 -0
  3. package/dist/cli.js +9986 -9776
  4. package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
  5. package/dist/types/advisor/runtime.d.ts +8 -2
  6. package/dist/types/advisor/transcript-recorder.d.ts +15 -0
  7. package/dist/types/auto-thinking/classifier.d.ts +9 -2
  8. package/dist/types/cli/args.d.ts +2 -0
  9. package/dist/types/cli/session-picker.d.ts +14 -7
  10. package/dist/types/collab/guest.d.ts +21 -2
  11. package/dist/types/commands/launch.d.ts +6 -0
  12. package/dist/types/config/settings-schema.d.ts +63 -8
  13. package/dist/types/cursor-bridge-tools.d.ts +54 -0
  14. package/dist/types/cursor.d.ts +193 -8
  15. package/dist/types/edit/edit-clipboard.d.ts +19 -0
  16. package/dist/types/edit/hashline/diff.d.ts +9 -1
  17. package/dist/types/edit/index.d.ts +8 -1
  18. package/dist/types/edit/streaming.d.ts +8 -1
  19. package/dist/types/extensibility/extensions/runner.d.ts +19 -2
  20. package/dist/types/extensibility/extensions/types.d.ts +25 -1
  21. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
  22. package/dist/types/internal-urls/local-protocol.d.ts +3 -2
  23. package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
  24. package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
  25. package/dist/types/launch/terminal-output-worker.d.ts +1 -0
  26. package/dist/types/lsp/client.d.ts +2 -0
  27. package/dist/types/main.d.ts +2 -0
  28. package/dist/types/mcp/manager.d.ts +33 -4
  29. package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
  30. package/dist/types/modes/components/read-tool-group.d.ts +12 -0
  31. package/dist/types/modes/components/session-selector.d.ts +6 -0
  32. package/dist/types/modes/components/status-line/component.d.ts +23 -0
  33. package/dist/types/modes/components/tool-execution.d.ts +16 -7
  34. package/dist/types/modes/components/usage-row.d.ts +2 -0
  35. package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
  36. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  37. package/dist/types/modes/interactive-mode.d.ts +4 -2
  38. package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
  39. package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
  40. package/dist/types/modes/theme/theme.d.ts +12 -1
  41. package/dist/types/modes/types.d.ts +2 -1
  42. package/dist/types/sdk.d.ts +1 -0
  43. package/dist/types/session/agent-session-events.d.ts +2 -0
  44. package/dist/types/session/agent-session-types.d.ts +36 -1
  45. package/dist/types/session/agent-session.d.ts +4 -2
  46. package/dist/types/session/claude-session-store.d.ts +13 -0
  47. package/dist/types/session/codex-session-store.d.ts +14 -0
  48. package/dist/types/session/credential-pin.d.ts +58 -0
  49. package/dist/types/session/foreign-session-import.d.ts +15 -0
  50. package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
  51. package/dist/types/session/foreign-session-store.d.ts +23 -0
  52. package/dist/types/session/session-advisors.d.ts +47 -2
  53. package/dist/types/session/session-entries.d.ts +19 -1
  54. package/dist/types/session/session-handoff.d.ts +4 -1
  55. package/dist/types/session/session-loader.d.ts +2 -0
  56. package/dist/types/session/session-maintenance.d.ts +1 -1
  57. package/dist/types/session/session-manager.d.ts +21 -0
  58. package/dist/types/session/session-tools.d.ts +25 -1
  59. package/dist/types/session/turn-recovery.d.ts +10 -4
  60. package/dist/types/system-prompt.d.ts +2 -1
  61. package/dist/types/task/executor.d.ts +11 -2
  62. package/dist/types/task/render.d.ts +7 -0
  63. package/dist/types/thinking.d.ts +18 -6
  64. package/dist/types/tools/acp-bridge.d.ts +28 -5
  65. package/dist/types/tools/grep.d.ts +17 -1
  66. package/dist/types/tools/hub/launch.d.ts +5 -1
  67. package/dist/types/tools/index.d.ts +4 -1
  68. package/dist/types/tools/path-utils.d.ts +19 -0
  69. package/dist/types/tools/read.d.ts +2 -0
  70. package/dist/types/tools/xdev.d.ts +2 -1
  71. package/dist/types/utils/changelog.d.ts +19 -0
  72. package/dist/types/utils/git.d.ts +10 -0
  73. package/dist/types/utils/jj.d.ts +11 -4
  74. package/dist/types/web/search/types.d.ts +2 -2
  75. package/package.json +12 -12
  76. package/src/advisor/runtime.ts +138 -12
  77. package/src/advisor/transcript-recorder.ts +56 -0
  78. package/src/auto-thinking/classifier.ts +48 -10
  79. package/src/cli/args.ts +6 -0
  80. package/src/cli/flag-tables.ts +2 -0
  81. package/src/cli/gallery-fixtures/fs.ts +25 -0
  82. package/src/cli/session-picker.ts +34 -17
  83. package/src/cli/ttsr-cli.ts +19 -1
  84. package/src/cli.ts +9 -0
  85. package/src/collab/guest.ts +37 -4
  86. package/src/collab/host.ts +1 -0
  87. package/src/commands/launch.ts +6 -0
  88. package/src/config/model-registry.ts +103 -25
  89. package/src/config/settings-schema.ts +65 -6
  90. package/src/config/settings.ts +25 -0
  91. package/src/cursor-bridge-tools.ts +81 -0
  92. package/src/cursor.ts +463 -12
  93. package/src/edit/edit-clipboard.ts +23 -0
  94. package/src/edit/hashline/diff.ts +49 -10
  95. package/src/edit/hashline/execute.ts +38 -6
  96. package/src/edit/hashline/filesystem.ts +27 -3
  97. package/src/edit/index.ts +12 -2
  98. package/src/edit/renderer.ts +4 -4
  99. package/src/edit/streaming.ts +17 -2
  100. package/src/export/html/template.js +1 -1
  101. package/src/extensibility/extensions/runner.ts +70 -0
  102. package/src/extensibility/extensions/types.ts +30 -0
  103. package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
  104. package/src/internal-urls/local-protocol.ts +3 -2
  105. package/src/internal-urls/mcp-protocol.ts +4 -1
  106. package/src/launch/terminal-output-worker-client.ts +53 -0
  107. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  108. package/src/launch/terminal-output-worker.ts +23 -0
  109. package/src/lsp/client.ts +18 -3
  110. package/src/lsp/index.ts +4 -0
  111. package/src/lsp/render.ts +1 -1
  112. package/src/main.ts +127 -56
  113. package/src/mcp/manager.ts +120 -18
  114. package/src/modes/acp/acp-agent.ts +15 -9
  115. package/src/modes/components/chat-transcript-builder.ts +30 -11
  116. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  117. package/src/modes/components/read-tool-group.ts +159 -17
  118. package/src/modes/components/session-selector.ts +19 -3
  119. package/src/modes/components/status-line/component.ts +381 -54
  120. package/src/modes/components/tool-execution.ts +78 -31
  121. package/src/modes/components/usage-row.ts +10 -5
  122. package/src/modes/controllers/event-controller.ts +26 -4
  123. package/src/modes/controllers/mcp-command-controller.ts +68 -3
  124. package/src/modes/controllers/selector-controller.ts +94 -36
  125. package/src/modes/controllers/tan-command-controller.ts +14 -0
  126. package/src/modes/interactive-mode.ts +31 -17
  127. package/src/modes/rpc/rpc-client.ts +18 -0
  128. package/src/modes/rpc/rpc-mode.ts +16 -1
  129. package/src/modes/rpc/rpc-types.ts +11 -0
  130. package/src/modes/theme/theme.ts +151 -6
  131. package/src/modes/types.ts +2 -1
  132. package/src/modes/utils/ui-helpers.ts +38 -21
  133. package/src/prompts/system/auto-thinking-difficulty.md +4 -2
  134. package/src/prompts/system/plan-mode-active.md +2 -2
  135. package/src/prompts/system/system-prompt.md +3 -0
  136. package/src/prompts/system/xdev-mount-notice.md +1 -1
  137. package/src/prompts/tools/bash.md +15 -17
  138. package/src/prompts/tools/checkpoint.md +1 -1
  139. package/src/prompts/tools/glob.md +7 -6
  140. package/src/prompts/tools/grep.md +7 -6
  141. package/src/sdk.ts +178 -15
  142. package/src/session/agent-session-events.ts +1 -0
  143. package/src/session/agent-session-types.ts +40 -1
  144. package/src/session/agent-session.ts +302 -175
  145. package/src/session/claude-session-store.ts +426 -0
  146. package/src/session/codex-session-store.ts +673 -0
  147. package/src/session/credential-pin.ts +93 -0
  148. package/src/session/foreign-session-import.ts +52 -0
  149. package/src/session/foreign-session-jsonl.ts +29 -0
  150. package/src/session/foreign-session-store.ts +26 -0
  151. package/src/session/model-controls.ts +11 -3
  152. package/src/session/session-advisors.ts +140 -16
  153. package/src/session/session-entries.ts +21 -1
  154. package/src/session/session-handoff.ts +20 -3
  155. package/src/session/session-loader.ts +25 -9
  156. package/src/session/session-maintenance.ts +59 -11
  157. package/src/session/session-manager.ts +61 -0
  158. package/src/session/session-tools.ts +107 -5
  159. package/src/session/settings-stream-fn.ts +1 -0
  160. package/src/session/turn-recovery.ts +97 -51
  161. package/src/slash-commands/builtin-registry.ts +131 -3
  162. package/src/system-prompt.ts +3 -2
  163. package/src/task/executor.ts +17 -4
  164. package/src/task/render.ts +20 -4
  165. package/src/thinking.ts +29 -10
  166. package/src/tools/acp-bridge.ts +52 -8
  167. package/src/tools/browser.ts +5 -0
  168. package/src/tools/checkpoint.ts +0 -13
  169. package/src/tools/grep.ts +64 -8
  170. package/src/tools/hub/launch.ts +10 -15
  171. package/src/tools/index.ts +28 -4
  172. package/src/tools/path-utils.ts +88 -0
  173. package/src/tools/read.ts +3 -0
  174. package/src/tools/write.ts +9 -3
  175. package/src/tools/xdev.ts +39 -14
  176. package/src/utils/changelog.ts +144 -4
  177. package/src/utils/git.ts +61 -14
  178. package/src/utils/jj.ts +19 -13
  179. package/src/web/search/providers/codex.ts +34 -0
  180. package/src/web/search/types.ts +1 -1
@@ -395,7 +395,7 @@
395
395
  }
396
396
 
397
397
  // Apply filter mode
398
- const isSettingsEntry = ['label', 'custom', 'model_change', 'thinking_level_change', 'mode_change', 'ttsr_injection', 'session_init'].includes(entry.type);
398
+ const isSettingsEntry = ['label', 'custom', 'model_change', 'thinking_level_change', 'mode_change', 'ttsr_injection', 'session_init', 'credential_pin'].includes(entry.type);
399
399
  let passesFilter = true;
400
400
 
401
401
  switch (filterMode) {
@@ -38,7 +38,7 @@ export interface AdvisorRuntimeHost {
38
38
  * recovery path must never replay the full primary transcript.
39
39
  * Optional: hosts that omit it get no proactive maintenance.
40
40
  */
41
- maintainContext?(incomingTokens: number): Promise<boolean>;
41
+ maintainContext?(incomingTokens: number, signal: AbortSignal): Promise<boolean>;
42
42
  /**
43
43
  * Called immediately before each `agent.prompt(batch)` cycle. Lets the host
44
44
  * clear per-update advisor state — currently the one-advise-per-update gate
@@ -55,7 +55,7 @@ export interface AdvisorRuntimeHost {
55
55
  * failed prompt's appended turns before rollback. Errors thrown here are
56
56
  * logged and swallowed.
57
57
  */
58
- onTurnError?(error: unknown, failedMessages: readonly AgentMessage[]): Promise<boolean | undefined> | boolean | undefined;
58
+ onTurnError?(error: unknown, failedMessages: readonly AgentMessage[], signal: AbortSignal): Promise<boolean | undefined> | boolean | undefined;
59
59
  /** Called after a successful advisor turn so the host can finish fallback lifecycle reporting. */
60
60
  onTurnSuccess?(): Promise<void> | void;
61
61
  /** Surface a non-recovering advisor failure to the host UI without adding model-visible context. */
@@ -64,6 +64,8 @@ export interface AdvisorRuntimeHost {
64
64
  * recovery (credential switch, fallback chain) declined. Cleared only by
65
65
  * an explicit reset (`/new`, config rebuild, session restart). */
66
66
  notifyQuotaExhausted?(): void;
67
+ /** Stable identity for the live advisor model. Used to restore full transcript rendering after a model switch. */
68
+ getModelIdentity?(): string;
67
69
  }
68
70
  /** Signals that an advisor response was discarded before it could become model-visible context. */
69
71
  export declare class AdvisorOutputQuarantinedError extends Error {
@@ -116,6 +118,10 @@ export declare class AdvisorRuntime {
116
118
  */
117
119
  waitForCatchup(maxMs: number, threshold: number, signal?: AbortSignal): Promise<boolean>;
118
120
  dispose(): void;
121
+ /** Stop new advisor work and wait only for the active prompt's recorder-visible events. */
122
+ pauseForSessionTransition(): Promise<void>;
123
+ /** Continue queued work after a session transition rolls back or preserves the conversation. */
124
+ resumeAfterSessionTransition(): void;
119
125
  /**
120
126
  * Re-prime the advisor after a history rewrite (compaction, session
121
127
  * switch/resume, branch). Clears the advisor's own (non-persisted) context
@@ -14,6 +14,21 @@ export declare const ADVISOR_TRANSCRIPT_FILENAME = "__advisor.jsonl";
14
14
  export declare function advisorTranscriptFilename(slug: string): string;
15
15
  /** Whether a filename is any advisor transcript (`__advisor.jsonl` or `__advisor.<slug>.jsonl`). */
16
16
  export declare function isAdvisorTranscriptName(name: string): boolean;
17
+ /**
18
+ * Sum the advisor spend already persisted next to a primary session transcript,
19
+ * keyed by advisor slug.
20
+ *
21
+ * The ledger a session keeps in memory only covers the current process, so a
22
+ * resumed session would report zero until the next advisor turn. The recorded
23
+ * transcripts are the durable copy of exactly the same finalized messages, so
24
+ * they are read back through the shared loader - no lock, no writer, and no
25
+ * second parser to keep in step with the session format.
26
+ *
27
+ * Only the session's own advisors count: subagent advisors write to
28
+ * `<session>/<SubId>/__advisor.jsonl`, and their spend belongs to the subagent,
29
+ * not to this roster. Hence the scan stays at the top level of the directory.
30
+ */
31
+ export declare function loadAdvisorTranscriptCosts(sessionFile: string | undefined): Promise<Map<string, number>>;
17
32
  /**
18
33
  * Append-only persister for an advisor agent's transcript.
19
34
  *
@@ -5,7 +5,8 @@
5
5
  * {@link Effort}, clamped into the active model's supported range (never below
6
6
  * {@link Effort.Low}). Two backends, selected by `providers.autoThinkingModel`:
7
7
  *
8
- * - `online` (default): a smol model classifies into `low|medium|high|xhigh`.
8
+ * - `online` (default): a smol model classifies into `low|medium|high|xhigh`,
9
+ * plus `max` when the target model exposes that tier.
9
10
  * - a local key: an on-device memory model classifies into the coarser
10
11
  * `trivial|moderate|hard` scheme (3-class is more reliable than 4-way ordinal
11
12
  * on sub-2B models), mapped to `low|high|xhigh`.
@@ -31,7 +32,13 @@ export interface ClassifyDifficultyDeps {
31
32
  * @throws when the backend cannot produce a usable classification.
32
33
  */
33
34
  export declare function classifyDifficulty(promptText: string, deps: ClassifyDifficultyDeps): Promise<Effort | undefined>;
34
- /** Map the online 4-way level keyword to an {@link Effort}; earliest match wins. */
35
+ /**
36
+ * Map an online level keyword to an {@link Effort}; earliest match wins.
37
+ *
38
+ * `max` is only offered to the classifier when the target model exposes that
39
+ * tier, but it is always parsed: an unsupported `max` is snapped back down by
40
+ * {@link clampAutoThinkingEffort} rather than failing the turn.
41
+ */
35
42
  export declare function parseDifficultyLevel(text: string): Effort | undefined;
36
43
  /** Map the local 3-way bucket keyword to an {@link Effort}; earliest match wins. */
37
44
  export declare function parseDifficultyBucket(text: string): Effort | undefined;
@@ -27,6 +27,8 @@ export interface Args {
27
27
  advisor?: boolean;
28
28
  continue?: boolean;
29
29
  resume?: string | true;
30
+ fromClaude?: boolean;
31
+ fromCodex?: boolean;
30
32
  help?: boolean;
31
33
  version?: boolean;
32
34
  mode?: Mode;
@@ -1,11 +1,18 @@
1
1
  import type { SessionInfo } from "../session/session-listing.js";
2
+ /** Presentation and capability controls for the standalone session picker. */
3
+ export interface SessionPickerOptions {
4
+ allSessions?: SessionInfo[];
5
+ title?: string;
6
+ scopeLabel?: string | false;
7
+ showCwd?: boolean;
8
+ allowDelete?: boolean;
9
+ allowGlobalScope?: boolean;
10
+ historySearch?: boolean;
11
+ }
2
12
  /**
3
13
  * Show the TUI session selector and return the selected session, or null if
4
- * cancelled. Rendered as a fullscreen overlay on the terminal's alternate
5
- * screen, so the list scrolls and rows are clickable with the mouse. Tab
6
- * toggles between current-folder and all-projects scope; the all-projects list
7
- * is loaded lazily via `SessionManager.listAll`.
14
+ * cancelled. The default OMP picker supports deletion, transcript-history
15
+ * search, and an all-projects scope; foreign import pickers disable those
16
+ * source-owned capabilities.
8
17
  */
9
- export declare function selectSession(sessions: SessionInfo[], options?: {
10
- allSessions?: SessionInfo[];
11
- }): Promise<SessionInfo | null>;
18
+ export declare function selectSession(sessions: SessionInfo[], options?: SessionPickerOptions): Promise<SessionInfo | null>;
@@ -10,6 +10,7 @@ export interface GuestIdleReconcilerCtx {
10
10
  statusLine: {
11
11
  markActivityEnd: () => void;
12
12
  };
13
+ statusContainer: Pick<InteractiveModeContext["statusContainer"], "disposeChildren">;
13
14
  loadingAnimation: {
14
15
  stop: () => void;
15
16
  } | undefined;
@@ -17,8 +18,8 @@ export interface GuestIdleReconcilerCtx {
17
18
  /**
18
19
  * Close the guest UI state held open by an earlier `agent_start` whose
19
20
  * matching `agent_end` never reached us — most often because a reconnect
20
- * dropped the event mid-stream. Triggered from {@link CollabGuestLink}'s
21
- * `state` reconciler when the host reports `isStreaming === false`:
21
+ * dropped the event mid-stream. Reached via {@link reconcileGuestSnapshotHostState}
22
+ * (the live `state`-frame and welcome/resync reconciler) when the host reports `isStreaming === false`:
22
23
  * folds the in-flight active-time window into the per-session meter (so
23
24
  * `time_spent` stops ticking) and stops the `Working…` loader if one is
24
25
  * still animating. No-op when the host is still streaming.
@@ -32,7 +33,25 @@ export interface GuestSnapshotActivityReconcilerCtx extends GuestIdleReconcilerC
32
33
  statusLine: GuestIdleReconcilerCtx["statusLine"] & {
33
34
  markActivityStart: () => void;
34
35
  };
36
+ /**
37
+ * Start (or re-attach) the live "Working…" loader. Mirrors
38
+ * `InteractiveModeContext.ensureLoadingAnimation`, which is what
39
+ * `EventController` calls on `agent_start`. Required so a guest that
40
+ * missed an earlier `agent_start` (a reconnect dropped it mid-stream)
41
+ * starts its spinner when the host later reports it is streaming.
42
+ */
43
+ ensureLoadingAnimation: InteractiveModeContext["ensureLoadingAnimation"];
44
+ autoCompactionLoader: InteractiveModeContext["autoCompactionLoader"];
45
+ retryLoader: InteractiveModeContext["retryLoader"];
35
46
  }
47
+ /** Status-area state which cannot outlive removal of its child components. */
48
+ export interface GuestTransientStatusCtx {
49
+ statusContainer: Pick<InteractiveModeContext["statusContainer"], "clear">;
50
+ autoCompactionLoader: InteractiveModeContext["autoCompactionLoader"];
51
+ retryLoader: InteractiveModeContext["retryLoader"];
52
+ }
53
+ /** Stop and forget status-area loaders before detaching their components. */
54
+ export declare function clearGuestTransientStatus(ctx: GuestTransientStatusCtx): void;
36
55
  export declare function reconcileGuestSnapshotHostState(ctx: GuestSnapshotActivityReconcilerCtx, isStreaming: boolean): void;
37
56
  export declare class CollabGuestLink {
38
57
  #private;
@@ -88,6 +88,12 @@ export default class Index extends Command {
88
88
  char: string;
89
89
  description: string;
90
90
  };
91
+ "from-claude": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
92
+ description: string;
93
+ };
94
+ "from-codex": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
95
+ description: string;
96
+ };
91
97
  "session-dir": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
92
98
  description: string;
93
99
  };
@@ -839,6 +839,16 @@ export declare const SETTINGS_SCHEMA: {
839
839
  readonly description: "Render Mermaid fenced code blocks as ASCII diagrams";
840
840
  };
841
841
  };
842
+ readonly "tui.codexResetFireworks": {
843
+ readonly type: "boolean";
844
+ readonly default: false;
845
+ readonly ui: {
846
+ readonly tab: "appearance";
847
+ readonly group: "Display";
848
+ readonly label: "Codex Reset Fireworks";
849
+ readonly description: "Celebrate unscheduled Codex weekly usage resets and newly banked saved resets with a top-third fireworks overlay that remains until Escape";
850
+ };
851
+ };
842
852
  readonly "tui.titleState": {
843
853
  readonly type: "boolean";
844
854
  readonly default: true;
@@ -1796,14 +1806,28 @@ export declare const SETTINGS_SCHEMA: {
1796
1806
  }];
1797
1807
  };
1798
1808
  };
1799
- readonly collapseChangelog: {
1800
- readonly type: "boolean";
1801
- readonly default: false;
1809
+ readonly "startup.changelogMode": {
1810
+ readonly type: "enum";
1811
+ readonly values: readonly ["summary", "expanded", "hidden"];
1812
+ readonly default: "summary";
1802
1813
  readonly ui: {
1803
1814
  readonly tab: "interaction";
1804
1815
  readonly group: "Startup & Updates";
1805
- readonly label: "Collapse Changelog";
1806
- readonly description: "Show condensed changelog after updates";
1816
+ readonly label: "Startup Changelog";
1817
+ readonly description: "Choose whether update notes start as a summary, full details, or stay hidden";
1818
+ readonly options: readonly [{
1819
+ readonly value: "summary";
1820
+ readonly label: "Summary";
1821
+ readonly description: "Show release and change counts with a /changelog hint";
1822
+ }, {
1823
+ readonly value: "expanded";
1824
+ readonly label: "Expanded";
1825
+ readonly description: "Show the recent release notes in full";
1826
+ }, {
1827
+ readonly value: "hidden";
1828
+ readonly label: "Hidden";
1829
+ readonly description: "Do not show release notes on startup";
1830
+ }];
1807
1831
  };
1808
1832
  };
1809
1833
  readonly "magicKeywords.enabled": {
@@ -4147,6 +4171,16 @@ export declare const SETTINGS_SCHEMA: {
4147
4171
  readonly description: "Enable the browser tool for scripted Chromium automation (puppeteer)";
4148
4172
  };
4149
4173
  };
4174
+ readonly "browser.cdpUrl": {
4175
+ readonly type: "string";
4176
+ readonly default: undefined;
4177
+ readonly ui: {
4178
+ readonly tab: "tools";
4179
+ readonly group: "Grep & Browser";
4180
+ readonly label: "Browser CDP URL";
4181
+ readonly description: "Default HTTP CDP discovery endpoint (for example http://127.0.0.1:9222) to attach to instead of launching a browser. Explicit app.cdp_url or app.path on the tool call take precedence.";
4182
+ };
4183
+ };
4150
4184
  readonly "browser.headless": {
4151
4185
  readonly type: "boolean";
4152
4186
  readonly default: true;
@@ -4717,7 +4751,7 @@ export declare const SETTINGS_SCHEMA: {
4717
4751
  readonly tab: "tasks";
4718
4752
  readonly group: "Subagents";
4719
4753
  readonly label: "Soft Subagent Request Budget";
4720
- readonly description: "Soft per-subagent request budget (assistant requests per run). Crossing it injects a wrap-up steering notice (see task.softRequestBudgetNotice); at 1.5x the budget the run is force-stopped and the agent must yield its partial findings. 0 disables the guard. Bundled scout/sonic agents use a lower built-in budget.";
4754
+ readonly description: "Soft per-subagent request budget (assistant requests per run). Crossing it injects a wrap-up steering notice (see task.softRequestBudgetNotice); at 1.5x the budget the run is force-stopped and the agent must yield its partial findings. 0 disables the guard. Bundled scout/sonic agents cap out at a lower built-in budget, so a value below that cap still applies to them.";
4721
4755
  readonly options: readonly [{
4722
4756
  readonly value: "0";
4723
4757
  readonly label: "Disabled";
@@ -4970,7 +5004,7 @@ export declare const SETTINGS_SCHEMA: {
4970
5004
  } | {
4971
5005
  readonly value: "exa";
4972
5006
  readonly label: "Exa";
4973
- readonly description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP";
5007
+ readonly description: "API via /login exa or EXA_API_KEY; explicit keyless fallback via MCP";
4974
5008
  } | {
4975
5009
  readonly value: "tinyfish";
4976
5010
  readonly label: "TinyFish";
@@ -5074,7 +5108,7 @@ export declare const SETTINGS_SCHEMA: {
5074
5108
  } | {
5075
5109
  readonly value: "exa";
5076
5110
  readonly label: "Exa";
5077
- readonly description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP";
5111
+ readonly description: "API via /login exa or EXA_API_KEY; explicit keyless fallback via MCP";
5078
5112
  } | {
5079
5113
  readonly value: "tinyfish";
5080
5114
  readonly label: "TinyFish";
@@ -5580,6 +5614,27 @@ export declare const SETTINGS_SCHEMA: {
5580
5614
  })[];
5581
5615
  };
5582
5616
  };
5617
+ readonly "providers.autoThinkingMaxEffort": {
5618
+ readonly type: "enum";
5619
+ readonly values: readonly ["xhigh", "max"];
5620
+ readonly default: "xhigh";
5621
+ readonly ui: {
5622
+ readonly tab: "model";
5623
+ readonly group: "Thinking";
5624
+ readonly label: "Auto Thinking Ceiling";
5625
+ readonly description: "Highest effort the `auto` classifier may resolve. `xhigh` keeps the classifier one tier below the top, so only an explicit `ultrathink` reaches `max`; `max` lets a turn the classifier judges exceptional bill the top tier on models that expose it.";
5626
+ readonly condition: "autoThinkingActive";
5627
+ readonly options: readonly [{
5628
+ readonly value: "xhigh";
5629
+ readonly label: "xhigh";
5630
+ readonly description: "Classifier stops at xhigh (default)";
5631
+ }, {
5632
+ readonly value: "max";
5633
+ readonly label: "max";
5634
+ readonly description: "Classifier may resolve max where the model supports it";
5635
+ }];
5636
+ };
5637
+ };
5583
5638
  readonly "features.unexpectedStopDetection": {
5584
5639
  readonly type: "boolean";
5585
5640
  readonly default: false;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Per-call tools the Cursor exec bridge needs but the model-facing registry
3
+ * cannot supply.
4
+ *
5
+ * Both bridge callsites — the primary session and the advisor roster — build
6
+ * the same instances, and both must apply the session's approval wrapper. A
7
+ * raw tool here silently escapes the gate every registry call goes through, so
8
+ * the construction lives in one place rather than being repeated per callsite.
9
+ */
10
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
11
+ import type { ExtensionRunner } from "./extensibility/extensions/index.js";
12
+ import type { GrepToolOptions, ToolSession } from "./tools/index.js";
13
+ /**
14
+ * Build the bridge's `createGrepTool` factory for one tool session.
15
+ *
16
+ * A `pi_grep` frame carries its own context width and total match cap. Neither
17
+ * is expressible in the model-facing `grep` schema — context comes from
18
+ * `grep.contextBefore`/`grep.contextAfter`, fixed when the shared instance is
19
+ * constructed — so honoring them needs a fresh tool per call.
20
+ *
21
+ * The result is wrapped exactly like a registry tool: the approval gate runs on
22
+ * every call site, and a per-call instance is no exception.
23
+ */
24
+ export declare function createBridgeGrepFactory(session: ToolSession, extensionRunner: ExtensionRunner): (options: GrepToolOptions) => AgentTool;
25
+ /**
26
+ * Build the `replace`-mode `edit` the bridge answers `pi_edit` with.
27
+ *
28
+ * `PiEditExecArgs` carries `old_text`/`new_text` pairs, which is exactly
29
+ * `replace`'s schema and nothing else's. The session's own instance follows the
30
+ * configured `edit.mode` — `hashline` by default, whose schema is a single
31
+ * `input` string — so a frame handed that instance fails validation instead of
32
+ * editing the file.
33
+ *
34
+ * Callers MUST gate this on the session having actually granted `edit`: the
35
+ * tool is constructed rather than looked up, so building one unconditionally
36
+ * hands a restricted agent a mutating tool it was denied (issue #5680).
37
+ */
38
+ export declare function createBridgeEditTool(session: ToolSession, extensionRunner: ExtensionRunner): AgentTool;
39
+ /**
40
+ * The tool map the exec bridge should run, given the map a caller granted.
41
+ *
42
+ * `pi_edit` needs a `replace`-mode instance, but only when `edit` was granted:
43
+ * the tool is constructed rather than looked up, so substituting
44
+ * unconditionally would hand a restricted roster a mutating tool it was denied
45
+ * (issue #5680). The granted map is never mutated: an unsubstituted result is a
46
+ * copy, so a caller without an `edit` grant cannot accidentally gain one.
47
+ *
48
+ * The advisor roster passes its granted map here; the primary session keeps its
49
+ * instance out of the registry entirely (Cursor does not advertise `edit`) and
50
+ * serves it through the bridge's `getEditReplaceTool` accessor instead — not
51
+ * the `getTool` fallback, which doubles as the agent loop's resolver for
52
+ * unadvertised calls and must stay device-only.
53
+ */
54
+ export declare function bridgeToolMap(granted: ReadonlyMap<string, AgentTool>, createEditTool: (() => AgentTool | undefined) | undefined): Map<string, AgentTool>;
@@ -1,23 +1,69 @@
1
1
  import type { AgentEvent, AgentTool, AgentToolContext } from "@oh-my-pi/pi-agent-core";
2
- import type { CursorMcpCall, CursorShellStreamCallbacks, CursorTodoSnapshot, CursorExecHandlers as ICursorExecHandlers, ToolResultMessage } from "@oh-my-pi/pi-ai";
2
+ import type { CursorMcpCall, CursorMcpResource, CursorMcpResourceContent, CursorShellStreamCallbacks, CursorTodoSnapshot, CursorExecHandlers as ICursorExecHandlers, ToolResultMessage } from "@oh-my-pi/pi-ai";
3
+ import type { MCPResourceReadResult } from "./mcp/types.js";
3
4
  import type { TodoPhase } from "./tools/todo.js";
5
+ /**
6
+ * A tool instance the bridge can run, matching the erased shape the session's
7
+ * tool registry stores. The concrete tools have narrower `execute` parameter
8
+ * types than the default `AgentTool`, which only unify through this alias.
9
+ */
10
+ type CursorBridgeTool = AgentTool<any, any, any>;
11
+ /**
12
+ * The live MCP connections Cursor's resource frames are answered from.
13
+ *
14
+ * Named so every construction site can hand over the same adapter; a session
15
+ * and its advisors share one set of connections.
16
+ */
17
+ export interface CursorMcpResourceAdapter {
18
+ serverNames(): string[];
19
+ getServerResources(name: string): Promise<{
20
+ resources: {
21
+ uri: string;
22
+ name?: string;
23
+ description?: string;
24
+ mimeType?: string;
25
+ }[];
26
+ } | undefined>;
27
+ readServerResource(name: string, uri: string): Promise<MCPResourceReadResult | undefined>;
28
+ }
4
29
  interface CursorExecBridgeOptions {
5
30
  cwd: string;
6
31
  getCwd?: () => string;
7
32
  tools: Map<string, AgentTool>;
8
33
  /** Resolves execution overrides (mounted-device permission wrappers) before the canonical map. */
9
34
  getExecutableTool?: (name: string) => AgentTool | undefined;
35
+ /**
36
+ * The `replace`-mode `edit` instance `pi_edit` must run, when the session
37
+ * granted `edit` at all.
38
+ *
39
+ * `PiEditExecArgs` is that mode's schema verbatim, and the session's own
40
+ * `edit` may be in any mode — `hashline` by default — whose schema rejects
41
+ * `old_text`/`new_text` outright. {@link tools} therefore cannot be trusted
42
+ * for this one frame: a session that starts on another provider keeps its
43
+ * configured instance in the map (only Cursor sessions move `edit` out), and
44
+ * switching to Cursor later does not rebuild the roster.
45
+ */
46
+ getEditReplaceTool?: () => CursorBridgeTool | undefined;
10
47
  getToolContext?: () => AgentToolContext | undefined;
11
48
  emitEvent?: (event: AgentEvent) => void;
12
49
  /**
13
- * Whether the Cursor native `delete` frame may remove files. Unlike every
14
- * other exec handler, `executeDelete` mutates the filesystem directly instead
15
- * of consulting {@link tools}, so a background read-only advisor could delete
16
- * workspace files it was never granted a mutating tool for (issue #5680
17
- * review). Defaults to allowed to preserve the primary agent's behavior;
18
- * callers with a restricted tool set (advisors) opt out.
50
+ * Whether frames that mutate the filesystem WITHOUT running a registry tool
51
+ * may do so: the native `delete` frame, and a `read_mcp_resource` carrying
52
+ * `download_path`. Both write or remove workspace files directly instead of
53
+ * consulting {@link tools}, so a background read-only advisor could touch
54
+ * files it was never granted a mutating tool for (issue #5680 review).
55
+ *
56
+ * This is a grant, not a policy: it answers "did the session hand this
57
+ * channel a file-writing tool", which callers derive from their own roster
58
+ * before any bridge-specific rewriting. The primary Cursor session moves
59
+ * `edit` out of {@link tools} and serves it through
60
+ * {@link getEditReplaceTool}, so reading the map here would deny an
61
+ * edit-only session. Defaults to allowed
62
+ * to preserve the primary agent's behavior; callers with a restricted tool
63
+ * set (advisors) opt out. The user's approval policy is resolved separately,
64
+ * per call.
19
65
  */
20
- allowNativeDelete?: boolean;
66
+ allowDirectFileMutation?: boolean;
21
67
  /**
22
68
  * Mirror Cursor's server-owned todo list into local session state. Cursor
23
69
  * resolves `update_todos` / `read_todos` remotely, so without this bridge
@@ -30,18 +76,148 @@ interface CursorExecBridgeOptions {
30
76
  * Cursor emits no local `todo` toolResult, so nothing else records it.
31
77
  */
32
78
  persistTodoPhases?: (phases: TodoPhase[]) => void;
79
+ /**
80
+ * Build a `grep` tool honoring a frame's own context width and match cap.
81
+ *
82
+ * The modern `pi_grep` frame carries both, and the shared `grep` instance
83
+ * is fixed to the session settings at construction — so without this the
84
+ * two fields are silently dropped. Callers that cannot supply it keep the
85
+ * shared instance and the session's defaults.
86
+ *
87
+ * The returned tool is executed as-is. Callers whose registry tools carry an
88
+ * approval wrapper MUST apply the same wrapper here, or a frame supplying
89
+ * either field silently escapes the approval gate that every other call
90
+ * goes through.
91
+ */
92
+ createGrepTool?(options: {
93
+ context?: number;
94
+ totalMatchLimit?: number;
95
+ }): CursorBridgeTool | undefined;
96
+ /**
97
+ * The session's live MCP connections, for Cursor's resource frames.
98
+ *
99
+ * `list_mcp_resources` / `read_mcp_resource` ask what this client's servers
100
+ * advertise. Without this the bridge answers an empty catalog and
101
+ * `not_found`, hiding resources the session is in fact connected to.
102
+ *
103
+ * `getServerResources` is async because a server's catalog loads in the
104
+ * background after its tools register: a frame arriving in that window would
105
+ * otherwise read the not-yet-populated cache and report an empty catalog,
106
+ * which is indistinguishable from a server that advertises nothing.
107
+ */
108
+ mcpResources?: CursorMcpResourceAdapter;
33
109
  }
34
110
  export declare class CursorExecHandlers implements ICursorExecHandlers {
35
111
  private options;
36
112
  constructor(options: CursorExecBridgeOptions);
113
+ /**
114
+ * Modern Cursor builds paginate the legacy `read` frame with
115
+ * `offset`/`limit`, exactly as `pi_read` does. Dropping them returns the
116
+ * whole file (or its own truncation) for every page, so a model walking a
117
+ * large file never advances. Composed with the same helper, so both frames
118
+ * translate a range identically.
119
+ */
37
120
  read(args: Parameters<NonNullable<ICursorExecHandlers["read"]>>[0]): Promise<ToolResultMessage<unknown>>;
38
121
  ls(args: Parameters<NonNullable<ICursorExecHandlers["ls"]>>[0]): Promise<ToolResultMessage<unknown>>;
122
+ /**
123
+ * Modern Cursor builds paginate this frame with `offset`. The local `grep`
124
+ * paginates by file through `skip`, which is the same unit its own
125
+ * "use skip=N for the next page" advice counts in — so an unforwarded
126
+ * offset re-runs the identical search and returns page one forever.
127
+ */
39
128
  grep(args: Parameters<NonNullable<ICursorExecHandlers["grep"]>>[0]): Promise<ToolResultMessage<unknown>>;
40
129
  write(args: Parameters<NonNullable<ICursorExecHandlers["write"]>>[0]): Promise<ToolResultMessage<unknown>>;
41
130
  delete(args: Parameters<NonNullable<ICursorExecHandlers["delete"]>>[0]): Promise<ToolResultMessage<unknown>>;
42
131
  shell(args: Parameters<NonNullable<ICursorExecHandlers["shell"]>>[0]): Promise<ToolResultMessage<unknown>>;
43
132
  shellStream(args: Parameters<NonNullable<ICursorExecHandlers["shellStream"]>>[0], callbacks: CursorShellStreamCallbacks): Promise<ToolResultMessage<unknown>>;
44
133
  diagnostics(args: Parameters<NonNullable<ICursorExecHandlers["diagnostics"]>>[0]): Promise<ToolResultMessage<unknown>>;
134
+ /**
135
+ * Modern Cursor CLI Pi tool frames (`ExecServerMessage` 45-51).
136
+ *
137
+ * These are a separate frame family from the legacy `read`/`shell`/... set,
138
+ * not aliases: different args, different result oneofs, and no `tool_call_id`
139
+ * (the provider mints one and passes it in `call.toolCallId`). Each maps onto
140
+ * the local tool with matching semantics, so the same approval, sandboxing
141
+ * and event plumbing applies as for a model-issued call.
142
+ */
143
+ /**
144
+ * `offset`/`limit` are a 1-indexed start line plus a line count (verified
145
+ * against the reference `LocalPiReadExecutor`), which is exactly the local
146
+ * `read` tool's `:N+K` inline selector — the tool takes no range kwargs, so
147
+ * the range has to be composed onto the path or ranged reads silently
148
+ * return the whole file.
149
+ */
150
+ piRead(call: Parameters<NonNullable<ICursorExecHandlers["piRead"]>>[0]): Promise<ToolResultMessage<unknown>>;
151
+ piBash(call: Parameters<NonNullable<ICursorExecHandlers["piBash"]>>[0]): Promise<ToolResultMessage<unknown>>;
152
+ /**
153
+ * `PiEditExecArgs` is the local `edit` tool's replace mode verbatim: a path
154
+ * plus `old_text`/`new_text` pairs. The tool's schema is snake_case, so the
155
+ * proto's camelCase accessors are mapped back on the way in.
156
+ *
157
+ * The replace-mode instance is requested explicitly rather than resolved
158
+ * from {@link CursorExecBridgeOptions.tools}: the registry's `edit` is in
159
+ * the session's configured mode, whose schema rejects these arguments.
160
+ */
161
+ piEdit(call: Parameters<NonNullable<ICursorExecHandlers["piEdit"]>>[0]): Promise<ToolResultMessage<unknown>>;
162
+ piWrite(call: Parameters<NonNullable<ICursorExecHandlers["piWrite"]>>[0]): Promise<ToolResultMessage<unknown>>;
163
+ /**
164
+ * `literal` makes the pattern a fixed string; the local tool is regex-only,
165
+ * so the pattern is escaped on the way in (same translation the legacy pi
166
+ * shim does).
167
+ *
168
+ * `context` and `limit` are not expressible in the model-facing schema —
169
+ * context width comes from settings fixed at tool construction — so the
170
+ * frame's values are honored by building a per-call `grep` through
171
+ * {@link CursorExecBridgeOptions.createGrepTool}. Both are `optional int32`,
172
+ * so a present `0` context means "no context lines", not "use the default".
173
+ * Without the factory the shared instance runs with session defaults.
174
+ */
175
+ piGrep(call: Parameters<NonNullable<ICursorExecHandlers["piGrep"]>>[0]): Promise<ToolResultMessage<unknown>>;
176
+ /**
177
+ * `pi_find` is a filename search, which is the local `glob` tool — not
178
+ * `grep`. Its `pattern` is a glob, joined onto `path` because `glob` takes a
179
+ * single combined path spec.
180
+ *
181
+ * `limit` is `optional int32`, so `0` is present rather than unset; the
182
+ * reference clamps it with `Math.max(1, limit ?? 1000)`, and an unset limit
183
+ * leaves the local tool's own default in place.
184
+ */
185
+ piFind(call: Parameters<NonNullable<ICursorExecHandlers["piFind"]>>[0]): Promise<ToolResultMessage<unknown>>;
186
+ /**
187
+ * Redirected to `read`, which lists directories — same as the legacy `ls`.
188
+ * The frame's entry `limit` is not mapped; see {@link piLsPath}.
189
+ */
190
+ piLs(call: Parameters<NonNullable<ICursorExecHandlers["piLs"]>>[0]): Promise<ToolResultMessage<unknown>>;
191
+ /**
192
+ * The resources this client's MCP servers advertise.
193
+ *
194
+ * Cursor addresses a later read by `server`, so every entry carries the name
195
+ * it came from. An absent `server` filter means "all of them".
196
+ */
197
+ listMcpResources({ server }: {
198
+ server?: string;
199
+ }): Promise<CursorMcpResource[]>;
200
+ /**
201
+ * Read one resource, or `null` when the server or uri is unknown.
202
+ *
203
+ * MCP returns a list of content items; the wire carries exactly one text or
204
+ * blob. Text items are joined, since a multi-part text resource is one
205
+ * document; otherwise the first blob stands in. `blob` arrives base64 and
206
+ * the wire wants bytes.
207
+ *
208
+ * A `downloadPath` frame is a different contract: write the bytes to that
209
+ * workspace-relative path and answer with the path alone, so a large binary
210
+ * lands on disk instead of in the model's context. That makes it a workspace
211
+ * mutation reached without a registry tool, so it is gated exactly like the
212
+ * native `delete` frame — on the session actually granting a file-writing
213
+ * tool, and on the user's `write`-tier policy. The gate runs before the read
214
+ * so a refused download never fetches the resource either.
215
+ */
216
+ readMcpResource({ server, uri, downloadPath, }: {
217
+ server: string;
218
+ uri: string;
219
+ downloadPath?: string;
220
+ }): Promise<CursorMcpResourceContent | null>;
45
221
  /**
46
222
  * Settle a completed native Cursor todo call, mirroring its list when the
47
223
  * server supplied an authoritative one.
@@ -76,5 +252,14 @@ export declare class CursorExecHandlers implements ICursorExecHandlers {
76
252
  */
77
253
  todoSync(snapshot: CursorTodoSnapshot | null, toolCallId: string, error?: string | null): ToolResultMessage;
78
254
  mcp(call: CursorMcpCall): Promise<ToolResultMessage<unknown>>;
255
+ /**
256
+ * Resolve an MCP call's approval without running it.
257
+ *
258
+ * Same resolution the wrapper applies at execution time, minus the
259
+ * execution: an unknown tool is not approvable, and a `prompt` is not an
260
+ * approval — the frame has no way to carry an interactive question, and the
261
+ * user is asked for real when the call itself arrives.
262
+ */
263
+ mcpApprovalPreflight(call: CursorMcpCall): Promise<boolean>;
79
264
  }
80
265
  export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Session-bound edit clipboard register.
3
+ *
4
+ * `CUT`/`PASTE` hashline ops share one {@link Clipboard} per session so
5
+ * content captured by one edit call can be pasted by a later one (and across
6
+ * files within one call). The executor works on a fork per batch and commits
7
+ * it back only after every write lands — see `executeHashlineSingle`.
8
+ */
9
+ import type { Clipboard } from "@oh-my-pi/hashline";
10
+ interface EditClipboardOwner {
11
+ editClipboard?: Clipboard;
12
+ }
13
+ /**
14
+ * Look up (or lazily create) the clipboard register attached to a session.
15
+ * Storage lives on `session.editClipboard` so it ages out exactly with the
16
+ * session itself.
17
+ */
18
+ export declare function getEditClipboard(session: EditClipboardOwner): Clipboard;
19
+ export {};