@schlenkr/ragents 0.1.0 → 0.1.1

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 (94) hide show
  1. package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
  2. package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
  3. package/apps/server/src/plugin-support/run-scripts.ts +4 -1
  4. package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
  5. package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
  6. package/apps/web/src/App.tsx +0 -10
  7. package/apps/web/src/PluginChat.tsx +8 -8
  8. package/apps/web/src/PluginRegistry.tsx +0 -12
  9. package/apps/web/src/StartSurface.tsx +2 -2
  10. package/apps/web/src/ThemeSettings.tsx +1 -1
  11. package/apps/web/src/column/ColumnApp.tsx +83 -7
  12. package/apps/web/src/column/clipboard.ts +64 -0
  13. package/apps/web/src/column/host-contract.ts +10 -0
  14. package/apps/web/src/column.tsx +2 -0
  15. package/apps/web/src/panel/LoginForm.tsx +9 -3
  16. package/apps/web/src/panel/OverviewPage.tsx +187 -62
  17. package/apps/web/src/panel/PanelDialogs.tsx +115 -0
  18. package/apps/web/src/panel/PanelPage.tsx +1 -1
  19. package/apps/web/src/panel/SettingsPage.tsx +75 -79
  20. package/apps/web/src/panel/contract.ts +17 -9
  21. package/apps/web/src/panel/target-state.ts +27 -2
  22. package/apps/web/src/ui/theme.css +4 -0
  23. package/package.json +2 -2
  24. package/packages/ragents/src/plugin-host.ts +5 -2
  25. package/packages/ragents/src/plugin-types.ts +3 -2
  26. package/packages/workspace-executor/src/context.ts +4 -0
  27. package/packages/workspace-executor/src/executor.ts +9 -2
  28. package/packages/workspace-executor/src/index.ts +2 -0
  29. package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
  30. package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
  31. package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
  32. package/packages/workspace-executor/src/language-server/contract.ts +7 -3
  33. package/packages/workspace-executor/src/language-server/host.ts +275 -138
  34. package/plugins/ragents.actor-programs/contract.ts +1 -1
  35. package/plugins/ragents.actor-programs/prompt.hbs +4 -4
  36. package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
  37. package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
  38. package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
  39. package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
  40. package/plugins/ragents.actor-programs/web/api.ts +1 -10
  41. package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
  42. package/plugins/ragents.orchestration/canvas.hbs +28 -44
  43. package/plugins/ragents.orchestration/contract.ts +17 -287
  44. package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
  45. package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
  46. package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
  47. package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
  48. package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
  49. package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
  50. package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
  51. package/plugins/ragents.orchestration/web/Center.tsx +28 -463
  52. package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
  53. package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
  54. package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
  55. package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
  56. package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
  57. package/plugins/ragents.orchestration/web/constants.ts +0 -2
  58. package/plugins/ragents.orchestration/web/index.tsx +0 -6
  59. package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
  60. package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
  61. package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
  62. package/plugins/ragents.reference/examples.ts +1 -2
  63. package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
  64. package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
  65. package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
  66. package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
  67. package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
  68. package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
  69. package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
  70. package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
  71. package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
  72. package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
  73. package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
  74. package/plugins/ragents.reference/walkthroughs.ts +14 -14
  75. package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
  76. package/scripts/package/publish-package.ts +4 -5
  77. package/scripts/workspace-client/run-workspace-client.ts +3 -1
  78. package/apps/web/src/MaterialSettings.tsx +0 -40
  79. package/apps/web/src/material-settings.ts +0 -104
  80. package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
  81. package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
  82. package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
  83. package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
  84. package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
  85. package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
  86. package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
  87. package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
  88. package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
  89. package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
  90. package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
  91. package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
  92. package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
  93. package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
  94. package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
@@ -308,7 +308,7 @@ export class ActorProgramRuntime implements ActorProgramsService, ActorProgramEx
308
308
  const clientFile = `view-${view.id}.mjs`;
309
309
  clients.push({ file: clientFile, javaScript: client.javaScript });
