@stigmer/react 3.6.0 → 3.8.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 (260) hide show
  1. package/billing/CreditLedgerTable.d.ts.map +1 -1
  2. package/billing/CreditLedgerTable.js +2 -7
  3. package/billing/CreditLedgerTable.js.map +1 -1
  4. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  5. package/channel/AgentChannelsPanel.js +21 -2
  6. package/channel/AgentChannelsPanel.js.map +1 -1
  7. package/channel/ChannelConversationsDialog.d.ts +12 -4
  8. package/channel/ChannelConversationsDialog.d.ts.map +1 -1
  9. package/channel/ChannelConversationsDialog.js +18 -24
  10. package/channel/ChannelConversationsDialog.js.map +1 -1
  11. package/channel/providers.d.ts +23 -0
  12. package/channel/providers.d.ts.map +1 -1
  13. package/channel/providers.js +4 -0
  14. package/channel/providers.js.map +1 -1
  15. package/channel/useSaveAgentChannel.d.ts +5 -4
  16. package/channel/useSaveAgentChannel.d.ts.map +1 -1
  17. package/channel/useSaveAgentChannel.js +18 -4
  18. package/channel/useSaveAgentChannel.js.map +1 -1
  19. package/composer/ComposerToolbar.d.ts +6 -1
  20. package/composer/ComposerToolbar.d.ts.map +1 -1
  21. package/composer/ComposerToolbar.js +2 -2
  22. package/composer/ComposerToolbar.js.map +1 -1
  23. package/composer/SessionComposer.d.ts +9 -0
  24. package/composer/SessionComposer.d.ts.map +1 -1
  25. package/composer/SessionComposer.js +13 -2
  26. package/composer/SessionComposer.js.map +1 -1
  27. package/conversation/ConversationAttentionBanner.d.ts +22 -0
  28. package/conversation/ConversationAttentionBanner.d.ts.map +1 -0
  29. package/conversation/ConversationAttentionBanner.js +25 -0
  30. package/conversation/ConversationAttentionBanner.js.map +1 -0
  31. package/conversation/ConversationComposer.d.ts +41 -0
  32. package/conversation/ConversationComposer.d.ts.map +1 -0
  33. package/conversation/ConversationComposer.js +57 -0
  34. package/conversation/ConversationComposer.js.map +1 -0
  35. package/conversation/ConversationControlBanner.d.ts +52 -0
  36. package/conversation/ConversationControlBanner.d.ts.map +1 -0
  37. package/conversation/ConversationControlBanner.js +59 -0
  38. package/conversation/ConversationControlBanner.js.map +1 -0
  39. package/conversation/ConversationListPane.d.ts +68 -0
  40. package/conversation/ConversationListPane.d.ts.map +1 -0
  41. package/conversation/ConversationListPane.js +120 -0
  42. package/conversation/ConversationListPane.js.map +1 -0
  43. package/conversation/ConversationTimelineView.d.ts +46 -0
  44. package/conversation/ConversationTimelineView.d.ts.map +1 -0
  45. package/conversation/ConversationTimelineView.js +166 -0
  46. package/conversation/ConversationTimelineView.js.map +1 -0
  47. package/conversation/ConversationsWorkbench.d.ts +63 -0
  48. package/conversation/ConversationsWorkbench.d.ts.map +1 -0
  49. package/conversation/ConversationsWorkbench.js +165 -0
  50. package/conversation/ConversationsWorkbench.js.map +1 -0
  51. package/conversation/conversationPresentation.d.ts +137 -0
  52. package/conversation/conversationPresentation.d.ts.map +1 -0
  53. package/conversation/conversationPresentation.js +230 -0
  54. package/conversation/conversationPresentation.js.map +1 -0
  55. package/conversation/index.d.ts +26 -0
  56. package/conversation/index.d.ts.map +1 -0
  57. package/conversation/index.js +14 -0
  58. package/conversation/index.js.map +1 -0
  59. package/conversation/polling.d.ts +39 -0
  60. package/conversation/polling.d.ts.map +1 -0
  61. package/conversation/polling.js +39 -0
  62. package/conversation/polling.js.map +1 -0
  63. package/conversation/useConversation.d.ts +61 -0
  64. package/conversation/useConversation.d.ts.map +1 -0
  65. package/conversation/useConversation.js +153 -0
  66. package/conversation/useConversation.js.map +1 -0
  67. package/conversation/useConversationList.d.ts +98 -0
  68. package/conversation/useConversationList.d.ts.map +1 -0
  69. package/conversation/useConversationList.js +200 -0
  70. package/conversation/useConversationList.js.map +1 -0
  71. package/conversation/useConversationParticipation.d.ts +63 -0
  72. package/conversation/useConversationParticipation.d.ts.map +1 -0
  73. package/conversation/useConversationParticipation.js +84 -0
  74. package/conversation/useConversationParticipation.js.map +1 -0
  75. package/conversation/useConversationTimeline.d.ts +71 -0
  76. package/conversation/useConversationTimeline.d.ts.map +1 -0
  77. package/conversation/useConversationTimeline.js +152 -0
  78. package/conversation/useConversationTimeline.js.map +1 -0
  79. package/conversation/useConversationsWantsHumanCount.d.ts +42 -0
  80. package/conversation/useConversationsWantsHumanCount.d.ts.map +1 -0
  81. package/conversation/useConversationsWantsHumanCount.js +40 -0
  82. package/conversation/useConversationsWantsHumanCount.js.map +1 -0
  83. package/execution/useCreateAgentExecution.d.ts +14 -0
  84. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  85. package/execution/useCreateAgentExecution.js +5 -0
  86. package/execution/useCreateAgentExecution.js.map +1 -1
  87. package/index.d.ts +6 -2
  88. package/index.d.ts.map +1 -1
  89. package/index.js +14 -5
  90. package/index.js.map +1 -1
  91. package/internal/Pagination.d.ts +15 -0
  92. package/internal/Pagination.d.ts.map +1 -0
  93. package/internal/Pagination.js +20 -0
  94. package/internal/Pagination.js.map +1 -0
  95. package/internal/tooltip.d.ts.map +1 -1
  96. package/internal/tooltip.js +7 -1
  97. package/internal/tooltip.js.map +1 -1
  98. package/internal/useAutoScroll.d.ts +25 -4
  99. package/internal/useAutoScroll.d.ts.map +1 -1
  100. package/internal/useAutoScroll.js +55 -15
  101. package/internal/useAutoScroll.js.map +1 -1
  102. package/internal/useFetch.d.ts.map +1 -1
  103. package/internal/useFetch.js +14 -4
  104. package/internal/useFetch.js.map +1 -1
  105. package/models/ModelSelector.d.ts +45 -1
  106. package/models/ModelSelector.d.ts.map +1 -1
  107. package/models/ModelSelector.js +49 -6
  108. package/models/ModelSelector.js.map +1 -1
  109. package/models/index.d.ts +2 -0
  110. package/models/index.d.ts.map +1 -1
  111. package/models/index.js +1 -0
  112. package/models/index.js.map +1 -1
  113. package/models/registry.d.ts +11 -0
  114. package/models/registry.d.ts.map +1 -1
  115. package/models/registry.js +3 -0
  116. package/models/registry.js.map +1 -1
  117. package/models/service-tier.d.ts +27 -0
  118. package/models/service-tier.d.ts.map +1 -0
  119. package/models/service-tier.js +34 -0
  120. package/models/service-tier.js.map +1 -0
  121. package/package.json +4 -4
  122. package/schedule/ScheduleDetailView.d.ts +31 -8
  123. package/schedule/ScheduleDetailView.d.ts.map +1 -1
  124. package/schedule/ScheduleDetailView.js +441 -29
  125. package/schedule/ScheduleDetailView.js.map +1 -1
  126. package/schedule/ScheduleForm.d.ts +7 -4
  127. package/schedule/ScheduleForm.d.ts.map +1 -1
  128. package/schedule/ScheduleForm.js +94 -8
  129. package/schedule/ScheduleForm.js.map +1 -1
  130. package/schedule/ScheduleRunsTable.d.ts +72 -0
  131. package/schedule/ScheduleRunsTable.d.ts.map +1 -0
  132. package/schedule/ScheduleRunsTable.js +161 -0
  133. package/schedule/ScheduleRunsTable.js.map +1 -0
  134. package/schedule/index.d.ts +6 -0
  135. package/schedule/index.d.ts.map +1 -1
  136. package/schedule/index.js +3 -0
  137. package/schedule/index.js.map +1 -1
  138. package/schedule/useScheduleRuns.d.ts +44 -0
  139. package/schedule/useScheduleRuns.d.ts.map +1 -0
  140. package/schedule/useScheduleRuns.js +48 -0
  141. package/schedule/useScheduleRuns.js.map +1 -0
  142. package/schedule/useTriggerSchedule.d.ts +23 -10
  143. package/schedule/useTriggerSchedule.d.ts.map +1 -1
  144. package/schedule/useTriggerSchedule.js +28 -9
  145. package/schedule/useTriggerSchedule.js.map +1 -1
  146. package/schedule/useUpdateScheduleSpec.d.ts +49 -0
  147. package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
  148. package/schedule/useUpdateScheduleSpec.js +70 -0
  149. package/schedule/useUpdateScheduleSpec.js.map +1 -0
  150. package/session/useNewSessionFlow.d.ts.map +1 -1
  151. package/session/useNewSessionFlow.js +1 -0
  152. package/session/useNewSessionFlow.js.map +1 -1
  153. package/session/useSessionConversation.d.ts +11 -0
  154. package/session/useSessionConversation.d.ts.map +1 -1
  155. package/session/useSessionConversation.js +1 -0
  156. package/session/useSessionConversation.js.map +1 -1
  157. package/session/useSessionPageFlow.d.ts.map +1 -1
  158. package/session/useSessionPageFlow.js +1 -0
  159. package/session/useSessionPageFlow.js.map +1 -1
  160. package/sharing/useSaveAgentShare.d.ts +10 -1
  161. package/sharing/useSaveAgentShare.d.ts.map +1 -1
  162. package/sharing/useSaveAgentShare.js +9 -0
  163. package/sharing/useSaveAgentShare.js.map +1 -1
  164. package/sidebar/WorkspaceSidebar.d.ts +12 -4
  165. package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
  166. package/sidebar/WorkspaceSidebar.js +19 -7
  167. package/sidebar/WorkspaceSidebar.js.map +1 -1
  168. package/src/billing/CreditLedgerTable.tsx +3 -42
  169. package/src/channel/AgentChannelsPanel.tsx +33 -2
  170. package/src/channel/ChannelConversationsDialog.tsx +22 -25
  171. package/src/channel/__tests__/AgentChannelsPanel.test.tsx +36 -5
  172. package/src/channel/__tests__/ChannelConversationsDialog.test.tsx +38 -1
  173. package/src/channel/__tests__/useSaveAgentChannel.test.tsx +26 -0
  174. package/src/channel/providers.ts +27 -0
  175. package/src/channel/useSaveAgentChannel.ts +18 -4
  176. package/src/composer/ComposerToolbar.tsx +9 -0
  177. package/src/composer/SessionComposer.tsx +24 -1
  178. package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +157 -0
  179. package/src/conversation/ConversationAttentionBanner.tsx +77 -0
  180. package/src/conversation/ConversationComposer.tsx +156 -0
  181. package/src/conversation/ConversationControlBanner.tsx +193 -0
  182. package/src/conversation/ConversationListPane.tsx +459 -0
  183. package/src/conversation/ConversationTimelineView.tsx +435 -0
  184. package/src/conversation/ConversationsWorkbench.tsx +378 -0
  185. package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +82 -0
  186. package/src/conversation/__tests__/ConversationComposer.test.tsx +160 -0
  187. package/src/conversation/__tests__/ConversationControlBanner.test.tsx +190 -0
  188. package/src/conversation/__tests__/ConversationListPane.test.tsx +372 -0
  189. package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +160 -0
  190. package/src/conversation/__tests__/ConversationTimelineView.test.tsx +405 -0
  191. package/src/conversation/__tests__/ConversationsWorkbench.test.tsx +860 -0
  192. package/src/conversation/__tests__/a11y/conversation-surfaces.a11y.test.tsx +235 -0
  193. package/src/conversation/__tests__/a11y/harness.tsx +100 -0
  194. package/src/conversation/__tests__/conversation-tooltips.layout.test.tsx +167 -0
  195. package/src/conversation/__tests__/conversationPollingComposition.test.tsx +138 -0
  196. package/src/conversation/__tests__/conversationPresentation.test.ts +289 -0
  197. package/src/conversation/__tests__/useConversation.test.tsx +243 -0
  198. package/src/conversation/__tests__/useConversationList.test.tsx +492 -0
  199. package/src/conversation/__tests__/useConversationParticipation.test.tsx +190 -0
  200. package/src/conversation/__tests__/useConversationTimeline.test.tsx +258 -0
  201. package/src/conversation/__tests__/useConversationsWantsHumanCount.test.tsx +102 -0
  202. package/src/conversation/conversationPresentation.ts +305 -0
  203. package/src/conversation/index.ts +80 -0
  204. package/src/conversation/polling.ts +41 -0
  205. package/src/conversation/useConversation.ts +221 -0
  206. package/src/conversation/useConversationList.ts +328 -0
  207. package/src/conversation/useConversationParticipation.ts +178 -0
  208. package/src/conversation/useConversationTimeline.ts +244 -0
  209. package/src/conversation/useConversationsWantsHumanCount.ts +86 -0
  210. package/src/execution/useCreateAgentExecution.ts +18 -0
  211. package/src/index.ts +76 -4
  212. package/src/internal/Pagination.tsx +72 -0
  213. package/src/internal/__tests__/fake-timer-slices.ts +29 -0
  214. package/src/internal/__tests__/useAutoScroll.layout.test.tsx +143 -0
  215. package/src/internal/__tests__/useAutoScroll.test.tsx +62 -5
  216. package/src/internal/__tests__/useFetch-poll-under-render-pressure.test.tsx +79 -0
  217. package/src/internal/tooltip.tsx +6 -1
  218. package/src/internal/useAutoScroll.ts +66 -20
  219. package/src/internal/useFetch.ts +15 -4
  220. package/src/models/ModelSelector.tsx +133 -4
  221. package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +256 -0
  222. package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
  223. package/src/models/index.ts +2 -0
  224. package/src/models/registry.ts +17 -0
  225. package/src/models/service-tier.ts +49 -0
  226. package/src/schedule/ScheduleDetailView.tsx +1179 -111
  227. package/src/schedule/ScheduleForm.tsx +218 -7
  228. package/src/schedule/ScheduleRunsTable.tsx +424 -0
  229. package/src/schedule/__tests__/ScheduleDetailView.test.tsx +431 -8
  230. package/src/schedule/__tests__/scheduleCreation.test.tsx +197 -0
  231. package/src/schedule/__tests__/scheduleHooks.test.tsx +94 -7
  232. package/src/schedule/index.ts +12 -0
  233. package/src/schedule/useScheduleRuns.ts +93 -0
  234. package/src/schedule/useTriggerSchedule.ts +39 -14
  235. package/src/schedule/useUpdateScheduleSpec.ts +108 -0
  236. package/src/session/useNewSessionFlow.ts +1 -0
  237. package/src/session/useSessionConversation.ts +12 -0
  238. package/src/session/useSessionPageFlow.ts +1 -0
  239. package/src/sharing/__tests__/useSaveAgentShare.test.tsx +55 -1
  240. package/src/sharing/__tests__/useShareToolReadiness.test.tsx +1 -0
  241. package/src/sharing/useSaveAgentShare.ts +19 -1
  242. package/src/sidebar/WorkspaceSidebar.tsx +62 -3
  243. package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +56 -1
  244. package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
  245. package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
  246. package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
  247. package/src/workflow/starter-workflow-yaml.ts +2 -1
  248. package/styles.css +1 -1
  249. package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
  250. package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
  251. package/workflow/inspector/forms/AgentCallForm.js +87 -9
  252. package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
  253. package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
  254. package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
  255. package/workflow/inspector/tabs/RuntimeTab.js +31 -5
  256. package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
  257. package/workflow/starter-workflow-yaml.d.ts +1 -1
  258. package/workflow/starter-workflow-yaml.d.ts.map +1 -1
  259. package/workflow/starter-workflow-yaml.js +2 -1
  260. package/workflow/starter-workflow-yaml.js.map +1 -1
