@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.
Files changed (108) hide show
  1. package/README.md +10 -4
  2. package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
  3. package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
  4. package/apps/server/src/plugin-support/run-scripts.ts +4 -1
  5. package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
  6. package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
  7. package/apps/server/src/profile-target.ts +25 -0
  8. package/apps/server/src/web-build.ts +82 -0
  9. package/apps/server/tsconfig.json +38 -0
  10. package/apps/web/column.html +12 -0
  11. package/apps/web/index.html +12 -0
  12. package/apps/web/src/App.tsx +0 -10
  13. package/apps/web/src/PluginChat.tsx +8 -8
  14. package/apps/web/src/PluginRegistry.tsx +0 -12
  15. package/apps/web/src/StartSurface.tsx +2 -2
  16. package/apps/web/src/ThemeSettings.tsx +1 -1
  17. package/apps/web/src/column/ColumnApp.tsx +83 -7
  18. package/apps/web/src/column/clipboard.ts +64 -0
  19. package/apps/web/src/column/host-contract.ts +10 -0
  20. package/apps/web/src/column.tsx +2 -0
  21. package/apps/web/src/panel/LoginForm.tsx +9 -3
  22. package/apps/web/src/panel/OverviewPage.tsx +187 -62
  23. package/apps/web/src/panel/PanelDialogs.tsx +115 -0
  24. package/apps/web/src/panel/PanelPage.tsx +1 -1
  25. package/apps/web/src/panel/SettingsPage.tsx +75 -79
  26. package/apps/web/src/panel/contract.ts +17 -9
  27. package/apps/web/src/panel/target-state.ts +27 -2
  28. package/apps/web/src/ui/theme.css +4 -0
  29. package/apps/web/tsconfig.json +41 -0
  30. package/apps/web/vite.config.ts +140 -0
  31. package/package.json +6 -2
  32. package/packages/ragents/src/plugin-host.ts +5 -2
  33. package/packages/ragents/src/plugin-types.ts +3 -2
  34. package/packages/workspace-executor/src/context.ts +4 -0
  35. package/packages/workspace-executor/src/executor.ts +9 -2
  36. package/packages/workspace-executor/src/index.ts +2 -0
  37. package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
  38. package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
  39. package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
  40. package/packages/workspace-executor/src/language-server/contract.ts +7 -3
  41. package/packages/workspace-executor/src/language-server/host.ts +275 -138
  42. package/plugins/ragents.actor-programs/contract.ts +1 -1
  43. package/plugins/ragents.actor-programs/prompt.hbs +4 -4
  44. package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
  45. package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
  46. package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
  47. package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
  48. package/plugins/ragents.actor-programs/web/api.ts +1 -10
  49. package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
  50. package/plugins/ragents.orchestration/canvas.hbs +28 -44
  51. package/plugins/ragents.orchestration/contract.ts +17 -287
  52. package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
  53. package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
  54. package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
  55. package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
  56. package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
  57. package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
  58. package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
  59. package/plugins/ragents.orchestration/web/Center.tsx +28 -463
  60. package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
  61. package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
  62. package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
  63. package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
  64. package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
  65. package/plugins/ragents.orchestration/web/constants.ts +0 -2
  66. package/plugins/ragents.orchestration/web/index.tsx +0 -6
  67. package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
  68. package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
  69. package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
  70. package/plugins/ragents.profile-distribution/server/web.ts +3 -27
  71. package/plugins/ragents.reference/examples.ts +1 -2
  72. package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
  73. package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
  74. package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
  75. package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
  76. package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
  77. package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
  78. package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
  79. package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
  80. package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
  81. package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
  82. package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
  83. package/plugins/ragents.reference/walkthroughs.ts +14 -14
  84. package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
  85. package/scripts/agent/agent-cli.ts +9 -24
  86. package/scripts/package/build-package.ts +18 -5
  87. package/scripts/package/publish-package.ts +24 -10
  88. package/scripts/package/ragents.mjs +3 -2
  89. package/scripts/remote/connect.ts +16 -5
  90. package/scripts/remote/start.ts +33 -15
  91. package/scripts/workspace-client/run-workspace-client.ts +3 -1
  92. package/apps/web/src/MaterialSettings.tsx +0 -40
  93. package/apps/web/src/material-settings.ts +0 -104
  94. package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
  95. package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
  96. package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
  97. package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
  98. package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
  99. package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
  100. package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
  101. package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
  102. package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
  103. package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
  104. package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
  105. package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
  106. package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
  107. package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
  108. package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
