@mrclrchtr/supi-code-intelligence 4.0.0 → 4.1.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 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- package/package.json +6 -5
- package/src/app/app.ts +4 -1
- package/src/extension.ts +6 -84
- package/src/headless.ts +41 -0
- package/src/substrate/lsp/lifecycle.ts +25 -39
- package/src/substrate/lsp/state.ts +3 -0
- package/src/substrate/workspace-provider-host.ts +129 -0
- package/src/substrate/tree-sitter/lifecycle.ts +0 -52
package/README.md
CHANGED
|
@@ -251,6 +251,8 @@ The old global `lsp.enabled` and `lsp.active` keys are deprecated and ignored. M
|
|
|
251
251
|
- `supi-code-runtime` owns canonical provider contracts and workspace capability state.
|
|
252
252
|
- `supi-lsp` owns semantic lifecycle and the Workspace LSP runtime.
|
|
253
253
|
- `supi-tree-sitter` owns structural parser reuse.
|
|
254
|
+
- The process-shared Workspace provider host starts LSP and Tree-sitter once per canonical workspace and releases them after the final session lease. Target and refactor handles remain session-local.
|
|
255
|
+
- The Headless inspection profile is for managed child sessions and registers only the six non-mutating inspection tools; it omits refactors, settings, UI, commands, and overview injection.
|
|
254
256
|
|
|
255
257
|
Markdown and TUI are adapters over assembled typed results. Providers, clients, mutable targets, and the LSP manager do not cross the workflow seam.
|
|
256
258
|
|
|
@@ -264,4 +266,5 @@ See:
|
|
|
264
266
|
## Package exports
|
|
265
267
|
|
|
266
268
|
- `@mrclrchtr/supi-code-intelligence/api` — reusable type contracts
|
|
267
|
-
- `@mrclrchtr/supi-code-intelligence/extension` — PI extension entrypoint
|
|
269
|
+
- `@mrclrchtr/supi-code-intelligence/extension` — full interactive PI extension entrypoint
|
|
270
|
+
- `@mrclrchtr/supi-code-intelligence/headless` — managed-child inspection profile
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-runtime",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"!__tests__"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@mrclrchtr/supi-core": "4.
|
|
32
|
+
"@mrclrchtr/supi-core": "4.1.0"
|
|
33
33
|
},
|
|
34
34
|
"bundledDependencies": [
|
|
35
35
|
"@mrclrchtr/supi-core"
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,10 +48,6 @@
|
|
|
48
48
|
"optional": true
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@types/node": "25.9.5",
|
|
53
|
-
"vitest": "4.1.10"
|
|
54
|
-
},
|
|
55
51
|
"main": "src/api.ts",
|
|
56
52
|
"exports": {
|
|
57
53
|
"./api": "./src/api.ts",
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-runtime",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"!__tests__"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@mrclrchtr/supi-core": "4.
|
|
32
|
+
"@mrclrchtr/supi-core": "4.1.0"
|
|
33
33
|
},
|
|
34
34
|
"bundledDependencies": [
|
|
35
35
|
"@mrclrchtr/supi-core"
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-lsp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi LSP runtime — Language Server Protocol integration library for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"vscode-jsonrpc": "^9.0.0",
|
|
38
38
|
"vscode-languageserver-protocol": "^3.17.5",
|
|
39
39
|
"vscode-languageserver-types": "^3.17.5",
|
|
40
|
-
"@mrclrchtr/supi-code-runtime": "4.
|
|
41
|
-
"@mrclrchtr/supi-core": "4.
|
|
40
|
+
"@mrclrchtr/supi-code-runtime": "4.1.0",
|
|
41
|
+
"@mrclrchtr/supi-core": "4.1.0"
|
|
42
42
|
},
|
|
43
43
|
"bundledDependencies": [
|
|
44
44
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,10 +48,6 @@
|
|
|
48
48
|
"optional": true
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@types/node": "25.9.5",
|
|
53
|
-
"vitest": "4.1.10"
|
|
54
|
-
},
|
|
55
51
|
"main": "src/api.ts",
|
|
56
52
|
"exports": {
|
|
57
53
|
"./api": "./src/api.ts",
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-runtime",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"!__tests__"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@mrclrchtr/supi-core": "4.
|
|
32
|
+
"@mrclrchtr/supi-core": "4.1.0"
|
|
33
33
|
},
|
|
34
34
|
"bundledDependencies": [
|
|
35
35
|
"@mrclrchtr/supi-core"
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-tree-sitter",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi Tree-sitter library — structural AST analysis for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"web-tree-sitter": "^0.26.8",
|
|
37
|
-
"@mrclrchtr/supi-code-runtime": "4.
|
|
38
|
-
"@mrclrchtr/supi-core": "4.
|
|
37
|
+
"@mrclrchtr/supi-code-runtime": "4.1.0",
|
|
38
|
+
"@mrclrchtr/supi-core": "4.1.0"
|
|
39
39
|
},
|
|
40
40
|
"bundledDependencies": [
|
|
41
41
|
"@mrclrchtr/supi-code-runtime",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-intelligence",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "SuPi Code Intelligence extension — workspace orientation, target resolution and inspection, relationship analysis, code-aware search, live health, and semantic refactoring for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"yaml": "^2.9.0",
|
|
37
|
-
"@mrclrchtr/supi-
|
|
38
|
-
"@mrclrchtr/supi-
|
|
39
|
-
"@mrclrchtr/supi-
|
|
40
|
-
"@mrclrchtr/supi-
|
|
37
|
+
"@mrclrchtr/supi-core": "4.1.0",
|
|
38
|
+
"@mrclrchtr/supi-tree-sitter": "4.1.0",
|
|
39
|
+
"@mrclrchtr/supi-lsp": "4.1.0",
|
|
40
|
+
"@mrclrchtr/supi-code-runtime": "4.1.0"
|
|
41
41
|
},
|
|
42
42
|
"bundledDependencies": [
|
|
43
43
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
".": "./src/index.ts",
|
|
78
78
|
"./api": "./src/api.ts",
|
|
79
79
|
"./extension": "./src/extension.ts",
|
|
80
|
+
"./headless": "./src/headless.ts",
|
|
80
81
|
"./package.json": "./package.json"
|
|
81
82
|
}
|
|
82
83
|
}
|
package/src/app/app.ts
CHANGED
|
@@ -61,7 +61,10 @@ export function createSessionCache(): {
|
|
|
61
61
|
const sessions = new Map<string, WorkspaceCodeIntelligenceSession>();
|
|
62
62
|
return {
|
|
63
63
|
getOrCreate: (cwd) => getOrCreateSession(sessions, cwd),
|
|
64
|
-
clear: () =>
|
|
64
|
+
clear: () => {
|
|
65
|
+
for (const session of sessions.values()) session.clearStores();
|
|
66
|
+
sessions.clear();
|
|
67
|
+
},
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
|
package/src/extension.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Code Intelligence extension entry point — registers the focused code-intelligence tools,
|
|
2
|
-
//
|
|
2
|
+
// shared provider lifecycle, settings, UI, and the unified /supi-ci-status command.
|
|
3
3
|
|
|
4
4
|
import type { BeforeAgentStartEventResult, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { buildArchitectureModel } from "./analysis/architecture/discovery.ts";
|
|
@@ -10,10 +10,6 @@ import { registerLspSessionLifecycle } from "./substrate/lsp/lifecycle.ts";
|
|
|
10
10
|
import { registerWorkspaceRecoveryHandler } from "./substrate/lsp/recovery.ts";
|
|
11
11
|
import { registerLspSettings } from "./substrate/lsp/settings.ts";
|
|
12
12
|
import { createLspAdapterState } from "./substrate/lsp/state.ts";
|
|
13
|
-
import {
|
|
14
|
-
createTsAdapterState,
|
|
15
|
-
registerTsSessionLifecycle,
|
|
16
|
-
} from "./substrate/tree-sitter/lifecycle.ts";
|
|
17
13
|
import { registerCodeIntelligenceTools } from "./tool/register.ts";
|
|
18
14
|
import { registerLspFooterContribution } from "./ui/footer.ts";
|
|
19
15
|
import { estimateTokens, OVERVIEW_TOKEN_BUDGET, renderOverview } from "./ui/markdown/overview.ts";
|
|
@@ -21,102 +17,44 @@ import { buildOverviewData } from "./ui/markdown/overview-data.ts";
|
|
|
21
17
|
import { registerCiStatusCommand } from "./ui/status-command.ts";
|
|
22
18
|
|
|
23
19
|
const OVERVIEW_CUSTOM_TYPE = "code-intelligence-overview";
|
|
24
|
-
const PROCESS_EXIT_SAFETY_NET = Symbol.for("supi-code-intelligence/process-exit-safety-net");
|
|
25
|
-
|
|
26
|
-
interface ProcessExitSafetyNet {
|
|
27
|
-
cleanup: (() => void) | null;
|
|
28
|
-
handlerRegistered: boolean;
|
|
29
|
-
handler: () => void;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Keep one process-exit listener across `/reload` extension instances.
|
|
34
|
-
* Pi hosts one active extension API per process; replacing its cleanup callback
|
|
35
|
-
* prevents old adapter state from being retained after reload.
|
|
36
|
-
*/
|
|
37
|
-
function registerProcessExitSafetyNet(cleanup: () => void): () => void {
|
|
38
|
-
const host = globalThis as Record<symbol, ProcessExitSafetyNet | undefined>;
|
|
39
|
-
const safetyNet = host[PROCESS_EXIT_SAFETY_NET] ?? createProcessExitSafetyNet();
|
|
40
|
-
host[PROCESS_EXIT_SAFETY_NET] = safetyNet;
|
|
41
|
-
|
|
42
|
-
safetyNet.cleanup = cleanup;
|
|
43
|
-
if (!safetyNet.handlerRegistered) {
|
|
44
|
-
process.once("exit", safetyNet.handler);
|
|
45
|
-
safetyNet.handlerRegistered = true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return () => {
|
|
49
|
-
if (safetyNet.cleanup !== cleanup) return;
|
|
50
|
-
safetyNet.cleanup = null;
|
|
51
|
-
if (!safetyNet.handlerRegistered) return;
|
|
52
|
-
process.off("exit", safetyNet.handler);
|
|
53
|
-
safetyNet.handlerRegistered = false;
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function createProcessExitSafetyNet(): ProcessExitSafetyNet {
|
|
58
|
-
const safetyNet: ProcessExitSafetyNet = {
|
|
59
|
-
cleanup: null,
|
|
60
|
-
handlerRegistered: false,
|
|
61
|
-
handler: () => {},
|
|
62
|
-
};
|
|
63
|
-
safetyNet.handler = () => {
|
|
64
|
-
safetyNet.handlerRegistered = false;
|
|
65
|
-
const activeCleanup = safetyNet.cleanup;
|
|
66
|
-
safetyNet.cleanup = null;
|
|
67
|
-
activeCleanup?.();
|
|
68
|
-
};
|
|
69
|
-
return safetyNet;
|
|
70
|
-
}
|
|
71
20
|
|
|
21
|
+
/** Register the full interactive Code Intelligence profile. */
|
|
72
22
|
export default function codeIntelligenceExtension(
|
|
73
23
|
pi: ExtensionAPI,
|
|
74
24
|
getOrCreateSession?: (cwd: string) => WorkspaceCodeIntelligenceSession,
|
|
75
25
|
) {
|
|
76
26
|
const app = createCodeIntelligenceApp(pi);
|
|
77
|
-
|
|
78
27
|
const lspState = createLspAdapterState();
|
|
79
|
-
const tsState = createTsAdapterState();
|
|
80
28
|
|
|
81
|
-
// ── Substrate wiring ──────────────────────────────────────────────
|
|
82
29
|
registerCodeIntelligenceSettings(pi);
|
|
83
30
|
registerLspSettings(pi);
|
|
84
|
-
registerLspSessionLifecycle(pi, lspState)
|
|
85
|
-
registerWorkspaceRecoveryHandler(pi, lspState);
|
|
86
|
-
registerTsSessionLifecycle(pi, tsState);
|
|
87
|
-
|
|
88
|
-
// ── Attach controller refs to sessions on startup (ADR 0008) ──────
|
|
89
|
-
pi.on("session_start", (_event, ctx) => {
|
|
31
|
+
registerLspSessionLifecycle(pi, lspState, (ctx) => {
|
|
90
32
|
const session = app.getSession(ctx.cwd);
|
|
91
33
|
if (!session) return;
|
|
92
34
|
session.attachLspController(lspState.controller);
|
|
93
35
|
session.seedSentinelSnapshot(lspState.sentinelSnapshot);
|
|
94
36
|
session.setProjectTrusted(ctx.isProjectTrusted());
|
|
95
37
|
});
|
|
38
|
+
registerWorkspaceRecoveryHandler(pi, lspState);
|
|
96
39
|
|
|
97
|
-
// ── Tool registration ─────────────────────────────────────────────
|
|
98
40
|
registerCodeIntelligenceTools(
|
|
99
41
|
pi,
|
|
100
42
|
getOrCreateSession ?? ((cwd) => app.getSession(cwd) ?? app.createSession(cwd)),
|
|
101
43
|
);
|
|
102
44
|
|
|
103
|
-
// ── UI registration ───────────────────────────────────────────────
|
|
104
45
|
registerCiStatusCommand(pi);
|
|
105
46
|
registerLspFooterContribution(lspState);
|
|
106
47
|
|
|
107
|
-
// ── Native context path capture for instruction-file dedup ─────────
|
|
108
48
|
pi.on("before_agent_start", (event, ctx) => {
|
|
109
49
|
const session = app.getSession(ctx.cwd) ?? app.createSession(ctx.cwd);
|
|
110
50
|
session.captureNativeInstructionPaths(event.systemPromptOptions.contextFiles ?? []);
|
|
111
51
|
});
|
|
112
52
|
|
|
113
|
-
// ── Overview injection — uses the app-managed session state ────────
|
|
114
53
|
pi.on(
|
|
115
54
|
"before_agent_start",
|
|
116
55
|
async (_event, ctx): Promise<BeforeAgentStartEventResult | undefined> => {
|
|
117
56
|
const session = app.getSession(ctx.cwd);
|
|
118
|
-
if (!session) return;
|
|
119
|
-
if (!session.claimOverviewInjection()) return;
|
|
57
|
+
if (!session?.claimOverviewInjection()) return;
|
|
120
58
|
|
|
121
59
|
const model = await buildArchitectureModel(ctx.cwd);
|
|
122
60
|
if (model.modules.length === 0) return;
|
|
@@ -138,24 +76,8 @@ export default function codeIntelligenceExtension(
|
|
|
138
76
|
}
|
|
139
77
|
|
|
140
78
|
return {
|
|
141
|
-
message: {
|
|
142
|
-
customType: OVERVIEW_CUSTOM_TYPE,
|
|
143
|
-
display: false,
|
|
144
|
-
content: overview,
|
|
145
|
-
},
|
|
79
|
+
message: { customType: OVERVIEW_CUSTOM_TYPE, display: false, content: overview },
|
|
146
80
|
};
|
|
147
81
|
},
|
|
148
82
|
);
|
|
149
|
-
|
|
150
|
-
// ── Process-exit safety net ─────────────────────────────────────────
|
|
151
|
-
let cleaningUp = false;
|
|
152
|
-
const unregisterExitSafetyNet = registerProcessExitSafetyNet(() => {
|
|
153
|
-
if (cleaningUp) return;
|
|
154
|
-
cleaningUp = true;
|
|
155
|
-
// Exit handlers cannot await. Controller shutdown begins synchronous
|
|
156
|
-
// process cleanup before its first await.
|
|
157
|
-
void lspState.controller?.shutdown();
|
|
158
|
-
void tsState.controller?.shutdown();
|
|
159
|
-
});
|
|
160
|
-
pi.on("session_shutdown", unregisterExitSafetyNet);
|
|
161
83
|
}
|
package/src/headless.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { createSessionCache } from "./app/app.ts";
|
|
3
|
+
import { registerLspSessionLifecycle } from "./substrate/lsp/lifecycle.ts";
|
|
4
|
+
import { createLspAdapterState } from "./substrate/lsp/state.ts";
|
|
5
|
+
import { registerCodeIntelligenceTools } from "./tool/register.ts";
|
|
6
|
+
import { CODE_INTELLIGENCE_TOOL_SPECS } from "./tool/specs.ts";
|
|
7
|
+
|
|
8
|
+
/** Exact Code Intelligence tool surface available to managed Reviewer Sessions. */
|
|
9
|
+
export const HEADLESS_INSPECTION_TOOL_NAMES = [
|
|
10
|
+
"code_resolve",
|
|
11
|
+
"code_inspect",
|
|
12
|
+
"code_orientation",
|
|
13
|
+
"code_graph",
|
|
14
|
+
"code_find",
|
|
15
|
+
"code_health",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
const inspectionToolNames = new Set<string>(HEADLESS_INSPECTION_TOOL_NAMES);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register the managed-child profile: six inspection tools backed by the shared
|
|
22
|
+
* Workspace provider host, without refactors, settings, UI, or overview state.
|
|
23
|
+
*/
|
|
24
|
+
export default function headlessInspectionProfile(pi: ExtensionAPI): void {
|
|
25
|
+
const sessions = createSessionCache();
|
|
26
|
+
const lspState = createLspAdapterState();
|
|
27
|
+
registerLspSessionLifecycle(pi, lspState, (ctx) => {
|
|
28
|
+
const session = sessions.getOrCreate(ctx.cwd);
|
|
29
|
+
session.attachLspController(lspState.controller);
|
|
30
|
+
session.seedSentinelSnapshot(lspState.sentinelSnapshot);
|
|
31
|
+
session.setProjectTrusted(ctx.isProjectTrusted());
|
|
32
|
+
});
|
|
33
|
+
pi.on("session_shutdown", () => sessions.clear());
|
|
34
|
+
|
|
35
|
+
registerCodeIntelligenceTools(
|
|
36
|
+
pi,
|
|
37
|
+
sessions.getOrCreate,
|
|
38
|
+
undefined,
|
|
39
|
+
CODE_INTELLIGENCE_TOOL_SPECS.filter((spec) => inspectionToolNames.has(spec.name)),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -1,53 +1,39 @@
|
|
|
1
|
-
// LSP
|
|
2
|
-
//
|
|
3
|
-
// Registers session_start and session_shutdown handlers that manage
|
|
4
|
-
// the LspRuntimeController lifecycle.
|
|
1
|
+
// LSP lifecycle backed by the process-shared Workspace provider host.
|
|
5
2
|
|
|
6
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { getDefaultWorkspaceRuntime } from "@mrclrchtr/supi-code-runtime/api";
|
|
8
|
-
import { LspRuntimeController, scanWorkspaceSentinels } from "@mrclrchtr/supi-lsp/api";
|
|
9
4
|
import { unregisterLspFooterContribution } from "../../ui/footer.ts";
|
|
5
|
+
import { acquireWorkspaceProviderHost } from "../workspace-provider-host.ts";
|
|
10
6
|
import type { LspAdapterState } from "./state.ts";
|
|
11
7
|
|
|
12
|
-
|
|
8
|
+
/** Acquire providers once per workspace and release them after the final Pi session ends. */
|
|
9
|
+
export function registerLspSessionLifecycle(
|
|
10
|
+
pi: ExtensionAPI,
|
|
11
|
+
state: LspAdapterState,
|
|
12
|
+
onStarted?: (ctx: ExtensionContext) => void | Promise<void>,
|
|
13
|
+
): void {
|
|
13
14
|
pi.on("session_start", async (_event, ctx: ExtensionContext) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (state.controller) {
|
|
18
|
-
await state.controller.shutdown();
|
|
19
|
-
state.controller = null;
|
|
20
|
-
}
|
|
15
|
+
await state.providerLease?.release();
|
|
16
|
+
state.providerLease = null;
|
|
17
|
+
state.controller = null;
|
|
21
18
|
state.lspActive = false;
|
|
19
|
+
state.sentinelSnapshot = new Map();
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const controller = new LspRuntimeController(cwd, runtime);
|
|
33
|
-
const result = await controller.start();
|
|
34
|
-
|
|
35
|
-
if (result.kind === "ready") {
|
|
36
|
-
state.controller = controller;
|
|
37
|
-
state.lspActive = true;
|
|
38
|
-
state.sentinelSnapshot = scanWorkspaceSentinels(cwd);
|
|
39
|
-
} else {
|
|
40
|
-
state.controller = null;
|
|
41
|
-
state.lspActive = false;
|
|
42
|
-
}
|
|
21
|
+
const lease = await acquireWorkspaceProviderHost(ctx.cwd, {
|
|
22
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
23
|
+
});
|
|
24
|
+
state.providerLease = lease;
|
|
25
|
+
state.controller = lease.lspController;
|
|
26
|
+
state.lspActive = lease.lspController?.kind === "ready";
|
|
27
|
+
state.sentinelSnapshot = lease.sentinelSnapshot;
|
|
28
|
+
await onStarted?.(ctx);
|
|
43
29
|
});
|
|
44
30
|
|
|
45
|
-
pi.on("session_shutdown", async (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
31
|
+
pi.on("session_shutdown", async () => {
|
|
32
|
+
await state.providerLease?.release();
|
|
33
|
+
state.providerLease = null;
|
|
34
|
+
state.controller = null;
|
|
50
35
|
state.lspActive = false;
|
|
36
|
+
state.sentinelSnapshot = new Map();
|
|
51
37
|
unregisterLspFooterContribution();
|
|
52
38
|
});
|
|
53
39
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// through LspRuntimeController from @mrclrchtr/supi-lsp/api.
|
|
5
5
|
|
|
6
6
|
import type { LspRuntimeController } from "@mrclrchtr/supi-lsp/api";
|
|
7
|
+
import type { WorkspaceProviderHostLease } from "../workspace-provider-host.ts";
|
|
7
8
|
|
|
8
9
|
/** LSP status overlay UI state (handle + close). */
|
|
9
10
|
export interface LspInspectorState {
|
|
@@ -19,6 +20,7 @@ export interface LspInspectorState {
|
|
|
19
20
|
*/
|
|
20
21
|
export interface LspAdapterState {
|
|
21
22
|
controller: LspRuntimeController | null;
|
|
23
|
+
providerLease: WorkspaceProviderHostLease | null;
|
|
22
24
|
inspector: LspInspectorState;
|
|
23
25
|
lspActive: boolean;
|
|
24
26
|
/** Snapshot of workspace sentinel files (package.json, tsconfig, lockfiles) for change detection. */
|
|
@@ -28,6 +30,7 @@ export interface LspAdapterState {
|
|
|
28
30
|
export function createLspAdapterState(): LspAdapterState {
|
|
29
31
|
return {
|
|
30
32
|
controller: null,
|
|
33
|
+
providerLease: null,
|
|
31
34
|
inspector: { handle: null, close: null },
|
|
32
35
|
lspActive: false,
|
|
33
36
|
sentinelSnapshot: new Map(),
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { getDefaultWorkspaceRuntime } from "@mrclrchtr/supi-code-runtime/api";
|
|
4
|
+
import { LspRuntimeController, scanWorkspaceSentinels } from "@mrclrchtr/supi-lsp/api";
|
|
5
|
+
import { TreeSitterRuntimeController } from "@mrclrchtr/supi-tree-sitter/api";
|
|
6
|
+
|
|
7
|
+
const HOSTS = Symbol.for("supi-code-intelligence/workspace-provider-hosts");
|
|
8
|
+
const SHUTDOWN_GRACE_MS = 2_000;
|
|
9
|
+
|
|
10
|
+
type HostRegistry = Map<string, WorkspaceProviderHost>;
|
|
11
|
+
|
|
12
|
+
/** One acquired reference to a process-shared Workspace provider host. */
|
|
13
|
+
export interface WorkspaceProviderHostLease {
|
|
14
|
+
cwd: string;
|
|
15
|
+
lspController: LspRuntimeController | null;
|
|
16
|
+
sentinelSnapshot: Map<string, number>;
|
|
17
|
+
release(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function registry(): HostRegistry {
|
|
21
|
+
const global = globalThis as typeof globalThis & { [HOSTS]?: HostRegistry };
|
|
22
|
+
global[HOSTS] ??= new Map();
|
|
23
|
+
return global[HOSTS];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function canonicalWorkspace(cwd: string): string {
|
|
27
|
+
try {
|
|
28
|
+
return realpathSync(cwd);
|
|
29
|
+
} catch {
|
|
30
|
+
return resolve(cwd);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function settleWithin(operation: Promise<void>): Promise<void> {
|
|
35
|
+
return Promise.race([
|
|
36
|
+
operation.catch(() => undefined),
|
|
37
|
+
new Promise<void>((resolveGrace) => {
|
|
38
|
+
const timeout = setTimeout(resolveGrace, SHUTDOWN_GRACE_MS);
|
|
39
|
+
timeout.unref?.();
|
|
40
|
+
}),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class WorkspaceProviderHost {
|
|
45
|
+
readonly cwd: string;
|
|
46
|
+
#leases = 0;
|
|
47
|
+
#lsp: LspRuntimeController | null = null;
|
|
48
|
+
#tree: TreeSitterRuntimeController | null = null;
|
|
49
|
+
#treeStarted = false;
|
|
50
|
+
#lspStarted = false;
|
|
51
|
+
#settledStart = Promise.resolve();
|
|
52
|
+
#closed = false;
|
|
53
|
+
|
|
54
|
+
constructor(cwd: string) {
|
|
55
|
+
this.cwd = cwd;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async acquire(projectTrusted: boolean): Promise<WorkspaceProviderHostLease> {
|
|
59
|
+
if (this.#closed) throw new Error("Workspace provider host is closed.");
|
|
60
|
+
// Count this pending acquirer before awaiting startup so the final active lease cannot shut it down.
|
|
61
|
+
this.#leases++;
|
|
62
|
+
this.#settledStart = this.#settledStart
|
|
63
|
+
.then(() => this.#startMissing(projectTrusted))
|
|
64
|
+
.catch(() => undefined);
|
|
65
|
+
await this.#settledStart;
|
|
66
|
+
let released = false;
|
|
67
|
+
return {
|
|
68
|
+
cwd: this.cwd,
|
|
69
|
+
lspController: projectTrusted ? this.#lsp : null,
|
|
70
|
+
sentinelSnapshot:
|
|
71
|
+
projectTrusted && this.#lsp?.kind === "ready"
|
|
72
|
+
? scanWorkspaceSentinels(this.cwd)
|
|
73
|
+
: new Map(),
|
|
74
|
+
release: async () => {
|
|
75
|
+
if (released) return;
|
|
76
|
+
released = true;
|
|
77
|
+
this.#leases--;
|
|
78
|
+
if (this.#leases === 0) {
|
|
79
|
+
registry().delete(this.cwd);
|
|
80
|
+
await this.#shutdown();
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async #startMissing(projectTrusted: boolean): Promise<void> {
|
|
87
|
+
const runtime = getDefaultWorkspaceRuntime();
|
|
88
|
+
if (!this.#treeStarted) {
|
|
89
|
+
this.#treeStarted = true;
|
|
90
|
+
this.#tree = new TreeSitterRuntimeController(this.cwd, runtime);
|
|
91
|
+
await this.#tree.start().catch(() => undefined);
|
|
92
|
+
}
|
|
93
|
+
if (!projectTrusted || this.#lspStarted) return;
|
|
94
|
+
this.#lspStarted = true;
|
|
95
|
+
this.#lsp = new LspRuntimeController(this.cwd, runtime);
|
|
96
|
+
await this.#lsp.start().catch(() => undefined);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async #shutdown(): Promise<void> {
|
|
100
|
+
if (this.#closed) return;
|
|
101
|
+
this.#closed = true;
|
|
102
|
+
await Promise.all([
|
|
103
|
+
...(this.#lsp ? [settleWithin(this.#lsp.shutdown())] : []),
|
|
104
|
+
...(this.#tree ? [settleWithin(this.#tree.shutdown())] : []),
|
|
105
|
+
]);
|
|
106
|
+
this.#lsp = null;
|
|
107
|
+
this.#tree = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Acquire shared semantic and structural providers for one canonical workspace. */
|
|
112
|
+
export async function acquireWorkspaceProviderHost(
|
|
113
|
+
cwd: string,
|
|
114
|
+
options: { projectTrusted: boolean },
|
|
115
|
+
): Promise<WorkspaceProviderHostLease> {
|
|
116
|
+
const workspace = canonicalWorkspace(cwd);
|
|
117
|
+
const hosts = registry();
|
|
118
|
+
let host = hosts.get(workspace);
|
|
119
|
+
if (!host) {
|
|
120
|
+
host = new WorkspaceProviderHost(workspace);
|
|
121
|
+
hosts.set(workspace, host);
|
|
122
|
+
}
|
|
123
|
+
return host.acquire(options.projectTrusted);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Clear process-shared hosts between isolated tests. */
|
|
127
|
+
export function resetWorkspaceProviderHostsForTests(): void {
|
|
128
|
+
registry().clear();
|
|
129
|
+
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Tree-sitter session lifecycle — uses TreeSitterRuntimeController from @mrclrchtr/supi-tree-sitter/api.
|
|
2
|
-
|
|
3
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { getDefaultWorkspaceRuntime } from "@mrclrchtr/supi-code-runtime/api";
|
|
5
|
-
import { TreeSitterRuntimeController } from "@mrclrchtr/supi-tree-sitter/api";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Tree-sitter adapter state for the umbrella extension.
|
|
9
|
-
*/
|
|
10
|
-
export interface TsAdapterState {
|
|
11
|
-
controller: TreeSitterRuntimeController | null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function createTsAdapterState(): TsAdapterState {
|
|
15
|
-
return { controller: null };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Register Tree-sitter session lifecycle handlers.
|
|
20
|
-
*
|
|
21
|
-
* - session_start: creates TreeSitterRuntimeController, starts it, registers tools
|
|
22
|
-
* - session_shutdown: shuts down the controller
|
|
23
|
-
*/
|
|
24
|
-
export function registerTsSessionLifecycle(pi: ExtensionAPI, state: TsAdapterState): void {
|
|
25
|
-
pi.on("session_start", async (_event, ctx: ExtensionContext) => {
|
|
26
|
-
const cwd = ctx.cwd;
|
|
27
|
-
const runtime = getDefaultWorkspaceRuntime();
|
|
28
|
-
|
|
29
|
-
// Shut down any existing controller
|
|
30
|
-
if (state.controller) {
|
|
31
|
-
await state.controller.shutdown();
|
|
32
|
-
state.controller = null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const controller = new TreeSitterRuntimeController(cwd, runtime);
|
|
36
|
-
const result = await controller.start();
|
|
37
|
-
|
|
38
|
-
if (result.kind === "ready") {
|
|
39
|
-
state.controller = controller;
|
|
40
|
-
} else {
|
|
41
|
-
await controller.shutdown();
|
|
42
|
-
state.controller = null;
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
pi.on("session_shutdown", async () => {
|
|
47
|
-
if (state.controller) {
|
|
48
|
-
await state.controller.shutdown();
|
|
49
|
-
state.controller = null;
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|