310
310
  views.push({ id: `${name}--${view.id}`, key: view.id, title: view.title ?? pkg.title, visible: previous?.views.find((item) => item.key === view.id)?.visible ?? true,
311
- placements: [{ kind: "canvas", anchorActorId: actorId, width: view.width ?? 440, height: view.height ?? 360 }], html: `<!doctype html><html class="${FRAME_DOCUMENT_CLASS}"><head><meta charset="utf-8"></head><body class="${FRAME_BODY_CLASS}"><div id="root" class="${FRAME_ROOT_CLASS}"></div></body></html>`, styles, clientFile });
311
+ placements: [{ kind: "canvas", anchorActorId: actorId }], html: `<!doctype html><html class="${FRAME_DOCUMENT_CLASS}"><head><meta charset="utf-8"></head><body class="${FRAME_BODY_CLASS}"><div id="root" class="${FRAME_ROOT_CLASS}"></div></body></html>`, styles, clientFile });
312
312
  }
313
313
  const viewSources = [...new Set((pkg.views ?? []).map((view) => path.dirname(path.resolve(directory, view.client))))];
314
314
  const frameStyles = viewSources.length > 0 ? await buildTailwind(viewSources) : "";
@@ -19,7 +19,6 @@ import type {
19
19
  } from "@aicontainer/web/PluginRegistry";
20
20
  import { runViewFrom, type RunAction } from "@aicontainer/plugins/ragents.orchestration/web/contract";
21
21
  import { useStageEntities } from "@aicontainer/plugins/ragents.orchestration/web/canvas-stage";
22
- import { useCanvasPresentation } from "@aicontainer/plugins/ragents.orchestration/web/tiled-view-settings";
23
22
  import { CANVAS_TILE_DRAG_TYPE } from "@aicontainer/plugins/ragents.orchestration/web/tile-docking";
