@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.
- package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
- package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
- package/apps/server/src/plugin-support/run-scripts.ts +4 -1
- package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
- package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
- package/apps/web/src/App.tsx +0 -10
- package/apps/web/src/PluginChat.tsx +8 -8
- package/apps/web/src/PluginRegistry.tsx +0 -12
- package/apps/web/src/StartSurface.tsx +2 -2
- package/apps/web/src/ThemeSettings.tsx +1 -1
- package/apps/web/src/column/ColumnApp.tsx +83 -7
- package/apps/web/src/column/clipboard.ts +64 -0
- package/apps/web/src/column/host-contract.ts +10 -0
- package/apps/web/src/column.tsx +2 -0
- package/apps/web/src/panel/LoginForm.tsx +9 -3
- package/apps/web/src/panel/OverviewPage.tsx +187 -62
- package/apps/web/src/panel/PanelDialogs.tsx +115 -0
- package/apps/web/src/panel/PanelPage.tsx +1 -1
- package/apps/web/src/panel/SettingsPage.tsx +75 -79
- package/apps/web/src/panel/contract.ts +17 -9
- package/apps/web/src/panel/target-state.ts +27 -2
- package/apps/web/src/ui/theme.css +4 -0
- package/package.json +2 -2
- package/packages/ragents/src/plugin-host.ts +5 -2
- package/packages/ragents/src/plugin-types.ts +3 -2
- package/packages/workspace-executor/src/context.ts +4 -0
- package/packages/workspace-executor/src/executor.ts +9 -2
- package/packages/workspace-executor/src/index.ts +2 -0
- package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
- package/packages/workspace-executor/src/language-server/contract.ts +7 -3
- package/packages/workspace-executor/src/language-server/host.ts +275 -138
- package/plugins/ragents.actor-programs/contract.ts +1 -1
- package/plugins/ragents.actor-programs/prompt.hbs +4 -4
- package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
- package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
- package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
- package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
- package/plugins/ragents.actor-programs/web/api.ts +1 -10
- package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
- package/plugins/ragents.orchestration/canvas.hbs +28 -44
- package/plugins/ragents.orchestration/contract.ts +17 -287
- package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
- package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
- package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
- package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
- package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
- package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
- package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
- package/plugins/ragents.orchestration/web/Center.tsx +28 -463
- package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
- package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
- package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
- package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
- package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
- package/plugins/ragents.orchestration/web/constants.ts +0 -2
- package/plugins/ragents.orchestration/web/index.tsx +0 -6
- package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
- package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
- package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
- package/plugins/ragents.reference/examples.ts +1 -2
- package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
- package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
- package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
- package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
- package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
- package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
- package/plugins/ragents.reference/walkthroughs.ts +14 -14
- package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
- package/scripts/package/publish-package.ts +4 -5
- package/scripts/workspace-client/run-workspace-client.ts +3 -1
- package/apps/web/src/MaterialSettings.tsx +0 -40
- package/apps/web/src/material-settings.ts +0 -104
- package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
- package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
- package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
- package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
- package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
- package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
- package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
- package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
- package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
- package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
- package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
- package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
- package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
|
@@ -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
|
|
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={
|
|
384
|
+
draggable={canArrange}
|
|
387
385
|
onDragStart={(event) => {
|
|
388
|
-
if (!
|
|
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}${
|
|
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 {
|
|
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 &&
|
|
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({
|
|
75
|
-
const { api, invoke, listing,
|
|
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=
|
|
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,
|
|
97
|
-
|
|
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
|
|
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 = (
|
|
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
|
-
|
|
39
|
-
if (
|
|
40
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
<
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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>{
|
|
150
|
+
<EmptyDescription>{instance.summary || "Die Initialisierung läuft."}</EmptyDescription>
|
|
150
151
|
</EmptyHeader>
|
|
151
152
|
</Empty>
|
|
152
153
|
)}
|
|
153
|
-
{
|
|
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]">{
|
|
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="
|
|
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
|
|
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
|
-
##
|
|
1
|
+
## Arranging the tiled work surface
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
11
|
-
{entity:"@helper"} or {entity:"app:@workspace/main"}. A split
|
|
12
|
-
(left/right) or "vertical" (top/bottom), two positive weights and exactly two
|
|
13
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
not
|
|
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.
|