@opengeni/react 0.42.1 → 0.44.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.
Files changed (53) hide show
  1. package/dist/{chunk-SJKT4TKW.js → chunk-23EJ676W.js} +3 -1
  2. package/dist/chunk-23EJ676W.js.map +1 -0
  3. package/dist/{chunk-Q2NCKWTK.js → chunk-HFO4ERGQ.js} +51 -4
  4. package/dist/chunk-HFO4ERGQ.js.map +1 -0
  5. package/dist/{chunk-4IJCL7YO.js → chunk-LWR4MXSS.js} +4 -1
  6. package/dist/chunk-LWR4MXSS.js.map +1 -0
  7. package/dist/{chunk-UWYTCQWW.js → chunk-QQRM3DO3.js} +93 -22
  8. package/dist/{chunk-UWYTCQWW.js.map → chunk-QQRM3DO3.js.map} +1 -1
  9. package/dist/{chunk-JALF5FI3.js → chunk-SRFUT2ZU.js} +2 -2
  10. package/dist/{chunk-WZT5G5OR.js → chunk-U6K24XQD.js} +5 -4
  11. package/dist/{chunk-WZT5G5OR.js.map → chunk-U6K24XQD.js.map} +1 -1
  12. package/dist/{chunk-KR2SK5GJ.js → chunk-YNYIAYXQ.js} +427 -94
  13. package/dist/chunk-YNYIAYXQ.js.map +1 -0
  14. package/dist/components/chat-composer.d.ts +5 -1
  15. package/dist/components/composer-transcription-control.d.ts +3 -1
  16. package/dist/components/composer.d.ts +6 -4
  17. package/dist/components/session-chrome.d.ts +1 -1
  18. package/dist/composer.js +4 -4
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +70 -21
  21. package/dist/index.js.map +1 -1
  22. package/dist/model-policy.d.ts +2 -0
  23. package/dist/model-policy.js +1 -1
  24. package/dist/realtime/realtime-control.d.ts +29 -1
  25. package/dist/realtime.d.ts +1 -1
  26. package/dist/realtime.js +269 -151
  27. package/dist/realtime.js.map +1 -1
  28. package/dist/session-ui.js +2 -2
  29. package/dist/session.js +3 -3
  30. package/dist/timeline/index.d.ts +1 -1
  31. package/dist/timeline/parsers.d.ts +13 -8
  32. package/package.json +2 -2
  33. package/src/components/chat-composer.tsx +58 -19
  34. package/src/components/composer-transcription-control.tsx +194 -165
  35. package/src/components/composer.tsx +78 -14
  36. package/src/components/model-policy-picker.tsx +7 -2
  37. package/src/components/session-chrome.tsx +89 -78
  38. package/src/hooks/use-composer.ts +79 -9
  39. package/src/index.ts +2 -0
  40. package/src/model-policy.ts +4 -0
  41. package/src/realtime/realtime-control.tsx +307 -137
  42. package/src/realtime.ts +1 -0
  43. package/src/timeline/index.ts +2 -0
  44. package/src/timeline/parsers.ts +201 -19
  45. package/src/timeline/projection.ts +11 -0
  46. package/src/timeline/tool-renderers.tsx +213 -8
  47. package/src/timeline/turn-summary.tsx +7 -2
  48. package/styles/tokens.css +8 -5
  49. package/dist/chunk-4IJCL7YO.js.map +0 -1
  50. package/dist/chunk-KR2SK5GJ.js.map +0 -1
  51. package/dist/chunk-Q2NCKWTK.js.map +0 -1
  52. package/dist/chunk-SJKT4TKW.js.map +0 -1
  53. /package/dist/{chunk-JALF5FI3.js.map → chunk-SRFUT2ZU.js.map} +0 -0
@@ -210,40 +210,222 @@ export function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff {
210
210
  };
211
211
  }
212
212
 
