@oh-my-pi/pi-coding-agent 16.1.23 → 16.2.1

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 (260) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/cli.js +3785 -3695
  3. package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
  4. package/dist/types/advisor/emission-guard.d.ts +73 -0
  5. package/dist/types/advisor/index.d.ts +1 -0
  6. package/dist/types/advisor/runtime.d.ts +7 -0
  7. package/dist/types/advisor/watchdog.d.ts +13 -0
  8. package/dist/types/cli/flag-tables.d.ts +1 -0
  9. package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
  10. package/dist/types/collab/display-name.d.ts +3 -0
  11. package/dist/types/collab/host.d.ts +0 -2
  12. package/dist/types/commands/worktree.d.ts +0 -3
  13. package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
  14. package/dist/types/config/model-discovery.d.ts +6 -1
  15. package/dist/types/config/model-registry.d.ts +6 -2
  16. package/dist/types/config/model-resolver.d.ts +12 -0
  17. package/dist/types/config/models-config-schema.d.ts +29 -2
  18. package/dist/types/config/models-config.d.ts +22 -1
  19. package/dist/types/config/settings-schema.d.ts +91 -21
  20. package/dist/types/config/settings.d.ts +2 -0
  21. package/dist/types/dap/config.d.ts +1 -1
  22. package/dist/types/edit/hashline/filesystem.d.ts +4 -2
  23. package/dist/types/edit/renderer.d.ts +4 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -17
  25. package/dist/types/extensibility/hooks/types.d.ts +10 -10
  26. package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
  27. package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
  28. package/dist/types/internal-urls/index.d.ts +1 -0
  29. package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
  30. package/dist/types/internal-urls/router.d.ts +1 -1
  31. package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
  32. package/dist/types/internal-urls/types.d.ts +19 -2
  33. package/dist/types/irc/bus.d.ts +6 -0
  34. package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -0
  35. package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
  36. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  37. package/dist/types/modes/components/move-overlay.d.ts +23 -0
  38. package/dist/types/modes/components/plugin-selector.d.ts +2 -1
  39. package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
  40. package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
  41. package/dist/types/modes/components/settings-defs.d.ts +4 -1
  42. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  43. package/dist/types/modes/components/show-images-selector.d.ts +2 -1
  44. package/dist/types/modes/components/status-line/component.d.ts +2 -0
  45. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  46. package/dist/types/modes/components/theme-selector.d.ts +2 -1
  47. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  48. package/dist/types/modes/controllers/command-controller.d.ts +10 -1
  49. package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
  50. package/dist/types/modes/interactive-mode.d.ts +10 -1
  51. package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
  52. package/dist/types/modes/running-subagent-badge.d.ts +6 -0
  53. package/dist/types/modes/theme/theme.d.ts +1 -1
  54. package/dist/types/modes/types.d.ts +9 -1
  55. package/dist/types/sdk.d.ts +2 -2
  56. package/dist/types/session/agent-session.d.ts +8 -1
  57. package/dist/types/session/blob-store.d.ts +4 -0
  58. package/dist/types/session/session-manager.d.ts +13 -0
  59. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  60. package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
  61. package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
  62. package/dist/types/ssh/connection-manager.d.ts +2 -0
  63. package/dist/types/ssh/file-transfer.d.ts +79 -0
  64. package/dist/types/ssh/utils.d.ts +6 -0
  65. package/dist/types/system-prompt.d.ts +3 -0
  66. package/dist/types/task/executor.d.ts +16 -0
  67. package/dist/types/tiny/text.d.ts +1 -1
  68. package/dist/types/tools/builtin-names.d.ts +5 -1
  69. package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
  70. package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
  71. package/dist/types/tools/index.d.ts +3 -3
  72. package/dist/types/tools/path-utils.d.ts +29 -0
  73. package/dist/types/tools/read.d.ts +2 -2
  74. package/dist/types/tools/render-utils.d.ts +8 -0
  75. package/dist/types/tools/renderers.d.ts +11 -0
  76. package/dist/types/tools/ssh.d.ts +2 -0
  77. package/dist/types/tools/todo.d.ts +0 -16
  78. package/dist/types/tools/write.d.ts +2 -2
  79. package/dist/types/utils/active-repo-context.d.ts +8 -0
  80. package/dist/types/utils/image-resize.d.ts +1 -0
  81. package/dist/types/utils/markit-cache.d.ts +23 -0
  82. package/dist/types/utils/markit.d.ts +5 -1
  83. package/dist/types/utils/prompt-path.d.ts +1 -0
  84. package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
  85. package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
  86. package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
  87. package/dist/types/web/search/providers/xai.d.ts +13 -0
  88. package/dist/types/web/search/types.d.ts +18 -2
  89. package/package.json +30 -15
  90. package/scripts/build-binary.ts +9 -9
  91. package/scripts/bundle-dist.ts +2 -2
  92. package/src/advisor/__tests__/advisor.test.ts +61 -4
  93. package/src/advisor/__tests__/emission-guard.test.ts +147 -0
  94. package/src/advisor/advise-tool.ts +1 -1
  95. package/src/advisor/emission-guard.ts +172 -0
  96. package/src/advisor/index.ts +1 -0
  97. package/src/advisor/runtime.ts +11 -0
  98. package/src/advisor/watchdog.ts +27 -1
  99. package/src/cli/args.ts +5 -2
  100. package/src/cli/auth-broker-cli.ts +17 -0
  101. package/src/cli/config-cli.ts +4 -0
  102. package/src/cli/flag-tables.ts +7 -4
  103. package/src/cli/gallery-cli.ts +14 -2
  104. package/src/cli/gallery-fixtures/edit.ts +60 -0
  105. package/src/cli/gallery-fixtures/fs.ts +17 -17
  106. package/src/cli/gallery-fixtures/search.ts +4 -4
  107. package/src/cli/usage-cli.ts +20 -1
  108. package/src/cli/web-search-cli.ts +1 -1
  109. package/src/collab/display-name.ts +13 -0
  110. package/src/collab/guest.ts +5 -1
  111. package/src/collab/host.ts +2 -13
  112. package/src/commands/worktree.ts +6 -0
  113. package/src/config/inline-tool-descriptors-mode.ts +27 -0
  114. package/src/config/model-discovery.ts +77 -8
  115. package/src/config/model-registry.ts +89 -11
  116. package/src/config/model-resolver.ts +39 -1
  117. package/src/config/models-config-schema.ts +41 -5
  118. package/src/config/models-config.ts +4 -1
  119. package/src/config/settings-schema.ts +85 -27
  120. package/src/config/settings.ts +172 -1
  121. package/src/cursor.ts +1 -1
  122. package/src/dap/config.ts +152 -8
  123. package/src/dap/session.ts +1 -1
  124. package/src/discovery/claude-plugins.ts +3 -2
  125. package/src/discovery/helpers.ts +3 -1
  126. package/src/edit/hashline/diff.ts +14 -3
  127. package/src/edit/hashline/execute.ts +42 -6
  128. package/src/edit/hashline/filesystem.ts +38 -2
  129. package/src/edit/index.ts +1 -0
  130. package/src/edit/modes/patch.ts +11 -1
  131. package/src/edit/renderer.ts +140 -13
  132. package/src/eval/__tests__/agent-bridge.test.ts +101 -0
  133. package/src/eval/agent-bridge.ts +26 -3
  134. package/src/eval/js/tool-bridge.ts +2 -2
  135. package/src/export/html/index.ts +1 -1
  136. package/src/export/html/template.js +3 -1
  137. package/src/export/html/tool-views.generated.js +20 -20
  138. package/src/extensibility/extensions/types.ts +22 -22
  139. package/src/extensibility/hooks/types.ts +11 -11
  140. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +8 -3
  141. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -10
  142. package/src/extensibility/plugins/marketplace/manager.ts +22 -0
  143. package/src/extensibility/plugins/marketplace/types.ts +1 -0
  144. package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
  145. package/src/internal-urls/docs-index.generated.txt +2 -2
  146. package/src/internal-urls/docs-index.ts +2 -3
  147. package/src/internal-urls/index.ts +1 -0
  148. package/src/internal-urls/registry-helpers.ts +19 -4
  149. package/src/internal-urls/router.ts +5 -3
  150. package/src/internal-urls/ssh-protocol.ts +367 -0
  151. package/src/internal-urls/types.ts +19 -2
  152. package/src/irc/bus.ts +11 -3
  153. package/src/mcp/tool-bridge.ts +32 -2
  154. package/src/memories/index.ts +1 -1
  155. package/src/modes/acp/acp-agent.ts +14 -1
  156. package/src/modes/acp/acp-event-mapper.ts +91 -27
  157. package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
  158. package/src/modes/components/agent-hub.ts +3 -0
  159. package/src/modes/components/agent-transcript-viewer.ts +9 -7
  160. package/src/modes/components/custom-editor.ts +90 -5
  161. package/src/modes/components/move-overlay.ts +282 -0
  162. package/src/modes/components/plan-review-overlay.ts +35 -35
  163. package/src/modes/components/plugin-selector.ts +6 -1
  164. package/src/modes/components/queue-mode-selector.ts +6 -1
  165. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  166. package/src/modes/components/session-selector.ts +11 -10
  167. package/src/modes/components/settings-defs.ts +14 -1
  168. package/src/modes/components/settings-selector.ts +196 -29
  169. package/src/modes/components/show-images-selector.ts +6 -1
  170. package/src/modes/components/status-line/component.ts +108 -28
  171. package/src/modes/components/status-line/segments.ts +5 -1
  172. package/src/modes/components/status-line/types.ts +2 -0
  173. package/src/modes/components/theme-selector.ts +6 -1
  174. package/src/modes/components/thinking-selector.ts +6 -1
  175. package/src/modes/components/tool-execution.ts +25 -9
  176. package/src/modes/components/tree-selector.ts +5 -5
  177. package/src/modes/controllers/command-controller.ts +140 -47
  178. package/src/modes/controllers/event-controller.ts +59 -3
  179. package/src/modes/controllers/input-controller.ts +68 -2
  180. package/src/modes/controllers/selector-controller.ts +5 -2
  181. package/src/modes/controllers/streaming-reveal.ts +17 -0
  182. package/src/modes/controllers/tool-args-reveal.ts +1 -1
  183. package/src/modes/interactive-mode.ts +116 -50
  184. package/src/modes/internal-url-autocomplete.ts +17 -2
  185. package/src/modes/prompt-action-autocomplete.ts +3 -1
  186. package/src/modes/running-subagent-badge.ts +13 -0
  187. package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
  188. package/src/modes/setup-wizard/scenes/providers.ts +2 -1
  189. package/src/modes/setup-wizard/scenes/theme.ts +6 -12
  190. package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
  191. package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
  192. package/src/modes/theme/theme.ts +9 -1
  193. package/src/modes/types.ts +9 -1
  194. package/src/modes/utils/interactive-context-helpers.ts +1 -1
  195. package/src/priority.json +15 -0
  196. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  197. package/src/prompts/advisor/context-files.md +8 -0
  198. package/src/prompts/advisor/system.md +21 -7
  199. package/src/prompts/agents/designer.md +1 -1
  200. package/src/prompts/agents/explore.md +1 -1
  201. package/src/prompts/agents/librarian.md +2 -2
  202. package/src/prompts/agents/plan.md +2 -2
  203. package/src/prompts/agents/reviewer.md +1 -1
  204. package/src/prompts/agents/task.md +2 -2
  205. package/src/prompts/system/active-repo-context.md +4 -0
  206. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  207. package/src/prompts/system/plan-mode-active.md +3 -3
  208. package/src/prompts/system/project-prompt.md +2 -2
  209. package/src/prompts/system/system-prompt.md +6 -6
  210. package/src/prompts/tools/bash.md +2 -2
  211. package/src/prompts/tools/checkpoint.md +1 -1
  212. package/src/prompts/tools/{find.md → glob.md} +1 -1
  213. package/src/prompts/tools/{search.md → grep.md} +3 -3
  214. package/src/prompts/tools/read.md +4 -2
  215. package/src/sdk.ts +65 -25
  216. package/src/session/agent-session.ts +519 -132
  217. package/src/session/blob-store.ts +24 -0
  218. package/src/session/session-history-format.ts +2 -2
  219. package/src/session/session-manager.ts +46 -0
  220. package/src/slash-commands/builtin-registry.ts +138 -20
  221. package/src/slash-commands/helpers/usage-report.ts +23 -2
  222. package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
  223. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
  224. package/src/ssh/connection-manager.ts +12 -15
  225. package/src/ssh/file-transfer.ts +209 -0
  226. package/src/ssh/utils.ts +24 -0
  227. package/src/system-prompt.ts +56 -28
  228. package/src/task/executor.ts +96 -36
  229. package/src/task/index.ts +3 -3
  230. package/src/task/render.ts +14 -13
  231. package/src/task/worktree.ts +38 -1
  232. package/src/tiny/text.ts +49 -4
  233. package/src/tiny/worker.ts +3 -3
  234. package/src/tools/bash.ts +2 -2
  235. package/src/tools/builtin-names.ts +26 -2
  236. package/src/tools/{find.ts → glob.ts} +48 -42
  237. package/src/tools/{search.ts → grep.ts} +311 -129
  238. package/src/tools/index.ts +13 -14
  239. package/src/tools/irc.ts +6 -2
  240. package/src/tools/path-utils.ts +64 -1
  241. package/src/tools/read.ts +35 -6
  242. package/src/tools/render-utils.ts +14 -0
  243. package/src/tools/renderers.ts +15 -4
  244. package/src/tools/ssh.ts +17 -2
  245. package/src/tools/todo.ts +4 -26
  246. package/src/tools/write.ts +25 -9
  247. package/src/utils/active-repo-context.ts +143 -0
  248. package/src/utils/image-resize.ts +88 -7
  249. package/src/utils/lang-from-path.ts +3 -3
  250. package/src/utils/markit-cache.ts +166 -0
  251. package/src/utils/markit.ts +86 -18
  252. package/src/utils/prompt-path.ts +3 -0
  253. package/src/utils/title-generator.ts +1 -1
  254. package/src/web/search/provider.ts +54 -34
  255. package/src/web/search/providers/duckduckgo.ts +140 -0
  256. package/src/web/search/providers/firecrawl.ts +144 -0
  257. package/src/web/search/providers/tinyfish.ts +159 -0
  258. package/src/web/search/providers/xai.ts +292 -0
  259. package/src/web/search/providers/zai.ts +142 -56
  260. package/src/web/search/types.ts +6 -2
