@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.
Files changed (206) hide show
  1. package/apps/server/src/host-api.json +6 -2
  2. package/apps/server/src/host-api.ts +3 -2
  3. package/apps/server/src/host-web.ts +3 -0
  4. package/apps/server/src/plugin-support/actor-programs/contract.ts +0 -3
  5. package/apps/server/src/plugin-support/removed-layout.ts +94 -0
  6. package/apps/server/src/ragents/engine.ts +2 -1
  7. package/apps/server/src/title-compactor.ts +5 -7
  8. package/apps/web/dist/assets/highlighted-body-KPVGNVTW-k67VKdbw.js +1 -0
  9. package/apps/web/dist/assets/main-CVjIz7BS.js +674 -0
  10. package/apps/web/dist/help/guide-clients.html +22 -42
  11. package/apps/web/dist/help/guide-clients.md +24 -45
  12. package/apps/web/dist/help/guide-functions.html +1 -1
  13. package/apps/web/dist/help/guide-functions.md +1 -1
  14. package/apps/web/dist/help/guide-getting-started.html +14 -22
  15. package/apps/web/dist/help/guide-getting-started.md +17 -24
  16. package/apps/web/dist/help/guide-ideas.html +1 -1
  17. package/apps/web/dist/help/guide-ideas.md +1 -1
  18. package/apps/web/dist/help/guide-plugins.html +1 -1
  19. package/apps/web/dist/help/guide-plugins.md +1 -1
  20. package/apps/web/dist/help/guide-programs.html +1 -1
  21. package/apps/web/dist/help/guide-programs.md +1 -1
  22. package/apps/web/dist/help/index.html +12 -79
  23. package/apps/web/dist/help/mini-app.css +8 -502
  24. package/apps/web/dist/host-web.json +28 -23
  25. package/apps/web/dist/index.html +2 -3
  26. package/apps/web/dist/run-panel.html +3 -4
  27. package/apps/web/src/Overview.tsx +4 -143
  28. package/apps/web/src/PluginChat.tsx +22 -116
  29. package/apps/web/src/PluginRegistry.tsx +4 -9
  30. package/apps/web/src/RunPreparationChat.tsx +4 -1
  31. package/apps/web/src/host-modules.ts +2 -0
  32. package/apps/web/src/main.tsx +23 -3
  33. package/apps/web/src/panel/RunLine.tsx +5 -2
  34. package/apps/web/src/panel/RunsPage.tsx +6 -6
  35. package/apps/web/src/panel/StartPage.tsx +6 -5
  36. package/apps/web/src/panel/contract.ts +3 -0
  37. package/apps/web/src/panel/page-props.ts +2 -0
  38. package/apps/web/src/run-apps.ts +34 -0
  39. package/apps/web/src/run-overview.ts +0 -27
  40. package/apps/web/src/run-panel/PanelContributions.tsx +41 -0
  41. package/apps/web/src/run-panel/RunPanelApp.tsx +172 -91
  42. package/apps/web/src/run-panel/RunPanelMenu.tsx +4 -3
  43. package/apps/web/src/run-panel/RunPanelWorkspace.tsx +8 -2
  44. package/apps/web/src/run-panel/clipboard.ts +17 -14
  45. package/apps/web/src/run-panel/host-contract.ts +8 -9
  46. package/apps/web/src/run-panel/host.ts +5 -31
  47. package/apps/web/src/run-panel/input-bridge.ts +15 -7
  48. package/bundles/ragents.activity/ragents-bundle.json +2 -2
  49. package/bundles/ragents.actor-programs/prompt.hbs +4 -5
  50. package/bundles/ragents.actor-programs/ragents-bundle.json +4 -14
  51. package/bundles/ragents.actor-programs/server/index.js +2 -14
  52. package/bundles/ragents.actor-programs/server/index.js.map +2 -2
  53. package/bundles/ragents.actor-programs/web/classes.json +1 -1
  54. package/bundles/ragents.actor-programs/web/index.js +1 -3
  55. package/bundles/ragents.actor-programs/web/index.js.map +4 -4
  56. package/bundles/ragents.ask/ragents-bundle.json +2 -2
  57. package/bundles/ragents.browser/ragents-bundle.json +2 -2
  58. package/bundles/ragents.documents/ragents-bundle.json +2 -2
  59. package/bundles/ragents.lsp-fsharp/ragents-bundle.json +2 -2
  60. package/bundles/ragents.lsp-roslyn/ragents-bundle.json +2 -2
  61. package/bundles/ragents.lsp-typescript/ragents-bundle.json +2 -2
  62. package/bundles/ragents.model-relay/ragents-bundle.json +2 -2
  63. package/bundles/ragents.orchestration/ragents-bundle.json +15 -41
  64. package/bundles/ragents.orchestration/server/index.js +10 -249
  65. package/bundles/ragents.orchestration/server/index.js.map +4 -4
  66. package/bundles/ragents.orchestration/web/chunks/chunk-KU7O6MEX.js +2 -0
  67. package/bundles/ragents.orchestration/web/chunks/chunk-KU7O6MEX.js.map +7 -0
  68. package/bundles/ragents.orchestration/web/classes.json +1 -1
  69. package/bundles/ragents.orchestration/web/exports/web/program-slot.js +1 -1
  70. package/bundles/ragents.orchestration/web/index.js +10 -18
  71. package/bundles/ragents.orchestration/web/index.js.map +4 -4
  72. package/bundles/ragents.overseer/ragents-bundle.json +3 -3
  73. package/bundles/ragents.overseer/server/index.js +0 -1
  74. package/bundles/ragents.overseer/server/index.js.map +2 -2
  75. package/bundles/ragents.processes/ragents-bundle.json +2 -2
  76. package/bundles/ragents.product/ragents-bundle.json +2 -2
  77. package/bundles/ragents.profile-distribution/ragents-bundle.json +2 -2
  78. package/bundles/ragents.reference/ragents-bundle.json +3 -3
  79. package/bundles/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
  80. package/bundles/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -2
  81. package/bundles/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +6 -7
  82. package/bundles/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -3
  83. package/bundles/ragents.reference/run-scripts/conversation-circle/src/server.ts +3 -13
  84. package/bundles/ragents.reference/run-scripts/conversation-circle/tests/helpers.ts +1 -1
  85. package/bundles/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +1 -6
  86. package/bundles/ragents.reference/run-scripts/learning-afternoon/src/server.ts +1 -15
  87. package/bundles/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
  88. package/bundles/ragents.reference/run-scripts/moderated-round/tests/helpers.ts +1 -1
  89. package/bundles/ragents.reference/run-scripts/quick-note/RUN.md +1 -1
  90. package/bundles/ragents.reference/run-scripts/quick-note/src/server.ts +1 -2
  91. package/bundles/ragents.reference/run-scripts/quick-note/tests/program.test.ts +0 -2
  92. package/bundles/ragents.reference/run-scripts/run-roster/RUN.md +2 -2
  93. package/bundles/ragents.reference/run-scripts/run-roster/src/server.ts +1 -2
  94. package/bundles/ragents.reference/run-scripts/run-roster/tests/program.test.ts +0 -2
  95. package/bundles/ragents.reference/run-scripts/shared-actor-list/src/server.ts +1 -1
  96. package/bundles/ragents.reference/run-scripts/shared-actor-list/tests/helpers.ts +1 -1
  97. package/bundles/ragents.reference/run-scripts/word-game/src/contract.ts +1 -1
  98. package/bundles/ragents.reference/run-scripts/word-game/src/server.ts +0 -1
  99. package/bundles/ragents.reference/run-scripts/word-game/tests/program.test.ts +1 -2
  100. package/bundles/ragents.reference/skills/80-actor-text-analysis/SKILL.md +1 -1
  101. package/bundles/ragents.reference/skills/90-actor-notes/SKILL.md +1 -1
  102. package/bundles/ragents.todo/ragents-bundle.json +2 -2
  103. package/bundles/ragents.transcript/ragents-bundle.json +2 -2
  104. package/bundles/ragents.watch/ragents-bundle.json +2 -2
  105. package/bundles/ragents.workspace/ragents-bundle.json +2 -2
  106. package/package.json +8 -8
  107. package/packages/agent/dist/core/agent-settings.d.ts +1 -1
  108. package/packages/ai/dist/types.d.ts +2 -1
  109. package/packages/ragents/src/runtime/journal.ts +4 -0
  110. package/plugins/ragents.actor-programs/prompt.hbs +4 -5
  111. package/plugins/ragents.actor-programs/server/access-projections.ts +1 -1
  112. package/plugins/ragents.actor-programs/server/index.ts +0 -2
  113. package/plugins/ragents.actor-programs/server/runtime.ts +2 -10
  114. package/plugins/ragents.actor-programs/web/ActorViewFrame.tsx +5 -20
  115. package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -79
  116. package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +0 -9
  117. package/plugins/ragents.actor-programs/web/api.ts +0 -20
  118. package/plugins/ragents.actor-programs/web/context.ts +0 -3
  119. package/plugins/ragents.actor-programs/web/index.tsx +0 -9
  120. package/plugins/ragents.orchestration/ragents-plugin.json +1 -2
  121. package/plugins/ragents.orchestration/server/index.ts +1 -11
  122. package/plugins/ragents.orchestration/web/ActorChat.tsx +1 -1
  123. package/plugins/ragents.orchestration/web/ActorChatControls.tsx +1 -1
  124. package/plugins/ragents.orchestration/web/FlowInspector.tsx +2 -4
  125. package/plugins/ragents.orchestration/web/InspectionPanel.tsx +21 -0
  126. package/plugins/ragents.orchestration/web/index.tsx +13 -9
  127. package/plugins/ragents.orchestration/web/program-slot.ts +0 -3
  128. package/plugins/ragents.orchestration/web/run-panel/AddresseeControl.tsx +15 -36
  129. package/plugins/ragents.orchestration/web/run-panel/RunPanel.tsx +76 -451
  130. package/plugins/ragents.orchestration/web/run-panel/run-panel-actors.ts +0 -27
  131. package/plugins/ragents.orchestration/web/run-panel/run-panel-state.ts +7 -40
  132. package/plugins/ragents.overseer/server/coordinator-prompt.ts +0 -1
  133. package/plugins/ragents.reference/examples.ts +1 -2
  134. package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
  135. package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -2
  136. package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +6 -7
  137. package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -3
  138. package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +3 -13
  139. package/plugins/ragents.reference/run-scripts/conversation-circle/tests/helpers.ts +1 -1
  140. package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +1 -6
  141. package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +1 -15
  142. package/plugins/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
  143. package/plugins/ragents.reference/run-scripts/moderated-round/tests/helpers.ts +1 -1
  144. package/plugins/ragents.reference/run-scripts/quick-note/RUN.md +1 -1
  145. package/plugins/ragents.reference/run-scripts/quick-note/src/server.ts +1 -2
  146. package/plugins/ragents.reference/run-scripts/quick-note/tests/program.test.ts +0 -2
  147. package/plugins/ragents.reference/run-scripts/run-roster/RUN.md +2 -2
  148. package/plugins/ragents.reference/run-scripts/run-roster/src/server.ts +1 -2
  149. package/plugins/ragents.reference/run-scripts/run-roster/tests/program.test.ts +0 -2
  150. package/plugins/ragents.reference/run-scripts/shared-actor-list/src/server.ts +1 -1
  151. package/plugins/ragents.reference/run-scripts/shared-actor-list/tests/helpers.ts +1 -1
  152. package/plugins/ragents.reference/run-scripts/word-game/src/contract.ts +1 -1
  153. package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +0 -1
  154. package/plugins/ragents.reference/run-scripts/word-game/tests/program.test.ts +1 -2
  155. package/plugins/ragents.reference/skills/80-actor-text-analysis/SKILL.md +1 -1
  156. package/plugins/ragents.reference/skills/90-actor-notes/SKILL.md +1 -1
  157. package/plugins/ragents.reference/walkthroughs.ts +13 -14
  158. package/apps/web/dist/assets/highlighted-body-KPVGNVTW-BwD4uYEe.js +0 -1
  159. package/apps/web/dist/assets/main-KBrc5Q77.js +0 -11
  160. package/apps/web/dist/assets/mermaid-HWGCJPDP-CSCkaArr.js +0 -608
  161. package/apps/web/dist/assets/runPanel-BjG5NBKm.js +0 -26
  162. package/apps/web/src/App.tsx +0 -318
  163. package/apps/web/src/SessionList.tsx +0 -157
  164. package/apps/web/src/SurfaceShortcuts.tsx +0 -60
  165. package/apps/web/src/WorkspacePanel.tsx +0 -309
  166. package/apps/web/src/run-panel.tsx +0 -52
  167. package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js +0 -11
  168. package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js.map +0 -7
  169. package/bundles/ragents.orchestration/server/exports/server/contract.js +0 -9
  170. package/bundles/ragents.orchestration/server/exports/server/contract.js.map +0 -7
  171. package/bundles/ragents.orchestration/surface.hbs +0 -39
  172. package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js +0 -2
  173. package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js.map +0 -7
  174. package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js +0 -2
  175. package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js.map +0 -7
  176. package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js +0 -2
  177. package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js.map +0 -7
  178. package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js +0 -2
  179. package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js.map +0 -7
  180. package/bundles/ragents.orchestration/web/exports/web/surface-entities.js +0 -2
  181. package/bundles/ragents.orchestration/web/exports/web/surface-entities.js.map +0 -7
  182. package/bundles/ragents.orchestration/web/exports/web/tile-docking.js +0 -2
  183. package/bundles/ragents.orchestration/web/exports/web/tile-docking.js.map +0 -7
  184. package/plugins/ragents.actor-programs/web/ExpandIcon.tsx +0 -5
  185. package/plugins/ragents.actor-programs/web/FullscreenHost.tsx +0 -31
  186. package/plugins/ragents.orchestration/contract.ts +0 -51
  187. package/plugins/ragents.orchestration/server/contract.ts +0 -14
  188. package/plugins/ragents.orchestration/server/surface-tool.ts +0 -198
  189. package/plugins/ragents.orchestration/surface.hbs +0 -39
  190. package/plugins/ragents.orchestration/tiled-layout.ts +0 -55
  191. package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +0 -29
  192. package/plugins/ragents.orchestration/web/ActorShortcut.tsx +0 -74
  193. package/plugins/ragents.orchestration/web/ActorSurfaceControls.tsx +0 -130
  194. package/plugins/ragents.orchestration/web/ActorTile.tsx +0 -23
  195. package/plugins/ragents.orchestration/web/Center.tsx +0 -133
  196. package/plugins/ragents.orchestration/web/SurfaceTiles.tsx +0 -47
  197. package/plugins/ragents.orchestration/web/TiledSurface.tsx +0 -280
  198. package/plugins/ragents.orchestration/web/actor-header-settings.ts +0 -41
  199. package/plugins/ragents.orchestration/web/run-panel/RunPanelSettings.tsx +0 -50
  200. package/plugins/ragents.orchestration/web/run-panel/run-panel-settings.ts +0 -47
  201. package/plugins/ragents.orchestration/web/run-panel/sheet-status.ts +0 -30
  202. package/plugins/ragents.orchestration/web/surface-entities.ts +0 -21
  203. package/plugins/ragents.orchestration/web/surface-view-settings.ts +0 -42
  204. package/plugins/ragents.orchestration/web/tile-docking.ts +0 -105
  205. package/plugins/ragents.orchestration/web/tiled-view-settings.ts +0 -74
  206. /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 { SessionList } from "../SessionList";
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 in the browser the run list; layout=app shows exactly one surface element. */
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
- const selectRun = useCallback((id: string) => {
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 = settingsOpen && <SettingsModal onClose={() => setSettingsOpen(false)} registry={registry} />;
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
- if (launch) return <>
195
- {launch.error === undefined
196
- ? <PendingPanel header={pendingHeader(runTitle ?? NEW_RUN_TITLE)} state={launchStarting} />
197
- : <PendingPanel header={pendingHeader(runTitle ?? NEW_RUN_TITLE)} state={{ kind: "error", title: "The run could not be started", detail: launch.error }}>{toStart}</PendingPanel>}
198
- {settings}
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
- </div>
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
- /** The sidebar as a pop-out over the run panel's chat and stage, with two areas only over the right one; the rest is dimmed, a click outside, Escape, and X close it, visited keepMounted tabs stay mounted. */
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, (text: string) => void>();
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<string>((resolve) => {
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<string>): () => void {
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 || !text || !isEditable(target) || !target.isConnected) return;
58
+ void read().then(({ text, files }) => {
59
+ if (!active || !isEditable(target) || !target.isConnected) return;
60
+ browser.focus();
64
61
  target.focus();
65
- browser.document.execCommand("insertText", false, text);
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 clipboard text: a cross-origin iframe may not read it in the webview, the shell above it may. */
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: "clipboardText"; id: string; text: string };
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 === "clipboardText" && isText(value.id) && typeof value.text === "string";
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 { createContext, createElement, useContext, useSyncExternalStore, type PropsWithChildren } from "react";
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
- centerElements: () => emptySet,
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
- centerElements: (runId) => placements.get(runId) ?? emptySet,
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 };