@stigmer/react 3.2.1 → 3.2.3
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/activity/entry-status-badge.d.ts +23 -0
- package/activity/entry-status-badge.d.ts.map +1 -0
- package/activity/entry-status-badge.js +18 -0
- package/activity/entry-status-badge.js.map +1 -0
- package/activity/format-relative-time.d.ts +16 -0
- package/activity/format-relative-time.d.ts.map +1 -0
- package/activity/format-relative-time.js +38 -0
- package/activity/format-relative-time.js.map +1 -0
- package/activity/index.d.ts +2 -0
- package/activity/index.d.ts.map +1 -1
- package/activity/index.js +2 -0
- package/activity/index.js.map +1 -1
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +6 -3
- package/agent/AgentDetailView.js.map +1 -1
- package/execution/index.d.ts +1 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +0 -1
- package/execution/index.js.map +1 -1
- package/execution/useLiveAgentExecution.d.ts +17 -1
- package/execution/useLiveAgentExecution.d.ts.map +1 -1
- package/execution/useLiveAgentExecution.js +19 -4
- package/execution/useLiveAgentExecution.js.map +1 -1
- package/index.d.ts +7 -7
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/useInViewport.d.ts +24 -0
- package/internal/useInViewport.d.ts.map +1 -0
- package/internal/useInViewport.js +49 -0
- package/internal/useInViewport.js.map +1 -0
- package/library/VisibilitySelector.d.ts +22 -6
- package/library/VisibilitySelector.d.ts.map +1 -1
- package/library/VisibilitySelector.js +12 -4
- package/library/VisibilitySelector.js.map +1 -1
- package/library/index.d.ts +1 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +4 -3
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/package.json +4 -4
- package/session/SessionViewer.js +8 -1
- package/session/SessionViewer.js.map +1 -1
- package/session/audience.d.ts +6 -1
- package/session/audience.d.ts.map +1 -1
- package/skill/SkillDetailView.d.ts.map +1 -1
- package/skill/SkillDetailView.js +4 -3
- package/skill/SkillDetailView.js.map +1 -1
- package/src/access/__tests__/useManageAccess.test.tsx +104 -0
- package/src/activity/__tests__/format-relative-time.test.ts +40 -0
- package/src/activity/entry-status-badge.ts +39 -0
- package/src/activity/format-relative-time.ts +38 -0
- package/src/activity/index.ts +7 -0
- package/src/agent/AgentDetailView.tsx +9 -3
- package/src/execution/__tests__/useLiveAgentExecution.test.tsx +125 -0
- package/src/execution/index.ts +4 -7
- package/src/execution/useLiveAgentExecution.ts +41 -3
- package/src/index.ts +7 -5
- package/src/internal/__tests__/useInViewport.test.tsx +115 -0
- package/src/internal/useInViewport.ts +63 -0
- package/src/library/VisibilitySelector.tsx +50 -9
- package/src/library/__tests__/VisibilitySelector.test.tsx +38 -0
- package/src/library/index.ts +4 -1
- package/src/mcp-server/McpServerDetailView.tsx +7 -3
- package/src/session/SessionViewer.tsx +7 -1
- package/src/session/__tests__/audienceWiring.test.tsx +31 -1
- package/src/session/audience.ts +6 -1
- package/src/skill/SkillDetailView.tsx +7 -3
- package/src/workflow/WorkflowAgentCallTranscript.tsx +279 -0
- package/src/workflow/WorkflowDetailView.tsx +7 -3
- package/src/workflow/WorkflowExecutionViewer.tsx +20 -77
- package/src/workflow/WorkflowTaskApprovalSummary.tsx +45 -8
- package/src/workflow/__tests__/WorkflowAgentCallTranscript.liveregion.test.tsx +124 -0
- package/src/workflow/__tests__/{WorkflowAgentExecutionDocument.test.tsx → WorkflowAgentCallTranscript.test.tsx} +146 -121
- package/src/workflow/__tests__/WorkflowExecutionViewer.approvals.test.tsx +81 -220
- package/src/workflow/__tests__/WorkflowExecutionViewer.reconciliation.test.tsx +17 -16
- package/src/workflow/__tests__/WorkflowTaskApprovalSummary.test.tsx +52 -2
- package/src/workflow/__tests__/WorkflowTaskThread.test.tsx +168 -185
- package/src/workflow/__tests__/agent-call-live-experience.test.ts +4 -4
- package/src/workflow/__tests__/task-approval.test.ts +119 -0
- package/src/workflow/__tests__/task-presentation.test.ts +35 -1
- package/src/workflow/__tests__/useWorkflowExecutionPanel.test.ts +0 -57
- package/src/workflow/index.ts +6 -3
- package/src/workflow/task-detail/index.ts +3 -0
- package/src/workflow/task-detail/task-approval.ts +95 -1
- package/src/workflow/thread/WorkflowTaskThread.tsx +96 -252
- package/src/workflow/thread/task-presentation.ts +8 -2
- package/src/workflow/useWorkflowExecutionPanel.ts +0 -47
- package/workflow/WorkflowAgentCallTranscript.d.ts +87 -0
- package/workflow/WorkflowAgentCallTranscript.d.ts.map +1 -0
- package/workflow/WorkflowAgentCallTranscript.js +98 -0
- package/workflow/WorkflowAgentCallTranscript.js.map +1 -0
- package/workflow/WorkflowDetailView.d.ts.map +1 -1
- package/workflow/WorkflowDetailView.js +4 -3
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
- package/workflow/WorkflowExecutionViewer.js +23 -52
- package/workflow/WorkflowExecutionViewer.js.map +1 -1
- package/workflow/WorkflowTaskApprovalSummary.d.ts +1 -1
- package/workflow/WorkflowTaskApprovalSummary.d.ts.map +1 -1
- package/workflow/WorkflowTaskApprovalSummary.js +18 -1
- package/workflow/WorkflowTaskApprovalSummary.js.map +1 -1
- package/workflow/index.d.ts +2 -2
- package/workflow/index.d.ts.map +1 -1
- package/workflow/index.js +2 -2
- package/workflow/index.js.map +1 -1
- package/workflow/task-detail/index.d.ts +1 -1
- package/workflow/task-detail/index.d.ts.map +1 -1
- package/workflow/task-detail/index.js +1 -1
- package/workflow/task-detail/index.js.map +1 -1
- package/workflow/task-detail/task-approval.d.ts +44 -1
- package/workflow/task-detail/task-approval.d.ts.map +1 -1
- package/workflow/task-detail/task-approval.js +47 -0
- package/workflow/task-detail/task-approval.js.map +1 -1
- package/workflow/thread/WorkflowTaskThread.d.ts +17 -25
- package/workflow/thread/WorkflowTaskThread.d.ts.map +1 -1
- package/workflow/thread/WorkflowTaskThread.js +60 -105
- package/workflow/thread/WorkflowTaskThread.js.map +1 -1
- package/workflow/thread/task-presentation.d.ts.map +1 -1
- package/workflow/thread/task-presentation.js +7 -2
- package/workflow/thread/task-presentation.js.map +1 -1
- package/workflow/useWorkflowExecutionPanel.d.ts +0 -12
- package/workflow/useWorkflowExecutionPanel.d.ts.map +1 -1
- package/workflow/useWorkflowExecutionPanel.js +0 -12
- package/workflow/useWorkflowExecutionPanel.js.map +1 -1
- package/execution/agent-execution-document.d.ts +0 -31
- package/execution/agent-execution-document.d.ts.map +0 -1
- package/execution/agent-execution-document.js +0 -48
- package/execution/agent-execution-document.js.map +0 -1
- package/src/execution/__tests__/agent-execution-document.test.ts +0 -45
- package/src/execution/agent-execution-document.ts +0 -54
- package/src/workflow/WorkflowAgentExecutionDocument.tsx +0 -283
- package/workflow/WorkflowAgentExecutionDocument.d.ts +0 -79
- package/workflow/WorkflowAgentExecutionDocument.d.ts.map +0 -1
- package/workflow/WorkflowAgentExecutionDocument.js +0 -87
- package/workflow/WorkflowAgentExecutionDocument.js.map +0 -1
|
@@ -27,9 +27,11 @@ import type { Stigmer } from "@stigmer/sdk";
|
|
|
27
27
|
vi.mock("../../execution/useLiveAgentExecution", () => ({
|
|
28
28
|
useLiveAgentExecution: vi.fn(),
|
|
29
29
|
}));
|
|
30
|
-
// The thread is the execution domain's heaviest organism; the
|
|
30
|
+
// The thread is the execution domain's heaviest organism; the transcript's
|
|
31
31
|
// contract with it is props-shaped, so a probe recording them suffices.
|
|
32
32
|
// The FileReviewDock renders REAL — its decision routing is the S5 subject.
|
|
33
|
+
// useInViewport runs REAL over the stubbed IntersectionObserver below, so
|
|
34
|
+
// the viewport gate is exercised end-to-end (ref attachment included).
|
|
33
35
|
vi.mock("../../execution/MessageThread", () => ({
|
|
34
36
|
MessageThread: vi.fn(() => <div data-testid="message-thread-probe" />),
|
|
35
37
|
}));
|
|
@@ -39,13 +41,34 @@ import { MessageThread } from "../../execution/MessageThread";
|
|
|
39
41
|
import { StigmerContext } from "../../context";
|
|
40
42
|
import { useWorkflowExecutionActions } from "../useWorkflowExecutionActions";
|
|
41
43
|
import {
|
|
42
|
-
|
|
44
|
+
WorkflowAgentCallTranscript,
|
|
43
45
|
type WorkflowAgentExecutionHitl,
|
|
44
|
-
} from "../
|
|
46
|
+
} from "../WorkflowAgentCallTranscript";
|
|
45
47
|
|
|
46
48
|
const mockUseLiveAgentExecution = vi.mocked(useLiveAgentExecution);
|
|
47
49
|
const mockMessageThread = vi.mocked(MessageThread);
|
|
48
50
|
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// IntersectionObserver stub (the useAutoScroll.test pattern) — drives the
|
|
53
|
+
// real useInViewport inside the component.
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
let ioCallback: IntersectionObserverCallback;
|
|
57
|
+
let ioDisconnect: ReturnType<typeof vi.fn>;
|
|
58
|
+
|
|
59
|
+
function fireIO(isIntersecting: boolean) {
|
|
60
|
+
act(() => {
|
|
61
|
+
ioCallback(
|
|
62
|
+
[{ isIntersecting } as IntersectionObserverEntry],
|
|
63
|
+
{} as IntersectionObserver,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Fixtures
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
49
72
|
function executionFixture(
|
|
50
73
|
id: string,
|
|
51
74
|
phase: ExecutionPhase,
|
|
@@ -115,12 +138,35 @@ function hookState(
|
|
|
115
138
|
|
|
116
139
|
beforeEach(() => {
|
|
117
140
|
vi.clearAllMocks();
|
|
141
|
+
ioDisconnect = vi.fn();
|
|
142
|
+
vi.stubGlobal(
|
|
143
|
+
"IntersectionObserver",
|
|
144
|
+
vi.fn((cb: IntersectionObserverCallback) => {
|
|
145
|
+
ioCallback = cb;
|
|
146
|
+
return {
|
|
147
|
+
observe: vi.fn(),
|
|
148
|
+
unobserve: vi.fn(),
|
|
149
|
+
disconnect: ioDisconnect,
|
|
150
|
+
takeRecords: vi.fn(() => []),
|
|
151
|
+
root: null,
|
|
152
|
+
rootMargin: "",
|
|
153
|
+
thresholds: [0],
|
|
154
|
+
};
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
cleanup();
|
|
161
|
+
vi.restoreAllMocks();
|
|
118
162
|
});
|
|
119
163
|
|
|
120
|
-
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Rendering & states
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
121
167
|
|
|
122
|
-
describe("
|
|
123
|
-
it("renders
|
|
168
|
+
describe("WorkflowAgentCallTranscript", () => {
|
|
169
|
+
it("renders the child transcript with the streamed execution", () => {
|
|
124
170
|
const running = executionFixture("aex_1", ExecutionPhase.EXECUTION_IN_PROGRESS);
|
|
125
171
|
mockUseLiveAgentExecution.mockReturnValue(
|
|
126
172
|
hookState({
|
|
@@ -131,21 +177,30 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
131
177
|
);
|
|
132
178
|
|
|
133
179
|
render(
|
|
134
|
-
<
|
|
135
|
-
childExecutionId="aex_1"
|
|
136
|
-
taskName="summarize-report"
|
|
137
|
-
agentSlug="analyst"
|
|
138
|
-
/>,
|
|
180
|
+
<WorkflowAgentCallTranscript childExecutionId="aex_1" agentSlug="analyst" />,
|
|
139
181
|
);
|
|
140
182
|
|
|
141
|
-
expect(mockUseLiveAgentExecution).toHaveBeenCalledWith("aex_1");
|
|
142
183
|
expect(screen.getByTestId("message-thread-probe")).toBeTruthy();
|
|
143
|
-
expect(screen.getByText("Live")).toBeTruthy();
|
|
144
|
-
expect(screen.getByText("summarize-report")).toBeTruthy();
|
|
145
|
-
expect(screen.getByText("analyst")).toBeTruthy();
|
|
146
|
-
|
|
147
184
|
const threadProps = mockMessageThread.mock.calls[0][0];
|
|
148
185
|
expect(threadProps.activeStreamExecution).toBe(running);
|
|
186
|
+
expect(
|
|
187
|
+
screen.getByRole("group", { name: "Transcript of agent analyst" }),
|
|
188
|
+
).toBeTruthy();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("is bounded: the root carries the height cap, never full-height", () => {
|
|
192
|
+
mockUseLiveAgentExecution.mockReturnValue(
|
|
193
|
+
hookState({
|
|
194
|
+
execution: executionFixture("aex_1", ExecutionPhase.EXECUTION_COMPLETED),
|
|
195
|
+
phase: ExecutionPhase.EXECUTION_COMPLETED,
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_1" />);
|
|
200
|
+
|
|
201
|
+
const root = screen.getByRole("group", { name: "Agent transcript" });
|
|
202
|
+
expect(root.className).toContain("max-h-[60vh]");
|
|
203
|
+
expect(root.className).not.toContain("h-full");
|
|
149
204
|
});
|
|
150
205
|
|
|
151
206
|
it("is read-only without hitl: no thread handlers, no records, no dock (DD-011)", () => {
|
|
@@ -161,13 +216,8 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
161
216
|
}),
|
|
162
217
|
);
|
|
163
218
|
|
|
164
|
-
render(
|
|
165
|
-
<WorkflowAgentExecutionDocument childExecutionId="aex_1" taskName="t" />,
|
|
166
|
-
);
|
|
219
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_1" />);
|
|
167
220
|
|
|
168
|
-
// The pre-S5 contract, preserved as the omitted-prop default: a host
|
|
169
|
-
// composing the document without workflow actions gets an honest
|
|
170
|
-
// status-only transcript — even with a pending set on the child.
|
|
171
221
|
const threadProps = mockMessageThread.mock.calls[0][0];
|
|
172
222
|
expect(threadProps.onApprovalSubmit).toBeUndefined();
|
|
173
223
|
expect(threadProps.submittingApprovalIds).toBeUndefined();
|
|
@@ -177,21 +227,6 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
177
227
|
).toBeNull();
|
|
178
228
|
});
|
|
179
229
|
|
|
180
|
-
it("renders a terminal transcript with the phase badge and no Live indicator", () => {
|
|
181
|
-
const done = executionFixture("aex_2", ExecutionPhase.EXECUTION_COMPLETED);
|
|
182
|
-
mockUseLiveAgentExecution.mockReturnValue(
|
|
183
|
-
hookState({ execution: done, phase: ExecutionPhase.EXECUTION_COMPLETED }),
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
render(
|
|
187
|
-
<WorkflowAgentExecutionDocument childExecutionId="aex_2" taskName="t" />,
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
expect(screen.getByTestId("message-thread-probe")).toBeTruthy();
|
|
191
|
-
expect(screen.getByRole("status", { name: "Completed" })).toBeTruthy();
|
|
192
|
-
expect(screen.queryByText("Live")).toBeNull();
|
|
193
|
-
});
|
|
194
|
-
|
|
195
230
|
it("shows a Reconnecting affordance during a transient stream drop", () => {
|
|
196
231
|
mockUseLiveAgentExecution.mockReturnValue(
|
|
197
232
|
hookState({
|
|
@@ -201,9 +236,7 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
201
236
|
}),
|
|
202
237
|
);
|
|
203
238
|
|
|
204
|
-
render(
|
|
205
|
-
<WorkflowAgentExecutionDocument childExecutionId="aex_3" taskName="t" />,
|
|
206
|
-
);
|
|
239
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_3" />);
|
|
207
240
|
|
|
208
241
|
expect(screen.getByText("Reconnecting…")).toBeTruthy();
|
|
209
242
|
// The last snapshot stays visible through the drop.
|
|
@@ -213,9 +246,7 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
213
246
|
it("shows the loading skeleton before the first snapshot", () => {
|
|
214
247
|
mockUseLiveAgentExecution.mockReturnValue(hookState({ isLoading: true }));
|
|
215
248
|
|
|
216
|
-
render(
|
|
217
|
-
<WorkflowAgentExecutionDocument childExecutionId="aex_4" taskName="t" />,
|
|
218
|
-
);
|
|
249
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_4" />);
|
|
219
250
|
|
|
220
251
|
expect(screen.getByLabelText("Loading conversation")).toBeTruthy();
|
|
221
252
|
expect(screen.queryByTestId("message-thread-probe")).toBeNull();
|
|
@@ -227,9 +258,7 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
227
258
|
hookState({ error: new Error("stream exhausted retries"), reconnect }),
|
|
228
259
|
);
|
|
229
260
|
|
|
230
|
-
render(
|
|
231
|
-
<WorkflowAgentExecutionDocument childExecutionId="aex_5" taskName="t" />,
|
|
232
|
-
);
|
|
261
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_5" />);
|
|
233
262
|
|
|
234
263
|
expect(screen.getByRole("alert").textContent).toContain(
|
|
235
264
|
"stream exhausted retries",
|
|
@@ -241,9 +270,7 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
241
270
|
it("renders an honest not-found notice when the execution no longer exists", () => {
|
|
242
271
|
mockUseLiveAgentExecution.mockReturnValue(hookState());
|
|
243
272
|
|
|
244
|
-
render(
|
|
245
|
-
<WorkflowAgentExecutionDocument childExecutionId="aex_6" taskName="t" />,
|
|
246
|
-
);
|
|
273
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_6" />);
|
|
247
274
|
|
|
248
275
|
expect(
|
|
249
276
|
screen.getByText("This agent execution is no longer available."),
|
|
@@ -261,9 +288,8 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
261
288
|
);
|
|
262
289
|
|
|
263
290
|
render(
|
|
264
|
-
<
|
|
291
|
+
<WorkflowAgentCallTranscript
|
|
265
292
|
childExecutionId="aex_7"
|
|
266
|
-
taskName="t"
|
|
267
293
|
onNavigateToAgentExecution={navigate}
|
|
268
294
|
/>,
|
|
269
295
|
);
|
|
@@ -272,7 +298,7 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
272
298
|
expect(navigate).toHaveBeenCalledWith("aex_7");
|
|
273
299
|
});
|
|
274
300
|
|
|
275
|
-
it("omits the
|
|
301
|
+
it("omits the chrome bar entirely when there is nothing to show", () => {
|
|
276
302
|
mockUseLiveAgentExecution.mockReturnValue(
|
|
277
303
|
hookState({
|
|
278
304
|
execution: executionFixture("aex_8", ExecutionPhase.EXECUTION_COMPLETED),
|
|
@@ -280,19 +306,62 @@ describe("WorkflowAgentExecutionDocument", () => {
|
|
|
280
306
|
}),
|
|
281
307
|
);
|
|
282
308
|
|
|
283
|
-
render(
|
|
284
|
-
<WorkflowAgentExecutionDocument childExecutionId="aex_8" taskName="t" />,
|
|
285
|
-
);
|
|
309
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_8" />);
|
|
286
310
|
|
|
287
311
|
expect(screen.queryByRole("button", { name: /Open standalone/ })).toBeNull();
|
|
312
|
+
expect(screen.queryByText("Reconnecting…")).toBeNull();
|
|
288
313
|
});
|
|
289
314
|
});
|
|
290
315
|
|
|
291
316
|
// ---------------------------------------------------------------------------
|
|
292
|
-
//
|
|
317
|
+
// Viewport-gated streaming (T07)
|
|
293
318
|
// ---------------------------------------------------------------------------
|
|
294
319
|
|
|
295
|
-
describe("
|
|
320
|
+
describe("WorkflowAgentCallTranscript — viewport gate", () => {
|
|
321
|
+
it("passes live: false while off-screen and live: true once visible", () => {
|
|
322
|
+
mockUseLiveAgentExecution.mockReturnValue(
|
|
323
|
+
hookState({
|
|
324
|
+
execution: executionFixture("aex_1", ExecutionPhase.EXECUTION_IN_PROGRESS),
|
|
325
|
+
phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
326
|
+
}),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_1" />);
|
|
330
|
+
|
|
331
|
+
// Before the observer's first callback the card is presumed off-screen:
|
|
332
|
+
// the snapshot fetch runs (the id is always passed) but live is false.
|
|
333
|
+
let lastCall = mockUseLiveAgentExecution.mock.calls.at(-1)!;
|
|
334
|
+
expect(lastCall[0]).toBe("aex_1");
|
|
335
|
+
expect(lastCall[1]).toEqual({ live: false });
|
|
336
|
+
|
|
337
|
+
// Scrolled into the (pre-warmed) viewport → the stream may open.
|
|
338
|
+
fireIO(true);
|
|
339
|
+
lastCall = mockUseLiveAgentExecution.mock.calls.at(-1)!;
|
|
340
|
+
expect(lastCall[1]).toEqual({ live: true });
|
|
341
|
+
|
|
342
|
+
// Scrolled away → the stream pauses (the hook keeps the last snapshot;
|
|
343
|
+
// its no-rewind behavior is unit-tested in useLiveAgentExecution).
|
|
344
|
+
fireIO(false);
|
|
345
|
+
lastCall = mockUseLiveAgentExecution.mock.calls.at(-1)!;
|
|
346
|
+
expect(lastCall[1]).toEqual({ live: false });
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("disconnects its observer on unmount", () => {
|
|
350
|
+
mockUseLiveAgentExecution.mockReturnValue(hookState());
|
|
351
|
+
|
|
352
|
+
const { unmount } = render(
|
|
353
|
+
<WorkflowAgentCallTranscript childExecutionId="aex_1" />,
|
|
354
|
+
);
|
|
355
|
+
unmount();
|
|
356
|
+
expect(ioDisconnect).toHaveBeenCalled();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
// In-place HITL (migrated from the S4 document, whose tab this replaces)
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
describe("WorkflowAgentCallTranscript — HITL wiring", () => {
|
|
296
365
|
it("threads the workflow approval handlers into the MessageThread", () => {
|
|
297
366
|
const hitl = hitlStub();
|
|
298
367
|
mockUseLiveAgentExecution.mockReturnValue(
|
|
@@ -303,17 +372,11 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
303
372
|
}),
|
|
304
373
|
);
|
|
305
374
|
|
|
306
|
-
render(
|
|
307
|
-
<WorkflowAgentExecutionDocument
|
|
308
|
-
childExecutionId="aex_1"
|
|
309
|
-
taskName="t"
|
|
310
|
-
hitl={hitl}
|
|
311
|
-
/>,
|
|
312
|
-
);
|
|
375
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_1" hitl={hitl} />);
|
|
313
376
|
|
|
314
377
|
// Identity, not equivalence: the thread must submit through the SAME
|
|
315
|
-
// workflow-level handler
|
|
316
|
-
// error state can never fork
|
|
378
|
+
// workflow-level handler every other surface uses, so in-flight and
|
|
379
|
+
// error state can never fork.
|
|
317
380
|
const threadProps = mockMessageThread.mock.calls[0][0];
|
|
318
381
|
expect(threadProps.onApprovalSubmit).toBe(hitl.submitApproval);
|
|
319
382
|
expect(threadProps.submittingApprovalIds).toBe(hitl.approvalSubmittingToolCallIds);
|
|
@@ -335,16 +398,8 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
335
398
|
}),
|
|
336
399
|
);
|
|
337
400
|
|
|
338
|
-
render(
|
|
339
|
-
<WorkflowAgentExecutionDocument
|
|
340
|
-
childExecutionId="aex_1"
|
|
341
|
-
taskName="t"
|
|
342
|
-
hitl={hitl}
|
|
343
|
-
/>,
|
|
344
|
-
);
|
|
401
|
+
render(<WorkflowAgentCallTranscript childExecutionId="aex_1" hitl={hitl} />);
|
|
345
402
|
|
|
346
|
-
// The real dock renders (region + accessible label), pinned in the
|
|
347
|
-
// document rather than in-thread.
|
|
348
403
|
expect(
|
|
349
404
|
screen.getByRole("region", { name: "File changes awaiting review" }),
|
|
350
405
|
).toBeTruthy();
|
|
@@ -375,11 +430,7 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
375
430
|
);
|
|
376
431
|
|
|
377
432
|
render(
|
|
378
|
-
<
|
|
379
|
-
childExecutionId="aex_1"
|
|
380
|
-
taskName="t"
|
|
381
|
-
hitl={hitlStub()}
|
|
382
|
-
/>,
|
|
433
|
+
<WorkflowAgentCallTranscript childExecutionId="aex_1" hitl={hitlStub()} />,
|
|
383
434
|
);
|
|
384
435
|
|
|
385
436
|
expect(
|
|
@@ -413,11 +464,7 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
413
464
|
);
|
|
414
465
|
|
|
415
466
|
render(
|
|
416
|
-
<
|
|
417
|
-
childExecutionId="aex_1"
|
|
418
|
-
taskName="t"
|
|
419
|
-
hitl={hitlStub()}
|
|
420
|
-
/>,
|
|
467
|
+
<WorkflowAgentCallTranscript childExecutionId="aex_1" hitl={hitlStub()} />,
|
|
421
468
|
);
|
|
422
469
|
|
|
423
470
|
expect(
|
|
@@ -435,18 +482,14 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
435
482
|
}),
|
|
436
483
|
);
|
|
437
484
|
|
|
438
|
-
const props = {
|
|
439
|
-
|
|
440
|
-
taskName: "t",
|
|
441
|
-
hitl,
|
|
442
|
-
} as const;
|
|
443
|
-
const { rerender } = render(<WorkflowAgentExecutionDocument {...props} />);
|
|
485
|
+
const props = { childExecutionId: "aex_1", hitl } as const;
|
|
486
|
+
const { rerender } = render(<WorkflowAgentCallTranscript {...props} />);
|
|
444
487
|
const rendersAfterMount = mockMessageThread.mock.calls.length;
|
|
445
488
|
|
|
446
|
-
// Same refs (the viewer's memoized bundle) → memo bails, no thread
|
|
447
|
-
// This is what keeps unrelated viewer churn
|
|
448
|
-
//
|
|
449
|
-
rerender(<
|
|
489
|
+
// Same refs (the viewer's memoized bundle) → memo bails, no thread
|
|
490
|
+
// render. This is what keeps unrelated viewer churn out of every
|
|
491
|
+
// mounted transcript (DD-010).
|
|
492
|
+
rerender(<WorkflowAgentCallTranscript {...props} />);
|
|
450
493
|
expect(mockMessageThread.mock.calls.length).toBe(rendersAfterMount);
|
|
451
494
|
|
|
452
495
|
// A gate going in-flight produces a NEW submitting set → the transcript
|
|
@@ -455,13 +498,13 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
455
498
|
...hitl,
|
|
456
499
|
approvalSubmittingToolCallIds: new Set(["tc-1"]),
|
|
457
500
|
};
|
|
458
|
-
rerender(<
|
|
501
|
+
rerender(<WorkflowAgentCallTranscript {...props} hitl={submitting} />);
|
|
459
502
|
expect(mockMessageThread.mock.calls.length).toBeGreaterThan(rendersAfterMount);
|
|
460
503
|
const latest = mockMessageThread.mock.calls.at(-1)![0];
|
|
461
504
|
expect(latest.submittingApprovalIds).toBe(submitting.approvalSubmittingToolCallIds);
|
|
462
505
|
});
|
|
463
506
|
|
|
464
|
-
it("keeps the
|
|
507
|
+
it("keeps the dock below the thread, outside the scroll container", () => {
|
|
465
508
|
mockUseLiveAgentExecution.mockReturnValue(
|
|
466
509
|
hookState({
|
|
467
510
|
execution: executionFixture(
|
|
@@ -475,16 +518,9 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
475
518
|
);
|
|
476
519
|
|
|
477
520
|
render(
|
|
478
|
-
<
|
|
479
|
-
childExecutionId="aex_1"
|
|
480
|
-
taskName="t"
|
|
481
|
-
hitl={hitlStub()}
|
|
482
|
-
/>,
|
|
521
|
+
<WorkflowAgentCallTranscript childExecutionId="aex_1" hitl={hitlStub()} />,
|
|
483
522
|
);
|
|
484
523
|
|
|
485
|
-
// The dock is a SIBLING after the thread's wrapper (the fixed strip at
|
|
486
|
-
// the document bottom), never inside the scroll container where it
|
|
487
|
-
// could scroll out of view.
|
|
488
524
|
const thread = screen.getByTestId("message-thread-probe");
|
|
489
525
|
const dock = document.querySelector('[data-cursor-target="file-review-dock"]')!;
|
|
490
526
|
expect(dock.contains(thread)).toBe(false);
|
|
@@ -495,18 +531,18 @@ describe("WorkflowAgentExecutionDocument — HITL wiring (S5)", () => {
|
|
|
495
531
|
});
|
|
496
532
|
|
|
497
533
|
// ---------------------------------------------------------------------------
|
|
498
|
-
//
|
|
534
|
+
// Routing guardrail: decisions go through the WORKFLOW RPCs (S5, preserved)
|
|
499
535
|
// ---------------------------------------------------------------------------
|
|
500
536
|
|
|
501
537
|
/**
|
|
502
|
-
* Integration-shaped guardrail: the
|
|
538
|
+
* Integration-shaped guardrail: the transcript driven by a REAL
|
|
503
539
|
* `useWorkflowExecutionActions` instance must reach the workflow-scoped RPCs
|
|
504
540
|
* (`workflowExecution.submitApproval` / `submitFileDecision`) and never the
|
|
505
541
|
* child's own `agentExecution.*` submit path. The two are server-equivalent
|
|
506
542
|
* but check different authorization (workflow vs. runner-spawned child) —
|
|
507
543
|
* a refactor that silently reintroduces the child path is a permission bug.
|
|
508
544
|
*/
|
|
509
|
-
describe("
|
|
545
|
+
describe("WorkflowAgentCallTranscript — workflow-RPC routing", () => {
|
|
510
546
|
const wfSubmitApproval = vi.fn();
|
|
511
547
|
const wfSubmitFileDecision = vi.fn();
|
|
512
548
|
const agentSubmitApproval = vi.fn();
|
|
@@ -525,15 +561,11 @@ describe("WorkflowAgentExecutionDocument — workflow-RPC routing", () => {
|
|
|
525
561
|
} as unknown as Stigmer;
|
|
526
562
|
}
|
|
527
563
|
|
|
528
|
-
/** Renders the
|
|
564
|
+
/** Renders the transcript exactly as the viewer wires it: hitl from the hook. */
|
|
529
565
|
function Harness({ children: _unused }: { children?: ReactNode }) {
|
|
530
566
|
const actions = useWorkflowExecutionActions("wex-1");
|
|
531
567
|
return (
|
|
532
|
-
<
|
|
533
|
-
childExecutionId="aex_1"
|
|
534
|
-
taskName="t"
|
|
535
|
-
hitl={actions}
|
|
536
|
-
/>
|
|
568
|
+
<WorkflowAgentCallTranscript childExecutionId="aex_1" hitl={actions} />
|
|
537
569
|
);
|
|
538
570
|
}
|
|
539
571
|
|
|
@@ -605,11 +637,6 @@ describe("WorkflowAgentExecutionDocument — workflow-RPC routing", () => {
|
|
|
605
637
|
});
|
|
606
638
|
|
|
607
639
|
it("delivers a failed submit to the thread as a keyed in-card error, and a retry clears it", async () => {
|
|
608
|
-
// The parent-surfacing race (or any server rejection) must degrade to a
|
|
609
|
-
// retryable in-card error: the keyed map records the failure, the
|
|
610
|
-
// document re-renders and delivers it to the thread (which renders it
|
|
611
|
-
// beside the gate — MessageThread's own tested behavior), and the
|
|
612
|
-
// handler resolves rather than throwing (no error boundary, no teardown).
|
|
613
640
|
mockUseLiveAgentExecution.mockReturnValue(
|
|
614
641
|
hookState({
|
|
615
642
|
execution: executionFixture("aex_1", ExecutionPhase.EXECUTION_IN_PROGRESS),
|
|
@@ -638,8 +665,6 @@ describe("WorkflowAgentExecutionDocument — workflow-RPC routing", () => {
|
|
|
638
665
|
);
|
|
639
666
|
expect(failedProps.submittingApprovalIds?.has("tc-1")).toBe(false);
|
|
640
667
|
|
|
641
|
-
// Retrying the same gate goes back through the workflow RPC and clears
|
|
642
|
-
// the keyed error on success.
|
|
643
668
|
await act(async () => {
|
|
644
669
|
await failedProps.onApprovalSubmit!("tc-1", ApprovalAction.APPROVE);
|
|
645
670
|
});
|