@tangle-network/agent-app 0.45.62 → 0.45.64

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.
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Command palette — the React-free selection half of the Cmd/Ctrl+K surface
3
+ * (`/web-react` holds the rendered half, `CommandPalette`).
4
+ *
5
+ * Pure and import-free beyond this module's own types: no React, no DOM, no
6
+ * fuse.js. A route loader or a worker can build and rank the same items the
7
+ * browser renders.
8
+ *
9
+ * Domain stays a parameter. The palette knows two kinds of row — a SESSION the
10
+ * user can jump to and an ACTION the product offers (new chat, toggle theme,
11
+ * open settings) — and both arrive as data. What a selection DOES is the
12
+ * product's business; the shell only builds, ranks, and groups.
13
+ *
14
+ * Ranking is the documented ladder, not a fuzzy library: exact > prefix >
15
+ * word-prefix > substring (earlier index wins) > token-order, with keyword
16
+ * hits ranked a fixed step below the same hit on the label. Deterministic —
17
+ * no index-building, no async, same input always sorts the same way.
18
+ */
19
+ import { type SessionSummary } from './index';
20
+ /** A product-supplied palette action. `hint` is the right-aligned affordance
21
+ * copy (a kbd chord, a route name) — rendered verbatim, never interpreted. */
22
+ export interface CommandPaletteAction {
23
+ id: string;
24
+ label: string;
25
+ description?: string;
26
+ hint?: string;
27
+ /** Extra match vocabulary that never renders (`settings` matching
28
+ * "preferences"). A keyword hit ranks below the same hit on the label. */
29
+ keywords?: string[];
30
+ }
31
+ /** One selectable row. `group` is the section header it renders under. */
32
+ export interface CommandPaletteItem {
33
+ id: string;
34
+ group: string;
35
+ label: string;
36
+ description?: string;
37
+ hint?: string;
38
+ keywords?: string[];
39
+ /** Recency key (ISO-8601). Breaks score ties and orders the unfiltered
40
+ * list recent-first. Rows without one sort below rows with one. */
41
+ recentAt?: string | null;
42
+ }
43
+ /** One rendered section: a header plus its rows, in first-seen group order. */
44
+ export interface CommandPaletteGroup {
45
+ group: string;
46
+ items: CommandPaletteItem[];
47
+ }
48
+ export declare const COMMAND_PALETTE_SESSIONS_GROUP = "Sessions";
49
+ export declare const COMMAND_PALETTE_ACTIONS_GROUP = "Actions";
50
+ export interface BuildCommandPaletteItemsOptions {
51
+ sessions?: readonly SessionSummary[];
52
+ actions?: readonly CommandPaletteAction[];
53
+ /** Section label for sessions. Default "Sessions". */
54
+ sessionsLabel?: string;
55
+ /** Section label for actions. Default "Actions". */
56
+ actionsLabel?: string;
57
+ /** Placeholder title for an untitled session. */
58
+ untitledLabel?: string;
59
+ }
60
+ /**
61
+ * Flatten sessions + actions into palette items, sessions group first (the
62
+ * jump-back-in list), actions after. Sessions order recent-first by
63
+ * `updatedAt` — a palette with an empty query IS the recency list, so the
64
+ * build order is the render order and the filter never has to re-derive it.
65
+ * Pinned sessions lead the recency sort, matching the rail.
66
+ */
67
+ export declare function buildCommandPaletteItems({ sessions, actions, sessionsLabel, actionsLabel, untitledLabel, }: BuildCommandPaletteItemsOptions): CommandPaletteItem[];
68
+ /**
69
+ * Score an item: the best label score, or the best keyword score a fixed step
70
+ * below. `null` when neither matches — the item is filtered out. An empty
71
+ * query scores every item 0 (the caller keeps build order: recent-first).
72
+ */
73
+ export declare function scoreCommandPaletteItem(item: CommandPaletteItem, query: string): number | null;
74
+ /**
75
+ * Filter + rank: an empty query returns the items untouched (build order is
76
+ * the recency order); a real query drops non-matches and sorts by score, then
77
+ * recency, then original position — stable and deterministic.
78
+ */
79
+ export declare function filterCommandPaletteItems(items: readonly CommandPaletteItem[], query: string): CommandPaletteItem[];
80
+ /**
81
+ * Fold a flat (already ordered) item list into renderable sections. Groups
82
+ * appear in first-seen order and each group appears ONCE — a filtered ranking
83
+ * interleaves sessions and actions by score, and folding only consecutive runs
84
+ * would render the same header twice. Within a group, rows keep the flat
85
+ * order. Empty groups vanish, so a filter that leaves only actions renders no
86
+ * "Sessions" header over nothing.
87
+ */
88
+ export declare function groupCommandPaletteItems(items: readonly CommandPaletteItem[]): CommandPaletteGroup[];
@@ -15,6 +15,7 @@
15
15
  * rather than the shell knowing any URL.
