squadrant 0.19.2 → 0.19.4

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.
@@ -2322,6 +2322,26 @@ function parseProgramArgs(plistXml) {
2322
2322
  return null;
2323
2323
  return { nodeBin: xmlUnescape(m[1]), daemonEntry: xmlUnescape(m[2]) };
2324
2324
  }
2325
+ function isVersionUpgrade(registeredEntry, thisEntry) {
2326
+ const a = registeredEntry.split("/");
2327
+ const b = thisEntry.split("/");
2328
+ if (a.length !== b.length)
2329
+ return false;
2330
+ let diffIndex = -1;
2331
+ for (let i = 0; i < a.length; i++) {
2332
+ if (a[i] !== b[i]) {
2333
+ if (diffIndex !== -1)
2334
+ return false;
2335
+ diffIndex = i;
2336
+ }
2337
+ }
2338
+ if (diffIndex === -1)
2339
+ return false;
2340
+ const versionedSegment = /^(.+)@([^@]+)$/;
2341
+ const ma = a[diffIndex].match(versionedSegment);
2342
+ const mb = b[diffIndex].match(versionedSegment);
2343
+ return ma !== null && mb !== null && ma[1] === mb[1] && ma[2] !== mb[2];
2344
+ }
2325
2345
  function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
2326
2346
  if (!parsed)
2327
2347
  return null;
@@ -2329,7 +2349,11 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
2329
2349
  return null;
2330
2350
  if (!registeredEntryExists)
2331
2351
  return null;
2332
- return { registeredEntry: parsed.daemonEntry, thisEntry };
2352
+ return {
2353
+ registeredEntry: parsed.daemonEntry,
2354
+ thisEntry,
2355
+ isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
2356
+ };
2333
2357
  }
