@agentknight/sdk 0.9.38 → 0.9.39

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.
package/dist/index.js CHANGED
@@ -4,12 +4,12 @@ import {
4
4
  PeerChatRequestSchema,
5
5
  PeerChatResultSchema,
6
6
  validateDataDirectory
7
- } from "./chunks/chunk-4FC2WG64.js";
7
+ } from "./chunks/chunk-UE4QYZCG.js";
8
8
  import {
9
9
  AgentSelectionSchema,
10
10
  assertSelectionSnapshotFits,
11
11
  supportsAgentSelection
12
- } from "./chunks/chunk-6YF3VZLR.js";
12
+ } from "./chunks/chunk-KPRPUIXR.js";
13
13
  import {
14
14
  ConversationNormalizer,
15
15
  LiveTimelineProjection,
@@ -31,16 +31,16 @@ import {
31
31
  resolveGitProjectIdentity,
32
32
  sessionRestoreFailure,
33
33
  validateOperationResponse
34
- } from "./chunks/chunk-UIM3KQRC.js";
34
+ } from "./chunks/chunk-QGPX4UDW.js";
35
35
  import {
36
36
  GROUP_CHAT_MAX_TURNS,
37
37
  GroupChatError
38
- } from "./chunks/chunk-YSJRQ35E.js";
38
+ } from "./chunks/chunk-VCNYBWYE.js";
39
39
  import {
40
40
  assistantPreview,
41
41
  isContentfulAssistant,
42
42
  userPreview
43
- } from "./chunks/chunk-34DUD2Y4.js";
43
+ } from "./chunks/chunk-N4MN54CD.js";
44
44
  import {
45
45
  AgentModelListingError,
46
46
  AgentSessionRenameError,
@@ -49,7 +49,7 @@ import {
49
49
  UnsupportedAgentOperationError,
50
50
  captureResearchAccess,
51
51
  captureWorkbenchAccess
52
- } from "./chunks/chunk-6GVMVE5N.js";
52
+ } from "./chunks/chunk-BM6T2DLH.js";
53
53
  import {
54
54
  A2aRelayRequestSchema,
55
55
  A2aRelayResponseSchema,
@@ -107,8 +107,11 @@ import {
107
107
  SESSION_FILE_MAX_IMAGE_EDGE,
108
108
  SESSION_FILE_MAX_IMAGE_PIXELS,
109
109
  SESSION_FILE_MAX_ORIGINAL_BYTES,
110
+ SESSION_FILE_MAX_VIDEO_PIXELS,
110
111
  SESSION_FILE_READ_OPERATION,
112
+ SESSION_FILE_READ_V2_VERSION,
111
113
  SESSION_FILE_READ_VERSION,
114
+ SESSION_FILE_V2_MAX_ORIGINAL_BYTES,
112
115
  SESSION_GIT_DIFF_GZIP_MIN_BYTES,
113
116
  SESSION_GIT_DIFF_GZIP_VERSION,
114
117
  SESSION_GIT_DIFF_MAX_FILES,
@@ -116,6 +119,9 @@ import {
116
119
  SESSION_GIT_DIFF_MAX_RESPONSE_BYTES,
117
120
  SESSION_GIT_DIFF_OPERATION,
118
121
  SESSION_GIT_DIFF_VERSION,
122
+ SESSION_IMAGE_API_PATH,
123
+ SESSION_IMAGE_MAX_BYTES,
124
+ SESSION_IMAGE_MAX_PIXELS,
119
125
  SESSION_LIST_UPDATES_CAPABILITY,
120
126
  SESSION_OPTIONS_PARTIAL_CAPABILITY,
121
127
  SESSION_PROJECT_IDENTITY_OPERATION,
@@ -144,9 +150,14 @@ import {
144
150
  SessionDirectoryListResultDataSchema,
145
151
  SessionFileReadDataSchema,
146
152
  SessionFileReadResultDataSchema,
153
+ SessionFileReadV2ResultDataSchema,
147
154
  SessionGitDiffDataSchema,
148
155
  SessionGitDiffGzipResultDataSchema,
149
156
  SessionGitDiffResultDataSchema,
157
+ SessionImageCommitResultSchema,
158
+ SessionImageScopeSchema,
159
+ SessionImageUploadRequestSchema,
160
+ SessionImageUploadResultSchema,
150
161
  SessionListRequestSchema,
151
162
  SessionListResultSchema,
152
163
  SessionOptionsRequestSchema,
@@ -164,6 +175,8 @@ import {
164
175
  SessionTimelinePageSchema,
165
176
  SessionWatchRequestSchema,
166
177
  SessionsSnapshotSchema,
178
+ TIMELINE_IMAGE_BLOB_OPERATION,
179
+ TIMELINE_IMAGE_BLOB_VERSION,
167
180
  TIMELINE_IMAGE_CHUNK_BYTES,
168
181
  TIMELINE_IMAGE_MAX_BYTES,
169
182
  TIMELINE_IMAGE_OPERATION,
@@ -172,6 +185,8 @@ import {
172
185
  TIMELINE_SYNC_OPERATION,
173
186
  TIMELINE_SYNC_VERSION,
174
187
  TRANSPORT_V2,
188
+ TimelineImageBlobRequestSchema,
189
+ TimelineImageBlobResultSchema,
175
190
  TimelineImageChunkSchema,
176
191
  TimelineImageRequestSchema,
177
192
  TimelineSyncRequestSchema,
@@ -208,12 +223,12 @@ import {
208
223
  sessionCommunicationError,
209
224
  sessionCommunicationOperationCapability,
210
225
  workbenchCapability
211
- } from "./chunks/chunk-GNQC2IS2.js";
226
+ } from "./chunks/chunk-E3XCZWRK.js";
212
227
 
213
228
  // src/initialize.ts
214
- import { createHash as createHash14 } from "node:crypto";
229
+ import { createHash as createHash16 } from "node:crypto";
215
230
  import { readFile as readFile4 } from "node:fs/promises";
216
- import { z as z10 } from "zod";
231
+ import { z as z12 } from "zod";
217
232
 
218
233
  // src/coding-agent-factory.ts
219
234
  import { resolve } from "node:path";
@@ -286,7 +301,7 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
286
301
  switch (engine.kind) {
287
302
  case "managed": {
288
303
  if (owner === void 0) throw new Error("Managed coding agents require the PC owner identity");
289
- const { ManagedCodingAgentAdapter } = await import("./chunks/managed-agent-adapter-EMBDKITT.js");
304
+ const { ManagedCodingAgentAdapter } = await import("./chunks/managed-agent-adapter-T3FVUJHA.js");
290
305
  return new ManagedCodingAgentAdapter({
291
306
  dataDirectory: config.dataDirectory,
292
307
  owner,
@@ -325,7 +340,7 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
325
340
  });
326
341
  }
327
342
  case "copilot": {
328
- const { GithubCopilotAdapter } = await import("./chunks/copilot-adapter-USX4UEWH.js");
343
+ const { GithubCopilotAdapter } = await import("./chunks/copilot-adapter-N5B7BNWO.js");
329
344
  if (owner !== void 0) {
330
345
  return new GithubCopilotAdapter(void 0, void 0, void 0, {
331
346
  dataDirectory: config.dataDirectory,
@@ -345,13 +360,13 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
345
360
  }
346
361
  case "pi": {
347
362
  assertPiRuntimeSupported();
348
- const { PiCodingAgentAdapter } = await import("./chunks/pi-adapter-KT5M7Z6R.js");
363
+ const { PiCodingAgentAdapter } = await import("./chunks/pi-adapter-PHEFFJVF.js");
349
364
  return new PiCodingAgentAdapter(
350
365
  engine.agentDirectory === void 0 ? {} : { agentDirectory: resolve(engine.agentDirectory) }
351
366
  );
352
367
  }
353
368
  case "acp": {
354
- const { AcpCodingAgentAdapter } = await import("./chunks/acp-adapter-DZ3EE36H.js");
369
+ const { AcpCodingAgentAdapter } = await import("./chunks/acp-adapter-BWVM6WIF.js");
355
370
  return new AcpCodingAgentAdapter({
356
371
  command: engine.command,
357
372
  args: engine.args,
@@ -367,6 +382,18 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
367
382
  }
368
383
  }
369
384
 
385
+ // src/config.ts
386
+ import { z } from "zod";
387
+ var SessionImagesConfigSchema = z.object({
388
+ allowUnencryptedStorage: z.literal(true),
389
+ storageAccount: z.string().regex(/^[a-z0-9]{3,24}$/u),
390
+ storageContainer: z.string().min(3).max(63).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u)
391
+ }).strict();
392
+ var SessionImageRelayUrlSchema = z.url().refine((value) => {
393
+ const url = new URL(value);
394
+ return /^https:\/\/[^/?#\\@\s\x00-\x1f\x7f]+\/?$/iu.test(value) && url.protocol === "https:" && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "";
395
+ }, "Session images require an HTTPS Relay origin without a path, credentials, query or fragment").transform((value) => new URL(value).origin);
396
+
370
397
  // src/content-encryption.ts
371
398
  import {
372
399
  createCipheriv,
@@ -383,22 +410,22 @@ import {
383
410
  } from "node:fs";
384
411
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
385
412
  import { join as join2 } from "node:path";
386
- import { z } from "zod";
413
+ import { z as z2 } from "zod";
387
414
  var KEY_BYTES = 32;
388
415
  var NONCE_BYTES = 12;
389
416
  var AUTH_TAG_BYTES = 16;
390
417
  var DEFAULT_REPLAY_WINDOW_MS = 10 * 60 * 1e3;
391
418
  var MAX_REPLAY_ENTRIES = 1e4;
392
419
  var CONTENT_KEY_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
393
- var ContentKeyFileSchema = z.object({
394
- schemaVersion: z.literal(1),
395
- key: z.string().regex(CONTENT_KEY_PATTERN)
420
+ var ContentKeyFileSchema = z2.object({
421
+ schemaVersion: z2.literal(1),
422
+ key: z2.string().regex(CONTENT_KEY_PATTERN)
396
423
  }).strict();
397
- var ReplayJournalEntrySchema = z.object({
398
- schemaVersion: z.literal(1),
399
- keyId: z.string().regex(/^[A-Za-z0-9_-]{22}$/u),
400
- messageId: z.string().trim().min(1).max(256),
401
- sentAt: z.number().int().nonnegative()
424
+ var ReplayJournalEntrySchema = z2.object({
425
+ schemaVersion: z2.literal(1),
426
+ keyId: z2.string().regex(/^[A-Za-z0-9_-]{22}$/u),
427
+ messageId: z2.string().trim().min(1).max(256),
428
+ sentAt: z2.number().int().nonnegative()
402
429
  }).strict();
403
430
  var REPLAY_JOURNAL_COMPACT_EVERY = 256;
404
431
  var REPLAY_JOURNAL_MAX_BYTES = 1024 * 1024;
@@ -709,13 +736,13 @@ var ContentCipher = class {
709
736
  };
710
737
 
711
738
  // src/device-identity.ts
712
- import { z as z2 } from "zod";
713
- var DeviceIdentitySchema = z2.object({
714
- schemaVersion: z2.literal(2),
715
- machineId: z2.uuid(),
716
- stableDeviceId: z2.uuid(),
717
- machineName: z2.string().trim().min(1).max(120),
718
- createdAt: z2.iso.datetime({ offset: true })
739
+ import { z as z3 } from "zod";
740
+ var DeviceIdentitySchema = z3.object({
741
+ schemaVersion: z3.literal(2),
742
+ machineId: z3.uuid(),
743
+ stableDeviceId: z3.uuid(),
744
+ machineName: z3.string().trim().min(1).max(120),
745
+ createdAt: z3.iso.datetime({ offset: true })
719
746
  });
720
747
 
721
748
  // src/pairing.ts
@@ -812,7 +839,7 @@ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypt
812
839
  import { lstat as lstat3, realpath as realpath3, stat as stat4 } from "node:fs/promises";
813
840
  import { isAbsolute as isAbsolute4, join as join5 } from "node:path";
814
841
  import { gunzipSync } from "node:zlib";
815
- import { z as z4 } from "zod";
842
+ import { z as z5 } from "zod";
816
843
 
817
844
  // src/session-directory.ts
818
845
  import { createHash as createHash2 } from "node:crypto";
@@ -967,6 +994,152 @@ import { basename, extname, isAbsolute as isAbsolute3, join as join4 } from "nod
967
994
  import { promisify } from "node:util";
968
995
  import { gzip } from "node:zlib";
969
996
  import sharp from "sharp";
997
+
998
+ // src/session-video.ts
999
+ function requireVideo(valid) {
1000
+ if (!valid) throw new Error("Unsupported video container");
1001
+ }
1002
+ function one(elements, id) {
1003
+ const matches = elements.filter((element) => element.id === id);
1004
+ const match = matches[0];
1005
+ requireVideo(matches.length === 1 && match !== void 0);
1006
+ return match;
1007
+ }
1008
+ function validateSessionVideo(bytes, mediaType) {
1009
+ requireVideo(bytes.length > 0 && bytes.length <= SESSION_FILE_V2_MAX_ORIGINAL_BYTES);
1010
+ if (mediaType === "video/mp4") validateMp4(bytes);
1011
+ else validateWebm(bytes);
1012
+ }
1013
+ function validateMp4(bytes) {
1014
+ const boxes = (start, end) => {
1015
+ const result = [];
1016
+ while (start < end) {
1017
+ requireVideo(end - start >= 8);
1018
+ let size = bytes.readUInt32BE(start);
1019
+ const id = bytes.toString("latin1", start + 4, start + 8);
1020
+ let header = 8;
1021
+ if (size === 1) {
1022
+ requireVideo(end - start >= 16 && bytes.readUInt32BE(start + 8) === 0);
1023
+ size = bytes.readUInt32BE(start + 12);
1024
+ header = 16;
1025
+ }
1026
+ if (size === 0) {
1027
+ requireVideo(id === "mdat");
1028
+ size = end - start;
1029
+ }
1030
+ requireVideo(size >= header && size <= end - start);
1031
+ result.push({ id, start: start + header, end: start + size });
1032
+ start += size;
1033
+ }
1034
+ return result;
1035
+ };
1036
+ const children = (element) => boxes(element.start, element.end);
1037
+ const top = boxes(0, bytes.length);
1038
+ requireVideo(top[0]?.id === "ftyp" && top.every((box) => ["ftyp", "moov", "mdat", "free", "skip", "wide"].includes(box.id)));
1039
+ const ftyp = one(top, "ftyp");
1040
+ requireVideo(ftyp.end - ftyp.start >= 8 && (ftyp.end - ftyp.start) % 4 === 0);
1041
+ const brands = [bytes.toString("latin1", ftyp.start, ftyp.start + 4)];
1042
+ for (let offset = ftyp.start + 8; offset < ftyp.end; offset += 4) brands.push(bytes.toString("latin1", offset, offset + 4));
1043
+ requireVideo(brands.some((brand) => ["isom", "iso2", "mp41", "mp42", "avc1"].includes(brand)));
1044
+ requireVideo(top.some((box) => box.id === "mdat" && box.end > box.start));
1045
+ const movie = children(one(top, "moov"));
1046
+ requireVideo(!movie.some((box) => ["rmra", "mvex"].includes(box.id)));
1047
+ let videoTracks = 0;
1048
+ for (const track of movie.filter((box) => box.id === "trak")) {
1049
+ const media = children(one(children(track), "mdia"));
1050
+ const handler = one(media, "hdlr");
1051
+ requireVideo(handler.end - handler.start >= 12);
1052
+ const type = bytes.toString("latin1", handler.start + 8, handler.start + 12);
1053
+ requireVideo(type === "vide" || type === "soun");
1054
+ const info = children(one(media, "minf"));
1055
+ const dref = one(children(one(info, "dinf")), "dref");
1056
+ requireVideo(dref.end - dref.start >= 8 && bytes.readUInt32BE(dref.start) === 0 && bytes.readUInt32BE(dref.start + 4) === 1);
1057
+ const references = boxes(dref.start + 8, dref.end);
1058
+ const reference = one(references, "url ");
1059
+ requireVideo(references.length === 1 && reference.end - reference.start === 4 && bytes.readUInt32BE(reference.start) === 1);
1060
+ const descriptions = one(children(one(info, "stbl")), "stsd");
1061
+ requireVideo(descriptions.end - descriptions.start >= 8 && bytes.readUInt32BE(descriptions.start) === 0 && bytes.readUInt32BE(descriptions.start + 4) === 1);
1062
+ const entries = boxes(descriptions.start + 8, descriptions.end);
1063
+ const entry = entries[0];
1064
+ requireVideo(entries.length === 1 && entry !== void 0 && entry.end - entry.start >= 8 && bytes.readUInt16BE(entry.start + 6) === 1);
1065
+ if (type === "vide") {
1066
+ requireVideo(["avc1", "avc3"].includes(entry.id) && entry.end - entry.start >= 78);
1067
+ const width = bytes.readUInt16BE(entry.start + 24);
1068
+ const height = bytes.readUInt16BE(entry.start + 26);
1069
+ requireVideo(width > 0 && height > 0 && width * height <= SESSION_FILE_MAX_VIDEO_PIXELS);
1070
+ const config = one(boxes(entry.start + 78, entry.end), "avcC");
1071
+ requireVideo(config.end - config.start >= 7 && bytes[config.start] === 1);
1072
+ videoTracks++;
1073
+ } else requireVideo(entry.id === "mp4a" && entry.end - entry.start >= 28);
1074
+ }
1075
+ requireVideo(videoTracks === 1);
1076
+ }
1077
+ function validateWebm(bytes) {
1078
+ const vint = (offset, end, id) => {
1079
+ requireVideo(offset < end);
1080
+ const first = bytes[offset];
1081
+ requireVideo(first !== void 0 && first !== 0);
1082
+ let length = 1;
1083
+ while ((first & 128 >> length - 1) === 0) length++;
1084
+ requireVideo(length <= (id ? 4 : 8) && length <= end - offset);
1085
+ let value = id ? first : first & 255 >> length;
1086
+ let unknown = !id && value === 255 >> length;
1087
+ for (let index = 1; index < length; index++) {
1088
+ const byte = bytes[offset + index];
1089
+ requireVideo(byte !== void 0);
1090
+ value = value * 256 + byte;
1091
+ unknown = unknown && byte === 255;
1092
+ }
1093
+ requireVideo(unknown || Number.isSafeInteger(value));
1094
+ return { value, next: offset + length, unknown };
1095
+ };
1096
+ const elements = (start, end) => {
1097
+ const result = [];
1098
+ while (start < end) {
1099
+ const id = vint(start, end, true);
1100
+ const size = vint(id.next, end, false);
1101
+ const tag = id.value.toString(16);
1102
+ requireVideo(!size.unknown || tag === "18538067");
1103
+ const length = size.unknown ? end - size.next : size.value;
1104
+ requireVideo(length <= end - size.next);
1105
+ result.push({ id: tag, start: size.next, end: size.next + length });
1106
+ start = size.next + length;
1107
+ }
1108
+ return result;
1109
+ };
1110
+ const text = (element) => bytes.toString("latin1", element.start, element.end);
1111
+ const uint = (element) => {
1112
+ requireVideo(element.end - element.start >= 1 && element.end - element.start <= 4);
1113
+ return bytes.readUIntBE(element.start, element.end - element.start);
1114
+ };
1115
+ const top = elements(0, bytes.length);
1116
+ requireVideo(top.length === 2 && top[0]?.id === "1a45dfa3");
1117
+ const header = one(top, "1a45dfa3");
1118
+ requireVideo(text(one(elements(header.start, header.end), "4282")) === "webm");
1119
+ const segment = one(top, "18538067");
1120
+ const contents = elements(segment.start, segment.end);
1121
+ requireVideo(contents.some((element) => element.id === "1f43b675" && element.end > element.start));
1122
+ const tracks = one(contents, "1654ae6b");
1123
+ let videoTracks = 0;
1124
+ for (const track of elements(tracks.start, tracks.end).filter((element) => element.id === "ae")) {
1125
+ const fields = elements(track.start, track.end);
1126
+ const type = uint(one(fields, "83"));
1127
+ const codec = text(one(fields, "86"));
1128
+ requireVideo(!fields.some((field2) => field2.id === "6d80" || field2.id === "6fab"));
1129
+ if (type === 1) {
1130
+ requireVideo(codec === "V_VP8" || codec === "V_VP9");
1131
+ const video = one(fields, "e0");
1132
+ const dimensions = elements(video.start, video.end);
1133
+ const width = uint(one(dimensions, "b0"));
1134
+ const height = uint(one(dimensions, "ba"));
1135
+ requireVideo(width > 0 && height > 0 && width * height <= SESSION_FILE_MAX_VIDEO_PIXELS);
1136
+ videoTracks++;
1137
+ } else requireVideo(type === 2 && (codec === "A_VORBIS" || codec === "A_OPUS"));
1138
+ }
1139
+ requireVideo(videoTracks === 1);
1140
+ }
1141
+
1142
+ // src/session-file.ts
970
1143
  var SESSION_FILE_CONCURRENCY = 4;
971
1144
  var SESSION_FILE_TIMEOUT_MS = 5e3;
972
1145
  var compress = promisify(gzip);
@@ -1204,10 +1377,12 @@ var SessionFileReader = class {
1204
1377
  }
1205
1378
  metadata;
1206
1379
  #active = 0;
1207
- async read(sessionId, path, rootRevision) {
1380
+ async read(sessionId, path, rootRevision, version = SESSION_FILE_READ_VERSION) {
1381
+ if (version !== SESSION_FILE_READ_VERSION && version !== SESSION_FILE_READ_V2_VERSION) fail2("UNSUPPORTED_OPERATION_VERSION");
1208
1382
  if (!SessionFileReadDataSchema.safeParse({ path, rootRevision }).success) fail2("INVALID_FILE_PATH");
1209
- return SessionFileReadResultDataSchema.parse(await this.#run(
1210
- (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, false)
1383
+ const schema = version === SESSION_FILE_READ_V2_VERSION ? SessionFileReadV2ResultDataSchema : SessionFileReadResultDataSchema;
1384
+ return schema.parse(await this.#run(
1385
+ (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, false, version)
1211
1386
  ));
1212
1387
  }
1213
1388
  async readResource(sessionId, path, rootRevision) {
@@ -1241,7 +1416,7 @@ var SessionFileReader = class {
1241
1416
  clearTimeout(timer);
1242
1417
  }
1243
1418
  }
1244
- async #read(sessionId, path, rootRevision, checkDeadline, resource) {
1419
+ async #read(sessionId, path, rootRevision, checkDeadline, resource, version = SESSION_FILE_READ_VERSION) {
1245
1420
  const metadata = await this.metadata(sessionId);
1246
1421
  checkDeadline();
1247
1422
  if (metadata === void 0) fail2("SESSION_NOT_FOUND");
@@ -1283,11 +1458,12 @@ var SessionFileReader = class {
1283
1458
  return file;
1284
1459
  };
1285
1460
  const before = await inspectPath();
1286
- const maxOriginalBytes = resource ? SESSION_RESOURCE_MAX_ORIGINAL_BYTES : SESSION_FILE_MAX_ORIGINAL_BYTES;
1461
+ const maxOriginalBytes = resource ? SESSION_RESOURCE_MAX_ORIGINAL_BYTES : version === SESSION_FILE_READ_V2_VERSION ? SESSION_FILE_V2_MAX_ORIGINAL_BYTES : SESSION_FILE_MAX_ORIGINAL_BYTES;
1287
1462
  if (before.size > BigInt(maxOriginalBytes)) fail2("FILE_TOO_LARGE");
1288
1463
  const extension = extname(path).toLowerCase();
1289
1464
  const image = [".png", ".jpg", ".jpeg", ".webp"].includes(extension);
1290
- const mediaType = resource ? resourceMediaType(extension) : image ? "image/webp" : textMediaType(path);
1465
+ const video = !resource && version === SESSION_FILE_READ_V2_VERSION && [".mp4", ".webm"].includes(extension);
1466
+ const mediaType = resource ? resourceMediaType(extension) : image ? "image/webp" : video ? extension === ".mp4" ? "video/mp4" : "video/webm" : textMediaType(path);
1291
1467
  const handle = await open(target, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0) | (constants2.O_NONBLOCK ?? 0));
1292
1468
  try {
1293
1469
  const opened = await handle.stat({ bigint: true });
@@ -1313,6 +1489,13 @@ var SessionFileReader = class {
1313
1489
  await validateRawImage(bytes, extension, checkDeadline);
1314
1490
  canonical = bytes;
1315
1491
  } else canonical = await imagePreview(bytes, extension, checkDeadline);
1492
+ } else if (mediaType === "video/mp4" || mediaType === "video/webm") {
1493
+ try {
1494
+ validateSessionVideo(bytes, mediaType);
1495
+ } catch (error) {
1496
+ throw new SessionFileReadError("FILE_UNSUPPORTED", { cause: error });
1497
+ }
1498
+ canonical = bytes;
1316
1499
  } else {
1317
1500
  let text;
1318
1501
  try {
@@ -1345,7 +1528,7 @@ var SessionFileReader = class {
1345
1528
  throw new SessionFileReadError("FILE_CHANGED", { cause: error });
1346
1529
  }
1347
1530
  checkDeadline();
1348
- return (resource ? SessionResourceReadResultDataSchema : SessionFileReadResultDataSchema).parse({
1531
+ return (resource ? SessionResourceReadResultDataSchema : version === SESSION_FILE_READ_V2_VERSION ? SessionFileReadV2ResultDataSchema : SessionFileReadResultDataSchema).parse({
1349
1532
  path,
1350
1533
  rootRevision,
1351
1534
  mediaType,
@@ -1784,20 +1967,20 @@ function sensitiveSegments(segments) {
1784
1967
  }
1785
1968
  return false;
1786
1969
  }
1787
- var pathSchema = z4.string().max(4096).default("");
1788
- var listSchema = z4.object({
1970
+ var pathSchema = z5.string().max(4096).default("");
1971
+ var listSchema = z5.object({
1789
1972
  path: pathSchema,
1790
- cursor: z4.string().uuid().optional()
1973
+ cursor: z5.string().uuid().optional()
1791
1974
  }).strict();
1792
- var readSchema = z4.object({
1793
- path: z4.string().min(1).max(4096),
1794
- startLine: z4.number().int().min(1).max(500001).default(1),
1795
- lineCount: z4.number().int().min(1).max(200).default(200)
1975
+ var readSchema = z5.object({
1976
+ path: z5.string().min(1).max(4096),
1977
+ startLine: z5.number().int().min(1).max(500001).default(1),
1978
+ lineCount: z5.number().int().min(1).max(200).default(200)
1796
1979
  }).strict();
1797
- var searchSchema = z4.object({
1980
+ var searchSchema = z5.object({
1798
1981
  path: pathSchema,
1799
- query: z4.string().min(1).max(256),
1800
- cursor: z4.string().uuid().optional()
1982
+ query: z5.string().min(1).max(256),
1983
+ cursor: z5.string().uuid().optional()
1801
1984
  }).strict();
1802
1985
  var ResearchTools = class {
1803
1986
  constructor(parentSessionId, root, sourceMetadata, privateDirectories = [], onError, captureSourceAccess) {
@@ -2057,7 +2240,7 @@ var ResearchTools = class {
2057
2240
  const definition = (name, description, schema, run) => ({
2058
2241
  name,
2059
2242
  description,
2060
- parameters: z4.toJSONSchema(schema),
2243
+ parameters: z5.toJSONSchema(schema),
2061
2244
  // Only these host-owned read tools bypass prompts; their path policy remains mandatory.
2062
2245
  skipPermission: true,
2063
2246
  defer: "never",
@@ -2070,7 +2253,7 @@ var ResearchTools = class {
2070
2253
  return { textResultForLlm: JSON.stringify(result), resultType: "success" };
2071
2254
  } catch (error) {
2072
2255
  const knownCode = workbenchErrorDetails(error).code;
2073
- const code = (error instanceof ResearchError || error instanceof SessionFileReadError) && knownCode !== "UNCLASSIFIED" ? knownCode : error instanceof z4.ZodError ? "RESEARCH_INVALID_ARGUMENT" : "RESEARCH_TOOL_FAILED";
2256
+ const code = (error instanceof ResearchError || error instanceof SessionFileReadError) && knownCode !== "UNCLASSIFIED" ? knownCode : error instanceof z5.ZodError ? "RESEARCH_INVALID_ARGUMENT" : "RESEARCH_TOOL_FAILED";
2074
2257
  this.onError?.(new ResearchError(code, "The research tool failed", { cause: error }), { stage: name });
2075
2258
  return { textResultForLlm: JSON.stringify({ error: code }), resultType: "failure" };
2076
2259
  }
@@ -2225,7 +2408,7 @@ import { io } from "socket.io-client";
2225
2408
 
2226
2409
  // src/service/requests/session-communication.ts
2227
2410
  import { AsyncLocalStorage } from "node:async_hooks";
2228
- import { createHash as createHash6 } from "node:crypto";
2411
+ import { createHash as createHash8 } from "node:crypto";
2229
2412
  import { join as join7 } from "node:path";
2230
2413
  import { performance as performance2 } from "node:perf_hooks";
2231
2414
 
@@ -3097,6 +3280,430 @@ var SessionGitDiffReader = class {
3097
3280
  }
3098
3281
  };
3099
3282
 
3283
+ // src/timeline-image-cache.ts
3284
+ import { createHash as createHash6 } from "node:crypto";
3285
+ var TimelineAssetError = class extends Error {
3286
+ constructor(code, message, options) {
3287
+ super(message, options);
3288
+ this.code = code;
3289
+ }
3290
+ code;
3291
+ };
3292
+ var TimelineImageCache = class {
3293
+ constructor(maxBytes = 32 * 1024 * 1024) {
3294
+ this.maxBytes = maxBytes;
3295
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < TIMELINE_IMAGE_MAX_BYTES) {
3296
+ throw new Error("Image cache must hold at least one maximum-size image");
3297
+ }
3298
+ }
3299
+ maxBytes;
3300
+ #assets = /* @__PURE__ */ new Map();
3301
+ #bytes = 0;
3302
+ get bytes() {
3303
+ return this.#bytes;
3304
+ }
3305
+ referenceItem(sessionId, item) {
3306
+ if (!("attachments" in item) || item.attachments === void 0) return item;
3307
+ return { ...item, attachments: item.attachments.map((attachment) => this.#reference(sessionId, attachment)) };
3308
+ }
3309
+ read(sessionId, request) {
3310
+ const key = JSON.stringify([sessionId, request.sha256]);
3311
+ const asset = this.#assets.get(key);
3312
+ if (asset === void 0) throw new TimelineAssetError("IMAGE_ABSENT", "The image is no longer cached on this PC");
3313
+ if (request.offset >= asset.bytes.length) {
3314
+ throw new TimelineAssetError("IMAGE_OFFSET_INVALID", "The image chunk offset exceeds its length");
3315
+ }
3316
+ this.#assets.delete(key);
3317
+ this.#assets.set(key, asset);
3318
+ const end = Math.min(request.offset + TIMELINE_IMAGE_CHUNK_BYTES, asset.bytes.length);
3319
+ return TimelineImageChunkSchema.parse({
3320
+ sha256: request.sha256,
3321
+ offset: request.offset,
3322
+ totalBytes: asset.bytes.length,
3323
+ mimeType: asset.mimeType,
3324
+ data: asset.bytes.subarray(request.offset, end).toString("base64"),
3325
+ eof: end === asset.bytes.length
3326
+ });
3327
+ }
3328
+ readAsset(sessionId, sha256) {
3329
+ const key = JSON.stringify([sessionId, sha256]);
3330
+ const asset = this.#assets.get(key);
3331
+ if (asset === void 0) throw new TimelineAssetError("IMAGE_ABSENT", "The image is no longer cached on this PC");
3332
+ this.#assets.delete(key);
3333
+ this.#assets.set(key, asset);
3334
+ return { bytes: Buffer.from(asset.bytes), mimeType: asset.mimeType };
3335
+ }
3336
+ clearSession(sessionId) {
3337
+ const prefix = JSON.stringify([sessionId]).slice(0, -1) + ",";
3338
+ for (const [key, asset] of this.#assets) {
3339
+ if (key.startsWith(prefix)) {
3340
+ this.#assets.delete(key);
3341
+ this.#bytes -= asset.bytes.length;
3342
+ }
3343
+ }
3344
+ }
3345
+ clear() {
3346
+ this.#assets.clear();
3347
+ this.#bytes = 0;
3348
+ }
3349
+ #reference(sessionId, attachment) {
3350
+ if (attachment.kind !== "image" || attachment.data === void 0) return attachment;
3351
+ const parsed = ConversationAttachmentSchema.parse(attachment);
3352
+ const bytes = Buffer.from(attachment.data, "base64");
3353
+ if (bytes.length !== parsed.byteLength || createHash6("sha256").update(bytes).digest("hex") !== parsed.sha256 || !hasSignature(bytes, parsed.mimeType)) {
3354
+ throw new TimelineAssetError("IMAGE_INVALID", "Image content does not match its metadata");
3355
+ }
3356
+ const key = JSON.stringify([sessionId, parsed.sha256]);
3357
+ const previous = this.#assets.get(key);
3358
+ if (previous !== void 0) this.#bytes -= previous.bytes.length;
3359
+ this.#assets.delete(key);
3360
+ this.#assets.set(key, { bytes, mimeType: parsed.mimeType ?? "" });
3361
+ this.#bytes += bytes.length;
3362
+ while (this.#bytes > this.maxBytes) {
3363
+ const oldest = this.#assets.entries().next().value;
3364
+ if (oldest === void 0) break;
3365
+ this.#assets.delete(oldest[0]);
3366
+ this.#bytes -= oldest[1].bytes.length;
3367
+ }
3368
+ const { data: _data, ...metadata } = parsed;
3369
+ return { ...metadata, imageRef: "sha256" };
3370
+ }
3371
+ };
3372
+ function hasSignature(bytes, mimeType) {
3373
+ switch (mimeType) {
3374
+ case "image/png":
3375
+ return bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
3376
+ case "image/jpeg":
3377
+ return bytes.subarray(0, 3).equals(Buffer.from([255, 216, 255]));
3378
+ case "image/gif":
3379
+ return ["GIF87a", "GIF89a"].includes(bytes.subarray(0, 6).toString("ascii"));
3380
+ case "image/webp":
3381
+ return bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP";
3382
+ default:
3383
+ return false;
3384
+ }
3385
+ }
3386
+
3387
+ // src/session-image-uploader.ts
3388
+ import { createHash as createHash7 } from "node:crypto";
3389
+ import sharp2 from "sharp";
3390
+ import { z as z6 } from "zod";
3391
+ var MAX_ACTIVE_UPLOADS = 3;
3392
+ var MAX_JSON_BYTES = 16 * 1024;
3393
+ var REQUEST_TIMEOUT_MS = 2e4;
3394
+ var UPLOAD_TIMEOUT_MS = 7e4;
3395
+ var MAX_GRANT_LIFETIME_MS = 10 * 6e4 + 3e4;
3396
+ var UploaderConfigSchema = z6.object({
3397
+ relayUrl: SessionImageRelayUrlSchema,
3398
+ relayToken: z6.string().min(24).max(4096).regex(/^[\x21-\x7e]+$/u),
3399
+ userId: IdSchema.refine((value) => /^[\x20-\x7e]+$/u.test(value)),
3400
+ machineId: IdSchema,
3401
+ storageAccount: SessionImagesConfigSchema.shape.storageAccount,
3402
+ storageContainer: SessionImagesConfigSchema.shape.storageContainer
3403
+ });
3404
+ function safeCause(error) {
3405
+ if (error instanceof SessionImageUploadError) return error;
3406
+ if (error instanceof z6.ZodError) return new Error("Schema validation failed.");
3407
+ const result = new Error("Image operation failed.");
3408
+ if (error instanceof Error) {
3409
+ if (["TypeError", "SyntaxError", "AbortError", "TimeoutError"].includes(error.name)) result.name = error.name;
3410
+ const code = "code" in error ? error.code : void 0;
3411
+ if (typeof code === "string" && ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN", "UND_ERR_CONNECT_TIMEOUT"].includes(code)) {
3412
+ result.cause = { code };
3413
+ }
3414
+ }
3415
+ return result;
3416
+ }
3417
+ var SessionImageUploadError = class extends Error {
3418
+ constructor(code, stage, status, cause) {
3419
+ super(`${code} (${stage})`, cause === void 0 ? void 0 : { cause: safeCause(cause) });
3420
+ this.code = code;
3421
+ this.stage = stage;
3422
+ this.status = status;
3423
+ this.name = "SessionImageUploadError";
3424
+ }
3425
+ code;
3426
+ stage;
3427
+ status;
3428
+ };
3429
+ function digest(bytes) {
3430
+ return createHash7("sha256").update(bytes).digest("hex");
3431
+ }
3432
+ function assert(condition, code, stage) {
3433
+ if (!condition) throw new SessionImageUploadError(code, stage);
3434
+ }
3435
+ function parse(schema, input, stage) {
3436
+ const result = schema.safeParse(input);
3437
+ if (!result.success) throw new SessionImageUploadError(
3438
+ stage === "config" ? "IMAGE_CONFIG_INVALID" : stage === "source" ? "IMAGE_CONTENT_INVALID" : "IMAGE_RESPONSE_INVALID",
3439
+ stage,
3440
+ void 0,
3441
+ result.error
3442
+ );
3443
+ return result.data;
3444
+ }
3445
+ function abortable(operation, signal, stage) {
3446
+ return new Promise((resolve5, reject) => {
3447
+ const abort = () => reject(new SessionImageUploadError("IMAGE_ABORTED", stage));
3448
+ if (signal.aborted) abort();
3449
+ else signal.addEventListener("abort", abort, { once: true });
3450
+ void operation.then((value) => {
3451
+ signal.removeEventListener("abort", abort);
3452
+ resolve5(value);
3453
+ }, (error) => {
3454
+ signal.removeEventListener("abort", abort);
3455
+ reject(error);
3456
+ });
3457
+ });
3458
+ }
3459
+ var SessionImageUploader = class {
3460
+ #config;
3461
+ #fetch;
3462
+ #active = /* @__PURE__ */ new Set();
3463
+ #closed = false;
3464
+ constructor(config, dependencies = {}) {
3465
+ this.#config = parse(UploaderConfigSchema, config, "config");
3466
+ this.#fetch = dependencies.fetch ?? globalThis.fetch;
3467
+ }
3468
+ close() {
3469
+ this.#closed = true;
3470
+ for (const controller of this.#active) controller.abort();
3471
+ }
3472
+ async upload(sessionId, source, signal, assertCurrent = () => void 0) {
3473
+ assert(!this.#closed, "IMAGE_UPLOADER_CLOSED", "source");
3474
+ assert(!signal?.aborted, "IMAGE_ABORTED", "source");
3475
+ const revalidate = (stage) => {
3476
+ try {
3477
+ assertCurrent();
3478
+ } catch (error) {
3479
+ throw new SessionImageUploadError("IMAGE_ACCESS_REVOKED", stage, void 0, error);
3480
+ }
3481
+ };
3482
+ revalidate("source");
3483
+ assert(this.#active.size < MAX_ACTIVE_UPLOADS, "IMAGE_CAPACITY_EXCEEDED", "source");
3484
+ assert(
3485
+ Buffer.isBuffer(source) && source.length > 0 && source.length <= SESSION_IMAGE_MAX_BYTES,
3486
+ "IMAGE_CONTENT_INVALID",
3487
+ "source"
3488
+ );
3489
+ const scope = parse(SessionImageScopeSchema, { machineId: this.#config.machineId, sessionId }, "source");
3490
+ const controller = new AbortController();
3491
+ this.#active.add(controller);
3492
+ const abort = () => controller.abort();
3493
+ signal?.addEventListener("abort", abort, { once: true });
3494
+ let timedOut = false;
3495
+ const timer = setTimeout(() => {
3496
+ timedOut = true;
3497
+ controller.abort();
3498
+ }, UPLOAD_TIMEOUT_MS);
3499
+ timer.unref();
3500
+ try {
3501
+ const bytes = Buffer.from(source);
3502
+ const dimensions = await this.#dimensions(bytes);
3503
+ const hash2 = digest(bytes);
3504
+ const request = parse(SessionImageUploadRequestSchema, { ...scope, sizeBytes: bytes.length }, "source");
3505
+ revalidate("reserve");
3506
+ const upload = await this.#json(
3507
+ `${SESSION_IMAGE_API_PATH}/uploads`,
3508
+ request,
3509
+ 201,
3510
+ SessionImageUploadResultSchema,
3511
+ "reserve",
3512
+ controller.signal
3513
+ );
3514
+ const target = this.#target(upload, scope.sessionId);
3515
+ let ambiguous;
3516
+ revalidate("put");
3517
+ try {
3518
+ await this.#request(target, {
3519
+ method: "PUT",
3520
+ headers: upload.mode === "direct" ? upload.headers : { ...this.#headers(), ...upload.headers },
3521
+ body: new Uint8Array(bytes)
3522
+ }, "put", controller.signal, async (response) => {
3523
+ if (response.status !== 201) throw new SessionImageUploadError("IMAGE_HTTP_ERROR", "put", response.status);
3524
+ });
3525
+ } catch (error) {
3526
+ if (!(error instanceof SessionImageUploadError) || controller.signal.aborted || !(error.code === "IMAGE_NETWORK_ERROR" || error.code === "IMAGE_TIMEOUT" || error.code === "IMAGE_HTTP_ERROR" && [409, 412].includes(error.status ?? 0))) throw error;
3527
+ ambiguous = error;
3528
+ }
3529
+ revalidate("complete");
3530
+ try {
3531
+ const committed = await this.#json(
3532
+ `${SESSION_IMAGE_API_PATH}/${upload.imageId}/complete`,
3533
+ scope,
3534
+ 200,
3535
+ SessionImageCommitResultSchema,
3536
+ "complete",
3537
+ controller.signal
3538
+ );
3539
+ assert(
3540
+ committed.imageId === upload.imageId && committed.machineId === scope.machineId && committed.sessionId === scope.sessionId && committed.sha256 === hash2 && committed.sizeBytes === bytes.length && committed.width === dimensions.width && committed.height === dimensions.height && Number.isSafeInteger(committed.width) && Number.isSafeInteger(committed.height) && committed.width * committed.height <= SESSION_IMAGE_MAX_PIXELS && Number.isSafeInteger(committed.expiresAt) && committed.expiresAt > Date.now(),
3541
+ "IMAGE_COMMIT_MISMATCH",
3542
+ "complete"
3543
+ );
3544
+ revalidate("complete");
3545
+ return committed;
3546
+ } catch (error) {
3547
+ if (ambiguous === void 0 || error instanceof SessionImageUploadError && error.code === "IMAGE_ACCESS_REVOKED") {
3548
+ throw error;
3549
+ }
3550
+ const failure = new SessionImageUploadError("IMAGE_COMPLETION_FAILED", "complete", void 0, error);
3551
+ failure.cause = new AggregateError([ambiguous, safeCause(error)], "PUT outcome and completion failed.");
3552
+ throw failure;
3553
+ }
3554
+ } catch (error) {
3555
+ if (controller.signal.aborted) {
3556
+ throw new SessionImageUploadError(
3557
+ timedOut ? "IMAGE_TIMEOUT" : "IMAGE_ABORTED",
3558
+ error instanceof SessionImageUploadError ? error.stage : "source",
3559
+ void 0,
3560
+ error
3561
+ );
3562
+ }
3563
+ if (error instanceof SessionImageUploadError) throw error;
3564
+ throw new SessionImageUploadError("IMAGE_CONTENT_INVALID", "source", void 0, error);
3565
+ } finally {
3566
+ clearTimeout(timer);
3567
+ signal?.removeEventListener("abort", abort);
3568
+ this.#active.delete(controller);
3569
+ }
3570
+ }
3571
+ async #dimensions(bytes) {
3572
+ assert(
3573
+ bytes.length >= 20 && bytes.toString("ascii", 0, 4) === "RIFF" && bytes.toString("ascii", 8, 12) === "WEBP" && bytes.readUInt32LE(4) + 8 === bytes.length,
3574
+ "IMAGE_CONTENT_INVALID",
3575
+ "source"
3576
+ );
3577
+ const decoder = sharp2(bytes, { limitInputPixels: SESSION_IMAGE_MAX_PIXELS, failOn: "warning" });
3578
+ try {
3579
+ const metadata = await decoder.metadata();
3580
+ assert(metadata.format === "webp" && metadata.width !== void 0 && metadata.height !== void 0 && metadata.width > 0 && metadata.height > 0 && (metadata.pages ?? 1) === 1 && metadata.width * metadata.height <= SESSION_IMAGE_MAX_PIXELS, "IMAGE_CONTENT_INVALID", "source");
3581
+ return { width: metadata.width, height: metadata.height };
3582
+ } finally {
3583
+ decoder.destroy();
3584
+ }
3585
+ }
3586
+ #headers() {
3587
+ return { authorization: `Bearer ${this.#config.relayToken}`, "x-agentknight-user-id": this.#config.userId };
3588
+ }
3589
+ #target(upload, sessionId) {
3590
+ const now = Date.now();
3591
+ assert(Number.isSafeInteger(upload.uploadExpiresAt) && upload.uploadExpiresAt > now && upload.uploadExpiresAt <= now + MAX_GRANT_LIFETIME_MS, "IMAGE_UPLOAD_EXPIRED", "reserve");
3592
+ assert(
3593
+ upload.headers["content-type"] === "image/webp" && (upload.headers["if-none-match"] === void 0 || upload.headers["if-none-match"] === "*"),
3594
+ "IMAGE_UPLOAD_TARGET_INVALID",
3595
+ "reserve"
3596
+ );
3597
+ if (upload.mode === "proxy") {
3598
+ const path2 = `${SESSION_IMAGE_API_PATH}/${upload.imageId}/content`;
3599
+ const url2 = new URL(upload.uploadUrl, this.#config.relayUrl);
3600
+ assert(
3601
+ upload.uploadUrl.split("?")[0] === path2 && url2.origin === this.#config.relayUrl && [...url2.searchParams.keys()].length === 2 && url2.searchParams.getAll("machineId").length === 1 && url2.searchParams.get("machineId") === this.#config.machineId && url2.searchParams.getAll("sessionId").length === 1 && url2.searchParams.get("sessionId") === sessionId && upload.headers["x-ms-blob-type"] === void 0,
3602
+ "IMAGE_UPLOAD_TARGET_INVALID",
3603
+ "reserve"
3604
+ );
3605
+ return url2.href;
3606
+ }
3607
+ const { storageAccount, storageContainer, userId, machineId } = this.#config;
3608
+ const host = `${storageAccount}.blob.core.windows.net`;
3609
+ const path = `/${storageContainer}/uploads/${digest(userId)}/${digest(machineId)}/${upload.imageId}.webp`;
3610
+ const url = new URL(upload.uploadUrl);
3611
+ const query = url.searchParams;
3612
+ const allowed = /* @__PURE__ */ new Set(["sv", "spr", "st", "se", "sr", "sp", "sig", "skoid", "sktid", "skt", "ske", "sks", "skv"]);
3613
+ assert(
3614
+ upload.uploadUrl.startsWith(`https://${host}${path}?`) && url.host === host && url.pathname === path && upload.headers["x-ms-blob-type"] === "BlockBlob" && [...query.keys()].every((key) => allowed.has(key) && query.getAll(key).length === 1) && [...allowed].every((key) => (query.get(key)?.length ?? 0) > 0) && query.get("sp") === "c" && query.get("sr") === "b" && query.get("spr") === "https" && query.get("sks") === "b",
3615
+ "IMAGE_UPLOAD_TARGET_INVALID",
3616
+ "reserve"
3617
+ );
3618
+ const expiresAt = Date.parse(query.get("se") ?? "");
3619
+ const startsAt = Date.parse(query.get("st") ?? "");
3620
+ const keyStartsAt = Date.parse(query.get("skt") ?? "");
3621
+ const keyExpiresAt = Date.parse(query.get("ske") ?? "");
3622
+ assert(
3623
+ Number.isFinite(startsAt) && startsAt <= now + 3e4 && Number.isFinite(keyStartsAt) && keyStartsAt <= now + 3e4 && Number.isFinite(expiresAt) && expiresAt > now && expiresAt <= upload.uploadExpiresAt && Number.isFinite(keyExpiresAt) && keyExpiresAt >= expiresAt,
3624
+ "IMAGE_UPLOAD_TARGET_INVALID",
3625
+ "reserve"
3626
+ );
3627
+ return url.href;
3628
+ }
3629
+ async #json(path, body, status, schema, stage, signal) {
3630
+ return this.#request(`${this.#config.relayUrl}${path}`, {
3631
+ method: "POST",
3632
+ headers: { ...this.#headers(), "content-type": "application/json" },
3633
+ body: JSON.stringify(body)
3634
+ }, stage, signal, async (response, requestSignal) => {
3635
+ if (response.status !== status) throw new SessionImageUploadError("IMAGE_HTTP_ERROR", stage, response.status);
3636
+ const contentLength = response.headers.get("content-length");
3637
+ assert(
3638
+ contentLength === null || /^\d+$/u.test(contentLength) && Number(contentLength) <= MAX_JSON_BYTES,
3639
+ "IMAGE_RESPONSE_TOO_LARGE",
3640
+ stage
3641
+ );
3642
+ assert(response.body !== null, "IMAGE_RESPONSE_INVALID", stage);
3643
+ const reader = response.body.getReader();
3644
+ const chunks = [];
3645
+ let size = 0;
3646
+ try {
3647
+ while (true) {
3648
+ const next = await abortable(reader.read(), requestSignal, stage);
3649
+ requestSignal.throwIfAborted();
3650
+ if (next.done) break;
3651
+ size += next.value.byteLength;
3652
+ assert(size <= MAX_JSON_BYTES, "IMAGE_RESPONSE_TOO_LARGE", stage);
3653
+ chunks.push(next.value);
3654
+ }
3655
+ let value;
3656
+ try {
3657
+ value = JSON.parse(Buffer.concat(chunks, size).toString("utf8"));
3658
+ } catch (error) {
3659
+ throw new SessionImageUploadError("IMAGE_RESPONSE_INVALID", stage, void 0, error);
3660
+ }
3661
+ return parse(schema, value, stage);
3662
+ } finally {
3663
+ reader.releaseLock();
3664
+ }
3665
+ });
3666
+ }
3667
+ async #request(url, init, stage, signal, consume) {
3668
+ assert(!signal.aborted, "IMAGE_ABORTED", stage);
3669
+ const controller = new AbortController();
3670
+ const abort = () => controller.abort();
3671
+ signal.addEventListener("abort", abort, { once: true });
3672
+ let timedOut = false;
3673
+ const timer = setTimeout(() => {
3674
+ timedOut = true;
3675
+ controller.abort();
3676
+ }, REQUEST_TIMEOUT_MS);
3677
+ timer.unref();
3678
+ try {
3679
+ const response = await this.#fetch(url, {
3680
+ ...init,
3681
+ signal: controller.signal,
3682
+ redirect: "error",
3683
+ credentials: "omit",
3684
+ cache: "no-store",
3685
+ referrerPolicy: "no-referrer"
3686
+ });
3687
+ try {
3688
+ controller.signal.throwIfAborted();
3689
+ assert(!response.redirected, "IMAGE_RESPONSE_INVALID", stage);
3690
+ return await consume(response, controller.signal);
3691
+ } finally {
3692
+ await response.body?.cancel();
3693
+ }
3694
+ } catch (error) {
3695
+ if (controller.signal.aborted) {
3696
+ throw new SessionImageUploadError(timedOut ? "IMAGE_TIMEOUT" : "IMAGE_ABORTED", stage, void 0, error);
3697
+ }
3698
+ if (error instanceof SessionImageUploadError) throw error;
3699
+ throw new SessionImageUploadError("IMAGE_NETWORK_ERROR", stage, void 0, error);
3700
+ } finally {
3701
+ clearTimeout(timer);
3702
+ signal.removeEventListener("abort", abort);
3703
+ }
3704
+ }
3705
+ };
3706
+
3100
3707
  // src/service/requests/request-errors.ts
3101
3708
  function operationErrorCode(agentId, operation, error) {
3102
3709
  if (isSessionRestoreFailure(error)) return "SESSION_RESTORE_FAILED";
@@ -3113,7 +3720,7 @@ function unsupportedOperation(error) {
3113
3720
  // src/service/requests/communication-operations.ts
3114
3721
  var compress2 = promisify2(gzip3);
3115
3722
  function createCommunicationOperations(options) {
3116
- const { adapter, publications } = options;
3723
+ const { adapter, publications, imageBlob } = options;
3117
3724
  const directoryLister = new SessionDirectoryLister(async (sessionId) => adapter.getSessionDirectoryMetadata?.(sessionId));
3118
3725
  const fileReader = new SessionFileReader(async (sessionId) => adapter.getSessionDirectoryMetadata?.(sessionId));
3119
3726
  const gitDiffReader = new SessionGitDiffReader(async (sessionId) => adapter.getSessionDirectoryMetadata?.(sessionId), options.gitDiffDirectory);
@@ -3122,6 +3729,24 @@ function createCommunicationOperations(options) {
3122
3729
  const renameSession = adapter.renameSession?.bind(adapter);
3123
3730
  return [
3124
3731
  ...options.workbenchOperations,
3732
+ ...imageBlob === void 0 ? [] : [
3733
+ sessionCommunicationOperation({
3734
+ operation: TIMELINE_IMAGE_BLOB_OPERATION,
3735
+ version: TIMELINE_IMAGE_BLOB_VERSION,
3736
+ schema: TimelineImageBlobRequestSchema,
3737
+ handle: async ({ sha256 }, request) => {
3738
+ const assertCurrent = options.captureRequestAccess();
3739
+ assertCurrent();
3740
+ const result = await imageBlob.publish(request.sessionId, sha256, assertCurrent);
3741
+ assertCurrent();
3742
+ return SessionCommunicationDataSchema.parse(result);
3743
+ },
3744
+ onError: (error) => ({
3745
+ code: error instanceof TimelineAssetError || error instanceof SessionImageUploadError || error instanceof SessionNotSharedError || error instanceof AgentSessionRenameError ? error.code : "IMAGE_BLOB_FAILED",
3746
+ message: "The cloud image is unavailable"
3747
+ })
3748
+ })
3749
+ ],
3125
3750
  ...renameSession === void 0 ? [] : [
3126
3751
  sessionCommunicationOperation({
3127
3752
  operation: SESSION_RENAME_OPERATION,
@@ -3198,16 +3823,16 @@ function createCommunicationOperations(options) {
3198
3823
  }),
3199
3824
  onError: (error) => unsupportedOperation(error) ?? (error instanceof SessionDirectoryError ? { code: error.code, message: error.message } : { code: "DIRECTORY_UNAVAILABLE", message: "The directory is unavailable" })
3200
3825
  }),
3201
- sessionCommunicationOperation({
3826
+ ...[SESSION_FILE_READ_VERSION, SESSION_FILE_READ_V2_VERSION].map((version) => sessionCommunicationOperation({
3202
3827
  operation: SESSION_FILE_READ_OPERATION,
3203
- version: SESSION_FILE_READ_VERSION,
3828
+ version,
3204
3829
  schema: SessionFileReadDataSchema,
3205
- handle: ({ path, rootRevision }, request) => fileReader.read(request.sessionId, path, rootRevision),
3830
+ handle: ({ path, rootRevision }, request) => fileReader.read(request.sessionId, path, rootRevision, version),
3206
3831
  onError: (error) => unsupportedOperation(error) ?? {
3207
3832
  code: error instanceof SessionFileReadError ? error.code : "FILE_UNAVAILABLE",
3208
3833
  message: "The file preview is unavailable"
3209
3834
  }
3210
- }),
3835
+ })),
3211
3836
  sessionCommunicationOperation({
3212
3837
  operation: SESSION_RESOURCE_READ_OPERATION,
3213
3838
  version: SESSION_RESOURCE_READ_VERSION,
@@ -3269,6 +3894,7 @@ var SessionCommunicationHandler = class {
3269
3894
  return assertCurrent;
3270
3895
  },
3271
3896
  incrementalOperations: options.incrementalOperations.filter((operation) => config.timelineIncrementalEnabled !== false || operation.operation === TIMELINE_IMAGE_OPERATION),
3897
+ ...options.imageBlob === void 0 ? {} : { imageBlob: options.imageBlob },
3272
3898
  workbenchOperations: this.#operations === void 0 ? [] : createWorkbenchOperations({
3273
3899
  ...workbench === void 0 ? {} : { runtime: workbench },
3274
3900
  ...researchWorkbench === void 0 ? {} : { research: researchWorkbench },
@@ -3437,7 +4063,7 @@ var SessionCommunicationHandler = class {
3437
4063
  }
3438
4064
  const clientId = scope.clientId;
3439
4065
  const owner = [config.userId, machineId, clientId, request.sessionId];
3440
- const key = (parts) => createHash6("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 24);
4066
+ const key = (parts) => createHash8("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 24);
3441
4067
  const knownSource = this.#researchParents.get(request.sessionId) ?? adapter.sessionPublicationSource?.(request.sessionId) ?? request.sessionId;
3442
4068
  const arrivalPublication = adapter.isSessionVisible?.(knownSource) === true ? publications.capture(knownSource) : void 0;
3443
4069
  let researchParent = this.#researchParents.get(request.sessionId);
@@ -3470,7 +4096,7 @@ var SessionCommunicationHandler = class {
3470
4096
  }
3471
4097
  if (researchParent !== void 0) {
3472
4098
  if (lifecycle !== void 0) lifecycle.preflightPhase = "isolation";
3473
- const unifiedSync = research?.chat !== void 0 && this.options.supportsResearchChat?.(clientId) === true && (request.operation === TIMELINE_SYNC_OPERATION || request.operation === TIMELINE_IMAGE_OPERATION);
4099
+ const unifiedSync = research?.chat !== void 0 && this.options.supportsResearchChat?.(clientId) === true && (request.operation === TIMELINE_SYNC_OPERATION || request.operation === TIMELINE_IMAGE_OPERATION || request.operation === TIMELINE_IMAGE_BLOB_OPERATION);
3474
4100
  if (!unifiedSync && !["research.timeline", "research.send", "research.abort", "research.delete", "operation.get", "insights.read"].includes(request.operation)) {
3475
4101
  reject("research-isolated");
3476
4102
  emit("session.communication", sessionCommunicationError(request, {
@@ -3617,7 +4243,7 @@ var SessionCommunicationHandler = class {
3617
4243
  };
3618
4244
 
3619
4245
  // src/desktop-workspaces.ts
3620
- import { createHash as createHash7 } from "node:crypto";
4246
+ import { createHash as createHash9 } from "node:crypto";
3621
4247
  import { readdir, realpath as realpath5, stat as stat6 } from "node:fs/promises";
3622
4248
  import { homedir as homedir2 } from "node:os";
3623
4249
  import { basename as basename2, join as join8, normalize } from "node:path";
@@ -3633,7 +4259,7 @@ function desktopRootCandidates(environment2 = process.env) {
3633
4259
  }
3634
4260
  function workspaceId(path) {
3635
4261
  const normalizedPath = workspacePathKey(path);
3636
- return createHash7("sha256").update(normalizedPath).digest("base64url").slice(0, 32);
4262
+ return createHash9("sha256").update(normalizedPath).digest("base64url").slice(0, 32);
3637
4263
  }
3638
4264
  function workspacePathKey(path) {
3639
4265
  const normalizedPath = normalize(path);
@@ -4395,9 +5021,9 @@ var SessionCommandsHandler = class {
4395
5021
  };
4396
5022
 
4397
5023
  // src/service/requests/session-prompt.ts
4398
- import { createHash as createHash8 } from "node:crypto";
5024
+ import { createHash as createHash10 } from "node:crypto";
4399
5025
  function promptOperationFingerprint(request) {
4400
- return createHash8("sha256").update(request.sessionId).update("\0").update(request.mode).update("\0").update(request.prompt).update("\0").update(request.attachments?.map((attachment) => attachment.sha256).join(",") ?? "").update("\0").update(JSON.stringify(request.fileAttachments?.map(({ name, mimeType, sha256 }) => ({
5026
+ return createHash10("sha256").update(request.sessionId).update("\0").update(request.mode).update("\0").update(request.prompt).update("\0").update(request.attachments?.map((attachment) => attachment.sha256).join(",") ?? "").update("\0").update(JSON.stringify(request.fileAttachments?.map(({ name, mimeType, sha256 }) => ({
4401
5027
  name,
4402
5028
  mimeType,
4403
5029
  sha256
@@ -4643,8 +5269,8 @@ import { randomUUID as randomUUID5 } from "node:crypto";
4643
5269
  import { setTimeout as delay } from "node:timers/promises";
4644
5270
  import { Role, TaskState } from "@a2a-js/sdk";
4645
5271
  import { ClientFactory, DefaultAgentCardResolver, JsonRpcTransportFactory } from "@a2a-js/sdk/client";
4646
- import { z as z5 } from "zod";
4647
- var TextSchema = z5.string().max(16e3).refine((text) => text.trim().length > 0);
5272
+ import { z as z7 } from "zod";
5273
+ var TextSchema = z7.string().max(16e3).refine((text) => text.trim().length > 0);
4648
5274
  var HTTP_TIMEOUT_MS = 1e4;
4649
5275
  var MAX_RESPONSE_BYTES = 262144;
4650
5276
  function relayEndpoint(relayUrl, target) {
@@ -4803,7 +5429,7 @@ var A2aPeerClient = class {
4803
5429
  };
4804
5430
 
4805
5431
  // src/a2a-server.ts
4806
- import { createHash as createHash9, randomUUID as randomUUID6 } from "node:crypto";
5432
+ import { createHash as createHash11, randomUUID as randomUUID6 } from "node:crypto";
4807
5433
  import {
4808
5434
  AgentCard,
4809
5435
  Role as Role2,
@@ -4829,7 +5455,7 @@ import {
4829
5455
  ServerCallContext,
4830
5456
  validateVersion
4831
5457
  } from "@a2a-js/sdk/server";
4832
- import { z as z6 } from "zod";
5458
+ import { z as z8 } from "zod";
4833
5459
  var MAX_ACTIVE = 4;
4834
5460
  var MAX_TASKS = 128;
4835
5461
  var MAX_RECEIPTS = 256;
@@ -4838,52 +5464,52 @@ var TURN_TIMEOUT_MS = 12e4;
4838
5464
  var SHORT_REQUEST_TIMEOUT_MS = 9e3;
4839
5465
  var MAX_TEXT2 = 16e3;
4840
5466
  var TASK_WIRE_BUDGET = 6e4;
4841
- var idSchema = z6.string().min(1).max(256).regex(/^[^\s\u0000-\u001f\u007f]+$/u);
4842
- var optionalIdSchema = z6.union([z6.literal(""), idSchema]).optional();
4843
- var historySchema = z6.number().int().min(0).max(128).optional();
4844
- var emptyMetadataSchema = z6.object({}).strict().optional();
4845
- var tenantSchema = z6.literal("").optional();
4846
- var rpcSchema = z6.object({
4847
- jsonrpc: z6.literal("2.0"),
4848
- id: z6.union([idSchema, z6.number().int().safe(), z6.null()]),
4849
- method: z6.string().min(1).max(128),
4850
- params: z6.unknown()
5467
+ var idSchema = z8.string().min(1).max(256).regex(/^[^\s\u0000-\u001f\u007f]+$/u);
5468
+ var optionalIdSchema = z8.union([z8.literal(""), idSchema]).optional();
5469
+ var historySchema = z8.number().int().min(0).max(128).optional();
5470
+ var emptyMetadataSchema = z8.object({}).strict().optional();
5471
+ var tenantSchema = z8.literal("").optional();
5472
+ var rpcSchema = z8.object({
5473
+ jsonrpc: z8.literal("2.0"),
5474
+ id: z8.union([idSchema, z8.number().int().safe(), z8.null()]),
5475
+ method: z8.string().min(1).max(128),
5476
+ params: z8.unknown()
4851
5477
  }).strict();
4852
- var sendSchema = z6.object({
5478
+ var sendSchema = z8.object({
4853
5479
  tenant: tenantSchema,
4854
- message: z6.object({
5480
+ message: z8.object({
4855
5481
  messageId: idSchema,
4856
5482
  contextId: optionalIdSchema,
4857
5483
  taskId: optionalIdSchema,
4858
- role: z6.union([z6.literal("ROLE_USER"), z6.literal(Role2.ROLE_USER)]),
4859
- parts: z6.array(z6.object({
4860
- text: z6.string().max(MAX_TEXT2),
4861
- mediaType: z6.union([z6.literal(""), z6.literal("text/plain")]).optional(),
4862
- filename: z6.literal("").optional(),
5484
+ role: z8.union([z8.literal("ROLE_USER"), z8.literal(Role2.ROLE_USER)]),
5485
+ parts: z8.array(z8.object({
5486
+ text: z8.string().max(MAX_TEXT2),
5487
+ mediaType: z8.union([z8.literal(""), z8.literal("text/plain")]).optional(),
5488
+ filename: z8.literal("").optional(),
4863
5489
  metadata: emptyMetadataSchema
4864
5490
  }).strict()).min(1).max(32),
4865
5491
  metadata: emptyMetadataSchema,
4866
- extensions: z6.array(z6.never()).optional(),
4867
- referenceTaskIds: z6.array(z6.never()).optional()
5492
+ extensions: z8.array(z8.never()).optional(),
5493
+ referenceTaskIds: z8.array(z8.never()).optional()
4868
5494
  }).strict().refine((message) => {
4869
5495
  const text = message.parts.map((part) => part.text).join("\n");
4870
5496
  return text.trim().length > 0 && text.length <= MAX_TEXT2;
4871
5497
  }, "A message must contain nonempty plain text of at most 16000 characters."),
4872
- configuration: z6.object({
4873
- acceptedOutputModes: z6.array(z6.literal("text/plain")).max(1).optional(),
4874
- returnImmediately: z6.boolean().optional(),
5498
+ configuration: z8.object({
5499
+ acceptedOutputModes: z8.array(z8.literal("text/plain")).max(1).optional(),
5500
+ returnImmediately: z8.boolean().optional(),
4875
5501
  historyLength: historySchema
4876
5502
  }).strict().optional(),
4877
5503
  metadata: emptyMetadataSchema
4878
5504
  }).strict();
4879
- var getSchema = z6.object({ tenant: tenantSchema, id: idSchema, historyLength: historySchema }).strict();
4880
- var cancelSchema = z6.object({ tenant: tenantSchema, id: idSchema, metadata: emptyMetadataSchema }).strict();
4881
- var listSchema2 = z6.object({
5505
+ var getSchema = z8.object({ tenant: tenantSchema, id: idSchema, historyLength: historySchema }).strict();
5506
+ var cancelSchema = z8.object({ tenant: tenantSchema, id: idSchema, metadata: emptyMetadataSchema }).strict();
5507
+ var listSchema2 = z8.object({
4882
5508
  tenant: tenantSchema,
4883
5509
  contextId: optionalIdSchema,
4884
- status: z6.union([
4885
- z6.number().int().min(0).max(8),
4886
- z6.enum([
5510
+ status: z8.union([
5511
+ z8.number().int().min(0).max(8),
5512
+ z8.enum([
4887
5513
  "TASK_STATE_UNSPECIFIED",
4888
5514
  "TASK_STATE_SUBMITTED",
4889
5515
  "TASK_STATE_WORKING",
@@ -4895,11 +5521,11 @@ var listSchema2 = z6.object({
4895
5521
  "TASK_STATE_AUTH_REQUIRED"
4896
5522
  ])
4897
5523
  ]).optional(),
4898
- pageSize: z6.number().int().min(1).max(100).optional(),
4899
- pageToken: z6.string().regex(/^(?:|0|[1-9]\d{0,3})$/u).optional(),
5524
+ pageSize: z8.number().int().min(1).max(100).optional(),
5525
+ pageToken: z8.string().regex(/^(?:|0|[1-9]\d{0,3})$/u).optional(),
4900
5526
  historyLength: historySchema,
4901
- statusTimestampAfter: z6.iso.datetime({ offset: true }).optional(),
4902
- includeArtifacts: z6.boolean().optional()
5527
+ statusTimestampAfter: z8.iso.datetime({ offset: true }).optional(),
5528
+ includeArtifacts: z8.boolean().optional()
4903
5529
  }).strict();
4904
5530
  function deferred() {
4905
5531
  let resolve5 = () => {
@@ -5105,7 +5731,7 @@ var A2aPeerServer = class {
5105
5731
  if (message === void 0) throw new RequestMalformedError("A message is required.");
5106
5732
  const scope = scopedOwner(context);
5107
5733
  const key = JSON.stringify([scope, message.messageId]);
5108
- const fingerprint = createHash9("sha256").update(JSON.stringify(SendMessageRequest.toJSON(params))).digest("hex");
5734
+ const fingerprint = createHash11("sha256").update(JSON.stringify(SendMessageRequest.toJSON(params))).digest("hex");
5109
5735
  const previous = this.#receipts.get(key);
5110
5736
  if (previous !== void 0) {
5111
5737
  if (previous.fingerprint !== fingerprint) throw new RequestMalformedError("messageId was already used with different content.");
@@ -5980,7 +6606,7 @@ var SessionInventory = class {
5980
6606
  };
5981
6607
 
5982
6608
  // src/snapshot-audience.ts
5983
- import { createHash as createHash10 } from "node:crypto";
6609
+ import { createHash as createHash12 } from "node:crypto";
5984
6610
  var SnapshotAudience = class {
5985
6611
  constructor(send) {
5986
6612
  this.send = send;
@@ -6009,9 +6635,9 @@ var SnapshotAudience = class {
6009
6635
  async publish(snapshot, recipient) {
6010
6636
  const generation = this.#generation;
6011
6637
  const { sequence: _sequence, generatedAt: _generatedAt, ...content } = snapshot;
6012
- const digest = createHash10("sha256").update(JSON.stringify(content)).digest("base64url");
6638
+ const digest2 = createHash12("sha256").update(JSON.stringify(content)).digest("base64url");
6013
6639
  if (recipient !== void 0) {
6014
- await this.#direct(snapshot, recipient, digest, generation);
6640
+ await this.#direct(snapshot, recipient, digest2, generation);
6015
6641
  return;
6016
6642
  }
6017
6643
  if (!this.#negotiated || this.#registrationBaseline) {
@@ -6023,10 +6649,10 @@ var SnapshotAudience = class {
6023
6649
  }
6024
6650
  const pending = [];
6025
6651
  for (const [clientId, baseline] of this.#audiences) {
6026
- if (baseline.digest === digest) {
6652
+ if (baseline.digest === digest2) {
6027
6653
  this.#metrics.unchangedRecipients += 1;
6028
6654
  } else {
6029
- pending.push(this.#direct(snapshot, clientId, digest, generation));
6655
+ pending.push(this.#direct(snapshot, clientId, digest2, generation));
6030
6656
  }
6031
6657
  }
6032
6658
  if (this.#audiences.size === 0) this.#metrics.withoutRecipients += 1;
@@ -6039,12 +6665,12 @@ var SnapshotAudience = class {
6039
6665
  this.#metrics = this.#emptyMetrics();
6040
6666
  return metrics;
6041
6667
  }
6042
- async #direct(snapshot, recipient, digest, generation) {
6668
+ async #direct(snapshot, recipient, digest2, generation) {
6043
6669
  const baseline = this.#audiences.get(recipient);
6044
6670
  this.#metrics.directedAttempts += 1;
6045
6671
  await this.send(snapshot, recipient);
6046
6672
  if (generation === this.#generation && baseline !== void 0 && this.#audiences.get(recipient) === baseline) {
6047
- baseline.digest = digest;
6673
+ baseline.digest = digest2;
6048
6674
  }
6049
6675
  }
6050
6676
  #emptyMetrics() {
@@ -6141,106 +6767,8 @@ var ConversationAudience = class {
6141
6767
  };
6142
6768
 
6143
6769
  // src/timeline-incremental-transport.ts
6144
- import { createHash as createHash12, randomUUID as randomUUID8 } from "node:crypto";
6770
+ import { createHash as createHash13, randomUUID as randomUUID8 } from "node:crypto";
6145
6771
  import { isDeepStrictEqual } from "node:util";
6146
-
6147
- // src/timeline-image-cache.ts
6148
- import { createHash as createHash11 } from "node:crypto";
6149
- var TimelineAssetError = class extends Error {
6150
- constructor(code, message) {
6151
- super(message);
6152
- this.code = code;
6153
- }
6154
- code;
6155
- };
6156
- var TimelineImageCache = class {
6157
- constructor(maxBytes = 32 * 1024 * 1024) {
6158
- this.maxBytes = maxBytes;
6159
- if (!Number.isSafeInteger(maxBytes) || maxBytes < TIMELINE_IMAGE_MAX_BYTES) {
6160
- throw new Error("Image cache must hold at least one maximum-size image");
6161
- }
6162
- }
6163
- maxBytes;
6164
- #assets = /* @__PURE__ */ new Map();
6165
- #bytes = 0;
6166
- get bytes() {
6167
- return this.#bytes;
6168
- }
6169
- referenceItem(sessionId, item) {
6170
- if (!("attachments" in item) || item.attachments === void 0) return item;
6171
- return { ...item, attachments: item.attachments.map((attachment) => this.#reference(sessionId, attachment)) };
6172
- }
6173
- read(sessionId, request) {
6174
- const key = JSON.stringify([sessionId, request.sha256]);
6175
- const asset = this.#assets.get(key);
6176
- if (asset === void 0) throw new TimelineAssetError("IMAGE_ABSENT", "The image is no longer cached on this PC");
6177
- if (request.offset >= asset.bytes.length) {
6178
- throw new TimelineAssetError("IMAGE_OFFSET_INVALID", "The image chunk offset exceeds its length");
6179
- }
6180
- this.#assets.delete(key);
6181
- this.#assets.set(key, asset);
6182
- const end = Math.min(request.offset + TIMELINE_IMAGE_CHUNK_BYTES, asset.bytes.length);
6183
- return TimelineImageChunkSchema.parse({
6184
- sha256: request.sha256,
6185
- offset: request.offset,
6186
- totalBytes: asset.bytes.length,
6187
- mimeType: asset.mimeType,
6188
- data: asset.bytes.subarray(request.offset, end).toString("base64"),
6189
- eof: end === asset.bytes.length
6190
- });
6191
- }
6192
- clearSession(sessionId) {
6193
- const prefix = JSON.stringify([sessionId]).slice(0, -1) + ",";
6194
- for (const [key, asset] of this.#assets) {
6195
- if (key.startsWith(prefix)) {
6196
- this.#assets.delete(key);
6197
- this.#bytes -= asset.bytes.length;
6198
- }
6199
- }
6200
- }
6201
- clear() {
6202
- this.#assets.clear();
6203
- this.#bytes = 0;
6204
- }
6205
- #reference(sessionId, attachment) {
6206
- if (attachment.kind !== "image" || attachment.data === void 0) return attachment;
6207
- const parsed = ConversationAttachmentSchema.parse(attachment);
6208
- const bytes = Buffer.from(attachment.data, "base64");
6209
- if (bytes.length !== parsed.byteLength || createHash11("sha256").update(bytes).digest("hex") !== parsed.sha256 || !hasSignature(bytes, parsed.mimeType)) {
6210
- throw new TimelineAssetError("IMAGE_INVALID", "Image content does not match its metadata");
6211
- }
6212
- const key = JSON.stringify([sessionId, parsed.sha256]);
6213
- const previous = this.#assets.get(key);
6214
- if (previous !== void 0) this.#bytes -= previous.bytes.length;
6215
- this.#assets.delete(key);
6216
- this.#assets.set(key, { bytes, mimeType: parsed.mimeType ?? "" });
6217
- this.#bytes += bytes.length;
6218
- while (this.#bytes > this.maxBytes) {
6219
- const oldest = this.#assets.entries().next().value;
6220
- if (oldest === void 0) break;
6221
- this.#assets.delete(oldest[0]);
6222
- this.#bytes -= oldest[1].bytes.length;
6223
- }
6224
- const { data: _data, ...metadata } = parsed;
6225
- return { ...metadata, imageRef: "sha256" };
6226
- }
6227
- };
6228
- function hasSignature(bytes, mimeType) {
6229
- switch (mimeType) {
6230
- case "image/png":
6231
- return bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
6232
- case "image/jpeg":
6233
- return bytes.subarray(0, 3).equals(Buffer.from([255, 216, 255]));
6234
- case "image/gif":
6235
- return ["GIF87a", "GIF89a"].includes(bytes.subarray(0, 6).toString("ascii"));
6236
- case "image/webp":
6237
- return bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP";
6238
- default:
6239
- return false;
6240
- }
6241
- }
6242
-
6243
- // src/timeline-incremental-transport.ts
6244
6772
  function journalDiagnostic(state) {
6245
6773
  return {
6246
6774
  currentSequence: state.sequence,
@@ -6373,7 +6901,7 @@ var TimelineIncrementalTransport = class {
6373
6901
  }
6374
6902
  #observeColdStep(state, upsert) {
6375
6903
  const cold = state.coldSteps;
6376
- const key = createHash12("sha256").update(JSON.stringify([upsert.activityId, upsert.step.id])).digest("hex");
6904
+ const key = createHash13("sha256").update(JSON.stringify([upsert.activityId, upsert.step.id])).digest("hex");
6377
6905
  const revision = cold.revisions.get(key);
6378
6906
  if (revision !== void 0 && upsert.revision < revision) return false;
6379
6907
  cold.revisions.delete(key);
@@ -6383,7 +6911,7 @@ var TimelineIncrementalTransport = class {
6383
6911
  if (oldest === void 0) break;
6384
6912
  cold.revisions.delete(oldest);
6385
6913
  }
6386
- const fingerprint = createHash12("sha256").update(JSON.stringify(upsert)).digest("hex");
6914
+ const fingerprint = createHash13("sha256").update(JSON.stringify(upsert)).digest("hex");
6387
6915
  const observed = cold.fingerprints.has(fingerprint);
6388
6916
  cold.fingerprints.delete(fingerprint);
6389
6917
  cold.fingerprints.add(fingerprint);
@@ -6664,6 +7192,104 @@ function canAppend(previous, item) {
6664
7192
  return isDeepStrictEqual(oldMetadata, newMetadata);
6665
7193
  }
6666
7194
 
7195
+ // src/timeline-image-blob.ts
7196
+ import { createHash as createHash14 } from "node:crypto";
7197
+ import sharp3 from "sharp";
7198
+ var TimelineImageBlobPublisher = class {
7199
+ constructor(images, uploader) {
7200
+ this.images = images;
7201
+ this.uploader = uploader;
7202
+ }
7203
+ images;
7204
+ uploader;
7205
+ #references = /* @__PURE__ */ new Map();
7206
+ #flights = /* @__PURE__ */ new Map();
7207
+ #closed = false;
7208
+ async publish(sessionId, sha256, assertCurrent) {
7209
+ TimelineImageBlobRequestSchema.parse({ sha256 });
7210
+ assertCurrent();
7211
+ if (this.#closed) throw new TimelineAssetError("IMAGE_BLOB_CLOSED", "The image publisher is closed");
7212
+ const key = JSON.stringify([sessionId, sha256]);
7213
+ const cached = this.#references.get(key);
7214
+ if (cached !== void 0) {
7215
+ this.#references.delete(key);
7216
+ if (cached.image.expiresAt > Date.now() + 3e4) {
7217
+ this.#references.set(key, cached);
7218
+ return cached;
7219
+ }
7220
+ }
7221
+ let flight = this.#flights.get(key);
7222
+ if (flight === void 0) {
7223
+ if (this.#flights.size >= 3) throw new TimelineAssetError("IMAGE_BLOB_BUSY", "Too many image uploads");
7224
+ const controller = new AbortController();
7225
+ const check = () => {
7226
+ controller.signal.throwIfAborted();
7227
+ assertCurrent();
7228
+ };
7229
+ const promise = this.#publish(sessionId, sha256, controller.signal, check).then((result2) => {
7230
+ check();
7231
+ this.#references.set(key, result2);
7232
+ while (this.#references.size > 256) {
7233
+ const oldest = this.#references.keys().next().value;
7234
+ if (oldest === void 0) break;
7235
+ this.#references.delete(oldest);
7236
+ }
7237
+ return result2;
7238
+ }).finally(() => {
7239
+ this.#flights.delete(key);
7240
+ });
7241
+ flight = { sessionId, controller, promise };
7242
+ this.#flights.set(key, flight);
7243
+ }
7244
+ const result = await flight.promise;
7245
+ assertCurrent();
7246
+ return result;
7247
+ }
7248
+ clearSession(sessionId) {
7249
+ for (const [key, reference] of this.#references) {
7250
+ if (reference.image.sessionId === sessionId) this.#references.delete(key);
7251
+ }
7252
+ for (const flight of this.#flights.values()) {
7253
+ if (flight.sessionId === sessionId) flight.controller.abort();
7254
+ }
7255
+ }
7256
+ reset() {
7257
+ this.#references.clear();
7258
+ for (const flight of this.#flights.values()) flight.controller.abort();
7259
+ }
7260
+ async close() {
7261
+ this.#closed = true;
7262
+ this.reset();
7263
+ await Promise.allSettled([...this.#flights.values()].map((flight) => flight.promise));
7264
+ }
7265
+ async #publish(sessionId, sha256, signal, check) {
7266
+ const asset = this.images.readAsset(sessionId, sha256);
7267
+ const extension = { "image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp" }[asset.mimeType];
7268
+ if (extension === void 0) throw new TimelineAssetError("IMAGE_BLOB_UNSUPPORTED", "This image requires encrypted transfer");
7269
+ if (createHash14("sha256").update(asset.bytes).digest("hex") !== sha256) {
7270
+ throw new TimelineAssetError("IMAGE_INVALID", "Image content does not match its reference");
7271
+ }
7272
+ let bytes;
7273
+ try {
7274
+ await validateRawImage(asset.bytes, extension, check);
7275
+ bytes = extension === ".webp" ? asset.bytes : await sharp3(asset.bytes, { limitInputPixels: SESSION_IMAGE_MAX_PIXELS, failOn: "warning" }).rotate().webp({ lossless: true, effort: 3 }).timeout({ seconds: 10 }).toBuffer();
7276
+ } catch (error) {
7277
+ check();
7278
+ if (error instanceof SessionFileReadError) {
7279
+ throw new TimelineAssetError("IMAGE_BLOB_UNSUPPORTED", "The image cannot be safely stored as WebP", { cause: error });
7280
+ }
7281
+ throw new TimelineAssetError("IMAGE_BLOB_ENCODING_FAILED", "Unable to encode the image for storage", { cause: error });
7282
+ }
7283
+ check();
7284
+ if (bytes.length > SESSION_IMAGE_MAX_BYTES) {
7285
+ throw new TimelineAssetError("IMAGE_BLOB_TOO_LARGE", "The lossless WebP exceeds the image storage limit");
7286
+ }
7287
+ const image = await this.uploader.upload(sessionId, bytes, signal, check);
7288
+ check();
7289
+ return TimelineImageBlobResultSchema.parse({ sha256, image });
7290
+ }
7291
+ };
7292
+
6667
7293
  // src/secure-traffic-metrics.ts
6668
7294
  var SecureTrafficMetrics = class {
6669
7295
  #samples = /* @__PURE__ */ new Map();
@@ -7208,6 +7834,14 @@ var PcAgent = class {
7208
7834
  this.#timelineIncremental = new TimelineIncrementalTransport({
7209
7835
  readHead: (sessionId) => this.adapter.readSessionTimelinePage(sessionId, { pageSize: 100 })
7210
7836
  });
7837
+ const imageSettings = config.sessionImages === void 0 ? void 0 : SessionImagesConfigSchema.parse(config.sessionImages);
7838
+ this.#imageBlob = imageSettings === void 0 ? void 0 : new TimelineImageBlobPublisher(this.#timelineIncremental.images, new SessionImageUploader({
7839
+ relayUrl: config.relayUrl,
7840
+ relayToken: config.relayToken,
7841
+ userId: config.userId,
7842
+ machineId: identity2.machineId,
7843
+ ...imageSettings
7844
+ }));
7211
7845
  const emit = (event, payload) => this.#emitSecure(event, payload);
7212
7846
  const handlerOptions = { adapter, config, machineId: identity2.machineId, logger, emit };
7213
7847
  this.#communication = new SessionCommunicationHandler({
@@ -7218,6 +7852,7 @@ var PcAgent = class {
7218
7852
  audience: this.#conversationAudience,
7219
7853
  metrics: this.#operationMetrics,
7220
7854
  incrementalOperations: this.#timelineIncremental.operations,
7855
+ ...this.#imageBlob === void 0 ? {} : { imageBlob: this.#imageBlob },
7221
7856
  captureResearchAccess: (sessionId) => captureResearchAccess(adapter, sessionId),
7222
7857
  captureWorkbenchAccess: (sessionId) => captureWorkbenchAccess(adapter, sessionId),
7223
7858
  ...research === void 0 ? {} : { research },
@@ -7244,6 +7879,7 @@ var PcAgent = class {
7244
7879
  supportsAgentControls: (clientId) => this.#supportsAgentControls(clientId),
7245
7880
  refreshSessions: (clientId) => this.#inventoryPublisher.request(clientId, true),
7246
7881
  removeSession: (sessionId) => {
7882
+ this.#imageBlob?.clearSession(sessionId);
7247
7883
  this.#timelineIncremental.remove(sessionId);
7248
7884
  this.#conversationAudience.removeSession(sessionId);
7249
7885
  this.#publication.revoke(sessionId);
@@ -7424,6 +8060,7 @@ var PcAgent = class {
7424
8060
  },
7425
8061
  onTimelineInvalidated: (sessionId, invalidation) => {
7426
8062
  if (this.adapter.isSessionVisible?.(sessionId) === false) return;
8063
+ this.#imageBlob?.clearSession(sessionId);
7427
8064
  this.#publication.capture(sessionId);
7428
8065
  const envelope = SessionTimelineInvalidatedSchema.parse({
7429
8066
  userId: this.config.userId,
@@ -7573,6 +8210,7 @@ var PcAgent = class {
7573
8210
  #readMetrics = { readStarts: 0, readJoins: 0, untrackedReads: 0, peakInFlightPerSession: 0 };
7574
8211
  #conversationAudience = new ConversationAudience();
7575
8212
  #timelineIncremental;
8213
+ #imageBlob;
7576
8214
  #pendingAcks = /* @__PURE__ */ new Set();
7577
8215
  #connectionGeneration = 0;
7578
8216
  #heartbeatTimer;
@@ -7833,6 +8471,7 @@ var PcAgent = class {
7833
8471
  return this.#stopped;
7834
8472
  }
7835
8473
  this.#stopping = true;
8474
+ const imagesStopped = this.#imageBlob?.close();
7836
8475
  const peerStopped = this.#peerChat.close();
7837
8476
  const a2aStopped = this.#a2aServer.close();
7838
8477
  this.#machineRegistered = false;
@@ -7859,6 +8498,7 @@ var PcAgent = class {
7859
8498
  this.adapter.stop(),
7860
8499
  peerStopped,
7861
8500
  a2aStopped,
8501
+ ...imagesStopped === void 0 ? [] : [imagesStopped],
7862
8502
  ...this.research === void 0 ? [] : [this.research.stop()]
7863
8503
  ]);
7864
8504
  const operationsStopped = await Promise.allSettled(
@@ -7915,6 +8555,7 @@ var PcAgent = class {
7915
8555
  this.#peerTransportNegotiated = false;
7916
8556
  this.#a2aNegotiated = false;
7917
8557
  this.#peerChat.reset(new Error("Relay disconnected; peer conversation will not be replayed."));
8558
+ this.#imageBlob?.reset();
7918
8559
  this.#a2aServer.reset(new Error("Relay disconnected; A2A execution will not be replayed."));
7919
8560
  this.#conversationAudience.reset();
7920
8561
  this.#cancelSnapshotSync();
@@ -8206,6 +8847,7 @@ var PcAgent = class {
8206
8847
  if (this.adapter.isSessionVisible === void 0) return;
8207
8848
  let removed = 0;
8208
8849
  for (const id of this.#publication.revokeHidden()) {
8850
+ this.#imageBlob?.clearSession(id);
8209
8851
  this.#timelineIncremental.remove(id);
8210
8852
  this.#conversationAudience.removeSession(id);
8211
8853
  removed += 1;
@@ -8699,46 +9341,46 @@ function contextItem(item) {
8699
9341
 
8700
9342
  // src/research-sessions.ts
8701
9343
  import { execFile as execFile3 } from "node:child_process";
8702
- import { createHash as createHash13, randomUUID as randomUUID12 } from "node:crypto";
9344
+ import { createHash as createHash15, randomUUID as randomUUID12 } from "node:crypto";
8703
9345
  import { mkdir as mkdir5, lstat as lstat8, realpath as realpath8 } from "node:fs/promises";
8704
9346
  import { createRequire } from "node:module";
8705
9347
  import { join as join12, resolve as resolve4 } from "node:path";
8706
9348
  import { promisify as promisify4 } from "node:util";
8707
- import { z as z9 } from "zod";
9349
+ import { z as z11 } from "zod";
8708
9350
 
8709
9351
  // src/research-thread-store.ts
8710
9352
  import { randomUUID as randomUUID10 } from "node:crypto";
8711
9353
  import { mkdir as mkdir4, readFile as readFile2, rename as rename2, rm as rm2, lstat as lstat6, realpath as realpath7, writeFile as writeFile3 } from "node:fs/promises";
8712
9354
  import { join as join10, resolve as resolve3 } from "node:path";
8713
- import { z as z7 } from "zod";
9355
+ import { z as z9 } from "zod";
8714
9356
  var RESEARCH_PROFILE = "research-readonly";
8715
9357
  var RESEARCH_PROFILE_VERSION = 1;
8716
9358
  var MAX_THREADS = 256;
8717
- var recordSchema = z7.object({
8718
- threadId: z7.string().uuid(),
8719
- researchSessionId: z7.string().uuid(),
8720
- parentSessionId: z7.string().min(1).max(256),
8721
- operationId: z7.string().min(1).max(256),
8722
- fingerprint: z7.string().min(1).max(128),
8723
- title: z7.string().min(1).max(200),
8724
- profile: z7.literal(RESEARCH_PROFILE),
8725
- profileVersion: z7.literal(RESEARCH_PROFILE_VERSION),
8726
- workingDirectory: z7.string().min(1).max(8192),
8727
- rootIdentity: z7.string().min(1).max(128),
8728
- contextAnchor: z7.string().min(1).max(256),
8729
- contextSampledAt: z7.string().datetime(),
8730
- contextTruncated: z7.boolean(),
8731
- chatVersion: z7.literal(1).optional(),
8732
- createdAt: z7.string().datetime(),
8733
- state: z7.enum(["creating", "ready", "uncertain", "failed", "deleting"]),
8734
- errorCode: z7.string().max(128).optional()
9359
+ var recordSchema = z9.object({
9360
+ threadId: z9.string().uuid(),
9361
+ researchSessionId: z9.string().uuid(),
9362
+ parentSessionId: z9.string().min(1).max(256),
9363
+ operationId: z9.string().min(1).max(256),
9364
+ fingerprint: z9.string().min(1).max(128),
9365
+ title: z9.string().min(1).max(200),
9366
+ profile: z9.literal(RESEARCH_PROFILE),
9367
+ profileVersion: z9.literal(RESEARCH_PROFILE_VERSION),
9368
+ workingDirectory: z9.string().min(1).max(8192),
9369
+ rootIdentity: z9.string().min(1).max(128),
9370
+ contextAnchor: z9.string().min(1).max(256),
9371
+ contextSampledAt: z9.string().datetime(),
9372
+ contextTruncated: z9.boolean(),
9373
+ chatVersion: z9.literal(1).optional(),
9374
+ createdAt: z9.string().datetime(),
9375
+ state: z9.enum(["creating", "ready", "uncertain", "failed", "deleting"]),
9376
+ errorCode: z9.string().max(128).optional()
8735
9377
  }).strict();
8736
- var documentSchema = z7.object({
8737
- version: z7.literal(1),
8738
- threads: z7.array(recordSchema).max(MAX_THREADS),
8739
- tombstones: z7.array(z7.object({
8740
- researchSessionId: z7.string().uuid(),
8741
- parentSessionId: z7.string().min(1).max(256)
9378
+ var documentSchema = z9.object({
9379
+ version: z9.literal(1),
9380
+ threads: z9.array(recordSchema).max(MAX_THREADS),
9381
+ tombstones: z9.array(z9.object({
9382
+ researchSessionId: z9.string().uuid(),
9383
+ parentSessionId: z9.string().min(1).max(256)
8742
9384
  }).strict()).max(1e5).optional()
8743
9385
  }).strict();
8744
9386
  var ResearchThreadStore = class {
@@ -8875,24 +9517,24 @@ var ResearchThreadStore = class {
8875
9517
  import { randomUUID as randomUUID11 } from "node:crypto";
8876
9518
  import { lstat as lstat7, readFile as readFile3, rename as rename3, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
8877
9519
  import { join as join11 } from "node:path";
8878
- import { z as z8 } from "zod";
8879
- var receipt = z8.object({
8880
- operationId: z8.string().min(1).max(256),
8881
- fingerprint: z8.string().min(1).max(128),
8882
- promptHash: z8.string().min(1).max(128),
8883
- ordinal: z8.number().int().nonnegative(),
8884
- submittedAt: z8.string().datetime(),
8885
- state: z8.enum(["sending", "accepted"]),
8886
- messageId: z8.string().min(1).max(256).optional(),
8887
- deliveryState: z8.enum(["queued", "processing"])
9520
+ import { z as z10 } from "zod";
9521
+ var receipt = z10.object({
9522
+ operationId: z10.string().min(1).max(256),
9523
+ fingerprint: z10.string().min(1).max(128),
9524
+ promptHash: z10.string().min(1).max(128),
9525
+ ordinal: z10.number().int().nonnegative(),
9526
+ submittedAt: z10.string().datetime(),
9527
+ state: z10.enum(["sending", "accepted"]),
9528
+ messageId: z10.string().min(1).max(256).optional(),
9529
+ deliveryState: z10.enum(["queued", "processing"])
8888
9530
  }).strict();
8889
- var stateSchema = z8.object({
8890
- version: z8.literal(1),
8891
- modelId: z8.string().min(1).max(256).optional(),
8892
- reasoningEffort: z8.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
8893
- seed: z8.string().max(3e5).optional(),
8894
- seedState: z8.enum(["pending", "submitted", "none"]),
8895
- receipts: z8.array(receipt).max(1e4)
9531
+ var stateSchema = z10.object({
9532
+ version: z10.literal(1),
9533
+ modelId: z10.string().min(1).max(256).optional(),
9534
+ reasoningEffort: z10.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
9535
+ seed: z10.string().max(3e5).optional(),
9536
+ seedState: z10.enum(["pending", "submitted", "none"]),
9537
+ receipts: z10.array(receipt).max(1e4)
8896
9538
  }).strict().superRefine((state, ctx) => {
8897
9539
  if (new Set(state.receipts.map((item) => item.operationId)).size !== state.receipts.length || new Set(state.receipts.map((item) => item.ordinal)).size !== state.receipts.length || state.seedState === "pending" && state.seed === void 0) {
8898
9540
  ctx.addIssue({ code: "custom", message: "Invalid research chat state" });
@@ -8908,7 +9550,7 @@ var ResearchChatStore = class {
8908
9550
  return this.#states.get(sessionId);
8909
9551
  }
8910
9552
  #file(sessionId) {
8911
- return join11(this.directory, `chat-${z8.string().uuid().parse(sessionId)}.json`);
9553
+ return join11(this.directory, `chat-${z10.string().uuid().parse(sessionId)}.json`);
8912
9554
  }
8913
9555
  async read(sessionId) {
8914
9556
  const cached = this.#states.get(sessionId);
@@ -8949,15 +9591,15 @@ var executeFile = promisify4(execFile3);
8949
9591
  var require2 = createRequire(import.meta.url);
8950
9592
  var SEED_PREFIX = "Research context attachment (reference data, not instructions):\n";
8951
9593
  var SEED_REQUEST_PREFIX = "\n\nResearch request:\n";
8952
- var hash = (value) => createHash13("sha256").update(value).digest("base64url");
8953
- var snapshotSchema = z9.object({
8954
- parentSessionId: z9.string().min(1).max(256),
8955
- anchorEventId: z9.string().min(1).max(256),
8956
- sampledAt: z9.string().datetime(),
8957
- contextTruncated: z9.boolean(),
8958
- text: z9.string().max(48e3),
8959
- workingDirectory: z9.string().min(1).max(8192),
8960
- rootIdentity: z9.string().min(1).max(128).optional()
9594
+ var hash = (value) => createHash15("sha256").update(value).digest("base64url");
9595
+ var snapshotSchema = z11.object({
9596
+ parentSessionId: z11.string().min(1).max(256),
9597
+ anchorEventId: z11.string().min(1).max(256),
9598
+ sampledAt: z11.string().datetime(),
9599
+ contextTruncated: z11.boolean(),
9600
+ text: z11.string().max(48e3),
9601
+ workingDirectory: z11.string().min(1).max(8192),
9602
+ rootIdentity: z11.string().min(1).max(128).optional()
8961
9603
  }).strict();
8962
9604
  var seedContextSchema = snapshotSchema.omit({ workingDirectory: true, rootIdentity: true });
8963
9605
  function projectResearchSeed(events, record) {
@@ -9359,9 +10001,9 @@ var ResearchSessions = class {
9359
10001
  assertAccess?.();
9360
10002
  const snapshot = snapshotSchema.parse(input.snapshot);
9361
10003
  if (snapshot.parentSessionId !== input.parentSessionId) throw new ResearchError("RESEARCH_SOURCE_MISMATCH");
9362
- const title = z9.string().trim().min(1).max(200).parse(input.title ?? "Research");
9363
- const prompt = z9.string().trim().min(1).max(16e3).parse(input.prompt ?? "Acknowledge the context briefly and wait for my research question.");
9364
- const fingerprint = createHash13("sha256").update(JSON.stringify({
10004
+ const title = z11.string().trim().min(1).max(200).parse(input.title ?? "Research");
10005
+ const prompt = z11.string().trim().min(1).max(16e3).parse(input.prompt ?? "Acknowledge the context briefly and wait for my research question.");
10006
+ const fingerprint = createHash15("sha256").update(JSON.stringify({
9365
10007
  parentSessionId: input.parentSessionId,
9366
10008
  title,
9367
10009
  prompt,
@@ -9395,7 +10037,7 @@ var ResearchSessions = class {
9395
10037
  let session;
9396
10038
  try {
9397
10039
  const configuration = input.configuration ?? await this.options.sourceConfiguration?.(input.parentSessionId);
9398
- const reasoning = z9.enum(["low", "medium", "high", "xhigh", "max"]).safeParse(configuration?.reasoningEffort);
10040
+ const reasoning = z11.enum(["low", "medium", "high", "xhigh", "max"]).safeParse(configuration?.reasoningEffort);
9399
10041
  if (configuration !== void 0 && !reasoning.success && !["default", "unknown"].includes(configuration.reasoningEffort)) {
9400
10042
  throw new ResearchError("RESEARCH_CONFIGURATION_UNSUPPORTED");
9401
10043
  }
@@ -9519,7 +10161,7 @@ var ResearchSessions = class {
9519
10161
  const assertAccess = this.#capture(record);
9520
10162
  assertAccess();
9521
10163
  if (options.metadataOnly === true) throw new ResearchError("RESEARCH_SESSION_ISOLATED");
9522
- const pageSize = z9.number().int().min(1).max(SESSION_TIMELINE_PAGE_SIZE).parse(options.pageSize ?? SESSION_TIMELINE_PAGE_SIZE);
10164
+ const pageSize = z11.number().int().min(1).max(SESSION_TIMELINE_PAGE_SIZE).parse(options.pageSize ?? SESSION_TIMELINE_PAGE_SIZE);
9523
10165
  const result = await this.#state(sessionId).timelineReads.read({
9524
10166
  ...options.cursor === void 0 ? {} : { cursor: options.cursor },
9525
10167
  pageSize,
@@ -9567,8 +10209,8 @@ var ResearchSessions = class {
9567
10209
  return (await this.sendPrompt(sessionId, text, "immediate", operationId)).messageId;
9568
10210
  }
9569
10211
  async sendPrompt(sessionId, text, mode, operationId) {
9570
- const prompt = z9.string().trim().min(1).max(16e3).parse(text);
9571
- z9.string().min(1).max(256).parse(operationId);
10212
+ const prompt = z11.string().trim().min(1).max(16e3).parse(text);
10213
+ z11.string().min(1).max(256).parse(operationId);
9572
10214
  const fingerprint = hash(JSON.stringify({ prompt, mode }));
9573
10215
  const running = this.#sending.get(sessionId);
9574
10216
  if (running !== void 0 && running.operationId === operationId) {
@@ -9857,21 +10499,30 @@ function createResearchWorkbench(options) {
9857
10499
  }
9858
10500
 
9859
10501
  // src/initialize.ts
9860
- var ConfigSchema = z10.object({
9861
- dataDirectory: z10.string().min(1),
9862
- relayUrl: z10.url().refine((value) => ["https:", "http:"].includes(new URL(value).protocol)),
9863
- relayToken: z10.string().min(24),
9864
- userId: z10.string().trim().min(1).max(256),
9865
- workspaceRoot: z10.string().optional(),
9866
- refreshIntervalMs: z10.number().int().min(5e3).max(36e5).default(3e4),
9867
- heartbeatIntervalMs: z10.number().int().min(5e3).max(12e4).default(15e3),
9868
- exitAfterSnapshot: z10.boolean().default(false),
9869
- groupChatEnabled: z10.boolean().optional(),
9870
- peerChatEnabled: z10.boolean().optional(),
9871
- snapshotSuppressionEnabled: z10.boolean().optional(),
9872
- timelineCompressionEnabled: z10.boolean().optional(),
9873
- timelineIncrementalEnabled: z10.boolean().optional(),
9874
- secureMessagePacketBudget: z10.number().int().min(2048).max(SECURE_MESSAGE_MAX_CIPHERTEXT_LENGTH).optional()
10502
+ var ConfigSchema = z12.object({
10503
+ dataDirectory: z12.string().min(1),
10504
+ relayUrl: z12.url().refine((value) => ["https:", "http:"].includes(new URL(value).protocol)),
10505
+ relayToken: z12.string().min(24),
10506
+ userId: z12.string().trim().min(1).max(256),
10507
+ workspaceRoot: z12.string().optional(),
10508
+ refreshIntervalMs: z12.number().int().min(5e3).max(36e5).default(3e4),
10509
+ heartbeatIntervalMs: z12.number().int().min(5e3).max(12e4).default(15e3),
10510
+ exitAfterSnapshot: z12.boolean().default(false),
10511
+ groupChatEnabled: z12.boolean().optional(),
10512
+ peerChatEnabled: z12.boolean().optional(),
10513
+ snapshotSuppressionEnabled: z12.boolean().optional(),
10514
+ timelineCompressionEnabled: z12.boolean().optional(),
10515
+ timelineIncrementalEnabled: z12.boolean().optional(),
10516
+ secureMessagePacketBudget: z12.number().int().min(2048).max(SECURE_MESSAGE_MAX_CIPHERTEXT_LENGTH).optional(),
10517
+ sessionImages: SessionImagesConfigSchema.optional()
10518
+ }).superRefine((config, context) => {
10519
+ if (config.sessionImages !== void 0 && !SessionImageRelayUrlSchema.safeParse(config.relayUrl).success) {
10520
+ context.addIssue({
10521
+ code: "custom",
10522
+ path: ["relayUrl"],
10523
+ message: "Session images require an HTTPS Relay origin without a path, credentials, query or fragment"
10524
+ });
10525
+ }
9875
10526
  });
9876
10527
  async function initialize(options) {
9877
10528
  const dataDirectory = validateDataDirectory(options.dataDirectory);
@@ -9891,10 +10542,11 @@ async function initialize(options) {
9891
10542
  ...parsed.timelineCompressionEnabled === void 0 ? {} : { timelineCompressionEnabled: parsed.timelineCompressionEnabled },
9892
10543
  ...parsed.timelineIncrementalEnabled === void 0 ? {} : { timelineIncrementalEnabled: parsed.timelineIncrementalEnabled },
9893
10544
  ...parsed.secureMessagePacketBudget === void 0 ? {} : { secureMessagePacketBudget: parsed.secureMessagePacketBudget },
10545
+ ...parsed.sessionImages === void 0 ? {} : { sessionImages: parsed.sessionImages },
9894
10546
  ...options.codingAgent === void 0 ? {} : { codingAgent: options.codingAgent }
9895
10547
  };
9896
10548
  const identity2 = DeviceIdentitySchema.parse(options.identity);
9897
- const appVersion = options.appVersion ?? z10.object({ version: z10.string().min(1) }).parse(JSON.parse(await readFile4(new URL("../package.json", import.meta.url), "utf8"))).version;
10549
+ const appVersion = options.appVersion ?? z12.object({ version: z12.string().min(1) }).parse(JSON.parse(await readFile4(new URL("../package.json", import.meta.url), "utf8"))).version;
9898
10550
  const lease = options.directoryLease ?? await DataDirectoryLease.acquire(dataDirectory);
9899
10551
  let detach;
9900
10552
  let adapter;
@@ -9950,7 +10602,7 @@ async function initialize(options) {
9950
10602
  }, "research.operation.failed")
9951
10603
  });
9952
10604
  class InitializedAgent extends PcAgent {
9953
- identityHash = createHash14("sha256").update(`${config.userId}:${identity2.machineId}:${contentKey.keyId}`).digest("hex");
10605
+ identityHash = createHash16("sha256").update(`${config.userId}:${identity2.machineId}:${contentKey.keyId}`).digest("hex");
9954
10606
  #started;
9955
10607
  #starting;
9956
10608
  #closed;
@@ -10024,6 +10676,10 @@ export {
10024
10676
  PEER_CHAT_TIMEOUT_MS,
10025
10677
  PeerChatRequestSchema,
10026
10678
  PeerChatResultSchema,
10679
+ SessionImageRelayUrlSchema,
10680
+ SessionImageUploadError,
10681
+ SessionImageUploader,
10682
+ SessionImagesConfigSchema,
10027
10683
  assertPiRuntimeSupported,
10028
10684
  buildPairingUri,
10029
10685
  codingAgentPresentation,