@opengeni/react 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-5TIXRCSI.js → chunk-NFYVQWIB.js} +150 -64
  3. package/dist/chunk-NFYVQWIB.js.map +1 -0
  4. package/dist/index.d.ts +760 -142
  5. package/dist/index.js +5345 -2146
  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 +127 -23
  69. package/src/lib/format.ts +3 -3
  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 +388 -85
  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-5TIXRCSI.js.map +0 -1
@@ -1,7 +1,12 @@
1
1
  import type { SessionEvent, SessionStatus, StreamConnectionState } from "@opengeni/sdk";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { useOpenGeni, type ClientOverride } from "../provider";
4
- import { buildTimeline, groupTimeline, sessionStatusFromEvents, type TimelineItem } from "../timeline";
4
+ import {
5
+ buildTimeline,
6
+ groupTimeline,
7
+ sessionStatusFromEvents,
8
+ type TimelineItem,
9
+ } from "../timeline";
5
10
  import type { SessionClientLike } from "../client";
6
11
 
7
12
  export type SessionEventsConnectionState = StreamConnectionState | "idle" | "ended" | "error";
@@ -36,23 +41,29 @@ export type UseSessionEventsResult = {
36
41
  error: Error | null;
37
42
  };
38
43
 
39
- const TAIL_PAGE_SIZE = 5000;
44
+ const INITIAL_TAIL_PAGE_SIZE = 1000;
45
+ const OLDER_PAGE_SIZE = 5000;
40
46
  const INITIAL_FETCH_CAP = 1;
41
47
  const OLDER_GROUP_TARGET = 32;
42
48
  const OLDER_FETCH_CAP = 2;
43
49
  const BOUNDARY_PAGE_CAP = 4;
50
+ const EMPTY_EVENTS: SessionEvent[] = [];
44
51
 
45
52
  /**
46
53
  * Live-stream a session's event log with replay-by-sequence, reconnect, and
47
54
  * batched React updates. Fresh loads default to a bounded tail window; pass
48
55
  * `replay: "full"` or a nonzero `after` for the previous full replay path.
49
56
  */
