@stigmer/react 0.0.101 → 0.1.0

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 (262) hide show
  1. package/agent/AgentPicker.js +1 -1
  2. package/agent/AgentPicker.js.map +1 -1
  3. package/agent/__tests__/useDefaultAgent.test.d.ts +2 -0
  4. package/agent/__tests__/useDefaultAgent.test.d.ts.map +1 -0
  5. package/agent/__tests__/useDefaultAgent.test.js +252 -0
  6. package/agent/__tests__/useDefaultAgent.test.js.map +1 -0
  7. package/agent/useAgent.d.ts +2 -0
  8. package/agent/useAgent.d.ts.map +1 -1
  9. package/agent/useAgent.js +14 -37
  10. package/agent/useAgent.js.map +1 -1
  11. package/agent/useAgentCount.d.ts +1 -1
  12. package/agent/useAgentCount.d.ts.map +1 -1
  13. package/agent/useAgentList.d.ts +2 -2
  14. package/agent/useAgentList.d.ts.map +1 -1
  15. package/agent/useDefaultAgent.d.ts +6 -2
  16. package/agent/useDefaultAgent.d.ts.map +1 -1
  17. package/agent/useDefaultAgent.js +51 -31
  18. package/agent/useDefaultAgent.js.map +1 -1
  19. package/agent-instance/useAgentInstance.d.ts +2 -0
  20. package/agent-instance/useAgentInstance.d.ts.map +1 -1
  21. package/agent-instance/useAgentInstance.js +6 -33
  22. package/agent-instance/useAgentInstance.js.map +1 -1
  23. package/agent-instance/useAgentInstanceList.d.ts +2 -0
  24. package/agent-instance/useAgentInstanceList.d.ts.map +1 -1
  25. package/agent-instance/useAgentInstanceList.js +22 -40
  26. package/agent-instance/useAgentInstanceList.js.map +1 -1
  27. package/api-key/useApiKeyList.d.ts +2 -0
  28. package/api-key/useApiKeyList.d.ts.map +1 -1
  29. package/api-key/useApiKeyList.js +3 -27
  30. package/api-key/useApiKeyList.js.map +1 -1
  31. package/composer/ComposerToolbar.d.ts +1 -5
  32. package/composer/ComposerToolbar.d.ts.map +1 -1
  33. package/composer/ComposerToolbar.js +3 -4
  34. package/composer/ComposerToolbar.js.map +1 -1
  35. package/composer/ContextChip.d.ts +1 -1
  36. package/composer/ContextChip.d.ts.map +1 -1
  37. package/composer/ContextChip.js +1 -0
  38. package/composer/ContextChip.js.map +1 -1
  39. package/composer/SessionComposer.d.ts.map +1 -1
  40. package/composer/SessionComposer.js +77 -4
  41. package/composer/SessionComposer.js.map +1 -1
  42. package/composer/icons.d.ts +1 -0
  43. package/composer/icons.d.ts.map +1 -1
  44. package/composer/icons.js +3 -0
  45. package/composer/icons.js.map +1 -1
  46. package/environment/useEnvironment.d.ts +2 -0
  47. package/environment/useEnvironment.d.ts.map +1 -1
  48. package/environment/useEnvironment.js +6 -33
  49. package/environment/useEnvironment.js.map +1 -1
  50. package/environment/useEnvironmentList.d.ts +2 -0
  51. package/environment/useEnvironmentList.d.ts.map +1 -1
  52. package/environment/useEnvironmentList.js +23 -53
  53. package/environment/useEnvironmentList.js.map +1 -1
  54. package/execution/ArtifactPreviewModal.js +1 -1
  55. package/execution/ArtifactPreviewModal.js.map +1 -1
  56. package/execution/useArtifactContent.d.ts +4 -2
  57. package/execution/useArtifactContent.d.ts.map +1 -1
  58. package/execution/useArtifactContent.js +23 -45
  59. package/execution/useArtifactContent.js.map +1 -1
  60. package/iam-policy/usePrincipalsCount.d.ts +2 -0
  61. package/iam-policy/usePrincipalsCount.d.ts.map +1 -1
  62. package/iam-policy/usePrincipalsCount.js +7 -46
  63. package/iam-policy/usePrincipalsCount.js.map +1 -1
  64. package/iam-policy/useResourceAccess.d.ts +2 -0
  65. package/iam-policy/useResourceAccess.d.ts.map +1 -1
  66. package/iam-policy/useResourceAccess.js +12 -47
  67. package/iam-policy/useResourceAccess.js.map +1 -1
  68. package/identity-provider/useIdentityProvider.d.ts +2 -0
  69. package/identity-provider/useIdentityProvider.d.ts.map +1 -1
  70. package/identity-provider/useIdentityProvider.js +3 -33
  71. package/identity-provider/useIdentityProvider.js.map +1 -1
  72. package/identity-provider/useIdentityProviderList.d.ts +2 -0
  73. package/identity-provider/useIdentityProviderList.d.ts.map +1 -1
  74. package/identity-provider/useIdentityProviderList.js +7 -33
  75. package/identity-provider/useIdentityProviderList.js.map +1 -1
  76. package/index.d.ts +2 -2
  77. package/index.d.ts.map +1 -1
  78. package/index.js +1 -1
  79. package/index.js.map +1 -1
  80. package/internal/__tests__/useFetch.test.d.ts +2 -0
  81. package/internal/__tests__/useFetch.test.d.ts.map +1 -0
  82. package/internal/__tests__/useFetch.test.js +95 -0
  83. package/internal/__tests__/useFetch.test.js.map +1 -0
  84. package/internal/useFetch.d.ts +51 -0
  85. package/internal/useFetch.d.ts.map +1 -0
  86. package/internal/useFetch.js +75 -0
  87. package/internal/useFetch.js.map +1 -0
  88. package/invitation/useInvitationPreview.d.ts +2 -0
  89. package/invitation/useInvitationPreview.d.ts.map +1 -1
  90. package/invitation/useInvitationPreview.js +5 -35
  91. package/invitation/useInvitationPreview.js.map +1 -1
  92. package/invitation/useOrgInvitations.d.ts +2 -0
  93. package/invitation/useOrgInvitations.d.ts.map +1 -1
  94. package/invitation/useOrgInvitations.js +6 -34
  95. package/invitation/useOrgInvitations.js.map +1 -1
  96. package/library/ResourceListView.d.ts +2 -2
  97. package/library/ResourceListView.d.ts.map +1 -1
  98. package/library/ResourceListView.js +1 -1
  99. package/library/ResourceListView.js.map +1 -1
  100. package/library/useDetectSkillPackage.d.ts +1 -1
  101. package/library/useDetectSkillPackage.d.ts.map +1 -1
  102. package/mcp-server/McpServerPicker.js +1 -1
  103. package/mcp-server/McpServerPicker.js.map +1 -1
  104. package/mcp-server/useMcpServer.d.ts +2 -0
  105. package/mcp-server/useMcpServer.d.ts.map +1 -1
  106. package/mcp-server/useMcpServer.js +13 -37
  107. package/mcp-server/useMcpServer.js.map +1 -1
  108. package/mcp-server/useMcpServerCount.d.ts +1 -1
  109. package/mcp-server/useMcpServerCount.d.ts.map +1 -1
  110. package/mcp-server/useMcpServerList.d.ts +2 -2
  111. package/mcp-server/useMcpServerList.d.ts.map +1 -1
  112. package/mcp-server/useOAuthGrantStatus.d.ts +2 -0
  113. package/mcp-server/useOAuthGrantStatus.d.ts.map +1 -1
  114. package/mcp-server/useOAuthGrantStatus.js +14 -61
  115. package/mcp-server/useOAuthGrantStatus.js.map +1 -1
  116. package/mcp-server/useOrgOAuthApp.d.ts +2 -0
  117. package/mcp-server/useOrgOAuthApp.d.ts.map +1 -1
  118. package/mcp-server/useOrgOAuthApp.js +19 -43
  119. package/mcp-server/useOrgOAuthApp.js.map +1 -1
  120. package/oauth-app/useOAuthAppList.d.ts +2 -0
  121. package/oauth-app/useOAuthAppList.d.ts.map +1 -1
  122. package/oauth-app/useOAuthAppList.js +6 -34
  123. package/oauth-app/useOAuthAppList.js.map +1 -1
  124. package/organization/useOrganization.d.ts +2 -0
  125. package/organization/useOrganization.d.ts.map +1 -1
  126. package/organization/useOrganization.js +3 -33
  127. package/organization/useOrganization.js.map +1 -1
  128. package/package.json +4 -4
  129. package/platform-client/usePlatformClient.d.ts +2 -0
  130. package/platform-client/usePlatformClient.d.ts.map +1 -1
  131. package/platform-client/usePlatformClient.js +3 -33
  132. package/platform-client/usePlatformClient.js.map +1 -1
  133. package/platform-client/usePlatformClientList.d.ts +2 -0
  134. package/platform-client/usePlatformClientList.d.ts.map +1 -1
  135. package/platform-client/usePlatformClientList.js +6 -34
  136. package/platform-client/usePlatformClientList.js.map +1 -1
  137. package/runner/RunnerListPanel.d.ts.map +1 -1
  138. package/runner/RunnerListPanel.js +21 -5
  139. package/runner/RunnerListPanel.js.map +1 -1
  140. package/runner/__tests__/phase.test.d.ts +2 -0
  141. package/runner/__tests__/phase.test.d.ts.map +1 -0
  142. package/runner/__tests__/phase.test.js +33 -0
  143. package/runner/__tests__/phase.test.js.map +1 -0
  144. package/runner/__tests__/useRunnerList.test.d.ts +2 -0
  145. package/runner/__tests__/useRunnerList.test.d.ts.map +1 -0
  146. package/runner/__tests__/useRunnerList.test.js +68 -0
  147. package/runner/__tests__/useRunnerList.test.js.map +1 -0
  148. package/runner/index.d.ts +3 -1
  149. package/runner/index.d.ts.map +1 -1
  150. package/runner/index.js +2 -1
  151. package/runner/index.js.map +1 -1
  152. package/runner/phase.d.ts +13 -0
  153. package/runner/phase.d.ts.map +1 -1
  154. package/runner/phase.js +15 -0
  155. package/runner/phase.js.map +1 -1
  156. package/runner/useRunnerCredential.d.ts +66 -0
  157. package/runner/useRunnerCredential.d.ts.map +1 -0
  158. package/runner/useRunnerCredential.js +50 -0
  159. package/runner/useRunnerCredential.js.map +1 -0
  160. package/runner/useRunnerList.d.ts +21 -0
  161. package/runner/useRunnerList.d.ts.map +1 -1
  162. package/runner/useRunnerList.js +9 -36
  163. package/runner/useRunnerList.js.map +1 -1
  164. package/search/useResourceCount.d.ts +2 -1
  165. package/search/useResourceCount.d.ts.map +1 -1
  166. package/search/useResourceCount.js +13 -37
  167. package/search/useResourceCount.js.map +1 -1
  168. package/search/useResourceList.d.ts +2 -1
  169. package/search/useResourceList.d.ts.map +1 -1
  170. package/search/useResourceList.js +26 -46
  171. package/search/useResourceList.js.map +1 -1
  172. package/search/useResourceSearch.d.ts +4 -2
  173. package/search/useResourceSearch.d.ts.map +1 -1
  174. package/search/useResourceSearch.js +7 -27
  175. package/search/useResourceSearch.js.map +1 -1
  176. package/session/useNewSessionFlow.d.ts.map +1 -1
  177. package/session/useNewSessionFlow.js +26 -1
  178. package/session/useNewSessionFlow.js.map +1 -1
  179. package/session/useSession.d.ts +3 -1
  180. package/session/useSession.d.ts.map +1 -1
  181. package/session/useSession.js +3 -33
  182. package/session/useSession.js.map +1 -1
  183. package/session/useSessionExecutions.d.ts +3 -1
  184. package/session/useSessionExecutions.d.ts.map +1 -1
  185. package/session/useSessionExecutions.js +6 -34
  186. package/session/useSessionExecutions.js.map +1 -1
  187. package/session/useSessionList.d.ts +5 -3
  188. package/session/useSessionList.d.ts.map +1 -1
  189. package/session/useSessionList.js +9 -31
  190. package/session/useSessionList.js.map +1 -1
  191. package/skill/SkillPicker.js +1 -1
  192. package/skill/SkillPicker.js.map +1 -1
  193. package/skill/useSkill.d.ts +2 -0
  194. package/skill/useSkill.d.ts.map +1 -1
  195. package/skill/useSkill.js +13 -37
  196. package/skill/useSkill.js.map +1 -1
  197. package/skill/useSkillCount.d.ts +1 -1
  198. package/skill/useSkillCount.d.ts.map +1 -1
  199. package/skill/useSkillList.d.ts +2 -2
  200. package/skill/useSkillList.d.ts.map +1 -1
  201. package/src/agent/AgentPicker.tsx +1 -1
  202. package/src/agent/__tests__/useDefaultAgent.test.tsx +308 -0
  203. package/src/agent/useAgent.ts +19 -41
  204. package/src/agent/useAgentCount.ts +1 -1
  205. package/src/agent/useAgentList.ts +2 -2
  206. package/src/agent/useDefaultAgent.ts +67 -35
  207. package/src/agent-instance/useAgentInstance.ts +13 -37
  208. package/src/agent-instance/useAgentInstanceList.ts +31 -47
  209. package/src/api-key/useApiKeyList.ts +9 -32
  210. package/src/composer/ComposerToolbar.tsx +1 -22
  211. package/src/composer/ContextChip.tsx +2 -1
  212. package/src/composer/SessionComposer.tsx +206 -5
  213. package/src/composer/icons.tsx +27 -0
  214. package/src/environment/useEnvironment.ts +13 -37
  215. package/src/environment/useEnvironmentList.ts +31 -58
  216. package/src/execution/ArtifactPreviewModal.tsx +2 -2
  217. package/src/execution/useArtifactContent.ts +48 -65
  218. package/src/iam-policy/usePrincipalsCount.ts +17 -53
  219. package/src/iam-policy/useResourceAccess.ts +18 -55
  220. package/src/identity-provider/useIdentityProvider.ts +9 -39
  221. package/src/identity-provider/useIdentityProviderList.ts +14 -40
  222. package/src/index.ts +4 -0
  223. package/src/internal/__tests__/useFetch.test.ts +133 -0
  224. package/src/internal/useFetch.ts +121 -0
  225. package/src/invitation/useInvitationPreview.ts +14 -40
  226. package/src/invitation/useOrgInvitations.ts +15 -41
  227. package/src/library/ResourceListView.tsx +3 -3
  228. package/src/library/useDetectSkillPackage.ts +1 -1
  229. package/src/mcp-server/McpServerPicker.tsx +1 -1
  230. package/src/mcp-server/useMcpServer.ts +17 -42
  231. package/src/mcp-server/useMcpServerCount.ts +1 -1
  232. package/src/mcp-server/useMcpServerList.ts +2 -2
  233. package/src/mcp-server/useOAuthGrantStatus.ts +31 -69
  234. package/src/mcp-server/useOrgOAuthApp.ts +34 -51
  235. package/src/oauth-app/useOAuthAppList.ts +15 -41
  236. package/src/organization/useOrganization.ts +9 -38
  237. package/src/platform-client/usePlatformClient.ts +9 -39
  238. package/src/platform-client/usePlatformClientList.ts +14 -42
  239. package/src/runner/RunnerListPanel.tsx +49 -41
  240. package/src/runner/__tests__/phase.test.ts +35 -0
  241. package/src/runner/__tests__/useRunnerList.test.tsx +96 -0
  242. package/src/runner/index.ts +7 -0
  243. package/src/runner/phase.ts +16 -0
  244. package/src/runner/useRunnerCredential.ts +89 -0
  245. package/src/runner/useRunnerList.ts +36 -44
  246. package/src/search/useResourceCount.ts +20 -48
  247. package/src/search/useResourceList.ts +40 -57
  248. package/src/search/useResourceSearch.ts +22 -43
  249. package/src/session/useNewSessionFlow.ts +35 -1
  250. package/src/session/useSession.ts +10 -39
  251. package/src/session/useSessionExecutions.ts +20 -46
  252. package/src/session/useSessionList.ts +21 -42
  253. package/src/skill/SkillPicker.tsx +1 -1
  254. package/src/skill/useSkill.ts +17 -42
  255. package/src/skill/useSkillCount.ts +1 -1
  256. package/src/skill/useSkillList.ts +2 -2
  257. package/src/usage/useOrgUsageReport.ts +18 -46
  258. package/styles.css +1 -1
  259. package/usage/useOrgUsageReport.d.ts +2 -0
  260. package/usage/useOrgUsageReport.d.ts.map +1 -1
  261. package/usage/useOrgUsageReport.js +5 -35
  262. package/usage/useOrgUsageReport.js.map +1 -1
