@agentknight/sdk 0.9.42 → 0.9.44

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-QN3OD5UE.js";
7
+ } from "./chunks/chunk-ZGO2OSQZ.js";
8
8
  import {
9
9
  AgentSelectionSchema,
10
10
  assertSelectionSnapshotFits,
11
11
  supportsAgentSelection
12
- } from "./chunks/chunk-ASHXZ46V.js";
12
+ } from "./chunks/chunk-4UAMAGVT.js";
13
13
  import {
14
14
  ConversationNormalizer,
15
15
  LiveTimelineProjection,
@@ -31,26 +31,33 @@ import {
31
31
  resolveGitProjectIdentity,
32
32
  sessionRestoreFailure,
33
33
  validateOperationResponse
34
- } from "./chunks/chunk-SHOE7YAU.js";
34
+ } from "./chunks/chunk-EWXFUECA.js";
35
35
  import {
36
36
  GROUP_CHAT_MAX_TURNS,
37
37
  GroupChatError
38
- } from "./chunks/chunk-VDC4G7ZG.js";
38
+ } from "./chunks/chunk-LRSHP7WN.js";
39
+ import {
40
+ detectClaude,
41
+ detectCopilot,
42
+ detectPi,
43
+ hasLocalExecutable
44
+ } from "./chunks/chunk-EIFNERXM.js";
39
45
  import {
40
46
  assistantPreview,
41
47
  isContentfulAssistant,
42
48
  userPreview
43
- } from "./chunks/chunk-RBULXS7T.js";
49
+ } from "./chunks/chunk-AK3YVBEP.js";
44
50
  import {
45
51
  AgentModelListingError,
46
52
  AgentSessionRenameError,
53
+ CODING_AGENT_IDS,
47
54
  SessionOperationError,
48
55
  SessionOperations,
49
56
  TimelinePageBudgetError,
50
57
  UnsupportedAgentOperationError,
51
58
  captureResearchAccess,
52
59
  captureWorkbenchAccess
53
- } from "./chunks/chunk-LQIW4ARE.js";
60
+ } from "./chunks/chunk-UNRPSTRM.js";
54
61
  import {
55
62
  A2aRelayRequestSchema,
56
63
  A2aRelayResponseSchema,
@@ -105,6 +112,9 @@ import {
105
112
  SESSION_DIRECTORY_MAX_ENTRIES,
106
113
  SESSION_DIRECTORY_MAX_RESPONSE_BYTES,
107
114
  SESSION_FILE_ATTACHMENTS_CAPABILITY,
115
+ SESSION_FILE_DOWNLOAD_MAX_ORIGINAL_BYTES,
116
+ SESSION_FILE_DOWNLOAD_OPERATION,
117
+ SESSION_FILE_DOWNLOAD_VERSION,
108
118
  SESSION_FILE_GZIP_THRESHOLD_BYTES,
109
119
  SESSION_FILE_MAX_IMAGE_BYTES,
110
120
  SESSION_FILE_MAX_IMAGE_EDGE,
@@ -121,6 +131,7 @@ import {
121
131
  SESSION_GIT_DIFF_MAX_PATCH_BYTES,
122
132
  SESSION_GIT_DIFF_MAX_RESPONSE_BYTES,
123
133
  SESSION_GIT_DIFF_OPERATION,
134
+ SESSION_GIT_DIFF_PARTIAL_VERSION,
124
135
  SESSION_GIT_DIFF_VERSION,
125
136
  SESSION_IMAGE_API_PATH,
126
137
  SESSION_IMAGE_MAX_BYTES,
@@ -152,11 +163,14 @@ import {
152
163
  SessionDeleteRequestSchema,
153
164
  SessionDirectoryListDataSchema,
154
165
  SessionDirectoryListResultDataSchema,
166
+ SessionFileDownloadDataSchema,
167
+ SessionFileDownloadResultDataSchema,
155
168
  SessionFileReadDataSchema,
156
169
  SessionFileReadResultDataSchema,
157
170
  SessionFileReadV2ResultDataSchema,
158
171
  SessionGitDiffDataSchema,
159
172
  SessionGitDiffGzipResultDataSchema,
173
+ SessionGitDiffPartialResultDataSchema,
160
174
  SessionGitDiffResultDataSchema,
161
175
  SessionImageCommitResultSchema,
162
176
  SessionImageScopeSchema,
@@ -227,7 +241,7 @@ import {
227
241
  sessionCommunicationError,
228
242
  sessionCommunicationOperationCapability,
229
243
  workbenchCapability
230
- } from "./chunks/chunk-QTBB2U7G.js";
244
+ } from "./chunks/chunk-W6TZVD7W.js";
231
245
 
232
246
  // src/initialize.ts
233
247
  import { createHash as createHash16 } from "node:crypto";
@@ -244,6 +258,8 @@ var genericIcon = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"
244
258
  var presentations = {
245
259
  copilot: { name: "GitHub Copilot", icon: copilotIcon },
246
260
  pi: { name: "PI", icon: piIcon },
261
+ codex: { name: "Codex", icon: genericIcon },
262
+ claude: { name: "Claude Code", icon: genericIcon },
247
263
  acp: { name: "ACP", icon: genericIcon }
248
264
  };
249
265
  var fallback = { name: "Agent", icon: genericIcon };
@@ -251,48 +267,6 @@ function codingAgentPresentation(agentId) {
251
267
  return Object.hasOwn(presentations, agentId) ? presentations[agentId] : fallback;
252
268
  }
253
269
 
254
- // src/local-agent-discovery.ts
255
- import { constants } from "node:fs";
256
- import { access, stat } from "node:fs/promises";
257
- import { homedir } from "node:os";
258
- import { delimiter, isAbsolute, join } from "node:path";
259
- import { fileURLToPath } from "node:url";
260
- async function accessible(path, executable = false) {
261
- try {
262
- await access(path, executable && process.platform !== "win32" ? constants.X_OK : constants.F_OK);
263
- return (await stat(path)).isFile();
264
- } catch (error) {
265
- if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) return false;
266
- throw error;
267
- }
268
- }
269
- async function hasLocalExecutable(command, environment2 = process.env) {
270
- const extensions = process.platform === "win32" ? ["", ...(environment2.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
271
- const paths = isAbsolute(command) || command.includes("/") || command.includes("\\") ? [command] : (environment2.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join(directory, command));
272
- for (const path of paths) {
273
- for (const extension of extensions) {
274
- if (await accessible(`${path}${extension}`, true)) return true;
275
- }
276
- }
277
- return false;
278
- }
279
- async function hasPackage(name) {
280
- try {
281
- return await accessible(fileURLToPath(import.meta.resolve(name)));
282
- } catch (error) {
283
- if (error instanceof Error && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") return false;
284
- throw error;
285
- }
286
- }
287
- async function detectCopilot() {
288
- return process.env.COPILOT_CLI_PATH ? accessible(process.env.COPILOT_CLI_PATH) : await hasPackage("@github/copilot/npm-loader.js") || hasLocalExecutable("copilot");
289
- }
290
- async function detectPi(agentDirectory) {
291
- if (!await hasPackage("@earendil-works/pi-coding-agent")) return false;
292
- const directory = agentDirectory ?? (process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent"));
293
- return agentDirectory !== void 0 || await accessible(join(directory, "auth.json")) || await hasLocalExecutable("pi");
294
- }
295
-
296
270
  // src/coding-agent-factory.ts
297
271
  function assertPiRuntimeSupported(nodeVersion = process.versions.node) {
298
272
  const [major, minor] = nodeVersion.split(".").map(Number);
@@ -305,7 +279,7 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
305
279
  switch (engine.kind) {
306
280
  case "managed": {
307
281
  if (owner === void 0) throw new Error("Managed coding agents require the PC owner identity");
308
- const { ManagedCodingAgentAdapter } = await import("./chunks/managed-agent-adapter-LMHQX6IH.js");
282
+ const { ManagedCodingAgentAdapter } = await import("./chunks/managed-agent-adapter-HDLOSSHZ.js");
309
283
  return new ManagedCodingAgentAdapter({
310
284
  dataDirectory: config.dataDirectory,
311
285
  owner,
@@ -331,6 +305,24 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
331
305
  }
332
306
  }, onDiagnostic)
333
307
  },
308
+ {
309
+ id: "codex",
310
+ name: codingAgentPresentation("codex").name,
311
+ detect: () => hasLocalExecutable(engine.codexCommand ?? "codex"),
312
+ create: () => createCodingAgentAdapter({
313
+ ...config,
314
+ codingAgent: {
315
+ kind: "codex",
316
+ ...engine.codexCommand === void 0 ? {} : { command: engine.codexCommand }
317
+ }
318
+ }, onDiagnostic)
319
+ },
320
+ {
321
+ id: "claude",
322
+ name: codingAgentPresentation("claude").name,
323
+ detect: detectClaude,
324
+ create: () => createCodingAgentAdapter({ ...config, codingAgent: { kind: "claude" } }, onDiagnostic)
325
+ },
334
326
  {
335
327
  id: "acp",
336
328
  name: codingAgentPresentation("acp").name,
@@ -344,7 +336,7 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
344
336
  });
345
337
  }
346
338
  case "copilot": {
347
- const { GithubCopilotAdapter } = await import("./chunks/copilot-adapter-A6QB4RD4.js");
339
+ const { GithubCopilotAdapter } = await import("./chunks/copilot-adapter-Y5W4ZUFD.js");
348
340
  if (owner !== void 0) {
349
341
  return new GithubCopilotAdapter(void 0, void 0, void 0, {
350
342
  dataDirectory: config.dataDirectory,
@@ -364,13 +356,28 @@ async function createCodingAgentAdapter(config, onDiagnostic, owner) {
364
356
  }
365
357
  case "pi": {
366
358
  assertPiRuntimeSupported();
367
- const { PiCodingAgentAdapter } = await import("./chunks/pi-adapter-NWQWUNXO.js");
359
+ const { PiCodingAgentAdapter } = await import("./chunks/pi-adapter-6EF6Z5CP.js");
368
360
  return new PiCodingAgentAdapter(
369
361
  engine.agentDirectory === void 0 ? {} : { agentDirectory: resolve(engine.agentDirectory) }
370
362
  );
371
363
  }
364
+ case "codex": {
365
+ const { CodexCodingAgentAdapter } = await import("./chunks/codex-adapter-MMQK47KY.js");
366
+ return new CodexCodingAgentAdapter({
367
+ dataDirectory: resolve(config.dataDirectory, "coding-agents"),
368
+ ...engine.command === void 0 ? {} : { command: engine.command },
369
+ ...onDiagnostic === void 0 ? {} : { onDiagnostic }
370
+ });
371
+ }
372
+ case "claude": {
373
+ const { ClaudeCodingAgentAdapter } = await import("./chunks/claude-adapter-A5EFNV5S.js");
374
+ return new ClaudeCodingAgentAdapter({
375
+ dataDirectory: resolve(config.dataDirectory, "coding-agents"),
376
+ ...onDiagnostic === void 0 ? {} : { onDiagnostic }
377
+ });
378
+ }
372
379
  case "acp": {
373
- const { AcpCodingAgentAdapter } = await import("./chunks/acp-adapter-SPIMGGL2.js");
380
+ const { AcpCodingAgentAdapter } = await import("./chunks/acp-adapter-JKSOVQJ7.js");
374
381
  return new AcpCodingAgentAdapter({
375
382
  command: engine.command,
376
383
  args: engine.args,
@@ -413,7 +420,7 @@ import {
413
420
  writeFileSync
414
421
  } from "node:fs";
415
422
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
416
- import { join as join2 } from "node:path";
423
+ import { join } from "node:path";
417
424
  import { z as z2 } from "zod";
418
425
  var KEY_BYTES = 32;
419
426
  var NONCE_BYTES = 12;
@@ -480,7 +487,7 @@ function generateContentKey() {
480
487
  return parseContentKey(randomBytes(KEY_BYTES).toString("base64url"));
481
488
  }
482
489
  async function loadOrCreateContentKey(dataDirectory) {
483
- const path = join2(dataDirectory, "content-key.json");
490
+ const path = join(dataDirectory, "content-key.json");
484
491
  await mkdir(dataDirectory, { recursive: true });
485
492
  try {
486
493
  const stored = ContentKeyFileSchema.parse(
@@ -509,7 +516,7 @@ async function loadOrCreateContentKey(dataDirectory) {
509
516
  }
510
517
  async function createPersistentContentCipher(dataDirectory, key) {
511
518
  await mkdir(dataDirectory, { recursive: true });
512
- const path = join2(dataDirectory, "content-replay.log");
519
+ const path = join(dataDirectory, "content-replay.log");
513
520
  let processedMessages = /* @__PURE__ */ new Map();
514
521
  let compactOnNextWrite = false;
515
522
  try {
@@ -861,15 +868,15 @@ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
861
868
 
862
869
  // src/research-tools.ts
863
870
  import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
864
- import { lstat as lstat3, realpath as realpath3, stat as stat4 } from "node:fs/promises";
865
- import { isAbsolute as isAbsolute4, join as join5 } from "node:path";
871
+ import { lstat as lstat3, realpath as realpath3, stat as stat3 } from "node:fs/promises";
872
+ import { isAbsolute as isAbsolute3, join as join4 } from "node:path";
866
873
  import { gunzipSync } from "node:zlib";
867
874
  import { z as z5 } from "zod";
868
875
 
869
876
  // src/session-directory.ts
870
877
  import { createHash as createHash2 } from "node:crypto";
871
- import { lstat, opendir, realpath, stat as stat2 } from "node:fs/promises";
872
- import { isAbsolute as isAbsolute2, join as join3, relative, sep } from "node:path";
878
+ import { lstat, opendir, realpath, stat } from "node:fs/promises";
879
+ import { isAbsolute, join as join2, relative, sep } from "node:path";
873
880
  var SESSION_DIRECTORY_CONCURRENCY = 4;
874
881
  var SESSION_DIRECTORY_TIMEOUT_MS = 5e3;
875
882
  var SessionDirectoryError = class extends Error {
@@ -884,7 +891,7 @@ function fail(code, message) {
884
891
  }
885
892
  function contained(root, candidate) {
886
893
  const path = relative(root, candidate);
887
- return path === "" || !isAbsolute2(path) && path !== ".." && !path.startsWith(`..${sep}`);
894
+ return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
888
895
  }
889
896
  function sessionRootIdentity(root, info) {
890
897
  return `${root}\0${info.dev}\0${info.ino}`;
@@ -933,14 +940,14 @@ var SessionDirectoryLister = class {
933
940
  checkDeadline();
934
941
  if (metadata === void 0) fail("SESSION_NOT_FOUND", "The session is unavailable");
935
942
  const workingDirectory = metadata.workingDirectory;
936
- if (!workingDirectory || !isAbsolute2(workingDirectory)) {
943
+ if (!workingDirectory || !isAbsolute(workingDirectory)) {
937
944
  fail("SESSION_ROOT_UNAVAILABLE", "The session working directory is unavailable");
938
945
  }
939
946
  let root;
940
947
  let rootIdentity;
941
948
  try {
942
949
  root = await realpath(workingDirectory);
943
- const info = await stat2(root, { bigint: true });
950
+ const info = await stat(root, { bigint: true });
944
951
  if (!info.isDirectory()) fail("SESSION_ROOT_UNAVAILABLE", "The session working directory is unavailable");
945
952
  rootIdentity = sessionRootIdentity(root, info);
946
953
  } catch (error) {
@@ -953,7 +960,7 @@ var SessionDirectoryLister = class {
953
960
  checkDeadline();
954
961
  let target = root;
955
962
  for (const segment of path === "" ? [] : path.split("/")) {
956
- target = join3(target, segment);
963
+ target = join2(target, segment);
957
964
  const info = await lstat(target);
958
965
  checkDeadline();
959
966
  if (info.isSymbolicLink()) fail("DIRECTORY_BLOCKED", "Links cannot be opened");
@@ -978,7 +985,7 @@ var SessionDirectoryLister = class {
978
985
  }
979
986
  let kind = "blocked";
980
987
  if (isSafeSessionDirectoryPath(entry.name) && !entry.name.includes("/")) {
981
- const child = join3(target, entry.name);
988
+ const child = join2(target, entry.name);
982
989
  const info = await lstat(child);
983
990
  checkDeadline();
984
991
  if (!info.isSymbolicLink() && contained(root, await realpath(child))) {
@@ -994,7 +1001,7 @@ var SessionDirectoryLister = class {
994
1001
  }
995
1002
  const latestMetadata = await this.metadata(sessionId);
996
1003
  const latestRoot = await realpath(workingDirectory);
997
- const latestInfo = await stat2(latestRoot, { bigint: true });
1004
+ const latestInfo = await stat(latestRoot, { bigint: true });
998
1005
  if (latestMetadata?.workingDirectory !== workingDirectory || sessionRootIdentity(latestRoot, latestInfo) !== rootIdentity || await realpath(target) !== canonicalTarget) {
999
1006
  fail("SESSION_ROOT_CHANGED", "The session directory changed");
1000
1007
  }
@@ -1012,13 +1019,15 @@ var SessionDirectoryLister = class {
1012
1019
  };
1013
1020
 
1014
1021
  // src/session-file.ts
1022
+ import { isUtf8 } from "node:buffer";
1015
1023
  import { createHash as createHash3 } from "node:crypto";
1016
- import { constants as constants2 } from "node:fs";
1017
- import { lstat as lstat2, open, realpath as realpath2, stat as stat3 } from "node:fs/promises";
1018
- import { basename, extname, isAbsolute as isAbsolute3, join as join4 } from "node:path";
1024
+ import { constants } from "node:fs";
1025
+ import { lstat as lstat2, open, realpath as realpath2, stat as stat2 } from "node:fs/promises";
1026
+ import { extname, isAbsolute as isAbsolute2, join as join3 } from "node:path";
1019
1027
  import { promisify } from "node:util";
1020
1028
  import { gzip } from "node:zlib";
1021
1029
  import sharp from "sharp";
1030
+ import { fileTypeFromBuffer } from "file-type";
1022
1031
 
1023
1032
  // src/session-video.ts
1024
1033
  function requireVideo(valid) {
@@ -1181,105 +1190,8 @@ function fail2(code) {
1181
1190
  function fileIdentity(info, includeChangeTime = true) {
1182
1191
  return `${info.dev}\0${info.ino}\0${info.size}\0${info.mtimeNs}\0${includeChangeTime ? info.ctimeNs : ""}\0${info.mode}\0${info.nlink}`;
1183
1192
  }
1184
- var textExtensions = /* @__PURE__ */ new Set([
1185
- ".txt",
1186
- ".log",
1187
- ".csv",
1188
- ".tsv",
1189
- ".md",
1190
- ".mdx",
1191
- ".markdown",
1192
- ".rst",
1193
- ".json",
1194
- ".jsonc",
1195
- ".jsonl",
1196
- ".yaml",
1197
- ".yml",
1198
- ".toml",
1199
- ".ini",
1200
- ".cfg",
1201
- ".conf",
1202
- ".xml",
1203
- ".html",
1204
- ".htm",
1205
- ".css",
1206
- ".scss",
1207
- ".sass",
1208
- ".less",
1209
- ".js",
1210
- ".jsx",
1211
- ".mjs",
1212
- ".cjs",
1213
- ".ts",
1214
- ".tsx",
1215
- ".kt",
1216
- ".kts",
1217
- ".java",
1218
- ".py",
1219
- ".pyi",
1220
- ".go",
1221
- ".rs",
1222
- ".c",
1223
- ".h",
1224
- ".cpp",
1225
- ".hpp",
1226
- ".cc",
1227
- ".cs",
1228
- ".fs",
1229
- ".swift",
1230
- ".m",
1231
- ".mm",
1232
- ".rb",
1233
- ".php",
1234
- ".sh",
1235
- ".bash",
1236
- ".zsh",
1237
- ".fish",
1238
- ".ps1",
1239
- ".psm1",
1240
- ".bat",
1241
- ".cmd",
1242
- ".sql",
1243
- ".r",
1244
- ".dart",
1245
- ".vue",
1246
- ".svelte",
1247
- ".lua",
1248
- ".gradle",
1249
- ".properties",
1250
- ".gitignore",
1251
- ".gitattributes",
1252
- ".editorconfig",
1253
- ".env",
1254
- ".dockerignore",
1255
- ".graphql",
1256
- ".gql",
1257
- ".proto",
1258
- ".cmake",
1259
- ".diff",
1260
- ".patch"
1261
- ]);
1262
- var textNames = /* @__PURE__ */ new Set([
1263
- "readme",
1264
- "license",
1265
- "licence",
1266
- "notice",
1267
- "changelog",
1268
- "authors",
1269
- "dockerfile",
1270
- "containerfile",
1271
- "makefile",
1272
- "gemfile",
1273
- "rakefile",
1274
- ".gitignore",
1275
- ".gitattributes",
1276
- ".editorconfig",
1277
- ".env",
1278
- ".dockerignore"
1279
- ]);
1280
1193
  function textMediaType(path) {
1281
1194
  const extension = extname(path).toLowerCase();
1282
- if (!textExtensions.has(extension) && !textNames.has(basename(path).toLowerCase())) fail2("FILE_UNSUPPORTED");
1283
1195
  switch (extension) {
1284
1196
  case ".html":
1285
1197
  case ".htm":
@@ -1304,6 +1216,9 @@ function textMediaType(path) {
1304
1216
  return "text/plain";
1305
1217
  }
1306
1218
  }
1219
+ function isPreviewableText(bytes) {
1220
+ return isUtf8(bytes) && !bytes.some((byte) => byte <= 8 || byte === 11 || byte >= 14 && byte <= 31 || byte === 127);
1221
+ }
1307
1222
  function rejectAnimatedPng(bytes) {
1308
1223
  for (let offset = 8; offset + 12 <= bytes.length; ) {
1309
1224
  const size = bytes.readUInt32BE(offset);
@@ -1407,13 +1322,19 @@ var SessionFileReader = class {
1407
1322
  if (!SessionFileReadDataSchema.safeParse({ path, rootRevision }).success) fail2("INVALID_FILE_PATH");
1408
1323
  const schema = version === SESSION_FILE_READ_V2_VERSION ? SessionFileReadV2ResultDataSchema : SessionFileReadResultDataSchema;
1409
1324
  return schema.parse(await this.#run(
1410
- (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, false, version)
1325
+ (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, "preview", version)
1411
1326
  ));
1412
1327
  }
1413
1328
  async readResource(sessionId, path, rootRevision) {
1414
1329
  if (!SessionResourceReadDataSchema.safeParse({ path, rootRevision }).success) fail2("INVALID_FILE_PATH");
1415
1330
  return SessionResourceReadResultDataSchema.parse(await this.#run(
1416
- (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, true)
1331
+ (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, "resource")
1332
+ ));
1333
+ }
1334
+ async download(sessionId, path, rootRevision) {
1335
+ if (!SessionFileDownloadDataSchema.safeParse({ path, rootRevision }).success) fail2("INVALID_FILE_PATH");
1336
+ return SessionFileDownloadResultDataSchema.parse(await this.#run(
1337
+ (checkDeadline) => this.#read(sessionId, path, rootRevision, checkDeadline, "download")
1417
1338
  ));
1418
1339
  }
1419
1340
  async #run(read) {
@@ -1441,18 +1362,20 @@ var SessionFileReader = class {
1441
1362
  clearTimeout(timer);
1442
1363
  }
1443
1364
  }
1444
- async #read(sessionId, path, rootRevision, checkDeadline, resource, version = SESSION_FILE_READ_VERSION) {
1365
+ async #read(sessionId, path, rootRevision, checkDeadline, mode, version = SESSION_FILE_READ_VERSION) {
1366
+ const resource = mode === "resource";
1367
+ const download = mode === "download";
1445
1368
  const metadata = await this.metadata(sessionId);
1446
1369
  checkDeadline();
1447
1370
  if (metadata === void 0) fail2("SESSION_NOT_FOUND");
1448
1371
  const workingDirectory = metadata.workingDirectory;
1449
- if (!workingDirectory || !isAbsolute3(workingDirectory)) fail2("SESSION_ROOT_UNAVAILABLE");
1372
+ if (!workingDirectory || !isAbsolute2(workingDirectory)) fail2("SESSION_ROOT_UNAVAILABLE");
1450
1373
  let root;
1451
1374
  let rootIdentity;
1452
1375
  try {
1453
1376
  if ((await lstat2(workingDirectory)).isSymbolicLink()) fail2("FILE_UNAVAILABLE");
1454
1377
  root = await realpath2(workingDirectory);
1455
- const info = await stat3(root, { bigint: true });
1378
+ const info = await stat2(root, { bigint: true });
1456
1379
  if (!info.isDirectory()) fail2("SESSION_ROOT_UNAVAILABLE");
1457
1380
  rootIdentity = sessionRootIdentity(root, info);
1458
1381
  } catch (error) {
@@ -1461,13 +1384,13 @@ var SessionFileReader = class {
1461
1384
  }
1462
1385
  if (createHash3("sha256").update(rootIdentity).digest("base64url") !== rootRevision) fail2("SESSION_ROOT_CHANGED");
1463
1386
  checkDeadline();
1464
- const target = join4(root, ...path.split("/"));
1387
+ const target = join3(root, ...path.split("/"));
1465
1388
  const inspectPath = async () => {
1466
1389
  let current = root;
1467
1390
  const segments = path.split("/");
1468
1391
  let file;
1469
1392
  for (const [index, segment] of segments.entries()) {
1470
- current = join4(current, segment);
1393
+ current = join3(current, segment);
1471
1394
  const info = await lstat2(current, { bigint: true });
1472
1395
  checkDeadline();
1473
1396
  if (info.isSymbolicLink() || (index === segments.length - 1 ? !info.isFile() : !info.isDirectory())) {
@@ -1483,13 +1406,11 @@ var SessionFileReader = class {
1483
1406
  return file;
1484
1407
  };
1485
1408
  const before = await inspectPath();
1486
- 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;
1409
+ const maxOriginalBytes = download ? SESSION_FILE_DOWNLOAD_MAX_ORIGINAL_BYTES : resource ? SESSION_RESOURCE_MAX_ORIGINAL_BYTES : version === SESSION_FILE_READ_V2_VERSION ? SESSION_FILE_V2_MAX_ORIGINAL_BYTES : SESSION_FILE_MAX_ORIGINAL_BYTES;
1487
1410
  if (before.size > BigInt(maxOriginalBytes)) fail2("FILE_TOO_LARGE");
1488
1411
  const extension = extname(path).toLowerCase();
1489
- const image = [".png", ".jpg", ".jpeg", ".webp"].includes(extension);
1490
- const video = !resource && version === SESSION_FILE_READ_V2_VERSION && [".mp4", ".webm"].includes(extension);
1491
- const mediaType = resource ? resourceMediaType(extension) : image ? "image/webp" : video ? extension === ".mp4" ? "video/mp4" : "video/webm" : textMediaType(path);
1492
- const handle = await open(target, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0) | (constants2.O_NONBLOCK ?? 0));
1412
+ const resourceType = resource ? resourceMediaType(extension) : void 0;
1413
+ const handle = await open(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
1493
1414
  try {
1494
1415
  const opened = await handle.stat({ bigint: true });
1495
1416
  const includeChangeTime = process.platform !== "win32";
@@ -1509,27 +1430,43 @@ var SessionFileReader = class {
1509
1430
  };
1510
1431
  const bytes = await readOriginal();
1511
1432
  let canonical;
1512
- if (image) {
1513
- if (resource) {
1433
+ let mediaType;
1434
+ if (download) {
1435
+ mediaType = "application/octet-stream";
1436
+ canonical = bytes;
1437
+ } else if (resourceType !== void 0) {
1438
+ mediaType = resourceType;
1439
+ if (resourceType.startsWith("image/")) {
1514
1440
  await validateRawImage(bytes, extension, checkDeadline);
1515
- canonical = bytes;
1516
- } else canonical = await imagePreview(bytes, extension, checkDeadline);
1517
- } else if (mediaType === "video/mp4" || mediaType === "video/webm") {
1518
- try {
1519
- validateSessionVideo(bytes, mediaType);
1520
- } catch (error) {
1521
- throw new SessionFileReadError("FILE_UNSUPPORTED", { cause: error });
1522
- }
1441
+ } else if (!isPreviewableText(bytes)) fail2("FILE_UNSUPPORTED");
1442
+ canonical = bytes;
1443
+ } else if (isPreviewableText(bytes)) {
1444
+ mediaType = textMediaType(path);
1523
1445
  canonical = bytes;
1524
1446
  } else {
1525
- let text;
1447
+ let detected;
1526
1448
  try {
1527
- text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
1449
+ detected = await fileTypeFromBuffer(bytes, {
1450
+ maxBufferSize: maxOriginalBytes,
1451
+ signal: AbortSignal.timeout(SESSION_FILE_TIMEOUT_MS)
1452
+ });
1528
1453
  } catch (error) {
1529
1454
  throw new SessionFileReadError("FILE_UNSUPPORTED", { cause: error });
1530
1455
  }
1531
- if (/[\u0000-\u0008\u000b\u000e-\u001f\u007f]/u.test(text)) fail2("FILE_UNSUPPORTED");
1532
- canonical = bytes;
1456
+ checkDeadline();
1457
+ if (detected?.mime === "image/png" || detected?.mime === "image/jpeg" || detected?.mime === "image/webp") {
1458
+ mediaType = "image/webp";
1459
+ canonical = await imagePreview(bytes, `.${detected.ext}`, checkDeadline);
1460
+ } else if (detected?.mime === "video/mp4" || detected?.mime === "video/webm") {
1461
+ if (version !== SESSION_FILE_READ_V2_VERSION || extension !== (detected.mime === "video/mp4" ? ".mp4" : ".webm")) fail2("FILE_UNSUPPORTED");
1462
+ mediaType = detected.mime;
1463
+ try {
1464
+ validateSessionVideo(bytes, mediaType);
1465
+ } catch (error) {
1466
+ throw new SessionFileReadError("FILE_UNSUPPORTED", { cause: error });
1467
+ }
1468
+ canonical = bytes;
1469
+ } else fail2("FILE_UNSUPPORTED");
1533
1470
  }
1534
1471
  const threshold = resource ? SESSION_RESOURCE_GZIP_THRESHOLD_BYTES : SESSION_FILE_GZIP_THRESHOLD_BYTES;
1535
1472
  const encoding = originalByteLength >= threshold ? "gzip" : "identity";
@@ -1538,7 +1475,7 @@ var SessionFileReader = class {
1538
1475
  checkDeadline();
1539
1476
  try {
1540
1477
  const latestRoot = await realpath2(workingDirectory);
1541
- const latestRootInfo = await stat3(latestRoot, { bigint: true });
1478
+ const latestRootInfo = await stat2(latestRoot, { bigint: true });
1542
1479
  if (latestMetadata?.workingDirectory !== workingDirectory || (await lstat2(workingDirectory)).isSymbolicLink() || sessionRootIdentity(latestRoot, latestRootInfo) !== rootIdentity) fail2("SESSION_ROOT_CHANGED");
1543
1480
  } catch (error) {
1544
1481
  if (error instanceof SessionFileReadError) throw error;
@@ -1553,7 +1490,7 @@ var SessionFileReader = class {
1553
1490
  throw new SessionFileReadError("FILE_CHANGED", { cause: error });
1554
1491
  }
1555
1492
  checkDeadline();
1556
- return (resource ? SessionResourceReadResultDataSchema : version === SESSION_FILE_READ_V2_VERSION ? SessionFileReadV2ResultDataSchema : SessionFileReadResultDataSchema).parse({
1493
+ return (download ? SessionFileDownloadResultDataSchema : resource ? SessionResourceReadResultDataSchema : version === SESSION_FILE_READ_V2_VERSION ? SessionFileReadV2ResultDataSchema : SessionFileReadResultDataSchema).parse({
1557
1494
  path,
1558
1495
  rootRevision,
1559
1496
  mediaType,
@@ -1902,6 +1839,9 @@ function createWorkbenchOperations(options) {
1902
1839
  return routes;
1903
1840
  }
1904
1841
 
1842
+ // src/legacy-client-state.ts
1843
+ var LEGACY_CLIENT_STATE_DIRECTORY = Buffer.from("2e626c7565706177", "hex").toString("utf8");
1844
+
1905
1845
  // src/research-tools.ts
1906
1846
  var RESEARCH_TOOL_NAMES = ["research_list", "research_read", "research_search"];
1907
1847
  var RESEARCH_SEARCH_FILES = 64;
@@ -1919,11 +1859,11 @@ var ResearchError = class extends Error {
1919
1859
  code;
1920
1860
  };
1921
1861
  async function researchRoot(workingDirectory) {
1922
- if (!isAbsolute4(workingDirectory)) throw new ResearchError("SESSION_ROOT_UNAVAILABLE");
1862
+ if (!isAbsolute3(workingDirectory)) throw new ResearchError("SESSION_ROOT_UNAVAILABLE");
1923
1863
  try {
1924
1864
  if ((await lstat3(workingDirectory)).isSymbolicLink()) throw new ResearchError("SESSION_ROOT_UNAVAILABLE");
1925
1865
  const root = await realpath3(workingDirectory);
1926
- const info = await stat4(root, { bigint: true });
1866
+ const info = await stat3(root, { bigint: true });
1927
1867
  if (!info.isDirectory()) throw new ResearchError("SESSION_ROOT_UNAVAILABLE");
1928
1868
  return {
1929
1869
  workingDirectory: root,
@@ -1948,7 +1888,7 @@ var privateNames = /* @__PURE__ */ new Set([
1948
1888
  ".docker",
1949
1889
  ".copilot",
1950
1890
  ".ak",
1951
- ".bluepaw",
1891
+ LEGACY_CLIENT_STATE_DIRECTORY,
1952
1892
  ".copilot-remote",
1953
1893
  ".agentknight",
1954
1894
  "session-state",
@@ -2041,7 +1981,7 @@ var ResearchTools = class {
2041
1981
  }
2042
1982
  async #validate(path) {
2043
1983
  const source = await this.#source();
2044
- const target = join5(this.root.workingDirectory, ...path.split("/"));
1984
+ const target = join4(this.root.workingDirectory, ...path.split("/"));
2045
1985
  if (sensitiveSegments(this.root.workingDirectory.split(/[\\/]/u)) || !allowedPath(path, source.excludedPaths ?? []) || this.privateDirectories.some((directory) => contained(directory, target))) {
2046
1986
  throw new ResearchError("RESEARCH_PATH_BLOCKED", "The requested path is excluded from research");
2047
1987
  }
@@ -2086,7 +2026,7 @@ var ResearchTools = class {
2086
2026
  const source = await this.#source();
2087
2027
  return result.entries.filter((entry) => {
2088
2028
  const child = path ? `${path}/${entry.name}` : entry.name;
2089
- return entry.kind !== "blocked" && allowedPath(child, source.excludedPaths ?? []) && !this.privateDirectories.some((directory) => contained(directory, join5(this.root.workingDirectory, ...child.split("/"))));
2029
+ return entry.kind !== "blocked" && allowedPath(child, source.excludedPaths ?? []) && !this.privateDirectories.some((directory) => contained(directory, join4(this.root.workingDirectory, ...child.split("/"))));
2090
2030
  });
2091
2031
  }
2092
2032
  async list(input) {
@@ -2186,7 +2126,7 @@ var ResearchTools = class {
2186
2126
  if (path === void 0) break;
2187
2127
  inspected++;
2188
2128
  await this.#validate(path);
2189
- const target = join5(this.root.workingDirectory, ...path.split("/"));
2129
+ const target = join4(this.root.workingDirectory, ...path.split("/"));
2190
2130
  const info = await lstat3(target);
2191
2131
  if (info.isSymbolicLink()) {
2192
2132
  skippedFiles++;
@@ -2307,9 +2247,9 @@ function researchAwareCodingAgentAdapter(inner, research) {
2307
2247
  if (!research.store.has(id)) return false;
2308
2248
  if (inner.isSessionVisible?.(parent) !== false) return true;
2309
2249
  try {
2310
- const access2 = inner.captureResearchAccess?.(parent);
2311
- access2?.assertCurrent();
2312
- return access2?.detached === true;
2250
+ const access = inner.captureResearchAccess?.(parent);
2251
+ access?.assertCurrent();
2252
+ return access?.detached === true;
2313
2253
  } catch {
2314
2254
  return false;
2315
2255
  }
@@ -2461,7 +2401,7 @@ function createRelaySocket(uri, options) {
2461
2401
  // src/service/requests/session-communication.ts
2462
2402
  import { AsyncLocalStorage } from "node:async_hooks";
2463
2403
  import { createHash as createHash8 } from "node:crypto";
2464
- import { join as join7 } from "node:path";
2404
+ import { join as join6 } from "node:path";
2465
2405
  import { performance as performance2 } from "node:perf_hooks";
2466
2406
 
2467
2407
  // src/repeated-error-diagnostics.ts
@@ -2729,14 +2669,14 @@ var SessionPublicationScope = class {
2729
2669
 
2730
2670
  // src/service/requests/communication-operations.ts
2731
2671
  import { promisify as promisify2 } from "node:util";
2732
- import { constants as constants4, gzip as gzip3 } from "node:zlib";
2672
+ import { constants as constants3, gzip as gzip3 } from "node:zlib";
2733
2673
 
2734
2674
  // src/session-git-diff.ts
2735
2675
  import { execFile } from "node:child_process";
2736
2676
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
2737
- import { constants as constants3 } from "node:fs";
2738
- import { chmod, lstat as lstat4, mkdir as mkdir2, open as open2, readlink, realpath as realpath4, rm, stat as stat5, writeFile as writeFile2 } from "node:fs/promises";
2739
- import { dirname, isAbsolute as isAbsolute5, join as join6, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
2677
+ import { constants as constants2 } from "node:fs";
2678
+ import { chmod, lstat as lstat4, mkdir as mkdir2, open as open2, readlink, realpath as realpath4, rm, stat as stat4, writeFile as writeFile2 } from "node:fs/promises";
2679
+ import { dirname, isAbsolute as isAbsolute4, join as join5, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
2740
2680
  var SESSION_GIT_DIFF_CONCURRENCY = 4;
2741
2681
  var SESSION_GIT_DIFF_TIMEOUT_MS = 15e3;
2742
2682
  var SESSION_GIT_DIFF_MAX_SOURCE_BYTES = 8 * 1024 * 1024;
@@ -2752,6 +2692,28 @@ var SessionGitDiffError = class extends Error {
2752
2692
  function fail3(code, message) {
2753
2693
  throw new SessionGitDiffError(code, message);
2754
2694
  }
2695
+ function fileError(error) {
2696
+ if (error instanceof SessionGitDiffError && error.code === "DIFF_BLOCKED") {
2697
+ return { code: "DIFF_BLOCKED", message: error.message };
2698
+ }
2699
+ if (error instanceof Error && "code" in error && typeof error.code === "string" && ["EACCES", "EPERM", "EBUSY", "ENAMETOOLONG", "EINVAL", "EIO", "ELOOP"].includes(error.code)) {
2700
+ return { code: "DIFF_FILE_UNAVAILABLE", message: `Cannot read this path (${error.code})` };
2701
+ }
2702
+ return void 0;
2703
+ }
2704
+ async function forFile(path, work, operation) {
2705
+ try {
2706
+ return await operation();
2707
+ } catch (error) {
2708
+ const failure = work.partial ? fileError(error) : void 0;
2709
+ if (failure === void 0) throw error;
2710
+ throw new SessionGitDiffError(
2711
+ failure.code === "DIFF_BLOCKED" ? "DIFF_FILE_BLOCKED" : failure.code,
2712
+ `${path}: ${failure.message}`,
2713
+ { cause: error }
2714
+ );
2715
+ }
2716
+ }
2755
2717
  function changed() {
2756
2718
  return fail3("DIFF_CHANGED", "The project diff changed; refresh the changed-file list");
2757
2719
  }
@@ -2787,12 +2749,30 @@ function environment() {
2787
2749
  function isExit(error, code) {
2788
2750
  return error instanceof Error && "code" in error && error.code === code && "stdout" in error && Buffer.isBuffer(error.stdout) && "stderr" in error && Buffer.isBuffer(error.stderr);
2789
2751
  }
2752
+ function hashFileError(error, path) {
2753
+ if (!isExit(error, 128)) return void 0;
2754
+ const diagnostic = decode(error.stderr).trim().split(/\r?\n/u).at(-1) ?? "";
2755
+ if (!diagnostic.startsWith(`fatal: could not open '${path}' for reading: `)) return void 0;
2756
+ return { code: "DIFF_FILE_UNAVAILABLE", message: "Cannot normalize this path for comparison" };
2757
+ }
2790
2758
  var Work = class {
2759
+ constructor(partial) {
2760
+ this.partial = partial;
2761
+ }
2762
+ partial;
2791
2763
  deadline = Date.now() + SESSION_GIT_DIFF_TIMEOUT_MS;
2792
2764
  env = environment();
2793
2765
  bytes = 0;
2766
+ linkFiles = /* @__PURE__ */ new Set();
2767
+ symlinks = true;
2794
2768
  #filterConfig = [];
2795
2769
  async disableExternalFilters(repository) {
2770
+ this.symlinks = decode(await this.git(repository, [
2771
+ "config",
2772
+ "--type=bool",
2773
+ "--get",
2774
+ "core.symlinks"
2775
+ ], { allowExitOne: true })).trim() !== "false";
2796
2776
  const names = records(await this.git(repository, [
2797
2777
  "config",
2798
2778
  "--includes",
@@ -2842,10 +2822,10 @@ var Work = class {
2842
2822
  ...this.env,
2843
2823
  ...options.objectDirectory === void 0 ? {} : { GIT_OBJECT_DIRECTORY: options.objectDirectory },
2844
2824
  ...options.isolated ? {
2845
- GIT_DIR: join6(cwd, "no-repository"),
2825
+ GIT_DIR: join5(cwd, "no-repository"),
2846
2826
  GIT_CONFIG_NOSYSTEM: "1",
2847
2827
  GIT_ATTR_NOSYSTEM: "1",
2848
- GIT_CONFIG_GLOBAL: join6(cwd, "gitconfig")
2828
+ GIT_CONFIG_GLOBAL: join5(cwd, "gitconfig")
2849
2829
  } : {}
2850
2830
  }
2851
2831
  }, (error, output2, stderr) => {
@@ -2873,7 +2853,7 @@ async function inspect(root, path, work, kind = "file") {
2873
2853
  let current = root;
2874
2854
  const segments = path.split("/");
2875
2855
  for (const [index, segment] of segments.entries()) {
2876
- current = join6(current, segment);
2856
+ current = join5(current, segment);
2877
2857
  let info;
2878
2858
  try {
2879
2859
  info = await lstat4(current, { bigint: true });
@@ -2898,13 +2878,27 @@ async function inspect(root, path, work, kind = "file") {
2898
2878
  return void 0;
2899
2879
  }
2900
2880
  async function readCurrent(root, path, algorithm, work, keepBytes = false) {
2901
- const before = await inspect(root, path, work);
2881
+ const kind = work.partial ? "link" : "file";
2882
+ const before = await inspect(root, path, work, kind);
2902
2883
  if (before === void 0) return void 0;
2884
+ if (before.isSymbolicLink()) {
2885
+ const bytes = Buffer.from(await readlink(join5(root, ...path.split("/"))), "utf8");
2886
+ const latest = await inspect(root, path, work, "link");
2887
+ if (latest === void 0 || identity(latest) !== identity(before)) changed();
2888
+ const oid = createHash5(algorithm).update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
2889
+ return {
2890
+ oid,
2891
+ rawOid: oid,
2892
+ identity: identity(before),
2893
+ mode: "120000",
2894
+ ...keepBytes ? { bytes } : {}
2895
+ };
2896
+ }
2903
2897
  if (before.size > BigInt(MAX_SCAN_BYTES - work.bytes) || keepBytes && before.size > BigInt(SESSION_GIT_DIFF_MAX_SOURCE_BYTES)) limit();
2904
2898
  work.bytes += Number(before.size);
2905
2899
  const handle = await open2(
2906
- join6(root, ...path.split("/")),
2907
- constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0) | (constants3.O_NONBLOCK ?? 0)
2900
+ join5(root, ...path.split("/")),
2901
+ constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0) | (constants2.O_NONBLOCK ?? 0)
2908
2902
  );
2909
2903
  try {
2910
2904
  const opened = await handle.stat({ bigint: true });
@@ -2929,7 +2923,7 @@ async function readCurrent(root, path, algorithm, work, keepBytes = false) {
2929
2923
  oid: rawOid,
2930
2924
  rawOid,
2931
2925
  identity: identity(before),
2932
- mode: process.platform !== "win32" && (before.mode & 0o111n) !== 0n ? "100755" : "100644",
2926
+ mode: work.linkFiles.has(path) ? "120000" : process.platform !== "win32" && (before.mode & 0o111n) !== 0n ? "100755" : "100644",
2933
2927
  ...keepBytes ? { bytes: Buffer.concat(chunks) } : {}
2934
2928
  };
2935
2929
  } finally {
@@ -2965,7 +2959,6 @@ function scopedPath(context, path) {
2965
2959
  const prefix = context.prefix === "" ? "" : `${context.prefix}/`;
2966
2960
  if (!path.startsWith(prefix)) fail3("DIFF_BLOCKED", "Git returned a path outside the session");
2967
2961
  const scoped = path.slice(prefix.length);
2968
- if (!safePath(scoped)) fail3("DIFF_BLOCKED", "The project contains an unsupported path");
2969
2962
  return scoped;
2970
2963
  }
2971
2964
  function records(output2) {
@@ -2996,7 +2989,7 @@ async function changes(context, work) {
2996
2989
  return scoped2;
2997
2990
  };
2998
2991
  for (const record of tree) {
2999
- const match = /^(\d{6}) (blob|commit) ([a-f0-9]{40}|[a-f0-9]{64})\t(.+)$/u.exec(record);
2992
+ const match = /^(\d{6}) (blob|commit) ([a-f0-9]{40}|[a-f0-9]{64})\t([\s\S]+)$/u.exec(record);
3000
2993
  if (!match?.[1] || !match[3] || !match[4]) fail3("DIFF_UNAVAILABLE", "Git returned an invalid tree");
3001
2994
  blobs.set(scoped(match[4]), { mode: match[1], oid: match[3] });
3002
2995
  }
@@ -3008,7 +3001,7 @@ async function changes(context, work) {
3008
3001
  "--",
3009
3002
  ...paths
3010
3003
  ]))) {
3011
- const match = /^(\d{6}) ([a-f0-9]{40}|[a-f0-9]{64}) [0-3]\t(.+)$/u.exec(record);
3004
+ const match = /^(\d{6}) ([a-f0-9]{40}|[a-f0-9]{64}) [0-3]\t([\s\S]+)$/u.exec(record);
3012
3005
  if (!match?.[1] || !match[2] || !match[3]) fail3("DIFF_UNAVAILABLE", "Git returned an invalid index");
3013
3006
  tracked.set(scoped(match[3]), { mode: match[1], oid: match[2] });
3014
3007
  }
@@ -3047,38 +3040,88 @@ async function changes(context, work) {
3047
3040
  files: untracked
3048
3041
  }), "utf8") > SESSION_GIT_DIFF_MAX_RESPONSE_BYTES) limit();
3049
3042
  const result = [];
3043
+ const errors = /* @__PURE__ */ new Map();
3050
3044
  const algorithm = context.baseCommit.length === 64 ? "sha256" : "sha1";
3051
3045
  const currentFiles = /* @__PURE__ */ new Map();
3046
+ if (work.partial && !work.symlinks) {
3047
+ for (const [path, blob] of tracked) if (blob.mode === "120000") work.linkFiles.add(path);
3048
+ }
3052
3049
  for (const path of [...candidates].sort()) {
3053
3050
  work.check();
3054
- const original = blobs.get(path);
3055
- const indexed = tracked.get(path);
3056
- if ([original?.mode, indexed?.mode].some((mode) => mode !== void 0 && mode !== "100644" && mode !== "100755")) {
3057
- await unchangedSpecial(context, path, original, indexed, work);
3058
- candidates.delete(path);
3059
- continue;
3051
+ try {
3052
+ if (!safePath(path)) fail3("DIFF_BLOCKED", "This path cannot be safely accessed on the PC");
3053
+ const original = blobs.get(path);
3054
+ const indexed = tracked.get(path);
3055
+ if ([original?.mode, indexed?.mode].some((mode) => mode !== void 0 && mode !== "100644" && mode !== "100755" && !(work.partial && mode === "120000"))) {
3056
+ await unchangedSpecial(context, path, original, indexed, work);
3057
+ candidates.delete(path);
3058
+ continue;
3059
+ }
3060
+ const current = await readCurrent(context.root, path, algorithm, work);
3061
+ if (current !== void 0) currentFiles.set(path, current);
3062
+ } catch (error) {
3063
+ const failure = fileError(error);
3064
+ if (failure === void 0) throw error;
3065
+ errors.set(path, failure);
3060
3066
  }
3061
- const current = await readCurrent(context.root, path, algorithm, work);
3062
- if (current !== void 0) currentFiles.set(path, current);
3063
3067
  }
3064
- if (currentFiles.size > 0) {
3065
- const hashes = decode(await work.git(context.repository, ["hash-object", "--stdin-paths"], {
3066
- input: [...currentFiles.keys()].map((path) => [context.prefix, path].filter(Boolean).join("/")).join("\n") + "\n"
3067
- })).trim().split("\n");
3068
- if (hashes.length !== currentFiles.size) changed();
3069
- for (const [index, [path, current]] of [...currentFiles].entries()) {
3070
- const oid = hashes[index];
3071
- if (!oid || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(oid)) changed();
3072
- const latest = await inspect(context.root, path, work);
3073
- if (latest === void 0 || identity(latest) !== current.identity) changed();
3074
- current.oid = oid;
3068
+ const regularFiles = [...currentFiles].filter(([, current]) => current.mode !== "120000");
3069
+ if (regularFiles.length > 0) {
3070
+ let hashes;
3071
+ try {
3072
+ hashes = decode(await work.git(context.repository, ["hash-object", "--stdin-paths"], {
3073
+ input: regularFiles.map(([path]) => JSON.stringify([context.prefix, path].filter(Boolean).join("/"))).join("\n") + "\n"
3074
+ })).trim().split("\n");
3075
+ } catch (error) {
3076
+ if (!work.partial || !isExit(error, 128) || regularFiles.length > SESSION_GIT_DIFF_MAX_FILES) throw error;
3077
+ hashes = [];
3078
+ for (const [path] of regularFiles) {
3079
+ const repositoryPath = [context.prefix, path].filter(Boolean).join("/");
3080
+ try {
3081
+ hashes.push(decode(await work.git(context.repository, ["hash-object", "--stdin-paths"], {
3082
+ input: JSON.stringify(repositoryPath) + "\n"
3083
+ })).trim());
3084
+ } catch (error2) {
3085
+ const failure = hashFileError(error2, repositoryPath);
3086
+ if (failure === void 0) throw error2;
3087
+ errors.set(path, failure);
3088
+ currentFiles.delete(path);
3089
+ hashes.push("");
3090
+ }
3091
+ }
3092
+ }
3093
+ if (hashes.length !== regularFiles.length) changed();
3094
+ for (const [index, [path, current]] of regularFiles.entries()) {
3095
+ if (errors.has(path)) continue;
3096
+ try {
3097
+ const oid = hashes[index];
3098
+ if (!oid || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(oid)) changed();
3099
+ const latest = await inspect(context.root, path, work);
3100
+ if (latest === void 0 || identity(latest) !== current.identity) changed();
3101
+ current.oid = oid;
3102
+ } catch (error) {
3103
+ const failure = work.partial ? fileError(error) : void 0;
3104
+ if (failure === void 0) throw error;
3105
+ errors.set(path, failure);
3106
+ currentFiles.delete(path);
3107
+ }
3075
3108
  }
3076
3109
  }
3077
3110
  for (const path of [...candidates].sort()) {
3078
3111
  const original = blobs.get(path);
3079
3112
  const current = currentFiles.get(path);
3113
+ const error = errors.get(path);
3114
+ if (error !== void 0) {
3115
+ result.push({
3116
+ entry: { path, status: original === void 0 ? tracked.has(path) ? "added" : "untracked" : tracked.has(path) ? "modified" : "deleted", error },
3117
+ base: original,
3118
+ current: void 0
3119
+ });
3120
+ if (result.length > SESSION_GIT_DIFF_MAX_FILES) limit();
3121
+ continue;
3122
+ }
3080
3123
  if (original === void 0 && current === void 0) continue;
3081
- if (original?.oid === current?.oid && (process.platform === "win32" || original?.mode === current?.mode)) continue;
3124
+ if (original?.oid === current?.oid && original?.mode === "120000" === (current?.mode === "120000") && (process.platform === "win32" || original?.mode === current?.mode)) continue;
3082
3125
  result.push({
3083
3126
  entry: { path, status: original === void 0 ? tracked.has(path) ? "added" : "untracked" : current === void 0 ? "deleted" : "modified" },
3084
3127
  base: original,
@@ -3093,9 +3136,9 @@ async function unchangedSpecial(context, path, original, indexed, work) {
3093
3136
  if (original === void 0 || indexed?.mode !== original.mode || indexed.oid !== original.oid) return blocked();
3094
3137
  if (original.mode === "160000") {
3095
3138
  if (await inspect(context.root, path, work, "directory") === void 0) blocked();
3096
- const target = join6(context.root, ...path.split("/"));
3139
+ const target = join5(context.root, ...path.split("/"));
3097
3140
  try {
3098
- await lstat4(join6(target, ".git"));
3141
+ await lstat4(join5(target, ".git"));
3099
3142
  } catch (error) {
3100
3143
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
3101
3144
  throw error;
@@ -3107,7 +3150,7 @@ async function unchangedSpecial(context, path, original, indexed, work) {
3107
3150
  const algorithm = context.baseCommit.length === 64 ? "sha256" : "sha1";
3108
3151
  let oid;
3109
3152
  if (info.isSymbolicLink()) {
3110
- const target = Buffer.from(await readlink(join6(context.root, ...path.split("/"))), "utf8");
3153
+ const target = Buffer.from(await readlink(join5(context.root, ...path.split("/"))), "utf8");
3111
3154
  oid = createHash5(algorithm).update(`blob ${target.length}\0`).update(target).digest("hex");
3112
3155
  } else {
3113
3156
  oid = (await readCurrent(context.root, path, algorithm, work))?.rawOid;
@@ -3130,7 +3173,20 @@ async function patch(context, change, work, scratchDirectory) {
3130
3173
  if (current?.rawOid !== change.current?.rawOid || current?.mode !== change.current?.mode) changed();
3131
3174
  let original = Buffer.alloc(0);
3132
3175
  if (change.base !== void 0) {
3133
- const size = Number(decode(await work.git(context.repository, ["cat-file", "-s", change.base.oid])).trim());
3176
+ let sizeBytes;
3177
+ try {
3178
+ sizeBytes = await work.git(context.repository, ["cat-file", "-s", change.base.oid]);
3179
+ } catch (error) {
3180
+ if (work.partial && isExit(error, 128) && decode(error.stderr).trim() === "fatal: git cat-file: could not get object info") {
3181
+ throw new SessionGitDiffError(
3182
+ "DIFF_FILE_UNAVAILABLE",
3183
+ `${change.entry.path}: The base version of this file is unavailable`,
3184
+ { cause: error }
3185
+ );
3186
+ }
3187
+ throw error;
3188
+ }
3189
+ const size = Number(decode(sizeBytes).trim());
3134
3190
  if (!Number.isSafeInteger(size) || size < 0 || size > SESSION_GIT_DIFF_MAX_SOURCE_BYTES) limit();
3135
3191
  original = await work.git(context.repository, ["cat-file", "blob", change.base.oid], { maxBuffer: size + 1 });
3136
3192
  if (original.length !== size) changed();
@@ -3143,13 +3199,13 @@ async function patch(context, change, work, scratchDirectory) {
3143
3199
  if ((await lstat4(directory)).isSymbolicLink() || !samePath(await realpath4(directory), directory)) {
3144
3200
  fail3("DIFF_UNAVAILABLE", "Project diff storage cannot use links");
3145
3201
  }
3146
- const scratch = join6(directory, randomUUID3());
3202
+ const scratch = join5(directory, randomUUID3());
3147
3203
  await mkdir2(scratch, { mode: 448 });
3148
3204
  let text;
3149
3205
  try {
3150
3206
  let bytes = current?.bytes ?? Buffer.alloc(0);
3151
- if (current !== void 0) {
3152
- const objectDirectory = join6(scratch, "objects");
3207
+ if (current !== void 0 && current.mode !== "120000") {
3208
+ const objectDirectory = join5(scratch, "objects");
3153
3209
  await mkdir2(objectDirectory);
3154
3210
  const path = [context.prefix, change.entry.path].filter(Boolean).join("/");
3155
3211
  const oid = decode(await work.git(context.repository, ["hash-object", "-w", "--stdin", `--path=${path}`], {
@@ -3163,18 +3219,18 @@ async function patch(context, change, work, scratchDirectory) {
3163
3219
  });
3164
3220
  }
3165
3221
  if (binary(original) || binary(bytes)) return { patch: "", binary: true };
3166
- for (const side of ["a", "b"]) await mkdir2(join6(scratch, side));
3222
+ for (const side of ["a", "b"]) await mkdir2(join5(scratch, side));
3167
3223
  for (const [side, content, mode] of [
3168
3224
  ["a", original, change.base?.mode],
3169
3225
  ["b", bytes, current?.mode]
3170
3226
  ]) {
3171
3227
  if (mode === void 0) continue;
3172
- const file = join6(scratch, side, ...change.entry.path.split("/"));
3228
+ const file = join5(scratch, side, ...change.entry.path.split("/"));
3173
3229
  await mkdir2(dirname(file), { recursive: true });
3174
3230
  await writeFile2(file, content, { flag: "wx", mode: 384 });
3175
3231
  if (process.platform !== "win32") await chmod(file, mode === "100755" ? 448 : 384);
3176
3232
  }
3177
- await writeFile2(join6(scratch, "gitconfig"), "", { flag: "wx", mode: 384 });
3233
+ await writeFile2(join5(scratch, "gitconfig"), "", { flag: "wx", mode: 384 });
3178
3234
  text = decode(await work.git(scratch, [
3179
3235
  "-c",
3180
3236
  "core.quotePath=false",
@@ -3196,6 +3252,18 @@ async function patch(context, change, work, scratchDirectory) {
3196
3252
  } finally {
3197
3253
  await rm(scratch, { recursive: true, force: true, maxRetries: 3 });
3198
3254
  }
3255
+ if (current?.mode === "120000" || change.base?.mode === "120000") {
3256
+ text = text.replace(/^new file mode \d+$/mu, `new file mode ${current?.mode}`).replace(/^deleted file mode \d+$/mu, `deleted file mode ${change.base?.mode}`);
3257
+ if (current !== void 0 && change.base !== void 0 && current.mode !== change.base.mode) {
3258
+ text = text.replace(/^old mode \d+\nnew mode \d+\n/mu, "");
3259
+ if (text === "") text = `diff --git a/${change.entry.path} b/${change.entry.path}
3260
+ `;
3261
+ const end = text.indexOf("\n") + 1;
3262
+ text = `${text.slice(0, end)}old mode ${change.base.mode}
3263
+ new mode ${current.mode}
3264
+ ${text.slice(end)}`;
3265
+ }
3266
+ }
3199
3267
  if (Buffer.byteLength(text, "utf8") > SESSION_GIT_DIFF_MAX_PATCH_BYTES) limit();
3200
3268
  return { patch: text, binary: false };
3201
3269
  }
@@ -3207,14 +3275,14 @@ var SessionGitDiffReader = class {
3207
3275
  metadata;
3208
3276
  scratchDirectory;
3209
3277
  #active = 0;
3210
- async read(sessionId, data) {
3278
+ async read(sessionId, data, version = SESSION_GIT_DIFF_PARTIAL_VERSION) {
3211
3279
  if (!SessionGitDiffDataSchema.safeParse(data).success || data.path !== void 0 && !safePath(data.path)) {
3212
3280
  fail3("INVALID_DIFF_PATH", "The project diff request is invalid");
3213
3281
  }
3214
3282
  if (this.#active >= SESSION_GIT_DIFF_CONCURRENCY) fail3("DIFF_BUSY", "Too many project diff requests");
3215
3283
  this.#active++;
3216
- const work = new Work();
3217
- const pending = this.#read(sessionId, data, work).finally(() => {
3284
+ const work = new Work(version >= SESSION_GIT_DIFF_PARTIAL_VERSION);
3285
+ const pending = this.#read(sessionId, data, work, version).finally(() => {
3218
3286
  this.#active--;
3219
3287
  });
3220
3288
  let timer;
@@ -3240,7 +3308,7 @@ var SessionGitDiffReader = class {
3240
3308
  work.check();
3241
3309
  if (metadata === void 0) fail3("SESSION_NOT_FOUND", "The session is unavailable");
3242
3310
  const workingDirectory = metadata.workingDirectory;
3243
- if (!workingDirectory || !isAbsolute5(workingDirectory)) {
3311
+ if (!workingDirectory || !isAbsolute4(workingDirectory)) {
3244
3312
  fail3("SESSION_ROOT_UNAVAILABLE", "The session working directory is unavailable");
3245
3313
  }
3246
3314
  let root;
@@ -3250,7 +3318,7 @@ var SessionGitDiffReader = class {
3250
3318
  if ((await lstat4(workingDirectory)).isSymbolicLink() || !samePath(root, resolve2(workingDirectory))) {
3251
3319
  fail3("DIFF_BLOCKED", "Links cannot be compared");
3252
3320
  }
3253
- const info = await stat5(root, { bigint: true });
3321
+ const info = await stat4(root, { bigint: true });
3254
3322
  if (!info.isDirectory()) fail3("SESSION_ROOT_UNAVAILABLE", "The session working directory is unavailable");
3255
3323
  rootIdentity = sessionRootIdentity(root, info);
3256
3324
  } catch (error) {
@@ -3263,7 +3331,7 @@ var SessionGitDiffReader = class {
3263
3331
  let repository;
3264
3332
  try {
3265
3333
  const top = decode(await work.git(root, ["rev-parse", "--show-toplevel"])).replace(/\r?\n$/u, "");
3266
- if (!isAbsolute5(top)) fail3("DIFF_UNAVAILABLE", "Git returned an invalid repository");
3334
+ if (!isAbsolute4(top)) fail3("DIFF_UNAVAILABLE", "Git returned an invalid repository");
3267
3335
  repository = await realpath4(top);
3268
3336
  } catch (error) {
3269
3337
  if (error instanceof SessionGitDiffError) throw error;
@@ -3282,12 +3350,12 @@ var SessionGitDiffReader = class {
3282
3350
  rootIdentity,
3283
3351
  workingDirectory,
3284
3352
  repository,
3285
- repositoryIdentity: sessionRootIdentity(repository, await stat5(repository, { bigint: true })),
3353
+ repositoryIdentity: sessionRootIdentity(repository, await stat4(repository, { bigint: true })),
3286
3354
  prefix: relative2(repository, root).split(sep2).join("/"),
3287
3355
  ...await base(repository, work)
3288
3356
  };
3289
3357
  }
3290
- async #read(sessionId, data, work) {
3358
+ async #read(sessionId, data, work, version) {
3291
3359
  const context = await this.#context(sessionId, data.rootRevision, work);
3292
3360
  if (data.baseCommit !== void 0 && data.baseCommit !== context.baseCommit) changed();
3293
3361
  await work.disableExternalFilters(context.repository);
@@ -3300,7 +3368,23 @@ var SessionGitDiffReader = class {
3300
3368
  } else {
3301
3369
  selected = files.find(({ entry }) => entry.path === data.path);
3302
3370
  if (selected === void 0) changed();
3303
- result = { ...common, path: data.path, ...await patch(context, selected, work, this.scratchDirectory) };
3371
+ if (selected.entry.error !== void 0) {
3372
+ const error = selected.entry.error;
3373
+ fail3(
3374
+ work.partial && error.code === "DIFF_BLOCKED" ? "DIFF_FILE_BLOCKED" : error.code,
3375
+ `${data.path}: ${error.message}`
3376
+ );
3377
+ }
3378
+ const change = selected;
3379
+ result = {
3380
+ ...common,
3381
+ path: data.path,
3382
+ ...await forFile(data.path, work, () => patch(context, change, work, this.scratchDirectory))
3383
+ };
3384
+ }
3385
+ if (version < SESSION_GIT_DIFF_PARTIAL_VERSION) {
3386
+ const blocked = files.find(({ entry }) => entry.error !== void 0)?.entry;
3387
+ if (blocked?.error !== void 0) fail3(blocked.error.code, `${blocked.path}: ${blocked.error.message}`);
3304
3388
  }
3305
3389
  let latest;
3306
3390
  try {
@@ -3319,17 +3403,18 @@ var SessionGitDiffReader = class {
3319
3403
  }
3320
3404
  if (latest.repositoryIdentity !== context.repositoryIdentity || latest.baseRef !== context.baseRef || latest.baseCommit !== context.baseCommit) changed();
3321
3405
  if (selected !== void 0) {
3322
- const current = await readCurrent(
3406
+ const path = selected.entry.path;
3407
+ const current = await forFile(path, work, () => readCurrent(
3323
3408
  context.root,
3324
- selected.entry.path,
3409
+ path,
3325
3410
  context.baseCommit.length === 64 ? "sha256" : "sha1",
3326
3411
  work
3327
- );
3412
+ ));
3328
3413
  if (current?.rawOid !== selected.current?.rawOid || current?.mode !== selected.current?.mode) changed();
3329
3414
  }
3330
3415
  work.check();
3331
3416
  if (Buffer.byteLength(JSON.stringify(result), "utf8") > SESSION_GIT_DIFF_MAX_RESPONSE_BYTES) limit();
3332
- return SessionGitDiffResultDataSchema.parse(result);
3417
+ return (version >= SESSION_GIT_DIFF_PARTIAL_VERSION ? SessionGitDiffPartialResultDataSchema : SessionGitDiffResultDataSchema).parse(result);
3333
3418
  }
3334
3419
  };
3335
3420
 
@@ -3845,22 +3930,22 @@ function createCommunicationOperations(options) {
3845
3930
  ],
3846
3931
  ...options.incrementalOperations,
3847
3932
  ...adapter.getSessionDirectoryMetadata === void 0 ? [] : [
3848
- ...[SESSION_GIT_DIFF_VERSION, SESSION_GIT_DIFF_GZIP_VERSION].map((version) => sessionCommunicationOperation({
3933
+ ...[SESSION_GIT_DIFF_VERSION, SESSION_GIT_DIFF_GZIP_VERSION, SESSION_GIT_DIFF_PARTIAL_VERSION].map((version) => sessionCommunicationOperation({
3849
3934
  operation: SESSION_GIT_DIFF_OPERATION,
3850
3935
  version,
3851
3936
  schema: SessionGitDiffDataSchema,
3852
3937
  handle: async (data, request) => {
3853
3938
  const assertAccess = options.captureRequestAccess();
3854
3939
  assertAccess();
3855
- const result = await gitDiffReader.read(request.sessionId, data);
3940
+ const result = await gitDiffReader.read(request.sessionId, data, version);
3856
3941
  let response = SessionCommunicationDataSchema.parse(result);
3857
- if (version === SESSION_GIT_DIFF_GZIP_VERSION) {
3942
+ if (version >= SESSION_GIT_DIFF_GZIP_VERSION) {
3858
3943
  const bytes = Buffer.from(JSON.stringify(result), "utf8");
3859
3944
  if (bytes.length >= SESSION_GIT_DIFF_GZIP_MIN_BYTES) {
3860
3945
  response = SessionGitDiffGzipResultDataSchema.parse({
3861
3946
  encoding: "gzip",
3862
3947
  byteLength: bytes.length,
3863
- data: (await compress2(bytes, { level: constants4.Z_BEST_SPEED })).toString("base64")
3948
+ data: (await compress2(bytes, { level: constants3.Z_BEST_SPEED })).toString("base64")
3864
3949
  });
3865
3950
  }
3866
3951
  }
@@ -3889,6 +3974,22 @@ function createCommunicationOperations(options) {
3889
3974
  message: "The file preview is unavailable"
3890
3975
  }
3891
3976
  })),
3977
+ sessionCommunicationOperation({
3978
+ operation: SESSION_FILE_DOWNLOAD_OPERATION,
3979
+ version: SESSION_FILE_DOWNLOAD_VERSION,
3980
+ schema: SessionFileDownloadDataSchema,
3981
+ handle: async ({ path, rootRevision }, request) => {
3982
+ const assertAccess = options.captureRequestAccess();
3983
+ assertAccess();
3984
+ const result = await fileReader.download(request.sessionId, path, rootRevision);
3985
+ assertAccess();
3986
+ return result;
3987
+ },
3988
+ onError: (error) => unsupportedOperation(error) ?? {
3989
+ code: error instanceof SessionFileReadError || error instanceof SessionNotSharedError ? error.code : "FILE_UNAVAILABLE",
3990
+ message: "The file download is unavailable"
3991
+ }
3992
+ }),
3892
3993
  sessionCommunicationOperation({
3893
3994
  operation: SESSION_RESOURCE_READ_OPERATION,
3894
3995
  version: SESSION_RESOURCE_READ_VERSION,
@@ -3935,14 +4036,14 @@ var SessionCommunicationHandler = class {
3935
4036
  const workbench = adapter.agentId === "copilot" || adapter.agentId === "managed" ? adapter.workbench : void 0;
3936
4037
  const researchWorkbench = workbench === void 0 ? void 0 : research;
3937
4038
  this.#operations = workbench === void 0 && researchWorkbench === void 0 ? void 0 : new SessionOperations({
3938
- directory: join7(config.dataDirectory, "workbench"),
4039
+ directory: join6(config.dataDirectory, "workbench"),
3939
4040
  agentRunId,
3940
4041
  namespace: `${config.userId}\0${machineId}`,
3941
4042
  onError: operationError
3942
4043
  });
3943
4044
  this.#dispatcher = new SessionCommunicationDispatcher(createCommunicationOperations({
3944
4045
  adapter,
3945
- gitDiffDirectory: join7(config.dataDirectory, "git-diff"),
4046
+ gitDiffDirectory: join6(config.dataDirectory, "git-diff"),
3946
4047
  publications: options.publications,
3947
4048
  captureRequestAccess: () => {
3948
4049
  const assertCurrent = this.#requestContext.getStore()?.assertRequestCurrent;
@@ -4010,9 +4111,9 @@ var SessionCommunicationHandler = class {
4010
4111
  let visible = adapter.isSessionVisible(parent ?? communication.sessionId);
4011
4112
  if (!visible && (parent !== void 0 || communication.operation === "research.list")) {
4012
4113
  try {
4013
- const access2 = this.options.captureResearchAccess(parent ?? communication.sessionId);
4014
- access2.assertCurrent();
4015
- visible = access2.detached;
4114
+ const access = this.options.captureResearchAccess(parent ?? communication.sessionId);
4115
+ access.assertCurrent();
4116
+ visible = access.detached;
4016
4117
  } catch {
4017
4118
  visible = false;
4018
4119
  }
@@ -4300,16 +4401,16 @@ var SessionCommunicationHandler = class {
4300
4401
 
4301
4402
  // src/desktop-workspaces.ts
4302
4403
  import { createHash as createHash9 } from "node:crypto";
4303
- import { readdir, realpath as realpath5, stat as stat6 } from "node:fs/promises";
4304
- import { homedir as homedir2 } from "node:os";
4305
- import { basename as basename2, join as join8, normalize } from "node:path";
4404
+ import { readdir, realpath as realpath5, stat as stat5 } from "node:fs/promises";
4405
+ import { homedir } from "node:os";
4406
+ import { basename, join as join7, normalize } from "node:path";
4306
4407
  function desktopRootCandidates(environment2 = process.env) {
4307
4408
  const candidates = [
4308
4409
  environment2.AGENTKNIGHT_WORKSPACE_ROOT,
4309
- environment2.OneDrive === void 0 ? void 0 : join8(environment2.OneDrive, "Desktop"),
4310
- environment2.OneDriveCommercial === void 0 ? void 0 : join8(environment2.OneDriveCommercial, "Desktop"),
4311
- environment2.OneDriveConsumer === void 0 ? void 0 : join8(environment2.OneDriveConsumer, "Desktop"),
4312
- join8(environment2.USERPROFILE ?? homedir2(), "Desktop")
4410
+ environment2.OneDrive === void 0 ? void 0 : join7(environment2.OneDrive, "Desktop"),
4411
+ environment2.OneDriveCommercial === void 0 ? void 0 : join7(environment2.OneDriveCommercial, "Desktop"),
4412
+ environment2.OneDriveConsumer === void 0 ? void 0 : join7(environment2.OneDriveConsumer, "Desktop"),
4413
+ join7(environment2.USERPROFILE ?? homedir(), "Desktop")
4313
4414
  ];
4314
4415
  return [...new Set(candidates.filter((path) => path !== void 0))];
4315
4416
  }
@@ -4328,7 +4429,7 @@ async function findDesktopRoot(candidates) {
4328
4429
  for (const candidate of candidates) {
4329
4430
  try {
4330
4431
  const resolved = await realpath5(candidate);
4331
- if ((await stat6(resolved)).isDirectory()) {
4432
+ if ((await stat5(resolved)).isDirectory()) {
4332
4433
  return resolved;
4333
4434
  }
4334
4435
  } catch (error) {
@@ -4350,7 +4451,7 @@ async function listDesktopWorkspaces(rootOverride) {
4350
4451
  const workspaces = entries.filter(
4351
4452
  (entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name.trim().length > 0
4352
4453
  ).map((entry) => {
4353
- const path = join8(root, entry.name);
4454
+ const path = join7(root, entry.name);
4354
4455
  return {
4355
4456
  id: workspaceId(path),
4356
4457
  name: entry.name,
@@ -4371,12 +4472,12 @@ async function resolveSessionWorkspace(path) {
4371
4472
  }
4372
4473
  try {
4373
4474
  const resolved = await realpath5(candidate);
4374
- if (!(await stat6(resolved)).isDirectory()) {
4475
+ if (!(await stat5(resolved)).isDirectory()) {
4375
4476
  return void 0;
4376
4477
  }
4377
4478
  return {
4378
4479
  id: workspaceId(resolved),
4379
- name: basename2(resolved) || resolved,
4480
+ name: basename(resolved) || resolved,
4380
4481
  path: resolved
4381
4482
  };
4382
4483
  } catch (error) {
@@ -4422,7 +4523,7 @@ import { execFile as execFile2 } from "node:child_process";
4422
4523
  import { randomUUID as randomUUID4 } from "node:crypto";
4423
4524
  import { lstat as lstat5, mkdir as mkdir3, realpath as realpath6, rmdir } from "node:fs/promises";
4424
4525
  import { devNull } from "node:os";
4425
- import { dirname as dirname2, isAbsolute as isAbsolute6, join as join9, relative as relative3 } from "node:path";
4526
+ import { dirname as dirname2, isAbsolute as isAbsolute5, join as join8, relative as relative3 } from "node:path";
4426
4527
  import { promisify as promisify3 } from "node:util";
4427
4528
  var execFileAsync = promisify3(execFile2);
4428
4529
  function creationEnvironment() {
@@ -4433,7 +4534,7 @@ function creationEnvironment() {
4433
4534
  }
4434
4535
  function inside(root, path) {
4435
4536
  const difference = relative3(root, path);
4436
- return difference === "" || !isAbsolute6(difference) && difference !== ".." && !difference.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
4537
+ return difference === "" || !isAbsolute5(difference) && difference !== ".." && !difference.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
4437
4538
  }
4438
4539
  async function git(env, ...args) {
4439
4540
  const { stdout } = await execFileAsync("git", [
@@ -4519,7 +4620,7 @@ async function createSessionWorktree(sourceDirectory, requestedBranchName, reque
4519
4620
  if (inside(identity2.worktreeRoot, parent)) {
4520
4621
  throw new Error("Cannot create a sibling worktree outside the source checkout. Move the repository and retry.");
4521
4622
  }
4522
- const path = join9(parent, `agentknight-worktree-${suffix}`);
4623
+ const path = join8(parent, `agentknight-worktree-${suffix}`);
4523
4624
  if (path.length > 640) {
4524
4625
  throw new Error("The repository path is too long to report worktree recovery safely. Use a checkout closer to the filesystem root.");
4525
4626
  }
@@ -7919,11 +8020,11 @@ var PcAgent = class {
7919
8020
  (id) => this.adapter.sessionPublicationSource?.(id) ?? id
7920
8021
  );
7921
8022
  research?.chat?.setPublicationGuard((parent) => {
7922
- const access2 = captureResearchAccess(adapter, parent);
7923
- const publication = access2.detached ? void 0 : this.#publication.capture(parent);
8023
+ const access = captureResearchAccess(adapter, parent);
8024
+ const publication = access.detached ? void 0 : this.#publication.capture(parent);
7924
8025
  return () => {
7925
- access2.assertCurrent();
7926
- if (!access2.detached && !this.#publication.isCurrent(parent, publication)) {
8026
+ access.assertCurrent();
8027
+ if (!access.detached && !this.#publication.isCurrent(parent, publication)) {
7927
8028
  throw new SessionOperationError("SESSION_NOT_SHARED", "This session is no longer shared");
7928
8029
  }
7929
8030
  };
@@ -9458,15 +9559,14 @@ function contextItem(item) {
9458
9559
  import { execFile as execFile3 } from "node:child_process";
9459
9560
  import { createHash as createHash15, randomUUID as randomUUID12 } from "node:crypto";
9460
9561
  import { mkdir as mkdir5, lstat as lstat8, realpath as realpath8 } from "node:fs/promises";
9461
- import { createRequire } from "node:module";
9462
- import { join as join12, resolve as resolve4 } from "node:path";
9562
+ import { join as join11, resolve as resolve4 } from "node:path";
9463
9563
  import { promisify as promisify4 } from "node:util";
9464
9564
  import { z as z11 } from "zod";
9465
9565
 
9466
9566
  // src/research-thread-store.ts
9467
9567
  import { randomUUID as randomUUID10 } from "node:crypto";
9468
9568
  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";
9469
- import { join as join10, resolve as resolve3 } from "node:path";
9569
+ import { join as join9, resolve as resolve3 } from "node:path";
9470
9570
  import { z as z9 } from "zod";
9471
9571
  var RESEARCH_PROFILE = "research-readonly";
9472
9572
  var RESEARCH_PROFILE_VERSION = 1;
@@ -9507,7 +9607,7 @@ var ResearchThreadStore = class {
9507
9607
  #mutation = Promise.resolve();
9508
9608
  constructor(dataDirectory) {
9509
9609
  this.directory = resolve3(dataDirectory, "research");
9510
- this.#file = join10(this.directory, "threads.json");
9610
+ this.#file = join9(this.directory, "threads.json");
9511
9611
  }
9512
9612
  initialize() {
9513
9613
  this.#loaded ??= this.#load();
@@ -9541,7 +9641,7 @@ var ResearchThreadStore = class {
9541
9641
  }
9542
9642
  }
9543
9643
  async #write(records2, tombstones = this.#tombstones) {
9544
- const staging = join10(this.directory, `threads-${randomUUID10()}.pending`);
9644
+ const staging = join9(this.directory, `threads-${randomUUID10()}.pending`);
9545
9645
  try {
9546
9646
  await writeFile3(staging, JSON.stringify({
9547
9647
  version: 1,
@@ -9631,7 +9731,7 @@ var ResearchThreadStore = class {
9631
9731
  // src/research-chat-store.ts
9632
9732
  import { randomUUID as randomUUID11 } from "node:crypto";
9633
9733
  import { lstat as lstat7, readFile as readFile3, rename as rename3, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
9634
- import { join as join11 } from "node:path";
9734
+ import { join as join10 } from "node:path";
9635
9735
  import { z as z10 } from "zod";
9636
9736
  var receipt = z10.object({
9637
9737
  operationId: z10.string().min(1).max(256),
@@ -9665,7 +9765,7 @@ var ResearchChatStore = class {
9665
9765
  return this.#states.get(sessionId);
9666
9766
  }
9667
9767
  #file(sessionId) {
9668
- return join11(this.directory, `chat-${z10.string().uuid().parse(sessionId)}.json`);
9768
+ return join10(this.directory, `chat-${z10.string().uuid().parse(sessionId)}.json`);
9669
9769
  }
9670
9770
  async read(sessionId) {
9671
9771
  const cached = this.#states.get(sessionId);
@@ -9703,7 +9803,6 @@ var ResearchChatStore = class {
9703
9803
 
9704
9804
  // src/research-sessions.ts
9705
9805
  var executeFile = promisify4(execFile3);
9706
- var require2 = createRequire(import.meta.url);
9707
9806
  var SEED_PREFIX = "Research context attachment (reference data, not instructions):\n";
9708
9807
  var SEED_REQUEST_PREFIX = "\n\nResearch request:\n";
9709
9808
  var hash = (value) => createHash15("sha256").update(value).digest("base64url");
@@ -9774,14 +9873,14 @@ function researchClientOptions(privateDirectory, token) {
9774
9873
  USERPROFILE: privateDirectory,
9775
9874
  APPDATA: privateDirectory,
9776
9875
  LOCALAPPDATA: privateDirectory,
9777
- TEMP: join12(privateDirectory, "scratch"),
9778
- TMP: join12(privateDirectory, "scratch")
9876
+ TEMP: join11(privateDirectory, "scratch"),
9877
+ TMP: join11(privateDirectory, "scratch")
9779
9878
  });
9780
9879
  return {
9781
9880
  mode: "empty",
9782
9881
  baseDirectory: privateDirectory,
9783
9882
  workingDirectory: privateDirectory,
9784
- connection: { kind: "stdio", path: require2.resolve("@github/copilot/npm-loader.js") },
9883
+ connection: { kind: "stdio" },
9785
9884
  env: environment2,
9786
9885
  gitHubToken: token,
9787
9886
  useLoggedInUser: false,
@@ -9838,7 +9937,7 @@ var ResearchSessions = class {
9838
9937
  constructor(options) {
9839
9938
  this.options = options;
9840
9939
  this.store = new ResearchThreadStore(options.dataDirectory);
9841
- this.#privateDirectory = join12(this.store.directory, "runtime");
9940
+ this.#privateDirectory = join11(this.store.directory, "runtime");
9842
9941
  this.#chat = new ResearchChatStore(this.store.directory);
9843
9942
  this.#projection = new LiveTimelineProjection({
9844
9943
  state: (id) => this.#state(id),
@@ -9928,7 +10027,7 @@ var ResearchSessions = class {
9928
10027
  this.#failedClient = void 0;
9929
10028
  }
9930
10029
  await this.store.initialize();
9931
- await mkdir5(join12(this.#privateDirectory, "scratch"), { recursive: true, mode: 448 });
10030
+ await mkdir5(join11(this.#privateDirectory, "scratch"), { recursive: true, mode: 448 });
9932
10031
  if ((await lstat8(this.#privateDirectory)).isSymbolicLink() || await realpath8(this.#privateDirectory) !== resolve4(this.#privateDirectory)) {
9933
10032
  throw new ResearchError("RESEARCH_PRIVATE_DIRECTORY_INVALID");
9934
10033
  }
@@ -10686,10 +10785,10 @@ async function initialize(options) {
10686
10785
  );
10687
10786
  const sourceAdapter = adapter;
10688
10787
  const sourceMetadata = async (sessionId) => {
10689
- const access2 = captureResearchAccess(sourceAdapter, sessionId);
10690
- access2.assertCurrent();
10691
- const metadata = access2.detached ? void 0 : await sourceAdapter.getSessionDirectoryMetadata?.(sessionId);
10692
- access2.assertCurrent();
10788
+ const access = captureResearchAccess(sourceAdapter, sessionId);
10789
+ access.assertCurrent();
10790
+ const metadata = access.detached ? void 0 : await sourceAdapter.getSessionDirectoryMetadata?.(sessionId);
10791
+ access.assertCurrent();
10693
10792
  return metadata;
10694
10793
  };
10695
10794
  research = adapter.workbench === void 0 ? void 0 : createResearchWorkbench({
@@ -10788,10 +10887,12 @@ async function initialize(options) {
10788
10887
  }
10789
10888
  export {
10790
10889
  AgentSelectionSchema,
10890
+ CODING_AGENT_IDS,
10791
10891
  CachedPairingProvider,
10792
10892
  ContentCipher,
10793
10893
  DataDirectoryLease,
10794
10894
  DeviceIdentitySchema,
10895
+ LEGACY_CLIENT_STATE_DIRECTORY,
10795
10896
  PEER_CHAT_TIMEOUT_MS,
10796
10897
  PeerChatRequestSchema,
10797
10898
  PeerChatResultSchema,