squadrant 0.21.0 → 0.21.1
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 +205 -17
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +146 -8
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4237,6 +4237,11 @@ function assertBackendUsable(o) {
|
|
|
4237
4237
|
function shouldBuildRouterService(router, isVitest) {
|
|
4238
4238
|
return !!router && !isVitest;
|
|
4239
4239
|
}
|
|
4240
|
+
function claudeEnvShadowsRouter(claudeEnv) {
|
|
4241
|
+
if (!claudeEnv)
|
|
4242
|
+
return [];
|
|
4243
|
+
return Object.keys(claudeEnv).filter((key) => key.startsWith("ANTHROPIC_"));
|
|
4244
|
+
}
|
|
4240
4245
|
var init_router_resolution = __esm({
|
|
4241
4246
|
"packages/core/dist/router-resolution.js"() {
|
|
4242
4247
|
init_dist();
|
|
@@ -4263,6 +4268,14 @@ function buildRouterEnv(creds, model) {
|
|
|
4263
4268
|
env.ANTHROPIC_MODEL = model;
|
|
4264
4269
|
return env;
|
|
4265
4270
|
}
|
|
4271
|
+
function mergeClaudeEnvRouterSettings(routerEnv, claudeEnv) {
|
|
4272
|
+
const merged = { ...routerEnv };
|
|
4273
|
+
for (const [key, value] of Object.entries(claudeEnv ?? {})) {
|
|
4274
|
+
if (!key.startsWith("ANTHROPIC_"))
|
|
4275
|
+
merged[key] = value;
|
|
4276
|
+
}
|
|
4277
|
+
return merged;
|
|
4278
|
+
}
|
|
4266
4279
|
function formatCustomHeaders(headers) {
|
|
4267
4280
|
const entries = Object.entries(headers ?? {});
|
|
4268
4281
|
if (entries.length === 0)
|
|
@@ -4576,10 +4589,18 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4576
4589
|
if (agentName === "claude") {
|
|
4577
4590
|
let routerEnv = {};
|
|
4578
4591
|
if (backend !== "native") {
|
|
4592
|
+
if (!deps.writeRouterSettings) {
|
|
4593
|
+
throw new Error(`backend '${backend}' requires a per-spawn --settings writer to outrank defaults.claudeEnv, but the spawn path has none \u2014 refusing to launch a routed session that would silently bypass the router shim`);
|
|
4594
|
+
}
|
|
4579
4595
|
if (!deps.routerCredentials) {
|
|
4580
4596
|
throw new Error(`backend '${backend}' requires router credentials, but the spawn path has no daemon credentials provider`);
|
|
4581
4597
|
}
|
|
4582
4598
|
routerEnv = buildRouterEnv(await deps.routerCredentials({ project: input.project, backend }), crewModel);
|
|
4599
|
+
const shadowed = claudeEnvShadowsRouter(config.defaults.claudeEnv);
|
|
4600
|
+
if (shadowed.length > 0) {
|
|
4601
|
+
process.stderr.write(`\u26A0\uFE0F backend '${backend}' selected but defaults.claudeEnv sets ${shadowed.join(", ")} \u2014 these would shadow the router shim. Overriding via a per-spawn --settings env block (outranks ~/.claude/settings.json).
|
|
4602
|
+
`);
|
|
4603
|
+
}
|
|
4583
4604
|
}
|
|
4584
4605
|
ensureSocksDir();
|
|
4585
4606
|
const messagingSocketPath = path9.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
|
|
@@ -4592,6 +4613,12 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4592
4613
|
name,
|
|
4593
4614
|
messagingSocketPath
|
|
4594
4615
|
});
|
|
4616
|
+
const routerSettingsPath = Object.keys(routerEnv).length > 0 ? deps.writeRouterSettings({
|
|
4617
|
+
stateRoot: STATE_ROOT,
|
|
4618
|
+
project: input.project,
|
|
4619
|
+
taskId: rec.id,
|
|
4620
|
+
env: mergeClaudeEnvRouterSettings(routerEnv, config.defaults.claudeEnv)
|
|
4621
|
+
}) : void 0;
|
|
4595
4622
|
let hooksInstalled = false;
|
|
4596
4623
|
try {
|
|
4597
4624
|
deps.writeSettingsLocal(spawnCwd);
|
|
@@ -4607,18 +4634,25 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4607
4634
|
messagingSocketPath,
|
|
4608
4635
|
// Permission mode is config-driven so squadrant can default crews to 'auto'
|
|
4609
4636
|
// or keep the semi-automatic 'acceptEdits' gate. Falls back to 'acceptEdits'.
|
|
4610
|
-
|
|
4637
|
+
// #772: a router-backed crew MUST use 'default' — 'auto' makes the built-in
|
|
4638
|
+
// Sonnet-5 classifier own PermissionRequest and the gate yields, which is
|
|
4639
|
+
// exactly the fail-closed dead end U7 exists to fix. The rubric owns the
|
|
4640
|
+
// permission mode for a routed spawn; the operator's config cannot override
|
|
4641
|
+
// it back into the broken path.
|
|
4642
|
+
permissionMode: backend !== "native" ? "default" : config.defaults.permissions?.crew ?? "acceptEdits",
|
|
4611
4643
|
// #708: self-describing name so ListAgents/the registry can tell this
|
|
4612
4644
|
// crew apart from an unrelated session instead of an auto-derived cwd
|
|
4613
4645
|
// basename (only the claude driver reads this — other agents ignore it).
|
|
4614
4646
|
sessionName: crewSessionName(input.project, name),
|
|
4615
4647
|
...crewModel ? { model: crewModel } : {},
|
|
4616
|
-
...crewThinking ? { thinking: crewThinking } : {}
|
|
4648
|
+
...crewThinking ? { thinking: crewThinking } : {},
|
|
4649
|
+
...routerSettingsPath ? { settingsPath: routerSettingsPath } : {}
|
|
4617
4650
|
});
|
|
4618
4651
|
const direction2 = input.direction ?? "tab";
|
|
4619
4652
|
const title2 = titleFor(input.project, name);
|
|
4620
4653
|
const pane2 = await deps.runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
4621
|
-
const
|
|
4654
|
+
const gateEnv = backend !== "native" ? " SQUADRANT_GATE=on" : "";
|
|
4655
|
+
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}${gateEnv}`;
|
|
4622
4656
|
const routerPrefix = Object.keys(routerEnv).length > 0 ? ` ${renderEnvAssignments(routerEnv)}` : "";
|
|
4623
4657
|
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix}${routerPrefix} ${niceCrewCommand(cliCommand2)}`);
|
|
4624
4658
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
@@ -7526,6 +7560,26 @@ function writeJson(res, status, body) {
|
|
|
7526
7560
|
res.writeHead(status, { "content-type": "application/json" });
|
|
7527
7561
|
res.end(JSON.stringify(body));
|
|
7528
7562
|
}
|
|
7563
|
+
function pathnameOf(url) {
|
|
7564
|
+
if (!url)
|
|
7565
|
+
return "";
|
|
7566
|
+
const q = url.indexOf("?");
|
|
7567
|
+
return q === -1 ? url : url.slice(0, q);
|
|
7568
|
+
}
|
|
7569
|
+
function modelsList(ids) {
|
|
7570
|
+
const data = ids.map((id) => ({
|
|
7571
|
+
type: "model",
|
|
7572
|
+
id,
|
|
7573
|
+
display_name: id,
|
|
7574
|
+
created_at: "1970-01-01T00:00:00Z"
|
|
7575
|
+
}));
|
|
7576
|
+
return {
|
|
7577
|
+
data,
|
|
7578
|
+
has_more: false,
|
|
7579
|
+
first_id: ids[0] ?? null,
|
|
7580
|
+
last_id: ids[ids.length - 1] ?? null
|
|
7581
|
+
};
|
|
7582
|
+
}
|
|
7529
7583
|
function buildUpstreamHeaders(req, upstream, stream) {
|
|
7530
7584
|
const headers = {
|
|
7531
7585
|
"content-type": "application/json",
|
|
@@ -7554,6 +7608,7 @@ function createRouterShim(opts) {
|
|
|
7554
7608
|
let lastError;
|
|
7555
7609
|
let upstreamReachable = true;
|
|
7556
7610
|
const upstreamUrl = joinUrl(opts.upstream.baseUrl, "/v1/messages");
|
|
7611
|
+
const advertisedModels = new Set(opts.models ?? []);
|
|
7557
7612
|
function emitUsage(u) {
|
|
7558
7613
|
try {
|
|
7559
7614
|
opts.onUsage?.(u);
|
|
@@ -7583,6 +7638,8 @@ function createRouterShim(opts) {
|
|
|
7583
7638
|
const body = parsed;
|
|
7584
7639
|
const wantsStream = body.stream === true;
|
|
7585
7640
|
const model = typeof body.model === "string" ? body.model : void 0;
|
|
7641
|
+
if (model)
|
|
7642
|
+
advertisedModels.add(model);
|
|
7586
7643
|
const sanitized = sanitizeRequest(body, opts.upstream);
|
|
7587
7644
|
let upstreamRes;
|
|
7588
7645
|
try {
|
|
@@ -7655,8 +7712,20 @@ function createRouterShim(opts) {
|
|
|
7655
7712
|
emitUsage(usage2);
|
|
7656
7713
|
writeJson(res, upstreamRes.status, upstreamBody);
|
|
7657
7714
|
}
|
|
7715
|
+
async function handleCountTokens(req, res) {
|
|
7716
|
+
let text;
|
|
7717
|
+
try {
|
|
7718
|
+
text = await readBody(req);
|
|
7719
|
+
} catch {
|
|
7720
|
+
const e = anthropicError(400, "invalid_request_error", "malformed request body");
|
|
7721
|
+
writeJson(res, e.status, e.body);
|
|
7722
|
+
return;
|
|
7723
|
+
}
|
|
7724
|
+
writeJson(res, 200, { input_tokens: Math.max(1, Math.ceil(text.length / 4)) });
|
|
7725
|
+
}
|
|
7658
7726
|
function handler(req, res) {
|
|
7659
|
-
|
|
7727
|
+
const path39 = pathnameOf(req.url);
|
|
7728
|
+
if (req.method === "POST" && path39 === "/v1/messages") {
|
|
7660
7729
|
void handleMessages(req, res).catch((err) => {
|
|
7661
7730
|
log(`router internal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
7662
7731
|
if (!res.headersSent) {
|
|
@@ -7668,7 +7737,18 @@ function createRouterShim(opts) {
|
|
|
7668
7737
|
});
|
|
7669
7738
|
return;
|
|
7670
7739
|
}
|
|
7671
|
-
if (req.method === "
|
|
7740
|
+
if (req.method === "POST" && path39 === "/v1/messages/count_tokens") {
|
|
7741
|
+
void handleCountTokens(req, res).catch(() => {
|
|
7742
|
+
if (!res.headersSent)
|
|
7743
|
+
writeJson(res, 502, anthropicError(502, "api_error", "router internal error").body);
|
|
7744
|
+
});
|
|
7745
|
+
return;
|
|
7746
|
+
}
|
|
7747
|
+
if (req.method === "GET" && path39 === "/v1/models") {
|
|
7748
|
+
writeJson(res, 200, modelsList([...advertisedModels]));
|
|
7749
|
+
return;
|
|
7750
|
+
}
|
|
7751
|
+
if (req.method === "GET" && path39 === "/healthz") {
|
|
7672
7752
|
writeJson(res, 200, { ready: server?.listening === true, upstreamReachable, lastError });
|
|
7673
7753
|
return;
|
|
7674
7754
|
}
|
|
@@ -7793,12 +7873,17 @@ function createRouterService(router, projects, deps = {}) {
|
|
|
7793
7873
|
tokenByProject.set(p, t);
|
|
7794
7874
|
projectTokens.set(t, p);
|
|
7795
7875
|
}
|
|
7876
|
+
const models = [
|
|
7877
|
+
...deps.models ?? [],
|
|
7878
|
+
...Object.values(router.models ?? {}).map((m) => m.upstream)
|
|
7879
|
+
];
|
|
7796
7880
|
const ledger = createUsageLedger();
|
|
7797
7881
|
const shim = createRouterShim({
|
|
7798
7882
|
upstream,
|
|
7799
7883
|
projectTokens,
|
|
7800
7884
|
port: router.port ?? 0,
|
|
7801
7885
|
log,
|
|
7886
|
+
models,
|
|
7802
7887
|
onUsage: (u) => ledger.record(u),
|
|
7803
7888
|
...deps.fetch ? { fetch: deps.fetch } : {}
|
|
7804
7889
|
});
|
|
@@ -7861,12 +7946,53 @@ var init_router = __esm({
|
|
|
7861
7946
|
}
|
|
7862
7947
|
});
|
|
7863
7948
|
|
|
7949
|
+
// packages/core/dist/captain-router.js
|
|
7950
|
+
async function prepareCaptainRoute(o) {
|
|
7951
|
+
assertBackendUsable({ backend: o.backend, agent: o.agentName, router: o.config.defaults.router });
|
|
7952
|
+
if (o.backend === "native") {
|
|
7953
|
+
return { backend: "native", model: o.model, permissionMode: o.configuredPermissionMode, env: {} };
|
|
7954
|
+
}
|
|
7955
|
+
if (!o.deps.routerCredentials) {
|
|
7956
|
+
throw new Error(`captain launch: backend '${o.backend}' requires router credentials from the daemon, but this launch path has no credentials provider`);
|
|
7957
|
+
}
|
|
7958
|
+
if (!o.deps.writeRouterSettings) {
|
|
7959
|
+
throw new Error(`captain launch: backend '${o.backend}' requires a per-spawn --settings writer to outrank defaults.claudeEnv, but this launch path has none \u2014 refusing to launch a routed captain that would silently bypass the router shim`);
|
|
7960
|
+
}
|
|
7961
|
+
const model = resolveRouterModel(o.model, o.agentName, o.config.defaults.router);
|
|
7962
|
+
const creds = await o.deps.routerCredentials({ project: o.project, backend: o.backend });
|
|
7963
|
+
const routerEnv = buildRouterEnv(creds, model);
|
|
7964
|
+
const shadowed = claudeEnvShadowsRouter(o.config.defaults.claudeEnv);
|
|
7965
|
+
if (shadowed.length > 0) {
|
|
7966
|
+
o.warn?.(`backend '${o.backend}' selected but defaults.claudeEnv sets ${shadowed.join(", ")} \u2014 these would shadow the router shim. Overriding via a per-spawn --settings env block (outranks ~/.claude/settings.json).`);
|
|
7967
|
+
}
|
|
7968
|
+
const settingsPath = o.deps.writeRouterSettings({
|
|
7969
|
+
stateRoot: o.stateRoot,
|
|
7970
|
+
project: o.project,
|
|
7971
|
+
taskId: "captain",
|
|
7972
|
+
env: mergeClaudeEnvRouterSettings(routerEnv, o.config.defaults.claudeEnv)
|
|
7973
|
+
});
|
|
7974
|
+
return {
|
|
7975
|
+
backend: o.backend,
|
|
7976
|
+
model,
|
|
7977
|
+
permissionMode: "default",
|
|
7978
|
+
env: { ...routerEnv, SQUADRANT_GATE: "on" },
|
|
7979
|
+
settingsPath
|
|
7980
|
+
};
|
|
7981
|
+
}
|
|
7982
|
+
var init_captain_router = __esm({
|
|
7983
|
+
"packages/core/dist/captain-router.js"() {
|
|
7984
|
+
init_dist();
|
|
7985
|
+
init_router_resolution();
|
|
7986
|
+
init_env();
|
|
7987
|
+
}
|
|
7988
|
+
});
|
|
7989
|
+
|
|
7864
7990
|
// packages/core/dist/permission-gate.js
|
|
7865
7991
|
import { createHash } from "crypto";
|
|
7866
7992
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, renameSync as renameSync4, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
7867
7993
|
import { dirname as dirname5, join as join15 } from "path";
|
|
7868
7994
|
function isGateSession(env) {
|
|
7869
|
-
return Boolean(env.SQUADRANT_CREW_TASK_ID) || env[SIDE_SESSION_ENV] === "1";
|
|
7995
|
+
return Boolean(env.SQUADRANT_CREW_TASK_ID) || env[SIDE_SESSION_ENV] === "1" || env.SQUADRANT_ROLE === "captain";
|
|
7870
7996
|
}
|
|
7871
7997
|
function resolveGateMode(env, gate) {
|
|
7872
7998
|
const fromEnv = env.SQUADRANT_GATE;
|
|
@@ -8062,7 +8188,15 @@ async function callClassifier(o) {
|
|
|
8062
8188
|
return "ask";
|
|
8063
8189
|
}
|
|
8064
8190
|
const data = await res.json();
|
|
8191
|
+
if (data?.stop_reason === "max_tokens") {
|
|
8192
|
+
o.log?.("gate: classifier exhausted max_tokens before a verdict \u2014 asking");
|
|
8193
|
+
return "ask";
|
|
8194
|
+
}
|
|
8065
8195
|
const text = Array.isArray(data?.content) ? data.content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ") : "";
|
|
8196
|
+
if (!text.trim()) {
|
|
8197
|
+
o.log?.("gate: classifier returned no text verdict \u2014 asking");
|
|
8198
|
+
return "ask";
|
|
8199
|
+
}
|
|
8066
8200
|
return parseClassifierVerdict(text);
|
|
8067
8201
|
} catch (e) {
|
|
8068
8202
|
o.log?.(`gate: classifier unavailable (${e.message}) \u2014 asking`);
|
|
@@ -8081,7 +8215,7 @@ ${toolPayload}`).digest("hex");
|
|
|
8081
8215
|
}
|
|
8082
8216
|
async function evaluatePermissionRequest(input) {
|
|
8083
8217
|
if (!isGateSession(input.env)) {
|
|
8084
|
-
return { decision: "yield", reason: "not a squadrant crew/side session" };
|
|
8218
|
+
return { decision: "yield", reason: "not a squadrant crew/side/captain session" };
|
|
8085
8219
|
}
|
|
8086
8220
|
const gate = input.config.defaults.gate;
|
|
8087
8221
|
const mode = resolveGateMode(input.env, gate);
|
|
@@ -8167,7 +8301,7 @@ var init_permission_gate = __esm({
|
|
|
8167
8301
|
DEFAULT_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
8168
8302
|
DEFAULT_CACHE_MAX_ENTRIES = 500;
|
|
8169
8303
|
DEFAULT_CLASSIFIER_TIMEOUT_MS = 5e3;
|
|
8170
|
-
CLASSIFIER_MAX_TOKENS =
|
|
8304
|
+
CLASSIFIER_MAX_TOKENS = 256;
|
|
8171
8305
|
MAX_PAYLOAD_CHARS = 2e3;
|
|
8172
8306
|
DEFAULT_GATE_DENY_RULES = [
|
|
8173
8307
|
// Flag-order-agnostic: both recursive and force must be present, in any order,
|
|
@@ -9094,6 +9228,7 @@ __export(dist_exports2, {
|
|
|
9094
9228
|
captainSocketPath: () => captainSocketPath,
|
|
9095
9229
|
checkFact: () => checkFact,
|
|
9096
9230
|
classifyHealth: () => classifyHealth,
|
|
9231
|
+
claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
|
|
9097
9232
|
closeWorkItem: () => closeWorkItem,
|
|
9098
9233
|
computeTemplateHash: () => computeTemplateHash,
|
|
9099
9234
|
consumeExitMarker: () => consumeExitMarker,
|
|
@@ -9194,6 +9329,7 @@ __export(dist_exports2, {
|
|
|
9194
9329
|
makeGate: () => makeGate,
|
|
9195
9330
|
maskToken: () => maskToken,
|
|
9196
9331
|
matchTier1Deny: () => matchTier1Deny,
|
|
9332
|
+
mergeClaudeEnvRouterSettings: () => mergeClaudeEnvRouterSettings,
|
|
9197
9333
|
mintToken: () => mintToken,
|
|
9198
9334
|
nameFromTitle: () => nameFromTitle,
|
|
9199
9335
|
newestSessionInDirectory: () => newestSessionInDirectory,
|
|
@@ -9208,6 +9344,7 @@ __export(dist_exports2, {
|
|
|
9208
9344
|
parseProgramArgs: () => parseProgramArgs,
|
|
9209
9345
|
plistPath: () => plistPath,
|
|
9210
9346
|
pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
|
|
9347
|
+
prepareCaptainRoute: () => prepareCaptainRoute,
|
|
9211
9348
|
printForeignInstallError: () => printForeignInstallError,
|
|
9212
9349
|
printVersionUpgradeNotice: () => printVersionUpgradeNotice,
|
|
9213
9350
|
probeCaptainChannel: () => probeCaptainChannel,
|
|
@@ -9340,6 +9477,7 @@ var init_dist2 = __esm({
|
|
|
9340
9477
|
init_router();
|
|
9341
9478
|
init_crew_routing();
|
|
9342
9479
|
init_router_resolution();
|
|
9480
|
+
init_captain_router();
|
|
9343
9481
|
init_permission_gate();
|
|
9344
9482
|
init_restart_daemon();
|
|
9345
9483
|
init_group_dispatch();
|
|
@@ -9868,7 +10006,7 @@ var init_registry2 = __esm({
|
|
|
9868
10006
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
9869
10007
|
import fs14 from "fs";
|
|
9870
10008
|
import path13 from "path";
|
|
9871
|
-
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot) {
|
|
10009
|
+
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot, settingsPath) {
|
|
9872
10010
|
const driver = registry.getDriver(agentName);
|
|
9873
10011
|
if (driver.name === "claude") {
|
|
9874
10012
|
let cmd = fresh ? "claude" : "claude -c";
|
|
@@ -9881,6 +10019,8 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
9881
10019
|
cmd += " --permission-mode auto";
|
|
9882
10020
|
} else if (permissionMode === "bypassPermissions") {
|
|
9883
10021
|
cmd += " --dangerously-skip-permissions";
|
|
10022
|
+
} else if (permissionMode === "default") {
|
|
10023
|
+
cmd += " --permission-mode default";
|
|
9884
10024
|
}
|
|
9885
10025
|
if (model) {
|
|
9886
10026
|
cmd += ` --model ${model}`;
|
|
@@ -9888,6 +10028,9 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
9888
10028
|
if (thinking) {
|
|
9889
10029
|
cmd += ` --effort ${thinking}`;
|
|
9890
10030
|
}
|
|
10031
|
+
if (settingsPath) {
|
|
10032
|
+
cmd += ` --settings ${settingsPath}`;
|
|
10033
|
+
}
|
|
9891
10034
|
if (templatesDir) {
|
|
9892
10035
|
const roleFile2 = path13.join(templatesDir, `${role}.claude.md`);
|
|
9893
10036
|
const legacyRoleFile = path13.join(templatesDir, `${role}.CLAUDE.md`);
|
|
@@ -15140,6 +15283,13 @@ function mergeCrewPermissions(settings) {
|
|
|
15140
15283
|
permissions.allow = [.../* @__PURE__ */ new Set([...existing, ...CREW_PERMISSION_ALLOWLIST])];
|
|
15141
15284
|
return next;
|
|
15142
15285
|
}
|
|
15286
|
+
function writeRouterSettings(o) {
|
|
15287
|
+
const dir = join26(o.stateRoot, o.project, o.taskId);
|
|
15288
|
+
mkdirSync9(dir, { recursive: true });
|
|
15289
|
+
const file = join26(dir, "router-settings.json");
|
|
15290
|
+
writeFileSync12(file, JSON.stringify({ env: o.env }, null, 2));
|
|
15291
|
+
return file;
|
|
15292
|
+
}
|
|
15143
15293
|
function writePerCrewSettingsLocal(o) {
|
|
15144
15294
|
const dir = join26(o.projectCwd, ".claude");
|
|
15145
15295
|
mkdirSync9(dir, { recursive: true });
|
|
@@ -16533,6 +16683,7 @@ async function runCrewSpawn2(input) {
|
|
|
16533
16683
|
},
|
|
16534
16684
|
writeSettingsLocal: (cwd) => writePerCrewSettingsLocal({ projectCwd: cwd }),
|
|
16535
16685
|
writeOpencodeConfig: writePerCrewOpencodeConfig,
|
|
16686
|
+
writeRouterSettings,
|
|
16536
16687
|
sendFirstTurn: (pane, firstTurn, preLaunchScreen, opts) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen, opts),
|
|
16537
16688
|
getFreePort,
|
|
16538
16689
|
sendCodexFirstTurn,
|
|
@@ -18528,6 +18679,10 @@ function resolveLaunchAgent(overrides, roleConfig, roleModelDefault) {
|
|
|
18528
18679
|
thinking: overrides.thinking ?? roleConfig?.thinking
|
|
18529
18680
|
};
|
|
18530
18681
|
}
|
|
18682
|
+
function resolveLaunchBackend(o) {
|
|
18683
|
+
const roleBackend = o.agentName === "claude" ? o.roleConfig?.backend : void 0;
|
|
18684
|
+
return o.backendOverride ?? roleBackend ?? "native";
|
|
18685
|
+
}
|
|
18531
18686
|
|
|
18532
18687
|
// packages/cli/src/commands/launch.ts
|
|
18533
18688
|
init_dist2();
|
|
@@ -18575,11 +18730,18 @@ function ensureCmuxReady(headless) {
|
|
|
18575
18730
|
}
|
|
18576
18731
|
var launchCommand = new Command14("launch").description(
|
|
18577
18732
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
18578
|
-
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").option("--agent <name>", "Override captain agent for this launch (claude|codex|gemini|opencode); takes precedence over defaults.roles.captain.agent").option("--model <name>", "Override captain model for this launch; takes precedence over defaults.roles.captain.model").option("--thinking <level>", `Override captain thinking level for this launch (${THINKING_LEVELS.join("|")}) \u2192 claude --effort; takes precedence over defaults.roles.captain.thinking`).action(async (project, opts) => {
|
|
18733
|
+
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").option("--agent <name>", "Override captain agent for this launch (claude|codex|gemini|opencode); takes precedence over defaults.roles.captain.agent").option("--model <name>", "Override captain model for this launch; takes precedence over defaults.roles.captain.model").option("--thinking <level>", `Override captain thinking level for this launch (${THINKING_LEVELS.join("|")}) \u2192 claude --effort; takes precedence over defaults.roles.captain.thinking`).option("--backend <mode>", "Override captain backend for this launch (native|direct|proxy); takes precedence over defaults.roles.captain.backend").action(async (project, opts) => {
|
|
18579
18734
|
if (opts.fresh && opts.keep) {
|
|
18580
18735
|
console.error(chalk15.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
18581
18736
|
process.exit(1);
|
|
18582
18737
|
}
|
|
18738
|
+
if (opts.backend !== void 0 && !isBackendMode(opts.backend)) {
|
|
18739
|
+
console.error(chalk15.red(`
|
|
18740
|
+
\u2718 Invalid --backend value '${opts.backend}'. Valid values: native, direct, proxy
|
|
18741
|
+
`));
|
|
18742
|
+
process.exit(1);
|
|
18743
|
+
}
|
|
18744
|
+
const backendOverride = opts.backend;
|
|
18583
18745
|
let thinkingOverride;
|
|
18584
18746
|
try {
|
|
18585
18747
|
thinkingOverride = opts.thinking ? parseThinkingLevel(opts.thinking) : void 0;
|
|
@@ -18606,6 +18768,7 @@ var launchCommand = new Command14("launch").description(
|
|
|
18606
18768
|
roleConfig,
|
|
18607
18769
|
config.defaults.models?.[role]
|
|
18608
18770
|
);
|
|
18771
|
+
const backend = resolveLaunchBackend({ backendOverride, agentName, roleConfig });
|
|
18609
18772
|
const refusal = resolveAnthropicRefusal(role, workspaceName, agentName, model);
|
|
18610
18773
|
if (refusal) {
|
|
18611
18774
|
console.error(chalk15.red(`
|
|
@@ -18635,6 +18798,29 @@ var launchCommand = new Command14("launch").description(
|
|
|
18635
18798
|
if (isOpencodeCaptain && projectName) {
|
|
18636
18799
|
captainOpencodeConfigPath = writePerCrewOpencodeConfig({ stateRoot, project: projectName, taskId: "captain" });
|
|
18637
18800
|
}
|
|
18801
|
+
let route;
|
|
18802
|
+
if (role === "captain") {
|
|
18803
|
+
try {
|
|
18804
|
+
route = await prepareCaptainRoute({
|
|
18805
|
+
backend,
|
|
18806
|
+
agentName,
|
|
18807
|
+
configuredPermissionMode: permissionMode,
|
|
18808
|
+
project: projectName ?? "",
|
|
18809
|
+
model,
|
|
18810
|
+
stateRoot,
|
|
18811
|
+
config,
|
|
18812
|
+
deps: {
|
|
18813
|
+
routerCredentials: (o) => fetchRouterCredentials(o.project, o.backend, { call: squadrantdCall }),
|
|
18814
|
+
writeRouterSettings
|
|
18815
|
+
},
|
|
18816
|
+
warn: (m) => console.error(chalk15.yellow(` \u26A0 ${m}`))
|
|
18817
|
+
});
|
|
18818
|
+
} catch (err) {
|
|
18819
|
+
console.error(chalk15.red(` \u2718 ${err.message}`));
|
|
18820
|
+
hadFailure = true;
|
|
18821
|
+
return;
|
|
18822
|
+
}
|
|
18823
|
+
}
|
|
18638
18824
|
const priorRecord = projectName ? readCaptainAddress(stateRoot, projectName) : null;
|
|
18639
18825
|
const captainPort = isOpencodeCaptain ? await getFreePort() : void 0;
|
|
18640
18826
|
let opencodeResumeSessionId;
|
|
@@ -18660,15 +18846,18 @@ var launchCommand = new Command14("launch").description(
|
|
|
18660
18846
|
registry,
|
|
18661
18847
|
role,
|
|
18662
18848
|
forceFresh,
|
|
18663
|
-
permissionMode,
|
|
18664
|
-
model,
|
|
18849
|
+
route?.permissionMode ?? permissionMode,
|
|
18850
|
+
route?.model ?? model,
|
|
18665
18851
|
TEMPLATES_DIR4,
|
|
18666
18852
|
resolveCaptainSocketPath(captainChannelEnabled, projectName, workspaceName),
|
|
18667
18853
|
resolveCaptainSessionName(agentName, projectName),
|
|
18668
18854
|
thinking,
|
|
18669
|
-
captainBoot
|
|
18855
|
+
captainBoot,
|
|
18856
|
+
route?.settingsPath
|
|
18670
18857
|
);
|
|
18671
|
-
|
|
18858
|
+
const gatePrefix = route && Object.keys(route.env).length > 0 ? `${renderEnvAssignments(route.env)} ` : "";
|
|
18859
|
+
const cmd = `${gatePrefix}${baseCmd}`;
|
|
18860
|
+
return captainOpencodeConfigPath ? `OPENCODE_CONFIG=${captainOpencodeConfigPath} ${cmd}` : cmd;
|
|
18672
18861
|
},
|
|
18673
18862
|
initialPrompt,
|
|
18674
18863
|
runtime,
|
|
@@ -21110,9 +21299,8 @@ async function runGatePermissionRequest(deps) {
|
|
|
21110
21299
|
const p = payload;
|
|
21111
21300
|
const taskId = env.SQUADRANT_CREW_TASK_ID;
|
|
21112
21301
|
const project = env.SQUADRANT_CREW_PROJECT;
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
return { decision: "yield", reason: "not a squadrant crew/side session" };
|
|
21302
|
+
if (!isGateSession(env)) {
|
|
21303
|
+
return { decision: "yield", reason: "not a squadrant crew/side/captain session" };
|
|
21116
21304
|
}
|
|
21117
21305
|
const stdout = deps.stdout ?? (() => {
|
|
21118
21306
|
});
|