@schlenkr/ragents 0.1.0 → 0.1.1

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 (94) hide show
  1. package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
  2. package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
  3. package/apps/server/src/plugin-support/run-scripts.ts +4 -1
  4. package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
  5. package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
  6. package/apps/web/src/App.tsx +0 -10
  7. package/apps/web/src/PluginChat.tsx +8 -8
  8. package/apps/web/src/PluginRegistry.tsx +0 -12
  9. package/apps/web/src/StartSurface.tsx +2 -2
  10. package/apps/web/src/ThemeSettings.tsx +1 -1
  11. package/apps/web/src/column/ColumnApp.tsx +83 -7
  12. package/apps/web/src/column/clipboard.ts +64 -0
  13. package/apps/web/src/column/host-contract.ts +10 -0
  14. package/apps/web/src/column.tsx +2 -0
  15. package/apps/web/src/panel/LoginForm.tsx +9 -3
  16. package/apps/web/src/panel/OverviewPage.tsx +187 -62
  17. package/apps/web/src/panel/PanelDialogs.tsx +115 -0
  18. package/apps/web/src/panel/PanelPage.tsx +1 -1
  19. package/apps/web/src/panel/SettingsPage.tsx +75 -79
  20. package/apps/web/src/panel/contract.ts +17 -9
  21. package/apps/web/src/panel/target-state.ts +27 -2
  22. package/apps/web/src/ui/theme.css +4 -0
  23. package/package.json +2 -2
  24. package/packages/ragents/src/plugin-host.ts +5 -2
  25. package/packages/ragents/src/plugin-types.ts +3 -2
  26. package/packages/workspace-executor/src/context.ts +4 -0
  27. package/packages/workspace-executor/src/executor.ts +9 -2
  28. package/packages/workspace-executor/src/index.ts +2 -0
  29. package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
  30. package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
  31. package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
  32. package/packages/workspace-executor/src/language-server/contract.ts +7 -3
  33. package/packages/workspace-executor/src/language-server/host.ts +275 -138
  34. package/plugins/ragents.actor-programs/contract.ts +1 -1
  35. package/plugins/ragents.actor-programs/prompt.hbs +4 -4
  36. package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
  37. package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
  38. package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
  39. package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
  40. package/plugins/ragents.actor-programs/web/api.ts +1 -10
  41. package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
  42. package/plugins/ragents.orchestration/canvas.hbs +28 -44
  43. package/plugins/ragents.orchestration/contract.ts +17 -287
  44. package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
  45. package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
  46. package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
  47. package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
  48. package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
  49. package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
  50. package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
  51. package/plugins/ragents.orchestration/web/Center.tsx +28 -463
  52. package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
  53. package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
  54. package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
  55. package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
  56. package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
  57. package/plugins/ragents.orchestration/web/constants.ts +0 -2
  58. package/plugins/ragents.orchestration/web/index.tsx +0 -6
  59. package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
  60. package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
  61. package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
  62. package/plugins/ragents.reference/examples.ts +1 -2
  63. package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
  64. package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
  65. package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
  66. package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
  67. package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
  68. package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
  69. package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
  70. package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
  71. package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
  72. package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
  73. package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
  74. package/plugins/ragents.reference/walkthroughs.ts +14 -14
  75. package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
  76. package/scripts/package/publish-package.ts +4 -5
  77. package/scripts/workspace-client/run-workspace-client.ts +3 -1
  78. package/apps/web/src/MaterialSettings.tsx +0 -40
  79. package/apps/web/src/material-settings.ts +0 -104
  80. package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
  81. package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
  82. package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
  83. package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
  84. package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
  85. package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
  86. package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
  87. package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
  88. package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
  89. package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
  90. package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
  91. package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
  92. package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
  93. package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
  94. package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
@@ -1,6 +1,6 @@
1
1
  import type { ColumnTheme } from "../column/host-contract";
2
2
 
3
- /** Ein Ziel der Erweiterung: ein RAgents-Server oder ein lokales Profil, das sie selbst startet. */
3
+ /** Eine Umgebung der Erweiterung: ein RAgents-Server oder ein lokales Profil, das sie selbst startet. */
4
4
  export type TargetKind = "server" | "profile";
5
5
 
6
6
  export type TargetState =