package/README.md CHANGED
@@ -22,11 +22,15 @@ npm install -g @schlenkr/ragents
22
22
  RAGENTS_TOKEN=<token> ragents connect https://ragents.example.com
23
23
  ```
24
24
 
25
- - `start <profile>` - start a profile shipped with the package (`core`, `developer`) or a stand that
26
- `connect` already fetched, without asking the server.
25
+ - `start <profile|path>` - start a profile shipped with the package (`core`, `developer`), your
26
+ own `ragents.config.<name>.ts` anywhere on disk, or a stand that `connect` already fetched,
27
+ without asking the server. The web interface is built for that set of plugins when it is
28
+ missing or was last built for another profile or host version; the result goes to
29
+ `<data dir>/web/<profile>`, never into the package folder.
27
30
 
28
31
  ```sh
29
32
  ragents start developer
33
+ ragents start ~/projects/mine/ragents.config.mine.ts
30
34
  ```
31
35
 
32
36
  - `provision [<profile>|--workspace]` - fetch the tools the plugins of that profile need, such as
@@ -44,7 +48,8 @@ npm install -g @schlenkr/ragents
44
48
  ```
45
49
 
46
50
  - `run <folder> "<task>"` - start the host if none answers, bind a new run to the folder and wait
47
- until the turn is finished; the last line is always `run: <id>`.
51
+ until the turn is finished; the last line is always `run: <id>`. This host serves no web
52
+ interface - an agent does not need one; use `start` for that.
48
53
 
49
54
  ```sh
50
55
  ragents run ~/projects/demo "Fix the type error in src/broken.ts"
@@ -71,7 +76,8 @@ npm install -g @schlenkr/ragents
71
76
 
72
77
  ## Requirements
73
78
 
74
- - Node 22 (>= 22.19.0). No git, no pnpm, no build step.
79
+ - Node 22 (>= 22.19.0). No git, no pnpm, no source checkout; the package builds its own web
80
+ interface per profile with the Vite it brings along.
75
81
  - `dotnet` (.NET 10 SDK) for the C# diagnostics through Roslyn and the F# diagnostics through FSAC;
76
82
  without it TypeScript diagnostics still work.
77
83
  - Model access: either `OPENROUTER_API_KEY` in the environment, or `ragents connect <server-url>`
@@ -3,7 +3,12 @@ import { defineOperation } from "@aicontainer/ragents/src/rpc/contract";
3
3
  import { openJson } from "@aicontainer/ragents/src/http/contracts";
4
4
  import type { LanguageServerSnapshot } from "@aicontainer/workspace-executor";
5
5
 
6
- export type { LanguageServerSeverity, LanguageServerSnapshot } from "@aicontainer/workspace-executor";
6
+ export type {
7
+ LanguageServerInstanceSnapshot,
8
+ LanguageServerSeverity,
9
+ LanguageServerSnapshot,
10
+ LanguageServerState,
11
+ } from "@aicontainer/workspace-executor";
7
12
 
8
13
  /** Je Sprachserver-Plugin eine Methode; die Rechte tragen seine Kennung. */
9
14
  export const languageServerSnapshotContract = (pluginId: string) =>
@@ -1,6 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { defineRunFunction, type ToolContributor } from "@aicontainer/ragents";
3
3
  import {
4
+ languageServerCloseTool,
4
5
  languageServerDiagnosticsTool,
5
6
  languageServerOpenTool,
6
7
  type LanguageServerAdapter,
@@ -19,15 +20,22 @@ export const createLanguageServerToolContributor = (
19
20
  const openMetadata = {
20
21
  name: languageServerOpenTool(id),
21
22
  nativeTool: true,
22
- description: `Start the ${label} language server for this conversation's workspace and load ${rootDescription}. `
23
+ description: `Start a ${label} language server instance for this conversation's workspace and load ${rootDescription}. `
23
24
  + `Afterwards every edit or write of a ${extensions} file gets its diagnostics appended automatically, `
