@schlenkr/ragents 0.1.18 → 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 (209) 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/agent/src/core/agent-settings.ts +2 -2
  109. package/packages/ai/dist/types.d.ts +2 -1
  110. package/packages/ai/src/api/ai-sdk.ts +15 -2
  111. package/packages/ai/src/types.ts +2 -1
  112. package/packages/ragents/src/runtime/journal.ts +4 -0
  113. package/plugins/ragents.actor-programs/prompt.hbs +4 -5
  114. package/plugins/ragents.actor-programs/server/access-projections.ts +1 -1
  115. package/plugins/ragents.actor-programs/server/index.ts +0 -2
  116. package/plugins/ragents.actor-programs/server/runtime.ts +2 -10
  117. package/plugins/ragents.actor-programs/web/ActorViewFrame.tsx +5 -20
  118. package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -79
  119. package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +0 -9
  120. package/plugins/ragents.actor-programs/web/api.ts +0 -20
  121. package/plugins/ragents.actor-programs/web/context.ts +0 -3
  122. package/plugins/ragents.actor-programs/web/index.tsx +0 -9
  123. package/plugins/ragents.orchestration/ragents-plugin.json +1 -2
  124. package/plugins/ragents.orchestration/server/index.ts +1 -11
  125. package/plugins/ragents.orchestration/web/ActorChat.tsx +1 -1
  126. package/plugins/ragents.orchestration/web/ActorChatControls.tsx +1 -1
  127. package/plugins/ragents.orchestration/web/FlowInspector.tsx +2 -4
  128. package/plugins/ragents.orchestration/web/InspectionPanel.tsx +21 -0
  129. package/plugins/ragents.orchestration/web/index.tsx +13 -9
  130. package/plugins/ragents.orchestration/web/program-slot.ts +0 -3
  131. package/plugins/ragents.orchestration/web/run-panel/AddresseeControl.tsx +15 -36
  132. package/plugins/ragents.orchestration/web/run-panel/RunPanel.tsx +76 -451
  133. package/plugins/ragents.orchestration/web/run-panel/run-panel-actors.ts +0 -27
  134. package/plugins/ragents.orchestration/web/run-panel/run-panel-state.ts +7 -40
  135. package/plugins/ragents.overseer/server/coordinator-prompt.ts +0 -1
  136. package/plugins/ragents.reference/examples.ts +1 -2
  137. package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
  138. package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -2
  139. package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +6 -7
  140. package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -3
  141. package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +3 -13
  142. package/plugins/ragents.reference/run-scripts/conversation-circle/tests/helpers.ts +1 -1
  143. package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +1 -6
  144. package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +1 -15
  145. package/plugins/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
  146. package/plugins/ragents.reference/run-scripts/moderated-round/tests/helpers.ts +1 -1
  147. package/plugins/ragents.reference/run-scripts/quick-note/RUN.md +1 -1
  148. package/plugins/ragents.reference/run-scripts/quick-note/src/server.ts +1 -2
  149. package/plugins/ragents.reference/run-scripts/quick-note/tests/program.test.ts +0 -2
  150. package/plugins/ragents.reference/run-scripts/run-roster/RUN.md +2 -2
  151. package/plugins/ragents.reference/run-scripts/run-roster/src/server.ts +1 -2
  152. package/plugins/ragents.reference/run-scripts/run-roster/tests/program.test.ts +0 -2
  153. package/plugins/ragents.reference/run-scripts/shared-actor-list/src/server.ts +1 -1
  154. package/plugins/ragents.reference/run-scripts/shared-actor-list/tests/helpers.ts +1 -1
  155. package/plugins/ragents.reference/run-scripts/word-game/src/contract.ts +1 -1
  156. package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +0 -1
  157. package/plugins/ragents.reference/run-scripts/word-game/tests/program.test.ts +1 -2
  158. package/plugins/ragents.reference/skills/80-actor-text-analysis/SKILL.md +1 -1
  159. package/plugins/ragents.reference/skills/90-actor-notes/SKILL.md +1 -1
  160. package/plugins/ragents.reference/walkthroughs.ts +13 -14
  161. package/apps/web/dist/assets/highlighted-body-KPVGNVTW-BwD4uYEe.js +0 -1
  162. package/apps/web/dist/assets/main-KBrc5Q77.js +0 -11
  163. package/apps/web/dist/assets/mermaid-HWGCJPDP-CSCkaArr.js +0 -608
  164. package/apps/web/dist/assets/runPanel-BjG5NBKm.js +0 -26
  165. package/apps/web/src/App.tsx +0 -318
  166. package/apps/web/src/SessionList.tsx +0 -157
  167. package/apps/web/src/SurfaceShortcuts.tsx +0 -60
  168. package/apps/web/src/WorkspacePanel.tsx +0 -309
  169. package/apps/web/src/run-panel.tsx +0 -52
  170. package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js +0 -11
  171. package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js.map +0 -7
  172. package/bundles/ragents.orchestration/server/exports/server/contract.js +0 -9
  173. package/bundles/ragents.orchestration/server/exports/server/contract.js.map +0 -7
  174. package/bundles/ragents.orchestration/surface.hbs +0 -39
  175. package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js +0 -2
  176. package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js.map +0 -7
  177. package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js +0 -2
  178. package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js.map +0 -7
  179. package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js +0 -2
  180. package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js.map +0 -7
  181. package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js +0 -2
  182. package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js.map +0 -7
  183. package/bundles/ragents.orchestration/web/exports/web/surface-entities.js +0 -2
  184. package/bundles/ragents.orchestration/web/exports/web/surface-entities.js.map +0 -7
  185. package/bundles/ragents.orchestration/web/exports/web/tile-docking.js +0 -2
  186. package/bundles/ragents.orchestration/web/exports/web/tile-docking.js.map +0 -7
  187. package/plugins/ragents.actor-programs/web/ExpandIcon.tsx +0 -5
  188. package/plugins/ragents.actor-programs/web/FullscreenHost.tsx +0 -31
  189. package/plugins/ragents.orchestration/contract.ts +0 -51
  190. package/plugins/ragents.orchestration/server/contract.ts +0 -14
  191. package/plugins/ragents.orchestration/server/surface-tool.ts +0 -198
  192. package/plugins/ragents.orchestration/surface.hbs +0 -39
  193. package/plugins/ragents.orchestration/tiled-layout.ts +0 -55
  194. package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +0 -29
  195. package/plugins/ragents.orchestration/web/ActorShortcut.tsx +0 -74
  196. package/plugins/ragents.orchestration/web/ActorSurfaceControls.tsx +0 -130
  197. package/plugins/ragents.orchestration/web/ActorTile.tsx +0 -23
  198. package/plugins/ragents.orchestration/web/Center.tsx +0 -133
  199. package/plugins/ragents.orchestration/web/SurfaceTiles.tsx +0 -47
  200. package/plugins/ragents.orchestration/web/TiledSurface.tsx +0 -280
  201. package/plugins/ragents.orchestration/web/actor-header-settings.ts +0 -41
  202. package/plugins/ragents.orchestration/web/run-panel/RunPanelSettings.tsx +0 -50
  203. package/plugins/ragents.orchestration/web/run-panel/run-panel-settings.ts +0 -47
  204. package/plugins/ragents.orchestration/web/run-panel/sheet-status.ts +0 -30
  205. package/plugins/ragents.orchestration/web/surface-entities.ts +0 -21
  206. package/plugins/ragents.orchestration/web/surface-view-settings.ts +0 -42
  207. package/plugins/ragents.orchestration/web/tile-docking.ts +0 -105
  208. package/plugins/ragents.orchestration/web/tiled-view-settings.ts +0 -74
  209. /package/apps/web/dist/assets/{mermaid-HWGCJPDP-BPn4aV2r.css → main-BPn4aV2r.css} +0 -0