50
- export function useSessionEvents(sessionId: string | null | undefined, options: UseSessionEventsOptions = {}): UseSessionEventsResult {
57
+ export function useSessionEvents(
58
+ sessionId: string | null | undefined,
59
+ options: UseSessionEventsOptions = {},
60
+ ): UseSessionEventsResult {
51
61
  const { client, workspaceId } = useOpenGeni(options);
52
62
  const enabled = options.enabled ?? true;
53
63
  const after = options.after ?? 0;
54
64
  const replay = options.replay ?? "windowed";
55
65
  const fullReplay = replay === "full" || after !== 0;
66
+ const streamKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${after}\u0000${fullReplay ? "full" : "windowed"}`;
56
67
 
57
68
  const [events, setEvents] = useState<SessionEvent[]>([]);
58
69
  const [connectionState, setConnectionState] = useState<SessionEventsConnectionState>("idle");
@@ -66,14 +77,17 @@ export function useSessionEvents(sessionId: string | null | undefined, options:
66
77
  const loadingOlderRef = useRef(false);
67
78
  const streamKeyRef = useRef<string | null>(null);
68
79
  const generationRef = useRef(0);
80
+ // Effects reset state after commit. Tag the state so the first render for a
81
+ // new stream identity cannot expose the previous session's event log.
82
+ const [stateStreamKey, setStateStreamKey] = useState(streamKey);
69
83
 
70
84
  useEffect(() => {
71
85
  // Reset the accumulated log only when the stream identity changes —
72
86
  // pausing via `enabled: false` keeps the timeline visible.
73
- const streamKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${after}\u0000${fullReplay ? "full" : "windowed"}`;
74
87
  if (streamKeyRef.current !== streamKey) {
75
88
  streamKeyRef.current = streamKey;
76
89
  generationRef.current += 1;
90
+ setStateStreamKey(streamKey);
77
91
  setEvents([]);
78
92
  setError(null);
79
93
  setHasOlder(false);
@@ -105,7 +119,10 @@ export function useSessionEvents(sessionId: string | null | undefined, options:
105
119
  // the next connect instead of being skipped.
106
120
  const lastInBatch = batch[batch.length - 1];
107
121
  if (lastInBatch) {
108
- lastSequenceRef.current = Math.max(lastSequenceRef.current, ...batch.map(eventResumeSequence));
122
+ lastSequenceRef.current = Math.max(
123
+ lastSequenceRef.current,
124
+ ...batch.map(eventResumeSequence),
125
+ );
109
126
  }
110
127
  setEvents((existing) => [...existing, ...batch]);
111
128
  };
@@ -122,7 +139,7 @@ export function useSessionEvents(sessionId: string | null | undefined, options:
122
139
  // reader actually scrolls up (the sentinel drives loadOlder).
123
140
  const window = await loadEventWindow(client, workspaceId, sessionId, {
124
141
  before: Number.MAX_SAFE_INTEGER,
125
- pageSize: TAIL_PAGE_SIZE,
142
+ pageSize: INITIAL_TAIL_PAGE_SIZE,
126
143
  targetGroups: Number.POSITIVE_INFINITY,
127
144
  maxFetches: INITIAL_FETCH_CAP,
128
145
  signal: controller.signal,
@@ -172,7 +189,7 @@ export function useSessionEvents(sessionId: string | null | undefined, options:
172
189
  clearTimeout(flushTimer);
173
190
  }
174
191
  };
175
- }, [client, workspaceId, sessionId, after, enabled, fullReplay]);
192
+ }, [client, workspaceId, sessionId, after, enabled, fullReplay, streamKey]);
176
193
 
177
194
  const loadOlder = useCallback(async (): Promise<boolean> => {
178
195
  if (!sessionId || fullReplay || loadingOlderRef.current || !hasOlderRef.current) {
@@ -190,7 +207,7 @@ export function useSessionEvents(sessionId: string | null | undefined, options:
190
207
  try {
191
208
  const window = await loadEventWindow(client, workspaceId, sessionId, {
192
209
  before,
193
- pageSize: TAIL_PAGE_SIZE,
210
+ pageSize: OLDER_PAGE_SIZE,
194
211
  targetGroups: OLDER_GROUP_TARGET,
195
212
  maxFetches: OLDER_FETCH_CAP,
196
213
  });
@@ -219,20 +236,22 @@ export function useSessionEvents(sessionId: string | null | undefined, options:
219
236
  }
220
237
  }, [client, workspaceId, sessionId, fullReplay]);
221
238
 
222
- const timeline = useMemo(() => buildTimeline(events), [events]);
223
- const sessionStatus = useMemo(() => sessionStatusFromEvents(events), [events]);
239
+ const identityMatches = stateStreamKey === streamKey;
240
+ const visibleEvents = identityMatches ? events : EMPTY_EVENTS;
241
+ const timeline = useMemo(() => buildTimeline(visibleEvents), [visibleEvents]);
242
+ const sessionStatus = useMemo(() => sessionStatusFromEvents(visibleEvents), [visibleEvents]);
224
243
 
225
244
  return {
226
- events,
245
+ events: visibleEvents,
227
246
  timeline,
228
247
  sessionStatus,
229
- connectionState,
230
- lastSequence: lastSequenceRef.current,
231
- initialLoading: fullReplay ? false : initialLoading,
232
- hasOlder: fullReplay ? false : hasOlder,
233
- loadingOlder: fullReplay ? false : loadingOlder,
248
+ connectionState: identityMatches ? connectionState : "idle",
249
+ lastSequence: identityMatches ? lastSequenceRef.current : after,
250
+ initialLoading: fullReplay ? false : identityMatches ? initialLoading : true,
251
+ hasOlder: fullReplay || !identityMatches ? false : hasOlder,
252
+ loadingOlder: fullReplay || !identityMatches ? false : loadingOlder,
234
253
  loadOlder,
235
- error,
254
+ error: identityMatches ? error : null,
236
255
  };
237
256
  }
238
257
 
@@ -289,7 +308,12 @@ async function loadEventWindow(
289
308
  // pages are fetched only when the buffer holds no boundary at all (one
290
309
  // monster turn); past the cap a mid-turn top is accepted.
291
310
  let snapPages = 0;
292
- while (!reachedStart && findBoundaryIndex(buffer) === -1 && snapPages < BOUNDARY_PAGE_CAP && fetches < options.maxFetches) {
311
+ while (
312
+ !reachedStart &&
313
+ findBoundaryIndex(buffer) === -1 &&
314
+ snapPages < BOUNDARY_PAGE_CAP &&
315
+ fetches < options.maxFetches
316
+ ) {
293
317
  const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
294
318
  pageSize: options.pageSize,
295
319
  ...(options.signal ? { signal: options.signal } : {}),
@@ -355,7 +379,11 @@ async function loadPreviousPage(
355
379
  if (requested === 0) {
356
380
  return Object.assign([], { requested });
357
381
  }
358
- const page = await client.listEvents(workspaceId, sessionId, { before, limit: requested, compact: true });
382
+ const page = await client.listEvents(workspaceId, sessionId, {
383
+ before,
384
+ limit: requested,
385
+ compact: true,
386
+ });
359
387
  if (options.signal?.aborted) {
360
388
  throw abortError();
361
389
  }
@@ -384,7 +412,7 @@ function eventResumeSequence(event: SessionEvent): number {
384
412
  }
385
413
 
386
414
  function asRecord(value: unknown): Record<string, unknown> {
387
- return value && typeof value === "object" ? value as Record<string, unknown> : {};
415
+ return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
388
416
  }
389
417
 
390
418
  function assertAscending(events: SessionEvent[]): void {
@@ -0,0 +1,119 @@
1
+ import type { SessionEvent, SessionLineageResponse } from "@opengeni/sdk";
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ import { useOpenGeni, type ClientOverride } from "../provider";
4
+ import { useDebouncedCallback, usePolledValue, useSessionEventTrigger } from "./internal";
5
+
6
+ export type UseSessionLineageOptions = ClientOverride & {
7
+ events?: SessionEvent[] | undefined;
8
+ /** Refresh interval (ms). Off by default. */
9
+ pollIntervalMs?: number | undefined;
10
+ enabled?: boolean | undefined;
11
+ };
12
+
13
+ export type UseSessionLineageResult = {
14
+ lineage: SessionLineageResponse | null;
15
+ loading: boolean;
16
+ error: Error | null;
17
+ refresh: () => Promise<void>;
18
+ };
19
+
20
+ export function isLineageRefreshEvent(event: SessionEvent): boolean {
21
+ if (event.type === "session.status.changed" || event.type === "session.created") {
22
+ return true;
23
+ }
24
+ // A child's creation never appears on the PARENT's stream as session.created —
25
+ // the parent sees its own spawn as an agent.toolCall.* for session_create.
26
+ // Created events are handled separately so they can refresh immediately and
27
+ // once after the child row has had time to commit.
28
+ if (event.type === "agent.toolCall.output") {
29
+ const payload = event.payload as { name?: unknown; toolName?: unknown } | null | undefined;
30
+ const name =
31
+ typeof payload?.name === "string"
32
+ ? payload.name
33
+ : typeof payload?.toolName === "string"
34
+ ? payload.toolName
35
+ : "";
36
+ return name === "session_create" || name.endsWith("__session_create");
37
+ }
38
+ return false;
39
+ }
40
+
41
+ function isSessionCreateToolCallCreated(event: SessionEvent): boolean {
42
+ if (event.type !== "agent.toolCall.created") {
43
+ return false;
44
+ }
45
+ const payload = event.payload as { name?: unknown; toolName?: unknown } | null | undefined;
46
+ const name =
47
+ typeof payload?.name === "string"
48
+ ? payload.name
49
+ : typeof payload?.toolName === "string"
50
+ ? payload.toolName
51
+ : "";
52
+ return name === "session_create" || name.endsWith("__session_create");
53
+ }
54
+
55
+ /** Read the ancestors + descendant tree for one session. Data-only; no UI state. */
56
+ export function useSessionLineage(
57
+ sessionId: string | null | undefined,
58
+ options: UseSessionLineageOptions = {},
59
+ ): UseSessionLineageResult {
60
+ const { client, workspaceId } = useOpenGeni(options);
61
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
62
+ const load = useCallback(
63
+ async () =>
64
+ sessionId
65
+ ? await client.getSessionLineage(workspaceId, sessionId)
66
+ : { ancestors: [], children: [], truncated: false },
67
+ [client, workspaceId, sessionId],
68
+ );
69
+ const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
70
+ const refreshSoon = useDebouncedCallback(() => void state.refresh(), 150);
71
+ const delayedChildRefreshRef = useRef<ReturnType<typeof setTimeout> | null>(null);
72
+ // Clear a pending delayed refresh on session/workspace SWITCH (not just
73
+ // unmount): otherwise a timer scheduled for the previous session can fire
74
+ // ~2.5s later and commit that session's lineage onto the new one.
75
+ useEffect(() => {
76
+ return () => {
77
+ if (delayedChildRefreshRef.current !== null) {
78
+ clearTimeout(delayedChildRefreshRef.current);
79
+ delayedChildRefreshRef.current = null;
80
+ }
81
+ };
82
+ }, [sessionId, workspaceId]);
83
+ const refreshAfterChildCreate = useCallback(() => {
84
+ void state.refresh();
85
+ if (delayedChildRefreshRef.current !== null) {
86
+ clearTimeout(delayedChildRefreshRef.current);
87
+ }
88
+ delayedChildRefreshRef.current = setTimeout(() => {
89
+ delayedChildRefreshRef.current = null;
90
+ void state.refresh();
91
+ }, 2500);
92
+ }, [state]);
93
+ useSessionEventTrigger(
94
+ client,
95
+ workspaceId,
96
+ sessionId,
97
+ isLineageRefreshEvent,
98
+ refreshSoon,
99
+ // SHARED-FEED ONLY: without a caller-provided events log the trigger would
100
+ // open its OWN streamEvents tail — a second live SSE connection next to the
101
+ // session route's useSessionEvents. A caller with no feed opts into polling
102
+ // (pollIntervalMs), never a duplicate stream.
103
+ { events: options.events, enabled: enabled && options.events !== undefined },
104
+ );
105
+ useSessionEventTrigger(
106
+ client,
107
+ workspaceId,
108
+ sessionId,
109
+ isSessionCreateToolCallCreated,
110
+ refreshAfterChildCreate,
111
+ { events: options.events, enabled: enabled && options.events !== undefined },
112
+ );
113
+ return {
114
+ lineage: state.data,
115
+ loading: state.loading,
116
+ error: state.error,
117
+ refresh: state.refresh,
118
+ };
119
+ }
@@ -1,7 +1,12 @@
1
1
  import type { Session, SessionEvent } from "@opengeni/sdk";
2
2
  import { useCallback, useState } from "react";
3
3
  import { useOpenGeni, type ClientOverride } from "../provider";
4
- import { useMutationRunner, usePolledValue, useSessionEventTrigger, type SessionEventFeedOptions } from "./internal";
4
+ import {
5
+ useMutationRunner,
6
+ usePolledValue,
7
+ useSessionEventTrigger,
8
+ type SessionEventFeedOptions,
9
+ } from "./internal";
5
10
 
6
11
  export type UseSessionOptions = ClientOverride &
7
12
  SessionEventFeedOptions & {
@@ -28,11 +33,14 @@ export function isTitleEvent(event: Pick<SessionEvent, "type">): boolean {
28
33
  }
29
34
 
30
35
  /** Fetch one session (with optional polling), live-patching its title on `session.title_set`. */
31
- export function useSession(sessionId: string | null | undefined, options: UseSessionOptions = {}): UseSessionResult {
36
+ export function useSession(
37
+ sessionId: string | null | undefined,
38
+ options: UseSessionOptions = {},
39
+ ): UseSessionResult {
32
40
  const { client, workspaceId } = useOpenGeni(options);
33
41
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
34
42
  const [override, setOverride] = useState<Session | null>(null);
35
- const mutation = useMutationRunner();
43
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
36
44
  const load = useCallback(async () => {
37
45
  if (!sessionId) {
38
46
  return null;
@@ -42,33 +50,40 @@ export function useSession(sessionId: string | null | undefined, options: UseSes
42
50
  setOverride(null);
43
51
  return fetched;
44
52
  }, [client, workspaceId, sessionId]);
45
- const state = usePolledValue(load, {
53
+ const { data, loading, error, refresh } = usePolledValue(load, {
46
54
  pollIntervalMs: options.pollIntervalMs,
47
55
  enabled,
48
56
  });
49
57
 
50
- const base = state.data ?? null;
58
+ const base = data ?? null;
51
59
  // The override only ever carries title/titleSource patches; it is reset on
52
60
  // every fresh load so it can never go stale against the server snapshot.
53
- const session = base && override && override.id === base.id ? { ...base, title: override.title, titleSource: override.titleSource } : base;
61
+ const session =
62
+ base && override && override.id === base.id
63
+ ? { ...base, title: override.title, titleSource: override.titleSource }
64
+ : base;
54
65
 
55
66
  // Live-patch the title on auto (agent) + cross-client (user/agent) renames so
56
67
  // the UI reflects the new title without polling or a full re-fetch.
57
- const onTitleEvent = useCallback((event: SessionEvent) => {
58
- const payload = (event.payload ?? {}) as { title?: unknown; source?: unknown };
59
- const title = payload.title;
60
- if (typeof title !== "string") {
61
- return;
62
- }
63
- const source: "user" | "agent" | null = payload.source === "user" || payload.source === "agent" ? payload.source : null;
64
- setOverride((current): Session | null => {
65
- const next = current ?? base;
66
- if (!next) {
67
- return current;
68
+ const onTitleEvent = useCallback(
69
+ (event: SessionEvent) => {
70
+ const payload = (event.payload ?? {}) as { title?: unknown; source?: unknown };
71
+ const title = payload.title;
72
+ if (typeof title !== "string") {
73
+ return;
68
74
  }
69
- return { ...next, title, titleSource: source };
70
- });
71
- }, [base]);
75
+ const source: "user" | "agent" | null =
76
+ payload.source === "user" || payload.source === "agent" ? payload.source : null;
77
+ setOverride((current): Session | null => {
78
+ const next = current ?? base;
79
+ if (!next) {
80
+ return current;
81
+ }
82
+ return { ...next, title, titleSource: source };
83
+ });
84
+ },
85
+ [base],
86
+ );
72
87
  useSessionEventTrigger(client, workspaceId, sessionId, isTitleEvent, onTitleEvent, {
73
88
  enabled,
74
89
  ...(options.events !== undefined ? { events: options.events } : {}),
@@ -79,23 +94,23 @@ export function useSession(sessionId: string | null | undefined, options: UseSes
79
94
  if (!sessionId) {
80
95
  return null;
81
96
  }
82
- const result = await mutation.run(() => client.updateSession(workspaceId, sessionId, { title }));
97
+ const result = await run(() => client.updateSession(workspaceId, sessionId, { title }));
83
98
  if (result) {
84
99
  setOverride(result);
85
100
  }
86
101
  return result;
87
102
  },
88
- [client, workspaceId, sessionId, mutation.run],
103
+ [client, workspaceId, sessionId, run],
89
104
  );
90
105
 
91
106
  return {
92
107
  session,
93
- loading: state.loading,
94
- error: state.error,
95
- refresh: state.refresh,
108
+ loading,
109
+ error,
110
+ refresh,
96
111
  updateTitle,
97
- updating: mutation.mutating,
98
- mutationError: mutation.mutationError,
99
- clearMutationError: mutation.clearMutationError,
112
+ updating: mutating,
113
+ mutationError,
114
+ clearMutationError,
100
115
  };
101
116
  }
@@ -7,7 +7,7 @@ import {
7
7
  matchCommand,
8
8
  parseCommandLine,
9
9
  } from "../commands/registry";
10
- import type { CommandContext, Notice, SlashCommand } from "../commands/types";
10
+ import type { CommandContext, SlashCommand } from "../commands/types";
11
11
 
12
12
  /**
13
13
  * Context the composer supplies for command execution and visibility. The
@@ -200,11 +200,11 @@ export function useSlashCommands(options: UseSlashCommandsOptions): UseSlashComm
200
200
  // exact-match heuristic) and a pointer click (which has ALREADY chosen the
201
201
  // command — the clicked row — and must not re-resolve to a near-match).
202
202
  const runResolved = useCallback(
203
- async (command: SlashCommand, options?: { explicit?: boolean }): Promise<void> => {
203
+ async (command: SlashCommand, executionOptions?: { explicit?: boolean }): Promise<void> => {
204
204
  if (!parsed) {
205
205
  return;
206
206
  }
207
- const explicit = options?.explicit ?? false;
207
+ const explicit = executionOptions?.explicit ?? false;
208
208
  // Token-vs-command equality is case-insensitive (matching the registry's
209
209
  // matchCommand/filterCommands), so a fully-typed "/Clear" counts as having
210
210
  // named `clear` and runs rather than autocompleting.
@@ -298,7 +298,9 @@ export function useSlashCommands(options: UseSlashCommandsOptions): UseSlashComm
298
298
  case "ArrowDown": {
299
299
  event.preventDefault();
300
300
  navigatedRef.current = true;
301
- setHighlight((current) => (items.length === 0 ? 0 : (Math.min(current, items.length - 1) + 1) % items.length));
301
+ setHighlight((current) =>
302
+ items.length === 0 ? 0 : (Math.min(current, items.length - 1) + 1) % items.length,
303
+ );
302
304
  return true;
303
305
  }
304
306
  case "ArrowUp": {