@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.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 (266) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/dry-balance-cli.d.ts +15 -1
  4. package/dist/types/cli/gallery-cli.d.ts +43 -0
  5. package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
  6. package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
  7. package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
  8. package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
  9. package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
  10. package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
  11. package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
  12. package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
  13. package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
  14. package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
  15. package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
  16. package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
  17. package/dist/types/cli/gallery-screenshot.d.ts +35 -0
  18. package/dist/types/commands/gallery.d.ts +47 -0
  19. package/dist/types/commit/analysis/conventional.d.ts +2 -2
  20. package/dist/types/commit/analysis/summary.d.ts +2 -2
  21. package/dist/types/commit/changelog/generate.d.ts +2 -2
  22. package/dist/types/commit/changelog/index.d.ts +2 -2
  23. package/dist/types/commit/map-reduce/index.d.ts +3 -3
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
  26. package/dist/types/commit/model-selection.d.ts +10 -4
  27. package/dist/types/config/api-key-resolver.d.ts +34 -0
  28. package/dist/types/config/keybindings.d.ts +6 -1
  29. package/dist/types/config/model-id-affixes.d.ts +2 -0
  30. package/dist/types/config/model-registry.d.ts +25 -2
  31. package/dist/types/config/settings-schema.d.ts +41 -6
  32. package/dist/types/dap/config.d.ts +14 -1
  33. package/dist/types/dap/types.d.ts +10 -0
  34. package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
  35. package/dist/types/lsp/types.d.ts +10 -0
  36. package/dist/types/lsp/utils.d.ts +3 -2
  37. package/dist/types/main.d.ts +3 -2
  38. package/dist/types/memory-backend/index.d.ts +2 -1
  39. package/dist/types/memory-backend/resolve.d.ts +1 -1
  40. package/dist/types/memory-backend/types.d.ts +1 -1
  41. package/dist/types/modes/components/chat-block.d.ts +64 -0
  42. package/dist/types/modes/components/custom-editor.d.ts +5 -1
  43. package/dist/types/modes/components/overlay-box.d.ts +17 -0
  44. package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
  45. package/dist/types/modes/components/plan-toc.d.ts +41 -0
  46. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  47. package/dist/types/modes/components/tool-execution.d.ts +18 -0
  48. package/dist/types/modes/components/transcript-container.d.ts +11 -0
  49. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  50. package/dist/types/modes/controllers/event-controller.d.ts +0 -1
  51. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
  52. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  53. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  54. package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
  55. package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
  56. package/dist/types/modes/index.d.ts +5 -4
  57. package/dist/types/modes/interactive-mode.d.ts +16 -6
  58. package/dist/types/modes/setup-version.d.ts +11 -0
  59. package/dist/types/modes/setup-wizard/index.d.ts +2 -1
  60. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
  61. package/dist/types/modes/theme/theme.d.ts +1 -1
  62. package/dist/types/modes/types.d.ts +19 -6
  63. package/dist/types/modes/utils/copy-targets.d.ts +21 -1
  64. package/dist/types/plan-mode/approved-plan.d.ts +27 -8
  65. package/dist/types/plan-mode/plan-protection.d.ts +4 -4
  66. package/dist/types/sdk.d.ts +3 -1
  67. package/dist/types/session/agent-session.d.ts +21 -0
  68. package/dist/types/session/messages.d.ts +12 -0
  69. package/dist/types/session/session-manager.d.ts +3 -1
  70. package/dist/types/slash-commands/types.d.ts +4 -6
  71. package/dist/types/task/executor.d.ts +14 -0
  72. package/dist/types/task/index.d.ts +1 -0
  73. package/dist/types/task/render.d.ts +3 -2
  74. package/dist/types/telemetry-export.d.ts +1 -1
  75. package/dist/types/tools/archive-reader.d.ts +5 -0
  76. package/dist/types/tools/ast-edit.d.ts +3 -0
  77. package/dist/types/tools/ast-grep.d.ts +3 -0
  78. package/dist/types/tools/bash.d.ts +1 -0
  79. package/dist/types/tools/eval-render.d.ts +1 -8
  80. package/dist/types/tools/fetch.d.ts +15 -7
  81. package/dist/types/tools/find.d.ts +8 -4
  82. package/dist/types/tools/grouped-file-output.d.ts +95 -12
  83. package/dist/types/tools/memory-render.d.ts +4 -1
  84. package/dist/types/tools/plan-mode-guard.d.ts +8 -9
  85. package/dist/types/tools/render-utils.d.ts +13 -9
  86. package/dist/types/tools/renderers.d.ts +16 -2
  87. package/dist/types/tools/search.d.ts +5 -1
  88. package/dist/types/tools/sqlite-reader.d.ts +1 -0
  89. package/dist/types/tools/todo.d.ts +3 -2
  90. package/dist/types/tools/write.d.ts +5 -0
  91. package/dist/types/tui/output-block.d.ts +16 -4
  92. package/dist/types/tui/status-line.d.ts +3 -0
  93. package/dist/types/utils/enhanced-paste.d.ts +20 -0
  94. package/dist/types/web/scrapers/github.d.ts +22 -0
  95. package/dist/types/web/search/providers/kimi.d.ts +1 -1
  96. package/dist/types/web/search/providers/perplexity.d.ts +8 -1
  97. package/dist/types/web/search/types.d.ts +1 -1
  98. package/package.json +9 -9
  99. package/scripts/dev-launch +42 -0
  100. package/scripts/dev-launch-preload.ts +19 -0
  101. package/src/auto-thinking/classifier.ts +5 -1
  102. package/src/cli/args.ts +2 -2
  103. package/src/cli/dry-balance-cli.ts +52 -17
  104. package/src/cli/gallery-cli.ts +226 -0
  105. package/src/cli/gallery-fixtures/agentic.ts +292 -0
  106. package/src/cli/gallery-fixtures/codeintel.ts +188 -0
  107. package/src/cli/gallery-fixtures/edit.ts +194 -0
  108. package/src/cli/gallery-fixtures/fs.ts +153 -0
  109. package/src/cli/gallery-fixtures/index.ts +40 -0
  110. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  111. package/src/cli/gallery-fixtures/memory.ts +81 -0
  112. package/src/cli/gallery-fixtures/misc.ts +250 -0
  113. package/src/cli/gallery-fixtures/search.ts +213 -0
  114. package/src/cli/gallery-fixtures/shell.ts +167 -0
  115. package/src/cli/gallery-fixtures/types.ts +41 -0
  116. package/src/cli/gallery-fixtures/web.ts +158 -0
  117. package/src/cli/gallery-screenshot.ts +279 -0
  118. package/src/cli-commands.ts +1 -0
  119. package/src/commands/gallery.ts +52 -0
  120. package/src/commands/launch.ts +1 -1
  121. package/src/commit/analysis/conventional.ts +2 -2
  122. package/src/commit/analysis/summary.ts +2 -2
  123. package/src/commit/changelog/generate.ts +2 -2
  124. package/src/commit/changelog/index.ts +2 -2
  125. package/src/commit/map-reduce/index.ts +3 -3
  126. package/src/commit/map-reduce/map-phase.ts +2 -2
  127. package/src/commit/map-reduce/reduce-phase.ts +2 -2
  128. package/src/commit/model-selection.ts +33 -9
  129. package/src/commit/pipeline.ts +4 -4
  130. package/src/config/api-key-resolver.ts +58 -0
  131. package/src/config/keybindings.ts +15 -6
  132. package/src/config/model-equivalence.ts +35 -12
  133. package/src/config/model-id-affixes.ts +39 -22
  134. package/src/config/model-registry.ts +41 -18
  135. package/src/config/settings-schema.ts +28 -5
  136. package/src/config/settings.ts +31 -2
  137. package/src/dap/client.ts +14 -16
  138. package/src/dap/config.ts +41 -2
  139. package/src/dap/defaults.json +1 -0
  140. package/src/dap/session.ts +1 -0
  141. package/src/dap/types.ts +10 -0
  142. package/src/debug/index.ts +40 -54
  143. package/src/edit/renderer.ts +111 -119
  144. package/src/eval/__tests__/agent-bridge.test.ts +75 -32
  145. package/src/eval/__tests__/llm-bridge.test.ts +90 -31
  146. package/src/eval/agent-bridge.ts +34 -7
  147. package/src/eval/llm-bridge.ts +8 -3
  148. package/src/extensibility/extensions/runner.ts +1 -0
  149. package/src/extensibility/plugins/doctor.ts +0 -1
  150. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  151. package/src/goals/tools/goal-tool.ts +37 -27
  152. package/src/internal-urls/docs-index.generated.ts +10 -10
  153. package/src/lsp/client.ts +104 -55
  154. package/src/lsp/types.ts +10 -0
  155. package/src/lsp/utils.ts +3 -2
  156. package/src/main.ts +53 -56
  157. package/src/memories/index.ts +12 -5
  158. package/src/memory-backend/index.ts +13 -1
  159. package/src/memory-backend/resolve.ts +3 -5
  160. package/src/memory-backend/types.ts +1 -1
  161. package/src/mnemopi/backend.ts +5 -1
  162. package/src/modes/acp/acp-agent.ts +33 -26
  163. package/src/modes/components/assistant-message.ts +2 -9
  164. package/src/modes/components/chat-block.ts +111 -0
  165. package/src/modes/components/copy-selector.ts +1 -44
  166. package/src/modes/components/custom-editor.ts +33 -1
  167. package/src/modes/components/custom-message.ts +1 -3
  168. package/src/modes/components/execution-shared.ts +1 -2
  169. package/src/modes/components/hook-message.ts +1 -3
  170. package/src/modes/components/overlay-box.ts +108 -0
  171. package/src/modes/components/plan-review-overlay.ts +799 -0
  172. package/src/modes/components/plan-toc.ts +138 -0
  173. package/src/modes/components/read-tool-group.ts +20 -4
  174. package/src/modes/components/skill-message.ts +0 -1
  175. package/src/modes/components/status-line.ts +3 -5
  176. package/src/modes/components/tips.txt +1 -0
  177. package/src/modes/components/todo-reminder.ts +0 -2
  178. package/src/modes/components/tool-execution.ts +115 -90
  179. package/src/modes/components/transcript-container.ts +84 -24
  180. package/src/modes/components/user-message.ts +1 -2
  181. package/src/modes/controllers/command-controller-shared.ts +7 -6
  182. package/src/modes/controllers/command-controller.ts +70 -57
  183. package/src/modes/controllers/event-controller.ts +41 -40
  184. package/src/modes/controllers/extension-ui-controller.ts +10 -73
  185. package/src/modes/controllers/input-controller.ts +135 -122
  186. package/src/modes/controllers/mcp-command-controller.ts +69 -60
  187. package/src/modes/controllers/selector-controller.ts +25 -27
  188. package/src/modes/controllers/streaming-reveal.ts +212 -0
  189. package/src/modes/controllers/tan-command-controller.ts +173 -0
  190. package/src/modes/index.ts +5 -4
  191. package/src/modes/interactive-mode.ts +171 -82
  192. package/src/modes/setup-version.ts +11 -0
  193. package/src/modes/setup-wizard/index.ts +3 -2
  194. package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
  195. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  196. package/src/modes/theme/theme-schema.json +1 -1
  197. package/src/modes/theme/theme.ts +8 -4
  198. package/src/modes/types.ts +19 -8
  199. package/src/modes/utils/context-usage.ts +10 -6
  200. package/src/modes/utils/copy-targets.ts +133 -27
  201. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  202. package/src/modes/utils/ui-helpers.ts +44 -46
  203. package/src/plan-mode/approved-plan.ts +66 -43
  204. package/src/plan-mode/plan-protection.ts +4 -4
  205. package/src/prompts/system/background-tan-dispatch.md +8 -0
  206. package/src/prompts/system/plan-mode-active.md +67 -58
  207. package/src/prompts/system/plan-mode-approved.md +1 -1
  208. package/src/sdk.ts +32 -60
  209. package/src/session/agent-session.ts +89 -13
  210. package/src/session/messages.ts +26 -0
  211. package/src/session/session-manager.ts +13 -5
  212. package/src/slash-commands/builtin-registry.ts +37 -10
  213. package/src/slash-commands/helpers/usage-report.ts +2 -0
  214. package/src/slash-commands/types.ts +4 -6
  215. package/src/task/executor.ts +25 -4
  216. package/src/task/index.ts +4 -0
  217. package/src/task/render.ts +212 -148
  218. package/src/telemetry-export.ts +25 -7
  219. package/src/tools/archive-reader.ts +64 -0
  220. package/src/tools/ask.ts +119 -164
  221. package/src/tools/ast-edit.ts +98 -71
  222. package/src/tools/ast-grep.ts +37 -43
  223. package/src/tools/bash.ts +50 -6
  224. package/src/tools/debug.ts +20 -8
  225. package/src/tools/eval-backends.ts +6 -17
  226. package/src/tools/eval-render.ts +21 -18
  227. package/src/tools/eval.ts +5 -4
  228. package/src/tools/fetch.ts +391 -91
  229. package/src/tools/find.ts +44 -30
  230. package/src/tools/gh-renderer.ts +81 -42
  231. package/src/tools/grouped-file-output.ts +272 -48
  232. package/src/tools/image-gen.ts +150 -103
  233. package/src/tools/inspect-image-renderer.ts +63 -41
  234. package/src/tools/inspect-image.ts +8 -1
  235. package/src/tools/job.ts +3 -4
  236. package/src/tools/memory-render.ts +4 -1
  237. package/src/tools/plan-mode-guard.ts +21 -39
  238. package/src/tools/read.ts +23 -16
  239. package/src/tools/render-utils.ts +38 -40
  240. package/src/tools/renderers.ts +16 -1
  241. package/src/tools/report-tool-issue.ts +1 -1
  242. package/src/tools/resolve.ts +14 -0
  243. package/src/tools/search-tool-bm25.ts +36 -23
  244. package/src/tools/search.ts +189 -95
  245. package/src/tools/sqlite-reader.ts +9 -12
  246. package/src/tools/todo.ts +138 -59
  247. package/src/tools/write.ts +100 -60
  248. package/src/tui/output-block.ts +60 -13
  249. package/src/tui/status-line.ts +5 -1
  250. package/src/utils/commit-message-generator.ts +9 -1
  251. package/src/utils/enhanced-paste.ts +202 -0
  252. package/src/utils/title-generator.ts +2 -1
  253. package/src/web/scrapers/github.ts +255 -3
  254. package/src/web/scrapers/youtube.ts +3 -2
  255. package/src/web/search/providers/anthropic.ts +25 -19
  256. package/src/web/search/providers/exa.ts +11 -3
  257. package/src/web/search/providers/kimi.ts +28 -17
  258. package/src/web/search/providers/parallel.ts +35 -24
  259. package/src/web/search/providers/perplexity.ts +199 -51
  260. package/src/web/search/providers/synthetic.ts +8 -6
  261. package/src/web/search/providers/tavily.ts +9 -8
  262. package/src/web/search/providers/zai.ts +8 -6
  263. package/src/web/search/render.ts +39 -54
  264. package/src/web/search/types.ts +5 -1
  265. package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
  266. package/src/eval/__tests__/shared-executors.test.ts +0 -609
