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 +223 -27
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +130 -17
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/squadrantd.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
2557
|
-
|
|
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";
|
|
@@ -3442,12 +3528,20 @@ function ensureSocksDir(dir = CC_SOCKS_DIR) {
|
|
|
3442
3528
|
if ((fs9.statSync(dir).mode & 511) !== 448)
|
|
3443
3529
|
fs9.chmodSync(dir, 448);
|
|
3444
3530
|
}
|
|
3445
|
-
async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
|
|
3446
|
-
|
|
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;
|
|
3447
3538
|
for (; ; ) {
|
|
3539
|
+
if (cancel.stopped)
|
|
3540
|
+
return false;
|
|
3448
3541
|
const rec = await getTaskRecord(project, id).catch(() => void 0);
|
|
3449
3542
|
if (rec?.firstTurnConfirmedAt)
|
|
3450
3543
|
return true;
|
|
3544
|
+
const deadline = scrapeSettledAt !== void 0 ? Math.min(hardDeadline, scrapeSettledAt + FIRST_TURN_HOOK_CONFIRM_GRACE_MS) : hardDeadline;
|
|
3451
3545
|
if (Date.now() >= deadline)
|
|
3452
3546
|
return false;
|
|
3453
3547
|
await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
|
|
@@ -3629,7 +3723,9 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3629
3723
|
|
|
3630
3724
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
3631
3725
|
const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
|
|
3632
|
-
const
|
|
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;
|
|
3633
3729
|
if (!delivered) {
|
|
3634
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.
|
|
3635
3731
|
`);
|
|
@@ -3911,7 +4007,7 @@ async function runCrewList(project, runtime, workspaceId) {
|
|
|
3911
4007
|
surfaceId: c.surfaceId
|
|
3912
4008
|
}));
|
|
3913
4009
|
}
|
|
3914
|
-
var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT,
|
|
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;
|
|
3915
4011
|
var init_crew_spawn = __esm({
|
|
3916
4012
|
"packages/core/dist/crew-spawn.js"() {
|
|
3917
4013
|
init_control_channel();
|
|
@@ -3923,7 +4019,8 @@ var init_crew_spawn = __esm({
|
|
|
3923
4019
|
FIRST_TURN_INLINE_MAX_BYTES = 1200;
|
|
3924
4020
|
TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
|
|
3925
4021
|
STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
|
|
3926
|
-
|
|
4022
|
+
FIRST_TURN_HOOK_CONFIRM_GRACE_MS = 15e3;
|
|
4023
|
+
FIRST_TURN_HOOK_CONFIRM_MAX_MS = 18e4;
|
|
3927
4024
|
FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
|
|
3928
4025
|
CLOSE_LOOKUP_RETRIES = 3;
|
|
3929
4026
|
CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
|
|
@@ -4169,7 +4266,13 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4169
4266
|
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
|
|
4170
4267
|
}
|
|
4171
4268
|
}
|
|
4172
|
-
|
|
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) => {
|
|
4173
4276
|
let handledByChannel = false;
|
|
4174
4277
|
try {
|
|
4175
4278
|
const mode = ctx.captainChannelMode?.() ?? "off";
|
|
@@ -4261,6 +4364,7 @@ var init_delivery_loop = __esm({
|
|
|
4261
4364
|
init_defer_delivery();
|
|
4262
4365
|
init_dist();
|
|
4263
4366
|
init_interactive_probe();
|
|
4367
|
+
init_down_alert();
|
|
4264
4368
|
init_liveness2();
|
|
4265
4369
|
init_captain_channel();
|
|
4266
4370
|
CURSOR_SUBSCRIBER = "captain";
|
|
@@ -4695,8 +4799,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4695
4799
|
log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
|
|
4696
4800
|
const bootTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
4697
4801
|
{
|
|
4698
|
-
const sendDownAlert = (minutes, reasonText) => {
|
|
4699
|
-
const text =
|
|
4802
|
+
const sendDownAlert = (startTs, endTs, minutes, reasonText) => {
|
|
4803
|
+
const text = formatDownAlertText({ startTs, endTs, minutes, reasonText });
|
|
4700
4804
|
const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4701
4805
|
for (const project of alertProjects) {
|
|
4702
4806
|
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
|
|
@@ -4707,13 +4811,13 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4707
4811
|
if (marker) {
|
|
4708
4812
|
log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
|
|
4709
4813
|
if ((gapMs ?? 0) > 6e4)
|
|
4710
|
-
sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
|
|
4814
|
+
sendDownAlert(marker.ts, bootTs, Math.round((gapMs ?? 0) / 6e4), marker.reason);
|
|
4711
4815
|
} else if (prevRunning) {
|
|
4712
4816
|
const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
|
|
4713
4817
|
const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
|
|
4714
4818
|
log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
|
|
4715
4819
|
if (uncleanGapMs > 6e4) {
|
|
4716
|
-
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");
|
|
4717
4821
|
}
|
|
4718
4822
|
} else {
|
|
4719
4823
|
log("previous exit: none (clean or first boot)");
|
|
@@ -4835,6 +4939,7 @@ var init_start = __esm({
|
|
|
4835
4939
|
init_server();
|
|
4836
4940
|
init_mailbox();
|
|
4837
4941
|
init_exit_marker();
|
|
4942
|
+
init_down_alert();
|
|
4838
4943
|
init_liveness2();
|
|
4839
4944
|
init_dist();
|
|
4840
4945
|
init_snapshot_gather();
|
|
@@ -6804,6 +6909,7 @@ __export(dist_exports, {
|
|
|
6804
6909
|
OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
|
|
6805
6910
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
6806
6911
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
6912
|
+
READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
|
|
6807
6913
|
STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
|
|
6808
6914
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
6809
6915
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
@@ -6852,6 +6958,7 @@ __export(dist_exports, {
|
|
|
6852
6958
|
crewTag: () => crewTag,
|
|
6853
6959
|
daemonEntryPath: () => daemonEntryPath,
|
|
6854
6960
|
daemonLockPath: () => daemonLockPath,
|
|
6961
|
+
decideForeignInstall: () => decideForeignInstall,
|
|
6855
6962
|
decodeFrames: () => decodeFrames,
|
|
6856
6963
|
defaultIsPidAlive: () => defaultIsPidAlive,
|
|
6857
6964
|
defaultListenError: () => defaultListenError,
|
|
@@ -6893,9 +7000,11 @@ __export(dist_exports, {
|
|
|
6893
7000
|
isDaemonSocketLive: () => isDaemonSocketLive,
|
|
6894
7001
|
isNotifyActive: () => isNotifyActive,
|
|
6895
7002
|
isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
|
|
7003
|
+
isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
|
|
6896
7004
|
isSideTitle: () => isSideTitle,
|
|
6897
7005
|
isStickyAttention: () => isStickyAttention,
|
|
6898
7006
|
isTurnAccepted: () => isTurnAccepted,
|
|
7007
|
+
isVersionUpgrade: () => isVersionUpgrade,
|
|
6899
7008
|
kickstartArgv: () => kickstartArgv,
|
|
6900
7009
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
6901
7010
|
loadSessions: () => loadSessions,
|
|
@@ -6912,6 +7021,7 @@ __export(dist_exports, {
|
|
|
6912
7021
|
parseProgramArgs: () => parseProgramArgs,
|
|
6913
7022
|
plistPath: () => plistPath,
|
|
6914
7023
|
printForeignInstallError: () => printForeignInstallError,
|
|
7024
|
+
printVersionUpgradeNotice: () => printVersionUpgradeNotice,
|
|
6915
7025
|
programArgsBlock: () => programArgsBlock,
|
|
6916
7026
|
projectHealth: () => projectHealth,
|
|
6917
7027
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
@@ -9809,9 +9919,12 @@ async function confirmedSendToPane(runtime, pane, message) {
|
|
|
9809
9919
|
let sawDraft = await settleInputBox(runtime, pane);
|
|
9810
9920
|
await runtime.sendKeyToPane(pane, "Enter");
|
|
9811
9921
|
let repasted = false;
|
|
9922
|
+
let screenMoved = false;
|
|
9812
9923
|
for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
|
|
9813
9924
|
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
9814
9925
|
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
9926
|
+
if (afterScreen !== preSendScreen)
|
|
9927
|
+
screenMoved = true;
|
|
9815
9928
|
const draft = parseDraftFromScreen(afterScreen);
|
|
9816
9929
|
if (draft !== "" && draft !== null)
|
|
9817
9930
|
sawDraft = true;
|
|
@@ -9822,7 +9935,7 @@ async function confirmedSendToPane(runtime, pane, message) {
|
|
|
9822
9935
|
const settled = await settleInputBox(runtime, pane);
|
|
9823
9936
|
if (settled)
|
|
9824
9937
|
sawDraft = true;
|
|
9825
|
-
if (!sawDraft && !repasted) {
|
|
9938
|
+
if (!sawDraft && !repasted && !screenMoved) {
|
|
9826
9939
|
repasted = true;
|
|
9827
9940
|
await runtime.pasteToPane(pane, message);
|
|
9828
9941
|
}
|