@@ -25,14 +25,16 @@ export interface TargetEntry {
25
25
  readonly id: string;
26
26
  readonly title: string;
27
27
  readonly description: string;
28
- readonly action: "skill" | "script";
28
+ readonly kind: "skill" | "script";
29
+ /** Die Gruppe der Kachelansicht; ein Run-Script ohne eigene Kategorie steht unter "Run-Scripts". */
30
+ readonly category: string;
29
31
  }
30
32
 
31
33
  export interface TargetView {
32
34
  readonly name: string;
33
35
  readonly kind: TargetKind;
34
- /** Adresse des Servers oder Profil samt Pfad. */
35
- readonly description: string;
36
+ /** Adresse des Servers oder Pfad der Profildatei. */
37
+ readonly address: string;
36
38
  readonly state: TargetState;
37
39
  readonly runs: readonly TargetRun[];
38
40
  readonly entries: readonly TargetEntry[];
@@ -42,9 +44,9 @@ export interface TargetView {
42
44
  readonly user?: string;
43
45
  /** Die zuletzt versuchte Kennung; das Anmeldeformular übernimmt sie. */
44
46
  readonly loginUser?: string;
45
- /** Anmeldedaten liegen in der SecretStorage, damit sich das Ziel still anmeldet. */
47
+ /** Anmeldedaten liegen in der SecretStorage, damit die Umgebung sich still anmeldet. */
46
48
  readonly savedLogin?: boolean;
47
- /** Fehler der letzten Aktion an diesem Ziel, direkt in seiner Karte. */
49
+ /** Fehler der letzten Aktion an dieser Umgebung, direkt in ihrer Zeile. */
48
50
  readonly problem?: string;
49
51
  }
50
52
 
@@ -52,9 +54,11 @@ export interface PanelState {
52
54
  readonly theme: ColumnTheme;
53
55
  readonly page: "overview" | "settings";
54
56
  readonly targets: readonly TargetView[];
57
+ /** Die Profildateien im Host-Ordner; der Dialog bietet sie an, ohne Host bleibt die Liste leer. */
58
+ readonly profileSuggestions: readonly string[];
55
59
  /** Die Einstellung ragents.connections ist fehlerhaft; die Seite nennt den Grund. */
56
60
  readonly problem?: string;
57
- /** Der zuletzt über den Dateidialog gewählte Profilpfad; das Formular übernimmt ihn. */
61
+ /** Der zuletzt über den Dateidialog gewählte Profilpfad; der Dialog übernimmt ihn. */
58
62
  readonly pickedProfileFile?: string;
59
63
  }
60
64
 
@@ -71,7 +75,9 @@ export type PanelAction =
71
75
  | { readonly action: "pickProfile" }
72
76
  | { readonly action: "addServer"; readonly name: string; readonly url: string }
73
77
  | { readonly action: "addProfile"; readonly name: string; readonly profileFile: string }
74
- | { readonly action: "remove" | "connect" | "disconnect" | "logout"; readonly name: string }
78
+ | { readonly action: "updateServer"; readonly name: string; readonly newName: string; readonly url: string }
79
+ | { readonly action: "updateProfile"; readonly name: string; readonly newName: string; readonly profileFile: string }
80
+ | { readonly action: "remove" | "connect" | "disconnect" | "logout" | "startProfile" | "stopProfile" | "retry"; readonly name: string }
75
81
  | { readonly action: "login"; readonly name: string; readonly user: string; readonly password: string }
76
82
  | { readonly action: "login"; readonly name: string; readonly token: string }
77
83
  | { readonly action: "openRun"; readonly name: string; readonly runId: string }
@@ -88,7 +94,9 @@ export const isPanelActionMessage = (value: unknown): value is PanelActionMessag
88
94
  case "settingsFile": case "pickProfile": return true;
89
95
  case "addServer": return text(value, "name") && text(value, "url");
90
96
  case "addProfile": return text(value, "name") && text(value, "profileFile");
91
- case "remove": case "connect": case "disconnect": case "logout": return text(value, "name");
97
+ case "updateServer": return text(value, "name") && text(value, "newName") && text(value, "url");
98
+ case "updateProfile": return text(value, "name") && text(value, "newName") && text(value, "profileFile");
99
+ case "remove": case "connect": case "disconnect": case "logout": case "startProfile": case "stopProfile": case "retry": return text(value, "name");
92
100
  case "login": return text(value, "name") && (text(value, "token") || (text(value, "user") && text(value, "password")));
93
101
  case "openRun": return text(value, "name") && text(value, "runId");
94
102
  case "newRun": return text(value, "name") && ((value as { entryId?: unknown }).entryId === undefined || text(value, "entryId"));
@@ -1,6 +1,6 @@
1
1
  import type { TargetRun, TargetView } from "./contract";
2
2
 
3
- /** Wie der Zustand eines Ziels in seiner Karte steht; ein lokales Profil ist nicht getrennt, sondern nicht gestartet. */
3
+ /** Wie der Zustand einer Umgebung in ihrer Zeile steht; ein lokales Profil ist nicht getrennt, sondern nicht gestartet. */
4
4
  export const stateLabel = (target: TargetView): string => {
5
5
  switch (target.state.kind) {
6
6
  case "stopped": return target.kind === "profile" ? "nicht gestartet" : "getrennt";
@@ -27,13 +27,17 @@ export const stateTone = (target: TargetView): string => {
27
27
  }
28
28
  };
29
29
 
30
- /** Der Grund, der zum Zustand gehört; er steht unter der Kopfzeile der Karte. */
30
+ /** Der Grund, der zum Zustand gehört; er steht unter der Zeile der Umgebung. */
31
31
  export const stateDetail = (target: TargetView): string | undefined => {
32
32
  const state = target.state;
33
33
  if (state.kind === "unreachable" || state.kind === "forbidden" || state.kind === "failed") return state.message;
34
34
  return undefined;
35
35
  };
36
36
 
37
+ export const kindLabel = (target: TargetView): string => target.kind === "profile" ? "lokales Profil" : "Server";
38
+
39
+ export const busyState = (target: TargetView): boolean => target.state.kind === "starting" || target.state.kind === "connecting";
40
+
37
41
  export const runStateLabel = (run: TargetRun): string => {
38
42
  switch (run.state) {
39
43
  case "running": return "läuft";
@@ -51,3 +55,24 @@ export const runTone = (run: TargetRun): string => {
51
55
  case "idle": return "text-muted-foreground";
52
56
  }
53
57
  };
58
+
59
+ const MINUTE = 60_000;
60
+ const HOUR = 60 * MINUTE;
61
+ const DAY = 24 * HOUR;
62
+
63
+ /** Wann ein Run zuletzt etwas getan hat; alles ab gestern nennt das Datum statt einer Spanne. */
64
+ export const runTime = (run: TargetRun, now: number = Date.now()): string => {
65
+ const passed = now - run.updatedAt;
66
+ if (passed < MINUTE) return "gerade eben";
67
+ if (passed < HOUR) return `vor ${Math.floor(passed / MINUTE)} Min`;
68
+ if (passed < DAY) return `vor ${Math.floor(passed / HOUR)} Std`;
69
+ return new Date(run.updatedAt).toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" });
70
+ };
71
+
72
+ const HUES = [255, 170, 300, 10, 130, 210, 60, 330];
73
+
74
+ /** Die Farbe einer Umgebung hängt an ihrem Namen, nicht an ihrer Stelle in der Liste; Umsortieren ändert sie nicht. */
75
+ export const environmentHue = (name: string): number => {
76
+ const hash = [...name].reduce((value, character) => (value * 31 + character.codePointAt(0)!) % 100_003, 7);
77
+ return HUES[hash % HUES.length]!;
78
+ };
@@ -134,6 +134,8 @@
134
134
  --warning-soft: #f3e4d8;
135
135
  --info: #4b648a;
136
136
  --teal: #356c70;
137
+ --env-lightness: 0.55;
138
+ --env-chroma: 0.12;
137
139
  --border: #c9c4d5;
138
140
  --border-soft: #dad6e3;
139
141
  --border-strong: #a397b2;
@@ -186,6 +188,8 @@
186
188
  --warning-soft: #43382b;
187
189
  --info: #9dbade;
188
190
  --teal: #8ec9c4;
191
+ --env-lightness: 0.79;
192
+ --env-chroma: 0.1;
189
193
  --border: #555065;
190
194
  --border-soft: #403c50;
191
195
  --border-strong: #8e809f;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schlenkr/ragents",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "RAgents-Host: Server, Engine, Plugins und Werkzeuge ohne Quell-Checkout",
5
5
  "keywords": [
6
6
  "agents",
@@ -77,6 +77,6 @@
77
77
  "yaml": "2.9.0"
78
78
  },
79
79
  "ragents": {
80
- "hostVersion": "c21cf651ca70b5851dbedfbc6f03d21cea77e596"
80
+ "hostVersion": "1f5768e0029dbc9663ecd4e31b365c4e4e99f531"
81
81
  }
82
82
  }
@@ -522,7 +522,10 @@ const validateStartEntry = (entry: StartEntryContribution): void => {
522
522
  }
523
523
  return;
524
524
  case "script":
525
- assertKnownFields(entry, entry, [...base, "script"], "Einstieg");
525
+ assertKnownFields(entry, entry, [...base, "script", "category"], "Einstieg");
526
+ if (entry.category !== undefined && (typeof entry.category !== "string" || !entry.category.trim() || entry.category !== entry.category.trim())) {
527
+ throw new Error(`Einstieg ${entry.id}: category muss ein einzelner nicht leerer Text sein`);
528
+ }
526
529
  validateScriptPackage(entry, entry.script);
527
530
  return;
528
531
  default:
@@ -572,7 +575,7 @@ export class StartEntryContributionRegistry {
572
575
  case "skill":
573
576
  return { ...base, action: "skill", skill: value.skill, category: value.category, prompt: value.prompt };
574
577
  case "script":
575
- return { ...base, action: "script", coordinator: value.script.coordinator };
578
+ return { ...base, action: "script", coordinator: value.script.coordinator, ...(value.category !== undefined ? { category: value.category } : {}) };
576
579
  }
577
580
  })
