@stigmer/react 0.0.92 → 0.0.94

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 (328) hide show
  1. package/README.md +57 -12
  2. package/agent/AgentDetailView.js +7 -7
  3. package/agent/AgentDetailView.js.map +1 -1
  4. package/agent/AgentPicker.js +3 -3
  5. package/agent/AgentPicker.js.map +1 -1
  6. package/api-key/ApiKeyCreatedAlert.js +1 -1
  7. package/api-key/ApiKeyCreatedAlert.js.map +1 -1
  8. package/api-key/ApiKeyListPanel.js +4 -4
  9. package/api-key/ApiKeyListPanel.js.map +1 -1
  10. package/api-key/CreateApiKeyForm.js +1 -1
  11. package/api-key/CreateApiKeyForm.js.map +1 -1
  12. package/attachment/AttachmentChipList.js +2 -2
  13. package/attachment/AttachmentChipList.js.map +1 -1
  14. package/color-mode.d.ts +48 -0
  15. package/color-mode.d.ts.map +1 -0
  16. package/color-mode.js +53 -0
  17. package/color-mode.js.map +1 -0
  18. package/composer/ComposerToolbar.d.ts +6 -2
  19. package/composer/ComposerToolbar.d.ts.map +1 -1
  20. package/composer/ComposerToolbar.js +5 -3
  21. package/composer/ComposerToolbar.js.map +1 -1
  22. package/composer/ConfigureMenu.js +3 -3
  23. package/composer/ConfigureMenu.js.map +1 -1
  24. package/composer/ContextChip.js +1 -1
  25. package/composer/ContextChip.js.map +1 -1
  26. package/composer/ContextPopover.js +1 -1
  27. package/composer/ContextPopover.js.map +1 -1
  28. package/composer/SessionComposer.d.ts +24 -3
  29. package/composer/SessionComposer.d.ts.map +1 -1
  30. package/composer/SessionComposer.js +5 -4
  31. package/composer/SessionComposer.js.map +1 -1
  32. package/environment/CreateEnvironmentForm.js +1 -1
  33. package/environment/CreateEnvironmentForm.js.map +1 -1
  34. package/environment/EnvVarForm.js +2 -2
  35. package/environment/EnvVarForm.js.map +1 -1
  36. package/environment/EnvironmentListPanel.js +2 -2
  37. package/environment/EnvironmentListPanel.js.map +1 -1
  38. package/environment/EnvironmentVariableEditor.js +6 -6
  39. package/environment/EnvironmentVariableEditor.js.map +1 -1
  40. package/error/ErrorMessage.js +1 -1
  41. package/error/ErrorMessage.js.map +1 -1
  42. package/execution/ApprovalCard.js +4 -4
  43. package/execution/ApprovalCard.js.map +1 -1
  44. package/execution/ArtifactCard.js +1 -1
  45. package/execution/ArtifactCard.js.map +1 -1
  46. package/execution/ArtifactPreviewModal.js +4 -4
  47. package/execution/ArtifactPreviewModal.js.map +1 -1
  48. package/execution/FollowUpInput.js +1 -1
  49. package/execution/FollowUpInput.js.map +1 -1
  50. package/execution/McpToolDetail.js +4 -4
  51. package/execution/McpToolDetail.js.map +1 -1
  52. package/execution/MessageEntry.js +1 -1
  53. package/execution/MessageEntry.js.map +1 -1
  54. package/execution/MessageThread.js +1 -1
  55. package/execution/MessageThread.js.map +1 -1
  56. package/execution/SessionVariablesInput.js +4 -4
  57. package/execution/SessionVariablesInput.js.map +1 -1
  58. package/execution/SubAgentSection.js +2 -2
  59. package/execution/SubAgentSection.js.map +1 -1
  60. package/execution/ToolCallDetail.js +2 -2
  61. package/execution/ToolCallDetail.js.map +1 -1
  62. package/execution/ToolCallGroup.js +1 -1
  63. package/execution/ToolCallGroup.js.map +1 -1
  64. package/execution/ToolCallItem.js +2 -2
  65. package/execution/ToolCallItem.js.map +1 -1
  66. package/execution/WriteBackCard.js +3 -3
  67. package/execution/WriteBackCard.js.map +1 -1
  68. package/execution/tool-rendering-primitives.js +3 -3
  69. package/execution/tool-rendering-primitives.js.map +1 -1
  70. package/github/GitHubRepoPicker.js +5 -5
  71. package/github/GitHubRepoPicker.js.map +1 -1
  72. package/iam-policy/GrantAccessForm.js +1 -1
  73. package/iam-policy/GrantAccessForm.js.map +1 -1
  74. package/iam-policy/OrgMembersPanel.js +5 -5
  75. package/iam-policy/OrgMembersPanel.js.map +1 -1
  76. package/identity-provider/CreateIdentityProviderForm.js +3 -3
  77. package/identity-provider/CreateIdentityProviderForm.js.map +1 -1
  78. package/identity-provider/IdentityProviderDetailPanel.js +5 -5
  79. package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
  80. package/identity-provider/IdentityProviderListPanel.js +3 -3
  81. package/identity-provider/IdentityProviderListPanel.js.map +1 -1
  82. package/identity-provider/IdentityProviderWizard.js +7 -7
  83. package/identity-provider/IdentityProviderWizard.js.map +1 -1
  84. package/identity-provider/ProviderPicker.js +2 -2
  85. package/identity-provider/ProviderPicker.js.map +1 -1
  86. package/index.d.ts +8 -4
  87. package/index.d.ts.map +1 -1
  88. package/index.js +7 -3
  89. package/index.js.map +1 -1
  90. package/internal/CloudFeatureNotice.js +1 -1
  91. package/internal/CloudFeatureNotice.js.map +1 -1
  92. package/internal/Tabs.js +1 -1
  93. package/internal/Tabs.js.map +1 -1
  94. package/internal/markdown-components.js +2 -2
  95. package/internal/markdown-components.js.map +1 -1
  96. package/invitation/InvitationCreatedAlert.js +1 -1
  97. package/invitation/InvitationCreatedAlert.js.map +1 -1
  98. package/invitation/InvitationManager.js +5 -5
  99. package/invitation/InvitationManager.js.map +1 -1
  100. package/invitation/InvitationRedemption.js +4 -4
  101. package/invitation/InvitationRedemption.js.map +1 -1
  102. package/library/ResourceCountCard.js +1 -1
  103. package/library/ResourceCountCard.js.map +1 -1
  104. package/library/ResourceListView.js +5 -5
  105. package/library/ResourceListView.js.map +1 -1
  106. package/mcp-server/McpServerConfigPanel.js +5 -5
  107. package/mcp-server/McpServerConfigPanel.js.map +1 -1
  108. package/mcp-server/McpServerConnectDialog.js +4 -4
  109. package/mcp-server/McpServerConnectDialog.js.map +1 -1
  110. package/mcp-server/McpServerDetailView.js +14 -14
  111. package/mcp-server/McpServerDetailView.js.map +1 -1
  112. package/mcp-server/McpServerPicker.js +4 -4
  113. package/mcp-server/McpServerPicker.js.map +1 -1
  114. package/mcp-server/McpToolSelector.js +3 -3
  115. package/mcp-server/McpToolSelector.js.map +1 -1
  116. package/mcp-server/OAuthAppForm.js +1 -1
  117. package/mcp-server/OAuthAppForm.js.map +1 -1
  118. package/models/ModelSelector.js +1 -1
  119. package/models/ModelSelector.js.map +1 -1
  120. package/oauth-app/CreateOAuthAppForm.js +1 -1
  121. package/oauth-app/CreateOAuthAppForm.js.map +1 -1
  122. package/oauth-app/OAuthAppDetailPanel.js +3 -3
  123. package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
  124. package/oauth-app/OAuthAppListPanel.js +2 -2
  125. package/oauth-app/OAuthAppListPanel.js.map +1 -1
  126. package/organization/CreateOrganizationForm.js +1 -1
  127. package/organization/CreateOrganizationForm.js.map +1 -1
  128. package/organization/OrgProfilePanel.js +3 -3
  129. package/organization/OrgProfilePanel.js.map +1 -1
  130. package/package.json +4 -4
  131. package/platform-client/CreatePlatformClientForm.js +2 -2
  132. package/platform-client/CreatePlatformClientForm.js.map +1 -1
  133. package/platform-client/PlatformClientDetailPanel.js +4 -4
  134. package/platform-client/PlatformClientDetailPanel.js.map +1 -1
  135. package/platform-client/PlatformClientListPanel.js +3 -3
  136. package/platform-client/PlatformClientListPanel.js.map +1 -1
  137. package/platform-client/PlatformClientSecretAlert.js +2 -2
  138. package/platform-client/PlatformClientSecretAlert.js.map +1 -1
  139. package/provider.d.ts +39 -2
  140. package/provider.d.ts.map +1 -1
  141. package/provider.js +11 -3
  142. package/provider.js.map +1 -1
  143. package/runner/RunnerListPanel.d.ts +65 -0
  144. package/runner/RunnerListPanel.d.ts.map +1 -0
  145. package/runner/RunnerListPanel.js +237 -0
  146. package/runner/RunnerListPanel.js.map +1 -0
  147. package/runner/RunnerPicker.d.ts +54 -0
  148. package/runner/RunnerPicker.d.ts.map +1 -0
  149. package/runner/RunnerPicker.js +133 -0
  150. package/runner/RunnerPicker.js.map +1 -0
  151. package/runner/__tests__/useDeleteRunner.test.d.ts +2 -0
  152. package/runner/__tests__/useDeleteRunner.test.d.ts.map +1 -0
  153. package/runner/__tests__/useDeleteRunner.test.js +108 -0
  154. package/runner/__tests__/useDeleteRunner.test.js.map +1 -0
  155. package/runner/__tests__/useLaunchLocalRunner.test.d.ts +2 -0
  156. package/runner/__tests__/useLaunchLocalRunner.test.d.ts.map +1 -0
  157. package/runner/__tests__/useLaunchLocalRunner.test.js +143 -0
  158. package/runner/__tests__/useLaunchLocalRunner.test.js.map +1 -0
  159. package/runner/__tests__/useStopRunner.test.d.ts +2 -0
  160. package/runner/__tests__/useStopRunner.test.d.ts.map +1 -0
  161. package/runner/__tests__/useStopRunner.test.js +114 -0
  162. package/runner/__tests__/useStopRunner.test.js.map +1 -0
  163. package/runner/index.d.ts +14 -0
  164. package/runner/index.d.ts.map +1 -0
  165. package/runner/index.js +8 -0
  166. package/runner/index.js.map +1 -0
  167. package/runner/phase.d.ts +30 -0
  168. package/runner/phase.d.ts.map +1 -0
  169. package/runner/phase.js +58 -0
  170. package/runner/phase.js.map +1 -0
  171. package/runner/useDeleteRunner.d.ts +36 -0
  172. package/runner/useDeleteRunner.d.ts.map +1 -0
  173. package/runner/useDeleteRunner.js +42 -0
  174. package/runner/useDeleteRunner.js.map +1 -0
  175. package/runner/useLaunchLocalRunner.d.ts +84 -0
  176. package/runner/useLaunchLocalRunner.d.ts.map +1 -0
  177. package/runner/useLaunchLocalRunner.js +75 -0
  178. package/runner/useLaunchLocalRunner.js.map +1 -0
  179. package/runner/useRunnerList.d.ts +49 -0
  180. package/runner/useRunnerList.d.ts.map +1 -0
  181. package/runner/useRunnerList.js +70 -0
  182. package/runner/useRunnerList.js.map +1 -0
  183. package/runner/useStopRunner.d.ts +53 -0
  184. package/runner/useStopRunner.d.ts.map +1 -0
  185. package/runner/useStopRunner.js +50 -0
  186. package/runner/useStopRunner.js.map +1 -0
  187. package/session/draft.d.ts +53 -0
  188. package/session/draft.d.ts.map +1 -0
  189. package/session/draft.js +45 -0
  190. package/session/draft.js.map +1 -0
  191. package/session/index.d.ts +10 -0
  192. package/session/index.d.ts.map +1 -1
  193. package/session/index.js +5 -0
  194. package/session/index.js.map +1 -1
  195. package/session/useCreateSession.d.ts +8 -0
  196. package/session/useCreateSession.d.ts.map +1 -1
  197. package/session/useCreateSession.js +1 -0
  198. package/session/useCreateSession.js.map +1 -1
  199. package/session/useEditSessionPrep.d.ts +26 -0
  200. package/session/useEditSessionPrep.d.ts.map +1 -0
  201. package/session/useEditSessionPrep.js +83 -0
  202. package/session/useEditSessionPrep.js.map +1 -0
  203. package/session/useNewSessionFlow.d.ts +110 -0
  204. package/session/useNewSessionFlow.d.ts.map +1 -0
  205. package/session/useNewSessionFlow.js +184 -0
  206. package/session/useNewSessionFlow.js.map +1 -0
  207. package/session/usePersistedModel.d.ts +18 -0
  208. package/session/usePersistedModel.d.ts.map +1 -0
  209. package/session/usePersistedModel.js +31 -0
  210. package/session/usePersistedModel.js.map +1 -0
  211. package/session/useSessionPageFlow.d.ts +104 -0
  212. package/session/useSessionPageFlow.d.ts.map +1 -0
  213. package/session/useSessionPageFlow.js +172 -0
  214. package/session/useSessionPageFlow.js.map +1 -0
  215. package/skill/SkillDetailView.js +3 -3
  216. package/skill/SkillDetailView.js.map +1 -1
  217. package/skill/SkillPicker.js +3 -3
  218. package/skill/SkillPicker.js.map +1 -1
  219. package/src/agent/AgentDetailView.tsx +8 -8
  220. package/src/agent/AgentPicker.tsx +3 -3
  221. package/src/api-key/ApiKeyCreatedAlert.tsx +2 -2
  222. package/src/api-key/ApiKeyListPanel.tsx +6 -6
  223. package/src/api-key/CreateApiKeyForm.tsx +2 -2
  224. package/src/attachment/AttachmentChipList.tsx +3 -3
  225. package/src/color-mode.ts +75 -0
  226. package/src/composer/ComposerToolbar.tsx +29 -7
  227. package/src/composer/ConfigureMenu.tsx +6 -6
  228. package/src/composer/ContextChip.tsx +1 -1
  229. package/src/composer/ContextPopover.tsx +2 -2
  230. package/src/composer/SessionComposer.tsx +34 -5
  231. package/src/environment/CreateEnvironmentForm.tsx +3 -3
  232. package/src/environment/EnvVarForm.tsx +6 -6
  233. package/src/environment/EnvironmentListPanel.tsx +3 -3
  234. package/src/environment/EnvironmentVariableEditor.tsx +7 -7
  235. package/src/error/ErrorMessage.tsx +5 -5
  236. package/src/execution/ApprovalCard.tsx +5 -5
  237. package/src/execution/ArtifactCard.tsx +2 -2
  238. package/src/execution/ArtifactPreviewModal.tsx +4 -4
  239. package/src/execution/FollowUpInput.tsx +2 -2
  240. package/src/execution/McpToolDetail.tsx +4 -4
  241. package/src/execution/MessageEntry.tsx +1 -1
  242. package/src/execution/MessageThread.tsx +1 -1
  243. package/src/execution/SessionVariablesInput.tsx +7 -7
  244. package/src/execution/SubAgentSection.tsx +5 -5
  245. package/src/execution/ToolCallDetail.tsx +2 -2
  246. package/src/execution/ToolCallGroup.tsx +3 -3
  247. package/src/execution/ToolCallItem.tsx +4 -4
  248. package/src/execution/WriteBackCard.tsx +5 -5
  249. package/src/execution/tool-rendering-primitives.tsx +5 -5
  250. package/src/github/GitHubRepoPicker.tsx +5 -5
  251. package/src/iam-policy/GrantAccessForm.tsx +2 -2
  252. package/src/iam-policy/OrgMembersPanel.tsx +11 -11
  253. package/src/identity-provider/CreateIdentityProviderForm.tsx +4 -4
  254. package/src/identity-provider/IdentityProviderDetailPanel.tsx +7 -7
  255. package/src/identity-provider/IdentityProviderListPanel.tsx +7 -7
  256. package/src/identity-provider/IdentityProviderWizard.tsx +8 -8
  257. package/src/identity-provider/ProviderPicker.tsx +2 -2
  258. package/src/index.ts +46 -7
  259. package/src/internal/CloudFeatureNotice.tsx +1 -1
  260. package/src/internal/Tabs.tsx +1 -1
  261. package/src/internal/markdown-components.tsx +2 -2
  262. package/src/invitation/InvitationCreatedAlert.tsx +2 -2
  263. package/src/invitation/InvitationManager.tsx +9 -9
  264. package/src/invitation/InvitationRedemption.tsx +11 -11
  265. package/src/library/ResourceCountCard.tsx +1 -1
  266. package/src/library/ResourceListView.tsx +7 -7
  267. package/src/mcp-server/McpServerConfigPanel.tsx +7 -7
  268. package/src/mcp-server/McpServerConnectDialog.tsx +5 -5
  269. package/src/mcp-server/McpServerDetailView.tsx +19 -19
  270. package/src/mcp-server/McpServerPicker.tsx +6 -6
  271. package/src/mcp-server/McpToolSelector.tsx +4 -4
  272. package/src/mcp-server/OAuthAppForm.tsx +3 -3
  273. package/src/models/ModelSelector.tsx +1 -1
  274. package/src/oauth-app/CreateOAuthAppForm.tsx +3 -3
  275. package/src/oauth-app/OAuthAppDetailPanel.tsx +7 -7
  276. package/src/oauth-app/OAuthAppListPanel.tsx +3 -3
  277. package/src/organization/CreateOrganizationForm.tsx +3 -3
  278. package/src/organization/OrgProfilePanel.tsx +4 -5
  279. package/src/platform-client/CreatePlatformClientForm.tsx +6 -6
  280. package/src/platform-client/PlatformClientDetailPanel.tsx +19 -19
  281. package/src/platform-client/PlatformClientListPanel.tsx +7 -7
  282. package/src/platform-client/PlatformClientSecretAlert.tsx +2 -2
  283. package/src/provider.tsx +52 -2
  284. package/src/runner/RunnerListPanel.tsx +725 -0
  285. package/src/runner/RunnerPicker.tsx +319 -0
  286. package/src/runner/__tests__/useDeleteRunner.test.tsx +150 -0
  287. package/src/runner/__tests__/useLaunchLocalRunner.test.tsx +223 -0
  288. package/src/runner/__tests__/useStopRunner.test.tsx +154 -0
  289. package/src/runner/index.ts +34 -0
  290. package/src/runner/phase.ts +62 -0
  291. package/src/runner/useDeleteRunner.ts +67 -0
  292. package/src/runner/useLaunchLocalRunner.ts +139 -0
  293. package/src/runner/useRunnerList.ts +114 -0
  294. package/src/runner/useStopRunner.ts +92 -0
  295. package/src/session/draft.ts +82 -0
  296. package/src/session/index.ts +28 -0
  297. package/src/session/useCreateSession.ts +9 -0
  298. package/src/session/useEditSessionPrep.ts +111 -0
  299. package/src/session/useNewSessionFlow.ts +283 -0
  300. package/src/session/usePersistedModel.ts +41 -0
  301. package/src/session/useSessionPageFlow.ts +280 -0
  302. package/src/skill/SkillDetailView.tsx +5 -5
  303. package/src/skill/SkillPicker.tsx +3 -3
  304. package/src/styles.css +25 -1
  305. package/src/usage/OrgUsagePanel.tsx +4 -4
  306. package/src/workspace/WorkspaceEditor.tsx +78 -66
  307. package/src/workspace/index.ts +0 -8
  308. package/styles.css +1 -1
  309. package/usage/OrgUsagePanel.js +2 -2
  310. package/usage/OrgUsagePanel.js.map +1 -1
  311. package/workspace/WorkspaceEditor.d.ts +28 -3
  312. package/workspace/WorkspaceEditor.d.ts.map +1 -1
  313. package/workspace/WorkspaceEditor.js +24 -25
  314. package/workspace/WorkspaceEditor.js.map +1 -1
  315. package/workspace/index.d.ts +0 -4
  316. package/workspace/index.d.ts.map +1 -1
  317. package/workspace/index.js +0 -2
  318. package/workspace/index.js.map +1 -1
  319. package/src/workspace/FolderBrowser.tsx +0 -579
  320. package/src/workspace/useFolderListing.ts +0 -164
  321. package/workspace/FolderBrowser.d.ts +0 -37
  322. package/workspace/FolderBrowser.d.ts.map +0 -1
  323. package/workspace/FolderBrowser.js +0 -188
  324. package/workspace/FolderBrowser.js.map +0 -1
  325. package/workspace/useFolderListing.d.ts +0 -73
  326. package/workspace/useFolderListing.d.ts.map +0 -1
  327. package/workspace/useFolderListing.js +0 -110
  328. package/workspace/useFolderListing.js.map +0 -1
