@tangle-network/agent-app 0.45.63 → 0.45.65

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,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;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The composer's file-ingress filter, and the clipboard rename that goes with
3
+ * it.
4
+ *
5
+ * A file reaches a composer by three routes — the picker dialog, a drag-and-drop,
6
+ * and a clipboard paste — and only the picker gets a native `accept` filter (one
7
+ * the user can defeat with "All Files"). Every route therefore funnels through
8
+ * {@link filterAcceptedFiles}, so a type the picker will not offer cannot arrive
9
+ * by another route instead.
10
+ *
11
+ * One route can still reach a different verdict, and it does so deliberately.
12
+ * Paste is the only route that RENAMES, and {@link renamePastedImages} names a
13
+ * file after the type it declares. So a clipboard bitmap called `image.png` that
14
+ * declares `image/heic` is judged as `.heic` on paste, while the picker and a
15
+ * drop judge the name they were handed and admit it under `accept=".png"`. The
16
+ * filter is the same on all three; what differs is the name it is given, and
17
+ * paste is stricter precisely because a rename that kept the contradicting name
18
+ * would let the composer manufacture its own way past the filter.
19
+ *
20
+ * ONE accept matcher serves the package: `ChatComposer` gates its ingress on it
21
+ * and `useComposerAttachments` gates `addFiles` on it. A second implementation
22
+ * of the `accept` grammar is how the two ends of the same staging path start
23
+ * disagreeing about what a file is.
24
+ *
25
+ * Pure data in, pure data out — nothing here throws, logs, or touches the DOM,
26
+ * so a caller decides how a rejection is surfaced. Import-free beyond the
27
+ * browser's own `File`, which keeps it usable from `/web-react`'s client bundle.
28
+ */
29
+ /** A file the `accept` list refused, with the reason to show for it. */
30
+ export interface ComposerFileRejection {
31
+ file: File;
32
+ reason: string;
33
+ }
34
+ /**
35
+ * Checks one file against a comma-separated `accept` list, using the grammar of
36
+ * the native `<input accept>` attribute: extensions (`.png`), exact MIME types
37
+ * (`image/png`), and MIME wildcards (`image/*`). An absent or empty list accepts
38
+ * everything, which is what an unset `accept` prop means.
39
+ */
40
+ export declare function isAcceptedFileType(file: File, accept?: string): boolean;
41
+ /** The reason an `accept` list refused a file. One wording for every ingress
42
+ * route, so the same file reads the same whether it was picked or dropped. */
43
+ export declare function acceptRejectionReason(file: File, accept: string): string;
44
+ /**
45
+ * Splits a batch into what the `accept` list admits and what it refuses. Size
46
+ * and count limits are NOT applied here: they belong to the staging queue, which
47
+ * knows what is already staged (`useComposerAttachments`), while this runs at the
48
+ * composer's edge where that is unknown.
49
+ */
50
+ export declare function filterAcceptedFiles(files: File[] | FileList, accept?: string): {
51
+ accepted: File[];
52
+ rejected: ComposerFileRejection[];
53
+ };
54
+ /**
55
+ * Gives every generically-named clipboard image a distinct
56
+ * `pasted-image-<n>.<ext>` name. Two pastes of the same bitmap otherwise arrive
57
+ * as `image.png` twice, and a staging queue that keys on the name treats the
58
+ * second as a duplicate of the first.
59
+ *
60
+ * A number is never reused. The search avoids every `pasted-image-<n>` already
61
+ * present in `stagedNames` (the queue the host still holds, which outlives this
62
+ * composer's own count) and in the batch itself (one paste can carry a file
63
+ * already named that way beside a raw bitmap), so a collision is not reachable
64
+ * rather than merely unlikely. `startIndex` is the caller's running count, and
65
+ * `nextIndex` is the count to hand the next paste.
66
+ *
67
+ * Files that already carry a real name pass through untouched, so a copied
68
+ * `report.pdf` keeps being `report.pdf`. So does an image whose extension
69
+ * cannot be derived from what it declares — a renamed file must never claim a
70
+ * format it is not. Only the name changes: the bytes, the type and the
71
+ * modification time travel with it, so downstream fingerprinting still sees
72
+ * the file the user pasted.
73
+ */
74
+ export declare function renamePastedImages(files: File[], startIndex: number, stagedNames?: Iterable<string>): {
75
+ files: File[];
76
+ nextIndex: number;
77
+ };
@@ -30,6 +30,7 @@ import type { WorkProductPersistedPart } from '../work-product/types';
30
30
  export * from './chat-stream';
