squadrant 0.19.3 → 0.19.5
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 +358 -39
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +151 -19
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1587,6 +1587,11 @@ function reduce(rec, ev, now) {
|
|
|
1587
1587
|
return stampAttempt(base, { resumeRef: ev.resumeRef }, now);
|
|
1588
1588
|
case "task.turn.started":
|
|
1589
1589
|
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0, pendingMonitor: void 0 };
|
|
1590
|
+
case "task.turn.failed":
|
|
1591
|
+
// #763: a crew's turn died on an API error (StopFailure hook). Anti-#2576:
|
|
1592
|
+
// NOT task.failed — a hook may never terminalize a task. Falls through to
|
|
1593
|
+
// the exact same turn-boundary transition as task.turn.completed below;
|
|
1594
|
+
// only the notify message (reduce.ts formatMessage) differs.
|
|
1590
1595
|
case "task.turn.completed":
|
|
1591
1596
|
if (isStickyAttention(rec.state))
|
|
1592
1597
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
@@ -1702,6 +1707,9 @@ function formatMessage(rec, event) {
|
|
|
1702
1707
|
return `CREW STALLED ${tag}: no heartbeat in ${rec.heartbeatBudgetMs}ms`;
|
|
1703
1708
|
}
|
|
1704
1709
|
case "awaiting-input":
|
|
1710
|
+
if (event?.type === "task.turn.failed") {
|
|
1711
|
+
return `CREW TURN FAILED ${tag}: API error ended the turn (not a stall) \u2014 ${event.error}`;
|
|
1712
|
+
}
|
|
1705
1713
|
return `CREW IDLE ${tag}: turn ended, awaiting your reply.`;
|
|
1706
1714
|
default:
|
|
1707
1715
|
return null;
|
|
@@ -2111,6 +2119,7 @@ var init_reduce = __esm({
|
|
|
2111
2119
|
"task.session",
|
|
2112
2120
|
"task.turn.started",
|
|
2113
2121
|
"task.turn.completed",
|
|
2122
|
+
"task.turn.failed",
|
|
2114
2123
|
"task.delta",
|
|
2115
2124
|
"task.input.requested",
|
|
2116
2125
|
"task.approval.requested",
|
|
@@ -3022,6 +3031,26 @@ function parseProgramArgs(plistXml) {
|
|
|
3022
3031
|
return null;
|
|
3023
3032
|
return { nodeBin: xmlUnescape(m[1]), daemonEntry: xmlUnescape(m[2]) };
|
|
3024
3033
|
}
|
|
3034
|
+
function isVersionUpgrade(registeredEntry, thisEntry) {
|
|
3035
|
+
const a = registeredEntry.split("/");
|
|
3036
|
+
const b = thisEntry.split("/");
|
|
3037
|
+
if (a.length !== b.length)
|
|
3038
|
+
return false;
|
|
3039
|
+
let diffIndex = -1;
|
|
3040
|
+
for (let i = 0; i < a.length; i++) {
|
|
3041
|
+
if (a[i] !== b[i]) {
|
|
3042
|
+
if (diffIndex !== -1)
|
|
3043
|
+
return false;
|
|
3044
|
+
diffIndex = i;
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
if (diffIndex === -1)
|
|
3048
|
+
return false;
|
|
3049
|
+
const versionedSegment = /^(.+)@([^@]+)$/;
|
|
3050
|
+
const ma = a[diffIndex].match(versionedSegment);
|
|
3051
|
+
const mb = b[diffIndex].match(versionedSegment);
|
|
3052
|
+
return ma !== null && mb !== null && ma[1] === mb[1] && ma[2] !== mb[2];
|
|
3053
|
+
}
|
|
3025
3054
|
function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
|
|
3026
3055
|
if (!parsed)
|
|
3027
3056
|
return null;
|
|
@@ -3029,7 +3058,11 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
|
|
|
3029
3058
|
return null;
|
|
3030
3059
|
if (!registeredEntryExists)
|
|
3031
3060
|
return null;
|
|
3032
|
-
return {
|
|
3061
|
+
return {
|
|
3062
|
+
registeredEntry: parsed.daemonEntry,
|
|
3063
|
+
thisEntry,
|
|
3064
|
+
isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
|
|
3065
|
+
};
|
|
3033
3066
|
}
|
|
3034
3067
|
function sanitizePathForPlist(path36) {
|
|
3035
3068
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3196,7 +3229,7 @@ function forceKickstartAndVerify(target, opts = {}) {
|
|
|
3196
3229
|
const pollDelayMs = opts.pollDelayMs ?? 300;
|
|
3197
3230
|
const kickstartRetries = opts.kickstartRetries ?? 10;
|
|
3198
3231
|
const kickstartRetryDelayMs = opts.kickstartRetryDelayMs ?? 300;
|
|
3199
|
-
const pidBefore = getDaemonPid(target);
|
|
3232
|
+
const pidBefore = opts.pidBefore !== void 0 ? opts.pidBefore : getDaemonPid(target);
|
|
3200
3233
|
let kickstartError = null;
|
|
3201
3234
|
for (let i = 0; i < kickstartRetries; i++) {
|
|
3202
3235
|
try {
|
|
@@ -3233,6 +3266,9 @@ function forceKickstartAndVerify(target, opts = {}) {
|
|
|
3233
3266
|
function isOperatorInitiatedCommand(topLevelArg) {
|
|
3234
3267
|
return topLevelArg !== void 0 && OPERATOR_INITIATED_COMMANDS.has(topLevelArg);
|
|
3235
3268
|
}
|
|
3269
|
+
function isReadOnlyCrewCommand(argv) {
|
|
3270
|
+
return argv[2] === "crew" && argv[3] !== void 0 && READ_ONLY_CREW_SUBCOMMANDS.has(argv[3]);
|
|
3271
|
+
}
|
|
3236
3272
|
function ensureDaemon(nodeBin = process.execPath, opts = {}) {
|
|
3237
3273
|
if (restartInFlight)
|
|
3238
3274
|
return;
|
|
@@ -3253,8 +3289,10 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
|
|
|
3253
3289
|
try {
|
|
3254
3290
|
const drift = computeDaemonDrift(nodeBin);
|
|
3255
3291
|
if (drift.foreignInstall) {
|
|
3256
|
-
|
|
3257
|
-
|
|
3292
|
+
const decision = decideForeignInstall(drift.foreignInstall);
|
|
3293
|
+
process.stderr.write(decision.message);
|
|
3294
|
+
if (!decision.proceed)
|
|
3295
|
+
return;
|
|
3258
3296
|
}
|
|
3259
3297
|
applyDaemonDrift(drift);
|
|
3260
3298
|
} catch (e) {
|
|
@@ -3264,6 +3302,12 @@ function ensureDaemon(nodeBin = process.execPath, opts = {}) {
|
|
|
3264
3302
|
releaseDaemonLock();
|
|
3265
3303
|
}
|
|
3266
3304
|
}
|
|
3305
|
+
function decideForeignInstall(foreign) {
|
|
3306
|
+
if (foreign.isUpgrade) {
|
|
3307
|
+
return { proceed: true, message: printVersionUpgradeNotice(foreign) };
|
|
3308
|
+
}
|
|
3309
|
+
return { proceed: false, message: printForeignInstallError(foreign) };
|
|
3310
|
+
}
|
|
3267
3311
|
function printForeignInstallError(foreign) {
|
|
3268
3312
|
return `[squadrant] refusing to restart the daemon: the registered launchd config belongs to a DIFFERENT squadrant install than this one.
|
|
3269
3313
|
registered install: ${foreign.registeredEntry}
|
|
@@ -3271,14 +3315,19 @@ function printForeignInstallError(foreign) {
|
|
|
3271
3315
|
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
3316
|
`;
|
|
3273
3317
|
}
|
|
3318
|
+
function printVersionUpgradeNotice(foreign) {
|
|
3319
|
+
return `[squadrant] upgraded plist: ${foreign.registeredEntry} -> ${foreign.thisEntry}
|
|
3320
|
+
`;
|
|
3321
|
+
}
|
|
3274
3322
|
function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
|
|
3275
3323
|
if (!tryAcquireDaemonLock()) {
|
|
3276
3324
|
throw new Error("could not acquire the daemon lock \u2014 another squadrant process is already restarting the daemon");
|
|
3277
3325
|
}
|
|
3278
3326
|
try {
|
|
3279
3327
|
const drift = computeDaemonDrift(nodeBin);
|
|
3328
|
+
const pidBeforeReconcile = getDaemonPid(drift.target);
|
|
3280
3329
|
reconcilePlistAndService(drift);
|
|
3281
|
-
const result = forceKickstartAndVerify(drift.target, kickstartOpts);
|
|
3330
|
+
const result = forceKickstartAndVerify(drift.target, { ...kickstartOpts, pidBefore: pidBeforeReconcile });
|
|
3282
3331
|
if (!result.restarted) {
|
|
3283
3332
|
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
3333
|
}
|
|
@@ -3287,13 +3336,14 @@ function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
|
|
|
3287
3336
|
releaseDaemonLock();
|
|
3288
3337
|
}
|
|
3289
3338
|
}
|
|
3290
|
-
var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS;
|
|
3339
|
+
var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS, READ_ONLY_CREW_SUBCOMMANDS;
|
|
3291
3340
|
var init_launchd = __esm({
|
|
3292
3341
|
"packages/core/dist/launchd.js"() {
|
|
3293
3342
|
LABEL = "com.squadrant.daemon";
|
|
3294
3343
|
AGENT_BINS = ["cmux", "claude", "opencode", "codex", "gemini", "node"];
|
|
3295
3344
|
restartInFlight = false;
|
|
3296
3345
|
OPERATOR_INITIATED_COMMANDS = /* @__PURE__ */ new Set(["launch", "init"]);
|
|
3346
|
+
READ_ONLY_CREW_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "read", "tasks"]);
|
|
3297
3347
|
}
|
|
3298
3348
|
});
|
|
3299
3349
|
|
|
@@ -3990,6 +4040,51 @@ var init_captain_delivery = __esm({
|
|
|
3990
4040
|
}
|
|
3991
4041
|
});
|
|
3992
4042
|
|
|
4043
|
+
// packages/core/dist/daemon/down-alert.js
|
|
4044
|
+
function pad2(n) {
|
|
4045
|
+
return String(n).padStart(2, "0");
|
|
4046
|
+
}
|
|
4047
|
+
function formatLocalDateTime(d) {
|
|
4048
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
4049
|
+
}
|
|
4050
|
+
function formatTzOffset(d) {
|
|
4051
|
+
const offMin = -d.getTimezoneOffset();
|
|
4052
|
+
const sign = offMin >= 0 ? "+" : "-";
|
|
4053
|
+
const abs = Math.abs(offMin);
|
|
4054
|
+
const hh = pad2(Math.floor(abs / 60));
|
|
4055
|
+
const mm = abs % 60;
|
|
4056
|
+
return mm === 0 ? `${sign}${hh}` : `${sign}${hh}:${pad2(mm)}`;
|
|
4057
|
+
}
|
|
4058
|
+
function formatDownAlertText(opts) {
|
|
4059
|
+
const start = new Date(opts.startTs);
|
|
4060
|
+
const end = new Date(opts.endTs);
|
|
4061
|
+
return `\u26A0\uFE0F daemon was down ${formatLocalDateTime(start)} \u2192 ${formatLocalDateTime(end)} (${formatTzOffset(end)}), ${opts.minutes} min (last exit reason=${opts.reasonText})`;
|
|
4062
|
+
}
|
|
4063
|
+
function formatRelative(ms) {
|
|
4064
|
+
const minutes = Math.round(ms / 6e4);
|
|
4065
|
+
if (minutes < 60)
|
|
4066
|
+
return `${minutes} min`;
|
|
4067
|
+
const hours = Math.floor(minutes / 60);
|
|
4068
|
+
const remMinutes = minutes % 60;
|
|
4069
|
+
if (hours < 24)
|
|
4070
|
+
return remMinutes > 0 ? `${hours}h ${remMinutes}m` : `${hours}h`;
|
|
4071
|
+
const days = Math.floor(hours / 24);
|
|
4072
|
+
const remHours = hours % 24;
|
|
4073
|
+
return remHours > 0 ? `${days}d ${remHours}h` : `${days}d`;
|
|
4074
|
+
}
|
|
4075
|
+
function stalePrefix(generatedAtMs, deliveredAtMs) {
|
|
4076
|
+
const ageMs = deliveredAtMs - generatedAtMs;
|
|
4077
|
+
if (ageMs <= STALE_ALERT_THRESHOLD_MS)
|
|
4078
|
+
return null;
|
|
4079
|
+
return `[stale \u2014 generated ${formatRelative(ageMs)} ago] `;
|
|
4080
|
+
}
|
|
4081
|
+
var STALE_ALERT_THRESHOLD_MS;
|
|
4082
|
+
var init_down_alert = __esm({
|
|
4083
|
+
"packages/core/dist/daemon/down-alert.js"() {
|
|
4084
|
+
STALE_ALERT_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
4085
|
+
}
|
|
4086
|
+
});
|
|
4087
|
+
|
|
3993
4088
|
// packages/core/dist/control-channel.js
|
|
3994
4089
|
function fallsBackToPane(o) {
|
|
3995
4090
|
return o.status === "gone" || o.status === "unsupported";
|
|
@@ -4142,12 +4237,20 @@ function ensureSocksDir(dir = CC_SOCKS_DIR) {
|
|
|
4142
4237
|
if ((fs9.statSync(dir).mode & 511) !== 448)
|
|
4143
4238
|
fs9.chmodSync(dir, 448);
|
|
4144
4239
|
}
|
|
4145
|
-
async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
|
|
4146
|
-
|
|
4240
|
+
async function pollFirstTurnConfirmedAt(getTaskRecord, project, id, scrapeSettled, cancel) {
|
|
4241
|
+
let scrapeSettledAt;
|
|
4242
|
+
const markSettled = () => {
|
|
4243
|
+
scrapeSettledAt ??= Date.now();
|
|
4244
|
+
};
|
|
4245
|
+
scrapeSettled.then(markSettled, markSettled);
|
|
4246
|
+
const hardDeadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_MAX_MS;
|
|
4147
4247
|
for (; ; ) {
|
|
4248
|
+
if (cancel.stopped)
|
|
4249
|
+
return false;
|
|
4148
4250
|
const rec = await getTaskRecord(project, id).catch(() => void 0);
|
|
4149
4251
|
if (rec?.firstTurnConfirmedAt)
|
|
4150
4252
|
return true;
|
|
4253
|
+
const deadline = scrapeSettledAt !== void 0 ? Math.min(hardDeadline, scrapeSettledAt + FIRST_TURN_HOOK_CONFIRM_GRACE_MS) : hardDeadline;
|
|
4151
4254
|
if (Date.now() >= deadline)
|
|
4152
4255
|
return false;
|
|
4153
4256
|
await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
|
|
@@ -4329,7 +4432,9 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4329
4432
|
|
|
4330
4433
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
4331
4434
|
const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
|
|
4332
|
-
const
|
|
4435
|
+
const cancelHookPoll = { stopped: false };
|
|
4436
|
+
const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id, scrapeDelivered, cancelHookPoll)) : await scrapeDelivered;
|
|
4437
|
+
cancelHookPoll.stopped = true;
|
|
4333
4438
|
if (!delivered) {
|
|
4334
4439
|
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
4440
|
`);
|
|
@@ -4611,7 +4716,7 @@ async function runCrewList(project, runtime, workspaceId) {
|
|
|
4611
4716
|
surfaceId: c.surfaceId
|
|
4612
4717
|
}));
|
|
4613
4718
|
}
|
|
4614
|
-
var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT,
|
|
4719
|
+
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
4720
|
var init_crew_spawn = __esm({
|
|
4616
4721
|
"packages/core/dist/crew-spawn.js"() {
|
|
4617
4722
|
init_control_channel();
|
|
@@ -4623,7 +4728,8 @@ var init_crew_spawn = __esm({
|
|
|
4623
4728
|
FIRST_TURN_INLINE_MAX_BYTES = 1200;
|
|
4624
4729
|
TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
|
|
4625
4730
|
STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
|
|
4626
|
-
|
|
4731
|
+
FIRST_TURN_HOOK_CONFIRM_GRACE_MS = 15e3;
|
|
4732
|
+
FIRST_TURN_HOOK_CONFIRM_MAX_MS = 18e4;
|
|
4627
4733
|
FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
|
|
4628
4734
|
CLOSE_LOOKUP_RETRIES = 3;
|
|
4629
4735
|
CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
|
|
@@ -4869,7 +4975,13 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4869
4975
|
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
|
|
4870
4976
|
}
|
|
4871
4977
|
}
|
|
4872
|
-
|
|
4978
|
+
let deliverEntry = entry;
|
|
4979
|
+
if (entry.kind === "captain.message" && entry.payload?.source === "daemon" && entry.message) {
|
|
4980
|
+
const prefix = stalePrefix(new Date(entry.ts).getTime(), Date.now());
|
|
4981
|
+
if (prefix)
|
|
4982
|
+
deliverEntry = { ...entry, message: `${prefix}${entry.message}` };
|
|
4983
|
+
}
|
|
4984
|
+
const result = await d.deliver(deliverEntry, async (text, sendOpts) => {
|
|
4873
4985
|
let handledByChannel = false;
|
|
4874
4986
|
try {
|
|
4875
4987
|
const mode = ctx.captainChannelMode?.() ?? "off";
|
|
@@ -4961,6 +5073,7 @@ var init_delivery_loop = __esm({
|
|
|
4961
5073
|
init_defer_delivery();
|
|
4962
5074
|
init_dist();
|
|
4963
5075
|
init_interactive_probe();
|
|
5076
|
+
init_down_alert();
|
|
4964
5077
|
init_liveness2();
|
|
4965
5078
|
init_captain_channel();
|
|
4966
5079
|
CURSOR_SUBSCRIBER = "captain";
|
|
@@ -5395,8 +5508,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
5395
5508
|
log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
|
|
5396
5509
|
const bootTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
5397
5510
|
{
|
|
5398
|
-
const sendDownAlert = (minutes, reasonText) => {
|
|
5399
|
-
const text =
|
|
5511
|
+
const sendDownAlert = (startTs, endTs, minutes, reasonText) => {
|
|
5512
|
+
const text = formatDownAlertText({ startTs, endTs, minutes, reasonText });
|
|
5400
5513
|
const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
5401
5514
|
for (const project of alertProjects) {
|
|
5402
5515
|
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
|
|
@@ -5407,13 +5520,13 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
5407
5520
|
if (marker) {
|
|
5408
5521
|
log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
|
|
5409
5522
|
if ((gapMs ?? 0) > 6e4)
|
|
5410
|
-
sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
|
|
5523
|
+
sendDownAlert(marker.ts, bootTs, Math.round((gapMs ?? 0) / 6e4), marker.reason);
|
|
5411
5524
|
} else if (prevRunning) {
|
|
5412
5525
|
const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
|
|
5413
5526
|
const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
|
|
5414
5527
|
log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
|
|
5415
5528
|
if (uncleanGapMs > 6e4) {
|
|
5416
|
-
sendDownAlert(Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
|
|
5529
|
+
sendDownAlert(prevRunning.lastHeartbeatTs, bootTs, Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
|
|
5417
5530
|
}
|
|
5418
5531
|
} else {
|
|
5419
5532
|
log("previous exit: none (clean or first boot)");
|
|
@@ -5535,6 +5648,7 @@ var init_start = __esm({
|
|
|
5535
5648
|
init_server();
|
|
5536
5649
|
init_mailbox();
|
|
5537
5650
|
init_exit_marker();
|
|
5651
|
+
init_down_alert();
|
|
5538
5652
|
init_liveness2();
|
|
5539
5653
|
init_dist();
|
|
5540
5654
|
init_snapshot_gather();
|
|
@@ -7504,6 +7618,7 @@ __export(dist_exports2, {
|
|
|
7504
7618
|
OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
|
|
7505
7619
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
7506
7620
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
7621
|
+
READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
|
|
7507
7622
|
STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
|
|
7508
7623
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
7509
7624
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
@@ -7552,6 +7667,7 @@ __export(dist_exports2, {
|
|
|
7552
7667
|
crewTag: () => crewTag,
|
|
7553
7668
|
daemonEntryPath: () => daemonEntryPath,
|
|
7554
7669
|
daemonLockPath: () => daemonLockPath,
|
|
7670
|
+
decideForeignInstall: () => decideForeignInstall,
|
|
7555
7671
|
decodeFrames: () => decodeFrames,
|
|
7556
7672
|
defaultIsPidAlive: () => defaultIsPidAlive,
|
|
7557
7673
|
defaultListenError: () => defaultListenError,
|
|
@@ -7593,9 +7709,11 @@ __export(dist_exports2, {
|
|
|
7593
7709
|
isDaemonSocketLive: () => isDaemonSocketLive,
|
|
7594
7710
|
isNotifyActive: () => isNotifyActive,
|
|
7595
7711
|
isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
|
|
7712
|
+
isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
|
|
7596
7713
|
isSideTitle: () => isSideTitle,
|
|
7597
7714
|
isStickyAttention: () => isStickyAttention,
|
|
7598
7715
|
isTurnAccepted: () => isTurnAccepted,
|
|
7716
|
+
isVersionUpgrade: () => isVersionUpgrade,
|
|
7599
7717
|
kickstartArgv: () => kickstartArgv,
|
|
7600
7718
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
7601
7719
|
loadSessions: () => loadSessions,
|
|
@@ -7612,6 +7730,7 @@ __export(dist_exports2, {
|
|
|
7612
7730
|
parseProgramArgs: () => parseProgramArgs,
|
|
7613
7731
|
plistPath: () => plistPath,
|
|
7614
7732
|
printForeignInstallError: () => printForeignInstallError,
|
|
7733
|
+
printVersionUpgradeNotice: () => printVersionUpgradeNotice,
|
|
7615
7734
|
programArgsBlock: () => programArgsBlock,
|
|
7616
7735
|
projectHealth: () => projectHealth,
|
|
7617
7736
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
@@ -9109,6 +9228,10 @@ function mapSubToLifecycle(sub) {
|
|
|
9109
9228
|
return "needsInput";
|
|
9110
9229
|
case "ask-question":
|
|
9111
9230
|
return "needsInput";
|
|
9231
|
+
case "permission-request":
|
|
9232
|
+
return "needsInput";
|
|
9233
|
+
case "stop-failure":
|
|
9234
|
+
return "idle";
|
|
9112
9235
|
case "session-end":
|
|
9113
9236
|
return "session-end";
|
|
9114
9237
|
default:
|
|
@@ -9150,7 +9273,13 @@ var init_native_hook_source = __esm({
|
|
|
9150
9273
|
["Stop", "stop"],
|
|
9151
9274
|
["Notification", "notification"],
|
|
9152
9275
|
["PreToolUse", "ask-question", "AskUserQuestion"],
|
|
9153
|
-
["SessionEnd", "session-end"]
|
|
9276
|
+
["SessionEnd", "session-end"],
|
|
9277
|
+
// #760: fires ~6s before the matching Notification, carrying tool_name +
|
|
9278
|
+
// tool_input directly — a strictly better task.blocked source.
|
|
9279
|
+
["PermissionRequest", "permission-request"],
|
|
9280
|
+
// #763: the turn died on an API error — today squadrant has no source for
|
|
9281
|
+
// this at all; without it the watchdog reports a plain stall (wrong story).
|
|
9282
|
+
["StopFailure", "stop-failure"]
|
|
9154
9283
|
];
|
|
9155
9284
|
DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
9156
9285
|
NativeHookSource = class {
|
|
@@ -9297,9 +9426,12 @@ async function confirmedSendToPane(runtime, pane, message) {
|
|
|
9297
9426
|
let sawDraft = await settleInputBox(runtime, pane);
|
|
9298
9427
|
await runtime.sendKeyToPane(pane, "Enter");
|
|
9299
9428
|
let repasted = false;
|
|
9429
|
+
let screenMoved = false;
|
|
9300
9430
|
for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
|
|
9301
9431
|
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
9302
9432
|
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
9433
|
+
if (afterScreen !== preSendScreen)
|
|
9434
|
+
screenMoved = true;
|
|
9303
9435
|
const draft = parseDraftFromScreen(afterScreen);
|
|
9304
9436
|
if (draft !== "" && draft !== null)
|
|
9305
9437
|
sawDraft = true;
|
|
@@ -9310,7 +9442,7 @@ async function confirmedSendToPane(runtime, pane, message) {
|
|
|
9310
9442
|
const settled = await settleInputBox(runtime, pane);
|
|
9311
9443
|
if (settled)
|
|
9312
9444
|
sawDraft = true;
|
|
9313
|
-
if (!sawDraft && !repasted) {
|
|
9445
|
+
if (!sawDraft && !repasted && !screenMoved) {
|
|
9314
9446
|
repasted = true;
|
|
9315
9447
|
await runtime.pasteToPane(pane, message);
|
|
9316
9448
|
}
|
|
@@ -9374,9 +9506,12 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
9374
9506
|
await runtime.sendKeyToPane(pane, "Enter");
|
|
9375
9507
|
const retryLimit = acceptanceConfig?.retryLimit ?? SUBMIT_RETRY_LIMIT;
|
|
9376
9508
|
let repasted = false;
|
|
9509
|
+
let screenMoved = false;
|
|
9377
9510
|
for (let attempt = 0; attempt < retryLimit; attempt++) {
|
|
9378
9511
|
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
9379
9512
|
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
9513
|
+
if (afterScreen !== preSendScreen)
|
|
9514
|
+
screenMoved = true;
|
|
9380
9515
|
const draft = parseDraftFromScreen(afterScreen);
|
|
9381
9516
|
if (draft !== "" && draft !== null)
|
|
9382
9517
|
sawDraft = true;
|
|
@@ -9387,13 +9522,13 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
9387
9522
|
const settled = await settleInputBox(runtime, pane);
|
|
9388
9523
|
if (settled)
|
|
9389
9524
|
sawDraft = true;
|
|
9390
|
-
if (!sawDraft && !repasted) {
|
|
9525
|
+
if (!sawDraft && !repasted && !screenMoved) {
|
|
9391
9526
|
repasted = true;
|
|
9392
9527
|
await runtime.pasteToPane(pane, task);
|
|
9393
9528
|
}
|
|
9394
9529
|
await runtime.sendKeyToPane(pane, "Enter");
|
|
9395
9530
|
}
|
|
9396
|
-
if (!sawDraft) {
|
|
9531
|
+
if (!sawDraft && !screenMoved) {
|
|
9397
9532
|
return confirmedSendToPane(runtime, pane, task);
|
|
9398
9533
|
}
|
|
9399
9534
|
return { delivered: false };
|
|
@@ -9509,12 +9644,12 @@ function createClaudeDriver() {
|
|
|
9509
9644
|
if (opts.settingsPath) {
|
|
9510
9645
|
cmd += ` --settings ${opts.settingsPath}`;
|
|
9511
9646
|
}
|
|
9512
|
-
if (opts.messagingSocketPath) {
|
|
9513
|
-
cmd += ` --messaging-socket-path ${opts.messagingSocketPath}`;
|
|
9514
|
-
}
|
|
9515
9647
|
const pluginSubdir = opts.role === "crew" ? "plugin-crew" : "plugin";
|
|
9516
9648
|
const pluginDir = `${process.env.HOME}/.config/squadrant/${pluginSubdir}`;
|
|
9517
9649
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
9650
|
+
if (opts.messagingSocketPath) {
|
|
9651
|
+
cmd += ` --messaging-socket-path ${opts.messagingSocketPath}`;
|
|
9652
|
+
}
|
|
9518
9653
|
if (!opts.interactive) {
|
|
9519
9654
|
cmd += ` -p "${opts.prompt.replace(/"/g, '\\"')}"`;
|
|
9520
9655
|
}
|
|
@@ -11273,6 +11408,70 @@ function decideCaptainMemoryWrite(toolName, toolInput, env, homeDir) {
|
|
|
11273
11408
|
return { decision: "allow" };
|
|
11274
11409
|
return { decision: "deny", reason: DENY_REASON };
|
|
11275
11410
|
}
|
|
11411
|
+
function resolveTurnId(payload) {
|
|
11412
|
+
const id = payload?.prompt_id;
|
|
11413
|
+
return typeof id === "string" && id.trim() ? id : "hook-stop";
|
|
11414
|
+
}
|
|
11415
|
+
function logStopPermissionMode(payload, taskId) {
|
|
11416
|
+
const mode = payload?.permission_mode;
|
|
11417
|
+
if (typeof mode === "string" && mode) {
|
|
11418
|
+
console.error(`[squadrant] hook: Stop permission_mode=${mode} task=${taskId}`);
|
|
11419
|
+
}
|
|
11420
|
+
}
|
|
11421
|
+
function classifyNotification(payload) {
|
|
11422
|
+
const p = payload;
|
|
11423
|
+
const msg = typeof p?.message === "string" ? p.message : "";
|
|
11424
|
+
const notificationType = typeof p?.notification_type === "string" ? p.notification_type : null;
|
|
11425
|
+
if (notificationType === "permission_prompt" || notificationType === "worker_permission_prompt") {
|
|
11426
|
+
return { kind: "blocked", question: msg || "crew awaiting permission" };
|
|
11427
|
+
}
|
|
11428
|
+
if (notificationType === "agent_needs_input") {
|
|
11429
|
+
return { kind: "input-requested", question: msg || "crew needs input" };
|
|
11430
|
+
}
|
|
11431
|
+
if (notificationType != null) {
|
|
11432
|
+
return { kind: "progress" };
|
|
11433
|
+
}
|
|
11434
|
+
if (msg && isPermissionNotification(msg)) {
|
|
11435
|
+
return { kind: "blocked", question: msg };
|
|
11436
|
+
}
|
|
11437
|
+
return { kind: "progress" };
|
|
11438
|
+
}
|
|
11439
|
+
function summarizeToolInput(toolName, toolInput) {
|
|
11440
|
+
if (typeof toolName !== "string" || !toolName)
|
|
11441
|
+
return "a tool call";
|
|
11442
|
+
const input = toolInput;
|
|
11443
|
+
if (!input || typeof input !== "object")
|
|
11444
|
+
return toolName;
|
|
11445
|
+
const hintField = FILE_PATH_FIELD_BY_TOOL[toolName] ?? (toolName === "Bash" ? "command" : void 0);
|
|
11446
|
+
const raw = hintField ? input[hintField] : void 0;
|
|
11447
|
+
if (typeof raw === "string" && raw) {
|
|
11448
|
+
const short = raw.length > TOOL_INPUT_HINT_LEN ? `${raw.slice(0, TOOL_INPUT_HINT_LEN)}\u2026` : raw;
|
|
11449
|
+
return `${toolName}(${short})`;
|
|
11450
|
+
}
|
|
11451
|
+
return toolName;
|
|
11452
|
+
}
|
|
11453
|
+
function formatPermissionRequestQuestion(payload) {
|
|
11454
|
+
const p = payload;
|
|
11455
|
+
return `crew needs permission to run ${summarizeToolInput(p?.tool_name, p?.tool_input)}`;
|
|
11456
|
+
}
|
|
11457
|
+
function hasActiveBackgroundWork(payload) {
|
|
11458
|
+
const p = payload;
|
|
11459
|
+
const tasks = p?.background_tasks;
|
|
11460
|
+
if (Array.isArray(tasks) && tasks.length > 0)
|
|
11461
|
+
return true;
|
|
11462
|
+
const crons = p?.session_crons;
|
|
11463
|
+
if (Array.isArray(crons) && crons.length > 0)
|
|
11464
|
+
return true;
|
|
11465
|
+
return false;
|
|
11466
|
+
}
|
|
11467
|
+
function redactApiError(error) {
|
|
11468
|
+
if (typeof error !== "string" || !error.trim())
|
|
11469
|
+
return "unknown API error";
|
|
11470
|
+
let scrubbed = error.trim();
|
|
11471
|
+
for (const re of SECRET_PATTERNS)
|
|
11472
|
+
scrubbed = scrubbed.replace(re, "[redacted]");
|
|
11473
|
+
return scrubbed.length > MAX_API_ERROR_LEN ? `${scrubbed.slice(0, MAX_API_ERROR_LEN)}\u2026` : scrubbed;
|
|
11474
|
+
}
|
|
11276
11475
|
function mapClaudeHookToEvent(event, payload, taskId) {
|
|
11277
11476
|
switch (event) {
|
|
11278
11477
|
case "PreToolUse": {
|
|
@@ -11280,7 +11479,7 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
11280
11479
|
if (toolName !== "AskUserQuestion")
|
|
11281
11480
|
return null;
|
|
11282
11481
|
const question = formatAskUserQuestionPrompt(payload?.tool_input) ?? "crew opened an AskUserQuestion prompt (options unavailable)";
|
|
11283
|
-
return { type: "task.input.requested", id: taskId, requestId:
|
|
11482
|
+
return { type: "task.input.requested", id: taskId, requestId: nextHookRequestId++, question };
|
|
11284
11483
|
}
|
|
11285
11484
|
case "Stop": {
|
|
11286
11485
|
const text = resolveLastAssistantText(payload);
|
|
@@ -11288,15 +11487,36 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
11288
11487
|
if (question) {
|
|
11289
11488
|
return { type: "task.blocked", id: taskId, reason: "crew asked a question (auto-detected)", question };
|
|
11290
11489
|
}
|
|
11291
|
-
|
|
11490
|
+
if (hasActiveBackgroundWork(payload)) {
|
|
11491
|
+
return { type: "task.progress", id: taskId, note: "stop-background-work" };
|
|
11492
|
+
}
|
|
11493
|
+
logStopPermissionMode(payload, taskId);
|
|
11494
|
+
return { type: "task.turn.completed", id: taskId, turnId: resolveTurnId(payload) };
|
|
11292
11495
|
}
|
|
11496
|
+
case "StopFailure":
|
|
11497
|
+
return {
|
|
11498
|
+
type: "task.turn.failed",
|
|
11499
|
+
id: taskId,
|
|
11500
|
+
turnId: resolveTurnId(payload),
|
|
11501
|
+
error: redactApiError(payload?.error)
|
|
11502
|
+
};
|
|
11293
11503
|
case "Notification": {
|
|
11294
|
-
const
|
|
11295
|
-
if (
|
|
11296
|
-
return { type: "task.blocked", id: taskId, reason: "crew awaiting permission (notification hook)", question:
|
|
11504
|
+
const cls = classifyNotification(payload);
|
|
11505
|
+
if (cls.kind === "blocked") {
|
|
11506
|
+
return { type: "task.blocked", id: taskId, reason: "crew awaiting permission (notification hook)", question: cls.question };
|
|
11507
|
+
}
|
|
11508
|
+
if (cls.kind === "input-requested") {
|
|
11509
|
+
return { type: "task.input.requested", id: taskId, requestId: nextHookRequestId++, question: cls.question };
|
|
11297
11510
|
}
|
|
11298
11511
|
return { type: "task.progress", id: taskId, note: "notification" };
|
|
11299
11512
|
}
|
|
11513
|
+
case "PermissionRequest":
|
|
11514
|
+
return {
|
|
11515
|
+
type: "task.blocked",
|
|
11516
|
+
id: taskId,
|
|
11517
|
+
reason: "crew awaiting permission (permission-request hook)",
|
|
11518
|
+
question: formatPermissionRequestQuestion(payload)
|
|
11519
|
+
};
|
|
11300
11520
|
case "SessionEnd":
|
|
11301
11521
|
return { type: "task.session.ended", id: taskId };
|
|
11302
11522
|
case "SubagentStop":
|
|
@@ -11308,14 +11528,14 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
11308
11528
|
return null;
|
|
11309
11529
|
}
|
|
11310
11530
|
}
|
|
11311
|
-
var EVENTS, MATCHED_EVENTS,
|
|
11531
|
+
var EVENTS, MATCHED_EVENTS, nextHookRequestId, CAPTAIN_MEMORY_PATH_RE, DENY_REASON, FILE_PATH_FIELD_BY_TOOL, TOOL_INPUT_HINT_LEN, SECRET_PATTERNS, MAX_API_ERROR_LEN, claudeInteractive;
|
|
11312
11532
|
var init_claude3 = __esm({
|
|
11313
11533
|
"packages/agents/dist/interactive/claude.js"() {
|
|
11314
|
-
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit"];
|
|
11534
|
+
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit", "PermissionRequest", "StopFailure"];
|
|
11315
11535
|
MATCHED_EVENTS = [
|
|
11316
11536
|
["PreToolUse", "AskUserQuestion"]
|
|
11317
11537
|
];
|
|
11318
|
-
|
|
11538
|
+
nextHookRequestId = Date.now();
|
|
11319
11539
|
CAPTAIN_MEMORY_PATH_RE = /\.claude\/projects\/.*\/memory\//;
|
|
11320
11540
|
DENY_REASON = "Crews do not write captain memory. Put the finding in your done/blocked message; the captain decides what is durable (#556).";
|
|
11321
11541
|
FILE_PATH_FIELD_BY_TOOL = {
|
|
@@ -11324,6 +11544,16 @@ var init_claude3 = __esm({
|
|
|
11324
11544
|
MultiEdit: "file_path",
|
|
11325
11545
|
NotebookEdit: "notebook_path"
|
|
11326
11546
|
};
|
|
11547
|
+
TOOL_INPUT_HINT_LEN = 80;
|
|
11548
|
+
SECRET_PATTERNS = [
|
|
11549
|
+
/sk-ant-[A-Za-z0-9_-]{10,}/g,
|
|
11550
|
+
// Anthropic API key
|
|
11551
|
+
/gh[pousr]_[A-Za-z0-9]{10,}/g,
|
|
11552
|
+
// GitHub token
|
|
11553
|
+
/\b\d{6,}:[A-Za-z0-9_-]{20,}\b/g
|
|
11554
|
+
// Telegram bot token shape
|
|
11555
|
+
];
|
|
11556
|
+
MAX_API_ERROR_LEN = 300;
|
|
11327
11557
|
claudeInteractive = {
|
|
11328
11558
|
provider: "claude",
|
|
11329
11559
|
tier: "strong",
|
|
@@ -12287,6 +12517,7 @@ __export(dist_exports4, {
|
|
|
12287
12517
|
buildAgentCmd: () => buildAgentCmd,
|
|
12288
12518
|
buildCodexDeveloperInstructions: () => buildCodexDeveloperInstructions,
|
|
12289
12519
|
buildUserEnvelope: () => buildUserEnvelope,
|
|
12520
|
+
classifyNotification: () => classifyNotification,
|
|
12290
12521
|
classifyPaneTail: () => classifyPaneTail2,
|
|
12291
12522
|
claudeInteractive: () => claudeInteractive,
|
|
12292
12523
|
createClaudeDriver: () => createClaudeDriver,
|
|
@@ -12303,8 +12534,10 @@ __export(dist_exports4, {
|
|
|
12303
12534
|
deriveTranscriptPath: () => deriveTranscriptPath,
|
|
12304
12535
|
detectTrailingQuestion: () => detectTrailingQuestion2,
|
|
12305
12536
|
formatAskUserQuestionPrompt: () => formatAskUserQuestionPrompt,
|
|
12537
|
+
formatPermissionRequestQuestion: () => formatPermissionRequestQuestion,
|
|
12306
12538
|
getHeadlessAdapter: () => getHeadlessAdapter,
|
|
12307
12539
|
getInteractiveAdapter: () => getInteractiveAdapter,
|
|
12540
|
+
hasActiveBackgroundWork: () => hasActiveBackgroundWork,
|
|
12308
12541
|
isPermissionNotification: () => isPermissionNotification,
|
|
12309
12542
|
mapClaudeHookToEvent: () => mapClaudeHookToEvent,
|
|
12310
12543
|
mergeClaudeHooks: () => mergeClaudeHooks,
|
|
@@ -12315,6 +12548,8 @@ __export(dist_exports4, {
|
|
|
12315
12548
|
readClaudeStatus: () => readClaudeStatus,
|
|
12316
12549
|
readClaudeStatusByCwd: () => readClaudeStatusByCwd,
|
|
12317
12550
|
readClaudeStatusBySocketPath: () => readClaudeStatusBySocketPath,
|
|
12551
|
+
redactApiError: () => redactApiError,
|
|
12552
|
+
resolveTurnId: () => resolveTurnId,
|
|
12318
12553
|
runHeadless: () => runHeadless,
|
|
12319
12554
|
shouldReattachCodex: () => shouldReattachCodex,
|
|
12320
12555
|
toLifecycleSnapshot: () => toLifecycleSnapshot,
|
|
@@ -18805,10 +19040,14 @@ function mapHookSub(sub, payload, taskId) {
|
|
|
18805
19040
|
return { type: "task.first-turn.confirmed", id: taskId };
|
|
18806
19041
|
case "stop":
|
|
18807
19042
|
return mapClaudeHookToEvent("Stop", payload, taskId);
|
|
19043
|
+
case "stop-failure":
|
|
19044
|
+
return mapClaudeHookToEvent("StopFailure", payload, taskId);
|
|
18808
19045
|
case "notification":
|
|
18809
19046
|
return mapClaudeHookToEvent("Notification", payload, taskId);
|
|
18810
19047
|
case "ask-question":
|
|
18811
19048
|
return mapClaudeHookToEvent("PreToolUse", payload, taskId);
|
|
19049
|
+
case "permission-request":
|
|
19050
|
+
return mapClaudeHookToEvent("PermissionRequest", payload, taskId);
|
|
18812
19051
|
case "session-end":
|
|
18813
19052
|
return mapClaudeHookToEvent("SessionEnd", payload, taskId);
|
|
18814
19053
|
default:
|
|
@@ -19310,6 +19549,49 @@ function queryClaudeMem(dbPath, project) {
|
|
|
19310
19549
|
// packages/cli/src/lib/handoff-transcript.ts
|
|
19311
19550
|
import fs31 from "fs";
|
|
19312
19551
|
var TRANSCRIPT_BYTE_CAP = 2e5;
|
|
19552
|
+
var DIGEST_BYTE_CAP = 8 * 1024;
|
|
19553
|
+
var PROMPT_TRUNCATE_CHARS = 300;
|
|
19554
|
+
var ASSISTANT_TRUNCATE_CHARS = 300;
|
|
19555
|
+
function truncate2(text, maxChars) {
|
|
19556
|
+
const trimmed = text.trim();
|
|
19557
|
+
return trimmed.length <= maxChars ? trimmed : `${trimmed.slice(0, maxChars)}\u2026`;
|
|
19558
|
+
}
|
|
19559
|
+
var ISSUE_OR_PR_REF_RE = /#\d+/g;
|
|
19560
|
+
var COMMIT_REF_RE = /\b[0-9a-f]{7,40}\b/gi;
|
|
19561
|
+
function findRefs(text) {
|
|
19562
|
+
const refs = [];
|
|
19563
|
+
for (const m of text.matchAll(ISSUE_OR_PR_REF_RE)) refs.push(m[0]);
|
|
19564
|
+
for (const m of text.matchAll(COMMIT_REF_RE)) {
|
|
19565
|
+
const token = m[0];
|
|
19566
|
+
if (/[0-9]/.test(token) && /[a-f]/i.test(token)) refs.push(token.toLowerCase());
|
|
19567
|
+
}
|
|
19568
|
+
return refs;
|
|
19569
|
+
}
|
|
19570
|
+
function byteSize(value) {
|
|
19571
|
+
return Buffer.byteLength(JSON.stringify(value), "utf-8");
|
|
19572
|
+
}
|
|
19573
|
+
function capDigest(digest, capBytes = DIGEST_BYTE_CAP) {
|
|
19574
|
+
if (byteSize(digest) <= capBytes) return digest;
|
|
19575
|
+
const capped = {
|
|
19576
|
+
...digest,
|
|
19577
|
+
userPrompts: [...digest.userPrompts],
|
|
19578
|
+
assistantTexts: [...digest.assistantTexts],
|
|
19579
|
+
filesTouched: [...digest.filesTouched],
|
|
19580
|
+
refs: [...digest.refs],
|
|
19581
|
+
truncated: true
|
|
19582
|
+
};
|
|
19583
|
+
let dropFromPrompts = true;
|
|
19584
|
+
while (byteSize(capped) > capBytes && (capped.userPrompts.length > 0 || capped.assistantTexts.length > 0)) {
|
|
19585
|
+
const target = dropFromPrompts && capped.userPrompts.length > 0 ? capped.userPrompts : capped.assistantTexts;
|
|
19586
|
+
if (target.length > 0) target.shift();
|
|
19587
|
+
dropFromPrompts = !dropFromPrompts;
|
|
19588
|
+
}
|
|
19589
|
+
while (byteSize(capped) > capBytes && (capped.filesTouched.length > 0 || capped.refs.length > 0)) {
|
|
19590
|
+
if (capped.filesTouched.length > 0) capped.filesTouched.shift();
|
|
19591
|
+
else capped.refs.shift();
|
|
19592
|
+
}
|
|
19593
|
+
return capped;
|
|
19594
|
+
}
|
|
19313
19595
|
function tailOf(content, byteCap) {
|
|
19314
19596
|
const buf = Buffer.from(content, "utf-8");
|
|
19315
19597
|
if (buf.length <= byteCap) return content;
|
|
@@ -19319,6 +19601,15 @@ function tailOf(content, byteCap) {
|
|
|
19319
19601
|
function extractMessages(tailText) {
|
|
19320
19602
|
let lastUserMessage = null;
|
|
19321
19603
|
let lastAssistantText = null;
|
|
19604
|
+
const userPrompts = [];
|
|
19605
|
+
const assistantTexts = [];
|
|
19606
|
+
const toolCalls = {};
|
|
19607
|
+
const filesTouched = /* @__PURE__ */ new Set();
|
|
19608
|
+
const refs = /* @__PURE__ */ new Set();
|
|
19609
|
+
let currentTurnText = null;
|
|
19610
|
+
const collectRefs = (text) => {
|
|
19611
|
+
for (const ref of findRefs(text)) refs.add(ref);
|
|
19612
|
+
};
|
|
19322
19613
|
for (const line of tailText.split("\n")) {
|
|
19323
19614
|
if (!line.trim()) continue;
|
|
19324
19615
|
let obj;
|
|
@@ -19328,20 +19619,48 @@ function extractMessages(tailText) {
|
|
|
19328
19619
|
continue;
|
|
19329
19620
|
}
|
|
19330
19621
|
if (obj.type === "user" && typeof obj.message?.content === "string") {
|
|
19622
|
+
if (currentTurnText !== null) {
|
|
19623
|
+
assistantTexts.push(truncate2(currentTurnText, ASSISTANT_TRUNCATE_CHARS));
|
|
19624
|
+
currentTurnText = null;
|
|
19625
|
+
}
|
|
19331
19626
|
lastUserMessage = obj.message.content;
|
|
19627
|
+
userPrompts.push(truncate2(obj.message.content, PROMPT_TRUNCATE_CHARS));
|
|
19628
|
+
collectRefs(obj.message.content);
|
|
19332
19629
|
} else if (obj.type === "assistant" && Array.isArray(obj.message?.content)) {
|
|
19333
|
-
const
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
19630
|
+
const blocks = obj.message.content;
|
|
19631
|
+
const texts = blocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text);
|
|
19632
|
+
if (texts.length > 0) {
|
|
19633
|
+
currentTurnText = texts.join("\n");
|
|
19634
|
+
lastAssistantText = currentTurnText;
|
|
19635
|
+
collectRefs(currentTurnText);
|
|
19636
|
+
}
|
|
19637
|
+
for (const block of blocks) {
|
|
19638
|
+
if (block.type !== "tool_use" || typeof block.name !== "string") continue;
|
|
19639
|
+
toolCalls[block.name] = (toolCalls[block.name] ?? 0) + 1;
|
|
19640
|
+
const filePath = block.input?.file_path;
|
|
19641
|
+
if (typeof filePath === "string") filesTouched.add(filePath);
|
|
19642
|
+
}
|
|
19643
|
+
}
|
|
19644
|
+
}
|
|
19645
|
+
if (currentTurnText !== null) {
|
|
19646
|
+
assistantTexts.push(truncate2(currentTurnText, ASSISTANT_TRUNCATE_CHARS));
|
|
19647
|
+
}
|
|
19648
|
+
const digest = capDigest({
|
|
19649
|
+
userPrompts,
|
|
19650
|
+
assistantTexts,
|
|
19651
|
+
toolCalls,
|
|
19652
|
+
filesTouched: [...filesTouched],
|
|
19653
|
+
refs: [...refs],
|
|
19654
|
+
truncated: false
|
|
19655
|
+
});
|
|
19656
|
+
return { lastUserMessage, lastAssistantText, digest };
|
|
19338
19657
|
}
|
|
19339
19658
|
function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
|
|
19340
19659
|
if (!fs31.existsSync(transcriptPath)) return null;
|
|
19341
19660
|
const content = fs31.readFileSync(transcriptPath, "utf-8");
|
|
19342
|
-
const { lastUserMessage, lastAssistantText } = extractMessages(tailOf(content, byteCap));
|
|
19661
|
+
const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
|
|
19343
19662
|
const mtimeIso = fs31.statSync(transcriptPath).mtime.toISOString();
|
|
19344
|
-
return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText };
|
|
19663
|
+
return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
|
|
19345
19664
|
}
|
|
19346
19665
|
|
|
19347
19666
|
// packages/cli/src/lib/handoff-archive.ts
|
|
@@ -19457,7 +19776,7 @@ if (process.argv[2] !== "config") {
|
|
|
19457
19776
|
} catch {
|
|
19458
19777
|
}
|
|
19459
19778
|
}
|
|
19460
|
-
if (!process.env.SQUADRANT_DAEMON_SKIP) {
|
|
19779
|
+
if (!process.env.SQUADRANT_DAEMON_SKIP && !isReadOnlyCrewCommand(process.argv)) {
|
|
19461
19780
|
ensureDaemon(void 0, { operatorInitiated: isOperatorInitiatedCommand(process.argv[2]) });
|
|
19462
19781
|
}
|
|
19463
19782
|
var program = new Command35();
|