appostle-installer 0.0.9 → 0.0.11

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/appostle.js CHANGED
@@ -454,18 +454,18 @@ function resolveNodePtyPackageRoot() {
454
454
  return null;
455
455
  }
456
456
  }
457
- function ensureExecutableBit(path24) {
458
- if (!existsSync2(path24)) {
457
+ function ensureExecutableBit(path26) {
458
+ if (!existsSync2(path26)) {
459
459
  return;
460
460
  }
461
- const stat5 = statSync(path24);
461
+ const stat5 = statSync(path26);
462
462
  if (!stat5.isFile()) {
463
463
  return;
464
464
  }
465
465
  if ((stat5.mode & 73) === 73) {
466
466
  return;
467
467
  }
468
- chmodSync(path24, stat5.mode | 73);
468
+ chmodSync(path26, stat5.mode | 73);
469
469
  }
470
470
  function ensureNodePtySpawnHelperExecutableForCurrentPlatform(options = {}) {
471
471
  const platform2 = options.platform ?? process.platform;
@@ -547,11 +547,11 @@ function parseGitRevParsePath(stdout) {
547
547
  if (lines.length !== 1) {
548
548
  return null;
549
549
  }
550
- const path24 = lines[0]?.trim() ?? "";
551
- if (!path24 || path24.startsWith("--")) {
550
+ const path26 = lines[0]?.trim() ?? "";
551
+ if (!path26 || path26.startsWith("--")) {
552
552
  return null;
553
553
  }
554
- return path24;
554
+ return path26;
555
555
  }
556
556
  function resolveGitRevParsePath(cwd, stdout) {
557
557
  const parsed = parseGitRevParsePath(stdout);
@@ -1324,9 +1324,9 @@ async function deleteAppostleWorktree({
1324
1324
  }
1325
1325
  }
1326
1326
  }
1327
- async function pathExists(path24) {
1327
+ async function pathExists(path26) {
1328
1328
  try {
1329
- await stat(path24);
1329
+ await stat(path26);
1330
1330
  return true;
1331
1331
  } catch (error) {
1332
1332
  if (error.code === "ENOENT") {
@@ -1335,8 +1335,8 @@ async function pathExists(path24) {
1335
1335
  throw error;
1336
1336
  }
1337
1337
  }
1338
- async function removeDirectoryWithRetries(path24) {
1339
- if (!await pathExists(path24)) {
1338
+ async function removeDirectoryWithRetries(path26) {
1339
+ if (!await pathExists(path26)) {
1340
1340
  return;
1341
1341
  }
1342
1342
  const delaysMs = [0, 100, 300, 700, 1500];
@@ -1346,17 +1346,17 @@ async function removeDirectoryWithRetries(path24) {
1346
1346
  await new Promise((resolve12) => setTimeout(resolve12, delay));
1347
1347
  }
1348
1348
  try {
1349
- await rm(path24, { recursive: true, force: true });
1350
- if (!await pathExists(path24)) {
1349
+ await rm(path26, { recursive: true, force: true });
1350
+ if (!await pathExists(path26)) {
1351
1351
  return;
1352
1352
  }
1353
- lastError = new Error(`Directory still present after rm: ${path24}`);
1353
+ lastError = new Error(`Directory still present after rm: ${path26}`);
1354
1354
  } catch (error) {
1355
1355
  lastError = error;
1356
1356
  }
1357
1357
  }
1358
- if (await pathExists(path24)) {
1359
- throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path24}`);
1358
+ if (await pathExists(path26)) {
1359
+ throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path26}`);
1360
1360
  }
1361
1361
  }
1362
1362
  var createWorktree = async ({
@@ -3373,7 +3373,46 @@ var AgentTimelineItemPayloadSchema = z10.union([
3373
3373
  z10.object({
3374
3374
  type: z10.literal("user_message"),
3375
3375
  text: z10.string(),
3376
- messageId: z10.string().optional()
3376
+ messageId: z10.string().optional(),
3377
+ // Bytes-stripped attachment metadata so a reload re-renders the chip
3378
+ // without round-tripping image/file bytes through the timeline. Both
3379
+ // optional → backward-compatible with old daemons (silently strip) and
3380
+ // old clients (silently ignore). Inlined (not factored to a const)
3381
+ // because they're only used in this one place and forward-declaring
3382
+ // them would force a top-level reorder.
3383
+ images: z10.array(
3384
+ z10.object({
3385
+ id: z10.string(),
3386
+ mimeType: z10.string(),
3387
+ fileName: z10.string().optional(),
3388
+ byteSize: z10.number().int().nonnegative().optional(),
3389
+ // Daemon-side pointer for fetch-by-id. Optional → old daemons
3390
+ // (which only emit local-cache-friendly metadata) keep working;
3391
+ // old clients see the field and ignore it.
3392
+ daemonRef: z10.object({
3393
+ kind: z10.enum(["image", "file"]),
3394
+ attachmentId: z10.string(),
3395
+ relativePath: z10.string()
3396
+ }).optional()
3397
+ })
3398
+ ).optional(),
3399
+ files: z10.array(
3400
+ z10.object({
3401
+ id: z10.string(),
3402
+ fileName: z10.string(),
3403
+ mimeType: z10.string(),
3404
+ size: z10.number().int().nonnegative(),
3405
+ daemonRef: z10.object({
3406
+ kind: z10.enum(["image", "file"]),
3407
+ attachmentId: z10.string(),
3408
+ relativePath: z10.string()
3409
+ }).optional()
3410
+ })
3411
+ ).optional(),
3412
+ // Marker for daemon-synthesized text (e.g. "Sent 1 image" when the
3413
+ // user typed nothing). The chat bubble hides the text when this is
3414
+ // true; the LLM still sees it for cache_control compliance.
3415
+ textIsSynthesized: z10.boolean().optional()
3377
3416
  }),
3378
3417
  z10.object({
3379
3418
  type: z10.literal("assistant_message"),
@@ -3653,8 +3692,34 @@ var AgentAttachmentsSchema = z10.unknown().transform(normalizeAgentAttachments).
3653
3692
  var ImageAttachmentSchema = z10.object({
3654
3693
  data: z10.string(),
3655
3694
  // base64 encoded image
3656
- mimeType: z10.string()
3695
+ mimeType: z10.string(),
3657
3696
  // e.g., "image/jpeg", "image/png"
3697
+ // Optional client-side identity. When provided, the daemon echoes these
3698
+ // (without bytes) on the `user_message` timeline event so a reload can
3699
+ // re-render the chip. All optional → backward-compatible: old clients
3700
+ // omit them, old daemons strip them.
3701
+ id: z10.string().optional(),
3702
+ fileName: z10.string().optional(),
3703
+ byteSize: z10.number().int().nonnegative().optional()
3704
+ });
3705
+ var FileAttachmentSchema = z10.object({
3706
+ fileName: z10.string().min(1).max(255),
3707
+ mimeType: z10.string().min(1).max(255),
3708
+ size: z10.number().int().nonnegative(),
3709
+ data: z10.string()
3710
+ // base64-encoded bytes
3711
+ });
3712
+ var SessionUploadSchema = z10.object({
3713
+ id: z10.string(),
3714
+ fileName: z10.string(),
3715
+ mimeType: z10.string(),
3716
+ size: z10.number().int().nonnegative(),
3717
+ createdAt: z10.string(),
3718
+ // ISO-8601 UTC timestamp
3719
+ messageId: z10.string().nullable(),
3720
+ // Path relative to the agent's workspace root, e.g.
3721
+ // ".appostle/uploaded_files/<id>__report.sql". Always uses POSIX separators.
3722
+ relativePath: z10.string()
3658
3723
  });
3659
3724
  var SendAgentMessageSchema = z10.object({
3660
3725
  type: z10.literal("send_agent_message"),
@@ -3663,6 +3728,7 @@ var SendAgentMessageSchema = z10.object({
3663
3728
  messageId: z10.string().optional(),
3664
3729
  // Client-provided ID for deduplication
3665
3730
  images: z10.array(ImageAttachmentSchema).optional(),
3731
+ files: z10.array(FileAttachmentSchema).optional(),
3666
3732
  attachments: AgentAttachmentsSchema
3667
3733
  });
3668
3734
  var FetchAgentsRequestMessageSchema = z10.object({
@@ -3727,6 +3793,7 @@ var SendAgentMessageRequestSchema = z10.object({
3727
3793
  messageId: z10.string().optional(),
3728
3794
  // Client-provided ID for deduplication
3729
3795
  images: z10.array(ImageAttachmentSchema).optional(),
3796
+ files: z10.array(FileAttachmentSchema).optional(),
3730
3797
  attachments: AgentAttachmentsSchema
3731
3798
  });
3732
3799
  var WaitForFinishRequestSchema = z10.object({
@@ -3788,6 +3855,7 @@ var CreateAgentRequestMessageSchema = z10.object({
3788
3855
  clientMessageId: z10.string().optional(),
3789
3856
  outputSchema: z10.record(z10.unknown()).optional(),
3790
3857
  images: z10.array(ImageAttachmentSchema).optional(),
3858
+ files: z10.array(FileAttachmentSchema).optional(),
3791
3859
  attachments: AgentAttachmentsSchema,
3792
3860
  git: GitSetupOptionsSchema.optional(),
3793
3861
  labels: z10.record(z10.string()).default({}),
@@ -4584,6 +4652,131 @@ var LinkAccountResponseMessageSchema = z10.object({
4584
4652
  })
4585
4653
  ])
4586
4654
  });
4655
+ var ListSessionUploadsRequestSchema = z10.object({
4656
+ type: z10.literal("list_session_uploads_request"),
4657
+ requestId: z10.string(),
4658
+ /** Accepts full ID, unique prefix, or exact full title (server resolves). */
4659
+ agentId: z10.string()
4660
+ });
4661
+ var ListSessionUploadsResponseSchema = z10.object({
4662
+ type: z10.literal("list_session_uploads_response"),
4663
+ payload: z10.discriminatedUnion("ok", [
4664
+ z10.object({
4665
+ requestId: z10.string(),
4666
+ ok: z10.literal(true),
4667
+ uploads: z10.array(SessionUploadSchema)
4668
+ }),
4669
+ z10.object({
4670
+ requestId: z10.string(),
4671
+ ok: z10.literal(false),
4672
+ error: z10.string()
4673
+ })
4674
+ ])
4675
+ });
4676
+ var DeleteSessionUploadRequestSchema = z10.object({
4677
+ type: z10.literal("delete_session_upload_request"),
4678
+ requestId: z10.string(),
4679
+ /** Accepts full ID, unique prefix, or exact full title (server resolves). */
4680
+ agentId: z10.string(),
4681
+ uploadId: z10.string()
4682
+ });
4683
+ var DeleteSessionUploadResponseSchema = z10.object({
4684
+ type: z10.literal("delete_session_upload_response"),
4685
+ payload: z10.discriminatedUnion("ok", [
4686
+ z10.object({
4687
+ requestId: z10.string(),
4688
+ ok: z10.literal(true)
4689
+ }),
4690
+ z10.object({
4691
+ requestId: z10.string(),
4692
+ ok: z10.literal(false),
4693
+ error: z10.string()
4694
+ })
4695
+ ])
4696
+ });
4697
+ var SessionImageSchema = z10.object({
4698
+ id: z10.string(),
4699
+ fileName: z10.string(),
4700
+ mimeType: z10.string(),
4701
+ size: z10.number().int().nonnegative(),
4702
+ createdAt: z10.string(),
4703
+ // ISO-8601 UTC timestamp
4704
+ relativePath: z10.string()
4705
+ });
4706
+ var ListSessionImagesRequestSchema = z10.object({
4707
+ type: z10.literal("list_session_images_request"),
4708
+ requestId: z10.string(),
4709
+ /** Accepts full ID, unique prefix, or exact full title (server resolves). */
4710
+ agentId: z10.string()
4711
+ });
4712
+ var ListSessionImagesResponseSchema = z10.object({
4713
+ type: z10.literal("list_session_images_response"),
4714
+ payload: z10.discriminatedUnion("ok", [
4715
+ z10.object({
4716
+ requestId: z10.string(),
4717
+ ok: z10.literal(true),
4718
+ images: z10.array(SessionImageSchema)
4719
+ }),
4720
+ z10.object({
4721
+ requestId: z10.string(),
4722
+ ok: z10.literal(false),
4723
+ error: z10.string()
4724
+ })
4725
+ ])
4726
+ });
4727
+ var DeleteSessionImageRequestSchema = z10.object({
4728
+ type: z10.literal("delete_session_image_request"),
4729
+ requestId: z10.string(),
4730
+ /** Accepts full ID, unique prefix, or exact full title (server resolves). */
4731
+ agentId: z10.string(),
4732
+ /** Workspace-relative POSIX path; traversal-guarded by the daemon. */
4733
+ relativePath: z10.string()
4734
+ });
4735
+ var DeleteSessionImageResponseSchema = z10.object({
4736
+ type: z10.literal("delete_session_image_response"),
4737
+ payload: z10.discriminatedUnion("ok", [
4738
+ z10.object({
4739
+ requestId: z10.string(),
4740
+ ok: z10.literal(true)
4741
+ }),
4742
+ z10.object({
4743
+ requestId: z10.string(),
4744
+ ok: z10.literal(false),
4745
+ error: z10.string()
4746
+ })
4747
+ ])
4748
+ });
4749
+ var FetchAttachmentBytesRequestSchema = z10.object({
4750
+ type: z10.literal("fetch_attachment_bytes_request"),
4751
+ requestId: z10.string(),
4752
+ /** Accepts full ID, unique prefix, or exact full title (server resolves). */
4753
+ agentId: z10.string(),
4754
+ kind: z10.enum(["image", "file"]),
4755
+ attachmentId: z10.string(),
4756
+ /** Workspace-relative POSIX path to the byte source on the daemon's disk. */
4757
+ relativePath: z10.string()
4758
+ });
4759
+ var FetchAttachmentBytesResponseSchema = z10.object({
4760
+ type: z10.literal("fetch_attachment_bytes_response"),
4761
+ payload: z10.discriminatedUnion("ok", [
4762
+ z10.object({
4763
+ requestId: z10.string(),
4764
+ ok: z10.literal(true),
4765
+ attachmentId: z10.string(),
4766
+ kind: z10.enum(["image", "file"]),
4767
+ data: z10.string(),
4768
+ // base64
4769
+ mimeType: z10.string(),
4770
+ byteSize: z10.number().int().nonnegative(),
4771
+ fileName: z10.string().optional()
4772
+ }),
4773
+ z10.object({
4774
+ requestId: z10.string(),
4775
+ ok: z10.literal(false),
4776
+ error: z10.string()
4777
+ })
4778
+ ])
4779
+ });
4587
4780
  var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
4588
4781
  VoiceAudioChunkMessageSchema,
4589
4782
  AbortRequestMessageSchema,
@@ -4725,7 +4918,12 @@ var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
4725
4918
  QuestListRequestSchema,
4726
4919
  QuestInspectRequestSchema,
4727
4920
  QuestStopRequestSchema,
4728
- RolesListRequestSchema
4921
+ RolesListRequestSchema,
4922
+ ListSessionUploadsRequestSchema,
4923
+ DeleteSessionUploadRequestSchema,
4924
+ ListSessionImagesRequestSchema,
4925
+ DeleteSessionImageRequestSchema,
4926
+ FetchAttachmentBytesRequestSchema
4729
4927
  ]);
4730
4928
  var ActivityLogPayloadSchema = z10.object({
4731
4929
  id: z10.string(),
@@ -6269,7 +6467,12 @@ var SessionOutboundMessageSchema = z10.discriminatedUnion("type", [
6269
6467
  BrandAssetCopyResponseSchema,
6270
6468
  BrandAssetUploadResponseSchema,
6271
6469
  GetVapidPublicKeyResponseSchema,
6272
- LinkAccountResponseMessageSchema
6470
+ LinkAccountResponseMessageSchema,
6471
+ ListSessionUploadsResponseSchema,
6472
+ DeleteSessionUploadResponseSchema,
6473
+ ListSessionImagesResponseSchema,
6474
+ DeleteSessionImageResponseSchema,
6475
+ FetchAttachmentBytesResponseSchema
6273
6476
  ]);
6274
6477
  var WSPingMessageSchema = z10.object({
6275
6478
  type: z10.literal("ping")
@@ -8671,14 +8874,14 @@ var DictationStreamManager = class {
8671
8874
  PCM_CHANNELS,
8672
8875
  PCM_BITS_PER_SAMPLE
8673
8876
  );
8674
- const path24 = await maybePersistDictationDebugAudio(
8877
+ const path26 = await maybePersistDictationDebugAudio(
8675
8878
  wavBuffer,
8676
8879
  { sessionId: state.sessionId, dictationId: state.dictationId, format: "audio/wav" },
8677
8880
  this.logger,
8678
8881
  state.debugChunkWriter?.folder
8679
8882
  );
8680
- state.debugRecordingPath = path24;
8681
- return path24;
8883
+ state.debugRecordingPath = path26;
8884
+ return path26;
8682
8885
  }
8683
8886
  failDictationStream(dictationId, error, retryable) {
8684
8887
  this.emit({
@@ -9707,6 +9910,9 @@ async function sendPromptToAgent(params) {
9707
9910
  messageId,
9708
9911
  runOptions,
9709
9912
  sessionMode,
9913
+ userMessageImages,
9914
+ userMessageFiles,
9915
+ userMessageTextIsSynthesized,
9710
9916
  logger
9711
9917
  } = params;
9712
9918
  await unarchiveAgentState(agentStorage, agentManager, agentId);
@@ -9721,7 +9927,10 @@ async function sendPromptToAgent(params) {
9721
9927
  try {
9722
9928
  agentManager.recordUserMessage(agentId, userMessageText, {
9723
9929
  messageId,
9724
- emitState: false
9930
+ emitState: false,
9931
+ ...userMessageImages && userMessageImages.length > 0 ? { images: userMessageImages } : {},
9932
+ ...userMessageFiles && userMessageFiles.length > 0 ? { files: userMessageFiles } : {},
9933
+ ...userMessageTextIsSynthesized ? { textIsSynthesized: true } : {}
9725
9934
  });
9726
9935
  } catch (error) {
9727
9936
  logger.error({ err: error, agentId }, "Failed to record user message");
@@ -9732,6 +9941,544 @@ async function sendPromptToAgent(params) {
9732
9941
  });
9733
9942
  }
9734
9943
 
9944
+ // ../server/src/server/agent/prompt-attachments.ts
9945
+ function renderPromptAttachmentAsText(attachment) {
9946
+ switch (attachment.type) {
9947
+ case "github_pr": {
9948
+ const lines = [`GitHub PR #${attachment.number}: ${attachment.title}`, attachment.url];
9949
+ if (attachment.baseRefName) {
9950
+ lines.push(`Base: ${attachment.baseRefName}`);
9951
+ }
9952
+ if (attachment.headRefName) {
9953
+ lines.push(`Head: ${attachment.headRefName}`);
9954
+ }
9955
+ if (attachment.body) {
9956
+ lines.push("", attachment.body);
9957
+ }
9958
+ return lines.join("\n");
9959
+ }
9960
+ case "github_issue": {
9961
+ const lines = [`GitHub Issue #${attachment.number}: ${attachment.title}`, attachment.url];
9962
+ if (attachment.body) {
9963
+ lines.push("", attachment.body);
9964
+ }
9965
+ return lines.join("\n");
9966
+ }
9967
+ case "gitlab_mr": {
9968
+ const lines = [`GitLab MR !${attachment.iid}: ${attachment.title}`, attachment.url];
9969
+ if (attachment.targetBranch) {
9970
+ lines.push(`Target: ${attachment.targetBranch}`);
9971
+ }
9972
+ if (attachment.sourceBranch) {
9973
+ lines.push(`Source: ${attachment.sourceBranch}`);
9974
+ }
9975
+ if (attachment.body) {
9976
+ lines.push("", attachment.body);
9977
+ }
9978
+ return lines.join("\n");
9979
+ }
9980
+ case "gitlab_issue": {
9981
+ const lines = [`GitLab Issue #${attachment.iid}: ${attachment.title}`, attachment.url];
9982
+ if (attachment.body) {
9983
+ lines.push("", attachment.body);
9984
+ }
9985
+ return lines.join("\n");
9986
+ }
9987
+ }
9988
+ }
9989
+ function renderFileAttachmentFooter(uploads) {
9990
+ if (uploads.length === 0) {
9991
+ return "";
9992
+ }
9993
+ const lines = [
9994
+ "",
9995
+ "The user attached the following file(s) to this message. Read them with your file-read tool when relevant; the paths are relative to the workspace root:"
9996
+ ];
9997
+ for (const upload of uploads) {
9998
+ const size = formatBytes(upload.size);
9999
+ lines.push(`- ${upload.relativePath} (${upload.mimeType}, ${size})`);
10000
+ }
10001
+ return lines.join("\n");
10002
+ }
10003
+ function formatBytes(bytes) {
10004
+ if (bytes < 1024) return `${bytes} B`;
10005
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
10006
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
10007
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
10008
+ }
10009
+ function findGitHubPrAttachment(attachments) {
10010
+ if (!attachments) {
10011
+ return null;
10012
+ }
10013
+ return attachments.find(
10014
+ (attachment) => attachment.type === "github_pr"
10015
+ ) ?? null;
10016
+ }
10017
+
10018
+ // ../server/src/server/uploads/session-upload-store.ts
10019
+ import { promises as fs } from "node:fs";
10020
+ import * as path5 from "node:path";
10021
+ var MAX_UPLOAD_BYTES_PER_MESSAGE = 50 * 1024 * 1024;
10022
+ var MAX_UPLOAD_BYTES_PER_FILE = 50 * 1024 * 1024;
10023
+ var UPLOADS_DIR_REL = path5.posix.join(".appostle", "uploaded_files");
10024
+ var MANIFEST_BASENAME = "uploads.json";
10025
+ function originalUploadFileName(relativePath) {
10026
+ const safe = path5.posix.normalize(relativePath);
10027
+ if (!safe.startsWith(`${UPLOADS_DIR_REL}/`)) return null;
10028
+ const baseName = path5.posix.basename(safe);
10029
+ const sep4 = baseName.indexOf("__");
10030
+ if (sep4 < 0) return null;
10031
+ return baseName.slice(sep4 + 2);
10032
+ }
10033
+ var GITIGNORE_MARKER_LINE = "# Added by Appostle: uploaded chat attachments";
10034
+ var GITIGNORE_BLOCK = `${GITIGNORE_MARKER_LINE}
10035
+ .appostle/
10036
+ `;
10037
+ function emptyManifest() {
10038
+ return { version: 1, uploads: [] };
10039
+ }
10040
+ function sanitizeFileName(input) {
10041
+ const stripped = input.replace(/[\\/\x00-\x1F\x7F]/g, "_").trim();
10042
+ const noTraversal = stripped.replace(/\.{2,}/g, ".");
10043
+ const trimmed = noTraversal.replace(/^[.\s]+|[\s.]+$/g, "");
10044
+ if (trimmed.length === 0) {
10045
+ return "upload.bin";
10046
+ }
10047
+ return trimmed.slice(0, 200);
10048
+ }
10049
+ function manifestDirFor(cwd) {
10050
+ return path5.join(cwd, UPLOADS_DIR_REL);
10051
+ }
10052
+ function manifestPathFor(cwd) {
10053
+ return path5.join(manifestDirFor(cwd), MANIFEST_BASENAME);
10054
+ }
10055
+ function relativeUploadPath(id, sanitizedName) {
10056
+ return path5.posix.join(UPLOADS_DIR_REL, `${id}__${sanitizedName}`);
10057
+ }
10058
+ var UploadSizeError = class extends Error {
10059
+ constructor(totalBytes, cap) {
10060
+ super(`Upload payload exceeds ${cap} bytes (got ${totalBytes})`);
10061
+ this.totalBytes = totalBytes;
10062
+ this.cap = cap;
10063
+ this.name = "UploadSizeError";
10064
+ }
10065
+ };
10066
+ var SessionUploadStore = class {
10067
+ constructor(opts = {}) {
10068
+ this.logger = opts.logger;
10069
+ }
10070
+ /**
10071
+ * Decode and write each file. Throws `UploadSizeError` if either a single
10072
+ * file exceeds the per-file cap or the message total exceeds the per-message
10073
+ * cap. On success, returns the manifest entries (in the same order as input).
10074
+ */
10075
+ async persistFiles(params) {
10076
+ const { cwd, agentId, files, messageId } = params;
10077
+ if (files.length === 0) {
10078
+ return [];
10079
+ }
10080
+ const decoded = [];
10081
+ let totalBytes = 0;
10082
+ for (const file of files) {
10083
+ let bytes;
10084
+ try {
10085
+ bytes = Buffer.from(file.data, "base64");
10086
+ } catch (error) {
10087
+ throw new Error(
10088
+ `Failed to decode base64 payload for "${file.fileName}": ${error instanceof Error ? error.message : String(error)}`
10089
+ );
10090
+ }
10091
+ if (bytes.length > MAX_UPLOAD_BYTES_PER_FILE) {
10092
+ throw new UploadSizeError(bytes.length, MAX_UPLOAD_BYTES_PER_FILE);
10093
+ }
10094
+ totalBytes += bytes.length;
10095
+ if (totalBytes > MAX_UPLOAD_BYTES_PER_MESSAGE) {
10096
+ throw new UploadSizeError(totalBytes, MAX_UPLOAD_BYTES_PER_MESSAGE);
10097
+ }
10098
+ decoded.push({ file, bytes });
10099
+ }
10100
+ await fs.mkdir(manifestDirFor(cwd), { recursive: true });
10101
+ await this.ensureGitignoreEntry(cwd);
10102
+ const manifest = await this.readManifest(cwd);
10103
+ const created = [];
10104
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
10105
+ for (const { file, bytes } of decoded) {
10106
+ const id = crypto.randomUUID();
10107
+ const sanitizedName = sanitizeFileName(file.fileName);
10108
+ const relPath = relativeUploadPath(id, sanitizedName);
10109
+ const absPath = path5.join(cwd, relPath);
10110
+ await fs.writeFile(absPath, bytes);
10111
+ const entry = {
10112
+ id,
10113
+ fileName: sanitizedName,
10114
+ mimeType: file.mimeType,
10115
+ size: bytes.length,
10116
+ createdAt,
10117
+ messageId,
10118
+ relativePath: relPath
10119
+ };
10120
+ manifest.uploads.push({ ...entry, agentId });
10121
+ created.push(entry);
10122
+ }
10123
+ await this.writeManifest(cwd, manifest);
10124
+ this.logger?.info(
10125
+ { agentId, count: created.length, totalBytes },
10126
+ "Persisted chat file uploads"
10127
+ );
10128
+ return created;
10129
+ }
10130
+ /**
10131
+ * Read bytes back from disk by workspace-relative path. Symmetric with
10132
+ * `SessionImageStore.readImageBytes` so the fetch-bytes RPC handler can
10133
+ * route uniformly. Traversal-guarded: refuses anything that escapes
10134
+ * `.appostle/uploaded_files/` or contains `..`.
10135
+ */
10136
+ async readUploadBytes(params) {
10137
+ const safe = path5.posix.normalize(params.relativePath);
10138
+ if (!safe.startsWith(`${UPLOADS_DIR_REL}/`) || safe.includes("..")) {
10139
+ throw new Error(`Refused to read upload outside ${UPLOADS_DIR_REL}: ${params.relativePath}`);
10140
+ }
10141
+ const abs = path5.join(params.cwd, safe);
10142
+ const data = await fs.readFile(abs);
10143
+ const baseName = path5.posix.basename(safe);
10144
+ const sep4 = baseName.indexOf("__");
10145
+ const fileName = sep4 >= 0 ? baseName.slice(sep4 + 2) : baseName;
10146
+ let mimeType = "application/octet-stream";
10147
+ try {
10148
+ const manifest = await this.readManifest(params.cwd);
10149
+ const match = manifest.uploads.find((entry) => entry.relativePath === safe);
10150
+ if (match) {
10151
+ mimeType = match.mimeType;
10152
+ }
10153
+ } catch {
10154
+ }
10155
+ return { data, mimeType, byteSize: data.length, fileName };
10156
+ }
10157
+ /**
10158
+ * List every file stored for this workspace. Workspace-scoped, NOT
10159
+ * agent-scoped — uploads live under `<workspace>/.appostle/uploaded_files/`
10160
+ * and persist across agent lifecycles, so filtering by `agentId` would hide
10161
+ * files attached by older or sibling agents in the same workspace. (Mirrors
10162
+ * the image store's directory-scan behaviour.) The `agentId` parameter is
10163
+ * preserved on the API for backward-compat but ignored.
10164
+ *
10165
+ * Newest first.
10166
+ */
10167
+ async listUploads(params) {
10168
+ const manifest = await this.readManifest(params.cwd);
10169
+ return manifest.uploads.map(({ agentId: _agentId, ...rest }) => rest).sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
10170
+ }
10171
+ /**
10172
+ * Delete a file by upload id. Workspace-scoped to match `listUploads` — any
10173
+ * agent in the workspace can delete any file. The `agentId` parameter is
10174
+ * preserved on the API for backward-compat but ignored.
10175
+ */
10176
+ async deleteUpload(params) {
10177
+ const manifest = await this.readManifest(params.cwd);
10178
+ const idx = manifest.uploads.findIndex((entry) => entry.id === params.uploadId);
10179
+ if (idx === -1) {
10180
+ return { deleted: false };
10181
+ }
10182
+ const [removed] = manifest.uploads.splice(idx, 1);
10183
+ if (removed) {
10184
+ const absPath = path5.join(params.cwd, removed.relativePath);
10185
+ try {
10186
+ await fs.unlink(absPath);
10187
+ } catch (error) {
10188
+ this.logger?.warn(
10189
+ { uploadId: params.uploadId, error: error.message },
10190
+ "deleteUpload: file already missing, removing manifest entry"
10191
+ );
10192
+ }
10193
+ }
10194
+ await this.writeManifest(params.cwd, manifest);
10195
+ return { deleted: true };
10196
+ }
10197
+ async readManifest(cwd) {
10198
+ const file = manifestPathFor(cwd);
10199
+ let raw;
10200
+ try {
10201
+ raw = await fs.readFile(file, "utf8");
10202
+ } catch (error) {
10203
+ if (error.code === "ENOENT") {
10204
+ return emptyManifest();
10205
+ }
10206
+ throw error;
10207
+ }
10208
+ let parsed;
10209
+ try {
10210
+ parsed = JSON.parse(raw);
10211
+ } catch {
10212
+ this.logger?.warn({ file }, "Manifest is not valid JSON; treating as empty");
10213
+ return emptyManifest();
10214
+ }
10215
+ if (typeof parsed === "object" && parsed !== null && "version" in parsed && parsed.version === 1 && Array.isArray(parsed.uploads)) {
10216
+ return parsed;
10217
+ }
10218
+ return emptyManifest();
10219
+ }
10220
+ async writeManifest(cwd, manifest) {
10221
+ const file = manifestPathFor(cwd);
10222
+ const tmp = `${file}.tmp`;
10223
+ await fs.writeFile(tmp, JSON.stringify(manifest, null, 2), "utf8");
10224
+ await fs.rename(tmp, file);
10225
+ }
10226
+ /** Append a `.appostle/` ignore line to the workspace `.gitignore`, idempotently. */
10227
+ async ensureGitignoreEntry(cwd) {
10228
+ const file = path5.join(cwd, ".gitignore");
10229
+ let existing = "";
10230
+ try {
10231
+ existing = await fs.readFile(file, "utf8");
10232
+ } catch (error) {
10233
+ if (error.code !== "ENOENT") {
10234
+ throw error;
10235
+ }
10236
+ }
10237
+ if (existing.includes(GITIGNORE_MARKER_LINE)) {
10238
+ return;
10239
+ }
10240
+ const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
10241
+ await fs.writeFile(file, `${existing}${separator}${GITIGNORE_BLOCK}`, "utf8");
10242
+ }
10243
+ };
10244
+
10245
+ // ../server/src/server/uploads/session-image-store.ts
10246
+ import { promises as fs2 } from "node:fs";
10247
+ import { createHash as createHash2, randomUUID } from "node:crypto";
10248
+ import * as path6 from "node:path";
10249
+ var IMAGES_DIR_REL = path6.posix.join(".appostle", "uploaded_images");
10250
+ var GITIGNORE_MARKER_LINE2 = "# Added by Appostle: uploaded chat attachments";
10251
+ var GITIGNORE_BLOCK2 = `${GITIGNORE_MARKER_LINE2}
10252
+ .appostle/
10253
+ `;
10254
+ var EXTENSION_BY_MIME = {
10255
+ "image/png": "png",
10256
+ "image/jpeg": "jpg",
10257
+ "image/jpg": "jpg",
10258
+ "image/gif": "gif",
10259
+ "image/webp": "webp",
10260
+ "image/avif": "avif",
10261
+ "image/heic": "heic",
10262
+ "image/heif": "heif",
10263
+ "image/tiff": "tiff",
10264
+ "image/bmp": "bmp",
10265
+ "image/svg+xml": "svg"
10266
+ };
10267
+ function extensionForImage(input) {
10268
+ if (input.fileName) {
10269
+ const dot = input.fileName.lastIndexOf(".");
10270
+ if (dot >= 0 && dot < input.fileName.length - 1) {
10271
+ const ext = input.fileName.slice(dot + 1).toLowerCase();
10272
+ if (/^[a-z0-9]{1,8}$/.test(ext)) {
10273
+ return ext;
10274
+ }
10275
+ }
10276
+ }
10277
+ return EXTENSION_BY_MIME[input.mimeType] ?? "img";
10278
+ }
10279
+ function mimeTypeForExtension(ext) {
10280
+ switch (ext) {
10281
+ case "png":
10282
+ return "image/png";
10283
+ case "jpg":
10284
+ case "jpeg":
10285
+ return "image/jpeg";
10286
+ case "gif":
10287
+ return "image/gif";
10288
+ case "webp":
10289
+ return "image/webp";
10290
+ case "avif":
10291
+ return "image/avif";
10292
+ case "heic":
10293
+ return "image/heic";
10294
+ case "heif":
10295
+ return "image/heif";
10296
+ case "tiff":
10297
+ return "image/tiff";
10298
+ case "bmp":
10299
+ return "image/bmp";
10300
+ case "svg":
10301
+ return "image/svg+xml";
10302
+ default:
10303
+ return "application/octet-stream";
10304
+ }
10305
+ }
10306
+ var SessionImageStore = class {
10307
+ constructor(opts = {}) {
10308
+ this.logger = opts.logger;
10309
+ }
10310
+ /**
10311
+ * Decode and write each image. Throws `UploadSizeError` on cap violations.
10312
+ * On success returns one entry per input image (in order).
10313
+ */
10314
+ async persistImages(params) {
10315
+ const { cwd, agentId, messageId, images } = params;
10316
+ if (images.length === 0) {
10317
+ return [];
10318
+ }
10319
+ const decoded = [];
10320
+ let totalBytes = 0;
10321
+ for (const image of images) {
10322
+ let bytes;
10323
+ try {
10324
+ bytes = Buffer.from(image.data, "base64");
10325
+ } catch (error) {
10326
+ throw new Error(
10327
+ `Failed to decode base64 image: ${error instanceof Error ? error.message : String(error)}`
10328
+ );
10329
+ }
10330
+ if (bytes.length > MAX_UPLOAD_BYTES_PER_FILE) {
10331
+ throw new UploadSizeError(bytes.length, MAX_UPLOAD_BYTES_PER_FILE);
10332
+ }
10333
+ totalBytes += bytes.length;
10334
+ if (totalBytes > MAX_UPLOAD_BYTES_PER_MESSAGE) {
10335
+ throw new UploadSizeError(totalBytes, MAX_UPLOAD_BYTES_PER_MESSAGE);
10336
+ }
10337
+ const hash = createHash2("sha256").update(bytes).digest("hex").slice(0, 16);
10338
+ decoded.push({ image, bytes, hash });
10339
+ }
10340
+ const dir = path6.join(cwd, IMAGES_DIR_REL);
10341
+ await fs2.mkdir(dir, { recursive: true });
10342
+ await this.ensureGitignoreEntry(cwd);
10343
+ const created = [];
10344
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
10345
+ const timestampMs = Date.now();
10346
+ for (const entry of decoded) {
10347
+ const ext = extensionForImage({
10348
+ mimeType: entry.image.mimeType,
10349
+ fileName: entry.image.fileName
10350
+ });
10351
+ const baseName = `${timestampMs}-${entry.hash}.${ext}`;
10352
+ const relPath = path6.posix.join(IMAGES_DIR_REL, baseName);
10353
+ const absPath = path6.join(cwd, relPath);
10354
+ await fs2.writeFile(absPath, entry.bytes);
10355
+ created.push({
10356
+ id: entry.image.id ?? randomUUID(),
10357
+ fileName: entry.image.fileName ?? null,
10358
+ mimeType: entry.image.mimeType,
10359
+ size: entry.bytes.length,
10360
+ createdAt,
10361
+ messageId,
10362
+ relativePath: relPath,
10363
+ hash: entry.hash
10364
+ });
10365
+ }
10366
+ this.logger?.info(
10367
+ { agentId, count: created.length, totalBytes },
10368
+ "Persisted chat image uploads"
10369
+ );
10370
+ return created;
10371
+ }
10372
+ /**
10373
+ * Read bytes back from disk by workspace-relative path. Used by the (future
10374
+ * Phase 4) fetch-bytes RPC and by Phase 3's prompt-build path.
10375
+ *
10376
+ * Defense in depth: refuses anything outside `.appostle/uploaded_images/`
10377
+ * or anything containing `..`. The caller is the trust boundary, but
10378
+ * costing nothing to double-check at the I/O edge.
10379
+ */
10380
+ async readImageBytes(params) {
10381
+ const safe = path6.posix.normalize(params.relativePath);
10382
+ if (!safe.startsWith(`${IMAGES_DIR_REL}/`) || safe.includes("..")) {
10383
+ throw new Error(`Refused to read image outside ${IMAGES_DIR_REL}: ${params.relativePath}`);
10384
+ }
10385
+ const abs = path6.join(params.cwd, safe);
10386
+ const data = await fs2.readFile(abs);
10387
+ const ext = path6.extname(safe).slice(1).toLowerCase();
10388
+ return { data, mimeType: mimeTypeForExtension(ext), byteSize: data.length };
10389
+ }
10390
+ /**
10391
+ * List every image stored for this workspace. There is no manifest — the
10392
+ * directory listing IS the source of truth. Filenames carry the timestamp
10393
+ * (`<unix_ms>-<hash>.<ext>`); we parse that for `createdAt` and fall back to
10394
+ * file mtime if the prefix is missing.
10395
+ *
10396
+ * Returns newest-first to match the upload-store ordering expected by the UI.
10397
+ */
10398
+ async listImages(params) {
10399
+ const dir = path6.join(params.cwd, IMAGES_DIR_REL);
10400
+ let names;
10401
+ try {
10402
+ names = await fs2.readdir(dir);
10403
+ } catch (error) {
10404
+ if (error.code === "ENOENT") {
10405
+ return [];
10406
+ }
10407
+ throw error;
10408
+ }
10409
+ const entries = [];
10410
+ for (const name of names) {
10411
+ if (name.startsWith(".")) continue;
10412
+ const abs = path6.join(dir, name);
10413
+ let stat5;
10414
+ try {
10415
+ stat5 = await fs2.stat(abs);
10416
+ } catch {
10417
+ continue;
10418
+ }
10419
+ if (!stat5.isFile()) continue;
10420
+ const ext = path6.extname(name).slice(1).toLowerCase();
10421
+ const mimeType = mimeTypeForExtension(ext);
10422
+ let createdAt;
10423
+ const dash = name.indexOf("-");
10424
+ const msCandidate = dash > 0 ? Number(name.slice(0, dash)) : Number.NaN;
10425
+ if (Number.isFinite(msCandidate) && msCandidate > 0) {
10426
+ createdAt = new Date(msCandidate).toISOString();
10427
+ } else {
10428
+ createdAt = stat5.mtime.toISOString();
10429
+ }
10430
+ const relPath = path6.posix.join(IMAGES_DIR_REL, name);
10431
+ entries.push({
10432
+ id: relPath,
10433
+ fileName: name,
10434
+ mimeType,
10435
+ size: stat5.size,
10436
+ createdAt,
10437
+ relativePath: relPath
10438
+ });
10439
+ }
10440
+ entries.sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
10441
+ return entries;
10442
+ }
10443
+ /**
10444
+ * Delete a single stored image by workspace-relative path. Same traversal
10445
+ * guard as `readImageBytes`. Idempotent: returns `{ deleted: false }` if
10446
+ * the file is already gone (so the caller can refresh its listing).
10447
+ */
10448
+ async deleteImage(params) {
10449
+ const safe = path6.posix.normalize(params.relativePath);
10450
+ if (!safe.startsWith(`${IMAGES_DIR_REL}/`) || safe.includes("..")) {
10451
+ throw new Error(`Refused to delete image outside ${IMAGES_DIR_REL}: ${params.relativePath}`);
10452
+ }
10453
+ const abs = path6.join(params.cwd, safe);
10454
+ try {
10455
+ await fs2.unlink(abs);
10456
+ return { deleted: true };
10457
+ } catch (error) {
10458
+ if (error.code === "ENOENT") {
10459
+ return { deleted: false };
10460
+ }
10461
+ throw error;
10462
+ }
10463
+ }
10464
+ async ensureGitignoreEntry(cwd) {
10465
+ const file = path6.join(cwd, ".gitignore");
10466
+ let existing = "";
10467
+ try {
10468
+ existing = await fs2.readFile(file, "utf8");
10469
+ } catch (error) {
10470
+ if (error.code !== "ENOENT") {
10471
+ throw error;
10472
+ }
10473
+ }
10474
+ if (existing.includes(GITIGNORE_MARKER_LINE2)) {
10475
+ return;
10476
+ }
10477
+ const sep4 = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
10478
+ await fs2.writeFile(file, `${existing}${sep4}${GITIGNORE_BLOCK2}`, "utf8");
10479
+ }
10480
+ };
10481
+
9735
10482
  // ../server/src/server/session.ts
9736
10483
  import { experimental_createMCPClient } from "ai";
9737
10484
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -9924,14 +10671,14 @@ function parseDiff(diffText) {
9924
10671
  const firstLine = lines[0];
9925
10672
  const isNew = section.includes("new file mode") || section.includes("--- /dev/null");
9926
10673
  const isDeleted = section.includes("deleted file mode") || section.includes("+++ /dev/null");
9927
- let path24 = "unknown";
10674
+ let path26 = "unknown";
9928
10675
  const pathMatch = firstLine.match(/a\/(.*?) b\//);
9929
10676
  if (pathMatch) {
9930
- path24 = pathMatch[1];
10677
+ path26 = pathMatch[1];
9931
10678
  } else {
9932
10679
  const newFileMatch = firstLine.match(/b\/(.+)$/);
9933
10680
  if (newFileMatch) {
9934
- path24 = newFileMatch[1];
10681
+ path26 = newFileMatch[1];
9935
10682
  }
9936
10683
  }
9937
10684
  const hunks = [];
@@ -9975,7 +10722,7 @@ function parseDiff(diffText) {
9975
10722
  if (currentHunk) {
9976
10723
  hunks.push(currentHunk);
9977
10724
  }
9978
- files.push({ path: path24, isNew, isDeleted, additions, deletions, hunks });
10725
+ files.push({ path: path26, isNew, isDeleted, additions, deletions, hunks });
9979
10726
  }
9980
10727
  return files;
9981
10728
  }
@@ -10031,9 +10778,9 @@ function buildTokenLookup(lineMap, highlighted) {
10031
10778
  }
10032
10779
  return lookup2;
10033
10780
  }
10034
- function buildFullFileTokenLookup(fileContent, path24) {
10781
+ function buildFullFileTokenLookup(fileContent, path26) {
10035
10782
  const lookup2 = /* @__PURE__ */ new Map();
10036
- const highlighted = highlightCode(fileContent, path24);
10783
+ const highlighted = highlightCode(fileContent, path26);
10037
10784
  for (let i = 0; i < highlighted.length; i++) {
10038
10785
  lookup2.set(i + 1, highlighted[i]);
10039
10786
  }
@@ -11734,11 +12481,11 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
11734
12481
  }
11735
12482
  continue;
11736
12483
  }
11737
- const path24 = tabParts[1];
11738
- if (!path24) continue;
12484
+ const path26 = tabParts[1];
12485
+ if (!path26) continue;
11739
12486
  const code = rawStatus[0];
11740
12487
  changes.push({
11741
- path: path24,
12488
+ path: path26,
11742
12489
  status: rawStatus,
11743
12490
  isNew: code === "A",
11744
12491
  isDeleted: code === "D"
@@ -11773,9 +12520,9 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
11773
12520
  }
11774
12521
  return Array.from(byPath.values());
11775
12522
  }
11776
- async function readGitFileContentAtRef(cwd, ref, path24) {
12523
+ async function readGitFileContentAtRef(cwd, ref, path26) {
11777
12524
  try {
11778
- const { stdout } = await runGitCommand(["show", `${ref}:${path24}`], {
12525
+ const { stdout } = await runGitCommand(["show", `${ref}:${path26}`], {
11779
12526
  cwd,
11780
12527
  env: READ_ONLY_GIT_ENV2
11781
12528
  });
@@ -11836,21 +12583,21 @@ async function getTrackedNumstatByPath(cwd, ref, ignoreWhitespace = false) {
11836
12583
  const additionsField = parts[0] ?? "";
11837
12584
  const deletionsField = parts[1] ?? "";
11838
12585
  const rawPath = parts.slice(2).join(" ");
11839
- const path24 = normalizeNumstatPath(rawPath);
11840
- if (!path24) {
12586
+ const path26 = normalizeNumstatPath(rawPath);
12587
+ if (!path26) {
11841
12588
  continue;
11842
12589
  }
11843
12590
  if (additionsField === "-" || deletionsField === "-") {
11844
- stats.set(path24, { additions: 0, deletions: 0, isBinary: true });
12591
+ stats.set(path26, { additions: 0, deletions: 0, isBinary: true });
11845
12592
  continue;
11846
12593
  }
11847
12594
  const additions = Number.parseInt(additionsField, 10);
11848
12595
  const deletions = Number.parseInt(deletionsField, 10);
11849
12596
  if (Number.isNaN(additions) || Number.isNaN(deletions)) {
11850
- stats.set(path24, null);
12597
+ stats.set(path26, null);
11851
12598
  continue;
11852
12599
  }
11853
- stats.set(path24, { additions, deletions, isBinary: false });
12600
+ stats.set(path26, { additions, deletions, isBinary: false });
11854
12601
  }
11855
12602
  return stats;
11856
12603
  }
@@ -12435,10 +13182,10 @@ async function listUncommittedFiles(cwd) {
12435
13182
  if (dest) results.push({ path: dest, changeType: code });
12436
13183
  continue;
12437
13184
  }
12438
- const path24 = parts[1];
12439
- if (!path24) continue;
13185
+ const path26 = parts[1];
13186
+ if (!path26) continue;
12440
13187
  if (code === "A" || code === "M" || code === "D") {
12441
- results.push({ path: path24, changeType: code });
13188
+ results.push({ path: path26, changeType: code });
12442
13189
  }
12443
13190
  }
12444
13191
  } catch {
@@ -14136,11 +14883,11 @@ async function spawnWorktreeScripts(options) {
14136
14883
  }
14137
14884
 
14138
14885
  // ../server/src/server/agent/providers/claude-agent.ts
14139
- import { randomUUID } from "node:crypto";
14140
- import fs from "node:fs";
14886
+ import { randomUUID as randomUUID2 } from "node:crypto";
14887
+ import fs3 from "node:fs";
14141
14888
  import { promises } from "node:fs";
14142
14889
  import os2 from "node:os";
14143
- import path6 from "node:path";
14890
+ import path8 from "node:path";
14144
14891
  import {
14145
14892
  query
14146
14893
  } from "@anthropic-ai/claude-agent-sdk";
@@ -15215,7 +15962,7 @@ function mapClaudeCanceledToolCall(params) {
15215
15962
  }
15216
15963
 
15217
15964
  // ../server/src/server/agent/providers/claude/task-notification-tool-call.ts
15218
- import { createHash as createHash2 } from "node:crypto";
15965
+ import { createHash as createHash3 } from "node:crypto";
15219
15966
  import { z as z21 } from "zod";
15220
15967
  var TASK_NOTIFICATION_MARKER = "<task-notification>";
15221
15968
  var TAG_NAME_PATTERN = /[.*+?^${}()|[\]\\]/g;
@@ -15352,7 +16099,7 @@ function buildTaskNotificationCallId(envelope) {
15352
16099
  return `task_notification_${taskSegment}`;
15353
16100
  }
15354
16101
  const seed = [envelope.status, envelope.summary, envelope.outputFile, envelope.rawText].filter((value) => typeof value === "string").join("|") || "task_notification";
15355
- const digest = createHash2("sha1").update(seed).digest("hex").slice(0, 12);
16102
+ const digest = createHash3("sha1").update(seed).digest("hex").slice(0, 12);
15356
16103
  return `task_notification_${digest}`;
15357
16104
  }
15358
16105
  function buildTaskNotificationLabel(envelope) {
@@ -15531,6 +16278,40 @@ function normalizeClaudeRuntimeModelId(value) {
15531
16278
  return `claude-${family}-${major}-${minor}${suffix}`;
15532
16279
  }
15533
16280
 
16281
+ // ../server/src/server/agent/providers/claude-feature-definitions.ts
16282
+ var CLAUDE_FAST_MODE_SUPPORTED_MODEL_PREFIXES = ["claude-opus-"];
16283
+ var CLAUDE_FAST_MODE_FEATURE = {
16284
+ type: "toggle",
16285
+ id: "fast_mode",
16286
+ label: "Fast",
16287
+ description: "Priority inference for faster responses",
16288
+ tooltip: "Toggle fast mode",
16289
+ icon: "zap"
16290
+ };
16291
+ function normalizeClaudeModelId(modelId) {
16292
+ const normalized = typeof modelId === "string" ? modelId.trim() : "";
16293
+ return normalized.length > 0 ? normalized : null;
16294
+ }
16295
+ function claudeModelSupportsFastMode(modelId) {
16296
+ const normalizedModelId = normalizeClaudeModelId(modelId);
16297
+ if (!normalizedModelId) {
16298
+ return false;
16299
+ }
16300
+ return CLAUDE_FAST_MODE_SUPPORTED_MODEL_PREFIXES.some(
16301
+ (prefix) => normalizedModelId.startsWith(prefix)
16302
+ );
16303
+ }
16304
+ function buildClaudeFeatures(input) {
16305
+ const features = [];
16306
+ if (claudeModelSupportsFastMode(input.modelId)) {
16307
+ features.push({
16308
+ ...CLAUDE_FAST_MODE_FEATURE,
16309
+ value: input.fastModeEnabled
16310
+ });
16311
+ }
16312
+ return features;
16313
+ }
16314
+
15534
16315
  // ../server/src/server/agent/providers/claude/partial-json.ts
15535
16316
  function skipWhitespace(input, index) {
15536
16317
  let currentIndex = index;
@@ -16098,62 +16879,8 @@ async function resolveBinaryVersion(binaryPath) {
16098
16879
  }
16099
16880
  }
16100
16881
 
16101
- // ../server/src/server/agent/prompt-attachments.ts
16102
- function renderPromptAttachmentAsText(attachment) {
16103
- switch (attachment.type) {
16104
- case "github_pr": {
16105
- const lines = [`GitHub PR #${attachment.number}: ${attachment.title}`, attachment.url];
16106
- if (attachment.baseRefName) {
16107
- lines.push(`Base: ${attachment.baseRefName}`);
16108
- }
16109
- if (attachment.headRefName) {
16110
- lines.push(`Head: ${attachment.headRefName}`);
16111
- }
16112
- if (attachment.body) {
16113
- lines.push("", attachment.body);
16114
- }
16115
- return lines.join("\n");
16116
- }
16117
- case "github_issue": {
16118
- const lines = [`GitHub Issue #${attachment.number}: ${attachment.title}`, attachment.url];
16119
- if (attachment.body) {
16120
- lines.push("", attachment.body);
16121
- }
16122
- return lines.join("\n");
16123
- }
16124
- case "gitlab_mr": {
16125
- const lines = [`GitLab MR !${attachment.iid}: ${attachment.title}`, attachment.url];
16126
- if (attachment.targetBranch) {
16127
- lines.push(`Target: ${attachment.targetBranch}`);
16128
- }
16129
- if (attachment.sourceBranch) {
16130
- lines.push(`Source: ${attachment.sourceBranch}`);
16131
- }
16132
- if (attachment.body) {
16133
- lines.push("", attachment.body);
16134
- }
16135
- return lines.join("\n");
16136
- }
16137
- case "gitlab_issue": {
16138
- const lines = [`GitLab Issue #${attachment.iid}: ${attachment.title}`, attachment.url];
16139
- if (attachment.body) {
16140
- lines.push("", attachment.body);
16141
- }
16142
- return lines.join("\n");
16143
- }
16144
- }
16145
- }
16146
- function findGitHubPrAttachment(attachments) {
16147
- if (!attachments) {
16148
- return null;
16149
- }
16150
- return attachments.find(
16151
- (attachment) => attachment.type === "github_pr"
16152
- ) ?? null;
16153
- }
16154
-
16155
16882
  // ../server/src/server/agent/plan-filename.ts
16156
- import path5 from "node:path";
16883
+ import path7 from "node:path";
16157
16884
  var MAX_SLUG_LENGTH2 = 40;
16158
16885
  function slugifyPlanName(name) {
16159
16886
  if (typeof name !== "string") {
@@ -16202,12 +16929,12 @@ function extractPlanNameFromFrontmatter(content) {
16202
16929
  }
16203
16930
  function resolvePlanFilename(options) {
16204
16931
  const { originalPath, newSlug, existsSync: existsSync14 } = options;
16205
- const dir = path5.dirname(originalPath);
16932
+ const dir = path7.dirname(originalPath);
16206
16933
  const base = `${newSlug}.md`;
16207
- let candidate = path5.join(dir, base);
16934
+ let candidate = path7.join(dir, base);
16208
16935
  let counter = 2;
16209
16936
  while (candidate !== originalPath && existsSync14(candidate)) {
16210
- candidate = path5.join(dir, `${newSlug}-${counter}.md`);
16937
+ candidate = path7.join(dir, `${newSlug}-${counter}.md`);
16211
16938
  counter += 1;
16212
16939
  }
16213
16940
  return candidate;
@@ -16766,12 +17493,12 @@ var homeMcpServersCache = null;
16766
17493
  function loadHomeMcpServers(logger) {
16767
17494
  try {
16768
17495
  const home = process.env.HOME ?? os2.homedir();
16769
- const filePath = path6.join(home, ".claude.json");
16770
- const stat5 = fs.statSync(filePath);
17496
+ const filePath = path8.join(home, ".claude.json");
17497
+ const stat5 = fs3.statSync(filePath);
16771
17498
  if (homeMcpServersCache && homeMcpServersCache.mtimeMs === stat5.mtimeMs) {
16772
17499
  return homeMcpServersCache.servers;
16773
17500
  }
16774
- const raw = fs.readFileSync(filePath, "utf8");
17501
+ const raw = fs3.readFileSync(filePath, "utf8");
16775
17502
  const parsed = JSON.parse(raw);
16776
17503
  if (!isMetadata(parsed)) return null;
16777
17504
  const rawServers = parsed.mcpServers;
@@ -17118,8 +17845,8 @@ var ClaudeAgentClient = class {
17118
17845
  return getClaudeModels();
17119
17846
  }
17120
17847
  async listPersistedAgents(options) {
17121
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path6.join(os2.homedir(), ".claude");
17122
- const projectsRoot = path6.join(configDir, "projects");
17848
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path8.join(os2.homedir(), ".claude");
17849
+ const projectsRoot = path8.join(configDir, "projects");
17123
17850
  if (!await pathExists2(projectsRoot)) {
17124
17851
  return [];
17125
17852
  }
@@ -17340,9 +18067,10 @@ var ClaudeAgentSession = class {
17340
18067
  this.userMessageIds = [];
17341
18068
  this.recentStderr = "";
17342
18069
  this.closed = false;
18070
+ this.fastModeEnabled = false;
17343
18071
  this.handlePermissionRequest = async (toolName, input, options) => {
17344
18072
  this.maybeRewritePlanFilename(toolName, input);
17345
- const requestId = `permission-${randomUUID()}`;
18073
+ const requestId = `permission-${randomUUID2()}`;
17346
18074
  const kind = resolvePermissionKind(toolName, input);
17347
18075
  if (kind === "tool") {
17348
18076
  if (this.currentMode === "bypassPermissions") {
@@ -17438,10 +18166,17 @@ var ClaudeAgentSession = class {
17438
18166
  if (this.currentMode !== "plan") {
17439
18167
  this.planResumeMode = this.currentMode;
17440
18168
  }
18169
+ this.fastModeEnabled = Boolean(config.featureValues?.fast_mode);
17441
18170
  }
17442
18171
  get id() {
17443
18172
  return this.claudeSessionId;
17444
18173
  }
18174
+ get features() {
18175
+ return buildClaudeFeatures({
18176
+ modelId: this.config.model,
18177
+ fastModeEnabled: this.fastModeEnabled
18178
+ });
18179
+ }
17445
18180
  async getRuntimeInfo() {
17446
18181
  if (this.cachedRuntimeInfo) {
17447
18182
  return { ...this.cachedRuntimeInfo };
@@ -17680,6 +18415,41 @@ var ClaudeAgentSession = class {
17680
18415
  }
17681
18416
  this.queryRestartNeeded = true;
17682
18417
  }
18418
+ async setFeature(featureId, value) {
18419
+ if (featureId === "fast_mode") {
18420
+ const next = Boolean(value);
18421
+ if (next === this.fastModeEnabled) {
18422
+ return;
18423
+ }
18424
+ this.fastModeEnabled = next;
18425
+ this.config.featureValues = {
18426
+ ...this.config.featureValues ?? {},
18427
+ fast_mode: next
18428
+ };
18429
+ this.cachedRuntimeInfo = null;
18430
+ if (this.query?.applyFlagSettings) {
18431
+ await this.query.applyFlagSettings({ fastMode: next });
18432
+ const queryWithGetSettings = this.query;
18433
+ if (typeof queryWithGetSettings.getSettings === "function") {
18434
+ try {
18435
+ const effective = await queryWithGetSettings.getSettings();
18436
+ const observedFastMode = effective && typeof effective === "object" && "fastMode" in effective && typeof effective.fastMode === "boolean" ? effective.fastMode : null;
18437
+ this.logger.info(
18438
+ { featureId, requested: next, observed: observedFastMode },
18439
+ "claude: fast_mode read-back from SDK"
18440
+ );
18441
+ } catch (error) {
18442
+ this.logger.warn(
18443
+ { err: error, featureId },
18444
+ "claude: fast_mode getSettings read-back failed"
18445
+ );
18446
+ }
18447
+ }
18448
+ }
18449
+ return;
18450
+ }
18451
+ throw new Error(`Unknown feature: ${featureId}`);
18452
+ }
17683
18453
  getPendingPermissions() {
17684
18454
  return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
17685
18455
  }
@@ -17931,12 +18701,12 @@ var ClaudeAgentSession = class {
17931
18701
  return [];
17932
18702
  }
17933
18703
  const historyPath = this.resolveHistoryPath(this.claudeSessionId);
17934
- if (!historyPath || !fs.existsSync(historyPath)) {
18704
+ if (!historyPath || !fs3.existsSync(historyPath)) {
17935
18705
  return [];
17936
18706
  }
17937
18707
  try {
17938
18708
  const ids = [];
17939
- const content = fs.readFileSync(historyPath, "utf8");
18709
+ const content = fs3.readFileSync(historyPath, "utf8");
17940
18710
  for (const line of content.split(/\n+/)) {
17941
18711
  const trimmed = line.trim();
17942
18712
  if (!trimmed) continue;
@@ -18082,6 +18852,10 @@ var ClaudeAgentSession = class {
18082
18852
  ...this.claudeSessionId ? { resume: this.claudeSessionId } : {},
18083
18853
  ...thinking ? { thinking } : {},
18084
18854
  ...effort ? { effort } : {},
18855
+ // Initial-spawn path for fast mode: bake the flag into the new
18856
+ // process's flag-settings layer. Mid-session toggles go through
18857
+ // setFeature → query.applyFlagSettings instead (no restart needed).
18858
+ ...this.fastModeEnabled ? { settings: { fastMode: true } } : {},
18085
18859
  ...this.config.extra?.claude
18086
18860
  };
18087
18861
  if (this.config.mcpServers) {
@@ -18129,7 +18903,9 @@ var ClaudeAgentSession = class {
18129
18903
  if (Array.isArray(prompt)) {
18130
18904
  for (const chunk of prompt) {
18131
18905
  if (chunk.type === "text") {
18132
- content.push({ type: "text", text: chunk.text });
18906
+ if (chunk.text.length > 0) {
18907
+ content.push({ type: "text", text: chunk.text });
18908
+ }
18133
18909
  } else if (chunk.type === "image") {
18134
18910
  content.push({
18135
18911
  type: "image",
@@ -18140,13 +18916,19 @@ var ClaudeAgentSession = class {
18140
18916
  }
18141
18917
  });
18142
18918
  } else if (chunk.type === "github_pr" || chunk.type === "github_issue" || chunk.type === "gitlab_mr" || chunk.type === "gitlab_issue") {
18143
- content.push({ type: "text", text: renderPromptAttachmentAsText(chunk) });
18919
+ const rendered = renderPromptAttachmentAsText(chunk);
18920
+ if (rendered.length > 0) {
18921
+ content.push({ type: "text", text: rendered });
18922
+ }
18144
18923
  }
18145
18924
  }
18146
- } else {
18925
+ } else if (prompt.length > 0) {
18147
18926
  content.push({ type: "text", text: prompt });
18148
18927
  }
18149
- const messageId = randomUUID();
18928
+ if (content.length === 0) {
18929
+ content.push({ type: "text", text: "(empty message)" });
18930
+ }
18931
+ const messageId = randomUUID2();
18150
18932
  this.rememberUserMessageId(messageId);
18151
18933
  return {
18152
18934
  type: "user",
@@ -18899,9 +19681,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18899
19681
  if (typeof filePath !== "string" || typeof content !== "string") {
18900
19682
  return;
18901
19683
  }
18902
- const dir = path6.dirname(filePath);
18903
- const dirName = path6.basename(dir);
18904
- const baseName = path6.basename(filePath);
19684
+ const dir = path8.dirname(filePath);
19685
+ const dirName = path8.basename(dir);
19686
+ const baseName = path8.basename(filePath);
18905
19687
  if (dirName !== ".plans" || !baseName.endsWith(".md")) {
18906
19688
  return;
18907
19689
  }
@@ -18920,7 +19702,7 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18920
19702
  const newPath = resolvePlanFilename({
18921
19703
  originalPath: filePath,
18922
19704
  newSlug: slug,
18923
- existsSync: fs.existsSync
19705
+ existsSync: fs3.existsSync
18924
19706
  });
18925
19707
  if (newPath === filePath) {
18926
19708
  return;
@@ -19008,10 +19790,10 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19008
19790
  loadPersistedHistory(sessionId) {
19009
19791
  try {
19010
19792
  const historyPath = this.resolveHistoryPath(sessionId);
19011
- if (!historyPath || !fs.existsSync(historyPath)) {
19793
+ if (!historyPath || !fs3.existsSync(historyPath)) {
19012
19794
  return;
19013
19795
  }
19014
- this.ingestPersistedHistory(fs.readFileSync(historyPath, "utf8"));
19796
+ this.ingestPersistedHistory(fs3.readFileSync(historyPath, "utf8"));
19015
19797
  } catch (error) {
19016
19798
  }
19017
19799
  }
@@ -19054,9 +19836,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19054
19836
  const cwd = this.config.cwd;
19055
19837
  if (!cwd) return null;
19056
19838
  const sanitized = sanitizeClaudeProjectPath(cwd);
19057
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path6.join(os2.homedir(), ".claude");
19058
- const dir = path6.join(configDir, "projects", sanitized);
19059
- return path6.join(dir, `${sessionId}.jsonl`);
19839
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path8.join(os2.homedir(), ".claude");
19840
+ const dir = path8.join(configDir, "projects", sanitized);
19841
+ return path8.join(dir, `${sessionId}.jsonl`);
19060
19842
  }
19061
19843
  convertHistoryEntry(entry) {
19062
19844
  return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
@@ -19518,7 +20300,7 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19518
20300
  }
19519
20301
  detectFileKind(filePath) {
19520
20302
  try {
19521
- return fs.existsSync(filePath) ? "update" : "add";
20303
+ return fs3.existsSync(filePath) ? "update" : "add";
19522
20304
  } catch {
19523
20305
  return "update";
19524
20306
  }
@@ -19529,8 +20311,8 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19529
20311
  }
19530
20312
  const cwd = this.config.cwd;
19531
20313
  if (cwd && target.startsWith(cwd)) {
19532
- const relative = path6.relative(cwd, target);
19533
- return relative.length > 0 ? relative : path6.basename(target);
20314
+ const relative = path8.relative(cwd, target);
20315
+ return relative.length > 0 ? relative : path8.basename(target);
19534
20316
  }
19535
20317
  return target;
19536
20318
  }
@@ -19719,7 +20501,7 @@ async function collectRecentClaudeSessions(root, limit) {
19719
20501
  }
19720
20502
  const candidates = [];
19721
20503
  for (const dirName of projectDirs) {
19722
- const projectPath = path6.join(root, dirName);
20504
+ const projectPath = path8.join(root, dirName);
19723
20505
  let stats;
19724
20506
  try {
19725
20507
  stats = await fsPromises.stat(projectPath);
@@ -19739,7 +20521,7 @@ async function collectRecentClaudeSessions(root, limit) {
19739
20521
  if (!file.endsWith(".jsonl")) {
19740
20522
  continue;
19741
20523
  }
19742
- const fullPath = path6.join(projectPath, file);
20524
+ const fullPath = path8.join(projectPath, file);
19743
20525
  try {
19744
20526
  const fileStats = await fsPromises.stat(fullPath);
19745
20527
  candidates.push({ path: fullPath, mtime: fileStats.mtime });
@@ -19914,17 +20696,17 @@ function unescapePartialJsonString(buffer, start) {
19914
20696
 
19915
20697
  // ../server/src/server/agent/providers/codex-app-server-agent.ts
19916
20698
  import { execSync as execSync2 } from "node:child_process";
19917
- import { randomUUID as randomUUID2 } from "node:crypto";
19918
- import fs3 from "node:fs/promises";
20699
+ import { randomUUID as randomUUID3 } from "node:crypto";
20700
+ import fs5 from "node:fs/promises";
19919
20701
  import os4 from "node:os";
19920
- import path8 from "node:path";
20702
+ import path10 from "node:path";
19921
20703
  import readline from "node:readline";
19922
20704
  import { z as z25 } from "zod";
19923
20705
 
19924
20706
  // ../server/src/server/agent/providers/codex-rollout-timeline.ts
19925
- import { promises as fs2 } from "node:fs";
20707
+ import { promises as fs4 } from "node:fs";
19926
20708
  import os3 from "node:os";
19927
- import path7 from "node:path";
20709
+ import path9 from "node:path";
19928
20710
  import { z as z24 } from "zod";
19929
20711
 
19930
20712
  // ../server/src/server/agent/providers/codex/tool-call-mapper.ts
@@ -20376,14 +21158,14 @@ function codexApplyPatchToUnifiedDiff(text) {
20376
21158
  for (const line of lines) {
20377
21159
  const directive = parseCodexApplyPatchDirective(line);
20378
21160
  if (directive) {
20379
- const path24 = normalizeDiffHeaderPath(directive.path);
20380
- if (path24.length > 0) {
21161
+ const path26 = normalizeDiffHeaderPath(directive.path);
21162
+ if (path26.length > 0) {
20381
21163
  if (output.length > 0 && output[output.length - 1] !== "") {
20382
21164
  output.push("");
20383
21165
  }
20384
- const left = directive.kind === "add" ? "/dev/null" : `a/${path24}`;
20385
- const right = directive.kind === "delete" ? "/dev/null" : `b/${path24}`;
20386
- output.push(`diff --git a/${path24} b/${path24}`);
21166
+ const left = directive.kind === "add" ? "/dev/null" : `a/${path26}`;
21167
+ const right = directive.kind === "delete" ? "/dev/null" : `b/${path26}`;
21168
+ output.push(`diff --git a/${path26} b/${path26}`);
20387
21169
  output.push(`--- ${left}`);
20388
21170
  output.push(`+++ ${right}`);
20389
21171
  sawDiffContent = true;
@@ -20453,9 +21235,9 @@ function asEditTextFields(text) {
20453
21235
  function normalizeRolloutEditInput(input) {
20454
21236
  if (typeof input === "string") {
20455
21237
  const textFields2 = asEditTextFields(input);
20456
- const path24 = extractPatchPrimaryFilePath(input);
21238
+ const path26 = extractPatchPrimaryFilePath(input);
20457
21239
  return {
20458
- ...path24 ? { path: path24 } : {},
21240
+ ...path26 ? { path: path26 } : {},
20459
21241
  ...textFields2.unifiedDiff ? { patch: textFields2.unifiedDiff } : {},
20460
21242
  ...textFields2.newString ? { content: textFields2.newString } : {}
20461
21243
  };
@@ -20603,12 +21385,12 @@ function parseFileChangeDiff(entry) {
20603
21385
  ]);
20604
21386
  }
20605
21387
  function toFileChangeEntry(entry, options, fallbackPath) {
20606
- const path24 = parseFileChangePath(entry, options, fallbackPath);
20607
- if (!path24) {
21388
+ const path26 = parseFileChangePath(entry, options, fallbackPath);
21389
+ if (!path26) {
20608
21390
  return null;
20609
21391
  }
20610
21392
  return {
20611
- path: path24,
21393
+ path: path26,
20612
21394
  kind: parseFileChangeKind(entry),
20613
21395
  diff: parseFileChangeDiff(entry)
20614
21396
  };
@@ -20630,12 +21412,12 @@ function parseFileChangeEntries(changes, options) {
20630
21412
  if (singleEntry) {
20631
21413
  return [singleEntry];
20632
21414
  }
20633
- return Object.entries(changes).map(([path24, value]) => {
21415
+ return Object.entries(changes).map(([path26, value]) => {
20634
21416
  if (isRecord2(value)) {
20635
- return toFileChangeEntry(value, options, path24);
21417
+ return toFileChangeEntry(value, options, path26);
20636
21418
  }
20637
21419
  if (typeof value === "string") {
20638
- const normalizedPath = normalizeCodexFilePath(path24.trim(), options?.cwd);
21420
+ const normalizedPath = normalizeCodexFilePath(path26.trim(), options?.cwd);
20639
21421
  if (!normalizedPath) {
20640
21422
  return null;
20641
21423
  }
@@ -20797,8 +21579,8 @@ function resolveCodexSessionRoot() {
20797
21579
  if (process.env.CODEX_SESSION_DIR) {
20798
21580
  return process.env.CODEX_SESSION_DIR;
20799
21581
  }
20800
- const codexHome = process.env.CODEX_HOME ?? path7.join(os3.homedir(), ".codex");
20801
- return path7.join(codexHome, "sessions");
21582
+ const codexHome = process.env.CODEX_HOME ?? path9.join(os3.homedir(), ".codex");
21583
+ return path9.join(codexHome, "sessions");
20802
21584
  }
20803
21585
  async function findRolloutFile(threadId, root) {
20804
21586
  const stack = [{ dir: root, depth: 0 }];
@@ -20806,12 +21588,12 @@ async function findRolloutFile(threadId, root) {
20806
21588
  const { dir, depth } = stack.pop();
20807
21589
  let entries;
20808
21590
  try {
20809
- entries = await fs2.readdir(dir, { withFileTypes: true });
21591
+ entries = await fs4.readdir(dir, { withFileTypes: true });
20810
21592
  } catch {
20811
21593
  continue;
20812
21594
  }
20813
21595
  for (const entry of entries) {
20814
- const entryPath = path7.join(dir, entry.name);
21596
+ const entryPath = path9.join(dir, entry.name);
20815
21597
  if (entry.isFile()) {
20816
21598
  const matchesThread = entry.name.includes(threadId);
20817
21599
  const matchesPrefix = entry.name.startsWith("rollout-");
@@ -21171,7 +21953,7 @@ function dedupeMirroredTextTimelineItems(timeline) {
21171
21953
  return deduped;
21172
21954
  }
21173
21955
  async function parseRolloutFile(filePath) {
21174
- const content = await fs2.readFile(filePath, "utf8");
21956
+ const content = await fs4.readFile(filePath, "utf8");
21175
21957
  const trimmed = content.trim();
21176
21958
  if (!trimmed) {
21177
21959
  return [];
@@ -21224,7 +22006,7 @@ async function loadCodexPersistedTimeline(sessionId, options, logger) {
21224
22006
  const rolloutPath = options?.rolloutPath ?? null;
21225
22007
  if (rolloutPath) {
21226
22008
  try {
21227
- const stat5 = await fs2.stat(rolloutPath);
22009
+ const stat5 = await fs4.stat(rolloutPath);
21228
22010
  if (stat5.isFile()) {
21229
22011
  const timeline = await parseRolloutFile(rolloutPath);
21230
22012
  if (timeline.length > 0) {
@@ -21380,16 +22162,16 @@ function isObjectSchemaNode(schema) {
21380
22162
  const type = schema.type;
21381
22163
  return isSchemaRecord(schema.properties) || type === "object" || Array.isArray(type) && type.includes("object");
21382
22164
  }
21383
- function normalizeCodexOutputSchemaNode(schema, path24) {
22165
+ function normalizeCodexOutputSchemaNode(schema, path26) {
21384
22166
  if (Array.isArray(schema)) {
21385
- return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path24}[${index}]`));
22167
+ return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path26}[${index}]`));
21386
22168
  }
21387
22169
  if (!isSchemaRecord(schema)) {
21388
22170
  return schema;
21389
22171
  }
21390
22172
  const normalized = {};
21391
22173
  for (const [key, value] of Object.entries(schema)) {
21392
- normalized[key] = normalizeCodexOutputSchemaNode(value, `${path24}.${key}`);
22174
+ normalized[key] = normalizeCodexOutputSchemaNode(value, `${path26}.${key}`);
21393
22175
  }
21394
22176
  if (!isObjectSchemaNode(normalized)) {
21395
22177
  return normalized;
@@ -21398,7 +22180,7 @@ function normalizeCodexOutputSchemaNode(schema, path24) {
21398
22180
  normalized.additionalProperties = false;
21399
22181
  } else if (normalized.additionalProperties !== false) {
21400
22182
  throw new Error(
21401
- `Codex structured outputs require ${path24} to set additionalProperties to false for object schemas.`
22183
+ `Codex structured outputs require ${path26} to set additionalProperties to false for object schemas.`
21402
22184
  );
21403
22185
  }
21404
22186
  const properties = isSchemaRecord(normalized.properties) ? normalized.properties : null;
@@ -21439,7 +22221,7 @@ async function resolveCodexLaunchPrefix(runtimeSettings) {
21439
22221
  return resolveProviderCommandPrefix(runtimeSettings?.command, resolveCodexBinary);
21440
22222
  }
21441
22223
  function resolveCodexHomeDir() {
21442
- return process.env.CODEX_HOME ?? path8.join(os4.homedir(), ".codex");
22224
+ return process.env.CODEX_HOME ?? path10.join(os4.homedir(), ".codex");
21443
22225
  }
21444
22226
  function tokenizeCommandArgs(args) {
21445
22227
  const tokens = [];
@@ -21519,10 +22301,10 @@ function parseFrontMatter(markdown) {
21519
22301
  }
21520
22302
  async function listCodexCustomPrompts() {
21521
22303
  const codexHome = resolveCodexHomeDir();
21522
- const promptsDir = path8.join(codexHome, "prompts");
22304
+ const promptsDir = path10.join(codexHome, "prompts");
21523
22305
  let entries;
21524
22306
  try {
21525
- entries = await fs3.readdir(promptsDir, { withFileTypes: true });
22307
+ entries = await fs5.readdir(promptsDir, { withFileTypes: true });
21526
22308
  } catch {
21527
22309
  return [];
21528
22310
  }
@@ -21538,10 +22320,10 @@ async function listCodexCustomPrompts() {
21538
22320
  if (!name) {
21539
22321
  continue;
21540
22322
  }
21541
- const fullPath = path8.join(promptsDir, entry.name);
22323
+ const fullPath = path10.join(promptsDir, entry.name);
21542
22324
  let content;
21543
22325
  try {
21544
- content = await fs3.readFile(fullPath, "utf8");
22326
+ content = await fs5.readFile(fullPath, "utf8");
21545
22327
  } catch {
21546
22328
  continue;
21547
22329
  }
@@ -21558,7 +22340,7 @@ async function listCodexCustomPrompts() {
21558
22340
  }
21559
22341
  async function listCodexSkills(cwd) {
21560
22342
  const candidates = [];
21561
- candidates.push(path8.join(cwd, ".codex", "skills"));
22343
+ candidates.push(path10.join(cwd, ".codex", "skills"));
21562
22344
  const repoRoot = (() => {
21563
22345
  try {
21564
22346
  const output = execSync2("git rev-parse --show-toplevel", {
@@ -21573,15 +22355,15 @@ async function listCodexSkills(cwd) {
21573
22355
  }
21574
22356
  })();
21575
22357
  if (repoRoot) {
21576
- candidates.push(path8.join(path8.dirname(cwd), ".codex", "skills"));
21577
- candidates.push(path8.join(repoRoot, ".codex", "skills"));
22358
+ candidates.push(path10.join(path10.dirname(cwd), ".codex", "skills"));
22359
+ candidates.push(path10.join(repoRoot, ".codex", "skills"));
21578
22360
  }
21579
- candidates.push(path8.join(resolveCodexHomeDir(), "skills"));
22361
+ candidates.push(path10.join(resolveCodexHomeDir(), "skills"));
21580
22362
  const commandsByName = /* @__PURE__ */ new Map();
21581
22363
  for (const dir of candidates) {
21582
22364
  let entries;
21583
22365
  try {
21584
- entries = await fs3.readdir(dir, { withFileTypes: true });
22366
+ entries = await fs5.readdir(dir, { withFileTypes: true });
21585
22367
  } catch {
21586
22368
  continue;
21587
22369
  }
@@ -21589,11 +22371,11 @@ async function listCodexSkills(cwd) {
21589
22371
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
21590
22372
  continue;
21591
22373
  }
21592
- const skillDir = path8.join(dir, entry.name);
21593
- const skillPath = path8.join(skillDir, "SKILL.md");
22374
+ const skillDir = path10.join(dir, entry.name);
22375
+ const skillPath = path10.join(skillDir, "SKILL.md");
21594
22376
  let content;
21595
22377
  try {
21596
- content = await fs3.readFile(skillPath, "utf8");
22378
+ content = await fs5.readFile(skillPath, "utf8");
21597
22379
  } catch {
21598
22380
  continue;
21599
22381
  }
@@ -22162,8 +22944,8 @@ function parseCodexPatchChanges(changes) {
22162
22944
  }
22163
22945
  ];
22164
22946
  }
22165
- return Object.entries(recordChanges).map(([path24, value]) => {
22166
- const normalizedPath = path24.trim();
22947
+ return Object.entries(recordChanges).map(([path26, value]) => {
22948
+ const normalizedPath = path26.trim();
22167
22949
  if (!normalizedPath) {
22168
22950
  return null;
22169
22951
  }
@@ -22901,13 +23683,13 @@ var CodexNotificationSchema = z25.union([
22901
23683
  )
22902
23684
  ]);
22903
23685
  async function writeImageAttachment(mimeType, data) {
22904
- const attachmentsDir = path8.join(os4.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
22905
- await fs3.mkdir(attachmentsDir, { recursive: true });
23686
+ const attachmentsDir = path10.join(os4.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
23687
+ await fs5.mkdir(attachmentsDir, { recursive: true });
22906
23688
  const normalized = normalizeImageData(mimeType, data);
22907
23689
  const extension = getImageExtension(normalized.mimeType);
22908
- const filename = `${randomUUID2()}.${extension}`;
22909
- const filePath = path8.join(attachmentsDir, filename);
22910
- await fs3.writeFile(filePath, Buffer.from(normalized.data, "base64"));
23690
+ const filename = `${randomUUID3()}.${extension}`;
23691
+ const filePath = path10.join(attachmentsDir, filename);
23692
+ await fs5.writeFile(filePath, Buffer.from(normalized.data, "base64"));
22911
23693
  return filePath;
22912
23694
  }
22913
23695
  async function readCodexConfiguredDefaults(client, logger) {
@@ -23187,7 +23969,7 @@ var CodexAppServerAgentSession = class {
23187
23969
  };
23188
23970
  }
23189
23971
  emitSyntheticPlanApprovalRequest(planText) {
23190
- const requestId = `permission-${randomUUID2()}`;
23972
+ const requestId = `permission-${randomUUID3()}`;
23191
23973
  const request = {
23192
23974
  id: requestId,
23193
23975
  provider: CODEX_PROVIDER,
@@ -23344,8 +24126,8 @@ var CodexAppServerAgentSession = class {
23344
24126
  if (commandName.startsWith("prompts:")) {
23345
24127
  const promptName = commandName.slice("prompts:".length);
23346
24128
  const codexHome = resolveCodexHomeDir();
23347
- const promptPath = path8.join(codexHome, "prompts", `${promptName}.md`);
23348
- const raw = await fs3.readFile(promptPath, "utf8");
24129
+ const promptPath = path10.join(codexHome, "prompts", `${promptName}.md`);
24130
+ const raw = await fs5.readFile(promptPath, "utf8");
23349
24131
  const parsed = parseFrontMatter(raw);
23350
24132
  return expandCodexCustomPrompt(parsed.body, args);
23351
24133
  }
@@ -24577,9 +25359,9 @@ var CodexAppServerAgentClient = class {
24577
25359
  };
24578
25360
 
24579
25361
  // ../server/src/server/agent/providers/acp-agent.ts
24580
- import { randomUUID as randomUUID3 } from "node:crypto";
24581
- import fs4 from "node:fs/promises";
24582
- import path9 from "node:path";
25362
+ import { randomUUID as randomUUID4 } from "node:crypto";
25363
+ import fs6 from "node:fs/promises";
25364
+ import path11 from "node:path";
24583
25365
  import { Readable, Writable } from "node:stream";
24584
25366
  import {
24585
25367
  ClientSideConnection,
@@ -24876,12 +25658,12 @@ ${stderr}` : String(error)));
24876
25658
  async sessionUpdate() {
24877
25659
  },
24878
25660
  async readTextFile(params) {
24879
- const content = await fs4.readFile(params.path, "utf8");
25661
+ const content = await fs6.readFile(params.path, "utf8");
24880
25662
  return { content };
24881
25663
  },
24882
25664
  async writeTextFile(params) {
24883
- await fs4.mkdir(path9.dirname(params.path), { recursive: true });
24884
- await fs4.writeFile(params.path, params.content, "utf8");
25665
+ await fs6.mkdir(path11.dirname(params.path), { recursive: true });
25666
+ await fs6.writeFile(params.path, params.content, "utf8");
24885
25667
  return {};
24886
25668
  },
24887
25669
  async createTerminal() {
@@ -25103,8 +25885,8 @@ var ACPAgentSession = class {
25103
25885
  if (this.activeForegroundTurnId) {
25104
25886
  throw new Error("A foreground turn is already active");
25105
25887
  }
25106
- const turnId = randomUUID3();
25107
- const messageId = randomUUID3();
25888
+ const turnId = randomUUID4();
25889
+ const messageId = randomUUID4();
25108
25890
  this.activeForegroundTurnId = turnId;
25109
25891
  this.suppressUserEchoMessageId = messageId;
25110
25892
  this.suppressUserEchoText = extractPromptText(prompt);
@@ -25405,7 +26187,7 @@ var ACPAgentSession = class {
25405
26187
  }
25406
26188
  } : { outcome: { outcome: "cancelled" } };
25407
26189
  }
25408
- const requestId = randomUUID3();
26190
+ const requestId = randomUUID4();
25409
26191
  let toolSnapshot = this.toolCalls.get(params.toolCall.toolCallId) ?? mergeToolSnapshot(params.toolCall.toolCallId, params.toolCall);
25410
26192
  if (this.toolSnapshotTransformer) {
25411
26193
  toolSnapshot = this.toolSnapshotTransformer(toolSnapshot);
@@ -25446,7 +26228,7 @@ var ACPAgentSession = class {
25446
26228
  }
25447
26229
  }
25448
26230
  async readTextFile(params) {
25449
- const raw = await fs4.readFile(params.path, "utf8");
26231
+ const raw = await fs6.readFile(params.path, "utf8");
25450
26232
  if (!params.line && !params.limit) {
25451
26233
  return { content: raw };
25452
26234
  }
@@ -25456,12 +26238,12 @@ var ACPAgentSession = class {
25456
26238
  return { content: lines.slice(start, end).join("\n") };
25457
26239
  }
25458
26240
  async writeTextFile(params) {
25459
- await fs4.mkdir(path9.dirname(params.path), { recursive: true });
25460
- await fs4.writeFile(params.path, params.content, "utf8");
26241
+ await fs6.mkdir(path11.dirname(params.path), { recursive: true });
26242
+ await fs6.writeFile(params.path, params.content, "utf8");
25461
26243
  return {};
25462
26244
  }
25463
26245
  async createTerminal(params) {
25464
- const terminalId = randomUUID3();
26246
+ const terminalId = randomUUID4();
25465
26247
  const env = Object.fromEntries(
25466
26248
  (params.env ?? []).map((entry2) => [entry2.name, entry2.value])
25467
26249
  );
@@ -28515,9 +29297,9 @@ var OpenCodeAgentSession = class {
28515
29297
  };
28516
29298
 
28517
29299
  // ../server/src/server/agent/providers/pi-direct-agent.ts
28518
- import { randomUUID as randomUUID4 } from "node:crypto";
29300
+ import { randomUUID as randomUUID5 } from "node:crypto";
28519
29301
  import { existsSync as existsSync9 } from "node:fs";
28520
- import { join as join7 } from "node:path";
29302
+ import { join as join9 } from "node:path";
28521
29303
  import { homedir } from "node:os";
28522
29304
  import {
28523
29305
  AuthStorage,
@@ -29288,7 +30070,7 @@ var PiDirectAgentSession = class {
29288
30070
  throw new Error("A Pi turn is already active");
29289
30071
  }
29290
30072
  const payload = convertPromptInput(prompt);
29291
- const turnId = randomUUID4();
30073
+ const turnId = randomUUID5();
29292
30074
  this.activeTurnId = turnId;
29293
30075
  void this.session.prompt(payload.text, payload.images ? { images: payload.images } : void 0).catch((error) => {
29294
30076
  const failedTurnId = this.activeTurnId ?? turnId;
@@ -29523,7 +30305,7 @@ var PiDirectAgentClient = class {
29523
30305
  } else if (!await isCommandAvailable(PI_BINARY_COMMAND)) {
29524
30306
  return false;
29525
30307
  }
29526
- return Boolean(process.env.OPENAI_API_KEY) || Boolean(process.env.ANTHROPIC_API_KEY) || Boolean(process.env.OPENROUTER_API_KEY) || existsSync9(join7(homedir(), ".pi", "agent", "auth.json"));
30308
+ return Boolean(process.env.OPENAI_API_KEY) || Boolean(process.env.ANTHROPIC_API_KEY) || Boolean(process.env.OPENROUTER_API_KEY) || existsSync9(join9(homedir(), ".pi", "agent", "auth.json"));
29527
30309
  }
29528
30310
  async getDiagnostic() {
29529
30311
  try {
@@ -29531,7 +30313,7 @@ var PiDirectAgentClient = class {
29531
30313
  const binaryOverride = this.runtimeSettings?.command;
29532
30314
  const binary = binaryOverride?.mode === "replace" && binaryOverride.argv[0] ? binaryOverride.argv[0] : await findExecutable(PI_BINARY_COMMAND);
29533
30315
  const version = binary ? await resolveBinaryVersion(binary) : "unknown";
29534
- const authConfigPath = join7(homedir(), ".pi", "agent", "auth.json");
30316
+ const authConfigPath = join9(homedir(), ".pi", "agent", "auth.json");
29535
30317
  let modelsValue = "Not checked";
29536
30318
  let status = formatDiagnosticStatus(available);
29537
30319
  if (available) {
@@ -29924,8 +30706,8 @@ function buildZodValidator(schema, schemaName) {
29924
30706
  return { ok: true, value: result.data };
29925
30707
  }
29926
30708
  const errors = result.error.issues.map((issue) => {
29927
- const path24 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
29928
- return `${path24}: ${issue.message}`;
30709
+ const path26 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
30710
+ return `${path26}: ${issue.message}`;
29929
30711
  });
29930
30712
  return { ok: false, errors };
29931
30713
  }
@@ -29943,9 +30725,9 @@ function buildJsonSchemaValidator(schema) {
29943
30725
  return { ok: true, value };
29944
30726
  }
29945
30727
  const errors = (validate.errors ?? []).map((error) => {
29946
- const path24 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
30728
+ const path26 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
29947
30729
  const message = error.message ?? "is invalid";
29948
- return `${path24}: ${message}`;
30730
+ return `${path26}: ${message}`;
29949
30731
  });
29950
30732
  return { ok: false, errors };
29951
30733
  }
@@ -30830,8 +31612,8 @@ function isVoicePermissionAllowed(request) {
30830
31612
  }
30831
31613
 
30832
31614
  // ../server/src/server/file-explorer/service.ts
30833
- import { promises as fs5 } from "fs";
30834
- import path10 from "path";
31615
+ import { promises as fs7 } from "fs";
31616
+ import path12 from "path";
30835
31617
 
30836
31618
  // ../server/src/server/path-utils.ts
30837
31619
  import { homedir as homedir2 } from "node:os";
@@ -30878,14 +31660,14 @@ async function listDirectoryEntries({
30878
31660
  relativePath = "."
30879
31661
  }) {
30880
31662
  const directoryPath = await resolveScopedPath({ root, relativePath });
30881
- const stats = await fs5.stat(directoryPath);
31663
+ const stats = await fs7.stat(directoryPath);
30882
31664
  if (!stats.isDirectory()) {
30883
31665
  throw new Error("Requested path is not a directory");
30884
31666
  }
30885
- const dirents = await fs5.readdir(directoryPath, { withFileTypes: true });
31667
+ const dirents = await fs7.readdir(directoryPath, { withFileTypes: true });
30886
31668
  const entriesWithNulls = await Promise.all(
30887
31669
  dirents.map(async (dirent) => {
30888
- const targetPath = path10.join(directoryPath, dirent.name);
31670
+ const targetPath = path12.join(directoryPath, dirent.name);
30889
31671
  const kind = dirent.isDirectory() ? "directory" : "file";
30890
31672
  try {
30891
31673
  return await buildEntryPayload({
@@ -30920,18 +31702,18 @@ async function readExplorerFile({
30920
31702
  relativePath
30921
31703
  }) {
30922
31704
  const filePath = await resolveScopedPath({ root, relativePath });
30923
- const stats = await fs5.stat(filePath);
31705
+ const stats = await fs7.stat(filePath);
30924
31706
  if (!stats.isFile()) {
30925
31707
  throw new Error("Requested path is not a file");
30926
31708
  }
30927
- const ext = path10.extname(filePath).toLowerCase();
31709
+ const ext = path12.extname(filePath).toLowerCase();
30928
31710
  const basePayload = {
30929
31711
  path: normalizeRelativePath({ root, targetPath: filePath }),
30930
31712
  size: stats.size,
30931
31713
  modifiedAt: stats.mtime.toISOString()
30932
31714
  };
30933
31715
  if (ext in IMAGE_MIME_TYPES) {
30934
- const buffer2 = await fs5.readFile(filePath);
31716
+ const buffer2 = await fs7.readFile(filePath);
30935
31717
  return {
30936
31718
  ...basePayload,
30937
31719
  kind: "image",
@@ -30940,7 +31722,7 @@ async function readExplorerFile({
30940
31722
  mimeType: IMAGE_MIME_TYPES[ext]
30941
31723
  };
30942
31724
  }
30943
- const buffer = await fs5.readFile(filePath);
31725
+ const buffer = await fs7.readFile(filePath);
30944
31726
  if (isLikelyBinary(buffer)) {
30945
31727
  return {
30946
31728
  ...basePayload,
@@ -30962,34 +31744,34 @@ async function writeTextFile({
30962
31744
  relativePath,
30963
31745
  content
30964
31746
  }) {
30965
- const ext = path10.extname(relativePath).toLowerCase();
31747
+ const ext = path12.extname(relativePath).toLowerCase();
30966
31748
  if (ext in IMAGE_MIME_TYPES) {
30967
31749
  throw new Error(`Refusing to write '${relativePath}': binary/image file`);
30968
31750
  }
30969
31751
  const filePath = await resolveScopedPath({ root, relativePath });
30970
31752
  const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
30971
- await fs5.writeFile(tempPath, content, "utf8");
30972
- await fs5.rename(tempPath, filePath);
31753
+ await fs7.writeFile(tempPath, content, "utf8");
31754
+ await fs7.rename(tempPath, filePath);
30973
31755
  }
30974
31756
  async function deleteFile({ root, relativePath }) {
30975
- const ext = path10.extname(relativePath).toLowerCase();
31757
+ const ext = path12.extname(relativePath).toLowerCase();
30976
31758
  if (ext !== ".md") {
30977
31759
  throw new Error(`Refusing to delete '${relativePath}': only .md files allowed`);
30978
31760
  }
30979
31761
  const filePath = await resolveScopedPath({ root, relativePath });
30980
- const stats = await fs5.stat(filePath);
31762
+ const stats = await fs7.stat(filePath);
30981
31763
  if (!stats.isFile()) {
30982
31764
  throw new Error("Requested path is not a file");
30983
31765
  }
30984
- await fs5.unlink(filePath);
31766
+ await fs7.unlink(filePath);
30985
31767
  }
30986
31768
  async function deleteEntry({ root, relativePath }) {
30987
31769
  const entryPath = await resolveScopedPath({ root, relativePath });
30988
- await fs5.rm(entryPath, { recursive: true, force: false });
31770
+ await fs7.rm(entryPath, { recursive: true, force: false });
30989
31771
  }
30990
31772
  async function createDirectory({ root, relativePath }) {
30991
31773
  const dirPath = await resolveScopedPath({ root, relativePath });
30992
- await fs5.mkdir(dirPath, { recursive: true });
31774
+ await fs7.mkdir(dirPath, { recursive: true });
30993
31775
  }
30994
31776
  async function moveEntry({
30995
31777
  root,
@@ -30998,22 +31780,22 @@ async function moveEntry({
30998
31780
  }) {
30999
31781
  const src = await resolveScopedPath({ root, relativePath: sourcePath });
31000
31782
  const dest = await resolveScopedPath({ root, relativePath: destinationPath });
31001
- await fs5.rename(src, dest);
31783
+ await fs7.rename(src, dest);
31002
31784
  }
31003
31785
  async function getDownloadableFileInfo({ root, relativePath }) {
31004
31786
  const filePath = await resolveScopedPath({ root, relativePath });
31005
- const stats = await fs5.stat(filePath);
31787
+ const stats = await fs7.stat(filePath);
31006
31788
  if (!stats.isFile()) {
31007
31789
  throw new Error("Requested path is not a file");
31008
31790
  }
31009
- const ext = path10.extname(filePath).toLowerCase();
31791
+ const ext = path12.extname(filePath).toLowerCase();
31010
31792
  let mimeType = "application/octet-stream";
31011
31793
  if (ext in IMAGE_MIME_TYPES) {
31012
31794
  mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
31013
31795
  } else if (ext in FONT_MIME_TYPES) {
31014
31796
  mimeType = FONT_MIME_TYPES[ext] ?? mimeType;
31015
31797
  } else {
31016
- const handle = await fs5.open(filePath, "r");
31798
+ const handle = await fs7.open(filePath, "r");
31017
31799
  const sample = Buffer.alloc(8192);
31018
31800
  try {
31019
31801
  const { bytesRead } = await handle.read(sample, 0, sample.length, 0);
@@ -31028,23 +31810,23 @@ async function getDownloadableFileInfo({ root, relativePath }) {
31028
31810
  return {
31029
31811
  path: normalizeRelativePath({ root, targetPath: filePath }),
31030
31812
  absolutePath: filePath,
31031
- fileName: path10.basename(filePath),
31813
+ fileName: path12.basename(filePath),
31032
31814
  mimeType,
31033
31815
  size: stats.size
31034
31816
  };
31035
31817
  }
31036
31818
  async function resolveScopedPath({ root, relativePath = "." }) {
31037
- const normalizedRoot = path10.resolve(root);
31819
+ const normalizedRoot = path12.resolve(root);
31038
31820
  const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
31039
- const relative = path10.relative(normalizedRoot, requestedPath);
31040
- if (relative !== "" && (relative.startsWith("..") || path10.isAbsolute(relative))) {
31821
+ const relative = path12.relative(normalizedRoot, requestedPath);
31822
+ if (relative !== "" && (relative.startsWith("..") || path12.isAbsolute(relative))) {
31041
31823
  throw new Error("Access outside of workspace is not allowed");
31042
31824
  }
31043
- const realRoot = await fs5.realpath(normalizedRoot);
31825
+ const realRoot = await fs7.realpath(normalizedRoot);
31044
31826
  try {
31045
- const realPath = await fs5.realpath(requestedPath);
31046
- const realRelative = path10.relative(realRoot, realPath);
31047
- if (realRelative !== "" && (realRelative.startsWith("..") || path10.isAbsolute(realRelative))) {
31827
+ const realPath = await fs7.realpath(requestedPath);
31828
+ const realRelative = path12.relative(realRoot, realPath);
31829
+ if (realRelative !== "" && (realRelative.startsWith("..") || path12.isAbsolute(realRelative))) {
31048
31830
  throw new Error("Access outside of workspace is not allowed");
31049
31831
  }
31050
31832
  return requestedPath;
@@ -31061,7 +31843,7 @@ async function buildEntryPayload({
31061
31843
  name,
31062
31844
  kind
31063
31845
  }) {
31064
- const stats = await fs5.stat(targetPath);
31846
+ const stats = await fs7.stat(targetPath);
31065
31847
  return {
31066
31848
  name,
31067
31849
  path: normalizeRelativePath({ root, targetPath }),
@@ -31075,10 +31857,10 @@ function isMissingEntryError(error) {
31075
31857
  return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
31076
31858
  }
31077
31859
  function normalizeRelativePath({ root, targetPath }) {
31078
- const normalizedRoot = path10.resolve(root);
31079
- const normalizedTarget = path10.resolve(targetPath);
31080
- const relative = path10.relative(normalizedRoot, normalizedTarget);
31081
- return relative === "" ? "." : relative.split(path10.sep).join("/");
31860
+ const normalizedRoot = path12.resolve(root);
31861
+ const normalizedTarget = path12.resolve(targetPath);
31862
+ const relative = path12.relative(normalizedRoot, normalizedTarget);
31863
+ return relative === "" ? "." : relative.split(path12.sep).join("/");
31082
31864
  }
31083
31865
  function textMimeTypeForExtension(ext) {
31084
31866
  return TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE;
@@ -31105,7 +31887,7 @@ function isLikelyBinary(buffer) {
31105
31887
 
31106
31888
  // ../server/src/utils/project-icon.ts
31107
31889
  import { readdir, readFile as readFile2, stat as stat2 } from "fs/promises";
31108
- import { extname as extname2, join as join8 } from "path";
31890
+ import { extname as extname3, join as join10 } from "path";
31109
31891
  var ICON_PATTERNS = [
31110
31892
  "favicon.ico",
31111
31893
  "favicon.png",
@@ -31219,7 +32001,7 @@ function isSquareImage(buffer, mimeType) {
31219
32001
  return dimensions.width === dimensions.height;
31220
32002
  }
31221
32003
  function getMimeType(filename) {
31222
- const ext = extname2(filename).toLowerCase();
32004
+ const ext = extname3(filename).toLowerCase();
31223
32005
  switch (ext) {
31224
32006
  case ".ico":
31225
32007
  return "image/x-icon";
@@ -31255,7 +32037,7 @@ async function findIconInDir(dir, patterns) {
31255
32037
  for (const pattern of patterns) {
31256
32038
  for (const entry of entries) {
31257
32039
  if (matchesPattern(entry, pattern)) {
31258
- const fullPath = join8(dir, entry);
32040
+ const fullPath = join10(dir, entry);
31259
32041
  try {
31260
32042
  const stats = await stat2(fullPath);
31261
32043
  if (stats.isFile()) {
@@ -31286,7 +32068,7 @@ async function searchDirRecursively(dir, patterns, ignoredDirs, maxDepth, curren
31286
32068
  if (ignoredDirs.has(entry)) {
31287
32069
  continue;
31288
32070
  }
31289
- const fullPath = join8(dir, entry);
32071
+ const fullPath = join10(dir, entry);
31290
32072
  try {
31291
32073
  const stats = await stat2(fullPath);
31292
32074
  if (stats.isDirectory()) {
@@ -31309,7 +32091,7 @@ async function searchDirRecursively(dir, patterns, ignoredDirs, maxDepth, curren
31309
32091
  async function findProjectIcon(projectDir, maxDepth = 3) {
31310
32092
  const ignoredDirsSet = new Set(IGNORED_DIRS);
31311
32093
  for (const priorityDir of PRIORITY_DIRS) {
31312
- const priorityPath = join8(projectDir, priorityDir);
32094
+ const priorityPath = join10(projectDir, priorityDir);
31313
32095
  try {
31314
32096
  const stats = await stat2(priorityPath);
31315
32097
  if (stats.isDirectory()) {
@@ -31327,7 +32109,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
31327
32109
  }
31328
32110
  }
31329
32111
  for (const monoDir of MONOREPO_PACKAGE_DIRS) {
31330
- const monoPath = join8(projectDir, monoDir);
32112
+ const monoPath = join10(projectDir, monoDir);
31331
32113
  let packageEntries;
31332
32114
  try {
31333
32115
  packageEntries = await readdir(monoPath);
@@ -31335,7 +32117,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
31335
32117
  continue;
31336
32118
  }
31337
32119
  for (const packageName of packageEntries) {
31338
- const packagePath = join8(monoPath, packageName);
32120
+ const packagePath = join10(monoPath, packageName);
31339
32121
  try {
31340
32122
  const packageStats = await stat2(packagePath);
31341
32123
  if (!packageStats.isDirectory()) continue;
@@ -31343,7 +32125,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
31343
32125
  continue;
31344
32126
  }
31345
32127
  for (const priorityDir of PRIORITY_DIRS) {
31346
- const priorityPath = join8(packagePath, priorityDir);
32128
+ const priorityPath = join10(packagePath, priorityDir);
31347
32129
  try {
31348
32130
  const priorityStats = await stat2(priorityPath);
31349
32131
  if (priorityStats.isDirectory()) {
@@ -31393,7 +32175,7 @@ async function findDirRecursively(dir, maxDepth = 2, currentDepth = 0) {
31393
32175
  if (ignoredDirsSet.has(entry) || priorityDirsSet.has(entry)) {
31394
32176
  continue;
31395
32177
  }
31396
- const fullPath = join8(dir, entry);
32178
+ const fullPath = join10(dir, entry);
31397
32179
  try {
31398
32180
  const stats = await stat2(fullPath);
31399
32181
  if (stats.isDirectory()) {
@@ -31432,64 +32214,64 @@ async function getProjectIcon(projectDir) {
31432
32214
 
31433
32215
  // ../server/src/utils/path.ts
31434
32216
  import os5 from "os";
31435
- function expandTilde(path24) {
31436
- if (path24.startsWith("~/")) {
32217
+ function expandTilde(path26) {
32218
+ if (path26.startsWith("~/")) {
31437
32219
  const homeDir3 = process.env.HOME || os5.homedir();
31438
- return path24.replace("~", homeDir3);
32220
+ return path26.replace("~", homeDir3);
31439
32221
  }
31440
- if (path24 === "~") {
32222
+ if (path26 === "~") {
31441
32223
  return process.env.HOME || os5.homedir();
31442
32224
  }
31443
- return path24;
32225
+ return path26;
31444
32226
  }
31445
32227
 
31446
32228
  // ../server/src/server/skills/scanner.ts
31447
- import fs6 from "node:fs/promises";
32229
+ import fs8 from "node:fs/promises";
31448
32230
  import os6 from "node:os";
31449
- import path11 from "node:path";
32231
+ import path13 from "node:path";
31450
32232
  var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
31451
32233
  function homeDir() {
31452
32234
  return process.env.HOME || os6.homedir();
31453
32235
  }
31454
32236
  function codexHomeDir() {
31455
- return process.env.CODEX_HOME || path11.join(homeDir(), ".codex");
32237
+ return process.env.CODEX_HOME || path13.join(homeDir(), ".codex");
31456
32238
  }
31457
32239
  function resolveScopeDir(provider, scope, workspaceRoot) {
31458
32240
  if (scope === "codex-prompts") {
31459
32241
  if (provider !== "codex") {
31460
32242
  throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
31461
32243
  }
31462
- return path11.join(codexHomeDir(), "prompts");
32244
+ return path13.join(codexHomeDir(), "prompts");
31463
32245
  }
31464
32246
  if (scope === "project") {
31465
32247
  if (!workspaceRoot) {
31466
32248
  throw new Error(`workspaceRoot is required for scope "project"`);
31467
32249
  }
31468
32250
  const dotDir = provider === "claude" ? ".claude" : ".codex";
31469
- return path11.join(workspaceRoot, dotDir, "skills");
32251
+ return path13.join(workspaceRoot, dotDir, "skills");
31470
32252
  }
31471
32253
  if (provider === "claude") {
31472
- return path11.join(homeDir(), ".claude", "skills");
32254
+ return path13.join(homeDir(), ".claude", "skills");
31473
32255
  }
31474
- return path11.join(codexHomeDir(), "skills");
32256
+ return path13.join(codexHomeDir(), "skills");
31475
32257
  }
31476
32258
  function allowedRoots(workspaceRoot) {
31477
32259
  const roots = [
31478
- path11.join(homeDir(), ".claude", "skills"),
31479
- path11.join(codexHomeDir(), "skills"),
31480
- path11.join(codexHomeDir(), "prompts")
32260
+ path13.join(homeDir(), ".claude", "skills"),
32261
+ path13.join(codexHomeDir(), "skills"),
32262
+ path13.join(codexHomeDir(), "prompts")
31481
32263
  ];
31482
32264
  if (workspaceRoot) {
31483
- roots.push(path11.join(workspaceRoot, ".claude", "skills"));
31484
- roots.push(path11.join(workspaceRoot, ".codex", "skills"));
32265
+ roots.push(path13.join(workspaceRoot, ".claude", "skills"));
32266
+ roots.push(path13.join(workspaceRoot, ".codex", "skills"));
31485
32267
  }
31486
- return roots.map((r) => path11.resolve(r));
32268
+ return roots.map((r) => path13.resolve(r));
31487
32269
  }
31488
32270
  function isInsideAllowedRoot(absPath, workspaceRoot) {
31489
- const resolved = path11.resolve(absPath);
32271
+ const resolved = path13.resolve(absPath);
31490
32272
  for (const root of allowedRoots(workspaceRoot)) {
31491
- const rel = path11.relative(root, resolved);
31492
- if (rel === "" || !rel.startsWith("..") && !path11.isAbsolute(rel)) {
32273
+ const rel = path13.relative(root, resolved);
32274
+ if (rel === "" || !rel.startsWith("..") && !path13.isAbsolute(rel)) {
31493
32275
  return true;
31494
32276
  }
31495
32277
  }
@@ -31609,7 +32391,7 @@ async function listSkills(args) {
31609
32391
  const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
31610
32392
  let entries;
31611
32393
  try {
31612
- entries = await fs6.readdir(dir, { withFileTypes: true });
32394
+ entries = await fs8.readdir(dir, { withFileTypes: true });
31613
32395
  } catch {
31614
32396
  return [];
31615
32397
  }
@@ -31620,7 +32402,7 @@ async function listSkills(args) {
31620
32402
  if (!entry.name.endsWith(".md")) continue;
31621
32403
  const name = entry.name.slice(0, -".md".length);
31622
32404
  if (!name) continue;
31623
- const fullPath = path11.join(dir, entry.name);
32405
+ const fullPath = path13.join(dir, entry.name);
31624
32406
  const stat5 = await safeStat(fullPath);
31625
32407
  if (!stat5) continue;
31626
32408
  const description = await readDescriptionSafely(fullPath);
@@ -31638,8 +32420,8 @@ async function listSkills(args) {
31638
32420
  } else {
31639
32421
  for (const entry of entries) {
31640
32422
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
31641
- const skillDir = path11.join(dir, entry.name);
31642
- const skillPath = path11.join(skillDir, "SKILL.md");
32423
+ const skillDir = path13.join(dir, entry.name);
32424
+ const skillPath = path13.join(skillDir, "SKILL.md");
31643
32425
  const stat5 = await safeStat(skillPath);
31644
32426
  if (!stat5) continue;
31645
32427
  const description = await readDescriptionSafely(skillPath);
@@ -31660,7 +32442,7 @@ async function listSkills(args) {
31660
32442
  }
31661
32443
  async function safeStat(filePath) {
31662
32444
  try {
31663
- const s = await fs6.stat(filePath);
32445
+ const s = await fs8.stat(filePath);
31664
32446
  return { mtime: s.mtime, size: s.size };
31665
32447
  } catch {
31666
32448
  return null;
@@ -31668,7 +32450,7 @@ async function safeStat(filePath) {
31668
32450
  }
31669
32451
  async function readDescriptionSafely(filePath) {
31670
32452
  try {
31671
- const text = await fs6.readFile(filePath, "utf8");
32453
+ const text = await fs8.readFile(filePath, "utf8");
31672
32454
  return readDescription(text);
31673
32455
  } catch {
31674
32456
  return "";
@@ -31684,11 +32466,11 @@ async function createSkill(args) {
31684
32466
  throw new Error(`Skill name must not contain path separators or "..".`);
31685
32467
  }
31686
32468
  const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
31687
- await fs6.mkdir(dir, { recursive: true });
32469
+ await fs8.mkdir(dir, { recursive: true });
31688
32470
  if (args.scope === "codex-prompts") {
31689
- const filePath2 = path11.join(dir, `${args.name}.md`);
32471
+ const filePath2 = path13.join(dir, `${args.name}.md`);
31690
32472
  try {
31691
- await fs6.access(filePath2);
32473
+ await fs8.access(filePath2);
31692
32474
  throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
31693
32475
  } catch (err) {
31694
32476
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -31699,13 +32481,13 @@ async function createSkill(args) {
31699
32481
  }
31700
32482
  }
31701
32483
  const initial2 = buildStarterPrompt(args.name);
31702
- await fs6.writeFile(filePath2, initial2, "utf8");
32484
+ await fs8.writeFile(filePath2, initial2, "utf8");
31703
32485
  return { path: filePath2 };
31704
32486
  }
31705
- const skillDir = path11.join(dir, args.name);
32487
+ const skillDir = path13.join(dir, args.name);
31706
32488
  let dirExists = false;
31707
32489
  try {
31708
- const stat5 = await fs6.stat(skillDir);
32490
+ const stat5 = await fs8.stat(skillDir);
31709
32491
  dirExists = stat5.isDirectory();
31710
32492
  } catch {
31711
32493
  dirExists = false;
@@ -31713,10 +32495,10 @@ async function createSkill(args) {
31713
32495
  if (dirExists) {
31714
32496
  throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
31715
32497
  }
31716
- await fs6.mkdir(skillDir, { recursive: true });
31717
- const filePath = path11.join(skillDir, "SKILL.md");
32498
+ await fs8.mkdir(skillDir, { recursive: true });
32499
+ const filePath = path13.join(skillDir, "SKILL.md");
31718
32500
  const initial = buildStarterSkill(args.name);
31719
- await fs6.writeFile(filePath, initial, "utf8");
32501
+ await fs8.writeFile(filePath, initial, "utf8");
31720
32502
  return { path: filePath };
31721
32503
  }
31722
32504
  function buildStarterSkill(name) {
@@ -31743,7 +32525,7 @@ Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expa
31743
32525
  `;
31744
32526
  }
31745
32527
  async function writeSkillFrontmatter(args, workspaceRoot) {
31746
- if (!path11.isAbsolute(args.path)) {
32528
+ if (!path13.isAbsolute(args.path)) {
31747
32529
  throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
31748
32530
  }
31749
32531
  if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
@@ -31751,7 +32533,7 @@ async function writeSkillFrontmatter(args, workspaceRoot) {
31751
32533
  }
31752
32534
  let original;
31753
32535
  try {
31754
- original = await fs6.readFile(args.path, "utf8");
32536
+ original = await fs8.readFile(args.path, "utf8");
31755
32537
  } catch (err) {
31756
32538
  throw new Error(
31757
32539
  `Failed to read skill file: ${err instanceof Error ? err.message : String(err)}`
@@ -31764,12 +32546,12 @@ async function writeSkillFrontmatter(args, workspaceRoot) {
31764
32546
  ${parsed.body}` : `${nextFrontmatter}
31765
32547
 
31766
32548
  ${original}`;
31767
- await fs6.writeFile(args.path, nextContent, "utf8");
32549
+ await fs8.writeFile(args.path, nextContent, "utf8");
31768
32550
  }
31769
32551
 
31770
32552
  // ../server/src/utils/directory-suggestions.ts
31771
32553
  import { readdir as readdir2, realpath, stat as stat3 } from "node:fs/promises";
31772
- import path12 from "node:path";
32554
+ import path14 from "node:path";
31773
32555
  var DEFAULT_LIMIT = 30;
31774
32556
  var MAX_LIMIT = 100;
31775
32557
  var DEFAULT_MAX_DEPTH = 6;
@@ -31855,7 +32637,7 @@ function normalizeLimit(limit) {
31855
32637
  return Math.max(1, Math.min(MAX_LIMIT, bounded));
31856
32638
  }
31857
32639
  async function searchWithinParentDirectory(input) {
31858
- const parentPath = path12.resolve(input.homeRoot, input.parentPart || ".");
32640
+ const parentPath = path14.resolve(input.homeRoot, input.parentPart || ".");
31859
32641
  const parentRoot = await resolveDirectory(parentPath);
31860
32642
  if (!parentRoot || !isPathInsideRoot(input.homeRoot, parentRoot)) {
31861
32643
  return [];
@@ -31920,7 +32702,7 @@ async function searchAcrossHomeTree(input) {
31920
32702
  return dedupeAndSort(ranked).slice(0, input.limit);
31921
32703
  }
31922
32704
  async function searchWorkspaceWithinParentDirectory(input) {
31923
- const parentPath = path12.resolve(input.workspaceRoot, input.parentPart || ".");
32705
+ const parentPath = path14.resolve(input.workspaceRoot, input.parentPart || ".");
31924
32706
  const parentRoot = await resolveDirectory(parentPath);
31925
32707
  if (!parentRoot || !isPathInsideRoot(input.workspaceRoot, parentRoot)) {
31926
32708
  return [];
@@ -32166,15 +32948,15 @@ function findSegmentMatchIndex(segments, predicate) {
32166
32948
  return -1;
32167
32949
  }
32168
32950
  function normalizeRelativePath2(homeRoot, absolutePath) {
32169
- const relative = path12.relative(homeRoot, absolutePath);
32951
+ const relative = path14.relative(homeRoot, absolutePath);
32170
32952
  if (!relative) {
32171
32953
  return ".";
32172
32954
  }
32173
- return relative.split(path12.sep).join("/");
32955
+ return relative.split(path14.sep).join("/");
32174
32956
  }
32175
32957
  function isPathInsideRoot(root, target) {
32176
- const relative = path12.relative(root, target);
32177
- return relative === "" || !relative.startsWith("..") && !path12.isAbsolute(relative);
32958
+ const relative = path14.relative(root, target);
32959
+ return relative === "" || !relative.startsWith("..") && !path14.isAbsolute(relative);
32178
32960
  }
32179
32961
  function normalizeQueryParts(query2, homeRoot) {
32180
32962
  const typedQuery = query2.trim().replace(/\\/g, "/");
@@ -32190,9 +32972,9 @@ function normalizeQueryParts(query2, homeRoot) {
32190
32972
  normalized = normalized.slice(1);
32191
32973
  }
32192
32974
  }
32193
- if (path12.isAbsolute(normalized)) {
32975
+ if (path14.isAbsolute(normalized)) {
32194
32976
  isRooted = true;
32195
- const absolute = path12.resolve(normalized);
32977
+ const absolute = path14.resolve(normalized);
32196
32978
  if (!isPathInsideRoot(homeRoot, absolute)) {
32197
32979
  return null;
32198
32980
  }
@@ -32231,8 +33013,8 @@ function normalizeQueryParts(query2, homeRoot) {
32231
33013
  }
32232
33014
  function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
32233
33015
  let normalized = query2.trim().replace(/\\/g, "/");
32234
- if (path12.isAbsolute(normalized)) {
32235
- const absolute = path12.resolve(normalized);
33016
+ if (path14.isAbsolute(normalized)) {
33017
+ const absolute = path14.resolve(normalized);
32236
33018
  if (!isPathInsideRoot(workspaceRoot, absolute)) {
32237
33019
  return null;
32238
33020
  }
@@ -32258,7 +33040,7 @@ function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
32258
33040
  }
32259
33041
  async function resolveDirectory(inputPath) {
32260
33042
  try {
32261
- const resolved = await realpath(path12.resolve(inputPath));
33043
+ const resolved = await realpath(path14.resolve(inputPath));
32262
33044
  const stats = await stat3(resolved);
32263
33045
  if (!stats.isDirectory()) {
32264
33046
  return null;
@@ -32285,7 +33067,7 @@ async function listChildDirectories(input) {
32285
33067
  if (!dirent.isDirectory() && !dirent.isSymbolicLink()) {
32286
33068
  continue;
32287
33069
  }
32288
- const candidatePath = path12.join(input.directory, dirent.name);
33070
+ const candidatePath = path14.join(input.directory, dirent.name);
32289
33071
  const absolutePath = await resolveDirectoryCandidate({
32290
33072
  candidatePath,
32291
33073
  dirent,
@@ -32322,7 +33104,7 @@ async function listWorkspaceChildEntries(input) {
32322
33104
  if (isIgnoredWorkspaceDirectoryName(dirent.name)) {
32323
33105
  continue;
32324
33106
  }
32325
- const candidatePath = path12.join(input.directory, dirent.name);
33107
+ const candidatePath = path14.join(input.directory, dirent.name);
32326
33108
  const entry = await resolveWorkspaceCandidate({
32327
33109
  candidatePath,
32328
33110
  dirent,
@@ -32345,7 +33127,7 @@ async function listWorkspaceChildEntries(input) {
32345
33127
  }
32346
33128
  async function resolveDirectoryCandidate(input) {
32347
33129
  if (input.dirent.isDirectory()) {
32348
- const resolved2 = path12.resolve(input.candidatePath);
33130
+ const resolved2 = path14.resolve(input.candidatePath);
32349
33131
  return isPathInsideRoot(input.homeRoot, resolved2) ? resolved2 : null;
32350
33132
  }
32351
33133
  const resolved = await resolveDirectory(input.candidatePath);
@@ -32356,14 +33138,14 @@ async function resolveDirectoryCandidate(input) {
32356
33138
  }
32357
33139
  async function resolveWorkspaceCandidate(input) {
32358
33140
  if (input.dirent.isDirectory()) {
32359
- const resolved = path12.resolve(input.candidatePath);
33141
+ const resolved = path14.resolve(input.candidatePath);
32360
33142
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
32361
33143
  return null;
32362
33144
  }
32363
33145
  return { absolutePath: resolved, kind: "directory" };
32364
33146
  }
32365
33147
  if (input.dirent.isFile()) {
32366
- const resolved = path12.resolve(input.candidatePath);
33148
+ const resolved = path14.resolve(input.candidatePath);
32367
33149
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
32368
33150
  return null;
32369
33151
  }
@@ -32443,7 +33225,7 @@ function pruneWorkspaceEntryListCache() {
32443
33225
  // ../server/src/utils/directory-listing.ts
32444
33226
  import { readdir as readdir3, stat as stat4, realpath as realpath2 } from "node:fs/promises";
32445
33227
  import { homedir as homedir3 } from "node:os";
32446
- import path13 from "node:path";
33228
+ import path15 from "node:path";
32447
33229
  var DEFAULT_LIMIT2 = 500;
32448
33230
  async function listDirectoryContents(options) {
32449
33231
  const includeFiles = options.includeFiles ?? false;
@@ -32454,7 +33236,7 @@ async function listDirectoryContents(options) {
32454
33236
  const collected = [];
32455
33237
  for (const dirent of dirents) {
32456
33238
  if (!includeHidden && dirent.name.startsWith(".")) continue;
32457
- const childPath = path13.join(resolvedPath, dirent.name);
33239
+ const childPath = path15.join(resolvedPath, dirent.name);
32458
33240
  const kind = await classifyEntry(dirent, childPath);
32459
33241
  if (!kind) continue;
32460
33242
  if (kind === "file" && !includeFiles) continue;
@@ -32462,7 +33244,7 @@ async function listDirectoryContents(options) {
32462
33244
  if (collected.length >= limit) break;
32463
33245
  }
32464
33246
  collected.sort(compareEntries);
32465
- const parent = path13.dirname(resolvedPath);
33247
+ const parent = path15.dirname(resolvedPath);
32466
33248
  return {
32467
33249
  path: resolvedPath,
32468
33250
  parent: parent === resolvedPath ? null : parent,
@@ -32473,12 +33255,12 @@ async function resolveAbsolutePath(rawPath) {
32473
33255
  const home = process.env.HOME ?? homedir3();
32474
33256
  const trimmed = rawPath.trim();
32475
33257
  if (trimmed === "" || trimmed === "~") {
32476
- return path13.resolve(home);
33258
+ return path15.resolve(home);
32477
33259
  }
32478
33260
  if (trimmed.startsWith("~/")) {
32479
- return path13.resolve(home, trimmed.slice(2));
33261
+ return path15.resolve(home, trimmed.slice(2));
32480
33262
  }
32481
- if (!path13.isAbsolute(trimmed)) {
33263
+ if (!path15.isAbsolute(trimmed)) {
32482
33264
  throw new Error(
32483
33265
  `list_directory requires an absolute path, an empty string, or a "~"-prefixed path; got ${JSON.stringify(rawPath)}`
32484
33266
  );
@@ -32486,7 +33268,7 @@ async function resolveAbsolutePath(rawPath) {
32486
33268
  try {
32487
33269
  return await realpath2(trimmed);
32488
33270
  } catch {
32489
- return path13.resolve(trimmed);
33271
+ return path15.resolve(trimmed);
32490
33272
  }
32491
33273
  }
32492
33274
  async function classifyEntry(dirent, fullPath) {
@@ -32619,9 +33401,9 @@ function buildChatMentionNotification(input) {
32619
33401
  }
32620
33402
 
32621
33403
  // ../server/src/server/roles/scanner.ts
32622
- import fs7 from "node:fs/promises";
33404
+ import fs9 from "node:fs/promises";
32623
33405
  import os7 from "node:os";
32624
- import path14 from "node:path";
33406
+ import path16 from "node:path";
32625
33407
  var NAME_REGEX2 = /^[a-z0-9][a-z0-9._-]*$/i;
32626
33408
  function homeDir2() {
32627
33409
  return process.env.HOME || os7.homedir();
@@ -32631,22 +33413,22 @@ function resolveScopeDir2(scope, workspaceRoot) {
32631
33413
  if (!workspaceRoot) {
32632
33414
  throw new Error('workspaceRoot is required for scope "project"');
32633
33415
  }
32634
- return path14.join(workspaceRoot, ".roles");
33416
+ return path16.join(workspaceRoot, ".roles");
32635
33417
  }
32636
- return path14.join(homeDir2(), ".appostle", ".roles");
33418
+ return path16.join(homeDir2(), ".appostle", ".roles");
32637
33419
  }
32638
33420
  function allowedRoots2(workspaceRoot) {
32639
- const roots = [path14.join(homeDir2(), ".appostle", ".roles")];
33421
+ const roots = [path16.join(homeDir2(), ".appostle", ".roles")];
32640
33422
  if (workspaceRoot) {
32641
- roots.push(path14.join(workspaceRoot, ".roles"));
33423
+ roots.push(path16.join(workspaceRoot, ".roles"));
32642
33424
  }
32643
- return roots.map((r) => path14.resolve(r));
33425
+ return roots.map((r) => path16.resolve(r));
32644
33426
  }
32645
33427
  function isInsideAllowedRoot2(absPath, workspaceRoot) {
32646
- const resolved = path14.resolve(absPath);
33428
+ const resolved = path16.resolve(absPath);
32647
33429
  for (const root of allowedRoots2(workspaceRoot)) {
32648
- const rel = path14.relative(root, resolved);
32649
- if (rel === "" || !rel.startsWith("..") && !path14.isAbsolute(rel)) {
33430
+ const rel = path16.relative(root, resolved);
33431
+ if (rel === "" || !rel.startsWith("..") && !path16.isAbsolute(rel)) {
32650
33432
  return true;
32651
33433
  }
32652
33434
  }
@@ -32834,7 +33616,7 @@ function rewriteFrontmatter2(originalLines, next) {
32834
33616
  async function readRolesFromDir(scope, dir, category) {
32835
33617
  let entries;
32836
33618
  try {
32837
- entries = await fs7.readdir(dir, { withFileTypes: true });
33619
+ entries = await fs9.readdir(dir, { withFileTypes: true });
32838
33620
  } catch {
32839
33621
  return [];
32840
33622
  }
@@ -32844,17 +33626,17 @@ async function readRolesFromDir(scope, dir, category) {
32844
33626
  if (!entry.name.endsWith(".md")) continue;
32845
33627
  const name = entry.name.slice(0, -".md".length);
32846
33628
  if (!name || !NAME_REGEX2.test(name)) continue;
32847
- const fullPath = path14.join(dir, entry.name);
33629
+ const fullPath = path16.join(dir, entry.name);
32848
33630
  let stat5;
32849
33631
  try {
32850
- const s = await fs7.stat(fullPath);
33632
+ const s = await fs9.stat(fullPath);
32851
33633
  stat5 = { mtime: s.mtime, size: s.size };
32852
33634
  } catch {
32853
33635
  continue;
32854
33636
  }
32855
33637
  let parsed;
32856
33638
  try {
32857
- const text = await fs7.readFile(fullPath, "utf8");
33639
+ const text = await fs9.readFile(fullPath, "utf8");
32858
33640
  parsed = parseFrontmatter(text);
32859
33641
  } catch {
32860
33642
  parsed = {
@@ -32888,13 +33670,13 @@ async function readRolesFromDir(scope, dir, category) {
32888
33670
  async function readRolesFromScopeDir(scope, scopeDir) {
32889
33671
  let topEntries;
32890
33672
  try {
32891
- topEntries = await fs7.readdir(scopeDir, { withFileTypes: true });
33673
+ topEntries = await fs9.readdir(scopeDir, { withFileTypes: true });
32892
33674
  } catch {
32893
33675
  return [];
32894
33676
  }
32895
33677
  const flat = await readRolesFromDir(scope, scopeDir, null);
32896
33678
  const categoryResults = await Promise.all(
32897
- topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path14.join(scopeDir, e.name), e.name))
33679
+ topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path16.join(scopeDir, e.name), e.name))
32898
33680
  );
32899
33681
  const all = [...flat, ...categoryResults.flat()];
32900
33682
  all.sort((a, b) => {
@@ -32934,11 +33716,11 @@ async function createRole(args) {
32934
33716
  throw new Error(`Role name must not contain path separators or "..".`);
32935
33717
  }
32936
33718
  const scopeDir = resolveScopeDir2(args.scope, args.workspaceRoot);
32937
- const dir = args.category ? path14.join(scopeDir, args.category) : scopeDir;
32938
- await fs7.mkdir(dir, { recursive: true });
32939
- const filePath = path14.join(dir, `${args.name}.md`);
33719
+ const dir = args.category ? path16.join(scopeDir, args.category) : scopeDir;
33720
+ await fs9.mkdir(dir, { recursive: true });
33721
+ const filePath = path16.join(dir, `${args.name}.md`);
32940
33722
  try {
32941
- await fs7.access(filePath);
33723
+ await fs9.access(filePath);
32942
33724
  throw new Error(`A role named "${args.name}" already exists at ${filePath}`);
32943
33725
  } catch (err) {
32944
33726
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -32949,7 +33731,7 @@ async function createRole(args) {
32949
33731
  }
32950
33732
  }
32951
33733
  const initial = buildStarterRole(args.name);
32952
- await fs7.writeFile(filePath, initial, "utf8");
33734
+ await fs9.writeFile(filePath, initial, "utf8");
32953
33735
  return { path: filePath };
32954
33736
  }
32955
33737
  function buildStarterRole(name) {
@@ -32970,7 +33752,7 @@ the role is invoked.
32970
33752
  `;
32971
33753
  }
32972
33754
  async function writeRoleFrontmatter(args, workspaceRoot) {
32973
- if (!path14.isAbsolute(args.path)) {
33755
+ if (!path16.isAbsolute(args.path)) {
32974
33756
  throw new Error(`writeRoleFrontmatter expects an absolute path; got "${args.path}"`);
32975
33757
  }
32976
33758
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
@@ -32978,7 +33760,7 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
32978
33760
  }
32979
33761
  let original;
32980
33762
  try {
32981
- original = await fs7.readFile(args.path, "utf8");
33763
+ original = await fs9.readFile(args.path, "utf8");
32982
33764
  } catch (err) {
32983
33765
  throw new Error(
32984
33766
  `Failed to read role file: ${err instanceof Error ? err.message : String(err)}`
@@ -32991,23 +33773,23 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
32991
33773
  ${parsed.body}` : `${nextFrontmatter}
32992
33774
 
32993
33775
  ${original}`;
32994
- await fs7.writeFile(args.path, nextContent, "utf8");
33776
+ await fs9.writeFile(args.path, nextContent, "utf8");
32995
33777
  }
32996
33778
  async function moveRole(args, workspaceRoot) {
32997
- if (!path14.isAbsolute(args.path)) {
33779
+ if (!path16.isAbsolute(args.path)) {
32998
33780
  throw new Error(`moveRole expects an absolute path; got "${args.path}"`);
32999
33781
  }
33000
33782
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
33001
33783
  throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
33002
33784
  }
33003
- const oldDir = path14.dirname(args.path);
33004
- const oldFilename = path14.basename(args.path, ".md");
33785
+ const oldDir = path16.dirname(args.path);
33786
+ const oldFilename = path16.basename(args.path, ".md");
33005
33787
  const roots = allowedRoots2(workspaceRoot);
33006
- const rolesRoot = roots.find((r) => path14.resolve(args.path).startsWith(r));
33788
+ const rolesRoot = roots.find((r) => path16.resolve(args.path).startsWith(r));
33007
33789
  if (!rolesRoot) {
33008
33790
  throw new Error(`Cannot determine roles root for "${args.path}"`);
33009
33791
  }
33010
- const relFromRoot = path14.relative(rolesRoot, path14.dirname(args.path));
33792
+ const relFromRoot = path16.relative(rolesRoot, path16.dirname(args.path));
33011
33793
  const currentCategory = relFromRoot && relFromRoot !== "." ? relFromRoot : "";
33012
33794
  const newName = args.newName ?? oldFilename;
33013
33795
  const newCategory = args.newCategory !== void 0 ? args.newCategory : currentCategory;
@@ -33017,19 +33799,19 @@ async function moveRole(args, workspaceRoot) {
33017
33799
  if (newCategory && !NAME_REGEX2.test(newCategory)) {
33018
33800
  throw new Error(`Invalid category name: "${newCategory}"`);
33019
33801
  }
33020
- const newDir = newCategory ? path14.join(rolesRoot, newCategory) : rolesRoot;
33021
- const newPath = path14.join(newDir, `${newName}.md`);
33022
- if (path14.resolve(newPath) === path14.resolve(args.path)) {
33802
+ const newDir = newCategory ? path16.join(rolesRoot, newCategory) : rolesRoot;
33803
+ const newPath = path16.join(newDir, `${newName}.md`);
33804
+ if (path16.resolve(newPath) === path16.resolve(args.path)) {
33023
33805
  return { path: args.path };
33024
33806
  }
33025
- await fs7.mkdir(newDir, { recursive: true });
33807
+ await fs9.mkdir(newDir, { recursive: true });
33026
33808
  try {
33027
- await fs7.access(newPath);
33809
+ await fs9.access(newPath);
33028
33810
  throw new Error(`A role already exists at "${newPath}"`);
33029
33811
  } catch (err) {
33030
33812
  if (err.code !== "ENOENT") throw err;
33031
33813
  }
33032
- await fs7.rename(args.path, newPath);
33814
+ await fs9.rename(args.path, newPath);
33033
33815
  const frontmatterPatch = {};
33034
33816
  if (newName !== oldFilename) {
33035
33817
  frontmatterPatch.description = void 0;
@@ -33045,9 +33827,9 @@ async function moveRole(args, workspaceRoot) {
33045
33827
  );
33046
33828
  if (oldDir !== rolesRoot) {
33047
33829
  try {
33048
- const remaining = await fs7.readdir(oldDir);
33830
+ const remaining = await fs9.readdir(oldDir);
33049
33831
  if (remaining.length === 0) {
33050
- await fs7.rmdir(oldDir);
33832
+ await fs9.rmdir(oldDir);
33051
33833
  }
33052
33834
  } catch {
33053
33835
  }
@@ -33056,30 +33838,30 @@ async function moveRole(args, workspaceRoot) {
33056
33838
  }
33057
33839
 
33058
33840
  // ../server/src/server/brands/scanner.ts
33059
- import fs8 from "node:fs/promises";
33060
- import path15 from "node:path";
33841
+ import fs10 from "node:fs/promises";
33842
+ import path17 from "node:path";
33061
33843
  var NAME_REGEX3 = /^[a-z0-9][a-z0-9._-]*$/i;
33062
33844
  function resolveScopeDir3(scope, workspaceRoot) {
33063
33845
  if (scope === "project") {
33064
33846
  if (!workspaceRoot) {
33065
33847
  throw new Error('workspaceRoot is required for scope "project"');
33066
33848
  }
33067
- return path15.join(workspaceRoot, ".brand");
33849
+ return path17.join(workspaceRoot, ".brand");
33068
33850
  }
33069
33851
  throw new Error(`Unknown scope: ${scope}`);
33070
33852
  }
33071
33853
  function allowedRoots3(workspaceRoot) {
33072
33854
  const roots = [];
33073
33855
  if (workspaceRoot) {
33074
- roots.push(path15.join(workspaceRoot, ".brand"));
33856
+ roots.push(path17.join(workspaceRoot, ".brand"));
33075
33857
  }
33076
- return roots.map((r) => path15.resolve(r));
33858
+ return roots.map((r) => path17.resolve(r));
33077
33859
  }
33078
33860
  function isInsideAllowedRoot3(absPath, workspaceRoot) {
33079
- const resolved = path15.resolve(absPath);
33861
+ const resolved = path17.resolve(absPath);
33080
33862
  for (const root of allowedRoots3(workspaceRoot)) {
33081
- const rel = path15.relative(root, resolved);
33082
- if (rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel)) {
33863
+ const rel = path17.relative(root, resolved);
33864
+ if (rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel)) {
33083
33865
  return true;
33084
33866
  }
33085
33867
  }
@@ -33296,7 +34078,7 @@ function rewriteFrontmatter3(originalLines, next) {
33296
34078
  async function readBrandsFromDir(scope, dir) {
33297
34079
  let entries;
33298
34080
  try {
33299
- entries = await fs8.readdir(dir, { withFileTypes: true });
34081
+ entries = await fs10.readdir(dir, { withFileTypes: true });
33300
34082
  } catch {
33301
34083
  return [];
33302
34084
  }
@@ -33306,17 +34088,17 @@ async function readBrandsFromDir(scope, dir) {
33306
34088
  if (!entry.name.endsWith(".md")) continue;
33307
34089
  const name = entry.name.slice(0, -".md".length);
33308
34090
  if (!name || !NAME_REGEX3.test(name)) continue;
33309
- const fullPath = path15.join(dir, entry.name);
34091
+ const fullPath = path17.join(dir, entry.name);
33310
34092
  let stat5;
33311
34093
  try {
33312
- const s = await fs8.stat(fullPath);
34094
+ const s = await fs10.stat(fullPath);
33313
34095
  stat5 = { mtime: s.mtime, size: s.size };
33314
34096
  } catch {
33315
34097
  continue;
33316
34098
  }
33317
34099
  let parsed;
33318
34100
  try {
33319
- const text = await fs8.readFile(fullPath, "utf8");
34101
+ const text = await fs10.readFile(fullPath, "utf8");
33320
34102
  const { rawFrontmatterLines, hadFrontmatter } = parseBrandFile(text);
33321
34103
  parsed = hadFrontmatter ? parseFrontmatter2(rawFrontmatterLines) : { description: "", tags: [], variables: [] };
33322
34104
  } catch {
@@ -33350,10 +34132,10 @@ async function createBrand(args) {
33350
34132
  throw new Error(`Brand name must not contain path separators or "..".`);
33351
34133
  }
33352
34134
  const dir = resolveScopeDir3("project", args.workspaceRoot);
33353
- await fs8.mkdir(dir, { recursive: true });
33354
- const filePath = path15.join(dir, `${args.name}.md`);
34135
+ await fs10.mkdir(dir, { recursive: true });
34136
+ const filePath = path17.join(dir, `${args.name}.md`);
33355
34137
  try {
33356
- await fs8.access(filePath);
34138
+ await fs10.access(filePath);
33357
34139
  throw new Error(`A brand named "${args.name}" already exists at ${filePath}`);
33358
34140
  } catch (err) {
33359
34141
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -33363,7 +34145,7 @@ async function createBrand(args) {
33363
34145
  throw err;
33364
34146
  }
33365
34147
  }
33366
- await fs8.writeFile(filePath, buildStarterBrand(args.name), "utf8");
34148
+ await fs10.writeFile(filePath, buildStarterBrand(args.name), "utf8");
33367
34149
  return { path: filePath };
33368
34150
  }
33369
34151
  function buildStarterBrand(name) {
@@ -33408,7 +34190,7 @@ async function copyBrandAsset(args) {
33408
34190
  if (!args.workspaceRoot) {
33409
34191
  throw new Error("workspaceRoot is required to copy a brand asset");
33410
34192
  }
33411
- if (!args.sourcePath || !path15.isAbsolute(args.sourcePath)) {
34193
+ if (!args.sourcePath || !path17.isAbsolute(args.sourcePath)) {
33412
34194
  throw new Error(`copyBrandAsset expects an absolute sourcePath; got "${args.sourcePath}"`);
33413
34195
  }
33414
34196
  if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
@@ -33416,20 +34198,20 @@ async function copyBrandAsset(args) {
33416
34198
  `Invalid targetName "${args.targetName}". Use letters, digits, dot, underscore, dash.`
33417
34199
  );
33418
34200
  }
33419
- const stats = await fs8.stat(args.sourcePath);
34201
+ const stats = await fs10.stat(args.sourcePath);
33420
34202
  if (!stats.isFile()) {
33421
34203
  throw new Error(`Source path is not a regular file: ${args.sourcePath}`);
33422
34204
  }
33423
- const ext = path15.extname(args.sourcePath).toLowerCase();
34205
+ const ext = path17.extname(args.sourcePath).toLowerCase();
33424
34206
  const fileName = `${args.targetName}${ext}`;
33425
- const assetsDir = path15.join(args.workspaceRoot, ".brand", "assets");
33426
- const destAbs = path15.resolve(assetsDir, fileName);
33427
- const rel = path15.relative(path15.resolve(assetsDir), destAbs);
33428
- if (rel.startsWith("..") || path15.isAbsolute(rel)) {
34207
+ const assetsDir = path17.join(args.workspaceRoot, ".brand", "assets");
34208
+ const destAbs = path17.resolve(assetsDir, fileName);
34209
+ const rel = path17.relative(path17.resolve(assetsDir), destAbs);
34210
+ if (rel.startsWith("..") || path17.isAbsolute(rel)) {
33429
34211
  throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
33430
34212
  }
33431
- await fs8.mkdir(assetsDir, { recursive: true });
33432
- await fs8.copyFile(args.sourcePath, destAbs);
34213
+ await fs10.mkdir(assetsDir, { recursive: true });
34214
+ await fs10.copyFile(args.sourcePath, destAbs);
33433
34215
  return {
33434
34216
  relativePath: `assets/${fileName}`,
33435
34217
  absolutePath: destAbs
@@ -33447,13 +34229,13 @@ async function uploadBrandAsset(args) {
33447
34229
  if (!args.dataBase64 || args.dataBase64.trim().length === 0) {
33448
34230
  throw new Error("No file data provided for brand asset upload");
33449
34231
  }
33450
- const extFromSource = args.sourceName ? path15.extname(args.sourceName).toLowerCase() : "";
34232
+ const extFromSource = args.sourceName ? path17.extname(args.sourceName).toLowerCase() : "";
33451
34233
  const ext = extFromSource || ".png";
33452
34234
  const fileName = `${args.targetName}${ext}`;
33453
- const assetsDir = path15.join(args.workspaceRoot, ".brand", "assets");
33454
- const destAbs = path15.resolve(assetsDir, fileName);
33455
- const rel = path15.relative(path15.resolve(assetsDir), destAbs);
33456
- if (rel.startsWith("..") || path15.isAbsolute(rel)) {
34235
+ const assetsDir = path17.join(args.workspaceRoot, ".brand", "assets");
34236
+ const destAbs = path17.resolve(assetsDir, fileName);
34237
+ const rel = path17.relative(path17.resolve(assetsDir), destAbs);
34238
+ if (rel.startsWith("..") || path17.isAbsolute(rel)) {
33457
34239
  throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
33458
34240
  }
33459
34241
  let data;
@@ -33465,15 +34247,15 @@ async function uploadBrandAsset(args) {
33465
34247
  if (data.length === 0) {
33466
34248
  throw new Error("Uploaded brand asset is empty");
33467
34249
  }
33468
- await fs8.mkdir(assetsDir, { recursive: true });
33469
- await fs8.writeFile(destAbs, data);
34250
+ await fs10.mkdir(assetsDir, { recursive: true });
34251
+ await fs10.writeFile(destAbs, data);
33470
34252
  return {
33471
34253
  relativePath: `assets/${fileName}`,
33472
34254
  absolutePath: destAbs
33473
34255
  };
33474
34256
  }
33475
34257
  async function writeBrandFrontmatter(args, workspaceRoot) {
33476
- if (!path15.isAbsolute(args.path)) {
34258
+ if (!path17.isAbsolute(args.path)) {
33477
34259
  throw new Error(`writeBrandFrontmatter expects an absolute path; got "${args.path}"`);
33478
34260
  }
33479
34261
  if (!isInsideAllowedRoot3(args.path, workspaceRoot)) {
@@ -33481,7 +34263,7 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
33481
34263
  }
33482
34264
  let original;
33483
34265
  try {
33484
- original = await fs8.readFile(args.path, "utf8");
34266
+ original = await fs10.readFile(args.path, "utf8");
33485
34267
  } catch (err) {
33486
34268
  throw new Error(
33487
34269
  `Failed to read brand file: ${err instanceof Error ? err.message : String(err)}`
@@ -33494,14 +34276,14 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
33494
34276
  ${parsed.body}` : `${nextFrontmatter}
33495
34277
 
33496
34278
  ${original}`;
33497
- await fs8.writeFile(args.path, nextContent, "utf8");
34279
+ await fs10.writeFile(args.path, nextContent, "utf8");
33498
34280
  }
33499
34281
 
33500
34282
  // ../server/src/services/oauth-service.ts
33501
- import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
34283
+ import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
33502
34284
  import { mkdir as mkdir4, readFile as readFile3, rename, unlink, writeFile as writeFile4 } from "node:fs/promises";
33503
34285
  import { homedir as homedir4 } from "node:os";
33504
- import { dirname as dirname4, join as join9 } from "node:path";
34286
+ import { dirname as dirname4, join as join11 } from "node:path";
33505
34287
  import * as YAML from "yaml";
33506
34288
  var GITLAB_CLIENT_ID = "7783dfb277ccf3dbce35a9d54c3c0aec24ed2006cb2d171e0749fd8444ed4f7c";
33507
34289
  var GITLAB_AUTHORIZE_URL = "https://gitlab.com/oauth/authorize";
@@ -33523,7 +34305,7 @@ var OAuthError = class extends Error {
33523
34305
  function createOAuthService(options) {
33524
34306
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
33525
34307
  const now = options.now ?? Date.now;
33526
- const credentialsPath = join9(options.appostleHome, "credentials.json");
34308
+ const credentialsPath = join11(options.appostleHome, "credentials.json");
33527
34309
  const glabConfigPath = options.glabConfigPath ?? defaultGlabConfigPath();
33528
34310
  const oauthLogger = options.logger.child({ module: "oauth-service" });
33529
34311
  const pending = /* @__PURE__ */ new Map();
@@ -33645,7 +34427,7 @@ function generateCodeVerifier() {
33645
34427
  return base64url(randomBytes2(32));
33646
34428
  }
33647
34429
  function computeCodeChallenge(verifier) {
33648
- return base64url(createHash3("sha256").update(verifier).digest());
34430
+ return base64url(createHash4("sha256").update(verifier).digest());
33649
34431
  }
33650
34432
  function generateState() {
33651
34433
  return base64url(randomBytes2(24));
@@ -33742,80 +34524,80 @@ async function fetchGitLabUsername(fetchImpl, accessToken) {
33742
34524
  return null;
33743
34525
  }
33744
34526
  }
33745
- async function readCredential(path24, log2) {
34527
+ async function readCredential(path26, log2) {
33746
34528
  try {
33747
- const raw = await readFile3(path24, "utf8");
34529
+ const raw = await readFile3(path26, "utf8");
33748
34530
  const parsed = JSON.parse(raw);
33749
34531
  return parsed.gitlab ?? null;
33750
34532
  } catch (error) {
33751
34533
  if (isNotFound(error)) return null;
33752
- log2.warn({ err: error, path: path24 }, "oauth.credentials.read_failed");
34534
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.read_failed");
33753
34535
  return null;
33754
34536
  }
33755
34537
  }
33756
- async function persistCredential(path24, credential, log2) {
33757
- await mkdir4(dirname4(path24), { recursive: true });
34538
+ async function persistCredential(path26, credential, log2) {
34539
+ await mkdir4(dirname4(path26), { recursive: true });
33758
34540
  let current = {};
33759
34541
  try {
33760
- const raw = await readFile3(path24, "utf8");
34542
+ const raw = await readFile3(path26, "utf8");
33761
34543
  current = JSON.parse(raw);
33762
34544
  } catch (error) {
33763
34545
  if (!isNotFound(error)) {
33764
- log2.warn({ err: error, path: path24 }, "oauth.credentials.read_failed_overwriting");
34546
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.read_failed_overwriting");
33765
34547
  }
33766
34548
  }
33767
34549
  const next = { ...current, gitlab: credential };
33768
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34550
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33769
34551
  await writeFile4(tmpPath, JSON.stringify(next, null, 2), { mode: 384 });
33770
- await rename(tmpPath, path24);
34552
+ await rename(tmpPath, path26);
33771
34553
  }
33772
- async function deleteCredential(path24, log2) {
34554
+ async function deleteCredential(path26, log2) {
33773
34555
  let current = {};
33774
34556
  try {
33775
- const raw = await readFile3(path24, "utf8");
34557
+ const raw = await readFile3(path26, "utf8");
33776
34558
  current = JSON.parse(raw);
33777
34559
  } catch (error) {
33778
34560
  if (isNotFound(error)) return;
33779
- log2.warn({ err: error, path: path24 }, "oauth.credentials.delete_read_failed");
34561
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.delete_read_failed");
33780
34562
  return;
33781
34563
  }
33782
34564
  delete current.gitlab;
33783
34565
  if (Object.keys(current).length === 0) {
33784
34566
  try {
33785
- await unlink(path24);
34567
+ await unlink(path26);
33786
34568
  } catch (error) {
33787
34569
  if (!isNotFound(error)) {
33788
- log2.warn({ err: error, path: path24 }, "oauth.credentials.unlink_failed");
34570
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.unlink_failed");
33789
34571
  }
33790
34572
  }
33791
34573
  return;
33792
34574
  }
33793
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34575
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33794
34576
  await writeFile4(tmpPath, JSON.stringify(current, null, 2), { mode: 384 });
33795
- await rename(tmpPath, path24);
34577
+ await rename(tmpPath, path26);
33796
34578
  }
33797
34579
  function defaultGlabConfigPath() {
33798
34580
  const home = homedir4();
33799
34581
  if (process.platform === "darwin") {
33800
- return join9(home, "Library", "Application Support", "glab-cli", "config.yml");
34582
+ return join11(home, "Library", "Application Support", "glab-cli", "config.yml");
33801
34583
  }
33802
34584
  if (process.platform === "win32") {
33803
34585
  const appData = process.env.APPDATA;
33804
- if (appData) return join9(appData, "glab-cli", "config.yml");
33805
- return join9(home, "AppData", "Roaming", "glab-cli", "config.yml");
34586
+ if (appData) return join11(appData, "glab-cli", "config.yml");
34587
+ return join11(home, "AppData", "Roaming", "glab-cli", "config.yml");
33806
34588
  }
33807
34589
  const xdg = process.env.XDG_CONFIG_HOME;
33808
- return join9(xdg && xdg.length > 0 ? xdg : join9(home, ".config"), "glab-cli", "config.yml");
34590
+ return join11(xdg && xdg.length > 0 ? xdg : join11(home, ".config"), "glab-cli", "config.yml");
33809
34591
  }
33810
- async function writeGlabConfig(path24, credential, log2) {
33811
- await mkdir4(dirname4(path24), { recursive: true });
34592
+ async function writeGlabConfig(path26, credential, log2) {
34593
+ await mkdir4(dirname4(path26), { recursive: true });
33812
34594
  let doc;
33813
34595
  try {
33814
- const raw = await readFile3(path24, "utf8");
34596
+ const raw = await readFile3(path26, "utf8");
33815
34597
  doc = YAML.parseDocument(raw);
33816
34598
  if (doc.errors.length > 0) {
33817
34599
  log2.warn(
33818
- { errors: doc.errors.map((e) => e.message), path: path24 },
34600
+ { errors: doc.errors.map((e) => e.message), path: path26 },
33819
34601
  "oauth.glab.parse_errors_replacing"
33820
34602
  );
33821
34603
  doc = YAML.parseDocument("{}");
@@ -33824,7 +34606,7 @@ async function writeGlabConfig(path24, credential, log2) {
33824
34606
  if (isNotFound(error)) {
33825
34607
  doc = YAML.parseDocument("{}");
33826
34608
  } else {
33827
- log2.warn({ err: error, path: path24 }, "oauth.glab.read_failed_replacing");
34609
+ log2.warn({ err: error, path: path26 }, "oauth.glab.read_failed_replacing");
33828
34610
  doc = YAML.parseDocument("{}");
33829
34611
  }
33830
34612
  }
@@ -33837,18 +34619,18 @@ async function writeGlabConfig(path24, credential, log2) {
33837
34619
  if (credential.username) {
33838
34620
  hostEntry.set("user", credential.username);
33839
34621
  }
33840
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34622
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33841
34623
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
33842
- await rename(tmpPath, path24);
34624
+ await rename(tmpPath, path26);
33843
34625
  }
33844
- async function removeGlabHost(path24, log2) {
34626
+ async function removeGlabHost(path26, log2) {
33845
34627
  let doc;
33846
34628
  try {
33847
- const raw = await readFile3(path24, "utf8");
34629
+ const raw = await readFile3(path26, "utf8");
33848
34630
  doc = YAML.parseDocument(raw);
33849
34631
  } catch (error) {
33850
34632
  if (isNotFound(error)) return;
33851
- log2.warn({ err: error, path: path24 }, "oauth.glab.remove_read_failed");
34633
+ log2.warn({ err: error, path: path26 }, "oauth.glab.remove_read_failed");
33852
34634
  return;
33853
34635
  }
33854
34636
  const hosts = doc.get("hosts");
@@ -33857,9 +34639,9 @@ async function removeGlabHost(path24, log2) {
33857
34639
  if (hosts.items.length === 0) {
33858
34640
  doc.delete("hosts");
33859
34641
  }
33860
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34642
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33861
34643
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
33862
- await rename(tmpPath, path24);
34644
+ await rename(tmpPath, path26);
33863
34645
  }
33864
34646
  function ensureMap(doc, key) {
33865
34647
  const existing = doc.get(key);
@@ -34825,6 +35607,50 @@ function summarizeFetchWorkspacesEntries(entries) {
34825
35607
  workspaces
34826
35608
  };
34827
35609
  }
35610
+ function synthesizeRecordedUserText(rawText, files, imageCount = 0) {
35611
+ if (rawText.trim().length > 0) {
35612
+ return rawText;
35613
+ }
35614
+ if (files.length === 0 && imageCount === 0) {
35615
+ return rawText;
35616
+ }
35617
+ const parts = [];
35618
+ if (imageCount > 0) {
35619
+ parts.push(imageCount === 1 ? "Sent 1 image" : `Sent ${imageCount} images`);
35620
+ }
35621
+ if (files.length > 0) {
35622
+ const names = files.map((f) => f.fileName).join(", ");
35623
+ parts.push(
35624
+ files.length === 1 ? `uploaded ${names}` : `uploaded ${files.length} files: ${names}`
35625
+ );
35626
+ }
35627
+ return parts.join(" and ");
35628
+ }
35629
+ function buildImageEcho(incoming, persisted) {
35630
+ const out = [];
35631
+ for (let i = 0; i < incoming.length; i++) {
35632
+ const img = incoming[i];
35633
+ const stored = persisted[i];
35634
+ const id = stored?.id ?? img?.id;
35635
+ if (!img || !id) continue;
35636
+ const fileName = img.fileName ?? stored?.fileName ?? void 0;
35637
+ const byteSize = typeof img.byteSize === "number" ? img.byteSize : typeof stored?.size === "number" ? stored.size : void 0;
35638
+ out.push({
35639
+ id,
35640
+ mimeType: img.mimeType,
35641
+ ...fileName ? { fileName } : {},
35642
+ ...typeof byteSize === "number" ? { byteSize } : {},
35643
+ ...stored ? {
35644
+ daemonRef: {
35645
+ kind: "image",
35646
+ attachmentId: stored.id,
35647
+ relativePath: stored.relativePath
35648
+ }
35649
+ } : {}
35650
+ });
35651
+ }
35652
+ return out;
35653
+ }
34828
35654
  var SessionRequestError = class extends Error {
34829
35655
  constructor(code, message) {
34830
35656
  super(message);
@@ -35002,6 +35828,8 @@ var Session = class _Session {
35002
35828
  });
35003
35829
  this.agentManager = agentManager;
35004
35830
  this.agentStorage = agentStorage;
35831
+ this.uploadStore = new SessionUploadStore({ logger: this.sessionLogger });
35832
+ this.imageStore = new SessionImageStore({ logger: this.sessionLogger });
35005
35833
  this.projectRegistry = projectRegistry;
35006
35834
  this.workspaceRegistry = workspaceRegistry;
35007
35835
  this.chatService = chatService;
@@ -35113,11 +35941,12 @@ var Session = class _Session {
35113
35941
  /**
35114
35942
  * Normalize a user prompt (with optional image metadata) for AgentManager
35115
35943
  */
35116
- buildAgentPrompt(text, images, attachments) {
35944
+ buildAgentPrompt(text, images, attachments, files) {
35117
35945
  const normalized = text?.trim() ?? "";
35118
35946
  const hasImages = Boolean(images && images.length > 0);
35119
35947
  const hasAttachments = Boolean(attachments && attachments.length > 0);
35120
- if (!hasImages && !hasAttachments) {
35948
+ const hasFiles = Boolean(files && files.length > 0);
35949
+ if (!hasImages && !hasAttachments && !hasFiles) {
35121
35950
  return normalized;
35122
35951
  }
35123
35952
  const blocks = [];
@@ -35130,8 +35959,46 @@ var Session = class _Session {
35130
35959
  for (const attachment of attachments ?? []) {
35131
35960
  blocks.push(attachment);
35132
35961
  }
35962
+ if (hasFiles && files) {
35963
+ blocks.push({ type: "text", text: renderFileAttachmentFooter(files) });
35964
+ }
35133
35965
  return blocks;
35134
35966
  }
35967
+ /**
35968
+ * Read persisted image bytes off disk for the LLM prompt. The daemon is
35969
+ * the canonical source of truth for image bytes (Clay pattern); the wire
35970
+ * payload is just an upload conduit. Reads through the agent's cwd via
35971
+ * `imageStore.readImageBytes`, which traversal-guards the relative path.
35972
+ *
35973
+ * Falls back to skipping any image whose disk read fails (logged) — better
35974
+ * to send a partial prompt than to crash the whole turn.
35975
+ */
35976
+ async loadPromptImagesFromDisk(persistedImages, agentId) {
35977
+ const agent = this.agentManager.getAgent(agentId);
35978
+ if (!agent) {
35979
+ this.sessionLogger.warn(
35980
+ { agentId },
35981
+ "loadPromptImagesFromDisk: agent vanished between persist and read"
35982
+ );
35983
+ return [];
35984
+ }
35985
+ const out = [];
35986
+ for (const persisted of persistedImages) {
35987
+ try {
35988
+ const { data, mimeType } = await this.imageStore.readImageBytes({
35989
+ cwd: agent.cwd,
35990
+ relativePath: persisted.relativePath
35991
+ });
35992
+ out.push({ data: data.toString("base64"), mimeType });
35993
+ } catch (error) {
35994
+ this.sessionLogger.warn(
35995
+ { agentId, attachmentId: persisted.id, err: error },
35996
+ "loadPromptImagesFromDisk: failed to read image; skipping for this turn"
35997
+ );
35998
+ }
35999
+ }
36000
+ return out;
36001
+ }
35135
36002
  /**
35136
36003
  * Interrupt the agent's active run so the next prompt starts a fresh turn.
35137
36004
  * Returns once the manager confirms the stream has been cancelled.
@@ -35600,6 +36467,21 @@ var Session = class _Session {
35600
36467
  case "send_agent_message_request":
35601
36468
  await this.handleSendAgentMessageRequest(msg);
35602
36469
  break;
36470
+ case "list_session_uploads_request":
36471
+ await this.handleListSessionUploadsRequest(msg);
36472
+ break;
36473
+ case "delete_session_upload_request":
36474
+ await this.handleDeleteSessionUploadRequest(msg);
36475
+ break;
36476
+ case "list_session_images_request":
36477
+ await this.handleListSessionImagesRequest(msg);
36478
+ break;
36479
+ case "delete_session_image_request":
36480
+ await this.handleDeleteSessionImageRequest(msg);
36481
+ break;
36482
+ case "fetch_attachment_bytes_request":
36483
+ await this.handleFetchAttachmentBytesRequest(msg);
36484
+ break;
35603
36485
  case "wait_for_finish_request":
35604
36486
  await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
35605
36487
  break;
@@ -36870,26 +37752,86 @@ var Session = class _Session {
36870
37752
  * Handle text message to agent (with optional image attachments)
36871
37753
  */
36872
37754
  async handleSendAgentMessage(agentId, text, messageId, images, attachments, runOptions, options) {
37755
+ const files = options?.files ?? [];
36873
37756
  this.sessionLogger.info(
36874
37757
  {
36875
37758
  agentId,
36876
37759
  textPreview: text.substring(0, 50),
36877
37760
  imageCount: images?.length ?? 0,
36878
- attachmentCount: attachments?.length ?? 0
37761
+ attachmentCount: attachments?.length ?? 0,
37762
+ fileCount: files.length
36879
37763
  },
36880
- `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ""}${attachments && attachments.length > 0 ? ` and ${attachments.length} structured attachment(s)` : ""}`
37764
+ `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ""}${attachments && attachments.length > 0 ? ` and ${attachments.length} structured attachment(s)` : ""}${files.length > 0 ? ` and ${files.length} file attachment(s)` : ""}`
36881
37765
  );
37766
+ let persistedFiles = [];
37767
+ let persistedImages = [];
37768
+ const hasFiles = files.length > 0;
37769
+ const hasImages = (images?.length ?? 0) > 0;
37770
+ if (hasFiles || hasImages) {
37771
+ const agent = this.agentManager.getAgent(agentId);
37772
+ if (!agent) {
37773
+ return {
37774
+ ok: false,
37775
+ error: `Agent ${agentId} not found; cannot persist attachments`
37776
+ };
37777
+ }
37778
+ try {
37779
+ if (hasFiles) {
37780
+ persistedFiles = await this.uploadStore.persistFiles({
37781
+ cwd: agent.cwd,
37782
+ agentId,
37783
+ messageId: messageId ?? null,
37784
+ files
37785
+ });
37786
+ }
37787
+ if (hasImages && images) {
37788
+ persistedImages = await this.imageStore.persistImages({
37789
+ cwd: agent.cwd,
37790
+ agentId,
37791
+ messageId: messageId ?? null,
37792
+ images
37793
+ });
37794
+ }
37795
+ } catch (error) {
37796
+ if (error instanceof UploadSizeError) {
37797
+ return {
37798
+ ok: false,
37799
+ error: `Attachments exceed the ${MAX_UPLOAD_BYTES_PER_MESSAGE} byte limit per message`
37800
+ };
37801
+ }
37802
+ const message = error instanceof Error ? error.message : String(error);
37803
+ return { ok: false, error: `Failed to persist attachments: ${message}` };
37804
+ }
37805
+ }
36882
37806
  const promptText = options?.spokenInput ? wrapSpokenInput(text) : text;
36883
- const prompt = this.buildAgentPrompt(promptText, images, attachments);
37807
+ const promptImages = persistedImages.length > 0 ? await this.loadPromptImagesFromDisk(persistedImages, agentId) : images;
37808
+ const prompt = this.buildAgentPrompt(promptText, promptImages, attachments, persistedFiles);
37809
+ const recordedText = synthesizeRecordedUserText(text, persistedFiles, images?.length ?? 0);
37810
+ const userMessageImages = buildImageEcho(images ?? [], persistedImages);
37811
+ const userMessageFiles = persistedFiles.map((f) => ({
37812
+ id: f.id,
37813
+ fileName: f.fileName,
37814
+ mimeType: f.mimeType,
37815
+ size: f.size,
37816
+ daemonRef: {
37817
+ kind: "file",
37818
+ attachmentId: f.id,
37819
+ relativePath: f.relativePath
37820
+ }
37821
+ }));
37822
+ const recordedTextIsSynthesized = text.trim().length === 0 && (userMessageImages.length > 0 || userMessageFiles.length > 0);
36884
37823
  try {
36885
37824
  await sendPromptToAgent({
36886
37825
  agentManager: this.agentManager,
36887
37826
  agentStorage: this.agentStorage,
36888
37827
  agentId,
36889
- userMessageText: text,
37828
+ userMessageText: recordedText,
36890
37829
  prompt,
36891
37830
  messageId,
36892
37831
  runOptions,
37832
+ ...userMessageImages.length > 0 ? { userMessageImages } : {},
37833
+ ...userMessageFiles.length > 0 ? { userMessageFiles } : {},
37834
+ ...recordedTextIsSynthesized ? { userMessageTextIsSynthesized: true } : {},
36893
37835
  logger: this.sessionLogger
36894
37836
  });
36895
37837
  return { ok: true };
@@ -36914,6 +37856,7 @@ var Session = class _Session {
36914
37856
  outputSchema,
36915
37857
  git,
36916
37858
  images,
37859
+ files,
36917
37860
  attachments,
36918
37861
  labels
36919
37862
  } = msg;
@@ -36954,7 +37897,7 @@ var Session = class _Session {
36954
37897
  }
36955
37898
  );
36956
37899
  await this.forwardAgentUpdate(snapshot);
36957
- if (trimmedPrompt || (images?.length ?? 0) > 0 || (attachments?.length ?? 0) > 0) {
37900
+ if (trimmedPrompt || (images?.length ?? 0) > 0 || (attachments?.length ?? 0) > 0 || (files?.length ?? 0) > 0) {
36958
37901
  scheduleAgentMetadataGeneration({
36959
37902
  agentManager: this.agentManager,
36960
37903
  agentId: snapshot.id,
@@ -36970,7 +37913,8 @@ var Session = class _Session {
36970
37913
  resolveClientMessageId(clientMessageId),
36971
37914
  images,
36972
37915
  attachments,
36973
- outputSchema ? { outputSchema } : void 0
37916
+ outputSchema ? { outputSchema } : void 0,
37917
+ files && files.length > 0 ? { files } : void 0
36974
37918
  );
36975
37919
  if (!started.ok) {
36976
37920
  throw new Error(started.error);
@@ -38387,7 +39331,7 @@ var Session = class _Session {
38387
39331
  homeDir: process.env.HOME ?? homedir5(),
38388
39332
  query: query2,
38389
39333
  limit
38390
- })).map((path24) => ({ path: path24, kind: "directory" }));
39334
+ })).map((path26) => ({ path: path26, kind: "directory" }));
38391
39335
  const directories = entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
38392
39336
  this.emit({
38393
39337
  type: "directory_suggestions_response",
@@ -39441,10 +40385,11 @@ ${details}`.trim());
39441
40385
  root: cwd,
39442
40386
  relativePath: requestedPath
39443
40387
  });
40388
+ const recoveredFileName = originalUploadFileName(info.path) ?? info.fileName;
39444
40389
  const entry = this.downloadTokenStore.issueToken({
39445
40390
  path: info.path,
39446
40391
  absolutePath: info.absolutePath,
39447
- fileName: info.fileName,
40392
+ fileName: recoveredFileName,
39448
40393
  mimeType: info.mimeType,
39449
40394
  size: info.size
39450
40395
  });
@@ -40878,19 +41823,96 @@ ${details}`.trim());
40878
41823
  }
40879
41824
  try {
40880
41825
  const agentId = resolved.agentId;
40881
- const prompt = this.buildAgentPrompt(msg.text, msg.images, msg.attachments);
41826
+ let persistedFiles = [];
41827
+ let persistedImages = [];
41828
+ const hasFiles = (msg.files?.length ?? 0) > 0;
41829
+ const hasImages = (msg.images?.length ?? 0) > 0;
41830
+ if (hasFiles || hasImages) {
41831
+ const agent = this.agentManager.getAgent(agentId);
41832
+ if (!agent) {
41833
+ this.emit({
41834
+ type: "send_agent_message_response",
41835
+ payload: {
41836
+ requestId: msg.requestId,
41837
+ agentId,
41838
+ accepted: false,
41839
+ error: `Agent ${agentId} not found; cannot persist attachments`
41840
+ }
41841
+ });
41842
+ return;
41843
+ }
41844
+ try {
41845
+ if (hasFiles && msg.files) {
41846
+ persistedFiles = await this.uploadStore.persistFiles({
41847
+ cwd: agent.cwd,
41848
+ agentId,
41849
+ messageId: msg.messageId ?? null,
41850
+ files: msg.files
41851
+ });
41852
+ }
41853
+ if (hasImages && msg.images) {
41854
+ persistedImages = await this.imageStore.persistImages({
41855
+ cwd: agent.cwd,
41856
+ agentId,
41857
+ messageId: msg.messageId ?? null,
41858
+ images: msg.images
41859
+ });
41860
+ }
41861
+ } catch (error) {
41862
+ const message = error instanceof UploadSizeError ? `Attachments exceed the ${MAX_UPLOAD_BYTES_PER_MESSAGE} byte limit per message` : error instanceof Error ? `Failed to persist attachments: ${error.message}` : "Failed to persist attachments";
41863
+ this.emit({
41864
+ type: "send_agent_message_response",
41865
+ payload: {
41866
+ requestId: msg.requestId,
41867
+ agentId,
41868
+ accepted: false,
41869
+ error: message
41870
+ }
41871
+ });
41872
+ return;
41873
+ }
41874
+ }
41875
+ const promptImages = persistedImages.length > 0 ? await this.loadPromptImagesFromDisk(persistedImages, agentId) : msg.images;
41876
+ const prompt = this.buildAgentPrompt(msg.text, promptImages, msg.attachments, persistedFiles);
41877
+ const recordedText = synthesizeRecordedUserText(
41878
+ msg.text,
41879
+ persistedFiles,
41880
+ msg.images?.length ?? 0
41881
+ );
41882
+ const userMessageImages = buildImageEcho(msg.images ?? [], persistedImages);
41883
+ const userMessageFiles = persistedFiles.map((f) => ({
41884
+ id: f.id,
41885
+ fileName: f.fileName,
41886
+ mimeType: f.mimeType,
41887
+ size: f.size,
41888
+ daemonRef: {
41889
+ kind: "file",
41890
+ attachmentId: f.id,
41891
+ relativePath: f.relativePath
41892
+ }
41893
+ }));
40882
41894
  this.sessionLogger.trace(
40883
- { agentId, messageId: msg.messageId, textPrefix: msg.text.slice(0, 80) },
41895
+ {
41896
+ agentId,
41897
+ messageId: msg.messageId,
41898
+ textPrefix: msg.text.slice(0, 80),
41899
+ fileCount: persistedFiles.length,
41900
+ echoImageCount: userMessageImages.length
41901
+ },
40884
41902
  "send_agent_message_request: dispatching shared sendPromptToAgent"
40885
41903
  );
41904
+ const recordedTextIsSynthesized = msg.text.trim().length === 0 && (userMessageImages.length > 0 || userMessageFiles.length > 0);
40886
41905
  try {
40887
41906
  await sendPromptToAgent({
40888
41907
  agentManager: this.agentManager,
40889
41908
  agentStorage: this.agentStorage,
40890
41909
  agentId,
40891
- userMessageText: msg.text,
41910
+ userMessageText: recordedText,
40892
41911
  prompt,
40893
41912
  messageId: msg.messageId,
41913
+ ...userMessageImages.length > 0 ? { userMessageImages } : {},
41914
+ ...userMessageFiles.length > 0 ? { userMessageFiles } : {},
41915
+ ...recordedTextIsSynthesized ? { userMessageTextIsSynthesized: true } : {},
40894
41916
  logger: this.sessionLogger
40895
41917
  });
40896
41918
  } catch (error) {
@@ -40949,6 +41971,242 @@ ${details}`.trim());
40949
41971
  });
40950
41972
  }
40951
41973
  }
41974
+ // ──────────────────────────────────────────────────────────────────────
41975
+ // Session uploads — backs the in-app "Files" tab inside an active session.
41976
+ // Both handlers resolve `agentId` against the agent registry first so we
41977
+ // get the same accept-prefix-or-title behaviour as send_agent_message, and
41978
+ // both fail closed on a missing agent (no implicit "list everything" that
41979
+ // could leak across sessions).
41980
+ // ──────────────────────────────────────────────────────────────────────
41981
+ async handleListSessionUploadsRequest(msg) {
41982
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
41983
+ if (!resolved.ok) {
41984
+ this.emit({
41985
+ type: "list_session_uploads_response",
41986
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
41987
+ });
41988
+ return;
41989
+ }
41990
+ const agent = this.agentManager.getAgent(resolved.agentId);
41991
+ if (!agent) {
41992
+ this.emit({
41993
+ type: "list_session_uploads_response",
41994
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
41995
+ });
41996
+ return;
41997
+ }
41998
+ try {
41999
+ const uploads = await this.uploadStore.listUploads({
42000
+ cwd: agent.cwd,
42001
+ agentId: resolved.agentId
42002
+ });
42003
+ this.emit({
42004
+ type: "list_session_uploads_response",
42005
+ payload: { requestId: msg.requestId, ok: true, uploads }
42006
+ });
42007
+ } catch (error) {
42008
+ const message = error instanceof Error ? error.message : String(error);
42009
+ this.emit({
42010
+ type: "list_session_uploads_response",
42011
+ payload: { requestId: msg.requestId, ok: false, error: message }
42012
+ });
42013
+ }
42014
+ }
42015
+ async handleDeleteSessionUploadRequest(msg) {
42016
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
42017
+ if (!resolved.ok) {
42018
+ this.emit({
42019
+ type: "delete_session_upload_response",
42020
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
42021
+ });
42022
+ return;
42023
+ }
42024
+ const agent = this.agentManager.getAgent(resolved.agentId);
42025
+ if (!agent) {
42026
+ this.emit({
42027
+ type: "delete_session_upload_response",
42028
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
42029
+ });
42030
+ return;
42031
+ }
42032
+ try {
42033
+ const result = await this.uploadStore.deleteUpload({
42034
+ cwd: agent.cwd,
42035
+ agentId: resolved.agentId,
42036
+ uploadId: msg.uploadId
42037
+ });
42038
+ if (!result.deleted) {
42039
+ this.emit({
42040
+ type: "delete_session_upload_response",
42041
+ payload: { requestId: msg.requestId, ok: false, error: "Upload not found" }
42042
+ });
42043
+ return;
42044
+ }
42045
+ this.emit({
42046
+ type: "delete_session_upload_response",
42047
+ payload: { requestId: msg.requestId, ok: true }
42048
+ });
42049
+ } catch (error) {
42050
+ const message = error instanceof Error ? error.message : String(error);
42051
+ this.emit({
42052
+ type: "delete_session_upload_response",
42053
+ payload: { requestId: msg.requestId, ok: false, error: message }
42054
+ });
42055
+ }
42056
+ }
42057
+ // ──────────────────────────────────────────────────────────────────────
42058
+ // Session images — backs the "Images" tab inside the uploads modal. Same
42059
+ // shape as the file handlers above; the store has no manifest, so listing
42060
+ // is a directory scan and delete is `unlink` (traversal-guarded).
42061
+ // ──────────────────────────────────────────────────────────────────────
42062
+ async handleListSessionImagesRequest(msg) {
42063
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
42064
+ if (!resolved.ok) {
42065
+ this.emit({
42066
+ type: "list_session_images_response",
42067
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
42068
+ });
42069
+ return;
42070
+ }
42071
+ const agent = this.agentManager.getAgent(resolved.agentId);
42072
+ if (!agent) {
42073
+ this.emit({
42074
+ type: "list_session_images_response",
42075
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
42076
+ });
42077
+ return;
42078
+ }
42079
+ try {
42080
+ const images = await this.imageStore.listImages({ cwd: agent.cwd });
42081
+ this.emit({
42082
+ type: "list_session_images_response",
42083
+ payload: { requestId: msg.requestId, ok: true, images }
42084
+ });
42085
+ } catch (error) {
42086
+ const message = error instanceof Error ? error.message : String(error);
42087
+ this.emit({
42088
+ type: "list_session_images_response",
42089
+ payload: { requestId: msg.requestId, ok: false, error: message }
42090
+ });
42091
+ }
42092
+ }
42093
+ async handleDeleteSessionImageRequest(msg) {
42094
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
42095
+ if (!resolved.ok) {
42096
+ this.emit({
42097
+ type: "delete_session_image_response",
42098
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
42099
+ });
42100
+ return;
42101
+ }
42102
+ const agent = this.agentManager.getAgent(resolved.agentId);
42103
+ if (!agent) {
42104
+ this.emit({
42105
+ type: "delete_session_image_response",
42106
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
42107
+ });
42108
+ return;
42109
+ }
42110
+ try {
42111
+ const result = await this.imageStore.deleteImage({
42112
+ cwd: agent.cwd,
42113
+ relativePath: msg.relativePath
42114
+ });
42115
+ if (!result.deleted) {
42116
+ this.emit({
42117
+ type: "delete_session_image_response",
42118
+ payload: { requestId: msg.requestId, ok: false, error: "Image not found" }
42119
+ });
42120
+ return;
42121
+ }
42122
+ this.emit({
42123
+ type: "delete_session_image_response",
42124
+ payload: { requestId: msg.requestId, ok: true }
42125
+ });
42126
+ } catch (error) {
42127
+ const message = error instanceof Error ? error.message : String(error);
42128
+ this.emit({
42129
+ type: "delete_session_image_response",
42130
+ payload: { requestId: msg.requestId, ok: false, error: message }
42131
+ });
42132
+ }
42133
+ }
42134
+ /**
42135
+ * Serve attachment bytes by id. The daemon is the canonical source — every
42136
+ * paired client (Electron after Cmd-R, phone, web) hits this RPC to render
42137
+ * thumbnails or download files. Bytes never round-trip through the chat
42138
+ * timeline; they're fetched on demand.
42139
+ *
42140
+ * `relativePath` comes from the user_message timeline echo's `daemonRef`;
42141
+ * the store's read methods traversal-guard it so it can't escape the
42142
+ * dedicated subdirectories.
42143
+ */
42144
+ async handleFetchAttachmentBytesRequest(msg) {
42145
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
42146
+ if (!resolved.ok) {
42147
+ this.emit({
42148
+ type: "fetch_attachment_bytes_response",
42149
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
42150
+ });
42151
+ return;
42152
+ }
42153
+ const agent = this.agentManager.getAgent(resolved.agentId);
42154
+ if (!agent) {
42155
+ this.emit({
42156
+ type: "fetch_attachment_bytes_response",
42157
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
42158
+ });
42159
+ return;
42160
+ }
42161
+ try {
42162
+ if (msg.kind === "image") {
42163
+ const { data, mimeType, byteSize } = await this.imageStore.readImageBytes({
42164
+ cwd: agent.cwd,
42165
+ relativePath: msg.relativePath
42166
+ });
42167
+ this.emit({
42168
+ type: "fetch_attachment_bytes_response",
42169
+ payload: {
42170
+ requestId: msg.requestId,
42171
+ ok: true,
42172
+ attachmentId: msg.attachmentId,
42173
+ kind: "image",
42174
+ data: data.toString("base64"),
42175
+ mimeType,
42176
+ byteSize
42177
+ }
42178
+ });
42179
+ } else {
42180
+ const { data, mimeType, byteSize, fileName } = await this.uploadStore.readUploadBytes({
42181
+ cwd: agent.cwd,
42182
+ relativePath: msg.relativePath
42183
+ });
42184
+ this.emit({
42185
+ type: "fetch_attachment_bytes_response",
42186
+ payload: {
42187
+ requestId: msg.requestId,
42188
+ ok: true,
42189
+ attachmentId: msg.attachmentId,
42190
+ kind: "file",
42191
+ data: data.toString("base64"),
42192
+ mimeType,
42193
+ byteSize,
42194
+ fileName
42195
+ }
42196
+ });
42197
+ }
42198
+ } catch (error) {
42199
+ const message = error instanceof Error ? error.message : String(error);
42200
+ this.sessionLogger.warn(
42201
+ { agentId: resolved.agentId, attachmentId: msg.attachmentId, kind: msg.kind, err: error },
42202
+ "fetch_attachment_bytes_request: failed to read"
42203
+ );
42204
+ this.emit({
42205
+ type: "fetch_attachment_bytes_response",
42206
+ payload: { requestId: msg.requestId, ok: false, error: message }
42207
+ });
42208
+ }
42209
+ }
40952
42210
  async handleWaitForFinish(agentIdOrIdentifier, requestId, timeoutMs) {
40953
42211
  const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
40954
42212
  if (!resolved.ok) {
@@ -43272,16 +44530,16 @@ function isRelayClientWebSocketUrl(url) {
43272
44530
  }
43273
44531
 
43274
44532
  // ../server/src/server/config.ts
43275
- import path17 from "node:path";
44533
+ import path19 from "node:path";
43276
44534
  import { z as z47 } from "zod";
43277
44535
 
43278
44536
  // ../server/src/server/speech/speech-config-resolver.ts
43279
44537
  import { z as z46 } from "zod";
43280
44538
 
43281
44539
  // ../server/src/server/speech/providers/local/config.ts
43282
- import path16 from "node:path";
44540
+ import path18 from "node:path";
43283
44541
  import { z as z44 } from "zod";
43284
- var DEFAULT_LOCAL_MODELS_SUBDIR = path16.join("models", "local-speech");
44542
+ var DEFAULT_LOCAL_MODELS_SUBDIR = path18.join("models", "local-speech");
43285
44543
  var NumberLikeSchema2 = z44.union([z44.number(), z44.string().trim().min(1)]);
43286
44544
  var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z44.coerce.number().finite()).optional();
43287
44545
  var OptionalIntegerSchema = NumberLikeSchema2.pipe(z44.coerce.number().int()).optional();
@@ -43308,7 +44566,7 @@ function resolveLocalSpeechConfig(params) {
43308
44566
  const includeProviderConfig = shouldIncludeLocalProviderConfig(params);
43309
44567
  const parsed = LocalSpeechResolutionSchema.parse({
43310
44568
  includeProviderConfig,
43311
- modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path16.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
44569
+ modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path18.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
43312
44570
  dictationLocalSttModel: params.env.APPOSTLE_DICTATION_LOCAL_STT_MODEL ?? persistedLocalFeatureModel(
43313
44571
  params.providers.dictationStt.provider,
43314
44572
  params.providers.dictationStt.enabled,
@@ -43564,7 +44822,7 @@ function loadConfig(appostleHome, options) {
43564
44822
  chromeEnabled,
43565
44823
  mcpDebug: env.MCP_DEBUG === "1",
43566
44824
  daemonIcon,
43567
- agentStoragePath: path17.join(appostleHome, "agents"),
44825
+ agentStoragePath: path19.join(appostleHome, "agents"),
43568
44826
  staticDir: "public",
43569
44827
  agentClients: {},
43570
44828
  relayEnabled,
@@ -44782,12 +46040,12 @@ var DaemonClient = class {
44782
46040
  timeout: 1e4
44783
46041
  });
44784
46042
  }
44785
- async openInEditor(path24, editorId, requestId) {
46043
+ async openInEditor(path26, editorId, requestId) {
44786
46044
  return this.sendCorrelatedSessionRequest({
44787
46045
  requestId,
44788
46046
  message: {
44789
46047
  type: "open_in_editor_request",
44790
- path: path24,
46048
+ path: path26,
44791
46049
  editorId
44792
46050
  },
44793
46051
  responseType: "open_in_editor_response",
@@ -44887,6 +46145,7 @@ var DaemonClient = class {
44887
46145
  ...options.clientMessageId ? { clientMessageId: options.clientMessageId } : {},
44888
46146
  ...options.outputSchema ? { outputSchema: options.outputSchema } : {},
44889
46147
  ...options.images && options.images.length > 0 ? { images: options.images } : {},
46148
+ ...options.files && options.files.length > 0 ? { files: options.files } : {},
44890
46149
  ...options.attachments && options.attachments.length > 0 ? { attachments: options.attachments } : {},
44891
46150
  ...options.git ? { git: options.git } : {},
44892
46151
  ...options.worktreeName ? { worktreeName: options.worktreeName } : {},
@@ -45112,6 +46371,7 @@ var DaemonClient = class {
45112
46371
  text,
45113
46372
  ...messageId ? { messageId } : {},
45114
46373
  ...options?.images ? { images: options.images } : {},
46374
+ ...options?.files ? { files: options.files } : {},
45115
46375
  ...options?.attachments ? { attachments: options.attachments } : {}
45116
46376
  });
45117
46377
  const payload = await this.sendRequest({
@@ -45136,6 +46396,157 @@ var DaemonClient = class {
45136
46396
  async sendMessage(agentId, text, options) {
45137
46397
  await this.sendAgentMessage(agentId, text, options);
45138
46398
  }
46399
+ /**
46400
+ * List the files the user has attached to this agent's chat (the "Files"
46401
+ * tab in an active session). Returns newest first; the daemon serves the
46402
+ * manifest stored under `<workspace>/.appostle/uploaded_files/`.
46403
+ */
46404
+ async listSessionUploads(agentId) {
46405
+ const requestId = this.createRequestId();
46406
+ const message = SessionInboundMessageSchema.parse({
46407
+ type: "list_session_uploads_request",
46408
+ requestId,
46409
+ agentId
46410
+ });
46411
+ const payload = await this.sendRequest({
46412
+ requestId,
46413
+ message,
46414
+ timeout: 15e3,
46415
+ options: { skipQueue: true },
46416
+ select: (msg) => {
46417
+ if (msg.type !== "list_session_uploads_response") return null;
46418
+ if (msg.payload.requestId !== requestId) return null;
46419
+ return msg.payload;
46420
+ }
46421
+ });
46422
+ if (!payload.ok) {
46423
+ throw new Error(payload.error || "Failed to list session uploads");
46424
+ }
46425
+ return payload.uploads;
46426
+ }
46427
+ /**
46428
+ * Delete a single uploaded file. Removes both the manifest entry and the
46429
+ * file on disk; the operation is idempotent — deleting a no-longer-existent
46430
+ * upload throws so the caller can refresh the listing.
46431
+ */
46432
+ async deleteSessionUpload(agentId, uploadId) {
46433
+ const requestId = this.createRequestId();
46434
+ const message = SessionInboundMessageSchema.parse({
46435
+ type: "delete_session_upload_request",
46436
+ requestId,
46437
+ agentId,
46438
+ uploadId
46439
+ });
46440
+ const payload = await this.sendRequest({
46441
+ requestId,
46442
+ message,
46443
+ timeout: 15e3,
46444
+ options: { skipQueue: true },
46445
+ select: (msg) => {
46446
+ if (msg.type !== "delete_session_upload_response") return null;
46447
+ if (msg.payload.requestId !== requestId) return null;
46448
+ return msg.payload;
46449
+ }
46450
+ });
46451
+ if (!payload.ok) {
46452
+ throw new Error(payload.error || "Failed to delete session upload");
46453
+ }
46454
+ }
46455
+ /**
46456
+ * List the images the user has attached to this agent's chat (the "Images"
46457
+ * tab in the uploads modal). Returns newest first; the daemon scans
46458
+ * `<workspace>/.appostle/uploaded_images/` directly — there's no manifest.
46459
+ */
46460
+ async listSessionImages(agentId) {
46461
+ const requestId = this.createRequestId();
46462
+ const message = SessionInboundMessageSchema.parse({
46463
+ type: "list_session_images_request",
46464
+ requestId,
46465
+ agentId
46466
+ });
46467
+ const payload = await this.sendRequest({
46468
+ requestId,
46469
+ message,
46470
+ timeout: 15e3,
46471
+ options: { skipQueue: true },
46472
+ select: (msg) => {
46473
+ if (msg.type !== "list_session_images_response") return null;
46474
+ if (msg.payload.requestId !== requestId) return null;
46475
+ return msg.payload;
46476
+ }
46477
+ });
46478
+ if (!payload.ok) {
46479
+ throw new Error(payload.error || "Failed to list session images");
46480
+ }
46481
+ return payload.images;
46482
+ }
46483
+ /**
46484
+ * Delete a single uploaded image by workspace-relative path. The daemon
46485
+ * traversal-guards the path so it cannot escape `.appostle/uploaded_images/`.
46486
+ */
46487
+ async deleteSessionImage(agentId, relativePath) {
46488
+ const requestId = this.createRequestId();
46489
+ const message = SessionInboundMessageSchema.parse({
46490
+ type: "delete_session_image_request",
46491
+ requestId,
46492
+ agentId,
46493
+ relativePath
46494
+ });
46495
+ const payload = await this.sendRequest({
46496
+ requestId,
46497
+ message,
46498
+ timeout: 15e3,
46499
+ options: { skipQueue: true },
46500
+ select: (msg) => {
46501
+ if (msg.type !== "delete_session_image_response") return null;
46502
+ if (msg.payload.requestId !== requestId) return null;
46503
+ return msg.payload;
46504
+ }
46505
+ });
46506
+ if (!payload.ok) {
46507
+ throw new Error(payload.error || "Failed to delete session image");
46508
+ }
46509
+ }
46510
+ /**
46511
+ * Fetch attachment bytes by id from the daemon. The daemon is the canonical
46512
+ * source for image and file bytes — every paired client (Electron after
46513
+ * Cmd-R, phone, web) hits this RPC to render thumbnails and previews.
46514
+ *
46515
+ * `relativePath` comes from the user_message timeline echo's `daemonRef`.
46516
+ * Returns base64-encoded bytes plus mime/size.
46517
+ */
46518
+ async fetchAttachmentBytes(input) {
46519
+ const requestId = this.createRequestId();
46520
+ const message = SessionInboundMessageSchema.parse({
46521
+ type: "fetch_attachment_bytes_request",
46522
+ requestId,
46523
+ agentId: input.agentId,
46524
+ kind: input.kind,
46525
+ attachmentId: input.attachmentId,
46526
+ relativePath: input.relativePath
46527
+ });
46528
+ const payload = await this.sendRequest({
46529
+ requestId,
46530
+ message,
46531
+ // Image bytes can be a couple MB; allow more time than a typical RPC.
46532
+ timeout: 3e4,
46533
+ options: { skipQueue: true },
46534
+ select: (msg) => {
46535
+ if (msg.type !== "fetch_attachment_bytes_response") return null;
46536
+ if (msg.payload.requestId !== requestId) return null;
46537
+ return msg.payload;
46538
+ }
46539
+ });
46540
+ if (!payload.ok) {
46541
+ throw new Error(payload.error || "Failed to fetch attachment bytes");
46542
+ }
46543
+ return {
46544
+ data: payload.data,
46545
+ mimeType: payload.mimeType,
46546
+ byteSize: payload.byteSize,
46547
+ ...payload.fileName !== void 0 ? { fileName: payload.fileName } : {}
46548
+ };
46549
+ }
45139
46550
  async cancelAgent(agentId) {
45140
46551
  const requestId = this.createRequestId();
45141
46552
  const message = SessionInboundMessageSchema.parse({
@@ -46049,13 +47460,13 @@ var DaemonClient = class {
46049
47460
  // ============================================================================
46050
47461
  // File Explorer
46051
47462
  // ============================================================================
46052
- async exploreFileSystem(cwd, path24, mode = "list", requestId) {
47463
+ async exploreFileSystem(cwd, path26, mode = "list", requestId) {
46053
47464
  return this.sendCorrelatedSessionRequest({
46054
47465
  requestId,
46055
47466
  message: {
46056
47467
  type: "file_explorer_request",
46057
47468
  cwd,
46058
- path: path24,
47469
+ path: path26,
46059
47470
  mode
46060
47471
  },
46061
47472
  responseType: "file_explorer_response",
@@ -46067,13 +47478,13 @@ var DaemonClient = class {
46067
47478
  * allowlists extensions (currently `.md` only) — callers don't need to
46068
47479
  * re-check. Used by the plan-todos UI to rewrite plan-file frontmatter.
46069
47480
  */
46070
- async writeFile(cwd, path24, content, requestId) {
47481
+ async writeFile(cwd, path26, content, requestId) {
46071
47482
  return this.sendCorrelatedSessionRequest({
46072
47483
  requestId,
46073
47484
  message: {
46074
47485
  type: "file_write_request",
46075
47486
  cwd,
46076
- path: path24,
47487
+ path: path26,
46077
47488
  content
46078
47489
  },
46079
47490
  responseType: "file_write_response",
@@ -46086,42 +47497,42 @@ var DaemonClient = class {
46086
47497
  * action is the only consumer). Returns the daemon's structured response
46087
47498
  * so callers can surface the error in the UI.
46088
47499
  */
46089
- async deleteFile(cwd, path24, requestId) {
47500
+ async deleteFile(cwd, path26, requestId) {
46090
47501
  return this.sendCorrelatedSessionRequest({
46091
47502
  requestId,
46092
47503
  message: {
46093
47504
  type: "file_delete_request",
46094
47505
  cwd,
46095
- path: path24
47506
+ path: path26
46096
47507
  },
46097
47508
  responseType: "file_delete_response",
46098
47509
  timeout: 1e4
46099
47510
  });
46100
47511
  }
46101
- async requestDownloadToken(cwd, path24, requestId) {
47512
+ async requestDownloadToken(cwd, path26, requestId) {
46102
47513
  return this.sendCorrelatedSessionRequest({
46103
47514
  requestId,
46104
47515
  message: {
46105
47516
  type: "file_download_token_request",
46106
47517
  cwd,
46107
- path: path24
47518
+ path: path26
46108
47519
  },
46109
47520
  responseType: "file_download_token_response",
46110
47521
  timeout: 1e4
46111
47522
  });
46112
47523
  }
46113
- async explorerDeleteEntry(cwd, path24, requestId) {
47524
+ async explorerDeleteEntry(cwd, path26, requestId) {
46114
47525
  return this.sendCorrelatedSessionRequest({
46115
47526
  requestId,
46116
- message: { type: "file_explorer_delete_request", cwd, path: path24 },
47527
+ message: { type: "file_explorer_delete_request", cwd, path: path26 },
46117
47528
  responseType: "file_explorer_delete_response",
46118
47529
  timeout: 1e4
46119
47530
  });
46120
47531
  }
46121
- async explorerMkdir(cwd, path24, requestId) {
47532
+ async explorerMkdir(cwd, path26, requestId) {
46122
47533
  return this.sendCorrelatedSessionRequest({
46123
47534
  requestId,
46124
- message: { type: "file_mkdir_request", cwd, path: path24 },
47535
+ message: { type: "file_mkdir_request", cwd, path: path26 },
46125
47536
  responseType: "file_mkdir_response",
46126
47537
  timeout: 1e4
46127
47538
  });
@@ -47511,16 +48922,16 @@ function resolveAgentConfig(options) {
47511
48922
  }
47512
48923
 
47513
48924
  // ../cli/src/utils/client.ts
47514
- import path18 from "node:path";
48925
+ import path20 from "node:path";
47515
48926
  import WebSocket3 from "ws";
47516
48927
 
47517
48928
  // ../cli/src/utils/client-id.ts
47518
48929
  import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
47519
- import { randomUUID as randomUUID5 } from "node:crypto";
47520
- import { dirname as dirname5, join as join10 } from "node:path";
48930
+ import { randomUUID as randomUUID6 } from "node:crypto";
48931
+ import { dirname as dirname5, join as join12 } from "node:path";
47521
48932
  import { homedir as homedir6 } from "node:os";
47522
- var CLIENT_SESSION_KEY_FILE = join10(
47523
- process.env.APPOSTLE_HOME ?? join10(homedir6(), ".appostle"),
48933
+ var CLIENT_SESSION_KEY_FILE = join12(
48934
+ process.env.APPOSTLE_HOME ?? join12(homedir6(), ".appostle"),
47524
48935
  "cli-client-id"
47525
48936
  );
47526
48937
  var cachedClientId = null;
@@ -47529,7 +48940,7 @@ function normalizeClientId2(value) {
47529
48940
  return trimmed.length > 0 ? trimmed : null;
47530
48941
  }
47531
48942
  function generateClientId() {
47532
- return `cid_${randomUUID5().replace(/-/g, "")}`;
48943
+ return `cid_${randomUUID6().replace(/-/g, "")}`;
47533
48944
  }
47534
48945
  async function getOrCreateCliClientId() {
47535
48946
  if (cachedClientId) {
@@ -47587,7 +48998,7 @@ function isTcpDaemonHost(host) {
47587
48998
  return host !== null && !isIpcDaemonHost(host);
47588
48999
  }
47589
49000
  function readPidSocketTarget(appostleHome) {
47590
- const pidPath = path18.join(appostleHome, PID_FILENAME);
49001
+ const pidPath = path20.join(appostleHome, PID_FILENAME);
47591
49002
  if (!existsSync10(pidPath)) {
47592
49003
  return null;
47593
49004
  }
@@ -48056,12 +49467,12 @@ function relativeTime(date) {
48056
49467
  if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
48057
49468
  return `${Math.floor(seconds / 86400)} days ago`;
48058
49469
  }
48059
- function shortenPath(path24) {
49470
+ function shortenPath(path26) {
48060
49471
  const home = process.env.HOME;
48061
- if (home && path24.startsWith(home)) {
48062
- return "~" + path24.slice(home.length);
49472
+ if (home && path26.startsWith(home)) {
49473
+ return "~" + path26.slice(home.length);
48063
49474
  }
48064
- return path24;
49475
+ return path26;
48065
49476
  }
48066
49477
  function normalizeModelId(modelId) {
48067
49478
  if (typeof modelId !== "string") return null;
@@ -48904,7 +50315,7 @@ async function runStopCommand(id, options, _command) {
48904
50315
 
48905
50316
  // ../cli/src/commands/agent/send.ts
48906
50317
  import { readFile as readFile5 } from "node:fs/promises";
48907
- import { extname as extname3, resolve as resolve11 } from "node:path";
50318
+ import { extname as extname4, resolve as resolve11 } from "node:path";
48908
50319
  var agentSendSchema = {
48909
50320
  idField: "agentId",
48910
50321
  columns: [
@@ -48918,10 +50329,10 @@ function addSendOptions(cmd) {
48918
50329
  }
48919
50330
  async function readImageFiles(imagePaths) {
48920
50331
  const images = [];
48921
- for (const path24 of imagePaths) {
50332
+ for (const path26 of imagePaths) {
48922
50333
  try {
48923
- const buffer = await readFile5(path24);
48924
- const ext = extname3(path24).toLowerCase();
50334
+ const buffer = await readFile5(path26);
50335
+ const ext = extname4(path26).toLowerCase();
48925
50336
  let mimeType = "image/jpeg";
48926
50337
  switch (ext) {
48927
50338
  case ".png":
@@ -48949,7 +50360,7 @@ async function readImageFiles(imagePaths) {
48949
50360
  const message = err instanceof Error ? err.message : String(err);
48950
50361
  const error = {
48951
50362
  code: "IMAGE_READ_ERROR",
48952
- message: `Failed to read image file: ${path24}`,
50363
+ message: `Failed to read image file: ${path26}`,
48953
50364
  details: message
48954
50365
  };
48955
50366
  throw error;
@@ -49122,12 +50533,12 @@ function createInspectSchema(agent) {
49122
50533
  serialize: (_item) => agent
49123
50534
  };
49124
50535
  }
49125
- function shortenPath2(path24) {
50536
+ function shortenPath2(path26) {
49126
50537
  const home = process.env.HOME;
49127
- if (home && path24.startsWith(home)) {
49128
- return "~" + path24.slice(home.length);
50538
+ if (home && path26.startsWith(home)) {
50539
+ return "~" + path26.slice(home.length);
49129
50540
  }
49130
- return path24;
50541
+ return path26;
49131
50542
  }
49132
50543
  function formatCost(costUsd) {
49133
50544
  if (costUsd === 0) return "$0.00";
@@ -50095,7 +51506,7 @@ import chalk3 from "chalk";
50095
51506
  import { spawn as spawn7, spawnSync } from "node:child_process";
50096
51507
  import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
50097
51508
  import { createRequire as createRequire3 } from "node:module";
50098
- import path19 from "node:path";
51509
+ import path21 from "node:path";
50099
51510
  import { fileURLToPath } from "node:url";
50100
51511
  var DETACHED_STARTUP_GRACE_MS = 1200;
50101
51512
  var PID_POLL_INTERVAL_MS = 100;
@@ -50146,7 +51557,7 @@ function buildChildEnv(options) {
50146
51557
  function resolveDaemonRunnerEntry() {
50147
51558
  try {
50148
51559
  const here = fileURLToPath(import.meta.url);
50149
- const sibling = path19.join(path19.dirname(here), "supervisor-entrypoint.js");
51560
+ const sibling = path21.join(path21.dirname(here), "supervisor-entrypoint.js");
50150
51561
  if (existsSync11(sibling)) {
50151
51562
  return sibling;
50152
51563
  }
@@ -50160,23 +51571,23 @@ function resolveDaemonRunnerEntry() {
50160
51571
  "Unable to resolve @appostle/server package root for daemon runner (and no sibling supervisor-entrypoint.js was bundled)"
50161
51572
  );
50162
51573
  }
50163
- let currentDir = path19.dirname(serverExportPath);
51574
+ let currentDir = path21.dirname(serverExportPath);
50164
51575
  while (true) {
50165
- const packageJsonPath = path19.join(currentDir, "package.json");
51576
+ const packageJsonPath = path21.join(currentDir, "package.json");
50166
51577
  if (existsSync11(packageJsonPath)) {
50167
51578
  try {
50168
51579
  const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
50169
51580
  if (packageJson.name === "@appostle/server") {
50170
- const distRunner = path19.join(currentDir, "dist", "scripts", "supervisor-entrypoint.js");
51581
+ const distRunner = path21.join(currentDir, "dist", "scripts", "supervisor-entrypoint.js");
50171
51582
  if (existsSync11(distRunner)) {
50172
51583
  return distRunner;
50173
51584
  }
50174
- return path19.join(currentDir, "scripts", "supervisor-entrypoint.ts");
51585
+ return path21.join(currentDir, "scripts", "supervisor-entrypoint.ts");
50175
51586
  }
50176
51587
  } catch {
50177
51588
  }
50178
51589
  }
50179
- const parentDir = path19.dirname(currentDir);
51590
+ const parentDir = path21.dirname(currentDir);
50180
51591
  if (parentDir === currentDir) {
50181
51592
  break;
50182
51593
  }
@@ -50185,7 +51596,7 @@ function resolveDaemonRunnerEntry() {
50185
51596
  throw new Error("Unable to resolve @appostle/server package root for daemon runner");
50186
51597
  }
50187
51598
  function pidFilePath(appostleHome) {
50188
- return path19.join(appostleHome, DAEMON_PID_FILENAME);
51599
+ return path21.join(appostleHome, DAEMON_PID_FILENAME);
50189
51600
  }
50190
51601
  function readPidFile(pidPath) {
50191
51602
  try {
@@ -50327,7 +51738,7 @@ function resolveLocalDaemonState(options = {}) {
50327
51738
  const home = resolveAppostleHome(env);
50328
51739
  const config = loadConfig(home, { env });
50329
51740
  const pidPath = pidFilePath(home);
50330
- const logPath = path19.join(home, DAEMON_LOG_FILENAME);
51741
+ const logPath = path21.join(home, DAEMON_LOG_FILENAME);
50331
51742
  const pidInfo = existsSync11(pidPath) ? readPidFile(pidPath) : null;
50332
51743
  const running = pidInfo ? isProcessRunning(pidInfo.pid) : false;
50333
51744
  const listen = pidInfo?.listen ?? config.listen;
@@ -50342,7 +51753,7 @@ function resolveLocalDaemonState(options = {}) {
50342
51753
  };
50343
51754
  }
50344
51755
  function tailDaemonLog(home, lines = 30) {
50345
- const logPath = path19.join(resolveLocalAppostleHome(home), DAEMON_LOG_FILENAME);
51756
+ const logPath = path21.join(resolveLocalAppostleHome(home), DAEMON_LOG_FILENAME);
50346
51757
  return tailFile(logPath, lines);
50347
51758
  }
50348
51759
  async function startLocalDaemonDetached(options) {
@@ -50351,7 +51762,7 @@ async function startLocalDaemonDetached(options) {
50351
51762
  }
50352
51763
  const childEnv = buildChildEnv(options);
50353
51764
  const appostleHome = resolveAppostleHome(childEnv);
50354
- const logPath = path19.join(appostleHome, DAEMON_LOG_FILENAME);
51765
+ const logPath = path21.join(appostleHome, DAEMON_LOG_FILENAME);
50355
51766
  const daemonRunnerEntry = resolveDaemonRunnerEntry();
50356
51767
  const child = spawn7(
50357
51768
  process.execPath,
@@ -53076,22 +54487,22 @@ import { Command as Command13 } from "commander";
53076
54487
 
53077
54488
  // ../cli/src/commands/worktree/ls.ts
53078
54489
  import { homedir as homedir7 } from "node:os";
53079
- import { basename as basename7, join as join11, sep as sep3 } from "node:path";
53080
- function shortenPath3(path24) {
54490
+ import { basename as basename7, join as join13, sep as sep3 } from "node:path";
54491
+ function shortenPath3(path26) {
53081
54492
  const home = process.env.HOME;
53082
- if (home && path24.startsWith(home)) {
53083
- return "~" + path24.slice(home.length);
54493
+ if (home && path26.startsWith(home)) {
54494
+ return "~" + path26.slice(home.length);
53084
54495
  }
53085
- return path24;
54496
+ return path26;
53086
54497
  }
53087
- function extractWorktreeName(path24) {
53088
- return basename7(path24);
54498
+ function extractWorktreeName(path26) {
54499
+ return basename7(path26);
53089
54500
  }
53090
54501
  function resolveAppostleHomePath() {
53091
- return process.env.APPOSTLE_HOME ?? join11(homedir7(), ".appostle");
54502
+ return process.env.APPOSTLE_HOME ?? join13(homedir7(), ".appostle");
53092
54503
  }
53093
54504
  function resolveAppostleWorktreesDir() {
53094
- return join11(resolveAppostleHomePath(), "worktrees");
54505
+ return join13(resolveAppostleHomePath(), "worktrees");
53095
54506
  }
53096
54507
  function isAgentInManagedWorktree(agentCwd) {
53097
54508
  const worktreesDir = resolveAppostleWorktreesDir();
@@ -53165,7 +54576,7 @@ async function runLsCommand7(options, _command) {
53165
54576
  }
53166
54577
 
53167
54578
  // ../cli/src/commands/worktree/archive.ts
53168
- import path20 from "path";
54579
+ import path22 from "path";
53169
54580
  var archiveSchema2 = {
53170
54581
  idField: "name",
53171
54582
  columns: [
@@ -53209,7 +54620,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
53209
54620
  throw error;
53210
54621
  }
53211
54622
  const worktree = listResponse.worktrees.find((wt) => {
53212
- const name = path20.basename(wt.worktreePath);
54623
+ const name = path22.basename(wt.worktreePath);
53213
54624
  return name === nameArg || wt.branchName === nameArg;
53214
54625
  });
53215
54626
  if (!worktree) {
@@ -53231,7 +54642,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
53231
54642
  };
53232
54643
  throw error;
53233
54644
  }
53234
- const worktreeName = path20.basename(worktree.worktreePath) || nameArg;
54645
+ const worktreeName = path22.basename(worktree.worktreePath) || nameArg;
53235
54646
  return {
53236
54647
  type: "single",
53237
54648
  data: {
@@ -53272,7 +54683,7 @@ function createWorktreeCommand() {
53272
54683
  import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
53273
54684
  import { Command as Command14, Option as Option4 } from "commander";
53274
54685
  import { writeFileSync as writeFileSync5 } from "node:fs";
53275
- import path21 from "node:path";
54686
+ import path23 from "node:path";
53276
54687
  var DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1e3;
53277
54688
  var OnboardCancelledError = class extends Error {
53278
54689
  };
@@ -53315,7 +54726,7 @@ function toCliOverrides(options) {
53315
54726
  return cliOverrides;
53316
54727
  }
53317
54728
  function savePersistedConfig2(appostleHome, config) {
53318
- const configPath = path21.join(appostleHome, "config.json");
54729
+ const configPath = path23.join(appostleHome, "config.json");
53319
54730
  writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
53320
54731
  `);
53321
54732
  }
@@ -53436,7 +54847,7 @@ ${recentLogs}` : null
53436
54847
  );
53437
54848
  }
53438
54849
  function printNextSteps(pairingUrl, appostleHome, richUi) {
53439
- const daemonLogPath = path21.join(appostleHome, "daemon.log");
54850
+ const daemonLogPath = path23.join(appostleHome, "daemon.log");
53440
54851
  const nextStepsLines = [
53441
54852
  pairingUrl ? "1. Open Appostle and scan the QR code above, or paste the pairing link." : "1. Open Appostle and connect to your daemon.",
53442
54853
  "2. Web app: https://appostle.app",
@@ -53690,18 +55101,18 @@ function createCli() {
53690
55101
  // ../cli/src/classify.ts
53691
55102
  import { existsSync as existsSync12, statSync as statSync3 } from "node:fs";
53692
55103
  import { homedir as homedir8 } from "node:os";
53693
- import path22 from "node:path";
55104
+ import path24 from "node:path";
53694
55105
  function expandUserPath2(inputPath) {
53695
55106
  if (inputPath === "~") {
53696
55107
  return homedir8();
53697
55108
  }
53698
55109
  if (inputPath.startsWith("~/")) {
53699
- return path22.join(homedir8(), inputPath.slice(2));
55110
+ return path24.join(homedir8(), inputPath.slice(2));
53700
55111
  }
53701
55112
  return inputPath;
53702
55113
  }
53703
55114
  function isExistingDirectory(input) {
53704
- const resolvedPath = path22.resolve(input.cwd, expandUserPath2(input.pathArg));
55115
+ const resolvedPath = path24.resolve(input.cwd, expandUserPath2(input.pathArg));
53705
55116
  if (!existsSync12(resolvedPath)) {
53706
55117
  return false;
53707
55118
  }
@@ -53721,7 +55132,7 @@ function classifyInvocation(input) {
53721
55132
  if (isExistingDirectory({ pathArg: firstArg, cwd: input.cwd })) {
53722
55133
  return {
53723
55134
  kind: "open-project",
53724
- resolvedPath: path22.resolve(input.cwd, expandUserPath2(firstArg))
55135
+ resolvedPath: path24.resolve(input.cwd, expandUserPath2(firstArg))
53725
55136
  };
53726
55137
  }
53727
55138
  return { kind: "cli", argv: input.argv };
@@ -53731,12 +55142,12 @@ function classifyInvocation(input) {
53731
55142
  import { existsSync as existsSync13 } from "node:fs";
53732
55143
  import { spawn as spawn8 } from "node:child_process";
53733
55144
  import { homedir as homedir9 } from "node:os";
53734
- import path23 from "node:path";
55145
+ import path25 from "node:path";
53735
55146
  function findDesktopApp() {
53736
55147
  if (process.platform === "darwin") {
53737
55148
  const candidates = [
53738
55149
  "/Applications/Appostle.app",
53739
- path23.join(homedir9(), "Applications", "Appostle.app")
55150
+ path25.join(homedir9(), "Applications", "Appostle.app")
53740
55151
  ];
53741
55152
  for (const candidate of candidates) {
53742
55153
  if (existsSync13(candidate)) {
@@ -53749,7 +55160,7 @@ function findDesktopApp() {
53749
55160
  const candidates = [
53750
55161
  "/usr/bin/Appostle",
53751
55162
  "/opt/Appostle/Appostle",
53752
- path23.join(homedir9(), "Applications", "Appostle.AppImage")
55163
+ path25.join(homedir9(), "Applications", "Appostle.AppImage")
53753
55164
  ];
53754
55165
  for (const candidate of candidates) {
53755
55166
  if (existsSync13(candidate)) {
@@ -53763,7 +55174,7 @@ function findDesktopApp() {
53763
55174
  if (!localAppData) {
53764
55175
  return null;
53765
55176
  }
53766
- const candidate = path23.join(localAppData, "Programs", "Appostle", "Appostle.exe");
55177
+ const candidate = path25.join(localAppData, "Programs", "Appostle", "Appostle.exe");
53767
55178
  return existsSync13(candidate) ? candidate : null;
53768
55179
  }
53769
55180
  return null;