@tangle-network/agent-app 0.43.50 → 0.43.52

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.
@@ -5,14 +5,14 @@ export { d as ComposerAnswerDelivery, I as INTERACTION_CANCEL_EVENT, e as INTERA
5
5
  import { ChatPlan } from '../plans/index.js';
6
6
  import { InteractionData } from '@tangle-network/agent-interface';
7
7
  export { InteractionData, InteractionOutcome, InteractionRequest } from '@tangle-network/agent-interface';
8
- import { K as FileMention, f as ChatMentionPart } from '../parts-IB-Kbb7z.js';
9
- export { O as ChatAttachmentInput, C as ChatAttachmentKind, a as ChatAttachmentPart, e as ChatMentionKind, N as ChatTurnFilePartInput, M as ChatTurnPartInput, L as ChatTurnRequestPayload, R as DISPATCH_MAX_MEDIA_PARTS, T as DISPATCH_MAX_PARTS, U as DISPATCH_REQUEST_MAX_BYTES, V as DISPATCH_STRUCTURAL_RESERVE_BYTES, t as attachmentInputToPart, u as attachmentKindForMime, v as attachmentPartsFromMessageParts, $ as base64WireLen, a0 as buildMentionPromptBlock, a1 as chatTurnRequestInit, a2 as fileMentionsToParts, y as isChatAttachmentPart, a4 as mediaTypeForMentionPath, H as mentionInputToPart, a5 as mentionKindForPath, I as mentionPartsFromMessageParts } from '../parts-IB-Kbb7z.js';
8
+ import { K as FileMention, f as ChatMentionPart, a as ChatAttachmentPart, C as ChatAttachmentKind, O as ChatAttachmentInput } from '../parts-IB-Kbb7z.js';
9
+ export { e as ChatMentionKind, N as ChatTurnFilePartInput, M as ChatTurnPartInput, L as ChatTurnRequestPayload, R as DISPATCH_MAX_MEDIA_PARTS, T as DISPATCH_MAX_PARTS, U as DISPATCH_REQUEST_MAX_BYTES, V as DISPATCH_STRUCTURAL_RESERVE_BYTES, t as attachmentInputToPart, u as attachmentKindForMime, v as attachmentPartsFromMessageParts, $ as base64WireLen, a0 as buildMentionPromptBlock, a1 as chatTurnRequestInit, a2 as fileMentionsToParts, y as isChatAttachmentPart, a4 as mediaTypeForMentionPath, H as mentionInputToPart, a5 as mentionKindForPath, I as mentionPartsFromMessageParts } from '../parts-IB-Kbb7z.js';
10
10
  import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
11
11
  import { a as FlowTrace } from '../flow-types-Cb_AblZs.js';
12
12
  export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse, U as UseSandboxTerminalConnectionOptions, b as UseSandboxTerminalConnectionResult, t as tabTerminalConnectionId, u as useSandboxTerminalConnection } from '../sandbox-terminal-BIIC__CP.js';
13
13
  import { CatalogModel } from '../catalog/index.js';
14
14
  import { Harness } from '../harness/index.js';
15
- export { b as FileIndexReadyResponse, c as FileIndexResponse, d as FileIndexWarmingResponse } from '../file-index-b26ee-_R.js';
15
+ export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-DX2KIzMC.js';
16
16
  export { d as attachmentPartKey } from '../stream-normalizer-DWvtmY6F.js';
17
17
 
18
18
  type DurablePlanDecision = 'approved' | 'rejected';
@@ -672,6 +672,170 @@ declare function segmentMentionContent(content: string, parts: ReadonlyArray<Cha
672
672
  matched: Set<ChatMentionPart>;
673
673
  };
674
674
 