@@ -2,12 +2,24 @@
2
2
 
3
3
  import { useCallback, useState, type FormEvent } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
- import { getUserMessage, type ResourceRef } from "@stigmer/sdk";
5
+ import {
6
+ getUserMessage,
7
+ type ResourceRef,
8
+ type RunConfigInput,
9
+ } from "@stigmer/sdk";
6
10
  import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
11
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
7
12
  import { Popover } from "@base-ui/react/popover";
8
13
  import { AgentPicker } from "../agent/AgentPicker.js";
14
+ import { EnvironmentPicker } from "../environment/EnvironmentPicker.js";
15
+ import { useGitHubConnection } from "../github/useGitHubConnection.js";
16
+ import { ModelSelector } from "../models/ModelSelector.js";
17
+ import { toProtoHarness, type HarnessOption } from "../models/harness.js";
18
+ import { toProtoServiceTier, type ServiceTierOption } from "../models/service-tier.js";
9
19
  import { Switch } from "../switch/Switch.js";
10
20
  import { useStigmerPortalContainer } from "../portal-container.js";
21
+ import { useWorkspaceEntries } from "../workspace/useWorkspaceEntries.js";
22
+ import { WorkspaceEditor } from "../workspace/WorkspaceEditor.js";
11
23
  import { CadenceField } from "./CadenceField.js";
