appostle-installer 0.0.13 → 0.0.15

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
@@ -16,10 +16,10 @@ __export(rightfont_service_exports, {
16
16
  isRightFontLibrary: () => isRightFontLibrary,
17
17
  readRightFontLibrary: () => readRightFontLibrary
18
18
  });
19
- import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync6 } from "node:fs";
19
+ import { existsSync as existsSync11, readdirSync, readFileSync as readFileSync6 } from "node:fs";
20
20
  import { join as join12 } from "node:path";
21
21
  function isRightFontLibrary(libraryPath) {
22
- return libraryPath.endsWith(".rightfontlibrary") && existsSync10(join12(libraryPath, "fonts")) && existsSync10(join12(libraryPath, "metadata"));
22
+ return libraryPath.endsWith(".rightfontlibrary") && existsSync11(join12(libraryPath, "fonts")) && existsSync11(join12(libraryPath, "metadata"));
23
23
  }
24
24
  function readRightFontLibrary(libraryPath) {
25
25
  if (!isRightFontLibrary(libraryPath)) {
@@ -30,7 +30,7 @@ function readRightFontLibrary(libraryPath) {
30
30
  const metadataListsDir = join12(libraryPath, "metadata", "fontlists");
31
31
  const familyMap = /* @__PURE__ */ new Map();
32
32
  let totalFonts = 0;
33
- if (existsSync10(metadataFontsDir)) {
33
+ if (existsSync11(metadataFontsDir)) {
34
34
  const files = readdirSync(metadataFontsDir).filter((f) => f.endsWith(".rightfontmetadata"));
35
35
  for (const file of files) {
36
36
  try {
@@ -70,7 +70,7 @@ function readRightFontLibrary(libraryPath) {
70
70
  fam.fonts.sort((a, b) => a.weight - b.weight || a.style.localeCompare(b.style));
71
71
  }
72
72
  const collections = [];
73
- if (existsSync10(metadataListsDir)) {
73
+ if (existsSync11(metadataListsDir)) {
74
74
  const files = readdirSync(metadataListsDir).filter((f) => f.endsWith(".rightfontmetadata"));
75
75
  for (const file of files) {
76
76
  try {
@@ -255,7 +255,7 @@ import { createRequire as createRequire5 } from "node:module";
255
255
  import { Command as Command2 } from "commander";
256
256
 
257
257
  // ../cli/src/utils/client.ts
258
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
258
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "node:fs";
259
259
 
260
260
  // ../server/src/server/bootstrap.ts
261
261
  import express from "express";
@@ -700,18 +700,18 @@ function resolveNodePtyPackageRoot() {
700
700
  return null;
701
701
  }
702
702
  }
703
- function ensureExecutableBit(path28) {
704
- if (!existsSync2(path28)) {
703
+ function ensureExecutableBit(path29) {
704
+ if (!existsSync2(path29)) {
705
705
  return;
706
706
  }
707
- const stat5 = statSync(path28);
707
+ const stat5 = statSync(path29);
708
708
  if (!stat5.isFile()) {
709
709
  return;
710
710
  }
711
711
  if ((stat5.mode & 73) === 73) {
712
712
  return;
713
713
  }
714
- chmodSync(path28, stat5.mode | 73);
714
+ chmodSync(path29, stat5.mode | 73);
715
715
  }
716
716
  function ensureNodePtySpawnHelperExecutableForCurrentPlatform(options = {}) {
717
717
  const platform2 = options.platform ?? process.platform;
@@ -793,11 +793,11 @@ function parseGitRevParsePath(stdout) {
793
793
  if (lines.length !== 1) {
794
794
  return null;
795
795
  }
796
- const path28 = lines[0]?.trim() ?? "";
797
- if (!path28 || path28.startsWith("--")) {
796
+ const path29 = lines[0]?.trim() ?? "";
797
+ if (!path29 || path29.startsWith("--")) {
798
798
  return null;
799
799
  }
800
- return path28;
800
+ return path29;
801
801
  }
802
802
  function resolveGitRevParsePath(cwd, stdout) {
803
803
  const parsed = parseGitRevParsePath(stdout);
@@ -1570,9 +1570,9 @@ async function deleteAppostleWorktree({
1570
1570
  }
1571
1571
  }
1572
1572
  }
1573
- async function pathExists(path28) {
1573
+ async function pathExists(path29) {
1574
1574
  try {
1575
- await stat(path28);
1575
+ await stat(path29);
1576
1576
  return true;
1577
1577
  } catch (error) {
1578
1578
  if (error.code === "ENOENT") {
@@ -1581,8 +1581,8 @@ async function pathExists(path28) {
1581
1581
  throw error;
1582
1582
  }
1583
1583
  }
1584
- async function removeDirectoryWithRetries(path28) {
1585
- if (!await pathExists(path28)) {
1584
+ async function removeDirectoryWithRetries(path29) {
1585
+ if (!await pathExists(path29)) {
1586
1586
  return;
1587
1587
  }
1588
1588
  const delaysMs = [0, 100, 300, 700, 1500];
@@ -1592,17 +1592,17 @@ async function removeDirectoryWithRetries(path28) {
1592
1592
  await new Promise((resolve12) => setTimeout(resolve12, delay));
1593
1593
  }
1594
1594
  try {
1595
- await rm(path28, { recursive: true, force: true });
1596
- if (!await pathExists(path28)) {
1595
+ await rm(path29, { recursive: true, force: true });
1596
+ if (!await pathExists(path29)) {
1597
1597
  return;
1598
1598
  }
1599
- lastError = new Error(`Directory still present after rm: ${path28}`);
1599
+ lastError = new Error(`Directory still present after rm: ${path29}`);
1600
1600
  } catch (error) {
1601
1601
  lastError = error;
1602
1602
  }
1603
1603
  }
1604
- if (await pathExists(path28)) {
1605
- throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path28}`);
1604
+ if (await pathExists(path29)) {
1605
+ throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path29}`);
1606
1606
  }
1607
1607
  }
1608
1608
  var createWorktree = async ({
@@ -1832,7 +1832,13 @@ function extractTimestamps(record) {
1832
1832
  // Fork lineage — preserved across resume so the in-memory ManagedAgent
1833
1833
  // can flow `parentAgentId` into snapshots that drive the tab Split icon.
1834
1834
  ...record.parentAgentId ? { parentAgentId: record.parentAgentId } : {},
1835
- ...record.forkedFromMessageUuid ? { forkedFromMessageUuid: record.forkedFromMessageUuid } : {}
1835
+ ...record.forkedFromMessageUuid ? { forkedFromMessageUuid: record.forkedFromMessageUuid } : {},
1836
+ // Multi-tenant ownership — closes the daemon-restart gap. Old records
1837
+ // lack these fields → ownerUserId stays undefined (→ null in
1838
+ // registerSession), agent rehydrates as unscoped.
1839
+ ownerUserId: record.ownerUserId ?? null,
1840
+ sharedWithUserIds: record.sharedWithUserIds ?? [],
1841
+ ownerUsername: record.ownerUsername ?? null
1836
1842
  };
1837
1843
  }
1838
1844
  function hasRegisteredProvider(registeredProviders, value) {
@@ -1916,6 +1922,11 @@ function toAgentPayload(agent, options) {
1916
1922
  title: options?.title ?? null,
1917
1923
  labels: agent.labels,
1918
1924
  internal: agent.internal,
1925
+ // Surface ownership so the client can render an owner badge / detect
1926
+ // "shared with me" agents. `sharedWithUserIds` deliberately stays off
1927
+ // the snapshot — only owners read the full ACL, via the dedicated
1928
+ // `list_agent_shared_users_request` RPC.
1929
+ ownerUserId: agent.ownerUserId,
1919
1930
  // Fork lineage — the client's tab descriptor uses `parentAgentId` to
1920
1931
  // mark the agent as a fork (Split glyph). Carry it from the live
1921
1932
  // ManagedAgent so the marker doesn't disappear once the agent is
@@ -3982,7 +3993,19 @@ var AgentSnapshotPayloadSchema = z11.object({
3982
3993
  * lists at display time. The agent itself is a real, full-featured session
3983
3994
  * in every other respect; `internal` is a UI visibility hint, nothing more.
3984
3995
  */
3985
- internal: z11.boolean().optional()
3996
+ internal: z11.boolean().optional(),
3997
+ /**
3998
+ * Multi-tenant ownership (Phase 2c/4). Surfaces the auth-server user-id
3999
+ * of the agent's creator so the app can render an owner badge and
4000
+ * detect "shared with me" state (owner !== current user). Optional/
4001
+ * nullable for legacy agents created before per-agent ownership
4002
+ * existed — those render without a badge.
4003
+ *
4004
+ * NOTE: `sharedWithUserIds` is intentionally NOT exposed here. Only the
4005
+ * owner can enumerate the full ACL, via `list_agent_shared_users_request`.
4006
+ * The snapshot stays cheap and recipient-safe.
4007
+ */
4008
+ ownerUserId: z11.string().nullable().optional()
3986
4009
  });
3987
4010
  var VoiceAudioChunkMessageSchema = z11.object({
3988
4011
  type: z11.literal("voice_audio_chunk"),
@@ -5105,6 +5128,50 @@ var LinkAccountResponseMessageSchema = z11.object({
5105
5128
  })
5106
5129
  ])
5107
5130
  });
5131
+ var ClaudeProfileLoginRequestMessageSchema = z11.object({
5132
+ type: z11.literal("claude_profile_login_request"),
5133
+ requestId: z11.string(),
5134
+ /** Username for the profile dir (~/.claude-{username}/). */
5135
+ username: z11.string().min(1)
5136
+ });
5137
+ var ClaudeProfileLoginUrlMessageSchema = z11.object({
5138
+ type: z11.literal("claude_profile_login_url"),
5139
+ requestId: z11.string(),
5140
+ url: z11.string().min(1)
5141
+ });
5142
+ var ClaudeProfileLoginCallbackMessageSchema = z11.object({
5143
+ type: z11.literal("claude_profile_login_callback"),
5144
+ requestId: z11.string(),
5145
+ callbackToken: z11.string().min(1)
5146
+ });
5147
+ var ClaudeProfileLoginResultMessageSchema = z11.object({
5148
+ type: z11.literal("claude_profile_login_result"),
5149
+ requestId: z11.string(),
5150
+ ok: z11.boolean(),
5151
+ error: z11.string().optional()
5152
+ });
5153
+ var ClaudeProfileRemoveRequestMessageSchema = z11.object({
5154
+ type: z11.literal("claude_profile_remove_request"),
5155
+ requestId: z11.string(),
5156
+ username: z11.string().min(1)
5157
+ });
5158
+ var ClaudeProfileRemoveResponseMessageSchema = z11.object({
5159
+ type: z11.literal("claude_profile_remove_response"),
5160
+ requestId: z11.string(),
5161
+ ok: z11.boolean(),
5162
+ error: z11.string().optional()
5163
+ });
5164
+ var ClaudeProfileStatusRequestMessageSchema = z11.object({
5165
+ type: z11.literal("claude_profile_status_request"),
5166
+ requestId: z11.string(),
5167
+ username: z11.string().min(1)
5168
+ });
5169
+ var ClaudeProfileStatusResponseMessageSchema = z11.object({
5170
+ type: z11.literal("claude_profile_status_response"),
5171
+ requestId: z11.string(),
5172
+ hasProfile: z11.boolean(),
5173
+ isAuthenticated: z11.boolean()
5174
+ });
5108
5175
  var ListSessionUploadsRequestSchema = z11.object({
5109
5176
  type: z11.literal("list_session_uploads_request"),
5110
5177
  requestId: z11.string(),
@@ -5147,6 +5214,74 @@ var DeleteSessionUploadResponseSchema = z11.object({
5147
5214
  })
5148
5215
  ])
5149
5216
  });
5217
+ var ShareAgentWithUserRequestSchema = z11.object({
5218
+ type: z11.literal("share_agent_with_user_request"),
5219
+ requestId: z11.string(),
5220
+ /** Accepts full ID, unique prefix, or exact full title (server resolves). */
5221
+ agentId: z11.string(),
5222
+ /** Auth-server user-id of the recipient. */
5223
+ userId: z11.string()
5224
+ });
5225
+ var ShareAgentWithUserResponseSchema = z11.object({
5226
+ type: z11.literal("share_agent_with_user_response"),
5227
+ payload: z11.discriminatedUnion("ok", [
5228
+ z11.object({
5229
+ requestId: z11.string(),
5230
+ ok: z11.literal(true),
5231
+ /** Updated ACL after the share applied (owner is not included). */
5232
+ sharedWithUserIds: z11.array(z11.string())
5233
+ }),
5234
+ z11.object({
5235
+ requestId: z11.string(),
5236
+ ok: z11.literal(false),
5237
+ error: z11.string()
5238
+ })
5239
+ ])
5240
+ });
5241
+ var UnshareAgentWithUserRequestSchema = z11.object({
5242
+ type: z11.literal("unshare_agent_with_user_request"),
5243
+ requestId: z11.string(),
5244
+ agentId: z11.string(),
5245
+ userId: z11.string()
5246
+ });
5247
+ var UnshareAgentWithUserResponseSchema = z11.object({
5248
+ type: z11.literal("unshare_agent_with_user_response"),
5249
+ payload: z11.discriminatedUnion("ok", [
5250
+ z11.object({
5251
+ requestId: z11.string(),
5252
+ ok: z11.literal(true),
5253
+ sharedWithUserIds: z11.array(z11.string())
5254
+ }),
5255
+ z11.object({
5256
+ requestId: z11.string(),
5257
+ ok: z11.literal(false),
5258
+ error: z11.string()
5259
+ })
5260
+ ])
5261
+ });
5262
+ var ListAgentSharedUsersRequestSchema = z11.object({
5263
+ type: z11.literal("list_agent_shared_users_request"),
5264
+ requestId: z11.string(),
5265
+ agentId: z11.string()
5266
+ });
5267
+ var ListAgentSharedUsersResponseSchema = z11.object({
5268
+ type: z11.literal("list_agent_shared_users_response"),
5269
+ payload: z11.discriminatedUnion("ok", [
5270
+ z11.object({
5271
+ requestId: z11.string(),
5272
+ ok: z11.literal(true),
5273
+ /** Auth-server user-id of the owner (always present when ok). */
5274
+ ownerUserId: z11.string().nullable(),
5275
+ /** Auth-server user-ids that the owner has granted access to. */
5276
+ sharedWithUserIds: z11.array(z11.string())
5277
+ }),
5278
+ z11.object({
5279
+ requestId: z11.string(),
5280
+ ok: z11.literal(false),
5281
+ error: z11.string()
5282
+ })
5283
+ ])
5284
+ });
5150
5285
  var SessionImageSchema = z11.object({
5151
5286
  id: z11.string(),
5152
5287
  fileName: z11.string(),
@@ -5235,6 +5370,10 @@ var SessionInboundMessageSchema = z11.discriminatedUnion("type", [
5235
5370
  AbortRequestMessageSchema,
5236
5371
  AudioPlayedMessageSchema,
5237
5372
  LinkAccountRequestMessageSchema,
5373
+ ClaudeProfileLoginRequestMessageSchema,
5374
+ ClaudeProfileLoginCallbackMessageSchema,
5375
+ ClaudeProfileStatusRequestMessageSchema,
5376
+ ClaudeProfileRemoveRequestMessageSchema,
5238
5377
  FetchAgentsRequestMessageSchema,
5239
5378
  FetchWorkspacesRequestMessageSchema,
5240
5379
  FetchAgentRequestMessageSchema,
@@ -5385,6 +5524,9 @@ var SessionInboundMessageSchema = z11.discriminatedUnion("type", [
5385
5524
  DeleteSessionUploadRequestSchema,
5386
5525
  ListSessionImagesRequestSchema,
5387
5526
  DeleteSessionImageRequestSchema,
5527
+ ShareAgentWithUserRequestSchema,
5528
+ UnshareAgentWithUserRequestSchema,
5529
+ ListAgentSharedUsersRequestSchema,
5388
5530
  FetchAttachmentBytesRequestSchema
5389
5531
  ]);
5390
5532
  var ActivityLogPayloadSchema = z11.object({
@@ -6960,10 +7102,17 @@ var SessionOutboundMessageSchema = z11.discriminatedUnion("type", [
6960
7102
  GoogleFontsDownloadResponseSchema,
6961
7103
  GetVapidPublicKeyResponseSchema,
6962
7104
  LinkAccountResponseMessageSchema,
7105
+ ClaudeProfileLoginUrlMessageSchema,
7106
+ ClaudeProfileLoginResultMessageSchema,
7107
+ ClaudeProfileStatusResponseMessageSchema,
7108
+ ClaudeProfileRemoveResponseMessageSchema,
6963
7109
  ListSessionUploadsResponseSchema,
6964
7110
  DeleteSessionUploadResponseSchema,
6965
7111
  ListSessionImagesResponseSchema,
6966
7112
  DeleteSessionImageResponseSchema,
7113
+ ShareAgentWithUserResponseSchema,
7114
+ UnshareAgentWithUserResponseSchema,
7115
+ ListAgentSharedUsersResponseSchema,
6967
7116
  FetchAttachmentBytesResponseSchema
6968
7117
  ]);
6969
7118
  var WSPingMessageSchema = z11.object({
@@ -6981,7 +7130,11 @@ var WSHelloMessageSchema = z11.object({
6981
7130
  capabilities: z11.object({
6982
7131
  voice: z11.boolean().optional(),
6983
7132
  pushNotifications: z11.boolean().optional()
6984
- }).passthrough().optional()
7133
+ }).passthrough().optional(),
7134
+ /** Auth-server userId of the connecting user. Used for per-user Claude profile selection. */
7135
+ userId: z11.string().optional(),
7136
+ /** Display name of the connecting user (for profile dir naming). */
7137
+ username: z11.string().optional()
6985
7138
  });
6986
7139
  var WSRecordingStateMessageSchema = z11.object({
6987
7140
  type: z11.literal("recording_state"),
@@ -7153,7 +7306,7 @@ import { exec } from "node:child_process";
7153
7306
  import { promisify as promisify3 } from "util";
7154
7307
  import { join as join14, resolve as resolve9, sep as sep2 } from "path";
7155
7308
  import { homedir as homedir5, hostname as osHostname } from "node:os";
7156
- import { z as z36 } from "zod";
7309
+ import { z as z38 } from "zod";
7157
7310
 
7158
7311
  // ../server/src/server/persisted-config.ts
7159
7312
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
@@ -7667,7 +7820,9 @@ function ensurePrng() {
7667
7820
  const cryptoObj = globalThis.crypto;
7668
7821
  if (cryptoObj?.getRandomValues) {
7669
7822
  nacl.setPRNG((x, n) => {
7670
- cryptoObj.getRandomValues(x.subarray(0, n));
7823
+ const buf = new Uint8Array(n);
7824
+ cryptoObj.getRandomValues(buf);
7825
+ x.set(buf, 0);
7671
7826
  });
7672
7827
  prngReady = true;
7673
7828
  return;
@@ -7778,8 +7933,8 @@ function base64ToArrayBuffer(base64) {
7778
7933
  // ../relay/dist/encrypted-channel.js
7779
7934
  var HANDSHAKE_RETRY_MS = 1e3;
7780
7935
  var MAX_PENDING_SENDS = 200;
7781
- async function createClientChannel(transport, daemonPublicKeyB64, events = {}) {
7782
- const keyPair = generateKeyPair();
7936
+ async function createClientChannel(transport, daemonPublicKeyB64, events = {}, staticKeyPair) {
7937
+ const keyPair = staticKeyPair ?? generateKeyPair();
7783
7938
  const daemonPublicKey = importPublicKey(daemonPublicKeyB64);
7784
7939
  const sharedKey = deriveSharedKey(keyPair.secretKey, daemonPublicKey);
7785
7940
  const channel = new EncryptedChannel(transport, sharedKey, events);
@@ -7951,6 +8106,16 @@ var EncryptedChannel = class {
7951
8106
  isOpen() {
7952
8107
  return this.state === "open";
7953
8108
  }
8109
+ /**
8110
+ * Peer's X25519 public key (base64) captured during the daemon-side
8111
+ * handshake. Returns `null` on the client side or when the channel was
8112
+ * built without this metadata (legacy code paths). Used by the daemon's
8113
+ * WS-server to resolve the connecting device → owning user.
8114
+ */
8115
+ getPeerPublicKeyB64() {
8116
+ const v = this.options.peerPublicKeyB64;
8117
+ return v && v.length > 0 ? v : null;
8118
+ }
7954
8119
  onTransitionToOpen(cb) {
7955
8120
  this.onOpenCallbacks.push(cb);
7956
8121
  }
@@ -9410,14 +9575,14 @@ var DictationStreamManager = class {
9410
9575
  PCM_CHANNELS,
9411
9576
  PCM_BITS_PER_SAMPLE
9412
9577
  );
9413
- const path28 = await maybePersistDictationDebugAudio(
9578
+ const path29 = await maybePersistDictationDebugAudio(
9414
9579
  wavBuffer,
9415
9580
  { sessionId: state.sessionId, dictationId: state.dictationId, format: "audio/wav" },
9416
9581
  this.logger,
9417
9582
  state.debugChunkWriter?.folder
9418
9583
  );
9419
- state.debugRecordingPath = path28;
9420
- return path28;
9584
+ state.debugRecordingPath = path29;
9585
+ return path29;
9421
9586
  }
9422
9587
  failDictationStream(dictationId, error, retryable) {
9423
9588
  this.emit({
@@ -9893,7 +10058,14 @@ async function ensureAgentLoaded(agentId, deps) {
9893
10058
  if (!config) {
9894
10059
  throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
9895
10060
  }
9896
- snapshot = await deps.agentManager.createAgent(config, agentId, { labels: record.labels });
10061
+ snapshot = await deps.agentManager.createAgent(config, agentId, {
10062
+ labels: record.labels,
10063
+ // Preserve multi-tenant ownership across the no-handle rehydrate
10064
+ // path (records that never landed a persistence handle, e.g. very
10065
+ // early agents). Without this, agents would silently drop their
10066
+ // owner whenever they took this branch through `ensureAgentLoaded`.
10067
+ ownerUserId: record.ownerUserId ?? null
10068
+ });
9897
10069
  deps.logger.info({ agentId, provider: record.provider }, "Agent created from stored config");
9898
10070
  }
9899
10071
  await deps.agentManager.hydrateTimelineFromProvider(agentId);
@@ -11208,14 +11380,14 @@ function parseDiff(diffText) {
11208
11380
  const firstLine = lines[0];
11209
11381
  const isNew = section.includes("new file mode") || section.includes("--- /dev/null");
11210
11382
  const isDeleted = section.includes("deleted file mode") || section.includes("+++ /dev/null");
11211
- let path28 = "unknown";
11383
+ let path29 = "unknown";
11212
11384
  const pathMatch = firstLine.match(/a\/(.*?) b\//);
11213
11385
  if (pathMatch) {
11214
- path28 = pathMatch[1];
11386
+ path29 = pathMatch[1];
11215
11387
  } else {
11216
11388
  const newFileMatch = firstLine.match(/b\/(.+)$/);
11217
11389
  if (newFileMatch) {
11218
- path28 = newFileMatch[1];
11390
+ path29 = newFileMatch[1];
11219
11391
  }
11220
11392
  }
11221
11393
  const hunks = [];
@@ -11259,7 +11431,7 @@ function parseDiff(diffText) {
11259
11431
  if (currentHunk) {
11260
11432
  hunks.push(currentHunk);
11261
11433
  }
11262
- files.push({ path: path28, isNew, isDeleted, additions, deletions, hunks });
11434
+ files.push({ path: path29, isNew, isDeleted, additions, deletions, hunks });
11263
11435
  }
11264
11436
  return files;
11265
11437
  }
@@ -11315,9 +11487,9 @@ function buildTokenLookup(lineMap, highlighted) {
11315
11487
  }
11316
11488
  return lookup2;
11317
11489
  }
11318
- function buildFullFileTokenLookup(fileContent, path28) {
11490
+ function buildFullFileTokenLookup(fileContent, path29) {
11319
11491
  const lookup2 = /* @__PURE__ */ new Map();
11320
- const highlighted = highlightCode(fileContent, path28);
11492
+ const highlighted = highlightCode(fileContent, path29);
11321
11493
  for (let i = 0; i < highlighted.length; i++) {
11322
11494
  lookup2.set(i + 1, highlighted[i]);
11323
11495
  }
@@ -13018,11 +13190,11 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
13018
13190
  }
13019
13191
  continue;
13020
13192
  }
13021
- const path28 = tabParts[1];
13022
- if (!path28) continue;
13193
+ const path29 = tabParts[1];
13194
+ if (!path29) continue;
13023
13195
  const code = rawStatus[0];
13024
13196
  changes.push({
13025
- path: path28,
13197
+ path: path29,
13026
13198
  status: rawStatus,
13027
13199
  isNew: code === "A",
13028
13200
  isDeleted: code === "D"
@@ -13057,9 +13229,9 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
13057
13229
  }
13058
13230
  return Array.from(byPath.values());
13059
13231
  }
13060
- async function readGitFileContentAtRef(cwd, ref, path28) {
13232
+ async function readGitFileContentAtRef(cwd, ref, path29) {
13061
13233
  try {
13062
- const { stdout } = await runGitCommand(["show", `${ref}:${path28}`], {
13234
+ const { stdout } = await runGitCommand(["show", `${ref}:${path29}`], {
13063
13235
  cwd,
13064
13236
  env: READ_ONLY_GIT_ENV2
13065
13237
  });
@@ -13120,21 +13292,21 @@ async function getTrackedNumstatByPath(cwd, ref, ignoreWhitespace = false) {
13120
13292
  const additionsField = parts[0] ?? "";
13121
13293
  const deletionsField = parts[1] ?? "";
13122
13294
  const rawPath = parts.slice(2).join(" ");
13123
- const path28 = normalizeNumstatPath(rawPath);
13124
- if (!path28) {
13295
+ const path29 = normalizeNumstatPath(rawPath);
13296
+ if (!path29) {
13125
13297
  continue;
13126
13298
  }
13127
13299
  if (additionsField === "-" || deletionsField === "-") {
13128
- stats.set(path28, { additions: 0, deletions: 0, isBinary: true });
13300
+ stats.set(path29, { additions: 0, deletions: 0, isBinary: true });
13129
13301
  continue;
13130
13302
  }
13131
13303
  const additions = Number.parseInt(additionsField, 10);
13132
13304
  const deletions = Number.parseInt(deletionsField, 10);
13133
13305
  if (Number.isNaN(additions) || Number.isNaN(deletions)) {
13134
- stats.set(path28, null);
13306
+ stats.set(path29, null);
13135
13307
  continue;
13136
13308
  }
13137
- stats.set(path28, { additions, deletions, isBinary: false });
13309
+ stats.set(path29, { additions, deletions, isBinary: false });
13138
13310
  }
13139
13311
  return stats;
13140
13312
  }
@@ -13719,10 +13891,10 @@ async function listUncommittedFiles(cwd) {
13719
13891
  if (dest) results.push({ path: dest, changeType: code });
13720
13892
  continue;
13721
13893
  }
13722
- const path28 = parts[1];
13723
- if (!path28) continue;
13894
+ const path29 = parts[1];
13895
+ if (!path29) continue;
13724
13896
  if (code === "A" || code === "M" || code === "D") {
13725
- results.push({ path: path28, changeType: code });
13897
+ results.push({ path: path29, changeType: code });
13726
13898
  }
13727
13899
  }
13728
13900
  } catch {
@@ -17465,12 +17637,12 @@ function extractPlanNameFromFrontmatter(content) {
17465
17637
  return null;
17466
17638
  }
17467
17639
  function resolvePlanFilename(options) {
17468
- const { originalPath, newSlug, existsSync: existsSync15 } = options;
17640
+ const { originalPath, newSlug, existsSync: existsSync16 } = options;
17469
17641
  const dir = path8.dirname(originalPath);
17470
17642
  const base = `${newSlug}.md`;
17471
17643
  let candidate = path8.join(dir, base);
17472
17644
  let counter = 2;
17473
- while (candidate !== originalPath && existsSync15(candidate)) {
17645
+ while (candidate !== originalPath && existsSync16(candidate)) {
17474
17646
  candidate = path8.join(dir, `${newSlug}-${counter}.md`);
17475
17647
  counter += 1;
17476
17648
  }
@@ -21707,14 +21879,14 @@ function codexApplyPatchToUnifiedDiff(text) {
21707
21879
  for (const line of lines) {
21708
21880
  const directive = parseCodexApplyPatchDirective(line);
21709
21881
  if (directive) {
21710
- const path28 = normalizeDiffHeaderPath(directive.path);
21711
- if (path28.length > 0) {
21882
+ const path29 = normalizeDiffHeaderPath(directive.path);
21883
+ if (path29.length > 0) {
21712
21884
  if (output.length > 0 && output[output.length - 1] !== "") {
21713
21885
  output.push("");
21714
21886
  }
21715
- const left = directive.kind === "add" ? "/dev/null" : `a/${path28}`;
21716
- const right = directive.kind === "delete" ? "/dev/null" : `b/${path28}`;
21717
- output.push(`diff --git a/${path28} b/${path28}`);
21887
+ const left = directive.kind === "add" ? "/dev/null" : `a/${path29}`;
21888
+ const right = directive.kind === "delete" ? "/dev/null" : `b/${path29}`;
21889
+ output.push(`diff --git a/${path29} b/${path29}`);
21718
21890
  output.push(`--- ${left}`);
21719
21891
  output.push(`+++ ${right}`);
21720
21892
  sawDiffContent = true;
@@ -21784,9 +21956,9 @@ function asEditTextFields(text) {
21784
21956
  function normalizeRolloutEditInput(input) {
21785
21957
  if (typeof input === "string") {
21786
21958
  const textFields2 = asEditTextFields(input);
21787
- const path28 = extractPatchPrimaryFilePath(input);
21959
+ const path29 = extractPatchPrimaryFilePath(input);
21788
21960
  return {
21789
- ...path28 ? { path: path28 } : {},
21961
+ ...path29 ? { path: path29 } : {},
21790
21962
  ...textFields2.unifiedDiff ? { patch: textFields2.unifiedDiff } : {},
21791
21963
  ...textFields2.newString ? { content: textFields2.newString } : {}
21792
21964
  };
@@ -21934,12 +22106,12 @@ function parseFileChangeDiff(entry) {
21934
22106
  ]);
21935
22107
  }
21936
22108
  function toFileChangeEntry(entry, options, fallbackPath) {
21937
- const path28 = parseFileChangePath(entry, options, fallbackPath);
21938
- if (!path28) {
22109
+ const path29 = parseFileChangePath(entry, options, fallbackPath);
22110
+ if (!path29) {
21939
22111
  return null;
21940
22112
  }
21941
22113
  return {
21942
- path: path28,
22114
+ path: path29,
21943
22115
  kind: parseFileChangeKind(entry),
21944
22116
  diff: parseFileChangeDiff(entry)
21945
22117
  };
@@ -21961,12 +22133,12 @@ function parseFileChangeEntries(changes, options) {
21961
22133
  if (singleEntry) {
21962
22134
  return [singleEntry];
21963
22135
  }
21964
- return Object.entries(changes).map(([path28, value]) => {
22136
+ return Object.entries(changes).map(([path29, value]) => {
21965
22137
  if (isRecord2(value)) {
21966
- return toFileChangeEntry(value, options, path28);
22138
+ return toFileChangeEntry(value, options, path29);
21967
22139
  }
21968
22140
  if (typeof value === "string") {
21969
- const normalizedPath = normalizeCodexFilePath(path28.trim(), options?.cwd);
22141
+ const normalizedPath = normalizeCodexFilePath(path29.trim(), options?.cwd);
21970
22142
  if (!normalizedPath) {
21971
22143
  return null;
21972
22144
  }
@@ -22711,16 +22883,16 @@ function isObjectSchemaNode(schema) {
22711
22883
  const type = schema.type;
22712
22884
  return isSchemaRecord(schema.properties) || type === "object" || Array.isArray(type) && type.includes("object");
22713
22885
  }
22714
- function normalizeCodexOutputSchemaNode(schema, path28) {
22886
+ function normalizeCodexOutputSchemaNode(schema, path29) {
22715
22887
  if (Array.isArray(schema)) {
22716
- return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path28}[${index}]`));
22888
+ return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path29}[${index}]`));
22717
22889
  }
22718
22890
  if (!isSchemaRecord(schema)) {
22719
22891
  return schema;
22720
22892
  }
22721
22893
  const normalized = {};
22722
22894
  for (const [key, value] of Object.entries(schema)) {
22723
- normalized[key] = normalizeCodexOutputSchemaNode(value, `${path28}.${key}`);
22895
+ normalized[key] = normalizeCodexOutputSchemaNode(value, `${path29}.${key}`);
22724
22896
  }
22725
22897
  if (!isObjectSchemaNode(normalized)) {
22726
22898
  return normalized;
@@ -22729,7 +22901,7 @@ function normalizeCodexOutputSchemaNode(schema, path28) {
22729
22901
  normalized.additionalProperties = false;
22730
22902
  } else if (normalized.additionalProperties !== false) {
22731
22903
  throw new Error(
22732
- `Codex structured outputs require ${path28} to set additionalProperties to false for object schemas.`
22904
+ `Codex structured outputs require ${path29} to set additionalProperties to false for object schemas.`
22733
22905
  );
22734
22906
  }
22735
22907
  const properties = isSchemaRecord(normalized.properties) ? normalized.properties : null;
@@ -23493,8 +23665,8 @@ function parseCodexPatchChanges(changes) {
23493
23665
  }
23494
23666
  ];
23495
23667
  }
23496
- return Object.entries(recordChanges).map(([path28, value]) => {
23497
- const normalizedPath = path28.trim();
23668
+ return Object.entries(recordChanges).map(([path29, value]) => {
23669
+ const normalizedPath = path29.trim();
23498
23670
  if (!normalizedPath) {
23499
23671
  return null;
23500
23672
  }
@@ -31205,6 +31377,53 @@ function buildProviderRegistry(logger, options) {
31205
31377
  );
31206
31378
  }
31207
31379
 
31380
+ // ../server/src/server/claude-profile.ts
31381
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, rmSync as rmSync2 } from "node:fs";
31382
+ import path13 from "node:path";
31383
+ import os5 from "node:os";
31384
+ var SHARED_ITEMS = ["settings.json", "hooks", "agents", "skills", "plugins", "keybindings.json"];
31385
+ function getClaudeProfileDir(username) {
31386
+ return path13.join(os5.homedir(), `.claude-${username}`);
31387
+ }
31388
+ function ensureClaudeProfile(username, logger) {
31389
+ const profileDir = getClaudeProfileDir(username);
31390
+ const ownerDir = path13.join(os5.homedir(), ".claude");
31391
+ if (!existsSync10(ownerDir)) {
31392
+ throw new Error(`Owner claude config dir not found: ${ownerDir}`);
31393
+ }
31394
+ if (!existsSync10(profileDir)) {
31395
+ mkdirSync5(profileDir, { recursive: true });
31396
+ logger?.info({ profileDir, username }, "created claude profile directory");
31397
+ }
31398
+ for (const item of SHARED_ITEMS) {
31399
+ const target = path13.join(ownerDir, item);
31400
+ const link = path13.join(profileDir, item);
31401
+ if (!existsSync10(target)) continue;
31402
+ if (existsSync10(link)) continue;
31403
+ symlinkSync(target, link);
31404
+ logger?.info({ item, profileDir }, "symlinked shared config item");
31405
+ }
31406
+ return profileDir;
31407
+ }
31408
+ function hasClaudeAuth(username) {
31409
+ const profileDir = getClaudeProfileDir(username);
31410
+ return existsSync10(profileDir);
31411
+ }
31412
+ function removeClaudeProfile(username, logger) {
31413
+ const profileDir = getClaudeProfileDir(username);
31414
+ if (!existsSync10(profileDir)) return;
31415
+ rmSync2(profileDir, { recursive: true, force: true });
31416
+ logger?.info({ profileDir, username }, "removed claude profile directory");
31417
+ }
31418
+
31419
+ // ../server/src/server/agent/agent-manager.ts
31420
+ import { z as z33 } from "zod";
31421
+ import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
31422
+
31423
+ // ../server/src/server/agent/handoff-mcp.ts
31424
+ import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
31425
+ import { z as z32 } from "zod";
31426
+
31208
31427
  // ../server/src/server/agent/agent-metadata-generator.ts
31209
31428
  import { basename as basename5 } from "path";
31210
31429
  import { z as z31 } from "zod";
@@ -31255,8 +31474,8 @@ function buildZodValidator(schema, schemaName) {
31255
31474
  return { ok: true, value: result.data };
31256
31475
  }
31257
31476
  const errors = result.error.issues.map((issue) => {
31258
- const path28 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
31259
- return `${path28}: ${issue.message}`;
31477
+ const path29 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
31478
+ return `${path29}: ${issue.message}`;
31260
31479
  });
31261
31480
  return { ok: false, errors };
31262
31481
  }
@@ -31274,9 +31493,9 @@ function buildJsonSchemaValidator(schema) {
31274
31493
  return { ok: true, value };
31275
31494
  }
31276
31495
  const errors = (validate.errors ?? []).map((error) => {
31277
- const path28 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
31496
+ const path29 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
31278
31497
  const message = error.message ?? "is invalid";
31279
- return `${path28}: ${message}`;
31498
+ return `${path29}: ${message}`;
31280
31499
  });
31281
31500
  return { ok: false, errors };
31282
31501
  }
@@ -31682,6 +31901,14 @@ function scheduleAgentMetadataGeneration(options) {
31682
31901
  });
31683
31902
  }
31684
31903
 
31904
+ // ../server/src/server/agent/agent-manager.ts
31905
+ var AgentIdSchema = z33.string().uuid();
31906
+ function canUserAccessAgent(agent, requesterUserId) {
31907
+ if (agent.ownerUserId === null) return true;
31908
+ if (agent.ownerUserId === requesterUserId) return true;
31909
+ return agent.sharedWithUserIds.includes(requesterUserId);
31910
+ }
31911
+
31685
31912
  // ../server/src/server/agent/timeline-append.ts
31686
31913
  async function appendTimelineItemIfAgentKnown(options) {
31687
31914
  try {
@@ -32065,25 +32292,25 @@ async function buildProjectPlacementForCwd(input) {
32065
32292
  }
32066
32293
 
32067
32294
  // ../server/src/server/workspace-registry.ts
32068
- import { z as z32 } from "zod";
32069
- var PersistedProjectRecordSchema = z32.object({
32070
- projectId: z32.string(),
32071
- rootPath: z32.string(),
32072
- kind: z32.enum(["git", "non_git"]),
32073
- displayName: z32.string(),
32074
- createdAt: z32.string(),
32075
- updatedAt: z32.string(),
32076
- archivedAt: z32.string().nullable()
32077
- });
32078
- var PersistedWorkspaceRecordSchema = z32.object({
32079
- workspaceId: z32.string(),
32080
- projectId: z32.string(),
32081
- cwd: z32.string(),
32082
- kind: z32.enum(["local_checkout", "worktree", "directory"]),
32083
- displayName: z32.string(),
32084
- createdAt: z32.string(),
32085
- updatedAt: z32.string(),
32086
- archivedAt: z32.string().nullable()
32295
+ import { z as z34 } from "zod";
32296
+ var PersistedProjectRecordSchema = z34.object({
32297
+ projectId: z34.string(),
32298
+ rootPath: z34.string(),
32299
+ kind: z34.enum(["git", "non_git"]),
32300
+ displayName: z34.string(),
32301
+ createdAt: z34.string(),
32302
+ updatedAt: z34.string(),
32303
+ archivedAt: z34.string().nullable()
32304
+ });
32305
+ var PersistedWorkspaceRecordSchema = z34.object({
32306
+ workspaceId: z34.string(),
32307
+ projectId: z34.string(),
32308
+ cwd: z34.string(),
32309
+ kind: z34.enum(["local_checkout", "worktree", "directory"]),
32310
+ displayName: z34.string(),
32311
+ createdAt: z34.string(),
32312
+ updatedAt: z34.string(),
32313
+ archivedAt: z34.string().nullable()
32087
32314
  });
32088
32315
  function createPersistedProjectRecord(input) {
32089
32316
  return PersistedProjectRecordSchema.parse({
@@ -32162,7 +32389,7 @@ function isVoicePermissionAllowed(request) {
32162
32389
 
32163
32390
  // ../server/src/server/file-explorer/service.ts
32164
32391
  import { promises as fs8 } from "fs";
32165
- import path13 from "path";
32392
+ import path14 from "path";
32166
32393
 
32167
32394
  // ../server/src/server/path-utils.ts
32168
32395
  import { homedir as homedir2 } from "node:os";
@@ -32216,7 +32443,7 @@ async function listDirectoryEntries({
32216
32443
  const dirents = await fs8.readdir(directoryPath, { withFileTypes: true });
32217
32444
  const entriesWithNulls = await Promise.all(
32218
32445
  dirents.map(async (dirent) => {
32219
- const targetPath = path13.join(directoryPath, dirent.name);
32446
+ const targetPath = path14.join(directoryPath, dirent.name);
32220
32447
  const kind = dirent.isDirectory() ? "directory" : "file";
32221
32448
  try {
32222
32449
  return await buildEntryPayload({
@@ -32255,7 +32482,7 @@ async function readExplorerFile({
32255
32482
  if (!stats.isFile()) {
32256
32483
  throw new Error("Requested path is not a file");
32257
32484
  }
32258
- const ext = path13.extname(filePath).toLowerCase();
32485
+ const ext = path14.extname(filePath).toLowerCase();
32259
32486
  const basePayload = {
32260
32487
  path: normalizeRelativePath({ root, targetPath: filePath }),
32261
32488
  size: stats.size,
@@ -32293,7 +32520,7 @@ async function writeTextFile({
32293
32520
  relativePath,
32294
32521
  content
32295
32522
  }) {
32296
- const ext = path13.extname(relativePath).toLowerCase();
32523
+ const ext = path14.extname(relativePath).toLowerCase();
32297
32524
  if (ext in IMAGE_MIME_TYPES) {
32298
32525
  throw new Error(`Refusing to write '${relativePath}': binary/image file`);
32299
32526
  }
@@ -32303,7 +32530,7 @@ async function writeTextFile({
32303
32530
  await fs8.rename(tempPath, filePath);
32304
32531
  }
32305
32532
  async function deleteFile({ root, relativePath }) {
32306
- const ext = path13.extname(relativePath).toLowerCase();
32533
+ const ext = path14.extname(relativePath).toLowerCase();
32307
32534
  if (ext !== ".md") {
32308
32535
  throw new Error(`Refusing to delete '${relativePath}': only .md files allowed`);
32309
32536
  }
@@ -32337,7 +32564,7 @@ async function getDownloadableFileInfo({ root, relativePath }) {
32337
32564
  if (!stats.isFile()) {
32338
32565
  throw new Error("Requested path is not a file");
32339
32566
  }
32340
- const ext = path13.extname(filePath).toLowerCase();
32567
+ const ext = path14.extname(filePath).toLowerCase();
32341
32568
  let mimeType = "application/octet-stream";
32342
32569
  if (ext in IMAGE_MIME_TYPES) {
32343
32570
  mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
@@ -32359,23 +32586,23 @@ async function getDownloadableFileInfo({ root, relativePath }) {
32359
32586
  return {
32360
32587
  path: normalizeRelativePath({ root, targetPath: filePath }),
32361
32588
  absolutePath: filePath,
32362
- fileName: path13.basename(filePath),
32589
+ fileName: path14.basename(filePath),
32363
32590
  mimeType,
32364
32591
  size: stats.size
32365
32592
  };
32366
32593
  }
32367
32594
  async function resolveScopedPath({ root, relativePath = "." }) {
32368
- const normalizedRoot = path13.resolve(root);
32595
+ const normalizedRoot = path14.resolve(root);
32369
32596
  const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
32370
- const relative = path13.relative(normalizedRoot, requestedPath);
32371
- if (relative !== "" && (relative.startsWith("..") || path13.isAbsolute(relative))) {
32597
+ const relative = path14.relative(normalizedRoot, requestedPath);
32598
+ if (relative !== "" && (relative.startsWith("..") || path14.isAbsolute(relative))) {
32372
32599
  throw new Error("Access outside of workspace is not allowed");
32373
32600
  }
32374
32601
  const realRoot = await fs8.realpath(normalizedRoot);
32375
32602
  try {
32376
32603
  const realPath = await fs8.realpath(requestedPath);
32377
- const realRelative = path13.relative(realRoot, realPath);
32378
- if (realRelative !== "" && (realRelative.startsWith("..") || path13.isAbsolute(realRelative))) {
32604
+ const realRelative = path14.relative(realRoot, realPath);
32605
+ if (realRelative !== "" && (realRelative.startsWith("..") || path14.isAbsolute(realRelative))) {
32379
32606
  throw new Error("Access outside of workspace is not allowed");
32380
32607
  }
32381
32608
  return requestedPath;
@@ -32406,10 +32633,10 @@ function isMissingEntryError(error) {
32406
32633
  return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
32407
32634
  }
32408
32635
  function normalizeRelativePath({ root, targetPath }) {
32409
- const normalizedRoot = path13.resolve(root);
32410
- const normalizedTarget = path13.resolve(targetPath);
32411
- const relative = path13.relative(normalizedRoot, normalizedTarget);
32412
- return relative === "" ? "." : relative.split(path13.sep).join("/");
32636
+ const normalizedRoot = path14.resolve(root);
32637
+ const normalizedTarget = path14.resolve(targetPath);
32638
+ const relative = path14.relative(normalizedRoot, normalizedTarget);
32639
+ return relative === "" ? "." : relative.split(path14.sep).join("/");
32413
32640
  }
32414
32641
  function textMimeTypeForExtension(ext) {
32415
32642
  return TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE;
@@ -32762,65 +32989,65 @@ async function getProjectIcon(projectDir) {
32762
32989
  }
32763
32990
 
32764
32991
  // ../server/src/utils/path.ts
32765
- import os5 from "os";
32766
- function expandTilde(path28) {
32767
- if (path28.startsWith("~/")) {
32768
- const homeDir3 = process.env.HOME || os5.homedir();
32769
- return path28.replace("~", homeDir3);
32992
+ import os6 from "os";
32993
+ function expandTilde(path29) {
32994
+ if (path29.startsWith("~/")) {
32995
+ const homeDir3 = process.env.HOME || os6.homedir();
32996
+ return path29.replace("~", homeDir3);
32770
32997
  }
32771
- if (path28 === "~") {
32772
- return process.env.HOME || os5.homedir();
32998
+ if (path29 === "~") {
32999
+ return process.env.HOME || os6.homedir();
32773
33000
  }
32774
- return path28;
33001
+ return path29;
32775
33002
  }
32776
33003
 
32777
33004
  // ../server/src/server/skills/scanner.ts
32778
33005
  import fs9 from "node:fs/promises";
32779
- import os6 from "node:os";
32780
- import path14 from "node:path";
33006
+ import os7 from "node:os";
33007
+ import path15 from "node:path";
32781
33008
  var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
32782
33009
  function homeDir() {
32783
- return process.env.HOME || os6.homedir();
33010
+ return process.env.HOME || os7.homedir();
32784
33011
  }
32785
33012
  function codexHomeDir() {
32786
- return process.env.CODEX_HOME || path14.join(homeDir(), ".codex");
33013
+ return process.env.CODEX_HOME || path15.join(homeDir(), ".codex");
32787
33014
  }
32788
33015
  function resolveScopeDir(provider, scope, workspaceRoot) {
32789
33016
  if (scope === "codex-prompts") {
32790
33017
  if (provider !== "codex") {
32791
33018
  throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
32792
33019
  }
32793
- return path14.join(codexHomeDir(), "prompts");
33020
+ return path15.join(codexHomeDir(), "prompts");
32794
33021
  }
32795
33022
  if (scope === "project") {
32796
33023
  if (!workspaceRoot) {
32797
33024
  throw new Error(`workspaceRoot is required for scope "project"`);
32798
33025
  }
32799
33026
  const dotDir = provider === "claude" ? ".claude" : ".codex";
32800
- return path14.join(workspaceRoot, dotDir, "skills");
33027
+ return path15.join(workspaceRoot, dotDir, "skills");
32801
33028
  }
32802
33029
  if (provider === "claude") {
32803
- return path14.join(homeDir(), ".claude", "skills");
33030
+ return path15.join(homeDir(), ".claude", "skills");
32804
33031
  }
32805
- return path14.join(codexHomeDir(), "skills");
33032
+ return path15.join(codexHomeDir(), "skills");
32806
33033
  }
32807
33034
  function allowedRoots(workspaceRoot) {
32808
33035
  const roots = [
32809
- path14.join(homeDir(), ".claude", "skills"),
32810
- path14.join(codexHomeDir(), "skills"),
32811
- path14.join(codexHomeDir(), "prompts")
33036
+ path15.join(homeDir(), ".claude", "skills"),
33037
+ path15.join(codexHomeDir(), "skills"),
33038
+ path15.join(codexHomeDir(), "prompts")
32812
33039
  ];
32813
33040
  if (workspaceRoot) {
32814
- roots.push(path14.join(workspaceRoot, ".claude", "skills"));
32815
- roots.push(path14.join(workspaceRoot, ".codex", "skills"));
33041
+ roots.push(path15.join(workspaceRoot, ".claude", "skills"));
33042
+ roots.push(path15.join(workspaceRoot, ".codex", "skills"));
32816
33043
  }
32817
- return roots.map((r) => path14.resolve(r));
33044
+ return roots.map((r) => path15.resolve(r));
32818
33045
  }
32819
33046
  function isInsideAllowedRoot(absPath, workspaceRoot) {
32820
- const resolved = path14.resolve(absPath);
33047
+ const resolved = path15.resolve(absPath);
32821
33048
  for (const root of allowedRoots(workspaceRoot)) {
32822
- const rel = path14.relative(root, resolved);
32823
- if (rel === "" || !rel.startsWith("..") && !path14.isAbsolute(rel)) {
33049
+ const rel = path15.relative(root, resolved);
33050
+ if (rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel)) {
32824
33051
  return true;
32825
33052
  }
32826
33053
  }
@@ -32951,7 +33178,7 @@ async function listSkills(args) {
32951
33178
  if (!entry.name.endsWith(".md")) continue;
32952
33179
  const name = entry.name.slice(0, -".md".length);
32953
33180
  if (!name) continue;
32954
- const fullPath = path14.join(dir, entry.name);
33181
+ const fullPath = path15.join(dir, entry.name);
32955
33182
  const stat5 = await safeStat(fullPath);
32956
33183
  if (!stat5) continue;
32957
33184
  const description = await readDescriptionSafely(fullPath);
@@ -32969,8 +33196,8 @@ async function listSkills(args) {
32969
33196
  } else {
32970
33197
  for (const entry of entries) {
32971
33198
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
32972
- const skillDir = path14.join(dir, entry.name);
32973
- const skillPath = path14.join(skillDir, "SKILL.md");
33199
+ const skillDir = path15.join(dir, entry.name);
33200
+ const skillPath = path15.join(skillDir, "SKILL.md");
32974
33201
  const stat5 = await safeStat(skillPath);
32975
33202
  if (!stat5) continue;
32976
33203
  const description = await readDescriptionSafely(skillPath);
@@ -33017,7 +33244,7 @@ async function createSkill(args) {
33017
33244
  const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
33018
33245
  await fs9.mkdir(dir, { recursive: true });
33019
33246
  if (args.scope === "codex-prompts") {
33020
- const filePath2 = path14.join(dir, `${args.name}.md`);
33247
+ const filePath2 = path15.join(dir, `${args.name}.md`);
33021
33248
  try {
33022
33249
  await fs9.access(filePath2);
33023
33250
  throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
@@ -33033,7 +33260,7 @@ async function createSkill(args) {
33033
33260
  await fs9.writeFile(filePath2, initial2, "utf8");
33034
33261
  return { path: filePath2 };
33035
33262
  }
33036
- const skillDir = path14.join(dir, args.name);
33263
+ const skillDir = path15.join(dir, args.name);
33037
33264
  let dirExists = false;
33038
33265
  try {
33039
33266
  const stat5 = await fs9.stat(skillDir);
@@ -33045,7 +33272,7 @@ async function createSkill(args) {
33045
33272
  throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
33046
33273
  }
33047
33274
  await fs9.mkdir(skillDir, { recursive: true });
33048
- const filePath = path14.join(skillDir, "SKILL.md");
33275
+ const filePath = path15.join(skillDir, "SKILL.md");
33049
33276
  const initial = buildStarterSkill(args.name);
33050
33277
  await fs9.writeFile(filePath, initial, "utf8");
33051
33278
  return { path: filePath };
@@ -33074,7 +33301,7 @@ Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expa
33074
33301
  `;
33075
33302
  }
33076
33303
  async function writeSkillFrontmatter(args, workspaceRoot) {
33077
- if (!path14.isAbsolute(args.path)) {
33304
+ if (!path15.isAbsolute(args.path)) {
33078
33305
  throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
33079
33306
  }
33080
33307
  if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
@@ -33100,7 +33327,7 @@ ${original}`;
33100
33327
 
33101
33328
  // ../server/src/utils/directory-suggestions.ts
33102
33329
  import { readdir as readdir2, realpath, stat as stat3 } from "node:fs/promises";
33103
- import path15 from "node:path";
33330
+ import path16 from "node:path";
33104
33331
  var DEFAULT_LIMIT = 30;
33105
33332
  var MAX_LIMIT = 100;
33106
33333
  var DEFAULT_MAX_DEPTH = 6;
@@ -33186,7 +33413,7 @@ function normalizeLimit(limit) {
33186
33413
  return Math.max(1, Math.min(MAX_LIMIT, bounded));
33187
33414
  }
33188
33415
  async function searchWithinParentDirectory(input) {
33189
- const parentPath = path15.resolve(input.homeRoot, input.parentPart || ".");
33416
+ const parentPath = path16.resolve(input.homeRoot, input.parentPart || ".");
33190
33417
  const parentRoot = await resolveDirectory(parentPath);
33191
33418
  if (!parentRoot || !isPathInsideRoot(input.homeRoot, parentRoot)) {
33192
33419
  return [];
@@ -33251,7 +33478,7 @@ async function searchAcrossHomeTree(input) {
33251
33478
  return dedupeAndSort(ranked).slice(0, input.limit);
33252
33479
  }
33253
33480
  async function searchWorkspaceWithinParentDirectory(input) {
33254
- const parentPath = path15.resolve(input.workspaceRoot, input.parentPart || ".");
33481
+ const parentPath = path16.resolve(input.workspaceRoot, input.parentPart || ".");
33255
33482
  const parentRoot = await resolveDirectory(parentPath);
33256
33483
  if (!parentRoot || !isPathInsideRoot(input.workspaceRoot, parentRoot)) {
33257
33484
  return [];
@@ -33497,15 +33724,15 @@ function findSegmentMatchIndex(segments, predicate) {
33497
33724
  return -1;
33498
33725
  }
33499
33726
  function normalizeRelativePath2(homeRoot, absolutePath) {
33500
- const relative = path15.relative(homeRoot, absolutePath);
33727
+ const relative = path16.relative(homeRoot, absolutePath);
33501
33728
  if (!relative) {
33502
33729
  return ".";
33503
33730
  }
33504
- return relative.split(path15.sep).join("/");
33731
+ return relative.split(path16.sep).join("/");
33505
33732
  }
33506
33733
  function isPathInsideRoot(root, target) {
33507
- const relative = path15.relative(root, target);
33508
- return relative === "" || !relative.startsWith("..") && !path15.isAbsolute(relative);
33734
+ const relative = path16.relative(root, target);
33735
+ return relative === "" || !relative.startsWith("..") && !path16.isAbsolute(relative);
33509
33736
  }
33510
33737
  function normalizeQueryParts(query2, homeRoot) {
33511
33738
  const typedQuery = query2.trim().replace(/\\/g, "/");
@@ -33521,9 +33748,9 @@ function normalizeQueryParts(query2, homeRoot) {
33521
33748
  normalized = normalized.slice(1);
33522
33749
  }
33523
33750
  }
33524
- if (path15.isAbsolute(normalized)) {
33751
+ if (path16.isAbsolute(normalized)) {
33525
33752
  isRooted = true;
33526
- const absolute = path15.resolve(normalized);
33753
+ const absolute = path16.resolve(normalized);
33527
33754
  if (!isPathInsideRoot(homeRoot, absolute)) {
33528
33755
  return null;
33529
33756
  }
@@ -33562,8 +33789,8 @@ function normalizeQueryParts(query2, homeRoot) {
33562
33789
  }
33563
33790
  function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
33564
33791
  let normalized = query2.trim().replace(/\\/g, "/");
33565
- if (path15.isAbsolute(normalized)) {
33566
- const absolute = path15.resolve(normalized);
33792
+ if (path16.isAbsolute(normalized)) {
33793
+ const absolute = path16.resolve(normalized);
33567
33794
  if (!isPathInsideRoot(workspaceRoot, absolute)) {
33568
33795
  return null;
33569
33796
  }
@@ -33589,7 +33816,7 @@ function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
33589
33816
  }
33590
33817
  async function resolveDirectory(inputPath) {
33591
33818
  try {
33592
- const resolved = await realpath(path15.resolve(inputPath));
33819
+ const resolved = await realpath(path16.resolve(inputPath));
33593
33820
  const stats = await stat3(resolved);
33594
33821
  if (!stats.isDirectory()) {
33595
33822
  return null;
@@ -33616,7 +33843,7 @@ async function listChildDirectories(input) {
33616
33843
  if (!dirent.isDirectory() && !dirent.isSymbolicLink()) {
33617
33844
  continue;
33618
33845
  }
33619
- const candidatePath = path15.join(input.directory, dirent.name);
33846
+ const candidatePath = path16.join(input.directory, dirent.name);
33620
33847
  const absolutePath = await resolveDirectoryCandidate({
33621
33848
  candidatePath,
33622
33849
  dirent,
@@ -33653,7 +33880,7 @@ async function listWorkspaceChildEntries(input) {
33653
33880
  if (isIgnoredWorkspaceDirectoryName(dirent.name)) {
33654
33881
  continue;
33655
33882
  }
33656
- const candidatePath = path15.join(input.directory, dirent.name);
33883
+ const candidatePath = path16.join(input.directory, dirent.name);
33657
33884
  const entry = await resolveWorkspaceCandidate({
33658
33885
  candidatePath,
33659
33886
  dirent,
@@ -33676,7 +33903,7 @@ async function listWorkspaceChildEntries(input) {
33676
33903
  }
33677
33904
  async function resolveDirectoryCandidate(input) {
33678
33905
  if (input.dirent.isDirectory()) {
33679
- const resolved2 = path15.resolve(input.candidatePath);
33906
+ const resolved2 = path16.resolve(input.candidatePath);
33680
33907
  return isPathInsideRoot(input.homeRoot, resolved2) ? resolved2 : null;
33681
33908
  }
33682
33909
  const resolved = await resolveDirectory(input.candidatePath);
@@ -33687,14 +33914,14 @@ async function resolveDirectoryCandidate(input) {
33687
33914
  }
33688
33915
  async function resolveWorkspaceCandidate(input) {
33689
33916
  if (input.dirent.isDirectory()) {
33690
- const resolved = path15.resolve(input.candidatePath);
33917
+ const resolved = path16.resolve(input.candidatePath);
33691
33918
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
33692
33919
  return null;
33693
33920
  }
33694
33921
  return { absolutePath: resolved, kind: "directory" };
33695
33922
  }
33696
33923
  if (input.dirent.isFile()) {
33697
- const resolved = path15.resolve(input.candidatePath);
33924
+ const resolved = path16.resolve(input.candidatePath);
33698
33925
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
33699
33926
  return null;
33700
33927
  }
@@ -33774,7 +34001,7 @@ function pruneWorkspaceEntryListCache() {
33774
34001
  // ../server/src/utils/directory-listing.ts
33775
34002
  import { readdir as readdir3, stat as stat4, realpath as realpath2 } from "node:fs/promises";
33776
34003
  import { homedir as homedir3 } from "node:os";
33777
- import path16 from "node:path";
34004
+ import path17 from "node:path";
33778
34005
  var DEFAULT_LIMIT2 = 500;
33779
34006
  async function listDirectoryContents(options) {
33780
34007
  const includeFiles = options.includeFiles ?? false;
@@ -33785,7 +34012,7 @@ async function listDirectoryContents(options) {
33785
34012
  const collected = [];
33786
34013
  for (const dirent of dirents) {
33787
34014
  if (!includeHidden && dirent.name.startsWith(".")) continue;
33788
- const childPath = path16.join(resolvedPath, dirent.name);
34015
+ const childPath = path17.join(resolvedPath, dirent.name);
33789
34016
  const kind = await classifyEntry(dirent, childPath);
33790
34017
  if (!kind) continue;
33791
34018
  if (kind === "file" && !includeFiles) continue;
@@ -33793,7 +34020,7 @@ async function listDirectoryContents(options) {
33793
34020
  if (collected.length >= limit) break;
33794
34021
  }
33795
34022
  collected.sort(compareEntries);
33796
- const parent = path16.dirname(resolvedPath);
34023
+ const parent = path17.dirname(resolvedPath);
33797
34024
  return {
33798
34025
  path: resolvedPath,
33799
34026
  parent: parent === resolvedPath ? null : parent,
@@ -33804,12 +34031,12 @@ async function resolveAbsolutePath(rawPath) {
33804
34031
  const home = process.env.HOME ?? homedir3();
33805
34032
  const trimmed = rawPath.trim();
33806
34033
  if (trimmed === "" || trimmed === "~") {
33807
- return path16.resolve(home);
34034
+ return path17.resolve(home);
33808
34035
  }
33809
34036
  if (trimmed.startsWith("~/")) {
33810
- return path16.resolve(home, trimmed.slice(2));
34037
+ return path17.resolve(home, trimmed.slice(2));
33811
34038
  }
33812
- if (!path16.isAbsolute(trimmed)) {
34039
+ if (!path17.isAbsolute(trimmed)) {
33813
34040
  throw new Error(
33814
34041
  `list_directory requires an absolute path, an empty string, or a "~"-prefixed path; got ${JSON.stringify(rawPath)}`
33815
34042
  );
@@ -33817,7 +34044,7 @@ async function resolveAbsolutePath(rawPath) {
33817
34044
  try {
33818
34045
  return await realpath2(trimmed);
33819
34046
  } catch {
33820
- return path16.resolve(trimmed);
34047
+ return path17.resolve(trimmed);
33821
34048
  }
33822
34049
  }
33823
34050
  async function classifyEntry(dirent, fullPath) {
@@ -33857,10 +34084,10 @@ function resolveClientMessageId(clientMessageId, generateId = uuidv45) {
33857
34084
  }
33858
34085
 
33859
34086
  // ../server/src/server/chat/chat-service.ts
33860
- import { z as z33 } from "zod";
33861
- var ChatStorePayloadSchema = z33.object({
33862
- rooms: z33.array(ChatRoomSchema),
33863
- messages: z33.array(ChatMessageSchema)
34087
+ import { z as z35 } from "zod";
34088
+ var ChatStorePayloadSchema = z35.object({
34089
+ rooms: z35.array(ChatRoomSchema),
34090
+ messages: z35.array(ChatMessageSchema)
33864
34091
  });
33865
34092
  var ChatServiceError = class extends Error {
33866
34093
  constructor(code, message) {
@@ -33951,33 +34178,33 @@ function buildChatMentionNotification(input) {
33951
34178
 
33952
34179
  // ../server/src/server/roles/scanner.ts
33953
34180
  import fs10 from "node:fs/promises";
33954
- import os7 from "node:os";
33955
- import path17 from "node:path";
34181
+ import os8 from "node:os";
34182
+ import path18 from "node:path";
33956
34183
  var NAME_REGEX2 = /^[a-z0-9][a-z0-9._-]*$/i;
33957
34184
  function homeDir2() {
33958
- return process.env.HOME || os7.homedir();
34185
+ return process.env.HOME || os8.homedir();
33959
34186
  }
33960
34187
  function resolveScopeDir2(scope, workspaceRoot) {
33961
34188
  if (scope === "project") {
33962
34189
  if (!workspaceRoot) {
33963
34190
  throw new Error('workspaceRoot is required for scope "project"');
33964
34191
  }
33965
- return path17.join(workspaceRoot, ".roles");
34192
+ return path18.join(workspaceRoot, ".roles");
33966
34193
  }
33967
- return path17.join(homeDir2(), ".appostle", ".roles");
34194
+ return path18.join(homeDir2(), ".appostle", ".roles");
33968
34195
  }
33969
34196
  function allowedRoots2(workspaceRoot) {
33970
- const roots = [path17.join(homeDir2(), ".appostle", ".roles")];
34197
+ const roots = [path18.join(homeDir2(), ".appostle", ".roles")];
33971
34198
  if (workspaceRoot) {
33972
- roots.push(path17.join(workspaceRoot, ".roles"));
34199
+ roots.push(path18.join(workspaceRoot, ".roles"));
33973
34200
  }
33974
- return roots.map((r) => path17.resolve(r));
34201
+ return roots.map((r) => path18.resolve(r));
33975
34202
  }
33976
34203
  function isInsideAllowedRoot2(absPath, workspaceRoot) {
33977
- const resolved = path17.resolve(absPath);
34204
+ const resolved = path18.resolve(absPath);
33978
34205
  for (const root of allowedRoots2(workspaceRoot)) {
33979
- const rel = path17.relative(root, resolved);
33980
- if (rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel)) {
34206
+ const rel = path18.relative(root, resolved);
34207
+ if (rel === "" || !rel.startsWith("..") && !path18.isAbsolute(rel)) {
33981
34208
  return true;
33982
34209
  }
33983
34210
  }
@@ -34175,7 +34402,7 @@ async function readRolesFromDir(scope, dir, category) {
34175
34402
  if (!entry.name.endsWith(".md")) continue;
34176
34403
  const name = entry.name.slice(0, -".md".length);
34177
34404
  if (!name || !NAME_REGEX2.test(name)) continue;
34178
- const fullPath = path17.join(dir, entry.name);
34405
+ const fullPath = path18.join(dir, entry.name);
34179
34406
  let stat5;
34180
34407
  try {
34181
34408
  const s = await fs10.stat(fullPath);
@@ -34225,7 +34452,7 @@ async function readRolesFromScopeDir(scope, scopeDir) {
34225
34452
  }
34226
34453
  const flat = await readRolesFromDir(scope, scopeDir, null);
34227
34454
  const categoryResults = await Promise.all(
34228
- topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path17.join(scopeDir, e.name), e.name))
34455
+ topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path18.join(scopeDir, e.name), e.name))
34229
34456
  );
34230
34457
  const all = [...flat, ...categoryResults.flat()];
34231
34458
  all.sort((a, b) => {
@@ -34265,9 +34492,9 @@ async function createRole(args) {
34265
34492
  throw new Error(`Role name must not contain path separators or "..".`);
34266
34493
  }
34267
34494
  const scopeDir = resolveScopeDir2(args.scope, args.workspaceRoot);
34268
- const dir = args.category ? path17.join(scopeDir, args.category) : scopeDir;
34495
+ const dir = args.category ? path18.join(scopeDir, args.category) : scopeDir;
34269
34496
  await fs10.mkdir(dir, { recursive: true });
34270
- const filePath = path17.join(dir, `${args.name}.md`);
34497
+ const filePath = path18.join(dir, `${args.name}.md`);
34271
34498
  try {
34272
34499
  await fs10.access(filePath);
34273
34500
  throw new Error(`A role named "${args.name}" already exists at ${filePath}`);
@@ -34301,7 +34528,7 @@ the role is invoked.
34301
34528
  `;
34302
34529
  }
34303
34530
  async function writeRoleFrontmatter(args, workspaceRoot) {
34304
- if (!path17.isAbsolute(args.path)) {
34531
+ if (!path18.isAbsolute(args.path)) {
34305
34532
  throw new Error(`writeRoleFrontmatter expects an absolute path; got "${args.path}"`);
34306
34533
  }
34307
34534
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
@@ -34325,20 +34552,20 @@ ${original}`;
34325
34552
  await fs10.writeFile(args.path, nextContent, "utf8");
34326
34553
  }
34327
34554
  async function moveRole(args, workspaceRoot) {
34328
- if (!path17.isAbsolute(args.path)) {
34555
+ if (!path18.isAbsolute(args.path)) {
34329
34556
  throw new Error(`moveRole expects an absolute path; got "${args.path}"`);
34330
34557
  }
34331
34558
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
34332
34559
  throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
34333
34560
  }
34334
- const oldDir = path17.dirname(args.path);
34335
- const oldFilename = path17.basename(args.path, ".md");
34561
+ const oldDir = path18.dirname(args.path);
34562
+ const oldFilename = path18.basename(args.path, ".md");
34336
34563
  const roots = allowedRoots2(workspaceRoot);
34337
- const rolesRoot = roots.find((r) => path17.resolve(args.path).startsWith(r));
34564
+ const rolesRoot = roots.find((r) => path18.resolve(args.path).startsWith(r));
34338
34565
  if (!rolesRoot) {
34339
34566
  throw new Error(`Cannot determine roles root for "${args.path}"`);
34340
34567
  }
34341
- const relFromRoot = path17.relative(rolesRoot, path17.dirname(args.path));
34568
+ const relFromRoot = path18.relative(rolesRoot, path18.dirname(args.path));
34342
34569
  const currentCategory = relFromRoot && relFromRoot !== "." ? relFromRoot : "";
34343
34570
  const newName = args.newName ?? oldFilename;
34344
34571
  const newCategory = args.newCategory !== void 0 ? args.newCategory : currentCategory;
@@ -34348,9 +34575,9 @@ async function moveRole(args, workspaceRoot) {
34348
34575
  if (newCategory && !NAME_REGEX2.test(newCategory)) {
34349
34576
  throw new Error(`Invalid category name: "${newCategory}"`);
34350
34577
  }
34351
- const newDir = newCategory ? path17.join(rolesRoot, newCategory) : rolesRoot;
34352
- const newPath = path17.join(newDir, `${newName}.md`);
34353
- if (path17.resolve(newPath) === path17.resolve(args.path)) {
34578
+ const newDir = newCategory ? path18.join(rolesRoot, newCategory) : rolesRoot;
34579
+ const newPath = path18.join(newDir, `${newName}.md`);
34580
+ if (path18.resolve(newPath) === path18.resolve(args.path)) {
34354
34581
  return { path: args.path };
34355
34582
  }
34356
34583
  await fs10.mkdir(newDir, { recursive: true });
@@ -34388,17 +34615,17 @@ async function moveRole(args, workspaceRoot) {
34388
34615
 
34389
34616
  // ../server/src/server/brands/scanner.ts
34390
34617
  import fs11 from "node:fs/promises";
34391
- import path18 from "node:path";
34618
+ import path19 from "node:path";
34392
34619
  var CATEGORY_REGEX = /^[a-z0-9][a-z0-9_-]*$/i;
34393
34620
  function resolveAssetsDir(workspaceRoot, category) {
34394
- const base = path18.join(workspaceRoot, ".appostle", "brand", "assets");
34621
+ const base = path19.join(workspaceRoot, ".appostle", "brand", "assets");
34395
34622
  if (!category) return base;
34396
34623
  if (!CATEGORY_REGEX.test(category) || category.includes("..")) {
34397
34624
  throw new Error(
34398
34625
  `Invalid asset category "${category}". Use letters, digits, dot, underscore, dash.`
34399
34626
  );
34400
34627
  }
34401
- return path18.join(base, category);
34628
+ return path19.join(base, category);
34402
34629
  }
34403
34630
  function relativePathFor(category, fileName) {
34404
34631
  return category ? `assets/${category}/${fileName}` : `assets/${fileName}`;
@@ -34414,9 +34641,9 @@ async function removeSiblingExtensions(dir, targetName, keepFileName) {
34414
34641
  if (entry === keepFileName) continue;
34415
34642
  if (!entry.startsWith(`${targetName}.`)) continue;
34416
34643
  try {
34417
- const stat5 = await fs11.stat(path18.join(dir, entry));
34644
+ const stat5 = await fs11.stat(path19.join(dir, entry));
34418
34645
  if (!stat5.isFile()) continue;
34419
- await fs11.unlink(path18.join(dir, entry));
34646
+ await fs11.unlink(path19.join(dir, entry));
34420
34647
  } catch {
34421
34648
  }
34422
34649
  }
@@ -34427,7 +34654,7 @@ function convertSvgToMono(svg, color) {
34427
34654
  async function runDerivations(options) {
34428
34655
  const { primaryAbsolutePath, assetsDir, category, derive } = options;
34429
34656
  if (derive.length === 0) return [];
34430
- const ext = path18.extname(primaryAbsolutePath).toLowerCase();
34657
+ const ext = path19.extname(primaryAbsolutePath).toLowerCase();
34431
34658
  const isSvg = ext === ".svg";
34432
34659
  const results = [];
34433
34660
  for (const spec of derive) {
@@ -34462,9 +34689,9 @@ async function runDerivations(options) {
34462
34689
  const sourceText = await fs11.readFile(primaryAbsolutePath, "utf8");
34463
34690
  const monoText = convertSvgToMono(sourceText, spec.color);
34464
34691
  const fileName = `${spec.targetName}${ext}`;
34465
- const destAbs = path18.resolve(assetsDir, fileName);
34466
- const rel = path18.relative(path18.resolve(assetsDir), destAbs);
34467
- if (rel.startsWith("..") || path18.isAbsolute(rel)) {
34692
+ const destAbs = path19.resolve(assetsDir, fileName);
34693
+ const rel = path19.relative(path19.resolve(assetsDir), destAbs);
34694
+ if (rel.startsWith("..") || path19.isAbsolute(rel)) {
34468
34695
  results.push({
34469
34696
  targetName: spec.targetName,
34470
34697
  relativePath: "",
@@ -34498,22 +34725,22 @@ function resolveScopeDir3(scope, workspaceRoot) {
34498
34725
  if (!workspaceRoot) {
34499
34726
  throw new Error('workspaceRoot is required for scope "project"');
34500
34727
  }
34501
- return path18.join(workspaceRoot, ".appostle", "brand");
34728
+ return path19.join(workspaceRoot, ".appostle", "brand");
34502
34729
  }
34503
34730
  throw new Error(`Unknown scope: ${scope}`);
34504
34731
  }
34505
34732
  function allowedRoots3(workspaceRoot) {
34506
34733
  const roots = [];
34507
34734
  if (workspaceRoot) {
34508
- roots.push(path18.join(workspaceRoot, ".appostle", "brand"));
34735
+ roots.push(path19.join(workspaceRoot, ".appostle", "brand"));
34509
34736
  }
34510
- return roots.map((r) => path18.resolve(r));
34737
+ return roots.map((r) => path19.resolve(r));
34511
34738
  }
34512
34739
  function isInsideAllowedRoot3(absPath, workspaceRoot) {
34513
- const resolved = path18.resolve(absPath);
34740
+ const resolved = path19.resolve(absPath);
34514
34741
  for (const root of allowedRoots3(workspaceRoot)) {
34515
- const rel = path18.relative(root, resolved);
34516
- if (rel === "" || !rel.startsWith("..") && !path18.isAbsolute(rel)) {
34742
+ const rel = path19.relative(root, resolved);
34743
+ if (rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel)) {
34517
34744
  return true;
34518
34745
  }
34519
34746
  }
@@ -34727,7 +34954,7 @@ async function readBrandsFromDir(scope, dir) {
34727
34954
  if (!entry.name.endsWith(".md")) continue;
34728
34955
  const name = entry.name.slice(0, -".md".length);
34729
34956
  if (!name || !NAME_REGEX3.test(name)) continue;
34730
- const fullPath = path18.join(dir, entry.name);
34957
+ const fullPath = path19.join(dir, entry.name);
34731
34958
  let stat5;
34732
34959
  try {
34733
34960
  const s = await fs11.stat(fullPath);
@@ -34771,7 +34998,7 @@ async function createBrand(args) {
34771
34998
  }
34772
34999
  const dir = resolveScopeDir3("project", args.workspaceRoot);
34773
35000
  await fs11.mkdir(dir, { recursive: true });
34774
- const filePath = path18.join(dir, `${args.name}.md`);
35001
+ const filePath = path19.join(dir, `${args.name}.md`);
34775
35002
  try {
34776
35003
  await fs11.access(filePath);
34777
35004
  throw new Error(`A brand named "${args.name}" already exists at ${filePath}`);
@@ -34827,7 +35054,7 @@ async function copyBrandAsset(args) {
34827
35054
  if (!args.workspaceRoot) {
34828
35055
  throw new Error("workspaceRoot is required to copy a brand asset");
34829
35056
  }
34830
- if (!args.sourcePath || !path18.isAbsolute(args.sourcePath)) {
35057
+ if (!args.sourcePath || !path19.isAbsolute(args.sourcePath)) {
34831
35058
  throw new Error(`copyBrandAsset expects an absolute sourcePath; got "${args.sourcePath}"`);
34832
35059
  }
34833
35060
  if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
@@ -34839,12 +35066,12 @@ async function copyBrandAsset(args) {
34839
35066
  if (!stats.isFile()) {
34840
35067
  throw new Error(`Source path is not a regular file: ${args.sourcePath}`);
34841
35068
  }
34842
- const ext = path18.extname(args.sourcePath).toLowerCase();
35069
+ const ext = path19.extname(args.sourcePath).toLowerCase();
34843
35070
  const fileName = `${args.targetName}${ext}`;
34844
35071
  const assetsDir = resolveAssetsDir(args.workspaceRoot, args.category);
34845
- const destAbs = path18.resolve(assetsDir, fileName);
34846
- const rel = path18.relative(path18.resolve(assetsDir), destAbs);
34847
- if (rel.startsWith("..") || path18.isAbsolute(rel)) {
35072
+ const destAbs = path19.resolve(assetsDir, fileName);
35073
+ const rel = path19.relative(path19.resolve(assetsDir), destAbs);
35074
+ if (rel.startsWith("..") || path19.isAbsolute(rel)) {
34848
35075
  throw new Error(`Refusing to write outside of .appostle/brand/assets: ${destAbs}`);
34849
35076
  }
34850
35077
  await fs11.mkdir(assetsDir, { recursive: true });
@@ -34874,13 +35101,13 @@ async function uploadBrandAsset(args) {
34874
35101
  if (!args.dataBase64 || args.dataBase64.trim().length === 0) {
34875
35102
  throw new Error("No file data provided for brand asset upload");
34876
35103
  }
34877
- const extFromSource = args.sourceName ? path18.extname(args.sourceName).toLowerCase() : "";
35104
+ const extFromSource = args.sourceName ? path19.extname(args.sourceName).toLowerCase() : "";
34878
35105
  const ext = extFromSource || ".png";
34879
35106
  const fileName = `${args.targetName}${ext}`;
34880
35107
  const assetsDir = resolveAssetsDir(args.workspaceRoot, args.category);
34881
- const destAbs = path18.resolve(assetsDir, fileName);
34882
- const rel = path18.relative(path18.resolve(assetsDir), destAbs);
34883
- if (rel.startsWith("..") || path18.isAbsolute(rel)) {
35108
+ const destAbs = path19.resolve(assetsDir, fileName);
35109
+ const rel = path19.relative(path19.resolve(assetsDir), destAbs);
35110
+ if (rel.startsWith("..") || path19.isAbsolute(rel)) {
34884
35111
  throw new Error(`Refusing to write outside of .appostle/brand/assets: ${destAbs}`);
34885
35112
  }
34886
35113
  let data;
@@ -34908,7 +35135,7 @@ async function uploadBrandAsset(args) {
34908
35135
  };
34909
35136
  }
34910
35137
  async function writeBrandFrontmatter(args, workspaceRoot) {
34911
- if (!path18.isAbsolute(args.path)) {
35138
+ if (!path19.isAbsolute(args.path)) {
34912
35139
  throw new Error(`writeBrandFrontmatter expects an absolute path; got "${args.path}"`);
34913
35140
  }
34914
35141
  if (!isInsideAllowedRoot3(args.path, workspaceRoot)) {
@@ -34933,10 +35160,10 @@ ${original}`;
34933
35160
  }
34934
35161
 
34935
35162
  // ../server/src/server/brand/token-generator.ts
34936
- import { z as z34 } from "zod";
35163
+ import { z as z36 } from "zod";
34937
35164
  var HEX6 = /^#[0-9a-f]{6}$/i;
34938
- var TokensResponseSchema = z34.object({
34939
- tokens: z34.record(z34.string(), z34.string())
35165
+ var TokensResponseSchema = z36.object({
35166
+ tokens: z36.record(z36.string(), z36.string())
34940
35167
  });
34941
35168
  function buildPrompt2(args) {
34942
35169
  const baseColours = args.paletteVars.filter((v) => v.type === "color");
@@ -35154,21 +35381,21 @@ async function generateAndApplyBrandTokens(options) {
35154
35381
 
35155
35382
  // ../server/src/server/brand/art-direction-generator.ts
35156
35383
  import { promises as fs12 } from "node:fs";
35157
- import path19 from "node:path";
35384
+ import path20 from "node:path";
35158
35385
  import { fileURLToPath as fileURLToPath2 } from "node:url";
35159
- import { z as z35 } from "zod";
35386
+ import { z as z37 } from "zod";
35160
35387
  var PROMPT_FILENAME = "art-direction-prompt.md";
35161
35388
  var MAX_LOOKUP_LEVELS = 10;
35162
35389
  async function findPromptFile() {
35163
- let dir = path19.dirname(fileURLToPath2(import.meta.url));
35390
+ let dir = path20.dirname(fileURLToPath2(import.meta.url));
35164
35391
  for (let i = 0; i < MAX_LOOKUP_LEVELS; i++) {
35165
- const candidate = path19.join(dir, PROMPT_FILENAME);
35392
+ const candidate = path20.join(dir, PROMPT_FILENAME);
35166
35393
  try {
35167
35394
  await fs12.access(candidate);
35168
35395
  return candidate;
35169
35396
  } catch {
35170
35397
  }
35171
- const parent = path19.dirname(dir);
35398
+ const parent = path20.dirname(dir);
35172
35399
  if (parent === dir) break;
35173
35400
  dir = parent;
35174
35401
  }
@@ -35201,8 +35428,8 @@ var EMBEDDED_PROMPT_FALLBACK = [
35201
35428
  "",
35202
35429
  'Return ONLY JSON: { "fields": { "art-direction.intent": "...", ... } }'
35203
35430
  ].join("\n");
35204
- var ArtDirectionResponseSchema = z35.object({
35205
- fields: z35.record(z35.string(), z35.string())
35431
+ var ArtDirectionResponseSchema = z37.object({
35432
+ fields: z37.record(z37.string(), z37.string())
35206
35433
  });
35207
35434
  var KNOWN_KEYS = /* @__PURE__ */ new Set([
35208
35435
  "art-direction.intent",
@@ -35602,57 +35829,57 @@ async function fetchGitLabUsername(fetchImpl, accessToken) {
35602
35829
  return null;
35603
35830
  }
35604
35831
  }
35605
- async function readCredential(path28, log2) {
35832
+ async function readCredential(path29, log2) {
35606
35833
  try {
35607
- const raw = await readFile3(path28, "utf8");
35834
+ const raw = await readFile3(path29, "utf8");
35608
35835
  const parsed = JSON.parse(raw);
35609
35836
  return parsed.gitlab ?? null;
35610
35837
  } catch (error) {
35611
35838
  if (isNotFound(error)) return null;
35612
- log2.warn({ err: error, path: path28 }, "oauth.credentials.read_failed");
35839
+ log2.warn({ err: error, path: path29 }, "oauth.credentials.read_failed");
35613
35840
  return null;
35614
35841
  }
35615
35842
  }
35616
- async function persistCredential(path28, credential, log2) {
35617
- await mkdir4(dirname4(path28), { recursive: true });
35843
+ async function persistCredential(path29, credential, log2) {
35844
+ await mkdir4(dirname4(path29), { recursive: true });
35618
35845
  let current = {};
35619
35846
  try {
35620
- const raw = await readFile3(path28, "utf8");
35847
+ const raw = await readFile3(path29, "utf8");
35621
35848
  current = JSON.parse(raw);
35622
35849
  } catch (error) {
35623
35850
  if (!isNotFound(error)) {
35624
- log2.warn({ err: error, path: path28 }, "oauth.credentials.read_failed_overwriting");
35851
+ log2.warn({ err: error, path: path29 }, "oauth.credentials.read_failed_overwriting");
35625
35852
  }
35626
35853
  }
35627
35854
  const next = { ...current, gitlab: credential };
35628
- const tmpPath = `${path28}.tmp-${process.pid}-${Date.now()}`;
35855
+ const tmpPath = `${path29}.tmp-${process.pid}-${Date.now()}`;
35629
35856
  await writeFile4(tmpPath, JSON.stringify(next, null, 2), { mode: 384 });
35630
- await rename(tmpPath, path28);
35857
+ await rename(tmpPath, path29);
35631
35858
  }
35632
- async function deleteCredential(path28, log2) {
35859
+ async function deleteCredential(path29, log2) {
35633
35860
  let current = {};
35634
35861
  try {
35635
- const raw = await readFile3(path28, "utf8");
35862
+ const raw = await readFile3(path29, "utf8");
35636
35863
  current = JSON.parse(raw);
35637
35864
  } catch (error) {
35638
35865
  if (isNotFound(error)) return;
35639
- log2.warn({ err: error, path: path28 }, "oauth.credentials.delete_read_failed");
35866
+ log2.warn({ err: error, path: path29 }, "oauth.credentials.delete_read_failed");
35640
35867
  return;
35641
35868
  }
35642
35869
  delete current.gitlab;
35643
35870
  if (Object.keys(current).length === 0) {
35644
35871
  try {
35645
- await unlink(path28);
35872
+ await unlink(path29);
35646
35873
  } catch (error) {
35647
35874
  if (!isNotFound(error)) {
35648
- log2.warn({ err: error, path: path28 }, "oauth.credentials.unlink_failed");
35875
+ log2.warn({ err: error, path: path29 }, "oauth.credentials.unlink_failed");
35649
35876
  }
35650
35877
  }
35651
35878
  return;
35652
35879
  }
35653
- const tmpPath = `${path28}.tmp-${process.pid}-${Date.now()}`;
35880
+ const tmpPath = `${path29}.tmp-${process.pid}-${Date.now()}`;
35654
35881
  await writeFile4(tmpPath, JSON.stringify(current, null, 2), { mode: 384 });
35655
- await rename(tmpPath, path28);
35882
+ await rename(tmpPath, path29);
35656
35883
  }
35657
35884
  function defaultGlabConfigPath() {
35658
35885
  const home = homedir4();
@@ -35667,15 +35894,15 @@ function defaultGlabConfigPath() {
35667
35894
  const xdg = process.env.XDG_CONFIG_HOME;
35668
35895
  return join11(xdg && xdg.length > 0 ? xdg : join11(home, ".config"), "glab-cli", "config.yml");
35669
35896
  }
35670
- async function writeGlabConfig(path28, credential, log2) {
35671
- await mkdir4(dirname4(path28), { recursive: true });
35897
+ async function writeGlabConfig(path29, credential, log2) {
35898
+ await mkdir4(dirname4(path29), { recursive: true });
35672
35899
  let doc;
35673
35900
  try {
35674
- const raw = await readFile3(path28, "utf8");
35901
+ const raw = await readFile3(path29, "utf8");
35675
35902
  doc = YAML.parseDocument(raw);
35676
35903
  if (doc.errors.length > 0) {
35677
35904
  log2.warn(
35678
- { errors: doc.errors.map((e) => e.message), path: path28 },
35905
+ { errors: doc.errors.map((e) => e.message), path: path29 },
35679
35906
  "oauth.glab.parse_errors_replacing"
35680
35907
  );
35681
35908
  doc = YAML.parseDocument("{}");
@@ -35684,7 +35911,7 @@ async function writeGlabConfig(path28, credential, log2) {
35684
35911
  if (isNotFound(error)) {
35685
35912
  doc = YAML.parseDocument("{}");
35686
35913
  } else {
35687
- log2.warn({ err: error, path: path28 }, "oauth.glab.read_failed_replacing");
35914
+ log2.warn({ err: error, path: path29 }, "oauth.glab.read_failed_replacing");
35688
35915
  doc = YAML.parseDocument("{}");
35689
35916
  }
35690
35917
  }
@@ -35697,18 +35924,18 @@ async function writeGlabConfig(path28, credential, log2) {
35697
35924
  if (credential.username) {
35698
35925
  hostEntry.set("user", credential.username);
35699
35926
  }
35700
- const tmpPath = `${path28}.tmp-${process.pid}-${Date.now()}`;
35927
+ const tmpPath = `${path29}.tmp-${process.pid}-${Date.now()}`;
35701
35928
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
35702
- await rename(tmpPath, path28);
35929
+ await rename(tmpPath, path29);
35703
35930
  }
35704
- async function removeGlabHost(path28, log2) {
35931
+ async function removeGlabHost(path29, log2) {
35705
35932
  let doc;
35706
35933
  try {
35707
- const raw = await readFile3(path28, "utf8");
35934
+ const raw = await readFile3(path29, "utf8");
35708
35935
  doc = YAML.parseDocument(raw);
35709
35936
  } catch (error) {
35710
35937
  if (isNotFound(error)) return;
35711
- log2.warn({ err: error, path: path28 }, "oauth.glab.remove_read_failed");
35938
+ log2.warn({ err: error, path: path29 }, "oauth.glab.remove_read_failed");
35712
35939
  return;
35713
35940
  }
35714
35941
  const hosts = doc.get("hosts");
@@ -35717,9 +35944,9 @@ async function removeGlabHost(path28, log2) {
35717
35944
  if (hosts.items.length === 0) {
35718
35945
  doc.delete("hosts");
35719
35946
  }
35720
- const tmpPath = `${path28}.tmp-${process.pid}-${Date.now()}`;
35947
+ const tmpPath = `${path29}.tmp-${process.pid}-${Date.now()}`;
35721
35948
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
35722
- await rename(tmpPath, path28);
35949
+ await rename(tmpPath, path29);
35723
35950
  }
35724
35951
  function ensureMap(doc, key) {
35725
35952
  const existing = doc.get(key);
@@ -36748,7 +36975,7 @@ var MIN_STREAMING_SEGMENT_DURATION_MS = 1e3;
36748
36975
  var MIN_STREAMING_SEGMENT_BYTES = Math.round(
36749
36976
  PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS
36750
36977
  );
36751
- var AgentIdSchema = z36.string().uuid();
36978
+ var AgentIdSchema2 = z38.string().uuid();
36752
36979
  var VOICE_INTERRUPT_CONFIRMATION_MS = 500;
36753
36980
  var VoiceFeatureUnavailableError = class extends Error {
36754
36981
  constructor(context) {
@@ -36835,6 +37062,8 @@ var Session = class _Session {
36835
37062
  this.nextTerminalSlot = 0;
36836
37063
  this.inflightRequests = 0;
36837
37064
  this.peakInflightRequests = 0;
37065
+ /** In-progress `claude login` processes, keyed by requestId. */
37066
+ this.pendingClaudeLogins = /* @__PURE__ */ new Map();
36838
37067
  this.checkoutDiffSubscriptions = /* @__PURE__ */ new Map();
36839
37068
  this.workspaceGitWatchTargets = /* @__PURE__ */ new Map();
36840
37069
  this.workspaceSetupSnapshots = /* @__PURE__ */ new Map();
@@ -36852,6 +37081,8 @@ var Session = class _Session {
36852
37081
  const {
36853
37082
  clientId,
36854
37083
  appVersion,
37084
+ peerPublicKeyB64,
37085
+ ownerUserId,
36855
37086
  onMessage,
36856
37087
  onBinaryMessage,
36857
37088
  onLifecycleIntent,
@@ -36896,6 +37127,8 @@ var Session = class _Session {
36896
37127
  this.clientId = clientId;
36897
37128
  this.appVersion = appVersion ?? null;
36898
37129
  this.sessionId = uuidv47();
37130
+ this.userId = options.userId ?? null;
37131
+ this.username = options.username ?? null;
36899
37132
  this.onMessage = onMessage;
36900
37133
  this.onBinaryMessage = onBinaryMessage ?? null;
36901
37134
  this.onLifecycleIntent = onLifecycleIntent ?? null;
@@ -36912,6 +37145,8 @@ var Session = class _Session {
36912
37145
  });
36913
37146
  this.agentManager = agentManager;
36914
37147
  this.agentStorage = agentStorage;
37148
+ this.peerPublicKeyB64 = peerPublicKeyB64 ?? null;
37149
+ this.ownerUserId = ownerUserId ?? null;
36915
37150
  this.uploadStore = new SessionUploadStore({ logger: this.sessionLogger });
36916
37151
  this.imageStore = new SessionImageStore({ logger: this.sessionLogger });
36917
37152
  this.projectRegistry = projectRegistry;
@@ -36986,7 +37221,43 @@ var Session = class _Session {
36986
37221
  });
36987
37222
  void this.initializeAgentMcp();
36988
37223
  this.subscribeToAgentEvents();
36989
- this.sessionLogger.trace("Session created");
37224
+ this.sessionLogger.trace(
37225
+ {
37226
+ hasPeerPubkey: this.peerPublicKeyB64 !== null,
37227
+ // Log a fingerprint, not the full key, so the audit log stays useful
37228
+ // without bloating every entry with 44-char base64. First 8 chars is
37229
+ // unique enough for human cross-reference.
37230
+ peerPubkeyPrefix: this.peerPublicKeyB64 ? this.peerPublicKeyB64.slice(0, 8) : null,
37231
+ ownerUserId: this.ownerUserId
37232
+ },
37233
+ "Session created"
37234
+ );
37235
+ }
37236
+ /**
37237
+ * Peer device's e2ee pubkey (base64). Surfaced so the upcoming user-scope
37238
+ * lookup can resolve it via the auth-server allow-list. Null when unknown
37239
+ * (local TCP, legacy paths).
37240
+ */
37241
+ getPeerPublicKeyB64() {
37242
+ return this.peerPublicKeyB64;
37243
+ }
37244
+ /**
37245
+ * Auth-server user-id that owns the connecting device. Null when the
37246
+ * daemon has no auth-server linkage, when the peer pubkey is unknown
37247
+ * (local TCP), or when the resolver returned no match (allow-list entry
37248
+ * for this pubkey hasn't been seen yet). Callers use this for per-user
37249
+ * agent filtering; null means "show everything" (single-tenant fallback).
37250
+ */
37251
+ getOwnerUserId() {
37252
+ return this.ownerUserId;
37253
+ }
37254
+ /**
37255
+ * Whether this session is bound to a specific account. Equivalent to
37256
+ * `getOwnerUserId() !== null` but reads cleaner at call sites that gate
37257
+ * on "should we apply per-user filtering at all?".
37258
+ */
37259
+ hasOwnerUserId() {
37260
+ return this.ownerUserId !== null;
36990
37261
  }
36991
37262
  updateAppVersion(appVersion) {
36992
37263
  if (appVersion && appVersion !== this.appVersion) {
@@ -37231,6 +37502,9 @@ var Session = class _Session {
37231
37502
  );
37232
37503
  });
37233
37504
  }
37505
+ if (this.ownerUserId !== null && !this.agentManager.canUserAccessAgentById(event.agentId, this.ownerUserId)) {
37506
+ return;
37507
+ }
37234
37508
  const activity = this.clientActivity;
37235
37509
  if (activity?.deviceType === "mobile") {
37236
37510
  if (!activity.focusedAgentId) {
@@ -37458,6 +37732,9 @@ var Session = class _Session {
37458
37732
  }
37459
37733
  async forwardAgentUpdate(agent) {
37460
37734
  try {
37735
+ if (this.ownerUserId !== null && !this.agentManager.canUserAccessAgentById(agent.id, this.ownerUserId)) {
37736
+ return;
37737
+ }
37461
37738
  const subscription = this.agentUpdatesSubscription;
37462
37739
  const payload = await this.buildAgentPayload(agent);
37463
37740
  if (subscription) {
@@ -37515,6 +37792,18 @@ var Session = class _Session {
37515
37792
  case "link_account_request":
37516
37793
  await this.handleLinkAccountRequest(msg);
37517
37794
  break;
37795
+ case "claude_profile_login_request":
37796
+ await this.handleClaudeProfileLoginRequest(msg);
37797
+ break;
37798
+ case "claude_profile_login_callback":
37799
+ await this.handleClaudeProfileLoginCallback(msg);
37800
+ break;
37801
+ case "claude_profile_status_request":
37802
+ await this.handleClaudeProfileStatusRequest(msg);
37803
+ break;
37804
+ case "claude_profile_remove_request":
37805
+ await this.handleClaudeProfileRemoveRequest(msg);
37806
+ break;
37518
37807
  case "fetch_agents_request":
37519
37808
  await this.handleFetchAgents(msg);
37520
37809
  break;
@@ -37560,6 +37849,15 @@ var Session = class _Session {
37560
37849
  case "delete_session_upload_request":
37561
37850
  await this.handleDeleteSessionUploadRequest(msg);
37562
37851
  break;
37852
+ case "share_agent_with_user_request":
37853
+ await this.handleShareAgentWithUserRequest(msg);
37854
+ break;
37855
+ case "unshare_agent_with_user_request":
37856
+ await this.handleUnshareAgentWithUserRequest(msg);
37857
+ break;
37858
+ case "list_agent_shared_users_request":
37859
+ await this.handleListAgentSharedUsersRequest(msg);
37860
+ break;
37563
37861
  case "list_session_images_request":
37564
37862
  await this.handleListSessionImagesRequest(msg);
37565
37863
  break;
@@ -38221,6 +38519,138 @@ var Session = class _Session {
38221
38519
  respond({ ok: false, error: message });
38222
38520
  }
38223
38521
  }
38522
+ // ---------------------------------------------------------------------------
38523
+ // Claude profile login — per-user subscription on shared hosts
38524
+ // ---------------------------------------------------------------------------
38525
+ async handleClaudeProfileLoginRequest(msg) {
38526
+ const { requestId, username } = msg;
38527
+ const log2 = this.sessionLogger.child({ handler: "claude_profile_login" });
38528
+ try {
38529
+ const profileDir = ensureClaudeProfile(username, log2);
38530
+ const child = spawnProcess("claude", ["auth", "login"], {
38531
+ env: {
38532
+ ...process.env,
38533
+ CLAUDE_CONFIG_DIR: profileDir
38534
+ },
38535
+ stdio: ["pipe", "pipe", "pipe"]
38536
+ });
38537
+ this.pendingClaudeLogins.set(requestId, { process: child, username });
38538
+ let stdoutBuffer = "";
38539
+ child.stdout?.on("data", (chunk) => {
38540
+ stdoutBuffer += chunk.toString();
38541
+ const urlMatch = stdoutBuffer.match(/(https?:\/\/[^\s]+)/);
38542
+ if (urlMatch) {
38543
+ this.emit({
38544
+ type: "claude_profile_login_url",
38545
+ requestId,
38546
+ url: urlMatch[1]
38547
+ });
38548
+ }
38549
+ });
38550
+ child.stderr?.on("data", (chunk) => {
38551
+ log2.debug({ stderr: chunk.toString() }, "claude login stderr");
38552
+ });
38553
+ child.on("close", (code) => {
38554
+ this.pendingClaudeLogins.delete(requestId);
38555
+ if (code === 0) {
38556
+ log2.info({ username }, "claude profile login succeeded");
38557
+ this.emit({
38558
+ type: "claude_profile_login_result",
38559
+ requestId,
38560
+ ok: true
38561
+ });
38562
+ } else {
38563
+ log2.warn({ username, code }, "claude profile login failed");
38564
+ this.emit({
38565
+ type: "claude_profile_login_result",
38566
+ requestId,
38567
+ ok: false,
38568
+ error: `claude login exited with code ${code}`
38569
+ });
38570
+ }
38571
+ });
38572
+ child.on("error", (err) => {
38573
+ this.pendingClaudeLogins.delete(requestId);
38574
+ log2.error({ err }, "claude login process error");
38575
+ this.emit({
38576
+ type: "claude_profile_login_result",
38577
+ requestId,
38578
+ ok: false,
38579
+ error: err.message
38580
+ });
38581
+ });
38582
+ } catch (err) {
38583
+ const message = err instanceof Error ? err.message : "unknown_error";
38584
+ log2.error({ err }, "claude profile login request failed");
38585
+ this.emit({
38586
+ type: "claude_profile_login_result",
38587
+ requestId,
38588
+ ok: false,
38589
+ error: message
38590
+ });
38591
+ }
38592
+ }
38593
+ async handleClaudeProfileLoginCallback(msg) {
38594
+ const { requestId, callbackToken } = msg;
38595
+ const pending = this.pendingClaudeLogins.get(requestId);
38596
+ if (!pending) {
38597
+ this.sessionLogger.warn({ requestId }, "claude_profile_login_callback: no pending login");
38598
+ this.emit({
38599
+ type: "claude_profile_login_result",
38600
+ requestId,
38601
+ ok: false,
38602
+ error: "no_pending_login"
38603
+ });
38604
+ return;
38605
+ }
38606
+ try {
38607
+ pending.process.stdin?.write(callbackToken + "\n");
38608
+ } catch (err) {
38609
+ this.sessionLogger.error({ err }, "failed to write callback token to claude login stdin");
38610
+ }
38611
+ }
38612
+ async handleClaudeProfileStatusRequest(msg) {
38613
+ const { requestId, username } = msg;
38614
+ const profileExists = hasClaudeAuth(username);
38615
+ let isAuthenticated = false;
38616
+ if (profileExists) {
38617
+ try {
38618
+ const profileDir = ensureClaudeProfile(username);
38619
+ const result = await execCommand("claude", ["auth", "status"], {
38620
+ env: { ...process.env, CLAUDE_CONFIG_DIR: profileDir },
38621
+ timeout: 5e3
38622
+ });
38623
+ isAuthenticated = !result.stdout.toLowerCase().includes("not logged in");
38624
+ } catch {
38625
+ }
38626
+ }
38627
+ this.emit({
38628
+ type: "claude_profile_status_response",
38629
+ requestId,
38630
+ hasProfile: profileExists,
38631
+ isAuthenticated
38632
+ });
38633
+ }
38634
+ async handleClaudeProfileRemoveRequest(msg) {
38635
+ const { requestId, username } = msg;
38636
+ try {
38637
+ removeClaudeProfile(username, this.sessionLogger);
38638
+ this.emit({
38639
+ type: "claude_profile_remove_response",
38640
+ requestId,
38641
+ ok: true
38642
+ });
38643
+ } catch (err) {
38644
+ const message = err instanceof Error ? err.message : "unknown_error";
38645
+ this.sessionLogger.error({ err, username }, "claude profile remove failed");
38646
+ this.emit({
38647
+ type: "claude_profile_remove_response",
38648
+ requestId,
38649
+ ok: false,
38650
+ error: message
38651
+ });
38652
+ }
38653
+ }
38224
38654
  async handleRestartServerRequest(requestId, reason) {
38225
38655
  const payload = {
38226
38656
  status: "restart_requested",
@@ -38726,7 +39156,7 @@ var Session = class _Session {
38726
39156
  }
38727
39157
  }
38728
39158
  parseVoiceTargetAgentId(rawId, source) {
38729
- const parsed = AgentIdSchema.safeParse(rawId.trim());
39159
+ const parsed = AgentIdSchema2.safeParse(rawId.trim());
38730
39160
  if (!parsed.success) {
38731
39161
  throw new Error(`${source}: agentId must be a UUID`);
38732
39162
  }
@@ -39054,7 +39484,15 @@ var Session = class _Session {
39054
39484
  {
39055
39485
  labels,
39056
39486
  workspaceId: resolvedWorkspace.workspaceId,
39057
- initialPrompt: trimmedPrompt
39487
+ initialPrompt: trimmedPrompt,
39488
+ // Stamp the agent with the user that created it. Null on single-
39489
+ // tenant daemons (no auth-server linkage) — agent stays globally
39490
+ // visible, preserving legacy behavior. This is the authoritative
39491
+ // owner (pubkey-derived), used for `canAccessAgent`.
39492
+ ownerUserId: this.ownerUserId,
39493
+ // Self-declared identity, used for Claude profile dir naming etc.
39494
+ userId: this.userId ?? void 0,
39495
+ username: this.username ?? void 0
39058
39496
  }
39059
39497
  );
39060
39498
  await this.forwardAgentUpdate(snapshot);
@@ -39533,8 +39971,8 @@ var Session = class _Session {
39533
39971
  }
39534
39972
  async generateCommitMessage(cwd) {
39535
39973
  const files = await listUncommittedFiles(cwd);
39536
- const schema = z36.object({
39537
- message: z36.string().min(1).max(100).describe(
39974
+ const schema = z38.object({
39975
+ message: z38.string().min(1).max(100).describe(
39538
39976
  "Short feature-level summary, lowercase, imperative mood, no trailing period, no filename references."
39539
39977
  )
39540
39978
  });
@@ -39579,9 +40017,9 @@ var Session = class _Session {
39579
40017
  },
39580
40018
  { appostleHome: this.appostleHome }
39581
40019
  );
39582
- const schema = z36.object({
39583
- title: z36.string().min(1).max(72),
39584
- body: z36.string().min(1)
40020
+ const schema = z38.object({
40021
+ title: z38.string().min(1).max(72),
40022
+ body: z38.string().min(1)
39585
40023
  });
39586
40024
  const fileList = diff.structured && diff.structured.length > 0 ? [
39587
40025
  "Files changed:",
@@ -40522,7 +40960,7 @@ var Session = class _Session {
40522
40960
  homeDir: process.env.HOME ?? homedir5(),
40523
40961
  query: query2,
40524
40962
  limit
40525
- })).map((path28) => ({ path: path28, kind: "directory" }));
40963
+ })).map((path29) => ({ path: path29, kind: "directory" }));
40526
40964
  const directories = entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
40527
40965
  this.emit({
40528
40966
  type: "directory_suggestions_response",
@@ -41621,13 +42059,22 @@ ${details}`.trim());
41621
42059
  * Build the current agent list payload (live + persisted), optionally filtered by labels.
41622
42060
  */
41623
42061
  async listAgentPayloads(filter) {
41624
- const agentSnapshots = this.agentManager.listAgents();
42062
+ const agentSnapshots = this.agentManager.listAgentsForUser(this.ownerUserId);
41625
42063
  const liveAgents = await Promise.all(
41626
42064
  agentSnapshots.map((agent) => this.buildAgentPayload(agent))
41627
42065
  );
41628
42066
  const registryRecords = await this.agentStorage.list();
42067
+ const requesterId = this.ownerUserId;
41629
42068
  const liveIds = new Set(agentSnapshots.map((a) => a.id));
41630
- const persistedAgents = registryRecords.filter((record) => !liveIds.has(record.id)).map((record) => this.buildStoredAgentPayload(record));
42069
+ const persistedAgents = registryRecords.filter((record) => !liveIds.has(record.id)).filter(
42070
+ (record) => requesterId === null || canUserAccessAgent(
42071
+ {
42072
+ ownerUserId: record.ownerUserId ?? null,
42073
+ sharedWithUserIds: record.sharedWithUserIds
42074
+ },
42075
+ requesterId
42076
+ )
42077
+ ).map((record) => this.buildStoredAgentPayload(record));
41631
42078
  let agents = [...liveAgents, ...persistedAgents];
41632
42079
  agents = agents.filter((agent) => this.isProviderVisibleToClient(agent.provider));
41633
42080
  if (filter?.labels) {
@@ -41644,6 +42091,9 @@ ${details}`.trim());
41644
42091
  return { ok: false, error: "Agent identifier cannot be empty" };
41645
42092
  }
41646
42093
  if (this.agentManager.getAgent(trimmed)) {
42094
+ if (!this.agentManager.canUserAccessAgentById(trimmed, this.ownerUserId)) {
42095
+ return { ok: false, error: "Agent not found" };
42096
+ }
41647
42097
  return { ok: true, agentId: trimmed };
41648
42098
  }
41649
42099
  const exactStored = await this.agentStorage.get(trimmed);
@@ -43247,6 +43697,139 @@ ${details}`.trim());
43247
43697
  }
43248
43698
  }
43249
43699
  // ──────────────────────────────────────────────────────────────────────
43700
+ // Phase 4: agent sharing — owner-only mutations of the access ACL.
43701
+ // The visibility predicate (Phase 2c) already honors `sharedWithUserIds`;
43702
+ // these handlers surface a typed user-facing path so an owner can grant /
43703
+ // revoke / inspect access from the app instead of editing JSON on disk.
43704
+ //
43705
+ // Authorization:
43706
+ // - All three handlers require the session's `ownerUserId` to equal the
43707
+ // agent's `ownerUserId`. Anyone else gets `unauthorized` (including
43708
+ // existing share-recipients — only the owner can re-share).
43709
+ // - `resolveAgentIdentifier` already applies `canUserAccessAgent`, so a
43710
+ // non-recipient even sees the agent as "not found". The owner check
43711
+ // here is the strictly-tighter follow-up gate for mutations.
43712
+ // - On a single-tenant daemon (session.ownerUserId == null) the gate
43713
+ // opens — no isolation to enforce, sharing is a no-op for unscoped
43714
+ // agents (their ownerUserId is also null).
43715
+ // ──────────────────────────────────────────────────────────────────────
43716
+ async handleShareAgentWithUserRequest(msg) {
43717
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
43718
+ if (!resolved.ok) {
43719
+ this.emit({
43720
+ type: "share_agent_with_user_response",
43721
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
43722
+ });
43723
+ return;
43724
+ }
43725
+ const agent = this.agentManager.getAgent(resolved.agentId);
43726
+ if (!agent) {
43727
+ this.emit({
43728
+ type: "share_agent_with_user_response",
43729
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
43730
+ });
43731
+ return;
43732
+ }
43733
+ if (this.ownerUserId !== null && agent.ownerUserId !== null && agent.ownerUserId !== this.ownerUserId) {
43734
+ this.emit({
43735
+ type: "share_agent_with_user_response",
43736
+ payload: { requestId: msg.requestId, ok: false, error: "unauthorized" }
43737
+ });
43738
+ return;
43739
+ }
43740
+ try {
43741
+ const sharedWithUserIds = await this.agentManager.shareAgentWithUser(
43742
+ resolved.agentId,
43743
+ msg.userId
43744
+ );
43745
+ this.emit({
43746
+ type: "share_agent_with_user_response",
43747
+ payload: { requestId: msg.requestId, ok: true, sharedWithUserIds }
43748
+ });
43749
+ } catch (error) {
43750
+ const message = error instanceof Error ? error.message : String(error);
43751
+ this.emit({
43752
+ type: "share_agent_with_user_response",
43753
+ payload: { requestId: msg.requestId, ok: false, error: message }
43754
+ });
43755
+ }
43756
+ }
43757
+ async handleUnshareAgentWithUserRequest(msg) {
43758
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
43759
+ if (!resolved.ok) {
43760
+ this.emit({
43761
+ type: "unshare_agent_with_user_response",
43762
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
43763
+ });
43764
+ return;
43765
+ }
43766
+ const agent = this.agentManager.getAgent(resolved.agentId);
43767
+ if (!agent) {
43768
+ this.emit({
43769
+ type: "unshare_agent_with_user_response",
43770
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
43771
+ });
43772
+ return;
43773
+ }
43774
+ if (this.ownerUserId !== null && agent.ownerUserId !== null && agent.ownerUserId !== this.ownerUserId) {
43775
+ this.emit({
43776
+ type: "unshare_agent_with_user_response",
43777
+ payload: { requestId: msg.requestId, ok: false, error: "unauthorized" }
43778
+ });
43779
+ return;
43780
+ }
43781
+ try {
43782
+ const sharedWithUserIds = await this.agentManager.unshareAgentWithUser(
43783
+ resolved.agentId,
43784
+ msg.userId
43785
+ );
43786
+ this.emit({
43787
+ type: "unshare_agent_with_user_response",
43788
+ payload: { requestId: msg.requestId, ok: true, sharedWithUserIds }
43789
+ });
43790
+ } catch (error) {
43791
+ const message = error instanceof Error ? error.message : String(error);
43792
+ this.emit({
43793
+ type: "unshare_agent_with_user_response",
43794
+ payload: { requestId: msg.requestId, ok: false, error: message }
43795
+ });
43796
+ }
43797
+ }
43798
+ async handleListAgentSharedUsersRequest(msg) {
43799
+ const resolved = await this.resolveAgentIdentifier(msg.agentId);
43800
+ if (!resolved.ok) {
43801
+ this.emit({
43802
+ type: "list_agent_shared_users_response",
43803
+ payload: { requestId: msg.requestId, ok: false, error: resolved.error }
43804
+ });
43805
+ return;
43806
+ }
43807
+ const agent = this.agentManager.getAgent(resolved.agentId);
43808
+ if (!agent) {
43809
+ this.emit({
43810
+ type: "list_agent_shared_users_response",
43811
+ payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
43812
+ });
43813
+ return;
43814
+ }
43815
+ if (this.ownerUserId !== null && agent.ownerUserId !== null && agent.ownerUserId !== this.ownerUserId) {
43816
+ this.emit({
43817
+ type: "list_agent_shared_users_response",
43818
+ payload: { requestId: msg.requestId, ok: false, error: "unauthorized" }
43819
+ });
43820
+ return;
43821
+ }
43822
+ this.emit({
43823
+ type: "list_agent_shared_users_response",
43824
+ payload: {
43825
+ requestId: msg.requestId,
43826
+ ok: true,
43827
+ ownerUserId: agent.ownerUserId,
43828
+ sharedWithUserIds: [...agent.sharedWithUserIds]
43829
+ }
43830
+ });
43831
+ }
43832
+ // ──────────────────────────────────────────────────────────────────────
43250
43833
  // Session images — backs the "Images" tab inside the uploads modal. Same
43251
43834
  // shape as the file handlers above; the store has no manifest, so listing
43252
43835
  // is a directory scan and delete is `unlink` (traversal-guarded).
@@ -45421,7 +46004,7 @@ import webpush from "web-push";
45421
46004
  import webpush2 from "web-push";
45422
46005
 
45423
46006
  // ../server/src/server/speech/providers/local/sherpa/model-catalog.ts
45424
- import { z as z37 } from "zod";
46007
+ import { z as z39 } from "zod";
45425
46008
  var SHERPA_ONNX_MODEL_CATALOG = {
45426
46009
  "zipformer-bilingual-zh-en-2023-02-20": {
45427
46010
  kind: "stt-online",
@@ -45514,7 +46097,7 @@ function buildAliasMap(modelIds) {
45514
46097
  }
45515
46098
  function createAliasedModelIdSchema(params) {
45516
46099
  const validIds = new Set(params.modelIds);
45517
- return z37.string().trim().toLowerCase().refine(
46100
+ return z39.string().trim().toLowerCase().refine(
45518
46101
  (value) => validIds.has(value) || Object.prototype.hasOwnProperty.call(params.aliases, value),
45519
46102
  {
45520
46103
  message: "Invalid model id"
@@ -45545,20 +46128,20 @@ import { v4 as uuidv410 } from "uuid";
45545
46128
  import { v4 as uuidv411 } from "uuid";
45546
46129
 
45547
46130
  // ../server/src/server/speech/providers/openai/config.ts
45548
- import { z as z38 } from "zod";
46131
+ import { z as z40 } from "zod";
45549
46132
  var DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL = "gpt-4o-transcribe";
45550
46133
  var DEFAULT_OPENAI_TTS_MODEL = "tts-1";
45551
- var OpenAiTtsVoiceSchema = z38.enum(["alloy", "echo", "fable", "onyx", "nova", "shimmer"]);
45552
- var OpenAiTtsModelSchema = z38.enum(["tts-1", "tts-1-hd"]);
45553
- var NumberLikeSchema = z38.union([z38.number(), z38.string().trim().min(1)]);
45554
- var OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z38.coerce.number().finite()).optional();
45555
- var OptionalTrimmedStringSchema = z38.string().trim().optional().transform((value) => value && value.length > 0 ? value : void 0);
45556
- var OpenAiSpeechResolutionSchema = z38.object({
46134
+ var OpenAiTtsVoiceSchema = z40.enum(["alloy", "echo", "fable", "onyx", "nova", "shimmer"]);
46135
+ var OpenAiTtsModelSchema = z40.enum(["tts-1", "tts-1-hd"]);
46136
+ var NumberLikeSchema = z40.union([z40.number(), z40.string().trim().min(1)]);
46137
+ var OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z40.coerce.number().finite()).optional();
46138
+ var OptionalTrimmedStringSchema = z40.string().trim().optional().transform((value) => value && value.length > 0 ? value : void 0);
46139
+ var OpenAiSpeechResolutionSchema = z40.object({
45557
46140
  apiKey: OptionalTrimmedStringSchema,
45558
46141
  sttConfidenceThreshold: OptionalFiniteNumberSchema,
45559
46142
  sttModel: OptionalTrimmedStringSchema,
45560
- ttsVoice: z38.string().trim().toLowerCase().pipe(OpenAiTtsVoiceSchema).default("alloy"),
45561
- ttsModel: z38.string().trim().toLowerCase().pipe(OpenAiTtsModelSchema).default(DEFAULT_OPENAI_TTS_MODEL),
46143
+ ttsVoice: z40.string().trim().toLowerCase().pipe(OpenAiTtsVoiceSchema).default("alloy"),
46144
+ ttsModel: z40.string().trim().toLowerCase().pipe(OpenAiTtsModelSchema).default(DEFAULT_OPENAI_TTS_MODEL),
45562
46145
  realtimeTranscriptionModel: OptionalTrimmedStringSchema.default(
45563
46146
  DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL
45564
46147
  )
@@ -45602,17 +46185,6 @@ import { v4 } from "uuid";
45602
46185
  // ../server/src/server/speech/providers/openai/tts.ts
45603
46186
  import OpenAI2 from "openai";
45604
46187
 
45605
- // ../server/src/server/agent/agent-manager.ts
45606
- import { z as z40 } from "zod";
45607
- import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
45608
-
45609
- // ../server/src/server/agent/handoff-mcp.ts
45610
- import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
45611
- import { z as z39 } from "zod";
45612
-
45613
- // ../server/src/server/agent/agent-manager.ts
45614
- var AgentIdSchema2 = z40.string().uuid();
45615
-
45616
46188
  // ../server/src/server/agent/agent-storage.ts
45617
46189
  import { z as z41 } from "zod";
45618
46190
  var SERIALIZABLE_CONFIG_SCHEMA = z41.object({
@@ -45662,7 +46234,22 @@ var STORED_AGENT_SCHEMA = z41.object({
45662
46234
  archivedAt: z41.string().nullable().optional(),
45663
46235
  // Fork lineage (optional for backward compat with pre-fork records).
45664
46236
  parentAgentId: z41.string().optional(),
45665
- forkedFromMessageUuid: z41.string().optional()
46237
+ forkedFromMessageUuid: z41.string().optional(),
46238
+ // Multi-tenant session isolation: the auth-server user-id that created
46239
+ // (and therefore owns) this agent + the additive ACL of other users
46240
+ // granted access. Both optional/null-default for backward compatibility
46241
+ // with pre-Phase-2c records — those load with `null` owner and stay
46242
+ // visible to every connecting user (matches today's single-tenant
46243
+ // behavior). Set on new agents at create time via Session.ownerUserId.
46244
+ ownerUserId: z41.string().nullable().optional(),
46245
+ sharedWithUserIds: z41.array(z41.string()).default([]),
46246
+ // Owner's display username — needed on resume to route to the correct
46247
+ // `CLAUDE_CONFIG_DIR` via `ensureClaudeProfile(username)` for agents
46248
+ // created by a shared (non-owner) user. Without this, a resumed shared-
46249
+ // user agent would silently fall back to the daemon owner's Claude
46250
+ // profile/subscription. Optional — pre-Phase-3 records rehydrate without
46251
+ // per-user profile routing.
46252
+ ownerUsername: z41.string().optional()
45666
46253
  });
45667
46254
 
45668
46255
  // ../server/src/server/agent/mcp-server.ts
@@ -45863,16 +46450,16 @@ function isRelayClientWebSocketUrl(url) {
45863
46450
  }
45864
46451
 
45865
46452
  // ../server/src/server/config.ts
45866
- import path21 from "node:path";
46453
+ import path22 from "node:path";
45867
46454
  import { z as z51 } from "zod";
45868
46455
 
45869
46456
  // ../server/src/server/speech/speech-config-resolver.ts
45870
46457
  import { z as z50 } from "zod";
45871
46458
 
45872
46459
  // ../server/src/server/speech/providers/local/config.ts
45873
- import path20 from "node:path";
46460
+ import path21 from "node:path";
45874
46461
  import { z as z48 } from "zod";
45875
- var DEFAULT_LOCAL_MODELS_SUBDIR = path20.join("models", "local-speech");
46462
+ var DEFAULT_LOCAL_MODELS_SUBDIR = path21.join("models", "local-speech");
45876
46463
  var NumberLikeSchema2 = z48.union([z48.number(), z48.string().trim().min(1)]);
45877
46464
  var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z48.coerce.number().finite()).optional();
45878
46465
  var OptionalIntegerSchema = NumberLikeSchema2.pipe(z48.coerce.number().int()).optional();
@@ -45899,7 +46486,7 @@ function resolveLocalSpeechConfig(params) {
45899
46486
  const includeProviderConfig = shouldIncludeLocalProviderConfig(params);
45900
46487
  const parsed = LocalSpeechResolutionSchema.parse({
45901
46488
  includeProviderConfig,
45902
- modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path20.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
46489
+ modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path21.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
45903
46490
  dictationLocalSttModel: params.env.APPOSTLE_DICTATION_LOCAL_STT_MODEL ?? persistedLocalFeatureModel(
45904
46491
  params.providers.dictationStt.provider,
45905
46492
  params.providers.dictationStt.enabled,
@@ -46155,7 +46742,7 @@ function loadConfig(appostleHome, options) {
46155
46742
  chromeEnabled,
46156
46743
  mcpDebug: env.MCP_DEBUG === "1",
46157
46744
  daemonIcon,
46158
- agentStoragePath: path21.join(appostleHome, "agents"),
46745
+ agentStoragePath: path22.join(appostleHome, "agents"),
46159
46746
  staticDir: "public",
46160
46747
  agentClients: {},
46161
46748
  relayEnabled,
@@ -46346,10 +46933,10 @@ function encodeUtf8String(value) {
46346
46933
  function createRelayE2eeTransportFactory(args) {
46347
46934
  return ({ url, headers }) => {
46348
46935
  const base = args.baseFactory({ url, headers });
46349
- return createEncryptedTransport(base, args.daemonPublicKeyB64, args.logger);
46936
+ return createEncryptedTransport(base, args.daemonPublicKeyB64, args.logger, args.staticKeyPair);
46350
46937
  };
46351
46938
  }
46352
- function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
46939
+ function createEncryptedTransport(base, daemonPublicKeyB64, logger, staticKeyPair) {
46353
46940
  let channel = null;
46354
46941
  let opened = false;
46355
46942
  let closed = false;
@@ -46406,12 +46993,17 @@ function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
46406
46993
  };
46407
46994
  const startHandshake = async () => {
46408
46995
  try {
46409
- channel = await createClientChannel(relayTransport, daemonPublicKeyB64, {
46410
- onopen: emitOpen,
46411
- onmessage: (data) => emitMessage(data),
46412
- onclose: (code, reason) => emitClose({ code, reason }),
46413
- onerror: (error) => emitError(error)
46414
- });
46996
+ channel = await createClientChannel(
46997
+ relayTransport,
46998
+ daemonPublicKeyB64,
46999
+ {
47000
+ onopen: emitOpen,
47001
+ onmessage: (data) => emitMessage(data),
47002
+ onclose: (code, reason) => emitClose({ code, reason }),
47003
+ onerror: (error) => emitError(error)
47004
+ },
47005
+ staticKeyPair
47006
+ );
46415
47007
  } catch (error) {
46416
47008
  logger.warn({ err: normalizeTransportError(error) }, "relay_e2ee_handshake_failed");
46417
47009
  emitError(error);
@@ -46774,7 +47366,8 @@ var DaemonClient = class {
46774
47366
  transportFactory = createRelayE2eeTransportFactory({
46775
47367
  baseFactory: baseTransportFactory,
46776
47368
  daemonPublicKeyB64,
46777
- logger: this.logger
47369
+ logger: this.logger,
47370
+ staticKeyPair: this.config.e2ee?.staticKeyPair
46778
47371
  });
46779
47372
  }
46780
47373
  const transportUrl = this.resolveTransportUrlForAttempt();
@@ -47405,12 +47998,12 @@ var DaemonClient = class {
47405
47998
  timeout: 1e4
47406
47999
  });
47407
48000
  }
47408
- async openInEditor(path28, editorId, requestId) {
48001
+ async openInEditor(path29, editorId, requestId) {
47409
48002
  return this.sendCorrelatedSessionRequest({
47410
48003
  requestId,
47411
48004
  message: {
47412
48005
  type: "open_in_editor_request",
47413
- path: path28,
48006
+ path: path29,
47414
48007
  editorId
47415
48008
  },
47416
48009
  responseType: "open_in_editor_response",
@@ -47848,6 +48441,99 @@ var DaemonClient = class {
47848
48441
  throw new Error(payload.error || "Failed to delete session upload");
47849
48442
  }
47850
48443
  }
48444
+ // ──────────────────────────────────────────────────────────────────────
48445
+ // Phase 4 agent sharing — owner-only mutations of the per-agent ACL.
48446
+ // ──────────────────────────────────────────────────────────────────────
48447
+ /**
48448
+ * Grant a user access to this agent. Owner-only. Idempotent — sharing
48449
+ * with a user already on the ACL is a no-op. The recipient does not
48450
+ * need to be currently paired; access takes effect at their next
48451
+ * connection.
48452
+ */
48453
+ async shareAgentWithUser(agentId, userId) {
48454
+ const requestId = this.createRequestId();
48455
+ const message = SessionInboundMessageSchema.parse({
48456
+ type: "share_agent_with_user_request",
48457
+ requestId,
48458
+ agentId,
48459
+ userId
48460
+ });
48461
+ const payload = await this.sendRequest({
48462
+ requestId,
48463
+ message,
48464
+ timeout: 15e3,
48465
+ options: { skipQueue: true },
48466
+ select: (msg) => {
48467
+ if (msg.type !== "share_agent_with_user_response") return null;
48468
+ if (msg.payload.requestId !== requestId) return null;
48469
+ return msg.payload;
48470
+ }
48471
+ });
48472
+ if (!payload.ok) {
48473
+ throw new Error(payload.error || "Failed to share agent");
48474
+ }
48475
+ return payload.sharedWithUserIds;
48476
+ }
48477
+ /**
48478
+ * Revoke a user's access to this agent. Owner-only. Effects are
48479
+ * immediate — once the auth-server allow-list propagates (and the
48480
+ * daemon's in-memory state updates here), the removed user's resolver
48481
+ * answers "Agent not found" for any further per-agent RPC.
48482
+ */
48483
+ async unshareAgentWithUser(agentId, userId) {
48484
+ const requestId = this.createRequestId();
48485
+ const message = SessionInboundMessageSchema.parse({
48486
+ type: "unshare_agent_with_user_request",
48487
+ requestId,
48488
+ agentId,
48489
+ userId
48490
+ });
48491
+ const payload = await this.sendRequest({
48492
+ requestId,
48493
+ message,
48494
+ timeout: 15e3,
48495
+ options: { skipQueue: true },
48496
+ select: (msg) => {
48497
+ if (msg.type !== "unshare_agent_with_user_response") return null;
48498
+ if (msg.payload.requestId !== requestId) return null;
48499
+ return msg.payload;
48500
+ }
48501
+ });
48502
+ if (!payload.ok) {
48503
+ throw new Error(payload.error || "Failed to unshare agent");
48504
+ }
48505
+ return payload.sharedWithUserIds;
48506
+ }
48507
+ /**
48508
+ * Return the ACL — owner + the user-ids the owner has granted access
48509
+ * to. Owner-only: recipients can't enumerate who else has access.
48510
+ */
48511
+ async listAgentSharedUsers(agentId) {
48512
+ const requestId = this.createRequestId();
48513
+ const message = SessionInboundMessageSchema.parse({
48514
+ type: "list_agent_shared_users_request",
48515
+ requestId,
48516
+ agentId
48517
+ });
48518
+ const payload = await this.sendRequest({
48519
+ requestId,
48520
+ message,
48521
+ timeout: 15e3,
48522
+ options: { skipQueue: true },
48523
+ select: (msg) => {
48524
+ if (msg.type !== "list_agent_shared_users_response") return null;
48525
+ if (msg.payload.requestId !== requestId) return null;
48526
+ return msg.payload;
48527
+ }
48528
+ });
48529
+ if (!payload.ok) {
48530
+ throw new Error(payload.error || "Failed to list agent shared users");
48531
+ }
48532
+ return {
48533
+ ownerUserId: payload.ownerUserId,
48534
+ sharedWithUserIds: payload.sharedWithUserIds
48535
+ };
48536
+ }
47851
48537
  /**
47852
48538
  * List the images the user has attached to this agent's chat (the "Images"
47853
48539
  * tab in the uploads modal). Returns newest first; the daemon scans
@@ -48896,13 +49582,13 @@ var DaemonClient = class {
48896
49582
  // ============================================================================
48897
49583
  // File Explorer
48898
49584
  // ============================================================================
48899
- async exploreFileSystem(cwd, path28, mode = "list", requestId) {
49585
+ async exploreFileSystem(cwd, path29, mode = "list", requestId) {
48900
49586
  return this.sendCorrelatedSessionRequest({
48901
49587
  requestId,
48902
49588
  message: {
48903
49589
  type: "file_explorer_request",
48904
49590
  cwd,
48905
- path: path28,
49591
+ path: path29,
48906
49592
  mode
48907
49593
  },
48908
49594
  responseType: "file_explorer_response",
@@ -48914,13 +49600,13 @@ var DaemonClient = class {
48914
49600
  * allowlists extensions (currently `.md` only) — callers don't need to
48915
49601
  * re-check. Used by the plan-todos UI to rewrite plan-file frontmatter.
48916
49602
  */
48917
- async writeFile(cwd, path28, content, requestId) {
49603
+ async writeFile(cwd, path29, content, requestId) {
48918
49604
  return this.sendCorrelatedSessionRequest({
48919
49605
  requestId,
48920
49606
  message: {
48921
49607
  type: "file_write_request",
48922
49608
  cwd,
48923
- path: path28,
49609
+ path: path29,
48924
49610
  content
48925
49611
  },
48926
49612
  responseType: "file_write_response",
@@ -48933,42 +49619,42 @@ var DaemonClient = class {
48933
49619
  * action is the only consumer). Returns the daemon's structured response
48934
49620
  * so callers can surface the error in the UI.
48935
49621
  */
48936
- async deleteFile(cwd, path28, requestId) {
49622
+ async deleteFile(cwd, path29, requestId) {
48937
49623
  return this.sendCorrelatedSessionRequest({
48938
49624
  requestId,
48939
49625
  message: {
48940
49626
  type: "file_delete_request",
48941
49627
  cwd,
48942
- path: path28
49628
+ path: path29
48943
49629
  },
48944
49630
  responseType: "file_delete_response",
48945
49631
  timeout: 1e4
48946
49632
  });
48947
49633
  }
48948
- async requestDownloadToken(cwd, path28, requestId) {
49634
+ async requestDownloadToken(cwd, path29, requestId) {
48949
49635
  return this.sendCorrelatedSessionRequest({
48950
49636
  requestId,
48951
49637
  message: {
48952
49638
  type: "file_download_token_request",
48953
49639
  cwd,
48954
- path: path28
49640
+ path: path29
48955
49641
  },
48956
49642
  responseType: "file_download_token_response",
48957
49643
  timeout: 1e4
48958
49644
  });
48959
49645
  }
48960
- async explorerDeleteEntry(cwd, path28, requestId) {
49646
+ async explorerDeleteEntry(cwd, path29, requestId) {
48961
49647
  return this.sendCorrelatedSessionRequest({
48962
49648
  requestId,
48963
- message: { type: "file_explorer_delete_request", cwd, path: path28 },
49649
+ message: { type: "file_explorer_delete_request", cwd, path: path29 },
48964
49650
  responseType: "file_explorer_delete_response",
48965
49651
  timeout: 1e4
48966
49652
  });
48967
49653
  }
48968
- async explorerMkdir(cwd, path28, requestId) {
49654
+ async explorerMkdir(cwd, path29, requestId) {
48969
49655
  return this.sendCorrelatedSessionRequest({
48970
49656
  requestId,
48971
- message: { type: "file_mkdir_request", cwd, path: path28 },
49657
+ message: { type: "file_mkdir_request", cwd, path: path29 },
48972
49658
  responseType: "file_mkdir_response",
48973
49659
  timeout: 1e4
48974
49660
  });
@@ -49998,7 +50684,9 @@ var DaemonClient = class {
49998
50684
  clientId: this.config.clientId,
49999
50685
  clientType: this.config.clientType ?? "cli",
50000
50686
  protocolVersion: 1,
50001
- ...this.config.appVersion ? { appVersion: this.config.appVersion } : {}
50687
+ ...this.config.appVersion ? { appVersion: this.config.appVersion } : {},
50688
+ ...this.config.userId ? { userId: this.config.userId } : {},
50689
+ ...this.config.username ? { username: this.config.username } : {}
50002
50690
  })
50003
50691
  );
50004
50692
  } catch (error) {
@@ -50394,7 +51082,7 @@ function resolveAgentConfig(options) {
50394
51082
  }
50395
51083
 
50396
51084
  // ../cli/src/utils/client.ts
50397
- import path22 from "node:path";
51085
+ import path23 from "node:path";
50398
51086
  import WebSocket3 from "ws";
50399
51087
 
50400
51088
  // ../cli/src/utils/client-id.ts
@@ -50470,8 +51158,8 @@ function isTcpDaemonHost(host) {
50470
51158
  return host !== null && !isIpcDaemonHost(host);
50471
51159
  }
50472
51160
  function readPidSocketTarget(appostleHome) {
50473
- const pidPath = path22.join(appostleHome, PID_FILENAME);
50474
- if (!existsSync11(pidPath)) {
51161
+ const pidPath = path23.join(appostleHome, PID_FILENAME);
51162
+ if (!existsSync12(pidPath)) {
50475
51163
  return null;
50476
51164
  }
50477
51165
  try {
@@ -50939,12 +51627,12 @@ function relativeTime(date) {
50939
51627
  if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
50940
51628
  return `${Math.floor(seconds / 86400)} days ago`;
50941
51629
  }
50942
- function shortenPath(path28) {
51630
+ function shortenPath(path29) {
50943
51631
  const home = process.env.HOME;
50944
- if (home && path28.startsWith(home)) {
50945
- return "~" + path28.slice(home.length);
51632
+ if (home && path29.startsWith(home)) {
51633
+ return "~" + path29.slice(home.length);
50946
51634
  }
50947
- return path28;
51635
+ return path29;
50948
51636
  }
50949
51637
  function normalizeModelId(modelId) {
50950
51638
  if (typeof modelId !== "string") return null;
@@ -51801,10 +52489,10 @@ function addSendOptions(cmd) {
51801
52489
  }
51802
52490
  async function readImageFiles(imagePaths) {
51803
52491
  const images = [];
51804
- for (const path28 of imagePaths) {
52492
+ for (const path29 of imagePaths) {
51805
52493
  try {
51806
- const buffer = await readFile5(path28);
51807
- const ext = extname5(path28).toLowerCase();
52494
+ const buffer = await readFile5(path29);
52495
+ const ext = extname5(path29).toLowerCase();
51808
52496
  let mimeType = "image/jpeg";
51809
52497
  switch (ext) {
51810
52498
  case ".png":
@@ -51832,7 +52520,7 @@ async function readImageFiles(imagePaths) {
51832
52520
  const message = err instanceof Error ? err.message : String(err);
51833
52521
  const error = {
51834
52522
  code: "IMAGE_READ_ERROR",
51835
- message: `Failed to read image file: ${path28}`,
52523
+ message: `Failed to read image file: ${path29}`,
51836
52524
  details: message
51837
52525
  };
51838
52526
  throw error;
@@ -52005,12 +52693,12 @@ function createInspectSchema(agent) {
52005
52693
  serialize: (_item) => agent
52006
52694
  };
52007
52695
  }
52008
- function shortenPath2(path28) {
52696
+ function shortenPath2(path29) {
52009
52697
  const home = process.env.HOME;
52010
- if (home && path28.startsWith(home)) {
52011
- return "~" + path28.slice(home.length);
52698
+ if (home && path29.startsWith(home)) {
52699
+ return "~" + path29.slice(home.length);
52012
52700
  }
52013
- return path28;
52701
+ return path29;
52014
52702
  }
52015
52703
  function formatCost(costUsd) {
52016
52704
  if (costUsd === 0) return "$0.00";
@@ -52976,9 +53664,9 @@ import chalk3 from "chalk";
52976
53664
 
52977
53665
  // ../cli/src/commands/daemon/local-daemon.ts
52978
53666
  import { spawn as spawn7, spawnSync } from "node:child_process";
52979
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
53667
+ import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
52980
53668
  import { createRequire as createRequire3 } from "node:module";
52981
- import path23 from "node:path";
53669
+ import path24 from "node:path";
52982
53670
  import { fileURLToPath as fileURLToPath3 } from "node:url";
52983
53671
  var DETACHED_STARTUP_GRACE_MS = 1200;
52984
53672
  var PID_POLL_INTERVAL_MS = 100;
@@ -53029,8 +53717,8 @@ function buildChildEnv(options) {
53029
53717
  function resolveDaemonRunnerEntry() {
53030
53718
  try {
53031
53719
  const here = fileURLToPath3(import.meta.url);
53032
- const sibling = path23.join(path23.dirname(here), "supervisor-entrypoint.js");
53033
- if (existsSync12(sibling)) {
53720
+ const sibling = path24.join(path24.dirname(here), "supervisor-entrypoint.js");
53721
+ if (existsSync13(sibling)) {
53034
53722
  return sibling;
53035
53723
  }
53036
53724
  } catch {
@@ -53043,23 +53731,23 @@ function resolveDaemonRunnerEntry() {
53043
53731
  "Unable to resolve @appostle/server package root for daemon runner (and no sibling supervisor-entrypoint.js was bundled)"
53044
53732
  );
53045
53733
  }
53046
- let currentDir = path23.dirname(serverExportPath);
53734
+ let currentDir = path24.dirname(serverExportPath);
53047
53735
  while (true) {
53048
- const packageJsonPath = path23.join(currentDir, "package.json");
53049
- if (existsSync12(packageJsonPath)) {
53736
+ const packageJsonPath = path24.join(currentDir, "package.json");
53737
+ if (existsSync13(packageJsonPath)) {
53050
53738
  try {
53051
53739
  const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
53052
53740
  if (packageJson.name === "@appostle/server") {
53053
- const distRunner = path23.join(currentDir, "dist", "scripts", "supervisor-entrypoint.js");
53054
- if (existsSync12(distRunner)) {
53741
+ const distRunner = path24.join(currentDir, "dist", "scripts", "supervisor-entrypoint.js");
53742
+ if (existsSync13(distRunner)) {
53055
53743
  return distRunner;
53056
53744
  }
53057
- return path23.join(currentDir, "scripts", "supervisor-entrypoint.ts");
53745
+ return path24.join(currentDir, "scripts", "supervisor-entrypoint.ts");
53058
53746
  }
53059
53747
  } catch {
53060
53748
  }
53061
53749
  }
53062
- const parentDir = path23.dirname(currentDir);
53750
+ const parentDir = path24.dirname(currentDir);
53063
53751
  if (parentDir === currentDir) {
53064
53752
  break;
53065
53753
  }
@@ -53068,7 +53756,7 @@ function resolveDaemonRunnerEntry() {
53068
53756
  throw new Error("Unable to resolve @appostle/server package root for daemon runner");
53069
53757
  }
53070
53758
  function pidFilePath(appostleHome) {
53071
- return path23.join(appostleHome, DAEMON_PID_FILENAME);
53759
+ return path24.join(appostleHome, DAEMON_PID_FILENAME);
53072
53760
  }
53073
53761
  function readPidFile(pidPath) {
53074
53762
  try {
@@ -53210,8 +53898,8 @@ function resolveLocalDaemonState(options = {}) {
53210
53898
  const home = resolveAppostleHome(env);
53211
53899
  const config = loadConfig(home, { env });
53212
53900
  const pidPath = pidFilePath(home);
53213
- const logPath = path23.join(home, DAEMON_LOG_FILENAME);
53214
- const pidInfo = existsSync12(pidPath) ? readPidFile(pidPath) : null;
53901
+ const logPath = path24.join(home, DAEMON_LOG_FILENAME);
53902
+ const pidInfo = existsSync13(pidPath) ? readPidFile(pidPath) : null;
53215
53903
  const running = pidInfo ? isProcessRunning(pidInfo.pid) : false;
53216
53904
  const listen = pidInfo?.listen ?? config.listen;
53217
53905
  return {
@@ -53225,7 +53913,7 @@ function resolveLocalDaemonState(options = {}) {
53225
53913
  };
53226
53914
  }
53227
53915
  function tailDaemonLog(home, lines = 30) {
53228
- const logPath = path23.join(resolveLocalAppostleHome(home), DAEMON_LOG_FILENAME);
53916
+ const logPath = path24.join(resolveLocalAppostleHome(home), DAEMON_LOG_FILENAME);
53229
53917
  return tailFile(logPath, lines);
53230
53918
  }
53231
53919
  async function startLocalDaemonDetached(options) {
@@ -53234,7 +53922,7 @@ async function startLocalDaemonDetached(options) {
53234
53922
  }
53235
53923
  const childEnv = buildChildEnv(options);
53236
53924
  const appostleHome = resolveAppostleHome(childEnv);
53237
- const logPath = path23.join(appostleHome, DAEMON_LOG_FILENAME);
53925
+ const logPath = path24.join(appostleHome, DAEMON_LOG_FILENAME);
53238
53926
  const daemonRunnerEntry = resolveDaemonRunnerEntry();
53239
53927
  const child = spawn7(
53240
53928
  process.execPath,
@@ -55960,15 +56648,15 @@ import { Command as Command13 } from "commander";
55960
56648
  // ../cli/src/commands/worktree/ls.ts
55961
56649
  import { homedir as homedir7 } from "node:os";
55962
56650
  import { basename as basename7, join as join16, sep as sep3 } from "node:path";
55963
- function shortenPath3(path28) {
56651
+ function shortenPath3(path29) {
55964
56652
  const home = process.env.HOME;
55965
- if (home && path28.startsWith(home)) {
55966
- return "~" + path28.slice(home.length);
56653
+ if (home && path29.startsWith(home)) {
56654
+ return "~" + path29.slice(home.length);
55967
56655
  }
55968
- return path28;
56656
+ return path29;
55969
56657
  }
55970
- function extractWorktreeName(path28) {
55971
- return basename7(path28);
56658
+ function extractWorktreeName(path29) {
56659
+ return basename7(path29);
55972
56660
  }
55973
56661
  function resolveAppostleHomePath() {
55974
56662
  return process.env.APPOSTLE_HOME ?? join16(homedir7(), ".appostle");
@@ -56048,7 +56736,7 @@ async function runLsCommand7(options, _command) {
56048
56736
  }
56049
56737
 
56050
56738
  // ../cli/src/commands/worktree/archive.ts
56051
- import path24 from "path";
56739
+ import path25 from "path";
56052
56740
  var archiveSchema2 = {
56053
56741
  idField: "name",
56054
56742
  columns: [
@@ -56092,7 +56780,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
56092
56780
  throw error;
56093
56781
  }
56094
56782
  const worktree = listResponse.worktrees.find((wt) => {
56095
- const name = path24.basename(wt.worktreePath);
56783
+ const name = path25.basename(wt.worktreePath);
56096
56784
  return name === nameArg || wt.branchName === nameArg;
56097
56785
  });
56098
56786
  if (!worktree) {
@@ -56114,7 +56802,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
56114
56802
  };
56115
56803
  throw error;
56116
56804
  }
56117
- const worktreeName = path24.basename(worktree.worktreePath) || nameArg;
56805
+ const worktreeName = path25.basename(worktree.worktreePath) || nameArg;
56118
56806
  return {
56119
56807
  type: "single",
56120
56808
  data: {
@@ -56155,7 +56843,7 @@ function createWorktreeCommand() {
56155
56843
  import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
56156
56844
  import { Command as Command14, Option as Option4 } from "commander";
56157
56845
  import { writeFileSync as writeFileSync5 } from "node:fs";
56158
- import path25 from "node:path";
56846
+ import path26 from "node:path";
56159
56847
  var DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1e3;
56160
56848
  var OnboardCancelledError = class extends Error {
56161
56849
  };
@@ -56198,7 +56886,7 @@ function toCliOverrides(options) {
56198
56886
  return cliOverrides;
56199
56887
  }
56200
56888
  function savePersistedConfig2(appostleHome, config) {
56201
- const configPath = path25.join(appostleHome, "config.json");
56889
+ const configPath = path26.join(appostleHome, "config.json");
56202
56890
  writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
56203
56891
  `);
56204
56892
  }
@@ -56319,7 +57007,7 @@ ${recentLogs}` : null
56319
57007
  );
56320
57008
  }
56321
57009
  function printNextSteps(pairingUrl, appostleHome, richUi) {
56322
- const daemonLogPath = path25.join(appostleHome, "daemon.log");
57010
+ const daemonLogPath = path26.join(appostleHome, "daemon.log");
56323
57011
  const nextStepsLines = [
56324
57012
  pairingUrl ? "1. Open Appostle and scan the QR code above, or paste the pairing link." : "1. Open Appostle and connect to your daemon.",
56325
57013
  "2. Web app: https://appostle.app",
@@ -56571,21 +57259,21 @@ function createCli() {
56571
57259
  }
56572
57260
 
56573
57261
  // ../cli/src/classify.ts
56574
- import { existsSync as existsSync13, statSync as statSync3 } from "node:fs";
57262
+ import { existsSync as existsSync14, statSync as statSync3 } from "node:fs";
56575
57263
  import { homedir as homedir8 } from "node:os";
56576
- import path26 from "node:path";
57264
+ import path27 from "node:path";
56577
57265
  function expandUserPath2(inputPath) {
56578
57266
  if (inputPath === "~") {
56579
57267
  return homedir8();
56580
57268
  }
56581
57269
  if (inputPath.startsWith("~/")) {
56582
- return path26.join(homedir8(), inputPath.slice(2));
57270
+ return path27.join(homedir8(), inputPath.slice(2));
56583
57271
  }
56584
57272
  return inputPath;
56585
57273
  }
56586
57274
  function isExistingDirectory(input) {
56587
- const resolvedPath = path26.resolve(input.cwd, expandUserPath2(input.pathArg));
56588
- if (!existsSync13(resolvedPath)) {
57275
+ const resolvedPath = path27.resolve(input.cwd, expandUserPath2(input.pathArg));
57276
+ if (!existsSync14(resolvedPath)) {
56589
57277
  return false;
56590
57278
  }
56591
57279
  return statSync3(resolvedPath).isDirectory();
@@ -56604,25 +57292,25 @@ function classifyInvocation(input) {
56604
57292
  if (isExistingDirectory({ pathArg: firstArg, cwd: input.cwd })) {
56605
57293
  return {
56606
57294
  kind: "open-project",
56607
- resolvedPath: path26.resolve(input.cwd, expandUserPath2(firstArg))
57295
+ resolvedPath: path27.resolve(input.cwd, expandUserPath2(firstArg))
56608
57296
  };
56609
57297
  }
56610
57298
  return { kind: "cli", argv: input.argv };
56611
57299
  }
56612
57300
 
56613
57301
  // ../cli/src/commands/open.ts
56614
- import { existsSync as existsSync14 } from "node:fs";
57302
+ import { existsSync as existsSync15 } from "node:fs";
56615
57303
  import { spawn as spawn8 } from "node:child_process";
56616
57304
  import { homedir as homedir9 } from "node:os";
56617
- import path27 from "node:path";
57305
+ import path28 from "node:path";
56618
57306
  function findDesktopApp() {
56619
57307
  if (process.platform === "darwin") {
56620
57308
  const candidates = [
56621
57309
  "/Applications/Appostle.app",
56622
- path27.join(homedir9(), "Applications", "Appostle.app")
57310
+ path28.join(homedir9(), "Applications", "Appostle.app")
56623
57311
  ];
56624
57312
  for (const candidate of candidates) {
56625
- if (existsSync14(candidate)) {
57313
+ if (existsSync15(candidate)) {
56626
57314
  return candidate;
56627
57315
  }
56628
57316
  }
@@ -56632,10 +57320,10 @@ function findDesktopApp() {
56632
57320
  const candidates = [
56633
57321
  "/usr/bin/Appostle",
56634
57322
  "/opt/Appostle/Appostle",
56635
- path27.join(homedir9(), "Applications", "Appostle.AppImage")
57323
+ path28.join(homedir9(), "Applications", "Appostle.AppImage")
56636
57324
  ];
56637
57325
  for (const candidate of candidates) {
56638
- if (existsSync14(candidate)) {
57326
+ if (existsSync15(candidate)) {
56639
57327
  return candidate;
56640
57328
  }
56641
57329
  }
@@ -56646,8 +57334,8 @@ function findDesktopApp() {
56646
57334
  if (!localAppData) {
56647
57335
  return null;
56648
57336
  }
56649
- const candidate = path27.join(localAppData, "Programs", "Appostle", "Appostle.exe");
56650
- return existsSync14(candidate) ? candidate : null;
57337
+ const candidate = path28.join(localAppData, "Programs", "Appostle", "Appostle.exe");
57338
+ return existsSync15(candidate) ? candidate : null;
56651
57339
  }
56652
57340
  return null;
56653
57341
  }