@@ -99,7 +99,10 @@ export function RunPreparationChat({ initialPrompt, entry, registry, sessionRef
99
99
  return <section aria-label="Discuss task" className={cn(composerPlacement, messages.length === 0 && emptyComposerPlacement)} data-preparation={messages.length === 0 ? "empty" : "active"}>
100
100
  <ChatPanel composer={<div>
101
101
  <ChatInputToolbar {...attachments} disabled={operation === "starting"} sendDisabled={busy}
102
- initialValue={initialPrompt} onDraftChange={setDraft} onAttachmentsChange={setHasAttachments} handleRef={composer}
102
+ initialValue={initialPrompt} onDraftChange={setDraft} onAttachmentsChange={(active) => {
103
+ setHasAttachments(active);
104
+ attachments.onAttachmentsChange(active);
105
+ }} handleRef={composer}
103
106
  rows={3} maxRows={8} running={operation === "preparing"} onStop={() => active.current?.abort()} onSend={discuss}
104
107
  texts={{ placeholder: "Additions or questions about the task ...", send: "Discuss task", stop: "Stop discussion" }}
105
108
  toolbarLeft={<ChatViewSwitches settings={chatView} />}
@@ -1,3 +1,4 @@
1
+ import * as webRunApps from "@ragents/web/run-apps";
1
2
  import * as engineSrcDomainEvents from "@ragents/engine/src/domain/events";
2
3
  import * as engineSrcHttpContracts from "@ragents/engine/src/http/contracts";
3
4
  import * as engineSrcRpcContract from "@ragents/engine/src/rpc/contract";
@@ -70,6 +71,7 @@ export const hostModules: Readonly<Record<string, object>> = {
70
71
  "@ragents/web/product/ProductModelSettings": webProductProductModelSettings,
71
72
  "@ragents/web/product/start-options": webProductStartOptions,
72
73
  "@ragents/web/rpc": webRpc,
74
+ "@ragents/web/run-apps": webRunApps,
73
75
  "@ragents/web/run-panel/host": webRunPanelHost,
74
76
  "@ragents/web/run-panel/input-bridge": webRunPanelInputBridge,
75
77
  "@ragents/web/run-view": webRunView,
@@ -4,9 +4,14 @@ import "./diff-view.css";
4
4
  import { StrictMode } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { Button } from "./ui";
7
- import { App } from "./App";
8
7
  import { AccessGate, AccessScreen } from "./AccessContext";
9
8
  import { QuasselHost } from "./chat/QuasselHost";
9
+ import { installAccessToken } from "./access-token";
10
+ import { installRunPanelInputBridge } from "./run-panel/input-bridge";
11
+ import { RunPanelApp, HostLogin } from "./run-panel/RunPanelApp";
12
+ import { parseRunPanelLocation } from "./run-panel/run-panel-location";
13
+ import { RunPanelHostProvider, createRunPanelHost } from "./run-panel/host";
14
+ import { PageOpenerProvider } from "./page-opener";
10
15
  import { initializeTheme } from "./theme";
11
16
  import { installHostModules } from "./host-modules";
12
17
 
@@ -16,16 +21,31 @@ if (!root) throw new Error("Root element is missing");
16
21
 
17
22
  const reactRoot = createRoot(root);
18
23
  try {
24
+ const location = parseRunPanelLocation(window.location.search);
25
+ if (location.access !== undefined) installAccessToken(location.access);
26
+ const host = createRunPanelHost(location.host, window);
27
+ if (location.host === "vscode") {
28
+ const disposeInput = installRunPanelInputBridge(window);
29
+ import.meta.hot?.dispose(disposeInput);
30
+ }
19
31
  const theme = initializeTheme(window);
32
+ if (location.theme !== undefined) theme.setPreference(location.theme);
33
+ host.onCommand((message) => { if (message.type === "theme") theme.setPreference(message.theme); });
20
34
  import.meta.hot?.dispose(() => theme.dispose());
21
35
  reactRoot.render(
22
36
  <StrictMode>
23
- <QuasselHost><AccessGate><App /></AccessGate></QuasselHost>
37
+ <QuasselHost>
38
+ <RunPanelHostProvider value={host}>
39
+ <PageOpenerProvider value={location.host === "vscode" ? { open: host.openPage } : undefined}>
40
+ <AccessGate Login={location.host === "vscode" ? HostLogin : undefined}><RunPanelApp location={location} /></AccessGate>
41
+ </PageOpenerProvider>
42
+ </RunPanelHostProvider>
43
+ </QuasselHost>
24
44
  </StrictMode>,
25
45
  );
26
46
  } catch (cause) {
27
47
  reactRoot.render(<AccessScreen>
28
- <h1 className="my-3 text-[1.5rem]">The view could not be loaded</h1>
48
+ <h1 className="my-3 text-[1.5rem]">The RAgents panel could not be loaded</h1>
29
49
  <p className="mb-6 leading-normal" role="alert">{cause instanceof Error ? cause.message : String(cause)}</p>
30
50
  <Button onClick={() => window.location.reload()} variant="outline">Reload</Button>
31
51
  </AccessScreen>);
@@ -1,5 +1,5 @@
1
1
  import { cn } from "cn";
2
- import { CircleAlertIcon } from "lucide-react";
2
+ import { CircleAlertIcon, CircleIcon } from "lucide-react";
3
3
  import type { ReactNode } from "react";
4
4
  import { Checkbox, longTime, RunStateIcon, shortTime } from "../ui";
5
5
  import type { ConnectionRun, ConnectionView } from "./contract";
@@ -23,7 +23,8 @@ export function RunList({ label, showConnection, selecting = false, children }:
23
23
  }
24
24
 
25
25
  /** One line per run, the same on Start and on Runs: state and title on the left, time and server on the right in the list columns. */
26
- export function RunLine({ connection, run, showConnection, selecting, selected, onOpen, onToggle }: {
26
+ export function RunLine({ connection, run, showConnection, selecting, selected, onOpen, onToggle, details }: {
27
+ details?: ReactNode;
27
28
  connection: ConnectionView;
28
29
  run: ConnectionRun;
29
30
  showConnection: boolean;
@@ -41,10 +42,12 @@ export function RunLine({ connection, run, showConnection, selecting, selected,
41
42
  <RunStateIcon open={run.pendingActions} state={run.state} />
42
43
  <span className="flex min-w-0 items-center gap-1.5">
43
44
  <span className="min-w-0 truncate text-[0.8rem] font-medium">{run.title}</span>
45
+ {run.notice && !run.locked && <span className="text-primary" title={run.notice === "unseen" ? "Not viewed yet" : "New activity"}><CircleIcon aria-label={run.notice === "unseen" ? "Not viewed yet" : "New activity"} className="size-2 fill-current" /></span>}
44
46
  {problem !== undefined && <span className="flex flex-none text-destructive" title={problem}><CircleAlertIcon aria-hidden className="size-3.5" /><span className="sr-only">{problem}</span></span>}
45
47
  </span>
46
48
  <span className="w-[42px] text-right font-mono text-[0.62rem] text-muted-foreground" data-cell="time" title={longTime(run.updatedAt)}>{time}</span>
47
49
  {showConnection && <span className="max-w-[92px] truncate text-[0.64rem] font-semibold text-muted-foreground" data-cell="connection" title={connection.name}>{connection.name}</span>}
48
50
  </button>
51
+ {details && <div className="col-span-full flex flex-wrap gap-x-3 px-2 pb-1 text-[0.65rem] text-muted-foreground">{details}</div>}
49
52
  </li>;
50
53
  }
@@ -13,7 +13,7 @@ const matches = (connection: ConnectionView, run: ConnectionRun, query: string):
13
13
  `${run.title} ${connection.name}`.toLocaleLowerCase("en-US").includes(query);
14
14
 
15
15
  /** All runs of all servers as one list: search, hide ended, the server filter from the chip on Start, and a multi-selection for deleting. */
16
- export function RunsPage({ state, send }: PanelPageProps) {
16
+ export function RunsPage({ state, send, runDetails }: PanelPageProps) {
17
17
  const [query, setQuery] = useState("");
18
18
  const [onlyConnection, setOnlyConnection] = useState(state.runsConnection);
19
19
  const [hideEnded, setHideEnded] = useState(false);
@@ -38,7 +38,7 @@ export function RunsPage({ state, send }: PanelPageProps) {
38
38
  return next;
39
39
  });
40
40
  const remove = () => {
41
- for (const connection of state.connections) {
41
+ for (const connection of state.connections.filter((connection) => connection.canDelete !== false)) {
42
42
  const runIds = connection.runs.filter((run) => selected.has(keyOf(connection.name, run.id))).map((run) => run.id);
43
43
  if (runIds.length > 0) send({ action: "deleteRuns", name: connection.name, runIds });
44
44
  }
@@ -57,16 +57,16 @@ export function RunsPage({ state, send }: PanelPageProps) {
57
57
  {onlyConnection}<XIcon data-icon="inline-end" />
58
58
  </Toggle>}
59
59
  <Toggle onPressedChange={setHideEnded} pressed={hideEnded} size="sm" variant="outline">Hide ended</Toggle>
60
- <Toggle onPressedChange={(next) => (next ? setSelecting(true) : leaveSelection())} pressed={selecting} size="sm" variant="outline">
60
+ {state.connections.some((connection) => connection.canDelete !== false) && <Toggle onPressedChange={(next) => (next ? setSelecting(true) : leaveSelection())} pressed={selecting} size="sm" variant="outline">
61
61
  <CheckSquareIcon data-icon="inline-start" />Select
62
- </Toggle>
62
+ </Toggle>}
63
63
  </div>
64
64
  {shown.length === 0
65
65
  ? <p className="text-[0.75rem] text-muted-foreground" role="status">{all.length === 0 ? "No runs yet." : "No matching run."}</p>
66
66
  : <RunList label="Runs" selecting={selecting} showConnection={marked}>
67
- {shown.map(({ connection, run }) => <RunLine connection={connection} key={keyOf(connection.name, run.id)}
67
+ {shown.map(({ connection, run }) => <RunLine details={runDetails?.(run.id, connection.name)} connection={connection} key={keyOf(connection.name, run.id)}
68
68
  onOpen={() => send({ action: "openRun", name: connection.name, runId: run.id })}
69
- onToggle={() => toggle(keyOf(connection.name, run.id))} run={run} selected={selected.has(keyOf(connection.name, run.id))} selecting={selecting} showConnection={marked} />)}
69
+ onToggle={() => toggle(keyOf(connection.name, run.id))} run={run} selected={selected.has(keyOf(connection.name, run.id))} selecting={selecting && connection.canDelete !== false} showConnection={marked} />)}
70
70
  </RunList>}
71
71
  {selecting && <div className="sticky bottom-0 flex flex-wrap items-center gap-2 border-t border-border-soft bg-background py-2">
72
72
  <span className="flex-1 text-[0.72rem] text-muted-foreground" role="status">{selected.size} selected</span>
@@ -85,7 +85,8 @@ function ConnectionChip({ connection, send, onLogin }: {
85
85
  const busy = busyState(connection);
86
86
  const detail = stateDetail(connection);
87
87
  useEffect(() => { if (!busy && detail === undefined) setFailure(undefined); }, [busy, detail]);
88
- const canCreate = connection.state.kind === "connected" && connection.canCreate;
88
+ const canCreate = connection.state.kind === "connected" && connection.canCreate
89
+ && (connection.canCreateFree !== false || defaultEntryOf(connection) !== undefined);
89
90
  const route = routeLabel(connection);
90
91
  const starter = defaultEntryOf(connection);
91
92
  const plusLabel = starter ? `New run from ${starter.title} on ${connection.name}` : `New chat on ${connection.name}`;
@@ -115,19 +116,19 @@ function ConnectionOffers({ connection, marked, send }: { connection: Connection
115
116
  return <div className="grid grid-cols-1 gap-1.5">
116
117
  {marked && <h3 className="flex items-center gap-1.5 pt-1 text-[0.78rem] font-semibold"><ConnectionStateIcon state={connectionState(connection)} />{connection.name}</h3>}
117
118
  <StartTiles defaultEntry={connection.defaultEntry} entries={connection.entries} label={marked ? `Templates on ${connection.name}` : "Templates"}
118
- onNewChat={() => send({ action: "newRun", name: connection.name })} onStart={(entryId) => send({ action: "newRun", name: connection.name, entryId })} />
119
+ onNewChat={connection.canCreateFree === false ? undefined : () => send({ action: "newRun", name: connection.name })} onStart={(entryId) => send({ action: "newRun", name: connection.name, entryId })} />
119
120
  </div>;
120
121
  }
121
122
 
122
123
  /** The start page: the servers as a block, below them the latest runs and, per reachable server, its templates, the default template or New chat first. */
123
- export function StartPage({ state, send }: PanelPageProps) {
124
+ export function StartPage({ state, send, runDetails }: PanelPageProps) {
124
125
  const [login, setLogin] = useState<string>();
125
126
  const connections = state.connections;
126
127
  const marked = connections.length > 1;
127
128
  const runs = connections.flatMap((connection) => connection.runs.map((run) => ({ connection, run })))
128
129
  .sort((left, right) => right.run.updatedAt - left.run.updatedAt);
129
130
  const reachable = connections.filter((connection) => connection.state.kind === "connected" && connection.canCreate);
130
- const tiles = reachable.reduce((count, connection) => count + startTileCount(connection.entries, connection.defaultEntry, true), 0);
131
+ const tiles = reachable.reduce((count, connection) => count + startTileCount(connection.entries, connection.defaultEntry, connection.canCreateFree !== false), 0);
131
132
  const loginConnection = connections.find((connection) => connection.name === login);
132
133
  return <div className="@container/panel grid grid-cols-1 gap-4">
133
134
  {state.problem && <p className="text-[0.8rem] leading-normal text-destructive [overflow-wrap:anywhere]" role="alert">{state.problem}</p>}
@@ -153,7 +154,7 @@ export function StartPage({ state, send }: PanelPageProps) {
153
154
  {runs.length === 0
154
155
  ? <p className="text-[0.75rem] text-muted-foreground">No runs yet.</p>
155
156
  : <RunList label="Recent" showConnection={marked}>
156
- {runs.slice(0, RECENT_RUNS).map(({ connection, run }) => <RunLine connection={connection} key={`${connection.name}:${run.id}`} onOpen={() => send({ action: "openRun", name: connection.name, runId: run.id })} run={run} showConnection={marked} />)}
157
+ {runs.slice(0, RECENT_RUNS).map(({ connection, run }) => <RunLine details={runDetails?.(run.id, connection.name)} connection={connection} key={`${connection.name}:${run.id}`} onOpen={() => send({ action: "openRun", name: connection.name, runId: run.id })} run={run} showConnection={marked} />)}
157
158
  </RunList>}
158
159
  </section>
159
160
  {reachable.length > 0 && <section className="grid grid-cols-1 gap-1.5">
@@ -26,6 +26,7 @@ export interface ConnectionRun {
26
26
  readonly title: string;
27
27
  readonly state: "running" | "waiting" | "idle" | "ended";
28
28
  readonly pendingActions: number;
29
+ readonly notice?: "unseen" | "updated";
29
30
  readonly updatedAt: number;
30
31
  /** The extension could not read the run view; the row names the reason. */
31
32
  readonly problem?: string;
@@ -62,6 +63,8 @@ export interface ConnectionView {
62
63
  readonly defaultEntry?: string;
63
64
  /** New runs are allowed; without the right only the list remains. */
64
65
  readonly canCreate: boolean;
66
+ readonly canDelete?: boolean;
67
+ readonly canCreateFree?: boolean;
65
68
  /** The signed-in user, if the server manages users. */
66
69
  readonly user?: string;
67
70
  /** The most recently tried user name; the sign-in form takes it over. */
@@ -1,6 +1,8 @@
1
+ import type { ReactNode } from "react";
1
2
  import type { PanelAction, PanelState } from "./contract";
2
3
 
3
4
  export interface PanelPageProps {
5
+ readonly runDetails?: (runId: string, connection: string) => ReactNode;
4
6
  readonly state: PanelState;
5
7
  readonly send: (action: PanelAction) => void;
6
8
  }
@@ -0,0 +1,34 @@
1
+ import { createElement } from "react";
2
+ import type { SessionContext, SessionNavigation, SurfaceElementContribution, SurfaceElementDefinition } from "./PluginRegistry";
3
+
4
+ export interface RunApp {
5
+ readonly runId: string;
6
+ readonly definition: SurfaceElementDefinition;
7
+ readonly Element: SurfaceElementContribution["Element"];
8
+ }
9
+
10
+ export interface RunAppNavigation {
11
+ openApp(runId: string, elementId: string, title: string): void;
12
+ }
13
+
14
+ export function runApps(session: SessionContext, contributions: readonly SurfaceElementContribution[]): readonly RunApp[] {
15
+ const ids = new Set<string>();
16
+ return contributions.flatMap(({ Element, select }) => select(session).flatMap((definition) => {
17
+ if (ids.has(definition.id)) throw new Error(`Duplicate mini-app ID: ${definition.id}`);
18
+ ids.add(definition.id);
19
+ return definition.visible === false ? [] : [{ runId: session.session.id, definition, Element }];
20
+ }));
21
+ }
22
+
23
+ export function selectedRunApp(apps: readonly RunApp[], elementId: string | null | undefined): RunApp | undefined {
24
+ return apps.find(({ definition }) => definition.id === elementId);
25
+ }
26
+
27
+ export function RunAppView({ app, navigation, session }: {
28
+ app: RunApp;
29
+ navigation: SessionNavigation;
30
+ session: SessionContext;
31
+ }) {
32
+ if (app.runId !== session.session.id) throw new Error("The mini-app belongs to another run.");
33
+ return createElement(app.Element, { definition: app.definition, navigation, session });
34
+ }
@@ -1,32 +1,5 @@
1
1
  import type { SessionInfo } from "./api";
2
2
 
3
- export interface RunDayGroup {
4
- date: string;
5
- label: string;
6
- sessions: SessionInfo[];
7
- }
8
-
9
- const dayKey = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
10
-
11
- export function groupRunsByActivity(sessions: readonly SessionInfo[], now = new Date()): RunDayGroup[] {
12
- const today = dayKey(now);
13
- const yesterday = new Date(now);
14
- yesterday.setDate(yesterday.getDate() - 1);
15
- const groups = new Map<string, RunDayGroup>();
16
- for (const session of [...sessions].sort((left, right) => right.updatedAt - left.updatedAt || left.id.localeCompare(right.id))) {
17
- const date = new Date(session.updatedAt);
18
- const key = dayKey(date);
19
- const group = groups.get(key) ?? {
20
- date: key,
21
- label: key === today ? "Today" : key === dayKey(yesterday) ? "Yesterday" : date.toLocaleDateString(undefined, { weekday: "long", day: "numeric", month: "long", ...(date.getFullYear() === now.getFullYear() ? {} : { year: "numeric" }) }),
22
- sessions: [],
23
- };
24
- group.sessions.push(session);
25
- groups.set(key, group);
26
- }
27
- return [...groups.values()];
28
- }
29
-
30
3
  export function runActivityNotice(session: SessionInfo, seenRevision: number | undefined): "unseen" | "updated" | undefined {
31
4
  if (session.revision === undefined) return undefined;
32
5
  if (seenRevision === undefined) return "unseen";
@@ -0,0 +1,41 @@
1
+ import { useCallback, useEffect, useState, type ComponentType } from "react";
2
+ import { useAccess } from "../AccessContext";
3
+ import { Overview } from "../Overview";
4
+ import type { OverviewPanelContext, PluginRegistry } from "../PluginRegistry";
5
+ import type { ChatUserLocation } from "../../../server/src/chat-context";
6
+ import { Button } from "../ui";
7
+
8
+ export function PanelContributions({ registry, userLocation, placement = "toolbar" }: { registry: PluginRegistry; userLocation: ChatUserLocation; placement?: "toolbar" | "idle" }) {
9
+ const access = useAccess();
10
+ const [open, setOpen] = useState<string>();
11
+ const [overviewActivated, setOverviewActivated] = useState(false);
12
+ const [busy, setBusy] = useState<ReadonlySet<string>>(() => new Set());
13
+ const reportBusy = useCallback((id: string, value: boolean) => setBusy((current) => {
14
+ if (current.has(id) === value) return current;
15
+ const next = new Set(current);
16
+ if (value) next.add(id);
17
+ else next.delete(id);
18
+ return next;
19
+ }), []);
20
+ const panels = registry.overviewPanels.filter((panel) => panel.readRight === undefined || access.can(panel.readRight));
21
+ const overview = placement === "idle" ? [] : panels.filter((panel) => (panel.placement ?? "overview") === "overview");
22
+ const openOverview = () => { setOverviewActivated(true); setOpen("overview"); };
23
+ return <>
24
+ {overview.length > 0 && <Button onClick={() => open === "overview" ? setOpen(undefined) : openOverview()} variant="ghost">{busy.size ? "Overview (working)" : "Overview"}</Button>}
25
+ {panels.filter((panel) => panel.placement === placement).slice(0, placement === "idle" ? 1 : undefined).map(({ id, Panel, placement }) =>
26
+ <Contribution id={id} key={id} onBusy={reportBusy} onClose={() => setOpen(undefined)} onOpen={() => setOpen(id)}
27
+ open={placement === "idle" || open === id} Panel={Panel} registry={registry} userLocation={userLocation} />)}
28
+ {overviewActivated && overview.length > 0 && <Overview onBusy={reportBusy} onClose={() => setOpen(undefined)} onOpen={openOverview}
29
+ open={open === "overview"} panels={overview} registry={registry} />}
30
+ </>;
31
+ }
32
+
33
+ function Contribution({ id, onBusy, Panel, ...context }: Omit<OverviewPanelContext, "onBusy"> & {
34
+ id: string;
35
+ onBusy: (id: string, busy: boolean) => void;
36
+ Panel: ComponentType<OverviewPanelContext>;
37
+ }) {
38
+ const report = useCallback((busy: boolean) => onBusy(id, busy), [id, onBusy]);
39
+ useEffect(() => () => onBusy(id, false), [id, onBusy]);
40
+ return <Panel {...context} onBusy={report} />;
41
+ }