12
24
  import { TimeZoneField, browserTimeZone } from "./TimeZoneField.js";
13
25
  import { useCreateSchedule } from "./useCreateSchedule.js";
@@ -33,7 +45,7 @@ export interface ScheduleFormProps {
33
45
  readonly className?: string;
34
46
  }
35
47
 
36
- /** spec.proto pins AgentTarget.message to 8192 characters. */
48
+ /** invocation.proto pins AgentInvocation.message to 8192 characters. */
37
49
  const MESSAGE_MAX_LEN = 8192;
38
50
  /** Show the remaining-characters counter once within this margin. */
39
51
  const MESSAGE_COUNTER_THRESHOLD = 500;
@@ -41,10 +53,13 @@ const MESSAGE_COUNTER_THRESHOLD = 500;
41
53
  /**
42
54
  * Single-page form for creating a {@link Schedule}.
43
55
  *
44
- * Collects the four things a user decides about a schedule — which
45
- * agent, what message, when ({@link CadenceField}, generating the cron
46
- * expression from human-friendly presets), and whether it starts
47
- * enabled then creates it via {@link useCreateSchedule}.
56
+ * The form speaks the composer's vocabulary (DD-018 D-5): a schedule is
57
+ * a saved run — which agent, what message, what workspace, which
58
+ * engine and model, what budget plus a cadence. It reuses the
59
+ * composer's own pickers ({@link ModelSelector}, {@link WorkspaceEditor})
60
+ * so the interactive and scheduled surfaces cannot drift, minus only
61
+ * what unattended makes impossible (no local folders, no attachments,
62
+ * no approval-mode choice).
48
63
  *
49
64
  * Enabled defaults to OFF, deliberately: it matches the proto/YAML
50
65
  * default (an omitted `enabled` is false) and the platform's
@@ -83,6 +98,33 @@ export function ScheduleForm({
83
98
  const [timeZone, setTimeZone] = useState(browserTimeZone);
84
99
  const [enabled, setEnabled] = useState(false);
85
100
  const [pickerOpen, setPickerOpen] = useState(false);
101
+ const [environmentRefs, setEnvironmentRefs] = useState<ResourceRef[]>([]);
102
+
103
+ // Engine & model are one atomic choice: picking a model pins BOTH the
104
+ // harness and the model (the registry scopes models per harness, so a
105
+ // model without its harness is meaningless). Empty model = nothing
106
+ // pinned = the platform defaults apply to both. Browsing harnesses in
107
+ // the popover without picking a model pins nothing.
108
+ const [modelName, setModelName] = useState("");
109
+ const [modelHarness, setModelHarness] = useState<HarnessOption>("cursor");
110
+ // Service tier is scoped by the model choice (#357): the ModelSelector
111
+ // renders the fast-tier switch only while the selected model's registry
112
+ // entry prices a fast variant. An active fast tier survives switches
113
+ // between fast-capable models and resets only when a model without one
114
+ // is picked. No model pinned = no tier pinned (fast requires a model,
115
+ // and the form's reset button clears both).
116
+ const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
117
+
118
+ const [budgetUsd, setBudgetUsd] = useState("");
119
+
120
+ // Workspace the fresh per-fire session clones — git sources only
121
+ // (write-time validated server-side: no client is connected at fire
122
+ // time to serve a local folder). The GitHub repo picker lights up
123
+ // when the user already has a connection; otherwise the editor's
124
+ // manual URL+branch input applies, with no OAuth flow forced into
125
+ // this form.
126
+ const workspace = useWorkspaceEntries();
127
+ const gitHub = useGitHubConnection(org);
86
128
 
87
129
  const trimmedName = name.trim();
88
130
  const trimmedMessage = message.trim();
@@ -108,6 +150,7 @@ export function ScheduleForm({
108
150
  if (!canSubmit || !agentRef) return;
109
151
 
110
152
  clearError();
153
+ const runConfig = buildRunConfig(modelName, budgetUsd, serviceTier);
111
154
  try {
112
155
  const schedule = await create({
113
156
  name: trimmedName,
@@ -115,7 +158,20 @@ export function ScheduleForm({
115
158
  cron,
116
159
  timeZone,
117
160
  enabled,
118
- agent: { agentRef, message: trimmedMessage },
161
+ agent: {
162
+ agentRef,
163
+ message: trimmedMessage,
164
+ // The harness travels with the model it was picked under;
165
+ // no model means no harness — the platform defaults apply.
166
+ ...(modelName !== ""
167
+ ? { harness: toProtoHarness(modelHarness) }
168
+ : {}),
169
+ ...(workspace.hasEntries
170
+ ? { workspaceEntries: workspace.toInput() }
171
+ : {}),
172
+ ...(environmentRefs.length > 0 ? { environmentRefs } : {}),
173
+ ...(runConfig ? { runConfig } : {}),
174
+ },
119
175
  });
120
176
  onComplete?.(schedule);
121
177
  } catch {
@@ -133,6 +189,12 @@ export function ScheduleForm({
133
189
  timeZone,
134
190
  enabled,
135
191
  trimmedMessage,
192
+ environmentRefs,
193
+ modelName,
194
+ modelHarness,
195
+ serviceTier,
196
+ budgetUsd,
197
+ workspace,
136
198
  onComplete,
137
199
  ],
138
200
  );
@@ -224,6 +286,53 @@ export function ScheduleForm({
224
286
  )}
225
287
  </div>
226
288
 
289
+ {/* Workspace — what each fire's fresh session operates on
290
+ (DD-018 D-4). Git sources only; the server refuses local
291
+ folders at write time because no client is connected when a
292
+ schedule fires. */}
293
+ <div className="space-y-1">
294
+ <span className={labelClasses}>
295
+ Workspace{" "}
296
+ <span className="font-normal text-muted-foreground">(optional)</span>
297
+ </span>
298
+ <WorkspaceEditor
299
+ workspace={workspace}
300
+ gitHubConnection={gitHub.isConnected ? gitHub : undefined}
301
+ enableGitHub
302
+ enableLocal={false}
303
+ disabled={isCreating}
304
+ />
305
+ <p className={hintClasses}>
306
+ Each run clones these repositories fresh. Private repositories need
307
+ a <code className="font-mono">GITHUB_TOKEN</code> in one of this
308
+ schedule&rsquo;s environments; public ones need nothing.
309
+ </p>
310
+ </div>
311
+
312
+ {/* Environments — how a tool-using agent becomes schedulable
313
+ (DD-017 D-2). Only org-shared environments resolve for a
314
+ schedule fire, so the picker is filtered to visibility_org —
315
+ the same credential surface a channel binding uses. */}
316
+ <div className="space-y-1">
317
+ <span id="stgm-new-schedule-env-label" className={labelClasses}>
318
+ Environments <span className="font-normal text-muted-foreground">(optional)</span>
319
+ </span>
320
+ <EnvironmentPicker
321
+ org={org}
322
+ value={environmentRefs}
323
+ onChange={setEnvironmentRefs}
324
+ disabled={isCreating}
325
+ filterEnvironment={(env) =>
326
+ env.metadata?.visibility === ApiResourceVisibility.visibility_org
327
+ }
328
+ />
329
+ <p className={hintClasses}>
330
+ Bind org-shared credentials (for example an MCP server&rsquo;s secret)
331
+ so the agent&rsquo;s tools work on an unattended fire. Without this, an
332
+ agent whose tools need credentials will be refused every run.
333
+ </p>
334
+ </div>
335
+
227
336
  {/* Cadence */}
