squadrant 0.19.5 → 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 +6326 -4589
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +1547 -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();
|
|
@@ -1234,7 +1253,7 @@ function createDaemon(deps) {
|
|
|
1234
1253
|
async sweep() {
|
|
1235
1254
|
const t = now();
|
|
1236
1255
|
const surfaceAlive = deps.isSurfaceAlive ?? (async () => "unknown");
|
|
1237
|
-
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)));
|
|
1238
1257
|
for (const project of projects) {
|
|
1239
1258
|
const terminal = store.list(project).filter((r) => TERMINAL_STATES.has(r.state)).sort((a, b) => b.lastHeartbeat - a.lastHeartbeat);
|
|
1240
1259
|
for (const r of terminal.slice(TERMINAL_RECORD_KEEP_PER_PROJECT)) {
|
|
@@ -2029,6 +2048,12 @@ function safeSegment(kind, s) {
|
|
|
2029
2048
|
}
|
|
2030
2049
|
return s;
|
|
2031
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
|
+
}
|
|
2032
2057
|
function createStore(root) {
|
|
2033
2058
|
const rootResolved = resolve(root);
|
|
2034
2059
|
const assertUnderRoot = (target) => {
|
|
@@ -2076,7 +2101,7 @@ function createStore(root) {
|
|
|
2076
2101
|
} catch {
|
|
2077
2102
|
return void 0;
|
|
2078
2103
|
}
|
|
2079
|
-
}).filter(
|
|
2104
|
+
}).filter(isTaskRecord);
|
|
2080
2105
|
},
|
|
2081
2106
|
listAll() {
|
|
2082
2107
|
if (!existsSync6(root))
|
|
@@ -2364,10 +2389,10 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
|
|
|
2364
2389
|
isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
|
|
2365
2390
|
};
|
|
2366
2391
|
}
|
|
2367
|
-
function sanitizePathForPlist(
|
|
2392
|
+
function sanitizePathForPlist(path23) {
|
|
2368
2393
|
const seen = /* @__PURE__ */ new Set();
|
|
2369
2394
|
const stable = [];
|
|
2370
|
-
for (const p of
|
|
2395
|
+
for (const p of path23.split(":")) {
|
|
2371
2396
|
if (!p)
|
|
2372
2397
|
continue;
|
|
2373
2398
|
if (p.includes("/.claude/plugins/"))
|
|
@@ -2569,6 +2594,9 @@ function isOperatorInitiatedCommand(topLevelArg) {
|
|
|
2569
2594
|
function isReadOnlyCrewCommand(argv) {
|
|
2570
2595
|
return argv[2] === "crew" && argv[3] !== void 0 && READ_ONLY_CREW_SUBCOMMANDS.has(argv[3]);
|
|
2571
2596
|
}
|
|
2597
|
+
function isReadOnlyTopLevelCommand(argv) {
|
|
2598
|
+
return argv[2] !== void 0 && READ_ONLY_TOP_LEVEL_COMMANDS.has(argv[2]);
|
|
2599
|
+
}
|
|
2572
2600
|
function ensureDaemon(nodeBin = process.execPath, opts = {}) {
|
|
2573
2601
|
if (restartInFlight)
|
|
2574
2602
|
return;
|
|
@@ -2636,7 +2664,7 @@ function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
|
|
|
2636
2664
|
releaseDaemonLock();
|
|
2637
2665
|
}
|
|
2638
2666
|
}
|
|
2639
|
-
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;
|
|
2640
2668
|
var init_launchd = __esm({
|
|
2641
2669
|
"packages/core/dist/launchd.js"() {
|
|
2642
2670
|
LABEL = "com.squadrant.daemon";
|
|
@@ -2644,6 +2672,7 @@ var init_launchd = __esm({
|
|
|
2644
2672
|
restartInFlight = false;
|
|
2645
2673
|
OPERATOR_INITIATED_COMMANDS = /* @__PURE__ */ new Set(["launch", "init"]);
|
|
2646
2674
|
READ_ONLY_CREW_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "read", "tasks"]);
|
|
2675
|
+
READ_ONLY_TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set(["sessions", "whoami"]);
|
|
2647
2676
|
}
|
|
2648
2677
|
});
|
|
2649
2678
|
|
|
@@ -2923,6 +2952,7 @@ function buildContext(opts) {
|
|
|
2923
2952
|
opencodeBridge: null,
|
|
2924
2953
|
cmuxEventsBridge: null,
|
|
2925
2954
|
telegramBridge: void 0,
|
|
2955
|
+
routerService: void 0,
|
|
2926
2956
|
notifyFault: opts.notifyFault ?? (() => {
|
|
2927
2957
|
}),
|
|
2928
2958
|
lifecycleSources: opts.lifecycleSources ?? [],
|
|
@@ -3408,6 +3438,72 @@ var init_control_channel = __esm({
|
|
|
3408
3438
|
}
|
|
3409
3439
|
});
|
|
3410
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
|
+
|
|
3411
3507
|
// packages/core/dist/crew-routing.js
|
|
3412
3508
|
function resolveCrewRoute(taskText, config) {
|
|
3413
3509
|
const rules = config.defaults.crewRouting?.rules;
|
|
@@ -3419,6 +3515,7 @@ function resolveCrewRoute(taskText, config) {
|
|
|
3419
3515
|
return {
|
|
3420
3516
|
agent: rule.agent,
|
|
3421
3517
|
...rule.model !== void 0 ? { model: rule.model } : {},
|
|
3518
|
+
...rule.backend !== void 0 ? { backend: rule.backend } : {},
|
|
3422
3519
|
tier: rule.tier,
|
|
3423
3520
|
matchedRule: rule.match
|
|
3424
3521
|
};
|
|
@@ -3556,17 +3653,28 @@ async function pollFirstTurnConfirmedAt(getTaskRecord, project, id, scrapeSettle
|
|
|
3556
3653
|
await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
|
|
3557
3654
|
}
|
|
3558
3655
|
}
|
|
3559
|
-
function firstTrueOrBothFalse(a, b) {
|
|
3656
|
+
function firstTrueOrBothFalse(a, b, deadlineMs) {
|
|
3560
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);
|
|
3561
3667
|
let settledFalseCount = 0;
|
|
3562
3668
|
const onSettle = (ok2) => {
|
|
3669
|
+
if (done)
|
|
3670
|
+
return;
|
|
3563
3671
|
if (ok2) {
|
|
3564
|
-
|
|
3672
|
+
finish(true);
|
|
3565
3673
|
return;
|
|
3566
3674
|
}
|
|
3567
3675
|
settledFalseCount++;
|
|
3568
3676
|
if (settledFalseCount === 2)
|
|
3569
|
-
|
|
3677
|
+
finish(false);
|
|
3570
3678
|
};
|
|
3571
3679
|
a.then(onSettle, () => onSettle(false));
|
|
3572
3680
|
b.then(onSettle, () => onSettle(false));
|
|
@@ -3654,6 +3762,11 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3654
3762
|
if (!agent) {
|
|
3655
3763
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
3656
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 });
|
|
3657
3770
|
if (agentName === "codex") {
|
|
3658
3771
|
const codexRoleFile = path9.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
|
|
3659
3772
|
const roleInstructions = fs9.existsSync(codexRoleFile) ? fs9.readFileSync(codexRoleFile, "utf8") : void 0;
|
|
@@ -3676,12 +3789,19 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3676
3789
|
const interactive = agent.name === "claude" || agent.name === "opencode";
|
|
3677
3790
|
const crewRole = config.defaults.roles?.crew;
|
|
3678
3791
|
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
3679
|
-
const crewModel = input.model ?? route?.model ?? configModel;
|
|
3792
|
+
const crewModel = resolveRouterModel(input.model ?? route?.model ?? configModel, agent.name, config.defaults.router);
|
|
3680
3793
|
const crewThinking = input.thinking ?? config.defaults.roles?.crew?.thinking;
|
|
3681
3794
|
if (agentName !== "claude") {
|
|
3682
3795
|
deps.onModelResolved?.({ agentName, model: crewModel });
|
|
3683
3796
|
}
|
|
3684
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
|
+
}
|
|
3685
3805
|
ensureSocksDir();
|
|
3686
3806
|
const messagingSocketPath = path9.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
|
|
3687
3807
|
const rec = await deps.dispatchCrew({
|
|
@@ -3720,7 +3840,8 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3720
3840
|
const title2 = titleFor(input.project, name);
|
|
3721
3841
|
const pane2 = await deps.runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
3722
3842
|
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
3723
|
-
|
|
3843
|
+
const routerPrefix = Object.keys(routerEnv).length > 0 ? ` ${renderEnvAssignments(routerEnv)}` : "";
|
|
3844
|
+
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix}${routerPrefix} ${niceCrewCommand(cliCommand2)}`);
|
|
3724
3845
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
3725
3846
|
let claudeFirstTurn = firstTurnTask;
|
|
3726
3847
|
if (Buffer.byteLength(claudeFirstTurn, "utf8") > FIRST_TURN_INLINE_MAX_BYTES) {
|
|
@@ -3733,7 +3854,7 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3733
3854
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
3734
3855
|
const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
|
|
3735
3856
|
const cancelHookPoll = { stopped: false };
|
|
3736
|
-
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;
|
|
3737
3858
|
cancelHookPoll.stopped = true;
|
|
3738
3859
|
if (!delivered) {
|
|
3739
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.
|
|
@@ -4021,6 +4142,8 @@ var init_crew_spawn = __esm({
|
|
|
4021
4142
|
"packages/core/dist/crew-spawn.js"() {
|
|
4022
4143
|
init_control_channel();
|
|
4023
4144
|
init_dist();
|
|
4145
|
+
init_router_resolution();
|
|
4146
|
+
init_env();
|
|
4024
4147
|
init_crew_routing();
|
|
4025
4148
|
init_crew_protocol();
|
|
4026
4149
|
init_crew_lifecycle();
|
|
@@ -4071,6 +4194,164 @@ var init_captain_channel = __esm({
|
|
|
4071
4194
|
}
|
|
4072
4195
|
});
|
|
4073
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
|
+
|
|
4074
4355
|
// packages/core/dist/daemon/delivery-loop.js
|
|
4075
4356
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
4076
4357
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
@@ -4196,7 +4477,12 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4196
4477
|
const cmux2 = daemonCmux;
|
|
4197
4478
|
const cfg = loadConfig();
|
|
4198
4479
|
const deliveries = /* @__PURE__ */ new Map();
|
|
4199
|
-
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
|
+
};
|
|
4200
4486
|
const lastDeferred = /* @__PURE__ */ new Map();
|
|
4201
4487
|
const inFlightDelivery = () => {
|
|
4202
4488
|
let worst = null;
|
|
@@ -4282,20 +4568,55 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4282
4568
|
deliverEntry = { ...entry, message: `${prefix}${entry.message}` };
|
|
4283
4569
|
}
|
|
4284
4570
|
const result = await d.deliver(deliverEntry, async (text, sendOpts) => {
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
mode,
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
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");
|
|
4299
4620
|
}
|
|
4300
4621
|
try {
|
|
4301
4622
|
return await cmux2.send(surface, text, sendOpts);
|
|
@@ -4319,20 +4640,23 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4319
4640
|
lastDeferred.delete(project);
|
|
4320
4641
|
projectBackoff.delete(project);
|
|
4321
4642
|
} else {
|
|
4322
|
-
const { maxDeferCount
|
|
4643
|
+
const { maxDeferCount } = d.stats();
|
|
4644
|
+
const stuckNow = deliveryStats(project)?.stuck ?? false;
|
|
4323
4645
|
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
4324
4646
|
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
4325
4647
|
}
|
|
4326
4648
|
lastDeferred.set(project, { seq: entry.seq, deferCount: maxDeferCount });
|
|
4327
|
-
if (
|
|
4649
|
+
if (stuckNow) {
|
|
4328
4650
|
const streak = (projectBackoff.get(project)?.streak ?? 0) + 1;
|
|
4329
|
-
const
|
|
4651
|
+
const cap = d.stats().reason === "no-channel" ? 3e5 : 6e4;
|
|
4652
|
+
const backoffMs = Math.min(cap, 1e3 * 2 ** streak);
|
|
4330
4653
|
projectBackoff.set(project, { nextAttemptAt: Date.now() + backoffMs, streak });
|
|
4331
4654
|
}
|
|
4332
4655
|
break;
|
|
4333
4656
|
}
|
|
4334
4657
|
}
|
|
4335
|
-
const
|
|
4658
|
+
const stats = d.stats();
|
|
4659
|
+
const stuck = stats.stuck || stats.reason === "no-channel";
|
|
4336
4660
|
if (stuck && !stuckNotified.has(project)) {
|
|
4337
4661
|
stuckNotified.add(project);
|
|
4338
4662
|
const { maxDeferCount, reason } = d.stats();
|
|
@@ -4376,6 +4700,8 @@ var init_delivery_loop = __esm({
|
|
|
4376
4700
|
init_down_alert();
|
|
4377
4701
|
init_liveness2();
|
|
4378
4702
|
init_captain_channel();
|
|
4703
|
+
init_captain_record();
|
|
4704
|
+
init_opencode_session();
|
|
4379
4705
|
CURSOR_SUBSCRIBER = "captain";
|
|
4380
4706
|
TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
4381
4707
|
STUCK_ALERT_TEXT = {
|
|
@@ -4383,6 +4709,7 @@ var init_delivery_loop = __esm({
|
|
|
4383
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.`,
|
|
4384
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.`,
|
|
4385
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)`,
|
|
4386
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.`,
|
|
4387
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.`
|
|
4388
4715
|
};
|
|
@@ -4410,6 +4737,32 @@ var init_gates = __esm({
|
|
|
4410
4737
|
}
|
|
4411
4738
|
});
|
|
4412
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
|
+
|
|
4413
4766
|
// packages/core/dist/daemon/server.js
|
|
4414
4767
|
function createServer2(ctx, handlers) {
|
|
4415
4768
|
const { store, log, attachConns } = ctx;
|
|
@@ -4435,6 +4788,13 @@ function createServer2(ctx, handlers) {
|
|
|
4435
4788
|
if (msg.kind === "event") {
|
|
4436
4789
|
return ctx.d.handle(msg);
|
|
4437
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
|
+
}
|
|
4438
4798
|
return ctx.d.handle(msg);
|
|
4439
4799
|
},
|
|
4440
4800
|
onAttach: (conn, frame) => {
|
|
@@ -4470,18 +4830,19 @@ function createServer2(ctx, handlers) {
|
|
|
4470
4830
|
var init_server = __esm({
|
|
4471
4831
|
"packages/core/dist/daemon/server.js"() {
|
|
4472
4832
|
init_protocol();
|
|
4833
|
+
init_credentials();
|
|
4473
4834
|
}
|
|
4474
4835
|
});
|
|
4475
4836
|
|
|
4476
4837
|
// packages/core/dist/daemon/exit-marker.js
|
|
4477
|
-
import { readFileSync as
|
|
4478
|
-
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";
|
|
4479
4840
|
function exitMarkerPath(stateRoot) {
|
|
4480
|
-
return
|
|
4841
|
+
return join12(stateRoot, "exit-marker.json");
|
|
4481
4842
|
}
|
|
4482
4843
|
function writeExitMarker(stateRoot, marker, log) {
|
|
4483
4844
|
try {
|
|
4484
|
-
|
|
4845
|
+
writeFileSync9(exitMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4485
4846
|
} catch (e) {
|
|
4486
4847
|
log(`exit marker write failed: ${e.message}`);
|
|
4487
4848
|
}
|
|
@@ -4492,7 +4853,7 @@ function consumeExitMarker(stateRoot, now = Date.now) {
|
|
|
4492
4853
|
return { marker: null };
|
|
4493
4854
|
let marker = null;
|
|
4494
4855
|
try {
|
|
4495
|
-
marker = JSON.parse(
|
|
4856
|
+
marker = JSON.parse(readFileSync9(p, "utf-8"));
|
|
4496
4857
|
} catch {
|
|
4497
4858
|
marker = null;
|
|
4498
4859
|
}
|
|
@@ -4506,18 +4867,18 @@ function consumeExitMarker(stateRoot, now = Date.now) {
|
|
|
4506
4867
|
return { marker, gapMs };
|
|
4507
4868
|
}
|
|
4508
4869
|
function runningMarkerPath(stateRoot) {
|
|
4509
|
-
return
|
|
4870
|
+
return join12(stateRoot, "running-marker.json");
|
|
4510
4871
|
}
|
|
4511
4872
|
function writeRunningMarker(stateRoot, marker, log) {
|
|
4512
4873
|
try {
|
|
4513
|
-
|
|
4874
|
+
writeFileSync9(runningMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4514
4875
|
} catch (e) {
|
|
4515
4876
|
log(`running marker write failed: ${e.message}`);
|
|
4516
4877
|
}
|
|
4517
4878
|
}
|
|
4518
4879
|
function readRunningMarker(stateRoot) {
|
|
4519
4880
|
try {
|
|
4520
|
-
return JSON.parse(
|
|
4881
|
+
return JSON.parse(readFileSync9(runningMarkerPath(stateRoot), "utf-8"));
|
|
4521
4882
|
} catch {
|
|
4522
4883
|
return null;
|
|
4523
4884
|
}
|
|
@@ -4537,8 +4898,8 @@ var init_exit_marker = __esm({
|
|
|
4537
4898
|
|
|
4538
4899
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
4539
4900
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4540
|
-
import { join as
|
|
4541
|
-
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";
|
|
4542
4903
|
function distBuiltAt() {
|
|
4543
4904
|
try {
|
|
4544
4905
|
return statSync3(SELF_PATH).mtimeMs;
|
|
@@ -4546,10 +4907,10 @@ function distBuiltAt() {
|
|
|
4546
4907
|
return 0;
|
|
4547
4908
|
}
|
|
4548
4909
|
}
|
|
4549
|
-
function gatherLogStats(
|
|
4910
|
+
function gatherLogStats(path23, now, windowMs) {
|
|
4550
4911
|
let sizeBytes = 0;
|
|
4551
4912
|
try {
|
|
4552
|
-
sizeBytes = statSync3(
|
|
4913
|
+
sizeBytes = statSync3(path23).size;
|
|
4553
4914
|
} catch {
|
|
4554
4915
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
4555
4916
|
}
|
|
@@ -4560,7 +4921,7 @@ function gatherLogStats(path21, now, windowMs) {
|
|
|
4560
4921
|
const len = sizeBytes - start;
|
|
4561
4922
|
let text = "";
|
|
4562
4923
|
try {
|
|
4563
|
-
const fd = openSync2(
|
|
4924
|
+
const fd = openSync2(path23, "r");
|
|
4564
4925
|
try {
|
|
4565
4926
|
const buf = Buffer.alloc(len);
|
|
4566
4927
|
readSync(fd, buf, 0, len, start);
|
|
@@ -4591,7 +4952,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4591
4952
|
for (const r of store.list(project))
|
|
4592
4953
|
byState[r.state] = (byState[r.state] ?? 0) + 1;
|
|
4593
4954
|
let corruptCount = 0;
|
|
4594
|
-
const dir =
|
|
4955
|
+
const dir = join13(stateRoot, project);
|
|
4595
4956
|
try {
|
|
4596
4957
|
for (const n of readdirSync3(dir)) {
|
|
4597
4958
|
if (n.includes(".corrupt.")) {
|
|
@@ -4601,7 +4962,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4601
4962
|
if (!n.endsWith(".json"))
|
|
4602
4963
|
continue;
|
|
4603
4964
|
try {
|
|
4604
|
-
JSON.parse(
|
|
4965
|
+
JSON.parse(readFileSync10(join13(dir, n), "utf-8"));
|
|
4605
4966
|
} catch {
|
|
4606
4967
|
corruptCount++;
|
|
4607
4968
|
}
|
|
@@ -4616,7 +4977,7 @@ function gatherResults(resultsDir) {
|
|
|
4616
4977
|
try {
|
|
4617
4978
|
for (const n of readdirSync3(resultsDir)) {
|
|
4618
4979
|
try {
|
|
4619
|
-
const s = statSync3(
|
|
4980
|
+
const s = statSync3(join13(resultsDir, n));
|
|
4620
4981
|
if (s.isFile()) {
|
|
4621
4982
|
fileCount++;
|
|
4622
4983
|
totalBytes += s.size;
|
|
@@ -4636,7 +4997,7 @@ var init_snapshot_gather = __esm({
|
|
|
4636
4997
|
});
|
|
4637
4998
|
|
|
4638
4999
|
// packages/core/dist/daemon/start.js
|
|
4639
|
-
import { join as
|
|
5000
|
+
import { join as join14, dirname as dirname4 } from "path";
|
|
4640
5001
|
import { readdir } from "fs/promises";
|
|
4641
5002
|
function startDaemon(ctx, opts, pkgVersion) {
|
|
4642
5003
|
const { stateRoot, store, log, isPidAlive, resultsDir, taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills, broadcast, cancelPromotionsFor } = ctx;
|
|
@@ -4713,7 +5074,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4713
5074
|
return out;
|
|
4714
5075
|
}
|
|
4715
5076
|
async function gatherSnapshotInputs(now) {
|
|
4716
|
-
const logPath2 =
|
|
5077
|
+
const logPath2 = join14(dirname4(stateRoot), "squadrantd.log");
|
|
4717
5078
|
const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4718
5079
|
const projects = await Promise.all(tier2Projects.map(async (project) => {
|
|
4719
5080
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
|
|
@@ -4790,6 +5151,9 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4790
5151
|
log(`telegram bridge start failed: ${e.message}`);
|
|
4791
5152
|
}
|
|
4792
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
|
+
}
|
|
4793
5157
|
const autoConfigSafe = !!opts.runCmuxAutoConfig || !process.env.VITEST;
|
|
4794
5158
|
if (autoConfigSafe) {
|
|
4795
5159
|
try {
|
|
@@ -4875,7 +5239,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4875
5239
|
let rotationTimer;
|
|
4876
5240
|
let rotationTick;
|
|
4877
5241
|
if (rotationInterval > 0) {
|
|
4878
|
-
const inboxPath =
|
|
5242
|
+
const inboxPath = join14(stateRoot, "inbox");
|
|
4879
5243
|
rotationTick = async () => {
|
|
4880
5244
|
try {
|
|
4881
5245
|
let entries;
|
|
@@ -4899,7 +5263,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4899
5263
|
rotationTimer.unref?.();
|
|
4900
5264
|
}
|
|
4901
5265
|
return {
|
|
4902
|
-
stop(reason = "requested") {
|
|
5266
|
+
async stop(reason = "requested") {
|
|
4903
5267
|
const ppid = process.ppid;
|
|
4904
5268
|
const uptimeMs = Math.round(process.uptime() * 1e3);
|
|
4905
5269
|
const inFlight = inFlightDelivery();
|
|
@@ -4928,6 +5292,10 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4928
5292
|
}
|
|
4929
5293
|
for (const kill of ctx.activeHeadlessKills)
|
|
4930
5294
|
kill();
|
|
5295
|
+
try {
|
|
5296
|
+
await ctx.routerService?.stop();
|
|
5297
|
+
} catch {
|
|
5298
|
+
}
|
|
4931
5299
|
return new Promise((resolve4) => server.close(() => {
|
|
4932
5300
|
log(`exit-complete pid=${process.pid}`);
|
|
4933
5301
|
resolve4();
|
|
@@ -5942,7 +6310,7 @@ var init_bridge = __esm({
|
|
|
5942
6310
|
});
|
|
5943
6311
|
|
|
5944
6312
|
// packages/core/dist/restart-daemon.js
|
|
5945
|
-
import { execFileSync as
|
|
6313
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
5946
6314
|
import { existsSync as existsSync10 } from "fs";
|
|
5947
6315
|
function defaultIsRunning() {
|
|
5948
6316
|
return existsSync10(DAEMON_SOCK_PATH);
|
|
@@ -5952,7 +6320,7 @@ function defaultRunKickstart() {
|
|
|
5952
6320
|
const target = `gui/${uid}/${LABEL}`;
|
|
5953
6321
|
if (tryAcquireDaemonLock()) {
|
|
5954
6322
|
try {
|
|
5955
|
-
|
|
6323
|
+
execFileSync5("launchctl", kickstartArgv(target, true), { stdio: "ignore" });
|
|
5956
6324
|
} finally {
|
|
5957
6325
|
releaseDaemonLock();
|
|
5958
6326
|
}
|
|
@@ -6174,129 +6542,675 @@ var init_telegram = __esm({
|
|
|
6174
6542
|
}
|
|
6175
6543
|
});
|
|
6176
6544
|
|
|
6177
|
-
// packages/core/dist/
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
const cwd = process.cwd();
|
|
6181
|
-
for (const [name, proj] of Object.entries(config.projects)) {
|
|
6182
|
-
const resolvedPath = resolveHome(proj.path);
|
|
6183
|
-
if (cwd.startsWith(resolvedPath))
|
|
6184
|
-
return name;
|
|
6545
|
+
// packages/core/dist/router/types.js
|
|
6546
|
+
var init_types = __esm({
|
|
6547
|
+
"packages/core/dist/router/types.js"() {
|
|
6185
6548
|
}
|
|
6186
|
-
|
|
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");
|
|
6187
6555
|
}
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
} catch {
|
|
6194
|
-
return false;
|
|
6195
|
-
}
|
|
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;
|
|
6196
6561
|
}
|
|
6197
|
-
|
|
6198
|
-
const
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
await new Promise((r) => setTimeout(r, pollMs));
|
|
6203
|
-
}
|
|
6204
|
-
return false;
|
|
6562
|
+
function resolveProject(tokens, header) {
|
|
6563
|
+
const token = parseBearer(header);
|
|
6564
|
+
if (!token)
|
|
6565
|
+
return null;
|
|
6566
|
+
return tokens.get(token) ?? null;
|
|
6205
6567
|
}
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
const fromCfg = config.projects[opts.fromProject];
|
|
6209
|
-
const toCfg = config.projects[opts.toProject];
|
|
6210
|
-
if (!toCfg) {
|
|
6211
|
-
throw new Error(`target project '${opts.toProject}' not found in config`);
|
|
6568
|
+
var init_auth2 = __esm({
|
|
6569
|
+
"packages/core/dist/router/auth.js"() {
|
|
6212
6570
|
}
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
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"() {
|
|
6216
6579
|
}
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
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)));
|
|
6229
6593
|
}
|
|
6230
6594
|
}
|
|
6231
|
-
|
|
6232
|
-
const attemptId = randomUUID4();
|
|
6233
|
-
const record = {
|
|
6234
|
-
id: randomUUID4(),
|
|
6235
|
-
project: opts.toProject,
|
|
6236
|
-
originProject: opts.fromProject,
|
|
6237
|
-
provider: opts.provider ?? "claude",
|
|
6238
|
-
mode: opts.mode ?? "headless",
|
|
6239
|
-
state: "submitted",
|
|
6240
|
-
task: opts.task,
|
|
6241
|
-
createdAt: now,
|
|
6242
|
-
lastHeartbeat: now,
|
|
6243
|
-
lastEvent: "dispatch",
|
|
6244
|
-
heartbeatBudgetMs: 3e5,
|
|
6245
|
-
attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }]
|
|
6246
|
-
};
|
|
6247
|
-
const result = await sendRequest(sockPath, { kind: "dispatch", record });
|
|
6248
|
-
return result;
|
|
6595
|
+
return out;
|
|
6249
6596
|
}
|
|
6250
|
-
var
|
|
6251
|
-
var
|
|
6252
|
-
"packages/core/dist/
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
GROUP_DISPATCH_WARMUP_TIMEOUT_MS = 12e4;
|
|
6256
|
-
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"];
|
|
6257
6602
|
}
|
|
6258
6603
|
});
|
|
6259
6604
|
|
|
6260
|
-
// packages/core/dist/
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
6270
|
-
const deadline = Date.now() + readyTimeoutMs;
|
|
6271
|
-
let preSend = await read();
|
|
6272
|
-
let state = classify(preSend);
|
|
6273
|
-
while (state === "loading" && Date.now() < deadline) {
|
|
6274
|
-
await sleep3(pollMs);
|
|
6275
|
-
preSend = await read();
|
|
6276
|
-
state = classify(preSend);
|
|
6277
|
-
}
|
|
6278
|
-
if (state === "working")
|
|
6279
|
-
return;
|
|
6280
|
-
await runtime.send(refId, prompt).catch(() => {
|
|
6281
|
-
});
|
|
6282
|
-
if (state === "loading")
|
|
6283
|
-
return;
|
|
6284
|
-
await sleep3(settleMs);
|
|
6285
|
-
const after = await read();
|
|
6286
|
-
if (after !== preSend)
|
|
6287
|
-
return;
|
|
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;
|
|
6288
6614
|
}
|
|
6615
|
+
return void 0;
|
|
6289
6616
|
}
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
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
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6679
|
+
return saw;
|
|
6680
|
+
}
|
|
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);
|
|
7213
|
+
return;
|
|
6300
7214
|
}
|
|
6301
7215
|
const rawCurrent = opts.getCurrentWorkspace?.() ?? null;
|
|
6302
7216
|
const currentRef = rawCurrent?.match(/workspace:\d+/)?.[0];
|
|
@@ -6898,8 +7812,10 @@ var init_source = __esm({
|
|
|
6898
7812
|
var dist_exports = {};
|
|
6899
7813
|
__export(dist_exports, {
|
|
6900
7814
|
AGENT_BINS: () => AGENT_BINS,
|
|
7815
|
+
ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
|
|
6901
7816
|
BOT_COMMANDS: () => BOT_COMMANDS,
|
|
6902
7817
|
CC_SOCKS_DIR: () => CC_SOCKS_DIR,
|
|
7818
|
+
CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
|
|
6903
7819
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
6904
7820
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
6905
7821
|
CaptainDelivery: () => CaptainDelivery,
|
|
@@ -6919,6 +7835,10 @@ __export(dist_exports, {
|
|
|
6919
7835
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
6920
7836
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
6921
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,
|
|
6922
7842
|
STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
|
|
6923
7843
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
6924
7844
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
@@ -6927,17 +7847,22 @@ __export(dist_exports, {
|
|
|
6927
7847
|
WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
|
|
6928
7848
|
_resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
|
|
6929
7849
|
ageText: () => ageText,
|
|
7850
|
+
anthropicError: () => anthropicError,
|
|
6930
7851
|
appendCaptainMessage: () => appendCaptainMessage,
|
|
6931
7852
|
appendToMailbox: () => appendToMailbox,
|
|
6932
7853
|
assembleDaemonSnapshot: () => assembleDaemonSnapshot,
|
|
7854
|
+
assertBackendUsable: () => assertBackendUsable,
|
|
6933
7855
|
bootWorkspace: () => bootWorkspace,
|
|
6934
7856
|
buildCompletionProtocol: () => buildCompletionProtocol,
|
|
6935
7857
|
buildContext: () => buildContext,
|
|
6936
7858
|
buildDaemonPath: () => buildDaemonPath,
|
|
6937
7859
|
buildFreshness: () => buildFreshness,
|
|
7860
|
+
buildRouterCredentialsRequest: () => buildRouterCredentialsRequest,
|
|
7861
|
+
buildRouterEnv: () => buildRouterEnv,
|
|
6938
7862
|
buildSideFirstTurn: () => buildSideFirstTurn,
|
|
6939
7863
|
capAllowed: () => capAllowed,
|
|
6940
7864
|
capOutput: () => capOutput,
|
|
7865
|
+
captainRecordPath: () => captainRecordPath,
|
|
6941
7866
|
captainSocketPath: () => captainSocketPath,
|
|
6942
7867
|
checkFact: () => checkFact,
|
|
6943
7868
|
classifyHealth: () => classifyHealth,
|
|
@@ -6956,11 +7881,14 @@ __export(dist_exports, {
|
|
|
6956
7881
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
6957
7882
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
6958
7883
|
createLaunch: () => createLaunch,
|
|
7884
|
+
createRouterService: () => createRouterService,
|
|
7885
|
+
createRouterShim: () => createRouterShim,
|
|
6959
7886
|
createRunCommand: () => createRunCommand,
|
|
6960
7887
|
createStore: () => createStore,
|
|
6961
7888
|
createSurfaceLivenessProbe: () => createSurfaceLivenessProbe,
|
|
6962
7889
|
createTelegramBridge: () => createTelegramBridge,
|
|
6963
7890
|
createTelegramClient: () => createTelegramClient,
|
|
7891
|
+
createUsageTee: () => createUsageTee,
|
|
6964
7892
|
createWorkItem: () => createWorkItem,
|
|
6965
7893
|
createWorkStore: () => createWorkStore,
|
|
6966
7894
|
crewPaneTitle: () => crewPaneTitle,
|
|
@@ -6981,6 +7909,7 @@ __export(dist_exports, {
|
|
|
6981
7909
|
detectGroupAndUser: () => detectGroupAndUser,
|
|
6982
7910
|
detectGroupId: () => detectGroupId,
|
|
6983
7911
|
discoverCaptainSurface: () => discoverCaptainSurface,
|
|
7912
|
+
discoverLiveOpencodeServer: () => discoverLiveOpencodeServer,
|
|
6984
7913
|
dispatchToSibling: () => dispatchToSibling,
|
|
6985
7914
|
encodeFrame: () => encodeFrame,
|
|
6986
7915
|
encodeMsg: () => encodeMsg,
|
|
@@ -6994,6 +7923,7 @@ __export(dist_exports, {
|
|
|
6994
7923
|
findProjectByThread: () => findProjectByThread,
|
|
6995
7924
|
findWorkItemById: () => findWorkItemById,
|
|
6996
7925
|
forceKickstartAndVerify: () => forceKickstartAndVerify,
|
|
7926
|
+
formatCustomHeaders: () => formatCustomHeaders,
|
|
6997
7927
|
formatInbound: () => formatInbound,
|
|
6998
7928
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
6999
7929
|
formatLifecycle: () => formatLifecycle,
|
|
@@ -7010,33 +7940,44 @@ __export(dist_exports, {
|
|
|
7010
7940
|
isNotifyActive: () => isNotifyActive,
|
|
7011
7941
|
isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
|
|
7012
7942
|
isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
|
|
7943
|
+
isReadOnlyTopLevelCommand: () => isReadOnlyTopLevelCommand,
|
|
7013
7944
|
isSideTitle: () => isSideTitle,
|
|
7014
7945
|
isStickyAttention: () => isStickyAttention,
|
|
7015
7946
|
isTurnAccepted: () => isTurnAccepted,
|
|
7016
7947
|
isVersionUpgrade: () => isVersionUpgrade,
|
|
7948
|
+
joinUrl: () => joinUrl,
|
|
7017
7949
|
kickstartArgv: () => kickstartArgv,
|
|
7018
7950
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
7951
|
+
listSessions: () => listSessions,
|
|
7019
7952
|
loadSessions: () => loadSessions,
|
|
7020
7953
|
loadState: () => loadState,
|
|
7021
7954
|
mailboxStats: () => mailboxStats,
|
|
7022
7955
|
makeGate: () => makeGate,
|
|
7023
7956
|
maskToken: () => maskToken,
|
|
7957
|
+
mintToken: () => mintToken,
|
|
7024
7958
|
nameFromTitle: () => nameFromTitle,
|
|
7959
|
+
newestSessionInDirectory: () => newestSessionInDirectory,
|
|
7025
7960
|
nextAutoName: () => nextAutoName,
|
|
7026
7961
|
niceCrewCommand: () => niceCrewCommand,
|
|
7027
7962
|
notifyToggle: () => notifyToggle,
|
|
7963
|
+
parseBearer: () => parseBearer,
|
|
7028
7964
|
parseCommand: () => parseCommand,
|
|
7965
|
+
parseLiveOpencodeServers: () => parseLiveOpencodeServers,
|
|
7029
7966
|
parseNotifyPref: () => parseNotifyPref,
|
|
7030
7967
|
parseProgramArgs: () => parseProgramArgs,
|
|
7031
7968
|
plistPath: () => plistPath,
|
|
7969
|
+
pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
|
|
7032
7970
|
printForeignInstallError: () => printForeignInstallError,
|
|
7033
7971
|
printVersionUpgradeNotice: () => printVersionUpgradeNotice,
|
|
7972
|
+
probeCaptainChannel: () => probeCaptainChannel,
|
|
7034
7973
|
programArgsBlock: () => programArgsBlock,
|
|
7035
7974
|
projectHealth: () => projectHealth,
|
|
7036
7975
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
7976
|
+
readCaptainAddress: () => readCaptainAddress,
|
|
7037
7977
|
readCursor: () => readCursor,
|
|
7038
7978
|
readFromCursor: () => readFromCursor,
|
|
7039
7979
|
readRunningMarker: () => readRunningMarker,
|
|
7980
|
+
realpathOrSelf: () => realpathOrSelf,
|
|
7040
7981
|
reapCrewChildren: () => reapCrewChildren,
|
|
7041
7982
|
reapOrphanedCrews: () => reapOrphanedCrews,
|
|
7042
7983
|
reconcileLiveness: () => reconcileLiveness,
|
|
@@ -7046,12 +7987,18 @@ __export(dist_exports, {
|
|
|
7046
7987
|
reduceLifecycle: () => reduceLifecycle,
|
|
7047
7988
|
releaseDaemonLock: () => releaseDaemonLock,
|
|
7048
7989
|
removeRunningMarker: () => removeRunningMarker,
|
|
7990
|
+
renderEnvAssignments: () => renderEnvAssignments,
|
|
7049
7991
|
renderPlist: () => renderPlist,
|
|
7050
7992
|
reregisterDaemon: () => reregisterDaemon,
|
|
7051
7993
|
resolveAgentBinDirs: () => resolveAgentBinDirs,
|
|
7994
|
+
resolveAndPersistOpencodeCaptain: () => resolveAndPersistOpencodeCaptain,
|
|
7995
|
+
resolveBackend: () => resolveBackend,
|
|
7052
7996
|
resolveCrewRoute: () => resolveCrewRoute,
|
|
7053
7997
|
resolveCurrentProject: () => resolveCurrentProject,
|
|
7054
7998
|
resolveGate: () => resolveGate,
|
|
7999
|
+
resolveProject: () => resolveProject,
|
|
8000
|
+
resolveRouterCredentials: () => resolveRouterCredentials,
|
|
8001
|
+
resolveRouterUpstream: () => resolveRouterUpstream,
|
|
7055
8002
|
resolveSetupGroup: () => resolveSetupGroup,
|
|
7056
8003
|
resolveSetupToken: () => resolveSetupToken,
|
|
7057
8004
|
resolveSetupUserId: () => resolveSetupUserId,
|
|
@@ -7079,7 +8026,9 @@ __export(dist_exports, {
|
|
|
7079
8026
|
runTelegramSend: () => runTelegramSend,
|
|
7080
8027
|
runTelegramStatus: () => runTelegramStatus,
|
|
7081
8028
|
runningMarkerPath: () => runningMarkerPath,
|
|
8029
|
+
sameDirectory: () => sameDirectory,
|
|
7082
8030
|
sanitizePathForPlist: () => sanitizePathForPlist,
|
|
8031
|
+
sanitizeRequest: () => sanitizeRequest,
|
|
7083
8032
|
saveSessions: () => saveSessions,
|
|
7084
8033
|
saveState: () => saveState,
|
|
7085
8034
|
screenHasSplashMarker: () => screenHasSplashMarker,
|
|
@@ -7088,6 +8037,7 @@ __export(dist_exports, {
|
|
|
7088
8037
|
setNotify: () => setNotify,
|
|
7089
8038
|
setTopic: () => setTopic,
|
|
7090
8039
|
shellQuote: () => shellQuote,
|
|
8040
|
+
shouldBuildRouterService: () => shouldBuildRouterService,
|
|
7091
8041
|
shouldStartFresh: () => shouldStartFresh,
|
|
7092
8042
|
sideNameFromTitle: () => sideNameFromTitle,
|
|
7093
8043
|
sideNextAutoName: () => sideNextAutoName,
|
|
@@ -7103,8 +8053,10 @@ __export(dist_exports, {
|
|
|
7103
8053
|
topicKey: () => topicKey,
|
|
7104
8054
|
topicName: () => topicName,
|
|
7105
8055
|
tryAcquireDaemonLock: () => tryAcquireDaemonLock,
|
|
8056
|
+
usageFromJson: () => usageFromJson,
|
|
7106
8057
|
waitForCaptainDelivery: () => waitForCaptainDelivery,
|
|
7107
8058
|
waitForWarmup: () => waitForWarmup,
|
|
8059
|
+
writeCaptainAddress: () => writeCaptainAddress,
|
|
7108
8060
|
writeCursor: () => writeCursor,
|
|
7109
8061
|
writeExitMarker: () => writeExitMarker,
|
|
7110
8062
|
writeRunningMarker: () => writeRunningMarker,
|
|
@@ -7138,7 +8090,9 @@ var init_dist2 = __esm({
|
|
|
7138
8090
|
init_crew_protocol();
|
|
7139
8091
|
init_crew_lifecycle();
|
|
7140
8092
|
init_telegram();
|
|
8093
|
+
init_router();
|
|
7141
8094
|
init_crew_routing();
|
|
8095
|
+
init_router_resolution();
|
|
7142
8096
|
init_restart_daemon();
|
|
7143
8097
|
init_group_dispatch();
|
|
7144
8098
|
init_launch_workspace();
|
|
@@ -7154,6 +8108,8 @@ var init_dist2 = __esm({
|
|
|
7154
8108
|
init_source();
|
|
7155
8109
|
init_control_channel();
|
|
7156
8110
|
init_captain_channel();
|
|
8111
|
+
init_captain_record();
|
|
8112
|
+
init_opencode_session();
|
|
7157
8113
|
}
|
|
7158
8114
|
});
|
|
7159
8115
|
|
|
@@ -7169,14 +8125,75 @@ init_dist2();
|
|
|
7169
8125
|
init_dist2();
|
|
7170
8126
|
init_dist2();
|
|
7171
8127
|
init_dist2();
|
|
7172
|
-
import { join as
|
|
7173
|
-
import { homedir as
|
|
8128
|
+
import { join as join26, dirname as dirname5, resolve as resolve3 } from "path";
|
|
8129
|
+
import { homedir as homedir14 } from "os";
|
|
7174
8130
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7175
|
-
import { readFileSync as
|
|
8131
|
+
import { readFileSync as readFileSync18, statSync as statSync4, existsSync as existsSync13 } from "fs";
|
|
7176
8132
|
|
|
7177
8133
|
// packages/agents/dist/drivers/claude.js
|
|
7178
8134
|
import { execSync as execSync2 } from "child_process";
|
|
7179
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
|
+
|
|
7180
8197
|
// packages/agents/dist/drivers/codex.js
|
|
7181
8198
|
import { execSync as execSync3 } from "child_process";
|
|
7182
8199
|
|
|
@@ -7186,8 +8203,14 @@ import { execSync as execSync4 } from "child_process";
|
|
|
7186
8203
|
// packages/agents/dist/drivers/opencode.js
|
|
7187
8204
|
import { execSync as execSync5 } from "child_process";
|
|
7188
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
|
+
|
|
7189
8212
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
7190
|
-
import
|
|
8213
|
+
import fs14 from "fs";
|
|
7191
8214
|
import path13 from "path";
|
|
7192
8215
|
|
|
7193
8216
|
// packages/agents/dist/projection/cursor.js
|
|
@@ -7210,10 +8233,102 @@ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from
|
|
|
7210
8233
|
import path17 from "path";
|
|
7211
8234
|
import os9 from "os";
|
|
7212
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
|
+
|
|
7213
8328
|
// packages/agents/dist/projection/claude.js
|
|
7214
8329
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
7215
|
-
import
|
|
7216
|
-
import
|
|
8330
|
+
import path19 from "path";
|
|
8331
|
+
import os11 from "os";
|
|
7217
8332
|
|
|
7218
8333
|
// packages/agents/dist/codex/app-server-client.js
|
|
7219
8334
|
import { EventEmitter } from "events";
|
|
@@ -7487,11 +8602,11 @@ function toSnapshot(ev) {
|
|
|
7487
8602
|
|
|
7488
8603
|
// packages/agents/dist/codex/config.js
|
|
7489
8604
|
import { readFile as readFile6 } from "fs/promises";
|
|
7490
|
-
import { homedir as
|
|
7491
|
-
import { join as
|
|
8605
|
+
import { homedir as homedir9 } from "os";
|
|
8606
|
+
import { join as join19 } from "path";
|
|
7492
8607
|
async function resolveCodexModel() {
|
|
7493
|
-
const home = process.env["CODEX_HOME"] ??
|
|
7494
|
-
const configPath =
|
|
8608
|
+
const home = process.env["CODEX_HOME"] ?? join19(homedir9(), ".codex");
|
|
8609
|
+
const configPath = join19(home, "config.toml");
|
|
7495
8610
|
let text;
|
|
7496
8611
|
try {
|
|
7497
8612
|
text = await readFile6(configPath, "utf8");
|
|
@@ -8003,9 +9118,9 @@ var OpencodeSseBridge = class {
|
|
|
8003
9118
|
|
|
8004
9119
|
// packages/agents/dist/interactive/claude.js
|
|
8005
9120
|
import { execSync as execSync6 } from "child_process";
|
|
8006
|
-
import { readFileSync as
|
|
8007
|
-
import { homedir as
|
|
8008
|
-
import { join as
|
|
9121
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
9122
|
+
import { homedir as homedir10 } from "os";
|
|
9123
|
+
import { join as join20 } from "path";
|
|
8009
9124
|
var nextHookRequestId = Date.now();
|
|
8010
9125
|
|
|
8011
9126
|
// packages/agents/dist/headless/types.js
|
|
@@ -8169,67 +9284,8 @@ function runHeadless(opts) {
|
|
|
8169
9284
|
}
|
|
8170
9285
|
|
|
8171
9286
|
// packages/agents/dist/claude/peer-registry-source.js
|
|
8172
|
-
import { readdirSync as
|
|
8173
|
-
import { join as
|
|
8174
|
-
|
|
8175
|
-
// packages/agents/dist/claude/registry.js
|
|
8176
|
-
import fs14 from "fs";
|
|
8177
|
-
import { join as join16 } from "path";
|
|
8178
|
-
import { homedir as homedir9 } from "os";
|
|
8179
|
-
var CLAUDE_SESSIONS_DIR = join16(homedir9(), ".claude", "sessions");
|
|
8180
|
-
var PID_JSON = /^(\d+)\.json$/;
|
|
8181
|
-
function parseRegistryDir(files, readFile7) {
|
|
8182
|
-
const out = [];
|
|
8183
|
-
for (const name of files) {
|
|
8184
|
-
const m = PID_JSON.exec(name);
|
|
8185
|
-
if (!m)
|
|
8186
|
-
continue;
|
|
8187
|
-
let json;
|
|
8188
|
-
try {
|
|
8189
|
-
json = JSON.parse(readFile7(name));
|
|
8190
|
-
} catch {
|
|
8191
|
-
continue;
|
|
8192
|
-
}
|
|
8193
|
-
out.push({ ...json, pid: parseInt(m[1], 10) });
|
|
8194
|
-
}
|
|
8195
|
-
return out;
|
|
8196
|
-
}
|
|
8197
|
-
function toLifecycleSnapshot(entry, taskId, alive, now) {
|
|
8198
|
-
const at = entry.statusUpdatedAt ?? now;
|
|
8199
|
-
const base = { taskId, alive, origin: "agent", at, pid: entry.pid };
|
|
8200
|
-
if (!alive)
|
|
8201
|
-
return { ...base, state: "unknown" };
|
|
8202
|
-
switch (entry.status) {
|
|
8203
|
-
case "busy":
|
|
8204
|
-
case "shell":
|
|
8205
|
-
return { ...base, state: "running" };
|
|
8206
|
-
case "idle":
|
|
8207
|
-
return { ...base, state: "idle" };
|
|
8208
|
-
case "waiting":
|
|
8209
|
-
return {
|
|
8210
|
-
...base,
|
|
8211
|
-
state: "needsInput",
|
|
8212
|
-
detail: { reason: entry.waitingFor, note: entry.waitingFor }
|
|
8213
|
-
};
|
|
8214
|
-
default:
|
|
8215
|
-
return { ...base, state: "unknown" };
|
|
8216
|
-
}
|
|
8217
|
-
}
|
|
8218
|
-
function readClaudeStatusBySocketPath(socketPath) {
|
|
8219
|
-
let files;
|
|
8220
|
-
try {
|
|
8221
|
-
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
8222
|
-
} catch {
|
|
8223
|
-
return void 0;
|
|
8224
|
-
}
|
|
8225
|
-
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join16(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
8226
|
-
const entry = entries.find((e) => e.messagingSocketPath === socketPath);
|
|
8227
|
-
if (!entry)
|
|
8228
|
-
return void 0;
|
|
8229
|
-
return { status: entry.status, statusUpdatedAt: entry.statusUpdatedAt, sessionId: entry.sessionId };
|
|
8230
|
-
}
|
|
8231
|
-
|
|
8232
|
-
// 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";
|
|
8233
9289
|
function defaultIsAlive(pid) {
|
|
8234
9290
|
try {
|
|
8235
9291
|
process.kill(pid, 0);
|
|
@@ -8253,8 +9309,8 @@ var ClaudePeerRegistrySource = class {
|
|
|
8253
9309
|
pollMs;
|
|
8254
9310
|
log;
|
|
8255
9311
|
constructor(o = {}) {
|
|
8256
|
-
this.readdir = o.readdir ?? (() =>
|
|
8257
|
-
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"));
|
|
8258
9314
|
this.isAlive = o.isAlive ?? defaultIsAlive;
|
|
8259
9315
|
this.now = o.now ?? Date.now;
|
|
8260
9316
|
this.pollMs = o.pollMs ?? 2e3;
|
|
@@ -8566,6 +9622,119 @@ var ClaudeReceiptListener = class {
|
|
|
8566
9622
|
}
|
|
8567
9623
|
};
|
|
8568
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
|
+
|
|
8569
9738
|
// packages/agents/dist/opencode/fact-adapter.js
|
|
8570
9739
|
function createOpencodeFactAdapter(deps) {
|
|
8571
9740
|
return {
|
|
@@ -8598,6 +9767,9 @@ function createOpencodeFactAdapter(deps) {
|
|
|
8598
9767
|
}
|
|
8599
9768
|
if (type === "permission.replied")
|
|
8600
9769
|
return [{ kind: "activity" }];
|
|
9770
|
+
if (type === "server.connected" || type === "server.heartbeat") {
|
|
9771
|
+
return [{ kind: "activity" }];
|
|
9772
|
+
}
|
|
8601
9773
|
return [{ kind: "unknown", name: type }];
|
|
8602
9774
|
}
|
|
8603
9775
|
};
|
|
@@ -8608,7 +9780,8 @@ init_dist();
|
|
|
8608
9780
|
init_dist();
|
|
8609
9781
|
init_dist();
|
|
8610
9782
|
init_dist2();
|
|
8611
|
-
|
|
9783
|
+
init_dist2();
|
|
9784
|
+
import { execFile as execFile2, execFileSync as execFileSync6 } from "child_process";
|
|
8612
9785
|
var CMUX_TIMEOUT = 15e3;
|
|
8613
9786
|
var CmuxTimeoutError = class extends Error {
|
|
8614
9787
|
constructor(cmd) {
|
|
@@ -9204,9 +10377,9 @@ var NotifierRegistry = class {
|
|
|
9204
10377
|
};
|
|
9205
10378
|
|
|
9206
10379
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
9207
|
-
import
|
|
10380
|
+
import fs17 from "fs/promises";
|
|
9208
10381
|
import { existsSync as existsSync11 } from "fs";
|
|
9209
|
-
import
|
|
10382
|
+
import path21 from "path";
|
|
9210
10383
|
|
|
9211
10384
|
// packages/workspaces/dist/workspaces/registry.js
|
|
9212
10385
|
init_dist();
|
|
@@ -9359,16 +10532,16 @@ var CmuxEventsBridge = class {
|
|
|
9359
10532
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
9360
10533
|
init_dist2();
|
|
9361
10534
|
init_dist();
|
|
9362
|
-
import { readdirSync as
|
|
9363
|
-
import { join as
|
|
9364
|
-
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";
|
|
9365
10538
|
|
|
9366
10539
|
// packages/workspaces/dist/cmux-daemon/store-fingerprint.js
|
|
9367
10540
|
init_dist();
|
|
9368
10541
|
init_dist2();
|
|
9369
|
-
import { execFileSync as
|
|
10542
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
9370
10543
|
function defaultExecPs(pid) {
|
|
9371
|
-
return
|
|
10544
|
+
return execFileSync7("ps", ["-o", "command=", "-p", String(pid)], {
|
|
9372
10545
|
encoding: "utf-8",
|
|
9373
10546
|
stdio: ["ignore", "pipe", "ignore"]
|
|
9374
10547
|
});
|
|
@@ -9458,15 +10631,15 @@ function readLivenessSnapshot(files, readFile7, projects, argvRecovery = {}) {
|
|
|
9458
10631
|
|
|
9459
10632
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
9460
10633
|
async function readCmuxLiveness() {
|
|
9461
|
-
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
10634
|
+
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join22(homedir11(), ".cmuxterm");
|
|
9462
10635
|
const projects = loadConfig().projects;
|
|
9463
10636
|
let files;
|
|
9464
10637
|
try {
|
|
9465
|
-
files =
|
|
10638
|
+
files = readdirSync7(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
9466
10639
|
} catch (e) {
|
|
9467
10640
|
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
9468
10641
|
}
|
|
9469
|
-
return readLivenessSnapshot(files, (f) =>
|
|
10642
|
+
return readLivenessSnapshot(files, (f) => readFileSync15(join22(dir, f), "utf-8"), projects);
|
|
9470
10643
|
}
|
|
9471
10644
|
var DaemonCmux = class {
|
|
9472
10645
|
driver;
|
|
@@ -9529,13 +10702,15 @@ var DaemonCmux = class {
|
|
|
9529
10702
|
};
|
|
9530
10703
|
|
|
9531
10704
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
9532
|
-
import { join as
|
|
9533
|
-
import { homedir as
|
|
9534
|
-
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";
|
|
9535
10708
|
var CmuxStoreSource = class {
|
|
9536
10709
|
name = "cmux-store";
|
|
9537
10710
|
stateDir;
|
|
9538
10711
|
debounceMs;
|
|
10712
|
+
lockRetryMs;
|
|
10713
|
+
maxLockRetries;
|
|
9539
10714
|
isPidAlive;
|
|
9540
10715
|
listFiles;
|
|
9541
10716
|
readFile;
|
|
@@ -9547,13 +10722,19 @@ var CmuxStoreSource = class {
|
|
|
9547
10722
|
deps;
|
|
9548
10723
|
stopWatcher;
|
|
9549
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();
|
|
9550
10729
|
/** taskId → last reported snapshot (for snapshot() liveness floor). */
|
|
9551
10730
|
cache = /* @__PURE__ */ new Map();
|
|
9552
10731
|
active = false;
|
|
9553
10732
|
lastError = null;
|
|
9554
10733
|
constructor(opts = {}) {
|
|
9555
|
-
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");
|
|
9556
10735
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
10736
|
+
this.lockRetryMs = opts.lockRetryMs ?? 50;
|
|
10737
|
+
this.maxLockRetries = opts.maxLockRetries ?? 3;
|
|
9557
10738
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
9558
10739
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
9559
10740
|
this.readFile = opts.readFile ?? defaultReadFile;
|
|
@@ -9581,6 +10762,11 @@ var CmuxStoreSource = class {
|
|
|
9581
10762
|
this.cancelTimer(this.debounceTimer);
|
|
9582
10763
|
this.debounceTimer = void 0;
|
|
9583
10764
|
}
|
|
10765
|
+
for (const timer of this.lockRetryTimers.values()) {
|
|
10766
|
+
this.cancelTimer(timer);
|
|
10767
|
+
}
|
|
10768
|
+
this.lockRetryTimers.clear();
|
|
10769
|
+
this.lockRetryCounts.clear();
|
|
9584
10770
|
this.stopWatcher?.();
|
|
9585
10771
|
this.stopWatcher = void 0;
|
|
9586
10772
|
this.deps = void 0;
|
|
@@ -9614,12 +10800,13 @@ var CmuxStoreSource = class {
|
|
|
9614
10800
|
}
|
|
9615
10801
|
scanFile(filename) {
|
|
9616
10802
|
const deps = this.deps;
|
|
9617
|
-
const filePath =
|
|
10803
|
+
const filePath = join23(this.stateDir, filename);
|
|
9618
10804
|
const lockPath = `${filePath}.lock`;
|
|
9619
10805
|
if (this.fileExists(lockPath)) {
|
|
9620
|
-
this.
|
|
10806
|
+
this.scheduleLockRetry(filename);
|
|
9621
10807
|
return;
|
|
9622
10808
|
}
|
|
10809
|
+
this.clearLockRetry(filename);
|
|
9623
10810
|
const raw = this.readFile(filePath);
|
|
9624
10811
|
if (!raw)
|
|
9625
10812
|
return;
|
|
@@ -9634,6 +10821,31 @@ var CmuxStoreSource = class {
|
|
|
9634
10821
|
this.processSession(session, deps);
|
|
9635
10822
|
}
|
|
9636
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
|
+
}
|
|
9637
10849
|
processSession(session, deps) {
|
|
9638
10850
|
if (!session.sessionId || !session.cwd || typeof session.pid !== "number")
|
|
9639
10851
|
return;
|
|
@@ -9680,14 +10892,14 @@ function defaultIsPidAlive2(pid) {
|
|
|
9680
10892
|
}
|
|
9681
10893
|
function defaultListFiles(dir) {
|
|
9682
10894
|
try {
|
|
9683
|
-
return
|
|
10895
|
+
return readdirSync8(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
9684
10896
|
} catch {
|
|
9685
10897
|
return [];
|
|
9686
10898
|
}
|
|
9687
10899
|
}
|
|
9688
|
-
function defaultReadFile(
|
|
10900
|
+
function defaultReadFile(path23) {
|
|
9689
10901
|
try {
|
|
9690
|
-
return
|
|
10902
|
+
return readFileSync16(path23, "utf-8");
|
|
9691
10903
|
} catch {
|
|
9692
10904
|
return void 0;
|
|
9693
10905
|
}
|
|
@@ -9702,9 +10914,9 @@ function defaultWatchDir(dir, cb) {
|
|
|
9702
10914
|
}
|
|
9703
10915
|
|
|
9704
10916
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
9705
|
-
import { join as
|
|
9706
|
-
import { homedir as
|
|
9707
|
-
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";
|
|
9708
10920
|
var CLAUDE_HOOK_EVENTS = [
|
|
9709
10921
|
["SessionStart", "session-start"],
|
|
9710
10922
|
["UserPromptSubmit", "prompt-submit"],
|
|
@@ -9722,7 +10934,7 @@ var CLAUDE_HOOK_EVENTS = [
|
|
|
9722
10934
|
];
|
|
9723
10935
|
var DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
9724
10936
|
function installClaudeHooks(opts = {}) {
|
|
9725
|
-
const settingsPath = opts.settingsPath ??
|
|
10937
|
+
const settingsPath = opts.settingsPath ?? join24(homedir13(), ".claude", "settings.json");
|
|
9726
10938
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
9727
10939
|
const readFile7 = opts.readFile ?? defaultReadFile2;
|
|
9728
10940
|
const writeFile6 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -9894,16 +11106,16 @@ function extractDetail(sub, payload) {
|
|
|
9894
11106
|
}
|
|
9895
11107
|
return void 0;
|
|
9896
11108
|
}
|
|
9897
|
-
function defaultReadFile2(
|
|
11109
|
+
function defaultReadFile2(path23) {
|
|
9898
11110
|
try {
|
|
9899
|
-
return
|
|
11111
|
+
return readFileSync17(path23, "utf-8");
|
|
9900
11112
|
} catch {
|
|
9901
11113
|
return void 0;
|
|
9902
11114
|
}
|
|
9903
11115
|
}
|
|
9904
|
-
function defaultWriteFile(
|
|
9905
|
-
|
|
9906
|
-
|
|
11116
|
+
function defaultWriteFile(path23, content) {
|
|
11117
|
+
mkdirSync7(path23.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
11118
|
+
writeFileSync10(path23, content, "utf-8");
|
|
9907
11119
|
}
|
|
9908
11120
|
|
|
9909
11121
|
// packages/workspaces/dist/crew-pane.js
|
|
@@ -9983,9 +11195,9 @@ init_dist();
|
|
|
9983
11195
|
|
|
9984
11196
|
// packages/cli/src/lib/daemon-restart-broadcast.ts
|
|
9985
11197
|
init_dist();
|
|
9986
|
-
import
|
|
11198
|
+
import path22 from "path";
|
|
9987
11199
|
function statePath2(stateRoot) {
|
|
9988
|
-
return
|
|
11200
|
+
return path22.join(stateRoot, "daemon-restart-state.json");
|
|
9989
11201
|
}
|
|
9990
11202
|
function computeRestartSignature(version, buildMtimeMs) {
|
|
9991
11203
|
return `${version}::${buildMtimeMs}`;
|
|
@@ -10033,25 +11245,25 @@ async function maybeBroadcastDaemonRestart(opts) {
|
|
|
10033
11245
|
}
|
|
10034
11246
|
|
|
10035
11247
|
// packages/cli/src/lib/captain-channel-factory.ts
|
|
10036
|
-
import { createServer as
|
|
10037
|
-
import
|
|
10038
|
-
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";
|
|
10039
11251
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
10040
11252
|
import chalk2 from "chalk";
|
|
10041
11253
|
init_dist2();
|
|
10042
11254
|
var shared;
|
|
10043
|
-
var registryEntryPath = () =>
|
|
11255
|
+
var registryEntryPath = () => join25(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
10044
11256
|
function unregisterSenderIdentity() {
|
|
10045
11257
|
try {
|
|
10046
|
-
|
|
11258
|
+
fs18.unlinkSync(registryEntryPath());
|
|
10047
11259
|
} catch {
|
|
10048
11260
|
}
|
|
10049
11261
|
}
|
|
10050
11262
|
function registerSenderIdentity(socketPath) {
|
|
10051
11263
|
try {
|
|
10052
11264
|
unregisterSenderIdentity();
|
|
10053
|
-
|
|
10054
|
-
|
|
11265
|
+
fs18.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
11266
|
+
fs18.writeFileSync(
|
|
10055
11267
|
registryEntryPath(),
|
|
10056
11268
|
JSON.stringify({
|
|
10057
11269
|
pid: process.pid,
|
|
@@ -10072,12 +11284,12 @@ async function sharedReceiptListener() {
|
|
|
10072
11284
|
const socketPath = `${CC_SOCKS_DIR}/squadrantd-${process.pid}.sock`;
|
|
10073
11285
|
const listener = new ClaudeReceiptListener({
|
|
10074
11286
|
socketPath,
|
|
10075
|
-
createServer: (h) =>
|
|
11287
|
+
createServer: (h) => createServer4(h),
|
|
10076
11288
|
// A UDS path is not cleaned up when a process is killed, so our own leftover
|
|
10077
11289
|
// must never be the reason we refuse to start.
|
|
10078
11290
|
unlinkStale: (p) => {
|
|
10079
11291
|
try {
|
|
10080
|
-
|
|
11292
|
+
fs18.unlinkSync(p);
|
|
10081
11293
|
} catch {
|
|
10082
11294
|
}
|
|
10083
11295
|
},
|
|
@@ -10102,15 +11314,28 @@ async function buildCaptainChannel() {
|
|
|
10102
11314
|
// in /tmp/x registers cwd "/private/tmp/x" on macOS, so cwd matching silently
|
|
10103
11315
|
// missed and every captain ping reported accepted-unconfirmed.
|
|
10104
11316
|
statusFor: (taskId) => readClaudeStatusBySocketPath(captainSocketPath(taskId)),
|
|
10105
|
-
wire: (p, e) => writeLine(p, e, { connect: (
|
|
11317
|
+
wire: (p, e) => writeLine(p, e, { connect: (path23) => netConnect(path23) }),
|
|
10106
11318
|
receipts,
|
|
10107
11319
|
newMsgId: () => randomUUID5(),
|
|
10108
11320
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
10109
11321
|
log: (m) => console.error(chalk2.dim(m))
|
|
10110
11322
|
});
|
|
10111
11323
|
}
|
|
10112
|
-
async function
|
|
10113
|
-
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 = {}) {
|
|
10114
11339
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => {
|
|
10115
11340
|
const t = setTimeout(r, ms);
|
|
10116
11341
|
t.unref?.();
|
|
@@ -10129,14 +11354,20 @@ async function buildCaptainChannelWithRetry(opts = {}) {
|
|
|
10129
11354
|
}
|
|
10130
11355
|
}
|
|
10131
11356
|
}
|
|
11357
|
+
function buildCaptainChannelsWithRetry(opts) {
|
|
11358
|
+
return retryForever(
|
|
11359
|
+
() => buildCaptainChannels({ stateRoot: opts.stateRoot, configAgent: opts.configAgent }),
|
|
11360
|
+
opts
|
|
11361
|
+
);
|
|
11362
|
+
}
|
|
10132
11363
|
|
|
10133
11364
|
// packages/cli/src/squadrantd.ts
|
|
10134
11365
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
10135
|
-
var CLI_BIN =
|
|
11366
|
+
var CLI_BIN = join26(dirname5(SELF_PATH2), "index.js");
|
|
10136
11367
|
function readPkgVersion() {
|
|
10137
11368
|
try {
|
|
10138
|
-
const pkgPath =
|
|
10139
|
-
return JSON.parse(
|
|
11369
|
+
const pkgPath = join26(dirname5(SELF_PATH2), "..", "package.json");
|
|
11370
|
+
return JSON.parse(readFileSync18(pkgPath, "utf-8")).version ?? "unknown";
|
|
10140
11371
|
} catch {
|
|
10141
11372
|
return "unknown";
|
|
10142
11373
|
}
|
|
@@ -10158,7 +11389,7 @@ function buildTelegramBridge(cfg, stateRoot, log, deliverInbound) {
|
|
|
10158
11389
|
return createTelegramBridge({
|
|
10159
11390
|
cfg,
|
|
10160
11391
|
stateRoot,
|
|
10161
|
-
configRoot:
|
|
11392
|
+
configRoot: dirname5(stateRoot),
|
|
10162
11393
|
client,
|
|
10163
11394
|
appendCaptainMessage,
|
|
10164
11395
|
log,
|
|
@@ -10283,7 +11514,7 @@ function startSquadrantd(opts = {}) {
|
|
|
10283
11514
|
(r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
|
|
10284
11515
|
);
|
|
10285
11516
|
},
|
|
10286
|
-
cursorFile:
|
|
11517
|
+
cursorFile: join26(stateRoot, "cmux-events.seq"),
|
|
10287
11518
|
log
|
|
10288
11519
|
});
|
|
10289
11520
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
@@ -10310,14 +11541,23 @@ function startSquadrantd(opts = {}) {
|
|
|
10310
11541
|
log
|
|
10311
11542
|
}))
|
|
10312
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);
|
|
10313
11547
|
if (opts.notifyFault) ctx.notifyFault = opts.notifyFault;
|
|
10314
11548
|
else if (!process.env.VITEST) ctx.notifyFault = buildNotifyFault(log);
|
|
10315
11549
|
ctx.daemonCmux = opts.daemonCmux ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();
|
|
10316
11550
|
if (!process.env.VITEST) {
|
|
10317
11551
|
ctx.captainChannelMode = () => loadConfig().defaults.captainChannel ?? "off";
|
|
10318
11552
|
if (ctx.captainChannelMode() !== "off") {
|
|
10319
|
-
void
|
|
10320
|
-
|
|
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;
|
|
10321
11561
|
}).catch((e) => log(`captain-channel: unexpected retry-loop error: ${e.message}`));
|
|
10322
11562
|
}
|
|
10323
11563
|
}
|
|
@@ -10426,6 +11666,11 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
10426
11666
|
} catch (e) {
|
|
10427
11667
|
log(`native hook install failed: ${e.message}`);
|
|
10428
11668
|
}
|
|
11669
|
+
try {
|
|
11670
|
+
syncShippedOpencodeSkills({ pkgRoot: join26(dirname5(SELF_PATH2), "..") });
|
|
11671
|
+
} catch (e) {
|
|
11672
|
+
log(`opencode skills sync failed: ${e.message}`);
|
|
11673
|
+
}
|
|
10429
11674
|
const hookPrevSnaps = /* @__PURE__ */ new Map();
|
|
10430
11675
|
const hookDeps = {
|
|
10431
11676
|
resolve: (hint) => {
|
|
@@ -10560,7 +11805,7 @@ function logCrashMarker(kind, err) {
|
|
|
10560
11805
|
const message = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
10561
11806
|
process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${kind} pid=${process.pid} error=${message}
|
|
10562
11807
|
`);
|
|
10563
|
-
const stateRoot =
|
|
11808
|
+
const stateRoot = join26(homedir14(), ".config", "squadrant", "state");
|
|
10564
11809
|
writeExitMarker(stateRoot, {
|
|
10565
11810
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10566
11811
|
pid: process.pid,
|
|
@@ -10573,7 +11818,7 @@ function logCrashMarker(kind, err) {
|
|
|
10573
11818
|
`));
|
|
10574
11819
|
}
|
|
10575
11820
|
function isMonorepoCheckout(scriptPath, dirExists = existsSync13) {
|
|
10576
|
-
return dirExists(
|
|
11821
|
+
return dirExists(join26(dirname5(resolve3(scriptPath)), "..", "packages"));
|
|
10577
11822
|
}
|
|
10578
11823
|
function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
10579
11824
|
try {
|
|
@@ -10582,7 +11827,7 @@ function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
|
10582
11827
|
return void 0;
|
|
10583
11828
|
}
|
|
10584
11829
|
}) {
|
|
10585
|
-
const dotGit =
|
|
11830
|
+
const dotGit = join26(dirname5(resolve3(scriptPath)), "..", ".git");
|
|
10586
11831
|
return statFile(dotGit)?.isFile === true;
|
|
10587
11832
|
}
|
|
10588
11833
|
if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
|