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.
package/dist/index.js CHANGED
@@ -3022,6 +3022,26 @@ function parseProgramArgs(plistXml) {
3022
3022
  return null;
3023
3023
  return { nodeBin: xmlUnescape(m[1]), daemonEntry: xmlUnescape(m[2]) };
3024
3024
  }
3025
+ function isVersionUpgrade(registeredEntry, thisEntry) {
3026
+ const a = registeredEntry.split("/");
3027
+ const b = thisEntry.split("/");
3028
+ if (a.length !== b.length)
3029
+ return false;
3030
+ let diffIndex = -1;
3031
+ for (let i = 0; i < a.length; i++) {
3032
+ if (a[i] !== b[i]) {
3033
+ if (diffIndex !== -1)
3034
+ return false;
3035
+ diffIndex = i;
3036
+ }
3037
+ }
3038
+ if (diffIndex === -1)
3039
+ return false;
3040
+ const versionedSegment = /^(.+)@([^@]+)$/;
3041
+ const ma = a[diffIndex].match(versionedSegment);
3042
+ const mb = b[diffIndex].match(versionedSegment);
3043
+ return ma !== null && mb !== null && ma[1] === mb[1] && ma[2] !== mb[2];
3044
+ }
3025
3045
  function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
3026
3046
  if (!parsed)
3027
3047
  return null;
@@ -3029,7 +3049,11 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
3029
3049
  return null;
3030
3050
  if (!registeredEntryExists)
3031
3051
  return null;
3032
- return { registeredEntry: parsed.daemonEntry, thisEntry };
3052
+ return {
3053
+ registeredEntry: parsed.daemonEntry,
3054
+ thisEntry,
3055
+ isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
3056
+ };
3033
3057
  }