213
+ const BEGIN_PATCH = "*** Begin Patch";
214
+ const END_PATCH = "*** End Patch";
215
+ const ADD_FILE = "*** Add File: ";
216
+ const DELETE_FILE = "*** Delete File: ";
217
+ const UPDATE_FILE = "*** Update File: ";
218
+ const MOVE_TO = "*** Move to: ";
219
+
220
+ const APPLY_PATCH_OP_TYPES = new Set(["create_file", "update_file", "delete_file"]);
221
+
222
+ function isRecord(value: unknown): value is Record<string, unknown> {
223
+ return value !== null && typeof value === "object" && !Array.isArray(value);
224
+ }
225
+
226
+ /** Freeform / `{ patch }` / command payloads — tolerate leading whitespace. */
227
+ function freeformApplyPatchOps(rawPatch: string): ApplyPatchOperation[] {
228
+ return parseFreeformApplyPatch(rawPatch.trimStart());
229
+ }
230
+
231
+ function asApplyPatchOperation(value: unknown): ApplyPatchOperation | null {
232
+ if (!isRecord(value)) return null;
233
+ if (typeof value.type !== "string" || !APPLY_PATCH_OP_TYPES.has(value.type)) {
234
+ return null;
235
+ }
236
+ if (typeof value.path !== "string" || !value.path) {
237
+ return null;
238
+ }
239
+ const op: ApplyPatchOperation = {
240
+ type: value.type as ApplyPatchOperation["type"],
241
+ path: value.path,
242
+ };
243
+ if (typeof value.diff === "string") op.diff = value.diff;
244
+ if (typeof value.moveTo === "string" && value.moveTo.length > 0) op.moveTo = value.moveTo;
245
+ return op;
246
+ }
247
+
248
+ function parseStructuredOperations(payloads: unknown[]): ApplyPatchOperation[] {
249
+ if (payloads.length === 0) return [];
250
+ const operations: ApplyPatchOperation[] = [];
251
+ for (const payload of payloads) {
252
+ const op = asApplyPatchOperation(payload);
253
+ if (!op) return [];
254
+ operations.push(op);
255
+ }
256
+ return operations;
257
+ }
258
+
213
259
  /**
214
- * Extract the `apply_patch` operations from a provider-native tool item's `raw`
215
- * payload, normalizing the two wire shapes (`raw.operations[]` for a multi-file
216
- * patch, `raw.operation` for a single op). The single owner of this shape so the
217
- * renderer and the turn-summary facet counter never drift.
260
+ * Mirror of `@openai/agents-core` freeform `*** Begin Patch` ops. Kept here so
261
+ * the timeline can render Codex function-tool apply_patch without importing the
262
+ * server SDK package.
218
263
  */
