@schlenkr/ragents 0.1.0 → 0.1.2
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 +10 -4
- package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
- package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
- package/apps/server/src/plugin-support/run-scripts.ts +4 -1
- package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
- package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
- package/apps/server/src/profile-target.ts +25 -0
- package/apps/server/src/web-build.ts +82 -0
- package/apps/server/tsconfig.json +38 -0
- package/apps/web/column.html +12 -0
- package/apps/web/index.html +12 -0
- package/apps/web/src/App.tsx +0 -10
- package/apps/web/src/PluginChat.tsx +8 -8
- package/apps/web/src/PluginRegistry.tsx +0 -12
- package/apps/web/src/StartSurface.tsx +2 -2
- package/apps/web/src/ThemeSettings.tsx +1 -1
- package/apps/web/src/column/ColumnApp.tsx +83 -7
- package/apps/web/src/column/clipboard.ts +64 -0
- package/apps/web/src/column/host-contract.ts +10 -0
- package/apps/web/src/column.tsx +2 -0
- package/apps/web/src/panel/LoginForm.tsx +9 -3
- package/apps/web/src/panel/OverviewPage.tsx +187 -62
- package/apps/web/src/panel/PanelDialogs.tsx +115 -0
- package/apps/web/src/panel/PanelPage.tsx +1 -1
- package/apps/web/src/panel/SettingsPage.tsx +75 -79
- package/apps/web/src/panel/contract.ts +17 -9
- package/apps/web/src/panel/target-state.ts +27 -2
- package/apps/web/src/ui/theme.css +4 -0
- package/apps/web/tsconfig.json +41 -0
- package/apps/web/vite.config.ts +140 -0
- package/package.json +6 -2
- package/packages/ragents/src/plugin-host.ts +5 -2
- package/packages/ragents/src/plugin-types.ts +3 -2
- package/packages/workspace-executor/src/context.ts +4 -0
- package/packages/workspace-executor/src/executor.ts +9 -2
- package/packages/workspace-executor/src/index.ts +2 -0
- package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
- package/packages/workspace-executor/src/language-server/contract.ts +7 -3
- package/packages/workspace-executor/src/language-server/host.ts +275 -138
- package/plugins/ragents.actor-programs/contract.ts +1 -1
- package/plugins/ragents.actor-programs/prompt.hbs +4 -4
- package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
- package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
- package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
- package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
- package/plugins/ragents.actor-programs/web/api.ts +1 -10
- package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
- package/plugins/ragents.orchestration/canvas.hbs +28 -44
- package/plugins/ragents.orchestration/contract.ts +17 -287
- package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
- package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
- package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
- package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
- package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
- package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
- package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
- package/plugins/ragents.orchestration/web/Center.tsx +28 -463
- package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
- package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
- package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
- package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
- package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
- package/plugins/ragents.orchestration/web/constants.ts +0 -2
- package/plugins/ragents.orchestration/web/index.tsx +0 -6
- package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
- package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
- package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
- package/plugins/ragents.profile-distribution/server/web.ts +3 -27
- package/plugins/ragents.reference/examples.ts +1 -2
- package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
- package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
- package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
- package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
- package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
- package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
- package/plugins/ragents.reference/walkthroughs.ts +14 -14
- package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
- package/scripts/agent/agent-cli.ts +9 -24
- package/scripts/package/build-package.ts +18 -5
- package/scripts/package/publish-package.ts +24 -10
- package/scripts/package/ragents.mjs +3 -2
- package/scripts/remote/connect.ts +16 -5
- package/scripts/remote/start.ts +33 -15
- package/scripts/workspace-client/run-workspace-client.ts +3 -1
- package/apps/web/src/MaterialSettings.tsx +0 -40
- package/apps/web/src/material-settings.ts +0 -104
- package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
- package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
- package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
- package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
- package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
- package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
- package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
- package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
- package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
- package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
- package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
- package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
- package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
|
@@ -2,7 +2,7 @@ import { realpath, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type { WorkspaceProcessContext } from "../context.js";
|
|
4
4
|
import { runManagedProcess } from "../managed-process.js";
|
|
5
|
-
import type { LanguageServerDiagnostic, LanguageServerSnapshot } from "./contract.js";
|
|
5
|
+
import type { LanguageServerDiagnostic, LanguageServerInstanceSnapshot, LanguageServerSnapshot } from "./contract.js";
|
|
6
6
|
import { diagnosticEntries, formatDiagnostics } from "./diagnostics.js";
|
|
7
7
|
import { LanguageServerSession, withTimeout, type LanguageServerLaunch } from "./session.js";
|
|
8
8
|
import { allowedWorkspacePath, containsWorkspacePath, expandWorkspaceAlias, resolvedWorkspacePath } from "../paths.js";
|
|
@@ -13,6 +13,7 @@ export interface LanguageServerAdapter {
|
|
|
13
13
|
languages: Readonly<Record<string, string>>;
|
|
14
14
|
rootDescription: string;
|
|
15
15
|
resolveRoot: (workspaceRoot: string, root: string) => Promise<string>;
|
|
16
|
+
rootDirectory: (root: string) => string;
|
|
16
17
|
launch: (context: WorkspaceProcessContext, root: string) => Promise<LanguageServerLaunch>;
|
|
17
18
|
open: (session: LanguageServerSession, root: string, timeoutMs: number) => Promise<string>;
|
|
18
19
|
}
|
|
@@ -30,6 +31,7 @@ interface OpenServer {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
interface ServerEntry {
|
|
34
|
+
runId: string;
|
|
33
35
|
root: string;
|
|
34
36
|
state: "opening" | "ready" | "failed";
|
|
35
37
|
summary: string;
|
|
@@ -47,12 +49,23 @@ const GIT_TIMEOUT_MS = 30 * 1000;
|
|
|
47
49
|
|
|
48
50
|
const messageOf = (error: unknown): string => error instanceof Error ? error.message : String(error);
|
|
49
51
|
|
|
50
|
-
const
|
|
52
|
+
const keyOf = (runId: string, root: string): string => `${runId}\n${root}`;
|
|
53
|
+
|
|
54
|
+
const runOf = (key: string): string => key.slice(0, key.indexOf("\n"));
|
|
55
|
+
|
|
56
|
+
const instanceCount = (count: number): string => `${count} offene Instanz${count === 1 ? "" : "en"}`;
|
|
57
|
+
|
|
58
|
+
const git = async (
|
|
59
|
+
context: WorkspaceProcessContext,
|
|
60
|
+
directory: string,
|
|
61
|
+
tool: string,
|
|
62
|
+
args: readonly string[],
|
|
63
|
+
): Promise<string> => {
|
|
51
64
|
let output = "";
|
|
52
65
|
const result = await runManagedProcess({
|
|
53
66
|
command: "git",
|
|
54
67
|
args: [...args],
|
|
55
|
-
cwd:
|
|
68
|
+
cwd: directory,
|
|
56
69
|
env: context.env,
|
|
57
70
|
uid: context.uid,
|
|
58
71
|
gid: context.gid,
|
|
@@ -66,14 +79,15 @@ const git = async (context: WorkspaceProcessContext, tool: string, args: readonl
|
|
|
66
79
|
return output;
|
|
67
80
|
};
|
|
68
81
|
|
|
69
|
-
/** Git nennt seine Pfade relativ zur Repo-Wurzel; `-- .` grenzt sie auf
|
|
82
|
+
/** Git nennt seine Pfade relativ zur Repo-Wurzel; `-- .` grenzt sie auf die Wurzel der Instanz ein. */
|
|
70
83
|
export const changedWorkspaceFiles = async (
|
|
71
84
|
context: WorkspaceProcessContext,
|
|
85
|
+
directory: string,
|
|
72
86
|
tool: string,
|
|
73
87
|
handles: (filePath: string) => boolean,
|
|
74
88
|
): Promise<string[]> => {
|
|
75
|
-
const topLevel = (await git(context, tool, ["rev-parse", "--show-toplevel"])).trim();
|
|
76
|
-
const candidates = (await git(context, tool, ["status", "--porcelain", "--untracked-files=all", "--no-renames", "--", "."]))
|
|
89
|
+
const topLevel = (await git(context, directory, tool, ["rev-parse", "--show-toplevel"])).trim();
|
|
90
|
+
const candidates = (await git(context, directory, tool, ["status", "--porcelain", "--untracked-files=all", "--no-renames", "--", "."]))
|
|
77
91
|
.split("\n")
|
|
78
92
|
.filter((line) => line.length > 3 && line[1] !== "D" && line[0] !== "D")
|
|
79
93
|
.map((line) => path.resolve(topLevel, line.slice(3)))
|
|
@@ -92,7 +106,7 @@ export class LanguageServerHost {
|
|
|
92
106
|
readonly #openTimeoutMs: number;
|
|
93
107
|
readonly #diagnosticsTimeoutMs: number;
|
|
94
108
|
readonly #servers = new Map<string, ServerEntry>();
|
|
95
|
-
readonly #roots = new Map<string, string
|
|
109
|
+
readonly #roots = new Map<string, Set<string>>();
|
|
96
110
|
readonly #lifetimes = new Map<string, object>();
|
|
97
111
|
readonly #closing = new Map<string, Set<Promise<void>>>();
|
|
98
112
|
readonly #idleTimers = new Map<string, NodeJS.Timeout>();
|
|
@@ -117,71 +131,163 @@ export class LanguageServerHost {
|
|
|
117
131
|
|
|
118
132
|
async open(runId: string, root: string): Promise<string> {
|
|
119
133
|
const lifetime = this.#lifetime(runId);
|
|
120
|
-
const
|
|
134
|
+
const { context, absoluteRoot } = await this.#prepare(runId, lifetime, root);
|
|
135
|
+
const current = this.#current(runId, absoluteRoot);
|
|
136
|
+
if (current && current.state !== "failed") {
|
|
137
|
+
const server = await current.pending!.catch(async (error: unknown) => {
|
|
138
|
+
await this.#recordFailure(runId, lifetime, absoluteRoot, error);
|
|
139
|
+
throw error;
|
|
140
|
+
});
|
|
141
|
+
this.#assertCurrent(current);
|
|
142
|
+
this.#touch(runId, absoluteRoot);
|
|
143
|
+
return this.#openMessage(runId, `${this.adapter.label} ist für ${absoluteRoot} bereits geöffnet: ${server.summary}`);
|
|
144
|
+
}
|
|
145
|
+
const server = await this.#start(runId, context, absoluteRoot);
|
|
146
|
+
this.#assertLifetime(runId, lifetime);
|
|
147
|
+
if (this.#servers.get(keyOf(runId, absoluteRoot))?.server !== server) throw this.#stopped();
|
|
148
|
+
return this.#openMessage(runId, `${server.summary} (Wurzel ${absoluteRoot})`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async close(runId: string, root?: string): Promise<string> {
|
|
152
|
+
if (root === undefined) {
|
|
153
|
+
const roots = this.#openRoots(runId);
|
|
154
|
+
if (roots.length === 0) return `${this.adapter.label} ist in diesem Run nicht geöffnet`;
|
|
155
|
+
await Promise.all(roots.map((entry) => this.#closeInstance(runId, entry)));
|
|
156
|
+
const closed = roots.length === 1 ? "eine Instanz" : `${roots.length} Instanzen`;
|
|
157
|
+
return `${this.adapter.label}: ${closed} beendet; keine offene Instanz mehr in diesem Run`;
|
|
158
|
+
}
|
|
159
|
+
const context = await this.#contextFor(runId);
|
|
160
|
+
const chosen = await this.#known(runId, context, root);
|
|
161
|
+
await this.#closeInstance(runId, chosen);
|
|
162
|
+
return `${this.adapter.label} für ${chosen} beendet; ${instanceCount(this.#openRoots(runId).length)} in diesem Run`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async snapshot(runId: string): Promise<LanguageServerSnapshot> {
|
|
166
|
+
const instances: LanguageServerInstanceSnapshot[] = [];
|
|
167
|
+
for (const root of this.#openRoots(runId)) {
|
|
168
|
+
const instance = await this.#instanceSnapshot(runId, root);
|
|
169
|
+
if (instance) instances.push(instance);
|
|
170
|
+
}
|
|
171
|
+
return { instances };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async diagnostics(
|
|
175
|
+
runId: string,
|
|
176
|
+
paths: readonly string[] | undefined,
|
|
177
|
+
includeWarnings: boolean,
|
|
178
|
+
root?: string,
|
|
179
|
+
): Promise<string> {
|
|
180
|
+
const lifetime = this.#lifetime(runId);
|
|
181
|
+
const context = await this.#contextFor(runId);
|
|
182
|
+
this.#assertLifetime(runId, lifetime);
|
|
183
|
+
const roots = root === undefined ? this.#openRoots(runId) : [await this.#known(runId, context, root)];
|
|
184
|
+
if (roots.length === 0) throw new Error(`Kein ${this.adapter.label}-Server geöffnet; zuerst ${this.adapter.id}_open aufrufen`);
|
|
185
|
+
const results = paths === undefined || paths.length === 0
|
|
186
|
+
? await this.#changed(runId, context, roots, includeWarnings)
|
|
187
|
+
: await this.#requested(runId, context, roots, paths, includeWarnings);
|
|
188
|
+
if (results.length === 0) return `Keine geänderten ${this.adapter.label}-Dateien in den geöffneten Wurzeln`;
|
|
189
|
+
return results.join("\n\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async annotate(runId: string, absolutePath: string): Promise<string | undefined> {
|
|
193
|
+
if (!this.handles(absolutePath) || this.#openRoots(runId).length === 0) return undefined;
|
|
194
|
+
try {
|
|
195
|
+
const lifetime = this.#lifetime(runId);
|
|
196
|
+
const context = await this.#contextFor(runId);
|
|
197
|
+
this.#assertLifetime(runId, lifetime);
|
|
198
|
+
const file = await allowedWorkspacePath(absolutePath, [context.root, ...context.additionalRoots ?? []]);
|
|
199
|
+
const chosen = this.#match(this.#openRoots(runId), file);
|
|
200
|
+
if (!chosen) return undefined;
|
|
201
|
+
const server = await this.#ensure(runId, context, chosen);
|
|
202
|
+
return await this.#format(context, server, file, false);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return `Diagnostik (${this.adapter.label}) fehlgeschlagen: ${messageOf(error)}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async stopSession(runId: string): Promise<void> {
|
|
209
|
+
this.#lifetimes.delete(runId);
|
|
210
|
+
const keys = new Set([
|
|
211
|
+
...this.#openRoots(runId).map((root) => keyOf(runId, root)),
|
|
212
|
+
...[...this.#servers.keys()].filter((key) => runOf(key) === runId),
|
|
213
|
+
]);
|
|
214
|
+
this.#roots.delete(runId);
|
|
215
|
+
for (const key of keys) this.#seen.delete(key);
|
|
216
|
+
await Promise.all([...keys].map((key) => this.#close(key)));
|
|
217
|
+
await Promise.all(this.#closing.get(runId) ?? []);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async shutdown(): Promise<void> {
|
|
221
|
+
this.#closed = true;
|
|
222
|
+
const runs = new Set([
|
|
223
|
+
...this.#lifetimes.keys(),
|
|
224
|
+
...this.#roots.keys(),
|
|
225
|
+
...[...this.#servers.keys()].map(runOf),
|
|
226
|
+
...this.#closing.keys(),
|
|
227
|
+
]);
|
|
228
|
+
const results = await Promise.allSettled([...runs].map((runId) => this.stopSession(runId)));
|
|
229
|
+
const failures = results
|
|
230
|
+
.filter((result): result is PromiseRejectedResult => result.status === "rejected")
|
|
231
|
+
.map((result) => result.reason);
|
|
232
|
+
if (failures.length > 0) throw new AggregateError(failures, `${this.adapter.label}-Shutdown fehlgeschlagen`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async #prepare(
|
|
236
|
+
runId: string,
|
|
237
|
+
lifetime: object,
|
|
238
|
+
root: string,
|
|
239
|
+
): Promise<{ context: WorkspaceProcessContext; absoluteRoot: string }> {
|
|
121
240
|
let absoluteRoot = root;
|
|
122
241
|
try {
|
|
123
242
|
const context = await this.#contextFor(runId);
|
|
124
243
|
this.#assertLifetime(runId, lifetime);
|
|
125
244
|
const allowed = await Promise.all([context.root, ...context.additionalRoots ?? []].map(resolvedWorkspacePath));
|
|
126
245
|
const requested = await allowedWorkspacePath(path.resolve(context.root, expandWorkspaceAlias(root, context.workspaceAliases ?? {})), allowed);
|
|
246
|
+
absoluteRoot = requested;
|
|
127
247
|
const workspaceRoot = allowed.find((directory) => containsWorkspacePath(directory, requested))!;
|
|
128
248
|
absoluteRoot = await this.adapter.resolveRoot(workspaceRoot, requested);
|
|
129
249
|
this.#assertLifetime(runId, lifetime);
|
|
130
|
-
|
|
131
|
-
if (current && current.root === absoluteRoot && current.state !== "failed") {
|
|
132
|
-
const server = await current.pending!;
|
|
133
|
-
this.#assertCurrent(runId, current);
|
|
134
|
-
this.#touch(runId);
|
|
135
|
-
return `${this.adapter.label} ist bereits geöffnet: ${server.summary}`;
|
|
136
|
-
}
|
|
137
|
-
const server = await this.#start(runId, context, absoluteRoot);
|
|
138
|
-
this.#assertLifetime(runId, lifetime);
|
|
139
|
-
if (this.#servers.get(runId)?.server !== server) throw this.#stopped();
|
|
140
|
-
return server.summary;
|
|
250
|
+
return { context, absoluteRoot };
|
|
141
251
|
} catch (error) {
|
|
142
|
-
|
|
143
|
-
const failed: ServerEntry = { root: absoluteRoot, state: "failed", summary: messageOf(error), controller: new AbortController() };
|
|
144
|
-
this.#servers.set(runId, failed);
|
|
145
|
-
this.#roots.set(runId, absoluteRoot);
|
|
146
|
-
this.#seen.delete(runId);
|
|
147
|
-
if (previous) await this.#dispose(runId, previous);
|
|
148
|
-
}
|
|
252
|
+
await this.#recordFailure(runId, lifetime, absoluteRoot, error);
|
|
149
253
|
throw error;
|
|
150
254
|
}
|
|
151
255
|
}
|
|
152
256
|
|
|
153
|
-
async
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
:
|
|
257
|
+
async #recordFailure(runId: string, lifetime: object, root: string, error: unknown): Promise<void> {
|
|
258
|
+
if (this.#closed || this.#lifetimes.get(runId) !== lifetime) return;
|
|
259
|
+
const key = keyOf(runId, root);
|
|
260
|
+
const previous = this.#servers.get(key);
|
|
261
|
+
if (previous && previous.state !== "failed") return;
|
|
262
|
+
this.#servers.set(key, {
|
|
263
|
+
runId,
|
|
264
|
+
root,
|
|
265
|
+
state: "failed",
|
|
266
|
+
summary: messageOf(error),
|
|
267
|
+
controller: new AbortController(),
|
|
268
|
+
});
|
|
269
|
+
this.#remember(runId, root);
|
|
270
|
+
this.#seen.delete(key);
|
|
271
|
+
if (previous) await this.#dispose(previous);
|
|
162
272
|
}
|
|
163
273
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
274
|
+
#openMessage(runId: string, text: string): string {
|
|
275
|
+
return `${text}; ${instanceCount(this.#openRoots(runId).length)} von ${this.adapter.label} in diesem Run`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async #instanceSnapshot(runId: string, root: string): Promise<LanguageServerInstanceSnapshot | undefined> {
|
|
279
|
+
const entry = this.#current(runId, root);
|
|
280
|
+
if (entry?.state === "opening" || entry?.state === "failed") {
|
|
281
|
+
return { state: entry.state, root, summary: entry.summary, files: [] };
|
|
168
282
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!rootExists) {
|
|
173
|
-
await this.stopSession(runId);
|
|
174
|
-
return { state: "closed", root: null, summary: null, files: [] };
|
|
283
|
+
if (!await this.#exists(root)) {
|
|
284
|
+
await this.#closeInstance(runId, root);
|
|
285
|
+
return undefined;
|
|
175
286
|
}
|
|
176
|
-
const seen = this.#seen.get(runId);
|
|
287
|
+
const seen = this.#seen.get(keyOf(runId, root));
|
|
177
288
|
const files = seen === undefined ? [] : await this.#existingFiles(runId, seen);
|
|
178
|
-
if (this.#
|
|
179
|
-
return {
|
|
180
|
-
state: current ? "ready" : root ? "suspended" : "closed",
|
|
181
|
-
root: root ?? null,
|
|
182
|
-
summary: current?.summary ?? null,
|
|
183
|
-
files,
|
|
184
|
-
};
|
|
289
|
+
if (!this.#roots.get(runId)?.has(root)) return undefined;
|
|
290
|
+
return { state: entry ? "ready" : "suspended", root, summary: entry?.summary ?? null, files };
|
|
185
291
|
}
|
|
186
292
|
|
|
187
293
|
async #exists(absolutePath: string): Promise<boolean> {
|
|
@@ -191,7 +297,7 @@ export class LanguageServerHost {
|
|
|
191
297
|
async #existingFiles(
|
|
192
298
|
runId: string,
|
|
193
299
|
seen: ReadonlyMap<string, readonly LanguageServerDiagnostic[]>,
|
|
194
|
-
): Promise<
|
|
300
|
+
): Promise<LanguageServerInstanceSnapshot["files"]> {
|
|
195
301
|
const context = await this.#contextFor(runId);
|
|
196
302
|
const entries = await Promise.all([...seen.entries()].map(async ([file, diagnostics]) =>
|
|
197
303
|
await this.#exists(path.resolve(context.root, expandWorkspaceAlias(file, context.workspaceAliases ?? {}))) ? { path: file, diagnostics: [...diagnostics] } : null));
|
|
@@ -200,54 +306,61 @@ export class LanguageServerHost {
|
|
|
200
306
|
.sort((left, right) => left.path.localeCompare(right.path, "de"));
|
|
201
307
|
}
|
|
202
308
|
|
|
203
|
-
async
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
? await changedWorkspaceFiles(context, `${this.adapter.id}_diagnostics`, (file) => this.handles(file))
|
|
210
|
-
: await Promise.all(paths.map((requested) => allowedWorkspacePath(path.resolve(context.root, expandWorkspaceAlias(requested, context.workspaceAliases ?? {})), [context.root, ...context.additionalRoots ?? []])));
|
|
211
|
-
if (files.length === 0) return `Keine geänderten ${this.adapter.label}-Dateien im Arbeitsverzeichnis`;
|
|
309
|
+
async #changed(
|
|
310
|
+
runId: string,
|
|
311
|
+
context: WorkspaceProcessContext,
|
|
312
|
+
roots: readonly string[],
|
|
313
|
+
includeWarnings: boolean,
|
|
314
|
+
): Promise<string[]> {
|
|
212
315
|
const results: string[] = [];
|
|
213
|
-
for (const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
316
|
+
for (const root of roots) {
|
|
317
|
+
const server = await this.#ensure(runId, context, root);
|
|
318
|
+
const files = await changedWorkspaceFiles(
|
|
319
|
+
context,
|
|
320
|
+
this.adapter.rootDirectory(root),
|
|
321
|
+
`${this.adapter.id}_diagnostics`,
|
|
322
|
+
(file) => this.handles(file),
|
|
323
|
+
);
|
|
324
|
+
for (const file of files) results.push(await this.#format(context, server, file, includeWarnings));
|
|
218
325
|
}
|
|
219
|
-
return results
|
|
326
|
+
return results;
|
|
220
327
|
}
|
|
221
328
|
|
|
222
|
-
async
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
329
|
+
async #requested(
|
|
330
|
+
runId: string,
|
|
331
|
+
context: WorkspaceProcessContext,
|
|
332
|
+
roots: readonly string[],
|
|
333
|
+
paths: readonly string[],
|
|
334
|
+
includeWarnings: boolean,
|
|
335
|
+
): Promise<string[]> {
|
|
336
|
+
const results: string[] = [];
|
|
337
|
+
for (const requested of paths) {
|
|
338
|
+
const file = await allowedWorkspacePath(path.resolve(context.root, expandWorkspaceAlias(requested, context.workspaceAliases ?? {})), [context.root, ...context.additionalRoots ?? []]);
|
|
339
|
+
if (!this.handles(file)) {
|
|
340
|
+
throw new Error(`${path.relative(context.root, file)} hat keine ${this.adapter.label}-Endung (${Object.keys(this.adapter.languages).join(", ")})`);
|
|
341
|
+
}
|
|
342
|
+
const chosen = this.#match(roots, file);
|
|
343
|
+
if (!chosen) {
|
|
344
|
+
throw new Error(`${path.relative(context.root, file)} liegt in keiner geöffneten ${this.adapter.label}-Wurzel; offen: ${roots.join(", ")}`);
|
|
345
|
+
}
|
|
346
|
+
results.push(await this.#format(context, await this.#ensure(runId, context, chosen), file, includeWarnings));
|
|
232
347
|
}
|
|
348
|
+
return results;
|
|
233
349
|
}
|
|
234
350
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
await this.#close(runId);
|
|
240
|
-
await Promise.all(this.#closing.get(runId) ?? []);
|
|
351
|
+
#match(roots: readonly string[], file: string): string | undefined {
|
|
352
|
+
return [...roots]
|
|
353
|
+
.filter((root) => containsWorkspacePath(this.adapter.rootDirectory(root), file))
|
|
354
|
+
.sort((left, right) => this.adapter.rootDirectory(right).length - this.adapter.rootDirectory(left).length)[0];
|
|
241
355
|
}
|
|
242
356
|
|
|
243
|
-
async
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (failures.length > 0) throw new AggregateError(failures, `${this.adapter.label}-Shutdown fehlgeschlagen`);
|
|
357
|
+
async #known(runId: string, context: WorkspaceProcessContext, root: string): Promise<string> {
|
|
358
|
+
const roots = this.#openRoots(runId);
|
|
359
|
+
if (roots.length === 0) throw new Error(`Kein ${this.adapter.label}-Server geöffnet; zuerst ${this.adapter.id}_open aufrufen`);
|
|
360
|
+
const requested = await resolvedWorkspacePath(path.resolve(context.root, expandWorkspaceAlias(root, context.workspaceAliases ?? {})));
|
|
361
|
+
const found = roots.find((open) => open === requested);
|
|
362
|
+
if (!found) throw new Error(`Für ${root} ist kein ${this.adapter.label}-Server geöffnet; offen: ${roots.join(", ")}`);
|
|
363
|
+
return found;
|
|
251
364
|
}
|
|
252
365
|
|
|
253
366
|
async #format(
|
|
@@ -256,61 +369,81 @@ export class LanguageServerHost {
|
|
|
256
369
|
absolutePath: string,
|
|
257
370
|
includeWarnings: boolean,
|
|
258
371
|
): Promise<string> {
|
|
259
|
-
this.#touch(context.runId);
|
|
372
|
+
this.#touch(context.runId, server.root);
|
|
373
|
+
const key = keyOf(context.runId, server.root);
|
|
260
374
|
const checkedPath = await allowedWorkspacePath(absolutePath, [context.root, ...context.additionalRoots ?? []]);
|
|
261
375
|
const diagnostics = await server.session.diagnosticsFor(await realpath(checkedPath), this.#diagnosticsTimeoutMs);
|
|
262
|
-
if (this.#servers.get(
|
|
376
|
+
if (this.#servers.get(key)?.server !== server) throw this.#stopped();
|
|
263
377
|
const alias = Object.entries(context.workspaceAliases ?? {}).find(([, root]) => containsWorkspacePath(root, checkedPath));
|
|
264
378
|
const relativePath = alias ? `${alias[0]}/${path.relative(alias[1], checkedPath)}`
|
|
265
379
|
: containsWorkspacePath(context.root, checkedPath) ? path.relative(context.root, checkedPath) : checkedPath;
|
|
266
|
-
const seen = this.#seen.get(
|
|
380
|
+
const seen = this.#seen.get(key) ?? new Map<string, readonly LanguageServerDiagnostic[]>();
|
|
267
381
|
seen.set(relativePath, diagnosticEntries(diagnostics));
|
|
268
|
-
this.#seen.set(
|
|
382
|
+
this.#seen.set(key, seen);
|
|
269
383
|
return formatDiagnostics(this.adapter.label, relativePath, diagnostics, includeWarnings);
|
|
270
384
|
}
|
|
271
385
|
|
|
272
|
-
#
|
|
273
|
-
|
|
386
|
+
#openRoots(runId: string): string[] {
|
|
387
|
+
return [...this.#roots.get(runId) ?? []];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
#remember(runId: string, root: string): void {
|
|
391
|
+
const roots = this.#roots.get(runId) ?? new Set<string>();
|
|
392
|
+
roots.add(root);
|
|
393
|
+
this.#roots.set(runId, roots);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async #closeInstance(runId: string, root: string): Promise<void> {
|
|
397
|
+
const roots = this.#roots.get(runId);
|
|
398
|
+
roots?.delete(root);
|
|
399
|
+
if (roots?.size === 0) this.#roots.delete(runId);
|
|
400
|
+
const key = keyOf(runId, root);
|
|
401
|
+
this.#seen.delete(key);
|
|
402
|
+
await this.#close(key);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#current(runId: string, root: string): ServerEntry | undefined {
|
|
406
|
+
const key = keyOf(runId, root);
|
|
407
|
+
const current = this.#servers.get(key);
|
|
274
408
|
if (current?.server?.session.exited) {
|
|
275
|
-
this.#servers.delete(
|
|
276
|
-
void this.#dispose(
|
|
409
|
+
this.#servers.delete(key);
|
|
410
|
+
void this.#dispose(current).catch((error) => console.error(`${this.adapter.label}: Bereinigung fehlgeschlagen: ${messageOf(error)}`));
|
|
277
411
|
return undefined;
|
|
278
412
|
}
|
|
279
413
|
return current;
|
|
280
414
|
}
|
|
281
415
|
|
|
282
|
-
async #ensure(runId: string, context: WorkspaceProcessContext): Promise<OpenServer> {
|
|
416
|
+
async #ensure(runId: string, context: WorkspaceProcessContext, root: string): Promise<OpenServer> {
|
|
283
417
|
if (this.#closed) throw this.#stopped();
|
|
284
|
-
const current = this.#current(runId);
|
|
418
|
+
const current = this.#current(runId, root);
|
|
285
419
|
if (current?.state === "failed") throw new Error(current.summary);
|
|
286
420
|
if (current) {
|
|
287
421
|
const server = await current.pending!;
|
|
288
|
-
this.#assertCurrent(
|
|
422
|
+
this.#assertCurrent(current);
|
|
289
423
|
return server;
|
|
290
424
|
}
|
|
291
|
-
const root = this.#roots.get(runId);
|
|
292
|
-
if (!root) throw new Error(`Kein ${this.adapter.label}-Server geöffnet; zuerst ${this.adapter.id}_open aufrufen`);
|
|
293
425
|
return this.#start(runId, context, root);
|
|
294
426
|
}
|
|
295
427
|
|
|
296
428
|
#start(runId: string, context: WorkspaceProcessContext, root: string): Promise<OpenServer> {
|
|
297
|
-
const
|
|
298
|
-
const
|
|
429
|
+
const key = keyOf(runId, root);
|
|
430
|
+
const previous = this.#servers.get(key);
|
|
431
|
+
const closing = previous ? this.#dispose(previous) : undefined;
|
|
299
432
|
const entry: ServerEntry = {
|
|
300
|
-
root, state: "opening", summary: `${this.adapter.label} lädt ${root}`, controller: new AbortController(),
|
|
433
|
+
runId, root, state: "opening", summary: `${this.adapter.label} lädt ${root}`, controller: new AbortController(),
|
|
301
434
|
};
|
|
302
|
-
this.#servers.set(
|
|
303
|
-
this.#
|
|
304
|
-
this.#seen.delete(
|
|
435
|
+
this.#servers.set(key, entry);
|
|
436
|
+
this.#remember(runId, root);
|
|
437
|
+
this.#seen.delete(key);
|
|
305
438
|
entry.pending = (async (): Promise<OpenServer> => {
|
|
306
439
|
try {
|
|
307
440
|
await closing;
|
|
308
|
-
this.#assertCurrent(
|
|
441
|
+
this.#assertCurrent(entry);
|
|
309
442
|
const launch = await this.adapter.launch(context, root);
|
|
310
|
-
this.#assertCurrent(
|
|
443
|
+
this.#assertCurrent(entry);
|
|
311
444
|
entry.initializing = LanguageServerSession.start(launch, this.#openTimeoutMs);
|
|
312
445
|
const session = await entry.initializing;
|
|
313
|
-
this.#assertCurrent(
|
|
446
|
+
this.#assertCurrent(entry);
|
|
314
447
|
let aborted!: () => void;
|
|
315
448
|
const cancelled = new Promise<never>((_resolve, reject) => {
|
|
316
449
|
aborted = () => reject(entry.controller.signal.reason);
|
|
@@ -326,49 +459,49 @@ export class LanguageServerHost {
|
|
|
326
459
|
} finally {
|
|
327
460
|
entry.controller.signal.removeEventListener("abort", aborted);
|
|
328
461
|
}
|
|
329
|
-
this.#assertCurrent(
|
|
462
|
+
this.#assertCurrent(entry);
|
|
330
463
|
if (session.exited) throw new Error(`${this.adapter.label} wurde während des Ladens beendet`);
|
|
331
464
|
const server = { root, session, summary };
|
|
332
465
|
entry.server = server;
|
|
333
466
|
entry.state = "ready";
|
|
334
467
|
entry.summary = summary;
|
|
335
|
-
this.#touch(runId);
|
|
468
|
+
this.#touch(runId, root);
|
|
336
469
|
return server;
|
|
337
470
|
} catch (error) {
|
|
338
|
-
if (this.#servers.get(
|
|
471
|
+
if (this.#servers.get(key) === entry && !entry.controller.signal.aborted) {
|
|
339
472
|
entry.state = "failed";
|
|
340
473
|
entry.summary = messageOf(error);
|
|
341
474
|
}
|
|
342
|
-
await this.#dispose(
|
|
475
|
+
await this.#dispose(entry);
|
|
343
476
|
throw error;
|
|
344
477
|
}
|
|
345
478
|
})();
|
|
346
479
|
return entry.pending;
|
|
347
480
|
}
|
|
348
481
|
|
|
349
|
-
async #close(
|
|
350
|
-
const timer = this.#idleTimers.get(
|
|
482
|
+
async #close(key: string): Promise<void> {
|
|
483
|
+
const timer = this.#idleTimers.get(key);
|
|
351
484
|
if (timer) clearTimeout(timer);
|
|
352
|
-
this.#idleTimers.delete(
|
|
353
|
-
const entry = this.#servers.get(
|
|
485
|
+
this.#idleTimers.delete(key);
|
|
486
|
+
const entry = this.#servers.get(key);
|
|
354
487
|
if (!entry) return;
|
|
355
|
-
this.#servers.delete(
|
|
356
|
-
await this.#dispose(
|
|
488
|
+
this.#servers.delete(key);
|
|
489
|
+
await this.#dispose(entry);
|
|
357
490
|
}
|
|
358
491
|
|
|
359
|
-
#dispose(
|
|
492
|
+
#dispose(entry: ServerEntry): Promise<void> {
|
|
360
493
|
entry.controller.abort(this.#stopped());
|
|
361
494
|
if (entry.closing) return entry.closing;
|
|
362
495
|
entry.closing = (async () => {
|
|
363
496
|
const session = await entry.initializing?.catch(() => undefined);
|
|
364
497
|
await session?.shutdown();
|
|
365
498
|
})();
|
|
366
|
-
const pending = this.#closing.get(runId) ?? new Set<Promise<void>>();
|
|
367
|
-
this.#closing.set(runId, pending);
|
|
499
|
+
const pending = this.#closing.get(entry.runId) ?? new Set<Promise<void>>();
|
|
500
|
+
this.#closing.set(entry.runId, pending);
|
|
368
501
|
pending.add(entry.closing);
|
|
369
502
|
const finished = () => {
|
|
370
503
|
pending.delete(entry.closing!);
|
|
371
|
-
if (pending.size === 0 && this.#closing.get(runId) === pending) this.#closing.delete(runId);
|
|
504
|
+
if (pending.size === 0 && this.#closing.get(entry.runId) === pending) this.#closing.delete(entry.runId);
|
|
372
505
|
};
|
|
373
506
|
entry.closing.then(finished, finished);
|
|
374
507
|
return entry.closing;
|
|
@@ -385,25 +518,29 @@ export class LanguageServerHost {
|
|
|
385
518
|
if (this.#closed || this.#lifetimes.get(runId) !== lifetime) throw this.#stopped();
|
|
386
519
|
}
|
|
387
520
|
|
|
388
|
-
#assertCurrent(
|
|
389
|
-
if (this.#closed
|
|
521
|
+
#assertCurrent(entry: ServerEntry): void {
|
|
522
|
+
if (this.#closed
|
|
523
|
+
|| entry.controller.signal.aborted
|
|
524
|
+
|| entry.server?.session.exited
|
|
525
|
+
|| this.#servers.get(keyOf(entry.runId, entry.root)) !== entry) throw this.#stopped();
|
|
390
526
|
}
|
|
391
527
|
|
|
392
528
|
#stopped(): Error {
|
|
393
529
|
return new Error(`${this.adapter.label}-Start wurde beendet`);
|
|
394
530
|
}
|
|
395
531
|
|
|
396
|
-
#touch(runId: string): void {
|
|
397
|
-
const
|
|
532
|
+
#touch(runId: string, root: string): void {
|
|
533
|
+
const key = keyOf(runId, root);
|
|
534
|
+
const entry = this.#servers.get(key);
|
|
398
535
|
if (!entry || entry.state !== "ready") return;
|
|
399
|
-
const existing = this.#idleTimers.get(
|
|
536
|
+
const existing = this.#idleTimers.get(key);
|
|
400
537
|
if (existing) clearTimeout(existing);
|
|
401
538
|
const timer = setTimeout(() => {
|
|
402
|
-
this.#idleTimers.delete(
|
|
403
|
-
if (this.#servers.get(
|
|
404
|
-
void this.#close(
|
|
539
|
+
this.#idleTimers.delete(key);
|
|
540
|
+
if (this.#servers.get(key) !== entry) return;
|
|
541
|
+
void this.#close(key).catch((error) => console.error(`${this.adapter.label}: Beenden nach Leerlauf fehlgeschlagen: ${messageOf(error)}`));
|
|
405
542
|
}, this.#idleMs);
|
|
406
543
|
timer.unref();
|
|
407
|
-
this.#idleTimers.set(
|
|
544
|
+
this.#idleTimers.set(key, timer);
|
|
408
545
|
}
|
|
409
546
|
}
|
|
@@ -7,7 +7,7 @@ export const ACTOR_INVOCATIONS_STATE_ID = "ragents.actor-programs.invocations";
|
|
|
7
7
|
export const ACTOR_SCRIPT_STATE_ID = "ragents.actor-programs.script";
|
|
8
8
|
export const ACTOR_STATE_ID = "ragents.actor-state";
|
|
9
9
|
|
|
10
|
-
export interface ActorViewPlacement { kind: "canvas"; anchorActorId: string;
|
|
10
|
+
export interface ActorViewPlacement { kind: "canvas"; anchorActorId: string; }
|
|
11
11
|
export interface ActorViewDefinition {
|
|
12
12
|
id: string;
|
|
13
13
|
key: string;
|
|
@@ -29,7 +29,7 @@ one active program package; add further functions and views to that package.
|
|
|
29
29
|
|
|
30
30
|
`package.json` contains `name`, `private: true`, `type: "module"` and `ragents` metadata:
|
|
31
31
|
`title`, optional `description`, optional `backend`, and optional `views`. Each view has an
|
|
32
|
-
`id`, `client
|
|
32
|
+
`id`, `client` and optional `title` and `styles`. At least a backend or a view
|
|
33
33
|
is required. The host supplies the HTML root and fixed local dependencies.
|
|
34
34
|
|
|
35
35
|
Import `defineActor` from `@ragents/server` and `Type` from `typebox`. Its contract contains
|
|
@@ -68,9 +68,9 @@ Use `FlowDiagram` for other graphs (star layout, fit, actions). Keep local IDs s
|
|
|
68
68
|
See `controls`. For custom SVG use `SvgEdge`, Tailwind utilities, responsive `viewBox` and
|
|
69
69
|
accessible descriptions.
|
|
70
70
|
Address view visibility with `package-name/view-key`, `@handle/view-key` or a unique title;
|
|
71
|
-
|
|
72
|
-
The host
|
|
73
|
-
|
|
71
|
+
a tile uses the same names with the `app:` prefix.
|
|
72
|
+
The host sizes the tile and offers the full view. Views appear as tiles; owner cards are not
|
|
73
|
+
placed by themselves but stay in the Actors list. Personal actor visibility is separate from
|
|
74
74
|
`actor_view_set_visibility` and cannot be changed by layouts.
|
|
75
75
|
|
|
76
76
|
Tests call `program.functions.<name>(input, context)` or `program.onInput(input, context)`.
|
|
@@ -78,8 +78,6 @@ export const appPackageSchema = Type.Object({
|
|
|
78
78
|
views: Type.Optional(Type.Array(Type.Object({
|
|
79
79
|
id: Type.String({ pattern: "^[a-z][a-z0-9-]{0,63}$" }), title: Type.Optional(Type.String({ minLength: 1 })),
|
|
80
80
|
client: Type.String({ minLength: 1 }), styles: Type.Optional(Type.String()),
|
|
81
|
-
width: Type.Optional(Type.Integer({ minimum: 280, maximum: 960 })),
|
|
82
|
-
height: Type.Optional(Type.Integer({ minimum: 180, maximum: 720 })),
|
|
83
81
|
}, { additionalProperties: false }))),
|
|
84
82
|
}, { additionalProperties: false });
|
|
85
83
|
export type AppPackage = Static<typeof appPackageSchema>;
|