@tangle-network/agent-app 0.60.190 → 0.60.192

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  ReasoningRow,
3
3
  chatToolCallPart
4
- } from "../chunk-Y2SOQOJE.js";
4
+ } from "../chunk-KNTRS7OZ.js";
5
5
  import "../chunk-3HUFJ5LO.js";
6
6
  import "../chunk-KMIDRUUH.js";
7
7
  import "../chunk-FBVLEGEG.js";
8
8
  import "../chunk-Y6PRDUFJ.js";
9
9
  import {
10
10
  ChatComposer
11
- } from "../chunk-MSAGTFN4.js";
11
+ } from "../chunk-6RFNLZCK.js";
12
12
  import "../chunk-L7MB2LLM.js";
13
13
  import "../chunk-GNBBY3KN.js";
14
14
  import "../chunk-A2GG4D4A.js";
@@ -28,14 +28,14 @@ import {
28
28
  AsyncView
29
29
  } from "../chunk-AVP5SNIG.js";
30
30
  import "../chunk-FQYAJYPW.js";
31
- import "../chunk-WTC6AOPL.js";
31
+ import "../chunk-PBQYVAB6.js";
32
32
  import "../chunk-VBRUG4N5.js";
33
33
  import "../chunk-B7CL7ENS.js";
34
34
  import "../chunk-PV33JZKV.js";
35
35
  import "../chunk-AMUXRAUO.js";
36
- import "../chunk-TNWDYAFX.js";
37
- import "../chunk-YHRWZZJN.js";
36
+ import "../chunk-E6UTFXKQ.js";
38
37
  import "../chunk-ZVEEWGDK.js";
38
+ import "../chunk-YHRWZZJN.js";
39
39
  import "../chunk-TXD5HXLE.js";
40
40
  import "../chunk-ZGUXI5SI.js";
41
41
  import "../chunk-6VQCFJWP.js";
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  ComposerModeControls,
3
3
  EntryComposer
4
- } from "../chunk-MSAGTFN4.js";
4
+ } from "../chunk-6RFNLZCK.js";
5
5
  import "../chunk-L7MB2LLM.js";
6
6
  import "../chunk-SJWIZT7B.js";
7
7
  import "../chunk-5IS3GCMR.js";
8
8
  import "../chunk-HEPEGY6Q.js";
9
9
  import "../chunk-XG6FODYO.js";
10
10
  import "../chunk-RASGKEAL.js";
11
- import "../chunk-WTC6AOPL.js";
11
+ import "../chunk-PBQYVAB6.js";
12
12
  import "../chunk-VBRUG4N5.js";
13
13
  import "../chunk-AMUXRAUO.js";
14
14
  import "../chunk-YHRWZZJN.js";
@@ -29,7 +29,7 @@
29
29
  */
30
30
  import type { ChatAttachmentKind } from './wire';
31
31
  import { type AtomicAttachmentWriter, type WriteAttachmentFn } from './attachment-store';
32
- import { type AttachmentPathCheck } from './resolve-attachments';
32
+ import { type AttachmentPathCheck } from './attachment-validation';
33
33
  /** Stable authorization result for the original writer contract. */
