@opengeni/react 0.34.1 → 0.34.3

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 (60) hide show
  1. package/dist/{chunk-A2WITZTI.js → chunk-4NMH6UJ3.js} +2 -2
  2. package/dist/{chunk-GZ4KR67U.js → chunk-ALA3UGWB.js} +2 -2
  3. package/dist/chunk-ATSTR5P3.js +138 -0
  4. package/dist/chunk-ATSTR5P3.js.map +1 -0
  5. package/dist/{chunk-KZRUYCL4.js → chunk-FT2TXNGZ.js} +5 -2
  6. package/dist/chunk-FT2TXNGZ.js.map +1 -0
  7. package/dist/{chunk-4QOQ2DSR.js → chunk-LAKLF4PA.js} +14 -1
  8. package/dist/chunk-LAKLF4PA.js.map +1 -0
  9. package/dist/{chunk-TCCE5ZU5.js → chunk-MEGI4FS6.js} +38 -9
  10. package/dist/chunk-MEGI4FS6.js.map +1 -0
  11. package/dist/{chunk-MS3FBZ6A.js → chunk-PVW56EVR.js} +105 -62
  12. package/dist/chunk-PVW56EVR.js.map +1 -0
  13. package/dist/{chunk-L7R5RK6A.js → chunk-QRYJCWGD.js} +1199 -897
  14. package/dist/chunk-QRYJCWGD.js.map +1 -0
  15. package/dist/components/chat-composer.d.ts +2 -1
  16. package/dist/components/composer.d.ts +8 -2
  17. package/dist/components/copy-button.d.ts +4 -2
  18. package/dist/components/tip-follow.d.ts +3 -0
  19. package/dist/composer.d.ts +2 -1
  20. package/dist/composer.js +4 -3
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +106 -101
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/format.d.ts +2 -0
  25. package/dist/machines.js +2 -2
  26. package/dist/session-ui.js +4 -3
  27. package/dist/session.js +4 -4
  28. package/dist/timeline/index.d.ts +1 -1
  29. package/dist/timeline/parsers.d.ts +5 -0
  30. package/dist/timeline/projection.d.ts +1 -12
  31. package/dist/timeline/tool-display-name.d.ts +17 -6
  32. package/dist/workstream-control-event.d.ts +6 -0
  33. package/package.json +1 -1
  34. package/src/components/chat-composer.tsx +1 -1
  35. package/src/components/composer-transcription-control.tsx +70 -57
  36. package/src/components/composer.tsx +197 -126
  37. package/src/components/copy-button.tsx +27 -8
  38. package/src/components/message-timeline.tsx +236 -39
  39. package/src/components/tip-follow.ts +15 -0
  40. package/src/composer.ts +1 -1
  41. package/src/hooks/use-composer.ts +7 -1
  42. package/src/hooks/use-workspace-capture.ts +6 -0
  43. package/src/index.ts +2 -1
  44. package/src/lib/format.ts +14 -0
  45. package/src/timeline/index.ts +2 -0
  46. package/src/timeline/parsers.ts +18 -1
  47. package/src/timeline/projection.ts +50 -20
  48. package/src/timeline/registry.ts +15 -2
  49. package/src/timeline/shared.tsx +30 -28
  50. package/src/timeline/tool-display-name.ts +30 -11
  51. package/src/timeline/tool-renderers.tsx +347 -16
  52. package/src/timeline/turn-summary.tsx +5 -2
  53. package/src/workstream-control-event.ts +6 -0
  54. package/dist/chunk-4QOQ2DSR.js.map +0 -1
  55. package/dist/chunk-KZRUYCL4.js.map +0 -1
  56. package/dist/chunk-L7R5RK6A.js.map +0 -1
  57. package/dist/chunk-MS3FBZ6A.js.map +0 -1
  58. package/dist/chunk-TCCE5ZU5.js.map +0 -1
  59. /package/dist/{chunk-A2WITZTI.js.map → chunk-4NMH6UJ3.js.map} +0 -0
  60. /package/dist/{chunk-GZ4KR67U.js.map → chunk-ALA3UGWB.js.map} +0 -0
@@ -6,6 +6,7 @@ import {
6
6
  isCreditExhaustion,
7
7
  tryParseJson,
8
8
  } from "../lib/format";
