@schlenkr/ragents 0.1.9 → 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.
Files changed (82) hide show
  1. package/apps/server/package.json +1 -1
  2. package/apps/server/src/access-projection.ts +6 -36
  3. package/apps/server/src/api/contracts.ts +5 -2
  4. package/apps/server/src/api/core-methods.ts +5 -3
  5. package/apps/server/src/config-file.ts +1 -1
  6. package/apps/server/src/host-api.json +2 -0
  7. package/apps/server/src/plugin-support/process-sandbox.ts +16 -15
  8. package/apps/server/src/provider.ts +2 -2
  9. package/apps/server/src/ragents/engine.ts +2 -1
  10. package/apps/server/src/ragents/global-chat.ts +7 -1
  11. package/apps/server/src/server.ts +3 -1
  12. package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-xXXhTDbh.js → highlighted-body-KPVGNVTW-BO4wLQtE.js} +1 -1
  13. package/apps/web/dist/assets/main-DnnqWHkH.js +11 -0
  14. package/apps/web/dist/assets/mermaid-HWGCJPDP-BLLeth5_.js +568 -0
  15. package/apps/web/dist/assets/{runPanel-ZT6TPDLM.js → runPanel-DPm7s3KM.js} +2 -2
  16. package/apps/web/dist/help/guide-clients.html +17 -7
  17. package/apps/web/dist/help/guide-clients.md +18 -7
  18. package/apps/web/dist/help/index.html +1911 -908
  19. package/apps/web/dist/help/mini-app.css +24 -0
  20. package/apps/web/dist/host-web.json +2 -2
  21. package/apps/web/dist/index.html +2 -2
  22. package/apps/web/dist/run-panel.html +2 -2
  23. package/apps/web/package.json +1 -1
  24. package/apps/web/src/panel/ConnectionsPage.tsx +3 -0
  25. package/apps/web/src/panel/StartPage.tsx +3 -0
  26. package/apps/web/src/panel/contract.ts +7 -0
  27. package/bundles/ragents.activity/ragents-bundle.json +1 -1
  28. package/bundles/ragents.actor-programs/ragents-bundle.json +2 -2
  29. package/bundles/ragents.actor-programs/server/index.js +31 -3
  30. package/bundles/ragents.actor-programs/server/index.js.map +4 -4
  31. package/bundles/ragents.actor-programs/web/index.js.map +1 -1
  32. package/bundles/ragents.ask/ragents-bundle.json +2 -4
  33. package/bundles/ragents.ask/web/chunks/{chunk-V7W3QDVS.js → chunk-MBRPS477.js} +2 -2
  34. package/bundles/ragents.ask/web/chunks/{chunk-V7W3QDVS.js.map → chunk-MBRPS477.js.map} +4 -4
  35. package/bundles/ragents.ask/web/classes.json +1 -1
  36. package/bundles/ragents.ask/web/exports/web/QuestionCard.js +1 -1
  37. package/bundles/ragents.ask/web/index.js +1 -1
  38. package/bundles/ragents.ask/web/index.js.map +4 -4
  39. package/bundles/ragents.browser/ragents-bundle.json +1 -1
  40. package/bundles/ragents.documents/ragents-bundle.json +1 -1
  41. package/bundles/ragents.lsp-fsharp/ragents-bundle.json +1 -1
  42. package/bundles/ragents.lsp-roslyn/ragents-bundle.json +1 -1
  43. package/bundles/ragents.lsp-typescript/ragents-bundle.json +1 -1
  44. package/bundles/ragents.model-relay/ragents-bundle.json +1 -1
  45. package/bundles/ragents.orchestration/ragents-bundle.json +2 -2
  46. package/bundles/ragents.orchestration/web/classes.json +1 -1
  47. package/bundles/ragents.orchestration/web/index.js +6 -6
  48. package/bundles/ragents.orchestration/web/index.js.map +3 -3
  49. package/bundles/ragents.overseer/ragents-bundle.json +2 -2
  50. package/bundles/ragents.overseer/server/index.js +1 -1
  51. package/bundles/ragents.overseer/server/index.js.map +2 -2
  52. package/bundles/ragents.processes/ragents-bundle.json +1 -1
  53. package/bundles/ragents.product/ragents-bundle.json +1 -1
  54. package/bundles/ragents.profile-distribution/ragents-bundle.json +1 -1
  55. package/bundles/ragents.reference/ragents-bundle.json +1 -1
  56. package/bundles/ragents.todo/ragents-bundle.json +1 -1
  57. package/bundles/ragents.transcript/ragents-bundle.json +1 -1
  58. package/bundles/ragents.watch/ragents-bundle.json +1 -1
  59. package/bundles/ragents.workspace/ragents-bundle.json +2 -2
  60. package/bundles/ragents.workspace/server/index.js +4 -2
  61. package/bundles/ragents.workspace/server/index.js.map +2 -2
  62. package/package.json +3 -3
  63. package/packages/agent/dist/core/tools/bash.d.ts +1 -1
  64. package/packages/agent/src/core/tools/bash.ts +1 -1
  65. package/packages/ragents/src/index.ts +1 -0
  66. package/packages/ragents/src/plugin-host.ts +46 -0
  67. package/packages/ragents/src/plugin-types.ts +19 -0
  68. package/packages/workspace-executor/src/managed-process.ts +28 -0
  69. package/packages/workspace-executor/src/sandbox-tools.ts +9 -3
  70. package/plugins/ragents.actor-programs/server/access-projections.ts +31 -0
  71. package/plugins/ragents.actor-programs/server/index.ts +2 -0
  72. package/plugins/ragents.ask/web/index.tsx +0 -6
  73. package/plugins/ragents.orchestration/web/ActorTile.tsx +1 -1
  74. package/plugins/ragents.orchestration/web/run-panel/RunPanel.tsx +8 -7
  75. package/plugins/ragents.orchestration/web/run-panel/sheet-status.ts +3 -3
  76. package/plugins/ragents.overseer/server/api.ts +2 -2
  77. package/plugins/ragents.workspace/client/workspace-client.ts +19 -6
  78. package/plugins/ragents.workspace/server/config.ts +3 -0
  79. package/plugins/ragents.workspace/server/index.ts +3 -2
  80. package/apps/web/dist/assets/main-C7JeZFjs.js +0 -6
  81. package/apps/web/dist/assets/mermaid-HWGCJPDP-DfsU5ofC.js +0 -478
  82. package/plugins/ragents.ask/web/QuestionSection.tsx +0 -48
@@ -11,7 +11,9 @@ import type { AgentProfile, CatalogModel } from "./agents/catalog.ts";
11
11
  import type { ToolContributor } from "./agents/plugins.ts";
