@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,82 +1,49 @@
1
- import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type MouseEvent, type PointerEvent, type ReactNode, type RefObject } from "react";
2
- import { createPortal } from "react-dom";
3
- import { ArrowUpRightIcon, LayoutGridIcon, Maximize2Icon, MessageSquareIcon, PanelBottomIcon, PanelRightIcon } from "lucide-react";
1
+ import { runApps, selectedRunApp, RunAppView } from "@ragents/web/run-apps";
2
+ import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
3
+ import { ArrowUpRightIcon, LayoutGridIcon, SearchIcon } from "lucide-react";
4
4
  import { useAccess } from "@ragents/web/AccessContext";
5
5
  import { ChatPanel } from "quassel";
6
- import { runIsWorking } from "@ragents/web/chat/chat-target";
7
- import { useCenterElements, useRunPanelHost } from "@ragents/web/run-panel/host";
8
- import type { SurfaceCenterContext, SurfaceElementContribution, SurfaceElementDefinition, CardSectionContribution, SessionContext, SessionNavigation } from "@ragents/web/PluginRegistry";
9
- import { Button, cn, Spinner, StartupNotice } from "@ragents/web/ui";
6
+ import { useRunPanelHost } from "@ragents/web/run-panel/host";
7
+ import { useSurfaceController, type SurfaceCenterContext, type CardSectionContribution, type SessionContext, type SessionNavigation } from "@ragents/web/PluginRegistry";
8
+ import { INSPECTION_TAB_ID } from "../InspectionPanel";
9
+ import { Button, StartupNotice } from "@ragents/web/ui";
10
10
  import { ActorChat } from "../ActorChat";
11
11
  import { ActorChatControls } from "../ActorChatControls";
12
- import { useActorHeaderMode } from "../actor-header-settings";
13
12
  import { surfaceStartupState, type SurfaceStartupState } from "../surface-startup";
14
- import { actorVisibleOnSurface, saveSurfaceViewPreferences, useSurfaceViewPreferences } from "../surface-view-settings";
15
13
  import { cardSectionsClass } from "../constants";
16
14
  import type { FlowSelection } from "../FlowInspector";
17
- import { useProgramSlot } from "../program-slot";
18
15
  import { chatPrimaryId, runViewFrom, type RunActor, type RunView } from "@ragents/web/run-view";
19
16
  import { AddresseeControl } from "./AddresseeControl";
20
- import { runPanelActors, elementNeedsAttention, partitionRunPanelActors } from "./run-panel-actors";
21
- import { useRunPanelSettings } from "./run-panel-settings";
17
+ import { runPanelActors } from "./run-panel-actors";
22
18
  import { chatShowsContent, useRunPanelStartup } from "./run-panel-startup";
23
- import { chatLayoutFor, clampChatWidth, saveRunPanelState, useRunPanelState, type ChatLayout, type RunPanelChatMode, type RunPanelState } from "./run-panel-state";
24
- import { sheetStatus } from "./sheet-status";
19
+ import { saveRunPanelState, useRunPanelState, type RunPanelState } from "./run-panel-state";
25
20
 
26
- const SHEET_BLUR_DELAY = 220;
27
-
28
- const chipRowClass = "flex flex-none flex-wrap items-center gap-1.5 border-b border-border bg-shell px-2 py-1.5";
29
- const chipClass = "flex h-7 max-w-[200px] flex-none cursor-pointer items-center gap-1.5 rounded-full border border-border bg-card pr-2 pl-1 text-[0.72rem] font-semibold text-foreground hover:bg-accent focus-visible:outline-2 focus-visible:outline-ring/60 focus-visible:outline-offset-1 aria-pressed:border-primary aria-pressed:bg-primary/10";
30
- const chatLayoutClass: Readonly<Record<ChatLayout, string>> = {
31
- full: "relative flex min-h-0 min-w-0 flex-1 flex-col",
32
- side: "relative flex min-h-0 min-w-0 flex-col",
33
- floating: "absolute inset-x-2.5 bottom-0 z-20 flex flex-col overflow-hidden rounded-t-2xl border border-b-0 border-border bg-card shadow-[0_-16px_60px_#00000080] transition-[height] duration-300 ease-[cubic-bezier(.32,.72,0,1)] motion-reduce:transition-none",
34
- };
21
+ const chipClass = "flex h-7 max-w-[200px] flex-none cursor-pointer items-center gap-1.5 rounded-full border border-border bg-card px-2 text-[0.72rem] font-semibold text-foreground hover:bg-accent focus-visible:outline-2 focus-visible:outline-ring/60 aria-selected:border-primary aria-selected:bg-primary/10";
22
+ const viewClass = "relative flex min-h-0 min-w-0 flex-1 flex-col";
35
23
  const chatSurfaceClass = "rounded-none border-0 bg-transparent";
36
- const chatModeButtonClass = "aria-pressed:bg-accent aria-pressed:text-primary";
37
- const chatModes: readonly { Icon: typeof MessageSquareIcon; label: string; mode: RunPanelChatMode; title: string }[] = [
38
- { Icon: MessageSquareIcon, label: "Chat only", mode: "chat", title: "Chat only: the chat fills the run panel, the mini-app steps back" },
39
- { Icon: PanelBottomIcon, label: "Chat at bottom", mode: "bottom", title: "Chat at bottom: the sheet over the mini-app" },
40
- { Icon: PanelRightIcon, label: "Chat on right", mode: "side", title: "Chat on right: next to the mini-app" },
41
- ];
42
-
43
- interface RunPanelElement {
44
- Element: SurfaceElementContribution["Element"];
45
- definition: SurfaceElementDefinition;
46
- }
47
24
 
