squadrant 0.19.4 → 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 +135 -12
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +21 -2
- 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",
|
|
@@ -9219,6 +9228,10 @@ function mapSubToLifecycle(sub) {
|
|
|
9219
9228
|
return "needsInput";
|
|
9220
9229
|
case "ask-question":
|
|
9221
9230
|
return "needsInput";
|
|
9231
|
+
case "permission-request":
|
|
9232
|
+
return "needsInput";
|
|
9233
|
+
case "stop-failure":
|
|
9234
|
+
return "idle";
|
|
9222
9235
|
case "session-end":
|
|
9223
9236
|
return "session-end";
|
|
9224
9237
|
default:
|
|
@@ -9260,7 +9273,13 @@ var init_native_hook_source = __esm({
|
|
|
9260
9273
|
["Stop", "stop"],
|
|
9261
9274
|
["Notification", "notification"],
|
|
9262
9275
|
["PreToolUse", "ask-question", "AskUserQuestion"],
|
|
9263
|
-
["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"]
|
|
9264
9283
|
];
|
|
9265
9284
|
DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
9266
9285
|
NativeHookSource = class {
|
|
@@ -9625,12 +9644,12 @@ function createClaudeDriver() {
|
|
|
9625
9644
|
if (opts.settingsPath) {
|
|
9626
9645
|
cmd += ` --settings ${opts.settingsPath}`;
|
|
9627
9646
|
}
|
|
9628
|
-
if (opts.messagingSocketPath) {
|
|
9629
|
-
cmd += ` --messaging-socket-path ${opts.messagingSocketPath}`;
|
|
9630
|
-
}
|
|
9631
9647
|
const pluginSubdir = opts.role === "crew" ? "plugin-crew" : "plugin";
|
|
9632
9648
|
const pluginDir = `${process.env.HOME}/.config/squadrant/${pluginSubdir}`;
|
|
9633
9649
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
9650
|
+
if (opts.messagingSocketPath) {
|
|
9651
|
+
cmd += ` --messaging-socket-path ${opts.messagingSocketPath}`;
|
|
9652
|
+
}
|
|
9634
9653
|
if (!opts.interactive) {
|
|
9635
9654
|
cmd += ` -p "${opts.prompt.replace(/"/g, '\\"')}"`;
|
|
9636
9655
|
}
|
|
@@ -11389,6 +11408,70 @@ function decideCaptainMemoryWrite(toolName, toolInput, env, homeDir) {
|
|
|
11389
11408
|
return { decision: "allow" };
|
|
11390
11409
|
return { decision: "deny", reason: DENY_REASON };
|
|
11391
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
|
+
}
|
|
11392
11475
|
function mapClaudeHookToEvent(event, payload, taskId) {
|
|
11393
11476
|
switch (event) {
|
|
11394
11477
|
case "PreToolUse": {
|
|
@@ -11396,7 +11479,7 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
11396
11479
|
if (toolName !== "AskUserQuestion")
|
|
11397
11480
|
return null;
|
|
11398
11481
|
const question = formatAskUserQuestionPrompt(payload?.tool_input) ?? "crew opened an AskUserQuestion prompt (options unavailable)";
|
|
11399
|
-
return { type: "task.input.requested", id: taskId, requestId:
|
|
11482
|
+
return { type: "task.input.requested", id: taskId, requestId: nextHookRequestId++, question };
|
|
11400
11483
|
}
|
|
11401
11484
|
case "Stop": {
|
|
11402
11485
|
const text = resolveLastAssistantText(payload);
|
|
@@ -11404,15 +11487,36 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
11404
11487
|
if (question) {
|
|
11405
11488
|
return { type: "task.blocked", id: taskId, reason: "crew asked a question (auto-detected)", question };
|
|
11406
11489
|
}
|
|
11407
|
-
|
|
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) };
|
|
11408
11495
|
}
|
|
11496
|
+
case "StopFailure":
|
|
11497
|
+
return {
|
|
11498
|
+
type: "task.turn.failed",
|
|
11499
|
+
id: taskId,
|
|
11500
|
+
turnId: resolveTurnId(payload),
|
|
11501
|
+
error: redactApiError(payload?.error)
|
|
11502
|
+
};
|
|
11409
11503
|
case "Notification": {
|
|
11410
|
-
const
|
|
11411
|
-
if (
|
|
11412
|
-
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 };
|
|
11413
11510
|
}
|
|
11414
11511
|
return { type: "task.progress", id: taskId, note: "notification" };
|
|
11415
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
|
+
};
|
|
11416
11520
|
case "SessionEnd":
|
|
11417
11521
|
return { type: "task.session.ended", id: taskId };
|
|
11418
11522
|
case "SubagentStop":
|
|
@@ -11424,14 +11528,14 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
11424
11528
|
return null;
|
|
11425
11529
|
}
|
|
11426
11530
|
}
|
|
11427
|
-
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;
|
|
11428
11532
|
var init_claude3 = __esm({
|
|
11429
11533
|
"packages/agents/dist/interactive/claude.js"() {
|
|
11430
|
-
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit"];
|
|
11534
|
+
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit", "PermissionRequest", "StopFailure"];
|
|
11431
11535
|
MATCHED_EVENTS = [
|
|
11432
11536
|
["PreToolUse", "AskUserQuestion"]
|
|
11433
11537
|
];
|
|
11434
|
-
|
|
11538
|
+
nextHookRequestId = Date.now();
|
|
11435
11539
|
CAPTAIN_MEMORY_PATH_RE = /\.claude\/projects\/.*\/memory\//;
|
|
11436
11540
|
DENY_REASON = "Crews do not write captain memory. Put the finding in your done/blocked message; the captain decides what is durable (#556).";
|
|
11437
11541
|
FILE_PATH_FIELD_BY_TOOL = {
|
|
@@ -11440,6 +11544,16 @@ var init_claude3 = __esm({
|
|
|
11440
11544
|
MultiEdit: "file_path",
|
|
11441
11545
|
NotebookEdit: "notebook_path"
|
|
11442
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;
|
|
11443
11557
|
claudeInteractive = {
|
|
11444
11558
|
provider: "claude",
|
|
11445
11559
|
tier: "strong",
|
|
@@ -12403,6 +12517,7 @@ __export(dist_exports4, {
|
|
|
12403
12517
|
buildAgentCmd: () => buildAgentCmd,
|
|
12404
12518
|
buildCodexDeveloperInstructions: () => buildCodexDeveloperInstructions,
|
|
12405
12519
|
buildUserEnvelope: () => buildUserEnvelope,
|
|
12520
|
+
classifyNotification: () => classifyNotification,
|
|
12406
12521
|
classifyPaneTail: () => classifyPaneTail2,
|
|
12407
12522
|
claudeInteractive: () => claudeInteractive,
|
|
12408
12523
|
createClaudeDriver: () => createClaudeDriver,
|
|
@@ -12419,8 +12534,10 @@ __export(dist_exports4, {
|
|
|
12419
12534
|
deriveTranscriptPath: () => deriveTranscriptPath,
|
|
12420
12535
|
detectTrailingQuestion: () => detectTrailingQuestion2,
|
|
12421
12536
|
formatAskUserQuestionPrompt: () => formatAskUserQuestionPrompt,
|
|
12537
|
+
formatPermissionRequestQuestion: () => formatPermissionRequestQuestion,
|
|
12422
12538
|
getHeadlessAdapter: () => getHeadlessAdapter,
|
|
12423
12539
|
getInteractiveAdapter: () => getInteractiveAdapter,
|
|
12540
|
+
hasActiveBackgroundWork: () => hasActiveBackgroundWork,
|
|
12424
12541
|
isPermissionNotification: () => isPermissionNotification,
|
|
12425
12542
|
mapClaudeHookToEvent: () => mapClaudeHookToEvent,
|
|
12426
12543
|
mergeClaudeHooks: () => mergeClaudeHooks,
|
|
@@ -12431,6 +12548,8 @@ __export(dist_exports4, {
|
|
|
12431
12548
|
readClaudeStatus: () => readClaudeStatus,
|
|
12432
12549
|
readClaudeStatusByCwd: () => readClaudeStatusByCwd,
|
|
12433
12550
|
readClaudeStatusBySocketPath: () => readClaudeStatusBySocketPath,
|
|
12551
|
+
redactApiError: () => redactApiError,
|
|
12552
|
+
resolveTurnId: () => resolveTurnId,
|
|
12434
12553
|
runHeadless: () => runHeadless,
|
|
12435
12554
|
shouldReattachCodex: () => shouldReattachCodex,
|
|
12436
12555
|
toLifecycleSnapshot: () => toLifecycleSnapshot,
|
|
@@ -18921,10 +19040,14 @@ function mapHookSub(sub, payload, taskId) {
|
|
|
18921
19040
|
return { type: "task.first-turn.confirmed", id: taskId };
|
|
18922
19041
|
case "stop":
|
|
18923
19042
|
return mapClaudeHookToEvent("Stop", payload, taskId);
|
|
19043
|
+
case "stop-failure":
|
|
19044
|
+
return mapClaudeHookToEvent("StopFailure", payload, taskId);
|
|
18924
19045
|
case "notification":
|
|
18925
19046
|
return mapClaudeHookToEvent("Notification", payload, taskId);
|
|
18926
19047
|
case "ask-question":
|
|
18927
19048
|
return mapClaudeHookToEvent("PreToolUse", payload, taskId);
|
|
19049
|
+
case "permission-request":
|
|
19050
|
+
return mapClaudeHookToEvent("PermissionRequest", payload, taskId);
|
|
18928
19051
|
case "session-end":
|
|
18929
19052
|
return mapClaudeHookToEvent("SessionEnd", payload, taskId);
|
|
18930
19053
|
default:
|