578
581
  .sort(byOrderThenId);
@@ -207,7 +207,8 @@ export interface RunScriptPackage {
207
207
 
208
208
  export type StartEntryContribution = StartEntryBase & (
209
209
  | { action: "skill"; skill: string; category: string; prompt: string }
210
- | { action: "script"; script: RunScriptPackage }
210
+ /** A run script may name a category of its own; without one the start surfaces group it as a run script. */
211
+ | { action: "script"; script: RunScriptPackage; category?: string }
211
212
  );
212
213
 
213
214
  export type StartEntryAction = StartEntryContribution["action"];
@@ -215,7 +216,7 @@ export type StartEntryAction = StartEntryContribution["action"];
215
216
  /** The entry as the web sees it: a script entry shows its kind and coordinator flag, never its source. */
216
217
  export type PublicStartEntry = StartEntryBase & { owner: string } & (
217
218
  | { action: "skill"; skill: string; category: string; prompt: string }
218
- | { action: "script"; coordinator: boolean }
219
+ | { action: "script"; coordinator: boolean; category?: string }
219
220
  );
220
221
 
221
222
  export interface SkillContribution {
@@ -12,6 +12,8 @@ export interface WorkspaceProcessContext {
12
12
  home: string;
13
13
  /** Wohin ein Sprachserver Protokolle und Zwischenstände legen darf; nie das Home eines Entwicklers. */
14
14
  logDirectory: string;
15
+ /** Die Wurzel des Hosts auf dieser Maschine, aus der ein Adapter seinen Sprachserver auflöst; ohne Host bleibt sie offen. */
16
+ hostRoot: string | undefined;
15
17
  env: NodeJS.ProcessEnv;
16
18
  uid?: number;
17
19
  gid?: number;
@@ -78,6 +80,7 @@ export interface WorkspaceContextOptions {
78
80
  root: string;
79
81
  home: SandboxHomeEnvironment;
80
82
  logDirectory: string;
83
+ hostRoot: string | undefined;
81
84
  ident?: SessionIdent;
82
85
  additionalRoots?: readonly ResolvedWorkspaceRoot[];
83
86
  readOnlyRoots?: readonly ResolvedWorkspaceRoot[];
@@ -107,6 +110,7 @@ export const workspaceProcessContext = (options: WorkspaceContextOptions): Works
107
110
  root: options.root,
108
111
  home: options.home.home,
109
112
  logDirectory: options.logDirectory,
113
+ hostRoot: options.hostRoot,
110
114
  env: sandboxEnvironment(options.source ?? process.env, {
111
115
  home: options.home,
112
116
  ...(options.ident ? { ident: options.ident } : {}),
@@ -12,6 +12,8 @@ export const languageServerOpenTool = (adapterId: string): string => `${adapterI
12
12
 
13
13
  export const languageServerDiagnosticsTool = (adapterId: string): string => `${adapterId}_diagnostics`;
14
14
 
15
+ export const languageServerCloseTool = (adapterId: string): string => `${adapterId}_close`;
16
+
15
17
  export const languageServerSnapshotTool = (adapterId: string): string => `${adapterId}_snapshot`;
16
18
 
17
19
  export interface WorkspaceExecuteOptions {
@@ -62,6 +64,7 @@ export class SandboxWorkspaceExecutor implements WorkspaceExecutor {
62
64
  const languageServer = this.#languageServers.find((host) =>
63
65
  tool === languageServerOpenTool(host.adapter.id)
64
66
  || tool === languageServerDiagnosticsTool(host.adapter.id)
67
+ || tool === languageServerCloseTool(host.adapter.id)
65
68
  || tool === languageServerSnapshotTool(host.adapter.id));
66
69
  if (languageServer) return this.#languageServerCall(languageServer, runId, tool, input);
67
70
  return this.#toolCall(runId, tool, input, options);
@@ -105,8 +108,12 @@ export class SandboxWorkspaceExecutor implements WorkspaceExecutor {
105
108
  const { root } = input as { root: string };
106
109
  return host.open(runId, root);
107
110
  }
108
- const { paths, warnings } = input as { paths?: readonly string[]; warnings?: boolean };
109
- return host.diagnostics(runId, paths, warnings ?? false);
111
+ if (tool === languageServerCloseTool(host.adapter.id)) {
112
+ const { root } = (input ?? {}) as { root?: string };
113
+ return host.close(runId, root);
114
+ }
115
+ const { paths, warnings, root } = input as { paths?: readonly string[]; warnings?: boolean; root?: string };
116
+ return host.diagnostics(runId, paths, warnings ?? false, root);
110
117
  }
111
118
 
112
119
  #sandbox(runId: string): Promise<SandboxTools> {
@@ -9,6 +9,7 @@ export {
9
9
  SandboxWorkspaceExecutor,
10
10
  WORKSPACE_EXECUTOR_VERSION,
11
11
  WORKSPACE_STOP_OPERATION,
12
+ languageServerCloseTool,
12
13
  languageServerDiagnosticsTool,
13
14
  languageServerOpenTool,
14
15
  languageServerSnapshotTool,
@@ -38,6 +39,7 @@ export {
38
39
  export {
39
40
  type LanguageServerDiagnostic,
40
41
  type LanguageServerFileDiagnostics,
42
+ type LanguageServerInstanceSnapshot,
41
43
  type LanguageServerSeverity,
42
44
  type LanguageServerSnapshot,
43
45
  type LanguageServerState,
@@ -30,6 +30,7 @@ export const fsharpAdapter: LanguageServerAdapter = {
30
30
  languages,
31
31
  rootDescription: "the .sln file (or a single .fsproj)",
32
32
  resolveRoot: (workspaceRoot, root) => resolveRootFile(workspaceRoot, root, [".sln", ".fsproj"]),
33
+ rootDirectory: (root) => path.dirname(root),
33
34
  launch: async (context, root) => ({
34
35
  label: "FSAC",
35
36
  ...dotnetCommand(serverPath(), ["--state-directory", path.join(context.logDirectory, "fsautocomplete")]),
@@ -27,6 +27,7 @@ export const roslynAdapter: LanguageServerAdapter = {
27
27
  languages,
28
28
  rootDescription: "the .sln file (or a single .csproj)",
29
29
  resolveRoot: (workspaceRoot, root) => resolveRootFile(workspaceRoot, root, [".sln", ".slnx", ".csproj"]),
30
+ rootDirectory: (root) => path.dirname(root),
30
31
  launch: async (context, root) => ({
31
32
  label: "Roslyn",
32
33
  ...dotnetCommand(serverPath(), [
@@ -5,10 +5,6 @@ import { pathToFileURL } from "node:url";
5
5
  import type { LanguageServerAdapter } from "../host.js";
6
6
  import { resolveRootDirectory } from "../roots.js";
7
7
 
8
- const require = createRequire(import.meta.url);
9
- const serverScript = require.resolve("typescript-language-server/lib/cli.mjs");
10
- const bundledTypeScriptLib = path.dirname(require.resolve("typescript"));
11
-
12
8
  const languages = {
13
9
  ".ts": "typescript",
14
10
  ".tsx": "typescriptreact",
@@ -18,6 +14,37 @@ const languages = {
18
14
  ".jsx": "javascriptreact",
19
15
  } as const;
20
16
 
17
+ /** Der Sprachserver liegt in den node_modules des Hosts; ohne Host gibt es ihn auf dieser Maschine nicht. */
18
+ const hostRequire = (hostRoot: string | undefined): NodeRequire => {
19
+ if (!hostRoot) {
20
+ throw new Error("Für TypeScript ist auf diesem Rechner kein Host bekannt, aus dem sich typescript-language-server "
21
+ + "auflösen ließe. Ein Arbeitsplatz bekommt ihn mit der ersten Verbindung zu einem verteilenden Server oder über "
22
+ + "die Einstellung ragents.hostPath");
23
+ }
24
+ return createRequire(path.join(hostRoot, "package.json"));
25
+ };
26
+
27
+ const missing = (hostRoot: string | undefined, name: string, cause: unknown): Error =>
28
+ new Error(`${name} liegt nicht im Host ${hostRoot}: ${cause instanceof Error ? cause.message : String(cause)}`);
29
+
30
+ const serverScript = (hostRoot: string | undefined): string => {
31
+ const require = hostRequire(hostRoot);
32
+ try {
33
+ return require.resolve("typescript-language-server/lib/cli.mjs");
34
+ } catch (cause) {
35
+ throw missing(hostRoot, "typescript-language-server", cause);
36
+ }
37
+ };
38
+
39
+ const bundledTypeScriptLib = (hostRoot: string | undefined): string => {
40
+ const require = hostRequire(hostRoot);
41
+ try {
42
+ return path.dirname(require.resolve("typescript"));
43
+ } catch (cause) {
44
+ throw missing(hostRoot, "typescript", cause);
45
+ }
46
+ };
47
+
21
48
  const hasWorkspaceTypeScript = (root: string): Promise<boolean> =>
22
49
  stat(path.join(root, "node_modules", "typescript", "lib", "tsserver.js")).then((info) => info.isFile(), () => false);
23
50
 
@@ -27,17 +54,18 @@ export const typescriptAdapter: LanguageServerAdapter = {
27
54
  languages,
28
55
  rootDescription: "the directory whose tsconfig.json projects should be served (e.g. src)",
29
56
  resolveRoot: resolveRootDirectory,
57
+ rootDirectory: (root) => root,
30
58
  launch: async (context, root) => ({
31
59
  label: "TypeScript",
32
60
  command: process.execPath,
33
- args: [serverScript, "--stdio"],
61
+ args: [serverScript(context.hostRoot), "--stdio"],
34
62
  cwd: root,
35
63
  // Im Extension-Host ist process.execPath Electron; ohne diese Variable startet es die Anwendung statt des Skripts.
36
64
  env: { ...context.env, ELECTRON_RUN_AS_NODE: "1" },
37
65
  uid: context.uid,
38
66
  gid: context.gid,
39
67
  rootUri: pathToFileURL(root).href,
40
- initializationOptions: await hasWorkspaceTypeScript(root) ? {} : { tsserver: { path: bundledTypeScriptLib } },
68
+ initializationOptions: await hasWorkspaceTypeScript(root) ? {} : { tsserver: { path: bundledTypeScriptLib(context.hostRoot) } },
41
69
  languages,
42
70
  publishesOnlyChangedDiagnostics: true,
43
71
  }),
@@ -1,4 +1,4 @@
1
- export type LanguageServerState = "closed" | "opening" | "ready" | "failed" | "suspended";
1
+ export type LanguageServerState = "opening" | "ready" | "failed" | "suspended";
2
2
 
3
3
  export type LanguageServerSeverity = "error" | "warning" | "information" | "hint";
4
4
 
@@ -15,9 +15,13 @@ export interface LanguageServerFileDiagnostics {
15
15
  diagnostics: readonly LanguageServerDiagnostic[];
16
16
  }
17
17
 
18
- export interface LanguageServerSnapshot {
18
+ export interface LanguageServerInstanceSnapshot {
19
19
  state: LanguageServerState;
20
- root: string | null;
20
+ root: string;
21
21
  summary: string | null;
22
22
  files: readonly LanguageServerFileDiagnostics[];
23
23
  }
24
+
25
+ export interface LanguageServerSnapshot {
26
+ instances: readonly LanguageServerInstanceSnapshot[];
27
+ }