appostle-installer 0.0.4 → 0.0.5

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
@@ -8,7 +8,7 @@ import { createReadStream, unlinkSync, existsSync as existsSync18 } from "fs";
8
8
  import { stat as stat9 } from "fs/promises";
9
9
  import { randomUUID as randomUUID15 } from "node:crypto";
10
10
  import { hostname as getHostname2 } from "node:os";
11
- import path26 from "node:path";
11
+ import path29 from "node:path";
12
12
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
13
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
14
14
 
@@ -461,18 +461,18 @@ function resolveNodePtyPackageRoot() {
461
461
  return null;
462
462
  }
463
463
  }
464
- function ensureExecutableBit(path30) {
465
- if (!existsSync2(path30)) {
464
+ function ensureExecutableBit(path33) {
465
+ if (!existsSync2(path33)) {
466
466
  return;
467
467
  }
468
- const stat10 = statSync(path30);
468
+ const stat10 = statSync(path33);
469
469
  if (!stat10.isFile()) {
470
470
  return;
471
471
  }
472
472
  if ((stat10.mode & 73) === 73) {
473
473
  return;
474
474
  }
475
- chmodSync(path30, stat10.mode | 73);
475
+ chmodSync(path33, stat10.mode | 73);
476
476
  }
477
477
  function ensureNodePtySpawnHelperExecutableForCurrentPlatform(options = {}) {
478
478
  const platform = options.platform ?? process.platform;
@@ -1090,11 +1090,11 @@ function parseGitRevParsePath(stdout) {
1090
1090
  if (lines.length !== 1) {
1091
1091
  return null;
1092
1092
  }
1093
- const path30 = lines[0]?.trim() ?? "";
1094
- if (!path30 || path30.startsWith("--")) {
1093
+ const path33 = lines[0]?.trim() ?? "";
1094
+ if (!path33 || path33.startsWith("--")) {
1095
1095
  return null;
1096
1096
  }
1097
- return path30;
1097
+ return path33;
1098
1098
  }
1099
1099
  function resolveGitRevParsePath(cwd, stdout) {
1100
1100
  const parsed = parseGitRevParsePath(stdout);
@@ -1867,9 +1867,9 @@ async function deleteAppostleWorktree({
1867
1867
  }
1868
1868
  }
1869
1869
  }
1870
- async function pathExists(path30) {
1870
+ async function pathExists(path33) {
1871
1871
  try {
1872
- await stat(path30);
1872
+ await stat(path33);
1873
1873
  return true;
1874
1874
  } catch (error) {
1875
1875
  if (error.code === "ENOENT") {
@@ -1878,8 +1878,8 @@ async function pathExists(path30) {
1878
1878
  throw error;
1879
1879
  }
1880
1880
  }
1881
- async function removeDirectoryWithRetries(path30) {
1882
- if (!await pathExists(path30)) {
1881
+ async function removeDirectoryWithRetries(path33) {
1882
+ if (!await pathExists(path33)) {
1883
1883
  return;
1884
1884
  }
1885
1885
  const delaysMs = [0, 100, 300, 700, 1500];
@@ -1889,17 +1889,17 @@ async function removeDirectoryWithRetries(path30) {
1889
1889
  await new Promise((resolve14) => setTimeout(resolve14, delay));
1890
1890
  }
1891
1891
  try {
1892
- await rm(path30, { recursive: true, force: true });
1893
- if (!await pathExists(path30)) {
1892
+ await rm(path33, { recursive: true, force: true });
1893
+ if (!await pathExists(path33)) {
1894
1894
  return;
1895
1895
  }
1896
- lastError = new Error(`Directory still present after rm: ${path30}`);
1896
+ lastError = new Error(`Directory still present after rm: ${path33}`);
1897
1897
  } catch (error) {
1898
1898
  lastError = error;
1899
1899
  }
1900
1900
  }
1901
- if (await pathExists(path30)) {
1902
- throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path30}`);
1901
+ if (await pathExists(path33)) {
1902
+ throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path33}`);
1903
1903
  }
1904
1904
  }
1905
1905
  var createWorktree = async ({
@@ -3225,14 +3225,14 @@ var LoopStopResponseSchema = z7.object({
3225
3225
  })
3226
3226
  });
3227
3227
 
3228
- // ../server/src/server/workflow/rpc-schemas.ts
3228
+ // ../server/src/server/quest/rpc-schemas.ts
3229
3229
  import { z as z9 } from "zod";
3230
3230
 
3231
- // ../server/src/server/workflow/types.ts
3231
+ // ../server/src/server/quest/types.ts
3232
3232
  import { z as z8 } from "zod";
3233
- var WorkflowKindSchema = z8.enum(["loop", "ralph"]);
3233
+ var QuestKindSchema = z8.enum(["loop", "ralph", "run", "orchestrate"]);
3234
3234
  var FieldModeSchema = z8.enum(["locked", "inherit", "free"]);
3235
- var WorkflowRulesSchema = z8.object({
3235
+ var QuestRulesSchema = z8.object({
3236
3236
  perCardRole: FieldModeSchema,
3237
3237
  perCardModel: FieldModeSchema,
3238
3238
  perCardPrompt: FieldModeSchema,
@@ -3256,79 +3256,101 @@ var CardWiringSchema = z8.object({
3256
3256
  inputs: CardWiringInputsSchema,
3257
3257
  outputs: CardWiringOutputsSchema
3258
3258
  });
3259
- var WorkflowCardStatusSchema = z8.enum([
3259
+ var QuestCardStatusSchema = z8.enum([
3260
3260
  "queued",
3261
3261
  "running",
3262
3262
  "succeeded",
3263
3263
  "failed",
3264
3264
  "stopped"
3265
3265
  ]);
3266
- var WorkflowCardVerifySchema = z8.object({
3266
+ var QuestCardVerifySchema = z8.object({
3267
3267
  verifierAgentId: z8.string().nullable(),
3268
3268
  passed: z8.boolean().nullable(),
3269
3269
  reason: z8.string().nullable(),
3270
3270
  startedAt: z8.string().nullable(),
3271
3271
  completedAt: z8.string().nullable()
3272
3272
  });
3273
- var WorkflowCardSchema = z8.object({
3273
+ var QuestCardSchema = z8.object({
3274
3274
  /**
3275
3275
  * 1..N for iteration cards. The summarizer card uses 0 as a sentinel —
3276
- * it lives on `WorkflowRecord.summarizerCard`, not in the cards array,
3276
+ * it lives on `QuestRecord.summarizerCard`, not in the cards array,
3277
3277
  * and the UI renders it as "Summary" when it sees index === 0.
3278
3278
  */
3279
3279
  index: z8.number().int().nonnegative(),
3280
- /** Role name resolved against the agents/.scanner. Null = use workflow default. */
3280
+ /** Role name resolved against the agents/.scanner. Null = use quest default. */
3281
3281
  role: z8.string().nullable(),
3282
- /** Model override. Null = use workflow.defaultModel. */
3282
+ /** Model override. Null = use quest.defaultModel. */
3283
3283
  model: z8.string().nullable(),
3284
- /** Prompt override. Null = use workflow.prompt. */
3284
+ /** Prompt override. Null = use quest.prompt. */
3285
3285
  prompt: z8.string().nullable(),
3286
3286
  wiring: CardWiringSchema,
3287
- status: WorkflowCardStatusSchema,
3287
+ status: QuestCardStatusSchema,
3288
3288
  /** Populated when the card runs. Null until the runner spawns the agent. */
3289
3289
  agentId: z8.string().nullable(),
3290
3290
  startedAt: z8.string().nullable(),
3291
3291
  completedAt: z8.string().nullable(),
3292
3292
  /** Ralph-only — null for loop cards. */
3293
- verify: WorkflowCardVerifySchema.nullable()
3293
+ verify: QuestCardVerifySchema.nullable()
3294
3294
  });
3295
- var WorkflowTerminationCountSchema = z8.object({
3295
+ var QuestTerminationCountSchema = z8.object({
3296
3296
  type: z8.literal("count"),
3297
3297
  /** 1..10 in v1. */
3298
3298
  n: z8.number().int().min(1).max(10)
3299
3299
  });
3300
- var WorkflowTerminationVerifySchema = z8.object({
3300
+ var QuestTerminationVerifySchema = z8.object({
3301
3301
  type: z8.literal("verify"),
3302
3302
  /** Verifier agent system prompt. Null means rely on shell checks alone. */
3303
3303
  verifyPrompt: z8.string().nullable(),
3304
3304
  /** Shell commands that must exit 0 for the iteration to pass. */
3305
3305
  verifyChecks: z8.array(z8.string()),
3306
+ /**
3307
+ * Exact completion token that MUST appear in worker output before the quest
3308
+ * may succeed. Mirrors Ralph's completion-promise guardrail.
3309
+ */
3310
+ completionPromise: z8.string().trim().min(1).optional().default("<promise>COMPLETE</promise>"),
3306
3311
  /** Hard cap on iterations regardless of verifier outcome. */
3307
3312
  maxIterations: z8.number().int().min(1)
3308
3313
  });
3309
- var WorkflowTerminationSchema = z8.discriminatedUnion("type", [
3310
- WorkflowTerminationCountSchema,
3311
- WorkflowTerminationVerifySchema
3314
+ var QuestTerminationOrchestrateSchema = z8.object({
3315
+ type: z8.literal("orchestrate")
3316
+ });
3317
+ var QuestTerminationSchema = z8.discriminatedUnion("type", [
3318
+ QuestTerminationCountSchema,
3319
+ QuestTerminationVerifySchema,
3320
+ QuestTerminationOrchestrateSchema
3312
3321
  ]);
3313
- var WorkflowStatusSchema = z8.enum(["running", "succeeded", "failed", "stopped"]);
3314
- var WorkflowRecordSchema = z8.object({
3322
+ var QuestStatusSchema = z8.enum(["running", "succeeded", "failed", "stopped"]);
3323
+ var QuestRecordSchema = z8.object({
3315
3324
  id: z8.string(),
3316
- kind: WorkflowKindSchema,
3317
- rules: WorkflowRulesSchema,
3325
+ kind: QuestKindSchema,
3326
+ rules: QuestRulesSchema,
3318
3327
  cwd: z8.string(),
3319
3328
  /** User's task prompt — fed to every card unless overridden per-card. */
3320
3329
  prompt: z8.string(),
3321
3330
  defaultProvider: AgentProviderSchema,
3322
3331
  defaultModel: z8.string().nullable(),
3323
- termination: WorkflowTerminationSchema,
3332
+ termination: QuestTerminationSchema,
3324
3333
  /**
3325
3334
  * For "loop" kind: pre-rendered N cards from the start.
3326
3335
  * For "ralph" kind: appended as iterations execute.
3327
3336
  */
3328
- cards: z8.array(WorkflowCardSchema),
3337
+ cards: z8.array(QuestCardSchema),
3329
3338
  /** Populated when summarizer is enabled. Runs after all cards complete. */
3330
- summarizerCard: WorkflowCardSchema.nullable(),
3331
- status: WorkflowStatusSchema,
3339
+ summarizerCard: QuestCardSchema.nullable(),
3340
+ /**
3341
+ * Queen-bee coordinator card for "orchestrate" kind. The queen reads the
3342
+ * hivemind doc, decides dispatch waves, and writes JSON decisions back. It
3343
+ * is promotable (same affordance as regular cards). Null for non-orchestrate
3344
+ * kinds. Optional + default so old persisted records remain valid.
3345
+ */
3346
+ queenCard: QuestCardSchema.nullable().optional().default(null),
3347
+ /**
3348
+ * Path to the orchestration's hivemind markdown doc. Null for non-
3349
+ * orchestrate kinds. Optional + default so old persisted records remain
3350
+ * valid.
3351
+ */
3352
+ hivemindPath: z8.string().nullable().optional().default(null),
3353
+ status: QuestStatusSchema,
3332
3354
  createdAt: z8.string(),
3333
3355
  updatedAt: z8.string(),
3334
3356
  startedAt: z8.string().nullable(),
@@ -3336,107 +3358,293 @@ var WorkflowRecordSchema = z8.object({
3336
3358
  stopRequestedAt: z8.string().nullable()
3337
3359
  });
3338
3360
 
3339
- // ../server/src/server/workflow/rpc-schemas.ts
3340
- var WorkflowCardConfigInputSchema = z9.object({
3361
+ // ../server/src/server/quest/rpc-schemas.ts
3362
+ var QuestCardConfigInputSchema = z9.object({
3341
3363
  role: z9.string().nullable().optional(),
3342
3364
  model: z9.string().nullable().optional(),
3343
3365
  prompt: z9.string().nullable().optional()
3344
3366
  });
3345
- var WorkflowRunRequestSchema = z9.object({
3346
- type: z9.literal("workflow/run"),
3367
+ var QuestRunRequestSchema = z9.object({
3368
+ type: z9.literal("quest/run"),
3347
3369
  requestId: z9.string(),
3348
- kind: WorkflowKindSchema,
3370
+ kind: QuestKindSchema,
3349
3371
  cwd: z9.string(),
3350
3372
  prompt: z9.string().trim().min(1),
3351
3373
  defaultProvider: AgentProviderSchema.optional(),
3352
3374
  defaultModel: z9.string().trim().min(1).optional(),
3353
- termination: WorkflowTerminationSchema,
3375
+ termination: QuestTerminationSchema,
3354
3376
  /** Per-card overrides (length must match termination.n for "count"). Optional. */
3355
- cards: z9.array(WorkflowCardConfigInputSchema).optional(),
3377
+ cards: z9.array(QuestCardConfigInputSchema).optional(),
3356
3378
  summarizerEnabled: z9.boolean().optional()
3357
3379
  });
3358
- var WorkflowListRequestSchema = z9.object({
3359
- type: z9.literal("workflow/list"),
3380
+ var QuestListRequestSchema = z9.object({
3381
+ type: z9.literal("quest/list"),
3360
3382
  requestId: z9.string()
3361
3383
  });
3362
- var WorkflowInspectRequestSchema = z9.object({
3363
- type: z9.literal("workflow/inspect"),
3384
+ var QuestInspectRequestSchema = z9.object({
3385
+ type: z9.literal("quest/inspect"),
3364
3386
  requestId: z9.string(),
3365
3387
  id: z9.string().trim().min(1)
3366
3388
  });
3367
- var WorkflowStopRequestSchema = z9.object({
3368
- type: z9.literal("workflow/stop"),
3389
+ var QuestStopRequestSchema = z9.object({
3390
+ type: z9.literal("quest/stop"),
3369
3391
  requestId: z9.string(),
3370
3392
  id: z9.string().trim().min(1)
3371
3393
  });
3372
- var WorkflowListItemSchema = z9.object({
3394
+ var QuestListItemSchema = z9.object({
3373
3395
  id: z9.string(),
3374
- kind: WorkflowKindSchema,
3375
- status: WorkflowStatusSchema,
3396
+ kind: QuestKindSchema,
3397
+ status: QuestStatusSchema,
3376
3398
  cwd: z9.string(),
3377
3399
  prompt: z9.string(),
3378
3400
  createdAt: z9.string(),
3379
3401
  updatedAt: z9.string()
3380
3402
  });
3381
- var WorkflowRunResponseSchema = z9.object({
3382
- type: z9.literal("workflow/run/response"),
3403
+ var QuestRunResponseSchema = z9.object({
3404
+ type: z9.literal("quest/run/response"),
3405
+ payload: z9.object({
3406
+ requestId: z9.string(),
3407
+ quest: QuestRecordSchema.nullable(),
3408
+ error: z9.string().nullable()
3409
+ })
3410
+ });
3411
+ var QuestListResponseSchema = z9.object({
3412
+ type: z9.literal("quest/list/response"),
3413
+ payload: z9.object({
3414
+ requestId: z9.string(),
3415
+ quests: z9.array(QuestListItemSchema),
3416
+ error: z9.string().nullable()
3417
+ })
3418
+ });
3419
+ var QuestInspectResponseSchema = z9.object({
3420
+ type: z9.literal("quest/inspect/response"),
3421
+ payload: z9.object({
3422
+ requestId: z9.string(),
3423
+ quest: QuestRecordSchema.nullable(),
3424
+ error: z9.string().nullable()
3425
+ })
3426
+ });
3427
+ var QuestStopResponseSchema = z9.object({
3428
+ type: z9.literal("quest/stop/response"),
3429
+ payload: z9.object({
3430
+ requestId: z9.string(),
3431
+ quest: QuestRecordSchema.nullable(),
3432
+ error: z9.string().nullable()
3433
+ })
3434
+ });
3435
+ var QuestStreamMessageSchema = z9.object({
3436
+ type: z9.literal("quest/stream"),
3437
+ questId: z9.string(),
3438
+ quest: QuestRecordSchema
3439
+ });
3440
+ var RoleScopeSchema = z9.enum(["global", "project"]);
3441
+ var RoleEntrySchema = z9.object({
3442
+ id: z9.string(),
3443
+ scope: RoleScopeSchema,
3444
+ /** Category subfolder inside .roles/, or null for flat files. */
3445
+ category: z9.string().nullable().optional(),
3446
+ name: z9.string(),
3447
+ description: z9.string(),
3448
+ /** Trigger words from the `trigger-words` frontmatter key. */
3449
+ triggerWords: z9.array(z9.string()).optional().default([]),
3450
+ provider: z9.string(),
3451
+ model: z9.string(),
3452
+ mode: z9.string(),
3453
+ path: z9.string(),
3454
+ modifiedAt: z9.string(),
3455
+ size: z9.number()
3456
+ });
3457
+ var RoleFrontmatterSchema = z9.object({
3458
+ description: z9.string().optional(),
3459
+ triggerWords: z9.array(z9.string()).optional(),
3460
+ allowedTools: z9.array(z9.string()).optional(),
3461
+ provider: z9.string().optional(),
3462
+ model: z9.string().optional(),
3463
+ mode: z9.string().optional()
3464
+ });
3465
+ var RolesListRequestSchema = z9.object({
3466
+ type: z9.literal("roles/list"),
3467
+ requestId: z9.string(),
3468
+ workspaceRoot: z9.string().optional()
3469
+ });
3470
+ var RolesListResponseSchema = z9.object({
3471
+ type: z9.literal("roles/list/response"),
3383
3472
  payload: z9.object({
3384
3473
  requestId: z9.string(),
3385
- workflow: WorkflowRecordSchema.nullable(),
3474
+ roles: z9.array(RoleEntrySchema),
3386
3475
  error: z9.string().nullable()
3387
3476
  })
3388
3477
  });
3389
- var WorkflowListResponseSchema = z9.object({
3390
- type: z9.literal("workflow/list/response"),
3478
+ var RolesListScopeRequestSchema = z9.object({
3479
+ type: z9.literal("roles/list-scope"),
3480
+ requestId: z9.string(),
3481
+ scope: RoleScopeSchema,
3482
+ workspaceRoot: z9.string().optional()
3483
+ });
3484
+ var RolesListScopeResponseSchema = z9.object({
3485
+ type: z9.literal("roles/list-scope/response"),
3391
3486
  payload: z9.object({
3392
3487
  requestId: z9.string(),
3393
- workflows: z9.array(WorkflowListItemSchema),
3488
+ roles: z9.array(RoleEntrySchema),
3394
3489
  error: z9.string().nullable()
3395
3490
  })
3396
3491
  });
3397
- var WorkflowInspectResponseSchema = z9.object({
3398
- type: z9.literal("workflow/inspect/response"),
3492
+ var RoleCreateRequestSchema = z9.object({
3493
+ type: z9.literal("roles/create"),
3494
+ requestId: z9.string(),
3495
+ scope: RoleScopeSchema,
3496
+ name: z9.string(),
3497
+ category: z9.string().optional(),
3498
+ workspaceRoot: z9.string().optional()
3499
+ });
3500
+ var RoleCreateResponseSchema = z9.object({
3501
+ type: z9.literal("roles/create/response"),
3399
3502
  payload: z9.object({
3400
3503
  requestId: z9.string(),
3401
- workflow: WorkflowRecordSchema.nullable(),
3504
+ /** Absolute path of the created .md file. Empty string when error is set. */
3505
+ path: z9.string(),
3402
3506
  error: z9.string().nullable()
3403
3507
  })
3404
3508
  });
3405
- var WorkflowStopResponseSchema = z9.object({
3406
- type: z9.literal("workflow/stop/response"),
3509
+ var RoleWriteFrontmatterRequestSchema = z9.object({
3510
+ type: z9.literal("roles/write-frontmatter"),
3511
+ requestId: z9.string(),
3512
+ /** Absolute path inside an allowlisted role root. */
3513
+ path: z9.string(),
3514
+ workspaceRoot: z9.string().optional(),
3515
+ frontmatter: RoleFrontmatterSchema
3516
+ });
3517
+ var RoleWriteFrontmatterResponseSchema = z9.object({
3518
+ type: z9.literal("roles/write-frontmatter/response"),
3407
3519
  payload: z9.object({
3408
3520
  requestId: z9.string(),
3409
- workflow: WorkflowRecordSchema.nullable(),
3521
+ path: z9.string(),
3410
3522
  error: z9.string().nullable()
3411
3523
  })
3412
3524
  });
3413
- var WorkflowStreamMessageSchema = z9.object({
3414
- type: z9.literal("workflow/stream"),
3415
- workflowId: z9.string(),
3416
- workflow: WorkflowRecordSchema
3525
+ var RoleMoveRequestSchema = z9.object({
3526
+ type: z9.literal("roles/move"),
3527
+ requestId: z9.string(),
3528
+ path: z9.string(),
3529
+ newName: z9.string().optional(),
3530
+ newCategory: z9.string().optional(),
3531
+ workspaceRoot: z9.string().optional()
3417
3532
  });
3418
- var AgentRoleEntrySchema = z9.object({
3533
+ var RoleMoveResponseSchema = z9.object({
3534
+ type: z9.literal("roles/move/response"),
3535
+ payload: z9.object({
3536
+ requestId: z9.string(),
3537
+ path: z9.string(),
3538
+ error: z9.string().nullable()
3539
+ })
3540
+ });
3541
+ var BrandScopeSchema = z9.literal("project");
3542
+ var BrandVariableTypeSchema = z9.enum([
3543
+ "color",
3544
+ "font",
3545
+ "asset",
3546
+ "text",
3547
+ "number",
3548
+ "select"
3549
+ ]);
3550
+ var BrandSectionSchema = z9.enum(["visual", "voice"]);
3551
+ var BrandVariableSchema = z9.object({
3552
+ key: z9.string(),
3553
+ type: BrandVariableTypeSchema,
3554
+ label: z9.string(),
3555
+ value: z9.string(),
3556
+ section: BrandSectionSchema.optional(),
3557
+ options: z9.array(z9.string()).optional(),
3558
+ fileRef: z9.string().optional()
3559
+ });
3560
+ var BrandEntrySchema = z9.object({
3419
3561
  id: z9.string(),
3420
- scope: z9.enum(["global", "project"]),
3562
+ scope: BrandScopeSchema,
3421
3563
  name: z9.string(),
3422
3564
  description: z9.string(),
3423
- provider: z9.string().nullable(),
3424
- model: z9.string().nullable(),
3425
- mode: z9.string().nullable(),
3565
+ tags: z9.array(z9.string()).optional().default([]),
3566
+ variables: z9.array(BrandVariableSchema).optional().default([]),
3426
3567
  path: z9.string(),
3427
3568
  modifiedAt: z9.string(),
3428
3569
  size: z9.number()
3429
3570
  });
3430
- var AgentRolesListRequestSchema = z9.object({
3431
- type: z9.literal("agents/list"),
3571
+ var BrandFrontmatterSchema = z9.object({
3572
+ description: z9.string().optional(),
3573
+ tags: z9.array(z9.string()).optional(),
3574
+ variables: z9.array(BrandVariableSchema).optional()
3575
+ });
3576
+ var BrandsListScopeRequestSchema = z9.object({
3577
+ type: z9.literal("brands/list-scope"),
3578
+ requestId: z9.string(),
3579
+ workspaceRoot: z9.string().optional()
3580
+ });
3581
+ var BrandsListScopeResponseSchema = z9.object({
3582
+ type: z9.literal("brands/list-scope/response"),
3583
+ payload: z9.object({
3584
+ requestId: z9.string(),
3585
+ brands: z9.array(BrandEntrySchema),
3586
+ error: z9.string().nullable()
3587
+ })
3588
+ });
3589
+ var BrandCreateRequestSchema = z9.object({
3590
+ type: z9.literal("brands/create"),
3432
3591
  requestId: z9.string(),
3592
+ name: z9.string(),
3433
3593
  workspaceRoot: z9.string().optional()
3434
3594
  });
3435
- var AgentRolesListResponseSchema = z9.object({
3436
- type: z9.literal("agents/list/response"),
3595
+ var BrandCreateResponseSchema = z9.object({
3596
+ type: z9.literal("brands/create/response"),
3597
+ payload: z9.object({
3598
+ requestId: z9.string(),
3599
+ path: z9.string(),
3600
+ error: z9.string().nullable()
3601
+ })
3602
+ });
3603
+ var BrandAssetCopyRequestSchema = z9.object({
3604
+ type: z9.literal("brands/asset-copy"),
3605
+ requestId: z9.string(),
3606
+ workspaceRoot: z9.string(),
3607
+ sourcePath: z9.string(),
3608
+ targetName: z9.string()
3609
+ });
3610
+ var BrandAssetUploadRequestSchema = z9.object({
3611
+ type: z9.literal("brands/asset-upload"),
3612
+ requestId: z9.string(),
3613
+ workspaceRoot: z9.string(),
3614
+ targetName: z9.string(),
3615
+ sourceName: z9.string().optional(),
3616
+ dataBase64: z9.string()
3617
+ });
3618
+ var BrandAssetUploadResponseSchema = z9.object({
3619
+ type: z9.literal("brands/asset-upload/response"),
3620
+ payload: z9.object({
3621
+ requestId: z9.string(),
3622
+ relativePath: z9.string(),
3623
+ absolutePath: z9.string(),
3624
+ error: z9.string().nullable()
3625
+ })
3626
+ });
3627
+ var BrandAssetCopyResponseSchema = z9.object({
3628
+ type: z9.literal("brands/asset-copy/response"),
3629
+ payload: z9.object({
3630
+ requestId: z9.string(),
3631
+ relativePath: z9.string(),
3632
+ absolutePath: z9.string(),
3633
+ error: z9.string().nullable()
3634
+ })
3635
+ });
3636
+ var BrandWriteFrontmatterRequestSchema = z9.object({
3637
+ type: z9.literal("brands/write-frontmatter"),
3638
+ requestId: z9.string(),
3639
+ path: z9.string(),
3640
+ workspaceRoot: z9.string().optional(),
3641
+ frontmatter: BrandFrontmatterSchema
3642
+ });
3643
+ var BrandWriteFrontmatterResponseSchema = z9.object({
3644
+ type: z9.literal("brands/write-frontmatter/response"),
3437
3645
  payload: z9.object({
3438
3646
  requestId: z9.string(),
3439
- roles: z9.array(AgentRoleEntrySchema),
3647
+ path: z9.string(),
3440
3648
  error: z9.string().nullable()
3441
3649
  })
3442
3650
  });
@@ -3990,6 +4198,7 @@ var UpdateAgentRequestMessageSchema = z10.object({
3990
4198
  agentId: z10.string(),
3991
4199
  name: z10.string().optional(),
3992
4200
  labels: z10.record(z10.string()).optional(),
4201
+ internal: z10.boolean().optional(),
3993
4202
  requestId: z10.string()
3994
4203
  });
3995
4204
  var SetVoiceModeMessageSchema = z10.object({
@@ -4779,6 +4988,25 @@ var FileDeleteRequestSchema = z10.object({
4779
4988
  path: z10.string(),
4780
4989
  requestId: z10.string()
4781
4990
  });
4991
+ var FileExplorerDeleteRequestSchema = z10.object({
4992
+ type: z10.literal("file_explorer_delete_request"),
4993
+ cwd: z10.string(),
4994
+ path: z10.string(),
4995
+ requestId: z10.string()
4996
+ });
4997
+ var FileMkdirRequestSchema = z10.object({
4998
+ type: z10.literal("file_mkdir_request"),
4999
+ cwd: z10.string(),
5000
+ path: z10.string(),
5001
+ requestId: z10.string()
5002
+ });
5003
+ var FileMoveRequestSchema = z10.object({
5004
+ type: z10.literal("file_move_request"),
5005
+ cwd: z10.string(),
5006
+ sourcePath: z10.string(),
5007
+ destinationPath: z10.string(),
5008
+ requestId: z10.string()
5009
+ });
4782
5010
  var ClearAgentAttentionMessageSchema = z10.object({
4783
5011
  type: z10.literal("clear_agent_attention"),
4784
5012
  agentId: z10.union([z10.string(), z10.array(z10.string())]),
@@ -5051,6 +5279,9 @@ var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
5051
5279
  FileExplorerRequestSchema,
5052
5280
  FileWriteRequestSchema,
5053
5281
  FileDeleteRequestSchema,
5282
+ FileExplorerDeleteRequestSchema,
5283
+ FileMkdirRequestSchema,
5284
+ FileMoveRequestSchema,
5054
5285
  ProjectIconRequestSchema,
5055
5286
  FileDownloadTokenRequestSchema,
5056
5287
  ClearAgentAttentionMessageSchema,
@@ -5060,6 +5291,15 @@ var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
5060
5291
  ListSkillsRequestSchema,
5061
5292
  CreateSkillRequestSchema,
5062
5293
  WriteSkillFrontmatterRequestSchema,
5294
+ RolesListScopeRequestSchema,
5295
+ RoleCreateRequestSchema,
5296
+ RoleWriteFrontmatterRequestSchema,
5297
+ RoleMoveRequestSchema,
5298
+ BrandsListScopeRequestSchema,
5299
+ BrandCreateRequestSchema,
5300
+ BrandWriteFrontmatterRequestSchema,
5301
+ BrandAssetCopyRequestSchema,
5302
+ BrandAssetUploadRequestSchema,
5063
5303
  RegisterPushTokenMessageSchema,
5064
5304
  RegisterWebPushSubscriptionMessageSchema,
5065
5305
  UnregisterWebPushSubscriptionMessageSchema,
@@ -5094,11 +5334,11 @@ var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
5094
5334
  LoopInspectRequestSchema,
5095
5335
  LoopLogsRequestSchema,
5096
5336
  LoopStopRequestSchema,
5097
- WorkflowRunRequestSchema,
5098
- WorkflowListRequestSchema,
5099
- WorkflowInspectRequestSchema,
5100
- WorkflowStopRequestSchema,
5101
- AgentRolesListRequestSchema
5337
+ QuestRunRequestSchema,
5338
+ QuestListRequestSchema,
5339
+ QuestInspectRequestSchema,
5340
+ QuestStopRequestSchema,
5341
+ RolesListRequestSchema
5102
5342
  ]);
5103
5343
  var ActivityLogPayloadSchema = z10.object({
5104
5344
  id: z10.string(),
@@ -6224,6 +6464,34 @@ var FileDeleteResponseSchema = z10.object({
6224
6464
  requestId: z10.string()
6225
6465
  })
6226
6466
  });
6467
+ var FileExplorerDeleteResponseSchema = z10.object({
6468
+ type: z10.literal("file_explorer_delete_response"),
6469
+ payload: z10.object({
6470
+ cwd: z10.string(),
6471
+ path: z10.string(),
6472
+ error: z10.string().nullable(),
6473
+ requestId: z10.string()
6474
+ })
6475
+ });
6476
+ var FileMkdirResponseSchema = z10.object({
6477
+ type: z10.literal("file_mkdir_response"),
6478
+ payload: z10.object({
6479
+ cwd: z10.string(),
6480
+ path: z10.string(),
6481
+ error: z10.string().nullable(),
6482
+ requestId: z10.string()
6483
+ })
6484
+ });
6485
+ var FileMoveResponseSchema = z10.object({
6486
+ type: z10.literal("file_move_response"),
6487
+ payload: z10.object({
6488
+ cwd: z10.string(),
6489
+ sourcePath: z10.string(),
6490
+ destinationPath: z10.string(),
6491
+ error: z10.string().nullable(),
6492
+ requestId: z10.string()
6493
+ })
6494
+ });
6227
6495
  var ProjectIconSchema = z10.object({
6228
6496
  data: z10.string(),
6229
6497
  mimeType: z10.string()
@@ -6556,6 +6824,9 @@ var SessionOutboundMessageSchema = z10.discriminatedUnion("type", [
6556
6824
  FileExplorerResponseSchema,
6557
6825
  FileWriteResponseSchema,
6558
6826
  FileDeleteResponseSchema,
6827
+ FileExplorerDeleteResponseSchema,
6828
+ FileMkdirResponseSchema,
6829
+ FileMoveResponseSchema,
6559
6830
  ProjectIconResponseSchema,
6560
6831
  FileDownloadTokenResponseSchema,
6561
6832
  ListProviderModelsResponseMessageSchema,
@@ -6570,6 +6841,10 @@ var SessionOutboundMessageSchema = z10.discriminatedUnion("type", [
6570
6841
  ListSkillsResponseSchema,
6571
6842
  CreateSkillResponseSchema,
6572
6843
  WriteSkillFrontmatterResponseSchema,
6844
+ RolesListScopeResponseSchema,
6845
+ RoleCreateResponseSchema,
6846
+ RoleWriteFrontmatterResponseSchema,
6847
+ RoleMoveResponseSchema,
6573
6848
  ListTerminalsResponseSchema,
6574
6849
  TerminalsChangedSchema,
6575
6850
  CreateTerminalResponseSchema,
@@ -6596,11 +6871,16 @@ var SessionOutboundMessageSchema = z10.discriminatedUnion("type", [
6596
6871
  LoopInspectResponseSchema,
6597
6872
  LoopLogsResponseSchema,
6598
6873
  LoopStopResponseSchema,
6599
- WorkflowRunResponseSchema,
6600
- WorkflowListResponseSchema,
6601
- WorkflowInspectResponseSchema,
6602
- WorkflowStopResponseSchema,
6603
- AgentRolesListResponseSchema,
6874
+ QuestRunResponseSchema,
6875
+ QuestListResponseSchema,
6876
+ QuestInspectResponseSchema,
6877
+ QuestStopResponseSchema,
6878
+ RolesListResponseSchema,
6879
+ BrandsListScopeResponseSchema,
6880
+ BrandCreateResponseSchema,
6881
+ BrandWriteFrontmatterResponseSchema,
6882
+ BrandAssetCopyResponseSchema,
6883
+ BrandAssetUploadResponseSchema,
6604
6884
  GetVapidPublicKeyResponseSchema,
6605
6885
  LinkAccountResponseMessageSchema
6606
6886
  ]);
@@ -8655,6 +8935,12 @@ async function resolveEditorLaunch(input) {
8655
8935
  if (!executable) {
8656
8936
  throw new Error(`Editor target unavailable: ${target.label}`);
8657
8937
  }
8938
+ if (input.editorId === "finder") {
8939
+ return {
8940
+ command: executable,
8941
+ args: ["-R", input.path]
8942
+ };
8943
+ }
8658
8944
  return {
8659
8945
  command: executable,
8660
8946
  args: [input.path]
@@ -9086,14 +9372,14 @@ var DictationStreamManager = class {
9086
9372
  PCM_CHANNELS,
9087
9373
  PCM_BITS_PER_SAMPLE
9088
9374
  );
9089
- const path30 = await maybePersistDictationDebugAudio(
9375
+ const path33 = await maybePersistDictationDebugAudio(
9090
9376
  wavBuffer,
9091
9377
  { sessionId: state.sessionId, dictationId: state.dictationId, format: "audio/wav" },
9092
9378
  this.logger,
9093
9379
  state.debugChunkWriter?.folder
9094
9380
  );
9095
- state.debugRecordingPath = path30;
9096
- return path30;
9381
+ state.debugRecordingPath = path33;
9382
+ return path33;
9097
9383
  }
9098
9384
  failDictationStream(dictationId, error, retryable) {
9099
9385
  this.emit({
@@ -10559,14 +10845,14 @@ function parseDiff(diffText) {
10559
10845
  const firstLine = lines[0];
10560
10846
  const isNew = section.includes("new file mode") || section.includes("--- /dev/null");
10561
10847
  const isDeleted = section.includes("deleted file mode") || section.includes("+++ /dev/null");
10562
- let path30 = "unknown";
10848
+ let path33 = "unknown";
10563
10849
  const pathMatch = firstLine.match(/a\/(.*?) b\//);
10564
10850
  if (pathMatch) {
10565
- path30 = pathMatch[1];
10851
+ path33 = pathMatch[1];
10566
10852
  } else {
10567
10853
  const newFileMatch = firstLine.match(/b\/(.+)$/);
10568
10854
  if (newFileMatch) {
10569
- path30 = newFileMatch[1];
10855
+ path33 = newFileMatch[1];
10570
10856
  }
10571
10857
  }
10572
10858
  const hunks = [];
@@ -10610,7 +10896,7 @@ function parseDiff(diffText) {
10610
10896
  if (currentHunk) {
10611
10897
  hunks.push(currentHunk);
10612
10898
  }
10613
- files.push({ path: path30, isNew, isDeleted, additions, deletions, hunks });
10899
+ files.push({ path: path33, isNew, isDeleted, additions, deletions, hunks });
10614
10900
  }
10615
10901
  return files;
10616
10902
  }
@@ -10666,9 +10952,9 @@ function buildTokenLookup(lineMap, highlighted) {
10666
10952
  }
10667
10953
  return lookup;
10668
10954
  }
10669
- function buildFullFileTokenLookup(fileContent, path30) {
10955
+ function buildFullFileTokenLookup(fileContent, path33) {
10670
10956
  const lookup = /* @__PURE__ */ new Map();
10671
- const highlighted = highlightCode(fileContent, path30);
10957
+ const highlighted = highlightCode(fileContent, path33);
10672
10958
  for (let i = 0; i < highlighted.length; i++) {
10673
10959
  lookup.set(i + 1, highlighted[i]);
10674
10960
  }
@@ -12373,11 +12659,11 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
12373
12659
  }
12374
12660
  continue;
12375
12661
  }
12376
- const path30 = tabParts[1];
12377
- if (!path30) continue;
12662
+ const path33 = tabParts[1];
12663
+ if (!path33) continue;
12378
12664
  const code = rawStatus[0];
12379
12665
  changes.push({
12380
- path: path30,
12666
+ path: path33,
12381
12667
  status: rawStatus,
12382
12668
  isNew: code === "A",
12383
12669
  isDeleted: code === "D"
@@ -12412,9 +12698,9 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
12412
12698
  }
12413
12699
  return Array.from(byPath.values());
12414
12700
  }
12415
- async function readGitFileContentAtRef(cwd, ref, path30) {
12701
+ async function readGitFileContentAtRef(cwd, ref, path33) {
12416
12702
  try {
12417
- const { stdout } = await runGitCommand(["show", `${ref}:${path30}`], {
12703
+ const { stdout } = await runGitCommand(["show", `${ref}:${path33}`], {
12418
12704
  cwd,
12419
12705
  env: READ_ONLY_GIT_ENV2
12420
12706
  });
@@ -12475,21 +12761,21 @@ async function getTrackedNumstatByPath(cwd, ref, ignoreWhitespace = false) {
12475
12761
  const additionsField = parts[0] ?? "";
12476
12762
  const deletionsField = parts[1] ?? "";
12477
12763
  const rawPath = parts.slice(2).join(" ");
12478
- const path30 = normalizeNumstatPath(rawPath);
12479
- if (!path30) {
12764
+ const path33 = normalizeNumstatPath(rawPath);
12765
+ if (!path33) {
12480
12766
  continue;
12481
12767
  }
12482
12768
  if (additionsField === "-" || deletionsField === "-") {
12483
- stats.set(path30, { additions: 0, deletions: 0, isBinary: true });
12769
+ stats.set(path33, { additions: 0, deletions: 0, isBinary: true });
12484
12770
  continue;
12485
12771
  }
12486
12772
  const additions = Number.parseInt(additionsField, 10);
12487
12773
  const deletions = Number.parseInt(deletionsField, 10);
12488
12774
  if (Number.isNaN(additions) || Number.isNaN(deletions)) {
12489
- stats.set(path30, null);
12775
+ stats.set(path33, null);
12490
12776
  continue;
12491
12777
  }
12492
- stats.set(path30, { additions, deletions, isBinary: false });
12778
+ stats.set(path33, { additions, deletions, isBinary: false });
12493
12779
  }
12494
12780
  return stats;
12495
12781
  }
@@ -13181,10 +13467,10 @@ async function listUncommittedFiles(cwd) {
13181
13467
  if (dest) results.push({ path: dest, changeType: code });
13182
13468
  continue;
13183
13469
  }
13184
- const path30 = parts[1];
13185
- if (!path30) continue;
13470
+ const path33 = parts[1];
13471
+ if (!path33) continue;
13186
13472
  if (code === "A" || code === "M" || code === "D") {
13187
- results.push({ path: path30, changeType: code });
13473
+ results.push({ path: path33, changeType: code });
13188
13474
  }
13189
13475
  }
13190
13476
  } catch {
@@ -16528,6 +16814,14 @@ var CLAUDE_MODELS = [
16528
16814
  isDefault: true,
16529
16815
  thinkingOptions: [...CLAUDE_THINKING_OPTIONS]
16530
16816
  },
16817
+ {
16818
+ provider: "claude",
16819
+ id: "claude-sonnet-4-6[1m]",
16820
+ label: "Sonnet 4.6 1M",
16821
+ description: "Sonnet 4.6 with 1M context window",
16822
+ thinkingOptions: [...CLAUDE_THINKING_OPTIONS],
16823
+ betas: ["context-1m-2025-08-07"]
16824
+ },
16531
16825
  {
16532
16826
  provider: "claude",
16533
16827
  id: "claude-sonnet-4-6",
@@ -16545,6 +16839,9 @@ var CLAUDE_MODELS = [
16545
16839
  function getClaudeModels() {
16546
16840
  return CLAUDE_MODELS.map((model) => ({ ...model }));
16547
16841
  }
16842
+ function getClaudeModelBetas(modelId) {
16843
+ return CLAUDE_MODELS.find((m) => m.id === modelId)?.betas;
16844
+ }
16548
16845
  function normalizeClaudeRuntimeModelId(value) {
16549
16846
  const trimmed = typeof value === "string" ? value.trim() : "";
16550
16847
  if (!trimmed) {
@@ -19127,11 +19424,18 @@ var ClaudeAgentSession = class {
19127
19424
  }
19128
19425
  if (this.config.model) {
19129
19426
  base.model = this.config.model;
19427
+ const modelBetas = getClaudeModelBetas(this.config.model);
19428
+ if (modelBetas?.length) {
19429
+ base.betas = [...base.betas ?? [], ...modelBetas];
19430
+ }
19130
19431
  }
19131
19432
  this.lastOptionsModel = base.model ?? null;
19132
19433
  if (this.claudeSessionId) {
19133
19434
  base.resume = this.claudeSessionId;
19134
19435
  }
19436
+ if (this.config.allowedTools?.length) {
19437
+ base.allowedTools = [...base.allowedTools ?? [], ...this.config.allowedTools];
19438
+ }
19135
19439
  if (this.runtimeSettings?.disallowedTools?.length) {
19136
19440
  base.disallowedTools = [
19137
19441
  ...base.disallowedTools ?? [],
@@ -21362,14 +21666,14 @@ function codexApplyPatchToUnifiedDiff(text) {
21362
21666
  for (const line of lines) {
21363
21667
  const directive = parseCodexApplyPatchDirective(line);
21364
21668
  if (directive) {
21365
- const path30 = normalizeDiffHeaderPath(directive.path);
21366
- if (path30.length > 0) {
21669
+ const path33 = normalizeDiffHeaderPath(directive.path);
21670
+ if (path33.length > 0) {
21367
21671
  if (output.length > 0 && output[output.length - 1] !== "") {
21368
21672
  output.push("");
21369
21673
  }
21370
- const left = directive.kind === "add" ? "/dev/null" : `a/${path30}`;
21371
- const right = directive.kind === "delete" ? "/dev/null" : `b/${path30}`;
21372
- output.push(`diff --git a/${path30} b/${path30}`);
21674
+ const left = directive.kind === "add" ? "/dev/null" : `a/${path33}`;
21675
+ const right = directive.kind === "delete" ? "/dev/null" : `b/${path33}`;
21676
+ output.push(`diff --git a/${path33} b/${path33}`);
21373
21677
  output.push(`--- ${left}`);
21374
21678
  output.push(`+++ ${right}`);
21375
21679
  sawDiffContent = true;
@@ -21439,9 +21743,9 @@ function asEditTextFields(text) {
21439
21743
  function normalizeRolloutEditInput(input) {
21440
21744
  if (typeof input === "string") {
21441
21745
  const textFields2 = asEditTextFields(input);
21442
- const path30 = extractPatchPrimaryFilePath(input);
21746
+ const path33 = extractPatchPrimaryFilePath(input);
21443
21747
  return {
21444
- ...path30 ? { path: path30 } : {},
21748
+ ...path33 ? { path: path33 } : {},
21445
21749
  ...textFields2.unifiedDiff ? { patch: textFields2.unifiedDiff } : {},
21446
21750
  ...textFields2.newString ? { content: textFields2.newString } : {}
21447
21751
  };
@@ -21589,12 +21893,12 @@ function parseFileChangeDiff(entry) {
21589
21893
  ]);
21590
21894
  }
21591
21895
  function toFileChangeEntry(entry, options, fallbackPath) {
21592
- const path30 = parseFileChangePath(entry, options, fallbackPath);
21593
- if (!path30) {
21896
+ const path33 = parseFileChangePath(entry, options, fallbackPath);
21897
+ if (!path33) {
21594
21898
  return null;
21595
21899
  }
21596
21900
  return {
21597
- path: path30,
21901
+ path: path33,
21598
21902
  kind: parseFileChangeKind(entry),
21599
21903
  diff: parseFileChangeDiff(entry)
21600
21904
  };
@@ -21616,12 +21920,12 @@ function parseFileChangeEntries(changes, options) {
21616
21920
  if (singleEntry) {
21617
21921
  return [singleEntry];
21618
21922
  }
21619
- return Object.entries(changes).map(([path30, value]) => {
21923
+ return Object.entries(changes).map(([path33, value]) => {
21620
21924
  if (isRecord2(value)) {
21621
- return toFileChangeEntry(value, options, path30);
21925
+ return toFileChangeEntry(value, options, path33);
21622
21926
  }
21623
21927
  if (typeof value === "string") {
21624
- const normalizedPath = normalizeCodexFilePath(path30.trim(), options?.cwd);
21928
+ const normalizedPath = normalizeCodexFilePath(path33.trim(), options?.cwd);
21625
21929
  if (!normalizedPath) {
21626
21930
  return null;
21627
21931
  }
@@ -22366,16 +22670,16 @@ function isObjectSchemaNode(schema) {
22366
22670
  const type = schema.type;
22367
22671
  return isSchemaRecord(schema.properties) || type === "object" || Array.isArray(type) && type.includes("object");
22368
22672
  }
22369
- function normalizeCodexOutputSchemaNode(schema, path30) {
22673
+ function normalizeCodexOutputSchemaNode(schema, path33) {
22370
22674
  if (Array.isArray(schema)) {
22371
- return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path30}[${index}]`));
22675
+ return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path33}[${index}]`));
22372
22676
  }
22373
22677
  if (!isSchemaRecord(schema)) {
22374
22678
  return schema;
22375
22679
  }
22376
22680
  const normalized = {};
22377
22681
  for (const [key, value] of Object.entries(schema)) {
22378
- normalized[key] = normalizeCodexOutputSchemaNode(value, `${path30}.${key}`);
22682
+ normalized[key] = normalizeCodexOutputSchemaNode(value, `${path33}.${key}`);
22379
22683
  }
22380
22684
  if (!isObjectSchemaNode(normalized)) {
22381
22685
  return normalized;
@@ -22384,7 +22688,7 @@ function normalizeCodexOutputSchemaNode(schema, path30) {
22384
22688
  normalized.additionalProperties = false;
22385
22689
  } else if (normalized.additionalProperties !== false) {
22386
22690
  throw new Error(
22387
- `Codex structured outputs require ${path30} to set additionalProperties to false for object schemas.`
22691
+ `Codex structured outputs require ${path33} to set additionalProperties to false for object schemas.`
22388
22692
  );
22389
22693
  }
22390
22694
  const properties = isSchemaRecord(normalized.properties) ? normalized.properties : null;
@@ -23146,8 +23450,8 @@ function parseCodexPatchChanges(changes) {
23146
23450
  }
23147
23451
  ];
23148
23452
  }
23149
- return Object.entries(recordChanges).map(([path30, value]) => {
23150
- const normalizedPath = path30.trim();
23453
+ return Object.entries(recordChanges).map(([path33, value]) => {
23454
+ const normalizedPath = path33.trim();
23151
23455
  if (!normalizedPath) {
23152
23456
  return null;
23153
23457
  }
@@ -30908,8 +31212,8 @@ function buildZodValidator(schema, schemaName) {
30908
31212
  return { ok: true, value: result.data };
30909
31213
  }
30910
31214
  const errors = result.error.issues.map((issue) => {
30911
- const path30 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
30912
- return `${path30}: ${issue.message}`;
31215
+ const path33 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
31216
+ return `${path33}: ${issue.message}`;
30913
31217
  });
30914
31218
  return { ok: false, errors };
30915
31219
  }
@@ -30927,9 +31231,9 @@ function buildJsonSchemaValidator(schema) {
30927
31231
  return { ok: true, value };
30928
31232
  }
30929
31233
  const errors = (validate.errors ?? []).map((error) => {
30930
- const path30 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
31234
+ const path33 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
30931
31235
  const message = error.message ?? "is invalid";
30932
- return `${path30}: ${message}`;
31236
+ return `${path33}: ${message}`;
30933
31237
  });
30934
31238
  return { ok: false, errors };
30935
31239
  }
@@ -31968,6 +32272,12 @@ var IMAGE_MIME_TYPES = {
31968
32272
  ".webp": "image/webp",
31969
32273
  ".svg": "image/svg+xml"
31970
32274
  };
32275
+ var FONT_MIME_TYPES = {
32276
+ ".ttf": "font/ttf",
32277
+ ".otf": "font/otf",
32278
+ ".woff": "font/woff",
32279
+ ".woff2": "font/woff2"
32280
+ };
31971
32281
  async function listDirectoryEntries({
31972
32282
  root,
31973
32283
  relativePath = "."
@@ -32078,6 +32388,23 @@ async function deleteFile({ root, relativePath }) {
32078
32388
  }
32079
32389
  await fs6.unlink(filePath);
32080
32390
  }
32391
+ async function deleteEntry({ root, relativePath }) {
32392
+ const entryPath = await resolveScopedPath({ root, relativePath });
32393
+ await fs6.rm(entryPath, { recursive: true, force: false });
32394
+ }
32395
+ async function createDirectory({ root, relativePath }) {
32396
+ const dirPath = await resolveScopedPath({ root, relativePath });
32397
+ await fs6.mkdir(dirPath, { recursive: true });
32398
+ }
32399
+ async function moveEntry({
32400
+ root,
32401
+ sourcePath,
32402
+ destinationPath
32403
+ }) {
32404
+ const src = await resolveScopedPath({ root, relativePath: sourcePath });
32405
+ const dest = await resolveScopedPath({ root, relativePath: destinationPath });
32406
+ await fs6.rename(src, dest);
32407
+ }
32081
32408
  async function getDownloadableFileInfo({ root, relativePath }) {
32082
32409
  const filePath = await resolveScopedPath({ root, relativePath });
32083
32410
  const stats = await fs6.stat(filePath);
@@ -32087,7 +32414,9 @@ async function getDownloadableFileInfo({ root, relativePath }) {
32087
32414
  const ext = path11.extname(filePath).toLowerCase();
32088
32415
  let mimeType = "application/octet-stream";
32089
32416
  if (ext in IMAGE_MIME_TYPES) {
32090
- mimeType = IMAGE_MIME_TYPES[ext];
32417
+ mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
32418
+ } else if (ext in FONT_MIME_TYPES) {
32419
+ mimeType = FONT_MIME_TYPES[ext] ?? mimeType;
32091
32420
  } else {
32092
32421
  const handle = await fs6.open(filePath, "r");
32093
32422
  const sample = Buffer.alloc(8192);
@@ -32508,15 +32837,15 @@ async function getProjectIcon(projectDir) {
32508
32837
 
32509
32838
  // ../server/src/utils/path.ts
32510
32839
  import os5 from "os";
32511
- function expandTilde(path30) {
32512
- if (path30.startsWith("~/")) {
32840
+ function expandTilde(path33) {
32841
+ if (path33.startsWith("~/")) {
32513
32842
  const homeDir3 = process.env.HOME || os5.homedir();
32514
- return path30.replace("~", homeDir3);
32843
+ return path33.replace("~", homeDir3);
32515
32844
  }
32516
- if (path30 === "~") {
32845
+ if (path33 === "~") {
32517
32846
  return process.env.HOME || os5.homedir();
32518
32847
  }
32519
- return path30;
32848
+ return path33;
32520
32849
  }
32521
32850
 
32522
32851
  // ../server/src/server/skills/scanner.ts
@@ -34023,7 +34352,7 @@ function buildChatMentionNotification(input) {
34023
34352
  ].join("\n");
34024
34353
  }
34025
34354
 
34026
- // ../server/src/server/agents/scanner.ts
34355
+ // ../server/src/server/roles/scanner.ts
34027
34356
  import fs9 from "node:fs/promises";
34028
34357
  import os7 from "node:os";
34029
34358
  import path16 from "node:path";
@@ -34036,11 +34365,41 @@ function resolveScopeDir2(scope, workspaceRoot) {
34036
34365
  if (!workspaceRoot) {
34037
34366
  throw new Error('workspaceRoot is required for scope "project"');
34038
34367
  }
34039
- return path16.join(workspaceRoot, ".agents");
34368
+ return path16.join(workspaceRoot, ".roles");
34369
+ }
34370
+ return path16.join(homeDir2(), ".appostle", ".roles");
34371
+ }
34372
+ function allowedRoots2(workspaceRoot) {
34373
+ const roots = [path16.join(homeDir2(), ".appostle", ".roles")];
34374
+ if (workspaceRoot) {
34375
+ roots.push(path16.join(workspaceRoot, ".roles"));
34040
34376
  }
34041
- return path16.join(homeDir2(), ".appostle", ".agents");
34377
+ return roots.map((r) => path16.resolve(r));
34378
+ }
34379
+ function isInsideAllowedRoot2(absPath, workspaceRoot) {
34380
+ const resolved = path16.resolve(absPath);
34381
+ for (const root of allowedRoots2(workspaceRoot)) {
34382
+ const rel = path16.relative(root, resolved);
34383
+ if (rel === "" || !rel.startsWith("..") && !path16.isAbsolute(rel)) {
34384
+ return true;
34385
+ }
34386
+ }
34387
+ return false;
34042
34388
  }
34043
34389
  var FRONTMATTER_RE2 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
34390
+ function parseRoleFile(text) {
34391
+ const match = FRONTMATTER_RE2.exec(text);
34392
+ if (!match) {
34393
+ return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
34394
+ }
34395
+ const yamlBlock = match[1] ?? "";
34396
+ const body = match[2] ?? "";
34397
+ return {
34398
+ rawFrontmatterLines: yamlBlock.split(/\r?\n/),
34399
+ body,
34400
+ hadFrontmatter: true
34401
+ };
34402
+ }
34044
34403
  function unquote(value) {
34045
34404
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
34046
34405
  return value.slice(1, -1);
@@ -34048,36 +34407,165 @@ function unquote(value) {
34048
34407
  return value;
34049
34408
  }
34050
34409
  function parseFrontmatter(text) {
34051
- const match = FRONTMATTER_RE2.exec(text);
34052
34410
  const empty = {
34053
34411
  description: "",
34054
- provider: null,
34055
- model: null,
34056
- mode: null
34057
- };
34058
- if (!match) {
34059
- return empty;
34060
- }
34061
- const lines = (match[1] ?? "").split(/\r?\n/);
34412
+ triggerWords: [],
34413
+ allowedTools: [],
34414
+ provider: "",
34415
+ model: "",
34416
+ mode: ""
34417
+ };
34418
+ const { rawFrontmatterLines: lines, hadFrontmatter } = parseRoleFile(text);
34419
+ if (!hadFrontmatter) return empty;
34062
34420
  const out = { ...empty };
34063
- for (const line of lines) {
34421
+ let i = 0;
34422
+ while (i < lines.length) {
34423
+ const line = lines[i] ?? "";
34064
34424
  const m = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
34065
- if (!m) continue;
34425
+ if (!m) {
34426
+ i++;
34427
+ continue;
34428
+ }
34066
34429
  const key = m[1];
34067
34430
  const value = unquote((m[2] ?? "").trim());
34068
34431
  if (key === "description") {
34069
34432
  out.description = value;
34070
34433
  } else if (key === "provider") {
34071
- out.provider = value || null;
34434
+ out.provider = value;
34072
34435
  } else if (key === "model") {
34073
- out.model = value || null;
34436
+ out.model = value;
34074
34437
  } else if (key === "mode") {
34075
- out.mode = value || null;
34438
+ out.mode = value;
34439
+ } else if (key === "trigger-words" || key === "allowed-tools") {
34440
+ const target = key === "trigger-words" ? "triggerWords" : "allowedTools";
34441
+ if (value) {
34442
+ out[target] = value.replace(/^\[|\]$/g, "").split(",").map((s) => unquote(s.trim())).filter((s) => s.length > 0);
34443
+ } else {
34444
+ const items = [];
34445
+ let j = i + 1;
34446
+ while (j < lines.length) {
34447
+ const next = lines[j] ?? "";
34448
+ const itemMatch = /^\s+-\s+(.+)$/.exec(next);
34449
+ if (!itemMatch) break;
34450
+ items.push(unquote(itemMatch[1].trim()));
34451
+ j++;
34452
+ }
34453
+ out[target] = items;
34454
+ i = j;
34455
+ continue;
34456
+ }
34076
34457
  }
34458
+ i++;
34077
34459
  }
34078
34460
  return out;
34079
34461
  }
34080
- async function readRolesFromDir(scope, dir) {
34462
+ var OWNED_KEYS = [
34463
+ "name",
34464
+ "category",
34465
+ "description",
34466
+ "trigger-words",
34467
+ "allowed-tools",
34468
+ "provider",
34469
+ "model",
34470
+ "mode"
34471
+ ];
34472
+ function findOwnedSpans2(lines) {
34473
+ const spans = [];
34474
+ let i = 0;
34475
+ while (i < lines.length) {
34476
+ const line = lines[i] ?? "";
34477
+ const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
34478
+ if (!keyMatch) {
34479
+ i++;
34480
+ continue;
34481
+ }
34482
+ const key = keyMatch[1];
34483
+ const value = keyMatch[2];
34484
+ if (!OWNED_KEYS.includes(key)) {
34485
+ i++;
34486
+ continue;
34487
+ }
34488
+ let end = i + 1;
34489
+ if (value === "") {
34490
+ while (end < lines.length) {
34491
+ const next = lines[end] ?? "";
34492
+ if (/^\s+-\s+/.test(next) || /^\s*$/.test(next)) {
34493
+ end++;
34494
+ } else {
34495
+ break;
34496
+ }
34497
+ }
34498
+ }
34499
+ spans.push({ key, startLine: i, endLine: end });
34500
+ i = end;
34501
+ }
34502
+ return spans;
34503
+ }
34504
+ function emitFrontmatterValue2(key, value) {
34505
+ if (key === "trigger-words" || key === "allowed-tools") {
34506
+ if (!Array.isArray(value) || value.length === 0) return [];
34507
+ return [`${key}:`, ...value.map((v) => ` - ${v}`)];
34508
+ }
34509
+ const text = typeof value === "string" ? value : "";
34510
+ if (text.length === 0) return [];
34511
+ const needsQuote = /[:#\n]/.test(text) || text.startsWith(" ") || text.endsWith(" ");
34512
+ const formatted = needsQuote ? `"${text.replace(/"/g, '\\"')}"` : text;
34513
+ return [`${key}: ${formatted}`];
34514
+ }
34515
+ function rewriteFrontmatter2(originalLines, next) {
34516
+ const spans = findOwnedSpans2(originalLines);
34517
+ const ownedKeys = new Set(spans.map((s) => s.key));
34518
+ const replacements = /* @__PURE__ */ new Map();
34519
+ if (next.name !== void 0) {
34520
+ replacements.set("name", emitFrontmatterValue2("name", next.name));
34521
+ }
34522
+ if (next.category !== void 0) {
34523
+ replacements.set("category", emitFrontmatterValue2("category", next.category));
34524
+ }
34525
+ if (next.description !== void 0) {
34526
+ replacements.set("description", emitFrontmatterValue2("description", next.description));
34527
+ }
34528
+ if (next.triggerWords !== void 0) {
34529
+ replacements.set("trigger-words", emitFrontmatterValue2("trigger-words", next.triggerWords));
34530
+ }
34531
+ if (next.allowedTools !== void 0) {
34532
+ replacements.set("allowed-tools", emitFrontmatterValue2("allowed-tools", next.allowedTools));
34533
+ }
34534
+ if (next.provider !== void 0) {
34535
+ replacements.set("provider", emitFrontmatterValue2("provider", next.provider));
34536
+ }
34537
+ if (next.model !== void 0) {
34538
+ replacements.set("model", emitFrontmatterValue2("model", next.model));
34539
+ }
34540
+ if (next.mode !== void 0) {
34541
+ replacements.set("mode", emitFrontmatterValue2("mode", next.mode));
34542
+ }
34543
+ const out = [];
34544
+ let i = 0;
34545
+ while (i < originalLines.length) {
34546
+ const span = spans.find((s) => s.startLine === i);
34547
+ if (span) {
34548
+ if (replacements.has(span.key)) {
34549
+ out.push(...replacements.get(span.key) ?? []);
34550
+ } else {
34551
+ for (let j = span.startLine; j < span.endLine; j++) {
34552
+ out.push(originalLines[j] ?? "");
34553
+ }
34554
+ }
34555
+ i = span.endLine;
34556
+ continue;
34557
+ }
34558
+ out.push(originalLines[i] ?? "");
34559
+ i++;
34560
+ }
34561
+ for (const key of OWNED_KEYS) {
34562
+ if (replacements.has(key) && !ownedKeys.has(key)) {
34563
+ out.push(...replacements.get(key) ?? []);
34564
+ }
34565
+ }
34566
+ return out;
34567
+ }
34568
+ async function readRolesFromDir(scope, dir, category) {
34081
34569
  let entries;
34082
34570
  try {
34083
34571
  entries = await fs9.readdir(dir, { withFileTypes: true });
@@ -34103,13 +34591,23 @@ async function readRolesFromDir(scope, dir) {
34103
34591
  const text = await fs9.readFile(fullPath, "utf8");
34104
34592
  parsed = parseFrontmatter(text);
34105
34593
  } catch {
34106
- parsed = { description: "", provider: null, model: null, mode: null };
34594
+ parsed = {
34595
+ description: "",
34596
+ triggerWords: [],
34597
+ allowedTools: [],
34598
+ provider: "",
34599
+ model: "",
34600
+ mode: ""
34601
+ };
34107
34602
  }
34603
+ const id = category ? `${scope}:${category}/${name}` : `${scope}:${name}`;
34108
34604
  results.push({
34109
- id: `${scope}:${name}`,
34605
+ id,
34110
34606
  scope,
34607
+ category,
34111
34608
  name,
34112
34609
  description: parsed.description,
34610
+ triggerWords: parsed.triggerWords,
34113
34611
  provider: parsed.provider,
34114
34612
  model: parsed.model,
34115
34613
  mode: parsed.mode,
@@ -34118,16 +34616,635 @@ async function readRolesFromDir(scope, dir) {
34118
34616
  size: stat10.size
34119
34617
  });
34120
34618
  }
34619
+ results.sort((a, b) => a.name.localeCompare(b.name));
34121
34620
  return results;
34122
34621
  }
34123
- async function listAgentRoles(args) {
34124
- const project = args.workspaceRoot ? await readRolesFromDir("project", resolveScopeDir2("project", args.workspaceRoot)) : [];
34125
- const global = await readRolesFromDir("global", resolveScopeDir2("global"));
34126
- const projectNames = new Set(project.map((r) => r.name));
34127
- const merged = [...project, ...global.filter((r) => !projectNames.has(r.name))];
34128
- merged.sort((a, b) => a.name.localeCompare(b.name));
34622
+ async function readRolesFromScopeDir(scope, scopeDir) {
34623
+ let topEntries;
34624
+ try {
34625
+ topEntries = await fs9.readdir(scopeDir, { withFileTypes: true });
34626
+ } catch {
34627
+ return [];
34628
+ }
34629
+ const flat = await readRolesFromDir(scope, scopeDir, null);
34630
+ const categoryResults = await Promise.all(
34631
+ topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path16.join(scopeDir, e.name), e.name))
34632
+ );
34633
+ const all = [...flat, ...categoryResults.flat()];
34634
+ all.sort((a, b) => {
34635
+ const catA = a.category ?? "";
34636
+ const catB = b.category ?? "";
34637
+ if (catA !== catB) return catA.localeCompare(catB);
34638
+ return a.name.localeCompare(b.name);
34639
+ });
34640
+ return all;
34641
+ }
34642
+ async function listRoles(args) {
34643
+ if (args.scope === "project") {
34644
+ return readRolesFromScopeDir("project", resolveScopeDir2("project", args.workspaceRoot));
34645
+ }
34646
+ if (args.scope === "global") {
34647
+ return readRolesFromScopeDir("global", resolveScopeDir2("global"));
34648
+ }
34649
+ const project = args.workspaceRoot ? await readRolesFromScopeDir("project", resolveScopeDir2("project", args.workspaceRoot)) : [];
34650
+ const global = await readRolesFromScopeDir("global", resolveScopeDir2("global"));
34651
+ const projectIds = new Set(project.map((r) => r.id));
34652
+ const merged = [...project, ...global.filter((r) => !projectIds.has(r.id))];
34653
+ merged.sort((a, b) => {
34654
+ const catA = a.category ?? "";
34655
+ const catB = b.category ?? "";
34656
+ if (catA !== catB) return catA.localeCompare(catB);
34657
+ return a.name.localeCompare(b.name);
34658
+ });
34129
34659
  return merged;
34130
34660
  }
34661
+ async function resolveRole(args) {
34662
+ const roles = await listRoles({ workspaceRoot: args.workspaceRoot });
34663
+ const match = roles.find((r) => r.name === args.roleName);
34664
+ if (!match) return null;
34665
+ try {
34666
+ const text = await fs9.readFile(match.path, "utf8");
34667
+ const parsed = parseRoleFile(text);
34668
+ const fm = parseFrontmatter(text);
34669
+ const body = parsed.body.trim();
34670
+ if (!body && fm.allowedTools.length === 0) return null;
34671
+ return { body, allowedTools: fm.allowedTools };
34672
+ } catch {
34673
+ return null;
34674
+ }
34675
+ }
34676
+ async function createRole(args) {
34677
+ if (!NAME_REGEX2.test(args.name)) {
34678
+ throw new Error(
34679
+ `Invalid role name "${args.name}". Use letters, digits, dot, underscore, dash.`
34680
+ );
34681
+ }
34682
+ if (args.name.includes("/") || args.name.includes("\\") || args.name.includes("..")) {
34683
+ throw new Error(`Role name must not contain path separators or "..".`);
34684
+ }
34685
+ const scopeDir = resolveScopeDir2(args.scope, args.workspaceRoot);
34686
+ const dir = args.category ? path16.join(scopeDir, args.category) : scopeDir;
34687
+ await fs9.mkdir(dir, { recursive: true });
34688
+ const filePath = path16.join(dir, `${args.name}.md`);
34689
+ try {
34690
+ await fs9.access(filePath);
34691
+ throw new Error(`A role named "${args.name}" already exists at ${filePath}`);
34692
+ } catch (err) {
34693
+ if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
34694
+ throw err;
34695
+ }
34696
+ if (err instanceof Error && err.message.includes("already exists")) {
34697
+ throw err;
34698
+ }
34699
+ }
34700
+ const initial = buildStarterRole(args.name);
34701
+ await fs9.writeFile(filePath, initial, "utf8");
34702
+ return { path: filePath };
34703
+ }
34704
+ function buildStarterRole(name) {
34705
+ return `---
34706
+ name: ${name}
34707
+ description: ""
34708
+ trigger-words: []
34709
+ allowed-tools: []
34710
+ provider: claude/opus
34711
+ mode: default
34712
+ ---
34713
+
34714
+ # ${name}
34715
+
34716
+ You are a ${name}. Describe the role's behavior, scope, and any constraints
34717
+ here. The body of this file is loaded as the agent's system prompt when
34718
+ the role is invoked.
34719
+ `;
34720
+ }
34721
+ async function writeRoleFrontmatter(args, workspaceRoot) {
34722
+ if (!path16.isAbsolute(args.path)) {
34723
+ throw new Error(`writeRoleFrontmatter expects an absolute path; got "${args.path}"`);
34724
+ }
34725
+ if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
34726
+ throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
34727
+ }
34728
+ let original;
34729
+ try {
34730
+ original = await fs9.readFile(args.path, "utf8");
34731
+ } catch (err) {
34732
+ throw new Error(
34733
+ `Failed to read role file: ${err instanceof Error ? err.message : String(err)}`
34734
+ );
34735
+ }
34736
+ const parsed = parseRoleFile(original);
34737
+ const newLines = rewriteFrontmatter2(parsed.rawFrontmatterLines, args.frontmatter);
34738
+ const nextFrontmatter = ["---", ...newLines, "---"].join("\n");
34739
+ const nextContent = parsed.hadFrontmatter ? `${nextFrontmatter}
34740
+ ${parsed.body}` : `${nextFrontmatter}
34741
+
34742
+ ${original}`;
34743
+ await fs9.writeFile(args.path, nextContent, "utf8");
34744
+ }
34745
+ async function moveRole(args, workspaceRoot) {
34746
+ if (!path16.isAbsolute(args.path)) {
34747
+ throw new Error(`moveRole expects an absolute path; got "${args.path}"`);
34748
+ }
34749
+ if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
34750
+ throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
34751
+ }
34752
+ const oldDir = path16.dirname(args.path);
34753
+ const oldFilename = path16.basename(args.path, ".md");
34754
+ const roots = allowedRoots2(workspaceRoot);
34755
+ const rolesRoot = roots.find((r) => path16.resolve(args.path).startsWith(r));
34756
+ if (!rolesRoot) {
34757
+ throw new Error(`Cannot determine roles root for "${args.path}"`);
34758
+ }
34759
+ const relFromRoot = path16.relative(rolesRoot, path16.dirname(args.path));
34760
+ const currentCategory = relFromRoot && relFromRoot !== "." ? relFromRoot : "";
34761
+ const newName = args.newName ?? oldFilename;
34762
+ const newCategory = args.newCategory !== void 0 ? args.newCategory : currentCategory;
34763
+ if (!NAME_REGEX2.test(newName)) {
34764
+ throw new Error(`Invalid role name: "${newName}"`);
34765
+ }
34766
+ if (newCategory && !NAME_REGEX2.test(newCategory)) {
34767
+ throw new Error(`Invalid category name: "${newCategory}"`);
34768
+ }
34769
+ const newDir = newCategory ? path16.join(rolesRoot, newCategory) : rolesRoot;
34770
+ const newPath = path16.join(newDir, `${newName}.md`);
34771
+ if (path16.resolve(newPath) === path16.resolve(args.path)) {
34772
+ return { path: args.path };
34773
+ }
34774
+ await fs9.mkdir(newDir, { recursive: true });
34775
+ try {
34776
+ await fs9.access(newPath);
34777
+ throw new Error(`A role already exists at "${newPath}"`);
34778
+ } catch (err) {
34779
+ if (err.code !== "ENOENT") throw err;
34780
+ }
34781
+ await fs9.rename(args.path, newPath);
34782
+ const frontmatterPatch = {};
34783
+ if (newName !== oldFilename) {
34784
+ frontmatterPatch.description = void 0;
34785
+ }
34786
+ await writeRoleFrontmatter(
34787
+ {
34788
+ path: newPath,
34789
+ frontmatter: {
34790
+ ...newName !== oldFilename ? { name: newName } : {}
34791
+ }
34792
+ },
34793
+ workspaceRoot
34794
+ );
34795
+ if (oldDir !== rolesRoot) {
34796
+ try {
34797
+ const remaining = await fs9.readdir(oldDir);
34798
+ if (remaining.length === 0) {
34799
+ await fs9.rmdir(oldDir);
34800
+ }
34801
+ } catch {
34802
+ }
34803
+ }
34804
+ return { path: newPath };
34805
+ }
34806
+
34807
+ // ../server/src/server/brands/scanner.ts
34808
+ import fs10 from "node:fs/promises";
34809
+ import path17 from "node:path";
34810
+ var NAME_REGEX3 = /^[a-z0-9][a-z0-9._-]*$/i;
34811
+ function resolveScopeDir3(scope, workspaceRoot) {
34812
+ if (scope === "project") {
34813
+ if (!workspaceRoot) {
34814
+ throw new Error('workspaceRoot is required for scope "project"');
34815
+ }
34816
+ return path17.join(workspaceRoot, ".brand");
34817
+ }
34818
+ throw new Error(`Unknown scope: ${scope}`);
34819
+ }
34820
+ function allowedRoots3(workspaceRoot) {
34821
+ const roots = [];
34822
+ if (workspaceRoot) {
34823
+ roots.push(path17.join(workspaceRoot, ".brand"));
34824
+ }
34825
+ return roots.map((r) => path17.resolve(r));
34826
+ }
34827
+ function isInsideAllowedRoot3(absPath, workspaceRoot) {
34828
+ const resolved = path17.resolve(absPath);
34829
+ for (const root of allowedRoots3(workspaceRoot)) {
34830
+ const rel = path17.relative(root, resolved);
34831
+ if (rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel)) {
34832
+ return true;
34833
+ }
34834
+ }
34835
+ return false;
34836
+ }
34837
+ var FRONTMATTER_RE3 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
34838
+ function parseBrandFile(text) {
34839
+ const match = FRONTMATTER_RE3.exec(text);
34840
+ if (!match) {
34841
+ return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
34842
+ }
34843
+ return {
34844
+ rawFrontmatterLines: (match[1] ?? "").split(/\r?\n/),
34845
+ body: match[2] ?? "",
34846
+ hadFrontmatter: true
34847
+ };
34848
+ }
34849
+ function unquote2(value) {
34850
+ const trimmed = value.trim();
34851
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
34852
+ return trimmed.slice(1, -1);
34853
+ }
34854
+ return trimmed;
34855
+ }
34856
+ function quoteIfNeeded(value) {
34857
+ if (/[:#\n\[\]]/.test(value) || value.startsWith(" ") || value.endsWith(" ")) {
34858
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
34859
+ }
34860
+ return value;
34861
+ }
34862
+ function parseFrontmatter2(lines) {
34863
+ const result = {
34864
+ description: "",
34865
+ tags: [],
34866
+ variables: []
34867
+ };
34868
+ let i = 0;
34869
+ while (i < lines.length) {
34870
+ const line = lines[i] ?? "";
34871
+ const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
34872
+ if (!keyMatch) {
34873
+ i++;
34874
+ continue;
34875
+ }
34876
+ const key = keyMatch[1];
34877
+ const rawValue = (keyMatch[2] ?? "").trim();
34878
+ if (key === "description") {
34879
+ result.description = unquote2(rawValue);
34880
+ i++;
34881
+ } else if (key === "tags") {
34882
+ if (rawValue) {
34883
+ result.tags = rawValue.replace(/^\[|\]$/g, "").split(",").map((s) => unquote2(s)).filter((s) => s.length > 0);
34884
+ } else {
34885
+ const items = [];
34886
+ let j = i + 1;
34887
+ while (j < lines.length) {
34888
+ const next = lines[j] ?? "";
34889
+ const m = /^\s+-\s+(.+)$/.exec(next);
34890
+ if (!m) break;
34891
+ items.push(unquote2(m[1]));
34892
+ j++;
34893
+ }
34894
+ result.tags = items;
34895
+ i = j;
34896
+ continue;
34897
+ }
34898
+ i++;
34899
+ } else if (key === "variables") {
34900
+ const vars = [];
34901
+ let j = i + 1;
34902
+ while (j < lines.length) {
34903
+ const next = lines[j] ?? "";
34904
+ if (/^\s+-\s+key\s*:\s*/.test(next)) {
34905
+ const keyVal = unquote2((/^\s+-\s+key\s*:\s*(.*)$/.exec(next) ?? [])[1] ?? "");
34906
+ const varObj = { key: keyVal, type: "text", label: "", value: "" };
34907
+ j++;
34908
+ while (j < lines.length) {
34909
+ const sub = lines[j] ?? "";
34910
+ if (/^\s+-\s+/.test(sub) && !/^\s{4,}-\s+/.test(sub)) break;
34911
+ const subMatch = /^\s+(\w+)\s*:\s*(.*)$/.exec(sub);
34912
+ if (!subMatch) {
34913
+ j++;
34914
+ break;
34915
+ }
34916
+ const subKey = subMatch[1];
34917
+ const subVal = unquote2(subMatch[2] ?? "");
34918
+ if (subKey === "type") {
34919
+ varObj.type = subVal;
34920
+ } else if (subKey === "label") {
34921
+ varObj.label = subVal;
34922
+ } else if (subKey === "value") {
34923
+ varObj.value = subVal;
34924
+ } else if (subKey === "section") {
34925
+ varObj.section = subVal;
34926
+ } else if (subKey === "options") {
34927
+ varObj.options = subVal.replace(/^\[|\]$/g, "").split(",").map((s) => unquote2(s)).filter((s) => s.length > 0);
34928
+ } else if (subKey === "fileRef") {
34929
+ varObj.fileRef = subVal;
34930
+ }
34931
+ j++;
34932
+ }
34933
+ vars.push(varObj);
34934
+ } else if (/^[A-Za-z]/.test(next)) {
34935
+ break;
34936
+ } else {
34937
+ j++;
34938
+ }
34939
+ }
34940
+ result.variables = vars;
34941
+ i = j;
34942
+ continue;
34943
+ } else {
34944
+ i++;
34945
+ }
34946
+ }
34947
+ return result;
34948
+ }
34949
+ function serializeVariables(variables) {
34950
+ if (variables.length === 0) return ["variables: []"];
34951
+ const lines = ["variables:"];
34952
+ for (const v of variables) {
34953
+ lines.push(` - key: ${quoteIfNeeded(v.key)}`);
34954
+ lines.push(` type: ${v.type}`);
34955
+ lines.push(` label: ${quoteIfNeeded(v.label)}`);
34956
+ lines.push(` value: ${quoteIfNeeded(v.value)}`);
34957
+ if (v.section) {
34958
+ lines.push(` section: ${v.section}`);
34959
+ }
34960
+ if (v.options && v.options.length > 0) {
34961
+ lines.push(` options: [${v.options.map(quoteIfNeeded).join(", ")}]`);
34962
+ }
34963
+ if (v.fileRef) {
34964
+ lines.push(` fileRef: ${quoteIfNeeded(v.fileRef)}`);
34965
+ }
34966
+ }
34967
+ return lines;
34968
+ }
34969
+ var OWNED_KEYS2 = ["description", "tags", "variables"];
34970
+ function findOwnedSpans3(lines) {
34971
+ const spans = [];
34972
+ let i = 0;
34973
+ while (i < lines.length) {
34974
+ const line = lines[i] ?? "";
34975
+ const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
34976
+ if (!keyMatch) {
34977
+ i++;
34978
+ continue;
34979
+ }
34980
+ const key = keyMatch[1];
34981
+ const rawValue = keyMatch[2] ?? "";
34982
+ if (!OWNED_KEYS2.includes(key)) {
34983
+ i++;
34984
+ continue;
34985
+ }
34986
+ let end = i + 1;
34987
+ if (rawValue.trim() === "" || rawValue.trim() === "[]") {
34988
+ while (end < lines.length) {
34989
+ const next = lines[end] ?? "";
34990
+ if (/^\s+/.test(next)) {
34991
+ end++;
34992
+ } else {
34993
+ break;
34994
+ }
34995
+ }
34996
+ }
34997
+ spans.push({ key, startLine: i, endLine: end });
34998
+ i = end;
34999
+ }
35000
+ return spans;
35001
+ }
35002
+ function rewriteFrontmatter3(originalLines, next) {
35003
+ const spans = findOwnedSpans3(originalLines);
35004
+ const ownedKeys = new Set(spans.map((s) => s.key));
35005
+ const replacements = /* @__PURE__ */ new Map();
35006
+ if (next.description !== void 0) {
35007
+ const v = next.description;
35008
+ replacements.set("description", v.length > 0 ? [`description: ${quoteIfNeeded(v)}`] : []);
35009
+ }
35010
+ if (next.tags !== void 0) {
35011
+ if (next.tags.length === 0) {
35012
+ replacements.set("tags", []);
35013
+ } else {
35014
+ replacements.set("tags", [`tags: [${next.tags.map(quoteIfNeeded).join(", ")}]`]);
35015
+ }
35016
+ }
35017
+ if (next.variables !== void 0) {
35018
+ replacements.set("variables", serializeVariables(next.variables));
35019
+ }
35020
+ const out = [];
35021
+ let i = 0;
35022
+ while (i < originalLines.length) {
35023
+ const span = spans.find((s) => s.startLine === i);
35024
+ if (span) {
35025
+ if (replacements.has(span.key)) {
35026
+ out.push(...replacements.get(span.key) ?? []);
35027
+ } else {
35028
+ for (let j = span.startLine; j < span.endLine; j++) {
35029
+ out.push(originalLines[j] ?? "");
35030
+ }
35031
+ }
35032
+ i = span.endLine;
35033
+ continue;
35034
+ }
35035
+ out.push(originalLines[i] ?? "");
35036
+ i++;
35037
+ }
35038
+ for (const key of OWNED_KEYS2) {
35039
+ if (replacements.has(key) && !ownedKeys.has(key)) {
35040
+ out.push(...replacements.get(key) ?? []);
35041
+ }
35042
+ }
35043
+ return out;
35044
+ }
35045
+ async function readBrandsFromDir(scope, dir) {
35046
+ let entries;
35047
+ try {
35048
+ entries = await fs10.readdir(dir, { withFileTypes: true });
35049
+ } catch {
35050
+ return [];
35051
+ }
35052
+ const results = [];
35053
+ for (const entry of entries) {
35054
+ if (!entry.isFile()) continue;
35055
+ if (!entry.name.endsWith(".md")) continue;
35056
+ const name = entry.name.slice(0, -".md".length);
35057
+ if (!name || !NAME_REGEX3.test(name)) continue;
35058
+ const fullPath = path17.join(dir, entry.name);
35059
+ let stat10;
35060
+ try {
35061
+ const s = await fs10.stat(fullPath);
35062
+ stat10 = { mtime: s.mtime, size: s.size };
35063
+ } catch {
35064
+ continue;
35065
+ }
35066
+ let parsed;
35067
+ try {
35068
+ const text = await fs10.readFile(fullPath, "utf8");
35069
+ const { rawFrontmatterLines, hadFrontmatter } = parseBrandFile(text);
35070
+ parsed = hadFrontmatter ? parseFrontmatter2(rawFrontmatterLines) : { description: "", tags: [], variables: [] };
35071
+ } catch {
35072
+ parsed = { description: "", tags: [], variables: [] };
35073
+ }
35074
+ results.push({
35075
+ id: `${scope}:${name}`,
35076
+ scope,
35077
+ name,
35078
+ description: parsed.description,
35079
+ tags: parsed.tags,
35080
+ variables: parsed.variables,
35081
+ path: fullPath,
35082
+ modifiedAt: stat10.mtime.toISOString(),
35083
+ size: stat10.size
35084
+ });
35085
+ }
35086
+ results.sort((a, b) => a.name.localeCompare(b.name));
35087
+ return results;
35088
+ }
35089
+ async function listBrands(args) {
35090
+ return readBrandsFromDir("project", resolveScopeDir3("project", args.workspaceRoot));
35091
+ }
35092
+ async function createBrand(args) {
35093
+ if (!NAME_REGEX3.test(args.name)) {
35094
+ throw new Error(
35095
+ `Invalid brand name "${args.name}". Use letters, digits, dot, underscore, dash.`
35096
+ );
35097
+ }
35098
+ if (args.name.includes("/") || args.name.includes("\\") || args.name.includes("..")) {
35099
+ throw new Error(`Brand name must not contain path separators or "..".`);
35100
+ }
35101
+ const dir = resolveScopeDir3("project", args.workspaceRoot);
35102
+ await fs10.mkdir(dir, { recursive: true });
35103
+ const filePath = path17.join(dir, `${args.name}.md`);
35104
+ try {
35105
+ await fs10.access(filePath);
35106
+ throw new Error(`A brand named "${args.name}" already exists at ${filePath}`);
35107
+ } catch (err) {
35108
+ if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
35109
+ throw err;
35110
+ }
35111
+ if (err instanceof Error && err.message.includes("already exists")) {
35112
+ throw err;
35113
+ }
35114
+ }
35115
+ await fs10.writeFile(filePath, buildStarterBrand(args.name), "utf8");
35116
+ return { path: filePath };
35117
+ }
35118
+ function buildStarterBrand(name) {
35119
+ return `---
35120
+ name: ${name}
35121
+ description: ""
35122
+ tags: []
35123
+ variables:
35124
+ - key: color.primary
35125
+ type: color
35126
+ label: Primary Color
35127
+ value: "#000000"
35128
+ - key: color.accent
35129
+ type: color
35130
+ label: Accent Color
35131
+ value: "#ffffff"
35132
+ - key: font.heading
35133
+ type: font
35134
+ label: Heading Font
35135
+ value: ""
35136
+ - key: font.body
35137
+ type: font
35138
+ label: Body Font
35139
+ value: ""
35140
+ - key: logo
35141
+ type: asset
35142
+ label: Logo
35143
+ value: ""
35144
+ - key: tone
35145
+ type: text
35146
+ label: Tone of Voice
35147
+ value: ""
35148
+ ---
35149
+
35150
+ # ${name}
35151
+
35152
+ Describe this brand's identity, design philosophy, and usage guidelines here.
35153
+ `;
35154
+ }
35155
+ var TARGET_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
35156
+ async function copyBrandAsset(args) {
35157
+ if (!args.workspaceRoot) {
35158
+ throw new Error("workspaceRoot is required to copy a brand asset");
35159
+ }
35160
+ if (!args.sourcePath || !path17.isAbsolute(args.sourcePath)) {
35161
+ throw new Error(`copyBrandAsset expects an absolute sourcePath; got "${args.sourcePath}"`);
35162
+ }
35163
+ if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
35164
+ throw new Error(
35165
+ `Invalid targetName "${args.targetName}". Use letters, digits, dot, underscore, dash.`
35166
+ );
35167
+ }
35168
+ const stats = await fs10.stat(args.sourcePath);
35169
+ if (!stats.isFile()) {
35170
+ throw new Error(`Source path is not a regular file: ${args.sourcePath}`);
35171
+ }
35172
+ const ext = path17.extname(args.sourcePath).toLowerCase();
35173
+ const fileName = `${args.targetName}${ext}`;
35174
+ const assetsDir = path17.join(args.workspaceRoot, ".brand", "assets");
35175
+ const destAbs = path17.resolve(assetsDir, fileName);
35176
+ const rel = path17.relative(path17.resolve(assetsDir), destAbs);
35177
+ if (rel.startsWith("..") || path17.isAbsolute(rel)) {
35178
+ throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
35179
+ }
35180
+ await fs10.mkdir(assetsDir, { recursive: true });
35181
+ await fs10.copyFile(args.sourcePath, destAbs);
35182
+ return {
35183
+ relativePath: `assets/${fileName}`,
35184
+ absolutePath: destAbs
35185
+ };
35186
+ }
35187
+ async function uploadBrandAsset(args) {
35188
+ if (!args.workspaceRoot) {
35189
+ throw new Error("workspaceRoot is required to upload a brand asset");
35190
+ }
35191
+ if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
35192
+ throw new Error(
35193
+ `Invalid targetName "${args.targetName}". Use letters, digits, dot, underscore, dash.`
35194
+ );
35195
+ }
35196
+ if (!args.dataBase64 || args.dataBase64.trim().length === 0) {
35197
+ throw new Error("No file data provided for brand asset upload");
35198
+ }
35199
+ const extFromSource = args.sourceName ? path17.extname(args.sourceName).toLowerCase() : "";
35200
+ const ext = extFromSource || ".png";
35201
+ const fileName = `${args.targetName}${ext}`;
35202
+ const assetsDir = path17.join(args.workspaceRoot, ".brand", "assets");
35203
+ const destAbs = path17.resolve(assetsDir, fileName);
35204
+ const rel = path17.relative(path17.resolve(assetsDir), destAbs);
35205
+ if (rel.startsWith("..") || path17.isAbsolute(rel)) {
35206
+ throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
35207
+ }
35208
+ let data;
35209
+ try {
35210
+ data = Buffer.from(args.dataBase64, "base64");
35211
+ } catch {
35212
+ throw new Error("Failed to decode uploaded brand asset");
35213
+ }
35214
+ if (data.length === 0) {
35215
+ throw new Error("Uploaded brand asset is empty");
35216
+ }
35217
+ await fs10.mkdir(assetsDir, { recursive: true });
35218
+ await fs10.writeFile(destAbs, data);
35219
+ return {
35220
+ relativePath: `assets/${fileName}`,
35221
+ absolutePath: destAbs
35222
+ };
35223
+ }
35224
+ async function writeBrandFrontmatter(args, workspaceRoot) {
35225
+ if (!path17.isAbsolute(args.path)) {
35226
+ throw new Error(`writeBrandFrontmatter expects an absolute path; got "${args.path}"`);
35227
+ }
35228
+ if (!isInsideAllowedRoot3(args.path, workspaceRoot)) {
35229
+ throw new Error(`Path "${args.path}" is not inside an allowlisted brand root`);
35230
+ }
35231
+ let original;
35232
+ try {
35233
+ original = await fs10.readFile(args.path, "utf8");
35234
+ } catch (err) {
35235
+ throw new Error(
35236
+ `Failed to read brand file: ${err instanceof Error ? err.message : String(err)}`
35237
+ );
35238
+ }
35239
+ const parsed = parseBrandFile(original);
35240
+ const newLines = rewriteFrontmatter3(parsed.rawFrontmatterLines, args.frontmatter);
35241
+ const nextFrontmatter = ["---", ...newLines, "---"].join("\n");
35242
+ const nextContent = parsed.hadFrontmatter ? `${nextFrontmatter}
35243
+ ${parsed.body}` : `${nextFrontmatter}
35244
+
35245
+ ${original}`;
35246
+ await fs10.writeFile(args.path, nextContent, "utf8");
35247
+ }
34131
35248
 
34132
35249
  // ../server/src/services/oauth-service.ts
34133
35250
  import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
@@ -34374,57 +35491,57 @@ async function fetchGitLabUsername(fetchImpl, accessToken) {
34374
35491
  return null;
34375
35492
  }
34376
35493
  }
34377
- async function readCredential(path30, log) {
35494
+ async function readCredential(path33, log) {
34378
35495
  try {
34379
- const raw = await readFile3(path30, "utf8");
35496
+ const raw = await readFile3(path33, "utf8");
34380
35497
  const parsed = JSON.parse(raw);
34381
35498
  return parsed.gitlab ?? null;
34382
35499
  } catch (error) {
34383
35500
  if (isNotFound(error)) return null;
34384
- log.warn({ err: error, path: path30 }, "oauth.credentials.read_failed");
35501
+ log.warn({ err: error, path: path33 }, "oauth.credentials.read_failed");
34385
35502
  return null;
34386
35503
  }
34387
35504
  }
34388
- async function persistCredential(path30, credential, log) {
34389
- await mkdir4(dirname4(path30), { recursive: true });
35505
+ async function persistCredential(path33, credential, log) {
35506
+ await mkdir4(dirname4(path33), { recursive: true });
34390
35507
  let current = {};
34391
35508
  try {
34392
- const raw = await readFile3(path30, "utf8");
35509
+ const raw = await readFile3(path33, "utf8");
34393
35510
  current = JSON.parse(raw);
34394
35511
  } catch (error) {
34395
35512
  if (!isNotFound(error)) {
34396
- log.warn({ err: error, path: path30 }, "oauth.credentials.read_failed_overwriting");
35513
+ log.warn({ err: error, path: path33 }, "oauth.credentials.read_failed_overwriting");
34397
35514
  }
34398
35515
  }
34399
35516
  const next = { ...current, gitlab: credential };
34400
- const tmpPath = `${path30}.tmp-${process.pid}-${Date.now()}`;
35517
+ const tmpPath = `${path33}.tmp-${process.pid}-${Date.now()}`;
34401
35518
  await writeFile4(tmpPath, JSON.stringify(next, null, 2), { mode: 384 });
34402
- await rename(tmpPath, path30);
35519
+ await rename(tmpPath, path33);
34403
35520
  }
34404
- async function deleteCredential(path30, log) {
35521
+ async function deleteCredential(path33, log) {
34405
35522
  let current = {};
34406
35523
  try {
34407
- const raw = await readFile3(path30, "utf8");
35524
+ const raw = await readFile3(path33, "utf8");
34408
35525
  current = JSON.parse(raw);
34409
35526
  } catch (error) {
34410
35527
  if (isNotFound(error)) return;
34411
- log.warn({ err: error, path: path30 }, "oauth.credentials.delete_read_failed");
35528
+ log.warn({ err: error, path: path33 }, "oauth.credentials.delete_read_failed");
34412
35529
  return;
34413
35530
  }
34414
35531
  delete current.gitlab;
34415
35532
  if (Object.keys(current).length === 0) {
34416
35533
  try {
34417
- await unlink(path30);
35534
+ await unlink(path33);
34418
35535
  } catch (error) {
34419
35536
  if (!isNotFound(error)) {
34420
- log.warn({ err: error, path: path30 }, "oauth.credentials.unlink_failed");
35537
+ log.warn({ err: error, path: path33 }, "oauth.credentials.unlink_failed");
34421
35538
  }
34422
35539
  }
34423
35540
  return;
34424
35541
  }
34425
- const tmpPath = `${path30}.tmp-${process.pid}-${Date.now()}`;
35542
+ const tmpPath = `${path33}.tmp-${process.pid}-${Date.now()}`;
34426
35543
  await writeFile4(tmpPath, JSON.stringify(current, null, 2), { mode: 384 });
34427
- await rename(tmpPath, path30);
35544
+ await rename(tmpPath, path33);
34428
35545
  }
34429
35546
  function defaultGlabConfigPath() {
34430
35547
  const home = homedir4();
@@ -34439,15 +35556,15 @@ function defaultGlabConfigPath() {
34439
35556
  const xdg = process.env.XDG_CONFIG_HOME;
34440
35557
  return join9(xdg && xdg.length > 0 ? xdg : join9(home, ".config"), "glab-cli", "config.yml");
34441
35558
  }
34442
- async function writeGlabConfig(path30, credential, log) {
34443
- await mkdir4(dirname4(path30), { recursive: true });
35559
+ async function writeGlabConfig(path33, credential, log) {
35560
+ await mkdir4(dirname4(path33), { recursive: true });
34444
35561
  let doc;
34445
35562
  try {
34446
- const raw = await readFile3(path30, "utf8");
35563
+ const raw = await readFile3(path33, "utf8");
34447
35564
  doc = YAML.parseDocument(raw);
34448
35565
  if (doc.errors.length > 0) {
34449
35566
  log.warn(
34450
- { errors: doc.errors.map((e) => e.message), path: path30 },
35567
+ { errors: doc.errors.map((e) => e.message), path: path33 },
34451
35568
  "oauth.glab.parse_errors_replacing"
34452
35569
  );
34453
35570
  doc = YAML.parseDocument("{}");
@@ -34456,7 +35573,7 @@ async function writeGlabConfig(path30, credential, log) {
34456
35573
  if (isNotFound(error)) {
34457
35574
  doc = YAML.parseDocument("{}");
34458
35575
  } else {
34459
- log.warn({ err: error, path: path30 }, "oauth.glab.read_failed_replacing");
35576
+ log.warn({ err: error, path: path33 }, "oauth.glab.read_failed_replacing");
34460
35577
  doc = YAML.parseDocument("{}");
34461
35578
  }
34462
35579
  }
@@ -34469,18 +35586,18 @@ async function writeGlabConfig(path30, credential, log) {
34469
35586
  if (credential.username) {
34470
35587
  hostEntry.set("user", credential.username);
34471
35588
  }
34472
- const tmpPath = `${path30}.tmp-${process.pid}-${Date.now()}`;
35589
+ const tmpPath = `${path33}.tmp-${process.pid}-${Date.now()}`;
34473
35590
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
34474
- await rename(tmpPath, path30);
35591
+ await rename(tmpPath, path33);
34475
35592
  }
34476
- async function removeGlabHost(path30, log) {
35593
+ async function removeGlabHost(path33, log) {
34477
35594
  let doc;
34478
35595
  try {
34479
- const raw = await readFile3(path30, "utf8");
35596
+ const raw = await readFile3(path33, "utf8");
34480
35597
  doc = YAML.parseDocument(raw);
34481
35598
  } catch (error) {
34482
35599
  if (isNotFound(error)) return;
34483
- log.warn({ err: error, path: path30 }, "oauth.glab.remove_read_failed");
35600
+ log.warn({ err: error, path: path33 }, "oauth.glab.remove_read_failed");
34484
35601
  return;
34485
35602
  }
34486
35603
  const hosts = doc.get("hosts");
@@ -34489,9 +35606,9 @@ async function removeGlabHost(path30, log) {
34489
35606
  if (hosts.items.length === 0) {
34490
35607
  doc.delete("hosts");
34491
35608
  }
34492
- const tmpPath = `${path30}.tmp-${process.pid}-${Date.now()}`;
35609
+ const tmpPath = `${path33}.tmp-${process.pid}-${Date.now()}`;
34493
35610
  await writeFile4(tmpPath, doc.toString(), { mode: 384 });
34494
- await rename(tmpPath, path30);
35611
+ await rename(tmpPath, path33);
34495
35612
  }
34496
35613
  function ensureMap(doc, key) {
34497
35614
  const existing = doc.get(key);
@@ -35602,7 +36719,7 @@ var Session = class _Session {
35602
36719
  chatService,
35603
36720
  scheduleService,
35604
36721
  loopService,
35605
- workflowService,
36722
+ questService,
35606
36723
  checkoutDiffManager,
35607
36724
  github,
35608
36725
  gitlab,
@@ -35649,7 +36766,7 @@ var Session = class _Session {
35649
36766
  this.chatService = chatService;
35650
36767
  this.scheduleService = scheduleService;
35651
36768
  this.loopService = loopService;
35652
- this.workflowService = workflowService;
36769
+ this.questService = questService;
35653
36770
  this.checkoutDiffManager = checkoutDiffManager;
35654
36771
  this.github = github ?? createGitHubService();
35655
36772
  this.gitlab = gitlab ?? createGitLabService();
@@ -36228,7 +37345,13 @@ var Session = class _Session {
36228
37345
  await this.handleCloseItemsRequest(msg);
36229
37346
  break;
36230
37347
  case "update_agent_request":
36231
- await this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
37348
+ await this.handleUpdateAgentRequest(
37349
+ msg.agentId,
37350
+ msg.name,
37351
+ msg.labels,
37352
+ msg.requestId,
37353
+ msg.internal
37354
+ );
36232
37355
  break;
36233
37356
  case "set_voice_mode":
36234
37357
  await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
@@ -36458,6 +37581,15 @@ var Session = class _Session {
36458
37581
  case "file_delete_request":
36459
37582
  await this.handleFileDeleteRequest(msg);
36460
37583
  break;
37584
+ case "file_explorer_delete_request":
37585
+ await this.handleFileExplorerDeleteRequest(msg);
37586
+ break;
37587
+ case "file_mkdir_request":
37588
+ await this.handleFileMkdirRequest(msg);
37589
+ break;
37590
+ case "file_move_request":
37591
+ await this.handleFileMoveRequest(msg);
37592
+ break;
36461
37593
  case "project_icon_request":
36462
37594
  await this.handleProjectIconRequest(msg);
36463
37595
  break;
@@ -36618,20 +37750,47 @@ var Session = class _Session {
36618
37750
  case "loop/stop":
36619
37751
  await this.handleLoopStopRequest(msg);
36620
37752
  break;
36621
- case "workflow/run":
36622
- await this.handleWorkflowRunRequest(msg);
37753
+ case "quest/run":
37754
+ await this.handleQuestRunRequest(msg);
36623
37755
  break;
36624
- case "workflow/list":
36625
- await this.handleWorkflowListRequest(msg);
37756
+ case "quest/list":
37757
+ await this.handleQuestListRequest(msg);
36626
37758
  break;
36627
- case "workflow/inspect":
36628
- await this.handleWorkflowInspectRequest(msg);
37759
+ case "quest/inspect":
37760
+ await this.handleQuestInspectRequest(msg);
36629
37761
  break;
36630
- case "workflow/stop":
36631
- await this.handleWorkflowStopRequest(msg);
37762
+ case "quest/stop":
37763
+ await this.handleQuestStopRequest(msg);
36632
37764
  break;
36633
- case "agents/list":
36634
- await this.handleAgentsListRequest(msg);
37765
+ case "roles/list":
37766
+ await this.handleRolesListRequest(msg);
37767
+ break;
37768
+ case "roles/list-scope":
37769
+ await this.handleRolesListScopeRequest(msg);
37770
+ break;
37771
+ case "roles/create":
37772
+ await this.handleRoleCreateRequest(msg);
37773
+ break;
37774
+ case "roles/write-frontmatter":
37775
+ await this.handleRoleWriteFrontmatterRequest(msg);
37776
+ break;
37777
+ case "roles/move":
37778
+ await this.handleRoleMoveRequest(msg);
37779
+ break;
37780
+ case "brands/list-scope":
37781
+ await this.handleBrandsListScopeRequest(msg);
37782
+ break;
37783
+ case "brands/create":
37784
+ await this.handleBrandCreateRequest(msg);
37785
+ break;
37786
+ case "brands/write-frontmatter":
37787
+ await this.handleBrandWriteFrontmatterRequest(msg);
37788
+ break;
37789
+ case "brands/asset-copy":
37790
+ await this.handleBrandAssetCopyRequest(msg);
37791
+ break;
37792
+ case "brands/asset-upload":
37793
+ await this.handleBrandAssetUploadRequest(msg);
36635
37794
  break;
36636
37795
  }
36637
37796
  } catch (error) {
@@ -37063,26 +38222,27 @@ var Session = class _Session {
37063
38222
  }
37064
38223
  await unarchiveAgentState(this.agentStorage, this.agentManager, matched.id);
37065
38224
  }
37066
- async handleUpdateAgentRequest(agentId, name, labels, requestId) {
38225
+ async handleUpdateAgentRequest(agentId, name, labels, requestId, internal) {
37067
38226
  this.sessionLogger.info(
37068
38227
  {
37069
38228
  agentId,
37070
38229
  requestId,
37071
38230
  hasName: typeof name === "string",
37072
- labelCount: labels ? Object.keys(labels).length : 0
38231
+ labelCount: labels ? Object.keys(labels).length : 0,
38232
+ internal
37073
38233
  },
37074
38234
  "session: update_agent_request"
37075
38235
  );
37076
38236
  const normalizedName = name?.trim();
37077
38237
  const normalizedLabels = labels && Object.keys(labels).length > 0 ? labels : void 0;
37078
- if (!normalizedName && !normalizedLabels) {
38238
+ if (!normalizedName && !normalizedLabels && internal === void 0) {
37079
38239
  this.emit({
37080
38240
  type: "update_agent_response",
37081
38241
  payload: {
37082
38242
  requestId,
37083
38243
  agentId,
37084
38244
  accepted: false,
37085
- error: "Nothing to update (provide name and/or labels)"
38245
+ error: "Nothing to update (provide name, labels, and/or internal)"
37086
38246
  }
37087
38247
  });
37088
38248
  return;
@@ -37090,7 +38250,8 @@ var Session = class _Session {
37090
38250
  try {
37091
38251
  await this.agentManager.updateAgentMetadata(agentId, {
37092
38252
  ...normalizedName ? { title: normalizedName } : {},
37093
- ...normalizedLabels ? { labels: normalizedLabels } : {}
38253
+ ...normalizedLabels ? { labels: normalizedLabels } : {},
38254
+ ...internal !== void 0 ? { internal } : {}
37094
38255
  });
37095
38256
  this.emit({
37096
38257
  type: "update_agent_response",
@@ -38985,7 +40146,7 @@ var Session = class _Session {
38985
40146
  homeDir: process.env.HOME ?? homedir5(),
38986
40147
  query: query2,
38987
40148
  limit
38988
- })).map((path30) => ({ path: path30, kind: "directory" }));
40149
+ })).map((path33) => ({ path: path33, kind: "directory" }));
38989
40150
  const directories = entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
38990
40151
  this.emit({
38991
40152
  type: "directory_suggestions_response",
@@ -39882,6 +41043,104 @@ ${details}`.trim());
39882
41043
  });
39883
41044
  }
39884
41045
  }
41046
+ async handleFileExplorerDeleteRequest(request) {
41047
+ const { cwd: workspaceCwd, path: requestedPath, requestId } = request;
41048
+ const cwd = workspaceCwd.trim();
41049
+ if (!cwd) {
41050
+ this.emit({
41051
+ type: "file_explorer_delete_response",
41052
+ payload: { cwd: workspaceCwd, path: requestedPath, error: "cwd is required", requestId }
41053
+ });
41054
+ return;
41055
+ }
41056
+ try {
41057
+ await deleteEntry({ root: cwd, relativePath: requestedPath });
41058
+ this.emit({
41059
+ type: "file_explorer_delete_response",
41060
+ payload: { cwd, path: requestedPath, error: null, requestId }
41061
+ });
41062
+ } catch (error) {
41063
+ this.sessionLogger.error(
41064
+ { err: error, cwd, path: requestedPath },
41065
+ `Failed to delete entry ${requestedPath} in workspace ${cwd}`
41066
+ );
41067
+ this.emit({
41068
+ type: "file_explorer_delete_response",
41069
+ payload: { cwd, path: requestedPath, error: error?.message ?? "delete failed", requestId }
41070
+ });
41071
+ }
41072
+ }
41073
+ async handleFileMkdirRequest(request) {
41074
+ const { cwd: workspaceCwd, path: requestedPath, requestId } = request;
41075
+ const cwd = workspaceCwd.trim();
41076
+ if (!cwd) {
41077
+ this.emit({
41078
+ type: "file_mkdir_response",
41079
+ payload: { cwd: workspaceCwd, path: requestedPath, error: "cwd is required", requestId }
41080
+ });
41081
+ return;
41082
+ }
41083
+ try {
41084
+ await createDirectory({ root: cwd, relativePath: requestedPath });
41085
+ this.emit({
41086
+ type: "file_mkdir_response",
41087
+ payload: { cwd, path: requestedPath, error: null, requestId }
41088
+ });
41089
+ } catch (error) {
41090
+ this.sessionLogger.error(
41091
+ { err: error, cwd, path: requestedPath },
41092
+ `Failed to create directory ${requestedPath} in workspace ${cwd}`
41093
+ );
41094
+ this.emit({
41095
+ type: "file_mkdir_response",
41096
+ payload: {
41097
+ cwd,
41098
+ path: requestedPath,
41099
+ error: error?.message ?? "mkdir failed",
41100
+ requestId
41101
+ }
41102
+ });
41103
+ }
41104
+ }
41105
+ async handleFileMoveRequest(request) {
41106
+ const { cwd: workspaceCwd, sourcePath, destinationPath, requestId } = request;
41107
+ const cwd = workspaceCwd.trim();
41108
+ if (!cwd) {
41109
+ this.emit({
41110
+ type: "file_move_response",
41111
+ payload: {
41112
+ cwd: workspaceCwd,
41113
+ sourcePath,
41114
+ destinationPath,
41115
+ error: "cwd is required",
41116
+ requestId
41117
+ }
41118
+ });
41119
+ return;
41120
+ }
41121
+ try {
41122
+ await moveEntry({ root: cwd, sourcePath, destinationPath });
41123
+ this.emit({
41124
+ type: "file_move_response",
41125
+ payload: { cwd, sourcePath, destinationPath, error: null, requestId }
41126
+ });
41127
+ } catch (error) {
41128
+ this.sessionLogger.error(
41129
+ { err: error, cwd, sourcePath, destinationPath },
41130
+ `Failed to move ${sourcePath} to ${destinationPath} in workspace ${cwd}`
41131
+ );
41132
+ this.emit({
41133
+ type: "file_move_response",
41134
+ payload: {
41135
+ cwd,
41136
+ sourcePath,
41137
+ destinationPath,
41138
+ error: error?.message ?? "move failed",
41139
+ requestId
41140
+ }
41141
+ });
41142
+ }
41143
+ }
39885
41144
  /**
39886
41145
  * Handle project icon request for a given cwd
39887
41146
  */
@@ -42543,17 +43802,17 @@ ${details}`.trim());
42543
43802
  this.emitLoopRpcError(request, error);
42544
43803
  }
42545
43804
  }
42546
- emitWorkflowRpcError(request, error) {
43805
+ emitQuestRpcError(request, error) {
42547
43806
  const message = error instanceof Error ? error.message : String(error);
42548
43807
  const responseType = `${request.type}/response`;
42549
- if (responseType === "workflow/list/response") {
43808
+ if (responseType === "quest/list/response") {
42550
43809
  this.emit({
42551
43810
  type: responseType,
42552
- payload: { requestId: request.requestId, workflows: [], error: message }
43811
+ payload: { requestId: request.requestId, quests: [], error: message }
42553
43812
  });
42554
43813
  return;
42555
43814
  }
42556
- if (responseType === "agents/list/response") {
43815
+ if (responseType === "roles/list/response") {
42557
43816
  this.emit({
42558
43817
  type: responseType,
42559
43818
  payload: { requestId: request.requestId, roles: [], error: message }
@@ -42562,12 +43821,12 @@ ${details}`.trim());
42562
43821
  }
42563
43822
  this.emit({
42564
43823
  type: responseType,
42565
- payload: { requestId: request.requestId, workflow: null, error: message }
43824
+ payload: { requestId: request.requestId, quest: null, error: message }
42566
43825
  });
42567
43826
  }
42568
- async handleWorkflowRunRequest(request) {
43827
+ async handleQuestRunRequest(request) {
42569
43828
  try {
42570
- const workflow = await this.workflowService.run({
43829
+ const quest = await this.questService.run({
42571
43830
  kind: request.kind,
42572
43831
  cwd: request.cwd,
42573
43832
  prompt: request.prompt,
@@ -42578,16 +43837,16 @@ ${details}`.trim());
42578
43837
  summarizerEnabled: request.summarizerEnabled
42579
43838
  });
42580
43839
  this.emit({
42581
- type: "workflow/run/response",
42582
- payload: { requestId: request.requestId, workflow, error: null }
43840
+ type: "quest/run/response",
43841
+ payload: { requestId: request.requestId, quest, error: null }
42583
43842
  });
42584
43843
  } catch (error) {
42585
- this.emitWorkflowRpcError(request, error);
43844
+ this.emitQuestRpcError(request, error);
42586
43845
  }
42587
43846
  }
42588
- async handleWorkflowListRequest(request) {
43847
+ async handleQuestListRequest(request) {
42589
43848
  try {
42590
- const workflows = this.workflowService.list().map((w) => ({
43849
+ const quests = this.questService.list().map((w) => ({
42591
43850
  id: w.id,
42592
43851
  kind: w.kind,
42593
43852
  status: w.status,
@@ -42597,44 +43856,247 @@ ${details}`.trim());
42597
43856
  updatedAt: w.updatedAt
42598
43857
  }));
42599
43858
  this.emit({
42600
- type: "workflow/list/response",
42601
- payload: { requestId: request.requestId, workflows, error: null }
43859
+ type: "quest/list/response",
43860
+ payload: { requestId: request.requestId, quests, error: null }
42602
43861
  });
42603
43862
  } catch (error) {
42604
- this.emitWorkflowRpcError(request, error);
43863
+ this.emitQuestRpcError(request, error);
42605
43864
  }
42606
43865
  }
42607
- async handleWorkflowInspectRequest(request) {
43866
+ async handleQuestInspectRequest(request) {
42608
43867
  try {
42609
- const workflow = this.workflowService.inspect(request.id);
43868
+ const quest = this.questService.inspect(request.id);
42610
43869
  this.emit({
42611
- type: "workflow/inspect/response",
42612
- payload: { requestId: request.requestId, workflow, error: null }
43870
+ type: "quest/inspect/response",
43871
+ payload: { requestId: request.requestId, quest, error: null }
42613
43872
  });
42614
43873
  } catch (error) {
42615
- this.emitWorkflowRpcError(request, error);
43874
+ this.emitQuestRpcError(request, error);
42616
43875
  }
42617
43876
  }
42618
- async handleWorkflowStopRequest(request) {
43877
+ async handleQuestStopRequest(request) {
42619
43878
  try {
42620
- const workflow = await this.workflowService.stop(request.id);
43879
+ const quest = await this.questService.stop(request.id);
42621
43880
  this.emit({
42622
- type: "workflow/stop/response",
42623
- payload: { requestId: request.requestId, workflow, error: null }
43881
+ type: "quest/stop/response",
43882
+ payload: { requestId: request.requestId, quest, error: null }
42624
43883
  });
42625
43884
  } catch (error) {
42626
- this.emitWorkflowRpcError(request, error);
43885
+ this.emitQuestRpcError(request, error);
42627
43886
  }
42628
43887
  }
42629
- async handleAgentsListRequest(request) {
43888
+ async handleRolesListRequest(request) {
42630
43889
  try {
42631
- const roles = await listAgentRoles({ workspaceRoot: request.workspaceRoot });
43890
+ const roles = await listRoles({ workspaceRoot: request.workspaceRoot });
42632
43891
  this.emit({
42633
- type: "agents/list/response",
43892
+ type: "roles/list/response",
42634
43893
  payload: { requestId: request.requestId, roles, error: null }
42635
43894
  });
42636
43895
  } catch (error) {
42637
- this.emitWorkflowRpcError(request, error);
43896
+ this.emitQuestRpcError(request, error);
43897
+ }
43898
+ }
43899
+ async handleRolesListScopeRequest(request) {
43900
+ const { scope, workspaceRoot, requestId } = request;
43901
+ try {
43902
+ const roles = await listRoles({
43903
+ scope,
43904
+ ...workspaceRoot ? { workspaceRoot: expandTilde(workspaceRoot) } : {}
43905
+ });
43906
+ this.emit({
43907
+ type: "roles/list-scope/response",
43908
+ payload: { requestId, roles, error: null }
43909
+ });
43910
+ } catch (error) {
43911
+ const message = error instanceof Error ? error.message : String(error);
43912
+ this.sessionLogger.error({ err: error, scope }, "Failed to list roles by scope");
43913
+ this.emit({
43914
+ type: "roles/list-scope/response",
43915
+ payload: { requestId, roles: [], error: message }
43916
+ });
43917
+ }
43918
+ }
43919
+ async handleRoleCreateRequest(request) {
43920
+ const { scope, name, category, workspaceRoot, requestId } = request;
43921
+ try {
43922
+ const result = await createRole({
43923
+ scope,
43924
+ name,
43925
+ ...category ? { category } : {},
43926
+ ...workspaceRoot ? { workspaceRoot: expandTilde(workspaceRoot) } : {}
43927
+ });
43928
+ this.emit({
43929
+ type: "roles/create/response",
43930
+ payload: { requestId, path: result.path, error: null }
43931
+ });
43932
+ } catch (error) {
43933
+ const message = error instanceof Error ? error.message : String(error);
43934
+ this.sessionLogger.error({ err: error, scope, name }, "Failed to create role");
43935
+ this.emit({
43936
+ type: "roles/create/response",
43937
+ payload: { requestId, path: "", error: message }
43938
+ });
43939
+ }
43940
+ }
43941
+ async handleRoleWriteFrontmatterRequest(request) {
43942
+ const { path: rolePath, workspaceRoot, frontmatter, requestId } = request;
43943
+ try {
43944
+ await writeRoleFrontmatter(
43945
+ { path: rolePath, frontmatter },
43946
+ workspaceRoot ? expandTilde(workspaceRoot) : void 0
43947
+ );
43948
+ this.emit({
43949
+ type: "roles/write-frontmatter/response",
43950
+ payload: { requestId, path: rolePath, error: null }
43951
+ });
43952
+ } catch (error) {
43953
+ const message = error instanceof Error ? error.message : String(error);
43954
+ this.sessionLogger.error({ err: error, path: rolePath }, "Failed to write role frontmatter");
43955
+ this.emit({
43956
+ type: "roles/write-frontmatter/response",
43957
+ payload: { requestId, path: rolePath, error: message }
43958
+ });
43959
+ }
43960
+ }
43961
+ async handleRoleMoveRequest(request) {
43962
+ const { path: rolePath, newName, newCategory, workspaceRoot, requestId } = request;
43963
+ try {
43964
+ const result = await moveRole(
43965
+ { path: rolePath, newName, newCategory },
43966
+ workspaceRoot ? expandTilde(workspaceRoot) : void 0
43967
+ );
43968
+ this.emit({
43969
+ type: "roles/move/response",
43970
+ payload: { requestId, path: result.path, error: null }
43971
+ });
43972
+ } catch (error) {
43973
+ const message = error instanceof Error ? error.message : String(error);
43974
+ this.sessionLogger.error({ err: error, path: rolePath }, "Failed to move role");
43975
+ this.emit({
43976
+ type: "roles/move/response",
43977
+ payload: { requestId, path: rolePath, error: message }
43978
+ });
43979
+ }
43980
+ }
43981
+ async handleBrandsListScopeRequest(request) {
43982
+ const { workspaceRoot, requestId } = request;
43983
+ try {
43984
+ const brands = await listBrands({
43985
+ ...workspaceRoot ? { workspaceRoot: expandTilde(workspaceRoot) } : {}
43986
+ });
43987
+ this.emit({
43988
+ type: "brands/list-scope/response",
43989
+ payload: { requestId, brands, error: null }
43990
+ });
43991
+ } catch (error) {
43992
+ const message = error instanceof Error ? error.message : String(error);
43993
+ this.sessionLogger.error({ err: error }, "Failed to list brands");
43994
+ this.emit({
43995
+ type: "brands/list-scope/response",
43996
+ payload: { requestId, brands: [], error: message }
43997
+ });
43998
+ }
43999
+ }
44000
+ async handleBrandCreateRequest(request) {
44001
+ const { name, workspaceRoot, requestId } = request;
44002
+ try {
44003
+ const result = await createBrand({
44004
+ name,
44005
+ ...workspaceRoot ? { workspaceRoot: expandTilde(workspaceRoot) } : {}
44006
+ });
44007
+ this.emit({
44008
+ type: "brands/create/response",
44009
+ payload: { requestId, path: result.path, error: null }
44010
+ });
44011
+ } catch (error) {
44012
+ const message = error instanceof Error ? error.message : String(error);
44013
+ this.sessionLogger.error({ err: error, name }, "Failed to create brand");
44014
+ this.emit({
44015
+ type: "brands/create/response",
44016
+ payload: { requestId, path: "", error: message }
44017
+ });
44018
+ }
44019
+ }
44020
+ async handleBrandWriteFrontmatterRequest(request) {
44021
+ const { path: brandPath, workspaceRoot, frontmatter, requestId } = request;
44022
+ try {
44023
+ await writeBrandFrontmatter(
44024
+ { path: brandPath, frontmatter },
44025
+ workspaceRoot ? expandTilde(workspaceRoot) : void 0
44026
+ );
44027
+ this.emit({
44028
+ type: "brands/write-frontmatter/response",
44029
+ payload: { requestId, path: brandPath, error: null }
44030
+ });
44031
+ } catch (error) {
44032
+ const message = error instanceof Error ? error.message : String(error);
44033
+ this.sessionLogger.error(
44034
+ { err: error, path: brandPath },
44035
+ "Failed to write brand frontmatter"
44036
+ );
44037
+ this.emit({
44038
+ type: "brands/write-frontmatter/response",
44039
+ payload: { requestId, path: brandPath, error: message }
44040
+ });
44041
+ }
44042
+ }
44043
+ async handleBrandAssetCopyRequest(request) {
44044
+ const { workspaceRoot, sourcePath, targetName, requestId } = request;
44045
+ try {
44046
+ const result = await copyBrandAsset({
44047
+ workspaceRoot: expandTilde(workspaceRoot),
44048
+ sourcePath: expandTilde(sourcePath),
44049
+ targetName
44050
+ });
44051
+ this.emit({
44052
+ type: "brands/asset-copy/response",
44053
+ payload: {
44054
+ requestId,
44055
+ relativePath: result.relativePath,
44056
+ absolutePath: result.absolutePath,
44057
+ error: null
44058
+ }
44059
+ });
44060
+ } catch (error) {
44061
+ const message = error instanceof Error ? error.message : String(error);
44062
+ this.sessionLogger.error(
44063
+ { err: error, sourcePath, targetName },
44064
+ "Failed to copy brand asset"
44065
+ );
44066
+ this.emit({
44067
+ type: "brands/asset-copy/response",
44068
+ payload: { requestId, relativePath: "", absolutePath: "", error: message }
44069
+ });
44070
+ }
44071
+ }
44072
+ async handleBrandAssetUploadRequest(request) {
44073
+ const { workspaceRoot, targetName, sourceName, dataBase64, requestId } = request;
44074
+ try {
44075
+ const result = await uploadBrandAsset({
44076
+ workspaceRoot: expandTilde(workspaceRoot),
44077
+ targetName,
44078
+ sourceName,
44079
+ dataBase64
44080
+ });
44081
+ this.emit({
44082
+ type: "brands/asset-upload/response",
44083
+ payload: {
44084
+ requestId,
44085
+ relativePath: result.relativePath,
44086
+ absolutePath: result.absolutePath,
44087
+ error: null
44088
+ }
44089
+ });
44090
+ } catch (error) {
44091
+ const message = error instanceof Error ? error.message : String(error);
44092
+ this.sessionLogger.error(
44093
+ { err: error, targetName, sourceName },
44094
+ "Failed to upload brand asset"
44095
+ );
44096
+ this.emit({
44097
+ type: "brands/asset-upload/response",
44098
+ payload: { requestId, relativePath: "", absolutePath: "", error: message }
44099
+ });
42638
44100
  }
42639
44101
  }
42640
44102
  emitTerminalsChangedSnapshot(input) {
@@ -43974,7 +45436,7 @@ var MissingDaemonVersionError = class extends Error {
43974
45436
  }
43975
45437
  };
43976
45438
  var VoiceAssistantWebSocketServer = class {
43977
- constructor(server, logger, serverId, agentManager, agentStorage, downloadTokenStore, appostleHome, daemonConfigStore, mcpBaseUrl, wsConfig, speech, terminalManager, dictation, agentProviderRuntimeSettings, providerOverrides, daemonVersion, onLifecycleIntent, projectRegistry, workspaceRegistry, chatService, loopService, workflowService, scheduleService, checkoutDiffManager, scriptRouteStore, scriptRuntimeStore, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, workspaceGitService, github, gitlab) {
45439
+ constructor(server, logger, serverId, agentManager, agentStorage, downloadTokenStore, appostleHome, daemonConfigStore, mcpBaseUrl, wsConfig, speech, terminalManager, dictation, agentProviderRuntimeSettings, providerOverrides, daemonVersion, onLifecycleIntent, projectRegistry, workspaceRegistry, chatService, loopService, questService, scheduleService, checkoutDiffManager, scriptRouteStore, scriptRuntimeStore, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, workspaceGitService, github, gitlab) {
43978
45440
  this.pendingConnections = /* @__PURE__ */ new Map();
43979
45441
  this.sessions = /* @__PURE__ */ new Map();
43980
45442
  this.externalSessionsByKey = /* @__PURE__ */ new Map();
@@ -44029,10 +45491,10 @@ var VoiceAssistantWebSocketServer = class {
44029
45491
  throw new Error("VoiceAssistantWebSocketServer requires a loop service.");
44030
45492
  }
44031
45493
  this.loopService = loopService;
44032
- if (!workflowService) {
44033
- throw new Error("VoiceAssistantWebSocketServer requires a workflow service.");
45494
+ if (!questService) {
45495
+ throw new Error("VoiceAssistantWebSocketServer requires a quest service.");
44034
45496
  }
44035
- this.workflowService = workflowService;
45497
+ this.questService = questService;
44036
45498
  if (!scheduleService) {
44037
45499
  throw new Error("VoiceAssistantWebSocketServer requires a schedule service.");
44038
45500
  }
@@ -44333,7 +45795,7 @@ var VoiceAssistantWebSocketServer = class {
44333
45795
  workspaceRegistry: this.workspaceRegistry,
44334
45796
  chatService: this.chatService,
44335
45797
  loopService: this.loopService,
44336
- workflowService: this.workflowService,
45798
+ questService: this.questService,
44337
45799
  scheduleService: this.scheduleService,
44338
45800
  checkoutDiffManager: this.checkoutDiffManager,
44339
45801
  github: this.github,
@@ -45153,7 +46615,7 @@ import { join as join12 } from "node:path";
45153
46615
  // ../server/src/server/speech/providers/local/sherpa/model-downloader.ts
45154
46616
  import { createWriteStream } from "node:fs";
45155
46617
  import { mkdir as mkdir5, rename as rename2, rm as rm2, stat as stat5 } from "node:fs/promises";
45156
- import path17 from "node:path";
46618
+ import path18 from "node:path";
45157
46619
  import { Readable as Readable2 } from "node:stream";
45158
46620
  import { pipeline } from "node:stream/promises";
45159
46621
  import { spawn as spawn7 } from "node:child_process";
@@ -45289,11 +46751,11 @@ function getSherpaOnnxModelSpec(id) {
45289
46751
  // ../server/src/server/speech/providers/local/sherpa/model-downloader.ts
45290
46752
  function getSherpaOnnxModelDir(modelsDir, modelId) {
45291
46753
  const spec = getSherpaOnnxModelSpec(modelId);
45292
- return path17.join(modelsDir, spec.extractedDir);
46754
+ return path18.join(modelsDir, spec.extractedDir);
45293
46755
  }
45294
46756
  async function hasRequiredFiles(modelDir, requiredFiles) {
45295
46757
  for (const rel of requiredFiles) {
45296
- const abs = path17.join(modelDir, rel);
46758
+ const abs = path18.join(modelDir, rel);
45297
46759
  try {
45298
46760
  const s = await stat5(abs);
45299
46761
  if (s.isDirectory()) {
@@ -45319,7 +46781,7 @@ async function downloadToFile(options) {
45319
46781
  throw new Error(`Failed to download ${url}: missing response body`);
45320
46782
  }
45321
46783
  const tmpPath = `${outputPath}.tmp-${Date.now()}`;
45322
- await mkdir5(path17.dirname(outputPath), { recursive: true });
46784
+ await mkdir5(path18.dirname(outputPath), { recursive: true });
45323
46785
  const nodeStream = Readable2.fromWeb(res.body);
45324
46786
  try {
45325
46787
  await pipeline(nodeStream, createWriteStream(tmpPath));
@@ -45356,16 +46818,16 @@ async function ensureSherpaOnnxModel(options) {
45356
46818
  modelId: options.modelId
45357
46819
  });
45358
46820
  const spec = getSherpaOnnxModelSpec(options.modelId);
45359
- const modelDir = path17.join(options.modelsDir, spec.extractedDir);
46821
+ const modelDir = path18.join(options.modelsDir, spec.extractedDir);
45360
46822
  if (await hasRequiredFiles(modelDir, spec.requiredFiles)) {
45361
46823
  return modelDir;
45362
46824
  }
45363
46825
  logger.info({ modelsDir: options.modelsDir }, "Starting model download");
45364
46826
  try {
45365
46827
  if (spec.archiveUrl) {
45366
- const downloadsDir = path17.join(options.modelsDir, ".downloads");
45367
- const archiveFilename = path17.basename(new URL(spec.archiveUrl).pathname);
45368
- const archivePath = path17.join(downloadsDir, archiveFilename);
46828
+ const downloadsDir = path18.join(options.modelsDir, ".downloads");
46829
+ const archiveFilename = path18.basename(new URL(spec.archiveUrl).pathname);
46830
+ const archivePath = path18.join(downloadsDir, archiveFilename);
45369
46831
  if (!await isNonEmptyFile(archivePath)) {
45370
46832
  await downloadToFile({
45371
46833
  url: spec.archiveUrl,
@@ -45410,7 +46872,7 @@ async function ensureSherpaOnnxModel(options) {
45410
46872
  if (spec.downloadFiles && spec.downloadFiles.length > 0) {
45411
46873
  await mkdir5(modelDir, { recursive: true });
45412
46874
  for (const file of spec.downloadFiles) {
45413
- const dst = path17.join(modelDir, file.relPath);
46875
+ const dst = path18.join(modelDir, file.relPath);
45414
46876
  if (await isNonEmptyFile(dst)) {
45415
46877
  continue;
45416
46878
  }
@@ -45473,13 +46935,13 @@ import { existsSync as existsSync13 } from "node:fs";
45473
46935
 
45474
46936
  // ../server/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.ts
45475
46937
  import { createRequire as createRequire4 } from "node:module";
45476
- import path19 from "node:path";
46938
+ import path20 from "node:path";
45477
46939
  import { existsSync as existsSync12 } from "node:fs";
45478
46940
  import { spawnSync } from "node:child_process";
45479
46941
 
45480
46942
  // ../server/src/server/speech/providers/local/sherpa/sherpa-runtime-env.ts
45481
46943
  import { createRequire as createRequire3 } from "node:module";
45482
- import path18 from "node:path";
46944
+ import path19 from "node:path";
45483
46945
  function sherpaPlatformArch(platform = process.platform, arch = process.arch) {
45484
46946
  const normalizedPlatform = platform === "win32" ? "win" : platform;
45485
46947
  return `${normalizedPlatform}-${arch}`;
@@ -45500,11 +46962,11 @@ function sherpaLoaderEnvKey(platform = process.platform) {
45500
46962
  return null;
45501
46963
  }
45502
46964
  function prependEnvPath(existing, value) {
45503
- const parts = (existing ?? "").split(path18.delimiter).filter(Boolean);
46965
+ const parts = (existing ?? "").split(path19.delimiter).filter(Boolean);
45504
46966
  if (parts.includes(value)) {
45505
- return parts.join(path18.delimiter);
46967
+ return parts.join(path19.delimiter);
45506
46968
  }
45507
- return [value, ...parts].join(path18.delimiter);
46969
+ return [value, ...parts].join(path19.delimiter);
45508
46970
  }
45509
46971
  function resolveSherpaLoaderEnv(platform = process.platform, arch = process.arch) {
45510
46972
  const key = sherpaLoaderEnvKey(platform);
@@ -45517,7 +46979,7 @@ function resolveSherpaLoaderEnv(platform = process.platform, arch = process.arch
45517
46979
  const pkgJson = require4.resolve(`${packageName}/package.json`);
45518
46980
  return {
45519
46981
  key,
45520
- libDir: path18.dirname(pkgJson),
46982
+ libDir: path19.dirname(pkgJson),
45521
46983
  packageName
45522
46984
  };
45523
46985
  } catch {
@@ -45626,7 +47088,7 @@ function loadSherpaOnnxNode() {
45626
47088
  const platformPkgDir = resolvedEnv?.libDir ?? null;
45627
47089
  if (platformPkgDir) {
45628
47090
  applySherpaLoaderEnv(process.env);
45629
- const addonPath = path19.join(platformPkgDir, "sherpa-onnx.node");
47091
+ const addonPath = path20.join(platformPkgDir, "sherpa-onnx.node");
45630
47092
  if (existsSync12(addonPath)) {
45631
47093
  const byPath = loadWithRequire(require4, addonPath, attempts);
45632
47094
  if (byPath) {
@@ -46398,7 +47860,7 @@ var SherpaOnnxTTS = class {
46398
47860
 
46399
47861
  // ../server/src/server/speech/providers/local/sherpa/silero-vad-provider.ts
46400
47862
  import { copyFile, mkdir as mkdir6, stat as stat6 } from "node:fs/promises";
46401
- import path20 from "node:path";
47863
+ import path21 from "node:path";
46402
47864
 
46403
47865
  // ../server/src/server/speech/providers/local/sherpa/silero-vad-session.ts
46404
47866
  import { EventEmitter as EventEmitter6 } from "node:events";
@@ -46590,8 +48052,8 @@ var SherpaSileroVadSession = class extends EventEmitter6 {
46590
48052
  var SILERO_VAD_DIR = "silero-vad";
46591
48053
  var SILERO_VAD_FILE = "silero_vad.onnx";
46592
48054
  async function ensureSileroVadModel(modelsDir, logger) {
46593
- const destDir = path20.join(modelsDir, SILERO_VAD_DIR);
46594
- const destPath = path20.join(destDir, SILERO_VAD_FILE);
48055
+ const destDir = path21.join(modelsDir, SILERO_VAD_DIR);
48056
+ const destPath = path21.join(destDir, SILERO_VAD_FILE);
46595
48057
  try {
46596
48058
  const s = await stat6(destPath);
46597
48059
  if (s.isFile() && s.size > 0) return destPath;
@@ -47245,7 +48707,7 @@ var OpenAISTT = class {
47245
48707
  const supportsLogprobs = modelToUse === "gpt-4o-transcribe" || modelToUse === "gpt-4o-mini-transcribe";
47246
48708
  const includeLogprobs = ["logprobs"];
47247
48709
  const response = await this.openaiClient.audio.transcriptions.create({
47248
- file: await import("fs").then((fs13) => fs13.createReadStream(tempFilePath)),
48710
+ file: await import("fs").then((fs14) => fs14.createReadStream(tempFilePath)),
47249
48711
  language,
47250
48712
  model: modelToUse,
47251
48713
  ...supportsLogprobs ? { include: includeLogprobs } : {},
@@ -49213,6 +50675,11 @@ var AgentManager = class {
49213
50675
  if (updates.labels) {
49214
50676
  await this.setLabels(agentId, updates.labels);
49215
50677
  }
50678
+ if (updates.internal !== void 0) {
50679
+ liveAgent.internal = updates.internal;
50680
+ await this.persistSnapshot(liveAgent, { internal: updates.internal });
50681
+ this.dispatch({ type: "agent_state", agent: { ...liveAgent } });
50682
+ }
49216
50683
  return;
49217
50684
  }
49218
50685
  const registry = this.requireRegistry();
@@ -50649,8 +52116,8 @@ var AgentManager = class {
50649
52116
 
50650
52117
  // ../server/src/server/agent/agent-storage.ts
50651
52118
  import { randomUUID as randomUUID11 } from "node:crypto";
50652
- import { promises as fs10 } from "node:fs";
50653
- import path21 from "node:path";
52119
+ import { promises as fs11 } from "node:fs";
52120
+ import path22 from "node:path";
50654
52121
  import { z as z37 } from "zod";
50655
52122
  var SERIALIZABLE_CONFIG_SCHEMA = z37.object({
50656
52123
  title: z37.string().nullable().optional(),
@@ -50737,12 +52204,12 @@ var AgentStorage = class {
50737
52204
  }
50738
52205
  const nextPath = this.buildRecordPath(record);
50739
52206
  const previousPath = this.pathById.get(agentId);
50740
- await fs10.mkdir(path21.dirname(nextPath), { recursive: true });
52207
+ await fs11.mkdir(path22.dirname(nextPath), { recursive: true });
50741
52208
  await writeFileAtomically(nextPath, JSON.stringify(record, null, 2));
50742
52209
  this.addIndexedPath(agentId, nextPath);
50743
52210
  if (previousPath && previousPath !== nextPath) {
50744
52211
  try {
50745
- await fs10.unlink(previousPath);
52212
+ await fs11.unlink(previousPath);
50746
52213
  } catch {
50747
52214
  }
50748
52215
  this.removeIndexedPath(agentId, previousPath);
@@ -50770,7 +52237,7 @@ var AgentStorage = class {
50770
52237
  const paths = Array.from(this.pathsById.get(agentId) ?? []);
50771
52238
  for (const filePath of paths) {
50772
52239
  try {
50773
- await fs10.unlink(filePath);
52240
+ await fs11.unlink(filePath);
50774
52241
  } catch (error) {
50775
52242
  const code = error.code;
50776
52243
  if (code && code !== "ENOENT") {
@@ -50844,7 +52311,7 @@ var AgentStorage = class {
50844
52311
  const records = [];
50845
52312
  let entries = [];
50846
52313
  try {
50847
- entries = await fs10.readdir(this.baseDir, { withFileTypes: true });
52314
+ entries = await fs11.readdir(this.baseDir, { withFileTypes: true });
50848
52315
  } catch (error) {
50849
52316
  if (error.code === "ENOENT") {
50850
52317
  return [];
@@ -50853,7 +52320,7 @@ var AgentStorage = class {
50853
52320
  }
50854
52321
  for (const entry of entries) {
50855
52322
  if (entry.isFile() && entry.name.endsWith(".json")) {
50856
- const rootPath = path21.join(this.baseDir, entry.name);
52323
+ const rootPath = path22.join(this.baseDir, entry.name);
50857
52324
  const rootRecord = await this.readRecordFile(rootPath);
50858
52325
  if (!rootRecord) {
50859
52326
  continue;
@@ -50867,10 +52334,10 @@ var AgentStorage = class {
50867
52334
  if (!entry.isDirectory()) {
50868
52335
  continue;
50869
52336
  }
50870
- const projectDir = path21.join(this.baseDir, entry.name);
52337
+ const projectDir = path22.join(this.baseDir, entry.name);
50871
52338
  let files = [];
50872
52339
  try {
50873
- files = await fs10.readdir(projectDir, { withFileTypes: true });
52340
+ files = await fs11.readdir(projectDir, { withFileTypes: true });
50874
52341
  } catch {
50875
52342
  continue;
50876
52343
  }
@@ -50878,7 +52345,7 @@ var AgentStorage = class {
50878
52345
  if (!file.isFile() || !file.name.endsWith(".json")) {
50879
52346
  continue;
50880
52347
  }
50881
- const filePath = path21.join(projectDir, file.name);
52348
+ const filePath = path22.join(projectDir, file.name);
50882
52349
  const record = await this.readRecordFile(filePath);
50883
52350
  if (!record) {
50884
52351
  continue;
@@ -50893,7 +52360,7 @@ var AgentStorage = class {
50893
52360
  }
50894
52361
  async readRecordFile(filePath) {
50895
52362
  try {
50896
- const content = await fs10.readFile(filePath, "utf8");
52363
+ const content = await fs11.readFile(filePath, "utf8");
50897
52364
  const parsed = JSON.parse(content);
50898
52365
  return parseStoredAgentRecord(parsed);
50899
52366
  } catch (error) {
@@ -50903,7 +52370,7 @@ var AgentStorage = class {
50903
52370
  }
50904
52371
  buildRecordPath(record) {
50905
52372
  const projectDir = projectDirNameFromCwd(record.cwd);
50906
- return path21.join(this.baseDir, projectDir, `${record.id}.json`);
52373
+ return path22.join(this.baseDir, projectDir, `${record.id}.json`);
50907
52374
  }
50908
52375
  addIndexedPath(agentId, filePath) {
50909
52376
  const paths = this.pathsById.get(agentId) ?? /* @__PURE__ */ new Set();
@@ -50925,7 +52392,7 @@ var AgentStorage = class {
50925
52392
  }
50926
52393
  };
50927
52394
  function projectDirNameFromCwd(cwd) {
50928
- const { root } = path21.win32.parse(cwd);
52395
+ const { root } = path22.win32.parse(cwd);
50929
52396
  const withoutRoot = cwd.slice(root.length).replace(/[\\/]+$/, "");
50930
52397
  const sanitizedRoot = root.replace(/[:\\/]+/g, "-").replace(/^-+|-+$/g, "");
50931
52398
  const prefix = sanitizedRoot ? sanitizedRoot + "-" : "";
@@ -50935,10 +52402,10 @@ function projectDirNameFromCwd(cwd) {
50935
52402
  return prefix + withoutRoot.replace(/[\\/]+/g, "-");
50936
52403
  }
50937
52404
  async function writeFileAtomically(targetPath, payload) {
50938
- const directory = path21.dirname(targetPath);
50939
- const tempPath = path21.join(directory, `.agent.tmp-${process.pid}-${Date.now()}-${randomUUID11()}`);
50940
- await fs10.writeFile(tempPath, payload, "utf8");
50941
- await fs10.rename(tempPath, targetPath);
52405
+ const directory = path22.dirname(targetPath);
52406
+ const tempPath = path22.join(directory, `.agent.tmp-${process.pid}-${Date.now()}-${randomUUID11()}`);
52407
+ await fs11.writeFile(tempPath, payload, "utf8");
52408
+ await fs11.rename(tempPath, targetPath);
50942
52409
  }
50943
52410
 
50944
52411
  // ../server/src/server/agent/mcp-server.ts
@@ -52444,7 +53911,7 @@ async function createMcpWorktree(options) {
52444
53911
  }
52445
53912
 
52446
53913
  // ../server/src/server/workspace-registry-bootstrap.ts
52447
- import path22 from "node:path";
53914
+ import path23 from "node:path";
52448
53915
  function minIsoDate(left, right) {
52449
53916
  if (!left) {
52450
53917
  return right;
@@ -52541,8 +54008,8 @@ async function bootstrapWorkspaceRegistries(options) {
52541
54008
  }
52542
54009
  options.logger.info(
52543
54010
  {
52544
- projectsFile: path22.join(options.appostleHome, "projects", "projects.json"),
52545
- workspacesFile: path22.join(options.appostleHome, "projects", "workspaces.json"),
54011
+ projectsFile: path23.join(options.appostleHome, "projects", "projects.json"),
54012
+ workspacesFile: path23.join(options.appostleHome, "projects", "workspaces.json"),
52546
54013
  materializedProjects: projectRanges.size,
52547
54014
  materializedWorkspaces: recordsByWorkspaceId.size
52548
54015
  },
@@ -52738,8 +54205,8 @@ var CheckoutDiffManager = class {
52738
54205
 
52739
54206
  // ../server/src/server/loop-service.ts
52740
54207
  import { randomUUID as randomUUID12 } from "node:crypto";
52741
- import { promises as fs11 } from "node:fs";
52742
- import path23 from "node:path";
54208
+ import { promises as fs12 } from "node:fs";
54209
+ import path24 from "node:path";
52743
54210
  import { z as z39 } from "zod";
52744
54211
  var LOOP_ID_LENGTH = 8;
52745
54212
  var DEFAULT_LOOP_PROVIDER = "claude";
@@ -52945,7 +54412,7 @@ var LoopService = class {
52945
54412
  this.loops = /* @__PURE__ */ new Map();
52946
54413
  this.persistQueue = Promise.resolve();
52947
54414
  this.running = /* @__PURE__ */ new Map();
52948
- this.storePath = path23.join(options.appostleHome, "loops", "loops.json");
54415
+ this.storePath = path24.join(options.appostleHome, "loops", "loops.json");
52949
54416
  this.logger = options.logger.child({ module: "loop-service" });
52950
54417
  }
52951
54418
  async initialize() {
@@ -52954,7 +54421,7 @@ var LoopService = class {
52954
54421
  }
52955
54422
  this.loops.clear();
52956
54423
  try {
52957
- const raw = await fs11.readFile(this.storePath, "utf8");
54424
+ const raw = await fs12.readFile(this.storePath, "utf8");
52958
54425
  const parsed = StoredLoopsSchema.parse(JSON.parse(raw));
52959
54426
  for (const record of parsed) {
52960
54427
  if (record.status === "running") {
@@ -53014,7 +54481,7 @@ var LoopService = class {
53014
54481
  id: createLoopId(),
53015
54482
  name: normalizeName(input.name),
53016
54483
  prompt,
53017
- cwd: path23.resolve(input.cwd),
54484
+ cwd: path24.resolve(input.cwd),
53018
54485
  provider: input.provider ?? DEFAULT_LOOP_PROVIDER,
53019
54486
  model: normalizePrompt(input.model, "model"),
53020
54487
  workerProvider: input.workerProvider ?? null,
@@ -53462,11 +54929,11 @@ ${output}` : `exit ${result.exitCode}`
53462
54929
  }
53463
54930
  async persist() {
53464
54931
  const nextPersist = this.persistQueue.then(async () => {
53465
- await fs11.mkdir(path23.dirname(this.storePath), { recursive: true });
54932
+ await fs12.mkdir(path24.dirname(this.storePath), { recursive: true });
53466
54933
  const records = Array.from(this.loops.values()).sort(
53467
54934
  (left, right) => left.createdAt.localeCompare(right.createdAt)
53468
54935
  );
53469
- await fs11.writeFile(this.storePath, JSON.stringify(records, null, 2), "utf8");
54936
+ await fs12.writeFile(this.storePath, JSON.stringify(records, null, 2), "utf8");
53470
54937
  });
53471
54938
  this.persistQueue = nextPersist.catch(() => {
53472
54939
  });
@@ -54003,25 +55470,25 @@ var ScheduleService = class {
54003
55470
  }
54004
55471
  };
54005
55472
 
54006
- // ../server/src/server/workflow/service.ts
55473
+ // ../server/src/server/quest/service.ts
54007
55474
  import { randomUUID as randomUUID14 } from "node:crypto";
54008
55475
 
54009
- // ../server/src/server/workflow/store.ts
54010
- import { promises as fs12 } from "node:fs";
54011
- import path24 from "node:path";
55476
+ // ../server/src/server/quest/store.ts
55477
+ import { promises as fs13 } from "node:fs";
55478
+ import path25 from "node:path";
54012
55479
  import { z as z40 } from "zod";
54013
- var StoredWorkflowsSchema = z40.array(WorkflowRecordSchema);
54014
- var WorkflowStore = class {
55480
+ var StoredQuestsSchema = z40.array(QuestRecordSchema);
55481
+ var QuestStore = class {
54015
55482
  constructor(options) {
54016
- this.filePath = path24.join(options.appostleHome, "workflows", "workflows.json");
55483
+ this.filePath = path25.join(options.appostleHome, "quests", "quests.json");
54017
55484
  this.logger = options.logger;
54018
55485
  this.records = /* @__PURE__ */ new Map();
54019
55486
  this.persistQueue = Promise.resolve();
54020
55487
  }
54021
55488
  async initialize() {
54022
55489
  try {
54023
- const raw = await fs12.readFile(this.filePath, "utf8");
54024
- const parsed = StoredWorkflowsSchema.parse(JSON.parse(raw));
55490
+ const raw = await fs13.readFile(this.filePath, "utf8");
55491
+ const parsed = StoredQuestsSchema.parse(JSON.parse(raw));
54025
55492
  this.records = new Map(parsed.map((r) => [r.id, r]));
54026
55493
  } catch (err) {
54027
55494
  const code = err && typeof err === "object" && "code" in err ? err.code : null;
@@ -54031,15 +55498,13 @@ var WorkflowStore = class {
54031
55498
  }
54032
55499
  this.logger.warn(
54033
55500
  { err: err instanceof Error ? err.message : String(err) },
54034
- "workflow-store: failed to read workflows.json \u2014 starting empty"
55501
+ "quest-store: failed to read quests.json \u2014 starting empty"
54035
55502
  );
54036
55503
  this.records = /* @__PURE__ */ new Map();
54037
55504
  }
54038
55505
  }
54039
55506
  list() {
54040
- return [...this.records.values()].sort(
54041
- (a, b) => a.createdAt.localeCompare(b.createdAt)
54042
- );
55507
+ return [...this.records.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
54043
55508
  }
54044
55509
  get(id) {
54045
55510
  return this.records.get(id) ?? null;
@@ -54056,20 +55521,20 @@ var WorkflowStore = class {
54056
55521
  }
54057
55522
  async persist() {
54058
55523
  this.persistQueue = this.persistQueue.then(async () => {
54059
- await fs12.mkdir(path24.dirname(this.filePath), { recursive: true });
55524
+ await fs13.mkdir(path25.dirname(this.filePath), { recursive: true });
54060
55525
  const payload = JSON.stringify([...this.records.values()], null, 2);
54061
- await fs12.writeFile(this.filePath, payload, "utf8");
55526
+ await fs13.writeFile(this.filePath, payload, "utf8");
54062
55527
  }).catch((err) => {
54063
55528
  this.logger.error(
54064
55529
  { err: err instanceof Error ? err.message : String(err) },
54065
- "workflow-store: persist failed"
55530
+ "quest-store: persist failed"
54066
55531
  );
54067
55532
  });
54068
55533
  return this.persistQueue;
54069
55534
  }
54070
55535
  };
54071
55536
 
54072
- // ../server/src/server/workflow/rules.ts
55537
+ // ../server/src/server/quest/rules.ts
54073
55538
  var LOOP_RULES = {
54074
55539
  perCardRole: "inherit",
54075
55540
  perCardModel: "inherit",
@@ -54086,28 +55551,65 @@ var RALPH_RULES = {
54086
55551
  summarizerCard: "off",
54087
55552
  cardExpansion: "tool-calls"
54088
55553
  };
55554
+ var RUN_RULES = {
55555
+ perCardRole: "free",
55556
+ perCardModel: "free",
55557
+ perCardPrompt: "locked",
55558
+ cardsEditableWhileRunning: false,
55559
+ summarizerCard: "off",
55560
+ cardExpansion: "tool-calls"
55561
+ };
55562
+ var ORCHESTRATE_RULES = {
55563
+ perCardRole: "free",
55564
+ perCardModel: "free",
55565
+ perCardPrompt: "locked",
55566
+ cardsEditableWhileRunning: false,
55567
+ summarizerCard: "off",
55568
+ cardExpansion: "tool-calls"
55569
+ };
54089
55570
  function getRulesForKind(kind) {
54090
55571
  switch (kind) {
54091
55572
  case "loop":
54092
55573
  return { ...LOOP_RULES };
54093
55574
  case "ralph":
54094
55575
  return { ...RALPH_RULES };
55576
+ case "run":
55577
+ return { ...RUN_RULES };
55578
+ case "orchestrate":
55579
+ return { ...ORCHESTRATE_RULES };
54095
55580
  }
54096
55581
  }
54097
55582
 
54098
- // ../server/src/server/workflow/service.ts
54099
- var WORKFLOW_ID_LENGTH = 8;
55583
+ // ../server/src/server/quest/service.ts
55584
+ var QUEST_ID_LENGTH = 8;
54100
55585
  var DEFAULT_PROVIDER = "claude";
54101
55586
  function nowIso2() {
54102
55587
  return (/* @__PURE__ */ new Date()).toISOString();
54103
55588
  }
54104
- function createWorkflowId() {
54105
- return randomUUID14().replace(/-/g, "").slice(0, WORKFLOW_ID_LENGTH);
55589
+ function createQuestId() {
55590
+ return randomUUID14().replace(/-/g, "").slice(0, QUEST_ID_LENGTH);
54106
55591
  }
54107
55592
  function defaultWiring() {
54108
55593
  return { inputs: "user-prompt", outputs: "none" };
54109
55594
  }
54110
55595
  function buildInitialCards(options, rulesAllowOverride) {
55596
+ if (options.kind === "orchestrate") {
55597
+ const overrides2 = options.cards ?? [];
55598
+ return overrides2.map(
55599
+ (override, i) => QuestCardSchema.parse({
55600
+ index: i + 1,
55601
+ role: rulesAllowOverride.role ? override.role ?? null : null,
55602
+ model: rulesAllowOverride.model ? override.model ?? null : null,
55603
+ prompt: rulesAllowOverride.prompt ? override.prompt ?? null : null,
55604
+ wiring: defaultWiring(),
55605
+ status: "queued",
55606
+ agentId: null,
55607
+ startedAt: null,
55608
+ completedAt: null,
55609
+ verify: null
55610
+ })
55611
+ );
55612
+ }
54111
55613
  if (options.termination.type !== "count") {
54112
55614
  return [];
54113
55615
  }
@@ -54117,7 +55619,7 @@ function buildInitialCards(options, rulesAllowOverride) {
54117
55619
  for (let i = 0; i < n; i++) {
54118
55620
  const override = overrides[i] ?? {};
54119
55621
  cards.push(
54120
- WorkflowCardSchema.parse({
55622
+ QuestCardSchema.parse({
54121
55623
  index: i + 1,
54122
55624
  role: rulesAllowOverride.role ? override.role ?? null : null,
54123
55625
  model: rulesAllowOverride.model ? override.model ?? null : null,
@@ -54140,7 +55642,7 @@ function buildSummarizerCard(options) {
54140
55642
  if (!options.summarizerEnabled) {
54141
55643
  return null;
54142
55644
  }
54143
- return WorkflowCardSchema.parse({
55645
+ return QuestCardSchema.parse({
54144
55646
  index: 0,
54145
55647
  // sentinel — summarizer is not in the cards array
54146
55648
  role: null,
@@ -54154,14 +55656,14 @@ function buildSummarizerCard(options) {
54154
55656
  verify: null
54155
55657
  });
54156
55658
  }
54157
- var WorkflowService = class {
55659
+ var QuestService = class {
54158
55660
  constructor(options) {
54159
55661
  this.options = options;
54160
55662
  this.running = /* @__PURE__ */ new Map();
54161
55663
  this.subscribers = /* @__PURE__ */ new Set();
54162
55664
  this.loaded = false;
54163
- this.logger = options.logger.child({ module: "workflow-service" });
54164
- this.store = new WorkflowStore({
55665
+ this.logger = options.logger.child({ module: "quest-service" });
55666
+ this.store = new QuestStore({
54165
55667
  appostleHome: options.appostleHome,
54166
55668
  logger: this.logger
54167
55669
  });
@@ -54179,7 +55681,7 @@ var WorkflowService = class {
54179
55681
  get agentManager() {
54180
55682
  return this.options.agentManager;
54181
55683
  }
54182
- /** Subscribe to workflow state changes. Returns an unsubscribe function. */
55684
+ /** Subscribe to quest state changes. Returns an unsubscribe function. */
54183
55685
  subscribe(callback) {
54184
55686
  this.subscribers.add(callback);
54185
55687
  return () => {
@@ -54195,7 +55697,7 @@ var WorkflowService = class {
54195
55697
  if (record.status !== "running") {
54196
55698
  continue;
54197
55699
  }
54198
- const recovered = WorkflowRecordSchema.parse({
55700
+ const recovered = QuestRecordSchema.parse({
54199
55701
  ...record,
54200
55702
  status: "stopped",
54201
55703
  updatedAt: nowIso2(),
@@ -54204,12 +55706,13 @@ var WorkflowService = class {
54204
55706
  cards: record.cards.map(
54205
55707
  (card) => card.status === "running" ? { ...card, status: "stopped", completedAt: nowIso2() } : card
54206
55708
  ),
54207
- summarizerCard: record.summarizerCard && record.summarizerCard.status === "running" ? { ...record.summarizerCard, status: "stopped", completedAt: nowIso2() } : record.summarizerCard
55709
+ summarizerCard: record.summarizerCard && record.summarizerCard.status === "running" ? { ...record.summarizerCard, status: "stopped", completedAt: nowIso2() } : record.summarizerCard,
55710
+ queenCard: record.queenCard && record.queenCard.status === "running" ? { ...record.queenCard, status: "stopped", completedAt: nowIso2() } : record.queenCard
54208
55711
  });
54209
55712
  await this.store.upsert(recovered);
54210
55713
  this.logger.info(
54211
- { workflowId: recovered.id },
54212
- "workflow-service: recovered running workflow \u2192 stopped"
55714
+ { questId: recovered.id },
55715
+ "quest-service: recovered running quest \u2192 stopped"
54213
55716
  );
54214
55717
  }
54215
55718
  this.loaded = true;
@@ -54223,9 +55726,15 @@ var WorkflowService = class {
54223
55726
  if (options.kind === "loop" && options.termination.type !== "count") {
54224
55727
  throw new Error('kind "loop" requires termination.type === "count"');
54225
55728
  }
55729
+ if (options.kind === "run" && options.termination.type !== "count") {
55730
+ throw new Error('kind "run" requires termination.type === "count"');
55731
+ }
54226
55732
  if (options.kind === "ralph" && options.termination.type !== "verify") {
54227
55733
  throw new Error('kind "ralph" requires termination.type === "verify"');
54228
55734
  }
55735
+ if (options.kind === "orchestrate" && options.termination.type !== "orchestrate") {
55736
+ throw new Error('kind "orchestrate" requires termination.type === "orchestrate"');
55737
+ }
54229
55738
  const rules = getRulesForKind(options.kind);
54230
55739
  const overrideAllowed = {
54231
55740
  role: rules.perCardRole !== "locked",
@@ -54233,8 +55742,8 @@ var WorkflowService = class {
54233
55742
  prompt: rules.perCardPrompt !== "locked"
54234
55743
  };
54235
55744
  const createdAt = nowIso2();
54236
- const record = WorkflowRecordSchema.parse({
54237
- id: createWorkflowId(),
55745
+ const record = QuestRecordSchema.parse({
55746
+ id: createQuestId(),
54238
55747
  kind: options.kind,
54239
55748
  rules,
54240
55749
  cwd: options.cwd,
@@ -54255,7 +55764,7 @@ var WorkflowService = class {
54255
55764
  this.publish(record);
54256
55765
  const runner = this.runners[options.kind];
54257
55766
  if (!runner) {
54258
- const failed = WorkflowRecordSchema.parse({
55767
+ const failed = QuestRecordSchema.parse({
54259
55768
  ...record,
54260
55769
  status: "failed",
54261
55770
  updatedAt: nowIso2(),
@@ -54263,21 +55772,18 @@ var WorkflowService = class {
54263
55772
  });
54264
55773
  await this.store.upsert(failed);
54265
55774
  this.publish(failed);
54266
- throw new Error(`No runner registered for workflow kind "${options.kind}"`);
55775
+ throw new Error(`No runner registered for quest kind "${options.kind}"`);
54267
55776
  }
54268
55777
  const abortController = new AbortController();
54269
55778
  const promise = (async () => {
54270
55779
  try {
54271
55780
  await runner.execute({
54272
- workflowId: record.id,
55781
+ questId: record.id,
54273
55782
  signal: abortController.signal,
54274
55783
  service: this
54275
55784
  });
54276
55785
  } catch (err) {
54277
- this.logger.error(
54278
- { err, workflowId: record.id },
54279
- "workflow-service: runner threw"
54280
- );
55786
+ this.logger.error({ err, questId: record.id }, "quest-service: runner threw");
54281
55787
  const current = this.store.get(record.id);
54282
55788
  if (current && current.status === "running") {
54283
55789
  await this.markStatus(record.id, "failed");
@@ -54298,13 +55804,13 @@ var WorkflowService = class {
54298
55804
  async stop(id) {
54299
55805
  const current = this.store.get(id);
54300
55806
  if (!current) {
54301
- throw new Error(`Workflow "${id}" not found`);
55807
+ throw new Error(`Quest "${id}" not found`);
54302
55808
  }
54303
55809
  if (current.status !== "running") {
54304
55810
  return current;
54305
55811
  }
54306
55812
  const stopRequestedAt = nowIso2();
54307
- const updated = WorkflowRecordSchema.parse({
55813
+ const updated = QuestRecordSchema.parse({
54308
55814
  ...current,
54309
55815
  stopRequestedAt,
54310
55816
  updatedAt: stopRequestedAt
@@ -54325,45 +55831,76 @@ var WorkflowService = class {
54325
55831
  // Mutation helpers (used by runners)
54326
55832
  // -------------------------------------------------------------------------
54327
55833
  /** Updates a single card by index (1-based). Throws if not found. */
54328
- async updateCard(workflowId, cardIndex, patch) {
54329
- const record = this.store.get(workflowId);
55834
+ async updateCard(questId, cardIndex, patch) {
55835
+ const record = this.store.get(questId);
54330
55836
  if (!record) {
54331
- throw new Error(`Workflow "${workflowId}" not found`);
55837
+ throw new Error(`Quest "${questId}" not found`);
54332
55838
  }
54333
55839
  const cards = record.cards.map(
54334
- (card) => card.index === cardIndex ? WorkflowCardSchema.parse({ ...card, ...patch }) : card
55840
+ (card) => card.index === cardIndex ? QuestCardSchema.parse({ ...card, ...patch }) : card
54335
55841
  );
54336
55842
  return this.persistAndPublish(record, { cards });
54337
55843
  }
54338
55844
  /** Append a card (used by ralph runner as iterations execute). */
54339
- async appendCard(workflowId, card) {
54340
- const record = this.store.get(workflowId);
55845
+ async appendCard(questId, card) {
55846
+ const record = this.store.get(questId);
54341
55847
  if (!record) {
54342
- throw new Error(`Workflow "${workflowId}" not found`);
55848
+ throw new Error(`Quest "${questId}" not found`);
54343
55849
  }
54344
- const cards = [...record.cards, WorkflowCardSchema.parse(card)];
55850
+ const cards = [...record.cards, QuestCardSchema.parse(card)];
54345
55851
  return this.persistAndPublish(record, { cards });
54346
55852
  }
54347
55853
  /** Set the summarizer card (creates or updates). */
54348
- async updateSummarizer(workflowId, patch) {
54349
- const record = this.store.get(workflowId);
55854
+ async updateSummarizer(questId, patch) {
55855
+ const record = this.store.get(questId);
54350
55856
  if (!record) {
54351
- throw new Error(`Workflow "${workflowId}" not found`);
55857
+ throw new Error(`Quest "${questId}" not found`);
54352
55858
  }
54353
55859
  if (!record.summarizerCard) {
54354
- throw new Error(`Workflow "${workflowId}" has no summarizer card`);
55860
+ throw new Error(`Quest "${questId}" has no summarizer card`);
54355
55861
  }
54356
- const summarizerCard = WorkflowCardSchema.parse({
55862
+ const summarizerCard = QuestCardSchema.parse({
54357
55863
  ...record.summarizerCard,
54358
55864
  ...patch
54359
55865
  });
54360
55866
  return this.persistAndPublish(record, { summarizerCard });
54361
55867
  }
54362
- /** Mark the workflow's terminal status. Idempotent running→stopped is fine. */
54363
- async markStatus(workflowId, status) {
54364
- const record = this.store.get(workflowId);
55868
+ /** Set or replace the queen-bee card on an orchestrate quest. */
55869
+ async setQueenCard(questId, card) {
55870
+ const record = this.store.get(questId);
55871
+ if (!record) {
55872
+ throw new Error(`Quest "${questId}" not found`);
55873
+ }
55874
+ return this.persistAndPublish(record, { queenCard: QuestCardSchema.parse(card) });
55875
+ }
55876
+ /** Patch the queen-bee card. Throws if no queen card exists. */
55877
+ async updateQueenCard(questId, patch) {
55878
+ const record = this.store.get(questId);
55879
+ if (!record) {
55880
+ throw new Error(`Quest "${questId}" not found`);
55881
+ }
55882
+ if (!record.queenCard) {
55883
+ throw new Error(`Quest "${questId}" has no queen card`);
55884
+ }
55885
+ const queenCard = QuestCardSchema.parse({
55886
+ ...record.queenCard,
55887
+ ...patch
55888
+ });
55889
+ return this.persistAndPublish(record, { queenCard });
55890
+ }
55891
+ /** Record the hivemind doc absolute path for an orchestrate quest. */
55892
+ async setHivemindPath(questId, hivemindPath) {
55893
+ const record = this.store.get(questId);
55894
+ if (!record) {
55895
+ throw new Error(`Quest "${questId}" not found`);
55896
+ }
55897
+ return this.persistAndPublish(record, { hivemindPath });
55898
+ }
55899
+ /** Mark the quest's terminal status. Idempotent — running→stopped is fine. */
55900
+ async markStatus(questId, status) {
55901
+ const record = this.store.get(questId);
54365
55902
  if (!record) {
54366
- throw new Error(`Workflow "${workflowId}" not found`);
55903
+ throw new Error(`Quest "${questId}" not found`);
54367
55904
  }
54368
55905
  const completedAt = status === "running" ? null : record.completedAt ?? nowIso2();
54369
55906
  return this.persistAndPublish(record, { status, completedAt });
@@ -54372,7 +55909,7 @@ var WorkflowService = class {
54372
55909
  // Internals
54373
55910
  // -------------------------------------------------------------------------
54374
55911
  async persistAndPublish(record, patch) {
54375
- const updated = WorkflowRecordSchema.parse({
55912
+ const updated = QuestRecordSchema.parse({
54376
55913
  ...record,
54377
55914
  ...patch,
54378
55915
  updatedAt: nowIso2()
@@ -54388,25 +55925,36 @@ var WorkflowService = class {
54388
55925
  } catch (err) {
54389
55926
  this.logger.warn(
54390
55927
  { err: err instanceof Error ? err.message : String(err) },
54391
- "workflow-service: subscriber threw"
55928
+ "quest-service: subscriber threw"
54392
55929
  );
54393
55930
  }
54394
55931
  }
54395
55932
  }
54396
55933
  };
54397
55934
 
54398
- // ../server/src/server/workflow/runner-loop.ts
55935
+ // ../server/src/server/quest/runner-loop.ts
54399
55936
  function nowIso3() {
54400
55937
  return (/* @__PURE__ */ new Date()).toISOString();
54401
55938
  }
54402
- function buildAgentConfig(record, card, titleSuffix) {
55939
+ async function buildAgentConfig(record, card, titleSuffix) {
54403
55940
  const modeId = getProviderFullAccessModeId(record.defaultProvider) ?? void 0;
55941
+ let systemPrompt;
55942
+ let allowedTools;
55943
+ if (card.role) {
55944
+ const role = await resolveRole({ roleName: card.role, workspaceRoot: record.cwd });
55945
+ if (role) {
55946
+ if (role.body) systemPrompt = role.body;
55947
+ if (role.allowedTools.length > 0) allowedTools = role.allowedTools;
55948
+ }
55949
+ }
54404
55950
  return {
54405
55951
  provider: record.defaultProvider,
54406
55952
  cwd: record.cwd,
54407
55953
  model: card.model ?? record.defaultModel ?? void 0,
54408
55954
  modeId,
54409
- title: `workflow ${record.id} ${titleSuffix}`,
55955
+ systemPrompt,
55956
+ allowedTools,
55957
+ title: `quest ${record.id} ${titleSuffix}`,
54410
55958
  internal: true
54411
55959
  };
54412
55960
  }
@@ -54427,21 +55975,21 @@ function resolveFinalTextFromRun(run) {
54427
55975
  }
54428
55976
  var LoopRunner = class {
54429
55977
  constructor(options) {
54430
- this.logger = options.logger.child({ module: "workflow-runner-loop" });
55978
+ this.logger = options.logger.child({ module: "quest-runner-loop" });
54431
55979
  }
54432
55980
  async execute(args) {
54433
- const { workflowId, signal, service } = args;
54434
- const initial = service.getRecord(workflowId);
55981
+ const { questId, signal, service } = args;
55982
+ const initial = service.getRecord(questId);
54435
55983
  if (!initial) {
54436
- throw new Error(`Workflow "${workflowId}" not found`);
55984
+ throw new Error(`Quest "${questId}" not found`);
54437
55985
  }
54438
- if (initial.kind !== "loop") {
54439
- throw new Error(`LoopRunner cannot execute workflow of kind "${initial.kind}"`);
55986
+ if (initial.kind !== "loop" && initial.kind !== "run") {
55987
+ throw new Error(`LoopRunner cannot execute quest of kind "${initial.kind}"`);
54440
55988
  }
54441
55989
  const completedFinalTexts = [];
54442
55990
  let aborted = false;
54443
55991
  for (const card of initial.cards) {
54444
- const current = service.getRecord(workflowId);
55992
+ const current = service.getRecord(questId);
54445
55993
  if (!current || current.status !== "running") {
54446
55994
  aborted = true;
54447
55995
  break;
@@ -54467,32 +56015,32 @@ var LoopRunner = class {
54467
56015
  break;
54468
56016
  }
54469
56017
  if (result.status === "failed") {
54470
- await service.markStatus(workflowId, "failed");
56018
+ await service.markStatus(questId, "failed");
54471
56019
  return;
54472
56020
  }
54473
56021
  }
54474
56022
  if (aborted) {
54475
- const current = service.getRecord(workflowId);
56023
+ const current = service.getRecord(questId);
54476
56024
  if (current) {
54477
56025
  for (const card of current.cards) {
54478
56026
  if (card.status === "queued" || card.status === "running") {
54479
- await service.updateCard(workflowId, card.index, {
56027
+ await service.updateCard(questId, card.index, {
54480
56028
  status: "stopped",
54481
56029
  completedAt: nowIso3()
54482
56030
  });
54483
56031
  }
54484
56032
  }
54485
56033
  if (current.summarizerCard && current.summarizerCard.status === "queued") {
54486
- await service.updateSummarizer(workflowId, {
56034
+ await service.updateSummarizer(questId, {
54487
56035
  status: "stopped",
54488
56036
  completedAt: nowIso3()
54489
56037
  });
54490
56038
  }
54491
56039
  }
54492
- await service.markStatus(workflowId, "stopped");
56040
+ await service.markStatus(questId, "stopped");
54493
56041
  return;
54494
56042
  }
54495
- const beforeSummary = service.getRecord(workflowId);
56043
+ const beforeSummary = service.getRecord(questId);
54496
56044
  if (beforeSummary?.summarizerCard) {
54497
56045
  const summaryPrompt = buildSummaryPrompt(initial.prompt, completedFinalTexts);
54498
56046
  const summaryResult = await this.runSummarizer({
@@ -54502,21 +56050,21 @@ var LoopRunner = class {
54502
56050
  signal
54503
56051
  });
54504
56052
  if (summaryResult.status === "failed") {
54505
- await service.markStatus(workflowId, "failed");
56053
+ await service.markStatus(questId, "failed");
54506
56054
  return;
54507
56055
  }
54508
56056
  if (summaryResult.status === "stopped") {
54509
- await service.markStatus(workflowId, "stopped");
56057
+ await service.markStatus(questId, "stopped");
54510
56058
  return;
54511
56059
  }
54512
56060
  }
54513
- await service.markStatus(workflowId, "succeeded");
56061
+ await service.markStatus(questId, "succeeded");
54514
56062
  }
54515
56063
  async runCard(args) {
54516
56064
  const { record, card, prompt, titleSuffix, service, signal } = args;
54517
56065
  const startedAt = nowIso3();
54518
56066
  const agent = await service.agentManager.createAgent(
54519
- buildAgentConfig(record, card, titleSuffix)
56067
+ await buildAgentConfig(record, card, titleSuffix)
54520
56068
  );
54521
56069
  await service.updateCard(record.id, card.index, {
54522
56070
  status: "running",
@@ -54545,7 +56093,7 @@ var LoopRunner = class {
54545
56093
  return { status: "succeeded", finalText };
54546
56094
  } catch (err) {
54547
56095
  this.logger.error(
54548
- { err, workflowId: record.id, cardIndex: card.index },
56096
+ { err, questId: record.id, cardIndex: card.index },
54549
56097
  "loop-runner: card failed"
54550
56098
  );
54551
56099
  await service.updateCard(record.id, card.index, {
@@ -54569,7 +56117,7 @@ var LoopRunner = class {
54569
56117
  }
54570
56118
  const startedAt = nowIso3();
54571
56119
  const agent = await service.agentManager.createAgent(
54572
- buildAgentConfig(record, summarizer, "summary")
56120
+ await buildAgentConfig(record, summarizer, "summary")
54573
56121
  );
54574
56122
  await service.updateSummarizer(record.id, {
54575
56123
  status: "running",
@@ -54597,10 +56145,7 @@ var LoopRunner = class {
54597
56145
  });
54598
56146
  return { status: "succeeded", finalText };
54599
56147
  } catch (err) {
54600
- this.logger.error(
54601
- { err, workflowId: record.id },
54602
- "loop-runner: summarizer failed"
54603
- );
56148
+ this.logger.error({ err, questId: record.id }, "loop-runner: summarizer failed");
54604
56149
  await service.updateSummarizer(record.id, {
54605
56150
  status: "failed",
54606
56151
  completedAt: nowIso3()
@@ -54620,23 +56165,555 @@ function buildSummaryPrompt(taskPrompt, priorOutputs) {
54620
56165
 
54621
56166
  ${text || "(no output)"}`).join("\n\n");
54622
56167
  return [
54623
- "You are a summarizer. The original task was:",
56168
+ "Summarise in 2 plain english paragraphs that are easily understandable for the user and not technical giberish.",
54624
56169
  "",
54625
- taskPrompt,
56170
+ "Original task:",
54626
56171
  "",
54627
- "Below are the outputs of each preceding card in the workflow.",
54628
- "Produce a concise summary of what was accomplished and any open issues.",
56172
+ taskPrompt,
54629
56173
  "",
54630
56174
  sections
54631
56175
  ].join("\n");
54632
56176
  }
54633
56177
 
54634
- // ../server/src/server/workflow/runner-ralph.ts
56178
+ // ../server/src/server/quest/runner-orchestrator.ts
56179
+ import { appendFile, mkdir as mkdir8, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
56180
+ import path26 from "node:path";
56181
+ function nowIso4() {
56182
+ return (/* @__PURE__ */ new Date()).toISOString();
56183
+ }
56184
+ async function resolveAllRoles(record) {
56185
+ const out = [];
56186
+ for (const card of record.cards) {
56187
+ if (!card.role) {
56188
+ out.push({
56189
+ cardIndex: card.index,
56190
+ roleName: `card-${card.index}`,
56191
+ body: null,
56192
+ allowedTools: []
56193
+ });
56194
+ continue;
56195
+ }
56196
+ const resolved = await resolveRole({ roleName: card.role, workspaceRoot: record.cwd });
56197
+ out.push({
56198
+ cardIndex: card.index,
56199
+ roleName: card.role,
56200
+ body: resolved?.body ?? null,
56201
+ allowedTools: resolved?.allowedTools ?? []
56202
+ });
56203
+ }
56204
+ return out;
56205
+ }
56206
+ function buildQueenSystemPrompt(args) {
56207
+ const roleSections = args.roles.map((r) => {
56208
+ const body = r.body ? r.body.trim() : "(no role body found \u2014 this card has no system prompt)";
56209
+ return [`### Card ${r.cardIndex} \xB7 role: \`${r.roleName}\``, "", body].join("\n");
56210
+ }).join("\n\n---\n\n");
56211
+ return [
56212
+ "You are the QUEEN BEE \u2014 the planner of a multi-agent orchestration.",
56213
+ "",
56214
+ "You do NOT do the work yourself. Your one and only job is to read the",
56215
+ "user's task plus the role roster below and emit ONE JSON plan that",
56216
+ "delegates the work across your team.",
56217
+ "",
56218
+ "A plan is an ORDERED list of waves. Each wave is a SET of role dispatches",
56219
+ "that the runner will execute IN PARALLEL. Wave N+1 starts only after",
56220
+ "every dispatch in wave N has finished.",
56221
+ "",
56222
+ "Use parallelism aggressively. Place roles in the SAME wave whenever they",
56223
+ "do not depend on each other's output. Place a role in a LATER wave only",
56224
+ "when it genuinely needs the result of an earlier wave.",
56225
+ "",
56226
+ "Each role on the roster runs AT MOST ONCE \u2014 do not schedule the same",
56227
+ "role twice anywhere in the plan. You may also OMIT roles that are not",
56228
+ "needed for the user's task.",
56229
+ "",
56230
+ "## Your team",
56231
+ "",
56232
+ "Read every role description carefully so you understand what each worker",
56233
+ "can and cannot do, and what inputs they need.",
56234
+ "",
56235
+ roleSections,
56236
+ "",
56237
+ "## Hivemind doc",
56238
+ "",
56239
+ `The hivemind doc lives at: ${args.hivemindPath}`,
56240
+ "",
56241
+ "After every wave the runner appends each worker's final output to that",
56242
+ "doc, and feeds the hivemind contents inline into the prompts of any",
56243
+ "workers in later waves so they can build on prior results. You do not",
56244
+ "need to think about this \u2014 just write self-contained dispatch prompts.",
56245
+ "",
56246
+ "## Response contract \u2014 STRICT",
56247
+ "",
56248
+ "Respond with EXACTLY one fenced JSON code block and nothing else. No",
56249
+ "prose before or after. The JSON must match this shape:",
56250
+ "",
56251
+ "```json",
56252
+ "{",
56253
+ ' "plan": [',
56254
+ ' { "dispatches": [',
56255
+ ' { "role": "<roleName>", "prompt": "<concrete sub-task>" }',
56256
+ " ]}",
56257
+ " ]",
56258
+ "}",
56259
+ "```",
56260
+ "",
56261
+ "Rules:",
56262
+ "- `role` must match one of the role names listed above EXACTLY.",
56263
+ "- A role appears AT MOST ONCE across the entire plan.",
56264
+ "- `prompt` is a precise, self-contained task for the worker. Cite any",
56265
+ " expected upstream inputs by role name (the runner injects the hivemind",
56266
+ " contents automatically).",
56267
+ "- The plan ends when its last wave finishes. There is no `done` action."
56268
+ ].join("\n");
56269
+ }
56270
+ function extractFencedJson(text) {
56271
+ const fence = /```(?:json)?\s*([\s\S]*?)```/i.exec(text);
56272
+ if (fence?.[1]) return fence[1].trim();
56273
+ const brace = /\{[\s\S]*\}/.exec(text);
56274
+ return brace ? brace[0].trim() : null;
56275
+ }
56276
+ function parseQueenPlan(text, knownRoles) {
56277
+ const block = extractFencedJson(text);
56278
+ if (!block) {
56279
+ return { ok: false, reason: "no JSON block found in queen response" };
56280
+ }
56281
+ let parsed;
56282
+ try {
56283
+ parsed = JSON.parse(block);
56284
+ } catch (err) {
56285
+ return {
56286
+ ok: false,
56287
+ reason: `JSON parse error: ${err instanceof Error ? err.message : String(err)}`
56288
+ };
56289
+ }
56290
+ if (!parsed || typeof parsed !== "object") {
56291
+ return { ok: false, reason: "queen response is not an object" };
56292
+ }
56293
+ const planRaw = parsed.plan;
56294
+ if (!Array.isArray(planRaw) || planRaw.length === 0) {
56295
+ return { ok: false, reason: "plan must be a non-empty array of waves" };
56296
+ }
56297
+ const seenRoles = /* @__PURE__ */ new Set();
56298
+ const waves = [];
56299
+ for (let i = 0; i < planRaw.length; i += 1) {
56300
+ const waveRaw = planRaw[i];
56301
+ if (!waveRaw || typeof waveRaw !== "object") {
56302
+ return { ok: false, reason: `wave ${i + 1} is not an object` };
56303
+ }
56304
+ const dispatchesRaw = waveRaw.dispatches;
56305
+ if (!Array.isArray(dispatchesRaw) || dispatchesRaw.length === 0) {
56306
+ return { ok: false, reason: `wave ${i + 1} has no dispatches` };
56307
+ }
56308
+ const dispatches = [];
56309
+ for (const d of dispatchesRaw) {
56310
+ if (!d || typeof d !== "object" || typeof d.role !== "string" || typeof d.prompt !== "string") {
56311
+ return {
56312
+ ok: false,
56313
+ reason: `wave ${i + 1}: every dispatch needs string \`role\` and string \`prompt\``
56314
+ };
56315
+ }
56316
+ const role = d.role.trim();
56317
+ const prompt = d.prompt;
56318
+ if (!knownRoles.has(role)) {
56319
+ return { ok: false, reason: `wave ${i + 1}: unknown role \`${role}\`` };
56320
+ }
56321
+ if (seenRoles.has(role)) {
56322
+ return {
56323
+ ok: false,
56324
+ reason: `wave ${i + 1}: role \`${role}\` was already scheduled in an earlier wave`
56325
+ };
56326
+ }
56327
+ seenRoles.add(role);
56328
+ dispatches.push({ role, prompt });
56329
+ }
56330
+ waves.push({ dispatches });
56331
+ }
56332
+ return { ok: true, plan: waves };
56333
+ }
56334
+ function resolveFinalTextFromRun2(run) {
56335
+ if (run.finalText.trim()) {
56336
+ return run.finalText;
56337
+ }
56338
+ for (let i = run.timeline.length - 1; i >= 0; i -= 1) {
56339
+ const item = run.timeline[i];
56340
+ if (item && typeof item === "object" && "type" in item && item.type === "assistant_message" && "text" in item && typeof item.text === "string") {
56341
+ const text = item.text;
56342
+ if (text.trim()) {
56343
+ return text;
56344
+ }
56345
+ }
56346
+ }
56347
+ return "";
56348
+ }
56349
+ function resolveHivemindPaths(cwd, questId) {
56350
+ const dir = path26.join(cwd, ".hiveminds", "orchestrator");
56351
+ return {
56352
+ dir,
56353
+ filePath: path26.join(dir, `orchestrator-${questId}.md`)
56354
+ };
56355
+ }
56356
+ async function initializeHivemind(args) {
56357
+ await mkdir8(args.paths.dir, { recursive: true });
56358
+ const roster = args.roles.map((r) => `- card ${r.cardIndex} \xB7 \`${r.roleName}\``).join("\n");
56359
+ const planSummary = args.plan.map(
56360
+ (wave, i) => `**wave ${i + 1}** \u2014 ${wave.dispatches.map((d) => `\`${d.role}\``).join(", ")}`
56361
+ ).join("\n");
56362
+ const body = [
56363
+ `# Orchestrator ${args.record.id}`,
56364
+ "",
56365
+ `- createdAt: ${args.record.createdAt}`,
56366
+ `- prompt: ${args.record.prompt}`,
56367
+ "",
56368
+ "## Roster",
56369
+ "",
56370
+ roster,
56371
+ "",
56372
+ "## Plan",
56373
+ "",
56374
+ planSummary,
56375
+ "",
56376
+ "## Waves",
56377
+ ""
56378
+ ].join("\n");
56379
+ await writeFile7(args.paths.filePath, body, "utf8");
56380
+ }
56381
+ async function appendHivemind(filePath, chunk) {
56382
+ await appendFile(filePath, chunk, "utf8");
56383
+ }
56384
+ async function readHivemind(filePath) {
56385
+ try {
56386
+ return await readFile5(filePath, "utf8");
56387
+ } catch {
56388
+ return "";
56389
+ }
56390
+ }
56391
+ function buildQueenAgentConfig(args) {
56392
+ const modeId = getProviderFullAccessModeId(args.record.defaultProvider) ?? void 0;
56393
+ return {
56394
+ provider: args.record.defaultProvider,
56395
+ cwd: args.record.cwd,
56396
+ model: args.record.defaultModel ?? void 0,
56397
+ modeId,
56398
+ systemPrompt: args.systemPrompt,
56399
+ title: `quest ${args.record.id} queen`,
56400
+ internal: true
56401
+ };
56402
+ }
56403
+ function buildWorkerAgentConfig(args) {
56404
+ const modeId = getProviderFullAccessModeId(args.record.defaultProvider) ?? void 0;
56405
+ return {
56406
+ provider: args.record.defaultProvider,
56407
+ cwd: args.record.cwd,
56408
+ model: args.card.model ?? args.record.defaultModel ?? void 0,
56409
+ modeId,
56410
+ systemPrompt: args.roleBody ?? void 0,
56411
+ allowedTools: args.allowedTools.length > 0 ? args.allowedTools : void 0,
56412
+ title: `quest ${args.record.id} card ${args.card.index} ${args.card.role ?? ""}`.trim(),
56413
+ internal: true
56414
+ };
56415
+ }
56416
+ function buildWorkerPrompt(args) {
56417
+ if (args.isFirstWave) {
56418
+ return args.dispatchPrompt;
56419
+ }
56420
+ return [
56421
+ args.dispatchPrompt,
56422
+ "",
56423
+ "---",
56424
+ "",
56425
+ "## Hivemind context (outputs from earlier waves)",
56426
+ "",
56427
+ args.hivemindMarkdown
56428
+ ].join("\n");
56429
+ }
56430
+ var OrchestratorRunner = class {
56431
+ constructor(options) {
56432
+ this.logger = options.logger.child({ module: "quest-runner-orchestrator" });
56433
+ }
56434
+ async execute(args) {
56435
+ const { questId, signal, service } = args;
56436
+ const initial = service.getRecord(questId);
56437
+ if (!initial) {
56438
+ throw new Error(`Quest "${questId}" not found`);
56439
+ }
56440
+ if (initial.kind !== "orchestrate") {
56441
+ throw new Error(`OrchestratorRunner cannot execute quest of kind "${initial.kind}"`);
56442
+ }
56443
+ if (initial.termination.type !== "orchestrate") {
56444
+ throw new Error('OrchestratorRunner expects termination.type === "orchestrate"');
56445
+ }
56446
+ if (initial.cards.length === 0) {
56447
+ await service.markStatus(questId, "failed");
56448
+ throw new Error("Orchestrate quest requires at least one role card");
56449
+ }
56450
+ const hivemindPaths = resolveHivemindPaths(initial.cwd, initial.id);
56451
+ const resolvedRoles = await resolveAllRoles(initial);
56452
+ const knownRoles = new Set(resolvedRoles.map((r) => r.roleName));
56453
+ const cardIndexByRole = /* @__PURE__ */ new Map();
56454
+ for (const r of resolvedRoles) {
56455
+ if (!cardIndexByRole.has(r.roleName)) {
56456
+ cardIndexByRole.set(r.roleName, r.cardIndex);
56457
+ }
56458
+ }
56459
+ await service.setHivemindPath(questId, hivemindPaths.filePath);
56460
+ const queenSystemPrompt = buildQueenSystemPrompt({
56461
+ roles: resolvedRoles,
56462
+ hivemindPath: hivemindPaths.filePath
56463
+ });
56464
+ const queenStartedAt = nowIso4();
56465
+ const queenAgent = await service.agentManager.createAgent(
56466
+ buildQueenAgentConfig({ record: initial, systemPrompt: queenSystemPrompt })
56467
+ );
56468
+ const queenCard = QuestCardSchema.parse({
56469
+ index: 0,
56470
+ role: "queen",
56471
+ model: initial.defaultModel,
56472
+ prompt: null,
56473
+ wiring: { inputs: "user-prompt", outputs: "next-card" },
56474
+ status: "running",
56475
+ agentId: queenAgent.id,
56476
+ startedAt: queenStartedAt,
56477
+ completedAt: null,
56478
+ verify: null
56479
+ });
56480
+ await service.setQueenCard(questId, queenCard);
56481
+ const onAbort = () => {
56482
+ void service.agentManager.cancelAgentRun(queenAgent.id).catch(() => {
56483
+ });
56484
+ };
56485
+ signal.addEventListener("abort", onAbort);
56486
+ let plan;
56487
+ try {
56488
+ const queenRun = await service.agentManager.runAgent(queenAgent.id, initial.prompt);
56489
+ if (queenRun.canceled || signal.aborted) {
56490
+ await service.updateQueenCard(questId, {
56491
+ status: "stopped",
56492
+ completedAt: nowIso4()
56493
+ });
56494
+ await this.markRemainingCardsStopped(questId, service);
56495
+ await service.markStatus(questId, "stopped");
56496
+ return;
56497
+ }
56498
+ const queenText = resolveFinalTextFromRun2(queenRun);
56499
+ const result = parseQueenPlan(queenText, knownRoles);
56500
+ if (!result.ok) {
56501
+ await mkdir8(hivemindPaths.dir, { recursive: true });
56502
+ await writeFile7(
56503
+ hivemindPaths.filePath,
56504
+ [
56505
+ `# Orchestrator ${initial.id}`,
56506
+ "",
56507
+ `- createdAt: ${initial.createdAt}`,
56508
+ `- prompt: ${initial.prompt}`,
56509
+ "",
56510
+ "## Plan parse failed",
56511
+ "",
56512
+ `Reason: ${result.reason}`,
56513
+ "",
56514
+ "### Queen response",
56515
+ "",
56516
+ "```",
56517
+ queenText.slice(0, 4e3),
56518
+ "```",
56519
+ ""
56520
+ ].join("\n"),
56521
+ "utf8"
56522
+ );
56523
+ await service.updateQueenCard(questId, {
56524
+ status: "failed",
56525
+ completedAt: nowIso4()
56526
+ });
56527
+ await this.markRemainingCardsStopped(questId, service);
56528
+ await service.markStatus(questId, "failed");
56529
+ return;
56530
+ }
56531
+ plan = result.plan;
56532
+ } catch (err) {
56533
+ this.logger.error({ err, questId }, "orchestrator: queen errored");
56534
+ await service.updateQueenCard(questId, {
56535
+ status: "failed",
56536
+ completedAt: nowIso4()
56537
+ });
56538
+ await this.markRemainingCardsStopped(questId, service);
56539
+ await service.markStatus(questId, "failed");
56540
+ return;
56541
+ } finally {
56542
+ signal.removeEventListener("abort", onAbort);
56543
+ }
56544
+ await service.updateQueenCard(questId, {
56545
+ status: "succeeded",
56546
+ completedAt: nowIso4()
56547
+ });
56548
+ await initializeHivemind({
56549
+ paths: hivemindPaths,
56550
+ record: initial,
56551
+ roles: resolvedRoles,
56552
+ plan
56553
+ });
56554
+ const scheduledRoles = /* @__PURE__ */ new Set();
56555
+ for (const wave of plan) {
56556
+ for (const d of wave.dispatches) scheduledRoles.add(d.role);
56557
+ }
56558
+ for (const card of initial.cards) {
56559
+ if (card.role && !scheduledRoles.has(card.role) && card.status === "queued") {
56560
+ await service.updateCard(questId, card.index, {
56561
+ status: "stopped",
56562
+ completedAt: nowIso4()
56563
+ });
56564
+ }
56565
+ }
56566
+ let aborted = false;
56567
+ let anyWaveFailed = false;
56568
+ for (let i = 0; i < plan.length; i += 1) {
56569
+ const wave = plan[i];
56570
+ const waveNumber = i + 1;
56571
+ const current = service.getRecord(questId);
56572
+ if (!current || current.status !== "running" || signal.aborted) {
56573
+ aborted = true;
56574
+ break;
56575
+ }
56576
+ const waveHeader = [
56577
+ `
56578
+ ### wave ${waveNumber} \u2014 dispatch`,
56579
+ "",
56580
+ ...wave.dispatches.map(
56581
+ (d) => `- \`${d.role}\` \u2190 ${d.prompt.replace(/\s+/g, " ").slice(0, 200)}`
56582
+ ),
56583
+ ""
56584
+ ].join("\n");
56585
+ await appendHivemind(hivemindPaths.filePath, waveHeader);
56586
+ const hivemindMarkdown = await readHivemind(hivemindPaths.filePath);
56587
+ const outcomes = await Promise.all(
56588
+ wave.dispatches.map((dispatch) => {
56589
+ const cardIndex = cardIndexByRole.get(dispatch.role);
56590
+ const role = resolvedRoles.find((r) => r.cardIndex === cardIndex);
56591
+ return this.runWorker({
56592
+ record: initial,
56593
+ cardIndex,
56594
+ workerPrompt: buildWorkerPrompt({
56595
+ dispatchPrompt: dispatch.prompt,
56596
+ hivemindMarkdown,
56597
+ isFirstWave: i === 0
56598
+ }),
56599
+ roleBody: role?.body ?? null,
56600
+ allowedTools: role?.allowedTools ?? [],
56601
+ service,
56602
+ signal
56603
+ });
56604
+ })
56605
+ );
56606
+ for (const outcome of outcomes) {
56607
+ const heading = `
56608
+ #### card ${outcome.card.index} \xB7 \`${outcome.card.role ?? "(no role)"}\` \u2014 ${outcome.status}
56609
+
56610
+ `;
56611
+ const bodyText = outcome.status === "succeeded" ? outcome.finalText.trim() || "_(empty output)_" : `_(${outcome.errorMessage ?? outcome.status})_`;
56612
+ await appendHivemind(hivemindPaths.filePath, heading + bodyText + "\n");
56613
+ }
56614
+ if (outcomes.some((o) => o.status === "stopped") || signal.aborted) {
56615
+ aborted = true;
56616
+ break;
56617
+ }
56618
+ if (outcomes.some((o) => o.status === "failed")) {
56619
+ anyWaveFailed = true;
56620
+ }
56621
+ }
56622
+ if (aborted) {
56623
+ await this.markRemainingCardsStopped(questId, service);
56624
+ await service.markStatus(questId, "stopped");
56625
+ return;
56626
+ }
56627
+ await service.markStatus(questId, anyWaveFailed ? "failed" : "succeeded");
56628
+ }
56629
+ async markRemainingCardsStopped(questId, service) {
56630
+ const current = service.getRecord(questId);
56631
+ if (!current) return;
56632
+ for (const card of current.cards) {
56633
+ if (card.status === "queued" || card.status === "running") {
56634
+ await service.updateCard(questId, card.index, {
56635
+ status: "stopped",
56636
+ completedAt: nowIso4()
56637
+ });
56638
+ }
56639
+ }
56640
+ }
56641
+ async runWorker(args) {
56642
+ const { record, cardIndex, workerPrompt, roleBody, allowedTools, service, signal } = args;
56643
+ const fresh = service.getRecord(record.id);
56644
+ const card = fresh?.cards.find((c) => c.index === cardIndex) ?? record.cards.find((c) => c.index === cardIndex);
56645
+ if (!card) {
56646
+ return {
56647
+ card: record.cards[0],
56648
+ status: "failed",
56649
+ finalText: "",
56650
+ errorMessage: `card ${cardIndex} not found`
56651
+ };
56652
+ }
56653
+ const startedAt = nowIso4();
56654
+ let agent;
56655
+ try {
56656
+ agent = await service.agentManager.createAgent(
56657
+ buildWorkerAgentConfig({ record, card, roleBody, allowedTools })
56658
+ );
56659
+ } catch (err) {
56660
+ const msg = err instanceof Error ? err.message : String(err);
56661
+ this.logger.error({ err, questId: record.id, cardIndex }, "orchestrator: spawn failed");
56662
+ await service.updateCard(record.id, cardIndex, {
56663
+ status: "failed",
56664
+ completedAt: nowIso4(),
56665
+ prompt: workerPrompt
56666
+ });
56667
+ return { card, status: "failed", finalText: "", errorMessage: msg };
56668
+ }
56669
+ await service.updateCard(record.id, cardIndex, {
56670
+ status: "running",
56671
+ agentId: agent.id,
56672
+ startedAt,
56673
+ prompt: workerPrompt
56674
+ });
56675
+ const onAbort = () => {
56676
+ void service.agentManager.cancelAgentRun(agent.id).catch(() => {
56677
+ });
56678
+ };
56679
+ signal.addEventListener("abort", onAbort, { once: true });
56680
+ try {
56681
+ const run = await service.agentManager.runAgent(agent.id, workerPrompt);
56682
+ const finalText = resolveFinalTextFromRun2(run);
56683
+ if (run.canceled) {
56684
+ await service.updateCard(record.id, cardIndex, {
56685
+ status: "stopped",
56686
+ completedAt: nowIso4()
56687
+ });
56688
+ return { card, status: "stopped", finalText, errorMessage: null };
56689
+ }
56690
+ await service.updateCard(record.id, cardIndex, {
56691
+ status: "succeeded",
56692
+ completedAt: nowIso4()
56693
+ });
56694
+ return { card, status: "succeeded", finalText, errorMessage: null };
56695
+ } catch (err) {
56696
+ const msg = err instanceof Error ? err.message : String(err);
56697
+ this.logger.error({ err, questId: record.id, cardIndex }, "orchestrator: worker failed");
56698
+ await service.updateCard(record.id, cardIndex, {
56699
+ status: "failed",
56700
+ completedAt: nowIso4()
56701
+ });
56702
+ return { card, status: "failed", finalText: "", errorMessage: msg };
56703
+ } finally {
56704
+ signal.removeEventListener("abort", onAbort);
56705
+ }
56706
+ }
56707
+ };
56708
+
56709
+ // ../server/src/server/quest/runner-ralph.ts
54635
56710
  import { promisify as promisify4 } from "node:util";
54636
56711
  import { execFile as execFile3 } from "node:child_process";
56712
+ import { appendFile as appendFile2, mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
56713
+ import path27 from "node:path";
54637
56714
  var execFileAsync3 = promisify4(execFile3);
54638
56715
  var MAX_VERIFY_OUTPUT_BYTES2 = 64 * 1024;
54639
- function nowIso4() {
56716
+ function nowIso5() {
54640
56717
  return (/* @__PURE__ */ new Date()).toISOString();
54641
56718
  }
54642
56719
  function platformShell2() {
@@ -54672,12 +56749,82 @@ function buildWorkerConfig(record, iteration) {
54672
56749
  cwd: record.cwd,
54673
56750
  model: record.defaultModel ?? void 0,
54674
56751
  modeId,
54675
- title: `workflow ${record.id} ralph #${iteration}`,
56752
+ title: `quest ${record.id} ralph #${iteration}`,
54676
56753
  internal: true
54677
56754
  };
54678
56755
  }
56756
+ function buildRalphIterationPrompt(args) {
56757
+ const checks = args.verifyChecks.length > 0 ? args.verifyChecks.map((c) => `- ${c}`).join("\n") : "- (none)";
56758
+ return [
56759
+ args.basePrompt,
56760
+ "",
56761
+ "Ralph loop completion contract (strict):",
56762
+ `- You may only declare completion when all verify checks pass:`,
56763
+ checks,
56764
+ `- When complete, output this exact token verbatim: ${args.completionPromise}`,
56765
+ "- If not complete yet, continue making changes and do not output the token."
56766
+ ].join("\n");
56767
+ }
56768
+ function resolveFinalTextFromRun3(run) {
56769
+ if (run.finalText.trim()) {
56770
+ return run.finalText;
56771
+ }
56772
+ for (let i = run.timeline.length - 1; i >= 0; i -= 1) {
56773
+ const item = run.timeline[i];
56774
+ if (item && typeof item === "object" && "type" in item && item.type === "assistant_message" && "text" in item && typeof item.text === "string") {
56775
+ const text = item.text;
56776
+ if (text.trim()) {
56777
+ return text;
56778
+ }
56779
+ }
56780
+ }
56781
+ return "";
56782
+ }
56783
+ function runContainsPromise(run, completionPromise) {
56784
+ const finalText = resolveFinalTextFromRun3(run);
56785
+ return finalText.includes(completionPromise);
56786
+ }
56787
+ function resolveRalphStatePaths(cwd) {
56788
+ const dir = path27.join(cwd, ".hiveminds", "ralph loop");
56789
+ return {
56790
+ dir,
56791
+ logPath: ""
56792
+ };
56793
+ }
56794
+ async function initializeRalphState(args) {
56795
+ const base = resolveRalphStatePaths(args.record.cwd);
56796
+ const paths = {
56797
+ dir: base.dir,
56798
+ logPath: path27.join(base.dir, `ralphloop_${args.record.id}.md`)
56799
+ };
56800
+ await mkdir9(paths.dir, { recursive: true });
56801
+ await writeFile8(
56802
+ paths.logPath,
56803
+ [
56804
+ `# Ralph Loop ${args.record.id}`,
56805
+ "",
56806
+ `- createdAt: ${args.record.createdAt}`,
56807
+ `- prompt: ${args.record.prompt}`,
56808
+ `- completionPromise: ${args.completionPromise}`,
56809
+ `- maxIterations: ${args.maxIterations}`,
56810
+ "",
56811
+ "## Verify Checks",
56812
+ ...args.verifyChecks.length > 0 ? args.verifyChecks.map((check) => `- ${check}`) : ["- (none)"],
56813
+ "",
56814
+ "## Progress"
56815
+ ].join("\n") + "\n",
56816
+ "utf8"
56817
+ );
56818
+ await appendFile2(paths.logPath, `- [${nowIso5()}] started
56819
+ `, "utf8");
56820
+ return paths;
56821
+ }
56822
+ async function appendRalphProgress(paths, message) {
56823
+ await appendFile2(paths.logPath, `- [${nowIso5()}] ${message}
56824
+ `, "utf8");
56825
+ }
54679
56826
  function newCard(index) {
54680
- return WorkflowCardSchema.parse({
56827
+ return QuestCardSchema.parse({
54681
56828
  index,
54682
56829
  role: null,
54683
56830
  model: null,
@@ -54698,46 +56845,51 @@ function newCard(index) {
54698
56845
  }
54699
56846
  var RalphRunner = class {
54700
56847
  constructor(options) {
54701
- this.logger = options.logger.child({ module: "workflow-runner-ralph" });
56848
+ this.logger = options.logger.child({ module: "quest-runner-ralph" });
54702
56849
  }
54703
56850
  async execute(args) {
54704
- const { workflowId, signal, service } = args;
54705
- const initial = service.getRecord(workflowId);
56851
+ const { questId, signal, service } = args;
56852
+ const initial = service.getRecord(questId);
54706
56853
  if (!initial) {
54707
- throw new Error(`Workflow "${workflowId}" not found`);
56854
+ throw new Error(`Quest "${questId}" not found`);
54708
56855
  }
54709
56856
  if (initial.kind !== "ralph") {
54710
- throw new Error(`RalphRunner cannot execute workflow of kind "${initial.kind}"`);
56857
+ throw new Error(`RalphRunner cannot execute quest of kind "${initial.kind}"`);
54711
56858
  }
54712
56859
  if (initial.termination.type !== "verify") {
54713
56860
  throw new Error(`RalphRunner expects termination.type === "verify"`);
54714
56861
  }
54715
- const { verifyPrompt, verifyChecks, maxIterations } = initial.termination;
56862
+ const { verifyPrompt, verifyChecks, completionPromise, maxIterations } = initial.termination;
56863
+ const statePaths = await initializeRalphState({
56864
+ record: initial,
56865
+ verifyChecks,
56866
+ completionPromise,
56867
+ maxIterations
56868
+ });
54716
56869
  if (verifyPrompt) {
54717
56870
  this.logger.warn(
54718
- { workflowId },
56871
+ { questId },
54719
56872
  "ralph-runner: verifyPrompt is set but verifier-agent support is not yet implemented in v1 \u2014 relying on verifyChecks only"
54720
56873
  );
54721
56874
  }
54722
56875
  if (verifyChecks.length === 0) {
54723
- await service.markStatus(workflowId, "failed");
54724
- throw new Error(
54725
- "Ralph workflow requires at least one verifyCheck (verifier-agent path is v2)"
54726
- );
56876
+ await service.markStatus(questId, "failed");
56877
+ throw new Error("Ralph quest requires at least one verifyCheck (verifier-agent path is v2)");
54727
56878
  }
54728
56879
  for (let iteration = 1; iteration <= maxIterations; iteration += 1) {
54729
- const current = service.getRecord(workflowId);
56880
+ const current = service.getRecord(questId);
54730
56881
  if (!current || current.status !== "running" || signal.aborted) {
54731
- await service.markStatus(workflowId, "stopped");
56882
+ await service.markStatus(questId, "stopped");
54732
56883
  return;
54733
56884
  }
54734
56885
  const card = newCard(iteration);
54735
- let recordWithCard = await service.appendCard(workflowId, card);
54736
- const startedAt = nowIso4();
56886
+ let recordWithCard = await service.appendCard(questId, card);
56887
+ await appendRalphProgress(statePaths, `iteration=${iteration} status=started`);
56888
+ const startedAt = nowIso5();
54737
56889
  const agent = await service.agentManager.createAgent(
54738
56890
  buildWorkerConfig(recordWithCard, iteration)
54739
56891
  );
54740
- await service.updateCard(workflowId, iteration, {
56892
+ await service.updateCard(questId, iteration, {
54741
56893
  status: "running",
54742
56894
  agentId: agent.id,
54743
56895
  startedAt
@@ -54748,25 +56900,39 @@ var RalphRunner = class {
54748
56900
  };
54749
56901
  signal.addEventListener("abort", onAbort, { once: true });
54750
56902
  let workerOk = false;
56903
+ let workerRun = null;
54751
56904
  try {
54752
- const run = await service.agentManager.runAgent(agent.id, recordWithCard.prompt);
56905
+ const run = await service.agentManager.runAgent(
56906
+ agent.id,
56907
+ buildRalphIterationPrompt({
56908
+ basePrompt: recordWithCard.prompt,
56909
+ completionPromise,
56910
+ verifyChecks
56911
+ })
56912
+ );
54753
56913
  if (run.canceled) {
54754
- await service.updateCard(workflowId, iteration, {
56914
+ await appendRalphProgress(
56915
+ statePaths,
56916
+ `iteration=${iteration} status=stopped reason=canceled`
56917
+ );
56918
+ await service.updateCard(questId, iteration, {
54755
56919
  status: "stopped",
54756
- completedAt: nowIso4()
56920
+ completedAt: nowIso5()
54757
56921
  });
54758
- await service.markStatus(workflowId, "stopped");
56922
+ await service.markStatus(questId, "stopped");
54759
56923
  return;
54760
56924
  }
56925
+ workerRun = run;
54761
56926
  workerOk = true;
54762
56927
  } catch (err) {
54763
- this.logger.error(
54764
- { err, workflowId, iteration },
54765
- "ralph-runner: worker errored"
56928
+ await appendRalphProgress(
56929
+ statePaths,
56930
+ `iteration=${iteration} status=failed reason=worker_error`
54766
56931
  );
54767
- await service.updateCard(workflowId, iteration, {
56932
+ this.logger.error({ err, questId, iteration }, "ralph-runner: worker errored");
56933
+ await service.updateCard(questId, iteration, {
54768
56934
  status: "failed",
54769
- completedAt: nowIso4()
56935
+ completedAt: nowIso5()
54770
56936
  });
54771
56937
  } finally {
54772
56938
  signal.removeEventListener("abort", onAbort);
@@ -54778,12 +56944,30 @@ var RalphRunner = class {
54778
56944
  if (!workerOk) {
54779
56945
  continue;
54780
56946
  }
54781
- const verifyStartedAt = nowIso4();
56947
+ if (!workerRun || !runContainsPromise(workerRun, completionPromise)) {
56948
+ await appendRalphProgress(
56949
+ statePaths,
56950
+ `iteration=${iteration} status=failed reason=missing_completion_promise`
56951
+ );
56952
+ await service.updateCard(questId, iteration, {
56953
+ status: "failed",
56954
+ completedAt: nowIso5(),
56955
+ verify: {
56956
+ verifierAgentId: null,
56957
+ passed: false,
56958
+ reason: `missing completion promise: ${completionPromise}`,
56959
+ startedAt: null,
56960
+ completedAt: nowIso5()
56961
+ }
56962
+ });
56963
+ continue;
56964
+ }
56965
+ const verifyStartedAt = nowIso5();
54782
56966
  let allPassed = true;
54783
56967
  const reasons = [];
54784
56968
  for (const command of verifyChecks) {
54785
56969
  if (signal.aborted) {
54786
- await service.markStatus(workflowId, "stopped");
56970
+ await service.markStatus(questId, "stopped");
54787
56971
  return;
54788
56972
  }
54789
56973
  const outcome = await runVerifyCheck2({
@@ -54796,10 +56980,10 @@ var RalphRunner = class {
54796
56980
  break;
54797
56981
  }
54798
56982
  }
54799
- const verifyCompletedAt = nowIso4();
54800
- await service.updateCard(workflowId, iteration, {
56983
+ const verifyCompletedAt = nowIso5();
56984
+ await service.updateCard(questId, iteration, {
54801
56985
  status: allPassed ? "succeeded" : "failed",
54802
- completedAt: nowIso4(),
56986
+ completedAt: nowIso5(),
54803
56987
  verify: {
54804
56988
  verifierAgentId: null,
54805
56989
  passed: allPassed,
@@ -54809,12 +56993,18 @@ var RalphRunner = class {
54809
56993
  }
54810
56994
  });
54811
56995
  if (allPassed) {
54812
- await service.markStatus(workflowId, "succeeded");
56996
+ await appendRalphProgress(statePaths, `iteration=${iteration} status=succeeded`);
56997
+ await service.markStatus(questId, "succeeded");
54813
56998
  return;
54814
56999
  }
54815
- recordWithCard = service.getRecord(workflowId) ?? recordWithCard;
57000
+ await appendRalphProgress(
57001
+ statePaths,
57002
+ `iteration=${iteration} status=failed reason=verify_checks`
57003
+ );
57004
+ recordWithCard = service.getRecord(questId) ?? recordWithCard;
54816
57005
  }
54817
- await service.markStatus(workflowId, "failed");
57006
+ await appendRalphProgress(statePaths, "quest status=failed reason=max_iterations_exhausted");
57007
+ await service.markStatus(questId, "failed");
54818
57008
  }
54819
57009
  };
54820
57010
 
@@ -54840,8 +57030,8 @@ function deepMerge(current, patch) {
54840
57030
  }
54841
57031
  return next;
54842
57032
  }
54843
- function getValueAtPath(config, path30) {
54844
- return path30.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, config);
57033
+ function getValueAtPath(config, path33) {
57034
+ return path33.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, config);
54845
57035
  }
54846
57036
  function isEqualValue(a, b) {
54847
57037
  return JSON.stringify(a) === JSON.stringify(b);
@@ -54860,20 +57050,20 @@ var DaemonConfigStore = class {
54860
57050
  patch(partial) {
54861
57051
  const parsedPatch = MutableDaemonConfigPatchSchema.parse(partial);
54862
57052
  const next = MutableDaemonConfigSchema.parse(deepMerge(this.current, parsedPatch));
54863
- const changedFieldPaths = Array.from(this.fieldChangeHandlers.keys()).filter((path30) => {
54864
- return !isEqualValue(getValueAtPath(this.current, path30), getValueAtPath(next, path30));
57053
+ const changedFieldPaths = Array.from(this.fieldChangeHandlers.keys()).filter((path33) => {
57054
+ return !isEqualValue(getValueAtPath(this.current, path33), getValueAtPath(next, path33));
54865
57055
  });
54866
57056
  if (changedFieldPaths.length === 0 && isEqualValue(this.current, next)) {
54867
57057
  return this.current;
54868
57058
  }
54869
57059
  this.persistConfig(next);
54870
57060
  this.current = next;
54871
- for (const path30 of changedFieldPaths) {
54872
- const handlers = this.fieldChangeHandlers.get(path30);
57061
+ for (const path33 of changedFieldPaths) {
57062
+ const handlers = this.fieldChangeHandlers.get(path33);
54873
57063
  if (!handlers) {
54874
57064
  continue;
54875
57065
  }
54876
- const value = getValueAtPath(next, path30);
57066
+ const value = getValueAtPath(next, path33);
54877
57067
  for (const handler of handlers) {
54878
57068
  handler(value);
54879
57069
  }
@@ -54883,18 +57073,18 @@ var DaemonConfigStore = class {
54883
57073
  }
54884
57074
  return next;
54885
57075
  }
54886
- onFieldChange(path30, handler) {
54887
- const handlers = this.fieldChangeHandlers.get(path30) ?? /* @__PURE__ */ new Set();
57076
+ onFieldChange(path33, handler) {
57077
+ const handlers = this.fieldChangeHandlers.get(path33) ?? /* @__PURE__ */ new Set();
54888
57078
  handlers.add(handler);
54889
- this.fieldChangeHandlers.set(path30, handlers);
57079
+ this.fieldChangeHandlers.set(path33, handlers);
54890
57080
  return () => {
54891
- const currentHandlers = this.fieldChangeHandlers.get(path30);
57081
+ const currentHandlers = this.fieldChangeHandlers.get(path33);
54892
57082
  if (!currentHandlers) {
54893
57083
  return;
54894
57084
  }
54895
57085
  currentHandlers.delete(handler);
54896
57086
  if (currentHandlers.size === 0) {
54897
- this.fieldChangeHandlers.delete(path30);
57087
+ this.fieldChangeHandlers.delete(path33);
54898
57088
  }
54899
57089
  };
54900
57090
  }
@@ -54934,7 +57124,7 @@ function mergeMutableConfigIntoPersistedConfig(params) {
54934
57124
 
54935
57125
  // ../server/src/server/workspace-git-service.ts
54936
57126
  import { watch } from "node:fs";
54937
- import { readFile as readFile5, readdir as readdir5 } from "node:fs/promises";
57127
+ import { readFile as readFile6, readdir as readdir5 } from "node:fs/promises";
54938
57128
  import { join as join17, resolve as resolve12 } from "node:path";
54939
57129
  var WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
54940
57130
  var BACKGROUND_GIT_FETCH_INTERVAL_MS = 18e4;
@@ -55164,7 +57354,7 @@ var WorkspaceGitServiceImpl = class {
55164
57354
  }
55165
57355
  async resolveWorkspaceGitRefsRoot(gitDir) {
55166
57356
  try {
55167
- const commonDir = (await readFile5(join17(gitDir, "commondir"), "utf8")).trim();
57357
+ const commonDir = (await readFile6(join17(gitDir, "commondir"), "utf8")).trim();
55168
57358
  if (commonDir.length > 0) {
55169
57359
  return resolve12(gitDir, commonDir);
55170
57360
  }
@@ -56391,7 +58581,7 @@ function createAuthServerClient(options) {
56391
58581
 
56392
58582
  // ../server/src/server/package-version.ts
56393
58583
  import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
56394
- import path25 from "node:path";
58584
+ import path28 from "node:path";
56395
58585
  import { fileURLToPath as fileURLToPath3 } from "node:url";
56396
58586
  var PackageVersionResolutionError = class extends Error {
56397
58587
  constructor(params) {
@@ -56401,9 +58591,9 @@ var PackageVersionResolutionError = class extends Error {
56401
58591
  };
56402
58592
  function resolvePackageVersion(params) {
56403
58593
  const moduleUrl = params.moduleUrl ?? import.meta.url;
56404
- let currentDir = path25.dirname(fileURLToPath3(moduleUrl));
58594
+ let currentDir = path28.dirname(fileURLToPath3(moduleUrl));
56405
58595
  while (true) {
56406
- const packageJsonPath = path25.join(currentDir, "package.json");
58596
+ const packageJsonPath = path28.join(currentDir, "package.json");
56407
58597
  if (existsSync17(packageJsonPath)) {
56408
58598
  try {
56409
58599
  const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf8"));
@@ -56422,7 +58612,7 @@ function resolvePackageVersion(params) {
56422
58612
  }
56423
58613
  }
56424
58614
  }
56425
- const parentDir = path25.dirname(currentDir);
58615
+ const parentDir = path28.dirname(currentDir);
56426
58616
  if (parentDir === currentDir) {
56427
58617
  break;
56428
58618
  }
@@ -56925,11 +59115,11 @@ async function createAppostleDaemon(config, rootLogger) {
56925
59115
  httpServer.on("upgrade", scriptProxyUpgradeHandler);
56926
59116
  const agentStorage = new AgentStorage(config.agentStoragePath, logger);
56927
59117
  const projectRegistry = new FileBackedProjectRegistry(
56928
- path26.join(config.appostleHome, "projects", "projects.json"),
59118
+ path29.join(config.appostleHome, "projects", "projects.json"),
56929
59119
  logger
56930
59120
  );
56931
59121
  workspaceRegistry = new FileBackedWorkspaceRegistry(
56932
- path26.join(config.appostleHome, "projects", "workspaces.json"),
59122
+ path29.join(config.appostleHome, "projects", "workspaces.json"),
56933
59123
  logger
56934
59124
  );
56935
59125
  const chatService = new FileBackedChatService({
@@ -56991,15 +59181,17 @@ async function createAppostleDaemon(config, rootLogger) {
56991
59181
  });
56992
59182
  await loopService.initialize();
56993
59183
  logger.info({ elapsed: elapsed() }, "Loop service initialized");
56994
- const workflowService = new WorkflowService({
59184
+ const questService = new QuestService({
56995
59185
  appostleHome: config.appostleHome,
56996
59186
  logger,
56997
59187
  agentManager
56998
59188
  });
56999
- workflowService.registerRunner("loop", new LoopRunner({ logger }));
57000
- workflowService.registerRunner("ralph", new RalphRunner({ logger }));
57001
- await workflowService.initialize();
57002
- logger.info({ elapsed: elapsed() }, "Workflow service initialized");
59189
+ questService.registerRunner("loop", new LoopRunner({ logger }));
59190
+ questService.registerRunner("ralph", new RalphRunner({ logger }));
59191
+ questService.registerRunner("run", new LoopRunner({ logger }));
59192
+ questService.registerRunner("orchestrate", new OrchestratorRunner({ logger }));
59193
+ await questService.initialize();
59194
+ logger.info({ elapsed: elapsed() }, "Quest service initialized");
57003
59195
  const scheduleService = new ScheduleService({
57004
59196
  appostleHome: config.appostleHome,
57005
59197
  logger,
@@ -57225,7 +59417,7 @@ async function createAppostleDaemon(config, rootLogger) {
57225
59417
  workspaceRegistry,
57226
59418
  chatService,
57227
59419
  loopService,
57228
- workflowService,
59420
+ questService,
57229
59421
  scheduleService,
57230
59422
  checkoutDiffManager,
57231
59423
  scriptRouteStore,
@@ -57333,16 +59525,16 @@ async function closeAllAgents(logger, agentManager) {
57333
59525
  }
57334
59526
 
57335
59527
  // ../server/src/server/config.ts
57336
- import path28 from "node:path";
59528
+ import path31 from "node:path";
57337
59529
  import { z as z45 } from "zod";
57338
59530
 
57339
59531
  // ../server/src/server/speech/speech-config-resolver.ts
57340
59532
  import { z as z44 } from "zod";
57341
59533
 
57342
59534
  // ../server/src/server/speech/providers/local/config.ts
57343
- import path27 from "node:path";
59535
+ import path30 from "node:path";
57344
59536
  import { z as z42 } from "zod";
57345
- var DEFAULT_LOCAL_MODELS_SUBDIR = path27.join("models", "local-speech");
59537
+ var DEFAULT_LOCAL_MODELS_SUBDIR = path30.join("models", "local-speech");
57346
59538
  var NumberLikeSchema2 = z42.union([z42.number(), z42.string().trim().min(1)]);
57347
59539
  var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z42.coerce.number().finite()).optional();
57348
59540
  var OptionalIntegerSchema = NumberLikeSchema2.pipe(z42.coerce.number().int()).optional();
@@ -57369,7 +59561,7 @@ function resolveLocalSpeechConfig(params) {
57369
59561
  const includeProviderConfig = shouldIncludeLocalProviderConfig(params);
57370
59562
  const parsed = LocalSpeechResolutionSchema.parse({
57371
59563
  includeProviderConfig,
57372
- modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path27.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
59564
+ modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path30.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
57373
59565
  dictationLocalSttModel: params.env.APPOSTLE_DICTATION_LOCAL_STT_MODEL ?? persistedLocalFeatureModel(
57374
59566
  params.providers.dictationStt.provider,
57375
59567
  params.providers.dictationStt.enabled,
@@ -57625,7 +59817,7 @@ function loadConfig(appostleHome, options) {
57625
59817
  chromeEnabled,
57626
59818
  mcpDebug: env.MCP_DEBUG === "1",
57627
59819
  daemonIcon,
57628
- agentStoragePath: path28.join(appostleHome, "agents"),
59820
+ agentStoragePath: path31.join(appostleHome, "agents"),
57629
59821
  staticDir: "public",
57630
59822
  agentClients: {},
57631
59823
  relayEnabled,
@@ -57644,7 +59836,7 @@ function loadConfig(appostleHome, options) {
57644
59836
 
57645
59837
  // ../server/src/server/logger.ts
57646
59838
  import { existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync2, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
57647
- import path29 from "node:path";
59839
+ import path32 from "node:path";
57648
59840
  import pino from "pino";
57649
59841
  import pretty from "pino-pretty";
57650
59842
  import { createStream as createRotatingFileStream } from "rotating-file-stream";
@@ -57687,14 +59879,14 @@ function parsePositiveInteger(value) {
57687
59879
  return parsed;
57688
59880
  }
57689
59881
  function resolveFilePath(appostleHome, configuredPath) {
57690
- const fallback = path29.join(appostleHome, DEFAULT_DAEMON_LOG_FILENAME);
59882
+ const fallback = path32.join(appostleHome, DEFAULT_DAEMON_LOG_FILENAME);
57691
59883
  if (!configuredPath) {
57692
59884
  return fallback;
57693
59885
  }
57694
- if (path29.isAbsolute(configuredPath)) {
59886
+ if (path32.isAbsolute(configuredPath)) {
57695
59887
  return configuredPath;
57696
59888
  }
57697
- return path29.resolve(appostleHome, configuredPath);
59889
+ return path32.resolve(appostleHome, configuredPath);
57698
59890
  }
57699
59891
  function minLogLevel(levels) {
57700
59892
  let minLevel = levels[0];
@@ -57731,20 +59923,20 @@ function normalizeLoggerConfigInput(config) {
57731
59923
  }
57732
59924
  function rotateOnRestart(filePath, maxFiles) {
57733
59925
  if (!existsSync19(filePath)) return;
57734
- const dir = path29.dirname(filePath);
57735
- const base = path29.basename(filePath);
59926
+ const dir = path32.dirname(filePath);
59927
+ const base = path32.basename(filePath);
57736
59928
  const now = /* @__PURE__ */ new Date();
57737
59929
  const pad = (n) => String(n).padStart(2, "0");
57738
59930
  const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
57739
59931
  try {
57740
- renameSync3(filePath, path29.join(dir, `${ts}-00-${base}`));
59932
+ renameSync3(filePath, path32.join(dir, `${ts}-00-${base}`));
57741
59933
  } catch {
57742
59934
  return;
57743
59935
  }
57744
59936
  const rotatedFiles = readdirSync2(dir).filter((f) => f.endsWith(`-${base}`) && f !== base).sort().reverse();
57745
59937
  for (const file of rotatedFiles.slice(maxFiles)) {
57746
59938
  try {
57747
- unlinkSync2(path29.join(dir, file));
59939
+ unlinkSync2(path32.join(dir, file));
57748
59940
  } catch {
57749
59941
  }
57750
59942
  }
@@ -57793,15 +59985,15 @@ function resolveLogConfig(configInput, options) {
57793
59985
  }
57794
59986
  function createRootLogger(configInput, options) {
57795
59987
  const config = resolveLogConfig(configInput, options);
57796
- mkdirSync7(path29.dirname(config.file.path), { recursive: true });
59988
+ mkdirSync7(path32.dirname(config.file.path), { recursive: true });
57797
59989
  const consoleStream = config.console.format === "pretty" ? pretty({
57798
59990
  colorize: true,
57799
59991
  singleLine: true,
57800
59992
  ignore: "pid,hostname"
57801
59993
  }) : pino.destination({ dest: 1, sync: false });
57802
59994
  rotateOnRestart(config.file.path, config.file.rotate.maxFiles);
57803
- const fileStream = createRotatingFileStream(path29.basename(config.file.path), {
57804
- path: path29.dirname(config.file.path),
59995
+ const fileStream = createRotatingFileStream(path32.basename(config.file.path), {
59996
+ path: path32.dirname(config.file.path),
57805
59997
  size: toRotatingFileStreamSize(config.file.rotate.maxSize),
57806
59998
  maxFiles: config.file.rotate.maxFiles
57807
59999
  });
@@ -57815,7 +60007,7 @@ function createRootLogger(configInput, options) {
57815
60007
  }
57816
60008
 
57817
60009
  // ../server/src/server/pid-lock.ts
57818
- import { open, readFile as readFile6, unlink as unlink3, mkdir as mkdir8 } from "node:fs/promises";
60010
+ import { open, readFile as readFile7, unlink as unlink3, mkdir as mkdir10 } from "node:fs/promises";
57819
60011
  import { existsSync as existsSync20 } from "node:fs";
57820
60012
  import { join as join18 } from "node:path";
57821
60013
  import { hostname } from "node:os";
@@ -57846,11 +60038,11 @@ function resolveOwnerPid(ownerPid) {
57846
60038
  async function acquirePidLock(appostleHome, listen, options) {
57847
60039
  const pidPath = getPidFilePath(appostleHome);
57848
60040
  if (!existsSync20(appostleHome)) {
57849
- await mkdir8(appostleHome, { recursive: true });
60041
+ await mkdir10(appostleHome, { recursive: true });
57850
60042
  }
57851
60043
  let existingLock = null;
57852
60044
  try {
57853
- const content = await readFile6(pidPath, "utf-8");
60045
+ const content = await readFile7(pidPath, "utf-8");
57854
60046
  existingLock = JSON.parse(content);
57855
60047
  } catch {
57856
60048
  }
@@ -57883,7 +60075,7 @@ async function acquirePidLock(appostleHome, listen, options) {
57883
60075
  } catch (err) {
57884
60076
  if (err.code === "EEXIST") {
57885
60077
  try {
57886
- const content = await readFile6(pidPath, "utf-8");
60078
+ const content = await readFile7(pidPath, "utf-8");
57887
60079
  const raceLock = JSON.parse(content);
57888
60080
  throw new PidLockError(
57889
60081
  `Another Appostle daemon is already running (PID ${raceLock.pid})`,
@@ -57902,7 +60094,7 @@ async function acquirePidLock(appostleHome, listen, options) {
57902
60094
  async function updatePidLock(appostleHome, patch, options) {
57903
60095
  const pidPath = getPidFilePath(appostleHome);
57904
60096
  const lockOwnerPid = resolveOwnerPid(options?.ownerPid);
57905
- const content = await readFile6(pidPath, "utf-8");
60097
+ const content = await readFile7(pidPath, "utf-8");
57906
60098
  const existingLock = JSON.parse(content);
57907
60099
  if (existingLock.pid !== lockOwnerPid) {
57908
60100
  throw new PidLockError(`Cannot update PID lock owned by PID ${existingLock.pid}`, existingLock);
@@ -57923,7 +60115,7 @@ async function releasePidLock(appostleHome, options) {
57923
60115
  const pidPath = getPidFilePath(appostleHome);
57924
60116
  const lockOwnerPid = resolveOwnerPid(options?.ownerPid);
57925
60117
  try {
57926
- const content = await readFile6(pidPath, "utf-8");
60118
+ const content = await readFile7(pidPath, "utf-8");
57927
60119
  const lock = JSON.parse(content);
57928
60120
  if (lock.pid === lockOwnerPid) {
57929
60121
  await unlink3(pidPath);