appostle-installer 0.0.21 → 0.0.23

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/worker.js CHANGED
@@ -259,7 +259,7 @@ import { createReadStream, unlinkSync, existsSync as existsSync20 } from "fs";
259
259
  import { stat as stat9 } from "fs/promises";
260
260
  import { randomUUID as randomUUID16 } from "node:crypto";
261
261
  import { hostname as getHostname2 } from "node:os";
262
- import path35 from "node:path";
262
+ import path36 from "node:path";
263
263
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
264
264
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
265
265
 
@@ -712,18 +712,18 @@ function resolveNodePtyPackageRoot() {
712
712
  return null;
713
713
  }
714
714
  }
715
- function ensureExecutableBit(path39) {
716
- if (!existsSync2(path39)) {
715
+ function ensureExecutableBit(path40) {
716
+ if (!existsSync2(path40)) {
717
717
  return;
718
718
  }
719
- const stat10 = statSync(path39);
719
+ const stat10 = statSync(path40);
720
720
  if (!stat10.isFile()) {
721
721
  return;
722
722
  }
723
723
  if ((stat10.mode & 73) === 73) {
724
724
  return;
725
725
  }
726
- chmodSync(path39, stat10.mode | 73);
726
+ chmodSync(path40, stat10.mode | 73);
727
727
  }
728
728
  function ensureNodePtySpawnHelperExecutableForCurrentPlatform(options = {}) {
729
729
  const platform = options.platform ?? process.platform;
@@ -1341,11 +1341,11 @@ function parseGitRevParsePath(stdout) {
1341
1341
  if (lines.length !== 1) {
1342
1342
  return null;
1343
1343
  }
1344
- const path39 = lines[0]?.trim() ?? "";
1345
- if (!path39 || path39.startsWith("--")) {
1344
+ const path40 = lines[0]?.trim() ?? "";
1345
+ if (!path40 || path40.startsWith("--")) {
1346
1346
  return null;
1347
1347
  }
1348
- return path39;
1348
+ return path40;
1349
1349
  }
1350
1350
  function resolveGitRevParsePath(cwd, stdout) {
1351
1351
  const parsed = parseGitRevParsePath(stdout);
@@ -2118,9 +2118,9 @@ async function deleteAppostleWorktree({
2118
2118
  }
2119
2119
  }
2120
2120
  }
2121
- async function pathExists(path39) {
2121
+ async function pathExists(path40) {
2122
2122
  try {
2123
- await stat(path39);
2123
+ await stat(path40);
2124
2124
  return true;
2125
2125
  } catch (error) {
2126
2126
  if (error.code === "ENOENT") {
@@ -2129,8 +2129,8 @@ async function pathExists(path39) {
2129
2129
  throw error;
2130
2130
  }
2131
2131
  }
2132
- async function removeDirectoryWithRetries(path39) {
2133
- if (!await pathExists(path39)) {
2132
+ async function removeDirectoryWithRetries(path40) {
2133
+ if (!await pathExists(path40)) {
2134
2134
  return;
2135
2135
  }
2136
2136
  const delaysMs = [0, 100, 300, 700, 1500];
@@ -2140,17 +2140,17 @@ async function removeDirectoryWithRetries(path39) {
2140
2140
  await new Promise((resolve15) => setTimeout(resolve15, delay));
2141
2141
  }
2142
2142
  try {
2143
- await rm(path39, { recursive: true, force: true });
2144
- if (!await pathExists(path39)) {
2143
+ await rm(path40, { recursive: true, force: true });
2144
+ if (!await pathExists(path40)) {
2145
2145
  return;
2146
2146
  }
2147
- lastError = new Error(`Directory still present after rm: ${path39}`);
2147
+ lastError = new Error(`Directory still present after rm: ${path40}`);
2148
2148
  } catch (error) {
2149
2149
  lastError = error;
2150
2150
  }
2151
2151
  }
2152
- if (await pathExists(path39)) {
2153
- throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path39}`);
2152
+ if (await pathExists(path40)) {
2153
+ throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path40}`);
2154
2154
  }
2155
2155
  }
2156
2156
  var createWorktree = async ({
@@ -3975,7 +3975,9 @@ var BrandEntrySchema = z10.object({
3975
3975
  variables: z10.array(BrandVariableSchema).optional().default([]),
3976
3976
  path: z10.string(),
3977
3977
  modifiedAt: z10.string(),
3978
- size: z10.number()
3978
+ size: z10.number(),
3979
+ /** Raw markdown body (frontmatter stripped). Optional, fed by the scanner. */
3980
+ body: z10.string().optional()
3979
3981
  });
3980
3982
  var BrandFrontmatterSchema = z10.object({
3981
3983
  description: z10.string().optional(),
@@ -4152,6 +4154,22 @@ var BrandGenerateWireframeResponseSchema = z10.object({
4152
4154
  error: z10.string().nullable()
4153
4155
  })
4154
4156
  });
4157
+ var BrandAnalyzePhotographyRequestSchema = z10.object({
4158
+ type: z10.literal("brands/analyze-photography"),
4159
+ requestId: z10.string(),
4160
+ workspaceRoot: z10.string(),
4161
+ brandPath: z10.string(),
4162
+ brief: z10.string(),
4163
+ referenceDescriptions: z10.array(z10.string()).optional()
4164
+ });
4165
+ var BrandAnalyzePhotographyResponseSchema = z10.object({
4166
+ type: z10.literal("brands/analyze-photography/response"),
4167
+ payload: z10.object({
4168
+ requestId: z10.string(),
4169
+ updatedCount: z10.number(),
4170
+ error: z10.string().nullable()
4171
+ })
4172
+ });
4155
4173
  var RightFontEntrySchema = z10.object({
4156
4174
  id: z10.string(),
4157
4175
  name: z10.string(),
@@ -4251,10 +4269,14 @@ var MutableDaemonConfigSchema = z11.object({
4251
4269
  icon: z11.string().optional()
4252
4270
  }).passthrough().optional(),
4253
4271
  chrome: z11.object({
4254
- // When false, agents on this host won't get the --chrome flag,
4255
- // preventing the client's Chrome browser from being controlled.
4256
- // Useful on headless servers where browser tools should target
4257
- // a local headless Chrome instead of the remote client's browser.
4272
+ // When false, agents on this host won't have the claude-in-chrome
4273
+ // tools injected. Set to false on hosts where the client's browser
4274
+ // should not be controlled (e.g. headless/server-only hosts).
4275
+ enabled: z11.boolean()
4276
+ }).passthrough().optional(),
4277
+ playwright: z11.object({
4278
+ // When false, agents on this host won't have the Playwright MCP
4279
+ // server injected.
4258
4280
  enabled: z11.boolean()
4259
4281
  }).passthrough().optional()
4260
4282
  }).passthrough();
@@ -4263,7 +4285,8 @@ var MutableDaemonConfigPatchSchema = z11.object({
4263
4285
  identity: z11.object({
4264
4286
  icon: z11.string().optional()
4265
4287
  }).partial().passthrough().optional(),
4266
- chrome: MutableDaemonConfigSchema.shape.chrome
4288
+ chrome: MutableDaemonConfigSchema.shape.chrome,
4289
+ playwright: MutableDaemonConfigSchema.shape.playwright
4267
4290
  }).partial().passthrough();
4268
4291
  var AgentStatusSchema = z11.enum(AGENT_LIFECYCLE_STATUSES);
4269
4292
  var AgentModeSchema = z11.object({
@@ -6273,6 +6296,7 @@ var SessionInboundMessageSchema = z11.discriminatedUnion("type", [
6273
6296
  BrandGenerateLayoutRequestSchema,
6274
6297
  BrandLayoutQaNextRequestSchema,
6275
6298
  BrandGenerateWireframeRequestSchema,
6299
+ BrandAnalyzePhotographyRequestSchema,
6276
6300
  RightFontLibraryRequestSchema,
6277
6301
  GoogleFontsCatalogRequestSchema,
6278
6302
  GoogleFontsDownloadRequestSchema,
@@ -7909,6 +7933,7 @@ var SessionOutboundMessageSchema = z11.discriminatedUnion("type", [
7909
7933
  BrandGenerateLayoutResponseSchema,
7910
7934
  BrandLayoutQaNextResponseSchema,
7911
7935
  BrandGenerateWireframeResponseSchema,
7936
+ BrandAnalyzePhotographyResponseSchema,
7912
7937
  RightFontLibraryResponseSchema,
7913
7938
  GoogleFontsCatalogResponseSchema,
7914
7939
  GoogleFontsDownloadResponseSchema,
@@ -8157,7 +8182,7 @@ import { exec } from "node:child_process";
8157
8182
  import { promisify as promisify3 } from "util";
8158
8183
  import { join as join16, resolve as resolve10, sep as sep2 } from "path";
8159
8184
  import { homedir as homedir6, hostname as osHostname } from "node:os";
8160
- import { z as z39 } from "zod";
8185
+ import { z as z40 } from "zod";
8161
8186
 
8162
8187
  // ../server/src/server/persisted-config.ts
8163
8188
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
@@ -8454,6 +8479,9 @@ var PersistedConfigSchema = z14.object({
8454
8479
  }).strict().optional(),
8455
8480
  chrome: z14.object({
8456
8481
  enabled: z14.boolean().optional()
8482
+ }).strict().optional(),
8483
+ playwright: z14.object({
8484
+ enabled: z14.boolean().optional()
8457
8485
  }).strict().optional()
8458
8486
  }).strict().transform(({ allowedHosts, ...daemon }) => {
8459
8487
  const hostnames = daemon.hostnames ?? allowedHosts;
@@ -10480,14 +10508,14 @@ var DictationStreamManager = class {
10480
10508
  PCM_CHANNELS,
10481
10509
  PCM_BITS_PER_SAMPLE
10482
10510
  );
10483
- const path39 = await maybePersistDictationDebugAudio(
10511
+ const path40 = await maybePersistDictationDebugAudio(
10484
10512
  wavBuffer,
10485
10513
  { sessionId: state.sessionId, dictationId: state.dictationId, format: "audio/wav" },
10486
10514
  this.logger,
10487
10515
  state.debugChunkWriter?.folder
10488
10516
  );
10489
- state.debugRecordingPath = path39;
10490
- return path39;
10517
+ state.debugRecordingPath = path40;
10518
+ return path40;
10491
10519
  }
10492
10520
  failDictationStream(dictationId, error, retryable) {
10493
10521
  this.emit({
@@ -12505,14 +12533,14 @@ function parseDiff(diffText) {
12505
12533
  const firstLine = lines[0];
12506
12534
  const isNew = section.includes("new file mode") || section.includes("--- /dev/null");
12507
12535
  const isDeleted = section.includes("deleted file mode") || section.includes("+++ /dev/null");
12508
- let path39 = "unknown";
12536
+ let path40 = "unknown";
12509
12537
  const pathMatch = firstLine.match(/a\/(.*?) b\//);
12510
12538
  if (pathMatch) {
12511
- path39 = pathMatch[1];
12539
+ path40 = pathMatch[1];
12512
12540
  } else {
12513
12541
  const newFileMatch = firstLine.match(/b\/(.+)$/);
12514
12542
  if (newFileMatch) {
12515
- path39 = newFileMatch[1];
12543
+ path40 = newFileMatch[1];
12516
12544
  }
12517
12545
  }
12518
12546
  const hunks = [];
@@ -12556,7 +12584,7 @@ function parseDiff(diffText) {
12556
12584
  if (currentHunk) {
12557
12585
  hunks.push(currentHunk);
12558
12586
  }
12559
- files.push({ path: path39, isNew, isDeleted, additions, deletions, hunks });
12587
+ files.push({ path: path40, isNew, isDeleted, additions, deletions, hunks });
12560
12588
  }
12561
12589
  return files;
12562
12590
  }
@@ -12612,9 +12640,9 @@ function buildTokenLookup(lineMap, highlighted) {
12612
12640
  }
12613
12641
  return lookup;
12614
12642
  }
12615
- function buildFullFileTokenLookup(fileContent, path39) {
12643
+ function buildFullFileTokenLookup(fileContent, path40) {
12616
12644
  const lookup = /* @__PURE__ */ new Map();
12617
- const highlighted = highlightCode(fileContent, path39);
12645
+ const highlighted = highlightCode(fileContent, path40);
12618
12646
  for (let i = 0; i < highlighted.length; i++) {
12619
12647
  lookup.set(i + 1, highlighted[i]);
12620
12648
  }
@@ -14319,11 +14347,11 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
14319
14347
  }
14320
14348
  continue;
14321
14349
  }
14322
- const path39 = tabParts[1];
14323
- if (!path39) continue;
14350
+ const path40 = tabParts[1];
14351
+ if (!path40) continue;
14324
14352
  const code = rawStatus[0];
14325
14353
  changes.push({
14326
- path: path39,
14354
+ path: path40,
14327
14355
  status: rawStatus,
14328
14356
  isNew: code === "A",
14329
14357
  isDeleted: code === "D"
@@ -14358,9 +14386,9 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
14358
14386
  }
14359
14387
  return Array.from(byPath.values());
14360
14388
  }
14361
- async function readGitFileContentAtRef(cwd, ref, path39) {
14389
+ async function readGitFileContentAtRef(cwd, ref, path40) {
14362
14390
  try {
14363
- const { stdout } = await runGitCommand(["show", `${ref}:${path39}`], {
14391
+ const { stdout } = await runGitCommand(["show", `${ref}:${path40}`], {
14364
14392
  cwd,
14365
14393
  env: READ_ONLY_GIT_ENV2
14366
14394
  });
@@ -14421,21 +14449,21 @@ async function getTrackedNumstatByPath(cwd, ref, ignoreWhitespace = false) {
14421
14449
  const additionsField = parts[0] ?? "";
14422
14450
  const deletionsField = parts[1] ?? "";
14423
14451
  const rawPath = parts.slice(2).join(" ");
14424
- const path39 = normalizeNumstatPath(rawPath);
14425
- if (!path39) {
14452
+ const path40 = normalizeNumstatPath(rawPath);
14453
+ if (!path40) {
14426
14454
  continue;
14427
14455
  }
14428
14456
  if (additionsField === "-" || deletionsField === "-") {
14429
- stats.set(path39, { additions: 0, deletions: 0, isBinary: true });
14457
+ stats.set(path40, { additions: 0, deletions: 0, isBinary: true });
14430
14458
  continue;
14431
14459
  }
14432
14460
  const additions = Number.parseInt(additionsField, 10);
14433
14461
  const deletions = Number.parseInt(deletionsField, 10);
14434
14462
  if (Number.isNaN(additions) || Number.isNaN(deletions)) {
14435
- stats.set(path39, null);
14463
+ stats.set(path40, null);
14436
14464
  continue;
14437
14465
  }
14438
- stats.set(path39, { additions, deletions, isBinary: false });
14466
+ stats.set(path40, { additions, deletions, isBinary: false });
14439
14467
  }
14440
14468
  return stats;
14441
14469
  }
@@ -15127,10 +15155,10 @@ async function listUncommittedFiles(cwd) {
15127
15155
  if (dest) results.push({ path: dest, changeType: code });
15128
15156
  continue;
15129
15157
  }
15130
- const path39 = parts[1];
15131
- if (!path39) continue;
15158
+ const path40 = parts[1];
15159
+ if (!path40) continue;
15132
15160
  if (code === "A" || code === "M" || code === "D") {
15133
- results.push({ path: path39, changeType: code });
15161
+ results.push({ path: path40, changeType: code });
15134
15162
  }
15135
15163
  }
15136
15164
  } catch {
@@ -19959,6 +19987,7 @@ var ClaudeAgentClient = class {
19959
19987
  runtimeSettings: this.runtimeSettings,
19960
19988
  launchEnv: launchContext?.env,
19961
19989
  chromeEnabled: launchContext?.chromeEnabled,
19990
+ playwrightEnabled: launchContext?.playwrightEnabled,
19962
19991
  logger: this.logger,
19963
19992
  queryFactory: this.queryFactory
19964
19993
  });
@@ -19977,6 +20006,7 @@ var ClaudeAgentClient = class {
19977
20006
  handle,
19978
20007
  launchEnv: launchContext?.env,
19979
20008
  chromeEnabled: launchContext?.chromeEnabled,
20009
+ playwrightEnabled: launchContext?.playwrightEnabled,
19980
20010
  logger: this.logger,
19981
20011
  queryFactory: this.queryFactory
19982
20012
  });
@@ -20291,6 +20321,7 @@ var ClaudeAgentSession = class {
20291
20321
  this.config = config;
20292
20322
  this.launchEnv = options.launchEnv;
20293
20323
  this.chromeEnabled = options.chromeEnabled ?? true;
20324
+ this.playwrightEnabled = options.playwrightEnabled ?? true;
20294
20325
  this.defaults = options.defaults;
20295
20326
  this.runtimeSettings = options.runtimeSettings;
20296
20327
  this.logger = options.logger;
@@ -21054,7 +21085,8 @@ var ClaudeAgentSession = class {
21054
21085
  } else {
21055
21086
  const homeMcps = loadHomeMcpServers(this.logger);
21056
21087
  if (homeMcps) {
21057
- base.mcpServers = this.normalizeMcpServers(homeMcps);
21088
+ const filtered = this.playwrightEnabled ? homeMcps : Object.fromEntries(Object.entries(homeMcps).filter(([k]) => k !== "playwright"));
21089
+ base.mcpServers = this.normalizeMcpServers(filtered);
21058
21090
  }
21059
21091
  }
21060
21092
  if (this.config.model) {
@@ -22192,6 +22224,12 @@ ${error.stack ?? ""}` : JSON.stringify(error);
22192
22224
  buildStructuredToolResult(server, tool4, output, input) {
22193
22225
  const normalizedServer = server.toLowerCase();
22194
22226
  const normalizedTool = tool4.toLowerCase();
22227
+ if (normalizedTool === "mcp__playwright__browser_take_screenshot" || normalizedTool === "browser_take_screenshot") {
22228
+ const imageOutput = this.tryInlinePlaywrightScreenshot(output, input);
22229
+ if (imageOutput) {
22230
+ return { output: imageOutput };
22231
+ }
22232
+ }
22195
22233
  if (normalizedServer.includes("bash") || normalizedServer.includes("shell") || normalizedServer.includes("command") || normalizedTool.includes("bash") || normalizedTool.includes("shell") || normalizedTool.includes("command") || input && (typeof input.command === "string" || Array.isArray(input.command))) {
22196
22234
  const command = this.extractCommandText(input ?? {}) ?? "command";
22197
22235
  return {
@@ -22233,8 +22271,46 @@ ${error.stack ?? ""}` : JSON.stringify(error);
22233
22271
  };
22234
22272
  }
22235
22273
  }
22274
+ if (normalizedServer === "playwright" && normalizedTool === "browser_take_screenshot") {
22275
+ const imageDetail = this.tryReadPlaywrightScreenshot(output, input);
22276
+ if (imageDetail) return imageDetail;
22277
+ }
22236
22278
  return void 0;
22237
22279
  }
22280
+ /**
22281
+ * Playwright MCP's `browser_take_screenshot` saves the image to a local file
22282
+ * and returns a markdown link (`[Screenshot of viewport](./file.png)`).
22283
+ * Extract the path, resolve it against the agent CWD, read the bytes, and
22284
+ * return an image ToolCallDetail so the client renders it inline.
22285
+ */
22286
+ tryReadPlaywrightScreenshot(output, input) {
22287
+ let filePath;
22288
+ if (input && typeof input.filename === "string") {
22289
+ filePath = input.filename;
22290
+ }
22291
+ if (!filePath) {
22292
+ const match = output.match(/\[Screenshot[^\]]*\]\(([^)]+)\)/i);
22293
+ if (match?.[1]) {
22294
+ filePath = match[1];
22295
+ }
22296
+ }
22297
+ if (!filePath) return void 0;
22298
+ const cwd = this.config.cwd;
22299
+ if (!cwd) return void 0;
22300
+ const abs = path9.isAbsolute(filePath) ? filePath : path9.resolve(cwd, filePath);
22301
+ try {
22302
+ const buf = fs4.readFileSync(abs);
22303
+ const ext = path9.extname(abs).toLowerCase();
22304
+ const mime = ext === ".jpeg" || ext === ".jpg" ? "image/jpeg" : "image/png";
22305
+ return {
22306
+ type: "image",
22307
+ toolName: "mcp__playwright__browser_take_screenshot",
22308
+ images: [{ mimeType: mime, data: buf.toString("base64") }]
22309
+ };
22310
+ } catch {
22311
+ return void 0;
22312
+ }
22313
+ }
22238
22314
  mapPartialEvent(event, options) {
22239
22315
  if (event.type === "content_block_start") {
22240
22316
  const block = isClaudeContentChunk2(event.content_block) ? event.content_block : null;
@@ -22407,6 +22483,44 @@ ${error.stack ?? ""}` : JSON.stringify(error);
22407
22483
  }
22408
22484
  return input;
22409
22485
  }
22486
+ /**
22487
+ * Playwright MCP returns `### Result\n- [Screenshot of viewport](./foo.png)`
22488
+ * and writes the file to disk instead of inlining the image. Resolve that
22489
+ * path against the agent cwd and turn the file into an Anthropic-style
22490
+ * image content block array, so downstream `extractImageToolDetail`
22491
+ * promotes the tool call to a renderable `image` detail.
22492
+ *
22493
+ * Returns `null` when the path cannot be resolved/read safely; the caller
22494
+ * then falls back to the default text output behavior.
22495
+ */
22496
+ tryInlinePlaywrightScreenshot(output, input) {
22497
+ const cwd = this.config.cwd;
22498
+ if (!cwd) return null;
22499
+ let relPath;
22500
+ if (input && typeof input.filename === "string" && input.filename.length > 0) {
22501
+ relPath = input.filename;
22502
+ } else {
22503
+ const match = output.match(/\[Screenshot[^\]]*\]\(([^)]+)\)/);
22504
+ if (match?.[1]) {
22505
+ relPath = match[1];
22506
+ }
22507
+ }
22508
+ if (!relPath) return null;
22509
+ const resolved = path9.resolve(cwd, relPath);
22510
+ const cwdReal = path9.resolve(cwd);
22511
+ if (!resolved.startsWith(cwdReal + path9.sep) && resolved !== cwdReal) {
22512
+ return null;
22513
+ }
22514
+ let buf;
22515
+ try {
22516
+ buf = fs4.readFileSync(resolved);
22517
+ } catch {
22518
+ return null;
22519
+ }
22520
+ const ext = path9.extname(resolved).toLowerCase();
22521
+ const mimeType = ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : ext === ".webp" ? "image/webp" : ext === ".gif" ? "image/gif" : "image/png";
22522
+ return [{ type: "image", mimeType, data: buf.toString("base64") }];
22523
+ }
22410
22524
  areToolInputsEqual(left, right) {
22411
22525
  if (!left) {
22412
22526
  return false;
@@ -23349,14 +23463,14 @@ function codexApplyPatchToUnifiedDiff(text) {
23349
23463
  for (const line of lines) {
23350
23464
  const directive = parseCodexApplyPatchDirective(line);
23351
23465
  if (directive) {
23352
- const path39 = normalizeDiffHeaderPath(directive.path);
23353
- if (path39.length > 0) {
23466
+ const path40 = normalizeDiffHeaderPath(directive.path);
23467
+ if (path40.length > 0) {
23354
23468
  if (output.length > 0 && output[output.length - 1] !== "") {
23355
23469
  output.push("");
23356
23470
  }
23357
- const left = directive.kind === "add" ? "/dev/null" : `a/${path39}`;
23358
- const right = directive.kind === "delete" ? "/dev/null" : `b/${path39}`;
23359
- output.push(`diff --git a/${path39} b/${path39}`);
23471
+ const left = directive.kind === "add" ? "/dev/null" : `a/${path40}`;
23472
+ const right = directive.kind === "delete" ? "/dev/null" : `b/${path40}`;
23473
+ output.push(`diff --git a/${path40} b/${path40}`);
23360
23474
  output.push(`--- ${left}`);
23361
23475
  output.push(`+++ ${right}`);
23362
23476
  sawDiffContent = true;
@@ -23426,9 +23540,9 @@ function asEditTextFields(text) {
23426
23540
  function normalizeRolloutEditInput(input) {
23427
23541
  if (typeof input === "string") {
23428
23542
  const textFields2 = asEditTextFields(input);
23429
- const path39 = extractPatchPrimaryFilePath(input);
23543
+ const path40 = extractPatchPrimaryFilePath(input);
23430
23544
  return {
23431
- ...path39 ? { path: path39 } : {},
23545
+ ...path40 ? { path: path40 } : {},
23432
23546
  ...textFields2.unifiedDiff ? { patch: textFields2.unifiedDiff } : {},
23433
23547
  ...textFields2.newString ? { content: textFields2.newString } : {}
23434
23548
  };
@@ -23576,12 +23690,12 @@ function parseFileChangeDiff(entry) {
23576
23690
  ]);
23577
23691
  }
23578
23692
  function toFileChangeEntry(entry, options, fallbackPath) {
23579
- const path39 = parseFileChangePath(entry, options, fallbackPath);
23580
- if (!path39) {
23693
+ const path40 = parseFileChangePath(entry, options, fallbackPath);
23694
+ if (!path40) {
23581
23695
  return null;
23582
23696
  }
23583
23697
  return {
23584
- path: path39,
23698
+ path: path40,
23585
23699
  kind: parseFileChangeKind(entry),
23586
23700
  diff: parseFileChangeDiff(entry)
23587
23701
  };
@@ -23603,12 +23717,12 @@ function parseFileChangeEntries(changes, options) {
23603
23717
  if (singleEntry) {
23604
23718
  return [singleEntry];
23605
23719
  }
23606
- return Object.entries(changes).map(([path39, value]) => {
23720
+ return Object.entries(changes).map(([path40, value]) => {
23607
23721
  if (isRecord2(value)) {
23608
- return toFileChangeEntry(value, options, path39);
23722
+ return toFileChangeEntry(value, options, path40);
23609
23723
  }
23610
23724
  if (typeof value === "string") {
23611
- const normalizedPath = normalizeCodexFilePath(path39.trim(), options?.cwd);
23725
+ const normalizedPath = normalizeCodexFilePath(path40.trim(), options?.cwd);
23612
23726
  if (!normalizedPath) {
23613
23727
  return null;
23614
23728
  }
@@ -24353,16 +24467,16 @@ function isObjectSchemaNode(schema) {
24353
24467
  const type = schema.type;
24354
24468
  return isSchemaRecord(schema.properties) || type === "object" || Array.isArray(type) && type.includes("object");
24355
24469
  }
24356
- function normalizeCodexOutputSchemaNode(schema, path39) {
24470
+ function normalizeCodexOutputSchemaNode(schema, path40) {
24357
24471
  if (Array.isArray(schema)) {
24358
- return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path39}[${index}]`));
24472
+ return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path40}[${index}]`));
24359
24473
  }
24360
24474
  if (!isSchemaRecord(schema)) {
24361
24475
  return schema;
24362
24476
  }
24363
24477
  const normalized = {};
24364
24478
  for (const [key, value] of Object.entries(schema)) {
24365
- normalized[key] = normalizeCodexOutputSchemaNode(value, `${path39}.${key}`);
24479
+ normalized[key] = normalizeCodexOutputSchemaNode(value, `${path40}.${key}`);
24366
24480
  }
24367
24481
  if (!isObjectSchemaNode(normalized)) {
24368
24482
  return normalized;
@@ -24371,7 +24485,7 @@ function normalizeCodexOutputSchemaNode(schema, path39) {
24371
24485
  normalized.additionalProperties = false;
24372
24486
  } else if (normalized.additionalProperties !== false) {
24373
24487
  throw new Error(
24374
- `Codex structured outputs require ${path39} to set additionalProperties to false for object schemas.`
24488
+ `Codex structured outputs require ${path40} to set additionalProperties to false for object schemas.`
24375
24489
  );
24376
24490
  }
24377
24491
  const properties = isSchemaRecord(normalized.properties) ? normalized.properties : null;
@@ -25135,8 +25249,8 @@ function parseCodexPatchChanges(changes) {
25135
25249
  }
25136
25250
  ];
25137
25251
  }
25138
- return Object.entries(recordChanges).map(([path39, value]) => {
25139
- const normalizedPath = path39.trim();
25252
+ return Object.entries(recordChanges).map(([path40, value]) => {
25253
+ const normalizedPath = path40.trim();
25140
25254
  if (!normalizedPath) {
25141
25255
  return null;
25142
25256
  }
@@ -33012,6 +33126,428 @@ function forkSession(input) {
33012
33126
  };
33013
33127
  }
33014
33128
 
33129
+ // ../server/src/server/agent/providers/claude-draft-commands.ts
33130
+ import fs9 from "node:fs/promises";
33131
+ import os7 from "node:os";
33132
+ import path15 from "node:path";
33133
+
33134
+ // ../server/src/server/skills/scanner.ts
33135
+ import fs8 from "node:fs/promises";
33136
+ import os6 from "node:os";
33137
+ import path14 from "node:path";
33138
+ var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
33139
+ function homeDir() {
33140
+ return process.env.HOME || os6.homedir();
33141
+ }
33142
+ function codexHomeDir() {
33143
+ return process.env.CODEX_HOME || path14.join(homeDir(), ".codex");
33144
+ }
33145
+ function resolveScopeDir(provider, scope, workspaceRoot) {
33146
+ if (scope === "codex-prompts") {
33147
+ if (provider !== "codex") {
33148
+ throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
33149
+ }
33150
+ return path14.join(codexHomeDir(), "prompts");
33151
+ }
33152
+ if (scope === "project") {
33153
+ if (!workspaceRoot) {
33154
+ throw new Error(`workspaceRoot is required for scope "project"`);
33155
+ }
33156
+ const dotDir = provider === "claude" ? ".claude" : ".codex";
33157
+ return path14.join(workspaceRoot, dotDir, "skills");
33158
+ }
33159
+ if (provider === "claude") {
33160
+ return path14.join(homeDir(), ".claude", "skills");
33161
+ }
33162
+ return path14.join(codexHomeDir(), "skills");
33163
+ }
33164
+ function allowedRoots(workspaceRoot) {
33165
+ const roots = [
33166
+ path14.join(homeDir(), ".claude", "skills"),
33167
+ path14.join(codexHomeDir(), "skills"),
33168
+ path14.join(codexHomeDir(), "prompts")
33169
+ ];
33170
+ if (workspaceRoot) {
33171
+ roots.push(path14.join(workspaceRoot, ".claude", "skills"));
33172
+ roots.push(path14.join(workspaceRoot, ".codex", "skills"));
33173
+ }
33174
+ return roots.map((r) => path14.resolve(r));
33175
+ }
33176
+ function isInsideAllowedRoot(absPath, workspaceRoot) {
33177
+ const resolved = path14.resolve(absPath);
33178
+ for (const root of allowedRoots(workspaceRoot)) {
33179
+ const rel = path14.relative(root, resolved);
33180
+ if (rel === "" || !rel.startsWith("..") && !path14.isAbsolute(rel)) {
33181
+ return true;
33182
+ }
33183
+ }
33184
+ return false;
33185
+ }
33186
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
33187
+ function parseSkillFile(text) {
33188
+ const match = FRONTMATTER_RE.exec(text);
33189
+ if (!match) {
33190
+ return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
33191
+ }
33192
+ const yamlBlock = match[1] ?? "";
33193
+ const body = match[2] ?? "";
33194
+ return {
33195
+ rawFrontmatterLines: yamlBlock.split(/\r?\n/),
33196
+ body,
33197
+ hadFrontmatter: true
33198
+ };
33199
+ }
33200
+ function readDescription(text) {
33201
+ const parsed = parseSkillFile(text);
33202
+ if (!parsed.hadFrontmatter) return "";
33203
+ for (const line of parsed.rawFrontmatterLines) {
33204
+ const m = /^description\s*:\s*(.*)$/.exec(line);
33205
+ if (m) {
33206
+ const value = (m[1] ?? "").trim();
33207
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
33208
+ return value.slice(1, -1);
33209
+ }
33210
+ return value;
33211
+ }
33212
+ }
33213
+ return "";
33214
+ }
33215
+ function findOwnedSpans(lines) {
33216
+ const spans = [];
33217
+ let i = 0;
33218
+ while (i < lines.length) {
33219
+ const line = lines[i] ?? "";
33220
+ const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
33221
+ if (!keyMatch) {
33222
+ i++;
33223
+ continue;
33224
+ }
33225
+ const key = keyMatch[1];
33226
+ const value = keyMatch[2];
33227
+ if (key !== "description" && key !== "argument-hint" && key !== "allowed-tools") {
33228
+ i++;
33229
+ continue;
33230
+ }
33231
+ let end = i + 1;
33232
+ if (value === "") {
33233
+ while (end < lines.length) {
33234
+ const next = lines[end] ?? "";
33235
+ if (/^\s+-\s+/.test(next) || /^\s*$/.test(next)) {
33236
+ end++;
33237
+ } else {
33238
+ break;
33239
+ }
33240
+ }
33241
+ }
33242
+ spans.push({ key, startLine: i, endLine: end });
33243
+ i = end;
33244
+ }
33245
+ return spans;
33246
+ }
33247
+ function emitFrontmatterValue(key, value) {
33248
+ if (key === "allowed-tools") {
33249
+ if (!Array.isArray(value) || value.length === 0) return [];
33250
+ return [`allowed-tools:`, ...value.map((v) => ` - ${v}`)];
33251
+ }
33252
+ const text = typeof value === "string" ? value : "";
33253
+ if (text.length === 0) return [];
33254
+ const needsQuote = /[:#\n]/.test(text) || text.startsWith(" ") || text.endsWith(" ");
33255
+ const formatted = needsQuote ? `"${text.replace(/"/g, '\\"')}"` : text;
33256
+ return [`${key}: ${formatted}`];
33257
+ }
33258
+ function rewriteFrontmatter(originalLines, next) {
33259
+ const spans = findOwnedSpans(originalLines);
33260
+ const ownedKeys = new Set(spans.map((s) => s.key));
33261
+ const replacements = /* @__PURE__ */ new Map();
33262
+ if (next.description !== void 0) {
33263
+ replacements.set("description", emitFrontmatterValue("description", next.description));
33264
+ }
33265
+ if (next.argumentHint !== void 0) {
33266
+ replacements.set("argument-hint", emitFrontmatterValue("argument-hint", next.argumentHint));
33267
+ }
33268
+ if (next.allowedTools !== void 0) {
33269
+ replacements.set("allowed-tools", emitFrontmatterValue("allowed-tools", next.allowedTools));
33270
+ }
33271
+ const out = [];
33272
+ let i = 0;
33273
+ while (i < originalLines.length) {
33274
+ const span = spans.find((s) => s.startLine === i);
33275
+ if (span) {
33276
+ if (replacements.has(span.key)) {
33277
+ out.push(...replacements.get(span.key) ?? []);
33278
+ } else {
33279
+ for (let j = span.startLine; j < span.endLine; j++) {
33280
+ out.push(originalLines[j] ?? "");
33281
+ }
33282
+ }
33283
+ i = span.endLine;
33284
+ continue;
33285
+ }
33286
+ out.push(originalLines[i] ?? "");
33287
+ i++;
33288
+ }
33289
+ for (const key of ["description", "argument-hint", "allowed-tools"]) {
33290
+ if (replacements.has(key) && !ownedKeys.has(key)) {
33291
+ out.push(...replacements.get(key) ?? []);
33292
+ }
33293
+ }
33294
+ return out;
33295
+ }
33296
+ async function listSkills(args) {
33297
+ const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
33298
+ let entries;
33299
+ try {
33300
+ entries = await fs8.readdir(dir, { withFileTypes: true });
33301
+ } catch {
33302
+ return [];
33303
+ }
33304
+ const results = [];
33305
+ if (args.scope === "codex-prompts") {
33306
+ for (const entry of entries) {
33307
+ if (!entry.isFile()) continue;
33308
+ if (!entry.name.endsWith(".md")) continue;
33309
+ const name = entry.name.slice(0, -".md".length);
33310
+ if (!name) continue;
33311
+ const fullPath = path14.join(dir, entry.name);
33312
+ const stat10 = await safeStat(fullPath);
33313
+ if (!stat10) continue;
33314
+ const description = await readDescriptionSafely(fullPath);
33315
+ results.push({
33316
+ id: `${args.provider}:${args.scope}:${name}`,
33317
+ provider: args.provider,
33318
+ scope: args.scope,
33319
+ name,
33320
+ path: fullPath,
33321
+ description,
33322
+ modifiedAt: stat10.mtime.toISOString(),
33323
+ size: stat10.size
33324
+ });
33325
+ }
33326
+ } else {
33327
+ for (const entry of entries) {
33328
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
33329
+ const skillDir = path14.join(dir, entry.name);
33330
+ const skillPath = path14.join(skillDir, "SKILL.md");
33331
+ const stat10 = await safeStat(skillPath);
33332
+ if (!stat10) continue;
33333
+ const description = await readDescriptionSafely(skillPath);
33334
+ results.push({
33335
+ id: `${args.provider}:${args.scope}:${entry.name}`,
33336
+ provider: args.provider,
33337
+ scope: args.scope,
33338
+ name: entry.name,
33339
+ path: skillPath,
33340
+ description,
33341
+ modifiedAt: stat10.mtime.toISOString(),
33342
+ size: stat10.size
33343
+ });
33344
+ }
33345
+ }
33346
+ results.sort((a, b) => a.name.localeCompare(b.name));
33347
+ return results;
33348
+ }
33349
+ async function safeStat(filePath) {
33350
+ try {
33351
+ const s = await fs8.stat(filePath);
33352
+ return { mtime: s.mtime, size: s.size };
33353
+ } catch {
33354
+ return null;
33355
+ }
33356
+ }
33357
+ async function readDescriptionSafely(filePath) {
33358
+ try {
33359
+ const text = await fs8.readFile(filePath, "utf8");
33360
+ return readDescription(text);
33361
+ } catch {
33362
+ return "";
33363
+ }
33364
+ }
33365
+ async function createSkill(args) {
33366
+ if (!NAME_REGEX.test(args.name)) {
33367
+ throw new Error(
33368
+ `Invalid skill name "${args.name}". Use letters, digits, dot, underscore, dash.`
33369
+ );
33370
+ }
33371
+ if (args.name.includes("/") || args.name.includes("\\") || args.name.includes("..")) {
33372
+ throw new Error(`Skill name must not contain path separators or "..".`);
33373
+ }
33374
+ const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
33375
+ await fs8.mkdir(dir, { recursive: true });
33376
+ if (args.scope === "codex-prompts") {
33377
+ const filePath2 = path14.join(dir, `${args.name}.md`);
33378
+ try {
33379
+ await fs8.access(filePath2);
33380
+ throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
33381
+ } catch (err) {
33382
+ if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
33383
+ throw err;
33384
+ }
33385
+ if (err instanceof Error && err.message.includes("already exists")) {
33386
+ throw err;
33387
+ }
33388
+ }
33389
+ const initial2 = buildStarterPrompt(args.name);
33390
+ await fs8.writeFile(filePath2, initial2, "utf8");
33391
+ return { path: filePath2 };
33392
+ }
33393
+ const skillDir = path14.join(dir, args.name);
33394
+ let dirExists = false;
33395
+ try {
33396
+ const stat10 = await fs8.stat(skillDir);
33397
+ dirExists = stat10.isDirectory();
33398
+ } catch {
33399
+ dirExists = false;
33400
+ }
33401
+ if (dirExists) {
33402
+ throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
33403
+ }
33404
+ await fs8.mkdir(skillDir, { recursive: true });
33405
+ const filePath = path14.join(skillDir, "SKILL.md");
33406
+ const initial = buildStarterSkill(args.name);
33407
+ await fs8.writeFile(filePath, initial, "utf8");
33408
+ return { path: filePath };
33409
+ }
33410
+ function buildStarterSkill(name) {
33411
+ return `---
33412
+ name: ${name}
33413
+ description: ""
33414
+ ---
33415
+
33416
+ # ${name}
33417
+
33418
+ Describe when this skill should be used and what it does. The body of this
33419
+ file is loaded into the agent's context when the skill is invoked.
33420
+ `;
33421
+ }
33422
+ function buildStarterPrompt(name) {
33423
+ return `---
33424
+ description: ""
33425
+ argument-hint: ""
33426
+ ---
33427
+
33428
+ # ${name}
33429
+
33430
+ Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expansion.
33431
+ `;
33432
+ }
33433
+ async function writeSkillFrontmatter(args, workspaceRoot) {
33434
+ if (!path14.isAbsolute(args.path)) {
33435
+ throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
33436
+ }
33437
+ if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
33438
+ throw new Error(`Path "${args.path}" is not inside an allowlisted skill root`);
33439
+ }
33440
+ let original;
33441
+ try {
33442
+ original = await fs8.readFile(args.path, "utf8");
33443
+ } catch (err) {
33444
+ throw new Error(
33445
+ `Failed to read skill file: ${err instanceof Error ? err.message : String(err)}`
33446
+ );
33447
+ }
33448
+ const parsed = parseSkillFile(original);
33449
+ const newLines = rewriteFrontmatter(parsed.rawFrontmatterLines, args.frontmatter);
33450
+ const nextFrontmatter = ["---", ...newLines, "---"].join("\n");
33451
+ const nextContent = parsed.hadFrontmatter ? `${nextFrontmatter}
33452
+ ${parsed.body}` : `${nextFrontmatter}
33453
+
33454
+ ${original}`;
33455
+ await fs8.writeFile(args.path, nextContent, "utf8");
33456
+ }
33457
+
33458
+ // ../server/src/server/agent/providers/claude-draft-commands.ts
33459
+ async function enumerateClaudeDraftCommands(cwd) {
33460
+ const byName = /* @__PURE__ */ new Map();
33461
+ const [global, project, plugin] = await Promise.all([
33462
+ listSkills({ provider: "claude", scope: "global" }).catch(() => []),
33463
+ listSkills({ provider: "claude", scope: "project", workspaceRoot: cwd }).catch(() => []),
33464
+ listClaudePluginSkills().catch(() => [])
33465
+ ]);
33466
+ for (const entry of [...project, ...global, ...plugin]) {
33467
+ if (!byName.has(entry.name)) {
33468
+ byName.set(entry.name, {
33469
+ name: entry.name,
33470
+ description: entry.description,
33471
+ argumentHint: ""
33472
+ });
33473
+ }
33474
+ }
33475
+ return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
33476
+ }
33477
+ async function listClaudePluginSkills() {
33478
+ const pluginsRoot = path15.join(os7.homedir(), ".claude", "plugins");
33479
+ const installedPath = path15.join(pluginsRoot, "installed_plugins.json");
33480
+ let raw;
33481
+ try {
33482
+ raw = await fs9.readFile(installedPath, "utf8");
33483
+ } catch {
33484
+ return [];
33485
+ }
33486
+ let parsed;
33487
+ try {
33488
+ parsed = JSON.parse(raw);
33489
+ } catch {
33490
+ return [];
33491
+ }
33492
+ const out = [];
33493
+ const plugins = parsed.plugins ?? {};
33494
+ for (const [key, installs] of Object.entries(plugins)) {
33495
+ if (!Array.isArray(installs) || installs.length === 0) continue;
33496
+ const prefix = key.split("@")[0];
33497
+ if (!prefix) continue;
33498
+ const install = installs[installs.length - 1];
33499
+ const installPath = install?.installPath;
33500
+ if (!installPath) continue;
33501
+ const skills = await readSkillFolder(path15.join(installPath, "skills"));
33502
+ for (const skill of skills) {
33503
+ out.push({
33504
+ name: `${prefix}:${skill.name}`,
33505
+ description: skill.description
33506
+ });
33507
+ }
33508
+ }
33509
+ return out;
33510
+ }
33511
+ async function readSkillFolder(dir) {
33512
+ let entries;
33513
+ try {
33514
+ entries = await fs9.readdir(dir, { withFileTypes: true });
33515
+ } catch {
33516
+ return [];
33517
+ }
33518
+ const out = [];
33519
+ for (const entry of entries) {
33520
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
33521
+ const skillPath = path15.join(dir, entry.name, "SKILL.md");
33522
+ const description = await readDescriptionField(skillPath);
33523
+ if (description === null) continue;
33524
+ out.push({ name: entry.name, description });
33525
+ }
33526
+ return out;
33527
+ }
33528
+ var FRONTMATTER_RE2 = /^---\r?\n([\s\S]*?)\r?\n---/;
33529
+ async function readDescriptionField(filePath) {
33530
+ let text;
33531
+ try {
33532
+ text = await fs9.readFile(filePath, "utf8");
33533
+ } catch {
33534
+ return null;
33535
+ }
33536
+ const match = FRONTMATTER_RE2.exec(text);
33537
+ if (!match) return "";
33538
+ const yaml = match[1] ?? "";
33539
+ for (const line of yaml.split(/\r?\n/)) {
33540
+ const m = /^description\s*:\s*(.*)$/.exec(line);
33541
+ if (!m) continue;
33542
+ let value = (m[1] ?? "").trim();
33543
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
33544
+ value = value.slice(1, -1);
33545
+ }
33546
+ return value;
33547
+ }
33548
+ return "";
33549
+ }
33550
+
33015
33551
  // ../server/src/server/agent/handoff-mcp.ts
33016
33552
  import { createSdkMcpServer, tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
33017
33553
  import { z as z33 } from "zod";
@@ -33066,8 +33602,8 @@ function buildZodValidator(schema, schemaName) {
33066
33602
  return { ok: true, value: result.data };
33067
33603
  }
33068
33604
  const errors = result.error.issues.map((issue) => {
33069
- const path39 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
33070
- return `${path39}: ${issue.message}`;
33605
+ const path40 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
33606
+ return `${path40}: ${issue.message}`;
33071
33607
  });
33072
33608
  return { ok: false, errors };
33073
33609
  }
@@ -33085,9 +33621,9 @@ function buildJsonSchemaValidator(schema) {
33085
33621
  return { ok: true, value };
33086
33622
  }
33087
33623
  const errors = (validate.errors ?? []).map((error) => {
33088
- const path39 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
33624
+ const path40 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
33089
33625
  const message = error.message ?? "is invalid";
33090
- return `${path39}: ${message}`;
33626
+ return `${path40}: ${message}`;
33091
33627
  });
33092
33628
  return { ok: false, errors };
33093
33629
  }
@@ -33494,8 +34030,8 @@ function scheduleAgentMetadataGeneration(options) {
33494
34030
  }
33495
34031
 
33496
34032
  // ../server/src/server/agent/write-plan-tool.ts
33497
- import fs8 from "node:fs";
33498
- import path14 from "node:path";
34033
+ import fs10 from "node:fs";
34034
+ import path16 from "node:path";
33499
34035
  import { tool } from "@anthropic-ai/claude-agent-sdk";
33500
34036
  import { z as z32 } from "zod";
33501
34037
  var TODO_STATUSES = ["pending", "in_progress", "needs_help", "completed"];
@@ -33545,9 +34081,9 @@ function buildWritePlanTool(callerAgentId, options) {
33545
34081
  ]
33546
34082
  };
33547
34083
  }
33548
- const plansDir = path14.join(agent.cwd, ".plans");
34084
+ const plansDir = path16.join(agent.cwd, ".plans");
33549
34085
  try {
33550
- fs8.mkdirSync(plansDir, { recursive: true });
34086
+ fs10.mkdirSync(plansDir, { recursive: true });
33551
34087
  } catch (err) {
33552
34088
  log.error({ err, plansDir }, "write_plan: mkdir failed");
33553
34089
  return {
@@ -33561,9 +34097,9 @@ function buildWritePlanTool(callerAgentId, options) {
33561
34097
  };
33562
34098
  }
33563
34099
  const targetPath = resolvePlanFilename({
33564
- originalPath: path14.join(plansDir, `${slug}.md`),
34100
+ originalPath: path16.join(plansDir, `${slug}.md`),
33565
34101
  newSlug: slug,
33566
- existsSync: fs8.existsSync
34102
+ existsSync: fs10.existsSync
33567
34103
  });
33568
34104
  const frontmatter = serializeFrontmatter({
33569
34105
  name: args.name,
@@ -33575,7 +34111,7 @@ function buildWritePlanTool(callerAgentId, options) {
33575
34111
  ${args.body}`;
33576
34112
  const fileContent = `${frontmatter}${body}${body.endsWith("\n") ? "" : "\n"}`;
33577
34113
  try {
33578
- fs8.writeFileSync(targetPath, fileContent, "utf8");
34114
+ fs10.writeFileSync(targetPath, fileContent, "utf8");
33579
34115
  } catch (err) {
33580
34116
  log.error({ err, targetPath }, "write_plan: writeFile failed");
33581
34117
  return {
@@ -33722,18 +34258,7 @@ function buildHandoffMcpServer(callerAgentId, options) {
33722
34258
  );
33723
34259
  const reportTool = tool2(
33724
34260
  "report",
33725
- [
33726
- "Send a curated message from this handoff child session back to its parent.",
33727
- "Use this when the user types `>report` \u2014 they want to deliver a specific update",
33728
- "to the parent right now, in their own words.",
33729
- "",
33730
- "Independent of the automatic on-finish notification: the parent still receives",
33731
- "the auto-return when this child eventually finishes. You can call this tool any",
33732
- "number of times over the lifetime of the handoff \u2014 each call is a separate",
33733
- "update.",
33734
- "",
33735
- "Only works in sessions that were spawned via `handoff`. Errors otherwise."
33736
- ].join("\n"),
34261
+ '**ALWAYS call this tool as your final action before finishing your task, to deliver your findings to the parent agent.** Without this call, the parent only receives a content-free "finished" notification and is blind to whatever you did or learned. The first sentence of your report should be a one-line verdict; the rest can fill in detail. Also call this tool any time the user explicitly types `>report` mid-task to send an interim update \u2014 the auto-return on session end is independent from manual reports, and you can call it any number of times over the session\'s lifetime. Only works in sessions that were spawned via `handoff`; errors otherwise.',
33737
34262
  {
33738
34263
  message: z33.string().min(1).describe(
33739
34264
  "The curated report to deliver to the parent session. Self-contained: the parent does not see this conversation's timeline."
@@ -34317,6 +34842,7 @@ var AgentManager = class {
34317
34842
  this.mcpBaseUrl = options?.mcpBaseUrl ?? null;
34318
34843
  this.appostleHome = options?.appostleHome ?? null;
34319
34844
  this.chromeEnabled = options?.chromeEnabled ?? true;
34845
+ this.playwrightEnabled = options?.playwrightEnabled ?? true;
34320
34846
  this.ownerUserId = options?.ownerUserId ?? null;
34321
34847
  this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
34322
34848
  this.agentStreamCoalescer = new AgentStreamCoalescer({
@@ -34540,6 +35066,9 @@ var AgentManager = class {
34540
35066
  }
34541
35067
  async listDraftCommands(config) {
34542
35068
  const normalizedConfig = await this.normalizeConfig(config);
35069
+ if (normalizedConfig.provider === "claude") {
35070
+ return enumerateClaudeDraftCommands(normalizedConfig.cwd);
35071
+ }
34543
35072
  const client = this.requireClient(normalizedConfig.provider);
34544
35073
  const available = await client.isAvailable();
34545
35074
  if (!available) {
@@ -36591,7 +37120,7 @@ var AgentManager = class {
36591
37120
  "using shared user claude profile"
36592
37121
  );
36593
37122
  }
36594
- return { env, chromeEnabled: this.chromeEnabled };
37123
+ return { env, chromeEnabled: this.chromeEnabled, playwrightEnabled: this.playwrightEnabled };
36595
37124
  }
36596
37125
  requireClient(provider) {
36597
37126
  const client = this.clients.get(provider);
@@ -37001,8 +37530,8 @@ async function buildProjectPlacementForCwd(input) {
37001
37530
 
37002
37531
  // ../server/src/server/workspace-registry.ts
37003
37532
  import { randomUUID as randomUUID9 } from "node:crypto";
37004
- import { promises as fs9 } from "node:fs";
37005
- import path15 from "node:path";
37533
+ import { promises as fs11 } from "node:fs";
37534
+ import path17 from "node:path";
37006
37535
  import { z as z35 } from "zod";
37007
37536
  var PersistedProjectRecordSchema = z35.object({
37008
37537
  projectId: z35.string(),
@@ -37041,7 +37570,7 @@ var FileBackedRegistry = class {
37041
37570
  }
37042
37571
  async existsOnDisk() {
37043
37572
  try {
37044
- await fs9.access(this.filePath);
37573
+ await fs11.access(this.filePath);
37045
37574
  return true;
37046
37575
  } catch {
37047
37576
  return false;
@@ -37088,7 +37617,7 @@ var FileBackedRegistry = class {
37088
37617
  }
37089
37618
  this.cache.clear();
37090
37619
  try {
37091
- const raw = await fs9.readFile(this.filePath, "utf8");
37620
+ const raw = await fs11.readFile(this.filePath, "utf8");
37092
37621
  const parsed = z35.array(this.schema).parse(JSON.parse(raw));
37093
37622
  for (const record of parsed) {
37094
37623
  this.cache.set(this.getId(record), record);
@@ -37103,10 +37632,10 @@ var FileBackedRegistry = class {
37103
37632
  }
37104
37633
  async persist() {
37105
37634
  const records = Array.from(this.cache.values());
37106
- await fs9.mkdir(path15.dirname(this.filePath), { recursive: true });
37635
+ await fs11.mkdir(path17.dirname(this.filePath), { recursive: true });
37107
37636
  const tempPath = `${this.filePath}.${process.pid}.${Date.now()}.${randomUUID9()}.tmp`;
37108
- await fs9.writeFile(tempPath, JSON.stringify(records, null, 2), "utf8");
37109
- await fs9.rename(tempPath, this.filePath);
37637
+ await fs11.writeFile(tempPath, JSON.stringify(records, null, 2), "utf8");
37638
+ await fs11.rename(tempPath, this.filePath);
37110
37639
  }
37111
37640
  async enqueuePersist() {
37112
37641
  const nextPersist = this.persistQueue.then(() => this.persist());
@@ -37213,8 +37742,8 @@ function isVoicePermissionAllowed(request) {
37213
37742
  }
37214
37743
 
37215
37744
  // ../server/src/server/file-explorer/service.ts
37216
- import { promises as fs10 } from "fs";
37217
- import path16 from "path";
37745
+ import { promises as fs12 } from "fs";
37746
+ import path18 from "path";
37218
37747
 
37219
37748
  // ../server/src/server/path-utils.ts
37220
37749
  import { homedir as homedir3 } from "node:os";
@@ -37261,14 +37790,14 @@ async function listDirectoryEntries({
37261
37790
  relativePath = "."
37262
37791
  }) {
37263
37792
  const directoryPath = await resolveScopedPath({ root, relativePath });
37264
- const stats = await fs10.stat(directoryPath);
37793
+ const stats = await fs12.stat(directoryPath);
37265
37794
  if (!stats.isDirectory()) {
37266
37795
  throw new Error("Requested path is not a directory");
37267
37796
  }
37268
- const dirents = await fs10.readdir(directoryPath, { withFileTypes: true });
37797
+ const dirents = await fs12.readdir(directoryPath, { withFileTypes: true });
37269
37798
  const entriesWithNulls = await Promise.all(
37270
37799
  dirents.map(async (dirent) => {
37271
- const targetPath = path16.join(directoryPath, dirent.name);
37800
+ const targetPath = path18.join(directoryPath, dirent.name);
37272
37801
  const kind = dirent.isDirectory() ? "directory" : "file";
37273
37802
  try {
37274
37803
  return await buildEntryPayload({
@@ -37303,18 +37832,18 @@ async function readExplorerFile({
37303
37832
  relativePath
37304
37833
  }) {
37305
37834
  const filePath = await resolveScopedPath({ root, relativePath });
37306
- const stats = await fs10.stat(filePath);
37835
+ const stats = await fs12.stat(filePath);
37307
37836
  if (!stats.isFile()) {
37308
37837
  throw new Error("Requested path is not a file");
37309
37838
  }
37310
- const ext = path16.extname(filePath).toLowerCase();
37839
+ const ext = path18.extname(filePath).toLowerCase();
37311
37840
  const basePayload = {
37312
37841
  path: normalizeRelativePath({ root, targetPath: filePath }),
37313
37842
  size: stats.size,
37314
37843
  modifiedAt: stats.mtime.toISOString()
37315
37844
  };
37316
37845
  if (ext in IMAGE_MIME_TYPES) {
37317
- const buffer2 = await fs10.readFile(filePath);
37846
+ const buffer2 = await fs12.readFile(filePath);
37318
37847
  return {
37319
37848
  ...basePayload,
37320
37849
  kind: "image",
@@ -37323,7 +37852,7 @@ async function readExplorerFile({
37323
37852
  mimeType: IMAGE_MIME_TYPES[ext]
37324
37853
  };
37325
37854
  }
37326
- const buffer = await fs10.readFile(filePath);
37855
+ const buffer = await fs12.readFile(filePath);
37327
37856
  if (isLikelyBinary(buffer)) {
37328
37857
  return {
37329
37858
  ...basePayload,
@@ -37345,34 +37874,34 @@ async function writeTextFile({
37345
37874
  relativePath,
37346
37875
  content
37347
37876
  }) {
37348
- const ext = path16.extname(relativePath).toLowerCase();
37877
+ const ext = path18.extname(relativePath).toLowerCase();
37349
37878
  if (ext in IMAGE_MIME_TYPES) {
37350
37879
  throw new Error(`Refusing to write '${relativePath}': binary/image file`);
37351
37880
  }
37352
37881
  const filePath = await resolveScopedPath({ root, relativePath });
37353
37882
  const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
37354
- await fs10.writeFile(tempPath, content, "utf8");
37355
- await fs10.rename(tempPath, filePath);
37883
+ await fs12.writeFile(tempPath, content, "utf8");
37884
+ await fs12.rename(tempPath, filePath);
37356
37885
  }
37357
37886
  async function deleteFile({ root, relativePath }) {
37358
- const ext = path16.extname(relativePath).toLowerCase();
37887
+ const ext = path18.extname(relativePath).toLowerCase();
37359
37888
  if (ext !== ".md") {
37360
37889
  throw new Error(`Refusing to delete '${relativePath}': only .md files allowed`);
37361
37890
  }
37362
37891
  const filePath = await resolveScopedPath({ root, relativePath });
37363
- const stats = await fs10.stat(filePath);
37892
+ const stats = await fs12.stat(filePath);
37364
37893
  if (!stats.isFile()) {
37365
37894
  throw new Error("Requested path is not a file");
37366
37895
  }
37367
- await fs10.unlink(filePath);
37896
+ await fs12.unlink(filePath);
37368
37897
  }
37369
37898
  async function deleteEntry({ root, relativePath }) {
37370
37899
  const entryPath = await resolveScopedPath({ root, relativePath });
37371
- await fs10.rm(entryPath, { recursive: true, force: false });
37900
+ await fs12.rm(entryPath, { recursive: true, force: false });
37372
37901
  }
37373
37902
  async function createDirectory({ root, relativePath }) {
37374
37903
  const dirPath = await resolveScopedPath({ root, relativePath });
37375
- await fs10.mkdir(dirPath, { recursive: true });
37904
+ await fs12.mkdir(dirPath, { recursive: true });
37376
37905
  }
37377
37906
  async function moveEntry({
37378
37907
  root,
@@ -37381,22 +37910,22 @@ async function moveEntry({
37381
37910
  }) {
37382
37911
  const src = await resolveScopedPath({ root, relativePath: sourcePath });
37383
37912
  const dest = await resolveScopedPath({ root, relativePath: destinationPath });
37384
- await fs10.rename(src, dest);
37913
+ await fs12.rename(src, dest);
37385
37914
  }
37386
37915
  async function getDownloadableFileInfo({ root, relativePath }) {
37387
37916
  const filePath = await resolveScopedPath({ root, relativePath });
37388
- const stats = await fs10.stat(filePath);
37917
+ const stats = await fs12.stat(filePath);
37389
37918
  if (!stats.isFile()) {
37390
37919
  throw new Error("Requested path is not a file");
37391
37920
  }
37392
- const ext = path16.extname(filePath).toLowerCase();
37921
+ const ext = path18.extname(filePath).toLowerCase();
37393
37922
  let mimeType = "application/octet-stream";
37394
37923
  if (ext in IMAGE_MIME_TYPES) {
37395
37924
  mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
37396
37925
  } else if (ext in FONT_MIME_TYPES) {
37397
37926
  mimeType = FONT_MIME_TYPES[ext] ?? mimeType;
37398
37927
  } else {
37399
- const handle = await fs10.open(filePath, "r");
37928
+ const handle = await fs12.open(filePath, "r");
37400
37929
  const sample = Buffer.alloc(8192);
37401
37930
  try {
37402
37931
  const { bytesRead } = await handle.read(sample, 0, sample.length, 0);
@@ -37411,23 +37940,23 @@ async function getDownloadableFileInfo({ root, relativePath }) {
37411
37940
  return {
37412
37941
  path: normalizeRelativePath({ root, targetPath: filePath }),
37413
37942
  absolutePath: filePath,
37414
- fileName: path16.basename(filePath),
37943
+ fileName: path18.basename(filePath),
37415
37944
  mimeType,
37416
37945
  size: stats.size
37417
37946
  };
37418
37947
  }
37419
37948
  async function resolveScopedPath({ root, relativePath = "." }) {
37420
- const normalizedRoot = path16.resolve(root);
37949
+ const normalizedRoot = path18.resolve(root);
37421
37950
  const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
37422
- const relative = path16.relative(normalizedRoot, requestedPath);
37423
- if (relative !== "" && (relative.startsWith("..") || path16.isAbsolute(relative))) {
37951
+ const relative = path18.relative(normalizedRoot, requestedPath);
37952
+ if (relative !== "" && (relative.startsWith("..") || path18.isAbsolute(relative))) {
37424
37953
  throw new Error("Access outside of workspace is not allowed");
37425
37954
  }
37426
- const realRoot = await fs10.realpath(normalizedRoot);
37955
+ const realRoot = await fs12.realpath(normalizedRoot);
37427
37956
  try {
37428
- const realPath = await fs10.realpath(requestedPath);
37429
- const realRelative = path16.relative(realRoot, realPath);
37430
- if (realRelative !== "" && (realRelative.startsWith("..") || path16.isAbsolute(realRelative))) {
37957
+ const realPath = await fs12.realpath(requestedPath);
37958
+ const realRelative = path18.relative(realRoot, realPath);
37959
+ if (realRelative !== "" && (realRelative.startsWith("..") || path18.isAbsolute(realRelative))) {
37431
37960
  throw new Error("Access outside of workspace is not allowed");
37432
37961
  }
37433
37962
  return requestedPath;
@@ -37444,7 +37973,7 @@ async function buildEntryPayload({
37444
37973
  name,
37445
37974
  kind
37446
37975
  }) {
37447
- const stats = await fs10.stat(targetPath);
37976
+ const stats = await fs12.stat(targetPath);
37448
37977
  return {
37449
37978
  name,
37450
37979
  path: normalizeRelativePath({ root, targetPath }),
@@ -37458,10 +37987,10 @@ function isMissingEntryError(error) {
37458
37987
  return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
37459
37988
  }
37460
37989
  function normalizeRelativePath({ root, targetPath }) {
37461
- const normalizedRoot = path16.resolve(root);
37462
- const normalizedTarget = path16.resolve(targetPath);
37463
- const relative = path16.relative(normalizedRoot, normalizedTarget);
37464
- return relative === "" ? "." : relative.split(path16.sep).join("/");
37990
+ const normalizedRoot = path18.resolve(root);
37991
+ const normalizedTarget = path18.resolve(targetPath);
37992
+ const relative = path18.relative(normalizedRoot, normalizedTarget);
37993
+ return relative === "" ? "." : relative.split(path18.sep).join("/");
37465
37994
  }
37466
37995
  function textMimeTypeForExtension(ext) {
37467
37996
  return TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE;
@@ -37814,345 +38343,21 @@ async function getProjectIcon(projectDir) {
37814
38343
  }
37815
38344
 
37816
38345
  // ../server/src/utils/path.ts
37817
- import os6 from "os";
37818
- function expandTilde(path39) {
37819
- if (path39.startsWith("~/")) {
37820
- const homeDir3 = process.env.HOME || os6.homedir();
37821
- return path39.replace("~", homeDir3);
38346
+ import os8 from "os";
38347
+ function expandTilde(path40) {
38348
+ if (path40.startsWith("~/")) {
38349
+ const homeDir3 = process.env.HOME || os8.homedir();
38350
+ return path40.replace("~", homeDir3);
37822
38351
  }
37823
- if (path39 === "~") {
37824
- return process.env.HOME || os6.homedir();
38352
+ if (path40 === "~") {
38353
+ return process.env.HOME || os8.homedir();
37825
38354
  }
37826
- return path39;
37827
- }
37828
-
37829
- // ../server/src/server/skills/scanner.ts
37830
- import fs11 from "node:fs/promises";
37831
- import os7 from "node:os";
37832
- import path17 from "node:path";
37833
- var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
37834
- function homeDir() {
37835
- return process.env.HOME || os7.homedir();
37836
- }
37837
- function codexHomeDir() {
37838
- return process.env.CODEX_HOME || path17.join(homeDir(), ".codex");
37839
- }
37840
- function resolveScopeDir(provider, scope, workspaceRoot) {
37841
- if (scope === "codex-prompts") {
37842
- if (provider !== "codex") {
37843
- throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
37844
- }
37845
- return path17.join(codexHomeDir(), "prompts");
37846
- }
37847
- if (scope === "project") {
37848
- if (!workspaceRoot) {
37849
- throw new Error(`workspaceRoot is required for scope "project"`);
37850
- }
37851
- const dotDir = provider === "claude" ? ".claude" : ".codex";
37852
- return path17.join(workspaceRoot, dotDir, "skills");
37853
- }
37854
- if (provider === "claude") {
37855
- return path17.join(homeDir(), ".claude", "skills");
37856
- }
37857
- return path17.join(codexHomeDir(), "skills");
37858
- }
37859
- function allowedRoots(workspaceRoot) {
37860
- const roots = [
37861
- path17.join(homeDir(), ".claude", "skills"),
37862
- path17.join(codexHomeDir(), "skills"),
37863
- path17.join(codexHomeDir(), "prompts")
37864
- ];
37865
- if (workspaceRoot) {
37866
- roots.push(path17.join(workspaceRoot, ".claude", "skills"));
37867
- roots.push(path17.join(workspaceRoot, ".codex", "skills"));
37868
- }
37869
- return roots.map((r) => path17.resolve(r));
37870
- }
37871
- function isInsideAllowedRoot(absPath, workspaceRoot) {
37872
- const resolved = path17.resolve(absPath);
37873
- for (const root of allowedRoots(workspaceRoot)) {
37874
- const rel = path17.relative(root, resolved);
37875
- if (rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel)) {
37876
- return true;
37877
- }
37878
- }
37879
- return false;
37880
- }
37881
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
37882
- function parseSkillFile(text) {
37883
- const match = FRONTMATTER_RE.exec(text);
37884
- if (!match) {
37885
- return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
37886
- }
37887
- const yamlBlock = match[1] ?? "";
37888
- const body = match[2] ?? "";
37889
- return {
37890
- rawFrontmatterLines: yamlBlock.split(/\r?\n/),
37891
- body,
37892
- hadFrontmatter: true
37893
- };
37894
- }
37895
- function readDescription(text) {
37896
- const parsed = parseSkillFile(text);
37897
- if (!parsed.hadFrontmatter) return "";
37898
- for (const line of parsed.rawFrontmatterLines) {
37899
- const m = /^description\s*:\s*(.*)$/.exec(line);
37900
- if (m) {
37901
- const value = (m[1] ?? "").trim();
37902
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
37903
- return value.slice(1, -1);
37904
- }
37905
- return value;
37906
- }
37907
- }
37908
- return "";
37909
- }
37910
- function findOwnedSpans(lines) {
37911
- const spans = [];
37912
- let i = 0;
37913
- while (i < lines.length) {
37914
- const line = lines[i] ?? "";
37915
- const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
37916
- if (!keyMatch) {
37917
- i++;
37918
- continue;
37919
- }
37920
- const key = keyMatch[1];
37921
- const value = keyMatch[2];
37922
- if (key !== "description" && key !== "argument-hint" && key !== "allowed-tools") {
37923
- i++;
37924
- continue;
37925
- }
37926
- let end = i + 1;
37927
- if (value === "") {
37928
- while (end < lines.length) {
37929
- const next = lines[end] ?? "";
37930
- if (/^\s+-\s+/.test(next) || /^\s*$/.test(next)) {
37931
- end++;
37932
- } else {
37933
- break;
37934
- }
37935
- }
37936
- }
37937
- spans.push({ key, startLine: i, endLine: end });
37938
- i = end;
37939
- }
37940
- return spans;
37941
- }
37942
- function emitFrontmatterValue(key, value) {
37943
- if (key === "allowed-tools") {
37944
- if (!Array.isArray(value) || value.length === 0) return [];
37945
- return [`allowed-tools:`, ...value.map((v) => ` - ${v}`)];
37946
- }
37947
- const text = typeof value === "string" ? value : "";
37948
- if (text.length === 0) return [];
37949
- const needsQuote = /[:#\n]/.test(text) || text.startsWith(" ") || text.endsWith(" ");
37950
- const formatted = needsQuote ? `"${text.replace(/"/g, '\\"')}"` : text;
37951
- return [`${key}: ${formatted}`];
37952
- }
37953
- function rewriteFrontmatter(originalLines, next) {
37954
- const spans = findOwnedSpans(originalLines);
37955
- const ownedKeys = new Set(spans.map((s) => s.key));
37956
- const replacements = /* @__PURE__ */ new Map();
37957
- if (next.description !== void 0) {
37958
- replacements.set("description", emitFrontmatterValue("description", next.description));
37959
- }
37960
- if (next.argumentHint !== void 0) {
37961
- replacements.set("argument-hint", emitFrontmatterValue("argument-hint", next.argumentHint));
37962
- }
37963
- if (next.allowedTools !== void 0) {
37964
- replacements.set("allowed-tools", emitFrontmatterValue("allowed-tools", next.allowedTools));
37965
- }
37966
- const out = [];
37967
- let i = 0;
37968
- while (i < originalLines.length) {
37969
- const span = spans.find((s) => s.startLine === i);
37970
- if (span) {
37971
- if (replacements.has(span.key)) {
37972
- out.push(...replacements.get(span.key) ?? []);
37973
- } else {
37974
- for (let j = span.startLine; j < span.endLine; j++) {
37975
- out.push(originalLines[j] ?? "");
37976
- }
37977
- }
37978
- i = span.endLine;
37979
- continue;
37980
- }
37981
- out.push(originalLines[i] ?? "");
37982
- i++;
37983
- }
37984
- for (const key of ["description", "argument-hint", "allowed-tools"]) {
37985
- if (replacements.has(key) && !ownedKeys.has(key)) {
37986
- out.push(...replacements.get(key) ?? []);
37987
- }
37988
- }
37989
- return out;
37990
- }
37991
- async function listSkills(args) {
37992
- const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
37993
- let entries;
37994
- try {
37995
- entries = await fs11.readdir(dir, { withFileTypes: true });
37996
- } catch {
37997
- return [];
37998
- }
37999
- const results = [];
38000
- if (args.scope === "codex-prompts") {
38001
- for (const entry of entries) {
38002
- if (!entry.isFile()) continue;
38003
- if (!entry.name.endsWith(".md")) continue;
38004
- const name = entry.name.slice(0, -".md".length);
38005
- if (!name) continue;
38006
- const fullPath = path17.join(dir, entry.name);
38007
- const stat10 = await safeStat(fullPath);
38008
- if (!stat10) continue;
38009
- const description = await readDescriptionSafely(fullPath);
38010
- results.push({
38011
- id: `${args.provider}:${args.scope}:${name}`,
38012
- provider: args.provider,
38013
- scope: args.scope,
38014
- name,
38015
- path: fullPath,
38016
- description,
38017
- modifiedAt: stat10.mtime.toISOString(),
38018
- size: stat10.size
38019
- });
38020
- }
38021
- } else {
38022
- for (const entry of entries) {
38023
- if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
38024
- const skillDir = path17.join(dir, entry.name);
38025
- const skillPath = path17.join(skillDir, "SKILL.md");
38026
- const stat10 = await safeStat(skillPath);
38027
- if (!stat10) continue;
38028
- const description = await readDescriptionSafely(skillPath);
38029
- results.push({
38030
- id: `${args.provider}:${args.scope}:${entry.name}`,
38031
- provider: args.provider,
38032
- scope: args.scope,
38033
- name: entry.name,
38034
- path: skillPath,
38035
- description,
38036
- modifiedAt: stat10.mtime.toISOString(),
38037
- size: stat10.size
38038
- });
38039
- }
38040
- }
38041
- results.sort((a, b) => a.name.localeCompare(b.name));
38042
- return results;
38043
- }
38044
- async function safeStat(filePath) {
38045
- try {
38046
- const s = await fs11.stat(filePath);
38047
- return { mtime: s.mtime, size: s.size };
38048
- } catch {
38049
- return null;
38050
- }
38051
- }
38052
- async function readDescriptionSafely(filePath) {
38053
- try {
38054
- const text = await fs11.readFile(filePath, "utf8");
38055
- return readDescription(text);
38056
- } catch {
38057
- return "";
38058
- }
38059
- }
38060
- async function createSkill(args) {
38061
- if (!NAME_REGEX.test(args.name)) {
38062
- throw new Error(
38063
- `Invalid skill name "${args.name}". Use letters, digits, dot, underscore, dash.`
38064
- );
38065
- }
38066
- if (args.name.includes("/") || args.name.includes("\\") || args.name.includes("..")) {
38067
- throw new Error(`Skill name must not contain path separators or "..".`);
38068
- }
38069
- const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
38070
- await fs11.mkdir(dir, { recursive: true });
38071
- if (args.scope === "codex-prompts") {
38072
- const filePath2 = path17.join(dir, `${args.name}.md`);
38073
- try {
38074
- await fs11.access(filePath2);
38075
- throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
38076
- } catch (err) {
38077
- if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
38078
- throw err;
38079
- }
38080
- if (err instanceof Error && err.message.includes("already exists")) {
38081
- throw err;
38082
- }
38083
- }
38084
- const initial2 = buildStarterPrompt(args.name);
38085
- await fs11.writeFile(filePath2, initial2, "utf8");
38086
- return { path: filePath2 };
38087
- }
38088
- const skillDir = path17.join(dir, args.name);
38089
- let dirExists = false;
38090
- try {
38091
- const stat10 = await fs11.stat(skillDir);
38092
- dirExists = stat10.isDirectory();
38093
- } catch {
38094
- dirExists = false;
38095
- }
38096
- if (dirExists) {
38097
- throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
38098
- }
38099
- await fs11.mkdir(skillDir, { recursive: true });
38100
- const filePath = path17.join(skillDir, "SKILL.md");
38101
- const initial = buildStarterSkill(args.name);
38102
- await fs11.writeFile(filePath, initial, "utf8");
38103
- return { path: filePath };
38104
- }
38105
- function buildStarterSkill(name) {
38106
- return `---
38107
- name: ${name}
38108
- description: ""
38109
- ---
38110
-
38111
- # ${name}
38112
-
38113
- Describe when this skill should be used and what it does. The body of this
38114
- file is loaded into the agent's context when the skill is invoked.
38115
- `;
38116
- }
38117
- function buildStarterPrompt(name) {
38118
- return `---
38119
- description: ""
38120
- argument-hint: ""
38121
- ---
38122
-
38123
- # ${name}
38124
-
38125
- Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expansion.
38126
- `;
38127
- }
38128
- async function writeSkillFrontmatter(args, workspaceRoot) {
38129
- if (!path17.isAbsolute(args.path)) {
38130
- throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
38131
- }
38132
- if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
38133
- throw new Error(`Path "${args.path}" is not inside an allowlisted skill root`);
38134
- }
38135
- let original;
38136
- try {
38137
- original = await fs11.readFile(args.path, "utf8");
38138
- } catch (err) {
38139
- throw new Error(
38140
- `Failed to read skill file: ${err instanceof Error ? err.message : String(err)}`
38141
- );
38142
- }
38143
- const parsed = parseSkillFile(original);
38144
- const newLines = rewriteFrontmatter(parsed.rawFrontmatterLines, args.frontmatter);
38145
- const nextFrontmatter = ["---", ...newLines, "---"].join("\n");
38146
- const nextContent = parsed.hadFrontmatter ? `${nextFrontmatter}
38147
- ${parsed.body}` : `${nextFrontmatter}
38148
-
38149
- ${original}`;
38150
- await fs11.writeFile(args.path, nextContent, "utf8");
38355
+ return path40;
38151
38356
  }
38152
38357
 
38153
38358
  // ../server/src/utils/directory-suggestions.ts
38154
38359
  import { readdir as readdir2, realpath, stat as stat4 } from "node:fs/promises";
38155
- import path18 from "node:path";
38360
+ import path19 from "node:path";
38156
38361
  var DEFAULT_LIMIT = 30;
38157
38362
  var MAX_LIMIT = 100;
38158
38363
  var DEFAULT_MAX_DEPTH = 6;
@@ -38238,7 +38443,7 @@ function normalizeLimit(limit) {
38238
38443
  return Math.max(1, Math.min(MAX_LIMIT, bounded));
38239
38444
  }
38240
38445
  async function searchWithinParentDirectory(input) {
38241
- const parentPath = path18.resolve(input.homeRoot, input.parentPart || ".");
38446
+ const parentPath = path19.resolve(input.homeRoot, input.parentPart || ".");
38242
38447
  const parentRoot = await resolveDirectory(parentPath);
38243
38448
  if (!parentRoot || !isPathInsideRoot(input.homeRoot, parentRoot)) {
38244
38449
  return [];
@@ -38303,7 +38508,7 @@ async function searchAcrossHomeTree(input) {
38303
38508
  return dedupeAndSort(ranked).slice(0, input.limit);
38304
38509
  }
38305
38510
  async function searchWorkspaceWithinParentDirectory(input) {
38306
- const parentPath = path18.resolve(input.workspaceRoot, input.parentPart || ".");
38511
+ const parentPath = path19.resolve(input.workspaceRoot, input.parentPart || ".");
38307
38512
  const parentRoot = await resolveDirectory(parentPath);
38308
38513
  if (!parentRoot || !isPathInsideRoot(input.workspaceRoot, parentRoot)) {
38309
38514
  return [];
@@ -38549,15 +38754,15 @@ function findSegmentMatchIndex(segments, predicate) {
38549
38754
  return -1;
38550
38755
  }
38551
38756
  function normalizeRelativePath2(homeRoot, absolutePath) {
38552
- const relative = path18.relative(homeRoot, absolutePath);
38757
+ const relative = path19.relative(homeRoot, absolutePath);
38553
38758
  if (!relative) {
38554
38759
  return ".";
38555
38760
  }
38556
- return relative.split(path18.sep).join("/");
38761
+ return relative.split(path19.sep).join("/");
38557
38762
  }
38558
38763
  function isPathInsideRoot(root, target) {
38559
- const relative = path18.relative(root, target);
38560
- return relative === "" || !relative.startsWith("..") && !path18.isAbsolute(relative);
38764
+ const relative = path19.relative(root, target);
38765
+ return relative === "" || !relative.startsWith("..") && !path19.isAbsolute(relative);
38561
38766
  }
38562
38767
  function normalizeQueryParts(query2, homeRoot) {
38563
38768
  const typedQuery = query2.trim().replace(/\\/g, "/");
@@ -38573,9 +38778,9 @@ function normalizeQueryParts(query2, homeRoot) {
38573
38778
  normalized = normalized.slice(1);
38574
38779
  }
38575
38780
  }
38576
- if (path18.isAbsolute(normalized)) {
38781
+ if (path19.isAbsolute(normalized)) {
38577
38782
  isRooted = true;
38578
- const absolute = path18.resolve(normalized);
38783
+ const absolute = path19.resolve(normalized);
38579
38784
  if (!isPathInsideRoot(homeRoot, absolute)) {
38580
38785
  return null;
38581
38786
  }
@@ -38614,8 +38819,8 @@ function normalizeQueryParts(query2, homeRoot) {
38614
38819
  }
38615
38820
  function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
38616
38821
  let normalized = query2.trim().replace(/\\/g, "/");
38617
- if (path18.isAbsolute(normalized)) {
38618
- const absolute = path18.resolve(normalized);
38822
+ if (path19.isAbsolute(normalized)) {
38823
+ const absolute = path19.resolve(normalized);
38619
38824
  if (!isPathInsideRoot(workspaceRoot, absolute)) {
38620
38825
  return null;
38621
38826
  }
@@ -38641,7 +38846,7 @@ function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
38641
38846
  }
38642
38847
  async function resolveDirectory(inputPath) {
38643
38848
  try {
38644
- const resolved = await realpath(path18.resolve(inputPath));
38849
+ const resolved = await realpath(path19.resolve(inputPath));
38645
38850
  const stats = await stat4(resolved);
38646
38851
  if (!stats.isDirectory()) {
38647
38852
  return null;
@@ -38668,7 +38873,7 @@ async function listChildDirectories(input) {
38668
38873
  if (!dirent.isDirectory() && !dirent.isSymbolicLink()) {
38669
38874
  continue;
38670
38875
  }
38671
- const candidatePath = path18.join(input.directory, dirent.name);
38876
+ const candidatePath = path19.join(input.directory, dirent.name);
38672
38877
  const absolutePath = await resolveDirectoryCandidate({
38673
38878
  candidatePath,
38674
38879
  dirent,
@@ -38705,7 +38910,7 @@ async function listWorkspaceChildEntries(input) {
38705
38910
  if (isIgnoredWorkspaceDirectoryName(dirent.name)) {
38706
38911
  continue;
38707
38912
  }
38708
- const candidatePath = path18.join(input.directory, dirent.name);
38913
+ const candidatePath = path19.join(input.directory, dirent.name);
38709
38914
  const entry = await resolveWorkspaceCandidate({
38710
38915
  candidatePath,
38711
38916
  dirent,
@@ -38728,7 +38933,7 @@ async function listWorkspaceChildEntries(input) {
38728
38933
  }
38729
38934
  async function resolveDirectoryCandidate(input) {
38730
38935
  if (input.dirent.isDirectory()) {
38731
- const resolved2 = path18.resolve(input.candidatePath);
38936
+ const resolved2 = path19.resolve(input.candidatePath);
38732
38937
  return isPathInsideRoot(input.homeRoot, resolved2) ? resolved2 : null;
38733
38938
  }
38734
38939
  const resolved = await resolveDirectory(input.candidatePath);
@@ -38739,14 +38944,14 @@ async function resolveDirectoryCandidate(input) {
38739
38944
  }
38740
38945
  async function resolveWorkspaceCandidate(input) {
38741
38946
  if (input.dirent.isDirectory()) {
38742
- const resolved = path18.resolve(input.candidatePath);
38947
+ const resolved = path19.resolve(input.candidatePath);
38743
38948
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
38744
38949
  return null;
38745
38950
  }
38746
38951
  return { absolutePath: resolved, kind: "directory" };
38747
38952
  }
38748
38953
  if (input.dirent.isFile()) {
38749
- const resolved = path18.resolve(input.candidatePath);
38954
+ const resolved = path19.resolve(input.candidatePath);
38750
38955
  if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
38751
38956
  return null;
38752
38957
  }
@@ -38826,7 +39031,7 @@ function pruneWorkspaceEntryListCache() {
38826
39031
  // ../server/src/utils/directory-listing.ts
38827
39032
  import { readdir as readdir3, stat as stat5, realpath as realpath2 } from "node:fs/promises";
38828
39033
  import { homedir as homedir4 } from "node:os";
38829
- import path19 from "node:path";
39034
+ import path20 from "node:path";
38830
39035
  var DEFAULT_LIMIT2 = 500;
38831
39036
  async function listDirectoryContents(options) {
38832
39037
  const includeFiles = options.includeFiles ?? false;
@@ -38837,7 +39042,7 @@ async function listDirectoryContents(options) {
38837
39042
  const collected = [];
38838
39043
  for (const dirent of dirents) {
38839
39044
  if (!includeHidden && dirent.name.startsWith(".")) continue;
38840
- const childPath = path19.join(resolvedPath, dirent.name);
39045
+ const childPath = path20.join(resolvedPath, dirent.name);
38841
39046
  const kind = await classifyEntry(dirent, childPath);
38842
39047
  if (!kind) continue;
38843
39048
  if (kind === "file" && !includeFiles) continue;
@@ -38845,7 +39050,7 @@ async function listDirectoryContents(options) {
38845
39050
  if (collected.length >= limit) break;
38846
39051
  }
38847
39052
  collected.sort(compareEntries);
38848
- const parent = path19.dirname(resolvedPath);
39053
+ const parent = path20.dirname(resolvedPath);
38849
39054
  return {
38850
39055
  path: resolvedPath,
38851
39056
  parent: parent === resolvedPath ? null : parent,
@@ -38856,12 +39061,12 @@ async function resolveAbsolutePath(rawPath) {
38856
39061
  const home = process.env.HOME ?? homedir4();
38857
39062
  const trimmed = rawPath.trim();
38858
39063
  if (trimmed === "" || trimmed === "~") {
38859
- return path19.resolve(home);
39064
+ return path20.resolve(home);
38860
39065
  }
38861
39066
  if (trimmed.startsWith("~/")) {
38862
- return path19.resolve(home, trimmed.slice(2));
39067
+ return path20.resolve(home, trimmed.slice(2));
38863
39068
  }
38864
- if (!path19.isAbsolute(trimmed)) {
39069
+ if (!path20.isAbsolute(trimmed)) {
38865
39070
  throw new Error(
38866
39071
  `list_directory requires an absolute path, an empty string, or a "~"-prefixed path; got ${JSON.stringify(rawPath)}`
38867
39072
  );
@@ -38869,7 +39074,7 @@ async function resolveAbsolutePath(rawPath) {
38869
39074
  try {
38870
39075
  return await realpath2(trimmed);
38871
39076
  } catch {
38872
- return path19.resolve(trimmed);
39077
+ return path20.resolve(trimmed);
38873
39078
  }
38874
39079
  }
38875
39080
  async function classifyEntry(dirent, fullPath) {
@@ -38910,8 +39115,8 @@ function resolveClientMessageId(clientMessageId, generateId = uuidv45) {
38910
39115
 
38911
39116
  // ../server/src/server/chat/chat-service.ts
38912
39117
  import { randomUUID as randomUUID10 } from "node:crypto";
38913
- import { promises as fs12 } from "node:fs";
38914
- import path20 from "node:path";
39118
+ import { promises as fs13 } from "node:fs";
39119
+ import path21 from "node:path";
38915
39120
  import { z as z36 } from "zod";
38916
39121
  var ChatStorePayloadSchema = z36.object({
38917
39122
  rooms: z36.array(ChatRoomSchema),
@@ -38957,7 +39162,7 @@ var FileBackedChatService = class {
38957
39162
  this.messagesByRoomId = /* @__PURE__ */ new Map();
38958
39163
  this.persistQueue = Promise.resolve();
38959
39164
  this.waitersByRoomId = /* @__PURE__ */ new Map();
38960
- this.filePath = path20.join(options.appostleHome, "chat", "rooms.json");
39165
+ this.filePath = path21.join(options.appostleHome, "chat", "rooms.json");
38961
39166
  this.logger = options.logger.child({ component: "chat-service" });
38962
39167
  }
38963
39168
  async initialize() {
@@ -39152,7 +39357,7 @@ var FileBackedChatService = class {
39152
39357
  this.rooms.clear();
39153
39358
  this.messagesByRoomId.clear();
39154
39359
  try {
39155
- const raw = await fs12.readFile(this.filePath, "utf8");
39360
+ const raw = await fs13.readFile(this.filePath, "utf8");
39156
39361
  const parsed = ChatStorePayloadSchema.parse(JSON.parse(raw));
39157
39362
  for (const room of parsed.rooms) {
39158
39363
  this.rooms.set(room.id, room);
@@ -39183,10 +39388,10 @@ var FileBackedChatService = class {
39183
39388
  ),
39184
39389
  messages: Array.from(this.messagesByRoomId.values()).flat().sort((left, right) => left.createdAt.localeCompare(right.createdAt))
39185
39390
  };
39186
- await fs12.mkdir(path20.dirname(this.filePath), { recursive: true });
39391
+ await fs13.mkdir(path21.dirname(this.filePath), { recursive: true });
39187
39392
  const tempPath = `${this.filePath}.${process.pid}.${Date.now()}.${randomUUID10()}.tmp`;
39188
- await fs12.writeFile(tempPath, JSON.stringify(payload, null, 2), "utf8");
39189
- await fs12.rename(tempPath, this.filePath);
39393
+ await fs13.writeFile(tempPath, JSON.stringify(payload, null, 2), "utf8");
39394
+ await fs13.rename(tempPath, this.filePath);
39190
39395
  }
39191
39396
  /**
39192
39397
  * Look up a room by name, scoped to a specific owner (used by createRoom's
@@ -39358,42 +39563,42 @@ function buildChatMentionNotification(input) {
39358
39563
  }
39359
39564
 
39360
39565
  // ../server/src/server/roles/scanner.ts
39361
- import fs13 from "node:fs/promises";
39362
- import os8 from "node:os";
39363
- import path21 from "node:path";
39566
+ import fs14 from "node:fs/promises";
39567
+ import os9 from "node:os";
39568
+ import path22 from "node:path";
39364
39569
  var NAME_REGEX2 = /^[a-z0-9][a-z0-9._-]*$/i;
39365
39570
  function homeDir2() {
39366
- return process.env.HOME || os8.homedir();
39571
+ return process.env.HOME || os9.homedir();
39367
39572
  }
39368
39573
  function resolveScopeDir2(scope, workspaceRoot) {
39369
39574
  if (scope === "project") {
39370
39575
  if (!workspaceRoot) {
39371
39576
  throw new Error('workspaceRoot is required for scope "project"');
39372
39577
  }
39373
- return path21.join(workspaceRoot, ".roles");
39578
+ return path22.join(workspaceRoot, ".roles");
39374
39579
  }
39375
- return path21.join(homeDir2(), ".appostle", ".roles");
39580
+ return path22.join(homeDir2(), ".appostle", ".roles");
39376
39581
  }
39377
39582
  function allowedRoots2(workspaceRoot) {
39378
- const roots = [path21.join(homeDir2(), ".appostle", ".roles")];
39583
+ const roots = [path22.join(homeDir2(), ".appostle", ".roles")];
39379
39584
  if (workspaceRoot) {
39380
- roots.push(path21.join(workspaceRoot, ".roles"));
39585
+ roots.push(path22.join(workspaceRoot, ".roles"));
39381
39586
  }
39382
- return roots.map((r) => path21.resolve(r));
39587
+ return roots.map((r) => path22.resolve(r));
39383
39588
  }
39384
39589
  function isInsideAllowedRoot2(absPath, workspaceRoot) {
39385
- const resolved = path21.resolve(absPath);
39590
+ const resolved = path22.resolve(absPath);
39386
39591
  for (const root of allowedRoots2(workspaceRoot)) {
39387
- const rel = path21.relative(root, resolved);
39388
- if (rel === "" || !rel.startsWith("..") && !path21.isAbsolute(rel)) {
39592
+ const rel = path22.relative(root, resolved);
39593
+ if (rel === "" || !rel.startsWith("..") && !path22.isAbsolute(rel)) {
39389
39594
  return true;
39390
39595
  }
39391
39596
  }
39392
39597
  return false;
39393
39598
  }
39394
- var FRONTMATTER_RE2 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
39599
+ var FRONTMATTER_RE3 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
39395
39600
  function parseRoleFile(text) {
39396
- const match = FRONTMATTER_RE2.exec(text);
39601
+ const match = FRONTMATTER_RE3.exec(text);
39397
39602
  if (!match) {
39398
39603
  return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
39399
39604
  }
@@ -39573,7 +39778,7 @@ function rewriteFrontmatter2(originalLines, next) {
39573
39778
  async function readRolesFromDir(scope, dir, category) {
39574
39779
  let entries;
39575
39780
  try {
39576
- entries = await fs13.readdir(dir, { withFileTypes: true });
39781
+ entries = await fs14.readdir(dir, { withFileTypes: true });
39577
39782
  } catch {
39578
39783
  return [];
39579
39784
  }
@@ -39583,17 +39788,17 @@ async function readRolesFromDir(scope, dir, category) {
39583
39788
  if (!entry.name.endsWith(".md")) continue;
39584
39789
  const name = entry.name.slice(0, -".md".length);
39585
39790
  if (!name || !NAME_REGEX2.test(name)) continue;
39586
- const fullPath = path21.join(dir, entry.name);
39791
+ const fullPath = path22.join(dir, entry.name);
39587
39792
  let stat10;
39588
39793
  try {
39589
- const s = await fs13.stat(fullPath);
39794
+ const s = await fs14.stat(fullPath);
39590
39795
  stat10 = { mtime: s.mtime, size: s.size };
39591
39796
  } catch {
39592
39797
  continue;
39593
39798
  }
39594
39799
  let parsed;
39595
39800
  try {
39596
- const text = await fs13.readFile(fullPath, "utf8");
39801
+ const text = await fs14.readFile(fullPath, "utf8");
39597
39802
  parsed = parseFrontmatter(text);
39598
39803
  } catch {
39599
39804
  parsed = {
@@ -39627,13 +39832,13 @@ async function readRolesFromDir(scope, dir, category) {
39627
39832
  async function readRolesFromScopeDir(scope, scopeDir) {
39628
39833
  let topEntries;
39629
39834
  try {
39630
- topEntries = await fs13.readdir(scopeDir, { withFileTypes: true });
39835
+ topEntries = await fs14.readdir(scopeDir, { withFileTypes: true });
39631
39836
  } catch {
39632
39837
  return [];
39633
39838
  }
39634
39839
  const flat = await readRolesFromDir(scope, scopeDir, null);
39635
39840
  const categoryResults = await Promise.all(
39636
- topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path21.join(scopeDir, e.name), e.name))
39841
+ topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path22.join(scopeDir, e.name), e.name))
39637
39842
  );
39638
39843
  const all = [...flat, ...categoryResults.flat()];
39639
39844
  all.sort((a, b) => {
@@ -39668,7 +39873,7 @@ async function resolveRole(args) {
39668
39873
  const match = roles.find((r) => r.name === args.roleName);
39669
39874
  if (!match) return null;
39670
39875
  try {
39671
- const text = await fs13.readFile(match.path, "utf8");
39876
+ const text = await fs14.readFile(match.path, "utf8");
39672
39877
  const parsed = parseRoleFile(text);
39673
39878
  const fm = parseFrontmatter(text);
39674
39879
  const body = parsed.body.trim();
@@ -39688,11 +39893,11 @@ async function createRole(args) {
39688
39893
  throw new Error(`Role name must not contain path separators or "..".`);
39689
39894
  }
39690
39895
  const scopeDir = resolveScopeDir2(args.scope, args.workspaceRoot);
39691
- const dir = args.category ? path21.join(scopeDir, args.category) : scopeDir;
39692
- await fs13.mkdir(dir, { recursive: true });
39693
- const filePath = path21.join(dir, `${args.name}.md`);
39896
+ const dir = args.category ? path22.join(scopeDir, args.category) : scopeDir;
39897
+ await fs14.mkdir(dir, { recursive: true });
39898
+ const filePath = path22.join(dir, `${args.name}.md`);
39694
39899
  try {
39695
- await fs13.access(filePath);
39900
+ await fs14.access(filePath);
39696
39901
  throw new Error(`A role named "${args.name}" already exists at ${filePath}`);
39697
39902
  } catch (err) {
39698
39903
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -39703,7 +39908,7 @@ async function createRole(args) {
39703
39908
  }
39704
39909
  }
39705
39910
  const initial = buildStarterRole(args.name);
39706
- await fs13.writeFile(filePath, initial, "utf8");
39911
+ await fs14.writeFile(filePath, initial, "utf8");
39707
39912
  return { path: filePath };
39708
39913
  }
39709
39914
  function buildStarterRole(name) {
@@ -39724,7 +39929,7 @@ the role is invoked.
39724
39929
  `;
39725
39930
  }
39726
39931
  async function writeRoleFrontmatter(args, workspaceRoot) {
39727
- if (!path21.isAbsolute(args.path)) {
39932
+ if (!path22.isAbsolute(args.path)) {
39728
39933
  throw new Error(`writeRoleFrontmatter expects an absolute path; got "${args.path}"`);
39729
39934
  }
39730
39935
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
@@ -39732,7 +39937,7 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
39732
39937
  }
39733
39938
  let original;
39734
39939
  try {
39735
- original = await fs13.readFile(args.path, "utf8");
39940
+ original = await fs14.readFile(args.path, "utf8");
39736
39941
  } catch (err) {
39737
39942
  throw new Error(
39738
39943
  `Failed to read role file: ${err instanceof Error ? err.message : String(err)}`
@@ -39745,23 +39950,23 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
39745
39950
  ${parsed.body}` : `${nextFrontmatter}
39746
39951
 
39747
39952
  ${original}`;
39748
- await fs13.writeFile(args.path, nextContent, "utf8");
39953
+ await fs14.writeFile(args.path, nextContent, "utf8");
39749
39954
  }
39750
39955
  async function moveRole(args, workspaceRoot) {
39751
- if (!path21.isAbsolute(args.path)) {
39956
+ if (!path22.isAbsolute(args.path)) {
39752
39957
  throw new Error(`moveRole expects an absolute path; got "${args.path}"`);
39753
39958
  }
39754
39959
  if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
39755
39960
  throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
39756
39961
  }
39757
- const oldDir = path21.dirname(args.path);
39758
- const oldFilename = path21.basename(args.path, ".md");
39962
+ const oldDir = path22.dirname(args.path);
39963
+ const oldFilename = path22.basename(args.path, ".md");
39759
39964
  const roots = allowedRoots2(workspaceRoot);
39760
- const rolesRoot = roots.find((r) => path21.resolve(args.path).startsWith(r));
39965
+ const rolesRoot = roots.find((r) => path22.resolve(args.path).startsWith(r));
39761
39966
  if (!rolesRoot) {
39762
39967
  throw new Error(`Cannot determine roles root for "${args.path}"`);
39763
39968
  }
39764
- const relFromRoot = path21.relative(rolesRoot, path21.dirname(args.path));
39969
+ const relFromRoot = path22.relative(rolesRoot, path22.dirname(args.path));
39765
39970
  const currentCategory = relFromRoot && relFromRoot !== "." ? relFromRoot : "";
39766
39971
  const newName = args.newName ?? oldFilename;
39767
39972
  const newCategory = args.newCategory !== void 0 ? args.newCategory : currentCategory;
@@ -39771,19 +39976,19 @@ async function moveRole(args, workspaceRoot) {
39771
39976
  if (newCategory && !NAME_REGEX2.test(newCategory)) {
39772
39977
  throw new Error(`Invalid category name: "${newCategory}"`);
39773
39978
  }
39774
- const newDir = newCategory ? path21.join(rolesRoot, newCategory) : rolesRoot;
39775
- const newPath = path21.join(newDir, `${newName}.md`);
39776
- if (path21.resolve(newPath) === path21.resolve(args.path)) {
39979
+ const newDir = newCategory ? path22.join(rolesRoot, newCategory) : rolesRoot;
39980
+ const newPath = path22.join(newDir, `${newName}.md`);
39981
+ if (path22.resolve(newPath) === path22.resolve(args.path)) {
39777
39982
  return { path: args.path };
39778
39983
  }
39779
- await fs13.mkdir(newDir, { recursive: true });
39984
+ await fs14.mkdir(newDir, { recursive: true });
39780
39985
  try {
39781
- await fs13.access(newPath);
39986
+ await fs14.access(newPath);
39782
39987
  throw new Error(`A role already exists at "${newPath}"`);
39783
39988
  } catch (err) {
39784
39989
  if (err.code !== "ENOENT") throw err;
39785
39990
  }
39786
- await fs13.rename(args.path, newPath);
39991
+ await fs14.rename(args.path, newPath);
39787
39992
  const frontmatterPatch = {};
39788
39993
  if (newName !== oldFilename) {
39789
39994
  frontmatterPatch.description = void 0;
@@ -39799,9 +40004,9 @@ async function moveRole(args, workspaceRoot) {
39799
40004
  );
39800
40005
  if (oldDir !== rolesRoot) {
39801
40006
  try {
39802
- const remaining = await fs13.readdir(oldDir);
40007
+ const remaining = await fs14.readdir(oldDir);
39803
40008
  if (remaining.length === 0) {
39804
- await fs13.rmdir(oldDir);
40009
+ await fs14.rmdir(oldDir);
39805
40010
  }
39806
40011
  } catch {
39807
40012
  }
@@ -39810,18 +40015,18 @@ async function moveRole(args, workspaceRoot) {
39810
40015
  }
39811
40016
 
39812
40017
  // ../server/src/server/brands/scanner.ts
39813
- import fs14 from "node:fs/promises";
39814
- import path22 from "node:path";
40018
+ import fs15 from "node:fs/promises";
40019
+ import path23 from "node:path";
39815
40020
  var CATEGORY_REGEX = /^[a-z0-9][a-z0-9_-]*$/i;
39816
40021
  function resolveAssetsDir(workspaceRoot, category) {
39817
- const base = path22.join(workspaceRoot, ".appostle", "brand", "assets");
40022
+ const base = path23.join(workspaceRoot, ".appostle", "brand", "assets");
39818
40023
  if (!category) return base;
39819
40024
  if (!CATEGORY_REGEX.test(category) || category.includes("..")) {
39820
40025
  throw new Error(
39821
40026
  `Invalid asset category "${category}". Use letters, digits, dot, underscore, dash.`
39822
40027
  );
39823
40028
  }
39824
- return path22.join(base, category);
40029
+ return path23.join(base, category);
39825
40030
  }
39826
40031
  function relativePathFor(category, fileName) {
39827
40032
  return category ? `assets/${category}/${fileName}` : `assets/${fileName}`;
@@ -39829,7 +40034,7 @@ function relativePathFor(category, fileName) {
39829
40034
  async function removeSiblingExtensions(dir, targetName, keepFileName) {
39830
40035
  let entries;
39831
40036
  try {
39832
- entries = await fs14.readdir(dir);
40037
+ entries = await fs15.readdir(dir);
39833
40038
  } catch {
39834
40039
  return;
39835
40040
  }
@@ -39837,9 +40042,9 @@ async function removeSiblingExtensions(dir, targetName, keepFileName) {
39837
40042
  if (entry === keepFileName) continue;
39838
40043
  if (!entry.startsWith(`${targetName}.`)) continue;
39839
40044
  try {
39840
- const stat10 = await fs14.stat(path22.join(dir, entry));
40045
+ const stat10 = await fs15.stat(path23.join(dir, entry));
39841
40046
  if (!stat10.isFile()) continue;
39842
- await fs14.unlink(path22.join(dir, entry));
40047
+ await fs15.unlink(path23.join(dir, entry));
39843
40048
  } catch {
39844
40049
  }
39845
40050
  }
@@ -39850,7 +40055,7 @@ function convertSvgToMono(svg, color) {
39850
40055
  async function runDerivations(options) {
39851
40056
  const { primaryAbsolutePath, assetsDir, category, derive } = options;
39852
40057
  if (derive.length === 0) return [];
39853
- const ext = path22.extname(primaryAbsolutePath).toLowerCase();
40058
+ const ext = path23.extname(primaryAbsolutePath).toLowerCase();
39854
40059
  const isSvg = ext === ".svg";
39855
40060
  const results = [];
39856
40061
  for (const spec of derive) {
@@ -39882,12 +40087,12 @@ async function runDerivations(options) {
39882
40087
  continue;
39883
40088
  }
39884
40089
  try {
39885
- const sourceText = await fs14.readFile(primaryAbsolutePath, "utf8");
40090
+ const sourceText = await fs15.readFile(primaryAbsolutePath, "utf8");
39886
40091
  const monoText = convertSvgToMono(sourceText, spec.color);
39887
40092
  const fileName = `${spec.targetName}${ext}`;
39888
- const destAbs = path22.resolve(assetsDir, fileName);
39889
- const rel = path22.relative(path22.resolve(assetsDir), destAbs);
39890
- if (rel.startsWith("..") || path22.isAbsolute(rel)) {
40093
+ const destAbs = path23.resolve(assetsDir, fileName);
40094
+ const rel = path23.relative(path23.resolve(assetsDir), destAbs);
40095
+ if (rel.startsWith("..") || path23.isAbsolute(rel)) {
39891
40096
  results.push({
39892
40097
  targetName: spec.targetName,
39893
40098
  relativePath: "",
@@ -39896,7 +40101,7 @@ async function runDerivations(options) {
39896
40101
  });
39897
40102
  continue;
39898
40103
  }
39899
- await fs14.writeFile(destAbs, monoText, "utf8");
40104
+ await fs15.writeFile(destAbs, monoText, "utf8");
39900
40105
  results.push({
39901
40106
  targetName: spec.targetName,
39902
40107
  relativePath: relativePathFor(category, fileName),
@@ -39921,30 +40126,30 @@ function resolveScopeDir3(scope, workspaceRoot) {
39921
40126
  if (!workspaceRoot) {
39922
40127
  throw new Error('workspaceRoot is required for scope "project"');
39923
40128
  }
39924
- return path22.join(workspaceRoot, ".appostle", "brand");
40129
+ return path23.join(workspaceRoot, ".appostle", "brand");
39925
40130
  }
39926
40131
  throw new Error(`Unknown scope: ${scope}`);
39927
40132
  }
39928
40133
  function allowedRoots3(workspaceRoot) {
39929
40134
  const roots = [];
39930
40135
  if (workspaceRoot) {
39931
- roots.push(path22.join(workspaceRoot, ".appostle", "brand"));
40136
+ roots.push(path23.join(workspaceRoot, ".appostle", "brand"));
39932
40137
  }
39933
- return roots.map((r) => path22.resolve(r));
40138
+ return roots.map((r) => path23.resolve(r));
39934
40139
  }
39935
40140
  function isInsideAllowedRoot3(absPath, workspaceRoot) {
39936
- const resolved = path22.resolve(absPath);
40141
+ const resolved = path23.resolve(absPath);
39937
40142
  for (const root of allowedRoots3(workspaceRoot)) {
39938
- const rel = path22.relative(root, resolved);
39939
- if (rel === "" || !rel.startsWith("..") && !path22.isAbsolute(rel)) {
40143
+ const rel = path23.relative(root, resolved);
40144
+ if (rel === "" || !rel.startsWith("..") && !path23.isAbsolute(rel)) {
39940
40145
  return true;
39941
40146
  }
39942
40147
  }
39943
40148
  return false;
39944
40149
  }
39945
- var FRONTMATTER_RE3 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
40150
+ var FRONTMATTER_RE4 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
39946
40151
  function parseBrandFile(text) {
39947
- const match = FRONTMATTER_RE3.exec(text);
40152
+ const match = FRONTMATTER_RE4.exec(text);
39948
40153
  if (!match) {
39949
40154
  return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
39950
40155
  }
@@ -40140,7 +40345,7 @@ function rewriteFrontmatter3(originalLines, next) {
40140
40345
  async function readBrandsFromDir(scope, dir) {
40141
40346
  let entries;
40142
40347
  try {
40143
- entries = await fs14.readdir(dir, { withFileTypes: true });
40348
+ entries = await fs15.readdir(dir, { withFileTypes: true });
40144
40349
  } catch {
40145
40350
  return [];
40146
40351
  }
@@ -40150,19 +40355,21 @@ async function readBrandsFromDir(scope, dir) {
40150
40355
  if (!entry.name.endsWith(".md")) continue;
40151
40356
  const name = entry.name.slice(0, -".md".length);
40152
40357
  if (!name || !NAME_REGEX3.test(name)) continue;
40153
- const fullPath = path22.join(dir, entry.name);
40358
+ const fullPath = path23.join(dir, entry.name);
40154
40359
  let stat10;
40155
40360
  try {
40156
- const s = await fs14.stat(fullPath);
40361
+ const s = await fs15.stat(fullPath);
40157
40362
  stat10 = { mtime: s.mtime, size: s.size };
40158
40363
  } catch {
40159
40364
  continue;
40160
40365
  }
40161
40366
  let parsed;
40367
+ let body = "";
40162
40368
  try {
40163
- const text = await fs14.readFile(fullPath, "utf8");
40164
- const { rawFrontmatterLines, hadFrontmatter } = parseBrandFile(text);
40369
+ const text = await fs15.readFile(fullPath, "utf8");
40370
+ const { rawFrontmatterLines, body: parsedBody, hadFrontmatter } = parseBrandFile(text);
40165
40371
  parsed = hadFrontmatter ? parseFrontmatter2(rawFrontmatterLines) : { description: "", variables: [] };
40372
+ body = parsedBody;
40166
40373
  } catch {
40167
40374
  parsed = { description: "", variables: [] };
40168
40375
  }
@@ -40174,7 +40381,8 @@ async function readBrandsFromDir(scope, dir) {
40174
40381
  variables: parsed.variables,
40175
40382
  path: fullPath,
40176
40383
  modifiedAt: stat10.mtime.toISOString(),
40177
- size: stat10.size
40384
+ size: stat10.size,
40385
+ body
40178
40386
  });
40179
40387
  }
40180
40388
  results.sort((a, b) => a.name.localeCompare(b.name));
@@ -40193,10 +40401,10 @@ async function createBrand(args) {
40193
40401
  throw new Error(`Brand name must not contain path separators or "..".`);
40194
40402
  }
40195
40403
  const dir = resolveScopeDir3("project", args.workspaceRoot);
40196
- await fs14.mkdir(dir, { recursive: true });
40197
- const filePath = path22.join(dir, `${args.name}.md`);
40404
+ await fs15.mkdir(dir, { recursive: true });
40405
+ const filePath = path23.join(dir, `${args.name}.md`);
40198
40406
  try {
40199
- await fs14.access(filePath);
40407
+ await fs15.access(filePath);
40200
40408
  throw new Error(`A brand named "${args.name}" already exists at ${filePath}`);
40201
40409
  } catch (err) {
40202
40410
  if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
@@ -40206,7 +40414,7 @@ async function createBrand(args) {
40206
40414
  throw err;
40207
40415
  }
40208
40416
  }
40209
- await fs14.writeFile(filePath, buildStarterBrand(args.name), "utf8");
40417
+ await fs15.writeFile(filePath, buildStarterBrand(args.name), "utf8");
40210
40418
  return { path: filePath };
40211
40419
  }
40212
40420
  function buildStarterBrand(name) {
@@ -40250,7 +40458,7 @@ async function copyBrandAsset(args) {
40250
40458
  if (!args.workspaceRoot) {
40251
40459
  throw new Error("workspaceRoot is required to copy a brand asset");
40252
40460
  }
40253
- if (!args.sourcePath || !path22.isAbsolute(args.sourcePath)) {
40461
+ if (!args.sourcePath || !path23.isAbsolute(args.sourcePath)) {
40254
40462
  throw new Error(`copyBrandAsset expects an absolute sourcePath; got "${args.sourcePath}"`);
40255
40463
  }
40256
40464
  if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
@@ -40258,21 +40466,21 @@ async function copyBrandAsset(args) {
40258
40466
  `Invalid targetName "${args.targetName}". Use letters, digits, dot, underscore, dash.`
40259
40467
  );
40260
40468
  }
40261
- const stats = await fs14.stat(args.sourcePath);
40469
+ const stats = await fs15.stat(args.sourcePath);
40262
40470
  if (!stats.isFile()) {
40263
40471
  throw new Error(`Source path is not a regular file: ${args.sourcePath}`);
40264
40472
  }
40265
- const ext = path22.extname(args.sourcePath).toLowerCase();
40473
+ const ext = path23.extname(args.sourcePath).toLowerCase();
40266
40474
  const fileName = `${args.targetName}${ext}`;
40267
40475
  const assetsDir = resolveAssetsDir(args.workspaceRoot, args.category);
40268
- const destAbs = path22.resolve(assetsDir, fileName);
40269
- const rel = path22.relative(path22.resolve(assetsDir), destAbs);
40270
- if (rel.startsWith("..") || path22.isAbsolute(rel)) {
40476
+ const destAbs = path23.resolve(assetsDir, fileName);
40477
+ const rel = path23.relative(path23.resolve(assetsDir), destAbs);
40478
+ if (rel.startsWith("..") || path23.isAbsolute(rel)) {
40271
40479
  throw new Error(`Refusing to write outside of .appostle/brand/assets: ${destAbs}`);
40272
40480
  }
40273
- await fs14.mkdir(assetsDir, { recursive: true });
40481
+ await fs15.mkdir(assetsDir, { recursive: true });
40274
40482
  await removeSiblingExtensions(assetsDir, args.targetName, fileName);
40275
- await fs14.copyFile(args.sourcePath, destAbs);
40483
+ await fs15.copyFile(args.sourcePath, destAbs);
40276
40484
  const derived = await runDerivations({
40277
40485
  primaryAbsolutePath: destAbs,
40278
40486
  assetsDir,
@@ -40297,13 +40505,13 @@ async function uploadBrandAsset(args) {
40297
40505
  if (!args.dataBase64 || args.dataBase64.trim().length === 0) {
40298
40506
  throw new Error("No file data provided for brand asset upload");
40299
40507
  }
40300
- const extFromSource = args.sourceName ? path22.extname(args.sourceName).toLowerCase() : "";
40508
+ const extFromSource = args.sourceName ? path23.extname(args.sourceName).toLowerCase() : "";
40301
40509
  const ext = extFromSource || ".png";
40302
40510
  const fileName = `${args.targetName}${ext}`;
40303
40511
  const assetsDir = resolveAssetsDir(args.workspaceRoot, args.category);
40304
- const destAbs = path22.resolve(assetsDir, fileName);
40305
- const rel = path22.relative(path22.resolve(assetsDir), destAbs);
40306
- if (rel.startsWith("..") || path22.isAbsolute(rel)) {
40512
+ const destAbs = path23.resolve(assetsDir, fileName);
40513
+ const rel = path23.relative(path23.resolve(assetsDir), destAbs);
40514
+ if (rel.startsWith("..") || path23.isAbsolute(rel)) {
40307
40515
  throw new Error(`Refusing to write outside of .appostle/brand/assets: ${destAbs}`);
40308
40516
  }
40309
40517
  let data;
@@ -40315,9 +40523,9 @@ async function uploadBrandAsset(args) {
40315
40523
  if (data.length === 0) {
40316
40524
  throw new Error("Uploaded brand asset is empty");
40317
40525
  }
40318
- await fs14.mkdir(assetsDir, { recursive: true });
40526
+ await fs15.mkdir(assetsDir, { recursive: true });
40319
40527
  await removeSiblingExtensions(assetsDir, args.targetName, fileName);
40320
- await fs14.writeFile(destAbs, data);
40528
+ await fs15.writeFile(destAbs, data);
40321
40529
  const derived = await runDerivations({
40322
40530
  primaryAbsolutePath: destAbs,
40323
40531
  assetsDir,
@@ -40331,7 +40539,7 @@ async function uploadBrandAsset(args) {
40331
40539
  };
40332
40540
  }
40333
40541
  async function writeBrandFrontmatter(args, workspaceRoot) {
40334
- if (!path22.isAbsolute(args.path)) {
40542
+ if (!path23.isAbsolute(args.path)) {
40335
40543
  throw new Error(`writeBrandFrontmatter expects an absolute path; got "${args.path}"`);
40336
40544
  }
40337
40545
  if (!isInsideAllowedRoot3(args.path, workspaceRoot)) {
@@ -40339,7 +40547,7 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
40339
40547
  }
40340
40548
  let original;
40341
40549
  try {
40342
- original = await fs14.readFile(args.path, "utf8");
40550
+ original = await fs15.readFile(args.path, "utf8");
40343
40551
  } catch (err) {
40344
40552
  throw new Error(
40345
40553
  `Failed to read brand file: ${err instanceof Error ? err.message : String(err)}`
@@ -40352,7 +40560,7 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
40352
40560
  ${parsed.body}` : `${nextFrontmatter}
40353
40561
 
40354
40562
  ${original}`;
40355
- await fs14.writeFile(args.path, nextContent, "utf8");
40563
+ await fs15.writeFile(args.path, nextContent, "utf8");
40356
40564
  }
40357
40565
 
40358
40566
  // ../server/src/server/brand/token-generator.ts
@@ -40613,73 +40821,194 @@ async function generateAndApplyBrandTokens(options) {
40613
40821
  return { generatedCount: acceptedUpdates.size };
40614
40822
  }
40615
40823
 
40824
+ // ../server/src/server/brand/photography-analyzer.ts
40825
+ import { z as z38 } from "zod";
40826
+ var PhotographyResponseSchema = z38.object({
40827
+ dna: z38.string().min(1),
40828
+ dials: z38.record(z38.string(), z38.string())
40829
+ });
40830
+ function buildPrompt3(args) {
40831
+ const dialLines = args.selectVars.map((v) => {
40832
+ const options = (v.options ?? []).join(", ");
40833
+ return `- ${v.key} (${v.label || v.key}): current="${v.value || "(unset)"}" options=[${options}]`;
40834
+ }).join("\n");
40835
+ const refSection = args.referenceDescriptions.length > 0 ? [
40836
+ "",
40837
+ "Reference image filenames/descriptions provided by the user:",
40838
+ ...args.referenceDescriptions.map((r) => `- ${r}`)
40839
+ ].join("\n") : "";
40840
+ return [
40841
+ "You are a photography director analyzing a brand's photographic identity.",
40842
+ "The user has provided a brief describing the visual direction they want.",
40843
+ "Your job is to:",
40844
+ "1. Write a rich, specific Style DNA (2-3 sentences describing the photographic",
40845
+ " identity in concrete visual terms \u2014 lighting quality, tonal mood, depth",
40846
+ " characteristics, color rendering. No marketing fluff.)",
40847
+ "2. Pre-dial every camera control to match that style direction.",
40848
+ " ONLY use values that appear in the options list for each dial.",
40849
+ "",
40850
+ "User brief:",
40851
+ args.brief,
40852
+ refSection,
40853
+ "",
40854
+ "Camera dials (key, current value, valid options):",
40855
+ dialLines,
40856
+ "",
40857
+ "Return JSON only with the shape:",
40858
+ '{ "dna": "Rich style DNA description...", "dials": { "key": "value", ... } }',
40859
+ "",
40860
+ "Rules:",
40861
+ "- The dna field must be a non-empty string, 2-3 sentences.",
40862
+ "- Every value in dials must be one of the listed options for that key.",
40863
+ "- Include ALL dial keys in dials, even if you keep the current value.",
40864
+ "- Do not invent new keys. Do not output anything outside the JSON."
40865
+ ].filter((line) => line !== "").join("\n");
40866
+ }
40867
+ async function analyzeAndApplyPhotographyStyle(options) {
40868
+ const { agentManager, workspaceRoot, brandPath, brief, referenceDescriptions, logger } = options;
40869
+ const brands = await listBrands({ workspaceRoot });
40870
+ const photoBrand = brands.find((b) => b.path === brandPath) ?? null;
40871
+ if (!photoBrand) {
40872
+ throw new Error(`Brand file not found at ${brandPath}`);
40873
+ }
40874
+ const allVars = photoBrand.variables;
40875
+ const selectVars = allVars.filter(
40876
+ (v) => v.type === "select" && v.options && v.options.length > 0
40877
+ );
40878
+ if (selectVars.length === 0) {
40879
+ throw new Error("No camera dials found in photography brand file");
40880
+ }
40881
+ const validOptions = /* @__PURE__ */ new Map();
40882
+ for (const v of selectVars) {
40883
+ validOptions.set(v.key, new Set(v.options ?? []));
40884
+ }
40885
+ const agentPrompt = buildPrompt3({
40886
+ brief,
40887
+ referenceDescriptions: referenceDescriptions ?? [],
40888
+ selectVars
40889
+ });
40890
+ let response;
40891
+ try {
40892
+ response = await generateStructuredAgentResponseWithFallback({
40893
+ manager: agentManager,
40894
+ cwd: workspaceRoot,
40895
+ prompt: agentPrompt,
40896
+ schema: PhotographyResponseSchema,
40897
+ schemaName: "PhotographyAnalysis",
40898
+ maxRetries: 2,
40899
+ providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
40900
+ agentConfigOverrides: {
40901
+ title: "Photography style analyzer",
40902
+ internal: true
40903
+ }
40904
+ });
40905
+ } catch (error) {
40906
+ if (error instanceof StructuredAgentResponseError || error instanceof StructuredAgentFallbackError) {
40907
+ logger.warn({ err: error, brandPath }, "Structured photography analysis failed");
40908
+ throw new Error("Photography analysis failed \u2014 the agent did not return valid JSON");
40909
+ }
40910
+ throw error;
40911
+ }
40912
+ const acceptedUpdates = /* @__PURE__ */ new Map();
40913
+ for (const [key, value] of Object.entries(response.dials)) {
40914
+ if (typeof value !== "string") continue;
40915
+ const trimmed = value.trim();
40916
+ const opts = validOptions.get(key);
40917
+ if (!opts) continue;
40918
+ if (!opts.has(trimmed)) continue;
40919
+ acceptedUpdates.set(key, trimmed);
40920
+ }
40921
+ acceptedUpdates.set("photo.dna", response.dna);
40922
+ acceptedUpdates.set("photo.brief", brief);
40923
+ const nextVariables = allVars.map((v) => {
40924
+ const update = acceptedUpdates.get(v.key);
40925
+ if (update === void 0) return v;
40926
+ return { ...v, value: update };
40927
+ });
40928
+ await writeBrandFrontmatter(
40929
+ {
40930
+ path: brandPath,
40931
+ frontmatter: {
40932
+ description: photoBrand.description,
40933
+ variables: nextVariables
40934
+ }
40935
+ },
40936
+ workspaceRoot
40937
+ );
40938
+ logger.info(
40939
+ { brandPath, updatedCount: acceptedUpdates.size },
40940
+ "photography-analyzer: applied style analysis"
40941
+ );
40942
+ return { updatedCount: acceptedUpdates.size };
40943
+ }
40944
+
40616
40945
  // ../server/src/server/brand/layout-generator.ts
40617
- import { promises as fs15 } from "node:fs";
40618
- import path23 from "node:path";
40946
+ import { promises as fs16 } from "node:fs";
40947
+ import path24 from "node:path";
40619
40948
  import { fileURLToPath as fileURLToPath3 } from "node:url";
40620
- import { z as z38 } from "zod";
40949
+ import { z as z39 } from "zod";
40621
40950
  var QA_FILENAME = "layout-qa.md";
40622
40951
  var PROMPT_FILENAME = "layout-prompt.md";
40623
40952
  var MAX_LOOKUP_LEVELS = 10;
40624
40953
  var ROLE_FILE_RELATIVE = ".appostle/brand/assets/role/brand-layout-role.md";
40625
40954
  async function findFileUpward(filename) {
40626
- let dir = path23.dirname(fileURLToPath3(import.meta.url));
40955
+ let dir = path24.dirname(fileURLToPath3(import.meta.url));
40627
40956
  for (let i = 0; i < MAX_LOOKUP_LEVELS; i++) {
40628
- const candidate = path23.join(dir, filename);
40957
+ const candidate = path24.join(dir, filename);
40629
40958
  try {
40630
- await fs15.access(candidate);
40959
+ await fs16.access(candidate);
40631
40960
  return candidate;
40632
40961
  } catch {
40633
40962
  }
40634
- const parent = path23.dirname(dir);
40963
+ const parent = path24.dirname(dir);
40635
40964
  if (parent === dir) break;
40636
40965
  dir = parent;
40637
40966
  }
40638
40967
  return null;
40639
40968
  }
40640
40969
  function getRoleFilePath(workspaceRoot) {
40641
- return path23.join(workspaceRoot, ROLE_FILE_RELATIVE);
40970
+ return path24.join(workspaceRoot, ROLE_FILE_RELATIVE);
40642
40971
  }
40643
40972
  async function readRoleFile(workspaceRoot) {
40644
40973
  try {
40645
- return await fs15.readFile(getRoleFilePath(workspaceRoot), "utf8");
40974
+ return await fs16.readFile(getRoleFilePath(workspaceRoot), "utf8");
40646
40975
  } catch {
40647
40976
  return null;
40648
40977
  }
40649
40978
  }
40650
40979
  async function writeRoleFile(workspaceRoot, content) {
40651
40980
  const filePath = getRoleFilePath(workspaceRoot);
40652
- await fs15.mkdir(path23.dirname(filePath), { recursive: true });
40653
- await fs15.writeFile(filePath, content, "utf8");
40654
- }
40655
- var QaNextResponseSchema = z38.object({
40656
- done: z38.boolean(),
40657
- question: z38.string().optional(),
40658
- options: z38.array(z38.string()).optional(),
40659
- roleDocument: z38.string().optional()
40660
- });
40661
- var RefinementResponseSchema = z38.object({
40662
- roleDocument: z38.string()
40663
- });
40664
- var WireframeBlockSchema = z38.object({
40665
- type: z38.enum(["headline", "subheadline", "text", "image", "cta", "spacer"]),
40666
- widthFraction: z38.number(),
40667
- heightVh: z38.number()
40668
- });
40669
- var WireframeSectionSchema = z38.object({
40670
- type: z38.enum(["hero", "features", "content", "cta", "footer", "divider"]),
40671
- widthMode: z38.enum(["full-bleed", "contained"]),
40672
- heightVh: z38.number(),
40673
- arrangement: z38.enum(["single-column", "split", "asymmetric-grid", "bento", "stacked"]),
40674
- blocks: z38.array(WireframeBlockSchema),
40675
- bg: z38.enum(["neutral", "alt", "accent", "image"]),
40676
- gapAfterVh: z38.number()
40677
- });
40678
- var WireframeDataSchema = z38.object({
40679
- viewport: z38.object({ width: z38.number(), height: z38.number() }),
40680
- maxWidth: z38.number(),
40681
- pageBg: z38.string(),
40682
- sections: z38.array(WireframeSectionSchema)
40981
+ await fs16.mkdir(path24.dirname(filePath), { recursive: true });
40982
+ await fs16.writeFile(filePath, content, "utf8");
40983
+ }
40984
+ var QaNextResponseSchema = z39.object({
40985
+ done: z39.boolean(),
40986
+ question: z39.string().optional(),
40987
+ options: z39.array(z39.string()).optional(),
40988
+ roleDocument: z39.string().optional()
40989
+ });
40990
+ var RefinementResponseSchema = z39.object({
40991
+ roleDocument: z39.string()
40992
+ });
40993
+ var WireframeBlockSchema = z39.object({
40994
+ type: z39.enum(["headline", "subheadline", "text", "image", "cta", "spacer"]),
40995
+ widthFraction: z39.number(),
40996
+ heightVh: z39.number()
40997
+ });
40998
+ var WireframeSectionSchema = z39.object({
40999
+ type: z39.enum(["hero", "features", "content", "cta", "footer", "divider"]),
41000
+ widthMode: z39.enum(["full-bleed", "contained"]),
41001
+ heightVh: z39.number(),
41002
+ arrangement: z39.enum(["single-column", "split", "asymmetric-grid", "bento", "stacked"]),
41003
+ blocks: z39.array(WireframeBlockSchema),
41004
+ bg: z39.enum(["neutral", "alt", "accent", "image"]),
41005
+ gapAfterVh: z39.number()
41006
+ });
41007
+ var WireframeDataSchema = z39.object({
41008
+ viewport: z39.object({ width: z39.number(), height: z39.number() }),
41009
+ maxWidth: z39.number(),
41010
+ pageBg: z39.string(),
41011
+ sections: z39.array(WireframeSectionSchema)
40683
41012
  });
40684
41013
  function buildFullBrandContext(allBrands) {
40685
41014
  const lines = [];
@@ -40736,7 +41065,7 @@ async function loadSpecFile(filename, logger) {
40736
41065
  );
40737
41066
  }
40738
41067
  try {
40739
- const content = await fs15.readFile(filePath, "utf8");
41068
+ const content = await fs16.readFile(filePath, "utf8");
40740
41069
  logger.debug({ filePath }, `layout-generator: loaded ${filename} from disk`);
40741
41070
  return content;
40742
41071
  } catch (err) {
@@ -41208,57 +41537,57 @@ async function fetchGitLabUsername(fetchImpl, accessToken) {
41208
41537
  return null;
41209
41538
  }
41210
41539
  }
41211
- async function readCredential(path39, log) {
41540
+ async function readCredential(path40, log) {
41212
41541
  try {
41213
- const raw = await readFile3(path39, "utf8");
41542
+ const raw = await readFile3(path40, "utf8");
41214
41543
  const parsed = JSON.parse(raw);
41215
41544
  return parsed.gitlab ?? null;
41216
41545
  } catch (error) {
41217
41546
  if (isNotFound(error)) return null;
41218
- log.warn({ err: error, path: path39 }, "oauth.credentials.read_failed");
41547
+ log.warn({ err: error, path: path40 }, "oauth.credentials.read_failed");
41219
41548
  return null;
41220
41549
  }
41221
41550
  }
41222
- async function persistCredential(path39, credential, log) {
41223
- await mkdir4(dirname6(path39), { recursive: true });
41551
+ async function persistCredential(path40, credential, log) {
41552
+ await mkdir4(dirname6(path40), { recursive: true });
41224
41553
  let current = {};
41225
41554
  try {
41226
- const raw = await readFile3(path39, "utf8");
41555
+ const raw = await readFile3(path40, "utf8");
41227
41556
  current = JSON.parse(raw);
41228
41557
  } catch (error) {
41229
41558
  if (!isNotFound(error)) {
41230
- log.warn({ err: error, path: path39 }, "oauth.credentials.read_failed_overwriting");
41559
+ log.warn({ err: error, path: path40 }, "oauth.credentials.read_failed_overwriting");
41231
41560
  }
41232
41561
  }
41233
41562
  const next = { ...current, gitlab: credential };
41234
- const tmpPath = `${path39}.tmp-${process.pid}-${Date.now()}`;
41563
+ const tmpPath = `${path40}.tmp-${process.pid}-${Date.now()}`;
41235
41564
  await writeFile4(tmpPath, JSON.stringify(next, null, 2), { mode: 384 });
41236
- await rename(tmpPath, path39);
41565
+ await rename(tmpPath, path40);
41237
41566
  }
41238
- async function deleteCredential(path39, log) {
41567
+ async function deleteCredential(path40, log) {
41239
41568
  let current = {};
41240
41569
  try {
41241
- const raw = await readFile3(path39, "utf8");
41570
+ const raw = await readFile3(path40, "utf8");
41242
41571
  current = JSON.parse(raw);
41243
41572
  } catch (error) {
41244
41573
  if (isNotFound(error)) return;
41245
- log.warn({ err: error, path: path39 }, "oauth.credentials.delete_read_failed");
41574
+ log.warn({ err: error, path: path40 }, "oauth.credentials.delete_read_failed");
41246
41575
  return;
41247
41576
  }
41248
41577
  delete current.gitlab;
41249
41578
  if (Object.keys(current).length === 0) {
41250
41579
  try {
41251
- await unlink(path39);
41580
+ await unlink(path40);
41252
41581
  } catch (error) {
41253
41582
  if (!isNotFound(error)) {
41254
- log.warn({ err: error, path: path39 }, "oauth.credentials.unlink_failed");
41583
+ log.warn({ err: error, path: path40 }, "oauth.credentials.unlink_failed");
41255
41584
  }
41256
41585
  }
41257
41586
  return;
41258
41587
  }
41259
- const tmpPath = `${path39}.tmp-${process.pid}-${Date.now()}`;
41588
+ const tmpPath = `${path40}.tmp-${process.pid}-${Date.now()}`;
41260
41589
  await writeFile4(tmpPath, JSON.stringify(current, null, 2), { mode: 384 });
41261
- await rename(tmpPath, path39);
41590
+ await rename(tmpPath, path40);
41262
41591
  }
41263
41592
  function defaultGlabConfigPath() {
41264
41593
  const home = homedir5();
@@ -41273,15 +41602,15 @@ function defaultGlabConfigPath() {
41273
41602
  const xdg = process.env.XDG_CONFIG_HOME;
41274
41603
  return join13(xdg && xdg.length > 0 ? xdg : join13(home, ".config"), "glab-cli", "config.yml");
41275
41604
  }
41276
- async function writeGlabConfig(path39, credential, log) {
41277
- await mkdir4(dirname6(path39), { recursive: true });
41605
+ async function writeGlabConfig(path40, credential, log) {
41606
+ await mkdir4(dirname6(path40), { recursive: true });
41278
41607
  let doc;
41279
41608
  try {
41280
- const raw = await readFile3(path39, "utf8");
41609
+ const raw = await readFile3(path40, "utf8");
41281
41610
  doc = YAML.parseDocument(raw);
41282
41611
  if (doc.errors.length > 0) {
41283
41612
  log.warn(
41284
- { errors: doc.errors.map((e) => e.message), path: path39 },
41613
+ { errors: doc.errors.map((e) => e.message), path: path40 },
41285
41614
  "oauth.glab.parse_errors_replacing"
41286
41615
  );
41287
41616
  doc = YAML.parseDocument("{}");
@@ -41290,7 +41619,7 @@ async function writeGlabConfig(path39, credential, log) {
41290
41619
  if (isNotFound(error)) {
41291
41620
  doc = YAML.parseDocument("{}");
41292
41621
  } else {
41293
- log.warn({ err: error, path: path39 }, "oauth.glab.read_failed_replacing");
41622
+ log.warn({ err: error, path: path40 }, "oauth.glab.read_failed_replacing");
41294
41623
  doc = YAML.parseDocument("{}");
41295
41624
  }
41296
41625
  }
@@ -41303,18 +41632,18 @@ async function writeGlabConfig(path39, credential, log) {
41303
41632
  if (credential.username) {
41304
41633
  hostEntry.set("user", credential.username);
41305
41634
  }
41306
- const tmpPath = `${path39}.tmp-${process.pid}-${Date.now()}`;
41635
+ const tmpPath = `${path40}.tmp-${process.pid}-${Date.now()}`;
41307
41636
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
41308
- await rename(tmpPath, path39);
41637
+ await rename(tmpPath, path40);
41309
41638
  }
41310
- async function removeGlabHost(path39, log) {
41639
+ async function removeGlabHost(path40, log) {
41311
41640
  let doc;
41312
41641
  try {
41313
- const raw = await readFile3(path39, "utf8");
41642
+ const raw = await readFile3(path40, "utf8");
41314
41643
  doc = YAML.parseDocument(raw);
41315
41644
  } catch (error) {
41316
41645
  if (isNotFound(error)) return;
41317
- log.warn({ err: error, path: path39 }, "oauth.glab.remove_read_failed");
41646
+ log.warn({ err: error, path: path40 }, "oauth.glab.remove_read_failed");
41318
41647
  return;
41319
41648
  }
41320
41649
  const hosts = doc.get("hosts");
@@ -41323,9 +41652,9 @@ async function removeGlabHost(path39, log) {
41323
41652
  if (hosts.items.length === 0) {
41324
41653
  doc.delete("hosts");
41325
41654
  }
41326
- const tmpPath = `${path39}.tmp-${process.pid}-${Date.now()}`;
41655
+ const tmpPath = `${path40}.tmp-${process.pid}-${Date.now()}`;
41327
41656
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
41328
- await rename(tmpPath, path39);
41657
+ await rename(tmpPath, path40);
41329
41658
  }
41330
41659
  function ensureMap(doc, key) {
41331
41660
  const existing = doc.get(key);
@@ -42364,7 +42693,7 @@ var MIN_STREAMING_SEGMENT_DURATION_MS = 1e3;
42364
42693
  var MIN_STREAMING_SEGMENT_BYTES = Math.round(
42365
42694
  PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS
42366
42695
  );
42367
- var AgentIdSchema2 = z39.string().uuid();
42696
+ var AgentIdSchema2 = z40.string().uuid();
42368
42697
  var VOICE_INTERRUPT_CONFIRMATION_MS = 500;
42369
42698
  var VoiceFeatureUnavailableError = class extends Error {
42370
42699
  constructor(context) {
@@ -43745,6 +44074,9 @@ var Session = class _Session {
43745
44074
  case "brands/generate-wireframe":
43746
44075
  await this.handleBrandGenerateWireframeRequest(msg);
43747
44076
  break;
44077
+ case "brands/analyze-photography":
44078
+ await this.handleBrandAnalyzePhotographyRequest(msg);
44079
+ break;
43748
44080
  case "rightfont/library":
43749
44081
  await this.handleRightFontLibraryRequest(msg);
43750
44082
  break;
@@ -45404,8 +45736,8 @@ var Session = class _Session {
45404
45736
  }
45405
45737
  async generateCommitMessage(cwd) {
45406
45738
  const files = await listUncommittedFiles(cwd);
45407
- const schema = z39.object({
45408
- message: z39.string().min(1).max(100).describe(
45739
+ const schema = z40.object({
45740
+ message: z40.string().min(1).max(100).describe(
45409
45741
  "Short feature-level summary, lowercase, imperative mood, no trailing period, no filename references."
45410
45742
  )
45411
45743
  });
@@ -45450,9 +45782,9 @@ var Session = class _Session {
45450
45782
  },
45451
45783
  { appostleHome: this.appostleHome }
45452
45784
  );
45453
- const schema = z39.object({
45454
- title: z39.string().min(1).max(72),
45455
- body: z39.string().min(1)
45785
+ const schema = z40.object({
45786
+ title: z40.string().min(1).max(72),
45787
+ body: z40.string().min(1)
45456
45788
  });
45457
45789
  const fileList = diff.structured && diff.structured.length > 0 ? [
45458
45790
  "Files changed:",
@@ -46393,7 +46725,7 @@ var Session = class _Session {
46393
46725
  homeDir: process.env.HOME ?? homedir6(),
46394
46726
  query: query2,
46395
46727
  limit
46396
- })).map((path39) => ({ path: path39, kind: "directory" }));
46728
+ })).map((path40) => ({ path: path40, kind: "directory" }));
46397
46729
  const directories = entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
46398
46730
  this.emit({
46399
46731
  type: "directory_suggestions_response",
@@ -50944,6 +51276,38 @@ ${details}`.trim());
50944
51276
  });
50945
51277
  }
50946
51278
  }
51279
+ async handleBrandAnalyzePhotographyRequest(request) {
51280
+ const { requestId, workspaceRoot, brandPath, brief, referenceDescriptions } = request;
51281
+ try {
51282
+ const result = await analyzeAndApplyPhotographyStyle({
51283
+ agentManager: this.agentManager,
51284
+ workspaceRoot: expandTilde(workspaceRoot),
51285
+ brandPath: expandTilde(brandPath),
51286
+ brief,
51287
+ referenceDescriptions,
51288
+ logger: this.sessionLogger
51289
+ });
51290
+ this.emit({
51291
+ type: "brands/analyze-photography/response",
51292
+ payload: {
51293
+ requestId,
51294
+ updatedCount: result.updatedCount,
51295
+ error: null
51296
+ }
51297
+ });
51298
+ } catch (error) {
51299
+ const message = error instanceof Error ? error.message : String(error);
51300
+ this.sessionLogger.error({ err: error, brandPath }, "Failed to analyze photography style");
51301
+ this.emit({
51302
+ type: "brands/analyze-photography/response",
51303
+ payload: {
51304
+ requestId,
51305
+ updatedCount: 0,
51306
+ error: message
51307
+ }
51308
+ });
51309
+ }
51310
+ }
50947
51311
  async handleRightFontLibraryRequest(request) {
50948
51312
  const { requestId, libraryPath } = request;
50949
51313
  try {
@@ -53802,13 +54166,13 @@ import { join as join19 } from "node:path";
53802
54166
  // ../server/src/server/speech/providers/local/sherpa/model-downloader.ts
53803
54167
  import { createWriteStream } from "node:fs";
53804
54168
  import { mkdir as mkdir6, rename as rename2, rm as rm2, stat as stat6 } from "node:fs/promises";
53805
- import path24 from "node:path";
54169
+ import path25 from "node:path";
53806
54170
  import { Readable as Readable2 } from "node:stream";
53807
54171
  import { pipeline } from "node:stream/promises";
53808
54172
  import { spawn as spawn7 } from "node:child_process";
53809
54173
 
53810
54174
  // ../server/src/server/speech/providers/local/sherpa/model-catalog.ts
53811
- import { z as z40 } from "zod";
54175
+ import { z as z41 } from "zod";
53812
54176
  var SHERPA_ONNX_MODEL_CATALOG = {
53813
54177
  "zipformer-bilingual-zh-en-2023-02-20": {
53814
54178
  kind: "stt-online",
@@ -53901,7 +54265,7 @@ function buildAliasMap(modelIds) {
53901
54265
  }
53902
54266
  function createAliasedModelIdSchema(params) {
53903
54267
  const validIds = new Set(params.modelIds);
53904
- return z40.string().trim().toLowerCase().refine(
54268
+ return z41.string().trim().toLowerCase().refine(
53905
54269
  (value) => validIds.has(value) || Object.prototype.hasOwnProperty.call(params.aliases, value),
53906
54270
  {
53907
54271
  message: "Invalid model id"
@@ -53938,11 +54302,11 @@ function getSherpaOnnxModelSpec(id) {
53938
54302
  // ../server/src/server/speech/providers/local/sherpa/model-downloader.ts
53939
54303
  function getSherpaOnnxModelDir(modelsDir, modelId) {
53940
54304
  const spec = getSherpaOnnxModelSpec(modelId);
53941
- return path24.join(modelsDir, spec.extractedDir);
54305
+ return path25.join(modelsDir, spec.extractedDir);
53942
54306
  }
53943
54307
  async function hasRequiredFiles(modelDir, requiredFiles) {
53944
54308
  for (const rel of requiredFiles) {
53945
- const abs = path24.join(modelDir, rel);
54309
+ const abs = path25.join(modelDir, rel);
53946
54310
  try {
53947
54311
  const s = await stat6(abs);
53948
54312
  if (s.isDirectory()) {
@@ -53968,7 +54332,7 @@ async function downloadToFile(options) {
53968
54332
  throw new Error(`Failed to download ${url}: missing response body`);
53969
54333
  }
53970
54334
  const tmpPath = `${outputPath}.tmp-${Date.now()}`;
53971
- await mkdir6(path24.dirname(outputPath), { recursive: true });
54335
+ await mkdir6(path25.dirname(outputPath), { recursive: true });
53972
54336
  const nodeStream = Readable2.fromWeb(res.body);
53973
54337
  try {
53974
54338
  await pipeline(nodeStream, createWriteStream(tmpPath));
@@ -54005,16 +54369,16 @@ async function ensureSherpaOnnxModel(options) {
54005
54369
  modelId: options.modelId
54006
54370
  });
54007
54371
  const spec = getSherpaOnnxModelSpec(options.modelId);
54008
- const modelDir = path24.join(options.modelsDir, spec.extractedDir);
54372
+ const modelDir = path25.join(options.modelsDir, spec.extractedDir);
54009
54373
  if (await hasRequiredFiles(modelDir, spec.requiredFiles)) {
54010
54374
  return modelDir;
54011
54375
  }
54012
54376
  logger.info({ modelsDir: options.modelsDir }, "Starting model download");
54013
54377
  try {
54014
54378
  if (spec.archiveUrl) {
54015
- const downloadsDir = path24.join(options.modelsDir, ".downloads");
54016
- const archiveFilename = path24.basename(new URL(spec.archiveUrl).pathname);
54017
- const archivePath = path24.join(downloadsDir, archiveFilename);
54379
+ const downloadsDir = path25.join(options.modelsDir, ".downloads");
54380
+ const archiveFilename = path25.basename(new URL(spec.archiveUrl).pathname);
54381
+ const archivePath = path25.join(downloadsDir, archiveFilename);
54018
54382
  if (!await isNonEmptyFile(archivePath)) {
54019
54383
  await downloadToFile({
54020
54384
  url: spec.archiveUrl,
@@ -54059,7 +54423,7 @@ async function ensureSherpaOnnxModel(options) {
54059
54423
  if (spec.downloadFiles && spec.downloadFiles.length > 0) {
54060
54424
  await mkdir6(modelDir, { recursive: true });
54061
54425
  for (const file of spec.downloadFiles) {
54062
- const dst = path24.join(modelDir, file.relPath);
54426
+ const dst = path25.join(modelDir, file.relPath);
54063
54427
  if (await isNonEmptyFile(dst)) {
54064
54428
  continue;
54065
54429
  }
@@ -54122,13 +54486,13 @@ import { existsSync as existsSync16 } from "node:fs";
54122
54486
 
54123
54487
  // ../server/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.ts
54124
54488
  import { createRequire as createRequire4 } from "node:module";
54125
- import path26 from "node:path";
54489
+ import path27 from "node:path";
54126
54490
  import { existsSync as existsSync15 } from "node:fs";
54127
54491
  import { spawnSync } from "node:child_process";
54128
54492
 
54129
54493
  // ../server/src/server/speech/providers/local/sherpa/sherpa-runtime-env.ts
54130
54494
  import { createRequire as createRequire3 } from "node:module";
54131
- import path25 from "node:path";
54495
+ import path26 from "node:path";
54132
54496
  function sherpaPlatformArch(platform = process.platform, arch = process.arch) {
54133
54497
  const normalizedPlatform = platform === "win32" ? "win" : platform;
54134
54498
  return `${normalizedPlatform}-${arch}`;
@@ -54149,11 +54513,11 @@ function sherpaLoaderEnvKey(platform = process.platform) {
54149
54513
  return null;
54150
54514
  }
54151
54515
  function prependEnvPath(existing, value) {
54152
- const parts = (existing ?? "").split(path25.delimiter).filter(Boolean);
54516
+ const parts = (existing ?? "").split(path26.delimiter).filter(Boolean);
54153
54517
  if (parts.includes(value)) {
54154
- return parts.join(path25.delimiter);
54518
+ return parts.join(path26.delimiter);
54155
54519
  }
54156
- return [value, ...parts].join(path25.delimiter);
54520
+ return [value, ...parts].join(path26.delimiter);
54157
54521
  }
54158
54522
  function resolveSherpaLoaderEnv(platform = process.platform, arch = process.arch) {
54159
54523
  const key = sherpaLoaderEnvKey(platform);
@@ -54166,7 +54530,7 @@ function resolveSherpaLoaderEnv(platform = process.platform, arch = process.arch
54166
54530
  const pkgJson = require4.resolve(`${packageName}/package.json`);
54167
54531
  return {
54168
54532
  key,
54169
- libDir: path25.dirname(pkgJson),
54533
+ libDir: path26.dirname(pkgJson),
54170
54534
  packageName
54171
54535
  };
54172
54536
  } catch {
@@ -54275,7 +54639,7 @@ function loadSherpaOnnxNode() {
54275
54639
  const platformPkgDir = resolvedEnv?.libDir ?? null;
54276
54640
  if (platformPkgDir) {
54277
54641
  applySherpaLoaderEnv(process.env);
54278
- const addonPath = path26.join(platformPkgDir, "sherpa-onnx.node");
54642
+ const addonPath = path27.join(platformPkgDir, "sherpa-onnx.node");
54279
54643
  if (existsSync15(addonPath)) {
54280
54644
  const byPath = loadWithRequire(require4, addonPath, attempts);
54281
54645
  if (byPath) {
@@ -55047,7 +55411,7 @@ var SherpaOnnxTTS = class {
55047
55411
 
55048
55412
  // ../server/src/server/speech/providers/local/sherpa/silero-vad-provider.ts
55049
55413
  import { copyFile, mkdir as mkdir7, stat as stat7 } from "node:fs/promises";
55050
- import path27 from "node:path";
55414
+ import path28 from "node:path";
55051
55415
 
55052
55416
  // ../server/src/server/speech/providers/local/sherpa/silero-vad-session.ts
55053
55417
  import { EventEmitter as EventEmitter6 } from "node:events";
@@ -55239,8 +55603,8 @@ var SherpaSileroVadSession = class extends EventEmitter6 {
55239
55603
  var SILERO_VAD_DIR = "silero-vad";
55240
55604
  var SILERO_VAD_FILE = "silero_vad.onnx";
55241
55605
  async function ensureSileroVadModel(modelsDir, logger) {
55242
- const destDir = path27.join(modelsDir, SILERO_VAD_DIR);
55243
- const destPath = path27.join(destDir, SILERO_VAD_FILE);
55606
+ const destDir = path28.join(modelsDir, SILERO_VAD_DIR);
55607
+ const destPath = path28.join(destDir, SILERO_VAD_FILE);
55244
55608
  try {
55245
55609
  const s = await stat7(destPath);
55246
55610
  if (s.isFile() && s.size > 0) return destPath;
@@ -55533,20 +55897,20 @@ async function initializeLocalSpeechServices(params) {
55533
55897
  }
55534
55898
 
55535
55899
  // ../server/src/server/speech/providers/openai/config.ts
55536
- import { z as z41 } from "zod";
55900
+ import { z as z42 } from "zod";
55537
55901
  var DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL = "gpt-4o-transcribe";
55538
55902
  var DEFAULT_OPENAI_TTS_MODEL = "tts-1";
55539
- var OpenAiTtsVoiceSchema = z41.enum(["alloy", "echo", "fable", "onyx", "nova", "shimmer"]);
55540
- var OpenAiTtsModelSchema = z41.enum(["tts-1", "tts-1-hd"]);
55541
- var NumberLikeSchema = z41.union([z41.number(), z41.string().trim().min(1)]);
55542
- var OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z41.coerce.number().finite()).optional();
55543
- var OptionalTrimmedStringSchema = z41.string().trim().optional().transform((value) => value && value.length > 0 ? value : void 0);
55544
- var OpenAiSpeechResolutionSchema = z41.object({
55903
+ var OpenAiTtsVoiceSchema = z42.enum(["alloy", "echo", "fable", "onyx", "nova", "shimmer"]);
55904
+ var OpenAiTtsModelSchema = z42.enum(["tts-1", "tts-1-hd"]);
55905
+ var NumberLikeSchema = z42.union([z42.number(), z42.string().trim().min(1)]);
55906
+ var OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z42.coerce.number().finite()).optional();
55907
+ var OptionalTrimmedStringSchema = z42.string().trim().optional().transform((value) => value && value.length > 0 ? value : void 0);
55908
+ var OpenAiSpeechResolutionSchema = z42.object({
55545
55909
  apiKey: OptionalTrimmedStringSchema,
55546
55910
  sttConfidenceThreshold: OptionalFiniteNumberSchema,
55547
55911
  sttModel: OptionalTrimmedStringSchema,
55548
- ttsVoice: z41.string().trim().toLowerCase().pipe(OpenAiTtsVoiceSchema).default("alloy"),
55549
- ttsModel: z41.string().trim().toLowerCase().pipe(OpenAiTtsModelSchema).default(DEFAULT_OPENAI_TTS_MODEL),
55912
+ ttsVoice: z42.string().trim().toLowerCase().pipe(OpenAiTtsVoiceSchema).default("alloy"),
55913
+ ttsModel: z42.string().trim().toLowerCase().pipe(OpenAiTtsModelSchema).default(DEFAULT_OPENAI_TTS_MODEL),
55550
55914
  realtimeTranscriptionModel: OptionalTrimmedStringSchema.default(
55551
55915
  DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL
55552
55916
  )
@@ -55894,7 +56258,7 @@ var OpenAISTT = class {
55894
56258
  const supportsLogprobs = modelToUse === "gpt-4o-transcribe" || modelToUse === "gpt-4o-mini-transcribe";
55895
56259
  const includeLogprobs = ["logprobs"];
55896
56260
  const response = await this.openaiClient.audio.transcriptions.create({
55897
- file: await import("fs").then((fs19) => fs19.createReadStream(tempFilePath)),
56261
+ file: await import("fs").then((fs20) => fs20.createReadStream(tempFilePath)),
55898
56262
  language,
55899
56263
  model: modelToUse,
55900
56264
  ...supportsLogprobs ? { include: includeLogprobs } : {},
@@ -56658,72 +57022,72 @@ function createSpeechService(params) {
56658
57022
 
56659
57023
  // ../server/src/server/agent/agent-storage.ts
56660
57024
  import { randomUUID as randomUUID12 } from "node:crypto";
56661
- import { promises as fs16 } from "node:fs";
56662
- import path28 from "node:path";
56663
- import { z as z42 } from "zod";
56664
- var SERIALIZABLE_CONFIG_SCHEMA = z42.object({
56665
- title: z42.string().nullable().optional(),
56666
- modeId: z42.string().nullable().optional(),
56667
- model: z42.string().nullable().optional(),
56668
- thinkingOptionId: z42.string().nullable().optional(),
56669
- featureValues: z42.record(z42.unknown()).nullable().optional(),
56670
- extra: z42.record(z42.any()).nullable().optional(),
56671
- systemPrompt: z42.string().nullable().optional(),
56672
- mcpServers: z42.record(z42.any()).nullable().optional()
57025
+ import { promises as fs17 } from "node:fs";
57026
+ import path29 from "node:path";
57027
+ import { z as z43 } from "zod";
57028
+ var SERIALIZABLE_CONFIG_SCHEMA = z43.object({
57029
+ title: z43.string().nullable().optional(),
57030
+ modeId: z43.string().nullable().optional(),
57031
+ model: z43.string().nullable().optional(),
57032
+ thinkingOptionId: z43.string().nullable().optional(),
57033
+ featureValues: z43.record(z43.unknown()).nullable().optional(),
57034
+ extra: z43.record(z43.any()).nullable().optional(),
57035
+ systemPrompt: z43.string().nullable().optional(),
57036
+ mcpServers: z43.record(z43.any()).nullable().optional()
56673
57037
  }).nullable().optional();
56674
- var PERSISTENCE_HANDLE_SCHEMA = z42.object({
56675
- provider: z42.string(),
56676
- sessionId: z42.string(),
56677
- nativeHandle: z42.any().optional(),
56678
- metadata: z42.record(z42.any()).optional()
57038
+ var PERSISTENCE_HANDLE_SCHEMA = z43.object({
57039
+ provider: z43.string(),
57040
+ sessionId: z43.string(),
57041
+ nativeHandle: z43.any().optional(),
57042
+ metadata: z43.record(z43.any()).optional()
56679
57043
  }).nullable().optional();
56680
- var STORED_AGENT_SCHEMA = z42.object({
56681
- id: z42.string(),
56682
- provider: z42.string(),
56683
- cwd: z42.string(),
56684
- createdAt: z42.string(),
56685
- updatedAt: z42.string(),
56686
- lastActivityAt: z42.string().optional(),
56687
- lastUserMessageAt: z42.string().nullable().optional(),
56688
- title: z42.string().nullable().optional(),
56689
- labels: z42.record(z42.string()).default({}),
57044
+ var STORED_AGENT_SCHEMA = z43.object({
57045
+ id: z43.string(),
57046
+ provider: z43.string(),
57047
+ cwd: z43.string(),
57048
+ createdAt: z43.string(),
57049
+ updatedAt: z43.string(),
57050
+ lastActivityAt: z43.string().optional(),
57051
+ lastUserMessageAt: z43.string().nullable().optional(),
57052
+ title: z43.string().nullable().optional(),
57053
+ labels: z43.record(z43.string()).default({}),
56690
57054
  lastStatus: AgentStatusSchema.default("closed"),
56691
- lastModeId: z42.string().nullable().optional(),
57055
+ lastModeId: z43.string().nullable().optional(),
56692
57056
  config: SERIALIZABLE_CONFIG_SCHEMA,
56693
- runtimeInfo: z42.object({
56694
- provider: z42.string(),
56695
- sessionId: z42.string().nullable(),
56696
- model: z42.string().nullable().optional(),
56697
- thinkingOptionId: z42.string().nullable().optional(),
56698
- modeId: z42.string().nullable().optional(),
56699
- extra: z42.record(z42.unknown()).optional()
57057
+ runtimeInfo: z43.object({
57058
+ provider: z43.string(),
57059
+ sessionId: z43.string().nullable(),
57060
+ model: z43.string().nullable().optional(),
57061
+ thinkingOptionId: z43.string().nullable().optional(),
57062
+ modeId: z43.string().nullable().optional(),
57063
+ extra: z43.record(z43.unknown()).optional()
56700
57064
  }).optional(),
56701
- features: z42.array(AgentFeatureSchema).optional(),
57065
+ features: z43.array(AgentFeatureSchema).optional(),
56702
57066
  persistence: PERSISTENCE_HANDLE_SCHEMA,
56703
- lastError: z42.string().nullable().optional(),
56704
- requiresAttention: z42.boolean().optional(),
56705
- attentionReason: z42.enum(["finished", "error", "permission"]).nullable().optional(),
56706
- attentionTimestamp: z42.string().nullable().optional(),
56707
- internal: z42.boolean().optional(),
56708
- archivedAt: z42.string().nullable().optional(),
57067
+ lastError: z43.string().nullable().optional(),
57068
+ requiresAttention: z43.boolean().optional(),
57069
+ attentionReason: z43.enum(["finished", "error", "permission"]).nullable().optional(),
57070
+ attentionTimestamp: z43.string().nullable().optional(),
57071
+ internal: z43.boolean().optional(),
57072
+ archivedAt: z43.string().nullable().optional(),
56709
57073
  // Fork lineage (optional for backward compat with pre-fork records).
56710
- parentAgentId: z42.string().optional(),
56711
- forkedFromMessageUuid: z42.string().optional(),
57074
+ parentAgentId: z43.string().optional(),
57075
+ forkedFromMessageUuid: z43.string().optional(),
56712
57076
  // Multi-tenant session isolation: the auth-server user-id that created
56713
57077
  // (and therefore owns) this agent + the additive ACL of other users
56714
57078
  // granted access. Both optional/null-default for backward compatibility
56715
57079
  // with pre-Phase-2c records — those load with `null` owner and stay
56716
57080
  // visible to every connecting user (matches today's single-tenant
56717
57081
  // behavior). Set on new agents at create time via Session.ownerUserId.
56718
- ownerUserId: z42.string().nullable().optional(),
56719
- sharedWithUserIds: z42.array(z42.string()).default([]),
57082
+ ownerUserId: z43.string().nullable().optional(),
57083
+ sharedWithUserIds: z43.array(z43.string()).default([]),
56720
57084
  // Owner's display username — needed on resume to route to the correct
56721
57085
  // `CLAUDE_CONFIG_DIR` via `ensureClaudeProfile(username)` for agents
56722
57086
  // created by a shared (non-owner) user. Without this, a resumed shared-
56723
57087
  // user agent would silently fall back to the daemon owner's Claude
56724
57088
  // profile/subscription. Optional — pre-Phase-3 records rehydrate without
56725
57089
  // per-user profile routing.
56726
- ownerUsername: z42.string().optional()
57090
+ ownerUsername: z43.string().optional()
56727
57091
  });
56728
57092
  function parseStoredAgentRecord(value) {
56729
57093
  return STORED_AGENT_SCHEMA.parse(value);
@@ -56761,12 +57125,12 @@ var AgentStorage = class {
56761
57125
  }
56762
57126
  const nextPath = this.buildRecordPath(record);
56763
57127
  const previousPath = this.pathById.get(agentId);
56764
- await fs16.mkdir(path28.dirname(nextPath), { recursive: true });
57128
+ await fs17.mkdir(path29.dirname(nextPath), { recursive: true });
56765
57129
  await writeFileAtomically(nextPath, JSON.stringify(record, null, 2));
56766
57130
  this.addIndexedPath(agentId, nextPath);
56767
57131
  if (previousPath && previousPath !== nextPath) {
56768
57132
  try {
56769
- await fs16.unlink(previousPath);
57133
+ await fs17.unlink(previousPath);
56770
57134
  } catch {
56771
57135
  }
56772
57136
  this.removeIndexedPath(agentId, previousPath);
@@ -56794,7 +57158,7 @@ var AgentStorage = class {
56794
57158
  const paths = Array.from(this.pathsById.get(agentId) ?? []);
56795
57159
  for (const filePath of paths) {
56796
57160
  try {
56797
- await fs16.unlink(filePath);
57161
+ await fs17.unlink(filePath);
56798
57162
  } catch (error) {
56799
57163
  const code = error.code;
56800
57164
  if (code && code !== "ENOENT") {
@@ -56868,7 +57232,7 @@ var AgentStorage = class {
56868
57232
  const records = [];
56869
57233
  let entries = [];
56870
57234
  try {
56871
- entries = await fs16.readdir(this.baseDir, { withFileTypes: true });
57235
+ entries = await fs17.readdir(this.baseDir, { withFileTypes: true });
56872
57236
  } catch (error) {
56873
57237
  if (error.code === "ENOENT") {
56874
57238
  return [];
@@ -56877,7 +57241,7 @@ var AgentStorage = class {
56877
57241
  }
56878
57242
  for (const entry of entries) {
56879
57243
  if (entry.isFile() && entry.name.endsWith(".json")) {
56880
- const rootPath = path28.join(this.baseDir, entry.name);
57244
+ const rootPath = path29.join(this.baseDir, entry.name);
56881
57245
  const rootRecord = await this.readRecordFile(rootPath);
56882
57246
  if (!rootRecord) {
56883
57247
  continue;
@@ -56891,10 +57255,10 @@ var AgentStorage = class {
56891
57255
  if (!entry.isDirectory()) {
56892
57256
  continue;
56893
57257
  }
56894
- const projectDir = path28.join(this.baseDir, entry.name);
57258
+ const projectDir = path29.join(this.baseDir, entry.name);
56895
57259
  let files = [];
56896
57260
  try {
56897
- files = await fs16.readdir(projectDir, { withFileTypes: true });
57261
+ files = await fs17.readdir(projectDir, { withFileTypes: true });
56898
57262
  } catch {
56899
57263
  continue;
56900
57264
  }
@@ -56902,7 +57266,7 @@ var AgentStorage = class {
56902
57266
  if (!file.isFile() || !file.name.endsWith(".json")) {
56903
57267
  continue;
56904
57268
  }
56905
- const filePath = path28.join(projectDir, file.name);
57269
+ const filePath = path29.join(projectDir, file.name);
56906
57270
  const record = await this.readRecordFile(filePath);
56907
57271
  if (!record) {
56908
57272
  continue;
@@ -56917,7 +57281,7 @@ var AgentStorage = class {
56917
57281
  }
56918
57282
  async readRecordFile(filePath) {
56919
57283
  try {
56920
- const content = await fs16.readFile(filePath, "utf8");
57284
+ const content = await fs17.readFile(filePath, "utf8");
56921
57285
  const parsed = JSON.parse(content);
56922
57286
  return parseStoredAgentRecord(parsed);
56923
57287
  } catch (error) {
@@ -56927,7 +57291,7 @@ var AgentStorage = class {
56927
57291
  }
56928
57292
  buildRecordPath(record) {
56929
57293
  const projectDir = projectDirNameFromCwd(record.cwd);
56930
- return path28.join(this.baseDir, projectDir, `${record.id}.json`);
57294
+ return path29.join(this.baseDir, projectDir, `${record.id}.json`);
56931
57295
  }
56932
57296
  addIndexedPath(agentId, filePath) {
56933
57297
  const paths = this.pathsById.get(agentId) ?? /* @__PURE__ */ new Set();
@@ -56949,7 +57313,7 @@ var AgentStorage = class {
56949
57313
  }
56950
57314
  };
56951
57315
  function projectDirNameFromCwd(cwd) {
56952
- const { root } = path28.win32.parse(cwd);
57316
+ const { root } = path29.win32.parse(cwd);
56953
57317
  const withoutRoot = cwd.slice(root.length).replace(/[\\/]+$/, "");
56954
57318
  const sanitizedRoot = root.replace(/[:\\/]+/g, "-").replace(/^-+|-+$/g, "");
56955
57319
  const prefix = sanitizedRoot ? sanitizedRoot + "-" : "";
@@ -56959,15 +57323,15 @@ function projectDirNameFromCwd(cwd) {
56959
57323
  return prefix + withoutRoot.replace(/[\\/]+/g, "-");
56960
57324
  }
56961
57325
  async function writeFileAtomically(targetPath, payload) {
56962
- const directory = path28.dirname(targetPath);
56963
- const tempPath = path28.join(directory, `.agent.tmp-${process.pid}-${Date.now()}-${randomUUID12()}`);
56964
- await fs16.writeFile(tempPath, payload, "utf8");
56965
- await fs16.rename(tempPath, targetPath);
57326
+ const directory = path29.dirname(targetPath);
57327
+ const tempPath = path29.join(directory, `.agent.tmp-${process.pid}-${Date.now()}-${randomUUID12()}`);
57328
+ await fs17.writeFile(tempPath, payload, "utf8");
57329
+ await fs17.rename(tempPath, targetPath);
56966
57330
  }
56967
57331
 
56968
57332
  // ../server/src/server/agent/mcp-server.ts
56969
57333
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
56970
- import { z as z43 } from "zod";
57334
+ import { z as z44 } from "zod";
56971
57335
 
56972
57336
  // ../server/src/server/json-utils.ts
56973
57337
  function ensureValidJson(value) {
@@ -57100,16 +57464,16 @@ function resolveChildAgentCwd(params) {
57100
57464
  }
57101
57465
  return resolvePathFromBase(params.parentCwd, requestedCwd);
57102
57466
  }
57103
- var TerminalSummarySchema = z43.object({
57104
- id: z43.string(),
57105
- name: z43.string(),
57106
- cwd: z43.string()
57467
+ var TerminalSummarySchema = z44.object({
57468
+ id: z44.string(),
57469
+ name: z44.string(),
57470
+ cwd: z44.string()
57107
57471
  });
57108
- var WorktreeSummarySchema = z43.object({
57109
- path: z43.string(),
57110
- createdAt: z43.string(),
57111
- branchName: z43.string().optional(),
57112
- head: z43.string().optional()
57472
+ var WorktreeSummarySchema = z44.object({
57473
+ path: z44.string(),
57474
+ createdAt: z44.string(),
57475
+ branchName: z44.string().optional(),
57476
+ head: z44.string().optional()
57113
57477
  });
57114
57478
  function resolveTerminalKeyToken(key, literal) {
57115
57479
  if (literal) {
@@ -57246,48 +57610,48 @@ async function createAgentMcpServer(options) {
57246
57610
  };
57247
57611
  };
57248
57612
  const agentToAgentInputSchema = {
57249
- cwd: z43.string().optional().describe("Optional working directory. Defaults to the caller agent working directory."),
57250
- title: z43.string().trim().min(1, "Title is required").max(60, "Title must be 60 characters or fewer").describe("Short descriptive title (<= 60 chars) summarizing the agent's focus."),
57613
+ cwd: z44.string().optional().describe("Optional working directory. Defaults to the caller agent working directory."),
57614
+ title: z44.string().trim().min(1, "Title is required").max(60, "Title must be 60 characters or fewer").describe("Short descriptive title (<= 60 chars) summarizing the agent's focus."),
57251
57615
  provider: AgentProviderEnum.optional().describe(
57252
57616
  "Optional agent implementation to spawn. Defaults to 'claude'."
57253
57617
  ),
57254
- model: z43.string().optional().describe("Model to use (e.g. claude-sonnet-4-20250514)"),
57255
- thinking: z43.string().optional().describe("Thinking option ID"),
57256
- labels: z43.record(z43.string(), z43.string()).optional().describe("Labels to set on the agent"),
57257
- initialPrompt: z43.string().trim().min(1, "initialPrompt is required").describe("Required first task to run immediately after creation."),
57258
- background: z43.boolean().optional().default(false).describe(
57618
+ model: z44.string().optional().describe("Model to use (e.g. claude-sonnet-4-20250514)"),
57619
+ thinking: z44.string().optional().describe("Thinking option ID"),
57620
+ labels: z44.record(z44.string(), z44.string()).optional().describe("Labels to set on the agent"),
57621
+ initialPrompt: z44.string().trim().min(1, "initialPrompt is required").describe("Required first task to run immediately after creation."),
57622
+ background: z44.boolean().optional().default(false).describe(
57259
57623
  "Run agent in background. If false (default), waits for completion or permission request. If true, returns immediately."
57260
57624
  ),
57261
- notifyOnFinish: z43.boolean().optional().default(false).describe(
57625
+ notifyOnFinish: z44.boolean().optional().default(false).describe(
57262
57626
  "Send a notification prompt to the caller agent when this agent finishes, errors, or needs permission. Requires a caller agent context."
57263
57627
  )
57264
57628
  };
57265
57629
  const topLevelInputSchema = {
57266
- cwd: z43.string().describe("Required working directory for the agent (absolute, relative, or ~)."),
57267
- title: z43.string().trim().min(1, "Title is required").max(60, "Title must be 60 characters or fewer").describe("Short descriptive title (<= 60 chars) summarizing the agent's focus."),
57630
+ cwd: z44.string().describe("Required working directory for the agent (absolute, relative, or ~)."),
57631
+ title: z44.string().trim().min(1, "Title is required").max(60, "Title must be 60 characters or fewer").describe("Short descriptive title (<= 60 chars) summarizing the agent's focus."),
57268
57632
  provider: AgentProviderEnum.optional().describe(
57269
57633
  "Optional agent implementation to spawn. Defaults to 'claude'."
57270
57634
  ),
57271
- model: z43.string().optional().describe("Model to use (e.g. claude-sonnet-4-20250514)"),
57272
- thinking: z43.string().optional().describe("Thinking option ID"),
57273
- labels: z43.record(z43.string(), z43.string()).optional().describe("Labels to set on the agent"),
57274
- initialPrompt: z43.string().trim().min(1, "initialPrompt is required").describe("Required first task to run immediately after creation."),
57275
- mode: z43.string().optional().describe("Optional session mode to configure before the first run."),
57276
- worktreeName: z43.string().optional().describe("Optional git worktree branch name (lowercase alphanumerics + hyphen)."),
57277
- baseBranch: z43.string().optional().describe("Required when worktreeName is set: the base branch to diff/merge against."),
57278
- refName: z43.string().min(1).optional().describe("Optional source ref for worktree creation."),
57279
- action: z43.enum(["branch-off", "checkout"]).optional().describe("Optional worktree creation action."),
57280
- githubPrNumber: z43.number().int().positive().optional().describe("Optional GitHub pull request number to checkout."),
57281
- background: z43.boolean().optional().default(false).describe(
57635
+ model: z44.string().optional().describe("Model to use (e.g. claude-sonnet-4-20250514)"),
57636
+ thinking: z44.string().optional().describe("Thinking option ID"),
57637
+ labels: z44.record(z44.string(), z44.string()).optional().describe("Labels to set on the agent"),
57638
+ initialPrompt: z44.string().trim().min(1, "initialPrompt is required").describe("Required first task to run immediately after creation."),
57639
+ mode: z44.string().optional().describe("Optional session mode to configure before the first run."),
57640
+ worktreeName: z44.string().optional().describe("Optional git worktree branch name (lowercase alphanumerics + hyphen)."),
57641
+ baseBranch: z44.string().optional().describe("Required when worktreeName is set: the base branch to diff/merge against."),
57642
+ refName: z44.string().min(1).optional().describe("Optional source ref for worktree creation."),
57643
+ action: z44.enum(["branch-off", "checkout"]).optional().describe("Optional worktree creation action."),
57644
+ githubPrNumber: z44.number().int().positive().optional().describe("Optional GitHub pull request number to checkout."),
57645
+ background: z44.boolean().optional().default(false).describe(
57282
57646
  "Run agent in background. If false (default), waits for completion or permission request. If true, returns immediately."
57283
57647
  ),
57284
- notifyOnFinish: z43.boolean().optional().default(false).describe(
57648
+ notifyOnFinish: z44.boolean().optional().default(false).describe(
57285
57649
  "Send a notification prompt to the caller agent when this agent finishes, errors, or needs permission. Requires a caller agent context."
57286
57650
  )
57287
57651
  };
57288
57652
  const createAgentInputSchema = callerAgentId ? agentToAgentInputSchema : topLevelInputSchema;
57289
- const agentToAgentCreateAgentArgsSchema = z43.object(agentToAgentInputSchema);
57290
- const topLevelCreateAgentArgsSchema = z43.object(topLevelInputSchema);
57653
+ const agentToAgentCreateAgentArgsSchema = z44.object(agentToAgentInputSchema);
57654
+ const topLevelCreateAgentArgsSchema = z44.object(topLevelInputSchema);
57291
57655
  if (options.voiceOnly || options.enableVoiceTools || callerContext?.enableVoiceTools) {
57292
57656
  server.registerTool(
57293
57657
  "speak",
@@ -57295,10 +57659,10 @@ async function createAgentMcpServer(options) {
57295
57659
  title: "Speak",
57296
57660
  description: "Speak text to the user via daemon-managed voice output. Blocks until playback completes.",
57297
57661
  inputSchema: {
57298
- text: z43.string().trim().min(1, "text is required").max(4e3, "text must be 4000 characters or fewer")
57662
+ text: z44.string().trim().min(1, "text is required").max(4e3, "text must be 4000 characters or fewer")
57299
57663
  },
57300
57664
  outputSchema: {
57301
- ok: z43.boolean()
57665
+ ok: z44.boolean()
57302
57666
  }
57303
57667
  },
57304
57668
  async (args, context) => {
@@ -57331,19 +57695,19 @@ async function createAgentMcpServer(options) {
57331
57695
  description: "Create a new Claude or Codex agent tied to a working directory. Optionally run an initial prompt immediately or create a git worktree for the agent.",
57332
57696
  inputSchema: createAgentInputSchema,
57333
57697
  outputSchema: {
57334
- agentId: z43.string(),
57698
+ agentId: z44.string(),
57335
57699
  type: AgentProviderEnum,
57336
57700
  status: AgentStatusEnum,
57337
- cwd: z43.string(),
57338
- currentModeId: z43.string().nullable(),
57339
- availableModes: z43.array(
57340
- z43.object({
57341
- id: z43.string(),
57342
- label: z43.string(),
57343
- description: z43.string().nullable().optional()
57701
+ cwd: z44.string(),
57702
+ currentModeId: z44.string().nullable(),
57703
+ availableModes: z44.array(
57704
+ z44.object({
57705
+ id: z44.string(),
57706
+ label: z44.string(),
57707
+ description: z44.string().nullable().optional()
57344
57708
  })
57345
57709
  ),
57346
- lastMessage: z43.string().nullable().optional(),
57710
+ lastMessage: z44.string().nullable().optional(),
57347
57711
  permission: AgentPermissionRequestPayloadSchema.nullable().optional()
57348
57712
  }
57349
57713
  },
@@ -57530,13 +57894,13 @@ async function createAgentMcpServer(options) {
57530
57894
  title: "Wait for agent",
57531
57895
  description: "Block until the agent requests permission or the current run completes. Returns the pending permission (if any) and recent activity summary.",
57532
57896
  inputSchema: {
57533
- agentId: z43.string().describe("Agent identifier returned by the create_agent tool")
57897
+ agentId: z44.string().describe("Agent identifier returned by the create_agent tool")
57534
57898
  },
57535
57899
  outputSchema: {
57536
- agentId: z43.string(),
57900
+ agentId: z44.string(),
57537
57901
  status: AgentStatusEnum,
57538
57902
  permission: AgentPermissionRequestPayloadSchema.nullable(),
57539
- lastMessage: z43.string().nullable()
57903
+ lastMessage: z44.string().nullable()
57540
57904
  }
57541
57905
  },
57542
57906
  async ({ agentId }, { signal }) => {
@@ -57597,20 +57961,20 @@ async function createAgentMcpServer(options) {
57597
57961
  title: "Send agent prompt",
57598
57962
  description: "Send a task to a running agent. Returns immediately after the agent begins processing.",
57599
57963
  inputSchema: {
57600
- agentId: z43.string(),
57601
- prompt: z43.string(),
57602
- sessionMode: z43.string().optional().describe("Optional mode to set before running the prompt."),
57603
- background: z43.boolean().optional().default(false).describe(
57964
+ agentId: z44.string(),
57965
+ prompt: z44.string(),
57966
+ sessionMode: z44.string().optional().describe("Optional mode to set before running the prompt."),
57967
+ background: z44.boolean().optional().default(false).describe(
57604
57968
  "Run agent in background. If false (default), waits for completion or permission request. If true, returns immediately."
57605
57969
  ),
57606
- notifyOnFinish: z43.boolean().optional().default(false).describe(
57970
+ notifyOnFinish: z44.boolean().optional().default(false).describe(
57607
57971
  "Send a notification prompt to the caller agent when this agent finishes, errors, or needs permission."
57608
57972
  )
57609
57973
  },
57610
57974
  outputSchema: {
57611
- success: z43.boolean(),
57975
+ success: z44.boolean(),
57612
57976
  status: AgentStatusEnum,
57613
- lastMessage: z43.string().nullable().optional(),
57977
+ lastMessage: z44.string().nullable().optional(),
57614
57978
  permission: AgentPermissionRequestPayloadSchema.nullable().optional()
57615
57979
  }
57616
57980
  },
@@ -57674,7 +58038,7 @@ async function createAgentMcpServer(options) {
57674
58038
  title: "Get agent status",
57675
58039
  description: "Return the latest snapshot for an agent, including lifecycle state, capabilities, and pending permissions.",
57676
58040
  inputSchema: {
57677
- agentId: z43.string()
58041
+ agentId: z44.string()
57678
58042
  },
57679
58043
  outputSchema: {
57680
58044
  status: AgentStatusEnum,
@@ -57721,10 +58085,10 @@ async function createAgentMcpServer(options) {
57721
58085
  title: "List agents",
57722
58086
  description: "List all live agents managed by the server.",
57723
58087
  inputSchema: {
57724
- includeArchived: z43.boolean().optional().default(false)
58088
+ includeArchived: z44.boolean().optional().default(false)
57725
58089
  },
57726
58090
  outputSchema: {
57727
- agents: z43.array(AgentSnapshotPayloadSchema)
58091
+ agents: z44.array(AgentSnapshotPayloadSchema)
57728
58092
  }
57729
58093
  },
57730
58094
  async ({ includeArchived }) => {
@@ -57749,10 +58113,10 @@ async function createAgentMcpServer(options) {
57749
58113
  title: "Cancel agent run",
57750
58114
  description: "Abort the agent's current run but keep the agent alive for future tasks.",
57751
58115
  inputSchema: {
57752
- agentId: z43.string()
58116
+ agentId: z44.string()
57753
58117
  },
57754
58118
  outputSchema: {
57755
- success: z43.boolean()
58119
+ success: z44.boolean()
57756
58120
  }
57757
58121
  },
57758
58122
  async ({ agentId }) => {
@@ -57772,10 +58136,10 @@ async function createAgentMcpServer(options) {
57772
58136
  title: "Archive agent",
57773
58137
  description: "Archive an agent (soft-delete). The agent is interrupted if running and removed from the active list.",
57774
58138
  inputSchema: {
57775
- agentId: z43.string()
58139
+ agentId: z44.string()
57776
58140
  },
57777
58141
  outputSchema: {
57778
- success: z43.boolean()
58142
+ success: z44.boolean()
57779
58143
  }
57780
58144
  },
57781
58145
  async ({ agentId }) => {
@@ -57793,10 +58157,10 @@ async function createAgentMcpServer(options) {
57793
58157
  title: "Kill agent",
57794
58158
  description: "Terminate an agent session permanently.",
57795
58159
  inputSchema: {
57796
- agentId: z43.string()
58160
+ agentId: z44.string()
57797
58161
  },
57798
58162
  outputSchema: {
57799
- success: z43.boolean()
58163
+ success: z44.boolean()
57800
58164
  }
57801
58165
  },
57802
58166
  async ({ agentId }) => {
@@ -57814,12 +58178,12 @@ async function createAgentMcpServer(options) {
57814
58178
  title: "Update agent",
57815
58179
  description: "Update an agent name and/or labels.",
57816
58180
  inputSchema: {
57817
- agentId: z43.string(),
57818
- name: z43.string().optional(),
57819
- labels: z43.record(z43.string(), z43.string()).optional().describe("Labels to set on the agent")
58181
+ agentId: z44.string(),
58182
+ name: z44.string().optional(),
58183
+ labels: z44.record(z44.string(), z44.string()).optional().describe("Labels to set on the agent")
57820
58184
  },
57821
58185
  outputSchema: {
57822
- success: z43.boolean()
58186
+ success: z44.boolean()
57823
58187
  }
57824
58188
  },
57825
58189
  async ({ agentId, name, labels }) => {
@@ -57851,11 +58215,11 @@ async function createAgentMcpServer(options) {
57851
58215
  title: "List terminals",
57852
58216
  description: "List terminals for a working directory or across all working directories.",
57853
58217
  inputSchema: {
57854
- cwd: z43.string().optional().describe("Optional working directory. Defaults to the caller agent cwd."),
57855
- all: z43.boolean().optional().describe("List terminals across all working directories.")
58218
+ cwd: z44.string().optional().describe("Optional working directory. Defaults to the caller agent cwd."),
58219
+ all: z44.boolean().optional().describe("List terminals across all working directories.")
57856
58220
  },
57857
58221
  outputSchema: {
57858
- terminals: z43.array(TerminalSummarySchema)
58222
+ terminals: z44.array(TerminalSummarySchema)
57859
58223
  }
57860
58224
  },
57861
58225
  async ({ cwd, all }) => {
@@ -57889,8 +58253,8 @@ async function createAgentMcpServer(options) {
57889
58253
  title: "Create terminal",
57890
58254
  description: "Create a terminal session for a working directory.",
57891
58255
  inputSchema: {
57892
- cwd: z43.string().optional().describe("Optional working directory. Defaults to the caller agent cwd."),
57893
- name: z43.string().optional().describe("Optional terminal name.")
58256
+ cwd: z44.string().optional().describe("Optional working directory. Defaults to the caller agent cwd."),
58257
+ name: z44.string().optional().describe("Optional terminal name.")
57894
58258
  },
57895
58259
  outputSchema: TerminalSummarySchema.shape
57896
58260
  },
@@ -57918,10 +58282,10 @@ async function createAgentMcpServer(options) {
57918
58282
  title: "Kill terminal",
57919
58283
  description: "Kill an existing terminal session.",
57920
58284
  inputSchema: {
57921
- terminalId: z43.string()
58285
+ terminalId: z44.string()
57922
58286
  },
57923
58287
  outputSchema: {
57924
- success: z43.boolean()
58288
+ success: z44.boolean()
57925
58289
  }
57926
58290
  },
57927
58291
  async ({ terminalId }) => {
@@ -57945,16 +58309,16 @@ async function createAgentMcpServer(options) {
57945
58309
  title: "Capture terminal",
57946
58310
  description: "Capture plain-text terminal output lines from a terminal session.",
57947
58311
  inputSchema: {
57948
- terminalId: z43.string(),
57949
- start: z43.number().optional(),
57950
- end: z43.number().optional(),
57951
- scrollback: z43.boolean().optional(),
57952
- stripAnsi: z43.boolean().optional().default(true)
58312
+ terminalId: z44.string(),
58313
+ start: z44.number().optional(),
58314
+ end: z44.number().optional(),
58315
+ scrollback: z44.boolean().optional(),
58316
+ stripAnsi: z44.boolean().optional().default(true)
57953
58317
  },
57954
58318
  outputSchema: {
57955
- terminalId: z43.string(),
57956
- lines: z43.array(z43.string()),
57957
- totalLines: z43.number().int().nonnegative()
58319
+ terminalId: z44.string(),
58320
+ lines: z44.array(z44.string()),
58321
+ totalLines: z44.number().int().nonnegative()
57958
58322
  }
57959
58323
  },
57960
58324
  async ({ terminalId, start, end, scrollback, stripAnsi: stripAnsi3 = true }) => {
@@ -57986,12 +58350,12 @@ async function createAgentMcpServer(options) {
57986
58350
  title: "Send terminal keys",
57987
58351
  description: "Send literal text or special key tokens to a terminal session.",
57988
58352
  inputSchema: {
57989
- terminalId: z43.string(),
57990
- keys: z43.string(),
57991
- literal: z43.boolean().optional()
58353
+ terminalId: z44.string(),
58354
+ keys: z44.string(),
58355
+ literal: z44.boolean().optional()
57992
58356
  },
57993
58357
  outputSchema: {
57994
- success: z43.boolean()
58358
+ success: z44.boolean()
57995
58359
  }
57996
58360
  },
57997
58361
  async ({ terminalId, keys, literal = false }) => {
@@ -58018,17 +58382,17 @@ async function createAgentMcpServer(options) {
58018
58382
  title: "Create schedule",
58019
58383
  description: "Create a recurring schedule that runs on an agent or a new agent.",
58020
58384
  inputSchema: {
58021
- prompt: z43.string().trim().min(1, "prompt is required"),
58022
- every: z43.string().optional(),
58023
- cron: z43.string().optional(),
58024
- name: z43.string().optional(),
58025
- target: z43.enum(["self", "new-agent"]).optional(),
58385
+ prompt: z44.string().trim().min(1, "prompt is required"),
58386
+ every: z44.string().optional(),
58387
+ cron: z44.string().optional(),
58388
+ name: z44.string().optional(),
58389
+ target: z44.enum(["self", "new-agent"]).optional(),
58026
58390
  provider: AgentProviderEnum.optional().describe(
58027
58391
  "Provider, or provider/model (for example: codex or codex/gpt-5.4)."
58028
58392
  ),
58029
- cwd: z43.string().optional(),
58030
- maxRuns: z43.number().int().positive().optional(),
58031
- expiresIn: z43.string().optional()
58393
+ cwd: z44.string().optional(),
58394
+ maxRuns: z44.number().int().positive().optional(),
58395
+ expiresIn: z44.string().optional()
58032
58396
  },
58033
58397
  outputSchema: ScheduleSummarySchema.shape
58034
58398
  },
@@ -58070,7 +58434,7 @@ async function createAgentMcpServer(options) {
58070
58434
  description: "List all schedules managed by the daemon.",
58071
58435
  inputSchema: {},
58072
58436
  outputSchema: {
58073
- schedules: z43.array(ScheduleSummarySchema)
58437
+ schedules: z44.array(ScheduleSummarySchema)
58074
58438
  }
58075
58439
  },
58076
58440
  async () => {
@@ -58092,7 +58456,7 @@ async function createAgentMcpServer(options) {
58092
58456
  title: "Inspect schedule",
58093
58457
  description: "Inspect a schedule and its run history.",
58094
58458
  inputSchema: {
58095
- id: z43.string()
58459
+ id: z44.string()
58096
58460
  },
58097
58461
  outputSchema: StoredScheduleSchema.shape
58098
58462
  },
@@ -58113,10 +58477,10 @@ async function createAgentMcpServer(options) {
58113
58477
  title: "Pause schedule",
58114
58478
  description: "Pause an active schedule.",
58115
58479
  inputSchema: {
58116
- id: z43.string()
58480
+ id: z44.string()
58117
58481
  },
58118
58482
  outputSchema: {
58119
- success: z43.boolean()
58483
+ success: z44.boolean()
58120
58484
  }
58121
58485
  },
58122
58486
  async ({ id }) => {
@@ -58136,10 +58500,10 @@ async function createAgentMcpServer(options) {
58136
58500
  title: "Resume schedule",
58137
58501
  description: "Resume a paused schedule.",
58138
58502
  inputSchema: {
58139
- id: z43.string()
58503
+ id: z44.string()
58140
58504
  },
58141
58505
  outputSchema: {
58142
- success: z43.boolean()
58506
+ success: z44.boolean()
58143
58507
  }
58144
58508
  },
58145
58509
  async ({ id }) => {
@@ -58159,10 +58523,10 @@ async function createAgentMcpServer(options) {
58159
58523
  title: "Delete schedule",
58160
58524
  description: "Delete a schedule permanently.",
58161
58525
  inputSchema: {
58162
- id: z43.string()
58526
+ id: z44.string()
58163
58527
  },
58164
58528
  outputSchema: {
58165
- success: z43.boolean()
58529
+ success: z44.boolean()
58166
58530
  }
58167
58531
  },
58168
58532
  async ({ id }) => {
@@ -58183,7 +58547,7 @@ async function createAgentMcpServer(options) {
58183
58547
  description: "List available agent providers and their modes.",
58184
58548
  inputSchema: {},
58185
58549
  outputSchema: {
58186
- providers: z43.array(ProviderSummarySchema)
58550
+ providers: z44.array(ProviderSummarySchema)
58187
58551
  }
58188
58552
  },
58189
58553
  async () => ({
@@ -58210,8 +58574,8 @@ async function createAgentMcpServer(options) {
58210
58574
  provider: AgentProviderEnum
58211
58575
  },
58212
58576
  outputSchema: {
58213
- provider: z43.string(),
58214
- models: z43.array(AgentModelSchema)
58577
+ provider: z44.string(),
58578
+ models: z44.array(AgentModelSchema)
58215
58579
  }
58216
58580
  },
58217
58581
  async ({ provider }) => {
@@ -58238,10 +58602,10 @@ async function createAgentMcpServer(options) {
58238
58602
  title: "List worktrees",
58239
58603
  description: "List Appostle-managed git worktrees for a repository.",
58240
58604
  inputSchema: {
58241
- cwd: z43.string().optional().describe("Optional repository cwd. Defaults to the caller agent cwd.")
58605
+ cwd: z44.string().optional().describe("Optional repository cwd. Defaults to the caller agent cwd.")
58242
58606
  },
58243
58607
  outputSchema: {
58244
- worktrees: z43.array(WorktreeSummarySchema)
58608
+ worktrees: z44.array(WorktreeSummarySchema)
58245
58609
  }
58246
58610
  },
58247
58611
  async ({ cwd }) => {
@@ -58262,16 +58626,16 @@ async function createAgentMcpServer(options) {
58262
58626
  title: "Create worktree",
58263
58627
  description: "Create a Appostle-managed git worktree.",
58264
58628
  inputSchema: {
58265
- cwd: z43.string().optional().describe("Optional repository cwd. Defaults to the caller agent cwd."),
58266
- branchName: z43.string().optional(),
58267
- baseBranch: z43.string().optional(),
58268
- refName: z43.string().min(1).optional(),
58269
- action: z43.enum(["branch-off", "checkout"]).optional(),
58270
- githubPrNumber: z43.number().int().positive().optional()
58629
+ cwd: z44.string().optional().describe("Optional repository cwd. Defaults to the caller agent cwd."),
58630
+ branchName: z44.string().optional(),
58631
+ baseBranch: z44.string().optional(),
58632
+ refName: z44.string().min(1).optional(),
58633
+ action: z44.enum(["branch-off", "checkout"]).optional(),
58634
+ githubPrNumber: z44.number().int().positive().optional()
58271
58635
  },
58272
58636
  outputSchema: {
58273
- branchName: z43.string(),
58274
- worktreePath: z43.string()
58637
+ branchName: z44.string(),
58638
+ worktreePath: z44.string()
58275
58639
  }
58276
58640
  },
58277
58641
  async ({ cwd, branchName, baseBranch, refName, action, githubPrNumber }) => {
@@ -58308,12 +58672,12 @@ async function createAgentMcpServer(options) {
58308
58672
  title: "Archive worktree",
58309
58673
  description: "Delete a Appostle-managed git worktree.",
58310
58674
  inputSchema: {
58311
- cwd: z43.string().optional().describe("Optional repository cwd. Defaults to the caller agent cwd."),
58312
- worktreePath: z43.string().optional(),
58313
- worktreeSlug: z43.string().optional()
58675
+ cwd: z44.string().optional().describe("Optional repository cwd. Defaults to the caller agent cwd."),
58676
+ worktreePath: z44.string().optional(),
58677
+ worktreeSlug: z44.string().optional()
58314
58678
  },
58315
58679
  outputSchema: {
58316
- success: z43.boolean()
58680
+ success: z44.boolean()
58317
58681
  }
58318
58682
  },
58319
58683
  async ({ cwd, worktreePath, worktreeSlug }) => {
@@ -58335,14 +58699,14 @@ async function createAgentMcpServer(options) {
58335
58699
  title: "Get agent activity",
58336
58700
  description: "Return recent agent timeline entries as a curated summary.",
58337
58701
  inputSchema: {
58338
- agentId: z43.string(),
58339
- limit: z43.number().optional().describe("Optional limit for number of activities to include (most recent first).")
58702
+ agentId: z44.string(),
58703
+ limit: z44.number().optional().describe("Optional limit for number of activities to include (most recent first).")
58340
58704
  },
58341
58705
  outputSchema: {
58342
- agentId: z43.string(),
58343
- updateCount: z43.number(),
58344
- currentModeId: z43.string().nullable(),
58345
- content: z43.string()
58706
+ agentId: z44.string(),
58707
+ updateCount: z44.number(),
58708
+ currentModeId: z44.string().nullable(),
58709
+ content: z44.string()
58346
58710
  }
58347
58711
  },
58348
58712
  async ({ agentId, limit }) => {
@@ -58383,12 +58747,12 @@ ${curatedContent}`;
58383
58747
  title: "Set agent session mode",
58384
58748
  description: "Switch the agent's session mode (plan, bypassPermissions, read-only, auto, etc.).",
58385
58749
  inputSchema: {
58386
- agentId: z43.string(),
58387
- modeId: z43.string()
58750
+ agentId: z44.string(),
58751
+ modeId: z44.string()
58388
58752
  },
58389
58753
  outputSchema: {
58390
- success: z43.boolean(),
58391
- newMode: z43.string()
58754
+ success: z44.boolean(),
58755
+ newMode: z44.string()
58392
58756
  }
58393
58757
  },
58394
58758
  async ({ agentId, modeId }) => {
@@ -58406,9 +58770,9 @@ ${curatedContent}`;
58406
58770
  description: "Return all pending permission requests across all agents with the normalized payloads.",
58407
58771
  inputSchema: {},
58408
58772
  outputSchema: {
58409
- permissions: z43.array(
58410
- z43.object({
58411
- agentId: z43.string(),
58773
+ permissions: z44.array(
58774
+ z44.object({
58775
+ agentId: z44.string(),
58412
58776
  status: AgentStatusEnum,
58413
58777
  request: AgentPermissionRequestPayloadSchema
58414
58778
  })
@@ -58436,12 +58800,12 @@ ${curatedContent}`;
58436
58800
  title: "Respond to permission",
58437
58801
  description: "Approve or deny a pending permission request with an AgentManager-compatible response payload.",
58438
58802
  inputSchema: {
58439
- agentId: z43.string(),
58440
- requestId: z43.string(),
58803
+ agentId: z44.string(),
58804
+ requestId: z44.string(),
58441
58805
  response: AgentPermissionResponseSchema
58442
58806
  },
58443
58807
  outputSchema: {
58444
- success: z43.boolean()
58808
+ success: z44.boolean()
58445
58809
  }
58446
58810
  },
58447
58811
  async ({ agentId, requestId, response }) => {
@@ -58468,7 +58832,7 @@ async function createMcpWorktree(options) {
58468
58832
  }
58469
58833
 
58470
58834
  // ../server/src/server/workspace-registry-bootstrap.ts
58471
- import path29 from "node:path";
58835
+ import path30 from "node:path";
58472
58836
  function minIsoDate(left, right) {
58473
58837
  if (!left) {
58474
58838
  return right;
@@ -58565,8 +58929,8 @@ async function bootstrapWorkspaceRegistries(options) {
58565
58929
  }
58566
58930
  options.logger.info(
58567
58931
  {
58568
- projectsFile: path29.join(options.appostleHome, "projects", "projects.json"),
58569
- workspacesFile: path29.join(options.appostleHome, "projects", "workspaces.json"),
58932
+ projectsFile: path30.join(options.appostleHome, "projects", "projects.json"),
58933
+ workspacesFile: path30.join(options.appostleHome, "projects", "workspaces.json"),
58570
58934
  materializedProjects: projectRanges.size,
58571
58935
  materializedWorkspaces: recordsByWorkspaceId.size
58572
58936
  },
@@ -58762,83 +59126,83 @@ var CheckoutDiffManager = class {
58762
59126
 
58763
59127
  // ../server/src/server/loop-service.ts
58764
59128
  import { randomUUID as randomUUID13 } from "node:crypto";
58765
- import { promises as fs17 } from "node:fs";
58766
- import path30 from "node:path";
58767
- import { z as z44 } from "zod";
59129
+ import { promises as fs18 } from "node:fs";
59130
+ import path31 from "node:path";
59131
+ import { z as z45 } from "zod";
58768
59132
  var LOOP_ID_LENGTH = 8;
58769
59133
  var DEFAULT_LOOP_PROVIDER = "claude";
58770
59134
  var MAX_VERIFY_OUTPUT_BYTES = 64 * 1024;
58771
- var LoopVerifyPromptSchema = z44.object({
58772
- passed: z44.boolean(),
58773
- reason: z44.string().min(1)
58774
- });
58775
- var LoopLogEntrySchema2 = z44.object({
58776
- seq: z44.number().int().positive(),
58777
- timestamp: z44.string(),
58778
- iteration: z44.number().int().positive().nullable(),
58779
- source: z44.enum(["loop", "worker", "verifier", "verify-check"]),
58780
- level: z44.enum(["info", "error"]),
58781
- text: z44.string()
58782
- });
58783
- var LoopVerifyCheckResultSchema2 = z44.object({
58784
- command: z44.string(),
58785
- exitCode: z44.number().int(),
58786
- passed: z44.boolean(),
58787
- stdout: z44.string(),
58788
- stderr: z44.string(),
58789
- startedAt: z44.string(),
58790
- completedAt: z44.string()
58791
- });
58792
- var LoopVerifyPromptResultSchema2 = z44.object({
58793
- passed: z44.boolean(),
58794
- reason: z44.string(),
58795
- verifierAgentId: z44.string().nullable(),
58796
- startedAt: z44.string(),
58797
- completedAt: z44.string()
58798
- });
58799
- var LoopIterationRecordSchema2 = z44.object({
58800
- index: z44.number().int().positive(),
58801
- workerAgentId: z44.string().nullable(),
58802
- workerStartedAt: z44.string(),
58803
- workerCompletedAt: z44.string().nullable(),
58804
- verifierAgentId: z44.string().nullable(),
58805
- status: z44.enum(["running", "succeeded", "failed", "stopped"]),
58806
- workerOutcome: z44.enum(["completed", "failed", "canceled"]).nullable(),
58807
- failureReason: z44.string().nullable(),
58808
- verifyChecks: z44.array(LoopVerifyCheckResultSchema2),
59135
+ var LoopVerifyPromptSchema = z45.object({
59136
+ passed: z45.boolean(),
59137
+ reason: z45.string().min(1)
59138
+ });
59139
+ var LoopLogEntrySchema2 = z45.object({
59140
+ seq: z45.number().int().positive(),
59141
+ timestamp: z45.string(),
59142
+ iteration: z45.number().int().positive().nullable(),
59143
+ source: z45.enum(["loop", "worker", "verifier", "verify-check"]),
59144
+ level: z45.enum(["info", "error"]),
59145
+ text: z45.string()
59146
+ });
59147
+ var LoopVerifyCheckResultSchema2 = z45.object({
59148
+ command: z45.string(),
59149
+ exitCode: z45.number().int(),
59150
+ passed: z45.boolean(),
59151
+ stdout: z45.string(),
59152
+ stderr: z45.string(),
59153
+ startedAt: z45.string(),
59154
+ completedAt: z45.string()
59155
+ });
59156
+ var LoopVerifyPromptResultSchema2 = z45.object({
59157
+ passed: z45.boolean(),
59158
+ reason: z45.string(),
59159
+ verifierAgentId: z45.string().nullable(),
59160
+ startedAt: z45.string(),
59161
+ completedAt: z45.string()
59162
+ });
59163
+ var LoopIterationRecordSchema2 = z45.object({
59164
+ index: z45.number().int().positive(),
59165
+ workerAgentId: z45.string().nullable(),
59166
+ workerStartedAt: z45.string(),
59167
+ workerCompletedAt: z45.string().nullable(),
59168
+ verifierAgentId: z45.string().nullable(),
59169
+ status: z45.enum(["running", "succeeded", "failed", "stopped"]),
59170
+ workerOutcome: z45.enum(["completed", "failed", "canceled"]).nullable(),
59171
+ failureReason: z45.string().nullable(),
59172
+ verifyChecks: z45.array(LoopVerifyCheckResultSchema2),
58809
59173
  verifyPrompt: LoopVerifyPromptResultSchema2.nullable()
58810
59174
  });
58811
- var LoopRecordSchema2 = z44.object({
58812
- id: z44.string(),
58813
- name: z44.string().nullable(),
58814
- prompt: z44.string(),
58815
- cwd: z44.string(),
58816
- provider: z44.string(),
58817
- model: z44.string().nullable(),
58818
- workerProvider: z44.string().nullable(),
58819
- workerModel: z44.string().nullable(),
58820
- verifierProvider: z44.string().nullable(),
58821
- verifierModel: z44.string().nullable(),
58822
- verifyPrompt: z44.string().nullable(),
58823
- verifyChecks: z44.array(z44.string()),
58824
- archive: z44.boolean(),
58825
- sleepMs: z44.number().int().nonnegative(),
58826
- maxIterations: z44.number().int().positive().nullable(),
58827
- maxTimeMs: z44.number().int().positive().nullable(),
58828
- status: z44.enum(["running", "succeeded", "failed", "stopped"]),
58829
- createdAt: z44.string(),
58830
- updatedAt: z44.string(),
58831
- startedAt: z44.string(),
58832
- completedAt: z44.string().nullable(),
58833
- stopRequestedAt: z44.string().nullable(),
58834
- iterations: z44.array(LoopIterationRecordSchema2),
58835
- logs: z44.array(LoopLogEntrySchema2),
58836
- nextLogSeq: z44.number().int().positive(),
58837
- activeIteration: z44.number().int().positive().nullable(),
58838
- activeWorkerAgentId: z44.string().nullable(),
58839
- activeVerifierAgentId: z44.string().nullable()
58840
- });
58841
- var StoredLoopsSchema = z44.array(LoopRecordSchema2);
59175
+ var LoopRecordSchema2 = z45.object({
59176
+ id: z45.string(),
59177
+ name: z45.string().nullable(),
59178
+ prompt: z45.string(),
59179
+ cwd: z45.string(),
59180
+ provider: z45.string(),
59181
+ model: z45.string().nullable(),
59182
+ workerProvider: z45.string().nullable(),
59183
+ workerModel: z45.string().nullable(),
59184
+ verifierProvider: z45.string().nullable(),
59185
+ verifierModel: z45.string().nullable(),
59186
+ verifyPrompt: z45.string().nullable(),
59187
+ verifyChecks: z45.array(z45.string()),
59188
+ archive: z45.boolean(),
59189
+ sleepMs: z45.number().int().nonnegative(),
59190
+ maxIterations: z45.number().int().positive().nullable(),
59191
+ maxTimeMs: z45.number().int().positive().nullable(),
59192
+ status: z45.enum(["running", "succeeded", "failed", "stopped"]),
59193
+ createdAt: z45.string(),
59194
+ updatedAt: z45.string(),
59195
+ startedAt: z45.string(),
59196
+ completedAt: z45.string().nullable(),
59197
+ stopRequestedAt: z45.string().nullable(),
59198
+ iterations: z45.array(LoopIterationRecordSchema2),
59199
+ logs: z45.array(LoopLogEntrySchema2),
59200
+ nextLogSeq: z45.number().int().positive(),
59201
+ activeIteration: z45.number().int().positive().nullable(),
59202
+ activeWorkerAgentId: z45.string().nullable(),
59203
+ activeVerifierAgentId: z45.string().nullable()
59204
+ });
59205
+ var StoredLoopsSchema = z45.array(LoopRecordSchema2);
58842
59206
  function nowIso() {
58843
59207
  return (/* @__PURE__ */ new Date()).toISOString();
58844
59208
  }
@@ -58969,7 +59333,7 @@ var LoopService = class {
58969
59333
  this.loops = /* @__PURE__ */ new Map();
58970
59334
  this.persistQueue = Promise.resolve();
58971
59335
  this.running = /* @__PURE__ */ new Map();
58972
- this.storePath = path30.join(options.appostleHome, "loops", "loops.json");
59336
+ this.storePath = path31.join(options.appostleHome, "loops", "loops.json");
58973
59337
  this.logger = options.logger.child({ module: "loop-service" });
58974
59338
  }
58975
59339
  async initialize() {
@@ -58978,7 +59342,7 @@ var LoopService = class {
58978
59342
  }
58979
59343
  this.loops.clear();
58980
59344
  try {
58981
- const raw = await fs17.readFile(this.storePath, "utf8");
59345
+ const raw = await fs18.readFile(this.storePath, "utf8");
58982
59346
  const parsed = StoredLoopsSchema.parse(JSON.parse(raw));
58983
59347
  for (const record of parsed) {
58984
59348
  if (record.status === "running") {
@@ -59038,7 +59402,7 @@ var LoopService = class {
59038
59402
  id: createLoopId(),
59039
59403
  name: normalizeName(input.name),
59040
59404
  prompt,
59041
- cwd: path30.resolve(input.cwd),
59405
+ cwd: path31.resolve(input.cwd),
59042
59406
  provider: input.provider ?? DEFAULT_LOOP_PROVIDER,
59043
59407
  model: normalizePrompt(input.model, "model"),
59044
59408
  workerProvider: input.workerProvider ?? null,
@@ -59486,11 +59850,11 @@ ${output}` : `exit ${result.exitCode}`
59486
59850
  }
59487
59851
  async persist() {
59488
59852
  const nextPersist = this.persistQueue.then(async () => {
59489
- await fs17.mkdir(path30.dirname(this.storePath), { recursive: true });
59853
+ await fs18.mkdir(path31.dirname(this.storePath), { recursive: true });
59490
59854
  const records = Array.from(this.loops.values()).sort(
59491
59855
  (left, right) => left.createdAt.localeCompare(right.createdAt)
59492
59856
  );
59493
- await fs17.writeFile(this.storePath, JSON.stringify(records, null, 2), "utf8");
59857
+ await fs18.writeFile(this.storePath, JSON.stringify(records, null, 2), "utf8");
59494
59858
  });
59495
59859
  this.persistQueue = nextPersist.catch(() => {
59496
59860
  });
@@ -60031,20 +60395,20 @@ var ScheduleService = class {
60031
60395
  import { randomUUID as randomUUID15 } from "node:crypto";
60032
60396
 
60033
60397
  // ../server/src/server/quest/store.ts
60034
- import { promises as fs18 } from "node:fs";
60035
- import path31 from "node:path";
60036
- import { z as z45 } from "zod";
60037
- var StoredQuestsSchema = z45.array(QuestRecordSchema);
60398
+ import { promises as fs19 } from "node:fs";
60399
+ import path32 from "node:path";
60400
+ import { z as z46 } from "zod";
60401
+ var StoredQuestsSchema = z46.array(QuestRecordSchema);
60038
60402
  var QuestStore = class {
60039
60403
  constructor(options) {
60040
- this.filePath = path31.join(options.appostleHome, "quests", "quests.json");
60404
+ this.filePath = path32.join(options.appostleHome, "quests", "quests.json");
60041
60405
  this.logger = options.logger;
60042
60406
  this.records = /* @__PURE__ */ new Map();
60043
60407
  this.persistQueue = Promise.resolve();
60044
60408
  }
60045
60409
  async initialize() {
60046
60410
  try {
60047
- const raw = await fs18.readFile(this.filePath, "utf8");
60411
+ const raw = await fs19.readFile(this.filePath, "utf8");
60048
60412
  const parsed = StoredQuestsSchema.parse(JSON.parse(raw));
60049
60413
  this.records = new Map(parsed.map((r) => [r.id, r]));
60050
60414
  } catch (err) {
@@ -60078,9 +60442,9 @@ var QuestStore = class {
60078
60442
  }
60079
60443
  async persist() {
60080
60444
  this.persistQueue = this.persistQueue.then(async () => {
60081
- await fs18.mkdir(path31.dirname(this.filePath), { recursive: true });
60445
+ await fs19.mkdir(path32.dirname(this.filePath), { recursive: true });
60082
60446
  const payload = JSON.stringify([...this.records.values()], null, 2);
60083
- await fs18.writeFile(this.filePath, payload, "utf8");
60447
+ await fs19.writeFile(this.filePath, payload, "utf8");
60084
60448
  }).catch((err) => {
60085
60449
  this.logger.error(
60086
60450
  { err: err instanceof Error ? err.message : String(err) },
@@ -60138,7 +60502,7 @@ function getRulesForKind(kind) {
60138
60502
  }
60139
60503
 
60140
60504
  // ../server/src/server/quest/quest-metadata-generator.ts
60141
- import { z as z46 } from "zod";
60505
+ import { z as z47 } from "zod";
60142
60506
  var MAX_AUTO_QUEST_NAME_CHARS = 40;
60143
60507
  function hasExplicitName(name) {
60144
60508
  return Boolean(name && name.trim().length > 0);
@@ -60150,7 +60514,7 @@ function normalizeAutoName(name) {
60150
60514
  }
60151
60515
  return normalized.slice(0, MAX_AUTO_QUEST_NAME_CHARS).trim() || null;
60152
60516
  }
60153
- function buildPrompt3(prompt) {
60517
+ function buildPrompt4(prompt) {
60154
60518
  return [
60155
60519
  "Generate a short label for a multi-agent quest based on the user prompt.",
60156
60520
  `Name: short descriptive label (<= ${MAX_AUTO_QUEST_NAME_CHARS} chars).`,
@@ -60168,8 +60532,8 @@ async function generateAndApplyQuestMetadata(options) {
60168
60532
  if (hasExplicitName(options.explicitName)) {
60169
60533
  return;
60170
60534
  }
60171
- const schema = z46.object({
60172
- name: z46.string().min(1).max(MAX_AUTO_QUEST_NAME_CHARS)
60535
+ const schema = z47.object({
60536
+ name: z47.string().min(1).max(MAX_AUTO_QUEST_NAME_CHARS)
60173
60537
  });
60174
60538
  const generator = options.deps?.generateStructuredAgentResponseWithFallback ?? generateStructuredAgentResponseWithFallback;
60175
60539
  let result;
@@ -60177,7 +60541,7 @@ async function generateAndApplyQuestMetadata(options) {
60177
60541
  result = await generator({
60178
60542
  manager: options.questService.agentManager,
60179
60543
  cwd: options.cwd,
60180
- prompt: buildPrompt3(prompt),
60544
+ prompt: buildPrompt4(prompt),
60181
60545
  schema,
60182
60546
  schemaName: "QuestMetadata",
60183
60547
  maxRetries: 2,
@@ -60844,17 +61208,17 @@ ${text || "(no output)"}`).join("\n\n");
60844
61208
 
60845
61209
  // ../server/src/server/quest/runner-orchestrator.ts
60846
61210
  import { access, readFile as readFile5 } from "node:fs/promises";
60847
- import path32 from "node:path";
61211
+ import path33 from "node:path";
60848
61212
  import { fileURLToPath as fileURLToPath5 } from "node:url";
60849
61213
 
60850
61214
  // ../server/src/server/quest/orchestrator-mcp.ts
60851
61215
  import { createSdkMcpServer as createSdkMcpServer2, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
60852
- import { z as z47 } from "zod";
61216
+ import { z as z48 } from "zod";
60853
61217
  var HANDOFF_INPUT_SHAPE = {
60854
- rolePath: z47.string().min(1).describe(
61218
+ rolePath: z48.string().min(1).describe(
60855
61219
  "Absolute filesystem path to the role .md file the worker should adopt as its system prompt. Must be one of the role file paths listed in your team roster \u2014 do not invent paths."
60856
61220
  ),
60857
- task: z47.string().min(1).describe(
61221
+ task: z48.string().min(1).describe(
60858
61222
  "Concrete, self-contained instruction for the worker. The worker has its own toolbelt and reads the role at rolePath as its system prompt \u2014 write the task as a normal user message describing what to do, what inputs to read, and where to write outputs. Reference the hivemind doc by absolute path if relevant."
60859
61223
  )
60860
61224
  };
@@ -61075,16 +61439,16 @@ var FALLBACK_QUEEN_PROMPT_TEMPLATE = [
61075
61439
  var QUEEN_PROMPT_FILENAME = "queen-prompt.md";
61076
61440
  var QUEEN_PROMPT_MAX_LOOKUP_LEVELS = 10;
61077
61441
  async function findQueenPromptOnDisk() {
61078
- const start = path32.dirname(fileURLToPath5(import.meta.url));
61442
+ const start = path33.dirname(fileURLToPath5(import.meta.url));
61079
61443
  let cursor = start;
61080
61444
  for (let i = 0; i < QUEEN_PROMPT_MAX_LOOKUP_LEVELS; i += 1) {
61081
- const candidate = path32.join(cursor, QUEEN_PROMPT_FILENAME);
61445
+ const candidate = path33.join(cursor, QUEEN_PROMPT_FILENAME);
61082
61446
  try {
61083
61447
  await access(candidate);
61084
61448
  return candidate;
61085
61449
  } catch {
61086
61450
  }
61087
- const parent = path32.dirname(cursor);
61451
+ const parent = path33.dirname(cursor);
61088
61452
  if (parent === cursor) break;
61089
61453
  cursor = parent;
61090
61454
  }
@@ -61130,7 +61494,7 @@ async function resolveWorkerConfigForHandoff(args) {
61130
61494
  title: `quest ${record.id} handoff: ${taskTitle}`,
61131
61495
  internal: true
61132
61496
  };
61133
- const roleName = path32.basename(rolePath, path32.extname(rolePath));
61497
+ const roleName = path33.basename(rolePath, path33.extname(rolePath));
61134
61498
  try {
61135
61499
  const resolved = await resolveRole({
61136
61500
  roleName,
@@ -61309,7 +61673,7 @@ var OrchestratorRunner = class {
61309
61673
  import { promisify as promisify4 } from "node:util";
61310
61674
  import { execFile as execFile3 } from "node:child_process";
61311
61675
  import { appendFile, mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
61312
- import path33 from "node:path";
61676
+ import path34 from "node:path";
61313
61677
  var execFileAsync3 = promisify4(execFile3);
61314
61678
  var MAX_VERIFY_OUTPUT_BYTES2 = 64 * 1024;
61315
61679
  function nowIso5() {
@@ -61384,7 +61748,7 @@ function runContainsPromise(run, completionPromise) {
61384
61748
  return finalText.includes(completionPromise);
61385
61749
  }
61386
61750
  function resolveRalphStatePaths(cwd) {
61387
- const dir = path33.join(cwd, ".hiveminds", "ralph loop");
61751
+ const dir = path34.join(cwd, ".hiveminds", "ralph loop");
61388
61752
  return {
61389
61753
  dir,
61390
61754
  logPath: ""
@@ -61394,7 +61758,7 @@ async function initializeRalphState(args) {
61394
61758
  const base = resolveRalphStatePaths(args.record.cwd);
61395
61759
  const paths = {
61396
61760
  dir: base.dir,
61397
- logPath: path33.join(base.dir, `ralphloop_${args.record.id}.md`)
61761
+ logPath: path34.join(base.dir, `ralphloop_${args.record.id}.md`)
61398
61762
  };
61399
61763
  await mkdir9(paths.dir, { recursive: true });
61400
61764
  await writeFile8(
@@ -61629,8 +61993,8 @@ function deepMerge(current, patch) {
61629
61993
  }
61630
61994
  return next;
61631
61995
  }
61632
- function getValueAtPath(config, path39) {
61633
- return path39.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, config);
61996
+ function getValueAtPath(config, path40) {
61997
+ return path40.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, config);
61634
61998
  }
61635
61999
  function isEqualValue(a, b) {
61636
62000
  return JSON.stringify(a) === JSON.stringify(b);
@@ -61649,20 +62013,20 @@ var DaemonConfigStore = class {
61649
62013
  patch(partial) {
61650
62014
  const parsedPatch = MutableDaemonConfigPatchSchema.parse(partial);
61651
62015
  const next = MutableDaemonConfigSchema.parse(deepMerge(this.current, parsedPatch));
61652
- const changedFieldPaths = Array.from(this.fieldChangeHandlers.keys()).filter((path39) => {
61653
- return !isEqualValue(getValueAtPath(this.current, path39), getValueAtPath(next, path39));
62016
+ const changedFieldPaths = Array.from(this.fieldChangeHandlers.keys()).filter((path40) => {
62017
+ return !isEqualValue(getValueAtPath(this.current, path40), getValueAtPath(next, path40));
61654
62018
  });
61655
62019
  if (changedFieldPaths.length === 0 && isEqualValue(this.current, next)) {
61656
62020
  return this.current;
61657
62021
  }
61658
62022
  this.persistConfig(next);
61659
62023
  this.current = next;
61660
- for (const path39 of changedFieldPaths) {
61661
- const handlers = this.fieldChangeHandlers.get(path39);
62024
+ for (const path40 of changedFieldPaths) {
62025
+ const handlers = this.fieldChangeHandlers.get(path40);
61662
62026
  if (!handlers) {
61663
62027
  continue;
61664
62028
  }
61665
- const value = getValueAtPath(next, path39);
62029
+ const value = getValueAtPath(next, path40);
61666
62030
  for (const handler of handlers) {
61667
62031
  handler(value);
61668
62032
  }
@@ -61672,18 +62036,18 @@ var DaemonConfigStore = class {
61672
62036
  }
61673
62037
  return next;
61674
62038
  }
61675
- onFieldChange(path39, handler) {
61676
- const handlers = this.fieldChangeHandlers.get(path39) ?? /* @__PURE__ */ new Set();
62039
+ onFieldChange(path40, handler) {
62040
+ const handlers = this.fieldChangeHandlers.get(path40) ?? /* @__PURE__ */ new Set();
61677
62041
  handlers.add(handler);
61678
- this.fieldChangeHandlers.set(path39, handlers);
62042
+ this.fieldChangeHandlers.set(path40, handlers);
61679
62043
  return () => {
61680
- const currentHandlers = this.fieldChangeHandlers.get(path39);
62044
+ const currentHandlers = this.fieldChangeHandlers.get(path40);
61681
62045
  if (!currentHandlers) {
61682
62046
  return;
61683
62047
  }
61684
62048
  currentHandlers.delete(handler);
61685
62049
  if (currentHandlers.size === 0) {
61686
- this.fieldChangeHandlers.delete(path39);
62050
+ this.fieldChangeHandlers.delete(path40);
61687
62051
  }
61688
62052
  };
61689
62053
  }
@@ -62576,13 +62940,13 @@ function createTerminalManager() {
62576
62940
  }
62577
62941
 
62578
62942
  // ../server/src/shared/connection-offer.ts
62579
- import { z as z48 } from "zod";
62580
- var ConnectionOfferV2Schema = z48.object({
62581
- v: z48.literal(2),
62582
- serverId: z48.string().min(1),
62583
- daemonPublicKeyB64: z48.string().min(1),
62584
- relay: z48.object({
62585
- endpoint: z48.string().min(1)
62943
+ import { z as z49 } from "zod";
62944
+ var ConnectionOfferV2Schema = z49.object({
62945
+ v: z49.literal(2),
62946
+ serverId: z49.string().min(1),
62947
+ daemonPublicKeyB64: z49.string().min(1),
62948
+ relay: z49.object({
62949
+ endpoint: z49.string().min(1)
62586
62950
  })
62587
62951
  });
62588
62952
 
@@ -63229,7 +63593,7 @@ function createAuthServerClient(options) {
63229
63593
 
63230
63594
  // ../server/src/server/package-version.ts
63231
63595
  import { existsSync as existsSync19, readFileSync as readFileSync10 } from "node:fs";
63232
- import path34 from "node:path";
63596
+ import path35 from "node:path";
63233
63597
  import { fileURLToPath as fileURLToPath6 } from "node:url";
63234
63598
  var PackageVersionResolutionError = class extends Error {
63235
63599
  constructor(params) {
@@ -63239,9 +63603,9 @@ var PackageVersionResolutionError = class extends Error {
63239
63603
  };
63240
63604
  function resolvePackageVersion(params) {
63241
63605
  const moduleUrl = params.moduleUrl ?? import.meta.url;
63242
- let currentDir = path34.dirname(fileURLToPath6(moduleUrl));
63606
+ let currentDir = path35.dirname(fileURLToPath6(moduleUrl));
63243
63607
  while (true) {
63244
- const packageJsonPath = path34.join(currentDir, "package.json");
63608
+ const packageJsonPath = path35.join(currentDir, "package.json");
63245
63609
  if (existsSync19(packageJsonPath)) {
63246
63610
  try {
63247
63611
  const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf8"));
@@ -63260,7 +63624,7 @@ function resolvePackageVersion(params) {
63260
63624
  }
63261
63625
  }
63262
63626
  }
63263
- const parentDir = path34.dirname(currentDir);
63627
+ const parentDir = path35.dirname(currentDir);
63264
63628
  if (parentDir === currentDir) {
63265
63629
  break;
63266
63630
  }
@@ -63765,11 +64129,11 @@ async function createAppostleDaemon(config, rootLogger) {
63765
64129
  httpServer.on("upgrade", scriptProxyUpgradeHandler);
63766
64130
  const agentStorage = new AgentStorage(config.agentStoragePath, logger);
63767
64131
  const projectRegistry = new FileBackedProjectRegistry(
63768
- path35.join(config.appostleHome, "projects", "projects.json"),
64132
+ path36.join(config.appostleHome, "projects", "projects.json"),
63769
64133
  logger
63770
64134
  );
63771
64135
  workspaceRegistry = new FileBackedWorkspaceRegistry(
63772
- path35.join(config.appostleHome, "projects", "workspaces.json"),
64136
+ path36.join(config.appostleHome, "projects", "workspaces.json"),
63773
64137
  logger
63774
64138
  );
63775
64139
  const chatService = new FileBackedChatService({
@@ -63787,6 +64151,7 @@ async function createAppostleDaemon(config, rootLogger) {
63787
64151
  registry: agentStorage,
63788
64152
  appostleHome: config.appostleHome,
63789
64153
  chromeEnabled: config.chromeEnabled ?? true,
64154
+ playwrightEnabled: config.playwrightEnabled ?? true,
63790
64155
  ownerUserId: ownerUserId ?? void 0,
63791
64156
  logger
63792
64157
  });
@@ -64211,22 +64576,22 @@ async function closeAllAgents(logger, agentManager) {
64211
64576
  }
64212
64577
 
64213
64578
  // ../server/src/server/config.ts
64214
- import path37 from "node:path";
64215
- import { z as z52 } from "zod";
64579
+ import path38 from "node:path";
64580
+ import { z as z53 } from "zod";
64216
64581
 
64217
64582
  // ../server/src/server/speech/speech-config-resolver.ts
64218
- import { z as z51 } from "zod";
64583
+ import { z as z52 } from "zod";
64219
64584
 
64220
64585
  // ../server/src/server/speech/providers/local/config.ts
64221
- import path36 from "node:path";
64222
- import { z as z49 } from "zod";
64223
- var DEFAULT_LOCAL_MODELS_SUBDIR = path36.join("models", "local-speech");
64224
- var NumberLikeSchema2 = z49.union([z49.number(), z49.string().trim().min(1)]);
64225
- var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z49.coerce.number().finite()).optional();
64226
- var OptionalIntegerSchema = NumberLikeSchema2.pipe(z49.coerce.number().int()).optional();
64227
- var LocalSpeechResolutionSchema = z49.object({
64228
- includeProviderConfig: z49.boolean(),
64229
- modelsDir: z49.string().trim().min(1),
64586
+ import path37 from "node:path";
64587
+ import { z as z50 } from "zod";
64588
+ var DEFAULT_LOCAL_MODELS_SUBDIR = path37.join("models", "local-speech");
64589
+ var NumberLikeSchema2 = z50.union([z50.number(), z50.string().trim().min(1)]);
64590
+ var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z50.coerce.number().finite()).optional();
64591
+ var OptionalIntegerSchema = NumberLikeSchema2.pipe(z50.coerce.number().int()).optional();
64592
+ var LocalSpeechResolutionSchema = z50.object({
64593
+ includeProviderConfig: z50.boolean(),
64594
+ modelsDir: z50.string().trim().min(1),
64230
64595
  dictationLocalSttModel: LocalSttModelIdSchema.default(DEFAULT_LOCAL_STT_MODEL),
64231
64596
  voiceLocalSttModel: LocalSttModelIdSchema.default(DEFAULT_LOCAL_STT_MODEL),
64232
64597
  voiceLocalTtsModel: LocalTtsModelIdSchema.default(DEFAULT_LOCAL_TTS_MODEL),
@@ -64247,7 +64612,7 @@ function resolveLocalSpeechConfig(params) {
64247
64612
  const includeProviderConfig = shouldIncludeLocalProviderConfig(params);
64248
64613
  const parsed = LocalSpeechResolutionSchema.parse({
64249
64614
  includeProviderConfig,
64250
- modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path36.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
64615
+ modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path37.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
64251
64616
  dictationLocalSttModel: params.env.APPOSTLE_DICTATION_LOCAL_STT_MODEL ?? persistedLocalFeatureModel(
64252
64617
  params.providers.dictationStt.provider,
64253
64618
  params.providers.dictationStt.enabled,
@@ -64282,17 +64647,17 @@ function resolveLocalSpeechConfig(params) {
64282
64647
  }
64283
64648
 
64284
64649
  // ../server/src/server/speech/speech-types.ts
64285
- import { z as z50 } from "zod";
64286
- var SpeechProviderIdSchema2 = z50.enum(["openai", "local"]);
64287
- var RequestedSpeechProviderSchema = z50.object({
64650
+ import { z as z51 } from "zod";
64651
+ var SpeechProviderIdSchema2 = z51.enum(["openai", "local"]);
64652
+ var RequestedSpeechProviderSchema = z51.object({
64288
64653
  provider: SpeechProviderIdSchema2,
64289
- explicit: z50.boolean(),
64290
- enabled: z50.boolean().optional()
64654
+ explicit: z51.boolean(),
64655
+ enabled: z51.boolean().optional()
64291
64656
  });
64292
64657
 
64293
64658
  // ../server/src/server/speech/speech-config-resolver.ts
64294
- var OptionalSpeechProviderSchema = z51.string().trim().toLowerCase().pipe(SpeechProviderIdSchema2).optional();
64295
- var OptionalBooleanFlagSchema = z51.union([z51.boolean(), z51.string().trim().toLowerCase()]).optional().transform((value) => {
64659
+ var OptionalSpeechProviderSchema = z52.string().trim().toLowerCase().pipe(SpeechProviderIdSchema2).optional();
64660
+ var OptionalBooleanFlagSchema = z52.union([z52.boolean(), z52.string().trim().toLowerCase()]).optional().transform((value) => {
64296
64661
  if (typeof value === "boolean") {
64297
64662
  return value;
64298
64663
  }
@@ -64307,7 +64672,7 @@ var OptionalBooleanFlagSchema = z51.union([z51.boolean(), z51.string().trim().to
64307
64672
  }
64308
64673
  return void 0;
64309
64674
  });
64310
- var RequestedSpeechProvidersSchema = z51.object({
64675
+ var RequestedSpeechProvidersSchema = z52.object({
64311
64676
  dictationStt: OptionalSpeechProviderSchema.default("local"),
64312
64677
  voiceTurnDetection: OptionalSpeechProviderSchema.default("local"),
64313
64678
  voiceStt: OptionalSpeechProviderSchema.default("local"),
@@ -64416,9 +64781,9 @@ function parseBooleanEnv(value) {
64416
64781
  }
64417
64782
  return void 0;
64418
64783
  }
64419
- var OptionalVoiceLlmProviderSchema = z52.union([z52.string(), z52.null(), z52.undefined()]).transform(
64784
+ var OptionalVoiceLlmProviderSchema = z53.union([z53.string(), z53.null(), z53.undefined()]).transform(
64420
64785
  (value) => typeof value === "string" ? value.trim().toLowerCase() : null
64421
- ).pipe(z52.union([AgentProviderSchema, z52.null()]));
64786
+ ).pipe(z53.union([AgentProviderSchema, z53.null()]));
64422
64787
  function parseOptionalVoiceLlmProvider(value) {
64423
64788
  const parsed = OptionalVoiceLlmProviderSchema.safeParse(value);
64424
64789
  return parsed.success ? parsed.data : null;
@@ -64471,6 +64836,7 @@ function loadConfig(appostleHome, options) {
64471
64836
  const mcpEnabled = options?.cli?.mcpEnabled ?? persisted.daemon?.mcp?.enabled ?? true;
64472
64837
  const mcpInjectIntoAgents = options?.cli?.mcpInjectIntoAgents ?? persisted.daemon?.mcp?.injectIntoAgents ?? false;
64473
64838
  const chromeEnabled = persisted.daemon?.chrome?.enabled ?? true;
64839
+ const playwrightEnabled = persisted.daemon?.playwright?.enabled ?? true;
64474
64840
  const daemonIcon = persisted.daemon?.identity?.icon;
64475
64841
  const relayEnabled = options?.cli?.relayEnabled ?? parseBooleanEnv(env.APPOSTLE_RELAY_ENABLED) ?? persisted.daemon?.relay?.enabled ?? true;
64476
64842
  const relayEndpoint = env.APPOSTLE_RELAY_ENDPOINT ?? persisted.daemon?.relay?.endpoint ?? DEFAULT_RELAY_ENDPOINT;
@@ -64501,9 +64867,10 @@ function loadConfig(appostleHome, options) {
64501
64867
  mcpEnabled,
64502
64868
  mcpInjectIntoAgents,
64503
64869
  chromeEnabled,
64870
+ playwrightEnabled,
64504
64871
  mcpDebug: env.MCP_DEBUG === "1",
64505
64872
  daemonIcon,
64506
- agentStoragePath: path37.join(appostleHome, "agents"),
64873
+ agentStoragePath: path38.join(appostleHome, "agents"),
64507
64874
  staticDir: "public",
64508
64875
  agentClients: {},
64509
64876
  relayEnabled,
@@ -64522,7 +64889,7 @@ function loadConfig(appostleHome, options) {
64522
64889
 
64523
64890
  // ../server/src/server/logger.ts
64524
64891
  import { existsSync as existsSync21, mkdirSync as mkdirSync8, readdirSync as readdirSync3, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
64525
- import path38 from "node:path";
64892
+ import path39 from "node:path";
64526
64893
  import pino from "pino";
64527
64894
  import pretty from "pino-pretty";
64528
64895
  import { createStream as createRotatingFileStream } from "rotating-file-stream";
@@ -64565,14 +64932,14 @@ function parsePositiveInteger(value) {
64565
64932
  return parsed;
64566
64933
  }
64567
64934
  function resolveFilePath(appostleHome, configuredPath) {
64568
- const fallback = path38.join(appostleHome, DEFAULT_DAEMON_LOG_FILENAME);
64935
+ const fallback = path39.join(appostleHome, DEFAULT_DAEMON_LOG_FILENAME);
64569
64936
  if (!configuredPath) {
64570
64937
  return fallback;
64571
64938
  }
64572
- if (path38.isAbsolute(configuredPath)) {
64939
+ if (path39.isAbsolute(configuredPath)) {
64573
64940
  return configuredPath;
64574
64941
  }
64575
- return path38.resolve(appostleHome, configuredPath);
64942
+ return path39.resolve(appostleHome, configuredPath);
64576
64943
  }
64577
64944
  function minLogLevel(levels) {
64578
64945
  let minLevel = levels[0];
@@ -64609,20 +64976,20 @@ function normalizeLoggerConfigInput(config) {
64609
64976
  }
64610
64977
  function rotateOnRestart(filePath, maxFiles) {
64611
64978
  if (!existsSync21(filePath)) return;
64612
- const dir = path38.dirname(filePath);
64613
- const base = path38.basename(filePath);
64979
+ const dir = path39.dirname(filePath);
64980
+ const base = path39.basename(filePath);
64614
64981
  const now = /* @__PURE__ */ new Date();
64615
64982
  const pad = (n) => String(n).padStart(2, "0");
64616
64983
  const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
64617
64984
  try {
64618
- renameSync3(filePath, path38.join(dir, `${ts}-00-${base}`));
64985
+ renameSync3(filePath, path39.join(dir, `${ts}-00-${base}`));
64619
64986
  } catch {
64620
64987
  return;
64621
64988
  }
64622
64989
  const rotatedFiles = readdirSync3(dir).filter((f) => f.endsWith(`-${base}`) && f !== base).sort().reverse();
64623
64990
  for (const file of rotatedFiles.slice(maxFiles)) {
64624
64991
  try {
64625
- unlinkSync2(path38.join(dir, file));
64992
+ unlinkSync2(path39.join(dir, file));
64626
64993
  } catch {
64627
64994
  }
64628
64995
  }
@@ -64671,15 +65038,15 @@ function resolveLogConfig(configInput, options) {
64671
65038
  }
64672
65039
  function createRootLogger(configInput, options) {
64673
65040
  const config = resolveLogConfig(configInput, options);
64674
- mkdirSync8(path38.dirname(config.file.path), { recursive: true });
65041
+ mkdirSync8(path39.dirname(config.file.path), { recursive: true });
64675
65042
  const consoleStream = config.console.format === "pretty" ? pretty({
64676
65043
  colorize: true,
64677
65044
  singleLine: true,
64678
65045
  ignore: "pid,hostname"
64679
65046
  }) : pino.destination({ dest: 1, sync: false });
64680
65047
  rotateOnRestart(config.file.path, config.file.rotate.maxFiles);
64681
- const fileStream = createRotatingFileStream(path38.basename(config.file.path), {
64682
- path: path38.dirname(config.file.path),
65048
+ const fileStream = createRotatingFileStream(path39.basename(config.file.path), {
65049
+ path: path39.dirname(config.file.path),
64683
65050
  size: toRotatingFileStreamSize(config.file.rotate.maxSize),
64684
65051
  maxFiles: config.file.rotate.maxFiles
64685
65052
  });