@stigmer/react 3.12.7 → 3.12.9
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/README.md +1 -3
- package/composer/ComposerToolbar.d.ts +1 -19
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -7
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +0 -10
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +2 -2
- package/composer/SessionComposer.js.map +1 -1
- package/composer/index.d.ts +0 -1
- package/composer/index.d.ts.map +1 -1
- package/composer/index.js.map +1 -1
- package/conversation/ConversationTimelineView.d.ts +15 -1
- package/conversation/ConversationTimelineView.d.ts.map +1 -1
- package/conversation/ConversationTimelineView.js +3 -2
- package/conversation/ConversationTimelineView.js.map +1 -1
- package/conversation/ConversationsWorkbench.d.ts +14 -1
- package/conversation/ConversationsWorkbench.d.ts.map +1 -1
- package/conversation/ConversationsWorkbench.js +11 -2
- package/conversation/ConversationsWorkbench.js.map +1 -1
- package/execution/MessageThread.d.ts +33 -2
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +44 -6
- package/execution/MessageThread.js.map +1 -1
- package/execution/RecalledMemoriesCard.d.ts +47 -0
- package/execution/RecalledMemoriesCard.d.ts.map +1 -0
- package/execution/RecalledMemoriesCard.js +58 -0
- package/execution/RecalledMemoriesCard.js.map +1 -0
- package/execution/ResultView.d.ts.map +1 -1
- package/execution/ResultView.js +6 -0
- package/execution/ResultView.js.map +1 -1
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +13 -2
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/index.d.ts +2 -0
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -0
- package/execution/index.js.map +1 -1
- package/execution/tool-categories.d.ts +1 -1
- package/execution/tool-categories.d.ts.map +1 -1
- package/execution/tool-categories.js +6 -0
- package/execution/tool-categories.js.map +1 -1
- package/identity-account/AccountPreferencesPanel.d.ts +2 -1
- package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
- package/identity-account/AccountPreferencesPanel.js +41 -3
- package/identity-account/AccountPreferencesPanel.js.map +1 -1
- package/identity-account/useAccountExecutionDefaults.d.ts +7 -0
- package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -1
- package/identity-account/useAccountExecutionDefaults.js +5 -2
- package/identity-account/useAccountExecutionDefaults.js.map +1 -1
- package/index.d.ts +6 -6
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/internal/MemoryEnabledRow.d.ts +37 -0
- package/internal/MemoryEnabledRow.d.ts.map +1 -0
- package/internal/MemoryEnabledRow.js +25 -0
- package/internal/MemoryEnabledRow.js.map +1 -0
- package/internal/VirtualizedThread.d.ts +3 -1
- package/internal/VirtualizedThread.d.ts.map +1 -1
- package/internal/VirtualizedThread.js +3 -1
- package/internal/VirtualizedThread.js.map +1 -1
- package/internal/useAutoScroll.d.ts +18 -0
- package/internal/useAutoScroll.d.ts.map +1 -1
- package/internal/useAutoScroll.js +50 -0
- package/internal/useAutoScroll.js.map +1 -1
- package/memory/MemoryProposalCard.d.ts +35 -0
- package/memory/MemoryProposalCard.d.ts.map +1 -0
- package/memory/MemoryProposalCard.js +73 -0
- package/memory/MemoryProposalCard.js.map +1 -0
- package/memory/index.d.ts +2 -0
- package/memory/index.d.ts.map +1 -1
- package/memory/index.js +2 -0
- package/memory/index.js.map +1 -1
- package/memory/useMemory.d.ts +39 -0
- package/memory/useMemory.d.ts.map +1 -0
- package/memory/useMemory.js +43 -0
- package/memory/useMemory.js.map +1 -0
- package/organization/OrgPreferencesPanel.d.ts.map +1 -1
- package/organization/OrgPreferencesPanel.js +36 -4
- package/organization/OrgPreferencesPanel.js.map +1 -1
- package/package.json +4 -4
- package/session/AutoApproveIndicator.d.ts +18 -0
- package/session/AutoApproveIndicator.d.ts.map +1 -0
- package/session/AutoApproveIndicator.js +23 -0
- package/session/AutoApproveIndicator.js.map +1 -0
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +10 -9
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +21 -9
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +31 -21
- package/session/SessionViewer.js.map +1 -1
- package/session/facets/SetupTab.d.ts +43 -6
- package/session/facets/SetupTab.d.ts.map +1 -1
- package/session/facets/SetupTab.js +32 -36
- package/session/facets/SetupTab.js.map +1 -1
- package/session/facets/UsageTab.d.ts +2 -1
- package/session/facets/UsageTab.d.ts.map +1 -1
- package/session/facets/UsageTab.js +11 -5
- package/session/facets/UsageTab.js.map +1 -1
- package/session/facets/index.d.ts +1 -1
- package/session/facets/index.d.ts.map +1 -1
- package/session/facets/primitives.d.ts +58 -0
- package/session/facets/primitives.d.ts.map +1 -0
- package/session/facets/primitives.js +59 -0
- package/session/facets/primitives.js.map +1 -0
- package/session/index.d.ts +1 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +9 -4
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +20 -9
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +28 -10
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +16 -10
- package/session/useSessionPageFlow.js.map +1 -1
- package/settings/MemorySection.d.ts.map +1 -1
- package/settings/MemorySection.js +1 -1
- package/settings/MemorySection.js.map +1 -1
- package/src/attachment/__tests__/prepare-image.test.ts +12 -6
- package/src/composer/ComposerToolbar.tsx +0 -57
- package/src/composer/SessionComposer.tsx +1 -13
- package/src/composer/index.ts +0 -1
- package/src/conversation/ConversationTimelineView.tsx +17 -1
- package/src/conversation/ConversationsWorkbench.tsx +25 -0
- package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +37 -1
- package/src/execution/MessageThread.tsx +75 -3
- package/src/execution/RecalledMemoriesCard.tsx +164 -0
- package/src/execution/ResultView.tsx +6 -0
- package/src/execution/ToolCallItem.tsx +18 -1
- package/src/execution/__tests__/RecalledMemoriesCard.test.tsx +123 -0
- package/src/execution/__tests__/message-thread-recalled-memories.test.tsx +216 -0
- package/src/execution/__tests__/message-thread-scroll-on-send.test.tsx +141 -0
- package/src/execution/__tests__/message-thread-slots.test.tsx +36 -0
- package/src/execution/index.ts +2 -0
- package/src/execution/tool-categories.ts +7 -0
- package/src/identity-account/AccountPreferencesPanel.tsx +89 -2
- package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +112 -0
- package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +34 -1
- package/src/identity-account/useAccountExecutionDefaults.ts +12 -2
- package/src/index.ts +8 -1
- package/src/internal/MemoryEnabledRow.tsx +92 -0
- package/src/internal/VirtualizedThread.tsx +5 -0
- package/src/internal/__tests__/useAutoScroll.layout.test.tsx +105 -4
- package/src/internal/__tests__/useAutoScroll.test.tsx +41 -1
- package/src/internal/useAutoScroll.ts +54 -0
- package/src/memory/MemoryProposalCard.tsx +181 -0
- package/src/memory/__tests__/MemoryProposalCard.test.tsx +236 -0
- package/src/memory/__tests__/memoryHooks.test.tsx +63 -0
- package/src/memory/index.ts +5 -0
- package/src/memory/useMemory.ts +80 -0
- package/src/organization/OrgPreferencesPanel.tsx +52 -3
- package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +65 -0
- package/src/session/AutoApproveIndicator.tsx +45 -0
- package/src/session/NewSessionViewer.tsx +18 -15
- package/src/session/SessionViewer.tsx +70 -32
- package/src/session/__tests__/NewSessionViewer.test.tsx +32 -0
- package/src/session/__tests__/SessionViewer-autoApprove.test.tsx +244 -0
- package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +60 -22
- package/src/session/__tests__/useNewSessionFlow.test.tsx +56 -0
- package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +87 -0
- package/src/session/facets/SetupTab.tsx +193 -157
- package/src/session/facets/UsageTab.tsx +38 -38
- package/src/session/facets/index.ts +1 -0
- package/src/session/facets/primitives.tsx +169 -0
- package/src/session/index.ts +1 -0
- package/src/session/useNewSessionFlow.ts +30 -15
- package/src/session/useSessionPageFlow.ts +44 -20
- package/src/settings/MemorySection.tsx +4 -2
- package/src/switch/Switch.tsx +4 -0
- package/src/workflow/__tests__/WorkflowExecutionViewer.approvals.test.tsx +26 -7
- package/src/workflow/__tests__/task-presentation.test.ts +49 -12
- package/src/workflow/__tests__/useWorkflowCanvas.dirty.test.tsx +7 -2
- package/src/workflow/__tests__/useWorkflowCanvas.drag.test.tsx +7 -2
- package/src/workflow/__tests__/workflow-thread-scroll-on-send.test.tsx +145 -0
- package/src/workflow/thread/WorkflowTaskThread.tsx +59 -32
- package/src/workflow/thread/task-presentation.ts +52 -7
- package/src/workspace/WorkspaceSurface.tsx +26 -3
- package/styles.css +1 -1
- package/switch/Switch.d.ts +3 -1
- package/switch/Switch.d.ts.map +1 -1
- package/switch/Switch.js +2 -2
- package/switch/Switch.js.map +1 -1
- package/workflow/thread/WorkflowTaskThread.d.ts +13 -0
- package/workflow/thread/WorkflowTaskThread.d.ts.map +1 -1
- package/workflow/thread/WorkflowTaskThread.js +33 -25
- package/workflow/thread/WorkflowTaskThread.js.map +1 -1
- package/workflow/thread/task-presentation.d.ts.map +1 -1
- package/workflow/thread/task-presentation.js +50 -7
- package/workflow/thread/task-presentation.js.map +1 -1
- package/workspace/WorkspaceSurface.js +11 -2
- package/workspace/WorkspaceSurface.js.map +1 -1
- package/src/composer/__tests__/ComposerToolbar-autoApprove.test.tsx +0 -80
- package/src/session/__tests__/SessionViewer-autoApproveToggle.test.tsx +0 -178
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Scroll-on-send wiring pins for WorkflowTaskThread (stigmer-cloud#267):
|
|
2
|
+
// the workflow surface's send-analog is a HITL decision submission —
|
|
3
|
+
// submitting pins the thread (default-ON, opt-out via
|
|
4
|
+
// `scrollOnSend={false}`) BEFORE delegating, so the unblocked run's
|
|
5
|
+
// continuation lands in view. The real scroll mechanics are pinned in
|
|
6
|
+
// `internal/__tests__/useAutoScroll.layout.test.tsx`; this file pins the
|
|
7
|
+
// hitl-wrapper wiring through a spied `jumpToLatest`.
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
10
|
+
import { render, screen, fireEvent, cleanup, within } from "@testing-library/react";
|
|
11
|
+
import { create } from "@bufbuild/protobuf";
|
|
12
|
+
import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
|
|
13
|
+
import { ApprovalRequestedPayloadSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/event_pb";
|
|
14
|
+
import type { DerivedTaskState } from "../../internal/store/workflow-execution-event-store";
|
|
15
|
+
|
|
16
|
+
const jumpToLatestSpy = vi.fn();
|
|
17
|
+
|
|
18
|
+
// Replaces only the scroll machine (browser observers happy-dom lacks) with
|
|
19
|
+
// a spied jumpToLatest — the wrapper under test calls it directly.
|
|
20
|
+
vi.mock("../../internal/useAutoScroll", async (importOriginal) => {
|
|
21
|
+
const original =
|
|
22
|
+
await importOriginal<typeof import("../../internal/useAutoScroll")>();
|
|
23
|
+
return {
|
|
24
|
+
...original,
|
|
25
|
+
useAutoScroll: () => ({
|
|
26
|
+
scrollRef: { current: null },
|
|
27
|
+
sentinelRef: { current: null },
|
|
28
|
+
contentRef: () => {},
|
|
29
|
+
isFollowing: true,
|
|
30
|
+
jumpToLatest: jumpToLatestSpy,
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
WorkflowTaskThread,
|
|
37
|
+
type WorkflowThreadHitl,
|
|
38
|
+
} from "../thread/WorkflowTaskThread";
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
cleanup();
|
|
42
|
+
jumpToLatestSpy.mockClear();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function makeHitl(): WorkflowThreadHitl {
|
|
46
|
+
return {
|
|
47
|
+
submitApproval: vi.fn(),
|
|
48
|
+
approvalSubmittingToolCallIds: new Set<string>(),
|
|
49
|
+
approvalErrorsByToolCallId: new Map<string, Error>(),
|
|
50
|
+
submitTaskApproval: vi.fn(),
|
|
51
|
+
taskApprovalSubmittingTaskNames: new Set<string>(),
|
|
52
|
+
taskApprovalErrorsByTaskName: new Map<string, Error>(),
|
|
53
|
+
submitFileDecision: vi.fn(),
|
|
54
|
+
fileDecisionSubmittingKeys: new Set<string>(),
|
|
55
|
+
fileDecisionErrorsByKey: new Map<string, Error>(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A gating human_input task carrying its captured request payload. */
|
|
60
|
+
function gatedHumanInput(taskName: string): DerivedTaskState {
|
|
61
|
+
return {
|
|
62
|
+
taskName,
|
|
63
|
+
taskKind: WorkflowTaskKind.human_input,
|
|
64
|
+
status: "waiting_approval",
|
|
65
|
+
durationMs: 0,
|
|
66
|
+
costMicros: 0n,
|
|
67
|
+
tokensUsed: 0n,
|
|
68
|
+
attemptNumber: 1,
|
|
69
|
+
error: "",
|
|
70
|
+
childExecutionId: "",
|
|
71
|
+
agentSlug: "",
|
|
72
|
+
currentToolName: "",
|
|
73
|
+
messagesCount: 0,
|
|
74
|
+
toolCallsCount: 0,
|
|
75
|
+
inputSummary: null,
|
|
76
|
+
outputSummary: null,
|
|
77
|
+
approvalRequest: create(ApprovalRequestedPayloadSchema, {
|
|
78
|
+
prompt: "Ship the release?",
|
|
79
|
+
outcomes: [
|
|
80
|
+
{ name: "ship", label: "Ship It" },
|
|
81
|
+
{ name: "hold", label: "Hold" },
|
|
82
|
+
],
|
|
83
|
+
}),
|
|
84
|
+
approvalResolution: null,
|
|
85
|
+
} as DerivedTaskState;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function statesOf(...states: DerivedTaskState[]): ReadonlyMap<string, DerivedTaskState> {
|
|
89
|
+
return new Map(states.map((s) => [s.taskName, s]));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function submitShipIt(): void {
|
|
93
|
+
const form = screen.getByRole("form", {
|
|
94
|
+
name: "Approval decision for review-gate",
|
|
95
|
+
});
|
|
96
|
+
fireEvent.click(within(form).getByRole("button", { name: "Ship It" }));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
describe("WorkflowTaskThread — scroll-on-send (stigmer-cloud#267)", () => {
|
|
100
|
+
it("pins the thread when a HITL decision is submitted, then delegates to the workflow-level submit (default-on)", () => {
|
|
101
|
+
const hitl = makeHitl();
|
|
102
|
+
render(
|
|
103
|
+
<WorkflowTaskThread
|
|
104
|
+
taskStates={statesOf(gatedHumanInput("review-gate"))}
|
|
105
|
+
totalTasks={1}
|
|
106
|
+
isRunning
|
|
107
|
+
hitl={hitl}
|
|
108
|
+
/>,
|
|
109
|
+
);
|
|
110
|
+
expect(jumpToLatestSpy).not.toHaveBeenCalled();
|
|
111
|
+
|
|
112
|
+
submitShipIt();
|
|
113
|
+
|
|
114
|
+
expect(jumpToLatestSpy).toHaveBeenCalledTimes(1);
|
|
115
|
+
expect(hitl.submitTaskApproval).toHaveBeenCalledWith(
|
|
116
|
+
"review-gate",
|
|
117
|
+
"ship",
|
|
118
|
+
undefined,
|
|
119
|
+
undefined,
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("never pins with scrollOnSend={false} — the decision still routes through unchanged", () => {
|
|
124
|
+
const hitl = makeHitl();
|
|
125
|
+
render(
|
|
126
|
+
<WorkflowTaskThread
|
|
127
|
+
taskStates={statesOf(gatedHumanInput("review-gate"))}
|
|
128
|
+
totalTasks={1}
|
|
129
|
+
isRunning
|
|
130
|
+
hitl={hitl}
|
|
131
|
+
scrollOnSend={false}
|
|
132
|
+
/>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
submitShipIt();
|
|
136
|
+
|
|
137
|
+
expect(jumpToLatestSpy).not.toHaveBeenCalled();
|
|
138
|
+
expect(hitl.submitTaskApproval).toHaveBeenCalledWith(
|
|
139
|
+
"review-gate",
|
|
140
|
+
"ship",
|
|
141
|
+
undefined,
|
|
142
|
+
undefined,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -92,6 +92,19 @@ export interface WorkflowTaskThreadProps {
|
|
|
92
92
|
* summaries already on the items.
|
|
93
93
|
*/
|
|
94
94
|
readonly taskSnapshotsByName?: ReadonlyMap<string, WorkflowTask>;
|
|
95
|
+
/**
|
|
96
|
+
* Scroll to the latest content when the reader submits a HITL decision
|
|
97
|
+
* from a scrolled-up position (stigmer-cloud#267) — the workflow surface's
|
|
98
|
+
* send-analog: approving a gate, deciding a file review, or answering a
|
|
99
|
+
* task-level human_input gate re-engages follow mode, so the run's
|
|
100
|
+
* continuation lands in view. Incoming task activity is unaffected — it
|
|
101
|
+
* still never moves a scrolled-up reader. Default `true` on all three SDK
|
|
102
|
+
* thread surfaces at once (the ratified DD-011 divergence — cross-surface
|
|
103
|
+
* consistency is the point); set `false` to keep today's behavior.
|
|
104
|
+
*
|
|
105
|
+
* @default true
|
|
106
|
+
*/
|
|
107
|
+
readonly scrollOnSend?: boolean;
|
|
95
108
|
/** Additional CSS class names for the root container. */
|
|
96
109
|
readonly className?: string;
|
|
97
110
|
}
|
|
@@ -135,12 +148,38 @@ export const WorkflowTaskThread = memo(function WorkflowTaskThread({
|
|
|
135
148
|
onNavigateToAgentExecution,
|
|
136
149
|
hitl,
|
|
137
150
|
taskSnapshotsByName,
|
|
151
|
+
scrollOnSend = true,
|
|
138
152
|
className,
|
|
139
153
|
}: WorkflowTaskThreadProps) {
|
|
140
154
|
const { items, progress } = useWorkflowThreadItems(taskStates, totalTasks);
|
|
141
155
|
const { scrollRef, sentinelRef, contentRef, isFollowing, jumpToLatest } =
|
|
142
156
|
useAutoScroll();
|
|
143
157
|
|
|
158
|
+
// Scroll-on-send (stigmer-cloud#267): submitting a decision pins the
|
|
159
|
+
// thread before delegating, so the unblocked run's continuation lands in
|
|
160
|
+
// view. The wrapper's identity moves with the bundle's — deliberate: the
|
|
161
|
+
// bundle re-materializes exactly when its gate state flips (see the
|
|
162
|
+
// render-site note below), and a stale wrapper would leak stale state
|
|
163
|
+
// fields. `jumpToLatest` is referentially stable (useAutoScroll pins it).
|
|
164
|
+
const threadHitl = useMemo<WorkflowThreadHitl | undefined>(() => {
|
|
165
|
+
if (!hitl || !scrollOnSend) return hitl;
|
|
166
|
+
return {
|
|
167
|
+
...hitl,
|
|
168
|
+
submitApproval: (...args: Parameters<WorkflowThreadHitl["submitApproval"]>) => {
|
|
169
|
+
jumpToLatest();
|
|
170
|
+
return hitl.submitApproval(...args);
|
|
171
|
+
},
|
|
172
|
+
submitTaskApproval: (...args: Parameters<WorkflowThreadHitl["submitTaskApproval"]>) => {
|
|
173
|
+
jumpToLatest();
|
|
174
|
+
return hitl.submitTaskApproval(...args);
|
|
175
|
+
},
|
|
176
|
+
submitFileDecision: (...args: Parameters<WorkflowThreadHitl["submitFileDecision"]>) => {
|
|
177
|
+
jumpToLatest();
|
|
178
|
+
return hitl.submitFileDecision(...args);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}, [hitl, scrollOnSend, jumpToLatest]);
|
|
182
|
+
|
|
144
183
|
return (
|
|
145
184
|
<div className={cn("stg:relative stg:flex stg:h-full stg:min-h-0 stg:flex-col", className)}>
|
|
146
185
|
<ThreadProgressHeader progress={progress} isRunning={isRunning} />
|
|
@@ -164,7 +203,7 @@ export const WorkflowTaskThread = memo(function WorkflowTaskThread({
|
|
|
164
203
|
// would re-render the whole column per spinner tick. Scoped
|
|
165
204
|
// here, non-gating cards keep `undefined === undefined` and
|
|
166
205
|
// their memo bails hold.
|
|
167
|
-
hitl={item.status === "waiting_approval" ?
|
|
206
|
+
hitl={item.status === "waiting_approval" ? threadHitl : undefined}
|
|
168
207
|
snapshot={taskSnapshotsByName?.get(item.taskName)}
|
|
169
208
|
/>
|
|
170
209
|
))
|
|
@@ -412,6 +451,10 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
|
|
|
412
451
|
</pre>
|
|
413
452
|
</BoundedContent>
|
|
414
453
|
)}
|
|
454
|
+
{/* Carries the card's (wrapped) bundle: a child-gate decision
|
|
455
|
+
inside this transcript is a send too, and the gating card is
|
|
456
|
+
the thread's active tail — pinning the outer thread keeps
|
|
457
|
+
the transcript's continuation in view as it streams. */}
|
|
415
458
|
<WorkflowAgentCallTranscript
|
|
416
459
|
childExecutionId={item.childExecutionId}
|
|
417
460
|
agentSlug={item.agentSlug || undefined}
|
|
@@ -596,10 +639,11 @@ function ThreadTaskDetail({
|
|
|
596
639
|
readonly inputIO: TaskDetailIO | null;
|
|
597
640
|
readonly outputIO: TaskDetailIO | null;
|
|
598
641
|
}) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
642
|
+
// No Status/Duration rows (R6-6): the card header is the single source
|
|
643
|
+
// for both — the status glyph and the duration meta chip. The detail
|
|
644
|
+
// body carries only what the header cannot: attempt count, usage, the
|
|
645
|
+
// agent slug.
|
|
646
|
+
const rows: Array<[string, string]> = [];
|
|
603
647
|
if (item.attemptNumber > 1) rows.push(["Attempt", String(item.attemptNumber)]);
|
|
604
648
|
const costChip = formatMetaChips({
|
|
605
649
|
costMicros: item.costMicros,
|
|
@@ -612,14 +656,16 @@ function ThreadTaskDetail({
|
|
|
612
656
|
|
|
613
657
|
return (
|
|
614
658
|
<div className="stg:flex stg:flex-col stg:gap-2">
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
<
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
659
|
+
{rows.length > 0 && (
|
|
660
|
+
<dl className="stg:grid stg:grid-cols-[auto_1fr] stg:gap-x-4 stg:gap-y-1 stg:text-xs">
|
|
661
|
+
{rows.map(([label, value]) => (
|
|
662
|
+
<div key={label} className="stg:contents">
|
|
663
|
+
<dt className="stg:text-muted-foreground">{label}</dt>
|
|
664
|
+
<dd className="stg:text-foreground">{value}</dd>
|
|
665
|
+
</div>
|
|
666
|
+
))}
|
|
667
|
+
</dl>
|
|
668
|
+
)}
|
|
623
669
|
|
|
624
670
|
{item.error && (
|
|
625
671
|
<BoundedContent>
|
|
@@ -637,25 +683,6 @@ function ThreadTaskDetail({
|
|
|
637
683
|
);
|
|
638
684
|
}
|
|
639
685
|
|
|
640
|
-
function statusLabel(status: WorkflowThreadItem["status"]): string {
|
|
641
|
-
switch (status) {
|
|
642
|
-
case "waiting_approval":
|
|
643
|
-
return "Waiting for approval";
|
|
644
|
-
case "retrying":
|
|
645
|
-
return "Retrying";
|
|
646
|
-
case "running":
|
|
647
|
-
return "Running";
|
|
648
|
-
case "completed":
|
|
649
|
-
return "Completed";
|
|
650
|
-
case "failed":
|
|
651
|
-
return "Failed";
|
|
652
|
-
case "skipped":
|
|
653
|
-
return "Skipped";
|
|
654
|
-
case "pending":
|
|
655
|
-
return "Pending";
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
686
|
// ---------------------------------------------------------------------------
|
|
660
687
|
// Status glyph — the shared thread-card set, colored by status token (T05)
|
|
661
688
|
// ---------------------------------------------------------------------------
|
|
@@ -124,9 +124,18 @@ export function getTaskPresenter(
|
|
|
124
124
|
* the output-envelope follow-up standardizes them (DD-T04-3) — stays a
|
|
125
125
|
* clean one-line row with zero cost. `set_vars` earns its place from live
|
|
126
126
|
* data: the runner writes the seeded variables to task output (T05, R2-5).
|
|
127
|
+
* `try_catch` earns it the same way: its output is the try (or catch.do)
|
|
128
|
+
* block's settled result — the `transform` class. `raise_error` earns it
|
|
129
|
+
* from the OTHER `showBody` arm: it always fails, and the preview body
|
|
130
|
+
* renders the full failure detail without a click (R6-5).
|
|
127
131
|
*
|
|
128
|
-
* Only genuinely body-less kinds
|
|
129
|
-
* snapshot fallback's `unspecified
|
|
132
|
+
* Only genuinely body-less kinds stay compact summary rows: `wait`/
|
|
133
|
+
* `listen`, the snapshot fallback's `unspecified`, and the remaining
|
|
134
|
+
* control flow — including `switch_case`, DELIBERATELY (an R6-5
|
|
135
|
+
* refinement): its entire settled content is the one-word flow directive,
|
|
136
|
+
* carried by its always-visible preview line below; a body would only
|
|
137
|
+
* re-render the raw `{__flow_directive__}` marker, failing the "does the
|
|
138
|
+
* body carry content the one-line row cannot?" rule.
|
|
130
139
|
*/
|
|
131
140
|
const PREVIEW_KINDS: ReadonlySet<WorkflowTaskKind> = new Set([
|
|
132
141
|
WorkflowTaskKind.transform,
|
|
@@ -142,6 +151,8 @@ const PREVIEW_KINDS: ReadonlySet<WorkflowTaskKind> = new Set([
|
|
|
142
151
|
WorkflowTaskKind.grpc_call,
|
|
143
152
|
WorkflowTaskKind.activity_call,
|
|
144
153
|
WorkflowTaskKind.run_workflow,
|
|
154
|
+
WorkflowTaskKind.try_catch,
|
|
155
|
+
WorkflowTaskKind.raise_error,
|
|
145
156
|
]);
|
|
146
157
|
|
|
147
158
|
/** Default disclosure mode for a task kind. */
|
|
@@ -218,12 +229,17 @@ function defaultPreviewLine(state: DerivedTaskState): string | null {
|
|
|
218
229
|
return waitLine(state);
|
|
219
230
|
case WorkflowTaskKind.listen:
|
|
220
231
|
return listenLine(state);
|
|
232
|
+
case WorkflowTaskKind.switch_case:
|
|
233
|
+
return switchCaseLine(state.outputSummary);
|
|
234
|
+
case WorkflowTaskKind.try_catch:
|
|
235
|
+
return tryCatchLine(state);
|
|
221
236
|
default:
|
|
222
|
-
//
|
|
223
|
-
// thread does not have — deferred), raise_error (the
|
|
224
|
-
// precedence already shows the message
|
|
225
|
-
//
|
|
226
|
-
//
|
|
237
|
+
// Remaining control flow (for_each / fork progress need graph
|
|
238
|
+
// topology the thread does not have — deferred), raise_error (the
|
|
239
|
+
// failure precedence already shows the message; its preview body
|
|
240
|
+
// carries the full detail), the invocation kinds (pending the
|
|
241
|
+
// backend output-envelope follow-up), and the snapshot fallback's
|
|
242
|
+
// `unspecified` all stay status-only.
|
|
227
243
|
return null;
|
|
228
244
|
}
|
|
229
245
|
}
|
|
@@ -354,6 +370,35 @@ function listenLine(state: DerivedTaskState): string | null {
|
|
|
354
370
|
return null;
|
|
355
371
|
}
|
|
356
372
|
|
|
373
|
+
/**
|
|
374
|
+
* `→ approved-path` / `→ exit` — the flow directive the switch settled on
|
|
375
|
+
* (R6-5). The switch executor's only output is the `__flow_directive__`
|
|
376
|
+
* marker carrying the matched case's `then` (a target task name, or the
|
|
377
|
+
* `continue`/`end`/`exit` terminals), and `do-executor` writes raw task
|
|
378
|
+
* output to the event summary — so the branch taken is already on the
|
|
379
|
+
* wire. No case matched means no output: status-only, honestly.
|
|
380
|
+
*/
|
|
381
|
+
function switchCaseLine(output: JsonObject | null): string | null {
|
|
382
|
+
const directive = asString(output?.["__flow_directive__"]);
|
|
383
|
+
return directive ? `\u2192 ${directive}` : null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* `recovered after 2 attempts` / snippet of the settled block result
|
|
388
|
+
* (R6-5). The try task's own `task_retrying` events drive
|
|
389
|
+
* `attemptNumber`, so a completion above attempt 1 IS a catch-retry
|
|
390
|
+
* recovery. A catch.do-path recovery (no retry configured) is NOT
|
|
391
|
+
* distinguishable from plain success in the emitted data — deliberately
|
|
392
|
+
* unmarked: adding a marker to task output would leak presentation into
|
|
393
|
+
* workflow dataflow (`processTaskOutput` consumes it downstream).
|
|
394
|
+
*/
|
|
395
|
+
function tryCatchLine(state: DerivedTaskState): string | null {
|
|
396
|
+
if (state.status === "completed" && state.attemptNumber > 1) {
|
|
397
|
+
return `recovered after ${state.attemptNumber} attempts`;
|
|
398
|
+
}
|
|
399
|
+
return state.outputSummary ? valueSnippet(state.outputSummary) : null;
|
|
400
|
+
}
|
|
401
|
+
|
|
357
402
|
// ---------------------------------------------------------------------------
|
|
358
403
|
// Struct-reading helpers (defensive by construction)
|
|
359
404
|
// ---------------------------------------------------------------------------
|
|
@@ -695,8 +695,16 @@ function EditorArea({
|
|
|
695
695
|
<div className="stg:flex stg:h-full stg:min-h-0 stg:flex-col">
|
|
696
696
|
{/* Collapse control + tab strip. The far right is kept clear (pr-24):
|
|
697
697
|
the session viewer floats its top-right controls (host
|
|
698
|
-
`headerActions` and the panel chip) over this region.
|
|
699
|
-
|
|
698
|
+
`headerActions` and the panel chip) over this region. Fixed to the
|
|
699
|
+
shared HEADER_ROW_HEIGHT so this row's bottom border stays flush
|
|
700
|
+
with the sidebar facet header's — and so an empty tab strip (no
|
|
701
|
+
open editors) cannot collapse the row. */}
|
|
702
|
+
<div
|
|
703
|
+
className={cn(
|
|
704
|
+
HEADER_ROW_HEIGHT,
|
|
705
|
+
"stg:flex stg:shrink-0 stg:items-stretch stg:border-b stg:border-border stg:pr-24",
|
|
706
|
+
)}
|
|
707
|
+
>
|
|
700
708
|
<Tooltip>
|
|
701
709
|
<TooltipTrigger
|
|
702
710
|
render={
|
|
@@ -843,9 +851,24 @@ function Breadcrumbs({
|
|
|
843
851
|
// Shared sidebar chrome
|
|
844
852
|
// ---------------------------------------------------------------------------
|
|
845
853
|
|
|
854
|
+
/**
|
|
855
|
+
* One fixed height for every header row across the surface — the sidebar
|
|
856
|
+
* facet header AND the editor area's tab-strip row — so their bottom borders
|
|
857
|
+
* form a single continuous line across the panel. Fixed rather than
|
|
858
|
+
* padding-derived: the two rows have different content (micro-caps title vs
|
|
859
|
+
* text-xs tabs, sometimes no tabs at all), so padding math can never keep
|
|
860
|
+
* them aligned.
|
|
861
|
+
*/
|
|
862
|
+
const HEADER_ROW_HEIGHT = "stg:h-8";
|
|
863
|
+
|
|
846
864
|
function SidebarHeader({ title }: { readonly title: string }) {
|
|
847
865
|
return (
|
|
848
|
-
<div
|
|
866
|
+
<div
|
|
867
|
+
className={cn(
|
|
868
|
+
HEADER_ROW_HEIGHT,
|
|
869
|
+
"stg:flex stg:shrink-0 stg:items-center stg:border-b stg:border-border stg:px-3 stg:text-[0.65rem] stg:font-semibold stg:uppercase stg:tracking-wide stg:text-muted-foreground",
|
|
870
|
+
)}
|
|
871
|
+
>
|
|
849
872
|
{title}
|
|
850
873
|
</div>
|
|
851
874
|
);
|