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/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 {
|
|
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
|
-
|
|
3257
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
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();
|