@opengeni/react 0.34.2 → 0.35.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GZ4KR67U.js → chunk-ALA3UGWB.js} +2 -2
- package/dist/chunk-ATSTR5P3.js +138 -0
- package/dist/chunk-ATSTR5P3.js.map +1 -0
- package/dist/{chunk-L7R5RK6A.js → chunk-AXQQ2V2I.js} +1312 -926
- package/dist/chunk-AXQQ2V2I.js.map +1 -0
- package/dist/{chunk-KZRUYCL4.js → chunk-FT2TXNGZ.js} +5 -2
- package/dist/chunk-FT2TXNGZ.js.map +1 -0
- package/dist/{chunk-TCCE5ZU5.js → chunk-J2RVJ6JX.js} +39 -9
- package/dist/chunk-J2RVJ6JX.js.map +1 -0
- package/dist/{chunk-4QOQ2DSR.js → chunk-LAKLF4PA.js} +14 -1
- package/dist/chunk-LAKLF4PA.js.map +1 -0
- package/dist/{chunk-A2WITZTI.js → chunk-OKKMZDQF.js} +3 -2
- package/dist/chunk-OKKMZDQF.js.map +1 -0
- package/dist/{chunk-MS3FBZ6A.js → chunk-PVW56EVR.js} +105 -62
- package/dist/chunk-PVW56EVR.js.map +1 -0
- package/dist/components/chat-composer.d.ts +2 -1
- package/dist/components/composer.d.ts +8 -2
- package/dist/components/copy-button.d.ts +4 -2
- package/dist/components/tip-follow.d.ts +3 -0
- package/dist/composer.d.ts +2 -1
- package/dist/composer.js +4 -3
- package/dist/hooks/use-turn-queue.d.ts +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +105 -101
- package/dist/index.js.map +1 -1
- package/dist/lib/format.d.ts +2 -0
- package/dist/machines.js +2 -2
- package/dist/session-ui.js +4 -3
- package/dist/session.js +4 -4
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/parsers.d.ts +5 -0
- package/dist/timeline/projection.d.ts +1 -12
- package/dist/timeline/tool-display-name.d.ts +17 -6
- package/dist/workstream-control-event.d.ts +6 -0
- package/package.json +2 -2
- package/src/components/chat-composer.tsx +1 -1
- package/src/components/composer-transcription-control.tsx +70 -57
- package/src/components/composer.tsx +197 -126
- package/src/components/copy-button.tsx +27 -8
- package/src/components/message-timeline.tsx +385 -71
- package/src/components/tip-follow.ts +15 -0
- package/src/composer.ts +1 -1
- package/src/hooks/use-composer.ts +7 -1
- package/src/hooks/use-turn-queue.ts +4 -0
- package/src/index.ts +2 -1
- package/src/lib/format.ts +14 -0
- package/src/timeline/index.ts +2 -0
- package/src/timeline/parsers.ts +18 -1
- package/src/timeline/projection.ts +51 -20
- package/src/timeline/registry.ts +15 -2
- package/src/timeline/shared.tsx +30 -28
- package/src/timeline/tool-display-name.ts +30 -11
- package/src/timeline/tool-renderers.tsx +347 -16
- package/src/timeline/turn-summary.tsx +5 -2
- package/src/workstream-control-event.ts +6 -0
- package/dist/chunk-4QOQ2DSR.js.map +0 -1
- package/dist/chunk-A2WITZTI.js.map +0 -1
- package/dist/chunk-KZRUYCL4.js.map +0 -1
- package/dist/chunk-L7R5RK6A.js.map +0 -1
- package/dist/chunk-MS3FBZ6A.js.map +0 -1
- package/dist/chunk-TCCE5ZU5.js.map +0 -1
- /package/dist/{chunk-GZ4KR67U.js.map → chunk-ALA3UGWB.js.map} +0 -0
|
@@ -81,6 +81,21 @@ export const TIP_FOLLOW_VELOCITY_ARM_DEBT_PX = 72;
|
|
|
81
81
|
export const TIP_FOLLOW_VELOCITY_DECAY_MS = _t(180);
|
|
82
82
|
/** Reader-up pixels above clamp budget that count as leaving the tip. */
|
|
83
83
|
export const TIP_FOLLOW_READER_UP_EPS_PX = 2;
|
|
84
|
+
|
|
85
|
+
/** Test-only override for scrollend feature detection (`null` = probe DOM). */
|
|
86
|
+
let scrollEndSupportOverride: boolean | null = null;
|
|
87
|
+
|
|
88
|
+
/** @internal */ export function setScrollEndSupportForTests(value: boolean | null): void {
|
|
89
|
+
scrollEndSupportOverride = value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** True when the engine exposes element `scrollend` (prefer over rAF leave). */
|
|
93
|
+
export function supportsScrollEndEvent(): boolean {
|
|
94
|
+
if (scrollEndSupportOverride !== null) {
|
|
95
|
+
return scrollEndSupportOverride;
|
|
96
|
+
}
|
|
97
|
+
return typeof HTMLElement !== "undefined" && "onscrollend" in HTMLElement.prototype;
|
|
98
|
+
}
|
|
84
99
|
/** @deprecated Shrink lock removed; kept so old imports do not break. */
|
|
85
100
|
export const TIP_FOLLOW_SHRINK_LOCK_MS = 0;
|
|
86
101
|
/** @deprecated Absorb thresholds removed — growth track is continuous. */
|
package/src/composer.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `import * as Composer from "@opengeni/react/composer"`.
|
|
5
5
|
*/
|
|
6
|
+
export { OPEN_WORKSTREAM_CONTROL_EVENT } from "./workstream-control-event";
|
|
6
7
|
export {
|
|
7
8
|
Actions,
|
|
8
9
|
AttachButton,
|
|
@@ -16,7 +17,6 @@ export {
|
|
|
16
17
|
Hint,
|
|
17
18
|
Input,
|
|
18
19
|
ModelPicker,
|
|
19
|
-
OPEN_WORKSTREAM_CONTROL_EVENT,
|
|
20
20
|
PauseButton,
|
|
21
21
|
PausedState,
|
|
22
22
|
RestoredResources,
|
|
@@ -420,7 +420,13 @@ export function useComposer(
|
|
|
420
420
|
localSignatureAtStart === null
|
|
421
421
|
? localAtStart !== 0
|
|
422
422
|
: localSignatureAtStart !== lastSavedSignature.current;
|
|
423
|
-
|
|
423
|
+
// Only blank the picker on first hydrate / hard reload. Reconcile and
|
|
424
|
+
// event-triggered soft reloads (loadOlder SSE reconnect) must not flicker
|
|
425
|
+
// draftLoading — stale-while-revalidate keeps the settled UI mounted.
|
|
426
|
+
const showLoading = replaceLocal || draftRef.current === null;
|
|
427
|
+
if (showLoading) {
|
|
428
|
+
setDraftLoading(true);
|
|
429
|
+
}
|
|
424
430
|
try {
|
|
425
431
|
const fetched = await client.getComposerDraft(workspaceId, sessionId);
|
|
426
432
|
if (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ComposerDraft,
|
|
3
3
|
EffectiveSessionControl,
|
|
4
|
+
McpPersonalConnectionSummary,
|
|
4
5
|
SessionEvent,
|
|
5
6
|
SessionQueueMutationResponse,
|
|
6
7
|
SessionQueueSnapshot,
|
|
@@ -53,6 +54,8 @@ export type UseTurnQueueResult = {
|
|
|
53
54
|
pendingInputs: SessionPendingInputPreview[];
|
|
54
55
|
/** Exact pending members projected to join an already-waiting prompt. */
|
|
55
56
|
pendingInputAttachment: SessionQueueSnapshot["pendingInputAttachment"];
|
|
57
|
+
/** Secret-safe personal MCP summaries frozen on the exact active turn. */
|
|
58
|
+
activePersonalConnections: McpPersonalConnectionSummary[];
|
|
56
59
|
effectiveControl: EffectiveSessionControl | null;
|
|
57
60
|
/** The latest interrupted attempt has not yet durably proved physical quiescence. */
|
|
58
61
|
stoppingPreviousAttempt: boolean;
|
|
@@ -332,6 +335,7 @@ export function useTurnQueue(
|
|
|
332
335
|
queue: visibleSnapshot?.items ?? [],
|
|
333
336
|
pendingInputs: visibleSnapshot?.pendingInputs ?? [],
|
|
334
337
|
pendingInputAttachment: visibleSnapshot?.pendingInputAttachment ?? null,
|
|
338
|
+
activePersonalConnections: visibleSnapshot?.activePersonalConnections ?? [],
|
|
335
339
|
effectiveControl: visibleSnapshot?.effectiveControl ?? null,
|
|
336
340
|
stoppingPreviousAttempt: visibleSnapshot?.stoppingPreviousAttempt ?? false,
|
|
337
341
|
loading: identityMatches ? loading : enabled,
|
package/src/index.ts
CHANGED
|
@@ -100,7 +100,7 @@ export type {
|
|
|
100
100
|
SessionChromeSignalTone,
|
|
101
101
|
} from "./components/session-chrome";
|
|
102
102
|
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/tooltip";
|
|
103
|
-
export { OPEN_WORKSTREAM_CONTROL_EVENT } from "./
|
|
103
|
+
export { OPEN_WORKSTREAM_CONTROL_EVENT } from "./workstream-control-event";
|
|
104
104
|
export { useGoal, isGoalEvent } from "./hooks/use-goal";
|
|
105
105
|
export type { UseGoalOptions, UseGoalResult } from "./hooks/use-goal";
|
|
106
106
|
export { useSessionControl } from "./hooks/use-session-control";
|
|
@@ -486,6 +486,7 @@ export { cn } from "./lib/cn";
|
|
|
486
486
|
export {
|
|
487
487
|
CREDIT_EXHAUSTION_MESSAGE,
|
|
488
488
|
formatBytes,
|
|
489
|
+
formatClockTime,
|
|
489
490
|
formatRelativeTime,
|
|
490
491
|
humanizeFailureReason,
|
|
491
492
|
isCreditExhaustion,
|
package/src/lib/format.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { OpenGeniApiError } from "@opengeni/sdk";
|
|
2
2
|
|
|
3
|
+
/** Local finished-at stamp for message footers: "Aug 2, 3:42 PM" / locale-equivalent. */
|
|
4
|
+
export function formatClockTime(iso: string): string {
|
|
5
|
+
const then = new Date(iso);
|
|
6
|
+
if (Number.isNaN(then.getTime())) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
return then.toLocaleString(undefined, {
|
|
10
|
+
month: "short",
|
|
11
|
+
day: "numeric",
|
|
12
|
+
hour: "numeric",
|
|
13
|
+
minute: "2-digit",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
/** Compact relative time: "now", "42s", "7m", "3h", "2d", then a date. */
|
|
4
18
|
export function formatRelativeTime(iso: string, now: Date = new Date()): string {
|
|
5
19
|
const then = new Date(iso).getTime();
|
package/src/timeline/index.ts
CHANGED
package/src/timeline/parsers.ts
CHANGED
|
@@ -227,6 +227,19 @@ export function applyPatchOps(raw: unknown): ApplyPatchOperation[] {
|
|
|
227
227
|
return r.operation ? [r.operation] : [];
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/** Ops from provider `raw`, or from function-tool arguments when raw is empty. */
|
|
231
|
+
export function applyPatchOpsFromToolItem(item: {
|
|
232
|
+
raw: unknown;
|
|
233
|
+
arguments: unknown;
|
|
234
|
+
}): ApplyPatchOperation[] {
|
|
235
|
+
const fromRaw = applyPatchOps(item.raw);
|
|
236
|
+
if (fromRaw.length > 0) {
|
|
237
|
+
return fromRaw;
|
|
238
|
+
}
|
|
239
|
+
const args = parseToolArgs(item.arguments);
|
|
240
|
+
return applyPatchOps(args);
|
|
241
|
+
}
|
|
242
|
+
|
|
230
243
|
/**
|
|
231
244
|
* True when a tool item is an `apply_patch_call` — by its provider-native
|
|
232
245
|
* `raw.type` (the live-wire source of truth) or by tool `name` (first-party
|
|
@@ -235,7 +248,11 @@ export function applyPatchOps(raw: unknown): ApplyPatchOperation[] {
|
|
|
235
248
|
export function isApplyPatch(item: { name: string; raw: unknown }): boolean {
|
|
236
249
|
const type =
|
|
237
250
|
item.raw && typeof item.raw === "object" ? (item.raw as { type?: unknown }).type : undefined;
|
|
238
|
-
|
|
251
|
+
if (type === "apply_patch_call") {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
const name = item.name;
|
|
255
|
+
return name === "apply_patch_call" || name === "apply_patch" || name.endsWith("__apply_patch");
|
|
239
256
|
}
|
|
240
257
|
|
|
241
258
|
/* --- secret redaction ------------------------------------------------------- */
|
|
@@ -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
|
-
|
|
26
|
-
export
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
@@ -1466,6 +1507,7 @@ const AUTH_NEEDED_REASONS: ReadonlySet<string> = new Set([
|
|
|
1466
1507
|
"expired",
|
|
1467
1508
|
"insufficient_scope",
|
|
1468
1509
|
"refresh_failed",
|
|
1510
|
+
"personal_authority_unavailable",
|
|
1469
1511
|
"unsupported_auth",
|
|
1470
1512
|
"resource_scope_unavailable",
|
|
1471
1513
|
]);
|
|
@@ -1563,14 +1605,3 @@ export function extractSessionRef(value: unknown, depth = 0): string | null {
|
|
|
1563
1605
|
function looksLikeId(value: string): boolean {
|
|
1564
1606
|
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
1607
|
}
|
|
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
|
-
*/
|
package/src/timeline/registry.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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 };
|
package/src/timeline/shared.tsx
CHANGED
|
@@ -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
|
-
<
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
*
|
|
2
|
+
* MCP / first-party tool naming helpers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* and
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
}
|