squadrant 0.16.2 → 0.16.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 +177 -61
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +111 -11
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2332,13 +2332,14 @@ function projectHealth(input) {
|
|
|
2332
2332
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
2333
2333
|
const out = [];
|
|
2334
2334
|
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
2335
|
+
const deferral = input.captainDeferral;
|
|
2335
2336
|
out.push({
|
|
2336
2337
|
kind: "captain",
|
|
2337
2338
|
project,
|
|
2338
2339
|
ref: captainName,
|
|
2339
2340
|
state: captainState,
|
|
2340
2341
|
lastSeenMs: null,
|
|
2341
|
-
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : void 0
|
|
2342
|
+
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : deferral?.stuck ? `\u26A0\uFE0F delivery stuck (${deferral.maxDeferCount}+ retries) \u2014 draft/ghost text blocking captain pane; input never touched, delivers automatically once cleared` : void 0
|
|
2342
2343
|
});
|
|
2343
2344
|
if (commandPresent !== null) {
|
|
2344
2345
|
out.push({
|
|
@@ -3005,6 +3006,8 @@ function buildContext(opts) {
|
|
|
3005
3006
|
opencodeBridge: null,
|
|
3006
3007
|
cmuxEventsBridge: null,
|
|
3007
3008
|
telegramBridge: void 0,
|
|
3009
|
+
notifyFault: opts.notifyFault ?? (() => {
|
|
3010
|
+
}),
|
|
3008
3011
|
lifecycleSources: opts.lifecycleSources ?? [],
|
|
3009
3012
|
broadcast: () => {
|
|
3010
3013
|
},
|
|
@@ -3345,7 +3348,7 @@ var init_captain_delivery = __esm({
|
|
|
3345
3348
|
const seq = entry.seq;
|
|
3346
3349
|
const deferCount = this.deferCounts.get(seq) ?? 0;
|
|
3347
3350
|
const stable = (this.stableCounts.get(seq) ?? 0) >= this.opts.stableProbePolls;
|
|
3348
|
-
const probe = stable
|
|
3351
|
+
const probe = stable;
|
|
3349
3352
|
try {
|
|
3350
3353
|
await send(msg, probe ? { probe: true } : void 0);
|
|
3351
3354
|
this.deferCounts.delete(seq);
|
|
@@ -3469,7 +3472,9 @@ async function runLivenessTick(deps) {
|
|
|
3469
3472
|
}
|
|
3470
3473
|
}
|
|
3471
3474
|
function createDelivery(ctx, daemonCmux) {
|
|
3472
|
-
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
3475
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
3476
|
+
const notifyFault = ctx.notifyFault ?? (() => {
|
|
3477
|
+
});
|
|
3473
3478
|
const defaultNotify = async (args) => {
|
|
3474
3479
|
try {
|
|
3475
3480
|
await appendToMailbox({
|
|
@@ -3492,6 +3497,7 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3492
3497
|
const cfg = loadConfig();
|
|
3493
3498
|
const deliveries = /* @__PURE__ */ new Map();
|
|
3494
3499
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
3500
|
+
const stuckNotified = /* @__PURE__ */ new Set();
|
|
3495
3501
|
const sessionStartMs = Date.now();
|
|
3496
3502
|
let delivering = false;
|
|
3497
3503
|
const deliveryCore = async () => {
|
|
@@ -3563,6 +3569,18 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3563
3569
|
break;
|
|
3564
3570
|
}
|
|
3565
3571
|
}
|
|
3572
|
+
const stuck = d.stats().stuck;
|
|
3573
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
3574
|
+
stuckNotified.add(project);
|
|
3575
|
+
const { maxDeferCount } = d.stats();
|
|
3576
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
|
|
3577
|
+
const text = `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
3578
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
3579
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
3580
|
+
telegramBridge?.pushRaw(project, text);
|
|
3581
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
3582
|
+
stuckNotified.delete(project);
|
|
3583
|
+
}
|
|
3566
3584
|
}
|
|
3567
3585
|
};
|
|
3568
3586
|
const deliveryTick = async () => {
|
|
@@ -3839,7 +3857,12 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
3839
3857
|
captainStopped: null,
|
|
3840
3858
|
captainState: deriveCaptainState(capEntry),
|
|
3841
3859
|
commandPresent: null,
|
|
3842
|
-
crews: store.list(project)
|
|
3860
|
+
crews: store.list(project),
|
|
3861
|
+
// #579/#484 Gap 3: surface the same deferral stats already exposed to
|
|
3862
|
+
// the snapshot (line ~135 below) on the health row too, so `squadrant
|
|
3863
|
+
// doctor` / `squadrant status --detailed` show a stuck delivery with
|
|
3864
|
+
// zero configuration.
|
|
3865
|
+
captainDeferral: deliveryStats(project)
|
|
3843
3866
|
}));
|
|
3844
3867
|
}
|
|
3845
3868
|
return out;
|
|
@@ -4740,6 +4763,14 @@ function createTelegramBridge(opts) {
|
|
|
4740
4763
|
s.offset = next;
|
|
4741
4764
|
saveState(stateRoot, s);
|
|
4742
4765
|
}
|
|
4766
|
+
async function sendToTopic(project, text) {
|
|
4767
|
+
let threadId = loadState(stateRoot).topics[topicKey(project)];
|
|
4768
|
+
if (threadId === void 0) {
|
|
4769
|
+
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
4770
|
+
setTopic(stateRoot, project, threadId);
|
|
4771
|
+
}
|
|
4772
|
+
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
4773
|
+
}
|
|
4743
4774
|
async function deliverOutbound(project, ev) {
|
|
4744
4775
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
4745
4776
|
const live = loadState(stateRoot).notify[project];
|
|
@@ -4748,12 +4779,10 @@ function createTelegramBridge(opts) {
|
|
|
4748
4779
|
return;
|
|
4749
4780
|
if (!tierIncludes(resolved.crew, ev.type))
|
|
4750
4781
|
return;
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
}
|
|
4756
|
-
await client.sendMessage(cfg.supergroupId, threadId, formatLifecycle(project, ev));
|
|
4782
|
+
await sendToTopic(project, formatLifecycle(project, ev));
|
|
4783
|
+
}
|
|
4784
|
+
async function deliverRawOutbound(project, text) {
|
|
4785
|
+
await sendToTopic(project, text);
|
|
4757
4786
|
}
|
|
4758
4787
|
function resolveLiveNotify(project) {
|
|
4759
4788
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -5061,6 +5090,11 @@ function createTelegramBridge(opts) {
|
|
|
5061
5090
|
log(`telegram outbound failed project=${project}: ${e.message}`);
|
|
5062
5091
|
});
|
|
5063
5092
|
},
|
|
5093
|
+
pushRaw(project, text) {
|
|
5094
|
+
void deliverRawOutbound(project, text).catch((e) => {
|
|
5095
|
+
log(`telegram raw push failed project=${project}: ${e.message}`);
|
|
5096
|
+
});
|
|
5097
|
+
},
|
|
5064
5098
|
health() {
|
|
5065
5099
|
return { polling: running, lastSuccessfulPollAt, lastError, lastErrorAt };
|
|
5066
5100
|
}
|
|
@@ -5880,6 +5914,9 @@ ${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
|
5880
5914
|
}
|
|
5881
5915
|
return { ...pane, title };
|
|
5882
5916
|
}
|
|
5917
|
+
function pickMostRecentTask(tasks) {
|
|
5918
|
+
return tasks.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
|
|
5919
|
+
}
|
|
5883
5920
|
async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
5884
5921
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
5885
5922
|
if (!crew) {
|
|
@@ -5890,8 +5927,8 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
|
5890
5927
|
throw new Error(blockedByModalMessage());
|
|
5891
5928
|
}
|
|
5892
5929
|
try {
|
|
5893
|
-
const
|
|
5894
|
-
const task =
|
|
5930
|
+
const matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
5931
|
+
const task = matches.length > 0 ? pickMostRecentTask(matches) : void 0;
|
|
5895
5932
|
if (task) {
|
|
5896
5933
|
if (TERMINAL_STATES.has(task.state)) {
|
|
5897
5934
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
@@ -5929,7 +5966,7 @@ async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
|
5929
5966
|
matches = (await deps.listTasks(project)).filter((t) => t.name === name);
|
|
5930
5967
|
}
|
|
5931
5968
|
if (matches.length > 0) {
|
|
5932
|
-
const primary = matches
|
|
5969
|
+
const primary = pickMostRecentTask(matches);
|
|
5933
5970
|
taskId = primary.id;
|
|
5934
5971
|
if (primary.cwd && projRoot && primary.cwd !== projRoot) {
|
|
5935
5972
|
worktreeCwd = primary.cwd;
|
|
@@ -6735,7 +6772,8 @@ var init_runtimes = __esm({
|
|
|
6735
6772
|
});
|
|
6736
6773
|
|
|
6737
6774
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
6738
|
-
import {
|
|
6775
|
+
import { execFile as execFileCb, execSync as execSync2 } from "child_process";
|
|
6776
|
+
import { promisify as promisify2 } from "util";
|
|
6739
6777
|
function createCmuxNotifier(_scope) {
|
|
6740
6778
|
return {
|
|
6741
6779
|
name: "cmux",
|
|
@@ -6752,13 +6790,15 @@ function createCmuxNotifier(_scope) {
|
|
|
6752
6790
|
}
|
|
6753
6791
|
},
|
|
6754
6792
|
async notify(message) {
|
|
6755
|
-
|
|
6793
|
+
await execFile3("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
|
|
6756
6794
|
}
|
|
6757
6795
|
};
|
|
6758
6796
|
}
|
|
6797
|
+
var execFile3;
|
|
6759
6798
|
var init_cmux2 = __esm({
|
|
6760
6799
|
"packages/workspaces/dist/notifiers/cmux.js"() {
|
|
6761
6800
|
init_cmux();
|
|
6801
|
+
execFile3 = promisify2(execFileCb);
|
|
6762
6802
|
}
|
|
6763
6803
|
});
|
|
6764
6804
|
|
|
@@ -9341,16 +9381,23 @@ function isPermissionNotification(message) {
|
|
|
9341
9381
|
const lower = message.toLowerCase();
|
|
9342
9382
|
return lower.includes("permission") || lower.includes("approve");
|
|
9343
9383
|
}
|
|
9384
|
+
function installHookEntry(hooks, event, matcher, command) {
|
|
9385
|
+
if (!Array.isArray(hooks[event]))
|
|
9386
|
+
hooks[event] = [];
|
|
9387
|
+
const entries = hooks[event];
|
|
9388
|
+
const already = entries.some((m) => m?.matcher === matcher && Array.isArray(m?.hooks) && m.hooks.some((h) => typeof h?.command === "string" && h.command.includes(command)));
|
|
9389
|
+
if (!already) {
|
|
9390
|
+
entries.push({ matcher, hooks: [{ type: "command", command, timeout: 10 }] });
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9344
9393
|
function mergeClaudeHooks(settings, hookCmd) {
|
|
9345
9394
|
const next = structuredClone(settings ?? {});
|
|
9346
9395
|
next.hooks ??= {};
|
|
9347
9396
|
for (const ev of EVENTS) {
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
next.hooks[ev].push({ matcher: "", hooks: [{ type: "command", command: `${hookCmd} ${ev}`, timeout: 10 }] });
|
|
9353
|
-
}
|
|
9397
|
+
installHookEntry(next.hooks, ev, "", `${hookCmd} ${ev}`);
|
|
9398
|
+
}
|
|
9399
|
+
for (const [ev, matcher] of MATCHED_EVENTS) {
|
|
9400
|
+
installHookEntry(next.hooks, ev, matcher, `${hookCmd} ${ev}`);
|
|
9354
9401
|
}
|
|
9355
9402
|
return next;
|
|
9356
9403
|
}
|
|
@@ -9430,8 +9477,32 @@ function resolveLastAssistantText(payload) {
|
|
|
9430
9477
|
}
|
|
9431
9478
|
return null;
|
|
9432
9479
|
}
|
|
9480
|
+
function formatAskUserQuestionPrompt(toolInput) {
|
|
9481
|
+
const questions = toolInput?.questions;
|
|
9482
|
+
if (!Array.isArray(questions) || questions.length === 0)
|
|
9483
|
+
return null;
|
|
9484
|
+
const parts = [];
|
|
9485
|
+
for (const q of questions) {
|
|
9486
|
+
if (!q || typeof q !== "object")
|
|
9487
|
+
continue;
|
|
9488
|
+
const text = q.question;
|
|
9489
|
+
if (typeof text !== "string" || !text.trim())
|
|
9490
|
+
continue;
|
|
9491
|
+
const options = Array.isArray(q.options) ? q.options : [];
|
|
9492
|
+
const labels = options.map((o) => o && typeof o.label === "string" ? o.label.trim() : null).filter((l) => !!l);
|
|
9493
|
+
parts.push(labels.length > 0 ? `${text.trim()} (options: ${labels.join(", ")})` : text.trim());
|
|
9494
|
+
}
|
|
9495
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
9496
|
+
}
|
|
9433
9497
|
function mapClaudeHookToEvent(event, payload, taskId) {
|
|
9434
9498
|
switch (event) {
|
|
9499
|
+
case "PreToolUse": {
|
|
9500
|
+
const toolName = payload?.tool_name;
|
|
9501
|
+
if (toolName !== "AskUserQuestion")
|
|
9502
|
+
return null;
|
|
9503
|
+
const question = formatAskUserQuestionPrompt(payload?.tool_input) ?? "crew opened an AskUserQuestion prompt (options unavailable)";
|
|
9504
|
+
return { type: "task.input.requested", id: taskId, requestId: nextAskUserQuestionRequestId++, question };
|
|
9505
|
+
}
|
|
9435
9506
|
case "Stop": {
|
|
9436
9507
|
const text = resolveLastAssistantText(payload);
|
|
9437
9508
|
const question = text ? detectTrailingQuestion2(text) : null;
|
|
@@ -9458,10 +9529,14 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
9458
9529
|
return null;
|
|
9459
9530
|
}
|
|
9460
9531
|
}
|
|
9461
|
-
var EVENTS, claudeInteractive;
|
|
9532
|
+
var EVENTS, MATCHED_EVENTS, nextAskUserQuestionRequestId, claudeInteractive;
|
|
9462
9533
|
var init_claude2 = __esm({
|
|
9463
9534
|
"packages/agents/dist/interactive/claude.js"() {
|
|
9464
9535
|
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit"];
|
|
9536
|
+
MATCHED_EVENTS = [
|
|
9537
|
+
["PreToolUse", "AskUserQuestion"]
|
|
9538
|
+
];
|
|
9539
|
+
nextAskUserQuestionRequestId = Date.now();
|
|
9465
9540
|
claudeInteractive = {
|
|
9466
9541
|
provider: "claude",
|
|
9467
9542
|
tier: "strong",
|
|
@@ -9814,6 +9889,7 @@ __export(dist_exports4, {
|
|
|
9814
9889
|
createOpencodeEmitter: () => createOpencodeEmitter,
|
|
9815
9890
|
deriveTranscriptPath: () => deriveTranscriptPath,
|
|
9816
9891
|
detectTrailingQuestion: () => detectTrailingQuestion2,
|
|
9892
|
+
formatAskUserQuestionPrompt: () => formatAskUserQuestionPrompt,
|
|
9817
9893
|
getHeadlessAdapter: () => getHeadlessAdapter,
|
|
9818
9894
|
getInteractiveAdapter: () => getInteractiveAdapter,
|
|
9819
9895
|
isPermissionNotification: () => isPermissionNotification,
|
|
@@ -10760,9 +10836,18 @@ function progressBar(completed, total) {
|
|
|
10760
10836
|
}
|
|
10761
10837
|
function captainIndicator(state) {
|
|
10762
10838
|
if (state === "alive" || state === "stale") return chalk6.green("\u25CF");
|
|
10839
|
+
if (state === "stopped") return chalk6.magenta("\u23FB");
|
|
10763
10840
|
if (state === void 0 || state === "unknown") return chalk6.dim("?");
|
|
10764
10841
|
return chalk6.dim("\u25CB");
|
|
10765
10842
|
}
|
|
10843
|
+
function formatProjectRow(name, captainName, fm, statusMdState, captainState) {
|
|
10844
|
+
const sessionIndicator = captainIndicator(captainState);
|
|
10845
|
+
const captainDisplay = `${captainName.padEnd(11)} ${sessionIndicator}`;
|
|
10846
|
+
const crew = statusMdState === "ok" ? String(fm.active_crew ?? 0).padEnd(6) : chalk6.dim("?").padEnd(6);
|
|
10847
|
+
const progress = statusMdState === "ok" ? progressBar(fm.tasks_completed ?? 0, fm.tasks_total ?? 0).padEnd(25) : statusMdState === "unreadable" ? chalk6.red("status.md unreadable").padEnd(25) : chalk6.dim("no notes").padEnd(25);
|
|
10848
|
+
const updated = statusMdState === "ok" ? timeAgo(fm.last_updated) : chalk6.dim("\u2014");
|
|
10849
|
+
return ` ${name.padEnd(18)} ${captainDisplay} ${crew} ${progress} ${updated}`;
|
|
10850
|
+
}
|
|
10766
10851
|
var statusCommand = new Command4("status").description("Show status of all projects from spoke vault status files").option("--detailed", "also show live per-component service health from the daemon (#77)").action(async (opts) => {
|
|
10767
10852
|
const config = loadConfig();
|
|
10768
10853
|
const projects = Object.entries(config.projects);
|
|
@@ -10787,28 +10872,19 @@ var statusCommand = new Command4("status").description("Show status of all proje
|
|
|
10787
10872
|
console.log(chalk6.dim(" " + "\u2500".repeat(85)));
|
|
10788
10873
|
for (const [name, project] of projects) {
|
|
10789
10874
|
const workspace = registry.forProject(name, config);
|
|
10790
|
-
if (!await workspace.exists("status.md")) {
|
|
10791
|
-
console.log(` ${name.padEnd(18)} ${chalk6.dim("no status.md")}`);
|
|
10792
|
-
continue;
|
|
10793
|
-
}
|
|
10794
10875
|
let fm = {};
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10876
|
+
let statusMdState = "missing";
|
|
10877
|
+
if (await workspace.exists("status.md")) {
|
|
10878
|
+
try {
|
|
10879
|
+
const raw = await workspace.read("status.md");
|
|
10880
|
+
fm = matter2(raw).data;
|
|
10881
|
+
statusMdState = "ok";
|
|
10882
|
+
} catch {
|
|
10883
|
+
statusMdState = "unreadable";
|
|
10884
|
+
}
|
|
10801
10885
|
}
|
|
10802
|
-
const sessionIndicator = captainIndicator(captainStateByProject.get(name));
|
|
10803
|
-
const captainDisplay = `${project.captainName.padEnd(11)} ${sessionIndicator}`;
|
|
10804
|
-
const crew = String(fm.active_crew ?? 0).padEnd(6);
|
|
10805
|
-
const progress = progressBar(
|
|
10806
|
-
fm.tasks_completed ?? 0,
|
|
10807
|
-
fm.tasks_total ?? 0
|
|
10808
|
-
).padEnd(25);
|
|
10809
|
-
const updated = timeAgo(fm.last_updated);
|
|
10810
10886
|
console.log(
|
|
10811
|
-
|
|
10887
|
+
formatProjectRow(name, project.captainName, fm, statusMdState, captainStateByProject.get(name))
|
|
10812
10888
|
);
|
|
10813
10889
|
}
|
|
10814
10890
|
console.log("");
|
|
@@ -11310,6 +11386,22 @@ function defaultWriteResult(id, payload) {
|
|
|
11310
11386
|
writeFileSync10(file, payload);
|
|
11311
11387
|
return file;
|
|
11312
11388
|
}
|
|
11389
|
+
async function runCrewSignal(signal, o, deps) {
|
|
11390
|
+
const taskId = o.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
|
|
11391
|
+
const project = o.project ?? process.env.SQUADRANT_CREW_PROJECT;
|
|
11392
|
+
if (!taskId)
|
|
11393
|
+
throw new Error("not running under a crew (SQUADRANT_CREW_TASK_ID unset)");
|
|
11394
|
+
if (!project)
|
|
11395
|
+
throw new Error("not running under a crew (SQUADRANT_CREW_PROJECT unset)");
|
|
11396
|
+
const current = await deps.call(buildStatusRequest(project, taskId));
|
|
11397
|
+
if (current && TERMINAL_STATES.has(current.state)) {
|
|
11398
|
+
throw new Error(
|
|
11399
|
+
`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.`
|
|
11400
|
+
);
|
|
11401
|
+
}
|
|
11402
|
+
const req = buildSignalRequest(signal, { ...o, writeResult: o.writeResult ?? defaultWriteResult });
|
|
11403
|
+
await deps.call(req);
|
|
11404
|
+
}
|
|
11313
11405
|
function addControlPlaneCrewCommands(crew) {
|
|
11314
11406
|
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
11407
|
const req = buildDispatchRequest({ project, task, provider: opts.provider, mode: opts.mode, cwd: opts.cwd });
|
|
@@ -11391,15 +11483,14 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
11391
11483
|
process.exit(2);
|
|
11392
11484
|
}
|
|
11393
11485
|
try {
|
|
11394
|
-
|
|
11486
|
+
await runCrewSignal(state, {
|
|
11395
11487
|
...opts.message !== void 0 ? { message: opts.message } : {},
|
|
11396
11488
|
...opts.question !== void 0 ? { question: opts.question } : {},
|
|
11397
11489
|
...opts.error !== void 0 ? { error: opts.error } : {},
|
|
11398
11490
|
...opts.taskId !== void 0 ? { taskId: opts.taskId } : {},
|
|
11399
11491
|
...opts.project !== void 0 ? { project: opts.project } : {},
|
|
11400
11492
|
writeResult: defaultWriteResult
|
|
11401
|
-
});
|
|
11402
|
-
await squadrantdCall(req);
|
|
11493
|
+
}, { call: squadrantdCall });
|
|
11403
11494
|
process.exit(0);
|
|
11404
11495
|
} catch (e) {
|
|
11405
11496
|
process.stderr.write(`${e.message}
|
|
@@ -11928,7 +12019,7 @@ init_dist();
|
|
|
11928
12019
|
import { join as join22 } from "path";
|
|
11929
12020
|
import { homedir as homedir17 } from "os";
|
|
11930
12021
|
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
11931
|
-
import { execFile as
|
|
12022
|
+
import { execFile as execFile4 } from "child_process";
|
|
11932
12023
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
11933
12024
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
11934
12025
|
function withTimeout2(p, ms) {
|
|
@@ -12045,7 +12136,7 @@ function defaultProbeRunners() {
|
|
|
12045
12136
|
return {
|
|
12046
12137
|
probeCmuxBin: () => new Promise((resolve3) => {
|
|
12047
12138
|
try {
|
|
12048
|
-
|
|
12139
|
+
execFile4(resolveCmuxBin(), ["--version"], { timeout: 1500 }, (err) => resolve3(!err));
|
|
12049
12140
|
} catch {
|
|
12050
12141
|
resolve3(false);
|
|
12051
12142
|
}
|
|
@@ -14631,22 +14722,38 @@ var EFFORT_MEANING = {
|
|
|
14631
14722
|
balance: "normal routing \u2014 use default crew routing rules unchanged",
|
|
14632
14723
|
low: "conserve tokens \u2014 bias crew spawns toward opencode/sonnet; reserve opus for work that genuinely needs it"
|
|
14633
14724
|
};
|
|
14634
|
-
function runEffortGet(configPath = DEFAULT_CONFIG_PATH, projectName) {
|
|
14725
|
+
function runEffortGet(configPath = DEFAULT_CONFIG_PATH, projectName, projectConfigRoot) {
|
|
14635
14726
|
const config = loadConfig(configPath);
|
|
14636
|
-
|
|
14727
|
+
if (projectName && !(projectName in config.projects)) {
|
|
14728
|
+
const known = Object.keys(config.projects).sort().join(", ") || "(no projects registered)";
|
|
14729
|
+
throw new Error(`Unknown project '${projectName}'. Known projects: ${known}`);
|
|
14730
|
+
}
|
|
14731
|
+
const effort = resolveEffort(config, projectName, projectConfigRoot);
|
|
14637
14732
|
const description = `${effort}: ${EFFORT_MEANING[effort]}`;
|
|
14638
14733
|
return { effort, description };
|
|
14639
14734
|
}
|
|
14640
|
-
function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
|
|
14735
|
+
function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH, projectName, projectConfigRoot) {
|
|
14641
14736
|
if (!VALID_EFFORTS.includes(value)) {
|
|
14642
14737
|
throw new Error(
|
|
14643
14738
|
`Invalid effort '${value}'. Valid values: ${VALID_EFFORTS.join(" | ")}`
|
|
14644
14739
|
);
|
|
14645
14740
|
}
|
|
14741
|
+
if (projectName) {
|
|
14742
|
+
const config2 = loadConfig(configPath);
|
|
14743
|
+
if (!(projectName in config2.projects)) {
|
|
14744
|
+
const known = Object.keys(config2.projects).sort().join(", ") || "(no projects registered)";
|
|
14745
|
+
throw new Error(`Unknown project '${projectName}'. Known projects: ${known}`);
|
|
14746
|
+
}
|
|
14747
|
+
saveProjectOverride(projectName, { effort: value }, projectConfigRoot);
|
|
14748
|
+
return;
|
|
14749
|
+
}
|
|
14646
14750
|
const config = loadConfig(configPath);
|
|
14647
14751
|
config.defaults.effort = value;
|
|
14648
14752
|
saveConfig(config, configPath);
|
|
14649
14753
|
}
|
|
14754
|
+
function effortScopeLabel(projectName) {
|
|
14755
|
+
return projectName ? `project: ${projectName}` : "global";
|
|
14756
|
+
}
|
|
14650
14757
|
function canonical(p) {
|
|
14651
14758
|
try {
|
|
14652
14759
|
return fs28.realpathSync(p);
|
|
@@ -14654,11 +14761,16 @@ function canonical(p) {
|
|
|
14654
14761
|
return path29.resolve(p);
|
|
14655
14762
|
}
|
|
14656
14763
|
}
|
|
14657
|
-
async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append) {
|
|
14764
|
+
async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append, scopeProject, projectConfigRoot) {
|
|
14658
14765
|
const here = canonical(cwd);
|
|
14659
14766
|
const notice = `\u{1F39A}\uFE0F effort \u2192 ${effort}: ${EFFORT_MEANING[effort]}`;
|
|
14660
14767
|
for (const [projName, proj] of Object.entries(config.projects)) {
|
|
14661
14768
|
if (canonical(proj.path) === here) continue;
|
|
14769
|
+
if (scopeProject) {
|
|
14770
|
+
if (projName !== scopeProject) continue;
|
|
14771
|
+
} else if (loadProjectOverride(projName, projectConfigRoot).effort !== void 0) {
|
|
14772
|
+
continue;
|
|
14773
|
+
}
|
|
14662
14774
|
try {
|
|
14663
14775
|
const ref = await driver.status(proj.captainName);
|
|
14664
14776
|
if (ref) {
|
|
@@ -14668,22 +14780,28 @@ async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(
|
|
|
14668
14780
|
}
|
|
14669
14781
|
}
|
|
14670
14782
|
}
|
|
14671
|
-
var effortCommand = new Command28("effort").description("Get or set the
|
|
14783
|
+
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
14784
|
if (value === void 0) {
|
|
14673
|
-
|
|
14785
|
+
let result;
|
|
14786
|
+
try {
|
|
14787
|
+
result = runEffortGet(void 0, options.project);
|
|
14788
|
+
} catch (err) {
|
|
14789
|
+
console.error(chalk28.red(err.message));
|
|
14790
|
+
process.exit(1);
|
|
14791
|
+
}
|
|
14674
14792
|
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[
|
|
14793
|
+
console.log(chalk28.bold(`Current effort (${label}):`), chalk28.cyan(result.effort));
|
|
14794
|
+
console.log(chalk28.dim(EFFORT_MEANING[result.effort]));
|
|
14677
14795
|
return;
|
|
14678
14796
|
}
|
|
14679
14797
|
try {
|
|
14680
|
-
runEffortSet(value);
|
|
14798
|
+
runEffortSet(value, void 0, options.project);
|
|
14681
14799
|
} catch (err) {
|
|
14682
14800
|
console.error(chalk28.red(err.message));
|
|
14683
14801
|
process.exit(1);
|
|
14684
14802
|
}
|
|
14685
14803
|
const effort = value;
|
|
14686
|
-
console.log(chalk28.green(`\u2714 effort \u2192 ${effort}`));
|
|
14804
|
+
console.log(chalk28.green(`\u2714 effort \u2192 ${effort} (${effortScopeLabel(options.project)})`));
|
|
14687
14805
|
console.log(chalk28.dim(EFFORT_MEANING[effort]));
|
|
14688
14806
|
try {
|
|
14689
14807
|
const { createCmuxDriver: createCmuxDriver2, RuntimeRegistry: RuntimeRegistry2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports3));
|
|
@@ -14692,7 +14810,7 @@ var effortCommand = new Command28("effort").description("Get or set the global c
|
|
|
14692
14810
|
const driver = registry.global(config);
|
|
14693
14811
|
const stateRoot = path29.join(path29.dirname(DEFAULT_CONFIG_PATH), "state");
|
|
14694
14812
|
const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
14695
|
-
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append);
|
|
14813
|
+
await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
|
|
14696
14814
|
} catch {
|
|
14697
14815
|
console.log(chalk28.dim("(no running captain detected \u2014 change applies on next launch)"));
|
|
14698
14816
|
}
|
|
@@ -15048,10 +15166,8 @@ function mapHookSub(sub, payload, taskId) {
|
|
|
15048
15166
|
return mapClaudeHookToEvent("Stop", payload, taskId);
|
|
15049
15167
|
case "notification":
|
|
15050
15168
|
return mapClaudeHookToEvent("Notification", payload, taskId);
|
|
15051
|
-
case "ask-question":
|
|
15052
|
-
|
|
15053
|
-
return { type: "task.input.requested", id: taskId, requestId: 0, question: q };
|
|
15054
|
-
}
|
|
15169
|
+
case "ask-question":
|
|
15170
|
+
return mapClaudeHookToEvent("PreToolUse", payload, taskId);
|
|
15055
15171
|
case "session-end":
|
|
15056
15172
|
return mapClaudeHookToEvent("SessionEnd", payload, taskId);
|
|
15057
15173
|
default:
|