12
12
  import { describeToolAvailability, type RunFunction } from "./agents/tools.ts";
13
13
  import type { ChannelContribution, ChannelDescriptor, MethodContribution, MethodDescriptor } from "./rpc/contribution.ts";
14
+ import type { PluginState } from "./domain/model.ts";
14
15
  import type {
16
+ AccessProjectionContribution,
15
17
  AgentAudience,
16
18
  HttpRouteContribution,
17
19
  OperationContext,
@@ -19,6 +21,7 @@ import type {
19
21
  RegisteredOperationDescriptor,
20
22
  AgentContribution,
21
23
  AgentContributionContext,
24
+ PluginChatEvent,
22
25
  PluginConfigDescriptor,
23
26
  PluginManifest,
24
27
  PluginRegistration,
@@ -977,6 +980,47 @@ export class StartOptionContributionRegistry {
977
980
  }
978
981
  }
979
982
 
983
+ /** Was ein Zugang ohne runs.inspect von Plugin-Zuständen sieht: eine Startoption nur mit ihren Rechten, sonst die Projektion ihres Plugins, ohne sie alles. */
984
+ export class AccessProjectionRegistry {
985
+ readonly #projections = new ContributionRegistry<AccessProjectionContribution>("Zugriffsprojektion");
986
+ readonly #startOptions: StartOptionContributionRegistry;
987
+
988
+ constructor(startOptions = new StartOptionContributionRegistry()) {
989
+ this.#startOptions = startOptions;
990
+ }
991
+
992
+ register(owner: string, contributions: readonly AccessProjectionContribution[]): void {
993
+ for (const contribution of contributions) {
994
+ for (const name of ["state", "chatEvent"] as const) {
995
+ if (typeof contribution[name] !== "function") throw new Error(`Zugriffsprojektion ${contribution.id} hat kein ${name}`);
996
+ }
997
+ }
998
+ this.#projections.register(owner, contributions);
999
+ }
1000
+
1001
+ state(entry: PluginState, access: Pick<AccessContext, "can">): PluginState | undefined {
1002
+ if (access.can("runs.inspect")) return entry;
1003
+ if (this.#startOptions.missingRight(entry.pluginId, access) !== undefined) return undefined;
1004
+ const projection = this.#find(entry.pluginId);
1005
+ if (!projection) return entry;
1006
+ const state = projection.state(entry);
1007
+ return state === undefined ? undefined : { ...entry, state };
1008
+ }
1009
+
1010
+ chatEvent<T extends PluginChatEvent>(pluginId: string, event: T, access: Pick<AccessContext, "can">): T | undefined {
1011
+ if (access.can("runs.inspect")) return event;
1012
+ if (this.#startOptions.missingRight(pluginId, access) !== undefined) return undefined;
1013
+ const projection = this.#find(pluginId);
1014
+ if (!projection) return event;
1015
+ const projected = projection.chatEvent(event.payload === undefined ? { type: event.type } : { type: event.type, payload: event.payload });
1016
+ return projected === undefined ? undefined : { ...event, type: projected.type, payload: projected.payload };
1017
+ }
1018
+
1019
+ #find(id: string): AccessProjectionContribution | undefined {
1020
+ return this.#projections.entries().find(({ value }) => value.id === id)?.value;
1021
+ }
1022
+ }
1023
+
980
1024
  export class ConfigContributionRegistry {
981
1025
  readonly #entries: Array<{ owners: string[]; value: PluginConfigDescriptor }> = [];
982
1026
 
@@ -1125,6 +1169,7 @@ export class PluginHost {
1125
1169
  readonly lifecycle = new LifecycleContributionRegistry();
1126
1170
  readonly sessionMetadata = new SessionMetadataContributionRegistry();
1127
1171
  readonly startOptions = new StartOptionContributionRegistry();
1172
+ readonly accessProjections = new AccessProjectionRegistry(this.startOptions);
1128
1173
  readonly config = new ConfigContributionRegistry();
1129
1174
  readonly storage: StorageRegistry;
1130
1175
  readonly #product: ProductDescriptor;
@@ -1176,6 +1221,7 @@ export class PluginHost {
1176
1221
  skills: (...entries) => this.skills.register(manifest.id, entries),
1177
1222
  startEntries: (...entries) => this.startEntries.register(manifest.id, entries),
1178
1223
  startOptions: (...entries) => this.startOptions.register(manifest.id, entries),
1224
+ accessProjections: (...entries) => this.accessProjections.register(manifest.id, entries),
1179
1225
  functions: (...entries) => this.tools.registerFunctions(manifest.id, entries),
1180
1226
  script: (...entries) => this.script.register(manifest.id, entries),
1181
1227
  };
@@ -8,6 +8,7 @@ import type { WorkspaceToolNaming } from "./agents/workspace-tools.ts";
8
8
  import type { Orchestration } from "./runtime/orchestration.ts";
9
9
  import type { AgentDriver } from "./drivers/types.ts";
10
10
  import type { JsonValue } from "./domain/json.ts";
11
+ import type { PluginState } from "./domain/model.ts";
11
12
  import type { AccessContext } from "./access.ts";
12
13
  import type { ChannelContribution, MethodContribution } from "./rpc/contribution.ts";
13
14
 
@@ -370,6 +371,22 @@ export interface RegisteredStartOption {
370
371
  option: StartOptionContribution;
371
372
  }
372
373
 
374
+ /** Ein Chat-Ereignis zu einem Plugin-Zustand, ohne die Kennung, die die Projektion schon nennt. */
375
+ export interface PluginChatEvent {
376
+ type: string;
377
+ payload?: unknown;
378
+ }
379
+
380
+ /** Was ein Zugang ohne runs.inspect von einem Plugin-Zustand und dessen Chat-Ereignissen sieht; ohne Projektion sieht er beides unverändert. */
381
+ export interface AccessProjectionContribution {
382
+ /** Die Kennung des Zustands, wie sie im Journal (`pluginId`) und im Chat-Ereignis steht. */
383
+ id: string;
384
+ /** Der sichtbare Zustand; undefined nimmt ihn aus der Run-Ansicht. */
385
+ state: (entry: PluginState) => JsonValue | undefined;
386
+ /** Das sichtbare Chat-Ereignis; undefined unterdrückt es. */
387
+ chatEvent: (event: PluginChatEvent) => PluginChatEvent | undefined;
388
+ }
389
+
373
390
  export interface SessionMetadataContribution {
374
391
  id: string;
375
392
  /** Ob `describe` den Arbeitsbereich des Runs erreicht; dann ruft der Host es nur für Aufrufer, die ihn erreichen dürfen. */
@@ -432,6 +449,8 @@ export interface PluginRegistration {
432
449
  skills: (...contributions: readonly SkillContribution[]) => void;
433
450
  startEntries: (...contributions: readonly StartEntryContribution[]) => void;
434
451
  startOptions: (...contributions: readonly StartOptionContribution[]) => void;
452
+ /** Wie Zugänge ohne runs.inspect die Zustände dieses Plugins und ihre Chat-Ereignisse sehen, je Kennung eines Zustands. */
453
+ accessProjections: (...contributions: readonly AccessProjectionContribution[]) => void;
435
454
  functions: (...functions: readonly (RunFunction | ToolContributor)[]) => void;
436
455
  script: (...contributions: readonly ScriptContribution[]) => void;
437
456
  }
@@ -2,6 +2,7 @@ import { execFile, spawn, type ChildProcess } from "node:child_process";
2
2
  import { readdir, readFile } from "node:fs/promises";
3
3
  import type { Writable } from "node:stream";
4
4
  import { killProcessTree } from "@ragents/agent";
5
+ import { bashLaunch } from "./bash-launch.js";
5
6
 
6
7
  const PROCESS_GROUP_JOIN_TIMEOUT_MS = 5_000;
7
8
  const PROCESS_GROUP_POLL_MS = 25;
@@ -77,6 +78,33 @@ export const stopProcessTree = (pid: number): void => {
77
78
  if (processExists(pid)) killProcessTree(pid);
78
79
  };
79
80
 
81
+ /** Markiert unter Windows die Prozesse eines bash-Aufrufs: MSYS hängt die Kinder der Bash nicht an deren Windows-Prozessbaum, taskkill /T erreicht sie nicht. */
82
+ export const MSYS_CALL_ENV = "RAGENTS_BASH_CALL";
83
+
84
+ /** Die Windows-Kennungen aller MSYS-Prozesse in den Prozessgruppen, in denen ein Prozess die Markierung trägt, auch nach dem Ende der Bash selbst. */
85
+ const msysGroupScript = (marker: string): string => [
86
+ "groups=' '",
87
+ `for d in /proc/[0-9]*; do tr '\\0' '\\n' < "$d/environ" 2>/dev/null | grep -qxF '${MSYS_CALL_ENV}=${marker}' && groups="$groups$(cat "$d/pgid") "; done`,
88
+ `for d in /proc/[0-9]*; do case "$groups" in *" $(cat "$d/pgid" 2>/dev/null) "*) cat "$d/winpid" 2>/dev/null;; esac; done`,
89
+ ].join("\n");
90
+
91
+ const execFileText = (file: string, args: readonly string[], env?: NodeJS.ProcessEnv): Promise<{ stdout: string; error: Error | null }> =>
92
+ new Promise((resolve) => {
93
+ execFile(file, [...args], { env, windowsHide: true, timeout: 15_000 }, (error, stdout) => resolve({ stdout, error }));
94
+ });
95
+
96
+ /** Beendet unter Windows alles, was ein bash-Aufruf mit dieser Markierung gestartet hat, samt den nativen Bäumen darunter. */
97
+ export const stopMsysCall = async (bash: string, env: NodeJS.ProcessEnv, marker: string): Promise<void> => {
98
+ const launch = bashLaunch({ bash, rg: undefined }, msysGroupScript(marker), env, "win32");
99
+ const listing = await execFileText(launch.command, launch.args, launch.env);
100
+ if (listing.error) throw new Error(`Die Prozesse des bash-Aufrufs ließen sich nicht ermitteln: ${listing.error.message}`);
101
+ const winpids = [...new Set(listing.stdout.split(/\s+/).filter((pid) => /^\d+$/.test(pid)))];
102
+ if (winpids.length === 0) return;
103
+ await execFileText("taskkill", ["/F", "/T", ...winpids.flatMap((pid) => ["/PID", pid])]);
104
+ const alive = winpids.filter((pid) => processExists(Number(pid)));
105
+ if (alive.length > 0) throw new Error(`Die Prozesse ${alive.join(", ")} des bash-Aufrufs laufen nach taskkill weiter`);
106
+ };
107
+
80
108
  const darwinProcessGroupExists = (pid: number): Promise<boolean> =>
81
109
  new Promise((resolve, reject) => {
82
110
  execFile("/bin/ps", ["-axo", "pgid=,stat="], { timeout: 5_000, maxBuffer: 4 * 1024 * 1024 }, (error, stdout) => {
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { createHash } from "node:crypto";
2
+ import { createHash, randomUUID } from "node:crypto";
3
3
  import { readFile, stat } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import {
@@ -12,7 +12,7 @@ import {
12
12
  import { bashLaunch } from "./bash-launch.js";
13
13
  import type { WorkspaceProcessContext } from "./context.js";
14
14
  import { WorkspaceOperationError } from "./errors.js";
15
- import { processGroupExists, stopProcessTree } from "./managed-process.js";
15
+ import { MSYS_CALL_ENV, processGroupExists, stopMsysCall, stopProcessTree } from "./managed-process.js";
16
16
  import type { WorkspaceModuleFactory, WorkspaceOperation } from "./module.js";
17
17
  import { sandboxedLaunch } from "./process-sandbox.js";
18
18
  import { stopUidProcesses } from "./session-ident.js";
@@ -125,10 +125,14 @@ export const createSandboxTools = async (
125
125
  let toolOperation: Promise<void> = Promise.resolve();
126
126
  let shuttingDown = false;
127
127
  const processGroups = new Set<number>();
128
+ const msysCalls = new Map<number, { readonly bash: string; readonly env: NodeJS.ProcessEnv; readonly marker: string }>();
128
129
 
129
130
  const killProcessGroup = async (pid: number): Promise<void> => {
130
131
  if (onWindows) {
131
132
  stopProcessTree(pid);
133
+ const call = msysCalls.get(pid);
134
+ msysCalls.delete(pid);
135
+ if (call) await stopMsysCall(call.bash, call.env, call.marker);
132
136
  processGroups.delete(pid);
133
137
  return;
134
138
  }
@@ -268,17 +272,19 @@ export const createSandboxTools = async (
268
272
  const launch = await sandboxedLaunch(context, bash);
269
273
  if (shuttingDown) throw new Error("Die Werkzeuge werden killed");
270
274
  options.signal?.throwIfAborted();
275
+ const marker = onWindows ? randomUUID() : undefined;
271
276
  return new Promise((resolve, reject) => {
272
277
  const child = spawn(launch.command, [...launch.args], {
273
278
  cwd: commandCwd,
274
279
  detached: !onWindows,
275
280
  stdio: ["ignore", "pipe", "pipe"],
276
- env: bash.env,
281
+ env: marker === undefined ? bash.env : { ...bash.env, [MSYS_CALL_ENV]: marker },
277
282
  uid: context.uid,
278
283
  gid: context.gid,
279
284
  windowsHide: true,
280
285
  });
281
286
  if (child.pid) processGroups.add(child.pid);
287
+ if (child.pid && marker !== undefined && context.bash !== undefined) msysCalls.set(child.pid, { bash: context.bash, env: context.env, marker });
282
288
  child.stdout?.on("data", options.onData);
283
289
  child.stderr?.on("data", options.onData);
284
290
  let killed = false;
@@ -0,0 +1,31 @@
1
+ import type { AccessProjectionContribution, JsonValue } from "@ragents/engine";
2
+ import { ACTOR_INVOCATIONS_STATE_ID, ACTOR_PROGRAMS_STATE_ID } from "@ragents/host/plugin-support/actor-programs/contract.js";
3
+
4
+ const record = (value: JsonValue | undefined): Record<string, JsonValue | undefined> | undefined =>
5
+ value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
6
+
7
+ /** Ohne runs.inspect bleiben von einem Programm Name, Actor und Ansichten; Quellen, Funktionen und Verzeichnisse fehlen. */
8
+ const visibleProgram = (state: JsonValue): JsonValue => {
9
+ const program = record(record(state)?.program);
10
+ if (!program) return state;
11
+ const views = Array.isArray(program.views) ? program.views.map((value) => {
12
+ const view = record(value);
13
+ return view ? { id: view.id, key: view.key, title: view.title, visible: view.visible, placements: view.placements } : value;
14
+ }) : [];
15
+ return {
16
+ version: 1,
17
+ program: {
18
+ name: program.name,
19
+ title: program.title,
20
+ actorId: program.actorId,
21
+ actorHandle: program.actorHandle,
22
+ revision: program.revision,
23
+ views,
24
+ },
25
+ };
26
+ };
27
+
28
+ export const actorProgramAccessProjections: readonly AccessProjectionContribution[] = [
29
+ { id: ACTOR_PROGRAMS_STATE_ID, state: (entry) => visibleProgram(entry.state), chatEvent: () => undefined },
30
+ { id: ACTOR_INVOCATIONS_STATE_ID, state: (entry) => ({ version: 1, revision: entry.updatedAt }), chatEvent: () => undefined },
31
+ ];
@@ -13,6 +13,7 @@ import { actorProgramSummary } from "./prompts.js";
13
13
  import { sandboxServicesToken } from "@ragents/host/plugin-support/workspace-sandbox-host.js";
14
14
  import { actorProgramsToken } from "@ragents/host/plugin-support/actor-programs/service.js";
15
15
  import { createProjectDiagnostics } from "./project-diagnostics.js";
16
+ import { actorProgramAccessProjections } from "./access-projections.js";
16
17
  const actorProgramsPlugin = (pluginHost: PluginHost): RAgentsPlugin => ({
17
18
  manifest: { id: "ragents.actor-programs" },
18
19
  register: (host) => {
@@ -45,6 +46,7 @@ const actorProgramsPlugin = (pluginHost: PluginHost): RAgentsPlugin => ({
45
46
  deleteSession: async ({ runId }) => { diagnostics.forget(runId); await runtime.deleteSession(runId); }, shutdown: () => runtime.shutdown(),
46
47
  });
47
48
  host.prompts(actorProgramSummary);
49
+ host.accessProjections(...actorProgramAccessProjections);
48
50
  host.functions(createControlsToolContributor(), ...createActorProgramToolContributors(runtime, diagnostics));
49
51
  },
50
52
  });
@@ -6,7 +6,6 @@ import {
6
6
  import { runViewFrom, type RunView } from "@ragents/web/run-view";
7
7
  import { askPayloadOf, ASK_PLUGIN_ID } from "../ask-payload";
8
8
  import { AskActionView } from "./AskActionView";
9
- import { QuestionSection } from "./QuestionSection";
10
9
 
11
10
  const pendingQuestions = (view: RunView) =>
12
11
  view.actions.filter((action) => action.owner === ASK_PLUGIN_ID && action.status === "pending");
@@ -34,11 +33,6 @@ const configuredPlugin = (descriptor: WebPluginDescriptor): WebPlugin => ({
34
33
  owner: ASK_PLUGIN_ID,
35
34
  View: AskActionView,
36
35
  }],
37
- cardSections: [{
38
- id: "ragents.ask.questions",
39
- order: 100,
40
- Section: QuestionSection,
41
- }],
42
36
  attention: [{
43
37
  id: "ragents.ask.pending-questions",
44
38
  assess: attentionFor,
@@ -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 maximum = Math.max(minimum, Math.floor(container.getBoundingClientRect().height / scale * 0.9));
329
- setGeometry((previous) => previous.minimum === minimum && previous.maximum === maximum && previous.scale === scale ? previous : { minimum, maximum, scale });
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 = [...messages].reverse().find((message) => message.role === "action" && message.action?.status === undefined);
20
- if (waiting) return { kind: "waiting", text: `Wartet auf Eingabe: ${firstLine(waiting.text) || "Antwort erwartet"}` };
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");
@@ -33,10 +33,11 @@ import {
33
33
  type WorkspaceClientDescription,
34
34
  } from "../contract";
35
35
 
36
+ /** mismatch: Arbeitsplatz und Server tragen einen anderen Stand von Executor oder Bundles; nur ein Update einer Seite behebt es. */
36
37
  export type WorkspaceClientStatus =
37
38
  | { kind: "idle" }
38
39
  | { kind: "registered" }
39
- | { kind: "failed"; message: string };
40
+ | { kind: "failed"; message: string; mismatch: boolean };
40
41
 
41
42
  /** Was der Arbeitsplatz über sich sagt; ob seine Bash rg findet, ermittelt er selbst bei jeder Anmeldung. */
42
43
  export interface WorkspaceClientIdentity extends Omit<WorkspaceClientDescription, "ripgrep"> {
@@ -76,6 +77,18 @@ const SIGN_OFF_TIMEOUT_MS = 3_000;
76
77
 
77
78
  const messageOf = (cause: unknown): string => cause instanceof Error ? cause.message : String(cause);
78
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
+
79
92
  /** Der echte Pfad des nächsten vorhandenen Elternordners, um die noch fehlenden Namen ergänzt. */
80
93
  const realPathOf = async (target: string): Promise<string> => {
81
94
  try {
@@ -126,7 +139,7 @@ const loadedContributions = async (
126
139
  return Promise.all(wanted.map(async ({ plugin, stand }) => {
127
140
  if (!PLUGIN_ID_PATTERN.test(plugin)) throw new Error(`Der Server nennt ${plugin} als Plugin; das ist keine Kennung eines Plugins`);
128
141
  const loaded = await loadExecutorContribution(plugin, contributionFileOf(hostRoot, plugin), stand).catch((cause: unknown) => {
129
- throw new Error(`${messageOf(cause)}. Der Host dieses Arbeitsplatzes (${hostRoot}) muss dieselben Bundles tragen wie der Server: `
142
+ throw new StandMismatch(`${messageOf(cause)}. Der Host dieses Arbeitsplatzes (${hostRoot}) muss dieselben Bundles tragen wie der Server: `
130
143
  + "die RAgents-Erweiterung beziehungsweise das Paket @schlenkr/ragents in der Fassung des Servers, im Checkout pnpm build:plugins");
131
144
  });
132
145
  return prepareExecutorContribution(loaded, pluginToolsDirectory(hostDataDirectory(), plugin));
@@ -196,7 +209,7 @@ export class WorkspaceClient {
196
209
  try {
197
210
  await this.#connected();
198
211
  } catch (cause) {
199
- 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 });
200
213
  return;
201
214
  }
202
215
  if (attachment === this.#attachment) await this.#announce();
@@ -302,7 +315,7 @@ export class WorkspaceClient {
302
315
  executor: WORKSPACE_EXECUTOR_VERSION,
303
316
  }, { timeoutMs: REGISTER_TIMEOUT_MS }).catch((cause: unknown) => {
304
317
  if (!(cause instanceof RpcError) || cause.code !== RPC_ERROR_CODES.methodNotFound) throw cause;
305
- throw new Error(`Der Server kennt ${workspaceContracts.clients.contributions.id} nicht; er ist älter als dieser Arbeitsplatz `
318
+ throw new StandMismatch(`Der Server kennt ${workspaceContracts.clients.contributions.id} nicht; er ist älter als dieser Arbeitsplatz `
306
319
  + `mit dem Executor ${WORKSPACE_EXECUTOR_VERSION}. Den Server auf die Fassung des Arbeitsplatzes aktualisieren.`);
307
320
  });
308
321
  const { contributions } = await this.#built(attachment, wanted);
@@ -319,7 +332,7 @@ export class WorkspaceClient {
319
332
  }, { timeoutMs: REGISTER_TIMEOUT_MS });
320
333
  if (attachment === this.#attachment) this.#set({ kind: "registered" });
321
334
  } catch (cause) {
322
- 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) });
323
336
  }
324
337
  }
325
338
 
@@ -327,7 +340,7 @@ export class WorkspaceClient {
327
340
  try {
328
341
  await this.transport.rpc.call(workspaceContracts.clients.unregister, { id: this.id }, { timeoutMs: SIGN_OFF_TIMEOUT_MS });
329
342
  } catch (cause) {
330
- this.#set({ kind: "failed", message: messageOf(cause) });
343
+ this.#set({ kind: "failed", message: messageOf(cause), mismatch: false });
331
344
  }
332
345
  }
333
346
 
@@ -16,6 +16,9 @@ export interface ProcessSandboxSetting {
16
16
  readonly network: readonly string[];
17
17
  }
18
18
 
19
+ /** So schaltet der Betreiber die Prozess-Sandbox in der Profildatei ab. */
20
+ export const PROCESS_SANDBOX_OFF = 'PROCESS_SANDBOX: "off" in der Sektion ragents.workspace';
21
+
19
22
  /** Ohne Angabe ist die Sandbox an; abschalten geht nur ausdrücklich mit "off". */
20
23
  export const processSandboxSetting = (): ProcessSandboxSetting => {
21
24
  const mode = env.optional("PROCESS_SANDBOX") ?? "on";
@@ -23,11 +23,11 @@ import { WORKSPACE_BINDING_OPTION_ID, WORKSPACE_METADATA_ID } from "../contract.
23
23
  import { bindingOf, executorShellChapter, sessionMetadataOf, workspaceBindingOption, workspaceLocation, workspaceOwnerOf } from "./binding.js";
24
24
  import { createBrowseChannel, createBrowseMethods } from "./browse-route.js";
25
25
  import { clientMethods, WorkspaceClientRegistry } from "./clients.js";
26
- import { bashSetting, bashTimeoutSetting, processSandboxSetting, rgSetting, workspaceConfigDescriptors } from "./config.js";
26
+ import { bashSetting, bashTimeoutSetting, PROCESS_SANDBOX_OFF, processSandboxSetting, rgSetting, workspaceConfigDescriptors } from "./config.js";
27
27
  import { RunWorkspaceRuntime } from "./runtime.js";
28
28
 
29
29
  const warnWithoutSandbox = (): void => {
30
- console.warn("Prozess-Sandbox ausgeschaltet (PROCESS_SANDBOX: \"off\" in der Sektion ragents.workspace): Prozesse der Runs laufen auf dem Server ohne Sandbox.");
30
+ console.warn(`Prozess-Sandbox ausgeschaltet (${PROCESS_SANDBOX_OFF}): Prozesse der Runs laufen auf dem Server ohne Sandbox.`);
31
31
  };
32
32
 
33
33
  const ragentsWorkspacePlugin = (skillPaths: () => Promise<readonly string[]>): RAgentsPlugin => ({
@@ -60,6 +60,7 @@ const ragentsWorkspacePlugin = (skillPaths: () => Promise<readonly string[]>): R
60
60
  network: sandboxSetting.network,
61
61
  serverAddress: host.service(hostAddressToken)(),
62
62
  dataDirectory: path.dirname(host.storage.sessionsRoot),
63
+ disableSetting: PROCESS_SANDBOX_OFF,
63
64
  })
64
65
  : undefined;
65
66
  const bash = bashSetting();
@@ -1,6 +0,0 @@
1
- import{a as De,r as t,j as e,D as oe,b as ce,d as G,B as b,X as Le,M as Pe,C as de,e as Me,f as Oe,g as He,h as Ie,u as Be,i as _e,k as Fe,l as Ue,m as Ke,A as Te,n as qe,o as We,p as $e,U as Qe,P as Ve,W as Ge,R as Xe,q as ae,s as V,t as Je,v as Ye,w as Ze,x as et,y as tt,z as st,F as nt,Q as rt,G as at,H as it}from"./mermaid-HWGCJPDP-DfsU5ofC.js";/**
2
- * @license lucide-react v1.46.0 - ISC
3
- *
4
- * This source code is licensed under the ISC license.
5
- * See the LICENSE file in the root directory of this source tree.
6
- */const ue={name:"circle-question-mark",size:24,node:[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],aliases:["help-circle","circle-help"]};ue.node;const lt=De(ue);function ot(n,r,a,u){if(!r||n.source!==r||n.origin!==a)return;const i=n.data;if(!(!i||typeof i!="object"||Array.isArray(i))&&"type"in i){if(i.type==="ragents:help-ready")return{type:"ready"};if(i.type==="ragents:start-sample"&&"entry"in i&&typeof i.entry=="string"&&u.includes(i.entry))return{type:"start",entry:i.entry}}}function ct({onClose:n,sampleEntries:r,onStartSample:a}){const u=t.useRef(null),i=t.useRef(null),h=t.useRef(void 0),x=t.useRef(!1);t.useEffect(()=>()=>{var l;return(l=h.current)==null?void 0:l.call(h)},[]),t.useEffect(()=>{var j;const l=(j=i.current)==null?void 0:j.contentWindow;if(!l)return;const o=()=>l.postMessage({type:"ragents:help-context",canStartSamples:r.length>0,entries:r},window.location.origin),p=C=>{const c=ot(C,l,window.location.origin,r);(c==null?void 0:c.type)==="ready"&&o(),(c==null?void 0:c.type)==="start"&&!x.current&&(x.current=!0,a(c.entry))};return window.addEventListener("message",p),o(),()=>window.removeEventListener("message",p)},[a,r]);const m=()=>{var C;(C=h.current)==null||C.call(h);const l=i.current,o=l==null?void 0:l.contentDocument,p=o==null?void 0:o.defaultView;if(!l||!o||!p)return;o.documentElement.dataset.helpEmbedded="true";for(const c of o.querySelectorAll("a[href]"))new URL(c.href).origin!==window.location.origin&&(c.target="_blank",c.rel="noopener noreferrer");const j=c=>{var v;if(!c.defaultPrevented){if(c.key==="Escape")c.preventDefault(),n();else if(c.key==="Tab"){const g=[...o.querySelectorAll("a[href], button, input, select, textarea, summary, [tabindex]")].filter(y=>y.tabIndex>=0&&!y.matches(":disabled")&&y.getClientRects().length>0),O=c.shiftKey?g[0]:g.at(-1);if(!g.length||o.activeElement===O||o.activeElement===o.body){if(!c.shiftKey&&o.activeElement===o.body&&g.length)return;c.preventDefault(),(v=u.current)==null||v.focus()}}}};p.addEventListener("keydown",j),h.current=()=>p.removeEventListener("keydown",j)};return e.jsx(oe,{open:!0,onOpenChange:l=>{l||n()},children:e.jsxs(ce,{className:"gap-0 p-0",initialFocus:u,scope:"page",showCloseButton:!1,size:"full",children:[e.jsx(G,{className:"sr-only",children:"Hilfe"}),e.jsx(b,{"aria-label":"Hilfe schließen",className:"absolute top-3 right-4.5 z-1 rounded-full",onClick:n,ref:u,size:"icon",variant:"outline",children:e.jsx(Le,{})}),e.jsx("iframe",{className:"min-h-0 w-full min-w-0 flex-[1_1_0] bg-background",onLoad:m,ref:i,src:"/help/index.html",tabIndex:0,title:"RAgents: Homepage und Dokumentation"})]})})}const dt="button:not([disabled]), a[href], input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])",fe="pointer-events-auto shadow-pop";function ut({onBusy:n,onClose:r,onOpen:a,open:u,panels:i,registry:h,runs:x}){const m=t.useRef(null);return t.useEffect(()=>{if(!u)return;const l=requestAnimationFrame(()=>{var p;const o=m.current;!o||o.contains(document.activeElement)||(p=o.querySelector(dt))==null||p.focus({preventScroll:!0})});return()=>cancelAnimationFrame(l)},[u]),e.jsxs(Pe,{className:"pointer-events-none flex h-[min(900px,100%)] w-[min(1440px,100%)] flex-col rounded-none bg-transparent p-0 ring-0",keepMounted:!0,onClose:r,open:u,scope:"workspace",size:"full",children:[e.jsx(G,{className:"sr-only",children:"Übersicht"}),e.jsxs("div",{className:"flex min-h-0 min-w-0 flex-1 gap-4 max-[900px]:flex-col",id:"app-overview",ref:m,children:[i.map(({id:l,Panel:o})=>e.jsx(ft,{id:l,onBusy:n,onClose:r,onOpen:a,open:u,Panel:o,registry:h},l)),x&&e.jsx(ht,{...x})]})]})}function ft({id:n,onBusy:r,onClose:a,onOpen:u,open:i,Panel:h,registry:x}){const m=t.useCallback(l=>r(n,l),[n,r]);return t.useEffect(()=>()=>r(n,!1),[n,r]),e.jsx(de,{className:`${fe} min-h-0 min-w-0 flex-1 gap-0 p-0`,children:e.jsx(h,{onBusy:m,onClose:a,onOpen:u,open:i,registry:x})})}function ht({activeId:n,canCreate:r,canDelete:a,onCreate:u,onDelete:i,onOpen:h,registry:x,sessions:m,seenRevisions:l,unreachable:o}){const[p,j]=t.useState(),[C,c]=t.useState(!1),[v,g]=t.useState(()=>new Set),[O,y]=t.useState(!1),[N,B]=t.useState(!1),_=()=>{c(!1),g(new Set)},w=f=>{g(z=>{const H=new Set(z);return H.delete(f)||H.add(f),H})},E=async f=>{B(!0),j(void 0);try{await i(f)}catch(z){j(z instanceof Error?z.message:String(z))}finally{B(!1)}},S=async()=>{await E(m.filter(f=>v.has(f.id)).map(f=>f.id)),y(!1),_()};return e.jsx("section",{"aria-labelledby":"overview-runs-title",className:"flex min-h-0 min-w-0 flex-1 flex-col max-[900px]:flex-[1_1_50%]",children:e.jsxs(de,{className:`${fe} min-h-0 flex-1 gap-workspace-inset overflow-y-auto overscroll-contain pt-workspace-inset pr-6 pb-6 pl-6 max-[900px]:px-3 max-[900px]:pt-3.5 max-[900px]:pb-4`,children:[e.jsxs("header",{className:"flex flex-wrap items-center gap-x-3.5 gap-y-2.5",children:[e.jsxs("h2",{className:"text-[0.95rem] font-semibold",id:"overview-runs-title",children:["Runs ",e.jsx("span",{className:"ml-1 text-[0.8rem] font-medium tabular-nums text-muted-foreground",children:m.length})]}),o&&e.jsx("span",{className:"text-[0.72rem] font-medium text-destructive",role:"alert",children:"Server nicht erreichbar"}),a&&m.length>0&&e.jsx("div",{className:"ml-auto flex flex-wrap items-center gap-1",children:C?e.jsxs(e.Fragment,{children:[e.jsx(b,{onClick:()=>g(new Set(m.map(f=>f.id))),size:"sm",variant:"ghost",children:"Alle"}),e.jsx(b,{onClick:()=>g(new Set),size:"sm",variant:"ghost",children:"Keine"}),e.jsxs(b,{className:"ml-auto",disabled:v.size===0||N,onClick:()=>y(!0),size:"sm",variant:"destructive",children:["Löschen (",v.size,")"]}),e.jsx(b,{onClick:_,size:"sm",variant:"ghost",children:"Fertig"})]}):e.jsx(b,{onClick:()=>c(!0),size:"sm",variant:"ghost",children:"Auswählen"})})]}),p&&e.jsx("p",{className:"text-[0.72rem] font-medium text-destructive",role:"alert",children:p}),e.jsx("p",{className:"mb-5.5 text-[0.75rem] text-muted-foreground",children:"Nach letzter Aktivität im Run. Neue Aktivität ist seit Deinem letzten Ansehen hinzugekommen."}),e.jsx(Me,{seenRevisions:l,activeId:n,onCreate:r?u:void 0,onDelete:a?f=>void E([f]):void 0,onSelect:h,onToggleSelected:w,registry:x,selectMode:C,selectedIds:v,sessions:m}),O&&e.jsx(oe,{open:!0,onOpenChange:f=>{!f&&!N&&y(!1)},children:e.jsxs(ce,{scope:"page",showCloseButton:!1,size:"small",children:[e.jsxs(Oe,{children:[e.jsx(G,{children:"Runs löschen"}),e.jsx(He,{children:v.size===1?"Ein Run wird endgültig gelöscht.":`${v.size} Runs werden endgültig gelöscht.`})]}),e.jsxs(Ie,{children:[e.jsx(b,{disabled:N,onClick:()=>y(!1),variant:"outline",children:"Abbrechen"}),e.jsx(b,{disabled:N,onClick:()=>void S(),children:N?"Löscht ...":`Löschen (${v.size})`})]})]})})]})})}const ie="flex min-h-0 min-w-0 flex-1 flex-col",mt="inline-flex w-header min-h-header items-center justify-center border-r border-border text-primary transition-colors duration-100 not-disabled:cursor-pointer hover:not-disabled:bg-accent focus-visible:outline-2 focus-visible:outline-ring/60 focus-visible:-outline-offset-3 aria-expanded:bg-primary aria-expanded:text-primary-foreground aria-pressed:bg-primary aria-pressed:text-primary-foreground disabled:opacity-45";function xt(){var re;const n=Be(),r=_e((re=n.user)==null?void 0:re.id),a=n.can("runs.read"),u=a&&n.can("runs.write"),i=n.can("runs.inspect"),h=a&&n.can("runs.delete"),x=Fe(),m=t.useRef(null),l=t.useRef(null),[o,p]=t.useState(null),[j,C]=t.useState(null),[c,v]=t.useState(null),[g,O]=t.useState(null),[y,N]=t.useState(null),[B,_]=t.useState([]),[w,E]=t.useState(),[S,f]=t.useState(),[z,H]=t.useState(),[U,X]=t.useState(!1),[K,T]=t.useState(!1),[A,J]=t.useState(!1),[Y,D]=t.useState(),[me,xe]=t.useState(!1),[pe,ve]=t.useState(()=>new Set),[be,Z]=t.useState(!1),I=t.useRef(void 0),q=t.useRef(!1),P=t.useCallback(()=>{if(!a)return Promise.resolve();if(I.current)return q.current=!0,I.current;const s=async()=>{do{q.current=!1;try{_(await Ye()),Z(!1)}catch{Z(!0)}}while(q.current)};return I.current=s().finally(()=>{I.current=void 0}),I.current},[a]);t.useEffect(()=>{if(!a||w)return;P();const s=Ue.subscribe(Ke.channels.runs,{},()=>void P()),d=setInterval(()=>void P(),5e3);return()=>{s(),clearInterval(d)}},[w,a,P]);const L=t.useCallback((s=!1)=>{var d;J(!1),s&&((d=m.current)==null||d.focus({preventScroll:!0}))},[]),W=t.useCallback(()=>{D(void 0),xe(!0),J(!0)},[]),$=t.useCallback(()=>{A?L(!0):W()},[L,W,A]);t.useEffect(()=>{const s=d=>{if(d.defaultPrevented||d.repeat||d.altKey||d.shiftKey||d.key.toLowerCase()!=="i"||d.metaKey===d.ctrlKey)return;const R=m.current;!R||R.closest("[inert]")||(d.preventDefault(),$())};return window.addEventListener("keydown",s),()=>window.removeEventListener("keydown",s)},[$]);const ee=t.useCallback((s,d)=>ve(R=>{if(R.has(s)===d)return R;const M=new Set(R);return d?M.add(s):M.delete(s),M}),[]),te=B,ge=te.find(s=>s.id===S),we=t.useCallback(()=>{D(void 0),X(!0)},[]),je=t.useCallback(()=>T(!1),[]),ye=t.useCallback(()=>{X(!1),requestAnimationFrame(()=>{var s;return(s=l.current)==null?void 0:s.focus()})},[]),Se=e.jsx(b,{"aria-label":"Einstellungen öffnen",onClick:we,ref:l,size:"icon-lg",title:"Einstellungen",variant:"ghost",children:e.jsx(Ze,{})}),ke=s=>{f(s),L(!0)},Ce=()=>{D(void 0),L(!0),E({id:crypto.randomUUID(),title:"Neuer Run",updatedAt:Date.now()})},Re=async s=>{const d=await Promise.allSettled(s.map(F=>et(F))),R=s.filter((F,Ae)=>d[Ae].status==="fulfilled");S!==void 0&&R.includes(S)&&f(void 0),await P();const M=d.find(F=>F.status==="rejected");if(M)throw M.reason};if(x.status==="loading")return e.jsx("div",{className:"m-auto p-6 text-muted-foreground",children:"Profil wird geladen."});if(x.status==="failed")return e.jsx("div",{className:"flex h-full",children:e.jsx("main",{className:"relative z-1 flex min-h-0 min-w-0 flex-1 bg-[image:var(--surface-backdrop)]",children:e.jsxs("div",{className:ie,children:[e.jsx("header",{className:"flex min-h-[42px] flex-none items-center gap-3 border-b border-border-soft pr-0.5 pb-2 pl-2.5",children:e.jsx("div",{className:"flex min-w-0 flex-1 items-center gap-3 text-[0.82rem] font-semibold text-foreground",children:"Profil nicht verfügbar"})}),e.jsxs(Te,{className:"m-auto max-w-[560px] gap-3 p-6",variant:"destructive",children:[e.jsx("p",{children:x.error}),e.jsx(b,{variant:"outline",onClick:()=>window.location.reload(),children:"Neu laden"})]})]})})});const k=x.registry,se=k.overviewPanels.filter(s=>s.readRight===void 0||n.can(s.readRight)),Q=se.filter(s=>s.placement!=="toolbar"),Ne=se.filter(s=>s.placement==="toolbar"),Ee=a||Q.length>0,ne=Q.some(s=>pe.has(s.id)),ze=qe(a?S:void 0,A,z);return e.jsxs("div",{className:"flex h-full flex-col",children:[e.jsxs("header",{className:"relative z-[80] flex h-header flex-none items-stretch border-b border-border bg-shell shadow-bar",children:[Ee&&e.jsx("div",{className:"relative flex flex-none self-stretch",children:e.jsx("button",{"aria-controls":"app-overview","aria-expanded":A,"aria-haspopup":"dialog","aria-keyshortcuts":"Meta+I Control+I","aria-label":A?"Übersicht schließen":"Übersicht öffnen",className:mt,onClick:$,ref:m,title:`Übersicht (Cmd+I / Ctrl+I)${ne?": Bearbeitung läuft":""}`,type:"button",children:ne?e.jsx(We,{"aria-hidden":!0,className:"size-[18px]"}):e.jsx($e,{size:22})})}),Ne.map(({id:s,Panel:d})=>e.jsx(pt,{id:s,onBusy:ee,onClose:()=>D(void 0),onOpen:()=>{!U&&!K&&!w&&(L(),D(s))},open:Y===s,Panel:d,registry:k,userLocation:ze},s)),e.jsx("div",{"aria-label":"Run-Titelleiste",className:"flex min-w-0 flex-1 items-stretch overflow-x-auto no-scrollbar focus-visible:outline-2 focus-visible:outline-ring/60 focus-visible:-outline-offset-3 max-md:in-[header:has([data-slot=overseer-toolbar][data-open=true])]:flex-[0_1_0]",ref:p,role:"region",tabIndex:0}),e.jsx(Qe,{}),e.jsxs("div",{className:"flex min-w-0 flex-[0_1_auto] items-stretch bg-shell has-data-[workspace-state=expanded]:w-[var(--workspace-panel-width,560px)] has-data-[workspace-state=expanded]:border-l has-data-[workspace-state=expanded]:border-border",children:[e.jsx("div",{className:"flex min-w-0 flex-1 empty:hidden",ref:C}),e.jsxs("div",{className:"flex flex-none items-center gap-1.5 px-2 max-md:gap-0.5 max-md:px-1",children:[n.can("settings.read")&&Se,i&&e.jsx(b,{"aria-haspopup":"dialog","aria-label":"Hilfe öffnen",onClick:()=>{D(void 0),T(!0)},size:"icon-lg",title:"Hilfe",variant:"ghost",children:e.jsx(lt,{})})]})]})]}),e.jsx(Ve,{failures:x.failures}),e.jsx(Ge.Provider,{value:g,children:e.jsx("div",{className:"relative flex min-h-0 flex-1 flex-col",ref:O,children:e.jsxs(Xe.Provider,{value:y,children:[e.jsxs("div",{className:"relative flex h-auto min-h-0 flex-1",ref:N,children:[e.jsx("main",{className:"relative z-1 flex min-h-0 min-w-0 flex-1 bg-[image:var(--surface-backdrop)] @container/chat-content",children:a&&S?e.jsx(ae,{headerContainer:o,workspaceHeaderContainer:j,statusContainer:c,onLocationChange:H,onViewed:r.markViewed,viewing:!A&&!U&&!K&&!w&&!Y,registry:k,session:ge??{id:S,title:"Neuer Run",updatedAt:Date.now()}},S):e.jsx("div",{className:ie,children:e.jsx("div",{className:"m-auto text-center text-muted-foreground",children:a?u?"Öffne oben links die Übersicht, um einen Run zu wählen oder einen neuen zu starten.":"Öffne oben links die Übersicht, um einen Run zu wählen. Du hast Lesezugriff.":"Für dieses Benutzerkonto sind keine Runs freigegeben."})})}),me&&e.jsx(ut,{onOpen:W,onBusy:ee,onClose:()=>L(!0),open:A,panels:Q,registry:k,runs:a?{activeId:S,canCreate:u&&(n.can("runs.create")||k.scriptEntries.some(s=>V(n,s.id))),canDelete:h,onCreate:Ce,onDelete:Re,onOpen:ke,registry:k,sessions:te,seenRevisions:r.revisions,unreachable:be}:void 0}),w&&e.jsx(ae,{onStarted:()=>{f(w.id),E(void 0),P()},registry:k,session:w,startDialog:{onClose:()=>E(void 0),initialEntryId:w.startEntryId}},w.id),U&&e.jsx(Je,{onClose:ye,registry:k}),i&&K&&e.jsx(ct,{onClose:je,sampleEntries:k.scriptEntries.filter(s=>V(n,s.id)).map(s=>s.id),onStartSample:s=>{!V(n,s)||!k.scriptEntries.some(d=>d.id===s)||(T(!1),D(void 0),L(),E({id:crypto.randomUUID(),title:"Neuer Run",updatedAt:Date.now(),startEntryId:s}))}})]}),e.jsx("footer",{"aria-label":"Run-Statusleiste",className:"relative flex h-statusbar w-full flex-none items-stretch pl-3 before:pointer-events-none before:absolute before:inset-0 before:z-[2] before:border-t before:border-border before:bg-shell before:shadow-status before:backdrop-blur-[16px] before:content-['']",ref:v})]})})})]})}function pt({id:n,onBusy:r,Panel:a,...u}){const i=t.useCallback(h=>r(n,h),[n,r]);return t.useEffect(()=>()=>r(n,!1),[n,r]),e.jsx(a,{...u,onBusy:i})}tt();const he=document.getElementById("root");if(!he)throw new Error("Root-Element fehlt");const le=st.createRoot(he);try{const n=nt(window);le.render(e.jsx(t.StrictMode,{children:e.jsx(rt,{children:e.jsx(at,{children:e.jsx(xt,{})})})}))}catch(n){le.render(e.jsxs(it,{children:[e.jsx("h1",{className:"my-3 text-[1.5rem]",children:"Darstellung konnte nicht geladen werden"}),e.jsx("p",{className:"mb-6 leading-normal",role:"alert",children:n instanceof Error?n.message:String(n)}),e.jsx(b,{onClick:()=>window.location.reload(),variant:"outline",children:"Erneut laden"})]}))}