@tangle-network/agent-app 0.44.28 → 0.44.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/assistant/index.d.ts +8 -6
  2. package/dist/assistant/index.js +6 -3
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/{attachment-validation-Dvc_Livy.d.ts → attachment-validation-CNkH91Gs.d.ts} +1 -1
  5. package/dist/chat-react/index.d.ts +192 -0
  6. package/dist/chat-react/index.js +179 -0
  7. package/dist/chat-react/index.js.map +1 -0
  8. package/dist/chat-routes/index.d.ts +7 -6
  9. package/dist/chat-routes/index.js +13 -9
  10. package/dist/chat-routes/index.js.map +1 -1
  11. package/dist/chat-store/index.d.ts +6 -5
  12. package/dist/chat-store/index.js +2 -1
  13. package/dist/chat-store/index.js.map +1 -1
  14. package/dist/chunk-4OO7P3ZF.js +292 -0
  15. package/dist/chunk-4OO7P3ZF.js.map +1 -0
  16. package/dist/chunk-4PUMUTLU.js +158 -0
  17. package/dist/chunk-4PUMUTLU.js.map +1 -0
  18. package/dist/{chunk-7775L5NN.js → chunk-BAC2B2KI.js} +170 -7
  19. package/dist/chunk-BAC2B2KI.js.map +1 -0
  20. package/dist/{chunk-PRKSYTMQ.js → chunk-GCH3BUAZ.js} +31 -317
  21. package/dist/chunk-GCH3BUAZ.js.map +1 -0
  22. package/dist/{chunk-BI6NKSO4.js → chunk-IYLJS6VW.js} +3 -84
  23. package/dist/chunk-IYLJS6VW.js.map +1 -0
  24. package/dist/chunk-QY4BRKRJ.js +84 -0
  25. package/dist/chunk-QY4BRKRJ.js.map +1 -0
  26. package/dist/{chunk-C3SRFZGL.js → chunk-QYAQGCHF.js} +2 -154
  27. package/dist/chunk-QYAQGCHF.js.map +1 -0
  28. package/dist/{contract-CEewO6DI.d.ts → contract-CQNvv5th.d.ts} +1 -1
  29. package/dist/interactions/index.d.ts +2 -2
  30. package/dist/{parts-fyPPdDdK.d.ts → parts-7fbe2rj8.d.ts} +4 -274
  31. package/dist/{queue-vRI0Qx3X.d.ts → queue-VTBA5ONX.d.ts} +1 -1
  32. package/dist/sandbox/index.d.ts +253 -1
  33. package/dist/sandbox/index.js +9 -1
  34. package/dist/stream/index.d.ts +3 -3
  35. package/dist/{stream-normalizer-DnuqkZvw.d.ts → stream-normalizer-CnPnMaTp.d.ts} +1 -1
  36. package/dist/teams/index.js +5 -5
  37. package/dist/teams/invitations-api.js +4 -4
  38. package/dist/teams-react/index.js +3 -3
  39. package/dist/{types-DB82fktc.d.ts → types-CCeYywdS.d.ts} +1 -1
  40. package/dist/use-file-mentions-E6a7_cbH.d.ts +98 -0
  41. package/dist/web-react/index.d.ts +13 -104
  42. package/dist/web-react/index.js +17 -12
  43. package/dist/wire-DSp4LzEE.d.ts +272 -0
  44. package/dist/work-product/index.d.ts +3 -3
  45. package/dist/work-product-react/index.d.ts +1 -1
  46. package/package.json +6 -1
  47. package/dist/chunk-7775L5NN.js.map +0 -1
  48. package/dist/chunk-BI6NKSO4.js.map +0 -1
  49. package/dist/chunk-C3SRFZGL.js.map +0 -1
  50. package/dist/chunk-PRKSYTMQ.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  formatBytes
3
- } from "./chunk-C3SRFZGL.js";
3
+ } from "./chunk-QYAQGCHF.js";
4
4
 
5
5
  // src/chat-routes/binary-sniff.ts