24
- + `and ${languageServerDiagnosticsTool(id)} is available. Idempotent for the same root; a different root replaces the server.`,
25
+ + `and ${languageServerDiagnosticsTool(id)} is available. Idempotent for the same root; other roots stay open.`,
25
26
  } as const;
26
27
  const diagnosticsMetadata = {
27
28
  name: languageServerDiagnosticsTool(id),
28
29
  nativeTool: true,
29
30
  description: `Current ${label} diagnostics (errors, warnings on request) for ${extensions} files from the running `
30
- + `language server, without building. Without paths: all changed files of the workspace according to git.`,
31
+ + `language server, without building. Without paths: all changed files of every open root according to git. `
32
+ + `With paths every file is answered by the instance whose root contains it; root asks one instance.`,
33
+ } as const;
34
+ const closeMetadata = {
35
+ name: languageServerCloseTool(id),
36
+ nativeTool: true,
37
+ description: `Stop the ${label} language server instance of one root; without root every ${label} instance of `
38
+ + "this conversation. Other languages and other conversations stay untouched.",
31
39
  } as const;
32
40
  const openTool = defineRunFunction({
33
41
  ...openMetadata,
@@ -51,6 +59,7 @@ export const createLanguageServerToolContributor = (
51
59
  paths: Type.Optional(Type.Array(Type.String(), {
52
60
  description: "Files relative to the workspace root; omit for all changed files",
53
61
  })),
62
+ root: Type.Optional(Type.String({ description: "Ask only the instance of this open root" })),
54
63
  warnings: Type.Optional(Type.Boolean({ description: "Also list warnings (default: only counted)" })),
55
64
  }),
56
65
  resultSchema: Type.String(),
@@ -62,12 +71,28 @@ export const createLanguageServerToolContributor = (
62
71
  ...(scope.signal ? { signal: scope.signal } : {}),
63
72
  }) as string,
64
73
  });
74
+ const closeTool = defineRunFunction({
75
+ ...closeMetadata,
76
+ label: `${label} schließen`,
77
+ schema: Type.Object({
78
+ root: Type.Optional(Type.String({ description: "The open root to stop; omit for every instance of this conversation" })),
79
+ }),
80
+ resultSchema: Type.String(),
81
+ available: alwaysAvailable,
82
+ executionMode: "sequential",
83
+ run: async (scope, toolCallId, input) =>
84
+ await sandbox.execute(scope.caller.runId, closeMetadata.name, input, {
85
+ toolCallId,
86
+ ...(scope.signal ? { signal: scope.signal } : {}),
87
+ }) as string,
88
+ });
65
89
  return {
66
90
  name: `ragents.lsp-${id}`,
67
91
  descriptors: [
68
92
  toolDescriptorFrom(openMetadata, alwaysAvailable),
69
93
  toolDescriptorFrom(diagnosticsMetadata, alwaysAvailable),
94
+ toolDescriptorFrom(closeMetadata, alwaysAvailable),
70
95
  ],
71
- tools: () => [openTool, diagnosticsTool],
96
+ tools: () => [openTool, diagnosticsTool, closeTool],
72
97
  };
73
98
  };
@@ -6,7 +6,7 @@ import { flag, frontMatterOf, numberOf, tagsOf } from "./front-matter.js";
6
6
  export type ScriptStartEntry = Extract<StartEntryContribution, { action: "script" }>;
7
7
 
8
8
  const PACKAGE_NAME = /^[a-z0-9][a-z0-9-]*$/;
9
- const RUN_FIELDS = ["title", "description", "order", "guide", "tags", "coordinator"];
9
+ const RUN_FIELDS = ["title", "description", "order", "guide", "tags", "coordinator", "category"];
10
10
 