3034
3058
  function sanitizePathForPlist(path36) {
3035
3059
  const seen = /* @__PURE__ */ new Set();
@@ -3196,7 +3220,7 @@ function forceKickstartAndVerify(target, opts = {}) {
3196
3220
  const pollDelayMs = opts.pollDelayMs ?? 300;
3197
3221
  const kickstartRetries = opts.kickstartRetries ?? 10;
3198
3222
  const kickstartRetryDelayMs = opts.kickstartRetryDelayMs ?? 300;
3199
- const pidBefore = getDaemonPid(target);
3223
+ const pidBefore = opts.pidBefore !== void 0 ? opts.pidBefore : getDaemonPid(target);
3200
3224
  let kickstartError = null;
3201
3225
  for (let i = 0; i < kickstartRetries; i++) {
3202
3226
  try {
@@ -3233,6 +3257,9 @@ function forceKickstartAndVerify(target, opts = {}) {
3233
3257
  function isOperatorInitiatedCommand(topLevelArg) {
3234
3258
  return topLevelArg !== void 0 && OPERATOR_INITIATED_COMMANDS.has(topLevelArg);
3235
3259
  }
3260
+ function isReadOnlyCrewCommand(argv) {
3261
+ return argv[2] === "crew" && argv[3] !== void 0 && READ_ONLY_CREW_SUBCOMMANDS.has(argv[3]);
3262
+ }
3236
3263
  function ensureDaemon(nodeBin = process.execPath, opts = {}) {
3237
3264
  if (restartInFlight)
3238
3265
  return;
@@ -3253,8 +3280,10 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
3253
3280
  try {
3254
3281
  const drift = computeDaemonDrift(nodeBin);
3255
3282
  if (drift.foreignInstall) {
3256
- process.stderr.write(printForeignInstallError(drift.foreignInstall));
3257
- return;
3283
+ const decision = decideForeignInstall(drift.foreignInstall);
3284
+ process.stderr.write(decision.message);
3285
+ if (!decision.proceed)
3286
+ return;
3258
3287
  }
3259
3288
  applyDaemonDrift(drift);
3260
3289
  } catch (e) {
@@ -3264,6 +3293,12 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
3264
3293
  releaseDaemonLock();
3265
3294
  }
3266
3295
  }
3296
+ function decideForeignInstall(foreign) {
3297
+ if (foreign.isUpgrade) {
3298
+ return { proceed: true, message: printVersionUpgradeNotice(foreign) };
3299
+ }
3300
+ return { proceed: false, message: printForeignInstallError(foreign) };
3301
+ }
3267
3302
  function printForeignInstallError(foreign) {
3268
3303
  return `[squadrant] refusing to restart the daemon: the registered launchd config belongs to a DIFFERENT squadrant install than this one.
3269
3304
  registered install: ${foreign.registeredEntry}
@@ -3271,14 +3306,19 @@ function printForeignInstallError(foreign) {
3271
3306
  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.
3272
3307
  `;
3273
3308
  }
3309
+ function printVersionUpgradeNotice(foreign) {
3310
+ return `[squadrant] upgraded plist: ${foreign.registeredEntry} -> ${foreign.thisEntry}
3311
+ `;
3312
+ }
3274
3313
  function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
3275
3314
  if (!tryAcquireDaemonLock()) {
3276
3315
  throw new Error("could not acquire the daemon lock \u2014 another squadrant process is already restarting the daemon");
3277
3316
  }
3278
3317
  try {
3279
3318
  const drift = computeDaemonDrift(nodeBin);
3319
+ const pidBeforeReconcile = getDaemonPid(drift.target);
3280
3320
  reconcilePlistAndService(drift);
3281
- const result = forceKickstartAndVerify(drift.target, kickstartOpts);
3321
+ const result = forceKickstartAndVerify(drift.target, { ...kickstartOpts, pidBefore: pidBeforeReconcile });
3282
3322
  if (!result.restarted) {
3283
3323
  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`);
3284
3324
  }
@@ -3287,13 +3327,14 @@ function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
3287
3327
  releaseDaemonLock();
3288
3328
  }
3289
3329
  }
3290
- var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS;
3330
+ var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS, READ_ONLY_CREW_SUBCOMMANDS;
3291
3331
  var init_launchd = __esm({
3292
3332
  "packages/core/dist/launchd.js"() {
3293
3333
  LABEL = "com.squadrant.daemon";
3294
3334
  AGENT_BINS = ["cmux", "claude", "opencode", "codex", "gemini", "node"];
3295
3335
  restartInFlight = false;
3296
3336
  OPERATOR_INITIATED_COMMANDS = /* @__PURE__ */ new Set(["launch", "init"]);
3337
+ READ_ONLY_CREW_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "read", "tasks"]);
3297
3338
  }
3298
3339
  });
3299
3340
 
@@ -3990,6 +4031,51 @@ var init_captain_delivery = __esm({
3990
4031
  }
3991
4032
  });
3992
4033
 
4034
+ // packages/core/dist/daemon/down-alert.js
4035
+ function pad2(n) {
4036
+ return String(n).padStart(2, "0");
4037
+ }
4038
+ function formatLocalDateTime(d) {
4039
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
4040
+ }
4041
+ function formatTzOffset(d) {
4042
+ const offMin = -d.getTimezoneOffset();
4043
+ const sign = offMin >= 0 ? "+" : "-";
4044
+ const abs = Math.abs(offMin);
4045
+ const hh = pad2(Math.floor(abs / 60));
4046
+ const mm = abs % 60;
4047
+ return mm === 0 ? `${sign}${hh}` : `${sign}${hh}:${pad2(mm)}`;
4048
+ }
4049
+ function formatDownAlertText(opts) {
4050
+ const start = new Date(opts.startTs);
4051
+ const end = new Date(opts.endTs);
4052
+ return `\u26A0\uFE0F daemon was down ${formatLocalDateTime(start)} \u2192 ${formatLocalDateTime(end)} (${formatTzOffset(end)}), ${opts.minutes} min (last exit reason=${opts.reasonText})`;
4053
+ }
4054
+ function formatRelative(ms) {
4055
+ const minutes = Math.round(ms / 6e4);
4056
+ if (minutes < 60)
4057
+ return `${minutes} min`;
4058
+ const hours = Math.floor(minutes / 60);
4059
+ const remMinutes = minutes % 60;
4060
+ if (hours < 24)
4061
+ return remMinutes > 0 ? `${hours}h ${remMinutes}m` : `${hours}h`;
4062
+ const days = Math.floor(hours / 24);
4063
+ const remHours = hours % 24;
4064
+ return remHours > 0 ? `${days}d ${remHours}h` : `${days}d`;
4065
+ }
4066
+ function stalePrefix(generatedAtMs, deliveredAtMs) {
4067
+ const ageMs = deliveredAtMs - generatedAtMs;
4068
+ if (ageMs <= STALE_ALERT_THRESHOLD_MS)
4069
+ return null;
4070
+ return `[stale \u2014 generated ${formatRelative(ageMs)} ago] `;
4071
+ }
4072
+ var STALE_ALERT_THRESHOLD_MS;
4073
+ var init_down_alert = __esm({
4074
+ "packages/core/dist/daemon/down-alert.js"() {
4075
+ STALE_ALERT_THRESHOLD_MS = 60 * 60 * 1e3;
4076
+ }
4077
+ });
4078
+
3993
4079
  // packages/core/dist/control-channel.js
3994
4080
  function fallsBackToPane(o) {
3995
4081
  return o.status === "gone" || o.status === "unsupported";
@@ -4137,12 +4223,25 @@ import fs9 from "fs";
4137
4223
  import os4 from "os";
4138
4224
  import path9 from "path";
4139
4225
  import { randomUUID as randomUUID3 } from "crypto";
4140
- async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
4141
- const deadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_WINDOW_MS;
4226
+ function ensureSocksDir(dir = CC_SOCKS_DIR) {
4227
+ fs9.mkdirSync(dir, { recursive: true, mode: 448 });
4228
+ if ((fs9.statSync(dir).mode & 511) !== 448)
4229
+ fs9.chmodSync(dir, 448);
4230
+ }
4231
+ async function pollFirstTurnConfirmedAt(getTaskRecord, project, id, scrapeSettled, cancel) {
4232
+ let scrapeSettledAt;
4233
+ const markSettled = () => {
4234
+ scrapeSettledAt ??= Date.now();
4235
+ };
4236
+ scrapeSettled.then(markSettled, markSettled);
4237
+ const hardDeadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_MAX_MS;
4142
4238
  for (; ; ) {
4239
+ if (cancel.stopped)
4240
+ return false;
4143
4241
  const rec = await getTaskRecord(project, id).catch(() => void 0);
4144
4242
  if (rec?.firstTurnConfirmedAt)
4145
4243
  return true;
4244
+ const deadline = scrapeSettledAt !== void 0 ? Math.min(hardDeadline, scrapeSettledAt + FIRST_TURN_HOOK_CONFIRM_GRACE_MS) : hardDeadline;
4146
4245
  if (Date.now() >= deadline)
4147
4246
  return false;
4148
4247
  await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
@@ -4274,7 +4373,7 @@ async function runCrewSpawn(input, config, deps) {
4274
4373
  deps.onModelResolved?.({ agentName, model: crewModel });
4275
4374
  }
4276
4375
  if (agentName === "claude") {
4277
- fs9.mkdirSync(CC_SOCKS_DIR, { recursive: true });
4376
+ ensureSocksDir();
4278
4377
  const messagingSocketPath = path9.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
4279
4378
  const rec = await deps.dispatchCrew({
4280
4379
  provider: "claude",
@@ -4324,7 +4423,9 @@ async function runCrewSpawn(input, config, deps) {
4324
4423
 
4325
4424
  ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
4326
4425
  const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
4327
- const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id)) : await scrapeDelivered;
4426
+ const cancelHookPoll = { stopped: false };
4427
+ const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id, scrapeDelivered, cancelHookPoll)) : await scrapeDelivered;
4428
+ cancelHookPoll.stopped = true;
4328
4429
  if (!delivered) {
4329
4430
  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.
4330
4431
  `);
@@ -4606,7 +4707,7 @@ async function runCrewList(project, runtime, workspaceId) {
4606
4707
  surfaceId: c.surfaceId
4607
4708
  }));
4608
4709
  }
4609
- 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;
4710
+ 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;
4610
4711
  var init_crew_spawn = __esm({
4611
4712
  "packages/core/dist/crew-spawn.js"() {
4612
4713
  init_control_channel();
@@ -4618,7 +4719,8 @@ var init_crew_spawn = __esm({
4618
4719
  FIRST_TURN_INLINE_MAX_BYTES = 1200;
4619
4720
  TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
4620
4721
  STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
4621
- FIRST_TURN_HOOK_CONFIRM_WINDOW_MS = 1e5;
4722
+ FIRST_TURN_HOOK_CONFIRM_GRACE_MS = 15e3;
4723
+ FIRST_TURN_HOOK_CONFIRM_MAX_MS = 18e4;
4622
4724
  FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
4623
4725
  CLOSE_LOOKUP_RETRIES = 3;
4624
4726
  CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
@@ -4864,7 +4966,13 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
4864
4966
  log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
4865
4967
  }
4866
4968
  }
4867
- const result = await d.deliver(entry, async (text, sendOpts) => {
4969
+ let deliverEntry = entry;
4970
+ if (entry.kind === "captain.message" && entry.payload?.source === "daemon" && entry.message) {
4971
+ const prefix = stalePrefix(new Date(entry.ts).getTime(), Date.now());
4972
+ if (prefix)
4973
+ deliverEntry = { ...entry, message: `${prefix}${entry.message}` };
4974
+ }
4975
+ const result = await d.deliver(deliverEntry, async (text, sendOpts) => {
4868
4976
  let handledByChannel = false;
4869
4977
  try {
4870
4978
  const mode = ctx.captainChannelMode?.() ?? "off";
@@ -4956,6 +5064,7 @@ var init_delivery_loop = __esm({
4956
5064
  init_defer_delivery();
4957
5065
  init_dist();
4958
5066
  init_interactive_probe();
5067
+ init_down_alert();
4959
5068
  init_liveness2();
4960
5069
  init_captain_channel();
4961
5070
  CURSOR_SUBSCRIBER = "captain";
@@ -5390,8 +5499,8 @@ function startDaemon(ctx, opts, pkgVersion) {
5390
5499
  log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
5391
5500
  const bootTs = (/* @__PURE__ */ new Date()).toISOString();
5392
5501
  {
5393
- const sendDownAlert = (minutes, reasonText) => {
5394
- const text = `\u26A0\uFE0F daemon was down for ${minutes} min (last exit reason=${reasonText})`;
5502
+ const sendDownAlert = (startTs, endTs, minutes, reasonText) => {
5503
+ const text = formatDownAlertText({ startTs, endTs, minutes, reasonText });
5395
5504
  const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
5396
5505
  for (const project of alertProjects) {
5397
5506
  appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
@@ -5402,13 +5511,13 @@ function startDaemon(ctx, opts, pkgVersion) {
5402
5511
  if (marker) {
5403
5512
  log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
5404
5513
  if ((gapMs ?? 0) > 6e4)
5405
- sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
5514
+ sendDownAlert(marker.ts, bootTs, Math.round((gapMs ?? 0) / 6e4), marker.reason);
5406
5515
  } else if (prevRunning) {
5407
5516
  const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
5408
5517
  const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
5409
5518
  log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
5410
5519
  if (uncleanGapMs > 6e4) {
5411
- sendDownAlert(Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
5520
+ sendDownAlert(prevRunning.lastHeartbeatTs, bootTs, Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
5412
5521
  }
5413
5522
  } else {
5414
5523
  log("previous exit: none (clean or first boot)");
@@ -5530,6 +5639,7 @@ var init_start = __esm({
5530
5639
  init_server();
5531
5640
  init_mailbox();
5532
5641
  init_exit_marker();
5642
+ init_down_alert();
5533
5643
  init_liveness2();
5534
5644
  init_dist();
5535
5645
  init_snapshot_gather();
@@ -7499,6 +7609,7 @@ __export(dist_exports2, {
7499
7609
  OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
7500
7610
  PROBE_QUIET_MS: () => PROBE_QUIET_MS,
7501
7611
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
7612
+ READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
7502
7613
  STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
7503
7614
  TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
7504
7615
  TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
@@ -7547,6 +7658,7 @@ __export(dist_exports2, {
7547
7658
  crewTag: () => crewTag,
7548
7659
  daemonEntryPath: () => daemonEntryPath,
7549
7660
  daemonLockPath: () => daemonLockPath,
7661
+ decideForeignInstall: () => decideForeignInstall,
7550
7662
  decodeFrames: () => decodeFrames,
7551
7663
  defaultIsPidAlive: () => defaultIsPidAlive,
7552
7664
  defaultListenError: () => defaultListenError,
@@ -7564,6 +7676,7 @@ __export(dist_exports2, {
7564
7676
  encodeFrame: () => encodeFrame,
7565
7677
  encodeMsg: () => encodeMsg,
7566
7678
  ensureDaemon: () => ensureDaemon,
7679
+ ensureSocksDir: () => ensureSocksDir,
7567
7680
  evaluateStall: () => evaluateStall,
7568
7681
  exitMarkerPath: () => exitMarkerPath,
7569
7682
  fallsBackToPane: () => fallsBackToPane,
@@ -7587,9 +7700,11 @@ __export(dist_exports2, {
7587
7700
  isDaemonSocketLive: () => isDaemonSocketLive,
7588
7701
  isNotifyActive: () => isNotifyActive,
7589
7702
  isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
7703
+ isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
7590
7704
  isSideTitle: () => isSideTitle,
7591
7705
  isStickyAttention: () => isStickyAttention,
7592
7706
  isTurnAccepted: () => isTurnAccepted,
7707
+ isVersionUpgrade: () => isVersionUpgrade,
7593
7708
  kickstartArgv: () => kickstartArgv,
7594
7709
  launchOneWorkspace: () => launchOneWorkspace,
7595
7710
  loadSessions: () => loadSessions,
@@ -7606,6 +7721,7 @@ __export(dist_exports2, {
7606
7721
  parseProgramArgs: () => parseProgramArgs,
7607
7722
  plistPath: () => plistPath,
7608
7723
  printForeignInstallError: () => printForeignInstallError,
7724
+ printVersionUpgradeNotice: () => printVersionUpgradeNotice,
7609
7725
  programArgsBlock: () => programArgsBlock,
7610
7726
  projectHealth: () => projectHealth,
7611
7727
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
@@ -9291,9 +9407,12 @@ async function confirmedSendToPane(runtime, pane, message) {
9291
9407
  let sawDraft = await settleInputBox(runtime, pane);
9292
9408
  await runtime.sendKeyToPane(pane, "Enter");
9293
9409
  let repasted = false;
9410
+ let screenMoved = false;
9294
9411
  for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
9295
9412
  await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
9296
9413
  const afterScreen = await runtime.readPaneScreen(pane) ?? "";
9414
+ if (afterScreen !== preSendScreen)
9415
+ screenMoved = true;
9297
9416
  const draft = parseDraftFromScreen(afterScreen);
9298
9417
  if (draft !== "" && draft !== null)
9299
9418
  sawDraft = true;
@@ -9304,7 +9423,7 @@ async function confirmedSendToPane(runtime, pane, message) {
9304
9423
  const settled = await settleInputBox(runtime, pane);
9305
9424
  if (settled)
9306
9425
  sawDraft = true;
9307
- if (!sawDraft && !repasted) {
9426
+ if (!sawDraft && !repasted && !screenMoved) {
9308
9427
  repasted = true;
9309
9428
  await runtime.pasteToPane(pane, message);
9310
9429
  }
@@ -9368,9 +9487,12 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
9368
9487
  await runtime.sendKeyToPane(pane, "Enter");
9369
9488
  const retryLimit = acceptanceConfig?.retryLimit ?? SUBMIT_RETRY_LIMIT;
9370
9489
  let repasted = false;
9490
+ let screenMoved = false;
9371
9491
  for (let attempt = 0; attempt < retryLimit; attempt++) {
9372
9492
  await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
9373
9493
  const afterScreen = await runtime.readPaneScreen(pane) ?? "";
9494
+ if (afterScreen !== preSendScreen)
9495
+ screenMoved = true;
9374
9496
  const draft = parseDraftFromScreen(afterScreen);
9375
9497
  if (draft !== "" && draft !== null)
9376
9498
  sawDraft = true;
@@ -9381,13 +9503,13 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
9381
9503
  const settled = await settleInputBox(runtime, pane);
9382
9504
  if (settled)
9383
9505
  sawDraft = true;
9384
- if (!sawDraft && !repasted) {
9506
+ if (!sawDraft && !repasted && !screenMoved) {
9385
9507
  repasted = true;
9386
9508
  await runtime.pasteToPane(pane, task);
9387
9509
  }
9388
9510
  await runtime.sendKeyToPane(pane, "Enter");
9389
9511
  }
9390
- if (!sawDraft) {
9512
+ if (!sawDraft && !screenMoved) {
9391
9513
  return confirmedSendToPane(runtime, pane, task);
9392
9514
  }
9393
9515
  return { delivered: false };
@@ -16398,7 +16520,7 @@ var launchCommand = new Command14("launch").description(
16398
16520
  agentCmdFactory: (forceFresh) => {
16399
16521
  const captainChannelEnabled = shouldWireCaptainChannel(agentName, config);
16400
16522
  if (captainChannelEnabled) {
16401
- fs21.mkdirSync(CC_SOCKS_DIR, { recursive: true });
16523
+ ensureSocksDir();
16402
16524
  }
16403
16525
  return buildAgentCmd(
16404
16526
  agentName,
@@ -17826,6 +17948,7 @@ function registerSenderIdentity(socketPath2) {
17826
17948
  }
17827
17949
  async function sharedReceiptListener() {
17828
17950
  if (shared) return shared;
17951
+ ensureSocksDir();
17829
17952
  const socketPath2 = `${CC_SOCKS_DIR}/squadrantd-${process.pid}.sock`;
17830
17953
  const listener = new ClaudeReceiptListener({
17831
17954
  socketPath: socketPath2,
@@ -19303,6 +19426,49 @@ function queryClaudeMem(dbPath, project) {
19303
19426
  // packages/cli/src/lib/handoff-transcript.ts
19304
19427
  import fs31 from "fs";
19305
19428
  var TRANSCRIPT_BYTE_CAP = 2e5;
19429
+ var DIGEST_BYTE_CAP = 8 * 1024;
19430
+ var PROMPT_TRUNCATE_CHARS = 300;
19431
+ var ASSISTANT_TRUNCATE_CHARS = 300;
19432
+ function truncate2(text, maxChars) {
19433
+ const trimmed = text.trim();
19434
+ return trimmed.length <= maxChars ? trimmed : `${trimmed.slice(0, maxChars)}\u2026`;
19435
+ }
19436
+ var ISSUE_OR_PR_REF_RE = /#\d+/g;
19437
+ var COMMIT_REF_RE = /\b[0-9a-f]{7,40}\b/gi;
19438
+ function findRefs(text) {
19439
+ const refs = [];
19440
+ for (const m of text.matchAll(ISSUE_OR_PR_REF_RE)) refs.push(m[0]);
19441
+ for (const m of text.matchAll(COMMIT_REF_RE)) {
19442
+ const token = m[0];
19443
+ if (/[0-9]/.test(token) && /[a-f]/i.test(token)) refs.push(token.toLowerCase());
19444
+ }
19445
+ return refs;
19446
+ }
19447
+ function byteSize(value) {
19448
+ return Buffer.byteLength(JSON.stringify(value), "utf-8");
19449
+ }
19450
+ function capDigest(digest, capBytes = DIGEST_BYTE_CAP) {
19451
+ if (byteSize(digest) <= capBytes) return digest;
19452
+ const capped = {
19453
+ ...digest,
19454
+ userPrompts: [...digest.userPrompts],
19455
+ assistantTexts: [...digest.assistantTexts],
19456
+ filesTouched: [...digest.filesTouched],
19457
+ refs: [...digest.refs],
19458
+ truncated: true
19459
+ };
19460
+ let dropFromPrompts = true;
19461
+ while (byteSize(capped) > capBytes && (capped.userPrompts.length > 0 || capped.assistantTexts.length > 0)) {
19462
+ const target = dropFromPrompts && capped.userPrompts.length > 0 ? capped.userPrompts : capped.assistantTexts;
19463
+ if (target.length > 0) target.shift();
19464
+ dropFromPrompts = !dropFromPrompts;
19465
+ }
19466
+ while (byteSize(capped) > capBytes && (capped.filesTouched.length > 0 || capped.refs.length > 0)) {
19467
+ if (capped.filesTouched.length > 0) capped.filesTouched.shift();
19468
+ else capped.refs.shift();
19469
+ }
19470
+ return capped;
19471
+ }
19306
19472
  function tailOf(content, byteCap) {
19307
19473
  const buf = Buffer.from(content, "utf-8");
19308
19474
  if (buf.length <= byteCap) return content;
@@ -19312,6 +19478,15 @@ function tailOf(content, byteCap) {
19312
19478
  function extractMessages(tailText) {
19313
19479
  let lastUserMessage = null;
19314
19480
  let lastAssistantText = null;
19481
+ const userPrompts = [];
19482
+ const assistantTexts = [];
19483
+ const toolCalls = {};
19484
+ const filesTouched = /* @__PURE__ */ new Set();
19485
+ const refs = /* @__PURE__ */ new Set();
19486
+ let currentTurnText = null;
19487
+ const collectRefs = (text) => {
19488
+ for (const ref of findRefs(text)) refs.add(ref);
19489
+ };
19315
19490
  for (const line of tailText.split("\n")) {
19316
19491
  if (!line.trim()) continue;
19317
19492
  let obj;
@@ -19321,20 +19496,48 @@ function extractMessages(tailText) {
19321
19496
  continue;
19322
19497
  }
19323
19498
  if (obj.type === "user" && typeof obj.message?.content === "string") {
19499
+ if (currentTurnText !== null) {
19500
+ assistantTexts.push(truncate2(currentTurnText, ASSISTANT_TRUNCATE_CHARS));
19501
+ currentTurnText = null;
19502
+ }
19324
19503
  lastUserMessage = obj.message.content;
19504
+ userPrompts.push(truncate2(obj.message.content, PROMPT_TRUNCATE_CHARS));
19505
+ collectRefs(obj.message.content);
19325
19506
  } else if (obj.type === "assistant" && Array.isArray(obj.message?.content)) {
19326
- const texts = obj.message.content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text);
19327
- if (texts.length > 0) lastAssistantText = texts.join("\n");
19328
- }
19329
- }
19330
- return { lastUserMessage, lastAssistantText };
19507
+ const blocks = obj.message.content;
19508
+ const texts = blocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text);
19509
+ if (texts.length > 0) {
19510
+ currentTurnText = texts.join("\n");
19511
+ lastAssistantText = currentTurnText;
19512
+ collectRefs(currentTurnText);
19513
+ }
19514
+ for (const block of blocks) {
19515
+ if (block.type !== "tool_use" || typeof block.name !== "string") continue;
19516
+ toolCalls[block.name] = (toolCalls[block.name] ?? 0) + 1;
19517
+ const filePath = block.input?.file_path;
19518
+ if (typeof filePath === "string") filesTouched.add(filePath);
19519
+ }
19520
+ }
19521
+ }
19522
+ if (currentTurnText !== null) {
19523
+ assistantTexts.push(truncate2(currentTurnText, ASSISTANT_TRUNCATE_CHARS));
19524
+ }
19525
+ const digest = capDigest({
19526
+ userPrompts,
19527
+ assistantTexts,
19528
+ toolCalls,
19529
+ filesTouched: [...filesTouched],
19530
+ refs: [...refs],
19531
+ truncated: false
19532
+ });
19533
+ return { lastUserMessage, lastAssistantText, digest };
19331
19534
  }
19332
19535
  function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
19333
19536
  if (!fs31.existsSync(transcriptPath)) return null;
19334
19537
  const content = fs31.readFileSync(transcriptPath, "utf-8");
19335
- const { lastUserMessage, lastAssistantText } = extractMessages(tailOf(content, byteCap));
19538
+ const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
19336
19539
  const mtimeIso = fs31.statSync(transcriptPath).mtime.toISOString();
19337
- return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText };
19540
+ return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
19338
19541
  }
19339
19542
 
19340
19543
  // packages/cli/src/lib/handoff-archive.ts
@@ -19450,7 +19653,7 @@ if (process.argv[2] !== "config") {
19450
19653
  } catch {
19451
19654
  }
19452
19655
  }
19453
- if (!process.env.SQUADRANT_DAEMON_SKIP) {
19656
+ if (!process.env.SQUADRANT_DAEMON_SKIP && !isReadOnlyCrewCommand(process.argv)) {
19454
19657
  ensureDaemon(void 0, { operatorInitiated: isOperatorInitiatedCommand(process.argv[2]) });
19455
19658
  }
19456
19659
  var program = new Command35();