48
25
  export function OrchestrationRunPanel(props: SurfaceCenterContext) {
49
26
  return <RunPanel key={props.session.session.id} {...props} />;
50
27
  }
51
28
 
52
- function RunPanel({ surfaceElements, cardSections, navigation, renderChat, session, toolbarContainer }: SurfaceCenterContext) {
29
+ function RunPanel({ surfaceElements, cardSections, navigation, renderChat, session }: SurfaceCenterContext) {
53
30
  const runId = session.session.id;
31
+ const surface = useSurfaceController();
54
32
  const inspect = useAccess().can("runs.inspect");
55
33
  const host = useRunPanelHost();
56
34
  const view = runViewFrom(session.runView);
57
35
  const stored = useRunPanelState(runId);
58
- const centered = useCenterElements(runId);
59
- const rootRef = useRef<HTMLDivElement>(null);
60
- const sheetRef = useRef<HTMLElement>(null);
61
- const grabRef = useRef<HTMLButtonElement>(null);
62
- const statusRef = useRef<HTMLButtonElement>(null);
63
- const [chatWidth, setChatWidth] = useState(stored.chatWidth);
64
- const [width, setWidth] = useState(0);
65
- const [addresseeOpen, setAddresseeOpen] = useState(false);
66
- const settings = useRunPanelSettings();
67
- useEffect(() => setChatWidth(stored.chatWidth), [stored.chatWidth]);
68
- useEffect(() => {
69
- const root = rootRef.current;
70
- if (!root) return;
71
- const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));
72
- observer.observe(root);
73
- return () => observer.disconnect();
74
- }, []);
75
36
  const save = useCallback((patch: Partial<RunPanelState>) => saveRunPanelState(runId, { ...stored, ...patch }), [runId, stored]);
76
- const elements = useMemo<RunPanelElement[]>(() => surfaceElements.flatMap(({ Element, select }) => select(session)
77
- .filter((definition) => definition.visible !== false)
78
- .map((definition) => ({ Element, definition }))), [surfaceElements, session]);
79
- const selectedElement = elements.find((entry) => entry.definition.id === stored.element) ?? elements[0];
37
+ const elements = useMemo(() => runApps(session, surfaceElements), [surfaceElements, session]);
38
+ const selectedElement = host.kind === "browser" ? selectedRunApp(elements, stored.element) : undefined;
39
+ useEffect(() => {
40
+ if (host.kind === "browser" && session.connected && stored.element !== null && !selectedElement) save({ element: null });
41
+ }, [host.kind, save, selectedElement, session.connected, stored.element]);
42
+ const [visited, setVisited] = useState<ReadonlySet<string>>(() => new Set(stored.element ? [stored.element] : []));
43
+ const selectedId = selectedElement?.definition.id;
44
+ useEffect(() => {
45
+ if (selectedId) setVisited((current) => current.has(selectedId) ? current : new Set([...current, selectedId]));
46
+ }, [selectedId]);
80
47
  const startup = useRunPanelStartup(
81
48
  surfaceStartupState({ view, startup: session.startup, connected: session.connected, running: session.running, error: session.conversationError }),
82
49
  session.connected,
@@ -84,424 +51,82 @@ function RunPanel({ surfaceElements, cardSections, navigation, renderChat, sessi
84
51
  );
85
52
  const notice = startup ? <RunPanelStartup state={startup} /> : undefined;
86
53
  const actors = useMemo(() => view ? runPanelActors(view, inspect) : [], [inspect, view]);
87
- const mode = useActorHeaderMode(runId);
88
- const preferences = useSurfaceViewPreferences(runId);
89
- const appActorIds = useMemo(() => new Set(elements.flatMap(({ definition }) => definition.anchorActorId ? [definition.anchorActorId] : [])), [elements]);
90
54
  const primaryId = view ? chatPrimaryId(view) : undefined;
91
55
  const selectedActor = actors.find((actor) => actor.id === stored.actor) ?? actors.find((actor) => actor.id === primaryId) ?? actors[0];
92
- const { shown, hidden } = useMemo(() => partitionRunPanelActors(actors, {
93
- mode,
94
- primaryId,
95
- selectedId: selectedActor?.id,
96
- onStage: (actor) => actor.id === primaryId || actorVisibleOnSurface(actor, appActorIds, preferences, primaryId),
97
- }), [actors, appActorIds, mode, preferences, primaryId, selectedActor?.id]);
98
- const availableWidth = () => rootRef.current?.clientWidth ?? Number.POSITIVE_INFINITY;
99
- const selectActor = useCallback((actor: RunActor) => save({ actor: actor.id }), [save]);
100
- const revealActor = useCallback((actor: RunActor) => {
101
- saveSurfaceViewPreferences(runId, { ...preferences, actorVisibility: { ...preferences.actorVisibility, [actor.id]: true } });
56
+ const [visitedActors, setVisitedActors] = useState<ReadonlySet<string>>(() => new Set(stored.actor ? [stored.actor] : []));
57
+ const selectActor = useCallback((actor: RunActor) => {
58
+ setVisitedActors((current) => new Set([...current, actor.id]));
102
59
  save({ actor: actor.id });
103
- }, [preferences, runId, save]);
60
+ }, [save]);
104
61
  const navigate = useCallback((selection: FlowSelection) => {
105
62
  const actor = selection.type === "actor" ? actors.find((entry) => entry.id === selection.id) : undefined;
106
63
  if (actor) selectActor(actor);
107
- }, [actors, selectActor]);
64
+ else if (inspect) navigation.openTab(INSPECTION_TAB_ID, selection);
65
+ }, [actors, inspect, navigation, selectActor]);
108
66
  const primarySelected = selectedActor === undefined || selectedActor.id === primaryId;