6
6
  function bytesStartWith(bytes, offset, signature) {
@@ -142,86 +142,6 @@ function attachmentTotalSizeErrorMessage(totalBytes, limitBytes) {
142
142
  return `Attachments total ${formatBytes(totalBytes)}; each message is limited to ${formatBytes(limitBytes)}`;
143
143
  }
144
144
 
145
- // src/chat-routes/file-index.ts
146
- var DEFAULT_IGNORE_SEGMENTS = [
147
- "node_modules",
148
- "dist",
149
- "build",
150
- "out",
151
- "coverage",
152
- "target",
153
- "__pycache__",
154
- "venv"
155
- ];
156
- function isIgnored(relPath, ignoreSegments) {
157
- for (const segment of relPath.split("/")) {
158
- if (!segment) continue;
159
- if (segment.startsWith(".")) return true;
160
- if (ignoreSegments.has(segment)) return true;
161
- }
162
- return false;
163
- }
164
- function relativeTo(root, path) {
165
- const prefix = root.endsWith("/") ? root : `${root}/`;
166
- if (path.startsWith(prefix)) return path.slice(prefix.length);
167
- if (path === root) return "";
168
- return path;
169
- }
170
- function basename(path) {
171
- const segments = path.split("/").filter(Boolean);
172
- return segments[segments.length - 1] ?? path;
173
- }
174
- function isMissingRootError(err, root) {
175
- if (!(err instanceof Error)) return false;
176
- if (err.code !== "VALIDATION_ERROR") return false;
177
- return /ENOENT/.test(err.message) && /no such file or directory/.test(err.message) && new RegExp(`\\blstat '${escapeRegExp(root)}'`).test(err.message);
178
- }
179
- function escapeRegExp(value) {
180
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
181
- }
182
- function createSandboxFileIndexRoute(options) {
183
- const maxDepth = options.maxDepth ?? 12;
184
- const maxEntries = options.maxEntries ?? 5e3;
185
- const cacheTtlSeconds = options.cacheTtlSeconds ?? 20;
186
- const staticIgnore = /* @__PURE__ */ new Set([...DEFAULT_IGNORE_SEGMENTS, ...options.ignore ?? []]);
187
- return async function fileIndex(request) {
188
- const auth = await options.authorize({ request });
189
- if (auth.status === "denied") return auth.response;
190
- if (auth.status === "warming") {
191
- return Response.json({ status: "warming" });
192
- }
193
- const cache = options.cache;
194
- if (cache && auth.cacheKey) {
195
- const cached = await cache.get(auth.cacheKey);
196
- if (cached) return Response.json(cached);
197
- }
198
- const ignoreSegments = auth.ignore?.length ? /* @__PURE__ */ new Set([...staticIgnore, ...auth.ignore]) : staticIgnore;
199
- let scan;
200
- try {
201
- scan = await auth.fs.tree(auth.root, { maxDepth });
202
- } catch (err) {
203
- if (!isMissingRootError(err, auth.root)) throw err;
204
- return Response.json({ status: "warming" });
205
- }
206
- const filtered = scan.files.filter((f) => !isIgnored(relativeTo(scan.root, f.path), ignoreSegments));
207
- const truncated = scan.stats.truncated || filtered.length > maxEntries;
208
- const files = filtered.slice(0, maxEntries).map((f) => {
209
- const path = relativeTo(scan.root, f.path);
210
- const entry = { path, name: basename(path) };
211
- if (typeof f.size === "number") entry.size = f.size;
212
- return entry;
213
- });
214
- const body = {
215
- status: "ready",
216
- files,
217
- truncated,
218
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
219
- };
220
- if (cache && auth.cacheKey) await cache.put(auth.cacheKey, body, { ttlSeconds: cacheTtlSeconds });
221
- return Response.json(body);
222
- };
223
- }
224
-
225
145
  export {
226
146
  sniffBinary,
227
147
  MAX_BINARY_ATTACHMENT_BYTES,
@@ -233,7 +153,6 @@ export {
233
153
  checkAttachmentType,
234
154
  sanitizeAttachmentFileName,
235
155
  attachmentSizeErrorMessage,
236
- attachmentTotalSizeErrorMessage,
237
- createSandboxFileIndexRoute
156
+ attachmentTotalSizeErrorMessage
238
157
  };
239
- //# sourceMappingURL=chunk-BI6NKSO4.js.map
158
+ //# sourceMappingURL=chunk-IYLJS6VW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chat-routes/binary-sniff.ts","../src/chat-routes/attachment-validation.ts"],"sourcesContent":["/**\n * Content-based binary/text classification, shared by the attachment upload\n * route (server) and the composer's client-side pre-validation (browser) —\n * both sides must agree on what counts as binary before a byte ever leaves\n * the client. Extension-based allowlists lie (a renamed `.docx`, a PNG saved\n * as `.txt`), so classification reads the actual bytes: a magic-byte table\n * for common binary formats first, then a UTF-8 decode attempt for\n * everything else.\n *\n * Lifted near-verbatim from gtm-agent's `src/lib/binary-sniff.ts` (the\n * source PRs hardened this against real corruption/gate bugs: gtm#584,\n * gtm#592). Import-free by design — `/web-react` re-exports `/chat-routes`\n * modules into browser bundles (`tests/browser-safe-subpaths.test.ts` walks\n * the graph), so nothing here may reach a Node builtin or an engine package.\n */\n\nexport interface SniffResult {\n binary: boolean\n mime: string | null\n}\n\nfunction bytesStartWith(bytes: Uint8Array, offset: number, signature: number[]): boolean {\n if (bytes.length < offset + signature.length) return false\n for (let i = 0; i < signature.length; i++) {\n if (bytes[offset + i] !== signature[i]) return false\n }\n return true\n}\n\nfunction asciiAt(bytes: Uint8Array, offset: number, text: string): boolean {\n if (bytes.length < offset + text.length) return false\n for (let i = 0; i < text.length; i++) {\n if (bytes[offset + i] !== text.charCodeAt(i)) return false\n }\n return true\n}\n\n/** RIFF containers (WebP, WAV) share the `RIFF....<TYPE>` header; the type\n * tag at byte offset 8 distinguishes them. */\nfunction sniffRiff(bytes: Uint8Array): string | null {\n if (!asciiAt(bytes, 0, 'RIFF')) return null\n if (asciiAt(bytes, 8, 'WEBP')) return 'image/webp'\n if (asciiAt(bytes, 8, 'WAVE')) return 'audio/wav'\n return null\n}\n\n/** MP4/MOV/AVIF/HEIC containers share an `ftyp` box at byte offset 4; the\n * major brand at offset 8 tells them apart within the shared ISO-BMFF\n * family. Brands outside this table (mp4, m4a, m4v, etc) fall back to\n * `video/mp4`, the family's most common member. */\nfunction sniffFtyp(bytes: Uint8Array): string | null {\n if (!asciiAt(bytes, 4, 'ftyp')) return null\n if (asciiAt(bytes, 8, 'qt ')) return 'video/quicktime'\n if (asciiAt(bytes, 8, 'avif') || asciiAt(bytes, 8, 'avis')) return 'image/avif'\n if (asciiAt(bytes, 8, 'heic') || asciiAt(bytes, 8, 'heix') || asciiAt(bytes, 8, 'hevc') || asciiAt(bytes, 8, 'hevx')) return 'image/heic'\n if (asciiAt(bytes, 8, 'mif1') || asciiAt(bytes, 8, 'msf1')) return 'image/heif'\n return 'video/mp4'\n}\n\n/** `BM` alone matches ordinary prose (\"BMW…\"), so require the BMP header's\n * reserved bytes (offsets 6-9), which the format mandates to be zero. */\nfunction sniffBmp(bytes: Uint8Array): boolean {\n return asciiAt(bytes, 0, 'BM')\n && bytes.length >= 10\n && bytes[6] === 0 && bytes[7] === 0 && bytes[8] === 0 && bytes[9] === 0\n}\n\n/** `ID3` alone matches ordinary prose (\"ID3 tags…\"), so require the ID3v2\n * header shape: a plausible version byte and sync-safe size bytes. */\nfunction sniffId3(bytes: Uint8Array): boolean {\n return asciiAt(bytes, 0, 'ID3')\n && bytes.length >= 10\n && bytes[3]! < 0x10\n && bytes[6]! < 0x80 && bytes[7]! < 0x80 && bytes[8]! < 0x80 && bytes[9]! < 0x80\n}\n\nfunction sniffMagicBytes(bytes: Uint8Array): string | null {\n if (bytesStartWith(bytes, 0, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return 'image/png'\n if (bytesStartWith(bytes, 0, [0xff, 0xd8, 0xff])) return 'image/jpeg'\n if (asciiAt(bytes, 0, 'GIF87a') || asciiAt(bytes, 0, 'GIF89a')) return 'image/gif'\n if (sniffBmp(bytes)) return 'image/bmp'\n if (bytesStartWith(bytes, 0, [0x49, 0x49, 0x2a, 0x00])) return 'image/tiff' // little-endian\n if (bytesStartWith(bytes, 0, [0x4d, 0x4d, 0x00, 0x2a])) return 'image/tiff' // big-endian\n if (bytesStartWith(bytes, 0, [0x00, 0x00, 0x01, 0x00])) return 'image/x-icon'\n if (asciiAt(bytes, 0, '%PDF-')) return 'application/pdf'\n // OOXML (.docx/.xlsx/.pptx) is a zip archive; the container format is all\n // that matters here, so no attempt is made to distinguish the payload.\n if (bytesStartWith(bytes, 0, [0x50, 0x4b, 0x03, 0x04])) return 'application/zip'\n if (bytesStartWith(bytes, 0, [0x1f, 0x8b])) return 'application/gzip'\n if (sniffId3(bytes) || bytesStartWith(bytes, 0, [0xff, 0xfb])) return 'audio/mpeg'\n if (asciiAt(bytes, 0, 'OggS')) return 'audio/ogg'\n\n const riff = sniffRiff(bytes)\n if (riff) return riff\n\n const ftyp = sniffFtyp(bytes)\n if (ftyp) return ftyp\n\n return null\n}\n\n/** SVG is valid UTF-8 but must round-trip byte-identical (image tools read\n * it from the box as a file), so it is classified binary. Conservative\n * match: the document's first element is `<svg`, or an `<?xml` prolog is\n * followed by an `<svg` element within the first ~1KB (comments/doctype may\n * sit between). Plain XML without an svg root, or prose that merely\n * mentions \"<svg\", stays text. */\nfunction sniffSvgText(decoded: string): boolean {\n let text = decoded\n if (text.charCodeAt(0) === 0xfeff) text = text.slice(1)\n text = text.trimStart()\n if (/^<svg[\\s>/]/.test(text)) return true\n if (!text.startsWith('<?xml')) return false\n return /<svg[\\s>/]/.test(text.slice(0, 1024))\n}\n\n/** Decide whether uploaded bytes are binary or text, and identify the mime\n * type when it can be determined from content. Magic bytes are checked\n * first; anything unmatched falls back to a fatal UTF-8 decode. A NUL byte\n * or a decode failure means binary. Valid UTF-8 that is an SVG document is\n * binary (byte-identity matters for image tooling). Content that matches\n * nothing and does not decode as text is binary with an unknown mime —\n * extension-based guessing happens at the call site, not here. */\nexport function sniffBinary(bytes: Uint8Array): SniffResult {\n const mime = sniffMagicBytes(bytes)\n if (mime) return { binary: true, mime }\n\n if (bytes.includes(0x00)) return { binary: true, mime: null }\n\n let decoded: string\n try {\n decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n } catch {\n return { binary: true, mime: null }\n }\n if (sniffSvgText(decoded)) return { binary: true, mime: 'image/svg+xml' }\n return { binary: false, mime: null }\n}\n","/**\n * Shared attachment validation core — constants, type-gate, and filename\n * sanitization used by BOTH the (server) attachment upload route and the\n * (browser) composer's client-side pre-validation, so a rejection never\n * differs depending on which side classified the bytes first.\n *\n * ≈ gtm-agent's `src/lib/attachment-limits.ts`, minus what agent-app already\n * has (`ATTACHMENT_MAX_COUNT`/`MAX_ATTACHMENT_TOTAL_BYTES`/\n * `attachmentTotalSizeErrorMessage` lived in `./resolve-attachments` and are\n * re-homed here so the whole validation vocabulary — count cap, size caps,\n * and type gate — has one address). Import-free besides `./wire`\n * (`formatBytes`) and `./binary-sniff` (`SniffResult`): `/web-react`\n * re-exports `/chat-routes` modules into browser bundles\n * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here\n * may reach a Node builtin or an engine package.\n */\n\nimport { formatBytes } from './wire'\nimport type { SniffResult } from './binary-sniff'\n\n/** Ceiling on a binary attachment's raw (pre-encoding) byte size. */\nexport const MAX_BINARY_ATTACHMENT_BYTES = 10 * 1024 * 1024\n\n/** Ceiling on a text attachment's raw byte size. Text hydrates through\n * inline prompt parts, a separate path that remains proxy-capped (see\n * `INLINE_PARTS_MAX_BYTES` in `./wire`). */\nexport const MAX_TEXT_ATTACHMENT_BYTES = 950 * 1024\n\n/** Most files a single request may carry: the composer staging cap, the\n * upload route's per-request cap, and the chat body's `attachments` cap. */\nexport const ATTACHMENT_MAX_COUNT = 10\n\n/** Aggregate raw-byte ceiling across one message's attachments. */\nexport const MAX_ATTACHMENT_TOTAL_BYTES = 25 * 1024 * 1024\n\n/**\n * Accept list for the composer file picker + type validation, same grammar as\n * the native `<input accept>` attribute. Images plus the text/doc types a\n * product's store actually reads.\n */\nexport const ATTACHMENT_ACCEPT =\n 'image/*,.pdf,.txt,.md,.csv,.json,.yaml,.yml,.html'\n\n/** Sniffed-mime counterpart of `ATTACHMENT_ACCEPT`: the binary formats\n * `sniffBinary` can identify from magic bytes among the accepted types.\n * Values must match `sniffBinary`'s output strings verbatim, or every\n * upload of that format fails the type gate. */\nexport const ALLOWED_ATTACHMENT_SNIFFED_MIMES: ReadonlySet<string> = new Set([\n 'image/png',\n 'image/jpeg',\n 'image/gif',\n 'image/bmp',\n 'image/tiff',\n 'image/x-icon',\n 'image/webp',\n 'image/svg+xml',\n 'image/avif',\n 'image/heic',\n 'image/heif',\n 'application/pdf',\n])\n\n/** Extensions whose magic-byte family is unambiguous, mapped to the mime\n * `sniffBinary` emits for genuine content of that format. Keep in sync with\n * `ATTACHMENT_ACCEPT`: an extension only belongs here if its format has a\n * detectable magic-byte signature. Text extensions (.txt/.md/.csv/.json/\n * .yaml/.yml/.html) are deliberately absent — they have no magic bytes to\n * compare against, so they ride the plain UTF-8 gate instead. AVIF/HEIC/HEIF\n * extensions are also deliberately absent: the ISO-BMFF brand-to-extension\n * mapping in that family isn't one-to-one (a legitimate `.heic` can carry a\n * `mif1` brand), so an extension-implies-mime entry would reject genuine\n * files. They still ride the allowlist gate below, which is what catches an\n * mp4 renamed `.avif` (its content sniffs `video/mp4`, not an allowed mime). */\nconst EXTENSION_IMPLIES_SNIFFED_MIME: Readonly<Record<string, string>> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n bmp: 'image/bmp',\n tif: 'image/tiff',\n tiff: 'image/tiff',\n ico: 'image/x-icon',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n pdf: 'application/pdf',\n}\n\n/** Represent the result of checking an attachment's type with success or specific failure details */\nexport type AttachmentTypeCheckResult =\n | { succeeded: true }\n | { succeeded: false; code: 'attachment_type_mismatch' | 'attachment_type_not_allowed'; message: string }\n\n/**\n * Cross-check a filename's extension against its sniffed content.\n *\n * Text content (`sniff.binary === false`) always passes here — it has no\n * magic bytes to compare, so it rides the existing UTF-8 gate instead. For\n * binary content: an extension with an unambiguous magic-byte family (e.g.\n * `.pdf`) must match the sniffed mime, or the upload is a mismatch (a\n * renamed file). Otherwise the sniffed mime must be one of `allowed`\n * (default {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}), or the upload is\n * rejected outright. The `allowed` param feeds a route's override seam (a\n * product accepting a narrower or wider set than the default).\n */\nexport function checkAttachmentType(\n fileName: string,\n sniff: SniffResult,\n allowed: ReadonlySet<string> = ALLOWED_ATTACHMENT_SNIFFED_MIMES,\n): AttachmentTypeCheckResult {\n if (sniff.binary === false) return { succeeded: true }\n\n const extension = fileName.split('.').pop()?.toLowerCase() ?? ''\n const impliedMime = EXTENSION_IMPLIES_SNIFFED_MIME[extension]\n if (impliedMime && sniff.mime && sniff.mime !== impliedMime) {\n return {\n succeeded: false,\n code: 'attachment_type_mismatch',\n message: `${fileName} has a .${extension} extension, but its content is ${sniff.mime}`,\n }\n }\n\n if (!sniff.mime || !allowed.has(sniff.mime)) {\n return {\n succeeded: false,\n code: 'attachment_type_not_allowed',\n message: sniff.mime\n ? `${fileName}'s content (${sniff.mime}) is not an allowed attachment type`\n : `${fileName}'s content is not a recognized attachment type`,\n }\n }\n\n return { succeeded: true }\n}\n\n/**\n * Rewrite a filename into the store-path charset (`A-Za-z0-9._-` per\n * segment) — attachment paths double as store keys, sandbox file paths, and\n * in-message path references, none of which tolerate spaces or punctuation.\n * Runs of unsupported characters collapse to one `-`; leading dots/dashes are\n * stripped so the name can't read as a hidden segment. The original name is\n * preserved separately (the returned `ChatAttachmentInput.name`), so\n * sanitization loses nothing.\n */\nexport function sanitizeAttachmentFileName(name: string): string {\n const sanitized = name\n .trim()\n .replace(/[^A-Za-z0-9._-]+/g, '-')\n .replace(/^[.-]+/, '')\n return sanitized || 'file'\n}\n\n/** Human-readable error naming both the actual size and the limit that was\n * exceeded. Shared so the server route and the composer pre-check report\n * the same message shape. */\nexport function attachmentSizeErrorMessage(name: string, actualBytes: number, limitBytes: number): string {\n return `${name} is ${formatBytes(actualBytes)}; attachments are limited to ${formatBytes(limitBytes)}`\n}\n\n/** Human-readable error for a chat message whose combined attachments exceed\n * the aggregate raw-byte ceiling. */\nexport function attachmentTotalSizeErrorMessage(totalBytes: number, limitBytes: number): string {\n return `Attachments total ${formatBytes(totalBytes)}; each message is limited to ${formatBytes(limitBytes)}`\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,84 @@
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-QY4BRKRJ.js.map
@@ -0,0 +1 @@
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\n/** Describe a ready file index response with workspace-relative entries and truncation status */\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\n/** Resolve a response indicating the file index is either ready or warming up */\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\n/** Define authorization details and parameters for indexing a file workspace with optional caching and ignore rules */\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\n/** Define options to authorize and configure sandbox file index route behavior */\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\n/** Resolve a sandbox file index route with authorization, caching, and configurable depth and entries limits */\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":[]}
@@ -1,14 +1,3 @@
1
- import {
2
- persistedPartToWorkProduct
3
- } from "./chunk-ZVEEWGDK.js";
4
- import {
5
- persistedPartToInteraction
6
- } from "./chunk-3ZK5IJSW.js";
7
- import {
8
- persistedPartToPlan,
9
- planToPersistedPart
10
- } from "./chunk-YJMCRXQQ.js";
11
-
12
1
  // src/chat-routes/wire.ts
13
2
  function chatTurnRequestInit(payload) {
14
3
  return {
@@ -214,130 +203,6 @@ function parseChatTurnParts(raw) {
214
203
  });
215
204
  }
216
205
 
217
- // src/chat-store/parts.ts
218
- function toChatMessageParts(parts) {
219
- const out = [];
220
- for (const part of parts) {
221
- const typed = toChatMessagePart(part);
222
- if (typed) out.push(typed);
223
- }
224
- return out;
225
- }
226
- var str = (value) => typeof value === "string";
227
- function toChatMessagePart(part) {
228
- if (!part || typeof part !== "object") return null;
229
- const type = part.type;
230
- switch (type) {
231
- case "text":
232
- case "reasoning":
233
- return str(part.text) ? part : null;
234
- case "tool":
235
- return str(part.id) && str(part.tool) && part.state && typeof part.state === "object" ? part : null;
236
- case "file":
237
- case "image":
238
- return part;
239
- case "subtask":
240
- return str(part.prompt) && str(part.description) && str(part.agent) ? part : null;
241
- case "step-start":
242
- return { type: "step-start" };
243
- case "step-finish":
244
- return part;
245
- case "interaction":
246
- return persistedPartToInteraction(part) ? part : null;
247
- case "notice":
248
- return str(part.id) && str(part.noticeKind) && str(part.text) ? part : null;
249
- case "plan": {
250
- const plan = persistedPartToPlan(part);
251
- return plan ? { ...part, ...planToPersistedPart(plan) } : null;
252
- }
253
- case "work_product":
254
- return persistedPartToWorkProduct(part) ? part : null;
255
- case "mention":
256
- return isChatMentionPart(part) ? part : null;
257
- case void 0:
258
- return null;
259
- default: {
260
- const _exhaustive = type;
261
- void _exhaustive;
262
- return null;
263
- }
264
- }
265
- }
266
- function isChatToolPart(part) {
267
- return part.type === "tool";
268
- }
269
- function isChatTextPart(part) {
270
- return part.type === "text";
271
- }
272
- function isChatInteractionPart(part) {
273
- return part.type === "interaction";
274
- }
275
- function isChatPlanPart(part) {
276
- return part.type === "plan";
277
- }
278
- function isChatWorkProductPart(part) {
279
- return part.type === "work_product";
280
- }
281
- function isChatStepFinishPart(part) {
282
- return part.type === "step-finish";
283
- }
284
- function isChatMentionPart(part) {
285
- if (!part || typeof part !== "object") return false;
286
- const record = part;
287
- if (record.size !== void 0 && (typeof record.size !== "number" || !Number.isFinite(record.size))) {
288
- return false;
289
- }
290
- return record.type === "mention" && typeof record.path === "string" && record.path.length > 0 && typeof record.name === "string" && record.name.trim().length > 0 && (record.mentionKind === "image" || record.mentionKind === "file");
291
- }
292
- function mentionPartsFromMessageParts(parts) {
293
- if (!parts) return [];
294
- return parts.filter(isChatMentionPart);
295
- }
296
- function mentionInputToPart(input) {
297
- const part = {
298
- type: "mention",
299
- mentionKind: mentionKindForPath(input.path),
300
- path: input.path,
301
- name: input.name
302
- };
303
- if (typeof input.size === "number" && Number.isFinite(input.size)) part.size = input.size;
304
- return part;
305
- }
306
- function attachmentKindForMime(mime) {
307
- return mime?.startsWith("image/") ? "image" : "file";
308
- }
309
- function isChatAttachmentPart(part) {
310
- if (!part || typeof part !== "object") return false;
311
- const record = part;
312
- return (record.type === "image" || record.type === "file") && typeof record.path === "string" && record.path.length > 0;
313
- }
314
- function attachmentInputToPart(input) {
315
- const part = { type: input.kind, path: input.path, name: input.name };
316
- if (typeof input.size === "number" && Number.isFinite(input.size)) part.size = input.size;
317
- if (input.mediaType) part.mediaType = input.mediaType;
318
- return part;
319
- }
320
- function attachmentPartsFromMessageParts(parts) {
321
- if (!parts) return [];
322
- return parts.filter(isChatAttachmentPart);
323
- }
324
- var DEFAULT_ATTACHMENT_PROMPT_HEADER = "Attached files (already saved to the workspace vault \u2014 read them from these paths):";
325
- var PROMPT_BLOCK_CONTROL_CHARS = /[\x00-\x1F\x7F]/g;
326
- function buildAttachmentPromptBlock(atts, header = DEFAULT_ATTACHMENT_PROMPT_HEADER) {
327
- if (atts.length === 0) return "";
328
- const clean = (value) => value.replace(PROMPT_BLOCK_CONTROL_CHARS, " ");
329
- const lines = atts.map((a) => `- ${clean(a.name)} (vault: ${clean(a.path)})`);
330
- return `
331
-
332
- ${header}
333
- ${lines.join("\n")}`;
334
- }
335
- function historyContentWithAttachments(message, header) {
336
- const attachmentParts = attachmentPartsFromMessageParts(message.parts);
337
- if (attachmentParts.length === 0) return message.content;
338
- return `${message.content}${buildAttachmentPromptBlock(attachmentParts, header)}`;
339
- }
340
-
341
206
  export {
342
207
  chatTurnRequestInit,
343
208
  INLINE_PARTS_MAX_BYTES,
@@ -357,23 +222,6 @@ export {
357
222
  MENTION_MAX_COUNT,
358
223
  validateSandboxMentionPath,
359
224
  parseFileMentions,
360
- parseChatTurnParts,
361
- toChatMessageParts,
362
- isChatToolPart,
363
- isChatTextPart,
364
- isChatInteractionPart,
365
- isChatPlanPart,
366
- isChatWorkProductPart,
367
- isChatStepFinishPart,
368
- isChatMentionPart,
369
- mentionPartsFromMessageParts,
370
- mentionInputToPart,
371
- attachmentKindForMime,
372
- isChatAttachmentPart,
373
- attachmentInputToPart,
374
- attachmentPartsFromMessageParts,
375
- DEFAULT_ATTACHMENT_PROMPT_HEADER,
376
- buildAttachmentPromptBlock,
377
- historyContentWithAttachments
225
+ parseChatTurnParts
378
226
  };
379
- //# sourceMappingURL=chunk-C3SRFZGL.js.map
227
+ //# sourceMappingURL=chunk-QYAQGCHF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chat-routes/wire.ts"],"sourcesContent":["/**\n * Wire contract between the chat client (composer + `streamChatTurn`) and the\n * assembled server vertical (`createChatTurnRoutes`). Import-free on purpose:\n * `/web-react` re-exports these types into browser bundles, so nothing here may\n * reach a Node builtin or an engine package.\n *\n * The client part shape permits an absolute file path until the server converts\n * it to the URL required by the sandbox SDK. It is derived here, not imported,\n * so the client bundle never touches the SDK.\n */\n\nexport interface ChatTurnTextPartInput {\n type: 'text'\n text: string\n}\n\n/** A non-text prompt part the upload route hands back and the client echoes\n * on send. `url` carries an inline `data:` URI for small files; `path` is a\n * sandbox workspace reference for large ones (the >1 MiB gateway body cap\n * makes the two-step upload mandatory). */\nexport interface ChatTurnFilePartInput {\n type: 'image' | 'file'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n}\n\n/** Resolve input as either a text part or a file part of a chat turn */\nexport type ChatTurnPartInput = ChatTurnTextPartInput | ChatTurnFilePartInput\n\n// ── producer stream vocabulary ───────────────────────────────────────────────\n\n/** Represent a text event produced by a source with a fixed type and associated text content */\nexport interface ProducerTextEvent {\n type: 'text'\n text: string\n}\n\n/** Define an event representing reasoning output with a fixed type and associated text */\nexport interface ProducerReasoningEvent {\n type: 'reasoning'\n text: string\n}\n\n/** Represent an event triggered by a producer tool call with its identifier, name, and arguments */\nexport interface ProducerToolCallEvent {\n type: 'tool_call'\n call: {\n toolCallId: string\n toolName: string\n args: Record<string, unknown>\n }\n}\n\n/** Describe the structure of an event representing the result of a producer tool call */\nexport interface ProducerToolResultEvent {\n type: 'tool_result'\n toolCallId: string\n toolName: string\n outcome: {\n ok: boolean\n result?: unknown\n message?: string\n }\n}\n\n/** Describe usage event with prompt and completion token counts for a producer */\nexport interface ProducerUsageEvent {\n type: 'usage'\n usage: {\n promptTokens: number\n completionTokens: number\n }\n}\n\n/** Define the structure for a producer notice event with type, id, kind, and text fields */\nexport interface ProducerNoticeEvent {\n type: 'notice'\n id: string\n /** Kept inline with `/interactions`' `NoticeKind` so this file stays import-free. */\n noticeKind: 'warning' | 'auto-declined'\n text: string\n}\n\n/** Represent an error event emitted by a producer containing message, code, and optional details */\nexport interface ProducerErrorEvent {\n type: 'error'\n data: {\n message: string\n code?: string\n details?: Record<string, unknown>\n }\n}\n\n/** Stable raw lifecycle/interaction/plan/route events forwarded unchanged. */\nexport type ProducerPassthroughEventType =\n | 'turn'\n | 'metadata'\n | 'interaction'\n | 'interaction.cancel'\n | 'plan.submitted'\n | 'done'\n | 'warning'\n | 'session.run.started'\n | 'session.run.completed'\n | 'session.run.failed'\n | 'turn_status'\n\n/** Define an event carrying passthrough data with flexible properties for producer communication */\nexport interface ProducerPassthroughEvent {\n type: ProducerPassthroughEventType\n data?: Record<string, unknown>\n /** Route markers and raw passthroughs may carry `turnId`, `status`, `seq`, etc. */\n [key: string]: unknown\n}\n\n/** Represent events emitted by a producer during its operation for processing and handling */\nexport type ProducerWireEvent =\n | ProducerTextEvent\n | ProducerReasoningEvent\n | ProducerToolCallEvent\n | ProducerToolResultEvent\n | ProducerUsageEvent\n | ProducerNoticeEvent\n | ProducerErrorEvent\n | ProducerPassthroughEvent\n\n/** The image/file split an attachment is rendered and persisted under — the\n * same discriminant as {@link ChatMentionKind}, but a distinct name because an\n * attachment carries content the product uploaded (`ChatAttachmentInput`)\n * while a mention points at a file the box already has. Defined HERE (the\n * import-free layer) so `ChatAttachmentInput` can reference it and the client\n * composer imports it without pulling the persisted-part vocabulary;\n * `/chat-store`'s parts module re-exports it alongside the attachment helpers. */\nexport type ChatAttachmentKind = 'image' | 'file'\n\n/** `POST` turn-body entry describing a file already uploaded to the product's\n * store (vault/object-store) — distinct from an inline {@link\n * ChatTurnFilePartInput} (which carries bytes) and from a {@link FileMention}\n * (a sandbox path the box already holds). The route resolves this field with\n * {@link resolveChatAttachments}: every path is re-validated and every size is\n * re-derived from the stored body, so nothing here is trusted as sent. */\nexport interface ChatAttachmentInput {\n path: string\n name: string\n size: number\n mediaType: string\n kind: ChatAttachmentKind\n}\n\n/** POST body for the turn route. `content` may be empty when `parts` carry the\n * message (an image-only send). Product routing fields (workspaceId etc.) ride\n * alongside and are read by the product's `authorize` seam. */\nexport interface ChatTurnRequestPayload {\n threadId: string\n content?: string\n /** Non-text parts from the upload route, echoed back verbatim. */\n parts?: ChatTurnFilePartInput[]\n /** `@`-picked file mentions for this turn — path references into the\n * workspace sandbox, NOT uploads, so they travel in their own field rather\n * than as `parts` entries. A product whose `parts` field is already spoken\n * for (an attachment sentinel) can still send mentions, and mentions\n * persist as their own `ChatMentionPart`s so a retry rebuilds them. The\n * route validates this field with {@link parseFileMentions} and replaces it\n * on the payload with the validated, deduped list. */\n mentions?: FileMention[]\n /** Files uploaded to the product's store ahead of the turn — path\n * references, NOT inline bytes (those ride `parts`). Validated and\n * size-re-derived by {@link resolveChatAttachments} into persistable\n * attachment parts; a product whose `parts` field is spoken for by inline\n * uploads still sends store-backed files here. */\n attachments?: ChatAttachmentInput[]\n model?: string\n effort?: 'auto' | 'low' | 'medium' | 'high'\n harness?: string\n /** Client-generated idempotency key for the logical turn (retry-safe). */\n turnId?: string\n [key: string]: unknown\n}\n\n/** `fetch` init for the turn route — the one place the client wire shape is\n * serialized, so composer glue and products never drift from the server's\n * parser. */\nexport function chatTurnRequestInit(payload: ChatTurnRequestPayload): RequestInit {\n return {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n}\n\n// ── inline-part byte budget ─────────────────────────────────────────────────\n//\n// The sandbox gateway caps request bodies at 1 MiB; a turn body whose inline\n// `data:` parts exceed it dies at the gateway with an opaque 413. Enforce the\n// budget at the route boundary instead, with headroom for the JSON envelope\n// (same fail-loud-at-the-choke-point style as /sandbox's provision-payload and\n// env-size gates).\n\n/** Define the maximum byte size allowed for inline parts in data processing */\nexport const INLINE_PARTS_MAX_BYTES = 950_000\n\n// ── dispatch (parts[]) budget vocabulary ────────────────────────────────────\n//\n// The default caps `buildDispatchParts` sizes an attachment/mention dispatch\n// against — the sidecar/proxy limits an assembled `parts` array crosses, one\n// step past `INLINE_PARTS_MAX_BYTES` (which gates the raw turn BODY). Grouped\n// here, in the import-free layer, so the numbers are one overridable\n// vocabulary the client can read and a product can tune per call rather than\n// constants buried in the server module. NOTE: these model sidecar/proxy caps,\n// not a product's MIME accept-list or vault bucketing — those are DOMAIN\n// values the product supplies, never defaulted here.\n\n/** Hard cap on the whole `/prompt` request body as it crosses the sandbox\n * proxy — smaller in practice than a raw-file write cap because a dispatch\n * carries several inline parts plus the flattened history in one request. */\nexport const DISPATCH_REQUEST_MAX_BYTES = 1024 * 1024\n\n/** Bytes reserved off the top of {@link DISPATCH_REQUEST_MAX_BYTES} for the\n * JSON structure around the parts array (keys, delimiters, per-part\n * `type`/`filename`/`mediaType` fields) that {@link base64WireLen} does not\n * account for — keeps the inline budget off the exact proxy cap where one\n * stray byte trips the 413. */\nexport const DISPATCH_STRUCTURAL_RESERVE_BYTES = 64 * 1024\n\n/** Sidecar's hard cap on the `parts` array of one prompt request — a dispatch\n * must never assemble more parts than this or the whole turn 400s. */\nexport const DISPATCH_MAX_PARTS = 64\n\n/** Product-side cap on media parts per dispatch (current turn + carried\n * history), well under {@link DISPATCH_MAX_PARTS}. History trimming that keeps\n * a transcript's native media under this is a PRODUCT concern (the pointer\n * block keeps trimmed media reachable); `buildDispatchParts` enforces only the\n * total {@link DISPATCH_MAX_PARTS} cap. */\nexport const DISPATCH_MAX_MEDIA_PARTS = 24\n\n/** Size a base64-encoded string occupies on the wire given the raw\n * (pre-encoding) byte length: base64 packs 3 raw bytes into 4 output\n * characters, rounded up to the next multiple of 4. */\nexport function base64WireLen(byteLen: number): number {\n return Math.ceil(byteLen / 3) * 4\n}\n\n/**\n * Render a raw byte count as a human-readable size (`512B`, `3KB`, `12MB\n * 500KB`). Ported EXACTLY from gtm-agent's `attachment-limits.ts` — byte-\n * identical implementation, not a reinterpretation — so `resolve-attachments`'s\n * and `promote-file-part`'s error strings match gtm's wording verbatim. Lives\n * in the import-free wire layer (not `resolve-attachments.ts` alone) because\n * BOTH the aggregate-cap message here and the per-file oversize message in\n * `promote-file-part.ts` need it; a browser composer wanting the same\n * formatting for a client-side pre-check can also import it with no engine\n * pulled in.\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`\n if (bytes >= 1024 * 1024) {\n const megabytes = Math.floor(bytes / (1024 * 1024))\n const remainder = bytes % (1024 * 1024)\n return remainder === 0 ? `${megabytes}MB` : `${megabytes}MB ${formatBytes(remainder)}`\n }\n return `${Math.round(bytes / 1024)}KB`\n}\n\n/** Represent errors for invalid chat turn inputs with status and code properties */\nexport class ChatTurnInputError extends Error {\n constructor(message: string, readonly status = 400, readonly code = 'INVALID_CHAT_TURN') {\n super(message)\n this.name = 'ChatTurnInputError'\n }\n}\n\nfunction partByteSize(part: ChatTurnPartInput): number {\n let bytes = 0\n if (part.type === 'text') return part.text.length\n if (part.url) bytes += part.url.length\n if (part.path) bytes += part.path.length\n return bytes\n}\n\n/** Calculate the total byte size of an array of chat turn parts */\nexport function promptPartsByteSize(parts: ChatTurnPartInput[]): number {\n return parts.reduce((total, part) => total + partByteSize(part), 0)\n}\n\n/** Throws `ChatTurnInputError` (413) when the parts' inline payload would blow\n * the gateway cap. Path-ref parts are tiny by construction and always pass. */\nexport function assertPromptPartsWithinCap(\n parts: ChatTurnPartInput[],\n maxBytes = INLINE_PARTS_MAX_BYTES,\n): void {\n const total = promptPartsByteSize(parts)\n if (total <= maxBytes) return\n const largest = [...parts].sort((a, b) => partByteSize(b) - partByteSize(a))[0]\n const largestName = largest && largest.type !== 'text' ? largest.filename ?? largest.path ?? largest.type : 'text'\n throw new ChatTurnInputError(\n `Inline prompt parts total ${total}B, over the ${maxBytes}B budget (largest: ${largestName}, ${largest ? partByteSize(largest) : 0}B). ` +\n 'Upload large files through the upload route so they travel as sandbox path references.',\n 413,\n 'PROMPT_PARTS_TOO_LARGE',\n )\n}\n\n// ── file mentions ────────────────────────────────────────────────────────\n//\n// A file mention (`@`-picked in the composer, sandbox-ui#184) is a path\n// reference into the workspace sandbox — no byte upload. These helpers turn\n// a resolved mention list into wire parts and the prompt pointer block that\n// tells the agent where to read them from.\n\n/** A file mention resolved from the composer's `@`-picker: the\n * workspace-relative path plus enough metadata to build a prompt part and\n * pointer text. `path` is the canonical identity — the mention pill's\n * `MentionItem.id` for the file kind (`/web-react`'s `useFileMentions`). */\nexport interface FileMention {\n path: string\n name: string\n size?: number\n}\n\nconst MENTION_IMAGE_MEDIA_TYPES: ReadonlyMap<string, string> = new Map([\n ['.png', 'image/png'],\n ['.jpg', 'image/jpeg'],\n ['.jpeg', 'image/jpeg'],\n ['.gif', 'image/gif'],\n ['.webp', 'image/webp'],\n ['.svg', 'image/svg+xml'],\n ['.bmp', 'image/bmp'],\n ['.heic', 'image/heic'],\n ['.heif', 'image/heif'],\n ['.avif', 'image/avif'],\n])\n\nfunction extensionOf(path: string): string {\n const base = path.split('/').filter(Boolean).pop() ?? path\n const dot = base.lastIndexOf('.')\n return dot > 0 ? base.slice(dot).toLowerCase() : ''\n}\n\n/** The `image/*` mime for a mention path by extension, or `undefined` for\n * anything not in the known image set (dispatched as `type: 'file'`). */\nexport function mediaTypeForMentionPath(path: string): string | undefined {\n return MENTION_IMAGE_MEDIA_TYPES.get(extensionOf(path))\n}\n\n/** The image/file split a mention is rendered and persisted under — the\n * composer pill's icon, the dispatched part's `type`, and\n * `ChatMentionPart.mentionKind` are all this one value. */\nexport type ChatMentionKind = 'image' | 'file'\n\n/** `image` when the path's extension is in the known image set (the same table\n * {@link mediaTypeForMentionPath} reads), `file` otherwise. Exported so a\n * client that needs only the discriminant — a pill icon, a persisted part's\n * `mentionKind` — never re-declares the extension table; two frozen copies of\n * one mime table is how one gains a format and the other doesn't. */\nexport function mentionKindForPath(path: string): ChatMentionKind {\n return mediaTypeForMentionPath(path) ? 'image' : 'file'\n}\n\n/** Define options to resolve mention paths when converting file mentions to parts */\nexport interface FileMentionsToPartsOptions {\n /** Resolve a mention's workspace-relative path to the absolute path the\n * dispatched part should carry (e.g. a host prefixing the in-box vault\n * root). Default: identity — the path travels unchanged. */\n resolvePath?: (path: string) => string\n}\n\n/** Maps resolved file mentions to path-only `ChatTurnFilePartInput`s —\n * `image` vs `file` by extension, and always a `path`, never a `url` (the\n * url/path XOR invariant: a mention is a sandbox path reference, never\n * inline bytes). */\nexport function fileMentionsToParts(\n mentions: readonly FileMention[],\n opts: FileMentionsToPartsOptions = {},\n): ChatTurnFilePartInput[] {\n const resolvePath = opts.resolvePath ?? ((path: string) => path)\n return mentions.map((mention) => {\n const mediaType = mediaTypeForMentionPath(mention.path)\n const part: ChatTurnFilePartInput = {\n type: mediaType ? 'image' : 'file',\n filename: mention.name,\n path: resolvePath(mention.path),\n }\n if (mediaType) part.mediaType = mediaType\n return part\n })\n}\n\n/** The agent-facing pointer block appended to the dispatched prompt — never\n * persisted in message `content`. Empty array → `''` so callers can append\n * unconditionally. This is the sole producer of that text: the current\n * turn's dispatch and any history projection built from the same mention\n * list both route through here, so the two can't drift apart. */\nexport function buildMentionPromptBlock(\n mentions: readonly Pick<FileMention, 'name' | 'path'>[],\n): string {\n if (mentions.length === 0) return ''\n const lines = mentions.map((m) => `- ${m.name} (${m.path})`)\n return `\\n\\nMentioned files — read them from these paths:\\n${lines.join('\\n')}`\n}\n\n// ── mention validation ───────────────────────────────────────────────────\n//\n// This package owns BOTH ends of the mention path contract — it emits paths\n// from `createSandboxFileIndexRoute` and consumes them in `fileMentionsToParts`\n// / `buildMentionPromptBlock` — so the validation belongs here rather than in\n// each app that wires the pair up. A mention names a file that already exists\n// in the sandbox, so validation is a pure path/charset/count check with no\n// I/O: existence is proven later, when the agent reads the path and the turn\n// fails loudly if it is gone.\n\n/** Hard cap on mentions per turn. Bounds the prompt pointer block, the\n * persisted parts, and whatever media budget a dispatch draws from them. */\nexport const MENTION_MAX_COUNT = 16\n\n/** Longest mention display name accepted — bounds the pointer-block text and\n * the transcript pill label. */\nconst MAX_MENTION_NAME_LENGTH = 256\n/** Longest mention path accepted. */\nconst MAX_MENTION_PATH_LENGTH = 1024\n\n/** Represent the result of a sandbox mention path check indicating success or failure with an error message */\nexport type SandboxMentionPathCheck =\n | { succeeded: true }\n | { succeeded: false; error: string }\n\n/**\n * Validate a workspace-relative sandbox mention path. Rejects traversal (a\n * `..` path segment), absolute paths (leading `/`), backslashes, and null\n * bytes — the four ways a path picked in a client can escape the root the\n * index route scanned.\n *\n * Spaces and unicode are deliberately ALLOWED: in-box filenames are arbitrary,\n * and an ASCII-only charset would silently drop real files from a feature\n * whose whole job is naming them.\n */\nexport function validateSandboxMentionPath(path: unknown): SandboxMentionPathCheck {\n if (typeof path !== 'string' || path.length === 0) {\n return { succeeded: false, error: 'mention path must be a non-empty string' }\n }\n if (path.length > MAX_MENTION_PATH_LENGTH) {\n return { succeeded: false, error: `mention path must not exceed ${MAX_MENTION_PATH_LENGTH} characters` }\n }\n if (path.includes('\\0')) {\n return { succeeded: false, error: 'mention path must not contain null bytes' }\n }\n if (path.includes('\\\\')) {\n return { succeeded: false, error: 'mention path must not contain backslashes' }\n }\n if (path.startsWith('/')) {\n return { succeeded: false, error: 'mention path must be workspace-relative, not absolute' }\n }\n if (path.split('/').some((segment) => segment === '..')) {\n return { succeeded: false, error: 'mention path must not contain \"..\" segments' }\n }\n return { succeeded: true }\n}\n\nfunction parseFileMention(value: unknown, index: number): FileMention {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new ChatTurnInputError(`mentions[${index}] must be an object`)\n }\n const record = value as Record<string, unknown>\n\n const pathCheck = validateSandboxMentionPath(record.path)\n if (!pathCheck.succeeded) throw new ChatTurnInputError(`mentions[${index}]: ${pathCheck.error}`)\n\n const name = record.name\n if (typeof name !== 'string' || !name.trim()) {\n throw new ChatTurnInputError(`mentions[${index}].name must be a non-empty string`)\n }\n if (name.length > MAX_MENTION_NAME_LENGTH) {\n throw new ChatTurnInputError(`mentions[${index}].name must not exceed ${MAX_MENTION_NAME_LENGTH} characters`)\n }\n\n const size = record.size\n if (size !== undefined) {\n if (typeof size !== 'number' || !Number.isFinite(size)) {\n throw new ChatTurnInputError(`mentions[${index}].size must be a finite number`)\n }\n if (size < 0) {\n throw new ChatTurnInputError(`mentions[${index}].size must not be negative`)\n }\n }\n\n return { path: record.path as string, name, ...(typeof size === 'number' ? { size } : {}) }\n}\n\n/**\n * Validates the untyped `mentions` array off the wire, mirroring\n * {@link parseChatTurnParts}: the typed list, or `ChatTurnInputError` (400)\n * naming the offending entry. Never sanitizes-and-continues — a traversal path\n * is a rejected request, not a trimmed one.\n *\n * A path repeated within one turn is deduped to its first occurrence rather\n * than rejected: mentioning the same file twice is plausible user input, not\n * an attack.\n */\nexport function parseFileMentions(raw: unknown): FileMention[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('mentions must be an array')\n if (raw.length > MENTION_MAX_COUNT) {\n throw new ChatTurnInputError(`mentions must not exceed ${MENTION_MAX_COUNT} entries`)\n }\n\n const mentions: FileMention[] = []\n const seenPaths = new Set<string>()\n for (let index = 0; index < raw.length; index += 1) {\n const mention = parseFileMention(raw[index], index)\n if (seenPaths.has(mention.path)) continue\n seenPaths.add(mention.path)\n mentions.push(mention)\n }\n return mentions\n}\n\n/** Validates the untyped `parts` array off the wire. Returns the typed parts\n * or throws `ChatTurnInputError` (400) naming the offending entry. */\nexport function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('parts must be an array')\n return raw.map((entry, index) => {\n const part = entry as Record<string, unknown> | null\n if (!part || typeof part !== 'object') {\n throw new ChatTurnInputError(`parts[${index}] must be an object`)\n }\n if (part.type !== 'image' && part.type !== 'file') {\n throw new ChatTurnInputError(`parts[${index}].type must be 'image' or 'file'`)\n }\n for (const key of ['filename', 'mediaType', 'url', 'path'] as const) {\n if (part[key] !== undefined && typeof part[key] !== 'string') {\n throw new ChatTurnInputError(`parts[${index}].${key} must be a string`)\n }\n }\n if (part.content !== undefined) {\n throw new ChatTurnInputError(`parts[${index}].content is not supported; provide a url or path`)\n }\n\n const url = typeof part.url === 'string' && part.url.length > 0 ? part.url : undefined\n const path = typeof part.path === 'string' && part.path.length > 0 ? part.path : undefined\n if (Boolean(url) === Boolean(path)) {\n throw new ChatTurnInputError(`parts[${index}] requires exactly one url or path`)\n }\n if (path && !path.startsWith('/')) {\n throw new ChatTurnInputError(`parts[${index}].path must be absolute`)\n }\n\n const filename = typeof part.filename === 'string' ? part.filename.trim() : undefined\n if (part.type === 'file' && !filename) {\n throw new ChatTurnInputError(`parts[${index}].filename is required for file parts`)\n }\n return {\n type: part.type,\n ...(filename ? { filename } : {}),\n ...(part.mediaType !== undefined ? { mediaType: part.mediaType as string } : {}),\n ...(url ? { url } : { path: path! }),\n }\n })\n}\n"],"mappings":"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","names":[]}
@@ -182,4 +182,4 @@ declare function stampInteractionAnswers(parts: Array<Record<string, unknown>>,
182
182
  * Returns null (caller logs) when the part is not one of ours. */
183
183
  declare function persistedPartToInteraction(part: Record<string, unknown>): ChatInteraction | null;
184
184
 
185
- export { noticePart as A, noticePartKey as B, type ChatInteractionStatus as C, parseInteractionAnswers as D, parseInteractionCancel as E, parseInteractionRequest as F, persistedPartToInteraction as G, questionInteractionContentSignature as H, type InteractionAnswers as I, stampInteractionAnswers as J, type NoticeKind as N, type ParseInteractionAnswersResult as P, type ChatInteractionField as a, type ChatInteraction as b, type InteractionCancelData as c, type ChatSelectField as d, type InteractionRequestWire as e, type ChatFreeTextField as f, type ComposerAnswerDelivery as g, INTERACTION_CANCEL_EVENT as h, INTERACTION_EVENT as i, INTERACTION_RESOLVED_EVENT as j, type InteractionAnswerValue as k, type InteractionPersistedPart as l, type NoticePersistedPart as m, type ParseInteractionResult as n, canTransitionInteractionStatus as o, cancelStatusFor as p, composerAnswerData as q, composerAnswerDeliveries as r, dedupeQuestionInteractionsByContent as s, fieldAcceptsFreeText as t, interactionFromWireRequest as u, interactionPartKey as v, interactionToPersistedPart as w, isRenderableInteractionKind as x, isSafeInteractionFieldKey as y, isTerminalInteractionStatus as z };
185
+ export { noticePart as A, noticePartKey as B, type ChatInteractionField as C, parseInteractionAnswers as D, parseInteractionCancel as E, parseInteractionRequest as F, persistedPartToInteraction as G, questionInteractionContentSignature as H, type InteractionAnswers as I, stampInteractionAnswers as J, type NoticeKind as N, type ParseInteractionAnswersResult as P, type ChatInteractionStatus as a, type ChatInteraction as b, type InteractionCancelData as c, type ChatSelectField as d, type InteractionRequestWire as e, type ChatFreeTextField as f, type ComposerAnswerDelivery as g, INTERACTION_CANCEL_EVENT as h, INTERACTION_EVENT as i, INTERACTION_RESOLVED_EVENT as j, type InteractionAnswerValue as k, type InteractionPersistedPart as l, type NoticePersistedPart as m, type ParseInteractionResult as n, canTransitionInteractionStatus as o, cancelStatusFor as p, composerAnswerData as q, composerAnswerDeliveries as r, dedupeQuestionInteractionsByContent as s, fieldAcceptsFreeText as t, interactionFromWireRequest as u, interactionPartKey as v, interactionToPersistedPart as w, isRenderableInteractionKind as x, isSafeInteractionFieldKey as y, isTerminalInteractionStatus as z };
@@ -1,5 +1,5 @@
1
- import { e as InteractionRequestWire } from '../contract-CEewO6DI.js';
2
- export { f as ChatFreeTextField, b as ChatInteraction, a as ChatInteractionField, C as ChatInteractionStatus, d as ChatSelectField, g as ComposerAnswerDelivery, h as INTERACTION_CANCEL_EVENT, i as INTERACTION_EVENT, j as INTERACTION_RESOLVED_EVENT, k as InteractionAnswerValue, I as InteractionAnswers, c as InteractionCancelData, l as InteractionPersistedPart, N as NoticeKind, m as NoticePersistedPart, P as ParseInteractionAnswersResult, n as ParseInteractionResult, o as canTransitionInteractionStatus, p as cancelStatusFor, q as composerAnswerData, r as composerAnswerDeliveries, s as dedupeQuestionInteractionsByContent, t as fieldAcceptsFreeText, u as interactionFromWireRequest, v as interactionPartKey, w as interactionToPersistedPart, x as isRenderableInteractionKind, y as isSafeInteractionFieldKey, z as isTerminalInteractionStatus, A as noticePart, B as noticePartKey, D as parseInteractionAnswers, E as parseInteractionCancel, F as parseInteractionRequest, G as persistedPartToInteraction, H as questionInteractionContentSignature, J as stampInteractionAnswers } from '../contract-CEewO6DI.js';
1
+ import { e as InteractionRequestWire } from '../contract-CQNvv5th.js';
2
+ export { f as ChatFreeTextField, b as ChatInteraction, C as ChatInteractionField, a as ChatInteractionStatus, d as ChatSelectField, g as ComposerAnswerDelivery, h as INTERACTION_CANCEL_EVENT, i as INTERACTION_EVENT, j as INTERACTION_RESOLVED_EVENT, k as InteractionAnswerValue, I as InteractionAnswers, c as InteractionCancelData, l as InteractionPersistedPart, N as NoticeKind, m as NoticePersistedPart, P as ParseInteractionAnswersResult, n as ParseInteractionResult, o as canTransitionInteractionStatus, p as cancelStatusFor, q as composerAnswerData, r as composerAnswerDeliveries, s as dedupeQuestionInteractionsByContent, t as fieldAcceptsFreeText, u as interactionFromWireRequest, v as interactionPartKey, w as interactionToPersistedPart, x as isRenderableInteractionKind, y as isSafeInteractionFieldKey, z as isTerminalInteractionStatus, A as noticePart, B as noticePartKey, D as parseInteractionAnswers, E as parseInteractionCancel, F as parseInteractionRequest, G as persistedPartToInteraction, H as questionInteractionContentSignature, J as stampInteractionAnswers } from '../contract-CQNvv5th.js';
3
3
  import { InteractionOutcome, InteractionData } from '@tangle-network/agent-interface';
4
4
  export { InteractionData, InteractionOutcome, InteractionRequest } from '@tangle-network/agent-interface';
5
5