9
+ import { mcpToolLeaf, toolMatchesLeaf } from "./tool-display-name";
9
10
  import type {
10
11
  AgentMessageItem,
11
12
  ActivityItem,
@@ -22,12 +23,8 @@ import type {
22
23
  ToolCallItem,
23
24
  WorkerItem,
24
25
  } from "./types";
25
- /** Readable label for a tool call, without leaking an MCP server prefix. */
26
- export function toolDisplayName(name: string): string {
27
- const boundary = name.indexOf("__");
28
- const toolPart = boundary >= 0 ? name.slice(boundary + 2) : name;
29
- return toolPart.replace(/[_-]+/g, " ").trim();
30
- }
26
+
27
+ export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-name";
31
28
 
32
29
  /* ----------------------------------------------------------------------------
33
30
  Timeline projection
@@ -43,10 +40,23 @@ export function toolDisplayName(name: string): string {
43
40
  memoized, unit-tested, and re-run incrementally as new events stream in.
44
41
  -------------------------------------------------------------------------- */
45
42
 
46
- /** Tool names on the first-party OpenGeni MCP server that operate on sessions. */
43
+ /** Tool leaves on the first-party OpenGeni MCP server that operate on sessions. */
47
44
  const WORKER_SPAWN_TOOL = "session_create";
48
45
  const WORKER_MESSAGE_TOOL = "session_send_message";
49
46
 
47
+ /**
48
+ * Tools whose durable side-effect events already own the timeline (MemoryRow).
49
+ * Emitting a generic tool-call too is double chrome — skip the call.
50
+ *
51
+ * Goal tools are intentionally NOT landmark-only: an agent `goal_set` /
52
+ * `goal_update` / `goal_complete` / `goal_pause` stays an in-cluster tool row,
53
+ * and the matching `goal.*` session event is suppressed below when `actor` is
54
+ * `"agent"`. That keeps mid-turn goal tools from splitting the step rail with
55
+ * a breakaway GoalRow pill. Non-agent goal events (API, create-session,
56
+ * system auto-pause, continuations) still render as landmarks.
57
+ */
58
+ const LANDMARK_ONLY_TOOL_LEAVES = new Set(["memory_save", "memory_correct"]);
59
+
50
60
  export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
51
61
  const items: TimelineItem[] = [];
52
62
  const prescan = prescanTurnAnchors(events);
@@ -186,6 +196,9 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
186
196
  open.text = text || open.text;
187
197
  }
188
198
  open.streaming = false;
199
+ // Completion time is what the footer shows ("finished at"); keep the
200
+ // first-delta stamp only until this event arrives.
201
+ open.occurredAt = event.occurredAt;
189
202
  // The SDK can emit a hosted-tool item only after its provider-native
190
203
  // operation has completed, even though answer deltas were already
191
204
  // streamed. The completed message event is the durable ordering
@@ -237,13 +250,16 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
237
250
  const callId = typeof payload.id === "string" ? payload.id : null;
238
251
  const args = payload.arguments ?? null;
239
252
  closeStreamingTail();