@@ -21,6 +21,7 @@ interface AppKeybindings {
21
21
  "app.clear": true;
22
22
  "app.exit": true;
23
23
  "app.suspend": true;
24
+ "app.display.reset": true;
24
25
  "app.thinking.cycle": true;
25
26
  "app.thinking.toggle": true;
26
27
  "app.model.cycleForward": true;
@@ -86,6 +87,10 @@ export const KEYBINDINGS = {
86
87
  defaultKeys: "ctrl+z",
87
88
  description: "Suspend application",
88
89
  },
90
+ "app.display.reset": {
91
+ defaultKeys: "ctrl+l",
92
+ description: "Reset terminal display",
93
+ },
89
94
  "app.thinking.cycle": {
90
95
  defaultKeys: "shift+tab",
91
96
  description: "Cycle thinking level",
@@ -103,7 +108,7 @@ export const KEYBINDINGS = {
103
108
  description: "Cycle to previous model",
104
109
  },
105
110
  "app.model.select": {
106
- defaultKeys: "ctrl+l",
111
+ defaultKeys: "alt+m",
107
112
  description: "Select model",
108
113
  },
109
114
  "app.model.selectTemporary": {
@@ -216,6 +221,7 @@ const KEYBINDING_NAME_MIGRATIONS = {
216
221
  clear: "app.clear",
217
222
  exit: "app.exit",
218
223
  suspend: "app.suspend",
224
+ displayReset: "app.display.reset",
219
225
  cycleThinkingLevel: "app.thinking.cycle",
220
226
  cycleModelForward: "app.model.cycleForward",
221
227
  cycleModelBackward: "app.model.cycleBackward",
@@ -444,7 +450,6 @@ function migrateKeybindingsConfigFile(agentDir: string): void {
444
450
 
445
451
  const FOLLOW_UP_KEYBINDING: AppKeybinding = "app.message.followUp";
446
452
  const WINDOWS_FOLLOW_UP_FALLBACK_KEY: KeyId = "ctrl+q";
447
-
448
453
  function keyListIncludes(keys: KeyId | KeyId[] | undefined, target: KeyId): boolean {
449
454
  if (keys === undefined) return false;
450
455
  const keyList = Array.isArray(keys) ? keys : [keys];
@@ -525,10 +530,14 @@ export class KeybindingsManager extends TuiKeybindingsManager {
525
530
 
526
531
  getKeys(keybinding: Keybinding): KeyId[] {
527
532
  const keys = super.getKeys(keybinding);
528
- if (keybinding !== FOLLOW_UP_KEYBINDING) return keys;
529
- if (this.#userBindings[FOLLOW_UP_KEYBINDING] !== undefined) return keys;
530
- if (!userBindingClaimsKey(this.#userBindings, WINDOWS_FOLLOW_UP_FALLBACK_KEY, FOLLOW_UP_KEYBINDING)) return keys;
531
- return removeKey(keys, WINDOWS_FOLLOW_UP_FALLBACK_KEY);
533
+ if (keybinding === FOLLOW_UP_KEYBINDING) {
534
+ if (this.#userBindings[FOLLOW_UP_KEYBINDING] !== undefined) return keys;
535
+ if (!userBindingClaimsKey(this.#userBindings, WINDOWS_FOLLOW_UP_FALLBACK_KEY, FOLLOW_UP_KEYBINDING)) {
536
+ return keys;
537
+ }
538
+ return removeKey(keys, WINDOWS_FOLLOW_UP_FALLBACK_KEY);
539
+ }
540
+ return keys;
532
541
  }
533
542
 
534
543
  getResolvedBindings(): KeybindingsConfig {
@@ -58,7 +58,7 @@ const EMPTY_COMPILED_EQUIVALENCE: CompiledEquivalenceConfig = {
58
58
  };
59
59
  const kModelResolutionCache = Symbol("model-equivalence.resolutionCache");
60
60
  interface CompiledEquivalenceConfigWithCache extends CompiledEquivalenceConfig {
61
- [kModelResolutionCache]?: WeakMap<Model<Api>, ResolvedCanonicalModel>;
61
+ [kModelResolutionCache]?: Map<string, ResolvedCanonicalModel>;
62
62
  }
63
63
  const FAMILY_EXTRACTION_PATTERNS = [
64
64
  /(?:^|[/:._-])((?:claude|gemini|gpt|grok|glm|qwen|minimax|kimi|deepseek|llama|gemma|nova|mistral|ministral|pixtral|codestral|devstral|magistral|ernie|doubao|seed|aion|olmo|molmo|nemotron|palmyra|command|codex|coder|o[1345])[-a-z0-9.]+)(?::|$)/i,
@@ -128,10 +128,18 @@ function normalizeCanonicalIdKey(canonicalId: string): string {
128
128
  return canonicalId.trim().toLowerCase();
129
129
  }
130
130
 
131
+ function getCanonicalSuffixAliasKey(candidate: string): string {
132
+ return PENALTY_HAS_UPPERCASE.test(candidate) ? normalizeCanonicalIdKey(candidate) : candidate;
133
+ }
134
+
131
135
  export function formatCanonicalVariantSelector(model: Model<Api>): string {
132
136
  return `${model.provider}/${model.id}`;
133
137
  }
134
138
 
139
+ function getModelResolutionCacheKey(model: Model<Api>): string {
140
+ return `${model.provider}\0${model.id}`;
141
+ }
142
+
135
143
  function buildOverrideMap(overrides: Record<string, string> | undefined): Map<string, string> {
136
144
  const result = new Map<string, string>();
137
145
  if (!overrides) {
@@ -159,13 +167,24 @@ function buildExclusionSet(exclusions: readonly string[] | undefined): Set<strin
159
167
  return result;
160
168
  }
161
169
 
170
+ const compiledEquivalenceCache = new WeakMap<ModelEquivalenceConfig, CompiledEquivalenceConfig>();
162
171
  function compileEquivalenceConfig(config: ModelEquivalenceConfig | undefined): CompiledEquivalenceConfig {
172
+ if (config) {
173
+ const cached = compiledEquivalenceCache.get(config);
174
+ if (cached) {
175
+ return cached;
176
+ }
177
+ }
163
178
  const overrides = buildOverrideMap(config?.overrides);
164
179
  const exclude = buildExclusionSet(config?.exclude);
165
180
  if (overrides.size === 0 && exclude.size === 0) {
166
181
  return EMPTY_COMPILED_EQUIVALENCE;
167
182
  }
168
- return { overrides, exclude };
183
+ const compiled: CompiledEquivalenceConfig = { overrides, exclude };
184
+ if (config) {
185
+ compiledEquivalenceCache.set(config, compiled);
186
+ }
187
+ return compiled;
169
188
  }
170
189
 
171
190
  function addCanonicalCandidate(candidates: Set<string>, candidate: string): void {
@@ -277,7 +296,7 @@ function expandCompactSeriesMinorVersions(candidate: string): string[] {
277
296
  // safely return the same instance. Cap keeps memory bounded under adversarial
278
297
  // model-id churn.
279
298
  const QUALIFIED_NAMESPACE_SUFFIX_CACHE = new Map<string, string[]>();
280
- const QUALIFIED_NAMESPACE_SUFFIX_CACHE_CAP = 256;
299
+ const QUALIFIED_NAMESPACE_SUFFIX_CACHE_CAP = 4096;
281
300
  function getQualifiedNamespaceSuffixes(candidate: string): string[] {
282
301
  const cached = QUALIFIED_NAMESPACE_SUFFIX_CACHE.get(candidate);
283
302
  if (cached !== undefined) {
@@ -670,7 +689,7 @@ function expandHeavyCanonicalCandidates(normalized: string, queue: string[]): vo
670
689
  // is unused — kept for signature stability). The returned array is consumed via
671
690
  // `.filter` at every callsite, so sharing the cached instance is safe.
672
691
  const HEURISTIC_CANDIDATES_CACHE = new Map<string, string[]>();
673
- const HEURISTIC_CANDIDATES_CACHE_CAP = 256;
692
+ const HEURISTIC_CANDIDATES_CACHE_CAP = 4096;
674
693
  function getHeuristicCanonicalCandidates(modelId: string, _officialIds?: ReadonlySet<string>): string[] {
675
694
  const cached = HEURISTIC_CANDIDATES_CACHE.get(modelId);
676
695
  if (cached !== undefined) {
@@ -728,10 +747,10 @@ function getPreferredFallbackCanonicalCandidate(modelId: string, candidates: rea
728
747
 
729
748
  function resolveCanonicalIdForModel(
730
749
  model: Model<Api>,
750
+ selector: string,
731
751
  equivalence: CompiledEquivalenceConfig,
732
752
  referenceData: CanonicalReferenceData,
733
753
  ): ResolvedCanonicalModel {
734
- const selector = formatCanonicalVariantSelector(model);
735
754
  const normalizedSelector = normalizeSelectorKey(selector);
736
755
 
737
756
  if (equivalence.overrides.has(normalizedSelector)) {
@@ -753,9 +772,12 @@ function resolveCanonicalIdForModel(
753
772
  }
754
773
 
755
774
  const heuristicCandidates = getHeuristicCanonicalCandidates(model.id, referenceData.officialIds);
756
- const officialMatches = new Set(heuristicCandidates.filter(candidate => referenceData.officialIds.has(candidate)));
775
+ const officialMatches = new Set<string>();
757
776
  for (const candidate of heuristicCandidates) {
758
- const aliased = referenceData.suffixAliases.get(normalizeCanonicalIdKey(candidate));
777
+ if (referenceData.officialIds.has(candidate)) {
778
+ officialMatches.add(candidate);
779
+ }
780
+ const aliased = referenceData.suffixAliases.get(getCanonicalSuffixAliasKey(candidate));
759
781
  if (aliased) {
760
782
  officialMatches.add(aliased);
761
783
  }
@@ -814,17 +836,18 @@ export function buildCanonicalModelIndex(
814
836
  const compiledWithCache = compiledEquivalence as CompiledEquivalenceConfigWithCache;
815
837
  let modelCache = compiledWithCache[kModelResolutionCache];
816
838
  if (!modelCache) {
817
- modelCache = new WeakMap<Model<Api>, ResolvedCanonicalModel>();
839
+ modelCache = new Map<string, ResolvedCanonicalModel>();
818
840
  compiledWithCache[kModelResolutionCache] = modelCache;
819
841
  }
820
842
 
821
843
  for (const model of models) {
822
- let canonical = modelCache.get(model);
844
+ const selector = formatCanonicalVariantSelector(model);
845
+ const cacheKey = getModelResolutionCacheKey(model);
846
+ let canonical = modelCache.get(cacheKey);
823
847
  if (!canonical) {
824
- canonical = resolveCanonicalIdForModel(model, compiledEquivalence, referenceData);
825
- modelCache.set(model, canonical);
848
+ canonical = resolveCanonicalIdForModel(model, selector, compiledEquivalence, referenceData);
849
+ modelCache.set(cacheKey, canonical);
826
850
  }
827
- const selector = formatCanonicalVariantSelector(model);
828
851
  const variant: CanonicalModelVariant = {
829
852
  canonicalId: canonical.id,
830
853
  selector,
@@ -4,34 +4,49 @@ const MODEL_ID_SEGMENT_PATTERN = /[a-z0-9.:-]+/g;
4
4
  const MODEL_FAMILY_PREFIX_PATTERN =
5
5
  /^(claude|gemini|gpt|grok|glm|qwen|deepseek|kimi|mimo|doubao|ernie|gpt-oss|gemma|minimax|step|command|jamba|llama|o[1345])/i;
6
6
 
7
- function hasDigit(value: string): boolean {
8
- return /\d/.test(value);
7
+ function normalizeModelIdWhitespace(value: string): string {
8
+ return value.trim().replace(/\s+/g, " ");
9
9
  }
10
10
 
11
+ /** Ordering for model-like segments: longest first, ties broken lexicographically. */
11
12
  function compareSegmentPreference(left: string, right: string): number {
12
- if (left.length !== right.length) {
13
- return right.length - left.length;
14
- }
15
- return left.localeCompare(right);
13
+ return left.length !== right.length ? right.length - left.length : left.localeCompare(right);
16
14
  }
17
15
 
18
16
  export function getModelLikeIdSegments(modelId: string): string[] {
19
- const normalized = normalizeModelIdWhitespace(modelId).toLowerCase();
20
- if (!normalized) return [];
21
- const segments = (normalized.match(MODEL_ID_SEGMENT_PATTERN) ?? []).filter(
22
- segment => MODEL_FAMILY_PREFIX_PATTERN.test(segment) && hasDigit(segment),
23
- );
24
- const unique = [...new Set(segments)];
25
- unique.sort(compareSegmentPreference);
26
- return unique;
17
+ const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
18
+ if (!matches) return [];
19
+ const segments = new Set<string>();
20
+ for (const segment of matches) {
21
+ if (MODEL_FAMILY_PREFIX_PATTERN.test(segment) && /\d/.test(segment)) segments.add(segment);
22
+ }
23
+ return [...segments].sort(compareSegmentPreference);
27
24
  }
28
25
 
29
26
  export function getLongestModelLikeIdSegment(modelId: string): string | undefined {
30
- return getModelLikeIdSegments(modelId)[0];
27
+ const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
28
+ if (!matches) return undefined;
29
+ let best: string | undefined;
30
+ for (const segment of matches) {
31
+ if (
32
+ MODEL_FAMILY_PREFIX_PATTERN.test(segment) &&
33
+ /\d/.test(segment) &&
34
+ (best === undefined || compareSegmentPreference(segment, best) < 0)
35
+ ) {
36
+ best = segment;
37
+ }
38
+ }
39
+ return best;
31
40
  }
32
41
 
33
- function normalizeModelIdWhitespace(value: string): string {
34
- return value.trim().replace(/\s+/g, " ");
42
+ function hasBracketAffixMarker(value: string): boolean {
43
+ for (let index = 0; index < value.length; index++) {
44
+ const code = value.charCodeAt(index);
45
+ if (code === 91 || code === 93 || code === 0x3010 || code === 0x3011) {
46
+ return true;
47
+ }
48
+ }
49
+ return false;
35
50
  }
36
51
 
37
52
  /**
@@ -39,18 +54,20 @@ function normalizeModelIdWhitespace(value: string): string {
39
54
  * upstream model id, e.g.
40
55
  * "[Kiro] claude-opus-4-8" -> "claude-opus-4-8"
41
56
  * "[gcli转] gemini-3.1-pro-preview [假流]" -> "gemini-3.1-pro-preview"
57
+ *
58
+ * Candidates are returned most-stripped first: both ends, then leading-only, then trailing-only.
42
59
  */
43
60
  export function getBracketStrippedModelIdCandidates(modelId: string): string[] {
61
+ if (!hasBracketAffixMarker(modelId)) return [];
44
62
  const normalized = normalizeModelIdWhitespace(modelId);
45
63
  if (!normalized) return [];
46
64
 
47
- const candidates = new Set<string>();
48
- const withoutLeading = normalizeModelIdWhitespace(normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, ""));
65
+ const strippedLeading = normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "");
66
+ const withoutLeading = normalizeModelIdWhitespace(strippedLeading);
49
67
  const withoutTrailing = normalizeModelIdWhitespace(normalized.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
50
- const withoutBoth = normalizeModelIdWhitespace(
51
- normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "").replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""),
52
- );
68
+ const withoutBoth = normalizeModelIdWhitespace(strippedLeading.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
53
69
 
70
+ const candidates = new Set<string>();
54
71
  for (const candidate of [withoutBoth, withoutLeading, withoutTrailing]) {
55
72
  if (candidate && candidate !== normalized) {
56
73
  candidates.add(candidate);
@@ -1,27 +1,19 @@
1
1
  import * as path from "node:path";
2
+ import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
3
+ import { readModelCache } from "@oh-my-pi/pi-ai/model-cache";
4
+ import { createModelManager, type ModelManagerOptions, type ModelRefreshStrategy } from "@oh-my-pi/pi-ai/model-manager";
5
+ import { enrichModelThinking } from "@oh-my-pi/pi-ai/model-thinking";
6
+ import { getBundledModels, getBundledProviders } from "@oh-my-pi/pi-ai/models";
2
7
  import {
3
- type Api,
4
- type AssistantMessageEventStream,
5
- type Context,
6
- createModelManager,
7
- enrichModelThinking,
8
- getBundledModels,
9
- getBundledProviders,
10
8
  googleAntigravityModelManagerOptions,
11
9
  googleGeminiCliModelManagerOptions,
12
- type Model,
13
- type ModelManagerOptions,
14
- type ModelRefreshStrategy,
15
10
  openaiCodexModelManagerOptions,
16
11
  PROVIDER_DESCRIPTORS,
17
- readModelCache,
18
- registerCustomApi,
19
- type SimpleStreamOptions,
20
- type ThinkingConfig,
21
12
  UNK_CONTEXT_WINDOW,
22
13
  UNK_MAX_TOKENS,
23
- unregisterCustomApis,
24
- } from "@oh-my-pi/pi-ai";
14
+ } from "@oh-my-pi/pi-ai/provider-models";
15
+ import type { Api, Context, Model, SimpleStreamOptions, ThinkingConfig } from "@oh-my-pi/pi-ai/types";
16
+ import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
25
17
 
26
18
  // Sentinel for local-only OAuth token (LM Studio, vLLM) — declared inline to avoid loading
27
19
  // any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
@@ -103,12 +95,14 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
103
95
  ...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
104
96
  ];
105
97
 
98
+ import type { ApiKeyResolver } from "@oh-my-pi/pi-ai";
106
99
  import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
107
100
  import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/utils/oauth/types";
108
101
  import { isRecord, logger } from "@oh-my-pi/pi-utils";
109
102
  import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
110
103
  import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
111
104
  import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
105
+ import { type ApiKeyResolverOptions, createApiKeyResolver } from "./api-key-resolver";
112
106
  import { type ConfigError, ConfigFile } from "./config-file";
113
107
  import {
114
108
  buildCanonicalModelIndex,
@@ -2373,12 +2367,33 @@ export class ModelRegistry {
2373
2367
 
2374
2368
  /**
2375
2369
  * Get API key for a provider (e.g., "openai").
2370
+ *
2371
+ * `options.forceRefresh` powers step (b) of the auth-retry policy — it
2372
+ * re-mints the session-sticky OAuth token even when the cached copy still
2373
+ * looks valid. `options.signal` is threaded into any broker-bound refresh.
2376
2374
  */
2377
- async getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string): Promise<string | undefined> {
2375
+ async getApiKeyForProvider(
2376
+ provider: string,
2377
+ sessionId?: string,
2378
+ options?: { baseUrl?: string; forceRefresh?: boolean; signal?: AbortSignal },
2379
+ ): Promise<string | undefined> {
2378
2380
  if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2379
2381
  return kNoAuth;
2380
2382
  }
2381
- return this.authStorage.getApiKey(provider, sessionId, { baseUrl });
2383
+ return this.authStorage.getApiKey(provider, sessionId, {
2384
+ baseUrl: options?.baseUrl,
2385
+ forceRefresh: options?.forceRefresh,
2386
+ signal: options?.signal,
2387
+ });
2388
+ }
2389
+
2390
+ /**
2391
+ * Build an {@link ApiKeyResolver} for this provider, implementing the
2392
+ * central a/b/c auth-retry policy. Callers that need the initial key for
2393
+ * a guard can call `resolveApiKeyOnce(resolver)`.
2394
+ */
2395
+ resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver {
2396
+ return createApiKeyResolver(this, provider, options);
2382
2397
  }
2383
2398
 
2384
2399
  async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
@@ -2609,6 +2624,14 @@ export class ModelRegistry {
2609
2624
  }
2610
2625
  return true;
2611
2626
  }
2627
+
2628
+ /**
2629
+ * Clear all cooldown suppressions recorded via {@link suppressSelector}.
2630
+ * Used to reset retry-fallback cooldown state without a full {@link refresh}.
2631
+ */
2632
+ clearSuppressedSelectors(): void {
2633
+ this.#suppressedSelectors.clear();
2634
+ }
2612
2635
  }
2613
2636
 
2614
2637
  /**
@@ -660,6 +660,16 @@ export const SETTINGS_SCHEMA = {
660
660
  },
661
661
  },
662
662
 
663
+ "display.smoothStreaming": {
664
+ type: "boolean",
665
+ default: true,
666
+ ui: {
667
+ tab: "appearance",
668
+ label: "Smooth Streaming",
669
+ description: "Reveal assistant text smoothly while streamed chunks arrive",
670
+ },
671
+ },
672
+
663
673
  "display.showTokenUsage": {
664
674
  type: "boolean",
665
675
  default: false,
@@ -3059,13 +3069,26 @@ export const SETTINGS_SCHEMA = {
3059
3069
  ],
3060
3070
  },
3061
3071
  },
3062
- "providers.parallelFetch": {
3063
- type: "boolean",
3064
- default: true,
3072
+ "providers.fetch": {
3073
+ type: "enum",
3074
+ values: ["auto", "native", "trafilatura", "lynx", "parallel", "jina"] as const,
3075
+ default: "auto",
3065
3076
  ui: {
3066
3077
  tab: "providers",
3067
- label: "Parallel Fetch",
3068
- description: "Use Parallel extract API for URL fetching when credentials are available",
3078
+ label: "Fetch Provider",
3079
+ description: "Reader backend priority for the fetch/read URL tool",
3080
+ options: [
3081
+ {
3082
+ value: "auto",
3083
+ label: "Auto",
3084
+ description: "Priority: native > trafilatura > lynx > parallel > jina",
3085
+ },
3086
+ { value: "native", label: "Native", description: "In-process HTML→Markdown converter (always available)" },
3087
+ { value: "trafilatura", label: "Trafilatura", description: "Auto-installs via uv/pip" },
3088
+ { value: "lynx", label: "Lynx", description: "Requires lynx system package" },
3089
+ { value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
3090
+ { value: "jina", label: "Jina", description: "Uses r.jina.ai reader (JINA_API_KEY optional)" },
3091
+ ],
3069
3092
  },
3070
3093
  },
3071
3094
  "provider.appendOnlyContext": {
@@ -240,11 +240,13 @@ export class Settings {
240
240
  return promise.then(
241
241
  instance => {
242
242
  globalInstance = instance;
243
+ clearBoundSettingsMethods();
243
244
  globalInstancePromise = Promise.resolve(instance);
244
245
  return instance;
245
246
  },
246
247
  error => {
247
248
  globalInstance = null;
249
+ clearBoundSettingsMethods();
248
250
  throw error;
249
251
  },
250
252
  );
@@ -712,6 +714,17 @@ export class Settings {
712
714
  }
713
715
  }
714
716
 
717
+ // providers.parallelFetch (boolean) replaced by the providers.fetch reader
718
+ // priority enum. The new default ("auto") supersedes both old values —
719
+ // Parallel is now a deep fallback in the auto chain rather than the first
720
+ // choice — so drop the legacy key (flat and nested) and let the enum
721
+ // default apply.
722
+ const providersObj = raw.providers as Record<string, unknown> | undefined;
723
+ if (providersObj && "parallelFetch" in providersObj) {
724
+ delete providersObj.parallelFetch;
725
+ }
726
+ delete raw["providers.parallelFetch"];
727
+
715
728
  // Map legacy `memories.enabled` boolean to the explicit `memory.backend`
716
729
  // enum if the latter hasn't been set yet. Idempotent: subsequent
717
730
  // migrations are no-ops once memory.backend is materialised.
@@ -967,6 +980,13 @@ export function onHindsightScopeChanged(cb: () => void): () => void {
967
980
 
968
981
  let globalInstance: Settings | null = null;
969
982
  let globalInstancePromise: Promise<Settings> | null = null;
983
+ let boundSettingsInstance: Settings | null = null;
984
+ let boundSettingsMethods = new Map<PropertyKey, unknown>();
985
+
986
+ function clearBoundSettingsMethods(): void {
987
+ boundSettingsInstance = null;
988
+ boundSettingsMethods = new Map<PropertyKey, unknown>();
989
+ }
970
990
 
971
991
  export function isSettingsInitialized(): boolean {
972
992
  return globalInstance !== null;
@@ -979,6 +999,7 @@ export function isSettingsInitialized(): boolean {
979
999
  export function resetSettingsForTest(): void {
980
1000
  globalInstance = null;
981
1001
  globalInstancePromise = null;
1002
+ clearBoundSettingsMethods();
982
1003
  }
983
1004
 
984
1005
  /**
@@ -990,9 +1011,17 @@ export const settings = new Proxy({} as Settings, {
990
1011
  if (!globalInstance) {
991
1012
  throw new Error("Settings not initialized. Call Settings.init() first.");
992
1013
  }
993
- const value = (globalInstance as unknown as Record<string | symbol, unknown>)[prop];
1014
+ if (boundSettingsInstance !== globalInstance) {
1015
+ clearBoundSettingsMethods();
1016
+ boundSettingsInstance = globalInstance;
1017
+ }
1018
+ const value = (globalInstance as unknown as Record<PropertyKey, unknown>)[prop];
994
1019
  if (typeof value === "function") {
995
- return value.bind(globalInstance);
1020
+ const cached = boundSettingsMethods.get(prop);
1021
+ if (cached) return cached;
1022
+ const bound = value.bind(globalInstance);
1023
+ boundSettingsMethods.set(prop, bound);
1024
+ return bound;
996
1025
  }
997
1026
  return value;
998
1027
  },
package/src/dap/client.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { logger, ptree } from "@oh-my-pi/pi-utils";
1
+ import * as fs from "node:fs/promises";
2
+ import { isEnoent, logger, ptree } from "@oh-my-pi/pi-utils";
2
3
  import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
3
4
  import { ToolAbortError } from "../tools/tool-errors";
4
5
  import type {
@@ -165,19 +166,7 @@ export class DapClient {
165
166
  detached: true,
166
167
  });
167
168
 
168
- // Wait for the socket file to appear (dlv needs to start listening)
169
- await waitForCondition(
170
- () => {
171
- try {
172
- Bun.file(socketPath).size;
173
- return true;
174
- } catch {
175
- return false;
176
- }
177
- },
178
- 10_000,
179
- proc,
180
- );
169
+ await waitForCondition(() => isUnixSocketReady(socketPath), 10_000, proc);
181
170
 
182
171
  const { readable, writeSink, socket } = await connectSocket({ unix: socketPath });
183
172
  const client = new DapClient(adapter, cwd, proc, { readable, writeSink, socket });
@@ -553,15 +542,24 @@ export class DapClient {
553
542
  }
554
543
  }
555
544
 
545
+ async function isUnixSocketReady(socketPath: string): Promise<boolean> {
546
+ try {
547
+ return (await fs.stat(socketPath)).isSocket();
548
+ } catch (error) {
549
+ if (isEnoent(error)) return false;
550
+ throw error;
551
+ }
552
+ }
553
+
556
554
  /** Poll a condition until it returns true, or timeout/process exit. */
557
555
  async function waitForCondition(
558
- check: () => boolean,
556
+ check: () => boolean | Promise<boolean>,
559
557
  timeoutMs: number,
560
558
  proc: { exitCode: number | null },
561
559
  ): Promise<void> {
562
560
  const deadline = Date.now() + timeoutMs;
563
561
  while (Date.now() < deadline) {
564
- if (check()) return;
562
+ if (await check()) return;
565
563
  if (proc.exitCode !== null) {
566
564
  throw new Error("Adapter process exited before socket was ready");
567
565
  }
package/src/dap/config.ts CHANGED
@@ -27,6 +27,7 @@ function normalizeAdapterConfig(config: unknown): DapAdapterConfig | null {
27
27
  rootMarkers: normalizeStringArray(config.rootMarkers),
28
28
  launchDefaults: normalizeObject(config.launchDefaults),
29
29
  attachDefaults: normalizeObject(config.attachDefaults),
30
+ acceptsDirectoryProgram: config.acceptsDirectoryProgram === true,
30
31
  ...(connectMode ? { connectMode } : {}),
31
32
  };
32
33
  }
@@ -64,6 +65,7 @@ export function resolveAdapter(adapterName: string, cwd: string): DapResolvedAda
64
65
  launchDefaults: config.launchDefaults ?? {},
65
66
  attachDefaults: config.attachDefaults ?? {},
66
67
  connectMode: config.connectMode ?? "stdio",
68
+ acceptsDirectoryProgram: config.acceptsDirectoryProgram === true,
67
69
  };
68
70
  }
69
71
 
@@ -124,12 +126,19 @@ function sortAdaptersForLaunch(program: string, cwd: string, adapters: DapResolv
124
126
  return rootAware.map(entry => entry.adapter);
125
127
  }
126
128
 
127
- export function selectLaunchAdapter(program: string, cwd: string, adapterName?: string): DapResolvedAdapter | null {
129
+ export function selectLaunchAdapter(
130
+ program: string,
131
+ cwd: string,
132
+ adapterName?: string,
133
+ programKind: LaunchProgramKind = "file",
134
+ ): DapResolvedAdapter | null {
128
135
  if (adapterName) {
129
136
  return resolveAdapter(adapterName, cwd);
130
137
  }
131
138
  const matches = getMatchingAdapters(program, cwd);
132
- const sorted = sortAdaptersForLaunch(program, cwd, matches);
139
+ const candidates =
140
+ programKind === "directory" ? matches.filter(adapter => adapter.acceptsDirectoryProgram) : matches;
141
+ const sorted = sortAdaptersForLaunch(program, cwd, candidates.length > 0 ? candidates : matches);
133
142
  return sorted[0] ?? null;
134
143
  }
135
144
 
@@ -148,3 +157,33 @@ export function selectAttachAdapter(cwd: string, adapterName?: string, port?: nu
148
157
  }
149
158
  return available[0] ?? null;
150
159
  }
160
+
161
+ /** How the launch `program` resolves on disk. `"missing"` is reserved for
162
+ * programs the adapter creates on demand (rare); we treat them like files. */
163
+ export type LaunchProgramKind = "file" | "directory" | "missing";
164
+
165
+ /** Compute adapter-specific launch arguments that depend on the resolved
166
+ * program. Returned values are spread over `adapter.launchDefaults` so they
167
+ * take precedence over the static defaults but can still be overridden by
168
+ * the fields `DapSessionManager.launch` sets explicitly (program, cwd, args).
169
+ *
170
+ * Currently scoped to dlv, where `mode` selects how the program path is
171
+ * interpreted: directories and `.go` source files debug as a Go package
172
+ * (`mode=debug`), anything else is treated as a compiled binary (`mode=exec`).
173
+ */
174
+ export function resolveLaunchOverrides(
175
+ adapter: DapResolvedAdapter,
176
+ program: string,
177
+ programKind: LaunchProgramKind,
178
+ ): Record<string, unknown> {
179
+ if (adapter.name === "dlv") {
180
+ const extension = path.extname(program).toLowerCase();
181
+ if (programKind === "directory" || extension === ".go") {
182
+ return { mode: "debug" };
183
+ }
184
+ if (programKind === "file") {
185
+ return { mode: "exec" };
186
+ }
187
+ }
188
+ return {};
189
+ }
@@ -65,6 +65,7 @@
65
65
  "languages": ["go"],
66
66
  "fileTypes": [".go"],
67
67
  "rootMarkers": ["go.mod", "go.sum"],
68
+ "acceptsDirectoryProgram": true,
68
69
  "launchDefaults": {
69
70
  "request": "launch",
70
71
  "mode": "debug",
@@ -259,6 +259,7 @@ export class DapSessionManager {
259
259
  session.needsConfigurationDone = session.capabilities.supportsConfigurationDoneRequest === true;
260
260
  const launchArguments: DapLaunchArguments = {
261
261
  ...options.adapter.launchDefaults,
262
+ ...(options.extraLaunchArguments ?? {}),
262
263
  program: options.program,
263
264
  cwd: options.cwd,
264
265
  args: options.args,
package/src/dap/types.ts CHANGED
@@ -488,6 +488,10 @@ export interface DapAdapterConfig {
488
488
  * On Linux, connects via a unix domain socket.
489
489
  * On macOS, the adapter dials into a local TCP listener (--client-addr). */
490
490
  connectMode?: "stdio" | "socket";
491
+ /** When true, the adapter accepts a directory as the launch `program`
492
+ * (e.g. dlv treats it as a Go package path). When false/undefined, the
493
+ * debug tool rejects directory programs upfront. */
494
+ acceptsDirectoryProgram?: boolean;
491
495
  }
492
496
 
493
497
  export interface DapResolvedAdapter {
@@ -501,6 +505,7 @@ export interface DapResolvedAdapter {
501
505
  launchDefaults: Record<string, unknown>;
502
506
  attachDefaults: Record<string, unknown>;
503
507
  connectMode: "stdio" | "socket";
508
+ acceptsDirectoryProgram: boolean;
504
509
  }
505
510
 
506
511
  export interface DapBreakpointRecord {
@@ -589,6 +594,11 @@ export interface DapLaunchSessionOptions {
589
594
  program: string;
590
595
  args?: string[];
591
596
  cwd: string;
597
+ /** Per-launch overrides merged over `adapter.launchDefaults`. Used to
598
+ * inject adapter-specific values that depend on the resolved program
599
+ * (e.g. dlv's `mode` switches between `debug` and `exec` based on
600
+ * whether `program` is a Go package path or a compiled binary). */
601
+ extraLaunchArguments?: Record<string, unknown>;
592
602
  }
593
603
 
594
604
  export interface DapAttachSessionOptions {