squadrant 0.19.4 → 0.20.0
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 +6379 -4519
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +1566 -302
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/add-pick-crew-rule/SKILL.md +21 -4
- package/plugin/skills/captain-ops/SKILL.md +42 -0
- package/plugin/skills/prompt-master/SKILL.md +496 -0
- package/plugin/skills/prompt-master/references/patterns.md +82 -0
- package/plugin/skills/prompt-master/references/templates.md +442 -0
- package/templates/captain.claude.md +2 -1
- package/templates/captain.generic.md +1 -1
package/dist/squadrantd.js
CHANGED
|
@@ -85,6 +85,9 @@ var init_config_io = __esm({
|
|
|
85
85
|
import path2 from "path";
|
|
86
86
|
import os from "os";
|
|
87
87
|
import chalk from "chalk";
|
|
88
|
+
function isBackendMode(v2) {
|
|
89
|
+
return v2 === "native" || v2 === "direct" || v2 === "proxy";
|
|
90
|
+
}
|
|
88
91
|
function getDefaultConfig() {
|
|
89
92
|
return {
|
|
90
93
|
commandName: "\u{1F3DB}\uFE0F command",
|
|
@@ -245,6 +248,20 @@ var init_effort = __esm({
|
|
|
245
248
|
}
|
|
246
249
|
});
|
|
247
250
|
|
|
251
|
+
// packages/shared/dist/router-model.js
|
|
252
|
+
function resolveRouterModel(model, agentName, router) {
|
|
253
|
+
if (!model)
|
|
254
|
+
return void 0;
|
|
255
|
+
const alias = router?.models?.[model];
|
|
256
|
+
if (!alias)
|
|
257
|
+
return model;
|
|
258
|
+
return alias.agents?.[agentName] ?? alias.upstream;
|
|
259
|
+
}
|
|
260
|
+
var init_router_model = __esm({
|
|
261
|
+
"packages/shared/dist/router-model.js"() {
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
248
265
|
// packages/shared/dist/types/runtime.js
|
|
249
266
|
var init_runtime = __esm({
|
|
250
267
|
"packages/shared/dist/types/runtime.js"() {
|
|
@@ -298,22 +315,22 @@ function defaultCmuxConfigPath() {
|
|
|
298
315
|
return join(homedir(), ".config", "cmux", "cmux.json");
|
|
299
316
|
}
|
|
300
317
|
function ensureSocketAutomation(opts = {}) {
|
|
301
|
-
const
|
|
302
|
-
if (!existsSync(
|
|
303
|
-
mkdirSync(dirname(
|
|
304
|
-
writeFileSync(
|
|
305
|
-
return { path:
|
|
318
|
+
const path23 = opts.path ?? defaultCmuxConfigPath();
|
|
319
|
+
if (!existsSync(path23)) {
|
|
320
|
+
mkdirSync(dirname(path23), { recursive: true });
|
|
321
|
+
writeFileSync(path23, MINIMAL_TEMPLATE);
|
|
322
|
+
return { path: path23, changed: true, alreadySet: false };
|
|
306
323
|
}
|
|
307
|
-
const text = readFileSync(
|
|
324
|
+
const text = readFileSync(path23, "utf-8");
|
|
308
325
|
const current = parse(text)?.automation?.socketControlMode;
|
|
309
326
|
if (current === AUTOMATION_MODE) {
|
|
310
|
-
return { path:
|
|
327
|
+
return { path: path23, changed: false, alreadySet: true };
|
|
311
328
|
}
|
|
312
329
|
const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
|
|
313
330
|
formattingOptions: { insertSpaces: true, tabSize: 2 }
|
|
314
331
|
});
|
|
315
|
-
writeFileSync(
|
|
316
|
-
return { path:
|
|
332
|
+
writeFileSync(path23, applyEdits(text, edits));
|
|
333
|
+
return { path: path23, changed: true, alreadySet: false };
|
|
317
334
|
}
|
|
318
335
|
var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
|
|
319
336
|
var init_cmux_config = __esm({
|
|
@@ -463,9 +480,9 @@ import { join as join4 } from "path";
|
|
|
463
480
|
function defaultStatePath() {
|
|
464
481
|
return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
|
|
465
482
|
}
|
|
466
|
-
function readState(
|
|
483
|
+
function readState(path23) {
|
|
467
484
|
try {
|
|
468
|
-
return JSON.parse(readConfigFileSync(
|
|
485
|
+
return JSON.parse(readConfigFileSync(path23));
|
|
469
486
|
} catch {
|
|
470
487
|
return {};
|
|
471
488
|
}
|
|
@@ -511,7 +528,7 @@ var init_compat_manifest = __esm({
|
|
|
511
528
|
"packages/shared/dist/lib/compat-manifest.js"() {
|
|
512
529
|
compatManifest = {
|
|
513
530
|
tools: {
|
|
514
|
-
cmux: { min: "0.64.0", lastVerified: "0.64.
|
|
531
|
+
cmux: { min: "0.64.0", lastVerified: "0.64.22" },
|
|
515
532
|
claude: { min: "2.1.32" },
|
|
516
533
|
node: { min: "18.0.0", lastVerified: "24.6.0" },
|
|
517
534
|
// presence-checked; no floor enforced yet
|
|
@@ -526,6 +543,7 @@ var init_compat_manifest = __esm({
|
|
|
526
543
|
// packages/shared/dist/lib/config-drift.js
|
|
527
544
|
var init_config_drift = __esm({
|
|
528
545
|
"packages/shared/dist/lib/config-drift.js"() {
|
|
546
|
+
init_config();
|
|
529
547
|
}
|
|
530
548
|
});
|
|
531
549
|
|
|
@@ -762,6 +780,7 @@ var init_dist = __esm({
|
|
|
762
780
|
init_config();
|
|
763
781
|
init_project_config();
|
|
764
782
|
init_effort();
|
|
783
|
+
init_router_model();
|
|
765
784
|
init_runtime();
|
|
766
785
|
init_liveness();
|
|
767
786
|
init_control();
|
|
@@ -887,6 +906,11 @@ function reduce(rec, ev, now) {
|
|
|
887
906
|
return stampAttempt(base, { resumeRef: ev.resumeRef }, now);
|
|
888
907
|
case "task.turn.started":
|
|
889
908
|
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0, pendingMonitor: void 0 };
|
|
909
|
+
case "task.turn.failed":
|
|
910
|
+
// #763: a crew's turn died on an API error (StopFailure hook). Anti-#2576:
|
|
911
|
+
// NOT task.failed — a hook may never terminalize a task. Falls through to
|
|
912
|
+
// the exact same turn-boundary transition as task.turn.completed below;
|
|
913
|
+
// only the notify message (reduce.ts formatMessage) differs.
|
|
890
914
|
case "task.turn.completed":
|
|
891
915
|
if (isStickyAttention(rec.state))
|
|
892
916
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
@@ -1002,6 +1026,9 @@ function formatMessage(rec, event) {
|
|
|
1002
1026
|
return `CREW STALLED ${tag}: no heartbeat in ${rec.heartbeatBudgetMs}ms`;
|
|
1003
1027
|
}
|
|
1004
1028
|
case "awaiting-input":
|
|
1029
|
+
if (event?.type === "task.turn.failed") {
|
|
1030
|
+
return `CREW TURN FAILED ${tag}: API error ended the turn (not a stall) \u2014 ${event.error}`;
|
|
1031
|
+
}
|
|
1005
1032
|
return `CREW IDLE ${tag}: turn ended, awaiting your reply.`;
|
|
1006
1033
|
default:
|
|
1007
1034
|
return null;
|
|
@@ -1226,7 +1253,7 @@ function createDaemon(deps) {
|
|
|
1226
1253
|
async sweep() {
|
|
1227
1254
|
const t = now();
|
|
1228
1255
|
const surfaceAlive = deps.isSurfaceAlive ?? (async () => "unknown");
|
|
1229
|
-
const projects = new Set(store.listAll().map((r) => r.project));
|
|
1256
|
+
const projects = new Set(store.listAll().map((r) => r.project).filter((p) => Boolean(p)));
|
|
1230
1257
|
for (const project of projects) {
|
|
1231
1258
|
const terminal = store.list(project).filter((r) => TERMINAL_STATES.has(r.state)).sort((a, b) => b.lastHeartbeat - a.lastHeartbeat);
|
|
1232
1259
|
for (const r of terminal.slice(TERMINAL_RECORD_KEEP_PER_PROJECT)) {
|
|
@@ -1411,6 +1438,7 @@ var init_reduce = __esm({
|
|
|
1411
1438
|
"task.session",
|
|
1412
1439
|
"task.turn.started",
|
|
1413
1440
|
"task.turn.completed",
|
|
1441
|
+
"task.turn.failed",
|
|
1414
1442
|
"task.delta",
|
|
1415
1443
|
"task.input.requested",
|
|
1416
1444
|
"task.approval.requested",
|
|
@@ -2020,6 +2048,12 @@ function safeSegment(kind, s) {
|
|
|
2020
2048
|
}
|
|
2021
2049
|
return s;
|
|
2022
2050
|
}
|
|
2051
|
+
function isTaskRecord(r) {
|
|
2052
|
+
if (typeof r !== "object" || r === null)
|
|
2053
|
+
return false;
|
|
2054
|
+
const rec = r;
|
|
2055
|
+
return typeof rec.id === "string" && rec.id.length > 0 && typeof rec.project === "string" && rec.project.length > 0;
|
|
2056
|
+
}
|
|
2023
2057
|
function createStore(root) {
|
|
2024
2058
|
const rootResolved = resolve(root);
|
|
2025
2059
|
const assertUnderRoot = (target) => {
|
|
@@ -2067,7 +2101,7 @@ function createStore(root) {
|
|
|
2067
2101
|
} catch {
|
|
2068
2102
|
return void 0;
|
|
2069
2103
|
}
|
|
2070
|
-
}).filter(
|
|
2104
|
+
}).filter(isTaskRecord);
|
|
2071
2105
|
},
|
|
2072
2106
|
listAll() {
|
|
2073
2107
|
if (!existsSync6(root))
|
|
@@ -2355,10 +2389,10 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
|
|
|
2355
2389
|
isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
|
|
2356
2390
|
};
|
|
2357
2391
|
}
|
|
2358
|
-
function sanitizePathForPlist(
|
|
2392
|
+
function sanitizePathForPlist(path23) {
|
|
2359
2393
|
const seen = /* @__PURE__ */ new Set();
|
|
2360
2394
|
const stable = [];
|
|
2361
|
-
for (const p of
|
|
2395
|
+
for (const p of path23.split(":")) {
|
|
2362
2396
|
if (!p)
|
|
2363
2397
|
continue;
|
|
2364
2398
|
if (p.includes("/.claude/plugins/"))
|
|
@@ -2560,6 +2594,9 @@ function isOperatorInitiatedCommand(topLevelArg) {
|
|
|
2560
2594
|
function isReadOnlyCrewCommand(argv) {
|
|
2561
2595
|
return argv[2] === "crew" && argv[3] !== void 0 && READ_ONLY_CREW_SUBCOMMANDS.has(argv[3]);
|
|
2562
2596
|
}
|
|
2597
|
+
function isReadOnlyTopLevelCommand(argv) {
|
|
2598
|
+
return argv[2] !== void 0 && READ_ONLY_TOP_LEVEL_COMMANDS.has(argv[2]);
|
|
2599
|
+
}
|
|
2563
2600
|
function ensureDaemon(nodeBin = process.execPath, opts = {}) {
|
|
2564
2601
|
if (restartInFlight)
|
|
2565
2602
|
return;
|
|
@@ -2627,7 +2664,7 @@ function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
|
|
|
2627
2664
|
releaseDaemonLock();
|
|
2628
2665
|
}
|
|
2629
2666
|
}
|
|
2630
|
-
var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS, READ_ONLY_CREW_SUBCOMMANDS;
|
|
2667
|
+
var LABEL, AGENT_BINS, restartInFlight, OPERATOR_INITIATED_COMMANDS, READ_ONLY_CREW_SUBCOMMANDS, READ_ONLY_TOP_LEVEL_COMMANDS;
|
|
2631
2668
|
var init_launchd = __esm({
|
|
2632
2669
|
"packages/core/dist/launchd.js"() {
|
|
2633
2670
|
LABEL = "com.squadrant.daemon";
|
|
@@ -2635,6 +2672,7 @@ var init_launchd = __esm({
|
|
|
2635
2672
|
restartInFlight = false;
|
|
2636
2673
|
OPERATOR_INITIATED_COMMANDS = /* @__PURE__ */ new Set(["launch", "init"]);
|
|
2637
2674
|
READ_ONLY_CREW_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "read", "tasks"]);
|
|
2675
|
+
READ_ONLY_TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set(["sessions", "whoami"]);
|
|
2638
2676
|
}
|
|
2639
2677
|
});
|
|
2640
2678
|
|
|
@@ -2914,6 +2952,7 @@ function buildContext(opts) {
|
|
|
2914
2952
|
opencodeBridge: null,
|
|
2915
2953
|
cmuxEventsBridge: null,
|
|
2916
2954
|
telegramBridge: void 0,
|
|
2955
|
+
routerService: void 0,
|
|
2917
2956
|
notifyFault: opts.notifyFault ?? (() => {
|
|
2918
2957
|
}),
|
|
2919
2958
|
lifecycleSources: opts.lifecycleSources ?? [],
|
|
@@ -3399,6 +3438,72 @@ var init_control_channel = __esm({
|
|
|
3399
3438
|
}
|
|
3400
3439
|
});
|
|
3401
3440
|
|
|
3441
|
+
// packages/core/dist/router-resolution.js
|
|
3442
|
+
function resolveBackend(explicit, routeBackend, roleBackend) {
|
|
3443
|
+
return explicit ?? routeBackend ?? roleBackend ?? "native";
|
|
3444
|
+
}
|
|
3445
|
+
function assertBackendUsable(o) {
|
|
3446
|
+
if (!isBackendMode(o.backend)) {
|
|
3447
|
+
throw new Error(`unknown backend '${o.backend}'; expected native|direct|proxy`);
|
|
3448
|
+
}
|
|
3449
|
+
if (o.backend === "native")
|
|
3450
|
+
return;
|
|
3451
|
+
if (o.agent !== "claude") {
|
|
3452
|
+
throw new Error(`backend '${o.backend}' is claude-only; agent '${o.agent}' must use backend 'native'`);
|
|
3453
|
+
}
|
|
3454
|
+
if (!o.router) {
|
|
3455
|
+
throw new Error(`backend '${o.backend}' selected for agent 'claude' but defaults.router is not configured`);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
function shouldBuildRouterService(router, isVitest) {
|
|
3459
|
+
return !!router && !isVitest;
|
|
3460
|
+
}
|
|
3461
|
+
var init_router_resolution = __esm({
|
|
3462
|
+
"packages/core/dist/router-resolution.js"() {
|
|
3463
|
+
init_dist();
|
|
3464
|
+
}
|
|
3465
|
+
});
|
|
3466
|
+
|
|
3467
|
+
// packages/core/dist/router/env.js
|
|
3468
|
+
function buildRouterEnv(creds, model) {
|
|
3469
|
+
const env = {
|
|
3470
|
+
ANTHROPIC_BASE_URL: creds.baseUrl,
|
|
3471
|
+
// Empty string, never unset: an unset key lets Claude Code fall back to
|
|
3472
|
+
// authenticating against Anthropic directly (U1 §Env contract).
|
|
3473
|
+
ANTHROPIC_API_KEY: creds.backend === "direct" ? creds.apiKey ?? "" : "",
|
|
3474
|
+
[CMUX_PRESERVE_CLAUDE_AUTH_ENV]: "1"
|
|
3475
|
+
};
|
|
3476
|
+
if (creds.backend === "proxy") {
|
|
3477
|
+
env.ANTHROPIC_AUTH_TOKEN = creds.token ?? "";
|
|
3478
|
+
} else {
|
|
3479
|
+
const headers = formatCustomHeaders(creds.extraHeaders);
|
|
3480
|
+
if (headers)
|
|
3481
|
+
env.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
3482
|
+
}
|
|
3483
|
+
if (model)
|
|
3484
|
+
env.ANTHROPIC_MODEL = model;
|
|
3485
|
+
return env;
|
|
3486
|
+
}
|
|
3487
|
+
function formatCustomHeaders(headers) {
|
|
3488
|
+
const entries = Object.entries(headers ?? {});
|
|
3489
|
+
if (entries.length === 0)
|
|
3490
|
+
return void 0;
|
|
3491
|
+
return entries.map(([name, value]) => `${name}: ${value}`).join("\n");
|
|
3492
|
+
}
|
|
3493
|
+
function renderEnvAssignments(env) {
|
|
3494
|
+
return Object.keys(env).sort().map((key) => `${key}=${ansiCQuote(env[key] ?? "")}`).join(" ");
|
|
3495
|
+
}
|
|
3496
|
+
function ansiCQuote(value) {
|
|
3497
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\x0a");
|
|
3498
|
+
return `$'${escaped}'`;
|
|
3499
|
+
}
|
|
3500
|
+
var CMUX_PRESERVE_CLAUDE_AUTH_ENV;
|
|
3501
|
+
var init_env = __esm({
|
|
3502
|
+
"packages/core/dist/router/env.js"() {
|
|
3503
|
+
CMUX_PRESERVE_CLAUDE_AUTH_ENV = "CMUX_PRESERVE_CLAUDE_AUTH_SELECTION_ENV";
|
|
3504
|
+
}
|
|
3505
|
+
});
|
|
3506
|
+
|
|
3402
3507
|
// packages/core/dist/crew-routing.js
|
|
3403
3508
|
function resolveCrewRoute(taskText, config) {
|
|
3404
3509
|
const rules = config.defaults.crewRouting?.rules;
|
|
@@ -3410,6 +3515,7 @@ function resolveCrewRoute(taskText, config) {
|
|
|
3410
3515
|
return {
|
|
3411
3516
|
agent: rule.agent,
|
|
3412
3517
|
...rule.model !== void 0 ? { model: rule.model } : {},
|
|
3518
|
+
...rule.backend !== void 0 ? { backend: rule.backend } : {},
|
|
3413
3519
|
tier: rule.tier,
|
|
3414
3520
|
matchedRule: rule.match
|
|
3415
3521
|
};
|
|
@@ -3547,17 +3653,28 @@ async function pollFirstTurnConfirmedAt(getTaskRecord, project, id, scrapeSettle
|
|
|
3547
3653
|
await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
|
|
3548
3654
|
}
|
|
3549
3655
|
}
|
|
3550
|
-
function firstTrueOrBothFalse(a, b) {
|
|
3656
|
+
function firstTrueOrBothFalse(a, b, deadlineMs) {
|
|
3551
3657
|
return new Promise((resolve4) => {
|
|
3658
|
+
let done = false;
|
|
3659
|
+
const finish = (ok2) => {
|
|
3660
|
+
if (done)
|
|
3661
|
+
return;
|
|
3662
|
+
done = true;
|
|
3663
|
+
clearTimeout(timer);
|
|
3664
|
+
resolve4(ok2);
|
|
3665
|
+
};
|
|
3666
|
+
const timer = setTimeout(() => finish(false), deadlineMs);
|
|
3552
3667
|
let settledFalseCount = 0;
|
|
3553
3668
|
const onSettle = (ok2) => {
|
|
3669
|
+
if (done)
|
|
3670
|
+
return;
|
|
3554
3671
|
if (ok2) {
|
|
3555
|
-
|
|
3672
|
+
finish(true);
|
|
3556
3673
|
return;
|
|
3557
3674
|
}
|
|
3558
3675
|
settledFalseCount++;
|
|
3559
3676
|
if (settledFalseCount === 2)
|
|
3560
|
-
|
|
3677
|
+
finish(false);
|
|
3561
3678
|
};
|
|
3562
3679
|
a.then(onSettle, () => onSettle(false));
|
|
3563
3680
|
b.then(onSettle, () => onSettle(false));
|
|
@@ -3645,6 +3762,11 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3645
3762
|
if (!agent) {
|
|
3646
3763
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
3647
3764
|
}
|
|
3765
|
+
const crewRoleCfg = config.defaults.roles?.crew;
|
|
3766
|
+
const roleBackend = crewRoleCfg && crewRoleCfg.agent === agent.name ? crewRoleCfg.backend : void 0;
|
|
3767
|
+
const backend = resolveBackend(input.backend, route?.backend, roleBackend);
|
|
3768
|
+
assertBackendUsable({ backend, agent: agent.name, router: config.defaults.router });
|
|
3769
|
+
deps.onBackendResolved?.({ backend });
|
|
3648
3770
|
if (agentName === "codex") {
|
|
3649
3771
|
const codexRoleFile = path9.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
|
|
3650
3772
|
const roleInstructions = fs9.existsSync(codexRoleFile) ? fs9.readFileSync(codexRoleFile, "utf8") : void 0;
|
|
@@ -3667,12 +3789,19 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3667
3789
|
const interactive = agent.name === "claude" || agent.name === "opencode";
|
|
3668
3790
|
const crewRole = config.defaults.roles?.crew;
|
|
3669
3791
|
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
3670
|
-
const crewModel = input.model ?? route?.model ?? configModel;
|
|
3792
|
+
const crewModel = resolveRouterModel(input.model ?? route?.model ?? configModel, agent.name, config.defaults.router);
|
|
3671
3793
|
const crewThinking = input.thinking ?? config.defaults.roles?.crew?.thinking;
|
|
3672
3794
|
if (agentName !== "claude") {
|
|
3673
3795
|
deps.onModelResolved?.({ agentName, model: crewModel });
|
|
3674
3796
|
}
|
|
3675
3797
|
if (agentName === "claude") {
|
|
3798
|
+
let routerEnv = {};
|
|
3799
|
+
if (backend !== "native") {
|
|
3800
|
+
if (!deps.routerCredentials) {
|
|
3801
|
+
throw new Error(`backend '${backend}' requires router credentials, but the spawn path has no daemon credentials provider`);
|
|
3802
|
+
}
|
|
3803
|
+
routerEnv = buildRouterEnv(await deps.routerCredentials({ project: input.project, backend }), crewModel);
|
|
3804
|
+
}
|
|
3676
3805
|
ensureSocksDir();
|
|
3677
3806
|
const messagingSocketPath = path9.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
|
|
3678
3807
|
const rec = await deps.dispatchCrew({
|
|
@@ -3711,7 +3840,8 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3711
3840
|
const title2 = titleFor(input.project, name);
|
|
3712
3841
|
const pane2 = await deps.runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
3713
3842
|
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
3714
|
-
|
|
3843
|
+
const routerPrefix = Object.keys(routerEnv).length > 0 ? ` ${renderEnvAssignments(routerEnv)}` : "";
|
|
3844
|
+
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix}${routerPrefix} ${niceCrewCommand(cliCommand2)}`);
|
|
3715
3845
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
3716
3846
|
let claudeFirstTurn = firstTurnTask;
|
|
3717
3847
|
if (Buffer.byteLength(claudeFirstTurn, "utf8") > FIRST_TURN_INLINE_MAX_BYTES) {
|
|
@@ -3724,7 +3854,7 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3724
3854
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
3725
3855
|
const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
|
|
3726
3856
|
const cancelHookPoll = { stopped: false };
|
|
3727
|
-
const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id, scrapeDelivered, cancelHookPoll)) : await scrapeDelivered;
|
|
3857
|
+
const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id, scrapeDelivered, cancelHookPoll), FIRST_TURN_HOOK_CONFIRM_MAX_MS) : await scrapeDelivered;
|
|
3728
3858
|
cancelHookPoll.stopped = true;
|
|
3729
3859
|
if (!delivered) {
|
|
3730
3860
|
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.
|
|
@@ -4012,6 +4142,8 @@ var init_crew_spawn = __esm({
|
|
|
4012
4142
|
"packages/core/dist/crew-spawn.js"() {
|
|
4013
4143
|
init_control_channel();
|
|
4014
4144
|
init_dist();
|
|
4145
|
+
init_router_resolution();
|
|
4146
|
+
init_env();
|
|
4015
4147
|
init_crew_routing();
|
|
4016
4148
|
init_crew_protocol();
|
|
4017
4149
|
init_crew_lifecycle();
|
|
@@ -4062,6 +4194,164 @@ var init_captain_channel = __esm({
|
|
|
4062
4194
|
}
|
|
4063
4195
|
});
|
|
4064
4196
|
|
|
4197
|
+
// packages/core/dist/captain-record.js
|
|
4198
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync, writeFileSync as writeFileSync8 } from "fs";
|
|
4199
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
4200
|
+
function captainRecordPath(stateRoot, project) {
|
|
4201
|
+
return join11(stateRoot, project, "captain.json");
|
|
4202
|
+
}
|
|
4203
|
+
function readCaptainAddress(stateRoot, project) {
|
|
4204
|
+
try {
|
|
4205
|
+
const parsed = JSON.parse(readFileSync8(captainRecordPath(stateRoot, project), "utf-8"));
|
|
4206
|
+
return parsed && typeof parsed.agent === "string" ? parsed : null;
|
|
4207
|
+
} catch {
|
|
4208
|
+
return null;
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
function writeCaptainAddress(stateRoot, project, addr) {
|
|
4212
|
+
const p = captainRecordPath(stateRoot, project);
|
|
4213
|
+
mkdirSync6(dirname3(p), { recursive: true });
|
|
4214
|
+
writeFileSync8(p, JSON.stringify(addr, null, 2) + "\n");
|
|
4215
|
+
}
|
|
4216
|
+
function realpathOrSelf(p) {
|
|
4217
|
+
try {
|
|
4218
|
+
return realpathSync(p);
|
|
4219
|
+
} catch {
|
|
4220
|
+
return p;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
function sameDirectory(a, b) {
|
|
4224
|
+
if (!a)
|
|
4225
|
+
return false;
|
|
4226
|
+
const strip = (s) => realpathOrSelf(s).replace(/\/+$/, "");
|
|
4227
|
+
return strip(a) === strip(b);
|
|
4228
|
+
}
|
|
4229
|
+
var init_captain_record = __esm({
|
|
4230
|
+
"packages/core/dist/captain-record.js"() {
|
|
4231
|
+
}
|
|
4232
|
+
});
|
|
4233
|
+
|
|
4234
|
+
// packages/core/dist/opencode-session.js
|
|
4235
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
4236
|
+
async function listSessions(port, fetchImpl = fetch, timeoutMs = 5e3) {
|
|
4237
|
+
for (const path23 of ["/session", "/api/session"]) {
|
|
4238
|
+
const ac = new AbortController();
|
|
4239
|
+
const t = setTimeout(() => ac.abort(), timeoutMs);
|
|
4240
|
+
try {
|
|
4241
|
+
const res = await fetchImpl(`http://127.0.0.1:${port}${path23}`, { signal: ac.signal });
|
|
4242
|
+
if (!res.ok)
|
|
4243
|
+
continue;
|
|
4244
|
+
const rows = await res.json();
|
|
4245
|
+
if (Array.isArray(rows))
|
|
4246
|
+
return rows;
|
|
4247
|
+
} catch {
|
|
4248
|
+
} finally {
|
|
4249
|
+
clearTimeout(t);
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
return [];
|
|
4253
|
+
}
|
|
4254
|
+
function parseLiveOpencodeServers(psOutput) {
|
|
4255
|
+
const out = [];
|
|
4256
|
+
for (const line of psOutput.split("\n")) {
|
|
4257
|
+
const m = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
4258
|
+
if (!m)
|
|
4259
|
+
continue;
|
|
4260
|
+
const pid = Number(m[1]);
|
|
4261
|
+
const command = m[2].trim();
|
|
4262
|
+
const exe = command.split(/\s+/)[0] ?? "";
|
|
4263
|
+
if (!/(^|\/)opencode$/.test(exe))
|
|
4264
|
+
continue;
|
|
4265
|
+
const portRaw = command.match(/--port[= ](\d+)/)?.[1];
|
|
4266
|
+
if (!portRaw)
|
|
4267
|
+
continue;
|
|
4268
|
+
const sessionId = command.match(/--session[= ](\S+)/)?.[1];
|
|
4269
|
+
out.push({ pid, port: Number(portRaw), ...sessionId ? { sessionId } : {} });
|
|
4270
|
+
}
|
|
4271
|
+
return out;
|
|
4272
|
+
}
|
|
4273
|
+
function defaultPsOutput() {
|
|
4274
|
+
try {
|
|
4275
|
+
return execFileSync4("ps", ["-axo", "pid=,command="], { encoding: "utf-8", timeout: 2e3 });
|
|
4276
|
+
} catch {
|
|
4277
|
+
return "";
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
function defaultCwdOf(pid) {
|
|
4281
|
+
try {
|
|
4282
|
+
const out = execFileSync4("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], { encoding: "utf-8", timeout: 2e3 });
|
|
4283
|
+
const line = out.split("\n").find((l) => l.startsWith("n"));
|
|
4284
|
+
return line ? line.slice(1) : null;
|
|
4285
|
+
} catch {
|
|
4286
|
+
return null;
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
function discoverLiveOpencodeServer(opts) {
|
|
4290
|
+
const servers = parseLiveOpencodeServers((opts.psOutput ?? defaultPsOutput)());
|
|
4291
|
+
if (opts.sessionId) {
|
|
4292
|
+
const bySession = servers.find((s) => s.sessionId === opts.sessionId);
|
|
4293
|
+
if (bySession)
|
|
4294
|
+
return bySession;
|
|
4295
|
+
}
|
|
4296
|
+
const cwdOf = opts.cwdOf ?? defaultCwdOf;
|
|
4297
|
+
return servers.find((s) => sameDirectory(cwdOf(s.pid) ?? void 0, opts.directory)) ?? null;
|
|
4298
|
+
}
|
|
4299
|
+
function newestSessionInDirectory(rows, directory, createdAfterMs) {
|
|
4300
|
+
const hits = rows.filter((r) => sameDirectory(r.directory, directory) && (createdAfterMs === void 0 || (r.time?.created ?? 0) >= createdAfterMs));
|
|
4301
|
+
if (hits.length === 0)
|
|
4302
|
+
return null;
|
|
4303
|
+
return hits.reduce((a, b) => (b.time?.updated ?? 0) > (a.time?.updated ?? 0) ? b : a).id;
|
|
4304
|
+
}
|
|
4305
|
+
async function pollNewestSessionInDirectory(opts) {
|
|
4306
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
4307
|
+
const intervalMs = opts.intervalMs ?? 2e3;
|
|
4308
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
4309
|
+
const deadline = Date.now() + timeoutMs;
|
|
4310
|
+
for (; ; ) {
|
|
4311
|
+
const id = newestSessionInDirectory(await listSessions(opts.port, opts.fetchImpl), opts.directory, opts.createdAfterMs);
|
|
4312
|
+
if (id)
|
|
4313
|
+
return id;
|
|
4314
|
+
if (Date.now() >= deadline)
|
|
4315
|
+
return null;
|
|
4316
|
+
await sleep3(intervalMs);
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
async function resolveAndPersistOpencodeCaptain(opts) {
|
|
4320
|
+
if (opts.sessionId) {
|
|
4321
|
+
writeCaptainAddress(opts.stateRoot, opts.project, {
|
|
4322
|
+
agent: "opencode",
|
|
4323
|
+
port: opts.port,
|
|
4324
|
+
sessionId: opts.sessionId,
|
|
4325
|
+
directory: opts.directory,
|
|
4326
|
+
launchedAt: opts.launchedAt
|
|
4327
|
+
});
|
|
4328
|
+
return opts.sessionId;
|
|
4329
|
+
}
|
|
4330
|
+
const sessionId = await pollNewestSessionInDirectory({
|
|
4331
|
+
port: opts.port,
|
|
4332
|
+
directory: opts.directory,
|
|
4333
|
+
createdAfterMs: Date.parse(opts.launchedAt),
|
|
4334
|
+
timeoutMs: opts.timeoutMs,
|
|
4335
|
+
sleep: opts.sleep,
|
|
4336
|
+
fetchImpl: opts.fetchImpl
|
|
4337
|
+
});
|
|
4338
|
+
if (!sessionId)
|
|
4339
|
+
return null;
|
|
4340
|
+
writeCaptainAddress(opts.stateRoot, opts.project, {
|
|
4341
|
+
agent: "opencode",
|
|
4342
|
+
port: opts.port,
|
|
4343
|
+
sessionId,
|
|
4344
|
+
directory: opts.directory,
|
|
4345
|
+
launchedAt: opts.launchedAt
|
|
4346
|
+
});
|
|
4347
|
+
return sessionId;
|
|
4348
|
+
}
|
|
4349
|
+
var init_opencode_session = __esm({
|
|
4350
|
+
"packages/core/dist/opencode-session.js"() {
|
|
4351
|
+
init_captain_record();
|
|
4352
|
+
}
|
|
4353
|
+
});
|
|
4354
|
+
|
|
4065
4355
|
// packages/core/dist/daemon/delivery-loop.js
|
|
4066
4356
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
4067
4357
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
@@ -4187,7 +4477,12 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4187
4477
|
const cmux2 = daemonCmux;
|
|
4188
4478
|
const cfg = loadConfig();
|
|
4189
4479
|
const deliveries = /* @__PURE__ */ new Map();
|
|
4190
|
-
const deliveryStats = (project) =>
|
|
4480
|
+
const deliveryStats = (project) => {
|
|
4481
|
+
const s = deliveries.get(project)?.stats();
|
|
4482
|
+
if (!s)
|
|
4483
|
+
return s;
|
|
4484
|
+
return s.reason === "no-channel" ? { ...s, stuck: true } : s;
|
|
4485
|
+
};
|
|
4191
4486
|
const lastDeferred = /* @__PURE__ */ new Map();
|
|
4192
4487
|
const inFlightDelivery = () => {
|
|
4193
4488
|
let worst = null;
|
|
@@ -4273,20 +4568,55 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4273
4568
|
deliverEntry = { ...entry, message: `${prefix}${entry.message}` };
|
|
4274
4569
|
}
|
|
4275
4570
|
const result = await d.deliver(deliverEntry, async (text, sendOpts) => {
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
mode,
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4571
|
+
const agent = ctx.captainAgentFor?.(project);
|
|
4572
|
+
const picked = ctx.captainAgentFor ? agent ? ctx.captainChannels?.[agent] : void 0 : ctx.captainChannel;
|
|
4573
|
+
const mode = ctx.captainChannelMode?.() ?? "off";
|
|
4574
|
+
if (picked) {
|
|
4575
|
+
try {
|
|
4576
|
+
const r = await deliverToCaptain(project, text, { channel: picked, mode, log });
|
|
4577
|
+
if (r.handled)
|
|
4578
|
+
return;
|
|
4579
|
+
} catch (e) {
|
|
4580
|
+
log(`captain-channel ${project}: threw, falling back to pane \u2014 ${e.message}`);
|
|
4581
|
+
}
|
|
4582
|
+
if (agent === "opencode" && mode !== "off") {
|
|
4583
|
+
const rec = readCaptainAddress(stateRoot, project);
|
|
4584
|
+
let healed = false;
|
|
4585
|
+
if (rec?.port) {
|
|
4586
|
+
const fresh = newestSessionInDirectory(await listSessions(rec.port), rec.directory, Date.parse(rec.launchedAt));
|
|
4587
|
+
if (fresh && fresh !== rec.sessionId) {
|
|
4588
|
+
writeCaptainAddress(stateRoot, project, { ...rec, sessionId: fresh });
|
|
4589
|
+
healed = true;
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
if (!healed && rec) {
|
|
4593
|
+
const live = discoverLiveOpencodeServer({ directory: rec.directory, sessionId: rec.sessionId });
|
|
4594
|
+
if (live && (live.port !== rec.port || live.sessionId && live.sessionId !== rec.sessionId)) {
|
|
4595
|
+
writeCaptainAddress(stateRoot, project, {
|
|
4596
|
+
...rec,
|
|
4597
|
+
port: live.port,
|
|
4598
|
+
sessionId: live.sessionId ?? rec.sessionId
|
|
4599
|
+
});
|
|
4600
|
+
log(`captain-channel ${project}: re-resolved opencode address \u2192 port ${live.port}`);
|
|
4601
|
+
healed = true;
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
if (healed) {
|
|
4605
|
+
try {
|
|
4606
|
+
const retry = await deliverToCaptain(project, text, { channel: picked, mode, log });
|
|
4607
|
+
if (retry.handled)
|
|
4608
|
+
return;
|
|
4609
|
+
} catch (e) {
|
|
4610
|
+
log(`captain-channel ${project}: retry after re-resolve threw \u2014 ${e.message}`);
|
|
4611
|
+
}
|
|
4612
|
+
log(`captain-channel ${project}: re-resolved address still unreachable \u2014 falling back to pane`);
|
|
4613
|
+
} else {
|
|
4614
|
+
log(`captain-channel ${project}: no live opencode server \u2014 falling back to pane`);
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
} else if (ctx.captainAgentFor && (agent === "claude" || agent === "opencode")) {
|
|
4618
|
+
if (mode !== "off")
|
|
4619
|
+
throw new DeferDelivery(null, "no-channel");
|
|
4290
4620
|
}
|
|
4291
4621
|
try {
|
|
4292
4622
|
return await cmux2.send(surface, text, sendOpts);
|
|
@@ -4310,20 +4640,23 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4310
4640
|
lastDeferred.delete(project);
|
|
4311
4641
|
projectBackoff.delete(project);
|
|
4312
4642
|
} else {
|
|
4313
|
-
const { maxDeferCount
|
|
4643
|
+
const { maxDeferCount } = d.stats();
|
|
4644
|
+
const stuckNow = deliveryStats(project)?.stuck ?? false;
|
|
4314
4645
|
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
4315
4646
|
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
4316
4647
|
}
|
|
4317
4648
|
lastDeferred.set(project, { seq: entry.seq, deferCount: maxDeferCount });
|
|
4318
|
-
if (
|
|
4649
|
+
if (stuckNow) {
|
|
4319
4650
|
const streak = (projectBackoff.get(project)?.streak ?? 0) + 1;
|
|
4320
|
-
const
|
|
4651
|
+
const cap = d.stats().reason === "no-channel" ? 3e5 : 6e4;
|
|
4652
|
+
const backoffMs = Math.min(cap, 1e3 * 2 ** streak);
|
|
4321
4653
|
projectBackoff.set(project, { nextAttemptAt: Date.now() + backoffMs, streak });
|
|
4322
4654
|
}
|
|
4323
4655
|
break;
|
|
4324
4656
|
}
|
|
4325
4657
|
}
|
|
4326
|
-
const
|
|
4658
|
+
const stats = d.stats();
|
|
4659
|
+
const stuck = stats.stuck || stats.reason === "no-channel";
|
|
4327
4660
|
if (stuck && !stuckNotified.has(project)) {
|
|
4328
4661
|
stuckNotified.add(project);
|
|
4329
4662
|
const { maxDeferCount, reason } = d.stats();
|
|
@@ -4367,6 +4700,8 @@ var init_delivery_loop = __esm({
|
|
|
4367
4700
|
init_down_alert();
|
|
4368
4701
|
init_liveness2();
|
|
4369
4702
|
init_captain_channel();
|
|
4703
|
+
init_captain_record();
|
|
4704
|
+
init_opencode_session();
|
|
4370
4705
|
CURSOR_SUBSCRIBER = "captain";
|
|
4371
4706
|
TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
4372
4707
|
STUCK_ALERT_TEXT = {
|
|
@@ -4374,6 +4709,7 @@ var init_delivery_loop = __esm({
|
|
|
4374
4709
|
modal: (n) => `\u26A0\uFE0F DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${n}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.`,
|
|
4375
4710
|
draft: (n) => `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${n}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`,
|
|
4376
4711
|
"probe-failed": (n) => `\u26A0\uFE0F DELIVERY STUCK: reading your captain pane failed (stale/dead surface reference or cmux unavailable) and has blocked pending notification(s) for ${n}+ retries. Delivery re-resolves the pane automatically; if this persists after a captain restart, bounce the daemon to refresh its surface references.`,
|
|
4712
|
+
"no-channel": (n) => `\u26A0\uFE0F CAPTAIN NOT DELIVERABLE: this project's captain has no control channel (missing launch record, or a manually opened session). Crew notifications are queued and not lost, but cannot be delivered until the captain is launched by squadrant. Run \`squadrant launch <project>\` \u2014 a manually opened \`opencode -c\` cannot receive lifecycle notifications. (blocked for ${n}+ retries)`,
|
|
4377
4713
|
stable: (n) => `\u26A0\uFE0F DELIVERY STUCK: pending notification(s) have been blocked for ${n}+ retries. This keeps retrying safely and will deliver automatically once the blocker clears.`,
|
|
4378
4714
|
unknown: (n) => `\u26A0\uFE0F DELIVERY STUCK: pending notification(s) have been blocked for ${n}+ retries. This keeps retrying safely and will deliver automatically once the blocker clears.`
|
|
4379
4715
|
};
|
|
@@ -4401,6 +4737,32 @@ var init_gates = __esm({
|
|
|
4401
4737
|
}
|
|
4402
4738
|
});
|
|
4403
4739
|
|
|
4740
|
+
// packages/core/dist/router/credentials.js
|
|
4741
|
+
function buildRouterCredentialsRequest(project, backend) {
|
|
4742
|
+
return { kind: "router-credentials", project, backend };
|
|
4743
|
+
}
|
|
4744
|
+
async function resolveRouterCredentials(service, project, backend, deps = {}) {
|
|
4745
|
+
if (backend === "proxy") {
|
|
4746
|
+
const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
4747
|
+
const attempts = deps.attempts ?? ROUTER_READY_ATTEMPTS;
|
|
4748
|
+
const delayMs = deps.delayMs ?? ROUTER_READY_DELAY_MS;
|
|
4749
|
+
for (let i = 0; i < attempts; i++) {
|
|
4750
|
+
const health = await service.health();
|
|
4751
|
+
if (health.ready)
|
|
4752
|
+
break;
|
|
4753
|
+
await sleep3(delayMs);
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
return service.credentialsFor(project, backend);
|
|
4757
|
+
}
|
|
4758
|
+
var ROUTER_READY_ATTEMPTS, ROUTER_READY_DELAY_MS;
|
|
4759
|
+
var init_credentials = __esm({
|
|
4760
|
+
"packages/core/dist/router/credentials.js"() {
|
|
4761
|
+
ROUTER_READY_ATTEMPTS = 10;
|
|
4762
|
+
ROUTER_READY_DELAY_MS = 200;
|
|
4763
|
+
}
|
|
4764
|
+
});
|
|
4765
|
+
|
|
4404
4766
|
// packages/core/dist/daemon/server.js
|
|
4405
4767
|
function createServer2(ctx, handlers) {
|
|
4406
4768
|
const { store, log, attachConns } = ctx;
|
|
@@ -4426,6 +4788,13 @@ function createServer2(ctx, handlers) {
|
|
|
4426
4788
|
if (msg.kind === "event") {
|
|
4427
4789
|
return ctx.d.handle(msg);
|
|
4428
4790
|
}
|
|
4791
|
+
if (msg.kind === "router-credentials") {
|
|
4792
|
+
const service = ctx.routerService;
|
|
4793
|
+
if (!service) {
|
|
4794
|
+
throw new Error("router backend selected but squadrantd has no router service \u2014 configure defaults.router and restart the daemon");
|
|
4795
|
+
}
|
|
4796
|
+
return resolveRouterCredentials(service, msg.project, msg.backend);
|
|
4797
|
+
}
|
|
4429
4798
|
return ctx.d.handle(msg);
|
|
4430
4799
|
},
|
|
4431
4800
|
onAttach: (conn, frame) => {
|
|
@@ -4461,18 +4830,19 @@ function createServer2(ctx, handlers) {
|
|
|
4461
4830
|
var init_server = __esm({
|
|
4462
4831
|
"packages/core/dist/daemon/server.js"() {
|
|
4463
4832
|
init_protocol();
|
|
4833
|
+
init_credentials();
|
|
4464
4834
|
}
|
|
4465
4835
|
});
|
|
4466
4836
|
|
|
4467
4837
|
// packages/core/dist/daemon/exit-marker.js
|
|
4468
|
-
import { readFileSync as
|
|
4469
|
-
import { join as
|
|
4838
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3, existsSync as existsSync9 } from "fs";
|
|
4839
|
+
import { join as join12 } from "path";
|
|
4470
4840
|
function exitMarkerPath(stateRoot) {
|
|
4471
|
-
return
|
|
4841
|
+
return join12(stateRoot, "exit-marker.json");
|
|
4472
4842
|
}
|
|
4473
4843
|
function writeExitMarker(stateRoot, marker, log) {
|
|
4474
4844
|
try {
|
|
4475
|
-
|
|
4845
|
+
writeFileSync9(exitMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4476
4846
|
} catch (e) {
|
|
4477
4847
|
log(`exit marker write failed: ${e.message}`);
|
|
4478
4848
|
}
|
|
@@ -4483,7 +4853,7 @@ function consumeExitMarker(stateRoot, now = Date.now) {
|
|
|
4483
4853
|
return { marker: null };
|
|
4484
4854
|
let marker = null;
|
|
4485
4855
|
try {
|
|
4486
|
-
marker = JSON.parse(
|
|
4856
|
+
marker = JSON.parse(readFileSync9(p, "utf-8"));
|
|
4487
4857
|
} catch {
|
|
4488
4858
|
marker = null;
|
|
4489
4859
|
}
|
|
@@ -4497,18 +4867,18 @@ function consumeExitMarker(stateRoot, now = Date.now) {
|
|
|
4497
4867
|
return { marker, gapMs };
|
|
4498
4868
|
}
|
|
4499
4869
|
function runningMarkerPath(stateRoot) {
|
|
4500
|
-
return
|
|
4870
|
+
return join12(stateRoot, "running-marker.json");
|
|
4501
4871
|
}
|
|
4502
4872
|
function writeRunningMarker(stateRoot, marker, log) {
|
|
4503
4873
|
try {
|
|
4504
|
-
|
|
4874
|
+
writeFileSync9(runningMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4505
4875
|
} catch (e) {
|
|
4506
4876
|
log(`running marker write failed: ${e.message}`);
|
|
4507
4877
|
}
|
|
4508
4878
|
}
|
|
4509
4879
|
function readRunningMarker(stateRoot) {
|
|
4510
4880
|
try {
|
|
4511
|
-
return JSON.parse(
|
|
4881
|
+
return JSON.parse(readFileSync9(runningMarkerPath(stateRoot), "utf-8"));
|
|
4512
4882
|
} catch {
|
|
4513
4883
|
return null;
|
|
4514
4884
|
}
|
|
@@ -4528,8 +4898,8 @@ var init_exit_marker = __esm({
|
|
|
4528
4898
|
|
|
4529
4899
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
4530
4900
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4531
|
-
import { join as
|
|
4532
|
-
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as
|
|
4901
|
+
import { join as join13 } from "path";
|
|
4902
|
+
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
|
|
4533
4903
|
function distBuiltAt() {
|
|
4534
4904
|
try {
|
|
4535
4905
|
return statSync3(SELF_PATH).mtimeMs;
|
|
@@ -4537,10 +4907,10 @@ function distBuiltAt() {
|
|
|
4537
4907
|
return 0;
|
|
4538
4908
|
}
|
|
4539
4909
|
}
|
|
4540
|
-
function gatherLogStats(
|
|
4910
|
+
function gatherLogStats(path23, now, windowMs) {
|
|
4541
4911
|
let sizeBytes = 0;
|
|
4542
4912
|
try {
|
|
4543
|
-
sizeBytes = statSync3(
|
|
4913
|
+
sizeBytes = statSync3(path23).size;
|
|
4544
4914
|
} catch {
|
|
4545
4915
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
4546
4916
|
}
|
|
@@ -4551,7 +4921,7 @@ function gatherLogStats(path21, now, windowMs) {
|
|
|
4551
4921
|
const len = sizeBytes - start;
|
|
4552
4922
|
let text = "";
|
|
4553
4923
|
try {
|
|
4554
|
-
const fd = openSync2(
|
|
4924
|
+
const fd = openSync2(path23, "r");
|
|
4555
4925
|
try {
|
|
4556
4926
|
const buf = Buffer.alloc(len);
|
|
4557
4927
|
readSync(fd, buf, 0, len, start);
|
|
@@ -4582,7 +4952,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4582
4952
|
for (const r of store.list(project))
|
|
4583
4953
|
byState[r.state] = (byState[r.state] ?? 0) + 1;
|
|
4584
4954
|
let corruptCount = 0;
|
|
4585
|
-
const dir =
|
|
4955
|
+
const dir = join13(stateRoot, project);
|
|
4586
4956
|
try {
|
|
4587
4957
|
for (const n of readdirSync3(dir)) {
|
|
4588
4958
|
if (n.includes(".corrupt.")) {
|
|
@@ -4592,7 +4962,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4592
4962
|
if (!n.endsWith(".json"))
|
|
4593
4963
|
continue;
|
|
4594
4964
|
try {
|
|
4595
|
-
JSON.parse(
|
|
4965
|
+
JSON.parse(readFileSync10(join13(dir, n), "utf-8"));
|
|
4596
4966
|
} catch {
|
|
4597
4967
|
corruptCount++;
|
|
4598
4968
|
}
|
|
@@ -4607,7 +4977,7 @@ function gatherResults(resultsDir) {
|
|
|
4607
4977
|
try {
|
|
4608
4978
|
for (const n of readdirSync3(resultsDir)) {
|
|
4609
4979
|
try {
|
|
4610
|
-
const s = statSync3(
|
|
4980
|
+
const s = statSync3(join13(resultsDir, n));
|
|
4611
4981
|
if (s.isFile()) {
|
|
4612
4982
|
fileCount++;
|
|
4613
4983
|
totalBytes += s.size;
|
|
@@ -4627,7 +4997,7 @@ var init_snapshot_gather = __esm({
|
|
|
4627
4997
|
});
|
|
4628
4998
|
|
|
4629
4999
|
// packages/core/dist/daemon/start.js
|
|
4630
|
-
import { join as
|
|
5000
|
+
import { join as join14, dirname as dirname4 } from "path";
|
|
4631
5001
|
import { readdir } from "fs/promises";
|
|
4632
5002
|
function startDaemon(ctx, opts, pkgVersion) {
|
|
4633
5003
|
const { stateRoot, store, log, isPidAlive, resultsDir, taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills, broadcast, cancelPromotionsFor } = ctx;
|
|
@@ -4704,7 +5074,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4704
5074
|
return out;
|
|
4705
5075
|
}
|
|
4706
5076
|
async function gatherSnapshotInputs(now) {
|
|
4707
|
-
const logPath2 =
|
|
5077
|
+
const logPath2 = join14(dirname4(stateRoot), "squadrantd.log");
|
|
4708
5078
|
const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4709
5079
|
const projects = await Promise.all(tier2Projects.map(async (project) => {
|
|
4710
5080
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
|
|
@@ -4781,6 +5151,9 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4781
5151
|
log(`telegram bridge start failed: ${e.message}`);
|
|
4782
5152
|
}
|
|
4783
5153
|
}
|
|
5154
|
+
if (ctx.routerService) {
|
|
5155
|
+
void ctx.routerService.start().then(() => log(`router: listening ${ctx.routerService.url()}`)).catch((e) => log(`router service start failed: ${e.message}`));
|
|
5156
|
+
}
|
|
4784
5157
|
const autoConfigSafe = !!opts.runCmuxAutoConfig || !process.env.VITEST;
|
|
4785
5158
|
if (autoConfigSafe) {
|
|
4786
5159
|
try {
|
|
@@ -4866,7 +5239,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4866
5239
|
let rotationTimer;
|
|
4867
5240
|
let rotationTick;
|
|
4868
5241
|
if (rotationInterval > 0) {
|
|
4869
|
-
const inboxPath =
|
|
5242
|
+
const inboxPath = join14(stateRoot, "inbox");
|
|
4870
5243
|
rotationTick = async () => {
|
|
4871
5244
|
try {
|
|
4872
5245
|
let entries;
|
|
@@ -4890,7 +5263,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4890
5263
|
rotationTimer.unref?.();
|
|
4891
5264
|
}
|
|
4892
5265
|
return {
|
|
4893
|
-
stop(reason = "requested") {
|
|
5266
|
+
async stop(reason = "requested") {
|
|
4894
5267
|
const ppid = process.ppid;
|
|
4895
5268
|
const uptimeMs = Math.round(process.uptime() * 1e3);
|
|
4896
5269
|
const inFlight = inFlightDelivery();
|
|
@@ -4919,6 +5292,10 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4919
5292
|
}
|
|
4920
5293
|
for (const kill of ctx.activeHeadlessKills)
|
|
4921
5294
|
kill();
|
|
5295
|
+
try {
|
|
5296
|
+
await ctx.routerService?.stop();
|
|
5297
|
+
} catch {
|
|
5298
|
+
}
|
|
4922
5299
|
return new Promise((resolve4) => server.close(() => {
|
|
4923
5300
|
log(`exit-complete pid=${process.pid}`);
|
|
4924
5301
|
resolve4();
|
|
@@ -5933,7 +6310,7 @@ var init_bridge = __esm({
|
|
|
5933
6310
|
});
|
|
5934
6311
|
|
|
5935
6312
|
// packages/core/dist/restart-daemon.js
|
|
5936
|
-
import { execFileSync as
|
|
6313
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
5937
6314
|
import { existsSync as existsSync10 } from "fs";
|
|
5938
6315
|
function defaultIsRunning() {
|
|
5939
6316
|
return existsSync10(DAEMON_SOCK_PATH);
|
|
@@ -5943,7 +6320,7 @@ function defaultRunKickstart() {
|
|
|
5943
6320
|
const target = `gui/${uid}/${LABEL}`;
|
|
5944
6321
|
if (tryAcquireDaemonLock()) {
|
|
5945
6322
|
try {
|
|
5946
|
-
|
|
6323
|
+
execFileSync5("launchctl", kickstartArgv(target, true), { stdio: "ignore" });
|
|
5947
6324
|
} finally {
|
|
5948
6325
|
releaseDaemonLock();
|
|
5949
6326
|
}
|
|
@@ -6165,128 +6542,674 @@ var init_telegram = __esm({
|
|
|
6165
6542
|
}
|
|
6166
6543
|
});
|
|
6167
6544
|
|
|
6168
|
-
// packages/core/dist/
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
const cwd = process.cwd();
|
|
6172
|
-
for (const [name, proj] of Object.entries(config.projects)) {
|
|
6173
|
-
const resolvedPath = resolveHome(proj.path);
|
|
6174
|
-
if (cwd.startsWith(resolvedPath))
|
|
6175
|
-
return name;
|
|
6545
|
+
// packages/core/dist/router/types.js
|
|
6546
|
+
var init_types = __esm({
|
|
6547
|
+
"packages/core/dist/router/types.js"() {
|
|
6176
6548
|
}
|
|
6177
|
-
|
|
6549
|
+
});
|
|
6550
|
+
|
|
6551
|
+
// packages/core/dist/router/auth.js
|
|
6552
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
6553
|
+
function mintToken() {
|
|
6554
|
+
return randomBytes2(32).toString("base64url");
|
|
6178
6555
|
}
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
} catch {
|
|
6185
|
-
return false;
|
|
6186
|
-
}
|
|
6556
|
+
function parseBearer(header) {
|
|
6557
|
+
if (!header)
|
|
6558
|
+
return null;
|
|
6559
|
+
const m = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
6560
|
+
return m ? m[1].trim() : null;
|
|
6187
6561
|
}
|
|
6188
|
-
|
|
6189
|
-
const
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
await new Promise((r) => setTimeout(r, pollMs));
|
|
6194
|
-
}
|
|
6195
|
-
return false;
|
|
6562
|
+
function resolveProject(tokens, header) {
|
|
6563
|
+
const token = parseBearer(header);
|
|
6564
|
+
if (!token)
|
|
6565
|
+
return null;
|
|
6566
|
+
return tokens.get(token) ?? null;
|
|
6196
6567
|
}
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
const fromCfg = config.projects[opts.fromProject];
|
|
6200
|
-
const toCfg = config.projects[opts.toProject];
|
|
6201
|
-
if (!toCfg) {
|
|
6202
|
-
throw new Error(`target project '${opts.toProject}' not found in config`);
|
|
6568
|
+
var init_auth2 = __esm({
|
|
6569
|
+
"packages/core/dist/router/auth.js"() {
|
|
6203
6570
|
}
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6571
|
+
});
|
|
6572
|
+
|
|
6573
|
+
// packages/core/dist/router/errors.js
|
|
6574
|
+
function anthropicError(status, type, message) {
|
|
6575
|
+
return { status, body: { type: "error", error: { type, message } } };
|
|
6576
|
+
}
|
|
6577
|
+
var init_errors = __esm({
|
|
6578
|
+
"packages/core/dist/router/errors.js"() {
|
|
6207
6579
|
}
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6580
|
+
});
|
|
6581
|
+
|
|
6582
|
+
// packages/core/dist/router/sanitize.js
|
|
6583
|
+
function isObj(v2) {
|
|
6584
|
+
return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
|
|
6585
|
+
}
|
|
6586
|
+
function sanitizeRequest(body, upstream) {
|
|
6587
|
+
const out = { ...body };
|
|
6588
|
+
if (!upstream.isAnthropic) {
|
|
6589
|
+
for (const f of ANTHROPIC_ONLY_REQUEST_FIELDS)
|
|
6590
|
+
delete out[f];
|
|
6591
|
+
if (Array.isArray(out.tools)) {
|
|
6592
|
+
out.tools = out.tools.filter((t) => !(isObj(t) && typeof t.type === "string" && SERVER_TOOL_TYPE_RE.test(t.type)));
|
|
6220
6593
|
}
|
|
6221
6594
|
}
|
|
6222
|
-
|
|
6223
|
-
const attemptId = randomUUID4();
|
|
6224
|
-
const record = {
|
|
6225
|
-
id: randomUUID4(),
|
|
6226
|
-
project: opts.toProject,
|
|
6227
|
-
originProject: opts.fromProject,
|
|
6228
|
-
provider: opts.provider ?? "claude",
|
|
6229
|
-
mode: opts.mode ?? "headless",
|
|
6230
|
-
state: "submitted",
|
|
6231
|
-
task: opts.task,
|
|
6232
|
-
createdAt: now,
|
|
6233
|
-
lastHeartbeat: now,
|
|
6234
|
-
lastEvent: "dispatch",
|
|
6235
|
-
heartbeatBudgetMs: 3e5,
|
|
6236
|
-
attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }]
|
|
6237
|
-
};
|
|
6238
|
-
const result = await sendRequest(sockPath, { kind: "dispatch", record });
|
|
6239
|
-
return result;
|
|
6595
|
+
return out;
|
|
6240
6596
|
}
|
|
6241
|
-
var
|
|
6242
|
-
var
|
|
6243
|
-
"packages/core/dist/
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
GROUP_DISPATCH_WARMUP_TIMEOUT_MS = 12e4;
|
|
6247
|
-
GROUP_DISPATCH_WARMUP_POLL_MS = 1e3;
|
|
6597
|
+
var SERVER_TOOL_TYPE_RE, ANTHROPIC_ONLY_REQUEST_FIELDS;
|
|
6598
|
+
var init_sanitize = __esm({
|
|
6599
|
+
"packages/core/dist/router/sanitize.js"() {
|
|
6600
|
+
SERVER_TOOL_TYPE_RE = /^(bash|text_editor|str_replace_editor|computer|web_search|code_execution|memory)_\d+/;
|
|
6601
|
+
ANTHROPIC_ONLY_REQUEST_FIELDS = ["container", "context_management", "mcp_servers"];
|
|
6248
6602
|
}
|
|
6249
6603
|
});
|
|
6250
6604
|
|
|
6251
|
-
// packages/core/dist/
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6605
|
+
// packages/core/dist/router/stream.js
|
|
6606
|
+
import { Transform } from "stream";
|
|
6607
|
+
import { StringDecoder } from "string_decoder";
|
|
6608
|
+
function num(v2) {
|
|
6609
|
+
if (typeof v2 === "number")
|
|
6610
|
+
return Number.isFinite(v2) ? v2 : void 0;
|
|
6611
|
+
if (typeof v2 === "string" && v2.trim() !== "") {
|
|
6612
|
+
const n = Number(v2);
|
|
6613
|
+
return Number.isFinite(n) ? n : void 0;
|
|
6614
|
+
}
|
|
6615
|
+
return void 0;
|
|
6616
|
+
}
|
|
6617
|
+
function absorb(evt, acc) {
|
|
6618
|
+
if (typeof evt !== "object" || evt === null)
|
|
6619
|
+
return false;
|
|
6620
|
+
const e = evt;
|
|
6621
|
+
const message = typeof e.message === "object" && e.message !== null ? e.message : void 0;
|
|
6622
|
+
const usage2 = e.usage ?? message?.usage;
|
|
6623
|
+
let saw = false;
|
|
6624
|
+
if (usage2) {
|
|
6625
|
+
const input = num(usage2.input_tokens);
|
|
6626
|
+
if (input !== void 0) {
|
|
6627
|
+
acc.inputTokens = input;
|
|
6628
|
+
saw = true;
|
|
6629
|
+
}
|
|
6630
|
+
const output = num(usage2.output_tokens);
|
|
6631
|
+
if (output !== void 0) {
|
|
6632
|
+
acc.outputTokens = output;
|
|
6633
|
+
saw = true;
|
|
6634
|
+
}
|
|
6635
|
+
const read = num(usage2.cache_read_input_tokens);
|
|
6636
|
+
if (read !== void 0) {
|
|
6637
|
+
acc.cacheReadTokens = read;
|
|
6638
|
+
saw = true;
|
|
6639
|
+
}
|
|
6640
|
+
const write = num(usage2.cache_creation_input_tokens);
|
|
6641
|
+
if (write !== void 0) {
|
|
6642
|
+
acc.cacheWriteTokens = write;
|
|
6643
|
+
saw = true;
|
|
6644
|
+
}
|
|
6645
|
+
if (usage2.cost !== void 0) {
|
|
6646
|
+
const cost = num(usage2.cost);
|
|
6647
|
+
if (cost !== void 0) {
|
|
6648
|
+
acc.costUsd = cost;
|
|
6649
|
+
saw = true;
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
if (e.cost !== void 0) {
|
|
6654
|
+
const cost = num(e.cost);
|
|
6655
|
+
if (cost !== void 0) {
|
|
6656
|
+
acc.costUsd = cost;
|
|
6657
|
+
saw = true;
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
return saw;
|
|
6661
|
+
}
|
|
6662
|
+
function scan(bufRef, acc) {
|
|
6663
|
+
let saw = false;
|
|
6664
|
+
let idx;
|
|
6665
|
+
while ((idx = bufRef.value.indexOf("\n")) !== -1) {
|
|
6666
|
+
const line = bufRef.value.slice(0, idx).trim();
|
|
6667
|
+
bufRef.value = bufRef.value.slice(idx + 1);
|
|
6668
|
+
if (line.startsWith("data:")) {
|
|
6669
|
+
const payload = line.slice(5).trim();
|
|
6670
|
+
if (payload && payload !== "[DONE]") {
|
|
6671
|
+
try {
|
|
6672
|
+
if (absorb(JSON.parse(payload), acc))
|
|
6673
|
+
saw = true;
|
|
6674
|
+
} catch {
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6268
6677
|
}
|
|
6269
|
-
if (state === "working")
|
|
6270
|
-
return;
|
|
6271
|
-
await runtime.send(refId, prompt).catch(() => {
|
|
6272
|
-
});
|
|
6273
|
-
if (state === "loading")
|
|
6274
|
-
return;
|
|
6275
|
-
await sleep3(settleMs);
|
|
6276
|
-
const after = await read();
|
|
6277
|
-
if (after !== preSend)
|
|
6278
|
-
return;
|
|
6279
6678
|
}
|
|
6679
|
+
return saw;
|
|
6280
6680
|
}
|
|
6281
|
-
|
|
6282
|
-
const
|
|
6283
|
-
const
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6681
|
+
function createUsageTee(project, onUsage) {
|
|
6682
|
+
const buf = { value: "" };
|
|
6683
|
+
const acc = { project };
|
|
6684
|
+
const decoder = new StringDecoder("utf8");
|
|
6685
|
+
let sawUsage = false;
|
|
6686
|
+
return new Transform({
|
|
6687
|
+
transform(chunk, _enc, cb) {
|
|
6688
|
+
buf.value += decoder.write(chunk);
|
|
6689
|
+
sawUsage = scan(buf, acc) || sawUsage;
|
|
6690
|
+
cb(null, chunk);
|
|
6691
|
+
},
|
|
6692
|
+
flush(cb) {
|
|
6693
|
+
buf.value += decoder.end();
|
|
6694
|
+
if (buf.value.trim() !== "") {
|
|
6695
|
+
buf.value += "\n";
|
|
6696
|
+
sawUsage = scan(buf, acc) || sawUsage;
|
|
6697
|
+
}
|
|
6698
|
+
if (sawUsage) {
|
|
6699
|
+
try {
|
|
6700
|
+
onUsage(acc);
|
|
6701
|
+
} catch {
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
cb();
|
|
6705
|
+
}
|
|
6706
|
+
});
|
|
6707
|
+
}
|
|
6708
|
+
function usageFromJson(project, body) {
|
|
6709
|
+
const acc = { project };
|
|
6710
|
+
return absorb({ usage: body.usage, cost: body.cost }, acc) ? acc : void 0;
|
|
6711
|
+
}
|
|
6712
|
+
var init_stream = __esm({
|
|
6713
|
+
"packages/core/dist/router/stream.js"() {
|
|
6714
|
+
}
|
|
6715
|
+
});
|
|
6716
|
+
|
|
6717
|
+
// packages/core/dist/router/shim.js
|
|
6718
|
+
import { createServer as createServer3 } from "http";
|
|
6719
|
+
import { Readable } from "stream";
|
|
6720
|
+
function joinUrl(base, path23) {
|
|
6721
|
+
return base.replace(/\/+$/, "") + path23;
|
|
6722
|
+
}
|
|
6723
|
+
async function readBody(req) {
|
|
6724
|
+
const chunks = [];
|
|
6725
|
+
for await (const c of req)
|
|
6726
|
+
chunks.push(c);
|
|
6727
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
6728
|
+
}
|
|
6729
|
+
function writeJson(res, status, body) {
|
|
6730
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
6731
|
+
res.end(JSON.stringify(body));
|
|
6732
|
+
}
|
|
6733
|
+
function buildUpstreamHeaders(req, upstream, stream) {
|
|
6734
|
+
const headers = {
|
|
6735
|
+
"content-type": "application/json",
|
|
6736
|
+
accept: stream ? "text/event-stream" : "application/json"
|
|
6737
|
+
};
|
|
6738
|
+
for (const [k, v2] of Object.entries(req.headers)) {
|
|
6739
|
+
const key = k.toLowerCase();
|
|
6740
|
+
if (key.startsWith("anthropic-") && typeof v2 === "string")
|
|
6741
|
+
headers[key] = v2;
|
|
6742
|
+
}
|
|
6743
|
+
for (const [k, v2] of Object.entries(upstream.extraHeaders ?? {}))
|
|
6744
|
+
headers[k.toLowerCase()] = v2;
|
|
6745
|
+
const authName = (upstream.authHeader ?? DEFAULT_AUTH_HEADER).toLowerCase();
|
|
6746
|
+
headers[authName] = authName === "authorization" ? `Bearer ${upstream.apiKey}` : upstream.apiKey;
|
|
6747
|
+
if (!headers["anthropic-version"])
|
|
6748
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
6749
|
+
return headers;
|
|
6750
|
+
}
|
|
6751
|
+
function createRouterShim(opts) {
|
|
6752
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
6753
|
+
const log = opts.log ?? (() => {
|
|
6754
|
+
});
|
|
6755
|
+
let server;
|
|
6756
|
+
let starting;
|
|
6757
|
+
let boundPort = 0;
|
|
6758
|
+
let lastError;
|
|
6759
|
+
let upstreamReachable = true;
|
|
6760
|
+
const upstreamUrl = joinUrl(opts.upstream.baseUrl, "/v1/messages");
|
|
6761
|
+
function emitUsage(u) {
|
|
6762
|
+
try {
|
|
6763
|
+
opts.onUsage?.(u);
|
|
6764
|
+
} catch {
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
async function handleMessages(req, res) {
|
|
6768
|
+
const project = resolveProject(opts.projectTokens, req.headers.authorization);
|
|
6769
|
+
if (!project) {
|
|
6770
|
+
const e = anthropicError(401, "authentication_error", "invalid squadrant router token");
|
|
6771
|
+
writeJson(res, e.status, e.body);
|
|
6772
|
+
return;
|
|
6773
|
+
}
|
|
6774
|
+
let parsed;
|
|
6775
|
+
try {
|
|
6776
|
+
parsed = JSON.parse(await readBody(req));
|
|
6777
|
+
} catch {
|
|
6778
|
+
const e = anthropicError(400, "invalid_request_error", "malformed JSON body");
|
|
6779
|
+
writeJson(res, e.status, e.body);
|
|
6780
|
+
return;
|
|
6781
|
+
}
|
|
6782
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
6783
|
+
const e = anthropicError(400, "invalid_request_error", "request body must be a JSON object");
|
|
6784
|
+
writeJson(res, e.status, e.body);
|
|
6785
|
+
return;
|
|
6786
|
+
}
|
|
6787
|
+
const body = parsed;
|
|
6788
|
+
const wantsStream = body.stream === true;
|
|
6789
|
+
const sanitized = sanitizeRequest(body, opts.upstream);
|
|
6790
|
+
let upstreamRes;
|
|
6791
|
+
try {
|
|
6792
|
+
upstreamRes = await fetchImpl(upstreamUrl, {
|
|
6793
|
+
method: "POST",
|
|
6794
|
+
headers: buildUpstreamHeaders(req, opts.upstream, wantsStream),
|
|
6795
|
+
body: JSON.stringify(sanitized)
|
|
6796
|
+
});
|
|
6797
|
+
} catch (err) {
|
|
6798
|
+
upstreamReachable = false;
|
|
6799
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
6800
|
+
const e = anthropicError(502, "api_error", `router upstream unreachable: ${lastError}`);
|
|
6801
|
+
writeJson(res, e.status, e.body);
|
|
6802
|
+
return;
|
|
6803
|
+
}
|
|
6804
|
+
upstreamReachable = true;
|
|
6805
|
+
if (!upstreamRes.ok) {
|
|
6806
|
+
const text2 = await upstreamRes.text();
|
|
6807
|
+
log(`router upstream ${upstreamRes.status}: ${text2.slice(0, 500)}`);
|
|
6808
|
+
let message = text2.slice(0, 500);
|
|
6809
|
+
try {
|
|
6810
|
+
const parsedErr = JSON.parse(text2);
|
|
6811
|
+
message = parsedErr.error?.message ?? message;
|
|
6812
|
+
} catch {
|
|
6813
|
+
}
|
|
6814
|
+
if (upstreamRes.status >= 500) {
|
|
6815
|
+
upstreamReachable = false;
|
|
6816
|
+
lastError = message || "upstream error";
|
|
6817
|
+
const e2 = anthropicError(502, "api_error", lastError);
|
|
6818
|
+
writeJson(res, e2.status, e2.body);
|
|
6819
|
+
return;
|
|
6820
|
+
}
|
|
6821
|
+
const e = anthropicError(upstreamRes.status, "api_error", message || "upstream error");
|
|
6822
|
+
writeJson(res, e.status, e.body);
|
|
6823
|
+
return;
|
|
6824
|
+
}
|
|
6825
|
+
if (wantsStream && upstreamRes.body) {
|
|
6826
|
+
res.writeHead(upstreamRes.status, {
|
|
6827
|
+
"content-type": upstreamRes.headers.get("content-type") || "text/event-stream",
|
|
6828
|
+
"cache-control": "no-cache",
|
|
6829
|
+
connection: "keep-alive"
|
|
6830
|
+
});
|
|
6831
|
+
const nodeStream = Readable.fromWeb(upstreamRes.body);
|
|
6832
|
+
nodeStream.on("error", (err) => {
|
|
6833
|
+
log(`router upstream stream error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6834
|
+
try {
|
|
6835
|
+
res.end();
|
|
6836
|
+
} catch {
|
|
6837
|
+
}
|
|
6838
|
+
});
|
|
6839
|
+
const tee = createUsageTee(project, emitUsage);
|
|
6840
|
+
tee.on("error", (err) => {
|
|
6841
|
+
log(`router usage tee error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6842
|
+
});
|
|
6843
|
+
nodeStream.pipe(tee).pipe(res);
|
|
6844
|
+
res.on("close", () => nodeStream.destroy());
|
|
6845
|
+
return;
|
|
6846
|
+
}
|
|
6847
|
+
const text = await upstreamRes.text();
|
|
6848
|
+
let upstreamBody;
|
|
6849
|
+
try {
|
|
6850
|
+
upstreamBody = JSON.parse(text);
|
|
6851
|
+
} catch {
|
|
6852
|
+
const e = anthropicError(502, "api_error", "upstream returned non-JSON");
|
|
6853
|
+
writeJson(res, e.status, e.body);
|
|
6854
|
+
return;
|
|
6855
|
+
}
|
|
6856
|
+
const usage2 = usageFromJson(project, upstreamBody);
|
|
6857
|
+
if (usage2)
|
|
6858
|
+
emitUsage(usage2);
|
|
6859
|
+
writeJson(res, upstreamRes.status, upstreamBody);
|
|
6860
|
+
}
|
|
6861
|
+
function handler(req, res) {
|
|
6862
|
+
if (req.method === "POST" && req.url === "/v1/messages") {
|
|
6863
|
+
void handleMessages(req, res).catch((err) => {
|
|
6864
|
+
log(`router internal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6865
|
+
if (!res.headersSent) {
|
|
6866
|
+
const e = anthropicError(502, "api_error", "router internal error");
|
|
6867
|
+
writeJson(res, e.status, e.body);
|
|
6868
|
+
} else {
|
|
6869
|
+
res.end();
|
|
6870
|
+
}
|
|
6871
|
+
});
|
|
6872
|
+
return;
|
|
6873
|
+
}
|
|
6874
|
+
if (req.method === "GET" && req.url === "/healthz") {
|
|
6875
|
+
writeJson(res, 200, { ready: server?.listening === true, upstreamReachable, lastError });
|
|
6876
|
+
return;
|
|
6877
|
+
}
|
|
6878
|
+
writeJson(res, 404, anthropicError(404, "not_found_error", "not found").body);
|
|
6879
|
+
}
|
|
6880
|
+
return {
|
|
6881
|
+
async start() {
|
|
6882
|
+
if (server)
|
|
6883
|
+
return;
|
|
6884
|
+
if (starting)
|
|
6885
|
+
return starting;
|
|
6886
|
+
starting = new Promise((resolve4, reject) => {
|
|
6887
|
+
const s = createServer3(handler);
|
|
6888
|
+
const onStartupError = (err) => {
|
|
6889
|
+
s.off("error", onStartupError);
|
|
6890
|
+
reject(err);
|
|
6891
|
+
};
|
|
6892
|
+
s.once("error", onStartupError);
|
|
6893
|
+
s.listen(opts.port ?? 0, opts.host ?? "127.0.0.1", () => {
|
|
6894
|
+
s.off("error", onStartupError);
|
|
6895
|
+
s.on("error", (err) => {
|
|
6896
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
6897
|
+
log(`router server error: ${lastError}`);
|
|
6898
|
+
});
|
|
6899
|
+
server = s;
|
|
6900
|
+
const addr = s.address();
|
|
6901
|
+
boundPort = typeof addr === "object" && addr ? addr.port : 0;
|
|
6902
|
+
resolve4();
|
|
6903
|
+
});
|
|
6904
|
+
});
|
|
6905
|
+
try {
|
|
6906
|
+
await starting;
|
|
6907
|
+
} finally {
|
|
6908
|
+
starting = void 0;
|
|
6909
|
+
}
|
|
6910
|
+
},
|
|
6911
|
+
async stop() {
|
|
6912
|
+
try {
|
|
6913
|
+
await starting;
|
|
6914
|
+
} catch {
|
|
6915
|
+
}
|
|
6916
|
+
const s = server;
|
|
6917
|
+
if (!s)
|
|
6918
|
+
return;
|
|
6919
|
+
server = void 0;
|
|
6920
|
+
boundPort = 0;
|
|
6921
|
+
s.closeAllConnections?.();
|
|
6922
|
+
await new Promise((resolve4) => s.close(() => resolve4()));
|
|
6923
|
+
},
|
|
6924
|
+
url() {
|
|
6925
|
+
return `http://${opts.host ?? "127.0.0.1"}:${boundPort}`;
|
|
6926
|
+
},
|
|
6927
|
+
async health() {
|
|
6928
|
+
return { ready: server?.listening === true, upstreamReachable, lastError };
|
|
6929
|
+
}
|
|
6930
|
+
};
|
|
6931
|
+
}
|
|
6932
|
+
var DEFAULT_AUTH_HEADER;
|
|
6933
|
+
var init_shim = __esm({
|
|
6934
|
+
"packages/core/dist/router/shim.js"() {
|
|
6935
|
+
init_auth2();
|
|
6936
|
+
init_errors();
|
|
6937
|
+
init_sanitize();
|
|
6938
|
+
init_stream();
|
|
6939
|
+
DEFAULT_AUTH_HEADER = "Authorization";
|
|
6940
|
+
}
|
|
6941
|
+
});
|
|
6942
|
+
|
|
6943
|
+
// packages/core/dist/router/service.js
|
|
6944
|
+
function resolveRouterUpstream(router, env = process.env) {
|
|
6945
|
+
const credential = router.apiKey ?? (router.apiKeyEnv ? env[router.apiKeyEnv] : void 0) ?? "";
|
|
6946
|
+
const authHeader = router.authHeader ?? (router.kind === "opencode-go" ? "x-api-key" : "Authorization");
|
|
6947
|
+
return {
|
|
6948
|
+
baseUrl: router.baseUrl,
|
|
6949
|
+
apiKey: credential,
|
|
6950
|
+
authHeader,
|
|
6951
|
+
extraHeaders: router.extraHeaders,
|
|
6952
|
+
isAnthropic: router.isAnthropic ?? false
|
|
6953
|
+
};
|
|
6954
|
+
}
|
|
6955
|
+
function createRouterService(router, projects, deps = {}) {
|
|
6956
|
+
const log = deps.log ?? (() => {
|
|
6957
|
+
});
|
|
6958
|
+
const upstream = resolveRouterUpstream(router);
|
|
6959
|
+
if (!upstream.apiKey) {
|
|
6960
|
+
log("router: no credential configured (set defaults.router.apiKey or apiKeyEnv) \u2014 routed spawns will fail");
|
|
6961
|
+
}
|
|
6962
|
+
const tokenByProject = /* @__PURE__ */ new Map();
|
|
6963
|
+
const projectTokens = /* @__PURE__ */ new Map();
|
|
6964
|
+
for (const p of projects) {
|
|
6965
|
+
const t = mintToken();
|
|
6966
|
+
tokenByProject.set(p, t);
|
|
6967
|
+
projectTokens.set(t, p);
|
|
6968
|
+
}
|
|
6969
|
+
const shim = createRouterShim({
|
|
6970
|
+
upstream,
|
|
6971
|
+
projectTokens,
|
|
6972
|
+
port: router.port ?? 0,
|
|
6973
|
+
log,
|
|
6974
|
+
...deps.fetch ? { fetch: deps.fetch } : {}
|
|
6975
|
+
});
|
|
6976
|
+
let started = false;
|
|
6977
|
+
return {
|
|
6978
|
+
async start() {
|
|
6979
|
+
await shim.start();
|
|
6980
|
+
started = true;
|
|
6981
|
+
},
|
|
6982
|
+
async stop() {
|
|
6983
|
+
await shim.stop();
|
|
6984
|
+
started = false;
|
|
6985
|
+
},
|
|
6986
|
+
url: () => {
|
|
6987
|
+
if (!started)
|
|
6988
|
+
throw new Error("router service not started");
|
|
6989
|
+
return shim.url();
|
|
6990
|
+
},
|
|
6991
|
+
health: () => shim.health(),
|
|
6992
|
+
credentialsFor(project, backend) {
|
|
6993
|
+
if (!upstream.apiKey) {
|
|
6994
|
+
throw new Error("defaults.router credential is missing (set apiKey or apiKeyEnv)");
|
|
6995
|
+
}
|
|
6996
|
+
if (backend === "direct") {
|
|
6997
|
+
return { backend, baseUrl: router.baseUrl, apiKey: upstream.apiKey, extraHeaders: upstream.extraHeaders };
|
|
6998
|
+
}
|
|
6999
|
+
if (!started) {
|
|
7000
|
+
throw new Error("router service not started");
|
|
7001
|
+
}
|
|
7002
|
+
const token = tokenByProject.get(project);
|
|
7003
|
+
if (!token) {
|
|
7004
|
+
throw new Error(`router service has no token for project '${project}'`);
|
|
7005
|
+
}
|
|
7006
|
+
return { backend, baseUrl: shim.url(), token };
|
|
7007
|
+
}
|
|
7008
|
+
};
|
|
7009
|
+
}
|
|
7010
|
+
var init_service = __esm({
|
|
7011
|
+
"packages/core/dist/router/service.js"() {
|
|
7012
|
+
init_auth2();
|
|
7013
|
+
init_shim();
|
|
7014
|
+
}
|
|
7015
|
+
});
|
|
7016
|
+
|
|
7017
|
+
// packages/core/dist/router/index.js
|
|
7018
|
+
var init_router = __esm({
|
|
7019
|
+
"packages/core/dist/router/index.js"() {
|
|
7020
|
+
init_types();
|
|
7021
|
+
init_auth2();
|
|
7022
|
+
init_errors();
|
|
7023
|
+
init_sanitize();
|
|
7024
|
+
init_stream();
|
|
7025
|
+
init_shim();
|
|
7026
|
+
init_service();
|
|
7027
|
+
init_env();
|
|
7028
|
+
init_credentials();
|
|
7029
|
+
}
|
|
7030
|
+
});
|
|
7031
|
+
|
|
7032
|
+
// packages/core/dist/group-dispatch.js
|
|
7033
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
7034
|
+
import { createConnection as createConnection2 } from "net";
|
|
7035
|
+
import { join as join15 } from "path";
|
|
7036
|
+
function resolveCurrentProject(config) {
|
|
7037
|
+
const cwd = process.cwd();
|
|
7038
|
+
for (const [name, proj] of Object.entries(config.projects)) {
|
|
7039
|
+
const resolvedPath = resolveHome(proj.path);
|
|
7040
|
+
if (cwd.startsWith(resolvedPath))
|
|
7041
|
+
return name;
|
|
7042
|
+
}
|
|
7043
|
+
return null;
|
|
7044
|
+
}
|
|
7045
|
+
async function probeCaptainChannel(project, opts = {}) {
|
|
7046
|
+
const stateRoot = opts.stateRoot ?? join15(CONFIG_DIR, "state");
|
|
7047
|
+
const addr = readCaptainAddress(stateRoot, project);
|
|
7048
|
+
if (!addr)
|
|
7049
|
+
return false;
|
|
7050
|
+
if (addr.agent === "opencode") {
|
|
7051
|
+
if (addr.port == null)
|
|
7052
|
+
return false;
|
|
7053
|
+
return httpReachable(addr.port, opts.fetchImpl ?? fetch, opts.timeoutMs ?? 5e3);
|
|
7054
|
+
}
|
|
7055
|
+
try {
|
|
7056
|
+
return await (opts.socketAccepts ?? socketAccepts)(captainSocketPath(project));
|
|
7057
|
+
} catch {
|
|
7058
|
+
return false;
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
async function httpReachable(port, fetchImpl, timeoutMs) {
|
|
7062
|
+
for (const path23 of ["/session", "/api/session"]) {
|
|
7063
|
+
const ac = new AbortController();
|
|
7064
|
+
const t = setTimeout(() => ac.abort(), timeoutMs);
|
|
7065
|
+
try {
|
|
7066
|
+
const res = await fetchImpl(`http://127.0.0.1:${port}${path23}`, { signal: ac.signal });
|
|
7067
|
+
if (res.ok)
|
|
7068
|
+
return true;
|
|
7069
|
+
} catch {
|
|
7070
|
+
} finally {
|
|
7071
|
+
clearTimeout(t);
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
return false;
|
|
7075
|
+
}
|
|
7076
|
+
function socketAccepts(socketPath, timeoutMs = 2e3) {
|
|
7077
|
+
return new Promise((resolve4) => {
|
|
7078
|
+
let settled = false;
|
|
7079
|
+
const sock = createConnection2(socketPath);
|
|
7080
|
+
const done = (v2) => {
|
|
7081
|
+
if (settled)
|
|
7082
|
+
return;
|
|
7083
|
+
settled = true;
|
|
7084
|
+
sock.destroy();
|
|
7085
|
+
resolve4(v2);
|
|
7086
|
+
};
|
|
7087
|
+
sock.setTimeout(timeoutMs, () => done(false));
|
|
7088
|
+
sock.once("connect", () => done(true));
|
|
7089
|
+
sock.once("error", () => done(false));
|
|
7090
|
+
});
|
|
7091
|
+
}
|
|
7092
|
+
async function isCaptainAlive(project, sockPath = DAEMON_SOCK_PATH, channelAlive = probeCaptainChannel) {
|
|
7093
|
+
try {
|
|
7094
|
+
const health = await sendRequest(sockPath, { kind: "health", project }, 5e3);
|
|
7095
|
+
const captain = health?.find((h) => h.kind === "captain" && h.project === project);
|
|
7096
|
+
if (captain?.state === "alive")
|
|
7097
|
+
return true;
|
|
7098
|
+
} catch {
|
|
7099
|
+
}
|
|
7100
|
+
try {
|
|
7101
|
+
return await channelAlive(project);
|
|
7102
|
+
} catch {
|
|
7103
|
+
return false;
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
async function waitForWarmup(project, sockPath = DAEMON_SOCK_PATH, timeoutMs = GROUP_DISPATCH_WARMUP_TIMEOUT_MS, pollMs = GROUP_DISPATCH_WARMUP_POLL_MS, channelAlive) {
|
|
7107
|
+
const deadline = Date.now() + timeoutMs;
|
|
7108
|
+
while (Date.now() < deadline) {
|
|
7109
|
+
if (await isCaptainAlive(project, sockPath, channelAlive))
|
|
7110
|
+
return true;
|
|
7111
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
7112
|
+
}
|
|
7113
|
+
return false;
|
|
7114
|
+
}
|
|
7115
|
+
async function dispatchToSibling(opts) {
|
|
7116
|
+
const config = loadConfig();
|
|
7117
|
+
const fromCfg = config.projects[opts.fromProject];
|
|
7118
|
+
const toCfg = config.projects[opts.toProject];
|
|
7119
|
+
if (!toCfg) {
|
|
7120
|
+
throw new Error(`target project '${opts.toProject}' not found in config`);
|
|
7121
|
+
}
|
|
7122
|
+
const sameGroup = !!fromCfg?.group && !!toCfg.group && fromCfg.group === toCfg.group;
|
|
7123
|
+
if (toCfg.acceptDelegations === false) {
|
|
7124
|
+
throw new Error(`cannot dispatch to '${opts.toProject}': project has acceptDelegations set to false`);
|
|
7125
|
+
}
|
|
7126
|
+
const sockPath = opts.sockPath ?? DAEMON_SOCK_PATH;
|
|
7127
|
+
const alive = await isCaptainAlive(opts.toProject, sockPath, opts.channelAlive);
|
|
7128
|
+
if (!alive) {
|
|
7129
|
+
if (!sameGroup) {
|
|
7130
|
+
throw new Error(`cannot dispatch to '${opts.toProject}': captain is not running and cross-group dispatch does not auto-boot it. Use 'squadrant ping ${opts.toProject} "<msg>"' or start it manually with 'squadrant launch ${opts.toProject}', then retry.`);
|
|
7131
|
+
}
|
|
7132
|
+
if (opts.bootCaptain) {
|
|
7133
|
+
await opts.bootCaptain(opts.toProject);
|
|
7134
|
+
}
|
|
7135
|
+
const warmed = await waitForWarmup(opts.toProject, sockPath, opts.warmupTimeoutMs, opts.warmupPollMs, opts.channelAlive);
|
|
7136
|
+
if (!warmed) {
|
|
7137
|
+
throw new Error(`dispatch to '${opts.toProject}' timed out waiting for captain warmup (>${(opts.warmupTimeoutMs ?? GROUP_DISPATCH_WARMUP_TIMEOUT_MS) / 1e3}s)`);
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
const now = Date.now();
|
|
7141
|
+
const attemptId = randomUUID4();
|
|
7142
|
+
const record = {
|
|
7143
|
+
id: randomUUID4(),
|
|
7144
|
+
project: opts.toProject,
|
|
7145
|
+
originProject: opts.fromProject,
|
|
7146
|
+
provider: opts.provider ?? "claude",
|
|
7147
|
+
mode: opts.mode ?? "headless",
|
|
7148
|
+
state: "submitted",
|
|
7149
|
+
task: opts.task,
|
|
7150
|
+
createdAt: now,
|
|
7151
|
+
lastHeartbeat: now,
|
|
7152
|
+
lastEvent: "dispatch",
|
|
7153
|
+
heartbeatBudgetMs: 3e5,
|
|
7154
|
+
attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }]
|
|
7155
|
+
};
|
|
7156
|
+
const result = await sendRequest(sockPath, { kind: "dispatch", record });
|
|
7157
|
+
return result;
|
|
7158
|
+
}
|
|
7159
|
+
var GROUP_DISPATCH_WARMUP_TIMEOUT_MS, GROUP_DISPATCH_WARMUP_POLL_MS;
|
|
7160
|
+
var init_group_dispatch = __esm({
|
|
7161
|
+
"packages/core/dist/group-dispatch.js"() {
|
|
7162
|
+
init_dist();
|
|
7163
|
+
init_protocol();
|
|
7164
|
+
init_captain_record();
|
|
7165
|
+
init_captain_channel();
|
|
7166
|
+
GROUP_DISPATCH_WARMUP_TIMEOUT_MS = 12e4;
|
|
7167
|
+
GROUP_DISPATCH_WARMUP_POLL_MS = 1e3;
|
|
7168
|
+
}
|
|
7169
|
+
});
|
|
7170
|
+
|
|
7171
|
+
// packages/core/dist/launch-workspace.js
|
|
7172
|
+
async function deliverStartupPrompt(runtime, refId, prompt, opts = {}) {
|
|
7173
|
+
const classify = opts.classifyScreen ?? (() => "idle");
|
|
7174
|
+
const readyTimeoutMs = opts.readyTimeoutMs ?? 3e4;
|
|
7175
|
+
const settleMs = opts.settleMs ?? 2500;
|
|
7176
|
+
const pollMs = opts.pollMs ?? 1e3;
|
|
7177
|
+
const maxAttempts = opts.maxAttempts ?? 3;
|
|
7178
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7179
|
+
const read = async () => runtime.readScreen(refId).catch(() => "");
|
|
7180
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
7181
|
+
const deadline = Date.now() + readyTimeoutMs;
|
|
7182
|
+
let preSend = await read();
|
|
7183
|
+
let state = classify(preSend);
|
|
7184
|
+
while (state === "loading" && Date.now() < deadline) {
|
|
7185
|
+
await sleep3(pollMs);
|
|
7186
|
+
preSend = await read();
|
|
7187
|
+
state = classify(preSend);
|
|
7188
|
+
}
|
|
7189
|
+
if (state === "working")
|
|
7190
|
+
return true;
|
|
7191
|
+
await runtime.send(refId, prompt).catch(() => {
|
|
7192
|
+
});
|
|
7193
|
+
await sleep3(settleMs);
|
|
7194
|
+
const after = await read();
|
|
7195
|
+
if (after !== preSend)
|
|
7196
|
+
return true;
|
|
7197
|
+
if (state === "loading")
|
|
7198
|
+
break;
|
|
7199
|
+
}
|
|
7200
|
+
process.stderr.write(`\u26A0\uFE0F Startup prompt not confirmed on ${refId} after ${maxAttempts} attempt(s) \u2014 the agent may be idle with no startup prompt. Re-run launch, or send it manually.
|
|
7201
|
+
`);
|
|
7202
|
+
return false;
|
|
7203
|
+
}
|
|
7204
|
+
async function bootWorkspace(opts) {
|
|
7205
|
+
const { runtime, workspaceName, agentCmd, cwd, navigate = false, forceFresh = false, pinToTop = false, initialPrompt } = opts;
|
|
7206
|
+
const existing = await runtime.status(workspaceName);
|
|
7207
|
+
if (existing && forceFresh) {
|
|
7208
|
+
opts.onStoppingStale?.(workspaceName);
|
|
7209
|
+
await runtime.stop(existing.id);
|
|
7210
|
+
} else if (existing) {
|
|
7211
|
+
opts.onAlreadyExists?.(workspaceName);
|
|
7212
|
+
opts.selectWorkspace?.(existing.id);
|
|
6290
7213
|
return;
|
|
6291
7214
|
}
|
|
6292
7215
|
const rawCurrent = opts.getCurrentWorkspace?.() ?? null;
|
|
@@ -6889,8 +7812,10 @@ var init_source = __esm({
|
|
|
6889
7812
|
var dist_exports = {};
|
|
6890
7813
|
__export(dist_exports, {
|
|
6891
7814
|
AGENT_BINS: () => AGENT_BINS,
|
|
7815
|
+
ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
|
|
6892
7816
|
BOT_COMMANDS: () => BOT_COMMANDS,
|
|
6893
7817
|
CC_SOCKS_DIR: () => CC_SOCKS_DIR,
|
|
7818
|
+
CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
|
|
6894
7819
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
6895
7820
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
6896
7821
|
CaptainDelivery: () => CaptainDelivery,
|
|
@@ -6910,6 +7835,10 @@ __export(dist_exports, {
|
|
|
6910
7835
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
6911
7836
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
6912
7837
|
READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
|
|
7838
|
+
READ_ONLY_TOP_LEVEL_COMMANDS: () => READ_ONLY_TOP_LEVEL_COMMANDS,
|
|
7839
|
+
ROUTER_READY_ATTEMPTS: () => ROUTER_READY_ATTEMPTS,
|
|
7840
|
+
ROUTER_READY_DELAY_MS: () => ROUTER_READY_DELAY_MS,
|
|
7841
|
+
SERVER_TOOL_TYPE_RE: () => SERVER_TOOL_TYPE_RE,
|
|
6913
7842
|
STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
|
|
6914
7843
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
6915
7844
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
@@ -6918,17 +7847,22 @@ __export(dist_exports, {
|
|
|
6918
7847
|
WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
|
|
6919
7848
|
_resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
|
|
6920
7849
|
ageText: () => ageText,
|
|
7850
|
+
anthropicError: () => anthropicError,
|
|
6921
7851
|
appendCaptainMessage: () => appendCaptainMessage,
|
|
6922
7852
|
appendToMailbox: () => appendToMailbox,
|
|
6923
7853
|
assembleDaemonSnapshot: () => assembleDaemonSnapshot,
|
|
7854
|
+
assertBackendUsable: () => assertBackendUsable,
|
|
6924
7855
|
bootWorkspace: () => bootWorkspace,
|
|
6925
7856
|
buildCompletionProtocol: () => buildCompletionProtocol,
|
|
6926
7857
|
buildContext: () => buildContext,
|
|
6927
7858
|
buildDaemonPath: () => buildDaemonPath,
|
|
6928
7859
|
buildFreshness: () => buildFreshness,
|
|
7860
|
+
buildRouterCredentialsRequest: () => buildRouterCredentialsRequest,
|
|
7861
|
+
buildRouterEnv: () => buildRouterEnv,
|
|
6929
7862
|
buildSideFirstTurn: () => buildSideFirstTurn,
|
|
6930
7863
|
capAllowed: () => capAllowed,
|
|
6931
7864
|
capOutput: () => capOutput,
|
|
7865
|
+
captainRecordPath: () => captainRecordPath,
|
|
6932
7866
|
captainSocketPath: () => captainSocketPath,
|
|
6933
7867
|
checkFact: () => checkFact,
|
|
6934
7868
|
classifyHealth: () => classifyHealth,
|
|
@@ -6947,11 +7881,14 @@ __export(dist_exports, {
|
|
|
6947
7881
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
6948
7882
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
6949
7883
|
createLaunch: () => createLaunch,
|
|
7884
|
+
createRouterService: () => createRouterService,
|
|
7885
|
+
createRouterShim: () => createRouterShim,
|
|
6950
7886
|
createRunCommand: () => createRunCommand,
|
|
6951
7887
|
createStore: () => createStore,
|
|
6952
7888
|
createSurfaceLivenessProbe: () => createSurfaceLivenessProbe,
|
|
6953
7889
|
createTelegramBridge: () => createTelegramBridge,
|
|
6954
7890
|
createTelegramClient: () => createTelegramClient,
|
|
7891
|
+
createUsageTee: () => createUsageTee,
|
|
6955
7892
|
createWorkItem: () => createWorkItem,
|
|
6956
7893
|
createWorkStore: () => createWorkStore,
|
|
6957
7894
|
crewPaneTitle: () => crewPaneTitle,
|
|
@@ -6972,6 +7909,7 @@ __export(dist_exports, {
|
|
|
6972
7909
|
detectGroupAndUser: () => detectGroupAndUser,
|
|
6973
7910
|
detectGroupId: () => detectGroupId,
|
|
6974
7911
|
discoverCaptainSurface: () => discoverCaptainSurface,
|
|
7912
|
+
discoverLiveOpencodeServer: () => discoverLiveOpencodeServer,
|
|
6975
7913
|
dispatchToSibling: () => dispatchToSibling,
|
|
6976
7914
|
encodeFrame: () => encodeFrame,
|
|
6977
7915
|
encodeMsg: () => encodeMsg,
|
|
@@ -6985,6 +7923,7 @@ __export(dist_exports, {
|
|
|
6985
7923
|
findProjectByThread: () => findProjectByThread,
|
|
6986
7924
|
findWorkItemById: () => findWorkItemById,
|
|
6987
7925
|
forceKickstartAndVerify: () => forceKickstartAndVerify,
|
|
7926
|
+
formatCustomHeaders: () => formatCustomHeaders,
|
|
6988
7927
|
formatInbound: () => formatInbound,
|
|
6989
7928
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
6990
7929
|
formatLifecycle: () => formatLifecycle,
|
|
@@ -7001,33 +7940,44 @@ __export(dist_exports, {
|
|
|
7001
7940
|
isNotifyActive: () => isNotifyActive,
|
|
7002
7941
|
isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
|
|
7003
7942
|
isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
|
|
7943
|
+
isReadOnlyTopLevelCommand: () => isReadOnlyTopLevelCommand,
|
|
7004
7944
|
isSideTitle: () => isSideTitle,
|
|
7005
7945
|
isStickyAttention: () => isStickyAttention,
|
|
7006
7946
|
isTurnAccepted: () => isTurnAccepted,
|
|
7007
7947
|
isVersionUpgrade: () => isVersionUpgrade,
|
|
7948
|
+
joinUrl: () => joinUrl,
|
|
7008
7949
|
kickstartArgv: () => kickstartArgv,
|
|
7009
7950
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
7951
|
+
listSessions: () => listSessions,
|
|
7010
7952
|
loadSessions: () => loadSessions,
|
|
7011
7953
|
loadState: () => loadState,
|
|
7012
7954
|
mailboxStats: () => mailboxStats,
|
|
7013
7955
|
makeGate: () => makeGate,
|
|
7014
7956
|
maskToken: () => maskToken,
|
|
7957
|
+
mintToken: () => mintToken,
|
|
7015
7958
|
nameFromTitle: () => nameFromTitle,
|
|
7959
|
+
newestSessionInDirectory: () => newestSessionInDirectory,
|
|
7016
7960
|
nextAutoName: () => nextAutoName,
|
|
7017
7961
|
niceCrewCommand: () => niceCrewCommand,
|
|
7018
7962
|
notifyToggle: () => notifyToggle,
|
|
7963
|
+
parseBearer: () => parseBearer,
|
|
7019
7964
|
parseCommand: () => parseCommand,
|
|
7965
|
+
parseLiveOpencodeServers: () => parseLiveOpencodeServers,
|
|
7020
7966
|
parseNotifyPref: () => parseNotifyPref,
|
|
7021
7967
|
parseProgramArgs: () => parseProgramArgs,
|
|
7022
7968
|
plistPath: () => plistPath,
|
|
7969
|
+
pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
|
|
7023
7970
|
printForeignInstallError: () => printForeignInstallError,
|
|
7024
7971
|
printVersionUpgradeNotice: () => printVersionUpgradeNotice,
|
|
7972
|
+
probeCaptainChannel: () => probeCaptainChannel,
|
|
7025
7973
|
programArgsBlock: () => programArgsBlock,
|
|
7026
7974
|
projectHealth: () => projectHealth,
|
|
7027
7975
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
7976
|
+
readCaptainAddress: () => readCaptainAddress,
|
|
7028
7977
|
readCursor: () => readCursor,
|
|
7029
7978
|
readFromCursor: () => readFromCursor,
|
|
7030
7979
|
readRunningMarker: () => readRunningMarker,
|
|
7980
|
+
realpathOrSelf: () => realpathOrSelf,
|
|
7031
7981
|
reapCrewChildren: () => reapCrewChildren,
|
|
7032
7982
|
reapOrphanedCrews: () => reapOrphanedCrews,
|
|
7033
7983
|
reconcileLiveness: () => reconcileLiveness,
|
|
@@ -7037,12 +7987,18 @@ __export(dist_exports, {
|
|
|
7037
7987
|
reduceLifecycle: () => reduceLifecycle,
|
|
7038
7988
|
releaseDaemonLock: () => releaseDaemonLock,
|
|
7039
7989
|
removeRunningMarker: () => removeRunningMarker,
|
|
7990
|
+
renderEnvAssignments: () => renderEnvAssignments,
|
|
7040
7991
|
renderPlist: () => renderPlist,
|
|
7041
7992
|
reregisterDaemon: () => reregisterDaemon,
|
|
7042
7993
|
resolveAgentBinDirs: () => resolveAgentBinDirs,
|
|
7994
|
+
resolveAndPersistOpencodeCaptain: () => resolveAndPersistOpencodeCaptain,
|
|
7995
|
+
resolveBackend: () => resolveBackend,
|
|
7043
7996
|
resolveCrewRoute: () => resolveCrewRoute,
|
|
7044
7997
|
resolveCurrentProject: () => resolveCurrentProject,
|
|
7045
7998
|
resolveGate: () => resolveGate,
|
|
7999
|
+
resolveProject: () => resolveProject,
|
|
8000
|
+
resolveRouterCredentials: () => resolveRouterCredentials,
|
|
8001
|
+
resolveRouterUpstream: () => resolveRouterUpstream,
|
|
7046
8002
|
resolveSetupGroup: () => resolveSetupGroup,
|
|
7047
8003
|
resolveSetupToken: () => resolveSetupToken,
|
|
7048
8004
|
resolveSetupUserId: () => resolveSetupUserId,
|
|
@@ -7070,7 +8026,9 @@ __export(dist_exports, {
|
|
|
7070
8026
|
runTelegramSend: () => runTelegramSend,
|
|
7071
8027
|
runTelegramStatus: () => runTelegramStatus,
|
|
7072
8028
|
runningMarkerPath: () => runningMarkerPath,
|
|
8029
|
+
sameDirectory: () => sameDirectory,
|
|
7073
8030
|
sanitizePathForPlist: () => sanitizePathForPlist,
|
|
8031
|
+
sanitizeRequest: () => sanitizeRequest,
|
|
7074
8032
|
saveSessions: () => saveSessions,
|
|
7075
8033
|
saveState: () => saveState,
|
|
7076
8034
|
screenHasSplashMarker: () => screenHasSplashMarker,
|
|
@@ -7079,6 +8037,7 @@ __export(dist_exports, {
|
|
|
7079
8037
|
setNotify: () => setNotify,
|
|
7080
8038
|
setTopic: () => setTopic,
|
|
7081
8039
|
shellQuote: () => shellQuote,
|
|
8040
|
+
shouldBuildRouterService: () => shouldBuildRouterService,
|
|
7082
8041
|
shouldStartFresh: () => shouldStartFresh,
|
|
7083
8042
|
sideNameFromTitle: () => sideNameFromTitle,
|
|
7084
8043
|
sideNextAutoName: () => sideNextAutoName,
|
|
@@ -7094,8 +8053,10 @@ __export(dist_exports, {
|
|
|
7094
8053
|
topicKey: () => topicKey,
|
|
7095
8054
|
topicName: () => topicName,
|
|
7096
8055
|
tryAcquireDaemonLock: () => tryAcquireDaemonLock,
|
|
8056
|
+
usageFromJson: () => usageFromJson,
|
|
7097
8057
|
waitForCaptainDelivery: () => waitForCaptainDelivery,
|
|
7098
8058
|
waitForWarmup: () => waitForWarmup,
|
|
8059
|
+
writeCaptainAddress: () => writeCaptainAddress,
|
|
7099
8060
|
writeCursor: () => writeCursor,
|
|
7100
8061
|
writeExitMarker: () => writeExitMarker,
|
|
7101
8062
|
writeRunningMarker: () => writeRunningMarker,
|
|
@@ -7129,7 +8090,9 @@ var init_dist2 = __esm({
|
|
|
7129
8090
|
init_crew_protocol();
|
|
7130
8091
|
init_crew_lifecycle();
|
|
7131
8092
|
init_telegram();
|
|
8093
|
+
init_router();
|
|
7132
8094
|
init_crew_routing();
|
|
8095
|
+
init_router_resolution();
|
|
7133
8096
|
init_restart_daemon();
|
|
7134
8097
|
init_group_dispatch();
|
|
7135
8098
|
init_launch_workspace();
|
|
@@ -7145,6 +8108,8 @@ var init_dist2 = __esm({
|
|
|
7145
8108
|
init_source();
|
|
7146
8109
|
init_control_channel();
|
|
7147
8110
|
init_captain_channel();
|
|
8111
|
+
init_captain_record();
|
|
8112
|
+
init_opencode_session();
|
|
7148
8113
|
}
|
|
7149
8114
|
});
|
|
7150
8115
|
|
|
@@ -7160,14 +8125,75 @@ init_dist2();
|
|
|
7160
8125
|
init_dist2();
|
|
7161
8126
|
init_dist2();
|
|
7162
8127
|
init_dist2();
|
|
7163
|
-
import { join as
|
|
7164
|
-
import { homedir as
|
|
8128
|
+
import { join as join26, dirname as dirname5, resolve as resolve3 } from "path";
|
|
8129
|
+
import { homedir as homedir14 } from "os";
|
|
7165
8130
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7166
|
-
import { readFileSync as
|
|
8131
|
+
import { readFileSync as readFileSync18, statSync as statSync4, existsSync as existsSync13 } from "fs";
|
|
7167
8132
|
|
|
7168
8133
|
// packages/agents/dist/drivers/claude.js
|
|
7169
8134
|
import { execSync as execSync2 } from "child_process";
|
|
7170
8135
|
|
|
8136
|
+
// packages/agents/dist/sessions/claude-sessions.js
|
|
8137
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
8138
|
+
import { join as join17 } from "path";
|
|
8139
|
+
|
|
8140
|
+
// packages/agents/dist/claude/registry.js
|
|
8141
|
+
import fs13 from "fs";
|
|
8142
|
+
import { join as join16 } from "path";
|
|
8143
|
+
import { homedir as homedir7 } from "os";
|
|
8144
|
+
var CLAUDE_SESSIONS_DIR = join16(homedir7(), ".claude", "sessions");
|
|
8145
|
+
var PID_JSON = /^(\d+)\.json$/;
|
|
8146
|
+
function parseRegistryDir(files, readFile7) {
|
|
8147
|
+
const out = [];
|
|
8148
|
+
for (const name of files) {
|
|
8149
|
+
const m = PID_JSON.exec(name);
|
|
8150
|
+
if (!m)
|
|
8151
|
+
continue;
|
|
8152
|
+
let json;
|
|
8153
|
+
try {
|
|
8154
|
+
json = JSON.parse(readFile7(name));
|
|
8155
|
+
} catch {
|
|
8156
|
+
continue;
|
|
8157
|
+
}
|
|
8158
|
+
out.push({ ...json, pid: parseInt(m[1], 10) });
|
|
8159
|
+
}
|
|
8160
|
+
return out;
|
|
8161
|
+
}
|
|
8162
|
+
function toLifecycleSnapshot(entry, taskId, alive, now) {
|
|
8163
|
+
const at = entry.statusUpdatedAt ?? now;
|
|
8164
|
+
const base = { taskId, alive, origin: "agent", at, pid: entry.pid };
|
|
8165
|
+
if (!alive)
|
|
8166
|
+
return { ...base, state: "unknown" };
|
|
8167
|
+
switch (entry.status) {
|
|
8168
|
+
case "busy":
|
|
8169
|
+
case "shell":
|
|
8170
|
+
return { ...base, state: "running" };
|
|
8171
|
+
case "idle":
|
|
8172
|
+
return { ...base, state: "idle" };
|
|
8173
|
+
case "waiting":
|
|
8174
|
+
return {
|
|
8175
|
+
...base,
|
|
8176
|
+
state: "needsInput",
|
|
8177
|
+
detail: { reason: entry.waitingFor, note: entry.waitingFor }
|
|
8178
|
+
};
|
|
8179
|
+
default:
|
|
8180
|
+
return { ...base, state: "unknown" };
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
function readClaudeStatusBySocketPath(socketPath) {
|
|
8184
|
+
let files;
|
|
8185
|
+
try {
|
|
8186
|
+
files = fs13.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
8187
|
+
} catch {
|
|
8188
|
+
return void 0;
|
|
8189
|
+
}
|
|
8190
|
+
const entries = parseRegistryDir(files, (name) => fs13.readFileSync(join16(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
8191
|
+
const entry = entries.find((e) => e.messagingSocketPath === socketPath);
|
|
8192
|
+
if (!entry)
|
|
8193
|
+
return void 0;
|
|
8194
|
+
return { status: entry.status, statusUpdatedAt: entry.statusUpdatedAt, sessionId: entry.sessionId };
|
|
8195
|
+
}
|
|
8196
|
+
|
|
7171
8197
|
// packages/agents/dist/drivers/codex.js
|
|
7172
8198
|
import { execSync as execSync3 } from "child_process";
|
|
7173
8199
|
|
|
@@ -7177,8 +8203,14 @@ import { execSync as execSync4 } from "child_process";
|
|
|
7177
8203
|
// packages/agents/dist/drivers/opencode.js
|
|
7178
8204
|
import { execSync as execSync5 } from "child_process";
|
|
7179
8205
|
|
|
8206
|
+
// packages/agents/dist/sessions/opencode-sessions.js
|
|
8207
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
8208
|
+
import { join as join18 } from "path";
|
|
8209
|
+
import { homedir as homedir8 } from "os";
|
|
8210
|
+
var SQUADRANT_STATE_DIR = join18(homedir8(), ".config", "squadrant", "state");
|
|
8211
|
+
|
|
7180
8212
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
7181
|
-
import
|
|
8213
|
+
import fs14 from "fs";
|
|
7182
8214
|
import path13 from "path";
|
|
7183
8215
|
|
|
7184
8216
|
// packages/agents/dist/projection/cursor.js
|
|
@@ -7201,10 +8233,102 @@ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from
|
|
|
7201
8233
|
import path17 from "path";
|
|
7202
8234
|
import os9 from "os";
|
|
7203
8235
|
|
|
8236
|
+
// packages/agents/dist/projection/opencode-skills.js
|
|
8237
|
+
import fs15 from "fs";
|
|
8238
|
+
import os10 from "os";
|
|
8239
|
+
import path18 from "path";
|
|
8240
|
+
var OPENCODE_SKILL_MARKER = ".squadrant-managed";
|
|
8241
|
+
var MARKER_BODY = "squadrant\n";
|
|
8242
|
+
function defaultOpencodeSkillsRoot(env = process.env) {
|
|
8243
|
+
const configHome = env.XDG_CONFIG_HOME || path18.join(os10.homedir(), ".config");
|
|
8244
|
+
return path18.join(configHome, "opencode", "skills");
|
|
8245
|
+
}
|
|
8246
|
+
var SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
8247
|
+
function parseSkillName(raw) {
|
|
8248
|
+
const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
8249
|
+
if (!fm)
|
|
8250
|
+
return null;
|
|
8251
|
+
const name = fm[1].match(/^name:\s*(.+)$/m)?.[1]?.trim();
|
|
8252
|
+
const description = fm[1].match(/^description:\s*(.+)$/m)?.[1]?.trim();
|
|
8253
|
+
if (!name || !description)
|
|
8254
|
+
return null;
|
|
8255
|
+
return name;
|
|
8256
|
+
}
|
|
8257
|
+
function listSourceSkills(sourceSkillsDir) {
|
|
8258
|
+
const out = /* @__PURE__ */ new Map();
|
|
8259
|
+
if (!fs15.existsSync(sourceSkillsDir))
|
|
8260
|
+
return out;
|
|
8261
|
+
for (const entry of fs15.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
|
|
8262
|
+
if (!entry.isDirectory())
|
|
8263
|
+
continue;
|
|
8264
|
+
const skillPath = path18.join(sourceSkillsDir, entry.name, "SKILL.md");
|
|
8265
|
+
if (!fs15.existsSync(skillPath))
|
|
8266
|
+
continue;
|
|
8267
|
+
const name = parseSkillName(fs15.readFileSync(skillPath, "utf-8"));
|
|
8268
|
+
if (name && SAFE_NAME.test(name))
|
|
8269
|
+
out.set(name, skillPath);
|
|
8270
|
+
}
|
|
8271
|
+
return out;
|
|
8272
|
+
}
|
|
8273
|
+
function hasMarker(dir) {
|
|
8274
|
+
return fs15.existsSync(path18.join(dir, OPENCODE_SKILL_MARKER));
|
|
8275
|
+
}
|
|
8276
|
+
function syncOpencodeSkills(opts) {
|
|
8277
|
+
const result = { written: [], unchanged: [], removed: [], skipped: [] };
|
|
8278
|
+
const wanted = listSourceSkills(opts.sourceSkillsDir);
|
|
8279
|
+
if (wanted.size === 0 && !fs15.existsSync(opts.sourceSkillsDir))
|
|
8280
|
+
return result;
|
|
8281
|
+
if (!opts.dryRun)
|
|
8282
|
+
fs15.mkdirSync(opts.skillsRoot, { recursive: true });
|
|
8283
|
+
for (const [name, srcPath] of wanted) {
|
|
8284
|
+
const targetDir = path18.join(opts.skillsRoot, name);
|
|
8285
|
+
const targetSkill = path18.join(targetDir, "SKILL.md");
|
|
8286
|
+
const managed = hasMarker(targetDir);
|
|
8287
|
+
if (fs15.existsSync(targetSkill) && !managed) {
|
|
8288
|
+
result.skipped.push({ name, reason: "foreign-collision" });
|
|
8289
|
+
continue;
|
|
8290
|
+
}
|
|
8291
|
+
const current = fs15.existsSync(targetSkill) ? fs15.readFileSync(targetSkill) : null;
|
|
8292
|
+
if (current && current.equals(fs15.readFileSync(srcPath))) {
|
|
8293
|
+
if (!managed && !opts.dryRun) {
|
|
8294
|
+
fs15.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
8295
|
+
}
|
|
8296
|
+
result.unchanged.push(name);
|
|
8297
|
+
continue;
|
|
8298
|
+
}
|
|
8299
|
+
if (!opts.dryRun) {
|
|
8300
|
+
fs15.mkdirSync(targetDir, { recursive: true });
|
|
8301
|
+
fs15.copyFileSync(srcPath, targetSkill);
|
|
8302
|
+
fs15.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
8303
|
+
}
|
|
8304
|
+
result.written.push(name);
|
|
8305
|
+
}
|
|
8306
|
+
if (fs15.existsSync(opts.skillsRoot)) {
|
|
8307
|
+
for (const entry of fs15.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
|
|
8308
|
+
if (!entry.isDirectory() || wanted.has(entry.name))
|
|
8309
|
+
continue;
|
|
8310
|
+
const dir = path18.join(opts.skillsRoot, entry.name);
|
|
8311
|
+
if (!hasMarker(dir))
|
|
8312
|
+
continue;
|
|
8313
|
+
if (!opts.dryRun)
|
|
8314
|
+
fs15.rmSync(dir, { recursive: true, force: true });
|
|
8315
|
+
result.removed.push(entry.name);
|
|
8316
|
+
}
|
|
8317
|
+
}
|
|
8318
|
+
return result;
|
|
8319
|
+
}
|
|
8320
|
+
function syncShippedOpencodeSkills(opts) {
|
|
8321
|
+
return syncOpencodeSkills({
|
|
8322
|
+
sourceSkillsDir: path18.join(opts.pkgRoot, "plugin", "skills"),
|
|
8323
|
+
skillsRoot: opts.skillsRoot ?? defaultOpencodeSkillsRoot(),
|
|
8324
|
+
dryRun: opts.dryRun
|
|
8325
|
+
});
|
|
8326
|
+
}
|
|
8327
|
+
|
|
7204
8328
|
// packages/agents/dist/projection/claude.js
|
|
7205
8329
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
7206
|
-
import
|
|
7207
|
-
import
|
|
8330
|
+
import path19 from "path";
|
|
8331
|
+
import os11 from "os";
|
|
7208
8332
|
|
|
7209
8333
|
// packages/agents/dist/codex/app-server-client.js
|
|
7210
8334
|
import { EventEmitter } from "events";
|
|
@@ -7478,11 +8602,11 @@ function toSnapshot(ev) {
|
|
|
7478
8602
|
|
|
7479
8603
|
// packages/agents/dist/codex/config.js
|
|
7480
8604
|
import { readFile as readFile6 } from "fs/promises";
|
|
7481
|
-
import { homedir as
|
|
7482
|
-
import { join as
|
|
8605
|
+
import { homedir as homedir9 } from "os";
|
|
8606
|
+
import { join as join19 } from "path";
|
|
7483
8607
|
async function resolveCodexModel() {
|
|
7484
|
-
const home = process.env["CODEX_HOME"] ??
|
|
7485
|
-
const configPath =
|
|
8608
|
+
const home = process.env["CODEX_HOME"] ?? join19(homedir9(), ".codex");
|
|
8609
|
+
const configPath = join19(home, "config.toml");
|
|
7486
8610
|
let text;
|
|
7487
8611
|
try {
|
|
7488
8612
|
text = await readFile6(configPath, "utf8");
|
|
@@ -7994,10 +9118,10 @@ var OpencodeSseBridge = class {
|
|
|
7994
9118
|
|
|
7995
9119
|
// packages/agents/dist/interactive/claude.js
|
|
7996
9120
|
import { execSync as execSync6 } from "child_process";
|
|
7997
|
-
import { readFileSync as
|
|
7998
|
-
import { homedir as
|
|
7999
|
-
import { join as
|
|
8000
|
-
var
|
|
9121
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
9122
|
+
import { homedir as homedir10 } from "os";
|
|
9123
|
+
import { join as join20 } from "path";
|
|
9124
|
+
var nextHookRequestId = Date.now();
|
|
8001
9125
|
|
|
8002
9126
|
// packages/agents/dist/headless/types.js
|
|
8003
9127
|
var HEADLESS_ERROR_TAIL = 2e3;
|
|
@@ -8160,67 +9284,8 @@ function runHeadless(opts) {
|
|
|
8160
9284
|
}
|
|
8161
9285
|
|
|
8162
9286
|
// packages/agents/dist/claude/peer-registry-source.js
|
|
8163
|
-
import { readdirSync as
|
|
8164
|
-
import { join as
|
|
8165
|
-
|
|
8166
|
-
// packages/agents/dist/claude/registry.js
|
|
8167
|
-
import fs14 from "fs";
|
|
8168
|
-
import { join as join16 } from "path";
|
|
8169
|
-
import { homedir as homedir9 } from "os";
|
|
8170
|
-
var CLAUDE_SESSIONS_DIR = join16(homedir9(), ".claude", "sessions");
|
|
8171
|
-
var PID_JSON = /^(\d+)\.json$/;
|
|
8172
|
-
function parseRegistryDir(files, readFile7) {
|
|
8173
|
-
const out = [];
|
|
8174
|
-
for (const name of files) {
|
|
8175
|
-
const m = PID_JSON.exec(name);
|
|
8176
|
-
if (!m)
|
|
8177
|
-
continue;
|
|
8178
|
-
let json;
|
|
8179
|
-
try {
|
|
8180
|
-
json = JSON.parse(readFile7(name));
|
|
8181
|
-
} catch {
|
|
8182
|
-
continue;
|
|
8183
|
-
}
|
|
8184
|
-
out.push({ ...json, pid: parseInt(m[1], 10) });
|
|
8185
|
-
}
|
|
8186
|
-
return out;
|
|
8187
|
-
}
|
|
8188
|
-
function toLifecycleSnapshot(entry, taskId, alive, now) {
|
|
8189
|
-
const at = entry.statusUpdatedAt ?? now;
|
|
8190
|
-
const base = { taskId, alive, origin: "agent", at, pid: entry.pid };
|
|
8191
|
-
if (!alive)
|
|
8192
|
-
return { ...base, state: "unknown" };
|
|
8193
|
-
switch (entry.status) {
|
|
8194
|
-
case "busy":
|
|
8195
|
-
case "shell":
|
|
8196
|
-
return { ...base, state: "running" };
|
|
8197
|
-
case "idle":
|
|
8198
|
-
return { ...base, state: "idle" };
|
|
8199
|
-
case "waiting":
|
|
8200
|
-
return {
|
|
8201
|
-
...base,
|
|
8202
|
-
state: "needsInput",
|
|
8203
|
-
detail: { reason: entry.waitingFor, note: entry.waitingFor }
|
|
8204
|
-
};
|
|
8205
|
-
default:
|
|
8206
|
-
return { ...base, state: "unknown" };
|
|
8207
|
-
}
|
|
8208
|
-
}
|
|
8209
|
-
function readClaudeStatusBySocketPath(socketPath) {
|
|
8210
|
-
let files;
|
|
8211
|
-
try {
|
|
8212
|
-
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
8213
|
-
} catch {
|
|
8214
|
-
return void 0;
|
|
8215
|
-
}
|
|
8216
|
-
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join16(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
8217
|
-
const entry = entries.find((e) => e.messagingSocketPath === socketPath);
|
|
8218
|
-
if (!entry)
|
|
8219
|
-
return void 0;
|
|
8220
|
-
return { status: entry.status, statusUpdatedAt: entry.statusUpdatedAt, sessionId: entry.sessionId };
|
|
8221
|
-
}
|
|
8222
|
-
|
|
8223
|
-
// packages/agents/dist/claude/peer-registry-source.js
|
|
9287
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
|
|
9288
|
+
import { join as join21 } from "path";
|
|
8224
9289
|
function defaultIsAlive(pid) {
|
|
8225
9290
|
try {
|
|
8226
9291
|
process.kill(pid, 0);
|
|
@@ -8244,8 +9309,8 @@ var ClaudePeerRegistrySource = class {
|
|
|
8244
9309
|
pollMs;
|
|
8245
9310
|
log;
|
|
8246
9311
|
constructor(o = {}) {
|
|
8247
|
-
this.readdir = o.readdir ?? (() =>
|
|
8248
|
-
this.readFile = o.readFile ?? ((n) =>
|
|
9312
|
+
this.readdir = o.readdir ?? (() => readdirSync6(CLAUDE_SESSIONS_DIR));
|
|
9313
|
+
this.readFile = o.readFile ?? ((n) => readFileSync14(join21(CLAUDE_SESSIONS_DIR, n), "utf8"));
|
|
8249
9314
|
this.isAlive = o.isAlive ?? defaultIsAlive;
|
|
8250
9315
|
this.now = o.now ?? Date.now;
|
|
8251
9316
|
this.pollMs = o.pollMs ?? 2e3;
|
|
@@ -8557,6 +9622,119 @@ var ClaudeReceiptListener = class {
|
|
|
8557
9622
|
}
|
|
8558
9623
|
};
|
|
8559
9624
|
|
|
9625
|
+
// packages/agents/dist/claude/api-key-approval.js
|
|
9626
|
+
import fs16 from "fs";
|
|
9627
|
+
import os12 from "os";
|
|
9628
|
+
import path20 from "path";
|
|
9629
|
+
|
|
9630
|
+
// packages/agents/dist/opencode/http-channel.js
|
|
9631
|
+
var OpencodeHttpChannel = class {
|
|
9632
|
+
name = "opencode-http";
|
|
9633
|
+
agent = "opencode";
|
|
9634
|
+
/** taskId → resolved session id. Invalidated on 404 (see send()). */
|
|
9635
|
+
sessionByTask = /* @__PURE__ */ new Map();
|
|
9636
|
+
fetchImpl;
|
|
9637
|
+
portFor;
|
|
9638
|
+
sessionFor;
|
|
9639
|
+
timeoutMs;
|
|
9640
|
+
log;
|
|
9641
|
+
constructor(deps) {
|
|
9642
|
+
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
9643
|
+
this.portFor = deps.portFor;
|
|
9644
|
+
this.sessionFor = deps.sessionFor;
|
|
9645
|
+
this.timeoutMs = deps.timeoutMs ?? 5e3;
|
|
9646
|
+
this.log = deps.log;
|
|
9647
|
+
}
|
|
9648
|
+
async send(taskId, message) {
|
|
9649
|
+
const port = this.portFor(taskId);
|
|
9650
|
+
if (port == null)
|
|
9651
|
+
return { status: "unsupported" };
|
|
9652
|
+
const sessionId = this.sessionFor?.(taskId) ?? await this.resolveSession(taskId, port);
|
|
9653
|
+
if (!sessionId)
|
|
9654
|
+
return { status: "gone" };
|
|
9655
|
+
let res;
|
|
9656
|
+
try {
|
|
9657
|
+
res = await this.request(`http://127.0.0.1:${port}/session/${sessionId}/prompt_async`, {
|
|
9658
|
+
method: "POST",
|
|
9659
|
+
headers: { "content-type": "application/json" },
|
|
9660
|
+
body: JSON.stringify({ parts: [{ type: "text", text: message }] })
|
|
9661
|
+
});
|
|
9662
|
+
} catch (e) {
|
|
9663
|
+
this.log?.(`opencode-http send transport error for ${taskId}: ${e.message}`);
|
|
9664
|
+
return { status: "gone" };
|
|
9665
|
+
}
|
|
9666
|
+
if (res.status === 204 || res.status === 200) {
|
|
9667
|
+
return { status: "accepted", via: this.name };
|
|
9668
|
+
}
|
|
9669
|
+
if (res.status === 404) {
|
|
9670
|
+
this.sessionByTask.delete(taskId);
|
|
9671
|
+
this.log?.(`opencode-http: session ${sessionId} for ${taskId} is gone (404)`);
|
|
9672
|
+
return { status: "gone" };
|
|
9673
|
+
}
|
|
9674
|
+
this.log?.(`opencode-http: unexpected status ${res.status} for ${taskId}`);
|
|
9675
|
+
return { status: "gone" };
|
|
9676
|
+
}
|
|
9677
|
+
/**
|
|
9678
|
+
* Non-mutating reachability check. MUST NOT deliver anything — shadow mode
|
|
9679
|
+
* calls this alongside a real pane send, and a POST here would deliver the
|
|
9680
|
+
* message twice.
|
|
9681
|
+
*/
|
|
9682
|
+
async probe(taskId) {
|
|
9683
|
+
const port = this.portFor(taskId);
|
|
9684
|
+
if (port == null)
|
|
9685
|
+
return { status: "unsupported" };
|
|
9686
|
+
const sessionId = this.sessionFor?.(taskId) ?? await this.resolveSession(taskId, port);
|
|
9687
|
+
return sessionId ? { status: "reachable", via: this.name } : { status: "gone" };
|
|
9688
|
+
}
|
|
9689
|
+
// ── private ───────────────────────────────────────────────────────────────
|
|
9690
|
+
/**
|
|
9691
|
+
* Resolve (and cache) the crew's session id.
|
|
9692
|
+
*
|
|
9693
|
+
* opencode 1.18.18 is mid-migration from /session/* to /api/session/*, so both
|
|
9694
|
+
* are tried. This is a capability probe, NOT a version comparison — the honest
|
|
9695
|
+
* check is "does this route answer", and neither path is a promised-stable
|
|
9696
|
+
* contract. Re-run the smoke suite when opencode is upgraded.
|
|
9697
|
+
*/
|
|
9698
|
+
async resolveSession(taskId, port) {
|
|
9699
|
+
const cached = this.sessionByTask.get(taskId);
|
|
9700
|
+
if (cached)
|
|
9701
|
+
return cached;
|
|
9702
|
+
for (const path23 of ["/session?", "/api/session?"]) {
|
|
9703
|
+
let res;
|
|
9704
|
+
try {
|
|
9705
|
+
res = await this.request(`http://127.0.0.1:${port}${path23}`, { method: "GET" });
|
|
9706
|
+
} catch {
|
|
9707
|
+
return void 0;
|
|
9708
|
+
}
|
|
9709
|
+
if (!res.ok)
|
|
9710
|
+
continue;
|
|
9711
|
+
let sessions;
|
|
9712
|
+
try {
|
|
9713
|
+
sessions = await res.json();
|
|
9714
|
+
} catch {
|
|
9715
|
+
continue;
|
|
9716
|
+
}
|
|
9717
|
+
if (!Array.isArray(sessions) || sessions.length === 0)
|
|
9718
|
+
continue;
|
|
9719
|
+
const newest = sessions.reduce((a, b) => (b.time?.updated ?? 0) > (a.time?.updated ?? 0) ? b : a);
|
|
9720
|
+
if (!newest?.id)
|
|
9721
|
+
continue;
|
|
9722
|
+
this.sessionByTask.set(taskId, newest.id);
|
|
9723
|
+
return newest.id;
|
|
9724
|
+
}
|
|
9725
|
+
return void 0;
|
|
9726
|
+
}
|
|
9727
|
+
async request(url, init) {
|
|
9728
|
+
const ac = new AbortController();
|
|
9729
|
+
const t = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
9730
|
+
try {
|
|
9731
|
+
return await this.fetchImpl(url, { ...init, signal: ac.signal });
|
|
9732
|
+
} finally {
|
|
9733
|
+
clearTimeout(t);
|
|
9734
|
+
}
|
|
9735
|
+
}
|
|
9736
|
+
};
|
|
9737
|
+
|
|
8560
9738
|
// packages/agents/dist/opencode/fact-adapter.js
|
|
8561
9739
|
function createOpencodeFactAdapter(deps) {
|
|
8562
9740
|
return {
|
|
@@ -8589,6 +9767,9 @@ function createOpencodeFactAdapter(deps) {
|
|
|
8589
9767
|
}
|
|
8590
9768
|
if (type === "permission.replied")
|
|
8591
9769
|
return [{ kind: "activity" }];
|
|
9770
|
+
if (type === "server.connected" || type === "server.heartbeat") {
|
|
9771
|
+
return [{ kind: "activity" }];
|
|
9772
|
+
}
|
|
8592
9773
|
return [{ kind: "unknown", name: type }];
|
|
8593
9774
|
}
|
|
8594
9775
|
};
|
|
@@ -8599,7 +9780,8 @@ init_dist();
|
|
|
8599
9780
|
init_dist();
|
|
8600
9781
|
init_dist();
|
|
8601
9782
|
init_dist2();
|
|
8602
|
-
|
|
9783
|
+
init_dist2();
|
|
9784
|
+
import { execFile as execFile2, execFileSync as execFileSync6 } from "child_process";
|
|
8603
9785
|
var CMUX_TIMEOUT = 15e3;
|
|
8604
9786
|
var CmuxTimeoutError = class extends Error {
|
|
8605
9787
|
constructor(cmd) {
|
|
@@ -9195,9 +10377,9 @@ var NotifierRegistry = class {
|
|
|
9195
10377
|
};
|
|
9196
10378
|
|
|
9197
10379
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
9198
|
-
import
|
|
10380
|
+
import fs17 from "fs/promises";
|
|
9199
10381
|
import { existsSync as existsSync11 } from "fs";
|
|
9200
|
-
import
|
|
10382
|
+
import path21 from "path";
|
|
9201
10383
|
|
|
9202
10384
|
// packages/workspaces/dist/workspaces/registry.js
|
|
9203
10385
|
init_dist();
|
|
@@ -9350,16 +10532,16 @@ var CmuxEventsBridge = class {
|
|
|
9350
10532
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
9351
10533
|
init_dist2();
|
|
9352
10534
|
init_dist();
|
|
9353
|
-
import { readdirSync as
|
|
9354
|
-
import { join as
|
|
9355
|
-
import { homedir as
|
|
10535
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync15 } from "fs";
|
|
10536
|
+
import { join as join22 } from "path";
|
|
10537
|
+
import { homedir as homedir11 } from "os";
|
|
9356
10538
|
|
|
9357
10539
|
// packages/workspaces/dist/cmux-daemon/store-fingerprint.js
|
|
9358
10540
|
init_dist();
|
|
9359
10541
|
init_dist2();
|
|
9360
|
-
import { execFileSync as
|
|
10542
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
9361
10543
|
function defaultExecPs(pid) {
|
|
9362
|
-
return
|
|
10544
|
+
return execFileSync7("ps", ["-o", "command=", "-p", String(pid)], {
|
|
9363
10545
|
encoding: "utf-8",
|
|
9364
10546
|
stdio: ["ignore", "pipe", "ignore"]
|
|
9365
10547
|
});
|
|
@@ -9449,15 +10631,15 @@ function readLivenessSnapshot(files, readFile7, projects, argvRecovery = {}) {
|
|
|
9449
10631
|
|
|
9450
10632
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
9451
10633
|
async function readCmuxLiveness() {
|
|
9452
|
-
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
10634
|
+
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join22(homedir11(), ".cmuxterm");
|
|
9453
10635
|
const projects = loadConfig().projects;
|
|
9454
10636
|
let files;
|
|
9455
10637
|
try {
|
|
9456
|
-
files =
|
|
10638
|
+
files = readdirSync7(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
9457
10639
|
} catch (e) {
|
|
9458
10640
|
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
9459
10641
|
}
|
|
9460
|
-
return readLivenessSnapshot(files, (f) =>
|
|
10642
|
+
return readLivenessSnapshot(files, (f) => readFileSync15(join22(dir, f), "utf-8"), projects);
|
|
9461
10643
|
}
|
|
9462
10644
|
var DaemonCmux = class {
|
|
9463
10645
|
driver;
|
|
@@ -9520,13 +10702,15 @@ var DaemonCmux = class {
|
|
|
9520
10702
|
};
|
|
9521
10703
|
|
|
9522
10704
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
9523
|
-
import { join as
|
|
9524
|
-
import { homedir as
|
|
9525
|
-
import { watch, readdirSync as
|
|
10705
|
+
import { join as join23 } from "path";
|
|
10706
|
+
import { homedir as homedir12 } from "os";
|
|
10707
|
+
import { watch, readdirSync as readdirSync8, readFileSync as readFileSync16, existsSync as existsSync12 } from "fs";
|
|
9526
10708
|
var CmuxStoreSource = class {
|
|
9527
10709
|
name = "cmux-store";
|
|
9528
10710
|
stateDir;
|
|
9529
10711
|
debounceMs;
|
|
10712
|
+
lockRetryMs;
|
|
10713
|
+
maxLockRetries;
|
|
9530
10714
|
isPidAlive;
|
|
9531
10715
|
listFiles;
|
|
9532
10716
|
readFile;
|
|
@@ -9538,13 +10722,19 @@ var CmuxStoreSource = class {
|
|
|
9538
10722
|
deps;
|
|
9539
10723
|
stopWatcher;
|
|
9540
10724
|
debounceTimer;
|
|
10725
|
+
/** filename → pending lock-retry timer (one per locked file). */
|
|
10726
|
+
lockRetryTimers = /* @__PURE__ */ new Map();
|
|
10727
|
+
/** filename → re-scan attempts used in the current lock episode. */
|
|
10728
|
+
lockRetryCounts = /* @__PURE__ */ new Map();
|
|
9541
10729
|
/** taskId → last reported snapshot (for snapshot() liveness floor). */
|
|
9542
10730
|
cache = /* @__PURE__ */ new Map();
|
|
9543
10731
|
active = false;
|
|
9544
10732
|
lastError = null;
|
|
9545
10733
|
constructor(opts = {}) {
|
|
9546
|
-
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
10734
|
+
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join23(homedir12(), ".cmuxterm");
|
|
9547
10735
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
10736
|
+
this.lockRetryMs = opts.lockRetryMs ?? 50;
|
|
10737
|
+
this.maxLockRetries = opts.maxLockRetries ?? 3;
|
|
9548
10738
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
9549
10739
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
9550
10740
|
this.readFile = opts.readFile ?? defaultReadFile;
|
|
@@ -9572,6 +10762,11 @@ var CmuxStoreSource = class {
|
|
|
9572
10762
|
this.cancelTimer(this.debounceTimer);
|
|
9573
10763
|
this.debounceTimer = void 0;
|
|
9574
10764
|
}
|
|
10765
|
+
for (const timer of this.lockRetryTimers.values()) {
|
|
10766
|
+
this.cancelTimer(timer);
|
|
10767
|
+
}
|
|
10768
|
+
this.lockRetryTimers.clear();
|
|
10769
|
+
this.lockRetryCounts.clear();
|
|
9575
10770
|
this.stopWatcher?.();
|
|
9576
10771
|
this.stopWatcher = void 0;
|
|
9577
10772
|
this.deps = void 0;
|
|
@@ -9605,12 +10800,13 @@ var CmuxStoreSource = class {
|
|
|
9605
10800
|
}
|
|
9606
10801
|
scanFile(filename) {
|
|
9607
10802
|
const deps = this.deps;
|
|
9608
|
-
const filePath =
|
|
10803
|
+
const filePath = join23(this.stateDir, filename);
|
|
9609
10804
|
const lockPath = `${filePath}.lock`;
|
|
9610
10805
|
if (this.fileExists(lockPath)) {
|
|
9611
|
-
this.
|
|
10806
|
+
this.scheduleLockRetry(filename);
|
|
9612
10807
|
return;
|
|
9613
10808
|
}
|
|
10809
|
+
this.clearLockRetry(filename);
|
|
9614
10810
|
const raw = this.readFile(filePath);
|
|
9615
10811
|
if (!raw)
|
|
9616
10812
|
return;
|
|
@@ -9625,6 +10821,31 @@ var CmuxStoreSource = class {
|
|
|
9625
10821
|
this.processSession(session, deps);
|
|
9626
10822
|
}
|
|
9627
10823
|
}
|
|
10824
|
+
/** Bounded, one-timer-per-file retry for a file cmux currently has locked. */
|
|
10825
|
+
scheduleLockRetry(filename) {
|
|
10826
|
+
if (this.lockRetryTimers.has(filename))
|
|
10827
|
+
return;
|
|
10828
|
+
const attempts = this.lockRetryCounts.get(filename) ?? 0;
|
|
10829
|
+
if (attempts >= this.maxLockRetries)
|
|
10830
|
+
return;
|
|
10831
|
+
if (attempts === 0)
|
|
10832
|
+
this.log(`cmux-store: skipping ${filename} (locked)`);
|
|
10833
|
+
this.lockRetryCounts.set(filename, attempts + 1);
|
|
10834
|
+
const timer = this.scheduleTimer(() => {
|
|
10835
|
+
this.lockRetryTimers.delete(filename);
|
|
10836
|
+
this.scanFile(filename);
|
|
10837
|
+
}, this.lockRetryMs);
|
|
10838
|
+
this.lockRetryTimers.set(filename, timer);
|
|
10839
|
+
}
|
|
10840
|
+
/** A successful (unlocked) scan ends the lock episode. */
|
|
10841
|
+
clearLockRetry(filename) {
|
|
10842
|
+
const timer = this.lockRetryTimers.get(filename);
|
|
10843
|
+
if (timer !== void 0) {
|
|
10844
|
+
this.cancelTimer(timer);
|
|
10845
|
+
this.lockRetryTimers.delete(filename);
|
|
10846
|
+
}
|
|
10847
|
+
this.lockRetryCounts.delete(filename);
|
|
10848
|
+
}
|
|
9628
10849
|
processSession(session, deps) {
|
|
9629
10850
|
if (!session.sessionId || !session.cwd || typeof session.pid !== "number")
|
|
9630
10851
|
return;
|
|
@@ -9671,14 +10892,14 @@ function defaultIsPidAlive2(pid) {
|
|
|
9671
10892
|
}
|
|
9672
10893
|
function defaultListFiles(dir) {
|
|
9673
10894
|
try {
|
|
9674
|
-
return
|
|
10895
|
+
return readdirSync8(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
9675
10896
|
} catch {
|
|
9676
10897
|
return [];
|
|
9677
10898
|
}
|
|
9678
10899
|
}
|
|
9679
|
-
function defaultReadFile(
|
|
10900
|
+
function defaultReadFile(path23) {
|
|
9680
10901
|
try {
|
|
9681
|
-
return
|
|
10902
|
+
return readFileSync16(path23, "utf-8");
|
|
9682
10903
|
} catch {
|
|
9683
10904
|
return void 0;
|
|
9684
10905
|
}
|
|
@@ -9693,9 +10914,9 @@ function defaultWatchDir(dir, cb) {
|
|
|
9693
10914
|
}
|
|
9694
10915
|
|
|
9695
10916
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
9696
|
-
import { join as
|
|
9697
|
-
import { homedir as
|
|
9698
|
-
import { mkdirSync as
|
|
10917
|
+
import { join as join24 } from "path";
|
|
10918
|
+
import { homedir as homedir13 } from "os";
|
|
10919
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
|
|
9699
10920
|
var CLAUDE_HOOK_EVENTS = [
|
|
9700
10921
|
["SessionStart", "session-start"],
|
|
9701
10922
|
["UserPromptSubmit", "prompt-submit"],
|
|
@@ -9703,11 +10924,17 @@ var CLAUDE_HOOK_EVENTS = [
|
|
|
9703
10924
|
["Stop", "stop"],
|
|
9704
10925
|
["Notification", "notification"],
|
|
9705
10926
|
["PreToolUse", "ask-question", "AskUserQuestion"],
|
|
9706
|
-
["SessionEnd", "session-end"]
|
|
10927
|
+
["SessionEnd", "session-end"],
|
|
10928
|
+
// #760: fires ~6s before the matching Notification, carrying tool_name +
|
|
10929
|
+
// tool_input directly — a strictly better task.blocked source.
|
|
10930
|
+
["PermissionRequest", "permission-request"],
|
|
10931
|
+
// #763: the turn died on an API error — today squadrant has no source for
|
|
10932
|
+
// this at all; without it the watchdog reports a plain stall (wrong story).
|
|
10933
|
+
["StopFailure", "stop-failure"]
|
|
9707
10934
|
];
|
|
9708
10935
|
var DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
9709
10936
|
function installClaudeHooks(opts = {}) {
|
|
9710
|
-
const settingsPath = opts.settingsPath ??
|
|
10937
|
+
const settingsPath = opts.settingsPath ?? join24(homedir13(), ".claude", "settings.json");
|
|
9711
10938
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
9712
10939
|
const readFile7 = opts.readFile ?? defaultReadFile2;
|
|
9713
10940
|
const writeFile6 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -9781,6 +11008,10 @@ function mapSubToLifecycle(sub) {
|
|
|
9781
11008
|
return "needsInput";
|
|
9782
11009
|
case "ask-question":
|
|
9783
11010
|
return "needsInput";
|
|
11011
|
+
case "permission-request":
|
|
11012
|
+
return "needsInput";
|
|
11013
|
+
case "stop-failure":
|
|
11014
|
+
return "idle";
|
|
9784
11015
|
case "session-end":
|
|
9785
11016
|
return "session-end";
|
|
9786
11017
|
default:
|
|
@@ -9875,16 +11106,16 @@ function extractDetail(sub, payload) {
|
|
|
9875
11106
|
}
|
|
9876
11107
|
return void 0;
|
|
9877
11108
|
}
|
|
9878
|
-
function defaultReadFile2(
|
|
11109
|
+
function defaultReadFile2(path23) {
|
|
9879
11110
|
try {
|
|
9880
|
-
return
|
|
11111
|
+
return readFileSync17(path23, "utf-8");
|
|
9881
11112
|
} catch {
|
|
9882
11113
|
return void 0;
|
|
9883
11114
|
}
|
|
9884
11115
|
}
|
|
9885
|
-
function defaultWriteFile(
|
|
9886
|
-
|
|
9887
|
-
|
|
11116
|
+
function defaultWriteFile(path23, content) {
|
|
11117
|
+
mkdirSync7(path23.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
11118
|
+
writeFileSync10(path23, content, "utf-8");
|
|
9888
11119
|
}
|
|
9889
11120
|
|
|
9890
11121
|
// packages/workspaces/dist/crew-pane.js
|
|
@@ -9964,9 +11195,9 @@ init_dist();
|
|
|
9964
11195
|
|
|
9965
11196
|
// packages/cli/src/lib/daemon-restart-broadcast.ts
|
|
9966
11197
|
init_dist();
|
|
9967
|
-
import
|
|
11198
|
+
import path22 from "path";
|
|
9968
11199
|
function statePath2(stateRoot) {
|
|
9969
|
-
return
|
|
11200
|
+
return path22.join(stateRoot, "daemon-restart-state.json");
|
|
9970
11201
|
}
|
|
9971
11202
|
function computeRestartSignature(version, buildMtimeMs) {
|
|
9972
11203
|
return `${version}::${buildMtimeMs}`;
|
|
@@ -10014,25 +11245,25 @@ async function maybeBroadcastDaemonRestart(opts) {
|
|
|
10014
11245
|
}
|
|
10015
11246
|
|
|
10016
11247
|
// packages/cli/src/lib/captain-channel-factory.ts
|
|
10017
|
-
import { createServer as
|
|
10018
|
-
import
|
|
10019
|
-
import { join as
|
|
11248
|
+
import { createServer as createServer4, connect as netConnect } from "net";
|
|
11249
|
+
import fs18 from "fs";
|
|
11250
|
+
import { join as join25 } from "path";
|
|
10020
11251
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
10021
11252
|
import chalk2 from "chalk";
|
|
10022
11253
|
init_dist2();
|
|
10023
11254
|
var shared;
|
|
10024
|
-
var registryEntryPath = () =>
|
|
11255
|
+
var registryEntryPath = () => join25(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
10025
11256
|
function unregisterSenderIdentity() {
|
|
10026
11257
|
try {
|
|
10027
|
-
|
|
11258
|
+
fs18.unlinkSync(registryEntryPath());
|
|
10028
11259
|
} catch {
|
|
10029
11260
|
}
|
|
10030
11261
|
}
|
|
10031
11262
|
function registerSenderIdentity(socketPath) {
|
|
10032
11263
|
try {
|
|
10033
11264
|
unregisterSenderIdentity();
|
|
10034
|
-
|
|
10035
|
-
|
|
11265
|
+
fs18.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
11266
|
+
fs18.writeFileSync(
|
|
10036
11267
|
registryEntryPath(),
|
|
10037
11268
|
JSON.stringify({
|
|
10038
11269
|
pid: process.pid,
|
|
@@ -10053,12 +11284,12 @@ async function sharedReceiptListener() {
|
|
|
10053
11284
|
const socketPath = `${CC_SOCKS_DIR}/squadrantd-${process.pid}.sock`;
|
|
10054
11285
|
const listener = new ClaudeReceiptListener({
|
|
10055
11286
|
socketPath,
|
|
10056
|
-
createServer: (h) =>
|
|
11287
|
+
createServer: (h) => createServer4(h),
|
|
10057
11288
|
// A UDS path is not cleaned up when a process is killed, so our own leftover
|
|
10058
11289
|
// must never be the reason we refuse to start.
|
|
10059
11290
|
unlinkStale: (p) => {
|
|
10060
11291
|
try {
|
|
10061
|
-
|
|
11292
|
+
fs18.unlinkSync(p);
|
|
10062
11293
|
} catch {
|
|
10063
11294
|
}
|
|
10064
11295
|
},
|
|
@@ -10083,15 +11314,28 @@ async function buildCaptainChannel() {
|
|
|
10083
11314
|
// in /tmp/x registers cwd "/private/tmp/x" on macOS, so cwd matching silently
|
|
10084
11315
|
// missed and every captain ping reported accepted-unconfirmed.
|
|
10085
11316
|
statusFor: (taskId) => readClaudeStatusBySocketPath(captainSocketPath(taskId)),
|
|
10086
|
-
wire: (p, e) => writeLine(p, e, { connect: (
|
|
11317
|
+
wire: (p, e) => writeLine(p, e, { connect: (path23) => netConnect(path23) }),
|
|
10087
11318
|
receipts,
|
|
10088
11319
|
newMsgId: () => randomUUID5(),
|
|
10089
11320
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
10090
11321
|
log: (m) => console.error(chalk2.dim(m))
|
|
10091
11322
|
});
|
|
10092
11323
|
}
|
|
10093
|
-
async function
|
|
10094
|
-
const
|
|
11324
|
+
async function buildCaptainChannels(opts) {
|
|
11325
|
+
const claude = await buildCaptainChannel();
|
|
11326
|
+
const opencode = new OpencodeHttpChannel({
|
|
11327
|
+
portFor: (project) => readCaptainAddress(opts.stateRoot, project)?.port,
|
|
11328
|
+
sessionFor: (project) => readCaptainAddress(opts.stateRoot, project)?.sessionId
|
|
11329
|
+
});
|
|
11330
|
+
return {
|
|
11331
|
+
channels: { claude, opencode },
|
|
11332
|
+
agentFor: (project) => resolveCaptainAgent(opts.stateRoot, project, opts.configAgent)
|
|
11333
|
+
};
|
|
11334
|
+
}
|
|
11335
|
+
function resolveCaptainAgent(stateRoot, project, configAgent) {
|
|
11336
|
+
return readCaptainAddress(stateRoot, project)?.agent ?? configAgent;
|
|
11337
|
+
}
|
|
11338
|
+
async function retryForever(build, opts = {}) {
|
|
10095
11339
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => {
|
|
10096
11340
|
const t = setTimeout(r, ms);
|
|
10097
11341
|
t.unref?.();
|
|
@@ -10110,14 +11354,20 @@ async function buildCaptainChannelWithRetry(opts = {}) {
|
|
|
10110
11354
|
}
|
|
10111
11355
|
}
|
|
10112
11356
|
}
|
|
11357
|
+
function buildCaptainChannelsWithRetry(opts) {
|
|
11358
|
+
return retryForever(
|
|
11359
|
+
() => buildCaptainChannels({ stateRoot: opts.stateRoot, configAgent: opts.configAgent }),
|
|
11360
|
+
opts
|
|
11361
|
+
);
|
|
11362
|
+
}
|
|
10113
11363
|
|
|
10114
11364
|
// packages/cli/src/squadrantd.ts
|
|
10115
11365
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
10116
|
-
var CLI_BIN =
|
|
11366
|
+
var CLI_BIN = join26(dirname5(SELF_PATH2), "index.js");
|
|
10117
11367
|
function readPkgVersion() {
|
|
10118
11368
|
try {
|
|
10119
|
-
const pkgPath =
|
|
10120
|
-
return JSON.parse(
|
|
11369
|
+
const pkgPath = join26(dirname5(SELF_PATH2), "..", "package.json");
|
|
11370
|
+
return JSON.parse(readFileSync18(pkgPath, "utf-8")).version ?? "unknown";
|
|
10121
11371
|
} catch {
|
|
10122
11372
|
return "unknown";
|
|
10123
11373
|
}
|
|
@@ -10139,7 +11389,7 @@ function buildTelegramBridge(cfg, stateRoot, log, deliverInbound) {
|
|
|
10139
11389
|
return createTelegramBridge({
|
|
10140
11390
|
cfg,
|
|
10141
11391
|
stateRoot,
|
|
10142
|
-
configRoot:
|
|
11392
|
+
configRoot: dirname5(stateRoot),
|
|
10143
11393
|
client,
|
|
10144
11394
|
appendCaptainMessage,
|
|
10145
11395
|
log,
|
|
@@ -10264,7 +11514,7 @@ function startSquadrantd(opts = {}) {
|
|
|
10264
11514
|
(r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
|
|
10265
11515
|
);
|
|
10266
11516
|
},
|
|
10267
|
-
cursorFile:
|
|
11517
|
+
cursorFile: join26(stateRoot, "cmux-events.seq"),
|
|
10268
11518
|
log
|
|
10269
11519
|
});
|
|
10270
11520
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
@@ -10291,14 +11541,23 @@ function startSquadrantd(opts = {}) {
|
|
|
10291
11541
|
log
|
|
10292
11542
|
}))
|
|
10293
11543
|
) : void 0);
|
|
11544
|
+
const cfg = loadConfig();
|
|
11545
|
+
const routerCfg = cfg.defaults.router;
|
|
11546
|
+
ctx.routerService = opts.routerService ?? (shouldBuildRouterService(routerCfg, !!process.env.VITEST) && routerCfg ? createRouterService(routerCfg, Object.keys(cfg.projects), { log }) : void 0);
|
|
10294
11547
|
if (opts.notifyFault) ctx.notifyFault = opts.notifyFault;
|
|
10295
11548
|
else if (!process.env.VITEST) ctx.notifyFault = buildNotifyFault(log);
|
|
10296
11549
|
ctx.daemonCmux = opts.daemonCmux ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();
|
|
10297
11550
|
if (!process.env.VITEST) {
|
|
10298
11551
|
ctx.captainChannelMode = () => loadConfig().defaults.captainChannel ?? "off";
|
|
10299
11552
|
if (ctx.captainChannelMode() !== "off") {
|
|
10300
|
-
void
|
|
10301
|
-
|
|
11553
|
+
void buildCaptainChannelsWithRetry({
|
|
11554
|
+
stateRoot: join26(homedir14(), ".config", "squadrant", "state"),
|
|
11555
|
+
configAgent: loadConfig().defaults.roles?.captain?.agent,
|
|
11556
|
+
log
|
|
11557
|
+
}).then(({ channels, agentFor }) => {
|
|
11558
|
+
ctx.captainChannels = channels;
|
|
11559
|
+
ctx.captainAgentFor = agentFor;
|
|
11560
|
+
ctx.captainChannel = channels.claude;
|
|
10302
11561
|
}).catch((e) => log(`captain-channel: unexpected retry-loop error: ${e.message}`));
|
|
10303
11562
|
}
|
|
10304
11563
|
}
|
|
@@ -10407,6 +11666,11 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
10407
11666
|
} catch (e) {
|
|
10408
11667
|
log(`native hook install failed: ${e.message}`);
|
|
10409
11668
|
}
|
|
11669
|
+
try {
|
|
11670
|
+
syncShippedOpencodeSkills({ pkgRoot: join26(dirname5(SELF_PATH2), "..") });
|
|
11671
|
+
} catch (e) {
|
|
11672
|
+
log(`opencode skills sync failed: ${e.message}`);
|
|
11673
|
+
}
|
|
10410
11674
|
const hookPrevSnaps = /* @__PURE__ */ new Map();
|
|
10411
11675
|
const hookDeps = {
|
|
10412
11676
|
resolve: (hint) => {
|
|
@@ -10541,7 +11805,7 @@ function logCrashMarker(kind, err) {
|
|
|
10541
11805
|
const message = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
10542
11806
|
process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${kind} pid=${process.pid} error=${message}
|
|
10543
11807
|
`);
|
|
10544
|
-
const stateRoot =
|
|
11808
|
+
const stateRoot = join26(homedir14(), ".config", "squadrant", "state");
|
|
10545
11809
|
writeExitMarker(stateRoot, {
|
|
10546
11810
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10547
11811
|
pid: process.pid,
|
|
@@ -10554,7 +11818,7 @@ function logCrashMarker(kind, err) {
|
|
|
10554
11818
|
`));
|
|
10555
11819
|
}
|
|
10556
11820
|
function isMonorepoCheckout(scriptPath, dirExists = existsSync13) {
|
|
10557
|
-
return dirExists(
|
|
11821
|
+
return dirExists(join26(dirname5(resolve3(scriptPath)), "..", "packages"));
|
|
10558
11822
|
}
|
|
10559
11823
|
function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
10560
11824
|
try {
|
|
@@ -10563,7 +11827,7 @@ function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
|
10563
11827
|
return void 0;
|
|
10564
11828
|
}
|
|
10565
11829
|
}) {
|
|
10566
|
-
const dotGit =
|
|
11830
|
+
const dotGit = join26(dirname5(resolve3(scriptPath)), "..", ".git");
|
|
10567
11831
|
return statFile(dotGit)?.isFile === true;
|
|
10568
11832
|
}
|
|
10569
11833
|
if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
|