109
- const narrow = width < settings.sideWidth;
110
- const layout = chatLayoutFor({ chat: stored.chat, hasElement: selectedElement !== undefined, narrow });
111
- const sheet = useSheet(layout === "floating", sheetRef, grabRef, addresseeOpen, settings.openDelay);
112
- const geometry = useSheetPeek(layout === "floating", sheetRef, grabRef, statusRef, selectedActor?.id);
113
- const expandedHeight = Math.max(geometry.minimum, Math.min(stored.sheetExpandedHeight ?? geometry.maximum, geometry.maximum));
114
- const resize = useSheetResize(layout === "floating", sheetRef, grabRef, sheet, geometry, expandedHeight,
115
- (height) => save({ sheetExpandedHeight: Math.round(height) }));
116
- const scrollerRef = useRef<HTMLDivElement | null>(null);
117
67
  useEffect(() => {
118
- if (layout !== "floating" || !sheet.expanded) return;
119
- const scroller = scrollerRef.current;
120
- if (!scroller) return;
121
- const toBottom = () => { scroller.scrollTop = scroller.scrollHeight; };
122
- toBottom();
123
- const timer = window.setTimeout(toBottom, 320);
124
- return () => window.clearTimeout(timer);
125
- }, [layout, sheet.expanded]);
126
- const status = useMemo(() => {
127
- if (layout !== "floating" || !selectedActor) return undefined;
128
- const messages = primarySelected ? session.messages : session.actorConversations?.[selectedActor.id] ?? [];
129
- const working = primarySelected ? session.connected && (session.running || runIsWorking(session.runView, runId)) : selectedActor.lifecycle?.kind === "running";
130
- return sheetStatus(messages, working, selectedActor.handle);
131
- }, [layout, primarySelected, runId, selectedActor, session.actorConversations, session.connected, session.messages, session.runView, session.running]);
132
-
133
- const addressee = view && selectedActor && <AddresseeControl
134
- hidden={hidden}
135
- onOpenChange={setAddresseeOpen}
136
- onReveal={revealActor}
68
+ const selection = selectedElement ? { type: "run-app", id: selectedElement.definition.id }
69
+ : selectedActor ? { type: "actor", id: selectedActor.id } : undefined;
70
+ if (surface?.selection?.type !== selection?.type || surface?.selection?.id !== selection?.id) surface?.acceptSelection(selection);
71
+ }, [selectedActor, selectedElement, surface]);
72
+ const addressee = view && selectedActor && <><AddresseeControl
73
+ actors={actors}
137
74
  onSelect={selectActor}
138
- runId={runId}
139
75
  selected={selectedActor}
140
- shown={shown}
141
76
  technical={inspect}
142
77
  view={view}