11
11
  const requiredFile = (directory: string, name: string): string => {
12
12
  const file = path.join(directory, name);
@@ -45,6 +45,8 @@ const runScriptFrom = (directory: string, name: string, id: string): ScriptStart
45
45
  const order = numberOf(runFile, "order", fields.get("order"));
46
46
  const guide = fields.get("guide");
47
47
  const tags = tagsOf(runFile, fields.get("tags"));
48
+ const category = fields.get("category");
49
+ if (category !== undefined && !category.trim()) throw new Error(`${runFile}: category ist leer`);
48
50
  const programsDirectory = path.join(directory, "actors");
49
51
  const programsStats = statSync(programsDirectory, {throwIfNoEntry: false});
50
52
  if (programsStats && !programsStats.isDirectory()) throw new Error(`${programsDirectory} muss ein Verzeichnis sein`);
@@ -60,6 +62,7 @@ const runScriptFrom = (directory: string, name: string, id: string): ScriptStart
60
62
  ...(order !== undefined ? {order} : {}),
61
63
  ...(guide ? {guide} : {}),
62
64
  ...(tags !== undefined ? {tags} : {}),
65
+ ...(category ? {category} : {}),
63
66
  script: {
64
67
  handle: name,
65
68
  coordinator: flag(runFile, "coordinator", fields.get("coordinator"), true),
@@ -13,7 +13,7 @@ export interface StartEntryBase {
13
13
  /** One entry of the start surface as GET /api/plugins publishes it; a script entry never carries its source. */
14
14
  export type StartEntry = StartEntryBase & (
15
15
  | { action: "skill"; skill: string; category: string; prompt: string }
16
- | { action: "script"; coordinator: boolean }
16
+ | { action: "script"; coordinator: boolean; category?: string }
17
17
  );
18
18
 
19
19
  export type SkillStartEntry = Extract<StartEntry, { action: "skill" }>;
@@ -62,9 +62,10 @@ export const startEntryFrom = (value: unknown): StartEntry => {
62
62
  return { ...base, action: "skill", skill: value.skill, category: value.category, prompt: value.prompt };
63
63
  }
64
64
  case "script":
65
- allowed(["coordinator"]);
65
+ allowed(["coordinator", "category"]);
66
66
  if (typeof value.coordinator !== "boolean") throw invalid(`${id}: coordinator muss true oder false sein`);
67
- return { ...base, action: "script", coordinator: value.coordinator };
67
+ if (value.category !== undefined && (!text(value.category) || value.category !== value.category.trim())) throw invalid(`${id}: category muss ein einzelner nicht leerer Text sein`);
68
+ return { ...base, action: "script", coordinator: value.coordinator, ...(value.category !== undefined ? { category: value.category } : {}) };
68
69
  default:
69
70
  throw invalid(`${id}: unbekannte Aktion ${String(value.action)}; gültig sind skill und script`);
70
71
  }
@@ -25,6 +25,7 @@ import {
25
25
  type WorkspaceExecutor,
26
26
  type WorkspaceProcessContext,
27
27
  } from "@aicontainer/workspace-executor";
28
+ import { hostRoot } from "../host-version.js";
28
29
  import type { SessionWorkspace } from "../ragents/workspace-runtime.js";
29
30
  import { agentToolFrom, toolDescriptorFrom, type AgentToolDefinition } from "./agent-tool.js";
30
31
  import { alwaysAvailable } from "./tool-availability.js";
@@ -137,6 +138,7 @@ export class WorkspaceSandboxHost implements SandboxServices {
137
138
  root: await workspace.currentRoot(),
138
139
  home,
139
140
  logDirectory: home.home,
141
+ hostRoot: hostRoot(),
140
142
  ...(ident ? { ident } : {}),
141
143
  additionalRoots: await this.#additionalRoots(runId, ident),
142
144
  readOnlyRoots,
@@ -0,0 +1,25 @@
1
+ import { statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { isEnvironmentReference } from "./config-definition.js";
5
+ import { defaultDataDirectory } from "./data-directory.js";
6
+
7
+ export interface ProfileTarget {
8
+ readonly profile: string;
9
+ readonly profileFile: string;
10
+ readonly port: number;
11
+ readonly dataDirectory: string;
12
+ readonly baseUrl: string;
13
+ }
14
+
15
+ /** Port und Datenordner eines Profils, wie der Server sie später selbst bestimmt: Umgebung vor Profildatei vor Vorgabe. */
16
+ export const readProfileTarget = async (profile: string, profileFile: string, environment = process.env): Promise<ProfileTarget> => {
17
+ if (!statSync(profileFile, { throwIfNoEntry: false })?.isFile()) throw new Error(`Die Profildatei fehlt: ${profileFile}`);
18
+ const module = await import(pathToFileURL(profileFile).href) as { config: { host: { PORT?: number; DATA_DIR?: unknown } } };
19
+ const port = Number(environment.PORT || module.config.host.PORT);
20
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`host.PORT des Profils ${profile} ist keine gültige Portnummer.`);
21
+ const configured = module.config.host.DATA_DIR;
22
+ const fromFile = isEnvironmentReference(configured) ? environment[configured.name] : typeof configured === "string" ? configured : undefined;
23
+ const dataDirectory = path.resolve(environment.DATA_DIR ?? fromFile ?? defaultDataDirectory(profile));
24
+ return { profile, profileFile, port, dataDirectory, baseUrl: `http://localhost:${port}` };
25
+ };
@@ -0,0 +1,82 @@
1
+ import { spawn } from "node:child_process";
2
+ import { statSync } from "node:fs";
3
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { createRequire } from "node:module";
5
+ import path from "node:path";
6
+ import { createInterface } from "node:readline";
7
+ import { readHostVersion } from "./host-version.js";
8
+
9
+ /** Vite kommt im Checkout aus apps/web/node_modules, im Paket aus dessen Abhängigkeiten; beide Male ab apps/web aufgelöst. */
10
+ export const viteEntry = (root: string): string => {
11
+ try {
12
+ const manifest = createRequire(path.join(root, "apps/web/package.json")).resolve("vite/package.json");
13
+ return path.join(path.dirname(manifest), "bin/vite.js");
14
+ } catch (cause) {
15
+ throw new Error(`Der Web-Build findet Vite neben ${path.join(root, "apps/web")} nicht: `
16
+ + `${cause instanceof Error ? cause.message : String(cause)}`);
17
+ }
18
+ };
19
+
20
+ const run = (file: string, args: readonly string[], cwd: string, environment: NodeJS.ProcessEnv, log: (line: string) => void): Promise<void> =>
21
+ new Promise((resolve, reject) => {
22
+ const child = spawn(file, args, { cwd, env: environment, stdio: ["ignore", "pipe", "pipe"] });
23
+ for (const stream of [child.stdout, child.stderr]) createInterface({ input: stream! }).on("line", log);
24
+ child.once("error", reject);
25
+ child.once("exit", (code) => code === 0 ? resolve() : reject(new Error(`Der Web-Build endete mit Code ${code}`)));
26
+ });
27
+
28
+ export interface WebBuild {
29
+ readonly root: string;
30
+ /** Node, das Vite ausführt; in der Erweiterung ist process.execPath Electron und taugt nicht. */
31
+ readonly node?: string;
32
+ readonly outputDirectory: string;
33
+ readonly environment: NodeJS.ProcessEnv;
34
+ readonly log: (line: string) => void;
35
+ }
36
+
37
+ /** Baut das Web für den Pluginsatz, den die Umgebung nennt: PRODUCT_PROFILE samt Datei oder PLUGIN_LIST_FILE. */
38
+ export const buildWeb = async (build: WebBuild): Promise<void> => {
39
+ const entry = viteEntry(build.root);
40
+ await rm(build.outputDirectory, { recursive: true, force: true });
41
+ await mkdir(path.dirname(build.outputDirectory), { recursive: true });
42
+ await run(build.node ?? process.execPath, [entry, "build", "--outDir", build.outputDirectory, "--emptyOutDir"], path.join(build.root, "apps/web"), build.environment, build.log);
43
+ if (!statSync(path.join(build.outputDirectory, "index.html"), { throwIfNoEntry: false })?.isFile()) {
44
+ throw new Error(`Der Web-Build hat keine index.html in ${build.outputDirectory} hinterlassen`);
45
+ }
46
+ };
47
+
48
+ /** Je Profil ein gebautes Web im Datenordner; der Paketordner bleibt unberührt. */
49
+ export const webBuildDirectory = (dataDirectory: string, profile: string): string => path.join(dataDirectory, "web", profile);
50
+
51
+ export interface WebBuildTarget {
52
+ readonly root: string;
53
+ readonly node?: string;
54
+ readonly profile: string;
55
+ readonly profileFile: string;
56
+ readonly dataDirectory: string;
57
+ readonly environment: NodeJS.ProcessEnv;
58
+ readonly log: (line: string) => void;
59
+ readonly build?: (build: WebBuild) => Promise<void>;
60
+ }
61
+
62
+ const stampOf = (target: WebBuildTarget): string =>
63
+ JSON.stringify({ root: target.root, profileFile: target.profileFile, hostVersion: readHostVersion(target.root) }, null, 2);
64
+
65
+ /** Baut das Web des Profils, wenn es fehlt oder zuletzt für ein anderes Profil oder eine andere Host-Fassung gebaut wurde. */
66
+ export const ensureWebBuild = async (target: WebBuildTarget): Promise<string> => {
67
+ const directory = webBuildDirectory(target.dataDirectory, target.profile);
68
+ const stampFile = `${directory}.json`;
69
+ const stamp = stampOf(target);
70
+ const built = statSync(path.join(directory, "index.html"), { throwIfNoEntry: false })?.isFile() === true;
71
+ if (built && (await readFile(stampFile, "utf8").catch(() => undefined))?.trim() === stamp) return directory;
72
+ target.log(`== Web des Profils ${target.profile} bauen nach ${directory}`);
73
+ await (target.build ?? buildWeb)({
74
+ root: target.root,
75
+ node: target.node,
76
+ outputDirectory: directory,
77
+ environment: { ...target.environment, PRODUCT_PROFILE: target.profile, PRODUCT_PROFILE_FILE: target.profileFile },
78
+ log: target.log,
79
+ });
80
+ await writeFile(stampFile, `${stamp}\n`);
81
+ return directory;
82
+ };
@@ -0,0 +1,38 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2024",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "strict": true,
8
+ "isolatedModules": true,
9
+ "skipLibCheck": true,
10
+ "types": [
11
+ "node"
12
+ ],
13
+ "noEmit": true,
14
+ "lib": [
15
+ "ES2024"
16
+ ],
17
+ "paths": {
18
+ "@aicontainer/server/*": [
19
+ "../../apps/server/src/*"
20
+ ],
21
+ "@aicontainer/web/*": [
22
+ "../../apps/web/src/*"
23
+ ],
24
+ "@aicontainer/plugins/*": [
25
+ "../../plugins/*"
26
+ ],
27
+ "@ragents/workflow": [
28
+ "../../plugins/ragents.actor-programs/workflow/index.ts"
29
+ ]
30
+ }
31
+ },
32
+ "include": [
33
+ "src",
34
+ "../../plugins/*/server",
35
+ "../../plugins/*/contract.ts",
36
+ "../../ragents.config.*.ts"
37
+ ]
38
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>RAgents Panel</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/column.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>RAgents</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -30,7 +30,6 @@ export function App() {
30
30
  const overviewButtonRef = useRef<HTMLButtonElement>(null);
31
31
  const settingsButtonRef = useRef<HTMLButtonElement>(null);
32
32
  const [runHeaderContainer, setRunHeaderContainer] = useState<HTMLDivElement | null>(null);
33
- const [canvasToolbarContainer, setCanvasToolbarContainer] = useState<HTMLDivElement | null>(null);
34
33
  const [workspaceHeaderContainer, setWorkspaceHeaderContainer] = useState<HTMLDivElement | null>(null);
35
34
  const [statusContainer, setStatusContainer] = useState<HTMLElement | null>(null);
36
35
  const [workspaceModalContainer, setWorkspaceModalContainer] = useState<HTMLDivElement | null>(null);
@@ -80,13 +79,6 @@ export function App() {
80
79
  if (restoreFocus) overviewButtonRef.current?.focus({ preventScroll: true });
81
80
  }, []);
82
81
 
83
- useEffect(() => {
84
- if (!canvasToolbarContainer) return;
85
- const activateCanvas = () => { closeOverview(); setToolbarPanel(undefined); };
86
- canvasToolbarContainer.addEventListener("click", activateCanvas);
87
- return () => canvasToolbarContainer.removeEventListener("click", activateCanvas);
88
- }, [canvasToolbarContainer, closeOverview]);
89
-
90
82
  // Overview contributions keep their state after the first opening.
91
83
  const openOverview = useCallback(() => {
92
84
  setToolbarPanel(undefined);
@@ -202,7 +194,6 @@ export function App() {
202
194
  {busy ? <Spinner aria-hidden className="size-[18px]" /> : <SparklesIcon size={22} />}
203
195
  </button>
204
196
  </div>}
205
- <div className="relative flex flex-none self-stretch empty:hidden" ref={setCanvasToolbarContainer} />
206
197
  {toolbarPanels.map(({ id, Panel }) => <ToolbarPanelHost
207
198
  id={id} key={id} onBusy={setPanelBusy}
208
199
  onClose={() => setToolbarPanel(undefined)}
@@ -230,7 +221,6 @@ export function App() {
230
221
  {readRuns && activeId
231
222
  ? (
232
223
  <PluginChat
233
- canvasToolbarContainer={canvasToolbarContainer}
234
224
  headerContainer={runHeaderContainer}
235
225
  workspaceHeaderContainer={workspaceHeaderContainer}
236
226
  statusContainer={statusContainer}
@@ -55,7 +55,7 @@ export type ChatLayout = "workspace" | "column" | { element: string };
55
55
 
56
56
  interface PluginChatProps {
57
57
  layout?: ChatLayout;
58
- canvasToolbarContainer?: HTMLElement | null;
58
+ toolbarContainer?: HTMLElement | null;
59
59
  statusContainer?: HTMLElement | null;
60
60
  headerContainer?: HTMLElement | null;
61
61
  workspaceHeaderContainer?: HTMLElement | null;
@@ -71,7 +71,7 @@ interface PluginChatProps {
71
71
 
72
72
  interface ChatWorkspaceProps {
73
73
  layout: ChatLayout;
74
- canvasToolbarContainer?: HTMLElement | null;
74
+ toolbarContainer?: HTMLElement | null;
75
75
  statusContainer?: HTMLElement | null;
76
76
  panelState: WorkspacePanelState;
77
77
  onTogglePanel: () => void;
@@ -90,7 +90,7 @@ interface ChatWorkspaceProps {
90
90
  tabs: readonly WorkspaceTabContribution[];
91
91
  }
92
92
 
93
- export function PluginChat({ layout = "workspace", headerContainer, canvasToolbarContainer, workspaceHeaderContainer, statusContainer, initialStartOptions, onStarted, onLocationChange, onViewed, viewing = false, registry, session, startDialog }: PluginChatProps) {
93
+ export function PluginChat({ layout = "workspace", headerContainer, toolbarContainer, workspaceHeaderContainer, statusContainer, initialStartOptions, onStarted, onLocationChange, onViewed, viewing = false, registry, session, startDialog }: PluginChatProps) {
94
94
  const access = useAccess();
95
95
  const startOnly = startDialog !== undefined;
96
96
  const [modalContainer, setModalContainer] = useState<HTMLDivElement | null>(null);
@@ -235,7 +235,7 @@ export function PluginChat({ layout = "workspace", headerContainer, canvasToolba
235
235
  panelVisible={panelState === "expanded"} runId={session.id} tabs={availableTabs} />}
236
236
  <ChatWorkspace
237
237
  layout={layout}
238
- canvasToolbarContainer={canvasToolbarContainer}
238
+ toolbarContainer={toolbarContainer}
239
239
  panelState={panelState}
240
240
  onTogglePanel={() => updatePanelState(panelState === "expanded" ? "collapsed" : "expanded")}
241
241
  headerContainer={headerContainer}
@@ -281,7 +281,7 @@ function UserLocationReporter({ activeTabId, onChange, panelVisible, runId, tabs
281
281
 
282
282
  function ChatWorkspace({
283
283
  layout,
284
- canvasToolbarContainer = null,
284
+ toolbarContainer = null,
285
285
  panelState,
286
286
  onTogglePanel,
287
287
  headerContainer,
@@ -385,7 +385,7 @@ function ChatWorkspace({
385
385
  ? <CanvasElementView elementId={layout.element} navigation={navigation} registry={registry} session={session} />
386
386
  : CanvasColumn
387
387
  ? <CanvasColumn
388
- toolbarContainer={canvasToolbarContainer}
388
+ toolbarContainer={toolbarContainer}
389
389
  runToolbarContainer={null}
390
390
  statusContainer={statusContainer}
391
391
  cardSections={registry.cardSections}
@@ -420,7 +420,7 @@ function ChatWorkspace({
420
420
  {CanvasCenter
421
421
  ? (
422
422
  <CanvasCenter
423
- toolbarContainer={canvasToolbarContainer}
423
+ toolbarContainer={toolbarContainer}
424
424
  runToolbarContainer={runToolbarContainer}
425
425
  statusContainer={statusContainer}
426
426
  cardSections={registry.cardSections}
@@ -477,7 +477,7 @@ function CanvasElementView({ elementId, navigation, registry, session }: {
477
477
  {session.runView === undefined ? "Der Run wird geladen ..." : "Diese Mini-App gibt es in dem Run nicht mehr."}
478
478
  </div>;
479
479
  return <div className="flex min-h-0 min-w-0 flex-1 flex-col" data-view="element">
480
- <match.Element definition={match.definition} navigation={navigation} presentation="tiled" session={session} />
480
+ <match.Element definition={match.definition} navigation={navigation} session={session} />
481
481
  </div>;
482
482
  }
483
483
 
@@ -152,25 +152,13 @@ export interface CanvasElementDefinition {
152
152
  id: string;
153
153
  visible?: boolean;
154
154
  title?: string;
155
- width: number;
156
- height: number;
157
- collapsedHeight?: number;
158
155
  anchorActorId?: string;
159
156
  entity?: EntityReference;
160
- resizable?: {
161
- minWidth: number;
162
- maxWidth: number;
163
- minHeight: number;
164
- maxHeight: number;
165
- };
166
157
  data?: unknown;
167
158
  }
168
159
 
169
160
  export interface CanvasElementContext {
170
161
  definition: CanvasElementDefinition;
171
- presentation?: "canvas" | "tiled";
172
- collapsed?: boolean;
173
- onCollapsedChange?: (collapsed: boolean) => void;
174
162
  navigation: SessionNavigation;
175
163
  session: SessionContext;
176
164
  }
@@ -120,13 +120,13 @@ export function StartEntryLibrary({ skills, scripts, launchDisabled, onOpen, has
120
120
  const sorted = useMemo(() => [...[...skills].sort(byOrder), ...[...scripts].sort(byOrder)], [skills, scripts]);
121
121
  const tags = [...new Set(sorted.flatMap((entry) => entry.tags ?? []))].sort((a, b) => a.localeCompare(b, "de-DE"));
122
122
  const filtered = sorted.filter((entry) => (!tag || entry.tags?.includes(tag))
123
- && `${entry.title} ${entry.description} ${entry.action === "skill" ? entry.category : kindLabel(entry)} ${entry.owner} ${(entry.tags ?? []).join(" ")}`
123
+ && `${entry.title} ${entry.description} ${entry.category ?? kindLabel(entry)} ${entry.owner} ${(entry.tags ?? []).join(" ")}`
124
124
  .toLocaleLowerCase("de-DE").includes(query.trim().toLocaleLowerCase("de-DE")));
125
125
  const selected = filtered.find((entry) => entry.id === selectedId) ?? filtered[0];
126
126
  const guided = selected && hasGuide(selected);
127
127
  const tagOptions = [{ value: "", label: "Alle Schlagworte" }, ...tags.map((value) => ({ value, label: value }))];
128
128
  const items: ListDetailItem[] = filtered.map((entry) => ({ id: entry.id, title: entry.title,
129
- description: entry.description, group: entry.action === "skill" ? entry.category : technical ? "Run-Scripts" : "Abläufe",
129
+ description: entry.description, group: entry.category ?? (technical ? "Run-Scripts" : "Abläufe"),
130
130
  icon: entryIcon(entry), meta: technical ? kindLabel(entry) : undefined, tone: entry.action === "skill" ? "purple" : "success" }));
131
131
 
132
132
  return <ListDetail className="min-h-0 flex-1" label="Skills und Abläufe" detailLabel="Vorschau" items={items}
@@ -25,7 +25,7 @@ export function ThemeSettings() {
25
25
  </div>
26
26
  <div className="flex-[1_1_220px]">
27
27
  <h3 className="mb-1.5 text-lg font-semibold" id="theme-settings-title">Schichtwerk</h3>
28
- <p className={noteClasses}>Matte Flächen, gestapelte Tiefe und gezeichnete Konturen.</p>
28
+ <p className={noteClasses}>Matte Flächen und gezeichnete Konturen.</p>
29
29
  </div>
30
30
  <ToggleGroup aria-label="Schichtwerk-Farbschema" className="flex-none max-sm:w-full" disabled={!writable} size="sm" spacing={0}
31
31
  value={[preference]} variant="outline"