@@ -0,0 +1,725 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { cn } from "@stigmer/theme";
11
+ import { getUserMessage } from "@stigmer/sdk";
12
+ import { timestampDate } from "@bufbuild/protobuf/wkt";
13
+ import { RunnerPhase } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/enum_pb";
14
+ import type { Runner } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
15
+ import { useRunnerList } from "./useRunnerList";
16
+ import { useStopRunner } from "./useStopRunner";
17
+ import { useDeleteRunner } from "./useDeleteRunner";
18
+ import {
19
+ isActivePhase,
20
+ phaseLabel,
21
+ phaseDotColor,
22
+ PHASE_SORT_ORDER,
23
+ } from "./phase";
24
+
25
+ const SYSTEM_MANAGED_LABEL = "stigmer.ai/system-managed";
26
+
27
+ type ConfirmingState = {
28
+ readonly runnerId: string;
29
+ readonly action: "stop" | "delete";
30
+ } | null;
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Public API
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /** Props for {@link RunnerListPanel}. */
37
+ export interface RunnerListPanelProps {
38
+ /** Organization slug to scope the runner list. */
39
+ readonly org: string;
40
+ /**
41
+ * Include system-managed (ephemeral cloud) runners in the list.
42
+ *
43
+ * System-managed runners are auto-provisioned for cloud executions
44
+ * and labeled `stigmer.ai/system-managed: "true"`. Including them
45
+ * gives admins full visibility into all compute resources.
46
+ *
47
+ * @default true
48
+ */
49
+ readonly includeSystemManaged?: boolean;
50
+ /** Expose refetch so parent components can trigger a list refresh. */
51
+ readonly onRefetchRef?: (refetch: () => void) => void;
52
+ /**
53
+ * Notification callback fired after a runner is successfully stopped.
54
+ * Receives the updated runner resource with its new phase.
55
+ */
56
+ readonly onStopped?: (runner: Runner) => void;
57
+ /**
58
+ * Notification callback fired after a runner is successfully deleted.
59
+ * Receives the deleted runner resource for confirmation display.
60
+ */
61
+ readonly onDeleted?: (runner: Runner) => void;
62
+ /** Additional CSS class names for the root container. */
63
+ readonly className?: string;
64
+ }
65
+
66
+ /**
67
+ * Admin panel that displays all runners in an organization with
68
+ * lifecycle management actions.
69
+ *
70
+ * Each runner is rendered as a card row showing name, phase indicator,
71
+ * machine information, and operational metadata. Non-system-managed
72
+ * runners include an action menu for stop and delete operations with
73
+ * inline confirmation — no modals or portals.
74
+ *
75
+ * Rows are sorted by phase (active runners first) then alphabetically
76
+ * by name. System-managed runners display a "System" badge and have
77
+ * no action affordances.
78
+ *
79
+ * Designed for the Settings > Runners page but embeddable in any
80
+ * context that needs runner fleet management. Fetches data via
81
+ * {@link useRunnerList} and performs mutations via {@link useStopRunner}
82
+ * and {@link useDeleteRunner} — no Console-specific dependencies.
83
+ *
84
+ * All visual properties flow through `--stgm-*` design tokens.
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * <RunnerListPanel org="acme" />
89
+ *
90
+ * <RunnerListPanel
91
+ * org="acme"
92
+ * includeSystemManaged={false}
93
+ * onStopped={(runner) => toast(`${runner.metadata?.name} stopped`)}
94
+ * onDeleted={(runner) => toast(`${runner.metadata?.name} deleted`)}
95
+ * onRefetchRef={(refetch) => { refetchRef.current = refetch; }}
96
+ * />
97
+ * ```
98
+ */
99
+ export function RunnerListPanel({
100
+ org,
101
+ includeSystemManaged = true,
102
+ onRefetchRef,
103
+ onStopped,
104
+ onDeleted,
105
+ className,
106
+ }: RunnerListPanelProps) {
107
+ const { runners, isLoading, error, refetch } = useRunnerList(org, {
108
+ includeSystemManaged,
109
+ });
110
+ const [confirming, setConfirming] = useState<ConfirmingState>(null);
111
+
112
+ if (onRefetchRef) {
113
+ onRefetchRef(refetch);
114
+ }
115
+
116
+ const sorted = useMemo(
117
+ () => [...runners].sort(phaseThenName),
118
+ [runners],
119
+ );
120
+
121
+ const handleRequestConfirm = useCallback(
122
+ (runnerId: string, action: "stop" | "delete") => {
123
+ setConfirming({ runnerId, action });
124
+ },
125
+ [],
126
+ );
127
+
128
+ const handleCancelConfirm = useCallback(() => {
129
+ setConfirming(null);
130
+ }, []);
131
+
132
+ const handleActionComplete = useCallback(
133
+ (runner: Runner, action: "stop" | "delete") => {
134
+ setConfirming(null);
135
+ refetch();
136
+ if (action === "stop") onStopped?.(runner);
137
+ else onDeleted?.(runner);
138
+ },
139
+ [refetch, onStopped, onDeleted],
140
+ );
141
+
142
+ if (isLoading) {
143
+ return (
144
+ <div
145
+ className={cn("space-y-2", className)}
146
+ aria-busy="true"
147
+ aria-label="Loading runners"
148
+ >
149
+ {Array.from({ length: 3 }, (_, i) => (
150
+ <div
151
+ key={i}
152
+ className="bg-muted-subtle h-14 animate-pulse rounded-lg"
153
+ />
154
+ ))}
155
+ </div>
156
+ );
157
+ }
158
+
159
+ if (error) {
160
+ return (
161
+ <p className={cn("text-destructive text-xs", className)} role="alert">
162
+ {getUserMessage(error)}
163
+ </p>
164
+ );
165
+ }
166
+
167
+ if (sorted.length === 0) {
168
+ return (
169
+ <div
170
+ className={cn(
171
+ "flex flex-col items-center gap-2 py-8 text-center",
172
+ className,
173
+ )}
174
+ >
175
+ <RunnerIcon size={24} />
176
+ <p className="text-muted-foreground text-xs">
177
+ No runners registered.
178
+ </p>
179
+ <p className="text-muted-foreground-subtle max-w-xs text-[0.65rem]">
180
+ Start a runner with{" "}
181
+ <code className="bg-muted rounded px-1 py-0.5 font-mono text-[0.6rem]">
182
+ stigmer up
183
+ </code>{" "}
184
+ or launch one from your browser with the button above.
185
+ </p>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ return (
191
+ <div
192
+ className={cn("space-y-2", className)}
193
+ role="list"
194
+ aria-label="Runners"
195
+ >
196
+ {sorted.map((runner) => {
197
+ const id = runner.metadata!.id;
198
+ return (
199
+ <RunnerRow
200
+ key={id}
201
+ runner={runner}
202
+ confirmingAction={
203
+ confirming?.runnerId === id ? confirming.action : null
204
+ }
205
+ onRequestConfirm={(action) => handleRequestConfirm(id, action)}
206
+ onCancelConfirm={handleCancelConfirm}
207
+ onActionComplete={handleActionComplete}
208
+ />
209
+ );
210
+ })}
211
+ </div>
212
+ );
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // RunnerRow (internal)
217
+ // ---------------------------------------------------------------------------
218
+
219
+ function RunnerRow({
220
+ runner,
221
+ confirmingAction,
222
+ onRequestConfirm,
223
+ onCancelConfirm,
224
+ onActionComplete,
225
+ }: {
226
+ runner: Runner;
227
+ confirmingAction: "stop" | "delete" | null;
228
+ onRequestConfirm: (action: "stop" | "delete") => void;
229
+ onCancelConfirm: () => void;
230
+ onActionComplete: (runner: Runner, action: "stop" | "delete") => void;
231
+ }) {
232
+ const {
233
+ stop,
234
+ isStopping,
235
+ error: stopError,
236
+ clearError: clearStopError,
237
+ } = useStopRunner();
238
+ const {
239
+ deleteRunner,
240
+ isDeleting,
241
+ error: deleteError,
242
+ clearError: clearDeleteError,
243
+ } = useDeleteRunner();
244
+
245
+ const name = runner.metadata?.name ?? "Unnamed";
246
+ const id = runner.metadata?.id ?? "";
247
+ const phase = runner.status?.phase ?? RunnerPhase.UNSPECIFIED;
248
+ const active = isActivePhase(phase);
249
+ const systemManaged =
250
+ runner.metadata?.labels[SYSTEM_MANAGED_LABEL] === "true";
251
+ const hasActions = !systemManaged;
252
+ const canStop = active;
253
+
254
+ const info = runner.status?.connectionInfo;
255
+ const hostname = info?.hostname;
256
+ const osArch =
257
+ info?.os && info?.arch ? `${info.os}/${info.arch}` : undefined;
258
+ const version = info?.runnerVersion;
259
+ const executions = runner.status?.currentExecutions ?? 0;
260
+ const lastHeartbeat = runner.status?.lastHeartbeatAt;
261
+
262
+ useEffect(() => {
263
+ if (confirmingAction) {
264
+ clearStopError();
265
+ clearDeleteError();
266
+ }
267
+ }, [confirmingAction, clearStopError, clearDeleteError]);
268
+
269
+ const handleStop = useCallback(async () => {
270
+ try {
271
+ const updated = await stop({
272
+ runnerId: id,
273
+ reason: "stopped via web console",
274
+ });
275
+ onActionComplete(updated, "stop");
276
+ } catch {
277
+ // error state surfaced via useStopRunner hook
278
+ }
279
+ }, [id, stop, onActionComplete]);
280
+
281
+ const handleDelete = useCallback(async () => {
282
+ try {
283
+ const deleted = await deleteRunner(id);
284
+ onActionComplete(deleted, "delete");
285
+ } catch {
286
+ // error state surfaced via useDeleteRunner hook
287
+ }
288
+ }, [id, deleteRunner, onActionComplete]);
289
+
290
+ if (confirmingAction === "stop") {
291
+ return (
292
+ <ConfirmationRow
293
+ message={
294
+ <>
295
+ Stop <span className="font-medium">{name}</span>?
296
+ </>
297
+ }
298
+ description="Active executions on this runner will be interrupted."
299
+ confirmLabel="Stop runner"
300
+ isMutating={isStopping}
301
+ error={stopError}
302
+ onConfirm={handleStop}
303
+ onCancel={onCancelConfirm}
304
+ />
305
+ );
306
+ }
307
+
308
+ if (confirmingAction === "delete") {
309
+ return (
310
+ <ConfirmationRow
311
+ message={
312
+ <>
313
+ Delete <span className="font-medium">{name}</span>?
314
+ </>
315
+ }
316
+ description="This action is permanent. Sessions bound to this runner will fall back to auto-provisioning."
317
+ confirmLabel="Delete permanently"
318
+ isMutating={isDeleting}
319
+ error={deleteError}
320
+ onConfirm={handleDelete}
321
+ onCancel={onCancelConfirm}
322
+ />
323
+ );
324
+ }
325
+
326
+ return (
327
+ <div
328
+ role="listitem"
329
+ className={cn(
330
+ "flex items-center gap-3 rounded-lg border border-border-muted px-3 py-2.5",
331
+ "hover:border-border transition-colors",
332
+ !active && "opacity-60",
333
+ )}
334
+ >
335
+ <RunnerIcon size={14} />
336
+
337
+ {/* Name + phase badge */}
338
+ <div className="flex min-w-0 flex-1 items-center gap-2">
339
+ <span className="truncate text-sm font-medium text-foreground">
340
+ {name}
341
+ </span>
342
+ {systemManaged && (
343
+ <span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[0.6rem] font-medium text-muted-foreground">
344
+ System
345
+ </span>
346
+ )}
347
+ <PhaseBadge phase={phase} />
348
+ </div>
349
+
350
+ {/* Metadata columns — responsive */}
351
+ <div className="hidden items-center gap-4 text-xs text-muted-foreground sm:flex">
352
+ {hostname && (
353
+ <span className="max-w-[10rem] truncate" title={hostname}>
354
+ {hostname}
355
+ </span>
356
+ )}
357
+ {osArch && (
358
+ <span className="font-mono text-[0.65rem]">{osArch}</span>
359
+ )}
360
+ {version && (
361
+ <span className="font-mono text-[0.65rem]">v{version}</span>
362
+ )}
363
+ {active && (
364
+ <span title="Current executions">
365
+ {executions} exec{executions !== 1 ? "s" : ""}
366
+ </span>
367
+ )}
368
+ {lastHeartbeat && (
369
+ <span
370
+ title={`Last heartbeat: ${timestampDate(lastHeartbeat).toISOString()}`}
371
+ >
372
+ {formatRelativeTime(timestampDate(lastHeartbeat))}
373
+ </span>
374
+ )}
375
+ </div>
376
+
377
+ {hasActions && (
378
+ <ActionMenu
379
+ canStop={canStop}
380
+ onStop={() => onRequestConfirm("stop")}
381
+ onDelete={() => onRequestConfirm("delete")}
382
+ runnerName={name}
383
+ />
384
+ )}
385
+ </div>
386
+ );
387
+ }
388
+
389
+ // ---------------------------------------------------------------------------
390
+ // ConfirmationRow (internal)
391
+ // ---------------------------------------------------------------------------
392
+
393
+ function ConfirmationRow({
394
+ message,
395
+ description,
396
+ confirmLabel,
397
+ isMutating,
398
+ error,
399
+ onConfirm,
400
+ onCancel,
401
+ }: {
402
+ message: React.ReactNode;
403
+ description: string;
404
+ confirmLabel: string;
405
+ isMutating: boolean;
406
+ error: Error | null;
407
+ onConfirm: () => void;
408
+ onCancel: () => void;
409
+ }) {
410
+ return (
411
+ <div
412
+ role="listitem"
413
+ className="rounded-lg border border-destructive/30 bg-destructive-subtle px-3 py-2.5"
414
+ >
415
+ <p className="text-xs text-foreground">{message}</p>
416
+ <p className="mt-0.5 text-[0.65rem] text-muted-foreground">
417
+ {description}
418
+ </p>
419
+ {error && (
420
+ <p className="mt-1 text-[0.65rem] text-destructive" role="alert">
421
+ {getUserMessage(error)}
422
+ </p>
423
+ )}
424
+ <div className="mt-2 flex items-center gap-1.5">
425
+ <button
426
+ type="button"
427
+ onClick={onConfirm}
428
+ disabled={isMutating}
429
+ className={cn(
430
+ "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium",
431
+ "bg-destructive text-destructive-foreground hover:bg-destructive-hover",
432
+ "disabled:pointer-events-none disabled:opacity-50",
433
+ )}
434
+ >
435
+ {isMutating && <SpinnerIcon />}
436
+ {confirmLabel}
437
+ </button>
438
+ <button
439
+ type="button"
440
+ onClick={onCancel}
441
+ disabled={isMutating}
442
+ className={cn(
443
+ "rounded-md px-2.5 py-1 text-xs",
444
+ "text-muted-foreground hover:text-foreground hover:bg-accent-hover",
445
+ "disabled:pointer-events-none disabled:opacity-50",
446
+ )}
447
+ >
448
+ Cancel
449
+ </button>
450
+ </div>
451
+ </div>
452
+ );
453
+ }
454
+
455
+ // ---------------------------------------------------------------------------
456
+ // ActionMenu (internal)
457
+ // ---------------------------------------------------------------------------
458
+
459
+ function ActionMenu({
460
+ canStop,
461
+ onStop,
462
+ onDelete,
463
+ runnerName,
464
+ }: {
465
+ canStop: boolean;
466
+ onStop: () => void;
467
+ onDelete: () => void;
468
+ runnerName: string;
469
+ }) {
470
+ const [open, setOpen] = useState(false);
471
+ const containerRef = useRef<HTMLDivElement>(null);
472
+ const triggerRef = useRef<HTMLButtonElement>(null);
473
+
474
+ useEffect(() => {
475
+ if (!open) return;
476
+
477
+ function handlePointerDown(e: MouseEvent) {
478
+ if (
479
+ containerRef.current &&
480
+ !containerRef.current.contains(e.target as Node)
481
+ ) {
482
+ setOpen(false);
483
+ }
484
+ }
485
+
486
+ function handleKeyDown(e: KeyboardEvent) {
487
+ if (e.key === "Escape") {
488
+ setOpen(false);
489
+ triggerRef.current?.focus();
490
+ }
491
+ }
492
+
493
+ document.addEventListener("mousedown", handlePointerDown);
494
+ document.addEventListener("keydown", handleKeyDown);
495
+ return () => {
496
+ document.removeEventListener("mousedown", handlePointerDown);
497
+ document.removeEventListener("keydown", handleKeyDown);
498
+ };
499
+ }, [open]);
500
+
501
+ return (
502
+ <div ref={containerRef} className="relative">
503
+ <button
504
+ ref={triggerRef}
505
+ type="button"
506
+ onClick={() => setOpen((prev) => !prev)}
507
+ aria-label={`Actions for ${runnerName}`}
508
+ aria-haspopup="menu"
509
+ aria-expanded={open}
510
+ className={cn(
511
+ "shrink-0 rounded p-1",
512
+ "text-muted-foreground hover:text-foreground hover:bg-accent-hover",
513
+ "transition-colors",
514
+ )}
515
+ >
516
+ <MoreVerticalIcon />
517
+ </button>
518
+
519
+ {open && (
520
+ <div
521
+ role="menu"
522
+ aria-label={`Actions for ${runnerName}`}
523
+ className={cn(
524
+ "absolute right-0 top-full z-10 mt-1",
525
+ "min-w-[10rem] rounded-md border border-border bg-popover py-1 shadow-md",
526
+ )}
527
+ >
528
+ {canStop && (
529
+ <button
530
+ type="button"
531
+ role="menuitem"
532
+ onClick={() => {
533
+ setOpen(false);
534
+ onStop();
535
+ }}
536
+ className={cn(
537
+ "flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs",
538
+ "text-foreground hover:bg-accent-hover transition-colors",
539
+ )}
540
+ >
541
+ <StopIcon />
542
+ Stop runner
543
+ </button>
544
+ )}
545
+ <button
546
+ type="button"
547
+ role="menuitem"
548
+ onClick={() => {
549
+ setOpen(false);
550
+ onDelete();
551
+ }}
552
+ className={cn(
553
+ "flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs",
554
+ "text-destructive-muted hover:text-destructive hover:bg-destructive-subtle",
555
+ "transition-colors",
556
+ )}
557
+ >
558
+ <TrashIcon />
559
+ Delete runner
560
+ </button>
561
+ </div>
562
+ )}
563
+ </div>
564
+ );
565
+ }
566
+
567
+ // ---------------------------------------------------------------------------
568
+ // PhaseBadge (internal)
569
+ // ---------------------------------------------------------------------------
570
+
571
+ function PhaseBadge({ phase }: { phase: RunnerPhase }) {
572
+ return (
573
+ <span className="inline-flex shrink-0 items-center gap-1">
574
+ <span
575
+ className={`inline-block h-1.5 w-1.5 rounded-full ${phaseDotColor(phase)}`}
576
+ aria-hidden="true"
577
+ />
578
+ <span className="text-[0.65rem] text-muted-foreground">
579
+ {phaseLabel(phase)}
580
+ </span>
581
+ </span>
582
+ );
583
+ }
584
+
585
+ // ---------------------------------------------------------------------------
586
+ // Utilities
587
+ // ---------------------------------------------------------------------------
588
+
589
+ function phaseThenName(a: Runner, b: Runner): number {
590
+ const pa = a.status?.phase ?? RunnerPhase.UNSPECIFIED;
591
+ const pb = b.status?.phase ?? RunnerPhase.UNSPECIFIED;
592
+ const order = PHASE_SORT_ORDER[pa] - PHASE_SORT_ORDER[pb];
593
+ if (order !== 0) return order;
594
+ return (a.metadata?.name ?? "").localeCompare(b.metadata?.name ?? "");
595
+ }
596
+
597
+ function formatRelativeTime(date: Date): string {
598
+ const diffMs = Date.now() - date.getTime();
599
+
600
+ if (diffMs < 0) return "just now";
601
+
602
+ const seconds = Math.floor(diffMs / 1000);
603
+ if (seconds < 60) return "just now";
604
+
605
+ const minutes = Math.floor(seconds / 60);
606
+ if (minutes < 60) return `${minutes}m ago`;
607
+
608
+ const hours = Math.floor(minutes / 60);
609
+ if (hours < 24) return `${hours}h ago`;
610
+
611
+ const days = Math.floor(hours / 24);
612
+ if (days < 30) return `${days}d ago`;
613
+
614
+ return date.toLocaleDateString(undefined, {
615
+ month: "short",
616
+ day: "numeric",
617
+ year: "numeric",
618
+ });
619
+ }
620
+
621
+ // ---------------------------------------------------------------------------
622
+ // Icons
623
+ // ---------------------------------------------------------------------------
624
+
625
+ function RunnerIcon({ size = 14 }: { size?: number }) {
626
+ return (
627
+ <svg
628
+ width={size}
629
+ height={size}
630
+ viewBox="0 0 24 24"
631
+ fill="none"
632
+ stroke="currentColor"
633
+ strokeWidth="2"
634
+ strokeLinecap="round"
635
+ strokeLinejoin="round"
636
+ aria-hidden="true"
637
+ className="shrink-0 text-muted-foreground"
638
+ >
639
+ <rect x="4" y="4" width="16" height="16" rx="2" />
640
+ <rect x="9" y="9" width="6" height="6" />
641
+ <path d="M15 2v2" />
642
+ <path d="M15 20v2" />
643
+ <path d="M2 15h2" />
644
+ <path d="M2 9h2" />
645
+ <path d="M20 15h2" />
646
+ <path d="M20 9h2" />
647
+ <path d="M9 2v2" />
648
+ <path d="M9 20v2" />
649
+ </svg>
650
+ );
651
+ }
652
+
653
+ function MoreVerticalIcon() {
654
+ return (
655
+ <svg
656
+ width="14"
657
+ height="14"
658
+ viewBox="0 0 16 16"
659
+ fill="currentColor"
660
+ aria-hidden="true"
661
+ >
662
+ <circle cx="8" cy="3" r="1.5" />
663
+ <circle cx="8" cy="8" r="1.5" />
664
+ <circle cx="8" cy="13" r="1.5" />
665
+ </svg>
666
+ );
667
+ }
668
+
669
+ function StopIcon() {
670
+ return (
671
+ <svg
672
+ width="14"
673
+ height="14"
674
+ viewBox="0 0 16 16"
675
+ fill="none"
676
+ stroke="currentColor"
677
+ strokeWidth="1.5"
678
+ strokeLinecap="round"
679
+ strokeLinejoin="round"
680
+ aria-hidden="true"
681
+ >
682
+ <circle cx="8" cy="8" r="6" />
683
+ <rect x="5.5" y="5.5" width="5" height="5" rx="0.5" />
684
+ </svg>
685
+ );
686
+ }
687
+
688
+ function TrashIcon() {
689
+ return (
690
+ <svg
691
+ width="14"
692
+ height="14"
693
+ viewBox="0 0 16 16"
694
+ fill="none"
695
+ stroke="currentColor"
696
+ strokeWidth="1.5"
697
+ strokeLinecap="round"
698
+ strokeLinejoin="round"
699
+ aria-hidden="true"
700
+ >
701
+ <path d="M2.5 4h11M5.5 4V2.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1V4" />
702
+ <path d="M12.5 4v9a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1V4" />
703
+ <line x1="6.5" y1="7" x2="6.5" y2="11" />
704
+ <line x1="9.5" y1="7" x2="9.5" y2="11" />
705
+ </svg>
706
+ );
707
+ }
708
+
709
+ function SpinnerIcon() {
710
+ return (
711
+ <svg
712
+ width="12"
713
+ height="12"
714
+ viewBox="0 0 16 16"
715
+ fill="none"
716
+ stroke="currentColor"
717
+ strokeWidth="2"
718
+ strokeLinecap="round"
719
+ className="animate-spin"
720
+ aria-hidden="true"
721
+ >
722
+ <path d="M8 2a6 6 0 1 0 6 6" />
723
+ </svg>
724
+ );
725
+ }