143
- />;
144
- const chatModeSwitch = selectedElement && toolbarContainer && createPortal(<ChatModeSwitch
145
- narrow={narrow}
146
- onChange={(chat) => save({ chat })}
147
- selected={stored.chat}
148
- sideWidth={settings.sideWidth}
149
- />, toolbarContainer);
150
- const chat = primarySelected || !view || !selectedActor
151
- ? renderChat({ chatElementClassName: chatSurfaceClass, chatScrollerRef: (element) => { scrollerRef.current = element; }, notice, toolbarLeft: addressee })
152
- : <ActorRunPanelChat actor={selectedActor} cardSections={cardSections} navigation={navigation} notice={notice} onNavigate={navigate} session={session} toolbarLeft={addressee} view={view} />;
153
-
154
- return <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-app" data-run-panel="run" ref={rootRef}>
155
- {chatModeSwitch}
156
- {layout !== "full" && elements.length > 1 && <nav aria-label="Mini-apps of the run" className={chipRowClass}>
157
- {elements.map(({ definition }) => <ElementChip
158
- centered={centered.has(definition.id)}
159
- definition={definition}
78
+ />{inspect && <Button aria-label={`Inspect @${selectedActor.handle}`} onClick={() => navigation.openTab(INSPECTION_TAB_ID, { type: "actor", id: selectedActor.id })} size="icon-sm" title="Inspect actor" variant="ghost"><SearchIcon /></Button>}</>;
79
+
80
+ return <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-app" data-run-panel="run">
81
+ {elements.length > 0 && <nav aria-label="Mini-apps of the run" className="flex flex-none flex-wrap items-center gap-1.5 border-b border-border bg-shell px-2 py-1.5" role={host.kind === "browser" ? "tablist" : undefined}>
82
+ {host.kind === "browser" && <button aria-selected={!selectedElement} className={chipClass} onClick={() => save({ element: null })} role="tab" type="button">Chat</button>}
83
+ {elements.map(({ definition }) => <button
84
+ aria-selected={host.kind === "browser" ? definition.id === selectedElement?.definition.id : undefined}
85
+ className={chipClass}
160
86
  key={definition.id}
161
- onSelect={() => centered.has(definition.id)
162
- ? host.openInCenter(runId, definition.id, definition.title ?? definition.id)
163
- : save({ element: definition.id })}
164
- selected={definition.id === selectedElement?.definition.id}
165
- session={session}
166
- view={view}
167
- />)}
168
- </nav>}
169
- <div className={cn("relative min-h-0 min-w-0 flex-1", layout === "side" ? "grid" : "flex flex-col")} data-chat-layout={layout} style={layout === "side" ? { gridTemplateColumns: `minmax(0,1fr) auto ${chatWidth}px` } : undefined}>
170
- {layout !== "full" && selectedElement && <RunPanelStage centered={centered.has(selectedElement.definition.id)} element={selectedElement} navigation={navigation} peek={layout === "floating" ? geometry.minimum : 0} runId={runId} session={session} />}
171
- {layout === "side" && <Sash
172
- onChange={(next) => setChatWidth(clampChatWidth(next, availableWidth()))}
173
- onCommit={(next) => save({ chatWidth: clampChatWidth(next, availableWidth()) })}
174
- value={chatWidth}
175
- />}
176
- {layout === "floating" && <div
177
- aria-hidden
178
- className="absolute inset-0 z-[15] bg-backdrop opacity-0 transition-opacity duration-300 pointer-events-none data-expanded:pointer-events-auto data-expanded:opacity-100 motion-reduce:transition-none"
179
- data-expanded={sheet.expanded || undefined}
180
- onClick={sheet.close}
181
- />}
182
- <section
183
- aria-label="Chat"
184
- className={cn(chatLayoutClass[layout], layout === "floating" && "[&_[data-chat=composer]]:[--qsl-panel-height:var(--sheet-available-height)] [&:not([data-expanded])_[data-slot=card-sections]]:hidden [&[data-sheet-resizing]_[data-slot=card-sections]]:hidden")}
185
- data-expanded={layout === "floating" && sheet.expanded ? true : undefined}
186
- data-sheet-resizing={resize.height !== undefined || undefined}
187
- data-view={primarySelected ? "chat" : "actor-chat"}
188
- onBlurCapture={layout === "floating" ? () => sheet.closeLater(SHEET_BLUR_DELAY) : undefined}
189
- onFocusCapture={layout === "floating" ? (event) => { if (event.target !== grabRef.current) sheet.open(); } : undefined}
190
- onPointerEnter={layout === "floating" ? (event) => { if (event.pointerType === "mouse") sheet.openLater(); } : undefined}
191
- onPointerLeave={layout === "floating" ? () => sheet.closeLater(settings.closeDelay) : undefined}
192
- ref={sheetRef}
193
- style={layout === "floating" ? { height: resize.height ?? (sheet.expanded ? expandedHeight : geometry.minimum), transition: resize.height !== undefined ? "none" : undefined, "--sheet-available-height": `${geometry.available}px` } as CSSProperties : undefined}
87
+ onClick={() => {
88
+ if (host.kind === "vscode") host.openApp(runId, definition.id, definition.title ?? definition.id);
89
+ else {
90
+ setVisited((current) => new Set([...current, definition.id]));
91
+ save({ element: definition.id });
92
+ }
93
+ }}
94
+ role={host.kind === "browser" ? "tab" : undefined}
95
+ type="button"
194
96
  >
195
- {layout === "floating" && <button
196
- aria-expanded={sheet.expanded}
197
- aria-label="Height of the expanded chat"
198
- aria-orientation="horizontal"
199
- aria-valuemin={geometry.minimum}
200
- aria-valuemax={geometry.maximum}
201
- aria-valuenow={Math.round(resize.height ?? expandedHeight)}
202
- className="flex h-3 w-full flex-none cursor-row-resize touch-none items-center justify-center outline-none select-none [&:hover>span]:bg-foreground [&:focus-visible>span]:bg-foreground [&:focus-visible>span]:ring-2 [&:focus-visible>span]:ring-ring/60"
203
- data-run-panel="sheet-grip"
204
- onClick={resize.onClick}
205
- onKeyDown={resize.onKeyDown}
206
- onPointerDown={resize.onPointerDown}
207
- onPointerMove={resize.onPointerMove}
208
- onPointerUp={resize.onPointerUp}
209
- onPointerCancel={resize.onPointerCancel}
210
- onLostPointerCapture={resize.onPointerCancel}
211
- ref={grabRef}
212
- role="separator"
213
- title={`Drag: set the expanded height. Click: ${sheet.expanded ? "lower chat" : "raise chat"}.`}
214
- type="button"
215
- ><span aria-hidden className="h-1 w-9 rounded-full bg-border-strong transition-colors" /></button>}
216
- {status && <button
217
- className={cn("flex h-6 w-full flex-none cursor-pointer items-center gap-2 px-[var(--qsl-chat-horizontal-padding,24px)] text-left text-[0.7rem] transition-opacity duration-200 in-data-expanded:pointer-events-none in-data-expanded:opacity-0 motion-reduce:transition-none", status.kind === "waiting" ? "text-warning" : "text-muted-foreground")}
218
- onClick={sheet.toggle}
219
- ref={statusRef}
220
- title={status.text}
221
- type="button"
222
- >
223
- {status.kind === "working" && <Spinner aria-hidden className="size-3 flex-none" />}
224
- <span className="truncate">{status.text}</span>
225
- </button>}
226
- {chat}
227
- </section>
228
- </div>
229
- </div>;
230
- }
231
-
232
- /** The three views of the run panel in the header; "Chat on right" stays disabled while the run panel is too narrow. */
233
- function ChatModeSwitch({ narrow, onChange, selected, sideWidth }: {
234
- narrow: boolean;
235
- onChange: (mode: RunPanelChatMode) => void;
236
- selected: RunPanelChatMode;
237
- sideWidth: number;
238
- }) {
239
- return <div aria-label="Run panel view" className="flex flex-none items-center gap-0.5 self-center rounded-lg border border-border bg-card p-0.5" role="group">
240
- {chatModes.map(({ Icon, label, mode, title }) => {
241
- const blocked = mode === "side" && narrow;
242
- return <Button
243
- aria-label={label}
244
- aria-pressed={selected === mode}
245
- className={chatModeButtonClass}
246
- disabled={blocked}
247
- key={mode}
248
- onClick={() => onChange(mode)}
249
- size="icon-sm"
250
- title={blocked ? `Chat stays at the bottom while the panel is narrower than ${sideWidth} pixels` : title}
251
- variant="ghost"
252
- ><Icon /></Button>;
97
+ <LayoutGridIcon aria-hidden className="size-3" />
98
+ <span className="truncate">{definition.title ?? definition.id}</span>
99
+ {host.kind === "vscode" && <ArrowUpRightIcon aria-hidden className="size-3" />}
100
+ </button>)}
101
+ </nav>}
102
+ <section aria-label="Chat" className={viewClass} data-view={primarySelected ? "chat" : "actor-chat"} hidden={selectedElement !== undefined} inert={selectedElement !== undefined} style={selectedElement ? { display: "none" } : undefined}>
103
+ <div className={viewClass} hidden={!primarySelected} inert={!primarySelected} style={primarySelected ? undefined : { display: "none" }}>
104
+ {renderChat({ chatElementClassName: chatSurfaceClass, notice, toolbarLeft: addressee })}
105
+ </div>
106
+ {view && actors.filter((actor) => actor.id !== primaryId && (visitedActors.has(actor.id) || actor.id === selectedActor?.id)).map((actor) => {
107
+ const selected = actor.id === selectedActor?.id;
108
+ return <div className={viewClass} hidden={!selected} inert={!selected} key={actor.id} style={selected ? undefined : { display: "none" }}>
109
+ <ActorRunPanelChat actor={actor} cardSections={cardSections} navigation={navigation} notice={notice} onNavigate={navigate} session={session} toolbarLeft={addressee} view={view} />
110
+ </div>;
111
+ })}
112
+ </section>
113
+ {host.kind === "browser" && elements.filter(({ definition }) => visited.has(definition.id) || definition.id === selectedElement?.definition.id).map((element) => {
114
+ const selected = element === selectedElement;
115
+ return <section aria-label={`Mini-app ${element.definition.title ?? element.definition.id}`} className={viewClass} hidden={!selected} inert={!selected} key={element.definition.id} role="tabpanel" style={selected ? undefined : { display: "none" }}>
116
+ <RunAppView app={element} navigation={navigation} session={session} />
117
+ </section>;
253
118
  })}
254
119
  </div>;
255
120
  }
256
121
 
257
- /** The sheet: mouse, focus or the grip raise it; Escape, the stage or the grip lower it. An open pop-out keeps it up. */
258
- function useSheet(active: boolean, sheetRef: RefObject<HTMLElement | null>, grabRef: RefObject<HTMLButtonElement | null>, blocked: boolean, openDelay: number) {
259
- const [expanded, setExpanded] = useState(false);
260
- const [held, setHeld] = useState(false);
261
- const guard = useRef({ held, blocked });
262
- const resizing = useRef(false);
263
- const timer = useRef<number>(undefined);
264
- useEffect(() => { guard.current = { held, blocked }; }, [blocked, held]);
265
- const cancel = useCallback(() => window.clearTimeout(timer.current), []);
266
- const open = useCallback(() => { if (!resizing.current) { cancel(); setExpanded(true); } }, [cancel]);
267
- const openLater = useCallback(() => {
268
- if (resizing.current) return;
269
- cancel();
270
- timer.current = window.setTimeout(() => { if (!resizing.current) setExpanded(true); }, openDelay);
271
- }, [cancel, openDelay]);
272
- const closeLater = useCallback((delay: number) => {
273
- if (resizing.current) return;
274
- cancel();
275
- timer.current = window.setTimeout(() => {
276
- if (resizing.current || guard.current.held || guard.current.blocked
277
- || (document.activeElement !== grabRef.current && sheetRef.current?.contains(document.activeElement))) return;
278
- setExpanded(false);
279
- }, delay);
280
- }, [cancel, grabRef, sheetRef]);
281
- const close = useCallback(() => {
282
- cancel();
283
- setHeld(false);
284
- setExpanded(false);
285
- if (document.activeElement instanceof HTMLElement && sheetRef.current?.contains(document.activeElement)) document.activeElement.blur();
286
- }, [cancel, sheetRef]);
287
- const toggle = useCallback(() => {
288
- if (expanded) { close(); return; }
289
- setHeld(true);
290
- open();
291
- }, [close, expanded, open]);
292
- const pauseResize = useCallback(() => { cancel(); resizing.current = true; }, [cancel]);
293
- const finishResize = useCallback((previous?: { expanded: boolean; held: boolean }) => {
294
- cancel();
295
- resizing.current = false;
296
- setExpanded(previous?.expanded ?? false);
297
- setHeld(previous?.held ?? false);
298
- }, [cancel]);
299
- useEffect(() => { if (!active) close(); }, [active, close]);
300
- useEffect(() => cancel, [cancel]);
301
- useEffect(() => {
302
- if (!active || !expanded) return;
303
- const onKeyDown = (event: globalThis.KeyboardEvent) => { if (event.key === "Escape" && !resizing.current && !guard.current.blocked) close(); };
304
- window.addEventListener("keydown", onKeyDown);
305
- return () => window.removeEventListener("keydown", onKeyDown);
306
- }, [active, close, expanded]);
307
- return { expanded, held, open, openLater, closeLater, close, toggle, pauseResize, finishResize };
308
- }
309
-
310
- /** The height of the collapsed sheet: grip, status line and input including open actions above it, measured on the input of the chat frame. */
311
- function useSheetPeek(active: boolean, sheetRef: RefObject<HTMLElement | null>, grabRef: RefObject<HTMLButtonElement | null>, statusRef: RefObject<HTMLButtonElement | null>, actorId: string | undefined) {
312
- const [geometry, setGeometry] = useState({ minimum: 0, maximum: 0, available: 0, scale: 1 });
313
- useLayoutEffect(() => {
314
- if (!active) return;
315
- const sheet = sheetRef.current;
316
- const grab = grabRef.current;
317
- const status = statusRef.current;
318
- const composer = sheet?.querySelector<HTMLElement>('[data-chat="composer"]');
319
- const container = sheet?.parentElement;
320
- if (!sheet || !grab || !composer || !container) throw new Error("The panel sheet cannot find the grip, input or frame.");
321
- const measure = () => {
322
- const style = getComputedStyle(sheet);
323
- const width = sheet.getBoundingClientRect().width;
324
- if (width === 0) return;
325
- const scale = width / Number.parseFloat(style.width);
326
- const minimum = Math.ceil((grab.getBoundingClientRect().height + (status?.getBoundingClientRect().height ?? 0)
327
- + composer.getBoundingClientRect().height) / scale + Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth));
328
- const available = Math.floor(container.getBoundingClientRect().height / scale * 0.9);
329
- const maximum = Math.max(minimum, available);
330
- setGeometry((previous) => previous.minimum === minimum && previous.maximum === maximum && previous.available === available && previous.scale === scale ? previous : { minimum, maximum, available, scale });
331
- };
332
- const observer = new ResizeObserver(measure);
333
- observer.observe(composer);
334
- observer.observe(grab);
335
- observer.observe(container);
336
- if (status) observer.observe(status);
337
- measure();
338
- return () => observer.disconnect();
339
- }, [active, actorId, grabRef, sheetRef, statusRef]);
340
- return geometry;
341
- }
342
-
343
- function useSheetResize(active: boolean, sheetRef: RefObject<HTMLElement | null>, grabRef: RefObject<HTMLButtonElement | null>, sheet: ReturnType<typeof useSheet>,
344
- geometry: { minimum: number; maximum: number; scale: number }, expandedHeight: number, onCommit: (height: number) => void) {
345
- const [height, setHeight] = useState<number>();
346
- const drag = useRef<{ pointerId: number; startY: number; startHeight: number; height: number; moved: boolean; expanded: boolean; held: boolean }>(undefined);
347
- const suppressClick = useRef(false);
348
- const finish = (commit: boolean) => {
349
- const previous = drag.current;
350
- if (!previous) return;
351
- drag.current = undefined;
352
- suppressClick.current = previous.moved || !commit;
353
- const grip = grabRef.current;
354
- if (grip?.hasPointerCapture(previous.pointerId)) grip.releasePointerCapture(previous.pointerId);
355
- if (commit && previous.moved) onCommit(previous.height);
356
- sheet.finishResize(commit && previous.moved ? { expanded: true, held: previous.held } : previous);
357
- setHeight(undefined);
358
- };
359
- const finishResize = sheet.finishResize;
360
- useEffect(() => {
361
- if (active) return;
362
- drag.current = undefined;
363
- setHeight(undefined);
364
- finishResize();
365
- }, [active, finishResize]);
366
- const clamp = (value: number) => Math.max(geometry.minimum, Math.min(value, geometry.maximum));
367
- return {
368
- height,
369
- onClick: (event: MouseEvent<HTMLButtonElement>) => {
370
- const suppressed = suppressClick.current && event.detail > 0;
371
- suppressClick.current = false;
372
- if (suppressed) return;
373
- sheet.toggle();
374
- },
375
- onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {
376
- if (event.button !== 0 || drag.current || !sheetRef.current) return;
377
- event.preventDefault();
378
- const startHeight = sheetRef.current.getBoundingClientRect().height / geometry.scale;
379
- drag.current = { pointerId: event.pointerId, startY: event.clientY, startHeight, height: startHeight, moved: false, expanded: sheet.expanded, held: sheet.held };
380
- suppressClick.current = false;
381
- sheet.pauseResize();
382
- setHeight(startHeight);
383
- event.currentTarget.setPointerCapture(event.pointerId);
384
- event.currentTarget.focus({ preventScroll: true });
385
- },
386
- onPointerMove: (event: PointerEvent<HTMLButtonElement>) => {
387
- const current = drag.current;
388
- if (!current || current.pointerId !== event.pointerId) return;
389
- const distance = (current.startY - event.clientY) / geometry.scale;
390
- if (!current.moved && Math.abs(distance) < 3) return;
391
- current.moved = true;
392
- current.height = clamp(current.startHeight + distance);
393
- setHeight(current.height);
394
- },
395
- onPointerUp: (event: PointerEvent<HTMLButtonElement>) => { if (drag.current?.pointerId === event.pointerId) finish(true); },
396
- onPointerCancel: (event: PointerEvent<HTMLButtonElement>) => { if (drag.current?.pointerId === event.pointerId) finish(false); },
397
- onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => {
398
- if (event.key === "Escape" && drag.current) {
399
- event.preventDefault();
400
- event.stopPropagation();
401
- finish(false);
402
- return;
403
- }
404
- const next = event.key === "ArrowUp" ? expandedHeight + 24 : event.key === "ArrowDown" ? expandedHeight - 24
405
- : event.key === "Home" ? geometry.minimum : event.key === "End" ? geometry.maximum : undefined;
406
- if (next === undefined || drag.current) return;
407
- event.preventDefault();
408
- sheet.finishResize({ expanded: true, held: sheet.held });
409
- onCommit(clamp(next));
410
- },
411
- };
412
- }
413
-
414
- function ElementChip({ centered, definition, onSelect, selected, session, view }: {
415
- centered: boolean;
416
- definition: SurfaceElementDefinition;
417
- onSelect: () => void;
418
- selected: boolean;
419
- session: SessionContext;
420
- view: RunView | undefined;
421
- }) {
422
- const programs = useProgramSlot();
423
- const title = definition.title ?? definition.id;
424
- const attention = elementNeedsAttention(view, definition, programs?.needsAnswer(session, definition.id) ?? false);
425
- return <button aria-pressed={selected && !centered} className={chipClass} data-tone="app" onClick={onSelect} title={centered ? `${title} is in the center. Click brings the window to the front.` : `Show mini-app ${title}`} type="button">
426
- <span className="grid size-5 flex-none place-items-center rounded-full bg-glass-app text-foreground [&>svg]:size-3"><LayoutGridIcon /></span>
427
- <span className="truncate">{title}</span>
428
- {attention && <span aria-label="Answer expected" className="grid size-4 flex-none place-items-center rounded-full bg-warning text-[0.62rem] font-bold text-background" role="img">!</span>}
429
- {centered && <ArrowUpRightIcon aria-hidden className="size-3 flex-none text-muted-foreground" />}
430
- </button>;
431
- }
432
-
433
- function RunPanelStage({ centered, element, navigation, peek, runId, session }: {
434
- centered: boolean;
435
- element: RunPanelElement;
436
- navigation: SessionNavigation;
437
- peek: number;
438
- runId: string;
439
- session: SessionContext;
440
- }) {
441
- const host = useRunPanelHost();
442
- const programs = useProgramSlot();
443
- const { Element, definition } = element;
444
- const title = definition.title ?? definition.id;
445
- return <section aria-label={`Mini-app ${title}`} className="group/stage relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-app" style={peek > 0 ? { paddingBottom: peek } : undefined}>
446
- <div className="absolute top-1.5 right-2 z-[5] flex gap-0.5 rounded-lg border border-border bg-card p-0.5 opacity-0 transition-opacity group-hover/stage:opacity-100 group-focus-within/stage:opacity-100 motion-reduce:transition-none">
447
- {host.kind === "vscode"
448
- ? <Button aria-label={`Move ${title} to the center`} onClick={() => host.openInCenter(runId, definition.id, title)} size="icon-sm" title="Move to the center" variant="ghost"><ArrowUpRightIcon /></Button>
449
- : programs && <Button aria-label={`Open ${title} in full view`} onClick={() => programs.openFullscreen(definition.id)} size="icon-sm" title="Full view" variant="ghost"><Maximize2Icon /></Button>}
450
- </div>
451
- <div className="relative flex min-h-0 flex-1 flex-col" data-run-panel="stage">
452
- {centered
453
- ? <div className="m-auto grid gap-3 p-4 text-center text-[0.76rem] text-muted-foreground" role="status">
454
- <span>{title} is in the center.</span>
455
- <Button onClick={() => host.returnToRunPanel(runId, definition.id)} size="sm" variant="outline">Back to panel</Button>
456
- </div>
457
- : <Element definition={definition} navigation={navigation} session={session} />}
458
- </div>
459
- </section>;
460
- }
461
-
462
- /** The vertical grip between stage and the chat next to it; dragging left makes the chat wider. */
463
- function Sash({ onChange, onCommit, value }: { onChange: (next: number) => void; onCommit: (next: number) => void; value: number }) {
464
- const drag = useRef<{ startX: number; startWidth: number }>(undefined);
465
- const finish = (event: PointerEvent<HTMLDivElement>) => {
466
- if (!drag.current) return;
467
- drag.current = undefined;
468
- event.currentTarget.releasePointerCapture(event.pointerId);
469
- onCommit(value);
470
- };
471
- return <div
472
- aria-label="Chat width"
473
- aria-orientation="vertical"
474
- aria-valuenow={value}
475
- className="flex w-2.5 flex-none cursor-col-resize touch-none items-center justify-center border-r border-l border-border bg-shell select-none hover:bg-accent focus-visible:outline-2 focus-visible:outline-ring/60 focus-visible:-outline-offset-2"
476
- onKeyDown={(event) => {
477
- const step = event.key === "ArrowRight" ? -24 : event.key === "ArrowLeft" ? 24 : 0;
478
- if (step === 0) return;
479
- event.preventDefault();
480
- onChange(value + step);
481
- onCommit(value + step);
482
- }}
483
- onPointerCancel={finish}
484
- onPointerDown={(event) => {
485
- drag.current = { startX: event.clientX, startWidth: value };
486
- event.currentTarget.setPointerCapture(event.pointerId);
487
- }}
488
- onPointerMove={(event) => { if (drag.current) onChange(drag.current.startWidth + drag.current.startX - event.clientX); }}
489
- onPointerUp={finish}
490
- role="separator"
491
- tabIndex={0}
492
- >
493
- <span aria-hidden className="h-10 w-1 rounded-full bg-border-strong" />
494
- </div>;
495
- }
496
-
497
- function ActorRunPanelChat({ actor, cardSections, navigation, notice, onNavigate, session, toolbarLeft, view }: {
122
+ export function ActorRunPanelChat({ actor, cardSections, navigation, notice, onNavigate, session, toolbarLeft, view }: {
498
123
  actor: RunActor;
499
124
  cardSections: readonly CardSectionContribution[];
500
125
  navigation: SessionNavigation;
501
- notice: ReactNode;
126
+ notice?: ReactNode;
502
127
  onNavigate: (selection: FlowSelection) => void;
503
128
  session: SessionContext;
504
- toolbarLeft: ReactNode;
129
+ toolbarLeft?: ReactNode;
505
130
  view: RunView;
506
131
  }) {
507
132
  const running = actor.lifecycle?.kind === "running";
@@ -1,5 +1,3 @@
1
- import type { SurfaceElementDefinition } from "@ragents/web/PluginRegistry";
2
- import { actorVisibleInHeader, type ActorHeaderMode } from "../actor-header-settings";
3
1
  import { chatPrimaryId, type RunActor, type RunView } from "@ragents/web/run-view";
4
2
 
5
3
  /** The actors of the chip row: coordinator first, then in creation order; without the inspect right only LLM agents. */
@@ -10,30 +8,5 @@ export const runPanelActors = (view: RunView, inspect: boolean): RunActor[] => {
10
8
  return [...primary, ...candidates.filter((actor) => actor.id !== primaryId)];
11
9
  };
12
10
 
13
- export interface RunPanelActorFilter {
14
- mode: ActorHeaderMode;
15
- onStage: (actor: RunActor) => boolean;
16
- primaryId: string | null | undefined;
17
- selectedId: string | undefined;
18
- }
19
-
20
- /** Visible according to the surface's actor display; the coordinator and the selected actor always stay as a chip. */
21
- export const partitionRunPanelActors = (actors: readonly RunActor[], filter: RunPanelActorFilter): { shown: RunActor[]; hidden: RunActor[] } => {
22
- const shown: RunActor[] = [];
23
- const hidden: RunActor[] = [];
24
- for (const actor of actors) {
25
- const pinned = actor.id === filter.primaryId || actor.id === filter.selectedId;
26
- (pinned || actorVisibleInHeader(actor, filter.mode, filter.onStage(actor)) ? shown : hidden).push(actor);
27
- }
28
- return { shown, hidden };
29
- };
30
-
31
11
  export const pendingInputCount = (view: RunView, actorId: string): number =>
32
12
  view.inputs.filter((input) => input.actorId === actorId && input.lifecycle.kind === "pending").length;
33
-
34
- export const pendingActionCount = (view: RunView, askedBy: string): number =>
35
- view.actions.filter((action) => action.status === "pending" && action.askedBy === askedBy).length;
36
-
37
- /** A chip carries an exclamation mark when a host confirmation or an action of the actor waits for input. */
38
- export const elementNeedsAttention = (view: RunView | undefined, definition: SurfaceElementDefinition, confirmationPending: boolean): boolean =>
39
- confirmationPending || (view !== undefined && definition.anchorActorId !== undefined && pendingActionCount(view, definition.anchorActorId) > 0);