16
16
  */
17
17
  export * from './nav-guard';
18
+ export * from './command-palette';
18
19
  /** One session as the shell needs to see it. Products map their own row
19
20
  * (thread / session / matter) onto this before handing it over. */
20
21
  export interface SessionSummary {
@@ -1,13 +1,18 @@
1
1
  import {
2
+ COMMAND_PALETTE_ACTIONS_GROUP,
3
+ COMMAND_PALETTE_SESSIONS_GROUP,
2
4
  DEFAULT_RAIL_COOKIE_NAME,
3
5
  UNTITLED_SESSION_LABEL,
4
6
  activeSessionIdFromPath,
5
7
  assertNavHrefsRegistered,
8
+ buildCommandPaletteItems,
6
9
  buildSessionNavItem,
7
10
  buildSessionSubItems,
8
11
  checkNavHrefs,
9
12
  composeSidebarSessions,
13
+ filterCommandPaletteItems,
10
14
  flattenRouteTable,
15
+ groupCommandPaletteItems,
11
16
  mergeSessionPages,
12
17
  railCollapsedCookie,
13
18
  readRailCollapsedCookie,
@@ -16,19 +21,25 @@ import {
16
21
  resolveNavHref,
17
22
  resolveScopedActiveNavId,
18
23
  resolveSessionUnread,
24
+ scoreCommandPaletteItem,
19
25
  sessionLabel,
20
26
  writeRailCollapsedCookie
21
- } from "../chunk-PC2WYTK7.js";
27
+ } from "../chunk-EDTWGSQT.js";
22
28
  export {
29
+ COMMAND_PALETTE_ACTIONS_GROUP,
30
+ COMMAND_PALETTE_SESSIONS_GROUP,
23
31
  DEFAULT_RAIL_COOKIE_NAME,
24
32
  UNTITLED_SESSION_LABEL,
25
33
  activeSessionIdFromPath,
26
34
  assertNavHrefsRegistered,
35
+ buildCommandPaletteItems,
27
36
  buildSessionNavItem,
28
37
  buildSessionSubItems,
29
38
  checkNavHrefs,
30
39
  composeSidebarSessions,
40
+ filterCommandPaletteItems,
31
41
  flattenRouteTable,
42
+ groupCommandPaletteItems,
32
43
  mergeSessionPages,
33
44
  railCollapsedCookie,
34
45
  readRailCollapsedCookie,
@@ -37,6 +48,7 @@ export {
37
48
  resolveNavHref,
38
49
  resolveScopedActiveNavId,
39
50
  resolveSessionUnread,
51
+ scoreCommandPaletteItem,
40
52
  sessionLabel,
41
53
  writeRailCollapsedCookie
42
54
  };
@@ -25,6 +25,7 @@
25
25
  * fallbacks when a host hasn't defined a private chat-token set.
26
26
  */
27
27
  import { type ReactNode } from 'react';
28
+ import { type DictationAudio } from './use-dictation';
28
29
  /** Prompt-part descriptor an uploaded file carries (the upload route's
29
30
  * `UploadedChatFile.part`), echoed back in the turn body on send. Mirrors
30
31
  * `/chat-routes`' wire shape structurally — no server import here. */
@@ -105,6 +106,20 @@ export interface ComposerSendFailure {
105
106
  * the notice instead. */
106
107
  restored: boolean;
107
108
  }
109
+ /**
110
+ * One `/` command the composer offers. Typing `/` at position 0 opens the
111
+ * command menu; the rest of the token filters it (the same prefix > substring
112
+ * > token-order ranking as the command palette). Picking a command CLEARS the
113
+ * token from the draft and calls `run` — what the command does (a route, a
114
+ * dialog, a draft transformation) is the product's business.
115
+ */
116
+ export interface SlashCommand {
117
+ /** Command name without the leading slash: `model`, `clear`. */
118
+ name: string;
119
+ /** One line of what it does, rendered beside the name. */
120
+ description: string;
121
+ run: () => void;
122
+ }
108
123
  export interface ChatComposerProps {
109
124
  /** Send the trimmed, non-empty message. Attached files travel separately via
110
125
  * `onAttach` + `pendingFiles` (the host consumes and clears them on send).
@@ -162,6 +177,19 @@ export interface ChatComposerProps {
162
177
  accept?: string;
163
178
  dropTitle?: string;
164
179
  dropDescription?: string;
180
+ /** `/` commands offered when the draft is exactly a leading slash token.
181
+ * Omit (or pass []) and `/` types as ordinary text. */
182
+ slashCommands?: SlashCommand[];
183
+ /** Dictation is opt-in: pass `onDictate` and the action row gains a mic
184
+ * button (browsers without `MediaRecorder`/`getUserMedia` render none).
185
+ * Click starts the capture; the button flips to a stop control with the
186
+ * running elapsed seconds; stop hands the recorded audio blob here. The
187
+ * composer owns capture only — turning the audio into text (e.g. the
188
+ * Whisper provider from `sequences-react`) is the host's. */
189
+ onDictate?: (audio: DictationAudio) => void;
190
+ /** Capture failures (a denied mic prompt, no device), after the composer has
191
+ * shown its own dismissible notice. For hosts that log or track. */
192
+ onDictateError?: (message: string) => void;
165
193
  /** Cmd/Ctrl+L focuses the input and shows the hint. Default true. */
166
194
  focusShortcut?: boolean;
167
195
  /** Float the card on a soft two-layer foreground-tinted shadow (opt-in).
@@ -176,4 +204,4 @@ export interface ChatComposerProps {
176
204
  sendVariant?: 'pill' | 'icon';
177
205
  className?: string;
178
206
  }
179
- export declare function ChatComposer({ onSend, onSendParts, onSendFailed, sendFailureMessage, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, seed, onSeedApplied, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, accept, dropTitle, dropDescription, focusShortcut, floating, sendLabel, sendVariant, className, }: ChatComposerProps): import("react").JSX.Element;
207
+ export declare function ChatComposer({ onSend, onSendParts, onSendFailed, sendFailureMessage, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, seed, onSeedApplied, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, accept, dropTitle, dropDescription, slashCommands, onDictate, onDictateError, focusShortcut, floating, sendLabel, sendVariant, className, }: ChatComposerProps): import("react").JSX.Element;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CommandPalette — the rendered half of the Cmd/Ctrl+K surface. Selection,
3
+ * ranking, and grouping live in `/session-shell` (`buildCommandPaletteItems`,
4
+ * `filterCommandPaletteItems`, `groupCommandPaletteItems`); this component is
5
+ * the overlay, the input, and the keyboard model.
6
+ *
7
+ * Placement follows the PopoverSurface canon (AGENTS.md "UI chrome
8
+ * ownership"): the panel PORTALS to `document.body` and positions in viewport
9
+ * coordinates (`fixed`), so no host markup — a scroll rail, a `transform`, a
10
+ * stacking context — can clip or trap it. Unlike the pickers it is CENTERED,
11
+ * not trigger-anchored: a palette has no trigger, so it does not reuse
12
+ * `PopoverSurface` itself, but it carries the same grammar — `bg-popover`,
13
+ * `border-card-edge`, `OVERLAY_SHADOW`, the stamped surface attribute.
14
+ *
15
+ * The keyboard model is the ARIA combobox pattern: focus stays in the input,
16
+ * ArrowUp/ArrowDown move `aria-activedescendant` across the FLAT result list
17
+ * (groups are presentation), Enter selects, Escape closes, and closing returns
18
+ * focus to whatever had it before the palette opened.
19
+ */
20
+ import { type CommandPaletteItem } from '../session-shell/index';
21
+ export type { CommandPaletteItem } from '../session-shell/index';
22
+ export interface CommandPaletteProps {
23
+ /** The full item list, build-ordered (recent-first sessions, then actions).
24
+ * Filtering and ranking are owned here — pass the UNFILTERED list. */
25
+ items: CommandPaletteItem[];
26
+ /** A row was chosen (click or Enter). The palette closes itself. */
27
+ onSelect: (item: CommandPaletteItem) => void;
28
+ /** Controlled open state. Omit for self-managed state toggled by the hotkey. */
29
+ open?: boolean;
30
+ onOpenChange?: (open: boolean) => void;
31
+ /** Register the Cmd/Ctrl+K toggle. Default true. */
32
+ hotkey?: boolean;
33
+ /** Async source is still resolving — the input stays live, the list shows
34
+ * the loading row instead of a premature empty state. */
35
+ loading?: boolean;
36
+ /** Seed for the query (uncontrolled). */
37
+ initialQuery?: string;
38
+ placeholder?: string;
39
+ /** Empty-state copy. Default names the query: `No results for “…”`. */
40
+ emptyMessage?: string;
41
+ /** Accessible name for the dialog. Default "Command palette". */
42
+ label?: string;
43
+ }
44
+ export declare function CommandPalette({ items, onSelect, open: controlledOpen, onOpenChange, hotkey, loading, initialQuery, placeholder, emptyMessage, label, }: CommandPaletteProps): import("react").ReactPortal | null;
@@ -53,8 +53,10 @@ export * from './sandbox-terminal';
53
53
  export * from './seat-paywall';
54
54
  export * from './session-history';
55
55
  export * from './record-grid';
56
+ export * from './command-palette';
56
57
  export * from './sparkline';
57
58
  export * from './insight-card';
59
+ export * from './use-dictation';
58
60
  export { usePopover, usePending, PopoverSurface, POPOVER_SURFACE_ATTR, ModelPicker, EffortPicker, EffortMeter, effortMeterFill, effortLevelLabel, effortLevelsFromIds, reconcileEffortLevels, DEFAULT_EFFORT_LEVELS, EFFORT_METER_SEGMENTS, OVERLAY_SHADOW, type ModelPickerProps, type EffortPickerProps, type EffortLevel, type PopoverSurfaceProps, } from './controls';
59
61
  export { AgentSessionControls, type AgentSessionControlsProps, } from './agent-session-controls';
60
62
  import type { CatalogModel } from '../runtime/model-catalog';
@@ -3,6 +3,7 @@ import {
3
3
  ChatComposer,
4
4
  ChatEmptyState,
5
5
  ChatMessages,
6
+ CommandPalette,
6
7
  DEFAULT_INSIGHT_PAGE_SIZE,
7
8
  DEFAULT_MENTION_EMPTY_TEXT,
8
9
  DEFAULT_MENTION_LIMIT,
@@ -50,12 +51,15 @@ import {
50
51
  createSessionInteractionAttemptStore,
51
52
  describeProvenance,
52
53
  describeProvenanceSourceStatus,
54
+ dictationErrorMessage,
55
+ diffRecordGridProposal,
53
56
  dispatchChatStreamLine,
54
57
  durableChatCardsFromParts,
55
58
  fieldAnswer,
56
59
  fieldValuesFromAnswers,
57
60
  formatActivityCost,
58
61
  formatActivityDuration,
62
+ formatDictationElapsed,
59
63
  formatInsightDelta,
60
64
  formatModelCost,
61
65
  formatRecordGridValue,
@@ -81,6 +85,7 @@ import {
81
85
  nextRevealCount,
82
86
  parseRecordGridInput,
83
87
  pendingApprovalOf,
88
+ pickDictationMimeType,
84
89
  projectRecordGridRows,
85
90
  provenanceBasisMeta,
86
91
  provenanceGaps,
@@ -113,6 +118,7 @@ import {
113
118
  triggerAttachmentDownload,
114
119
  upsertChatInteraction,
115
120
  useChatInteractions,
121
+ useDictation,
116
122
  useDurablePlanFlow,
117
123
  useFileMentions,
118
124
  useInfiniteScroll,
@@ -131,7 +137,7 @@ import {
131
137
  withoutRecordGridCreated,
132
138
  withoutRecordGridRemoved,
133
139
  withoutRecordGridUpdate
134
- } from "../chunk-FOXGPGXF.js";
140
+ } from "../chunk-OMGV3CX2.js";
135
141
  import "../chunk-FBVLEGEG.js";
136
142
  import {
137
143
  EvidenceLineageTable,
@@ -175,7 +181,7 @@ import {
175
181
  useSandboxTerminalConnection
176
182
  } from "../chunk-BATKJP3P.js";
177
183
  import "../chunk-3UBAO3N5.js";
178
- import "../chunk-PC2WYTK7.js";
184
+ import "../chunk-EDTWGSQT.js";
179
185
  import "../chunk-QY4BRKRJ.js";
180
186
  import {
181
187
  ATTACHMENT_ACCEPT
@@ -238,6 +244,7 @@ export {
238
244
  ChatComposer,
239
245
  ChatEmptyState,
240
246
  ChatMessages,
247
+ CommandPalette,
241
248
  DEFAULT_EFFORT_LEVELS,
242
249
  DEFAULT_INSIGHT_PAGE_SIZE,
243
250
  DEFAULT_MENTION_EMPTY_TEXT,
@@ -320,6 +327,8 @@ export {
320
327
  dedupeQuestionInteractionsByContent,
321
328
  describeProvenance,
322
329
  describeProvenanceSourceStatus,
330
+ dictationErrorMessage,
331
+ diffRecordGridProposal,
323
332
  dispatchChatStreamLine,
324
333
  durableChatCardsFromParts,
325
334
  effortLevelLabel,
@@ -331,6 +340,7 @@ export {
331
340
  fileMentionsToParts,
332
341
  formatActivityCost,
333
342
  formatActivityDuration,
343
+ formatDictationElapsed,
334
344
  formatInsightDelta,
335
345
  formatModelCost,
336
346
  formatRecordGridValue,
@@ -375,6 +385,7 @@ export {
375
385
  parseReviewQueueItem,
376
386
  pendingApprovalOf,
377
387
  persistedPartToInteraction,
388
+ pickDictationMimeType,
378
389
  projectRecordGridRows,
379
390
  provenanceBasisMeta,
380
391
  provenanceGaps,
@@ -413,6 +424,7 @@ export {
413
424
  upsertChatInteraction,
414
425
  useChatInteractions,
415
426
  useComposerAttachments,
427
+ useDictation,
416
428
  useDurablePlanFlow,
417
429
  useFileMentions,
418
430
  useInfiniteScroll,
@@ -81,6 +81,10 @@ export interface InsightAction {
81
81
  onClick: () => void;
82
82
  }
83
83
  export interface InsightCardProps {
84
+ /** The lane the metric belongs to ("Spend", "Missions"), set small above the
85
+ * title. A deck of cards from different surfaces needs the grouping word
86
+ * before the metric's own name, not after it. */
87
+ eyebrow?: string;
84
88
  /** What was measured, in the reader's words ("Spend today"). */
85
89
  title: string;
86
90
  /** The number that moved. A `string` renders verbatim — a total the caller
@@ -113,7 +117,7 @@ export interface InsightCardProps {
113
117
  className?: string;
114
118
  style?: CSSProperties;
115
119
  }
116
- export declare function InsightCard({ title, value, unit, previous, polarity, format, series, seriesLabel, description, action, live, liveLabel, className, style, }: InsightCardProps): ReactElement;
120
+ export declare function InsightCard({ eyebrow, title, value, unit, previous, polarity, format, series, seriesLabel, description, action, live, liveLabel, className, style, }: InsightCardProps): ReactElement;
117
121
  export interface Insight extends InsightCardProps {
118
122
  /** Stable across refreshes: it keys the card. Paired with the deck holding
119
123
  * the last loaded page across a reload, a stable id is what lets a settled
@@ -177,6 +177,51 @@ export declare function isRecordGridCellApplicable(column: RecordGridColumn, val
177
177
  /** Value equality across the grid's value union, treating `undefined` as
178
178
  * `null` so an absent key and an explicit null never read as a change. */
179
179
  export declare function sameRecordGridValue(a: RecordGridValue | undefined, b: RecordGridValue | undefined): boolean;
180
+ /**
181
+ * A proposed change set, diffed against the live rows by
182
+ * {@link diffRecordGridProposal}. The grid's review mode renders exactly what
183
+ * this shape declares — it owns no opinion about where the proposal came from
184
+ * (an agent's `submit_proposal` call, a record store's pending entries).
185
+ */
186
+ export interface RecordGridProposal {
187
+ /** Proposed new cell values for EXISTING rows: row id → column id → value.
188
+ * Only cells that differ from the live value diff; an update that restates
189
+ * the current value is not a change. An id with no live row is ignored —
190
+ * adding a row is `additions`' job. */
191
+ updates?: Readonly<Record<string, Readonly<Record<string, RecordGridValue>>>>;
192
+ /** Proposed new rows. An addition whose id already names a live row is
193
+ * ignored — changing an existing row is `updates`' job. */
194
+ additions?: readonly RecordGridRow[];
195
+ /** Live row ids proposed for removal. Unknown ids are ignored. */
196
+ removals?: readonly string[];
197
+ }
198
+ /** One cell whose proposed value differs from the live one. */
199
+ export interface RecordGridCellDiff {
200
+ columnId: string;
201
+ /** The live value — what rejecting keeps. */
202
+ before: RecordGridValue;
203
+ /** The proposed value — what accepting writes. */
204
+ after: RecordGridValue;
205
+ }
206
+ export type RecordGridRowDiffKind = 'changed' | 'added' | 'removed';
207
+ /** One row's verdict: what the proposal does to it. */
208
+ export interface RecordGridRowDiff {
209
+ rowId: string;
210
+ kind: RecordGridRowDiffKind;
211
+ /** The differing cells. Empty for `added`/`removed` — every cell of those is
212
+ * part of the change by definition. */
213
+ cells: readonly RecordGridCellDiff[];
214
+ /** The live row for `changed`/`removed`, the proposed row for `added`. */
215
+ row: RecordGridRow;
216
+ }
217
+ /**
218
+ * Diff a proposal against the live rows. Pure and deterministic: input order
219
+ * in, diff order out — updates follow `rows` order, removals follow `rows`
220
+ * order, additions follow the proposal's order. A row whose update bag diffs
221
+ * to nothing produces no entry, so a no-op proposal yields an empty diff and
222
+ * the grid has nothing to review.
223
+ */
224
+ export declare function diffRecordGridProposal(rows: readonly RecordGridRow[], proposal: RecordGridProposal): RecordGridRowDiff[];
180
225
  /**
181
226
  * Turn what an editor control produced into a typed value. Syntax only —
182
227
  * range, length, and membership are {@link validateRecordGridCell}'s job.
@@ -17,6 +17,13 @@
17
17
  * - **Provenance per cell.** An optional quote + link + basis, so a
18
18
  * record-backed grid shows where a value came from without the product
19
19
  * building a second surface for it.
20
+ * - **Review of a proposed change set.** Hand the grid a `proposed` patch
21
+ * (`./record-grid-model`'s `diffRecordGridProposal`) and it becomes the
22
+ * red/green row-diff surface a tax/legal review needs: changed cells render
23
+ * the struck live value against the proposed one, added/removed rows are
24
+ * marked, and every diffed row carries accept/reject — per row and for the
25
+ * whole set. What accepting MEANS stays the caller's (a record-store review
26
+ * write); the grid reports decisions, it does not persist them.
20
27
  * - **Three distinct data states, on `web-react/async`'s own contract.**
21
28
  * `state: AsyncResourceState<Row[]>` and `empty: AsyncEmptySpec` are the
22
29
  * same types every other screen fetches through — loading, error-with-
@@ -35,7 +42,7 @@
35
42
  */
36
43
  import { type ReactNode } from 'react';
37
44
  import { type AsyncEmptySpec, type AsyncResourceState } from './async';
38
- import { type RecordGridColumn, type RecordGridRow, type RecordGridValue } from './record-grid-model';
45
+ import { type RecordGridColumn, type RecordGridProposal, type RecordGridRow, type RecordGridValue } from './record-grid-model';
39
46
  export * from './record-grid-model';
40
47
  /** One committed cell edit, handed to `onUpdate`. */
41
48
  export interface RecordGridCellChange {
@@ -88,6 +95,25 @@ export interface RecordGridProps {
88
95
  onUpdate?: (change: RecordGridCellChange) => Promise<RecordGridWriteOutcome>;
89
96
  /** Delete one row. Absent → no delete affordance. */
90
97
  onDelete?: (row: RecordGridRow) => Promise<RecordGridWriteOutcome>;
98
+ /** A proposed change set to review against the live rows (see
99
+ * `diffRecordGridProposal`). While a non-empty diff is on the table the grid
100
+ * is a REVIEW surface, not an editor: cell editing, row add, and row delete
101
+ * are inert; changed cells render the struck live value against the
102
+ * proposed one; added/removed rows are marked; each diffed row carries
103
+ * accept/reject controls. A proposal that diffs to nothing renders the grid
104
+ * unchanged — there is nothing to review. */
105
+ proposed?: RecordGridProposal;
106
+ /** Accept one diffed row — write its proposed values, adopt the addition, or
107
+ * confirm the removal. The caller owns what accepting MEANS (a record-store
108
+ * review write); the grid reports the decision and the caller moves the row
109
+ * out of `proposed`. */
110
+ onAcceptRow?: (rowId: string) => void;
111
+ /** Reject one diffed row — the live row stands. */
112
+ onRejectRow?: (rowId: string) => void;
113
+ /** Accept every remaining diffed row at once. */
114
+ onAcceptAll?: () => void;
115
+ /** Reject every remaining diffed row at once. */
116
+ onRejectAll?: () => void;
91
117
  /** Starting values for the add form. */
92
118
  newRowDefaults?: Readonly<Record<string, RecordGridValue>>;
93
119
  /** Label of the add control and of the add form. Defaults to `Add row`. */
@@ -105,4 +131,4 @@ export interface RecordGridProps {
105
131
  * id, so a record store's fold output maps straight on: one cell per entry,
106
132
  * its quote and link in `sources`.
107
133
  */
108
- export declare function RecordGrid({ columns, caption, state, empty, onCreate, onUpdate, onDelete, newRowDefaults, addLabel, locale, toolbar, loadingRowCount, className, }: RecordGridProps): import("react").JSX.Element;
134
+ export declare function RecordGrid({ columns, caption, state, empty, onCreate, onUpdate, onDelete, proposed, onAcceptRow, onRejectRow, onAcceptAll, onRejectAll, newRowDefaults, addLabel, locale, toolbar, loadingRowCount, className, }: RecordGridProps): import("react").JSX.Element;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `useDictation` — the capture half of composer dictation.
3
+ *
4
+ * Dictation splits at a clean seam: the browser owns capture (`getUserMedia` +
5
+ * `MediaRecorder`), the host owns what the audio MEANS (transcription —
6
+ * `sequences-react`'s Whisper provider — or a straight upload). This hook is
7
+ * the capture half and nothing else: it asks for the mic, records, ticks whole
8
+ * seconds while it does, and hands the assembled `Blob` to the host's
9
+ * `onDictate`. A hook rather than composer-private code, because a host whose
10
+ * composer is fully composed (hotkey, push-to-talk) needs the same capture
11
+ * without re-deriving it.
12
+ *
13
+ * The rules the implementation exists to hold:
14
+ *
15
+ * - **Unsupported is a render signal, not an exception.** A browser without
16
+ * `MediaRecorder`/`getUserMedia` gets `supported: false`, and the composer
17
+ * renders no dead button. `start()` stays a no-op rather than throwing, so
18
+ * a host that wired it to a hotkey cannot crash on such a browser.
19
+ * - **The mic is released the moment recording ends.** Tracks are stopped in
20
+ * every exit — stop, error, cancel-during-prompt, unmount. A red dot the
21
+ * browser keeps showing after the composer says "idle" is the failure this
22
+ * is written against.
23
+ * - **A denied prompt is a message, not a crash.** `NotAllowedError` and a
24
+ * missing device are reported through `onError` as words the composer can
25
+ * show; the hook returns to idle.
26
+ * - **Unmount discards.** A composer that unmounts mid-recording delivers
27
+ * nothing: the host it would have called has moved on, and an arriving
28
+ * transcript would land in a conversation the user left.
29
+ * - **Duration is measured, not counted.** `durationSeconds` comes off the
30
+ * clock at stop; the one-second ticker drives only the visible elapsed
31
+ * display, so a throttled timer never falsifies the delivered figure.
32
+ */
33
+ /** The audio a finished recording hands to the host. */
34
+ export interface DictationAudio {
35
+ /** The assembled recording, typed with the MIME the recorder actually used. */
36
+ readonly blob: Blob;
37
+ /** `blob.type`, surfaced so a host can switch on it without touching the blob. */
38
+ readonly mimeType: string;
39
+ /** Clock-measured whole seconds between start and stop. */
40
+ readonly durationSeconds: number;
41
+ }
42
+ export interface UseDictationOptions {
43
+ /** The host callback: receive the recording. Transcription is the host's. */
44
+ onDictate: (audio: DictationAudio) => void;
45
+ /** Capture failures in words ("Microphone access was denied…"). Optional —
46
+ * the composer shows its own notice either way; this is for hosts that log. */
47
+ onError?: (message: string) => void;
48
+ }
49
+ export interface DictationControls {
50
+ /** Whether this browser can record at all. When false, render no affordance. */
51
+ readonly supported: boolean;
52
+ readonly recording: boolean;
53
+ /** Whole seconds since the current recording started; drives the indicator. */
54
+ readonly elapsedSeconds: number;
55
+ /** Ask for the mic and start. A no-op while a recording or a prompt is open. */
56
+ readonly start: () => void;
57
+ /** Stop and deliver. Cancels a still-pending permission prompt instead. */
58
+ readonly stop: () => void;
59
+ }
60
+ /** The mime to ask the recorder for, or `undefined` to take the UA default. */
61
+ export declare function pickDictationMimeType(): string | undefined;
62
+ /** The failure as a sentence. The denied prompt is the common case and the one
63
+ * whose generic name ("NotAllowedError") says nothing to a reader. */
64
+ export declare function dictationErrorMessage(error: unknown): string;
65
+ /** `0:00`, `0:09`, `1:05`, `60:00` — minutes unbounded, seconds always two digits. */
66
+ export declare function formatDictationElapsed(totalSeconds: number): string;
67
+ export declare function useDictation({ onDictate, onError }: UseDictationOptions): DictationControls;
@@ -2,7 +2,7 @@ import {
2
2
  buildSessionNavItem,
3
3
  composeSidebarSessions,
4
4
  resolveActiveNavId
5
- } from "../chunk-PC2WYTK7.js";
5
+ } from "../chunk-EDTWGSQT.js";
6
6
 
7
7
  // src/workspace-react/index.tsx
8
8
  import { SidebarLayout } from "@tangle-network/sandbox-ui/dashboard";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.45.62",
3
+ "version": "0.45.64",
4
4
  "packageManager": "pnpm@11.17.0",
5
5
  "description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
6
6
  "keywords": [
@@ -519,15 +519,15 @@
519
519
  "@storybook/react": "^10.5.5",
520
520
  "@storybook/react-vite": "^10.5.5",
521
521
  "@tangle-network/agent-docs": "0.2.1",
522
- "@tangle-network/agent-eval": "0.145.21",
522
+ "@tangle-network/agent-eval": "0.147.0",
523
523
  "@tangle-network/agent-integrations": "0.53.54",
524
- "@tangle-network/agent-interface": "1.0.0",
525
- "@tangle-network/agent-knowledge": "8.0.5",
524
+ "@tangle-network/agent-interface": "1.0.1",
525
+ "@tangle-network/agent-knowledge": "8.0.8",
526
526
  "@tangle-network/agent-profile-materialize": "0.16.0",
527
- "@tangle-network/agent-runtime": "0.137.0",
527
+ "@tangle-network/agent-runtime": "0.141.1",
528
528
  "@tangle-network/brand": "1.5.0",
529
- "@tangle-network/sandbox": "0.27.1",
530
- "@tangle-network/sandbox-ui": "0.104.0",
529
+ "@tangle-network/sandbox": "0.27.2",
530
+ "@tangle-network/sandbox-ui": "0.105.0",
531
531
  "@tangle-network/ui": "^11.5.0",
532
532
  "@testing-library/dom": "^10.4.1",
533
533
  "@testing-library/react": "^16.3.2",
@@ -571,15 +571,15 @@
571
571
  "@firecrawl/pdf-inspector-wasm": ">=0.1.3",
572
572
  "@huggingface/transformers": ">=3",
573
573
  "@radix-ui/react-dialog": ">=1.1",
574
- "@tangle-network/agent-eval": ">=0.145.21",
574
+ "@tangle-network/agent-eval": ">=0.147.0",
575
575
  "@tangle-network/agent-integrations": ">=0.52.0",
576
576
  "@tangle-network/agent-interface": "^1.0.0",
577
- "@tangle-network/agent-knowledge": ">=8.0.5",
577
+ "@tangle-network/agent-knowledge": ">=8.0.8",
578
578
  "@tangle-network/agent-profile-materialize": ">=0.16.0",
579
- "@tangle-network/agent-runtime": ">=0.137.0",
579
+ "@tangle-network/agent-runtime": ">=0.141.1",
580
580
  "@tangle-network/brand": ">=1.5.0",
581
- "@tangle-network/sandbox": ">=0.27.1",
582
- "@tangle-network/sandbox-ui": ">=0.104.0",
581
+ "@tangle-network/sandbox": ">=0.27.2",
582
+ "@tangle-network/sandbox-ui": ">=0.105.0",
583
583
  "@tangle-network/ui": ">=11.5.0",
584
584
  "@xyflow/react": ">=12.0.0",
585
585
  "better-auth": ">=1.6.16",