@mrclrchtr/supi-prompt-suggestions 4.7.0 → 4.9.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 +5 -4
- package/node_modules/@mrclrchtr/supi-core/README.md +29 -35
- 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/debug-registry.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +107 -0
- 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 +36 -70
- 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
|
@@ -34,11 +34,12 @@ Suggestions only appear when:
|
|
|
34
34
|
|
|
35
35
|
| Key | Action |
|
|
36
36
|
|-----|--------|
|
|
37
|
-
| `→` / Right Arrow | Accept the suggestion into the editor without submitting it |
|
|
38
|
-
| `Esc` | Dismiss the suggestion |
|
|
39
|
-
| Any text input |
|
|
37
|
+
| `→` / Right Arrow / `Tab` | Accept the suggestion into the editor without submitting it |
|
|
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,33 +37,18 @@ 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
|
|
|
65
50
|
- context-provider registry for `/supi-context`
|
|
66
|
-
- debug-event registry for producers that want shared debug capture
|
|
51
|
+
- debug-event registry and monotonic phase timers for producers that want shared debug capture
|
|
67
52
|
- settings registry used by `/supi-settings`
|
|
68
53
|
|
|
69
54
|
### Project and session helpers
|
|
@@ -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,7 @@ 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/
|
|
111
|
+
- `src/debug-registry.ts` — Debug domain surface, event state, retention, redaction, listeners, and queries
|
|
112
|
+
- `src/debug-timing.ts` — monotonic total and phase timers for Debug Event Producers
|
|
113
|
+
- `src/settings/` — settings registry, schema, scope resolution, and persistence
|
|
120
114
|
- `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.9.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,
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
// supi-debug extension owns policy/configuration and exposes events through a
|
|
5
5
|
// command/tool while this module stays dependency-free for producers.
|
|
6
6
|
|
|
7
|
+
// biome-ignore lint/performance/noReExportAll: preserve the stable debug domain entry point
|
|
8
|
+
export * from "./debug-timing.ts";
|
|
9
|
+
|
|
7
10
|
export type DebugLevel = "debug" | "info" | "warning" | "error";
|
|
8
11
|
export type DebugAgentAccess = "off" | "sanitized" | "raw";
|
|
9
12
|
export interface DebugRegistryConfig {
|
|
@@ -180,6 +183,11 @@ export function getDebugRegistryConfig(): DebugRegistryConfig {
|
|
|
180
183
|
return cloneConfig(getState().config);
|
|
181
184
|
}
|
|
182
185
|
|
|
186
|
+
/** Return whether the Debug Registry currently retains producer events. */
|
|
187
|
+
export function isDebugRegistryEnabled(): boolean {
|
|
188
|
+
return getState().config.enabled;
|
|
189
|
+
}
|
|
190
|
+
|
|
183
191
|
/** Best-effort redaction helper for data exposed through sanitized debug views. */
|
|
184
192
|
export function redactDebugData<T>(value: T): T {
|
|
185
193
|
return redactValue(value, 8) as T;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import {
|
|
3
|
+
type DebugEvent,
|
|
4
|
+
type DebugEventInput,
|
|
5
|
+
isDebugRegistryEnabled,
|
|
6
|
+
recordDebugEvent,
|
|
7
|
+
} from "./debug-registry.ts";
|
|
8
|
+
|
|
9
|
+
/** Monotonic duration data added to a timed debug event. */
|
|
10
|
+
export interface DebugTiming {
|
|
11
|
+
readonly durationMs: number;
|
|
12
|
+
readonly phasesMs: Readonly<Record<string, number>>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Debug event input whose data can receive the reserved `timing` field. */
|
|
16
|
+
export interface TimedDebugEventInput extends Omit<DebugEventInput, "data"> {
|
|
17
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Test seam for the monotonic clock used by a debug timer. */
|
|
21
|
+
export interface DebugTimerOptions {
|
|
22
|
+
readonly now?: () => number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Lazy timed-event input that is not evaluated when Debug is disabled. */
|
|
26
|
+
export type TimedDebugEventFactory = () => TimedDebugEventInput;
|
|
27
|
+
|
|
28
|
+
/** One-shot debug timer with optional sequential phase measurements. */
|
|
29
|
+
export interface DebugTimer {
|
|
30
|
+
/** Whether this timer sampled Debug as enabled when it started. */
|
|
31
|
+
readonly enabled: boolean;
|
|
32
|
+
/** Finish the current phase and start the next unnamed interval. */
|
|
33
|
+
mark(phase: string): void;
|
|
34
|
+
/** Record one event. A second call returns `null` without recording another event. */
|
|
35
|
+
finish(
|
|
36
|
+
input: TimedDebugEventInput | TimedDebugEventFactory,
|
|
37
|
+
finalPhase?: string,
|
|
38
|
+
): DebugEvent | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Start a monotonic timer for one debug event.
|
|
43
|
+
*
|
|
44
|
+
* Each `mark(name)` stores the interval since the previous mark. `finish()`
|
|
45
|
+
* stores the total duration and can name the final interval. Repeated phase
|
|
46
|
+
* names are accumulated. Event data reserves the `timing` field. When Debug is
|
|
47
|
+
* disabled at start, this returns a no-op timer and does not read the clock.
|
|
48
|
+
* Pass a factory to `finish()` to avoid event-data construction when disabled.
|
|
49
|
+
*/
|
|
50
|
+
export function startDebugTimer(options: DebugTimerOptions = {}): DebugTimer {
|
|
51
|
+
if (!isDebugRegistryEnabled()) return DISABLED_DEBUG_TIMER;
|
|
52
|
+
const now = options.now ?? performance.now.bind(performance);
|
|
53
|
+
const startedAt = now();
|
|
54
|
+
let previousAt = startedAt;
|
|
55
|
+
let finished = false;
|
|
56
|
+
const phases = new Map<string, number>();
|
|
57
|
+
|
|
58
|
+
const markAt = (phase: string, current: number): void => {
|
|
59
|
+
const name = phase.trim();
|
|
60
|
+
if (!name) return;
|
|
61
|
+
phases.set(name, (phases.get(name) ?? 0) + Math.max(0, current - previousAt));
|
|
62
|
+
previousAt = current;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
enabled: true,
|
|
67
|
+
mark(phase) {
|
|
68
|
+
if (finished) return;
|
|
69
|
+
markAt(phase, now());
|
|
70
|
+
},
|
|
71
|
+
finish(input, finalPhase) {
|
|
72
|
+
if (finished) return null;
|
|
73
|
+
if (!isDebugRegistryEnabled()) {
|
|
74
|
+
finished = true;
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const completedAt = now();
|
|
78
|
+
if (finalPhase) markAt(finalPhase, completedAt);
|
|
79
|
+
finished = true;
|
|
80
|
+
const phasesMs = Object.fromEntries(
|
|
81
|
+
[...phases.entries()].map(([name, value]) => [name, duration(value)]),
|
|
82
|
+
);
|
|
83
|
+
const timing: DebugTiming = {
|
|
84
|
+
durationMs: duration(completedAt - startedAt),
|
|
85
|
+
phasesMs,
|
|
86
|
+
};
|
|
87
|
+
const eventInput = typeof input === "function" ? input() : input;
|
|
88
|
+
return recordDebugEvent({
|
|
89
|
+
...eventInput,
|
|
90
|
+
data: { ...eventInput.data, timing },
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const DISABLED_DEBUG_TIMER: DebugTimer = Object.freeze({
|
|
97
|
+
enabled: false,
|
|
98
|
+
mark() {},
|
|
99
|
+
finish() {
|
|
100
|
+
return null;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function duration(value: number): number {
|
|
105
|
+
if (!Number.isFinite(value)) return 0;
|
|
106
|
+
return Math.round(Math.max(0, value) * 10) / 10;
|
|
107
|
+
}
|
|
@@ -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
|
+
}
|