@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
@@ -0,0 +1,424 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { timestampDate } from "@bufbuild/protobuf/wkt";
6
+ import {
7
+ ScheduleRunOrigin,
8
+ ScheduleRunOutcome,
9
+ type ScheduleRun,
10
+ } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/io_pb";
11
+ import { formatRelativeTime } from "../activity/format-relative-time.js";
12
+ import { ErrorMessage } from "../error/ErrorMessage.js";
13
+ import { Pagination } from "../internal/Pagination.js";
14
+ import { useScheduleRuns } from "./useScheduleRuns.js";
15
+
16
+ /** Props for {@link ScheduleRunsTable}. */
17
+ export interface ScheduleRunsTableProps {
18
+ /** ID of the schedule whose run history to show. */
19
+ readonly scheduleId: string;
20
+ /**
21
+ * Called when the user activates a run's execution reference
22
+ * (`aex_…`). When omitted, the id renders as plain text.
23
+ */
24
+ readonly onNavigateToExecution?: (executionId: string) => void;
25
+ /**
26
+ * The instant relative timestamps are computed against.
27
+ * Injectable for deterministic tests and Scenar fixtures.
28
+ * @default new Date() at render
29
+ */
30
+ readonly now?: Date;
31
+ /** Runs per page. @default 25 */
32
+ readonly pageSize?: number;
33
+ /** Additional CSS classes for the root container. */
34
+ readonly className?: string;
35
+ }
36
+
37
+ /**
38
+ * Paginated run-history table for a schedule — the fire ledger,
39
+ * rendered in full (project DD-017 D-7).
40
+ *
41
+ * Every fire leaves a row, INCLUDING fires that created no execution (a
42
+ * refused launch gate, a missing target agent), carrying the refusing
43
+ * gate's copy verbatim. Columns: outcome, origin, fired time (relative,
44
+ * with the absolute instant on hover), duration (recorded → completed;
45
+ * "—" while a run is in flight or when no run was created), and the
46
+ * execution reference.
47
+ *
48
+ * Owns its data: give it a `scheduleId` and it fetches via
49
+ * {@link useScheduleRuns} with internal page state. To force a refresh
50
+ * from outside (e.g. after triggering a manual run), remount with a
51
+ * React `key` — the standard reset idiom (DD-014); the remount also
52
+ * returns to page 1, where the new run appears.
53
+ *
54
+ * Handles loading, error, and empty states automatically.
55
+ * Zero Console dependencies — safe for platform builder embedding.
56
+ * All visual properties flow through `--stgm-*` design tokens.
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * <ScheduleRunsTable
61
+ * scheduleId={schedule.metadata.id}
62
+ * onNavigateToExecution={(id) => router.push(`/executions/${id}`)}
63
+ * />
64
+ * ```
65
+ */
66
+ export function ScheduleRunsTable({
67
+ scheduleId,
68
+ onNavigateToExecution,
69
+ now,
70
+ pageSize = 25,
71
+ className,
72
+ }: ScheduleRunsTableProps) {
73
+ const [pageNum, setPageNum] = useState(1);
74
+ const { runs, totalCount, isLoading, error, refetch } = useScheduleRuns(
75
+ scheduleId,
76
+ { page: pageNum, pageSize },
77
+ );
78
+
79
+ if (error) {
80
+ return <ErrorMessage error={error} retry={refetch} className={className} />;
81
+ }
82
+ if (isLoading && runs.length === 0) {
83
+ return (
84
+ <div className={cn("space-y-2", className)} aria-busy="true">
85
+ {Array.from({ length: 5 }, (_, i) => (
86
+ <div key={i} className="h-9 animate-pulse rounded-lg bg-muted-subtle" />
87
+ ))}
88
+ </div>
89
+ );
90
+ }
91
+ if (runs.length === 0) {
92
+ return (
93
+ <div className={className}>
94
+ <EmptyRuns />
95
+ </div>
96
+ );
97
+ }
98
+
99
+ const renderNow = now ?? new Date();
100
+ const totalPages = Math.ceil(totalCount / pageSize);
101
+
102
+ return (
103
+ <div className={className}>
104
+ <div
105
+ role="table"
106
+ aria-label="Run history"
107
+ className="overflow-hidden rounded-lg border border-border"
108
+ >
109
+ <div
110
+ role="row"
111
+ className={cn(
112
+ rowGridClasses,
113
+ "border-b border-border px-3.5 py-2 text-[0.65rem] font-medium uppercase tracking-wider text-muted-foreground",
114
+ )}
115
+ >
116
+ <span role="columnheader">Outcome</span>
117
+ <span role="columnheader" className="hidden sm:block">
118
+ Origin
119
+ </span>
120
+ <span role="columnheader">Fired</span>
121
+ <span role="columnheader" className="hidden sm:block">
122
+ Duration
123
+ </span>
124
+ <span role="columnheader" className="text-right">
125
+ Execution
126
+ </span>
127
+ </div>
128
+ {runs.map((run, i) => (
129
+ <RunTableRow
130
+ key={runKey(run, i)}
131
+ run={run}
132
+ now={renderNow}
133
+ onNavigateToExecution={onNavigateToExecution}
134
+ />
135
+ ))}
136
+ </div>
137
+
138
+ {totalPages > 1 && (
139
+ <Pagination
140
+ pageNum={pageNum}
141
+ totalPages={totalPages}
142
+ onPageChange={setPageNum}
143
+ ariaLabel="Run history pagination"
144
+ className="mt-3"
145
+ />
146
+ )}
147
+ </div>
148
+ );
149
+ }
150
+
151
+ // Mobile keeps the three load-bearing columns (outcome, fired,
152
+ // execution); origin and duration join at the sm breakpoint.
153
+ const rowGridClasses =
154
+ "grid grid-cols-[7rem_1fr_auto] items-center gap-x-4 sm:grid-cols-[7rem_5.5rem_1fr_5rem_minmax(0,12rem)]";
155
+
156
+ function RunTableRow({
157
+ run,
158
+ now,
159
+ onNavigateToExecution,
160
+ }: {
161
+ readonly run: ScheduleRun;
162
+ readonly now: Date;
163
+ readonly onNavigateToExecution?: (executionId: string) => void;
164
+ }) {
165
+ const fireDate = run.nominalFireTime
166
+ ? timestampDate(run.nominalFireTime)
167
+ : null;
168
+ const badge = runOutcomeBadge(run.outcome);
169
+
170
+ return (
171
+ <div
172
+ role="row"
173
+ className={cn(
174
+ rowGridClasses,
175
+ "border-b border-border-muted px-3.5 py-2.5 last:border-b-0",
176
+ )}
177
+ >
178
+ <span role="cell">
179
+ <span
180
+ className={cn(
181
+ "inline-flex items-center rounded-full px-2 py-0.5 text-[0.65rem] font-medium",
182
+ badge.className,
183
+ )}
184
+ >
185
+ {badge.label}
186
+ </span>
187
+ </span>
188
+ <span role="cell" className="hidden text-xs text-muted-foreground sm:block">
189
+ {runOriginLabel(run.origin)}
190
+ </span>
191
+ <span
192
+ role="cell"
193
+ className="text-xs text-muted-foreground"
194
+ title={fireDate ? fireDate.toLocaleString() : undefined}
195
+ >
196
+ {fireDate ? formatRelativeTime(fireDate, now) : "—"}
197
+ </span>
198
+ <span
199
+ role="cell"
200
+ className="hidden text-xs tabular-nums text-muted-foreground sm:block"
201
+ >
202
+ {formatRunDuration(run) ?? "—"}
203
+ </span>
204
+ <span role="cell" className="min-w-0 text-right">
205
+ {run.executionId ? (
206
+ onNavigateToExecution ? (
207
+ <button
208
+ type="button"
209
+ onClick={() => onNavigateToExecution(run.executionId)}
210
+ className={cn(
211
+ "max-w-full truncate font-mono text-[0.65rem] text-primary underline-offset-2 hover:underline",
212
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm",
213
+ )}
214
+ >
215
+ {run.executionId}
216
+ </button>
217
+ ) : (
218
+ <span className="font-mono text-[0.65rem] text-muted-foreground">
219
+ {run.executionId}
220
+ </span>
221
+ )
222
+ ) : (
223
+ <span className="text-xs text-muted-foreground">—</span>
224
+ )}
225
+ </span>
226
+ {run.reason && (
227
+ <p
228
+ role="cell"
229
+ className="col-span-full mt-1 whitespace-pre-wrap break-words text-xs text-muted-foreground"
230
+ >
231
+ {run.reason}
232
+ </p>
233
+ )}
234
+ </div>
235
+ );
236
+ }
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // Compact list — the Overview tab's "recent runs" strip
240
+ // ---------------------------------------------------------------------------
241
+
242
+ /**
243
+ * Compact run list: badge, origin, relative time, refusal reason, and
244
+ * execution link per row — the at-a-glance form used by the detail
245
+ * view's Overview tab. Presentational: runs come in as props (the
246
+ * caller owns the fetch, so one hook can also feed the Runs tab badge).
247
+ *
248
+ * Not barrel-exported: the public run-history surface is
249
+ * {@link ScheduleRunsTable}; this exists for the detail view's strip.
250
+ */
251
+ export function ScheduleRunsCompactList({
252
+ runs,
253
+ isLoading,
254
+ now,
255
+ onNavigateToExecution,
256
+ }: {
257
+ readonly runs: readonly ScheduleRun[];
258
+ readonly isLoading: boolean;
259
+ readonly now: Date;
260
+ readonly onNavigateToExecution?: (executionId: string) => void;
261
+ }) {
262
+ if (isLoading && runs.length === 0) {
263
+ return (
264
+ <div className="space-y-2 px-4 py-3" aria-busy="true">
265
+ <div className="h-4 w-full animate-pulse rounded bg-muted" />
266
+ <div className="h-4 w-3/4 animate-pulse rounded bg-muted" />
267
+ </div>
268
+ );
269
+ }
270
+ if (runs.length === 0) {
271
+ return <EmptyRuns />;
272
+ }
273
+ return (
274
+ <ul className="divide-y divide-border">
275
+ {runs.map((run, i) => (
276
+ <CompactRunRow
277
+ key={runKey(run, i)}
278
+ run={run}
279
+ now={now}
280
+ onNavigateToExecution={onNavigateToExecution}
281
+ />
282
+ ))}
283
+ </ul>
284
+ );
285
+ }
286
+
287
+ function CompactRunRow({
288
+ run,
289
+ now,
290
+ onNavigateToExecution,
291
+ }: {
292
+ readonly run: ScheduleRun;
293
+ readonly now: Date;
294
+ readonly onNavigateToExecution?: (executionId: string) => void;
295
+ }) {
296
+ const fireDate = run.nominalFireTime
297
+ ? timestampDate(run.nominalFireTime)
298
+ : null;
299
+ const badge = runOutcomeBadge(run.outcome);
300
+
301
+ return (
302
+ <li className="flex flex-col gap-1 px-4 py-2.5">
303
+ <div className="flex items-center gap-2">
304
+ <span
305
+ className={cn(
306
+ "inline-flex items-center rounded-full px-2 py-0.5 text-[0.65rem] font-medium",
307
+ badge.className,
308
+ )}
309
+ >
310
+ {badge.label}
311
+ </span>
312
+ <span className="text-xs text-muted-foreground">
313
+ {runOriginLabel(run.origin)}
314
+ </span>
315
+ <span className="text-xs text-muted-foreground-subtle">·</span>
316
+ <span
317
+ className="text-xs text-muted-foreground"
318
+ title={fireDate ? fireDate.toLocaleString() : undefined}
319
+ >
320
+ {fireDate ? formatRelativeTime(fireDate, now) : "—"}
321
+ </span>
322
+ </div>
323
+ {run.reason && (
324
+ <p className="whitespace-pre-wrap break-words text-xs text-muted-foreground">
325
+ {run.reason}
326
+ </p>
327
+ )}
328
+ {run.executionId &&
329
+ (onNavigateToExecution ? (
330
+ <button
331
+ type="button"
332
+ onClick={() => onNavigateToExecution(run.executionId)}
333
+ className={cn(
334
+ "self-start font-mono text-[0.65rem] text-primary underline-offset-2 hover:underline",
335
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm",
336
+ )}
337
+ >
338
+ {run.executionId}
339
+ </button>
340
+ ) : (
341
+ <span className="self-start font-mono text-[0.65rem] text-muted-foreground">
342
+ {run.executionId}
343
+ </span>
344
+ ))}
345
+ </li>
346
+ );
347
+ }
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // Shared pieces
351
+ // ---------------------------------------------------------------------------
352
+
353
+ function EmptyRuns() {
354
+ return (
355
+ <p className="px-4 py-6 text-center text-xs text-muted-foreground">
356
+ No runs yet. Use &ldquo;Run now&rdquo; to fire a test run — every fire,
357
+ including a refused one, is recorded here.
358
+ </p>
359
+ );
360
+ }
361
+
362
+ /**
363
+ * Stable row key from the fire ledger's natural identity
364
+ * (nominal fire time + origin), with the index as a collision tail.
365
+ */
366
+ function runKey(run: ScheduleRun, index: number): string {
367
+ const fireMs = run.nominalFireTime
368
+ ? timestampDate(run.nominalFireTime).getTime()
369
+ : "no-time";
370
+ return `${fireMs}-${run.origin}-${index}`;
371
+ }
372
+
373
+ function runOriginLabel(origin: ScheduleRunOrigin): string {
374
+ return origin === ScheduleRunOrigin.MANUAL ? "Manual" : "Scheduled";
375
+ }
376
+
377
+ /** Verbatim-label badges for a run outcome, colored by health. */
378
+ export function runOutcomeBadge(outcome: ScheduleRunOutcome): {
379
+ label: string;
380
+ className: string;
381
+ } {
382
+ switch (outcome) {
383
+ case ScheduleRunOutcome.STARTED:
384
+ return { label: "Started", className: "bg-info/10 text-info" };
385
+ case ScheduleRunOutcome.COMPLETED:
386
+ return { label: "Completed", className: "bg-success/10 text-success" };
387
+ case ScheduleRunOutcome.REFUSED:
388
+ return { label: "Refused", className: "bg-warning/10 text-warning" };
389
+ case ScheduleRunOutcome.TARGET_MISSING:
390
+ return { label: "Target missing", className: "bg-warning/10 text-warning" };
391
+ case ScheduleRunOutcome.SKIPPED:
392
+ return { label: "Skipped", className: "bg-muted text-muted-foreground" };
393
+ case ScheduleRunOutcome.FAILED:
394
+ return { label: "Failed", className: "bg-destructive/10 text-destructive" };
395
+ case ScheduleRunOutcome.TIMED_OUT:
396
+ return { label: "Timed out", className: "bg-destructive/10 text-destructive" };
397
+ default:
398
+ return { label: "Unknown", className: "bg-muted text-muted-foreground" };
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Wall-clock duration of a run, `recorded_at` → `completed_at`, in the
404
+ * compact unit style the schedule views use elsewhere (`42s`, `5m 12s`,
405
+ * `2h 3m`). `null` while the run is in flight or when the fire created
406
+ * no run at all — the caller renders the em-dash.
407
+ */
408
+ function formatRunDuration(run: ScheduleRun): string | null {
409
+ if (!run.recordedAt || !run.completedAt) return null;
410
+ const ms =
411
+ timestampDate(run.completedAt).getTime() -
412
+ timestampDate(run.recordedAt).getTime();
413
+ if (ms < 0) return null;
414
+
415
+ const totalSeconds = Math.round(ms / 1000);
416
+ if (totalSeconds < 1) return "<1s";
417
+ if (totalSeconds < 60) return `${totalSeconds}s`;
418
+ const minutes = Math.floor(totalSeconds / 60);
419
+ const seconds = totalSeconds % 60;
420
+ if (minutes < 60) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
421
+ const hours = Math.floor(minutes / 60);
422
+ const remMinutes = minutes % 60;
423
+ return remMinutes > 0 ? `${hours}h ${remMinutes}m` : `${hours}h`;
424
+ }