@opengeni/react 0.12.0 → 0.13.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 +1 -1
- package/dist/{chunk-5TIXRCSI.js → chunk-NFYVQWIB.js} +150 -64
- package/dist/chunk-NFYVQWIB.js.map +1 -0
- package/dist/index.d.ts +760 -142
- package/dist/index.js +5345 -2146
- package/dist/index.js.map +1 -1
- package/dist/{machines-BeZ3bD3t.d.ts → machines-CnlMb7E-.d.ts} +5 -5
- package/dist/machines.d.ts +1 -1
- package/dist/machines.js +1 -1
- package/package.json +17 -12
- package/src/approvals.ts +16 -4
- package/src/client.ts +32 -5
- package/src/commands/index.ts +1 -7
- package/src/commands/registry.ts +46 -12
- package/src/components/chat-composer.tsx +135 -39
- package/src/components/code-editor.tsx +28 -36
- package/src/components/command-palette.tsx +26 -5
- package/src/components/desktop-viewer.tsx +29 -16
- package/src/components/diff-view.tsx +27 -189
- package/src/components/enrollment-consent.tsx +28 -10
- package/src/components/enrollment-device-flow.tsx +8 -5
- package/src/components/file-browser.tsx +190 -56
- package/src/components/fleet-tile.tsx +13 -4
- package/src/components/machine-card.tsx +16 -4
- package/src/components/machine-dock-bar.tsx +20 -8
- package/src/components/machine-metrics.tsx +31 -8
- package/src/components/machine-status-pill.tsx +26 -5
- package/src/components/machines-dashboard.tsx +8 -2
- package/src/components/markdown.tsx +39 -9
- package/src/components/message-timeline.tsx +633 -109
- package/src/components/pierre-diff.tsx +83 -15
- package/src/components/pierre-file.tsx +10 -9
- package/src/components/sandbox-files.tsx +110 -227
- package/src/components/sandbox-terminal.tsx +256 -88
- package/src/components/sandbox-workspace.tsx +823 -0
- package/src/components/session-status.tsx +28 -2
- package/src/components/workbench-changes.tsx +541 -0
- package/src/components/workspace-dock.tsx +85 -22
- package/src/hooks/internal.ts +5 -2
- package/src/hooks/use-available-models.ts +7 -2
- package/src/hooks/use-billing-usage.ts +4 -1
- package/src/hooks/use-codex-accounts.ts +74 -44
- package/src/hooks/use-composer.ts +57 -36
- package/src/hooks/use-environments.ts +92 -47
- package/src/hooks/use-file-attachments.ts +101 -55
- package/src/hooks/use-goal.ts +52 -16
- package/src/hooks/use-machine-chip.ts +134 -0
- package/src/hooks/use-machines.ts +34 -16
- package/src/hooks/use-packs.ts +24 -19
- package/src/hooks/use-relay-frame-stream.ts +5 -2
- package/src/hooks/use-rigs.ts +335 -0
- package/src/hooks/use-sandbox-files.ts +213 -39
- package/src/hooks/use-sandbox-git.ts +188 -11
- package/src/hooks/use-sandbox-terminal.ts +18 -14
- package/src/hooks/use-scheduled-tasks.ts +10 -2
- package/src/hooks/use-session-capabilities.ts +77 -20
- package/src/hooks/use-session-control.ts +49 -21
- package/src/hooks/use-session-events.ts +48 -20
- package/src/hooks/use-session-lineage.ts +119 -0
- package/src/hooks/use-session.ts +43 -28
- package/src/hooks/use-slash-commands.ts +6 -4
- package/src/hooks/use-turn-queue.ts +74 -147
- package/src/hooks/use-windowed-sections.ts +204 -0
- package/src/hooks/use-workspace-capture.ts +233 -0
- package/src/hooks/use-workspace-edit.ts +231 -0
- package/src/hooks/use-workspace-sessions.ts +48 -5
- package/src/hooks/use-workspaces.ts +18 -15
- package/src/index.ts +127 -23
- package/src/lib/format.ts +3 -3
- package/src/lib/xterm-renderer.ts +65 -0
- package/src/lib/xterm-theme.ts +224 -18
- package/src/machines.ts +13 -3
- package/src/provider.tsx +3 -1
- package/src/timeline/activity-rail.tsx +213 -54
- package/src/timeline/disclosure-context.tsx +12 -2
- package/src/timeline/index.ts +21 -2
- package/src/timeline/parsers.ts +44 -10
- package/src/timeline/projection.ts +388 -85
- package/src/timeline/shared.tsx +111 -24
- package/src/timeline/tool-diff.tsx +24 -20
- package/src/timeline/tool-renderers.tsx +98 -21
- package/src/timeline/turn-summary.tsx +76 -23
- package/src/timeline/types.ts +96 -12
- package/styles/tokens.css +2 -2
- package/dist/chunk-5TIXRCSI.js.map +0 -1
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ArrowRightIcon,
|
|
3
|
+
BotIcon,
|
|
4
|
+
BrainCircuitIcon,
|
|
5
|
+
BrainIcon,
|
|
6
|
+
SquareTerminalIcon,
|
|
7
|
+
} from "lucide-react";
|
|
2
8
|
import { cn } from "../lib/cn";
|
|
3
9
|
import { truncate } from "../lib/format";
|
|
4
10
|
import { defaultToolRegistry } from "./tool-renderers";
|
|
5
11
|
import { useEntranceAnimation } from "./entrance";
|
|
6
12
|
import type { ToolRegistry } from "./registry";
|
|
7
|
-
import { PayloadBlock, ActivityDisclosure } from "./shared";
|
|
13
|
+
import { BodyNote, PayloadBlock, ActivityDisclosure } from "./shared";
|
|
8
14
|
import { toolDisplayName } from "./projection";
|
|
9
|
-
import type { ActivityItem, ReasoningItem, SandboxItem, WorkerItem } from "./types";
|
|
15
|
+
import type { ActivityItem, MemoryItem, ReasoningItem, SandboxItem, WorkerItem } from "./types";
|
|
10
16
|
|
|
11
17
|
/* ----------------------------------------------------------------------------
|
|
12
18
|
Activity rail
|
|
@@ -25,6 +31,12 @@ export type ActivityRailProps = {
|
|
|
25
31
|
toolRegistry?: ToolRegistry | undefined;
|
|
26
32
|
/** Drill into a spawned worker session. */
|
|
27
33
|
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Deep-link a memory row to its record in the host's memory pane. Opt-in: the
|
|
36
|
+
* library draws no "View in memory" affordance without a handler (the memory
|
|
37
|
+
* row is then non-interactive rich content). See {@link MessageTimelineProps}.
|
|
38
|
+
*/
|
|
39
|
+
onMemoryClick?: ((memoryId: string) => void) | undefined;
|
|
28
40
|
/** Drop the left rule + indent (used inside a folded turn summary). */
|
|
29
41
|
bare?: boolean | undefined;
|
|
30
42
|
className?: string | undefined;
|
|
@@ -42,7 +54,14 @@ function familyOf(item: ActivityItem): string {
|
|
|
42
54
|
return item.kind;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
export function ActivityRail({
|
|
57
|
+
export function ActivityRail({
|
|
58
|
+
items,
|
|
59
|
+
toolRegistry = defaultToolRegistry,
|
|
60
|
+
onOpenSession,
|
|
61
|
+
onMemoryClick,
|
|
62
|
+
bare,
|
|
63
|
+
className,
|
|
64
|
+
}: ActivityRailProps) {
|
|
46
65
|
const enter = useEntranceAnimation();
|
|
47
66
|
return (
|
|
48
67
|
<div
|
|
@@ -58,7 +77,7 @@ export function ActivityRail({ items, toolRegistry = defaultToolRegistry, onOpen
|
|
|
58
77
|
>
|
|
59
78
|
{items.map((item, index) => {
|
|
60
79
|
const newFamily = index > 0 && familyOf(item) !== familyOf(items[index - 1]!);
|
|
61
|
-
const row = renderActivity(item, toolRegistry, onOpenSession);
|
|
80
|
+
const row = renderActivity(item, toolRegistry, onOpenSession, onMemoryClick);
|
|
62
81
|
return (
|
|
63
82
|
<div key={item.id} className={cn(newFamily && "mt-3")}>
|
|
64
83
|
{row}
|
|
@@ -78,6 +97,7 @@ function renderActivity(
|
|
|
78
97
|
item: ActivityItem,
|
|
79
98
|
toolRegistry: ToolRegistry,
|
|
80
99
|
onOpenSession: ((sessionId: string) => void) | undefined,
|
|
100
|
+
onMemoryClick: ((memoryId: string) => void) | undefined,
|
|
81
101
|
) {
|
|
82
102
|
switch (item.kind) {
|
|
83
103
|
case "reasoning":
|
|
@@ -90,6 +110,8 @@ function renderActivity(
|
|
|
90
110
|
return <WorkerRow item={item} onOpenSession={onOpenSession} />;
|
|
91
111
|
case "sandbox":
|
|
92
112
|
return <SandboxRow item={item} />;
|
|
113
|
+
case "memory":
|
|
114
|
+
return <MemoryRow item={item} onMemoryClick={onMemoryClick} />;
|
|
93
115
|
default:
|
|
94
116
|
return assertNever(item);
|
|
95
117
|
}
|
|
@@ -117,12 +139,131 @@ function ReasoningRow({ item }: { item: ReasoningItem }) {
|
|
|
117
139
|
);
|
|
118
140
|
}
|
|
119
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Human labels for the memory kinds, translated at the SDK boundary so a raw
|
|
144
|
+
* enum slug never renders as UI. Kept local to the library (the app has its own
|
|
145
|
+
* `KIND_LABEL`); an unknown kind simply omits the chip rather than showing a slug.
|
|
146
|
+
*/
|
|
147
|
+
const MEMORY_KIND_LABEL: Record<string, string> = {
|
|
148
|
+
preference: "Preference",
|
|
149
|
+
semantic: "Fact",
|
|
150
|
+
procedural: "Procedure",
|
|
151
|
+
decision: "Decision",
|
|
152
|
+
episodic: "History",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A memory write the agent made mid-turn. A calm, NEUTRAL step (a successful save
|
|
157
|
+
* is ordinary progress, never an exceptional state, so no accent/color): a brain-
|
|
158
|
+
* circuit glyph, "Saved to memory" / "Updated memory", a human kind chip, and the
|
|
159
|
+
* memory text. Expanding reveals the full text; a supersede shows the old text
|
|
160
|
+
* struck through above the new one. When the host opts in with `onMemoryClick`,
|
|
161
|
+
* a quiet "View in memory" affordance deep-links to the LIVE record.
|
|
162
|
+
*/
|
|
163
|
+
function MemoryRow({
|
|
164
|
+
item,
|
|
165
|
+
onMemoryClick,
|
|
166
|
+
}: {
|
|
167
|
+
item: MemoryItem;
|
|
168
|
+
onMemoryClick?: ((memoryId: string) => void) | undefined;
|
|
169
|
+
}) {
|
|
170
|
+
const corrected = item.variant === "corrected";
|
|
171
|
+
const kindLabel = MEMORY_KIND_LABEL[item.memoryKind];
|
|
172
|
+
// A supersede carries both the old text (`preview`) and the new (`replacementPreview`);
|
|
173
|
+
// an in-place update / archive carries only `preview`.
|
|
174
|
+
const superseded = corrected && Boolean(item.replacementPreview);
|
|
175
|
+
// Link to the LIVE record: a supersede's replacement when present, else the memory itself.
|
|
176
|
+
const targetId = corrected ? (item.replacementMemoryId ?? item.memoryId) : item.memoryId;
|
|
177
|
+
const deepLink = Boolean(onMemoryClick);
|
|
178
|
+
return (
|
|
179
|
+
<ActivityDisclosure
|
|
180
|
+
icon={<BrainCircuitIcon className="size-3.5" />}
|
|
181
|
+
iconTone="muted"
|
|
182
|
+
title={
|
|
183
|
+
<span className="inline-flex min-w-0 items-center gap-2">
|
|
184
|
+
<span className="shrink-0">{corrected ? "Updated memory" : "Saved to memory"}</span>
|
|
185
|
+
{kindLabel ? (
|
|
186
|
+
<span className="shrink-0 rounded-og-xs bg-og-surface-2 px-1.5 py-px text-og-xs font-normal leading-tight text-og-fg-subtle">
|
|
187
|
+
{kindLabel}
|
|
188
|
+
</span>
|
|
189
|
+
) : null}
|
|
190
|
+
</span>
|
|
191
|
+
}
|
|
192
|
+
preview={superseded ? item.replacementPreview : item.preview}
|
|
193
|
+
>
|
|
194
|
+
{superseded ? (
|
|
195
|
+
// The correction as a before → after: the old memory struck through and
|
|
196
|
+
// dimmed, the new text in the ordinary body weight below it.
|
|
197
|
+
<div className="flex flex-col gap-1.5">
|
|
198
|
+
<p className="whitespace-pre-wrap text-og-sm leading-6 text-og-fg-subtle line-through">
|
|
199
|
+
{item.preview}
|
|
200
|
+
</p>
|
|
201
|
+
<p className="whitespace-pre-wrap text-og-base leading-6 text-og-fg-muted">
|
|
202
|
+
{item.replacementPreview}
|
|
203
|
+
</p>
|
|
204
|
+
</div>
|
|
205
|
+
) : corrected && item.action === "updated" ? (
|
|
206
|
+
// Edited in place, no replacement record: the memory is still live, so
|
|
207
|
+
// show its current text — NOT the archived treatment.
|
|
208
|
+
<>
|
|
209
|
+
<p className="whitespace-pre-wrap text-og-base leading-6 text-og-fg-muted">
|
|
210
|
+
{item.preview}
|
|
211
|
+
</p>
|
|
212
|
+
<BodyNote tone="muted">Updated in place.</BodyNote>
|
|
213
|
+
</>
|
|
214
|
+
) : corrected ? (
|
|
215
|
+
// A correction with no replacement (and not an in-place update) archived the record.
|
|
216
|
+
<BodyNote tone="muted">Archived.</BodyNote>
|
|
217
|
+
) : (
|
|
218
|
+
<p className="whitespace-pre-wrap text-og-base leading-6 text-og-fg-muted">
|
|
219
|
+
{item.preview}
|
|
220
|
+
</p>
|
|
221
|
+
)}
|
|
222
|
+
{item.deduped ? <BodyNote tone="muted">Merged into an existing memory.</BodyNote> : null}
|
|
223
|
+
{deepLink ? (
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
onClick={() => onMemoryClick?.(targetId)}
|
|
227
|
+
className={cn(
|
|
228
|
+
"group/memlink -mx-1 inline-flex w-fit items-center gap-1 rounded-og-sm px-1 py-0.5 text-left text-og-sm text-og-fg-subtle",
|
|
229
|
+
"outline-none transition-colors duration-150 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent",
|
|
230
|
+
)}
|
|
231
|
+
>
|
|
232
|
+
View in memory
|
|
233
|
+
<ArrowRightIcon className="size-3.5 transition-transform duration-150 group-hover/memlink:translate-x-0.5" />
|
|
234
|
+
</button>
|
|
235
|
+
) : null}
|
|
236
|
+
</ActivityDisclosure>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* A human title for a sandbox operation row. Named platform operations (the lazy
|
|
242
|
+
* `sandbox.provision` first-establish that now runs mid-turn) read as calm,
|
|
243
|
+
* status-aware English instead of a raw op id — the box coming up should look
|
|
244
|
+
* like "Starting sandbox", never an unexplained long-running command. Unnamed /
|
|
245
|
+
* unknown ops fall back to the generic id-to-words {@link toolDisplayName}.
|
|
246
|
+
*/
|
|
247
|
+
function sandboxRowTitle(item: SandboxItem): string {
|
|
248
|
+
if (item.name === "sandbox.provision") {
|
|
249
|
+
if (item.status === "failed") return "Sandbox didn’t start";
|
|
250
|
+
if (item.status === "running") return "Starting sandbox";
|
|
251
|
+
if (item.origin === "resumed") return "Sandbox reattached";
|
|
252
|
+
if (item.origin === "restored") return "Sandbox restored";
|
|
253
|
+
if (item.origin === "created") return "Sandbox created";
|
|
254
|
+
return "Sandbox ready";
|
|
255
|
+
}
|
|
256
|
+
return toolDisplayName(item.name);
|
|
257
|
+
}
|
|
258
|
+
|
|
120
259
|
function SandboxRow({ item }: { item: SandboxItem }) {
|
|
121
260
|
return (
|
|
122
261
|
<ActivityDisclosure
|
|
123
262
|
icon={<SquareTerminalIcon className="size-3.5" />}
|
|
124
|
-
iconTone={
|
|
125
|
-
|
|
263
|
+
iconTone={
|
|
264
|
+
item.status === "failed" ? "failed" : item.status === "running" ? "running" : "muted"
|
|
265
|
+
}
|
|
266
|
+
title={sandboxRowTitle(item)}
|
|
126
267
|
running={item.status === "running"}
|
|
127
268
|
failed={item.status === "failed"}
|
|
128
269
|
cancelled={item.status === "cancelled"}
|
|
@@ -135,7 +276,13 @@ function SandboxRow({ item }: { item: SandboxItem }) {
|
|
|
135
276
|
}
|
|
136
277
|
|
|
137
278
|
/** Spawned/messaged worker sessions get a first-class card, not a tool row. */
|
|
138
|
-
function WorkerRow({
|
|
279
|
+
function WorkerRow({
|
|
280
|
+
item,
|
|
281
|
+
onOpenSession,
|
|
282
|
+
}: {
|
|
283
|
+
item: WorkerItem;
|
|
284
|
+
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
285
|
+
}) {
|
|
139
286
|
const running = item.status === "running";
|
|
140
287
|
const failed = item.status === "failed";
|
|
141
288
|
const cancelled = item.status === "cancelled";
|
|
@@ -148,47 +295,49 @@ function WorkerRow({ item, onOpenSession }: { item: WorkerItem; onOpenSession?:
|
|
|
148
295
|
: cancelled
|
|
149
296
|
? "Worker interrupted"
|
|
150
297
|
: "Worker spawned"
|
|
151
|
-
:
|
|
152
|
-
?
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
>
|
|
177
|
-
<BotIcon className="size-4" />
|
|
298
|
+
: running
|
|
299
|
+
? "Messaging worker"
|
|
300
|
+
: failed
|
|
301
|
+
? "Worker message failed"
|
|
302
|
+
: cancelled
|
|
303
|
+
? "Worker interrupted"
|
|
304
|
+
: "Worker messaged";
|
|
305
|
+
// A worker is a first-class actor but still a STEP on the rail — a borderless
|
|
306
|
+
// row (no card), aligned to its sibling tool rows: the chevron column is an
|
|
307
|
+
// empty spacer (a worker doesn't expand), then the bot glyph, then the title
|
|
308
|
+
// with a quiet prompt beneath, and one right-gutter affordance.
|
|
309
|
+
//
|
|
310
|
+
// Once the worker's session id is known, the WHOLE row is a clean, clickable
|
|
311
|
+
// deep-link into that child session (a trailing arrow brightens on hover) —
|
|
312
|
+
// the spawn moment itself is the affordance, not a small side button. A
|
|
313
|
+
// still-in-flight spawn (no id yet) or a failed/cancelled worker is inert, so
|
|
314
|
+
// there is never a dead click target. The gutter mirrors the worker-completion
|
|
315
|
+
// card's calm language: red chip on failure, "interrupted" on cancel.
|
|
316
|
+
const sessionId = item.workerSessionId;
|
|
317
|
+
const deepLink = Boolean(sessionId) && Boolean(onOpenSession) && !failed && !cancelled;
|
|
318
|
+
const inner = (
|
|
319
|
+
<>
|
|
320
|
+
<span className="size-3.5 shrink-0" aria-hidden />
|
|
321
|
+
<span className={cn("mt-px shrink-0", failed ? "text-og-status-failed" : "text-og-accent")}>
|
|
322
|
+
<BotIcon className="size-3.5" />
|
|
178
323
|
</span>
|
|
179
324
|
<div className="min-w-0 flex-1">
|
|
180
325
|
{/* In-flight state is carried ONLY by the shimmering title (no detached
|
|
181
326
|
pulse badge), matching every other running row in the rail. */}
|
|
182
|
-
<span
|
|
327
|
+
<span
|
|
328
|
+
className={cn(
|
|
329
|
+
"text-og-base font-medium",
|
|
330
|
+
running ? "og-shimmer-text" : failed ? "text-og-status-failed" : "text-og-fg",
|
|
331
|
+
)}
|
|
332
|
+
>
|
|
183
333
|
{title}
|
|
184
334
|
</span>
|
|
185
|
-
{item.prompt ?
|
|
186
|
-
|
|
187
|
-
|
|
335
|
+
{item.prompt ? (
|
|
336
|
+
<p className="mt-0.5 truncate text-og-sm text-og-fg-muted">
|
|
337
|
+
{truncate(item.prompt, 140)}
|
|
338
|
+
</p>
|
|
188
339
|
) : null}
|
|
189
340
|
</div>
|
|
190
|
-
{/* Right gutter: failed gets a red chip; cancelled gets a calm "interrupted"
|
|
191
|
-
chip (no dot, no red); a live complete worker shows an "Open session" button. */}
|
|
192
341
|
{failed ? (
|
|
193
342
|
<span className="inline-flex shrink-0 self-center items-center gap-1.5 font-og-mono text-og-xs leading-none text-og-status-failed">
|
|
194
343
|
<span className="size-1.5 rounded-full bg-og-status-failed" />
|
|
@@ -198,19 +347,29 @@ function WorkerRow({ item, onOpenSession }: { item: WorkerItem; onOpenSession?:
|
|
|
198
347
|
<span className="og-cancelled-chip shrink-0 self-center font-og-mono text-og-xs leading-none text-og-fg-subtle">
|
|
199
348
|
interrupted
|
|
200
349
|
</span>
|
|
201
|
-
) :
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
"shrink-0 self-center rounded-og-sm border border-og-border px-2.5 py-1 text-og-sm font-medium text-og-fg-muted pointer-coarse:py-2",
|
|
207
|
-
"outline-none transition-colors duration-150 hover:border-og-border-strong hover:text-og-fg",
|
|
208
|
-
"focus-visible:ring-2 focus-visible:ring-og-accent",
|
|
209
|
-
)}
|
|
210
|
-
>
|
|
211
|
-
Open session
|
|
212
|
-
</button>
|
|
350
|
+
) : deepLink ? (
|
|
351
|
+
<ArrowRightIcon
|
|
352
|
+
aria-hidden
|
|
353
|
+
className="mt-0.5 size-3.5 shrink-0 text-og-fg-subtle transition-[transform,color] duration-150 group-hover/worker:translate-x-0.5 group-hover/worker:text-og-fg"
|
|
354
|
+
/>
|
|
213
355
|
) : null}
|
|
214
|
-
|
|
356
|
+
</>
|
|
215
357
|
);
|
|
358
|
+
|
|
359
|
+
if (deepLink && sessionId && onOpenSession) {
|
|
360
|
+
return (
|
|
361
|
+
<button
|
|
362
|
+
type="button"
|
|
363
|
+
onClick={() => onOpenSession(sessionId)}
|
|
364
|
+
className={cn(
|
|
365
|
+
"group/worker -mx-1.5 flex w-full items-start gap-2 rounded-og-sm px-1.5 py-1.5 text-left",
|
|
366
|
+
"outline-none transition-colors duration-150 hover:bg-og-surface-1 focus-visible:ring-2 focus-visible:ring-og-accent",
|
|
367
|
+
"pointer-coarse:py-2.5",
|
|
368
|
+
)}
|
|
369
|
+
>
|
|
370
|
+
{inner}
|
|
371
|
+
</button>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return <div className="flex items-start gap-2 px-1.5 py-1.5">{inner}</div>;
|
|
216
375
|
}
|
|
@@ -21,8 +21,18 @@ const DisclosureDefaultsContext = createContext<boolean | undefined>(undefined);
|
|
|
21
21
|
* Seed the initial open state of every timeline collapsible below this node.
|
|
22
22
|
* Intended for screenshot/test instrumentation only; absent by default.
|
|
23
23
|
*/
|
|
24
|
-
export function DisclosureDefaultsProvider({
|
|
25
|
-
|
|
24
|
+
export function DisclosureDefaultsProvider({
|
|
25
|
+
defaultOpen,
|
|
26
|
+
children,
|
|
27
|
+
}: {
|
|
28
|
+
defaultOpen: boolean;
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
}) {
|
|
31
|
+
return (
|
|
32
|
+
<DisclosureDefaultsContext.Provider value={defaultOpen}>
|
|
33
|
+
{children}
|
|
34
|
+
</DisclosureDefaultsContext.Provider>
|
|
35
|
+
);
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
/**
|
package/src/timeline/index.ts
CHANGED
|
@@ -16,13 +16,22 @@
|
|
|
16
16
|
-------------------------------------------------------------------------- */
|
|
17
17
|
|
|
18
18
|
// projection
|
|
19
|
-
export {
|
|
19
|
+
export {
|
|
20
|
+
buildTimeline,
|
|
21
|
+
creditExhaustedFromEvents,
|
|
22
|
+
extractSessionRef,
|
|
23
|
+
groupTimeline,
|
|
24
|
+
sessionStatusFromEvents,
|
|
25
|
+
toolDisplayName,
|
|
26
|
+
} from "./projection";
|
|
20
27
|
|
|
21
28
|
// item types
|
|
22
29
|
export type {
|
|
23
30
|
ActivityItem,
|
|
24
31
|
AgentMessageItem,
|
|
32
|
+
AuthNeededItem,
|
|
25
33
|
GoalItem,
|
|
34
|
+
MemoryItem,
|
|
26
35
|
NoticeItem,
|
|
27
36
|
ReasoningItem,
|
|
28
37
|
SandboxItem,
|
|
@@ -32,6 +41,7 @@ export type {
|
|
|
32
41
|
TurnEndItem,
|
|
33
42
|
ToolCallItem,
|
|
34
43
|
UserMessageItem,
|
|
44
|
+
WorkerCompletionItem,
|
|
35
45
|
WorkerItem,
|
|
36
46
|
} from "./types";
|
|
37
47
|
|
|
@@ -53,7 +63,16 @@ export { ActivityRail } from "./activity-rail";
|
|
|
53
63
|
export type { ActivityRailProps } from "./activity-rail";
|
|
54
64
|
|
|
55
65
|
// shared primitives (extension authors compose these)
|
|
56
|
-
export {
|
|
66
|
+
export {
|
|
67
|
+
ActivityDisclosure,
|
|
68
|
+
BodyNote,
|
|
69
|
+
MediaEmpty,
|
|
70
|
+
MediaSkeleton,
|
|
71
|
+
PayloadBlock,
|
|
72
|
+
ScreenshotFigure,
|
|
73
|
+
TermBlock,
|
|
74
|
+
Thumbnail,
|
|
75
|
+
} from "./shared";
|
|
57
76
|
export type { ActivityDisclosureProps, DisclosureChip } from "./shared";
|
|
58
77
|
|
|
59
78
|
// screenshot lightbox
|
package/src/timeline/parsers.ts
CHANGED
|
@@ -29,7 +29,8 @@ export function sandboxCommandExitCode(out: unknown): number | null {
|
|
|
29
29
|
export function parseExecBannerSessionId(out: unknown): number | null {
|
|
30
30
|
const text = String(out ?? "");
|
|
31
31
|
const outputIdx = text.indexOf("\nOutput:\n");
|
|
32
|
-
const banner =
|
|
32
|
+
const banner =
|
|
33
|
+
outputIdx >= 0 ? text.slice(0, outputIdx) : text.startsWith("Output:\n") ? "" : text;
|
|
33
34
|
const match = banner.match(/Process running with session ID (\d+)/);
|
|
34
35
|
if (!match) {
|
|
35
36
|
return null;
|
|
@@ -53,7 +54,9 @@ export function stripExecBanner(out: unknown): string {
|
|
|
53
54
|
|
|
54
55
|
/** The sandbox clamped the output (token/line truncation markers in the banner). */
|
|
55
56
|
export function execTruncated(out: unknown): boolean {
|
|
56
|
-
return /Total output lines:|\.{3}\d+ tokens truncated\.{3}|\[\.{3}\d+ characters truncated/.test(
|
|
57
|
+
return /Total output lines:|\.{3}\d+ tokens truncated\.{3}|\[\.{3}\d+ characters truncated/.test(
|
|
58
|
+
String(out ?? ""),
|
|
59
|
+
);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
/** A `write_stdin` whose target PTY vanished (`write_stdin failed: session not found: N`). */
|
|
@@ -71,7 +74,10 @@ export function looksBinary(text: string): boolean {
|
|
|
71
74
|
* `write_stdin` keystroke payload reads cleanly in the row title.
|
|
72
75
|
*/
|
|
73
76
|
export function controlCaret(printable: string): string {
|
|
74
|
-
return String(printable).replace(
|
|
77
|
+
return String(printable).replace(
|
|
78
|
+
/[\u0000-\u001f]/g,
|
|
79
|
+
(c) => `^${String.fromCharCode(c.charCodeAt(0) + 64)}`,
|
|
80
|
+
);
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
/* --- V4A apply_patch diff -> GitFileDiff ------------------------------------ */
|
|
@@ -100,7 +106,13 @@ export type ApplyPatchOperation = {
|
|
|
100
106
|
*/
|
|
101
107
|
export function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff {
|
|
102
108
|
const status: GitFileDiff["status"] =
|
|
103
|
-
op.type === "create_file"
|
|
109
|
+
op.type === "create_file"
|
|
110
|
+
? "added"
|
|
111
|
+
: op.type === "delete_file"
|
|
112
|
+
? "deleted"
|
|
113
|
+
: op.moveTo
|
|
114
|
+
? "renamed"
|
|
115
|
+
: "modified";
|
|
104
116
|
const oldPath = op.moveTo ? op.path : null;
|
|
105
117
|
const path = op.moveTo || op.path;
|
|
106
118
|
|
|
@@ -150,7 +162,12 @@ export function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff {
|
|
|
150
162
|
cur.oldLines += 1;
|
|
151
163
|
deletions += 1;
|
|
152
164
|
} else {
|
|
153
|
-
cur.lines.push({
|
|
165
|
+
cur.lines.push({
|
|
166
|
+
type: "context",
|
|
167
|
+
oldNo: oldNo++,
|
|
168
|
+
newNo: newNo++,
|
|
169
|
+
text: raw.replace(/^ /, ""),
|
|
170
|
+
});
|
|
154
171
|
cur.oldLines += 1;
|
|
155
172
|
cur.newLines += 1;
|
|
156
173
|
}
|
|
@@ -163,7 +180,17 @@ export function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff {
|
|
|
163
180
|
}
|
|
164
181
|
}
|
|
165
182
|
|
|
166
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
path,
|
|
185
|
+
oldPath,
|
|
186
|
+
status,
|
|
187
|
+
isBinary: false,
|
|
188
|
+
isImage: false,
|
|
189
|
+
additions,
|
|
190
|
+
deletions,
|
|
191
|
+
hunks,
|
|
192
|
+
truncated: false,
|
|
193
|
+
};
|
|
167
194
|
}
|
|
168
195
|
|
|
169
196
|
/**
|
|
@@ -186,7 +213,8 @@ export function applyPatchOps(raw: unknown): ApplyPatchOperation[] {
|
|
|
186
213
|
* replays that omit `raw`). Centralizes the rawType-or-name check.
|
|
187
214
|
*/
|
|
188
215
|
export function isApplyPatch(item: { name: string; raw: unknown }): boolean {
|
|
189
|
-
const type =
|
|
216
|
+
const type =
|
|
217
|
+
item.raw && typeof item.raw === "object" ? (item.raw as { type?: unknown }).type : undefined;
|
|
190
218
|
return type === "apply_patch_call" || item.name === "apply_patch_call";
|
|
191
219
|
}
|
|
192
220
|
|
|
@@ -249,7 +277,10 @@ export function unwrapMcpOutput(output: unknown): { text: string; isError: boole
|
|
|
249
277
|
}
|
|
250
278
|
return { text: JSON.stringify(output), isError };
|
|
251
279
|
}
|
|
252
|
-
return {
|
|
280
|
+
return {
|
|
281
|
+
text: typeof output === "string" ? output : output == null ? "" : JSON.stringify(output),
|
|
282
|
+
isError: false,
|
|
283
|
+
};
|
|
253
284
|
}
|
|
254
285
|
|
|
255
286
|
/* --- computer-use screenshot extraction ------------------------------------- */
|
|
@@ -322,7 +353,8 @@ export function screenshotDataUrl(out: unknown): string | null {
|
|
|
322
353
|
/** Serialize whatever a Uint8Array became in JSON (number[], {"0":n,…} index
|
|
323
354
|
* map, or Buffer-JSON {type:"Buffer",data:[…]}) back into base64. */
|
|
324
355
|
function bytesToBase64(data: unknown): string | null {
|
|
325
|
-
const isByte = (n: unknown): n is number =>
|
|
356
|
+
const isByte = (n: unknown): n is number =>
|
|
357
|
+
typeof n === "number" && Number.isInteger(n) && n >= 0 && n <= 255;
|
|
326
358
|
let bytes: number[] | null = null;
|
|
327
359
|
if (Array.isArray(data) && data.every(isByte)) {
|
|
328
360
|
bytes = data;
|
|
@@ -349,7 +381,9 @@ function bytesToBase64(data: unknown): string | null {
|
|
|
349
381
|
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
350
382
|
binary += String.fromCharCode(...bytes.slice(i, i + CHUNK));
|
|
351
383
|
}
|
|
352
|
-
return typeof btoa === "function"
|
|
384
|
+
return typeof btoa === "function"
|
|
385
|
+
? btoa(binary)
|
|
386
|
+
: Buffer.from(binary, "binary").toString("base64");
|
|
353
387
|
} catch {
|
|
354
388
|
// A hostile/absurd payload must degrade to "no image", never crash a render.
|
|
355
389
|
return null;
|