@rallycry/conveyor-agent 8.4.1 → 8.5.1

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.
@@ -95,22 +95,31 @@ function parseEnvelope(line) {
95
95
  }
96
96
  if (typeof parsed !== "object" || parsed === null) return null;
97
97
  const record = parsed;
98
+ if (record.kind === "pre_tool_use") {
99
+ if (typeof record.id !== "string" || typeof record.tool_name !== "string") return null;
100
+ const toolInput = typeof record.tool_input === "object" && record.tool_input !== null ? record.tool_input : {};
101
+ return {
102
+ kind: "pre_tool_use",
103
+ request: { id: record.id, tool_name: record.tool_name, tool_input: toolInput }
104
+ };
105
+ }
98
106
  const result = {};
99
107
  if (typeof record.tool_name === "string") result.tool_name = record.tool_name;
100
108
  if (typeof record.elapsed_time_seconds === "number") {
101
109
  result.elapsed_time_seconds = record.elapsed_time_seconds;
102
110
  }
103
- return result;
111
+ return { kind: "progress", progress: result };
104
112
  }
105
113
  var HookSocketServer = class {
106
- constructor(socketPath, onProgress) {
114
+ constructor(socketPath, onProgress, onPreToolUse) {
107
115
  this.socketPath = socketPath;
108
116
  this.onProgress = onProgress;
117
+ this.onPreToolUse = onPreToolUse;
109
118
  }
110
119
  socketPath;
111
120
  onProgress;
121
+ onPreToolUse;
112
122
  server = null;
113
- buffer = "";
114
123
  _closed = false;
115
124
  async listen() {
116
125
  await unlink(this.socketPath).catch(() => void 0);
@@ -142,19 +151,41 @@ var HookSocketServer = class {
142
151
  }
143
152
  handleConnection(socket) {
144
153
  socket.on("error", () => void 0);
154
+ let buffer = "";
145
155
  socket.on("data", (chunk) => {
146
- this.buffer += chunk.toString("utf8");
147
- this.ingest();
156
+ buffer += chunk.toString("utf8");
157
+ let index = buffer.indexOf("\n");
158
+ while (index >= 0) {
159
+ const line = buffer.slice(0, index);
160
+ buffer = buffer.slice(index + 1);
161
+ this.dispatch(line, socket);
162
+ index = buffer.indexOf("\n");
163
+ }
148
164
  });
149
165
  }
150
- ingest() {
151
- let index = this.buffer.indexOf("\n");
152
- while (index >= 0) {
153
- const line = this.buffer.slice(0, index);
154
- this.buffer = this.buffer.slice(index + 1);
155
- const progress = parseEnvelope(line);
156
- if (progress) this.onProgress(progress);
157
- index = this.buffer.indexOf("\n");
166
+ dispatch(line, socket) {
167
+ const envelope = parseEnvelope(line);
168
+ if (!envelope) return;
169
+ if (envelope.kind === "progress") {
170
+ this.onProgress(envelope.progress);
171
+ return;
172
+ }
173
+ void this.respondToPreToolUse(envelope.request, socket);
174
+ }
175
+ async respondToPreToolUse(request, socket) {
176
+ let verdict;
177
+ try {
178
+ verdict = this.onPreToolUse ? await this.onPreToolUse(request) : { decision: "allow" };
179
+ } catch (err) {
180
+ verdict = {
181
+ decision: "deny",
182
+ reason: `Conveyor validation failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
183
+ };
184
+ }
185
+ try {
186
+ socket.write(`${JSON.stringify({ id: request.id, ...verdict })}
187
+ `);
188
+ } catch {
158
189
  }
159
190
  }
160
191
  };
@@ -380,6 +411,9 @@ function buildSpawnArgs(input) {
380
411
  args.push("--permission-mode", "plan");
381
412
  }
382
413
  args.push("--settings", input.settingsPath);
414
+ if (input.appendSystemPrompt) {
415
+ args.push("--append-system-prompt", input.appendSystemPrompt);
416
+ }
383
417
  if (input.mcpConfigPath) {
384
418
  args.push("--mcp-config", input.mcpConfigPath);
385
419
  if (input.strictMcpConfig) {
@@ -434,8 +468,33 @@ function projectSlug(cwd) {
434
468
  function sessionTranscriptPath(cwd, sessionId) {
435
469
  return join(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
436
470
  }
471
+ function configHomePlansDir() {
472
+ return join(claudeConfigHome(), "plans");
473
+ }
474
+ var ALLOW_RULES = [
475
+ "Bash",
476
+ "BashOutput",
477
+ "Edit",
478
+ "Write",
479
+ "Read",
480
+ "Glob",
481
+ "Grep",
482
+ "WebFetch",
483
+ "WebSearch",
484
+ "NotebookEdit",
485
+ "Task",
486
+ "TodoWrite",
487
+ "ToolSearch",
488
+ "KillShell",
489
+ "SlashCommand",
490
+ "Skill",
491
+ "mcp__conveyor__*"
492
+ ];
437
493
  var HOOK_HELPER_SOURCE = `"use strict";
438
494
  const net = require("node:net");
495
+ const crypto = require("node:crypto");
496
+
497
+ const PRE_TOOL_USE_TIMEOUT_MS = 110000;
439
498
 
440
499
  let raw = "";
441
500
  process.stdin.setEncoding("utf8");
@@ -443,17 +502,21 @@ process.stdin.on("data", (chunk) => {
443
502
  raw += chunk;
444
503
  });
445
504
  process.stdin.on("end", () => {
446
- let toolName = "";
505
+ let payload = {};
447
506
  try {
448
507
  const parsed = JSON.parse(raw);
449
- if (parsed && typeof parsed.tool_name === "string") toolName = parsed.tool_name;
508
+ if (parsed && typeof parsed === "object") payload = parsed;
450
509
  } catch {
451
- toolName = "";
510
+ payload = {};
511
+ }
512
+ if (payload.hook_event_name === "PreToolUse") {
513
+ preToolUse(payload);
514
+ } else {
515
+ relayProgress(typeof payload.tool_name === "string" ? payload.tool_name : "");
452
516
  }
453
- relay(toolName);
454
517
  });
455
518
 
456
- function relay(toolName) {
519
+ function relayProgress(toolName) {
457
520
  const socketPath = process.env.CONVEYOR_HOOK_SOCKET;
458
521
  let done = false;
459
522
  const finish = () => {
@@ -485,6 +548,76 @@ function relay(toolName) {
485
548
  finish();
486
549
  });
487
550
  }
551
+
552
+ function preToolUse(payload) {
553
+ const socketPath = process.env.CONVEYOR_HOOK_SOCKET;
554
+ let done = false;
555
+ const respond = (decision, reason) => {
556
+ if (done) return;
557
+ done = true;
558
+ process.stdout.write(
559
+ JSON.stringify({
560
+ hookSpecificOutput: {
561
+ hookEventName: "PreToolUse",
562
+ permissionDecision: decision,
563
+ ...(reason ? { permissionDecisionReason: reason } : {}),
564
+ },
565
+ }),
566
+ );
567
+ process.exit(0);
568
+ };
569
+ const denyUnavailable = () =>
570
+ respond(
571
+ "deny",
572
+ "Conveyor validation is unavailable right now. Wait a moment and call the tool again.",
573
+ );
574
+ if (!socketPath) {
575
+ denyUnavailable();
576
+ return;
577
+ }
578
+ const id = crypto.randomBytes(8).toString("hex");
579
+ const timer = setTimeout(denyUnavailable, PRE_TOOL_USE_TIMEOUT_MS);
580
+ const client = net.connect(socketPath, () => {
581
+ client.write(
582
+ JSON.stringify({
583
+ kind: "pre_tool_use",
584
+ id,
585
+ tool_name: typeof payload.tool_name === "string" ? payload.tool_name : "",
586
+ tool_input:
587
+ payload.tool_input && typeof payload.tool_input === "object" ? payload.tool_input : {},
588
+ }) + "\\n",
589
+ );
590
+ });
591
+ let buffer = "";
592
+ client.on("data", (chunk) => {
593
+ buffer += chunk.toString("utf8");
594
+ let index = buffer.indexOf("\\n");
595
+ while (index >= 0) {
596
+ const line = buffer.slice(0, index);
597
+ buffer = buffer.slice(index + 1);
598
+ try {
599
+ const verdict = JSON.parse(line);
600
+ if (verdict && verdict.id === id) {
601
+ clearTimeout(timer);
602
+ client.end();
603
+ respond(verdict.decision === "allow" ? "allow" : "deny", verdict.reason);
604
+ return;
605
+ }
606
+ } catch {
607
+ /* keep scanning */
608
+ }
609
+ index = buffer.indexOf("\\n");
610
+ }
611
+ });
612
+ client.on("error", () => {
613
+ clearTimeout(timer);
614
+ denyUnavailable();
615
+ });
616
+ client.on("close", () => {
617
+ clearTimeout(timer);
618
+ denyUnavailable();
619
+ });
620
+ }
488
621
  `;
489
622
  async function writeHookSettings(dir) {
490
623
  const helperPath = join(dir, "hook-helper.cjs");
@@ -493,21 +626,24 @@ async function writeHookSettings(dir) {
493
626
  await writeFile(helperPath, HOOK_HELPER_SOURCE, "utf8");
494
627
  await chmod(helperPath, 493);
495
628
  const settings = {
496
- // Auto-approve the Conveyor MCP tools so the interactive (PTY) agent never
497
- // stops to prompt the viewer for them. `mcp__conveyor__*` is the ONLY valid
498
- // allow form in Claude Code ≥2.1.170 the glob is permitted only in the
499
- // tool position after a literal `mcp__<server>__` prefix. A bare `"*"` (and
500
- // an unanchored `"mcp__conveyor"`) is rejected as an invalid rule, which
501
- // parks the TUI on a blocking "Settings Warning" screen.
502
- // Built-in tool auto-approval is NOT carried by this list the spawn flags
503
- // handle it: discovery/plan mode passes `--permission-mode plan` (read-only,
504
- // no write prompts) and build mode passes `--dangerously-skip-permissions`
505
- // (which ignores the allow-list entirely). So this list only needs the MCP
506
- // glob, which plan mode would otherwise prompt for.
629
+ // Auto-approve tool calls so the interactive (PTY) agent never stops to
630
+ // prompt the viewer. This only suppresses per-tool approval prompts — it
631
+ // does NOT relax the planning gate: in discovery/plan mode the spawn passes
632
+ // `--permission-mode plan`, which keeps the agent read-only (no edits/commits
633
+ // until it exits plan mode) regardless of this allow-list. In build mode the
634
+ // spawn already bypasses prompts via `--dangerously-skip-permissions`.
635
+ // NOTE: a bare "*" rule is INVALID (the CLI rejects it and parks the TUI on
636
+ // a Settings Warning dialog at boot) hence the explicit ALLOW_RULES list.
507
637
  permissions: {
508
- allow: ["mcp__conveyor__*"]
638
+ allow: ALLOW_RULES
509
639
  },
510
640
  hooks: {
641
+ PreToolUse: [
642
+ {
643
+ matcher: "ExitPlanMode",
644
+ hooks: [{ type: "command", command: `node ${JSON.stringify(helperPath)}`, timeout: 120 }]
645
+ }
646
+ ],
511
647
  PostToolUse: [
512
648
  {
513
649
  matcher: "*",
@@ -520,6 +656,58 @@ async function writeHookSettings(dir) {
520
656
  return { settingsPath, helperPath };
521
657
  }
522
658
 
659
+ // src/harness/pty/output-coalescer.ts
660
+ var DEFAULT_FLUSH_MS = 40;
661
+ var DEFAULT_MAX_BUFFER_CHARS = 48 * 1024;
662
+ var PtyOutputCoalescer = class {
663
+ constructor(sink, getDims, flushMs = DEFAULT_FLUSH_MS, maxBufferChars = DEFAULT_MAX_BUFFER_CHARS) {
664
+ this.sink = sink;
665
+ this.getDims = getDims;
666
+ this.flushMs = flushMs;
667
+ this.maxBufferChars = maxBufferChars;
668
+ }
669
+ sink;
670
+ getDims;
671
+ flushMs;
672
+ maxBufferChars;
673
+ buffer = "";
674
+ timer = null;
675
+ disposed = false;
676
+ /** Buffer a chunk; flushes synchronously when the size threshold is hit. */
677
+ write(data) {
678
+ if (this.disposed || data === "") return;
679
+ this.buffer += data;
680
+ if (this.buffer.length >= this.maxBufferChars) {
681
+ this.flush();
682
+ return;
683
+ }
684
+ if (!this.timer) {
685
+ this.timer = setTimeout(() => {
686
+ this.flush();
687
+ }, this.flushMs);
688
+ }
689
+ }
690
+ /** Ship the buffered bytes now (no-op when empty). Dims are read at flush
691
+ * time so a mid-buffer resize stamps the frame with the dims it will render
692
+ * under. */
693
+ flush() {
694
+ if (this.timer) {
695
+ clearTimeout(this.timer);
696
+ this.timer = null;
697
+ }
698
+ if (this.buffer === "") return;
699
+ const data = this.buffer;
700
+ this.buffer = "";
701
+ this.sink(data, this.getDims());
702
+ }
703
+ /** Final flush, then drop any future writes (session torn down). Idempotent. */
704
+ dispose() {
705
+ if (this.disposed) return;
706
+ this.flush();
707
+ this.disposed = true;
708
+ }
709
+ };
710
+
523
711
  // src/harness/pty/tool-server.ts
524
712
  import { createServer as createServer2 } from "http";
525
713
  import { writeFile as writeFile2 } from "fs/promises";
@@ -659,9 +847,16 @@ function inheritedEnv(socketPath) {
659
847
  env.CONVEYOR_HOOK_SOCKET = socketPath;
660
848
  return env;
661
849
  }
662
- function buildPromptBytes(text, delivery) {
663
- const paste = `\x1B[200~${text}\x1B[201~`;
664
- return delivery === "prefill" ? paste : `${paste}\r`;
850
+ function buildPromptBytes(text) {
851
+ return `\x1B[200~${text}\x1B[201~`;
852
+ }
853
+ var SUBMIT_SETTLE_MS = 300;
854
+ var SUBMIT_NUDGE_INTERVAL_MS = 2e3;
855
+ var SUBMIT_NUDGE_MAX_PRESSES = 5;
856
+ function sleep(ms) {
857
+ return new Promise((resolve) => {
858
+ setTimeout(resolve, ms);
859
+ });
665
860
  }
666
861
  async function transcriptSize(path) {
667
862
  try {
@@ -691,6 +886,7 @@ var PtySession = class {
691
886
  // CLI exits before emitting a result (the bytes are otherwise relayed to S5
692
887
  // and never become events). Trimmed to MAX_DIAGNOSTIC_OUTPUT on every write.
693
888
  recentOutput = "";
889
+ coalescer = null;
694
890
  _toreDown = false;
695
891
  exitListeners = [];
696
892
  idleListeners = [];
@@ -703,6 +899,12 @@ var PtySession = class {
703
899
  // plus the path to the `--mcp-config` that points at them.
704
900
  toolServers = [];
705
901
  mcpConfigPath = null;
902
+ // Plan-dialog auto-accept state (see armPlanDialogAutoAccept).
903
+ pendingPlanApproval = false;
904
+ planApprovalTimer = null;
905
+ // Submit-nudge state (see armSubmitNudge).
906
+ pendingSubmitNudge = false;
907
+ submitNudgeTimer = null;
706
908
  onIdle(listener) {
707
909
  this.idleListeners.push(listener);
708
910
  }
@@ -731,7 +933,11 @@ var PtySession = class {
731
933
  try {
732
934
  this.tempDir = await mkdtemp(join3(tmpdir(), "conveyor-pty-"));
733
935
  const socketPath = join3(this.tempDir, "hook.sock");
734
- this.socket = new HookSocketServer(socketPath, (progress) => this.handleProgress(progress));
936
+ this.socket = new HookSocketServer(
937
+ socketPath,
938
+ (progress) => this.handleProgress(progress),
939
+ (request) => this.handlePreToolUse(request)
940
+ );
735
941
  await this.socket.listen();
736
942
  const { settingsPath } = await writeHookSettings(this.tempDir);
737
943
  await this.setupToolServers();
@@ -777,6 +983,8 @@ var PtySession = class {
777
983
  async teardown() {
778
984
  if (this._toreDown) return;
779
985
  this._toreDown = true;
986
+ this.disarmPlanDialogAutoAccept();
987
+ this.disarmSubmitNudge();
780
988
  this.unsubInput?.();
781
989
  this.unsubInput = null;
782
990
  this.unsubResize?.();
@@ -790,6 +998,8 @@ var PtySession = class {
790
998
  } catch {
791
999
  }
792
1000
  this.pty = null;
1001
+ this.coalescer?.dispose();
1002
+ this.coalescer = null;
793
1003
  this.tailer?.close();
794
1004
  this.tailer = null;
795
1005
  if (this.socket) {
@@ -831,6 +1041,7 @@ var PtySession = class {
831
1041
  model: this.options.model,
832
1042
  permissionMode: this.options.permissionMode,
833
1043
  settingsPath,
1044
+ ...this.options.appendSystemPrompt ? { appendSystemPrompt: this.options.appendSystemPrompt } : {},
834
1045
  // Only this config: ignore the user's ~/.claude.json / project .mcp.json
835
1046
  // so the agent's tool set is deterministic (and a stale personal conveyor
836
1047
  // server doesn't load).
@@ -844,8 +1055,17 @@ var PtySession = class {
844
1055
  cwd: this.options.cwd,
845
1056
  env: inheritedEnv(socketPath)
846
1057
  });
1058
+ const bridge = this.bridge;
1059
+ if (bridge) {
1060
+ this.coalescer = new PtyOutputCoalescer(
1061
+ (data, dims) => {
1062
+ bridge.sendOutput(data, dims);
1063
+ },
1064
+ () => ({ cols: this.cols, rows: this.rows })
1065
+ );
1066
+ }
847
1067
  pty.onData((data) => {
848
- this.bridge?.sendOutput(data, { cols: this.cols, rows: this.rows });
1068
+ this.coalescer?.write(data);
849
1069
  this.recentOutput = (this.recentOutput + data).slice(-MAX_DIAGNOSTIC_OUTPUT);
850
1070
  });
851
1071
  pty.onExit((event) => {
@@ -853,18 +1073,54 @@ var PtySession = class {
853
1073
  });
854
1074
  this.pty = pty;
855
1075
  }
856
- deliverPrompt(text) {
857
- this.writeStdin(buildPromptBytes(text, this.options.promptDelivery));
1076
+ async deliverPrompt(text) {
1077
+ this.writeStdin(buildPromptBytes(text));
1078
+ if (this.options.promptDelivery === "prefill") return;
1079
+ await sleep(SUBMIT_SETTLE_MS);
1080
+ if (this._toreDown) return;
1081
+ this.writeStdin("\r");
1082
+ this.armSubmitNudge();
858
1083
  }
859
1084
  async feedPrompt() {
860
1085
  if (typeof this.prompt === "string") {
861
- this.deliverPrompt(this.prompt);
1086
+ await this.deliverPrompt(this.prompt);
862
1087
  return;
863
1088
  }
864
1089
  for await (const message of this.prompt) {
865
1090
  const content = message.message.content;
866
1091
  const text = typeof content === "string" ? content : JSON.stringify(content);
867
- this.deliverPrompt(text);
1092
+ await this.deliverPrompt(text);
1093
+ }
1094
+ }
1095
+ /**
1096
+ * Even as a separate write, the submitting Enter can race CLI startup and
1097
+ * be swallowed, leaving the pasted prompt parked in the input box with
1098
+ * nothing to un-park it (the 45s parked-TUI watchdog only reports status).
1099
+ * Until the first transcript record proves the turn started, re-press Enter
1100
+ * on a bounded interval. Enter on an empty or mid-turn input box is a no-op.
1101
+ * Deliberate trade-off (same as the plan-dialog auto-accept below): a press
1102
+ * can accept the default of an unexpected startup dialog — in a headless
1103
+ * pod that unblocks the run rather than parking it forever.
1104
+ */
1105
+ armSubmitNudge() {
1106
+ if (this.submitNudgeTimer) return;
1107
+ this.pendingSubmitNudge = true;
1108
+ let presses = 0;
1109
+ const press = () => {
1110
+ if (this._toreDown || !this.pendingSubmitNudge || presses >= SUBMIT_NUDGE_MAX_PRESSES) {
1111
+ this.disarmSubmitNudge();
1112
+ return;
1113
+ }
1114
+ presses++;
1115
+ this.writeStdin("\r");
1116
+ };
1117
+ this.submitNudgeTimer = setInterval(press, SUBMIT_NUDGE_INTERVAL_MS);
1118
+ }
1119
+ disarmSubmitNudge() {
1120
+ this.pendingSubmitNudge = false;
1121
+ if (this.submitNudgeTimer) {
1122
+ clearInterval(this.submitNudgeTimer);
1123
+ this.submitNudgeTimer = null;
868
1124
  }
869
1125
  }
870
1126
  handleProgress(progress) {
@@ -874,7 +1130,57 @@ var PtySession = class {
874
1130
  ...progress.elapsed_time_seconds === void 0 ? {} : { elapsed_time_seconds: progress.elapsed_time_seconds }
875
1131
  });
876
1132
  }
1133
+ /**
1134
+ * PreToolUse request from the hook helper → the runner's `canUseTool`
1135
+ * verdict. Only ExitPlanMode is matched by the settings hook, so this is
1136
+ * the Conveyor plan-exit gate (validation + identification trigger run
1137
+ * inside the callback). Absent callback → allow (mirrors the SDK default).
1138
+ */
1139
+ async handlePreToolUse(request) {
1140
+ const canUseTool = this.options.canUseTool;
1141
+ let verdict;
1142
+ if (canUseTool) {
1143
+ const result = await canUseTool(request.tool_name, request.tool_input);
1144
+ verdict = result.behavior === "allow" ? { decision: "allow" } : { decision: "deny", reason: result.message };
1145
+ } else {
1146
+ verdict = { decision: "allow" };
1147
+ }
1148
+ if (verdict.decision === "allow" && request.tool_name === "ExitPlanMode" && this.options.planDialogAutoAccept) {
1149
+ this.armPlanDialogAutoAccept();
1150
+ }
1151
+ return verdict;
1152
+ }
1153
+ /**
1154
+ * A hook "allow" does not bypass the CLI's plan-approval dialog — it renders
1155
+ * right after the verdict. Press Enter (default: "Yes, auto-accept edits")
1156
+ * until the turn resumes; the next transcript record only lands once the
1157
+ * dialog is answered, so it doubles as the stop signal.
1158
+ */
1159
+ armPlanDialogAutoAccept() {
1160
+ if (this.planApprovalTimer) return;
1161
+ this.pendingPlanApproval = true;
1162
+ let presses = 0;
1163
+ const press = () => {
1164
+ if (this._toreDown || !this.pendingPlanApproval || presses >= 6) {
1165
+ this.disarmPlanDialogAutoAccept();
1166
+ return;
1167
+ }
1168
+ presses++;
1169
+ this.writeStdin("\r");
1170
+ };
1171
+ this.planApprovalTimer = setInterval(press, 1500);
1172
+ setTimeout(press, 700);
1173
+ }
1174
+ disarmPlanDialogAutoAccept() {
1175
+ this.pendingPlanApproval = false;
1176
+ if (this.planApprovalTimer) {
1177
+ clearInterval(this.planApprovalTimer);
1178
+ this.planApprovalTimer = null;
1179
+ }
1180
+ }
877
1181
  handleTranscriptEvent(event) {
1182
+ if (this.pendingPlanApproval) this.disarmPlanDialogAutoAccept();
1183
+ if (this.pendingSubmitNudge) this.disarmSubmitNudge();
878
1184
  this.queue.push(event);
879
1185
  if (event.type === "result") {
880
1186
  this.sawResult = true;
@@ -883,6 +1189,7 @@ var PtySession = class {
883
1189
  }
884
1190
  }
885
1191
  async finalizeOnExit(exitCode) {
1192
+ this.coalescer?.flush();
886
1193
  if (this._toreDown) return;
887
1194
  if (this.tailer) {
888
1195
  this.tailer.close();
@@ -1011,6 +1318,10 @@ function planClaudeJsonSeed(existingRaw, trustCwd) {
1011
1318
  config.hasCompletedOnboarding = true;
1012
1319
  changed = true;
1013
1320
  }
1321
+ if (config.bypassPermissionsModeAccepted !== true) {
1322
+ config.bypassPermissionsModeAccepted = true;
1323
+ changed = true;
1324
+ }
1014
1325
  if (isFresh && typeof config.theme !== "string") {
1015
1326
  config.theme = "dark";
1016
1327
  changed = true;
@@ -1113,9 +1424,10 @@ function createServiceLogger(service) {
1113
1424
  export {
1114
1425
  defineTool,
1115
1426
  sessionTranscriptPath,
1427
+ configHomePlansDir,
1116
1428
  ensureClaudeCredentials,
1117
1429
  removeConveyorCredentials,
1118
1430
  createHarness,
1119
1431
  createServiceLogger
1120
1432
  };
1121
- //# sourceMappingURL=chunk-VDH55LTT.js.map
1433
+ //# sourceMappingURL=chunk-6B545CHM.js.map