squadrant 0.19.3 → 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";
@@ -4142,12 +4228,20 @@ function ensureSocksDir(dir = CC_SOCKS_DIR) {
4142
4228
  if ((fs9.statSync(dir).mode & 511) !== 448)
4143
4229
  fs9.chmodSync(dir, 448);
4144
4230
  }
4145
- async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
4146
- const deadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_WINDOW_MS;
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;
4147
4238
  for (; ; ) {
4239
+ if (cancel.stopped)
4240
+ return false;
4148
4241
  const rec = await getTaskRecord(project, id).catch(() => void 0);
4149
4242
  if (rec?.firstTurnConfirmedAt)
4150
4243
  return true;
4244
+ const deadline = scrapeSettledAt !== void 0 ? Math.min(hardDeadline, scrapeSettledAt + FIRST_TURN_HOOK_CONFIRM_GRACE_MS) : hardDeadline;
4151
4245
  if (Date.now() >= deadline)
4152
4246
  return false;
4153
4247
  await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
@@ -4329,7 +4423,9 @@ async function runCrewSpawn(input, config, deps) {
4329
4423
 
4330
4424
  ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
4331
4425
  const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
4332
- 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;
4333
4429
  if (!delivered) {
4334
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.
4335
4431
  `);
@@ -4611,7 +4707,7 @@ async function runCrewList(project, runtime, workspaceId) {
4611
4707
  surfaceId: c.surfaceId
4612
4708
  }));
4613
4709
  }
4614
- 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;
4615
4711
  var init_crew_spawn = __esm({
4616
4712
  "packages/core/dist/crew-spawn.js"() {
4617
4713
  init_control_channel();
@@ -4623,7 +4719,8 @@ var init_crew_spawn = __esm({
4623
4719
  FIRST_TURN_INLINE_MAX_BYTES = 1200;
4624
4720
  TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
4625
4721
  STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
4626
- FIRST_TURN_HOOK_CONFIRM_WINDOW_MS = 1e5;
4722
+ FIRST_TURN_HOOK_CONFIRM_GRACE_MS = 15e3;
4723
+ FIRST_TURN_HOOK_CONFIRM_MAX_MS = 18e4;
4627
4724
  FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
4628
4725
  CLOSE_LOOKUP_RETRIES = 3;
4629
4726
  CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
@@ -4869,7 +4966,13 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
4869
4966
  log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
4870
4967
  }
4871
4968
  }
4872
- 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) => {
4873
4976
  let handledByChannel = false;
4874
4977
  try {
4875
4978
  const mode = ctx.captainChannelMode?.() ?? "off";
@@ -4961,6 +5064,7 @@ var init_delivery_loop = __esm({
4961
5064
  init_defer_delivery();
4962
5065
  init_dist();
4963
5066
  init_interactive_probe();
5067
+ init_down_alert();
4964
5068
  init_liveness2();
4965
5069
  init_captain_channel();
4966
5070
  CURSOR_SUBSCRIBER = "captain";
@@ -5395,8 +5499,8 @@ function startDaemon(ctx, opts, pkgVersion) {
5395
5499
  log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
5396
5500
  const bootTs = (/* @__PURE__ */ new Date()).toISOString();
5397
5501
  {
5398
- const sendDownAlert = (minutes, reasonText) => {
5399
- 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 });
5400
5504
  const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
5401
5505
  for (const project of alertProjects) {
5402
5506
  appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
@@ -5407,13 +5511,13 @@ function startDaemon(ctx, opts, pkgVersion) {
5407
5511
  if (marker) {
5408
5512
  log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
5409
5513
  if ((gapMs ?? 0) > 6e4)
5410
- sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
5514
+ sendDownAlert(marker.ts, bootTs, Math.round((gapMs ?? 0) / 6e4), marker.reason);
5411
5515
  } else if (prevRunning) {
5412
5516
  const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
5413
5517
  const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
5414
5518
  log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
5415
5519
  if (uncleanGapMs > 6e4) {
5416
- 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");
5417
5521
  }
5418
5522
  } else {
5419
5523
  log("previous exit: none (clean or first boot)");
@@ -5535,6 +5639,7 @@ var init_start = __esm({
5535
5639
  init_server();
5536
5640
  init_mailbox();
5537
5641
  init_exit_marker();
5642
+ init_down_alert();
5538
5643
  init_liveness2();
5539
5644
  init_dist();
5540
5645
  init_snapshot_gather();
@@ -7504,6 +7609,7 @@ __export(dist_exports2, {
7504
7609
  OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
7505
7610
  PROBE_QUIET_MS: () => PROBE_QUIET_MS,
7506
7611
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
7612
+ READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
7507
7613
  STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
7508
7614
  TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
7509
7615
  TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
@@ -7552,6 +7658,7 @@ __export(dist_exports2, {
7552
7658
  crewTag: () => crewTag,
7553
7659
  daemonEntryPath: () => daemonEntryPath,
7554
7660
  daemonLockPath: () => daemonLockPath,
7661
+ decideForeignInstall: () => decideForeignInstall,
7555
7662
  decodeFrames: () => decodeFrames,
7556
7663
  defaultIsPidAlive: () => defaultIsPidAlive,
7557
7664
  defaultListenError: () => defaultListenError,
@@ -7593,9 +7700,11 @@ __export(dist_exports2, {
7593
7700
  isDaemonSocketLive: () => isDaemonSocketLive,
7594
7701
  isNotifyActive: () => isNotifyActive,
7595
7702
  isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
7703
+ isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
7596
7704
  isSideTitle: () => isSideTitle,
7597
7705
  isStickyAttention: () => isStickyAttention,
7598
7706
  isTurnAccepted: () => isTurnAccepted,
7707
+ isVersionUpgrade: () => isVersionUpgrade,
7599
7708
  kickstartArgv: () => kickstartArgv,
7600
7709
  launchOneWorkspace: () => launchOneWorkspace,
7601
7710
  loadSessions: () => loadSessions,
@@ -7612,6 +7721,7 @@ __export(dist_exports2, {
7612
7721
  parseProgramArgs: () => parseProgramArgs,
7613
7722
  plistPath: () => plistPath,
7614
7723
  printForeignInstallError: () => printForeignInstallError,
7724
+ printVersionUpgradeNotice: () => printVersionUpgradeNotice,
7615
7725
  programArgsBlock: () => programArgsBlock,
7616
7726
  projectHealth: () => projectHealth,
7617
7727
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
@@ -9297,9 +9407,12 @@ async function confirmedSendToPane(runtime, pane, message) {
9297
9407
  let sawDraft = await settleInputBox(runtime, pane);
9298
9408
  await runtime.sendKeyToPane(pane, "Enter");
9299
9409
  let repasted = false;
9410
+ let screenMoved = false;
9300
9411
  for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
9301
9412
  await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
9302
9413
  const afterScreen = await runtime.readPaneScreen(pane) ?? "";
9414
+ if (afterScreen !== preSendScreen)
9415
+ screenMoved = true;
9303
9416
  const draft = parseDraftFromScreen(afterScreen);
9304
9417
  if (draft !== "" && draft !== null)
9305
9418
  sawDraft = true;
@@ -9310,7 +9423,7 @@ async function confirmedSendToPane(runtime, pane, message) {
9310
9423
  const settled = await settleInputBox(runtime, pane);
9311
9424
  if (settled)
9312
9425
  sawDraft = true;
9313
- if (!sawDraft && !repasted) {
9426
+ if (!sawDraft && !repasted && !screenMoved) {
9314
9427
  repasted = true;
9315
9428
  await runtime.pasteToPane(pane, message);
9316
9429
  }
@@ -9374,9 +9487,12 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
9374
9487
  await runtime.sendKeyToPane(pane, "Enter");
9375
9488
  const retryLimit = acceptanceConfig?.retryLimit ?? SUBMIT_RETRY_LIMIT;
9376
9489
  let repasted = false;
9490
+ let screenMoved = false;
9377
9491
  for (let attempt = 0; attempt < retryLimit; attempt++) {
9378
9492
  await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
9379
9493
  const afterScreen = await runtime.readPaneScreen(pane) ?? "";
9494
+ if (afterScreen !== preSendScreen)
9495
+ screenMoved = true;
9380
9496
  const draft = parseDraftFromScreen(afterScreen);
9381
9497
  if (draft !== "" && draft !== null)
9382
9498
  sawDraft = true;
@@ -9387,13 +9503,13 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
9387
9503
  const settled = await settleInputBox(runtime, pane);
9388
9504
  if (settled)
9389
9505
  sawDraft = true;
9390
- if (!sawDraft && !repasted) {
9506
+ if (!sawDraft && !repasted && !screenMoved) {
9391
9507
  repasted = true;
9392
9508
  await runtime.pasteToPane(pane, task);
9393
9509
  }
9394
9510
  await runtime.sendKeyToPane(pane, "Enter");
9395
9511
  }
9396
- if (!sawDraft) {
9512
+ if (!sawDraft && !screenMoved) {
9397
9513
  return confirmedSendToPane(runtime, pane, task);
9398
9514
  }
9399
9515
  return { delivered: false };
@@ -19310,6 +19426,49 @@ function queryClaudeMem(dbPath, project) {
19310
19426
  // packages/cli/src/lib/handoff-transcript.ts
19311
19427
  import fs31 from "fs";
19312
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
+ }
19313
19472
  function tailOf(content, byteCap) {
19314
19473
  const buf = Buffer.from(content, "utf-8");
19315
19474
  if (buf.length <= byteCap) return content;
@@ -19319,6 +19478,15 @@ function tailOf(content, byteCap) {
19319
19478
  function extractMessages(tailText) {
19320
19479
  let lastUserMessage = null;
19321
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
+ };
19322
19490
  for (const line of tailText.split("\n")) {
19323
19491
  if (!line.trim()) continue;
19324
19492
  let obj;
@@ -19328,20 +19496,48 @@ function extractMessages(tailText) {
19328
19496
  continue;
19329
19497
  }
19330
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
+ }
19331
19503
  lastUserMessage = obj.message.content;
19504
+ userPrompts.push(truncate2(obj.message.content, PROMPT_TRUNCATE_CHARS));
19505
+ collectRefs(obj.message.content);
19332
19506
  } else if (obj.type === "assistant" && Array.isArray(obj.message?.content)) {
19333
- const texts = obj.message.content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text);
19334
- if (texts.length > 0) lastAssistantText = texts.join("\n");
19335
- }
19336
- }
19337
- 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 };
19338
19534
  }
19339
19535
  function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
19340
19536
  if (!fs31.existsSync(transcriptPath)) return null;
19341
19537
  const content = fs31.readFileSync(transcriptPath, "utf-8");
19342
- const { lastUserMessage, lastAssistantText } = extractMessages(tailOf(content, byteCap));
19538
+ const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
19343
19539
  const mtimeIso = fs31.statSync(transcriptPath).mtime.toISOString();
19344
- return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText };
19540
+ return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
19345
19541
  }
19346
19542
 
19347
19543
  // packages/cli/src/lib/handoff-archive.ts
@@ -19457,7 +19653,7 @@ if (process.argv[2] !== "config") {
19457
19653
  } catch {
19458
19654
  }
19459
19655
  }
19460
- if (!process.env.SQUADRANT_DAEMON_SKIP) {
19656
+ if (!process.env.SQUADRANT_DAEMON_SKIP && !isReadOnlyCrewCommand(process.argv)) {
19461
19657
  ensureDaemon(void 0, { operatorInitiated: isOperatorInitiatedCommand(process.argv[2]) });
19462
19658
  }
19463
19659
  var program = new Command35();