@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.
Files changed (121) hide show
  1. package/agent/AgentDetailView.d.ts.map +1 -1
  2. package/agent/AgentDetailView.js +6 -3
  3. package/agent/AgentDetailView.js.map +1 -1
  4. package/execution/index.d.ts +1 -2
  5. package/execution/index.d.ts.map +1 -1
  6. package/execution/index.js +0 -1
  7. package/execution/index.js.map +1 -1
  8. package/execution/useLiveAgentExecution.d.ts +17 -1
  9. package/execution/useLiveAgentExecution.d.ts.map +1 -1
  10. package/execution/useLiveAgentExecution.js +19 -4
  11. package/execution/useLiveAgentExecution.js.map +1 -1
  12. package/index.d.ts +5 -5
  13. package/index.d.ts.map +1 -1
  14. package/index.js +2 -2
  15. package/index.js.map +1 -1
  16. package/internal/useInViewport.d.ts +24 -0
  17. package/internal/useInViewport.d.ts.map +1 -0
  18. package/internal/useInViewport.js +49 -0
  19. package/internal/useInViewport.js.map +1 -0
  20. package/library/VisibilitySelector.d.ts +22 -6
  21. package/library/VisibilitySelector.d.ts.map +1 -1
  22. package/library/VisibilitySelector.js +12 -4
  23. package/library/VisibilitySelector.js.map +1 -1
  24. package/library/index.d.ts +1 -1
  25. package/library/index.d.ts.map +1 -1
  26. package/library/index.js.map +1 -1
  27. package/mcp-server/McpServerDetailView.d.ts.map +1 -1
  28. package/mcp-server/McpServerDetailView.js +4 -3
  29. package/mcp-server/McpServerDetailView.js.map +1 -1
  30. package/package.json +4 -4
  31. package/session/SessionViewer.js +8 -1
  32. package/session/SessionViewer.js.map +1 -1
  33. package/session/audience.d.ts +6 -1
  34. package/session/audience.d.ts.map +1 -1
  35. package/skill/SkillDetailView.d.ts.map +1 -1
  36. package/skill/SkillDetailView.js +4 -3
  37. package/skill/SkillDetailView.js.map +1 -1
  38. package/src/access/__tests__/useManageAccess.test.tsx +104 -0
  39. package/src/agent/AgentDetailView.tsx +9 -3
  40. package/src/execution/__tests__/useLiveAgentExecution.test.tsx +125 -0
  41. package/src/execution/index.ts +4 -7
  42. package/src/execution/useLiveAgentExecution.ts +41 -3
  43. package/src/index.ts +4 -5
  44. package/src/internal/__tests__/useInViewport.test.tsx +115 -0
  45. package/src/internal/useInViewport.ts +63 -0
  46. package/src/library/VisibilitySelector.tsx +50 -9
  47. package/src/library/__tests__/VisibilitySelector.test.tsx +38 -0
  48. package/src/library/index.ts +4 -1
  49. package/src/mcp-server/McpServerDetailView.tsx +7 -3
  50. package/src/session/SessionViewer.tsx +7 -1
  51. package/src/session/__tests__/audienceWiring.test.tsx +31 -1
  52. package/src/session/audience.ts +6 -1
  53. package/src/skill/SkillDetailView.tsx +7 -3
  54. package/src/workflow/WorkflowAgentCallTranscript.tsx +279 -0
  55. package/src/workflow/WorkflowDetailView.tsx +7 -3
  56. package/src/workflow/WorkflowExecutionViewer.tsx +20 -77
  57. package/src/workflow/WorkflowTaskApprovalSummary.tsx +45 -8
  58. package/src/workflow/__tests__/WorkflowAgentCallTranscript.liveregion.test.tsx +124 -0
  59. package/src/workflow/__tests__/{WorkflowAgentExecutionDocument.test.tsx → WorkflowAgentCallTranscript.test.tsx} +146 -121
  60. package/src/workflow/__tests__/WorkflowExecutionViewer.approvals.test.tsx +81 -220
  61. package/src/workflow/__tests__/WorkflowExecutionViewer.reconciliation.test.tsx +17 -16
  62. package/src/workflow/__tests__/WorkflowTaskApprovalSummary.test.tsx +52 -2
  63. package/src/workflow/__tests__/WorkflowTaskThread.test.tsx +168 -185
  64. package/src/workflow/__tests__/agent-call-live-experience.test.ts +4 -4
  65. package/src/workflow/__tests__/task-approval.test.ts +119 -0
  66. package/src/workflow/__tests__/task-presentation.test.ts +35 -1
  67. package/src/workflow/__tests__/useWorkflowExecutionPanel.test.ts +0 -57
  68. package/src/workflow/index.ts +6 -3
  69. package/src/workflow/task-detail/index.ts +3 -0
  70. package/src/workflow/task-detail/task-approval.ts +95 -1
  71. package/src/workflow/thread/WorkflowTaskThread.tsx +96 -252
  72. package/src/workflow/thread/task-presentation.ts +8 -2
  73. package/src/workflow/useWorkflowExecutionPanel.ts +0 -47
  74. package/workflow/WorkflowAgentCallTranscript.d.ts +87 -0
  75. package/workflow/WorkflowAgentCallTranscript.d.ts.map +1 -0
  76. package/workflow/WorkflowAgentCallTranscript.js +98 -0
  77. package/workflow/WorkflowAgentCallTranscript.js.map +1 -0
  78. package/workflow/WorkflowDetailView.d.ts.map +1 -1
  79. package/workflow/WorkflowDetailView.js +4 -3
  80. package/workflow/WorkflowDetailView.js.map +1 -1
  81. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  82. package/workflow/WorkflowExecutionViewer.js +23 -52
  83. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  84. package/workflow/WorkflowTaskApprovalSummary.d.ts +1 -1
  85. package/workflow/WorkflowTaskApprovalSummary.d.ts.map +1 -1
  86. package/workflow/WorkflowTaskApprovalSummary.js +18 -1
  87. package/workflow/WorkflowTaskApprovalSummary.js.map +1 -1
  88. package/workflow/index.d.ts +2 -2
  89. package/workflow/index.d.ts.map +1 -1
  90. package/workflow/index.js +2 -2
  91. package/workflow/index.js.map +1 -1
  92. package/workflow/task-detail/index.d.ts +1 -1
  93. package/workflow/task-detail/index.d.ts.map +1 -1
  94. package/workflow/task-detail/index.js +1 -1
  95. package/workflow/task-detail/index.js.map +1 -1
  96. package/workflow/task-detail/task-approval.d.ts +44 -1
  97. package/workflow/task-detail/task-approval.d.ts.map +1 -1
  98. package/workflow/task-detail/task-approval.js +47 -0
  99. package/workflow/task-detail/task-approval.js.map +1 -1
  100. package/workflow/thread/WorkflowTaskThread.d.ts +17 -25
  101. package/workflow/thread/WorkflowTaskThread.d.ts.map +1 -1
  102. package/workflow/thread/WorkflowTaskThread.js +60 -105
  103. package/workflow/thread/WorkflowTaskThread.js.map +1 -1
  104. package/workflow/thread/task-presentation.d.ts.map +1 -1
  105. package/workflow/thread/task-presentation.js +7 -2
  106. package/workflow/thread/task-presentation.js.map +1 -1
  107. package/workflow/useWorkflowExecutionPanel.d.ts +0 -12
  108. package/workflow/useWorkflowExecutionPanel.d.ts.map +1 -1
  109. package/workflow/useWorkflowExecutionPanel.js +0 -12
  110. package/workflow/useWorkflowExecutionPanel.js.map +1 -1
  111. package/execution/agent-execution-document.d.ts +0 -31
  112. package/execution/agent-execution-document.d.ts.map +0 -1
  113. package/execution/agent-execution-document.js +0 -48
  114. package/execution/agent-execution-document.js.map +0 -1
  115. package/src/execution/__tests__/agent-execution-document.test.ts +0 -45
  116. package/src/execution/agent-execution-document.ts +0 -54
  117. package/src/workflow/WorkflowAgentExecutionDocument.tsx +0 -283
  118. package/workflow/WorkflowAgentExecutionDocument.d.ts +0 -79
  119. package/workflow/WorkflowAgentExecutionDocument.d.ts.map +0 -1
  120. package/workflow/WorkflowAgentExecutionDocument.js +0 -87
  121. package/workflow/WorkflowAgentExecutionDocument.js.map +0 -1
