@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,124 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderHook } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
5
+ import { AgentChannelInstallState } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/status_pb";
6
+ import { DeploymentModeContext } from "../../deployment-mode";
7
+ import { useChannelTemplateReadiness } from "../useChannelTemplateReadiness";
8
+
9
+ function makeChannel(overrides?: {
10
+ installState?: AgentChannelInstallState;
11
+ enabled?: boolean;
12
+ proactiveMessagingEnabled?: boolean;
13
+ }): AgentChannel {
14
+ return {
15
+ metadata: { id: "ach_1", org: "acme", slug: "wa-main" },
16
+ spec: {
17
+ enabled: overrides?.enabled ?? true,
18
+ proactiveMessagingEnabled: overrides?.proactiveMessagingEnabled ?? true,
19
+ },
20
+ status: {
21
+ installState:
22
+ overrides?.installState ?? AgentChannelInstallState.installed,
23
+ },
24
+ } as unknown as AgentChannel;
25
+ }
26
+
27
+ function wrapper(mode: "cloud" | "local" = "cloud") {
28
+ return function Wrapper({ children }: { children: ReactNode }) {
29
+ return (
30
+ <DeploymentModeContext.Provider value={mode}>
31
+ {children}
32
+ </DeploymentModeContext.Provider>
33
+ );
34
+ };
35
+ }
36
+
37
+ function readinessOf(
38
+ channel: AgentChannel,
39
+ mode: "cloud" | "local" = "cloud",
40
+ ) {
41
+ const { result } = renderHook(() => useChannelTemplateReadiness(channel), {
42
+ wrapper: wrapper(mode),
43
+ });
44
+ return result.current;
45
+ }
46
+
47
+ describe("useChannelTemplateReadiness", () => {
48
+ it("is ready for an installed, enabled, proactive channel on cloud", () => {
49
+ expect(readinessOf(makeChannel())).toEqual({ status: "ready" });
50
+ });
51
+
52
+ it("is cloud-only on a local deployment — the template registry is cloud runtime", () => {
53
+ expect(readinessOf(makeChannel(), "local")).toEqual({
54
+ status: "cloud-only",
55
+ });
56
+ });
57
+
58
+ it("is not-installed for a pending install", () => {
59
+ expect(
60
+ readinessOf(
61
+ makeChannel({
62
+ installState: AgentChannelInstallState.pending_install,
63
+ }),
64
+ ),
65
+ ).toEqual({ status: "not-installed" });
66
+ });
67
+
68
+ it("is not-installed for a revoked install — coarse on purpose, describeChannel carries the distinction", () => {
69
+ expect(
70
+ readinessOf(
71
+ makeChannel({ installState: AgentChannelInstallState.revoked }),
72
+ ),
73
+ ).toEqual({ status: "not-installed" });
74
+ });
75
+
76
+ it("is channel-off when the serving switch is off", () => {
77
+ expect(readinessOf(makeChannel({ enabled: false }))).toEqual({
78
+ status: "channel-off",
79
+ });
80
+ });
81
+
82
+ it("is not-proactive when the grant is missing", () => {
83
+ expect(
84
+ readinessOf(makeChannel({ proactiveMessagingEnabled: false })),
85
+ ).toEqual({ status: "not-proactive" });
86
+ });
87
+
88
+ // The gate order is the server's (ChannelMessagingReach.resolveDirect:
89
+ // installed → enabled → proactive). A channel failing several gates
90
+ // must report the FIRST, so the teaching state names the next fix.
91
+ it("resolves gates in the server's order — first failure wins", () => {
92
+ expect(
93
+ readinessOf(
94
+ makeChannel({
95
+ installState: AgentChannelInstallState.pending_install,
96
+ enabled: false,
97
+ proactiveMessagingEnabled: false,
98
+ }),
99
+ "local",
100
+ ),
101
+ ).toEqual({ status: "cloud-only" });
102
+
103
+ expect(
104
+ readinessOf(
105
+ makeChannel({
106
+ installState: AgentChannelInstallState.pending_install,
107
+ enabled: false,
108
+ proactiveMessagingEnabled: false,
109
+ }),
110
+ ),
111
+ ).toEqual({ status: "not-installed" });
112
+
113
+ expect(
114
+ readinessOf(
115
+ makeChannel({ enabled: false, proactiveMessagingEnabled: false }),
116
+ ),
117
+ ).toEqual({ status: "channel-off" });
118
+ });
119
+
120
+ it("treats a channel with no status/spec as not yet installed", () => {
121
+ const bare = { metadata: { id: "ach_2" } } as unknown as AgentChannel;
122
+ expect(readinessOf(bare)).toEqual({ status: "not-installed" });
123
+ });
124
+ });
@@ -17,6 +17,9 @@ export {
17
17
  } from "./providerPresentation.js";
