@schlenkr/ragents 0.1.8 → 0.1.9
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/apps/server/src/config-definition.ts +24 -2
- package/apps/server/src/config.ts +0 -6
- package/apps/server/src/host-api.json +3 -10
- package/apps/server/src/host-api.ts +6 -7
- package/apps/server/src/plugin-build/build.ts +28 -5
- package/apps/server/src/plugin-build/plugin-description.ts +1 -1
- package/apps/server/src/plugin-build/typecheck.ts +2 -2
- package/apps/server/src/plugin-support/language-server/plugin.ts +8 -7
- package/apps/server/src/plugin-support/language-server/tools.ts +7 -6
- package/apps/server/src/plugin-support/model-aliases.ts +55 -25
- package/apps/server/src/plugin-support/product-relay.ts +10 -2
- package/apps/server/src/plugin-support/workspace-sandbox-host.ts +18 -9
- package/apps/server/src/profile/bundle-manifest.ts +7 -3
- package/apps/server/src/profile/compose.ts +6 -0
- package/apps/server/src/profile/plugin-discovery.ts +6 -1
- package/apps/server/src/profile/provisioning.ts +6 -5
- package/apps/server/src/ragents/engine.ts +0 -1
- package/apps/server/src/ragents/host-services.ts +4 -0
- package/apps/server/src/ragents/session.ts +1 -1
- package/apps/server/src/server.ts +1 -0
- package/apps/server/tsconfig.json +2 -0
- package/apps/web/dist/help/guide-clients.html +14 -6
- package/apps/web/dist/help/guide-clients.md +14 -6
- package/apps/web/dist/help/guide-distributed.html +12 -4
- package/apps/web/dist/help/guide-distributed.md +12 -4
- package/apps/web/dist/help/guide-getting-started.html +12 -8
- package/apps/web/dist/help/guide-getting-started.md +12 -8
- package/apps/web/dist/help/guide-plugins.html +6 -2
- package/apps/web/dist/help/guide-plugins.md +6 -2
- package/apps/web/dist/help/guide-runtime.html +2 -1
- package/apps/web/dist/help/guide-runtime.md +2 -1
- package/apps/web/dist/help/index.html +3328 -1135
- package/apps/web/dist/host-web.json +3 -3
- package/bundles/ragents.activity/ragents-bundle.json +3 -3
- package/bundles/ragents.actor-programs/ragents-bundle.json +3 -3
- package/bundles/ragents.ask/ragents-bundle.json +4 -4
- package/bundles/ragents.ask/server/index.js +42 -11
- package/bundles/ragents.ask/server/index.js.map +3 -3
- package/bundles/ragents.ask/web/chunks/chunk-WFN4374U.js +2 -0
- package/bundles/ragents.ask/web/chunks/chunk-WFN4374U.js.map +7 -0
- package/bundles/ragents.ask/web/classes.json +1 -1
- package/bundles/ragents.ask/web/exports/ask-payload.js +1 -1
- package/bundles/ragents.ask/web/index.js +1 -1
- package/bundles/ragents.ask/web/index.js.map +3 -3
- package/bundles/ragents.browser/executor/index.mjs +357 -0
- package/bundles/ragents.browser/executor/index.mjs.map +7 -0
- package/bundles/ragents.browser/ragents-bundle.json +5 -8
- package/bundles/ragents.browser/server/chunks/{chunk-2GKLLCBE.js → chunk-CWLX4VA5.js} +1 -1
- package/bundles/ragents.browser/server/chunks/chunk-CWLX4VA5.js.map +7 -0
- package/bundles/ragents.browser/server/exports/server/contract.js +1 -1
- package/bundles/ragents.browser/server/index.js +20 -9
- package/bundles/ragents.browser/server/index.js.map +4 -4
- package/bundles/ragents.documents/ragents-bundle.json +3 -3
- package/bundles/ragents.lsp-fsharp/executor/index.mjs +68 -0
- package/bundles/ragents.lsp-fsharp/executor/index.mjs.map +7 -0
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +5 -9
- package/bundles/ragents.lsp-fsharp/server/index.js +21 -6
- package/bundles/ragents.lsp-fsharp/server/index.js.map +4 -4
- package/bundles/ragents.lsp-roslyn/executor/index.mjs +68 -0
- package/bundles/ragents.lsp-roslyn/executor/index.mjs.map +7 -0
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +6 -8
- package/bundles/ragents.lsp-roslyn/server/index.js +54 -15
- package/bundles/ragents.lsp-roslyn/server/index.js.map +4 -4
- package/bundles/ragents.lsp-typescript/executor/index.mjs +49 -0
- package/bundles/ragents.lsp-typescript/executor/index.mjs.map +7 -0
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +5 -7
- package/bundles/ragents.lsp-typescript/server/index.js +22 -2
- package/bundles/ragents.lsp-typescript/server/index.js.map +3 -3
- package/bundles/ragents.model-relay/ragents-bundle.json +4 -4
- package/bundles/ragents.model-relay/server/index.js +3 -2
- package/bundles/ragents.model-relay/server/index.js.map +2 -2
- package/bundles/ragents.orchestration/ragents-bundle.json +3 -3
- package/bundles/ragents.overseer/ragents-bundle.json +3 -3
- package/bundles/ragents.processes/ragents-bundle.json +3 -3
- package/bundles/ragents.product/ragents-bundle.json +3 -3
- package/bundles/ragents.profile-distribution/ragents-bundle.json +4 -4
- package/bundles/ragents.profile-distribution/server/index.js +1 -1
- package/bundles/ragents.profile-distribution/server/index.js.map +2 -2
- package/bundles/ragents.reference/ragents-bundle.json +3 -3
- package/bundles/ragents.todo/ragents-bundle.json +3 -3
- package/bundles/ragents.transcript/ragents-bundle.json +3 -3
- package/bundles/ragents.watch/ragents-bundle.json +3 -3
- package/bundles/ragents.workspace/ragents-bundle.json +11 -5
- package/bundles/ragents.workspace/server/index.js +101 -28
- package/bundles/ragents.workspace/server/index.js.map +3 -3
- package/bundles/ragents.workspace/web/classes.json +1 -1
- package/bundles/ragents.workspace/web/index.js +1 -1
- package/bundles/ragents.workspace/web/index.js.map +3 -3
- package/package.json +2 -2
- package/packages/agent/dist/core/agent-settings.d.ts +1 -7
- package/packages/agent/dist/core/compaction/compaction.d.ts +17 -13
- package/packages/agent/dist/core/model-runtime.d.ts +5 -4
- package/packages/agent/dist/core/provider-composer.d.ts +2 -1
- package/packages/agent/dist/core/tools/bash.d.ts +9 -3
- package/packages/agent/dist/index.d.ts +1 -1
- package/packages/agent/src/core/agent-settings.ts +1 -4
- package/packages/agent/src/core/compaction/compaction.ts +68 -30
- package/packages/agent/src/core/model-runtime.ts +12 -5
- package/packages/agent/src/core/provider-composer.ts +5 -0
- package/packages/agent/src/core/tools/bash.ts +36 -30
- package/packages/agent/src/index.ts +3 -0
- package/packages/ai/dist/types.d.ts +11 -0
- package/packages/ai/src/types.ts +12 -0
- package/packages/ragents/src/domain/event-semantics.ts +3 -0
- package/packages/ragents/src/domain/event-validation.ts +15 -2
- package/packages/ragents/src/domain/events.ts +3 -0
- package/packages/ragents/src/domain/model.ts +2 -0
- package/packages/ragents/src/domain/projection.ts +1 -0
- package/packages/ragents/src/drivers/agent-runtime.ts +1 -1
- package/packages/ragents/src/drivers/agent-turn.ts +8 -6
- package/packages/ragents/src/runtime/decisions/inputs.ts +6 -2
- package/packages/ragents/src/runtime/journal-storage.ts +5 -4
- package/packages/workspace-executor/package.json +0 -1
- package/packages/workspace-executor/src/bash-launch.ts +56 -10
- package/packages/workspace-executor/src/context.ts +4 -0
- package/packages/workspace-executor/src/contributions.ts +150 -0
- package/packages/workspace-executor/src/executor.ts +1 -1
- package/packages/workspace-executor/src/index.ts +16 -19
- package/packages/workspace-executor/src/language-server/host.ts +3 -0
- package/packages/workspace-executor/src/language-server/module.ts +12 -2
- package/packages/workspace-executor/src/language-server/roots.ts +0 -5
- package/packages/workspace-executor/src/modules.ts +7 -6
- package/packages/workspace-executor/src/sandbox-tools.ts +3 -9
- package/packages/workspace-executor/src/shell-platform.ts +14 -3
- package/packages/workspace-executor/src/tools.ts +0 -3
- package/plugins/ragents.ask/ask-payload.ts +9 -0
- package/plugins/ragents.ask/server/ask-service.ts +53 -13
- package/plugins/ragents.ask/web/AskActionView.tsx +3 -2
- package/plugins/ragents.browser/executor/index.ts +5 -0
- package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/module.ts +25 -31
- package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/playwright.ts +9 -23
- package/plugins/ragents.browser/provision.ts +1 -1
- package/plugins/ragents.browser/server/browser.ts +3 -3
- package/plugins/ragents.browser/server/contract.ts +2 -2
- package/plugins/ragents.browser/server/index.ts +1 -1
- package/plugins/ragents.lsp-fsharp/executor.ts +80 -0
- package/plugins/ragents.lsp-fsharp/provision.ts +1 -1
- package/plugins/ragents.lsp-fsharp/server/index.ts +2 -2
- package/plugins/ragents.lsp-roslyn/executor.ts +64 -0
- package/plugins/ragents.lsp-roslyn/provision.ts +1 -1
- package/plugins/ragents.lsp-roslyn/server/index.ts +28 -5
- package/plugins/ragents.lsp-roslyn/server/solution-on-start.ts +18 -6
- package/plugins/ragents.lsp-typescript/executor.ts +50 -0
- package/plugins/ragents.lsp-typescript/server/index.ts +2 -2
- package/plugins/ragents.model-relay/server/config.ts +1 -1
- package/plugins/ragents.model-relay/server/relay.ts +4 -2
- package/plugins/ragents.profile-distribution/server/archive.ts +2 -1
- package/plugins/ragents.workspace/client/workspace-client.ts +111 -28
- package/plugins/ragents.workspace/contract.ts +33 -2
- package/plugins/ragents.workspace/server/binding.ts +10 -4
- package/plugins/ragents.workspace/server/clients.ts +56 -9
- package/plugins/ragents.workspace/server/config.ts +9 -0
- package/plugins/ragents.workspace/server/index.ts +13 -4
- package/plugins/ragents.workspace/server/runtime.ts +10 -0
- package/plugins/ragents.workspace/server/shell-platform.ts +6 -5
- package/plugins/ragents.workspace/web/WorkspaceBinding.tsx +1 -1
- package/ragents.config.core.ts +4 -1
- package/ragents.config.showcase.ts +4 -1
- package/scripts/agent/journal.ts +3 -1
- package/scripts/package/build-package.ts +3 -1
- package/scripts/provision/run-provision.ts +2 -2
- package/scripts/workspace-client/run-workspace-client.ts +7 -4
- package/apps/web/dist/help/screenshots/release-checklist-vscode.png +0 -0
- package/apps/web/dist/help/screenshots/release-checklist-web.png +0 -0
- package/bundles/ragents.ask/web/chunks/chunk-HGFDGTNC.js +0 -2
- package/bundles/ragents.ask/web/chunks/chunk-HGFDGTNC.js.map +0 -7
- package/bundles/ragents.browser/server/chunks/chunk-2GKLLCBE.js.map +0 -7
- package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +0 -60
- package/packages/workspace-executor/src/language-server/adapters/index.ts +0 -7
- package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +0 -56
- package/packages/workspace-executor/src/language-server/adapters/typescript.ts +0 -74
- package/packages/workspace-executor/src/language-server/solution.ts +0 -13
- /package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/contract.ts +0 -0
- /package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/pages.ts +0 -0
|
@@ -22,8 +22,3 @@ export const resolveRootDirectory = async (workspaceRoot: string, requested: str
|
|
|
22
22
|
if (!info?.isDirectory()) throw new Error(`${requested} ist kein Verzeichnis im Arbeitsverzeichnis`);
|
|
23
23
|
return absolute;
|
|
24
24
|
};
|
|
25
|
-
|
|
26
|
-
export const dotnetCommand = (server: string, args: readonly string[]): { command: string; args: string[] } =>
|
|
27
|
-
server.toLowerCase().endsWith(".dll")
|
|
28
|
-
? { command: "dotnet", args: [server, ...args] }
|
|
29
|
-
: { command: server, args: [...args] };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { browserModule } from "./browser/module.js";
|
|
2
1
|
import { commandModule } from "./commands.js";
|
|
2
|
+
import type { WorkspaceExecutorParts } from "./contributions.js";
|
|
3
3
|
import { fileModule } from "./files.js";
|
|
4
|
-
import { workspaceLanguageServers } from "./language-server/adapters/index.js";
|
|
5
4
|
import { languageServerModule } from "./language-server/module.js";
|
|
6
5
|
import type { WorkspaceModuleFactory } from "./module.js";
|
|
7
6
|
import { processModule } from "./processes/module.js";
|
|
@@ -11,15 +10,17 @@ import { sandboxToolsModule } from "./sandbox-tools.js";
|
|
|
11
10
|
export interface WorkspaceExecutorModuleOptions {
|
|
12
11
|
/** Der neue Ordner eines Runs auf dieser Maschine; nur ein Arbeitsplatz kennt ihn, auf dem Server legt ihn der Host an. */
|
|
13
12
|
runFolder?: (runId: string) => string;
|
|
13
|
+
/** Die Beiträge der Plugins, gebaut für diese Maschine; Server und Arbeitsplatz bekommen dieselben. */
|
|
14
|
+
contributions: readonly WorkspaceExecutorParts[];
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
/** Die Module
|
|
17
|
-
export const workspaceExecutorModules = (options: WorkspaceExecutorModuleOptions
|
|
17
|
+
/** Die Module eines Executors: die eigenen und was die Beiträge der Plugins hinzufügen. */
|
|
18
|
+
export const workspaceExecutorModules = (options: WorkspaceExecutorModuleOptions): readonly WorkspaceModuleFactory[] => [
|
|
18
19
|
sandboxToolsModule,
|
|
19
|
-
languageServerModule(
|
|
20
|
+
languageServerModule(options.contributions.flatMap((parts) => parts.languageServers ?? [])),
|
|
20
21
|
fileModule,
|
|
21
22
|
processModule(),
|
|
22
23
|
commandModule(),
|
|
23
|
-
|
|
24
|
+
...options.contributions.flatMap((parts) => parts.modules ?? []),
|
|
24
25
|
runFolderModule(options.runFolder),
|
|
25
26
|
];
|
|
@@ -40,11 +40,6 @@ export interface SandboxTools {
|
|
|
40
40
|
shutdown: () => Promise<void>;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export const DEFAULT_BASH_TIMEOUT_SECONDS = (() => {
|
|
44
|
-
const value = Number(process.env.RAGENTS_BASH_TIMEOUT_SECONDS);
|
|
45
|
-
return Number.isFinite(value) && value > 0 ? value : 600;
|
|
46
|
-
})();
|
|
47
|
-
|
|
48
43
|
type ToolResult = { content?: Array<{ type: string; text?: string }>; details?: unknown };
|
|
49
44
|
|
|
50
45
|
/** Der Stand einer Datei, den das Modell zuletzt gesehen hat; der Host führt ihn je Actor und gibt ihn mit, das Modell nennt ihn nie. */
|
|
@@ -269,7 +264,7 @@ export const createSandboxTools = async (
|
|
|
269
264
|
const context = await contextFor();
|
|
270
265
|
if (shuttingDown) throw new Error("Die Werkzeuge werden killed");
|
|
271
266
|
options.signal?.throwIfAborted();
|
|
272
|
-
const bash = bashLaunch(context.bash, command, context.env);
|
|
267
|
+
const bash = bashLaunch({ bash: context.bash, rg: context.rg }, command, context.env);
|
|
273
268
|
const launch = await sandboxedLaunch(context, bash);
|
|
274
269
|
if (shuttingDown) throw new Error("Die Werkzeuge werden killed");
|
|
275
270
|
options.signal?.throwIfAborted();
|
|
@@ -303,11 +298,10 @@ export const createSandboxTools = async (
|
|
|
303
298
|
}
|
|
304
299
|
});
|
|
305
300
|
};
|
|
306
|
-
const timeoutSeconds = options.timeout ?? DEFAULT_BASH_TIMEOUT_SECONDS;
|
|
307
301
|
const timer = setTimeout(() => {
|
|
308
302
|
timedOut = true;
|
|
309
303
|
kill();
|
|
310
|
-
},
|
|
304
|
+
}, options.timeout * 1000);
|
|
311
305
|
options.signal?.addEventListener("abort", kill);
|
|
312
306
|
const cleanup = () => {
|
|
313
307
|
if (timer) clearTimeout(timer);
|
|
@@ -322,7 +316,7 @@ export const createSandboxTools = async (
|
|
|
322
316
|
const finish = () => processError
|
|
323
317
|
? reject(processError)
|
|
324
318
|
: timedOut
|
|
325
|
-
? reject(new Error(`timeout:${
|
|
319
|
+
? reject(new Error(`timeout:${options.timeout}`))
|
|
326
320
|
: resolve({ exitCode: killed ? null : code });
|
|
327
321
|
if (!child.pid) {
|
|
328
322
|
finish();
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
const platformTexts: Partial<Record<NodeJS.Platform, string>> = {
|
|
2
|
-
darwin: "`bash` runs on macOS with the BSD userland: `grep` has no `-P` (use `grep -E`
|
|
2
|
+
darwin: "`bash` runs on macOS with the BSD userland: `grep` has no `-P` (use `grep -E`), in-place `sed` needs an explicit backup suffix (`sed -i '' ...`), `date` uses `-v` instead of `-d`, `xargs` uses `-I{}` instead of `-i`, and other GNU-only flags are unavailable. `/bin/bash` is version 3.2: no associative arrays (`declare -A`), no `mapfile`/`readarray`, no `${var,,}` case conversion.",
|
|
3
3
|
linux: "`bash` runs on Linux with the GNU userland: `grep -P`, `sed -i` without suffix and the usual GNU flags work.",
|
|
4
4
|
win32: "`bash` runs on Windows with the bash RAgents brings along, an MSYS userland with the GNU tools (coreutils, `grep`, `sed`, `awk`, `find`, `xargs`, `diff`, `patch`, `tar`, `gzip`, `unzip`, `file`, `cygpath`, `dos2unix`): `grep -P`, `sed -i` without suffix and the usual GNU flags work. `git`, `dotnet` and `node` are the Windows programs of this machine on the `PATH`, with the user's own configuration and login; they take Windows paths, not `/c/...` (`cygpath -w` converts). The file system is the Windows one: absolute paths are `C:/project/...` or `/c/project/...`, both spellings reach the same file, and a path from another tool may use backslashes. Files often carry CRLF line endings, so anchor patterns with `\\r\\?$` and keep the line ending a file already has.",
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const searchTexts = {
|
|
8
|
+
ripgrep: "Search code with `rg` (ripgrep) and list files with `rg --files`: it skips hidden files and everything `.gitignore` excludes, such as `node_modules`, `bin` and `obj`, and is far faster than `grep -r` (`rg -uu` searches those too). Use `grep` only on single files or in pipes.",
|
|
9
|
+
grep: "`rg` (ripgrep) is not available here. `grep -r` also walks dependency and build folders, so exclude them: `grep -r --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=bin --exclude-dir=obj ...`.",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Was der Executor dem Werkzeug bash über die Plattform hinaus bietet. */
|
|
13
|
+
export interface ShellTools {
|
|
14
|
+
readonly ripgrep: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const shellPlatformText = (platform: NodeJS.Platform, tools: ShellTools): string => {
|
|
8
18
|
const text = platformTexts[platform];
|
|
9
19
|
if (!text) throw new Error(`Für die Plattform ${platform} gibt es keine Shell-Beschreibung.`);
|
|
10
|
-
|
|
20
|
+
const search = tools.ripgrep ? searchTexts.ripgrep : searchTexts.grep;
|
|
21
|
+
return `${text} ${search} A nonzero exit code, for example \`grep\` without a match, comes back as an ordinary result with the code at the end, not as a tool error; only start, timeout and abort problems are tool errors.`;
|
|
11
22
|
};
|
|
@@ -23,6 +23,3 @@ export const workspaceDataDirectory = (home = homedir()): string => path.join(ra
|
|
|
23
23
|
/** Der Datenordner dieses Prozesses: DATA_DIR wie beim Server, sonst der des Arbeitsplatzes ohne Profil. */
|
|
24
24
|
export const hostDataDirectory = (environment: NodeJS.ProcessEnv = process.env): string =>
|
|
25
25
|
path.resolve(environment.DATA_DIR ?? workspaceDataDirectory());
|
|
26
|
-
|
|
27
|
-
export const hostToolFile = (pluginId: string, relative: string): string =>
|
|
28
|
-
path.join(pluginToolsDirectory(hostDataDirectory(), pluginId), ...relative.split("/"));
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export const ASK_PLUGIN_ID = "ragents.ask";
|
|
2
2
|
|
|
3
|
+
/** What the asking tool call gets when a person's message to the asker closes its question. */
|
|
4
|
+
export const SUPERSEDED_ANSWER = "Not answered: the user sent a new message instead.";
|
|
5
|
+
|
|
3
6
|
export interface AskPayload {
|
|
4
7
|
question: string;
|
|
5
8
|
options: string[];
|
|
@@ -23,3 +26,9 @@ export const askPayloadOf = (payload: unknown): AskPayload | undefined => {
|
|
|
23
26
|
...(typeof candidate.recipient === "string" ? { recipient: candidate.recipient } : {}),
|
|
24
27
|
};
|
|
25
28
|
};
|
|
29
|
+
|
|
30
|
+
export const supersedingInputOf = (result: unknown): string | undefined => {
|
|
31
|
+
if (typeof result !== "object" || result === null) return undefined;
|
|
32
|
+
const { supersededBy } = result as { supersededBy?: unknown };
|
|
33
|
+
return typeof supersededBy === "string" ? supersededBy : undefined;
|
|
34
|
+
};
|
|
@@ -4,20 +4,34 @@ import {
|
|
|
4
4
|
type Action,
|
|
5
5
|
type CommandContext,
|
|
6
6
|
type JournalEvent,
|
|
7
|
+
type JsonValue,
|
|
7
8
|
type Orchestration,
|
|
8
9
|
} from "@ragents/engine";
|
|
9
|
-
import { askPayloadOf, ASK_PLUGIN_ID } from "../ask-payload.js";
|
|
10
|
+
import { askPayloadOf, ASK_PLUGIN_ID, SUPERSEDED_ANSWER, supersedingInputOf } from "../ask-payload.js";
|
|
10
11
|
import { DISMISSED_ANSWER, type AskCall, type AskRequest, type AskService } from "./contract.js";
|
|
11
12
|
|
|
12
13
|
const answerOf = (decision: "approved" | "dismissed", result: unknown): string =>
|
|
13
|
-
decision !== "approved"
|
|
14
|
+
decision !== "approved"
|
|
15
|
+
? supersedingInputOf(result) === undefined ? DISMISSED_ANSWER : SUPERSEDED_ANSWER
|
|
16
|
+
: typeof result === "string" ? result : result === null || result === undefined ? "" : JSON.stringify(result);
|
|
14
17
|
|
|
15
18
|
const resolvedAnswerOf = (action: Action): string | null =>
|
|
16
19
|
action.status === "pending" ? null : answerOf(action.status, action.result);
|
|
17
20
|
|
|
21
|
+
const humanInputPendingFor = (runtime: Orchestration, runId: string, actorId: string): boolean =>
|
|
22
|
+
[...runtime.state(runId).inputs.values()].some((input) =>
|
|
23
|
+
input.actorId === actorId && input.origin === "human" && input.lifecycle.kind === "pending");
|
|
24
|
+
|
|
25
|
+
interface Waiter {
|
|
26
|
+
runId: string;
|
|
27
|
+
/** The actor whose running turn waits for the answer; null for a question asked outside a turn. */
|
|
28
|
+
blockedActorId: string | null;
|
|
29
|
+
settle: (answer: string) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
export class RuntimeAskService implements AskService {
|
|
19
33
|
#runtime: Orchestration | undefined;
|
|
20
|
-
readonly #waiters = new Map<string,
|
|
34
|
+
readonly #waiters = new Map<string, Waiter>();
|
|
21
35
|
readonly #aborting = new Set<string>();
|
|
22
36
|
|
|
23
37
|
bind(runtime: Orchestration): void {
|
|
@@ -28,6 +42,7 @@ export class RuntimeAskService implements AskService {
|
|
|
28
42
|
|
|
29
43
|
ask(call: AskCall, request: AskRequest, signal: AbortSignal | undefined): Promise<string> {
|
|
30
44
|
const runtime = this.#requireRuntime();
|
|
45
|
+
if (call.turnId && humanInputPendingFor(runtime, call.runId, call.agentId)) return Promise.resolve(SUPERSEDED_ANSWER);
|
|
31
46
|
const context: CommandContext = {
|
|
32
47
|
actorId: call.agentId,
|
|
33
48
|
commandId: call.commandId,
|
|
@@ -51,7 +66,7 @@ export class RuntimeAskService implements AskService {
|
|
|
51
66
|
if (proposed?.type !== "action.proposed") {
|
|
52
67
|
throw new Error(`Die Frage von ${call.agentId} wurde nicht im Journal von ${call.runId} angelegt`);
|
|
53
68
|
}
|
|
54
|
-
return this.#awaitAnswer(call.runId, proposed.payload.actionId, signal);
|
|
69
|
+
return this.#awaitAnswer(call.runId, proposed.payload.actionId, call.turnId ? call.agentId : null, signal);
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
answer(runId: string, actionId: string, input: { answer?: string; dismiss?: boolean }): void {
|
|
@@ -72,6 +87,18 @@ export class RuntimeAskService implements AskService {
|
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
withdraw(runId: string, actionId: string): void {
|
|
90
|
+
this.#dismiss(runId, actionId, "ask-withdraw", null, DISMISSED_ANSWER);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#supersede(runId: string, actionId: string, inputId: string): void {
|
|
94
|
+
try {
|
|
95
|
+
this.#dismiss(runId, actionId, "ask-supersede", { supersededBy: inputId }, SUPERSEDED_ANSWER);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`Question ${actionId} could not be closed for message ${inputId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#dismiss(runId: string, actionId: string, commandPrefix: string, result: JsonValue, answer: string): void {
|
|
75
102
|
const runtime = this.#requireRuntime();
|
|
76
103
|
const state = runtime.state(runId);
|
|
77
104
|
const action = state.actions.get(actionId);
|
|
@@ -79,18 +106,18 @@ export class RuntimeAskService implements AskService {
|
|
|
79
106
|
this.#aborting.add(actionId);
|
|
80
107
|
try {
|
|
81
108
|
runtime.resolveAction(
|
|
82
|
-
{ actorId: state.ownerId, commandId:
|
|
109
|
+
{ actorId: state.ownerId, commandId: `${commandPrefix}:${actionId}:${randomUUID()}` },
|
|
83
110
|
runId,
|
|
84
111
|
actionId,
|
|
85
|
-
{ decision: "dismissed", result
|
|
112
|
+
{ decision: "dismissed", result },
|
|
86
113
|
);
|
|
87
114
|
} finally {
|
|
88
115
|
this.#aborting.delete(actionId);
|
|
89
116
|
}
|
|
90
|
-
this.#waiters.get(actionId)?.(
|
|
117
|
+
this.#waiters.get(actionId)?.settle(answer);
|
|
91
118
|
}
|
|
92
119
|
|
|
93
|
-
#awaitAnswer(runId: string, actionId: string, signal: AbortSignal | undefined): Promise<string> {
|
|
120
|
+
#awaitAnswer(runId: string, actionId: string, blockedActorId: string | null, signal: AbortSignal | undefined): Promise<string> {
|
|
94
121
|
const runtime = this.#requireRuntime();
|
|
95
122
|
const action = runtime.state(runId).actions.get(actionId);
|
|
96
123
|
if (!action) throw new Error(`Frage ${actionId} existiert nicht in Run ${runId}`);
|
|
@@ -123,16 +150,29 @@ export class RuntimeAskService implements AskService {
|
|
|
123
150
|
return;
|
|
124
151
|
}
|
|
125
152
|
signal?.addEventListener("abort", abort, { once: true });
|
|
126
|
-
this.#waiters.set(actionId,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
this.#waiters.set(actionId, {
|
|
154
|
+
runId,
|
|
155
|
+
blockedActorId,
|
|
156
|
+
settle: (answer) => {
|
|
157
|
+
signal?.removeEventListener("abort", abort);
|
|
158
|
+
this.#waiters.delete(actionId);
|
|
159
|
+
resolve(answer);
|
|
160
|
+
},
|
|
130
161
|
});
|
|
131
162
|
});
|
|
132
163
|
}
|
|
133
164
|
|
|
134
165
|
#onJournal(events: JournalEvent[]): void {
|
|
135
166
|
for (const event of events) {
|
|
167
|
+
if (event.type === "actor.input.enqueued") {
|
|
168
|
+
if (event.payload.origin !== "human") continue;
|
|
169
|
+
const { actorId, inputId } = event.payload;
|
|
170
|
+
for (const [actionId, waiter] of this.#waiters) {
|
|
171
|
+
if (waiter.runId !== event.runId || waiter.blockedActorId !== actorId) continue;
|
|
172
|
+
queueMicrotask(() => this.#supersede(event.runId, actionId, inputId));
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
136
176
|
if (event.type !== "action.resolved") continue;
|
|
137
177
|
if (this.#aborting.has(event.payload.actionId)) continue;
|
|
138
178
|
const resolved = this.#requireRuntime().state(event.runId).actions.get(event.payload.actionId);
|
|
@@ -140,7 +180,7 @@ export class RuntimeAskService implements AskService {
|
|
|
140
180
|
const answer = answerOf(event.payload.decision, event.payload.result);
|
|
141
181
|
const waiter = this.#waiters.get(event.payload.actionId);
|
|
142
182
|
if (waiter) {
|
|
143
|
-
waiter(answer);
|
|
183
|
+
waiter.settle(answer);
|
|
144
184
|
continue;
|
|
145
185
|
}
|
|
146
186
|
queueMicrotask(() => this.#enqueueAnswer(event.runId, event.payload.actionId, answer));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useAccess } from "@ragents/web/AccessContext";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import type { ActionViewContext } from "@ragents/web/PluginRegistry";
|
|
4
|
-
import { askPayloadOf } from "../ask-payload";
|
|
4
|
+
import { askPayloadOf, SUPERSEDED_ANSWER, supersedingInputOf } from "../ask-payload";
|
|
5
5
|
import { answerQuestion } from "./api";
|
|
6
6
|
import { QuestionCard } from "./QuestionCard";
|
|
7
7
|
|
|
@@ -25,7 +25,8 @@ export function AskActionView({ action, session, text }: ActionViewContext) {
|
|
|
25
25
|
<QuestionCard
|
|
26
26
|
answer={action.status === undefined
|
|
27
27
|
? undefined
|
|
28
|
-
: action.status === "approved" ? answerTextOf(action.result)
|
|
28
|
+
: action.status === "approved" ? answerTextOf(action.result)
|
|
29
|
+
: supersedingInputOf(action.result) !== undefined ? SUPERSEDED_ANSWER : "Der Benutzer hat die Frage verworfen."}
|
|
29
30
|
onAnswer={access.can("runs.write") ? answer : undefined}
|
|
30
31
|
question={question}
|
|
31
32
|
text={text}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { WorkspaceExecutorContribution } from "@ragents/workspace-executor";
|
|
2
|
+
import { browserModule } from "./module.js";
|
|
3
|
+
|
|
4
|
+
/** Der Browser läuft dort, wo der Arbeitsbereich des Runs liegt: im Executor des Servers oder in dem des Arbeitsplatzes. */
|
|
5
|
+
export const executor: WorkspaceExecutorContribution = (machine) => ({ modules: [browserModule(machine)] });
|
package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/module.ts
RENAMED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Browser } from "playwright-core";
|
|
2
|
-
import {
|
|
3
|
-
import type { WorkspaceModuleFactory } from "../module.js";
|
|
2
|
+
import type { WorkspaceExecutorMachine, WorkspaceModuleFactory } from "@ragents/workspace-executor";
|
|
4
3
|
import { BROWSER_OPERATIONS, type BrowserCheck, type BrowserTarget, type BrowserViewport } from "./contract.js";
|
|
5
4
|
import { BrowserPages } from "./pages.js";
|
|
6
5
|
import { launchChromium } from "./playwright.js";
|
|
@@ -14,38 +13,33 @@ export interface BrowserModuleOptions {
|
|
|
14
13
|
checkTimeoutMs?: number;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
const invalid = (message: string): WorkspaceOperationError => new WorkspaceOperationError("browser-input-invalid", message, 400);
|
|
18
|
-
|
|
19
|
-
const fieldsOf = (input: unknown): Readonly<Record<string, unknown>> => {
|
|
20
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) throw invalid("Die Eingabe einer Browseroperation ist ein Objekt");
|
|
21
|
-
return input as Readonly<Record<string, unknown>>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const textOf = (input: unknown, key: string): string => {
|
|
25
|
-
const value = fieldsOf(input)[key];
|
|
26
|
-
if (typeof value !== "string") throw invalid(`Die Browseroperation braucht ${key} als Text`);
|
|
27
|
-
return value;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const targetOf = (input: unknown): BrowserTarget => {
|
|
31
|
-
const value = fieldsOf(input).target;
|
|
32
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw invalid("Die Browseroperation braucht ein Ziel");
|
|
33
|
-
return value as BrowserTarget;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const viewportOf = (input: unknown): BrowserViewport => {
|
|
37
|
-
const { width, height } = fieldsOf(input);
|
|
38
|
-
if (typeof width !== "number" || typeof height !== "number" || !Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
|
|
39
|
-
throw invalid("Ein Viewport braucht Breite und Höhe als positive ganze Zahlen");
|
|
40
|
-
}
|
|
41
|
-
return { width, height };
|
|
42
|
-
};
|
|
43
|
-
|
|
44
16
|
/** Der Browser eines Runs auf dieser Maschine: Seite, Aktionen, Prüfungen und Aufnahmen; Evidenz und Ablage hält der Aufrufer. */
|
|
45
|
-
export const browserModule = (options: BrowserModuleOptions = {}): WorkspaceModuleFactory => (host) => {
|
|
17
|
+
export const browserModule = (machine: WorkspaceExecutorMachine, options: BrowserModuleOptions = {}): WorkspaceModuleFactory => (host) => {
|
|
18
|
+
const invalid = (message: string): Error => machine.operationError("browser-input-invalid", message, 400);
|
|
19
|
+
const fieldsOf = (input: unknown): Readonly<Record<string, unknown>> => {
|
|
20
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) throw invalid("Die Eingabe einer Browseroperation ist ein Objekt");
|
|
21
|
+
return input as Readonly<Record<string, unknown>>;
|
|
22
|
+
};
|
|
23
|
+
const textOf = (input: unknown, key: string): string => {
|
|
24
|
+
const value = fieldsOf(input)[key];
|
|
25
|
+
if (typeof value !== "string") throw invalid(`Die Browseroperation braucht ${key} als Text`);
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
const targetOf = (input: unknown): BrowserTarget => {
|
|
29
|
+
const value = fieldsOf(input).target;
|
|
30
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw invalid("Die Browseroperation braucht ein Ziel");
|
|
31
|
+
return value as BrowserTarget;
|
|
32
|
+
};
|
|
33
|
+
const viewportOf = (input: unknown): BrowserViewport => {
|
|
34
|
+
const { width, height } = fieldsOf(input);
|
|
35
|
+
if (typeof width !== "number" || typeof height !== "number" || !Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
|
|
36
|
+
throw invalid("Ein Viewport braucht Breite und Höhe als positive ganze Zahlen");
|
|
37
|
+
}
|
|
38
|
+
return { width, height };
|
|
39
|
+
};
|
|
46
40
|
const timeoutMs = options.timeoutMs ?? BROWSER_TIMEOUT_MS;
|
|
47
41
|
const pages = new BrowserPages({
|
|
48
|
-
launch: options.launch ?? (async (runId) => launchChromium((await host.contextFor(runId)).hostRoot, runId, timeoutMs)),
|
|
42
|
+
launch: options.launch ?? (async (runId) => launchChromium(machine, (await host.contextFor(runId)).hostRoot, runId, timeoutMs)),
|
|
49
43
|
timeoutMs,
|
|
50
44
|
checkTimeoutMs: options.checkTimeoutMs ?? Math.min(5_000, timeoutMs),
|
|
51
45
|
});
|
package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/playwright.ts
RENAMED
|
@@ -1,30 +1,16 @@
|
|
|
1
1
|
import { constants } from "node:fs";
|
|
2
2
|
import { access } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
6
4
|
import type { Browser } from "playwright-core";
|
|
7
|
-
import {
|
|
8
|
-
import { safeProcessEnvironment } from "../safe-environment.js";
|
|
5
|
+
import type { WorkspaceExecutorMachine } from "@ragents/workspace-executor";
|
|
9
6
|
import { BROWSER_EXECUTABLE_VARIABLE } from "./contract.js";
|
|
10
7
|
|
|
11
8
|
type Playwright = typeof import("playwright-core");
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!hostRoot) {
|
|
18
|
-
throw new Error("Für die Browserprüfung ist auf diesem Rechner kein Host bekannt, aus dem sich playwright-core auflösen "
|
|
19
|
-
+ "ließe. Ein Arbeitsplatz bekommt ihn mit der ersten Verbindung zu einem verteilenden Server oder über die "
|
|
20
|
-
+ "Einstellung ragents.hostPath");
|
|
21
|
-
}
|
|
22
|
-
const load = createRequire(path.join(hostRoot, "package.json"));
|
|
23
|
-
try {
|
|
24
|
-
return load("playwright-core") as Playwright;
|
|
25
|
-
} catch (cause) {
|
|
26
|
-
throw new Error(`playwright-core liegt nicht im Host ${hostRoot}: ${messageOf(cause)}`);
|
|
27
|
-
}
|
|
10
|
+
/** playwright-core liegt in den node_modules des Hosts dieser Maschine und wird erst im Aufruf geladen, nie mit dem Beitrag. */
|
|
11
|
+
export const hostPlaywright = async (machine: WorkspaceExecutorMachine, hostRoot: string | undefined): Promise<Playwright> => {
|
|
12
|
+
const loaded = await import(pathToFileURL(machine.hostPackageFile(hostRoot, "playwright-core")).href) as { default: Playwright };
|
|
13
|
+
return loaded.default;
|
|
28
14
|
};
|
|
29
15
|
|
|
30
16
|
/** Der Chrome dieser Maschine: der aus ihrer Umgebung, sonst das Chromium, das die Provisionierung für playwright-core holt. */
|
|
@@ -41,12 +27,12 @@ export const browserExecutable = async (playwright: Playwright, environment: Nod
|
|
|
41
27
|
};
|
|
42
28
|
|
|
43
29
|
/** Startet Chrome headless mit der Umgebung dieser Maschine und dem Marker des Runs, damit die Prozessanzeige ihn zuordnet. */
|
|
44
|
-
export const launchChromium = async (hostRoot: string | undefined, runId: string, timeoutMs: number): Promise<Browser> => {
|
|
45
|
-
const playwright = hostPlaywright(hostRoot);
|
|
30
|
+
export const launchChromium = async (machine: WorkspaceExecutorMachine, hostRoot: string | undefined, runId: string, timeoutMs: number): Promise<Browser> => {
|
|
31
|
+
const playwright = await hostPlaywright(machine, hostRoot);
|
|
46
32
|
return playwright.chromium.launch({
|
|
47
33
|
executablePath: await browserExecutable(playwright),
|
|
48
34
|
headless: true,
|
|
49
35
|
timeout: timeoutMs,
|
|
50
|
-
env:
|
|
36
|
+
env: machine.processEnvironment(runId),
|
|
51
37
|
});
|
|
52
38
|
};
|
|
@@ -2,13 +2,13 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { access, constants } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { chromium } from "playwright-core";
|
|
5
|
-
import { BROWSER_EXECUTABLE_VARIABLE } from "@ragents/workspace-executor";
|
|
6
5
|
import {
|
|
7
6
|
hostPackageFolder,
|
|
8
7
|
provisionGap,
|
|
9
8
|
provisionReady,
|
|
10
9
|
type PluginProvision,
|
|
11
10
|
} from "@ragents/host/plugin-support/provision.js";
|
|
11
|
+
import { BROWSER_EXECUTABLE_VARIABLE } from "./executor/contract.js";
|
|
12
12
|
|
|
13
13
|
const executable = async (file: string): Promise<boolean> => access(file, constants.X_OK).then(() => true, () => false);
|
|
14
14
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import type { SandboxServices } from "@ragents/host/plugin-support/workspace-sandbox-host.js";
|
|
5
|
+
import { documentsApiPrefix } from "@ragents/plugins/ragents.documents/contract.js";
|
|
4
6
|
import {
|
|
5
7
|
BROWSER_OPERATIONS,
|
|
6
8
|
type BrowserCheck,
|
|
@@ -10,9 +12,7 @@ import {
|
|
|
10
12
|
type BrowserStep,
|
|
11
13
|
type BrowserTarget,
|
|
12
14
|
type BrowserViewport,
|
|
13
|
-
} from "
|
|
14
|
-
import type { SandboxServices } from "@ragents/host/plugin-support/workspace-sandbox-host.js";
|
|
15
|
-
import { documentsApiPrefix } from "@ragents/plugins/ragents.documents/contract.js";
|
|
15
|
+
} from "../executor/contract.js";
|
|
16
16
|
import type { BrowserCallOptions, BrowserEvidence, BrowserRuntime } from "./contract.js";
|
|
17
17
|
|
|
18
18
|
export const DEFAULT_VIEWPORT: BrowserViewport = { width: 1920, height: 1080 };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { serviceToken } from "@ragents/engine";
|
|
2
|
-
import type { BrowserSnapshot } from "
|
|
2
|
+
import type { BrowserSnapshot } from "../executor/contract.js";
|
|
3
3
|
|
|
4
|
-
export type { BrowserSnapshot, BrowserTarget } from "
|
|
4
|
+
export type { BrowserSnapshot, BrowserTarget } from "../executor/contract.js";
|
|
5
5
|
|
|
6
6
|
export interface BrowserEvidence {
|
|
7
7
|
checkedAt?: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { RAgentsPlugin } from "@ragents/engine";
|
|
2
|
-
import { BROWSER_EXECUTABLE_VARIABLE } from "@ragents/workspace-executor";
|
|
3
2
|
import type { PluginModule } from "@ragents/host/plugin-support/plugin-module.js";
|
|
4
3
|
import { sandboxServicesToken } from "@ragents/host/plugin-support/workspace-sandbox-host.js";
|
|
5
4
|
import { documentStoreToken } from "@ragents/host/ragents/document-store.js";
|
|
5
|
+
import { BROWSER_EXECUTABLE_VARIABLE } from "../executor/contract.js";
|
|
6
6
|
import { RunBrowser } from "./browser.js";
|
|
7
7
|
import { browserRuntimeToken } from "./contract.js";
|
|
8
8
|
import { createBrowserFunctions, createBrowserImageContribution } from "./tools.js";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import type { LanguageServerDescription, WorkspaceExecutorContribution } from "@ragents/workspace-executor";
|
|
6
|
+
|
|
7
|
+
export const FSHARP_SERVER_VARIABLE = "FSHARP_LANGUAGE_SERVER";
|
|
8
|
+
export const FSHARP_SERVER_FILE = "fsautocomplete/fsautocomplete.dll";
|
|
9
|
+
|
|
10
|
+
const languages = { ".fs": "fsharp", ".fsi": "fsharp", ".fsx": "fsharp" } as const;
|
|
11
|
+
|
|
12
|
+
export const fsharpLanguageServer: LanguageServerDescription = {
|
|
13
|
+
id: "fsharp",
|
|
14
|
+
label: "FSAC",
|
|
15
|
+
languages,
|
|
16
|
+
rootDescription: "the .sln file (or a single .fsproj)",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const PROJECT_LINE = /^Project\("\{[^}]+\}"\)\s*=\s*"[^"]*",\s*"([^"]+)"/gm;
|
|
20
|
+
|
|
21
|
+
/** Die Projekte einer .sln, aufgelöst neben ihr; Ordnereinträge der Solution zählen nicht. */
|
|
22
|
+
export const solutionProjects = async (solutionFile: string): Promise<string[]> => {
|
|
23
|
+
const text = await readFile(solutionFile, "utf8");
|
|
24
|
+
const directory = path.dirname(solutionFile);
|
|
25
|
+
return [...text.matchAll(PROJECT_LINE)]
|
|
26
|
+
.map((match) => match[1].replaceAll("\\", "/"))
|
|
27
|
+
.filter((relative) => /\.[a-z]+proj$/i.test(relative))
|
|
28
|
+
.map((relative) => path.resolve(directory, relative));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const dotnetCommand = (server: string, args: readonly string[]): { command: string; args: string[] } =>
|
|
32
|
+
server.toLowerCase().endsWith(".dll")
|
|
33
|
+
? { command: "dotnet", args: [server, ...args] }
|
|
34
|
+
: { command: server, args: [...args] };
|
|
35
|
+
|
|
36
|
+
const serverPath = (toolsDirectory: string): string => {
|
|
37
|
+
const configured = process.env[FSHARP_SERVER_VARIABLE];
|
|
38
|
+
if (configured) return configured;
|
|
39
|
+
const provisioned = path.join(toolsDirectory, ...FSHARP_SERVER_FILE.split("/"));
|
|
40
|
+
if (existsSync(provisioned)) return provisioned;
|
|
41
|
+
throw new Error("fsautocomplete gibt es auf diesem Rechner nicht: weder "
|
|
42
|
+
+ `${FSHARP_SERVER_VARIABLE} gesetzt noch ${provisioned} vorhanden. Ein Arbeitsplatz holt es mit `
|
|
43
|
+
+ `pnpm provision --workspace, ein Server über pnpm provision <profil>; ${FSHARP_SERVER_VARIABLE} übersteuert beides`);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const contentOf = (params: unknown): string => (params as { content?: string } | undefined)?.content ?? "";
|
|
47
|
+
|
|
48
|
+
export const executor: WorkspaceExecutorContribution = (machine) => ({
|
|
49
|
+
languageServers: [{
|
|
50
|
+
...fsharpLanguageServer,
|
|
51
|
+
resolveRoot: (workspaceRoot, root) => machine.resolveRootFile(workspaceRoot, root, [".sln", ".fsproj"]),
|
|
52
|
+
rootDirectory: (root) => path.dirname(root),
|
|
53
|
+
launch: async (context, root) => ({
|
|
54
|
+
label: "FSAC",
|
|
55
|
+
...dotnetCommand(serverPath(machine.toolsDirectory), ["--state-directory", path.join(context.logDirectory, "fsautocomplete")]),
|
|
56
|
+
cwd: path.dirname(root),
|
|
57
|
+
env: context.env,
|
|
58
|
+
uid: context.uid,
|
|
59
|
+
gid: context.gid,
|
|
60
|
+
rootUri: pathToFileURL(path.dirname(root)).href,
|
|
61
|
+
initializationOptions: { AutomaticWorkspaceInit: false },
|
|
62
|
+
languages,
|
|
63
|
+
}),
|
|
64
|
+
open: async (session, root, timeoutMs) => {
|
|
65
|
+
const projects = root.toLowerCase().endsWith(".sln")
|
|
66
|
+
? (await solutionProjects(root)).filter((project) => project.toLowerCase().endsWith(".fsproj"))
|
|
67
|
+
: [root];
|
|
68
|
+
if (projects.length === 0) throw new Error(`${path.basename(root)} enthält keine F#-Projekte`);
|
|
69
|
+
const finished = session.waitForNotification(timeoutMs, "Projekte geladen", (method, params) =>
|
|
70
|
+
method === "fsharp/notifyWorkspace"
|
|
71
|
+
&& contentOf(params).includes("\"workspaceLoad\"")
|
|
72
|
+
&& contentOf(params).includes("\"finished\""));
|
|
73
|
+
await session.request("fsharp/workspaceLoad", {
|
|
74
|
+
textDocuments: projects.map((project) => ({ uri: pathToFileURL(project).href })),
|
|
75
|
+
});
|
|
76
|
+
await finished;
|
|
77
|
+
return `${projects.length} F#-Projekt${projects.length === 1 ? "" : "e"} aus ${path.basename(root)} geladen`;
|
|
78
|
+
},
|
|
79
|
+
}],
|
|
80
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { FSHARP_SERVER_FILE } from "@ragents/workspace-executor";
|
|
4
3
|
import {
|
|
5
4
|
DOTNET_INSTRUCTION,
|
|
6
5
|
downloadArchive,
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
type PluginProvision,
|
|
16
15
|
type ProvisionState,
|
|
17
16
|
} from "@ragents/host/plugin-support/provision.js";
|
|
17
|
+
import { FSHARP_SERVER_FILE } from "./executor.js";
|
|
18
18
|
|
|
19
19
|
export const FSAUTOCOMPLETE_VERSION = "0.83.0";
|
|
20
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fsharpAdapter, FSHARP_SERVER_VARIABLE } from "@ragents/workspace-executor";
|
|
2
1
|
import { createLanguageServerPlugin } from "@ragents/host/plugin-support/language-server/plugin.js";
|
|
3
2
|
import type { PluginModule } from "@ragents/host/plugin-support/plugin-module.js";
|
|
3
|
+
import { fsharpLanguageServer, FSHARP_SERVER_VARIABLE } from "../executor.js";
|
|
4
4
|
|
|
5
5
|
export const fsharpConfigDescriptors = [
|
|
6
6
|
{ key: FSHARP_SERVER_VARIABLE, source: "environment" },
|
|
@@ -9,7 +9,7 @@ export const fsharpConfigDescriptors = [
|
|
|
9
9
|
export const plugin: PluginModule = {
|
|
10
10
|
create: () => createLanguageServerPlugin({
|
|
11
11
|
id: "ragents.lsp-fsharp",
|
|
12
|
-
|
|
12
|
+
languageServer: fsharpLanguageServer,
|
|
13
13
|
tabLabel: "F#",
|
|
14
14
|
configDescriptors: fsharpConfigDescriptors,
|
|
15
15
|
}),
|