@opengeni/react 0.11.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.
Files changed (85) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-5C7RGAWA.js → chunk-NFYVQWIB.js} +170 -64
  3. package/dist/chunk-NFYVQWIB.js.map +1 -0
  4. package/dist/index.d.ts +791 -145
  5. package/dist/index.js +5447 -2213
  6. package/dist/index.js.map +1 -1
  7. package/dist/{machines-BeZ3bD3t.d.ts → machines-CnlMb7E-.d.ts} +5 -5
  8. package/dist/machines.d.ts +1 -1
  9. package/dist/machines.js +1 -1
  10. package/package.json +17 -12
  11. package/src/approvals.ts +16 -4
  12. package/src/client.ts +32 -5
  13. package/src/commands/index.ts +1 -7
  14. package/src/commands/registry.ts +46 -12
  15. package/src/components/chat-composer.tsx +135 -39
  16. package/src/components/code-editor.tsx +28 -36
  17. package/src/components/command-palette.tsx +26 -5
  18. package/src/components/desktop-viewer.tsx +29 -16
  19. package/src/components/diff-view.tsx +27 -189
  20. package/src/components/enrollment-consent.tsx +28 -10
  21. package/src/components/enrollment-device-flow.tsx +8 -5
  22. package/src/components/file-browser.tsx +190 -56
  23. package/src/components/fleet-tile.tsx +13 -4
  24. package/src/components/machine-card.tsx +16 -4
  25. package/src/components/machine-dock-bar.tsx +20 -8
  26. package/src/components/machine-metrics.tsx +31 -8
  27. package/src/components/machine-status-pill.tsx +26 -5
  28. package/src/components/machines-dashboard.tsx +8 -2
  29. package/src/components/markdown.tsx +39 -9
  30. package/src/components/message-timeline.tsx +633 -109
  31. package/src/components/pierre-diff.tsx +83 -15
  32. package/src/components/pierre-file.tsx +10 -9
  33. package/src/components/sandbox-files.tsx +110 -227
  34. package/src/components/sandbox-terminal.tsx +256 -88
  35. package/src/components/sandbox-workspace.tsx +823 -0
  36. package/src/components/session-status.tsx +28 -2
  37. package/src/components/workbench-changes.tsx +541 -0
  38. package/src/components/workspace-dock.tsx +85 -22
  39. package/src/hooks/internal.ts +5 -2
  40. package/src/hooks/use-available-models.ts +7 -2
  41. package/src/hooks/use-billing-usage.ts +4 -1
  42. package/src/hooks/use-codex-accounts.ts +74 -44
  43. package/src/hooks/use-composer.ts +57 -36
  44. package/src/hooks/use-environments.ts +92 -47
  45. package/src/hooks/use-file-attachments.ts +101 -55
  46. package/src/hooks/use-goal.ts +52 -16
  47. package/src/hooks/use-machine-chip.ts +134 -0
  48. package/src/hooks/use-machines.ts +34 -16
  49. package/src/hooks/use-packs.ts +24 -19
  50. package/src/hooks/use-relay-frame-stream.ts +5 -2
  51. package/src/hooks/use-rigs.ts +335 -0
  52. package/src/hooks/use-sandbox-files.ts +213 -39
  53. package/src/hooks/use-sandbox-git.ts +188 -11
  54. package/src/hooks/use-sandbox-terminal.ts +18 -14
  55. package/src/hooks/use-scheduled-tasks.ts +10 -2
  56. package/src/hooks/use-session-capabilities.ts +77 -20
  57. package/src/hooks/use-session-control.ts +49 -21
  58. package/src/hooks/use-session-events.ts +48 -20
  59. package/src/hooks/use-session-lineage.ts +119 -0
  60. package/src/hooks/use-session.ts +43 -28
  61. package/src/hooks/use-slash-commands.ts +6 -4
  62. package/src/hooks/use-turn-queue.ts +74 -147
  63. package/src/hooks/use-windowed-sections.ts +204 -0
  64. package/src/hooks/use-workspace-capture.ts +233 -0
  65. package/src/hooks/use-workspace-edit.ts +231 -0
  66. package/src/hooks/use-workspace-sessions.ts +48 -5
  67. package/src/hooks/use-workspaces.ts +18 -15
  68. package/src/index.ts +128 -23
  69. package/src/lib/format.ts +44 -6
  70. package/src/lib/xterm-renderer.ts +65 -0
  71. package/src/lib/xterm-theme.ts +224 -18
  72. package/src/machines.ts +13 -3
  73. package/src/provider.tsx +3 -1
  74. package/src/timeline/activity-rail.tsx +213 -54
  75. package/src/timeline/disclosure-context.tsx +12 -2
  76. package/src/timeline/index.ts +21 -2
  77. package/src/timeline/parsers.ts +44 -10
  78. package/src/timeline/projection.ts +435 -84
  79. package/src/timeline/shared.tsx +111 -24
  80. package/src/timeline/tool-diff.tsx +24 -20
  81. package/src/timeline/tool-renderers.tsx +98 -21
  82. package/src/timeline/turn-summary.tsx +76 -23
  83. package/src/timeline/types.ts +96 -12
  84. package/styles/tokens.css +2 -2
  85. package/dist/chunk-5C7RGAWA.js.map +0 -1
