@mrclrchtr/supi-prompt-suggestions 4.7.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/node_modules/@mrclrchtr/supi-core/README.md +26 -34
- package/node_modules/@mrclrchtr/supi-core/package.json +4 -7
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +4 -6
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +0 -20
- package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +7 -1
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +0 -1
- package/node_modules/@mrclrchtr/supi-core/src/context.ts +1 -9
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +4 -6
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +54 -28
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +91 -125
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +10 -7
- package/package.json +2 -2
- package/src/config/settings.ts +18 -15
- package/src/editor/editor.ts +35 -69
- package/src/generation/client.ts +2 -1
- package/src/generation/generator.ts +2 -2
- package/src/generation/model-resolution.ts +2 -1
- package/node_modules/@mrclrchtr/supi-core/src/context/context-messages.ts +0 -119
- package/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +0 -189
- package/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +0 -373
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +0 -102
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +0 -15
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +0 -141
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +0 -118
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +0 -3
- package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +0 -192
package/README.md
CHANGED
|
@@ -35,10 +35,11 @@ Suggestions only appear when:
|
|
|
35
35
|
| Key | Action |
|
|
36
36
|
|-----|--------|
|
|
37
37
|
| `→` / Right Arrow | Accept the suggestion into the editor without submitting it |
|
|
38
|
-
| `Esc` | Dismiss the suggestion |
|
|
39
|
-
| Any text input |
|
|
38
|
+
| `Esc` | Dismiss the visible suggestion |
|
|
39
|
+
| Any text input | Hide the suggestion and continue typing |
|
|
40
|
+
| Delete all editor text | Restore the hidden suggestion |
|
|
40
41
|
|
|
41
|
-
Accepted suggestions are inserted into the editor so you can edit them before sending.
|
|
42
|
+
Accepted suggestions are inserted into the editor so you can edit them before sending. A hidden suggestion remains available until you accept it, dismiss it, or start new agent work.
|
|
42
43
|
|
|
43
44
|
## Configuration
|
|
44
45
|
|
|
@@ -37,28 +37,13 @@ Config file locations:
|
|
|
37
37
|
|
|
38
38
|
### Settings helpers
|
|
39
39
|
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
42
|
-
-
|
|
43
|
-
- `createInputSubmenu()` — helper for simple text-entry submenus
|
|
44
|
-
- `createModelPickerSubmenu()` — helper for scoped model selection submenus, with optional host-owned choices and `disabled` control
|
|
45
|
-
|
|
46
|
-
The built-in settings UI supports:
|
|
47
|
-
|
|
48
|
-
- project/global scope toggle
|
|
49
|
-
- source badges for project, global, and default values
|
|
50
|
-
- Inherit/Reset actions that delete scoped config keys
|
|
51
|
-
- grouped extension sections
|
|
52
|
-
- searchable setting lists
|
|
40
|
+
- `registerSettings(pi, module)` — register one canonical asynchronous settings module
|
|
41
|
+
- `defineConfigSettings(options)` — adapt a fixed SuPi config section to that module interface
|
|
42
|
+
- settings registry helpers and types for the `@mrclrchtr/supi-settings` configuration surface
|
|
53
43
|
|
|
54
44
|
### Context helpers
|
|
55
45
|
|
|
56
46
|
- `wrapExtensionContext()` — wrap injected text in SuPi's `<extension-context>` tag
|
|
57
|
-
- `findLastUserMessageIndex()`
|
|
58
|
-
- `getContextToken()`
|
|
59
|
-
- `getPromptContent()`
|
|
60
|
-
- `pruneAndReorderContextMessages()`
|
|
61
|
-
- `restorePromptContent()`
|
|
62
47
|
|
|
63
48
|
### Shared registries
|
|
64
49
|
|
|
@@ -84,25 +69,33 @@ The built-in settings UI supports:
|
|
|
84
69
|
|
|
85
70
|
```ts
|
|
86
71
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
87
|
-
import {
|
|
72
|
+
import {
|
|
73
|
+
defineConfigSettings,
|
|
74
|
+
loadSupiConfig,
|
|
75
|
+
registerSettings,
|
|
76
|
+
wrapExtensionContext,
|
|
77
|
+
} from "@mrclrchtr/supi-core/api";
|
|
88
78
|
|
|
89
79
|
export default function myExtension(pi: ExtensionAPI) {
|
|
90
80
|
const defaults = { enabled: true };
|
|
91
81
|
const config = loadSupiConfig("my-extension", process.cwd(), defaults);
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
registerSettings(
|
|
84
|
+
pi,
|
|
85
|
+
defineConfigSettings({
|
|
86
|
+
id: "my-extension",
|
|
87
|
+
label: "My Extension",
|
|
88
|
+
section: "my-extension",
|
|
89
|
+
defaults,
|
|
90
|
+
fields: [
|
|
91
|
+
{
|
|
92
|
+
kind: "boolean" as const,
|
|
93
|
+
key: "enabled",
|
|
94
|
+
label: "Enabled",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
106
99
|
|
|
107
100
|
const message = wrapExtensionContext("my-extension", "hello", {
|
|
108
101
|
enabled: config.enabled,
|
|
@@ -115,6 +108,5 @@ export default function myExtension(pi: ExtensionAPI) {
|
|
|
115
108
|
|
|
116
109
|
- `src/api.ts` — exported library surface
|
|
117
110
|
- `src/config.ts` — shared config loading and writing
|
|
118
|
-
- `src/
|
|
119
|
-
- `src/settings-ui.ts` — shared settings overlay
|
|
111
|
+
- `src/settings/` — settings registry, schema, scope resolution, and persistence
|
|
120
112
|
- `src/report.ts` — shared text/report rendering helpers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "Shared settings, configuration, reporting, and session infrastructure",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -60,16 +60,13 @@
|
|
|
60
60
|
"./model-selection": "./src/model-selection.ts",
|
|
61
61
|
"./package.json": "./package.json",
|
|
62
62
|
"./path": "./src/path.ts",
|
|
63
|
-
"./report": "./src/report.ts",
|
|
64
|
-
"./progress-widget": "./src/progress-widget.ts",
|
|
65
63
|
"./project": "./src/project.ts",
|
|
64
|
+
"./prompt-surface": "./src/prompt-surface.ts",
|
|
65
|
+
"./report": "./src/report.ts",
|
|
66
66
|
"./session": "./src/session.ts",
|
|
67
67
|
"./settings": "./src/settings.ts",
|
|
68
|
-
"./settings-ui": "./src/settings-ui.ts",
|
|
69
68
|
"./spinner-frames": "./src/spinner-frames.ts",
|
|
70
69
|
"./status-spinner": "./src/status-spinner.ts",
|
|
71
|
-
"./terminal": "./src/terminal.ts"
|
|
72
|
-
"./tool-framework": "./src/tool-framework.ts",
|
|
73
|
-
"./prompt-surface": "./src/prompt-surface.ts"
|
|
70
|
+
"./terminal": "./src/terminal.ts"
|
|
74
71
|
}
|
|
75
72
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// supi-core — shared infrastructure for SuPi extensions.
|
|
2
|
-
// Provides XML context tag wrapping, unified config
|
|
3
|
-
//
|
|
2
|
+
// Provides XML context tag wrapping, unified config, prompt-surface resolution,
|
|
3
|
+
// and shared settings, reporting, and session utilities.
|
|
4
4
|
//
|
|
5
5
|
// Convenience barrel — re-exports all domain entry points.
|
|
6
6
|
// For lighter imports, use one of the domain subpaths directly
|
|
@@ -25,18 +25,16 @@ export * from "./path.ts";
|
|
|
25
25
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
26
26
|
export * from "./project.ts";
|
|
27
27
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
28
|
+
export * from "./prompt-surface.ts";
|
|
29
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
28
30
|
export * from "./report.ts";
|
|
29
31
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
30
32
|
export * from "./session.ts";
|
|
31
33
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
32
34
|
export * from "./settings.ts";
|
|
33
35
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
34
|
-
export * from "./settings-ui.ts";
|
|
35
|
-
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
36
36
|
export * from "./spinner-frames.ts";
|
|
37
37
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
38
38
|
export * from "./status-spinner.ts";
|
|
39
39
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
40
40
|
export * from "./terminal.ts";
|
|
41
|
-
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
42
|
-
export * from "./tool-framework.ts";
|
|
@@ -199,23 +199,3 @@ function extractSection(
|
|
|
199
199
|
}
|
|
200
200
|
return null;
|
|
201
201
|
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Shorthand for {@link loadSupiConfig} that infers the return type from defaults.
|
|
205
|
-
*
|
|
206
|
-
* Reduces boilerplate when a package only needs the merged runtime config.
|
|
207
|
-
*
|
|
208
|
-
* @example
|
|
209
|
-
* ```ts
|
|
210
|
-
* const config = loadSectionConfig("my-ext", cwd, { enabled: true, timeout: 30 });
|
|
211
|
-
* // config is typed as { enabled: boolean; timeout: number }
|
|
212
|
-
* ```
|
|
213
|
-
*/
|
|
214
|
-
export function loadSectionConfig<T extends Record<string, unknown>>(
|
|
215
|
-
section: string,
|
|
216
|
-
cwd: string,
|
|
217
|
-
defaults: T,
|
|
218
|
-
options?: SupiConfigOptions,
|
|
219
|
-
): T {
|
|
220
|
-
return loadSupiConfig(section, cwd, defaults, options);
|
|
221
|
-
}
|
|
@@ -8,11 +8,17 @@ import {
|
|
|
8
8
|
type ExtensionContext,
|
|
9
9
|
hasTrustRequiringProjectResources,
|
|
10
10
|
} from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import type { SuiPiToolPromptSurface } from "../tool-framework.ts";
|
|
12
11
|
import { loadSupiConfigSectionForScope, type SupiConfigOptions } from "./config.ts";
|
|
13
12
|
|
|
14
13
|
// ── Public types ───────────────────────────────────────────────────────────
|
|
15
14
|
|
|
15
|
+
/** Model-facing text that PI adds to one registered tool's prompt surface. */
|
|
16
|
+
export interface SuiPiToolPromptSurface {
|
|
17
|
+
description: string;
|
|
18
|
+
promptSnippet: string;
|
|
19
|
+
promptGuidelines: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export type ToolPromptSurfaceDiagnosticCode =
|
|
17
23
|
| "invalidPromptSurfaceConfig"
|
|
18
24
|
| "invalidPromptSurfaceField"
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
// supi-core context domain — context
|
|
2
|
-
export type { ContextMessageLike } from "./context/context-messages.ts";
|
|
3
|
-
export {
|
|
4
|
-
findLastUserMessageIndex,
|
|
5
|
-
getContextToken,
|
|
6
|
-
getPromptContent,
|
|
7
|
-
pruneAndReorderContextMessages,
|
|
8
|
-
restorePromptContent,
|
|
9
|
-
} from "./context/context-messages.ts";
|
|
1
|
+
// supi-core context domain — context providers and XML tags.
|
|
10
2
|
export type { ContextProvider } from "./context/context-provider-registry.ts";
|
|
11
3
|
export {
|
|
12
4
|
clearRegisteredContextProviders,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// supi-core — shared infrastructure for SuPi extensions.
|
|
2
|
-
// Provides XML context tag wrapping, unified config
|
|
3
|
-
//
|
|
2
|
+
// Provides XML context tag wrapping, unified config, prompt-surface resolution,
|
|
3
|
+
// and shared settings, reporting, and session utilities.
|
|
4
4
|
//
|
|
5
5
|
// Convenience barrel — re-exports all domain entry points.
|
|
6
6
|
// For lighter imports, use one of the domain subpaths directly
|
|
@@ -21,16 +21,14 @@ export * from "./path.ts";
|
|
|
21
21
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
22
22
|
export * from "./project.ts";
|
|
23
23
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
24
|
+
export * from "./prompt-surface.ts";
|
|
25
|
+
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
24
26
|
export * from "./report.ts";
|
|
25
27
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
26
28
|
export * from "./session.ts";
|
|
27
29
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
28
30
|
export * from "./settings.ts";
|
|
29
31
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
30
|
-
export * from "./settings-ui.ts";
|
|
31
|
-
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
32
32
|
export * from "./status-spinner.ts";
|
|
33
33
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
34
34
|
export * from "./terminal.ts";
|
|
35
|
-
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
36
|
-
export * from "./tool-framework.ts";
|
|
@@ -1,35 +1,53 @@
|
|
|
1
|
-
// Event-backed settings
|
|
2
|
-
//
|
|
3
|
-
// Extensions contribute declarative settings sections through PI's shared
|
|
4
|
-
// event bus. The public helper is registerDeclarativeSettings(pi, ...); this module
|
|
5
|
-
// owns the internal collector protocol used by /supi-settings.
|
|
1
|
+
// Event-backed settings module registration and collection.
|
|
6
2
|
|
|
7
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import type { ScopedFieldValue,
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { ScopedFieldValue, SettingsAction } from "./settings-schema.ts";
|
|
9
5
|
|
|
10
6
|
export const SUPI_SETTINGS_COLLECT_EVENT = "supi:settings:collect";
|
|
11
7
|
|
|
12
8
|
export type SettingsScope = "project" | "global";
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
/** Scope and PI runtime state supplied for each settings read. */
|
|
11
|
+
export interface SettingsContext {
|
|
12
|
+
scope: SettingsScope;
|
|
13
|
+
cwd: string;
|
|
14
|
+
ctx?: ExtensionContext;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A resolved, source-aware settings view. */
|
|
18
|
+
export interface SettingsSnapshot {
|
|
19
|
+
rows: ScopedFieldValue[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One user action routed to its owning settings module. */
|
|
23
|
+
export interface SettingsActionRequest extends SettingsContext {
|
|
24
|
+
fieldKey: string;
|
|
25
|
+
action: SettingsAction;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Optional user-facing result from a successful settings action. */
|
|
29
|
+
export interface SettingsApplyResult {
|
|
30
|
+
notice?: {
|
|
31
|
+
message: string;
|
|
32
|
+
level: "info" | "warning" | "error";
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Canonical settings interface consumed by `/supi-settings`.
|
|
38
|
+
*
|
|
39
|
+
* Reads are always asynchronous. Apply resolves only after durable writes and
|
|
40
|
+
* module-owned refresh work complete. Implementations throw on failed writes.
|
|
41
|
+
*/
|
|
42
|
+
export interface SettingsModule {
|
|
16
43
|
id: string;
|
|
17
|
-
/** Human-readable label shown in the UI. */
|
|
18
44
|
label: string;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/** Handle a user action on a field in the selected scope. */
|
|
22
|
-
handleAction: (
|
|
23
|
-
scope: SettingsScope,
|
|
24
|
-
cwd: string,
|
|
25
|
-
fieldKey: string,
|
|
26
|
-
action: SettingsFieldAction,
|
|
27
|
-
ctx?: ExtensionContext,
|
|
28
|
-
) => void;
|
|
45
|
+
read(context: SettingsContext): Promise<SettingsSnapshot>;
|
|
46
|
+
apply(request: SettingsActionRequest): Promise<SettingsApplyResult>;
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
export interface SettingsContributionCollector {
|
|
32
|
-
add(
|
|
50
|
+
add(module: SettingsModule): void;
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
export interface SettingsCollectionDiagnostic {
|
|
@@ -38,7 +56,7 @@ export interface SettingsCollectionDiagnostic {
|
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
export interface SettingsCollectionResult {
|
|
41
|
-
|
|
59
|
+
modules: SettingsModule[];
|
|
42
60
|
diagnostics: SettingsCollectionDiagnostic[];
|
|
43
61
|
}
|
|
44
62
|
|
|
@@ -56,21 +74,29 @@ export function isSettingsContributionCollector(
|
|
|
56
74
|
export function createSettingsContributionCollector(): SettingsContributionCollector & {
|
|
57
75
|
result(): SettingsCollectionResult;
|
|
58
76
|
} {
|
|
59
|
-
const
|
|
77
|
+
const modules = new Map<string, SettingsModule>();
|
|
60
78
|
const diagnostics: SettingsCollectionDiagnostic[] = [];
|
|
61
79
|
|
|
62
80
|
return {
|
|
63
|
-
add(
|
|
64
|
-
if (
|
|
81
|
+
add(module: SettingsModule): void {
|
|
82
|
+
if (modules.has(module.id)) {
|
|
65
83
|
diagnostics.push({
|
|
66
84
|
kind: "warning",
|
|
67
|
-
message: `Duplicate SuPi settings contribution "${
|
|
85
|
+
message: `Duplicate SuPi settings contribution "${module.id}"; using the last contribution.`,
|
|
68
86
|
});
|
|
69
87
|
}
|
|
70
|
-
|
|
88
|
+
modules.set(module.id, module);
|
|
71
89
|
},
|
|
72
90
|
result(): SettingsCollectionResult {
|
|
73
|
-
return {
|
|
91
|
+
return { modules: Array.from(modules.values()), diagnostics: [...diagnostics] };
|
|
74
92
|
},
|
|
75
93
|
};
|
|
76
94
|
}
|
|
95
|
+
|
|
96
|
+
/** Register one settings module during extension factory setup. */
|
|
97
|
+
export function registerSettings(pi: ExtensionAPI, module: SettingsModule): void {
|
|
98
|
+
const dispose = pi.events.on(SUPI_SETTINGS_COLLECT_EVENT, (collector) => {
|
|
99
|
+
if (isSettingsContributionCollector(collector)) collector.add(module);
|
|
100
|
+
});
|
|
101
|
+
pi.on("session_shutdown", () => dispose());
|
|
102
|
+
}
|
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Fixed SuPi-config adapter for the canonical settings module interface.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// scope inheritance, source-state resolution, value rendering, persistence,
|
|
6
|
-
// and Inherit/Reset-to-default actions.
|
|
3
|
+
// Declarative field descriptors let the adapter own scope inheritance,
|
|
4
|
+
// source-state resolution, value rendering, persistence, and Unset actions.
|
|
7
5
|
//
|
|
8
6
|
// Custom fields remain for nested or unusual config; they report the same
|
|
9
7
|
// source state as declarative flat fields.
|
|
10
8
|
|
|
11
|
-
import type {
|
|
9
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
10
|
import type { Component } from "@earendil-works/pi-tui";
|
|
13
11
|
import {
|
|
14
12
|
loadSupiConfigSectionForScope,
|
|
15
13
|
removeSupiConfigKey,
|
|
16
14
|
writeSupiConfig,
|
|
17
15
|
} from "../config/config.ts";
|
|
18
|
-
import {
|
|
19
|
-
isSettingsContributionCollector,
|
|
20
|
-
type SettingsScope,
|
|
21
|
-
type SettingsSection,
|
|
22
|
-
SUPI_SETTINGS_COLLECT_EVENT,
|
|
23
|
-
} from "./settings-registry.ts";
|
|
16
|
+
import type { SettingsApplyResult, SettingsModule, SettingsScope } from "./settings-registry.ts";
|
|
24
17
|
|
|
25
18
|
// ── Types ──────────────────────────────────────────────────────────────────
|
|
26
19
|
|
|
@@ -52,10 +45,7 @@ export interface ConfigHelpers {
|
|
|
52
45
|
// ── Field actions ─────────────────────────────────────────────────────────
|
|
53
46
|
|
|
54
47
|
/** A user-initiated action on a settings row. */
|
|
55
|
-
export type
|
|
56
|
-
| { kind: "set"; value: string }
|
|
57
|
-
| { kind: "inherit" }
|
|
58
|
-
| { kind: "resetToDefault" };
|
|
48
|
+
export type SettingsAction = { kind: "set"; value: string } | { kind: "unset" };
|
|
59
49
|
|
|
60
50
|
// ── Field descriptors ─────────────────────────────────────────────────────
|
|
61
51
|
|
|
@@ -153,15 +143,15 @@ export interface CustomField extends BaseField {
|
|
|
153
143
|
ctx?: ExtensionContext,
|
|
154
144
|
) => Component;
|
|
155
145
|
/**
|
|
156
|
-
* Persist handler called on set
|
|
146
|
+
* Persist handler called on set or unset actions.
|
|
157
147
|
* Required for custom fields so they can write their nested config.
|
|
158
148
|
*/
|
|
159
149
|
persist: (
|
|
160
150
|
scope: SettingsScope,
|
|
161
151
|
cwd: string,
|
|
162
|
-
action:
|
|
152
|
+
action: SettingsAction,
|
|
163
153
|
helpers: ConfigHelpers,
|
|
164
|
-
) => void
|
|
154
|
+
) => void | Promise<void>;
|
|
165
155
|
}
|
|
166
156
|
|
|
167
157
|
/** Union of all supported field kinds. */
|
|
@@ -176,8 +166,8 @@ export type SettingsField =
|
|
|
176
166
|
|
|
177
167
|
// ── Contribution options ──────────────────────────────────────────────────
|
|
178
168
|
|
|
179
|
-
/** Options for
|
|
180
|
-
export interface
|
|
169
|
+
/** Options for the fixed SuPi-config settings adapter. */
|
|
170
|
+
export interface ConfigSettingsOptions {
|
|
181
171
|
/** Stable contribution identifier — e.g. "lsp", "claude-md". */
|
|
182
172
|
id: string;
|
|
183
173
|
/** Human-readable label shown in the UI. */
|
|
@@ -194,7 +184,7 @@ export interface DeclarativeSettingsOptions {
|
|
|
194
184
|
homeDir?: string;
|
|
195
185
|
}
|
|
196
186
|
|
|
197
|
-
// ──
|
|
187
|
+
// ── Source-aware row interface ───────────────────────────────────────────
|
|
198
188
|
|
|
199
189
|
/** Resolved value for one field in one scope. */
|
|
200
190
|
export interface ScopedFieldValue {
|
|
@@ -305,14 +295,14 @@ function createConfigHelpers(
|
|
|
305
295
|
};
|
|
306
296
|
}
|
|
307
297
|
|
|
308
|
-
// ──
|
|
298
|
+
// ── Fixed config adapter ─────────────────────────────────────────────────
|
|
309
299
|
|
|
310
300
|
interface NotifyAfterPersistInput {
|
|
311
|
-
options:
|
|
301
|
+
options: ConfigSettingsOptions;
|
|
312
302
|
field: SettingsField;
|
|
313
303
|
scope: SettingsScope;
|
|
314
304
|
cwd: string;
|
|
315
|
-
action:
|
|
305
|
+
action: SettingsAction;
|
|
316
306
|
storedValue: unknown;
|
|
317
307
|
ctx?: ExtensionContext;
|
|
318
308
|
}
|
|
@@ -354,86 +344,86 @@ function notifyAfterPersist(input: NotifyAfterPersistInput): void {
|
|
|
354
344
|
options.afterPersist(change);
|
|
355
345
|
}
|
|
356
346
|
|
|
357
|
-
function
|
|
347
|
+
function resolveConfigRows(
|
|
348
|
+
options: ConfigSettingsOptions,
|
|
349
|
+
scope: SettingsScope,
|
|
350
|
+
cwd: string,
|
|
351
|
+
ctx?: ExtensionContext,
|
|
352
|
+
): ScopedFieldValue[] {
|
|
358
353
|
const defaults = options.defaults as Record<string, unknown>;
|
|
354
|
+
const projectRaw = loadSupiConfigSectionForScope(options.section, cwd, {
|
|
355
|
+
scope: "project",
|
|
356
|
+
homeDir: options.homeDir,
|
|
357
|
+
});
|
|
358
|
+
const globalRaw = loadSupiConfigSectionForScope(options.section, cwd, {
|
|
359
|
+
scope: "global",
|
|
360
|
+
homeDir: options.homeDir,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return options.fields.map((field) => {
|
|
364
|
+
if (field.kind === "custom") {
|
|
365
|
+
const resolved = field.resolve(scope, cwd, ctx);
|
|
366
|
+
return {
|
|
367
|
+
field,
|
|
368
|
+
displayValue: resolved.displayValue
|
|
369
|
+
? sourceBadge(resolved.displayValue, resolved.source)
|
|
370
|
+
: "",
|
|
371
|
+
editValue: resolved.editValue ?? resolved.displayValue,
|
|
372
|
+
source: resolved.source,
|
|
373
|
+
inheritanceSource: resolved.inheritanceSource,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const { value, source } = resolveValue(field.key, defaults, projectRaw, globalRaw, scope);
|
|
378
|
+
const displayValue = formatValue(value, field);
|
|
379
|
+
const inheritanceSource =
|
|
380
|
+
scope === "project" && source === "project"
|
|
381
|
+
? globalRaw && field.key in globalRaw
|
|
382
|
+
? "global"
|
|
383
|
+
: "default"
|
|
384
|
+
: undefined;
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
field,
|
|
388
|
+
displayValue: sourceBadge(displayValue, source),
|
|
389
|
+
editValue: formatEditValue(value, field),
|
|
390
|
+
source,
|
|
391
|
+
inheritanceSource,
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function applyConfigAction(
|
|
397
|
+
options: ConfigSettingsOptions,
|
|
398
|
+
request: Parameters<SettingsModule["apply"]>[0],
|
|
399
|
+
): Promise<SettingsApplyResult> {
|
|
400
|
+
const { scope, cwd, fieldKey, action, ctx } = request;
|
|
401
|
+
const field = options.fields.find((candidate) => candidate.key === fieldKey);
|
|
402
|
+
if (!field) return {};
|
|
403
|
+
|
|
404
|
+
const helpers = createConfigHelpers(options.section, scope, cwd, options.homeDir);
|
|
405
|
+
let storedValue: unknown;
|
|
406
|
+
if (field.kind === "custom") {
|
|
407
|
+
await field.persist(scope, cwd, action, helpers);
|
|
408
|
+
storedValue = action.kind === "set" ? action.value : undefined;
|
|
409
|
+
} else if (action.kind === "set") {
|
|
410
|
+
storedValue = parseTypedValue(action.value, field);
|
|
411
|
+
helpers.set(field.key, storedValue);
|
|
412
|
+
} else {
|
|
413
|
+
helpers.unset(field.key);
|
|
414
|
+
}
|
|
415
|
+
notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
|
|
416
|
+
return {};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Adapt one fixed SuPi config section to the canonical settings interface. */
|
|
420
|
+
export function defineConfigSettings(options: ConfigSettingsOptions): SettingsModule {
|
|
359
421
|
return {
|
|
360
422
|
id: options.id,
|
|
361
423
|
label: options.label,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
scope: "project",
|
|
366
|
-
homeDir: options.homeDir,
|
|
367
|
-
});
|
|
368
|
-
const globalRaw = loadSupiConfigSectionForScope(options.section, cwd, {
|
|
369
|
-
scope: "global",
|
|
370
|
-
homeDir: options.homeDir,
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: natural discriminator on field kind and source
|
|
374
|
-
return options.fields.map((field) => {
|
|
375
|
-
if (field.kind === "custom") {
|
|
376
|
-
const resolved = field.resolve(scope, cwd, ctx);
|
|
377
|
-
return {
|
|
378
|
-
field,
|
|
379
|
-
displayValue: resolved.displayValue
|
|
380
|
-
? sourceBadge(resolved.displayValue, resolved.source)
|
|
381
|
-
: "",
|
|
382
|
-
editValue: resolved.editValue ?? resolved.displayValue,
|
|
383
|
-
source: resolved.source,
|
|
384
|
-
inheritanceSource: resolved.inheritanceSource,
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const { value, source } = resolveValue(field.key, defaults, projectRaw, globalRaw, scope);
|
|
389
|
-
const displayValue = formatValue(value, field);
|
|
390
|
-
|
|
391
|
-
// Compute inheritanceSource for project-scope overrides
|
|
392
|
-
let inheritanceSource: "global" | "default" | undefined;
|
|
393
|
-
if (scope === "project" && source === "project") {
|
|
394
|
-
inheritanceSource = globalRaw && field.key in globalRaw ? "global" : "default";
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return {
|
|
398
|
-
field,
|
|
399
|
-
displayValue: sourceBadge(displayValue, source),
|
|
400
|
-
editValue: formatEditValue(value, field),
|
|
401
|
-
source,
|
|
402
|
-
inheritanceSource,
|
|
403
|
-
};
|
|
404
|
-
});
|
|
405
|
-
},
|
|
406
|
-
// biome-ignore lint/complexity/useMaxParams: SettingsSection action handlers receive scope, cwd, field, action, and optional context
|
|
407
|
-
handleAction: (scope, cwd, fieldKey, action, ctx) => {
|
|
408
|
-
const field = options.fields.find((f) => f.key === fieldKey);
|
|
409
|
-
if (!field) return;
|
|
410
|
-
|
|
411
|
-
const section = options.section;
|
|
412
|
-
const helpers = createConfigHelpers(section, scope, cwd, options.homeDir);
|
|
413
|
-
let storedValue: unknown;
|
|
414
|
-
|
|
415
|
-
if (field.kind === "custom") {
|
|
416
|
-
field.persist(scope, cwd, action, helpers);
|
|
417
|
-
storedValue = action.kind === "set" ? action.value : undefined;
|
|
418
|
-
notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
switch (action.kind) {
|
|
423
|
-
case "set": {
|
|
424
|
-
storedValue = parseTypedValue(action.value, field);
|
|
425
|
-
helpers.set(field.key, storedValue);
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
case "inherit":
|
|
429
|
-
case "resetToDefault": {
|
|
430
|
-
helpers.unset(field.key);
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
|
|
436
|
-
},
|
|
424
|
+
read: ({ scope, cwd, ctx }) =>
|
|
425
|
+
Promise.resolve({ rows: resolveConfigRows(options, scope, cwd, ctx) }),
|
|
426
|
+
apply: (request) => applyConfigAction(options, request),
|
|
437
427
|
};
|
|
438
428
|
}
|
|
439
429
|
|
|
@@ -461,27 +451,3 @@ export function parseTypedValue(value: string, field: SettingsField): unknown {
|
|
|
461
451
|
return value;
|
|
462
452
|
}
|
|
463
453
|
}
|
|
464
|
-
|
|
465
|
-
// ── Registration ──────────────────────────────────────────────────────────
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Register a declarative settings contribution for `/supi-settings`.
|
|
469
|
-
*
|
|
470
|
-
* Contributions are collected through PI's process-local event bus. Call this
|
|
471
|
-
* during the extension factory function, not in async session handlers.
|
|
472
|
-
*/
|
|
473
|
-
export function registerDeclarativeSettings(
|
|
474
|
-
pi: ExtensionAPI,
|
|
475
|
-
options: DeclarativeSettingsOptions,
|
|
476
|
-
): void {
|
|
477
|
-
const section = toDeclarativeSection(options);
|
|
478
|
-
const dispose = pi.events.on(SUPI_SETTINGS_COLLECT_EVENT, (collector) => {
|
|
479
|
-
if (isSettingsContributionCollector(collector)) {
|
|
480
|
-
collector.add(section);
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
pi.on("session_shutdown", () => {
|
|
485
|
-
dispose();
|
|
486
|
-
});
|
|
487
|
-
}
|