@opengeni/react 3.7.0-canary.0 → 3.7.0-canary.2
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/dist/artifacts.js +24 -5
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-E5DDEFQ4.js → browser-viewer-NTD6UIPR.js} +3 -3
- package/dist/{chunk-W2RGYBCV.js → chunk-24M4YBCL.js} +4 -4
- package/dist/{chunk-R55HFTT3.js → chunk-3IY6UZI6.js} +4306 -4020
- package/dist/chunk-3IY6UZI6.js.map +1 -0
- package/dist/chunk-5PQJOCX5.js +902 -0
- package/dist/chunk-5PQJOCX5.js.map +1 -0
- package/dist/{chunk-FJBOU2TH.js → chunk-MOWYEBCQ.js} +1728 -12
- package/dist/chunk-MOWYEBCQ.js.map +1 -0
- package/dist/chunk-PAJU5KKL.js +331 -0
- package/dist/chunk-PAJU5KKL.js.map +1 -0
- package/dist/{chunk-VTOREEXF.js → chunk-PLYRL5ER.js} +24 -2
- package/dist/{chunk-VTOREEXF.js.map → chunk-PLYRL5ER.js.map} +1 -1
- package/dist/{chunk-DOK4KZF2.js → chunk-WZIMM3GX.js} +1536 -539
- package/dist/chunk-WZIMM3GX.js.map +1 -0
- package/dist/{chunk-YVCHAQZY.js → chunk-ZFOFY5ST.js} +27 -349
- package/dist/chunk-ZFOFY5ST.js.map +1 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +3 -2
- package/dist/components/child-session-link.d.ts +5 -0
- package/dist/components/session-chrome.d.ts +43 -6
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-conversation.d.ts +14 -0
- package/dist/components/timeline-anchor.d.ts +21 -0
- package/dist/composer.js +2 -3
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +716 -914
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +2 -2
- package/dist/lib/format.d.ts +5 -0
- package/dist/machines.js +3 -2
- package/dist/session-ui.d.ts +3 -0
- package/dist/session-ui.js +14 -4
- package/dist/session.js +19 -19
- package/dist/timeline/types.d.ts +3 -1
- package/package.json +2 -2
- package/src/components/artifacts/published-html-artifact-frame.tsx +27 -4
- package/src/components/child-session-link.tsx +27 -0
- package/src/components/composer.tsx +1 -1
- package/src/components/machine-input-display.ts +8 -2
- package/src/components/message-timeline.tsx +336 -198
- package/src/components/session-chrome.tsx +572 -161
- package/src/components/session-commands-panel.tsx +113 -0
- package/src/components/session-conversation.tsx +141 -0
- package/src/components/timeline-anchor.tsx +91 -0
- package/src/conversation-timeline.ts +83 -0
- package/src/hooks/use-session-background-commands.ts +21 -10
- package/src/hooks/use-session-events.ts +36 -14
- package/src/hooks/use-session.ts +23 -2
- package/src/index.ts +4 -0
- package/src/lib/format.ts +32 -0
- package/src/session-ui.ts +3 -0
- package/src/timeline/projection.ts +6 -12
- package/src/timeline/types.ts +4 -0
- package/styles/compiled.css +513 -410
- package/styles/index.css +23 -0
- package/dist/chunk-7EAFGICR.js +0 -302
- package/dist/chunk-7EAFGICR.js.map +0 -1
- package/dist/chunk-C6WDLHBA.js +0 -2590
- package/dist/chunk-C6WDLHBA.js.map +0 -1
- package/dist/chunk-DOK4KZF2.js.map +0 -1
- package/dist/chunk-FJBOU2TH.js.map +0 -1
- package/dist/chunk-R55HFTT3.js.map +0 -1
- package/dist/chunk-YVCHAQZY.js.map +0 -1
- /package/dist/{browser-viewer-E5DDEFQ4.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
- /package/dist/{chunk-W2RGYBCV.js.map → chunk-24M4YBCL.js.map} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ChildSessionLink } from "./child-session-link";
|
|
1
2
|
/**
|
|
2
3
|
* SessionChrome — compact merged session signals above the composer.
|
|
3
4
|
*
|
|
@@ -28,12 +29,25 @@
|
|
|
28
29
|
* Inbox has no product dismiss API; pass `onDismissIncoming` when the host
|
|
29
30
|
* wants a visible action (dev harness may use a local dummy).
|
|
30
31
|
*
|
|
32
|
+
* Queue opens by default when chrome is idle and at least one authoritative
|
|
33
|
+
* prompt is queued. Closing the queue dismisses that session's offer until
|
|
34
|
+
* occupancy drains; a different session on the same chrome instance may
|
|
35
|
+
* offer again. An in-flight optimistic Send never opens the drawer —
|
|
36
|
+
* admission stays a paint-only receipt on the chip so layout does not jump
|
|
37
|
+
* under the pointer.
|
|
38
|
+
*
|
|
31
39
|
* Segment switches keep the panel shell mounted and crossfade content. The
|
|
32
40
|
* shell uses one CSS grid-track transition for deliberate open/close actions;
|
|
33
41
|
* live queue reconciliation never feeds measurements back into layout.
|
|
34
42
|
*/
|
|
35
|
-
import type {
|
|
43
|
+
import type {
|
|
44
|
+
SessionGoal,
|
|
45
|
+
SessionPendingInputPreview,
|
|
46
|
+
SessionStatus,
|
|
47
|
+
SessionTurn,
|
|
48
|
+
} from "@opengeni/sdk";
|
|
36
49
|
import {
|
|
50
|
+
ActivityIcon,
|
|
37
51
|
AudioLinesIcon,
|
|
38
52
|
ArrowDownIcon,
|
|
39
53
|
ArrowUpIcon,
|
|
@@ -46,9 +60,11 @@ import {
|
|
|
46
60
|
PencilIcon,
|
|
47
61
|
PlayIcon,
|
|
48
62
|
Trash2Icon,
|
|
63
|
+
TerminalIcon,
|
|
49
64
|
TriangleAlertIcon,
|
|
50
65
|
XIcon,
|
|
51
|
-
|
|
66
|
+
CornerDownRightIcon,
|
|
67
|
+
TargetIcon,
|
|
52
68
|
} from "lucide-react";
|
|
53
69
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
54
70
|
import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react";
|
|
@@ -61,7 +77,13 @@ import { formatClockTime } from "../lib/format";
|
|
|
61
77
|
import { requestQueueDraftEdit } from "./queue-draft-policy";
|
|
62
78
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
63
79
|
|
|
64
|
-
export type SessionChromeSignalId =
|
|
80
|
+
export type SessionChromeSignalId =
|
|
81
|
+
| "incoming"
|
|
82
|
+
| "steering"
|
|
83
|
+
| "queue"
|
|
84
|
+
| "goal"
|
|
85
|
+
| "agents"
|
|
86
|
+
| "commands";
|
|
65
87
|
|
|
66
88
|
export type SessionChromeSignalTone = "neutral" | "accent" | "waiting" | "running";
|
|
67
89
|
|
|
@@ -72,12 +94,18 @@ export type SessionChromeAgentsSignal = {
|
|
|
72
94
|
};
|
|
73
95
|
|
|
74
96
|
export type SessionChromeProps = {
|
|
97
|
+
compact?: boolean;
|
|
98
|
+
/** Authoritative execution status; omitted by older embedding hosts. */
|
|
99
|
+
sessionStatus?: SessionStatus | undefined;
|
|
75
100
|
queue: UseTurnQueueResult;
|
|
76
101
|
/** Needed for queue edit → composer checkout. Omit with `readOnly`. */
|
|
77
102
|
composer?: ComposerState | undefined;
|
|
78
103
|
goal?: UseGoalResult | null | undefined;
|
|
79
104
|
/** Expanded agents body (host supplies tree / list). */
|
|
80
105
|
agentsPanel?: ReactNode;
|
|
106
|
+
/** Active commands only. The host mounts the body only when opened. */
|
|
107
|
+
commandsPanel?: ReactNode;
|
|
108
|
+
commandsCount?: number | undefined;
|
|
81
109
|
/** Chip summary; when `count > 0` the agents segment appears. */
|
|
82
110
|
agentsSignal?: SessionChromeAgentsSignal | undefined;
|
|
83
111
|
/**
|
|
@@ -85,6 +113,8 @@ export type SessionChromeProps = {
|
|
|
85
113
|
* (and the gallery) may still pass a handler so the action is visible.
|
|
86
114
|
*/
|
|
87
115
|
onDismissIncoming?: ((inputId: string) => void) | undefined;
|
|
116
|
+
/** Open a typed child update's source through the host's normal authorized route. */
|
|
117
|
+
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
88
118
|
readOnly?: boolean | undefined;
|
|
89
119
|
className?: string | undefined;
|
|
90
120
|
/** Controlled active segment; omit for uncontrolled. */
|
|
@@ -97,6 +127,7 @@ type GoalPillState =
|
|
|
97
127
|
| "pursuing"
|
|
98
128
|
| "waiting"
|
|
99
129
|
| "scheduled"
|
|
130
|
+
| "session_failed"
|
|
100
131
|
| "blocked"
|
|
101
132
|
| "held"
|
|
102
133
|
| "paused"
|
|
@@ -111,8 +142,9 @@ type QueuedTurnPresentation = {
|
|
|
111
142
|
const GOAL_LABEL: Record<GoalPillState, string> = {
|
|
112
143
|
pursuing: "Pursuing",
|
|
113
144
|
waiting: "Waiting",
|
|
114
|
-
scheduled: "
|
|
145
|
+
scheduled: "Waiting",
|
|
115
146
|
blocked: "Blocked",
|
|
147
|
+
session_failed: "Blocked by session failure",
|
|
116
148
|
held: "Held",
|
|
117
149
|
paused: "Paused",
|
|
118
150
|
invariant_broken: "Needs attention",
|
|
@@ -167,15 +199,18 @@ export function sessionChromeGoalPillExplanation(
|
|
|
167
199
|
record: GoalPillRecord | null | undefined,
|
|
168
200
|
): string | null {
|
|
169
201
|
const continuation = record?.continuation ?? null;
|
|
202
|
+
if (state === "session_failed") {
|
|
203
|
+
return "Resolve the session failure, then use Continue or send a message to continue this active goal.";
|
|
204
|
+
}
|
|
170
205
|
if (state === "paused") {
|
|
171
206
|
return record?.pausedReason
|
|
172
207
|
? (GOAL_PAUSED_REASON_EXPLANATION[record.pausedReason] ?? null)
|
|
173
208
|
: null;
|
|
174
209
|
}
|
|
175
|
-
if (state === "scheduled"
|
|
176
|
-
return continuation
|
|
177
|
-
? `
|
|
178
|
-
: "
|
|
210
|
+
if (state === "scheduled") {
|
|
211
|
+
return continuation?.nextAttemptAt
|
|
212
|
+
? `Continues at ${formatClockTime(continuation.nextAttemptAt)}.`
|
|
213
|
+
: "Waiting to continue automatically.";
|
|
179
214
|
}
|
|
180
215
|
if (state === "held" && continuation?.reason === "held_for_input") {
|
|
181
216
|
const reason = continuation.holdReason?.trim();
|
|
@@ -203,6 +238,82 @@ function isSteeringTurn(turn: SessionTurn): boolean {
|
|
|
203
238
|
return turn.metadata.delivery === "steer";
|
|
204
239
|
}
|
|
205
240
|
|
|
241
|
+
function isAuthoritativeQueuedTurn(
|
|
242
|
+
turn: SessionTurn,
|
|
243
|
+
mutationFor: UseTurnQueueResult["mutationFor"],
|
|
244
|
+
): boolean {
|
|
245
|
+
return !isSteeringTurn(turn) && mutationFor(turn.id) !== "steer";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function countAuthoritativeQueuedTurns(
|
|
249
|
+
turns: readonly SessionTurn[],
|
|
250
|
+
mutationFor: UseTurnQueueResult["mutationFor"],
|
|
251
|
+
): number {
|
|
252
|
+
return turns.filter((turn) => isAuthoritativeQueuedTurn(turn, mutationFor)).length;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function isOptimisticQueuedMessage(
|
|
256
|
+
message: ComposerOptimisticMessage,
|
|
257
|
+
queuedTurnIds: ReadonlySet<string>,
|
|
258
|
+
snapshot: UseTurnQueueResult["snapshot"],
|
|
259
|
+
): boolean {
|
|
260
|
+
return (
|
|
261
|
+
message.delivery === "send" &&
|
|
262
|
+
message.destination === "queue" &&
|
|
263
|
+
(!message.turnId || !queuedTurnIds.has(message.turnId)) &&
|
|
264
|
+
!(
|
|
265
|
+
message.turnId &&
|
|
266
|
+
message.appliedQueueVersion !== null &&
|
|
267
|
+
message.appliedQueueVersion !== undefined &&
|
|
268
|
+
snapshot &&
|
|
269
|
+
snapshot.version >= message.appliedQueueVersion
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function countOptimisticQueuedMessages(
|
|
275
|
+
messages: readonly ComposerOptimisticMessage[] | undefined,
|
|
276
|
+
queuedTurnIds: ReadonlySet<string>,
|
|
277
|
+
snapshot: UseTurnQueueResult["snapshot"],
|
|
278
|
+
): number {
|
|
279
|
+
return (messages ?? []).filter((message) =>
|
|
280
|
+
isOptimisticQueuedMessage(message, queuedTurnIds, snapshot),
|
|
281
|
+
).length;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* First uncontrolled segment. An existing authoritative queue opens itself
|
|
286
|
+
* when the host did not pick another default; optimistic-only occupancy
|
|
287
|
+
* stays collapsed so a live Send does not shove a drawer under the pointer.
|
|
288
|
+
*/
|
|
289
|
+
export function sessionChromeInitialActive(input: {
|
|
290
|
+
defaultActive: SessionChromeSignalId | null;
|
|
291
|
+
authoritativeQueuedCount: number;
|
|
292
|
+
}): SessionChromeSignalId | null {
|
|
293
|
+
if (input.defaultActive != null) return input.defaultActive;
|
|
294
|
+
return input.authoritativeQueuedCount >= 1 ? "queue" : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Whether idle chrome should offer the queue panel. A dismissed session
|
|
299
|
+
* stays collapsed until occupancy drains. Controlled hosts own this.
|
|
300
|
+
*/
|
|
301
|
+
export function sessionChromeShouldOfferQueue(input: {
|
|
302
|
+
controlled: boolean;
|
|
303
|
+
active: SessionChromeSignalId | null;
|
|
304
|
+
activityOpen: boolean;
|
|
305
|
+
authoritativeQueuedCount: number;
|
|
306
|
+
suppressed: boolean;
|
|
307
|
+
}): boolean {
|
|
308
|
+
return (
|
|
309
|
+
!input.controlled &&
|
|
310
|
+
input.active === null &&
|
|
311
|
+
!input.activityOpen &&
|
|
312
|
+
input.authoritativeQueuedCount >= 1 &&
|
|
313
|
+
!input.suppressed
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
206
317
|
function objectValue(value: unknown): Record<string, unknown> | null {
|
|
207
318
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
208
319
|
? (value as Record<string, unknown>)
|
|
@@ -213,9 +324,11 @@ function objectValue(value: unknown): Record<string, unknown> | null {
|
|
|
213
324
|
export function sessionChromeGoalPillState(
|
|
214
325
|
goalStatus: "active" | "paused" | "completed",
|
|
215
326
|
continuation: SessionGoal["continuation"] | null | undefined,
|
|
327
|
+
sessionStatus?: SessionStatus,
|
|
216
328
|
): GoalPillState {
|
|
217
329
|
if (goalStatus === "completed") return "completed";
|
|
218
330
|
if (goalStatus === "paused") return "paused";
|
|
331
|
+
if (sessionStatus === "failed") return "session_failed";
|
|
219
332
|
if (!continuation) return "invariant_broken";
|
|
220
333
|
if (continuation.state === "running") {
|
|
221
334
|
return continuation.reason === "goal_turn_running"
|
|
@@ -228,6 +341,7 @@ export function sessionChromeGoalPillState(
|
|
|
228
341
|
// next evaluation at `nextAttemptAt`) is an ordinary scheduled state.
|
|
229
342
|
if (continuation.state === "scheduled") return "scheduled";
|
|
230
343
|
if (continuation.state === "blocked") {
|
|
344
|
+
if (continuation.reason === "human_turn_running") return "waiting";
|
|
231
345
|
// `held_for_input` is the agent's own wait_for_input hold (waiting for child
|
|
232
346
|
// results / external input until a deadline); it shares the Held pill.
|
|
233
347
|
return continuation.reason === "workstream_paused" || continuation.reason === "held_for_input"
|
|
@@ -288,6 +402,12 @@ function pendingKindLabel(kind: SessionPendingInputPreview["kind"]): string {
|
|
|
288
402
|
return "Goal wake";
|
|
289
403
|
case "agent_message":
|
|
290
404
|
return "Update";
|
|
405
|
+
case "background_command_result":
|
|
406
|
+
return "Command result";
|
|
407
|
+
case "session_wait_timeout":
|
|
408
|
+
return "Wait ended";
|
|
409
|
+
case "media_generation_result":
|
|
410
|
+
return "Video result";
|
|
291
411
|
default:
|
|
292
412
|
return "Incoming";
|
|
293
413
|
}
|
|
@@ -307,18 +427,26 @@ function toneClass(tone: SessionChromeSignalTone, selected: boolean): string {
|
|
|
307
427
|
}
|
|
308
428
|
|
|
309
429
|
export function SessionChrome({
|
|
430
|
+
compact = false,
|
|
431
|
+
sessionStatus,
|
|
310
432
|
queue,
|
|
311
433
|
composer,
|
|
312
434
|
goal,
|
|
313
435
|
agentsPanel,
|
|
436
|
+
commandsPanel,
|
|
437
|
+
commandsCount = 0,
|
|
314
438
|
agentsSignal,
|
|
315
439
|
onDismissIncoming,
|
|
440
|
+
onOpenSession,
|
|
316
441
|
readOnly = false,
|
|
317
442
|
className,
|
|
318
443
|
active: activeControlled,
|
|
319
444
|
defaultActive = null,
|
|
320
445
|
onActiveChange,
|
|
321
446
|
}: SessionChromeProps) {
|
|
447
|
+
const [activityRequested, setActivityOpen] = useState(
|
|
448
|
+
Boolean(compact && defaultActive && ["incoming", "agents", "commands"].includes(defaultActive)),
|
|
449
|
+
);
|
|
322
450
|
const reactId = useId();
|
|
323
451
|
const panelId = `og-session-chrome-panel-${reactId}`;
|
|
324
452
|
const reduceMotion = useReducedMotion();
|
|
@@ -327,24 +455,14 @@ export function SessionChrome({
|
|
|
327
455
|
const turns = queue.queue;
|
|
328
456
|
const queueMutationFor = queue.mutationFor;
|
|
329
457
|
const queuedTurns = useMemo(
|
|
330
|
-
() => turns.filter((turn) =>
|
|
458
|
+
() => turns.filter((turn) => isAuthoritativeQueuedTurn(turn, queueMutationFor)),
|
|
331
459
|
[queueMutationFor, turns],
|
|
332
460
|
);
|
|
333
461
|
const queuedTurnIds = useMemo(() => new Set(queuedTurns.map((turn) => turn.id)), [queuedTurns]);
|
|
334
462
|
const optimisticQueued = useMemo(
|
|
335
463
|
() =>
|
|
336
|
-
(composer?.optimisticMessages ?? []).filter(
|
|
337
|
-
(message)
|
|
338
|
-
message.delivery === "send" &&
|
|
339
|
-
message.destination === "queue" &&
|
|
340
|
-
(!message.turnId || !queuedTurnIds.has(message.turnId)) &&
|
|
341
|
-
!(
|
|
342
|
-
message.turnId &&
|
|
343
|
-
message.appliedQueueVersion !== null &&
|
|
344
|
-
message.appliedQueueVersion !== undefined &&
|
|
345
|
-
queue.snapshot &&
|
|
346
|
-
queue.snapshot.version >= message.appliedQueueVersion
|
|
347
|
-
),
|
|
464
|
+
(composer?.optimisticMessages ?? []).filter((message) =>
|
|
465
|
+
isOptimisticQueuedMessage(message, queuedTurnIds, queue.snapshot),
|
|
348
466
|
),
|
|
349
467
|
[composer?.optimisticMessages, queue.snapshot, queuedTurnIds],
|
|
350
468
|
);
|
|
@@ -358,14 +476,51 @@ export function SessionChrome({
|
|
|
358
476
|
const stopping = stoppingKind !== null;
|
|
359
477
|
const canMutateQueue = !readOnly && composer !== undefined;
|
|
360
478
|
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
record
|
|
364
|
-
|
|
365
|
-
|
|
479
|
+
const elapsed = useLiveElapsed(record?.createdAt, Boolean(record));
|
|
480
|
+
const goalState = record
|
|
481
|
+
? sessionChromeGoalPillState(record.status, record.continuation, sessionStatus)
|
|
482
|
+
: null;
|
|
483
|
+
|
|
484
|
+
const initialAuthoritativeQueuedCount = countAuthoritativeQueuedTurns(
|
|
485
|
+
queue.queue,
|
|
486
|
+
queue.mutationFor,
|
|
366
487
|
);
|
|
367
|
-
const
|
|
488
|
+
const [activeUncontrolled, setActiveUncontrolled] = useState<SessionChromeSignalId | null>(() =>
|
|
489
|
+
sessionChromeInitialActive({
|
|
490
|
+
defaultActive,
|
|
491
|
+
authoritativeQueuedCount: initialAuthoritativeQueuedCount,
|
|
492
|
+
}),
|
|
493
|
+
);
|
|
494
|
+
const active = activeControlled !== undefined ? activeControlled : activeUncontrolled;
|
|
495
|
+
const activityOpen =
|
|
496
|
+
activityRequested ||
|
|
497
|
+
Boolean(compact && active && ["incoming", "agents", "commands"].includes(active));
|
|
498
|
+
const activityVisibleRef = useRef(activityOpen);
|
|
499
|
+
activityVisibleRef.current = activityOpen;
|
|
500
|
+
// Two independent suppressions, both occupancy-scoped:
|
|
501
|
+
// - admission: a live Send while idle must not open a drawer under the pointer
|
|
502
|
+
// - dismiss: closing the queue remembers that session until occupancy drains
|
|
503
|
+
const queueOfferAdmissionSuppressedRef = useRef(
|
|
504
|
+
defaultActive == null &&
|
|
505
|
+
initialAuthoritativeQueuedCount === 0 &&
|
|
506
|
+
countOptimisticQueuedMessages(composer?.optimisticMessages, queuedTurnIds, queue.snapshot) >
|
|
507
|
+
0,
|
|
508
|
+
);
|
|
509
|
+
const queueOfferDismissedSessionIdsRef = useRef<Set<string>>(new Set());
|
|
510
|
+
const occupancySessionId = queuedTurns[0]?.sessionId ?? null;
|
|
511
|
+
const setActive = (next: SessionChromeSignalId | null) => {
|
|
512
|
+
if (active === "queue" && next === null) {
|
|
513
|
+
if (occupancySessionId) {
|
|
514
|
+
queueOfferDismissedSessionIdsRef.current.add(occupancySessionId);
|
|
515
|
+
} else {
|
|
516
|
+
queueOfferAdmissionSuppressedRef.current = true;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (activeControlled === undefined) setActiveUncontrolled(next);
|
|
520
|
+
onActiveChange?.(next);
|
|
521
|
+
};
|
|
368
522
|
|
|
523
|
+
const hasCommandsPanel = commandsPanel != null;
|
|
369
524
|
const signals = useMemo(() => {
|
|
370
525
|
const rows: Array<{
|
|
371
526
|
id: SessionChromeSignalId;
|
|
@@ -437,6 +592,7 @@ export function SessionChrome({
|
|
|
437
592
|
const waiting =
|
|
438
593
|
goalState === "waiting" ||
|
|
439
594
|
goalState === "blocked" ||
|
|
595
|
+
goalState === "session_failed" ||
|
|
440
596
|
goalState === "held" ||
|
|
441
597
|
goalState === "paused";
|
|
442
598
|
const explanation = sessionChromeGoalPillExplanation(goalState, record);
|
|
@@ -450,16 +606,7 @@ export function SessionChrome({
|
|
|
450
606
|
: goalState === "pursuing" || goalState === "scheduled"
|
|
451
607
|
? "accent"
|
|
452
608
|
: "neutral",
|
|
453
|
-
icon:
|
|
454
|
-
goalState === "blocked" || goalState === "invariant_broken" ? (
|
|
455
|
-
<TriangleAlertIcon className="size-3" />
|
|
456
|
-
) : goalState === "waiting" ? (
|
|
457
|
-
<Loader2Icon className="size-3 animate-og-spin" />
|
|
458
|
-
) : goalState === "paused" || goalState === "held" ? (
|
|
459
|
-
<PauseIcon className="size-3" />
|
|
460
|
-
) : (
|
|
461
|
-
<ZapIcon className="size-3" />
|
|
462
|
-
),
|
|
609
|
+
icon: <TargetIcon className="size-3" />,
|
|
463
610
|
});
|
|
464
611
|
}
|
|
465
612
|
if (agentsSignal && agentsSignal.count > 0) {
|
|
@@ -472,8 +619,22 @@ export function SessionChrome({
|
|
|
472
619
|
icon: <BotIcon className="size-3" />,
|
|
473
620
|
});
|
|
474
621
|
}
|
|
622
|
+
if (commandsCount > 0 || (active === "commands" && hasCommandsPanel)) {
|
|
623
|
+
rows.push({
|
|
624
|
+
id: "commands",
|
|
625
|
+
label:
|
|
626
|
+
commandsCount > 0
|
|
627
|
+
? `${commandsCount} command${commandsCount === 1 ? "" : "s"}`
|
|
628
|
+
: "Commands",
|
|
629
|
+
tone: commandsCount > 0 ? "running" : "neutral",
|
|
630
|
+
icon: <TerminalIcon className="size-3" />,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
475
633
|
return rows;
|
|
476
634
|
}, [
|
|
635
|
+
commandsCount,
|
|
636
|
+
hasCommandsPanel,
|
|
637
|
+
active,
|
|
477
638
|
agentsSignal,
|
|
478
639
|
elapsed,
|
|
479
640
|
goalState,
|
|
@@ -485,14 +646,6 @@ export function SessionChrome({
|
|
|
485
646
|
record,
|
|
486
647
|
]);
|
|
487
648
|
|
|
488
|
-
const [activeUncontrolled, setActiveUncontrolled] = useState<SessionChromeSignalId | null>(
|
|
489
|
-
defaultActive,
|
|
490
|
-
);
|
|
491
|
-
const active = activeControlled !== undefined ? activeControlled : activeUncontrolled;
|
|
492
|
-
const setActive = (next: SessionChromeSignalId | null) => {
|
|
493
|
-
if (activeControlled === undefined) setActiveUncontrolled(next);
|
|
494
|
-
onActiveChange?.(next);
|
|
495
|
-
};
|
|
496
649
|
const optimisticQueueKeys = optimisticQueued.map((message) => message.clientEventId).join(",");
|
|
497
650
|
const previousOptimisticQueueKeys = useRef(optimisticQueueKeys);
|
|
498
651
|
const [queueArrivalNonce, setQueueArrivalNonce] = useState(0);
|
|
@@ -505,7 +658,45 @@ export function SessionChrome({
|
|
|
505
658
|
// the pointer. A stable, paint-only receipt on the queue chip communicates
|
|
506
659
|
// destination without participating in layout.
|
|
507
660
|
setQueueArrivalNonce((current) => current + 1);
|
|
508
|
-
|
|
661
|
+
if (activeControlled === undefined && active === null) {
|
|
662
|
+
queueOfferAdmissionSuppressedRef.current = true;
|
|
663
|
+
}
|
|
664
|
+
}, [active, activeControlled, optimisticQueueKeys, optimisticQueued]);
|
|
665
|
+
const queueOccupied = queuedTurns.length > 0 || optimisticQueued.length > 0;
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
if (queueOccupied) return;
|
|
668
|
+
// A later wave may offer again. A prior session's dismiss must not stick
|
|
669
|
+
// after this instance has actually gone empty.
|
|
670
|
+
queueOfferAdmissionSuppressedRef.current = false;
|
|
671
|
+
queueOfferDismissedSessionIdsRef.current.clear();
|
|
672
|
+
}, [queueOccupied]);
|
|
673
|
+
const queueOfferSuppressed =
|
|
674
|
+
queueOfferAdmissionSuppressedRef.current ||
|
|
675
|
+
(occupancySessionId != null &&
|
|
676
|
+
queueOfferDismissedSessionIdsRef.current.has(occupancySessionId));
|
|
677
|
+
useEffect(() => {
|
|
678
|
+
if (
|
|
679
|
+
!sessionChromeShouldOfferQueue({
|
|
680
|
+
controlled: activeControlled !== undefined,
|
|
681
|
+
active,
|
|
682
|
+
activityOpen,
|
|
683
|
+
authoritativeQueuedCount: queuedTurns.length,
|
|
684
|
+
suppressed: queueOfferSuppressed,
|
|
685
|
+
})
|
|
686
|
+
) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (activeControlled === undefined) setActiveUncontrolled("queue");
|
|
690
|
+
onActiveChange?.("queue");
|
|
691
|
+
}, [
|
|
692
|
+
active,
|
|
693
|
+
activeControlled,
|
|
694
|
+
activityOpen,
|
|
695
|
+
occupancySessionId,
|
|
696
|
+
onActiveChange,
|
|
697
|
+
queueOfferSuppressed,
|
|
698
|
+
queuedTurns.length,
|
|
699
|
+
]);
|
|
509
700
|
const [replaceDraftFor, setReplaceDraftFor] = useState<string | null>(null);
|
|
510
701
|
|
|
511
702
|
const chipRefs = useRef<Partial<Record<SessionChromeSignalId, HTMLButtonElement | null>>>({});
|
|
@@ -513,6 +704,13 @@ export function SessionChrome({
|
|
|
513
704
|
const [pill, setPill] = useState({ left: 0, top: 0, width: 0, height: 0, opacity: 0 });
|
|
514
705
|
|
|
515
706
|
const signalIds = signals.map((signal) => signal.id).join(",");
|
|
707
|
+
useEffect(() => {
|
|
708
|
+
if (
|
|
709
|
+
compact &&
|
|
710
|
+
!signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id))
|
|
711
|
+
)
|
|
712
|
+
setActivityOpen(false);
|
|
713
|
+
}, [compact, signals]);
|
|
516
714
|
useEffect(() => {
|
|
517
715
|
if (active && !signalIds.split(",").includes(active)) {
|
|
518
716
|
if (activeControlled === undefined) setActiveUncontrolled(null);
|
|
@@ -535,7 +733,10 @@ export function SessionChrome({
|
|
|
535
733
|
return;
|
|
536
734
|
}
|
|
537
735
|
const chip = chipRefs.current[active];
|
|
538
|
-
if (!chip)
|
|
736
|
+
if (!chip) {
|
|
737
|
+
setPill((prev) => ({ ...prev, opacity: 0 }));
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
539
740
|
// Measure against the chip's own box so a wrapped multi-row rail never
|
|
540
741
|
// stretches the highlight into a tall stripe across every signal.
|
|
541
742
|
const railBox = rail.getBoundingClientRect();
|
|
@@ -572,7 +773,11 @@ export function SessionChrome({
|
|
|
572
773
|
|
|
573
774
|
const panelBody =
|
|
574
775
|
active === "incoming" ? (
|
|
575
|
-
<IncomingPanel
|
|
776
|
+
<IncomingPanel
|
|
777
|
+
inputs={incoming}
|
|
778
|
+
onDismiss={onDismissIncoming}
|
|
779
|
+
onOpenSession={onOpenSession}
|
|
780
|
+
/>
|
|
576
781
|
) : active === "queue" ? (
|
|
577
782
|
<QueuePanel
|
|
578
783
|
turns={queuedTurns}
|
|
@@ -645,6 +850,8 @@ export function SessionChrome({
|
|
|
645
850
|
/>
|
|
646
851
|
) : active === "goal" && record && goalState && goal ? (
|
|
647
852
|
<GoalPanel goal={goal} state={goalState} elapsed={elapsed} readOnly={readOnly} />
|
|
853
|
+
) : active === "commands" ? (
|
|
854
|
+
<div data-og-session-chrome-panel="commands">{commandsPanel}</div>
|
|
648
855
|
) : active === "agents" ? (
|
|
649
856
|
<div data-og-session-chrome-panel="agents">
|
|
650
857
|
{agentsPanel ?? <p className="text-og-xs text-og-fg-muted">No agent details.</p>}
|
|
@@ -666,15 +873,21 @@ export function SessionChrome({
|
|
|
666
873
|
)}
|
|
667
874
|
style={{
|
|
668
875
|
borderRadius: "var(--_og-session-chrome-radius)",
|
|
669
|
-
background:
|
|
670
|
-
? "
|
|
671
|
-
:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
876
|
+
background: compact
|
|
877
|
+
? "transparent"
|
|
878
|
+
: open
|
|
879
|
+
? "var(--_og-session-chrome-surface-open)"
|
|
880
|
+
: "var(--_og-session-chrome-surface)",
|
|
881
|
+
borderColor: compact
|
|
882
|
+
? "transparent"
|
|
883
|
+
: open
|
|
884
|
+
? "var(--_og-session-chrome-border-open)"
|
|
885
|
+
: "var(--_og-session-chrome-border)",
|
|
886
|
+
boxShadow: compact
|
|
887
|
+
? "none"
|
|
888
|
+
: open
|
|
889
|
+
? "var(--og-session-chrome-shadow-open)"
|
|
890
|
+
: "var(--og-session-chrome-shadow)",
|
|
678
891
|
transitionDuration: "var(--og-session-chrome-duration)",
|
|
679
892
|
transitionTimingFunction: "var(--_og-session-chrome-ease)",
|
|
680
893
|
}}
|
|
@@ -690,9 +903,9 @@ export function SessionChrome({
|
|
|
690
903
|
>
|
|
691
904
|
<div
|
|
692
905
|
ref={railRef}
|
|
693
|
-
className="relative flex flex-
|
|
906
|
+
className={cn("relative flex items-center", compact ? "flex-nowrap" : "flex-wrap")}
|
|
694
907
|
style={{
|
|
695
|
-
gap: "var(--og-session-chrome-chip-gap)",
|
|
908
|
+
gap: compact ? "10px" : "var(--og-session-chrome-chip-gap)",
|
|
696
909
|
}}
|
|
697
910
|
>
|
|
698
911
|
<motion.div
|
|
@@ -712,71 +925,193 @@ export function SessionChrome({
|
|
|
712
925
|
}}
|
|
713
926
|
transition={{ duration: shellDuration, ease }}
|
|
714
927
|
/>
|
|
715
|
-
{signals
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
928
|
+
{signals
|
|
929
|
+
.filter(
|
|
930
|
+
(signal) => !compact || !["incoming", "agents", "commands"].includes(signal.id),
|
|
931
|
+
)
|
|
932
|
+
.map((signal) => {
|
|
933
|
+
const selected = active === signal.id;
|
|
934
|
+
return (
|
|
935
|
+
<div
|
|
936
|
+
key={signal.id}
|
|
937
|
+
role="group"
|
|
938
|
+
aria-label={
|
|
939
|
+
signal.id === "queue"
|
|
940
|
+
? "Queued messages and actions"
|
|
941
|
+
: signal.id === "goal"
|
|
942
|
+
? "Goal and actions"
|
|
943
|
+
: signal.label
|
|
944
|
+
}
|
|
945
|
+
className={cn(
|
|
946
|
+
"group/signal relative z-[1] inline-flex min-w-0 max-w-full items-center rounded-og-md",
|
|
947
|
+
compact && "bg-og-surface-2/60 px-0.5",
|
|
948
|
+
compact && selected && "bg-og-surface-3",
|
|
949
|
+
)}
|
|
950
|
+
>
|
|
951
|
+
<button
|
|
952
|
+
type="button"
|
|
953
|
+
ref={(node) => {
|
|
954
|
+
chipRefs.current[signal.id] = node;
|
|
955
|
+
}}
|
|
956
|
+
aria-expanded={selected}
|
|
957
|
+
aria-controls={panelId}
|
|
958
|
+
aria-label={
|
|
959
|
+
compact && signal.id === "goal"
|
|
960
|
+
? `Goal · ${goalState === "pursuing" ? "Running" : signal.label}`
|
|
961
|
+
: selected
|
|
962
|
+
? `Close ${signal.label}`
|
|
963
|
+
: undefined
|
|
964
|
+
}
|
|
965
|
+
data-testid={`session-chrome-${signal.id}`}
|
|
966
|
+
data-og-session-chrome-signal={signal.id}
|
|
967
|
+
title={signal.title ?? (compact ? signal.label : undefined)}
|
|
968
|
+
onClick={() => {
|
|
969
|
+
if (compact) setActivityOpen(false);
|
|
970
|
+
setActive(selected ? null : signal.id);
|
|
971
|
+
}}
|
|
972
|
+
className={cn(
|
|
973
|
+
"group relative z-[1] inline-flex min-w-0 min-h-[var(--og-session-chrome-chip-min-height)] max-w-full items-center gap-1 rounded-og-md py-1 text-left text-og-xs outline-hidden",
|
|
974
|
+
// Coarse pointers keep a 44px target (session-pins acceptance).
|
|
975
|
+
"pointer-coarse:min-h-11",
|
|
976
|
+
"transition-colors duration-150 motion-reduce:transition-none",
|
|
977
|
+
"hover:text-og-fg focus-visible:bg-og-surface-3/50",
|
|
978
|
+
selected ? "text-og-fg" : "text-og-fg-muted",
|
|
979
|
+
)}
|
|
980
|
+
style={{
|
|
981
|
+
paddingInline: "var(--og-session-chrome-chip-pad-x)",
|
|
982
|
+
}}
|
|
983
|
+
>
|
|
984
|
+
{signal.id === "queue" && queueArrivalNonce > 0 && !reduceMotion ? (
|
|
985
|
+
<motion.span
|
|
986
|
+
key={queueArrivalNonce}
|
|
987
|
+
aria-hidden="true"
|
|
988
|
+
data-testid="session-chrome-queue-arrival"
|
|
989
|
+
className="pointer-events-none absolute inset-0 rounded-og-md bg-og-accent-soft"
|
|
990
|
+
initial={{ opacity: 0 }}
|
|
991
|
+
animate={{ opacity: [0, 0.38, 0] }}
|
|
992
|
+
transition={{ duration: 0.72, times: [0, 0.18, 1], ease }}
|
|
993
|
+
/>
|
|
994
|
+
) : null}
|
|
995
|
+
<span className={cn("shrink-0", toneClass(signal.tone, selected))}>
|
|
996
|
+
{signal.icon}
|
|
762
997
|
</span>
|
|
763
|
-
<span
|
|
764
|
-
{
|
|
998
|
+
<span
|
|
999
|
+
className={cn(
|
|
1000
|
+
"font-medium text-og-fg",
|
|
1001
|
+
compact ? "min-w-0 truncate" : "shrink-0",
|
|
1002
|
+
)}
|
|
1003
|
+
>
|
|
1004
|
+
{compact && signal.id === "goal" ? "Goal · " : null}
|
|
1005
|
+
{compact && signal.id === "goal"
|
|
1006
|
+
? goalState === "pursuing"
|
|
1007
|
+
? "Running"
|
|
1008
|
+
: signal.label
|
|
1009
|
+
: compact && signal.id === "queue"
|
|
1010
|
+
? `${queuedTurns.length + optimisticQueued.length} queued`
|
|
1011
|
+
: signal.label}
|
|
765
1012
|
</span>
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1013
|
+
{signal.detail && !compact ? (
|
|
1014
|
+
<>
|
|
1015
|
+
<span aria-hidden className="shrink-0 text-og-fg-subtle/60">
|
|
1016
|
+
·
|
|
1017
|
+
</span>
|
|
1018
|
+
<span className="min-w-0 max-w-[8.5rem] truncate text-og-fg sm:max-w-[12rem]">
|
|
1019
|
+
{signal.detail}
|
|
1020
|
+
</span>
|
|
1021
|
+
</>
|
|
1022
|
+
) : null}
|
|
1023
|
+
{selected && !compact ? (
|
|
1024
|
+
<span
|
|
1025
|
+
data-testid="session-chrome-close"
|
|
1026
|
+
className="ml-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-og-sm text-og-fg-subtle transition-colors group-hover:text-og-fg pointer-coarse:size-5"
|
|
1027
|
+
aria-hidden
|
|
1028
|
+
>
|
|
1029
|
+
<XIcon className="size-3" />
|
|
1030
|
+
</span>
|
|
1031
|
+
) : null}
|
|
1032
|
+
</button>
|
|
1033
|
+
{signal.id === "goal" && goal && record && !readOnly ? (
|
|
1034
|
+
<div className={cn("flex shrink-0 items-center pr-1", compact && "pl-0.5")}>
|
|
1035
|
+
{record.status === "active" || record.status === "paused" ? (
|
|
1036
|
+
<IconAction
|
|
1037
|
+
label={record.status === "paused" ? "Resume goal" : "Pause goal"}
|
|
1038
|
+
tip={record.status === "paused" ? "Resume goal" : "Pause goal"}
|
|
1039
|
+
disabled={goal.updating}
|
|
1040
|
+
onClick={() =>
|
|
1041
|
+
void (record.status === "paused"
|
|
1042
|
+
? goal.resume()
|
|
1043
|
+
: goal.pause("Paused from session chrome"))
|
|
1044
|
+
}
|
|
1045
|
+
>
|
|
1046
|
+
{goal.updating ? (
|
|
1047
|
+
<Loader2Icon className="size-3 animate-og-spin" />
|
|
1048
|
+
) : record.status === "paused" ? (
|
|
1049
|
+
<PlayIcon className="size-3" />
|
|
1050
|
+
) : (
|
|
1051
|
+
<PauseIcon className="size-3" />
|
|
1052
|
+
)}
|
|
1053
|
+
</IconAction>
|
|
1054
|
+
) : null}
|
|
1055
|
+
<IconAction
|
|
1056
|
+
label="Clear goal"
|
|
1057
|
+
tip="Clear goal"
|
|
1058
|
+
danger
|
|
1059
|
+
disabled={goal.updating}
|
|
1060
|
+
onClick={() => void goal.deleteGoal()}
|
|
1061
|
+
>
|
|
1062
|
+
<Trash2Icon className="size-3" />
|
|
1063
|
+
</IconAction>
|
|
1064
|
+
</div>
|
|
1065
|
+
) : signal.id === "queue" && canMutateQueue && queuedTurns[0] ? (
|
|
1066
|
+
<div className={cn("flex shrink-0 items-center pr-1", compact && "pl-0.5")}>
|
|
1067
|
+
<IconAction
|
|
1068
|
+
label="Steer first queued message"
|
|
1069
|
+
text="Steer"
|
|
1070
|
+
tip={QUEUE_STEER_TIP}
|
|
1071
|
+
disabled={
|
|
1072
|
+
queue.mutating || Boolean(queue.mutationFor(queuedTurns[0].id))
|
|
1073
|
+
}
|
|
1074
|
+
onClick={() => void queue.steerTurn(queuedTurns[0]!.id)}
|
|
1075
|
+
>
|
|
1076
|
+
<CornerDownRightIcon className="size-3" />
|
|
1077
|
+
</IconAction>
|
|
1078
|
+
</div>
|
|
1079
|
+
) : null}
|
|
1080
|
+
</div>
|
|
1081
|
+
);
|
|
1082
|
+
})}
|
|
1083
|
+
{compact &&
|
|
1084
|
+
signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id)) ? (
|
|
1085
|
+
<button
|
|
1086
|
+
type="button"
|
|
1087
|
+
aria-label="Session activity"
|
|
1088
|
+
title={activityOpen ? "Close activity" : "Inbox, agents and commands"}
|
|
1089
|
+
aria-controls={panelId}
|
|
1090
|
+
aria-expanded={activityOpen}
|
|
1091
|
+
onClick={() => {
|
|
1092
|
+
setActivityOpen(!activityOpen);
|
|
1093
|
+
setActive(
|
|
1094
|
+
activityOpen
|
|
1095
|
+
? null
|
|
1096
|
+
: (signals.find((signal) =>
|
|
1097
|
+
["incoming", "agents", "commands"].includes(signal.id),
|
|
1098
|
+
)?.id ?? null),
|
|
1099
|
+
);
|
|
1100
|
+
}}
|
|
1101
|
+
className={cn(
|
|
1102
|
+
"ml-auto inline-flex min-h-8 shrink-0 items-center justify-center gap-1.5 rounded-og-md px-2 text-og-xs transition-colors outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/40 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
|
|
1103
|
+
activityOpen
|
|
1104
|
+
? "bg-og-surface-3 text-og-fg"
|
|
1105
|
+
: "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg",
|
|
1106
|
+
)}
|
|
1107
|
+
>
|
|
1108
|
+
<ActivityIcon className="size-3.5" />
|
|
1109
|
+
<span className="sr-only">Activity</span>
|
|
1110
|
+
{incoming.length > 0 ? (
|
|
1111
|
+
<span className="size-1.5 rounded-full bg-og-accent" />
|
|
1112
|
+
) : null}
|
|
1113
|
+
</button>
|
|
1114
|
+
) : null}
|
|
780
1115
|
{stopping ? (
|
|
781
1116
|
<span
|
|
782
1117
|
role="status"
|
|
@@ -794,6 +1129,47 @@ export function SessionChrome({
|
|
|
794
1129
|
</div>
|
|
795
1130
|
</div>
|
|
796
1131
|
|
|
1132
|
+
<AnimatePresence initial={false}>
|
|
1133
|
+
{compact && activityOpen ? (
|
|
1134
|
+
<motion.div
|
|
1135
|
+
key="activity-tabs"
|
|
1136
|
+
initial={{ height: 0, opacity: 0 }}
|
|
1137
|
+
animate={{ height: "auto", opacity: 1 }}
|
|
1138
|
+
exit={{ height: 0, opacity: 0 }}
|
|
1139
|
+
transition={{ duration: shellDuration, ease }}
|
|
1140
|
+
style={{ overflow: "hidden" }}
|
|
1141
|
+
>
|
|
1142
|
+
<div className="mt-1 flex items-center gap-1 rounded-t-og-lg bg-og-surface-2/50 px-2 py-1">
|
|
1143
|
+
{signals
|
|
1144
|
+
.filter((signal) => ["incoming", "agents", "commands"].includes(signal.id))
|
|
1145
|
+
.map((signal) => (
|
|
1146
|
+
<button
|
|
1147
|
+
key={signal.id}
|
|
1148
|
+
type="button"
|
|
1149
|
+
aria-expanded={active === signal.id}
|
|
1150
|
+
onClick={() => {
|
|
1151
|
+
if (activityVisibleRef.current) setActive(signal.id);
|
|
1152
|
+
}}
|
|
1153
|
+
className={cn(
|
|
1154
|
+
"inline-flex min-h-9 items-center gap-1.5 rounded-og-md px-2 text-og-xs pointer-coarse:min-h-11",
|
|
1155
|
+
active === signal.id
|
|
1156
|
+
? "bg-og-surface-3 text-og-fg"
|
|
1157
|
+
: "text-og-fg-muted hover:text-og-fg",
|
|
1158
|
+
)}
|
|
1159
|
+
>
|
|
1160
|
+
{signal.icon}
|
|
1161
|
+
{signal.id === "incoming" ? `${incoming.length} incoming` : signal.label}
|
|
1162
|
+
</button>
|
|
1163
|
+
))}
|
|
1164
|
+
</div>
|
|
1165
|
+
</motion.div>
|
|
1166
|
+
) : null}
|
|
1167
|
+
</AnimatePresence>
|
|
1168
|
+
{goal?.mutationError ? (
|
|
1169
|
+
<p role="alert" className="px-3 pb-2 text-og-xs text-og-danger">
|
|
1170
|
+
Goal action not confirmed. {goal.mutationError.message}
|
|
1171
|
+
</p>
|
|
1172
|
+
) : null}
|
|
797
1173
|
<div
|
|
798
1174
|
id={panelId}
|
|
799
1175
|
data-og-session-chrome-panel-shell=""
|
|
@@ -811,7 +1187,14 @@ export function SessionChrome({
|
|
|
811
1187
|
>
|
|
812
1188
|
<div className="min-h-0 overflow-hidden">
|
|
813
1189
|
<div
|
|
814
|
-
className=
|
|
1190
|
+
className={cn(
|
|
1191
|
+
"relative overflow-y-auto overscroll-contain",
|
|
1192
|
+
compact
|
|
1193
|
+
? activityOpen
|
|
1194
|
+
? "rounded-b-og-lg bg-og-surface-2/50"
|
|
1195
|
+
: "mt-1 rounded-og-lg bg-og-surface-2/50"
|
|
1196
|
+
: "border-t border-og-border/50",
|
|
1197
|
+
)}
|
|
815
1198
|
style={{
|
|
816
1199
|
maxHeight: "var(--og-session-chrome-panel-max-height)",
|
|
817
1200
|
paddingInline: "var(--og-session-chrome-panel-pad-x)",
|
|
@@ -819,7 +1202,7 @@ export function SessionChrome({
|
|
|
819
1202
|
}}
|
|
820
1203
|
>
|
|
821
1204
|
<AnimatePresence initial={false}>
|
|
822
|
-
{active && panelBody ? (
|
|
1205
|
+
{active && active !== "commands" && panelBody ? (
|
|
823
1206
|
<motion.div
|
|
824
1207
|
key={active}
|
|
825
1208
|
data-og-session-chrome-panel-frame={active}
|
|
@@ -842,6 +1225,16 @@ export function SessionChrome({
|
|
|
842
1225
|
</motion.div>
|
|
843
1226
|
) : null}
|
|
844
1227
|
</AnimatePresence>
|
|
1228
|
+
{active === "commands" ? (
|
|
1229
|
+
<motion.div
|
|
1230
|
+
key="commands"
|
|
1231
|
+
initial={reduceMotion ? false : { opacity: 0 }}
|
|
1232
|
+
animate={{ opacity: 1 }}
|
|
1233
|
+
transition={{ duration: crossfadeDuration, ease }}
|
|
1234
|
+
>
|
|
1235
|
+
{panelBody}
|
|
1236
|
+
</motion.div>
|
|
1237
|
+
) : null}
|
|
845
1238
|
</div>
|
|
846
1239
|
</div>
|
|
847
1240
|
</div>
|
|
@@ -854,47 +1247,59 @@ export function SessionChrome({
|
|
|
854
1247
|
function IncomingPanel({
|
|
855
1248
|
inputs,
|
|
856
1249
|
onDismiss,
|
|
1250
|
+
onOpenSession,
|
|
857
1251
|
}: {
|
|
858
1252
|
inputs: SessionPendingInputPreview[];
|
|
859
1253
|
onDismiss?: ((inputId: string) => void) | undefined;
|
|
1254
|
+
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
860
1255
|
}) {
|
|
861
1256
|
return (
|
|
862
|
-
<
|
|
863
|
-
className="
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
className={cn(
|
|
874
|
-
"mt-px shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4",
|
|
875
|
-
input.classification === "action_required" || input.classification === "failure"
|
|
876
|
-
? "bg-og-status-waiting/12 text-og-status-waiting"
|
|
877
|
-
: "bg-og-surface-3/80 text-og-fg-muted",
|
|
878
|
-
)}
|
|
1257
|
+
<div>
|
|
1258
|
+
<p className="mb-2 text-og-xs text-og-fg-subtle">Waiting to be included in an agent turn.</p>
|
|
1259
|
+
<ul
|
|
1260
|
+
className="flex flex-col gap-0.5"
|
|
1261
|
+
aria-label="Incoming updates"
|
|
1262
|
+
data-og-session-chrome-panel="incoming"
|
|
1263
|
+
>
|
|
1264
|
+
{inputs.map((input) => (
|
|
1265
|
+
<li
|
|
1266
|
+
key={input.id}
|
|
1267
|
+
className="group flex items-start gap-1.5 rounded-og-sm px-1.5 py-1 transition-colors hover:bg-[var(--_og-session-chrome-row-hover)]"
|
|
879
1268
|
>
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1269
|
+
<span
|
|
1270
|
+
className={cn(
|
|
1271
|
+
"mt-px shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4",
|
|
1272
|
+
input.classification === "action_required" || input.classification === "failure"
|
|
1273
|
+
? "bg-og-status-waiting/12 text-og-status-waiting"
|
|
1274
|
+
: "bg-og-surface-3/80 text-og-fg-muted",
|
|
1275
|
+
)}
|
|
1276
|
+
>
|
|
1277
|
+
{pendingKindLabel(input.kind)}
|
|
1278
|
+
</span>
|
|
1279
|
+
<div className="min-w-0 flex-1">
|
|
1280
|
+
<p className="break-words text-og-xs leading-4 text-og-fg">{input.summary}</p>
|
|
1281
|
+
<ChildSessionLink
|
|
1282
|
+
kind={input.kind}
|
|
1283
|
+
sourceId={input.sourceId}
|
|
1284
|
+
onOpenSession={onOpenSession}
|
|
1285
|
+
/>
|
|
893
1286
|
</div>
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1287
|
+
{onDismiss ? (
|
|
1288
|
+
<div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
|
|
1289
|
+
<IconAction
|
|
1290
|
+
label={`Dismiss incoming ${pendingKindLabel(input.kind)}`}
|
|
1291
|
+
tip="Dismiss"
|
|
1292
|
+
onClick={() => onDismiss(input.id)}
|
|
1293
|
+
danger
|
|
1294
|
+
>
|
|
1295
|
+
<Trash2Icon className="size-3" />
|
|
1296
|
+
</IconAction>
|
|
1297
|
+
</div>
|
|
1298
|
+
) : null}
|
|
1299
|
+
</li>
|
|
1300
|
+
))}
|
|
1301
|
+
</ul>
|
|
1302
|
+
</div>
|
|
898
1303
|
);
|
|
899
1304
|
}
|
|
900
1305
|
|
|
@@ -1017,6 +1422,7 @@ function QueuePanel({
|
|
|
1017
1422
|
{onSteer ? (
|
|
1018
1423
|
<IconAction
|
|
1019
1424
|
label={`Steer queued prompt ${index + 1}`}
|
|
1425
|
+
text="Steer"
|
|
1020
1426
|
tip={QUEUE_STEER_TIP}
|
|
1021
1427
|
disabled={settling || pending !== null}
|
|
1022
1428
|
onClick={() => onSteer(turn.id)}
|
|
@@ -1024,7 +1430,7 @@ function QueuePanel({
|
|
|
1024
1430
|
{pending === "steer" ? (
|
|
1025
1431
|
<Loader2Icon className="size-3 animate-og-spin" />
|
|
1026
1432
|
) : (
|
|
1027
|
-
<
|
|
1433
|
+
<CornerDownRightIcon className="size-3" />
|
|
1028
1434
|
)}
|
|
1029
1435
|
</IconAction>
|
|
1030
1436
|
) : null}
|
|
@@ -1206,7 +1612,7 @@ function GoalPanel({
|
|
|
1206
1612
|
<span>{sessionChromeGoalPillLabel(state, record)}</span>
|
|
1207
1613
|
{elapsed ? (
|
|
1208
1614
|
<span className="tabular-nums normal-case tracking-normal text-og-fg-muted">
|
|
1209
|
-
· {elapsed}
|
|
1615
|
+
· Created {elapsed} ago
|
|
1210
1616
|
</span>
|
|
1211
1617
|
) : null}
|
|
1212
1618
|
<span className="normal-case tracking-normal text-og-fg-muted">· v{record.version}</span>
|
|
@@ -1288,6 +1694,7 @@ const QUEUE_EDIT_TIP = "Edit in composer";
|
|
|
1288
1694
|
const QUEUE_DELETE_TIP = "Delete this queued prompt";
|
|
1289
1695
|
|
|
1290
1696
|
function IconAction({
|
|
1697
|
+
text,
|
|
1291
1698
|
label,
|
|
1292
1699
|
tip,
|
|
1293
1700
|
onClick,
|
|
@@ -1296,6 +1703,7 @@ function IconAction({
|
|
|
1296
1703
|
children,
|
|
1297
1704
|
}: {
|
|
1298
1705
|
label: string;
|
|
1706
|
+
text?: string;
|
|
1299
1707
|
tip: ReactNode;
|
|
1300
1708
|
onClick: () => void;
|
|
1301
1709
|
disabled?: boolean;
|
|
@@ -1311,13 +1719,16 @@ function IconAction({
|
|
|
1311
1719
|
disabled={disabled}
|
|
1312
1720
|
onClick={onClick}
|
|
1313
1721
|
className={cn(
|
|
1314
|
-
"inline-flex size-6 items-center justify-center rounded-og-sm
|
|
1722
|
+
"inline-flex size-6 items-center justify-center rounded-og-sm outline-hidden transition-colors",
|
|
1315
1723
|
"hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40",
|
|
1316
1724
|
"disabled:pointer-events-none disabled:opacity-40 pointer-coarse:size-9",
|
|
1725
|
+
text ? "text-og-fg" : "text-og-fg-subtle",
|
|
1726
|
+
text && "w-auto gap-1 px-1.5 text-og-xs pointer-coarse:w-auto",
|
|
1317
1727
|
danger && "hover:text-og-danger",
|
|
1318
1728
|
)}
|
|
1319
1729
|
>
|
|
1320
1730
|
{children}
|
|
1731
|
+
{text}
|
|
1321
1732
|
</button>
|
|
1322
1733
|
</TooltipTrigger>
|
|
1323
1734
|
<TooltipContent side="top">{tip}</TooltipContent>
|