31
31
  export * from './chat-interactions';
32
32
  export * from './chat-composer';
33
+ export * from './composer-file-accept';
33
34
  export * from './interaction-card-support';
34
35
  export * from './interaction-question-card';
35
36
  export * from './interaction-plan-card';
@@ -53,8 +54,10 @@ export * from './sandbox-terminal';
53
54
  export * from './seat-paywall';
54
55
  export * from './session-history';
55
56
  export * from './record-grid';
57
+ export * from './command-palette';
56
58
  export * from './sparkline';
57
59
  export * from './insight-card';
60
+ export * from './use-dictation';
58
61
  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
62
  export { AgentSessionControls, type AgentSessionControlsProps, } from './agent-session-controls';
60
63
  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-AWM4H5XR.js";
135
141
  import "../chunk-FBVLEGEG.js";
136
142
  import {
137
143
  EvidenceLineageTable,
@@ -159,14 +165,18 @@ import {
159
165
  OVERLAY_SHADOW,
160
166
  POPOVER_SURFACE_ATTR,
161
167
  PopoverSurface,
168
+ acceptRejectionReason,
162
169
  effortLevelLabel,
163
170
  effortLevelsFromIds,
164
171
  effortMeterFill,
172
+ filterAcceptedFiles,
173
+ isAcceptedFileType,
165
174
  reconcileEffortLevels,
175
+ renamePastedImages,
166
176
  useComposerAttachments,
167
177
  usePending,
168
178
  usePopover
169
- } from "../chunk-FZKNVQYP.js";
179
+ } from "../chunk-23J72UUA.js";
170
180
  import {
171
181
  ProviderLogo
172
182
  } from "../chunk-MLG6XKPV.js";
@@ -175,7 +185,7 @@ import {
175
185
  useSandboxTerminalConnection
176
186
  } from "../chunk-BATKJP3P.js";
177
187
  import "../chunk-3UBAO3N5.js";
178
- import "../chunk-PC2WYTK7.js";
188
+ import "../chunk-EDTWGSQT.js";
179
189
  import "../chunk-QY4BRKRJ.js";
