@oh-my-pi/pi-coding-agent 15.11.3 → 15.11.4
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 +54 -0
- package/dist/cli.js +353 -294
- package/dist/types/config/api-key-resolver.d.ts +9 -3
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-discovery.d.ts +6 -4
- package/dist/types/config/model-registry.d.ts +7 -4
- package/dist/types/config/settings-schema.d.ts +458 -155
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/mnemopi/config.d.ts +3 -1
- package/dist/types/modes/components/settings-defs.d.ts +9 -2
- package/dist/types/modes/components/settings-selector.d.ts +9 -4
- package/dist/types/modes/components/tool-execution.d.ts +12 -1
- package/dist/types/modes/components/transcript-container.d.ts +12 -0
- package/dist/types/modes/controllers/input-controller.d.ts +9 -1
- package/dist/types/modes/theme/theme.d.ts +23 -3
- package/dist/types/session/agent-session.d.ts +14 -7
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/snapcompact-inline.d.ts +28 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/task/render.d.ts +16 -6
- package/dist/types/tools/gh.d.ts +3 -0
- package/dist/types/tools/render-utils.d.ts +8 -16
- package/dist/types/utils/session-color.d.ts +15 -3
- package/dist/types/web/kagi.d.ts +1 -2
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +9 -6
- package/package.json +11 -11
- package/src/auto-thinking/classifier.ts +1 -5
- package/src/commit/model-selection.ts +3 -6
- package/src/config/api-key-resolver.ts +10 -3
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-discovery.ts +60 -46
- package/src/config/model-registry.ts +21 -8
- package/src/config/model-resolver.ts +57 -3
- package/src/config/settings-schema.ts +601 -153
- package/src/eval/completion-bridge.ts +1 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +13 -6
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/issue-pr-protocol.ts +10 -4
- package/src/memories/index.ts +2 -10
- package/src/mnemopi/backend.ts +30 -8
- package/src/mnemopi/config.ts +6 -1
- package/src/mnemopi/state.ts +6 -0
- package/src/modes/components/extensions/inspector-panel.ts +6 -2
- package/src/modes/components/plan-review-overlay.ts +15 -17
- package/src/modes/components/plugin-settings.ts +22 -5
- package/src/modes/components/settings-defs.ts +19 -4
- package/src/modes/components/settings-selector.ts +493 -93
- package/src/modes/components/status-line/component.ts +3 -1
- package/src/modes/components/status-line/segments.ts +3 -1
- package/src/modes/components/tool-execution.ts +69 -12
- package/src/modes/components/transcript-container.ts +26 -0
- package/src/modes/components/tree-selector.ts +16 -6
- package/src/modes/controllers/command-controller.ts +37 -7
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +68 -6
- package/src/modes/controllers/selector-controller.ts +81 -61
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/rpc/rpc-mode.ts +2 -1
- package/src/modes/shared.ts +2 -0
- package/src/modes/theme/theme.ts +100 -7
- package/src/modes/utils/context-usage.ts +3 -1
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +9 -5
- package/src/prompts/system/personalities/default.md +26 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/system-prompt.md +5 -22
- package/src/prompts/tools/task.md +3 -3
- package/src/sdk.ts +22 -1
- package/src/session/agent-session.ts +91 -24
- package/src/session/auth-storage.ts +1 -0
- package/src/session/session-dump-format.ts +8 -1
- package/src/session/session-manager.ts +5 -5
- package/src/session/snapcompact-inline.ts +187 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +24 -3
- package/src/system-prompt.ts +15 -1
- package/src/task/render.ts +29 -19
- package/src/tool-discovery/tool-index.ts +2 -0
- package/src/tools/bash.ts +10 -3
- package/src/tools/eval-render.ts +13 -8
- package/src/tools/gh.ts +39 -1
- package/src/tools/image-gen.ts +114 -78
- package/src/tools/inspect-image.ts +1 -5
- package/src/tools/job.ts +25 -5
- package/src/tools/read.ts +1 -57
- package/src/tools/render-utils.ts +29 -31
- package/src/tools/ssh.ts +3 -3
- package/src/tools/tts.ts +40 -20
- package/src/utils/clipboard.ts +56 -4
- package/src/utils/commit-message-generator.ts +1 -5
- package/src/utils/session-color.ts +83 -9
- package/src/utils/title-generator.ts +1 -1
- package/src/web/kagi.ts +26 -27
- package/src/web/search/providers/codex.ts +42 -40
- package/src/web/search/providers/gemini.ts +42 -22
- package/src/web/search/providers/perplexity.ts +22 -10
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getOrFetchIssue,
|
|
24
24
|
getOrFetchPr,
|
|
25
25
|
getOrFetchPrDiff,
|
|
26
|
+
githubIssueJsonWithStateReasonFallback,
|
|
26
27
|
type PrDiffFile,
|
|
27
28
|
parsePositiveDecimalInt,
|
|
28
29
|
resolveDefaultRepoMemoized,
|
|
@@ -294,7 +295,7 @@ async function fetchAndRenderList(
|
|
|
294
295
|
const cwd = resolveCwd(context);
|
|
295
296
|
const fields =
|
|
296
297
|
scheme === "issue"
|
|
297
|
-
? ["number", "title", "state", "
|
|
298
|
+
? ["number", "title", "state", "author", "labels", "createdAt", "updatedAt", "url"]
|
|
298
299
|
: [
|
|
299
300
|
"number",
|
|
300
301
|
"title",
|
|
@@ -323,9 +324,14 @@ async function fetchAndRenderList(
|
|
|
323
324
|
if (options.author) args.push("--author", options.author);
|
|
324
325
|
if (options.label) args.push("--label", options.label);
|
|
325
326
|
|
|
326
|
-
const items =
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
const items =
|
|
328
|
+
scheme === "issue"
|
|
329
|
+
? await githubIssueJsonWithStateReasonFallback<Array<IssueListItem>>(cwd, args, context?.signal, {
|
|
330
|
+
repoProvided: true,
|
|
331
|
+
})
|
|
332
|
+
: await git.github.json<Array<PrListItem>>(cwd, args, context?.signal, {
|
|
333
|
+
repoProvided: true,
|
|
334
|
+
});
|
|
329
335
|
const header =
|
|
330
336
|
scheme === "issue"
|
|
331
337
|
? `# Issues in ${repo} (${options.state}, up to ${options.limit})`
|
package/src/memories/index.ts
CHANGED
|
@@ -274,11 +274,7 @@ async function runPhase1(options: {
|
|
|
274
274
|
const result = await runStage1Job({
|
|
275
275
|
claim,
|
|
276
276
|
model: phase1Model,
|
|
277
|
-
apiKey: modelRegistry.resolver(phase1Model.
|
|
278
|
-
sessionId: session.sessionId,
|
|
279
|
-
baseUrl: phase1Model.baseUrl,
|
|
280
|
-
modelId: phase1Model.id,
|
|
281
|
-
}),
|
|
277
|
+
apiKey: modelRegistry.resolver(phase1Model, session.sessionId),
|
|
282
278
|
modelMaxTokens: computeModelTokenBudget(phase1Model, config),
|
|
283
279
|
config,
|
|
284
280
|
metadata: session.agent?.metadataForProvider(phase1Model.provider),
|
|
@@ -435,11 +431,7 @@ async function runPhase2(options: {
|
|
|
435
431
|
const consolidated = await runConsolidationModel({
|
|
436
432
|
memoryRoot,
|
|
437
433
|
model: phase2Model,
|
|
438
|
-
apiKey: modelRegistry.resolver(phase2Model.
|
|
439
|
-
sessionId: session.sessionId,
|
|
440
|
-
baseUrl: phase2Model.baseUrl,
|
|
441
|
-
modelId: phase2Model.id,
|
|
442
|
-
}),
|
|
434
|
+
apiKey: modelRegistry.resolver(phase2Model, session.sessionId),
|
|
443
435
|
metadata: session.agent?.metadataForProvider(phase2Model.provider),
|
|
444
436
|
});
|
|
445
437
|
await applyConsolidation(memoryRoot, consolidated);
|
package/src/mnemopi/backend.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { type ApiKeyResolver, completeSimple } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { hostMatchesUrl } from "@oh-my-pi/pi-catalog/hosts";
|
|
4
5
|
import type { Mnemopi } from "@oh-my-pi/pi-mnemopi";
|
|
5
6
|
import type * as MnemopiDiagnoseNs from "@oh-my-pi/pi-mnemopi/diagnose";
|
|
6
7
|
import type { DiagnosticSummary } from "@oh-my-pi/pi-mnemopi/diagnose";
|
|
@@ -433,6 +434,25 @@ async function loadMnemopiConfigWithProviders(
|
|
|
433
434
|
return config;
|
|
434
435
|
}
|
|
435
436
|
|
|
437
|
+
/**
|
|
438
|
+
* When mnemopi targets OpenRouter (its default embedding host) without a
|
|
439
|
+
* user-pinned key, hand it the central {@link ApiKeyResolver} so requests pick
|
|
440
|
+
* up AuthStorage credentials, force-refresh on 401, and rotate across sibling
|
|
441
|
+
* keys. Returns undefined when the URL points elsewhere or when no OpenRouter
|
|
442
|
+
* credential exists, preserving mnemopi's env-key fallback and its
|
|
443
|
+
* "no key -> API embeddings unavailable" gating.
|
|
444
|
+
*/
|
|
445
|
+
async function openrouterKeyResolver(
|
|
446
|
+
modelRegistry: ModelRegistry,
|
|
447
|
+
sessionId: string,
|
|
448
|
+
baseUrl: string | undefined,
|
|
449
|
+
): Promise<ApiKeyResolver | undefined> {
|
|
450
|
+
if (baseUrl !== undefined && !hostMatchesUrl(baseUrl, "openrouter")) return undefined;
|
|
451
|
+
const key = await modelRegistry.getApiKeyForProvider("openrouter", sessionId);
|
|
452
|
+
if (key === undefined || key === "") return undefined;
|
|
453
|
+
return modelRegistry.resolver("openrouter", { sessionId });
|
|
454
|
+
}
|
|
455
|
+
|
|
436
456
|
async function resolveMnemopiProviderOptions(
|
|
437
457
|
config: MnemopiBackendConfig,
|
|
438
458
|
settings: MemoryBackendStartOptions["settings"],
|
|
@@ -443,7 +463,9 @@ async function resolveMnemopiProviderOptions(
|
|
|
443
463
|
noEmbeddings: config.providerOptions.noEmbeddings,
|
|
444
464
|
embeddingModel: config.providerOptions.embeddingModel,
|
|
445
465
|
embeddingApiUrl: config.providerOptions.embeddingApiUrl,
|
|
446
|
-
embeddingApiKey:
|
|
466
|
+
embeddingApiKey:
|
|
467
|
+
config.providerOptions.embeddingApiKey ??
|
|
468
|
+
(await openrouterKeyResolver(modelRegistry, sessionId, config.providerOptions.embeddingApiUrl)),
|
|
447
469
|
llm: false,
|
|
448
470
|
};
|
|
449
471
|
|
|
@@ -469,7 +491,11 @@ async function resolveMnemopiProviderOptions(
|
|
|
469
491
|
...base,
|
|
470
492
|
llm: {
|
|
471
493
|
baseUrl: config.llmBaseUrl,
|
|
472
|
-
apiKey:
|
|
494
|
+
apiKey:
|
|
495
|
+
config.llmApiKey ??
|
|
496
|
+
(config.llmBaseUrl === undefined
|
|
497
|
+
? undefined
|
|
498
|
+
: await openrouterKeyResolver(modelRegistry, sessionId, config.llmBaseUrl)),
|
|
473
499
|
model: config.llmModel,
|
|
474
500
|
},
|
|
475
501
|
};
|
|
@@ -499,11 +525,7 @@ async function resolveMnemopiProviderOptions(
|
|
|
499
525
|
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
500
526
|
},
|
|
501
527
|
{
|
|
502
|
-
apiKey: modelRegistry.resolver(model
|
|
503
|
-
sessionId,
|
|
504
|
-
baseUrl: model.baseUrl,
|
|
505
|
-
modelId: model.id,
|
|
506
|
-
}),
|
|
528
|
+
apiKey: modelRegistry.resolver(model, sessionId),
|
|
507
529
|
maxTokens: opts?.maxTokens,
|
|
508
530
|
temperature: opts?.temperature,
|
|
509
531
|
},
|
package/src/mnemopi/config.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type MnemopiScoping = "global" | "per-project" | "per-project-tagged";
|
|
|
10
10
|
|
|
11
11
|
export type MnemopiProviderOptions = Pick<
|
|
12
12
|
MnemopiOptions,
|
|
13
|
-
"noEmbeddings" | "embeddingModel" | "embeddingApiUrl" | "embeddingApiKey" | "llm"
|
|
13
|
+
"noEmbeddings" | "embeddingModel" | "embeddingApiUrl" | "embeddingApiKey" | "llm" | "debug"
|
|
14
14
|
>;
|
|
15
15
|
|
|
16
16
|
export interface MnemopiBackendConfig {
|
|
@@ -23,6 +23,8 @@ export interface MnemopiBackendConfig {
|
|
|
23
23
|
scoping?: MnemopiScoping;
|
|
24
24
|
autoRecall: boolean;
|
|
25
25
|
autoRetain: boolean;
|
|
26
|
+
polyphonicRecall: boolean;
|
|
27
|
+
enhancedRecall: boolean;
|
|
26
28
|
retainEveryNTurns: number;
|
|
27
29
|
recallLimit: number;
|
|
28
30
|
recallContextTurns: number;
|
|
@@ -52,6 +54,8 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
|
|
|
52
54
|
scoping,
|
|
53
55
|
autoRecall: settings.get("mnemopi.autoRecall"),
|
|
54
56
|
autoRetain: settings.get("mnemopi.autoRetain"),
|
|
57
|
+
polyphonicRecall: settings.get("mnemopi.polyphonicRecall"),
|
|
58
|
+
enhancedRecall: settings.get("mnemopi.enhancedRecall"),
|
|
55
59
|
retainEveryNTurns: Math.max(1, Math.floor(settings.get("mnemopi.retainEveryNTurns"))),
|
|
56
60
|
recallLimit: Math.max(1, Math.floor(settings.get("mnemopi.recallLimit"))),
|
|
57
61
|
recallContextTurns: Math.max(1, Math.floor(settings.get("mnemopi.recallContextTurns"))),
|
|
@@ -60,6 +64,7 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
|
|
|
60
64
|
debug: settings.get("mnemopi.debug"),
|
|
61
65
|
providerOptions: {
|
|
62
66
|
noEmbeddings: settings.get("mnemopi.noEmbeddings"),
|
|
67
|
+
debug: settings.get("mnemopi.debug"),
|
|
63
68
|
embeddingModel: settings.get("mnemopi.embeddingModel"),
|
|
64
69
|
embeddingApiUrl: settings.get("mnemopi.embeddingApiUrl"),
|
|
65
70
|
embeddingApiKey: settings.get("mnemopi.embeddingApiKey"),
|
package/src/mnemopi/state.ts
CHANGED
|
@@ -421,6 +421,12 @@ export class MnemopiSessionState {
|
|
|
421
421
|
// `per-project-tagged` is implemented by opening both the project bank and the
|
|
422
422
|
// shared bank, then merging recall results while keeping writes project-local.
|
|
423
423
|
function createScopedResources(config: MnemopiBackendConfig): MnemopiScopedResources {
|
|
424
|
+
// Env vars (MNEMOPI_POLYPHONIC_RECALL / MNEMOPI_ENHANCED_RECALL) still override
|
|
425
|
+
// these config-driven defaults inside the core gates.
|
|
426
|
+
requireMnemopi().configureRecallFeatures({
|
|
427
|
+
polyphonicRecall: config.polyphonicRecall,
|
|
428
|
+
enhancedRecall: config.enhancedRecall,
|
|
429
|
+
});
|
|
424
430
|
const banks = resolveScopedBanks(config);
|
|
425
431
|
const memories = new Map<string, MnemopiScopedMemory>();
|
|
426
432
|
const open = (bank: string): MnemopiScopedMemory => {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Shows name, description, origin, status, and kind-specific preview.
|
|
5
5
|
*/
|
|
6
6
|
import * as os from "node:os";
|
|
7
|
+
import { isZodSchema, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
7
8
|
import { type Component, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
8
9
|
import { theme } from "../../../modes/theme/theme";
|
|
9
10
|
import { shortenPath } from "../../../tools/render-utils";
|
|
@@ -168,12 +169,15 @@ export class InspectorPanel implements Component {
|
|
|
168
169
|
|
|
169
170
|
try {
|
|
170
171
|
const tool = raw as any;
|
|
171
|
-
const
|
|
172
|
+
const wire = (s: unknown): any => (isZodSchema(s) ? zodToWireSchema(s) : s);
|
|
173
|
+
const paramSchema = wire(tool?.parameters);
|
|
174
|
+
const inputSchema = wire(tool?.inputSchema);
|
|
175
|
+
const params = paramSchema?.properties || inputSchema?.properties || {};
|
|
172
176
|
|
|
173
177
|
if (Object.keys(params).length === 0) {
|
|
174
178
|
lines.push(theme.fg("dim", " (no arguments)"));
|
|
175
179
|
} else {
|
|
176
|
-
const required = new Set(
|
|
180
|
+
const required = new Set(paramSchema?.required || inputSchema?.required || []);
|
|
177
181
|
|
|
178
182
|
for (const [name, spec] of Object.entries(params)) {
|
|
179
183
|
const param = spec as any;
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
Markdown,
|
|
24
24
|
type MarkdownTheme,
|
|
25
25
|
matchesKey,
|
|
26
|
+
parseSgrMouse,
|
|
26
27
|
ScrollView,
|
|
27
28
|
truncateToWidth,
|
|
28
29
|
visibleWidth,
|
|
@@ -141,7 +142,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
141
142
|
#optionClickRows = new Map<number, number>();
|
|
142
143
|
#tocClickRows = new Map<number, number>();
|
|
143
144
|
#bodyClickRows = new Set<number>();
|
|
144
|
-
/**
|
|
145
|
+
/** Exclusive 0-based column bound below which a region-row click targets the sidebar. */
|
|
145
146
|
#sidebarClickMaxCol = 0;
|
|
146
147
|
/** Option index the pointer is currently hovering, or undefined. Updated from
|
|
147
148
|
* motion mouse reports and cleared when the pointer leaves the option rows. */
|
|
@@ -332,26 +333,23 @@ export class PlanReviewOverlay implements Component {
|
|
|
332
333
|
* the body.
|
|
333
334
|
*/
|
|
334
335
|
#handleMouse(data: string): boolean {
|
|
335
|
-
const
|
|
336
|
-
if (!
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (button & 64) {
|
|
341
|
-
// Scroll wheel: low bit selects direction (64 up, 65 down).
|
|
342
|
-
this.#scrollView.scroll(button & 1 ? 3 : -3);
|
|
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);
|
|
343
341
|
return true;
|
|
344
342
|
}
|
|
345
|
-
if (
|
|
346
|
-
if (
|
|
343
|
+
if (event.release) return true;
|
|
344
|
+
if (event.motion) {
|
|
347
345
|
// Motion (hover or drag): light up the option row under the pointer so a
|
|
348
346
|
// mouse user gets the same affordance the keyboard cursor gives. Any
|
|
349
347
|
// non-option row clears the highlight.
|
|
350
|
-
this.#setHoveredOption(this.#optionClickRows.get(row));
|
|
348
|
+
this.#setHoveredOption(this.#optionClickRows.get(event.row));
|
|
351
349
|
return true;
|
|
352
350
|
}
|
|
353
|
-
if (
|
|
354
|
-
const optionIndex = this.#optionClickRows.get(row);
|
|
351
|
+
if (!event.leftClick) return true;
|
|
352
|
+
const optionIndex = this.#optionClickRows.get(event.row);
|
|
355
353
|
if (optionIndex !== undefined) {
|
|
356
354
|
if (!this.#disabled.has(optionIndex)) {
|
|
357
355
|
this.#focus = "actions";
|
|
@@ -360,14 +358,14 @@ export class PlanReviewOverlay implements Component {
|
|
|
360
358
|
}
|
|
361
359
|
return true;
|
|
362
360
|
}
|
|
363
|
-
const tocPos = this.#tocClickRows.get(row);
|
|
364
|
-
if (tocPos !== undefined &&
|
|
361
|
+
const tocPos = this.#tocClickRows.get(event.row);
|
|
362
|
+
if (tocPos !== undefined && event.col < this.#sidebarClickMaxCol) {
|
|
365
363
|
this.#focus = "toc";
|
|
366
364
|
this.#tocCursor = tocPos;
|
|
367
365
|
this.#scrubBodyToToc();
|
|
368
366
|
return true;
|
|
369
367
|
}
|
|
370
|
-
if (this.#bodyClickRows.has(row)) {
|
|
368
|
+
if (this.#bodyClickRows.has(event.row)) {
|
|
371
369
|
this.#setFocus("body");
|
|
372
370
|
}
|
|
373
371
|
return true;
|
|
@@ -629,11 +629,18 @@ export class PluginSettingsComponent extends Container {
|
|
|
629
629
|
this.#currentMarketplacePlugin = null;
|
|
630
630
|
this.clear();
|
|
631
631
|
|
|
632
|
-
// Surface
|
|
633
|
-
//
|
|
634
|
-
//
|
|
632
|
+
// Surface registry failures without taking the whole tab down — either
|
|
633
|
+
// registry can fail to load (corrupt JSON, missing project root) and the
|
|
634
|
+
// user still benefits from the other half. An uncaught rejection here
|
|
635
|
+
// would also leave the tab permanently blank: this method is invoked
|
|
636
|
+
// fire-and-forget from the constructor, so nothing awaits it.
|
|
635
637
|
const [npmPlugins, marketplacePlugins] = await Promise.all([
|
|
636
|
-
this.#manager.list()
|
|
638
|
+
this.#manager.list().catch(err => {
|
|
639
|
+
logger.error("Settings → Plugins: failed to list npm plugins", {
|
|
640
|
+
error: err instanceof Error ? err.message : String(err),
|
|
641
|
+
});
|
|
642
|
+
return [] as InstalledPlugin[];
|
|
643
|
+
}),
|
|
637
644
|
this.#buildMarketplaceManager()
|
|
638
645
|
.then(mgr => mgr.listInstalledPlugins())
|
|
639
646
|
.catch(err => {
|
|
@@ -717,6 +724,16 @@ export class PluginSettingsComponent extends Container {
|
|
|
717
724
|
}
|
|
718
725
|
|
|
719
726
|
handleInput(data: string): void {
|
|
720
|
-
this.#viewComponent
|
|
727
|
+
if (!this.#viewComponent) {
|
|
728
|
+
// The list view mounts asynchronously (npm + marketplace listing).
|
|
729
|
+
// Until it does — or if listing rejected and no view ever mounted —
|
|
730
|
+
// Escape must still close the panel instead of leaving /settings
|
|
731
|
+
// non-dismissible.
|
|
732
|
+
if (data === "\x1b" || data === "\x1b\x1b") {
|
|
733
|
+
this.callbacks.onClose();
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
this.#viewComponent.handleInput(data);
|
|
721
738
|
}
|
|
722
739
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* settings selector.
|
|
5
5
|
*
|
|
6
6
|
* To add a new setting to the UI: declare it in `settings-schema.ts`
|
|
7
|
-
* with a `ui` block
|
|
7
|
+
* with a `ui` block carrying `tab` and `group` (the group must be listed
|
|
8
|
+
* in `TAB_GROUPS[tab]`). If it needs a submenu, include `options: [...]`
|
|
8
9
|
* (or `options: "runtime"` for runtime-injected lists like themes).
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
type SettingPath,
|
|
22
23
|
type SettingTab,
|
|
23
24
|
type SubmenuOption,
|
|
25
|
+
TAB_GROUPS,
|
|
24
26
|
} from "../../config/settings-schema";
|
|
25
27
|
|
|
26
28
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -34,6 +36,8 @@ interface BaseSettingDef {
|
|
|
34
36
|
label: string;
|
|
35
37
|
description: string;
|
|
36
38
|
tab: SettingTab;
|
|
39
|
+
/** Section within the tab; items are ordered by TAB_GROUPS[tab] and rendered under a heading row. */
|
|
40
|
+
group?: string;
|
|
37
41
|
/**
|
|
38
42
|
* Optional visibility predicate. When supplied and returning false, the
|
|
39
43
|
* setting is hidden from the UI. Applies to every variant — booleans,
|
|
@@ -111,7 +115,7 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
111
115
|
|
|
112
116
|
const schemaType = getType(path);
|
|
113
117
|
const condition = ui.condition ? CONDITIONS[ui.condition] : undefined;
|
|
114
|
-
const base = { path, label: ui.label, description: ui.description, tab: ui.tab, condition };
|
|
118
|
+
const base = { path, label: ui.label, description: ui.description, tab: ui.tab, group: ui.group, condition };
|
|
115
119
|
|
|
116
120
|
if (schemaType === "boolean") {
|
|
117
121
|
return { ...base, type: "boolean" };
|
|
@@ -170,9 +174,20 @@ export function getAllSettingDefs(): SettingDef[] {
|
|
|
170
174
|
return defs;
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* Get settings for a specific tab, ordered by the tab's group layout
|
|
179
|
+
* (TAB_GROUPS). Ungrouped settings sort first; within a group, schema
|
|
180
|
+
* declaration order is preserved.
|
|
181
|
+
*/
|
|
174
182
|
export function getSettingsForTab(tab: SettingTab): SettingDef[] {
|
|
175
|
-
|
|
183
|
+
const defs = getAllSettingDefs().filter(def => def.tab === tab);
|
|
184
|
+
const order = TAB_GROUPS[tab];
|
|
185
|
+
const rank = (def: SettingDef): number => {
|
|
186
|
+
if (!def.group) return -1;
|
|
187
|
+
const index = order.indexOf(def.group);
|
|
188
|
+
return index >= 0 ? index : order.length;
|
|
189
|
+
};
|
|
190
|
+
return defs.sort((a, b) => rank(a) - rank(b));
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
/** Get a setting definition by path */
|