@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
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
5
4
  import { useStigmer } from "../hooks";
6
- import { toError } from "../internal/toError";
5
+ import { useFetch } from "../internal/useFetch";
7
6
 
8
7
  /** Return value of {@link usePlatformClient}. */
9
8
  export interface UsePlatformClientReturn {
@@ -11,6 +10,8 @@ export interface UsePlatformClientReturn {
11
10
  readonly platformClient: PlatformClient | null;
12
11
  /** `true` while the initial fetch or a refetch is in flight. */
13
12
  readonly isLoading: boolean;
13
+ /** `true` while a background refetch is in flight and stale data is shown. */
14
+ readonly isRefetching: boolean;
14
15
  /** Error from the last failed request, or `null` when healthy. */
15
16
  readonly error: Error | null;
16
17
  /** Discard cached data and re-fetch from the server. */
@@ -44,43 +45,12 @@ export function usePlatformClient(
44
45
  id: string | null,
45
46
  ): UsePlatformClientReturn {
46
47
  const stigmer = useStigmer();
47
- const [platformClient, setPlatformClient] =
48
- useState<PlatformClient | null>(null);
49
- const [isLoading, setIsLoading] = useState(false);
50
- const [error, setError] = useState<Error | null>(null);
51
- const [fetchKey, setFetchKey] = useState(0);
52
48
 
53
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
49
+ const { data: platformClient, isLoading, isRefetching, error, refetch } = useFetch(
50
+ id ? () => stigmer.platformclient.get(id) : null,
51
+ [id, stigmer],
52
+ null as PlatformClient | null,
53
+ );
54
54
 
55
- useEffect(() => {
56
- if (!id) {
57
- setPlatformClient(null);
58
- setIsLoading(false);
59
- setError(null);
60
- return;
61
- }
62
-
63
- const cancelled = { current: false };
64
- setIsLoading(true);
65
- setError(null);
66
-
67
- stigmer.platformclient.get(id).then(
68
- (result) => {
69
- if (cancelled.current) return;
70
- setPlatformClient(result);
71
- setIsLoading(false);
72
- },
73
- (err) => {
74
- if (cancelled.current) return;
75
- setError(toError(err));
76
- setIsLoading(false);
77
- },
78
- );
79
-
80
- return () => {
81
- cancelled.current = true;
82
- };
83
- }, [id, stigmer, fetchKey]);
84
-
85
- return { platformClient, isLoading, error, refetch };
55
+ return { platformClient, isLoading, isRefetching, error, refetch };
86
56
  }
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import { create } from "@bufbuild/protobuf";
5
4
  import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
6
5
  import { ListPlatformClientsByOrgInputSchema } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/io_pb";
7
6
  import { useStigmer } from "../hooks";
8
- import { toError } from "../internal/toError";
7
+ import { useFetch } from "../internal/useFetch";
9
8
 
10
9
  /** Return value of {@link usePlatformClientList}. */
11
10
  export interface UsePlatformClientListReturn {
@@ -13,6 +12,8 @@ export interface UsePlatformClientListReturn {
13
12
  readonly platformClients: readonly PlatformClient[];
14
13
  /** `true` while the initial fetch or a refetch is in flight. */
15
14
  readonly isLoading: boolean;
15
+ /** `true` while a background refetch is in flight and stale data is shown. */
16
+ readonly isRefetching: boolean;
16
17
  /** Error from the last failed request, or `null` when healthy. */
17
18
  readonly error: Error | null;
18
19
  /** Discard cached data and re-fetch from the server. */
@@ -51,46 +52,17 @@ export function usePlatformClientList(
51
52
  org: string | null,
52
53
  ): UsePlatformClientListReturn {
53
54
  const stigmer = useStigmer();
54
- const [platformClients, setPlatformClients] = useState<PlatformClient[]>([]);
55
- const [isLoading, setIsLoading] = useState(false);
56
- const [error, setError] = useState<Error | null>(null);
57
- const [fetchKey, setFetchKey] = useState(0);
58
55
 
59
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
56
+ const { data: platformClients, isLoading, isRefetching, error, refetch } = useFetch(
57
+ org
58
+ ? () =>
59
+ stigmer.platformclient
60
+ .listByOrg(create(ListPlatformClientsByOrgInputSchema, { org }))
61
+ .then((r) => [...r.entries])
62
+ : null,
63
+ [org, stigmer],
64
+ [] as PlatformClient[],
65
+ );
60
66
 
61
- useEffect(() => {
62
- if (!org) {
63
- setPlatformClients([]);
64
- setIsLoading(false);
65
- setError(null);
66
- return;
67
- }
68
-
69
- const cancelled = { current: false };
70
- setIsLoading(true);
71
- setError(null);
72
-
73
- stigmer.platformclient
74
- .listByOrg(
75
- create(ListPlatformClientsByOrgInputSchema, { org }),
76
- )
77
- .then(
78
- (result) => {
79
- if (cancelled.current) return;
80
- setPlatformClients([...result.entries]);
81
- setIsLoading(false);
82
- },
83
- (err) => {
84
- if (cancelled.current) return;
85
- setError(toError(err));
86
- setIsLoading(false);
87
- },
88
- );
89
-
90
- return () => {
91
- cancelled.current = true;
92
- };
93
- }, [org, stigmer, fetchKey]);
94
-
95
- return { platformClients, isLoading, error, refetch };
67
+ return { platformClients, isLoading, isRefetching, error, refetch };
96
68
  }
@@ -17,12 +17,14 @@ import { useStopRunner } from "./useStopRunner";
17
17
  import { useDeleteRunner } from "./useDeleteRunner";
18
18
  import {
19
19
  isActivePhase,
20
+ isTransitionalPhase,
20
21
  phaseLabel,
21
22
  phaseDotColor,
22
23
  PHASE_SORT_ORDER,
23
24
  } from "./phase";
24
25
 
25
26
  const SYSTEM_MANAGED_LABEL = "stigmer.ai/system-managed";
27
+ const TRANSITIONAL_POLL_MS = 5_000;
26
28
 
27
29
  type ConfirmingState = {
28
30
  readonly runnerId: string;
@@ -109,11 +111,21 @@ export function RunnerListPanel({
109
111
  onDeleted,
110
112
  className,
111
113
  }: RunnerListPanelProps) {
114
+ const [hasTransitional, setHasTransitional] = useState(false);
112
115
  const { runners, isLoading, error, refetch } = useRunnerList(org, {
113
116
  includeSystemManaged,
117
+ refetchInterval: hasTransitional ? TRANSITIONAL_POLL_MS : false,
114
118
  });
115
119
  const [confirming, setConfirming] = useState<ConfirmingState>(null);
116
120
 
121
+ useEffect(() => {
122
+ setHasTransitional(
123
+ runners.some((r) =>
124
+ isTransitionalPhase(r.status?.phase ?? RunnerPhase.UNSPECIFIED),
125
+ ),
126
+ );
127
+ }, [runners]);
128
+
117
129
  if (onRefetchRef) {
118
130
  onRefetchRef(refetch);
119
131
  }
@@ -154,7 +166,7 @@ export function RunnerListPanel({
154
166
  {Array.from({ length: 3 }, (_, i) => (
155
167
  <div
156
168
  key={i}
157
- className="bg-muted-subtle h-14 animate-pulse rounded-lg"
169
+ className="bg-muted-subtle h-[4.25rem] animate-pulse rounded-lg"
158
170
  />
159
171
  ))}
160
172
  </div>
@@ -328,54 +340,44 @@ function RunnerRow({
328
340
  );
329
341
  }
330
342
 
343
+ const metaSegments: string[] = [];
344
+ if (hostname) metaSegments.push(hostname);
345
+ if (osArch) metaSegments.push(osArch);
346
+ if (active) metaSegments.push(`${executions} exec${executions !== 1 ? "s" : ""}`);
347
+ if (lastHeartbeat) {
348
+ metaSegments.push(formatRelativeTime(timestampDate(lastHeartbeat)));
349
+ }
350
+
331
351
  return (
332
352
  <div
333
353
  role="listitem"
334
354
  className={cn(
335
- "flex items-center gap-3 rounded-lg border border-border-muted px-3 py-2.5",
355
+ "flex items-start gap-3 rounded-lg border border-border-muted px-3 py-2.5",
336
356
  "hover:border-border transition-colors",
337
357
  !active && "opacity-60",
338
358
  )}
339
359
  >
340
- <RunnerIcon size={14} />
341
-
342
- {/* Name + phase badge */}
343
- <div className="flex min-w-0 flex-1 items-center gap-2">
344
- <span className="truncate text-sm font-medium text-foreground">
345
- {name}
346
- </span>
347
- {systemManaged && (
348
- <span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[0.6rem] font-medium text-muted-foreground">
349
- System
350
- </span>
351
- )}
352
- <PhaseBadge phase={phase} />
353
- </div>
360
+ <RunnerIcon size={16} className="mt-0.5" />
354
361
 
355
- {/* Metadata columns — responsive */}
356
- <div className="hidden items-center gap-4 text-xs text-muted-foreground sm:flex">
357
- {hostname && (
358
- <span className="max-w-[10rem] truncate" title={hostname}>
359
- {hostname}
360
- </span>
361
- )}
362
- {osArch && (
363
- <span className="font-mono text-[0.65rem]">{osArch}</span>
364
- )}
365
- {version && (
366
- <span className="font-mono text-[0.65rem]">v{version}</span>
367
- )}
368
- {active && (
369
- <span title="Current executions">
370
- {executions} exec{executions !== 1 ? "s" : ""}
371
- </span>
372
- )}
373
- {lastHeartbeat && (
374
- <span
375
- title={`Last heartbeat: ${timestampDate(lastHeartbeat).toISOString()}`}
376
- >
377
- {formatRelativeTime(timestampDate(lastHeartbeat))}
362
+ <div className="min-w-0 flex-1">
363
+ {/* Line 1: name + badges + phase */}
364
+ <div className="flex items-center gap-2">
365
+ <span className="truncate text-sm font-medium text-foreground">
366
+ {name}
378
367
  </span>
368
+ {systemManaged && (
369
+ <span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[0.6rem] font-medium text-muted-foreground">
370
+ System
371
+ </span>
372
+ )}
373
+ <PhaseBadge phase={phase} />
374
+ </div>
375
+
376
+ {/* Line 2: metadata */}
377
+ {metaSegments.length > 0 && (
378
+ <p className="mt-0.5 truncate text-[0.65rem] text-muted-foreground">
379
+ {metaSegments.join(" \u00b7 ")}
380
+ </p>
379
381
  )}
380
382
  </div>
381
383
 
@@ -627,7 +629,13 @@ function formatRelativeTime(date: Date): string {
627
629
  // Icons
628
630
  // ---------------------------------------------------------------------------
629
631
 
630
- function RunnerIcon({ size = 14 }: { size?: number }) {
632
+ function RunnerIcon({
633
+ size = 14,
634
+ className,
635
+ }: {
636
+ size?: number;
637
+ className?: string;
638
+ }) {
631
639
  return (
632
640
  <svg
633
641
  width={size}
@@ -639,7 +647,7 @@ function RunnerIcon({ size = 14 }: { size?: number }) {
639
647
  strokeLinecap="round"
640
648
  strokeLinejoin="round"
641
649
  aria-hidden="true"
642
- className="shrink-0 text-muted-foreground"
650
+ className={cn("shrink-0 text-muted-foreground", className)}
643
651
  >
644
652
  <rect x="4" y="4" width="16" height="16" rx="2" />
645
653
  <rect x="9" y="9" width="6" height="6" />
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { RunnerPhase } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/enum_pb";
3
+ import { isTransitionalPhase, isActivePhase } from "../phase";
4
+
5
+ describe("isTransitionalPhase", () => {
6
+ it("returns true for PENDING", () => {
7
+ expect(isTransitionalPhase(RunnerPhase.PENDING)).toBe(true);
8
+ });
9
+
10
+ it.each([
11
+ ["READY", RunnerPhase.READY],
12
+ ["BUSY", RunnerPhase.BUSY],
13
+ ["STOPPED", RunnerPhase.STOPPED],
14
+ ["FAILED", RunnerPhase.FAILED],
15
+ ["UNSPECIFIED", RunnerPhase.UNSPECIFIED],
16
+ ] as const)("returns false for %s", (_label, phase) => {
17
+ expect(isTransitionalPhase(phase)).toBe(false);
18
+ });
19
+
20
+ it("is disjoint from isActivePhase", () => {
21
+ const allPhases = [
22
+ RunnerPhase.READY,
23
+ RunnerPhase.BUSY,
24
+ RunnerPhase.PENDING,
25
+ RunnerPhase.STOPPED,
26
+ RunnerPhase.FAILED,
27
+ RunnerPhase.UNSPECIFIED,
28
+ ];
29
+ for (const phase of allPhases) {
30
+ if (isTransitionalPhase(phase)) {
31
+ expect(isActivePhase(phase)).toBe(false);
32
+ }
33
+ }
34
+ });
35
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import {
6
+ RunnerSchema,
7
+ RunnerStatusSchema,
8
+ } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
9
+ import { RunnerPhase } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/enum_pb";
10
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
11
+ import type { Stigmer } from "@stigmer/sdk";
12
+ import { StigmerContext } from "../../context";
13
+ import { useRunnerList } from "../useRunnerList";
14
+
15
+ function makeRunner(id: string, name: string, phase: RunnerPhase) {
16
+ const runner = create(RunnerSchema);
17
+ runner.metadata = create(ApiResourceMetadataSchema);
18
+ runner.metadata.id = id;
19
+ runner.metadata.name = name;
20
+ runner.status = create(RunnerStatusSchema);
21
+ runner.status.phase = phase;
22
+ return runner;
23
+ }
24
+
25
+ function buildMockClient(listMock: ReturnType<typeof vi.fn>) {
26
+ return {
27
+ runner: { list: listMock },
28
+ } as unknown as Stigmer;
29
+ }
30
+
31
+ function makeWrapper(client: Stigmer) {
32
+ return ({ children }: { children: ReactNode }) => (
33
+ <StigmerContext.Provider value={client}>
34
+ {children}
35
+ </StigmerContext.Provider>
36
+ );
37
+ }
38
+
39
+ async function flush(): Promise<void> {
40
+ await act(async () => {
41
+ await Promise.resolve();
42
+ });
43
+ }
44
+
45
+ describe("useRunnerList — refetchInterval", () => {
46
+ let listMock: ReturnType<typeof vi.fn>;
47
+ let client: Stigmer;
48
+
49
+ beforeEach(() => {
50
+ vi.useFakeTimers();
51
+ listMock = vi.fn();
52
+ client = buildMockClient(listMock);
53
+ });
54
+
55
+ afterEach(() => {
56
+ vi.useRealTimers();
57
+ });
58
+
59
+ it("polls when refetchInterval is set", async () => {
60
+ const runners = [makeRunner("r1", "dev", RunnerPhase.PENDING)];
61
+ listMock.mockResolvedValue({ items: runners });
62
+
63
+ const { result } = renderHook(
64
+ () => useRunnerList("acme", { refetchInterval: 2000 }),
65
+ { wrapper: makeWrapper(client) },
66
+ );
67
+
68
+ // Initial fetch.
69
+ await flush();
70
+ expect(listMock).toHaveBeenCalledTimes(1);
71
+ expect(result.current.runners).toHaveLength(1);
72
+
73
+ // Advance by one interval.
74
+ await act(async () => {
75
+ await vi.advanceTimersByTimeAsync(2000);
76
+ });
77
+ expect(listMock).toHaveBeenCalledTimes(2);
78
+ });
79
+
80
+ it("does not poll when refetchInterval is false", async () => {
81
+ listMock.mockResolvedValue({ items: [] });
82
+
83
+ renderHook(
84
+ () => useRunnerList("acme", { refetchInterval: false }),
85
+ { wrapper: makeWrapper(client) },
86
+ );
87
+
88
+ await flush();
89
+ expect(listMock).toHaveBeenCalledTimes(1);
90
+
91
+ await act(async () => {
92
+ await vi.advanceTimersByTimeAsync(10_000);
93
+ });
94
+ expect(listMock).toHaveBeenCalledTimes(1);
95
+ });
96
+ });
@@ -11,6 +11,12 @@ export type {
11
11
  LaunchLocalRunnerResult,
12
12
  } from "./useLaunchLocalRunner";
13
13
 
14
+ export { useRunnerCredential } from "./useRunnerCredential";
15
+ export type {
16
+ RunnerCredential,
17
+ UseRunnerCredentialReturn,
18
+ } from "./useRunnerCredential";
19
+
14
20
  export { useStopRunner } from "./useStopRunner";
15
21
  export type {
16
22
  StopRunnerInput,
@@ -30,5 +36,6 @@ export {
30
36
  phaseLabel,
31
37
  phaseDotColor,
32
38
  isActivePhase,
39
+ isTransitionalPhase,
33
40
  PHASE_SORT_ORDER,
34
41
  } from "./phase";
@@ -60,3 +60,19 @@ export function phaseDotColor(phase: RunnerPhase): string {
60
60
  export function isActivePhase(phase: RunnerPhase): boolean {
61
61
  return phase === RunnerPhase.READY || phase === RunnerPhase.BUSY;
62
62
  }
63
+
64
+ /**
65
+ * Whether the runner is in a transitional phase whose status is
66
+ * expected to change soon without user action.
67
+ *
68
+ * Currently only `PENDING` — the runner has registered but hasn't
69
+ * completed its startup handshake yet. This is distinct from
70
+ * {@link isActivePhase} (READY | BUSY) where the runner is stable
71
+ * and accepting work.
72
+ *
73
+ * Useful for driving conditional polling: poll while transitional
74
+ * runners exist, stop when all runners reach a stable state.
75
+ */
76
+ export function isTransitionalPhase(phase: RunnerPhase): boolean {
77
+ return phase === RunnerPhase.PENDING;
78
+ }
@@ -0,0 +1,89 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import type { Stigmer } from "@stigmer/sdk";
5
+ import { useStigmer } from "../hooks";
6
+
7
+ /**
8
+ * Resolved credential bundle for starting a runner sidecar process.
9
+ *
10
+ * Contains everything a CLI sidecar needs to authenticate with the
11
+ * Stigmer backend — the Bearer token (Auth0 JWT, PlatformClient JWT,
12
+ * or API key), the API endpoint, and the organization scope.
13
+ */
14
+ export interface RunnerCredential {
15
+ /** Bearer token or API key. `null` when no credential is available. */
16
+ readonly token: string | null;
17
+ /** Base URL of the Stigmer API server (e.g., "https://api.stigmer.ai"). */
18
+ readonly endpoint: string;
19
+ /** Organization slug, if provided. */
20
+ readonly org?: string;
21
+ }
22
+
23
+ /** Return value of {@link useRunnerCredential}. */
24
+ export interface UseRunnerCredentialReturn {
25
+ /**
26
+ * Resolve the current authentication credential for a runner sidecar.
27
+ *
28
+ * Reads the credential from the {@link Stigmer} client configured in
29
+ * the nearest `StigmerProvider`. Works with any auth mode: static API
30
+ * key, dynamic token provider (Auth0, PlatformClient), or custom
31
+ * transport.
32
+ *
33
+ * @param org - Optional organization slug to include in the result.
34
+ * @returns The credential bundle for the sidecar process.
35
+ */
36
+ readonly getCredential: (org?: string) => Promise<RunnerCredential>;
37
+ }
38
+
39
+ /**
40
+ * Behavior hook that resolves the current auth credential for passing
41
+ * to a runner sidecar process.
42
+ *
43
+ * This is the SDK-level abstraction for starting runners from within
44
+ * a desktop or native application — where the app already holds the
45
+ * credential and needs to hand it to a CLI child process. It avoids
46
+ * the `createLaunchToken` / `exchangeLaunchToken` round-trip, which
47
+ * is only needed for cross-process browser-to-desktop handshakes
48
+ * (see {@link useLaunchLocalRunner} for that scenario).
49
+ *
50
+ * The hook reads from `useStigmer()` and calls
51
+ * `client.getAuthCredential()`, so it works identically regardless
52
+ * of auth mode (Auth0 JWT, PlatformClient JWT, API key).
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * function StartRunnerButton({ org }: { org: string }) {
57
+ * const { getCredential } = useRunnerCredential();
58
+ *
59
+ * const handleStart = async () => {
60
+ * const cred = await getCredential(org);
61
+ * // Pass cred.token, cred.endpoint, cred.org to your sidecar
62
+ * await nativeBridge.startRunner({
63
+ * token: cred.token,
64
+ * endpoint: cred.endpoint,
65
+ * org: cred.org,
66
+ * });
67
+ * };
68
+ *
69
+ * return <button onClick={handleStart}>Start Runner</button>;
70
+ * }
71
+ * ```
72
+ */
73
+ export function useRunnerCredential(): UseRunnerCredentialReturn {
74
+ const stigmer = useStigmer();
75
+
76
+ const getCredential = useCallback(
77
+ async (org?: string): Promise<RunnerCredential> => {
78
+ const token = await stigmer.getAuthCredential();
79
+ return {
80
+ token,
81
+ endpoint: stigmer.baseUrl,
82
+ org,
83
+ };
84
+ },
85
+ [stigmer],
86
+ );
87
+
88
+ return { getCredential };
89
+ }
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import { create } from "@bufbuild/protobuf";
5
4
  import type { Runner } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
6
5
  import { ListRunnersRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/io_pb";
7
6
  import { useStigmer } from "../hooks";
8
- import { toError } from "../internal/toError";
7
+ import { useFetch, type UseFetchOptions } from "../internal/useFetch";
9
8
 
10
9
  const SYSTEM_MANAGED_LABEL = "stigmer.ai/system-managed";
11
10
 
@@ -21,6 +20,24 @@ export interface UseRunnerListOptions {
21
20
  * @default false
22
21
  */
23
22
  readonly includeSystemManaged?: boolean;
23
+ /**
24
+ * Poll interval in milliseconds for automatic re-fetching.
25
+ *
26
+ * When set to a positive number, the hook re-fetches the runner list
27
+ * on a timer. Useful for monitoring status transitions (e.g. Pending
28
+ * to Ready) without requiring the user to navigate away and back.
29
+ *
30
+ * Set to `false` or `0` to disable polling (the default).
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // Poll every 5 seconds while runners are transitioning
35
+ * const { runners } = useRunnerList("acme", {
36
+ * refetchInterval: hasPendingRunners ? 5000 : false,
37
+ * });
38
+ * ```
39
+ */
40
+ readonly refetchInterval?: UseFetchOptions["refetchInterval"];
24
41
  }
25
42
 
26
43
  /** Return value of {@link useRunnerList}. */
@@ -29,6 +46,8 @@ export interface UseRunnerListReturn {
29
46
  readonly runners: readonly Runner[];
30
47
  /** `true` while the fetch is in flight. */
31
48
  readonly isLoading: boolean;
49
+ /** `true` while a background refetch is in flight and stale data is shown. */
50
+ readonly isRefetching: boolean;
32
51
  /** Error from the last failed fetch, or `null` when healthy. */
33
52
  readonly error: Error | null;
34
53
  /** Discard cached data and re-fetch the runner list from the server. */
@@ -62,53 +81,26 @@ export function useRunnerList(
62
81
  options?: UseRunnerListOptions,
63
82
  ): UseRunnerListReturn {
64
83
  const stigmer = useStigmer();
65
- const [runners, setRunners] = useState<Runner[]>([]);
66
- const [isLoading, setIsLoading] = useState(false);
67
- const [error, setError] = useState<Error | null>(null);
68
- const [fetchKey, setFetchKey] = useState(0);
69
-
70
84
  const includeSystemManaged = options?.includeSystemManaged ?? false;
85
+ const refetchInterval = options?.refetchInterval;
71
86
 
72
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
73
-
74
- useEffect(() => {
75
- if (!org) {
76
- setRunners([]);
77
- setIsLoading(false);
78
- setError(null);
79
- return;
80
- }
81
-
82
- const cancelled = { current: false };
83
- setIsLoading(true);
84
- setError(null);
85
-
86
- stigmer.runner
87
- .list(create(ListRunnersRequestSchema, { org }))
88
- .then(
89
- (result) => {
90
- if (cancelled.current) return;
91
-
92
- const items = includeSystemManaged
87
+ const { data: runners, isLoading, isRefetching, error, refetch } = useFetch(
88
+ org
89
+ ? async () => {
90
+ const result = await stigmer.runner.list(
91
+ create(ListRunnersRequestSchema, { org }),
92
+ );
93
+ return includeSystemManaged
93
94
  ? result.items
94
95
  : result.items.filter(
95
96
  (r) => r.metadata?.labels[SYSTEM_MANAGED_LABEL] !== "true",
96
97
  );
98
+ }
99
+ : null,
100
+ [stigmer, org, includeSystemManaged],
101
+ [] as Runner[],
102
+ { refetchInterval },
103
+ );
97
104
 
98
- setRunners(items);
99
- setIsLoading(false);
100
- },
101
- (err) => {
102
- if (cancelled.current) return;
103
- setError(toError(err));
104
- setIsLoading(false);
105
- },
106
- );
107
-
108
- return () => {
109
- cancelled.current = true;
110
- };
111
- }, [stigmer, org, includeSystemManaged, fetchKey]);
112
-
113
- return { runners, isLoading, error, refetch };
105
+ return { runners, isLoading, isRefetching, error, refetch };
114
106
  }