18
18
  export type { ChannelProviderPresentation } from "./providerPresentation.js";
19
19
 
20
+ export { templateStatusPhase, splitTemplateBody } from "./templatePresentation.js";
21
+ export type { TemplateBodySegment } from "./templatePresentation.js";
22
+
20
23
  export { useAgentChannel } from "./useAgentChannel.js";
21
24
  export type { UseAgentChannelReturn } from "./useAgentChannel.js";
22
25
 
@@ -43,9 +46,18 @@ export type {
43
46
 
44
47
  export { useChannelToolReadiness } from "./useChannelToolReadiness.js";
45
48
 
49
+ export { useChannelTemplateReadiness } from "./useChannelTemplateReadiness.js";
50
+ export type { ChannelTemplateReadiness } from "./useChannelTemplateReadiness.js";
51
+
46
52
  export { useChannelSessions } from "./useChannelSessions.js";
47
53
  export type { UseChannelSessionsReturn } from "./useChannelSessions.js";
48
54
 
55
+ export { useChannelTemplateList } from "./useChannelTemplateList.js";
56
+ export type {
57
+ UseChannelTemplateListOptions,
58
+ UseChannelTemplateListReturn,
59
+ } from "./useChannelTemplateList.js";
60
+
49
61
  export { AgentChannelsPanel } from "./AgentChannelsPanel.js";
50
62
  export type { AgentChannelsPanelProps } from "./AgentChannelsPanel.js";
51
63
 
@@ -63,3 +75,6 @@ export type { ChannelToolCredentialsProps } from "./ChannelToolCredentials.js";
63
75
 
64
76
  export { ChannelConversationsDialog } from "./ChannelConversationsDialog.js";
65
77
  export type { ChannelConversationsDialogProps } from "./ChannelConversationsDialog.js";
78
+
79
+ export { ChannelTemplatesDialog } from "./ChannelTemplatesDialog.js";
80
+ export type { ChannelTemplatesDialogProps } from "./ChannelTemplatesDialog.js";
@@ -43,6 +43,18 @@ export interface ChannelProviderDescriptor {
43
43
  readonly Icon: ComponentType<{ readonly className?: string }>;
44
44
  /** How this provider's install flow runs. */
45
45
  readonly installStyle: ChannelInstallStyle;
46
+ /**
47
+ * Whether this provider has a message-template registry the console
48
+ * can list (`listTemplates`). Mirrors the server's proactive-sender
49
+ * registry, which is the de-facto provider filter behind that RPC —
50
+ * a provider with no registered sender answers `PROVIDER_UNSUPPORTED`.
51
+ *
52
+ * `false` for Slack is not a gap: Slack has no template concept at
53
+ * all, so consumers hide the Templates affordance entirely rather
54
+ * than disable it — a disabled item would falsely imply it may work
55
+ * there one day.
56
+ */
57
+ readonly supportsMessageTemplates: boolean;
46
58
  }
47
59
 
48
60
  /**
@@ -55,8 +67,20 @@ export interface ChannelProviderDescriptor {
55
67
  * demos. A third entry is the signal to fold these into a provider menu.
56
68
  */
57
69
  export const CHANNEL_PROVIDERS: readonly ChannelProviderDescriptor[] = [
58
- { id: "slack", label: "Slack", Icon: SlackMarkIcon, installStyle: "redirect" },
59
- { id: "whatsapp", label: "WhatsApp", Icon: WhatsAppMarkIcon, installStyle: "direct" },
70
+ {
71
+ id: "slack",
72
+ label: "Slack",
73
+ Icon: SlackMarkIcon,
74
+ installStyle: "redirect",
75
+ supportsMessageTemplates: false,
76
+ },
77
+ {
78
+ id: "whatsapp",
79
+ label: "WhatsApp",
80
+ Icon: WhatsAppMarkIcon,
81
+ installStyle: "direct",
82
+ supportsMessageTemplates: true,
83
+ },
60
84
  ];
61
85
 
62
86
  /**
@@ -0,0 +1,82 @@
1
+ import type { StatusPhase } from "../resource-workbench/types.js";
2
+
3
+ /**
4
+ * Display helpers for provider message templates — the template
5
+ * counterpart of {@link ChannelProviderPresentation}'s module split:
6
+ * `ChannelTemplatesDialog` renders, this module decides how a
7
+ * provider-verbatim template projects onto the SDK's display
8
+ * vocabulary. Pure functions, one writer each.
9
+ */
10
+
11
+ /**
12
+ * Map a provider-verbatim template status onto a {@link StatusPhase}
13
+ * for `StatusBadge` coloring.
14
+ *
15
+ * The wire carries the provider's own vocabulary, never a Stigmer enum
16
+ * (DD-003 D6) — so the caller renders the verbatim status string as the
17
+ * badge label and uses this phase only for the color. WhatsApp's
18
+ * observed set exceeds the documented five (IN_APPEAL, LIMIT_EXCEEDED,
19
+ * PENDING_DELETION, DELETED occur, and Meta can add more), so unknown
20
+ * statuses deliberately land on `draft` — a neutral tone that renders
21
+ * the provider's word without claiming to understand it.
22
+ */
23
+ export function templateStatusPhase(status: string): StatusPhase {
24
+ switch (status) {
25
+ case "APPROVED":
26
+ return "ready";
27
+ case "PENDING":
28
+ case "IN_APPEAL":
29
+ return "pending";
30
+ case "PAUSED":
31
+ case "LIMIT_EXCEEDED":
32
+ return "degraded";
33
+ case "REJECTED":
34
+ case "DISABLED":
35
+ return "failed";
36
+ case "PENDING_DELETION":
37
+ case "DELETED":
38
+ return "disabled";
39
+ default:
40
+ return "draft";
41
+ }
42
+ }
43
+
44
+ /** One segment of a template body: literal text or a `{{...}}` placeholder. */
45
+ export interface TemplateBodySegment {
46
+ readonly kind: "text" | "placeholder";
47
+ /** The verbatim slice, braces included for placeholders (`"{{1}}"`). */
48
+ readonly value: string;
49
+ }
50
+
51
+ /**
52
+ * The provider's placeholder shape: `{{1}}` (positional) or
53
+ * `{{customer_name}}` (named). Single braces and unbalanced braces are
54
+ * literal text — the provider would not have accepted them either.
55
+ */
56
+ const PLACEHOLDER = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/g;
57
+
58
+ /**
59
+ * Split a template body into text and placeholder segments so the
60
+ * placeholders — the parts the agent fills at send time — can render
61
+ * visually distinct from the literal copy.
62
+ *
63
+ * The concatenation of all segment values is always the input,
64
+ * verbatim: this function classifies, it never rewrites.
65
+ */
66
+ export function splitTemplateBody(
67
+ body: string,
68
+ ): readonly TemplateBodySegment[] {
69
+ const segments: TemplateBodySegment[] = [];
70
+ let lastIndex = 0;
71
+ for (const match of body.matchAll(PLACEHOLDER)) {
72
+ if (match.index > lastIndex) {
73
+ segments.push({ kind: "text", value: body.slice(lastIndex, match.index) });
74
+ }
75
+ segments.push({ kind: "placeholder", value: match[0] });
76
+ lastIndex = match.index + match[0].length;
77
+ }
78
+ if (lastIndex < body.length) {
79
+ segments.push({ kind: "text", value: body.slice(lastIndex) });
80
+ }
81
+ return segments;
82
+ }
@@ -0,0 +1,123 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import type { ChannelTemplate } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
6
+ import { ListChannelTemplatesInputSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
7
+ import { useStigmer } from "../hooks.js";
8
+ import { useFetch } from "../internal/useFetch.js";
9
+
10
+ /** Options for {@link useChannelTemplateList}. */
11
+ export interface UseChannelTemplateListOptions {
12
+ /**
13
+ * When `true`, only templates the provider will accept for sending
14
+ * are returned (WhatsApp: status APPROVED). The console leaves it
15
+ * unset to render every status with its badge; agent-composition
16
+ * surfaces set it so a model never drafts against a paused or
17
+ * pending template.
18
+ */
19
+ readonly approvedOnly?: boolean;
20
+ }
21
+
22
+ /** Return value of {@link useChannelTemplateList}. */
23
+ export interface UseChannelTemplateListReturn {
24
+ /**
25
+ * The channel provider's templates, verbatim, in provider order.
26
+ * Empty while loading, on error, and when the registry answered with
27
+ * no templates — `templates.length === 0 && !isLoading && !error`
28
+ * means the provider reported none, which callers should phrase as
29
+ * "none found" rather than "none exist" (an empty success is
30
+ * indistinguishable from a filtered or truncated one).
31
+ */
32
+ readonly templates: readonly ChannelTemplate[];
33
+ /** `true` while the initial fetch is in flight and no data is shown. */
34
+ readonly isLoading: boolean;
35
+ /** `true` while a background refetch is in flight and stale data is shown. */
36
+ readonly isRefetching: boolean;
37
+ /** Error from the last failed request, or `null` when healthy. */
38
+ readonly error: Error | null;
39
+ /** Re-fetch the templates from the server. */
40
+ readonly refetch: () => void;
41
+ }
42
+
43
+ /**
44
+ * How often to re-read the registry while any template is PENDING.
45
+ * Matches the server's template-cache TTL (60s) — polling faster only
46
+ * re-reads a cached answer.
47
+ */
48
+ const PENDING_POLL_INTERVAL_MS = 60_000;
49
+
50
+ /**
51
+ * Data hook that lists a channel's message templates as the channel's
52
+ * provider registry reports them (`listTemplates`) — every field
53
+ * provider-verbatim, including approval `status`, the provider's
54
+ * `rejectionReason`, and Stigmer's own `unsupportedReason` (why this
55
+ * platform version cannot send an otherwise-approved template).
56
+ *
57
+ * The channel is addressed by `slug` + `org` (not id), because that is
58
+ * the RPC's key — the provider registry is derived from the channel's
59
+ * phone number, not stored on any Stigmer resource. Pass an empty
60
+ * `channelSlug` or `org` to skip fetching (stable no-op).
61
+ *
62
+ * While any entry is PENDING the hook polls every 60 seconds — the
63
+ * server's registry-cache TTL, so a faster interval would only re-read
64
+ * a cached answer. There is deliberately no `refetchInterval` option:
65
+ * the pending-only conditional poll is the hook's policy, and
66
+ * `approvedOnly` (a wire field) is the only knob exposed.
67
+ *
68
+ * Results are never cached across mounts: a template's approval status
69
+ * can change on the provider's side at any time, and rendering a stale
70
+ * "APPROVED" is exactly what this surface must not do.
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * const { templates, isLoading, error } = useChannelTemplateList(
75
+ * channel.metadata?.slug ?? "",
76
+ * channel.metadata?.org ?? "",
77
+ * );
78
+ * ```
79
+ */
80
+ export function useChannelTemplateList(
81
+ channelSlug: string,
82
+ org: string,
83
+ options?: UseChannelTemplateListOptions,
84
+ ): UseChannelTemplateListReturn {
85
+ const stigmer = useStigmer();
86
+ const approvedOnly = options?.approvedOnly ?? false;
87
+
88
+ // The pending-only poll rides useFetch's own guarded interval (the
89
+ // ONE polling implementation) rather than a second setInterval here.
90
+ // The interval cannot be derived inline — it depends on the data the
91
+ // same useFetch call returns — so it round-trips through state: data
92
+ // arrives, the effect below flips the interval, useFetch re-arms.
93
+ const [pollInterval, setPollInterval] = useState<number | false>(false);
94
+
95
+ const fetchFn =
96
+ channelSlug && org
97
+ ? async () => {
98
+ const result = await stigmer.agentChannel.listTemplates(
99
+ create(ListChannelTemplatesInputSchema, {
100
+ channel: channelSlug,
101
+ org,
102
+ approvedOnly,
103
+ }),
104
+ );
105
+ return result.entries;
106
+ }
107
+ : null;
108
+
109
+ const { data: templates, isLoading, isRefetching, error, refetch } =
110
+ useFetch(
111
+ fetchFn,
112
+ [channelSlug, org, approvedOnly, stigmer],
113
+ [] as ChannelTemplate[],
114
+ { refetchInterval: pollInterval },
115
+ );
116
+
117
+ const hasPending = templates.some((t) => t.status === "PENDING");
118
+ useEffect(() => {
119
+ setPollInterval(hasPending ? PENDING_POLL_INTERVAL_MS : false);
120
+ }, [hasPending]);
121
+
122
+ return { templates, isLoading, isRefetching, error, refetch };
123
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
4
+ import { AgentChannelInstallState } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/status_pb";
5
+ import { useDeploymentMode } from "../deployment-mode.js";
6
+
7
+ /**
8
+ * Whether a channel can answer a template-registry read
9
+ * (`listTemplates`), and if not, which precondition fails first.
10
+ *
11
+ * - `cloud-only` — the deployment is not cloud; the template registry
12
+ * is part of the cloud channel runtime.
13
+ * - `not-installed` — the provider install has not completed (covers
14
+ * both pending and revoked; render the per-provider
15
+ * `describeChannel` sentence, which already distinguishes them).
16
+ * - `channel-off` — the owner's serving switch (`spec.enabled`) is off.
17
+ * - `not-proactive` — `spec.proactive_messaging_enabled` is not set;
18
+ * business-initiated messaging (and with it the template surface)
19
+ * is not granted on this channel.
20
+ * - `ready` — the read can be made.
21
+ */
22
+ export type ChannelTemplateReadiness =
23
+ | { readonly status: "cloud-only" }
24
+ | { readonly status: "not-installed" }
25
+ | { readonly status: "channel-off" }
26
+ | { readonly status: "not-proactive" }
27
+ | { readonly status: "ready" };
28
+
29
+ /**
30
+ * Resolve whether a channel's template registry can be read, without a
31
+ * network call — the courtesy pre-check for the Templates surface.
32
+ *
33
+ * Mirrors the server's own gate order for `listTemplates`
34
+ * (`ChannelMessagingReach.resolveDirect`: installed → enabled →
35
+ * proactive grant), answering from fields already on the fetched
36
+ * {@link AgentChannel}. The server stays authoritative: a `ready`
37
+ * verdict here only means the call is worth making, and whatever the
38
+ * server then says wins.
39
+ *
40
+ * Two shape choices are deliberate. `channel-off` and `not-proactive`
41
+ * are split although the server collapses them into one refusal —
42
+ * their fixes differ (flip the card's serving switch vs. grant
43
+ * `proactive_messaging_enabled`). And `not-installed` stays coarse
44
+ * across pending/revoked, because `providerPresentation`'s
45
+ * `describeChannel` already renders the right sentence for each.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * const readiness = useChannelTemplateReadiness(channel);
50
+ * if (readiness.status !== "ready") return <TeachingState ... />;
51
+ * ```
52
+ */
53
+ export function useChannelTemplateReadiness(
54
+ channel: AgentChannel,
55
+ ): ChannelTemplateReadiness {
56
+ const deploymentMode = useDeploymentMode();
57
+
58
+ if (deploymentMode !== "cloud") return { status: "cloud-only" };
59
+ if (
60
+ channel.status?.installState !== AgentChannelInstallState.installed
61
+ ) {
62
+ return { status: "not-installed" };
63
+ }
64
+ if (!channel.spec?.enabled) return { status: "channel-off" };
65
+ if (!channel.spec.proactiveMessagingEnabled) {
66
+ return { status: "not-proactive" };
67
+ }
68
+ return { status: "ready" };
69
+ }
@@ -6,6 +6,7 @@ import { ConfigureMenu, type ConfigureMenuItem } from "./ConfigureMenu.js";
6
6
  import { ModelSelector } from "../models/ModelSelector.js";
7
7
  import { HarnessSelector } from "../models/HarnessSelector.js";
8
8
  import type { HarnessOption } from "../models/harness.js";
9
+ import type { ServiceTierOption } from "../models/service-tier.js";
9
10
  import { InteractionModePicker, type InteractionModeOption } from "./InteractionModePicker.js";
10
11
  import {
11
12
  PaperclipIcon,
@@ -42,6 +43,10 @@ export interface ComposerToolbarProps {
42
43
  readonly showModelSelector: boolean;
43
44
  readonly modelId?: string;
44
45
  readonly onModelChange: (id: string) => void;
46
+ /** Current service tier for the selected model (#357). */
47
+ readonly serviceTier?: ServiceTierOption;
48
+ /** Enables the fast-tier toggle inside the model selector. */
49
+ readonly onServiceTierChange?: (tier: ServiceTierOption) => void;
45
50
 
46
51
  // -- Right group: Secondary actions (icon-only) ---------------------------
47
52
 
@@ -115,6 +120,8 @@ export function ComposerToolbar({
115
120
  showModelSelector,
116
121
  modelId,
117
122
  onModelChange,
123
+ serviceTier,
124
+ onServiceTierChange,
118
125
  }: ComposerToolbarProps) {
119
126
  const showHarnessSeparate = showHarnessSelector && !showModelSelector;
120
127
 
@@ -146,6 +153,8 @@ export function ComposerToolbar({
146
153
  harness={showHarnessSelector ? undefined : harness}
147
154
  initialHarness={showHarnessSelector ? harness : undefined}
148
155
  onHarnessChange={showHarnessSelector ? onHarnessChange : undefined}
156
+ serviceTier={serviceTier}
157
+ onServiceTierChange={onServiceTierChange}
149
158
  disabled={disabled}
150
159
  />
151
160
  )}
@@ -7,6 +7,7 @@ import { useComposer } from "./useComposer.js";
7
7
  import { ComposerToolbar } from "./ComposerToolbar.js";
8
8
  import { type ConfigureMenuItem } from "./ConfigureMenu.js";
9
9
  import type { HarnessOption } from "../models/harness.js";
10
+ import type { ServiceTierOption } from "../models/service-tier.js";
10
11
  import type { InteractionModeOption } from "./InteractionModePicker.js";
11
12
  import { parseModelKey } from "../models/registry.js";
12
13
  import { WorkspaceEditor } from "../workspace/WorkspaceEditor.js";
@@ -147,6 +148,14 @@ export interface SessionComposerSubmitContext {
147
148
  * Pass to execution creation as `execution_config.interaction_mode`.
148
149
  */
149
150
  readonly interactionMode?: InteractionModeOption;
151
+ /**
152
+ * Service tier the user actively selected for this execution
153
+ * (stigmer/stigmer#357). Only ever `"fast"` — an untouched toggle means
154
+ * "platform default" and is carried as `undefined`, preserving the
155
+ * unspecified-vs-explicit distinction all the way to the ledger.
156
+ * Pass to execution creation as `execution_config.service_tier`.
157
+ */
158
+ readonly serviceTier?: ServiceTierOption;
150
159
  /**
151
160
  * Workspace-relative file paths the user referenced via drag-to-reference.
152
161
  *
@@ -622,6 +631,11 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
622
631
  setModelIdRaw(id);
623
632
  }, []);
624
633
 
634
+ // Service tier (#357): composer-local state like modelId. "standard" is
635
+ // the resting default; the ModelSelector's fail-safe resets it when the
636
+ // user switches to a model without a fast tier.
637
+ const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
638
+
625
639
  const [displayNames, setDisplayNames] = useState<Map<string, string>>(
626
640
  () => new Map(),
627
641
  );
@@ -850,13 +864,20 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
850
864
  : undefined);
851
865
  const hasFileRefs = enableFileReferences && fileRefs.hasRefs;
852
866
  const buildFromPlan = overrides?.buildFromPlan;
867
+ // Carried only when the user actively chose fast: an untouched
868
+ // toggle means "platform default" (which resolves to standard in
869
+ // the runner), and the UNSPECIFIED-vs-explicit distinction is
870
+ // load-bearing telemetry (#357).
871
+ const effectiveServiceTier = serviceTier === "fast" ? serviceTier : undefined;
853
872
 
854
873
  const context: SessionComposerSubmitContext | undefined =
855
874
  hasEnv || hasAttachments || effectiveMode || hasFileRefs || buildFromPlan
875
+ || effectiveServiceTier
856
876
  ? {
857
877
  runtimeEnv: hasEnv ? env : undefined,
858
878
  attachments: hasAttachments ? attachmentInputs : undefined,
859
879
  interactionMode: effectiveMode,
880
+ serviceTier: effectiveServiceTier,
860
881
  workspaceFileRefs: hasFileRefs ? [...fileRefs.refs] : undefined,
861
882
  buildFromPlan,
862
883
  }
@@ -874,7 +895,7 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
874
895
  fileRefs.clear();
875
896
  }
876
897
  },
877
- [onSubmit, modelId, stigmer, agentSetup.state, mcpSetup.pendingRuntimeEnv, sessionVariables, enableAttachments, attachments, personalEnv, showInteractionModePicker, interactionMode],
898
+ [onSubmit, modelId, serviceTier, stigmer, agentSetup.state, mcpSetup.pendingRuntimeEnv, sessionVariables, enableAttachments, attachments, personalEnv, showInteractionModePicker, interactionMode],
878
899
  );
879
900
 
880
901
  const composer = useComposer({
@@ -1614,6 +1635,8 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
1614
1635
  showModelSelector={showModelSelector}
1615
1636
  modelId={modelId}
1616
1637
  onModelChange={handleModelChange}
1638
+ serviceTier={serviceTier}
1639
+ onServiceTierChange={setServiceTier}
1617
1640
  />
1618
1641
  </div>
1619
1642
  </div>