@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.
- package/billing/CreditLedgerTable.d.ts.map +1 -1
- package/billing/CreditLedgerTable.js +2 -7
- package/billing/CreditLedgerTable.js.map +1 -1
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +21 -2
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/ChannelConversationsDialog.d.ts +12 -4
- package/channel/ChannelConversationsDialog.d.ts.map +1 -1
- package/channel/ChannelConversationsDialog.js +18 -24
- package/channel/ChannelConversationsDialog.js.map +1 -1
- package/channel/providers.d.ts +23 -0
- package/channel/providers.d.ts.map +1 -1
- package/channel/providers.js +4 -0
- package/channel/providers.js.map +1 -1
- package/channel/useSaveAgentChannel.d.ts +5 -4
- package/channel/useSaveAgentChannel.d.ts.map +1 -1
- package/channel/useSaveAgentChannel.js +18 -4
- package/channel/useSaveAgentChannel.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +6 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +9 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -2
- package/composer/SessionComposer.js.map +1 -1
- package/conversation/ConversationAttentionBanner.d.ts +22 -0
- package/conversation/ConversationAttentionBanner.d.ts.map +1 -0
- package/conversation/ConversationAttentionBanner.js +25 -0
- package/conversation/ConversationAttentionBanner.js.map +1 -0
- package/conversation/ConversationComposer.d.ts +41 -0
- package/conversation/ConversationComposer.d.ts.map +1 -0
- package/conversation/ConversationComposer.js +57 -0
- package/conversation/ConversationComposer.js.map +1 -0
- package/conversation/ConversationControlBanner.d.ts +52 -0
- package/conversation/ConversationControlBanner.d.ts.map +1 -0
- package/conversation/ConversationControlBanner.js +59 -0
- package/conversation/ConversationControlBanner.js.map +1 -0
- package/conversation/ConversationListPane.d.ts +68 -0
- package/conversation/ConversationListPane.d.ts.map +1 -0
- package/conversation/ConversationListPane.js +120 -0
- package/conversation/ConversationListPane.js.map +1 -0
- package/conversation/ConversationTimelineView.d.ts +46 -0
- package/conversation/ConversationTimelineView.d.ts.map +1 -0
- package/conversation/ConversationTimelineView.js +166 -0
- package/conversation/ConversationTimelineView.js.map +1 -0
- package/conversation/ConversationsWorkbench.d.ts +63 -0
- package/conversation/ConversationsWorkbench.d.ts.map +1 -0
- package/conversation/ConversationsWorkbench.js +165 -0
- package/conversation/ConversationsWorkbench.js.map +1 -0
- package/conversation/conversationPresentation.d.ts +137 -0
- package/conversation/conversationPresentation.d.ts.map +1 -0
- package/conversation/conversationPresentation.js +230 -0
- package/conversation/conversationPresentation.js.map +1 -0
- package/conversation/index.d.ts +26 -0
- package/conversation/index.d.ts.map +1 -0
- package/conversation/index.js +14 -0
- package/conversation/index.js.map +1 -0
- package/conversation/polling.d.ts +39 -0
- package/conversation/polling.d.ts.map +1 -0
- package/conversation/polling.js +39 -0
- package/conversation/polling.js.map +1 -0
- package/conversation/useConversation.d.ts +61 -0
- package/conversation/useConversation.d.ts.map +1 -0
- package/conversation/useConversation.js +153 -0
- package/conversation/useConversation.js.map +1 -0
- package/conversation/useConversationList.d.ts +98 -0
- package/conversation/useConversationList.d.ts.map +1 -0
- package/conversation/useConversationList.js +200 -0
- package/conversation/useConversationList.js.map +1 -0
- package/conversation/useConversationParticipation.d.ts +63 -0
- package/conversation/useConversationParticipation.d.ts.map +1 -0
- package/conversation/useConversationParticipation.js +84 -0
- package/conversation/useConversationParticipation.js.map +1 -0
- package/conversation/useConversationTimeline.d.ts +71 -0
- package/conversation/useConversationTimeline.d.ts.map +1 -0
- package/conversation/useConversationTimeline.js +152 -0
- package/conversation/useConversationTimeline.js.map +1 -0
- package/conversation/useConversationsWantsHumanCount.d.ts +42 -0
- package/conversation/useConversationsWantsHumanCount.d.ts.map +1 -0
- package/conversation/useConversationsWantsHumanCount.js +40 -0
- package/conversation/useConversationsWantsHumanCount.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +14 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +6 -2
- package/index.d.ts.map +1 -1
- package/index.js +14 -5
- package/index.js.map +1 -1
- package/internal/Pagination.d.ts +15 -0
- package/internal/Pagination.d.ts.map +1 -0
- package/internal/Pagination.js +20 -0
- package/internal/Pagination.js.map +1 -0
- package/internal/tooltip.d.ts.map +1 -1
- package/internal/tooltip.js +7 -1
- package/internal/tooltip.js.map +1 -1
- package/internal/useAutoScroll.d.ts +25 -4
- package/internal/useAutoScroll.d.ts.map +1 -1
- package/internal/useAutoScroll.js +55 -15
- package/internal/useAutoScroll.js.map +1 -1
- package/internal/useFetch.d.ts.map +1 -1
- package/internal/useFetch.js +14 -4
- package/internal/useFetch.js.map +1 -1
- package/models/ModelSelector.d.ts +45 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +49 -6
- package/models/ModelSelector.js.map +1 -1
- package/models/index.d.ts +2 -0
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -0
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +11 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +3 -0
- package/models/registry.js.map +1 -1
- package/models/service-tier.d.ts +27 -0
- package/models/service-tier.d.ts.map +1 -0
- package/models/service-tier.js +34 -0
- package/models/service-tier.js.map +1 -0
- package/package.json +4 -4
- package/schedule/ScheduleDetailView.d.ts +31 -8
- package/schedule/ScheduleDetailView.d.ts.map +1 -1
- package/schedule/ScheduleDetailView.js +441 -29
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleForm.d.ts +7 -4
- package/schedule/ScheduleForm.d.ts.map +1 -1
- package/schedule/ScheduleForm.js +94 -8
- package/schedule/ScheduleForm.js.map +1 -1
- package/schedule/ScheduleRunsTable.d.ts +72 -0
- package/schedule/ScheduleRunsTable.d.ts.map +1 -0
- package/schedule/ScheduleRunsTable.js +161 -0
- package/schedule/ScheduleRunsTable.js.map +1 -0
- package/schedule/index.d.ts +6 -0
- package/schedule/index.d.ts.map +1 -1
- package/schedule/index.js +3 -0
- package/schedule/index.js.map +1 -1
- package/schedule/useScheduleRuns.d.ts +44 -0
- package/schedule/useScheduleRuns.d.ts.map +1 -0
- package/schedule/useScheduleRuns.js +48 -0
- package/schedule/useScheduleRuns.js.map +1 -0
- package/schedule/useTriggerSchedule.d.ts +23 -10
- package/schedule/useTriggerSchedule.d.ts.map +1 -1
- package/schedule/useTriggerSchedule.js +28 -9
- package/schedule/useTriggerSchedule.js.map +1 -1
- package/schedule/useUpdateScheduleSpec.d.ts +49 -0
- package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
- package/schedule/useUpdateScheduleSpec.js +70 -0
- package/schedule/useUpdateScheduleSpec.js.map +1 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +1 -0
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts +11 -0
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +1 -0
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/sharing/useSaveAgentShare.d.ts +10 -1
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +9 -0
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/sidebar/WorkspaceSidebar.d.ts +12 -4
- package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
- package/sidebar/WorkspaceSidebar.js +19 -7
- package/sidebar/WorkspaceSidebar.js.map +1 -1
- package/src/billing/CreditLedgerTable.tsx +3 -42
- package/src/channel/AgentChannelsPanel.tsx +33 -2
- package/src/channel/ChannelConversationsDialog.tsx +22 -25
- package/src/channel/__tests__/AgentChannelsPanel.test.tsx +36 -5
- package/src/channel/__tests__/ChannelConversationsDialog.test.tsx +38 -1
- package/src/channel/__tests__/useSaveAgentChannel.test.tsx +26 -0
- package/src/channel/providers.ts +27 -0
- package/src/channel/useSaveAgentChannel.ts +18 -4
- package/src/composer/ComposerToolbar.tsx +9 -0
- package/src/composer/SessionComposer.tsx +24 -1
- package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +157 -0
- package/src/conversation/ConversationAttentionBanner.tsx +77 -0
- package/src/conversation/ConversationComposer.tsx +156 -0
- package/src/conversation/ConversationControlBanner.tsx +193 -0
- package/src/conversation/ConversationListPane.tsx +459 -0
- package/src/conversation/ConversationTimelineView.tsx +435 -0
- package/src/conversation/ConversationsWorkbench.tsx +378 -0
- package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +82 -0
- package/src/conversation/__tests__/ConversationComposer.test.tsx +160 -0
- package/src/conversation/__tests__/ConversationControlBanner.test.tsx +190 -0
- package/src/conversation/__tests__/ConversationListPane.test.tsx +372 -0
- package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +160 -0
- package/src/conversation/__tests__/ConversationTimelineView.test.tsx +405 -0
- package/src/conversation/__tests__/ConversationsWorkbench.test.tsx +860 -0
- package/src/conversation/__tests__/a11y/conversation-surfaces.a11y.test.tsx +235 -0
- package/src/conversation/__tests__/a11y/harness.tsx +100 -0
- package/src/conversation/__tests__/conversation-tooltips.layout.test.tsx +167 -0
- package/src/conversation/__tests__/conversationPollingComposition.test.tsx +138 -0
- package/src/conversation/__tests__/conversationPresentation.test.ts +289 -0
- package/src/conversation/__tests__/useConversation.test.tsx +243 -0
- package/src/conversation/__tests__/useConversationList.test.tsx +492 -0
- package/src/conversation/__tests__/useConversationParticipation.test.tsx +190 -0
- package/src/conversation/__tests__/useConversationTimeline.test.tsx +258 -0
- package/src/conversation/__tests__/useConversationsWantsHumanCount.test.tsx +102 -0
- package/src/conversation/conversationPresentation.ts +305 -0
- package/src/conversation/index.ts +80 -0
- package/src/conversation/polling.ts +41 -0
- package/src/conversation/useConversation.ts +221 -0
- package/src/conversation/useConversationList.ts +328 -0
- package/src/conversation/useConversationParticipation.ts +178 -0
- package/src/conversation/useConversationTimeline.ts +244 -0
- package/src/conversation/useConversationsWantsHumanCount.ts +86 -0
- package/src/execution/useCreateAgentExecution.ts +18 -0
- package/src/index.ts +76 -4
- package/src/internal/Pagination.tsx +72 -0
- package/src/internal/__tests__/fake-timer-slices.ts +29 -0
- package/src/internal/__tests__/useAutoScroll.layout.test.tsx +143 -0
- package/src/internal/__tests__/useAutoScroll.test.tsx +62 -5
- package/src/internal/__tests__/useFetch-poll-under-render-pressure.test.tsx +79 -0
- package/src/internal/tooltip.tsx +6 -1
- package/src/internal/useAutoScroll.ts +66 -20
- package/src/internal/useFetch.ts +15 -4
- package/src/models/ModelSelector.tsx +133 -4
- package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +256 -0
- package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
- package/src/models/index.ts +2 -0
- package/src/models/registry.ts +17 -0
- package/src/models/service-tier.ts +49 -0
- package/src/schedule/ScheduleDetailView.tsx +1179 -111
- package/src/schedule/ScheduleForm.tsx +218 -7
- package/src/schedule/ScheduleRunsTable.tsx +424 -0
- package/src/schedule/__tests__/ScheduleDetailView.test.tsx +431 -8
- package/src/schedule/__tests__/scheduleCreation.test.tsx +197 -0
- package/src/schedule/__tests__/scheduleHooks.test.tsx +94 -7
- package/src/schedule/index.ts +12 -0
- package/src/schedule/useScheduleRuns.ts +93 -0
- package/src/schedule/useTriggerSchedule.ts +39 -14
- package/src/schedule/useUpdateScheduleSpec.ts +108 -0
- package/src/session/useNewSessionFlow.ts +1 -0
- package/src/session/useSessionConversation.ts +12 -0
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/sharing/__tests__/useSaveAgentShare.test.tsx +55 -1
- package/src/sharing/__tests__/useShareToolReadiness.test.tsx +1 -0
- package/src/sharing/useSaveAgentShare.ts +19 -1
- package/src/sidebar/WorkspaceSidebar.tsx +62 -3
- package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +56 -1
- package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
- package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
- package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
- package/src/workflow/starter-workflow-yaml.ts +2 -1
- package/styles.css +1 -1
- package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
- package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
- package/workflow/inspector/forms/AgentCallForm.js +87 -9
- package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.js +31 -5
- package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
- package/workflow/starter-workflow-yaml.d.ts +1 -1
- package/workflow/starter-workflow-yaml.d.ts.map +1 -1
- package/workflow/starter-workflow-yaml.js +2 -1
- package/workflow/starter-workflow-yaml.js.map +1 -1
|
@@ -4,7 +4,15 @@ import type { ReactNode } from "react";
|
|
|
4
4
|
import { clone, create } from "@bufbuild/protobuf";
|
|
5
5
|
import { timestampFromDate } from "@bufbuild/protobuf/wkt";
|
|
6
6
|
import { ScheduleSchema, type Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
|
|
7
|
+
import {
|
|
8
|
+
ScheduleTriggerResultSchema,
|
|
9
|
+
ScheduleRunListSchema,
|
|
10
|
+
ScheduleRunSchema,
|
|
11
|
+
ScheduleRunOutcome,
|
|
12
|
+
ScheduleRunOrigin,
|
|
13
|
+
} from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/io_pb";
|
|
7
14
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
15
|
+
import { Harness } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
8
16
|
import { StigmerContext } from "../../context";
|
|
9
17
|
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
10
18
|
import { ScheduleDetailView } from "../ScheduleDetailView";
|
|
@@ -32,6 +40,15 @@ function makeSchedule(overrides?: {
|
|
|
32
40
|
enabled?: boolean;
|
|
33
41
|
pausedReason?: string;
|
|
34
42
|
lastExecutionId?: string;
|
|
43
|
+
cron?: string;
|
|
44
|
+
consecutiveFailures?: number;
|
|
45
|
+
environmentRefs?: readonly { org: string; slug: string }[];
|
|
46
|
+
harness?: Harness;
|
|
47
|
+
runConfig?: {
|
|
48
|
+
modelName?: string;
|
|
49
|
+
maxCostUsd?: number;
|
|
50
|
+
maxToolRounds?: number;
|
|
51
|
+
};
|
|
35
52
|
}): Schedule {
|
|
36
53
|
return create(ScheduleSchema, {
|
|
37
54
|
apiVersion: "agentic.stigmer.ai/v1",
|
|
@@ -43,7 +60,7 @@ function makeSchedule(overrides?: {
|
|
|
43
60
|
org: "isc",
|
|
44
61
|
},
|
|
45
62
|
spec: {
|
|
46
|
-
cron: "0 9 * * *",
|
|
63
|
+
cron: overrides?.cron ?? "0 9 * * *",
|
|
47
64
|
timeZone: "Asia/Kolkata",
|
|
48
65
|
enabled: overrides?.enabled ?? true,
|
|
49
66
|
target: {
|
|
@@ -51,6 +68,15 @@ function makeSchedule(overrides?: {
|
|
|
51
68
|
value: {
|
|
52
69
|
agentRef: { kind: ApiResourceKind.agent, org: "isc", slug: "fee-reminder" },
|
|
53
70
|
message: "Send today's fee reminders.",
|
|
71
|
+
environmentRefs: (overrides?.environmentRefs ?? []).map((r) => ({
|
|
72
|
+
kind: ApiResourceKind.environment,
|
|
73
|
+
org: r.org,
|
|
74
|
+
slug: r.slug,
|
|
75
|
+
})),
|
|
76
|
+
...(overrides?.harness !== undefined
|
|
77
|
+
? { harness: overrides.harness }
|
|
78
|
+
: {}),
|
|
79
|
+
...(overrides?.runConfig ? { runConfig: overrides.runConfig } : {}),
|
|
54
80
|
},
|
|
55
81
|
},
|
|
56
82
|
},
|
|
@@ -58,7 +84,8 @@ function makeSchedule(overrides?: {
|
|
|
58
84
|
nextFireAt: timestampFromDate(new Date(NOW.getTime() + 3 * 3_600_000)),
|
|
59
85
|
lastFireAt: timestampFromDate(new Date(NOW.getTime() - 5 * 60_000)),
|
|
60
86
|
lastExecutionId: overrides?.lastExecutionId ?? "aex_01run",
|
|
61
|
-
consecutiveFailures:
|
|
87
|
+
consecutiveFailures:
|
|
88
|
+
overrides?.consecutiveFailures ?? (overrides?.pausedReason ? 5 : 0),
|
|
62
89
|
pausedReason: overrides?.pausedReason ?? "",
|
|
63
90
|
},
|
|
64
91
|
});
|
|
@@ -69,9 +96,12 @@ interface MockClient {
|
|
|
69
96
|
getByReference: ReturnType<typeof vi.fn>;
|
|
70
97
|
resume: ReturnType<typeof vi.fn>;
|
|
71
98
|
trigger: ReturnType<typeof vi.fn>;
|
|
99
|
+
listRuns: ReturnType<typeof vi.fn>;
|
|
72
100
|
delete: ReturnType<typeof vi.fn>;
|
|
73
101
|
};
|
|
74
102
|
manifest: { apply: ReturnType<typeof vi.fn> };
|
|
103
|
+
// The environments inline editor's picker lists the org's environments.
|
|
104
|
+
environment: { list: ReturnType<typeof vi.fn> };
|
|
75
105
|
}
|
|
76
106
|
|
|
77
107
|
function makeClient(schedule: Schedule): MockClient {
|
|
@@ -79,7 +109,18 @@ function makeClient(schedule: Schedule): MockClient {
|
|
|
79
109
|
schedule: {
|
|
80
110
|
getByReference: vi.fn().mockResolvedValue(schedule),
|
|
81
111
|
resume: vi.fn().mockResolvedValue(schedule),
|
|
82
|
-
trigger
|
|
112
|
+
// The synchronous trigger answers with the run's real outcome
|
|
113
|
+
// (DD-017 D-6) — a started run by default.
|
|
114
|
+
trigger: vi.fn().mockResolvedValue(
|
|
115
|
+
create(ScheduleTriggerResultSchema, {
|
|
116
|
+
outcome: ScheduleRunOutcome.STARTED,
|
|
117
|
+
executionId: "aex_01triggered",
|
|
118
|
+
schedule,
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
listRuns: vi.fn().mockResolvedValue(
|
|
122
|
+
create(ScheduleRunListSchema, { items: [], totalCount: 0 }),
|
|
123
|
+
),
|
|
83
124
|
delete: vi.fn().mockResolvedValue(schedule),
|
|
84
125
|
},
|
|
85
126
|
manifest: {
|
|
@@ -93,6 +134,9 @@ function makeClient(schedule: Schedule): MockClient {
|
|
|
93
134
|
message: doc.message,
|
|
94
135
|
})),
|
|
95
136
|
},
|
|
137
|
+
environment: {
|
|
138
|
+
list: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
|
139
|
+
},
|
|
96
140
|
};
|
|
97
141
|
}
|
|
98
142
|
|
|
@@ -125,8 +169,10 @@ describe("ScheduleDetailView", () => {
|
|
|
125
169
|
renderView(makeClient(makeSchedule()));
|
|
126
170
|
|
|
127
171
|
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
172
|
+
// The cadence humanizes into plain English (the stored time zone is
|
|
173
|
+
// part of the sentence); the raw cron stays as the precise record.
|
|
174
|
+
expect(screen.getByText("Every day at 09:00 (Asia/Kolkata)")).toBeTruthy();
|
|
128
175
|
expect(screen.getByText("0 9 * * *")).toBeTruthy();
|
|
129
|
-
expect(screen.getByText("Asia/Kolkata")).toBeTruthy();
|
|
130
176
|
expect(screen.getByText("isc/fee-reminder")).toBeTruthy();
|
|
131
177
|
expect(screen.getByText("Send today's fee reminders.")).toBeTruthy();
|
|
132
178
|
expect(screen.getByText("in 3h")).toBeTruthy();
|
|
@@ -203,12 +249,68 @@ describe("ScheduleDetailView", () => {
|
|
|
203
249
|
);
|
|
204
250
|
});
|
|
205
251
|
|
|
206
|
-
it("
|
|
207
|
-
|
|
252
|
+
it("offers 'Enable & run now' on a disabled schedule (DD-017 D-5)", async () => {
|
|
253
|
+
const client = makeClient(makeSchedule({ enabled: false }));
|
|
254
|
+
renderView(client);
|
|
208
255
|
|
|
209
256
|
await screen.findByText("Schedule is disabled");
|
|
210
|
-
|
|
211
|
-
|
|
257
|
+
// No plain "Run now": a disabled schedule cannot fire server-side, so
|
|
258
|
+
// the staged-test flow is one click that enables THEN fires.
|
|
259
|
+
expect(screen.queryByRole("button", { name: "Run now" })).toBeNull();
|
|
260
|
+
const enableAndRun = screen.getByRole("button", { name: "Enable & run now" });
|
|
261
|
+
expect((enableAndRun as HTMLButtonElement).disabled).toBe(false);
|
|
262
|
+
|
|
263
|
+
fireEvent.click(enableAndRun);
|
|
264
|
+
await screen.findByText("Enable and run this schedule now?");
|
|
265
|
+
fireEvent.click(screen.getByText("Enable & run"));
|
|
266
|
+
|
|
267
|
+
// Enable (a full-proto re-apply) THEN fire.
|
|
268
|
+
await waitFor(() => expect(client.manifest.apply).toHaveBeenCalledOnce());
|
|
269
|
+
const doc = client.manifest.apply.mock.calls[0][0] as { message: Schedule };
|
|
270
|
+
expect(doc.message.spec?.enabled).toBe(true);
|
|
271
|
+
await waitFor(() =>
|
|
272
|
+
expect(client.schedule.trigger).toHaveBeenCalledWith("sch_01example"),
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("navigates to the execution on a started run", async () => {
|
|
277
|
+
const onNavigateToExecution = vi.fn();
|
|
278
|
+
const client = makeClient(makeSchedule());
|
|
279
|
+
renderView(client, { onNavigateToExecution });
|
|
280
|
+
|
|
281
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
282
|
+
fireEvent.click(screen.getByRole("button", { name: "Run now" }));
|
|
283
|
+
await screen.findByText("Run this schedule now?");
|
|
284
|
+
fireEvent.click(screen.getByText("Start run"));
|
|
285
|
+
|
|
286
|
+
await waitFor(() =>
|
|
287
|
+
expect(onNavigateToExecution).toHaveBeenCalledWith("aex_01triggered"),
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("renders the run history from the fire ledger", async () => {
|
|
292
|
+
const client = makeClient(makeSchedule());
|
|
293
|
+
client.schedule.listRuns.mockResolvedValue(
|
|
294
|
+
create(ScheduleRunListSchema, {
|
|
295
|
+
totalCount: 1,
|
|
296
|
+
items: [
|
|
297
|
+
create(ScheduleRunSchema, {
|
|
298
|
+
scheduleId: "sch_01example",
|
|
299
|
+
origin: ScheduleRunOrigin.CRON,
|
|
300
|
+
outcome: ScheduleRunOutcome.REFUSED,
|
|
301
|
+
reason:
|
|
302
|
+
"run refused: MCP server 'isc-gym' requires environment variable 'ISC_MCP_SHARED_SECRET'",
|
|
303
|
+
nominalFireTime: timestampFromDate(new Date(NOW.getTime() - 60_000)),
|
|
304
|
+
}),
|
|
305
|
+
],
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
renderView(client);
|
|
309
|
+
|
|
310
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
311
|
+
// The refusal reason — invisible before the ledger — is now on screen.
|
|
312
|
+
await screen.findByText(/ISC_MCP_SHARED_SECRET/);
|
|
313
|
+
expect(screen.getByText("Refused")).toBeTruthy();
|
|
212
314
|
});
|
|
213
315
|
|
|
214
316
|
it("navigates through the callback seams (DD-004)", async () => {
|
|
@@ -257,4 +359,325 @@ describe("ScheduleDetailView", () => {
|
|
|
257
359
|
|
|
258
360
|
expect(schedule.spec?.enabled).toBe(original.spec?.enabled);
|
|
259
361
|
});
|
|
362
|
+
|
|
363
|
+
// -------------------------------------------------------------------------
|
|
364
|
+
// Cadence humanization
|
|
365
|
+
// -------------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
it("falls back to the raw cron for expressions outside the builder's shapes", async () => {
|
|
368
|
+
renderView(makeClient(makeSchedule({ cron: "*/5 9-17 * * 1-5" })));
|
|
369
|
+
|
|
370
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
371
|
+
// Unrecognized shape: the raw expression IS the primary display —
|
|
372
|
+
// no invented sentence — with the time zone shown alongside.
|
|
373
|
+
expect(screen.getByText("*/5 9-17 * * 1-5")).toBeTruthy();
|
|
374
|
+
expect(screen.getByText("Asia/Kolkata")).toBeTruthy();
|
|
375
|
+
expect(screen.queryByText(/^Every /)).toBeNull();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// -------------------------------------------------------------------------
|
|
379
|
+
// Failure streak
|
|
380
|
+
// -------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
it("renders a plain 0 for a healthy failure streak", async () => {
|
|
383
|
+
renderView(makeClient(makeSchedule()));
|
|
384
|
+
|
|
385
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
386
|
+
expect(screen.getByText("Failure streak")).toBeTruthy();
|
|
387
|
+
expect(screen.getByText("0")).toBeTruthy();
|
|
388
|
+
expect(screen.queryByText(/consecutive failed/)).toBeNull();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("explains a non-zero failure streak in plain language", async () => {
|
|
392
|
+
renderView(makeClient(makeSchedule({ consecutiveFailures: 2 })));
|
|
393
|
+
|
|
394
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
395
|
+
expect(screen.getByText("2 consecutive failed runs")).toBeTruthy();
|
|
396
|
+
// The copy stays qualitative: the pause threshold is server config
|
|
397
|
+
// the API does not expose, so no hardcoded denominator.
|
|
398
|
+
expect(
|
|
399
|
+
screen.getByText(/pauses the schedule automatically/),
|
|
400
|
+
).toBeTruthy();
|
|
401
|
+
expect(screen.getByText(/Manual runs never count/)).toBeTruthy();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// -------------------------------------------------------------------------
|
|
405
|
+
// Tabs — Overview and the paginated Runs tab
|
|
406
|
+
// -------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
it("splits into Overview and Runs tabs, with the run count as badge", async () => {
|
|
409
|
+
const client = makeClient(makeSchedule());
|
|
410
|
+
client.schedule.listRuns.mockResolvedValue(
|
|
411
|
+
create(ScheduleRunListSchema, {
|
|
412
|
+
totalCount: 12,
|
|
413
|
+
items: [
|
|
414
|
+
create(ScheduleRunSchema, {
|
|
415
|
+
scheduleId: "sch_01example",
|
|
416
|
+
origin: ScheduleRunOrigin.CRON,
|
|
417
|
+
outcome: ScheduleRunOutcome.COMPLETED,
|
|
418
|
+
nominalFireTime: timestampFromDate(new Date(NOW.getTime() - 60_000)),
|
|
419
|
+
}),
|
|
420
|
+
],
|
|
421
|
+
}),
|
|
422
|
+
);
|
|
423
|
+
renderView(client);
|
|
424
|
+
|
|
425
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
426
|
+
// The badge fills in once the (separate) runs fetch resolves.
|
|
427
|
+
await waitFor(() => {
|
|
428
|
+
const tabs = screen.getAllByRole("tab");
|
|
429
|
+
expect(tabs.map((t) => t.textContent)).toEqual(["Overview", "Runs12"]);
|
|
430
|
+
});
|
|
431
|
+
// Overview is the default: definition on screen, no full table.
|
|
432
|
+
expect(screen.getByText("Target agent")).toBeTruthy();
|
|
433
|
+
|
|
434
|
+
fireEvent.click(screen.getByRole("tab", { name: /Runs/ }));
|
|
435
|
+
await screen.findByRole("table", { name: "Run history" });
|
|
436
|
+
expect(screen.queryByText("Target agent")).toBeNull();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("pages through the run history (the hook's pagination, finally used)", async () => {
|
|
440
|
+
const client = makeClient(makeSchedule());
|
|
441
|
+
client.schedule.listRuns.mockImplementation(
|
|
442
|
+
async (req: { pageInfo?: { num: number; size: number } }) =>
|
|
443
|
+
create(ScheduleRunListSchema, {
|
|
444
|
+
totalCount: 30,
|
|
445
|
+
items: Array.from(
|
|
446
|
+
{ length: Math.min(req.pageInfo?.size ?? 25, 30) },
|
|
447
|
+
(_, i) =>
|
|
448
|
+
create(ScheduleRunSchema, {
|
|
449
|
+
scheduleId: "sch_01example",
|
|
450
|
+
origin: ScheduleRunOrigin.CRON,
|
|
451
|
+
outcome: ScheduleRunOutcome.COMPLETED,
|
|
452
|
+
nominalFireTime: timestampFromDate(
|
|
453
|
+
new Date(NOW.getTime() - (i + 1) * 60_000),
|
|
454
|
+
),
|
|
455
|
+
}),
|
|
456
|
+
),
|
|
457
|
+
}),
|
|
458
|
+
);
|
|
459
|
+
renderView(client);
|
|
460
|
+
|
|
461
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
462
|
+
fireEvent.click(screen.getByRole("tab", { name: /Runs/ }));
|
|
463
|
+
await screen.findByText("Page 1 of 2");
|
|
464
|
+
|
|
465
|
+
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
|
466
|
+
await waitFor(() => {
|
|
467
|
+
const pageTwoCall = client.schedule.listRuns.mock.calls.find((call) => {
|
|
468
|
+
const req = call[0] as { pageInfo?: { num: number; size: number } };
|
|
469
|
+
return req.pageInfo?.num === 2 && req.pageInfo?.size === 25;
|
|
470
|
+
});
|
|
471
|
+
expect(pageTwoCall).toBeTruthy();
|
|
472
|
+
});
|
|
473
|
+
await screen.findByText("Page 2 of 2");
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("links the Overview recent-runs strip to the Runs tab", async () => {
|
|
477
|
+
const client = makeClient(makeSchedule());
|
|
478
|
+
client.schedule.listRuns.mockImplementation(
|
|
479
|
+
async (req: { pageInfo?: { num: number; size: number } }) =>
|
|
480
|
+
create(ScheduleRunListSchema, {
|
|
481
|
+
totalCount: 12,
|
|
482
|
+
items: Array.from(
|
|
483
|
+
{ length: Math.min(req.pageInfo?.size ?? 25, 12) },
|
|
484
|
+
(_, i) =>
|
|
485
|
+
create(ScheduleRunSchema, {
|
|
486
|
+
scheduleId: "sch_01example",
|
|
487
|
+
origin: ScheduleRunOrigin.CRON,
|
|
488
|
+
outcome: ScheduleRunOutcome.COMPLETED,
|
|
489
|
+
nominalFireTime: timestampFromDate(
|
|
490
|
+
new Date(NOW.getTime() - (i + 1) * 60_000),
|
|
491
|
+
),
|
|
492
|
+
}),
|
|
493
|
+
),
|
|
494
|
+
}),
|
|
495
|
+
);
|
|
496
|
+
renderView(client);
|
|
497
|
+
|
|
498
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
499
|
+
fireEvent.click(
|
|
500
|
+
await screen.findByRole("button", { name: "View all 12 runs" }),
|
|
501
|
+
);
|
|
502
|
+
await screen.findByRole("table", { name: "Run history" });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// -------------------------------------------------------------------------
|
|
506
|
+
// Inline editing — every save re-applies the FULL proto (never a
|
|
507
|
+
// down-converted input, which would silently wipe fields)
|
|
508
|
+
// -------------------------------------------------------------------------
|
|
509
|
+
|
|
510
|
+
it("does not offer inline editing without the editable prop", async () => {
|
|
511
|
+
renderView(makeClient(makeSchedule()));
|
|
512
|
+
|
|
513
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
514
|
+
fireEvent.click(screen.getByText("Send today's fee reminders."));
|
|
515
|
+
expect(screen.queryByRole("textbox")).toBeNull();
|
|
516
|
+
expect(screen.queryByRole("button", { name: "Edit cadence" })).toBeNull();
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("never offers an edit affordance for the immutable target agent", async () => {
|
|
520
|
+
renderView(makeClient(makeSchedule()), { editable: true });
|
|
521
|
+
|
|
522
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
523
|
+
// The mutable fields have editors; the server-immutable target does not.
|
|
524
|
+
expect(screen.getByRole("button", { name: "Edit cadence" })).toBeTruthy();
|
|
525
|
+
expect(
|
|
526
|
+
screen.queryByRole("button", { name: /edit target/i }),
|
|
527
|
+
).toBeNull();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("saves an edited message by re-applying the full proto", async () => {
|
|
531
|
+
const client = makeClient(makeSchedule());
|
|
532
|
+
renderView(client, { editable: true });
|
|
533
|
+
|
|
534
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
535
|
+
fireEvent.click(screen.getByText("Send today's fee reminders."));
|
|
536
|
+
const textarea = await screen.findByRole("textbox");
|
|
537
|
+
fireEvent.change(textarea, {
|
|
538
|
+
target: { value: "Send this week's fee reminders." },
|
|
539
|
+
});
|
|
540
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
541
|
+
|
|
542
|
+
await waitFor(() => expect(client.manifest.apply).toHaveBeenCalledOnce());
|
|
543
|
+
const doc = client.manifest.apply.mock.calls[0][0] as { message: Schedule };
|
|
544
|
+
const target =
|
|
545
|
+
doc.message.spec?.target?.case === "agent"
|
|
546
|
+
? doc.message.spec.target.value
|
|
547
|
+
: undefined;
|
|
548
|
+
expect(target?.message).toBe("Send this week's fee reminders.");
|
|
549
|
+
// Full-proto write: everything the editor did not touch survives.
|
|
550
|
+
expect(doc.message.metadata?.id).toBe("sch_01example");
|
|
551
|
+
expect(doc.message.spec?.cron).toBe("0 9 * * *");
|
|
552
|
+
expect(doc.message.spec?.enabled).toBe(true);
|
|
553
|
+
expect(target?.agentRef?.slug).toBe("fee-reminder");
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("saves an edited cadence through the custom-cron escape hatch", async () => {
|
|
557
|
+
const client = makeClient(makeSchedule());
|
|
558
|
+
renderView(client, { editable: true });
|
|
559
|
+
|
|
560
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
561
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit cadence" }));
|
|
562
|
+
// The stored cron round-trips into the preset picker; Custom
|
|
563
|
+
// prefills from it rather than starting blank.
|
|
564
|
+
fireEvent.click(screen.getByRole("radio", { name: "Custom cron" }));
|
|
565
|
+
fireEvent.change(screen.getByLabelText("Cron expression"), {
|
|
566
|
+
target: { value: "0 9 * * 1-5" },
|
|
567
|
+
});
|
|
568
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
569
|
+
|
|
570
|
+
await waitFor(() => expect(client.manifest.apply).toHaveBeenCalledOnce());
|
|
571
|
+
const doc = client.manifest.apply.mock.calls[0][0] as { message: Schedule };
|
|
572
|
+
expect(doc.message.spec?.cron).toBe("0 9 * * 1-5");
|
|
573
|
+
// The time zone rides along with the cadence editor, unchanged here.
|
|
574
|
+
expect(doc.message.spec?.timeZone).toBe("Asia/Kolkata");
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("saves an edited budget, preserving the model and the API-only tool rounds", async () => {
|
|
578
|
+
const client = makeClient(
|
|
579
|
+
makeSchedule({
|
|
580
|
+
harness: Harness.CURSOR,
|
|
581
|
+
runConfig: { modelName: "composer-2", maxToolRounds: 15 },
|
|
582
|
+
}),
|
|
583
|
+
);
|
|
584
|
+
renderView(client, { editable: true });
|
|
585
|
+
|
|
586
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
587
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit budget" }));
|
|
588
|
+
fireEvent.change(screen.getByLabelText("Budget per run (USD)"), {
|
|
589
|
+
target: { value: "2.5" },
|
|
590
|
+
});
|
|
591
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
592
|
+
|
|
593
|
+
await waitFor(() => expect(client.manifest.apply).toHaveBeenCalledOnce());
|
|
594
|
+
const doc = client.manifest.apply.mock.calls[0][0] as { message: Schedule };
|
|
595
|
+
const target =
|
|
596
|
+
doc.message.spec?.target?.case === "agent"
|
|
597
|
+
? doc.message.spec.target.value
|
|
598
|
+
: undefined;
|
|
599
|
+
expect(target?.runConfig?.maxCostUsd).toBe(2.5);
|
|
600
|
+
// The budget editor owns exactly one field — everything else the
|
|
601
|
+
// run config stores survives the save.
|
|
602
|
+
expect(target?.runConfig?.modelName).toBe("composer-2");
|
|
603
|
+
expect(target?.runConfig?.maxToolRounds).toBe(15);
|
|
604
|
+
expect(target?.harness).toBe(Harness.CURSOR);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("resetting engine & model unpins both, preserving the budget", async () => {
|
|
608
|
+
const client = makeClient(
|
|
609
|
+
makeSchedule({
|
|
610
|
+
harness: Harness.CURSOR,
|
|
611
|
+
runConfig: { modelName: "composer-2", maxCostUsd: 2.5 },
|
|
612
|
+
}),
|
|
613
|
+
);
|
|
614
|
+
renderView(client, { editable: true });
|
|
615
|
+
|
|
616
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
617
|
+
// The read view names the pinned engine and model.
|
|
618
|
+
expect(screen.getByText("Cursor · composer-2")).toBeTruthy();
|
|
619
|
+
|
|
620
|
+
fireEvent.click(
|
|
621
|
+
screen.getByRole("button", { name: "Edit engine and model" }),
|
|
622
|
+
);
|
|
623
|
+
fireEvent.click(
|
|
624
|
+
screen.getByRole("button", { name: "Reset to platform default" }),
|
|
625
|
+
);
|
|
626
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
627
|
+
|
|
628
|
+
await waitFor(() => expect(client.manifest.apply).toHaveBeenCalledOnce());
|
|
629
|
+
const doc = client.manifest.apply.mock.calls[0][0] as { message: Schedule };
|
|
630
|
+
const target =
|
|
631
|
+
doc.message.spec?.target?.case === "agent"
|
|
632
|
+
? doc.message.spec.target.value
|
|
633
|
+
: undefined;
|
|
634
|
+
// Model and harness are one atomic choice: unpinning the model
|
|
635
|
+
// unpins the engine with it (the platform defaults apply again)...
|
|
636
|
+
expect(target?.harness).toBe(Harness.UNSPECIFIED);
|
|
637
|
+
expect(target?.runConfig?.modelName).toBe("");
|
|
638
|
+
// ...while the budget the editor does not own survives.
|
|
639
|
+
expect(target?.runConfig?.maxCostUsd).toBe(2.5);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it("round-trips environment references with the environment kind stamped", async () => {
|
|
643
|
+
const client = makeClient(
|
|
644
|
+
makeSchedule({
|
|
645
|
+
environmentRefs: [{ org: "isc", slug: "isc-mcp-credentials" }],
|
|
646
|
+
}),
|
|
647
|
+
);
|
|
648
|
+
renderView(client, { editable: true });
|
|
649
|
+
|
|
650
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
651
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit environments" }));
|
|
652
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
653
|
+
|
|
654
|
+
await waitFor(() => expect(client.manifest.apply).toHaveBeenCalledOnce());
|
|
655
|
+
const doc = client.manifest.apply.mock.calls[0][0] as { message: Schedule };
|
|
656
|
+
const target =
|
|
657
|
+
doc.message.spec?.target?.case === "agent"
|
|
658
|
+
? doc.message.spec.target.value
|
|
659
|
+
: undefined;
|
|
660
|
+
expect(target?.environmentRefs).toHaveLength(1);
|
|
661
|
+
expect(target?.environmentRefs?.[0].slug).toBe("isc-mcp-credentials");
|
|
662
|
+
expect(target?.environmentRefs?.[0].kind).toBe(
|
|
663
|
+
ApiResourceKind.environment,
|
|
664
|
+
);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it("keeps the editor open with the server's message on a failed save", async () => {
|
|
668
|
+
const client = makeClient(makeSchedule());
|
|
669
|
+
client.manifest.apply.mockRejectedValueOnce(
|
|
670
|
+
new Error("spec.cron must have exactly 5 fields"),
|
|
671
|
+
);
|
|
672
|
+
renderView(client, { editable: true });
|
|
673
|
+
|
|
674
|
+
await screen.findByRole("heading", { name: "daily-fee-reminders" });
|
|
675
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit cadence" }));
|
|
676
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
677
|
+
|
|
678
|
+
// The server's message lands next to the field; the editor stays
|
|
679
|
+
// open so the draft is not lost (DD-006).
|
|
680
|
+
await screen.findByText(/must have exactly 5 fields/);
|
|
681
|
+
expect(screen.getByRole("button", { name: "Save" })).toBeTruthy();
|
|
682
|
+
});
|
|
260
683
|
});
|