@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
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.
- package/CHANGELOG.md +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +6 -2
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +164 -109
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +2 -3
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
|
@@ -17,6 +17,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
17
17
|
import chalk from "chalk";
|
|
18
18
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
19
19
|
import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
|
|
20
|
+
import { buildModelProviderPriorityRank } from "./model-provider-priority";
|
|
20
21
|
import { isAuthenticated, kNoAuth, MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
21
22
|
import type { Settings } from "./settings";
|
|
22
23
|
|
|
@@ -179,7 +180,9 @@ export function resolveProviderModelReference(
|
|
|
179
180
|
export interface ModelMatchPreferences {
|
|
180
181
|
/** Most-recently-used model keys (provider/modelId) to prefer when ambiguous. */
|
|
181
182
|
usageOrder?: string[];
|
|
182
|
-
/**
|
|
183
|
+
/** Provider precedence used for ambiguous unqualified model patterns. */
|
|
184
|
+
providerOrder?: readonly string[];
|
|
185
|
+
/** Providers to deprioritize when no recent usage or provider priority is available. */
|
|
183
186
|
deprioritizeProviders?: string[];
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -194,6 +197,7 @@ type RestorableModelRegistry = Pick<ModelRegistry, "getAvailable" | "find" | "ge
|
|
|
194
197
|
interface ModelPreferenceContext {
|
|
195
198
|
modelUsageRank: Map<string, number>;
|
|
196
199
|
providerUsageRank: Map<string, number>;
|
|
200
|
+
providerPriorityRank: Map<string, number>;
|
|
197
201
|
deprioritizedProviders: Set<string>;
|
|
198
202
|
modelOrder: Map<string, number>;
|
|
199
203
|
}
|
|
@@ -215,14 +219,35 @@ function buildPreferenceContext(
|
|
|
215
219
|
providerUsageRank.set(parsed.provider, i);
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
|
-
|
|
219
|
-
const deprioritizedProviders = new Set(preferences?.deprioritizeProviders ?? [
|
|
222
|
+
const providerPriorityRank = buildModelProviderPriorityRank(preferences?.providerOrder);
|
|
223
|
+
const deprioritizedProviders = new Set(preferences?.deprioritizeProviders ?? []);
|
|
220
224
|
const modelOrder = new Map<string, number>();
|
|
221
225
|
for (let i = 0; i < availableModels.length; i += 1) {
|
|
222
226
|
modelOrder.set(formatModelString(availableModels[i]), i);
|
|
223
227
|
}
|
|
224
228
|
|
|
225
|
-
return { modelUsageRank, providerUsageRank, deprioritizedProviders, modelOrder };
|
|
229
|
+
return { modelUsageRank, providerUsageRank, providerPriorityRank, deprioritizedProviders, modelOrder };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function getModelMatchPreferences(
|
|
233
|
+
settings?: Partial<Pick<Settings, "get" | "getStorage">>,
|
|
234
|
+
): ModelMatchPreferences {
|
|
235
|
+
return {
|
|
236
|
+
usageOrder: settings?.getStorage?.()?.getModelUsageOrder(),
|
|
237
|
+
providerOrder: settings?.get?.("modelProviderOrder"),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function mergeModelMatchPreferences(
|
|
242
|
+
settings: Settings | undefined,
|
|
243
|
+
preferences: ModelMatchPreferences | undefined,
|
|
244
|
+
): ModelMatchPreferences {
|
|
245
|
+
const settingsPreferences = getModelMatchPreferences(settings);
|
|
246
|
+
return {
|
|
247
|
+
usageOrder: preferences?.usageOrder ?? settingsPreferences.usageOrder,
|
|
248
|
+
providerOrder: preferences?.providerOrder ?? settingsPreferences.providerOrder,
|
|
249
|
+
deprioritizeProviders: preferences?.deprioritizeProviders,
|
|
250
|
+
};
|
|
226
251
|
}
|
|
227
252
|
|
|
228
253
|
function pickPreferredModel(candidates: Model<Api>[], context: ModelPreferenceContext): Model<Api> {
|
|
@@ -236,6 +261,12 @@ function pickPreferredModel(candidates: Model<Api>[], context: ModelPreferenceCo
|
|
|
236
261
|
return (aUsage ?? Number.POSITIVE_INFINITY) - (bUsage ?? Number.POSITIVE_INFINITY);
|
|
237
262
|
}
|
|
238
263
|
|
|
264
|
+
const aProviderPriority = context.providerPriorityRank.get(a.provider.toLowerCase());
|
|
265
|
+
const bProviderPriority = context.providerPriorityRank.get(b.provider.toLowerCase());
|
|
266
|
+
if (aProviderPriority !== undefined || bProviderPriority !== undefined) {
|
|
267
|
+
return (aProviderPriority ?? Number.POSITIVE_INFINITY) - (bProviderPriority ?? Number.POSITIVE_INFINITY);
|
|
268
|
+
}
|
|
269
|
+
|
|
239
270
|
const aProviderUsage = context.providerUsageRank.get(a.provider);
|
|
240
271
|
const bProviderUsage = context.providerUsageRank.get(b.provider);
|
|
241
272
|
if (aProviderUsage !== undefined || bProviderUsage !== undefined) {
|
|
@@ -618,8 +649,9 @@ export function resolveModelRoleValue(
|
|
|
618
649
|
}
|
|
619
650
|
|
|
620
651
|
let warning: string | undefined;
|
|
652
|
+
const matchPreferences = mergeModelMatchPreferences(options?.settings, options?.matchPreferences);
|
|
621
653
|
for (const effectivePattern of effectivePatterns) {
|
|
622
|
-
const resolved = parseModelPattern(effectivePattern, availableModels,
|
|
654
|
+
const resolved = parseModelPattern(effectivePattern, availableModels, matchPreferences, {
|
|
623
655
|
modelRegistry: options?.modelRegistry,
|
|
624
656
|
});
|
|
625
657
|
if (resolved.model) {
|
|
@@ -720,7 +752,7 @@ export function resolveModelOverride(
|
|
|
720
752
|
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
|
|
721
753
|
if (modelPatterns.length === 0) return { explicitThinkingLevel: false };
|
|
722
754
|
const availableModels = modelRegistry.getAvailable();
|
|
723
|
-
const matchPreferences =
|
|
755
|
+
const matchPreferences = getModelMatchPreferences(settings);
|
|
724
756
|
for (const pattern of modelPatterns) {
|
|
725
757
|
const { model, thinkingLevel, explicitThinkingLevel } = resolveModelRoleValue(pattern, availableModels, {
|
|
726
758
|
settings,
|
|
@@ -800,7 +832,7 @@ export function resolveRoleSelection(
|
|
|
800
832
|
availableModels: Model<Api>[],
|
|
801
833
|
modelRegistry?: CanonicalModelRegistry,
|
|
802
834
|
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
|
803
|
-
const matchPreferences =
|
|
835
|
+
const matchPreferences = getModelMatchPreferences(settings);
|
|
804
836
|
for (const role of roles) {
|
|
805
837
|
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
|
|
806
838
|
settings,
|
|
@@ -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,
|
package/src/config/settings.ts
CHANGED
|
@@ -72,7 +72,7 @@ export interface SettingsOptions {
|
|
|
72
72
|
/**
|
|
73
73
|
* Get a nested value from an object by path segments.
|
|
74
74
|
*/
|
|
75
|
-
function getByPath(obj: RawSettings, segments: string[]): unknown {
|
|
75
|
+
function getByPath(obj: RawSettings, segments: readonly string[]): unknown {
|
|
76
76
|
let current: unknown = obj;
|
|
77
77
|
for (const segment of segments) {
|
|
78
78
|
if (current === null || current === undefined || typeof current !== "object") {
|
|
@@ -83,6 +83,10 @@ function getByPath(obj: RawSettings, segments: string[]): unknown {
|
|
|
83
83
|
return current;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
const SETTING_PATH_SEGMENTS: Record<SettingPath, readonly string[]> = Object.fromEntries(
|
|
87
|
+
(Object.keys(SETTINGS_SCHEMA) as SettingPath[]).map(settingPath => [settingPath, settingPath.split(".")]),
|
|
88
|
+
) as unknown as Record<SettingPath, readonly string[]>;
|
|
89
|
+
|
|
86
90
|
/**
|
|
87
91
|
* Set a nested value in an object by path segments.
|
|
88
92
|
* Creates intermediate objects as needed.
|
|
@@ -196,6 +200,8 @@ export class Settings {
|
|
|
196
200
|
#overrides: RawSettings = {};
|
|
197
201
|
/** Merged view (global + project + overrides) */
|
|
198
202
|
#merged: RawSettings = {};
|
|
203
|
+
/** Cached resolved values from the merged view, including defaults/path scoping */
|
|
204
|
+
#resolvedCache = new Map<SettingPath, unknown>();
|
|
199
205
|
|
|
200
206
|
/** Paths modified during this session (for partial save) */
|
|
201
207
|
#modified = new Set<string>();
|
|
@@ -240,11 +246,13 @@ export class Settings {
|
|
|
240
246
|
return promise.then(
|
|
241
247
|
instance => {
|
|
242
248
|
globalInstance = instance;
|
|
249
|
+
clearBoundSettingsMethods();
|
|
243
250
|
globalInstancePromise = Promise.resolve(instance);
|
|
244
251
|
return instance;
|
|
245
252
|
},
|
|
246
253
|
error => {
|
|
247
254
|
globalInstance = null;
|
|
255
|
+
clearBoundSettingsMethods();
|
|
248
256
|
throw error;
|
|
249
257
|
},
|
|
250
258
|
);
|
|
@@ -280,13 +288,15 @@ export class Settings {
|
|
|
280
288
|
* Returns the merged value from global + project + overrides, or the default.
|
|
281
289
|
*/
|
|
282
290
|
get<P extends SettingPath>(path: P): SettingValue<P> {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (value !== undefined) {
|
|
286
|
-
const pathScopedValue = resolvePathScopedStringArray(path, value, this.#cwd);
|
|
287
|
-
return (pathScopedValue ?? value) as SettingValue<P>;
|
|
291
|
+
if (this.#resolvedCache.has(path)) {
|
|
292
|
+
return this.#resolvedCache.get(path) as SettingValue<P>;
|
|
288
293
|
}
|
|
289
|
-
|
|
294
|
+
|
|
295
|
+
const value = getByPath(this.#merged, SETTING_PATH_SEGMENTS[path]);
|
|
296
|
+
const resolved =
|
|
297
|
+
value !== undefined ? (resolvePathScopedStringArray(path, value, this.#cwd) ?? value) : getDefault(path);
|
|
298
|
+
this.#resolvedCache.set(path, resolved);
|
|
299
|
+
return resolved as SettingValue<P>;
|
|
290
300
|
}
|
|
291
301
|
|
|
292
302
|
/**
|
|
@@ -300,6 +310,7 @@ export class Settings {
|
|
|
300
310
|
setByPath(this.#global, segments, value);
|
|
301
311
|
this.#modified.add(path);
|
|
302
312
|
this.#rebuildMerged();
|
|
313
|
+
const next = this.get(path);
|
|
303
314
|
this.#queueSave();
|
|
304
315
|
|
|
305
316
|
// Trigger hook if exists
|
|
@@ -307,21 +318,25 @@ export class Settings {
|
|
|
307
318
|
if (hook) {
|
|
308
319
|
hook(value, prev);
|
|
309
320
|
}
|
|
321
|
+
this.#fireEffectiveSettingChanged(path, next, prev);
|
|
310
322
|
}
|
|
311
323
|
|
|
312
324
|
/**
|
|
313
325
|
* Apply runtime overrides (not persisted).
|
|
314
326
|
*/
|
|
315
327
|
override<P extends SettingPath>(path: P, value: SettingValue<P>): void {
|
|
328
|
+
const prev = this.get(path);
|
|
316
329
|
const segments = path.split(".");
|
|
317
330
|
setByPath(this.#overrides, segments, value);
|
|
318
331
|
this.#rebuildMerged();
|
|
332
|
+
this.#fireEffectiveSettingChanged(path, this.get(path), prev);
|
|
319
333
|
}
|
|
320
334
|
|
|
321
335
|
/**
|
|
322
336
|
* Clear a runtime override.
|
|
323
337
|
*/
|
|
324
338
|
clearOverride(path: SettingPath): void {
|
|
339
|
+
const prev = this.get(path);
|
|
325
340
|
const segments = path.split(".");
|
|
326
341
|
let current = this.#overrides;
|
|
327
342
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
@@ -331,6 +346,14 @@ export class Settings {
|
|
|
331
346
|
}
|
|
332
347
|
delete current[segments[segments.length - 1]];
|
|
333
348
|
this.#rebuildMerged();
|
|
349
|
+
this.#fireEffectiveSettingChanged(path, this.get(path), prev);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#fireEffectiveSettingChanged(path: SettingPath, value: unknown, prev: unknown): void {
|
|
353
|
+
if (Object.is(value, prev)) return;
|
|
354
|
+
if (path === "statusLine.sessionAccent") {
|
|
355
|
+
statusLineSessionAccentSignal.fire();
|
|
356
|
+
}
|
|
334
357
|
}
|
|
335
358
|
|
|
336
359
|
/**
|
|
@@ -840,6 +863,7 @@ export class Settings {
|
|
|
840
863
|
#rebuildMerged(): void {
|
|
841
864
|
this.#merged = this.#deepMerge(this.#deepMerge({}, this.#global), this.#project);
|
|
842
865
|
this.#merged = this.#deepMerge(this.#merged, this.#overrides);
|
|
866
|
+
this.#resolvedCache.clear();
|
|
843
867
|
}
|
|
844
868
|
|
|
845
869
|
#fireAllHooks(): void {
|
|
@@ -883,6 +907,45 @@ export class Settings {
|
|
|
883
907
|
|
|
884
908
|
type SettingHook<P extends SettingPath> = (value: SettingValue<P>, prev: SettingValue<P>) => void;
|
|
885
909
|
|
|
910
|
+
/**
|
|
911
|
+
* Minimal change-notification primitive backing the exported `on*Changed`
|
|
912
|
+
* subscriptions. Holds a listener set, hands out unsubscribe closures, and
|
|
913
|
+
* isolates errors so a single throwing listener can't abort the rest or bubble
|
|
914
|
+
* out of `Settings.set()`.
|
|
915
|
+
*
|
|
916
|
+
* @typeParam A - argument tuple forwarded to each listener on `fire`.
|
|
917
|
+
*/
|
|
918
|
+
class SettingSignal<A extends unknown[] = []> {
|
|
919
|
+
#listeners = new Set<(...args: A) => void>();
|
|
920
|
+
|
|
921
|
+
constructor(private readonly label: string) {}
|
|
922
|
+
|
|
923
|
+
/** Subscribe `cb`; returns an unsubscribe function. */
|
|
924
|
+
on(cb: (...args: A) => void): () => void {
|
|
925
|
+
this.#listeners.add(cb);
|
|
926
|
+
return () => {
|
|
927
|
+
this.#listeners.delete(cb);
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Invoke every listener with `args`. Iterates a snapshot so a listener may
|
|
933
|
+
* (un)subscribe mid-fire without re-entrancy — the Hindsight backend
|
|
934
|
+
* re-registers the fresh state's listener on every rebuild — and wraps each
|
|
935
|
+
* call so a throwing listener is logged and skipped instead of aborting the
|
|
936
|
+
* rest.
|
|
937
|
+
*/
|
|
938
|
+
fire(...args: A): void {
|
|
939
|
+
for (const cb of [...this.#listeners]) {
|
|
940
|
+
try {
|
|
941
|
+
cb(...args);
|
|
942
|
+
} catch (err) {
|
|
943
|
+
logger.warn(`Settings: ${this.label} hook failed`, { error: String(err) });
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
886
949
|
const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
|
|
887
950
|
"theme.dark": value => {
|
|
888
951
|
if (typeof value === "string") {
|
|
@@ -915,45 +978,34 @@ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
|
|
|
915
978
|
},
|
|
916
979
|
"provider.appendOnlyContext": value => {
|
|
917
980
|
if (typeof value === "string") {
|
|
918
|
-
|
|
981
|
+
appendOnlyModeSignal.fire(value);
|
|
919
982
|
}
|
|
920
983
|
},
|
|
921
|
-
"hindsight.bankId": () =>
|
|
922
|
-
"hindsight.bankIdPrefix": () =>
|
|
923
|
-
"hindsight.scoping": () =>
|
|
984
|
+
"hindsight.bankId": () => hindsightScopeSignal.fire(),
|
|
985
|
+
"hindsight.bankIdPrefix": () => hindsightScopeSignal.fire(),
|
|
986
|
+
"hindsight.scoping": () => hindsightScopeSignal.fire(),
|
|
924
987
|
};
|
|
925
|
-
/**
|
|
926
|
-
const
|
|
988
|
+
/** Fires when `provider.appendOnlyContext` changes at runtime. */
|
|
989
|
+
const appendOnlyModeSignal = new SettingSignal<[value: string]>("provider.appendOnlyContext");
|
|
927
990
|
|
|
928
991
|
/**
|
|
929
992
|
* Subscribe to append-only mode setting changes.
|
|
930
993
|
* Returns an unsubscribe function. Multiple sessions (main + subagents)
|
|
931
994
|
* can register independently without overwriting each other.
|
|
932
995
|
*/
|
|
933
|
-
export
|
|
934
|
-
appendOnlyModeCallbacks.add(cb);
|
|
935
|
-
return () => {
|
|
936
|
-
appendOnlyModeCallbacks.delete(cb);
|
|
937
|
-
};
|
|
938
|
-
}
|
|
996
|
+
export const onAppendOnlyModeChanged = (cb: (value: string) => void) => appendOnlyModeSignal.on(cb);
|
|
939
997
|
|
|
940
|
-
/**
|
|
941
|
-
const
|
|
998
|
+
/** Fires when `statusLine.sessionAccent` changes at runtime. */
|
|
999
|
+
const statusLineSessionAccentSignal = new SettingSignal("statusLine.sessionAccent");
|
|
942
1000
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
cb();
|
|
952
|
-
} catch (err) {
|
|
953
|
-
logger.warn("Settings: hindsight scope hook failed", { error: String(err) });
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Subscribe to session-accent setting changes.
|
|
1003
|
+
* Returns an unsubscribe function. Callers should re-read settings in the callback.
|
|
1004
|
+
*/
|
|
1005
|
+
export const onStatusLineSessionAccentChanged = (cb: () => void) => statusLineSessionAccentSignal.on(cb);
|
|
1006
|
+
|
|
1007
|
+
/** Fires when any `hindsight.bankId` / `bankIdPrefix` / `scoping` value changes. */
|
|
1008
|
+
const hindsightScopeSignal = new SettingSignal("hindsight scope");
|
|
957
1009
|
|
|
958
1010
|
/**
|
|
959
1011
|
* Subscribe to changes in the Hindsight bank-scoping settings. Lets the
|
|
@@ -965,12 +1017,7 @@ function fireHindsightScopeChanged(): void {
|
|
|
965
1017
|
* Returns an unsubscribe function. The callback receives no arguments — the
|
|
966
1018
|
* caller is expected to re-read the relevant settings via `Settings.get`.
|
|
967
1019
|
*/
|
|
968
|
-
export
|
|
969
|
-
hindsightScopeCallbacks.add(cb);
|
|
970
|
-
return () => {
|
|
971
|
-
hindsightScopeCallbacks.delete(cb);
|
|
972
|
-
};
|
|
973
|
-
}
|
|
1020
|
+
export const onHindsightScopeChanged = (cb: () => void) => hindsightScopeSignal.on(cb);
|
|
974
1021
|
|
|
975
1022
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
976
1023
|
// Global Singleton
|
|
@@ -978,6 +1025,13 @@ export function onHindsightScopeChanged(cb: () => void): () => void {
|
|
|
978
1025
|
|
|
979
1026
|
let globalInstance: Settings | null = null;
|
|
980
1027
|
let globalInstancePromise: Promise<Settings> | null = null;
|
|
1028
|
+
let boundSettingsInstance: Settings | null = null;
|
|
1029
|
+
let boundSettingsMethods = new Map<PropertyKey, unknown>();
|
|
1030
|
+
|
|
1031
|
+
function clearBoundSettingsMethods(): void {
|
|
1032
|
+
boundSettingsInstance = null;
|
|
1033
|
+
boundSettingsMethods = new Map<PropertyKey, unknown>();
|
|
1034
|
+
}
|
|
981
1035
|
|
|
982
1036
|
export function isSettingsInitialized(): boolean {
|
|
983
1037
|
return globalInstance !== null;
|
|
@@ -990,6 +1044,7 @@ export function isSettingsInitialized(): boolean {
|
|
|
990
1044
|
export function resetSettingsForTest(): void {
|
|
991
1045
|
globalInstance = null;
|
|
992
1046
|
globalInstancePromise = null;
|
|
1047
|
+
clearBoundSettingsMethods();
|
|
993
1048
|
}
|
|
994
1049
|
|
|
995
1050
|
/**
|
|
@@ -1001,9 +1056,17 @@ export const settings = new Proxy({} as Settings, {
|
|
|
1001
1056
|
if (!globalInstance) {
|
|
1002
1057
|
throw new Error("Settings not initialized. Call Settings.init() first.");
|
|
1003
1058
|
}
|
|
1004
|
-
|
|
1059
|
+
if (boundSettingsInstance !== globalInstance) {
|
|
1060
|
+
clearBoundSettingsMethods();
|
|
1061
|
+
boundSettingsInstance = globalInstance;
|
|
1062
|
+
}
|
|
1063
|
+
const value = (globalInstance as unknown as Record<PropertyKey, unknown>)[prop];
|
|
1005
1064
|
if (typeof value === "function") {
|
|
1006
|
-
|
|
1065
|
+
const cached = boundSettingsMethods.get(prop);
|
|
1066
|
+
if (cached) return cached;
|
|
1067
|
+
const bound = value.bind(globalInstance);
|
|
1068
|
+
boundSettingsMethods.set(prop, bound);
|
|
1069
|
+
return bound;
|
|
1007
1070
|
}
|
|
1008
1071
|
return value;
|
|
1009
1072
|
},
|
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(
|
|
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
|
|
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
|
+
}
|
package/src/dap/defaults.json
CHANGED
package/src/dap/session.ts
CHANGED
|
@@ -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 {
|