675
+ /**
676
+ * Renders a message's attachment parts (images + files) as thumbnails and
677
+ * download chips — the transcript-side counterpart to `ChatComposer`'s
678
+ * staged-upload chips. Ported from gtm-agent's `chat-attachment-parts.tsx`
679
+ * onto agent-app's RAW-BYTES download contract: the host supplies
680
+ * `resolveFileUrl(part)`, a URL that serves the attachment's raw bytes
681
+ * directly, so this module never parses a JSON `{file:{blobUrl,body}}`
682
+ * envelope or decodes a `[base64]` marker the way gtm's vault route did.
683
+ *
684
+ * No icon-library or primitives dependency (`ChatComposer`'s house style):
685
+ * the loading skeleton is an inline `animate-pulse` span and the few glyphs
686
+ * are inline SVGs.
687
+ */
688
+
689
+ /** Typed outcome of fetching one attachment's raw bytes. Callers must check
690
+ * `ok` before touching `blob` — a failed fetch never produces a blank
691
+ * render, it produces a visible error state. */
692
+ type AttachmentFileResult = {
693
+ ok: true;
694
+ blob: Blob;
695
+ } | {
696
+ ok: false;
697
+ message: string;
698
+ };
699
+ declare function __resetAttachmentFileCacheForTests(): void;
700
+ /** Fetches (and caches) the raw bytes behind one attachment url. Concurrent
701
+ * callers for the SAME url dedupe to one in-flight fetch. Only a successful
702
+ * settlement stays cached — evicting failures means a remount or click-retry
703
+ * after a transient error issues a fresh fetch. */
704
+ declare function loadAttachmentFile(url: string, fetchFile?: (url: string) => Promise<Response>): Promise<AttachmentFileResult>;
705
+ /** Drives an anchor-click download from an already-resolved blob. Returns a
706
+ * typed outcome rather than throwing — a chip that fails to synthesize the
707
+ * download must show the failure, not silently no-op. */
708
+ declare function triggerAttachmentDownload(name: string, blob: Blob): {
709
+ ok: true;
710
+ } | {
711
+ ok: false;
712
+ message: string;
713
+ };
714
+ interface MessageAttachmentsProps {
715
+ parts: ChatAttachmentPart[];
716
+ /** URL serving the attachment's RAW bytes. */
717
+ resolveFileUrl: (part: ChatAttachmentPart) => string;
718
+ /** Row alignment — a user-bubble attachment row is right-aligned by
719
+ * default; pass `"start"` for an assistant-turn attachment, which sits
720
+ * inline with the rest of the transcript. */
721
+ justify?: 'start' | 'end';
722
+ /** Override the fetch used to load an attachment's bytes. Default:
723
+ * `fetch(url, { credentials: 'same-origin' })`. */
724
+ fetchFile?: (url: string) => Promise<Response>;
725
+ }
726
+ /** Renders a message's attachment parts as a row of image thumbnails and file
727
+ * chips. `null` when there are none, so callers can render unconditionally
728
+ * without an extra length check. */
729
+ declare function MessageAttachments({ parts, resolveFileUrl, justify, fetchFile }: MessageAttachmentsProps): ReactNode;
730
+
731
+ /**
732
+ * `useComposerAttachments` — the composer's staged-upload lifecycle: validate
733
+ * selected/dropped/pasted files against the shared limits (the SAME
734
+ * `sniffBinary`/`checkAttachmentType`/size-cap vocabulary the store-backed
735
+ * upload route enforces server-side, `../chat-routes/attachment-validation`
736
+ * + `../chat-routes/binary-sniff`), upload each accepted file with one POST
737
+ * request per file (so a single failure never poisons the batch), and track
738
+ * every file's status so a host composer can render chips and gate sending.
739
+ *
740
+ * Ported from gtm-agent's `src/components/composer-attachments.tsx`
741
+ * (gtm#584/#592/#593 hardened the sniff gate and batch semantics this leans
742
+ * on), de-gtm-ified:
743
+ * - the hardcoded `/api/vault/upload?workspaceId=` URL becomes
744
+ * `uploadUrl`/`buildUploadRequest` (the latter wins — it hands back both
745
+ * the URL and a `RequestInit` override, e.g. an auth header);
746
+ * - `sonner` toasts become `onReject` (client pre-validation, never hits the
747
+ * network) and `onError` (a request that reached the server and failed);
748
+ * - the sandbox-ui `validateComposerFiles` import becomes a small
749
+ * accept-list matcher re-implemented locally (`isAcceptedFileType`,
750
+ * mirroring its `accept`-string matching byte-for-byte) — this module
751
+ * stays free of the sandbox-ui peer;
752
+ * - the response is expected to be `{ files: ChatAttachmentInput[] }` (full
753
+ * server-authoritative descriptors — size/mediaType/kind — not gtm's
754
+ * `{path, name}`), so `references` is a verbatim pass-through with no
755
+ * client recompute;
756
+ * - `workspaceId`'s truthiness gate becomes `enabled` (default `true`).
757
+ *
758
+ * Import-free beyond React + the browser-safe `/chat-routes` validation core:
759
+ * this module ships through `/web-react` into client bundles
760
+ * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here
761
+ * may reach a Node builtin, `sandbox-ui`, or an engine package.
762
+ */
763
+
764
+ interface UseComposerAttachmentsOptions {
765
+ /** Simple upload target: every file POSTs here. Ignored when
766
+ * `buildUploadRequest` is provided. */
767
+ uploadUrl?: string;
768
+ /** Full request-building seam (auth headers, per-file routing, …) — wins
769
+ * over `uploadUrl` when both are set. */
770
+ buildUploadRequest?: (args: {
771
+ file: File;
772
+ name: string;
773
+ form: FormData;
774
+ }) => {
775
+ url: string;
776
+ init?: Omit<RequestInit, 'body' | 'signal'>;
777
+ };
778
+ /** Client pre-validation rejections — a file that never reaches the
779
+ * network (bad type, over a size cap, over count, disallowed kind). */
780
+ onReject?: (reason: string, file?: File) => void;
781
+ /** A file that reached the upload endpoint and failed (HTTP error,
782
+ * transport error, malformed response). */
783
+ onError?: (reason: string) => void;
784
+ limits?: {
785
+ maxCount?: number;
786
+ maxBinaryBytes?: number;
787
+ maxTextBytes?: number;
788
+ maxTotalBytes?: number;
789
+ };
790
+ /** Attachment kinds accepted, checked against the sniffed content's
791
+ * mime. Default: both (`['image', 'file']` — i.e. no restriction). */
792
+ allowedKinds?: ChatAttachmentKind[];
793
+ /** `<input accept>`-style gate for the file picker/drop/paste path.
794
+ * Default {@link ATTACHMENT_ACCEPT}. */
795
+ accept?: string;
796
+ /** When `false`, `addFiles` rejects every call via `onReject` (and
797
+ * `blockReason` explains why) instead of staging anything — the
798
+ * replacement for gtm's `workspaceId`-truthiness gate (e.g. no workspace
799
+ * loaded yet). Default `true`. */
800
+ enabled?: boolean;
801
+ }
802
+ interface UseComposerAttachmentsResult {
803
+ /** Chip models for `ChatComposer`'s `pendingFiles` prop, one per staged
804
+ * file — `kind` is always `'file'` (agent-app's `ComposerFile.kind`
805
+ * discriminates file-vs-folder chips, not attachment media type). */
806
+ composerFiles: ComposerFile[];
807
+ /** Ready-to-send attachment descriptors — only files whose upload
808
+ * succeeded, straight from the server's response (no recompute). Feed
809
+ * this into `ChatTurnRequestPayload.attachments`. */
810
+ references: ChatAttachmentInput[];
811
+ /** Validate + stage + upload the given files, one request per file. */
812
+ addFiles: (files: File[] | FileList) => Promise<void>;
813
+ /** Re-upload a failed entry using its retained `File`. */
814
+ retry: (id: string) => void;
815
+ /** Drop one staged entry, aborting its upload and revoking its preview. */
816
+ removeAttachment: (id: string) => void;
817
+ /** Forget every staged entry (call after a successful send). */
818
+ clear: () => void;
819
+ /** True while any file is still pending or uploading. */
820
+ hasPending: boolean;
821
+ /** True while any file failed to upload. */
822
+ hasError: boolean;
823
+ /** Why a send is blocked, or `null` when the queue is clean. */
824
+ blockReason: string | null;
825
+ }
826
+ /**
827
+ * Owns the composer's attachment lifecycle: validate selected/dropped/pasted
828
+ * files against the shared limits, upload each accepted file to the
829
+ * product's store (one request per file), and track every file's status so
830
+ * the composer can render chips and gate sending.
831
+ *
832
+ * Failures surface loud — a rejected file calls `onReject` and is never
833
+ * uploaded; a failed upload calls `onError` and leaves an error chip the user
834
+ * can retry or remove. `references` only ever contains files whose upload the
835
+ * server actually confirmed.
836
+ */
837
+ declare function useComposerAttachments(options: UseComposerAttachmentsOptions): UseComposerAttachmentsResult;
838
+
675
839
  /**
676
840
  * Provider brand marks — real logo path data (simple-icons / SVG Logos, both
677
841
  * CC0) inlined so the picker shows actual provider identity instead of
@@ -1063,6 +1227,12 @@ interface ChatMessagesProps {
1063
1227
  * preserve the current layout; pass `{ title }` (or your own node via
1064
1228
  * `header`) to show the Tangle mark + product title in the chat shell. */
