appostle-installer 0.0.8 → 0.0.10

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 ({
@@ -1664,7 +1664,8 @@ function toAgentPayload(agent, options) {
1664
1664
  pendingPermissions: sanitizePendingPermissions(agent.pendingPermissions),
1665
1665
  persistence: sanitizePersistenceHandle(agent.persistence),
1666
1666
  title: options?.title ?? null,
1667
- labels: agent.labels
1667
+ labels: agent.labels,
1668
+ internal: agent.internal
1668
1669
  };
1669
1670
  const usage = sanitizeUsage(agent.lastUsage);
1670
1671
  if (usage !== void 0) {
@@ -1734,7 +1735,8 @@ function buildStoredAgentPayload(record, providerRegistry, logger) {
1734
1735
  attentionReason: record.attentionReason ?? null,
1735
1736
  attentionTimestamp: record.attentionTimestamp ?? null,
1736
1737
  archivedAt: record.archivedAt ?? null,
1737
- labels: record.labels
1738
+ labels: record.labels,
1739
+ internal: record.internal
1738
1740
  };
1739
1741
  }
1740
1742
  function resolveStoredAgentPayloadUpdatedAt(record) {
@@ -1783,34 +1785,42 @@ function cloneAvailableModes(modes) {
1783
1785
  function normalizeFeatures(features) {
1784
1786
  return Array.isArray(features) ? features.map((feature) => ({ ...feature })) : [];
1785
1787
  }
1786
- function sanitizeOptionalJson(value) {
1788
+ var SANITIZE_MAX_DEPTH = 32;
1789
+ function sanitizeOptionalJson(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
1787
1790
  if (value === void 0) {
1788
1791
  return void 0;
1789
1792
  }
1790
1793
  if (value === null) {
1791
1794
  return null;
1792
1795
  }
1793
- if (Array.isArray(value)) {
1794
- const sanitized = value.map((item) => sanitizeOptionalJson(item)).filter((item) => item !== void 0);
1795
- return sanitized;
1796
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1797
+ return value;
1796
1798
  }
1797
1799
  if (value instanceof Date) {
1798
1800
  return value.toISOString();
1799
1801
  }
1800
- if (typeof value === "object") {
1801
- const result = {};
1802
- for (const [key, val] of Object.entries(value)) {
1803
- const sanitized = sanitizeOptionalJson(val);
1804
- if (sanitized !== void 0) {
1805
- result[key] = sanitized;
1806
- }
1807
- }
1808
- return Object.keys(result).length ? result : void 0;
1802
+ if (typeof value !== "object") {
1803
+ return void 0;
1809
1804
  }
1810
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1811
- return value;
1805
+ if (depth >= SANITIZE_MAX_DEPTH) {
1806
+ return void 0;
1812
1807
  }
1813
- return void 0;
1808
+ if (seen.has(value)) {
1809
+ return void 0;
1810
+ }
1811
+ seen.add(value);
1812
+ if (Array.isArray(value)) {
1813
+ const sanitized = value.map((item) => sanitizeOptionalJson(item, seen, depth + 1)).filter((item) => item !== void 0);
1814
+ return sanitized;
1815
+ }
1816
+ const result = {};
1817
+ for (const [key, val] of Object.entries(value)) {
1818
+ const sanitized = sanitizeOptionalJson(val, seen, depth + 1);
1819
+ if (sanitized !== void 0) {
1820
+ result[key] = sanitized;
1821
+ }
1822
+ }
1823
+ return Object.keys(result).length ? result : void 0;
1814
1824
  }
1815
1825
  function isJsonObject(value) {
1816
1826
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1871,6 +1881,34 @@ function sanitizeUsage(value) {
1871
1881
  } else if (contextWindowUsedTokens !== void 0 && contextWindowUsedTokens !== null) {
1872
1882
  return void 0;
1873
1883
  }
1884
+ const rateLimits = sanitized.rateLimits;
1885
+ if (Array.isArray(rateLimits)) {
1886
+ const VALID_RATE_LIMIT_TYPES = /* @__PURE__ */ new Set([
1887
+ "five_hour",
1888
+ "seven_day",
1889
+ "seven_day_opus",
1890
+ "seven_day_sonnet",
1891
+ "overage"
1892
+ ]);
1893
+ const sanitizedRateLimits = [];
1894
+ for (const entry of rateLimits) {
1895
+ if (!entry || typeof entry !== "object") continue;
1896
+ const { rateLimitType, utilization, resetsAt } = entry;
1897
+ if (typeof rateLimitType !== "string" || !VALID_RATE_LIMIT_TYPES.has(rateLimitType)) continue;
1898
+ if (typeof utilization !== "number" || !Number.isFinite(utilization)) continue;
1899
+ const limit = {
1900
+ rateLimitType,
1901
+ utilization
1902
+ };
1903
+ if (typeof resetsAt === "number" && Number.isFinite(resetsAt)) {
1904
+ limit.resetsAt = resetsAt;
1905
+ }
1906
+ sanitizedRateLimits.push(limit);
1907
+ }
1908
+ if (sanitizedRateLimits.length > 0) {
1909
+ result.rateLimits = sanitizedRateLimits;
1910
+ }
1911
+ }
1874
1912
  return Object.keys(result).length ? result : void 0;
1875
1913
  }
1876
1914
  function sanitizeRuntimeInfo(runtimeInfo) {
@@ -2647,6 +2685,13 @@ var QuestRecordSchema = z8.object({
2647
2685
  cwd: z8.string(),
2648
2686
  /** User's task prompt — fed to every card unless overridden per-card. */
2649
2687
  prompt: z8.string(),
2688
+ /**
2689
+ * Short auto-generated label for the quest, derived from the user prompt by a
2690
+ * small internal metadata agent (mirrors how single-agent sessions get a
2691
+ * title). Empty string until generation finishes (or if it fails). Optional +
2692
+ * default so old persisted records remain valid.
2693
+ */
2694
+ name: z8.string().optional().default(""),
2650
2695
  defaultProvider: AgentProviderSchema,
2651
2696
  defaultModel: z8.string().nullable(),
2652
2697
  termination: QuestTerminationSchema,
@@ -2717,6 +2762,8 @@ var QuestListItemSchema = z9.object({
2717
2762
  status: QuestStatusSchema,
2718
2763
  cwd: z9.string(),
2719
2764
  prompt: z9.string(),
2765
+ /** Auto-generated short label. Optional for backward compat with old daemons. */
2766
+ name: z9.string().optional().default(""),
2720
2767
  createdAt: z9.string(),
2721
2768
  updatedAt: z9.string()
2722
2769
  });
@@ -3068,13 +3115,25 @@ var AgentCapabilityFlagsSchema = z10.object({
3068
3115
  supportsReasoningStream: z10.boolean(),
3069
3116
  supportsToolInvocations: z10.boolean()
3070
3117
  });
3118
+ var AgentRateLimitSchema = z10.object({
3119
+ rateLimitType: z10.enum([
3120
+ "five_hour",
3121
+ "seven_day",
3122
+ "seven_day_opus",
3123
+ "seven_day_sonnet",
3124
+ "overage"
3125
+ ]),
3126
+ utilization: z10.number(),
3127
+ resetsAt: z10.number().optional()
3128
+ });
3071
3129
  var AgentUsageSchema = z10.object({
3072
3130
  inputTokens: z10.number().optional(),
3073
3131
  cachedInputTokens: z10.number().optional(),
3074
3132
  outputTokens: z10.number().optional(),
3075
3133
  totalCostUsd: z10.number().optional(),
3076
3134
  contextWindowMaxTokens: z10.number().optional(),
3077
- contextWindowUsedTokens: z10.number().optional()
3135
+ contextWindowUsedTokens: z10.number().optional(),
3136
+ rateLimits: z10.array(AgentRateLimitSchema).optional()
3078
3137
  });
3079
3138
  var McpStdioServerConfigSchema = z10.object({
3080
3139
  type: z10.literal("stdio"),
@@ -3314,7 +3373,46 @@ var AgentTimelineItemPayloadSchema = z10.union([
3314
3373
  z10.object({
3315
3374
  type: z10.literal("user_message"),
3316
3375
  text: z10.string(),
3317
- 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()
3318
3416
  }),
3319
3417
  z10.object({
3320
3418
  type: z10.literal("assistant_message"),
@@ -3460,7 +3558,15 @@ var AgentSnapshotPayloadSchema = z10.object({
3460
3558
  archivedAt: z10.string().nullable().optional(),
3461
3559
  // Fork lineage — optional for backward compat with pre-fork agents.
3462
3560
  parentAgentId: z10.string().optional(),
3463
- forkedFromMessageUuid: z10.string().optional()
3561
+ forkedFromMessageUuid: z10.string().optional(),
3562
+ /**
3563
+ * True when the agent is a system-spawned helper (orchestrator queens,
3564
+ * handoff workers). Optional for backward compat — older clients ignore
3565
+ * the field. Newer clients filter these out of workspace tab lists / agent
3566
+ * lists at display time. The agent itself is a real, full-featured session
3567
+ * in every other respect; `internal` is a UI visibility hint, nothing more.
3568
+ */
3569
+ internal: z10.boolean().optional()
3464
3570
  });
3465
3571
  var VoiceAudioChunkMessageSchema = z10.object({
3466
3572
  type: z10.literal("voice_audio_chunk"),
@@ -3586,8 +3692,34 @@ var AgentAttachmentsSchema = z10.unknown().transform(normalizeAgentAttachments).
3586
3692
  var ImageAttachmentSchema = z10.object({
3587
3693
  data: z10.string(),
3588
3694
  // base64 encoded image
3589
- mimeType: z10.string()
3695
+ mimeType: z10.string(),
3590
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()
3591
3723
  });
3592
3724
  var SendAgentMessageSchema = z10.object({
3593
3725
  type: z10.literal("send_agent_message"),
@@ -3596,6 +3728,7 @@ var SendAgentMessageSchema = z10.object({
3596
3728
  messageId: z10.string().optional(),
3597
3729
  // Client-provided ID for deduplication
3598
3730
  images: z10.array(ImageAttachmentSchema).optional(),
3731
+ files: z10.array(FileAttachmentSchema).optional(),
3599
3732
  attachments: AgentAttachmentsSchema
3600
3733
  });
3601
3734
  var FetchAgentsRequestMessageSchema = z10.object({
@@ -3660,6 +3793,7 @@ var SendAgentMessageRequestSchema = z10.object({
3660
3793
  messageId: z10.string().optional(),
3661
3794
  // Client-provided ID for deduplication
3662
3795
  images: z10.array(ImageAttachmentSchema).optional(),
3796
+ files: z10.array(FileAttachmentSchema).optional(),
3663
3797
  attachments: AgentAttachmentsSchema
3664
3798
  });
3665
3799
  var WaitForFinishRequestSchema = z10.object({
@@ -3721,6 +3855,7 @@ var CreateAgentRequestMessageSchema = z10.object({
3721
3855
  clientMessageId: z10.string().optional(),
3722
3856
  outputSchema: z10.record(z10.unknown()).optional(),
3723
3857
  images: z10.array(ImageAttachmentSchema).optional(),
3858
+ files: z10.array(FileAttachmentSchema).optional(),
3724
3859
  attachments: AgentAttachmentsSchema,
3725
3860
  git: GitSetupOptionsSchema.optional(),
3726
3861
  labels: z10.record(z10.string()).default({}),
@@ -4517,6 +4652,131 @@ var LinkAccountResponseMessageSchema = z10.object({
4517
4652
  })
4518
4653
  ])
4519
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
+ });
4520
4780
  var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
4521
4781
  VoiceAudioChunkMessageSchema,
4522
4782
  AbortRequestMessageSchema,
@@ -4658,7 +4918,12 @@ var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
4658
4918
  QuestListRequestSchema,
4659
4919
  QuestInspectRequestSchema,
4660
4920
  QuestStopRequestSchema,
4661
- RolesListRequestSchema
4921
+ RolesListRequestSchema,
4922
+ ListSessionUploadsRequestSchema,
4923
+ DeleteSessionUploadRequestSchema,
4924
+ ListSessionImagesRequestSchema,
4925
+ DeleteSessionImageRequestSchema,
4926
+ FetchAttachmentBytesRequestSchema
4662
4927
  ]);
4663
4928
  var ActivityLogPayloadSchema = z10.object({
4664
4929
  id: z10.string(),
@@ -6202,7 +6467,12 @@ var SessionOutboundMessageSchema = z10.discriminatedUnion("type", [
6202
6467
  BrandAssetCopyResponseSchema,
6203
6468
  BrandAssetUploadResponseSchema,
6204
6469
  GetVapidPublicKeyResponseSchema,
6205
- LinkAccountResponseMessageSchema
6470
+ LinkAccountResponseMessageSchema,
6471
+ ListSessionUploadsResponseSchema,
6472
+ DeleteSessionUploadResponseSchema,
6473
+ ListSessionImagesResponseSchema,
6474
+ DeleteSessionImageResponseSchema,
6475
+ FetchAttachmentBytesResponseSchema
6206
6476
  ]);
6207
6477
  var WSPingMessageSchema = z10.object({
6208
6478
  type: z10.literal("ping")
@@ -8604,14 +8874,14 @@ var DictationStreamManager = class {
8604
8874
  PCM_CHANNELS,
8605
8875
  PCM_BITS_PER_SAMPLE
8606
8876
  );
8607
- const path24 = await maybePersistDictationDebugAudio(
8877
+ const path26 = await maybePersistDictationDebugAudio(
8608
8878
  wavBuffer,
8609
8879
  { sessionId: state.sessionId, dictationId: state.dictationId, format: "audio/wav" },
8610
8880
  this.logger,
8611
8881
  state.debugChunkWriter?.folder
8612
8882
  );
8613
- state.debugRecordingPath = path24;
8614
- return path24;
8883
+ state.debugRecordingPath = path26;
8884
+ return path26;
8615
8885
  }
8616
8886
  failDictationStream(dictationId, error, retryable) {
8617
8887
  this.emit({
@@ -9640,6 +9910,9 @@ async function sendPromptToAgent(params) {
9640
9910
  messageId,
9641
9911
  runOptions,
9642
9912
  sessionMode,
9913
+ userMessageImages,
9914
+ userMessageFiles,
9915
+ userMessageTextIsSynthesized,
9643
9916
  logger
9644
9917
  } = params;
9645
9918
  await unarchiveAgentState(agentStorage, agentManager, agentId);
@@ -9654,7 +9927,10 @@ async function sendPromptToAgent(params) {
9654
9927
  try {
9655
9928
  agentManager.recordUserMessage(agentId, userMessageText, {
9656
9929
  messageId,
9657
- emitState: false
9930
+ emitState: false,
9931
+ ...userMessageImages && userMessageImages.length > 0 ? { images: userMessageImages } : {},
9932
+ ...userMessageFiles && userMessageFiles.length > 0 ? { files: userMessageFiles } : {},
9933
+ ...userMessageTextIsSynthesized ? { textIsSynthesized: true } : {}
9658
9934
  });
9659
9935
  } catch (error) {
9660
9936
  logger.error({ err: error, agentId }, "Failed to record user message");
@@ -9665,6 +9941,544 @@ async function sendPromptToAgent(params) {
9665
9941
  });
9666
9942
  }
9667
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
+
9668
10482
  // ../server/src/server/session.ts
9669
10483
  import { experimental_createMCPClient } from "ai";
9670
10484
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -9857,14 +10671,14 @@ function parseDiff(diffText) {
9857
10671
  const firstLine = lines[0];
9858
10672
  const isNew = section.includes("new file mode") || section.includes("--- /dev/null");
9859
10673
  const isDeleted = section.includes("deleted file mode") || section.includes("+++ /dev/null");
9860
- let path24 = "unknown";
10674
+ let path26 = "unknown";
9861
10675
  const pathMatch = firstLine.match(/a\/(.*?) b\//);
9862
10676
  if (pathMatch) {
9863
- path24 = pathMatch[1];
10677
+ path26 = pathMatch[1];
9864
10678
  } else {
9865
10679
  const newFileMatch = firstLine.match(/b\/(.+)$/);
9866
10680
  if (newFileMatch) {
9867
- path24 = newFileMatch[1];
10681
+ path26 = newFileMatch[1];
9868
10682
  }
9869
10683
  }
9870
10684
  const hunks = [];
@@ -9908,7 +10722,7 @@ function parseDiff(diffText) {
9908
10722
  if (currentHunk) {
9909
10723
  hunks.push(currentHunk);
9910
10724
  }
9911
- files.push({ path: path24, isNew, isDeleted, additions, deletions, hunks });
10725
+ files.push({ path: path26, isNew, isDeleted, additions, deletions, hunks });
9912
10726
  }
9913
10727
  return files;
9914
10728
  }
@@ -9964,9 +10778,9 @@ function buildTokenLookup(lineMap, highlighted) {
9964
10778
  }
9965
10779
  return lookup2;
9966
10780
  }
9967
- function buildFullFileTokenLookup(fileContent, path24) {
10781
+ function buildFullFileTokenLookup(fileContent, path26) {
9968
10782
  const lookup2 = /* @__PURE__ */ new Map();
9969
- const highlighted = highlightCode(fileContent, path24);
10783
+ const highlighted = highlightCode(fileContent, path26);
9970
10784
  for (let i = 0; i < highlighted.length; i++) {
9971
10785
  lookup2.set(i + 1, highlighted[i]);
9972
10786
  }
@@ -11667,11 +12481,11 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
11667
12481
  }
11668
12482
  continue;
11669
12483
  }
11670
- const path24 = tabParts[1];
11671
- if (!path24) continue;
12484
+ const path26 = tabParts[1];
12485
+ if (!path26) continue;
11672
12486
  const code = rawStatus[0];
11673
12487
  changes.push({
11674
- path: path24,
12488
+ path: path26,
11675
12489
  status: rawStatus,
11676
12490
  isNew: code === "A",
11677
12491
  isDeleted: code === "D"
@@ -11706,9 +12520,9 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
11706
12520
  }
11707
12521
  return Array.from(byPath.values());
11708
12522
  }
11709
- async function readGitFileContentAtRef(cwd, ref, path24) {
12523
+ async function readGitFileContentAtRef(cwd, ref, path26) {
11710
12524
  try {
11711
- const { stdout } = await runGitCommand(["show", `${ref}:${path24}`], {
12525
+ const { stdout } = await runGitCommand(["show", `${ref}:${path26}`], {
11712
12526
  cwd,
11713
12527
  env: READ_ONLY_GIT_ENV2
11714
12528
  });
@@ -11769,21 +12583,21 @@ async function getTrackedNumstatByPath(cwd, ref, ignoreWhitespace = false) {
11769
12583
  const additionsField = parts[0] ?? "";
11770
12584
  const deletionsField = parts[1] ?? "";
11771
12585
  const rawPath = parts.slice(2).join(" ");
11772
- const path24 = normalizeNumstatPath(rawPath);
11773
- if (!path24) {
12586
+ const path26 = normalizeNumstatPath(rawPath);
12587
+ if (!path26) {
11774
12588
  continue;
11775
12589
  }
11776
12590
  if (additionsField === "-" || deletionsField === "-") {
11777
- stats.set(path24, { additions: 0, deletions: 0, isBinary: true });
12591
+ stats.set(path26, { additions: 0, deletions: 0, isBinary: true });
11778
12592
  continue;
11779
12593
  }
11780
12594
  const additions = Number.parseInt(additionsField, 10);
11781
12595
  const deletions = Number.parseInt(deletionsField, 10);
11782
12596
  if (Number.isNaN(additions) || Number.isNaN(deletions)) {
11783
- stats.set(path24, null);
12597
+ stats.set(path26, null);
11784
12598
  continue;
11785
12599
  }
11786
- stats.set(path24, { additions, deletions, isBinary: false });
12600
+ stats.set(path26, { additions, deletions, isBinary: false });
11787
12601
  }
11788
12602
  return stats;
11789
12603
  }
@@ -12368,10 +13182,10 @@ async function listUncommittedFiles(cwd) {
12368
13182
  if (dest) results.push({ path: dest, changeType: code });
12369
13183
  continue;
12370
13184
  }
12371
- const path24 = parts[1];
12372
- if (!path24) continue;
13185
+ const path26 = parts[1];
13186
+ if (!path26) continue;
12373
13187
  if (code === "A" || code === "M" || code === "D") {
12374
- results.push({ path: path24, changeType: code });
13188
+ results.push({ path: path26, changeType: code });
12375
13189
  }
12376
13190
  }
12377
13191
  } catch {
@@ -14069,11 +14883,11 @@ async function spawnWorktreeScripts(options) {
14069
14883
  }
14070
14884
 
14071
14885
  // ../server/src/server/agent/providers/claude-agent.ts
14072
- import { randomUUID } from "node:crypto";
14073
- import fs from "node:fs";
14886
+ import { randomUUID as randomUUID2 } from "node:crypto";
14887
+ import fs3 from "node:fs";
14074
14888
  import { promises } from "node:fs";
14075
14889
  import os2 from "node:os";
14076
- import path6 from "node:path";
14890
+ import path8 from "node:path";
14077
14891
  import {
14078
14892
  query
14079
14893
  } from "@anthropic-ai/claude-agent-sdk";
@@ -15148,7 +15962,7 @@ function mapClaudeCanceledToolCall(params) {
15148
15962
  }
15149
15963
 
15150
15964
  // ../server/src/server/agent/providers/claude/task-notification-tool-call.ts
15151
- import { createHash as createHash2 } from "node:crypto";
15965
+ import { createHash as createHash3 } from "node:crypto";
15152
15966
  import { z as z21 } from "zod";
15153
15967
  var TASK_NOTIFICATION_MARKER = "<task-notification>";
15154
15968
  var TAG_NAME_PATTERN = /[.*+?^${}()|[\]\\]/g;
@@ -15285,7 +16099,7 @@ function buildTaskNotificationCallId(envelope) {
15285
16099
  return `task_notification_${taskSegment}`;
15286
16100
  }
15287
16101
  const seed = [envelope.status, envelope.summary, envelope.outputFile, envelope.rawText].filter((value) => typeof value === "string").join("|") || "task_notification";
15288
- const digest = createHash2("sha1").update(seed).digest("hex").slice(0, 12);
16102
+ const digest = createHash3("sha1").update(seed).digest("hex").slice(0, 12);
15289
16103
  return `task_notification_${digest}`;
15290
16104
  }
15291
16105
  function buildTaskNotificationLabel(envelope) {
@@ -15464,6 +16278,40 @@ function normalizeClaudeRuntimeModelId(value) {
15464
16278
  return `claude-${family}-${major}-${minor}${suffix}`;
15465
16279
  }
15466
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
+
15467
16315
  // ../server/src/server/agent/providers/claude/partial-json.ts
15468
16316
  function skipWhitespace(input, index) {
15469
16317
  let currentIndex = index;
@@ -16031,62 +16879,8 @@ async function resolveBinaryVersion(binaryPath) {
16031
16879
  }
16032
16880
  }
16033
16881
 
16034
- // ../server/src/server/agent/prompt-attachments.ts
16035
- function renderPromptAttachmentAsText(attachment) {
16036
- switch (attachment.type) {
16037
- case "github_pr": {
16038
- const lines = [`GitHub PR #${attachment.number}: ${attachment.title}`, attachment.url];
16039
- if (attachment.baseRefName) {
16040
- lines.push(`Base: ${attachment.baseRefName}`);
16041
- }
16042
- if (attachment.headRefName) {
16043
- lines.push(`Head: ${attachment.headRefName}`);
16044
- }
16045
- if (attachment.body) {
16046
- lines.push("", attachment.body);
16047
- }
16048
- return lines.join("\n");
16049
- }
16050
- case "github_issue": {
16051
- const lines = [`GitHub Issue #${attachment.number}: ${attachment.title}`, attachment.url];
16052
- if (attachment.body) {
16053
- lines.push("", attachment.body);
16054
- }
16055
- return lines.join("\n");
16056
- }
16057
- case "gitlab_mr": {
16058
- const lines = [`GitLab MR !${attachment.iid}: ${attachment.title}`, attachment.url];
16059
- if (attachment.targetBranch) {
16060
- lines.push(`Target: ${attachment.targetBranch}`);
16061
- }
16062
- if (attachment.sourceBranch) {
16063
- lines.push(`Source: ${attachment.sourceBranch}`);
16064
- }
16065
- if (attachment.body) {
16066
- lines.push("", attachment.body);
16067
- }
16068
- return lines.join("\n");
16069
- }
16070
- case "gitlab_issue": {
16071
- const lines = [`GitLab Issue #${attachment.iid}: ${attachment.title}`, attachment.url];
16072
- if (attachment.body) {
16073
- lines.push("", attachment.body);
16074
- }
16075
- return lines.join("\n");
16076
- }
16077
- }
16078
- }
16079
- function findGitHubPrAttachment(attachments) {
16080
- if (!attachments) {
16081
- return null;
16082
- }
16083
- return attachments.find(
16084
- (attachment) => attachment.type === "github_pr"
16085
- ) ?? null;
16086
- }
16087
-
16088
16882
  // ../server/src/server/agent/plan-filename.ts
16089
- import path5 from "node:path";
16883
+ import path7 from "node:path";
16090
16884
  var MAX_SLUG_LENGTH2 = 40;
16091
16885
  function slugifyPlanName(name) {
16092
16886
  if (typeof name !== "string") {
@@ -16135,12 +16929,12 @@ function extractPlanNameFromFrontmatter(content) {
16135
16929
  }
16136
16930
  function resolvePlanFilename(options) {
16137
16931
  const { originalPath, newSlug, existsSync: existsSync14 } = options;
16138
- const dir = path5.dirname(originalPath);
16932
+ const dir = path7.dirname(originalPath);
16139
16933
  const base = `${newSlug}.md`;
16140
- let candidate = path5.join(dir, base);
16934
+ let candidate = path7.join(dir, base);
16141
16935
  let counter = 2;
16142
16936
  while (candidate !== originalPath && existsSync14(candidate)) {
16143
- candidate = path5.join(dir, `${newSlug}-${counter}.md`);
16937
+ candidate = path7.join(dir, `${newSlug}-${counter}.md`);
16144
16938
  counter += 1;
16145
16939
  }
16146
16940
  return candidate;
@@ -16691,18 +17485,20 @@ function toClaudeSdkMcpConfig(config) {
16691
17485
  url: config.url,
16692
17486
  headers: config.headers
16693
17487
  };
17488
+ case "sdk":
17489
+ return config.config;
16694
17490
  }
16695
17491
  }
16696
17492
  var homeMcpServersCache = null;
16697
17493
  function loadHomeMcpServers(logger) {
16698
17494
  try {
16699
17495
  const home = process.env.HOME ?? os2.homedir();
16700
- const filePath = path6.join(home, ".claude.json");
16701
- const stat5 = fs.statSync(filePath);
17496
+ const filePath = path8.join(home, ".claude.json");
17497
+ const stat5 = fs3.statSync(filePath);
16702
17498
  if (homeMcpServersCache && homeMcpServersCache.mtimeMs === stat5.mtimeMs) {
16703
17499
  return homeMcpServersCache.servers;
16704
17500
  }
16705
- const raw = fs.readFileSync(filePath, "utf8");
17501
+ const raw = fs3.readFileSync(filePath, "utf8");
16706
17502
  const parsed = JSON.parse(raw);
16707
17503
  if (!isMetadata(parsed)) return null;
16708
17504
  const rawServers = parsed.mcpServers;
@@ -17049,8 +17845,8 @@ var ClaudeAgentClient = class {
17049
17845
  return getClaudeModels();
17050
17846
  }
17051
17847
  async listPersistedAgents(options) {
17052
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path6.join(os2.homedir(), ".claude");
17053
- 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");
17054
17850
  if (!await pathExists2(projectsRoot)) {
17055
17851
  return [];
17056
17852
  }
@@ -17271,9 +18067,10 @@ var ClaudeAgentSession = class {
17271
18067
  this.userMessageIds = [];
17272
18068
  this.recentStderr = "";
17273
18069
  this.closed = false;
18070
+ this.fastModeEnabled = false;
17274
18071
  this.handlePermissionRequest = async (toolName, input, options) => {
17275
18072
  this.maybeRewritePlanFilename(toolName, input);
17276
- const requestId = `permission-${randomUUID()}`;
18073
+ const requestId = `permission-${randomUUID2()}`;
17277
18074
  const kind = resolvePermissionKind(toolName, input);
17278
18075
  if (kind === "tool") {
17279
18076
  if (this.currentMode === "bypassPermissions") {
@@ -17369,10 +18166,17 @@ var ClaudeAgentSession = class {
17369
18166
  if (this.currentMode !== "plan") {
17370
18167
  this.planResumeMode = this.currentMode;
17371
18168
  }
18169
+ this.fastModeEnabled = Boolean(config.featureValues?.fast_mode);
17372
18170
  }
17373
18171
  get id() {
17374
18172
  return this.claudeSessionId;
17375
18173
  }
18174
+ get features() {
18175
+ return buildClaudeFeatures({
18176
+ modelId: this.config.model,
18177
+ fastModeEnabled: this.fastModeEnabled
18178
+ });
18179
+ }
17376
18180
  async getRuntimeInfo() {
17377
18181
  if (this.cachedRuntimeInfo) {
17378
18182
  return { ...this.cachedRuntimeInfo };
@@ -17611,6 +18415,41 @@ var ClaudeAgentSession = class {
17611
18415
  }
17612
18416
  this.queryRestartNeeded = true;
17613
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
+ }
17614
18453
  getPendingPermissions() {
17615
18454
  return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
17616
18455
  }
@@ -17862,12 +18701,12 @@ var ClaudeAgentSession = class {
17862
18701
  return [];
17863
18702
  }
17864
18703
  const historyPath = this.resolveHistoryPath(this.claudeSessionId);
17865
- if (!historyPath || !fs.existsSync(historyPath)) {
18704
+ if (!historyPath || !fs3.existsSync(historyPath)) {
17866
18705
  return [];
17867
18706
  }
17868
18707
  try {
17869
18708
  const ids = [];
17870
- const content = fs.readFileSync(historyPath, "utf8");
18709
+ const content = fs3.readFileSync(historyPath, "utf8");
17871
18710
  for (const line of content.split(/\n+/)) {
17872
18711
  const trimmed = line.trim();
17873
18712
  if (!trimmed) continue;
@@ -18013,6 +18852,10 @@ var ClaudeAgentSession = class {
18013
18852
  ...this.claudeSessionId ? { resume: this.claudeSessionId } : {},
18014
18853
  ...thinking ? { thinking } : {},
18015
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 } } : {},
18016
18859
  ...this.config.extra?.claude
18017
18860
  };
18018
18861
  if (this.config.mcpServers) {
@@ -18060,7 +18903,9 @@ var ClaudeAgentSession = class {
18060
18903
  if (Array.isArray(prompt)) {
18061
18904
  for (const chunk of prompt) {
18062
18905
  if (chunk.type === "text") {
18063
- content.push({ type: "text", text: chunk.text });
18906
+ if (chunk.text.length > 0) {
18907
+ content.push({ type: "text", text: chunk.text });
18908
+ }
18064
18909
  } else if (chunk.type === "image") {
18065
18910
  content.push({
18066
18911
  type: "image",
@@ -18071,13 +18916,19 @@ var ClaudeAgentSession = class {
18071
18916
  }
18072
18917
  });
18073
18918
  } else if (chunk.type === "github_pr" || chunk.type === "github_issue" || chunk.type === "gitlab_mr" || chunk.type === "gitlab_issue") {
18074
- 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
+ }
18075
18923
  }
18076
18924
  }
18077
- } else {
18925
+ } else if (prompt.length > 0) {
18078
18926
  content.push({ type: "text", text: prompt });
18079
18927
  }
18080
- const messageId = randomUUID();
18928
+ if (content.length === 0) {
18929
+ content.push({ type: "text", text: "(empty message)" });
18930
+ }
18931
+ const messageId = randomUUID2();
18081
18932
  this.rememberUserMessageId(messageId);
18082
18933
  return {
18083
18934
  type: "user",
@@ -18610,6 +19461,43 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18610
19461
  }
18611
19462
  break;
18612
19463
  }
19464
+ case "rate_limit_event": {
19465
+ const info = message.rate_limit_info;
19466
+ if (info && typeof info === "object") {
19467
+ const { rateLimitType, utilization, resetsAt } = info;
19468
+ const VALID_TYPES = /* @__PURE__ */ new Set([
19469
+ "five_hour",
19470
+ "seven_day",
19471
+ "seven_day_opus",
19472
+ "seven_day_sonnet",
19473
+ "overage"
19474
+ ]);
19475
+ if (typeof rateLimitType === "string" && VALID_TYPES.has(rateLimitType) && typeof utilization === "number") {
19476
+ const rateLimit = {
19477
+ rateLimitType,
19478
+ utilization
19479
+ };
19480
+ if (typeof resetsAt === "number") {
19481
+ rateLimit.resetsAt = resetsAt;
19482
+ }
19483
+ const existingLimits = this.lastRateLimits ?? [];
19484
+ const updated = existingLimits.filter((l) => l.rateLimitType !== rateLimitType);
19485
+ updated.push(rateLimit);
19486
+ this.lastRateLimits = updated;
19487
+ const usage = {
19488
+ rateLimits: updated
19489
+ };
19490
+ if (this.lastContextWindowMaxTokens !== void 0) {
19491
+ usage.contextWindowMaxTokens = this.lastContextWindowMaxTokens;
19492
+ }
19493
+ if (this.lastContextWindowUsedTokens !== void 0) {
19494
+ usage.contextWindowUsedTokens = this.lastContextWindowUsedTokens;
19495
+ }
19496
+ events.push({ type: "usage_updated", provider: "claude", usage });
19497
+ }
19498
+ }
19499
+ break;
19500
+ }
18613
19501
  default:
18614
19502
  break;
18615
19503
  }
@@ -18718,6 +19606,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18718
19606
  outputTokens: message.usage.output_tokens,
18719
19607
  totalCostUsd: message.total_cost_usd
18720
19608
  };
19609
+ if (this.lastRateLimits !== void 0) {
19610
+ usage.rateLimits = this.lastRateLimits;
19611
+ }
18721
19612
  const contextWindowMaxTokens = extractContextWindowSize(modelUsage ?? message.modelUsage);
18722
19613
  if (contextWindowMaxTokens !== void 0) {
18723
19614
  this.lastContextWindowMaxTokens = contextWindowMaxTokens;
@@ -18790,9 +19681,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18790
19681
  if (typeof filePath !== "string" || typeof content !== "string") {
18791
19682
  return;
18792
19683
  }
18793
- const dir = path6.dirname(filePath);
18794
- const dirName = path6.basename(dir);
18795
- const baseName = path6.basename(filePath);
19684
+ const dir = path8.dirname(filePath);
19685
+ const dirName = path8.basename(dir);
19686
+ const baseName = path8.basename(filePath);
18796
19687
  if (dirName !== ".plans" || !baseName.endsWith(".md")) {
18797
19688
  return;
18798
19689
  }
@@ -18811,7 +19702,7 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18811
19702
  const newPath = resolvePlanFilename({
18812
19703
  originalPath: filePath,
18813
19704
  newSlug: slug,
18814
- existsSync: fs.existsSync
19705
+ existsSync: fs3.existsSync
18815
19706
  });
18816
19707
  if (newPath === filePath) {
18817
19708
  return;
@@ -18899,10 +19790,10 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18899
19790
  loadPersistedHistory(sessionId) {
18900
19791
  try {
18901
19792
  const historyPath = this.resolveHistoryPath(sessionId);
18902
- if (!historyPath || !fs.existsSync(historyPath)) {
19793
+ if (!historyPath || !fs3.existsSync(historyPath)) {
18903
19794
  return;
18904
19795
  }
18905
- this.ingestPersistedHistory(fs.readFileSync(historyPath, "utf8"));
19796
+ this.ingestPersistedHistory(fs3.readFileSync(historyPath, "utf8"));
18906
19797
  } catch (error) {
18907
19798
  }
18908
19799
  }
@@ -18945,9 +19836,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
18945
19836
  const cwd = this.config.cwd;
18946
19837
  if (!cwd) return null;
18947
19838
  const sanitized = sanitizeClaudeProjectPath(cwd);
18948
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path6.join(os2.homedir(), ".claude");
18949
- const dir = path6.join(configDir, "projects", sanitized);
18950
- 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`);
18951
19842
  }
18952
19843
  convertHistoryEntry(entry) {
18953
19844
  return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
@@ -19087,10 +19978,10 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19087
19978
  return void 0;
19088
19979
  }
19089
19980
  const server = entry?.server ?? block.server ?? "tool";
19090
- const tool = entry?.name ?? block.tool_name ?? "tool";
19981
+ const tool2 = entry?.name ?? block.tool_name ?? "tool";
19091
19982
  const content = coerceToolResultContentToString(block.content);
19092
19983
  const input = entry?.input;
19093
- const structured = this.buildStructuredToolResult(server, tool, content, input);
19984
+ const structured = this.buildStructuredToolResult(server, tool2, content, input);
19094
19985
  if (structured) {
19095
19986
  return structured;
19096
19987
  }
@@ -19107,9 +19998,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19107
19998
  }
19108
19999
  return Object.keys(result).length > 0 ? result : void 0;
19109
20000
  }
19110
- buildStructuredToolResult(server, tool, output, input) {
20001
+ buildStructuredToolResult(server, tool2, output, input) {
19111
20002
  const normalizedServer = server.toLowerCase();
19112
- const normalizedTool = tool.toLowerCase();
20003
+ const normalizedTool = tool2.toLowerCase();
19113
20004
  if (normalizedServer.includes("bash") || normalizedServer.includes("shell") || normalizedServer.includes("command") || normalizedTool.includes("bash") || normalizedTool.includes("shell") || normalizedTool.includes("command") || input && (typeof input.command === "string" || Array.isArray(input.command))) {
19114
20005
  const command = this.extractCommandText(input ?? {}) ?? "command";
19115
20006
  return {
@@ -19409,7 +20300,7 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19409
20300
  }
19410
20301
  detectFileKind(filePath) {
19411
20302
  try {
19412
- return fs.existsSync(filePath) ? "update" : "add";
20303
+ return fs3.existsSync(filePath) ? "update" : "add";
19413
20304
  } catch {
19414
20305
  return "update";
19415
20306
  }
@@ -19420,8 +20311,8 @@ ${error.stack ?? ""}` : JSON.stringify(error);
19420
20311
  }
19421
20312
  const cwd = this.config.cwd;
19422
20313
  if (cwd && target.startsWith(cwd)) {
19423
- const relative = path6.relative(cwd, target);
19424
- return relative.length > 0 ? relative : path6.basename(target);
20314
+ const relative = path8.relative(cwd, target);
20315
+ return relative.length > 0 ? relative : path8.basename(target);
19425
20316
  }
19426
20317
  return target;
19427
20318
  }
@@ -19610,7 +20501,7 @@ async function collectRecentClaudeSessions(root, limit) {
19610
20501
  }
19611
20502
  const candidates = [];
19612
20503
  for (const dirName of projectDirs) {
19613
- const projectPath = path6.join(root, dirName);
20504
+ const projectPath = path8.join(root, dirName);
19614
20505
  let stats;
19615
20506
  try {
19616
20507
  stats = await fsPromises.stat(projectPath);
@@ -19630,7 +20521,7 @@ async function collectRecentClaudeSessions(root, limit) {
19630
20521
  if (!file.endsWith(".jsonl")) {
19631
20522
  continue;
19632
20523
  }
19633
- const fullPath = path6.join(projectPath, file);
20524
+ const fullPath = path8.join(projectPath, file);
19634
20525
  try {
19635
20526
  const fileStats = await fsPromises.stat(fullPath);
19636
20527
  candidates.push({ path: fullPath, mtime: fileStats.mtime });
@@ -19805,17 +20696,17 @@ function unescapePartialJsonString(buffer, start) {
19805
20696
 
19806
20697
  // ../server/src/server/agent/providers/codex-app-server-agent.ts
19807
20698
  import { execSync as execSync2 } from "node:child_process";
19808
- import { randomUUID as randomUUID2 } from "node:crypto";
19809
- import fs3 from "node:fs/promises";
20699
+ import { randomUUID as randomUUID3 } from "node:crypto";
20700
+ import fs5 from "node:fs/promises";
19810
20701
  import os4 from "node:os";
19811
- import path8 from "node:path";
20702
+ import path10 from "node:path";
19812
20703
  import readline from "node:readline";
19813
20704
  import { z as z25 } from "zod";
19814
20705
 
19815
20706
  // ../server/src/server/agent/providers/codex-rollout-timeline.ts
19816
- import { promises as fs2 } from "node:fs";
20707
+ import { promises as fs4 } from "node:fs";
19817
20708
  import os3 from "node:os";
19818
- import path7 from "node:path";
20709
+ import path9 from "node:path";
19819
20710
  import { z as z24 } from "zod";
19820
20711
 
19821
20712
  // ../server/src/server/agent/providers/codex/tool-call-mapper.ts
@@ -20267,14 +21158,14 @@ function codexApplyPatchToUnifiedDiff(text) {
20267
21158
  for (const line of lines) {
20268
21159
  const directive = parseCodexApplyPatchDirective(line);
20269
21160
  if (directive) {
20270
- const path24 = normalizeDiffHeaderPath(directive.path);
20271
- if (path24.length > 0) {
21161
+ const path26 = normalizeDiffHeaderPath(directive.path);
21162
+ if (path26.length > 0) {
20272
21163
  if (output.length > 0 && output[output.length - 1] !== "") {
20273
21164
  output.push("");
20274
21165
  }
20275
- const left = directive.kind === "add" ? "/dev/null" : `a/${path24}`;
20276
- const right = directive.kind === "delete" ? "/dev/null" : `b/${path24}`;
20277
- 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}`);
20278
21169
  output.push(`--- ${left}`);
20279
21170
  output.push(`+++ ${right}`);
20280
21171
  sawDiffContent = true;
@@ -20344,9 +21235,9 @@ function asEditTextFields(text) {
20344
21235
  function normalizeRolloutEditInput(input) {
20345
21236
  if (typeof input === "string") {
20346
21237
  const textFields2 = asEditTextFields(input);
20347
- const path24 = extractPatchPrimaryFilePath(input);
21238
+ const path26 = extractPatchPrimaryFilePath(input);
20348
21239
  return {
20349
- ...path24 ? { path: path24 } : {},
21240
+ ...path26 ? { path: path26 } : {},
20350
21241
  ...textFields2.unifiedDiff ? { patch: textFields2.unifiedDiff } : {},
20351
21242
  ...textFields2.newString ? { content: textFields2.newString } : {}
20352
21243
  };
@@ -20423,8 +21314,8 @@ function resolveStatus(rawStatus, error, output) {
20423
21314
  }
20424
21315
  return output !== null && output !== void 0 ? "completed" : "running";
20425
21316
  }
20426
- function buildMcpToolName(server, tool) {
20427
- const trimmedTool = tool.trim();
21317
+ function buildMcpToolName(server, tool2) {
21318
+ const trimmedTool = tool2.trim();
20428
21319
  if (!trimmedTool) {
20429
21320
  return "tool";
20430
21321
  }
@@ -20494,12 +21385,12 @@ function parseFileChangeDiff(entry) {
20494
21385
  ]);
20495
21386
  }
20496
21387
  function toFileChangeEntry(entry, options, fallbackPath) {
20497
- const path24 = parseFileChangePath(entry, options, fallbackPath);
20498
- if (!path24) {
21388
+ const path26 = parseFileChangePath(entry, options, fallbackPath);
21389
+ if (!path26) {
20499
21390
  return null;
20500
21391
  }
20501
21392
  return {
20502
- path: path24,
21393
+ path: path26,
20503
21394
  kind: parseFileChangeKind(entry),
20504
21395
  diff: parseFileChangeDiff(entry)
20505
21396
  };
@@ -20521,12 +21412,12 @@ function parseFileChangeEntries(changes, options) {
20521
21412
  if (singleEntry) {
20522
21413
  return [singleEntry];
20523
21414
  }
20524
- return Object.entries(changes).map(([path24, value]) => {
21415
+ return Object.entries(changes).map(([path26, value]) => {
20525
21416
  if (isRecord2(value)) {
20526
- return toFileChangeEntry(value, options, path24);
21417
+ return toFileChangeEntry(value, options, path26);
20527
21418
  }
20528
21419
  if (typeof value === "string") {
20529
- const normalizedPath = normalizeCodexFilePath(path24.trim(), options?.cwd);
21420
+ const normalizedPath = normalizeCodexFilePath(path26.trim(), options?.cwd);
20530
21421
  if (!normalizedPath) {
20531
21422
  return null;
20532
21423
  }
@@ -20594,11 +21485,11 @@ function mapFileChangeItem(item, options) {
20594
21485
  };
20595
21486
  }
20596
21487
  function mapMcpToolCallItem(item, options) {
20597
- const tool = item.tool.trim();
20598
- if (!tool) {
21488
+ const tool2 = item.tool.trim();
21489
+ if (!tool2) {
20599
21490
  return null;
20600
21491
  }
20601
- const name = buildMcpToolName(item.server, tool);
21492
+ const name = buildMcpToolName(item.server, tool2);
20602
21493
  const input = item.arguments ?? null;
20603
21494
  const output = item.result ?? null;
20604
21495
  const error = item.error ?? null;
@@ -20688,8 +21579,8 @@ function resolveCodexSessionRoot() {
20688
21579
  if (process.env.CODEX_SESSION_DIR) {
20689
21580
  return process.env.CODEX_SESSION_DIR;
20690
21581
  }
20691
- const codexHome = process.env.CODEX_HOME ?? path7.join(os3.homedir(), ".codex");
20692
- return path7.join(codexHome, "sessions");
21582
+ const codexHome = process.env.CODEX_HOME ?? path9.join(os3.homedir(), ".codex");
21583
+ return path9.join(codexHome, "sessions");
20693
21584
  }
20694
21585
  async function findRolloutFile(threadId, root) {
20695
21586
  const stack = [{ dir: root, depth: 0 }];
@@ -20697,12 +21588,12 @@ async function findRolloutFile(threadId, root) {
20697
21588
  const { dir, depth } = stack.pop();
20698
21589
  let entries;
20699
21590
  try {
20700
- entries = await fs2.readdir(dir, { withFileTypes: true });
21591
+ entries = await fs4.readdir(dir, { withFileTypes: true });
20701
21592
  } catch {
20702
21593
  continue;
20703
21594
  }
20704
21595
  for (const entry of entries) {
20705
- const entryPath = path7.join(dir, entry.name);
21596
+ const entryPath = path9.join(dir, entry.name);
20706
21597
  if (entry.isFile()) {
20707
21598
  const matchesThread = entry.name.includes(threadId);
20708
21599
  const matchesPrefix = entry.name.startsWith("rollout-");
@@ -21062,7 +21953,7 @@ function dedupeMirroredTextTimelineItems(timeline) {
21062
21953
  return deduped;
21063
21954
  }
21064
21955
  async function parseRolloutFile(filePath) {
21065
- const content = await fs2.readFile(filePath, "utf8");
21956
+ const content = await fs4.readFile(filePath, "utf8");
21066
21957
  const trimmed = content.trim();
21067
21958
  if (!trimmed) {
21068
21959
  return [];
@@ -21115,7 +22006,7 @@ async function loadCodexPersistedTimeline(sessionId, options, logger) {
21115
22006
  const rolloutPath = options?.rolloutPath ?? null;
21116
22007
  if (rolloutPath) {
21117
22008
  try {
21118
- const stat5 = await fs2.stat(rolloutPath);
22009
+ const stat5 = await fs4.stat(rolloutPath);
21119
22010
  if (stat5.isFile()) {
21120
22011
  const timeline = await parseRolloutFile(rolloutPath);
21121
22012
  if (timeline.length > 0) {
@@ -21271,16 +22162,16 @@ function isObjectSchemaNode(schema) {
21271
22162
  const type = schema.type;
21272
22163
  return isSchemaRecord(schema.properties) || type === "object" || Array.isArray(type) && type.includes("object");
21273
22164
  }
21274
- function normalizeCodexOutputSchemaNode(schema, path24) {
22165
+ function normalizeCodexOutputSchemaNode(schema, path26) {
21275
22166
  if (Array.isArray(schema)) {
21276
- return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path24}[${index}]`));
22167
+ return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path26}[${index}]`));
21277
22168
  }
21278
22169
  if (!isSchemaRecord(schema)) {
21279
22170
  return schema;
21280
22171
  }
21281
22172
  const normalized = {};
21282
22173
  for (const [key, value] of Object.entries(schema)) {
21283
- normalized[key] = normalizeCodexOutputSchemaNode(value, `${path24}.${key}`);
22174
+ normalized[key] = normalizeCodexOutputSchemaNode(value, `${path26}.${key}`);
21284
22175
  }
21285
22176
  if (!isObjectSchemaNode(normalized)) {
21286
22177
  return normalized;
@@ -21289,7 +22180,7 @@ function normalizeCodexOutputSchemaNode(schema, path24) {
21289
22180
  normalized.additionalProperties = false;
21290
22181
  } else if (normalized.additionalProperties !== false) {
21291
22182
  throw new Error(
21292
- `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.`
21293
22184
  );
21294
22185
  }
21295
22186
  const properties = isSchemaRecord(normalized.properties) ? normalized.properties : null;
@@ -21330,7 +22221,7 @@ async function resolveCodexLaunchPrefix(runtimeSettings) {
21330
22221
  return resolveProviderCommandPrefix(runtimeSettings?.command, resolveCodexBinary);
21331
22222
  }
21332
22223
  function resolveCodexHomeDir() {
21333
- return process.env.CODEX_HOME ?? path8.join(os4.homedir(), ".codex");
22224
+ return process.env.CODEX_HOME ?? path10.join(os4.homedir(), ".codex");
21334
22225
  }
21335
22226
  function tokenizeCommandArgs(args) {
21336
22227
  const tokens = [];
@@ -21410,10 +22301,10 @@ function parseFrontMatter(markdown) {
21410
22301
  }
21411
22302
  async function listCodexCustomPrompts() {
21412
22303
  const codexHome = resolveCodexHomeDir();
21413
- const promptsDir = path8.join(codexHome, "prompts");
22304
+ const promptsDir = path10.join(codexHome, "prompts");
21414
22305
  let entries;
21415
22306
  try {
21416
- entries = await fs3.readdir(promptsDir, { withFileTypes: true });
22307
+ entries = await fs5.readdir(promptsDir, { withFileTypes: true });
21417
22308
  } catch {
21418
22309
  return [];
21419
22310
  }
@@ -21429,10 +22320,10 @@ async function listCodexCustomPrompts() {
21429
22320
  if (!name) {
21430
22321
  continue;
21431
22322
  }
21432
- const fullPath = path8.join(promptsDir, entry.name);
22323
+ const fullPath = path10.join(promptsDir, entry.name);
21433
22324
  let content;
21434
22325
  try {
21435
- content = await fs3.readFile(fullPath, "utf8");
22326
+ content = await fs5.readFile(fullPath, "utf8");
21436
22327
  } catch {
21437
22328
  continue;
21438
22329
  }
@@ -21449,7 +22340,7 @@ async function listCodexCustomPrompts() {
21449
22340
  }
21450
22341
  async function listCodexSkills(cwd) {
21451
22342
  const candidates = [];
21452
- candidates.push(path8.join(cwd, ".codex", "skills"));
22343
+ candidates.push(path10.join(cwd, ".codex", "skills"));
21453
22344
  const repoRoot = (() => {
21454
22345
  try {
21455
22346
  const output = execSync2("git rev-parse --show-toplevel", {
@@ -21464,15 +22355,15 @@ async function listCodexSkills(cwd) {
21464
22355
  }
21465
22356
  })();
21466
22357
  if (repoRoot) {
21467
- candidates.push(path8.join(path8.dirname(cwd), ".codex", "skills"));
21468
- 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"));
21469
22360
  }
21470
- candidates.push(path8.join(resolveCodexHomeDir(), "skills"));
22361
+ candidates.push(path10.join(resolveCodexHomeDir(), "skills"));
21471
22362
  const commandsByName = /* @__PURE__ */ new Map();
21472
22363
  for (const dir of candidates) {
21473
22364
  let entries;
21474
22365
  try {
21475
- entries = await fs3.readdir(dir, { withFileTypes: true });
22366
+ entries = await fs5.readdir(dir, { withFileTypes: true });
21476
22367
  } catch {
21477
22368
  continue;
21478
22369
  }
@@ -21480,11 +22371,11 @@ async function listCodexSkills(cwd) {
21480
22371
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
21481
22372
  continue;
21482
22373
  }
21483
- const skillDir = path8.join(dir, entry.name);
21484
- const skillPath = path8.join(skillDir, "SKILL.md");
22374
+ const skillDir = path10.join(dir, entry.name);
22375
+ const skillPath = path10.join(skillDir, "SKILL.md");
21485
22376
  let content;
21486
22377
  try {
21487
- content = await fs3.readFile(skillPath, "utf8");
22378
+ content = await fs5.readFile(skillPath, "utf8");
21488
22379
  } catch {
21489
22380
  continue;
21490
22381
  }
@@ -21559,6 +22450,8 @@ function toCodexMcpConfig(config) {
21559
22450
  url: config.url,
21560
22451
  http_headers: config.headers
21561
22452
  };
22453
+ case "sdk":
22454
+ return null;
21562
22455
  }
21563
22456
  }
21564
22457
  var CodexAppServerClient = class {
@@ -22051,8 +22944,8 @@ function parseCodexPatchChanges(changes) {
22051
22944
  }
22052
22945
  ];
22053
22946
  }
22054
- return Object.entries(recordChanges).map(([path24, value]) => {
22055
- const normalizedPath = path24.trim();
22947
+ return Object.entries(recordChanges).map(([path26, value]) => {
22948
+ const normalizedPath = path26.trim();
22056
22949
  if (!normalizedPath) {
22057
22950
  return null;
22058
22951
  }
@@ -22790,13 +23683,13 @@ var CodexNotificationSchema = z25.union([
22790
23683
  )
22791
23684
  ]);
22792
23685
  async function writeImageAttachment(mimeType, data) {
22793
- const attachmentsDir = path8.join(os4.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
22794
- await fs3.mkdir(attachmentsDir, { recursive: true });
23686
+ const attachmentsDir = path10.join(os4.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
23687
+ await fs5.mkdir(attachmentsDir, { recursive: true });
22795
23688
  const normalized = normalizeImageData(mimeType, data);
22796
23689
  const extension = getImageExtension(normalized.mimeType);
22797
- const filename = `${randomUUID2()}.${extension}`;
22798
- const filePath = path8.join(attachmentsDir, filename);
22799
- 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"));
22800
23693
  return filePath;
22801
23694
  }
22802
23695
  async function readCodexConfiguredDefaults(client, logger) {
@@ -23076,7 +23969,7 @@ var CodexAppServerAgentSession = class {
23076
23969
  };
23077
23970
  }
23078
23971
  emitSyntheticPlanApprovalRequest(planText) {
23079
- const requestId = `permission-${randomUUID2()}`;
23972
+ const requestId = `permission-${randomUUID3()}`;
23080
23973
  const request = {
23081
23974
  id: requestId,
23082
23975
  provider: CODEX_PROVIDER,
@@ -23233,8 +24126,8 @@ var CodexAppServerAgentSession = class {
23233
24126
  if (commandName.startsWith("prompts:")) {
23234
24127
  const promptName = commandName.slice("prompts:".length);
23235
24128
  const codexHome = resolveCodexHomeDir();
23236
- const promptPath = path8.join(codexHome, "prompts", `${promptName}.md`);
23237
- const raw = await fs3.readFile(promptPath, "utf8");
24129
+ const promptPath = path10.join(codexHome, "prompts", `${promptName}.md`);
24130
+ const raw = await fs5.readFile(promptPath, "utf8");
23238
24131
  const parsed = parseFrontMatter(raw);
23239
24132
  return expandCodexCustomPrompt(parsed.body, args);
23240
24133
  }
@@ -23701,7 +24594,8 @@ var CodexAppServerAgentSession = class {
23701
24594
  if (this.config.mcpServers) {
23702
24595
  const mcpServers = {};
23703
24596
  for (const [name, serverConfig] of Object.entries(this.config.mcpServers)) {
23704
- mcpServers[name] = toCodexMcpConfig(serverConfig);
24597
+ const codexConfig = toCodexMcpConfig(serverConfig);
24598
+ if (codexConfig) mcpServers[name] = codexConfig;
23705
24599
  }
23706
24600
  innerConfig.mcp_servers = mcpServers;
23707
24601
  }
@@ -24465,9 +25359,9 @@ var CodexAppServerAgentClient = class {
24465
25359
  };
24466
25360
 
24467
25361
  // ../server/src/server/agent/providers/acp-agent.ts
24468
- import { randomUUID as randomUUID3 } from "node:crypto";
24469
- import fs4 from "node:fs/promises";
24470
- 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";
24471
25365
  import { Readable, Writable } from "node:stream";
24472
25366
  import {
24473
25367
  ClientSideConnection,
@@ -24764,12 +25658,12 @@ ${stderr}` : String(error)));
24764
25658
  async sessionUpdate() {
24765
25659
  },
24766
25660
  async readTextFile(params) {
24767
- const content = await fs4.readFile(params.path, "utf8");
25661
+ const content = await fs6.readFile(params.path, "utf8");
24768
25662
  return { content };
24769
25663
  },
24770
25664
  async writeTextFile(params) {
24771
- await fs4.mkdir(path9.dirname(params.path), { recursive: true });
24772
- 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");
24773
25667
  return {};
24774
25668
  },
24775
25669
  async createTerminal() {
@@ -24991,8 +25885,8 @@ var ACPAgentSession = class {
24991
25885
  if (this.activeForegroundTurnId) {
24992
25886
  throw new Error("A foreground turn is already active");
24993
25887
  }
24994
- const turnId = randomUUID3();
24995
- const messageId = randomUUID3();
25888
+ const turnId = randomUUID4();
25889
+ const messageId = randomUUID4();
24996
25890
  this.activeForegroundTurnId = turnId;
24997
25891
  this.suppressUserEchoMessageId = messageId;
24998
25892
  this.suppressUserEchoText = extractPromptText(prompt);
@@ -25293,7 +26187,7 @@ var ACPAgentSession = class {
25293
26187
  }
25294
26188
  } : { outcome: { outcome: "cancelled" } };
25295
26189
  }
25296
- const requestId = randomUUID3();
26190
+ const requestId = randomUUID4();
25297
26191
  let toolSnapshot = this.toolCalls.get(params.toolCall.toolCallId) ?? mergeToolSnapshot(params.toolCall.toolCallId, params.toolCall);
25298
26192
  if (this.toolSnapshotTransformer) {
25299
26193
  toolSnapshot = this.toolSnapshotTransformer(toolSnapshot);
@@ -25334,7 +26228,7 @@ var ACPAgentSession = class {
25334
26228
  }
25335
26229
  }
25336
26230
  async readTextFile(params) {
25337
- const raw = await fs4.readFile(params.path, "utf8");
26231
+ const raw = await fs6.readFile(params.path, "utf8");
25338
26232
  if (!params.line && !params.limit) {
25339
26233
  return { content: raw };
25340
26234
  }
@@ -25344,12 +26238,12 @@ var ACPAgentSession = class {
25344
26238
  return { content: lines.slice(start, end).join("\n") };
25345
26239
  }
25346
26240
  async writeTextFile(params) {
25347
- await fs4.mkdir(path9.dirname(params.path), { recursive: true });
25348
- 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");
25349
26243
  return {};
25350
26244
  }
25351
26245
  async createTerminal(params) {
25352
- const terminalId = randomUUID3();
26246
+ const terminalId = randomUUID4();
25353
26247
  const env = Object.fromEntries(
25354
26248
  (params.env ?? []).map((entry2) => [entry2.name, entry2.value])
25355
26249
  );
@@ -25747,9 +26641,10 @@ function normalizeMcpServers(servers) {
25747
26641
  if (!servers) {
25748
26642
  return [];
25749
26643
  }
25750
- return Object.entries(servers).map(([name, config]) => {
26644
+ const out = [];
26645
+ for (const [name, config] of Object.entries(servers)) {
25751
26646
  if (config.type === "stdio") {
25752
- return {
26647
+ out.push({
25753
26648
  name,
25754
26649
  command: config.command,
25755
26650
  args: config.args ?? [],
@@ -25757,29 +26652,35 @@ function normalizeMcpServers(servers) {
25757
26652
  name: envName,
25758
26653
  value
25759
26654
  }))
25760
- };
26655
+ });
26656
+ continue;
25761
26657
  }
25762
26658
  if (config.type === "http") {
25763
- return {
26659
+ out.push({
25764
26660
  type: "http",
25765
26661
  name,
25766
26662
  url: config.url,
25767
26663
  headers: Object.entries(config.headers ?? {}).map(([headerName, value]) => ({
25768
26664
  name: headerName,
25769
- value
26665
+ value: String(value)
25770
26666
  }))
25771
- };
26667
+ });
26668
+ continue;
25772
26669
  }
25773
- return {
25774
- type: "sse",
25775
- name,
25776
- url: config.url,
25777
- headers: Object.entries(config.headers ?? {}).map(([headerName, value]) => ({
25778
- name: headerName,
25779
- value
25780
- }))
25781
- };
25782
- });
26670
+ if (config.type === "sse") {
26671
+ out.push({
26672
+ type: "sse",
26673
+ name,
26674
+ url: config.url,
26675
+ headers: Object.entries(config.headers ?? {}).map(([headerName, value]) => ({
26676
+ name: headerName,
26677
+ value: String(value)
26678
+ }))
26679
+ });
26680
+ continue;
26681
+ }
26682
+ }
26683
+ return out;
25783
26684
  }
25784
26685
  function toACPContentBlocks(prompt) {
25785
26686
  if (typeof prompt === "string") {
@@ -26610,12 +27511,15 @@ function toOpenCodeMcpConfig(config) {
26610
27511
  enabled: true
26611
27512
  };
26612
27513
  }
26613
- return {
26614
- type: "remote",
26615
- url: config.url,
26616
- ...config.headers ? { headers: config.headers } : {},
26617
- enabled: true
26618
- };
27514
+ if (config.type === "http" || config.type === "sse") {
27515
+ return {
27516
+ type: "remote",
27517
+ url: config.url,
27518
+ ...config.headers ? { headers: config.headers } : {},
27519
+ enabled: true
27520
+ };
27521
+ }
27522
+ return null;
26619
27523
  }
26620
27524
  function stringifyUnknownError(error) {
26621
27525
  if (typeof error === "string") {
@@ -27558,7 +28462,7 @@ function translateOpenCodeEvent(event, state) {
27558
28462
  break;
27559
28463
  }
27560
28464
  const metadata = readOpenCodeRecord(event.properties.metadata);
27561
- const tool = readOpenCodeRecord(event.properties.tool);
28465
+ const tool2 = readOpenCodeRecord(event.properties.tool);
27562
28466
  const patterns = Array.isArray(event.properties.patterns) ? event.properties.patterns.filter((value) => typeof value === "string") : [];
27563
28467
  const command = readPermissionField(metadata, PERMISSION_COMMAND_KEYS);
27564
28468
  const cwd = readPermissionField(metadata, PERMISSION_CWD_KEYS);
@@ -27566,7 +28470,7 @@ function translateOpenCodeEvent(event, state) {
27566
28470
  const input = buildOpenCodePermissionInput({
27567
28471
  patterns,
27568
28472
  metadata,
27569
- tool,
28473
+ tool: tool2,
27570
28474
  command
27571
28475
  });
27572
28476
  const detail = buildOpenCodePermissionDetail({
@@ -28314,6 +29218,7 @@ var OpenCodeAgentSession = class {
28314
29218
  async configureMcpServers(mcpServers) {
28315
29219
  for (const [name, serverConfig] of Object.entries(mcpServers)) {
28316
29220
  const mappedConfig = toOpenCodeMcpConfig(serverConfig);
29221
+ if (!mappedConfig) continue;
28317
29222
  await this.registerMcpServer(name, mappedConfig);
28318
29223
  }
28319
29224
  }
@@ -28392,9 +29297,9 @@ var OpenCodeAgentSession = class {
28392
29297
  };
28393
29298
 
28394
29299
  // ../server/src/server/agent/providers/pi-direct-agent.ts
28395
- import { randomUUID as randomUUID4 } from "node:crypto";
29300
+ import { randomUUID as randomUUID5 } from "node:crypto";
28396
29301
  import { existsSync as existsSync9 } from "node:fs";
28397
- import { join as join7 } from "node:path";
29302
+ import { join as join9 } from "node:path";
28398
29303
  import { homedir } from "node:os";
28399
29304
  import {
28400
29305
  AuthStorage,
@@ -29165,7 +30070,7 @@ var PiDirectAgentSession = class {
29165
30070
  throw new Error("A Pi turn is already active");
29166
30071
  }
29167
30072
  const payload = convertPromptInput(prompt);
29168
- const turnId = randomUUID4();
30073
+ const turnId = randomUUID5();
29169
30074
  this.activeTurnId = turnId;
29170
30075
  void this.session.prompt(payload.text, payload.images ? { images: payload.images } : void 0).catch((error) => {
29171
30076
  const failedTurnId = this.activeTurnId ?? turnId;
@@ -29400,7 +30305,7 @@ var PiDirectAgentClient = class {
29400
30305
  } else if (!await isCommandAvailable(PI_BINARY_COMMAND)) {
29401
30306
  return false;
29402
30307
  }
29403
- 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"));
29404
30309
  }
29405
30310
  async getDiagnostic() {
29406
30311
  try {
@@ -29408,7 +30313,7 @@ var PiDirectAgentClient = class {
29408
30313
  const binaryOverride = this.runtimeSettings?.command;
29409
30314
  const binary = binaryOverride?.mode === "replace" && binaryOverride.argv[0] ? binaryOverride.argv[0] : await findExecutable(PI_BINARY_COMMAND);
29410
30315
  const version = binary ? await resolveBinaryVersion(binary) : "unknown";
29411
- const authConfigPath = join7(homedir(), ".pi", "agent", "auth.json");
30316
+ const authConfigPath = join9(homedir(), ".pi", "agent", "auth.json");
29412
30317
  let modelsValue = "Not checked";
29413
30318
  let status = formatDiagnosticStatus(available);
29414
30319
  if (available) {
@@ -29801,8 +30706,8 @@ function buildZodValidator(schema, schemaName) {
29801
30706
  return { ok: true, value: result.data };
29802
30707
  }
29803
30708
  const errors = result.error.issues.map((issue) => {
29804
- const path24 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
29805
- return `${path24}: ${issue.message}`;
30709
+ const path26 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
30710
+ return `${path26}: ${issue.message}`;
29806
30711
  });
29807
30712
  return { ok: false, errors };
29808
30713
  }
@@ -29820,9 +30725,9 @@ function buildJsonSchemaValidator(schema) {
29820
30725
  return { ok: true, value };
29821
30726
  }
29822
30727
  const errors = (validate.errors ?? []).map((error) => {
29823
- const path24 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
30728
+ const path26 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
29824
30729
  const message = error.message ?? "is invalid";
29825
- return `${path24}: ${message}`;
30730
+ return `${path26}: ${message}`;
29826
30731
  });
29827
30732
  return { ok: false, errors };
29828
30733
  }
@@ -30707,8 +31612,8 @@ function isVoicePermissionAllowed(request) {
30707
31612
  }
30708
31613
 
30709
31614
  // ../server/src/server/file-explorer/service.ts
30710
- import { promises as fs5 } from "fs";
30711
- import path10 from "path";
31615
+ import { promises as fs7 } from "fs";
31616
+ import path12 from "path";
30712
31617
 
30713
31618
  // ../server/src/server/path-utils.ts
30714
31619
  import { homedir as homedir2 } from "node:os";
@@ -30755,14 +31660,14 @@ async function listDirectoryEntries({
30755
31660
  relativePath = "."
30756
31661
  }) {
30757
31662
  const directoryPath = await resolveScopedPath({ root, relativePath });
30758
- const stats = await fs5.stat(directoryPath);
31663
+ const stats = await fs7.stat(directoryPath);
30759
31664
  if (!stats.isDirectory()) {
30760
31665
  throw new Error("Requested path is not a directory");
30761
31666
  }
30762
- const dirents = await fs5.readdir(directoryPath, { withFileTypes: true });
31667
+ const dirents = await fs7.readdir(directoryPath, { withFileTypes: true });
30763
31668
  const entriesWithNulls = await Promise.all(
30764
31669
  dirents.map(async (dirent) => {
30765
- const targetPath = path10.join(directoryPath, dirent.name);
31670
+ const targetPath = path12.join(directoryPath, dirent.name);
30766
31671
  const kind = dirent.isDirectory() ? "directory" : "file";
30767
31672
  try {
30768
31673
  return await buildEntryPayload({
@@ -30797,18 +31702,18 @@ async function readExplorerFile({
30797
31702
  relativePath
30798
31703
  }) {
30799
31704
  const filePath = await resolveScopedPath({ root, relativePath });
30800
- const stats = await fs5.stat(filePath);
31705
+ const stats = await fs7.stat(filePath);
30801
31706
  if (!stats.isFile()) {
30802
31707
  throw new Error("Requested path is not a file");
30803
31708
  }
30804
- const ext = path10.extname(filePath).toLowerCase();
31709
+ const ext = path12.extname(filePath).toLowerCase();
30805
31710
  const basePayload = {
30806
31711
  path: normalizeRelativePath({ root, targetPath: filePath }),
30807
31712
  size: stats.size,
30808
31713
  modifiedAt: stats.mtime.toISOString()
30809
31714
  };
30810
31715
  if (ext in IMAGE_MIME_TYPES) {
30811
- const buffer2 = await fs5.readFile(filePath);
31716
+ const buffer2 = await fs7.readFile(filePath);
30812
31717
  return {
30813
31718
  ...basePayload,
30814
31719
  kind: "image",
@@ -30817,7 +31722,7 @@ async function readExplorerFile({
30817
31722
  mimeType: IMAGE_MIME_TYPES[ext]
30818
31723
  };
30819
31724
  }
30820
- const buffer = await fs5.readFile(filePath);
31725
+ const buffer = await fs7.readFile(filePath);
30821
31726
  if (isLikelyBinary(buffer)) {
30822
31727
  return {
30823
31728
  ...basePayload,
@@ -30839,34 +31744,34 @@ async function writeTextFile({
30839
31744
  relativePath,
30840
31745
  content
30841
31746
  }) {
30842
- const ext = path10.extname(relativePath).toLowerCase();
31747
+ const ext = path12.extname(relativePath).toLowerCase();
30843
31748
  if (ext in IMAGE_MIME_TYPES) {
30844
31749
  throw new Error(`Refusing to write '${relativePath}': binary/image file`);
30845
31750
  }
30846
31751
  const filePath = await resolveScopedPath({ root, relativePath });
30847
31752
  const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
30848
- await fs5.writeFile(tempPath, content, "utf8");
30849
- await fs5.rename(tempPath, filePath);
31753
+ await fs7.writeFile(tempPath, content, "utf8");
31754
+ await fs7.rename(tempPath, filePath);
30850
31755
  }
30851
31756
  async function deleteFile({ root, relativePath }) {
30852
- const ext = path10.extname(relativePath).toLowerCase();
31757
+ const ext = path12.extname(relativePath).toLowerCase();
30853
31758
  if (ext !== ".md") {
30854
31759
  throw new Error(`Refusing to delete '${relativePath}': only .md files allowed`);
30855
31760
  }
30856
31761
  const filePath = await resolveScopedPath({ root, relativePath });
30857
- const stats = await fs5.stat(filePath);
31762
+ const stats = await fs7.stat(filePath);
30858
31763
  if (!stats.isFile()) {
30859
31764
  throw new Error("Requested path is not a file");
30860
31765
  }
30861
- await fs5.unlink(filePath);
31766
+ await fs7.unlink(filePath);
30862
31767
  }
30863
31768
  async function deleteEntry({ root, relativePath }) {
30864
31769
  const entryPath = await resolveScopedPath({ root, relativePath });
30865
- await fs5.rm(entryPath, { recursive: true, force: false });
31770
+ await fs7.rm(entryPath, { recursive: true, force: false });
30866
31771
  }
30867
31772
  async function createDirectory({ root, relativePath }) {
30868
31773
  const dirPath = await resolveScopedPath({ root, relativePath });
30869
- await fs5.mkdir(dirPath, { recursive: true });
31774
+ await fs7.mkdir(dirPath, { recursive: true });
30870
31775
  }
30871
31776
  async function moveEntry({
30872
31777
  root,
@@ -30875,22 +31780,22 @@ async function moveEntry({
30875
31780
  }) {
30876
31781
  const src = await resolveScopedPath({ root, relativePath: sourcePath });
30877
31782
  const dest = await resolveScopedPath({ root, relativePath: destinationPath });
30878
- await fs5.rename(src, dest);
31783
+ await fs7.rename(src, dest);
30879
31784
  }
30880
31785
  async function getDownloadableFileInfo({ root, relativePath }) {
30881
31786
  const filePath = await resolveScopedPath({ root, relativePath });
30882
- const stats = await fs5.stat(filePath);
31787
+ const stats = await fs7.stat(filePath);
30883
31788
  if (!stats.isFile()) {
30884
31789
  throw new Error("Requested path is not a file");
30885
31790
  }
30886
- const ext = path10.extname(filePath).toLowerCase();
31791
+ const ext = path12.extname(filePath).toLowerCase();
30887
31792
  let mimeType = "application/octet-stream";
30888
31793
  if (ext in IMAGE_MIME_TYPES) {
30889
31794
  mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
30890
31795
  } else if (ext in FONT_MIME_TYPES) {
30891
31796
  mimeType = FONT_MIME_TYPES[ext] ?? mimeType;
30892
31797
  } else {
30893
- const handle = await fs5.open(filePath, "r");
31798
+ const handle = await fs7.open(filePath, "r");
30894
31799
  const sample = Buffer.alloc(8192);
30895
31800
  try {
30896
31801
  const { bytesRead } = await handle.read(sample, 0, sample.length, 0);
@@ -30905,23 +31810,23 @@ async function getDownloadableFileInfo({ root, relativePath }) {
30905
31810
  return {
30906
31811
  path: normalizeRelativePath({ root, targetPath: filePath }),
30907
31812
  absolutePath: filePath,
30908
- fileName: path10.basename(filePath),
31813
+ fileName: path12.basename(filePath),
30909
31814
  mimeType,
30910
31815
  size: stats.size
30911
31816
  };
30912
31817
  }
30913
31818
  async function resolveScopedPath({ root, relativePath = "." }) {
30914
- const normalizedRoot = path10.resolve(root);
31819
+ const normalizedRoot = path12.resolve(root);
30915
31820
  const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
30916
- const relative = path10.relative(normalizedRoot, requestedPath);
30917
- if (relative !== "" && (relative.startsWith("..") || path10.isAbsolute(relative))) {
31821
+ const relative = path12.relative(normalizedRoot, requestedPath);
31822
+ if (relative !== "" && (relative.startsWith("..") || path12.isAbsolute(relative))) {
30918
31823
  throw new Error("Access outside of workspace is not allowed");
30919
31824
  }
30920
- const realRoot = await fs5.realpath(normalizedRoot);
31825
+ const realRoot = await fs7.realpath(normalizedRoot);
30921
31826
  try {
30922
- const realPath = await fs5.realpath(requestedPath);
30923
- const realRelative = path10.relative(realRoot, realPath);
30924
- 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))) {
30925
31830
  throw new Error("Access outside of workspace is not allowed");
30926
31831
  }
30927
31832
  return requestedPath;
@@ -30938,7 +31843,7 @@ async function buildEntryPayload({
30938
31843
  name,
30939
31844
  kind
30940
31845
  }) {
30941
- const stats = await fs5.stat(targetPath);
31846
+ const stats = await fs7.stat(targetPath);
30942
31847
  return {
30943
31848
  name,
30944
31849
  path: normalizeRelativePath({ root, targetPath }),
@@ -30952,10 +31857,10 @@ function isMissingEntryError(error) {
30952
31857
  return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
30953
31858
  }
30954
31859
  function normalizeRelativePath({ root, targetPath }) {
30955
- const normalizedRoot = path10.resolve(root);
30956
- const normalizedTarget = path10.resolve(targetPath);
30957
- const relative = path10.relative(normalizedRoot, normalizedTarget);
30958
- 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("/");
30959
31864
  }
30960
31865
  function textMimeTypeForExtension(ext) {
30961
31866
  return TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE;
@@ -30982,7 +31887,7 @@ function isLikelyBinary(buffer) {
30982
31887
 
30983
31888
  // ../server/src/utils/project-icon.ts
30984
31889
  import { readdir, readFile as readFile2, stat as stat2 } from "fs/promises";
30985
- import { extname as extname2, join as join8 } from "path";
31890
+ import { extname as extname3, join as join10 } from "path";
30986
31891
  var ICON_PATTERNS = [
30987
31892
  "favicon.ico",
30988
31893
  "favicon.png",
@@ -31096,7 +32001,7 @@ function isSquareImage(buffer, mimeType) {
31096
32001
  return dimensions.width === dimensions.height;
31097
32002
  }
31098
32003
  function getMimeType(filename) {
31099
- const ext = extname2(filename).toLowerCase();
32004
+ const ext = extname3(filename).toLowerCase();
31100
32005
  switch (ext) {
31101
32006
  case ".ico":
31102
32007
  return "image/x-icon";
@@ -31132,7 +32037,7 @@ async function findIconInDir(dir, patterns) {
31132
32037
  for (const pattern of patterns) {
31133
32038
  for (const entry of entries) {
31134
32039
  if (matchesPattern(entry, pattern)) {
31135
- const fullPath = join8(dir, entry);
32040
+ const fullPath = join10(dir, entry);
31136
32041
  try {
31137
32042
  const stats = await stat2(fullPath);
31138
32043
  if (stats.isFile()) {
@@ -31163,7 +32068,7 @@ async function searchDirRecursively(dir, patterns, ignoredDirs, maxDepth, curren
31163
32068
  if (ignoredDirs.has(entry)) {
31164
32069
  continue;
31165
32070
  }
31166
- const fullPath = join8(dir, entry);
32071
+ const fullPath = join10(dir, entry);
31167
32072
  try {
31168
32073
  const stats = await stat2(fullPath);
31169
32074
  if (stats.isDirectory()) {
@@ -31186,7 +32091,7 @@ async function searchDirRecursively(dir, patterns, ignoredDirs, maxDepth, curren
31186
32091
  async function findProjectIcon(projectDir, maxDepth = 3) {
31187
32092
  const ignoredDirsSet = new Set(IGNORED_DIRS);
31188
32093
  for (const priorityDir of PRIORITY_DIRS) {
31189
- const priorityPath = join8(projectDir, priorityDir);
32094
+ const priorityPath = join10(projectDir, priorityDir);
31190
32095
  try {
31191
32096
  const stats = await stat2(priorityPath);
31192
32097
  if (stats.isDirectory()) {
@@ -31204,7 +32109,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
31204
32109
  }
31205
32110
  }
31206
32111
  for (const monoDir of MONOREPO_PACKAGE_DIRS) {
31207
- const monoPath = join8(projectDir, monoDir);
32112
+ const monoPath = join10(projectDir, monoDir);
31208
32113
  let packageEntries;
31209
32114
  try {
31210
32115
  packageEntries = await readdir(monoPath);
@@ -31212,7 +32117,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
31212
32117
  continue;
31213
32118
  }
31214
32119
  for (const packageName of packageEntries) {
31215
- const packagePath = join8(monoPath, packageName);
32120
+ const packagePath = join10(monoPath, packageName);
31216
32121
  try {
31217
32122
  const packageStats = await stat2(packagePath);
31218
32123
  if (!packageStats.isDirectory()) continue;
@@ -31220,7 +32125,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
31220
32125
  continue;
31221
32126
  }
31222
32127
  for (const priorityDir of PRIORITY_DIRS) {
31223
- const priorityPath = join8(packagePath, priorityDir);
32128
+ const priorityPath = join10(packagePath, priorityDir);
31224
32129
  try {
31225
32130
  const priorityStats = await stat2(priorityPath);
31226
32131
  if (priorityStats.isDirectory()) {
@@ -31270,7 +32175,7 @@ async function findDirRecursively(dir, maxDepth = 2, currentDepth = 0) {
31270
32175
  if (ignoredDirsSet.has(entry) || priorityDirsSet.has(entry)) {
31271
32176
  continue;
31272
32177
  }
31273
- const fullPath = join8(dir, entry);
32178
+ const fullPath = join10(dir, entry);
31274
32179
  try {
31275
32180
  const stats = await stat2(fullPath);
31276
32181
  if (stats.isDirectory()) {
@@ -31309,64 +32214,64 @@ async function getProjectIcon(projectDir) {
31309
32214
 
31310
32215
  // ../server/src/utils/path.ts
31311
32216
  import os5 from "os";
31312
- function expandTilde(path24) {
31313
- if (path24.startsWith("~/")) {
32217
+ function expandTilde(path26) {
32218
+ if (path26.startsWith("~/")) {
31314
32219
  const homeDir3 = process.env.HOME || os5.homedir();
31315
- return path24.replace("~", homeDir3);
32220
+ return path26.replace("~", homeDir3);
31316
32221
  }
31317
- if (path24 === "~") {
32222
+ if (path26 === "~") {
31318
32223
  return process.env.HOME || os5.homedir();
31319
32224
  }
31320
- return path24;
32225
+ return path26;
31321
32226
  }
31322
32227
 
31323
32228
  // ../server/src/server/skills/scanner.ts
31324
- import fs6 from "node:fs/promises";
32229
+ import fs8 from "node:fs/promises";
31325
32230
  import os6 from "node:os";
31326
- import path11 from "node:path";
32231
+ import path13 from "node:path";
31327
32232
  var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
31328
32233
  function homeDir() {
31329
32234
  return process.env.HOME || os6.homedir();
31330
32235
  }
31331
32236
  function codexHomeDir() {
31332
- return process.env.CODEX_HOME || path11.join(homeDir(), ".codex");
32237
+ return process.env.CODEX_HOME || path13.join(homeDir(), ".codex");
31333
32238
  }
31334
32239
  function resolveScopeDir(provider, scope, workspaceRoot) {
31335
32240
  if (scope === "codex-prompts") {
31336
32241
  if (provider !== "codex") {
31337
32242
  throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
31338
32243
  }
31339
- return path11.join(codexHomeDir(), "prompts");
32244
+ return path13.join(codexHomeDir(), "prompts");
31340
32245
  }
31341
32246
  if (scope === "project") {
31342
32247
  if (!workspaceRoot) {
31343
32248
  throw new Error(`workspaceRoot is required for scope "project"`);
31344
32249
  }
31345
32250
  const dotDir = provider === "claude" ? ".claude" : ".codex";
31346
- return path11.join(workspaceRoot, dotDir, "skills");
32251
+ return path13.join(workspaceRoot, dotDir, "skills");
31347
32252
  }
31348
32253
  if (provider === "claude") {
31349
- return path11.join(homeDir(), ".claude", "skills");
32254
+ return path13.join(homeDir(), ".claude", "skills");
31350
32255
  }
31351
- return path11.join(codexHomeDir(), "skills");
32256
+ return path13.join(codexHomeDir(), "skills");
31352
32257
  }
31353
32258
  function allowedRoots(workspaceRoot) {
31354
32259
  const roots = [
31355
- path11.join(homeDir(), ".claude", "skills"),
31356
- path11.join(codexHomeDir(), "skills"),
31357
- path11.join(codexHomeDir(), "prompts")
32260
+ path13.join(homeDir(), ".claude", "skills"),
32261
+ path13.join(codexHomeDir(), "skills"),
32262
+ path13.join(codexHomeDir(), "prompts")
31358
32263
  ];
31359
32264
  if (workspaceRoot) {
31360
- roots.push(path11.join(workspaceRoot, ".claude", "skills"));
31361
- roots.push(path11.join(workspaceRoot, ".codex", "skills"));
32265
+ roots.push(path13.join(workspaceRoot, ".claude", "skills"));
32266
+ roots.push(path13.join(workspaceRoot, ".codex", "skills"));
31362
32267
  }
31363
- return roots.map((r) => path11.resolve(r));
32268
+ return roots.map((r) => path13.resolve(r));
31364
32269
  }
31365
32270
  function isInsideAllowedRoot(absPath, workspaceRoot) {
31366
- const resolved = path11.resolve(absPath);
32271
+ const resolved = path13.resolve(absPath);
31367
32272
  for (const root of allowedRoots(workspaceRoot)) {
31368
- const rel = path11.relative(root, resolved);
31369
- if (rel === "" || !rel.startsWith("..") && !path11.isAbsolute(rel)) {
32273
+ const rel = path13.relative(root, resolved);
32274
+ if (rel === "" || !rel.startsWith("..") && !path13.isAbsolute(rel)) {
31370
32275
  return true;
31371
32276
  }
31372
32277
  }
@@ -31486,7 +32391,7 @@ async function listSkills(args) {
31486
32391
  const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
31487
32392
  let entries;
31488
32393
  try {
31489
- entries = await fs6.readdir(dir, { withFileTypes: true });
32394
+ entries = await fs8.readdir(dir, { withFileTypes: true });
31490
32395
  } catch {
31491
32396
  return [];
31492
32397
  }
@@ -31497,7 +32402,7 @@ async function listSkills(args) {
31497
32402
  if (!entry.name.endsWith(".md")) continue;
31498
32403
  const name = entry.name.slice(0, -".md".length);
31499
32404
  if (!name) continue;
31500
- const fullPath = path11.join(dir, entry.name);
32405
+ const fullPath = path13.join(dir, entry.name);
31501
32406
  const stat5 = await safeStat(fullPath);
31502
32407
  if (!stat5) continue;
31503
32408
  const description = await readDescriptionSafely(fullPath);
@@ -31515,8 +32420,8 @@ async function listSkills(args) {
31515
32420
  } else {
31516
32421
  for (const entry of entries) {
31517
32422
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
31518
- const skillDir = path11.join(dir, entry.name);
31519
- const skillPath = path11.join(skillDir, "SKILL.md");
32423
+ const skillDir = path13.join(dir, entry.name);
32424
+ const skillPath = path13.join(skillDir, "SKILL.md");
31520
32425
  const stat5 = await safeStat(skillPath);
31521
32426
  if (!stat5) continue;
31522
32427
  const description = await readDescriptionSafely(skillPath);
@@ -31537,7 +32442,7 @@ async function listSkills(args) {
31537
32442
  }
31538
32443
  async function safeStat(filePath) {
31539
32444
  try {
31540
- const s = await fs6.stat(filePath);
32445
+ const s = await fs8.stat(filePath);
31541
32446
  return { mtime: s.mtime, size: s.size };
31542
32447
  } catch {
31543
32448
  return null;
@@ -31545,7 +32450,7 @@ async function safeStat(filePath) {
31545
32450
  }
31546
32451
  async function readDescriptionSafely(filePath) {
31547
32452
  try {
31548
- const text = await fs6.readFile(filePath, "utf8");
32453
+ const text = await fs8.readFile(filePath, "utf8");
31549
32454
  return readDescription(text);
31550
32455
  } catch {
31551
32456
  return "";
@@ -31561,11 +32466,11 @@ async function createSkill(args) {
31561
32466
  throw new Error(`Skill name must not contain path separators or "..".`);
31562
32467
  }
31563
32468
  const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
31564
- await fs6.mkdir(dir, { recursive: true });
32469
+ await fs8.mkdir(dir, { recursive: true });
31565
32470
  if (args.scope === "codex-prompts") {
31566
- const filePath2 = path11.join(dir, `${args.name}.md`);
32471
+ const filePath2 = path13.join(dir, `${args.name}.md`);
31567
32472
  try {
31568
- await fs6.access(filePath2);
32473
+ await fs8.access(filePath2);
31569
32474
  throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
31570
32475
  } catch (err) {
31571
32476
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -31576,13 +32481,13 @@ async function createSkill(args) {
31576
32481
  }
31577
32482
  }
31578
32483
  const initial2 = buildStarterPrompt(args.name);
31579
- await fs6.writeFile(filePath2, initial2, "utf8");
32484
+ await fs8.writeFile(filePath2, initial2, "utf8");
31580
32485
  return { path: filePath2 };
31581
32486
  }
31582
- const skillDir = path11.join(dir, args.name);
32487
+ const skillDir = path13.join(dir, args.name);
31583
32488
  let dirExists = false;
31584
32489
  try {
31585
- const stat5 = await fs6.stat(skillDir);
32490
+ const stat5 = await fs8.stat(skillDir);
31586
32491
  dirExists = stat5.isDirectory();
31587
32492
  } catch {
31588
32493
  dirExists = false;
@@ -31590,10 +32495,10 @@ async function createSkill(args) {
31590
32495
  if (dirExists) {
31591
32496
  throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
31592
32497
  }
31593
- await fs6.mkdir(skillDir, { recursive: true });
31594
- const filePath = path11.join(skillDir, "SKILL.md");
32498
+ await fs8.mkdir(skillDir, { recursive: true });
32499
+ const filePath = path13.join(skillDir, "SKILL.md");
31595
32500
  const initial = buildStarterSkill(args.name);
31596
- await fs6.writeFile(filePath, initial, "utf8");
32501
+ await fs8.writeFile(filePath, initial, "utf8");
31597
32502
  return { path: filePath };
31598
32503
  }
31599
32504
  function buildStarterSkill(name) {
@@ -31620,7 +32525,7 @@ Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expa
31620
32525
  `;
31621
32526
  }
31622
32527
  async function writeSkillFrontmatter(args, workspaceRoot) {
31623
- if (!path11.isAbsolute(args.path)) {
32528
+ if (!path13.isAbsolute(args.path)) {
31624
32529
  throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
31625
32530
  }
31626
32531
  if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
@@ -31628,7 +32533,7 @@ async function writeSkillFrontmatter(args, workspaceRoot) {
31628
32533
  }
31629
32534
  let original;
31630
32535
  try {
31631
- original = await fs6.readFile(args.path, "utf8");
32536
+ original = await fs8.readFile(args.path, "utf8");
31632
32537
  } catch (err) {
31633
32538
  throw new Error(
31634
32539
  `Failed to read skill file: ${err instanceof Error ? err.message : String(err)}`
@@ -31641,12 +32546,12 @@ async function writeSkillFrontmatter(args, workspaceRoot) {
31641
32546
  ${parsed.body}` : `${nextFrontmatter}
31642
32547
 
31643
32548
  ${original}`;
31644
- await fs6.writeFile(args.path, nextContent, "utf8");
32549
+ await fs8.writeFile(args.path, nextContent, "utf8");
31645
32550
  }
31646
32551
 
31647
32552
  // ../server/src/utils/directory-suggestions.ts
31648
32553
  import { readdir as readdir2, realpath, stat as stat3 } from "node:fs/promises";
31649
- import path12 from "node:path";
32554
+ import path14 from "node:path";
31650
32555
  var DEFAULT_LIMIT = 30;
31651
32556
  var MAX_LIMIT = 100;
31652
32557
  var DEFAULT_MAX_DEPTH = 6;
@@ -31732,7 +32637,7 @@ function normalizeLimit(limit) {
31732
32637
  return Math.max(1, Math.min(MAX_LIMIT, bounded));
31733
32638
  }
31734
32639
  async function searchWithinParentDirectory(input) {
31735
- const parentPath = path12.resolve(input.homeRoot, input.parentPart || ".");
32640
+ const parentPath = path14.resolve(input.homeRoot, input.parentPart || ".");
31736
32641
  const parentRoot = await resolveDirectory(parentPath);
31737
32642
  if (!parentRoot || !isPathInsideRoot(input.homeRoot, parentRoot)) {
31738
32643
  return [];
@@ -31797,7 +32702,7 @@ async function searchAcrossHomeTree(input) {
31797
32702
  return dedupeAndSort(ranked).slice(0, input.limit);
31798
32703
  }
31799
32704
  async function searchWorkspaceWithinParentDirectory(input) {
31800
- const parentPath = path12.resolve(input.workspaceRoot, input.parentPart || ".");
32705
+ const parentPath = path14.resolve(input.workspaceRoot, input.parentPart || ".");
31801
32706
  const parentRoot = await resolveDirectory(parentPath);
31802
32707
  if (!parentRoot || !isPathInsideRoot(input.workspaceRoot, parentRoot)) {
31803
32708
  return [];
@@ -32043,15 +32948,15 @@ function findSegmentMatchIndex(segments, predicate) {
32043
32948
  return -1;
32044
32949
  }
32045
32950
  function normalizeRelativePath2(homeRoot, absolutePath) {
32046
- const relative = path12.relative(homeRoot, absolutePath);
32951
+ const relative = path14.relative(homeRoot, absolutePath);
32047
32952
  if (!relative) {
32048
32953
  return ".";
32049
32954
  }
32050
- return relative.split(path12.sep).join("/");
32955
+ return relative.split(path14.sep).join("/");
32051
32956
  }
32052
32957
  function isPathInsideRoot(root, target) {
32053
- const relative = path12.relative(root, target);
32054
- return relative === "" || !relative.startsWith("..") && !path12.isAbsolute(relative);
32958
+ const relative = path14.relative(root, target);
32959
+ return relative === "" || !relative.startsWith("..") && !path14.isAbsolute(relative);
32055
32960
  }
32056
32961
  function normalizeQueryParts(query2, homeRoot) {
32057
32962
  const typedQuery = query2.trim().replace(/\\/g, "/");
@@ -32067,9 +32972,9 @@ function normalizeQueryParts(query2, homeRoot) {
32067
32972
  normalized = normalized.slice(1);
32068
32973
  }
32069
32974
  }
32070
- if (path12.isAbsolute(normalized)) {
32975
+ if (path14.isAbsolute(normalized)) {
32071
32976
  isRooted = true;
32072
- const absolute = path12.resolve(normalized);
32977
+ const absolute = path14.resolve(normalized);
32073
32978
  if (!isPathInsideRoot(homeRoot, absolute)) {
32074
32979
  return null;
32075
32980
  }
@@ -32108,8 +33013,8 @@ function normalizeQueryParts(query2, homeRoot) {
32108
33013
  }
32109
33014
  function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
32110
33015
  let normalized = query2.trim().replace(/\\/g, "/");
32111
- if (path12.isAbsolute(normalized)) {
32112
- const absolute = path12.resolve(normalized);
33016
+ if (path14.isAbsolute(normalized)) {
33017
+ const absolute = path14.resolve(normalized);
32113
33018
  if (!isPathInsideRoot(workspaceRoot, absolute)) {
32114
33019
  return null;
32115
33020
  }
@@ -32135,7 +33040,7 @@ function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
32135
33040
  }
32136
33041
  async function resolveDirectory(inputPath) {
32137
33042
  try {
32138
- const resolved = await realpath(path12.resolve(inputPath));
33043
+ const resolved = await realpath(path14.resolve(inputPath));
32139
33044
  const stats = await stat3(resolved);
32140
33045
  if (!stats.isDirectory()) {
32141
33046
  return null;
@@ -32162,7 +33067,7 @@ async function listChildDirectories(input) {
32162
33067
  if (!dirent.isDirectory() && !dirent.isSymbolicLink()) {
32163
33068
  continue;
32164
33069
  }
32165
- const candidatePath = path12.join(input.directory, dirent.name);
33070
+ const candidatePath = path14.join(input.directory, dirent.name);
32166
33071
  const absolutePath = await resolveDirectoryCandidate({
32167
33072
  candidatePath,
32168
33073
  dirent,
@@ -32199,7 +33104,7 @@ async function listWorkspaceChildEntries(input) {
32199
33104
  if (isIgnoredWorkspaceDirectoryName(dirent.name)) {
32200
33105
  continue;
32201
33106
  }
32202
- const candidatePath = path12.join(input.directory, dirent.name);
33107
+ const candidatePath = path14.join(input.directory, dirent.name);
32203
33108
  const entry = await resolveWorkspaceCandidate({
32204
33109
  candidatePath,
32205
33110
  dirent,
@@ -32222,7 +33127,7 @@ async function listWorkspaceChildEntries(input) {
32222
33127
  }
32223
33128
  async function resolveDirectoryCandidate(input) {
32224
33129
  if (input.dirent.isDirectory()) {
32225
- const resolved2 = path12.resolve(input.candidatePath);
33130
+ const resolved2 = path14.resolve(input.candidatePath);
32226
33131
  return isPathInsideRoot(input.homeRoot, resolved2) ? resolved2 : null;
32227
33132
  }
32228
33133
  const resolved = await resolveDirectory(input.candidatePath);
@@ -32233,14 +33138,14 @@ async function resolveDirectoryCandidate(input) {
32233
33138
  }
32234
33139
  async function resolveWorkspaceCandidate(input) {
32235
33140
  if (input.dirent.isDirectory()) {
32236
- const resolved = path12.resolve(input.candidatePath);
33141
+ const resolved = path14.resolve(input.candidatePath);
32237
33142
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
32238
33143
  return null;
32239
33144
  }
32240
33145
  return { absolutePath: resolved, kind: "directory" };
32241
33146
  }
32242
33147
  if (input.dirent.isFile()) {
32243
- const resolved = path12.resolve(input.candidatePath);
33148
+ const resolved = path14.resolve(input.candidatePath);
32244
33149
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
32245
33150
  return null;
32246
33151
  }
@@ -32320,7 +33225,7 @@ function pruneWorkspaceEntryListCache() {
32320
33225
  // ../server/src/utils/directory-listing.ts
32321
33226
  import { readdir as readdir3, stat as stat4, realpath as realpath2 } from "node:fs/promises";
32322
33227
  import { homedir as homedir3 } from "node:os";
32323
- import path13 from "node:path";
33228
+ import path15 from "node:path";
32324
33229
  var DEFAULT_LIMIT2 = 500;
32325
33230
  async function listDirectoryContents(options) {
32326
33231
  const includeFiles = options.includeFiles ?? false;
@@ -32331,7 +33236,7 @@ async function listDirectoryContents(options) {
32331
33236
  const collected = [];
32332
33237
  for (const dirent of dirents) {
32333
33238
  if (!includeHidden && dirent.name.startsWith(".")) continue;
32334
- const childPath = path13.join(resolvedPath, dirent.name);
33239
+ const childPath = path15.join(resolvedPath, dirent.name);
32335
33240
  const kind = await classifyEntry(dirent, childPath);
32336
33241
  if (!kind) continue;
32337
33242
  if (kind === "file" && !includeFiles) continue;
@@ -32339,7 +33244,7 @@ async function listDirectoryContents(options) {
32339
33244
  if (collected.length >= limit) break;
32340
33245
  }
32341
33246
  collected.sort(compareEntries);
32342
- const parent = path13.dirname(resolvedPath);
33247
+ const parent = path15.dirname(resolvedPath);
32343
33248
  return {
32344
33249
  path: resolvedPath,
32345
33250
  parent: parent === resolvedPath ? null : parent,
@@ -32350,12 +33255,12 @@ async function resolveAbsolutePath(rawPath) {
32350
33255
  const home = process.env.HOME ?? homedir3();
32351
33256
  const trimmed = rawPath.trim();
32352
33257
  if (trimmed === "" || trimmed === "~") {
32353
- return path13.resolve(home);
33258
+ return path15.resolve(home);
32354
33259
  }
32355
33260
  if (trimmed.startsWith("~/")) {
32356
- return path13.resolve(home, trimmed.slice(2));
33261
+ return path15.resolve(home, trimmed.slice(2));
32357
33262
  }
32358
- if (!path13.isAbsolute(trimmed)) {
33263
+ if (!path15.isAbsolute(trimmed)) {
32359
33264
  throw new Error(
32360
33265
  `list_directory requires an absolute path, an empty string, or a "~"-prefixed path; got ${JSON.stringify(rawPath)}`
32361
33266
  );
@@ -32363,7 +33268,7 @@ async function resolveAbsolutePath(rawPath) {
32363
33268
  try {
32364
33269
  return await realpath2(trimmed);
32365
33270
  } catch {
32366
- return path13.resolve(trimmed);
33271
+ return path15.resolve(trimmed);
32367
33272
  }
32368
33273
  }
32369
33274
  async function classifyEntry(dirent, fullPath) {
@@ -32496,9 +33401,9 @@ function buildChatMentionNotification(input) {
32496
33401
  }
32497
33402
 
32498
33403
  // ../server/src/server/roles/scanner.ts
32499
- import fs7 from "node:fs/promises";
33404
+ import fs9 from "node:fs/promises";
32500
33405
  import os7 from "node:os";
32501
- import path14 from "node:path";
33406
+ import path16 from "node:path";
32502
33407
  var NAME_REGEX2 = /^[a-z0-9][a-z0-9._-]*$/i;
32503
33408
  function homeDir2() {
32504
33409
  return process.env.HOME || os7.homedir();
@@ -32508,22 +33413,22 @@ function resolveScopeDir2(scope, workspaceRoot) {
32508
33413
  if (!workspaceRoot) {
32509
33414
  throw new Error('workspaceRoot is required for scope "project"');
32510
33415
  }
32511
- return path14.join(workspaceRoot, ".roles");
33416
+ return path16.join(workspaceRoot, ".roles");
32512
33417
  }
32513
- return path14.join(homeDir2(), ".appostle", ".roles");
33418
+ return path16.join(homeDir2(), ".appostle", ".roles");
32514
33419
  }
32515
33420
  function allowedRoots2(workspaceRoot) {
32516
- const roots = [path14.join(homeDir2(), ".appostle", ".roles")];
33421
+ const roots = [path16.join(homeDir2(), ".appostle", ".roles")];
32517
33422
  if (workspaceRoot) {
32518
- roots.push(path14.join(workspaceRoot, ".roles"));
33423
+ roots.push(path16.join(workspaceRoot, ".roles"));
32519
33424
  }
32520
- return roots.map((r) => path14.resolve(r));
33425
+ return roots.map((r) => path16.resolve(r));
32521
33426
  }
32522
33427
  function isInsideAllowedRoot2(absPath, workspaceRoot) {
32523
- const resolved = path14.resolve(absPath);
33428
+ const resolved = path16.resolve(absPath);
32524
33429
  for (const root of allowedRoots2(workspaceRoot)) {
32525
- const rel = path14.relative(root, resolved);
32526
- if (rel === "" || !rel.startsWith("..") && !path14.isAbsolute(rel)) {
33430
+ const rel = path16.relative(root, resolved);
33431
+ if (rel === "" || !rel.startsWith("..") && !path16.isAbsolute(rel)) {
32527
33432
  return true;
32528
33433
  }
32529
33434
  }
@@ -32711,7 +33616,7 @@ function rewriteFrontmatter2(originalLines, next) {
32711
33616
  async function readRolesFromDir(scope, dir, category) {
32712
33617
  let entries;
32713
33618
  try {
32714
- entries = await fs7.readdir(dir, { withFileTypes: true });
33619
+ entries = await fs9.readdir(dir, { withFileTypes: true });
32715
33620
  } catch {
32716
33621
  return [];
32717
33622
  }
@@ -32721,17 +33626,17 @@ async function readRolesFromDir(scope, dir, category) {
32721
33626
  if (!entry.name.endsWith(".md")) continue;
32722
33627
  const name = entry.name.slice(0, -".md".length);
32723
33628
  if (!name || !NAME_REGEX2.test(name)) continue;
32724
- const fullPath = path14.join(dir, entry.name);
33629
+ const fullPath = path16.join(dir, entry.name);
32725
33630
  let stat5;
32726
33631
  try {
32727
- const s = await fs7.stat(fullPath);
33632
+ const s = await fs9.stat(fullPath);
32728
33633
  stat5 = { mtime: s.mtime, size: s.size };
32729
33634
  } catch {
32730
33635
  continue;
32731
33636
  }
32732
33637
  let parsed;
32733
33638
  try {
32734
- const text = await fs7.readFile(fullPath, "utf8");
33639
+ const text = await fs9.readFile(fullPath, "utf8");
32735
33640
  parsed = parseFrontmatter(text);
32736
33641
  } catch {
32737
33642
  parsed = {
@@ -32765,13 +33670,13 @@ async function readRolesFromDir(scope, dir, category) {
32765
33670
  async function readRolesFromScopeDir(scope, scopeDir) {
32766
33671
  let topEntries;
32767
33672
  try {
32768
- topEntries = await fs7.readdir(scopeDir, { withFileTypes: true });
33673
+ topEntries = await fs9.readdir(scopeDir, { withFileTypes: true });
32769
33674
  } catch {
32770
33675
  return [];
32771
33676
  }
32772
33677
  const flat = await readRolesFromDir(scope, scopeDir, null);
32773
33678
  const categoryResults = await Promise.all(
32774
- 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))
32775
33680
  );
32776
33681
  const all = [...flat, ...categoryResults.flat()];
32777
33682
  all.sort((a, b) => {
@@ -32811,11 +33716,11 @@ async function createRole(args) {
32811
33716
  throw new Error(`Role name must not contain path separators or "..".`);
32812
33717
  }
32813
33718
  const scopeDir = resolveScopeDir2(args.scope, args.workspaceRoot);
32814
- const dir = args.category ? path14.join(scopeDir, args.category) : scopeDir;
32815
- await fs7.mkdir(dir, { recursive: true });
32816
- 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`);
32817
33722
  try {
32818
- await fs7.access(filePath);
33723
+ await fs9.access(filePath);
32819
33724
  throw new Error(`A role named "${args.name}" already exists at ${filePath}`);
32820
33725
  } catch (err) {
32821
33726
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -32826,7 +33731,7 @@ async function createRole(args) {
32826
33731
  }
32827
33732
  }
32828
33733
  const initial = buildStarterRole(args.name);
32829
- await fs7.writeFile(filePath, initial, "utf8");
33734
+ await fs9.writeFile(filePath, initial, "utf8");
32830
33735
  return { path: filePath };
32831
33736
  }
32832
33737
  function buildStarterRole(name) {
@@ -32847,7 +33752,7 @@ the role is invoked.
32847
33752
  `;
32848
33753
  }
32849
33754
  async function writeRoleFrontmatter(args, workspaceRoot) {
32850
- if (!path14.isAbsolute(args.path)) {
33755
+ if (!path16.isAbsolute(args.path)) {
32851
33756
  throw new Error(`writeRoleFrontmatter expects an absolute path; got "${args.path}"`);
32852
33757
  }
32853
33758
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
@@ -32855,7 +33760,7 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
32855
33760
  }
32856
33761
  let original;
32857
33762
  try {
32858
- original = await fs7.readFile(args.path, "utf8");
33763
+ original = await fs9.readFile(args.path, "utf8");
32859
33764
  } catch (err) {
32860
33765
  throw new Error(
32861
33766
  `Failed to read role file: ${err instanceof Error ? err.message : String(err)}`
@@ -32868,23 +33773,23 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
32868
33773
  ${parsed.body}` : `${nextFrontmatter}
32869
33774
 
32870
33775
  ${original}`;
32871
- await fs7.writeFile(args.path, nextContent, "utf8");
33776
+ await fs9.writeFile(args.path, nextContent, "utf8");
32872
33777
  }
32873
33778
  async function moveRole(args, workspaceRoot) {
32874
- if (!path14.isAbsolute(args.path)) {
33779
+ if (!path16.isAbsolute(args.path)) {
32875
33780
  throw new Error(`moveRole expects an absolute path; got "${args.path}"`);
32876
33781
  }
32877
33782
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
32878
33783
  throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
32879
33784
  }
32880
- const oldDir = path14.dirname(args.path);
32881
- const oldFilename = path14.basename(args.path, ".md");
33785
+ const oldDir = path16.dirname(args.path);
33786
+ const oldFilename = path16.basename(args.path, ".md");
32882
33787
  const roots = allowedRoots2(workspaceRoot);
32883
- const rolesRoot = roots.find((r) => path14.resolve(args.path).startsWith(r));
33788
+ const rolesRoot = roots.find((r) => path16.resolve(args.path).startsWith(r));
32884
33789
  if (!rolesRoot) {
32885
33790
  throw new Error(`Cannot determine roles root for "${args.path}"`);
32886
33791
  }
32887
- const relFromRoot = path14.relative(rolesRoot, path14.dirname(args.path));
33792
+ const relFromRoot = path16.relative(rolesRoot, path16.dirname(args.path));
32888
33793
  const currentCategory = relFromRoot && relFromRoot !== "." ? relFromRoot : "";
32889
33794
  const newName = args.newName ?? oldFilename;
32890
33795
  const newCategory = args.newCategory !== void 0 ? args.newCategory : currentCategory;
@@ -32894,19 +33799,19 @@ async function moveRole(args, workspaceRoot) {
32894
33799
  if (newCategory && !NAME_REGEX2.test(newCategory)) {
32895
33800
  throw new Error(`Invalid category name: "${newCategory}"`);
32896
33801
  }
32897
- const newDir = newCategory ? path14.join(rolesRoot, newCategory) : rolesRoot;
32898
- const newPath = path14.join(newDir, `${newName}.md`);
32899
- 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)) {
32900
33805
  return { path: args.path };
32901
33806
  }
32902
- await fs7.mkdir(newDir, { recursive: true });
33807
+ await fs9.mkdir(newDir, { recursive: true });
32903
33808
  try {
32904
- await fs7.access(newPath);
33809
+ await fs9.access(newPath);
32905
33810
  throw new Error(`A role already exists at "${newPath}"`);
32906
33811
  } catch (err) {
32907
33812
  if (err.code !== "ENOENT") throw err;
32908
33813
  }
32909
- await fs7.rename(args.path, newPath);
33814
+ await fs9.rename(args.path, newPath);
32910
33815
  const frontmatterPatch = {};
32911
33816
  if (newName !== oldFilename) {
32912
33817
  frontmatterPatch.description = void 0;
@@ -32922,9 +33827,9 @@ async function moveRole(args, workspaceRoot) {
32922
33827
  );
32923
33828
  if (oldDir !== rolesRoot) {
32924
33829
  try {
32925
- const remaining = await fs7.readdir(oldDir);
33830
+ const remaining = await fs9.readdir(oldDir);
32926
33831
  if (remaining.length === 0) {
32927
- await fs7.rmdir(oldDir);
33832
+ await fs9.rmdir(oldDir);
32928
33833
  }
32929
33834
  } catch {
32930
33835
  }
@@ -32933,30 +33838,30 @@ async function moveRole(args, workspaceRoot) {
32933
33838
  }
32934
33839
 
32935
33840
  // ../server/src/server/brands/scanner.ts
32936
- import fs8 from "node:fs/promises";
32937
- import path15 from "node:path";
33841
+ import fs10 from "node:fs/promises";
33842
+ import path17 from "node:path";
32938
33843
  var NAME_REGEX3 = /^[a-z0-9][a-z0-9._-]*$/i;
32939
33844
  function resolveScopeDir3(scope, workspaceRoot) {
32940
33845
  if (scope === "project") {
32941
33846
  if (!workspaceRoot) {
32942
33847
  throw new Error('workspaceRoot is required for scope "project"');
32943
33848
  }
32944
- return path15.join(workspaceRoot, ".brand");
33849
+ return path17.join(workspaceRoot, ".brand");
32945
33850
  }
32946
33851
  throw new Error(`Unknown scope: ${scope}`);
32947
33852
  }
32948
33853
  function allowedRoots3(workspaceRoot) {
32949
33854
  const roots = [];
32950
33855
  if (workspaceRoot) {
32951
- roots.push(path15.join(workspaceRoot, ".brand"));
33856
+ roots.push(path17.join(workspaceRoot, ".brand"));
32952
33857
  }
32953
- return roots.map((r) => path15.resolve(r));
33858
+ return roots.map((r) => path17.resolve(r));
32954
33859
  }
32955
33860
  function isInsideAllowedRoot3(absPath, workspaceRoot) {
32956
- const resolved = path15.resolve(absPath);
33861
+ const resolved = path17.resolve(absPath);
32957
33862
  for (const root of allowedRoots3(workspaceRoot)) {
32958
- const rel = path15.relative(root, resolved);
32959
- if (rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel)) {
33863
+ const rel = path17.relative(root, resolved);
33864
+ if (rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel)) {
32960
33865
  return true;
32961
33866
  }
32962
33867
  }
@@ -33173,7 +34078,7 @@ function rewriteFrontmatter3(originalLines, next) {
33173
34078
  async function readBrandsFromDir(scope, dir) {
33174
34079
  let entries;
33175
34080
  try {
33176
- entries = await fs8.readdir(dir, { withFileTypes: true });
34081
+ entries = await fs10.readdir(dir, { withFileTypes: true });
33177
34082
  } catch {
33178
34083
  return [];
33179
34084
  }
@@ -33183,17 +34088,17 @@ async function readBrandsFromDir(scope, dir) {
33183
34088
  if (!entry.name.endsWith(".md")) continue;
33184
34089
  const name = entry.name.slice(0, -".md".length);
33185
34090
  if (!name || !NAME_REGEX3.test(name)) continue;
33186
- const fullPath = path15.join(dir, entry.name);
34091
+ const fullPath = path17.join(dir, entry.name);
33187
34092
  let stat5;
33188
34093
  try {
33189
- const s = await fs8.stat(fullPath);
34094
+ const s = await fs10.stat(fullPath);
33190
34095
  stat5 = { mtime: s.mtime, size: s.size };
33191
34096
  } catch {
33192
34097
  continue;
33193
34098
  }
33194
34099
  let parsed;
33195
34100
  try {
33196
- const text = await fs8.readFile(fullPath, "utf8");
34101
+ const text = await fs10.readFile(fullPath, "utf8");
33197
34102
  const { rawFrontmatterLines, hadFrontmatter } = parseBrandFile(text);
33198
34103
  parsed = hadFrontmatter ? parseFrontmatter2(rawFrontmatterLines) : { description: "", tags: [], variables: [] };
33199
34104
  } catch {
@@ -33227,10 +34132,10 @@ async function createBrand(args) {
33227
34132
  throw new Error(`Brand name must not contain path separators or "..".`);
33228
34133
  }
33229
34134
  const dir = resolveScopeDir3("project", args.workspaceRoot);
33230
- await fs8.mkdir(dir, { recursive: true });
33231
- const filePath = path15.join(dir, `${args.name}.md`);
34135
+ await fs10.mkdir(dir, { recursive: true });
34136
+ const filePath = path17.join(dir, `${args.name}.md`);
33232
34137
  try {
33233
- await fs8.access(filePath);
34138
+ await fs10.access(filePath);
33234
34139
  throw new Error(`A brand named "${args.name}" already exists at ${filePath}`);
33235
34140
  } catch (err) {
33236
34141
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -33240,7 +34145,7 @@ async function createBrand(args) {
33240
34145
  throw err;
33241
34146
  }
33242
34147
  }
33243
- await fs8.writeFile(filePath, buildStarterBrand(args.name), "utf8");
34148
+ await fs10.writeFile(filePath, buildStarterBrand(args.name), "utf8");
33244
34149
  return { path: filePath };
33245
34150
  }
33246
34151
  function buildStarterBrand(name) {
@@ -33285,7 +34190,7 @@ async function copyBrandAsset(args) {
33285
34190
  if (!args.workspaceRoot) {
33286
34191
  throw new Error("workspaceRoot is required to copy a brand asset");
33287
34192
  }
33288
- if (!args.sourcePath || !path15.isAbsolute(args.sourcePath)) {
34193
+ if (!args.sourcePath || !path17.isAbsolute(args.sourcePath)) {
33289
34194
  throw new Error(`copyBrandAsset expects an absolute sourcePath; got "${args.sourcePath}"`);
33290
34195
  }
33291
34196
  if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
@@ -33293,20 +34198,20 @@ async function copyBrandAsset(args) {
33293
34198
  `Invalid targetName "${args.targetName}". Use letters, digits, dot, underscore, dash.`
33294
34199
  );
33295
34200
  }
33296
- const stats = await fs8.stat(args.sourcePath);
34201
+ const stats = await fs10.stat(args.sourcePath);
33297
34202
  if (!stats.isFile()) {
33298
34203
  throw new Error(`Source path is not a regular file: ${args.sourcePath}`);
33299
34204
  }
33300
- const ext = path15.extname(args.sourcePath).toLowerCase();
34205
+ const ext = path17.extname(args.sourcePath).toLowerCase();
33301
34206
  const fileName = `${args.targetName}${ext}`;
33302
- const assetsDir = path15.join(args.workspaceRoot, ".brand", "assets");
33303
- const destAbs = path15.resolve(assetsDir, fileName);
33304
- const rel = path15.relative(path15.resolve(assetsDir), destAbs);
33305
- 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)) {
33306
34211
  throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
33307
34212
  }
33308
- await fs8.mkdir(assetsDir, { recursive: true });
33309
- await fs8.copyFile(args.sourcePath, destAbs);
34213
+ await fs10.mkdir(assetsDir, { recursive: true });
34214
+ await fs10.copyFile(args.sourcePath, destAbs);
33310
34215
  return {
33311
34216
  relativePath: `assets/${fileName}`,
33312
34217
  absolutePath: destAbs
@@ -33324,13 +34229,13 @@ async function uploadBrandAsset(args) {
33324
34229
  if (!args.dataBase64 || args.dataBase64.trim().length === 0) {
33325
34230
  throw new Error("No file data provided for brand asset upload");
33326
34231
  }
33327
- const extFromSource = args.sourceName ? path15.extname(args.sourceName).toLowerCase() : "";
34232
+ const extFromSource = args.sourceName ? path17.extname(args.sourceName).toLowerCase() : "";
33328
34233
  const ext = extFromSource || ".png";
33329
34234
  const fileName = `${args.targetName}${ext}`;
33330
- const assetsDir = path15.join(args.workspaceRoot, ".brand", "assets");
33331
- const destAbs = path15.resolve(assetsDir, fileName);
33332
- const rel = path15.relative(path15.resolve(assetsDir), destAbs);
33333
- 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)) {
33334
34239
  throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
33335
34240
  }
33336
34241
  let data;
@@ -33342,15 +34247,15 @@ async function uploadBrandAsset(args) {
33342
34247
  if (data.length === 0) {
33343
34248
  throw new Error("Uploaded brand asset is empty");
33344
34249
  }
33345
- await fs8.mkdir(assetsDir, { recursive: true });
33346
- await fs8.writeFile(destAbs, data);
34250
+ await fs10.mkdir(assetsDir, { recursive: true });
34251
+ await fs10.writeFile(destAbs, data);
33347
34252
  return {
33348
34253
  relativePath: `assets/${fileName}`,
33349
34254
  absolutePath: destAbs
33350
34255
  };
33351
34256
  }
33352
34257
  async function writeBrandFrontmatter(args, workspaceRoot) {
33353
- if (!path15.isAbsolute(args.path)) {
34258
+ if (!path17.isAbsolute(args.path)) {
33354
34259
  throw new Error(`writeBrandFrontmatter expects an absolute path; got "${args.path}"`);
33355
34260
  }
33356
34261
  if (!isInsideAllowedRoot3(args.path, workspaceRoot)) {
@@ -33358,7 +34263,7 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
33358
34263
  }
33359
34264
  let original;
33360
34265
  try {
33361
- original = await fs8.readFile(args.path, "utf8");
34266
+ original = await fs10.readFile(args.path, "utf8");
33362
34267
  } catch (err) {
33363
34268
  throw new Error(
33364
34269
  `Failed to read brand file: ${err instanceof Error ? err.message : String(err)}`
@@ -33371,14 +34276,14 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
33371
34276
  ${parsed.body}` : `${nextFrontmatter}
33372
34277
 
33373
34278
  ${original}`;
33374
- await fs8.writeFile(args.path, nextContent, "utf8");
34279
+ await fs10.writeFile(args.path, nextContent, "utf8");
33375
34280
  }
33376
34281
 
33377
34282
  // ../server/src/services/oauth-service.ts
33378
- import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
34283
+ import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
33379
34284
  import { mkdir as mkdir4, readFile as readFile3, rename, unlink, writeFile as writeFile4 } from "node:fs/promises";
33380
34285
  import { homedir as homedir4 } from "node:os";
33381
- import { dirname as dirname4, join as join9 } from "node:path";
34286
+ import { dirname as dirname4, join as join11 } from "node:path";
33382
34287
  import * as YAML from "yaml";
33383
34288
  var GITLAB_CLIENT_ID = "7783dfb277ccf3dbce35a9d54c3c0aec24ed2006cb2d171e0749fd8444ed4f7c";
33384
34289
  var GITLAB_AUTHORIZE_URL = "https://gitlab.com/oauth/authorize";
@@ -33400,7 +34305,7 @@ var OAuthError = class extends Error {
33400
34305
  function createOAuthService(options) {
33401
34306
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
33402
34307
  const now = options.now ?? Date.now;
33403
- const credentialsPath = join9(options.appostleHome, "credentials.json");
34308
+ const credentialsPath = join11(options.appostleHome, "credentials.json");
33404
34309
  const glabConfigPath = options.glabConfigPath ?? defaultGlabConfigPath();
33405
34310
  const oauthLogger = options.logger.child({ module: "oauth-service" });
33406
34311
  const pending = /* @__PURE__ */ new Map();
@@ -33522,7 +34427,7 @@ function generateCodeVerifier() {
33522
34427
  return base64url(randomBytes2(32));
33523
34428
  }
33524
34429
  function computeCodeChallenge(verifier) {
33525
- return base64url(createHash3("sha256").update(verifier).digest());
34430
+ return base64url(createHash4("sha256").update(verifier).digest());
33526
34431
  }
33527
34432
  function generateState() {
33528
34433
  return base64url(randomBytes2(24));
@@ -33619,80 +34524,80 @@ async function fetchGitLabUsername(fetchImpl, accessToken) {
33619
34524
  return null;
33620
34525
  }
33621
34526
  }
33622
- async function readCredential(path24, log2) {
34527
+ async function readCredential(path26, log2) {
33623
34528
  try {
33624
- const raw = await readFile3(path24, "utf8");
34529
+ const raw = await readFile3(path26, "utf8");
33625
34530
  const parsed = JSON.parse(raw);
33626
34531
  return parsed.gitlab ?? null;
33627
34532
  } catch (error) {
33628
34533
  if (isNotFound(error)) return null;
33629
- log2.warn({ err: error, path: path24 }, "oauth.credentials.read_failed");
34534
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.read_failed");
33630
34535
  return null;
33631
34536
  }
33632
34537
  }
33633
- async function persistCredential(path24, credential, log2) {
33634
- await mkdir4(dirname4(path24), { recursive: true });
34538
+ async function persistCredential(path26, credential, log2) {
34539
+ await mkdir4(dirname4(path26), { recursive: true });
33635
34540
  let current = {};
33636
34541
  try {
33637
- const raw = await readFile3(path24, "utf8");
34542
+ const raw = await readFile3(path26, "utf8");
33638
34543
  current = JSON.parse(raw);
33639
34544
  } catch (error) {
33640
34545
  if (!isNotFound(error)) {
33641
- log2.warn({ err: error, path: path24 }, "oauth.credentials.read_failed_overwriting");
34546
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.read_failed_overwriting");
33642
34547
  }
33643
34548
  }
33644
34549
  const next = { ...current, gitlab: credential };
33645
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34550
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33646
34551
  await writeFile4(tmpPath, JSON.stringify(next, null, 2), { mode: 384 });
33647
- await rename(tmpPath, path24);
34552
+ await rename(tmpPath, path26);
33648
34553
  }
33649
- async function deleteCredential(path24, log2) {
34554
+ async function deleteCredential(path26, log2) {
33650
34555
  let current = {};
33651
34556
  try {
33652
- const raw = await readFile3(path24, "utf8");
34557
+ const raw = await readFile3(path26, "utf8");
33653
34558
  current = JSON.parse(raw);
33654
34559
  } catch (error) {
33655
34560
  if (isNotFound(error)) return;
33656
- log2.warn({ err: error, path: path24 }, "oauth.credentials.delete_read_failed");
34561
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.delete_read_failed");
33657
34562
  return;
33658
34563
  }
33659
34564
  delete current.gitlab;
33660
34565
  if (Object.keys(current).length === 0) {
33661
34566
  try {
33662
- await unlink(path24);
34567
+ await unlink(path26);
33663
34568
  } catch (error) {
33664
34569
  if (!isNotFound(error)) {
33665
- log2.warn({ err: error, path: path24 }, "oauth.credentials.unlink_failed");
34570
+ log2.warn({ err: error, path: path26 }, "oauth.credentials.unlink_failed");
33666
34571
  }
33667
34572
  }
33668
34573
  return;
33669
34574
  }
33670
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34575
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33671
34576
  await writeFile4(tmpPath, JSON.stringify(current, null, 2), { mode: 384 });
33672
- await rename(tmpPath, path24);
34577
+ await rename(tmpPath, path26);
33673
34578
  }
33674
34579
  function defaultGlabConfigPath() {
33675
34580
  const home = homedir4();
33676
34581
  if (process.platform === "darwin") {
33677
- return join9(home, "Library", "Application Support", "glab-cli", "config.yml");
34582
+ return join11(home, "Library", "Application Support", "glab-cli", "config.yml");
33678
34583
  }
33679
34584
  if (process.platform === "win32") {
33680
34585
  const appData = process.env.APPDATA;
33681
- if (appData) return join9(appData, "glab-cli", "config.yml");
33682
- 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");
33683
34588
  }
33684
34589
  const xdg = process.env.XDG_CONFIG_HOME;
33685
- 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");
33686
34591
  }
33687
- async function writeGlabConfig(path24, credential, log2) {
33688
- await mkdir4(dirname4(path24), { recursive: true });
34592
+ async function writeGlabConfig(path26, credential, log2) {
34593
+ await mkdir4(dirname4(path26), { recursive: true });
33689
34594
  let doc;
33690
34595
  try {
33691
- const raw = await readFile3(path24, "utf8");
34596
+ const raw = await readFile3(path26, "utf8");
33692
34597
  doc = YAML.parseDocument(raw);
33693
34598
  if (doc.errors.length > 0) {
33694
34599
  log2.warn(
33695
- { errors: doc.errors.map((e) => e.message), path: path24 },
34600
+ { errors: doc.errors.map((e) => e.message), path: path26 },
33696
34601
  "oauth.glab.parse_errors_replacing"
33697
34602
  );
33698
34603
  doc = YAML.parseDocument("{}");
@@ -33701,7 +34606,7 @@ async function writeGlabConfig(path24, credential, log2) {
33701
34606
  if (isNotFound(error)) {
33702
34607
  doc = YAML.parseDocument("{}");
33703
34608
  } else {
33704
- log2.warn({ err: error, path: path24 }, "oauth.glab.read_failed_replacing");
34609
+ log2.warn({ err: error, path: path26 }, "oauth.glab.read_failed_replacing");
33705
34610
  doc = YAML.parseDocument("{}");
33706
34611
  }
33707
34612
  }
@@ -33714,18 +34619,18 @@ async function writeGlabConfig(path24, credential, log2) {
33714
34619
  if (credential.username) {
33715
34620
  hostEntry.set("user", credential.username);
33716
34621
  }
33717
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34622
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33718
34623
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
33719
- await rename(tmpPath, path24);
34624
+ await rename(tmpPath, path26);
33720
34625
  }
33721
- async function removeGlabHost(path24, log2) {
34626
+ async function removeGlabHost(path26, log2) {
33722
34627
  let doc;
33723
34628
  try {
33724
- const raw = await readFile3(path24, "utf8");
34629
+ const raw = await readFile3(path26, "utf8");
33725
34630
  doc = YAML.parseDocument(raw);
33726
34631
  } catch (error) {
33727
34632
  if (isNotFound(error)) return;
33728
- log2.warn({ err: error, path: path24 }, "oauth.glab.remove_read_failed");
34633
+ log2.warn({ err: error, path: path26 }, "oauth.glab.remove_read_failed");
33729
34634
  return;
33730
34635
  }
33731
34636
  const hosts = doc.get("hosts");
@@ -33734,9 +34639,9 @@ async function removeGlabHost(path24, log2) {
33734
34639
  if (hosts.items.length === 0) {
33735
34640
  doc.delete("hosts");
33736
34641
  }
33737
- const tmpPath = `${path24}.tmp-${process.pid}-${Date.now()}`;
34642
+ const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
33738
34643
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
33739
- await rename(tmpPath, path24);
34644
+ await rename(tmpPath, path26);
33740
34645
  }
33741
34646
  function ensureMap(doc, key) {
33742
34647
  const existing = doc.get(key);
@@ -34702,6 +35607,50 @@ function summarizeFetchWorkspacesEntries(entries) {
34702
35607
  workspaces
34703
35608
  };
34704
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
+ }
34705
35654
  var SessionRequestError = class extends Error {
34706
35655
  constructor(code, message) {
34707
35656
  super(message);
@@ -34879,6 +35828,8 @@ var Session = class _Session {
34879
35828
  });
34880
35829
  this.agentManager = agentManager;
34881
35830
  this.agentStorage = agentStorage;
35831
+ this.uploadStore = new SessionUploadStore({ logger: this.sessionLogger });
35832
+ this.imageStore = new SessionImageStore({ logger: this.sessionLogger });
34882
35833
  this.projectRegistry = projectRegistry;
34883
35834
  this.workspaceRegistry = workspaceRegistry;
34884
35835
  this.chatService = chatService;
@@ -34990,11 +35941,12 @@ var Session = class _Session {
34990
35941
  /**
34991
35942
  * Normalize a user prompt (with optional image metadata) for AgentManager
34992
35943
  */
34993
- buildAgentPrompt(text, images, attachments) {
35944
+ buildAgentPrompt(text, images, attachments, files) {
34994
35945
  const normalized = text?.trim() ?? "";
34995
35946
  const hasImages = Boolean(images && images.length > 0);
34996
35947
  const hasAttachments = Boolean(attachments && attachments.length > 0);
34997
- if (!hasImages && !hasAttachments) {
35948
+ const hasFiles = Boolean(files && files.length > 0);
35949
+ if (!hasImages && !hasAttachments && !hasFiles) {
34998
35950
  return normalized;
34999
35951
  }
35000
35952
  const blocks = [];
@@ -35007,8 +35959,46 @@ var Session = class _Session {
35007
35959
  for (const attachment of attachments ?? []) {
35008
35960
  blocks.push(attachment);
35009
35961
  }
35962
+ if (hasFiles && files) {
35963
+ blocks.push({ type: "text", text: renderFileAttachmentFooter(files) });
35964
+ }
35010
35965
  return blocks;
35011
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
+ }
35012
36002
  /**
35013
36003
  * Interrupt the agent's active run so the next prompt starts a fresh turn.
35014
36004
  * Returns once the manager confirms the stream has been cancelled.
@@ -35477,6 +36467,21 @@ var Session = class _Session {
35477
36467
  case "send_agent_message_request":
35478
36468
  await this.handleSendAgentMessageRequest(msg);
35479
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;
35480
36485
  case "wait_for_finish_request":
35481
36486
  await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
35482
36487
  break;
@@ -36747,26 +37752,86 @@ var Session = class _Session {
36747
37752
  * Handle text message to agent (with optional image attachments)
36748
37753
  */
36749
37754
  async handleSendAgentMessage(agentId, text, messageId, images, attachments, runOptions, options) {
37755
+ const files = options?.files ?? [];
36750
37756
  this.sessionLogger.info(
36751
37757
  {
36752
37758
  agentId,
36753
37759
  textPreview: text.substring(0, 50),
36754
37760
  imageCount: images?.length ?? 0,
36755
- attachmentCount: attachments?.length ?? 0
37761
+ attachmentCount: attachments?.length ?? 0,
37762
+ fileCount: files.length
36756
37763
  },
36757
- `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)` : ""}`
36758
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
+ }
36759
37806
  const promptText = options?.spokenInput ? wrapSpokenInput(text) : text;
36760
- 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);
36761
37823
  try {
36762
37824
  await sendPromptToAgent({
36763
37825
  agentManager: this.agentManager,
36764
37826
  agentStorage: this.agentStorage,
36765
37827
  agentId,
36766
- userMessageText: text,
37828
+ userMessageText: recordedText,
36767
37829
  prompt,
36768
37830
  messageId,
36769
37831
  runOptions,
37832
+ ...userMessageImages.length > 0 ? { userMessageImages } : {},
37833
+ ...userMessageFiles.length > 0 ? { userMessageFiles } : {},
37834
+ ...recordedTextIsSynthesized ? { userMessageTextIsSynthesized: true } : {},
36770
37835
  logger: this.sessionLogger
36771
37836
  });
36772
37837
  return { ok: true };
@@ -36791,6 +37856,7 @@ var Session = class _Session {
36791
37856
  outputSchema,
36792
37857
  git,
36793
37858
  images,
37859
+ files,
36794
37860
  attachments,
36795
37861
  labels
36796
37862
  } = msg;
@@ -36831,7 +37897,7 @@ var Session = class _Session {
36831
37897
  }
36832
37898
  );
36833
37899
  await this.forwardAgentUpdate(snapshot);
36834
- 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) {
36835
37901
  scheduleAgentMetadataGeneration({
36836
37902
  agentManager: this.agentManager,
36837
37903
  agentId: snapshot.id,
@@ -36847,7 +37913,8 @@ var Session = class _Session {
36847
37913
  resolveClientMessageId(clientMessageId),
36848
37914
  images,
36849
37915
  attachments,
36850
- outputSchema ? { outputSchema } : void 0
37916
+ outputSchema ? { outputSchema } : void 0,
37917
+ files && files.length > 0 ? { files } : void 0
36851
37918
  );
36852
37919
  if (!started.ok) {
36853
37920
  throw new Error(started.error);
@@ -38264,7 +39331,7 @@ var Session = class _Session {
38264
39331
  homeDir: process.env.HOME ?? homedir5(),
38265
39332
  query: query2,
38266
39333
  limit
38267
- })).map((path24) => ({ path: path24, kind: "directory" }));
39334
+ })).map((path26) => ({ path: path26, kind: "directory" }));
38268
39335
  const directories = entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
38269
39336
  this.emit({
38270
39337
  type: "directory_suggestions_response",
@@ -39318,10 +40385,11 @@ ${details}`.trim());
39318
40385
  root: cwd,
39319
40386
  relativePath: requestedPath
39320
40387
  });
40388
+ const recoveredFileName = originalUploadFileName(info.path) ?? info.fileName;
39321
40389
  const entry = this.downloadTokenStore.issueToken({
39322
40390
  path: info.path,
39323
40391
  absolutePath: info.absolutePath,
39324
- fileName: info.fileName,
40392
+ fileName: recoveredFileName,
39325
40393
  mimeType: info.mimeType,
39326
40394
  size: info.size
39327
40395
  });
@@ -39368,7 +40436,7 @@ ${details}`.trim());
39368
40436
  );
39369
40437
  const registryRecords = await this.agentStorage.list();
39370
40438
  const liveIds = new Set(agentSnapshots.map((a) => a.id));
39371
- const persistedAgents = registryRecords.filter((record) => !liveIds.has(record.id) && !record.internal).map((record) => this.buildStoredAgentPayload(record));
40439
+ const persistedAgents = registryRecords.filter((record) => !liveIds.has(record.id)).map((record) => this.buildStoredAgentPayload(record));
39372
40440
  let agents = [...liveAgents, ...persistedAgents];
39373
40441
  agents = agents.filter((agent) => this.isProviderVisibleToClient(agent.provider));
39374
40442
  if (filter?.labels) {
@@ -40755,19 +41823,96 @@ ${details}`.trim());
40755
41823
  }
40756
41824
  try {
40757
41825
  const agentId = resolved.agentId;
40758
- 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
+ }));
40759
41894
  this.sessionLogger.trace(
40760
- { 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
+ },
40761
41902
  "send_agent_message_request: dispatching shared sendPromptToAgent"
40762
41903
  );
41904
+ const recordedTextIsSynthesized = msg.text.trim().length === 0 && (userMessageImages.length > 0 || userMessageFiles.length > 0);
40763
41905
  try {
40764
41906
  await sendPromptToAgent({
40765
41907
  agentManager: this.agentManager,
40766
41908
  agentStorage: this.agentStorage,
40767
41909
  agentId,
40768
- userMessageText: msg.text,
41910
+ userMessageText: recordedText,
40769
41911
  prompt,
40770
41912
  messageId: msg.messageId,
41913
+ ...userMessageImages.length > 0 ? { userMessageImages } : {},
41914
+ ...userMessageFiles.length > 0 ? { userMessageFiles } : {},
41915
+ ...recordedTextIsSynthesized ? { userMessageTextIsSynthesized: true } : {},
40771
41916
  logger: this.sessionLogger
40772
41917
  });
40773
41918
  } catch (error) {
@@ -40826,6 +41971,242 @@ ${details}`.trim());
40826
41971
  });
40827
41972
  }
40828
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
+ }
40829
42210
  async handleWaitForFinish(agentIdOrIdentifier, requestId, timeoutMs) {
40830
42211
  const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
40831
42212
  if (!resolved.ok) {
@@ -41970,6 +43351,7 @@ ${details}`.trim());
41970
43351
  status: w.status,
41971
43352
  cwd: w.cwd,
41972
43353
  prompt: w.prompt,
43354
+ name: w.name,
41973
43355
  createdAt: w.createdAt,
41974
43356
  updatedAt: w.updatedAt
41975
43357
  }));
@@ -43044,6 +44426,64 @@ var StoredLoopsSchema = z39.array(LoopRecordSchema2);
43044
44426
  import { z as z40 } from "zod";
43045
44427
  var StoredQuestsSchema = z40.array(QuestRecordSchema);
43046
44428
 
44429
+ // ../server/src/server/quest/quest-metadata-generator.ts
44430
+ import { z as z41 } from "zod";
44431
+
44432
+ // ../server/src/server/quest/orchestrator-mcp.ts
44433
+ import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
44434
+ import { z as z42 } from "zod";
44435
+ var HANDOFF_INPUT_SHAPE = {
44436
+ rolePath: z42.string().min(1).describe(
44437
+ "Absolute filesystem path to the role .md file the worker should adopt as its system prompt. Must be one of the role file paths listed in your team roster \u2014 do not invent paths."
44438
+ ),
44439
+ task: z42.string().min(1).describe(
44440
+ "Concrete, self-contained instruction for the worker. The worker has its own toolbelt and reads the role at rolePath as its system prompt \u2014 write the task as a normal user message describing what to do, what inputs to read, and where to write outputs. Reference the hivemind doc by absolute path if relevant."
44441
+ )
44442
+ };
44443
+
44444
+ // ../server/src/server/quest/runner-orchestrator.ts
44445
+ var FALLBACK_QUEEN_PROMPT_TEMPLATE = [
44446
+ "you are going to send out agents, but first you get aquanted with 'the team'",
44447
+ "You don't go do the work yourself!",
44448
+ "",
44449
+ "this team has very deep knowledge of the task at hand. they have very detailed skills and output prefs so they are smarter than you. don't assume otherwise.",
44450
+ "",
44451
+ "the team is split up in categories. you will try to consolidate all of their knowledge into an approach for the task below.",
44452
+ "",
44453
+ "",
44454
+ "The team:",
44455
+ "",
44456
+ "{{team}}",
44457
+ "",
44458
+ "===",
44459
+ "",
44460
+ "The task at hand:",
44461
+ "",
44462
+ "",
44463
+ ">>>>>",
44464
+ "{{task}}",
44465
+ "<<<<<<<<<<",
44466
+ "",
44467
+ "Again, whatever you are thinking of now I repeat: You don't go do the work yourself!",
44468
+ "What your job IS tho:",
44469
+ "",
44470
+ "Now you have full knowledge of what the user wants and how he composed his 'team'",
44471
+ "you do not delegate this team as subagents but you spawn new handoff sessions, you decide on what can be done in paralel and what needs to happen before the next can start. stage. the amount of handof sessions you spawn is equal to the amount of team entries in your prompt.",
44472
+ "",
44473
+ "",
44474
+ "",
44475
+ "We are going to use a hivemind dock in the repo.",
44476
+ "What you can do, is now startup that hivemind doc in /.hivemind/orchestrator-{{questId}}.md",
44477
+ "",
44478
+ "",
44479
+ "You instruct every handoff with a clear instruction on it's task.",
44480
+ "But you also make sure that each handoff knows the greater context of the end goal and knows of the existence of the hivemind document.",
44481
+ "it should know that it can write to it but first needs to check if there are no conflicts.",
44482
+ "",
44483
+ "",
44484
+ "do not use subagents because these are emphetic. we need full sessions."
44485
+ ].join("\n");
44486
+
43047
44487
  // ../server/src/server/quest/runner-ralph.ts
43048
44488
  import { promisify as promisify4 } from "node:util";
43049
44489
  import { execFile as execFile3 } from "node:child_process";
@@ -43051,13 +44491,13 @@ var execFileAsync3 = promisify4(execFile3);
43051
44491
  var MAX_VERIFY_OUTPUT_BYTES2 = 64 * 1024;
43052
44492
 
43053
44493
  // ../server/src/shared/connection-offer.ts
43054
- import { z as z41 } from "zod";
43055
- var ConnectionOfferV2Schema = z41.object({
43056
- v: z41.literal(2),
43057
- serverId: z41.string().min(1),
43058
- daemonPublicKeyB64: z41.string().min(1),
43059
- relay: z41.object({
43060
- endpoint: z41.string().min(1)
44494
+ import { z as z43 } from "zod";
44495
+ var ConnectionOfferV2Schema = z43.object({
44496
+ v: z43.literal(2),
44497
+ serverId: z43.string().min(1),
44498
+ daemonPublicKeyB64: z43.string().min(1),
44499
+ relay: z43.object({
44500
+ endpoint: z43.string().min(1)
43061
44501
  })
43062
44502
  });
43063
44503
 
@@ -43090,22 +44530,22 @@ function isRelayClientWebSocketUrl(url) {
43090
44530
  }
43091
44531
 
43092
44532
  // ../server/src/server/config.ts
43093
- import path17 from "node:path";
43094
- import { z as z45 } from "zod";
44533
+ import path19 from "node:path";
44534
+ import { z as z47 } from "zod";
43095
44535
 
43096
44536
  // ../server/src/server/speech/speech-config-resolver.ts
43097
- import { z as z44 } from "zod";
44537
+ import { z as z46 } from "zod";
43098
44538
 
43099
44539
  // ../server/src/server/speech/providers/local/config.ts
43100
- import path16 from "node:path";
43101
- import { z as z42 } from "zod";
43102
- var DEFAULT_LOCAL_MODELS_SUBDIR = path16.join("models", "local-speech");
43103
- var NumberLikeSchema2 = z42.union([z42.number(), z42.string().trim().min(1)]);
43104
- var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z42.coerce.number().finite()).optional();
43105
- var OptionalIntegerSchema = NumberLikeSchema2.pipe(z42.coerce.number().int()).optional();
43106
- var LocalSpeechResolutionSchema = z42.object({
43107
- includeProviderConfig: z42.boolean(),
43108
- modelsDir: z42.string().trim().min(1),
44540
+ import path18 from "node:path";
44541
+ import { z as z44 } from "zod";
44542
+ var DEFAULT_LOCAL_MODELS_SUBDIR = path18.join("models", "local-speech");
44543
+ var NumberLikeSchema2 = z44.union([z44.number(), z44.string().trim().min(1)]);
44544
+ var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z44.coerce.number().finite()).optional();
44545
+ var OptionalIntegerSchema = NumberLikeSchema2.pipe(z44.coerce.number().int()).optional();
44546
+ var LocalSpeechResolutionSchema = z44.object({
44547
+ includeProviderConfig: z44.boolean(),
44548
+ modelsDir: z44.string().trim().min(1),
43109
44549
  dictationLocalSttModel: LocalSttModelIdSchema.default(DEFAULT_LOCAL_STT_MODEL),
43110
44550
  voiceLocalSttModel: LocalSttModelIdSchema.default(DEFAULT_LOCAL_STT_MODEL),
43111
44551
  voiceLocalTtsModel: LocalTtsModelIdSchema.default(DEFAULT_LOCAL_TTS_MODEL),
@@ -43126,7 +44566,7 @@ function resolveLocalSpeechConfig(params) {
43126
44566
  const includeProviderConfig = shouldIncludeLocalProviderConfig(params);
43127
44567
  const parsed = LocalSpeechResolutionSchema.parse({
43128
44568
  includeProviderConfig,
43129
- 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),
43130
44570
  dictationLocalSttModel: params.env.APPOSTLE_DICTATION_LOCAL_STT_MODEL ?? persistedLocalFeatureModel(
43131
44571
  params.providers.dictationStt.provider,
43132
44572
  params.providers.dictationStt.enabled,
@@ -43161,17 +44601,17 @@ function resolveLocalSpeechConfig(params) {
43161
44601
  }
43162
44602
 
43163
44603
  // ../server/src/server/speech/speech-types.ts
43164
- import { z as z43 } from "zod";
43165
- var SpeechProviderIdSchema2 = z43.enum(["openai", "local"]);
43166
- var RequestedSpeechProviderSchema = z43.object({
44604
+ import { z as z45 } from "zod";
44605
+ var SpeechProviderIdSchema2 = z45.enum(["openai", "local"]);
44606
+ var RequestedSpeechProviderSchema = z45.object({
43167
44607
  provider: SpeechProviderIdSchema2,
43168
- explicit: z43.boolean(),
43169
- enabled: z43.boolean().optional()
44608
+ explicit: z45.boolean(),
44609
+ enabled: z45.boolean().optional()
43170
44610
  });
43171
44611
 
43172
44612
  // ../server/src/server/speech/speech-config-resolver.ts
43173
- var OptionalSpeechProviderSchema = z44.string().trim().toLowerCase().pipe(SpeechProviderIdSchema2).optional();
43174
- var OptionalBooleanFlagSchema = z44.union([z44.boolean(), z44.string().trim().toLowerCase()]).optional().transform((value) => {
44613
+ var OptionalSpeechProviderSchema = z46.string().trim().toLowerCase().pipe(SpeechProviderIdSchema2).optional();
44614
+ var OptionalBooleanFlagSchema = z46.union([z46.boolean(), z46.string().trim().toLowerCase()]).optional().transform((value) => {
43175
44615
  if (typeof value === "boolean") {
43176
44616
  return value;
43177
44617
  }
@@ -43186,7 +44626,7 @@ var OptionalBooleanFlagSchema = z44.union([z44.boolean(), z44.string().trim().to
43186
44626
  }
43187
44627
  return void 0;
43188
44628
  });
43189
- var RequestedSpeechProvidersSchema = z44.object({
44629
+ var RequestedSpeechProvidersSchema = z46.object({
43190
44630
  dictationStt: OptionalSpeechProviderSchema.default("local"),
43191
44631
  voiceTurnDetection: OptionalSpeechProviderSchema.default("local"),
43192
44632
  voiceStt: OptionalSpeechProviderSchema.default("local"),
@@ -43295,9 +44735,9 @@ function parseBooleanEnv(value) {
43295
44735
  }
43296
44736
  return void 0;
43297
44737
  }
43298
- var OptionalVoiceLlmProviderSchema = z45.union([z45.string(), z45.null(), z45.undefined()]).transform(
44738
+ var OptionalVoiceLlmProviderSchema = z47.union([z47.string(), z47.null(), z47.undefined()]).transform(
43299
44739
  (value) => typeof value === "string" ? value.trim().toLowerCase() : null
43300
- ).pipe(z45.union([AgentProviderSchema, z45.null()]));
44740
+ ).pipe(z47.union([AgentProviderSchema, z47.null()]));
43301
44741
  function parseOptionalVoiceLlmProvider(value) {
43302
44742
  const parsed = OptionalVoiceLlmProviderSchema.safeParse(value);
43303
44743
  return parsed.success ? parsed.data : null;
@@ -43382,7 +44822,7 @@ function loadConfig(appostleHome, options) {
43382
44822
  chromeEnabled,
43383
44823
  mcpDebug: env.MCP_DEBUG === "1",
43384
44824
  daemonIcon,
43385
- agentStoragePath: path17.join(appostleHome, "agents"),
44825
+ agentStoragePath: path19.join(appostleHome, "agents"),
43386
44826
  staticDir: "public",
43387
44827
  agentClients: {},
43388
44828
  relayEnabled,
@@ -44600,12 +46040,12 @@ var DaemonClient = class {
44600
46040
  timeout: 1e4
44601
46041
  });
44602
46042
  }
44603
- async openInEditor(path24, editorId, requestId) {
46043
+ async openInEditor(path26, editorId, requestId) {
44604
46044
  return this.sendCorrelatedSessionRequest({
44605
46045
  requestId,
44606
46046
  message: {
44607
46047
  type: "open_in_editor_request",
44608
- path: path24,
46048
+ path: path26,
44609
46049
  editorId
44610
46050
  },
44611
46051
  responseType: "open_in_editor_response",
@@ -44705,6 +46145,7 @@ var DaemonClient = class {
44705
46145
  ...options.clientMessageId ? { clientMessageId: options.clientMessageId } : {},
44706
46146
  ...options.outputSchema ? { outputSchema: options.outputSchema } : {},
44707
46147
  ...options.images && options.images.length > 0 ? { images: options.images } : {},
46148
+ ...options.files && options.files.length > 0 ? { files: options.files } : {},
44708
46149
  ...options.attachments && options.attachments.length > 0 ? { attachments: options.attachments } : {},
44709
46150
  ...options.git ? { git: options.git } : {},
44710
46151
  ...options.worktreeName ? { worktreeName: options.worktreeName } : {},
@@ -44930,6 +46371,7 @@ var DaemonClient = class {
44930
46371
  text,
44931
46372
  ...messageId ? { messageId } : {},
44932
46373
  ...options?.images ? { images: options.images } : {},
46374
+ ...options?.files ? { files: options.files } : {},
44933
46375
  ...options?.attachments ? { attachments: options.attachments } : {}
44934
46376
  });
44935
46377
  const payload = await this.sendRequest({
@@ -44954,6 +46396,157 @@ var DaemonClient = class {
44954
46396
  async sendMessage(agentId, text, options) {
44955
46397
  await this.sendAgentMessage(agentId, text, options);
44956
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
+ }
44957
46550
  async cancelAgent(agentId) {
44958
46551
  const requestId = this.createRequestId();
44959
46552
  const message = SessionInboundMessageSchema.parse({
@@ -45867,13 +47460,13 @@ var DaemonClient = class {
45867
47460
  // ============================================================================
45868
47461
  // File Explorer
45869
47462
  // ============================================================================
45870
- async exploreFileSystem(cwd, path24, mode = "list", requestId) {
47463
+ async exploreFileSystem(cwd, path26, mode = "list", requestId) {
45871
47464
  return this.sendCorrelatedSessionRequest({
45872
47465
  requestId,
45873
47466
  message: {
45874
47467
  type: "file_explorer_request",
45875
47468
  cwd,
45876
- path: path24,
47469
+ path: path26,
45877
47470
  mode
45878
47471
  },
45879
47472
  responseType: "file_explorer_response",
@@ -45885,13 +47478,13 @@ var DaemonClient = class {
45885
47478
  * allowlists extensions (currently `.md` only) — callers don't need to
45886
47479
  * re-check. Used by the plan-todos UI to rewrite plan-file frontmatter.
45887
47480
  */
45888
- async writeFile(cwd, path24, content, requestId) {
47481
+ async writeFile(cwd, path26, content, requestId) {
45889
47482
  return this.sendCorrelatedSessionRequest({
45890
47483
  requestId,
45891
47484
  message: {
45892
47485
  type: "file_write_request",
45893
47486
  cwd,
45894
- path: path24,
47487
+ path: path26,
45895
47488
  content
45896
47489
  },
45897
47490
  responseType: "file_write_response",
@@ -45904,42 +47497,42 @@ var DaemonClient = class {
45904
47497
  * action is the only consumer). Returns the daemon's structured response
45905
47498
  * so callers can surface the error in the UI.
45906
47499
  */
45907
- async deleteFile(cwd, path24, requestId) {
47500
+ async deleteFile(cwd, path26, requestId) {
45908
47501
  return this.sendCorrelatedSessionRequest({
45909
47502
  requestId,
45910
47503
  message: {
45911
47504
  type: "file_delete_request",
45912
47505
  cwd,
45913
- path: path24
47506
+ path: path26
45914
47507
  },
45915
47508
  responseType: "file_delete_response",
45916
47509
  timeout: 1e4
45917
47510
  });
45918
47511
  }
45919
- async requestDownloadToken(cwd, path24, requestId) {
47512
+ async requestDownloadToken(cwd, path26, requestId) {
45920
47513
  return this.sendCorrelatedSessionRequest({
45921
47514
  requestId,
45922
47515
  message: {
45923
47516
  type: "file_download_token_request",
45924
47517
  cwd,
45925
- path: path24
47518
+ path: path26
45926
47519
  },
45927
47520
  responseType: "file_download_token_response",
45928
47521
  timeout: 1e4
45929
47522
  });
45930
47523
  }
45931
- async explorerDeleteEntry(cwd, path24, requestId) {
47524
+ async explorerDeleteEntry(cwd, path26, requestId) {
45932
47525
  return this.sendCorrelatedSessionRequest({
45933
47526
  requestId,
45934
- message: { type: "file_explorer_delete_request", cwd, path: path24 },
47527
+ message: { type: "file_explorer_delete_request", cwd, path: path26 },
45935
47528
  responseType: "file_explorer_delete_response",
45936
47529
  timeout: 1e4
45937
47530
  });
45938
47531
  }
45939
- async explorerMkdir(cwd, path24, requestId) {
47532
+ async explorerMkdir(cwd, path26, requestId) {
45940
47533
  return this.sendCorrelatedSessionRequest({
45941
47534
  requestId,
45942
- message: { type: "file_mkdir_request", cwd, path: path24 },
47535
+ message: { type: "file_mkdir_request", cwd, path: path26 },
45943
47536
  responseType: "file_mkdir_response",
45944
47537
  timeout: 1e4
45945
47538
  });
@@ -47329,16 +48922,16 @@ function resolveAgentConfig(options) {
47329
48922
  }
47330
48923
 
47331
48924
  // ../cli/src/utils/client.ts
47332
- import path18 from "node:path";
48925
+ import path20 from "node:path";
47333
48926
  import WebSocket3 from "ws";
47334
48927
 
47335
48928
  // ../cli/src/utils/client-id.ts
47336
48929
  import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
47337
- import { randomUUID as randomUUID5 } from "node:crypto";
47338
- 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";
47339
48932
  import { homedir as homedir6 } from "node:os";
47340
- var CLIENT_SESSION_KEY_FILE = join10(
47341
- process.env.APPOSTLE_HOME ?? join10(homedir6(), ".appostle"),
48933
+ var CLIENT_SESSION_KEY_FILE = join12(
48934
+ process.env.APPOSTLE_HOME ?? join12(homedir6(), ".appostle"),
47342
48935
  "cli-client-id"
47343
48936
  );
47344
48937
  var cachedClientId = null;
@@ -47347,7 +48940,7 @@ function normalizeClientId2(value) {
47347
48940
  return trimmed.length > 0 ? trimmed : null;
47348
48941
  }
47349
48942
  function generateClientId() {
47350
- return `cid_${randomUUID5().replace(/-/g, "")}`;
48943
+ return `cid_${randomUUID6().replace(/-/g, "")}`;
47351
48944
  }
47352
48945
  async function getOrCreateCliClientId() {
47353
48946
  if (cachedClientId) {
@@ -47405,7 +48998,7 @@ function isTcpDaemonHost(host) {
47405
48998
  return host !== null && !isIpcDaemonHost(host);
47406
48999
  }
47407
49000
  function readPidSocketTarget(appostleHome) {
47408
- const pidPath = path18.join(appostleHome, PID_FILENAME);
49001
+ const pidPath = path20.join(appostleHome, PID_FILENAME);
47409
49002
  if (!existsSync10(pidPath)) {
47410
49003
  return null;
47411
49004
  }
@@ -47874,12 +49467,12 @@ function relativeTime(date) {
47874
49467
  if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
47875
49468
  return `${Math.floor(seconds / 86400)} days ago`;
47876
49469
  }
47877
- function shortenPath(path24) {
49470
+ function shortenPath(path26) {
47878
49471
  const home = process.env.HOME;
47879
- if (home && path24.startsWith(home)) {
47880
- return "~" + path24.slice(home.length);
49472
+ if (home && path26.startsWith(home)) {
49473
+ return "~" + path26.slice(home.length);
47881
49474
  }
47882
- return path24;
49475
+ return path26;
47883
49476
  }
47884
49477
  function normalizeModelId(modelId) {
47885
49478
  if (typeof modelId !== "string") return null;
@@ -48722,7 +50315,7 @@ async function runStopCommand(id, options, _command) {
48722
50315
 
48723
50316
  // ../cli/src/commands/agent/send.ts
48724
50317
  import { readFile as readFile5 } from "node:fs/promises";
48725
- import { extname as extname3, resolve as resolve11 } from "node:path";
50318
+ import { extname as extname4, resolve as resolve11 } from "node:path";
48726
50319
  var agentSendSchema = {
48727
50320
  idField: "agentId",
48728
50321
  columns: [
@@ -48736,10 +50329,10 @@ function addSendOptions(cmd) {
48736
50329
  }
48737
50330
  async function readImageFiles(imagePaths) {
48738
50331
  const images = [];
48739
- for (const path24 of imagePaths) {
50332
+ for (const path26 of imagePaths) {
48740
50333
  try {
48741
- const buffer = await readFile5(path24);
48742
- const ext = extname3(path24).toLowerCase();
50334
+ const buffer = await readFile5(path26);
50335
+ const ext = extname4(path26).toLowerCase();
48743
50336
  let mimeType = "image/jpeg";
48744
50337
  switch (ext) {
48745
50338
  case ".png":
@@ -48767,7 +50360,7 @@ async function readImageFiles(imagePaths) {
48767
50360
  const message = err instanceof Error ? err.message : String(err);
48768
50361
  const error = {
48769
50362
  code: "IMAGE_READ_ERROR",
48770
- message: `Failed to read image file: ${path24}`,
50363
+ message: `Failed to read image file: ${path26}`,
48771
50364
  details: message
48772
50365
  };
48773
50366
  throw error;
@@ -48940,12 +50533,12 @@ function createInspectSchema(agent) {
48940
50533
  serialize: (_item) => agent
48941
50534
  };
48942
50535
  }
48943
- function shortenPath2(path24) {
50536
+ function shortenPath2(path26) {
48944
50537
  const home = process.env.HOME;
48945
- if (home && path24.startsWith(home)) {
48946
- return "~" + path24.slice(home.length);
50538
+ if (home && path26.startsWith(home)) {
50539
+ return "~" + path26.slice(home.length);
48947
50540
  }
48948
- return path24;
50541
+ return path26;
48949
50542
  }
48950
50543
  function formatCost(costUsd) {
48951
50544
  if (costUsd === 0) return "$0.00";
@@ -49913,7 +51506,7 @@ import chalk3 from "chalk";
49913
51506
  import { spawn as spawn7, spawnSync } from "node:child_process";
49914
51507
  import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
49915
51508
  import { createRequire as createRequire3 } from "node:module";
49916
- import path19 from "node:path";
51509
+ import path21 from "node:path";
49917
51510
  import { fileURLToPath } from "node:url";
49918
51511
  var DETACHED_STARTUP_GRACE_MS = 1200;
49919
51512
  var PID_POLL_INTERVAL_MS = 100;
@@ -49964,7 +51557,7 @@ function buildChildEnv(options) {
49964
51557
  function resolveDaemonRunnerEntry() {
49965
51558
  try {
49966
51559
  const here = fileURLToPath(import.meta.url);
49967
- const sibling = path19.join(path19.dirname(here), "supervisor-entrypoint.js");
51560
+ const sibling = path21.join(path21.dirname(here), "supervisor-entrypoint.js");
49968
51561
  if (existsSync11(sibling)) {
49969
51562
  return sibling;
49970
51563
  }
@@ -49978,23 +51571,23 @@ function resolveDaemonRunnerEntry() {
49978
51571
  "Unable to resolve @appostle/server package root for daemon runner (and no sibling supervisor-entrypoint.js was bundled)"
49979
51572
  );
49980
51573
  }
49981
- let currentDir = path19.dirname(serverExportPath);
51574
+ let currentDir = path21.dirname(serverExportPath);
49982
51575
  while (true) {
49983
- const packageJsonPath = path19.join(currentDir, "package.json");
51576
+ const packageJsonPath = path21.join(currentDir, "package.json");
49984
51577
  if (existsSync11(packageJsonPath)) {
49985
51578
  try {
49986
51579
  const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
49987
51580
  if (packageJson.name === "@appostle/server") {
49988
- const distRunner = path19.join(currentDir, "dist", "scripts", "supervisor-entrypoint.js");
51581
+ const distRunner = path21.join(currentDir, "dist", "scripts", "supervisor-entrypoint.js");
49989
51582
  if (existsSync11(distRunner)) {
49990
51583
  return distRunner;
49991
51584
  }
49992
- return path19.join(currentDir, "scripts", "supervisor-entrypoint.ts");
51585
+ return path21.join(currentDir, "scripts", "supervisor-entrypoint.ts");
49993
51586
  }
49994
51587
  } catch {
49995
51588
  }
49996
51589
  }
49997
- const parentDir = path19.dirname(currentDir);
51590
+ const parentDir = path21.dirname(currentDir);
49998
51591
  if (parentDir === currentDir) {
49999
51592
  break;
50000
51593
  }
@@ -50003,7 +51596,7 @@ function resolveDaemonRunnerEntry() {
50003
51596
  throw new Error("Unable to resolve @appostle/server package root for daemon runner");
50004
51597
  }
50005
51598
  function pidFilePath(appostleHome) {
50006
- return path19.join(appostleHome, DAEMON_PID_FILENAME);
51599
+ return path21.join(appostleHome, DAEMON_PID_FILENAME);
50007
51600
  }
50008
51601
  function readPidFile(pidPath) {
50009
51602
  try {
@@ -50145,7 +51738,7 @@ function resolveLocalDaemonState(options = {}) {
50145
51738
  const home = resolveAppostleHome(env);
50146
51739
  const config = loadConfig(home, { env });
50147
51740
  const pidPath = pidFilePath(home);
50148
- const logPath = path19.join(home, DAEMON_LOG_FILENAME);
51741
+ const logPath = path21.join(home, DAEMON_LOG_FILENAME);
50149
51742
  const pidInfo = existsSync11(pidPath) ? readPidFile(pidPath) : null;
50150
51743
  const running = pidInfo ? isProcessRunning(pidInfo.pid) : false;
50151
51744
  const listen = pidInfo?.listen ?? config.listen;
@@ -50160,7 +51753,7 @@ function resolveLocalDaemonState(options = {}) {
50160
51753
  };
50161
51754
  }
50162
51755
  function tailDaemonLog(home, lines = 30) {
50163
- const logPath = path19.join(resolveLocalAppostleHome(home), DAEMON_LOG_FILENAME);
51756
+ const logPath = path21.join(resolveLocalAppostleHome(home), DAEMON_LOG_FILENAME);
50164
51757
  return tailFile(logPath, lines);
50165
51758
  }
50166
51759
  async function startLocalDaemonDetached(options) {
@@ -50169,7 +51762,7 @@ async function startLocalDaemonDetached(options) {
50169
51762
  }
50170
51763
  const childEnv = buildChildEnv(options);
50171
51764
  const appostleHome = resolveAppostleHome(childEnv);
50172
- const logPath = path19.join(appostleHome, DAEMON_LOG_FILENAME);
51765
+ const logPath = path21.join(appostleHome, DAEMON_LOG_FILENAME);
50173
51766
  const daemonRunnerEntry = resolveDaemonRunnerEntry();
50174
51767
  const child = spawn7(
50175
51768
  process.execPath,
@@ -52894,22 +54487,22 @@ import { Command as Command13 } from "commander";
52894
54487
 
52895
54488
  // ../cli/src/commands/worktree/ls.ts
52896
54489
  import { homedir as homedir7 } from "node:os";
52897
- import { basename as basename7, join as join11, sep as sep3 } from "node:path";
52898
- function shortenPath3(path24) {
54490
+ import { basename as basename7, join as join13, sep as sep3 } from "node:path";
54491
+ function shortenPath3(path26) {
52899
54492
  const home = process.env.HOME;
52900
- if (home && path24.startsWith(home)) {
52901
- return "~" + path24.slice(home.length);
54493
+ if (home && path26.startsWith(home)) {
54494
+ return "~" + path26.slice(home.length);
52902
54495
  }
52903
- return path24;
54496
+ return path26;
52904
54497
  }
52905
- function extractWorktreeName(path24) {
52906
- return basename7(path24);
54498
+ function extractWorktreeName(path26) {
54499
+ return basename7(path26);
52907
54500
  }
52908
54501
  function resolveAppostleHomePath() {
52909
- return process.env.APPOSTLE_HOME ?? join11(homedir7(), ".appostle");
54502
+ return process.env.APPOSTLE_HOME ?? join13(homedir7(), ".appostle");
52910
54503
  }
52911
54504
  function resolveAppostleWorktreesDir() {
52912
- return join11(resolveAppostleHomePath(), "worktrees");
54505
+ return join13(resolveAppostleHomePath(), "worktrees");
52913
54506
  }
52914
54507
  function isAgentInManagedWorktree(agentCwd) {
52915
54508
  const worktreesDir = resolveAppostleWorktreesDir();
@@ -52983,7 +54576,7 @@ async function runLsCommand7(options, _command) {
52983
54576
  }
52984
54577
 
52985
54578
  // ../cli/src/commands/worktree/archive.ts
52986
- import path20 from "path";
54579
+ import path22 from "path";
52987
54580
  var archiveSchema2 = {
52988
54581
  idField: "name",
52989
54582
  columns: [
@@ -53027,7 +54620,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
53027
54620
  throw error;
53028
54621
  }
53029
54622
  const worktree = listResponse.worktrees.find((wt) => {
53030
- const name = path20.basename(wt.worktreePath);
54623
+ const name = path22.basename(wt.worktreePath);
53031
54624
  return name === nameArg || wt.branchName === nameArg;
53032
54625
  });
53033
54626
  if (!worktree) {
@@ -53049,7 +54642,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
53049
54642
  };
53050
54643
  throw error;
53051
54644
  }
53052
- const worktreeName = path20.basename(worktree.worktreePath) || nameArg;
54645
+ const worktreeName = path22.basename(worktree.worktreePath) || nameArg;
53053
54646
  return {
53054
54647
  type: "single",
53055
54648
  data: {
@@ -53090,7 +54683,7 @@ function createWorktreeCommand() {
53090
54683
  import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
53091
54684
  import { Command as Command14, Option as Option4 } from "commander";
53092
54685
  import { writeFileSync as writeFileSync5 } from "node:fs";
53093
- import path21 from "node:path";
54686
+ import path23 from "node:path";
53094
54687
  var DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1e3;
53095
54688
  var OnboardCancelledError = class extends Error {
53096
54689
  };
@@ -53133,7 +54726,7 @@ function toCliOverrides(options) {
53133
54726
  return cliOverrides;
53134
54727
  }
53135
54728
  function savePersistedConfig2(appostleHome, config) {
53136
- const configPath = path21.join(appostleHome, "config.json");
54729
+ const configPath = path23.join(appostleHome, "config.json");
53137
54730
  writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
53138
54731
  `);
53139
54732
  }
@@ -53254,7 +54847,7 @@ ${recentLogs}` : null
53254
54847
  );
53255
54848
  }
53256
54849
  function printNextSteps(pairingUrl, appostleHome, richUi) {
53257
- const daemonLogPath = path21.join(appostleHome, "daemon.log");
54850
+ const daemonLogPath = path23.join(appostleHome, "daemon.log");
53258
54851
  const nextStepsLines = [
53259
54852
  pairingUrl ? "1. Open Appostle and scan the QR code above, or paste the pairing link." : "1. Open Appostle and connect to your daemon.",
53260
54853
  "2. Web app: https://appostle.app",
@@ -53508,18 +55101,18 @@ function createCli() {
53508
55101
  // ../cli/src/classify.ts
53509
55102
  import { existsSync as existsSync12, statSync as statSync3 } from "node:fs";
53510
55103
  import { homedir as homedir8 } from "node:os";
53511
- import path22 from "node:path";
55104
+ import path24 from "node:path";
53512
55105
  function expandUserPath2(inputPath) {
53513
55106
  if (inputPath === "~") {
53514
55107
  return homedir8();
53515
55108
  }
53516
55109
  if (inputPath.startsWith("~/")) {
53517
- return path22.join(homedir8(), inputPath.slice(2));
55110
+ return path24.join(homedir8(), inputPath.slice(2));
53518
55111
  }
53519
55112
  return inputPath;
53520
55113
  }
53521
55114
  function isExistingDirectory(input) {
53522
- const resolvedPath = path22.resolve(input.cwd, expandUserPath2(input.pathArg));
55115
+ const resolvedPath = path24.resolve(input.cwd, expandUserPath2(input.pathArg));
53523
55116
  if (!existsSync12(resolvedPath)) {
53524
55117
  return false;
53525
55118
  }
@@ -53539,7 +55132,7 @@ function classifyInvocation(input) {
53539
55132
  if (isExistingDirectory({ pathArg: firstArg, cwd: input.cwd })) {
53540
55133
  return {
53541
55134
  kind: "open-project",
53542
- resolvedPath: path22.resolve(input.cwd, expandUserPath2(firstArg))
55135
+ resolvedPath: path24.resolve(input.cwd, expandUserPath2(firstArg))
53543
55136
  };
53544
55137
  }
53545
55138
  return { kind: "cli", argv: input.argv };
@@ -53549,12 +55142,12 @@ function classifyInvocation(input) {
53549
55142
  import { existsSync as existsSync13 } from "node:fs";
53550
55143
  import { spawn as spawn8 } from "node:child_process";
53551
55144
  import { homedir as homedir9 } from "node:os";
53552
- import path23 from "node:path";
55145
+ import path25 from "node:path";
53553
55146
  function findDesktopApp() {
53554
55147
  if (process.platform === "darwin") {
53555
55148
  const candidates = [
53556
55149
  "/Applications/Appostle.app",
53557
- path23.join(homedir9(), "Applications", "Appostle.app")
55150
+ path25.join(homedir9(), "Applications", "Appostle.app")
53558
55151
  ];
53559
55152
  for (const candidate of candidates) {
53560
55153
  if (existsSync13(candidate)) {
@@ -53567,7 +55160,7 @@ function findDesktopApp() {
53567
55160
  const candidates = [
53568
55161
  "/usr/bin/Appostle",
53569
55162
  "/opt/Appostle/Appostle",
53570
- path23.join(homedir9(), "Applications", "Appostle.AppImage")
55163
+ path25.join(homedir9(), "Applications", "Appostle.AppImage")
53571
55164
  ];
53572
55165
  for (const candidate of candidates) {
53573
55166
  if (existsSync13(candidate)) {
@@ -53581,7 +55174,7 @@ function findDesktopApp() {
53581
55174
  if (!localAppData) {
53582
55175
  return null;
53583
55176
  }
53584
- const candidate = path23.join(localAppData, "Programs", "Appostle", "Appostle.exe");
55177
+ const candidate = path25.join(localAppData, "Programs", "Appostle", "Appostle.exe");
53585
55178
  return existsSync13(candidate) ? candidate : null;
53586
55179
  }
53587
55180
  return null;