240
- if (name === WORKER_SPAWN_TOOL || name === WORKER_MESSAGE_TOOL) {
253
+ if (
254
+ toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ||
255
+ toolMatchesLeaf(name, WORKER_MESSAGE_TOOL)
256
+ ) {
241
257
  items.push({
242
258
  kind: "worker",
243
259
  id: event.id,
244
260
  turnId,
245
261
  callId,
246
- action: name === WORKER_SPAWN_TOOL ? "spawn" : "message",
262
+ action: toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ? "spawn" : "message",
247
263
  prompt: workerPrompt(args),
248
264
  workerSessionId: extractSessionRef(args),
249
265
  status: "running",
@@ -251,6 +267,10 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
251
267
  });
252
268
  break;
253
269
  }
270
+ if (LANDMARK_ONLY_TOOL_LEAVES.has(mcpToolLeaf(name))) {
271
+ // Goal/memory landmarks arrive as goal.* / memory.* events.
272
+ break;
273
+ }
254
274
  // Live Responses `web_search_call` events and the later SDK
255
275
  // `RunToolCallItem` share the same item id. Merge so mid-stream cards
256
276
  // do not duplicate when the step finally materializes.
@@ -639,6 +659,11 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
639
659
  case "goal.resumed":
640
660
  case "goal.cleared":
641
661
  case "goal.continuation": {
662
+ // Agent tool mutations already appear as tool-call rows in the activity
663
+ // cluster. Re-emitting them as GoalRow landmarks splits "N steps" mid-turn.
664
+ if (shouldSuppressAgentGoalLandmark(event.type, payload)) {
665
+ break;
666
+ }
642
667
  items.push({
643
668
  kind: "goal",
644
669
  id: event.id,
@@ -1423,6 +1448,22 @@ function goalText(payload: Record<string, unknown>): string | null {
1423
1448
  return null;
1424
1449
  }
1425
1450
 
1451
+ /**
1452
+ * Agent-owned goal mutations already have an in-cluster tool row. Suppress the
1453
+ * breakaway landmark for those only. `goal.completed` has no actor field today
1454
+ * and is only emitted by the agent tool, so it is always suppressed. API /
1455
+ * system / create-session / continuation landmarks stay visible.
1456
+ */
1457
+ function shouldSuppressAgentGoalLandmark(type: string, payload: Record<string, unknown>): boolean {
1458
+ if (type === "goal.completed") {
1459
+ return true;
1460
+ }
1461
+ if (type === "goal.set" || type === "goal.updated" || type === "goal.paused") {
1462
+ return payload.actor === "agent";
1463
+ }
1464
+ return false;
1465
+ }
1466
+
1426
1467
  /**
1427
1468
  * Fold a `memory.saved` / `memory.corrected` event into a {@link MemoryItem}.
1428
1469
  * Reads DEFENSIVELY (the payload is untyped `unknown`, no Zod schema): a missing
@@ -1563,14 +1604,3 @@ export function extractSessionRef(value: unknown, depth = 0): string | null {
1563
1604
  function looksLikeId(value: string): boolean {
1564
1605
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
1565
1606
  }
1566
-
1567
- /**
1568
- * Readable label for a tool call ("session_create" -> "session create").
1569
- *
1570
- * MCP tools are namespaced `<serverId>__<toolName>` (see prefixedMcpToolName),
1571
- * and for catalog-imported servers that serverId is an opaque slug+hash
1572
- * ("mcp-integrations-sh-supabase-com-34ed9dcf1390-0i6tcf8"). De-slugging the
1573
- * whole thing leaked that id into the timeline; strip the server prefix and show
1574
- * just the tool ("list organizations"). Names without the `__` boundary (plain
1575
- * built-ins like "session_create") are unaffected.
1576
- */
@@ -1,4 +1,5 @@
1
1
  import type { ComponentType } from "react";
2
+ import { mcpToolLeaf } from "./tool-display-name";
2
3
  import type { ToolCallItem } from "./types";
3
4
 
4
5
  /* ----------------------------------------------------------------------------
@@ -11,7 +12,8 @@ import type { ToolCallItem } from "./types";
11
12
  Resolution order (most → least specific):
12
13
  1. exact match on `raw.type` (e.g. "apply_patch_call", "computer_call")
13
14
  2. exact match on the tool `name` (e.g. "exec_command", "web_search_call")
14
- 3. the registry's generic fallback
15
+ 3. leaf match after MCP `__` prefix (e.g. opengeni__environment_set_variable)
16
+ 4. the registry's generic fallback
15
17
 
16
18
  A consumer extends the defaults without forking by passing overrides to
17
19
  `createToolRegistry` — e.g. a custom renderer for their own MCP tool, or a
@@ -89,7 +91,18 @@ export function createToolRegistry(
89
91
  return byType;
90
92
  }
91
93
  }
92
- return byName.get(item.name) ?? fallback;
94
+ const exact = byName.get(item.name);
95
+ if (exact) {
96
+ return exact;
97
+ }
98
+ const leaf = mcpToolLeaf(item.name);
99
+ if (leaf !== item.name) {
100
+ const byLeaf = byName.get(leaf);
101
+ if (byLeaf) {
102
+ return byLeaf;
103
+ }
104
+ }
105
+ return fallback;
93
106
  };
94
107
 
95
108
  return { resolve, fallback };
@@ -1,6 +1,5 @@
1
1
  import { CameraIcon, CameraOffIcon, ChevronRightIcon } from "lucide-react";
2
2
  import { useState, type ReactNode } from "react";
3
- import { Collapsible } from "radix-ui";
4
3
  import { cn } from "../lib/cn";
5
4
  import { stringifyPayload } from "../lib/format";
6
5
  import { useForcedDefaultOpen } from "./disclosure-context";
@@ -207,35 +206,38 @@ export function ActivityDisclosure({
207
206
  );
208
207
  }
209
208
 
209
+ // Native disclosure (not radix-ui Collapsible): importing `radix-ui` from the
210
+ // timeline registry pulls Popper into the session share-graph and crashes
211
+ // lazy workspace settings (`createPopperScope is not a function`).
210
212
  return (
211
- <Collapsible.Root open={open} onOpenChange={setOpen}>
212
- <Collapsible.Trigger asChild>
213
- <div
214
- role="button"
215
- tabIndex={0}
216
- // Space/Enter activate a native button; Radix doesn't add this for a
217
- // non-button asChild child, so we toggle here. preventDefault on Space
218
- // stops the page from scrolling.
219
- onKeyDown={(event) => {
220
- if (event.key === "Enter" || event.key === " ") {
221
- event.preventDefault();
222
- setOpen((prev) => !prev);
223
- }
224
- }}
225
- data-status={dataStatus}
226
- className={cn(
227
- rowClass,
228
- "cursor-pointer outline-none hover:bg-og-surface-1 hover:text-og-fg",
229
- "focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-0",
230
- )}
231
- >
232
- {inner}
213
+ <div>
214
+ <div
215
+ role="button"
216
+ tabIndex={0}
217
+ aria-expanded={open}
218
+ data-state={open ? "open" : "closed"}
219
+ onClick={() => setOpen((prev) => !prev)}
220
+ onKeyDown={(event) => {
221
+ if (event.key === "Enter" || event.key === " ") {
222
+ event.preventDefault();
223
+ setOpen((prev) => !prev);
224
+ }
225
+ }}
226
+ data-status={dataStatus}
227
+ className={cn(
228
+ rowClass,
229
+ "cursor-pointer outline-none hover:bg-og-surface-1 hover:text-og-fg",
230
+ "focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-0",
231
+ )}
232
+ >
233
+ {inner}
234
+ </div>
235
+ {open ? (
236
+ <div className="mb-2 ml-7 mt-1.5 flex flex-col gap-2 overflow-hidden animate-og-expand">
237
+ {children}
233
238
  </div>
234
- </Collapsible.Trigger>
235
- <Collapsible.Content className="overflow-hidden data-[state=closed]:animate-og-collapse data-[state=open]:animate-og-expand">
236
- <div className="mb-2 ml-7 mt-1.5 flex flex-col gap-2">{children}</div>
237
- </Collapsible.Content>
238
- </Collapsible.Root>
239
+ ) : null}
240
+ </div>
239
241
  );
240
242
  }
241
243
 
@@ -1,16 +1,35 @@
1
1
  /**
2
- * Readable label for a tool call ("session_create" -> "session create").
2
+ * MCP / first-party tool naming helpers.
3
3
  *
4
- * MCP tools are namespaced `<serverId>__<toolName>` (see prefixedMcpToolName),
5
- * and for catalog-imported servers that serverId is an opaque slug+hash.
6
- * De-slugging the whole thing leaked that id into the timeline; strip the
7
- * server prefix and show just the tool. Names without the `__` boundary (plain
8
- * built-ins like "session_create") are unaffected.
4
+ * Wire names are often `<serverId>__<toolName>` (see prefixedMcpToolName).
5
+ * Matching and titles must use the leaf tool name so `opengeni__session_create`
6
+ * and bare `session_create` resolve the same UI without inventing previews
7
+ * from argument JSON.
9
8
  */
10
- export function toolDisplayName(name: string): string {
11
- // The prefix is a single LEFT boundary (`registryId__toolName`), so split on
12
- // the FIRST `__` — the tool name itself may contain `__` and must survive whole.
9
+
10
+ /** Leaf tool name after the first `__` server boundary (or the whole name). */
11
+ export function mcpToolLeaf(name: string): string {
13
12
  const boundary = name.indexOf("__");
14
- const toolPart = boundary >= 0 ? name.slice(boundary + 2) : name;
15
- return toolPart.replace(/[_-]+/g, " ").trim();
13
+ return boundary >= 0 ? name.slice(boundary + 2) : name;
14
+ }
15
+
16
+ /**
17
+ * True when `wireName` is exactly `leaf` or ends with `__${leaf}` (MCP prefix).
18
+ * Does not treat arbitrary suffixes as matches — the leaf must be the full
19
+ * right-hand side after `__`.
20
+ */
21
+ export function toolMatchesLeaf(wireName: string, leaf: string): boolean {
22
+ return wireName === leaf || wireName.endsWith(`__${leaf}`);
23
+ }
24
+
25
+ /**
26
+ * Readable label for a tool call ("session_create" / "opengeni__session_create"
27
+ * → "Session create"). Title-cases the first character of the leaf phrase.
28
+ */
29
+ export function toolDisplayName(name: string): string {
30
+ const phrase = mcpToolLeaf(name).replace(/[_-]+/g, " ").trim();
31
+ if (!phrase) {
32
+ return name;
33
+ }
34
+ return phrase.charAt(0).toUpperCase() + phrase.slice(1);
16
35
  }