@stigmer/react 3.5.3 → 3.7.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 (283) hide show
  1. package/agent/AgentPicker.d.ts +10 -1
  2. package/agent/AgentPicker.d.ts.map +1 -1
  3. package/agent/AgentPicker.js +2 -2
  4. package/agent/AgentPicker.js.map +1 -1
  5. package/billing/CreditLedgerTable.d.ts.map +1 -1
  6. package/billing/CreditLedgerTable.js +2 -7
  7. package/billing/CreditLedgerTable.js.map +1 -1
  8. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  9. package/channel/AgentChannelsPanel.js +22 -5
  10. package/channel/AgentChannelsPanel.js.map +1 -1
  11. package/channel/ChannelTemplatesDialog.d.ts +53 -0
  12. package/channel/ChannelTemplatesDialog.d.ts.map +1 -0
  13. package/channel/ChannelTemplatesDialog.js +150 -0
  14. package/channel/ChannelTemplatesDialog.js.map +1 -0
  15. package/channel/index.d.ts +8 -0
  16. package/channel/index.d.ts.map +1 -1
  17. package/channel/index.js +4 -0
  18. package/channel/index.js.map +1 -1
  19. package/channel/providers.d.ts +12 -0
  20. package/channel/providers.d.ts.map +1 -1
  21. package/channel/providers.js +14 -2
  22. package/channel/providers.js.map +1 -1
  23. package/channel/templatePresentation.d.ts +37 -0
  24. package/channel/templatePresentation.d.ts.map +1 -0
  25. package/channel/templatePresentation.js +69 -0
  26. package/channel/templatePresentation.js.map +1 -0
  27. package/channel/useChannelTemplateList.d.ts +64 -0
  28. package/channel/useChannelTemplateList.d.ts.map +1 -0
  29. package/channel/useChannelTemplateList.js +69 -0
  30. package/channel/useChannelTemplateList.js.map +1 -0
  31. package/channel/useChannelTemplateReadiness.d.ts +53 -0
  32. package/channel/useChannelTemplateReadiness.d.ts.map +1 -0
  33. package/channel/useChannelTemplateReadiness.js +42 -0
  34. package/channel/useChannelTemplateReadiness.js.map +1 -0
  35. package/composer/ComposerToolbar.d.ts +6 -1
  36. package/composer/ComposerToolbar.d.ts.map +1 -1
  37. package/composer/ComposerToolbar.js +2 -2
  38. package/composer/ComposerToolbar.js.map +1 -1
  39. package/composer/SessionComposer.d.ts +9 -0
  40. package/composer/SessionComposer.d.ts.map +1 -1
  41. package/composer/SessionComposer.js +13 -2
  42. package/composer/SessionComposer.js.map +1 -1
  43. package/dashboard/useDashboardFailedRuns.d.ts.map +1 -1
  44. package/dashboard/useDashboardFailedRuns.js +2 -0
  45. package/dashboard/useDashboardFailedRuns.js.map +1 -1
  46. package/execution/useCreateAgentExecution.d.ts +14 -0
  47. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  48. package/execution/useCreateAgentExecution.js +5 -0
  49. package/execution/useCreateAgentExecution.js.map +1 -1
  50. package/index.d.ts +4 -2
  51. package/index.d.ts.map +1 -1
  52. package/index.js +10 -1
  53. package/index.js.map +1 -1
  54. package/internal/Pagination.d.ts +15 -0
  55. package/internal/Pagination.d.ts.map +1 -0
  56. package/internal/Pagination.js +20 -0
  57. package/internal/Pagination.js.map +1 -0
  58. package/library/useExportResource.d.ts +4 -3
  59. package/library/useExportResource.d.ts.map +1 -1
  60. package/library/useExportResource.js +1 -1
  61. package/library/useExportResource.js.map +1 -1
  62. package/models/ModelSelector.d.ts +26 -1
  63. package/models/ModelSelector.d.ts.map +1 -1
  64. package/models/ModelSelector.js +27 -6
  65. package/models/ModelSelector.js.map +1 -1
  66. package/models/index.d.ts +2 -0
  67. package/models/index.d.ts.map +1 -1
  68. package/models/index.js +1 -0
  69. package/models/index.js.map +1 -1
  70. package/models/registry.d.ts +10 -0
  71. package/models/registry.d.ts.map +1 -1
  72. package/models/registry.js +3 -0
  73. package/models/registry.js.map +1 -1
  74. package/models/service-tier.d.ts +27 -0
  75. package/models/service-tier.d.ts.map +1 -0
  76. package/models/service-tier.js +34 -0
  77. package/models/service-tier.js.map +1 -0
  78. package/organization/OrgProvider.d.ts +11 -4
  79. package/organization/OrgProvider.d.ts.map +1 -1
  80. package/organization/OrgProvider.js +28 -4
  81. package/organization/OrgProvider.js.map +1 -1
  82. package/package.json +4 -4
  83. package/resource-detail/useDeleteResource.d.ts +1 -1
  84. package/resource-detail/useDeleteResource.d.ts.map +1 -1
  85. package/resource-detail/useDeleteResource.js +5 -0
  86. package/resource-detail/useDeleteResource.js.map +1 -1
  87. package/resource-workbench/components/ResourceWorkbench.d.ts +10 -1
  88. package/resource-workbench/components/ResourceWorkbench.d.ts.map +1 -1
  89. package/resource-workbench/components/ResourceWorkbench.js +6 -2
  90. package/resource-workbench/components/ResourceWorkbench.js.map +1 -1
  91. package/resource-workbench/hooks/useResourceCollection.d.ts +8 -0
  92. package/resource-workbench/hooks/useResourceCollection.d.ts.map +1 -1
  93. package/resource-workbench/hooks/useResourceCollection.js +2 -2
  94. package/resource-workbench/hooks/useResourceCollection.js.map +1 -1
  95. package/schedule/CadenceField.d.ts +31 -0
  96. package/schedule/CadenceField.d.ts.map +1 -0
  97. package/schedule/CadenceField.js +116 -0
  98. package/schedule/CadenceField.js.map +1 -0
  99. package/schedule/ScheduleDetailView.d.ts +81 -0
  100. package/schedule/ScheduleDetailView.d.ts.map +1 -0
  101. package/schedule/ScheduleDetailView.js +648 -0
  102. package/schedule/ScheduleDetailView.js.map +1 -0
  103. package/schedule/ScheduleForm.d.ts +46 -0
  104. package/schedule/ScheduleForm.d.ts.map +1 -0
  105. package/schedule/ScheduleForm.js +214 -0
  106. package/schedule/ScheduleForm.js.map +1 -0
  107. package/schedule/ScheduleRowActions.d.ts +20 -0
  108. package/schedule/ScheduleRowActions.d.ts.map +1 -0
  109. package/schedule/ScheduleRowActions.js +54 -0
  110. package/schedule/ScheduleRowActions.js.map +1 -0
  111. package/schedule/ScheduleRunsTable.d.ts +72 -0
  112. package/schedule/ScheduleRunsTable.d.ts.map +1 -0
  113. package/schedule/ScheduleRunsTable.js +161 -0
  114. package/schedule/ScheduleRunsTable.js.map +1 -0
  115. package/schedule/TimeZoneField.d.ts +32 -0
  116. package/schedule/TimeZoneField.d.ts.map +1 -0
  117. package/schedule/TimeZoneField.js +104 -0
  118. package/schedule/TimeZoneField.js.map +1 -0
  119. package/schedule/cadence.d.ts +95 -0
  120. package/schedule/cadence.d.ts.map +1 -0
  121. package/schedule/cadence.js +242 -0
  122. package/schedule/cadence.js.map +1 -0
  123. package/schedule/index.d.ts +37 -0
  124. package/schedule/index.d.ts.map +1 -0
  125. package/schedule/index.js +19 -0
  126. package/schedule/index.js.map +1 -0
  127. package/schedule/scheduleColumns.d.ts +26 -0
  128. package/schedule/scheduleColumns.d.ts.map +1 -0
  129. package/schedule/scheduleColumns.js +77 -0
  130. package/schedule/scheduleColumns.js.map +1 -0
  131. package/schedule/scheduleListFn.d.ts +50 -0
  132. package/schedule/scheduleListFn.d.ts.map +1 -0
  133. package/schedule/scheduleListFn.js +65 -0
  134. package/schedule/scheduleListFn.js.map +1 -0
  135. package/schedule/scheduleState.d.ts +43 -0
  136. package/schedule/scheduleState.d.ts.map +1 -0
  137. package/schedule/scheduleState.js +67 -0
  138. package/schedule/scheduleState.js.map +1 -0
  139. package/schedule/useCreateSchedule.d.ts +57 -0
  140. package/schedule/useCreateSchedule.d.ts.map +1 -0
  141. package/schedule/useCreateSchedule.js +61 -0
  142. package/schedule/useCreateSchedule.js.map +1 -0
  143. package/schedule/useResumeSchedule.d.ts +30 -0
  144. package/schedule/useResumeSchedule.d.ts.map +1 -0
  145. package/schedule/useResumeSchedule.js +45 -0
  146. package/schedule/useResumeSchedule.js.map +1 -0
  147. package/schedule/useSchedule.d.ts +39 -0
  148. package/schedule/useSchedule.d.ts.map +1 -0
  149. package/schedule/useSchedule.js +44 -0
  150. package/schedule/useSchedule.js.map +1 -0
  151. package/schedule/useScheduleCount.d.ts +38 -0
  152. package/schedule/useScheduleCount.d.ts.map +1 -0
  153. package/schedule/useScheduleCount.js +30 -0
  154. package/schedule/useScheduleCount.js.map +1 -0
  155. package/schedule/useScheduleList.d.ts +48 -0
  156. package/schedule/useScheduleList.d.ts.map +1 -0
  157. package/schedule/useScheduleList.js +43 -0
  158. package/schedule/useScheduleList.js.map +1 -0
  159. package/schedule/useScheduleRuns.d.ts +44 -0
  160. package/schedule/useScheduleRuns.d.ts.map +1 -0
  161. package/schedule/useScheduleRuns.js +48 -0
  162. package/schedule/useScheduleRuns.js.map +1 -0
  163. package/schedule/useSetScheduleEnabled.d.ts +31 -0
  164. package/schedule/useSetScheduleEnabled.d.ts.map +1 -0
  165. package/schedule/useSetScheduleEnabled.js +53 -0
  166. package/schedule/useSetScheduleEnabled.js.map +1 -0
  167. package/schedule/useTriggerSchedule.d.ts +41 -0
  168. package/schedule/useTriggerSchedule.d.ts.map +1 -0
  169. package/schedule/useTriggerSchedule.js +62 -0
  170. package/schedule/useTriggerSchedule.js.map +1 -0
  171. package/schedule/useUpdateScheduleSpec.d.ts +49 -0
  172. package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
  173. package/schedule/useUpdateScheduleSpec.js +70 -0
  174. package/schedule/useUpdateScheduleSpec.js.map +1 -0
  175. package/search/useResourceCount.d.ts +2 -1
  176. package/search/useResourceCount.d.ts.map +1 -1
  177. package/search/useResourceCount.js +6 -1
  178. package/search/useResourceCount.js.map +1 -1
  179. package/session/useNewSessionFlow.d.ts.map +1 -1
  180. package/session/useNewSessionFlow.js +1 -0
  181. package/session/useNewSessionFlow.js.map +1 -1
  182. package/session/useSessionConversation.d.ts +11 -0
  183. package/session/useSessionConversation.d.ts.map +1 -1
  184. package/session/useSessionConversation.js +1 -0
  185. package/session/useSessionConversation.js.map +1 -1
  186. package/session/useSessionPageFlow.d.ts.map +1 -1
  187. package/session/useSessionPageFlow.js +1 -0
  188. package/session/useSessionPageFlow.js.map +1 -1
  189. package/src/agent/AgentPicker.tsx +13 -1
  190. package/src/billing/CreditLedgerTable.tsx +3 -42
  191. package/src/channel/AgentChannelsPanel.tsx +43 -1
  192. package/src/channel/ChannelTemplatesDialog.tsx +433 -0
  193. package/src/channel/__tests__/AgentChannelsPanel.test.tsx +73 -0
  194. package/src/channel/__tests__/ChannelTemplatesDialog.test.tsx +292 -0
  195. package/src/channel/__tests__/providers.test.ts +9 -0
  196. package/src/channel/__tests__/templatePresentation.test.ts +96 -0
  197. package/src/channel/__tests__/useChannelTemplateList.test.tsx +216 -0
  198. package/src/channel/__tests__/useChannelTemplateReadiness.test.tsx +124 -0
  199. package/src/channel/index.ts +15 -0
  200. package/src/channel/providers.ts +26 -2
  201. package/src/channel/templatePresentation.ts +82 -0
  202. package/src/channel/useChannelTemplateList.ts +123 -0
  203. package/src/channel/useChannelTemplateReadiness.ts +69 -0
  204. package/src/composer/ComposerToolbar.tsx +9 -0
  205. package/src/composer/SessionComposer.tsx +24 -1
  206. package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +156 -0
  207. package/src/dashboard/__tests__/useDashboardFailedRuns.test.tsx +106 -0
  208. package/src/dashboard/useDashboardFailedRuns.ts +2 -0
  209. package/src/execution/useCreateAgentExecution.ts +18 -0
  210. package/src/index.ts +70 -0
  211. package/src/internal/Pagination.tsx +72 -0
  212. package/src/library/useExportResource.ts +4 -3
  213. package/src/models/ModelSelector.tsx +100 -3
  214. package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +172 -0
  215. package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
  216. package/src/models/index.ts +2 -0
  217. package/src/models/registry.ts +16 -0
  218. package/src/models/service-tier.ts +49 -0
  219. package/src/organization/OrgProvider.tsx +29 -4
  220. package/src/organization/__tests__/OrgProvider.orgSwitch.test.tsx +89 -0
  221. package/src/resource-detail/useDeleteResource.ts +11 -1
  222. package/src/resource-workbench/__tests__/ResourceWorkbench.test.tsx +112 -0
  223. package/src/resource-workbench/components/ResourceWorkbench.tsx +32 -15
  224. package/src/resource-workbench/hooks/useResourceCollection.ts +11 -1
  225. package/src/schedule/CadenceField.tsx +365 -0
  226. package/src/schedule/ScheduleDetailView.tsx +1651 -0
  227. package/src/schedule/ScheduleForm.tsx +615 -0
  228. package/src/schedule/ScheduleRowActions.tsx +124 -0
  229. package/src/schedule/ScheduleRunsTable.tsx +424 -0
  230. package/src/schedule/TimeZoneField.tsx +199 -0
  231. package/src/schedule/__tests__/ScheduleDetailView.test.tsx +683 -0
  232. package/src/schedule/__tests__/cadence.test.ts +278 -0
  233. package/src/schedule/__tests__/scheduleColumnsRowActions.test.tsx +176 -0
  234. package/src/schedule/__tests__/scheduleCreation.test.tsx +537 -0
  235. package/src/schedule/__tests__/scheduleHooks.test.tsx +399 -0
  236. package/src/schedule/__tests__/scheduleListFn.test.ts +128 -0
  237. package/src/schedule/__tests__/scheduleState.test.ts +133 -0
  238. package/src/schedule/cadence.ts +298 -0
  239. package/src/schedule/index.ts +69 -0
  240. package/src/schedule/scheduleColumns.tsx +118 -0
  241. package/src/schedule/scheduleListFn.ts +108 -0
  242. package/src/schedule/scheduleState.ts +96 -0
  243. package/src/schedule/useCreateSchedule.ts +88 -0
  244. package/src/schedule/useResumeSchedule.ts +67 -0
  245. package/src/schedule/useSchedule.ts +67 -0
  246. package/src/schedule/useScheduleCount.ts +57 -0
  247. package/src/schedule/useScheduleList.ts +83 -0
  248. package/src/schedule/useScheduleRuns.ts +93 -0
  249. package/src/schedule/useSetScheduleEnabled.ts +84 -0
  250. package/src/schedule/useTriggerSchedule.ts +90 -0
  251. package/src/schedule/useUpdateScheduleSpec.ts +108 -0
  252. package/src/search/__tests__/useResourceCount.test.tsx +89 -0
  253. package/src/search/useResourceCount.ts +8 -2
  254. package/src/session/useNewSessionFlow.ts +1 -0
  255. package/src/session/useSessionConversation.ts +12 -0
  256. package/src/session/useSessionPageFlow.ts +1 -0
  257. package/src/workflow/WorkflowDashboard.tsx +1 -0
  258. package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
  259. package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
  260. package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
  261. package/src/workflow/starter-workflow-yaml.ts +2 -1
  262. package/src/workflow/useWorkflowExecutionList.ts +13 -2
  263. package/styles.css +1 -1
  264. package/workflow/CanvasTransitionEdge.d.ts +1 -1
  265. package/workflow/WorkflowDashboard.d.ts.map +1 -1
  266. package/workflow/WorkflowDashboard.js +1 -0
  267. package/workflow/WorkflowDashboard.js.map +1 -1
  268. package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
  269. package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
  270. package/workflow/inspector/forms/AgentCallForm.js +87 -9
  271. package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
  272. package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
  273. package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
  274. package/workflow/inspector/tabs/RuntimeTab.js +31 -5
  275. package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
  276. package/workflow/starter-workflow-yaml.d.ts +1 -1
  277. package/workflow/starter-workflow-yaml.d.ts.map +1 -1
  278. package/workflow/starter-workflow-yaml.js +2 -1
  279. package/workflow/starter-workflow-yaml.js.map +1 -1
  280. package/workflow/useWorkflowExecutionList.d.ts +10 -0
  281. package/workflow/useWorkflowExecutionList.d.ts.map +1 -1
  282. package/workflow/useWorkflowExecutionList.js +3 -2
  283. package/workflow/useWorkflowExecutionList.js.map +1 -1