@@ -2,11 +2,7 @@
2
2
 
3
3
  import { memo, useMemo, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
- import type {
6
- WorkflowPendingApproval,
7
- WorkflowPendingFileReview,
8
- WorkflowTask,
9
- } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
5
+ import type { WorkflowTask } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
10
6
  import type { DerivedTaskState } from "../../internal/store/workflow-execution-event-store.js";
11
7
  import { useAutoScroll } from "../../internal/useAutoScroll.js";
12
8
  import { JumpToLatestButton } from "../../internal/JumpToLatestButton.js";
@@ -24,8 +20,7 @@ import {
24
20
  } from "../../internal/thread-card/index.js";
25
21
  import { formatMetaChips } from "../format-utils.js";
26
22
  import type { UseWorkflowExecutionActionsReturn } from "../useWorkflowExecutionActions.js";
27
- import { WorkflowApprovalList } from "../WorkflowApprovalList.js";
28
- import { WorkflowFileReviewList } from "../WorkflowFileReviewList.js";
23
+ import { WorkflowAgentCallTranscript } from "../WorkflowAgentCallTranscript.js";
29
24
  import { WorkflowTaskReviewGate } from "../WorkflowTaskReviewGate.js";
30
25
  import { WorkflowTaskApprovalSummary } from "../WorkflowTaskApprovalSummary.js";
31
26
  import { buildIO, type TaskDetailIO } from "../task-detail/task-detail-io.js";
@@ -66,16 +61,6 @@ export type WorkflowThreadHitl = Pick<
66
61
  | "fileDecisionErrorsByKey"
67
62
  >;
68
63
 
69
- /**
70
- * The pending gates surfaced for one task card's child agent execution —
71
- * grouped once per thread render (memoized against the snapshot lists) so
72
- * non-gating cards receive `undefined` and their `React.memo` bails hold.
73
- */
74
- interface ThreadTaskGates {
75
- readonly approvals: readonly WorkflowPendingApproval[];
76
- readonly fileReviews: readonly WorkflowPendingFileReview[];
77
- }
78
-
79
64
  /** Props for {@link WorkflowTaskThread}. */
80
65
  export interface WorkflowTaskThreadProps {
81
66
  /** Live derived task states from the execution event stream. */
@@ -85,14 +70,11 @@ export interface WorkflowTaskThreadProps {
85
70
  /** Whether the execution is still running (drives streaming affordances). */
86
71
  readonly isRunning: boolean;
87
72
  /**
88
- * Callback to open an AGENT_CALL task's child transcript in the panel's
89
- * editor area (the S4 in-place expansion). Omitted → the affordance is
90
- * not rendered.
73
+ * Open an AGENT_CALL task's child execution as a standalone page — the
74
+ * inline transcript's deep-dive pop-out. Host-routed (DD-004). Omitted →
75
+ * the affordance is not rendered.
91
76
  */
92
- readonly onOpenAgentExecution?: (
93
- childExecutionId: string,
94
- taskName: string,
95
- ) => void;
77
+ readonly onNavigateToAgentExecution?: (agentExecutionId: string) => void;
96
78
  /**
97
79
  * Workflow-level HITL wiring (see {@link WorkflowThreadHitl}). When
98
80
  * provided, a gating task's card renders its decision surface directly in
@@ -101,16 +83,6 @@ export interface WorkflowTaskThreadProps {
101
83
  * and behaves exactly as before (DD-011; the S5 omitted-`hitl` precedent).
102
84
  */
103
85
  readonly hitl?: WorkflowThreadHitl;
104
- /**
105
- * The parent workflow's surfaced child tool-approval gates
106
- * (`status.pending_approvals`). Only read when {@link hitl} is provided.
107
- */
108
- readonly pendingApprovals?: readonly WorkflowPendingApproval[];
109
- /**
110
- * The parent workflow's surfaced child file-review references
111
- * (`status.pending_file_reviews`). Only read when {@link hitl} is provided.
112
- */
113
- readonly pendingFileReviews?: readonly WorkflowPendingFileReview[];
114
86
  /**
115
87
  * Per-task status snapshots (`status.tasks[]`) keyed by task name — the
116
88
  * FULL I/O source for card bodies (T04), never an event-log scan
@@ -132,22 +104,24 @@ export interface WorkflowTaskThreadProps {
132
104
  *
133
105
  * Pending tasks render no cards (D-T02-5); the progress header keeps
134
106
  * overall status visible. Retries collapse into one card with an attempt
135
- * indicator (D-T02-6). AGENT_CALL cards carry an always-visible body with
136
- * an "Open transcript" affordance (D-T02-2) — the full transcript opens as
137
- * a panel document, never inline. Since T04 the card is the PRIMARY surface
138
- * for a task's data (kind-aware preview line + bounded I/O body); since T06
139
- * it is the ONLY one the Inspect drill-down is gone, and cards compose
140
- * the session card language exactly (expand-or-none headers, no selection).
107
+ * indicator (D-T02-6). AGENT_CALL cards render the child's FULL transcript
108
+ * inline as the card body (T07) — live-streaming while the child runs,
109
+ * complete history once settled the session experience in place, with an
110
+ * "Open standalone" pop-out for a deep dive. Since T04 the card is the
111
+ * PRIMARY surface for a task's data (kind-aware preview line + bounded I/O
112
+ * body); since T06 it is the ONLY one the Inspect drill-down is gone, and
113
+ * cards compose the session card language exactly (expand-or-none headers,
114
+ * no selection).
141
115
  *
142
116
  * Auto-follow uses the session thread's mechanism: an IntersectionObserver
143
117
  * sentinel keeps the list pinned to the latest card until the user scrolls
144
118
  * up, with a jump-to-latest affordance to re-engage.
145
119
  *
146
120
  * With `hitl` wired, a gating task's card carries its decision surface
147
- * in-thread (S10/T06): the canonical `ApprovalCard`s for child tool gates,
148
- * the child-streaming `FileReviewCard`s for file gates, and the full
149
- * `WorkflowTaskReviewGate` (custom review renderers included) for
150
- * task-level human_input gates — with a read-only
121
+ * in-thread (S10/T06/T07): the child's own inline transcript renders the
122
+ * canonical `ApprovalCard`s and `FileReviewCard`s for child gates, and the
123
+ * full `WorkflowTaskReviewGate` (custom review renderers included) renders
124
+ * for task-level human_input gates — with a read-only
151
125
  * `WorkflowTaskApprovalSummary` once the gate resolves.
152
126
  *
153
127
  * This component is designed to work identically whether rendered in the
@@ -158,10 +132,8 @@ export const WorkflowTaskThread = memo(function WorkflowTaskThread({
158
132
  taskStates,
159
133
  totalTasks,
160
134
  isRunning,
161
- onOpenAgentExecution,
135
+ onNavigateToAgentExecution,
162
136
  hitl,
163
- pendingApprovals,
164
- pendingFileReviews,
165
137
  taskSnapshotsByName,
166
138
  className,
167
139
  }: WorkflowTaskThreadProps) {
@@ -169,34 +141,6 @@ export const WorkflowTaskThread = memo(function WorkflowTaskThread({
169
141
  const { scrollRef, sentinelRef, contentRef, isFollowing, jumpToLatest } =
170
142
  useAutoScroll();
171
143
 
172
- // Group the snapshot's gates by owning child ONCE per snapshot identity —
173
- // the lists change only on a snapshot refetch, never on stream event
174
- // appends, so during streaming every card's `gates` prop keeps a stable
175
- // reference (`undefined` for non-gating cards) and memoized rows bail
176
- // (DD-009/DD-010). Grouping is unconditional (it is cheap and usually
177
- // empty); rendering is gated on `hitl` at the card.
178
- const gatesByChild = useMemo((): ReadonlyMap<string, ThreadTaskGates> => {
179
- const map = new Map<
180
- string,
181
- { approvals: WorkflowPendingApproval[]; fileReviews: WorkflowPendingFileReview[] }
182
- >();
183
- const bucket = (childId: string) => {
184
- let entry = map.get(childId);
185
- if (!entry) {
186
- entry = { approvals: [], fileReviews: [] };
187
- map.set(childId, entry);
188
- }
189
- return entry;
190
- };
191
- for (const pa of pendingApprovals ?? []) {
192
- if (pa.childAgentExecutionId) bucket(pa.childAgentExecutionId).approvals.push(pa);
193
- }
194
- for (const ref of pendingFileReviews ?? []) {
195
- if (ref.childAgentExecutionId) bucket(ref.childAgentExecutionId).fileReviews.push(ref);
196
- }
197
- return map;
198
- }, [pendingApprovals, pendingFileReviews]);
199
-
200
144
  return (
201
145
  <div className={cn("relative flex h-full min-h-0 flex-col", className)}>
202
146
  <ThreadProgressHeader progress={progress} isRunning={isRunning} />
@@ -213,7 +157,7 @@ export const WorkflowTaskThread = memo(function WorkflowTaskThread({
213
157
  <ThreadTaskCard
214
158
  key={item.taskName}
215
159
  item={item}
216
- onOpenAgentExecution={onOpenAgentExecution}
160
+ onNavigateToAgentExecution={onNavigateToAgentExecution}
217
161
  // HITL props reach ONLY gating cards (DD-010): the bundle's
218
162
  // identity moves whenever any gate's in-flight/error state
219
163
  // flips (fresh Set/Map fields), so handing it to every card
@@ -221,11 +165,6 @@ export const WorkflowTaskThread = memo(function WorkflowTaskThread({
221
165
  // here, non-gating cards keep `undefined === undefined` and
222
166
  // their memo bails hold.
223
167
  hitl={item.status === "waiting_approval" ? hitl : undefined}
224
- gates={
225
- item.status === "waiting_approval" && item.childExecutionId
226
- ? gatesByChild.get(item.childExecutionId)
227
- : undefined
228
- }
229
168
  snapshot={taskSnapshotsByName?.get(item.taskName)}
230
169
  />
231
170
  ))
@@ -296,8 +235,8 @@ function ThreadEmptyState({ isRunning }: { readonly isRunning: boolean }) {
296
235
  /**
297
236
  * One task card. Memoized against the structurally-shared item (DD-010):
298
237
  * during streaming only the actively-changing task's item gets a fresh
299
- * identity, so settled cards bail here. `hitl`/`gates` arrive ONLY while
300
- * this card is gating (the thread scopes them — see the render site), so
238
+ * identity, so settled cards bail here. `hitl` arrives ONLY while this
239
+ * card is gating (the thread scopes it — see the render site), so
301
240
  * gate-state churn (a decision's in-flight Set flip re-materializes the
302
241
  * bundle) re-renders gating cards only, never the column. `snapshot`
303
242
  * changes identity only on a snapshot refetch, never on stream appends.
@@ -311,21 +250,22 @@ function ThreadEmptyState({ isRunning }: { readonly isRunning: boolean }) {
311
250
  * old "expand, then Show more" double control never comes back).
312
251
  * - `"summary"` kinds expand from the header — the session card's own
313
252
  * gesture, the chevron appended by the shell (T06).
253
+ * - AGENT_CALL cards with a spawned child render the child's inline
254
+ * transcript as the body (T07) — keyed on the VARIANT, not the
255
+ * disclosure, so a platform builder's presenter override can never route
256
+ * the flagship card away from its transcript. An agent_call that failed
257
+ * BEFORE spawning a child (agent resolution error) has no transcript and
258
+ * falls back to the generic preview body so its error still surfaces.
314
259
  */
315
260
  const ThreadTaskCard = memo(function ThreadTaskCard({
316
261
  item,
317
- onOpenAgentExecution,
262
+ onNavigateToAgentExecution,
318
263
  hitl,
319
- gates,
320
264
  snapshot,
321
265
  }: {
322
266
  readonly item: WorkflowThreadItem;
323
- readonly onOpenAgentExecution?: (
324
- childExecutionId: string,
325
- taskName: string,
326
- ) => void;
267
+ readonly onNavigateToAgentExecution?: (agentExecutionId: string) => void;
327
268
  readonly hitl?: WorkflowThreadHitl;
328
- readonly gates?: ThreadTaskGates;
329
269
  readonly snapshot?: WorkflowTask;
330
270
  }) {
331
271
  const [expanded, setExpanded] = useState(false);
@@ -361,30 +301,28 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
361
301
  );
362
302
 
363
303
  const isPreview = item.disclosure === "preview";
364
- const showHitl = !!hitl && item.status === "waiting_approval";
365
- // While gating, the in-card HITL section owns the card's affordances
366
- // (including "Open transcript" when the child's gates aren't surfaced) —
367
- // the preview body must not render a duplicate.
368
- const showBodyTranscript =
369
- item.variant === "agent-call" &&
370
- !!item.childExecutionId &&
371
- !!onOpenAgentExecution &&
372
- !showHitl;
304
+ // The flagship card (T07): an AGENT_CALL with a spawned child renders the
305
+ // child's transcript inline as its body the single home for everything
306
+ // the child did. Child gates decide INSIDE the transcript (the session's
307
+ // own ApprovalCard/FileReviewDock), so the generic HITL section below is
308
+ // for task-level (human_input) gates only.
309
+ const showTranscript =
310
+ item.variant === "agent-call" && item.childExecutionId !== "";
311
+ const showHitl =
312
+ !!hitl && item.status === "waiting_approval" && !showTranscript;
373
313
  // A resolved human_input gate presents its decision report in place of
374
314
  // the raw output Struct (the output IS the decision record — the report
375
315
  // is the readable rendering of it).
376
316
  const showApprovalSummary =
377
317
  !showHitl && approvalRequestView !== null && approvalDecision !== null;
378
318
  // The session's `showBody` gate: a preview body renders only when it has
379
- // content the header cannot carry (output, an error, the agent-call
380
- // transcript affordance, or a gate's decision report) — a content-less
381
- // preview card collapses back to a clean one-line row.
319
+ // content the header cannot carry (output, an error, or a gate's
320
+ // decision report) — a content-less preview card collapses back to a
321
+ // clean one-line row. Transcript cards have their own body.
382
322
  const showPreviewBody =
323
+ !showTranscript &&
383
324
  isPreview &&
384
- (outputIO !== null ||
385
- !!item.error ||
386
- showBodyTranscript ||
387
- showApprovalSummary);
325
+ (outputIO !== null || !!item.error || showApprovalSummary);
388
326
 
389
327
  const meta = formatMetaChips({
390
328
  durationMs: item.durationMs,
@@ -402,11 +340,12 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
402
340
  cursorTarget="workflow-task-row"
403
341
  >
404
342
  {/* The session card's gestures exactly (T06): summary rows expand
405
- from the header (chevron appended by the shell); preview rows'
406
- bodies are always visible, so the header is a plain layout row. */}
343
+ from the header (chevron appended by the shell); preview and
344
+ transcript rows' bodies are always visible, so the header is a
345
+ plain layout row. */}
407
346
  <ThreadCardHeader
408
347
  gesture={
409
- isPreview
348
+ isPreview || showTranscript
410
349
  ? { kind: "none" }
411
350
  : {
412
351
  kind: "expand",
@@ -444,23 +383,49 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
444
383
  )}
445
384
  </ThreadCardHeader>
446
385
 
447
- {/* In-thread HITL (S10): the decision surface renders whenever the
448
- task is gating — ALWAYS visible, never behind the expand chevron
449
- (the run is blocked; Nielsen #1). */}
386
+ {/* In-thread HITL (S10/T06): the task-level (human_input) decision
387
+ surface renders whenever the task is gating — ALWAYS visible,
388
+ never behind the expand chevron (the run is blocked; Nielsen #1).
389
+ Child gates are NOT rendered here — they decide inside the
390
+ transcript body below (T07). */}
450
391
  {showHitl && hitl && (
451
392
  <ThreadCardBody>
452
393
  <ThreadTaskCardHitl
453
394
  item={item}
454
- gates={gates}
455
395
  hitl={hitl}
456
396
  approvalRequestView={approvalRequestView}
457
- onOpenAgentExecution={onOpenAgentExecution}
458
397
  />
459
398
  </ThreadCardBody>
460
399
  )}
461
400
 
401
+ {/* The child's inline transcript — the agent-call card's body for
402
+ every state: streaming while the child runs, full history once
403
+ settled, the child's own gates decided in place (T07). The task's
404
+ own error (e.g. "child execution failed") renders above it. */}
405
+ {showTranscript && (
406
+ <ThreadCardBody cursorTarget="task-transcript">
407
+ <div className="flex flex-col gap-2">
408
+ {item.error && (
409
+ <BoundedContent>
410
+ <pre className="whitespace-pre-wrap break-words text-xs text-destructive">
411
+ {item.error}
412
+ </pre>
413
+ </BoundedContent>
414
+ )}
415
+ <WorkflowAgentCallTranscript
416
+ childExecutionId={item.childExecutionId}
417
+ agentSlug={item.agentSlug || undefined}
418
+ hitl={hitl}
419
+ onNavigateToAgentExecution={onNavigateToAgentExecution}
420
+ />
421
+ </div>
422
+ </ThreadCardBody>
423
+ )}
424
+
462
425
  {/* Always-visible bounded output body for I/O-bearing kinds (T04) —
463
- the session's preview-card model. */}
426
+ the session's preview-card model. Also the fallback for an
427
+ agent_call that failed before spawning a child (no transcript to
428
+ show; the error must still surface). */}
464
429
  {showPreviewBody && (
465
430
  <ThreadCardBody cursorTarget="task-preview">
466
431
  <ThreadTaskPreviewBody
@@ -474,21 +439,13 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
474
439
  }
475
440
  : undefined
476
441
  }
477
- onOpenAgentExecution={
478
- showBodyTranscript ? onOpenAgentExecution : undefined
479
- }
480
442
  />
481
443
  </ThreadCardBody>
482
444
  )}
483
445
 
484
- {!isPreview && expanded && (
446
+ {!isPreview && !showTranscript && expanded && (
485
447
  <ThreadCardBody>
486
- <ThreadTaskDetail
487
- item={item}
488
- inputIO={inputIO}
489
- outputIO={outputIO}
490
- onOpenAgentExecution={onOpenAgentExecution}
491
- />
448
+ <ThreadTaskDetail item={item} inputIO={inputIO} outputIO={outputIO} />
492
449
  </ThreadCardBody>
493
450
  )}
494
451
  </ThreadCardShell>
@@ -500,87 +457,27 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
500
457
  // ---------------------------------------------------------------------------
501
458
 
502
459
  /**
503
- * The decision surface on a gating card, resolved by gate kind:
460
+ * The decision surface on a gating card since T07 this is the task-level
461
+ * (human_input) gate only. Child gates (tool approvals, file reviews) are
462
+ * the inline transcript's job: an AGENT_CALL card's body renders the
463
+ * child's own `ApprovalCard`s and `FileReviewDock`, so this section never
464
+ * renders for transcript cards (see `showHitl` at the card).
504
465
  *
505
- * 1. Child gates surfaced on the parent snapshot → the shipped lists
506
- * (`WorkflowApprovalList` for tool approvals, `WorkflowFileReviewList`
507
- * for file reviews), filtered to this card's child. Decisions route
508
- * through the workflow-level RPCs on the supplied {@link
509
- * WorkflowThreadHitl} bundle.
510
- * 2. An AGENT_CALL card with NO surfaced gates → the child is gated but the
511
- * snapshot has no entries: on cloud a brief refetch window; on OSS the
512
- * steady state until the T04 forwarder lands. The honest surface is the
513
- * child's own transcript (the S5 in-place expansion renders the child's
514
- * ApprovalCards) — offer it, never a dead-end spinner.
515
- * 3. Any other gating card → a task-level (human_input) gate. The full
516
- * review surface — outcomes, forms, artifact-backed payloads, custom
517
- * review renderers — renders right here (T06): the card is the only
518
- * decision surface, so it carries the real `WorkflowTaskReviewGate`,
519
- * not a link to one. Degrades to an honest waiting notice when the
520
- * request payload is unavailable (no event stream — the snapshot
521
- * fallback path).
466
+ * The full review surface outcomes, forms, artifact-backed payloads,
467
+ * custom review renderers — renders right here (T06): the card is the only
468
+ * decision surface, so it carries the real `WorkflowTaskReviewGate`, not a
469
+ * link to one. Degrades to an honest waiting notice when the request
470
+ * payload is unavailable (no event stream — the snapshot fallback path).
522
471
  */
523
472
  function ThreadTaskCardHitl({
524
473
  item,
525
- gates,
526
474
  hitl,
527
475
  approvalRequestView,
528
- onOpenAgentExecution,
529
476
  }: {
530
477
  readonly item: WorkflowThreadItem;
531
- readonly gates?: ThreadTaskGates;
532
478
  readonly hitl: WorkflowThreadHitl;
533
479
  readonly approvalRequestView: TaskApprovalRequestView | null;
534
- readonly onOpenAgentExecution?: (
535
- childExecutionId: string,
536
- taskName: string,
537
- ) => void;
538
480
  }) {
539
- const hasChildGates =
540
- !!gates && (gates.approvals.length > 0 || gates.fileReviews.length > 0);
541
-
542
- if (hasChildGates) {
543
- return (
544
- <div className="flex flex-col gap-3">
545
- {gates.approvals.length > 0 && (
546
- <WorkflowApprovalList
547
- pendingApprovals={gates.approvals}
548
- onSubmitApproval={hitl.submitApproval}
549
- submittingToolCallIds={hitl.approvalSubmittingToolCallIds}
550
- approvalErrors={hitl.approvalErrorsByToolCallId}
551
- />
552
- )}
553
- {gates.fileReviews.length > 0 && (
554
- <WorkflowFileReviewList
555
- pendingFileReviews={gates.fileReviews}
556
- onSubmitFileDecision={hitl.submitFileDecision}
557
- submittingDecisionKeys={hitl.fileDecisionSubmittingKeys}
558
- decisionErrors={hitl.fileDecisionErrorsByKey}
559
- />
560
- )}
561
- </div>
562
- );
563
- }
564
-
565
- if (item.variant === "agent-call") {
566
- return (
567
- <div className="flex flex-wrap items-center gap-2">
568
- <p className="text-xs text-muted-foreground">
569
- The called agent is waiting for an approval. Decide it in the
570
- agent&apos;s transcript.
571
- </p>
572
- {item.childExecutionId && onOpenAgentExecution && (
573
- <ThreadActionButton
574
- label="Open transcript"
575
- onClick={() =>
576
- onOpenAgentExecution(item.childExecutionId, item.taskName)
577
- }
578
- />
579
- )}
580
- </div>
581
- );
582
- }
583
-
584
481
  if (approvalRequestView) {
585
482
  return (
586
483
  <WorkflowTaskReviewGate
@@ -607,24 +504,6 @@ function ThreadTaskCardHitl({
607
504
  );
608
505
  }
609
506
 
610
- function ThreadActionButton({
611
- label,
612
- onClick,
613
- }: {
614
- readonly label: string;
615
- readonly onClick: () => void;
616
- }) {
617
- return (
618
- <button
619
- type="button"
620
- onClick={onClick}
621
- className="shrink-0 rounded border border-border px-2 py-1 text-xs font-medium text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
622
- >
623
- {label}
624
- </button>
625
- );
626
- }
627
-
628
507
  // ---------------------------------------------------------------------------
629
508
  // Card bodies (T04)
630
509
  // ---------------------------------------------------------------------------
@@ -663,9 +542,10 @@ function ThreadTaskIOSection({
663
542
 
664
543
  /**
665
544
  * The always-visible body of a `"preview"`-kind card: the task's output
666
- * (via the I/O fallback ladder), the failure detail when the task failed,
667
- * and the agent-call transcript affordance. Content only — the header owns
668
- * all metadata (session `ToolCallDetail` invariant).
545
+ * (via the I/O fallback ladder) and the failure detail when the task
546
+ * failed. Content only — the header owns all metadata (session
547
+ * `ToolCallDetail` invariant). AGENT_CALL cards with a spawned child never
548
+ * reach this body — theirs is the inline transcript (T07).
669
549
  *
670
550
  * A resolved human_input gate supplies `approvalSummary` and gets the
671
551
  * read-only decision report instead of the raw output Struct — the output
@@ -675,7 +555,6 @@ function ThreadTaskPreviewBody({
675
555
  item,
676
556
  outputIO,
677
557
  approvalSummary,
678
- onOpenAgentExecution,
679
558
  }: {
680
559
  readonly item: WorkflowThreadItem;
681
560
  readonly outputIO: TaskDetailIO | null;
@@ -683,10 +562,6 @@ function ThreadTaskPreviewBody({
683
562
  readonly request: TaskApprovalRequestView;
684
563
  readonly decision: TaskDetailApprovalDecision | null;
685
564
  };
686
- readonly onOpenAgentExecution?: (
687
- childExecutionId: string,
688
- taskName: string,
689
- ) => void;
690
565
  }) {
691
566
  return (
692
567
  <div className="flex flex-col gap-2">
@@ -708,19 +583,6 @@ function ThreadTaskPreviewBody({
708
583
  ) : (
709
584
  outputIO && <ThreadTaskIOSection label="Output" io={outputIO} />
710
585
  )}
711
-
712
- {item.variant === "agent-call" &&
713
- item.childExecutionId &&
714
- onOpenAgentExecution && (
715
- <div>
716
- <ThreadActionButton
717
- label="Open transcript"
718
- onClick={() =>
719
- onOpenAgentExecution(item.childExecutionId, item.taskName)
720
- }
721
- />
722
- </div>
723
- )}
724
586
  </div>
725
587
  );
726
588
  }
@@ -729,15 +591,10 @@ function ThreadTaskDetail({
729
591
  item,
730
592
  inputIO,
731
593
  outputIO,
732
- onOpenAgentExecution,
733
594
  }: {
734
595
  readonly item: WorkflowThreadItem;
735
596
  readonly inputIO: TaskDetailIO | null;
736
597
  readonly outputIO: TaskDetailIO | null;
737
- readonly onOpenAgentExecution?: (
738
- childExecutionId: string,
739
- taskName: string,
740
- ) => void;
741
598
  }) {
742
599
  const rows: Array<[string, string]> = [["Status", statusLabel(item.status)]];
743
600
  if (item.durationMs > 0) {
@@ -776,19 +633,6 @@ function ThreadTaskDetail({
776
633
  the card; Inspect is the opt-in debug drill-down. */}
777
634
  {inputIO && <ThreadTaskIOSection label="Input" io={inputIO} />}
778
635
  {outputIO && <ThreadTaskIOSection label="Output" io={outputIO} />}
779
-
780
- {item.variant === "agent-call" &&
781
- item.childExecutionId &&
782
- onOpenAgentExecution && (
783
- <div>
784
- <ThreadActionButton
785
- label="Open transcript"
786
- onClick={() =>
787
- onOpenAgentExecution(item.childExecutionId, item.taskName)
788
- }
789
- />
790
- </div>
791
- )}
792
636
  </div>
793
637
  );
794
638
  }
@@ -317,13 +317,19 @@ function notificationLine(state: DerivedTaskState): string | null {
317
317
  return channel;
318
318
  }
319
319
 
320
- /** `approve · by suresh` — the settled decision from the task output. */
320
+ /** `approve · by Ada Lovelace` — the settled decision from the task output. */
321
321
  function humanInputLine(output: JsonObject | null): string | null {
322
322
  // The gating state is covered by the universal "Awaiting approval" line;
323
323
  // this renders only the settled decision record.
324
324
  const outcome = asString(output?.["outcome"]);
325
325
  if (!outcome) return null;
326
- const reviewer = asString(output?.["reviewer"]);
326
+ // Fallback ladder over the reviewer_actor display snapshot: name → email
327
+ // → raw canonical id (legacy records only) → omit the "by" segment.
328
+ const actor = asObject(output?.["reviewer_actor"]);
329
+ const reviewer =
330
+ asString(actor?.["display_name"]) ??
331
+ asString(actor?.["email"]) ??
332
+ asString(output?.["reviewer"]);
327
333
  return reviewer ? `${outcome} · by ${reviewer}` : outcome;
328
334
  }
329
335
 
@@ -16,10 +16,6 @@ import {
16
16
  FILE_CHANGE_DOCUMENT_ENTRY_ID,
17
17
  fileChangeTabPath,
18
18
  } from "../execution/file-change-document.js";
19
- import {
20
- AGENT_EXECUTION_DOCUMENT_ENTRY_ID,
21
- agentExecutionTabPath,
22
- } from "../execution/agent-execution-document.js";
23
19
  import {
24
20
  DIAGNOSIS_DOCUMENT_ENTRY_ID,
25
21
  DIAGNOSIS_DOCUMENT_PATH,
@@ -112,24 +108,6 @@ export interface WorkflowExecutionPanelController {
112
108
  * open/activate split. A no-op if the change is not open.
113
109
  */
114
110
  readonly pinFileChange: (change: FileChange) => void;
115
- /**
116
- * Open (or focus) a child AgentExecution's transcript as an editor-pane
117
- * document tab and expand the panel — the S4 in-place expansion. Same
118
- * preview-slot semantics as {@link openArtifact}: casually stepping
119
- * through agent calls reuses one tab; pinning keeps one around.
120
- */
121
- readonly openAgentExecution: (
122
- childExecutionId: string,
123
- taskName: string,
124
- ) => void;
125
- /**
126
- * Pin a child AgentExecution's transcript tab — the double-click half of
127
- * the open/activate split. A no-op if the transcript is not open.
128
- */
129
- readonly pinAgentExecution: (
130
- childExecutionId: string,
131
- taskName: string,
132
- ) => void;
133
111
  }
134
112
 
135
113
  /** Options for {@link useWorkflowExecutionPanel}. */
@@ -213,27 +191,6 @@ export function useWorkflowExecutionPanel({
213
191
  [editorsStore],
214
192
  );
215
193
 
216
- const openAgentExecution = useCallback(
217
- (childExecutionId: string, taskName: string) => {
218
- editorsStore.openPreview(
219
- AGENT_EXECUTION_DOCUMENT_ENTRY_ID,
220
- agentExecutionTabPath(childExecutionId, taskName),
221
- );
222
- setIsOpen(true);
223
- },
224
- [editorsStore],
225
- );
226
-
227
- const pinAgentExecution = useCallback(
228
- (childExecutionId: string, taskName: string) => {
229
- editorsStore.pin(
230
- AGENT_EXECUTION_DOCUMENT_ENTRY_ID,
231
- agentExecutionTabPath(childExecutionId, taskName),
232
- );
233
- },
234
- [editorsStore],
235
- );
236
-
237
194
  const activateEditor = useCallback(
238
195
  (entryId: string, path: string) => editorsStore.activate(entryId, path),
239
196
  [editorsStore],
@@ -266,8 +223,6 @@ export function useWorkflowExecutionPanel({
266
223
  pinArtifact,
267
224
  openFileChange,
268
225
  pinFileChange,
269
- openAgentExecution,
270
- pinAgentExecution,
271
226
  }),
272
227
  [
273
228
  editorsStore,
@@ -285,8 +240,6 @@ export function useWorkflowExecutionPanel({
285
240
  pinArtifact,
286
241
  openFileChange,
287
242
  pinFileChange,
288
- openAgentExecution,
289
- pinAgentExecution,
290
243
  ],
291
244
  );
292
245
  }