@@ -30,11 +30,19 @@ import {
30
30
  SYSTEM_ENV_VAR_KEYS,
31
31
  resolveSystemEnvVarValues,
32
32
  } from "../environment/systemEnvVars";
33
+ import { RunnerPhase } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/enum_pb";
34
+ import {
35
+ isActivePhase,
36
+ phaseLabel,
37
+ phaseDotColor,
38
+ PHASE_SORT_ORDER,
39
+ } from "../runner/phase";
33
40
  import {
34
41
  AgentIcon,
35
42
  McpServerIcon,
36
43
  SkillIcon,
37
44
  SecretsIcon,
45
+ RunnerIcon,
38
46
  AlertTriangleIcon,
39
47
  ResolveSpinner,
40
48
  } from "./icons";
@@ -927,6 +935,15 @@ export function SessionComposer({
927
935
  }
928
936
  }
929
937
 
938
+ if (showRunner && runnerId) {
939
+ items.push({
940
+ key: `runner:${runnerId}`,
941
+ label: selectedRunnerName ?? "Runner",
942
+ type: "runner",
943
+ onRemove: () => onRunnerIdChange?.(null),
944
+ });
945
+ }
946
+
930
947
  if (sessionVariables) {
931
948
  for (const entry of sessionVariables.entries) {
932
949
  const k = entry.key.trim();
@@ -951,6 +968,10 @@ export function SessionComposer({
951
968
  mcpSetup.entries,
952
969
  mcpSetup.removeServer,
953
970
  skillRefs,
971
+ showRunner,
972
+ runnerId,
973
+ selectedRunnerName,
974
+ onRunnerIdChange,
954
975
  sessionVariables,
955
976
  displayNames,
956
977
  onSkillRefsChange,
@@ -1025,6 +1046,14 @@ export function SessionComposer({
1025
1046
  count: skillCount,
1026
1047
  });
1027
1048
  }
1049
+ if (showRunner) {
1050
+ items.push({
1051
+ id: "runner",
1052
+ icon: <RunnerIcon />,
1053
+ label: "Runner",
1054
+ count: runnerId ? 1 : 0,
1055
+ });
1056
+ }
1028
1057
  if (showSessionVars) {
1029
1058
  items.push({
1030
1059
  id: "sessionVars",
@@ -1034,7 +1063,7 @@ export function SessionComposer({
1034
1063
  });
1035
1064
  }
1036
1065
  return items;
1037
- }, [showAgent, agentRef, agentSetup.state, showMcp, mcpCount, mcpSetup.needsSetupCount, showSkills, skillCount, showSessionVars, sessionVarCount]);
1066
+ }, [showAgent, agentRef, agentSetup.state, showMcp, mcpCount, mcpSetup.needsSetupCount, showSkills, skillCount, showRunner, runnerId, showSessionVars, sessionVarCount]);
1038
1067
 
1039
1068
  const renderConfigPanel = useCallback(
1040
1069
  (panelId: string): React.ReactNode => {
@@ -1119,6 +1148,16 @@ export function SessionComposer({
1119
1148
  />
1120
1149
  );
1121
1150
 
1151
+ case "runner":
1152
+ return (
1153
+ <RunnerConfigPanel
1154
+ org={org!}
1155
+ value={runnerId ?? null}
1156
+ onChange={(id) => onRunnerIdChange?.(id)}
1157
+ disabled={isDisabled}
1158
+ />
1159
+ );
1160
+
1122
1161
  case "sessionVars":
1123
1162
  return (
1124
1163
  <SessionVariablesInput
@@ -1145,6 +1184,8 @@ export function SessionComposer({
1145
1184
  mcpSetup,
1146
1185
  skillRefs,
1147
1186
  onSkillRefsChange,
1187
+ runnerId,
1188
+ onRunnerIdChange,
1148
1189
  sessionVariables,
1149
1190
  pool,
1150
1191
  requiredByMap,
@@ -1321,10 +1362,6 @@ export function SessionComposer({
1321
1362
  configActivePanel={configActivePanel}
1322
1363
  onConfigActivePanelChange={handleConfigActivePanelChange}
1323
1364
  renderConfigPanel={renderConfigPanel}
1324
- showRunner={showRunner}
1325
- runnerOrg={org ?? ""}
1326
- runnerId={runnerId ?? null}
1327
- onRunnerIdChange={onRunnerIdChange ?? (() => {})}
1328
1365
  showModelSelector={showModelSelector}
1329
1366
  modelId={modelId}
1330
1367
  onModelChange={handleModelChange}
@@ -1361,3 +1398,167 @@ function mcpRefFromKey(key: string): ResourceRef {
1361
1398
  kind: ApiResourceKind.mcp_server,
1362
1399
  };
1363
1400
  }
1401
+
1402
+ // ---------------------------------------------------------------------------
1403
+ // Runner config panel — inline list for the Configure menu
1404
+ // ---------------------------------------------------------------------------
1405
+
1406
+ function RunnerConfigPanel({
1407
+ org,
1408
+ value,
1409
+ onChange,
1410
+ disabled,
1411
+ }: {
1412
+ org: string;
1413
+ value: string | null;
1414
+ onChange: (runnerId: string | null) => void;
1415
+ disabled?: boolean;
1416
+ }) {
1417
+ const { runners, isLoading } = useRunnerList(org);
1418
+
1419
+ const { active, inactive } = useMemo(() => {
1420
+ type R = (typeof runners)[number];
1421
+ const act: R[] = [];
1422
+ const inact: R[] = [];
1423
+ for (const r of runners) {
1424
+ if (isActivePhase(r.status?.phase ?? RunnerPhase.UNSPECIFIED)) {
1425
+ act.push(r);
1426
+ } else {
1427
+ inact.push(r);
1428
+ }
1429
+ }
1430
+ const sorter = (a: R, b: R) => {
1431
+ const pa = a.status?.phase ?? RunnerPhase.UNSPECIFIED;
1432
+ const pb = b.status?.phase ?? RunnerPhase.UNSPECIFIED;
1433
+ const po = PHASE_SORT_ORDER[pa] - PHASE_SORT_ORDER[pb];
1434
+ if (po !== 0) return po;
1435
+ return (a.metadata?.name ?? "").localeCompare(b.metadata?.name ?? "");
1436
+ };
1437
+ act.sort(sorter);
1438
+ inact.sort(sorter);
1439
+ return { active: act, inactive: inact };
1440
+ }, [runners]);
1441
+
1442
+ const isAutoSelected = value === null;
1443
+
1444
+ return (
1445
+ <div className="w-72 space-y-1">
1446
+ {/* Auto option */}
1447
+ <button
1448
+ type="button"
1449
+ onClick={() => onChange(null)}
1450
+ disabled={disabled}
1451
+ className={cn(
1452
+ "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs transition-colors",
1453
+ "disabled:pointer-events-none disabled:opacity-50",
1454
+ isAutoSelected
1455
+ ? "bg-accent font-medium text-foreground"
1456
+ : "text-foreground hover:bg-accent-hover",
1457
+ )}
1458
+ role="option"
1459
+ aria-selected={isAutoSelected}
1460
+ >
1461
+ <span className="inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-primary" aria-hidden="true" />
1462
+ <span className="flex-1">Default</span>
1463
+ <span className="text-[0.6rem] text-muted-foreground">auto</span>
1464
+ </button>
1465
+
1466
+ {/* Available runners */}
1467
+ {active.length > 0 && (
1468
+ <>
1469
+ <div className="px-2 pt-1 text-[0.65rem] font-medium uppercase tracking-wider text-muted-foreground">
1470
+ Available
1471
+ </div>
1472
+ {active.map((r) => {
1473
+ const id = r.metadata!.id;
1474
+ const name = r.metadata?.name ?? "Unnamed";
1475
+ const phase = r.status?.phase ?? RunnerPhase.UNSPECIFIED;
1476
+ const hostname = r.status?.connectionInfo?.hostname;
1477
+ const isSelected = value === id;
1478
+ return (
1479
+ <button
1480
+ key={id}
1481
+ type="button"
1482
+ onClick={() => onChange(id)}
1483
+ disabled={disabled}
1484
+ className={cn(
1485
+ "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs transition-colors",
1486
+ "disabled:pointer-events-none disabled:opacity-50",
1487
+ isSelected
1488
+ ? "bg-accent font-medium text-foreground"
1489
+ : "text-foreground hover:bg-accent-hover",
1490
+ )}
1491
+ role="option"
1492
+ aria-selected={isSelected}
1493
+ >
1494
+ <span
1495
+ className={`inline-block h-1.5 w-1.5 shrink-0 rounded-full ${phaseDotColor(phase)}`}
1496
+ aria-hidden="true"
1497
+ />
1498
+ <div className="flex min-w-0 flex-1 flex-col">
1499
+ <span className="truncate">{name}</span>
1500
+ {hostname && (
1501
+ <span className="truncate text-[0.6rem] leading-tight text-muted-foreground">
1502
+ {hostname}
1503
+ </span>
1504
+ )}
1505
+ </div>
1506
+ <span className="shrink-0 text-[0.6rem] lowercase text-muted-foreground">
1507
+ {phaseLabel(phase)}
1508
+ </span>
1509
+ </button>
1510
+ );
1511
+ })}
1512
+ </>
1513
+ )}
1514
+
1515
+ {/* Offline runners */}
1516
+ {inactive.length > 0 && (
1517
+ <>
1518
+ <div className="px-2 pt-1 text-[0.65rem] font-medium uppercase tracking-wider text-muted-foreground">
1519
+ Offline
1520
+ </div>
1521
+ {inactive.map((r) => {
1522
+ const id = r.metadata!.id;
1523
+ const name = r.metadata?.name ?? "Unnamed";
1524
+ const phase = r.status?.phase ?? RunnerPhase.UNSPECIFIED;
1525
+ return (
1526
+ <div
1527
+ key={id}
1528
+ className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-xs text-muted-foreground opacity-50"
1529
+ >
1530
+ <span
1531
+ className={`inline-block h-1.5 w-1.5 shrink-0 rounded-full ${phaseDotColor(phase)}`}
1532
+ aria-hidden="true"
1533
+ />
1534
+ <span className="min-w-0 flex-1 truncate">{name}</span>
1535
+ <span className="shrink-0 text-[0.6rem] lowercase">
1536
+ {phaseLabel(phase)}
1537
+ </span>
1538
+ </div>
1539
+ );
1540
+ })}
1541
+ </>
1542
+ )}
1543
+
1544
+ {/* Empty state */}
1545
+ {runners.length === 0 && !isLoading && (
1546
+ <p className="py-3 text-center text-xs text-muted-foreground">
1547
+ No runners registered. Use the CLI or desktop app to start one.
1548
+ </p>
1549
+ )}
1550
+
1551
+ {/* Loading state */}
1552
+ {isLoading && (
1553
+ <div className="py-3 text-center text-xs text-muted-foreground">
1554
+ Loading runners...
1555
+ </div>
1556
+ )}
1557
+
1558
+ {/* Hint */}
1559
+ <p className="px-2 pt-1 text-[0.6rem] leading-relaxed text-muted-foreground">
1560
+ Default assigns a cloud runner automatically. Select a specific runner to execute on your own machine.
1561
+ </p>
1562
+ </div>
1563
+ );
1564
+ }
@@ -207,6 +207,33 @@ export function SecretsIcon() {
207
207
  );
208
208
  }
209
209
 
210
+ export function RunnerIcon() {
211
+ return (
212
+ <svg
213
+ width="14"
214
+ height="14"
215
+ viewBox="0 0 24 24"
216
+ fill="none"
217
+ stroke="currentColor"
218
+ strokeWidth="1.5"
219
+ strokeLinecap="round"
220
+ strokeLinejoin="round"
221
+ aria-hidden="true"
222
+ >
223
+ <rect x="4" y="4" width="16" height="16" rx="2" />
224
+ <rect x="9" y="9" width="6" height="6" />
225
+ <path d="M15 2v2" />
226
+ <path d="M15 20v2" />
227
+ <path d="M2 15h2" />
228
+ <path d="M2 9h2" />
229
+ <path d="M20 15h2" />
230
+ <path d="M20 9h2" />
231
+ <path d="M9 2v2" />
232
+ <path d="M9 20v2" />
233
+ </svg>
234
+ );
235
+ }
236
+
210
237
  /** Horizontal sliders icon -- signals "configuration" without conflating with "add content." */
211
238
  export function ConfigureIcon() {
212
239
  return (
@@ -1,10 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import type { Environment } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
5
4
  import type { ResourceRef } from "@stigmer/sdk";
6
5
  import { useStigmer } from "../hooks";
7
- import { toError } from "../internal/toError";
6
+ import { useFetch } from "../internal/useFetch";
8
7
 
9
8
  /** Return value of {@link useEnvironment}. */
10
9
  export interface UseEnvironmentReturn {
@@ -12,6 +11,8 @@ export interface UseEnvironmentReturn {
12
11
  readonly environment: Environment | null;
13
12
  /** `true` while the initial fetch or a refetch is in flight. */
14
13
  readonly isLoading: boolean;
14
+ /** `true` while a background refetch is in flight and stale data is shown. */
15
+ readonly isRefetching: boolean;
15
16
  /** Error from the last failed request, or `null` when healthy. */
16
17
  readonly error: Error | null;
17
18
  /** Discard cached data and re-fetch the environment from the server. */
@@ -49,46 +50,21 @@ export function useEnvironment(
49
50
  ref: ResourceRef | null,
50
51
  ): UseEnvironmentReturn {
51
52
  const stigmer = useStigmer();
52
- const [environment, setEnvironment] = useState<Environment | null>(null);
53
- const [isLoading, setIsLoading] = useState(false);
54
- const [error, setError] = useState<Error | null>(null);
55
- const [fetchKey, setFetchKey] = useState(0);
56
-
57
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
58
53
 
59
54
  const org = ref?.org;
60
55
  const slug = ref?.slug;
61
56
  const version = ref?.version;
62
57
 
63
- useEffect(() => {
64
- if (!org || !slug) {
65
- setEnvironment(null);
66
- setIsLoading(false);
67
- setError(null);
68
- return;
69
- }
70
-
71
- const cancelled = { current: false };
72
- setIsLoading(true);
73
- setError(null);
74
-
75
- stigmer.environment.getByReference({ org, slug, version }).then(
76
- (result) => {
77
- if (cancelled.current) return;
78
- setEnvironment(result);
79
- setIsLoading(false);
80
- },
81
- (err) => {
82
- if (cancelled.current) return;
83
- setError(toError(err));
84
- setIsLoading(false);
85
- },
86
- );
58
+ const fetchFn =
59
+ org && slug
60
+ ? () => stigmer.environment.getByReference({ org, slug, version })
61
+ : null;
87
62
 
88
- return () => {
89
- cancelled.current = true;
90
- };
91
- }, [org, slug, version, stigmer, fetchKey]);
63
+ const { data: environment, isLoading, isRefetching, error, refetch } = useFetch(
64
+ fetchFn,
65
+ [org, slug, version, stigmer],
66
+ null,
67
+ );
92
68
 
93
- return { environment, isLoading, error, refetch };
69
+ return { environment, isLoading, isRefetching, error, refetch };
94
70
  }
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { useRef } from "react";
4
4
  import { create } from "@bufbuild/protobuf";
5
5
  import type { Environment } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
6
6
  import { ListEnvironmentsRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/io_pb";
7
7
  import { useStigmer } from "../hooks";
8
- import { toError } from "../internal/toError";
8
+ import { useFetch } from "../internal/useFetch";
9
9
 
10
10
  /** Return value of {@link useEnvironmentList}. */
11
11
  export interface UseEnvironmentListReturn {
@@ -15,6 +15,8 @@ export interface UseEnvironmentListReturn {
15
15
  readonly totalCount: number;
16
16
  /** `true` while the initial fetch or a refetch is in flight. */
17
17
  readonly isLoading: boolean;
18
+ /** `true` while a background refetch is in flight and stale data is shown. */
19
+ readonly isRefetching: boolean;
18
20
  /** Error from the last failed request, or `null` when healthy. */
19
21
  readonly error: Error | null;
20
22
  /** Discard cached data and re-fetch the list from the server. */
@@ -47,30 +49,6 @@ export function useEnvironmentList(
47
49
  labels?: Record<string, string>,
48
50
  ): UseEnvironmentListReturn {
49
51
  const stigmer = useStigmer();
50
- const [environments, setEnvironments] = useState<Environment[]>([]);
51
- const [totalCount, setTotalCount] = useState(0);
52
- const [isLoading, setIsLoading] = useState(!!org);
53
- const [error, setError] = useState<Error | null>(null);
54
- const [fetchKey, setFetchKey] = useState(0);
55
-
56
- // Synchronously reset loading state when org changes so that
57
- // downstream hooks see isLoading=true in the SAME render —
58
- // not deferred to the next render via an effect.
59
- const [prevOrg, setPrevOrg] = useState(org);
60
- if (org !== prevOrg) {
61
- setPrevOrg(org);
62
- if (org) {
63
- setIsLoading(true);
64
- setEnvironments([]);
65
- setTotalCount(0);
66
- setError(null);
67
- } else {
68
- setIsLoading(false);
69
- setEnvironments([]);
70
- setTotalCount(0);
71
- setError(null);
72
- }
73
- }
74
52
 
75
53
  const labelsRef = useRef(labels);
76
54
  if (
@@ -81,38 +59,33 @@ export function useEnvironmentList(
81
59
  }
82
60
  const stableLabels = labelsRef.current;
83
61
 
84
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
85
-
86
- useEffect(() => {
87
- if (!org) return;
88
-
89
- const cancelled = { current: false };
90
-
91
- stigmer.environment
92
- .list(
93
- create(ListEnvironmentsRequestSchema, {
94
- org,
95
- labels: stableLabels ?? {},
96
- }),
97
- )
98
- .then(
99
- (result) => {
100
- if (cancelled.current) return;
101
- setEnvironments(result.items);
102
- setTotalCount(result.totalCount);
103
- setIsLoading(false);
104
- },
105
- (err) => {
106
- if (cancelled.current) return;
107
- setError(toError(err));
108
- setIsLoading(false);
109
- },
110
- );
62
+ const fetchFn = org
63
+ ? async () => {
64
+ const result = await stigmer.environment.list(
65
+ create(ListEnvironmentsRequestSchema, {
66
+ org,
67
+ labels: stableLabels ?? {},
68
+ }),
69
+ );
70
+ return {
71
+ environments: result.items as Environment[],
72
+ totalCount: result.totalCount,
73
+ };
74
+ }
75
+ : null;
111
76
 
112
- return () => {
113
- cancelled.current = true;
114
- };
115
- }, [org, stableLabels, stigmer, fetchKey]);
77
+ const { data, isLoading, isRefetching, error, refetch } = useFetch(
78
+ fetchFn,
79
+ [org, stableLabels, stigmer],
80
+ { environments: [] as Environment[], totalCount: 0 },
81
+ );
116
82
 
117
- return { environments, totalCount, isLoading, error, refetch };
83
+ return {
84
+ environments: data.environments,
85
+ totalCount: data.totalCount,
86
+ isLoading,
87
+ isRefetching,
88
+ error,
89
+ refetch,
90
+ };
118
91
  }
@@ -487,7 +487,7 @@ function FileContentStateView({
487
487
  readonly content: string | null;
488
488
  readonly contentType: string | null;
489
489
  readonly isLoading: boolean;
490
- readonly error: string | null;
490
+ readonly error: Error | null;
491
491
  readonly isTruncated: boolean;
492
492
  }) {
493
493
  if (isLoading) {
@@ -509,7 +509,7 @@ function FileContentStateView({
509
509
  return (
510
510
  <div className="flex flex-col items-center justify-center gap-2 p-8 text-center">
511
511
  <ErrorAlertIcon />
512
- <p className="text-sm text-destructive">{error}</p>
512
+ <p className="text-sm text-destructive">{error.message}</p>
513
513
  </div>
514
514
  );
515
515
  }
@@ -1,9 +1,21 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useRef, useState } from "react";
4
3
  import { create } from "@bufbuild/protobuf";
5
4
  import { GetArtifactContentRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
5
  import { useStigmer } from "../hooks";
6
+ import { useFetch } from "../internal/useFetch";
7
+
8
+ interface ArtifactContentData {
9
+ content: string | null;
10
+ contentType: string | null;
11
+ isTruncated: boolean;
12
+ }
13
+
14
+ const EMPTY_ARTIFACT: ArtifactContentData = {
15
+ content: null,
16
+ contentType: null,
17
+ isTruncated: false,
18
+ };
7
19
 
8
20
  /** Return value of {@link useArtifactContent}. */
9
21
  export interface UseArtifactContentReturn {
@@ -31,8 +43,11 @@ export interface UseArtifactContentReturn {
31
43
  /** `true` while the content request is in-flight. */
32
44
  readonly isLoading: boolean;
33
45
 
34
- /** Error message from the last failed attempt, or `null` when healthy. */
35
- readonly error: string | null;
46
+ /** `true` while a background refetch is in flight. */
47
+ readonly isRefetching: boolean;
48
+
49
+ /** Error from the last failed attempt, or `null` when healthy. */
50
+ readonly error: Error | null;
36
51
 
37
52
  /**
38
53
  * Re-fetch the artifact content. Uses the `fetchKey` counter pattern
@@ -130,66 +145,34 @@ export function useArtifactContent(
130
145
  ): UseArtifactContentReturn {
131
146
  const stigmer = useStigmer();
132
147
 
133
- const [content, setContent] = useState<string | null>(null);
134
- const [contentType, setContentType] = useState<string | null>(null);
135
- const [isTruncated, setIsTruncated] = useState(false);
136
- const [isLoading, setIsLoading] = useState(false);
137
- const [error, setError] = useState<string | null>(null);
138
- const [fetchKey, setFetchKey] = useState(0);
139
-
140
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
141
-
142
- useEffect(() => {
143
- if (!executionId || !storageKey) {
144
- setContent(null);
145
- setContentType(null);
146
- setIsTruncated(false);
147
- setIsLoading(false);
148
- setError(null);
149
- return;
150
- }
151
-
152
- const cancelled = { current: false };
153
- setIsLoading(true);
154
- setError(null);
155
-
156
- stigmer.agentExecution
157
- .getArtifactContent(
158
- create(GetArtifactContentRequestSchema, {
159
- executionId,
160
- storageKey,
161
- ...(entryPath ? { entryPath } : {}),
162
- }),
163
- )
164
- .then(
165
- (result) => {
166
- if (cancelled.current) return;
167
-
168
- const decoded = new TextDecoder().decode(result.content);
169
- setContent(decoded);
170
- setContentType(result.contentType || null);
171
- setIsTruncated(result.truncated);
172
- setIsLoading(false);
173
- },
174
- (err) => {
175
- if (cancelled.current) return;
176
-
177
- setError(
178
- err instanceof Error
179
- ? err.message
180
- : "Failed to load artifact content",
181
- );
182
- setContent(null);
183
- setContentType(null);
184
- setIsTruncated(false);
185
- setIsLoading(false);
186
- },
187
- );
188
-
189
- return () => {
190
- cancelled.current = true;
191
- };
192
- }, [executionId, storageKey, entryPath, contentHash, stigmer, fetchKey]);
193
-
194
- return { content, contentType, isTruncated, isLoading, error, refetch };
148
+ const { data, isLoading, isRefetching, error, refetch } = useFetch(
149
+ executionId && storageKey
150
+ ? () =>
151
+ stigmer.agentExecution
152
+ .getArtifactContent(
153
+ create(GetArtifactContentRequestSchema, {
154
+ executionId,
155
+ storageKey,
156
+ ...(entryPath ? { entryPath } : {}),
157
+ }),
158
+ )
159
+ .then((result): ArtifactContentData => ({
160
+ content: new TextDecoder().decode(result.content),
161
+ contentType: result.contentType || null,
162
+ isTruncated: result.truncated,
163
+ }))
164
+ : null,
165
+ [executionId, storageKey, entryPath, contentHash, stigmer],
166
+ EMPTY_ARTIFACT,
167
+ );
168
+
169
+ return {
170
+ content: data.content,
171
+ contentType: data.contentType,
172
+ isTruncated: data.isTruncated,
173
+ isLoading,
174
+ isRefetching,
175
+ error,
176
+ refetch,
177
+ };
195
178
  }