@@ -0,0 +1,282 @@
1
+ /**
2
+ * `/move` overlay: a path input with live directory autocomplete.
3
+ *
4
+ * Rendered as a centered modal via `showHookCustom(..., { overlay: true })`.
5
+ * The user types a path, Tab autocomtes the highlighted directory, and Enter
6
+ * confirms — yielding the resolved directory string (or `undefined` on cancel).
7
+ */
8
+ import * as fs from "node:fs";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
11
+ import { type Component, CURSOR_MARKER, type Focusable, Key, matchesKey } from "@oh-my-pi/pi-tui";
12
+ import { theme } from "../theme/theme";
13
+ import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../utils/keybinding-matchers";
14
+ import { bottomBorder, row, topBorder } from "./overlay-box";
15
+
16
+ export interface MoveOverlayResult {
17
+ directory: string;
18
+ }
19
+
20
+ interface DirEntry {
21
+ /** Full absolute path. */
22
+ value: string;
23
+ /** Display label (basename + trailing slash). */
24
+ label: string;
25
+ }
26
+
27
+ const MAX_RESULTS = 15;
28
+ const OVERLAY_WIDTH = 68;
29
+
30
+ /** TTL for the directory listing cache (ms). */
31
+ const DIR_CACHE_TTL = 500;
32
+ const dirCache = new Map<string, { time: number; entries: string[] }>();
33
+
34
+ function readDirCached(dir: string): string[] {
35
+ const now = Date.now();
36
+ const cached = dirCache.get(dir);
37
+ if (cached && now - cached.time < DIR_CACHE_TTL) return cached.entries;
38
+ try {
39
+ const entries = fs.readdirSync(dir);
40
+ dirCache.set(dir, { time: now, entries });
41
+ return entries;
42
+ } catch {
43
+ return [];
44
+ }
45
+ }
46
+
47
+ function printableInput(data: string): string {
48
+ const withoutPasteEnvelope = data.replaceAll("\x1b[200~", "").replaceAll("\x1b[201~", "");
49
+ if (withoutPasteEnvelope.includes("\x1b")) return "";
50
+ return Array.from(withoutPasteEnvelope)
51
+ .filter(ch => {
52
+ const code = ch.codePointAt(0);
53
+ return code !== undefined && code >= 32 && code !== 0x7f;
54
+ })
55
+ .join("");
56
+ }
57
+
58
+ /** Resolve a user-typed path (`~`, absolute, or relative to `cwd`) to an absolute path. */
59
+ export function resolveMovePath(input: string, cwd: string): string {
60
+ const trimmed = input.trim();
61
+ if (trimmed === "~") return os.homedir();
62
+ if (trimmed.startsWith("~/")) return path.join(os.homedir(), trimmed.slice(2));
63
+ if (path.isAbsolute(trimmed)) return path.normalize(trimmed);
64
+ return path.resolve(cwd, trimmed);
65
+ }
66
+
67
+ /** If `input` resolves to an existing directory, return it; otherwise `null`. */
68
+ export function resolveExistingDirectory(input: string, cwd: string): string | null {
69
+ const resolved = resolveMovePath(input, cwd);
70
+ try {
71
+ return fs.statSync(resolved).isDirectory() ? resolved : null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function listChildDirectories(dirPath: string, max: number, includeHidden = false): DirEntry[] {
78
+ const results: DirEntry[] = [];
79
+ const names = readDirCached(dirPath);
80
+ for (const name of names) {
81
+ if (results.length >= max) break;
82
+ if (!includeHidden && name.startsWith(".")) continue;
83
+ const full = path.join(dirPath, name);
84
+ try {
85
+ if (!fs.statSync(full).isDirectory()) continue;
86
+ } catch {
87
+ continue;
88
+ }
89
+ results.push({ value: full, label: `${name}/` });
90
+ }
91
+ results.sort((a, b) => a.label.localeCompare(b.label));
92
+ return results;
93
+ }
94
+
95
+ function searchDirectories(prefix: string, cwd: string, max: number): DirEntry[] {
96
+ if (!prefix) return listChildDirectories(cwd, max);
97
+
98
+ // Split into base dir + query so dot-prefixed segments can reveal hidden directories.
99
+ const norm = prefix.replace(/\\/g, "/");
100
+ const slashIdx = norm.lastIndexOf("/");
101
+ let baseDir: string;
102
+ let query: string;
103
+ if (slashIdx === -1) {
104
+ baseDir = cwd;
105
+ query = prefix;
106
+ } else {
107
+ const base = norm.slice(0, slashIdx + 1);
108
+ query = norm.slice(slashIdx + 1);
109
+ baseDir = resolveMovePath(base, cwd);
110
+ }
111
+
112
+ const includeHidden = query.startsWith(".");
113
+
114
+ // If the prefix already resolves to an existing directory, list its children.
115
+ // A dot-prefixed query is treated as a filter so hidden directories become reachable.
116
+ const resolved = includeHidden ? null : resolveExistingDirectory(prefix, cwd);
117
+ if (resolved) return listChildDirectories(resolved, max);
118
+
119
+ const lower = query.toLowerCase();
120
+ const results: DirEntry[] = [];
121
+ const names = readDirCached(baseDir);
122
+ for (const name of names) {
123
+ if (results.length >= max) break;
124
+ if (!includeHidden && name.startsWith(".")) continue;
125
+ const full = path.join(baseDir, name);
126
+ try {
127
+ if (!fs.statSync(full).isDirectory()) continue;
128
+ } catch {
129
+ continue;
130
+ }
131
+ if (!query || name.toLowerCase().includes(lower)) {
132
+ results.push({ value: full, label: `${name}/` });
133
+ }
134
+ }
135
+ return results;
136
+ }
137
+
138
+ /**
139
+ * Overlay component for `/move`: a single-line path input with a live-filtered
140
+ * list of matching directories. Tab accepts the highlighted suggestion; Enter
141
+ * confirms the current input (or the highlighted suggestion if the input is
142
+ * empty); Escape cancels.
143
+ */
144
+ export class MoveOverlay implements Component, Focusable {
145
+ #focused = false;
146
+ #input = "";
147
+ #cursor = 0;
148
+ #selectedIndex = 0;
149
+ #results: DirEntry[] = [];
150
+ #cwd: string;
151
+ #done: (result: MoveOverlayResult | undefined) => void;
152
+
153
+ constructor(cwd: string, done: (result: MoveOverlayResult | undefined) => void) {
154
+ this.#cwd = cwd;
155
+ this.#done = done;
156
+ // Warm the cache for the current directory so the first keystroke is instant.
157
+ readDirCached(cwd);
158
+ this.#updateResults();
159
+ }
160
+
161
+ get focused(): boolean {
162
+ return this.#focused;
163
+ }
164
+
165
+ set focused(value: boolean) {
166
+ this.#focused = value;
167
+ }
168
+
169
+ handleInput(data: string): void {
170
+ if (matchesSelectCancel(data) || matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
171
+ this.#done(undefined);
172
+ return;
173
+ }
174
+ if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
175
+ this.#confirm();
176
+ return;
177
+ }
178
+ if (matchesSelectUp(data) || matchesKey(data, Key.up)) {
179
+ if (this.#results.length > 0) this.#selectedIndex = Math.max(0, this.#selectedIndex - 1);
180
+ return;
181
+ }
182
+ if (matchesSelectDown(data) || matchesKey(data, Key.down)) {
183
+ if (this.#results.length > 0)
184
+ this.#selectedIndex = Math.min(this.#results.length - 1, this.#selectedIndex + 1);
185
+ return;
186
+ }
187
+ if (matchesKey(data, Key.tab)) {
188
+ const selected = this.#results[this.#selectedIndex];
189
+ if (selected) {
190
+ this.#input = selected.value;
191
+ this.#cursor = this.#input.length;
192
+ this.#selectedIndex = 0;
193
+ this.#updateResults();
194
+ }
195
+ return;
196
+ }
197
+ if (matchesKey(data, Key.left)) {
198
+ this.#cursor = Math.max(0, this.#cursor - 1);
199
+ return;
200
+ }
201
+ if (matchesKey(data, Key.right)) {
202
+ this.#cursor = Math.min(this.#input.length, this.#cursor + 1);
203
+ return;
204
+ }
205
+ if (matchesKey(data, Key.backspace) && this.#cursor > 0) {
206
+ this.#input = this.#input.slice(0, this.#cursor - 1) + this.#input.slice(this.#cursor);
207
+ this.#cursor--;
208
+ this.#selectedIndex = 0;
209
+ this.#updateResults();
210
+ return;
211
+ }
212
+ const text = printableInput(data);
213
+ if (text.length > 0) {
214
+ this.#input = this.#input.slice(0, this.#cursor) + text + this.#input.slice(this.#cursor);
215
+ this.#cursor += text.length;
216
+ this.#selectedIndex = 0;
217
+ this.#updateResults();
218
+ }
219
+ }
220
+
221
+ render(_width: number): readonly string[] {
222
+ const w = OVERLAY_WIDTH;
223
+ const lines: string[] = [];
224
+
225
+ lines.push(topBorder(w, "Move to directory"));
226
+ lines.push(row(this.#renderInput(), w));
227
+ lines.push(row("", w));
228
+
229
+ if (this.#results.length === 0 && this.#input.length > 0) {
230
+ lines.push(row(theme.fg("dim", "No matching directories"), w));
231
+ } else {
232
+ for (let i = 0; i < Math.min(this.#results.length, MAX_RESULTS); i++) {
233
+ const item = this.#results[i]!;
234
+ const selected = i === this.#selectedIndex;
235
+ const marker = selected ? theme.fg("accent", "▶ ") : " ";
236
+ const label = selected ? theme.fg("accent", item.label) : theme.fg("text", item.label);
237
+ lines.push(row(`${marker}${label}`, w));
238
+ }
239
+ }
240
+
241
+ lines.push(row("", w));
242
+ lines.push(row(theme.fg("dim", "Type to filter · ↑↓ navigate · Tab accept · Enter confirm · Esc cancel"), w));
243
+ lines.push(bottomBorder(w));
244
+ return lines;
245
+ }
246
+
247
+ invalidate(): void {}
248
+
249
+ #renderInput(): string {
250
+ const prompt = theme.fg("dim", "Path: ");
251
+ if (this.#input.length === 0) {
252
+ const placeholder = theme.fg("dim", "Type a directory path…");
253
+ const marker = this.#focused ? CURSOR_MARKER : "";
254
+ return `${prompt}${placeholder}${marker}\x1b[7m \x1b[27m`;
255
+ }
256
+ const before = this.#input.slice(0, this.#cursor);
257
+ const cursorChar = this.#cursor < this.#input.length ? this.#input[this.#cursor] : " ";
258
+ const after = this.#input.slice(this.#cursor + 1);
259
+ const marker = this.#focused ? CURSOR_MARKER : "";
260
+ return `${prompt}${before}${marker}\x1b[7m${cursorChar}\x1b[27m${after}`;
261
+ }
262
+
263
+ #updateResults(): void {
264
+ this.#results = searchDirectories(this.#input, this.#cwd, MAX_RESULTS + 5);
265
+ if (this.#selectedIndex >= this.#results.length) {
266
+ this.#selectedIndex = Math.max(0, this.#results.length - 1);
267
+ }
268
+ }
269
+
270
+ #confirm(): void {
271
+ const selected = this.#results[this.#selectedIndex];
272
+ if (selected) {
273
+ this.#done({ directory: selected.value });
274
+ return;
275
+ }
276
+ if (this.#input.trim().length > 0) {
277
+ this.#done({ directory: this.#input.trim() });
278
+ return;
279
+ }
280
+ this.#done(undefined);
281
+ }
282
+ }
@@ -23,7 +23,7 @@ import {
23
23
  Markdown,
24
24
  type MarkdownTheme,
25
25
  matchesKey,
26
- parseSgrMouse,
26
+ routeSgrMouseInput,
27
27
  ScrollView,
28
28
  truncateToWidth,
29
29
  visibleWidth,
@@ -333,42 +333,42 @@ export class PlanReviewOverlay implements Component {
333
333
  * the body.
334
334
  */
335
335
  #handleMouse(data: string): boolean {
336
- const event = parseSgrMouse(data);
337
- if (!event) return false;
338
- if (event.wheel !== null) {
339
- // Scroll wheel: three rows per notch.
340
- this.#scrollView.scroll(event.wheel * 3);
341
- return true;
342
- }
343
- if (event.release) return true;
344
- if (event.motion) {
345
- // Motion (hover or drag): light up the option row under the pointer so a
346
- // mouse user gets the same affordance the keyboard cursor gives. Any
347
- // non-option row clears the highlight.
348
- this.#setHoveredOption(this.#optionClickRows.get(event.row));
349
- return true;
350
- }
351
- if (!event.leftClick) return true;
352
- const optionIndex = this.#optionClickRows.get(event.row);
353
- if (optionIndex !== undefined) {
354
- if (!this.#disabled.has(optionIndex)) {
355
- this.#focus = "actions";
356
- this.#selectedIndex = optionIndex;
357
- this.#confirmSelection();
336
+ return routeSgrMouseInput(data, event => {
337
+ if (event.wheel !== null) {
338
+ // Scroll wheel: three rows per notch.
339
+ this.#scrollView.scroll(event.wheel * 3);
340
+ return true;
341
+ }
342
+ if (event.release) return true;
343
+ if (event.motion) {
344
+ // Motion (hover or drag): light up the option row under the pointer so a
345
+ // mouse user gets the same affordance the keyboard cursor gives. Any
346
+ // non-option row clears the highlight.
347
+ this.#setHoveredOption(this.#optionClickRows.get(event.row));
348
+ return true;
349
+ }
350
+ if (!event.leftClick) return true;
351
+ const optionIndex = this.#optionClickRows.get(event.row);
352
+ if (optionIndex !== undefined) {
353
+ if (!this.#disabled.has(optionIndex)) {
354
+ this.#focus = "actions";
355
+ this.#selectedIndex = optionIndex;
356
+ this.#confirmSelection();
357
+ }
358
+ return true;
359
+ }
360
+ const tocPos = this.#tocClickRows.get(event.row);
361
+ if (tocPos !== undefined && event.col < this.#sidebarClickMaxCol) {
362
+ this.#focus = "toc";
363
+ this.#tocCursor = tocPos;
364
+ this.#scrubBodyToToc();
365
+ return true;
366
+ }
367
+ if (this.#bodyClickRows.has(event.row)) {
368
+ this.#setFocus("body");
358
369
  }
359
370
  return true;
360
- }
361
- const tocPos = this.#tocClickRows.get(event.row);
362
- if (tocPos !== undefined && event.col < this.#sidebarClickMaxCol) {
363
- this.#focus = "toc";
364
- this.#tocCursor = tocPos;
365
- this.#scrubBodyToToc();
366
- return true;
367
- }
368
- if (this.#bodyClickRows.has(event.row)) {
369
- this.#setFocus("body");
370
- }
371
- return true;
371
+ });
372
372
  }
373
373
 
374
374
  /** Set the hovered option from a hit-tested row, ignoring disabled rows and
@@ -4,9 +4,10 @@
4
4
  * Shows available plugins from all configured marketplaces in a SelectList.
5
5
  * Selecting a plugin triggers installation. Esc cancels.
6
6
  */
7
- import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
7
+ import { Container, type SelectItem, SelectList, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
8
8
  import { getSelectListTheme } from "../theme/theme";
9
9
  import { DynamicBorder } from "./dynamic-border";
10
+ import { routeSelectListMouseWithTopBorder } from "./select-list-mouse-routing";
10
11
 
11
12
  export interface PluginSelectorCallbacks {
12
13
  onSelect: (pluginName: string, marketplace: string, scope?: "user" | "project") => void;
@@ -82,6 +83,10 @@ export class PluginSelectorComponent extends Container {
82
83
  getSelectList(): SelectList {
83
84
  return this.#selectList;
84
85
  }
86
+
87
+ routeMouse(event: SgrMouseEvent, line: number, col: number): void {
88
+ routeSelectListMouseWithTopBorder(this.#selectList, event, line, col);
89
+ }
85
90
  }
86
91
 
87
92
  function splitPluginId(id: string): [string, string, "user" | "project" | undefined] | [null, null, null] {
@@ -1,6 +1,7 @@
1
- import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
1
+ import { Container, type SelectItem, SelectList, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
2
2
  import { getSelectListTheme } from "../../modes/theme/theme";
3
3
  import { DynamicBorder } from "./dynamic-border";
4
+ import { routeSelectListMouseWithTopBorder } from "./select-list-mouse-routing";
4
5
 
5
6
  /**
6
7
  * Component that renders a queue mode selector with borders
@@ -53,4 +54,8 @@ export class QueueModeSelectorComponent extends Container {
53
54
  getSelectList(): SelectList {
54
55
  return this.#selectList;
55
56
  }
57
+
58
+ routeMouse(event: SgrMouseEvent, line: number, col: number): void {
59
+ routeSelectListMouseWithTopBorder(this.#selectList, event, line, col);
60
+ }
56
61
  }
@@ -0,0 +1,35 @@
1
+ import type { SelectList, SgrMouseEvent } from "@oh-my-pi/pi-tui";
2
+
3
+ interface RoutableSelectList {
4
+ routeMouse?: (event: SgrMouseEvent, line: number, col: number) => void;
5
+ handleWheel(delta: -1 | 1): void;
6
+ hitTest(line: number): number | undefined;
7
+ setHoverIndex(index: number | null): void;
8
+ clickItem(index: number): void;
9
+ }
10
+
11
+ export function routeSelectListMouseWithTopBorder(
12
+ selectList: SelectList,
13
+ event: SgrMouseEvent,
14
+ line: number,
15
+ col: number,
16
+ ): void {
17
+ const localLine = line - 1;
18
+ const target = selectList as RoutableSelectList;
19
+ if (typeof target.routeMouse === "function") {
20
+ target.routeMouse(event, localLine, col);
21
+ return;
22
+ }
23
+ if (event.wheel !== null) {
24
+ target.handleWheel(event.wheel);
25
+ return;
26
+ }
27
+ const index = target.hitTest(localLine);
28
+ if (event.motion) {
29
+ target.setHoverIndex(index ?? null);
30
+ return;
31
+ }
32
+ if (event.leftClick && index !== undefined) {
33
+ target.clickItem(index);
34
+ }
35
+ }
@@ -5,8 +5,8 @@ import {
5
5
  Input,
6
6
  matchesKey,
7
7
  padding,
8
- parseSgrMouse,
9
8
  replaceTabs,
9
+ routeSgrMouseInput,
10
10
  ScrollView,
11
11
  Spacer,
12
12
  Text,
@@ -726,15 +726,16 @@ export class SessionSelectorComponent extends Container {
726
726
  */
727
727
  #handleMouse(data: string): void {
728
728
  if (this.#confirmationDialog) return;
729
- const event = parseSgrMouse(data);
730
- if (!event) return;
731
- if (event.wheel !== null) {
732
- this.#sessionList.handleWheel(event.wheel);
733
- return;
734
- }
735
- if (!event.leftClick || event.row >= this.#footerStart) return;
736
- const index = this.#sessionList.hitTestSession(event.row - this.#listLineOffset);
737
- if (index !== undefined) this.#sessionList.selectAndConfirm(index);
729
+ routeSgrMouseInput(data, event => {
730
+ if (event.wheel !== null) {
731
+ this.#sessionList.handleWheel(event.wheel);
732
+ return true;
733
+ }
734
+ if (!event.leftClick || event.row >= this.#footerStart) return true;
735
+ const index = this.#sessionList.hitTestSession(event.row - this.#listLineOffset);
736
+ if (index !== undefined) this.#sessionList.selectAndConfirm(index);
737
+ return true;
738
+ });
738
739
  }
739
740
 
740
741
  getSessionList(): SessionList {
@@ -68,7 +68,16 @@ export interface TextInputSettingDef extends BaseSettingDef {
68
68
  type: "text";
69
69
  }
70
70
 
71
- export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef | TextInputSettingDef;
71
+ export interface ProviderLimitsSettingDef extends BaseSettingDef {
72
+ type: "providerLimits";
73
+ }
74
+
75
+ export type SettingDef =
76
+ | BooleanSettingDef
77
+ | EnumSettingDef
78
+ | SubmenuSettingDef
79
+ | TextInputSettingDef
80
+ | ProviderLimitsSettingDef;
72
81
 
73
82
  // ═══════════════════════════════════════════════════════════════════════════
74
83
  // Condition Functions
@@ -170,6 +179,10 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
170
179
  return { ...base, type: "text" };
171
180
  }
172
181
 
182
+ if (schemaType === "record") {
183
+ return path === "providers.maxInFlightRequests" ? { ...base, type: "providerLimits" } : null;
184
+ }
185
+
173
186
  return null;
174
187
  }
175
188