squadrant 0.16.2 → 0.16.3
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 +107 -28
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +1 -0
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5880,6 +5880,9 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
|
5880
5880
|
}
|
|
5881
5881
|
return { ...pane, title };
|
|
5882
5882
|
}
|
|
5883
|
+
function pickMostRecentTask(tasks) {
|
|
5884
|
+
return tasks.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
5885
|
+
}
|
|
5883
5886
|
async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
5884
5887
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
5885
5888
|
if (!crew) {
|
|
@@ -5890,8 +5893,8 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
5890
5893
|
throw new Error(blockedByModalMessage());
|
|
5891
5894
|
}
|
|
5892
5895
|
try {
|
|
5893
|
-
const
|
|
5894
|
-
const task =
|
|
5896
|
+
const matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
5897
|
+
const task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
|
|
5895
5898
|
if (task) {
|
|
5896
5899
|
if (TERMINAL_STATES.has(task.state)) {
|
|
5897
5900
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
@@ -5929,7 +5932,7 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
|
5929
5932
|
matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
5930
5933
|
}
|
|
5931
5934
|
if (matches.length > 0) {
|
|
5932
|
-
const primary = matches
|
|
5935
|
+
const primary = pickMostRecentTask(matches);
|
|
5933
5936
|
taskId = primary.id;
|
|
5934
5937
|
if (primary.cwd && projRoot && primary.cwd !== projRoot) {
|
|
5935
5938
|
worktreeCwd = primary.cwd;
|
|
@@ -9341,16 +9344,23 @@ function isPermissionNotification(message) {
|
|
|
9341
9344
|
const lower = message.toLowerCase();
|
|
9342
9345
|
return lower.includes("permission") || lower.includes("approve");
|
|
9343
9346
|
}
|
|
9347
|
+
function installHookEntry(hooks, event, matcher, command) {
|
|
9348
|
+
if (!Array.isArray(hooks[event]))
|
|
9349
|
+
hooks[event] = [];
|
|
9350
|
+
const entries = hooks[event];
|
|
9351
|
+
const already = entries.some((m) => m?.matcher === matcher && Array.isArray(m?.hooks) && m.hooks.some((h) => typeof h?.command === "string" && h.command.includes(command)));
|
|
9352
|
+
if (!already) {
|
|
9353
|
+
entries.push({ matcher, hooks: [{ type: "command", command, timeout: 10 }] });
|
|
9354
|
+
}
|
|
9355
|
+
}
|
|
9344
9356
|
function mergeClaudeHooks(settings, hookCmd) {
|
|
9345
9357
|
const next = structuredClone(settings ?? {});
|
|
9346
9358
|
next.hooks ??= {};
|
|
9347
9359
|
for (const ev of EVENTS) {
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
next.hooks[ev].push({ matcher: "", hooks: [{ type: "command", command: `${hookCmd} ${ev}`, timeout: 10 }] });
|
|
9353
|
-
}
|
|
9360
|
+
installHookEntry(next.hooks, ev, "", `${hookCmd} ${ev}`);
|
|
9361
|
+
}
|
|
9362
|
+
for (const [ev, matcher] of MATCHED_EVENTS) {
|
|
9363
|
+
installHookEntry(next.hooks, ev, matcher, `${hookCmd} ${ev}`);
|
|
9354
9364
|
}
|
|
9355
9365
|
return next;
|
|
9356
9366
|
}
|
|
@@ -9430,8 +9440,32 @@ function resolveLastAssistantText(payload) {
|
|
|
9430
9440
|
}
|
|
9431
9441
|
return null;
|
|
9432
9442
|
}
|
|
9443
|
+
function formatAskUserQuestionPrompt(toolInput) {
|
|
9444
|
+
const questions = toolInput?.questions;
|
|
9445
|
+
if (!Array.isArray(questions) || questions.length === 0)
|
|
9446
|
+
return null;
|
|
9447
|
+
const parts = [];
|
|
9448
|
+
for (const q of questions) {
|
|
9449
|
+
if (!q || typeof q !== "object")
|
|
9450
|
+
continue;
|
|
9451
|
+
const text = q.question;
|
|
9452
|
+
if (typeof text !== "string" || !text.trim())
|
|
9453
|
+
continue;
|
|
9454
|
+
const options = Array.isArray(q.options) ? q.options : [];
|
|
9455
|
+
const labels = options.map((o) => o && typeof o.label === "string" ? o.label.trim() : null).filter((l) => !!l);
|
|
9456
|
+
parts.push(labels.length > 0 ? `${text.trim()} (options: ${labels.join(", ")})` : text.trim());
|
|
9457
|
+
}
|
|
9458
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
9459
|
+
}
|
|
9433
9460
|
function mapClaudeHookToEvent(event, payload, taskId) {
|
|
9434
9461
|
switch (event) {
|
|
9462
|
+
case "PreToolUse": {
|
|
9463
|
+
const toolName = payload?.tool_name;
|
|
9464
|
+
if (toolName !== "AskUserQuestion")
|
|
9465
|
+
return null;
|
|
9466
|
+
const question = formatAskUserQuestionPrompt(payload?.tool_input) ?? "crew opened an AskUserQuestion prompt (options unavailable)";
|
|
9467
|
+
return { type: "task.input.requested", id: taskId, requestId: nextAskUserQuestionRequestId++, question };
|
|
9468
|
+
}
|
|
9435
9469
|
case "Stop": {
|
|
9436
9470
|
const text = resolveLastAssistantText(payload);
|
|
9437
9471
|
const question = text ? detectTrailingQuestion2(text) : null;
|
|
@@ -9458,10 +9492,14 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
9458
9492
|
return null;
|
|
9459
9493
|
}
|
|
9460
9494
|
}
|
|
9461
|
-
var EVENTS, claudeInteractive;
|
|
9495
|
+
var EVENTS, MATCHED_EVENTS, nextAskUserQuestionRequestId, claudeInteractive;
|
|
9462
9496
|
var init_claude2 = __esm({
|
|
9463
9497
|
"packages/agents/dist/interactive/claude.js"() {
|
|
9464
9498
|
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit"];
|
|
9499
|
+
MATCHED_EVENTS = [
|
|
9500
|
+
["PreToolUse", "AskUserQuestion"]
|
|
9501
|
+
];
|
|
9502
|
+
nextAskUserQuestionRequestId = Date.now();
|
|
9465
9503
|
claudeInteractive = {
|
|
9466
9504
|
provider: "claude",
|
|
9467
9505
|
tier: "strong",
|
|
@@ -9814,6 +9852,7 @@ __export(dist_exports4, {
|
|
|
9814
9852
|
createOpencodeEmitter: () => createOpencodeEmitter,
|
|
9815
9853
|
deriveTranscriptPath: () => deriveTranscriptPath,
|
|
9816
9854
|
detectTrailingQuestion: () => detectTrailingQuestion2,
|
|
9855
|
+
formatAskUserQuestionPrompt: () => formatAskUserQuestionPrompt,
|
|
9817
9856
|
getHeadlessAdapter: () => getHeadlessAdapter,
|
|
9818
9857
|
getInteractiveAdapter: () => getInteractiveAdapter,
|
|
9819
9858
|
isPermissionNotification: () => isPermissionNotification,
|
|
@@ -11310,6 +11349,22 @@ function defaultWriteResult(id, payload) {
|
|
|
11310
11349
|
writeFileSync10(file, payload);
|
|
11311
11350
|
return file;
|
|
11312
11351
|
}
|
|
11352
|
+
async function runCrewSignal(signal, o, deps) {
|
|
11353
|
+
const taskId = o.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
|
|
11354
|
+
const project = o.project ?? process.env.SQUADRANT_CREW_PROJECT;
|
|
11355
|
+
if (!taskId)
|
|
11356
|
+
throw new Error("not running under a crew (SQUADRANT_CREW_TASK_ID unset)");
|
|
11357
|
+
if (!project)
|
|
11358
|
+
throw new Error("not running under a crew (SQUADRANT_CREW_PROJECT unset)");
|
|
11359
|
+
const current = await deps.call(buildStatusRequest(project, taskId));
|
|
11360
|
+
if (current && TERMINAL_STATES.has(current.state)) {
|
|
11361
|
+
throw new Error(
|
|
11362
|
+
`Task ${taskId} is already terminal (state=${current.state}) \u2014 signal '${signal}' would be silently ignored by the daemon. Stop here: your task record was never reopened for this turn. Ask the captain to run 'squadrant crew send' to reopen it before signaling again.`
|
|
11363
|
+
);
|
|
11364
|
+
}
|
|
11365
|
+
const req = buildSignalRequest(signal, { ...o, writeResult: o.writeResult ?? defaultWriteResult });
|
|
11366
|
+
await deps.call(req);
|
|
11367
|
+
}
|
|
11313
11368
|
function addControlPlaneCrewCommands(crew) {
|
|
11314
11369
|
crew.command("dispatch <project> <task>").description("Dispatch a crew task via the control-plane daemon").requiredOption("--provider <p>", "claude|opencode|codex (gemini: experimental, headless not supported)").option("--mode <m>", "headless|interactive", "interactive").option("--cwd <dir>", "working dir for the crew (project/worktree); required for codex to edit code").action(async (project, task, opts) => {
|
|
11315
11370
|
const req = buildDispatchRequest({ project, task, provider: opts.provider, mode: opts.mode, cwd: opts.cwd });
|
|
@@ -11391,15 +11446,14 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11391
11446
|
process.exit(2);
|
|
11392
11447
|
}
|
|
11393
11448
|
try {
|
|
11394
|
-
|
|
11449
|
+
await runCrewSignal(state, {
|
|
11395
11450
|
...opts.message !== void 0 ? { message: opts.message } : {},
|
|
11396
11451
|
...opts.question !== void 0 ? { question: opts.question } : {},
|
|
11397
11452
|
...opts.error !== void 0 ? { error: opts.error } : {},
|
|
11398
11453
|
...opts.taskId !== void 0 ? { taskId: opts.taskId } : {},
|
|
11399
11454
|
...opts.project !== void 0 ? { project: opts.project } : {},
|
|
11400
11455
|
writeResult: defaultWriteResult
|
|
11401
|
-
});
|
|
11402
|
-
await squadrantdCall(req);
|
|
11456
|
+
}, { call: squadrantdCall });
|
|
11403
11457
|
process.exit(0);
|
|
11404
11458
|
} catch (e) {
|
|
11405
11459
|
process.stderr.write(`${e.message}
|
|
@@ -14631,22 +14685,38 @@ var EFFORT_MEANING = {
|
|
|
14631
14685
|
balance: "normal routing \u2014 use default crew routing rules unchanged",
|
|
14632
14686
|
low: "conserve tokens \u2014 bias crew spawns toward opencode/sonnet; reserve opus for work that genuinely needs it"
|
|
14633
14687
|
};
|
|
14634
|
-
function runEffortGet(configPath = DEFAULT_CONFIG_PATH, projectName) {
|
|
14688
|
+
function runEffortGet(configPath = DEFAULT_CONFIG_PATH, projectName, projectConfigRoot) {
|
|
14635
14689
|
const config = loadConfig(configPath);
|
|
14636
|
-
|
|
14690
|
+
if (projectName && !(projectName in config.projects)) {
|
|
14691
|
+
const known = Object.keys(config.projects).sort().join(", ") || "(no projects registered)";
|
|
14692
|
+
throw new Error(`Unknown project '${projectName}'. Known projects: ${known}`);
|
|
14693
|
+
}
|
|
14694
|
+
const effort = resolveEffort(config, projectName, projectConfigRoot);
|
|
14637
14695
|
const description = `${effort}: ${EFFORT_MEANING[effort]}`;
|
|
14638
14696
|
return { effort, description };
|
|
14639
14697
|
}
|
|
14640
|
-
function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
|
|
14698
|
+
function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH, projectName, projectConfigRoot) {
|
|
14641
14699
|
if (!VALID_EFFORTS.includes(value)) {
|
|
14642
14700
|
throw new Error(
|
|
14643
14701
|
`Invalid effort '${value}'. Valid values: ${VALID_EFFORTS.join(" | ")}`
|
|
14644
14702
|
);
|
|
14645
14703
|
}
|
|
14704
|
+
if (projectName) {
|
|
14705
|
+
const config2 = loadConfig(configPath);
|
|
14706
|
+
if (!(projectName in config2.projects)) {
|
|
14707
|
+
const known = Object.keys(config2.projects).sort().join(", ") || "(no projects registered)";
|
|
14708
|
+
throw new Error(`Unknown project '${projectName}'. Known projects: ${known}`);
|
|
14709
|
+
}
|
|
14710
|
+
saveProjectOverride(projectName, { effort: value }, projectConfigRoot);
|
|
14711
|
+
return;
|
|
14712
|
+
}
|
|
14646
14713
|
const config = loadConfig(configPath);
|
|
14647
14714
|
config.defaults.effort = value;
|
|
14648
14715
|
saveConfig(config, configPath);
|
|
14649
14716
|
}
|
|
14717
|
+
function effortScopeLabel(projectName) {
|
|
14718
|
+
return projectName ? `project: ${projectName}` : "global";
|
|
14719
|
+
}
|
|
14650
14720
|
function canonical(p) {
|
|
14651
14721
|
try {
|
|
14652
14722
|
return fs28.realpathSync(p);
|
|
@@ -14654,11 +14724,16 @@ function canonical(p) {
|
|
|
14654
14724
|
return path29.resolve(p);
|
|
14655
14725
|
}
|
|
14656
14726
|
}
|
|
14657
|
-
async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append) {
|
|
14727
|
+
async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append, scopeProject, projectConfigRoot) {
|
|
14658
14728
|
const here = canonical(cwd);
|
|
14659
14729
|
const notice = `\u{1F39A}\uFE0F effort \u2192 ${effort}: ${EFFORT_MEANING[effort]}`;
|
|
14660
14730
|
for (const [projName, proj] of Object.entries(config.projects)) {
|
|
14661
14731
|
if (canonical(proj.path) === here) continue;
|
|
14732
|
+
if (scopeProject) {
|
|
14733
|
+
if (projName !== scopeProject) continue;
|
|
14734
|
+
} else if (loadProjectOverride(projName, projectConfigRoot).effort !== void 0) {
|
|
14735
|
+
continue;
|
|
14736
|
+
}
|
|
14662
14737
|
try {
|
|
14663
14738
|
const ref = await driver.status(proj.captainName);
|
|
14664
14739
|
if (ref) {
|
|
@@ -14668,22 +14743,28 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
|
|
|
14668
14743
|
}
|
|
14669
14744
|
}
|
|
14670
14745
|
}
|
|
14671
|
-
var effortCommand = new Command28("effort").description("Get or set the
|
|
14746
|
+
var effortCommand = new Command28("effort").description("Get or set the crew tokenomics dial (max | balance | low) \u2014 global by default, or per-project with --project").argument("[value]", "effort level to set: max | balance | low").option("--project <name>", "target a specific project (get: show its resolved effort; set: write a per-project override)").action(async (value, options) => {
|
|
14672
14747
|
if (value === void 0) {
|
|
14673
|
-
|
|
14748
|
+
let result;
|
|
14749
|
+
try {
|
|
14750
|
+
result = runEffortGet(void 0, options.project);
|
|
14751
|
+
} catch (err) {
|
|
14752
|
+
console.error(chalk28.red(err.message));
|
|
14753
|
+
process.exit(1);
|
|
14754
|
+
}
|
|
14674
14755
|
const label = options.project ? `${options.project} project` : "global";
|
|
14675
|
-
console.log(chalk28.bold(`Current effort (${label}):`), chalk28.cyan(
|
|
14676
|
-
console.log(chalk28.dim(EFFORT_MEANING[
|
|
14756
|
+
console.log(chalk28.bold(`Current effort (${label}):`), chalk28.cyan(result.effort));
|
|
14757
|
+
console.log(chalk28.dim(EFFORT_MEANING[result.effort]));
|
|
14677
14758
|
return;
|
|
14678
14759
|
}
|
|
14679
14760
|
try {
|
|
14680
|
-
runEffortSet(value);
|
|
14761
|
+
runEffortSet(value, void 0, options.project);
|
|
14681
14762
|
} catch (err) {
|
|
14682
14763
|
console.error(chalk28.red(err.message));
|
|
14683
14764
|
process.exit(1);
|
|
14684
14765
|
}
|
|
14685
14766
|
const effort = value;
|
|
14686
|
-
console.log(chalk28.green(`\u2714 effort \u2192 ${effort}`));
|
|
14767
|
+
console.log(chalk28.green(`\u2714 effort \u2192 ${effort} (${effortScopeLabel(options.project)})`));
|
|
14687
14768
|
console.log(chalk28.dim(EFFORT_MEANING[effort]));
|
|
14688
14769
|
try {
|
|
14689
14770
|
const { createCmuxDriver: createCmuxDriver2, RuntimeRegistry: RuntimeRegistry2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports3));
|
|
@@ -14692,7 +14773,7 @@ var effortCommand = new Command28("effort").description("Get or set the global c
|
|
|
14692
14773
|
const driver = registry.global(config);
|
|
14693
14774
|
const stateRoot = path29.join(path29.dirname(DEFAULT_CONFIG_PATH), "state");
|
|
14694
14775
|
const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
14695
|
-
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append);
|
|
14776
|
+
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
|
|
14696
14777
|
} catch {
|
|
14697
14778
|
console.log(chalk28.dim("(no running captain detected \u2014 change applies on next launch)"));
|
|
14698
14779
|
}
|
|
@@ -15048,10 +15129,8 @@ function mapHookSub(sub, payload, taskId) {
|
|
|
15048
15129
|
return mapClaudeHookToEvent("Stop", payload, taskId);
|
|
15049
15130
|
case "notification":
|
|
15050
15131
|
return mapClaudeHookToEvent("Notification", payload, taskId);
|
|
15051
|
-
case "ask-question":
|
|
15052
|
-
|
|
15053
|
-
return { type: "task.input.requested", id: taskId, requestId: 0, question: q };
|
|
15054
|
-
}
|
|
15132
|
+
case "ask-question":
|
|
15133
|
+
return mapClaudeHookToEvent("PreToolUse", payload, taskId);
|
|
15055
15134
|
case "session-end":
|
|
15056
15135
|
return mapClaudeHookToEvent("SessionEnd", payload, taskId);
|
|
15057
15136
|
default:
|