@opengeni/react 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/{chunk-4F2YLLHP.js → chunk-AVLMIWYO.js} +3 -12
- package/dist/chunk-AVLMIWYO.js.map +1 -0
- package/dist/{chunk-NBYNFI5T.js → chunk-CSJBF3QJ.js} +5 -3
- package/dist/chunk-CSJBF3QJ.js.map +1 -0
- package/dist/{chunk-XVVQP7A4.js → chunk-IPVOVOW6.js} +2 -2
- package/dist/{chunk-OZDLELJQ.js → chunk-OKXCECLG.js} +55 -10
- package/dist/chunk-OKXCECLG.js.map +1 -0
- package/dist/{chunk-GQN2QIR2.js → chunk-ZMNPFZWL.js} +238 -87
- package/dist/chunk-ZMNPFZWL.js.map +1 -0
- package/dist/composer.js +2 -2
- package/dist/index.d.ts +7 -34
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/{queue-surface-implementation-3UW3MIIR.js → queue-surface-implementation-UQ535OKQ.js} +44 -12
- package/dist/queue-surface-implementation-UQ535OKQ.js.map +1 -0
- package/dist/{session-LsampPc6.d.ts → session-DaONt-dY.d.ts} +1 -1
- package/dist/{session-ui-BPOV-tuY.d.ts → session-ui-B5_roA95.d.ts} +81 -5
- package/dist/session-ui.d.ts +2 -2
- package/dist/session-ui.js +4 -2
- package/dist/session.d.ts +2 -2
- package/dist/session.js +3 -3
- package/dist/{use-turn-queue-aDnQvzVz.d.ts → use-turn-queue-D_1a8W_l.d.ts} +26 -4
- package/package.json +2 -2
- package/src/components/message-timeline.tsx +88 -0
- package/src/components/queue-surface-implementation.tsx +79 -11
- package/src/components/queue-surface.tsx +5 -5
- package/src/hooks/use-composer.ts +1 -15
- package/src/hooks/use-turn-queue.ts +8 -0
- package/src/index.ts +9 -0
- package/src/session-ui.ts +9 -0
- package/src/session.ts +2 -0
- package/src/timeline/index.ts +13 -2
- package/src/timeline/parsers.ts +3 -31
- package/src/timeline/projection.ts +73 -9
- package/src/timeline/turn-summary.tsx +243 -56
- package/src/timeline/types.ts +27 -1
- package/dist/chunk-4F2YLLHP.js.map +0 -1
- package/dist/chunk-GQN2QIR2.js.map +0 -1
- package/dist/chunk-NBYNFI5T.js.map +0 -1
- package/dist/chunk-OZDLELJQ.js.map +0 -1
- package/dist/queue-surface-implementation-3UW3MIIR.js.map +0 -1
- /package/dist/{chunk-XVVQP7A4.js.map → chunk-IPVOVOW6.js.map} +0 -0
|
@@ -42,10 +42,12 @@ import {
|
|
|
42
42
|
type AgentMessageItem,
|
|
43
43
|
type AuthNeededItem,
|
|
44
44
|
type GoalItem,
|
|
45
|
+
type MachineInputBatchItem,
|
|
45
46
|
type NoticeItem,
|
|
46
47
|
type TimelineGroup,
|
|
47
48
|
type TimelineItem,
|
|
48
49
|
type ToolRegistry,
|
|
50
|
+
type TurnSummaryOptions,
|
|
49
51
|
type UserMessageItem,
|
|
50
52
|
type WorkerCompletionItem,
|
|
51
53
|
TurnSummary,
|
|
@@ -97,6 +99,8 @@ export type MessageTimelineProps = {
|
|
|
97
99
|
* `createDefaultToolRegistry({ entries })` to add custom tool renderers.
|
|
98
100
|
*/
|
|
99
101
|
toolRegistry?: ToolRegistry | undefined;
|
|
102
|
+
/** Customize collapsed turn facets for this timeline instance. */
|
|
103
|
+
turnSummary?: TurnSummaryOptions | undefined;
|
|
100
104
|
/** Follow new events when pinned to the bottom. Defaults to true. */
|
|
101
105
|
autoFollow?: boolean | undefined;
|
|
102
106
|
/** Older durable history exists above the current window (see useSessionEvents). */
|
|
@@ -127,6 +131,7 @@ export function MessageTimeline({
|
|
|
127
131
|
onReconnect,
|
|
128
132
|
resolveProviderLogo,
|
|
129
133
|
toolRegistry = defaultToolRegistry,
|
|
134
|
+
turnSummary,
|
|
130
135
|
autoFollow = true,
|
|
131
136
|
hasOlder = false,
|
|
132
137
|
loadingOlder = false,
|
|
@@ -414,6 +419,7 @@ export function MessageTimeline({
|
|
|
414
419
|
onReconnect={onReconnect}
|
|
415
420
|
resolveProviderLogo={resolveProviderLogo}
|
|
416
421
|
toolRegistry={toolRegistry}
|
|
422
|
+
turnSummary={turnSummary}
|
|
417
423
|
foldLiveCluster={isAgentProgress(groups[index + 1]?.group)}
|
|
418
424
|
/>
|
|
419
425
|
</div>
|
|
@@ -621,6 +627,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
621
627
|
onReconnect,
|
|
622
628
|
resolveProviderLogo,
|
|
623
629
|
toolRegistry,
|
|
630
|
+
turnSummary,
|
|
624
631
|
insideTurn = false,
|
|
625
632
|
foldLiveCluster = false,
|
|
626
633
|
}: {
|
|
@@ -633,6 +640,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
633
640
|
onReconnect?: ((item: AuthNeededItem) => void | Promise<void>) | undefined;
|
|
634
641
|
resolveProviderLogo?: ((providerDomain: string) => string | null | undefined) | undefined;
|
|
635
642
|
toolRegistry: ToolRegistry;
|
|
643
|
+
turnSummary?: TurnSummaryOptions | undefined;
|
|
636
644
|
/** A completed cluster of a still-RUNNING turn (not the live tail) folds
|
|
637
645
|
behind a neutral chip — the one place activity without an outcome still
|
|
638
646
|
folds, bounding the DOM of days-long autonomous turns. */
|
|
@@ -651,6 +659,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
651
659
|
failureText={insideTurn ? undefined : group.failureText}
|
|
652
660
|
defaultOpen={!insideTurn && group.outcome === "failed" ? true : undefined}
|
|
653
661
|
bare={insideTurn}
|
|
662
|
+
facets={turnSummary?.facets}
|
|
654
663
|
>
|
|
655
664
|
<ActivityRail
|
|
656
665
|
items={group.items}
|
|
@@ -683,6 +692,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
683
692
|
onReconnect={onReconnect}
|
|
684
693
|
resolveProviderLogo={resolveProviderLogo}
|
|
685
694
|
toolRegistry={toolRegistry}
|
|
695
|
+
turnSummary={turnSummary}
|
|
686
696
|
insideTurn
|
|
687
697
|
/>
|
|
688
698
|
));
|
|
@@ -697,6 +707,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
697
707
|
durationMs={durationBetween(group.startedAt, group.endedAt)}
|
|
698
708
|
defaultOpen={!insideTurn && group.outcome === "failed" ? true : undefined}
|
|
699
709
|
bare={insideTurn}
|
|
710
|
+
facets={turnSummary?.facets}
|
|
700
711
|
>
|
|
701
712
|
{insideTurn ? (
|
|
702
713
|
// A nested turn is already on an ancestor rail — its body just stacks
|
|
@@ -833,6 +844,8 @@ export function TimelineRow({
|
|
|
833
844
|
return <SessionStatusRow item={item} />;
|
|
834
845
|
case "goal":
|
|
835
846
|
return <GoalRow item={item} />;
|
|
847
|
+
case "machine-input-batch":
|
|
848
|
+
return <MachineInputBatchRow item={item} />;
|
|
836
849
|
case "notice":
|
|
837
850
|
return <NoticeRow item={item} />;
|
|
838
851
|
case "auth-needed":
|
|
@@ -1155,6 +1168,81 @@ function GoalRow({ item }: { item: GoalItem }) {
|
|
|
1155
1168
|
);
|
|
1156
1169
|
}
|
|
1157
1170
|
|
|
1171
|
+
const MACHINE_INPUT_META: Record<MachineInputBatchItem["members"][number]["kind"], string> = {
|
|
1172
|
+
scheduled_occurrence: "Scheduled update",
|
|
1173
|
+
goal_continuation: "Goal continued",
|
|
1174
|
+
agent_message: "Agent update",
|
|
1175
|
+
agent_steer_instruction: "Agent direction",
|
|
1176
|
+
child_terminal_result: "Agent finished",
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
function MachineInputBatchRow({ item }: { item: MachineInputBatchItem }) {
|
|
1180
|
+
const enter = useEntranceAnimation();
|
|
1181
|
+
const visible = item.members.slice(0, 3);
|
|
1182
|
+
return (
|
|
1183
|
+
<div
|
|
1184
|
+
className={cn(
|
|
1185
|
+
enter && "animate-og-enter",
|
|
1186
|
+
"rounded-og-md border border-og-border/70 bg-og-surface-1/55 px-3 py-2.5",
|
|
1187
|
+
)}
|
|
1188
|
+
>
|
|
1189
|
+
{item.members.length > 1 && (
|
|
1190
|
+
<div className="mb-2 text-xs font-medium text-og-fg-subtle">
|
|
1191
|
+
{item.members.length} updates joined this turn
|
|
1192
|
+
</div>
|
|
1193
|
+
)}
|
|
1194
|
+
<div className="space-y-2">
|
|
1195
|
+
{visible.map((member) => (
|
|
1196
|
+
<MachineInputRow key={member.id} member={member} />
|
|
1197
|
+
))}
|
|
1198
|
+
</div>
|
|
1199
|
+
{item.members.length > visible.length && (
|
|
1200
|
+
<details className="mt-2 pl-8 text-xs text-og-fg-muted">
|
|
1201
|
+
<summary className="cursor-pointer select-none hover:text-og-fg">
|
|
1202
|
+
Show {item.members.length - visible.length} more
|
|
1203
|
+
</summary>
|
|
1204
|
+
<div className="mt-2 space-y-2">
|
|
1205
|
+
{item.members.slice(visible.length).map((member) => (
|
|
1206
|
+
<MachineInputRow key={member.id} member={member} />
|
|
1207
|
+
))}
|
|
1208
|
+
</div>
|
|
1209
|
+
</details>
|
|
1210
|
+
)}
|
|
1211
|
+
</div>
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function MachineInputRow({ member }: { member: MachineInputBatchItem["members"][number] }) {
|
|
1216
|
+
const source = readableMachineInputSource(member.sourceId);
|
|
1217
|
+
return (
|
|
1218
|
+
<div className="flex min-w-0 items-start gap-2.5">
|
|
1219
|
+
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-og-fg-subtle" aria-hidden />
|
|
1220
|
+
<div className="min-w-0 flex-1">
|
|
1221
|
+
<span className="text-xs font-medium text-og-fg-muted">
|
|
1222
|
+
{MACHINE_INPUT_META[member.kind]}
|
|
1223
|
+
</span>
|
|
1224
|
+
{source && <span className="ml-1.5 text-xs text-og-fg-subtle">from {source}</span>}
|
|
1225
|
+
{member.summary && (
|
|
1226
|
+
<p className="mt-0.5 whitespace-pre-wrap break-words text-sm leading-5 text-og-fg">
|
|
1227
|
+
{truncate(cleanMachineInputSummary(member.summary), 320)}
|
|
1228
|
+
</p>
|
|
1229
|
+
)}
|
|
1230
|
+
</div>
|
|
1231
|
+
</div>
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function readableMachineInputSource(sourceId: string): string | null {
|
|
1236
|
+
const value = sourceId.trim();
|
|
1237
|
+
if (!value || /^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(value)) return null;
|
|
1238
|
+
if (/^(goal|schedule|system):/i.test(value)) return null;
|
|
1239
|
+
return value.replaceAll("_", " ");
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function cleanMachineInputSummary(summary: string): string {
|
|
1243
|
+
return summary.replace(/^\[[A-Z][A-Z _-]*(?:\s+\d+\/\d+)?\]\s*/, "").trim();
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1158
1246
|
function NoticeRow({ item }: { item: NoticeItem }) {
|
|
1159
1247
|
const enter = useEntranceAnimation();
|
|
1160
1248
|
const tone =
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
verticalListSortingStrategy,
|
|
17
17
|
} from "@dnd-kit/sortable";
|
|
18
18
|
import { CSS } from "@dnd-kit/utilities";
|
|
19
|
-
import type { SessionTurn } from "@opengeni/sdk";
|
|
19
|
+
import type { SessionPendingInputPreview, SessionTurn } from "@opengeni/sdk";
|
|
20
20
|
import {
|
|
21
21
|
ArrowDownToLineIcon,
|
|
22
22
|
ArrowUpToLineIcon,
|
|
@@ -43,7 +43,7 @@ import type { QueueMutationKind, UseTurnQueueResult } from "../hooks/use-turn-qu
|
|
|
43
43
|
import { requestQueueDraftEdit } from "./queue-draft-policy";
|
|
44
44
|
import { QueueErrorAlert, QueueStoppingStatus } from "./queue-surface-state";
|
|
45
45
|
|
|
46
|
-
/** The sole
|
|
46
|
+
/** The sole pending-input surface: compact above Goal, Agents, and composer. */
|
|
47
47
|
type QueueSurfaceCommonProps = {
|
|
48
48
|
/** Focus the composer owned by this queue after checkout/removal. */
|
|
49
49
|
onRequestComposerFocus?: (() => void) | undefined;
|
|
@@ -85,10 +85,21 @@ export function QueueSurface({
|
|
|
85
85
|
projectedIndex: number;
|
|
86
86
|
} | null>(null);
|
|
87
87
|
const count = queue.queue.length;
|
|
88
|
+
const pendingInputCount = queue.pendingInputs.length;
|
|
89
|
+
const totalCount = count + pendingInputCount;
|
|
90
|
+
const attachedInputIds = queue.pendingInputAttachment?.inputIds ?? [];
|
|
91
|
+
const attachedInputs = queue.pendingInputs.filter((input) => attachedInputIds.includes(input.id));
|
|
92
|
+
const standaloneInputs = queue.pendingInputs.filter(
|
|
93
|
+
(input) => !attachedInputIds.includes(input.id),
|
|
94
|
+
);
|
|
88
95
|
const canEditInComposer = queueComposerCheckoutEnabled(composer, readOnly);
|
|
89
96
|
const collapsedPreview = useMemo(
|
|
90
|
-
() =>
|
|
91
|
-
|
|
97
|
+
() =>
|
|
98
|
+
queuePromptPreview(
|
|
99
|
+
queue.queue[0]?.prompt ?? queue.pendingInputs[0]?.summary ?? "",
|
|
100
|
+
QUEUE_COLLAPSED_PREVIEW_CHARACTERS,
|
|
101
|
+
),
|
|
102
|
+
[queue.pendingInputs, queue.queue],
|
|
92
103
|
);
|
|
93
104
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
|
|
94
105
|
|
|
@@ -227,7 +238,7 @@ export function QueueSurface({
|
|
|
227
238
|
[canEditInComposer, composer, edit],
|
|
228
239
|
);
|
|
229
240
|
|
|
230
|
-
if (
|
|
241
|
+
if (totalCount === 0 && !queue.stoppingPreviousAttempt && !queue.error && !queue.mutationError)
|
|
231
242
|
return null;
|
|
232
243
|
|
|
233
244
|
return (
|
|
@@ -242,23 +253,31 @@ export function QueueSurface({
|
|
|
242
253
|
type="button"
|
|
243
254
|
className="flex w-full min-w-0 flex-wrap items-center gap-2 px-3 py-2 text-left outline-none transition-colors hover:bg-surface-2/60 focus-visible:bg-surface-2/60 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
|
|
244
255
|
onClick={() => setOpen((value) => !value)}
|
|
245
|
-
aria-description={!open &&
|
|
256
|
+
aria-description={!open && totalCount > 0 ? collapsedPreview.summary : undefined}
|
|
246
257
|
aria-expanded={open}
|
|
247
|
-
aria-label={`${count} queued prompt${count === 1 ? "" : "s"}${
|
|
258
|
+
aria-label={`${count} queued prompt${count === 1 ? "" : "s"}${
|
|
259
|
+
pendingInputCount > 0
|
|
260
|
+
? ` and ${pendingInputCount} pending machine input${pendingInputCount === 1 ? "" : "s"}`
|
|
261
|
+
: ""
|
|
262
|
+
}${readOnly ? " Read-only" : ""}`}
|
|
248
263
|
>
|
|
249
264
|
<ChevronDownIcon
|
|
250
265
|
aria-hidden="true"
|
|
251
266
|
className={`size-3.5 shrink-0 text-fg-subtle transition-transform ${open ? "rotate-180" : ""}`}
|
|
252
267
|
/>
|
|
253
268
|
<span className="text-xs font-medium text-fg">
|
|
254
|
-
{count} queued prompt{count === 1 ? "" : "s"}
|
|
269
|
+
{count > 0 ? `${count} queued prompt${count === 1 ? "" : "s"}` : null}
|
|
270
|
+
{count > 0 && pendingInputCount > 0 ? " · " : null}
|
|
271
|
+
{pendingInputCount > 0
|
|
272
|
+
? `${pendingInputCount} incoming update${pendingInputCount === 1 ? "" : "s"}`
|
|
273
|
+
: null}
|
|
255
274
|
</span>
|
|
256
275
|
{readOnly ? (
|
|
257
276
|
<span className="shrink-0 rounded border border-border px-1.5 py-0.5 text-2xs text-fg-subtle">
|
|
258
277
|
Read-only
|
|
259
278
|
</span>
|
|
260
279
|
) : null}
|
|
261
|
-
{!open &&
|
|
280
|
+
{!open && totalCount > 0 ? (
|
|
262
281
|
<span
|
|
263
282
|
aria-hidden="true"
|
|
264
283
|
className={`max-w-full truncate text-fg-muted ${
|
|
@@ -275,6 +294,10 @@ export function QueueSurface({
|
|
|
275
294
|
{queue.loading ? <Loader2Icon className="ml-auto size-3.5 animate-spin" /> : null}
|
|
276
295
|
</button>
|
|
277
296
|
|
|
297
|
+
{open && standaloneInputs.length > 0 ? (
|
|
298
|
+
<PendingMachineInputs inputs={standaloneInputs} />
|
|
299
|
+
) : null}
|
|
300
|
+
|
|
278
301
|
{open && count > 0 && readOnly ? (
|
|
279
302
|
<ol
|
|
280
303
|
className="max-h-[min(30rem,60dvh)] divide-y divide-border overflow-y-auto overscroll-contain border-t border-border"
|
|
@@ -286,6 +309,9 @@ export function QueueSurface({
|
|
|
286
309
|
key={turn.id}
|
|
287
310
|
turn={turn}
|
|
288
311
|
index={index}
|
|
312
|
+
attachedInputs={
|
|
313
|
+
turn.id === queue.pendingInputAttachment?.turnId ? attachedInputs : []
|
|
314
|
+
}
|
|
289
315
|
onDisclosureChange={(expanded) =>
|
|
290
316
|
setAnnouncement(
|
|
291
317
|
`Full content for queued prompt ${index + 1} ${expanded ? "shown" : "hidden"}.`,
|
|
@@ -320,6 +346,9 @@ export function QueueSurface({
|
|
|
320
346
|
turn={turn}
|
|
321
347
|
index={index}
|
|
322
348
|
count={count}
|
|
349
|
+
attachedInputs={
|
|
350
|
+
turn.id === queue.pendingInputAttachment?.turnId ? attachedInputs : []
|
|
351
|
+
}
|
|
323
352
|
pending={queue.mutationFor(turn.id)}
|
|
324
353
|
confirmingReplace={replaceDraftFor === turn.id}
|
|
325
354
|
keyboardDragging={keyboardDrag?.turnId === turn.id}
|
|
@@ -396,6 +425,35 @@ export function QueueSurface({
|
|
|
396
425
|
);
|
|
397
426
|
}
|
|
398
427
|
|
|
428
|
+
function PendingMachineInputs({
|
|
429
|
+
inputs,
|
|
430
|
+
attached = false,
|
|
431
|
+
}: {
|
|
432
|
+
inputs: SessionPendingInputPreview[];
|
|
433
|
+
attached?: boolean;
|
|
434
|
+
}) {
|
|
435
|
+
return (
|
|
436
|
+
<section className="border-t border-border px-3 py-2">
|
|
437
|
+
<p className="text-2xs font-medium text-fg-subtle">
|
|
438
|
+
{attached
|
|
439
|
+
? `${inputs.length} update${inputs.length === 1 ? "" : "s"} will join this prompt`
|
|
440
|
+
: "Incoming updates waiting to run"}
|
|
441
|
+
</p>
|
|
442
|
+
<ol className="mt-1 space-y-1">
|
|
443
|
+
{inputs.map((input) => (
|
|
444
|
+
<li key={input.id} className="line-clamp-3 break-words text-xs text-fg" dir="auto">
|
|
445
|
+
<strong className="capitalize text-fg-muted">
|
|
446
|
+
{input.kind.replace("_terminal", "").replaceAll("_", " ")}
|
|
447
|
+
</strong>{" "}
|
|
448
|
+
· {input.sourceId}
|
|
449
|
+
{input.summary ? ` — ${input.summary}` : ""}
|
|
450
|
+
</li>
|
|
451
|
+
))}
|
|
452
|
+
</ol>
|
|
453
|
+
</section>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
399
457
|
const QUEUE_ROW_PREVIEW_CHARACTERS = 360;
|
|
400
458
|
const QUEUE_COLLAPSED_PREVIEW_CHARACTERS = 180;
|
|
401
459
|
const QUEUE_PREVIEW_GRAPHEME_CONTEXT_CHARACTERS = 32;
|
|
@@ -735,7 +793,7 @@ function QueuePrompt({
|
|
|
735
793
|
aria-controls={fullContentId}
|
|
736
794
|
aria-expanded={expanded}
|
|
737
795
|
aria-label={`${expanded ? "Hide" : "Show"} full content for queued prompt ${index + 1}`}
|
|
738
|
-
className="mt-1 inline-flex min-h-7 min-w-0 max-w-full items-center gap-1 whitespace-normal rounded-md text-left text-2xs font-medium text-fg
|
|
796
|
+
className="mt-1 inline-flex min-h-7 min-w-0 max-w-full items-center gap-1 whitespace-normal rounded-md text-left text-2xs font-medium text-fg outline-none focus-visible:ring-2 focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
|
|
739
797
|
data-testid={`queue-prompt-disclosure-${index + 1}`}
|
|
740
798
|
onClick={() => {
|
|
741
799
|
const next = !expanded;
|
|
@@ -769,10 +827,12 @@ function QueuePrompt({
|
|
|
769
827
|
function ReadOnlyQueueRow({
|
|
770
828
|
turn,
|
|
771
829
|
index,
|
|
830
|
+
attachedInputs,
|
|
772
831
|
onDisclosureChange,
|
|
773
832
|
}: {
|
|
774
833
|
turn: SessionTurn;
|
|
775
834
|
index: number;
|
|
835
|
+
attachedInputs: SessionPendingInputPreview[];
|
|
776
836
|
onDisclosureChange: (expanded: boolean) => void;
|
|
777
837
|
}) {
|
|
778
838
|
return (
|
|
@@ -780,6 +840,9 @@ function ReadOnlyQueueRow({
|
|
|
780
840
|
<span className="mt-1 shrink-0 font-mono text-2xs text-fg-subtle">{index + 1}</span>
|
|
781
841
|
<div className="min-w-0 flex-1">
|
|
782
842
|
<QueuePrompt prompt={turn.prompt} index={index} onDisclosureChange={onDisclosureChange} />
|
|
843
|
+
{attachedInputs.length > 0 ? (
|
|
844
|
+
<PendingMachineInputs inputs={attachedInputs} attached />
|
|
845
|
+
) : null}
|
|
783
846
|
<div className="mt-1 flex min-w-0 items-center gap-2 overflow-hidden whitespace-nowrap text-2xs text-fg-subtle">
|
|
784
847
|
{turn.resources.length > 0 ? (
|
|
785
848
|
<span className="shrink-0">
|
|
@@ -803,6 +866,7 @@ function SortableQueueRow({
|
|
|
803
866
|
turn,
|
|
804
867
|
index,
|
|
805
868
|
count,
|
|
869
|
+
attachedInputs,
|
|
806
870
|
pending,
|
|
807
871
|
confirmingReplace,
|
|
808
872
|
keyboardDragging,
|
|
@@ -818,6 +882,7 @@ function SortableQueueRow({
|
|
|
818
882
|
turn: SessionTurn;
|
|
819
883
|
index: number;
|
|
820
884
|
count: number;
|
|
885
|
+
attachedInputs: SessionPendingInputPreview[];
|
|
821
886
|
pending: QueueMutationKind | null;
|
|
822
887
|
confirmingReplace: boolean;
|
|
823
888
|
keyboardDragging: boolean;
|
|
@@ -864,6 +929,9 @@ function SortableQueueRow({
|
|
|
864
929
|
</span>
|
|
865
930
|
<div className="col-span-full row-start-2 min-w-0 sm:col-span-1 sm:col-start-3 sm:row-start-1">
|
|
866
931
|
<QueuePrompt prompt={turn.prompt} index={index} onDisclosureChange={onDisclosureChange} />
|
|
932
|
+
{attachedInputs.length > 0 ? (
|
|
933
|
+
<PendingMachineInputs inputs={attachedInputs} attached />
|
|
934
|
+
) : null}
|
|
867
935
|
<div className="mt-1 flex min-w-0 items-center gap-2 overflow-hidden whitespace-nowrap text-2xs text-fg-subtle">
|
|
868
936
|
{turn.resources.length > 0 ? (
|
|
869
937
|
<span className="shrink-0">
|
|
@@ -886,7 +954,7 @@ function SortableQueueRow({
|
|
|
886
954
|
onClick={onSteer}
|
|
887
955
|
aria-label={`Steer queued prompt ${index + 1}`}
|
|
888
956
|
title="Make this the next direction"
|
|
889
|
-
className="inline-flex h-7 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-xs font-medium outline-none transition-
|
|
957
|
+
className="inline-flex h-7 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-xs font-medium text-fg outline-none transition-[background-color] hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:min-h-[44px] pointer-coarse:min-w-[44px]"
|
|
890
958
|
>
|
|
891
959
|
{pending === "steer" ? (
|
|
892
960
|
<Loader2Icon className="size-3.5 animate-spin" />
|
|
@@ -11,7 +11,7 @@ const loadQueueSurface = () => import("./queue-surface-implementation");
|
|
|
11
11
|
type QueueSurfaceModule = { QueueSurface: ComponentType<QueueSurfaceProps> };
|
|
12
12
|
type QueueSurfaceLoader = () => Promise<QueueSurfaceModule>;
|
|
13
13
|
|
|
14
|
-
/** The sole
|
|
14
|
+
/** The sole pending-input surface: compact above Goal, Agents, and composer. */
|
|
15
15
|
export const QueueSurface = createQueueSurface(loadQueueSurface);
|
|
16
16
|
|
|
17
17
|
/** @internal Deterministic Suspense seam for the QueueSurface regression suite. */
|
|
@@ -26,20 +26,20 @@ function createQueueSurface(loadImplementation: QueueSurfaceLoader) {
|
|
|
26
26
|
|
|
27
27
|
return function QueueSurfaceBoundary(props: QueueSurfaceProps) {
|
|
28
28
|
const { queue } = props;
|
|
29
|
-
if (queue.queue.length === 0) {
|
|
29
|
+
if (queue.queue.length + queue.pendingInputs.length === 0) {
|
|
30
30
|
if (!queue.stoppingPreviousAttempt && !queue.error && !queue.mutationError) return null;
|
|
31
31
|
return <EmptyQueueStateSurface queue={queue} />;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<Suspense fallback={<QueueSurfaceFallback
|
|
35
|
+
<Suspense fallback={<QueueSurfaceFallback />}>
|
|
36
36
|
<LazyQueueSurface {...props} />
|
|
37
37
|
</Suspense>
|
|
38
38
|
);
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function QueueSurfaceFallback(
|
|
42
|
+
function QueueSurfaceFallback() {
|
|
43
43
|
return (
|
|
44
44
|
<div
|
|
45
45
|
aria-live="polite"
|
|
@@ -53,7 +53,7 @@ function QueueSurfaceFallback({ count }: { count: number }) {
|
|
|
53
53
|
aria-hidden="true"
|
|
54
54
|
className="size-3.5 shrink-0 animate-spin motion-reduce:animate-none"
|
|
55
55
|
/>
|
|
56
|
-
Loading
|
|
56
|
+
Loading inputs…
|
|
57
57
|
</div>
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
@@ -773,14 +773,8 @@ export function useComposer(
|
|
|
773
773
|
// The wire contract requires non-empty text (z.string().min(1)) and the
|
|
774
774
|
// worker rejects whitespace-only text; a file-only message therefore
|
|
775
775
|
// carries a minimal default so the attachments still get delivered.
|
|
776
|
-
const acknowledgedDraft = draftRef.current;
|
|
777
|
-
const sendExtras =
|
|
778
|
-
acknowledgedDraft?.toolsProvided === true &&
|
|
779
|
-
!Object.prototype.hasOwnProperty.call(extras, "tools")
|
|
780
|
-
? { ...extras, tools: acknowledgedDraft.tools }
|
|
781
|
-
: extras;
|
|
782
776
|
pendingClientEventId.current ??= generateClientEventId();
|
|
783
|
-
const input = composeSendInput(sendText, pendingClientEventId.current,
|
|
777
|
+
const input = composeSendInput(sendText, pendingClientEventId.current, extras, {
|
|
784
778
|
...(options.effectiveControl?.controlEtag
|
|
785
779
|
? { controlEtag: options.effectiveControl.controlEtag }
|
|
786
780
|
: {}),
|
|
@@ -1033,13 +1027,10 @@ export function useComposer(
|
|
|
1033
1027
|
const hasDraftContent = useCallback((): boolean => {
|
|
1034
1028
|
const current = draftRef.current;
|
|
1035
1029
|
const extras = resolveSendExtras(sendExtrasRef.current);
|
|
1036
|
-
const toolsProvidedByHost = Object.prototype.hasOwnProperty.call(extras, "tools");
|
|
1037
|
-
const tools = toolsProvidedByHost ? (extras.tools ?? []) : (current?.tools ?? []);
|
|
1038
1030
|
return (
|
|
1039
1031
|
valueRef.current.length > 0 ||
|
|
1040
1032
|
restoredResourcesRef.current.length > 0 ||
|
|
1041
1033
|
(extras.resources?.length ?? 0) > 0 ||
|
|
1042
|
-
tools.length > 0 ||
|
|
1043
1034
|
(current?.sourceTurnId !== null && current?.sourceTurnId !== undefined)
|
|
1044
1035
|
);
|
|
1045
1036
|
}, []);
|
|
@@ -1201,8 +1192,6 @@ function draftPayload(draft: ComposerDraft): SaveComposerDraftRequest {
|
|
|
1201
1192
|
expectedRevision: draft.revision,
|
|
1202
1193
|
text: draft.text,
|
|
1203
1194
|
resources: draft.resources,
|
|
1204
|
-
tools: draft.tools,
|
|
1205
|
-
toolsProvided: draft.toolsProvided,
|
|
1206
1195
|
model: draft.model,
|
|
1207
1196
|
reasoningEffort: draft.reasoningEffort,
|
|
1208
1197
|
};
|
|
@@ -1214,13 +1203,10 @@ function composerDraftPayload(
|
|
|
1214
1203
|
restoredResources: ResourceRef[],
|
|
1215
1204
|
extras: ComposerSendExtras,
|
|
1216
1205
|
): SaveComposerDraftRequest {
|
|
1217
|
-
const toolsProvidedByHost = Object.prototype.hasOwnProperty.call(extras, "tools");
|
|
1218
1206
|
return {
|
|
1219
1207
|
expectedRevision: base.revision,
|
|
1220
1208
|
text,
|
|
1221
1209
|
resources: mergeResources(restoredResources, extras.resources ?? []),
|
|
1222
|
-
tools: toolsProvidedByHost ? (extras.tools ?? []) : base.tools,
|
|
1223
|
-
toolsProvided: toolsProvidedByHost ? true : base.toolsProvided,
|
|
1224
1210
|
model: extras.model ?? base.model,
|
|
1225
1211
|
reasoningEffort: extras.reasoningEffort ?? base.reasoningEffort,
|
|
1226
1212
|
};
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
SessionEvent,
|
|
5
5
|
SessionQueueMutationResponse,
|
|
6
6
|
SessionQueueSnapshot,
|
|
7
|
+
SessionPendingInputPreview,
|
|
7
8
|
SessionTurn,
|
|
8
9
|
} from "@opengeni/sdk";
|
|
9
10
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
@@ -20,6 +21,7 @@ export function isTurnQueueEvent(event: Pick<SessionEvent, "type">): boolean {
|
|
|
20
21
|
event.type.startsWith("turn.") ||
|
|
21
22
|
event.type.startsWith("session.queue.") ||
|
|
22
23
|
event.type.startsWith("session.control.") ||
|
|
24
|
+
event.type.startsWith("system.update.") ||
|
|
23
25
|
event.type.startsWith("workspace.inference.")
|
|
24
26
|
);
|
|
25
27
|
}
|
|
@@ -47,6 +49,10 @@ export type UseTurnQueueResult = {
|
|
|
47
49
|
snapshot: SessionQueueSnapshot | null;
|
|
48
50
|
/** Human/API prompts exactly in server execution order. Never client-sorted. */
|
|
49
51
|
queue: SessionTurn[];
|
|
52
|
+
/** Canonical pending machine inputs. Events only trigger an authoritative refresh. */
|
|
53
|
+
pendingInputs: SessionPendingInputPreview[];
|
|
54
|
+
/** Exact pending members projected to join an already-waiting prompt. */
|
|
55
|
+
pendingInputAttachment: SessionQueueSnapshot["pendingInputAttachment"];
|
|
50
56
|
effectiveControl: EffectiveSessionControl | null;
|
|
51
57
|
/** The latest interrupted attempt has not yet durably proved physical quiescence. */
|
|
52
58
|
stoppingPreviousAttempt: boolean;
|
|
@@ -324,6 +330,8 @@ export function useTurnQueue(
|
|
|
324
330
|
return {
|
|
325
331
|
snapshot: visibleSnapshot,
|
|
326
332
|
queue: visibleSnapshot?.items ?? [],
|
|
333
|
+
pendingInputs: visibleSnapshot?.pendingInputs ?? [],
|
|
334
|
+
pendingInputAttachment: visibleSnapshot?.pendingInputAttachment ?? null,
|
|
327
335
|
effectiveControl: visibleSnapshot?.effectiveControl ?? null,
|
|
328
336
|
stoppingPreviousAttempt: visibleSnapshot?.stoppingPreviousAttempt ?? false,
|
|
329
337
|
loading: identityMatches ? loading : enabled,
|
package/src/index.ts
CHANGED
|
@@ -245,6 +245,8 @@ export type {
|
|
|
245
245
|
AgentMessageItem,
|
|
246
246
|
AuthNeededItem,
|
|
247
247
|
GoalItem,
|
|
248
|
+
MachineInputBatchItem,
|
|
249
|
+
MachineInputMember,
|
|
248
250
|
NoticeItem,
|
|
249
251
|
ReasoningItem,
|
|
250
252
|
SandboxItem,
|
|
@@ -286,6 +288,7 @@ export {
|
|
|
286
288
|
TermBlock,
|
|
287
289
|
Thumbnail,
|
|
288
290
|
TurnSummary,
|
|
291
|
+
BUILT_IN_TURN_SUMMARY_FACET_IDS,
|
|
289
292
|
useLightbox,
|
|
290
293
|
useLightboxOptional,
|
|
291
294
|
} from "./timeline";
|
|
@@ -293,7 +296,13 @@ export type {
|
|
|
293
296
|
ActivityDisclosureProps,
|
|
294
297
|
ActivityRailProps,
|
|
295
298
|
DisclosureChip,
|
|
299
|
+
BuiltInTurnSummaryFacetId,
|
|
296
300
|
TurnOutcome,
|
|
301
|
+
TurnSummaryContext,
|
|
302
|
+
TurnSummaryFacet,
|
|
303
|
+
TurnSummaryFacetConfiguration,
|
|
304
|
+
TurnSummaryFacetResult,
|
|
305
|
+
TurnSummaryOptions,
|
|
297
306
|
TurnSummaryProps,
|
|
298
307
|
} from "./timeline";
|
|
299
308
|
|
package/src/session-ui.ts
CHANGED
|
@@ -9,5 +9,14 @@ export type {
|
|
|
9
9
|
} from "./components/human-input-form";
|
|
10
10
|
export { MessageTimeline, TimelineRow } from "./components/message-timeline";
|
|
11
11
|
export type { MessageTimelineProps } from "./components/message-timeline";
|
|
12
|
+
export { BUILT_IN_TURN_SUMMARY_FACET_IDS } from "./timeline/turn-summary";
|
|
13
|
+
export type {
|
|
14
|
+
BuiltInTurnSummaryFacetId,
|
|
15
|
+
TurnSummaryContext,
|
|
16
|
+
TurnSummaryFacet,
|
|
17
|
+
TurnSummaryFacetConfiguration,
|
|
18
|
+
TurnSummaryFacetResult,
|
|
19
|
+
TurnSummaryOptions,
|
|
20
|
+
} from "./timeline/turn-summary";
|
|
12
21
|
export { QueueSurface } from "./components/queue-surface";
|
|
13
22
|
export type { QueueSurfaceProps } from "./components/queue-surface";
|
package/src/session.ts
CHANGED
package/src/timeline/index.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type {
|
|
|
33
33
|
FleetDecisionItem,
|
|
34
34
|
FleetDecisionScoreItem,
|
|
35
35
|
GoalItem,
|
|
36
|
+
MachineInputBatchItem,
|
|
37
|
+
MachineInputMember,
|
|
36
38
|
MemoryItem,
|
|
37
39
|
NoticeItem,
|
|
38
40
|
ReasoningItem,
|
|
@@ -84,8 +86,17 @@ export { LightboxProvider, useLightbox, useLightboxOptional } from "./screenshot
|
|
|
84
86
|
export { DisclosureDefaultsProvider, useForcedDefaultOpen } from "./disclosure-context";
|
|
85
87
|
|
|
86
88
|
// turn-collapse summary chip
|
|
87
|
-
export { TurnSummary } from "./turn-summary";
|
|
88
|
-
export type {
|
|
89
|
+
export { BUILT_IN_TURN_SUMMARY_FACET_IDS, TurnSummary } from "./turn-summary";
|
|
90
|
+
export type {
|
|
91
|
+
BuiltInTurnSummaryFacetId,
|
|
92
|
+
TurnOutcome,
|
|
93
|
+
TurnSummaryContext,
|
|
94
|
+
TurnSummaryFacet,
|
|
95
|
+
TurnSummaryFacetConfiguration,
|
|
96
|
+
TurnSummaryFacetResult,
|
|
97
|
+
TurnSummaryOptions,
|
|
98
|
+
TurnSummaryProps,
|
|
99
|
+
} from "./turn-summary";
|
|
89
100
|
|
|
90
101
|
// parsers (pure, reusable by custom renderers)
|
|
91
102
|
export {
|
package/src/timeline/parsers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { normalizeMcpOutput, type GitFileDiff } from "@opengeni/sdk";
|
|
2
2
|
import { tryParseJson } from "../lib/format";
|
|
3
3
|
|
|
4
4
|
/* ----------------------------------------------------------------------------
|
|
@@ -288,36 +288,8 @@ export function unwrapMcpOutput(output: unknown): {
|
|
|
288
288
|
text: string;
|
|
289
289
|
isError: boolean;
|
|
290
290
|
} {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (
|
|
294
|
-
output &&
|
|
295
|
-
typeof output === "object" &&
|
|
296
|
-
(output as { type?: unknown }).type === "text" &&
|
|
297
|
-
typeof (output as { text?: unknown }).text === "string"
|
|
298
|
-
) {
|
|
299
|
-
const record = output as { text: string; isError?: unknown };
|
|
300
|
-
return { text: record.text, isError: Boolean(record.isError) };
|
|
301
|
-
}
|
|
302
|
-
if (output && typeof output === "object" && "content" in output) {
|
|
303
|
-
const record = output as { content?: unknown; isError?: unknown };
|
|
304
|
-
const isError = Boolean(record.isError);
|
|
305
|
-
if (Array.isArray(record.content)) {
|
|
306
|
-
const textPart = record.content.find(
|
|
307
|
-
(part): part is { type: string; text: string } =>
|
|
308
|
-
!!part && typeof part === "object" && (part as { type?: unknown }).type === "text",
|
|
309
|
-
);
|
|
310
|
-
return {
|
|
311
|
-
text: textPart ? String(textPart.text) : JSON.stringify(output),
|
|
312
|
-
isError,
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
return { text: JSON.stringify(output), isError };
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
text: typeof output === "string" ? output : output == null ? "" : JSON.stringify(output),
|
|
319
|
-
isError: false,
|
|
320
|
-
};
|
|
291
|
+
const normalized = normalizeMcpOutput(output);
|
|
292
|
+
return { text: normalized.text, isError: normalized.isError };
|
|
321
293
|
}
|
|
322
294
|
|
|
323
295
|
/* --- computer-use screenshot extraction ------------------------------------- */
|