@stigmer/react 0.0.92 → 0.0.93
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.
- package/README.md +57 -12
- package/agent/AgentDetailView.js +7 -7
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/AgentPicker.js +3 -3
- package/agent/AgentPicker.js.map +1 -1
- package/api-key/ApiKeyCreatedAlert.js +1 -1
- package/api-key/ApiKeyCreatedAlert.js.map +1 -1
- package/api-key/ApiKeyListPanel.js +4 -4
- package/api-key/ApiKeyListPanel.js.map +1 -1
- package/api-key/CreateApiKeyForm.js +1 -1
- package/api-key/CreateApiKeyForm.js.map +1 -1
- package/attachment/AttachmentChipList.js +2 -2
- package/attachment/AttachmentChipList.js.map +1 -1
- package/color-mode.d.ts +48 -0
- package/color-mode.d.ts.map +1 -0
- package/color-mode.js +53 -0
- package/color-mode.js.map +1 -0
- package/composer/ComposerToolbar.d.ts +6 -2
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +5 -3
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/ConfigureMenu.js +3 -3
- package/composer/ConfigureMenu.js.map +1 -1
- package/composer/ContextChip.js +1 -1
- package/composer/ContextChip.js.map +1 -1
- package/composer/ContextPopover.js +1 -1
- package/composer/ContextPopover.js.map +1 -1
- package/composer/SessionComposer.d.ts +24 -3
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +5 -4
- package/composer/SessionComposer.js.map +1 -1
- package/environment/CreateEnvironmentForm.js +1 -1
- package/environment/CreateEnvironmentForm.js.map +1 -1
- package/environment/EnvVarForm.js +2 -2
- package/environment/EnvVarForm.js.map +1 -1
- package/environment/EnvironmentListPanel.js +2 -2
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/environment/EnvironmentVariableEditor.js +6 -6
- package/environment/EnvironmentVariableEditor.js.map +1 -1
- package/error/ErrorMessage.js +1 -1
- package/error/ErrorMessage.js.map +1 -1
- package/execution/ApprovalCard.js +4 -4
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ArtifactCard.js +1 -1
- package/execution/ArtifactCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.js +4 -4
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/FollowUpInput.js +1 -1
- package/execution/FollowUpInput.js.map +1 -1
- package/execution/McpToolDetail.js +4 -4
- package/execution/McpToolDetail.js.map +1 -1
- package/execution/MessageEntry.js +1 -1
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.js +1 -1
- package/execution/MessageThread.js.map +1 -1
- package/execution/SessionVariablesInput.js +4 -4
- package/execution/SessionVariablesInput.js.map +1 -1
- package/execution/SubAgentSection.js +2 -2
- package/execution/SubAgentSection.js.map +1 -1
- package/execution/ToolCallDetail.js +2 -2
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/ToolCallGroup.js +1 -1
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.js +2 -2
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/WriteBackCard.js +3 -3
- package/execution/WriteBackCard.js.map +1 -1
- package/execution/tool-rendering-primitives.js +3 -3
- package/execution/tool-rendering-primitives.js.map +1 -1
- package/github/GitHubRepoPicker.js +5 -5
- package/github/GitHubRepoPicker.js.map +1 -1
- package/iam-policy/GrantAccessForm.js +1 -1
- package/iam-policy/GrantAccessForm.js.map +1 -1
- package/iam-policy/OrgMembersPanel.js +5 -5
- package/iam-policy/OrgMembersPanel.js.map +1 -1
- package/identity-provider/CreateIdentityProviderForm.js +3 -3
- package/identity-provider/CreateIdentityProviderForm.js.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +5 -5
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/identity-provider/IdentityProviderListPanel.js +3 -3
- package/identity-provider/IdentityProviderListPanel.js.map +1 -1
- package/identity-provider/IdentityProviderWizard.js +7 -7
- package/identity-provider/IdentityProviderWizard.js.map +1 -1
- package/identity-provider/ProviderPicker.js +2 -2
- package/identity-provider/ProviderPicker.js.map +1 -1
- package/index.d.ts +8 -4
- package/index.d.ts.map +1 -1
- package/index.js +7 -3
- package/index.js.map +1 -1
- package/internal/CloudFeatureNotice.js +1 -1
- package/internal/CloudFeatureNotice.js.map +1 -1
- package/internal/Tabs.js +1 -1
- package/internal/Tabs.js.map +1 -1
- package/internal/markdown-components.js +2 -2
- package/internal/markdown-components.js.map +1 -1
- package/invitation/InvitationCreatedAlert.js +1 -1
- package/invitation/InvitationCreatedAlert.js.map +1 -1
- package/invitation/InvitationManager.js +5 -5
- package/invitation/InvitationManager.js.map +1 -1
- package/invitation/InvitationRedemption.js +4 -4
- package/invitation/InvitationRedemption.js.map +1 -1
- package/library/ResourceCountCard.js +1 -1
- package/library/ResourceCountCard.js.map +1 -1
- package/library/ResourceListView.js +5 -5
- package/library/ResourceListView.js.map +1 -1
- package/mcp-server/McpServerConfigPanel.js +5 -5
- package/mcp-server/McpServerConfigPanel.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +4 -4
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/McpServerDetailView.js +14 -14
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.js +4 -4
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/McpToolSelector.js +3 -3
- package/mcp-server/McpToolSelector.js.map +1 -1
- package/mcp-server/OAuthAppForm.js +1 -1
- package/mcp-server/OAuthAppForm.js.map +1 -1
- package/models/ModelSelector.js +1 -1
- package/models/ModelSelector.js.map +1 -1
- package/oauth-app/CreateOAuthAppForm.js +1 -1
- package/oauth-app/CreateOAuthAppForm.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +3 -3
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/oauth-app/OAuthAppListPanel.js +2 -2
- package/oauth-app/OAuthAppListPanel.js.map +1 -1
- package/organization/CreateOrganizationForm.js +1 -1
- package/organization/CreateOrganizationForm.js.map +1 -1
- package/organization/OrgProfilePanel.js +3 -3
- package/organization/OrgProfilePanel.js.map +1 -1
- package/package.json +4 -4
- package/platform-client/CreatePlatformClientForm.js +2 -2
- package/platform-client/CreatePlatformClientForm.js.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +4 -4
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/platform-client/PlatformClientListPanel.js +3 -3
- package/platform-client/PlatformClientListPanel.js.map +1 -1
- package/platform-client/PlatformClientSecretAlert.js +2 -2
- package/platform-client/PlatformClientSecretAlert.js.map +1 -1
- package/provider.d.ts +39 -2
- package/provider.d.ts.map +1 -1
- package/provider.js +11 -3
- package/provider.js.map +1 -1
- package/runner/RunnerListPanel.d.ts +65 -0
- package/runner/RunnerListPanel.d.ts.map +1 -0
- package/runner/RunnerListPanel.js +237 -0
- package/runner/RunnerListPanel.js.map +1 -0
- package/runner/RunnerPicker.d.ts +54 -0
- package/runner/RunnerPicker.d.ts.map +1 -0
- package/runner/RunnerPicker.js +133 -0
- package/runner/RunnerPicker.js.map +1 -0
- package/runner/__tests__/useDeleteRunner.test.d.ts +2 -0
- package/runner/__tests__/useDeleteRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useDeleteRunner.test.js +108 -0
- package/runner/__tests__/useDeleteRunner.test.js.map +1 -0
- package/runner/__tests__/useLaunchLocalRunner.test.d.ts +2 -0
- package/runner/__tests__/useLaunchLocalRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useLaunchLocalRunner.test.js +143 -0
- package/runner/__tests__/useLaunchLocalRunner.test.js.map +1 -0
- package/runner/__tests__/useStopRunner.test.d.ts +2 -0
- package/runner/__tests__/useStopRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useStopRunner.test.js +114 -0
- package/runner/__tests__/useStopRunner.test.js.map +1 -0
- package/runner/index.d.ts +14 -0
- package/runner/index.d.ts.map +1 -0
- package/runner/index.js +8 -0
- package/runner/index.js.map +1 -0
- package/runner/phase.d.ts +30 -0
- package/runner/phase.d.ts.map +1 -0
- package/runner/phase.js +58 -0
- package/runner/phase.js.map +1 -0
- package/runner/useDeleteRunner.d.ts +36 -0
- package/runner/useDeleteRunner.d.ts.map +1 -0
- package/runner/useDeleteRunner.js +42 -0
- package/runner/useDeleteRunner.js.map +1 -0
- package/runner/useLaunchLocalRunner.d.ts +84 -0
- package/runner/useLaunchLocalRunner.d.ts.map +1 -0
- package/runner/useLaunchLocalRunner.js +75 -0
- package/runner/useLaunchLocalRunner.js.map +1 -0
- package/runner/useRunnerList.d.ts +49 -0
- package/runner/useRunnerList.d.ts.map +1 -0
- package/runner/useRunnerList.js +70 -0
- package/runner/useRunnerList.js.map +1 -0
- package/runner/useStopRunner.d.ts +53 -0
- package/runner/useStopRunner.d.ts.map +1 -0
- package/runner/useStopRunner.js +50 -0
- package/runner/useStopRunner.js.map +1 -0
- package/session/draft.d.ts +53 -0
- package/session/draft.d.ts.map +1 -0
- package/session/draft.js +45 -0
- package/session/draft.js.map +1 -0
- package/session/index.d.ts +10 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +5 -0
- package/session/index.js.map +1 -1
- package/session/useCreateSession.d.ts +8 -0
- package/session/useCreateSession.d.ts.map +1 -1
- package/session/useCreateSession.js +1 -0
- package/session/useCreateSession.js.map +1 -1
- package/session/useEditSessionPrep.d.ts +26 -0
- package/session/useEditSessionPrep.d.ts.map +1 -0
- package/session/useEditSessionPrep.js +83 -0
- package/session/useEditSessionPrep.js.map +1 -0
- package/session/useNewSessionFlow.d.ts +110 -0
- package/session/useNewSessionFlow.d.ts.map +1 -0
- package/session/useNewSessionFlow.js +184 -0
- package/session/useNewSessionFlow.js.map +1 -0
- package/session/usePersistedModel.d.ts +18 -0
- package/session/usePersistedModel.d.ts.map +1 -0
- package/session/usePersistedModel.js +31 -0
- package/session/usePersistedModel.js.map +1 -0
- package/session/useSessionPageFlow.d.ts +104 -0
- package/session/useSessionPageFlow.d.ts.map +1 -0
- package/session/useSessionPageFlow.js +172 -0
- package/session/useSessionPageFlow.js.map +1 -0
- package/skill/SkillDetailView.js +3 -3
- package/skill/SkillDetailView.js.map +1 -1
- package/skill/SkillPicker.js +3 -3
- package/skill/SkillPicker.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +8 -8
- package/src/agent/AgentPicker.tsx +3 -3
- package/src/api-key/ApiKeyCreatedAlert.tsx +2 -2
- package/src/api-key/ApiKeyListPanel.tsx +6 -6
- package/src/api-key/CreateApiKeyForm.tsx +2 -2
- package/src/attachment/AttachmentChipList.tsx +3 -3
- package/src/color-mode.ts +75 -0
- package/src/composer/ComposerToolbar.tsx +29 -7
- package/src/composer/ConfigureMenu.tsx +6 -6
- package/src/composer/ContextChip.tsx +1 -1
- package/src/composer/ContextPopover.tsx +2 -2
- package/src/composer/SessionComposer.tsx +34 -5
- package/src/environment/CreateEnvironmentForm.tsx +3 -3
- package/src/environment/EnvVarForm.tsx +6 -6
- package/src/environment/EnvironmentListPanel.tsx +3 -3
- package/src/environment/EnvironmentVariableEditor.tsx +7 -7
- package/src/error/ErrorMessage.tsx +5 -5
- package/src/execution/ApprovalCard.tsx +5 -5
- package/src/execution/ArtifactCard.tsx +2 -2
- package/src/execution/ArtifactPreviewModal.tsx +4 -4
- package/src/execution/FollowUpInput.tsx +2 -2
- package/src/execution/McpToolDetail.tsx +4 -4
- package/src/execution/MessageEntry.tsx +1 -1
- package/src/execution/MessageThread.tsx +1 -1
- package/src/execution/SessionVariablesInput.tsx +7 -7
- package/src/execution/SubAgentSection.tsx +5 -5
- package/src/execution/ToolCallDetail.tsx +2 -2
- package/src/execution/ToolCallGroup.tsx +3 -3
- package/src/execution/ToolCallItem.tsx +4 -4
- package/src/execution/WriteBackCard.tsx +5 -5
- package/src/execution/tool-rendering-primitives.tsx +5 -5
- package/src/github/GitHubRepoPicker.tsx +5 -5
- package/src/iam-policy/GrantAccessForm.tsx +2 -2
- package/src/iam-policy/OrgMembersPanel.tsx +11 -11
- package/src/identity-provider/CreateIdentityProviderForm.tsx +4 -4
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +7 -7
- package/src/identity-provider/IdentityProviderListPanel.tsx +7 -7
- package/src/identity-provider/IdentityProviderWizard.tsx +8 -8
- package/src/identity-provider/ProviderPicker.tsx +2 -2
- package/src/index.ts +46 -7
- package/src/internal/CloudFeatureNotice.tsx +1 -1
- package/src/internal/Tabs.tsx +1 -1
- package/src/internal/markdown-components.tsx +2 -2
- package/src/invitation/InvitationCreatedAlert.tsx +2 -2
- package/src/invitation/InvitationManager.tsx +9 -9
- package/src/invitation/InvitationRedemption.tsx +11 -11
- package/src/library/ResourceCountCard.tsx +1 -1
- package/src/library/ResourceListView.tsx +7 -7
- package/src/mcp-server/McpServerConfigPanel.tsx +7 -7
- package/src/mcp-server/McpServerConnectDialog.tsx +5 -5
- package/src/mcp-server/McpServerDetailView.tsx +19 -19
- package/src/mcp-server/McpServerPicker.tsx +6 -6
- package/src/mcp-server/McpToolSelector.tsx +4 -4
- package/src/mcp-server/OAuthAppForm.tsx +3 -3
- package/src/models/ModelSelector.tsx +1 -1
- package/src/oauth-app/CreateOAuthAppForm.tsx +3 -3
- package/src/oauth-app/OAuthAppDetailPanel.tsx +7 -7
- package/src/oauth-app/OAuthAppListPanel.tsx +3 -3
- package/src/organization/CreateOrganizationForm.tsx +3 -3
- package/src/organization/OrgProfilePanel.tsx +4 -5
- package/src/platform-client/CreatePlatformClientForm.tsx +6 -6
- package/src/platform-client/PlatformClientDetailPanel.tsx +19 -19
- package/src/platform-client/PlatformClientListPanel.tsx +7 -7
- package/src/platform-client/PlatformClientSecretAlert.tsx +2 -2
- package/src/provider.tsx +52 -2
- package/src/runner/RunnerListPanel.tsx +725 -0
- package/src/runner/RunnerPicker.tsx +319 -0
- package/src/runner/__tests__/useDeleteRunner.test.tsx +150 -0
- package/src/runner/__tests__/useLaunchLocalRunner.test.tsx +223 -0
- package/src/runner/__tests__/useStopRunner.test.tsx +154 -0
- package/src/runner/index.ts +34 -0
- package/src/runner/phase.ts +62 -0
- package/src/runner/useDeleteRunner.ts +67 -0
- package/src/runner/useLaunchLocalRunner.ts +139 -0
- package/src/runner/useRunnerList.ts +114 -0
- package/src/runner/useStopRunner.ts +92 -0
- package/src/session/draft.ts +82 -0
- package/src/session/index.ts +28 -0
- package/src/session/useCreateSession.ts +9 -0
- package/src/session/useEditSessionPrep.ts +111 -0
- package/src/session/useNewSessionFlow.ts +283 -0
- package/src/session/usePersistedModel.ts +41 -0
- package/src/session/useSessionPageFlow.ts +280 -0
- package/src/skill/SkillDetailView.tsx +5 -5
- package/src/skill/SkillPicker.tsx +3 -3
- package/src/styles.css +25 -1
- package/src/usage/OrgUsagePanel.tsx +4 -4
- package/src/workspace/WorkspaceEditor.tsx +78 -66
- package/src/workspace/index.ts +0 -8
- package/styles.css +1 -1
- package/usage/OrgUsagePanel.js +2 -2
- package/usage/OrgUsagePanel.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts +28 -3
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +24 -25
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/index.d.ts +0 -4
- package/workspace/index.d.ts.map +1 -1
- package/workspace/index.js +0 -2
- package/workspace/index.js.map +1 -1
- package/src/workspace/FolderBrowser.tsx +0 -579
- package/src/workspace/useFolderListing.ts +0 -164
- package/workspace/FolderBrowser.d.ts +0 -37
- package/workspace/FolderBrowser.d.ts.map +0 -1
- package/workspace/FolderBrowser.js +0 -188
- package/workspace/FolderBrowser.js.map +0 -1
- package/workspace/useFolderListing.d.ts +0 -73
- package/workspace/useFolderListing.d.ts.map +0 -1
- package/workspace/useFolderListing.js +0 -110
- package/workspace/useFolderListing.js.map +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } 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
|
+
type Runner,
|
|
9
|
+
} from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
|
|
10
|
+
import { RunnerPhase } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/enum_pb";
|
|
11
|
+
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
12
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
13
|
+
import { StigmerContext } from "../../context";
|
|
14
|
+
import { useStopRunner } from "../useStopRunner";
|
|
15
|
+
|
|
16
|
+
function makeRunner(id: string, phase: RunnerPhase): Runner {
|
|
17
|
+
const runner = create(RunnerSchema);
|
|
18
|
+
runner.metadata = create(ApiResourceMetadataSchema);
|
|
19
|
+
runner.metadata.id = id;
|
|
20
|
+
runner.status = create(RunnerStatusSchema);
|
|
21
|
+
runner.status.phase = phase;
|
|
22
|
+
return runner;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildMockClient(overrides: {
|
|
26
|
+
stop?: ReturnType<typeof vi.fn>;
|
|
27
|
+
} = {}) {
|
|
28
|
+
return {
|
|
29
|
+
runner: {
|
|
30
|
+
stop: overrides.stop ?? vi.fn(),
|
|
31
|
+
},
|
|
32
|
+
} as unknown as Stigmer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeWrapper(client: Stigmer) {
|
|
36
|
+
return ({ children }: { children: ReactNode }) => (
|
|
37
|
+
<StigmerContext.Provider value={client}>
|
|
38
|
+
{children}
|
|
39
|
+
</StigmerContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("useStopRunner", () => {
|
|
44
|
+
let stopMock: ReturnType<typeof vi.fn>;
|
|
45
|
+
let client: Stigmer;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
stopMock = vi.fn();
|
|
49
|
+
client = buildMockClient({ stop: stopMock });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("calls runner.stop with correct proto message and returns the runner", async () => {
|
|
53
|
+
const stoppedRunner = makeRunner("rnr_1", RunnerPhase.STOPPED);
|
|
54
|
+
stopMock.mockResolvedValueOnce(stoppedRunner);
|
|
55
|
+
|
|
56
|
+
const { result } = renderHook(() => useStopRunner(), {
|
|
57
|
+
wrapper: makeWrapper(client),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(result.current.isStopping).toBe(false);
|
|
61
|
+
expect(result.current.error).toBeNull();
|
|
62
|
+
|
|
63
|
+
let returned: Runner;
|
|
64
|
+
await act(async () => {
|
|
65
|
+
returned = await result.current.stop({
|
|
66
|
+
runnerId: "rnr_1",
|
|
67
|
+
reason: "user requested",
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(stopMock).toHaveBeenCalledOnce();
|
|
72
|
+
const protoArg = stopMock.mock.calls[0][0];
|
|
73
|
+
expect(protoArg.runnerId).toBe("rnr_1");
|
|
74
|
+
expect(protoArg.reason).toBe("user requested");
|
|
75
|
+
|
|
76
|
+
expect(returned!).toBe(stoppedRunner);
|
|
77
|
+
expect(result.current.isStopping).toBe(false);
|
|
78
|
+
expect(result.current.error).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("defaults reason to empty string when omitted", async () => {
|
|
82
|
+
const stoppedRunner = makeRunner("rnr_2", RunnerPhase.STOPPED);
|
|
83
|
+
stopMock.mockResolvedValueOnce(stoppedRunner);
|
|
84
|
+
|
|
85
|
+
const { result } = renderHook(() => useStopRunner(), {
|
|
86
|
+
wrapper: makeWrapper(client),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await act(async () => {
|
|
90
|
+
await result.current.stop({ runnerId: "rnr_2" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const protoArg = stopMock.mock.calls[0][0];
|
|
94
|
+
expect(protoArg.reason).toBe("");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("sets error and rethrows on failure", async () => {
|
|
98
|
+
const rpcError = new Error("runner not found");
|
|
99
|
+
stopMock.mockRejectedValueOnce(rpcError);
|
|
100
|
+
|
|
101
|
+
const { result } = renderHook(() => useStopRunner(), {
|
|
102
|
+
wrapper: makeWrapper(client),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await act(async () => {
|
|
106
|
+
await expect(
|
|
107
|
+
result.current.stop({ runnerId: "rnr_missing" }),
|
|
108
|
+
).rejects.toThrow("runner not found");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
112
|
+
expect(result.current.error!.message).toBe("runner not found");
|
|
113
|
+
expect(result.current.isStopping).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("clears error via clearError", async () => {
|
|
117
|
+
stopMock.mockRejectedValueOnce(new Error("fail"));
|
|
118
|
+
|
|
119
|
+
const { result } = renderHook(() => useStopRunner(), {
|
|
120
|
+
wrapper: makeWrapper(client),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await act(async () => {
|
|
124
|
+
await result.current.stop({ runnerId: "rnr_1" }).catch(() => {});
|
|
125
|
+
});
|
|
126
|
+
expect(result.current.error).not.toBeNull();
|
|
127
|
+
|
|
128
|
+
act(() => {
|
|
129
|
+
result.current.clearError();
|
|
130
|
+
});
|
|
131
|
+
expect(result.current.error).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("resets previous error on a new successful stop", async () => {
|
|
135
|
+
stopMock.mockRejectedValueOnce(new Error("first fail"));
|
|
136
|
+
|
|
137
|
+
const { result } = renderHook(() => useStopRunner(), {
|
|
138
|
+
wrapper: makeWrapper(client),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await act(async () => {
|
|
142
|
+
await result.current.stop({ runnerId: "rnr_1" }).catch(() => {});
|
|
143
|
+
});
|
|
144
|
+
expect(result.current.error).not.toBeNull();
|
|
145
|
+
|
|
146
|
+
const stoppedRunner = makeRunner("rnr_1", RunnerPhase.STOPPED);
|
|
147
|
+
stopMock.mockResolvedValueOnce(stoppedRunner);
|
|
148
|
+
|
|
149
|
+
await act(async () => {
|
|
150
|
+
await result.current.stop({ runnerId: "rnr_1" });
|
|
151
|
+
});
|
|
152
|
+
expect(result.current.error).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export { useRunnerList } from "./useRunnerList";
|
|
2
|
+
export type {
|
|
3
|
+
UseRunnerListOptions,
|
|
4
|
+
UseRunnerListReturn,
|
|
5
|
+
} from "./useRunnerList";
|
|
6
|
+
|
|
7
|
+
export { useLaunchLocalRunner } from "./useLaunchLocalRunner";
|
|
8
|
+
export type {
|
|
9
|
+
UseLaunchLocalRunnerOptions,
|
|
10
|
+
UseLaunchLocalRunnerReturn,
|
|
11
|
+
LaunchLocalRunnerResult,
|
|
12
|
+
} from "./useLaunchLocalRunner";
|
|
13
|
+
|
|
14
|
+
export { useStopRunner } from "./useStopRunner";
|
|
15
|
+
export type {
|
|
16
|
+
StopRunnerInput,
|
|
17
|
+
UseStopRunnerReturn,
|
|
18
|
+
} from "./useStopRunner";
|
|
19
|
+
|
|
20
|
+
export { useDeleteRunner } from "./useDeleteRunner";
|
|
21
|
+
export type { UseDeleteRunnerReturn } from "./useDeleteRunner";
|
|
22
|
+
|
|
23
|
+
export { RunnerPicker } from "./RunnerPicker";
|
|
24
|
+
export type { RunnerPickerProps } from "./RunnerPicker";
|
|
25
|
+
|
|
26
|
+
export { RunnerListPanel } from "./RunnerListPanel";
|
|
27
|
+
export type { RunnerListPanelProps } from "./RunnerListPanel";
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
phaseLabel,
|
|
31
|
+
phaseDotColor,
|
|
32
|
+
isActivePhase,
|
|
33
|
+
PHASE_SORT_ORDER,
|
|
34
|
+
} from "./phase";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { RunnerPhase } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/enum_pb";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sort order for runner phases — active phases first, then by severity.
|
|
5
|
+
*
|
|
6
|
+
* Used by both {@link RunnerPicker} and {@link RunnerListPanel} to present
|
|
7
|
+
* runners in a consistent, predictable order across all UI surfaces.
|
|
8
|
+
*/
|
|
9
|
+
export const PHASE_SORT_ORDER: Record<RunnerPhase, number> = {
|
|
10
|
+
[RunnerPhase.READY]: 0,
|
|
11
|
+
[RunnerPhase.BUSY]: 1,
|
|
12
|
+
[RunnerPhase.PENDING]: 2,
|
|
13
|
+
[RunnerPhase.STOPPED]: 3,
|
|
14
|
+
[RunnerPhase.FAILED]: 4,
|
|
15
|
+
[RunnerPhase.UNSPECIFIED]: 5,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const LABELS: Record<RunnerPhase, string> = {
|
|
19
|
+
[RunnerPhase.READY]: "Ready",
|
|
20
|
+
[RunnerPhase.BUSY]: "Busy",
|
|
21
|
+
[RunnerPhase.PENDING]: "Pending",
|
|
22
|
+
[RunnerPhase.STOPPED]: "Stopped",
|
|
23
|
+
[RunnerPhase.FAILED]: "Failed",
|
|
24
|
+
[RunnerPhase.UNSPECIFIED]: "Unknown",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Human-readable label for a runner phase.
|
|
29
|
+
*
|
|
30
|
+
* Returns title-case labels suitable for both compact indicators
|
|
31
|
+
* ("Ready") and full-row display ("Stopped").
|
|
32
|
+
*/
|
|
33
|
+
export function phaseLabel(phase: RunnerPhase): string {
|
|
34
|
+
return LABELS[phase] ?? "Unknown";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tailwind `bg-*` class for the small colored dot indicator.
|
|
39
|
+
*
|
|
40
|
+
* - Ready → `bg-success` (green)
|
|
41
|
+
* - Busy → `bg-warning` (amber)
|
|
42
|
+
* - Others → `bg-muted-foreground` (neutral)
|
|
43
|
+
*/
|
|
44
|
+
export function phaseDotColor(phase: RunnerPhase): string {
|
|
45
|
+
switch (phase) {
|
|
46
|
+
case RunnerPhase.READY:
|
|
47
|
+
return "bg-success";
|
|
48
|
+
case RunnerPhase.BUSY:
|
|
49
|
+
return "bg-warning";
|
|
50
|
+
default:
|
|
51
|
+
return "bg-muted-foreground";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether the runner is in an active (workload-accepting) phase.
|
|
57
|
+
*
|
|
58
|
+
* Active = `READY` or `BUSY`. Inactive = everything else.
|
|
59
|
+
*/
|
|
60
|
+
export function isActivePhase(phase: RunnerPhase): boolean {
|
|
61
|
+
return phase === RunnerPhase.READY || phase === RunnerPhase.BUSY;
|
|
62
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import type { Runner } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
|
|
5
|
+
import { useStigmer } from "../hooks";
|
|
6
|
+
import { toError } from "../internal/toError";
|
|
7
|
+
|
|
8
|
+
/** Return value of {@link useDeleteRunner}. */
|
|
9
|
+
export interface UseDeleteRunnerReturn {
|
|
10
|
+
/**
|
|
11
|
+
* Delete a runner by its resource ID. Resolves with the deleted
|
|
12
|
+
* {@link Runner} for confirmation display.
|
|
13
|
+
*
|
|
14
|
+
* The deletion is permanent — any sessions bound to this runner will
|
|
15
|
+
* fall back to auto-provisioning on their next execution.
|
|
16
|
+
*/
|
|
17
|
+
readonly deleteRunner: (id: string) => Promise<Runner>;
|
|
18
|
+
/** `true` while the delete request is in flight. */
|
|
19
|
+
readonly isDeleting: boolean;
|
|
20
|
+
/** Error from the last failed delete, or `null` when healthy. */
|
|
21
|
+
readonly error: Error | null;
|
|
22
|
+
/** Reset `error` to `null`. */
|
|
23
|
+
readonly clearError: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Mutation hook that wraps `runner.delete()` with loading and error
|
|
28
|
+
* state.
|
|
29
|
+
*
|
|
30
|
+
* Deletes a runner by its resource ID. Returns the deleted
|
|
31
|
+
* {@link Runner} on success so callers can confirm which runner was
|
|
32
|
+
* removed (e.g., in a toast or undo prompt).
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* const { deleteRunner, isDeleting, error } = useDeleteRunner();
|
|
37
|
+
*
|
|
38
|
+
* await deleteRunner("rnr_abc123");
|
|
39
|
+
* refetch(); // refresh the runner list
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function useDeleteRunner(): UseDeleteRunnerReturn {
|
|
43
|
+
const stigmer = useStigmer();
|
|
44
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
45
|
+
const [error, setError] = useState<Error | null>(null);
|
|
46
|
+
|
|
47
|
+
const clearError = useCallback(() => setError(null), []);
|
|
48
|
+
|
|
49
|
+
const deleteRunner = useCallback(
|
|
50
|
+
async (id: string): Promise<Runner> => {
|
|
51
|
+
setIsDeleting(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
return await stigmer.runner.delete(id);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
setError(toError(err));
|
|
58
|
+
throw err;
|
|
59
|
+
} finally {
|
|
60
|
+
setIsDeleting(false);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[stigmer],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return { deleteRunner, isDeleting, error, clearError };
|
|
67
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
6
|
+
import { CreateLaunchTokenRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/io_pb";
|
|
7
|
+
import { useStigmer } from "../hooks";
|
|
8
|
+
import { toError } from "../internal/toError";
|
|
9
|
+
|
|
10
|
+
const LAUNCH_RUNNER_SCHEME = "stigmer://launch-runner";
|
|
11
|
+
|
|
12
|
+
function defaultOpenUrl(url: string): void {
|
|
13
|
+
window.location.href = url;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Options for {@link useLaunchLocalRunner}. */
|
|
17
|
+
export interface UseLaunchLocalRunnerOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Custom handler for opening the `stigmer://` URL.
|
|
20
|
+
*
|
|
21
|
+
* Defaults to `window.location.href` assignment, which is the standard
|
|
22
|
+
* browser pattern for dispatching custom URL schemes to the OS. The
|
|
23
|
+
* browser does not navigate away — it hands the scheme to the OS and
|
|
24
|
+
* the page stays on screen.
|
|
25
|
+
*
|
|
26
|
+
* Override this when embedding in non-standard environments (Electron,
|
|
27
|
+
* iframe, React Native WebView) where the default browser mechanism
|
|
28
|
+
* does not apply.
|
|
29
|
+
*/
|
|
30
|
+
readonly openUrl?: (url: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Result returned by a successful {@link UseLaunchLocalRunnerReturn.launch} call. */
|
|
34
|
+
export interface LaunchLocalRunnerResult {
|
|
35
|
+
/** The `stigmer://launch-runner?token=...` URL that was opened. */
|
|
36
|
+
readonly url: string;
|
|
37
|
+
/**
|
|
38
|
+
* Absolute expiry time of the launch token. The desktop app must
|
|
39
|
+
* exchange the token before this time (typically 60 seconds).
|
|
40
|
+
*/
|
|
41
|
+
readonly expiresAt: Date | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Return value of {@link useLaunchLocalRunner}. */
|
|
45
|
+
export interface UseLaunchLocalRunnerReturn {
|
|
46
|
+
/**
|
|
47
|
+
* Create a one-time launch token and open the `stigmer://` URL to
|
|
48
|
+
* trigger the desktop app (or CLI fallback) to start a local runner.
|
|
49
|
+
*
|
|
50
|
+
* Resolves with the constructed URL and token expiry on success.
|
|
51
|
+
* The caller cannot detect whether the desktop app received the URL —
|
|
52
|
+
* use {@link useRunnerList} with `refetch()` to poll for the runner
|
|
53
|
+
* appearing in the list.
|
|
54
|
+
*/
|
|
55
|
+
readonly launch: (input: { org: string }) => Promise<LaunchLocalRunnerResult>;
|
|
56
|
+
/** `true` while the token creation and URL dispatch are in flight. */
|
|
57
|
+
readonly isLaunching: boolean;
|
|
58
|
+
/** Error from the last failed launch attempt, or `null` when healthy. */
|
|
59
|
+
readonly error: Error | null;
|
|
60
|
+
/** Reset `error` to `null`. */
|
|
61
|
+
readonly clearError: () => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Behavior hook that initiates a browser-to-desktop runner launch.
|
|
66
|
+
*
|
|
67
|
+
* Orchestrates two steps:
|
|
68
|
+
* 1. Calls `stigmer.runner.createLaunchToken({ org })` to mint a
|
|
69
|
+
* one-time, 60-second token backed by the caller's credentials.
|
|
70
|
+
* 2. Opens `stigmer://launch-runner?token={token}` so the OS dispatches
|
|
71
|
+
* to the Stigmer Desktop app (which exchanges the token for a JWT
|
|
72
|
+
* and starts a local runner via its CLI sidecar).
|
|
73
|
+
*
|
|
74
|
+
* The hook reports success when the URL is opened. It does **not**
|
|
75
|
+
* detect whether the desktop app is installed or whether the runner
|
|
76
|
+
* actually started — those are observable via `useRunnerList`.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* function LaunchButton({ org }: { org: string }) {
|
|
81
|
+
* const { launch, isLaunching, error } = useLaunchLocalRunner();
|
|
82
|
+
*
|
|
83
|
+
* return (
|
|
84
|
+
* <button onClick={() => launch({ org })} disabled={isLaunching}>
|
|
85
|
+
* {isLaunching ? "Launching…" : "Launch Local Runner"}
|
|
86
|
+
* </button>
|
|
87
|
+
* );
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* // Custom URL handler for non-browser environments
|
|
94
|
+
* const { launch } = useLaunchLocalRunner({
|
|
95
|
+
* openUrl: (url) => nativeBridge.openExternal(url),
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function useLaunchLocalRunner(
|
|
100
|
+
options?: UseLaunchLocalRunnerOptions,
|
|
101
|
+
): UseLaunchLocalRunnerReturn {
|
|
102
|
+
const stigmer = useStigmer();
|
|
103
|
+
const [isLaunching, setIsLaunching] = useState(false);
|
|
104
|
+
const [error, setError] = useState<Error | null>(null);
|
|
105
|
+
|
|
106
|
+
const openUrl = options?.openUrl ?? defaultOpenUrl;
|
|
107
|
+
|
|
108
|
+
const clearError = useCallback(() => setError(null), []);
|
|
109
|
+
|
|
110
|
+
const launch = useCallback(
|
|
111
|
+
async (input: { org: string }): Promise<LaunchLocalRunnerResult> => {
|
|
112
|
+
setIsLaunching(true);
|
|
113
|
+
setError(null);
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const response = await stigmer.runner.createLaunchToken(
|
|
117
|
+
create(CreateLaunchTokenRequestSchema, { org: input.org }),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const url = `${LAUNCH_RUNNER_SCHEME}?token=${encodeURIComponent(response.token)}`;
|
|
121
|
+
const expiresAt = response.expiresAt
|
|
122
|
+
? timestampDate(response.expiresAt)
|
|
123
|
+
: undefined;
|
|
124
|
+
|
|
125
|
+
openUrl(url);
|
|
126
|
+
|
|
127
|
+
return { url, expiresAt };
|
|
128
|
+
} catch (err) {
|
|
129
|
+
setError(toError(err));
|
|
130
|
+
throw err;
|
|
131
|
+
} finally {
|
|
132
|
+
setIsLaunching(false);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
[stigmer, openUrl],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return { launch, isLaunching, error, clearError };
|
|
139
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import type { Runner } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
|
|
6
|
+
import { ListRunnersRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/io_pb";
|
|
7
|
+
import { useStigmer } from "../hooks";
|
|
8
|
+
import { toError } from "../internal/toError";
|
|
9
|
+
|
|
10
|
+
const SYSTEM_MANAGED_LABEL = "stigmer.ai/system-managed";
|
|
11
|
+
|
|
12
|
+
/** Options for {@link useRunnerList}. */
|
|
13
|
+
export interface UseRunnerListOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Include system-managed (ephemeral cloud) runners in the result.
|
|
16
|
+
*
|
|
17
|
+
* System-managed runners are labeled `stigmer.ai/system-managed: "true"`
|
|
18
|
+
* and are created by the platform for cloud executions. They are hidden
|
|
19
|
+
* from the session composer by default but useful in admin views.
|
|
20
|
+
*
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
readonly includeSystemManaged?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Return value of {@link useRunnerList}. */
|
|
27
|
+
export interface UseRunnerListReturn {
|
|
28
|
+
/** User-created runners for the organization, empty while loading or on error. */
|
|
29
|
+
readonly runners: readonly Runner[];
|
|
30
|
+
/** `true` while the fetch is in flight. */
|
|
31
|
+
readonly isLoading: boolean;
|
|
32
|
+
/** Error from the last failed fetch, or `null` when healthy. */
|
|
33
|
+
readonly error: Error | null;
|
|
34
|
+
/** Discard cached data and re-fetch the runner list from the server. */
|
|
35
|
+
readonly refetch: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Data hook that fetches runners for an organization.
|
|
40
|
+
*
|
|
41
|
+
* Calls `stigmer.runner.list({ org })` and returns the result with
|
|
42
|
+
* loading/error state. System-managed (ephemeral cloud) runners are
|
|
43
|
+
* filtered out by default — pass `includeSystemManaged: true` to
|
|
44
|
+
* include them (useful for admin panels like Settings > Runners).
|
|
45
|
+
*
|
|
46
|
+
* Returns an empty array when `org` is `null` (no organization selected).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* // Session composer — user-created runners only
|
|
51
|
+
* const { runners, isLoading } = useRunnerList("acme");
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* // Admin page — all runners including system-managed
|
|
57
|
+
* const { runners } = useRunnerList("acme", { includeSystemManaged: true });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function useRunnerList(
|
|
61
|
+
org: string | null,
|
|
62
|
+
options?: UseRunnerListOptions,
|
|
63
|
+
): UseRunnerListReturn {
|
|
64
|
+
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
|
+
const includeSystemManaged = options?.includeSystemManaged ?? false;
|
|
71
|
+
|
|
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
|
|
93
|
+
? result.items
|
|
94
|
+
: result.items.filter(
|
|
95
|
+
(r) => r.metadata?.labels[SYSTEM_MANAGED_LABEL] !== "true",
|
|
96
|
+
);
|
|
97
|
+
|
|
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 };
|
|
114
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import type { Runner } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
|
|
6
|
+
import { RunnerStopInputSchema } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/io_pb";
|
|
7
|
+
import { useStigmer } from "../hooks";
|
|
8
|
+
import { toError } from "../internal/toError";
|
|
9
|
+
|
|
10
|
+
/** Input for {@link UseStopRunnerReturn.stop}. */
|
|
11
|
+
export interface StopRunnerInput {
|
|
12
|
+
/** ID of the runner to stop. */
|
|
13
|
+
readonly runnerId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional reason for the stop, logged on the runner and in audit.
|
|
16
|
+
*
|
|
17
|
+
* @example "user requested via web console"
|
|
18
|
+
*/
|
|
19
|
+
readonly reason?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Return value of {@link useStopRunner}. */
|
|
23
|
+
export interface UseStopRunnerReturn {
|
|
24
|
+
/**
|
|
25
|
+
* Stop a runner gracefully.
|
|
26
|
+
*
|
|
27
|
+
* If the runner is connected, sends a stop command via the bidi stream
|
|
28
|
+
* and waits for acknowledgment. If offline, directly transitions to
|
|
29
|
+
* STOPPED. Idempotent — stopping an already-stopped or failed runner
|
|
30
|
+
* returns the resource as-is.
|
|
31
|
+
*
|
|
32
|
+
* Resolves with the updated {@link Runner} resource.
|
|
33
|
+
*/
|
|
34
|
+
readonly stop: (input: StopRunnerInput) => Promise<Runner>;
|
|
35
|
+
/** `true` while the stop RPC is in flight. */
|
|
36
|
+
readonly isStopping: boolean;
|
|
37
|
+
/** Error from the last failed stop attempt, or `null` when healthy. */
|
|
38
|
+
readonly error: Error | null;
|
|
39
|
+
/** Reset `error` to `null`. */
|
|
40
|
+
readonly clearError: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Mutation hook that wraps `runner.stop()` with loading and error state.
|
|
45
|
+
*
|
|
46
|
+
* Stops a runner gracefully. Connected runners receive a stop command
|
|
47
|
+
* via the bidi stream; offline runners transition directly to STOPPED.
|
|
48
|
+
* The operation is idempotent — stopping an already-stopped or failed
|
|
49
|
+
* runner succeeds without error.
|
|
50
|
+
*
|
|
51
|
+
* Returns the updated {@link Runner} resource on success so callers
|
|
52
|
+
* can reflect the new phase in the UI without a separate refetch.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { stop, isStopping, error } = useStopRunner();
|
|
57
|
+
*
|
|
58
|
+
* await stop({ runnerId: "rnr_abc123", reason: "user requested" });
|
|
59
|
+
* refetch(); // refresh the runner list
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function useStopRunner(): UseStopRunnerReturn {
|
|
63
|
+
const stigmer = useStigmer();
|
|
64
|
+
const [isStopping, setIsStopping] = useState(false);
|
|
65
|
+
const [error, setError] = useState<Error | null>(null);
|
|
66
|
+
|
|
67
|
+
const clearError = useCallback(() => setError(null), []);
|
|
68
|
+
|
|
69
|
+
const stop = useCallback(
|
|
70
|
+
async (input: StopRunnerInput): Promise<Runner> => {
|
|
71
|
+
setIsStopping(true);
|
|
72
|
+
setError(null);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
return await stigmer.runner.stop(
|
|
76
|
+
create(RunnerStopInputSchema, {
|
|
77
|
+
runnerId: input.runnerId,
|
|
78
|
+
reason: input.reason ?? "",
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
setError(toError(err));
|
|
83
|
+
throw err;
|
|
84
|
+
} finally {
|
|
85
|
+
setIsStopping(false);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
[stigmer],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return { stop, isStopping, error, clearError };
|
|
92
|
+
}
|