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 +232 -29
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +138 -18
- 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";
|
|
@@ -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
|
-
|
|
4141
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
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();
|