@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.
Files changed (38) hide show
  1. package/README.md +4 -1
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  6. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  7. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  8. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
  12. 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
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  20. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  21. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
  22. 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
  23. 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
  24. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  25. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  26. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  27. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  28. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  29. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  30. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  31. package/package.json +6 -5
  32. package/src/app/app.ts +4 -1
  33. package/src/extension.ts +6 -84
  34. package/src/headless.ts +41 -0
  35. package/src/substrate/lsp/lifecycle.ts +25 -39
  36. package/src/substrate/lsp/state.ts +3 -0
  37. package/src/substrate/workspace-provider-host.ts +129 -0
  38. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.0.0",
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": {
@@ -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(", ");
@@ -26,6 +26,7 @@ export type {
26
26
  SettingsField,
27
27
  SettingsFieldAction,
28
28
  SettingsPersistedChange,
29
+ StringField,
29
30
  StringListField,
30
31
  ValueSource,
31
32
  } from "./settings/settings-schema.ts";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "4.0.0",
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.0.0"
32
+ "@mrclrchtr/supi-core": "4.1.0"
33
33
  },
34
34
  "bundledDependencies": [
35
35
  "@mrclrchtr/supi-core"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.0.0",
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": {
@@ -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(", ");
@@ -26,6 +26,7 @@ export type {
26
26
  SettingsField,
27
27
  SettingsFieldAction,
28
28
  SettingsPersistedChange,
29
+ StringField,
29
30
  StringListField,
30
31
  ValueSource,
31
32
  } from "./settings/settings-schema.ts";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.0.0",
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(", ");
@@ -26,6 +26,7 @@ export type {
26
26
  SettingsField,
27
27
  SettingsFieldAction,
28
28
  SettingsPersistedChange,
29
+ StringField,
29
30
  StringListField,
30
31
  ValueSource,
31
32
  } from "./settings/settings-schema.ts";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "4.0.0",
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.0.0"
32
+ "@mrclrchtr/supi-core": "4.1.0"
33
33
  },
34
34
  "bundledDependencies": [
35
35
  "@mrclrchtr/supi-core"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.0.0",
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": {
@@ -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(", ");
@@ -26,6 +26,7 @@ export type {
26
26
  SettingsField,
27
27
  SettingsFieldAction,
28
28
  SettingsPersistedChange,
29
+ StringField,
29
30
  StringListField,
30
31
  ValueSource,
31
32
  } from "./settings/settings-schema.ts";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-lsp",
3
- "version": "4.0.0",
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.0.0",
41
- "@mrclrchtr/supi-core": "4.0.0"
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.0.0",
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(", ");
@@ -26,6 +26,7 @@ export type {
26
26
  SettingsField,
27
27
  SettingsFieldAction,
28
28
  SettingsPersistedChange,
29
+ StringField,
29
30
  StringListField,
30
31
  ValueSource,
31
32
  } from "./settings/settings-schema.ts";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "4.0.0",
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.0.0"
32
+ "@mrclrchtr/supi-core": "4.1.0"
33
33
  },
34
34
  "bundledDependencies": [
35
35
  "@mrclrchtr/supi-core"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.0.0",
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": {
@@ -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(", ");
@@ -26,6 +26,7 @@ export type {
26
26
  SettingsField,
27
27
  SettingsFieldAction,
28
28
  SettingsPersistedChange,
29
+ StringField,
29
30
  StringListField,
30
31
  ValueSource,
31
32
  } from "./settings/settings-schema.ts";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "4.0.0",
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.0.0",
38
- "@mrclrchtr/supi-core": "4.0.0"
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.0.0",
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-code-runtime": "4.0.0",
38
- "@mrclrchtr/supi-lsp": "4.0.0",
39
- "@mrclrchtr/supi-core": "4.0.0",
40
- "@mrclrchtr/supi-tree-sitter": "4.0.0"
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: () => sessions.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
- // LSP diagnostics and settings, and the unified /supi-ci-status command.
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
  }
@@ -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 session lifecycle uses LspRuntimeController from @mrclrchtr/supi-lsp/api.
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
- export function registerLspSessionLifecycle(pi: ExtensionAPI, state: LspAdapterState): void {
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
- const cwd = ctx.cwd;
15
- const runtime = getDefaultWorkspaceRuntime();
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
- // Project-local LSP configuration may define custom commands; defer
24
- // until the project is trusted so an untrusted repository cannot control
25
- // spawned processes.
26
- if (!ctx.isProjectTrusted()) {
27
- state.controller = null;
28
- state.lspActive = false;
29
- return;
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 (_event, _ctx: ExtensionContext) => {
46
- if (state.controller) {
47
- await state.controller.shutdown();
48
- state.controller = null;
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
- }