@schlenkr/ragents 0.1.19 → 0.1.20
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/host-api.json +6 -2
- package/apps/server/src/host-api.ts +3 -2
- package/apps/server/src/host-web.ts +3 -0
- package/apps/server/src/plugin-support/actor-programs/contract.ts +0 -3
- package/apps/server/src/plugin-support/removed-layout.ts +94 -0
- package/apps/server/src/ragents/engine.ts +2 -1
- package/apps/server/src/title-compactor.ts +5 -7
- package/apps/web/dist/assets/highlighted-body-KPVGNVTW-k67VKdbw.js +1 -0
- package/apps/web/dist/assets/main-CVjIz7BS.js +674 -0
- package/apps/web/dist/help/guide-clients.html +22 -42
- package/apps/web/dist/help/guide-clients.md +24 -45
- package/apps/web/dist/help/guide-functions.html +1 -1
- package/apps/web/dist/help/guide-functions.md +1 -1
- package/apps/web/dist/help/guide-getting-started.html +14 -22
- package/apps/web/dist/help/guide-getting-started.md +17 -24
- package/apps/web/dist/help/guide-ideas.html +1 -1
- package/apps/web/dist/help/guide-ideas.md +1 -1
- package/apps/web/dist/help/guide-plugins.html +1 -1
- package/apps/web/dist/help/guide-plugins.md +1 -1
- package/apps/web/dist/help/guide-programs.html +1 -1
- package/apps/web/dist/help/guide-programs.md +1 -1
- package/apps/web/dist/help/index.html +12 -79
- package/apps/web/dist/help/mini-app.css +8 -502
- package/apps/web/dist/host-web.json +28 -23
- package/apps/web/dist/index.html +2 -3
- package/apps/web/dist/run-panel.html +3 -4
- package/apps/web/src/Overview.tsx +4 -143
- package/apps/web/src/PluginChat.tsx +22 -116
- package/apps/web/src/PluginRegistry.tsx +4 -9
- package/apps/web/src/RunPreparationChat.tsx +4 -1
- package/apps/web/src/host-modules.ts +2 -0
- package/apps/web/src/main.tsx +23 -3
- package/apps/web/src/panel/RunLine.tsx +5 -2
- package/apps/web/src/panel/RunsPage.tsx +6 -6
- package/apps/web/src/panel/StartPage.tsx +6 -5
- package/apps/web/src/panel/contract.ts +3 -0
- package/apps/web/src/panel/page-props.ts +2 -0
- package/apps/web/src/run-apps.ts +34 -0
- package/apps/web/src/run-overview.ts +0 -27
- package/apps/web/src/run-panel/PanelContributions.tsx +41 -0
- package/apps/web/src/run-panel/RunPanelApp.tsx +172 -91
- package/apps/web/src/run-panel/RunPanelMenu.tsx +4 -3
- package/apps/web/src/run-panel/RunPanelWorkspace.tsx +8 -2
- package/apps/web/src/run-panel/clipboard.ts +17 -14
- package/apps/web/src/run-panel/host-contract.ts +8 -9
- package/apps/web/src/run-panel/host.ts +5 -31
- package/apps/web/src/run-panel/input-bridge.ts +15 -7
- package/bundles/ragents.activity/ragents-bundle.json +2 -2
- package/bundles/ragents.actor-programs/prompt.hbs +4 -5
- package/bundles/ragents.actor-programs/ragents-bundle.json +4 -14
- package/bundles/ragents.actor-programs/server/index.js +2 -14
- package/bundles/ragents.actor-programs/server/index.js.map +2 -2
- package/bundles/ragents.actor-programs/web/classes.json +1 -1
- package/bundles/ragents.actor-programs/web/index.js +1 -3
- package/bundles/ragents.actor-programs/web/index.js.map +4 -4
- package/bundles/ragents.ask/ragents-bundle.json +2 -2
- package/bundles/ragents.browser/ragents-bundle.json +2 -2
- package/bundles/ragents.documents/ragents-bundle.json +2 -2
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +2 -2
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +2 -2
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +2 -2
- package/bundles/ragents.model-relay/ragents-bundle.json +2 -2
- package/bundles/ragents.orchestration/ragents-bundle.json +15 -41
- package/bundles/ragents.orchestration/server/index.js +10 -249
- package/bundles/ragents.orchestration/server/index.js.map +4 -4
- package/bundles/ragents.orchestration/web/chunks/chunk-KU7O6MEX.js +2 -0
- package/bundles/ragents.orchestration/web/chunks/chunk-KU7O6MEX.js.map +7 -0
- package/bundles/ragents.orchestration/web/classes.json +1 -1
- package/bundles/ragents.orchestration/web/exports/web/program-slot.js +1 -1
- package/bundles/ragents.orchestration/web/index.js +10 -18
- package/bundles/ragents.orchestration/web/index.js.map +4 -4
- package/bundles/ragents.overseer/ragents-bundle.json +3 -3
- package/bundles/ragents.overseer/server/index.js +0 -1
- package/bundles/ragents.overseer/server/index.js.map +2 -2
- package/bundles/ragents.processes/ragents-bundle.json +2 -2
- package/bundles/ragents.product/ragents-bundle.json +2 -2
- package/bundles/ragents.profile-distribution/ragents-bundle.json +2 -2
- package/bundles/ragents.reference/ragents-bundle.json +3 -3
- package/bundles/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
- package/bundles/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -2
- package/bundles/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +6 -7
- package/bundles/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -3
- package/bundles/ragents.reference/run-scripts/conversation-circle/src/server.ts +3 -13
- package/bundles/ragents.reference/run-scripts/conversation-circle/tests/helpers.ts +1 -1
- package/bundles/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +1 -6
- package/bundles/ragents.reference/run-scripts/learning-afternoon/src/server.ts +1 -15
- package/bundles/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/bundles/ragents.reference/run-scripts/moderated-round/tests/helpers.ts +1 -1
- package/bundles/ragents.reference/run-scripts/quick-note/RUN.md +1 -1
- package/bundles/ragents.reference/run-scripts/quick-note/src/server.ts +1 -2
- package/bundles/ragents.reference/run-scripts/quick-note/tests/program.test.ts +0 -2
- package/bundles/ragents.reference/run-scripts/run-roster/RUN.md +2 -2
- package/bundles/ragents.reference/run-scripts/run-roster/src/server.ts +1 -2
- package/bundles/ragents.reference/run-scripts/run-roster/tests/program.test.ts +0 -2
- package/bundles/ragents.reference/run-scripts/shared-actor-list/src/server.ts +1 -1
- package/bundles/ragents.reference/run-scripts/shared-actor-list/tests/helpers.ts +1 -1
- package/bundles/ragents.reference/run-scripts/word-game/src/contract.ts +1 -1
- package/bundles/ragents.reference/run-scripts/word-game/src/server.ts +0 -1
- package/bundles/ragents.reference/run-scripts/word-game/tests/program.test.ts +1 -2
- package/bundles/ragents.reference/skills/80-actor-text-analysis/SKILL.md +1 -1
- package/bundles/ragents.reference/skills/90-actor-notes/SKILL.md +1 -1
- package/bundles/ragents.todo/ragents-bundle.json +2 -2
- package/bundles/ragents.transcript/ragents-bundle.json +2 -2
- package/bundles/ragents.watch/ragents-bundle.json +2 -2
- package/bundles/ragents.workspace/ragents-bundle.json +2 -2
- package/package.json +8 -8
- package/packages/agent/dist/core/agent-settings.d.ts +1 -1
- package/packages/ai/dist/types.d.ts +2 -1
- package/packages/ragents/src/runtime/journal.ts +4 -0
- package/plugins/ragents.actor-programs/prompt.hbs +4 -5
- package/plugins/ragents.actor-programs/server/access-projections.ts +1 -1
- package/plugins/ragents.actor-programs/server/index.ts +0 -2
- package/plugins/ragents.actor-programs/server/runtime.ts +2 -10
- package/plugins/ragents.actor-programs/web/ActorViewFrame.tsx +5 -20
- package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -79
- package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +0 -9
- package/plugins/ragents.actor-programs/web/api.ts +0 -20
- package/plugins/ragents.actor-programs/web/context.ts +0 -3
- package/plugins/ragents.actor-programs/web/index.tsx +0 -9
- package/plugins/ragents.orchestration/ragents-plugin.json +1 -2
- package/plugins/ragents.orchestration/server/index.ts +1 -11
- package/plugins/ragents.orchestration/web/ActorChat.tsx +1 -1
- package/plugins/ragents.orchestration/web/ActorChatControls.tsx +1 -1
- package/plugins/ragents.orchestration/web/FlowInspector.tsx +2 -4
- package/plugins/ragents.orchestration/web/InspectionPanel.tsx +21 -0
- package/plugins/ragents.orchestration/web/index.tsx +13 -9
- package/plugins/ragents.orchestration/web/program-slot.ts +0 -3
- package/plugins/ragents.orchestration/web/run-panel/AddresseeControl.tsx +15 -36
- package/plugins/ragents.orchestration/web/run-panel/RunPanel.tsx +76 -451
- package/plugins/ragents.orchestration/web/run-panel/run-panel-actors.ts +0 -27
- package/plugins/ragents.orchestration/web/run-panel/run-panel-state.ts +7 -40
- package/plugins/ragents.overseer/server/coordinator-prompt.ts +0 -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/src/server.ts +1 -2
- package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +6 -7
- package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -3
- package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +3 -13
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/helpers.ts +1 -1
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +1 -6
- package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +1 -15
- package/plugins/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/plugins/ragents.reference/run-scripts/moderated-round/tests/helpers.ts +1 -1
- package/plugins/ragents.reference/run-scripts/quick-note/RUN.md +1 -1
- package/plugins/ragents.reference/run-scripts/quick-note/src/server.ts +1 -2
- package/plugins/ragents.reference/run-scripts/quick-note/tests/program.test.ts +0 -2
- package/plugins/ragents.reference/run-scripts/run-roster/RUN.md +2 -2
- package/plugins/ragents.reference/run-scripts/run-roster/src/server.ts +1 -2
- package/plugins/ragents.reference/run-scripts/run-roster/tests/program.test.ts +0 -2
- package/plugins/ragents.reference/run-scripts/shared-actor-list/src/server.ts +1 -1
- package/plugins/ragents.reference/run-scripts/shared-actor-list/tests/helpers.ts +1 -1
- package/plugins/ragents.reference/run-scripts/word-game/src/contract.ts +1 -1
- package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +0 -1
- package/plugins/ragents.reference/run-scripts/word-game/tests/program.test.ts +1 -2
- package/plugins/ragents.reference/skills/80-actor-text-analysis/SKILL.md +1 -1
- package/plugins/ragents.reference/skills/90-actor-notes/SKILL.md +1 -1
- package/plugins/ragents.reference/walkthroughs.ts +13 -14
- package/apps/web/dist/assets/highlighted-body-KPVGNVTW-BwD4uYEe.js +0 -1
- package/apps/web/dist/assets/main-KBrc5Q77.js +0 -11
- package/apps/web/dist/assets/mermaid-HWGCJPDP-CSCkaArr.js +0 -608
- package/apps/web/dist/assets/runPanel-BjG5NBKm.js +0 -26
- package/apps/web/src/App.tsx +0 -318
- package/apps/web/src/SessionList.tsx +0 -157
- package/apps/web/src/SurfaceShortcuts.tsx +0 -60
- package/apps/web/src/WorkspacePanel.tsx +0 -309
- package/apps/web/src/run-panel.tsx +0 -52
- package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js +0 -11
- package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js.map +0 -7
- package/bundles/ragents.orchestration/server/exports/server/contract.js +0 -9
- package/bundles/ragents.orchestration/server/exports/server/contract.js.map +0 -7
- package/bundles/ragents.orchestration/surface.hbs +0 -39
- package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js.map +0 -7
- package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js.map +0 -7
- package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js.map +0 -7
- package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js.map +0 -7
- package/bundles/ragents.orchestration/web/exports/web/surface-entities.js +0 -2
- package/bundles/ragents.orchestration/web/exports/web/surface-entities.js.map +0 -7
- package/bundles/ragents.orchestration/web/exports/web/tile-docking.js +0 -2
- package/bundles/ragents.orchestration/web/exports/web/tile-docking.js.map +0 -7
- package/plugins/ragents.actor-programs/web/ExpandIcon.tsx +0 -5
- package/plugins/ragents.actor-programs/web/FullscreenHost.tsx +0 -31
- package/plugins/ragents.orchestration/contract.ts +0 -51
- package/plugins/ragents.orchestration/server/contract.ts +0 -14
- package/plugins/ragents.orchestration/server/surface-tool.ts +0 -198
- package/plugins/ragents.orchestration/surface.hbs +0 -39
- package/plugins/ragents.orchestration/tiled-layout.ts +0 -55
- package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +0 -29
- package/plugins/ragents.orchestration/web/ActorShortcut.tsx +0 -74
- package/plugins/ragents.orchestration/web/ActorSurfaceControls.tsx +0 -130
- package/plugins/ragents.orchestration/web/ActorTile.tsx +0 -23
- package/plugins/ragents.orchestration/web/Center.tsx +0 -133
- package/plugins/ragents.orchestration/web/SurfaceTiles.tsx +0 -47
- package/plugins/ragents.orchestration/web/TiledSurface.tsx +0 -280
- package/plugins/ragents.orchestration/web/actor-header-settings.ts +0 -41
- package/plugins/ragents.orchestration/web/run-panel/RunPanelSettings.tsx +0 -50
- package/plugins/ragents.orchestration/web/run-panel/run-panel-settings.ts +0 -47
- package/plugins/ragents.orchestration/web/run-panel/sheet-status.ts +0 -30
- package/plugins/ragents.orchestration/web/surface-entities.ts +0 -21
- package/plugins/ragents.orchestration/web/surface-view-settings.ts +0 -42
- package/plugins/ragents.orchestration/web/tile-docking.ts +0 -105
- package/plugins/ragents.orchestration/web/tiled-view-settings.ts +0 -74
- /package/apps/web/dist/assets/{mermaid-HWGCJPDP-BPn4aV2r.css → main-BPn4aV2r.css} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState, type ReactNode } from "react";
|
|
2
2
|
import { ArrowLeftIcon, LayoutGridIcon } from "lucide-react";
|
|
3
3
|
import { canStartEntry, type AccessContext } from "../../../../packages/ragents/src/access";
|
|
4
|
-
import { getStartOptions, setStartOption, stopRun } from "../api";
|
|
4
|
+
import { deleteSession, getStartOptions, setStartOption, stopRun } from "../api";
|
|
5
5
|
import { startEntryDirectly } from "../chat/requests";
|
|
6
6
|
import { initialStartOptionUpdates, withoutFixedStartOptions } from "../StartOptions";
|
|
7
7
|
import { AccessScreen, useAccess } from "../AccessContext";
|
|
@@ -10,10 +10,16 @@ import { PluginChat } from "../PluginChat";
|
|
|
10
10
|
import type { PluginRegistry } from "../PluginRegistry";
|
|
11
11
|
import { usePluginActivation } from "../PluginActivation";
|
|
12
12
|
import { PluginFailureNotice } from "../PluginFailureNotice";
|
|
13
|
+
import { runActivityNotice } from "../run-overview";
|
|
13
14
|
import { useRunReadState } from "../run-read-state";
|
|
14
|
-
import {
|
|
15
|
+
import { PanelPage } from "../panel/PanelPage";
|
|
16
|
+
import type { PanelAction, PanelState } from "../panel/contract";
|
|
17
|
+
import { HelpDialog } from "../HelpDialog";
|
|
18
|
+
import { chatUserLocation, type ChatRunLocation } from "../chat/user-location";
|
|
19
|
+
import { PanelContributions } from "./PanelContributions";
|
|
20
|
+
import { WorkspaceModalContext } from "../ui/dialog";
|
|
15
21
|
import { SettingsModal } from "../SettingsModal";
|
|
16
|
-
import { Alert, Button, RunStateIcon, StartupNotice, StopButton, type StartupNoticeState } from "../ui";
|
|
22
|
+
import { Alert, Button, longTime, RunStateIcon, StartupNotice, StopButton, type StartupNoticeState } from "../ui";
|
|
17
23
|
import type { RunPanelLocation } from "./run-panel-location";
|
|
18
24
|
import { RunPanelMenu } from "./RunPanelMenu";
|
|
19
25
|
import { useRunPanelHost } from "./host";
|
|
@@ -23,12 +29,12 @@ const headerClass = "relative z-[80] flex h-header flex-none items-stretch borde
|
|
|
23
29
|
const connectionClass = "max-w-[120px] self-center truncate rounded-full bg-secondary px-2 py-0.5 text-[0.66rem] font-semibold text-muted-foreground";
|
|
24
30
|
const pendingTitleClass = "min-w-0 flex-1 self-center truncate px-1.5 text-[0.82rem] font-semibold";
|
|
25
31
|
const STOP_REASON = "Stopped in the run panel";
|
|
32
|
+
const statusBarClass = "relative flex min-h-statusbar flex-none items-stretch border-t border-border bg-shell";
|
|
26
33
|
/** How long the panel in VS Code without a run waits for its host's start request. */
|
|
27
34
|
const HOST_COMMAND_TIMEOUT_MS = 5000;
|
|
28
35
|
const NEW_RUN_TITLE = "New run";
|
|
29
36
|
const listHeaderClass = "relative z-[80] flex h-header flex-none items-center gap-2 border-b border-border bg-shell px-3 shadow-bar";
|
|
30
37
|
const noticeClass = "m-auto max-w-[420px] p-6 text-center text-muted-foreground";
|
|
31
|
-
const noSelection: ReadonlySet<string> = new Set();
|
|
32
38
|
const newDraft = (): SessionInfo => ({ id: crypto.randomUUID(), title: NEW_RUN_TITLE, updatedAt: Date.now() });
|
|
33
39
|
const placeholderSession = (id: string, title = "Run"): SessionInfo => ({ id, title, updatedAt: Date.now() });
|
|
34
40
|
const profileLoading: StartupNoticeState = { kind: "working", title: "Loading profile", detail: "Loading the server's interface." };
|
|
@@ -37,7 +43,7 @@ const launchStarting: StartupNoticeState = { kind: "working", title: "Starting r
|
|
|
37
43
|
const noRunSelected: StartupNoticeState = { kind: "waiting", title: "No run selected", detail: "On the Start page you start a new run or open an existing one." };
|
|
38
44
|
const noRunsReadable: StartupNoticeState = { kind: "error", title: "No runs enabled", detail: "No runs are enabled for this user account." };
|
|
39
45
|
|
|
40
|
-
/** The run panel: a run with chat and mini-apps, without a run
|
|
46
|
+
/** The run panel: a run with chat and mini-apps, without a run the shared Start page; layout=app shows one mini-app. */
|
|
41
47
|
export function RunPanelApp({ location }: { location: RunPanelLocation }) {
|
|
42
48
|
const activation = usePluginActivation();
|
|
43
49
|
const headless = location.layout === "app";
|
|
@@ -68,6 +74,12 @@ function RunPanelPage({ connection, initialRunId, registry }: { connection: stri
|
|
|
68
74
|
const canCreate = canCreateFree || (writeRuns && registry.scriptEntries.some((entry) => canStartEntry(access, entry.id)));
|
|
69
75
|
const runReadState = useRunReadState(access.user?.id);
|
|
70
76
|
const [runId, setRunId] = useState(initialRunId);
|
|
77
|
+
const [page, setPage] = useState<"start" | "runs">("start");
|
|
78
|
+
const [runLocation, setRunLocation] = useState<ChatRunLocation>();
|
|
79
|
+
const [helpOpen, setHelpOpen] = useState(false);
|
|
80
|
+
const [navigationError, setNavigationError] = useState<string>();
|
|
81
|
+
const [workspaceContainer, setWorkspaceContainer] = useState<HTMLDivElement | null>(null);
|
|
82
|
+
const [statusContainer, setStatusContainer] = useState<HTMLElement | null>(null);
|
|
71
83
|
const [runTitle, setRunTitle] = useState<string>();
|
|
72
84
|
const [focusRunId, setFocusRunId] = useState<string>();
|
|
73
85
|
const settleAutoFocus = useCallback(() => setFocusRunId((pending) => pending === runId ? undefined : pending), [runId]);
|
|
@@ -86,12 +98,10 @@ function RunPanelPage({ connection, initialRunId, registry }: { connection: stri
|
|
|
86
98
|
const openSettings = useCallback(() => setSettingsOpen(true), []);
|
|
87
99
|
const { sessions, unreachable, refresh } = useSessionList(readRuns && !draft && !launch);
|
|
88
100
|
const [headerContainer, setHeaderContainer] = useState<HTMLDivElement | null>(null);
|
|
89
|
-
const [toolsContainer, setToolsContainer] = useState<HTMLDivElement | null>(null);
|
|
90
101
|
const startDraft = useCallback((startOptions?: Readonly<Record<string, unknown>>, entryId?: string) => {
|
|
91
102
|
const fixed = registry.startEntries.find((entry) => entry.id === entryId)?.fixedStartOptions;
|
|
92
103
|
setDraft({ session: newDraft(), startOptions: startOptions && withoutFixedStartOptions(startOptions, fixed), entryId });
|
|
93
104
|
}, [registry]);
|
|
94
|
-
const createRun = useCallback(() => startDraft(), [startDraft]);
|
|
95
105
|
const openRun = useCallback((id: string, startOptions?: Readonly<Record<string, unknown>>) => {
|
|
96
106
|
setDraft(undefined);
|
|
97
107
|
replaceLaunch(undefined);
|
|
@@ -108,19 +118,14 @@ function RunPanelPage({ connection, initialRunId, registry }: { connection: stri
|
|
|
108
118
|
(cause: unknown) => { if (launching.current === next.runId) setLaunch({ ...next, error: cause instanceof Error ? cause.message : String(cause) }); },
|
|
109
119
|
);
|
|
110
120
|
}, [access, openRun, registry, replaceLaunch]);
|
|
111
|
-
|
|
112
|
-
setFocusRunId(undefined);
|
|
113
|
-
setRunTitle(undefined);
|
|
114
|
-
setRunId(id);
|
|
115
|
-
}, []);
|
|
116
|
-
/** The back arrow always leads to the Start page; in the browser that is the run list of this server. */
|
|
121
|
+
/** The back arrow always leads to the Start page; in the browser it lists this server's templates and recent runs. */
|
|
117
122
|
const showStart = useCallback(() => {
|
|
118
123
|
setFocusRunId(undefined);
|
|
119
124
|
setDraft(undefined);
|
|
120
125
|
replaceLaunch(undefined);
|
|
121
126
|
setRefusal(undefined);
|
|
122
127
|
if (host.kind === "vscode") host.notify({ type: "showStart" });
|
|
123
|
-
else setRunId(undefined);
|
|
128
|
+
else { setRunId(undefined); setPage("start"); }
|
|
124
129
|
}, [host, replaceLaunch]);
|
|
125
130
|
|
|
126
131
|
useLayoutEffect(() => {
|
|
@@ -175,12 +180,21 @@ function RunPanelPage({ connection, initialRunId, registry }: { connection: stri
|
|
|
175
180
|
useEffect(() => { host.notify({ type: "ready" }); }, [host]);
|
|
176
181
|
useEffect(() => { host.notify({ type: "runChanged", runId: runId ?? null }); }, [host, runId]);
|
|
177
182
|
|
|
178
|
-
const settings =
|
|
183
|
+
const settings = <>
|
|
184
|
+
{settingsOpen && <SettingsModal onClose={() => setSettingsOpen(false)} registry={registry} />}
|
|
185
|
+
{helpOpen && <HelpDialog onClose={() => setHelpOpen(false)}
|
|
186
|
+
sampleEntries={registry.scriptEntries.filter((entry) => canStartEntry(access, entry.id)).map((entry) => entry.id)}
|
|
187
|
+
onStartSample={(entryId) => {
|
|
188
|
+
if (!canStartEntry(access, entryId)) return;
|
|
189
|
+
setHelpOpen(false);
|
|
190
|
+
startDraft(undefined, entryId);
|
|
191
|
+
}} />}
|
|
192
|
+
</>;
|
|
179
193
|
const pendingHeader = (title: string) => <>
|
|
180
194
|
<Button aria-label="Back to Start" className="self-center" onClick={showStart} size="icon-lg" title="Back to Start" variant="ghost"><ArrowLeftIcon /></Button>
|
|
181
195
|
<strong className={pendingTitleClass}>{title}</strong>
|
|
182
196
|
{connection && <span className={connectionClass} title={`Server ${connection}`}>{connection}</span>}
|
|
183
|
-
<RunPanelMenu onOpenSettings={openSettings} />
|
|
197
|
+
<RunPanelMenu onOpenSettings={openSettings} onOpenHelp={() => setHelpOpen(true)} />
|
|
184
198
|
</>;
|
|
185
199
|
const toStart = <Button onClick={showStart} size="sm" variant="outline">Back to Start</Button>;
|
|
186
200
|
const draftChat = draft && <PluginChat
|
|
@@ -191,83 +205,149 @@ function RunPanelPage({ connection, initialRunId, registry }: { connection: stri
|
|
|
191
205
|
session={draft.session}
|
|
192
206
|
startDialog={{ onClose: host.kind === "vscode" ? showStart : () => setDraft(undefined), initialEntryId: draft.entryId }}
|
|
193
207
|
/>;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
</>;
|
|
200
|
-
if (host.kind === "vscode" && draft) return <>
|
|
201
|
-
<PendingPanel header={pendingHeader(runTitle ?? NEW_RUN_TITLE)} state={launchStarting} />
|
|
202
|
-
{draftChat}
|
|
203
|
-
{settings}
|
|
204
|
-
</>;
|
|
205
|
-
// In VS Code the extension's Start page chooses the run; without one the panel waits for its request instead of showing a list.
|
|
206
|
-
if (host.kind === "vscode" && !(readRuns && runId)) return <>
|
|
207
|
-
{!readRuns ? <PendingPanel header={pendingHeader(NEW_RUN_TITLE)} state={noRunsReadable}>{toStart}</PendingPanel>
|
|
208
|
-
: refusal !== undefined ? <PendingPanel header={pendingHeader(NEW_RUN_TITLE)} state={{ kind: "error", title: "No new run possible", detail: refusal }}>{toStart}</PendingPanel>
|
|
209
|
-
: <HostWaiting header={pendingHeader(NEW_RUN_TITLE)}>{toStart}</HostWaiting>}
|
|
210
|
-
{settings}
|
|
211
|
-
</>;
|
|
212
|
-
|
|
213
|
-
const session = sessions.find((entry) => entry.id === runId);
|
|
214
|
-
const stop = () => {
|
|
215
|
-
if (runId === undefined) return;
|
|
216
|
-
setStopError(undefined);
|
|
217
|
-
void stopRun(runId, STOP_REASON).catch((cause: unknown) => setStopError(`Stop failed: ${cause instanceof Error ? cause.message : String(cause)}`));
|
|
218
|
-
};
|
|
219
|
-
const list = readRuns
|
|
220
|
-
? <SessionList activeId={runId} onCreate={canCreate ? createRun : undefined} onSelect={selectRun} onToggleSelected={() => undefined} registry={registry} seenRevisions={runReadState.revisions} selectMode={false} selectedIds={noSelection} sessions={sessions} />
|
|
221
|
-
: <p className={noticeClass}>No runs are enabled for this user account.</p>;
|
|
222
|
-
return (
|
|
223
|
-
<div className="flex h-full flex-col bg-[image:var(--surface-backdrop)]">
|
|
224
|
-
{readRuns && runId
|
|
225
|
-
? (
|
|
226
|
-
<>
|
|
227
|
-
<header className={headerClass}>
|
|
228
|
-
<Button aria-label="Back to Start" className="self-center" onClick={showStart} size="icon-lg" title="Back to Start" variant="ghost"><ArrowLeftIcon /></Button>
|
|
229
|
-
<div aria-label="Run title bar" className="flex min-w-0 flex-1 items-stretch overflow-x-auto no-scrollbar" ref={setHeaderContainer} role="region" />
|
|
230
|
-
{connection && <span className={connectionClass} title={`Server ${connection}`}>{connection}</span>}
|
|
231
|
-
<RunStateIcon className="self-center px-1.5" state={session?.running ? "running" : "idle"} />
|
|
232
|
-
<StopButton className="self-center" disabled={!writeRuns} label="Stop run" onClick={stop} size="icon-lg" title="Stop the run with all agents and flows" />
|
|
233
|
-
<div aria-label="Panel tools" className="flex flex-none items-center empty:hidden" ref={setToolsContainer} role="toolbar" />
|
|
234
|
-
<RunPanelMenu onOpenSettings={openSettings} runId={runId} />
|
|
235
|
-
</header>
|
|
236
|
-
{stopError && <Alert variant="destructive">{stopError}</Alert>}
|
|
237
|
-
<main className="relative z-1 flex min-h-0 min-w-0 flex-1 @container/chat-content">
|
|
238
|
-
<PluginChat
|
|
239
|
-
autoFocusChat={focusRunId === runId && !settingsOpen && !draft}
|
|
240
|
-
onAutoFocusChatSettled={settleAutoFocus}
|
|
241
|
-
toolbarContainer={toolsContainer}
|
|
242
|
-
headerContainer={headerContainer}
|
|
243
|
-
initialStartOptions={runStartOptions}
|
|
244
|
-
key={runId}
|
|
245
|
-
layout="panel"
|
|
246
|
-
onViewed={runReadState.markViewed}
|
|
247
|
-
registry={registry}
|
|
248
|
-
session={session ?? placeholderSession(runId, runTitle)}
|
|
249
|
-
viewing={!draft && !settingsOpen}
|
|
250
|
-
/>
|
|
251
|
-
</main>
|
|
252
|
-
</>
|
|
253
|
-
)
|
|
254
|
-
: (
|
|
255
|
-
<>
|
|
256
|
-
<header className={listHeaderClass}>
|
|
257
|
-
<LayoutGridIcon aria-hidden className="size-4 flex-none text-muted-foreground" />
|
|
258
|
-
<strong className="min-w-0 flex-1 truncate text-[0.82rem] font-semibold">{registry.brand.title}</strong>
|
|
259
|
-
<RunPanelMenu onOpenSettings={openSettings} />
|
|
260
|
-
</header>
|
|
261
|
-
<div className="min-h-0 flex-1 overflow-auto p-3">
|
|
262
|
-
{unreachable && <Alert className="mb-3" variant="destructive">The server is unreachable.</Alert>}
|
|
263
|
-
{list}
|
|
264
|
-
</div>
|
|
265
|
-
</>
|
|
266
|
-
)}
|
|
208
|
+
const content = () => {
|
|
209
|
+
if (launch) return <>
|
|
210
|
+
{launch.error === undefined
|
|
211
|
+
? <PendingPanel header={pendingHeader(runTitle ?? NEW_RUN_TITLE)} state={launchStarting} />
|
|
212
|
+
: <PendingPanel header={pendingHeader(runTitle ?? NEW_RUN_TITLE)} state={{ kind: "error", title: "The run could not be started", detail: launch.error }}>{toStart}</PendingPanel>}
|
|
267
213
|
{settings}
|
|
214
|
+
</>;
|
|
215
|
+
if (host.kind === "vscode" && draft) return <>
|
|
216
|
+
<PendingPanel header={pendingHeader(runTitle ?? NEW_RUN_TITLE)} state={launchStarting} />
|
|
268
217
|
{draftChat}
|
|
269
|
-
|
|
270
|
-
|
|
218
|
+
{settings}
|
|
219
|
+
</>;
|
|
220
|
+
// In VS Code the extension's Start page chooses the run; without one the panel waits for its request instead of showing a list.
|
|
221
|
+
if (host.kind === "vscode" && !(readRuns && runId)) return <>
|
|
222
|
+
{!readRuns ? <PendingPanel header={pendingHeader(NEW_RUN_TITLE)} state={noRunsReadable}>{toStart}</PendingPanel>
|
|
223
|
+
: refusal !== undefined ? <PendingPanel header={pendingHeader(NEW_RUN_TITLE)} state={{ kind: "error", title: "No new run possible", detail: refusal }}>{toStart}</PendingPanel>
|
|
224
|
+
: <HostWaiting header={pendingHeader(NEW_RUN_TITLE)}>{toStart}</HostWaiting>}
|
|
225
|
+
{settings}
|
|
226
|
+
</>;
|
|
227
|
+
|
|
228
|
+
const session = sessions.find((entry) => entry.id === runId);
|
|
229
|
+
const stop = () => {
|
|
230
|
+
if (runId === undefined) return;
|
|
231
|
+
setStopError(undefined);
|
|
232
|
+
void stopRun(runId, STOP_REASON).catch((cause: unknown) => setStopError(`Stop failed: ${cause instanceof Error ? cause.message : String(cause)}`));
|
|
233
|
+
};
|
|
234
|
+
const connectionName = registry.brand.title;
|
|
235
|
+
const navigationState: PanelState = {
|
|
236
|
+
theme: "light",
|
|
237
|
+
page,
|
|
238
|
+
profileSuggestions: [],
|
|
239
|
+
problem: navigationError,
|
|
240
|
+
connections: [{
|
|
241
|
+
name: connectionName,
|
|
242
|
+
kind: "server",
|
|
243
|
+
address: window.location.origin,
|
|
244
|
+
route: { kind: "server", host: window.location.host, localHost: false },
|
|
245
|
+
state: unreachable ? { kind: "unreachable", message: "The server is unreachable." } : { kind: "connected" },
|
|
246
|
+
canCreate,
|
|
247
|
+
canCreateFree,
|
|
248
|
+
canDelete: access.can("runs.delete"),
|
|
249
|
+
defaultEntry: registry.startEntries.some((entry) => entry.id === registry.defaultStartEntry && canStartEntry(access, entry.id)) ? registry.defaultStartEntry : undefined,
|
|
250
|
+
entries: registry.startEntries.filter((entry) => canStartEntry(access, entry.id)).map((entry) => ({
|
|
251
|
+
id: entry.id, title: entry.title, description: entry.description, kind: entry.action,
|
|
252
|
+
category: entry.category ?? (access.can("runs.inspect") ? "Run scripts" : "Workflows"), guided: registry.guideFor(entry) !== undefined,
|
|
253
|
+
})),
|
|
254
|
+
runs: readRuns ? sessions.map((entry) => ({ ...entry, state: entry.running ? "running" : "idle", pendingActions: 0, notice: runActivityNotice(entry, runReadState.revisions[entry.id]) })) : [],
|
|
255
|
+
}],
|
|
256
|
+
};
|
|
257
|
+
const navigate = (action: PanelAction) => {
|
|
258
|
+
setNavigationError(undefined);
|
|
259
|
+
switch (action.action) {
|
|
260
|
+
case "page":
|
|
261
|
+
if (action.page === "connections") throw new Error("The browser is connected to its current server.");
|
|
262
|
+
setPage(action.page);
|
|
263
|
+
return;
|
|
264
|
+
case "openRun": setRunTitle(undefined); openRun(action.runId); return;
|
|
265
|
+
case "newRun": {
|
|
266
|
+
if (!canCreate || action.entryId === undefined && !canCreateFree) throw new Error("New runs are not enabled.");
|
|
267
|
+
if (action.entryId && !canStartEntry(access, action.entryId)) throw new Error("This template is not enabled.");
|
|
268
|
+
const entry = registry.startEntries.find((entry) => entry.id === action.entryId);
|
|
269
|
+
setRunTitle(entry?.title ?? NEW_RUN_TITLE);
|
|
270
|
+
if (entry && registry.guideFor(entry)) startDraft(undefined, entry.id);
|
|
271
|
+
else if (action.entryId) startLaunch({ runId: crypto.randomUUID(), entryId: action.entryId });
|
|
272
|
+
else openRun(crypto.randomUUID());
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
case "deleteRuns":
|
|
276
|
+
if (!access.can("runs.delete")) throw new Error("Deleting runs is not enabled.");
|
|
277
|
+
void Promise.allSettled(action.runIds.map((id) => deleteSession(id))).then(async (results) => {
|
|
278
|
+
await refresh();
|
|
279
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
280
|
+
if (failure?.status === "rejected") setNavigationError(String(failure.reason));
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
case "retry": void refresh(); return;
|
|
284
|
+
default: throw new Error(`Unsupported browser action: ${action.action}`);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
return (
|
|
288
|
+
<div className="flex h-full flex-col bg-[image:var(--surface-backdrop)]">
|
|
289
|
+
{readRuns && runId
|
|
290
|
+
? (
|
|
291
|
+
<>
|
|
292
|
+
<header className={headerClass}>
|
|
293
|
+
<Button aria-label="Back to Start" className="self-center" onClick={showStart} size="icon-lg" title="Back to Start" variant="ghost"><ArrowLeftIcon /></Button>
|
|
294
|
+
<div aria-label="Run title bar" className="flex min-w-0 flex-1 items-stretch overflow-x-auto no-scrollbar" ref={setHeaderContainer} role="region" />
|
|
295
|
+
{connection && <span className={connectionClass} title={`Server ${connection}`}>{connection}</span>}
|
|
296
|
+
<RunStateIcon className="self-center px-1.5" state={session?.running ? "running" : "idle"} />
|
|
297
|
+
<StopButton className="self-center" disabled={!writeRuns} label="Stop run" onClick={stop} size="icon-lg" title="Stop the run with all agents and flows" />
|
|
298
|
+
<RunPanelMenu onOpenSettings={openSettings} onOpenHelp={() => setHelpOpen(true)} runId={runId} />
|
|
299
|
+
</header>
|
|
300
|
+
{stopError && <Alert variant="destructive">{stopError}</Alert>}
|
|
301
|
+
<main className="relative z-1 flex min-h-0 min-w-0 flex-1 @container/chat-content">
|
|
302
|
+
<PluginChat
|
|
303
|
+
autoFocusChat={focusRunId === runId && !settingsOpen && !draft}
|
|
304
|
+
onAutoFocusChatSettled={settleAutoFocus}
|
|
305
|
+
headerContainer={headerContainer}
|
|
306
|
+
initialStartOptions={runStartOptions}
|
|
307
|
+
key={runId}
|
|
308
|
+
onLocationChange={setRunLocation}
|
|
309
|
+
statusContainer={statusContainer}
|
|
310
|
+
onViewed={runReadState.markViewed}
|
|
311
|
+
registry={registry}
|
|
312
|
+
session={session ?? placeholderSession(runId, runTitle)}
|
|
313
|
+
viewing={!draft && !settingsOpen && !helpOpen}
|
|
314
|
+
/>
|
|
315
|
+
</main>
|
|
316
|
+
</>
|
|
317
|
+
)
|
|
318
|
+
: (
|
|
319
|
+
<>
|
|
320
|
+
<header className={listHeaderClass}>
|
|
321
|
+
<LayoutGridIcon aria-hidden className="size-4 flex-none text-muted-foreground" />
|
|
322
|
+
<strong className="min-w-0 flex-1 truncate text-[0.82rem] font-semibold">{registry.brand.title}</strong>
|
|
323
|
+
<RunPanelMenu onOpenSettings={openSettings} onOpenHelp={() => setHelpOpen(true)} />
|
|
324
|
+
</header>
|
|
325
|
+
<div className="min-h-0 flex-1 overflow-auto p-3">
|
|
326
|
+
{unreachable && <Alert className="mb-3" variant="destructive">The server is unreachable.</Alert>}
|
|
327
|
+
<PanelPage send={navigate} state={navigationState} runDetails={(id) => {
|
|
328
|
+
const entry = sessions.find((candidate) => candidate.id === id);
|
|
329
|
+
return entry && <>
|
|
330
|
+
{entry.createdAt !== undefined && <span>Created {longTime(entry.createdAt)}</span>}
|
|
331
|
+
{entry.ownerLabel && <span>{entry.ownerLabel}</span>}
|
|
332
|
+
{registry.sessionMetadata.map(({ id, Metadata }) => <Metadata key={id} placement="list" session={entry} />)}
|
|
333
|
+
</>;
|
|
334
|
+
}} />
|
|
335
|
+
{readRuns && registry.overviewPanels.some((panel) => panel.placement === "idle" && (!panel.readRight || access.can(panel.readRight))) && <div className="flex min-h-[320px] flex-col"><PanelContributions placement="idle" registry={registry} userLocation={chatUserLocation(undefined, false, undefined)} /></div>}
|
|
336
|
+
</div>
|
|
337
|
+
</>
|
|
338
|
+
)}
|
|
339
|
+
<footer aria-label="Run status bar" className={statusBarClass} ref={setStatusContainer} />
|
|
340
|
+
{settings}
|
|
341
|
+
{draftChat}
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
};
|
|
345
|
+
return <WorkspaceModalContext.Provider value={workspaceContainer}><div className="flex h-full flex-col">
|
|
346
|
+
<header className="relative z-[80] flex flex-none items-stretch border-b border-border bg-shell empty:hidden">
|
|
347
|
+
<PanelContributions registry={registry} userLocation={chatUserLocation(runId, false, runLocation)} />
|
|
348
|
+
</header>
|
|
349
|
+
<div className="relative flex min-h-0 flex-1 flex-col" ref={setWorkspaceContainer}>{content()}</div>
|
|
350
|
+
</div></WorkspaceModalContext.Provider>;
|
|
271
351
|
}
|
|
272
352
|
|
|
273
353
|
interface Launch {
|
|
@@ -307,6 +387,7 @@ function PendingPanel({ children, header, headless = false, state }: { children?
|
|
|
307
387
|
return <div className="flex h-full flex-col bg-[image:var(--surface-backdrop)]">
|
|
308
388
|
{!headless && <header className={headerClass}>{header}</header>}
|
|
309
389
|
<main className="grid min-h-0 flex-1 place-items-center overflow-hidden p-6"><StartupNotice state={state}>{children}</StartupNotice></main>
|
|
390
|
+
{!headless && <footer aria-label="Run status bar" className={statusBarClass} />}
|
|
310
391
|
</div>;
|
|
311
392
|
}
|
|
312
393
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useId, useRef, useState } from "react";
|
|
2
|
-
import { ChevronLeftIcon, ExternalLinkIcon, LogOutIcon, MoreVerticalIcon, PlayIcon, SettingsIcon } from "lucide-react";
|
|
2
|
+
import { CircleHelpIcon, ChevronLeftIcon, ExternalLinkIcon, LogOutIcon, MoreVerticalIcon, PlayIcon, SettingsIcon } from "lucide-react";
|
|
3
3
|
import { coreContracts } from "@ragents/host/api/contracts";
|
|
4
4
|
import type { RunScriptListing } from "../../../server/src/chat-handler";
|
|
5
5
|
import { useAccess } from "../AccessContext";
|
|
@@ -14,7 +14,7 @@ const messageOf = (cause: unknown): string => cause instanceof Error ? cause.mes
|
|
|
14
14
|
type Scripts = { readonly kind: "loading" } | { readonly kind: "ready"; readonly scripts: readonly RunScriptListing[] };
|
|
15
15
|
|
|
16
16
|
/** The menu at the top right of the panel: settings, run scripts for the open run, server in the browser, and sign-out behind one button. */
|
|
17
|
-
export function RunPanelMenu({ onOpenSettings, runId }: { onOpenSettings: () => void; runId?: string }) {
|
|
17
|
+
export function RunPanelMenu({ onOpenSettings, onOpenHelp, runId }: { onOpenSettings: () => void; onOpenHelp?: () => void; runId?: string }) {
|
|
18
18
|
const host = useRunPanelHost();
|
|
19
19
|
const access = useAccess();
|
|
20
20
|
const [open, setOpen] = useState(false);
|
|
@@ -61,7 +61,8 @@ export function RunPanelMenu({ onOpenSettings, runId }: { onOpenSettings: () =>
|
|
|
61
61
|
</button>)}
|
|
62
62
|
</div>
|
|
63
63
|
</> : <>
|
|
64
|
-
<button className={itemClass} onClick={() => { close(); onOpenSettings(); }} type="button"><SettingsIcon />Settings</button>
|
|
64
|
+
{access.can("settings.read") && <button className={itemClass} onClick={() => { close(); onOpenSettings(); }} type="button"><SettingsIcon />Settings</button>}
|
|
65
|
+
{onOpenHelp && access.can("runs.inspect") && <button className={itemClass} onClick={() => { close(); onOpenHelp(); }} type="button"><CircleHelpIcon />Help</button>}
|
|
65
66
|
{scriptRun !== undefined && <button className={itemClass} onClick={() => listScripts(scriptRun)} type="button"><PlayIcon />Run script</button>}
|
|
66
67
|
{host.kind === "vscode" && <button className={itemClass} onClick={() => { close(); host.openExternal(new URL("/", window.location.href).toString()); }} type="button"><ExternalLinkIcon />Open in browser</button>}
|
|
67
68
|
{access.enabled && access.user && <button className={itemClass} disabled={pending} onClick={logout} title={access.user.id} type="button"><LogOutIcon /><span className="grid min-w-0"><span>Sign out</span><span className="truncate text-[0.68rem] text-muted-foreground">{access.user.label}</span></span></button>}
|
|
@@ -3,10 +3,9 @@ import { XIcon } from "lucide-react";
|
|
|
3
3
|
import { cn } from "cn";
|
|
4
4
|
import type { SessionContext, SessionNavigation, WorkspaceTabContribution } from "../PluginRegistry";
|
|
5
5
|
import { Button } from "../ui";
|
|
6
|
-
import { mountedTabs } from "../WorkspacePanel";
|
|
7
6
|
import { RUN_PANEL_WORKSPACE_ID } from "./workspace-state";
|
|
8
7
|
|
|
9
|
-
/**
|
|
8
|
+
/** Inspection overlays the selected content; visited keepMounted tabs retain their state. */
|
|
10
9
|
export function RunPanelWorkspace({ navigation, onClose, open, session, tabs }: {
|
|
11
10
|
navigation: SessionNavigation;
|
|
12
11
|
onClose: () => void;
|
|
@@ -53,3 +52,10 @@ export function RunPanelWorkspace({ navigation, onClose, open, session, tabs }:
|
|
|
53
52
|
</section>
|
|
54
53
|
</>;
|
|
55
54
|
}
|
|
55
|
+
|
|
56
|
+
export const mountedTabs = (
|
|
57
|
+
tabs: readonly WorkspaceTabContribution[],
|
|
58
|
+
activeTabId: string | undefined,
|
|
59
|
+
visited: readonly string[],
|
|
60
|
+
): readonly WorkspaceTabContribution[] =>
|
|
61
|
+
tabs.filter((tab) => tab.id === activeTabId || tab.keepMounted === true && visited.includes(tab.id));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isClipboardRunPanelMessage } from "./host-contract";
|
|
1
|
+
import { isClipboardRunPanelMessage, type ClipboardContent } from "./host-contract";
|
|
2
2
|
|
|
3
3
|
type EditingAction = "paste" | "copy" | "cut";
|
|
4
4
|
|
|
@@ -15,24 +15,20 @@ const actionOf = (event: KeyboardEvent): EditingAction | undefined => {
|
|
|
15
15
|
const isEditable = (element: Element | null): element is HTMLElement =>
|
|
16
16
|
element !== null && (element.tagName === "INPUT" || element.tagName === "TEXTAREA" || (element as HTMLElement).isContentEditable === true);
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* macOS delivers paste, copy, and cut through the application menu, and VS Code passes the
|
|
20
|
-
* command only to the document of its webview, not into a cross-origin iframe. The run panel
|
|
21
|
-
* therefore executes it itself; it fetches the clipboard text through the shell.
|
|
22
|
-
*/
|
|
18
|
+
/** VS Code's shell reads the clipboard for the cross-origin run panel. */
|
|
23
19
|
export function createClipboardReader(browser: Window) {
|
|
24
20
|
const parent = browser.parent;
|
|
25
|
-
const pending = new Map<string, (
|
|
21
|
+
const pending = new Map<string, (content: ClipboardContent) => void>();
|
|
26
22
|
|
|
27
23
|
const onMessage = (event: MessageEvent) => {
|
|
28
24
|
if (event.source !== parent || !isClipboardRunPanelMessage(event.data)) return;
|
|
29
25
|
const settle = pending.get(event.data.id);
|
|
30
26
|
if (!settle) return;
|
|
31
27
|
pending.delete(event.data.id);
|
|
32
|
-
settle(event.data.text);
|
|
28
|
+
settle({ text: event.data.text, files: event.data.files });
|
|
33
29
|
};
|
|
34
30
|
|
|
35
|
-
const read = () => new Promise<
|
|
31
|
+
const read = () => new Promise<ClipboardContent>((resolve) => {
|
|
36
32
|
const id = crypto.randomUUID();
|
|
37
33
|
pending.set(id, resolve);
|
|
38
34
|
parent.postMessage({ type: "clipboardRead", id }, "*");
|
|
@@ -41,11 +37,11 @@ export function createClipboardReader(browser: Window) {
|
|
|
41
37
|
browser.addEventListener("message", onMessage);
|
|
42
38
|
return {
|
|
43
39
|
read,
|
|
44
|
-
dispose: () => { browser.removeEventListener("message", onMessage); pending.forEach((settle) => settle("")); pending.clear(); },
|
|
40
|
+
dispose: () => { browser.removeEventListener("message", onMessage); pending.forEach((settle) => settle({ text: "", files: [] })); pending.clear(); },
|
|
45
41
|
};
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
export function installClipboardBridge(browser: Window, readClipboard?: () => Promise<
|
|
44
|
+
export function installClipboardBridge(browser: Window, readClipboard?: () => Promise<ClipboardContent>): () => void {
|
|
49
45
|
let active = true;
|
|
50
46
|
const reader = readClipboard ? undefined : createClipboardReader(browser);
|
|
51
47
|
const read = readClipboard ?? reader!.read;
|
|
@@ -59,10 +55,17 @@ export function installClipboardBridge(browser: Window, readClipboard?: () => Pr
|
|
|
59
55
|
browser.document.execCommand(action);
|
|
60
56
|
return;
|
|
61
57
|
}
|
|
62
|
-
void read().then((text) => {
|
|
63
|
-
if (!active || !
|
|
58
|
+
void read().then(({ text, files }) => {
|
|
59
|
+
if (!active || !isEditable(target) || !target.isConnected) return;
|
|
60
|
+
browser.focus();
|
|
64
61
|
target.focus();
|
|
65
|
-
|
|
62
|
+
if (files.length > 0) {
|
|
63
|
+
const clipboardData = new DataTransfer();
|
|
64
|
+
for (const file of files) clipboardData.items.add(file);
|
|
65
|
+
clipboardData.setData("text/plain", text);
|
|
66
|
+
if (!target.dispatchEvent(new ClipboardEvent("paste", { clipboardData, bubbles: true, cancelable: true }))) return;
|
|
67
|
+
}
|
|
68
|
+
if (text) browser.document.execCommand("insertText", false, text);
|
|
66
69
|
});
|
|
67
70
|
};
|
|
68
71
|
|
|
@@ -28,15 +28,18 @@ export type RunPanelHostMessage =
|
|
|
28
28
|
| { type: "runChanged"; runId: string | null }
|
|
29
29
|
| { type: "showStart" }
|
|
30
30
|
| { type: "openInCenter"; runId: string; elementId: string; title: string }
|
|
31
|
-
| { type: "returnToRunPanel"; runId: string; elementId: string }
|
|
32
31
|
| { type: "login" }
|
|
33
32
|
| { type: "logout" }
|
|
34
33
|
| { type: "openExternal"; url: string }
|
|
35
34
|
| { type: "openPage"; url: string; title: string };
|
|
36
35
|
|
|
37
|
-
/** The
|
|
36
|
+
/** The shell reads clipboard contents on behalf of its cross-origin frames. */
|
|
37
|
+
export interface ClipboardContent {
|
|
38
|
+
text: string;
|
|
39
|
+
files: File[];
|
|
40
|
+
}
|
|
38
41
|
export type RunPanelClipboardMessage = { type: "clipboardRead"; id: string };
|
|
39
|
-
export type ClipboardRunPanelMessage = { type: "
|
|
42
|
+
export type ClipboardRunPanelMessage = ClipboardContent & { type: "clipboardContent"; id: string };
|
|
40
43
|
|
|
41
44
|
export interface RunPanelKeyboardMessage {
|
|
42
45
|
type: "keyboardEvent";
|
|
@@ -68,7 +71,6 @@ export function isRunPanelKeyboardMessage(value: unknown): value is RunPanelKeyb
|
|
|
68
71
|
export type HostRunPanelMessage =
|
|
69
72
|
| { type: "selectRun"; runId: string | null }
|
|
70
73
|
| { type: "newRun"; startOptions?: Record<string, unknown>; entryId?: string }
|
|
71
|
-
| { type: "placements"; runId: string; center: string[] }
|
|
72
74
|
| { type: "theme"; theme: RunPanelTheme };
|
|
73
75
|
|
|
74
76
|
const isObject = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
|
|
@@ -88,8 +90,6 @@ export const isRunPanelHostMessage = (value: unknown): value is RunPanelHostMess
|
|
|
88
90
|
return value.runId === null || isText(value.runId);
|
|
89
91
|
case "openInCenter":
|
|
90
92
|
return isText(value.runId) && isText(value.elementId) && typeof value.title === "string";
|
|
91
|
-
case "returnToRunPanel":
|
|
92
|
-
return isText(value.runId) && isText(value.elementId);
|
|
93
93
|
case "openExternal":
|
|
94
94
|
return isText(value.url);
|
|
95
95
|
case "openPage":
|
|
@@ -103,7 +103,8 @@ export const isRunPanelClipboardMessage = (value: unknown): value is RunPanelCli
|
|
|
103
103
|
isObject(value) && value.type === "clipboardRead" && isText(value.id);
|
|
104
104
|
|
|
105
105
|
export const isClipboardRunPanelMessage = (value: unknown): value is ClipboardRunPanelMessage =>
|
|
106
|
-
isObject(value) && value.type === "
|
|
106
|
+
isObject(value) && value.type === "clipboardContent" && isText(value.id) && typeof value.text === "string"
|
|
107
|
+
&& Array.isArray(value.files) && value.files.every((file) => file instanceof File);
|
|
107
108
|
|
|
108
109
|
export const isHostRunPanelMessage = (value: unknown): value is HostRunPanelMessage => {
|
|
109
110
|
if (!isObject(value)) return false;
|
|
@@ -113,8 +114,6 @@ export const isHostRunPanelMessage = (value: unknown): value is HostRunPanelMess
|
|
|
113
114
|
case "newRun":
|
|
114
115
|
return (value.startOptions === undefined || (isObject(value.startOptions) && !Array.isArray(value.startOptions)))
|
|
115
116
|
&& (value.entryId === undefined || isText(value.entryId));
|
|
116
|
-
case "placements":
|
|
117
|
-
return isText(value.runId) && Array.isArray(value.center) && value.center.every(isText);
|
|
118
117
|
case "theme":
|
|
119
118
|
return isRunPanelTheme(value.theme);
|
|
120
119
|
default:
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RunAppNavigation } from "../run-apps";
|
|
2
|
+
import { createContext, createElement, useContext, type PropsWithChildren } from "react";
|
|
2
3
|
import { OfferedMachinesProvider, type OfferedMachines } from "../offered-machines";
|
|
3
4
|
import type { RunPanelHostKind } from "./run-panel-location";
|
|
4
5
|
import { isHostRunPanelMessage, type RunPanelHostMessage, type RunPanelTheme, type HostRunPanelMessage } from "./host-contract";
|
|
5
6
|
|
|
6
|
-
export interface RunPanelHost {
|
|
7
|
+
export interface RunPanelHost extends RunAppNavigation {
|
|
7
8
|
readonly kind: RunPanelHostKind;
|
|
8
9
|
/** VS Code is a workstation itself and also offers workstations for new runs, the browser only the server. */
|
|
9
10
|
readonly machines: OfferedMachines;
|
|
10
|
-
/** Elements of the run that the host currently shows in the center; always empty in the browser. */
|
|
11
|
-
centerElements(runId: string): ReadonlySet<string>;
|
|
12
|
-
subscribe(listener: () => void): () => void;
|
|
13
|
-
openInCenter(runId: string, elementId: string, title: string): void;
|
|
14
|
-
returnToRunPanel(runId: string, elementId: string): void;
|
|
15
11
|
requestLogin(): void;
|
|
16
12
|
/** The host holds the session token and ends the session itself. */
|
|
17
13
|
requestLogout(): void;
|
|
@@ -23,8 +19,6 @@ export interface RunPanelHost {
|
|
|
23
19
|
notify(message: Extract<RunPanelHostMessage, { type: "ready" | "runChanged" | "showStart" }>): void;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
const emptySet: ReadonlySet<string> = new Set();
|
|
27
|
-
|
|
28
22
|
const unsupported = (action: string) => (): never => {
|
|
29
23
|
throw new Error(`${action} is only available in VS Code.`);
|
|
30
24
|
};
|
|
@@ -33,10 +27,7 @@ export function createBrowserHost(browser: Window): RunPanelHost {
|
|
|
33
27
|
return {
|
|
34
28
|
kind: "browser",
|
|
35
29
|
machines: "server",
|
|
36
|
-
|
|
37
|
-
subscribe: () => () => undefined,
|
|
38
|
-
openInCenter: unsupported("Placing a mini-app in the center"),
|
|
39
|
-
returnToRunPanel: unsupported("Bringing a mini-app back"),
|
|
30
|
+
openApp: unsupported("Opening a mini-app editor"),
|
|
40
31
|
requestLogin: unsupported("Signing in through the host"),
|
|
41
32
|
requestLogout: unsupported("Signing out through the host"),
|
|
42
33
|
openExternal: (url) => { browser.open(url, "_blank", "noopener"); },
|
|
@@ -50,29 +41,17 @@ export function createBrowserHost(browser: Window): RunPanelHost {
|
|
|
50
41
|
export function createVsCodeHost(browser: Window): RunPanelHost {
|
|
51
42
|
const parent = browser.parent;
|
|
52
43
|
if (parent === browser) throw new Error("The panel runs with host=vscode but is not embedded in a webview.");
|
|
53
|
-
const placements = new Map<string, ReadonlySet<string>>();
|
|
54
|
-
const placementListeners = new Set<() => void>();
|
|
55
44
|
const commandListeners = new Set<(message: HostRunPanelMessage) => void>();
|
|
56
45
|
const post = (message: RunPanelHostMessage) => parent.postMessage(message, "*");
|
|
57
46
|
browser.addEventListener("message", (event) => {
|
|
58
47
|
if (event.source !== parent || !isHostRunPanelMessage(event.data)) return;
|
|
59
48
|
const message = event.data;
|
|
60
|
-
if (message.type === "placements") {
|
|
61
|
-
placements.set(message.runId, new Set(message.center));
|
|
62
|
-
for (const listener of placementListeners) listener();
|
|
63
|
-
}
|
|
64
49
|
for (const listener of commandListeners) listener(message);
|
|
65
50
|
});
|
|
66
51
|
return {
|
|
67
52
|
kind: "vscode",
|
|
68
53
|
machines: "all",
|
|
69
|
-
|
|
70
|
-
subscribe: (listener) => {
|
|
71
|
-
placementListeners.add(listener);
|
|
72
|
-
return () => { placementListeners.delete(listener); };
|
|
73
|
-
},
|
|
74
|
-
openInCenter: (runId, elementId, title) => post({ type: "openInCenter", runId, elementId, title }),
|
|
75
|
-
returnToRunPanel: (runId, elementId) => post({ type: "returnToRunPanel", runId, elementId }),
|
|
54
|
+
openApp: (runId, elementId, title) => post({ type: "openInCenter", runId, elementId, title }),
|
|
76
55
|
requestLogin: () => post({ type: "login" }),
|
|
77
56
|
requestLogout: () => post({ type: "logout" }),
|
|
78
57
|
openExternal: (url) => post({ type: "openExternal", url }),
|
|
@@ -101,9 +80,4 @@ export function useRunPanelHost(): RunPanelHost {
|
|
|
101
80
|
return host;
|
|
102
81
|
}
|
|
103
82
|
|
|
104
|
-
export function useCenterElements(runId: string): ReadonlySet<string> {
|
|
105
|
-
const host = useRunPanelHost();
|
|
106
|
-
return useSyncExternalStore(host.subscribe, () => host.centerElements(runId), () => emptySet);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
83
|
export type { RunPanelTheme };
|