@agentknight/sdk 0.9.38 → 0.9.40

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-CVWUKZX7.js";
8
8
  import {
9
9
  AgentSelectionSchema,
10
10
  assertSelectionSnapshotFits,
11
11
  supportsAgentSelection
12
- } from "./chunks/chunk-6YF3VZLR.js";
12
+ } from "./chunks/chunk-6RWWNR37.js";
13
13
  import {
14
14
  ConversationNormalizer,
15
15
  LiveTimelineProjection,
@@ -31,25 +31,26 @@ import {
31
31
  resolveGitProjectIdentity,
32
32
  sessionRestoreFailure,
33
33
  validateOperationResponse
34
- } from "./chunks/chunk-UIM3KQRC.js";
34
+ } from "./chunks/chunk-ULJMR33R.js";
35
35
  import {
36
36
  GROUP_CHAT_MAX_TURNS,
37
37
  GroupChatError
38
- } from "./chunks/chunk-YSJRQ35E.js";
38
+ } from "./chunks/chunk-Q3TQPONK.js";
39
39
  import {
40
40
  assistantPreview,
41
41
  isContentfulAssistant,
42
42
  userPreview
43
- } from "./chunks/chunk-34DUD2Y4.js";
43
+ } from "./chunks/chunk-D6HWUCVM.js";
44
44
  import {
45
45
  AgentModelListingError,
46
46
  AgentSessionRenameError,
47
47
  SessionOperationError,
48
48
  SessionOperations,
49
+ TimelinePageBudgetError,
49
50
  UnsupportedAgentOperationError,
50
51
  captureResearchAccess,
51
52
  captureWorkbenchAccess
52
- } from "./chunks/chunk-6GVMVE5N.js";
53
+ } from "./chunks/chunk-AJ4YL22H.js";
53
54
  import {
54
55
  A2aRelayRequestSchema,
55
56
  A2aRelayResponseSchema,
@@ -95,6 +96,7 @@ import {
95
96
  SECURE_MESSAGE_MAX_CIPHERTEXT_LENGTH,
96
97
  SECURE_SOCKET_IO_NAMESPACE,
97
98
  SESSION_ASSISTANT_PREVIEW_CAPABILITY,
99
+ SESSION_AVATAR_PERSONA_CAPABILITY,
98
100
  SESSION_COMMUNICATION_CAPABILITY,
99
101
  SESSION_DELETE_CAPABILITY,
100
102
  SESSION_DIRECTORY_LIST_OPERATION,
@@ -107,8 +109,11 @@ import {
107
109
  SESSION_FILE_MAX_IMAGE_EDGE,
108
110
  SESSION_FILE_MAX_IMAGE_PIXELS,
109
111
  SESSION_FILE_MAX_ORIGINAL_BYTES,
112
+ SESSION_FILE_MAX_VIDEO_PIXELS,
110
113
  SESSION_FILE_READ_OPERATION,
114
+ SESSION_FILE_READ_V2_VERSION,
111
115
  SESSION_FILE_READ_VERSION,
116
+ SESSION_FILE_V2_MAX_ORIGINAL_BYTES,
112
117
  SESSION_GIT_DIFF_GZIP_MIN_BYTES,
113
118
  SESSION_GIT_DIFF_GZIP_VERSION,
114
119
  SESSION_GIT_DIFF_MAX_FILES,
@@ -116,8 +121,12 @@ import {
116
121
  SESSION_GIT_DIFF_MAX_RESPONSE_BYTES,
117
122
  SESSION_GIT_DIFF_OPERATION,
118
123
  SESSION_GIT_DIFF_VERSION,
124
+ SESSION_IMAGE_API_PATH,
125
+ SESSION_IMAGE_MAX_BYTES,
126
+ SESSION_IMAGE_MAX_PIXELS,
119
127
  SESSION_LIST_UPDATES_CAPABILITY,
120
128
  SESSION_OPTIONS_PARTIAL_CAPABILITY,
129
+ SESSION_OPTIONS_PROGRESSIVE_CAPABILITY,
121
130
  SESSION_PROJECT_IDENTITY_OPERATION,
122
131
  SESSION_PROJECT_IDENTITY_VERSION,
123
132
  SESSION_PROMPT_SUBMISSION_TIME_CAPABILITY,
@@ -144,9 +153,14 @@ import {
144
153
  SessionDirectoryListResultDataSchema,
145
154
  SessionFileReadDataSchema,
146
155
  SessionFileReadResultDataSchema,
156
+ SessionFileReadV2ResultDataSchema,
147
157
  SessionGitDiffDataSchema,
148
158
  SessionGitDiffGzipResultDataSchema,
149
159
  SessionGitDiffResultDataSchema,
160
+ SessionImageCommitResultSchema,
161
+ SessionImageScopeSchema,
162
+ SessionImageUploadRequestSchema,
163
+ SessionImageUploadResultSchema,
150
164
  SessionListRequestSchema,
151
165
  SessionListResultSchema,
152
166
  SessionOptionsRequestSchema,
@@ -164,6 +178,8 @@ import {
164
178
  SessionTimelinePageSchema,
165
179
  SessionWatchRequestSchema,
166
180
  SessionsSnapshotSchema,
181
+ TIMELINE_IMAGE_BLOB_OPERATION,
182
+ TIMELINE_IMAGE_BLOB_VERSION,
167
183
  TIMELINE_IMAGE_CHUNK_BYTES,
168
184
  TIMELINE_IMAGE_MAX_BYTES,
169
185
  TIMELINE_IMAGE_OPERATION,
@@ -172,6 +188,8 @@ import {
172
188
  TIMELINE_SYNC_OPERATION,
173
189
  TIMELINE_SYNC_VERSION,
174
190
  TRANSPORT_V2,
191
+ TimelineImageBlobRequestSchema,
192
+ TimelineImageBlobResultSchema,
175
193
  TimelineImageChunkSchema,
176
194
  TimelineImageRequestSchema,
177
195
  TimelineSyncRequestSchema,
@@ -208,12 +226,12 @@ import {
208
226
  sessionCommunicationError,
209
227
  sessionCommunicationOperationCapability,
210
228
  workbenchCapability
211
- } from "./chunks/chunk-GNQC2IS2.js";
229
+ } from "./chunks/chunk-OV77TRIH.js";
212
230
 
213
231
  // src/initialize.ts
214
- import { createHash as createHash14 } from "node:crypto";
232
+ import { createHash as createHash16 } from "node:crypto";
215
233
  import { readFile as readFile4 } from "node:fs/promises";
216
- import { z as z10 } from "zod";
234
+ import { z as z12 } from "zod";
217
235
 
218
236
  // src/coding-agent-factory.ts
219
237
  import { resolve } from "node:path";
@@ -286,7 +304,7 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
286
304
  switch (engine.kind) {
287
305
  case "managed": {
288
306
  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");
307
+ const { ManagedCodingAgentAdapter } = await import("./chunks/managed-agent-adapter-F7U26CH2.js");
290
308
  return new ManagedCodingAgentAdapter({
291
309
  dataDirectory: config.dataDirectory,
292
310
  owner,
@@ -325,7 +343,7 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
325
343
  });
326
344
  }
327
345
  case "copilot": {
328
- const { GithubCopilotAdapter } = await import("./chunks/copilot-adapter-USX4UEWH.js");
346
+ const { GithubCopilotAdapter } = await import("./chunks/copilot-adapter-KH6NXHGK.js");
329
347
  if (owner !== void 0) {
330
348
  return new GithubCopilotAdapter(void 0, void 0, void 0, {
331
349
  dataDirectory: config.dataDirectory,
@@ -345,13 +363,13 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
345
363
  }
346
364
  case "pi": {
347
365
  assertPiRuntimeSupported();
348
- const { PiCodingAgentAdapter } = await import("./chunks/pi-adapter-KT5M7Z6R.js");
366
+ const { PiCodingAgentAdapter } = await import("./chunks/pi-adapter-G4GQ5ULV.js");
349
367
  return new PiCodingAgentAdapter(
350
368
  engine.agentDirectory === void 0 ? {} : { agentDirectory: resolve(engine.agentDirectory) }
351
369
  );
352
370
  }
353
371
  case "acp": {
354
- const { AcpCodingAgentAdapter } = await import("./chunks/acp-adapter-DZ3EE36H.js");
372
+ const { AcpCodingAgentAdapter } = await import("./chunks/acp-adapter-TVAW7EHK.js");
355
373
  return new AcpCodingAgentAdapter({
356
374
  command: engine.command,
357
375
  args: engine.args,
@@ -367,6 +385,18 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
367
385
  }
368
386
  }
369
387
 
388
+ // src/config.ts
389
+ import { z } from "zod";
390
+ var SessionImagesConfigSchema = z.object({
391
+ allowUnencryptedStorage: z.literal(true),
392
+ storageAccount: z.string().regex(/^[a-z0-9]{3,24}$/u),
393
+ storageContainer: z.string().min(3).max(63).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u)
394
+ }).strict();
395
+ var SessionImageRelayUrlSchema = z.url().refine((value) => {
396
+ const url = new URL(value);
397
+ return /^https:\/\/[^/?#\\@\s\x00-\x1f\x7f]+\/?$/iu.test(value) && url.protocol === "https:" && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "";
398
+ }, "Session images require an HTTPS Relay origin without a path, credentials, query or fragment").transform((value) => new URL(value).origin);
399
+
370
400
  // src/content-encryption.ts
371
401
  import {
372
402
  createCipheriv,
@@ -383,22 +413,22 @@ import {
383
413
  } from "node:fs";
384
414
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
385
415
  import { join as join2 } from "node:path";
386
- import { z } from "zod";
416
+ import { z as z2 } from "zod";
387
417
  var KEY_BYTES = 32;
388
418
  var NONCE_BYTES = 12;
389
419
  var AUTH_TAG_BYTES = 16;
390
420
  var DEFAULT_REPLAY_WINDOW_MS = 10 * 60 * 1e3;
391
421
  var MAX_REPLAY_ENTRIES = 1e4;
392
422
  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)
423
+ var ContentKeyFileSchema = z2.object({
424
+ schemaVersion: z2.literal(1),
425
+ key: z2.string().regex(CONTENT_KEY_PATTERN)
396
426
  }).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()
427
+ var ReplayJournalEntrySchema = z2.object({
428
+ schemaVersion: z2.literal(1),
429
+ keyId: z2.string().regex(/^[A-Za-z0-9_-]{22}$/u),
430
+ messageId: z2.string().trim().min(1).max(256),
431
+ sentAt: z2.number().int().nonnegative()
402
432
  }).strict();
403
433
  var REPLAY_JOURNAL_COMPACT_EVERY = 256;
404
434
  var REPLAY_JOURNAL_MAX_BYTES = 1024 * 1024;
@@ -709,13 +739,13 @@ var ContentCipher = class {
709
739
  };
710
740
 
711
741
  // 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 })
742
+ import { z as z3 } from "zod";
743
+ var DeviceIdentitySchema = z3.object({
744
+ schemaVersion: z3.literal(2),
745
+ machineId: z3.uuid(),
746
+ stableDeviceId: z3.uuid(),
747
+ machineName: z3.string().trim().min(1).max(120),
748
+ createdAt: z3.iso.datetime({ offset: true })
719
749
  });
720
750
 
721
751
  // src/pairing.ts
@@ -812,7 +842,7 @@ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypt
812
842
  import { lstat as lstat3, realpath as realpath3, stat as stat4 } from "node:fs/promises";
813
843
  import { isAbsolute as isAbsolute4, join as join5 } from "node:path";
814
844
  import { gunzipSync } from "node:zlib";
815
- import { z as z4 } from "zod";
845
+ import { z as z5 } from "zod";
816
846
 
817
847
  // src/session-directory.ts
818
848
  import { createHash as createHash2 } from "node:crypto";
@@ -967,6 +997,152 @@ import { basename, extname, isAbsolute as isAbsolute3, join as join4 } from "nod
967
997
  import { promisify } from "node:util";
968
998
  import { gzip } from "node:zlib";
969
999
  import sharp from "sharp";
1000
+
1001
+ // src/session-video.ts
1002
+ function requireVideo(valid) {
1003
+ if (!valid) throw new Error("Unsupported video container");
1004
+ }
1005
+ function one(elements, id) {
1006
+ const matches = elements.filter((element) => element.id === id);
1007
+ const match = matches[0];
1008
+ requireVideo(matches.length === 1 && match !== void 0);
1009
+ return match;
1010
+ }
1011
+ function validateSessionVideo(bytes, mediaType) {
1012
+ requireVideo(bytes.length > 0 && bytes.length <= SESSION_FILE_V2_MAX_ORIGINAL_BYTES);
1013
+ if (mediaType === "video/mp4") validateMp4(bytes);
1014
+ else validateWebm(bytes);
1015
+ }
1016
+ function validateMp4(bytes) {
1017
+ const boxes = (start, end) => {
1018
+ const result = [];
1019
+ while (start < end) {
1020
+ requireVideo(end - start >= 8);
1021
+ let size = bytes.readUInt32BE(start);
1022
+ const id = bytes.toString("latin1", start + 4, start + 8);
1023
+ let header = 8;
1024
+ if (size === 1) {
1025
+ requireVideo(end - start >= 16 && bytes.readUInt32BE(start + 8) === 0);
1026
+ size = bytes.readUInt32BE(start + 12);
1027
+ header = 16;
1028
+ }
1029
+ if (size === 0) {
1030
+ requireVideo(id === "mdat");
1031
+ size = end - start;
1032
+ }
1033
+ requireVideo(size >= header && size <= end - start);
1034
+ result.push({ id, start: start + header, end: start + size });
1035
+ start += size;
1036
+ }
1037
+ return result;
1038
+ };
1039
+ const children = (element) => boxes(element.start, element.end);
1040
+ const top = boxes(0, bytes.length);
1041
+ requireVideo(top[0]?.id === "ftyp" && top.every((box) => ["ftyp", "moov", "mdat", "free", "skip", "wide"].includes(box.id)));
1042
+ const ftyp = one(top, "ftyp");
1043
+ requireVideo(ftyp.end - ftyp.start >= 8 && (ftyp.end - ftyp.start) % 4 === 0);
1044
+ const brands = [bytes.toString("latin1", ftyp.start, ftyp.start + 4)];
1045
+ for (let offset = ftyp.start + 8; offset < ftyp.end; offset += 4) brands.push(bytes.toString("latin1", offset, offset + 4));
1046
+ requireVideo(brands.some((brand) => ["isom", "iso2", "mp41", "mp42", "avc1"].includes(brand)));
1047
+ requireVideo(top.some((box) => box.id === "mdat" && box.end > box.start));
1048
+ const movie = children(one(top, "moov"));
1049
+ requireVideo(!movie.some((box) => ["rmra", "mvex"].includes(box.id)));
1050
+ let videoTracks = 0;
1051
+ for (const track of movie.filter((box) => box.id === "trak")) {
1052
+ const media = children(one(children(track), "mdia"));
1053
+ const handler = one(media, "hdlr");
1054
+ requireVideo(handler.end - handler.start >= 12);
1055
+ const type = bytes.toString("latin1", handler.start + 8, handler.start + 12);
1056
+ requireVideo(type === "vide" || type === "soun");
1057
+ const info = children(one(media, "minf"));
1058
+ const dref = one(children(one(info, "dinf")), "dref");
1059
+ requireVideo(dref.end - dref.start >= 8 && bytes.readUInt32BE(dref.start) === 0 && bytes.readUInt32BE(dref.start + 4) === 1);
1060
+ const references = boxes(dref.start + 8, dref.end);
1061
+ const reference = one(references, "url ");
1062
+ requireVideo(references.length === 1 && reference.end - reference.start === 4 && bytes.readUInt32BE(reference.start) === 1);
1063
+ const descriptions = one(children(one(info, "stbl")), "stsd");
1064
+ requireVideo(descriptions.end - descriptions.start >= 8 && bytes.readUInt32BE(descriptions.start) === 0 && bytes.readUInt32BE(descriptions.start + 4) === 1);
1065
+ const entries = boxes(descriptions.start + 8, descriptions.end);
1066
+ const entry = entries[0];
1067
+ requireVideo(entries.length === 1 && entry !== void 0 && entry.end - entry.start >= 8 && bytes.readUInt16BE(entry.start + 6) === 1);
1068
+ if (type === "vide") {
1069
+ requireVideo(["avc1", "avc3"].includes(entry.id) && entry.end - entry.start >= 78);
1070
+ const width = bytes.readUInt16BE(entry.start + 24);
1071
+ const height = bytes.readUInt16BE(entry.start + 26);
1072
+ requireVideo(width > 0 && height > 0 && width * height <= SESSION_FILE_MAX_VIDEO_PIXELS);
1073
+ const config = one(boxes(entry.start + 78, entry.end), "avcC");
1074
+ requireVideo(config.end - config.start >= 7 && bytes[config.start] === 1);
1075
+ videoTracks++;
1076
+ } else requireVideo(entry.id === "mp4a" && entry.end - entry.start >= 28);
1077
+ }
1078
+ requireVideo(videoTracks === 1);
1079
+ }
1080
+ function validateWebm(bytes) {
1081
+ const vint = (offset, end, id) => {
1082
+ requireVideo(offset < end);
1083
+ const first = bytes[offset];
1084
+ requireVideo(first !== void 0 && first !== 0);
1085
+ let length = 1;
1086
+ while ((first & 128 >> length - 1) === 0) length++;
1087
+ requireVideo(length <= (id ? 4 : 8) && length <= end - offset);
1088
+ let value = id ? first : first & 255 >> length;
1089
+ let unknown = !id && value === 255 >> length;
1090
+ for (let index = 1; index < length; index++) {
1091
+ const byte = bytes[offset + index];
1092
+ requireVideo(byte !== void 0);
1093
+ value = value * 256 + byte;
1094
+ unknown = unknown && byte === 255;
1095
+ }
1096
+ requireVideo(unknown || Number.isSafeInteger(value));
1097
+ return { value, next: offset + length, unknown };
1098
+ };
1099
+ const elements = (start, end) => {
1100
+ const result = [];
1101
+ while (start < end) {
1102
+ const id = vint(start, end, true);
1103
+ const size = vint(id.next, end, false);
1104
+ const tag = id.value.toString(16);
1105
+ requireVideo(!size.unknown || tag === "18538067");
1106
+ const length = size.unknown ? end - size.next : size.value;
1107
+ requireVideo(length <= end - size.next);
1108
+ result.push({ id: tag, start: size.next, end: size.next + length });
1109
+ start = size.next + length;
1110
+ }
1111
+ return result;
1112
+ };
1113
+ const text = (element) => bytes.toString("latin1", element.start, element.end);
1114
+ const uint = (element) => {
1115
+ requireVideo(element.end - element.start >= 1 && element.end - element.start <= 4);
1116
+ return bytes.readUIntBE(element.start, element.end - element.start);
1117
+ };
1118
+ const top = elements(0, bytes.length);
1119
+ requireVideo(top.length === 2 && top[0]?.id === "1a45dfa3");
1120
+ const header = one(top, "1a45dfa3");
1121
+ requireVideo(text(one(elements(header.start, header.end), "4282")) === "webm");
1122
+ const segment = one(top, "18538067");
1123
+ const contents = elements(segment.start, segment.end);
1124
+ requireVideo(contents.some((element) => element.id === "1f43b675" && element.end > element.start));
1125
+ const tracks = one(contents, "1654ae6b");
1126
+ let videoTracks = 0;
1127
+ for (const track of elements(tracks.start, tracks.end).filter((element) => element.id === "ae")) {
1128
+ const fields = elements(track.start, track.end);
1129
+ const type = uint(one(fields, "83"));
1130
+ const codec = text(one(fields, "86"));
1131
+ requireVideo(!fields.some((field2) => field2.id === "6d80" || field2.id === "6fab"));
1132
+ if (type === 1) {
1133
+ requireVideo(codec === "V_VP8" || codec === "V_VP9");
1134
+ const video = one(fields, "e0");
1135
+ const dimensions = elements(video.start, video.end);
1136
+ const width = uint(one(dimensions, "b0"));
1137
+ const height = uint(one(dimensions, "ba"));
1138
+ requireVideo(width > 0 && height > 0 && width * height <= SESSION_FILE_MAX_VIDEO_PIXELS);
1139
+ videoTracks++;
1140
+ } else requireVideo(type === 2 && (codec === "A_VORBIS" || codec === "A_OPUS"));
1141
+ }
1142
+ requireVideo(videoTracks === 1);
1143
+ }
1144
+
1145
+ // src/session-file.ts
970
1146
  var SESSION_FILE_CONCURRENCY = 4;
971
1147
  var SESSION_FILE_TIMEOUT_MS = 5e3;
972
1148
  var compress = promisify(gzip);
@@ -1204,10 +1380,12 @@ var SessionFileReader = class {
1204
1380
  }
1205
1381
  metadata;
1206
1382
  #active = 0;
1207
- async read(sessionId, path, rootRevision) {
1383
+ async read(sessionId, path, rootRevision, version = SESSION_FILE_READ_VERSION) {
1384
+ if (version !== SESSION_FILE_READ_VERSION && version !== SESSION_FILE_READ_V2_VERSION) fail2("UNSUPPORTED_OPERATION_VERSION");
1208
1385
  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)
1386
+ const schema = version === SESSION_FILE_READ_V2_VERSION ? SessionFileReadV2ResultDataSchema : SessionFileReadResultDataSchema;
1387
+ return schema.parse(await this.#run(
1388
+ (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, false, version)
1211
1389
  ));
1212
1390
  }
1213
1391
  async readResource(sessionId, path, rootRevision) {
@@ -1241,7 +1419,7 @@ var SessionFileReader = class {
1241
1419
  clearTimeout(timer);
1242
1420
  }
1243
1421
  }
1244
- async #read(sessionId, path, rootRevision, checkDeadline, resource) {
1422
+ async #read(sessionId, path, rootRevision, checkDeadline, resource, version = SESSION_FILE_READ_VERSION) {
1245
1423
  const metadata = await this.metadata(sessionId);
1246
1424
  checkDeadline();
1247
1425
  if (metadata === void 0) fail2("SESSION_NOT_FOUND");
@@ -1283,11 +1461,12 @@ var SessionFileReader = class {
1283
1461
  return file;
1284
1462
  };
1285
1463
  const before = await inspectPath();
1286
- const maxOriginalBytes = resource ? SESSION_RESOURCE_MAX_ORIGINAL_BYTES : SESSION_FILE_MAX_ORIGINAL_BYTES;
1464
+ 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
1465
  if (before.size > BigInt(maxOriginalBytes)) fail2("FILE_TOO_LARGE");
1288
1466
  const extension = extname(path).toLowerCase();
1289
1467
  const image = [".png", ".jpg", ".jpeg", ".webp"].includes(extension);
1290
- const mediaType = resource ? resourceMediaType(extension) : image ? "image/webp" : textMediaType(path);
1468
+ const video = !resource && version === SESSION_FILE_READ_V2_VERSION && [".mp4", ".webm"].includes(extension);
1469
+ const mediaType = resource ? resourceMediaType(extension) : image ? "image/webp" : video ? extension === ".mp4" ? "video/mp4" : "video/webm" : textMediaType(path);
1291
1470
  const handle = await open(target, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0) | (constants2.O_NONBLOCK ?? 0));
1292
1471
  try {
1293
1472
  const opened = await handle.stat({ bigint: true });
@@ -1313,6 +1492,13 @@ var SessionFileReader = class {
1313
1492
  await validateRawImage(bytes, extension, checkDeadline);
1314
1493
  canonical = bytes;
1315
1494
  } else canonical = await imagePreview(bytes, extension, checkDeadline);
1495
+ } else if (mediaType === "video/mp4" || mediaType === "video/webm") {
1496
+ try {
1497
+ validateSessionVideo(bytes, mediaType);
1498
+ } catch (error) {
1499
+ throw new SessionFileReadError("FILE_UNSUPPORTED", { cause: error });
1500
+ }
1501
+ canonical = bytes;
1316
1502
  } else {
1317
1503
  let text;
1318
1504
  try {
@@ -1345,7 +1531,7 @@ var SessionFileReader = class {
1345
1531
  throw new SessionFileReadError("FILE_CHANGED", { cause: error });
1346
1532
  }
1347
1533
  checkDeadline();
1348
- return (resource ? SessionResourceReadResultDataSchema : SessionFileReadResultDataSchema).parse({
1534
+ return (resource ? SessionResourceReadResultDataSchema : version === SESSION_FILE_READ_V2_VERSION ? SessionFileReadV2ResultDataSchema : SessionFileReadResultDataSchema).parse({
1349
1535
  path,
1350
1536
  rootRevision,
1351
1537
  mediaType,
@@ -1784,20 +1970,20 @@ function sensitiveSegments(segments) {
1784
1970
  }
1785
1971
  return false;
1786
1972
  }
1787
- var pathSchema = z4.string().max(4096).default("");
1788
- var listSchema = z4.object({
1973
+ var pathSchema = z5.string().max(4096).default("");
1974
+ var listSchema = z5.object({
1789
1975
  path: pathSchema,
1790
- cursor: z4.string().uuid().optional()
1976
+ cursor: z5.string().uuid().optional()
1791
1977
  }).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)
1978
+ var readSchema = z5.object({
1979
+ path: z5.string().min(1).max(4096),
1980
+ startLine: z5.number().int().min(1).max(500001).default(1),
1981
+ lineCount: z5.number().int().min(1).max(200).default(200)
1796
1982
  }).strict();
1797
- var searchSchema = z4.object({
1983
+ var searchSchema = z5.object({
1798
1984
  path: pathSchema,
1799
- query: z4.string().min(1).max(256),
1800
- cursor: z4.string().uuid().optional()
1985
+ query: z5.string().min(1).max(256),
1986
+ cursor: z5.string().uuid().optional()
1801
1987
  }).strict();
1802
1988
  var ResearchTools = class {
1803
1989
  constructor(parentSessionId, root, sourceMetadata, privateDirectories = [], onError, captureSourceAccess) {
@@ -2057,7 +2243,7 @@ var ResearchTools = class {
2057
2243
  const definition = (name, description, schema, run) => ({
2058
2244
  name,
2059
2245
  description,
2060
- parameters: z4.toJSONSchema(schema),
2246
+ parameters: z5.toJSONSchema(schema),
2061
2247
  // Only these host-owned read tools bypass prompts; their path policy remains mandatory.
2062
2248
  skipPermission: true,
2063
2249
  defer: "never",
@@ -2070,7 +2256,7 @@ var ResearchTools = class {
2070
2256
  return { textResultForLlm: JSON.stringify(result), resultType: "success" };
2071
2257
  } catch (error) {
2072
2258
  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";
2259
+ const code = (error instanceof ResearchError || error instanceof SessionFileReadError) && knownCode !== "UNCLASSIFIED" ? knownCode : error instanceof z5.ZodError ? "RESEARCH_INVALID_ARGUMENT" : "RESEARCH_TOOL_FAILED";
2074
2260
  this.onError?.(new ResearchError(code, "The research tool failed", { cause: error }), { stage: name });
2075
2261
  return { textResultForLlm: JSON.stringify({ error: code }), resultType: "failure" };
2076
2262
  }
@@ -2141,11 +2327,21 @@ function researchAwareCodingAgentAdapter(inner, research) {
2141
2327
  },
2142
2328
  readSessionTimelinePage: (id, options) => route(id, () => research.timeline(id, options), () => inner.readSessionTimelinePage(id, options)),
2143
2329
  watchSession: (id, watched) => route(id, () => research.watch(id, watched), () => inner.watchSession(id, watched)),
2144
- sendPrompt: (id, prompt, mode, attachments, persist, files, operationId) => route(id, () => {
2330
+ sendPrompt: (id, prompt, mode, attachments, persist, files, operationId, avatarPersona) => route(id, () => {
2331
+ if (avatarPersona !== void 0) return deny();
2145
2332
  if (attachments?.length || files?.length) return deny();
2146
2333
  if (operationId === void 0) throw new SessionOperationError("OPERATION_ID_REQUIRED", "A stable operation ID is required");
2147
2334
  return research.sendPrompt(id, prompt, mode, operationId);
2148
- }, () => inner.sendPrompt(id, prompt, mode, attachments, persist, files, operationId)),
2335
+ }, () => inner.sendPrompt(
2336
+ id,
2337
+ prompt,
2338
+ mode,
2339
+ attachments,
2340
+ persist,
2341
+ files,
2342
+ operationId,
2343
+ ...avatarPersona === void 0 ? [] : [avatarPersona]
2344
+ )),
2149
2345
  abortSession: (id) => route(id, () => research.abort(id), () => inner.abortSession(id)),
2150
2346
  getSessionConfiguration: (id) => route(id, () => research.configuration(id), () => inner.getSessionConfiguration(id)),
2151
2347
  getSessionCapabilities: (id) => id !== void 0 && owned(id) ? { agentModes: ["interactive"], supportsReasoningEffort: false, supportsFileAttachments: false, supportsPlanRead: false, agentName: "Side chat" } : inner.getSessionCapabilities?.(id) ?? inner,
@@ -2220,12 +2416,29 @@ function toErrorDetails(error) {
2220
2416
  };
2221
2417
  }
2222
2418
 
2223
- // src/pc-agent.ts
2419
+ // src/service/transport/relay-socket.ts
2224
2420
  import { io } from "socket.io-client";
2421
+ function createRelaySocket(uri, options) {
2422
+ const socket = io(uri, { ...options, autoConnect: false });
2423
+ socket.on("connect_error", (error) => {
2424
+ const manager = socket.io;
2425
+ const transports = manager.opts.transports;
2426
+ if (error.message !== "timeout" || manager.engine.readyState !== "opening" || !manager.opts.tryAllTransports || transports === void 0 || transports.length < 2 || !transports.every((transport) => typeof transport === "string")) return;
2427
+ const failedTransport = manager.engine.transport.name;
2428
+ const failedIndex = transports.findIndex((transport) => transport === failedTransport);
2429
+ if (failedIndex < 0) return;
2430
+ manager.opts.transports = [
2431
+ ...transports.slice(failedIndex + 1),
2432
+ ...transports.slice(0, failedIndex + 1)
2433
+ ];
2434
+ });
2435
+ if (options.autoConnect !== false) socket.connect();
2436
+ return socket;
2437
+ }
2225
2438
 
2226
2439
  // src/service/requests/session-communication.ts
2227
2440
  import { AsyncLocalStorage } from "node:async_hooks";
2228
- import { createHash as createHash6 } from "node:crypto";
2441
+ import { createHash as createHash8 } from "node:crypto";
2229
2442
  import { join as join7 } from "node:path";
2230
2443
  import { performance as performance2 } from "node:perf_hooks";
2231
2444
 
@@ -2610,7 +2823,7 @@ var Work = class {
2610
2823
  GIT_DIR: join6(cwd, "no-repository"),
2611
2824
  GIT_CONFIG_NOSYSTEM: "1",
2612
2825
  GIT_ATTR_NOSYSTEM: "1",
2613
- GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null"
2826
+ GIT_CONFIG_GLOBAL: join6(cwd, "gitconfig")
2614
2827
  } : {}
2615
2828
  }
2616
2829
  }, (error, output2, stderr) => {
@@ -2939,6 +3152,7 @@ async function patch(context, change, work, scratchDirectory) {
2939
3152
  await writeFile2(file, content, { flag: "wx", mode: 384 });
2940
3153
  if (process.platform !== "win32") await chmod(file, mode === "100755" ? 448 : 384);
2941
3154
  }
3155
+ await writeFile2(join6(scratch, "gitconfig"), "", { flag: "wx", mode: 384 });
2942
3156
  text = decode(await work.git(scratch, [
2943
3157
  "-c",
2944
3158
  "core.quotePath=false",
@@ -3097,6 +3311,430 @@ var SessionGitDiffReader = class {
3097
3311
  }
3098
3312
  };
3099
3313
 
3314
+ // src/timeline-image-cache.ts
3315
+ import { createHash as createHash6 } from "node:crypto";
3316
+ var TimelineAssetError = class extends Error {
3317
+ constructor(code, message, options) {
3318
+ super(message, options);
3319
+ this.code = code;
3320
+ }
3321
+ code;
3322
+ };
3323
+ var TimelineImageCache = class {
3324
+ constructor(maxBytes = 32 * 1024 * 1024) {
3325
+ this.maxBytes = maxBytes;
3326
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < TIMELINE_IMAGE_MAX_BYTES) {
3327
+ throw new Error("Image cache must hold at least one maximum-size image");
3328
+ }
3329
+ }
3330
+ maxBytes;
3331
+ #assets = /* @__PURE__ */ new Map();
3332
+ #bytes = 0;
3333
+ get bytes() {
3334
+ return this.#bytes;
3335
+ }
3336
+ referenceItem = (sessionId, item) => {
3337
+ if (!("attachments" in item) || item.attachments === void 0) return item;
3338
+ return { ...item, attachments: item.attachments.map((attachment) => this.#reference(sessionId, attachment)) };
3339
+ };
3340
+ read(sessionId, request) {
3341
+ const key = JSON.stringify([sessionId, request.sha256]);
3342
+ const asset = this.#assets.get(key);
3343
+ if (asset === void 0) throw new TimelineAssetError("IMAGE_ABSENT", "The image is no longer cached on this PC");
3344
+ if (request.offset >= asset.bytes.length) {
3345
+ throw new TimelineAssetError("IMAGE_OFFSET_INVALID", "The image chunk offset exceeds its length");
3346
+ }
3347
+ this.#assets.delete(key);
3348
+ this.#assets.set(key, asset);
3349
+ const end = Math.min(request.offset + TIMELINE_IMAGE_CHUNK_BYTES, asset.bytes.length);
3350
+ return TimelineImageChunkSchema.parse({
3351
+ sha256: request.sha256,
3352
+ offset: request.offset,
3353
+ totalBytes: asset.bytes.length,
3354
+ mimeType: asset.mimeType,
3355
+ data: asset.bytes.subarray(request.offset, end).toString("base64"),
3356
+ eof: end === asset.bytes.length
3357
+ });
3358
+ }
3359
+ readAsset(sessionId, sha256) {
3360
+ const key = JSON.stringify([sessionId, sha256]);
3361
+ const asset = this.#assets.get(key);
3362
+ if (asset === void 0) throw new TimelineAssetError("IMAGE_ABSENT", "The image is no longer cached on this PC");
3363
+ this.#assets.delete(key);
3364
+ this.#assets.set(key, asset);
3365
+ return { bytes: Buffer.from(asset.bytes), mimeType: asset.mimeType };
3366
+ }
3367
+ clearSession(sessionId) {
3368
+ const prefix = JSON.stringify([sessionId]).slice(0, -1) + ",";
3369
+ for (const [key, asset] of this.#assets) {
3370
+ if (key.startsWith(prefix)) {
3371
+ this.#assets.delete(key);
3372
+ this.#bytes -= asset.bytes.length;
3373
+ }
3374
+ }
3375
+ }
3376
+ clear() {
3377
+ this.#assets.clear();
3378
+ this.#bytes = 0;
3379
+ }
3380
+ #reference(sessionId, attachment) {
3381
+ if (attachment.kind !== "image" || attachment.data === void 0) return attachment;
3382
+ const parsed = ConversationAttachmentSchema.parse(attachment);
3383
+ const bytes = Buffer.from(attachment.data, "base64");
3384
+ if (bytes.length !== parsed.byteLength || createHash6("sha256").update(bytes).digest("hex") !== parsed.sha256 || !hasSignature(bytes, parsed.mimeType)) {
3385
+ throw new TimelineAssetError("IMAGE_INVALID", "Image content does not match its metadata");
3386
+ }
3387
+ const key = JSON.stringify([sessionId, parsed.sha256]);
3388
+ const previous = this.#assets.get(key);
3389
+ if (previous !== void 0) this.#bytes -= previous.bytes.length;
3390
+ this.#assets.delete(key);
3391
+ this.#assets.set(key, { bytes, mimeType: parsed.mimeType ?? "" });
3392
+ this.#bytes += bytes.length;
3393
+ while (this.#bytes > this.maxBytes) {
3394
+ const oldest = this.#assets.entries().next().value;
3395
+ if (oldest === void 0) break;
3396
+ this.#assets.delete(oldest[0]);
3397
+ this.#bytes -= oldest[1].bytes.length;
3398
+ }
3399
+ const { data: _data, ...metadata } = parsed;
3400
+ return { ...metadata, imageRef: "sha256" };
3401
+ }
3402
+ };
3403
+ function hasSignature(bytes, mimeType) {
3404
+ switch (mimeType) {
3405
+ case "image/png":
3406
+ return bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
3407
+ case "image/jpeg":
3408
+ return bytes.subarray(0, 3).equals(Buffer.from([255, 216, 255]));
3409
+ case "image/gif":
3410
+ return ["GIF87a", "GIF89a"].includes(bytes.subarray(0, 6).toString("ascii"));
3411
+ case "image/webp":
3412
+ return bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP";
3413
+ default:
3414
+ return false;
3415
+ }
3416
+ }
3417
+
3418
+ // src/session-image-uploader.ts
3419
+ import { createHash as createHash7 } from "node:crypto";
3420
+ import sharp2 from "sharp";
3421
+ import { z as z6 } from "zod";
3422
+ var MAX_ACTIVE_UPLOADS = 3;
3423
+ var MAX_JSON_BYTES = 16 * 1024;
3424
+ var REQUEST_TIMEOUT_MS = 2e4;
3425
+ var UPLOAD_TIMEOUT_MS = 7e4;
3426
+ var MAX_GRANT_LIFETIME_MS = 10 * 6e4 + 3e4;
3427
+ var UploaderConfigSchema = z6.object({
3428
+ relayUrl: SessionImageRelayUrlSchema,
3429
+ relayToken: z6.string().min(24).max(4096).regex(/^[\x21-\x7e]+$/u),
3430
+ userId: IdSchema.refine((value) => /^[\x20-\x7e]+$/u.test(value)),
3431
+ machineId: IdSchema,
3432
+ storageAccount: SessionImagesConfigSchema.shape.storageAccount,
3433
+ storageContainer: SessionImagesConfigSchema.shape.storageContainer
3434
+ });
3435
+ function safeCause(error) {
3436
+ if (error instanceof SessionImageUploadError) return error;
3437
+ if (error instanceof z6.ZodError) return new Error("Schema validation failed.");
3438
+ const result = new Error("Image operation failed.");
3439
+ if (error instanceof Error) {
3440
+ if (["TypeError", "SyntaxError", "AbortError", "TimeoutError"].includes(error.name)) result.name = error.name;
3441
+ const code = "code" in error ? error.code : void 0;
3442
+ if (typeof code === "string" && ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN", "UND_ERR_CONNECT_TIMEOUT"].includes(code)) {
3443
+ result.cause = { code };
3444
+ }
3445
+ }
3446
+ return result;
3447
+ }
3448
+ var SessionImageUploadError = class extends Error {
3449
+ constructor(code, stage, status, cause) {
3450
+ super(`${code} (${stage})`, cause === void 0 ? void 0 : { cause: safeCause(cause) });
3451
+ this.code = code;
3452
+ this.stage = stage;
3453
+ this.status = status;
3454
+ this.name = "SessionImageUploadError";
3455
+ }
3456
+ code;
3457
+ stage;
3458
+ status;
3459
+ };
3460
+ function digest(bytes) {
3461
+ return createHash7("sha256").update(bytes).digest("hex");
3462
+ }
3463
+ function assert(condition, code, stage) {
3464
+ if (!condition) throw new SessionImageUploadError(code, stage);
3465
+ }
3466
+ function parse(schema, input, stage) {
3467
+ const result = schema.safeParse(input);
3468
+ if (!result.success) throw new SessionImageUploadError(
3469
+ stage === "config" ? "IMAGE_CONFIG_INVALID" : stage === "source" ? "IMAGE_CONTENT_INVALID" : "IMAGE_RESPONSE_INVALID",
3470
+ stage,
3471
+ void 0,
3472
+ result.error
3473
+ );
3474
+ return result.data;
3475
+ }
3476
+ function abortable(operation, signal, stage) {
3477
+ return new Promise((resolve5, reject) => {
3478
+ const abort = () => reject(new SessionImageUploadError("IMAGE_ABORTED", stage));
3479
+ if (signal.aborted) abort();
3480
+ else signal.addEventListener("abort", abort, { once: true });
3481
+ void operation.then((value) => {
3482
+ signal.removeEventListener("abort", abort);
3483
+ resolve5(value);
3484
+ }, (error) => {
3485
+ signal.removeEventListener("abort", abort);
3486
+ reject(error);
3487
+ });
3488
+ });
3489
+ }
3490
+ var SessionImageUploader = class {
3491
+ #config;
3492
+ #fetch;
3493
+ #active = /* @__PURE__ */ new Set();
3494
+ #closed = false;
3495
+ constructor(config, dependencies = {}) {
3496
+ this.#config = parse(UploaderConfigSchema, config, "config");
3497
+ this.#fetch = dependencies.fetch ?? globalThis.fetch;
3498
+ }
3499
+ close() {
3500
+ this.#closed = true;
3501
+ for (const controller of this.#active) controller.abort();
3502
+ }
3503
+ async upload(sessionId, source, signal, assertCurrent = () => void 0) {
3504
+ assert(!this.#closed, "IMAGE_UPLOADER_CLOSED", "source");
3505
+ assert(!signal?.aborted, "IMAGE_ABORTED", "source");
3506
+ const revalidate = (stage) => {
3507
+ try {
3508
+ assertCurrent();
3509
+ } catch (error) {
3510
+ throw new SessionImageUploadError("IMAGE_ACCESS_REVOKED", stage, void 0, error);
3511
+ }
3512
+ };
3513
+ revalidate("source");
3514
+ assert(this.#active.size < MAX_ACTIVE_UPLOADS, "IMAGE_CAPACITY_EXCEEDED", "source");
3515
+ assert(
3516
+ Buffer.isBuffer(source) && source.length > 0 && source.length <= SESSION_IMAGE_MAX_BYTES,
3517
+ "IMAGE_CONTENT_INVALID",
3518
+ "source"
3519
+ );
3520
+ const scope = parse(SessionImageScopeSchema, { machineId: this.#config.machineId, sessionId }, "source");
3521
+ const controller = new AbortController();
3522
+ this.#active.add(controller);
3523
+ const abort = () => controller.abort();
3524
+ signal?.addEventListener("abort", abort, { once: true });
3525
+ let timedOut = false;
3526
+ const timer = setTimeout(() => {
3527
+ timedOut = true;
3528
+ controller.abort();
3529
+ }, UPLOAD_TIMEOUT_MS);
3530
+ timer.unref();
3531
+ try {
3532
+ const bytes = Buffer.from(source);
3533
+ const dimensions = await this.#dimensions(bytes);
3534
+ const hash2 = digest(bytes);
3535
+ const request = parse(SessionImageUploadRequestSchema, { ...scope, sizeBytes: bytes.length }, "source");
3536
+ revalidate("reserve");
3537
+ const upload = await this.#json(
3538
+ `${SESSION_IMAGE_API_PATH}/uploads`,
3539
+ request,
3540
+ 201,
3541
+ SessionImageUploadResultSchema,
3542
+ "reserve",
3543
+ controller.signal
3544
+ );
3545
+ const target = this.#target(upload, scope.sessionId);
3546
+ let ambiguous;
3547
+ revalidate("put");
3548
+ try {
3549
+ await this.#request(target, {
3550
+ method: "PUT",
3551
+ headers: upload.mode === "direct" ? upload.headers : { ...this.#headers(), ...upload.headers },
3552
+ body: new Uint8Array(bytes)
3553
+ }, "put", controller.signal, async (response) => {
3554
+ if (response.status !== 201) throw new SessionImageUploadError("IMAGE_HTTP_ERROR", "put", response.status);
3555
+ });
3556
+ } catch (error) {
3557
+ 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;
3558
+ ambiguous = error;
3559
+ }
3560
+ revalidate("complete");
3561
+ try {
3562
+ const committed = await this.#json(
3563
+ `${SESSION_IMAGE_API_PATH}/${upload.imageId}/complete`,
3564
+ scope,
3565
+ 200,
3566
+ SessionImageCommitResultSchema,
3567
+ "complete",
3568
+ controller.signal
3569
+ );
3570
+ assert(
3571
+ 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(),
3572
+ "IMAGE_COMMIT_MISMATCH",
3573
+ "complete"
3574
+ );
3575
+ revalidate("complete");
3576
+ return committed;
3577
+ } catch (error) {
3578
+ if (ambiguous === void 0 || error instanceof SessionImageUploadError && error.code === "IMAGE_ACCESS_REVOKED") {
3579
+ throw error;
3580
+ }
3581
+ const failure = new SessionImageUploadError("IMAGE_COMPLETION_FAILED", "complete", void 0, error);
3582
+ failure.cause = new AggregateError([ambiguous, safeCause(error)], "PUT outcome and completion failed.");
3583
+ throw failure;
3584
+ }
3585
+ } catch (error) {
3586
+ if (controller.signal.aborted) {
3587
+ throw new SessionImageUploadError(
3588
+ timedOut ? "IMAGE_TIMEOUT" : "IMAGE_ABORTED",
3589
+ error instanceof SessionImageUploadError ? error.stage : "source",
3590
+ void 0,
3591
+ error
3592
+ );
3593
+ }
3594
+ if (error instanceof SessionImageUploadError) throw error;
3595
+ throw new SessionImageUploadError("IMAGE_CONTENT_INVALID", "source", void 0, error);
3596
+ } finally {
3597
+ clearTimeout(timer);
3598
+ signal?.removeEventListener("abort", abort);
3599
+ this.#active.delete(controller);
3600
+ }
3601
+ }
3602
+ async #dimensions(bytes) {
3603
+ assert(
3604
+ bytes.length >= 20 && bytes.toString("ascii", 0, 4) === "RIFF" && bytes.toString("ascii", 8, 12) === "WEBP" && bytes.readUInt32LE(4) + 8 === bytes.length,
3605
+ "IMAGE_CONTENT_INVALID",
3606
+ "source"
3607
+ );
3608
+ const decoder = sharp2(bytes, { limitInputPixels: SESSION_IMAGE_MAX_PIXELS, failOn: "warning" });
3609
+ try {
3610
+ const metadata = await decoder.metadata();
3611
+ 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");
3612
+ return { width: metadata.width, height: metadata.height };
3613
+ } finally {
3614
+ decoder.destroy();
3615
+ }
3616
+ }
3617
+ #headers() {
3618
+ return { authorization: `Bearer ${this.#config.relayToken}`, "x-agentknight-user-id": this.#config.userId };
3619
+ }
3620
+ #target(upload, sessionId) {
3621
+ const now = Date.now();
3622
+ assert(Number.isSafeInteger(upload.uploadExpiresAt) && upload.uploadExpiresAt > now && upload.uploadExpiresAt <= now + MAX_GRANT_LIFETIME_MS, "IMAGE_UPLOAD_EXPIRED", "reserve");
3623
+ assert(
3624
+ upload.headers["content-type"] === "image/webp" && (upload.headers["if-none-match"] === void 0 || upload.headers["if-none-match"] === "*"),
3625
+ "IMAGE_UPLOAD_TARGET_INVALID",
3626
+ "reserve"
3627
+ );
3628
+ if (upload.mode === "proxy") {
3629
+ const path2 = `${SESSION_IMAGE_API_PATH}/${upload.imageId}/content`;
3630
+ const url2 = new URL(upload.uploadUrl, this.#config.relayUrl);
3631
+ assert(
3632
+ 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,
3633
+ "IMAGE_UPLOAD_TARGET_INVALID",
3634
+ "reserve"
3635
+ );
3636
+ return url2.href;
3637
+ }
3638
+ const { storageAccount, storageContainer, userId, machineId } = this.#config;
3639
+ const host = `${storageAccount}.blob.core.windows.net`;
3640
+ const path = `/${storageContainer}/uploads/${digest(userId)}/${digest(machineId)}/${upload.imageId}.webp`;
3641
+ const url = new URL(upload.uploadUrl);
3642
+ const query = url.searchParams;
3643
+ const allowed = /* @__PURE__ */ new Set(["sv", "spr", "st", "se", "sr", "sp", "sig", "skoid", "sktid", "skt", "ske", "sks", "skv"]);
3644
+ assert(
3645
+ 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",
3646
+ "IMAGE_UPLOAD_TARGET_INVALID",
3647
+ "reserve"
3648
+ );
3649
+ const expiresAt = Date.parse(query.get("se") ?? "");
3650
+ const startsAt = Date.parse(query.get("st") ?? "");
3651
+ const keyStartsAt = Date.parse(query.get("skt") ?? "");
3652
+ const keyExpiresAt = Date.parse(query.get("ske") ?? "");
3653
+ assert(
3654
+ 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,
3655
+ "IMAGE_UPLOAD_TARGET_INVALID",
3656
+ "reserve"
3657
+ );
3658
+ return url.href;
3659
+ }
3660
+ async #json(path, body, status, schema, stage, signal) {
3661
+ return this.#request(`${this.#config.relayUrl}${path}`, {
3662
+ method: "POST",
3663
+ headers: { ...this.#headers(), "content-type": "application/json" },
3664
+ body: JSON.stringify(body)
3665
+ }, stage, signal, async (response, requestSignal) => {
3666
+ if (response.status !== status) throw new SessionImageUploadError("IMAGE_HTTP_ERROR", stage, response.status);
3667
+ const contentLength = response.headers.get("content-length");
3668
+ assert(
3669
+ contentLength === null || /^\d+$/u.test(contentLength) && Number(contentLength) <= MAX_JSON_BYTES,
3670
+ "IMAGE_RESPONSE_TOO_LARGE",
3671
+ stage
3672
+ );
3673
+ assert(response.body !== null, "IMAGE_RESPONSE_INVALID", stage);
3674
+ const reader = response.body.getReader();
3675
+ const chunks = [];
3676
+ let size = 0;
3677
+ try {
3678
+ while (true) {
3679
+ const next = await abortable(reader.read(), requestSignal, stage);
3680
+ requestSignal.throwIfAborted();
3681
+ if (next.done) break;
3682
+ size += next.value.byteLength;
3683
+ assert(size <= MAX_JSON_BYTES, "IMAGE_RESPONSE_TOO_LARGE", stage);
3684
+ chunks.push(next.value);
3685
+ }
3686
+ let value;
3687
+ try {
3688
+ value = JSON.parse(Buffer.concat(chunks, size).toString("utf8"));
3689
+ } catch (error) {
3690
+ throw new SessionImageUploadError("IMAGE_RESPONSE_INVALID", stage, void 0, error);
3691
+ }
3692
+ return parse(schema, value, stage);
3693
+ } finally {
3694
+ reader.releaseLock();
3695
+ }
3696
+ });
3697
+ }
3698
+ async #request(url, init, stage, signal, consume) {
3699
+ assert(!signal.aborted, "IMAGE_ABORTED", stage);
3700
+ const controller = new AbortController();
3701
+ const abort = () => controller.abort();
3702
+ signal.addEventListener("abort", abort, { once: true });
3703
+ let timedOut = false;
3704
+ const timer = setTimeout(() => {
3705
+ timedOut = true;
3706
+ controller.abort();
3707
+ }, REQUEST_TIMEOUT_MS);
3708
+ timer.unref();
3709
+ try {
3710
+ const response = await this.#fetch(url, {
3711
+ ...init,
3712
+ signal: controller.signal,
3713
+ redirect: "error",
3714
+ credentials: "omit",
3715
+ cache: "no-store",
3716
+ referrerPolicy: "no-referrer"
3717
+ });
3718
+ try {
3719
+ controller.signal.throwIfAborted();
3720
+ assert(!response.redirected, "IMAGE_RESPONSE_INVALID", stage);
3721
+ return await consume(response, controller.signal);
3722
+ } finally {
3723
+ await response.body?.cancel();
3724
+ }
3725
+ } catch (error) {
3726
+ if (controller.signal.aborted) {
3727
+ throw new SessionImageUploadError(timedOut ? "IMAGE_TIMEOUT" : "IMAGE_ABORTED", stage, void 0, error);
3728
+ }
3729
+ if (error instanceof SessionImageUploadError) throw error;
3730
+ throw new SessionImageUploadError("IMAGE_NETWORK_ERROR", stage, void 0, error);
3731
+ } finally {
3732
+ clearTimeout(timer);
3733
+ signal.removeEventListener("abort", abort);
3734
+ }
3735
+ }
3736
+ };
3737
+
3100
3738
  // src/service/requests/request-errors.ts
3101
3739
  function operationErrorCode(agentId, operation, error) {
3102
3740
  if (isSessionRestoreFailure(error)) return "SESSION_RESTORE_FAILED";
@@ -3113,7 +3751,7 @@ function unsupportedOperation(error) {
3113
3751
  // src/service/requests/communication-operations.ts
3114
3752
  var compress2 = promisify2(gzip3);
3115
3753
  function createCommunicationOperations(options) {
3116
- const { adapter, publications } = options;
3754
+ const { adapter, publications, imageBlob } = options;
3117
3755
  const directoryLister = new SessionDirectoryLister(async (sessionId) => adapter.getSessionDirectoryMetadata?.(sessionId));
3118
3756
  const fileReader = new SessionFileReader(async (sessionId) => adapter.getSessionDirectoryMetadata?.(sessionId));
3119
3757
  const gitDiffReader = new SessionGitDiffReader(async (sessionId) => adapter.getSessionDirectoryMetadata?.(sessionId), options.gitDiffDirectory);
@@ -3122,6 +3760,24 @@ function createCommunicationOperations(options) {
3122
3760
  const renameSession = adapter.renameSession?.bind(adapter);
3123
3761
  return [
3124
3762
  ...options.workbenchOperations,
3763
+ ...imageBlob === void 0 ? [] : [
3764
+ sessionCommunicationOperation({
3765
+ operation: TIMELINE_IMAGE_BLOB_OPERATION,
3766
+ version: TIMELINE_IMAGE_BLOB_VERSION,
3767
+ schema: TimelineImageBlobRequestSchema,
3768
+ handle: async ({ sha256 }, request) => {
3769
+ const assertCurrent = options.captureRequestAccess();
3770
+ assertCurrent();
3771
+ const result = await imageBlob.publish(request.sessionId, sha256, assertCurrent);
3772
+ assertCurrent();
3773
+ return SessionCommunicationDataSchema.parse(result);
3774
+ },
3775
+ onError: (error) => ({
3776
+ code: error instanceof TimelineAssetError || error instanceof SessionImageUploadError || error instanceof SessionNotSharedError || error instanceof AgentSessionRenameError ? error.code : "IMAGE_BLOB_FAILED",
3777
+ message: "The cloud image is unavailable"
3778
+ })
3779
+ })
3780
+ ],
3125
3781
  ...renameSession === void 0 ? [] : [
3126
3782
  sessionCommunicationOperation({
3127
3783
  operation: SESSION_RENAME_OPERATION,
@@ -3198,16 +3854,16 @@ function createCommunicationOperations(options) {
3198
3854
  }),
3199
3855
  onError: (error) => unsupportedOperation(error) ?? (error instanceof SessionDirectoryError ? { code: error.code, message: error.message } : { code: "DIRECTORY_UNAVAILABLE", message: "The directory is unavailable" })
3200
3856
  }),
3201
- sessionCommunicationOperation({
3857
+ ...[SESSION_FILE_READ_VERSION, SESSION_FILE_READ_V2_VERSION].map((version) => sessionCommunicationOperation({
3202
3858
  operation: SESSION_FILE_READ_OPERATION,
3203
- version: SESSION_FILE_READ_VERSION,
3859
+ version,
3204
3860
  schema: SessionFileReadDataSchema,
3205
- handle: ({ path, rootRevision }, request) => fileReader.read(request.sessionId, path, rootRevision),
3861
+ handle: ({ path, rootRevision }, request) => fileReader.read(request.sessionId, path, rootRevision, version),
3206
3862
  onError: (error) => unsupportedOperation(error) ?? {
3207
3863
  code: error instanceof SessionFileReadError ? error.code : "FILE_UNAVAILABLE",
3208
3864
  message: "The file preview is unavailable"
3209
3865
  }
3210
- }),
3866
+ })),
3211
3867
  sessionCommunicationOperation({
3212
3868
  operation: SESSION_RESOURCE_READ_OPERATION,
3213
3869
  version: SESSION_RESOURCE_READ_VERSION,
@@ -3269,6 +3925,7 @@ var SessionCommunicationHandler = class {
3269
3925
  return assertCurrent;
3270
3926
  },
3271
3927
  incrementalOperations: options.incrementalOperations.filter((operation) => config.timelineIncrementalEnabled !== false || operation.operation === TIMELINE_IMAGE_OPERATION),
3928
+ ...options.imageBlob === void 0 ? {} : { imageBlob: options.imageBlob },
3272
3929
  workbenchOperations: this.#operations === void 0 ? [] : createWorkbenchOperations({
3273
3930
  ...workbench === void 0 ? {} : { runtime: workbench },
3274
3931
  ...researchWorkbench === void 0 ? {} : { research: researchWorkbench },
@@ -3437,7 +4094,7 @@ var SessionCommunicationHandler = class {
3437
4094
  }
3438
4095
  const clientId = scope.clientId;
3439
4096
  const owner = [config.userId, machineId, clientId, request.sessionId];
3440
- const key = (parts) => createHash6("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 24);
4097
+ const key = (parts) => createHash8("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 24);
3441
4098
  const knownSource = this.#researchParents.get(request.sessionId) ?? adapter.sessionPublicationSource?.(request.sessionId) ?? request.sessionId;
3442
4099
  const arrivalPublication = adapter.isSessionVisible?.(knownSource) === true ? publications.capture(knownSource) : void 0;
3443
4100
  let researchParent = this.#researchParents.get(request.sessionId);
@@ -3470,7 +4127,7 @@ var SessionCommunicationHandler = class {
3470
4127
  }
3471
4128
  if (researchParent !== void 0) {
3472
4129
  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);
4130
+ 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
4131
  if (!unifiedSync && !["research.timeline", "research.send", "research.abort", "research.delete", "operation.get", "insights.read"].includes(request.operation)) {
3475
4132
  reject("research-isolated");
3476
4133
  emit("session.communication", sessionCommunicationError(request, {
@@ -3617,7 +4274,7 @@ var SessionCommunicationHandler = class {
3617
4274
  };
3618
4275
 
3619
4276
  // src/desktop-workspaces.ts
3620
- import { createHash as createHash7 } from "node:crypto";
4277
+ import { createHash as createHash9 } from "node:crypto";
3621
4278
  import { readdir, realpath as realpath5, stat as stat6 } from "node:fs/promises";
3622
4279
  import { homedir as homedir2 } from "node:os";
3623
4280
  import { basename as basename2, join as join8, normalize } from "node:path";
@@ -3633,7 +4290,7 @@ function desktopRootCandidates(environment2 = process.env) {
3633
4290
  }
3634
4291
  function workspaceId(path) {
3635
4292
  const normalizedPath = workspacePathKey(path);
3636
- return createHash7("sha256").update(normalizedPath).digest("base64url").slice(0, 32);
4293
+ return createHash9("sha256").update(normalizedPath).digest("base64url").slice(0, 32);
3637
4294
  }
3638
4295
  function workspacePathKey(path) {
3639
4296
  const normalizedPath = normalize(path);
@@ -3950,14 +4607,14 @@ var SessionOptionsHandler = class {
3950
4607
  ]));
3951
4608
  if (modelResult.status === "fulfilled" && modelResult.value.length > 0) availableModels = modelResult.value;
3952
4609
  if (configurationResult.status === "fulfilled") configuration = configurationResult.value;
3953
- for (const [stage, result] of [
4610
+ for (const [stage, result2] of [
3954
4611
  ["configuration", configurationResult],
3955
4612
  ["models", modelResult],
3956
4613
  ["workspaces", workspaceResult]
3957
4614
  ]) {
3958
- if (result.status === "rejected" && isSessionRestoreFailure(result.reason)) {
4615
+ if (result2.status === "rejected" && isSessionRestoreFailure(result2.reason)) {
3959
4616
  failureStage = stage;
3960
- throw result.reason;
4617
+ throw result2.reason;
3961
4618
  }
3962
4619
  }
3963
4620
  if (configurationResult.status === "rejected") {
@@ -3979,7 +4636,8 @@ var SessionOptionsHandler = class {
3979
4636
  failureStage = "models";
3980
4637
  throw modelFailure ?? new Error("No coding-agent models are available. Select an authenticated coding agent on the PC.");
3981
4638
  }
3982
- const environment2 = await sessionEnvironment;
4639
+ const progressive = request.sessionId !== void 0 && request.capabilities?.includes(SESSION_OPTIONS_PROGRESSIVE_CAPABILITY) === true;
4640
+ const environment2 = progressive ? void 0 : await sessionEnvironment;
3983
4641
  const worktreeCreationSupported = request.sessionId === void 0 && request.capabilities?.includes(SESSION_WORKTREE_CREATION_CAPABILITY) === true;
3984
4642
  let worktreeChoices;
3985
4643
  if (worktreeCreationSupported && request.workspaceId !== void 0) {
@@ -3991,7 +4649,7 @@ var SessionOptionsHandler = class {
3991
4649
  }
3992
4650
  const capabilities = adapter.getSessionCapabilities?.(request.sessionId) ?? adapter;
3993
4651
  this.#errors.recovered(errorKey);
3994
- emit("session.options.result", {
4652
+ const result = {
3995
4653
  ok: true,
3996
4654
  requestId: request.requestId,
3997
4655
  machineId,
@@ -4013,7 +4671,21 @@ var SessionOptionsHandler = class {
4013
4671
  ...environment2 === void 0 ? {} : { environment: environment2 },
4014
4672
  agentName: "agentName" in capabilities && typeof capabilities.agentName === "string" ? capabilities.agentName : codingAgentPresentation(adapter.agentId).name,
4015
4673
  ...current?.configOptions === void 0 ? {} : { configOptions: current.configOptions }
4674
+ };
4675
+ if (!scope.isCurrent()) return;
4676
+ emit("session.options.result", {
4677
+ ...result,
4678
+ ...progressive ? { environmentPending: true } : {}
4016
4679
  });
4680
+ if (progressive) {
4681
+ const environment3 = await sessionEnvironment;
4682
+ if (!scope.isCurrent()) return;
4683
+ adapter.getSessionCapabilities?.(request.sessionId);
4684
+ emit("session.options.result", {
4685
+ ...result,
4686
+ ...environment3 === void 0 ? {} : { environment: environment3 }
4687
+ });
4688
+ }
4017
4689
  const plan = await sessionPlan;
4018
4690
  if (plan !== void 0 && scope.isCurrent()) {
4019
4691
  emit("conversation.item.upsert", plan);
@@ -4395,13 +5067,17 @@ var SessionCommandsHandler = class {
4395
5067
  };
4396
5068
 
4397
5069
  // src/service/requests/session-prompt.ts
4398
- import { createHash as createHash8 } from "node:crypto";
5070
+ import { createHash as createHash10 } from "node:crypto";
4399
5071
  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 }) => ({
5072
+ const fingerprint = 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
5073
  name,
4402
5074
  mimeType,
4403
5075
  sha256
4404
- })) ?? [])).digest("base64url");
5076
+ })) ?? []));
5077
+ if (request.avatarPersona !== void 0) {
5078
+ fingerprint.update("\0").update(JSON.stringify(request.avatarPersona));
5079
+ }
5080
+ return fingerprint.digest("base64url");
4405
5081
  }
4406
5082
  var SessionPromptHandler = class {
4407
5083
  constructor(options) {
@@ -4423,6 +5099,9 @@ var SessionPromptHandler = class {
4423
5099
  if (adapter.isSessionVisible?.(request.sessionId) === false) {
4424
5100
  throw new Error("This session's coding agent is not shared or is unavailable on the PC");
4425
5101
  }
5102
+ if (request.avatarPersona !== void 0 && adapter.agentId !== "copilot" && adapter.agentId !== "managed") {
5103
+ throw new Error("Avatar personas require Copilot; nothing was submitted");
5104
+ }
4426
5105
  const accepted = await diagnostics.run(() => this.#operations.run({
4427
5106
  operationId: request.operationId,
4428
5107
  fingerprint: promptOperationFingerprint(request),
@@ -4441,7 +5120,8 @@ var SessionPromptHandler = class {
4441
5120
  request.attachments,
4442
5121
  this.options.supportsPersistentInteractions(context.clientId),
4443
5122
  request.fileAttachments,
4444
- request.operationId
5123
+ request.operationId,
5124
+ ...request.avatarPersona === void 0 ? [] : [request.avatarPersona]
4445
5125
  )
4446
5126
  }));
4447
5127
  emit("session.prompt.result", {
@@ -4560,63 +5240,95 @@ var SessionTimelinePageHandler = class {
4560
5240
  if (request.metadataOnly === true && request.assistantPreview !== true && adapter.supportsSessionListUpdates !== true) {
4561
5241
  throw new Error("Passive session metadata is not supported by this runtime");
4562
5242
  }
4563
- const page = await diagnostics.run(() => measureOperationPhase("read", () => adapter.readSessionTimelinePage(
4564
- request.sessionId,
4565
- request.metadataOnly === true ? {
4566
- pageSize: request.pageSize,
4567
- metadataOnly: true,
4568
- ...request.assistantPreview === true ? { assistantPreview: true } : {},
4569
- ...request.cursor === void 0 ? {} : { cursor: request.cursor }
4570
- } : request.cursor === void 0 ? { pageSize: request.pageSize } : { cursor: request.cursor, pageSize: request.pageSize }
4571
- )));
4572
- if (diagnostics.activeMs >= 1e3) {
4573
- logger.info({
4574
- ...diagnosticRequest(request),
4575
- ...diagnostics.details(),
4576
- metadataOnly: request.metadataOnly === true
4577
- }, "session.timeline.page.read-slow");
4578
- }
4579
- if (!publications.isCurrent(request.sessionId, publication)) throw new SessionNotSharedError();
4580
- const latestAssistant = request.assistantPreview === true ? [...page.items].reverse().find(isContentfulAssistant) : void 0;
4581
- const preview = latestAssistant === void 0 ? void 0 : assistantPreview(
4582
- latestAssistant,
4583
- (item) => this.options.referenceItem(request.sessionId, item)
4584
- );
4585
- const latestUser = request.assistantPreview === true ? [...page.items].reverse().find((item) => item.kind === "human-message" && item.actor === "human") : void 0;
4586
- const humanPreview = latestUser === void 0 ? void 0 : userPreview(latestUser);
4587
- const responsePage = {
4588
- ...page,
4589
- ...preview === void 0 ? {} : { assistantPreview: preview },
4590
- ...humanPreview === void 0 ? {} : { userPreview: humanPreview },
4591
- ...request.assistantPreview === true ? { previewsComplete: !page.hasMore } : {},
4592
- items: request.metadataOnly === true && request.assistantPreview === true ? page.items.map((item) => item.kind === "assistant-message" || item.kind === "human-message" ? { ...item, content: "", attachments: [] } : item) : page.items
4593
- };
4594
- const result = {
4595
- ok: true,
4596
- requestId: request.requestId,
4597
- machineId,
4598
- sessionId: request.sessionId,
4599
- page: request.imageReferences === true && config.timelineIncrementalEnabled !== false && this.options.imageReferencesEnabled() ? { ...responsePage, items: responsePage.items.map((item) => this.options.referenceItem(request.sessionId, item)) } : responsePage
4600
- };
4601
- logger.debug({
4602
- requestId: request.requestId,
4603
- sessionId: request.sessionId,
4604
- itemCount: page.items.length
4605
- }, "session.timeline.page.ready");
4606
- phase = "encode";
4607
- const { message, metrics } = await this.options.encoder.seal(result, request, {
4608
- enabled: config.timelineCompressionEnabled === true,
4609
- ...scope.clientId === void 0 ? {} : { recipientClientId: scope.clientId },
4610
- ...config.secureMessagePacketBudget === void 0 ? {} : { packetBudget: config.secureMessagePacketBudget }
4611
- });
4612
- if (!scope.isCurrent()) return;
4613
- if (!publications.isCurrent(request.sessionId, publication)) throw new SessionNotSharedError();
4614
- phase = "emit";
4615
- this.options.sendSealed(message);
4616
- this.options.trafficMetrics.record(message, metrics.plaintextBytes, metrics.sealDurationMs);
4617
- this.#errors.recovered(errorKey);
4618
- const timingLevel = metrics.encodeDurationMs + metrics.sealDurationMs >= 1e3 ? "info" : "debug";
4619
- logger[timingLevel](metrics, "transport.timeline.encoding");
5243
+ const references = request.imageReferences === true && config.timelineIncrementalEnabled !== false && this.options.imageReferencesEnabled();
5244
+ let pageSize = request.pageSize;
5245
+ while (true) {
5246
+ let itemCount;
5247
+ try {
5248
+ phase = "read";
5249
+ const page = await diagnostics.run(() => measureOperationPhase("read", () => adapter.readSessionTimelinePage(
5250
+ request.sessionId,
5251
+ {
5252
+ pageSize,
5253
+ ...request.cursor === void 0 ? {} : { cursor: request.cursor },
5254
+ ...request.metadataOnly === true ? {
5255
+ metadataOnly: true,
5256
+ ...request.assistantPreview === true ? { assistantPreview: true } : {}
5257
+ } : {
5258
+ ...references || request.cursor !== void 0 || pageSize !== request.pageSize ? { readOnly: true } : {},
5259
+ ...references ? {
5260
+ imageProjection: { sessionId: request.sessionId, referenceItem: this.options.referenceItem }
5261
+ } : {}
5262
+ }
5263
+ }
5264
+ )));
5265
+ itemCount = page.items.length;
5266
+ if (diagnostics.activeMs >= 1e3) {
5267
+ logger.info({
5268
+ ...diagnosticRequest(request),
5269
+ ...diagnostics.details(),
5270
+ metadataOnly: request.metadataOnly === true
5271
+ }, "session.timeline.page.read-slow");
5272
+ }
5273
+ if (!publications.isCurrent(request.sessionId, publication)) throw new SessionNotSharedError();
5274
+ const latestAssistant = request.assistantPreview === true ? [...page.items].reverse().find(isContentfulAssistant) : void 0;
5275
+ const preview = latestAssistant === void 0 ? void 0 : assistantPreview(
5276
+ latestAssistant,
5277
+ (item) => this.options.referenceItem(request.sessionId, item)
5278
+ );
5279
+ const latestUser = request.assistantPreview === true ? [...page.items].reverse().find((item) => item.kind === "human-message" && item.actor === "human") : void 0;
5280
+ const humanPreview = latestUser === void 0 ? void 0 : userPreview(latestUser);
5281
+ const responsePage = {
5282
+ ...page,
5283
+ ...preview === void 0 ? {} : { assistantPreview: preview },
5284
+ ...humanPreview === void 0 ? {} : { userPreview: humanPreview },
5285
+ ...request.assistantPreview === true ? { previewsComplete: !page.hasMore } : {},
5286
+ items: request.metadataOnly === true && request.assistantPreview === true ? page.items.map((item) => {
5287
+ if (item.kind === "assistant-message" || item.kind === "human-message") {
5288
+ return { ...item, content: "", attachments: [] };
5289
+ }
5290
+ return isContentfulAssistant(item) ? { ...item, content: "" } : item;
5291
+ }) : page.items
5292
+ };
5293
+ const result = {
5294
+ ok: true,
5295
+ requestId: request.requestId,
5296
+ machineId,
5297
+ sessionId: request.sessionId,
5298
+ page: references ? { ...responsePage, items: responsePage.items.map((item) => this.options.referenceItem(request.sessionId, item)) } : responsePage
5299
+ };
5300
+ logger.debug({
5301
+ requestId: request.requestId,
5302
+ sessionId: request.sessionId,
5303
+ itemCount: page.items.length
5304
+ }, "session.timeline.page.ready");
5305
+ phase = "encode";
5306
+ const { message, metrics } = await this.options.encoder.seal(result, request, {
5307
+ enabled: config.timelineCompressionEnabled === true,
5308
+ ...scope.clientId === void 0 ? {} : { recipientClientId: scope.clientId },
5309
+ ...config.secureMessagePacketBudget === void 0 ? {} : { packetBudget: config.secureMessagePacketBudget }
5310
+ });
5311
+ if (!scope.isCurrent()) return;
5312
+ if (!publications.isCurrent(request.sessionId, publication)) throw new SessionNotSharedError();
5313
+ phase = "emit";
5314
+ this.options.sendSealed(message);
5315
+ this.options.trafficMetrics.record(message, metrics.plaintextBytes, metrics.sealDurationMs);
5316
+ this.#errors.recovered(errorKey);
5317
+ const timingLevel = metrics.encodeDurationMs + metrics.sealDurationMs >= 1e3 ? "info" : "debug";
5318
+ logger[timingLevel](metrics, "transport.timeline.encoding");
5319
+ return;
5320
+ } catch (error) {
5321
+ if (!scope.isCurrent()) return;
5322
+ if (!publications.isCurrent(request.sessionId, publication)) throw new SessionNotSharedError();
5323
+ if (phase === "emit" || request.metadataOnly === true || request.assistantPreview === true || pageSize <= 25 || !(error instanceof TimelinePageBudgetError || error instanceof SecureMessageBudgetError) || itemCount !== void 0 && itemCount <= 1) throw error;
5324
+ const nextPageSize = Math.max(25, Math.floor(pageSize / 2));
5325
+ logger.info(
5326
+ { ...diagnosticRequest(request), pageSize, nextPageSize, phase },
5327
+ "session.timeline.page.resized"
5328
+ );
5329
+ pageSize = nextPageSize;
5330
+ }
5331
+ }
4620
5332
  } catch (error) {
4621
5333
  if (!scope.isCurrent()) return;
4622
5334
  this.#errors.failure(errorKey, error, {
@@ -4630,7 +5342,7 @@ var SessionTimelinePageHandler = class {
4630
5342
  machineId,
4631
5343
  sessionId: request.sessionId,
4632
5344
  error: sessionRestoreFailure(error) ?? {
4633
- code: error instanceof SecureMessageBudgetError ? "TRANSPORT_BUDGET_EXCEEDED" : operationErrorCode(adapter.agentId, "TIMELINE_PAGE", error),
5345
+ code: error instanceof SecureMessageBudgetError || error instanceof TimelinePageBudgetError ? "TRANSPORT_BUDGET_EXCEEDED" : operationErrorCode(adapter.agentId, "TIMELINE_PAGE", error),
4634
5346
  message: toErrorDetails(error).message
4635
5347
  }
4636
5348
  });
@@ -4643,8 +5355,8 @@ import { randomUUID as randomUUID5 } from "node:crypto";
4643
5355
  import { setTimeout as delay } from "node:timers/promises";
4644
5356
  import { Role, TaskState } from "@a2a-js/sdk";
4645
5357
  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);
5358
+ import { z as z7 } from "zod";
5359
+ var TextSchema = z7.string().max(16e3).refine((text) => text.trim().length > 0);
4648
5360
  var HTTP_TIMEOUT_MS = 1e4;
4649
5361
  var MAX_RESPONSE_BYTES = 262144;
4650
5362
  function relayEndpoint(relayUrl, target) {
@@ -4803,7 +5515,7 @@ var A2aPeerClient = class {
4803
5515
  };
4804
5516
 
4805
5517
  // src/a2a-server.ts
4806
- import { createHash as createHash9, randomUUID as randomUUID6 } from "node:crypto";
5518
+ import { createHash as createHash11, randomUUID as randomUUID6 } from "node:crypto";
4807
5519
  import {
4808
5520
  AgentCard,
4809
5521
  Role as Role2,
@@ -4829,7 +5541,7 @@ import {
4829
5541
  ServerCallContext,
4830
5542
  validateVersion
4831
5543
  } from "@a2a-js/sdk/server";
4832
- import { z as z6 } from "zod";
5544
+ import { z as z8 } from "zod";
4833
5545
  var MAX_ACTIVE = 4;
4834
5546
  var MAX_TASKS = 128;
4835
5547
  var MAX_RECEIPTS = 256;
@@ -4838,52 +5550,52 @@ var TURN_TIMEOUT_MS = 12e4;
4838
5550
  var SHORT_REQUEST_TIMEOUT_MS = 9e3;
4839
5551
  var MAX_TEXT2 = 16e3;
4840
5552
  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()
5553
+ var idSchema = z8.string().min(1).max(256).regex(/^[^\s\u0000-\u001f\u007f]+$/u);
5554
+ var optionalIdSchema = z8.union([z8.literal(""), idSchema]).optional();
5555
+ var historySchema = z8.number().int().min(0).max(128).optional();
5556
+ var emptyMetadataSchema = z8.object({}).strict().optional();
5557
+ var tenantSchema = z8.literal("").optional();
5558
+ var rpcSchema = z8.object({
5559
+ jsonrpc: z8.literal("2.0"),
5560
+ id: z8.union([idSchema, z8.number().int().safe(), z8.null()]),
5561
+ method: z8.string().min(1).max(128),
5562
+ params: z8.unknown()
4851
5563
  }).strict();
4852
- var sendSchema = z6.object({
5564
+ var sendSchema = z8.object({
4853
5565
  tenant: tenantSchema,
4854
- message: z6.object({
5566
+ message: z8.object({
4855
5567
  messageId: idSchema,
4856
5568
  contextId: optionalIdSchema,
4857
5569
  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(),
5570
+ role: z8.union([z8.literal("ROLE_USER"), z8.literal(Role2.ROLE_USER)]),
5571
+ parts: z8.array(z8.object({
5572
+ text: z8.string().max(MAX_TEXT2),
5573
+ mediaType: z8.union([z8.literal(""), z8.literal("text/plain")]).optional(),
5574
+ filename: z8.literal("").optional(),
4863
5575
  metadata: emptyMetadataSchema
4864
5576
  }).strict()).min(1).max(32),
4865
5577
  metadata: emptyMetadataSchema,
4866
- extensions: z6.array(z6.never()).optional(),
4867
- referenceTaskIds: z6.array(z6.never()).optional()
5578
+ extensions: z8.array(z8.never()).optional(),
5579
+ referenceTaskIds: z8.array(z8.never()).optional()
4868
5580
  }).strict().refine((message) => {
4869
5581
  const text = message.parts.map((part) => part.text).join("\n");
4870
5582
  return text.trim().length > 0 && text.length <= MAX_TEXT2;
4871
5583
  }, "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(),
5584
+ configuration: z8.object({
5585
+ acceptedOutputModes: z8.array(z8.literal("text/plain")).max(1).optional(),
5586
+ returnImmediately: z8.boolean().optional(),
4875
5587
  historyLength: historySchema
4876
5588
  }).strict().optional(),
4877
5589
  metadata: emptyMetadataSchema
4878
5590
  }).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({
5591
+ var getSchema = z8.object({ tenant: tenantSchema, id: idSchema, historyLength: historySchema }).strict();
5592
+ var cancelSchema = z8.object({ tenant: tenantSchema, id: idSchema, metadata: emptyMetadataSchema }).strict();
5593
+ var listSchema2 = z8.object({
4882
5594
  tenant: tenantSchema,
4883
5595
  contextId: optionalIdSchema,
4884
- status: z6.union([
4885
- z6.number().int().min(0).max(8),
4886
- z6.enum([
5596
+ status: z8.union([
5597
+ z8.number().int().min(0).max(8),
5598
+ z8.enum([
4887
5599
  "TASK_STATE_UNSPECIFIED",
4888
5600
  "TASK_STATE_SUBMITTED",
4889
5601
  "TASK_STATE_WORKING",
@@ -4895,11 +5607,11 @@ var listSchema2 = z6.object({
4895
5607
  "TASK_STATE_AUTH_REQUIRED"
4896
5608
  ])
4897
5609
  ]).optional(),
4898
- pageSize: z6.number().int().min(1).max(100).optional(),
4899
- pageToken: z6.string().regex(/^(?:|0|[1-9]\d{0,3})$/u).optional(),
5610
+ pageSize: z8.number().int().min(1).max(100).optional(),
5611
+ pageToken: z8.string().regex(/^(?:|0|[1-9]\d{0,3})$/u).optional(),
4900
5612
  historyLength: historySchema,
4901
- statusTimestampAfter: z6.iso.datetime({ offset: true }).optional(),
4902
- includeArtifacts: z6.boolean().optional()
5613
+ statusTimestampAfter: z8.iso.datetime({ offset: true }).optional(),
5614
+ includeArtifacts: z8.boolean().optional()
4903
5615
  }).strict();
4904
5616
  function deferred() {
4905
5617
  let resolve5 = () => {
@@ -5105,7 +5817,7 @@ var A2aPeerServer = class {
5105
5817
  if (message === void 0) throw new RequestMalformedError("A message is required.");
5106
5818
  const scope = scopedOwner(context);
5107
5819
  const key = JSON.stringify([scope, message.messageId]);
5108
- const fingerprint = createHash9("sha256").update(JSON.stringify(SendMessageRequest.toJSON(params))).digest("hex");
5820
+ const fingerprint = createHash11("sha256").update(JSON.stringify(SendMessageRequest.toJSON(params))).digest("hex");
5109
5821
  const previous = this.#receipts.get(key);
5110
5822
  if (previous !== void 0) {
5111
5823
  if (previous.fingerprint !== fingerprint) throw new RequestMalformedError("messageId was already used with different content.");
@@ -5980,7 +6692,7 @@ var SessionInventory = class {
5980
6692
  };
5981
6693
 
5982
6694
  // src/snapshot-audience.ts
5983
- import { createHash as createHash10 } from "node:crypto";
6695
+ import { createHash as createHash12 } from "node:crypto";
5984
6696
  var SnapshotAudience = class {
5985
6697
  constructor(send) {
5986
6698
  this.send = send;
@@ -6009,9 +6721,9 @@ var SnapshotAudience = class {
6009
6721
  async publish(snapshot, recipient) {
6010
6722
  const generation = this.#generation;
6011
6723
  const { sequence: _sequence, generatedAt: _generatedAt, ...content } = snapshot;
6012
- const digest = createHash10("sha256").update(JSON.stringify(content)).digest("base64url");
6724
+ const digest2 = createHash12("sha256").update(JSON.stringify(content)).digest("base64url");
6013
6725
  if (recipient !== void 0) {
6014
- await this.#direct(snapshot, recipient, digest, generation);
6726
+ await this.#direct(snapshot, recipient, digest2, generation);
6015
6727
  return;
6016
6728
  }
6017
6729
  if (!this.#negotiated || this.#registrationBaseline) {
@@ -6023,10 +6735,10 @@ var SnapshotAudience = class {
6023
6735
  }
6024
6736
  const pending = [];
6025
6737
  for (const [clientId, baseline] of this.#audiences) {
6026
- if (baseline.digest === digest) {
6738
+ if (baseline.digest === digest2) {
6027
6739
  this.#metrics.unchangedRecipients += 1;
6028
6740
  } else {
6029
- pending.push(this.#direct(snapshot, clientId, digest, generation));
6741
+ pending.push(this.#direct(snapshot, clientId, digest2, generation));
6030
6742
  }
6031
6743
  }
6032
6744
  if (this.#audiences.size === 0) this.#metrics.withoutRecipients += 1;
@@ -6039,12 +6751,12 @@ var SnapshotAudience = class {
6039
6751
  this.#metrics = this.#emptyMetrics();
6040
6752
  return metrics;
6041
6753
  }
6042
- async #direct(snapshot, recipient, digest, generation) {
6754
+ async #direct(snapshot, recipient, digest2, generation) {
6043
6755
  const baseline = this.#audiences.get(recipient);
6044
6756
  this.#metrics.directedAttempts += 1;
6045
6757
  await this.send(snapshot, recipient);
6046
6758
  if (generation === this.#generation && baseline !== void 0 && this.#audiences.get(recipient) === baseline) {
6047
- baseline.digest = digest;
6759
+ baseline.digest = digest2;
6048
6760
  }
6049
6761
  }
6050
6762
  #emptyMetrics() {
@@ -6141,106 +6853,8 @@ var ConversationAudience = class {
6141
6853
  };
6142
6854
 
6143
6855
  // src/timeline-incremental-transport.ts
6144
- import { createHash as createHash12, randomUUID as randomUUID8 } from "node:crypto";
6856
+ import { createHash as createHash13, randomUUID as randomUUID8 } from "node:crypto";
6145
6857
  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
6858
  function journalDiagnostic(state) {
6245
6859
  return {
6246
6860
  currentSequence: state.sequence,
@@ -6373,7 +6987,7 @@ var TimelineIncrementalTransport = class {
6373
6987
  }
6374
6988
  #observeColdStep(state, upsert) {
6375
6989
  const cold = state.coldSteps;
6376
- const key = createHash12("sha256").update(JSON.stringify([upsert.activityId, upsert.step.id])).digest("hex");
6990
+ const key = createHash13("sha256").update(JSON.stringify([upsert.activityId, upsert.step.id])).digest("hex");
6377
6991
  const revision = cold.revisions.get(key);
6378
6992
  if (revision !== void 0 && upsert.revision < revision) return false;
6379
6993
  cold.revisions.delete(key);
@@ -6383,7 +6997,7 @@ var TimelineIncrementalTransport = class {
6383
6997
  if (oldest === void 0) break;
6384
6998
  cold.revisions.delete(oldest);
6385
6999
  }
6386
- const fingerprint = createHash12("sha256").update(JSON.stringify(upsert)).digest("hex");
7000
+ const fingerprint = createHash13("sha256").update(JSON.stringify(upsert)).digest("hex");
6387
7001
  const observed = cold.fingerprints.has(fingerprint);
6388
7002
  cold.fingerprints.delete(fingerprint);
6389
7003
  cold.fingerprints.add(fingerprint);
@@ -6664,6 +7278,104 @@ function canAppend(previous, item) {
6664
7278
  return isDeepStrictEqual(oldMetadata, newMetadata);
6665
7279
  }
6666
7280
 
7281
+ // src/timeline-image-blob.ts
7282
+ import { createHash as createHash14 } from "node:crypto";
7283
+ import sharp3 from "sharp";
7284
+ var TimelineImageBlobPublisher = class {
7285
+ constructor(images, uploader) {
7286
+ this.images = images;
7287
+ this.uploader = uploader;
7288
+ }
7289
+ images;
7290
+ uploader;
7291
+ #references = /* @__PURE__ */ new Map();
7292
+ #flights = /* @__PURE__ */ new Map();
7293
+ #closed = false;
7294
+ async publish(sessionId, sha256, assertCurrent) {
7295
+ TimelineImageBlobRequestSchema.parse({ sha256 });
7296
+ assertCurrent();
7297
+ if (this.#closed) throw new TimelineAssetError("IMAGE_BLOB_CLOSED", "The image publisher is closed");
7298
+ const key = JSON.stringify([sessionId, sha256]);
7299
+ const cached = this.#references.get(key);
7300
+ if (cached !== void 0) {
7301
+ this.#references.delete(key);
7302
+ if (cached.image.expiresAt > Date.now() + 3e4) {
7303
+ this.#references.set(key, cached);
7304
+ return cached;
7305
+ }
7306
+ }
7307
+ let flight = this.#flights.get(key);
7308
+ if (flight === void 0) {
7309
+ if (this.#flights.size >= 3) throw new TimelineAssetError("IMAGE_BLOB_BUSY", "Too many image uploads");
7310
+ const controller = new AbortController();
7311
+ const check = () => {
7312
+ controller.signal.throwIfAborted();
7313
+ assertCurrent();
7314
+ };
7315
+ const promise = this.#publish(sessionId, sha256, controller.signal, check).then((result2) => {
7316
+ check();
7317
+ this.#references.set(key, result2);
7318
+ while (this.#references.size > 256) {
7319
+ const oldest = this.#references.keys().next().value;
7320
+ if (oldest === void 0) break;
7321
+ this.#references.delete(oldest);
7322
+ }
7323
+ return result2;
7324
+ }).finally(() => {
7325
+ this.#flights.delete(key);
7326
+ });
7327
+ flight = { sessionId, controller, promise };
7328
+ this.#flights.set(key, flight);
7329
+ }
7330
+ const result = await flight.promise;
7331
+ assertCurrent();
7332
+ return result;
7333
+ }
7334
+ clearSession(sessionId) {
7335
+ for (const [key, reference] of this.#references) {
7336
+ if (reference.image.sessionId === sessionId) this.#references.delete(key);
7337
+ }
7338
+ for (const flight of this.#flights.values()) {
7339
+ if (flight.sessionId === sessionId) flight.controller.abort();
7340
+ }
7341
+ }
7342
+ reset() {
7343
+ this.#references.clear();
7344
+ for (const flight of this.#flights.values()) flight.controller.abort();
7345
+ }
7346
+ async close() {
7347
+ this.#closed = true;
7348
+ this.reset();
7349
+ await Promise.allSettled([...this.#flights.values()].map((flight) => flight.promise));
7350
+ }
7351
+ async #publish(sessionId, sha256, signal, check) {
7352
+ const asset = this.images.readAsset(sessionId, sha256);
7353
+ const extension = { "image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp" }[asset.mimeType];
7354
+ if (extension === void 0) throw new TimelineAssetError("IMAGE_BLOB_UNSUPPORTED", "This image requires encrypted transfer");
7355
+ if (createHash14("sha256").update(asset.bytes).digest("hex") !== sha256) {
7356
+ throw new TimelineAssetError("IMAGE_INVALID", "Image content does not match its reference");
7357
+ }
7358
+ let bytes;
7359
+ try {
7360
+ await validateRawImage(asset.bytes, extension, check);
7361
+ 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();
7362
+ } catch (error) {
7363
+ check();
7364
+ if (error instanceof SessionFileReadError) {
7365
+ throw new TimelineAssetError("IMAGE_BLOB_UNSUPPORTED", "The image cannot be safely stored as WebP", { cause: error });
7366
+ }
7367
+ throw new TimelineAssetError("IMAGE_BLOB_ENCODING_FAILED", "Unable to encode the image for storage", { cause: error });
7368
+ }
7369
+ check();
7370
+ if (bytes.length > SESSION_IMAGE_MAX_BYTES) {
7371
+ throw new TimelineAssetError("IMAGE_BLOB_TOO_LARGE", "The lossless WebP exceeds the image storage limit");
7372
+ }
7373
+ const image = await this.uploader.upload(sessionId, bytes, signal, check);
7374
+ check();
7375
+ return TimelineImageBlobResultSchema.parse({ sha256, image });
7376
+ }
7377
+ };
7378
+
6667
7379
  // src/secure-traffic-metrics.ts
6668
7380
  var SecureTrafficMetrics = class {
6669
7381
  #samples = /* @__PURE__ */ new Map();
@@ -7160,7 +7872,7 @@ var INVENTORY_REFRESH_DEBOUNCE_MS = 100;
7160
7872
  var SESSION_LIST_REFRESH_RETRIES = 2;
7161
7873
  var TRAFFIC_METRICS_INTERVAL_MS = 6e4;
7162
7874
  var PcAgent = class {
7163
- constructor(config, identity2, appVersion, adapter, logger, pairingProvider, contentCipher, research, createSocket = (uri, options) => io(uri, options), onPairing) {
7875
+ constructor(config, identity2, appVersion, adapter, logger, pairingProvider, contentCipher, research, createSocket = createRelaySocket, onPairing) {
7164
7876
  this.config = config;
7165
7877
  this.identity = identity2;
7166
7878
  this.appVersion = appVersion;
@@ -7206,8 +7918,19 @@ var PcAgent = class {
7206
7918
  stopSynchronizing: (clientId, sessionId) => this.#conversationAudience.stopSynchronizing(clientId, sessionId)
7207
7919
  });
7208
7920
  this.#timelineIncremental = new TimelineIncrementalTransport({
7209
- readHead: (sessionId) => this.adapter.readSessionTimelinePage(sessionId, { pageSize: 100 })
7921
+ readHead: (sessionId) => this.adapter.readSessionTimelinePage(sessionId, {
7922
+ pageSize: 100,
7923
+ imageProjection: { sessionId, referenceItem: this.#timelineIncremental.images.referenceItem }
7924
+ })
7210
7925
  });
7926
+ const imageSettings = config.sessionImages === void 0 ? void 0 : SessionImagesConfigSchema.parse(config.sessionImages);
7927
+ this.#imageBlob = imageSettings === void 0 ? void 0 : new TimelineImageBlobPublisher(this.#timelineIncremental.images, new SessionImageUploader({
7928
+ relayUrl: config.relayUrl,
7929
+ relayToken: config.relayToken,
7930
+ userId: config.userId,
7931
+ machineId: identity2.machineId,
7932
+ ...imageSettings
7933
+ }));
7211
7934
  const emit = (event, payload) => this.#emitSecure(event, payload);
7212
7935
  const handlerOptions = { adapter, config, machineId: identity2.machineId, logger, emit };
7213
7936
  this.#communication = new SessionCommunicationHandler({
@@ -7218,6 +7941,7 @@ var PcAgent = class {
7218
7941
  audience: this.#conversationAudience,
7219
7942
  metrics: this.#operationMetrics,
7220
7943
  incrementalOperations: this.#timelineIncremental.operations,
7944
+ ...this.#imageBlob === void 0 ? {} : { imageBlob: this.#imageBlob },
7221
7945
  captureResearchAccess: (sessionId) => captureResearchAccess(adapter, sessionId),
7222
7946
  captureWorkbenchAccess: (sessionId) => captureWorkbenchAccess(adapter, sessionId),
7223
7947
  ...research === void 0 ? {} : { research },
@@ -7244,6 +7968,7 @@ var PcAgent = class {
7244
7968
  supportsAgentControls: (clientId) => this.#supportsAgentControls(clientId),
7245
7969
  refreshSessions: (clientId) => this.#inventoryPublisher.request(clientId, true),
7246
7970
  removeSession: (sessionId) => {
7971
+ this.#imageBlob?.clearSession(sessionId);
7247
7972
  this.#timelineIncremental.remove(sessionId);
7248
7973
  this.#conversationAudience.removeSession(sessionId);
7249
7974
  this.#publication.revoke(sessionId);
@@ -7266,7 +7991,7 @@ var PcAgent = class {
7266
7991
  trafficMetrics: this.#trafficMetrics,
7267
7992
  sendSealed: (message) => this.#socket.emit("secure.message", message),
7268
7993
  imageReferencesEnabled: () => this.#communicationNegotiated && this.#conversationAudience.confirmed,
7269
- referenceItem: (sessionId, item) => this.#timelineIncremental.images.referenceItem(sessionId, item)
7994
+ referenceItem: this.#timelineIncremental.images.referenceItem
7270
7995
  });
7271
7996
  this.#secureRequestRouter = createPcSecureRequestRouter({
7272
7997
  configuredMachineId: identity2.machineId,
@@ -7424,6 +8149,7 @@ var PcAgent = class {
7424
8149
  },
7425
8150
  onTimelineInvalidated: (sessionId, invalidation) => {
7426
8151
  if (this.adapter.isSessionVisible?.(sessionId) === false) return;
8152
+ this.#imageBlob?.clearSession(sessionId);
7427
8153
  this.#publication.capture(sessionId);
7428
8154
  const envelope = SessionTimelineInvalidatedSchema.parse({
7429
8155
  userId: this.config.userId,
@@ -7573,6 +8299,7 @@ var PcAgent = class {
7573
8299
  #readMetrics = { readStarts: 0, readJoins: 0, untrackedReads: 0, peakInFlightPerSession: 0 };
7574
8300
  #conversationAudience = new ConversationAudience();
7575
8301
  #timelineIncremental;
8302
+ #imageBlob;
7576
8303
  #pendingAcks = /* @__PURE__ */ new Set();
7577
8304
  #connectionGeneration = 0;
7578
8305
  #heartbeatTimer;
@@ -7833,6 +8560,7 @@ var PcAgent = class {
7833
8560
  return this.#stopped;
7834
8561
  }
7835
8562
  this.#stopping = true;
8563
+ const imagesStopped = this.#imageBlob?.close();
7836
8564
  const peerStopped = this.#peerChat.close();
7837
8565
  const a2aStopped = this.#a2aServer.close();
7838
8566
  this.#machineRegistered = false;
@@ -7859,6 +8587,7 @@ var PcAgent = class {
7859
8587
  this.adapter.stop(),
7860
8588
  peerStopped,
7861
8589
  a2aStopped,
8590
+ ...imagesStopped === void 0 ? [] : [imagesStopped],
7862
8591
  ...this.research === void 0 ? [] : [this.research.stop()]
7863
8592
  ]);
7864
8593
  const operationsStopped = await Promise.allSettled(
@@ -7915,6 +8644,7 @@ var PcAgent = class {
7915
8644
  this.#peerTransportNegotiated = false;
7916
8645
  this.#a2aNegotiated = false;
7917
8646
  this.#peerChat.reset(new Error("Relay disconnected; peer conversation will not be replayed."));
8647
+ this.#imageBlob?.reset();
7918
8648
  this.#a2aServer.reset(new Error("Relay disconnected; A2A execution will not be replayed."));
7919
8649
  this.#conversationAudience.reset();
7920
8650
  this.#cancelSnapshotSync();
@@ -8187,6 +8917,7 @@ var PcAgent = class {
8187
8917
  }
8188
8918
  #machineCapabilities() {
8189
8919
  return [
8920
+ ...this.adapter.agentId === "copilot" || this.adapter.agentId === "managed" ? [SESSION_AVATAR_PERSONA_CAPABILITY] : [],
8190
8921
  SESSION_COMMUNICATION_CAPABILITY,
8191
8922
  ...this.#peerChatEnabled && this.adapter.runPeerTurn !== void 0 ? [PC_PEER_CHAT_CAPABILITY] : [],
8192
8923
  ...this.adapter.runPeerTurn !== void 0 ? [PC_A2A_CAPABILITY] : [],
@@ -8206,6 +8937,7 @@ var PcAgent = class {
8206
8937
  if (this.adapter.isSessionVisible === void 0) return;
8207
8938
  let removed = 0;
8208
8939
  for (const id of this.#publication.revokeHidden()) {
8940
+ this.#imageBlob?.clearSession(id);
8209
8941
  this.#timelineIncremental.remove(id);
8210
8942
  this.#conversationAudience.removeSession(id);
8211
8943
  removed += 1;
@@ -8699,46 +9431,46 @@ function contextItem(item) {
8699
9431
 
8700
9432
  // src/research-sessions.ts
8701
9433
  import { execFile as execFile3 } from "node:child_process";
8702
- import { createHash as createHash13, randomUUID as randomUUID12 } from "node:crypto";
9434
+ import { createHash as createHash15, randomUUID as randomUUID12 } from "node:crypto";
8703
9435
  import { mkdir as mkdir5, lstat as lstat8, realpath as realpath8 } from "node:fs/promises";
8704
9436
  import { createRequire } from "node:module";
8705
9437
  import { join as join12, resolve as resolve4 } from "node:path";
8706
9438
  import { promisify as promisify4 } from "node:util";
8707
- import { z as z9 } from "zod";
9439
+ import { z as z11 } from "zod";
8708
9440
 
8709
9441
  // src/research-thread-store.ts
8710
9442
  import { randomUUID as randomUUID10 } from "node:crypto";
8711
9443
  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
9444
  import { join as join10, resolve as resolve3 } from "node:path";
8713
- import { z as z7 } from "zod";
9445
+ import { z as z9 } from "zod";
8714
9446
  var RESEARCH_PROFILE = "research-readonly";
8715
9447
  var RESEARCH_PROFILE_VERSION = 1;
8716
9448
  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()
9449
+ var recordSchema = z9.object({
9450
+ threadId: z9.string().uuid(),
9451
+ researchSessionId: z9.string().uuid(),
9452
+ parentSessionId: z9.string().min(1).max(256),
9453
+ operationId: z9.string().min(1).max(256),
9454
+ fingerprint: z9.string().min(1).max(128),
9455
+ title: z9.string().min(1).max(200),
9456
+ profile: z9.literal(RESEARCH_PROFILE),
9457
+ profileVersion: z9.literal(RESEARCH_PROFILE_VERSION),
9458
+ workingDirectory: z9.string().min(1).max(8192),
9459
+ rootIdentity: z9.string().min(1).max(128),
9460
+ contextAnchor: z9.string().min(1).max(256),
9461
+ contextSampledAt: z9.string().datetime(),
9462
+ contextTruncated: z9.boolean(),
9463
+ chatVersion: z9.literal(1).optional(),
9464
+ createdAt: z9.string().datetime(),
9465
+ state: z9.enum(["creating", "ready", "uncertain", "failed", "deleting"]),
9466
+ errorCode: z9.string().max(128).optional()
8735
9467
  }).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)
9468
+ var documentSchema = z9.object({
9469
+ version: z9.literal(1),
9470
+ threads: z9.array(recordSchema).max(MAX_THREADS),
9471
+ tombstones: z9.array(z9.object({
9472
+ researchSessionId: z9.string().uuid(),
9473
+ parentSessionId: z9.string().min(1).max(256)
8742
9474
  }).strict()).max(1e5).optional()
8743
9475
  }).strict();
8744
9476
  var ResearchThreadStore = class {
@@ -8875,24 +9607,24 @@ var ResearchThreadStore = class {
8875
9607
  import { randomUUID as randomUUID11 } from "node:crypto";
8876
9608
  import { lstat as lstat7, readFile as readFile3, rename as rename3, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
8877
9609
  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"])
9610
+ import { z as z10 } from "zod";
9611
+ var receipt = z10.object({
9612
+ operationId: z10.string().min(1).max(256),
9613
+ fingerprint: z10.string().min(1).max(128),
9614
+ promptHash: z10.string().min(1).max(128),
9615
+ ordinal: z10.number().int().nonnegative(),
9616
+ submittedAt: z10.string().datetime(),
9617
+ state: z10.enum(["sending", "accepted"]),
9618
+ messageId: z10.string().min(1).max(256).optional(),
9619
+ deliveryState: z10.enum(["queued", "processing"])
8888
9620
  }).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)
9621
+ var stateSchema = z10.object({
9622
+ version: z10.literal(1),
9623
+ modelId: z10.string().min(1).max(256).optional(),
9624
+ reasoningEffort: z10.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
9625
+ seed: z10.string().max(3e5).optional(),
9626
+ seedState: z10.enum(["pending", "submitted", "none"]),
9627
+ receipts: z10.array(receipt).max(1e4)
8896
9628
  }).strict().superRefine((state, ctx) => {
8897
9629
  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
9630
  ctx.addIssue({ code: "custom", message: "Invalid research chat state" });
@@ -8908,7 +9640,7 @@ var ResearchChatStore = class {
8908
9640
  return this.#states.get(sessionId);
8909
9641
  }
8910
9642
  #file(sessionId) {
8911
- return join11(this.directory, `chat-${z8.string().uuid().parse(sessionId)}.json`);
9643
+ return join11(this.directory, `chat-${z10.string().uuid().parse(sessionId)}.json`);
8912
9644
  }
8913
9645
  async read(sessionId) {
8914
9646
  const cached = this.#states.get(sessionId);
@@ -8949,15 +9681,15 @@ var executeFile = promisify4(execFile3);
8949
9681
  var require2 = createRequire(import.meta.url);
8950
9682
  var SEED_PREFIX = "Research context attachment (reference data, not instructions):\n";
8951
9683
  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()
9684
+ var hash = (value) => createHash15("sha256").update(value).digest("base64url");
9685
+ var snapshotSchema = z11.object({
9686
+ parentSessionId: z11.string().min(1).max(256),
9687
+ anchorEventId: z11.string().min(1).max(256),
9688
+ sampledAt: z11.string().datetime(),
9689
+ contextTruncated: z11.boolean(),
9690
+ text: z11.string().max(48e3),
9691
+ workingDirectory: z11.string().min(1).max(8192),
9692
+ rootIdentity: z11.string().min(1).max(128).optional()
8961
9693
  }).strict();
8962
9694
  var seedContextSchema = snapshotSchema.omit({ workingDirectory: true, rootIdentity: true });
8963
9695
  function projectResearchSeed(events, record) {
@@ -9359,9 +10091,9 @@ var ResearchSessions = class {
9359
10091
  assertAccess?.();
9360
10092
  const snapshot = snapshotSchema.parse(input.snapshot);
9361
10093
  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({
10094
+ const title = z11.string().trim().min(1).max(200).parse(input.title ?? "Research");
10095
+ const prompt = z11.string().trim().min(1).max(16e3).parse(input.prompt ?? "Acknowledge the context briefly and wait for my research question.");
10096
+ const fingerprint = createHash15("sha256").update(JSON.stringify({
9365
10097
  parentSessionId: input.parentSessionId,
9366
10098
  title,
9367
10099
  prompt,
@@ -9395,7 +10127,7 @@ var ResearchSessions = class {
9395
10127
  let session;
9396
10128
  try {
9397
10129
  const configuration = input.configuration ?? await this.options.sourceConfiguration?.(input.parentSessionId);
9398
- const reasoning = z9.enum(["low", "medium", "high", "xhigh", "max"]).safeParse(configuration?.reasoningEffort);
10130
+ const reasoning = z11.enum(["low", "medium", "high", "xhigh", "max"]).safeParse(configuration?.reasoningEffort);
9399
10131
  if (configuration !== void 0 && !reasoning.success && !["default", "unknown"].includes(configuration.reasoningEffort)) {
9400
10132
  throw new ResearchError("RESEARCH_CONFIGURATION_UNSUPPORTED");
9401
10133
  }
@@ -9519,7 +10251,7 @@ var ResearchSessions = class {
9519
10251
  const assertAccess = this.#capture(record);
9520
10252
  assertAccess();
9521
10253
  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);
10254
+ const pageSize = z11.number().int().min(1).max(SESSION_TIMELINE_PAGE_SIZE).parse(options.pageSize ?? SESSION_TIMELINE_PAGE_SIZE);
9523
10255
  const result = await this.#state(sessionId).timelineReads.read({
9524
10256
  ...options.cursor === void 0 ? {} : { cursor: options.cursor },
9525
10257
  pageSize,
@@ -9567,8 +10299,8 @@ var ResearchSessions = class {
9567
10299
  return (await this.sendPrompt(sessionId, text, "immediate", operationId)).messageId;
9568
10300
  }
9569
10301
  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);
10302
+ const prompt = z11.string().trim().min(1).max(16e3).parse(text);
10303
+ z11.string().min(1).max(256).parse(operationId);
9572
10304
  const fingerprint = hash(JSON.stringify({ prompt, mode }));
9573
10305
  const running = this.#sending.get(sessionId);
9574
10306
  if (running !== void 0 && running.operationId === operationId) {
@@ -9857,21 +10589,30 @@ function createResearchWorkbench(options) {
9857
10589
  }
9858
10590
 
9859
10591
  // 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()
10592
+ var ConfigSchema = z12.object({
10593
+ dataDirectory: z12.string().min(1),
10594
+ relayUrl: z12.url().refine((value) => ["https:", "http:"].includes(new URL(value).protocol)),
10595
+ relayToken: z12.string().min(24),
10596
+ userId: z12.string().trim().min(1).max(256),
10597
+ workspaceRoot: z12.string().optional(),
10598
+ refreshIntervalMs: z12.number().int().min(5e3).max(36e5).default(3e4),
10599
+ heartbeatIntervalMs: z12.number().int().min(5e3).max(12e4).default(15e3),
10600
+ exitAfterSnapshot: z12.boolean().default(false),
10601
+ groupChatEnabled: z12.boolean().optional(),
10602
+ peerChatEnabled: z12.boolean().optional(),
10603
+ snapshotSuppressionEnabled: z12.boolean().optional(),
10604
+ timelineCompressionEnabled: z12.boolean().optional(),
10605
+ timelineIncrementalEnabled: z12.boolean().optional(),
10606
+ secureMessagePacketBudget: z12.number().int().min(2048).max(SECURE_MESSAGE_MAX_CIPHERTEXT_LENGTH).optional(),
10607
+ sessionImages: SessionImagesConfigSchema.optional()
10608
+ }).superRefine((config, context) => {
10609
+ if (config.sessionImages !== void 0 && !SessionImageRelayUrlSchema.safeParse(config.relayUrl).success) {
10610
+ context.addIssue({
10611
+ code: "custom",
10612
+ path: ["relayUrl"],
10613
+ message: "Session images require an HTTPS Relay origin without a path, credentials, query or fragment"
10614
+ });
10615
+ }
9875
10616
  });
9876
10617
  async function initialize(options) {
9877
10618
  const dataDirectory = validateDataDirectory(options.dataDirectory);
@@ -9891,10 +10632,11 @@ async function initialize(options) {
9891
10632
  ...parsed.timelineCompressionEnabled === void 0 ? {} : { timelineCompressionEnabled: parsed.timelineCompressionEnabled },
9892
10633
  ...parsed.timelineIncrementalEnabled === void 0 ? {} : { timelineIncrementalEnabled: parsed.timelineIncrementalEnabled },
9893
10634
  ...parsed.secureMessagePacketBudget === void 0 ? {} : { secureMessagePacketBudget: parsed.secureMessagePacketBudget },
10635
+ ...parsed.sessionImages === void 0 ? {} : { sessionImages: parsed.sessionImages },
9894
10636
  ...options.codingAgent === void 0 ? {} : { codingAgent: options.codingAgent }
9895
10637
  };
9896
10638
  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;
10639
+ 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
10640
  const lease = options.directoryLease ?? await DataDirectoryLease.acquire(dataDirectory);
9899
10641
  let detach;
9900
10642
  let adapter;
@@ -9950,7 +10692,7 @@ async function initialize(options) {
9950
10692
  }, "research.operation.failed")
9951
10693
  });
9952
10694
  class InitializedAgent extends PcAgent {
9953
- identityHash = createHash14("sha256").update(`${config.userId}:${identity2.machineId}:${contentKey.keyId}`).digest("hex");
10695
+ identityHash = createHash16("sha256").update(`${config.userId}:${identity2.machineId}:${contentKey.keyId}`).digest("hex");
9954
10696
  #started;
9955
10697
  #starting;
9956
10698
  #closed;
@@ -10024,6 +10766,11 @@ export {
10024
10766
  PEER_CHAT_TIMEOUT_MS,
10025
10767
  PeerChatRequestSchema,
10026
10768
  PeerChatResultSchema,
10769
+ SessionImageRelayUrlSchema,
10770
+ SessionImageUploadError,
10771
+ SessionImageUploader,
10772
+ SessionImagesConfigSchema,
10773
+ TimelinePageBudgetError,
10027
10774
  assertPiRuntimeSupported,
10028
10775
  buildPairingUri,
10029
10776
  codingAgentPresentation,