34
34
  export type AttachmentUploadAuthorization = {
35
35
  ok: true;
@@ -2,7 +2,9 @@
2
2
  * Shared attachment validation core — constants, type-gate, and filename
3
3
  * sanitization used by BOTH the (server) attachment upload route and the
4
4
  * (browser) composer's client-side pre-validation, so a rejection never
5
- * differs depending on which side classified the bytes first.
5
+ * differs depending on which side classified the bytes first. The structural
6
+ * `attachments` parser lives here too, so the chat route and the operator API
7
+ * refuse the same turn bodies without either pulling in the chat store.
6
8
  *
7
9
  * ≈ gtm-agent's `src/lib/attachment-limits.ts`, minus what agent-app already
8
10
  * has (`ATTACHMENT_MAX_COUNT`/`MAX_ATTACHMENT_TOTAL_BYTES`/
@@ -14,6 +16,7 @@
14
16
  * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here
15
17
  * may reach a Node builtin or an engine package.
16
18
  */
19
+ import { type ChatAttachmentInput } from './wire';
17
20
  import type { SniffResult } from './binary-sniff';
18
21
  /** Ceiling on a binary attachment's raw (pre-encoding) byte size. */
19
22
  export declare const MAX_BINARY_ATTACHMENT_BYTES: number;
@@ -93,3 +96,49 @@ export declare function attachmentSizeErrorMessage(name: string, actualBytes: nu
93
96
  /** Human-readable error for a chat message whose combined attachments exceed
94
97
  * the aggregate raw-byte ceiling. */
95
98
  export declare function attachmentTotalSizeErrorMessage(totalBytes: number, limitBytes: number): string;
99
+ /** Verdict of a path check: OK, or a rejection naming why. Mirrors
100
+ * `SandboxMentionPathCheck` in `./wire`. */
101
+ export type AttachmentPathCheck = {
102
+ succeeded: true;
103
+ } | {
104
+ succeeded: false;
105
+ error: string;
106
+ };
107
+ /**
108
+ * Default path validator when a caller supplies none. Rejects the ways a path
109
+ * picked in a client can escape the store root — traversal (`..` segment),
110
+ * absolute (leading `/`), backslashes, null bytes, control characters (see
111
+ * {@link CONTROL_CHARS} — a path also feeds {@link buildAttachmentPromptBlock}'s
112
+ * `(vault: ${path})` pointer, so it is exposed to the same injection surface as
113
+ * `name`) — plus a dotfile/hidden segment (a leading `.` on any segment).
114
+ * Generalized from gtm's `validateVaultFilePath`, in the spirit of
115
+ * `validateSandboxMentionPath` (`/chat-routes`'s wire mention-path validator) —
116
+ * but the dotfile rejection here is INTENTIONALLY stricter than that sibling:
117
+ * an uploaded attachment path is sanitized store-relative storage the product
118
+ * itself assigned, whereas a mention path points at a file that already exists
119
+ * in the sandbox and may legitimately live under a dotfile segment. A caller
120
+ * that needs gtm's exact (looser) rule can supply `validatePath` to override
121
+ * this default entirely.
122
+ */
123
+ export declare function defaultValidateAttachmentPath(path: string): AttachmentPathCheck;
124
+ /** Structural limits for {@link parseChatAttachmentInputs}. */
125
+ export interface ParseChatAttachmentInputsOptions {
126
+ /** Most attachments one request may carry. Default {@link ATTACHMENT_MAX_COUNT}. */
127
+ maxCount?: number;
128
+ /** Path validator override. Default {@link defaultValidateAttachmentPath}. */
129
+ validatePath?: (path: string) => AttachmentPathCheck;
130
+ }
131
+ /**
132
+ * Validate an `attachments` value's shape without reading the store: an
133
+ * array of at most `maxCount` {@link ChatAttachmentInput}s with valid,
134
+ * distinct paths. Absent (`undefined`/`null`) is an empty list. Sizes are
135
+ * still client-reported here; {@link resolveChatAttachments} re-derives them
136
+ * from the store.
137
+ */
138
+ export declare function parseChatAttachmentInputs(value: unknown, options?: ParseChatAttachmentInputsOptions): {
139
+ succeeded: true;
140
+ value: ChatAttachmentInput[];
141
+ } | {
142
+ succeeded: false;
143
+ error: string;
144
+ };
@@ -22,9 +22,11 @@ import {
22
22
  attachmentSizeErrorMessage,
23
23
  attachmentTotalSizeErrorMessage,
24
24
  checkAttachmentType,
25
+ defaultValidateAttachmentPath,
26
+ parseChatAttachmentInputs,
25
27
  sanitizeAttachmentFileName,
26
28
  sniffBinary
27
- } from "../chunk-WTC6AOPL.js";
29
+ } from "../chunk-PBQYVAB6.js";
28
30
  import {
29
31
  DEFAULT_STALE_TURN_LOCK_GRACE_MS,
30
32
  DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
@@ -72,7 +74,7 @@ import {
72
74
  rowIdOf,
73
75
  storeSupportsDraftPersistence,
74
76
  streamChatRouteAsSandboxEvents
75
- } from "../chunk-J26IXWFK.js";
77
+ } from "../chunk-Q2H5FKVG.js";
76
78
  import "../chunk-Q7WEEN7P.js";
77
79
  import "../chunk-UTAIRVOK.js";
78
80
  import "../chunk-RN4WPTBH.js";
@@ -87,7 +89,8 @@ import {
87
89
  attachmentInputToPart,
88
90
  attachmentKindForMime,
89
91
  toChatMessageParts
90
- } from "../chunk-TNWDYAFX.js";
92
+ } from "../chunk-E6UTFXKQ.js";
93
+ import "../chunk-ZVEEWGDK.js";
91
94
  import {
92
95
  ChatTurnInputError,
93
96
  DISPATCH_MAX_MEDIA_PARTS,
@@ -121,7 +124,6 @@ import {
121
124
  turnResponseEventFor,
122
125
  validateSandboxMentionPath
123
126
  } from "../chunk-YHRWZZJN.js";
124
- import "../chunk-ZVEEWGDK.js";
125
127
  import {
126
128
  isWorkspaceFileExportable
127
129
  } from "../chunk-TXD5HXLE.js";
@@ -2465,83 +2467,11 @@ function createAtomicAttachmentWriter(input) {
2465
2467
  }
2466
2468
 
2467
2469
  // src/chat-routes/resolve-attachments.ts
2468
- var MAX_ATTACHMENT_NAME_LENGTH = 256;
2469
- function isAttachmentKind(value) {
2470
- return value === "image" || value === "file";
2471
- }
2472
- var CONTROL_CHARS = /[\x00-\x1F\x7F]/;
2473
- function defaultValidateAttachmentPath(path) {
2474
- if (path.includes("\0")) return { succeeded: false, error: "attachment path must not contain null bytes" };
2475
- if (path.includes("\\")) return { succeeded: false, error: "attachment path must not contain backslashes" };
2476
- if (CONTROL_CHARS.test(path)) return { succeeded: false, error: "attachment path must not contain control characters" };
2477
- if (path.startsWith("/")) return { succeeded: false, error: "attachment path must be store-relative, not absolute" };
2478
- const segments = path.split("/");
2479
- if (segments.some((segment) => segment === "..")) {
2480
- return { succeeded: false, error: 'attachment path must not contain ".." segments' };
2481
- }
2482
- if (segments.some((segment) => segment.startsWith("."))) {
2483
- return { succeeded: false, error: "attachment path must not contain a hidden (dotfile) segment" };
2484
- }
2485
- return { succeeded: true };
2486
- }
2487
- function parseAttachmentInput(value, index, validatePath) {
2488
- if (!value || typeof value !== "object" || Array.isArray(value)) {
2489
- return { succeeded: false, error: `attachments[${index}] must be an object` };
2490
- }
2491
- const record2 = value;
2492
- const path = record2.path;
2493
- if (typeof path !== "string" || !path) {
2494
- return { succeeded: false, error: `attachments[${index}].path must be a non-empty string` };
2495
- }
2496
- const name = record2.name;
2497
- if (typeof name !== "string" || !name.trim()) {
2498
- return { succeeded: false, error: `attachments[${index}].name must be a non-empty string` };
2499
- }
2500
- if (name.length > MAX_ATTACHMENT_NAME_LENGTH) {
2501
- return { succeeded: false, error: `attachments[${index}].name must not exceed ${MAX_ATTACHMENT_NAME_LENGTH} characters` };
2502
- }
2503
- if (CONTROL_CHARS.test(name)) {
2504
- return { succeeded: false, error: `attachments[${index}].name must not contain control characters` };
2505
- }
2506
- const size = record2.size;
2507
- if (typeof size !== "number" || !Number.isFinite(size)) {
2508
- return { succeeded: false, error: `attachments[${index}].size must be a finite number` };
2509
- }
2510
- if (size < 0) {
2511
- return { succeeded: false, error: `attachments[${index}].size must not be negative` };
2512
- }
2513
- const mediaType = record2.mediaType;
2514
- if (typeof mediaType !== "string") {
2515
- return { succeeded: false, error: `attachments[${index}].mediaType must be a string` };
2516
- }
2517
- const kind = record2.kind;
2518
- if (!isAttachmentKind(kind)) {
2519
- return { succeeded: false, error: `attachments[${index}].kind must be "image" or "file"` };
2520
- }
2521
- const pathCheck = validatePath(path);
2522
- if (!pathCheck.succeeded) return { succeeded: false, error: pathCheck.error };
2523
- return { succeeded: true, value: { path, name, size, mediaType, kind } };
2524
- }
2525
2470
  async function resolveChatAttachments(value, options) {
2526
- const maxCount = options.maxCount ?? ATTACHMENT_MAX_COUNT;
2527
2471
  const maxTotalBytes = options.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES;
2528
- const validatePath = options.validatePath ?? defaultValidateAttachmentPath;
2529
- if (value === void 0 || value === null) return { succeeded: true, value: [] };
2530
- if (!Array.isArray(value)) return { succeeded: false, error: "attachments must be an array" };
2531
- if (value.length > maxCount) {
2532
- return { succeeded: false, error: `attachments must not exceed ${maxCount} entries` };
2533
- }
2534
- const inputs = [];
2535
- const seenPaths = /* @__PURE__ */ new Set();
2536
- for (let index = 0; index < value.length; index += 1) {
2537
- const parsed = parseAttachmentInput(value[index], index, validatePath);
2538
- if (!parsed.succeeded) return parsed;
2539
- if (seenPaths.has(parsed.value.path)) {
2540
- return { succeeded: false, error: `attachments must not repeat a path: ${parsed.value.path}` };
2541
- }
2542
- seenPaths.add(parsed.value.path);
2543
- inputs.push(parsed.value);
2544
- }
2472
+ const parsed = parseChatAttachmentInputs(value, options);
2473
+ if (!parsed.succeeded) return parsed;
2474
+ const inputs = parsed.value;
2545
2475
  const advisoryTotal = inputs.reduce((sum2, input) => sum2 + input.size, 0);
2546
2476
  if (advisoryTotal > maxTotalBytes) {
2547
2477
  return { succeeded: false, error: attachmentTotalSizeErrorMessage(advisoryTotal, maxTotalBytes) };
@@ -3636,6 +3566,7 @@ export {
3636
3566
  normalizeChatPromptForSandbox,
3637
3567
  observeChatTurnStream,
3638
3568
  observeNativeCompletion,
3569
+ parseChatAttachmentInputs,
3639
3570
  parseChatTurnParts,
3640
3571
  parseFileMentions,
3641
3572
  parsePlanFollowUpAttach,