219
- export function applyPatchOps(raw: unknown): ApplyPatchOperation[] {
220
- const r = (raw ?? {}) as {
221
- operation?: ApplyPatchOperation;
222
- operations?: ApplyPatchOperation[];
264
+ export function parseFreeformApplyPatch(rawPatch: string): ApplyPatchOperation[] {
265
+ const lines = rawPatch.split(/\r?\n/);
266
+ if (lines.at(-1) === "") lines.pop();
267
+ if (lines[0] !== BEGIN_PATCH) return [];
268
+ if (lines.length < 2 || lines.at(-1) !== END_PATCH) return [];
269
+
270
+ const operations: ApplyPatchOperation[] = [];
271
+ let index = 1;
272
+ while (index < lines.length - 1) {
273
+ const line = lines[index]!;
274
+ let parsed: { operation: ApplyPatchOperation; nextIndex: number } | { error: true } | null =
275
+ null;
276
+ if (line.startsWith(ADD_FILE)) parsed = parseAddFilePatch(lines, index);
277
+ else if (line.startsWith(DELETE_FILE)) parsed = parseDeleteFilePatch(lines, index);
278
+ else if (line.startsWith(UPDATE_FILE)) parsed = parseUpdateFilePatch(lines, index);
279
+ else return [];
280
+ if (!parsed || "error" in parsed) return [];
281
+ operations.push(parsed.operation);
282
+ index = parsed.nextIndex;
283
+ }
284
+ // Match the SDK: Begin/End with no file ops is not a valid patch.
285
+ return operations.length > 0 ? operations : [];
286
+ }
287
+
288
+ function parsePatchHeader(line: string, prefix: string): string | null {
289
+ const path = line.slice(prefix.length).trim();
290
+ return path || null;
291
+ }
292
+
293
+ function isFileOperationHeader(line: string): boolean {
294
+ return line.startsWith(ADD_FILE) || line.startsWith(DELETE_FILE) || line.startsWith(UPDATE_FILE);
295
+ }
296
+
297
+ function joinDiff(lines: string[]): string {
298
+ return `${lines.join("\n")}\n`;
299
+ }
300
+
301
+ function parseAddFilePatch(
302
+ lines: string[],
303
+ index: number,
304
+ ): { operation: ApplyPatchOperation; nextIndex: number } | { error: true } {
305
+ const path = parsePatchHeader(lines[index]!, ADD_FILE);
306
+ if (!path) return { error: true };
307
+ index += 1;
308
+ const diffLines: string[] = [];
309
+ while (index < lines.length - 1 && !isFileOperationHeader(lines[index]!)) {
310
+ const line = lines[index]!;
311
+ if (!line.startsWith("+")) return { error: true };
312
+ diffLines.push(line);
313
+ index += 1;
314
+ }
315
+ if (diffLines.length === 0) return { error: true };
316
+ return {
317
+ operation: { type: "create_file", path, diff: joinDiff(diffLines) },
318
+ nextIndex: index,
223
319
  };
224
- if (Array.isArray(r.operations)) {
225
- return r.operations;
320
+ }
321
+
322
+ function parseDeleteFilePatch(
323
+ lines: string[],
324
+ index: number,
325
+ ): { operation: ApplyPatchOperation; nextIndex: number } | { error: true } {
326
+ const path = parsePatchHeader(lines[index]!, DELETE_FILE);
327
+ if (!path) return { error: true };
328
+ index += 1;
329
+ if (index < lines.length - 1 && !isFileOperationHeader(lines[index]!)) {
330
+ return { error: true };
331
+ }
332
+ return { operation: { type: "delete_file", path }, nextIndex: index };
333
+ }
334
+
335
+ function parseUpdateFilePatch(
336
+ lines: string[],
337
+ index: number,
338
+ ): { operation: ApplyPatchOperation; nextIndex: number } | { error: true } {
339
+ const path = parsePatchHeader(lines[index]!, UPDATE_FILE);
340
+ if (!path) return { error: true };
341
+ index += 1;
342
+ let moveTo: string | undefined;
343
+ if (index < lines.length - 1 && lines[index]!.startsWith(MOVE_TO)) {
344
+ const parsedMoveTo = parsePatchHeader(lines[index]!, MOVE_TO);
345
+ if (!parsedMoveTo) return { error: true };
346
+ moveTo = parsedMoveTo;
347
+ index += 1;
348
+ }
349
+ const diffLines: string[] = [];
350
+ while (index < lines.length - 1 && !isFileOperationHeader(lines[index]!)) {
351
+ diffLines.push(lines[index]!);
352
+ index += 1;
353
+ }
354
+ if (diffLines.length === 0 && !moveTo) return { error: true };
355
+ return {
356
+ operation: {
357
+ type: "update_file",
358
+ path,
359
+ diff: diffLines.length > 0 ? joinDiff(diffLines) : "",
360
+ ...(moveTo ? { moveTo } : {}),
361
+ },
362
+ nextIndex: index,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Normalize every apply_patch payload the Agents SDK accepts into structured
368
+ * ops — hosted `{ operation }` / `{ operations }`, function-tool `{ patch }`,
369
+ * `command` tuple, flat op, freeform string, or op array.
370
+ */
371
+ export function applyPatchOps(raw: unknown): ApplyPatchOperation[] {
372
+ if (raw == null) return [];
373
+ if (typeof raw === "string") {
374
+ const trimmed = raw.trimStart();
375
+ if (trimmed.startsWith(BEGIN_PATCH)) return freeformApplyPatchOps(trimmed);
376
+ const parsed = tryParseJson(trimmed);
377
+ return parsed === undefined ? [] : applyPatchOps(parsed);
378
+ }
379
+ if (Array.isArray(raw)) return parseStructuredOperations(raw);
380
+ if (!isRecord(raw)) return [];
381
+
382
+ if (typeof raw.patch === "string") return freeformApplyPatchOps(raw.patch);
383
+ if (Array.isArray(raw.command)) {
384
+ const [commandName, patch] = raw.command;
385
+ if (commandName === "apply_patch" && typeof patch === "string") {
386
+ return freeformApplyPatchOps(patch);
387
+ }
226
388
  }
227
- return r.operation ? [r.operation] : [];
389
+ // Empty `operations: []` is not authoritative — fall through to operation/flat.
390
+ if (Array.isArray(raw.operations) && raw.operations.length > 0) {
391
+ return parseStructuredOperations(raw.operations);
392
+ }
393
+ if (raw.operation !== undefined) {
394
+ const op = asApplyPatchOperation(raw.operation);
395
+ return op ? [op] : [];
396
+ }
397
+ // Flat single op: `{ type, path, diff?, moveTo? }`.
398
+ const flat = asApplyPatchOperation(raw);
399
+ return flat ? [flat] : [];
228
400
  }
229
401
 
230
- /** Ops from provider `raw`, or from function-tool arguments when raw is empty. */
402
+ /** Ops from provider `raw` and/or function-tool arguments (Codex path). */
231
403
  export function applyPatchOpsFromToolItem(item: {
232
404
  raw: unknown;
233
405
  arguments: unknown;
234
406
  }): ApplyPatchOperation[] {
235
407
  const fromRaw = applyPatchOps(item.raw);
236
- if (fromRaw.length > 0) {
237
- return fromRaw;
408
+ if (fromRaw.length > 0) return fromRaw;
409
+
410
+ // function_call envelopes sometimes keep the payload only under raw.arguments.
411
+ if (isRecord(item.raw)) {
412
+ const nested = item.raw.arguments ?? item.raw.input;
413
+ if (nested !== undefined && nested !== item.arguments) {
414
+ const fromNested = applyPatchOps(nested);
415
+ if (fromNested.length > 0) return fromNested;
416
+ }
417
+ }
418
+
419
+ if (item.arguments !== undefined && item.arguments !== null) {
420
+ return applyPatchOps(item.arguments);
238
421
  }
239
- const args = parseToolArgs(item.arguments);
240
- return applyPatchOps(args);
422
+ return [];
241
423
  }
242
424
 
243
425
  /**
244
- * True when a tool item is an `apply_patch_call` by its provider-native
245
- * `raw.type` (the live-wire source of truth) or by tool `name` (first-party
246
- * replays that omit `raw`). Centralizes the rawType-or-name check.
426
+ * True when a tool item is apply_patchhosted `raw.type === "apply_patch_call"`,
427
+ * function-tool `name` `apply_patch` / `apply_patch_call`, or an MCP-prefixed
428
+ * `…__apply_patch` leaf. Centralizes the rawType-or-name check.
247
429
  */
248
430
  export function isApplyPatch(item: { name: string; raw: unknown }): boolean {
249
431
  const type =
@@ -54,6 +54,11 @@ const WORKER_MESSAGE_TOOL = "session_send_message";
54
54
  * `"agent"`. That keeps mid-turn goal tools from splitting the step rail with
55
55
  * a breakaway GoalRow pill. Non-agent goal events (API, create-session,
56
56
  * system auto-pause, continuations) still render as landmarks.
57
+ *
58
+ * Solo `goal_continuation` machine-input batches are also suppressed: the
59
+ * paired `goal.continuation` GoalRow already marks the tick; rendering both
60
+ * restates the goal text. Mixed batches (continuation + other kinds) still
61
+ * render as machine-input rows.
57
62
  */
58
63
  const LANDMARK_ONLY_TOOL_LEAVES = new Set(["memory_save", "memory_correct"]);
59
64
 
@@ -140,6 +145,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
140
145
  case "system.update.delivered": {
141
146
  const inputs = machineInputMembers(payload.members);
142
147
  if (inputs.length === 0) break;
148
+ // Goal continuations already land as `goal.continuation` GoalRows.
149
+ // A solo continuation batch would duplicate that landmark + dump the
150
+ // model-facing prompt — skip chrome for that case only.
151
+ if (inputs.every((member) => member.kind === "goal_continuation")) {
152
+ break;
153
+ }
143
154
  closeStreamingTail();
144
155
  items.push({
145
156
  kind: "machine-input-batch",
@@ -17,6 +17,7 @@ import {
17
17
  MessagesSquareIcon,
18
18
  MessageSquareIcon,
19
19
  MousePointer2Icon,
20
+ PackageSearchIcon,
20
21
  PanelsTopLeftIcon,
21
22
  PlugIcon,
22
23
  SearchIcon,
@@ -364,12 +365,13 @@ function ApplyPatchRenderer({ item }: ToolRendererProps) {
364
365
  </RunningPreview>
365
366
  }
366
367
  >
367
- {ops.map((op) => {
368
+ {ops.map((op, index) => {
368
369
  const file = safeParseOp(op);
370
+ const key = `${op.type}:${op.path}:${index}`;
369
371
  return file ? (
370
- <ToolDiff key={op.path} files={[file]} />
372
+ <ToolDiff key={key} files={[file]} />
371
373
  ) : (
372
- <div key={op.path}>
374
+ <div key={key}>
373
375
  <p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{op.path}</p>
374
376
  <RawPatch diff={op.diff ?? ""} />
375
377
  </div>
@@ -421,10 +423,11 @@ function ApplyPatchRenderer({ item }: ToolRendererProps) {
421
423
  >
422
424
  {ops.map((op, index) => {
423
425
  const file = parsed[index];
426
+ const key = `${op.type}:${op.path}:${index}`;
424
427
  return file ? (
425
- <ToolDiff key={op.path} files={[file]} />
428
+ <ToolDiff key={key} files={[file]} />
426
429
  ) : (
427
- <div key={op.path}>
430
+ <div key={key}>
428
431
  <p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{op.path}</p>
429
432
  <RawPatch diff={op.diff ?? ""} />
430
433
  </div>
@@ -1031,6 +1034,204 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
1031
1034
  );
1032
1035
  }
1033
1036
 
1037
+ /* ---- tool_search (progressive MCP disclosure) ------------------------------ */
1038
+
1039
+ type DisclosedTool = {
1040
+ /** Full wire name (`server__leaf` or bare). */
1041
+ name: string;
1042
+ /** Server / namespace prefix before `__`, when present. */
1043
+ source: string | null;
1044
+ /** Leaf tool name after `__`. */
1045
+ leaf: string;
1046
+ };
1047
+
1048
+ function splitToolWireName(name: string): DisclosedTool {
1049
+ const boundary = name.indexOf("__");
1050
+ if (boundary <= 0) {
1051
+ return { name, source: null, leaf: name };
1052
+ }
1053
+ return {
1054
+ name,
1055
+ source: name.slice(0, boundary),
1056
+ leaf: name.slice(boundary + 2),
1057
+ };
1058
+ }
1059
+
1060
+ /** Capability query from live tool_search args (object or JSON string). */
1061
+ function toolSearchQuery(item: ToolRendererProps["item"]): string {
1062
+ const fromArgs = parseToolArgs(item.arguments);
1063
+ if (typeof fromArgs.query === "string" && fromArgs.query.trim()) {
1064
+ return fromArgs.query.trim();
1065
+ }
1066
+ const raw = item.raw;
1067
+ if (raw && typeof raw === "object") {
1068
+ const rawArgs = (raw as { arguments?: unknown }).arguments;
1069
+ if (typeof rawArgs === "string" && rawArgs.trim()) {
1070
+ const parsed = tryParseJson(rawArgs);
1071
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1072
+ const query = (parsed as { query?: unknown }).query;
1073
+ if (typeof query === "string" && query.trim()) {
1074
+ return query.trim();
1075
+ }
1076
+ }
1077
+ } else if (rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)) {
1078
+ const query = (rawArgs as { query?: unknown }).query;
1079
+ if (typeof query === "string" && query.trim()) {
1080
+ return query.trim();
1081
+ }
1082
+ }
1083
+ }
1084
+ return "";
1085
+ }
1086
+
1087
+ /**
1088
+ * Parse disclosed tools from the runtime event shape.
1089
+ * `normalizeSdkEvent` collapses `tool_search_output.tools[]` into text:
1090
+ * "Disclosed tools: a, b" | "No matching tools found."
1091
+ * Also accept a structured `tools` array when a host/enricher preserves it.
1092
+ */
1093
+ function parseDisclosedTools(output: unknown): DisclosedTool[] | null {
1094
+ if (output && typeof output === "object" && !Array.isArray(output)) {
1095
+ const tools = (output as { tools?: unknown }).tools;
1096
+ if (Array.isArray(tools)) {
1097
+ return tools
1098
+ .map((tool) => {
1099
+ if (typeof tool === "string" && tool.trim()) {
1100
+ return splitToolWireName(tool.trim());
1101
+ }
1102
+ if (
1103
+ tool &&
1104
+ typeof tool === "object" &&
1105
+ typeof (tool as { name?: unknown }).name === "string"
1106
+ ) {
1107
+ const name = (tool as { name: string }).name.trim();
1108
+ return name ? splitToolWireName(name) : null;
1109
+ }
1110
+ return null;
1111
+ })
1112
+ .filter((tool): tool is DisclosedTool => tool != null);
1113
+ }
1114
+ }
1115
+
1116
+ const { text } = unwrapMcpOutput(output);
1117
+ const trimmed = text.trim();
1118
+ if (!trimmed) {
1119
+ return null;
1120
+ }
1121
+ if (/^no matching tools found\.?$/i.test(trimmed)) {
1122
+ return [];
1123
+ }
1124
+ const disclosed = trimmed.match(/^disclosed tools:\s*(.+)$/i);
1125
+ if (disclosed?.[1]) {
1126
+ return disclosed[1]
1127
+ .split(",")
1128
+ .map((part) => part.trim())
1129
+ .filter(Boolean)
1130
+ .map(splitToolWireName);
1131
+ }
1132
+ const parsed = tryParseJson(trimmed);
1133
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1134
+ return parseDisclosedTools(parsed);
1135
+ }
1136
+ return null;
1137
+ }
1138
+
1139
+ function toolSearchPreview(tools: DisclosedTool[] | null, cancelled: boolean): string | undefined {
1140
+ if (cancelled) {
1141
+ return undefined;
1142
+ }
1143
+ if (!tools) {
1144
+ return "Done";
1145
+ }
1146
+ if (tools.length === 0) {
1147
+ return "No matches";
1148
+ }
1149
+ if (tools.length === 1) {
1150
+ return tools[0]!.leaf;
1151
+ }
1152
+ const head = tools[0]!.leaf;
1153
+ return `${tools.length} tools · ${truncatePreview(head, 28)}`;
1154
+ }
1155
+
1156
+ function ToolSearchRenderer({ item }: ToolRendererProps) {
1157
+ const query = toolSearchQuery(item);
1158
+ const icon = <PackageSearchIcon className={ICON_SIZE} />;
1159
+ const running = item.status === "running";
1160
+ const queryPreview = query ? truncatePreview(query, 64) : "";
1161
+
1162
+ if (running) {
1163
+ return (
1164
+ <ActivityDisclosure
1165
+ icon={icon}
1166
+ iconTone="running"
1167
+ title="Looking up tools"
1168
+ running
1169
+ preview={
1170
+ queryPreview ? (
1171
+ <RunningPreview>{queryPreview}</RunningPreview>
1172
+ ) : (
1173
+ <RunningPreview>Matching capabilities…</RunningPreview>
1174
+ )
1175
+ }
1176
+ >
1177
+ {query ? <BodyNote>capability query: {query}</BodyNote> : null}
1178
+ <PayloadBlock label="Arguments" value={redactSecrets(parseToolArgs(item.arguments))} />
1179
+ </ActivityDisclosure>
1180
+ );
1181
+ }
1182
+
1183
+ const { text: outText, isError } = unwrapMcpOutput(item.output);
1184
+ if ((isError || item.status === "failed") && item.status !== "cancelled") {
1185
+ return (
1186
+ <ActivityDisclosure
1187
+ icon={icon}
1188
+ iconTone="failed"
1189
+ title="Tool lookup failed"
1190
+ failed
1191
+ preview={truncatePreview(outText, 80) || queryPreview || "Lookup failed"}
1192
+ >
1193
+ {query ? <BodyNote>capability query: {query}</BodyNote> : null}
1194
+ <PayloadBlock label="Arguments" value={redactSecrets(parseToolArgs(item.arguments))} />
1195
+ <PayloadBlock label="Error" value={outText} failed />
1196
+ </ActivityDisclosure>
1197
+ );
1198
+ }
1199
+
1200
+ const tools = parseDisclosedTools(item.output);
1201
+ const preview = toolSearchPreview(tools, item.status === "cancelled");
1202
+
1203
+ return (
1204
+ <ActivityDisclosure
1205
+ icon={icon}
1206
+ iconTone="muted"
1207
+ title="Looked up tools"
1208
+ cancelled={item.status === "cancelled"}
1209
+ preview={preview}
1210
+ >
1211
+ {query ? <BodyNote>capability query: {query}</BodyNote> : null}
1212
+ {tools && tools.length > 0 ? (
1213
+ <ul className="grid gap-1.5">
1214
+ {tools.slice(0, 12).map((tool) => (
1215
+ <li key={tool.name} className="flex min-w-0 items-baseline gap-2">
1216
+ {tool.source ? (
1217
+ <span className="shrink-0 text-og-xs text-og-fg-subtle">{tool.source}</span>
1218
+ ) : null}
1219
+ <span className="truncate font-mono text-og-sm text-og-fg">{tool.leaf}</span>
1220
+ </li>
1221
+ ))}
1222
+ {tools.length > 12 ? (
1223
+ <li className="text-og-xs text-og-fg-muted">+{tools.length - 12} more</li>
1224
+ ) : null}
1225
+ </ul>
1226
+ ) : tools && tools.length === 0 ? (
1227
+ <BodyNote>no deferred tools matched this capability query.</BodyNote>
1228
+ ) : null}
1229
+ <PayloadBlock label="Arguments" value={redactSecrets(parseToolArgs(item.arguments))} />
1230
+ {tools == null && outText ? <PayloadBlock label="Result" value={outText} /> : null}
1231
+ </ActivityDisclosure>
1232
+ );
1233
+ }
1234
+
1034
1235
  /* ---- docs / knowledge search ----------------------------------------------- */
1035
1236
 
1036
1237
  function DocsSearchRenderer({ item }: ToolRendererProps) {
@@ -1575,9 +1776,11 @@ function GenericToolIcon({ name }: { name: string }) {
1575
1776
  leaf.includes("knowledge") ||
1576
1777
  leaf === "list_document_bases"
1577
1778
  ? FileSearchIcon
1578
- : leaf === "tool_search" || leaf === "load_skill"
1579
- ? PlugIcon
1580
- : WrenchIcon;
1779
+ : leaf === "tool_search"
1780
+ ? PackageSearchIcon
1781
+ : leaf === "load_skill"
1782
+ ? PlugIcon
1783
+ : WrenchIcon;
1581
1784
  return <Icon className={ICON_SIZE} />;
1582
1785
  }
1583
1786
 
@@ -1589,6 +1792,7 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
1589
1792
  { match: "rawType", type: "apply_patch_call", render: ApplyPatchRenderer },
1590
1793
  { match: "rawType", type: "computer_call", render: ComputerCallRenderer },
1591
1794
  { match: "rawType", type: "hosted_tool_call", render: WebSearchRenderer },
1795
+ { match: "rawType", type: "tool_search_call", render: ToolSearchRenderer },
1592
1796
  // First-party sandbox + MCP tools resolve by name (exact or MCP leaf).
1593
1797
  { match: "name", name: "exec_command", render: ExecRenderer },
1594
1798
  { match: "name", name: "request_human_input", render: AskRenderer },
@@ -1607,6 +1811,7 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
1607
1811
  { match: "name", name: "computer_keypress", render: ComputerCallRenderer },
1608
1812
  { match: "name", name: "computer_drag", render: ComputerCallRenderer },
1609
1813
  { match: "name", name: "web_search_call", render: WebSearchRenderer },
1814
+ { match: "name", name: "tool_search", render: ToolSearchRenderer },
1610
1815
  { match: "name", name: "view_image", render: ViewImageRenderer },
1611
1816
  { match: "name", name: "environment_set_variable", render: SecretSetRenderer },
1612
1817
  { match: "name", name: "variable_set_set_variable", render: SecretSetRenderer },
@@ -16,7 +16,12 @@ import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
16
16
  import { useForcedDefaultOpen } from "./disclosure-context";
17
17
  import { useEntranceAnimation } from "./entrance";
18
18
  import { useFoldMemory, type FoldRestingState } from "./fold-memory";
19
- import { applyPatchOps, isApplyPatch, mediaPreviewFact, screenshotDataUrl } from "./parsers";
19
+ import {
20
+ applyPatchOpsFromToolItem,
21
+ isApplyPatch,
22
+ mediaPreviewFact,
23
+ screenshotDataUrl,
24
+ } from "./parsers";
20
25
  import { rawTypeOf } from "./registry";
21
26
  import type { ActivityItem, ToolCallItem, TurnOutcome } from "./types";
22
27
  export type { TurnOutcome } from "./types";
@@ -571,7 +576,7 @@ const BUILT_IN_TURN_SUMMARY_FACETS: readonly TurnSummaryFacet[] = Object.freeze(
571
576
  let files = 0;
572
577
  for (const item of toolCalls) {
573
578
  if (isApplyPatch(item)) {
574
- files += applyPatchOps(item.raw).length;
579
+ files += applyPatchOpsFromToolItem(item).length;
575
580
  }
576
581
  }
577
582
  return files ? { content: `${files} ${files === 1 ? "file" : "files"} edited` } : null;
package/styles/tokens.css CHANGED
@@ -97,16 +97,18 @@
97
97
  --og-session-chrome-surface-open: color-mix(in oklch, var(--og-color-surface-2) 92%, transparent);
98
98
  --og-session-chrome-border: color-mix(in oklch, var(--og-color-border) 85%, transparent);
99
99
  --og-session-chrome-border-open: var(--og-color-border);
100
- --og-session-chrome-highlight: color-mix(in oklch, var(--og-color-bg) 72%, transparent);
100
+ /* Selected chip: solid lift vs open dock surface (not a faint wash). */
101
+ --og-session-chrome-highlight: var(--og-color-surface-3);
102
+ --og-session-chrome-highlight-ring: color-mix(in oklch, var(--og-color-fg) 16%, transparent);
101
103
  --og-session-chrome-shadow: 0 6px 22px -14px oklch(0 0 0 / 0.42);
102
104
  --og-session-chrome-shadow-open: 0 10px 28px -16px oklch(0 0 0 / 0.5);
103
105
  --og-session-chrome-radius: var(--og-radius-lg);
104
106
  --og-session-chrome-chip-min-height: 1.75rem;
105
107
  --og-session-chrome-chip-pad-x: 0.5rem;
106
108
  --og-session-chrome-chip-gap: 0.125rem;
107
- --og-session-chrome-rail-pad: 0.2rem;
108
- --og-session-chrome-panel-pad-x: 0.5rem;
109
- --og-session-chrome-panel-pad-y: 0.4rem;
109
+ --og-session-chrome-rail-pad: 0.15rem;
110
+ --og-session-chrome-panel-pad-x: 0.45rem;
111
+ --og-session-chrome-panel-pad-y: 0.3rem;
110
112
  /* Expanded agents/queue/inbox/goal body — scroll inside, never grow unbound. */
111
113
  --og-session-chrome-panel-max-height: min(18rem, 40dvh);
112
114
  --og-session-chrome-duration: 220ms;
@@ -158,7 +160,8 @@
158
160
  --og-session-chrome-surface-open: var(--og-color-surface-2);
159
161
  --og-session-chrome-border: color-mix(in oklch, var(--og-color-border) 90%, transparent);
160
162
  --og-session-chrome-border-open: var(--og-color-border);
161
- --og-session-chrome-highlight: color-mix(in oklch, var(--og-color-surface-1) 88%, transparent);
163
+ --og-session-chrome-highlight: var(--og-color-surface-3);
164
+ --og-session-chrome-highlight-ring: color-mix(in oklch, var(--og-color-fg) 12%, transparent);
162
165
  --og-session-chrome-shadow: 0 4px 16px -10px oklch(0.2 0.02 260 / 0.14);
163
166
  --og-session-chrome-shadow-open: 0 8px 22px -12px oklch(0.2 0.02 260 / 0.18);
164
167
  --og-session-chrome-row-hover: color-mix(in oklch, var(--og-color-surface-3) 70%, transparent);