@tangle-network/agent-app 0.45.64 → 0.45.66

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.
@@ -1,9 +1,17 @@
1
1
  /**
2
2
  * ChatComposer — the shared message input every agent app used to hand-roll:
3
3
  * an auto-resizing textarea (Enter sends, Shift+Enter inserts a newline), an
4
- * opt-in attach + drag-and-drop surface with pending-file chips, a streaming
5
- * Stop/Send toggle, a slot for inline controls (model picker, reasoning
6
- * effort), and a Cmd/Ctrl+L focus shortcut.
4
+ * opt-in attach + drag-and-drop + clipboard-paste surface with pending-file
5
+ * chips, a streaming Stop/Send toggle, a slot for inline controls (model
6
+ * picker, reasoning effort), and a Cmd/Ctrl+L focus shortcut.
7
+ *
8
+ * Files arrive by three routes — the picker dialog, a drop, and a paste — and
9
+ * all three funnel through `accept` (`./composer-file-accept`) before they
10
+ * reach `onAttach`, so a type the picker will not offer cannot get in by
11
+ * another route. What `accept` refuses goes to `onRejectFiles` with a reason;
12
+ * without that prop a refusal is silent, which is what the native picker also
13
+ * does. Size and count limits stay the host's job — `useComposerAttachments`
14
+ * owns them, because they depend on what is already staged.
7
15
  *
8
16
  * A REJECTED send never destroys the draft. The input clears optimistically —
9
17
  * the composer stays editable while a turn streams precisely so the next
@@ -25,6 +33,7 @@
25
33
  * fallbacks when a host hasn't defined a private chat-token set.
26
34
  */
27
35
  import { type ReactNode } from 'react';
36
+ import { type ComposerFileRejection } from './composer-file-accept';
28
37
  import { type DictationAudio } from './use-dictation';
29
38
  /** Prompt-part descriptor an uploaded file carries (the upload route's
30
39
  * `UploadedChatFile.part`), echoed back in the turn body on send. Mirrors
@@ -48,6 +57,25 @@ export interface ComposerFile {
48
57
  /** Uploaded part descriptor; set once the upload route returns. Only
49
58
  * `status: 'ready'` files with a part travel on a parts-aware send. */
50
59
  part?: ComposerFilePart;
60
+ /** Object URL for an image thumbnail on the chip. The host owns the URL's
61
+ * whole life — `URL.createObjectURL` when the file is staged,
62
+ * `URL.revokeObjectURL` when it leaves — and the composer only reads it.
63
+ * `useComposerAttachments` already does both. */
64
+ previewUrl?: string;
65
+ /** Why this file failed, shown on the chip while `status: 'error'`. Without
66
+ * it an error chip is red and mute, which tells the user nothing. */
67
+ errorMessage?: string;
68
+ }
69
+ /** A piece of context the agent will see beside the next message — an open
70
+ * file, a selected record, a pinned document. Rendered as its own chip row,
71
+ * separate from staged attachments: context is what the turn already carries,
72
+ * an attachment is what the user is adding to it. */
73
+ export interface ComposerContextItem {
74
+ id: string;
75
+ label: string;
76
+ icon?: ReactNode;
77
+ /** Omit for a chip the user cannot dismiss. */
78
+ onRemove?: () => void;
51
79
  }
52
80
  /** A send the host refused. `error` is shown verbatim in the composer's notice;
53
81
  * omit it for the generic copy. */