180
190
  import {
181
191
  ATTACHMENT_ACCEPT
@@ -238,6 +248,7 @@ export {
238
248
  ChatComposer,
239
249
  ChatEmptyState,
240
250
  ChatMessages,
251
+ CommandPalette,
241
252
  DEFAULT_EFFORT_LEVELS,
242
253
  DEFAULT_INSIGHT_PAGE_SIZE,
243
254
  DEFAULT_MENTION_EMPTY_TEXT,
@@ -296,6 +307,7 @@ export {
296
307
  Sparkline,
297
308
  WorkProductCard,
298
309
  __resetAttachmentFileCacheForTests,
310
+ acceptRejectionReason,
299
311
  activityTone,
300
312
  attachmentInputToPart,
301
313
  attachmentKindForMime,
@@ -320,6 +332,8 @@ export {
320
332
  dedupeQuestionInteractionsByContent,
321
333
  describeProvenance,
322
334
  describeProvenanceSourceStatus,
335
+ dictationErrorMessage,
336
+ diffRecordGridProposal,
323
337
  dispatchChatStreamLine,
324
338
  durableChatCardsFromParts,
325
339
  effortLevelLabel,
@@ -329,8 +343,10 @@ export {
329
343
  fieldAnswer,
330
344
  fieldValuesFromAnswers,
331
345
  fileMentionsToParts,
346
+ filterAcceptedFiles,
332
347
  formatActivityCost,
333
348
  formatActivityDuration,
349
+ formatDictationElapsed,
334
350
  formatInsightDelta,
335
351
  formatModelCost,
336
352
  formatRecordGridValue,
@@ -350,6 +366,7 @@ export {
350
366
  interactionSubmissionSignature,
351
367
  interactionTerminalNotes,
352
368
  interactionToPersistedPart,
369
+ isAcceptedFileType,
353
370
  isChatAttachmentPart,
354
371
  isLateAnswerableStatus,
355
372
  isRecordGridCellApplicable,
@@ -375,6 +392,7 @@ export {
375
392
  parseReviewQueueItem,
376
393
  pendingApprovalOf,
377
394
  persistedPartToInteraction,
395
+ pickDictationMimeType,
378
396
  projectRecordGridRows,
379
397
  provenanceBasisMeta,
380
398
  provenanceGaps,
@@ -390,6 +408,7 @@ export {
390
408
  recordGridFail,
391
409
  recordGridOk,
392
410
  recordGridRowLabel,
411
+ renamePastedImages,
393
412
  resolveChatInteraction,
394
413
  resolveProvenanceStanding,
395
414
  responseErrorMessage,
@@ -413,6 +432,7 @@ export {
413
432
  upsertChatInteraction,
414
433
  useChatInteractions,
415
434
  useComposerAttachments,
435
+ useDictation,
416
436
  useDurablePlanFlow,
417
437
  useFileMentions,
418
438
  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;
@@ -15,10 +15,9 @@
15
15
  * the URL and a `RequestInit` override, e.g. an auth header);
16
16
  * - `sonner` toasts become `onReject` (client pre-validation, never hits the
17
17
  * network) and `onError` (a request that reached the server and failed);
18
- * - the sandbox-ui `validateComposerFiles` import becomes a small
19
- * accept-list matcher re-implemented locally (`isAcceptedFileType`,
20
- * mirroring its `accept`-string matching byte-for-byte) this module
21
- * stays free of the sandbox-ui peer;
18
+ * - the `accept`-string gate comes from `./composer-file-accept`, the one
19
+ * matcher `ChatComposer` also funnels its picker/drop/paste ingress
20
+ * through, so both ends of the staging path admit the same files;
22
21
  * - the response is expected to be `{ files: ChatAttachmentInput[] }` (full
23
22
  * server-authoritative descriptors — size/mediaType/kind — not gtm's
24
23
  * `{path, name}`), so `references` is a verbatim pass-through with no
@@ -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.63",
3
+ "version": "0.45.65",
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": [
@@ -524,7 +524,7 @@
524
524
  "@tangle-network/agent-interface": "1.0.1",
525
525
  "@tangle-network/agent-knowledge": "8.0.8",
526
526
  "@tangle-network/agent-profile-materialize": "0.16.0",
527
- "@tangle-network/agent-runtime": "0.140.0",
527
+ "@tangle-network/agent-runtime": "0.141.1",
528
528
  "@tangle-network/brand": "1.5.0",
529
529
  "@tangle-network/sandbox": "0.27.2",
530
530
  "@tangle-network/sandbox-ui": "0.105.0",
@@ -576,7 +576,7 @@
576
576
  "@tangle-network/agent-interface": "^1.0.0",
577
577
  "@tangle-network/agent-knowledge": ">=8.0.8",
578
578
  "@tangle-network/agent-profile-materialize": ">=0.16.0",
579
- "@tangle-network/agent-runtime": ">=0.140.0",
579
+ "@tangle-network/agent-runtime": ">=0.141.1",
580
580
  "@tangle-network/brand": ">=1.5.0",
581
581
  "@tangle-network/sandbox": ">=0.27.2",
582
582
  "@tangle-network/sandbox-ui": ">=0.105.0",