@schlenkr/ragents 0.1.8 → 0.1.10
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/package.json +1 -1
- package/apps/server/src/access-projection.ts +6 -36
- package/apps/server/src/api/contracts.ts +5 -2
- package/apps/server/src/api/core-methods.ts +5 -3
- package/apps/server/src/config-definition.ts +24 -2
- package/apps/server/src/config-file.ts +1 -1
- package/apps/server/src/config.ts +0 -6
- package/apps/server/src/host-api.json +5 -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/process-sandbox.ts +16 -15
- 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/provider.ts +2 -2
- package/apps/server/src/ragents/engine.ts +2 -2
- package/apps/server/src/ragents/global-chat.ts +7 -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 +4 -1
- package/apps/server/tsconfig.json +2 -0
- package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-xXXhTDbh.js → highlighted-body-KPVGNVTW-BO4wLQtE.js} +1 -1
- package/apps/web/dist/assets/main-DnnqWHkH.js +11 -0
- package/apps/web/dist/assets/mermaid-HWGCJPDP-BLLeth5_.js +568 -0
- package/apps/web/dist/assets/{runPanel-ZT6TPDLM.js → runPanel-DPm7s3KM.js} +2 -2
- package/apps/web/dist/help/guide-clients.html +26 -8
- package/apps/web/dist/help/guide-clients.md +27 -8
- 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 +4328 -1132
- package/apps/web/dist/help/mini-app.css +24 -0
- package/apps/web/dist/host-web.json +4 -4
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/run-panel.html +2 -2
- package/apps/web/package.json +1 -1
- package/apps/web/src/panel/ConnectionsPage.tsx +3 -0
- package/apps/web/src/panel/StartPage.tsx +3 -0
- package/apps/web/src/panel/contract.ts +7 -0
- package/bundles/ragents.activity/ragents-bundle.json +3 -3
- package/bundles/ragents.actor-programs/ragents-bundle.json +4 -4
- package/bundles/ragents.actor-programs/server/index.js +31 -3
- package/bundles/ragents.actor-programs/server/index.js.map +4 -4
- package/bundles/ragents.actor-programs/web/index.js.map +1 -1
- package/bundles/ragents.ask/ragents-bundle.json +4 -6
- 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-V7W3QDVS.js → chunk-MBRPS477.js} +2 -2
- package/bundles/ragents.ask/web/chunks/{chunk-V7W3QDVS.js.map → chunk-MBRPS477.js.map} +4 -4
- 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/exports/web/QuestionCard.js +1 -1
- package/bundles/ragents.ask/web/index.js +1 -1
- package/bundles/ragents.ask/web/index.js.map +4 -4
- 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 +4 -4
- package/bundles/ragents.orchestration/web/classes.json +1 -1
- package/bundles/ragents.orchestration/web/index.js +6 -6
- package/bundles/ragents.orchestration/web/index.js.map +3 -3
- package/bundles/ragents.overseer/ragents-bundle.json +4 -4
- package/bundles/ragents.overseer/server/index.js +1 -1
- package/bundles/ragents.overseer/server/index.js.map +2 -2
- 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 +105 -30
- 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 +3 -3
- 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/index.ts +1 -0
- package/packages/ragents/src/plugin-host.ts +46 -0
- package/packages/ragents/src/plugin-types.ts +19 -0
- 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/managed-process.ts +28 -0
- package/packages/workspace-executor/src/modules.ts +7 -6
- package/packages/workspace-executor/src/sandbox-tools.ts +12 -12
- package/packages/workspace-executor/src/shell-platform.ts +14 -3
- package/packages/workspace-executor/src/tools.ts +0 -3
- package/plugins/ragents.actor-programs/server/access-projections.ts +31 -0
- package/plugins/ragents.actor-programs/server/index.ts +2 -0
- 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.ask/web/index.tsx +0 -6
- 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.orchestration/web/ActorTile.tsx +1 -1
- package/plugins/ragents.orchestration/web/run-panel/RunPanel.tsx +8 -7
- package/plugins/ragents.orchestration/web/run-panel/sheet-status.ts +3 -3
- package/plugins/ragents.overseer/server/api.ts +2 -2
- package/plugins/ragents.profile-distribution/server/archive.ts +2 -1
- package/plugins/ragents.workspace/client/workspace-client.ts +128 -32
- 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 +12 -0
- package/plugins/ragents.workspace/server/index.ts +15 -5
- 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/assets/main-C7JeZFjs.js +0 -6
- package/apps/web/dist/assets/mermaid-HWGCJPDP-DfsU5ofC.js +0 -478
- 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/plugins/ragents.ask/web/QuestionSection.tsx +0 -48
- /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
|
@@ -8,6 +8,7 @@ import type { SandboxServices } from "@ragents/host/plugin-support/workspace-san
|
|
|
8
8
|
import { DISMISSED_ANSWER, type AskService } from "@ragents/plugins/ragents.ask/server/contract.js";
|
|
9
9
|
|
|
10
10
|
export const SOLUTION_ON_START_VARIABLE = "ROSLYN_SOLUTION_ON_START";
|
|
11
|
+
export const SOLUTION_PREFERRED_VARIABLE = "ROSLYN_SOLUTION_PREFERRED";
|
|
11
12
|
export const NO_SOLUTION = "Keine laden";
|
|
12
13
|
export const SOLUTION_QUESTION = "Welche Solution soll Roslyn für die Diagnostik laden?";
|
|
13
14
|
|
|
@@ -21,11 +22,21 @@ export type SolutionAnswer =
|
|
|
21
22
|
| { kind: "open"; path: string }
|
|
22
23
|
| { kind: "forward" };
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
25
|
+
/** Die Vorgabe passt auch in einem Unterordner, etwa wenn der Arbeitsbereich der Ordner über dem Checkout ist. */
|
|
26
|
+
const matchesPreferred = (solution: string, preferred: string): boolean => {
|
|
27
|
+
const path = solution.toLowerCase();
|
|
28
|
+
const expected = preferred.toLowerCase();
|
|
29
|
+
return path === expected || path.endsWith(`/${expected}`);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Was jemand schon geöffnet hat oder gerade öffnet, bleibt ohne Frage und ohne zweites Laden; passt die Vorgabe, zählen nur die passenden Solutions. */
|
|
33
|
+
export const solutionStartStep = (listing: LanguageServerSolutions, preferred?: string): SolutionStartStep => {
|
|
26
34
|
if (listing.opened || listing.solutions.length === 0) return { kind: "none" };
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
const paths = listing.solutions.map((solution) => solution.path);
|
|
36
|
+
const matching = preferred === undefined ? [] : paths.filter((path) => matchesPreferred(path, preferred));
|
|
37
|
+
const candidates = matching.length > 0 ? matching : paths;
|
|
38
|
+
if (candidates.length === 1) return { kind: "open", path: candidates[0] };
|
|
39
|
+
return { kind: "ask", options: [...candidates, NO_SOLUTION] };
|
|
29
40
|
};
|
|
30
41
|
|
|
31
42
|
/** Eine frei formulierte Antwort bekommt der Koordinator, damit sie nicht verloren geht. */
|
|
@@ -38,6 +49,7 @@ export const solutionAnswer = (options: readonly string[], answer: string): Solu
|
|
|
38
49
|
export interface SolutionOnStartOptions {
|
|
39
50
|
pluginId: string;
|
|
40
51
|
adapterId: string;
|
|
52
|
+
preferred?: string;
|
|
41
53
|
sandbox: () => SandboxServices;
|
|
42
54
|
runtime: () => Orchestration;
|
|
43
55
|
ask: () => AskService;
|
|
@@ -45,7 +57,7 @@ export interface SolutionOnStartOptions {
|
|
|
45
57
|
|
|
46
58
|
const messageOf = (error: unknown): string => error instanceof Error ? error.message : String(error);
|
|
47
59
|
|
|
48
|
-
/** Lädt beim Start ohne Run-Script die einzige Solution oder fragt bei mehreren; der Merker im Journal lässt das je Run höchstens einmal zu. */
|
|
60
|
+
/** Lädt beim Start ohne Run-Script die Vorgabe oder die einzige Solution oder fragt bei mehreren; der Merker im Journal lässt das je Run höchstens einmal zu. */
|
|
49
61
|
export interface SolutionOnStart {
|
|
50
62
|
lifecycle: SessionLifecycleContribution;
|
|
51
63
|
/** Jemand hat eine Instanz geöffnet; eine noch offene Startfrage erledigt sich damit ohne Laden und ohne Input. */
|
|
@@ -78,7 +90,7 @@ export const createSolutionOnStart = (options: SolutionOnStartOptions): Solution
|
|
|
78
90
|
|
|
79
91
|
const begin = async (runId: string, ownerId: string, coordinatorId: string, signal: AbortSignal): Promise<{ rest: Promise<void> }> => {
|
|
80
92
|
const listing = await options.sandbox().execute(runId, languageServerSolutionsOperation(options.adapterId), null, { signal }) as LanguageServerSolutions;
|
|
81
|
-
const step = solutionStartStep(listing);
|
|
93
|
+
const step = solutionStartStep(listing, options.preferred);
|
|
82
94
|
if (step.kind === "none") return { rest: Promise.resolve() };
|
|
83
95
|
if (step.kind === "open") return { rest: load(runId, step.path, true, signal) };
|
|
84
96
|
const answer = options.ask().ask(
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import type { LanguageServerDescription, WorkspaceExecutorContribution } from "@ragents/workspace-executor";
|
|
5
|
+
|
|
6
|
+
const languages = {
|
|
7
|
+
".ts": "typescript",
|
|
8
|
+
".tsx": "typescriptreact",
|
|
9
|
+
".mts": "typescript",
|
|
10
|
+
".cts": "typescript",
|
|
11
|
+
".js": "javascript",
|
|
12
|
+
".jsx": "javascriptreact",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export const typescriptLanguageServer: LanguageServerDescription = {
|
|
16
|
+
id: "typescript",
|
|
17
|
+
label: "TypeScript",
|
|
18
|
+
languages,
|
|
19
|
+
rootDescription: "the directory whose tsconfig.json projects should be served (e.g. src)",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const hasWorkspaceTypeScript = (root: string): Promise<boolean> =>
|
|
23
|
+
stat(path.join(root, "node_modules", "typescript", "lib", "tsserver.js")).then((info) => info.isFile(), () => false);
|
|
24
|
+
|
|
25
|
+
/** Sprachserver und Compiler liegen in den node_modules des Hosts dieser Maschine; ohne Host gibt es sie hier nicht. */
|
|
26
|
+
export const executor: WorkspaceExecutorContribution = (machine) => ({
|
|
27
|
+
languageServers: [{
|
|
28
|
+
...typescriptLanguageServer,
|
|
29
|
+
resolveRoot: machine.resolveRootDirectory,
|
|
30
|
+
rootDirectory: (root) => root,
|
|
31
|
+
launch: async (context, root) => ({
|
|
32
|
+
label: "TypeScript",
|
|
33
|
+
command: process.execPath,
|
|
34
|
+
args: [machine.hostPackageFile(context.hostRoot, "typescript-language-server/lib/cli.mjs"), "--stdio"],
|
|
35
|
+
cwd: root,
|
|
36
|
+
// Im Extension-Host ist process.execPath Electron; ohne diese Variable startet es die Anwendung statt des Skripts.
|
|
37
|
+
env: { ...context.env, ELECTRON_RUN_AS_NODE: "1" },
|
|
38
|
+
uid: context.uid,
|
|
39
|
+
gid: context.gid,
|
|
40
|
+
rootUri: pathToFileURL(root).href,
|
|
41
|
+
initializationOptions: await hasWorkspaceTypeScript(root)
|
|
42
|
+
? {}
|
|
43
|
+
: { tsserver: { path: path.dirname(machine.hostPackageFile(context.hostRoot, "typescript")) } },
|
|
44
|
+
languages,
|
|
45
|
+
publishesOnlyChangedDiagnostics: true,
|
|
46
|
+
}),
|
|
47
|
+
open: async (_session, root) =>
|
|
48
|
+
`TypeScript-Server auf ${path.basename(root)} bereit; Projekte werden je Datei aus der nächsten tsconfig.json geladen`,
|
|
49
|
+
}],
|
|
50
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { typescriptAdapter } 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 { typescriptLanguageServer } from "../executor.js";
|
|
4
4
|
|
|
5
5
|
export const plugin: PluginModule = {
|
|
6
|
-
create: () => createLanguageServerPlugin({ id: "ragents.lsp-typescript",
|
|
6
|
+
create: () => createLanguageServerPlugin({ id: "ragents.lsp-typescript", languageServer: typescriptLanguageServer }),
|
|
7
7
|
};
|
|
@@ -2,7 +2,7 @@ import { configuredModelAliases, type ModelAlias } from "@ragents/host/plugin-su
|
|
|
2
2
|
|
|
3
3
|
/** Das Relay bietet die Aliasse des Profils an (MODEL_ALIASES im Abschnitt host), dieselben wie für die eigenen Runs. */
|
|
4
4
|
export const relayAliases = (aliases: readonly ModelAlias[]): readonly ModelAlias[] => {
|
|
5
|
-
if (aliases.length === 0) throw new Error("ragents.model-relay
|
|
5
|
+
if (aliases.length === 0) throw new Error("ragents.model-relay needs MODEL_ALIASES in the host section, a list of { alias, model, thinking?, compaction }");
|
|
6
6
|
return aliases;
|
|
7
7
|
};
|
|
8
8
|
|
|
@@ -18,6 +18,7 @@ export interface ResolvedAlias {
|
|
|
18
18
|
readonly alias: string;
|
|
19
19
|
readonly upstream: ModelUpstream;
|
|
20
20
|
readonly model: ModelUpstream["models"][number];
|
|
21
|
+
readonly compaction: ModelAlias["compaction"];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/** Jeder Alias muss auf einen konfigurierten Anbieter und ein Modell aus dessen Katalog zeigen. */
|
|
@@ -30,10 +31,10 @@ export const resolveAliases = (aliases: readonly ModelAlias[], upstreams: readon
|
|
|
30
31
|
}
|
|
31
32
|
const model = upstream.models.find((candidate) => candidate.id === entry.model);
|
|
32
33
|
if (!model) throw new Error(`MODEL_ALIASES: das Modell ${entry.model} hinter ${entry.alias} fehlt im Katalog von ${entry.upstream}`);
|
|
33
|
-
return { alias: entry.alias, upstream, model };
|
|
34
|
+
return { alias: entry.alias, upstream, model, compaction: entry.compaction };
|
|
34
35
|
});
|
|
35
36
|
|
|
36
|
-
/** Der Katalogeintrag nennt nur, was der Verbraucher für
|
|
37
|
+
/** Der Katalogeintrag nennt nur, was der Verbraucher für Draht und Kompaktierung braucht; Name, Anbieter und Kosten bleiben beim Server. */
|
|
37
38
|
export const catalogEntryOf = (resolved: ResolvedAlias) => ({
|
|
38
39
|
id: resolved.alias,
|
|
39
40
|
object: "model" as const,
|
|
@@ -44,6 +45,7 @@ export const catalogEntryOf = (resolved: ResolvedAlias) => ({
|
|
|
44
45
|
input: [...resolved.model.input],
|
|
45
46
|
contextWindow: resolved.model.contextWindow,
|
|
46
47
|
maxTokens: resolved.model.maxTokens,
|
|
48
|
+
compaction: resolved.compaction,
|
|
47
49
|
...(resolved.model.compat ? { compat: resolved.model.compat } : {}),
|
|
48
50
|
},
|
|
49
51
|
});
|
|
@@ -12,7 +12,7 @@ export function ActorTile({ actor, view, cardSections, chatInput, session, navig
|
|
|
12
12
|
navigation: SessionNavigation;
|
|
13
13
|
onSelect: (selection: EntityReference) => void;
|
|
14
14
|
}) {
|
|
15
|
-
return <div className="flex flex-col overflow-hidden has-[>[data-slot=card-sections]:not(:empty)]:min-h-[260px]">
|
|
15
|
+
return <div className="flex flex-col overflow-hidden has-[>[data-slot=card-sections]:not(:empty)]:min-h-[260px] has-[[data-chat=actions]:not(:empty)]:min-h-[260px]">
|
|
16
16
|
<ActorChatPreview actor={actor} view={view} chatInput={chatInput} running={actor.lifecycle?.kind === "running"}
|
|
17
17
|
conversation={session.actorConversations?.[actor.id]} historyError={session.conversationError}
|
|
18
18
|
onNavigate={onSelect} primaryMessages={actor.id === view.primaryActorId ? session.messages : undefined} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent, type MouseEvent, type PointerEvent, type ReactNode, type RefObject } from "react";
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type MouseEvent, type PointerEvent, type ReactNode, type RefObject } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { ArrowUpRightIcon, LayoutGridIcon, Maximize2Icon, MessageSquareIcon, PanelBottomIcon, PanelRightIcon } from "lucide-react";
|
|
4
4
|
import { useAccess } from "@ragents/web/AccessContext";
|
|
@@ -181,7 +181,7 @@ function RunPanel({ surfaceElements, cardSections, navigation, renderChat, sessi
|
|
|
181
181
|
/>}
|
|
182
182
|
<section
|
|
183
183
|
aria-label="Chat"
|
|
184
|
-
className={cn(chatLayoutClass[layout], layout === "floating" && "[&:not([data-expanded])_[data-slot=card-sections]]:hidden [&[data-sheet-resizing]_[data-slot=card-sections]]:hidden")}
|
|
184
|
+
className={cn(chatLayoutClass[layout], layout === "floating" && "[&_[data-chat=composer]]:[--qsl-panel-height:var(--sheet-available-height)] [&:not([data-expanded])_[data-slot=card-sections]]:hidden [&[data-sheet-resizing]_[data-slot=card-sections]]:hidden")}
|
|
185
185
|
data-expanded={layout === "floating" && sheet.expanded ? true : undefined}
|
|
186
186
|
data-sheet-resizing={resize.height !== undefined || undefined}
|
|
187
187
|
data-view={primarySelected ? "chat" : "actor-chat"}
|
|
@@ -190,7 +190,7 @@ function RunPanel({ surfaceElements, cardSections, navigation, renderChat, sessi
|
|
|
190
190
|
onPointerEnter={layout === "floating" ? (event) => { if (event.pointerType === "mouse") sheet.openLater(); } : undefined}
|
|
191
191
|
onPointerLeave={layout === "floating" ? () => sheet.closeLater(settings.closeDelay) : undefined}
|
|
192
192
|
ref={sheetRef}
|
|
193
|
-
style={layout === "floating" ? { height: resize.height ?? (sheet.expanded ? expandedHeight : geometry.minimum), transition: resize.height !== undefined ? "none" : undefined } : undefined}
|
|
193
|
+
style={layout === "floating" ? { height: resize.height ?? (sheet.expanded ? expandedHeight : geometry.minimum), transition: resize.height !== undefined ? "none" : undefined, "--sheet-available-height": `${geometry.available}px` } as CSSProperties : undefined}
|
|
194
194
|
>
|
|
195
195
|
{layout === "floating" && <button
|
|
196
196
|
aria-expanded={sheet.expanded}
|
|
@@ -307,9 +307,9 @@ function useSheet(active: boolean, sheetRef: RefObject<HTMLElement | null>, grab
|
|
|
307
307
|
return { expanded, held, open, openLater, closeLater, close, toggle, pauseResize, finishResize };
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
/** Die Höhe des zugeschobenen Sheets: Griff, Statuszeile und Eingabe, gemessen an der Eingabe des Chat-Rahmens. */
|
|
310
|
+
/** Die Höhe des zugeschobenen Sheets: Griff, Statuszeile und Eingabe samt offener Aktionen darüber, gemessen an der Eingabe des Chat-Rahmens. */
|
|
311
311
|
function useSheetPeek(active: boolean, sheetRef: RefObject<HTMLElement | null>, grabRef: RefObject<HTMLButtonElement | null>, statusRef: RefObject<HTMLButtonElement | null>, actorId: string | undefined) {
|
|
312
|
-
const [geometry, setGeometry] = useState({ minimum: 0, maximum: 0, scale: 1 });
|
|
312
|
+
const [geometry, setGeometry] = useState({ minimum: 0, maximum: 0, available: 0, scale: 1 });
|
|
313
313
|
useLayoutEffect(() => {
|
|
314
314
|
if (!active) return;
|
|
315
315
|
const sheet = sheetRef.current;
|
|
@@ -325,8 +325,9 @@ function useSheetPeek(active: boolean, sheetRef: RefObject<HTMLElement | null>,
|
|
|
325
325
|
const scale = width / Number.parseFloat(style.width);
|
|
326
326
|
const minimum = Math.ceil((grab.getBoundingClientRect().height + (status?.getBoundingClientRect().height ?? 0)
|
|
327
327
|
+ composer.getBoundingClientRect().height) / scale + Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth));
|
|
328
|
-
const
|
|
329
|
-
|
|
328
|
+
const available = Math.floor(container.getBoundingClientRect().height / scale * 0.9);
|
|
329
|
+
const maximum = Math.max(minimum, available);
|
|
330
|
+
setGeometry((previous) => previous.minimum === minimum && previous.maximum === maximum && previous.available === available && previous.scale === scale ? previous : { minimum, maximum, available, scale });
|
|
330
331
|
};
|
|
331
332
|
const observer = new ResizeObserver(measure);
|
|
332
333
|
observer.observe(composer);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Message } from "quassel/events";
|
|
2
2
|
|
|
3
|
-
/** Die eine Zeile des zugeschobenen Sheets: was der Chat gerade tut oder zuletzt gesagt hat. */
|
|
3
|
+
/** Die eine Zeile des zugeschobenen Sheets: was der Chat gerade tut oder zuletzt gesagt hat; offene Aktionen selbst stehen im Dock darunter. */
|
|
4
4
|
export interface SheetStatus {
|
|
5
5
|
text: string;
|
|
6
6
|
kind: "idle" | "working" | "waiting";
|
|
@@ -16,8 +16,8 @@ const senderOf = (message: Message, handle: string): string =>
|
|
|
16
16
|
|
|
17
17
|
export function sheetStatus(messages: readonly Message[], working: boolean, handle: string): SheetStatus {
|
|
18
18
|
const last = messages[messages.length - 1];
|
|
19
|
-
const waiting =
|
|
20
|
-
if (waiting) return { kind: "waiting", text:
|
|
19
|
+
const waiting = messages.filter((message) => message.role === "action" && message.action?.status === undefined).length;
|
|
20
|
+
if (waiting > 0) return { kind: "waiting", text: waiting === 1 ? "Wartet auf Eingabe" : `Wartet auf ${waiting} Eingaben` };
|
|
21
21
|
if (working) {
|
|
22
22
|
if (last?.role === "thinking" && !last.closed) return { kind: "working", text: `@${handle} denkt ...` };
|
|
23
23
|
if (last?.role === "tool" && last.tool && last.tool.result === undefined) return { kind: "working", text: `@${handle} nutzt ${last.tool.name} ...` };
|
|
@@ -3,7 +3,7 @@ import type { Static } from "typebox";
|
|
|
3
3
|
import { DomainError, implement, type AccessContext, type JsonValue, type MethodContribution, type PluginHost } from "@ragents/engine";
|
|
4
4
|
import { methodReference, openRpcDocument, type ApiAuthentication } from "@ragents/host/api/reference.js";
|
|
5
5
|
import type { RunManagement } from "@ragents/host/ragents/global-chat.js";
|
|
6
|
-
import { overseerContracts } from "../contract.js";
|
|
6
|
+
import { OVERSEER_PLUGIN_ID, overseerContracts } from "../contract.js";
|
|
7
7
|
import { coordinatorRunIdOf } from "./coordinator.js";
|
|
8
8
|
import type { RunDirectory } from "./run-directory.js";
|
|
9
9
|
|
|
@@ -36,7 +36,7 @@ const create = async (context: ManagementContext, body: CreateInput, access: Acc
|
|
|
36
36
|
const runId = await (async () => {
|
|
37
37
|
if ("packageDirectory" in body) {
|
|
38
38
|
if (!path.isAbsolute(body.packageDirectory)) throw new DomainError("invalid-package-directory", "packageDirectory muss ein absoluter Serverpfad sein", 400);
|
|
39
|
-
return context.management().create({ ...common, kind: "package", directory: body.packageDirectory, input: (body.input ?? null) as JsonValue });
|
|
39
|
+
return context.management().create({ ...common, kind: "package", directory: body.packageDirectory, input: (body.input ?? null) as JsonValue, owner: OVERSEER_PLUGIN_ID });
|
|
40
40
|
}
|
|
41
41
|
if ("script" in body) {
|
|
42
42
|
const entries = context.root.startEntries.describe().filter((entry) => entry.action === "script");
|
|
@@ -55,7 +55,8 @@ const checkSections = (source: string, raw: Record<string, unknown>, pluginIds:
|
|
|
55
55
|
if (isEnvironmentReference(entry) || isProvisionedReference(entry)) continue;
|
|
56
56
|
if (SECRET_KEY_PATTERN.test(key)) throw new Error(`${source}: ${section}.${key} ist ein Secret und muss als env("...") stehen`);
|
|
57
57
|
const valid = typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean"
|
|
58
|
-
|| (Array.isArray(entry) && entry.every((item) => typeof item === "string"))
|
|
58
|
+
|| (Array.isArray(entry) && entry.every((item) => typeof item === "string"))
|
|
59
|
+
|| (Array.isArray(entry) && entry.every((item) => !!item && typeof item === "object" && !Array.isArray(item)));
|
|
59
60
|
if (!valid) throw new Error(`${source}: ${section}.${key} hat keinen gültigen Wert`);
|
|
60
61
|
if (typeof entry === "string" && /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(entry)) throw new Error(`${source}: ${section}.${key} verweist als Text auf die Umgebung; env("...") verwenden`);
|
|
61
62
|
}
|
|
@@ -2,20 +2,30 @@ import { realpath } from "node:fs/promises";
|
|
|
2
2
|
import { homedir, tmpdir } from "node:os";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import {
|
|
5
|
+
EXECUTOR_CONTRIBUTION_FILE,
|
|
5
6
|
WORKSPACE_EXECUTOR_VERSION,
|
|
6
7
|
containsWorkspacePath,
|
|
8
|
+
hostDataDirectory,
|
|
9
|
+
loadExecutorContribution,
|
|
10
|
+
pluginToolsDirectory,
|
|
11
|
+
prepareExecutorContribution,
|
|
12
|
+
ripgrepAvailable,
|
|
7
13
|
WorkspaceOperationError,
|
|
8
14
|
WorkspaceOperationExecutor,
|
|
9
15
|
workspaceDataDirectory,
|
|
10
16
|
workspaceExecutorModules,
|
|
11
17
|
workspaceProcessContext,
|
|
18
|
+
type ExecutorContributionStand,
|
|
19
|
+
type PreparedExecutorContribution,
|
|
12
20
|
type WorkspaceProcessContext,
|
|
13
21
|
} from "@ragents/workspace-executor";
|
|
14
22
|
import type { RpcClient } from "../../../apps/web/src/rpc/client";
|
|
15
23
|
import type { OperationInput } from "../../../packages/ragents/src/rpc/contract";
|
|
16
24
|
import type { RpcHandlerContext } from "../../../packages/ragents/src/rpc/peer";
|
|
25
|
+
import { RPC_ERROR_CODES, RpcError } from "../../../packages/ragents/src/rpc/protocol";
|
|
17
26
|
import { DomainError } from "../../../packages/ragents/src/runtime/domain-error";
|
|
18
27
|
import {
|
|
28
|
+
PLUGIN_ID_PATTERN,
|
|
19
29
|
WORKSPACE_CLIENT_STOP_OPERATION,
|
|
20
30
|
workspaceClientContracts,
|
|
21
31
|
workspaceContracts,
|
|
@@ -23,12 +33,14 @@ import {
|
|
|
23
33
|
type WorkspaceClientDescription,
|
|
24
34
|
} from "../contract";
|
|
25
35
|
|
|
36
|
+
/** mismatch: Arbeitsplatz und Server tragen einen anderen Stand von Executor oder Bundles; nur ein Update einer Seite behebt es. */
|
|
26
37
|
export type WorkspaceClientStatus =
|
|
27
38
|
| { kind: "idle" }
|
|
28
39
|
| { kind: "registered" }
|
|
29
|
-
| { kind: "failed"; message: string };
|
|
40
|
+
| { kind: "failed"; message: string; mismatch: boolean };
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
/** Was der Arbeitsplatz über sich sagt; ob seine Bash rg findet, ermittelt er selbst bei jeder Anmeldung. */
|
|
43
|
+
export interface WorkspaceClientIdentity extends Omit<WorkspaceClientDescription, "ripgrep"> {
|
|
32
44
|
id: string;
|
|
33
45
|
}
|
|
34
46
|
|
|
@@ -41,10 +53,12 @@ export interface WorkspaceClientExecution {
|
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
export interface WorkspaceClientOptions {
|
|
44
|
-
/** Die Host-Wurzel dieses Rechners, aus der
|
|
56
|
+
/** Die Host-Wurzel dieses Rechners mit den Bundles, aus denen der Executor seine Beiträge lädt; sie kann erst später entstehen. */
|
|
45
57
|
hostRoot: () => string | undefined;
|
|
46
58
|
/** Die Bash dieses Rechners für das Werkzeug bash; unter Windows die mitgebrachte und Pflicht, sonst ohne Angabe die des Systems. */
|
|
47
59
|
bash?: string | undefined;
|
|
60
|
+
/** Das mitgebrachte rg, dessen Ordner die Bash vorn im PATH hat; ohne Angabe gilt eines im PATH dieses Rechners. */
|
|
61
|
+
rg?: string | undefined;
|
|
48
62
|
onExecuted?: (execution: WorkspaceClientExecution) => void;
|
|
49
63
|
}
|
|
50
64
|
|
|
@@ -63,6 +77,18 @@ const SIGN_OFF_TIMEOUT_MS = 3_000;
|
|
|
63
77
|
|
|
64
78
|
const messageOf = (cause: unknown): string => cause instanceof Error ? cause.message : String(cause);
|
|
65
79
|
|
|
80
|
+
/** Ein Stand dieses Arbeitsplatzes, der nicht zu dem des Servers passt. */
|
|
81
|
+
class StandMismatch extends Error {}
|
|
82
|
+
|
|
83
|
+
const STAND_REFUSALS = ["workspace-executor-version", "workspace-executor-contributions"];
|
|
84
|
+
|
|
85
|
+
/** Nach dem Fachcode statt nach der Klasse: RpcError kann in einem Prozess zweimal geladen sein. */
|
|
86
|
+
const isStandMismatch = (cause: unknown): boolean => {
|
|
87
|
+
if (cause instanceof StandMismatch) return true;
|
|
88
|
+
const code = typeof cause === "object" && cause !== null ? (cause as { domainCode?: unknown }).domainCode : undefined;
|
|
89
|
+
return typeof code === "string" && STAND_REFUSALS.includes(code);
|
|
90
|
+
};
|
|
91
|
+
|
|
66
92
|
/** Der echte Pfad des nächsten vorhandenen Elternordners, um die noch fehlenden Namen ergänzt. */
|
|
67
93
|
const realPathOf = async (target: string): Promise<string> => {
|
|
68
94
|
try {
|
|
@@ -93,9 +119,44 @@ const contextOf = (runs: ReadonlyMap<string, WorkspaceProcessContext>, runId: st
|
|
|
93
119
|
return Promise.resolve(context);
|
|
94
120
|
};
|
|
95
121
|
|
|
122
|
+
const sameContributions = (left: readonly ExecutorContributionStand[], right: readonly ExecutorContributionStand[]): boolean =>
|
|
123
|
+
left.length === right.length && left.every((entry) => right.some((other) => other.plugin === entry.plugin && other.stand === entry.stand));
|
|
124
|
+
|
|
125
|
+
/** Die Bundles eines Arbeitsplatzes sind die eingebauten seines Hosts; ein Plugin, das dort fehlt, kann er nicht ausführen. */
|
|
126
|
+
const contributionFileOf = (hostRoot: string, plugin: string): string => join(hostRoot, "bundles", plugin, EXECUTOR_CONTRIBUTION_FILE);
|
|
127
|
+
|
|
128
|
+
/** Lädt die Beiträge, die der Server verlangt, aus den Bundles des Hosts dieser Maschine; ein fehlendes Bundle oder ein anderer Stand ist ein Fehler mit Ursache. */
|
|
129
|
+
const loadedContributions = async (
|
|
130
|
+
hostRoot: string | undefined,
|
|
131
|
+
wanted: readonly ExecutorContributionStand[],
|
|
132
|
+
): Promise<readonly PreparedExecutorContribution[]> => {
|
|
133
|
+
if (wanted.length === 0) return [];
|
|
134
|
+
if (!hostRoot) {
|
|
135
|
+
throw new Error(`Der Server verlangt die Executor-Beiträge von ${wanted.map(({ plugin }) => plugin).join(", ")}; dieser Arbeitsplatz `
|
|
136
|
+
+ "kennt keinen Host, aus dessen Bundles er sie laden könnte. Er bekommt ihn mit der ersten Verbindung zu einem verteilenden "
|
|
137
|
+
+ "Server oder über die Einstellung ragents.hostPath");
|
|
138
|
+
}
|
|
139
|
+
return Promise.all(wanted.map(async ({ plugin, stand }) => {
|
|
140
|
+
if (!PLUGIN_ID_PATTERN.test(plugin)) throw new Error(`Der Server nennt ${plugin} als Plugin; das ist keine Kennung eines Plugins`);
|
|
141
|
+
const loaded = await loadExecutorContribution(plugin, contributionFileOf(hostRoot, plugin), stand).catch((cause: unknown) => {
|
|
142
|
+
throw new StandMismatch(`${messageOf(cause)}. Der Host dieses Arbeitsplatzes (${hostRoot}) muss dieselben Bundles tragen wie der Server: `
|
|
143
|
+
+ "die RAgents-Erweiterung beziehungsweise das Paket @schlenkr/ragents in der Fassung des Servers, im Checkout pnpm build:plugins");
|
|
144
|
+
});
|
|
145
|
+
return prepareExecutorContribution(loaded, pluginToolsDirectory(hostDataDirectory(), plugin));
|
|
146
|
+
}));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** Ein Executor aus den Beiträgen, die der Server bei der Anmeldung verlangt hat. */
|
|
150
|
+
interface BuiltExecutor {
|
|
151
|
+
readonly executor: WorkspaceOperationExecutor;
|
|
152
|
+
readonly contributions: readonly ExecutorContributionStand[];
|
|
153
|
+
}
|
|
154
|
+
|
|
96
155
|
/** Was eine Anmeldung auf diesem Rechner hält: ihren eigenen Executor, die Aufträge ihrer Runs und ihre Handler an der Nachrichtenschicht. */
|
|
97
156
|
interface Attachment {
|
|
98
|
-
|
|
157
|
+
/** Entsteht mit der Antwort des Servers, welche Beiträge er verlangt; verlangt er nach einer Neuverbindung andere, ersetzt ein neuer ihn. */
|
|
158
|
+
built: BuiltExecutor | undefined;
|
|
159
|
+
readonly runsDirectory: string;
|
|
99
160
|
readonly runs: Map<string, WorkspaceProcessContext>;
|
|
100
161
|
readonly release: () => void;
|
|
101
162
|
}
|
|
@@ -148,7 +209,7 @@ export class WorkspaceClient {
|
|
|
148
209
|
try {
|
|
149
210
|
await this.#connected();
|
|
150
211
|
} catch (cause) {
|
|
151
|
-
if (attachment === this.#attachment) this.#set({ kind: "failed", message: messageOf(cause) });
|
|
212
|
+
if (attachment === this.#attachment) this.#set({ kind: "failed", message: messageOf(cause), mismatch: false });
|
|
152
213
|
return;
|
|
153
214
|
}
|
|
154
215
|
if (attachment === this.#attachment) await this.#announce();
|
|
@@ -172,7 +233,7 @@ export class WorkspaceClient {
|
|
|
172
233
|
this.#set({ kind: "idle" });
|
|
173
234
|
const signingOff = Promise.allSettled([this.#signingOff, this.#announcing]).then(() => this.#signOff());
|
|
174
235
|
this.#signingOff = signingOff;
|
|
175
|
-
await Promise.all([signingOff, attachment?.executor.shutdown()]);
|
|
236
|
+
await Promise.all([signingOff, attachment?.built?.executor.shutdown()]);
|
|
176
237
|
}
|
|
177
238
|
|
|
178
239
|
/** Der Arbeitsplatz bindet immer sich selbst; wo der Server läuft, ändert daran nichts. */
|
|
@@ -183,19 +244,44 @@ export class WorkspaceClient {
|
|
|
183
244
|
/** Jede Anmeldung baut ihren eigenen Executor; einer, den eine Abmeldung beendet hat, wird nie wieder benutzt. */
|
|
184
245
|
#attach(): Attachment {
|
|
185
246
|
if (this.#attachment) return this.#attachment;
|
|
186
|
-
const runs = new Map<string, WorkspaceProcessContext>();
|
|
187
|
-
const runsDirectory = this.#identity.runsDirectory;
|
|
188
|
-
const executor = new WorkspaceOperationExecutor({
|
|
189
|
-
contextFor: (runId) => contextOf(runs, runId),
|
|
190
|
-
modules: workspaceExecutorModules({ runFolder: (runId) => runFolderOf(runsDirectory, runId) }),
|
|
191
|
-
});
|
|
192
247
|
const rpc = this.transport.rpc;
|
|
193
|
-
const releases = [
|
|
194
|
-
|
|
248
|
+
const releases: Array<() => void> = [];
|
|
249
|
+
const attachment: Attachment = {
|
|
250
|
+
built: undefined,
|
|
251
|
+
runsDirectory: this.#identity.runsDirectory,
|
|
252
|
+
runs: new Map(),
|
|
253
|
+
release: () => { for (const release of releases) release(); },
|
|
254
|
+
};
|
|
255
|
+
releases.push(
|
|
256
|
+
rpc.handle(workspaceClientContracts.execute, (input, context) => this.#execute(attachment, input, context)),
|
|
195
257
|
rpc.onConnected(() => void this.#announce()),
|
|
196
|
-
|
|
197
|
-
this.#attachment =
|
|
198
|
-
return
|
|
258
|
+
);
|
|
259
|
+
this.#attachment = attachment;
|
|
260
|
+
return attachment;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Behält den Executor, solange der Server dieselben Beiträge verlangt; sonst baut es einen neuen aus den Bundles dieses Rechners und beendet den alten. */
|
|
264
|
+
async #built(attachment: Attachment, wanted: readonly ExecutorContributionStand[]): Promise<BuiltExecutor> {
|
|
265
|
+
const current = attachment.built;
|
|
266
|
+
if (current && sameContributions(current.contributions, wanted)) return current;
|
|
267
|
+
const contributions = await loadedContributions(this.options.hostRoot(), wanted);
|
|
268
|
+
const built: BuiltExecutor = {
|
|
269
|
+
executor: new WorkspaceOperationExecutor({
|
|
270
|
+
contextFor: (runId) => contextOf(attachment.runs, runId),
|
|
271
|
+
modules: workspaceExecutorModules({
|
|
272
|
+
runFolder: (runId) => runFolderOf(attachment.runsDirectory, runId),
|
|
273
|
+
contributions: contributions.map((contribution) => contribution.parts),
|
|
274
|
+
}),
|
|
275
|
+
}),
|
|
276
|
+
contributions: contributions.map(({ plugin, stand }) => ({ plugin, stand })),
|
|
277
|
+
};
|
|
278
|
+
if (attachment !== this.#attachment) {
|
|
279
|
+
await built.executor.shutdown();
|
|
280
|
+
throw new Error("Der Arbeitsplatz hat sich inzwischen abgemeldet");
|
|
281
|
+
}
|
|
282
|
+
attachment.built = built;
|
|
283
|
+
await current?.executor.shutdown();
|
|
284
|
+
return built;
|
|
199
285
|
}
|
|
200
286
|
|
|
201
287
|
/** Wartet auf den Ereignisstrom; ohne ihn nimmt der Server die Anmeldung nicht an. */
|
|
@@ -219,10 +305,20 @@ export class WorkspaceClient {
|
|
|
219
305
|
return this.#announcing;
|
|
220
306
|
}
|
|
221
307
|
|
|
222
|
-
/**
|
|
308
|
+
/** Erst die Beiträge, die der Server verlangt, dann die Anmeldung mit dem Executor daraus; eine Antwort nach einer Abmeldung ändert den Zustand nicht mehr. */
|
|
223
309
|
async #send(): Promise<void> {
|
|
224
310
|
const attachment = this.#attachment;
|
|
311
|
+
if (!attachment) return;
|
|
225
312
|
try {
|
|
313
|
+
const wanted = await this.transport.rpc.call(workspaceContracts.clients.contributions, {
|
|
314
|
+
label: this.#identity.label,
|
|
315
|
+
executor: WORKSPACE_EXECUTOR_VERSION,
|
|
316
|
+
}, { timeoutMs: REGISTER_TIMEOUT_MS }).catch((cause: unknown) => {
|
|
317
|
+
if (!(cause instanceof RpcError) || cause.code !== RPC_ERROR_CODES.methodNotFound) throw cause;
|
|
318
|
+
throw new StandMismatch(`Der Server kennt ${workspaceContracts.clients.contributions.id} nicht; er ist älter als dieser Arbeitsplatz `
|
|
319
|
+
+ `mit dem Executor ${WORKSPACE_EXECUTOR_VERSION}. Den Server auf die Fassung des Arbeitsplatzes aktualisieren.`);
|
|
320
|
+
});
|
|
321
|
+
const { contributions } = await this.#built(attachment, wanted);
|
|
226
322
|
await this.transport.rpc.call(workspaceContracts.clients.register, {
|
|
227
323
|
id: this.#identity.id,
|
|
228
324
|
label: this.#identity.label,
|
|
@@ -230,11 +326,13 @@ export class WorkspaceClient {
|
|
|
230
326
|
platform: this.#identity.platform,
|
|
231
327
|
folders: [...this.#identity.folders],
|
|
232
328
|
runsDirectory: this.#identity.runsDirectory,
|
|
329
|
+
ripgrep: ripgrepAvailable(this.options.rg, process.env),
|
|
233
330
|
executor: WORKSPACE_EXECUTOR_VERSION,
|
|
331
|
+
contributions: contributions.map((contribution) => ({ ...contribution })),
|
|
234
332
|
}, { timeoutMs: REGISTER_TIMEOUT_MS });
|
|
235
333
|
if (attachment === this.#attachment) this.#set({ kind: "registered" });
|
|
236
334
|
} catch (cause) {
|
|
237
|
-
if (attachment === this.#attachment) this.#set({ kind: "failed", message: messageOf(cause) });
|
|
335
|
+
if (attachment === this.#attachment) this.#set({ kind: "failed", message: messageOf(cause), mismatch: isStandMismatch(cause) });
|
|
238
336
|
}
|
|
239
337
|
}
|
|
240
338
|
|
|
@@ -242,22 +340,19 @@ export class WorkspaceClient {
|
|
|
242
340
|
try {
|
|
243
341
|
await this.transport.rpc.call(workspaceContracts.clients.unregister, { id: this.id }, { timeoutMs: SIGN_OFF_TIMEOUT_MS });
|
|
244
342
|
} catch (cause) {
|
|
245
|
-
this.#set({ kind: "failed", message: messageOf(cause) });
|
|
343
|
+
this.#set({ kind: "failed", message: messageOf(cause), mismatch: false });
|
|
246
344
|
}
|
|
247
345
|
}
|
|
248
346
|
|
|
249
347
|
/** Der Entwickler arbeitet auf seinem Arbeitsplatz mit seinen eigenen Zugangsdaten und seiner ganzen Umgebung; HOME bleibt sein Home. */
|
|
250
|
-
async #execute(
|
|
251
|
-
|
|
252
|
-
runs: Map<string, WorkspaceProcessContext>,
|
|
253
|
-
input: ExecuteInput,
|
|
254
|
-
context: RpcHandlerContext,
|
|
255
|
-
): Promise<{ value: unknown }> {
|
|
256
|
-
if (input.operation === WORKSPACE_CLIENT_STOP_OPERATION) return this.#stop(executor, runs, input.runId);
|
|
348
|
+
async #execute(attachment: Attachment, input: ExecuteInput, context: RpcHandlerContext): Promise<{ value: unknown }> {
|
|
349
|
+
if (input.operation === WORKSPACE_CLIENT_STOP_OPERATION) return this.#stop(attachment, input.runId);
|
|
257
350
|
const cwd = await this.#inside(input.cwd, input.runId);
|
|
258
351
|
const root = await realPathOf(cwd);
|
|
259
|
-
if (
|
|
260
|
-
|
|
352
|
+
if (attachment !== this.#attachment) throw new Error("Der Arbeitsplatz hat sich inzwischen abgemeldet");
|
|
353
|
+
const executor = attachment.built?.executor;
|
|
354
|
+
if (!executor) throw new Error("Der Arbeitsplatz ist beim Server noch nicht angemeldet");
|
|
355
|
+
attachment.runs.set(input.runId, workspaceProcessContext({
|
|
261
356
|
runId: input.runId,
|
|
262
357
|
cwd,
|
|
263
358
|
root,
|
|
@@ -267,6 +362,7 @@ export class WorkspaceClient {
|
|
|
267
362
|
additions: input.env,
|
|
268
363
|
baseEnvironment: "inherited",
|
|
269
364
|
...(this.options.bash === undefined ? {} : { bash: this.options.bash }),
|
|
365
|
+
...(this.options.rg === undefined ? {} : { rg: this.options.rg }),
|
|
270
366
|
}));
|
|
271
367
|
const startedAt = Date.now();
|
|
272
368
|
let failure: string | undefined;
|
|
@@ -293,11 +389,11 @@ export class WorkspaceClient {
|
|
|
293
389
|
}
|
|
294
390
|
|
|
295
391
|
/** Der Stopp braucht keinen Ordner: er gibt frei, was der Run hält, auch wenn sein Ordner nicht mehr angeboten wird. */
|
|
296
|
-
async #stop(
|
|
392
|
+
async #stop(attachment: Attachment, runId: string): Promise<{ value: null }> {
|
|
297
393
|
try {
|
|
298
|
-
await executor.stopRun(runId);
|
|
394
|
+
await attachment.built?.executor.stopRun(runId);
|
|
299
395
|
} finally {
|
|
300
|
-
runs.delete(runId);
|
|
396
|
+
attachment.runs.delete(runId);
|
|
301
397
|
}
|
|
302
398
|
return { value: null };
|
|
303
399
|
}
|
|
@@ -66,6 +66,8 @@ export interface WorkspaceClientDescription {
|
|
|
66
66
|
folders: string[];
|
|
67
67
|
/** Wo der Arbeitsplatz die neuen Ordner je Run anlegt, je Run ein Unterordner mit dessen Kennung. */
|
|
68
68
|
runsDirectory: string;
|
|
69
|
+
/** Ob die Bash des Arbeitsplatzes rg findet; der Prompt richtet die Suche danach. */
|
|
70
|
+
ripgrep: boolean;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
export interface WorkspaceClientInfo extends WorkspaceClientDescription {
|
|
@@ -95,12 +97,21 @@ const clientId = Type.String({ pattern: "^[A-Za-z0-9_-]{8,64}$", description: "S
|
|
|
95
97
|
|
|
96
98
|
const executorVersion = Type.String({ minLength: 1, maxLength: 64, description: "Stand des Executors, den der Arbeitsplatz mitbringt" });
|
|
97
99
|
|
|
100
|
+
/** Die Kennung eines Plugins; ein Arbeitsplatz sucht dessen Bundle unter genau diesem Namen, ein Pfad kann darin nicht stecken. */
|
|
101
|
+
export const PLUGIN_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
102
|
+
|
|
103
|
+
const executorContributions = Type.Array(Type.Object({
|
|
104
|
+
plugin: Type.String({ pattern: PLUGIN_ID_PATTERN.source, maxLength: 128, description: "Kennung des Plugins" }),
|
|
105
|
+
stand: Type.String({ pattern: "^[0-9a-f]{64}$", description: "SHA-256 der Datei des Beitrags" }),
|
|
106
|
+
}, { additionalProperties: false }), { maxItems: 64, description: "Die Beiträge der Plugins zum Executor, in der Reihenfolge der Pluginliste des Servers" });
|
|
107
|
+
|
|
98
108
|
const clientDescription = {
|
|
99
109
|
label: Type.String({ minLength: 1, maxLength: 120 }),
|
|
100
110
|
hostname: Type.String({ minLength: 1 }),
|
|
101
111
|
platform: Type.String({ minLength: 1 }),
|
|
102
112
|
folders: Type.Array(Type.String({ minLength: 1 }), { maxItems: 32 }),
|
|
103
113
|
runsDirectory: Type.String({ minLength: 1, description: "Absoluter Ordner, unter dem der Arbeitsplatz die neuen Ordner je Run anlegt" }),
|
|
114
|
+
ripgrep: Type.Boolean({ description: "Ob die Bash des Arbeitsplatzes rg findet" }),
|
|
104
115
|
};
|
|
105
116
|
|
|
106
117
|
export const clientInfoSchema = Type.Object({
|
|
@@ -108,6 +119,19 @@ export const clientInfoSchema = Type.Object({
|
|
|
108
119
|
...clientDescription,
|
|
109
120
|
}, { additionalProperties: false });
|
|
110
121
|
|
|
122
|
+
/** Die Anmeldung, wie ein Arbeitsplatz mit dem Executor des Servers sie sendet. */
|
|
123
|
+
export const clientRegistrationSchema = Type.Object({
|
|
124
|
+
id: clientId,
|
|
125
|
+
...clientDescription,
|
|
126
|
+
executor: executorVersion,
|
|
127
|
+
contributions: executorContributions,
|
|
128
|
+
}, { additionalProperties: false });
|
|
129
|
+
|
|
130
|
+
/** Ein Arbeitsplatz mit einem anderen Executor kennt die Form dieses Stands nicht; der Server lehnt ihn an seinem Stand ab, nicht an der Form. */
|
|
131
|
+
const otherExecutorRegistration = Type.Object({ label: clientDescription.label, executor: executorVersion }, {
|
|
132
|
+
description: "Anmeldung mit einem anderen Executor-Stand in beliebiger Form; der Server lehnt sie mit workspace-executor-version ab",
|
|
133
|
+
});
|
|
134
|
+
|
|
111
135
|
const absolutePath = Type.String({ minLength: 1, description: "Absoluter Pfad im gebundenen Ordner" });
|
|
112
136
|
|
|
113
137
|
/** Keine Operation eines Moduls: gibt frei, was der Executor des Arbeitsplatzes für den Run hält. */
|
|
@@ -172,11 +196,18 @@ export const workspaceContracts = {
|
|
|
172
196
|
input: Type.Object({}, { additionalProperties: false }),
|
|
173
197
|
result: Type.Array(clientInfoSchema),
|
|
174
198
|
}),
|
|
199
|
+
contributions: defineOperation({
|
|
200
|
+
id: "ragents.workspace.clients.contributions",
|
|
201
|
+
description: "Die Beiträge der Plugins zum Executor, die ein Arbeitsplatz vor der Anmeldung lädt und mit ihr zurückmeldet. Ein Arbeitsplatz mit anderem Executor-Stand scheitert an seinem Stand; weitere Felder zählen nicht.",
|
|
202
|
+
rights: ["runs.write"],
|
|
203
|
+
input: Type.Object({ label: clientDescription.label, executor: executorVersion }),
|
|
204
|
+
result: executorContributions,
|
|
205
|
+
}),
|
|
175
206
|
register: defineOperation({
|
|
176
207
|
id: "ragents.workspace.clients.register",
|
|
177
|
-
description: "Einen Arbeitsplatz anmelden oder seine Ordner erneuern; die Verbindung dieser Anfrage wird sein Rückweg und braucht einen Ereignisstrom.",
|
|
208
|
+
description: "Einen Arbeitsplatz anmelden oder seine Ordner erneuern; die Verbindung dieser Anfrage wird sein Rückweg und braucht einen Ereignisstrom. Ein Arbeitsplatz mit anderem Executor-Stand scheitert an seinem Stand, bevor die übrige Form zählt.",
|
|
178
209
|
rights: ["runs.write"],
|
|
179
|
-
input: Type.
|
|
210
|
+
input: Type.Union([clientRegistrationSchema, otherExecutorRegistration]),
|
|
180
211
|
result: clientInfoSchema,
|
|
181
212
|
}),
|
|
182
213
|
unregister: defineOperation({
|