1065
1229
  header?: ReactNode;
1230
+ /** Resolve a raw-bytes download URL for one attachment part. When set, any
1231
+ * message carrying attachment parts (`file`/`image` parts with a `path`,
1232
+ * see `attachmentPartsFromMessageParts`) renders them as a `MessageAttachments`
1233
+ * row next to the bubble — thumbnails for images, download chips for files.
1234
+ * Absent → today's rendering, byte-identical (no attachment row). */
1235
+ resolveAttachmentUrl?: (part: ChatAttachmentPart) => string;
1066
1236
  }
1067
1237
  /** One starting "door" in the chat first-run state — a concrete, labeled action
1068
1238
  * (start from a template, do it by hand, ask the agent), not a placeholder. */
@@ -1105,6 +1275,6 @@ declare function useThinkingSeconds(active: boolean): number;
1105
1275
  * model id, tokens/sec, and cost, plus a collapsible thinking section and
1106
1276
  * tool-call chips.
1107
1277
  */
1108
- declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, durableCards, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, emptyState, header, }: ChatMessagesProps): react.JSX.Element;
1278
+ declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, durableCards, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, emptyState, header, resolveAttachmentUrl, }: ChatMessagesProps): react.JSX.Element;
1109
1279
 
1110
- export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, CatalogModel, ChatComposer, type ChatComposerProps, type ChatEmptyDoor, ChatEmptyState, type ChatEmptyStateProps, ChatInteraction, ChatInteractionField, type ChatInteractionRestoreMode, ChatInteractionStatus, ChatMentionPart, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, ChatSelectField, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ComposerFile, type ComposerFilePart, type ComposerMentionProp, type ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, DEFAULT_MENTION_EMPTY_TEXT, DEFAULT_MENTION_LIMIT, type DurableChatCard, DurableChatCards, type DurableChatCardsProps, type DurableInteractionAnswerSubmitterOptions, DurablePlanCard, type DurablePlanCardProps, DurablePlanClientError, type DurablePlanCurrentInput, type DurablePlanDecision, type DurablePlanDecisionClient, type DurablePlanDecisionClientOptions, type DurablePlanDecisionInput, type DurablePlanDecisionResult, type DurablePlanFollowUpReceipt, type EffortLevel, EffortPicker, type EffortPickerProps, type FieldValues, FileMention, FlowWaterfall, type FlowWaterfallProps, INDEX_REFRESH_AFTER_MS, INTERACTION_SUBMIT_TIMEOUT_MESSAGE, INTERACTION_SUBMIT_TIMEOUT_MS, InteractionActionButton, type InteractionAnswerSubmission, type InteractionAnswerSubmitterOptions, InteractionAnswers, type InteractionAttemptStore, InteractionBadge, type InteractionBadgeVariant, InteractionCancelData, InteractionPlanCard, type InteractionPlanCardProps, InteractionQuestionCard, type InteractionQuestionCardProps, InteractionRequestWire, type InteractionSubmitResult, type MentionItem, type MentionTextSegment, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, QuestionOptionList, type QuestionOptionListProps, type RestoreChatInteractionsOptions, RunDrillIn, type RunDrillInProps, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type SubmitInteractionAnswer, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseChatInteractionsOptions, type UseChatInteractionsResult, type UseDurablePlanFlowOptions, type UseDurablePlanFlowResult, type UseFileMentionsOptions, type UseFileMentionsResult, type WaterfallRow, activityTone, buildAnswerData, cancelChatInteraction, consumeChatStream, createDurableInteractionAnswerSubmitter, createDurablePlanDecisionClient, createInteractionAnswerSubmitter, createMemoryInteractionAttemptStore, createSessionInteractionAttemptStore, dispatchChatStreamLine, durableChatCardsFromParts, fieldAnswer, fieldValuesFromAnswers, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, hasSecretField, hydrateChatInteractions, interactionStatusLabels, interactionSubmissionSignature, interactionTerminalNotes, isLateAnswerableStatus, lateAnswerMessage, mergeActivityPages, nextRevealCount, pendingApprovalOf, rankFileMentions, resolveChatInteraction, responseErrorMessage, restoreChatInteractions, segmentMentionContent, streamChatTurn, terminalizePendingChatInteractions, upsertChatInteraction, useChatInteractions, useDurablePlanFlow, useFileMentions, usePending, usePopover, useSmoothText, useThinkingSeconds, waterfallLayout };
1280
+ export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, type AttachmentFileResult, CatalogModel, ChatAttachmentInput, ChatAttachmentKind, ChatAttachmentPart, ChatComposer, type ChatComposerProps, type ChatEmptyDoor, ChatEmptyState, type ChatEmptyStateProps, ChatInteraction, ChatInteractionField, type ChatInteractionRestoreMode, ChatInteractionStatus, ChatMentionPart, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, ChatSelectField, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ComposerFile, type ComposerFilePart, type ComposerMentionProp, type ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, DEFAULT_MENTION_EMPTY_TEXT, DEFAULT_MENTION_LIMIT, type DurableChatCard, DurableChatCards, type DurableChatCardsProps, type DurableInteractionAnswerSubmitterOptions, DurablePlanCard, type DurablePlanCardProps, DurablePlanClientError, type DurablePlanCurrentInput, type DurablePlanDecision, type DurablePlanDecisionClient, type DurablePlanDecisionClientOptions, type DurablePlanDecisionInput, type DurablePlanDecisionResult, type DurablePlanFollowUpReceipt, type EffortLevel, EffortPicker, type EffortPickerProps, type FieldValues, FileMention, FlowWaterfall, type FlowWaterfallProps, INDEX_REFRESH_AFTER_MS, INTERACTION_SUBMIT_TIMEOUT_MESSAGE, INTERACTION_SUBMIT_TIMEOUT_MS, InteractionActionButton, type InteractionAnswerSubmission, type InteractionAnswerSubmitterOptions, InteractionAnswers, type InteractionAttemptStore, InteractionBadge, type InteractionBadgeVariant, InteractionCancelData, InteractionPlanCard, type InteractionPlanCardProps, InteractionQuestionCard, type InteractionQuestionCardProps, InteractionRequestWire, type InteractionSubmitResult, type MentionItem, type MentionTextSegment, MessageAttachments, type MessageAttachmentsProps, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, QuestionOptionList, type QuestionOptionListProps, type RestoreChatInteractionsOptions, RunDrillIn, type RunDrillInProps, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type SubmitInteractionAnswer, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseChatInteractionsOptions, type UseChatInteractionsResult, type UseComposerAttachmentsOptions, type UseComposerAttachmentsResult, type UseDurablePlanFlowOptions, type UseDurablePlanFlowResult, type UseFileMentionsOptions, type UseFileMentionsResult, type WaterfallRow, __resetAttachmentFileCacheForTests, activityTone, buildAnswerData, cancelChatInteraction, consumeChatStream, createDurableInteractionAnswerSubmitter, createDurablePlanDecisionClient, createInteractionAnswerSubmitter, createMemoryInteractionAttemptStore, createSessionInteractionAttemptStore, dispatchChatStreamLine, durableChatCardsFromParts, fieldAnswer, fieldValuesFromAnswers, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, hasSecretField, hydrateChatInteractions, interactionStatusLabels, interactionSubmissionSignature, interactionTerminalNotes, isLateAnswerableStatus, lateAnswerMessage, loadAttachmentFile, mergeActivityPages, nextRevealCount, pendingApprovalOf, rankFileMentions, resolveChatInteraction, responseErrorMessage, restoreChatInteractions, segmentMentionContent, streamChatTurn, terminalizePendingChatInteractions, triggerAttachmentDownload, upsertChatInteraction, useChatInteractions, useComposerAttachments, useDurablePlanFlow, useFileMentions, usePending, usePopover, useSmoothText, useThinkingSeconds, waterfallLayout };
@@ -19,12 +19,14 @@ import {
19
19
  InteractionBadge,
20
20
  InteractionPlanCard,
21
21
  InteractionQuestionCard,
22
+ MessageAttachments,
22
23
  MissionActivityLane,
23
24
  ModelPicker,
24
25
  ProviderLogo,
25
26
  QuestionOptionList,
26
27
  RunDrillIn,
27
28
  SeatPaywall,
29
+ __resetAttachmentFileCacheForTests,
28
30
  activityTone,
29
31
  buildAnswerData,
30
32
  cancelChatInteraction,
@@ -49,6 +51,7 @@ import {
49
51
  interactionTerminalNotes,
50
52
  isLateAnswerableStatus,
51
53
  lateAnswerMessage,
54
+ loadAttachmentFile,
52
55
  mergeActivityPages,
53
56
  nextRevealCount,
54
57
  pendingApprovalOf,
@@ -59,8 +62,10 @@ import {
59
62
  segmentMentionContent,
60
63
  streamChatTurn,
61
64
  terminalizePendingChatInteractions,
65
+ triggerAttachmentDownload,
62
66
  upsertChatInteraction,
63
67
  useChatInteractions,
68
+ useComposerAttachments,
64
69
  useDurablePlanFlow,
65
70
  useFileMentions,
66
71
  usePending,
@@ -68,12 +73,14 @@ import {
68
73
  useSmoothText,
69
74
  useThinkingSeconds,
70
75
  waterfallLayout
71
- } from "../chunk-O6H2WD3I.js";
76
+ } from "../chunk-EIG7ZQW2.js";
72
77
  import {
73
78
  tabTerminalConnectionId,
74
79
  useSandboxTerminalConnection
75
80
  } from "../chunk-65P3HJY3.js";
76
- import "../chunk-LCNY3DCM.js";
81
+ import {
82
+ ATTACHMENT_ACCEPT
83
+ } from "../chunk-3EKOSBYL.js";
77
84
  import "../chunk-2QI7XV2T.js";
78
85
  import {
79
86
  DISPATCH_MAX_MEDIA_PARTS,
@@ -124,6 +131,7 @@ import {
124
131
  import "../chunk-SIXYZ2FB.js";
125
132
  import "../chunk-MCJSS6SM.js";
126
133
  export {
134
+ ATTACHMENT_ACCEPT,
127
135
  AgentActivityPanel,
128
136
  AgentSessionControls,
129
137
  ChatComposer,
@@ -151,12 +159,14 @@ export {
151
159
  InteractionBadge,
152
160
  InteractionPlanCard,
153
161
  InteractionQuestionCard,
162
+ MessageAttachments,
154
163
  MissionActivityLane,
155
164
  ModelPicker,
156
165
  ProviderLogo,
157
166
  QuestionOptionList,
158
167
  RunDrillIn,
159
168
  SeatPaywall,
169
+ __resetAttachmentFileCacheForTests,
160
170
  activityTone,
161
171
  attachmentInputToPart,
162
172
  attachmentKindForMime,
@@ -202,6 +212,7 @@ export {
202
212
  isSafeInteractionFieldKey,
203
213
  isTerminalInteractionStatus,
204
214
  lateAnswerMessage,
215
+ loadAttachmentFile,
205
216
  mediaTypeForMentionPath,
206
217
  mentionInputToPart,
207
218
  mentionKindForPath,
@@ -225,8 +236,10 @@ export {
225
236
  streamChatTurn,
226
237
  tabTerminalConnectionId,
227
238
  terminalizePendingChatInteractions,
239
+ triggerAttachmentDownload,
228
240
  upsertChatInteraction,
229
241
  useChatInteractions,
242
+ useComposerAttachments,
230
243
  useDurablePlanFlow,
231
244
  useFileMentions,
232
245
  usePending,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.43.50",
3
+ "version": "0.43.52",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -1,84 +0,0 @@
1
- // src/chat-routes/file-index.ts
2
- var DEFAULT_IGNORE_SEGMENTS = [
3
- "node_modules",
4
- "dist",
5
- "build",
6
- "out",
7
- "coverage",
8
- "target",
9
- "__pycache__",
10
- "venv"
11
- ];
12
- function isIgnored(relPath, ignoreSegments) {
13
- for (const segment of relPath.split("/")) {
14
- if (!segment) continue;
15
- if (segment.startsWith(".")) return true;
16
- if (ignoreSegments.has(segment)) return true;
17
- }
18
- return false;
19
- }
20
- function relativeTo(root, path) {
21
- const prefix = root.endsWith("/") ? root : `${root}/`;
22
- if (path.startsWith(prefix)) return path.slice(prefix.length);
23
- if (path === root) return "";
24
- return path;
25
- }
26
- function basename(path) {
27
- const segments = path.split("/").filter(Boolean);
28
- return segments[segments.length - 1] ?? path;
29
- }
30
- function isMissingRootError(err, root) {
31
- if (!(err instanceof Error)) return false;
32
- if (err.code !== "VALIDATION_ERROR") return false;
33
- return /ENOENT/.test(err.message) && /no such file or directory/.test(err.message) && new RegExp(`\\blstat '${escapeRegExp(root)}'`).test(err.message);
34
- }
35
- function escapeRegExp(value) {
36
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37
- }
38
- function createSandboxFileIndexRoute(options) {
39
- const maxDepth = options.maxDepth ?? 12;
40
- const maxEntries = options.maxEntries ?? 5e3;
41
- const cacheTtlSeconds = options.cacheTtlSeconds ?? 20;
42
- const staticIgnore = /* @__PURE__ */ new Set([...DEFAULT_IGNORE_SEGMENTS, ...options.ignore ?? []]);
43
- return async function fileIndex(request) {
44
- const auth = await options.authorize({ request });
45
- if (auth.status === "denied") return auth.response;
46
- if (auth.status === "warming") {
47
- return Response.json({ status: "warming" });
48
- }
49
- const cache = options.cache;
50
- if (cache && auth.cacheKey) {
51
- const cached = await cache.get(auth.cacheKey);
52
- if (cached) return Response.json(cached);
53
- }
54
- const ignoreSegments = auth.ignore?.length ? /* @__PURE__ */ new Set([...staticIgnore, ...auth.ignore]) : staticIgnore;
55
- let scan;
56
- try {
57
- scan = await auth.fs.tree(auth.root, { maxDepth });
58
- } catch (err) {
59
- if (!isMissingRootError(err, auth.root)) throw err;
60
- return Response.json({ status: "warming" });
61
- }
62
- const filtered = scan.files.filter((f) => !isIgnored(relativeTo(scan.root, f.path), ignoreSegments));
63
- const truncated = scan.stats.truncated || filtered.length > maxEntries;
64
- const files = filtered.slice(0, maxEntries).map((f) => {
65
- const path = relativeTo(scan.root, f.path);
66
- const entry = { path, name: basename(path) };
67
- if (typeof f.size === "number") entry.size = f.size;
68
- return entry;
69
- });
70
- const body = {
71
- status: "ready",
72
- files,
73
- truncated,
74
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
75
- };
76
- if (cache && auth.cacheKey) await cache.put(auth.cacheKey, body, { ttlSeconds: cacheTtlSeconds });
77
- return Response.json(body);
78
- };
79
- }
80
-
81
- export {
82
- createSandboxFileIndexRoute
83
- };
84
- //# sourceMappingURL=chunk-LCNY3DCM.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/chat-routes/file-index.ts"],"sourcesContent":["/**\n * `createSandboxFileIndexRoute` — server side of `@`-file-mentions\n * (companion to sandbox-ui#184's composer mention primitive). Serves a flat,\n * ignore-filtered listing of the workspace sandbox so `useFileMentions`\n * (`/web-react`) can filter it client-side without a round trip per\n * keystroke.\n *\n * Same seam style as `createUploadRoute`: `authorize({ request })` resolves a\n * structural `{ tree(path, opts) }` handle (the shape of the sandbox SDK's\n * `box.fs.tree`) — no SDK import here. `authorize` also carries the\n * cold-box signal: a sandbox that isn't running yet answers `{ status:\n * 'warming' }` directly, never provisions-and-waits inside this route.\n *\n * A box can also be running with its workspace root not yet materialised, which\n * `authorize` cannot see; the route recognises that one signal off `fs.tree`\n * and answers `warming` too, so every consumer gets the retry-and-wait state\n * instead of a 500. Every other `tree()` failure propagates.\n */\n\nimport type { FileMention } from './wire'\n\n/** One entry from a structural `tree()` scan. Mirrors the sandbox SDK's\n * `FileTreeFile` (`path`, `size`, `mtime`) — `mtime` is unused here so it's\n * omitted from the structural match. */\nexport interface SandboxTreeFile {\n path: string\n size: number\n}\n\n/** Structural match of the sandbox SDK's `box.fs.tree` result shape\n * (`FileTreeResult`). `stats.truncated` is the only stat this route reads;\n * the rest ride through unread on the real SDK type. */\nexport interface SandboxTreeResult {\n root: string\n files: SandboxTreeFile[]\n stats: { truncated: boolean }\n}\n\n/** Structural match of the sandbox SDK's `box.fs` tree surface. */\nexport interface SandboxFileTreeSource {\n tree(path: string, options?: { maxDepth?: number }): Promise<SandboxTreeResult>\n}\n\nexport interface FileIndexReadyResponse {\n status: 'ready'\n /** Workspace-relative entries. Same shape as `FileMention` (`./wire`) so a\n * client can hand a response entry straight to `fileMentionsToParts` /\n * `buildMentionPromptBlock` without remapping. */\n files: FileMention[]\n /** True when either the underlying scan truncated (SDK-side cap) or this\n * route's own `maxEntries` cap trimmed the filtered list. The client\n * should show \"showing first N files\" rather than imply completeness. */\n truncated: boolean\n generatedAt: string\n}\n\n/** Cold-box answer: no provisioning happened, no files were scanned. The\n * client shows a warming state and retries — this route never blocks on a\n * box coming up. Two situations produce it: `authorize` reporting a box that\n * is not running, and a running box whose workspace root does not exist yet\n * (see `isMissingRootError`). */\nexport interface FileIndexWarmingResponse {\n status: 'warming'\n}\n\nexport type FileIndexResponse = FileIndexReadyResponse | FileIndexWarmingResponse\n\n/** Short-TTL cache seam so repeat popover opens in the same session don't\n * re-scan the workspace. Host-provided (e.g. a KV binding); `key` is\n * whatever `authorize` returns as `cacheKey` — this route treats it opaquely. */\nexport interface FileIndexCache {\n get(key: string): Promise<FileIndexReadyResponse | null> | FileIndexReadyResponse | null\n put(key: string, value: FileIndexReadyResponse, options?: { ttlSeconds?: number }): Promise<void> | void\n}\n\nexport type FileIndexAuthorization =\n | {\n status: 'ready'\n /** Structural sandbox `fs` handle, usually `ensureWorkspaceSandbox(...)` → `box.fs`. */\n fs: SandboxFileTreeSource\n /** Workspace root to index (e.g. `/home/agent`). */\n root: string\n /** Extra ignore segments for this request, merged with the route's\n * defaults + `CreateSandboxFileIndexRouteOptions.ignore`. */\n ignore?: string[]\n /** Opaque cache key for the optional cache seam. Omit to skip caching\n * for this request (e.g. a workspace the host chooses not to cache). */\n cacheKey?: string\n }\n | { status: 'warming' }\n | { status: 'denied'; response: Response }\n\nexport interface CreateSandboxFileIndexRouteOptions {\n /** Authenticate the caller, resolve the sandbox `fs` handle, and signal a\n * cold box — never provisions or waits. */\n authorize(args: { request: Request }): Promise<FileIndexAuthorization>\n /** Extra ignore segments beyond the route's defaults (node_modules, .git,\n * dotfiles/dot-dirs, common build dirs). Matched as exact path-segment\n * names, same rule as the defaults. */\n ignore?: string[]\n /** Passed to `fs.tree` as `options.maxDepth`. Default 12. */\n maxDepth?: number\n /** Hard cap on entries returned after filtering. Default 5000. */\n maxEntries?: number\n /** Optional host-provided cache seam. */\n cache?: FileIndexCache\n /** Cache TTL in seconds when `cache` is set. Default 20. */\n cacheTtlSeconds?: number\n}\n\n/** Segment names ignored anywhere in a path, beyond the generic dotfile rule\n * below. Intentionally small and language/framework-agnostic — callers\n * extend it via `ignore` for anything domain-specific (e.g. a vault's\n * `uploads` dir). */\nconst DEFAULT_IGNORE_SEGMENTS = [\n 'node_modules',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n 'target',\n '__pycache__',\n 'venv',\n]\n\n/** A path segment starting with `.` (`.git`, `.env`, `.next`, `.cache`, …) is\n * always ignored — this single rule covers most dot-prefixed VCS/tooling\n * dirs and dotfiles without enumerating them. */\nfunction isIgnored(relPath: string, ignoreSegments: ReadonlySet<string>): boolean {\n for (const segment of relPath.split('/')) {\n if (!segment) continue\n if (segment.startsWith('.')) return true\n if (ignoreSegments.has(segment)) return true\n }\n return false\n}\n\n/** Strips the tree result's echoed `root` prefix so entries are always\n * workspace-relative, whichever convention the structural `fs.tree` uses\n * (root-relative already, or root-prefixed). */\nfunction relativeTo(root: string, path: string): string {\n const prefix = root.endsWith('/') ? root : `${root}/`\n if (path.startsWith(prefix)) return path.slice(prefix.length)\n if (path === root) return ''\n return path\n}\n\nfunction basename(path: string): string {\n const segments = path.split('/').filter(Boolean)\n return segments[segments.length - 1] ?? path\n}\n\n/**\n * A box can answer `running` before it has materialised the workspace root —\n * `authorize` has already committed to `ready` by then, so `fs.tree` is the\n * first thing to notice, and it rejects with the sandbox SDK's\n * `ValidationError` wrapping a box-side `ENOENT … lstat` on the root. That is\n * the SAME \"not usable yet\" state `authorize` collapses onto `warming` for an\n * absent or stopped box, just discovered one step later, so it gets the same\n * answer instead of escaping as a 500.\n *\n * Matched STRUCTURALLY, not with `instanceof`: importing the SDK's error class\n * would make `@tangle-network/sandbox` a hard dependency of a route factory\n * whose entire `fs` seam is structural (`SandboxFileTreeSource`), and would\n * break any host feeding it a non-SDK handle.\n *\n * Deliberately narrow — the error code, `ENOENT`, the ENOENT message text, AND\n * the failing syscall's own operand all have to line up. A permission error, a\n * timeout, an auth failure, or an ENOENT on some other path inside the tree is\n * a real failure and still surfaces.\n *\n * The operand is matched as the quoted `lstat '<root>'` clause rather than by\n * substring, because the root is a PREFIX of everything under it: a plain\n * `includes(root)` would also swallow an ENOENT on `<root>/gone/x.md`, and on\n * a prefix sibling like `/home/agent-old/...`.\n */\nfunction isMissingRootError(err: unknown, root: string): boolean {\n if (!(err instanceof Error)) return false\n if ((err as { code?: unknown }).code !== 'VALIDATION_ERROR') return false\n return (\n /ENOENT/.test(err.message) &&\n /no such file or directory/.test(err.message) &&\n new RegExp(`\\\\blstat '${escapeRegExp(root)}'`).test(err.message)\n )\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nexport function createSandboxFileIndexRoute(\n options: CreateSandboxFileIndexRouteOptions,\n): (request: Request) => Promise<Response> {\n const maxDepth = options.maxDepth ?? 12\n const maxEntries = options.maxEntries ?? 5000\n const cacheTtlSeconds = options.cacheTtlSeconds ?? 20\n const staticIgnore = new Set([...DEFAULT_IGNORE_SEGMENTS, ...(options.ignore ?? [])])\n\n return async function fileIndex(request: Request): Promise<Response> {\n const auth = await options.authorize({ request })\n if (auth.status === 'denied') return auth.response\n if (auth.status === 'warming') {\n return Response.json({ status: 'warming' } satisfies FileIndexWarmingResponse)\n }\n\n const cache = options.cache\n if (cache && auth.cacheKey) {\n const cached = await cache.get(auth.cacheKey)\n if (cached) return Response.json(cached)\n }\n\n const ignoreSegments = auth.ignore?.length\n ? new Set([...staticIgnore, ...auth.ignore])\n : staticIgnore\n\n let scan: SandboxTreeResult\n try {\n scan = await auth.fs.tree(auth.root, { maxDepth })\n } catch (err) {\n if (!isMissingRootError(err, auth.root)) throw err\n return Response.json({ status: 'warming' } satisfies FileIndexWarmingResponse)\n }\n const filtered = scan.files.filter((f) => !isIgnored(relativeTo(scan.root, f.path), ignoreSegments))\n const truncated = scan.stats.truncated || filtered.length > maxEntries\n const files: FileMention[] = filtered.slice(0, maxEntries).map((f) => {\n const path = relativeTo(scan.root, f.path)\n const entry: FileMention = { path, name: basename(path) }\n if (typeof f.size === 'number') entry.size = f.size\n return entry\n })\n\n const body: FileIndexReadyResponse = {\n status: 'ready',\n files,\n truncated,\n generatedAt: new Date().toISOString(),\n }\n\n if (cache && auth.cacheKey) await cache.put(auth.cacheKey, body, { ttlSeconds: cacheTtlSeconds })\n\n return Response.json(body)\n }\n}\n"],"mappings":"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","names":[]}