@@ -0,0 +1,156 @@
1
+ import { describe, it, expect, vi, afterEach, beforeAll } from "vitest";
2
+ import { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/react";
3
+ import { type ReactNode } from "react";
4
+ import type { Stigmer } from "@stigmer/sdk";
5
+ import { StigmerContext } from "../../context";
6
+ import { ModelRegistryContext } from "../../models/ModelRegistryContext";
7
+ import type { ModelInfo } from "../../models/registry";
8
+ import { SessionComposer } from "../SessionComposer";
9
+
10
+ // Portal into document.body so the popover mounts under happy-dom, and shim
11
+ // ResizeObserver for Base UI's positioner (the scheduleCreation.test.tsx
12
+ // pattern).
13
+ vi.mock("../../portal-container", () => ({
14
+ useStigmerPortalContainer: () => document.body,
15
+ }));
16
+
17
+ beforeAll(() => {
18
+ if (!("ResizeObserver" in globalThis)) {
19
+ (globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
20
+ class {
21
+ observe() {}
22
+ unobserve() {}
23
+ disconnect() {}
24
+ };
25
+ }
26
+ });
27
+
28
+ /**
29
+ * End-to-end composer wiring for the service tier (stigmer/stigmer#357):
30
+ * the toolbar's ModelSelector toggle must reach composer state, and an
31
+ * active fast selection must ride the submit context to execution create.
32
+ * This seam (SessionComposer → ComposerToolbar props → submit context) is
33
+ * exactly where the feature can silently die while every layer on either
34
+ * side unit-tests green.
35
+ */
36
+
37
+ const FAST_CAPABLE: ModelInfo = {
38
+ modelId: "composer-2.5",
39
+ provider: "cursor",
40
+ displayName: "Composer 2.5",
41
+ shortDescription: "Fast agentic model",
42
+ speedTier: "fastest",
43
+ costTier: "economy",
44
+ harness: "cursor",
45
+ featured: true,
46
+ serviceTiers: ["fast"],
47
+ };
48
+
49
+ function createMinimalStigmerMock(): Stigmer {
50
+ return {
51
+ agentExecution: { uploadAttachment: vi.fn() },
52
+ environment: { getPersonal: vi.fn().mockResolvedValue(null) },
53
+ baseUrl: "http://localhost:8080",
54
+ getAuthCredential: vi.fn().mockResolvedValue("test-token"),
55
+ config: {
56
+ baseUrl: "http://localhost:8080",
57
+ getAccessToken: vi.fn().mockResolvedValue(""),
58
+ },
59
+ } as unknown as Stigmer;
60
+ }
61
+
62
+ function createWrapper(client: Stigmer) {
63
+ return function Wrapper({ children }: { children: ReactNode }) {
64
+ return (
65
+ <StigmerContext.Provider value={client}>
66
+ <ModelRegistryContext.Provider
67
+ value={{
68
+ models: [FAST_CAPABLE],
69
+ isLoading: false,
70
+ error: null,
71
+ refetch: vi.fn(),
72
+ }}
73
+ >
74
+ {children}
75
+ </ModelRegistryContext.Provider>
76
+ </StigmerContext.Provider>
77
+ );
78
+ };
79
+ }
80
+
81
+ function renderComposer() {
82
+ const client = createMinimalStigmerMock();
83
+ const onSubmit = vi.fn();
84
+
85
+ const result = render(
86
+ <SessionComposer
87
+ onSubmit={onSubmit}
88
+ harness="cursor"
89
+ defaultModelId="composer-2.5"
90
+ />,
91
+ { wrapper: createWrapper(client) },
92
+ );
93
+
94
+ return { ...result, onSubmit };
95
+ }
96
+
97
+ function submitMessage(message: string) {
98
+ const textarea = screen.getByRole("textbox");
99
+ fireEvent.change(textarea, { target: { value: message } });
100
+ fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
101
+ }
102
+
103
+ /**
104
+ * Open the model selector popover (Base UI portals mount asynchronously in
105
+ * jsdom — the switch is awaited) and flip the fast-tier toggle.
106
+ */
107
+ async function toggleFastTier() {
108
+ fireEvent.click(screen.getByRole("button", { name: /Composer 2\.5/ }));
109
+ const toggle = await screen.findByRole("switch", { name: "Fast tier" });
110
+ fireEvent.click(toggle);
111
+ fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
112
+ }
113
+
114
+ afterEach(cleanup);
115
+
116
+ describe("SessionComposer — service tier submit contract", () => {
117
+ it("renders the tier toggle through the toolbar for a fast-capable model", async () => {
118
+ renderComposer();
119
+
120
+ fireEvent.click(screen.getByRole("button", { name: /Composer 2\.5/ }));
121
+ expect(await screen.findByRole("switch", { name: "Fast tier" })).toBeTruthy();
122
+ });
123
+
124
+ it("carries serviceTier 'fast' on the submit context after toggling", async () => {
125
+ const { onSubmit } = renderComposer();
126
+
127
+ await toggleFastTier();
128
+ submitMessage("Run it fast");
129
+
130
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
131
+ const context = onSubmit.mock.calls[0][2];
132
+ expect(context?.serviceTier).toBe("fast");
133
+ });
134
+
135
+ it("leaves serviceTier undefined when the toggle is untouched", async () => {
136
+ const { onSubmit } = renderComposer();
137
+
138
+ submitMessage("Run it normally");
139
+
140
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
141
+ const context = onSubmit.mock.calls[0][2];
142
+ expect(context?.serviceTier).toBeUndefined();
143
+ });
144
+
145
+ it("leaves serviceTier undefined after toggling fast on and back off", async () => {
146
+ const { onSubmit } = renderComposer();
147
+
148
+ await toggleFastTier();
149
+ await toggleFastTier();
150
+ submitMessage("Changed my mind");
151
+
152
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
153
+ const context = onSubmit.mock.calls[0][2];
154
+ expect(context?.serviceTier).toBeUndefined();
155
+ });
156
+ });
@@ -0,0 +1,106 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { renderHook, waitFor } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { ExecutionPhase as AgentPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
5
+ import { ExecutionPhase as WorkflowPhase } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
6
+ import { StigmerContext } from "../../context";
7
+ import { FetchCacheContext } from "../../internal/FetchCacheProvider";
8
+ import { useDashboardFailedRuns } from "../useDashboardFailedRuns";
9
+
10
+ function createMockStigmer(overrides: {
11
+ agentList?: (...args: unknown[]) => Promise<unknown>;
12
+ workflowList?: (...args: unknown[]) => Promise<unknown>;
13
+ } = {}) {
14
+ return {
15
+ agentExecution: {
16
+ list: overrides.agentList ?? vi.fn().mockResolvedValue({ entries: [] }),
17
+ },
18
+ workflowExecution: {
19
+ list: overrides.workflowList ?? vi.fn().mockResolvedValue({ entries: [] }),
20
+ },
21
+ } as never;
22
+ }
23
+
24
+ function wrapper(client: unknown) {
25
+ return function Wrapper({ children }: { children: ReactNode }) {
26
+ return (
27
+ <FetchCacheContext.Provider value={null}>
28
+ <StigmerContext.Provider value={client as never}>
29
+ {children}
30
+ </StigmerContext.Provider>
31
+ </FetchCacheContext.Provider>
32
+ );
33
+ };
34
+ }
35
+
36
+ describe("useDashboardFailedRuns", () => {
37
+ it("scopes both list requests to the active org and the FAILED phase", async () => {
38
+ const agentList = vi.fn().mockResolvedValue({ entries: [] });
39
+ const workflowList = vi.fn().mockResolvedValue({ entries: [] });
40
+ const client = createMockStigmer({ agentList, workflowList });
41
+
42
+ const { result } = renderHook(() => useDashboardFailedRuns("acme"), {
43
+ wrapper: wrapper(client),
44
+ });
45
+
46
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
47
+
48
+ // Regression guard: the pre-fix hook used org only as a fetch gate and
49
+ // sent no org on the wire, so a brand-new org's dashboard showed other
50
+ // orgs' failures.
51
+ const agentRequest = agentList.mock.calls[0][0];
52
+ expect(agentRequest.org).toBe("acme");
53
+ expect(agentRequest.phase).toBe(AgentPhase.EXECUTION_FAILED);
54
+
55
+ const workflowRequest = workflowList.mock.calls[0][0];
56
+ expect(workflowRequest.org).toBe("acme");
57
+ expect(workflowRequest.phase).toBe(WorkflowPhase.EXECUTION_FAILED);
58
+ });
59
+
60
+ it("does not fetch until an org is available", async () => {
61
+ const agentList = vi.fn().mockResolvedValue({ entries: [] });
62
+ const workflowList = vi.fn().mockResolvedValue({ entries: [] });
63
+ const client = createMockStigmer({ agentList, workflowList });
64
+
65
+ renderHook(() => useDashboardFailedRuns(null), {
66
+ wrapper: wrapper(client),
67
+ });
68
+
69
+ // Give any wrongly-armed fetch a tick to fire.
70
+ await new Promise((resolve) => setTimeout(resolve, 0));
71
+
72
+ expect(agentList).not.toHaveBeenCalled();
73
+ expect(workflowList).not.toHaveBeenCalled();
74
+ });
75
+
76
+ it("interleaves agent and workflow failures newest-first, capped at 5", async () => {
77
+ const ts = (secs: number) => ({ seconds: BigInt(secs), nanos: 0 });
78
+ const agentEntries = [1, 3, 5].map((n) => ({
79
+ metadata: { id: `aex_${n}`, name: `agent ${n}` },
80
+ spec: { agentId: `agt_${n}` },
81
+ status: { error: "boom", audit: { specAudit: { createdAt: ts(n) } } },
82
+ }));
83
+ const workflowEntries = [2, 4, 6].map((n) => ({
84
+ metadata: { id: `wex_${n}`, name: `workflow ${n}`, slug: `wf-${n}` },
85
+ status: { error: "boom", audit: { specAudit: { createdAt: ts(n) } } },
86
+ }));
87
+ const client = createMockStigmer({
88
+ agentList: vi.fn().mockResolvedValue({ entries: agentEntries }),
89
+ workflowList: vi.fn().mockResolvedValue({ entries: workflowEntries }),
90
+ });
91
+
92
+ const { result } = renderHook(() => useDashboardFailedRuns("acme"), {
93
+ wrapper: wrapper(client),
94
+ });
95
+
96
+ await waitFor(() => expect(result.current.failedRuns).toHaveLength(5));
97
+
98
+ expect(result.current.failedRuns.map((run) => run.id)).toEqual([
99
+ "wex_6",
100
+ "aex_5",
101
+ "wex_4",
102
+ "aex_3",
103
+ "wex_2",
104
+ ]);
105
+ });
106
+ });
@@ -48,6 +48,7 @@ export function useDashboardFailedRuns(
48
48
  create(ListAgentExecutionsRequestSchema, {
49
49
  pageSize: PAGE_SIZE,
50
50
  phase: AgentPhase.EXECUTION_FAILED,
51
+ org: orgVal,
51
52
  }),
52
53
  );
53
54
  return [...resp.entries] as readonly AgentExecution[];
@@ -64,6 +65,7 @@ export function useDashboardFailedRuns(
64
65
  create(ListWorkflowExecutionsRequestSchema, {
65
66
  pageSize: PAGE_SIZE,
66
67
  phase: WorkflowPhase.EXECUTION_FAILED,
68
+ org: orgVal,
67
69
  }),
68
70
  );
69
71
  return [...resp.entries] as readonly WorkflowExecution[];
@@ -13,6 +13,7 @@ import {
13
13
  import { useStigmer } from "../hooks.js";
14
14
  import { toError } from "../internal/toError.js";
15
15
  import { toProtoInteractionMode } from "../composer/interaction-mode.js";
16
+ import { toProtoServiceTier, type ServiceTierOption } from "../models/service-tier.js";
16
17
  import { toProtoHarness, type HarnessOption } from "../models/harness.js";
17
18
  import {
18
19
  toProtoExecutionTarget,
@@ -123,6 +124,19 @@ export interface SharedAgentExecutionFields {
123
124
  * Maps to `ExecutionConfig.interaction_mode` in the proto.
124
125
  */
125
126
  readonly interactionMode?: "agent" | "plan";
127
+ /**
128
+ * Service tier for this execution's model calls (stigmer/stigmer#357).
129
+ *
130
+ * - `"standard"` (default): the model's base-priced configuration,
131
+ * requested explicitly — never the provider account default.
132
+ * - `"fast"`: the provider's fast variant at fast rates. Valid only for
133
+ * models whose registry entry prices a fast variant — the backend
134
+ * refuses the create otherwise, so gate the option on
135
+ * `ModelInfo.serviceTiers`.
136
+ *
137
+ * Maps to `ExecutionConfig.service_tier` in the proto.
138
+ */
139
+ readonly serviceTier?: ServiceTierOption;
126
140
  /**
127
141
  * Marks this execution as a Build-from-plan turn: the user approved a
128
142
  * plan from a prior Plan-mode execution and asked the agent to implement
@@ -302,6 +316,7 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
302
316
  const hasConfig =
303
317
  input.modelName ||
304
318
  input.interactionMode ||
319
+ input.serviceTier ||
305
320
  input.structuredOutputSchema ||
306
321
  input.buildFromPlan;
307
322
  const executionConfig = hasConfig
@@ -310,6 +325,9 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
310
325
  ...(input.interactionMode
311
326
  ? { interactionMode: toProtoInteractionMode(input.interactionMode) }
312
327
  : {}),
328
+ ...(input.serviceTier
329
+ ? { serviceTier: toProtoServiceTier(input.serviceTier) }
330
+ : {}),
313
331
  ...(input.structuredOutputSchema
314
332
  ? { structuredOutputSchema: input.structuredOutputSchema }
315
333
  : {}),
package/src/index.ts CHANGED
@@ -770,6 +770,66 @@ export type {
770
770
  DatastoreDetailViewProps,
771
771
  } from "./datastore/index.js";
772
772
 
773
+ // Schedule — the disabled-vs-paused state derivation, the direct-query
774
+ // list core + workbench adapter (schedules are not search-backed), data
775
+ // hooks (get by reference, paginated list, count), behavior hooks
776
+ // (create via apply, resume, trigger once, owner enable/disable and
777
+ // per-field spec edits via lossless manifest apply), the cadence model
778
+ // (preset ⇄ cron, no cron parsing — recognition of builder-emitted
779
+ // shapes only), the cadence builder, the creation form, the tabbed
780
+ // detail view with inline editing, and the paginated run-history table
781
+ export {
782
+ deriveScheduleState,
783
+ formatNextFire,
784
+ createScheduleListFn,
785
+ listSchedulesPage,
786
+ useSchedule,
787
+ useScheduleList,
788
+ useScheduleCount,
789
+ useCreateSchedule,
790
+ useResumeSchedule,
791
+ useTriggerSchedule,
792
+ useSetScheduleEnabled,
793
+ useUpdateScheduleSpec,
794
+ createScheduleColumns,
795
+ cadenceToCron,
796
+ cronToCadence,
797
+ describeCadence,
798
+ validateCron,
799
+ validateTimeZone,
800
+ WEEKDAY_LABELS,
801
+ CadenceField,
802
+ ScheduleForm,
803
+ ScheduleRowActions,
804
+ ScheduleDetailView,
805
+ ScheduleIcon,
806
+ ScheduleRunsTable,
807
+ } from "./schedule/index.js";
808
+ export type {
809
+ ScheduleState,
810
+ ScheduleStateInfo,
811
+ ScheduleListClient,
812
+ SchedulePage,
813
+ UseScheduleReturn,
814
+ UseScheduleListOptions,
815
+ UseScheduleListReturn,
816
+ UseScheduleCountOptions,
817
+ UseScheduleCountReturn,
818
+ UseCreateScheduleReturn,
819
+ UseResumeScheduleReturn,
820
+ UseTriggerScheduleReturn,
821
+ UseSetScheduleEnabledReturn,
822
+ CadencePreset,
823
+ CadenceKind,
824
+ CadenceFieldProps,
825
+ ScheduleFormProps,
826
+ ScheduleColumnsOptions,
827
+ ScheduleRowActionsProps,
828
+ ScheduleDetailViewProps,
829
+ ScheduleRunsTableProps,
830
+ UseUpdateScheduleSpecReturn,
831
+ } from "./schedule/index.js";
832
+
773
833
  // Environment — data hooks, list hook, personal convenience hook, secret reveal, variable management, env var form, system env vars, and styled components
774
834
  export {
775
835
  useEnvironment,
@@ -1259,13 +1319,18 @@ export {
1259
1319
  useConnectSlackChannel,
1260
1320
  useInstallChannel,
1261
1321
  useChannelToolReadiness,
1322
+ useChannelTemplateReadiness,
1262
1323
  useChannelSessions,
1324
+ useChannelTemplateList,
1325
+ templateStatusPhase,
1326
+ splitTemplateBody,
1263
1327
  AgentChannelsPanel,
1264
1328
  ConnectSlackDialog,
1265
1329
  ConnectWhatsAppDialog,
1266
1330
  ChannelCredentialsDialog,
1267
1331
  ChannelToolCredentials,
1268
1332
  ChannelConversationsDialog,
1333
+ ChannelTemplatesDialog,
1269
1334
  } from "./channel/index.js";
1270
1335
  export type {
1271
1336
  UseAgentChannelListReturn,
@@ -1277,13 +1342,18 @@ export type {
1277
1342
  SlackConnectPhase,
1278
1343
  UseInstallChannelReturn,
1279
1344
  InstallChannelPhase,
1345
+ ChannelTemplateReadiness,
1280
1346
  UseChannelSessionsReturn,
1347
+ UseChannelTemplateListOptions,
1348
+ UseChannelTemplateListReturn,
1349
+ TemplateBodySegment,
1281
1350
  AgentChannelsPanelProps,
1282
1351
  ConnectSlackDialogProps,
1283
1352
  ConnectWhatsAppDialogProps,
1284
1353
  ChannelCredentialsDialogProps,
1285
1354
  ChannelToolCredentialsProps,
1286
1355
  ChannelConversationsDialogProps,
1356
+ ChannelTemplatesDialogProps,
1287
1357
  } from "./channel/index.js";
1288
1358
 
1289
1359
  // Error — structured error display with classification, retry, and contextual guidance
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Shared prev/next pagination control for paginated SDK tables.
5
+ *
6
+ * Extracted from `CreditLedgerTable` (its original, file-private home)
7
+ * when `ScheduleRunsTable` became the second consumer. Deliberately
8
+ * minimal — Previous / "Page N of M" / Next — because every paginated
9
+ * surface in the SDK so far wants exactly this shape; page-number
10
+ * jumping earns its complexity only when a consumer actually needs it.
11
+ *
12
+ * Internal (not barrel-exported): the public contract is each table
13
+ * component, not this control.
14
+ */
15
+
16
+ import { cn } from "@stigmer/theme";
17
+
18
+ /** Props for {@link Pagination}. */
19
+ export interface PaginationProps {
20
+ /** Current page number, 1-indexed. */
21
+ readonly pageNum: number;
22
+ /** Total number of pages. Render the control only when > 1. */
23
+ readonly totalPages: number;
24
+ /** Called with the new page number when the user navigates. */
25
+ readonly onPageChange: (page: number) => void;
26
+ /** Accessible label naming what is being paginated (e.g. "Run history pagination"). */
27
+ readonly ariaLabel: string;
28
+ /** Additional CSS classes for the root container. */
29
+ readonly className?: string;
30
+ }
31
+
32
+ const buttonClasses = cn(
33
+ "rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors",
34
+ "hover:bg-accent hover:text-foreground",
35
+ "disabled:pointer-events-none disabled:opacity-40",
36
+ );
37
+
38
+ export function Pagination({
39
+ pageNum,
40
+ totalPages,
41
+ onPageChange,
42
+ ariaLabel,
43
+ className,
44
+ }: PaginationProps) {
45
+ return (
46
+ <div
47
+ className={cn("flex items-center justify-between", className)}
48
+ role="navigation"
49
+ aria-label={ariaLabel}
50
+ >
51
+ <button
52
+ type="button"
53
+ disabled={pageNum <= 1}
54
+ onClick={() => onPageChange(pageNum - 1)}
55
+ className={buttonClasses}
56
+ >
57
+ Previous
58
+ </button>
59
+ <span className="text-xs tabular-nums text-muted-foreground">
60
+ Page {pageNum} of {totalPages}
61
+ </span>
62
+ <button
63
+ type="button"
64
+ disabled={pageNum >= totalPages}
65
+ onClick={() => onPageChange(pageNum + 1)}
66
+ className={buttonClasses}
67
+ >
68
+ Next
69
+ </button>
70
+ </div>
71
+ );
72
+ }
@@ -5,6 +5,7 @@ import { parse as parseYaml } from "yaml";
5
5
  import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
6
6
  import type { Datastore } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
7
7
  import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
8
+ import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
8
9
  import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
9
10
  import { serializeManifest } from "@stigmer/sdk";
10
11
  import { toast } from "../feedback/toast.js";
@@ -17,9 +18,9 @@ import { serializeWorkflowYaml } from "../workflow/serialize-workflow-yaml.js";
17
18
  /** Options for {@link useExportResource}. */
18
19
  export interface UseExportResourceOptions {
19
20
  /** The resource kind — determines which serializer is used. */
20
- readonly kind: "Agent" | "McpServer" | "Workflow" | "Datastore";
21
+ readonly kind: "Agent" | "McpServer" | "Workflow" | "Datastore" | "Schedule";
21
22
  /** The proto resource to export, or `null` when not yet loaded. */
22
- readonly resource: Agent | McpServer | Workflow | Datastore | null;
23
+ readonly resource: Agent | McpServer | Workflow | Datastore | Schedule | null;
23
24
  }
24
25
 
25
26
  /** Return value of {@link useExportResource}. */
@@ -44,7 +45,7 @@ export interface UseExportResourceReturn {
44
45
 
45
46
  /**
46
47
  * Headless export hook for Stigmer resources (Agent, McpServer,
47
- * Workflow, Datastore).
48
+ * Workflow, Datastore, Schedule).
48
49
  *
49
50
  * Serializes the resource into YAML and JSON formats and provides
50
51
  * stable callbacks for copying to clipboard or triggering a file
@@ -7,6 +7,7 @@ import { useStigmerPortalContainer } from "../portal-container.js";
7
7
  import { useModelRegistry } from "./useModelRegistry.js";
8
8
  import type { ModelInfo, CostTier, SpeedTier } from "./registry.js";
9
9
  import { HARNESS_META, HARNESS_OPTIONS, type HarnessOption } from "./harness.js";
10
+ import { FAST_SERVICE_TIER, type ServiceTierOption } from "./service-tier.js";
10
11
 
11
12
  const COST_TIER_LABEL: Record<CostTier, string> = {
12
13
  economy: "$",
@@ -61,6 +62,31 @@ export interface ModelSelectorProps {
61
62
  readonly className?: string;
62
63
  /** When true, disables the selector. */
63
64
  readonly disabled?: boolean;
65
+ /**
66
+ * Trigger label shown while `value` is empty, instead of falling back
67
+ * to the registry's default model. Lets a form distinguish "nothing
68
+ * pinned — the platform default applies" from an actual selection
69
+ * (the schedule form's contract: an unset model inherits the
70
+ * surface's platform default). The harness prefix is hidden while
71
+ * the placeholder shows, since no engine is pinned either.
72
+ */
73
+ readonly placeholderLabel?: string;
74
+
75
+ /**
76
+ * Current service tier for the selected model (stigmer/stigmer#357).
77
+ * Defaults to "standard". Only meaningful together with
78
+ * {@link onServiceTierChange}.
79
+ */
80
+ readonly serviceTier?: ServiceTierOption;
81
+ /**
82
+ * Called when the user toggles the fast tier. Providing this enables the
83
+ * tier toggle, which renders ONLY when the selected model prices a fast
84
+ * variant ({@link ModelInfo.serviceTiers}) — no dead controls. Selecting
85
+ * a model without a fast tier while "fast" is active resets the tier to
86
+ * "standard" through this callback, so an unsupported combination can
87
+ * never be submitted.
88
+ */
89
+ readonly onServiceTierChange?: (tier: ServiceTierOption) => void;
64
90
 
65
91
  /**
66
92
  * @deprecated Use {@link onHarnessChange} instead.
@@ -104,6 +130,9 @@ export function ModelSelector({
104
130
  compact = false,
105
131
  className,
106
132
  disabled,
133
+ placeholderLabel,
134
+ serviceTier = "standard",
135
+ onServiceTierChange,
107
136
  }: ModelSelectorProps) {
108
137
  const portalContainer = useStigmerPortalContainer();
109
138
 
@@ -216,9 +245,19 @@ export function ModelSelector({
216
245
  const selectModel = useCallback(
217
246
  (model: ModelInfo) => {
218
247
  onValueChange(model.modelId);
248
+ // Fail-safe: the fast tier is per-model; carrying it onto a model
249
+ // that does not price it would be refused at create time (#357), so
250
+ // reset here instead of surfacing a dead selection.
251
+ if (
252
+ serviceTier === "fast"
253
+ && onServiceTierChange
254
+ && !model.serviceTiers.includes(FAST_SERVICE_TIER)
255
+ ) {
256
+ onServiceTierChange("standard");
257
+ }
219
258
  setOpen(false);
220
259
  },
221
- [onValueChange],
260
+ [onValueChange, serviceTier, onServiceTierChange],
222
261
  );
223
262
 
224
263
  const scrollHighlightIntoView = useCallback((idx: number) => {
@@ -265,8 +304,23 @@ export function ModelSelector({
265
304
 
266
305
  const showShowAllButton = !isSearching && !showAll && featuredModels.length > 0 && featuredModels.length < models.length;
267
306
 
268
- const triggerLabel = selectedModel?.displayName ?? "Select model";
269
- const triggerHarness = !isHarnessLocked ? HARNESS_META[activeHarness].label : undefined;
307
+ // The tier toggle exists only where it applies: a consumer opted in AND
308
+ // the selected model prices a fast variant (recognition over recall —
309
+ // never a dead control on models with no tier choice).
310
+ const showServiceTierToggle =
311
+ onServiceTierChange !== undefined
312
+ && selectedModel !== undefined
313
+ && selectedModel.serviceTiers.includes(FAST_SERVICE_TIER);
314
+ const fastActive = serviceTier === "fast";
315
+
316
+ const usingPlaceholder = !value && placeholderLabel !== undefined;
317
+ const triggerLabel = usingPlaceholder
318
+ ? placeholderLabel
319
+ : (selectedModel?.displayName ?? "Select model");
320
+ const triggerHarness =
321
+ !isHarnessLocked && !usingPlaceholder
322
+ ? HARNESS_META[activeHarness].label
323
+ : undefined;
270
324
 
271
325
  return (
272
326
  <Popover.Root open={open} onOpenChange={setOpen}>
@@ -288,6 +342,16 @@ export function ModelSelector({
288
342
  <span className="shrink-0 text-border" aria-hidden>·</span>
289
343
  )}
290
344
  <span className="truncate">{triggerLabel}</span>
345
+ {showServiceTierToggle && fastActive && (
346
+ <span
347
+ className={cn(
348
+ "shrink-0 rounded-sm border border-border px-1 py-px",
349
+ "text-[0.6rem] font-medium uppercase tracking-wider text-muted-foreground",
350
+ )}
351
+ >
352
+ Fast
353
+ </span>
354
+ )}
291
355
  <ChevronIcon />
292
356
  </Popover.Trigger>
293
357
 
@@ -471,6 +535,39 @@ export function ModelSelector({
471
535
  </button>
472
536
  )}
473
537
  </div>
538
+
539
+ {/* Service tier toggle — only for models that price a fast variant (#357) */}
540
+ {showServiceTierToggle && (
541
+ <div className="flex items-center justify-between border-t border-border px-3 py-2">
542
+ <div className="flex flex-col">
543
+ <span className="text-xs text-foreground">Fast tier</span>
544
+ <span className="text-[0.65rem] text-muted-foreground">
545
+ Faster responses at higher per-token rates
546
+ </span>
547
+ </div>
548
+ <button
549
+ type="button"
550
+ role="switch"
551
+ aria-checked={fastActive}
552
+ aria-label="Fast tier"
553
+ className={cn(
554
+ "relative h-4.5 w-8 shrink-0 rounded-full transition-colors cursor-pointer",
555
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
556
+ fastActive ? "bg-primary" : "bg-muted",
557
+ )}
558
+ onClick={() =>
559
+ onServiceTierChange?.(fastActive ? "standard" : "fast")
560
+ }
561
+ >
562
+ <span
563
+ className={cn(
564
+ "absolute top-0.5 size-3.5 rounded-full bg-background shadow-sm transition-transform",
565
+ fastActive ? "translate-x-4" : "translate-x-0.5",
566
+ )}
567
+ />
568
+ </button>
569
+ </div>
570
+ )}
474
571
  </Popover.Popup>
475
572
  </Popover.Positioner>
476
573
  </Popover.Portal>