@@ -1,12 +1,18 @@
1
- import { BotIcon, BrainIcon, SquareTerminalIcon } from "lucide-react";
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({ items, toolRegistry = defaultToolRegistry, onOpenSession, bare, className }: ActivityRailProps) {
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={item.status === "failed" ? "failed" : item.status === "running" ? "running" : "muted"}
125
- title={toolDisplayName(item.name)}
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({ item, onOpenSession }: { item: WorkerItem; onOpenSession?: ((sessionId: string) => void) | undefined }) {
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
- : item.action === "interrupt"
152
- ? (() => {
153
- const verb = item.mode === "steer" ? "Steering" : "Stopping";
154
- const done = item.mode === "steer" ? "Worker steered" : "Worker stopped";
155
- return running ? `${verb} worker` : failed ? "Worker interrupt failed" : cancelled ? "Worker interrupted" : done;
156
- })()
157
- : running
158
- ? "Messaging worker"
159
- : failed
160
- ? "Worker message failed"
161
- : cancelled
162
- ? "Worker interrupted"
163
- : "Worker messaged";
164
- return (
165
- <div
166
- className={cn(
167
- "my-0.5 flex items-start gap-3 rounded-og-md border bg-og-surface-1 p-3",
168
- failed ? "border-og-status-failed/40" : "border-og-border",
169
- )}
170
- >
171
- <span
172
- className={cn(
173
- "mt-0.5 inline-flex size-7 shrink-0 items-center justify-center",
174
- failed ? "text-og-status-failed" : "text-og-accent",
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 className={cn("text-og-base font-medium", running ? "og-shimmer-text" : failed ? "text-og-status-failed" : "text-og-fg")}>
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 ? <p className="mt-0.5 truncate text-og-sm text-og-fg-muted">{truncate(item.prompt, 140)}</p> : null}
186
- {item.workerSessionId ? (
187
- <p className="mt-1 font-og-mono text-og-xs text-og-fg-subtle">{item.workerSessionId.slice(0, 8)}</p>
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
- ) : item.workerSessionId && onOpenSession ? (
202
- <button
203
- type="button"
204
- onClick={() => item.workerSessionId && onOpenSession(item.workerSessionId)}
205
- className={cn(
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
- </div>
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({ defaultOpen, children }: { defaultOpen: boolean; children: ReactNode }) {
25
- return <DisclosureDefaultsContext.Provider value={defaultOpen}>{children}</DisclosureDefaultsContext.Provider>;
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
  /**
@@ -16,13 +16,22 @@
16
16
  -------------------------------------------------------------------------- */
17
17
 
18
18
  // projection
19
- export { buildTimeline, extractSessionRef, groupTimeline, sessionStatusFromEvents, toolDisplayName } from "./projection";
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 { ActivityDisclosure, BodyNote, MediaEmpty, MediaSkeleton, PayloadBlock, ScreenshotFigure, TermBlock, Thumbnail } from "./shared";
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
@@ -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 = outputIdx >= 0 ? text.slice(0, outputIdx) : text.startsWith("Output:\n") ? "" : text;
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(String(out ?? ""));
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(/[\u0000-\u001f]/g, (c) => `^${String.fromCharCode(c.charCodeAt(0) + 64)}`);
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" ? "added" : op.type === "delete_file" ? "deleted" : op.moveTo ? "renamed" : "modified";
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({ type: "context", oldNo: oldNo++, newNo: newNo++, text: raw.replace(/^ /, "") });
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 { path, oldPath, status, isBinary: false, isImage: false, additions, deletions, hunks, truncated: false };
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 = item.raw && typeof item.raw === "object" ? (item.raw as { type?: unknown }).type : undefined;
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 { text: typeof output === "string" ? output : output == null ? "" : JSON.stringify(output), isError: false };
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 => typeof n === "number" && Number.isInteger(n) && n >= 0 && n <= 255;
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" ? btoa(binary) : Buffer.from(binary, "binary").toString("base64");
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;