@schlenkr/ragents 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
- package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
- package/apps/server/src/plugin-support/run-scripts.ts +4 -1
- package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
- package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
- package/apps/server/src/profile-target.ts +25 -0
- package/apps/server/src/web-build.ts +82 -0
- package/apps/server/tsconfig.json +38 -0
- package/apps/web/column.html +12 -0
- package/apps/web/index.html +12 -0
- package/apps/web/src/App.tsx +0 -10
- package/apps/web/src/PluginChat.tsx +8 -8
- package/apps/web/src/PluginRegistry.tsx +0 -12
- package/apps/web/src/StartSurface.tsx +2 -2
- package/apps/web/src/ThemeSettings.tsx +1 -1
- package/apps/web/src/column/ColumnApp.tsx +83 -7
- package/apps/web/src/column/clipboard.ts +64 -0
- package/apps/web/src/column/host-contract.ts +10 -0
- package/apps/web/src/column.tsx +2 -0
- package/apps/web/src/panel/LoginForm.tsx +9 -3
- package/apps/web/src/panel/OverviewPage.tsx +187 -62
- package/apps/web/src/panel/PanelDialogs.tsx +115 -0
- package/apps/web/src/panel/PanelPage.tsx +1 -1
- package/apps/web/src/panel/SettingsPage.tsx +75 -79
- package/apps/web/src/panel/contract.ts +17 -9
- package/apps/web/src/panel/target-state.ts +27 -2
- package/apps/web/src/ui/theme.css +4 -0
- package/apps/web/tsconfig.json +41 -0
- package/apps/web/vite.config.ts +140 -0
- package/package.json +6 -2
- package/packages/ragents/src/plugin-host.ts +5 -2
- package/packages/ragents/src/plugin-types.ts +3 -2
- package/packages/workspace-executor/src/context.ts +4 -0
- package/packages/workspace-executor/src/executor.ts +9 -2
- package/packages/workspace-executor/src/index.ts +2 -0
- package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
- package/packages/workspace-executor/src/language-server/contract.ts +7 -3
- package/packages/workspace-executor/src/language-server/host.ts +275 -138
- package/plugins/ragents.actor-programs/contract.ts +1 -1
- package/plugins/ragents.actor-programs/prompt.hbs +4 -4
- package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
- package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
- package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
- package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
- package/plugins/ragents.actor-programs/web/api.ts +1 -10
- package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
- package/plugins/ragents.orchestration/canvas.hbs +28 -44
- package/plugins/ragents.orchestration/contract.ts +17 -287
- package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
- package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
- package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
- package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
- package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
- package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
- package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
- package/plugins/ragents.orchestration/web/Center.tsx +28 -463
- package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
- package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
- package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
- package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
- package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
- package/plugins/ragents.orchestration/web/constants.ts +0 -2
- package/plugins/ragents.orchestration/web/index.tsx +0 -6
- package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
- package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
- package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
- package/plugins/ragents.profile-distribution/server/web.ts +3 -27
- package/plugins/ragents.reference/examples.ts +1 -2
- package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
- package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
- package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
- package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
- package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
- package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
- package/plugins/ragents.reference/walkthroughs.ts +14 -14
- package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
- package/scripts/agent/agent-cli.ts +9 -24
- package/scripts/package/build-package.ts +18 -5
- package/scripts/package/publish-package.ts +24 -10
- package/scripts/package/ragents.mjs +3 -2
- package/scripts/remote/connect.ts +16 -5
- package/scripts/remote/start.ts +33 -15
- package/scripts/workspace-client/run-workspace-client.ts +3 -1
- package/apps/web/src/MaterialSettings.tsx +0 -40
- package/apps/web/src/material-settings.ts +0 -104
- package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
- package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
- package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
- package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
- package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
- package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
- package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
- package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
- package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
- package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
- package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
- package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
- package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
|
@@ -58,25 +58,19 @@ export default defineActor(contract, {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
await context.functions.canvas_layout_replace({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
lines: [
|
|
70
|
-
{ from: participants[0]!, to: participants[1]!, arrow: "end", label: "gibt weiter" },
|
|
71
|
-
{ from: participants[1]!, to: participants[2]!, arrow: "end", label: "gibt weiter" },
|
|
72
|
-
{ from: participants[2]!, to: participants[0]!, arrow: "end", label: "gibt weiter" },
|
|
73
|
-
{ from: "shape:thema", to: participants[0]!, style: "dashed", label: "Auftakt" },
|
|
74
|
-
],
|
|
61
|
+
root: {
|
|
62
|
+
direction: "vertical",
|
|
63
|
+
weights: [1, 1],
|
|
64
|
+
children: [
|
|
65
|
+
{ entity: participants[0]! },
|
|
66
|
+
{ direction: "horizontal", weights: [1, 1], children: [{ entity: participants[1]! }, { entity: participants[2]! }] },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
75
69
|
});
|
|
76
70
|
|
|
77
71
|
await context.functions.actor_input({
|
|
78
72
|
actor: "@coordinator",
|
|
79
|
-
content: `Die Runde steht: ${participants.join(", ")} sind aufgestellt, die Fläche ist
|
|
73
|
+
content: `Die Runde steht: ${participants.join(", ")} sind aufgestellt, die Fläche ist aufgeteilt. Thema: "${topic}". `
|
|
80
74
|
+ `Führe genau ${rounds} Gesprächsrunden durch: in jeder Runde ${participants.join(", ")} in dieser Reihenfolge. `
|
|
81
75
|
+ "Jeder erhält die bisherigen Beiträge. Ändere die Fläche nicht. "
|
|
82
76
|
+ "Fasse das Gespräch am Ende in drei Sätzen zusammen.",
|
|
@@ -8,9 +8,11 @@ test("Thema und Rundenzahl steuern Teilnehmer, Fläche und Auftrag", async () =>
|
|
|
8
8
|
await program.onInput!(startInput({ topic: "Teamfrühstück", rounds: 3 }), context);
|
|
9
9
|
assert.deepEqual(calls.filter((call) => call.name === "agent_spawn").map((call) => (call.input as { handle: string }).handle), ["mira", "jon", "ada"]);
|
|
10
10
|
assert.deepEqual(calls.find((call) => call.name === "run_configure")?.input, { title: "Gesprächsrunde: Teamfrühstück" });
|
|
11
|
-
const layout = calls.find((call) => call.name === "canvas_layout_replace")?.input as {
|
|
12
|
-
assert.
|
|
13
|
-
|
|
11
|
+
const layout = calls.find((call) => call.name === "canvas_layout_replace")?.input as { root: unknown };
|
|
12
|
+
assert.deepEqual(layout.root, { direction: "vertical", weights: [1, 1], children: [
|
|
13
|
+
{ entity: "@mira" },
|
|
14
|
+
{ direction: "horizontal", weights: [1, 1], children: [{ entity: "@jon" }, { entity: "@ada" }] },
|
|
15
|
+
] });
|
|
14
16
|
assert.match(JSON.stringify(calls.at(-1)), /genau 3 Gesprächsrunden/);
|
|
15
17
|
assert.deepEqual(context.state.read(), { built: true });
|
|
16
18
|
const completedCalls = calls.length;
|
|
@@ -38,6 +38,13 @@ const contract = {
|
|
|
38
38
|
const startMarker = "START_LEARNING_AFTERNOON";
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
type Tile = { entity: string } | { direction: "vertical"; weights: [number, number]; children: [Tile, Tile] };
|
|
42
|
+
|
|
43
|
+
function stackedTiles(entities: string[]): Tile {
|
|
44
|
+
const [first, ...rest] = entities;
|
|
45
|
+
return rest.length === 0 ? { entity: first! } : { direction: "vertical", weights: [1, rest.length], children: [{ entity: first! }, stackedTiles(rest)] };
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
function boardWith(helpers: HelperState[]): LearningState {
|
|
42
49
|
const working = helpers.some((helper) => helper.status === "working");
|
|
43
50
|
return { phase: working ? "working" : helpers.some((helper) => helper.status === "error") ? "error" : "complete", helpers };
|
|
@@ -65,10 +72,13 @@ export default defineActor(contract, {
|
|
|
65
72
|
const actor = await context.functions.agent_spawn({ handle: `learning-${helper.id}`, displayName: helper.label, profile: profile.name, prompt, tools: [] });
|
|
66
73
|
return { ...helper, actorId: actor.id };
|
|
67
74
|
}));
|
|
68
|
-
await context.functions.canvas_layout_replace({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
await context.functions.canvas_layout_replace({
|
|
76
|
+
root: {
|
|
77
|
+
direction: "horizontal",
|
|
78
|
+
weights: [1, 1],
|
|
79
|
+
children: [{ entity: `app:@${context.actor.handle}/main` }, stackedTiles(helpers.map((helper) => `@learning-${helper.id}`))],
|
|
80
|
+
},
|
|
81
|
+
});
|
|
72
82
|
await context.functions.run_configure({ title: learningWorkflow.title, primaryActor: `@${context.actor.handle}` });
|
|
73
83
|
context.state.replace({ board: { phase: "ready", helpers } });
|
|
74
84
|
return;
|
|
@@ -37,7 +37,7 @@ export default defineActor(contract, {
|
|
|
37
37
|
try {
|
|
38
38
|
if (!state.status) {
|
|
39
39
|
await context.functions.run_configure({ title: "Wortspiel", primaryActor: context.actor.id });
|
|
40
|
-
await context.functions.canvas_layout_replace({
|
|
40
|
+
await context.functions.canvas_layout_replace({ root: { entity: `app:@${context.actor.handle}/main` } });
|
|
41
41
|
const start = payloadOf(JSON.parse(input.content));
|
|
42
42
|
if (start.input !== null || !start.options || typeof start.options !== "object" || Array.isArray(start.options)
|
|
43
43
|
|| Object.keys(start).some((key) => key !== "input" && key !== "options")) {
|
|
@@ -6,7 +6,7 @@ title: "Gespräch an einen Moderator übergeben"
|
|
|
6
6
|
description: "Zeigt die Übergabe eines laufenden Chats an einen anderen Primary-Actor. Der Moderator übernimmt als direkter Ansprechpartner und hält den Aufgabenfortschritt fest."
|
|
7
7
|
category: "Zusammenarbeit"
|
|
8
8
|
order: 210
|
|
9
|
-
tags: "Rückfragen, To-dos, Anwendungsfall, Konzeptdemo, Agententeams, Primary-Actor
|
|
9
|
+
tags: "Rückfragen, To-dos, Anwendungsfall, Konzeptdemo, Agententeams, Primary-Actor"
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
Ich hätte gern einen Workshop für einen ruhigeren Arbeitstag: Ein Planer bringt zwei Ideen mit, ein skeptischer Gast prüft die Nachteile. Ein Moderator übernimmt die Runde und spricht danach direkt mit mir im Chat, damit ich einen Vorschlag auswählen kann. Zeig die drei auf der Fläche und übergib das Gespräch wirklich an den Moderator. Seine Aufgabenliste zeigt den Fortschritt.
|
|
@@ -2,25 +2,25 @@ export const referenceWalkthroughs = [
|
|
|
2
2
|
{
|
|
3
3
|
id: "mini-app-owner-inspector",
|
|
4
4
|
title: "Den Helfer hinter einer Mini-App öffnen",
|
|
5
|
-
description: "Die Mini-App bleibt
|
|
6
|
-
tags: ["Anwendungsfall", "Konzeptdemo", "
|
|
5
|
+
description: "Die Mini-App bleibt als Kachel sichtbar; ihr Besitzer ist auch ohne eigene Kachel erreichbar.",
|
|
6
|
+
tags: ["Anwendungsfall", "Konzeptdemo", "Kachelfläche"],
|
|
7
7
|
steps: [
|
|
8
|
-
"Im Profil core Sammelboard einrichten öffnen und den Leitfaden abschließen. Erwartung: Die Liste steht auf
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"Die
|
|
8
|
+
"Im Profil core Sammelboard einrichten öffnen und den Leitfaden abschließen. Erwartung: Die Liste steht als Kachel auf der Fläche; ihr Besitzer hat keine eigene Kachel.",
|
|
9
|
+
"In der Kopfzeile Actors öffnen und den Namen des Listenbesitzers wählen. Erwartung: Rechts öffnen sich dessen Chat und Details, ohne die Kachelaufteilung zu ändern.",
|
|
10
|
+
"Den Besitzer aus der Kopfzeile auf die Kachel der Liste ziehen und rechts andocken. Erwartung: Seine Kachel erscheint neben der Liste; die Mini-App bleibt bedienbar und behält ihre Einträge.",
|
|
11
|
+
"Die neue Kachel über das Kreuz in ihrer Kopfzeile wieder entfernen und die App erneut bedienen. Erwartung: Actor und Funktionen arbeiten weiter; sein Name bleibt in der Kopfzeile erreichbar.",
|
|
12
12
|
],
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
id: "personal-
|
|
16
|
-
title: "Eine Gesprächsrunde
|
|
17
|
-
description: "
|
|
18
|
-
tags: ["Anwendungsfall", "Konzeptdemo", "
|
|
15
|
+
id: "personal-tile-arrangement",
|
|
16
|
+
title: "Eine Gesprächsrunde anders aufteilen",
|
|
17
|
+
description: "Kacheln lassen sich persönlich umstellen und wieder auf die Programmvorgabe zurücksetzen.",
|
|
18
|
+
tags: ["Anwendungsfall", "Konzeptdemo", "Kachelfläche"],
|
|
19
19
|
steps: [
|
|
20
|
-
"Im Profil core den
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
20
|
+
"Im Profil core den Einstieg Gesprächsrunde einrichten ausführen und den Aufbau abwarten. Die vom Run gesetzte Aufteilung ansehen.",
|
|
21
|
+
"Eine Kachel an ihrer Kopfzeile fassen und am oberen Rand einer anderen Kachel andocken. Erwartung: Die Aufteilung ändert sich sofort; alle Gesprächspartner arbeiten weiter.",
|
|
22
|
+
"Die Trennlinie zwischen zwei Kacheln verschieben und denselben Run im selben Browser neu laden. Erwartung: Die persönliche Aufteilung bleibt erhalten; die Actors und die Programmanordnung sind unverändert.",
|
|
23
|
+
"In der Statusleiste Programmvorgabe übernehmen wählen. Erwartung: Die Fläche zeigt wieder die Aufteilung des Runs; der Knopf verschwindet, bis wieder eine eigene Änderung vorliegt.",
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
26
|
{
|
|
@@ -37,6 +37,8 @@ export interface WorkspaceClientExecution {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface WorkspaceClientOptions {
|
|
40
|
+
/** Die Host-Wurzel dieses Rechners, aus der die Sprachserver aufgelöst werden; sie kann erst später entstehen. */
|
|
41
|
+
hostRoot: () => string | undefined;
|
|
40
42
|
onExecuted?: (execution: WorkspaceClientExecution) => void;
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -75,7 +77,7 @@ export class WorkspaceClient {
|
|
|
75
77
|
constructor(
|
|
76
78
|
private readonly transport: WorkspaceClientTransport,
|
|
77
79
|
identity: WorkspaceClientIdentity,
|
|
78
|
-
private readonly options: WorkspaceClientOptions
|
|
80
|
+
private readonly options: WorkspaceClientOptions,
|
|
79
81
|
) {
|
|
80
82
|
this.#identity = { ...identity, folders: [...identity.folders] };
|
|
81
83
|
this.#executor = new SandboxWorkspaceExecutor({
|
|
@@ -198,6 +200,7 @@ export class WorkspaceClient {
|
|
|
198
200
|
root: await realPathOf(cwd),
|
|
199
201
|
home: { home: homedir() },
|
|
200
202
|
logDirectory: join(tmpdir(), "ragents-workspace-logs", input.runId),
|
|
203
|
+
hostRoot: this.options.hostRoot(),
|
|
201
204
|
additions: input.env,
|
|
202
205
|
}));
|
|
203
206
|
const startedAt = Date.now();
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
// Der Hook löst @aicontainer/* auch für eine Profildatei außerhalb des Hosts auf; im Paket gibt es dort kein node_modules.
|
|
2
|
+
import "../../apps/server/src/host-resolution.ts";
|
|
1
3
|
import { spawn } from "node:child_process";
|
|
2
4
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { closeSync,
|
|
5
|
+
import { closeSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
6
|
import path from "node:path";
|
|
5
|
-
import { fileURLToPath
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
6
8
|
import { coreContracts } from "../../apps/server/src/api/contracts.ts";
|
|
7
9
|
import { profileFileName } from "../../apps/server/src/config-file.ts";
|
|
8
|
-
import { isEnvironmentReference } from "../../apps/server/src/config-definition.ts";
|
|
9
|
-
import { defaultDataDirectory } from "../../apps/server/src/data-directory.ts";
|
|
10
10
|
import { hostRoot } from "../../apps/server/src/host-version.ts";
|
|
11
|
+
import { readProfileTarget, type ProfileTarget } from "../../apps/server/src/profile-target.ts";
|
|
11
12
|
import { RpcClient } from "../../apps/web/src/rpc/client.ts";
|
|
12
13
|
import { WORKSPACE_BINDING_OPTION_ID } from "../../plugins/ragents.workspace/contract.ts";
|
|
13
14
|
import { JournalReader, journalLines, readJournal, RUN_ID_PATTERN, type JournalEvent } from "./journal.ts";
|
|
@@ -33,7 +34,8 @@ export const usage = (): string => `Verwendung: ragents <befehl> [argumente]
|
|
|
33
34
|
Vorgabeprofil ist ${DEFAULT_PROFILE}. Exit-Code: 0 fertig, 2 abgebrochen, 1 fehlgeschlagen oder
|
|
34
35
|
Verbindungsproblem. Die letzte Zeile auf stdout ist "run: <id>". Die Adresse kommt aus
|
|
35
36
|
<Datenordner>/host.json, sonst aus RAGENTS_URL, sonst aus host.PORT des Profils; RAGENTS_TOKEN
|
|
36
|
-
geht als Bearer-Token mit, falls das Profil eine Anmeldung verlangt
|
|
37
|
+
geht als Bearer-Token mit, falls das Profil eine Anmeldung verlangt. Der so gestartete Host baut
|
|
38
|
+
die Oberfläche nicht - ein Agent braucht sie nicht; mit Oberfläche startet ragents start <profil>.`;
|
|
37
39
|
|
|
38
40
|
export type AgentCommand =
|
|
39
41
|
| { readonly kind: "run"; readonly profile: string; readonly folder: string; readonly text: string; readonly entry: string | undefined; readonly json: boolean }
|
|
@@ -123,14 +125,6 @@ export const parseArguments = (argv: readonly string[]): AgentCommand => {
|
|
|
123
125
|
throw new Error(command ? `Unbekannter Befehl: ${command}\n\n${usage()}` : usage());
|
|
124
126
|
};
|
|
125
127
|
|
|
126
|
-
export interface ProfileTarget {
|
|
127
|
-
readonly profile: string;
|
|
128
|
-
readonly profileFile: string;
|
|
129
|
-
readonly port: number;
|
|
130
|
-
readonly dataDirectory: string;
|
|
131
|
-
readonly baseUrl: string;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
128
|
export interface HostRecord {
|
|
135
129
|
readonly profile: string;
|
|
136
130
|
readonly url: string;
|
|
@@ -155,17 +149,8 @@ export const writeHostRecord = (dataDirectory: string, record: HostRecord): void
|
|
|
155
149
|
writeFileSync(hostRecordFile(dataDirectory), `${JSON.stringify(record, null, 2)}\n`);
|
|
156
150
|
};
|
|
157
151
|
|
|
158
|
-
export const loadProfile =
|
|
159
|
-
|
|
160
|
-
if (!existsSync(profileFile)) throw new Error(`Die Profildatei fehlt: ${profileFile}`);
|
|
161
|
-
const module = await import(pathToFileURL(profileFile).href) as { config: { host: { PORT?: number; DATA_DIR?: unknown } } };
|
|
162
|
-
const port = Number(process.env.PORT || module.config.host.PORT);
|
|
163
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`host.PORT des Profils ${profile} ist keine gültige Portnummer.`);
|
|
164
|
-
const configured = module.config.host.DATA_DIR;
|
|
165
|
-
const fromFile = isEnvironmentReference(configured) ? process.env[configured.name] : typeof configured === "string" ? configured : undefined;
|
|
166
|
-
const dataDirectory = path.resolve(process.env.DATA_DIR ?? fromFile ?? defaultDataDirectory(profile));
|
|
167
|
-
return { profile, profileFile, port, dataDirectory, baseUrl: `http://localhost:${port}` };
|
|
168
|
-
};
|
|
152
|
+
export const loadProfile = (profile: string, root = hostRoot()): Promise<ProfileTarget> =>
|
|
153
|
+
readProfileTarget(profile, path.join(root, profileFileName(profile)));
|
|
169
154
|
|
|
170
155
|
/** pnpm und der bin-Befehl führen dieses Skript mit dem Arbeitsverzeichnis apps/server aus; ein relativer Ordner gilt trotzdem ab dem Aufrufer. */
|
|
171
156
|
export const callerDirectory = (): string => process.env.RAGENTS_CWD ?? process.env.INIT_CWD ?? process.cwd();
|
|
@@ -39,12 +39,17 @@ const ROOT_FILE_SOURCES = new Set(PACKAGE_ROOT_FILES.map(([source]) => source));
|
|
|
39
39
|
|
|
40
40
|
const skippedInPackage = (relative: string): boolean => SKIPPED_IN_PACKAGE.test(relative) || ROOT_FILE_SOURCES.has(relative);
|
|
41
41
|
|
|
42
|
-
/** Was der Host zur Laufzeit lädt: Server, Engine, Plugins, Profile und die Skripte hinter den Unterbefehlen. */
|
|
42
|
+
/** Was der Host zur Laufzeit lädt: Server, Engine, Plugins, Profile, den Web-Build und die Skripte hinter den Unterbefehlen. */
|
|
43
43
|
export const packageContents = (root = repositoryRoot): readonly string[] => [
|
|
44
44
|
"apps/server/package.json",
|
|
45
|
+
"apps/server/tsconfig.json",
|
|
45
46
|
"apps/server/src",
|
|
46
47
|
"apps/server/agent-home/settings.json",
|
|
47
48
|
"apps/web/package.json",
|
|
49
|
+
"apps/web/tsconfig.json",
|
|
50
|
+
"apps/web/vite.config.ts",
|
|
51
|
+
"apps/web/index.html",
|
|
52
|
+
"apps/web/column.html",
|
|
48
53
|
"apps/web/src",
|
|
49
54
|
...readdirSync(path.join(root, "packages"), { withFileTypes: true })
|
|
50
55
|
.filter((entry) => entry.isDirectory())
|
|
@@ -58,10 +63,10 @@ export const packageContents = (root = repositoryRoot): readonly string[] => [
|
|
|
58
63
|
"scripts/workspace-client",
|
|
59
64
|
];
|
|
60
65
|
|
|
61
|
-
/** Was der Host ausführt
|
|
62
|
-
const SCANNED = ["apps/server/src", "apps/web
|
|
66
|
+
/** Was der Host ausführt, in eine Mini-App bündelt oder beim Start in sein Web baut. */
|
|
67
|
+
const SCANNED = ["apps/server/src", "apps/web", "packages", "plugins", "scripts"];
|
|
63
68
|
const PACKAGE_NAME_PATTERN = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
64
|
-
const SCAN_SKIPPED = new Set(["
|
|
69
|
+
const SCAN_SKIPPED = new Set(["node_modules", "dist"]);
|
|
65
70
|
const SCANNED_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".mjs", ".js", ".css"]);
|
|
66
71
|
const IMPORT_PATTERN = /\b(?:from|import|require(?:\.resolve)?)\s*\(?\s*["']([^"'\n]+)["']/g;
|
|
67
72
|
|
|
@@ -114,10 +119,18 @@ const packageNameOf = (specifier: string): string | undefined => {
|
|
|
114
119
|
return PACKAGE_NAME_PATTERN.test(name) ? name : undefined;
|
|
115
120
|
};
|
|
116
121
|
|
|
117
|
-
|
|
122
|
+
const declaredWebDependencies = (packageDirectory: string): readonly string[] => {
|
|
123
|
+
const file = path.join(packageDirectory, "apps/web/package.json");
|
|
124
|
+
const declared = (JSON.parse(readFileSync(file, "utf8")) as { dependencies?: Record<string, string> }).dependencies ?? {};
|
|
125
|
+
return Object.keys(declared);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Die Pakete, die der ausgeführte Teil des Pakets wirklich importiert, plus die zur Laufzeit verlinkten Bibliotheken
|
|
129
|
+
* und die, die das Web des Hosts den web-Hälften fremder Plugins anbietet. */
|
|
118
130
|
export const requiredPackages = (packageDirectory: string): ReadonlyMap<string, string> => {
|
|
119
131
|
const sources = new Map<string, string>();
|
|
120
132
|
for (const name of runtimeLibraries) sources.set(name, "plugins/ragents.actor-programs (verlinkt zur Laufzeit)");
|
|
133
|
+
for (const name of declaredWebDependencies(packageDirectory)) sources.set(name, "apps/web/package.json (für die web-Hälften der Plugins)");
|
|
121
134
|
for (const folder of scanRoots(packageDirectory)) {
|
|
122
135
|
for (const file of scannedFiles(folder)) {
|
|
123
136
|
const content = readFileSync(file, "utf8");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { compareVersions, nextVersion, readVersion, versionLine, writeHostPackageVersion, writeVersion } from "../publish-version.ts";
|
|
4
5
|
import { buildPackage, PACKAGE_FOLDER, PACKAGE_NAME } from "./build-package.ts";
|
|
5
6
|
|
|
6
7
|
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
@@ -8,7 +9,8 @@ const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url))
|
|
|
8
9
|
const usage = `Verwendung: pnpm publish:package [--dry-run]
|
|
9
10
|
Baut das Paket ${PACKAGE_NAME} und veröffentlicht es auf npm. Der Token kommt aus der
|
|
10
11
|
Umgebungsvariable npm_key. --dry-run reicht npm den Probelauf durch und veröffentlicht nichts.
|
|
11
|
-
Die Fassung steht in der package.json der Wurzel
|
|
12
|
+
Die Fassung steht in der package.json der Wurzel; das Skript zählt vor dem Bauen die letzte Stelle
|
|
13
|
+
über die zuletzt veröffentlichte hoch, im Probelauf nur in der Ausgabe.`;
|
|
12
14
|
|
|
13
15
|
const REGISTRY_TOKEN_KEY = "npm_config_//registry.npmjs.org/:_authToken";
|
|
14
16
|
|
|
@@ -52,6 +54,10 @@ export const publishedVersions = (name: string, npm: NpmRunner): readonly string
|
|
|
52
54
|
return typeof parsed === "string" ? [parsed] : parsed;
|
|
53
55
|
};
|
|
54
56
|
|
|
57
|
+
/** Die höchste Fassung, die auf npm liegt; ein dort unbekanntes Paket hat keine. */
|
|
58
|
+
export const latestPublishedVersion = (npm: NpmRunner = runNpm, name = PACKAGE_NAME): string | undefined =>
|
|
59
|
+
[...publishedVersions(name, npm)].sort(compareVersions).at(-1);
|
|
60
|
+
|
|
55
61
|
export interface PublishPlan {
|
|
56
62
|
readonly name: string;
|
|
57
63
|
readonly version: string;
|
|
@@ -59,13 +65,13 @@ export interface PublishPlan {
|
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/** Was vor einem Publish stimmen muss: das gebaute Manifest und eine Fassung, die es auf npm noch nicht gibt. */
|
|
62
|
-
export const publishPlan = (manifest: Record<string, unknown>, published: readonly string[]): PublishPlan => {
|
|
63
|
-
const { name
|
|
68
|
+
export const publishPlan = (manifest: Record<string, unknown>, published: readonly string[], version: string): PublishPlan => {
|
|
69
|
+
const { name } = manifest as { name?: unknown };
|
|
64
70
|
const access = (manifest.publishConfig as { access?: unknown } | undefined)?.access;
|
|
65
71
|
const hostVersion = (manifest.ragents as { hostVersion?: unknown } | undefined)?.hostVersion;
|
|
66
72
|
if (name !== PACKAGE_NAME) throw new Error(`Das gebaute Paket heißt ${String(name)}, veröffentlicht wird ${PACKAGE_NAME}`);
|
|
67
73
|
if (access !== "public") throw new Error(`${PACKAGE_NAME} hat einen Scope und braucht publishConfig.access "public", nicht ${JSON.stringify(access)}`);
|
|
68
|
-
if (
|
|
74
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9a-z.-]+)?$/.test(version)) {
|
|
69
75
|
throw new Error(`Die Fassung ${JSON.stringify(version)} ist keine Fassung; sie steht als version in der package.json der Wurzel`);
|
|
70
76
|
}
|
|
71
77
|
if (typeof hostVersion !== "string" || !/^[0-9a-f]{40}$/.test(hostVersion)) {
|
|
@@ -80,6 +86,7 @@ export const publishPlan = (manifest: Record<string, unknown>, published: readon
|
|
|
80
86
|
|
|
81
87
|
export interface PublishOptions {
|
|
82
88
|
readonly directory: string;
|
|
89
|
+
readonly version: string;
|
|
83
90
|
readonly token: string;
|
|
84
91
|
readonly dryRun: boolean;
|
|
85
92
|
readonly npm: NpmRunner;
|
|
@@ -92,7 +99,7 @@ const scopeAdvice = (output: string): string => /Scope not found/i.test(output)
|
|
|
92
99
|
+ "auf npmjs.com anlegen bzw. den Token für den Scope freigeben."
|
|
93
100
|
: "";
|
|
94
101
|
|
|
95
|
-
/** Veröffentlicht den gebauten Ordner
|
|
102
|
+
/** Veröffentlicht den gebauten Ordner; der Exit 0 von npm publish ist die Bestätigung. */
|
|
96
103
|
export const publishDirectory = (options: PublishOptions): void => {
|
|
97
104
|
const hide = redacting(options.token);
|
|
98
105
|
const published = options.npm(["publish", "--access", "public", ...options.dryRun ? ["--dry-run"] : []], { cwd: options.directory, token: options.token });
|
|
@@ -103,9 +110,7 @@ export const publishDirectory = (options: PublishOptions): void => {
|
|
|
103
110
|
options.log("== Probelauf: nichts veröffentlicht");
|
|
104
111
|
return;
|
|
105
112
|
}
|
|
106
|
-
|
|
107
|
-
if (view.status !== 0) throw new Error(`npm view ${PACKAGE_NAME} version endete mit Code ${view.status}: ${hide(oneLine(view.stderr))}`);
|
|
108
|
-
options.log(`== Auf npm steht jetzt ${PACKAGE_NAME}@${view.stdout.trim()}`);
|
|
113
|
+
options.log(`== Veröffentlicht: ${PACKAGE_NAME}@${options.version}`);
|
|
109
114
|
};
|
|
110
115
|
|
|
111
116
|
const main = async (): Promise<void> => {
|
|
@@ -114,10 +119,19 @@ const main = async (): Promise<void> => {
|
|
|
114
119
|
if (unknown.length) throw new Error(`Unbekanntes Argument: ${unknown.join(" ")}\n${usage}`);
|
|
115
120
|
const token = process.env.npm_key;
|
|
116
121
|
if (!token) throw new Error(`npm_key fehlt: der npm-Token mit Schreibrecht auf ${PACKAGE_NAME}.\n${usage}`);
|
|
122
|
+
const manifestFile = path.join(repositoryRoot, "package.json");
|
|
123
|
+
const published = publishedVersions(PACKAGE_NAME, runNpm);
|
|
124
|
+
const next = nextVersion(readVersion(manifestFile), published);
|
|
125
|
+
console.log(versionLine(next));
|
|
126
|
+
const extensionManifestFile = path.join(repositoryRoot, "apps", "vscode", "package.json");
|
|
127
|
+
if (!dryRun) {
|
|
128
|
+
writeVersion(manifestFile, next.version);
|
|
129
|
+
writeHostPackageVersion(extensionManifestFile, next.version);
|
|
130
|
+
}
|
|
117
131
|
const built = await buildPackage(path.join(repositoryRoot, "dist", PACKAGE_FOLDER));
|
|
118
|
-
const plan = publishPlan(built.manifest,
|
|
132
|
+
const plan = publishPlan(built.manifest, published, next.version);
|
|
119
133
|
console.log(`== ${plan.name}@${plan.version} aus Host ${plan.hostVersion.slice(0, 12)}, ${built.fileCount} Dateien, ${(built.byteSize / 1024 / 1024).toFixed(1)} MB`);
|
|
120
|
-
publishDirectory({ directory: built.directory, token, dryRun, npm: runNpm, log: (line) => console.log(line) });
|
|
134
|
+
publishDirectory({ directory: built.directory, version: plan.version, token, dryRun, npm: runNpm, log: (line) => console.log(line) });
|
|
121
135
|
};
|
|
122
136
|
|
|
123
137
|
const moduleUrl: string | undefined = import.meta.url;
|
|
@@ -20,12 +20,13 @@ export const commands = {
|
|
|
20
20
|
|
|
21
21
|
const usage = `Verwendung: ragents <befehl> [argumente]
|
|
22
22
|
|
|
23
|
-
run <ordner> "<auftrag>" Host starten, Run
|
|
23
|
+
run <ordner> "<auftrag>" Host ohne Oberfläche starten, Run anlegen und auf das Ende warten
|
|
24
24
|
send <run> "<text>" Folgeauftrag im selben Run
|
|
25
25
|
journal <run> [--tools] Verlauf eines Runs lesen
|
|
26
26
|
stop <run> | stop --host laufenden Turn abbrechen oder den gemerkten Host beenden
|
|
27
27
|
connect <server-url> Client-Profil holen und den lokalen Server damit starten
|
|
28
|
-
start <profil>
|
|
28
|
+
start <profil|pfad> ein Profil des Pakets, eine eigene Profildatei oder einen
|
|
29
|
+
geholten Stand starten; baut die Oberfläche bei Bedarf
|
|
29
30
|
provision [<profil>|--workspace] Werkzeuge der Plugins holen
|
|
30
31
|
workspace-client <server-url> diesen Rechner als Arbeitsplatz anmelden
|
|
31
32
|
`;
|
|
@@ -8,6 +8,8 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { extract as extractTar } from "tar";
|
|
9
9
|
import { ragentsDataRoot } from "../../apps/server/src/data-directory.ts";
|
|
10
10
|
import { hostRoot, readHostPackage, readHostVersion } from "../../apps/server/src/host-version.ts";
|
|
11
|
+
import { readProfileTarget } from "../../apps/server/src/profile-target.ts";
|
|
12
|
+
import { ensureWebBuild } from "../../apps/server/src/web-build.ts";
|
|
11
13
|
import { RpcClient } from "../../apps/web/src/rpc/client.ts";
|
|
12
14
|
import { profileDistributionContracts, type ClientProfileDescription } from "../../plugins/ragents.profile-distribution/contract.ts";
|
|
13
15
|
|
|
@@ -226,24 +228,33 @@ export interface StartedProfile {
|
|
|
226
228
|
readonly profileFile: string;
|
|
227
229
|
/** Nur bei einem geholten Stand gesetzt; ein Profil des Hosts nimmt die Vorgaben seiner eigenen Konfiguration. */
|
|
228
230
|
readonly dataDirectory?: string;
|
|
231
|
+
/** Nur bei einem geholten Stand gesetzt; sonst baut der Start das Web des Profils selbst. */
|
|
229
232
|
readonly webDirectory?: string;
|
|
230
233
|
readonly port: number | undefined;
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
/** Provisioniert die Werkzeuge des Profils und startet den Server damit. */
|
|
236
|
+
/** Provisioniert die Werkzeuge des Profils, baut sein Web falls nötig und startet den Server damit. */
|
|
234
237
|
export const startProfile = async (started: StartedProfile): Promise<number> => {
|
|
238
|
+
const dataDirectory = started.dataDirectory ?? (await readProfileTarget(started.profile, started.profileFile)).dataDirectory;
|
|
235
239
|
const env: NodeJS.ProcessEnv = {
|
|
236
240
|
...process.env,
|
|
237
241
|
PRODUCT_PROFILE: started.profile,
|
|
238
242
|
PRODUCT_PROFILE_FILE: started.profileFile,
|
|
239
|
-
|
|
240
|
-
...(started.webDirectory ? { WEB_DIST_DIR: started.webDirectory } : {}),
|
|
243
|
+
DATA_DIR: dataDirectory,
|
|
241
244
|
};
|
|
242
245
|
console.log("== Werkzeuge provisionieren");
|
|
243
246
|
const provisioned = await run(hostScript("scripts/provision/run-provision.ts"), env);
|
|
244
247
|
if (provisioned !== 0) throw new Error(`Die Provisionierung endete mit Code ${provisioned}`);
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
const webDirectory = started.webDirectory ?? await ensureWebBuild({
|
|
249
|
+
root: hostRoot(),
|
|
250
|
+
profile: started.profile,
|
|
251
|
+
profileFile: started.profileFile,
|
|
252
|
+
dataDirectory,
|
|
253
|
+
environment: env,
|
|
254
|
+
log: (line) => console.log(line),
|
|
255
|
+
});
|
|
256
|
+
console.log(`== Server starten (Web ${webDirectory}, Daten ${dataDirectory})`);
|
|
257
|
+
return run(hostScript("apps/server/src/main.ts", started.port !== undefined ? ["--port", String(started.port)] : []), { ...env, WEB_DIST_DIR: webDirectory });
|
|
247
258
|
};
|
|
248
259
|
|
|
249
260
|
/** Startet einen Stand, den connect geholt hat, mit dem Web aus dem Cache. */
|
package/scripts/remote/start.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Der Hook löst @aicontainer/* auch für eine Profildatei außerhalb des Hosts auf; im Paket gibt es dort kein node_modules.
|
|
2
|
+
import "../../apps/server/src/host-resolution.ts";
|
|
1
3
|
import { statSync } from "node:fs";
|
|
2
4
|
import { readFile, readdir } from "node:fs/promises";
|
|
3
5
|
import path from "node:path";
|
|
@@ -5,14 +7,16 @@ import { fileURLToPath } from "node:url";
|
|
|
5
7
|
import { profileFileName } from "../../apps/server/src/config-file.ts";
|
|
6
8
|
import { ragentsDataRoot } from "../../apps/server/src/data-directory.ts";
|
|
7
9
|
import { hostRoot } from "../../apps/server/src/host-version.ts";
|
|
10
|
+
import { callerDirectory } from "../agent/agent-cli.ts";
|
|
8
11
|
import { startCached, startProfile, type CachedProfile } from "./connect.ts";
|
|
9
12
|
|
|
10
|
-
const usage = (): string => `Verwendung: ragents start <profil> [--port <n>]
|
|
11
|
-
Startet ein Profil dieses Hosts (core, developer)
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const usage = (): string => `Verwendung: ragents start <profil|pfad> [--port <n>]
|
|
14
|
+
Startet ein Profil dieses Hosts (core, developer), eine eigene ragents.config.<profil>.ts an
|
|
15
|
+
beliebiger Stelle oder einen Stand, den ragents connect schon geholt hat, ohne den Server zu
|
|
16
|
+
fragen. Ein eigenes Profil bekommt sein Web beim Start gebaut, ein geholter Stand bringt es aus
|
|
17
|
+
demselben Cache mit. Ohne Argument nennt der Befehl die Profile im Cache.`;
|
|
14
18
|
|
|
15
|
-
export const parseArguments = (argv: readonly string[]): {
|
|
19
|
+
export const parseArguments = (argv: readonly string[]): { selection: string; port: number | undefined } => {
|
|
16
20
|
let profile: string | undefined;
|
|
17
21
|
let port: number | undefined;
|
|
18
22
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -28,7 +32,7 @@ export const parseArguments = (argv: readonly string[]): { profile: string; port
|
|
|
28
32
|
profile = argument;
|
|
29
33
|
}
|
|
30
34
|
if (!profile) throw new Error(`Der Profilname fehlt.\n${usage()}`);
|
|
31
|
-
return { profile, port };
|
|
35
|
+
return { selection: profile, port };
|
|
32
36
|
};
|
|
33
37
|
|
|
34
38
|
const directories = async (folder: string): Promise<readonly string[]> =>
|
|
@@ -60,22 +64,36 @@ export const selectCachedProfile = (cached: readonly CachedProfile[], profile: s
|
|
|
60
64
|
+ "nimm ragents connect <server-url>.");
|
|
61
65
|
};
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
const PROFILE_FILE = /^ragents\.config\.([a-z0-9]+(?:-[a-z0-9]+)*)\.ts$/;
|
|
68
|
+
|
|
69
|
+
export interface LocalProfile {
|
|
70
|
+
readonly profile: string;
|
|
71
|
+
readonly profileFile: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Ein Profil ist ein Name neben dem Host (Checkout wie Paket) oder ein Pfad zu einer ragents.config.<profil>.ts an beliebiger Stelle. */
|
|
75
|
+
export const localProfile = (selection: string, root = hostRoot(), caller = callerDirectory()): LocalProfile | undefined => {
|
|
76
|
+
if (!selection.includes("/") && !selection.endsWith(".ts")) {
|
|
77
|
+
const file = path.join(root, profileFileName(selection));
|
|
78
|
+
return statSync(file, { throwIfNoEntry: false })?.isFile() ? { profile: selection, profileFile: file } : undefined;
|
|
79
|
+
}
|
|
80
|
+
const profileFile = path.resolve(caller, selection);
|
|
81
|
+
const match = PROFILE_FILE.exec(path.basename(profileFile));
|
|
82
|
+
if (!match) throw new Error(`Eine Profildatei heißt ragents.config.<profil>.ts, nicht ${path.basename(profileFile)}`);
|
|
83
|
+
if (!statSync(profileFile, { throwIfNoEntry: false })?.isFile()) throw new Error(`Die Profildatei fehlt: ${profileFile}`);
|
|
84
|
+
return { profile: match[1]!, profileFile };
|
|
67
85
|
};
|
|
68
86
|
|
|
69
87
|
const main = async (): Promise<void> => {
|
|
70
88
|
const arguments_ = parseArguments(process.argv.slice(2));
|
|
71
|
-
const own =
|
|
89
|
+
const own = localProfile(arguments_.selection);
|
|
72
90
|
if (own) {
|
|
73
|
-
console.log(`== Profil ${
|
|
74
|
-
console.log(`== Profildatei ${own}`);
|
|
75
|
-
process.exitCode = await startProfile({ profile:
|
|
91
|
+
console.log(`== Profil ${own.profile}`);
|
|
92
|
+
console.log(`== Profildatei ${own.profileFile}`);
|
|
93
|
+
process.exitCode = await startProfile({ profile: own.profile, profileFile: own.profileFile, port: arguments_.port });
|
|
76
94
|
return;
|
|
77
95
|
}
|
|
78
|
-
const cached = selectCachedProfile(await cachedProfiles(ragentsDataRoot()), arguments_.
|
|
96
|
+
const cached = selectCachedProfile(await cachedProfiles(ragentsDataRoot()), arguments_.selection);
|
|
79
97
|
console.log(`== Profil ${cached.profile} vom Server ${cached.serverUrl}, Stand ${cached.version.slice(0, 12)} (aus dem Cache)`);
|
|
80
98
|
console.log(`== Profildatei ${cached.profileFile}`);
|
|
81
99
|
console.log(`== Daten ${cached.dataDirectory}`);
|
|
@@ -3,6 +3,7 @@ import { statSync } from "node:fs";
|
|
|
3
3
|
import { hostname } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { hostRoot } from "../../apps/server/src/host-version.ts";
|
|
6
7
|
import { provisionWorkspace } from "../../apps/server/src/profile/provisioning.ts";
|
|
7
8
|
import { workspaceClientTransport } from "../../plugins/ragents.workspace/client/transport.ts";
|
|
8
9
|
import { WorkspaceClient } from "../../plugins/ragents.workspace/client/workspace-client.ts";
|
|
@@ -13,7 +14,7 @@ dieses Rechners aus - dasselbe, was die VS-Code-Erweiterung tut, nur ohne VS Cod
|
|
|
13
14
|
gilt das aktuelle Verzeichnis. Jeder Werkzeugaufruf erscheint als eine Zeile auf stdout.
|
|
14
15
|
Die Sprachserver holt der Start in den Werkzeugordner dieses Rechners (dasselbe wie
|
|
15
16
|
pnpm provision --workspace); ROSLYN_LANGUAGE_SERVER und FSHARP_LANGUAGE_SERVER übersteuern ihn,
|
|
16
|
-
TypeScript
|
|
17
|
+
TypeScript kommt aus dem Host-Ordner. Das Skript läuft, bis es mit Strg-C beendet wird.`;
|
|
17
18
|
|
|
18
19
|
export interface WorkspaceClientArguments {
|
|
19
20
|
readonly serverUrl: string;
|
|
@@ -71,6 +72,7 @@ const main = async (): Promise<void> => {
|
|
|
71
72
|
};
|
|
72
73
|
const transport = workspaceClientTransport(parsed.serverUrl, process.env.RAGENTS_TOKEN);
|
|
73
74
|
const client = new WorkspaceClient(transport, identity, {
|
|
75
|
+
hostRoot,
|
|
74
76
|
onExecuted: ({ runId, tool, durationMs, error }) =>
|
|
75
77
|
console.log(`== ${runId.slice(0, 8)} ${tool} ${durationMs} ms ${error ?? "ok"}`),
|
|
76
78
|
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useEffect, useId, useState } from "react";
|
|
2
|
-
import { useAccess } from "./AccessContext";
|
|
3
|
-
import { DEFAULT_MATERIAL_SETTINGS, MATERIAL_SETTINGS_LIMITS, resetMaterialSettings, saveMaterialSettings, useMaterialSettings } from "./material-settings";
|
|
4
|
-
import { Button, Input } from "./ui";
|
|
5
|
-
|
|
6
|
-
const noteClasses = "text-sm leading-[1.6] text-muted-foreground";
|
|
7
|
-
|
|
8
|
-
export function MaterialSettings() {
|
|
9
|
-
const { settings, error } = useMaterialSettings();
|
|
10
|
-
const writable = useAccess().can("settings.write");
|
|
11
|
-
const prefix = useId();
|
|
12
|
-
const [steps, setSteps] = useState(String(settings.steps));
|
|
13
|
-
useEffect(() => { setSteps(String(settings.steps)); }, [settings]);
|
|
14
|
-
const valid = (text: string, min: number, max: number) => text.trim() !== "" && Number.isInteger(Number(text)) && Number(text) >= min && Number(text) <= max;
|
|
15
|
-
const stepsValid = valid(steps, MATERIAL_SETTINGS_LIMITS.minSteps, MATERIAL_SETTINGS_LIMITS.maxSteps);
|
|
16
|
-
const changeSteps = (value: string) => {
|
|
17
|
-
if (!writable) return;
|
|
18
|
-
setSteps(value);
|
|
19
|
-
if (valid(value, MATERIAL_SETTINGS_LIMITS.minSteps, MATERIAL_SETTINGS_LIMITS.maxSteps)) saveMaterialSettings({ ...settings, steps: Number(value) });
|
|
20
|
-
};
|
|
21
|
-
return <div className="flex min-w-0 max-w-[680px] flex-col gap-5">
|
|
22
|
-
<p className={noteClasses}>Schichtwerk mit matten Flächen und gestapelter Tiefe. Änderungen gelten sofort für alle Runs und werden in diesem Browser gespeichert.</p>
|
|
23
|
-
<div className="flex flex-col gap-2">
|
|
24
|
-
<label className="text-sm font-semibold text-foreground" id={`${prefix}-steps-label`} htmlFor={`${prefix}-steps`}>Tiefenstufen</label>
|
|
25
|
-
<div className="flex min-w-0 items-center gap-5">
|
|
26
|
-
<input className="w-0 min-w-0 flex-1 accent-primary" aria-labelledby={`${prefix}-steps-label`} disabled={!writable} type="range" min={MATERIAL_SETTINGS_LIMITS.minSteps} max={MATERIAL_SETTINGS_LIMITS.maxSteps} step="1" value={settings.steps} onChange={(event) => changeSteps(event.target.value)} />
|
|
27
|
-
<Input className="w-[84px] flex-none" id={`${prefix}-steps`} aria-invalid={!stepsValid} aria-describedby={`${prefix}-steps-note`} disabled={!writable} type="number" min={MATERIAL_SETTINGS_LIMITS.minSteps} max={MATERIAL_SETTINGS_LIMITS.maxSteps} step="1" value={steps} onChange={(event) => changeSteps(event.target.value)} />
|
|
28
|
-
</div>
|
|
29
|
-
<p id={`${prefix}-steps-note`} className={stepsValid ? noteClasses : "text-sm leading-[1.6] text-destructive"}>{stepsValid ? `0 ist flach, bis zu 5 gleich tiefe Stufen. Standard: ${DEFAULT_MATERIAL_SETTINGS.steps}.` : "Bitte eine ganze Zahl von 0 bis 5 eingeben."}</p>
|
|
30
|
-
</div>
|
|
31
|
-
<div><Button variant="outline" disabled={!writable} onClick={() => {
|
|
32
|
-
if (!writable) return;
|
|
33
|
-
if (resetMaterialSettings()) {
|
|
34
|
-
setSteps(String(DEFAULT_MATERIAL_SETTINGS.steps));
|
|
35
|
-
}
|
|
36
|
-
}}>Auf Standard zurücksetzen</Button></div>
|
|
37
|
-
{!writable && <p className={noteClasses}>Die Materialeinstellungen sind mit deinen Rechten nur lesbar.</p>}
|
|
38
|
-
{error && <p className="text-sm leading-[1.6] text-destructive" role="alert">{error}</p>}
|
|
39
|
-
</div>;
|
|
40
|
-
}
|