24
23
  import {
25
24
  type RunApp,
@@ -368,7 +367,6 @@ export function ToolsPanel({ active, navigation, selection, session }: Workspace
368
367
 
369
368
  export function ActorProgramsHeader({ navigation, session }: SessionHeaderContext) {
370
369
  const canArrange = useAccess().can("runs.inspect");
371
- const tiled = useCanvasPresentation(session.session.id, runViewFrom(session.runView)).presentation.mode === "tiled";
372
370
  const stage = useStageEntities(session.session.id);
373
371
  const { closeFullscreen, fullscreenAppId, openFullscreen } = useActorPrograms();
374
372
  const apps = actorProgramApps(session).filter((app) => app.app.visible !== false);
@@ -383,16 +381,16 @@ export function ActorProgramsHeader({ navigation, session }: SessionHeaderContex
383
381
  <ToolbarItem
384
382
  as="button"
385
383
  className="max-w-[214px] border-r-0 font-bold hover:not-disabled:bg-transparent"
386
- draggable={tiled && canArrange}
384
+ draggable={canArrange}
387
385
  onDragStart={(event) => {
388
- if (!tiled || !canArrange) { event.preventDefault(); return; }
386
+ if (!canArrange) { event.preventDefault(); return; }
389
387
  event.dataTransfer.setData(CANVAS_TILE_DRAG_TYPE, `app:${app.id}`);
390
388
  event.dataTransfer.effectAllowed = "move";
391
389
  closeFullscreen();
392
390
  }}
393
391
  onClick={() => { closeFullscreen(); navigation.revealEntity({ type: "run-app", id: app.id }); }}
394
392
  aria-label={`Mini-App: ${app.title} von @${app.actorHandle}`}
395
- title={`Mini-App: ${app.title} von @${app.actorHandle}${tiled && canArrange ? ". Zum Andocken auf eine Kachel ziehen." : ""}`}
393
+ title={`Mini-App: ${app.title} von @${app.actorHandle}${canArrange ? ". Zum Andocken auf eine Kachel ziehen." : ""}`}
396
394
  type="button"
397
395
  >
398
396
  <span className={cn(quickIconClass, !stage.has(`app:${app.id}`) && "bg-transparent")}><LayoutGridIcon size={20} /></span>
@@ -1,7 +1,5 @@
1
- import { ACTOR_CARD_SIZE_LIMITS, DEFAULT_ACTOR_CARD_SIZE } from "@aicontainer/plugins/ragents.orchestration/web/card-size-settings";
2
1
  import { useAccess } from "@aicontainer/web/AccessContext";
3
- import { ChevronDownIcon, LayoutGridIcon } from "lucide-react";
4
- import { Button, cn, Spinner } from "@aicontainer/web/ui";
2
+ import { cn, Spinner } from "@aicontainer/web/ui";
5
3
  import { isRecord } from "@aicontainer/web/lib/guards";
6
4
  import { runActorFrom } from "@aicontainer/plugins/ragents.orchestration/web/contract";
7
5
  import type {
@@ -12,13 +10,9 @@ import type {
12
10
  } from "@aicontainer/web/PluginRegistry";
13
11
  import { HostConfirmation, pendingConfirmationFor, actorProgramApps, useActorPrograms } from "./AppsPanel";
14
12
  import { ActorViewFrame } from "./ActorViewFrame";
15
- import { ExpandIcon } from "./ExpandIcon";
16
13
  import { FunctionForm } from "./FunctionForm";
17
14
 
18
- const materialIconClass = "in-data-[surface=material]:grid in-data-[surface=material]:size-8 in-data-[surface=material]:place-items-center in-data-[surface=material]:rounded-[11px] in-data-[surface=material]:border in-data-[surface=material]:border-glass-edge/55 in-data-[surface=material]:bg-white/9 in-data-[surface=material]:text-foreground in-data-[surface=material]:shadow-glass-icon in-data-[surface=material]:[&>svg]:size-5";
19
15
  const cardClass = "flex h-full min-h-0 min-w-0 flex-col overflow-hidden rounded-panel border border-glass-edge bg-glass-app shadow-card backdrop-blur-[8px] in-data-[surface=material]:rounded-[inherit] in-data-[surface=material]:border-0 in-data-[surface=material]:bg-transparent in-data-[surface=material]:shadow-none in-data-[surface=material]:backdrop-filter-none";
20
- const headClass = "flex flex-[0_0_32px] items-center gap-[3px] border-b border-glass-edge bg-glass-head pr-1 pl-2.5 in-data-[surface=material]:basis-[49px] in-data-[surface=material]:rounded-t-[15.5px] in-data-[surface=material]:border-glass-edge/22 in-data-[surface=material]:bg-[color-mix(in_srgb,var(--material-face)_96%,black)] in-data-[surface=material]:px-3 in-data-[surface=material]:py-2 [[data-surface=material][data-resizable=true]_&]:mr-0 [[data-surface=material][data-resizable=true]_&]:pr-[34px]";
21
- const headTitleClass = "min-w-0 flex-1 truncate text-[0.72rem] font-semibold in-data-[surface=material]:p-0 in-data-[surface=material]:text-[17px]/[1.3] in-data-[surface=material]:font-[730] in-data-[surface=material]:tracking-[-0.5px]";
22
16
 
23
17
  export function ActorProgramToolCardSection({ actor, session }: CardSectionContext) {
24
18
  const inspect = useAccess().can("runs.inspect");
@@ -39,16 +33,12 @@ export function ActorProgramToolCardSection({ actor, session }: CardSectionConte
39
33
  );
40
34
  }
41
35
 
42
- const boundedInteger = (value: unknown, minimum: number, maximum: number): value is number =>
43
- Number.isInteger(value) && (value as number) >= minimum && (value as number) <= maximum;
44
-
45
36
  export const actorProgramCanvasElements = (session: SessionContext): readonly CanvasElementDefinition[] =>
46
37
  actorProgramApps(session).flatMap((module): CanvasElementDefinition[] => {
47
38
  const rawPlacements = module.app.placements;
48
39
  const placement = Array.isArray(rawPlacements) ? rawPlacements.find((entry) => isRecord(entry) && entry.kind === "canvas") : undefined;
49
40
  const canvas = isRecord(placement) ? placement : undefined;
50
- if (canvas && (typeof canvas.anchorActorId !== "string"
51
- || !boundedInteger(canvas.width, 280, 960) || !boundedInteger(canvas.height, 180, 720))) {
41
+ if (canvas && typeof canvas.anchorActorId !== "string") {
52
42
  throw new Error(`Die Canvas-Platzierung der Actor-Ansicht ${module.title} ist ungültig`);
53
43
  }
54
44
  if (canvas && canvas.anchorActorId !== module.actorId) throw new Error(`Die Ansicht ${module.title} gehört zu @${module.actorHandle}.`);
@@ -56,23 +46,14 @@ export const actorProgramCanvasElements = (session: SessionContext): readonly Ca
56
46
  id: module.id,
57
47
  title: module.title,
58
48
  visible: module.app.visible !== false,
59
- width: DEFAULT_ACTOR_CARD_SIZE.width,
60
- height: DEFAULT_ACTOR_CARD_SIZE.height,
61
- collapsedHeight: 52,
62
49
  anchorActorId: module.actorId,
63
50
  data: { actorId: module.actorId, actorHandle: module.actorHandle },
64
51
  entity: { type: "run-app", id: module.id },
65
- resizable: {
66
- minWidth: 280,
67
- maxWidth: ACTOR_CARD_SIZE_LIMITS.maxWidth,
68
- minHeight: 180,
69
- maxHeight: ACTOR_CARD_SIZE_LIMITS.maxHeight,
70
- },
71
52
  }];
72
53
  });
73
54
 
74
- export function ActorProgramCanvasElement({ collapsed = false, definition, onCollapsedChange, presentation = "canvas", session }: CanvasElementContext) {
75
- const { api, invoke, listing, openFullscreen, runId } = useActorPrograms();
55
+ export function ActorProgramCanvasElement({ definition, session }: CanvasElementContext) {
56
+ const { api, invoke, listing, runId } = useActorPrograms();
76
57
  const app = listing?.apps.find((candidate) => candidate.id === definition.id);
77
58
  const confirmation = app ? pendingConfirmationFor(session, app) : undefined;
78
59
  const title = definition.title ?? definition.id;
@@ -82,7 +63,7 @@ export function ActorProgramCanvasElement({ collapsed = false, definition, onCol
82
63
  app={app}
83
64
  invoke={invoke}
84
65
  pendingConfirmationInvocationId={confirmation?.parameters.invocationId}
85
- presentation={presentation === "tiled" ? "tiled" : "embedded"}
66
+ presentation="tiled"
86
67
  runId={runId}
87
68
  session={session}
88
69
  />
@@ -93,21 +74,8 @@ export function ActorProgramCanvasElement({ collapsed = false, definition, onCol
93
74
  </div>
94
75
  );
95
76
  return (
96
- <article aria-label={title} className={cn(cardClass, presentation === "tiled" && "w-full rounded-none border-0 bg-transparent shadow-none backdrop-filter-none")}>
97
- {presentation !== "tiled" && <header className={headClass}>
98
- <span className={materialIconClass}><LayoutGridIcon size={20} /></span>
99
- <strong className={headTitleClass} title={title}>{title}</strong>
100
- {isRecord(definition.data) && typeof definition.data.actorHandle === "string" && <small className="mr-1 text-[0.65rem] text-muted-foreground">@{definition.data.actorHandle}</small>}
101
- <Button aria-label={`${title} in Vollansicht öffnen`} onClick={() => openFullscreen(definition.id)} size="icon-sm" title={`${title} in Vollansicht öffnen`} variant="ghost">
102
- <ExpandIcon />
103
- </Button>
104
- {onCollapsedChange && (
105
- <Button aria-expanded={!collapsed} aria-label={`${title} ${collapsed ? "aufklappen" : "einklappen"}`} onClick={() => onCollapsedChange(!collapsed)} size="icon-sm" title={`${title} ${collapsed ? "aufklappen" : "einklappen"}`} variant="ghost">
106
- <ChevronDownIcon className={collapsed ? undefined : "rotate-180"} size={14} />
107
- </Button>
108
- )}
109
- </header>}
110
- <div className="flex min-h-0 flex-1 flex-col overflow-hidden [&>[data-slot=host-confirmation]]:max-h-[65%] [&>[data-slot=host-confirmation]]:flex-[0_1_auto] [&>[data-slot=host-confirmation]]:overflow-auto" hidden={presentation !== "tiled" && collapsed}>
77
+ <article aria-label={title} className={cn(cardClass, "w-full rounded-none border-0 bg-transparent shadow-none backdrop-filter-none")}>
78
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden [&>[data-slot=host-confirmation]]:max-h-[65%] [&>[data-slot=host-confirmation]]:flex-[0_1_auto] [&>[data-slot=host-confirmation]]:overflow-auto">
111
79
  {confirmation && <HostConfirmation action={confirmation} key={confirmation.id} session={session} />}
112
80
  {frame}
113
81
  </div>
@@ -17,7 +17,7 @@ export interface RunAppAction {
17
17
  confirmation: string | null;
18
18
  }
19
19
 
20
- export type RunAppPlacement = { kind: "canvas"; anchorActorId: string; width: number; height: number };
20
+ export type RunAppPlacement = { kind: "canvas"; anchorActorId: string };
21
21
 
22
22
  export type RunAppInvocation = {
23
23
  id: string;
@@ -198,21 +198,12 @@ export const runAppInvocationFrom = (value: unknown): RunAppInvocation => {
198
198
  }
199
199
  };
200
200
 
201
- const boundedInteger = (value: unknown, field: string, minimum: number, maximum: number): number => {
202
- if (!Number.isInteger(value) || (value as number) < minimum || (value as number) > maximum) {
203
- throw new Error(`${field} liegt außerhalb des gültigen Bereichs`);
204
- }
205
- return value as number;
206
- };
207
-
208
201
  const runAppPlacementFrom = (value: unknown): RunAppPlacement => {
209
202
  if (!isRecord(value)) throw new Error("Eine App-Platzierung ist kein Objekt");
210
203
  if (value.kind === "canvas") {
211
204
  return {
212
205
  kind: "canvas",
213
206
  anchorActorId: requiredString(value.anchorActorId, "placement.anchorActorId"),
214
- width: boundedInteger(value.width, "placement.width", 280, 960),
215
- height: boundedInteger(value.height, "placement.height", 180, 720),
216
207
  };
217
208
  }
218
209
  throw new Error("kind einer App-Platzierung muss canvas sein");
@@ -1,8 +1,10 @@
1
1
  import { Alert, AlertDescription, Badge, Button, Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Spinner } from "@aicontainer/web/ui";
2
2
  import { useEffect, useState, type ComponentType } from "react";
3
3
  import type {
4
+ LanguageServerInstanceSnapshot,
4
5
  LanguageServerSeverity,
5
6
  LanguageServerSnapshot,
7
+ LanguageServerState,
6
8
  } from "@aicontainer/server/plugin-support/language-server/contract";
7
9
  import {
8
10
  type WebPlugin,
@@ -33,18 +35,30 @@ const textFrom = (config: Readonly<Record<string, unknown>>, key: string, plugin
33
35
  return value;
34
36
  };
35
37
 
36
- const stateLabel = (snapshot: LanguageServerSnapshot | undefined): string => {
38
+ const stateLabel = (state: LanguageServerState): string => {
39
+ if (state === "opening") return "wird geladen";
40
+ if (state === "failed") return "fehlgeschlagen";
41
+ if (state === "suspended") return "nach Leerlauf beendet";
42
+ return "bereit";
43
+ };
44
+
45
+ const headerLabel = (snapshot: LanguageServerSnapshot | undefined): string => {
37
46
  if (!snapshot) return "wird geladen";
38
- if (snapshot.state === "closed") return "nicht gestartet";
39
- if (snapshot.state === "opening") return "wird geladen";
40
- if (snapshot.state === "failed") return "fehlgeschlagen";
41
- if (snapshot.state === "suspended") return "nach Leerlauf beendet";
42
- return snapshot.summary ?? "bereit";
47
+ const count = snapshot.instances.length;
48
+ if (count === 0) return "nicht gestartet";
49
+ return count === 1 ? "1 Instanz" : `${count} Instanzen`;
43
50
  };
44
51
 
45
52
  const countLabel = (count: number, singular: string, plural: string): string =>
46
53
  `${count} ${count === 1 ? singular : plural}`;
47
54
 
55
+ const errorsOf = (snapshot: LanguageServerSnapshot | undefined): number =>
56
+ (snapshot?.instances ?? [])
57
+ .filter((instance) => instance.state === "ready" || instance.state === "suspended")
58
+ .flatMap((instance) => instance.files)
59
+ .flatMap((file) => file.diagnostics)
60
+ .filter((entry) => entry.severity === "error").length;
61
+
48
62
  const compactEmpty = "h-auto min-h-30 flex-none";
49
63
  const severityClass = "text-[0.64rem] uppercase tracking-[0.04em]"
50
64
  + " in-data-[severity=error]:text-destructive in-data-[severity=warning]:text-warning"
@@ -115,55 +129,33 @@ const panelFor = (settings: TabSettings) => {
115
129
  return LanguageServerPanel;
116
130
  };
117
131
 
118
- export function LanguageServerPanelView({ settings, snapshot, error, pending, onRefresh }: {
119
- settings: Pick<TabSettings, "label" | "openTool">;
120
- snapshot?: LanguageServerSnapshot;
121
- error?: string;
122
- pending: boolean;
123
- onRefresh: () => void;
124
- }) {
125
- const diagnosticsAvailable = snapshot?.state === "ready" || snapshot?.state === "suspended";
126
- const files = diagnosticsAvailable ? snapshot.files : [];
132
+ function LanguageServerInstanceView({ instance }: { instance: LanguageServerInstanceSnapshot }) {
133
+ const diagnosticsAvailable = instance.state === "ready" || instance.state === "suspended";
134
+ const files = diagnosticsAvailable ? instance.files : [];
127
135
  const rows = files.flatMap((file) => file.diagnostics.map((entry) => ({ file: file.path, entry })));
128
136
  const errors = rows.filter((row) => row.entry.severity === "error").length;
129
137
  const warnings = rows.filter((row) => row.entry.severity === "warning").length;
130
138
 
131
139
  return (
132
- <div className="flex h-full min-h-0 flex-col">
133
- <header className="flex items-center gap-2 px-2.5 py-2">
134
- <div className="flex min-w-0 flex-1 flex-col gap-0.5">
135
- <span className="text-[0.8rem] font-medium text-foreground">{settings.label}</span>
136
- <span className="text-xs text-muted-foreground">{stateLabel(snapshot)}</span>
137
- </div>
138
- <Button aria-label="Diagnosen aktualisieren" onClick={onRefresh} size="icon" title="Aktualisieren" variant="ghost">
139
- <IconRefresh className={pending ? "animate-spin" : undefined} />
140
- </Button>
141
- </header>
142
- {error && <Alert variant="destructive"><AlertDescription>{error}</AlertDescription></Alert>}
143
- {snapshot?.root && <div className="overflow-hidden text-ellipsis whitespace-nowrap px-2.5 pb-2 font-mono text-[0.66rem] text-muted-foreground" title={snapshot.root}>{snapshot.root}</div>}
144
- {snapshot?.state === "opening" && (
140
+ <section className="border-b border-border-soft last:border-b-0">
141
+ <div className="flex items-baseline gap-2 px-2.5 pt-2 pb-1">
142
+ <span className="min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap font-mono text-[0.66rem] text-muted-foreground" title={instance.root}>{instance.root}</span>
143
+ <span className="flex-none text-[0.64rem] uppercase tracking-[0.04em] text-muted-foreground">{stateLabel(instance.state)}</span>
144
+ </div>
145
+ {instance.state === "opening" && (
145
146
  <Empty className={compactEmpty} role="status">
146
147
  <EmptyHeader>
147
148
  <EmptyMedia><Spinner aria-label="Wird geladen" /></EmptyMedia>
148
149
  <EmptyTitle>Sprachserver wird geladen</EmptyTitle>
149
- <EmptyDescription>{snapshot.summary || "Die Initialisierung läuft."}</EmptyDescription>
150
+ <EmptyDescription>{instance.summary || "Die Initialisierung läuft."}</EmptyDescription>
150
151
  </EmptyHeader>
151
152
  </Empty>
152
153
  )}
153
- {snapshot?.state === "failed" && (
154
+ {instance.state === "failed" && (
154
155
  <Empty className={`${compactEmpty} text-destructive`} role="alert">
155
156
  <EmptyHeader>
156
157
  <EmptyTitle>Sprachserver nicht verfügbar</EmptyTitle>
157
- <EmptyDescription className="whitespace-pre-wrap text-destructive [overflow-wrap:anywhere]">{snapshot.summary || "Beim Laden ist ein Fehler aufgetreten."}</EmptyDescription>
158
- </EmptyHeader>
159
- </Empty>
160
- )}
161
- {snapshot?.state === "closed" && (
162
- <Empty>
163
- <EmptyHeader>
164
- <EmptyMedia><IconDiagnostics /></EmptyMedia>
165
- <EmptyTitle>Kein Sprachserver</EmptyTitle>
166
- <EmptyDescription>{`${settings.label}-Sprachserver ist nicht gestartet - ein Agent startet ihn mit ${settings.openTool}.`}</EmptyDescription>
158
+ <EmptyDescription className="whitespace-pre-wrap text-destructive [overflow-wrap:anywhere]">{instance.summary || "Beim Laden ist ein Fehler aufgetreten."}</EmptyDescription>
167
159
  </EmptyHeader>
168
160
  </Empty>
169
161
  )}
@@ -180,7 +172,7 @@ export function LanguageServerPanelView({ settings, snapshot, error, pending, on
180
172
  <div className="border-y border-border-soft px-2.5 py-1.5 text-xs text-muted-foreground">
181
173
  {`${countLabel(errors, "Fehler", "Fehler")}, ${countLabel(warnings, "Warnung", "Warnungen")} in ${countLabel(files.length, "Datei", "Dateien")}`}
182
174
  </div>
183
- <div className="min-h-0 flex-1 overflow-y-auto px-1.5 pt-1 pb-2.5">
175
+ <div className="px-1.5 pt-1 pb-2.5">
184
176
  {rows.map((row, index) => (
185
177
  <div className="grid grid-cols-[minmax(0,1fr)_auto] gap-x-2 gap-y-0.5 border-b border-border-soft px-2 py-1.5 text-[0.7rem]"
186
178
  data-severity={row.entry.severity} key={`${row.file}:${row.entry.line}:${row.entry.character}:${index}`}>
@@ -194,6 +186,45 @@ export function LanguageServerPanelView({ settings, snapshot, error, pending, on
194
186
  </div>
195
187
  </>
196
188
  )}
189
+ </section>
190
+ );
191
+ }
192
+
193
+ export function LanguageServerPanelView({ settings, snapshot, error, pending, onRefresh }: {
194
+ settings: Pick<TabSettings, "label" | "openTool">;
195
+ snapshot?: LanguageServerSnapshot;
196
+ error?: string;
197
+ pending: boolean;
198
+ onRefresh: () => void;
199
+ }) {
200
+ const instances = snapshot?.instances ?? [];
201
+
202
+ return (
203
+ <div className="flex h-full min-h-0 flex-col">
204
+ <header className="flex items-center gap-2 px-2.5 py-2">
205
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
206
+ <span className="text-[0.8rem] font-medium text-foreground">{settings.label}</span>
207
+ <span className="text-xs text-muted-foreground">{headerLabel(snapshot)}</span>
208
+ </div>
209
+ <Button aria-label="Diagnosen aktualisieren" onClick={onRefresh} size="icon" title="Aktualisieren" variant="ghost">
210
+ <IconRefresh className={pending ? "animate-spin" : undefined} />
211
+ </Button>
212
+ </header>
213
+ {error && <Alert variant="destructive"><AlertDescription>{error}</AlertDescription></Alert>}
214
+ {snapshot && instances.length === 0 && (
215
+ <Empty>
216
+ <EmptyHeader>
217
+ <EmptyMedia><IconDiagnostics /></EmptyMedia>
218
+ <EmptyTitle>Kein Sprachserver</EmptyTitle>
219
+ <EmptyDescription>{`${settings.label}-Sprachserver ist nicht gestartet - ein Agent startet ihn mit ${settings.openTool}.`}</EmptyDescription>
220
+ </EmptyHeader>
221
+ </Empty>
222
+ )}
223
+ {instances.length > 0 && (
224
+ <div className="min-h-0 flex-1 overflow-y-auto border-t border-border-soft">
225
+ {instances.map((instance) => <LanguageServerInstanceView instance={instance} key={instance.root} />)}
226
+ </div>
227
+ )}
197
228
  </div>
198
229
  );
199
230
  }
@@ -201,9 +232,7 @@ export function LanguageServerPanelView({ settings, snapshot, error, pending, on
201
232
  const badgeFor = (settings: TabSettings) => {
202
233
  function LanguageServerBadge({ session }: WorkspaceTabContext) {
203
234
  const { snapshot } = useSnapshot(settings.pluginId, session.session.id, true, BADGE_POLL_MS);
204
- const errors = (snapshot?.state === "ready" || snapshot?.state === "suspended" ? snapshot.files : [])
205
- .flatMap((file) => file.diagnostics)
206
- .filter((entry) => entry.severity === "error").length;
235
+ const errors = errorsOf(snapshot);
207
236
  return errors > 0 ? <Badge variant="secondary">{errors}</Badge> : null;
208
237
  }
209
238
 
@@ -1,50 +1,34 @@
1
- ## Designing the canvas
1
+ ## Arranging the tiled work surface
2
2
 
3
- The canvas shows the personally visible actors and actor views of this run. Use
4
- `context.functions.canvas_layout_replace` from TypeScript to arrange
5
- existing participants and explain their relationships. Free canvas is the default; do not switch
6
- to tiles unless the user requests them or the run setup explicitly specifies them. Read its exact input and result types with
7
- `typescript_api` before constructing a layout; they define the supported entities,
8
- groups, shapes, connections and size limits.
3
+ The work surface of a run is a grid of tiles that fills the viewport. Each tile shows one actor or
4
+ one activated mini-app. There is no other arrangement and no pan, zoom or free placement. Use
5
+ `context.functions.canvas_layout_replace` from TypeScript to set the arrangement, and read its exact
6
+ input type with `typescript_api` before the first call.
9
7
 
10
- For a fixed, viewport-filling workspace set mode to "tiled" and supply root. A leaf is
11
- {entity:"@helper"} or {entity:"app:@workspace/main"}. A split has direction "horizontal"
12
- (left/right) or "vertical" (top/bottom), two positive weights and exactly two children.
13
- Children can be split again. For "50:50, app left, chat right", use:
14
- {mode:"tiled",root:{direction:"horizontal",weights:[1,1],children:[
15
- {entity:"app:@workspace/main"},{entity:"@helper"}
16
- ]}}. Use [2,1] for two thirds and one third. Use an outer vertical split and an inner
17
- horizontal split for one pane above two panes below. Resolve the user's actual actor and app
18
- names before calling; the example names are not existing participants.
19
-
20
- Tiled mode disables canvas pan, zoom and overview. Users with Actors access can remove panes
21
- and drag actors/apps from the header or pane title onto docking targets. Split dividers remain
22
- resizable without Actors access.
23
- Personal user arrangements take precedence over program suggestions until the user resets them.
24
- Do not repeatedly resend a layout to fight a personal override. Newly created participants stay
25
- available in the header without changing existing tiles. A mode/root-only call preserves the free
26
- canvas arrangement; {mode:"free"} restores it. A root-only call implies tiled mode; root:null
27
- clears the tiles. Free-layout updates supply nodes and replace the explicit nodes, shapes and
28
- lines together, so include everything that should remain there.
8
+ `root` is the whole arrangement and replaces the stored one; it is the only parameter. A tile is
9
+ {entity:"@helper"} or {entity:"app:@workspace/main"}. A split is {direction, weights, children} with
10
+ direction "horizontal" (left/right) or "vertical" (top/bottom), two positive weights and exactly two
11
+ children, each of which may be a split again. `root:null` clears the surface.
29
12
 
30
- Activate actor programs before placing their views. Reference a view as `app:@handle/view-key`
31
- or `app:program-name/view-key`, using the handle, program name and view key you chose. The server
32
- resolves the actual view ID and rejects unknown views before storing the layout. Do not invent
33
- or copy generated view IDs. The same references work in nodes and connection endpoints.
34
- `actor_view_set_visibility` uses the same package/view or @handle/view names without the `app:` prefix.
13
+ App left, chat right at 50:50:
14
+ {root:{direction:"horizontal",weights:[1,1],children:[
15
+ {entity:"app:@workspace/main"},{entity:"@helper"}
16
+ ]}}. One tile above two tiles at 2:1:
17
+ {root:{direction:"vertical",weights:[2,1],children:[{entity:"@lead"},
18
+ {direction:"horizontal",weights:[1,1],children:[{entity:"@first"},{entity:"@second"}]}
19
+ ]}}. Resolve the actual actor and app names before calling; the example names are not existing
20
+ participants.
35
21
 
36
- Place the essentials first and update the layout when the participants or flow change. The host
37
- adds unplaced visible participants and visible actor views automatically in free mode. Actors with their own
38
- views start hidden; their views remain on the canvas and the Actors list opens their inspector.
39
- A layout never overrides the user's personal actor visibility or connection choice. The user
40
- participates through the chat and does not need a separate canvas node.
22
+ An actor tile carries {entity:"@helper",chatInput:false} when its chat composer should stay hidden.
23
+ That changes neither permissions nor the inspector. Mini-app tiles have no chatInput.
41
24
 
42
- Choose arrangements by meaning: peers in a discussion, a sequence of work stages, a hierarchy or
43
- a collection of independent items. Keep groups small enough to read, preserve a useful order and
44
- add a frame only where a visible boundary helps. Leave space for expanded content and connection
45
- labels instead of packing cards tightly together.
25
+ Activate actor programs before placing their views. Reference a view as `app:@handle/view-key` or
26
+ `app:program-name/view-key`, using the handle, program name and view key you chose. The server
27
+ resolves the actual view ID and rejects unknown views before storing the layout. Do not invent or
28
+ copy generated view IDs. `actor_view_set_visibility` uses the same names without the `app:` prefix.
46
29
 
47
- Connections should explain actual relationships. Give them short, meaningful labels and choose
48
- direction and line style deliberately. The canvas does not infer the connections for you. Every
49
- connection must join existing entities. Shapes can represent stages, decisions or topics that are
50
- not actors; use them to explain the work without inventing additional participants.
30
+ Place what the user needs to see side by side and update the arrangement when the work changes.
31
+ Participants you do not place stay reachable through the run header and can be dragged onto a tile
32
+ there. Users can move, resize and remove tiles themselves; their personal arrangement takes
33
+ precedence until they reset it, so do not resend a layout to fight it. The user takes part through
34
+ the chat and needs no tile of their own.