@stigmer/react 3.2.2 → 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/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 +5 -5
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- 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/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 +4 -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
|
@@ -253,4 +253,42 @@ describe("VisibilityBadge", () => {
|
|
|
253
253
|
render(<VisibilityBadge visibility={ApiResourceVisibility.visibility_platform} />);
|
|
254
254
|
expect(screen.getByText("Platform")).toBeTruthy();
|
|
255
255
|
});
|
|
256
|
+
|
|
257
|
+
it("is non-interactive without onClick — a plain badge, not a button", () => {
|
|
258
|
+
render(<VisibilityBadge visibility={ApiResourceVisibility.visibility_org} />);
|
|
259
|
+
expect(screen.getByText("Organization")).toBeTruthy();
|
|
260
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("renders as an accessible Manage-access button when onClick is provided", () => {
|
|
264
|
+
const onClick = vi.fn();
|
|
265
|
+
render(
|
|
266
|
+
<VisibilityBadge
|
|
267
|
+
visibility={ApiResourceVisibility.visibility_org}
|
|
268
|
+
onClick={onClick}
|
|
269
|
+
/>,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const button = screen.getByRole("button", {
|
|
273
|
+
name: /Organization visibility — manage access/i,
|
|
274
|
+
});
|
|
275
|
+
fireEvent.click(button);
|
|
276
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("is keyboard-activatable when clickable (native button semantics)", () => {
|
|
280
|
+
const onClick = vi.fn();
|
|
281
|
+
render(
|
|
282
|
+
<VisibilityBadge
|
|
283
|
+
visibility={ApiResourceVisibility.visibility_private}
|
|
284
|
+
onClick={onClick}
|
|
285
|
+
/>,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const button = screen.getByRole("button", { name: /manage access/i });
|
|
289
|
+
// A native <button type="button"> fires click on Enter/Space; assert the
|
|
290
|
+
// element is the real thing rather than a div with a handler.
|
|
291
|
+
expect(button.tagName).toBe("BUTTON");
|
|
292
|
+
expect(button.getAttribute("type")).toBe("button");
|
|
293
|
+
});
|
|
256
294
|
});
|
package/src/library/index.ts
CHANGED
|
@@ -76,7 +76,10 @@ export { ImportResourceDialog } from "./ImportResourceDialog.js";
|
|
|
76
76
|
export type { ImportResourceDialogProps } from "./ImportResourceDialog.js";
|
|
77
77
|
|
|
78
78
|
export { VisibilitySelector, VisibilityBadge } from "./VisibilitySelector.js";
|
|
79
|
-
export type {
|
|
79
|
+
export type {
|
|
80
|
+
VisibilitySelectorProps,
|
|
81
|
+
VisibilityBadgeProps,
|
|
82
|
+
} from "./VisibilitySelector.js";
|
|
80
83
|
|
|
81
84
|
export {
|
|
82
85
|
blueprintVisibilityLevels,
|
|
@@ -407,10 +407,14 @@ export function McpServerDetailView({
|
|
|
407
407
|
updatedAt: specAudit?.updatedAt ? timestampDate(specAudit.updatedAt) : null,
|
|
408
408
|
};
|
|
409
409
|
|
|
410
|
-
// Inline visibility is
|
|
411
|
-
//
|
|
410
|
+
// Inline visibility is at-a-glance AND a shortcut into the Manage access
|
|
411
|
+
// dialog — the single writer for both access axes. The chip is navigation
|
|
412
|
+
// only; it stays a static badge for users who cannot view access.
|
|
412
413
|
const visibilityControl = meta ? (
|
|
413
|
-
<VisibilityBadge
|
|
414
|
+
<VisibilityBadge
|
|
415
|
+
visibility={meta.visibility}
|
|
416
|
+
onClick={access.action ? access.open : undefined}
|
|
417
|
+
/>
|
|
414
418
|
) : undefined;
|
|
415
419
|
|
|
416
420
|
const mergedActions = access.action
|
|
@@ -817,7 +817,13 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
817
817
|
// a plan read-only in the panel remains — reading is the point.
|
|
818
818
|
onRetrySend={isObserver ? undefined : conv.retryLastSend}
|
|
819
819
|
onRetryExecution={isObserver ? undefined : onRetryExecution}
|
|
820
|
-
|
|
820
|
+
// Approval mechanics are an OPERATOR surface, never a guest's
|
|
821
|
+
// (DD-014): the HITL gate protects the org's tools, and an anonymous
|
|
822
|
+
// visitor is not its trustee. Guest executions run unattended
|
|
823
|
+
// (gated tools auto-skip server-side), so nothing is ever pending on
|
|
824
|
+
// a new execution — withholding the callback is the belt-and-braces
|
|
825
|
+
// for pre-existing sessions and embedders composing the SDK directly.
|
|
826
|
+
onApprovalSubmit={isObserver || isGuest ? undefined : flow.submitApproval}
|
|
821
827
|
submittingApprovalIds={conv.submittingApprovalIds}
|
|
822
828
|
approvalErrors={conv.approvalErrors}
|
|
823
829
|
showFileReviewRecords
|
|
@@ -35,8 +35,12 @@ vi.mock("../facets/SetupTab", () => ({
|
|
|
35
35
|
},
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
+
const threadProps: CapturedProps[] = [];
|
|
38
39
|
vi.mock("../../execution/MessageThread", () => ({
|
|
39
|
-
MessageThread: () =>
|
|
40
|
+
MessageThread: (props: CapturedProps) => {
|
|
41
|
+
threadProps.push(props);
|
|
42
|
+
return <div data-testid="thread-probe" />;
|
|
43
|
+
},
|
|
40
44
|
}));
|
|
41
45
|
|
|
42
46
|
// ---------------------------------------------------------------------------
|
|
@@ -93,6 +97,7 @@ const stubConv = {
|
|
|
93
97
|
isSending: false,
|
|
94
98
|
sendError: null,
|
|
95
99
|
clearSendError: vi.fn(),
|
|
100
|
+
retryLastSend: vi.fn(),
|
|
96
101
|
pendingUserMessage: null,
|
|
97
102
|
workspaceEntries: [],
|
|
98
103
|
mcpServerUsages: [],
|
|
@@ -159,6 +164,11 @@ function lastComposerProps(): CapturedProps {
|
|
|
159
164
|
return composerProps.at(-1)!;
|
|
160
165
|
}
|
|
161
166
|
|
|
167
|
+
function lastThreadProps(): CapturedProps {
|
|
168
|
+
expect(threadProps.length).toBeGreaterThan(0);
|
|
169
|
+
return threadProps.at(-1)!;
|
|
170
|
+
}
|
|
171
|
+
|
|
162
172
|
/**
|
|
163
173
|
* Reach the Config facet through the unified panel: expand it via the
|
|
164
174
|
* top-right chip, pick Config in the rail, and return the props the (mocked)
|
|
@@ -174,6 +184,7 @@ function openedConfigFacet(): { mutations?: unknown } {
|
|
|
174
184
|
beforeEach(() => {
|
|
175
185
|
composerProps.length = 0;
|
|
176
186
|
setupTabProps.length = 0;
|
|
187
|
+
threadProps.length = 0;
|
|
177
188
|
stubSessionPageFlow.submitError = null;
|
|
178
189
|
});
|
|
179
190
|
|
|
@@ -375,6 +386,25 @@ describe("SessionViewer — audience wiring", () => {
|
|
|
375
386
|
);
|
|
376
387
|
});
|
|
377
388
|
|
|
389
|
+
it("guest: approval mechanics are withheld from the thread (DD-014)", () => {
|
|
390
|
+
// The HITL gate protects the ORG's tools; an anonymous visitor is not
|
|
391
|
+
// its trustee. Guest executions run unattended server-side, so nothing
|
|
392
|
+
// is ever pending on a new execution — withholding the callback is the
|
|
393
|
+
// belt-and-braces for pre-existing sessions and direct SDK embedders.
|
|
394
|
+
render(<SessionViewer sessionId="ses_1" org="acme" audience="guest" />);
|
|
395
|
+
|
|
396
|
+
const props = lastThreadProps();
|
|
397
|
+
expect(props.onApprovalSubmit).toBeUndefined();
|
|
398
|
+
// Chat interaction survives — a guest owns their own conversation.
|
|
399
|
+
expect(props.onRetrySend).toBeDefined();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("integrator: approvals stay wired on the thread", () => {
|
|
403
|
+
render(<SessionViewer sessionId="ses_1" org="acme" />);
|
|
404
|
+
|
|
405
|
+
expect(lastThreadProps().onApprovalSubmit).toBeDefined();
|
|
406
|
+
});
|
|
407
|
+
|
|
378
408
|
it("forwards getRuntimeEnv to the flow", () => {
|
|
379
409
|
const getRuntimeEnv = vi.fn();
|
|
380
410
|
render(
|
package/src/session/audience.ts
CHANGED
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
* behavioral, not just cosmetic — the organisms also skip the
|
|
20
20
|
* org-level reads a guest principal cannot make (default-agent
|
|
21
21
|
* resolution, session→agent derivation, personal environments) and
|
|
22
|
-
* never fall back to the org's default agent.
|
|
22
|
+
* never fall back to the org's default agent. Approval mechanics are
|
|
23
|
+
* also withheld (DD-014): the HITL gate protects the ORG's tools and
|
|
24
|
+
* an anonymous visitor is not its trustee — guest executions run in
|
|
25
|
+
* unattended approval mode server-side (gated tools auto-skip and the
|
|
26
|
+
* agent explains in plain language), so tool-approval vocabulary never
|
|
27
|
+
* reaches a guest.
|
|
23
28
|
* - `"observer"` — a read-only transcript for someone watching a
|
|
24
29
|
* conversation they cannot participate in, e.g. a channel connector
|
|
25
30
|
* or org admin reviewing a Slack/WhatsApp conversation the channel
|
|
@@ -254,10 +254,14 @@ export function SkillDetailView({
|
|
|
254
254
|
statusLabel: status ? skillStateLabel(status.state) : undefined,
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
-
// Inline visibility is
|
|
258
|
-
//
|
|
257
|
+
// Inline visibility is at-a-glance AND a shortcut into the Manage access
|
|
258
|
+
// dialog — the single writer for both access axes. The chip is navigation
|
|
259
|
+
// only; it stays a static badge for users who cannot view access.
|
|
259
260
|
const visibilityControl = meta ? (
|
|
260
|
-
<VisibilityBadge
|
|
261
|
+
<VisibilityBadge
|
|
262
|
+
visibility={meta.visibility}
|
|
263
|
+
onClick={access.action ? access.open : undefined}
|
|
264
|
+
/>
|
|
261
265
|
) : undefined;
|
|
262
266
|
|
|
263
267
|
const mergedActions = access.action
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// The inline child-transcript body of an AGENT_CALL task card (T07): the
|
|
4
|
+
// card IS the session experience — live while running, full history when
|
|
5
|
+
// settled — replacing the S4 "Open transcript" document tab.
|
|
6
|
+
|
|
7
|
+
import { memo, useCallback, useMemo } from "react";
|
|
8
|
+
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
9
|
+
import {
|
|
10
|
+
FileChangeSetStatus,
|
|
11
|
+
type FileDecisionAction,
|
|
12
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
13
|
+
import type { FileChangeSet } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
14
|
+
import { displayFileChangeSets } from "@stigmer/sdk";
|
|
15
|
+
import { cn } from "@stigmer/theme";
|
|
16
|
+
import { useLiveAgentExecution } from "../execution/useLiveAgentExecution.js";
|
|
17
|
+
import { MessageThread } from "../execution/MessageThread.js";
|
|
18
|
+
import { ThreadSkeleton } from "../execution/ThreadSkeleton.js";
|
|
19
|
+
import { FileReviewDock } from "../execution/FileReviewDock.js";
|
|
20
|
+
import type { FileDecisionOptions } from "../execution/useFileReview.js";
|
|
21
|
+
import { isTerminalPhase } from "../execution/execution-phases.js";
|
|
22
|
+
import { useInViewport } from "../internal/useInViewport.js";
|
|
23
|
+
import type { UseWorkflowExecutionActionsReturn } from "./useWorkflowExecutionActions.js";
|
|
24
|
+
|
|
25
|
+
/** Stable empty list so the thread's memoized rows keep identity (DD-010). */
|
|
26
|
+
const EMPTY_EXECUTIONS: readonly AgentExecution[] = [];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The HITL wiring for an inline child transcript: the slice of
|
|
30
|
+
* {@link UseWorkflowExecutionActionsReturn} needed to submit child-gate
|
|
31
|
+
* decisions at the WORKFLOW level.
|
|
32
|
+
*
|
|
33
|
+
* A `Pick` (not a new shape) so the bundle can never drift from the actions
|
|
34
|
+
* hook — the viewer builds it from its single `useWorkflowExecutionActions`
|
|
35
|
+
* instance, so a gate's in-flight spinner or failure is identical wherever
|
|
36
|
+
* that gate is shown.
|
|
37
|
+
*/
|
|
38
|
+
export type WorkflowAgentExecutionHitl = Pick<
|
|
39
|
+
UseWorkflowExecutionActionsReturn,
|
|
40
|
+
| "submitApproval"
|
|
41
|
+
| "approvalSubmittingToolCallIds"
|
|
42
|
+
| "approvalErrorsByToolCallId"
|
|
43
|
+
| "submitFileDecision"
|
|
44
|
+
| "fileDecisionSubmittingKeys"
|
|
45
|
+
| "fileDecisionErrorsByKey"
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
/** Props for {@link WorkflowAgentCallTranscript}. */
|
|
49
|
+
export interface WorkflowAgentCallTranscriptProps {
|
|
50
|
+
/** ID of the child AgentExecution whose transcript to render. */
|
|
51
|
+
readonly childExecutionId: string;
|
|
52
|
+
/** Slug of the agent the task called, when known (labels the region). */
|
|
53
|
+
readonly agentSlug?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Workflow-level HITL handlers. When provided, the transcript is
|
|
56
|
+
* interactive: tool-approval gates render their decision cards inline on
|
|
57
|
+
* the gated rows, and pending file reviews dock at the bottom. When
|
|
58
|
+
* omitted, the transcript is read-only — gates show status only
|
|
59
|
+
* (backward compatible, DD-011).
|
|
60
|
+
*/
|
|
61
|
+
readonly hitl?: WorkflowAgentExecutionHitl;
|
|
62
|
+
/**
|
|
63
|
+
* Open the child execution as a standalone page — the deep-dive escape
|
|
64
|
+
* hatch now that the card is the transcript's single home. Host-routed
|
|
65
|
+
* (DD-004).
|
|
66
|
+
*/
|
|
67
|
+
readonly onNavigateToAgentExecution?: (agentExecutionId: string) => void;
|
|
68
|
+
/** Additional CSS classes for the root element. */
|
|
69
|
+
readonly className?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A child AgentExecution's full transcript rendered inline in the
|
|
74
|
+
* AGENT_CALL task card — the session-grade `MessageThread` (tool calls,
|
|
75
|
+
* sub-agents, plan cards, and — with {@link
|
|
76
|
+
* WorkflowAgentCallTranscriptProps.hitl | hitl} — in-place HITL), streaming
|
|
77
|
+
* live while the child runs and serving the full history once settled.
|
|
78
|
+
*
|
|
79
|
+
* BOUNDED, NOT FULL-HEIGHT. The transcript caps at a generous viewport
|
|
80
|
+
* fraction and owns its own scroll + auto-follow inside that box
|
|
81
|
+
* (`MessageThread`'s scroll machine is reused unchanged). Once a transcript
|
|
82
|
+
* hits its cap it stops growing, so the card height stabilizes and the
|
|
83
|
+
* OUTER thread's auto-follow stops chasing it — the bound fixes both the
|
|
84
|
+
* inner and the outer scroll ergonomics at once.
|
|
85
|
+
*
|
|
86
|
+
* VIEWPORT-GATED STREAMING. Every mounted card fetches its (DD-014-cached)
|
|
87
|
+
* snapshot so the body renders immediately, but the live subscription opens
|
|
88
|
+
* only while the card is on-screen (`useInViewport` → the hook's `live`
|
|
89
|
+
* gate). Off-screen running children pause their stream and keep the
|
|
90
|
+
* last-streamed snapshot visible (never a rewind); scrolling back — or
|
|
91
|
+
* toggling back from the CSS-hidden Graph view — re-attaches in place. This
|
|
92
|
+
* preserves the bounded-concurrency intent of the S4 single-active-tab
|
|
93
|
+
* design (DD-LIVE-006) for fan-out workflows: N concurrent children cost at
|
|
94
|
+
* most the streams the user can actually see.
|
|
95
|
+
*
|
|
96
|
+
* MINIMAL CHROME. The card header already owns the task's identity and
|
|
97
|
+
* metrics (the `agentCallLine` preview: slug, current tool, message/tool
|
|
98
|
+
* counts, plus duration/cost chips and the running/settled status glyph).
|
|
99
|
+
* The transcript adds only what the header cannot express: a
|
|
100
|
+
* `Reconnecting…` notice for the CHILD stream and the "Open standalone"
|
|
101
|
+
* pop-out. Body = purely the conversation.
|
|
102
|
+
*
|
|
103
|
+
* HITL ROUTING. Child gates are decided through the WORKFLOW-level RPCs
|
|
104
|
+
* (`WorkflowExecution.submitApproval` / `submitFileDecision`), never the
|
|
105
|
+
* child's own `agentExecution.*` submit path. The two paths are
|
|
106
|
+
* server-equivalent (the workflow RPC forwards to the child), but they
|
|
107
|
+
* differ in authorization: the workflow RPC checks `can_edit` on the
|
|
108
|
+
* workflow execution — the resource the operator owns — while the child
|
|
109
|
+
* path checks the runner-spawned AgentExecution, which the operator may
|
|
110
|
+
* not (the S5 rationale).
|
|
111
|
+
*
|
|
112
|
+
* FILE-REVIEW DOCK. Pending (AWAITING_REVIEW) sets derive from the CHILD's
|
|
113
|
+
* own live stream — the freshest source this component already holds — not
|
|
114
|
+
* from the parent's `status.pending_file_reviews`, which is fetched once
|
|
115
|
+
* and may lag a mid-run gate. Docked below the thread so a decision the
|
|
116
|
+
* child is blocked on can never scroll out of view; settled sets render as
|
|
117
|
+
* read-only records in-thread (`showFileReviewRecords`) — the two surfaces
|
|
118
|
+
* partition by the same pending test `MessageThread` uses.
|
|
119
|
+
*
|
|
120
|
+
* All visual properties flow through `--stgm-*` tokens (DD-005).
|
|
121
|
+
*/
|
|
122
|
+
export const WorkflowAgentCallTranscript = memo(
|
|
123
|
+
function WorkflowAgentCallTranscript({
|
|
124
|
+
childExecutionId,
|
|
125
|
+
agentSlug,
|
|
126
|
+
hitl,
|
|
127
|
+
onNavigateToAgentExecution,
|
|
128
|
+
className,
|
|
129
|
+
}: WorkflowAgentCallTranscriptProps) {
|
|
130
|
+
const { ref: viewportRef, isVisible } = useInViewport();
|
|
131
|
+
const {
|
|
132
|
+
execution,
|
|
133
|
+
phase,
|
|
134
|
+
isLoading,
|
|
135
|
+
isReconnecting,
|
|
136
|
+
error,
|
|
137
|
+
reconnect,
|
|
138
|
+
} = useLiveAgentExecution(childExecutionId, { live: isVisible });
|
|
139
|
+
|
|
140
|
+
// Pending file reviews from the child's own (streamed) status — see the
|
|
141
|
+
// component doc for why the parent's pending_file_reviews is not the
|
|
142
|
+
// source. Terminal executions never dock: their AWAITING_REVIEW sets
|
|
143
|
+
// are settled history and render as in-thread records instead.
|
|
144
|
+
const terminal = isTerminalPhase(phase);
|
|
145
|
+
const pendingReviewSets = useMemo<readonly FileChangeSet[]>(() => {
|
|
146
|
+
if (terminal) return [];
|
|
147
|
+
return displayFileChangeSets(execution?.status).filter(
|
|
148
|
+
(cs) =>
|
|
149
|
+
cs.status === FileChangeSetStatus.AWAITING_REVIEW &&
|
|
150
|
+
cs.changes.length > 0,
|
|
151
|
+
);
|
|
152
|
+
}, [execution?.status, terminal]);
|
|
153
|
+
|
|
154
|
+
// Bind the child id into the dock's card-level submit signature. Deps
|
|
155
|
+
// are the specific method (DD-010), so the callback survives unrelated
|
|
156
|
+
// in-flight churn on the actions instance.
|
|
157
|
+
const submitFileDecision = hitl?.submitFileDecision;
|
|
158
|
+
const handleFileDecision = useCallback(
|
|
159
|
+
(changeSetId: string, action: FileDecisionAction, options?: FileDecisionOptions) => {
|
|
160
|
+
submitFileDecision?.(childExecutionId, changeSetId, action, options);
|
|
161
|
+
},
|
|
162
|
+
[submitFileDecision, childExecutionId],
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const showBar = isReconnecting || !!onNavigateToAgentExecution;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div
|
|
169
|
+
ref={viewportRef}
|
|
170
|
+
role="group"
|
|
171
|
+
aria-label={
|
|
172
|
+
agentSlug ? `Transcript of agent ${agentSlug}` : "Agent transcript"
|
|
173
|
+
}
|
|
174
|
+
className={cn("flex max-h-[60vh] min-w-0 flex-col", className)}
|
|
175
|
+
>
|
|
176
|
+
{/* Only what the card header cannot express: the child stream's
|
|
177
|
+
reconnecting state and the standalone pop-out. */}
|
|
178
|
+
{showBar && (
|
|
179
|
+
<div className="flex min-w-0 shrink-0 items-center gap-3 pb-1.5">
|
|
180
|
+
{isReconnecting && (
|
|
181
|
+
<span className="text-xs text-muted-foreground">Reconnecting…</span>
|
|
182
|
+
)}
|
|
183
|
+
{onNavigateToAgentExecution && (
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
onClick={() => onNavigateToAgentExecution(childExecutionId)}
|
|
187
|
+
className="ml-auto inline-flex shrink-0 items-center gap-1.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:rounded-sm"
|
|
188
|
+
>
|
|
189
|
+
<PopOutIcon />
|
|
190
|
+
Open standalone
|
|
191
|
+
</button>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
{/* The thread owns the scrolling inside the bounded box (its
|
|
197
|
+
auto-scroll machine + jump-to-latest need the scroll container);
|
|
198
|
+
min-h-0 lets the flex slot shrink to the cap. */}
|
|
199
|
+
<div className="min-h-0 min-w-0 flex-1">
|
|
200
|
+
{execution ? (
|
|
201
|
+
<MessageThread
|
|
202
|
+
executions={EMPTY_EXECUTIONS}
|
|
203
|
+
activeStreamExecution={execution}
|
|
204
|
+
onApprovalSubmit={hitl?.submitApproval}
|
|
205
|
+
submittingApprovalIds={hitl?.approvalSubmittingToolCallIds}
|
|
206
|
+
approvalErrors={hitl?.approvalErrorsByToolCallId}
|
|
207
|
+
showFileReviewRecords={hitl != null}
|
|
208
|
+
className="h-full"
|
|
209
|
+
/>
|
|
210
|
+
) : isLoading ? (
|
|
211
|
+
<ThreadSkeleton className="py-2" />
|
|
212
|
+
) : error ? (
|
|
213
|
+
<div
|
|
214
|
+
role="alert"
|
|
215
|
+
className="flex flex-col items-start gap-2 py-2"
|
|
216
|
+
>
|
|
217
|
+
<p className="text-xs text-destructive">{error.message}</p>
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
onClick={reconnect}
|
|
221
|
+
className="rounded border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
222
|
+
>
|
|
223
|
+
Retry
|
|
224
|
+
</button>
|
|
225
|
+
</div>
|
|
226
|
+
) : (
|
|
227
|
+
<div role="status" className="flex flex-col gap-1 py-2">
|
|
228
|
+
<p className="text-xs font-medium text-foreground">
|
|
229
|
+
This agent execution is no longer available.
|
|
230
|
+
</p>
|
|
231
|
+
<p className="text-xs text-muted-foreground">
|
|
232
|
+
It may have been removed, or the workflow run predates
|
|
233
|
+
transcript retention.
|
|
234
|
+
</p>
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* Pending file reviews dock below the thread, inside the bounded
|
|
240
|
+
box — the decision the child is blocked on can never scroll out
|
|
241
|
+
of view. Same shape as the session's composer dock. */}
|
|
242
|
+
{hitl && pendingReviewSets.length > 0 && (
|
|
243
|
+
<FileReviewDock
|
|
244
|
+
changeSets={pendingReviewSets}
|
|
245
|
+
onSubmit={handleFileDecision}
|
|
246
|
+
submittingDecisionKeys={hitl.fileDecisionSubmittingKeys}
|
|
247
|
+
decisionErrors={hitl.fileDecisionErrorsByKey}
|
|
248
|
+
className="shrink-0"
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Inline SVG icons (SDK independence — no lucide dependency)
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
function PopOutIcon() {
|
|
261
|
+
return (
|
|
262
|
+
<svg
|
|
263
|
+
width="12"
|
|
264
|
+
height="12"
|
|
265
|
+
viewBox="0 0 12 12"
|
|
266
|
+
fill="none"
|
|
267
|
+
stroke="currentColor"
|
|
268
|
+
strokeWidth="1.3"
|
|
269
|
+
strokeLinecap="round"
|
|
270
|
+
strokeLinejoin="round"
|
|
271
|
+
className="shrink-0"
|
|
272
|
+
aria-hidden="true"
|
|
273
|
+
>
|
|
274
|
+
<path d="M10.5 4.5V1.5H7.5" />
|
|
275
|
+
<path d="M10.5 1.5L6 6" />
|
|
276
|
+
<path d="M8.5 7v3a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3" />
|
|
277
|
+
</svg>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
@@ -289,10 +289,14 @@ export function WorkflowDetailView({
|
|
|
289
289
|
<ValidationIndicator state={validationState} />
|
|
290
290
|
) : undefined;
|
|
291
291
|
|
|
292
|
-
// Inline visibility is
|
|
293
|
-
//
|
|
292
|
+
// Inline visibility is at-a-glance AND a shortcut into the Manage access
|
|
293
|
+
// dialog — the single writer for both access axes. The chip is navigation
|
|
294
|
+
// only; it stays a static badge for users who cannot view access.
|
|
294
295
|
const visibilityControl = meta ? (
|
|
295
|
-
<VisibilityBadge
|
|
296
|
+
<VisibilityBadge
|
|
297
|
+
visibility={meta.visibility}
|
|
298
|
+
onClick={access.action ? access.open : undefined}
|
|
299
|
+
/>
|
|
296
300
|
) : undefined;
|
|
297
301
|
|
|
298
302
|
const mergedActions = access.action
|