2334
2358
  function sanitizePathForPlist(path21) {
2335
2359
  const seen = /* @__PURE__ */ new Set();
@@ -2496,7 +2520,7 @@ function forceKickstartAndVerify(target, opts = {}) {
2496
2520
  const pollDelayMs = opts.pollDelayMs ?? 300;
2497
2521
  const kickstartRetries = opts.kickstartRetries ?? 10;
2498
2522
  const kickstartRetryDelayMs = opts.kickstartRetryDelayMs ?? 300;
2499
- const pidBefore = getDaemonPid(target);
2523
+ const pidBefore = opts.pidBefore !== void 0 ? opts.pidBefore : getDaemonPid(target);
2500
2524
  let kickstartError = null;
2501
2525
  for (let i = 0; i < kickstartRetries; i++) {
2502
2526
  try {
@@ -2533,6 +2557,9 @@ function forceKickstartAndVerify(target, opts = {}) {
2533
2557
  function isOperatorInitiatedCommand(topLevelArg) {
2534
2558
  return topLevelArg !== void 0 && OPERATOR_INITIATED_COMMANDS.has(topLevelArg);
2535
2559
  }
2560
+ function isReadOnlyCrewCommand(argv) {
2561
+ return argv[2] === "crew" && argv[3] !== void 0 && READ_ONLY_CREW_SUBCOMMANDS.has(argv[3]);
2562
+ }
2536
2563
  function ensureDaemon(nodeBin = process.execPath, opts = {}) {
2537
2564
  if (restartInFlight)
2538
2565
  return;
@@ -2553,8 +2580,10 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
2553
2580
  try {
2554
2581
  const drift = computeDaemonDrift(nodeBin);
2555
2582
  if (drift.foreignInstall) {
2556
- process.stderr.write(printForeignInstallError(drift.foreignInstall));
2557
- return;
2583
+ const decision = decideForeignInstall(drift.foreignInstall);
2584
+ process.stderr.write(decision.message);
2585
+ if (!decision.proceed)
2586
+ return;
2558
2587
  }
2559
2588
  applyDaemonDrift(drift);
2560
2589
  } catch (e) {
@@ -2564,6 +2593,12 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
2564
2593
  releaseDaemonLock();
2565
2594
  }
2566
2595
  }
2596
+ function decideForeignInstall(foreign) {
2597
+ if (foreign.isUpgrade) {
2598
+ return { proceed: true, message: printVersionUpgradeNotice(foreign) };
2599
+ }
2600
+ return { proceed: false, message: printForeignInstallError(foreign) };
2601
+ }
2567
2602
  function printForeignInstallError(foreign) {
2568
2603
  return `[squadrant] refusing to restart the daemon: the registered launchd config belongs to a DIFFERENT squadrant install than this one.
2569
2604
  registered install: ${foreign.registeredEntry}
@@ -2571,14 +2606,19 @@ function printForeignInstallError(foreign) {
2571
2606
  Two squadrant installs on this machine will keep fighting over the daemon (#670). Uninstall the one you don't use, then run \`squadrant heal daemon\` to reconcile.
2572
2607
  `;
2573
2608
  }
2609
+ function printVersionUpgradeNotice(foreign) {
2610
+ return `[squadrant] upgraded plist: ${foreign.registeredEntry} -> ${foreign.thisEntry}
2611
+ `;
2612
+ }
2574
2613
  function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
2575
2614
  if (!tryAcquireDaemonLock()) {
2576
2615
  throw new Error("could not acquire the daemon lock \u2014 another squadrant process is already restarting the daemon");
2577
2616
  }
2578
2617
  try {
2579
2618
  const drift = computeDaemonDrift(nodeBin);
2619
+ const pidBeforeReconcile = getDaemonPid(drift.target);
2580
2620
  reconcilePlistAndService(drift);
2581
- const result = forceKickstartAndVerify(drift.target, kickstartOpts);
2621
+ const result = forceKickstartAndVerify(drift.target, { ...kickstartOpts, pidBefore: pidBeforeReconcile });
2582
2622
  if (!result.restarted) {
2583
2623
  throw new Error(`daemon did not restart (pid before=${result.pidBefore ?? "none"}, after=${result.pidAfter ?? "none"}) \u2014 \`launchctl kickstart -k ${drift.target}\` ran but the pid never changed`);
2584
2624
  }
@@ -2587,13 +2627,14 @@ function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
2587
2627
  releaseDaemonLock();
2588
2628
  }
2589
2629
  }
2590
- var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS;
2630
+ var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS, READ_ONLY_CREW_SUBCOMMANDS;
2591
2631
  var init_launchd = __esm({
2592
2632
  "packages/core/dist/launchd.js"() {
2593
2633
  LABEL = "com.squadrant.daemon";
2594
2634
  AGENT_BINS = ["cmux", "claude", "opencode", "codex", "gemini", "node"];
2595
2635
  restartInFlight = false;
2596
2636
  OPERATOR_INITIATED_COMMANDS = /* @__PURE__ */ new Set(["launch", "init"]);
2637
+ READ_ONLY_CREW_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "read", "tasks"]);
2597
2638
  }
2598
2639
  });
2599
2640
 
@@ -3290,6 +3331,51 @@ var init_captain_delivery = __esm({
3290
3331
  }
3291
3332
  });
3292
3333
 
3334
+ // packages/core/dist/daemon/down-alert.js
3335
+ function pad2(n) {
3336
+ return String(n).padStart(2, "0");
3337
+ }
3338
+ function formatLocalDateTime(d) {
3339
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
3340
+ }
3341
+ function formatTzOffset(d) {
3342
+ const offMin = -d.getTimezoneOffset();
3343
+ const sign = offMin >= 0 ? "+" : "-";
3344
+ const abs = Math.abs(offMin);
3345
+ const hh = pad2(Math.floor(abs / 60));
3346
+ const mm = abs % 60;
3347
+ return mm === 0 ? `${sign}${hh}` : `${sign}${hh}:${pad2(mm)}`;
3348
+ }
3349
+ function formatDownAlertText(opts) {
3350
+ const start = new Date(opts.startTs);
3351
+ const end = new Date(opts.endTs);
3352
+ return `\u26A0\uFE0F daemon was down ${formatLocalDateTime(start)} \u2192 ${formatLocalDateTime(end)} (${formatTzOffset(end)}), ${opts.minutes} min (last exit reason=${opts.reasonText})`;
3353
+ }
3354
+ function formatRelative(ms) {
3355
+ const minutes = Math.round(ms / 6e4);
3356
+ if (minutes < 60)
3357
+ return `${minutes} min`;
3358
+ const hours = Math.floor(minutes / 60);
3359
+ const remMinutes = minutes % 60;
3360
+ if (hours < 24)
3361
+ return remMinutes > 0 ? `${hours}h ${remMinutes}m` : `${hours}h`;
3362
+ const days = Math.floor(hours / 24);
3363
+ const remHours = hours % 24;
3364
+ return remHours > 0 ? `${days}d ${remHours}h` : `${days}d`;
3365
+ }
3366
+ function stalePrefix(generatedAtMs, deliveredAtMs) {
3367
+ const ageMs = deliveredAtMs - generatedAtMs;
3368
+ if (ageMs <= STALE_ALERT_THRESHOLD_MS)
3369
+ return null;
3370
+ return `[stale \u2014 generated ${formatRelative(ageMs)} ago] `;
3371
+ }
3372
+ var STALE_ALERT_THRESHOLD_MS;
3373
+ var init_down_alert = __esm({
3374
+ "packages/core/dist/daemon/down-alert.js"() {
3375
+ STALE_ALERT_THRESHOLD_MS = 60 * 60 * 1e3;
3376
+ }
3377
+ });
3378
+
3293
3379
  // packages/core/dist/control-channel.js
3294
3380
  function fallsBackToPane(o) {
3295
3381
  return o.status === "gone" || o.status === "unsupported";
@@ -3437,12 +3523,25 @@ import fs9 from "fs";
3437
3523
  import os4 from "os";
3438
3524
  import path9 from "path";
3439
3525
  import { randomUUID as randomUUID3 } from "crypto";
3440
- async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
3441
- const deadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_WINDOW_MS;
3526
+ function ensureSocksDir(dir = CC_SOCKS_DIR) {
3527
+ fs9.mkdirSync(dir, { recursive: true, mode: 448 });
3528
+ if ((fs9.statSync(dir).mode & 511) !== 448)
3529
+ fs9.chmodSync(dir, 448);
3530
+ }
3531
+ async function pollFirstTurnConfirmedAt(getTaskRecord, project, id, scrapeSettled, cancel) {
3532
+ let scrapeSettledAt;
3533
+ const markSettled = () => {
3534
+ scrapeSettledAt ??= Date.now();
3535
+ };
3536
+ scrapeSettled.then(markSettled, markSettled);
3537
+ const hardDeadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_MAX_MS;
3442
3538
  for (; ; ) {
3539
+ if (cancel.stopped)
3540
+ return false;
3443
3541
  const rec = await getTaskRecord(project, id).catch(() => void 0);
3444
3542
  if (rec?.firstTurnConfirmedAt)
3445
3543
  return true;
3544
+ const deadline = scrapeSettledAt !== void 0 ? Math.min(hardDeadline, scrapeSettledAt + FIRST_TURN_HOOK_CONFIRM_GRACE_MS) : hardDeadline;
3446
3545
  if (Date.now() >= deadline)
3447
3546
  return false;
3448
3547
  await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
@@ -3574,7 +3673,7 @@ async function runCrewSpawn(input, config, deps) {
3574
3673
  deps.onModelResolved?.({ agentName, model: crewModel });
3575
3674
  }
3576
3675
  if (agentName === "claude") {
3577
- fs9.mkdirSync(CC_SOCKS_DIR, { recursive: true });
3676
+ ensureSocksDir();
3578
3677
  const messagingSocketPath = path9.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
3579
3678
  const rec = await deps.dispatchCrew({
3580
3679
  provider: "claude",
@@ -3624,7 +3723,9 @@ async function runCrewSpawn(input, config, deps) {
3624
3723
 
3625
3724
  ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
3626
3725
  const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
3627
- const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id)) : await scrapeDelivered;
3726
+ const cancelHookPoll = { stopped: false };
3727
+ const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id, scrapeDelivered, cancelHookPoll)) : await scrapeDelivered;
3728
+ cancelHookPoll.stopped = true;
3628
3729
  if (!delivered) {
3629
3730
  process.stderr.write(`\u26A0\uFE0F First turn not delivered for crew '${name}' \u2014 use 'squadrant crew send ${input.project} ${name}' to re-send the task.
3630
3731
  `);
@@ -3906,7 +4007,7 @@ async function runCrewList(project, runtime, workspaceId) {
3906
4007
  surfaceId: c.surfaceId
3907
4008
  }));
3908
4009
  }
3909
- var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT, FIRST_TURN_HOOK_CONFIRM_WINDOW_MS, FIRST_TURN_HOOK_POLL_INTERVAL_MS, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
4010
+ var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT, FIRST_TURN_HOOK_CONFIRM_GRACE_MS, FIRST_TURN_HOOK_CONFIRM_MAX_MS, FIRST_TURN_HOOK_POLL_INTERVAL_MS, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
3910
4011
  var init_crew_spawn = __esm({
3911
4012
  "packages/core/dist/crew-spawn.js"() {
3912
4013
  init_control_channel();
@@ -3918,7 +4019,8 @@ var init_crew_spawn = __esm({
3918
4019
  FIRST_TURN_INLINE_MAX_BYTES = 1200;
3919
4020
  TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
3920
4021
  STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
3921
- FIRST_TURN_HOOK_CONFIRM_WINDOW_MS = 1e5;
4022
+ FIRST_TURN_HOOK_CONFIRM_GRACE_MS = 15e3;
4023
+ FIRST_TURN_HOOK_CONFIRM_MAX_MS = 18e4;
3922
4024
  FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
3923
4025
  CLOSE_LOOKUP_RETRIES = 3;
3924
4026
  CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
@@ -4164,7 +4266,13 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
4164
4266
  log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
4165
4267
  }
4166
4268
  }
4167
- const result = await d.deliver(entry, async (text, sendOpts) => {
4269
+ let deliverEntry = entry;
4270
+ if (entry.kind === "captain.message" && entry.payload?.source === "daemon" && entry.message) {
4271
+ const prefix = stalePrefix(new Date(entry.ts).getTime(), Date.now());
4272
+ if (prefix)
4273
+ deliverEntry = { ...entry, message: `${prefix}${entry.message}` };
4274
+ }
4275
+ const result = await d.deliver(deliverEntry, async (text, sendOpts) => {
4168
4276
  let handledByChannel = false;
4169
4277
  try {
4170
4278
  const mode = ctx.captainChannelMode?.() ?? "off";
@@ -4256,6 +4364,7 @@ var init_delivery_loop = __esm({
4256
4364
  init_defer_delivery();
4257
4365
  init_dist();
4258
4366
  init_interactive_probe();
4367
+ init_down_alert();
4259
4368
  init_liveness2();
4260
4369
  init_captain_channel();
4261
4370
  CURSOR_SUBSCRIBER = "captain";
@@ -4690,8 +4799,8 @@ function startDaemon(ctx, opts, pkgVersion) {
4690
4799
  log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
4691
4800
  const bootTs = (/* @__PURE__ */ new Date()).toISOString();
4692
4801
  {
4693
- const sendDownAlert = (minutes, reasonText) => {
4694
- const text = `\u26A0\uFE0F daemon was down for ${minutes} min (last exit reason=${reasonText})`;
4802
+ const sendDownAlert = (startTs, endTs, minutes, reasonText) => {
4803
+ const text = formatDownAlertText({ startTs, endTs, minutes, reasonText });
4695
4804
  const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
4696
4805
  for (const project of alertProjects) {
4697
4806
  appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
@@ -4702,13 +4811,13 @@ function startDaemon(ctx, opts, pkgVersion) {
4702
4811
  if (marker) {
4703
4812
  log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
4704
4813
  if ((gapMs ?? 0) > 6e4)
4705
- sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
4814
+ sendDownAlert(marker.ts, bootTs, Math.round((gapMs ?? 0) / 6e4), marker.reason);
4706
4815
  } else if (prevRunning) {
4707
4816
  const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
4708
4817
  const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
4709
4818
  log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
4710
4819
  if (uncleanGapMs > 6e4) {
4711
- sendDownAlert(Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
4820
+ sendDownAlert(prevRunning.lastHeartbeatTs, bootTs, Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
4712
4821
  }
4713
4822
  } else {
4714
4823
  log("previous exit: none (clean or first boot)");
@@ -4830,6 +4939,7 @@ var init_start = __esm({
4830
4939
  init_server();
4831
4940
  init_mailbox();
4832
4941
  init_exit_marker();
4942
+ init_down_alert();
4833
4943
  init_liveness2();
4834
4944
  init_dist();
4835
4945
  init_snapshot_gather();
@@ -6799,6 +6909,7 @@ __export(dist_exports, {
6799
6909
  OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
6800
6910
  PROBE_QUIET_MS: () => PROBE_QUIET_MS,
6801
6911
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
6912
+ READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
6802
6913
  STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
6803
6914
  TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
6804
6915
  TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
@@ -6847,6 +6958,7 @@ __export(dist_exports, {
6847
6958
  crewTag: () => crewTag,
6848
6959
  daemonEntryPath: () => daemonEntryPath,
6849
6960
  daemonLockPath: () => daemonLockPath,
6961
+ decideForeignInstall: () => decideForeignInstall,
6850
6962
  decodeFrames: () => decodeFrames,
6851
6963
  defaultIsPidAlive: () => defaultIsPidAlive,
6852
6964
  defaultListenError: () => defaultListenError,
@@ -6864,6 +6976,7 @@ __export(dist_exports, {
6864
6976
  encodeFrame: () => encodeFrame,
6865
6977
  encodeMsg: () => encodeMsg,
6866
6978
  ensureDaemon: () => ensureDaemon,
6979
+ ensureSocksDir: () => ensureSocksDir,
6867
6980
  evaluateStall: () => evaluateStall,
6868
6981
  exitMarkerPath: () => exitMarkerPath,
6869
6982
  fallsBackToPane: () => fallsBackToPane,
@@ -6887,9 +7000,11 @@ __export(dist_exports, {
6887
7000
  isDaemonSocketLive: () => isDaemonSocketLive,
6888
7001
  isNotifyActive: () => isNotifyActive,
6889
7002
  isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
7003
+ isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
6890
7004
  isSideTitle: () => isSideTitle,
6891
7005
  isStickyAttention: () => isStickyAttention,
6892
7006
  isTurnAccepted: () => isTurnAccepted,
7007
+ isVersionUpgrade: () => isVersionUpgrade,
6893
7008
  kickstartArgv: () => kickstartArgv,
6894
7009
  launchOneWorkspace: () => launchOneWorkspace,
6895
7010
  loadSessions: () => loadSessions,
@@ -6906,6 +7021,7 @@ __export(dist_exports, {
6906
7021
  parseProgramArgs: () => parseProgramArgs,
6907
7022
  plistPath: () => plistPath,
6908
7023
  printForeignInstallError: () => printForeignInstallError,
7024
+ printVersionUpgradeNotice: () => printVersionUpgradeNotice,
6909
7025
  programArgsBlock: () => programArgsBlock,
6910
7026
  projectHealth: () => projectHealth,
6911
7027
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
@@ -9803,9 +9919,12 @@ async function confirmedSendToPane(runtime, pane, message) {
9803
9919
  let sawDraft = await settleInputBox(runtime, pane);
9804
9920
  await runtime.sendKeyToPane(pane, "Enter");
9805
9921
  let repasted = false;
9922
+ let screenMoved = false;
9806
9923
  for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
9807
9924
  await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
9808
9925
  const afterScreen = await runtime.readPaneScreen(pane) ?? "";
9926
+ if (afterScreen !== preSendScreen)
9927
+ screenMoved = true;
9809
9928
  const draft = parseDraftFromScreen(afterScreen);
9810
9929
  if (draft !== "" && draft !== null)
9811
9930
  sawDraft = true;
@@ -9816,7 +9935,7 @@ async function confirmedSendToPane(runtime, pane, message) {
9816
9935
  const settled = await settleInputBox(runtime, pane);
9817
9936
  if (settled)
9818
9937
  sawDraft = true;
9819
- if (!sawDraft && !repasted) {
9938
+ if (!sawDraft && !repasted && !screenMoved) {
9820
9939
  repasted = true;
9821
9940
  await runtime.pasteToPane(pane, message);
9822
9941
  }
@@ -9930,6 +10049,7 @@ function registerSenderIdentity(socketPath) {
9930
10049
  }
9931
10050
  async function sharedReceiptListener() {
9932
10051
  if (shared) return shared;
10052
+ ensureSocksDir();
9933
10053
  const socketPath = `${CC_SOCKS_DIR}/squadrantd-${process.pid}.sock`;
9934
10054
  const listener = new ClaudeReceiptListener({
9935
10055
  socketPath,