228
337
  <div className="space-y-1">
229
338
  <span className={labelClasses}>Runs</span>
@@ -235,6 +344,75 @@ export function ScheduleForm({
235
344
  />
236
345
  </div>
237
346
 
347
+ {/* Engine & model — the composer's own picker (DD-018 D-5),
348
+ replacing the free-text model box whose typos surfaced as
349
+ fire-time failures. Nothing is pinned until a model is picked. */}
350
+ <div className="space-y-1">
351
+ <span className={labelClasses}>
352
+ Engine &amp; model{" "}
353
+ <span className="font-normal text-muted-foreground">(optional)</span>
354
+ </span>
355
+ <div className="flex items-center gap-2">
356
+ <ModelSelector
357
+ value={modelName}
358
+ onValueChange={setModelName}
359
+ initialHarness={modelHarness}
360
+ onHarnessChange={setModelHarness}
361
+ serviceTier={serviceTier}
362
+ onServiceTierChange={setServiceTier}
363
+ placeholderLabel="Platform default"
364
+ disabled={isCreating}
365
+ />
366
+ {modelName !== "" && (
367
+ <button
368
+ type="button"
369
+ onClick={() => {
370
+ setModelName("");
371
+ setServiceTier("standard");
372
+ }}
373
+ disabled={isCreating}
374
+ className={cn(
375
+ "rounded-md px-2 py-1 text-[0.65rem] text-muted-foreground",
376
+ "hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
377
+ "disabled:pointer-events-none disabled:opacity-50",
378
+ )}
379
+ >
380
+ Reset to platform default
381
+ </button>
382
+ )}
383
+ </div>
384
+ <p className={hintClasses}>
385
+ Runs use the platform&rsquo;s default engine and model unless you
386
+ pick one here. Picking a model pins the engine it belongs to.
387
+ </p>
388
+ </div>
389
+
390
+ {/* Budget — the one run bound that matters for an unattended
391
+ surface: nobody is watching a 3 AM fire (DD-018 D-5). Clamped
392
+ by the platform profile; tool-round bounds stay API-only. */}
393
+ <div className="space-y-1">
394
+ <label htmlFor="stgm-new-schedule-budget" className={labelClasses}>
395
+ Budget per run (USD){" "}
396
+ <span className="font-normal text-muted-foreground">(optional)</span>
397
+ </label>
398
+ <input
399
+ id="stgm-new-schedule-budget"
400
+ type="number"
401
+ min="0"
402
+ step="any"
403
+ value={budgetUsd}
404
+ onChange={(e) => setBudgetUsd(e.target.value)}
405
+ placeholder="platform default"
406
+ disabled={isCreating}
407
+ className={cn(inputClasses, "sm:max-w-48")}
408
+ />
409
+ <p className={hintClasses}>
410
+ Each run stops when it reaches this spend. You can lower the
411
+ platform&rsquo;s per-run cap, never raise it past the
412
+ platform&rsquo;s ceiling.
413
+ </p>
414
+ </div>
415
+
238
416
  {/* Time zone */}
239
417
  <div className="space-y-1">
240
418
  <label htmlFor="stgm-new-schedule-tz" className={labelClasses}>
@@ -318,6 +496,39 @@ export function ScheduleForm({
318
496
 
319
497
  const labelClasses = "block text-xs font-medium text-foreground";
320
498
  const hintClasses = "text-[0.65rem] text-muted-foreground";
499
+
500
+ /**
501
+ * Assemble a {@link RunConfigInput} from the model choice and budget, or
502
+ * `undefined` when both are unset — an all-empty run_config carries no
503
+ * meaning, and omitting it keeps the schedule on the platform defaults
504
+ * (the server's own "empty = inherit" contract, DD-017 D-3 as carried
505
+ * into DD-018 D-2). Non-numeric or negative budgets are dropped rather
506
+ * than sent; the proto's `gte = 0` constraint would reject them anyway,
507
+ * and a blank field must not become a zero override. `max_tool_rounds`
508
+ * is deliberately not collected here — an implementation knob, not a
509
+ * user concept; API-reachable for operators (DD-018 D-5).
510
+ */
511
+ function buildRunConfig(
512
+ modelName: string,
513
+ budgetUsd: string,
514
+ serviceTier: ServiceTierOption,
515
+ ): RunConfigInput | undefined {
516
+ const model = modelName.trim();
517
+ const cost = Number.parseFloat(budgetUsd);
518
+
519
+ const config: RunConfigInput = {};
520
+ if (model !== "") config.modelName = model;
521
+ if (Number.isFinite(cost) && cost > 0) config.maxCostUsd = cost;
522
+ // Carried only when the user actively chose fast AND pinned a model
523
+ // (fast is a per-model price; the server refuses it without one). An
524
+ // untouched tier stays absent — unspecified-vs-explicit is a
525
+ // load-bearing ledger distinction (#357).
526
+ if (serviceTier === "fast" && model !== "") {
527
+ config.serviceTier = toProtoServiceTier(serviceTier);
528
+ }
529
+
530
+ return Object.keys(config).length > 0 ? config : undefined;
531
+ }
321
532
  const inputClasses = cn(
322
533
  "w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
323
534
  "placeholder:text-muted-foreground",