@@ -169,14 +197,68 @@ export interface ChatComposerProps {
169
197
  */
170
198
  controlsPlacement?: 'above' | 'inline';
171
199
  /** Attachments are opt-in: pass `onAttach` to show the attach button, accept
172
- * drag-and-drop onto the input, and render `pendingFiles` chips. */
200
+ * drag-and-drop and clipboard paste onto the input, and render
201
+ * `pendingFiles` chips. */
173
202
  onAttach?: (files: FileList) => void;
174
203
  onAttachFolder?: (files: FileList) => void;
175
204
  pendingFiles?: ComposerFile[];
176
205
  onRemoveFile?: (id: string) => void;
206
+ /** Pass it and a chip with `status: 'error'` gains a retry button. */
207
+ onRetryFile?: (id: string) => void;
208
+ /**
209
+ * File types the composer takes, in the native `<input accept>` grammar.
210
+ * Enforced on every ingress route — the picker dialog (which the user can
211
+ * override with "All Files"), drag-and-drop, and clipboard paste — so a type
212
+ * the picker will not offer cannot arrive by another route. A non-matching
213
+ * file goes to `onRejectFiles` and never reaches `onAttach`. Folder attach is
214
+ * exempt: directory selection has no native accept semantics.
215
+ */
177
216
  accept?: string;
217
+ /** Called with the files `accept` removed from a pick, drop, or paste, each
218
+ * with a reason. Without it a refusal is silent — the same feedback the
219
+ * native picker gives for a type it will not offer. */
220
+ onRejectFiles?: (rejections: ComposerFileRejection[]) => void;
178
221
  dropTitle?: string;
179
222
  dropDescription?: string;
223
+ /** Context the agent will see beside the next message, as its own chip row
224
+ * above the input. */
225
+ contextItems?: ReadonlyArray<ComposerContextItem>;
226
+ /**
227
+ * Let a staged file stand in for message text, so the send control stays live
228
+ * while an upload is in flight instead of going dead with nothing to explain
229
+ * it. Default false, where an empty message needs a `ready` file.
230
+ *
231
+ * It does NOT make an unfinished file sendable. A turn whose only content is a
232
+ * file that is still uploading or has failed never reaches the send handler —
233
+ * it would arrive empty and the attachment would be lost. The composer
234
+ * refuses it and names the reason in its notice
235
+ * ({@link attachmentsNotReadyMessage}). So the flag decides whether the
236
+ * control is live, and the composer keeps the integrity gate rather than
237
+ * leaving each host to re-derive it.
238
+ */
239
+ canSubmitAttachmentsOnly?: boolean;
240
+ /** Notice copy when a send is refused because no staged file is ready yet.
241
+ * Defaults to wording chosen from whether a file failed or is still
242
+ * uploading. */
243
+ attachmentsNotReadyMessage?: string;
244
+ /**
245
+ * Let Enter and Send keep firing while `isStreaming`, for a surface that
246
+ * queues the next turn rather than blocking on the current one. Default
247
+ * false. The button still flips to Stop while a turn streams, so this opens
248
+ * the keyboard path, not a second button.
249
+ */
250
+ canSubmitWhileBusy?: boolean;
251
+ /** Focus the input on mount — for a surface whose whole job is the input
252
+ * (an entry/hero composer), never for one docked under a transcript. */
253
+ autoFocus?: boolean;
254
+ /** Rows the input shows before it grows. Default 2. */
255
+ minRows?: number;
256
+ /** Pixel height the input grows to before it scrolls. Default 168. */
257
+ maxHeight?: number;
258
+ /** Content between the controls slot and Send — a token meter, a cost, a
259
+ * status line. It sits outside the controls slot and never shrinks, so a
260
+ * wrapping picker set cannot push it away. */
261
+ trailing?: ReactNode;
180
262
  /** `/` commands offered when the draft is exactly a leading slash token.
181
263
  * Omit (or pass []) and `/` types as ordinary text. */
182
264
  slashCommands?: SlashCommand[];
@@ -204,4 +286,4 @@ export interface ChatComposerProps {
204
286
  sendVariant?: 'pill' | 'icon';
205
287
  className?: string;
206
288
  }
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;
289
+ export declare function ChatComposer({ onSend, onSendParts, onSendFailed, sendFailureMessage, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, seed, onSeedApplied, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, onRetryFile, accept, onRejectFiles, dropTitle, dropDescription, contextItems, canSubmitAttachmentsOnly, attachmentsNotReadyMessage, canSubmitWhileBusy, autoFocus, minRows, maxHeight, trailing, slashCommands, onDictate, onDictateError, focusShortcut, floating, sendLabel, sendVariant, className, }: ChatComposerProps): import("react").JSX.Element;
@@ -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';
@@ -137,7 +137,7 @@ import {
137
137
  withoutRecordGridCreated,
138
138
  withoutRecordGridRemoved,
139
139
  withoutRecordGridUpdate
140
- } from "../chunk-OMGV3CX2.js";
140
+ } from "../chunk-AWM4H5XR.js";
141
141
  import "../chunk-FBVLEGEG.js";
142
142
  import {
143
143
  EvidenceLineageTable,
@@ -165,14 +165,18 @@ import {
165
165
  OVERLAY_SHADOW,
166
166
  POPOVER_SURFACE_ATTR,
167
167
  PopoverSurface,
168
+ acceptRejectionReason,
168
169
  effortLevelLabel,
169
170
  effortLevelsFromIds,
170
171
  effortMeterFill,
172
+ filterAcceptedFiles,
173
+ isAcceptedFileType,
171
174
  reconcileEffortLevels,
175
+ renamePastedImages,
172
176
  useComposerAttachments,
173
177
  usePending,
174
178
  usePopover
175
- } from "../chunk-FZKNVQYP.js";
179
+ } from "../chunk-23J72UUA.js";
176
180
  import {
177
181
  ProviderLogo
178
182
  } from "../chunk-MLG6XKPV.js";
@@ -303,6 +307,7 @@ export {
303
307
  Sparkline,
304
308
  WorkProductCard,
305
309
  __resetAttachmentFileCacheForTests,
310
+ acceptRejectionReason,
306
311
  activityTone,
307
312
  attachmentInputToPart,
308
313
  attachmentKindForMime,
@@ -338,6 +343,7 @@ export {
338
343
  fieldAnswer,
339
344
  fieldValuesFromAnswers,
340
345
  fileMentionsToParts,
346
+ filterAcceptedFiles,
341
347
  formatActivityCost,
342
348
  formatActivityDuration,
343
349
  formatDictationElapsed,
@@ -360,6 +366,7 @@ export {
360
366
  interactionSubmissionSignature,
361
367
  interactionTerminalNotes,
362
368
  interactionToPersistedPart,
369
+ isAcceptedFileType,
363
370
  isChatAttachmentPart,
364
371
  isLateAnswerableStatus,
365
372
  isRecordGridCellApplicable,
@@ -401,6 +408,7 @@ export {
401
408
  recordGridFail,
402
409
  recordGridOk,
403
410
  recordGridRowLabel,
411
+ renamePastedImages,
404
412
  resolveChatInteraction,
405
413
  resolveProvenanceStanding,
406
414
  responseErrorMessage,
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.45.64",
3
+ "version": "0.45.66",
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": [