@stigmer/react 3.6.0 → 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 (121) 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/composer/ComposerToolbar.d.ts +6 -1
  5. package/composer/ComposerToolbar.d.ts.map +1 -1
  6. package/composer/ComposerToolbar.js +2 -2
  7. package/composer/ComposerToolbar.js.map +1 -1
  8. package/composer/SessionComposer.d.ts +9 -0
  9. package/composer/SessionComposer.d.ts.map +1 -1
  10. package/composer/SessionComposer.js +13 -2
  11. package/composer/SessionComposer.js.map +1 -1
  12. package/execution/useCreateAgentExecution.d.ts +14 -0
  13. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  14. package/execution/useCreateAgentExecution.js +5 -0
  15. package/execution/useCreateAgentExecution.js.map +1 -1
  16. package/index.d.ts +2 -2
  17. package/index.d.ts.map +1 -1
  18. package/index.js +6 -5
  19. package/index.js.map +1 -1
  20. package/internal/Pagination.d.ts +15 -0
  21. package/internal/Pagination.d.ts.map +1 -0
  22. package/internal/Pagination.js +20 -0
  23. package/internal/Pagination.js.map +1 -0
  24. package/models/ModelSelector.d.ts +26 -1
  25. package/models/ModelSelector.d.ts.map +1 -1
  26. package/models/ModelSelector.js +27 -6
  27. package/models/ModelSelector.js.map +1 -1
  28. package/models/index.d.ts +2 -0
  29. package/models/index.d.ts.map +1 -1
  30. package/models/index.js +1 -0
  31. package/models/index.js.map +1 -1
  32. package/models/registry.d.ts +10 -0
  33. package/models/registry.d.ts.map +1 -1
  34. package/models/registry.js +3 -0
  35. package/models/registry.js.map +1 -1
  36. package/models/service-tier.d.ts +27 -0
  37. package/models/service-tier.d.ts.map +1 -0
  38. package/models/service-tier.js +34 -0
  39. package/models/service-tier.js.map +1 -0
  40. package/package.json +4 -4
  41. package/schedule/ScheduleDetailView.d.ts +31 -8
  42. package/schedule/ScheduleDetailView.d.ts.map +1 -1
  43. package/schedule/ScheduleDetailView.js +441 -29
  44. package/schedule/ScheduleDetailView.js.map +1 -1
  45. package/schedule/ScheduleForm.d.ts +7 -4
  46. package/schedule/ScheduleForm.d.ts.map +1 -1
  47. package/schedule/ScheduleForm.js +93 -8
  48. package/schedule/ScheduleForm.js.map +1 -1
  49. package/schedule/ScheduleRunsTable.d.ts +72 -0
  50. package/schedule/ScheduleRunsTable.d.ts.map +1 -0
  51. package/schedule/ScheduleRunsTable.js +161 -0
  52. package/schedule/ScheduleRunsTable.js.map +1 -0
  53. package/schedule/index.d.ts +6 -0
  54. package/schedule/index.d.ts.map +1 -1
  55. package/schedule/index.js +3 -0
  56. package/schedule/index.js.map +1 -1
  57. package/schedule/useScheduleRuns.d.ts +44 -0
  58. package/schedule/useScheduleRuns.d.ts.map +1 -0
  59. package/schedule/useScheduleRuns.js +48 -0
  60. package/schedule/useScheduleRuns.js.map +1 -0
  61. package/schedule/useTriggerSchedule.d.ts +23 -10
  62. package/schedule/useTriggerSchedule.d.ts.map +1 -1
  63. package/schedule/useTriggerSchedule.js +28 -9
  64. package/schedule/useTriggerSchedule.js.map +1 -1
  65. package/schedule/useUpdateScheduleSpec.d.ts +49 -0
  66. package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
  67. package/schedule/useUpdateScheduleSpec.js +70 -0
  68. package/schedule/useUpdateScheduleSpec.js.map +1 -0
  69. package/session/useNewSessionFlow.d.ts.map +1 -1
  70. package/session/useNewSessionFlow.js +1 -0
  71. package/session/useNewSessionFlow.js.map +1 -1
  72. package/session/useSessionConversation.d.ts +11 -0
  73. package/session/useSessionConversation.d.ts.map +1 -1
  74. package/session/useSessionConversation.js +1 -0
  75. package/session/useSessionConversation.js.map +1 -1
  76. package/session/useSessionPageFlow.d.ts.map +1 -1
  77. package/session/useSessionPageFlow.js +1 -0
  78. package/session/useSessionPageFlow.js.map +1 -1
  79. package/src/billing/CreditLedgerTable.tsx +3 -42
  80. package/src/composer/ComposerToolbar.tsx +9 -0
  81. package/src/composer/SessionComposer.tsx +24 -1
  82. package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +156 -0
  83. package/src/execution/useCreateAgentExecution.ts +18 -0
  84. package/src/index.ts +9 -4
  85. package/src/internal/Pagination.tsx +72 -0
  86. package/src/models/ModelSelector.tsx +100 -3
  87. package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +172 -0
  88. package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
  89. package/src/models/index.ts +2 -0
  90. package/src/models/registry.ts +16 -0
  91. package/src/models/service-tier.ts +49 -0
  92. package/src/schedule/ScheduleDetailView.tsx +1179 -111
  93. package/src/schedule/ScheduleForm.tsx +217 -7
  94. package/src/schedule/ScheduleRunsTable.tsx +424 -0
  95. package/src/schedule/__tests__/ScheduleDetailView.test.tsx +431 -8
  96. package/src/schedule/__tests__/scheduleCreation.test.tsx +197 -0
  97. package/src/schedule/__tests__/scheduleHooks.test.tsx +94 -7
  98. package/src/schedule/index.ts +12 -0
  99. package/src/schedule/useScheduleRuns.ts +93 -0
  100. package/src/schedule/useTriggerSchedule.ts +39 -14
  101. package/src/schedule/useUpdateScheduleSpec.ts +108 -0
  102. package/src/session/useNewSessionFlow.ts +1 -0
  103. package/src/session/useSessionConversation.ts +12 -0
  104. package/src/session/useSessionPageFlow.ts +1 -0
  105. package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
  106. package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
  107. package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
  108. package/src/workflow/starter-workflow-yaml.ts +2 -1
  109. package/styles.css +1 -1
  110. package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
  111. package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
  112. package/workflow/inspector/forms/AgentCallForm.js +87 -9
  113. package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
  114. package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
  115. package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
  116. package/workflow/inspector/tabs/RuntimeTab.js +31 -5
  117. package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
  118. package/workflow/starter-workflow-yaml.d.ts +1 -1
  119. package/workflow/starter-workflow-yaml.d.ts.map +1 -1
  120. package/workflow/starter-workflow-yaml.js +2 -1
  121. 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,32 @@ 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 rides the model choice (#357): the ModelSelector renders
111
+ // the fast toggle only for models whose registry entry prices a fast
112
+ // variant, and resets it when the user switches to one that does not.
113
+ // No model pinned = no tier pinned (fast requires a model, and the form's
114
+ // reset button clears both).
115
+ const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
116
+
117
+ const [budgetUsd, setBudgetUsd] = useState("");
118
+
119
+ // Workspace the fresh per-fire session clones — git sources only
120
+ // (write-time validated server-side: no client is connected at fire
121
+ // time to serve a local folder). The GitHub repo picker lights up
122
+ // when the user already has a connection; otherwise the editor's
123
+ // manual URL+branch input applies, with no OAuth flow forced into
124
+ // this form.
125
+ const workspace = useWorkspaceEntries();
126
+ const gitHub = useGitHubConnection(org);
86
127
 
87
128
  const trimmedName = name.trim();
88
129
  const trimmedMessage = message.trim();
@@ -108,6 +149,7 @@ export function ScheduleForm({
108
149
  if (!canSubmit || !agentRef) return;
109
150
 
110
151
  clearError();
152
+ const runConfig = buildRunConfig(modelName, budgetUsd, serviceTier);
111
153
  try {
112
154
  const schedule = await create({
113
155
  name: trimmedName,
@@ -115,7 +157,20 @@ export function ScheduleForm({
115
157
  cron,
116
158
  timeZone,
117
159
  enabled,
118
- agent: { agentRef, message: trimmedMessage },
160
+ agent: {
161
+ agentRef,
162
+ message: trimmedMessage,
163
+ // The harness travels with the model it was picked under;
164
+ // no model means no harness — the platform defaults apply.
165
+ ...(modelName !== ""
166
+ ? { harness: toProtoHarness(modelHarness) }
167
+ : {}),
168
+ ...(workspace.hasEntries
169
+ ? { workspaceEntries: workspace.toInput() }
170
+ : {}),
171
+ ...(environmentRefs.length > 0 ? { environmentRefs } : {}),
172
+ ...(runConfig ? { runConfig } : {}),
173
+ },
119
174
  });
120
175
  onComplete?.(schedule);
121
176
  } catch {
@@ -133,6 +188,12 @@ export function ScheduleForm({
133
188
  timeZone,
134
189
  enabled,
135
190
  trimmedMessage,
191
+ environmentRefs,
192
+ modelName,
193
+ modelHarness,
194
+ serviceTier,
195
+ budgetUsd,
196
+ workspace,
136
197
  onComplete,
137
198
  ],
138
199
  );
@@ -224,6 +285,53 @@ export function ScheduleForm({
224
285
  )}
225
286
  </div>
226
287
 
288
+ {/* Workspace — what each fire's fresh session operates on
289
+ (DD-018 D-4). Git sources only; the server refuses local
290
+ folders at write time because no client is connected when a
291
+ schedule fires. */}
292
+ <div className="space-y-1">
293
+ <span className={labelClasses}>
294
+ Workspace{" "}
295
+ <span className="font-normal text-muted-foreground">(optional)</span>
296
+ </span>
297
+ <WorkspaceEditor
298
+ workspace={workspace}
299
+ gitHubConnection={gitHub.isConnected ? gitHub : undefined}
300
+ enableGitHub
301
+ enableLocal={false}
302
+ disabled={isCreating}
303
+ />
304
+ <p className={hintClasses}>
305
+ Each run clones these repositories fresh. Private repositories need
306
+ a <code className="font-mono">GITHUB_TOKEN</code> in one of this
307
+ schedule&rsquo;s environments; public ones need nothing.
308
+ </p>
309
+ </div>
310
+
311
+ {/* Environments — how a tool-using agent becomes schedulable
312
+ (DD-017 D-2). Only org-shared environments resolve for a
313
+ schedule fire, so the picker is filtered to visibility_org —
314
+ the same credential surface a channel binding uses. */}
315
+ <div className="space-y-1">
316
+ <span id="stgm-new-schedule-env-label" className={labelClasses}>
317
+ Environments <span className="font-normal text-muted-foreground">(optional)</span>
318
+ </span>
319
+ <EnvironmentPicker
320
+ org={org}
321
+ value={environmentRefs}
322
+ onChange={setEnvironmentRefs}
323
+ disabled={isCreating}
324
+ filterEnvironment={(env) =>
325
+ env.metadata?.visibility === ApiResourceVisibility.visibility_org
326
+ }
327
+ />
328
+ <p className={hintClasses}>
329
+ Bind org-shared credentials (for example an MCP server&rsquo;s secret)
330
+ so the agent&rsquo;s tools work on an unattended fire. Without this, an
331
+ agent whose tools need credentials will be refused every run.
332
+ </p>
333
+ </div>
334
+
227
335
  {/* Cadence */}
228
336
  <div className="space-y-1">
229
337
  <span className={labelClasses}>Runs</span>
@@ -235,6 +343,75 @@ export function ScheduleForm({
235
343
  />
236
344
  </div>
237
345
 
346
+ {/* Engine & model — the composer's own picker (DD-018 D-5),
347
+ replacing the free-text model box whose typos surfaced as
348
+ fire-time failures. Nothing is pinned until a model is picked. */}
349
+ <div className="space-y-1">
350
+ <span className={labelClasses}>
351
+ Engine &amp; model{" "}
352
+ <span className="font-normal text-muted-foreground">(optional)</span>
353
+ </span>
354
+ <div className="flex items-center gap-2">
355
+ <ModelSelector
356
+ value={modelName}
357
+ onValueChange={setModelName}
358
+ initialHarness={modelHarness}
359
+ onHarnessChange={setModelHarness}
360
+ serviceTier={serviceTier}
361
+ onServiceTierChange={setServiceTier}
362
+ placeholderLabel="Platform default"
363
+ disabled={isCreating}
364
+ />
365
+ {modelName !== "" && (
366
+ <button
367
+ type="button"
368
+ onClick={() => {
369
+ setModelName("");
370
+ setServiceTier("standard");
371
+ }}
372
+ disabled={isCreating}
373
+ className={cn(
374
+ "rounded-md px-2 py-1 text-[0.65rem] text-muted-foreground",
375
+ "hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
376
+ "disabled:pointer-events-none disabled:opacity-50",
377
+ )}
378
+ >
379
+ Reset to platform default
380
+ </button>
381
+ )}
382
+ </div>
383
+ <p className={hintClasses}>
384
+ Runs use the platform&rsquo;s default engine and model unless you
385
+ pick one here. Picking a model pins the engine it belongs to.
386
+ </p>
387
+ </div>
388
+
389
+ {/* Budget — the one run bound that matters for an unattended
390
+ surface: nobody is watching a 3 AM fire (DD-018 D-5). Clamped
391
+ by the platform profile; tool-round bounds stay API-only. */}
392
+ <div className="space-y-1">
393
+ <label htmlFor="stgm-new-schedule-budget" className={labelClasses}>
394
+ Budget per run (USD){" "}
395
+ <span className="font-normal text-muted-foreground">(optional)</span>
396
+ </label>
397
+ <input
398
+ id="stgm-new-schedule-budget"
399
+ type="number"
400
+ min="0"
401
+ step="any"
402
+ value={budgetUsd}
403
+ onChange={(e) => setBudgetUsd(e.target.value)}
404
+ placeholder="platform default"
405
+ disabled={isCreating}
406
+ className={cn(inputClasses, "sm:max-w-48")}
407
+ />
408
+ <p className={hintClasses}>
409
+ Each run stops when it reaches this spend. You can lower the
410
+ platform&rsquo;s per-run cap, never raise it past the
411
+ platform&rsquo;s ceiling.
412
+ </p>
413
+ </div>
414
+
238
415
  {/* Time zone */}
239
416
  <div className="space-y-1">
240
417
  <label htmlFor="stgm-new-schedule-tz" className={labelClasses}>
@@ -318,6 +495,39 @@ export function ScheduleForm({
318
495
 
319
496
  const labelClasses = "block text-xs font-medium text-foreground";
320
497
  const hintClasses = "text-[0.65rem] text-muted-foreground";
498
+
499
+ /**
500
+ * Assemble a {@link RunConfigInput} from the model choice and budget, or
501
+ * `undefined` when both are unset — an all-empty run_config carries no
502
+ * meaning, and omitting it keeps the schedule on the platform defaults
503
+ * (the server's own "empty = inherit" contract, DD-017 D-3 as carried
504
+ * into DD-018 D-2). Non-numeric or negative budgets are dropped rather
505
+ * than sent; the proto's `gte = 0` constraint would reject them anyway,
506
+ * and a blank field must not become a zero override. `max_tool_rounds`
507
+ * is deliberately not collected here — an implementation knob, not a
508
+ * user concept; API-reachable for operators (DD-018 D-5).
509
+ */
510
+ function buildRunConfig(
511
+ modelName: string,
512
+ budgetUsd: string,
513
+ serviceTier: ServiceTierOption,
514
+ ): RunConfigInput | undefined {
515
+ const model = modelName.trim();
516
+ const cost = Number.parseFloat(budgetUsd);
517
+
518
+ const config: RunConfigInput = {};
519
+ if (model !== "") config.modelName = model;
520
+ if (Number.isFinite(cost) && cost > 0) config.maxCostUsd = cost;
521
+ // Carried only when the user actively chose fast AND pinned a model
522
+ // (fast is a per-model price; the server refuses it without one). An
523
+ // untouched toggle stays absent — unspecified-vs-explicit is a
524
+ // load-bearing ledger distinction (#357).
525
+ if (serviceTier === "fast" && model !== "") {
526
+ config.serviceTier = toProtoServiceTier(serviceTier);
527
+ }
528
+
529
+ return Object.keys(config).length > 0 ? config : undefined;
530
+ }
321
531
  const inputClasses = cn(
322
532
  "w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
323
533
  "placeholder:text-muted-foreground",