squadrant 0.21.0 → 0.22.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 +1612 -368
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +929 -192
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -156,12 +156,7 @@ function getDefaultConfig() {
|
|
|
156
156
|
// resolver fallback is the safety net).
|
|
157
157
|
controlChannel: { claude: "on", opencode: "on" },
|
|
158
158
|
crewRouting: {
|
|
159
|
-
rules:
|
|
160
|
-
{ tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
|
|
161
|
-
{ tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
|
|
162
|
-
{ tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
|
|
163
|
-
{ tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
|
|
164
|
-
]
|
|
159
|
+
rules: DEFAULT_CREW_ROUTING_RULES.map((r) => ({ ...r }))
|
|
165
160
|
}
|
|
166
161
|
},
|
|
167
162
|
metrics: {
|
|
@@ -203,7 +198,7 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
203
198
|
function resolveHome(p) {
|
|
204
199
|
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
205
200
|
}
|
|
206
|
-
var ROUTER_KINDS, GATE_MODES, GATE_POLICIES, THINKING_LEVELS, CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH;
|
|
201
|
+
var ROUTER_KINDS, GATE_MODES, GATE_POLICIES, THINKING_LEVELS, CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
|
|
207
202
|
var init_config = __esm({
|
|
208
203
|
"packages/shared/dist/config.js"() {
|
|
209
204
|
init_config_io();
|
|
@@ -215,6 +210,201 @@ var init_config = __esm({
|
|
|
215
210
|
DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
|
|
216
211
|
CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
|
|
217
212
|
DAEMON_SOCK_PATH = path2.join(CONFIG_DIR, "squadrant.sock");
|
|
213
|
+
DEFAULT_CREW_ROUTING_RULES = [
|
|
214
|
+
{ tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
|
|
215
|
+
{ tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
|
|
216
|
+
{ tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
|
|
217
|
+
{ tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// packages/shared/dist/provider-preset.js
|
|
223
|
+
function isProviderPresetId(v2) {
|
|
224
|
+
return v2 === "a" || v2 === "b" || v2 === "c" || v2 === "d";
|
|
225
|
+
}
|
|
226
|
+
function presetCrewRouting(id) {
|
|
227
|
+
const base = (tier) => {
|
|
228
|
+
const found = DEFAULT_CREW_ROUTING_RULES.find((r) => r.tier === tier);
|
|
229
|
+
return found ? { ...found } : { tier, match: "", agent: "claude" };
|
|
230
|
+
};
|
|
231
|
+
const codex = (tier) => ({
|
|
232
|
+
tier: base(tier).tier,
|
|
233
|
+
match: base(tier).match,
|
|
234
|
+
agent: "codex"
|
|
235
|
+
});
|
|
236
|
+
switch (id) {
|
|
237
|
+
case "a":
|
|
238
|
+
return DEFAULT_CREW_ROUTING_RULES.map((r) => ({ ...r }));
|
|
239
|
+
case "b":
|
|
240
|
+
return [
|
|
241
|
+
{ ...base("extreme"), agent: "opencode", model: OPENCODE_MODEL },
|
|
242
|
+
{ ...base("hard"), agent: "opencode", model: OPENCODE_MODEL },
|
|
243
|
+
codex("mobile"),
|
|
244
|
+
{ ...base("daily"), agent: "opencode", model: OPENCODE_MODEL }
|
|
245
|
+
];
|
|
246
|
+
case "c":
|
|
247
|
+
return [
|
|
248
|
+
{ ...base("extreme"), agent: "claude", backend: "proxy", model: ROUTER_PRESET_MODEL },
|
|
249
|
+
{ ...base("hard"), agent: "claude", backend: "proxy", model: ROUTER_PRESET_MODEL },
|
|
250
|
+
codex("mobile"),
|
|
251
|
+
{ ...base("daily"), agent: "opencode", model: OPENCODE_MODEL }
|
|
252
|
+
];
|
|
253
|
+
case "d":
|
|
254
|
+
return ["extreme", "hard", "mobile", "daily"].map(codex);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function providerPresetDefaults(id, router) {
|
|
258
|
+
switch (id) {
|
|
259
|
+
case "a":
|
|
260
|
+
return {
|
|
261
|
+
roles: {
|
|
262
|
+
command: { agent: "claude", model: "opus" },
|
|
263
|
+
captain: { agent: "claude", model: "opus" },
|
|
264
|
+
crew: { agent: "claude", model: "sonnet" },
|
|
265
|
+
exploration: { agent: "claude", model: "haiku" },
|
|
266
|
+
side: { agent: "claude", model: "opus" }
|
|
267
|
+
},
|
|
268
|
+
permissions: { command: "auto", captain: "auto", crew: "auto" },
|
|
269
|
+
crewRouting: { rules: presetCrewRouting("a") }
|
|
270
|
+
};
|
|
271
|
+
case "b": {
|
|
272
|
+
const opencode = { agent: "opencode", model: OPENCODE_MODEL };
|
|
273
|
+
return {
|
|
274
|
+
roles: {
|
|
275
|
+
command: { ...opencode },
|
|
276
|
+
captain: { ...opencode },
|
|
277
|
+
crew: { ...opencode },
|
|
278
|
+
exploration: { ...opencode },
|
|
279
|
+
side: { ...opencode }
|
|
280
|
+
},
|
|
281
|
+
permissions: { command: "auto", captain: "auto", crew: "auto" },
|
|
282
|
+
crewRouting: { rules: presetCrewRouting("b") }
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
case "c": {
|
|
286
|
+
if (!router) {
|
|
287
|
+
throw new Error("preset C (claude harness + router backend) requires a router config \u2014 set defaults.router first");
|
|
288
|
+
}
|
|
289
|
+
const routed = { agent: "claude", backend: "proxy", model: ROUTER_PRESET_MODEL };
|
|
290
|
+
return {
|
|
291
|
+
roles: {
|
|
292
|
+
command: { ...routed },
|
|
293
|
+
captain: { ...routed },
|
|
294
|
+
crew: { ...routed },
|
|
295
|
+
exploration: { ...routed },
|
|
296
|
+
side: { ...routed }
|
|
297
|
+
},
|
|
298
|
+
// Auto mode's classifier is hardcoded to Sonnet 5 and fails closed on a
|
|
299
|
+
// router upstream — every claude session must use a manual mode so the
|
|
300
|
+
// U7 gate owns the prompt.
|
|
301
|
+
permissions: { command: "default", captain: "default", crew: "default" },
|
|
302
|
+
crewRouting: { rules: presetCrewRouting("c") },
|
|
303
|
+
router,
|
|
304
|
+
gate: { mode: "on" }
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
case "d": {
|
|
308
|
+
const codex = { agent: "codex" };
|
|
309
|
+
return {
|
|
310
|
+
roles: {
|
|
311
|
+
command: { ...codex },
|
|
312
|
+
captain: { ...codex },
|
|
313
|
+
crew: { ...codex },
|
|
314
|
+
exploration: { ...codex },
|
|
315
|
+
side: { ...codex }
|
|
316
|
+
},
|
|
317
|
+
permissions: {},
|
|
318
|
+
crewRouting: { rules: presetCrewRouting("d") }
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function detectProviderPresetId(config) {
|
|
324
|
+
const crew = config.defaults?.roles?.crew;
|
|
325
|
+
if (crew?.agent === "opencode")
|
|
326
|
+
return "b";
|
|
327
|
+
if (crew?.agent === "codex")
|
|
328
|
+
return "d";
|
|
329
|
+
if (crew?.backend && crew.backend !== "native")
|
|
330
|
+
return "c";
|
|
331
|
+
return "a";
|
|
332
|
+
}
|
|
333
|
+
function applyProviderPreset(config, id, opts) {
|
|
334
|
+
const preset = providerPresetDefaults(id, opts.router);
|
|
335
|
+
const next = structuredClone(config);
|
|
336
|
+
const changes = [];
|
|
337
|
+
if (opts.overwrite) {
|
|
338
|
+
next.defaults.roles = { ...preset.roles };
|
|
339
|
+
changes.push("defaults.roles");
|
|
340
|
+
} else if (next.defaults.roles === void 0) {
|
|
341
|
+
next.defaults.roles = { ...preset.roles };
|
|
342
|
+
changes.push("defaults.roles");
|
|
343
|
+
}
|
|
344
|
+
if (opts.overwrite) {
|
|
345
|
+
next.defaults.crewRouting = { rules: preset.crewRouting.rules.map((r) => ({ ...r })) };
|
|
346
|
+
changes.push("defaults.crewRouting");
|
|
347
|
+
} else if (next.defaults.crewRouting === void 0) {
|
|
348
|
+
next.defaults.crewRouting = { rules: preset.crewRouting.rules.map((r) => ({ ...r })) };
|
|
349
|
+
changes.push("defaults.crewRouting");
|
|
350
|
+
}
|
|
351
|
+
const existingPerms = next.defaults.permissions ?? {};
|
|
352
|
+
if (opts.overwrite) {
|
|
353
|
+
next.defaults.permissions = { ...existingPerms, ...preset.permissions };
|
|
354
|
+
if (Object.keys(preset.permissions).length > 0)
|
|
355
|
+
changes.push("defaults.permissions");
|
|
356
|
+
} else if (next.defaults.permissions === void 0 && Object.keys(preset.permissions).length > 0) {
|
|
357
|
+
next.defaults.permissions = { ...preset.permissions };
|
|
358
|
+
changes.push("defaults.permissions");
|
|
359
|
+
}
|
|
360
|
+
if (preset.router) {
|
|
361
|
+
if (opts.overwrite || next.defaults.router === void 0) {
|
|
362
|
+
next.defaults.router = structuredClone(preset.router);
|
|
363
|
+
changes.push("defaults.router");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (preset.gate) {
|
|
367
|
+
if (opts.overwrite || next.defaults.gate === void 0) {
|
|
368
|
+
next.defaults.gate = { ...preset.gate };
|
|
369
|
+
changes.push("defaults.gate");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return { config: next, changes };
|
|
373
|
+
}
|
|
374
|
+
var PROVIDER_PRESETS, ROUTER_PRESET_MODEL, DEFAULT_ROUTER_KIND, DEFAULT_ROUTER_BASE_URL, OPENCODE_MODEL;
|
|
375
|
+
var init_provider_preset = __esm({
|
|
376
|
+
"packages/shared/dist/provider-preset.js"() {
|
|
377
|
+
init_config();
|
|
378
|
+
PROVIDER_PRESETS = [
|
|
379
|
+
{
|
|
380
|
+
id: "a",
|
|
381
|
+
label: "Claude Code (Pro/Max)",
|
|
382
|
+
summary: "Claude harness on your subscription/API key. Real auto mode.",
|
|
383
|
+
requiresAnthropic: true
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: "b",
|
|
387
|
+
label: "opencode (no Anthropic subscription)",
|
|
388
|
+
summary: "All roles run on the opencode harness. Auto mode is model-agnostic.",
|
|
389
|
+
requiresAnthropic: false
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
id: "c",
|
|
393
|
+
label: "Claude harness + router backend (advanced)",
|
|
394
|
+
summary: "Claude harness against a router upstream. The U7 gate replaces auto mode.",
|
|
395
|
+
requiresAnthropic: false
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: "d",
|
|
399
|
+
label: "Codex (ChatGPT Pro)",
|
|
400
|
+
summary: "All roles run on the codex harness.",
|
|
401
|
+
requiresAnthropic: false
|
|
402
|
+
}
|
|
403
|
+
];
|
|
404
|
+
ROUTER_PRESET_MODEL = "deepseek-v4.1-flash";
|
|
405
|
+
DEFAULT_ROUTER_KIND = "opencode-go";
|
|
406
|
+
DEFAULT_ROUTER_BASE_URL = "https://opencode.ai/zen/go";
|
|
407
|
+
OPENCODE_MODEL = "opencode-go/deepseek-v4.1-flash";
|
|
218
408
|
}
|
|
219
409
|
});
|
|
220
410
|
|
|
@@ -1465,11 +1655,16 @@ __export(dist_exports, {
|
|
|
1465
1655
|
CREW_SKILLS: () => CREW_SKILLS,
|
|
1466
1656
|
DAEMON_SOCK_PATH: () => DAEMON_SOCK_PATH,
|
|
1467
1657
|
DEFAULT_CONFIG_PATH: () => DEFAULT_CONFIG_PATH,
|
|
1658
|
+
DEFAULT_CREW_ROUTING_RULES: () => DEFAULT_CREW_ROUTING_RULES,
|
|
1468
1659
|
DEFAULT_NOTIFY: () => DEFAULT_NOTIFY,
|
|
1660
|
+
DEFAULT_ROUTER_BASE_URL: () => DEFAULT_ROUTER_BASE_URL,
|
|
1661
|
+
DEFAULT_ROUTER_KIND: () => DEFAULT_ROUTER_KIND,
|
|
1469
1662
|
GATE_MODES: () => GATE_MODES,
|
|
1470
1663
|
GATE_POLICIES: () => GATE_POLICIES,
|
|
1471
1664
|
MANAGED_TARGETS: () => MANAGED_TARGETS,
|
|
1665
|
+
PROVIDER_PRESETS: () => PROVIDER_PRESETS,
|
|
1472
1666
|
ROUTER_KINDS: () => ROUTER_KINDS,
|
|
1667
|
+
ROUTER_PRESET_MODEL: () => ROUTER_PRESET_MODEL,
|
|
1473
1668
|
SAFE_KINDS: () => SAFE_KINDS,
|
|
1474
1669
|
SOCKET_CONTROL_MODE_PATH: () => SOCKET_CONTROL_MODE_PATH,
|
|
1475
1670
|
SPOKE_SUBDIRS: () => SPOKE_SUBDIRS,
|
|
@@ -1478,6 +1673,7 @@ __export(dist_exports, {
|
|
|
1478
1673
|
THINKING_LEVELS: () => THINKING_LEVELS,
|
|
1479
1674
|
UPDATE_CHECK_STATE_PATH: () => UPDATE_CHECK_STATE_PATH,
|
|
1480
1675
|
addWorktree: () => addWorktree,
|
|
1676
|
+
applyProviderPreset: () => applyProviderPreset,
|
|
1481
1677
|
applySafeFixes: () => applySafeFixes,
|
|
1482
1678
|
captainSessionName: () => captainSessionName,
|
|
1483
1679
|
checkForUpdate: () => checkForUpdate,
|
|
@@ -1493,6 +1689,7 @@ __export(dist_exports, {
|
|
|
1493
1689
|
defaultStatePath: () => defaultStatePath,
|
|
1494
1690
|
detectDrift: () => detectDrift,
|
|
1495
1691
|
detectInstallManager: () => detectInstallManager,
|
|
1692
|
+
detectProviderPresetId: () => detectProviderPresetId,
|
|
1496
1693
|
ensureCmuxAutoConfig: () => ensureCmuxAutoConfig,
|
|
1497
1694
|
ensureDirSync: () => ensureDirSync,
|
|
1498
1695
|
ensureRuntimeSynced: () => ensureRuntimeSynced,
|
|
@@ -1511,6 +1708,7 @@ __export(dist_exports, {
|
|
|
1511
1708
|
isGateMode: () => isGateMode,
|
|
1512
1709
|
isGatePolicy: () => isGatePolicy,
|
|
1513
1710
|
isNewerVersion: () => isNewerVersion,
|
|
1711
|
+
isProviderPresetId: () => isProviderPresetId,
|
|
1514
1712
|
isQuieter: () => isQuieter,
|
|
1515
1713
|
isRouterKind: () => isRouterKind,
|
|
1516
1714
|
isThinkingLevel: () => isThinkingLevel,
|
|
@@ -1527,6 +1725,7 @@ __export(dist_exports, {
|
|
|
1527
1725
|
parseThinkingLevel: () => parseThinkingLevel,
|
|
1528
1726
|
probeCmuxDaemonDirect: () => probeCmuxDaemonDirect,
|
|
1529
1727
|
projectConfigPath: () => projectConfigPath,
|
|
1728
|
+
providerPresetDefaults: () => providerPresetDefaults,
|
|
1530
1729
|
readConfigFileSync: () => readConfigFileSync,
|
|
1531
1730
|
readDailyLog: () => readDailyLog,
|
|
1532
1731
|
readProjectLevelSource: () => readProjectLevelSource,
|
|
@@ -1556,6 +1755,7 @@ __export(dist_exports, {
|
|
|
1556
1755
|
var init_dist = __esm({
|
|
1557
1756
|
"packages/shared/dist/index.js"() {
|
|
1558
1757
|
init_config();
|
|
1758
|
+
init_provider_preset();
|
|
1559
1759
|
init_project_config();
|
|
1560
1760
|
init_effort();
|
|
1561
1761
|
init_router_model();
|
|
@@ -2793,8 +2993,8 @@ function reconcileLiveness(prev, next) {
|
|
|
2793
2993
|
if (!prev)
|
|
2794
2994
|
return next;
|
|
2795
2995
|
if (next.source === "scan") {
|
|
2796
|
-
const
|
|
2797
|
-
return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
2996
|
+
const pidAlive2 = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;
|
|
2997
|
+
return { ...prev, pidAlive: pidAlive2, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
2798
2998
|
}
|
|
2799
2999
|
if (next.startedAt >= prev.startedAt || next.lastState === "end")
|
|
2800
3000
|
return next;
|
|
@@ -3540,6 +3740,68 @@ function createDirectSurfaceLivenessProbe(cmux2, getCaptainTitle) {
|
|
|
3540
3740
|
}
|
|
3541
3741
|
};
|
|
3542
3742
|
}
|
|
3743
|
+
function hasModalOptionList(screen) {
|
|
3744
|
+
if (!screen)
|
|
3745
|
+
return false;
|
|
3746
|
+
const lines = screen.split(/\r?\n/);
|
|
3747
|
+
let bottomHR = -1;
|
|
3748
|
+
let topHR = -1;
|
|
3749
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3750
|
+
if (HR_RE.test(lines[i])) {
|
|
3751
|
+
if (bottomHR === -1)
|
|
3752
|
+
bottomHR = i;
|
|
3753
|
+
else {
|
|
3754
|
+
topHR = i;
|
|
3755
|
+
break;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
if (topHR === -1)
|
|
3760
|
+
return false;
|
|
3761
|
+
return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
|
|
3762
|
+
}
|
|
3763
|
+
function classifyCaptainPane(screen, opts = {}) {
|
|
3764
|
+
if (screen == null || screen.trim() === "")
|
|
3765
|
+
return { status: "unreadable" };
|
|
3766
|
+
if (hasModalOptionList(screen))
|
|
3767
|
+
return { status: "locked" };
|
|
3768
|
+
if (WORKING_RE.test(screen))
|
|
3769
|
+
return { status: "mid-turn" };
|
|
3770
|
+
const idleAfterMs = opts.idleAfterMs ?? 0;
|
|
3771
|
+
const stale = opts.outputAgoMs !== void 0 && opts.outputAgoMs >= idleAfterMs;
|
|
3772
|
+
return stale ? { status: "no-session" } : { status: "mid-turn" };
|
|
3773
|
+
}
|
|
3774
|
+
function screenHash(screen) {
|
|
3775
|
+
let h = 2166136261;
|
|
3776
|
+
for (let i = 0; i < screen.length; i++) {
|
|
3777
|
+
h ^= screen.charCodeAt(i);
|
|
3778
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
3779
|
+
}
|
|
3780
|
+
return h.toString(16).padStart(8, "0");
|
|
3781
|
+
}
|
|
3782
|
+
function createCaptainPaneReader(cmux2, getCaptainTitle, opts = {}) {
|
|
3783
|
+
const idleAfterMs = opts.idleAfterMs ?? PANE_IDLE_AFTER_MS;
|
|
3784
|
+
return async (project) => {
|
|
3785
|
+
try {
|
|
3786
|
+
const wsId = await cmux2.findWorkspaceId(getCaptainTitle(project));
|
|
3787
|
+
if (!wsId)
|
|
3788
|
+
return { status: "no-session" };
|
|
3789
|
+
const surfaces = await cmux2.listSurfaces(wsId);
|
|
3790
|
+
const want = getCaptainTitle(project);
|
|
3791
|
+
const pane = surfaces.find((s) => s.title === want) ?? surfaces[0];
|
|
3792
|
+
if (!pane)
|
|
3793
|
+
return { status: "no-session" };
|
|
3794
|
+
const screen = await cmux2.readPaneScreen(pane);
|
|
3795
|
+
if (screen == null)
|
|
3796
|
+
return { status: "unreadable" };
|
|
3797
|
+
const outputAgoMs = opts.noteScreen?.(project, screenHash(screen));
|
|
3798
|
+
return classifyCaptainPane(screen, { outputAgoMs, idleAfterMs });
|
|
3799
|
+
} catch (e) {
|
|
3800
|
+
opts.log?.(`captain pane read failed project=${project}: ${e.message}`);
|
|
3801
|
+
return { status: "unreadable" };
|
|
3802
|
+
}
|
|
3803
|
+
};
|
|
3804
|
+
}
|
|
3543
3805
|
function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
|
|
3544
3806
|
return async (rec) => {
|
|
3545
3807
|
try {
|
|
@@ -3562,11 +3824,14 @@ function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
|
|
|
3562
3824
|
}
|
|
3563
3825
|
};
|
|
3564
3826
|
}
|
|
3565
|
-
var TAIL_LINES;
|
|
3827
|
+
var TAIL_LINES, HR_RE, WORKING_RE, PANE_IDLE_AFTER_MS;
|
|
3566
3828
|
var init_crew_pane_reader = __esm({
|
|
3567
3829
|
"packages/core/dist/crew-pane-reader.js"() {
|
|
3568
3830
|
init_dist();
|
|
3569
3831
|
TAIL_LINES = 25;
|
|
3832
|
+
HR_RE = /^\s*─{10,}\s*$/;
|
|
3833
|
+
WORKING_RE = /esc to interrupt|\btokens?\b.*\b(used|left)\b|↓\s*[\d.]+\s*k?\s*tokens?\b|\(\d+m?\s*\d+s\b/i;
|
|
3834
|
+
PANE_IDLE_AFTER_MS = 10 * 6e4;
|
|
3570
3835
|
}
|
|
3571
3836
|
});
|
|
3572
3837
|
|
|
@@ -4237,6 +4502,11 @@ function assertBackendUsable(o) {
|
|
|
4237
4502
|
function shouldBuildRouterService(router, isVitest) {
|
|
4238
4503
|
return !!router && !isVitest;
|
|
4239
4504
|
}
|
|
4505
|
+
function claudeEnvShadowsRouter(claudeEnv) {
|
|
4506
|
+
if (!claudeEnv)
|
|
4507
|
+
return [];
|
|
4508
|
+
return Object.keys(claudeEnv).filter((key) => key.startsWith("ANTHROPIC_"));
|
|
4509
|
+
}
|
|
4240
4510
|
var init_router_resolution = __esm({
|
|
4241
4511
|
"packages/core/dist/router-resolution.js"() {
|
|
4242
4512
|
init_dist();
|
|
@@ -4263,6 +4533,14 @@ function buildRouterEnv(creds, model) {
|
|
|
4263
4533
|
env.ANTHROPIC_MODEL = model;
|
|
4264
4534
|
return env;
|
|
4265
4535
|
}
|
|
4536
|
+
function mergeClaudeEnvRouterSettings(routerEnv, claudeEnv) {
|
|
4537
|
+
const merged = { ...routerEnv };
|
|
4538
|
+
for (const [key, value] of Object.entries(claudeEnv ?? {})) {
|
|
4539
|
+
if (!key.startsWith("ANTHROPIC_"))
|
|
4540
|
+
merged[key] = value;
|
|
4541
|
+
}
|
|
4542
|
+
return merged;
|
|
4543
|
+
}
|
|
4266
4544
|
function formatCustomHeaders(headers) {
|
|
4267
4545
|
const entries = Object.entries(headers ?? {});
|
|
4268
4546
|
if (entries.length === 0)
|
|
@@ -4576,10 +4854,18 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4576
4854
|
if (agentName === "claude") {
|
|
4577
4855
|
let routerEnv = {};
|
|
4578
4856
|
if (backend !== "native") {
|
|
4857
|
+
if (!deps.writeRouterSettings) {
|
|
4858
|
+
throw new Error(`backend '${backend}' requires a per-spawn --settings writer to outrank defaults.claudeEnv, but the spawn path has none \u2014 refusing to launch a routed session that would silently bypass the router shim`);
|
|
4859
|
+
}
|
|
4579
4860
|
if (!deps.routerCredentials) {
|
|
4580
4861
|
throw new Error(`backend '${backend}' requires router credentials, but the spawn path has no daemon credentials provider`);
|
|
4581
4862
|
}
|
|
4582
4863
|
routerEnv = buildRouterEnv(await deps.routerCredentials({ project: input.project, backend }), crewModel);
|
|
4864
|
+
const shadowed = claudeEnvShadowsRouter(config.defaults.claudeEnv);
|
|
4865
|
+
if (shadowed.length > 0) {
|
|
4866
|
+
process.stderr.write(`\u26A0\uFE0F backend '${backend}' selected but defaults.claudeEnv sets ${shadowed.join(", ")} \u2014 these would shadow the router shim. Overriding via a per-spawn --settings env block (outranks ~/.claude/settings.json).
|
|
4867
|
+
`);
|
|
4868
|
+
}
|
|
4583
4869
|
}
|
|
4584
4870
|
ensureSocksDir();
|
|
4585
4871
|
const messagingSocketPath = path9.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
|
|
@@ -4592,6 +4878,12 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4592
4878
|
name,
|
|
4593
4879
|
messagingSocketPath
|
|
4594
4880
|
});
|
|
4881
|
+
const routerSettingsPath = Object.keys(routerEnv).length > 0 ? deps.writeRouterSettings({
|
|
4882
|
+
stateRoot: STATE_ROOT,
|
|
4883
|
+
project: input.project,
|
|
4884
|
+
taskId: rec.id,
|
|
4885
|
+
env: mergeClaudeEnvRouterSettings(routerEnv, config.defaults.claudeEnv)
|
|
4886
|
+
}) : void 0;
|
|
4595
4887
|
let hooksInstalled = false;
|
|
4596
4888
|
try {
|
|
4597
4889
|
deps.writeSettingsLocal(spawnCwd);
|
|
@@ -4607,18 +4899,25 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4607
4899
|
messagingSocketPath,
|
|
4608
4900
|
// Permission mode is config-driven so squadrant can default crews to 'auto'
|
|
4609
4901
|
// or keep the semi-automatic 'acceptEdits' gate. Falls back to 'acceptEdits'.
|
|
4610
|
-
|
|
4902
|
+
// #772: a router-backed crew MUST use 'default' — 'auto' makes the built-in
|
|
4903
|
+
// Sonnet-5 classifier own PermissionRequest and the gate yields, which is
|
|
4904
|
+
// exactly the fail-closed dead end U7 exists to fix. The rubric owns the
|
|
4905
|
+
// permission mode for a routed spawn; the operator's config cannot override
|
|
4906
|
+
// it back into the broken path.
|
|
4907
|
+
permissionMode: backend !== "native" ? "default" : config.defaults.permissions?.crew ?? "acceptEdits",
|
|
4611
4908
|
// #708: self-describing name so ListAgents/the registry can tell this
|
|
4612
4909
|
// crew apart from an unrelated session instead of an auto-derived cwd
|
|
4613
4910
|
// basename (only the claude driver reads this — other agents ignore it).
|
|
4614
4911
|
sessionName: crewSessionName(input.project, name),
|
|
4615
4912
|
...crewModel ? { model: crewModel } : {},
|
|
4616
|
-
...crewThinking ? { thinking: crewThinking } : {}
|
|
4913
|
+
...crewThinking ? { thinking: crewThinking } : {},
|
|
4914
|
+
...routerSettingsPath ? { settingsPath: routerSettingsPath } : {}
|
|
4617
4915
|
});
|
|
4618
4916
|
const direction2 = input.direction ?? "tab";
|
|
4619
4917
|
const title2 = titleFor(input.project, name);
|
|
4620
4918
|
const pane2 = await deps.runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
4621
|
-
const
|
|
4919
|
+
const gateEnv = backend !== "native" ? " SQUADRANT_GATE=on" : "";
|
|
4920
|
+
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}${gateEnv}`;
|
|
4622
4921
|
const routerPrefix = Object.keys(routerEnv).length > 0 ? ` ${renderEnvAssignments(routerEnv)}` : "";
|
|
4623
4922
|
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix}${routerPrefix} ${niceCrewCommand(cliCommand2)}`);
|
|
4624
4923
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
@@ -5397,8 +5696,9 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
5397
5696
|
if (mode !== "off")
|
|
5398
5697
|
throw new DeferDelivery(null, "no-channel");
|
|
5399
5698
|
}
|
|
5699
|
+
const paneOpts = { ...sendOpts, agent };
|
|
5400
5700
|
try {
|
|
5401
|
-
return await cmux2.send(surface, text,
|
|
5701
|
+
return await cmux2.send(surface, text, paneOpts);
|
|
5402
5702
|
} catch (e) {
|
|
5403
5703
|
if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
|
|
5404
5704
|
throw e;
|
|
@@ -5410,7 +5710,7 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
5410
5710
|
}
|
|
5411
5711
|
log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
|
|
5412
5712
|
surface = next;
|
|
5413
|
-
return cmux2.send(next, text,
|
|
5713
|
+
return cmux2.send(next, text, paneOpts);
|
|
5414
5714
|
}
|
|
5415
5715
|
});
|
|
5416
5716
|
if ("delivered" in result) {
|
|
@@ -6341,16 +6641,26 @@ function createRunCommand(cliBin) {
|
|
|
6341
6641
|
}
|
|
6342
6642
|
};
|
|
6343
6643
|
}
|
|
6644
|
+
function captainAliveStateFromHealth(rows, project) {
|
|
6645
|
+
const row = rows.find((h) => h.kind === "captain" && h.project === project);
|
|
6646
|
+
if (!row)
|
|
6647
|
+
return "unknown";
|
|
6648
|
+
if (row.state === "alive")
|
|
6649
|
+
return "alive";
|
|
6650
|
+
if (row.state === "gone" || row.state === "stopped")
|
|
6651
|
+
return "dead";
|
|
6652
|
+
return "unknown";
|
|
6653
|
+
}
|
|
6344
6654
|
function isCaptainAliveFromHealth(rows, project) {
|
|
6345
|
-
return rows
|
|
6655
|
+
return captainAliveStateFromHealth(rows, project) === "alive";
|
|
6346
6656
|
}
|
|
6347
6657
|
function createIsCaptainAlive(sock) {
|
|
6348
6658
|
return async (project) => {
|
|
6349
6659
|
try {
|
|
6350
6660
|
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
6351
|
-
return
|
|
6661
|
+
return captainAliveStateFromHealth(health ?? [], project);
|
|
6352
6662
|
} catch {
|
|
6353
|
-
return
|
|
6663
|
+
return "unknown";
|
|
6354
6664
|
}
|
|
6355
6665
|
};
|
|
6356
6666
|
}
|
|
@@ -6387,16 +6697,19 @@ function createEnsureCaptainAlive(deps) {
|
|
|
6387
6697
|
const now = deps.now ?? (() => Date.now());
|
|
6388
6698
|
const inFlight = /* @__PURE__ */ new Map();
|
|
6389
6699
|
async function run(project) {
|
|
6390
|
-
|
|
6700
|
+
const first = await deps.isAlive(project);
|
|
6701
|
+
if (first === "alive")
|
|
6391
6702
|
return "alive";
|
|
6392
6703
|
await deps.launch(project);
|
|
6393
6704
|
const deadline = now() + warmupTimeoutMs;
|
|
6705
|
+
let last = first;
|
|
6394
6706
|
while (now() < deadline) {
|
|
6395
|
-
|
|
6707
|
+
last = await deps.isAlive(project);
|
|
6708
|
+
if (last === "alive")
|
|
6396
6709
|
return "launched";
|
|
6397
6710
|
await sleep3(pollMs);
|
|
6398
6711
|
}
|
|
6399
|
-
return "timeout";
|
|
6712
|
+
return last === "dead" ? "timeout" : "unknown";
|
|
6400
6713
|
}
|
|
6401
6714
|
return function ensure(project) {
|
|
6402
6715
|
const existing = inFlight.get(project);
|
|
@@ -6451,6 +6764,26 @@ ${ev.question}`;
|
|
|
6451
6764
|
function formatInbound(text) {
|
|
6452
6765
|
return `\u{1F4E9} [from Telegram] ${text}`;
|
|
6453
6766
|
}
|
|
6767
|
+
function mediaKind(m) {
|
|
6768
|
+
for (const [field, name] of MEDIA_KINDS) {
|
|
6769
|
+
if (m[field] !== void 0)
|
|
6770
|
+
return name;
|
|
6771
|
+
}
|
|
6772
|
+
return void 0;
|
|
6773
|
+
}
|
|
6774
|
+
function mediaMarker(kind) {
|
|
6775
|
+
return `[${kind} attached - not forwarded]`;
|
|
6776
|
+
}
|
|
6777
|
+
function inboundBody(text, kind) {
|
|
6778
|
+
if (kind === void 0)
|
|
6779
|
+
return text ?? "";
|
|
6780
|
+
const marker = mediaMarker(kind);
|
|
6781
|
+
return text ? `${text}
|
|
6782
|
+
${marker}` : marker;
|
|
6783
|
+
}
|
|
6784
|
+
function formatMediaReceipt(kind, hasCaption) {
|
|
6785
|
+
return hasCaption ? `\u{1F4CE} ${kind} not forwarded \u2014 your caption reached the captain` : `\u{1F4CE} ${kind} not forwarded \u2014 the captain was told it arrived, nothing else was sent`;
|
|
6786
|
+
}
|
|
6454
6787
|
function formatUsageLine(u) {
|
|
6455
6788
|
const reqs = u.requests === 1 ? "1 req" : `${u.requests} reqs`;
|
|
6456
6789
|
const named = Object.keys(u.models).filter((m) => m !== "unknown").sort((a, b) => u.models[b].costUsd - u.models[a].costUsd);
|
|
@@ -6462,8 +6795,19 @@ function maskToken(token) {
|
|
|
6462
6795
|
return token;
|
|
6463
6796
|
return "*".repeat(token.length - 4) + token.slice(-4);
|
|
6464
6797
|
}
|
|
6798
|
+
var MEDIA_KINDS;
|
|
6465
6799
|
var init_format = __esm({
|
|
6466
6800
|
"packages/core/dist/telegram/format.js"() {
|
|
6801
|
+
MEDIA_KINDS = [
|
|
6802
|
+
["photo", "photo"],
|
|
6803
|
+
["voice", "voice message"],
|
|
6804
|
+
["video_note", "video message"],
|
|
6805
|
+
["video", "video"],
|
|
6806
|
+
["audio", "audio file"],
|
|
6807
|
+
["document", "document"],
|
|
6808
|
+
["animation", "animation"],
|
|
6809
|
+
["sticker", "sticker"]
|
|
6810
|
+
];
|
|
6467
6811
|
}
|
|
6468
6812
|
});
|
|
6469
6813
|
|
|
@@ -6486,6 +6830,8 @@ function loadState(stateRoot) {
|
|
|
6486
6830
|
};
|
|
6487
6831
|
if (typeof data.lastUserId === "number")
|
|
6488
6832
|
result.lastUserId = data.lastUserId;
|
|
6833
|
+
if (data.pending && Object.keys(data.pending).length > 0)
|
|
6834
|
+
result.pending = data.pending;
|
|
6489
6835
|
return result;
|
|
6490
6836
|
} catch {
|
|
6491
6837
|
return { offset: 0, topics: {}, notify: {} };
|
|
@@ -6499,6 +6845,19 @@ function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
|
6499
6845
|
s.topics[topicKey(project, scope)] = topicId;
|
|
6500
6846
|
saveState(stateRoot, s);
|
|
6501
6847
|
}
|
|
6848
|
+
function pruneTopics(stateRoot, keep) {
|
|
6849
|
+
const s = loadState(stateRoot);
|
|
6850
|
+
const removed = Object.keys(s.topics).filter((key) => {
|
|
6851
|
+
const sep3 = key.indexOf("::");
|
|
6852
|
+
return !keep(sep3 === -1 ? key : key.slice(0, sep3));
|
|
6853
|
+
});
|
|
6854
|
+
if (removed.length === 0)
|
|
6855
|
+
return [];
|
|
6856
|
+
for (const key of removed)
|
|
6857
|
+
delete s.topics[key];
|
|
6858
|
+
saveState(stateRoot, s);
|
|
6859
|
+
return removed;
|
|
6860
|
+
}
|
|
6502
6861
|
function isNotifyActive(stateRoot, project) {
|
|
6503
6862
|
return loadState(stateRoot).notify[project] === true;
|
|
6504
6863
|
}
|
|
@@ -6512,6 +6871,42 @@ function setNotify(stateRoot, project, active) {
|
|
|
6512
6871
|
s.notify[project] = active;
|
|
6513
6872
|
saveState(stateRoot, s);
|
|
6514
6873
|
}
|
|
6874
|
+
function loadPending(stateRoot) {
|
|
6875
|
+
return loadState(stateRoot).pending ?? {};
|
|
6876
|
+
}
|
|
6877
|
+
function setPending(stateRoot, project, p) {
|
|
6878
|
+
const s = loadState(stateRoot);
|
|
6879
|
+
s.pending = { ...s.pending ?? {}, [project]: p };
|
|
6880
|
+
saveState(stateRoot, s);
|
|
6881
|
+
}
|
|
6882
|
+
function clearPending(stateRoot, project) {
|
|
6883
|
+
const s = loadState(stateRoot);
|
|
6884
|
+
if (!s.pending || !(project in s.pending))
|
|
6885
|
+
return;
|
|
6886
|
+
delete s.pending[project];
|
|
6887
|
+
if (Object.keys(s.pending).length === 0)
|
|
6888
|
+
delete s.pending;
|
|
6889
|
+
saveState(stateRoot, s);
|
|
6890
|
+
}
|
|
6891
|
+
function markPendingWarned(stateRoot, project, at) {
|
|
6892
|
+
const s = loadState(stateRoot);
|
|
6893
|
+
const p = s.pending?.[project];
|
|
6894
|
+
if (!p)
|
|
6895
|
+
return;
|
|
6896
|
+
s.pending[project] = { ...p, warnedAt: at };
|
|
6897
|
+
saveState(stateRoot, s);
|
|
6898
|
+
}
|
|
6899
|
+
function notePaneScreen(stateRoot, project, hash, at) {
|
|
6900
|
+
const s = loadState(stateRoot);
|
|
6901
|
+
const p = s.pending?.[project];
|
|
6902
|
+
if (!p)
|
|
6903
|
+
return void 0;
|
|
6904
|
+
if (p.paneHash === hash && p.paneChangedAt !== void 0)
|
|
6905
|
+
return at - p.paneChangedAt;
|
|
6906
|
+
s.pending[project] = { ...p, paneHash: hash, paneChangedAt: at };
|
|
6907
|
+
saveState(stateRoot, s);
|
|
6908
|
+
return void 0;
|
|
6909
|
+
}
|
|
6515
6910
|
function findProjectByThread(stateRoot, threadId) {
|
|
6516
6911
|
const s = loadState(stateRoot);
|
|
6517
6912
|
for (const [key, id] of Object.entries(s.topics)) {
|
|
@@ -6534,17 +6929,18 @@ var init_state = __esm({
|
|
|
6534
6929
|
function createTelegramClient(opts) {
|
|
6535
6930
|
const fetchImpl = opts.fetch ?? fetch;
|
|
6536
6931
|
const base = `https://api.telegram.org/bot${opts.token}`;
|
|
6537
|
-
async function call(method, body) {
|
|
6932
|
+
async function call(method, body, signal) {
|
|
6538
6933
|
const res = await fetchImpl(`${base}/${method}`, {
|
|
6539
6934
|
method: "POST",
|
|
6540
6935
|
headers: { "content-type": "application/json" },
|
|
6541
|
-
body: JSON.stringify(body)
|
|
6936
|
+
body: JSON.stringify(body),
|
|
6937
|
+
...signal ? { signal } : {}
|
|
6542
6938
|
});
|
|
6543
6939
|
const json = await res.json();
|
|
6544
6940
|
if (!res.ok || !json.ok) {
|
|
6545
6941
|
const code = json.error_code ?? res.status;
|
|
6546
6942
|
const desc = json.description ?? "unknown error";
|
|
6547
|
-
throw new
|
|
6943
|
+
throw new TelegramApiError(code, `telegram ${method} failed (${code}): ${desc}`, json.parameters?.retry_after);
|
|
6548
6944
|
}
|
|
6549
6945
|
return json.result;
|
|
6550
6946
|
}
|
|
@@ -6553,8 +6949,8 @@ function createTelegramClient(opts) {
|
|
|
6553
6949
|
const r = await call("getMe", {});
|
|
6554
6950
|
return { id: r.id, username: r.username };
|
|
6555
6951
|
},
|
|
6556
|
-
getUpdates(offset, timeoutSec = 50) {
|
|
6557
|
-
return call("getUpdates", { offset, timeout: timeoutSec });
|
|
6952
|
+
getUpdates(offset, timeoutSec = 50, signal) {
|
|
6953
|
+
return call("getUpdates", { offset, timeout: timeoutSec }, signal);
|
|
6558
6954
|
},
|
|
6559
6955
|
async sendMessage(chatId, threadId, text, replyMarkup) {
|
|
6560
6956
|
const body = { chat_id: chatId, text };
|
|
@@ -6577,6 +6973,9 @@ function createTelegramClient(opts) {
|
|
|
6577
6973
|
const r = await call("createForumTopic", { chat_id: chatId, name });
|
|
6578
6974
|
return r.message_thread_id;
|
|
6579
6975
|
},
|
|
6976
|
+
async deleteForumTopic(chatId, threadId) {
|
|
6977
|
+
await call("deleteForumTopic", { chat_id: chatId, message_thread_id: threadId });
|
|
6978
|
+
},
|
|
6580
6979
|
async setMyCommands(commands) {
|
|
6581
6980
|
await call("setMyCommands", { commands });
|
|
6582
6981
|
},
|
|
@@ -6585,11 +6984,241 @@ function createTelegramClient(opts) {
|
|
|
6585
6984
|
if (threadId !== void 0)
|
|
6586
6985
|
body.message_thread_id = threadId;
|
|
6587
6986
|
await call("sendChatAction", body);
|
|
6987
|
+
},
|
|
6988
|
+
async setMessageReaction(chatId, messageId, emoji) {
|
|
6989
|
+
await call("setMessageReaction", {
|
|
6990
|
+
chat_id: chatId,
|
|
6991
|
+
message_id: messageId,
|
|
6992
|
+
reaction: [{ type: "emoji", emoji }]
|
|
6993
|
+
});
|
|
6588
6994
|
}
|
|
6589
6995
|
};
|
|
6590
6996
|
}
|
|
6997
|
+
var TelegramApiError;
|
|
6591
6998
|
var init_client = __esm({
|
|
6592
6999
|
"packages/core/dist/telegram/client.js"() {
|
|
7000
|
+
TelegramApiError = class extends Error {
|
|
7001
|
+
code;
|
|
7002
|
+
retryAfterSec;
|
|
7003
|
+
constructor(code, message, retryAfterSec) {
|
|
7004
|
+
super(message);
|
|
7005
|
+
this.code = code;
|
|
7006
|
+
this.retryAfterSec = retryAfterSec;
|
|
7007
|
+
this.name = "TelegramApiError";
|
|
7008
|
+
}
|
|
7009
|
+
};
|
|
7010
|
+
}
|
|
7011
|
+
});
|
|
7012
|
+
|
|
7013
|
+
// packages/core/dist/telegram/inbound-lifecycle.js
|
|
7014
|
+
function defaultSchedule(fn, ms) {
|
|
7015
|
+
const t = setTimeout(fn, ms);
|
|
7016
|
+
t.unref?.();
|
|
7017
|
+
return () => clearTimeout(t);
|
|
7018
|
+
}
|
|
7019
|
+
function formatWatchdogWarning(project, read, minutes) {
|
|
7020
|
+
const head = `\u23F1\uFE0F [${project}] no reply for ${minutes} minutes`;
|
|
7021
|
+
switch (read.status) {
|
|
7022
|
+
case "locked":
|
|
7023
|
+
return `${head} \u2014 the captain is LOCKED on a prompt, waiting for a human. Answer it in the pane (or with \`squadrant crew answer\`) and it will continue.`;
|
|
7024
|
+
case "mid-turn":
|
|
7025
|
+
return `${head} \u2014 the captain is still working (a turn is in flight), just slow. No action needed yet.`;
|
|
7026
|
+
case "no-session":
|
|
7027
|
+
return `${head} \u2014 the captain looks GONE (no live session in its pane). Run \`squadrant launch ${project}\`.`;
|
|
7028
|
+
case "unreadable":
|
|
7029
|
+
return `${head} \u2014 couldn't read the captain's pane to classify it. Check it manually.`;
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
function createInboundLifecycle(opts) {
|
|
7033
|
+
const { stateRoot, cfg, sendChatAction, sendReply, readPane, log } = opts;
|
|
7034
|
+
const now = opts.now ?? Date.now;
|
|
7035
|
+
const schedule = opts.schedule ?? defaultSchedule;
|
|
7036
|
+
const typingMs = opts.typingMs ?? TYPING_KEEPALIVE_MS;
|
|
7037
|
+
const watchdogMs = opts.watchdogMs ?? WATCHDOG_MS;
|
|
7038
|
+
const warnAtMs = watchdogMs + (opts.watchdogMs === void 0 ? WATCHDOG_GRACE_MS : 0);
|
|
7039
|
+
const active = /* @__PURE__ */ new Set();
|
|
7040
|
+
let running = false;
|
|
7041
|
+
function issue(project, threadId) {
|
|
7042
|
+
void Promise.resolve(sendChatAction(cfg.supergroupId, threadId, "typing")).catch((e) => log(`telegram typing keep-alive failed project=${project}: ${e.message}`));
|
|
7043
|
+
}
|
|
7044
|
+
function drain(project) {
|
|
7045
|
+
active.add(project);
|
|
7046
|
+
const tick = () => {
|
|
7047
|
+
if (!running || !active.has(project))
|
|
7048
|
+
return;
|
|
7049
|
+
const p = loadPending(stateRoot)[project];
|
|
7050
|
+
if (!p) {
|
|
7051
|
+
active.delete(project);
|
|
7052
|
+
return;
|
|
7053
|
+
}
|
|
7054
|
+
if (now() - p.startedAt >= warnAtMs && p.warnedAt === void 0) {
|
|
7055
|
+
void warn(project, p.threadId);
|
|
7056
|
+
}
|
|
7057
|
+
issue(project, p.threadId);
|
|
7058
|
+
schedule(tick, typingMs);
|
|
7059
|
+
};
|
|
7060
|
+
schedule(tick, typingMs);
|
|
7061
|
+
}
|
|
7062
|
+
async function warn(project, threadId) {
|
|
7063
|
+
markPendingWarned(stateRoot, project, now());
|
|
7064
|
+
let read = { status: "unreadable" };
|
|
7065
|
+
try {
|
|
7066
|
+
if (readPane)
|
|
7067
|
+
read = await readPane(project);
|
|
7068
|
+
} catch (e) {
|
|
7069
|
+
log(`telegram watchdog pane read failed project=${project}: ${e.message}`);
|
|
7070
|
+
}
|
|
7071
|
+
try {
|
|
7072
|
+
await sendReply(threadId, formatWatchdogWarning(project, read, Math.floor(watchdogMs / 6e4)));
|
|
7073
|
+
} catch (e) {
|
|
7074
|
+
log(`telegram watchdog warning failed project=${project}: ${e.message}`);
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
return {
|
|
7078
|
+
start() {
|
|
7079
|
+
running = true;
|
|
7080
|
+
for (const [project, p] of Object.entries(loadPending(stateRoot))) {
|
|
7081
|
+
void (async () => {
|
|
7082
|
+
let read = { status: "unreadable" };
|
|
7083
|
+
try {
|
|
7084
|
+
if (readPane)
|
|
7085
|
+
read = await readPane(project);
|
|
7086
|
+
} catch (e) {
|
|
7087
|
+
log(`telegram resume pane read failed project=${project}: ${e.message}`);
|
|
7088
|
+
}
|
|
7089
|
+
const still = loadPending(stateRoot)[project];
|
|
7090
|
+
if (!still || still.startedAt !== p.startedAt)
|
|
7091
|
+
return;
|
|
7092
|
+
if (read.status === "no-session") {
|
|
7093
|
+
clearPending(stateRoot, project);
|
|
7094
|
+
log(`telegram typing lifecycle cleared for ${project}: captain session gone on boot`);
|
|
7095
|
+
return;
|
|
7096
|
+
}
|
|
7097
|
+
log(`telegram typing lifecycle resumed for ${project} (thread ${p.threadId})`);
|
|
7098
|
+
issue(project, p.threadId);
|
|
7099
|
+
drain(project);
|
|
7100
|
+
})();
|
|
7101
|
+
}
|
|
7102
|
+
},
|
|
7103
|
+
stop() {
|
|
7104
|
+
running = false;
|
|
7105
|
+
active.clear();
|
|
7106
|
+
},
|
|
7107
|
+
begin(project, threadId) {
|
|
7108
|
+
setPending(stateRoot, project, { threadId, startedAt: now() });
|
|
7109
|
+
issue(project, threadId);
|
|
7110
|
+
if (running && !active.has(project))
|
|
7111
|
+
drain(project);
|
|
7112
|
+
},
|
|
7113
|
+
pending() {
|
|
7114
|
+
return loadPending(stateRoot);
|
|
7115
|
+
}
|
|
7116
|
+
};
|
|
7117
|
+
}
|
|
7118
|
+
var TYPING_KEEPALIVE_MS, WATCHDOG_MS, WATCHDOG_GRACE_MS;
|
|
7119
|
+
var init_inbound_lifecycle = __esm({
|
|
7120
|
+
"packages/core/dist/telegram/inbound-lifecycle.js"() {
|
|
7121
|
+
init_state();
|
|
7122
|
+
TYPING_KEEPALIVE_MS = 4e3;
|
|
7123
|
+
WATCHDOG_MS = 15 * 6e4;
|
|
7124
|
+
WATCHDOG_GRACE_MS = 1;
|
|
7125
|
+
}
|
|
7126
|
+
});
|
|
7127
|
+
|
|
7128
|
+
// packages/core/dist/telegram/notify.js
|
|
7129
|
+
function runTelegramStatus(opts) {
|
|
7130
|
+
const tg = opts.config.telegram;
|
|
7131
|
+
const env = opts.env ?? process.env;
|
|
7132
|
+
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
7133
|
+
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
7134
|
+
const sep3 = key.indexOf("::");
|
|
7135
|
+
return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
|
|
7136
|
+
});
|
|
7137
|
+
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
7138
|
+
}
|
|
7139
|
+
function runTelegramNotifySet(opts) {
|
|
7140
|
+
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
7141
|
+
}
|
|
7142
|
+
function runTelegramNotifyPref(args) {
|
|
7143
|
+
const { project, dimension, value, root } = args;
|
|
7144
|
+
if (dimension === "crew") {
|
|
7145
|
+
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
7146
|
+
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
7147
|
+
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
7148
|
+
return { ok: true };
|
|
7149
|
+
}
|
|
7150
|
+
if (value !== "on" && value !== "off")
|
|
7151
|
+
return { ok: false, message: "cap must be on|off" };
|
|
7152
|
+
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
7153
|
+
return { ok: true };
|
|
7154
|
+
}
|
|
7155
|
+
function runTelegramNotifyStatus(opts) {
|
|
7156
|
+
const s = loadState(opts.stateRoot);
|
|
7157
|
+
const projects = /* @__PURE__ */ new Set();
|
|
7158
|
+
for (const key of Object.keys(s.topics)) {
|
|
7159
|
+
const sep3 = key.indexOf("::");
|
|
7160
|
+
projects.add(sep3 === -1 ? key : key.slice(0, sep3));
|
|
7161
|
+
}
|
|
7162
|
+
for (const p of Object.keys(s.notify))
|
|
7163
|
+
projects.add(p);
|
|
7164
|
+
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
7165
|
+
}
|
|
7166
|
+
function capAllowed(project, globalNotify, root) {
|
|
7167
|
+
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
7168
|
+
}
|
|
7169
|
+
function confirmationText(project, before, after, dim) {
|
|
7170
|
+
if (dim === "active")
|
|
7171
|
+
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
7172
|
+
if (dim === "cap")
|
|
7173
|
+
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
7174
|
+
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
7175
|
+
}
|
|
7176
|
+
async function runNotifyConfirmation(opts) {
|
|
7177
|
+
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
7178
|
+
if (!quieter || dim === null)
|
|
7179
|
+
return false;
|
|
7180
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7181
|
+
if (topicId === void 0)
|
|
7182
|
+
return false;
|
|
7183
|
+
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
7184
|
+
try {
|
|
7185
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
7186
|
+
return true;
|
|
7187
|
+
} catch {
|
|
7188
|
+
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
7189
|
+
return false;
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
async function runTelegramSend(opts) {
|
|
7193
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7194
|
+
if (topicId === void 0) {
|
|
7195
|
+
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
7196
|
+
}
|
|
7197
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
7198
|
+
clearPending(opts.stateRoot, opts.project);
|
|
7199
|
+
return { chatId: opts.cfg.supergroupId, topicId };
|
|
7200
|
+
}
|
|
7201
|
+
async function runTelegramLink(opts) {
|
|
7202
|
+
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7203
|
+
if (existing !== void 0)
|
|
7204
|
+
return { topicId: existing, created: false };
|
|
7205
|
+
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
7206
|
+
const winner = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7207
|
+
if (winner !== void 0) {
|
|
7208
|
+
try {
|
|
7209
|
+
await opts.client.deleteForumTopic?.(opts.cfg.supergroupId, topicId);
|
|
7210
|
+
} catch {
|
|
7211
|
+
}
|
|
7212
|
+
return { topicId: winner, created: false };
|
|
7213
|
+
}
|
|
7214
|
+
setTopic(opts.stateRoot, opts.project, topicId);
|
|
7215
|
+
return { topicId, created: true };
|
|
7216
|
+
}
|
|
7217
|
+
var init_notify = __esm({
|
|
7218
|
+
"packages/core/dist/telegram/notify.js"() {
|
|
7219
|
+
init_dist();
|
|
7220
|
+
init_state();
|
|
7221
|
+
init_format();
|
|
6593
7222
|
}
|
|
6594
7223
|
});
|
|
6595
7224
|
|
|
@@ -6677,8 +7306,10 @@ var init_tiers = __esm({
|
|
|
6677
7306
|
});
|
|
6678
7307
|
|
|
6679
7308
|
// packages/core/dist/telegram/bridge.js
|
|
7309
|
+
import fs11 from "fs";
|
|
6680
7310
|
import os5 from "os";
|
|
6681
7311
|
import path12 from "path";
|
|
7312
|
+
import crypto2 from "crypto";
|
|
6682
7313
|
function formatInboundReceipt(project, outcome) {
|
|
6683
7314
|
if (!outcome)
|
|
6684
7315
|
return void 0;
|
|
@@ -6694,6 +7325,85 @@ function formatInboundReceipt(project, outcome) {
|
|
|
6694
7325
|
return void 0;
|
|
6695
7326
|
}
|
|
6696
7327
|
}
|
|
7328
|
+
function telegramPollLockPath(token) {
|
|
7329
|
+
const hash = crypto2.createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
7330
|
+
return path12.join(os5.tmpdir(), `squadrant-telegram-poll-${hash}.lock`);
|
|
7331
|
+
}
|
|
7332
|
+
function pidAlive(pid) {
|
|
7333
|
+
try {
|
|
7334
|
+
process.kill(pid, 0);
|
|
7335
|
+
return true;
|
|
7336
|
+
} catch (e) {
|
|
7337
|
+
return e.code === "EPERM";
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
function lockIsHeld(file) {
|
|
7341
|
+
let pid;
|
|
7342
|
+
let ageMs;
|
|
7343
|
+
try {
|
|
7344
|
+
const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
|
|
7345
|
+
pid = Number.isFinite(parsed) ? parsed : void 0;
|
|
7346
|
+
ageMs = Date.now() - fs11.statSync(file).mtimeMs;
|
|
7347
|
+
} catch {
|
|
7348
|
+
return false;
|
|
7349
|
+
}
|
|
7350
|
+
if (pid !== void 0)
|
|
7351
|
+
return pidAlive(pid);
|
|
7352
|
+
return ageMs < POLL_LOCK_STALE_MS;
|
|
7353
|
+
}
|
|
7354
|
+
function acquirePollLock(token) {
|
|
7355
|
+
const file = telegramPollLockPath(token);
|
|
7356
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7357
|
+
try {
|
|
7358
|
+
const fd = fs11.openSync(file, "wx");
|
|
7359
|
+
fs11.writeSync(fd, String(process.pid));
|
|
7360
|
+
fs11.closeSync(fd);
|
|
7361
|
+
return () => {
|
|
7362
|
+
try {
|
|
7363
|
+
fs11.unlinkSync(file);
|
|
7364
|
+
} catch {
|
|
7365
|
+
}
|
|
7366
|
+
};
|
|
7367
|
+
} catch (e) {
|
|
7368
|
+
if (e.code !== "EEXIST")
|
|
7369
|
+
return null;
|
|
7370
|
+
if (attempt === 0 && !lockIsHeld(file)) {
|
|
7371
|
+
try {
|
|
7372
|
+
fs11.unlinkSync(file);
|
|
7373
|
+
} catch {
|
|
7374
|
+
}
|
|
7375
|
+
continue;
|
|
7376
|
+
}
|
|
7377
|
+
return null;
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
7380
|
+
return null;
|
|
7381
|
+
}
|
|
7382
|
+
function defaultSchedule2(fn, ms) {
|
|
7383
|
+
const t = setTimeout(fn, ms);
|
|
7384
|
+
t.unref?.();
|
|
7385
|
+
return () => clearTimeout(t);
|
|
7386
|
+
}
|
|
7387
|
+
function foreignPollLockHolder(token, selfPid = process.pid) {
|
|
7388
|
+
const file = telegramPollLockPath(token);
|
|
7389
|
+
let pid;
|
|
7390
|
+
try {
|
|
7391
|
+
const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
|
|
7392
|
+
if (!Number.isFinite(parsed))
|
|
7393
|
+
return null;
|
|
7394
|
+
pid = parsed;
|
|
7395
|
+
} catch {
|
|
7396
|
+
return null;
|
|
7397
|
+
}
|
|
7398
|
+
if (pid === selfPid)
|
|
7399
|
+
return null;
|
|
7400
|
+
return pidAlive(pid) ? pid : null;
|
|
7401
|
+
}
|
|
7402
|
+
function pollBackoffMs(err, pollMs) {
|
|
7403
|
+
if (err instanceof TelegramApiError && err.retryAfterSec !== void 0)
|
|
7404
|
+
return err.retryAfterSec * 1e3;
|
|
7405
|
+
return pollMs;
|
|
7406
|
+
}
|
|
6697
7407
|
function parseNotifyPref(text) {
|
|
6698
7408
|
const parts = text.trim().split(/\s+/);
|
|
6699
7409
|
if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
|
|
@@ -6720,9 +7430,17 @@ function notifyToggle(text) {
|
|
|
6720
7430
|
}
|
|
6721
7431
|
function createTelegramBridge(opts) {
|
|
6722
7432
|
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
7433
|
+
const lifecycle = opts.lifecycle;
|
|
6723
7434
|
const configRoot = opts.configRoot ?? path12.join(os5.homedir(), ".config", "squadrant");
|
|
6724
7435
|
const pollMs = cfg.pollMs ?? 1e3;
|
|
7436
|
+
const schedule = opts.schedule ?? defaultSchedule2;
|
|
7437
|
+
const lockToken = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
7438
|
+
let releasePollLock = null;
|
|
7439
|
+
let pollAbort = null;
|
|
6725
7440
|
let running = false;
|
|
7441
|
+
let pollStopped = false;
|
|
7442
|
+
let selfHealCancel = null;
|
|
7443
|
+
let conflictCount = 0;
|
|
6726
7444
|
let lastSuccessfulPollAt = null;
|
|
6727
7445
|
let lastError = null;
|
|
6728
7446
|
let lastErrorAt = null;
|
|
@@ -6732,12 +7450,8 @@ function createTelegramBridge(opts) {
|
|
|
6732
7450
|
saveState(stateRoot, s);
|
|
6733
7451
|
}
|
|
6734
7452
|
async function sendToTopic(project, text) {
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
6738
|
-
setTopic(stateRoot, project, threadId);
|
|
6739
|
-
}
|
|
6740
|
-
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
7453
|
+
const { topicId } = await runTelegramLink({ project, cfg, client, stateRoot });
|
|
7454
|
+
await client.sendMessage(cfg.supergroupId, topicId, text);
|
|
6741
7455
|
}
|
|
6742
7456
|
async function deliverOutbound(project, ev) {
|
|
6743
7457
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -6924,10 +7638,13 @@ ${formatUsageLine(usage2)}`;
|
|
|
6924
7638
|
}
|
|
6925
7639
|
await runChannelCommand(text, fromId, void 0);
|
|
6926
7640
|
}
|
|
6927
|
-
async function handleProjectTopic(text, threadId, fromId) {
|
|
7641
|
+
async function handleProjectTopic(text, threadId, fromId, messageId, media) {
|
|
6928
7642
|
const resolved = findProjectByThread(stateRoot, threadId);
|
|
6929
|
-
if (!resolved)
|
|
7643
|
+
if (!resolved) {
|
|
7644
|
+
log(`telegram inbound in unbound topic thread=${threadId} \u2014 no project linked, message dropped`);
|
|
7645
|
+
await reply(threadId, "\u26A0\uFE0F this topic isn't linked to a project \u2014 run: squadrant telegram link <project>");
|
|
6930
7646
|
return;
|
|
7647
|
+
}
|
|
6931
7648
|
if (isBareSpawn(text)) {
|
|
6932
7649
|
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
6933
7650
|
await reply(threadId, "\u26D4 not authorized");
|
|
@@ -6977,21 +7694,16 @@ ${formatUsageLine(usage2)}`;
|
|
|
6977
7694
|
await runChannelCommand(text, fromId, threadId);
|
|
6978
7695
|
return;
|
|
6979
7696
|
}
|
|
6980
|
-
|
|
6981
|
-
log(`telegram
|
|
6982
|
-
}
|
|
7697
|
+
if (messageId !== void 0) {
|
|
7698
|
+
void Promise.resolve(client.setMessageReaction?.(cfg.supergroupId, messageId, "\u{1F44D}")).catch((e) => log(`telegram stage-1 reaction failed project=${resolved.project}: ${e.message}`));
|
|
7699
|
+
}
|
|
6983
7700
|
setNotify(stateRoot, resolved.project, true);
|
|
6984
7701
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
6985
|
-
|
|
6986
|
-
const r = await ensureCaptainAlive(resolved.project);
|
|
7702
|
+
void ensureCaptainAlive(resolved.project).then((r) => {
|
|
6987
7703
|
if (r === "timeout") {
|
|
6988
|
-
|
|
6989
|
-
} else {
|
|
6990
|
-
await reply(threadId, `\u{1F4E8} delivered to ${resolved.project} captain`);
|
|
7704
|
+
log(`telegram auto-launch: warmup timed out project=${resolved.project} \u2014 delivery verdict (not this probe) decides the receipt`);
|
|
6991
7705
|
}
|
|
6992
|
-
}
|
|
6993
|
-
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
6994
|
-
}
|
|
7706
|
+
}).catch((e) => log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`));
|
|
6995
7707
|
}
|
|
6996
7708
|
let handled = false;
|
|
6997
7709
|
let outcome;
|
|
@@ -7000,13 +7712,22 @@ ${formatUsageLine(usage2)}`;
|
|
|
7000
7712
|
handled = res.handled;
|
|
7001
7713
|
outcome = res.outcome;
|
|
7002
7714
|
}
|
|
7715
|
+
const deliveryHeld = outcome?.status === "held";
|
|
7003
7716
|
if (!handled) {
|
|
7004
7717
|
await appendCaptainMessage2({ stateRoot, project: resolved.project, text: formatInbound(text), source: "telegram" });
|
|
7718
|
+
outcome = void 0;
|
|
7005
7719
|
}
|
|
7006
7720
|
const receipt = formatInboundReceipt(resolved.project, outcome);
|
|
7007
7721
|
if (receipt && sendReply) {
|
|
7008
7722
|
await sendReply(threadId, receipt).catch((e) => log(`telegram receipt failed project=${resolved.project}: ${e.message}`));
|
|
7009
7723
|
}
|
|
7724
|
+
if (media) {
|
|
7725
|
+
await reply(threadId, formatMediaReceipt(media.kind, media.hasCaption));
|
|
7726
|
+
}
|
|
7727
|
+
if (deliveryHeld)
|
|
7728
|
+
return;
|
|
7729
|
+
await reply(threadId, CAPTAIN_RECEIVED_ACK);
|
|
7730
|
+
lifecycle?.begin(resolved.project, threadId);
|
|
7010
7731
|
}
|
|
7011
7732
|
async function handleUpdate(u) {
|
|
7012
7733
|
if (u.callback_query) {
|
|
@@ -7014,7 +7735,11 @@ ${formatUsageLine(usage2)}`;
|
|
|
7014
7735
|
return;
|
|
7015
7736
|
}
|
|
7016
7737
|
const m = u.message;
|
|
7017
|
-
if (!m
|
|
7738
|
+
if (!m)
|
|
7739
|
+
return;
|
|
7740
|
+
const kind = mediaKind(m);
|
|
7741
|
+
const text = m.text ?? m.caption;
|
|
7742
|
+
if (text === void 0 && kind === void 0)
|
|
7018
7743
|
return;
|
|
7019
7744
|
if (!cfg.chats.includes(m.chat.id))
|
|
7020
7745
|
return;
|
|
@@ -7028,7 +7753,7 @@ ${formatUsageLine(usage2)}`;
|
|
|
7028
7753
|
await reply(threadId, "\u26D4 not authorized");
|
|
7029
7754
|
return;
|
|
7030
7755
|
}
|
|
7031
|
-
const task =
|
|
7756
|
+
const task = (text ?? "").trim();
|
|
7032
7757
|
if (!task) {
|
|
7033
7758
|
await reply(threadId, "spawn cancelled \u2014 empty task");
|
|
7034
7759
|
return;
|
|
@@ -7039,39 +7764,122 @@ ${formatUsageLine(usage2)}`;
|
|
|
7039
7764
|
return;
|
|
7040
7765
|
}
|
|
7041
7766
|
if (m.message_thread_id === void 0) {
|
|
7767
|
+
if (m.text === void 0)
|
|
7768
|
+
return;
|
|
7042
7769
|
await handleGeneral(m.text, m.from?.id);
|
|
7043
7770
|
return;
|
|
7044
7771
|
}
|
|
7045
|
-
await handleProjectTopic(
|
|
7772
|
+
await handleProjectTopic(inboundBody(text, kind), m.message_thread_id, m.from?.id, m.message_id, kind === void 0 ? void 0 : { kind, hasCaption: text !== void 0 });
|
|
7773
|
+
}
|
|
7774
|
+
function acquirePollLockOrLog() {
|
|
7775
|
+
if (!lockToken)
|
|
7776
|
+
return true;
|
|
7777
|
+
releasePollLock = acquirePollLock(lockToken);
|
|
7778
|
+
if (releasePollLock)
|
|
7779
|
+
return true;
|
|
7780
|
+
const holder = foreignPollLockHolder(lockToken);
|
|
7781
|
+
log(`telegram inbound poll disabled: another consumer already polls this bot token (single-consumer guard, #830)${holder !== null ? ` \u2014 lock held by pid ${holder}` : ""}`);
|
|
7782
|
+
return false;
|
|
7783
|
+
}
|
|
7784
|
+
function scheduleSelfHeal() {
|
|
7785
|
+
selfHealCancel?.();
|
|
7786
|
+
selfHealCancel = schedule(() => {
|
|
7787
|
+
selfHealCancel = null;
|
|
7788
|
+
if (pollStopped || running)
|
|
7789
|
+
return;
|
|
7790
|
+
if (!acquirePollLockOrLog()) {
|
|
7791
|
+
scheduleSelfHeal();
|
|
7792
|
+
return;
|
|
7793
|
+
}
|
|
7794
|
+
running = true;
|
|
7795
|
+
conflictCount = 0;
|
|
7796
|
+
log("telegram inbound poll re-attempting after a 409 stop (#850)");
|
|
7797
|
+
void pollLoop();
|
|
7798
|
+
}, CONFLICT_409_SELF_HEAL_MS);
|
|
7046
7799
|
}
|
|
7047
7800
|
async function pollLoop() {
|
|
7048
7801
|
while (running) {
|
|
7802
|
+
const ac = new AbortController();
|
|
7803
|
+
pollAbort = ac;
|
|
7804
|
+
let waitMs = pollMs;
|
|
7049
7805
|
try {
|
|
7050
7806
|
const offset = loadState(stateRoot).offset;
|
|
7051
|
-
const updates = await client.getUpdates(offset, LONG_POLL_SEC);
|
|
7807
|
+
const updates = await client.getUpdates(offset, LONG_POLL_SEC, ac.signal);
|
|
7808
|
+
conflictCount = 0;
|
|
7052
7809
|
for (const u of updates) {
|
|
7053
7810
|
await handleUpdate(u);
|
|
7054
7811
|
persistOffset(u.update_id + 1);
|
|
7055
7812
|
}
|
|
7056
7813
|
lastSuccessfulPollAt = Date.now();
|
|
7057
7814
|
} catch (e) {
|
|
7058
|
-
|
|
7815
|
+
if (ac.signal.aborted)
|
|
7816
|
+
break;
|
|
7817
|
+
const msg = e.message;
|
|
7818
|
+
lastError = msg;
|
|
7059
7819
|
lastErrorAt = Date.now();
|
|
7060
|
-
|
|
7820
|
+
if (e instanceof TelegramApiError && e.code === 409) {
|
|
7821
|
+
conflictCount += 1;
|
|
7822
|
+
if (conflictCount >= CONFLICT_409_TERMINAL_AFTER) {
|
|
7823
|
+
log(`telegram inbound poll stopped: repeated 409 conflict (another getUpdates consumer holds this token) \u2014 ${msg}`);
|
|
7824
|
+
break;
|
|
7825
|
+
}
|
|
7826
|
+
} else {
|
|
7827
|
+
log(`telegram inbound poll failed: ${msg}`);
|
|
7828
|
+
}
|
|
7829
|
+
waitMs = pollBackoffMs(e, pollMs);
|
|
7830
|
+
} finally {
|
|
7831
|
+
pollAbort = null;
|
|
7061
7832
|
}
|
|
7062
7833
|
if (running)
|
|
7063
|
-
await sleep2(
|
|
7834
|
+
await sleep2(waitMs);
|
|
7064
7835
|
}
|
|
7836
|
+
running = false;
|
|
7837
|
+
releasePollLock?.();
|
|
7838
|
+
releasePollLock = null;
|
|
7839
|
+
if (!pollStopped)
|
|
7840
|
+
scheduleSelfHeal();
|
|
7065
7841
|
}
|
|
7066
7842
|
return {
|
|
7067
7843
|
start() {
|
|
7068
7844
|
if (running)
|
|
7069
7845
|
return;
|
|
7846
|
+
pollStopped = false;
|
|
7847
|
+
selfHealCancel?.();
|
|
7848
|
+
selfHealCancel = null;
|
|
7849
|
+
if (!acquirePollLockOrLog())
|
|
7850
|
+
return;
|
|
7851
|
+
const known = opts.registeredProjects?.();
|
|
7852
|
+
if (known !== void 0) {
|
|
7853
|
+
try {
|
|
7854
|
+
const removed = pruneTopics(stateRoot, (p) => known.includes(p));
|
|
7855
|
+
if (removed.length > 0)
|
|
7856
|
+
log(`telegram registry pruned ${removed.length} stale topic link(s): ${removed.join(", ")}`);
|
|
7857
|
+
} catch (e) {
|
|
7858
|
+
log(`telegram registry prune failed: ${e.message}`);
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7070
7861
|
running = true;
|
|
7862
|
+
conflictCount = 0;
|
|
7863
|
+
try {
|
|
7864
|
+
lifecycle?.start();
|
|
7865
|
+
} catch (e) {
|
|
7866
|
+
log(`telegram lifecycle start failed: ${e.message}`);
|
|
7867
|
+
}
|
|
7071
7868
|
void pollLoop();
|
|
7072
7869
|
},
|
|
7073
7870
|
stop() {
|
|
7074
7871
|
running = false;
|
|
7872
|
+
pollStopped = true;
|
|
7873
|
+
selfHealCancel?.();
|
|
7874
|
+
selfHealCancel = null;
|
|
7875
|
+
try {
|
|
7876
|
+
lifecycle?.stop();
|
|
7877
|
+
} catch (e) {
|
|
7878
|
+
log(`telegram lifecycle stop failed: ${e.message}`);
|
|
7879
|
+
}
|
|
7880
|
+
pollAbort?.abort();
|
|
7881
|
+
releasePollLock?.();
|
|
7882
|
+
releasePollLock = null;
|
|
7075
7883
|
},
|
|
7076
7884
|
pushLifecycle(project, ev) {
|
|
7077
7885
|
void deliverOutbound(project, ev).catch((e) => {
|
|
@@ -7088,17 +7896,23 @@ ${formatUsageLine(usage2)}`;
|
|
|
7088
7896
|
}
|
|
7089
7897
|
};
|
|
7090
7898
|
}
|
|
7091
|
-
var LONG_POLL_SEC, sleep2, CREW_TIERS, RECOGNIZED_CHANNEL_COMMANDS;
|
|
7899
|
+
var CAPTAIN_RECEIVED_ACK, LONG_POLL_SEC, POLL_LOCK_STALE_MS, CONFLICT_409_TERMINAL_AFTER, CONFLICT_409_SELF_HEAL_MS, sleep2, CREW_TIERS, RECOGNIZED_CHANNEL_COMMANDS;
|
|
7092
7900
|
var init_bridge = __esm({
|
|
7093
7901
|
"packages/core/dist/telegram/bridge.js"() {
|
|
7094
7902
|
init_dist();
|
|
7903
|
+
init_client();
|
|
7095
7904
|
init_auth();
|
|
7096
7905
|
init_commands();
|
|
7097
7906
|
init_format();
|
|
7907
|
+
init_notify();
|
|
7098
7908
|
init_panels();
|
|
7099
7909
|
init_state();
|
|
7100
7910
|
init_tiers();
|
|
7911
|
+
CAPTAIN_RECEIVED_ACK = "\u2705 captain received";
|
|
7101
7912
|
LONG_POLL_SEC = 50;
|
|
7913
|
+
POLL_LOCK_STALE_MS = 2 * 6e4;
|
|
7914
|
+
CONFLICT_409_TERMINAL_AFTER = 3;
|
|
7915
|
+
CONFLICT_409_SELF_HEAL_MS = 5 * 6e4;
|
|
7102
7916
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7103
7917
|
CREW_TIERS = ["all", "alert_only", "done_only", "none"];
|
|
7104
7918
|
RECOGNIZED_CHANNEL_COMMANDS = /* @__PURE__ */ new Set(["status", "projects", "crews", "launch", "effort", "spawn"]);
|
|
@@ -7143,7 +7957,7 @@ var init_restart_daemon = __esm({
|
|
|
7143
7957
|
});
|
|
7144
7958
|
|
|
7145
7959
|
// packages/core/dist/telegram/setup.js
|
|
7146
|
-
import
|
|
7960
|
+
import fs12 from "fs";
|
|
7147
7961
|
function resolveSetupGroup(existingSupergroupId, opts) {
|
|
7148
7962
|
if (existingSupergroupId !== void 0 && !opts.redetect)
|
|
7149
7963
|
return "reuse";
|
|
@@ -7194,130 +8008,42 @@ function writeTelegramConfig(configPath, opts) {
|
|
|
7194
8008
|
let config;
|
|
7195
8009
|
let raw = null;
|
|
7196
8010
|
try {
|
|
7197
|
-
raw =
|
|
8011
|
+
raw = fs12.readFileSync(configPath, "utf-8");
|
|
7198
8012
|
} catch (err) {
|
|
7199
8013
|
if (err.code !== "ENOENT") {
|
|
7200
8014
|
throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
|
|
7201
|
-
}
|
|
7202
|
-
}
|
|
7203
|
-
if (raw !== null) {
|
|
7204
|
-
try {
|
|
7205
|
-
config = JSON.parse(raw);
|
|
7206
|
-
} catch (err) {
|
|
7207
|
-
throw new Error(`refusing to overwrite corrupt config at ${configPath}: ${String(err)}`);
|
|
7208
|
-
}
|
|
7209
|
-
} else {
|
|
7210
|
-
config = {};
|
|
7211
|
-
}
|
|
7212
|
-
const prev = config.telegram && typeof config.telegram === "object" ? config.telegram : {};
|
|
7213
|
-
const next = {
|
|
7214
|
-
botToken: opts.token,
|
|
7215
|
-
supergroupId: opts.supergroupId,
|
|
7216
|
-
chats: [opts.supergroupId]
|
|
7217
|
-
};
|
|
7218
|
-
const users = opts.users ?? prev.users;
|
|
7219
|
-
const remoteControl = opts.remoteControl ?? prev.remoteControl;
|
|
7220
|
-
if (users !== void 0)
|
|
7221
|
-
next.users = users;
|
|
7222
|
-
if (remoteControl !== void 0)
|
|
7223
|
-
next.remoteControl = remoteControl;
|
|
7224
|
-
config.telegram = next;
|
|
7225
|
-
writeConfigFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
7226
|
-
}
|
|
7227
|
-
var init_setup = __esm({
|
|
7228
|
-
"packages/core/dist/telegram/setup.js"() {
|
|
7229
|
-
init_dist();
|
|
7230
|
-
init_state();
|
|
7231
|
-
init_bot_commands();
|
|
7232
|
-
init_restart_daemon();
|
|
7233
|
-
}
|
|
7234
|
-
});
|
|
7235
|
-
|
|
7236
|
-
// packages/core/dist/telegram/notify.js
|
|
7237
|
-
function runTelegramStatus(opts) {
|
|
7238
|
-
const tg = opts.config.telegram;
|
|
7239
|
-
const env = opts.env ?? process.env;
|
|
7240
|
-
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
7241
|
-
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
7242
|
-
const sep3 = key.indexOf("::");
|
|
7243
|
-
return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
|
|
7244
|
-
});
|
|
7245
|
-
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
7246
|
-
}
|
|
7247
|
-
function runTelegramNotifySet(opts) {
|
|
7248
|
-
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
7249
|
-
}
|
|
7250
|
-
function runTelegramNotifyPref(args) {
|
|
7251
|
-
const { project, dimension, value, root } = args;
|
|
7252
|
-
if (dimension === "crew") {
|
|
7253
|
-
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
7254
|
-
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
7255
|
-
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
7256
|
-
return { ok: true };
|
|
7257
|
-
}
|
|
7258
|
-
if (value !== "on" && value !== "off")
|
|
7259
|
-
return { ok: false, message: "cap must be on|off" };
|
|
7260
|
-
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
7261
|
-
return { ok: true };
|
|
7262
|
-
}
|
|
7263
|
-
function runTelegramNotifyStatus(opts) {
|
|
7264
|
-
const s = loadState(opts.stateRoot);
|
|
7265
|
-
const projects = /* @__PURE__ */ new Set();
|
|
7266
|
-
for (const key of Object.keys(s.topics)) {
|
|
7267
|
-
const sep3 = key.indexOf("::");
|
|
7268
|
-
projects.add(sep3 === -1 ? key : key.slice(0, sep3));
|
|
7269
|
-
}
|
|
7270
|
-
for (const p of Object.keys(s.notify))
|
|
7271
|
-
projects.add(p);
|
|
7272
|
-
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
7273
|
-
}
|
|
7274
|
-
function capAllowed(project, globalNotify, root) {
|
|
7275
|
-
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
7276
|
-
}
|
|
7277
|
-
function confirmationText(project, before, after, dim) {
|
|
7278
|
-
if (dim === "active")
|
|
7279
|
-
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
7280
|
-
if (dim === "cap")
|
|
7281
|
-
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
7282
|
-
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
7283
|
-
}
|
|
7284
|
-
async function runNotifyConfirmation(opts) {
|
|
7285
|
-
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
7286
|
-
if (!quieter || dim === null)
|
|
7287
|
-
return false;
|
|
7288
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7289
|
-
if (topicId === void 0)
|
|
7290
|
-
return false;
|
|
7291
|
-
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
7292
|
-
try {
|
|
7293
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
7294
|
-
return true;
|
|
7295
|
-
} catch {
|
|
7296
|
-
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
7297
|
-
return false;
|
|
7298
|
-
}
|
|
7299
|
-
}
|
|
7300
|
-
async function runTelegramSend(opts) {
|
|
7301
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7302
|
-
if (topicId === void 0) {
|
|
7303
|
-
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
8015
|
+
}
|
|
7304
8016
|
}
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
8017
|
+
if (raw !== null) {
|
|
8018
|
+
try {
|
|
8019
|
+
config = JSON.parse(raw);
|
|
8020
|
+
} catch (err) {
|
|
8021
|
+
throw new Error(`refusing to overwrite corrupt config at ${configPath}: ${String(err)}`);
|
|
8022
|
+
}
|
|
8023
|
+
} else {
|
|
8024
|
+
config = {};
|
|
8025
|
+
}
|
|
8026
|
+
const prev = config.telegram && typeof config.telegram === "object" ? config.telegram : {};
|
|
8027
|
+
const next = {
|
|
8028
|
+
botToken: opts.token,
|
|
8029
|
+
supergroupId: opts.supergroupId,
|
|
8030
|
+
chats: [opts.supergroupId]
|
|
8031
|
+
};
|
|
8032
|
+
const users = opts.users ?? prev.users;
|
|
8033
|
+
const remoteControl = opts.remoteControl ?? prev.remoteControl;
|
|
8034
|
+
if (users !== void 0)
|
|
8035
|
+
next.users = users;
|
|
8036
|
+
if (remoteControl !== void 0)
|
|
8037
|
+
next.remoteControl = remoteControl;
|
|
8038
|
+
config.telegram = next;
|
|
8039
|
+
writeConfigFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
7315
8040
|
}
|
|
7316
|
-
var
|
|
7317
|
-
"packages/core/dist/telegram/
|
|
8041
|
+
var init_setup = __esm({
|
|
8042
|
+
"packages/core/dist/telegram/setup.js"() {
|
|
7318
8043
|
init_dist();
|
|
7319
8044
|
init_state();
|
|
7320
|
-
|
|
8045
|
+
init_bot_commands();
|
|
8046
|
+
init_restart_daemon();
|
|
7321
8047
|
}
|
|
7322
8048
|
});
|
|
7323
8049
|
|
|
@@ -7332,6 +8058,7 @@ var init_telegram = __esm({
|
|
|
7332
8058
|
init_format();
|
|
7333
8059
|
init_state();
|
|
7334
8060
|
init_client();
|
|
8061
|
+
init_inbound_lifecycle();
|
|
7335
8062
|
init_bridge();
|
|
7336
8063
|
init_setup();
|
|
7337
8064
|
init_notify();
|
|
@@ -7526,6 +8253,26 @@ function writeJson(res, status, body) {
|
|
|
7526
8253
|
res.writeHead(status, { "content-type": "application/json" });
|
|
7527
8254
|
res.end(JSON.stringify(body));
|
|
7528
8255
|
}
|
|
8256
|
+
function pathnameOf(url) {
|
|
8257
|
+
if (!url)
|
|
8258
|
+
return "";
|
|
8259
|
+
const q = url.indexOf("?");
|
|
8260
|
+
return q === -1 ? url : url.slice(0, q);
|
|
8261
|
+
}
|
|
8262
|
+
function modelsList(ids) {
|
|
8263
|
+
const data = ids.map((id) => ({
|
|
8264
|
+
type: "model",
|
|
8265
|
+
id,
|
|
8266
|
+
display_name: id,
|
|
8267
|
+
created_at: "1970-01-01T00:00:00Z"
|
|
8268
|
+
}));
|
|
8269
|
+
return {
|
|
8270
|
+
data,
|
|
8271
|
+
has_more: false,
|
|
8272
|
+
first_id: ids[0] ?? null,
|
|
8273
|
+
last_id: ids[ids.length - 1] ?? null
|
|
8274
|
+
};
|
|
8275
|
+
}
|
|
7529
8276
|
function buildUpstreamHeaders(req, upstream, stream) {
|
|
7530
8277
|
const headers = {
|
|
7531
8278
|
"content-type": "application/json",
|
|
@@ -7554,6 +8301,7 @@ function createRouterShim(opts) {
|
|
|
7554
8301
|
let lastError;
|
|
7555
8302
|
let upstreamReachable = true;
|
|
7556
8303
|
const upstreamUrl = joinUrl(opts.upstream.baseUrl, "/v1/messages");
|
|
8304
|
+
const advertisedModels = new Set(opts.models ?? []);
|
|
7557
8305
|
function emitUsage(u) {
|
|
7558
8306
|
try {
|
|
7559
8307
|
opts.onUsage?.(u);
|
|
@@ -7583,6 +8331,8 @@ function createRouterShim(opts) {
|
|
|
7583
8331
|
const body = parsed;
|
|
7584
8332
|
const wantsStream = body.stream === true;
|
|
7585
8333
|
const model = typeof body.model === "string" ? body.model : void 0;
|
|
8334
|
+
if (model)
|
|
8335
|
+
advertisedModels.add(model);
|
|
7586
8336
|
const sanitized = sanitizeRequest(body, opts.upstream);
|
|
7587
8337
|
let upstreamRes;
|
|
7588
8338
|
try {
|
|
@@ -7655,8 +8405,20 @@ function createRouterShim(opts) {
|
|
|
7655
8405
|
emitUsage(usage2);
|
|
7656
8406
|
writeJson(res, upstreamRes.status, upstreamBody);
|
|
7657
8407
|
}
|
|
8408
|
+
async function handleCountTokens(req, res) {
|
|
8409
|
+
let text;
|
|
8410
|
+
try {
|
|
8411
|
+
text = await readBody(req);
|
|
8412
|
+
} catch {
|
|
8413
|
+
const e = anthropicError(400, "invalid_request_error", "malformed request body");
|
|
8414
|
+
writeJson(res, e.status, e.body);
|
|
8415
|
+
return;
|
|
8416
|
+
}
|
|
8417
|
+
writeJson(res, 200, { input_tokens: Math.max(1, Math.ceil(text.length / 4)) });
|
|
8418
|
+
}
|
|
7658
8419
|
function handler(req, res) {
|
|
7659
|
-
|
|
8420
|
+
const path39 = pathnameOf(req.url);
|
|
8421
|
+
if (req.method === "POST" && path39 === "/v1/messages") {
|
|
7660
8422
|
void handleMessages(req, res).catch((err) => {
|
|
7661
8423
|
log(`router internal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
7662
8424
|
if (!res.headersSent) {
|
|
@@ -7668,7 +8430,18 @@ function createRouterShim(opts) {
|
|
|
7668
8430
|
});
|
|
7669
8431
|
return;
|
|
7670
8432
|
}
|
|
7671
|
-
if (req.method === "
|
|
8433
|
+
if (req.method === "POST" && path39 === "/v1/messages/count_tokens") {
|
|
8434
|
+
void handleCountTokens(req, res).catch(() => {
|
|
8435
|
+
if (!res.headersSent)
|
|
8436
|
+
writeJson(res, 502, anthropicError(502, "api_error", "router internal error").body);
|
|
8437
|
+
});
|
|
8438
|
+
return;
|
|
8439
|
+
}
|
|
8440
|
+
if (req.method === "GET" && path39 === "/v1/models") {
|
|
8441
|
+
writeJson(res, 200, modelsList([...advertisedModels]));
|
|
8442
|
+
return;
|
|
8443
|
+
}
|
|
8444
|
+
if (req.method === "GET" && path39 === "/healthz") {
|
|
7672
8445
|
writeJson(res, 200, { ready: server?.listening === true, upstreamReachable, lastError });
|
|
7673
8446
|
return;
|
|
7674
8447
|
}
|
|
@@ -7793,12 +8566,17 @@ function createRouterService(router, projects, deps = {}) {
|
|
|
7793
8566
|
tokenByProject.set(p, t);
|
|
7794
8567
|
projectTokens.set(t, p);
|
|
7795
8568
|
}
|
|
8569
|
+
const models = [
|
|
8570
|
+
...deps.models ?? [],
|
|
8571
|
+
...Object.values(router.models ?? {}).map((m) => m.upstream)
|
|
8572
|
+
];
|
|
7796
8573
|
const ledger = createUsageLedger();
|
|
7797
8574
|
const shim = createRouterShim({
|
|
7798
8575
|
upstream,
|
|
7799
8576
|
projectTokens,
|
|
7800
8577
|
port: router.port ?? 0,
|
|
7801
8578
|
log,
|
|
8579
|
+
models,
|
|
7802
8580
|
onUsage: (u) => ledger.record(u),
|
|
7803
8581
|
...deps.fetch ? { fetch: deps.fetch } : {}
|
|
7804
8582
|
});
|
|
@@ -7861,12 +8639,53 @@ var init_router = __esm({
|
|
|
7861
8639
|
}
|
|
7862
8640
|
});
|
|
7863
8641
|
|
|
8642
|
+
// packages/core/dist/captain-router.js
|
|
8643
|
+
async function prepareCaptainRoute(o) {
|
|
8644
|
+
assertBackendUsable({ backend: o.backend, agent: o.agentName, router: o.config.defaults.router });
|
|
8645
|
+
if (o.backend === "native") {
|
|
8646
|
+
return { backend: "native", model: o.model, permissionMode: o.configuredPermissionMode, env: {} };
|
|
8647
|
+
}
|
|
8648
|
+
if (!o.deps.routerCredentials) {
|
|
8649
|
+
throw new Error(`captain launch: backend '${o.backend}' requires router credentials from the daemon, but this launch path has no credentials provider`);
|
|
8650
|
+
}
|
|
8651
|
+
if (!o.deps.writeRouterSettings) {
|
|
8652
|
+
throw new Error(`captain launch: backend '${o.backend}' requires a per-spawn --settings writer to outrank defaults.claudeEnv, but this launch path has none \u2014 refusing to launch a routed captain that would silently bypass the router shim`);
|
|
8653
|
+
}
|
|
8654
|
+
const model = resolveRouterModel(o.model, o.agentName, o.config.defaults.router);
|
|
8655
|
+
const creds = await o.deps.routerCredentials({ project: o.project, backend: o.backend });
|
|
8656
|
+
const routerEnv = buildRouterEnv(creds, model);
|
|
8657
|
+
const shadowed = claudeEnvShadowsRouter(o.config.defaults.claudeEnv);
|
|
8658
|
+
if (shadowed.length > 0) {
|
|
8659
|
+
o.warn?.(`backend '${o.backend}' selected but defaults.claudeEnv sets ${shadowed.join(", ")} \u2014 these would shadow the router shim. Overriding via a per-spawn --settings env block (outranks ~/.claude/settings.json).`);
|
|
8660
|
+
}
|
|
8661
|
+
const settingsPath = o.deps.writeRouterSettings({
|
|
8662
|
+
stateRoot: o.stateRoot,
|
|
8663
|
+
project: o.project,
|
|
8664
|
+
taskId: "captain",
|
|
8665
|
+
env: mergeClaudeEnvRouterSettings(routerEnv, o.config.defaults.claudeEnv)
|
|
8666
|
+
});
|
|
8667
|
+
return {
|
|
8668
|
+
backend: o.backend,
|
|
8669
|
+
model,
|
|
8670
|
+
permissionMode: "default",
|
|
8671
|
+
env: { ...routerEnv, SQUADRANT_GATE: "on" },
|
|
8672
|
+
settingsPath
|
|
8673
|
+
};
|
|
8674
|
+
}
|
|
8675
|
+
var init_captain_router = __esm({
|
|
8676
|
+
"packages/core/dist/captain-router.js"() {
|
|
8677
|
+
init_dist();
|
|
8678
|
+
init_router_resolution();
|
|
8679
|
+
init_env();
|
|
8680
|
+
}
|
|
8681
|
+
});
|
|
8682
|
+
|
|
7864
8683
|
// packages/core/dist/permission-gate.js
|
|
7865
8684
|
import { createHash } from "crypto";
|
|
7866
8685
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, renameSync as renameSync4, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
7867
8686
|
import { dirname as dirname5, join as join15 } from "path";
|
|
7868
8687
|
function isGateSession(env) {
|
|
7869
|
-
return Boolean(env.SQUADRANT_CREW_TASK_ID) || env[SIDE_SESSION_ENV] === "1";
|
|
8688
|
+
return Boolean(env.SQUADRANT_CREW_TASK_ID) || env[SIDE_SESSION_ENV] === "1" || env.SQUADRANT_ROLE === "captain";
|
|
7870
8689
|
}
|
|
7871
8690
|
function resolveGateMode(env, gate) {
|
|
7872
8691
|
const fromEnv = env.SQUADRANT_GATE;
|
|
@@ -8062,7 +8881,15 @@ async function callClassifier(o) {
|
|
|
8062
8881
|
return "ask";
|
|
8063
8882
|
}
|
|
8064
8883
|
const data = await res.json();
|
|
8884
|
+
if (data?.stop_reason === "max_tokens") {
|
|
8885
|
+
o.log?.("gate: classifier exhausted max_tokens before a verdict \u2014 asking");
|
|
8886
|
+
return "ask";
|
|
8887
|
+
}
|
|
8065
8888
|
const text = Array.isArray(data?.content) ? data.content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ") : "";
|
|
8889
|
+
if (!text.trim()) {
|
|
8890
|
+
o.log?.("gate: classifier returned no text verdict \u2014 asking");
|
|
8891
|
+
return "ask";
|
|
8892
|
+
}
|
|
8066
8893
|
return parseClassifierVerdict(text);
|
|
8067
8894
|
} catch (e) {
|
|
8068
8895
|
o.log?.(`gate: classifier unavailable (${e.message}) \u2014 asking`);
|
|
@@ -8081,7 +8908,7 @@ ${toolPayload}`).digest("hex");
|
|
|
8081
8908
|
}
|
|
8082
8909
|
async function evaluatePermissionRequest(input) {
|
|
8083
8910
|
if (!isGateSession(input.env)) {
|
|
8084
|
-
return { decision: "yield", reason: "not a squadrant crew/side session" };
|
|
8911
|
+
return { decision: "yield", reason: "not a squadrant crew/side/captain session" };
|
|
8085
8912
|
}
|
|
8086
8913
|
const gate = input.config.defaults.gate;
|
|
8087
8914
|
const mode = resolveGateMode(input.env, gate);
|
|
@@ -8167,7 +8994,7 @@ var init_permission_gate = __esm({
|
|
|
8167
8994
|
DEFAULT_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
8168
8995
|
DEFAULT_CACHE_MAX_ENTRIES = 500;
|
|
8169
8996
|
DEFAULT_CLASSIFIER_TIMEOUT_MS = 5e3;
|
|
8170
|
-
CLASSIFIER_MAX_TOKENS =
|
|
8997
|
+
CLASSIFIER_MAX_TOKENS = 256;
|
|
8171
8998
|
MAX_PAYLOAD_CHARS = 2e3;
|
|
8172
8999
|
DEFAULT_GATE_DENY_RULES = [
|
|
8173
9000
|
// Flag-order-agnostic: both recursive and force must be present, in any order,
|
|
@@ -8497,7 +9324,7 @@ var init_launch_workspace = __esm({
|
|
|
8497
9324
|
});
|
|
8498
9325
|
|
|
8499
9326
|
// packages/core/dist/side-session.js
|
|
8500
|
-
import
|
|
9327
|
+
import fs13 from "fs";
|
|
8501
9328
|
function sideTitleFor(project, name) {
|
|
8502
9329
|
return `\u{1F5D2} ${project}:${name}`;
|
|
8503
9330
|
}
|
|
@@ -8599,7 +9426,7 @@ async function runSideClose(runtime, workspaceId, project, name, projPath, workt
|
|
|
8599
9426
|
await runtime.closePane(pane);
|
|
8600
9427
|
if (projPath) {
|
|
8601
9428
|
const wtPath = worktreePath(projPath, worktreeDir, project, name);
|
|
8602
|
-
if (
|
|
9429
|
+
if (fs13.existsSync(wtPath)) {
|
|
8603
9430
|
try {
|
|
8604
9431
|
removeWorktree(projPath, wtPath);
|
|
8605
9432
|
} catch (e) {
|
|
@@ -9036,8 +9863,10 @@ __export(dist_exports2, {
|
|
|
9036
9863
|
AGENT_BINS: () => AGENT_BINS,
|
|
9037
9864
|
ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
|
|
9038
9865
|
BOT_COMMANDS: () => BOT_COMMANDS,
|
|
9866
|
+
CAPTAIN_RECEIVED_ACK: () => CAPTAIN_RECEIVED_ACK,
|
|
9039
9867
|
CC_SOCKS_DIR: () => CC_SOCKS_DIR,
|
|
9040
9868
|
CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
|
|
9869
|
+
CONFLICT_409_SELF_HEAL_MS: () => CONFLICT_409_SELF_HEAL_MS,
|
|
9041
9870
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
9042
9871
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
9043
9872
|
CaptainDelivery: () => CaptainDelivery,
|
|
@@ -9057,6 +9886,7 @@ __export(dist_exports2, {
|
|
|
9057
9886
|
LivenessRegistry: () => LivenessRegistry,
|
|
9058
9887
|
MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
|
|
9059
9888
|
OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
|
|
9889
|
+
PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
|
|
9060
9890
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
9061
9891
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
9062
9892
|
READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
|
|
@@ -9069,6 +9899,10 @@ __export(dist_exports2, {
|
|
|
9069
9899
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
9070
9900
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
9071
9901
|
TOOL_STALL_BUDGET_MS: () => TOOL_STALL_BUDGET_MS,
|
|
9902
|
+
TYPING_KEEPALIVE_MS: () => TYPING_KEEPALIVE_MS,
|
|
9903
|
+
TelegramApiError: () => TelegramApiError,
|
|
9904
|
+
WATCHDOG_GRACE_MS: () => WATCHDOG_GRACE_MS,
|
|
9905
|
+
WATCHDOG_MS: () => WATCHDOG_MS,
|
|
9072
9906
|
WORK_ITEM_TTL_MS: () => WORK_ITEM_TTL_MS,
|
|
9073
9907
|
WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
|
|
9074
9908
|
_resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
|
|
@@ -9090,14 +9924,19 @@ __export(dist_exports2, {
|
|
|
9090
9924
|
callClassifier: () => callClassifier,
|
|
9091
9925
|
capAllowed: () => capAllowed,
|
|
9092
9926
|
capOutput: () => capOutput,
|
|
9927
|
+
captainAliveStateFromHealth: () => captainAliveStateFromHealth,
|
|
9093
9928
|
captainRecordPath: () => captainRecordPath,
|
|
9094
9929
|
captainSocketPath: () => captainSocketPath,
|
|
9095
9930
|
checkFact: () => checkFact,
|
|
9931
|
+
classifyCaptainPane: () => classifyCaptainPane,
|
|
9096
9932
|
classifyHealth: () => classifyHealth,
|
|
9933
|
+
claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
|
|
9934
|
+
clearPending: () => clearPending,
|
|
9097
9935
|
closeWorkItem: () => closeWorkItem,
|
|
9098
9936
|
computeTemplateHash: () => computeTemplateHash,
|
|
9099
9937
|
consumeExitMarker: () => consumeExitMarker,
|
|
9100
9938
|
createAttach: () => createAttach,
|
|
9939
|
+
createCaptainPaneReader: () => createCaptainPaneReader,
|
|
9101
9940
|
createCrewPaneReader: () => createCrewPaneReader,
|
|
9102
9941
|
createDaemon: () => createDaemon,
|
|
9103
9942
|
createDecoder: () => createDecoder,
|
|
@@ -9106,6 +9945,7 @@ __export(dist_exports2, {
|
|
|
9106
9945
|
createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
|
|
9107
9946
|
createEnsureCaptainAlive: () => createEnsureCaptainAlive,
|
|
9108
9947
|
createEventsSource: () => createEventsSource,
|
|
9948
|
+
createInboundLifecycle: () => createInboundLifecycle,
|
|
9109
9949
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
9110
9950
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
9111
9951
|
createLaunch: () => createLaunch,
|
|
@@ -9161,12 +10001,16 @@ __export(dist_exports2, {
|
|
|
9161
10001
|
formatInbound: () => formatInbound,
|
|
9162
10002
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
9163
10003
|
formatLifecycle: () => formatLifecycle,
|
|
10004
|
+
formatMediaReceipt: () => formatMediaReceipt,
|
|
9164
10005
|
formatPermissionDecision: () => formatPermissionDecision,
|
|
9165
10006
|
formatUsageLine: () => formatUsageLine,
|
|
10007
|
+
formatWatchdogWarning: () => formatWatchdogWarning,
|
|
9166
10008
|
freshTrace: () => freshTrace,
|
|
9167
10009
|
gateCacheKey: () => gateCacheKey,
|
|
9168
10010
|
getDaemonPid: () => getDaemonPid,
|
|
10011
|
+
hasModalOptionList: () => hasModalOptionList,
|
|
9169
10012
|
healCmdFor: () => healCmdFor,
|
|
10013
|
+
inboundBody: () => inboundBody,
|
|
9170
10014
|
isAuthorized: () => isAuthorized,
|
|
9171
10015
|
isBareSpawn: () => isBareSpawn,
|
|
9172
10016
|
isCaptainAlive: () => isCaptainAlive,
|
|
@@ -9188,17 +10032,23 @@ __export(dist_exports2, {
|
|
|
9188
10032
|
kickstartArgv: () => kickstartArgv,
|
|
9189
10033
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
9190
10034
|
listSessions: () => listSessions,
|
|
10035
|
+
loadPending: () => loadPending,
|
|
9191
10036
|
loadSessions: () => loadSessions,
|
|
9192
10037
|
loadState: () => loadState,
|
|
9193
10038
|
mailboxStats: () => mailboxStats,
|
|
9194
10039
|
makeGate: () => makeGate,
|
|
10040
|
+
markPendingWarned: () => markPendingWarned,
|
|
9195
10041
|
maskToken: () => maskToken,
|
|
9196
10042
|
matchTier1Deny: () => matchTier1Deny,
|
|
10043
|
+
mediaKind: () => mediaKind,
|
|
10044
|
+
mediaMarker: () => mediaMarker,
|
|
10045
|
+
mergeClaudeEnvRouterSettings: () => mergeClaudeEnvRouterSettings,
|
|
9197
10046
|
mintToken: () => mintToken,
|
|
9198
10047
|
nameFromTitle: () => nameFromTitle,
|
|
9199
10048
|
newestSessionInDirectory: () => newestSessionInDirectory,
|
|
9200
10049
|
nextAutoName: () => nextAutoName,
|
|
9201
10050
|
niceCrewCommand: () => niceCrewCommand,
|
|
10051
|
+
notePaneScreen: () => notePaneScreen,
|
|
9202
10052
|
notifyToggle: () => notifyToggle,
|
|
9203
10053
|
parseBearer: () => parseBearer,
|
|
9204
10054
|
parseClassifierVerdict: () => parseClassifierVerdict,
|
|
@@ -9207,12 +10057,15 @@ __export(dist_exports2, {
|
|
|
9207
10057
|
parseNotifyPref: () => parseNotifyPref,
|
|
9208
10058
|
parseProgramArgs: () => parseProgramArgs,
|
|
9209
10059
|
plistPath: () => plistPath,
|
|
10060
|
+
pollBackoffMs: () => pollBackoffMs,
|
|
9210
10061
|
pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
|
|
10062
|
+
prepareCaptainRoute: () => prepareCaptainRoute,
|
|
9211
10063
|
printForeignInstallError: () => printForeignInstallError,
|
|
9212
10064
|
printVersionUpgradeNotice: () => printVersionUpgradeNotice,
|
|
9213
10065
|
probeCaptainChannel: () => probeCaptainChannel,
|
|
9214
10066
|
programArgsBlock: () => programArgsBlock,
|
|
9215
10067
|
projectHealth: () => projectHealth,
|
|
10068
|
+
pruneTopics: () => pruneTopics,
|
|
9216
10069
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
9217
10070
|
readCaptainAddress: () => readCaptainAddress,
|
|
9218
10071
|
readCursor: () => readCursor,
|
|
@@ -9279,9 +10132,11 @@ __export(dist_exports2, {
|
|
|
9279
10132
|
saveSessions: () => saveSessions,
|
|
9280
10133
|
saveState: () => saveState,
|
|
9281
10134
|
screenHasSplashMarker: () => screenHasSplashMarker,
|
|
10135
|
+
screenHash: () => screenHash,
|
|
9282
10136
|
sendRequest: () => sendRequest,
|
|
9283
10137
|
setLastUserId: () => setLastUserId,
|
|
9284
10138
|
setNotify: () => setNotify,
|
|
10139
|
+
setPending: () => setPending,
|
|
9285
10140
|
setTopic: () => setTopic,
|
|
9286
10141
|
shellQuote: () => shellQuote,
|
|
9287
10142
|
shouldBuildRouterService: () => shouldBuildRouterService,
|
|
@@ -9294,6 +10149,7 @@ __export(dist_exports2, {
|
|
|
9294
10149
|
startServer: () => startServer,
|
|
9295
10150
|
stripBotMention: () => stripBotMention,
|
|
9296
10151
|
surfaceVerdict: () => surfaceVerdict,
|
|
10152
|
+
telegramPollLockPath: () => telegramPollLockPath,
|
|
9297
10153
|
timeoutGate: () => timeoutGate,
|
|
9298
10154
|
titleFor: () => titleFor,
|
|
9299
10155
|
toControlEvent: () => toControlEvent,
|
|
@@ -9340,6 +10196,7 @@ var init_dist2 = __esm({
|
|
|
9340
10196
|
init_router();
|
|
9341
10197
|
init_crew_routing();
|
|
9342
10198
|
init_router_resolution();
|
|
10199
|
+
init_captain_router();
|
|
9343
10200
|
init_permission_gate();
|
|
9344
10201
|
init_restart_daemon();
|
|
9345
10202
|
init_group_dispatch();
|
|
@@ -9362,7 +10219,7 @@ var init_dist2 = __esm({
|
|
|
9362
10219
|
});
|
|
9363
10220
|
|
|
9364
10221
|
// packages/agents/dist/claude/registry.js
|
|
9365
|
-
import
|
|
10222
|
+
import fs14 from "fs";
|
|
9366
10223
|
import { join as join17 } from "path";
|
|
9367
10224
|
import { homedir as homedir7 } from "os";
|
|
9368
10225
|
function parseRegistryDir(files, readFile7) {
|
|
@@ -9407,11 +10264,11 @@ function readClaudeStatus(task) {
|
|
|
9407
10264
|
return void 0;
|
|
9408
10265
|
let files;
|
|
9409
10266
|
try {
|
|
9410
|
-
files =
|
|
10267
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
9411
10268
|
} catch {
|
|
9412
10269
|
return void 0;
|
|
9413
10270
|
}
|
|
9414
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
10271
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
9415
10272
|
let entry;
|
|
9416
10273
|
if (task.messagingSocketPath) {
|
|
9417
10274
|
entry = entries.find((e) => e.messagingSocketPath === task.messagingSocketPath);
|
|
@@ -9426,11 +10283,11 @@ function readClaudeStatus(task) {
|
|
|
9426
10283
|
function readClaudeStatusByCwd(cwd) {
|
|
9427
10284
|
let files;
|
|
9428
10285
|
try {
|
|
9429
|
-
files =
|
|
10286
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
9430
10287
|
} catch {
|
|
9431
10288
|
return void 0;
|
|
9432
10289
|
}
|
|
9433
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
10290
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
9434
10291
|
const entry = entries.find((e) => e.cwd === cwd);
|
|
9435
10292
|
if (!entry)
|
|
9436
10293
|
return void 0;
|
|
@@ -9439,11 +10296,11 @@ function readClaudeStatusByCwd(cwd) {
|
|
|
9439
10296
|
function readClaudeStatusBySocketPath(socketPath2) {
|
|
9440
10297
|
let files;
|
|
9441
10298
|
try {
|
|
9442
|
-
files =
|
|
10299
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
9443
10300
|
} catch {
|
|
9444
10301
|
return void 0;
|
|
9445
10302
|
}
|
|
9446
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
10303
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
9447
10304
|
const entry = entries.find((e) => e.messagingSocketPath === socketPath2);
|
|
9448
10305
|
if (!entry)
|
|
9449
10306
|
return void 0;
|
|
@@ -9866,9 +10723,9 @@ var init_registry2 = __esm({
|
|
|
9866
10723
|
});
|
|
9867
10724
|
|
|
9868
10725
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
9869
|
-
import
|
|
10726
|
+
import fs15 from "fs";
|
|
9870
10727
|
import path13 from "path";
|
|
9871
|
-
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot) {
|
|
10728
|
+
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot, settingsPath) {
|
|
9872
10729
|
const driver = registry.getDriver(agentName);
|
|
9873
10730
|
if (driver.name === "claude") {
|
|
9874
10731
|
let cmd = fresh ? "claude" : "claude -c";
|
|
@@ -9881,6 +10738,8 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
9881
10738
|
cmd += " --permission-mode auto";
|
|
9882
10739
|
} else if (permissionMode === "bypassPermissions") {
|
|
9883
10740
|
cmd += " --dangerously-skip-permissions";
|
|
10741
|
+
} else if (permissionMode === "default") {
|
|
10742
|
+
cmd += " --permission-mode default";
|
|
9884
10743
|
}
|
|
9885
10744
|
if (model) {
|
|
9886
10745
|
cmd += ` --model ${model}`;
|
|
@@ -9888,15 +10747,18 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
9888
10747
|
if (thinking) {
|
|
9889
10748
|
cmd += ` --effort ${thinking}`;
|
|
9890
10749
|
}
|
|
10750
|
+
if (settingsPath) {
|
|
10751
|
+
cmd += ` --settings ${settingsPath}`;
|
|
10752
|
+
}
|
|
9891
10753
|
if (templatesDir) {
|
|
9892
10754
|
const roleFile2 = path13.join(templatesDir, `${role}.claude.md`);
|
|
9893
10755
|
const legacyRoleFile = path13.join(templatesDir, `${role}.CLAUDE.md`);
|
|
9894
|
-
const actualRoleFile =
|
|
10756
|
+
const actualRoleFile = fs15.existsSync(roleFile2) ? roleFile2 : fs15.existsSync(legacyRoleFile) ? legacyRoleFile : null;
|
|
9895
10757
|
if (actualRoleFile) {
|
|
9896
10758
|
cmd += ` --append-system-prompt-file ${actualRoleFile}`;
|
|
9897
10759
|
}
|
|
9898
10760
|
const pluginDir = path13.join(templatesDir, "..", "plugin");
|
|
9899
|
-
if (
|
|
10761
|
+
if (fs15.existsSync(pluginDir)) {
|
|
9900
10762
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
9901
10763
|
}
|
|
9902
10764
|
}
|
|
@@ -9912,7 +10774,7 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
9912
10774
|
role,
|
|
9913
10775
|
model,
|
|
9914
10776
|
autoApprove: true,
|
|
9915
|
-
promptFile: roleFile &&
|
|
10777
|
+
promptFile: roleFile && fs15.existsSync(roleFile) ? roleFile : void 0,
|
|
9916
10778
|
...captainBoot && role === "captain" ? {
|
|
9917
10779
|
interactive: true,
|
|
9918
10780
|
...captainBoot.port ? { port: captainBoot.port } : {},
|
|
@@ -10284,7 +11146,7 @@ var init_opencode2 = __esm({
|
|
|
10284
11146
|
});
|
|
10285
11147
|
|
|
10286
11148
|
// packages/agents/dist/projection/opencode-skills.js
|
|
10287
|
-
import
|
|
11149
|
+
import fs16 from "fs";
|
|
10288
11150
|
import os10 from "os";
|
|
10289
11151
|
import path18 from "path";
|
|
10290
11152
|
function defaultOpencodeSkillsRoot(env = process.env) {
|
|
@@ -10303,62 +11165,62 @@ function parseSkillName(raw) {
|
|
|
10303
11165
|
}
|
|
10304
11166
|
function listSourceSkills(sourceSkillsDir) {
|
|
10305
11167
|
const out = /* @__PURE__ */ new Map();
|
|
10306
|
-
if (!
|
|
11168
|
+
if (!fs16.existsSync(sourceSkillsDir))
|
|
10307
11169
|
return out;
|
|
10308
|
-
for (const entry of
|
|
11170
|
+
for (const entry of fs16.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
|
|
10309
11171
|
if (!entry.isDirectory())
|
|
10310
11172
|
continue;
|
|
10311
11173
|
const skillPath = path18.join(sourceSkillsDir, entry.name, "SKILL.md");
|
|
10312
|
-
if (!
|
|
11174
|
+
if (!fs16.existsSync(skillPath))
|
|
10313
11175
|
continue;
|
|
10314
|
-
const name = parseSkillName(
|
|
11176
|
+
const name = parseSkillName(fs16.readFileSync(skillPath, "utf-8"));
|
|
10315
11177
|
if (name && SAFE_NAME.test(name))
|
|
10316
11178
|
out.set(name, skillPath);
|
|
10317
11179
|
}
|
|
10318
11180
|
return out;
|
|
10319
11181
|
}
|
|
10320
11182
|
function hasMarker(dir) {
|
|
10321
|
-
return
|
|
11183
|
+
return fs16.existsSync(path18.join(dir, OPENCODE_SKILL_MARKER));
|
|
10322
11184
|
}
|
|
10323
11185
|
function syncOpencodeSkills(opts) {
|
|
10324
11186
|
const result = { written: [], unchanged: [], removed: [], skipped: [] };
|
|
10325
11187
|
const wanted = listSourceSkills(opts.sourceSkillsDir);
|
|
10326
|
-
if (wanted.size === 0 && !
|
|
11188
|
+
if (wanted.size === 0 && !fs16.existsSync(opts.sourceSkillsDir))
|
|
10327
11189
|
return result;
|
|
10328
11190
|
if (!opts.dryRun)
|
|
10329
|
-
|
|
11191
|
+
fs16.mkdirSync(opts.skillsRoot, { recursive: true });
|
|
10330
11192
|
for (const [name, srcPath] of wanted) {
|
|
10331
11193
|
const targetDir = path18.join(opts.skillsRoot, name);
|
|
10332
11194
|
const targetSkill = path18.join(targetDir, "SKILL.md");
|
|
10333
11195
|
const managed = hasMarker(targetDir);
|
|
10334
|
-
if (
|
|
11196
|
+
if (fs16.existsSync(targetSkill) && !managed) {
|
|
10335
11197
|
result.skipped.push({ name, reason: "foreign-collision" });
|
|
10336
11198
|
continue;
|
|
10337
11199
|
}
|
|
10338
|
-
const current =
|
|
10339
|
-
if (current && current.equals(
|
|
11200
|
+
const current = fs16.existsSync(targetSkill) ? fs16.readFileSync(targetSkill) : null;
|
|
11201
|
+
if (current && current.equals(fs16.readFileSync(srcPath))) {
|
|
10340
11202
|
if (!managed && !opts.dryRun) {
|
|
10341
|
-
|
|
11203
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
10342
11204
|
}
|
|
10343
11205
|
result.unchanged.push(name);
|
|
10344
11206
|
continue;
|
|
10345
11207
|
}
|
|
10346
11208
|
if (!opts.dryRun) {
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
11209
|
+
fs16.mkdirSync(targetDir, { recursive: true });
|
|
11210
|
+
fs16.copyFileSync(srcPath, targetSkill);
|
|
11211
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
10350
11212
|
}
|
|
10351
11213
|
result.written.push(name);
|
|
10352
11214
|
}
|
|
10353
|
-
if (
|
|
10354
|
-
for (const entry of
|
|
11215
|
+
if (fs16.existsSync(opts.skillsRoot)) {
|
|
11216
|
+
for (const entry of fs16.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
|
|
10355
11217
|
if (!entry.isDirectory() || wanted.has(entry.name))
|
|
10356
11218
|
continue;
|
|
10357
11219
|
const dir = path18.join(opts.skillsRoot, entry.name);
|
|
10358
11220
|
if (!hasMarker(dir))
|
|
10359
11221
|
continue;
|
|
10360
11222
|
if (!opts.dryRun)
|
|
10361
|
-
|
|
11223
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
10362
11224
|
result.removed.push(entry.name);
|
|
10363
11225
|
}
|
|
10364
11226
|
}
|
|
@@ -12167,6 +13029,9 @@ var init_peer_channel = __esm({
|
|
|
12167
13029
|
return { status: "accepted", via: this.name, confirmed: true };
|
|
12168
13030
|
}
|
|
12169
13031
|
}
|
|
13032
|
+
if (statusBefore === "busy" || statusBefore === "shell") {
|
|
13033
|
+
return { status: "queued", via: this.name };
|
|
13034
|
+
}
|
|
12170
13035
|
if (statusBefore === "idle") {
|
|
12171
13036
|
await this.deps.sleep(window);
|
|
12172
13037
|
const afterInfo = this.deps.statusFor(taskId);
|
|
@@ -12312,7 +13177,7 @@ var init_receipt_listener = __esm({
|
|
|
12312
13177
|
});
|
|
12313
13178
|
|
|
12314
13179
|
// packages/agents/dist/claude/api-key-approval.js
|
|
12315
|
-
import
|
|
13180
|
+
import fs17 from "fs";
|
|
12316
13181
|
import os12 from "os";
|
|
12317
13182
|
import path20 from "path";
|
|
12318
13183
|
function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
|
|
@@ -12322,12 +13187,12 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
|
|
|
12322
13187
|
const file = opts.claudeJsonPath ?? path20.join(os12.homedir(), ".claude.json");
|
|
12323
13188
|
const log = opts.log ?? (() => {
|
|
12324
13189
|
});
|
|
12325
|
-
if (!
|
|
13190
|
+
if (!fs17.existsSync(file)) {
|
|
12326
13191
|
return { changed: false, reason: "no ~/.claude.json yet \u2014 skipping routed-key pre-approval" };
|
|
12327
13192
|
}
|
|
12328
13193
|
let doc;
|
|
12329
13194
|
try {
|
|
12330
|
-
doc = JSON.parse(
|
|
13195
|
+
doc = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
12331
13196
|
} catch {
|
|
12332
13197
|
return {
|
|
12333
13198
|
changed: false,
|
|
@@ -12349,11 +13214,11 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
|
|
|
12349
13214
|
};
|
|
12350
13215
|
const tmp = `${file}.squadrant-tmp`;
|
|
12351
13216
|
try {
|
|
12352
|
-
|
|
12353
|
-
|
|
13217
|
+
fs17.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs17.statSync(file).mode & 511 });
|
|
13218
|
+
fs17.renameSync(tmp, file);
|
|
12354
13219
|
} catch (e) {
|
|
12355
13220
|
try {
|
|
12356
|
-
|
|
13221
|
+
fs17.unlinkSync(tmp);
|
|
12357
13222
|
} catch {
|
|
12358
13223
|
}
|
|
12359
13224
|
return {
|
|
@@ -12709,11 +13574,11 @@ function parseDraftFromScreen(screen) {
|
|
|
12709
13574
|
if (!screen)
|
|
12710
13575
|
return null;
|
|
12711
13576
|
const lines = screen.split(/\r?\n/);
|
|
12712
|
-
const
|
|
13577
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12713
13578
|
let bottomHR = -1;
|
|
12714
13579
|
let topHR = -1;
|
|
12715
13580
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12716
|
-
if (
|
|
13581
|
+
if (HR_RE2.test(lines[i])) {
|
|
12717
13582
|
if (bottomHR === -1) {
|
|
12718
13583
|
bottomHR = i;
|
|
12719
13584
|
} else {
|
|
@@ -12751,11 +13616,11 @@ function hasCCInputBox(screen) {
|
|
|
12751
13616
|
if (!screen)
|
|
12752
13617
|
return false;
|
|
12753
13618
|
const lines = screen.split(/\r?\n/);
|
|
12754
|
-
const
|
|
13619
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12755
13620
|
let bottomHR = -1;
|
|
12756
13621
|
let topHR = -1;
|
|
12757
13622
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12758
|
-
if (
|
|
13623
|
+
if (HR_RE2.test(lines[i])) {
|
|
12759
13624
|
if (bottomHR === -1)
|
|
12760
13625
|
bottomHR = i;
|
|
12761
13626
|
else {
|
|
@@ -12768,15 +13633,15 @@ function hasCCInputBox(screen) {
|
|
|
12768
13633
|
return false;
|
|
12769
13634
|
return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));
|
|
12770
13635
|
}
|
|
12771
|
-
function
|
|
13636
|
+
function hasModalOptionList2(screen) {
|
|
12772
13637
|
if (!screen)
|
|
12773
13638
|
return false;
|
|
12774
13639
|
const lines = screen.split(/\r?\n/);
|
|
12775
|
-
const
|
|
13640
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12776
13641
|
let bottomHR = -1;
|
|
12777
13642
|
let topHR = -1;
|
|
12778
13643
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12779
|
-
if (
|
|
13644
|
+
if (HR_RE2.test(lines[i])) {
|
|
12780
13645
|
if (bottomHR === -1)
|
|
12781
13646
|
bottomHR = i;
|
|
12782
13647
|
else {
|
|
@@ -12790,14 +13655,14 @@ function hasModalOptionList(screen) {
|
|
|
12790
13655
|
return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
|
|
12791
13656
|
}
|
|
12792
13657
|
function parseModalOptions(screen) {
|
|
12793
|
-
if (!
|
|
13658
|
+
if (!hasModalOptionList2(screen))
|
|
12794
13659
|
return null;
|
|
12795
13660
|
const lines = screen.split(/\r?\n/);
|
|
12796
|
-
const
|
|
13661
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12797
13662
|
let bottomHR = -1;
|
|
12798
13663
|
let topHR = -1;
|
|
12799
13664
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12800
|
-
if (
|
|
13665
|
+
if (HR_RE2.test(lines[i])) {
|
|
12801
13666
|
if (bottomHR === -1)
|
|
12802
13667
|
bottomHR = i;
|
|
12803
13668
|
else {
|
|
@@ -12822,11 +13687,11 @@ function readInputBoxRaw(screen, opts) {
|
|
|
12822
13687
|
if (!screen)
|
|
12823
13688
|
return null;
|
|
12824
13689
|
const lines = screen.split(/\r?\n/);
|
|
12825
|
-
const
|
|
13690
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12826
13691
|
let bottomHR = -1;
|
|
12827
13692
|
let topHR = -1;
|
|
12828
13693
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12829
|
-
if (
|
|
13694
|
+
if (HR_RE2.test(lines[i])) {
|
|
12830
13695
|
if (bottomHR === -1)
|
|
12831
13696
|
bottomHR = i;
|
|
12832
13697
|
else {
|
|
@@ -12859,6 +13724,30 @@ function classifyOpencodeStartupSurface(screen) {
|
|
|
12859
13724
|
return "loading";
|
|
12860
13725
|
return OC_IDLE_MARKERS.some((m) => screenHasSplashMarker(screen, m)) ? "idle" : "loading";
|
|
12861
13726
|
}
|
|
13727
|
+
function parseOpencodeDraftFromScreen(screen) {
|
|
13728
|
+
if (!screen)
|
|
13729
|
+
return null;
|
|
13730
|
+
if (OC_BUSY_RE.test(screen))
|
|
13731
|
+
return null;
|
|
13732
|
+
const lines = screen.split(/\r?\n/);
|
|
13733
|
+
let bottom = -1;
|
|
13734
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
13735
|
+
if (OC_BOX_BOTTOM_RE.test(lines[i])) {
|
|
13736
|
+
bottom = i;
|
|
13737
|
+
break;
|
|
13738
|
+
}
|
|
13739
|
+
}
|
|
13740
|
+
if (bottom === -1)
|
|
13741
|
+
return null;
|
|
13742
|
+
const content = [];
|
|
13743
|
+
for (let i = bottom - 1; i >= 0 && OC_BOX_SIDE_RE.test(lines[i]); i--) {
|
|
13744
|
+
content.push(lines[i].replace(/^\s*┃\s*/, "").replace(/[\s┃│]+$/, ""));
|
|
13745
|
+
}
|
|
13746
|
+
if (content.length === 0)
|
|
13747
|
+
return null;
|
|
13748
|
+
const hasDraft = content.some((l) => l !== "" && !OC_BOX_STATUS_RE.test(l) && !screenHasSplashMarker(l, "Ask anything"));
|
|
13749
|
+
return hasDraft ? null : "";
|
|
13750
|
+
}
|
|
12862
13751
|
function sendDebugEnabled() {
|
|
12863
13752
|
return !!process.env.SQUADRANT_DEBUG_SEND;
|
|
12864
13753
|
}
|
|
@@ -13074,10 +13963,10 @@ function createCmuxDriver() {
|
|
|
13074
13963
|
`);
|
|
13075
13964
|
throw new DeferDelivery(null, "probe-failed");
|
|
13076
13965
|
}
|
|
13077
|
-
const draft = parseDraftFromScreen(screen);
|
|
13966
|
+
const draft = opts?.agent === "opencode" ? parseOpencodeDraftFromScreen(screen) : parseDraftFromScreen(screen);
|
|
13078
13967
|
if (draft === null)
|
|
13079
13968
|
throw new DeferDelivery(null, "no-box");
|
|
13080
|
-
if (
|
|
13969
|
+
if (hasModalOptionList2(screen))
|
|
13081
13970
|
throw new DeferDelivery(null, "modal");
|
|
13082
13971
|
if (draft === "") {
|
|
13083
13972
|
await deliver();
|
|
@@ -13181,7 +14070,7 @@ function createCmuxDriver() {
|
|
|
13181
14070
|
}
|
|
13182
14071
|
};
|
|
13183
14072
|
}
|
|
13184
|
-
var CMUX_TIMEOUT, CmuxTimeoutError, CC_INITIALIZED_RE, CC_WORKING_RE, OC_IDLE_MARKERS;
|
|
14073
|
+
var CMUX_TIMEOUT, CmuxTimeoutError, CC_INITIALIZED_RE, CC_WORKING_RE, OC_IDLE_MARKERS, OC_BOX_BOTTOM_RE, OC_BOX_SIDE_RE, OC_BOX_STATUS_RE, OC_BUSY_RE;
|
|
13185
14074
|
var init_cmux = __esm({
|
|
13186
14075
|
"packages/workspaces/dist/runtimes/cmux.js"() {
|
|
13187
14076
|
init_dist();
|
|
@@ -13199,6 +14088,10 @@ var init_cmux = __esm({
|
|
|
13199
14088
|
CC_INITIALIZED_RE = /⏵⏵|Ctx Used|for shortcuts|accept edits/i;
|
|
13200
14089
|
CC_WORKING_RE = /↓\s*[\d.]+\s*k?\s*tokens?\b|esc to interrupt|\bshell still running\b|·\s*\d+\s*shell\b|\(\d+m?\s*\d*s\b/i;
|
|
13201
14090
|
OC_IDLE_MARKERS = ["Ask anything", "ctrl+p commands"];
|
|
14091
|
+
OC_BOX_BOTTOM_RE = /^\s*╹[▀]{6,}\s*$/;
|
|
14092
|
+
OC_BOX_SIDE_RE = /^\s*┃/;
|
|
14093
|
+
OC_BOX_STATUS_RE = /^Build\s*·/i;
|
|
14094
|
+
OC_BUSY_RE = /esc\s+interrupt/i;
|
|
13202
14095
|
}
|
|
13203
14096
|
});
|
|
13204
14097
|
|
|
@@ -13323,7 +14216,7 @@ var init_notifiers = __esm({
|
|
|
13323
14216
|
});
|
|
13324
14217
|
|
|
13325
14218
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
13326
|
-
import
|
|
14219
|
+
import fs18 from "fs/promises";
|
|
13327
14220
|
import { existsSync as existsSync11 } from "fs";
|
|
13328
14221
|
import path21 from "path";
|
|
13329
14222
|
function resolveInRoot(root, relative) {
|
|
@@ -13348,16 +14241,16 @@ function createObsidianDriver(scope) {
|
|
|
13348
14241
|
};
|
|
13349
14242
|
},
|
|
13350
14243
|
async read(rel) {
|
|
13351
|
-
return
|
|
14244
|
+
return fs18.readFile(resolveInRoot(root, rel), "utf-8");
|
|
13352
14245
|
},
|
|
13353
14246
|
async write(rel, content) {
|
|
13354
14247
|
const abs = resolveInRoot(root, rel);
|
|
13355
|
-
await
|
|
13356
|
-
await
|
|
14248
|
+
await fs18.mkdir(path21.dirname(abs), { recursive: true });
|
|
14249
|
+
await fs18.writeFile(abs, content);
|
|
13357
14250
|
},
|
|
13358
14251
|
async exists(rel) {
|
|
13359
14252
|
try {
|
|
13360
|
-
await
|
|
14253
|
+
await fs18.access(resolveInRoot(root, rel));
|
|
13361
14254
|
return true;
|
|
13362
14255
|
} catch {
|
|
13363
14256
|
return false;
|
|
@@ -13365,13 +14258,13 @@ function createObsidianDriver(scope) {
|
|
|
13365
14258
|
},
|
|
13366
14259
|
async list(rel) {
|
|
13367
14260
|
try {
|
|
13368
|
-
return await
|
|
14261
|
+
return await fs18.readdir(resolveInRoot(root, rel));
|
|
13369
14262
|
} catch {
|
|
13370
14263
|
return [];
|
|
13371
14264
|
}
|
|
13372
14265
|
},
|
|
13373
14266
|
async mkdir(rel) {
|
|
13374
|
-
await
|
|
14267
|
+
await fs18.mkdir(resolveInRoot(root, rel), { recursive: true });
|
|
13375
14268
|
}
|
|
13376
14269
|
};
|
|
13377
14270
|
}
|
|
@@ -14269,7 +15162,7 @@ async function resolveCaptainWorkspace(project) {
|
|
|
14269
15162
|
}
|
|
14270
15163
|
async function paneHasOpenModal(runtime, pane) {
|
|
14271
15164
|
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
14272
|
-
return
|
|
15165
|
+
return hasModalOptionList2(screen);
|
|
14273
15166
|
}
|
|
14274
15167
|
async function readModalOptions(runtime, pane) {
|
|
14275
15168
|
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
@@ -14277,7 +15170,7 @@ async function readModalOptions(runtime, pane) {
|
|
|
14277
15170
|
}
|
|
14278
15171
|
async function confirmedSendToPane(runtime, pane, message) {
|
|
14279
15172
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
14280
|
-
if (
|
|
15173
|
+
if (hasModalOptionList2(preSendScreen)) {
|
|
14281
15174
|
return { delivered: false, blockedByModal: true };
|
|
14282
15175
|
}
|
|
14283
15176
|
await runtime.pasteToPane(pane, message);
|
|
@@ -14530,9 +15423,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
|
14530
15423
|
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14531
15424
|
await needRef(resolved);
|
|
14532
15425
|
const finalProject = opts.command ? config.commandName : target;
|
|
14533
|
-
const { join:
|
|
15426
|
+
const { join: join36, dirname: dirname12 } = await import("path");
|
|
14534
15427
|
const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
14535
|
-
const stateRoot =
|
|
15428
|
+
const stateRoot = join36(dirname12(DEFAULT_CONFIG_PATH2), "state");
|
|
14536
15429
|
const seq = await appendCaptainMessage2({
|
|
14537
15430
|
stateRoot,
|
|
14538
15431
|
project: finalProject,
|
|
@@ -14641,7 +15534,7 @@ init_dist3();
|
|
|
14641
15534
|
import { Command as Command38 } from "commander";
|
|
14642
15535
|
import { existsSync as existsSync14, readFileSync as readFileSync21 } from "fs";
|
|
14643
15536
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
14644
|
-
import { dirname as
|
|
15537
|
+
import { dirname as dirname11, join as join35 } from "path";
|
|
14645
15538
|
import { homedir as homedir20 } from "os";
|
|
14646
15539
|
|
|
14647
15540
|
// packages/cli/src/commands/doctor.ts
|
|
@@ -14651,7 +15544,7 @@ init_dist();
|
|
|
14651
15544
|
init_dist4();
|
|
14652
15545
|
import { Command } from "commander";
|
|
14653
15546
|
import { execSync as execSync8 } from "child_process";
|
|
14654
|
-
import
|
|
15547
|
+
import fs19 from "fs";
|
|
14655
15548
|
import { stat } from "fs/promises";
|
|
14656
15549
|
import path22 from "path";
|
|
14657
15550
|
import chalk3 from "chalk";
|
|
@@ -14755,7 +15648,7 @@ function settingsHaveAgentTeams() {
|
|
|
14755
15648
|
try {
|
|
14756
15649
|
const home = process.env.HOME || "";
|
|
14757
15650
|
const settings = JSON.parse(
|
|
14758
|
-
|
|
15651
|
+
fs19.readFileSync(`${home}/.claude/settings.json`, "utf-8")
|
|
14759
15652
|
);
|
|
14760
15653
|
return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
14761
15654
|
} catch {
|
|
@@ -14766,7 +15659,7 @@ function pluginInstalled(pluginKey) {
|
|
|
14766
15659
|
try {
|
|
14767
15660
|
const home = process.env.HOME || "";
|
|
14768
15661
|
const plugins = JSON.parse(
|
|
14769
|
-
|
|
15662
|
+
fs19.readFileSync(
|
|
14770
15663
|
`${home}/.claude/plugins/installed_plugins.json`,
|
|
14771
15664
|
"utf-8"
|
|
14772
15665
|
)
|
|
@@ -14812,7 +15705,7 @@ ${lines.join("\n")}
|
|
|
14812
15705
|
}
|
|
14813
15706
|
function readPackageVersion(packageJsonPath) {
|
|
14814
15707
|
try {
|
|
14815
|
-
const pkg2 = JSON.parse(
|
|
15708
|
+
const pkg2 = JSON.parse(fs19.readFileSync(packageJsonPath, "utf-8"));
|
|
14816
15709
|
return typeof pkg2.version === "string" ? pkg2.version : null;
|
|
14817
15710
|
} catch {
|
|
14818
15711
|
return null;
|
|
@@ -14848,7 +15741,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
14848
15741
|
));
|
|
14849
15742
|
results.push(check(
|
|
14850
15743
|
"Obsidian installed",
|
|
14851
|
-
commandExists("obsidian") ||
|
|
15744
|
+
commandExists("obsidian") || fs19.existsSync("/Applications/Obsidian.app"),
|
|
14852
15745
|
"Install from: https://obsidian.md"
|
|
14853
15746
|
));
|
|
14854
15747
|
results.push(check(
|
|
@@ -14956,7 +15849,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
14956
15849
|
results.push(
|
|
14957
15850
|
check(
|
|
14958
15851
|
"Squadrant config exists",
|
|
14959
|
-
|
|
15852
|
+
fs19.existsSync(
|
|
14960
15853
|
process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
|
|
14961
15854
|
),
|
|
14962
15855
|
"Run: squadrant init"
|
|
@@ -15040,7 +15933,7 @@ init_dist();
|
|
|
15040
15933
|
init_dist4();
|
|
15041
15934
|
init_dist();
|
|
15042
15935
|
import { Command as Command2 } from "commander";
|
|
15043
|
-
import
|
|
15936
|
+
import fs20 from "fs";
|
|
15044
15937
|
import path23 from "path";
|
|
15045
15938
|
import os13 from "os";
|
|
15046
15939
|
import readline from "readline";
|
|
@@ -15140,6 +16033,13 @@ function mergeCrewPermissions(settings) {
|
|
|
15140
16033
|
permissions.allow = [.../* @__PURE__ */ new Set([...existing, ...CREW_PERMISSION_ALLOWLIST])];
|
|
15141
16034
|
return next;
|
|
15142
16035
|
}
|
|
16036
|
+
function writeRouterSettings(o) {
|
|
16037
|
+
const dir = join26(o.stateRoot, o.project, o.taskId);
|
|
16038
|
+
mkdirSync9(dir, { recursive: true });
|
|
16039
|
+
const file = join26(dir, "router-settings.json");
|
|
16040
|
+
writeFileSync12(file, JSON.stringify({ env: o.env }, null, 2));
|
|
16041
|
+
return file;
|
|
16042
|
+
}
|
|
15143
16043
|
function writePerCrewSettingsLocal(o) {
|
|
15144
16044
|
const dir = join26(o.projectCwd, ".claude");
|
|
15145
16045
|
mkdirSync9(dir, { recursive: true });
|
|
@@ -15250,25 +16150,57 @@ function writePerCrewOpencodeConfig(o) {
|
|
|
15250
16150
|
return file;
|
|
15251
16151
|
}
|
|
15252
16152
|
|
|
16153
|
+
// packages/cli/src/lib/claude-auth.ts
|
|
16154
|
+
import { execSync as execSync9 } from "child_process";
|
|
16155
|
+
function parseClaudeAuthStatus(raw) {
|
|
16156
|
+
let parsed;
|
|
16157
|
+
try {
|
|
16158
|
+
parsed = JSON.parse(raw);
|
|
16159
|
+
} catch {
|
|
16160
|
+
return null;
|
|
16161
|
+
}
|
|
16162
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
16163
|
+
const obj = parsed;
|
|
16164
|
+
if (typeof obj.loggedIn !== "boolean") return null;
|
|
16165
|
+
return {
|
|
16166
|
+
authenticated: obj.loggedIn,
|
|
16167
|
+
method: typeof obj.authMethod === "string" ? obj.authMethod : void 0,
|
|
16168
|
+
provider: typeof obj.apiProvider === "string" ? obj.apiProvider : void 0
|
|
16169
|
+
};
|
|
16170
|
+
}
|
|
16171
|
+
function detectClaudeAuth(probe = {}) {
|
|
16172
|
+
const run = probe.run ?? (() => execSync9("claude auth status", {
|
|
16173
|
+
encoding: "utf8",
|
|
16174
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
16175
|
+
timeout: 1e4
|
|
16176
|
+
}));
|
|
16177
|
+
try {
|
|
16178
|
+
const parsed = parseClaudeAuthStatus(run());
|
|
16179
|
+
return parsed ?? { authenticated: false, reason: "unreadable `claude auth status` output" };
|
|
16180
|
+
} catch {
|
|
16181
|
+
return { authenticated: false, reason: "`claude auth status` unavailable" };
|
|
16182
|
+
}
|
|
16183
|
+
}
|
|
16184
|
+
|
|
15253
16185
|
// packages/cli/src/commands/init.ts
|
|
15254
16186
|
init_dist3();
|
|
15255
16187
|
function findPackageRoot() {
|
|
15256
16188
|
let dir = path23.dirname(new URL(import.meta.url).pathname);
|
|
15257
16189
|
while (dir !== "/") {
|
|
15258
|
-
if (
|
|
16190
|
+
if (fs20.existsSync(path23.join(dir, "package.json"))) return dir;
|
|
15259
16191
|
dir = path23.dirname(dir);
|
|
15260
16192
|
}
|
|
15261
16193
|
return process.cwd();
|
|
15262
16194
|
}
|
|
15263
16195
|
function copyDirRecursive(src, dest) {
|
|
15264
|
-
|
|
15265
|
-
for (const entry of
|
|
16196
|
+
fs20.mkdirSync(dest, { recursive: true });
|
|
16197
|
+
for (const entry of fs20.readdirSync(src, { withFileTypes: true })) {
|
|
15266
16198
|
const srcPath = path23.join(src, entry.name);
|
|
15267
16199
|
const destPath = path23.join(dest, entry.name);
|
|
15268
16200
|
if (entry.isDirectory()) {
|
|
15269
16201
|
copyDirRecursive(srcPath, destPath);
|
|
15270
16202
|
} else {
|
|
15271
|
-
|
|
16203
|
+
fs20.copyFileSync(srcPath, destPath);
|
|
15272
16204
|
}
|
|
15273
16205
|
}
|
|
15274
16206
|
}
|
|
@@ -15285,16 +16217,70 @@ function promptLine(question) {
|
|
|
15285
16217
|
});
|
|
15286
16218
|
});
|
|
15287
16219
|
}
|
|
15288
|
-
|
|
16220
|
+
async function resolveRouter(config, opts, isTTY) {
|
|
16221
|
+
if (config.defaults?.router) return config.defaults.router;
|
|
16222
|
+
let kind = opts.routerKind;
|
|
16223
|
+
let baseUrl = opts.routerBaseUrl;
|
|
16224
|
+
let apiKeyEnv = opts.routerApiKeyEnv;
|
|
16225
|
+
if (isTTY && (!kind || !baseUrl)) {
|
|
16226
|
+
const k = await promptLine(chalk4.cyan(` Router kind [${DEFAULT_ROUTER_KIND}]: `));
|
|
16227
|
+
kind = kind ?? (k || DEFAULT_ROUTER_KIND);
|
|
16228
|
+
const b = await promptLine(chalk4.cyan(` Router base URL [${DEFAULT_ROUTER_BASE_URL}]: `));
|
|
16229
|
+
baseUrl = baseUrl ?? (b || DEFAULT_ROUTER_BASE_URL);
|
|
16230
|
+
const e = await promptLine(chalk4.cyan(" Env var holding the API key (optional): "));
|
|
16231
|
+
apiKeyEnv = apiKeyEnv ?? (e || void 0);
|
|
16232
|
+
}
|
|
16233
|
+
const resolvedKind = kind && isRouterKind(kind) ? kind : DEFAULT_ROUTER_KIND;
|
|
16234
|
+
const resolvedBase = baseUrl || DEFAULT_ROUTER_BASE_URL;
|
|
16235
|
+
if (!isTTY && !opts.routerBaseUrl) {
|
|
16236
|
+
console.log(chalk4.dim(` - no router flags given; defaulting to ${resolvedKind} (${resolvedBase})`));
|
|
16237
|
+
console.log(chalk4.dim(" set a credential: squadrant config set defaults.router.apiKeyEnv <VAR>"));
|
|
16238
|
+
}
|
|
16239
|
+
return { kind: resolvedKind, baseUrl: resolvedBase, ...apiKeyEnv ? { apiKeyEnv } : {} };
|
|
16240
|
+
}
|
|
16241
|
+
async function askProvider(current, auth) {
|
|
16242
|
+
console.log(chalk4.bold("\n Choose your provider:"));
|
|
16243
|
+
for (const p of PROVIDER_PRESETS) {
|
|
16244
|
+
const currentTag = p.id === current ? chalk4.dim(" (current)") : "";
|
|
16245
|
+
console.log(` ${chalk4.cyan(`${p.id})`)} ${p.label}${currentTag}`);
|
|
16246
|
+
console.log(chalk4.dim(` ${p.summary}`));
|
|
16247
|
+
}
|
|
16248
|
+
if (!auth.authenticated) {
|
|
16249
|
+
console.log(chalk4.yellow("\n \u26A0 No Anthropic credential detected \u2014 preset a will not work; consider b or c."));
|
|
16250
|
+
}
|
|
16251
|
+
const answer = (await promptLine(chalk4.cyan(`
|
|
16252
|
+
Provider [a/b/c/d] (default ${current}): `))).toLowerCase();
|
|
16253
|
+
if (!answer) return current;
|
|
16254
|
+
if (isProviderPresetId(answer)) return answer;
|
|
16255
|
+
console.log(chalk4.yellow(` \u26A0 Unknown choice '${answer}' \u2014 keeping '${current}'.`));
|
|
16256
|
+
return current;
|
|
16257
|
+
}
|
|
16258
|
+
function warnIfNoAnthropic(preset, auth) {
|
|
16259
|
+
if (preset !== "a" || auth.authenticated) return;
|
|
16260
|
+
console.log(chalk4.yellow(" \u26A0 No Anthropic credential detected (`claude auth status`)."));
|
|
16261
|
+
console.log(chalk4.dim(" Preset A (Claude Pro/Max or an API key) may not work \u2014 consider preset b or c."));
|
|
16262
|
+
}
|
|
16263
|
+
var initCommand = new Command2("init").description("Guided first-time setup: provider, hub vault, agents, plugins, projects (re-run-safe)").option("--hub <path>", "Hub vault path", "~/squadrant-hub").option("--preset <id>", "provider preset: a|b|c|d (non-interactive)").option("--router-kind <kind>", "router kind for --preset c (opencode-go|openrouter|ccr|litellm|custom)").option("--router-base-url <url>", "router base URL for --preset c").option("--router-api-key-env <env>", "env var holding the router credential for --preset c").action(async (opts) => {
|
|
15289
16264
|
const hubPath = resolveHome(opts.hub);
|
|
15290
16265
|
const pkgRoot = findPackageRoot();
|
|
15291
16266
|
const configDir = path23.join(os13.homedir(), ".config", "squadrant");
|
|
15292
16267
|
const isTTY = process.stdin.isTTY === true;
|
|
15293
16268
|
console.log(chalk4.bold("\nSquadrant Init\n"));
|
|
15294
|
-
|
|
16269
|
+
let explicitPreset;
|
|
16270
|
+
if (opts.preset !== void 0) {
|
|
16271
|
+
const candidate = opts.preset.toLowerCase();
|
|
16272
|
+
if (!isProviderPresetId(candidate)) {
|
|
16273
|
+
console.error(chalk4.red(` \u2718 Unknown --preset '${opts.preset}'. Valid values: a, b, c, d`));
|
|
16274
|
+
process.exitCode = 1;
|
|
16275
|
+
return;
|
|
16276
|
+
}
|
|
16277
|
+
explicitPreset = candidate;
|
|
16278
|
+
}
|
|
16279
|
+
if (!isTTY && !explicitPreset) {
|
|
15295
16280
|
console.log(" Run these steps to get started:\n");
|
|
15296
|
-
console.log(chalk4.bold(" 1/5 Hub vault"));
|
|
16281
|
+
console.log(chalk4.bold(" 1/5 Hub vault + provider"));
|
|
15297
16282
|
console.log(chalk4.cyan(` squadrant init --hub ${opts.hub}`));
|
|
16283
|
+
console.log(chalk4.dim(" Choose a provider: squadrant init --preset a|b|c|d"));
|
|
15298
16284
|
console.log(chalk4.bold("\n 2/5 Agent + projection setup"));
|
|
15299
16285
|
console.log(" (handled automatically by: " + chalk4.cyan("squadrant init") + ")");
|
|
15300
16286
|
console.log(chalk4.bold("\n 3/5 Plugins \u2014 open Claude Code and run:"));
|
|
@@ -15314,8 +16300,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
15314
16300
|
}
|
|
15315
16301
|
const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
15316
16302
|
try {
|
|
15317
|
-
if (
|
|
15318
|
-
const existing = JSON.parse(
|
|
16303
|
+
if (fs20.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
16304
|
+
const existing = JSON.parse(fs20.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
|
|
15319
16305
|
wsRegistry.get(existing.workspace ?? "obsidian");
|
|
15320
16306
|
}
|
|
15321
16307
|
} catch (err) {
|
|
@@ -15323,45 +16309,76 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
15323
16309
|
return;
|
|
15324
16310
|
}
|
|
15325
16311
|
stepHeader(1, 5, "Hub vault");
|
|
15326
|
-
|
|
16312
|
+
const configExists = fs20.existsSync(DEFAULT_CONFIG_PATH);
|
|
16313
|
+
let config;
|
|
16314
|
+
if (configExists) {
|
|
15327
16315
|
console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
|
|
16316
|
+
config = loadConfig();
|
|
15328
16317
|
} else {
|
|
15329
|
-
|
|
16318
|
+
config = getDefaultConfig();
|
|
15330
16319
|
config.hubVault = hubPath;
|
|
16320
|
+
}
|
|
16321
|
+
const auth = detectClaudeAuth();
|
|
16322
|
+
let chosenPreset = explicitPreset;
|
|
16323
|
+
if (!chosenPreset && isTTY) {
|
|
16324
|
+
chosenPreset = await askProvider(detectProviderPresetId(config), auth);
|
|
16325
|
+
}
|
|
16326
|
+
let appliedChanges = [];
|
|
16327
|
+
if (chosenPreset) {
|
|
16328
|
+
const label = PROVIDER_PRESETS.find((p) => p.id === chosenPreset)?.label ?? chosenPreset;
|
|
16329
|
+
const router = chosenPreset === "c" ? await resolveRouter(config, opts, isTTY) : void 0;
|
|
16330
|
+
const overwrite = !configExists || explicitPreset !== void 0;
|
|
16331
|
+
const result = applyProviderPreset(config, chosenPreset, { router, overwrite });
|
|
16332
|
+
appliedChanges = result.changes;
|
|
16333
|
+
config = result.config;
|
|
16334
|
+
console.log(chalk4.bold(`
|
|
16335
|
+
Provider: ${chosenPreset.toUpperCase()} \u2014 ${label}`));
|
|
16336
|
+
if (appliedChanges.length) {
|
|
16337
|
+
console.log(" Will change:");
|
|
16338
|
+
for (const change of appliedChanges) console.log(chalk4.dim(` - ${change}`));
|
|
16339
|
+
} else {
|
|
16340
|
+
console.log(chalk4.dim(" - provider config already set (unchanged)"));
|
|
16341
|
+
}
|
|
16342
|
+
if (!isTTY) warnIfNoAnthropic(chosenPreset, auth);
|
|
16343
|
+
}
|
|
16344
|
+
if (!configExists) {
|
|
15331
16345
|
saveConfig(config);
|
|
15332
16346
|
console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
|
|
16347
|
+
} else if (appliedChanges.length) {
|
|
16348
|
+
saveConfig(config);
|
|
16349
|
+
console.log(chalk4.green(` \u2714 Config updated at ${DEFAULT_CONFIG_PATH}`));
|
|
15333
16350
|
}
|
|
15334
16351
|
const hubTemplate = path23.join(pkgRoot, "obsidian", "hub");
|
|
15335
|
-
if (
|
|
16352
|
+
if (fs20.existsSync(hubPath)) {
|
|
15336
16353
|
console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
|
|
15337
|
-
} else if (
|
|
16354
|
+
} else if (fs20.existsSync(hubTemplate)) {
|
|
15338
16355
|
copyDirRecursive(hubTemplate, hubPath);
|
|
15339
16356
|
console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
|
|
15340
16357
|
} else {
|
|
15341
|
-
|
|
16358
|
+
fs20.mkdirSync(hubPath, { recursive: true });
|
|
15342
16359
|
console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
|
|
15343
16360
|
}
|
|
15344
16361
|
const hubDashboardSrc = path23.join(pkgRoot, "obsidian", "hub", "dashboard.md");
|
|
15345
16362
|
const hubDashboardDest = path23.join(hubPath, "dashboard.md");
|
|
15346
|
-
if (
|
|
15347
|
-
|
|
16363
|
+
if (fs20.existsSync(hubDashboardSrc)) {
|
|
16364
|
+
fs20.copyFileSync(hubDashboardSrc, hubDashboardDest);
|
|
15348
16365
|
console.log(chalk4.green(` \u2714 Dashboard refreshed`));
|
|
15349
16366
|
}
|
|
15350
|
-
|
|
16367
|
+
fs20.mkdirSync(path23.join(hubPath, "projects"), { recursive: true });
|
|
15351
16368
|
ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
|
|
15352
16369
|
console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
|
|
15353
16370
|
stepHeader(2, 5, "Agent + projection setup");
|
|
15354
16371
|
const settingsPath = path23.join(os13.homedir(), ".claude", "settings.json");
|
|
15355
16372
|
try {
|
|
15356
16373
|
let settings = {};
|
|
15357
|
-
if (
|
|
15358
|
-
settings = JSON.parse(
|
|
16374
|
+
if (fs20.existsSync(settingsPath)) {
|
|
16375
|
+
settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
|
|
15359
16376
|
}
|
|
15360
16377
|
const env = settings.env || {};
|
|
15361
16378
|
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
15362
16379
|
settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
15363
|
-
|
|
15364
|
-
|
|
16380
|
+
fs20.mkdirSync(path23.dirname(settingsPath), { recursive: true });
|
|
16381
|
+
fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
15365
16382
|
console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
|
|
15366
16383
|
} else {
|
|
15367
16384
|
console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
|
|
@@ -15441,7 +16458,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
15441
16458
|
init_dist();
|
|
15442
16459
|
init_dist2();
|
|
15443
16460
|
import { Command as Command3 } from "commander";
|
|
15444
|
-
import
|
|
16461
|
+
import fs21 from "fs";
|
|
15445
16462
|
import path24 from "path";
|
|
15446
16463
|
import chalk5 from "chalk";
|
|
15447
16464
|
function restartAfterProjectsAdd(opts) {
|
|
@@ -15456,20 +16473,20 @@ function restartAfterProjectsAdd(opts) {
|
|
|
15456
16473
|
function findPackageRoot2() {
|
|
15457
16474
|
let dir = path24.dirname(new URL(import.meta.url).pathname);
|
|
15458
16475
|
while (dir !== "/") {
|
|
15459
|
-
if (
|
|
16476
|
+
if (fs21.existsSync(path24.join(dir, "package.json"))) return dir;
|
|
15460
16477
|
dir = path24.dirname(dir);
|
|
15461
16478
|
}
|
|
15462
16479
|
return process.cwd();
|
|
15463
16480
|
}
|
|
15464
16481
|
function copyDirRecursive2(src, dest) {
|
|
15465
|
-
|
|
15466
|
-
for (const entry of
|
|
16482
|
+
fs21.mkdirSync(dest, { recursive: true });
|
|
16483
|
+
for (const entry of fs21.readdirSync(src, { withFileTypes: true })) {
|
|
15467
16484
|
const srcPath = path24.join(src, entry.name);
|
|
15468
16485
|
const destPath = path24.join(dest, entry.name);
|
|
15469
16486
|
if (entry.isDirectory()) {
|
|
15470
16487
|
copyDirRecursive2(srcPath, destPath);
|
|
15471
16488
|
} else {
|
|
15472
|
-
|
|
16489
|
+
fs21.copyFileSync(srcPath, destPath);
|
|
15473
16490
|
}
|
|
15474
16491
|
}
|
|
15475
16492
|
}
|
|
@@ -15505,7 +16522,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
15505
16522
|
process.exit(1);
|
|
15506
16523
|
}
|
|
15507
16524
|
const resolvedPath = resolveHome(projectPath);
|
|
15508
|
-
if (!
|
|
16525
|
+
if (!fs21.existsSync(path24.join(resolvedPath, ".git"))) {
|
|
15509
16526
|
console.log(chalk5.yellow(`
|
|
15510
16527
|
\u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
|
|
15511
16528
|
`));
|
|
@@ -15574,19 +16591,19 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
15574
16591
|
restartAfterProjectsAdd({ noRestart: opts.restart === false });
|
|
15575
16592
|
const pkgRoot = findPackageRoot2();
|
|
15576
16593
|
const spokeTemplate = path24.join(pkgRoot, "obsidian", "spoke");
|
|
15577
|
-
if (
|
|
16594
|
+
if (fs21.existsSync(spokeVault)) {
|
|
15578
16595
|
console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
|
|
15579
|
-
} else if (
|
|
16596
|
+
} else if (fs21.existsSync(spokeTemplate)) {
|
|
15580
16597
|
copyDirRecursive2(spokeTemplate, spokeVault);
|
|
15581
16598
|
const statusPath = path24.join(spokeVault, "status.md");
|
|
15582
|
-
if (
|
|
15583
|
-
const content =
|
|
16599
|
+
if (fs21.existsSync(statusPath)) {
|
|
16600
|
+
const content = fs21.readFileSync(statusPath, "utf-8");
|
|
15584
16601
|
const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
|
|
15585
|
-
|
|
16602
|
+
fs21.writeFileSync(statusPath, updated);
|
|
15586
16603
|
}
|
|
15587
16604
|
console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
|
|
15588
16605
|
} else {
|
|
15589
|
-
|
|
16606
|
+
fs21.mkdirSync(spokeVault, { recursive: true });
|
|
15590
16607
|
console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
|
|
15591
16608
|
}
|
|
15592
16609
|
console.log("");
|
|
@@ -15654,7 +16671,7 @@ init_dist4();
|
|
|
15654
16671
|
init_dist3();
|
|
15655
16672
|
init_dist();
|
|
15656
16673
|
import { Command as Command5 } from "commander";
|
|
15657
|
-
import { execSync as
|
|
16674
|
+
import { execSync as execSync10 } from "child_process";
|
|
15658
16675
|
import path25 from "path";
|
|
15659
16676
|
import os14 from "os";
|
|
15660
16677
|
import chalk7 from "chalk";
|
|
@@ -15665,7 +16682,7 @@ var TASK_PROMPTS = {
|
|
|
15665
16682
|
"wiki-aggregate": "Run a wiki aggregation pass using the squadrant:command-ops skill. Scan each spoke wiki index, identify shared knowledge worth promoting, write hub wiki pages, and exit when done."
|
|
15666
16683
|
};
|
|
15667
16684
|
function detectCurrentWorkspace() {
|
|
15668
|
-
const out =
|
|
16685
|
+
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
15669
16686
|
const match = out.match(/workspace:\d+/);
|
|
15670
16687
|
if (!match) {
|
|
15671
16688
|
throw new Error("Could not detect current cmux workspace. Run `squadrant command` from inside a cmux workspace.");
|
|
@@ -16533,6 +17550,7 @@ async function runCrewSpawn2(input) {
|
|
|
16533
17550
|
},
|
|
16534
17551
|
writeSettingsLocal: (cwd) => writePerCrewSettingsLocal({ projectCwd: cwd }),
|
|
16535
17552
|
writeOpencodeConfig: writePerCrewOpencodeConfig,
|
|
17553
|
+
writeRouterSettings,
|
|
16536
17554
|
sendFirstTurn: (pane, firstTurn, preLaunchScreen, opts) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen, opts),
|
|
16537
17555
|
getFreePort,
|
|
16538
17556
|
sendCodexFirstTurn,
|
|
@@ -16982,7 +18000,7 @@ init_dist4();
|
|
|
16982
18000
|
init_dist();
|
|
16983
18001
|
init_dist2();
|
|
16984
18002
|
import { Command as Command12 } from "commander";
|
|
16985
|
-
import
|
|
18003
|
+
import fs22 from "fs";
|
|
16986
18004
|
import path26 from "path";
|
|
16987
18005
|
import os15 from "os";
|
|
16988
18006
|
import chalk12 from "chalk";
|
|
@@ -17019,7 +18037,7 @@ async function runSideSpawn2(input) {
|
|
|
17019
18037
|
prompt: input.topic,
|
|
17020
18038
|
workdir: spawnCwd,
|
|
17021
18039
|
role: "side",
|
|
17022
|
-
promptFile:
|
|
18040
|
+
promptFile: fs22.existsSync(promptFile) ? promptFile : void 0,
|
|
17023
18041
|
interactive: true,
|
|
17024
18042
|
permissionMode: config.defaults.permissions?.crew ?? "auto",
|
|
17025
18043
|
...sideModel ? { model: sideModel } : {},
|
|
@@ -17126,7 +18144,7 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
|
|
|
17126
18144
|
init_dist();
|
|
17127
18145
|
init_dist4();
|
|
17128
18146
|
import { Command as Command13 } from "commander";
|
|
17129
|
-
import { execSync as
|
|
18147
|
+
import { execSync as execSync11 } from "child_process";
|
|
17130
18148
|
import chalk14 from "chalk";
|
|
17131
18149
|
|
|
17132
18150
|
// packages/web/dist/read-status.js
|
|
@@ -17282,7 +18300,7 @@ function renderDashboard(rows, opts) {
|
|
|
17282
18300
|
|
|
17283
18301
|
// packages/web/dist/sync-hub.js
|
|
17284
18302
|
init_dist();
|
|
17285
|
-
import
|
|
18303
|
+
import fs23 from "fs";
|
|
17286
18304
|
import path27 from "path";
|
|
17287
18305
|
function buildMirrorMarkdown(s) {
|
|
17288
18306
|
const fenced = "```";
|
|
@@ -17309,8 +18327,8 @@ function buildMirrorMarkdown(s) {
|
|
|
17309
18327
|
function syncHub(deps) {
|
|
17310
18328
|
if (!deps.config.hubVault)
|
|
17311
18329
|
return [];
|
|
17312
|
-
const writeFile6 = deps.writeFile ?? ((p, c) =>
|
|
17313
|
-
const mkdir6 = deps.mkdir ?? ((p) =>
|
|
18330
|
+
const writeFile6 = deps.writeFile ?? ((p, c) => fs23.writeFileSync(p, c));
|
|
18331
|
+
const mkdir6 = deps.mkdir ?? ((p) => fs23.mkdirSync(p, { recursive: true }));
|
|
17314
18332
|
const projectsDir = path27.join(resolveHome(deps.config.hubVault), "projects");
|
|
17315
18333
|
mkdir6(projectsDir);
|
|
17316
18334
|
const out = [];
|
|
@@ -17330,6 +18348,7 @@ function syncHub(deps) {
|
|
|
17330
18348
|
// packages/web/dist/web-server.js
|
|
17331
18349
|
init_dist2();
|
|
17332
18350
|
import { createServer as createServer5 } from "http";
|
|
18351
|
+
import { dirname as dirname7, join as join29 } from "path";
|
|
17333
18352
|
|
|
17334
18353
|
// packages/web/dist/snapshot-merge.js
|
|
17335
18354
|
function mergeSnapshot(daemon, external, now) {
|
|
@@ -17795,6 +18814,24 @@ function renderEnv(ext) {
|
|
|
17795
18814
|
out.push(`</section>`);
|
|
17796
18815
|
return out.join("");
|
|
17797
18816
|
}
|
|
18817
|
+
function renderLogs() {
|
|
18818
|
+
return [
|
|
18819
|
+
`<section class="panel" data-panel="logs" role="tabpanel" aria-label="Logs">`,
|
|
18820
|
+
sectionHead("Daemon log", "Live tail of squadrantd.log \u2014 lifecycle events, delivery attempts, auto-launches and errors as the daemon writes them."),
|
|
18821
|
+
`<div class="log-controls">`,
|
|
18822
|
+
`<div class="filter-bar" role="group" aria-label="Filter log lines by severity">`,
|
|
18823
|
+
`<button class="filter-btn on" data-log-sev="all" aria-pressed="true">All</button>`,
|
|
18824
|
+
`<button class="filter-btn" data-log-sev="warn" aria-pressed="false">Warn+</button>`,
|
|
18825
|
+
`<button class="filter-btn" data-log-sev="error" aria-pressed="false">Errors</button>`,
|
|
18826
|
+
`</div>`,
|
|
18827
|
+
`<button class="filter-btn" data-log-pause aria-pressed="false">Pause</button>`,
|
|
18828
|
+
`<button class="filter-btn" data-log-copy>Copy</button>`,
|
|
18829
|
+
`<span class="log-status" data-log-status>connecting\u2026</span>`,
|
|
18830
|
+
`</div>`,
|
|
18831
|
+
`<div class="log-view mono" data-log-view aria-live="polite" aria-label="Daemon log lines"></div>`,
|
|
18832
|
+
`</section>`
|
|
18833
|
+
].join("");
|
|
18834
|
+
}
|
|
17798
18835
|
var ATTN_ORDER = { stale: 0, gone: 1, alive: 2, unknown: 3, stopped: 4 };
|
|
17799
18836
|
var STATE_LABEL = { alive: "running", stale: "blocked", gone: "errored", unknown: "idle", stopped: "offline" };
|
|
17800
18837
|
var STATE_CLS = { alive: "s-alive", stale: "s-stale", gone: "s-gone", unknown: "s-unknown", stopped: "s-stopped" };
|
|
@@ -17923,7 +18960,8 @@ var TABS = [
|
|
|
17923
18960
|
["overview", "Overview"],
|
|
17924
18961
|
["projects", "Projects"],
|
|
17925
18962
|
["daemon", "Daemon"],
|
|
17926
|
-
["environment", "Environment"]
|
|
18963
|
+
["environment", "Environment"],
|
|
18964
|
+
["logs", "Logs"]
|
|
17927
18965
|
];
|
|
17928
18966
|
function tabNav() {
|
|
17929
18967
|
const btns = TABS.map(([id, label], i) => `<button class="tab${i === 0 ? " on" : ""}" data-tab="${id}" role="tab" aria-selected="${i === 0 ? "true" : "false"}">${esc(label)}</button>`).join("");
|
|
@@ -17972,6 +19010,7 @@ function renderContent(snap) {
|
|
|
17972
19010
|
out.push(renderProjects(snap, col.now));
|
|
17973
19011
|
out.push(renderDaemon(snap));
|
|
17974
19012
|
out.push(renderEnv(snap.external));
|
|
19013
|
+
out.push(renderLogs());
|
|
17975
19014
|
out.push(`</div>`);
|
|
17976
19015
|
out.push(metricsBlob(col, linkLost));
|
|
17977
19016
|
return out.join("\n");
|
|
@@ -18213,6 +19252,17 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
|
|
|
18213
19252
|
|
|
18214
19253
|
.empty{border:1px dashed var(--bezel);border-radius:12px;padding:24px;color:var(--ink-dim);text-align:center;background:var(--panel)}
|
|
18215
19254
|
|
|
19255
|
+
/* Logs (live daemon-log tail) */
|
|
19256
|
+
.log-controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 12px}
|
|
19257
|
+
.log-controls .filter-bar{margin:0}
|
|
19258
|
+
.log-status{margin-left:auto;color:var(--ink-dim);font-size:11px}
|
|
19259
|
+
.log-view{height:min(58vh,520px);overflow:auto;background:#0b1220;border:1px solid var(--bezel);border-radius:12px;
|
|
19260
|
+
padding:12px 14px;box-shadow:var(--shadow);font-size:12px;line-height:1.55}
|
|
19261
|
+
.log-line{white-space:pre-wrap;word-break:break-word;color:#c7d2e0}
|
|
19262
|
+
.log-line.l-warn{color:#f0c674}
|
|
19263
|
+
.log-line.l-error{color:#ff8a80}
|
|
19264
|
+
.log-view .empty-log{color:#6b7a90}
|
|
19265
|
+
|
|
18216
19266
|
:focus-visible{outline:2px solid var(--hud);outline-offset:2px;border-radius:4px}
|
|
18217
19267
|
|
|
18218
19268
|
@keyframes beat{0%,100%{box-shadow:0 0 0 0 rgba(58,210,159,.45)}50%{box-shadow:0 0 0 6px rgba(58,210,159,0)}}
|
|
@@ -18226,6 +19276,7 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
|
|
|
18226
19276
|
var CLIENT_JS = `
|
|
18227
19277
|
(function(){
|
|
18228
19278
|
var activeTab='live';var hist={};var prev={};var currentFilter='all';var currentSort='attention';var sortAsc=true;var currentSearch='';var expandedProjects={};
|
|
19279
|
+
var logLines=[];var logSev='all';var logPaused=false;var logAutoscroll=true;var logConnected=false;var logScrollSave=0;var LOG_MAX=1000;
|
|
18229
19280
|
function pushHist(k,v,t){var a=hist[k]||(hist[k]=[]);if(a.length&&a[a.length-1].t===t)return;a.push({t:t,v:v});if(a.length>48)a.shift();}
|
|
18230
19281
|
function ingest(){var el=document.getElementById('squadrant-metrics');if(!el)return;var m;try{m=JSON.parse(el.textContent);}catch(e){return;}pushHist('errors',m.errors,m.t);pushHist('behind',m.behind,m.t);pushHist('crewAge',Math.round(m.crewAgeMs/1000),m.t);pushHist('defers',m.maxDeferCount,m.t);}
|
|
18231
19282
|
function sparkSVG(a){var W=100,H=28,p=2;if(!a.length)return'';var vs=a.map(function(x){return x.v;});var mx=Math.max.apply(null,vs),mn=Math.min.apply(null,vs);if(mx===mn)mx=mn+1;var n=a.length;var pts=a.map(function(x,i){var px=n>1?p+i/(n-1)*(W-2*p):W/2;var py=H-p-(x.v-mn)/(mx-mn)*(H-2*p);return[px,py];});var d=pts.map(function(pt,i){return(i?'L':'M')+pt[0].toFixed(1)+' '+pt[1].toFixed(1);}).join(' ');var last=pts[pts.length-1];var area=d+' L'+last[0].toFixed(1)+' '+H+' L'+pts[0][0].toFixed(1)+' '+H+' Z';return'<path class="spark-area" d="'+area+'"/><path class="spark-line" d="'+d+'"/><circle class="spark-dot" cx="'+last[0].toFixed(1)+'" cy="'+last[1].toFixed(1)+'" r="2"/>';}
|
|
@@ -18238,9 +19289,19 @@ var CLIENT_JS = `
|
|
|
18238
19289
|
function applySearch(){var q=currentSearch.toLowerCase();document.querySelectorAll('[data-panel="live"] .live-row').forEach(function(r){if(!q){if(!hideByFilter(r.getAttribute('data-rollup'),currentFilter))r.hidden=false;return;}var pn=(r.getAttribute('data-project')||'').toLowerCase();var dd=((r.querySelector('.live-detail')||{}).textContent||'').toLowerCase();var rl=r.getAttribute('data-rollup');if(hideByFilter(rl,currentFilter)){r.hidden=true;return;}r.hidden=pn.indexOf(q)===-1&&dd.indexOf(q)===-1;});}
|
|
18239
19290
|
function applySort(){var table=document.querySelector('[data-panel="live"] [data-live-table]');if(!table)return;var tbody=table.querySelector('tbody');if(!tbody)return;var rows=Array.prototype.slice.call(tbody.querySelectorAll('.live-row'));var dir=sortAsc?1:-1;rows.sort(function(a,b){var va,vb;if(currentSort==='name'){va=(a.getAttribute('data-project')||'').toLowerCase();vb=(b.getAttribute('data-project')||'').toLowerCase();}else if(currentSort==='attention'){var order={gone:0,stale:1,alive:2,unknown:3,stopped:4};va=order[a.getAttribute('data-rollup')]??99;vb=order[b.getAttribute('data-rollup')]??99;if(va===vb){va=(a.getAttribute('data-project')||'').toLowerCase();vb=(b.getAttribute('data-project')||'').toLowerCase();return va<vb?-1:va>vb?1:0;}}else if(currentSort==='activity'){va=parseInt(a.getAttribute('data-max-age'))||0;vb=parseInt(b.getAttribute('data-max-age'))||0;}return va<vb?-dir:va>vb?dir:0;});rows.forEach(function(r){tbody.appendChild(r);});}
|
|
18240
19291
|
function applyExpand(){document.querySelectorAll('[data-panel="live"] .live-row').forEach(function(r){var pn=r.getAttribute('data-project');var er=document.querySelector('[data-panel="live"] .live-expand[data-project="'+pn+'"]');if(er){var show=!!expandedProjects[pn];er.hidden=!show;r.classList.toggle('expanded',show);}});}
|
|
18241
|
-
function
|
|
18242
|
-
|
|
18243
|
-
|
|
19292
|
+
function logSevOf(s){return /error|failed|fatal/i.test(s)?'error':/warn/i.test(s)?'warn':'info';}
|
|
19293
|
+
function logMatch(l){if(logSev==='all')return true;if(logSev==='error')return logSevOf(l)==='error';if(logSev==='warn')return logSevOf(l)!=='info';return true;}
|
|
19294
|
+
function escHtml(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
19295
|
+
function logVisible(){return logLines.filter(logMatch);}
|
|
19296
|
+
function renderLogs(){var v=document.querySelector('[data-log-view]');if(!v)return;var vis=logVisible();var html='';for(var i=0;i<vis.length;i++){html+='<div class="log-line l-'+logSevOf(vis[i])+'">'+escHtml(vis[i])+'</div>';}v.innerHTML=html||'<div class="empty-log">waiting for log output\u2026</div>';if(logAutoscroll)v.scrollTop=v.scrollHeight;else v.scrollTop=logScrollSave;
|
|
19297
|
+
var sb=document.querySelectorAll('[data-log-sev]');for(var j=0;j<sb.length;j++){var on=sb[j].getAttribute('data-log-sev')===logSev;sb[j].classList.toggle('on',on);sb[j].setAttribute('aria-pressed',on?'true':'false');}
|
|
19298
|
+
var pb=document.querySelector('[data-log-pause]');if(pb){pb.textContent=logPaused?'Resume':'Pause';pb.classList.toggle('on',logPaused);pb.setAttribute('aria-pressed',logPaused?'true':'false');}
|
|
19299
|
+
var st=document.querySelector('[data-log-status]');if(st){if(!logConnected)st.textContent='log stream reconnecting\u2026';else st.textContent=(logPaused?'paused \xB7 ':'')+vis.length+(vis.length===logLines.length?'':'/'+logLines.length)+' lines';}
|
|
19300
|
+
}
|
|
19301
|
+
function ingestLog(lines){if(!lines||!lines.length)return;logLines=logLines.concat(lines);if(logLines.length>LOG_MAX)logLines=logLines.slice(logLines.length-LOG_MAX);if(activeTab==='logs'&&!logPaused)renderLogs();}
|
|
19302
|
+
function refresh(){ingest();applyTab();drawSparks();countUps();applyFilter();applySearch();applySort();applyExpand();if(activeTab==='logs')renderLogs();}
|
|
19303
|
+
document.addEventListener('click',function(e){var t=e.target&&e.target.closest?e.target.closest('[data-tab]'):null;if(t){activeTab=t.getAttribute('data-tab');applyTab();var c=document.getElementById('content');if(c){c.classList.remove('swap');void c.offsetWidth;c.classList.add('swap');}if(activeTab==='logs')renderLogs();}var fb=e.target&&e.target.closest?e.target.closest('[data-filter]'):null;if(fb){currentFilter=fb.getAttribute('data-filter');applyFilter();applySearch();}var sh=e.target&&e.target.closest?e.target.closest('[data-sort]'):null;if(sh){var key=sh.getAttribute('data-sort');if(key===currentSort)sortAsc=!sortAsc;else{currentSort=key;sortAsc=true;}applySort();}var sv=e.target&&e.target.closest?e.target.closest('[data-log-sev]'):null;if(sv){logSev=sv.getAttribute('data-log-sev');renderLogs();}var lp=e.target&&e.target.closest?e.target.closest('[data-log-pause]'):null;if(lp){logPaused=!logPaused;renderLogs();}var cp=e.target&&e.target.closest?e.target.closest('[data-log-copy]'):null;if(cp&&navigator.clipboard){navigator.clipboard.writeText(logVisible().join('\\n'));}var lr=e.target&&e.target.closest?e.target.closest('[data-panel="live"] .live-row'):null;if(lr&&!e.target.closest('[data-sort]')){var pn=lr.getAttribute('data-project');var er=document.querySelector('[data-panel="live"] .live-expand[data-project="'+pn+'"]');if(er){var show=er.hidden;er.hidden=!show;lr.classList.toggle('expanded',!!show);expandedProjects[pn]=!!show;}}});
|
|
19304
|
+
document.addEventListener('keydown',function(e){var ae=document.activeElement;if((e.key==='ArrowRight'||e.key==='ArrowLeft')&&ae&&ae.getAttribute&&ae.getAttribute('data-tab')){var o=['live','overview','projects','daemon','environment','logs'];var i=o.indexOf(activeTab);i=(i+(e.key==='ArrowRight'?1:o.length-1))%o.length;activeTab=o[i];applyTab();var nt=document.querySelector('[data-tab="'+activeTab+'"]');if(nt)nt.focus();e.preventDefault();}});
|
|
18244
19305
|
document.addEventListener('input',function(e){var sb=e.target&&e.target.closest?e.target.closest('[data-live-search]'):null;if(sb){currentSearch=sb.value;applySearch();}});
|
|
18245
19306
|
var led=document.getElementById('led');var conn=document.getElementById('conn');var updated=document.getElementById('updated');
|
|
18246
19307
|
function tickAge(){if(!generatedAt)return;var s=Math.max(0,Math.round((Date.now()-generatedAt)/1000));updated.textContent='updated '+s+'s ago';}
|
|
@@ -18249,6 +19310,11 @@ var CLIENT_JS = `
|
|
|
18249
19310
|
es.onopen=function(){conn.textContent='live';conn.className='conn live';led.className='led live';};
|
|
18250
19311
|
es.onerror=function(){conn.textContent='reconnecting';conn.className='conn down';led.className='led down';};
|
|
18251
19312
|
es.onmessage=function(e){try{var d=JSON.parse(e.data);document.getElementById('content').innerHTML=d.contentHtml;generatedAt=d.generatedAt;tickAge();refresh();}catch(_){}};
|
|
19313
|
+
document.addEventListener('scroll',function(e){var v=e.target;if(v&&v.getAttribute&&v.getAttribute('data-log-view')!==null){logAutoscroll=v.scrollTop+v.clientHeight>=v.scrollHeight-6;logScrollSave=v.scrollTop;}},true);
|
|
19314
|
+
var les=new EventSource('/logs');
|
|
19315
|
+
les.onopen=function(){logConnected=true;renderLogs();};
|
|
19316
|
+
les.onerror=function(){logConnected=false;renderLogs();};
|
|
19317
|
+
les.onmessage=function(e){try{var d=JSON.parse(e.data);if(!logPaused)ingestLog(d&&d.lines);}catch(_){}};
|
|
18252
19318
|
refresh();tickAge();
|
|
18253
19319
|
})();
|
|
18254
19320
|
`.trim();
|
|
@@ -18271,7 +19337,100 @@ function renderHtml(snap, opts = {}) {
|
|
|
18271
19337
|
].join("");
|
|
18272
19338
|
}
|
|
18273
19339
|
|
|
19340
|
+
// packages/web/dist/log-tail.js
|
|
19341
|
+
import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync4 } from "fs";
|
|
19342
|
+
var LOG_TAIL_BYTES = 256 * 1024;
|
|
19343
|
+
var LOG_RING_SIZE = 1e3;
|
|
19344
|
+
function defaultSize(path39) {
|
|
19345
|
+
try {
|
|
19346
|
+
return statSync4(path39).size;
|
|
19347
|
+
} catch {
|
|
19348
|
+
return 0;
|
|
19349
|
+
}
|
|
19350
|
+
}
|
|
19351
|
+
function defaultRead(path39, start, len) {
|
|
19352
|
+
if (len <= 0)
|
|
19353
|
+
return "";
|
|
19354
|
+
try {
|
|
19355
|
+
const fd = openSync3(path39, "r");
|
|
19356
|
+
try {
|
|
19357
|
+
const buf = Buffer.alloc(len);
|
|
19358
|
+
const n = readSync2(fd, buf, 0, len, start);
|
|
19359
|
+
return buf.subarray(0, n).toString("utf-8");
|
|
19360
|
+
} finally {
|
|
19361
|
+
closeSync3(fd);
|
|
19362
|
+
}
|
|
19363
|
+
} catch {
|
|
19364
|
+
return "";
|
|
19365
|
+
}
|
|
19366
|
+
}
|
|
19367
|
+
var LogTailer = class {
|
|
19368
|
+
path;
|
|
19369
|
+
ringSize;
|
|
19370
|
+
offset = 0;
|
|
19371
|
+
pending = "";
|
|
19372
|
+
ring = [];
|
|
19373
|
+
size;
|
|
19374
|
+
read;
|
|
19375
|
+
constructor(path39, ringSize = LOG_RING_SIZE, deps = {}) {
|
|
19376
|
+
this.path = path39;
|
|
19377
|
+
this.ringSize = ringSize;
|
|
19378
|
+
this.size = deps.size ?? defaultSize;
|
|
19379
|
+
this.read = deps.read ?? defaultRead;
|
|
19380
|
+
}
|
|
19381
|
+
/** Seed history from the last `maxBytes` and position the cursor at EOF.
|
|
19382
|
+
* Idempotent; call once at startup. A trailing partial line stays pending
|
|
19383
|
+
* so the next append completes it without loss or duplication. */
|
|
19384
|
+
init(maxBytes = LOG_TAIL_BYTES) {
|
|
19385
|
+
const size = this.size(this.path);
|
|
19386
|
+
this.pending = "";
|
|
19387
|
+
this.ring = [];
|
|
19388
|
+
if (size <= 0) {
|
|
19389
|
+
this.offset = size;
|
|
19390
|
+
return;
|
|
19391
|
+
}
|
|
19392
|
+
const start = Math.max(0, size - maxBytes);
|
|
19393
|
+
const parts = this.read(this.path, start, size - start).split("\n");
|
|
19394
|
+
const rest = parts.pop() ?? "";
|
|
19395
|
+
if (start > 0 && parts.length)
|
|
19396
|
+
parts.shift();
|
|
19397
|
+
this.push(parts);
|
|
19398
|
+
this.pending = rest;
|
|
19399
|
+
this.offset = size;
|
|
19400
|
+
}
|
|
19401
|
+
/** Complete lines appended since the previous poll. */
|
|
19402
|
+
poll() {
|
|
19403
|
+
const size = this.size(this.path);
|
|
19404
|
+
if (size < this.offset) {
|
|
19405
|
+
this.offset = 0;
|
|
19406
|
+
this.pending = "";
|
|
19407
|
+
}
|
|
19408
|
+
if (size <= this.offset)
|
|
19409
|
+
return [];
|
|
19410
|
+
const text = this.read(this.path, this.offset, size - this.offset);
|
|
19411
|
+
if (!text)
|
|
19412
|
+
return [];
|
|
19413
|
+
this.offset += Buffer.byteLength(text);
|
|
19414
|
+
const parts = (this.pending + text).split("\n");
|
|
19415
|
+
this.pending = parts.pop() ?? "";
|
|
19416
|
+
this.push(parts);
|
|
19417
|
+
return parts;
|
|
19418
|
+
}
|
|
19419
|
+
/** The buffered backlog (oldest first) replayed to a newly-connected client. */
|
|
19420
|
+
backlog() {
|
|
19421
|
+
return this.ring.slice();
|
|
19422
|
+
}
|
|
19423
|
+
push(lines) {
|
|
19424
|
+
for (const l of lines)
|
|
19425
|
+
this.ring.push(l);
|
|
19426
|
+
const over = this.ring.length - this.ringSize;
|
|
19427
|
+
if (over > 0)
|
|
19428
|
+
this.ring.splice(0, over);
|
|
19429
|
+
}
|
|
19430
|
+
};
|
|
19431
|
+
|
|
18274
19432
|
// packages/web/dist/web-server.js
|
|
19433
|
+
var LOG_POLL_MS = 1e3;
|
|
18275
19434
|
async function querySnapshot(sockPath) {
|
|
18276
19435
|
try {
|
|
18277
19436
|
const reply = await sendRequest(sockPath, { kind: "snapshot" });
|
|
@@ -18284,6 +19443,9 @@ async function startWebServer(opts) {
|
|
|
18284
19443
|
const host = opts.host ?? "127.0.0.1";
|
|
18285
19444
|
const now = opts.now ?? (() => Date.now());
|
|
18286
19445
|
const clients = /* @__PURE__ */ new Set();
|
|
19446
|
+
const logClients = /* @__PURE__ */ new Set();
|
|
19447
|
+
const logPath2 = opts.logPath ?? join29(dirname7(opts.sockPath), "squadrantd.log");
|
|
19448
|
+
const tailer = new LogTailer(logPath2);
|
|
18287
19449
|
let latest = null;
|
|
18288
19450
|
async function tick() {
|
|
18289
19451
|
const daemon = await querySnapshot(opts.sockPath);
|
|
@@ -18299,6 +19461,19 @@ async function startWebServer(opts) {
|
|
|
18299
19461
|
}
|
|
18300
19462
|
}
|
|
18301
19463
|
}
|
|
19464
|
+
function broadcastLog(lines) {
|
|
19465
|
+
if (lines.length === 0)
|
|
19466
|
+
return;
|
|
19467
|
+
const payload = `data: ${JSON.stringify({ lines })}
|
|
19468
|
+
|
|
19469
|
+
`;
|
|
19470
|
+
for (const res of logClients) {
|
|
19471
|
+
try {
|
|
19472
|
+
res.write(payload);
|
|
19473
|
+
} catch {
|
|
19474
|
+
}
|
|
19475
|
+
}
|
|
19476
|
+
}
|
|
18302
19477
|
const server = createServer5((req, res) => {
|
|
18303
19478
|
if (req.method !== "GET") {
|
|
18304
19479
|
res.writeHead(405).end();
|
|
@@ -18324,6 +19499,22 @@ async function startWebServer(opts) {
|
|
|
18324
19499
|
req.on("close", () => clients.delete(res));
|
|
18325
19500
|
return;
|
|
18326
19501
|
}
|
|
19502
|
+
if (req.url === "/logs") {
|
|
19503
|
+
res.writeHead(200, {
|
|
19504
|
+
"Content-Type": "text/event-stream",
|
|
19505
|
+
"Cache-Control": "no-cache",
|
|
19506
|
+
Connection: "keep-alive"
|
|
19507
|
+
});
|
|
19508
|
+
res.write("retry: 3000\n\n");
|
|
19509
|
+
const backlog = tailer.backlog();
|
|
19510
|
+
if (backlog.length)
|
|
19511
|
+
res.write(`data: ${JSON.stringify({ lines: backlog })}
|
|
19512
|
+
|
|
19513
|
+
`);
|
|
19514
|
+
logClients.add(res);
|
|
19515
|
+
req.on("close", () => logClients.delete(res));
|
|
19516
|
+
return;
|
|
19517
|
+
}
|
|
18327
19518
|
res.writeHead(404).end();
|
|
18328
19519
|
});
|
|
18329
19520
|
await new Promise((resolve4, reject) => {
|
|
@@ -18338,17 +19529,33 @@ async function startWebServer(opts) {
|
|
|
18338
19529
|
});
|
|
18339
19530
|
}, opts.intervalMs);
|
|
18340
19531
|
timer.unref?.();
|
|
19532
|
+
tailer.init();
|
|
19533
|
+
const logTimer = setInterval(() => {
|
|
19534
|
+
try {
|
|
19535
|
+
broadcastLog(tailer.poll());
|
|
19536
|
+
} catch {
|
|
19537
|
+
}
|
|
19538
|
+
}, LOG_POLL_MS);
|
|
19539
|
+
logTimer.unref?.();
|
|
18341
19540
|
return {
|
|
18342
19541
|
port: boundPort,
|
|
18343
19542
|
close: () => new Promise((resolve4) => {
|
|
18344
19543
|
clearInterval(timer);
|
|
19544
|
+
clearInterval(logTimer);
|
|
18345
19545
|
for (const res of clients) {
|
|
18346
19546
|
try {
|
|
18347
19547
|
res.end();
|
|
18348
19548
|
} catch {
|
|
18349
19549
|
}
|
|
18350
19550
|
}
|
|
19551
|
+
for (const res of logClients) {
|
|
19552
|
+
try {
|
|
19553
|
+
res.end();
|
|
19554
|
+
} catch {
|
|
19555
|
+
}
|
|
19556
|
+
}
|
|
18351
19557
|
clients.clear();
|
|
19558
|
+
logClients.clear();
|
|
18352
19559
|
server.close(() => resolve4());
|
|
18353
19560
|
})
|
|
18354
19561
|
};
|
|
@@ -18358,7 +19565,7 @@ async function startWebServer(opts) {
|
|
|
18358
19565
|
init_dist();
|
|
18359
19566
|
var SOCK3 = DAEMON_SOCK_PATH;
|
|
18360
19567
|
function detectCurrentWorkspace2() {
|
|
18361
|
-
const out =
|
|
19568
|
+
const out = execSync11(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
18362
19569
|
const match = out.match(/workspace:\d+/);
|
|
18363
19570
|
if (!match) {
|
|
18364
19571
|
throw new Error("Could not detect current cmux workspace. Run `squadrant dashboard --pane` from inside a cmux workspace.");
|
|
@@ -18441,8 +19648,8 @@ init_dist3();
|
|
|
18441
19648
|
init_dist4();
|
|
18442
19649
|
init_dist2();
|
|
18443
19650
|
import { Command as Command14 } from "commander";
|
|
18444
|
-
import { execSync as
|
|
18445
|
-
import
|
|
19651
|
+
import { execSync as execSync12, execFileSync as execFileSync10 } from "child_process";
|
|
19652
|
+
import fs24 from "fs";
|
|
18446
19653
|
import path28 from "path";
|
|
18447
19654
|
import os16 from "os";
|
|
18448
19655
|
import chalk15 from "chalk";
|
|
@@ -18528,6 +19735,10 @@ function resolveLaunchAgent(overrides, roleConfig, roleModelDefault) {
|
|
|
18528
19735
|
thinking: overrides.thinking ?? roleConfig?.thinking
|
|
18529
19736
|
};
|
|
18530
19737
|
}
|
|
19738
|
+
function resolveLaunchBackend(o) {
|
|
19739
|
+
const roleBackend = o.agentName === "claude" ? o.roleConfig?.backend : void 0;
|
|
19740
|
+
return o.backendOverride ?? roleBackend ?? "native";
|
|
19741
|
+
}
|
|
18531
19742
|
|
|
18532
19743
|
// packages/cli/src/commands/launch.ts
|
|
18533
19744
|
init_dist2();
|
|
@@ -18569,17 +19780,24 @@ function resolveAnthropicRefusal(role, workspaceName, agentName, model, readOpen
|
|
|
18569
19780
|
function ensureCmuxReady(headless) {
|
|
18570
19781
|
if (headless || isInsideCmux()) return;
|
|
18571
19782
|
console.log(chalk15.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
18572
|
-
|
|
19783
|
+
execSync12(`open "${CMUX_APP}"`, { stdio: "inherit" });
|
|
18573
19784
|
console.log(chalk15.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
18574
19785
|
process.exit(0);
|
|
18575
19786
|
}
|
|
18576
19787
|
var launchCommand = new Command14("launch").description(
|
|
18577
19788
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
18578
|
-
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").option("--agent <name>", "Override captain agent for this launch (claude|codex|gemini|opencode); takes precedence over defaults.roles.captain.agent").option("--model <name>", "Override captain model for this launch; takes precedence over defaults.roles.captain.model").option("--thinking <level>", `Override captain thinking level for this launch (${THINKING_LEVELS.join("|")}) \u2192 claude --effort; takes precedence over defaults.roles.captain.thinking`).action(async (project, opts) => {
|
|
19789
|
+
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").option("--agent <name>", "Override captain agent for this launch (claude|codex|gemini|opencode); takes precedence over defaults.roles.captain.agent").option("--model <name>", "Override captain model for this launch; takes precedence over defaults.roles.captain.model").option("--thinking <level>", `Override captain thinking level for this launch (${THINKING_LEVELS.join("|")}) \u2192 claude --effort; takes precedence over defaults.roles.captain.thinking`).option("--backend <mode>", "Override captain backend for this launch (native|direct|proxy); takes precedence over defaults.roles.captain.backend").action(async (project, opts) => {
|
|
18579
19790
|
if (opts.fresh && opts.keep) {
|
|
18580
19791
|
console.error(chalk15.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
18581
19792
|
process.exit(1);
|
|
18582
19793
|
}
|
|
19794
|
+
if (opts.backend !== void 0 && !isBackendMode(opts.backend)) {
|
|
19795
|
+
console.error(chalk15.red(`
|
|
19796
|
+
\u2718 Invalid --backend value '${opts.backend}'. Valid values: native, direct, proxy
|
|
19797
|
+
`));
|
|
19798
|
+
process.exit(1);
|
|
19799
|
+
}
|
|
19800
|
+
const backendOverride = opts.backend;
|
|
18583
19801
|
let thinkingOverride;
|
|
18584
19802
|
try {
|
|
18585
19803
|
thinkingOverride = opts.thinking ? parseThinkingLevel(opts.thinking) : void 0;
|
|
@@ -18606,6 +19824,7 @@ var launchCommand = new Command14("launch").description(
|
|
|
18606
19824
|
roleConfig,
|
|
18607
19825
|
config.defaults.models?.[role]
|
|
18608
19826
|
);
|
|
19827
|
+
const backend = resolveLaunchBackend({ backendOverride, agentName, roleConfig });
|
|
18609
19828
|
const refusal = resolveAnthropicRefusal(role, workspaceName, agentName, model);
|
|
18610
19829
|
if (refusal) {
|
|
18611
19830
|
console.error(chalk15.red(`
|
|
@@ -18635,6 +19854,29 @@ var launchCommand = new Command14("launch").description(
|
|
|
18635
19854
|
if (isOpencodeCaptain && projectName) {
|
|
18636
19855
|
captainOpencodeConfigPath = writePerCrewOpencodeConfig({ stateRoot, project: projectName, taskId: "captain" });
|
|
18637
19856
|
}
|
|
19857
|
+
let route;
|
|
19858
|
+
if (role === "captain") {
|
|
19859
|
+
try {
|
|
19860
|
+
route = await prepareCaptainRoute({
|
|
19861
|
+
backend,
|
|
19862
|
+
agentName,
|
|
19863
|
+
configuredPermissionMode: permissionMode,
|
|
19864
|
+
project: projectName ?? "",
|
|
19865
|
+
model,
|
|
19866
|
+
stateRoot,
|
|
19867
|
+
config,
|
|
19868
|
+
deps: {
|
|
19869
|
+
routerCredentials: (o) => fetchRouterCredentials(o.project, o.backend, { call: squadrantdCall }),
|
|
19870
|
+
writeRouterSettings
|
|
19871
|
+
},
|
|
19872
|
+
warn: (m) => console.error(chalk15.yellow(` \u26A0 ${m}`))
|
|
19873
|
+
});
|
|
19874
|
+
} catch (err) {
|
|
19875
|
+
console.error(chalk15.red(` \u2718 ${err.message}`));
|
|
19876
|
+
hadFailure = true;
|
|
19877
|
+
return;
|
|
19878
|
+
}
|
|
19879
|
+
}
|
|
18638
19880
|
const priorRecord = projectName ? readCaptainAddress(stateRoot, projectName) : null;
|
|
18639
19881
|
const captainPort = isOpencodeCaptain ? await getFreePort() : void 0;
|
|
18640
19882
|
let opencodeResumeSessionId;
|
|
@@ -18660,15 +19902,18 @@ var launchCommand = new Command14("launch").description(
|
|
|
18660
19902
|
registry,
|
|
18661
19903
|
role,
|
|
18662
19904
|
forceFresh,
|
|
18663
|
-
permissionMode,
|
|
18664
|
-
model,
|
|
19905
|
+
route?.permissionMode ?? permissionMode,
|
|
19906
|
+
route?.model ?? model,
|
|
18665
19907
|
TEMPLATES_DIR4,
|
|
18666
19908
|
resolveCaptainSocketPath(captainChannelEnabled, projectName, workspaceName),
|
|
18667
19909
|
resolveCaptainSessionName(agentName, projectName),
|
|
18668
19910
|
thinking,
|
|
18669
|
-
captainBoot
|
|
19911
|
+
captainBoot,
|
|
19912
|
+
route?.settingsPath
|
|
18670
19913
|
);
|
|
18671
|
-
|
|
19914
|
+
const gatePrefix = route && Object.keys(route.env).length > 0 ? `${renderEnvAssignments(route.env)} ` : "";
|
|
19915
|
+
const cmd = `${gatePrefix}${baseCmd}`;
|
|
19916
|
+
return captainOpencodeConfigPath ? `OPENCODE_CONFIG=${captainOpencodeConfigPath} ${cmd}` : cmd;
|
|
18672
19917
|
},
|
|
18673
19918
|
initialPrompt,
|
|
18674
19919
|
runtime,
|
|
@@ -18732,12 +19977,12 @@ var launchCommand = new Command14("launch").description(
|
|
|
18732
19977
|
}
|
|
18733
19978
|
if (opts.all) {
|
|
18734
19979
|
const hubPath = resolveHome(config.hubVault);
|
|
18735
|
-
|
|
19980
|
+
fs24.mkdirSync(hubPath, { recursive: true });
|
|
18736
19981
|
console.log(chalk15.bold("\nLaunching all captain workspaces\n"));
|
|
18737
19982
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
18738
19983
|
const projPath = resolveHome(proj.path);
|
|
18739
19984
|
const spokePath = resolveHome(proj.spokeVault);
|
|
18740
|
-
if (!
|
|
19985
|
+
if (!fs24.existsSync(spokePath)) {
|
|
18741
19986
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
18742
19987
|
await ensureSpokeLayout(spokeDriver);
|
|
18743
19988
|
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -18775,7 +20020,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
18775
20020
|
const proj = config.projects[name];
|
|
18776
20021
|
const projPath = resolveHome(proj.path);
|
|
18777
20022
|
const spokePath = resolveHome(proj.spokeVault);
|
|
18778
|
-
if (!
|
|
20023
|
+
if (!fs24.existsSync(spokePath)) {
|
|
18779
20024
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
18780
20025
|
await ensureSpokeLayout(spokeDriver);
|
|
18781
20026
|
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -18799,7 +20044,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
18799
20044
|
const proj = config.projects[project];
|
|
18800
20045
|
const projPath = resolveHome(proj.path);
|
|
18801
20046
|
const spokePath = resolveHome(proj.spokeVault);
|
|
18802
|
-
if (!
|
|
20047
|
+
if (!fs24.existsSync(spokePath)) {
|
|
18803
20048
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
18804
20049
|
await ensureSpokeLayout(spokeDriver);
|
|
18805
20050
|
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -18935,24 +20180,24 @@ Shutting down captain workspace for '${project}'...
|
|
|
18935
20180
|
// packages/cli/src/commands/feedback.ts
|
|
18936
20181
|
init_dist();
|
|
18937
20182
|
import { Command as Command16 } from "commander";
|
|
18938
|
-
import
|
|
20183
|
+
import fs25 from "fs";
|
|
18939
20184
|
import os17 from "os";
|
|
18940
20185
|
import path29 from "path";
|
|
18941
20186
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18942
|
-
import { execSync as
|
|
20187
|
+
import { execSync as execSync13 } from "child_process";
|
|
18943
20188
|
import chalk17 from "chalk";
|
|
18944
20189
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
18945
20190
|
function readPkgVersion() {
|
|
18946
20191
|
try {
|
|
18947
20192
|
const pkgPath = path29.join(path29.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
18948
|
-
return JSON.parse(
|
|
20193
|
+
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
18949
20194
|
} catch {
|
|
18950
20195
|
return "unknown";
|
|
18951
20196
|
}
|
|
18952
20197
|
}
|
|
18953
20198
|
function readMetrics(metricsPath) {
|
|
18954
20199
|
try {
|
|
18955
|
-
return JSON.parse(
|
|
20200
|
+
return JSON.parse(fs25.readFileSync(metricsPath, "utf-8"));
|
|
18956
20201
|
} catch {
|
|
18957
20202
|
return {};
|
|
18958
20203
|
}
|
|
@@ -18998,7 +20243,7 @@ var feedbackCommand = new Command16("feedback").description("Open a pre-filled G
|
|
|
18998
20243
|
console.log(chalk17.dim(` URL: ${issueUrl.substring(0, 80)}...
|
|
18999
20244
|
`));
|
|
19000
20245
|
try {
|
|
19001
|
-
|
|
20246
|
+
execSync13(`open "${issueUrl}"`, { stdio: "ignore" });
|
|
19002
20247
|
console.log(chalk17.green(" \u2714 Browser opened\n"));
|
|
19003
20248
|
} catch {
|
|
19004
20249
|
console.log(chalk17.yellow(" \u26A0 Could not open browser automatically."));
|
|
@@ -19400,7 +20645,7 @@ init_dist4();
|
|
|
19400
20645
|
init_dist();
|
|
19401
20646
|
import { Command as Command21 } from "commander";
|
|
19402
20647
|
import chalk22 from "chalk";
|
|
19403
|
-
import
|
|
20648
|
+
import fs26 from "fs";
|
|
19404
20649
|
import path30 from "path";
|
|
19405
20650
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
19406
20651
|
function parseScope(v2) {
|
|
@@ -19412,7 +20657,7 @@ function parseScope(v2) {
|
|
|
19412
20657
|
function findPackageRoot3() {
|
|
19413
20658
|
let dir = path30.dirname(fileURLToPath4(import.meta.url));
|
|
19414
20659
|
while (dir !== "/" && dir !== "") {
|
|
19415
|
-
if (
|
|
20660
|
+
if (fs26.existsSync(path30.join(dir, "package.json"))) return dir;
|
|
19416
20661
|
dir = path30.dirname(dir);
|
|
19417
20662
|
}
|
|
19418
20663
|
return process.cwd();
|
|
@@ -19609,12 +20854,12 @@ init_dist();
|
|
|
19609
20854
|
init_dist();
|
|
19610
20855
|
init_dist2();
|
|
19611
20856
|
import { Command as Command23 } from "commander";
|
|
19612
|
-
import
|
|
20857
|
+
import fs27 from "fs";
|
|
19613
20858
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
19614
|
-
import { dirname as
|
|
20859
|
+
import { dirname as dirname8, join as join30 } from "path";
|
|
19615
20860
|
import chalk23 from "chalk";
|
|
19616
20861
|
function runConfigCheck(opts) {
|
|
19617
|
-
const raw = JSON.parse(
|
|
20862
|
+
const raw = JSON.parse(fs27.readFileSync(opts.configPath, "utf-8"));
|
|
19618
20863
|
const def = getDefaultConfig();
|
|
19619
20864
|
const items = detectDrift(raw, def);
|
|
19620
20865
|
let working = raw;
|
|
@@ -19702,7 +20947,7 @@ function printItems(items) {
|
|
|
19702
20947
|
var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
|
|
19703
20948
|
configCommand.command("check").description("Detect config drift vs the current default schema").option("--fix", "Apply the safe tier (add missing, remove deprecated)", false).option("--accept", "Stamp the current version without changing config (dismiss advisories)", false).option("--json", "Output drift items as JSON", false).action((opts) => {
|
|
19704
20949
|
const pkgVersion = readPkgVersion2();
|
|
19705
|
-
if (!
|
|
20950
|
+
if (!fs27.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
19706
20951
|
console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
|
|
19707
20952
|
return;
|
|
19708
20953
|
}
|
|
@@ -19747,14 +20992,14 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
19747
20992
|
}
|
|
19748
20993
|
});
|
|
19749
20994
|
function readPkgVersion2() {
|
|
19750
|
-
const pkgPath =
|
|
19751
|
-
return JSON.parse(
|
|
20995
|
+
const pkgPath = join30(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
20996
|
+
return JSON.parse(fs27.readFileSync(pkgPath, "utf-8")).version;
|
|
19752
20997
|
}
|
|
19753
20998
|
|
|
19754
20999
|
// packages/cli/src/commands/heal.ts
|
|
19755
21000
|
init_dist();
|
|
19756
21001
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
19757
|
-
import { join as
|
|
21002
|
+
import { join as join31 } from "path";
|
|
19758
21003
|
import { homedir as homedir17 } from "os";
|
|
19759
21004
|
import { Command as Command24 } from "commander";
|
|
19760
21005
|
import chalk24 from "chalk";
|
|
@@ -19994,8 +21239,8 @@ var healCommand = new Command24("heal").description("Targeted, idempotent remedi
|
|
|
19994
21239
|
}
|
|
19995
21240
|
const config = loadConfig();
|
|
19996
21241
|
const projects = opts.all ? Object.keys(config.projects) : [project];
|
|
19997
|
-
const stateRoot =
|
|
19998
|
-
const registry = new LivenessRegistry({ path:
|
|
21242
|
+
const stateRoot = join31(homedir17(), ".config", "squadrant", "state");
|
|
21243
|
+
const registry = new LivenessRegistry({ path: join31(stateRoot, "liveness.json") });
|
|
19999
21244
|
registry.load();
|
|
20000
21245
|
const code = await runHealCaptain(projects, {
|
|
20001
21246
|
liveness: readCmuxLiveness,
|
|
@@ -20027,7 +21272,7 @@ import chalk26 from "chalk";
|
|
|
20027
21272
|
init_dist();
|
|
20028
21273
|
init_dist2();
|
|
20029
21274
|
import { Command as Command25 } from "commander";
|
|
20030
|
-
import { execSync as
|
|
21275
|
+
import { execSync as execSync14 } from "child_process";
|
|
20031
21276
|
import chalk25 from "chalk";
|
|
20032
21277
|
async function runDispatch(toProject, task, opts) {
|
|
20033
21278
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
@@ -20043,7 +21288,7 @@ async function runDispatch(toProject, task, opts) {
|
|
|
20043
21288
|
warmupTimeoutMs: opts.warmupTimeout,
|
|
20044
21289
|
bootCaptain: async (project) => {
|
|
20045
21290
|
try {
|
|
20046
|
-
|
|
21291
|
+
execSync14(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
|
|
20047
21292
|
} catch {
|
|
20048
21293
|
throw new Error(`failed to launch captain for '${project}' \u2014 is squadrant installed?`);
|
|
20049
21294
|
}
|
|
@@ -20079,7 +21324,7 @@ init_dist();
|
|
|
20079
21324
|
init_dist2();
|
|
20080
21325
|
init_runtime2();
|
|
20081
21326
|
init_require_daemon();
|
|
20082
|
-
import { join as
|
|
21327
|
+
import { join as join33, dirname as dirname9 } from "path";
|
|
20083
21328
|
import { homedir as homedir18 } from "os";
|
|
20084
21329
|
import { Command as Command27 } from "commander";
|
|
20085
21330
|
import chalk28 from "chalk";
|
|
@@ -20088,23 +21333,23 @@ import chalk28 from "chalk";
|
|
|
20088
21333
|
init_dist3();
|
|
20089
21334
|
init_dist2();
|
|
20090
21335
|
import { createServer as createServer6, connect as netConnect2 } from "net";
|
|
20091
|
-
import
|
|
20092
|
-
import { join as
|
|
21336
|
+
import fs28 from "fs";
|
|
21337
|
+
import { join as join32 } from "path";
|
|
20093
21338
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
20094
21339
|
import chalk27 from "chalk";
|
|
20095
21340
|
var shared;
|
|
20096
|
-
var registryEntryPath = () =>
|
|
21341
|
+
var registryEntryPath = () => join32(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
20097
21342
|
function unregisterSenderIdentity() {
|
|
20098
21343
|
try {
|
|
20099
|
-
|
|
21344
|
+
fs28.unlinkSync(registryEntryPath());
|
|
20100
21345
|
} catch {
|
|
20101
21346
|
}
|
|
20102
21347
|
}
|
|
20103
21348
|
function registerSenderIdentity(socketPath2) {
|
|
20104
21349
|
try {
|
|
20105
21350
|
unregisterSenderIdentity();
|
|
20106
|
-
|
|
20107
|
-
|
|
21351
|
+
fs28.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
21352
|
+
fs28.writeFileSync(
|
|
20108
21353
|
registryEntryPath(),
|
|
20109
21354
|
JSON.stringify({
|
|
20110
21355
|
pid: process.pid,
|
|
@@ -20130,7 +21375,7 @@ async function sharedReceiptListener() {
|
|
|
20130
21375
|
// must never be the reason we refuse to start.
|
|
20131
21376
|
unlinkStale: (p) => {
|
|
20132
21377
|
try {
|
|
20133
|
-
|
|
21378
|
+
fs28.unlinkSync(p);
|
|
20134
21379
|
} catch {
|
|
20135
21380
|
}
|
|
20136
21381
|
},
|
|
@@ -20200,7 +21445,7 @@ async function runPing(project, message) {
|
|
|
20200
21445
|
await requireDaemon();
|
|
20201
21446
|
await needRef(resolved);
|
|
20202
21447
|
const mode = resolveCaptainChannelMode(config.defaults);
|
|
20203
|
-
const stateRoot =
|
|
21448
|
+
const stateRoot = join33(homedir18(), ".config", "squadrant", "state");
|
|
20204
21449
|
const { channels, agentFor } = mode === "off" ? { channels: {}, agentFor: () => void 0 } : await buildCaptainChannels({ stateRoot, configAgent: config.defaults.roles?.captain?.agent });
|
|
20205
21450
|
const channel = channels[agentFor(project) ?? ""];
|
|
20206
21451
|
const { handled, outcome } = await deliverToCaptain(project, message, {
|
|
@@ -20209,7 +21454,7 @@ async function runPing(project, message) {
|
|
|
20209
21454
|
log: (m) => console.error(chalk28.dim(m))
|
|
20210
21455
|
});
|
|
20211
21456
|
if (!handled) {
|
|
20212
|
-
const stateRoot2 =
|
|
21457
|
+
const stateRoot2 = join33(dirname9(DEFAULT_CONFIG_PATH), "state");
|
|
20213
21458
|
await appendCaptainMessage({
|
|
20214
21459
|
stateRoot: stateRoot2,
|
|
20215
21460
|
project,
|
|
@@ -20288,7 +21533,7 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
|
|
|
20288
21533
|
// packages/cli/src/commands/effort.ts
|
|
20289
21534
|
init_dist();
|
|
20290
21535
|
init_dist2();
|
|
20291
|
-
import
|
|
21536
|
+
import fs29 from "fs";
|
|
20292
21537
|
import path31 from "path";
|
|
20293
21538
|
import { Command as Command29 } from "commander";
|
|
20294
21539
|
import chalk30 from "chalk";
|
|
@@ -20332,7 +21577,7 @@ function effortScopeLabel(projectName) {
|
|
|
20332
21577
|
}
|
|
20333
21578
|
function canonical(p) {
|
|
20334
21579
|
try {
|
|
20335
|
-
return
|
|
21580
|
+
return fs29.realpathSync(p);
|
|
20336
21581
|
} catch {
|
|
20337
21582
|
return path31.resolve(p);
|
|
20338
21583
|
}
|
|
@@ -20394,7 +21639,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
|
|
|
20394
21639
|
|
|
20395
21640
|
// packages/cli/src/commands/tokens.ts
|
|
20396
21641
|
init_dist();
|
|
20397
|
-
import
|
|
21642
|
+
import fs30 from "fs";
|
|
20398
21643
|
import path32 from "path";
|
|
20399
21644
|
import os18 from "os";
|
|
20400
21645
|
import readline3 from "readline";
|
|
@@ -20450,7 +21695,7 @@ function foldTranscriptLine(agg, rawLine, state) {
|
|
|
20450
21695
|
async function aggregateTranscriptFile(filePath) {
|
|
20451
21696
|
const agg = emptySessionAggregate();
|
|
20452
21697
|
const state = { lastCacheRead: null };
|
|
20453
|
-
const rl = readline3.createInterface({ input:
|
|
21698
|
+
const rl = readline3.createInterface({ input: fs30.createReadStream(filePath), crlfDelay: Infinity });
|
|
20454
21699
|
for await (const line of rl) {
|
|
20455
21700
|
foldTranscriptLine(agg, line, state);
|
|
20456
21701
|
}
|
|
@@ -20518,7 +21763,7 @@ function buildRoleReport(role, sessions) {
|
|
|
20518
21763
|
}
|
|
20519
21764
|
async function readdirSafe(dir) {
|
|
20520
21765
|
try {
|
|
20521
|
-
return await
|
|
21766
|
+
return await fs30.promises.readdir(dir);
|
|
20522
21767
|
} catch {
|
|
20523
21768
|
return [];
|
|
20524
21769
|
}
|
|
@@ -20685,12 +21930,12 @@ var tokensCommand = new Command30("tokens").description(
|
|
|
20685
21930
|
// packages/cli/src/commands/telegram.ts
|
|
20686
21931
|
init_dist();
|
|
20687
21932
|
init_dist2();
|
|
20688
|
-
import { join as
|
|
21933
|
+
import { join as join34, dirname as dirname10 } from "path";
|
|
20689
21934
|
import { emitKeypressEvents } from "readline";
|
|
20690
21935
|
import { Command as Command31 } from "commander";
|
|
20691
21936
|
import chalk32 from "chalk";
|
|
20692
21937
|
function defaultStateRoot() {
|
|
20693
|
-
return
|
|
21938
|
+
return join34(dirname10(DEFAULT_CONFIG_PATH), "state");
|
|
20694
21939
|
}
|
|
20695
21940
|
async function questionMasked() {
|
|
20696
21941
|
return new Promise((resolve4) => {
|
|
@@ -21019,7 +22264,7 @@ import { Command as Command33 } from "commander";
|
|
|
21019
22264
|
import { homedir as homedir19 } from "os";
|
|
21020
22265
|
|
|
21021
22266
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
21022
|
-
import
|
|
22267
|
+
import fs31 from "fs";
|
|
21023
22268
|
import path33 from "path";
|
|
21024
22269
|
|
|
21025
22270
|
// packages/cli/src/lib/handoff-facts.ts
|
|
@@ -21071,15 +22316,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
|
|
|
21071
22316
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
21072
22317
|
var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
|
|
21073
22318
|
function appendCaptainSession(spokeVault, record) {
|
|
21074
|
-
|
|
22319
|
+
fs31.mkdirSync(spokeVault, { recursive: true });
|
|
21075
22320
|
const file = path33.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
21076
|
-
|
|
22321
|
+
fs31.appendFileSync(file, JSON.stringify(record) + "\n");
|
|
21077
22322
|
}
|
|
21078
22323
|
function readCaptainSessionRegistry(spokeVault) {
|
|
21079
22324
|
const file = path33.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
21080
|
-
if (!
|
|
22325
|
+
if (!fs31.existsSync(file)) return [];
|
|
21081
22326
|
const records = [];
|
|
21082
|
-
for (const line of
|
|
22327
|
+
for (const line of fs31.readFileSync(file, "utf-8").split("\n")) {
|
|
21083
22328
|
if (!line.trim()) continue;
|
|
21084
22329
|
try {
|
|
21085
22330
|
records.push(JSON.parse(line));
|
|
@@ -21110,9 +22355,8 @@ async function runGatePermissionRequest(deps) {
|
|
|
21110
22355
|
const p = payload;
|
|
21111
22356
|
const taskId = env.SQUADRANT_CREW_TASK_ID;
|
|
21112
22357
|
const project = env.SQUADRANT_CREW_PROJECT;
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
return { decision: "yield", reason: "not a squadrant crew/side session" };
|
|
22358
|
+
if (!isGateSession(env)) {
|
|
22359
|
+
return { decision: "yield", reason: "not a squadrant crew/side/captain session" };
|
|
21116
22360
|
}
|
|
21117
22361
|
const stdout = deps.stdout ?? (() => {
|
|
21118
22362
|
});
|
|
@@ -21457,7 +22701,7 @@ import os19 from "os";
|
|
|
21457
22701
|
// packages/cli/src/lib/handoff-live-repo.ts
|
|
21458
22702
|
init_dist();
|
|
21459
22703
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
21460
|
-
import
|
|
22704
|
+
import fs32 from "fs";
|
|
21461
22705
|
import path35 from "path";
|
|
21462
22706
|
|
|
21463
22707
|
// packages/cli/src/lib/handoff-branch-state.ts
|
|
@@ -21597,7 +22841,7 @@ function localAheadOfBase(runner, projectPath, base) {
|
|
|
21597
22841
|
}
|
|
21598
22842
|
function readFetchAgeMs(projectPath, now) {
|
|
21599
22843
|
try {
|
|
21600
|
-
const stat2 =
|
|
22844
|
+
const stat2 = fs32.statSync(path35.join(projectPath, ".git", "FETCH_HEAD"));
|
|
21601
22845
|
return Math.max(0, now - stat2.mtime.getTime());
|
|
21602
22846
|
} catch {
|
|
21603
22847
|
return null;
|
|
@@ -21668,7 +22912,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
|
|
|
21668
22912
|
|
|
21669
22913
|
// packages/cli/src/lib/handoff-claude-mem.ts
|
|
21670
22914
|
import { createRequire } from "module";
|
|
21671
|
-
import
|
|
22915
|
+
import fs33 from "fs";
|
|
21672
22916
|
var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
|
|
21673
22917
|
var CLAUDE_MEM_RECENCY_LIMIT = 20;
|
|
21674
22918
|
function decisionText(row) {
|
|
@@ -21682,7 +22926,7 @@ function decisionText(row) {
|
|
|
21682
22926
|
return row.narrative ?? "";
|
|
21683
22927
|
}
|
|
21684
22928
|
function queryClaudeMem(dbPath, project) {
|
|
21685
|
-
if (!
|
|
22929
|
+
if (!fs33.existsSync(dbPath)) return null;
|
|
21686
22930
|
let db;
|
|
21687
22931
|
try {
|
|
21688
22932
|
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
@@ -21725,7 +22969,7 @@ function queryClaudeMem(dbPath, project) {
|
|
|
21725
22969
|
}
|
|
21726
22970
|
|
|
21727
22971
|
// packages/cli/src/lib/handoff-transcript.ts
|
|
21728
|
-
import
|
|
22972
|
+
import fs34 from "fs";
|
|
21729
22973
|
var TRANSCRIPT_BYTE_CAP = 2e5;
|
|
21730
22974
|
var DIGEST_BYTE_CAP = 8 * 1024;
|
|
21731
22975
|
var PROMPT_TRUNCATE_CHARS = 300;
|
|
@@ -21834,27 +23078,27 @@ function extractMessages(tailText) {
|
|
|
21834
23078
|
return { lastUserMessage, lastAssistantText, digest };
|
|
21835
23079
|
}
|
|
21836
23080
|
function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
|
|
21837
|
-
if (!
|
|
21838
|
-
const content =
|
|
23081
|
+
if (!fs34.existsSync(transcriptPath)) return null;
|
|
23082
|
+
const content = fs34.readFileSync(transcriptPath, "utf-8");
|
|
21839
23083
|
const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
|
|
21840
|
-
const mtimeIso =
|
|
23084
|
+
const mtimeIso = fs34.statSync(transcriptPath).mtime.toISOString();
|
|
21841
23085
|
return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
|
|
21842
23086
|
}
|
|
21843
23087
|
|
|
21844
23088
|
// packages/cli/src/lib/handoff-archive.ts
|
|
21845
|
-
import
|
|
23089
|
+
import fs35 from "fs";
|
|
21846
23090
|
import path36 from "path";
|
|
21847
23091
|
function readNewestArchivedHandoff(spokeVault, now) {
|
|
21848
23092
|
const dir = path36.join(spokeVault, "handoffs");
|
|
21849
|
-
if (!
|
|
21850
|
-
const candidates =
|
|
23093
|
+
if (!fs35.existsSync(dir)) return null;
|
|
23094
|
+
const candidates = fs35.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
|
|
21851
23095
|
const full = path36.join(dir, e.name);
|
|
21852
|
-
return { name: e.name, full, mtime:
|
|
23096
|
+
return { name: e.name, full, mtime: fs35.statSync(full).mtime };
|
|
21853
23097
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
21854
23098
|
for (const candidate of candidates) {
|
|
21855
23099
|
let content;
|
|
21856
23100
|
try {
|
|
21857
|
-
content = JSON.parse(
|
|
23101
|
+
content = JSON.parse(fs35.readFileSync(candidate.full, "utf-8"));
|
|
21858
23102
|
} catch {
|
|
21859
23103
|
continue;
|
|
21860
23104
|
}
|
|
@@ -22197,15 +23441,15 @@ init_dist();
|
|
|
22197
23441
|
init_dist();
|
|
22198
23442
|
init_dist();
|
|
22199
23443
|
init_dist();
|
|
22200
|
-
var __dirname =
|
|
22201
|
-
var pkg = JSON.parse(readFileSync21(
|
|
23444
|
+
var __dirname = dirname11(fileURLToPath6(import.meta.url));
|
|
23445
|
+
var pkg = JSON.parse(readFileSync21(join35(__dirname, "..", "package.json"), "utf-8"));
|
|
22202
23446
|
ensureRuntimeSynced({
|
|
22203
|
-
sourceRoot:
|
|
22204
|
-
runtimeRoot:
|
|
23447
|
+
sourceRoot: join35(__dirname, ".."),
|
|
23448
|
+
runtimeRoot: join35(homedir20(), ".config", "squadrant")
|
|
22205
23449
|
});
|
|
22206
23450
|
if (!process.env.VITEST) {
|
|
22207
23451
|
try {
|
|
22208
|
-
syncShippedOpencodeSkills({ pkgRoot:
|
|
23452
|
+
syncShippedOpencodeSkills({ pkgRoot: join35(__dirname, "..") });
|
|
22209
23453
|
} catch (err) {
|
|
22210
23454
|
process.stderr.write(
|
|
22211
23455
|
`squadrant: opencode skills sync skipped: ${err.message}
|
|
@@ -22215,7 +23459,7 @@ if (!process.env.VITEST) {
|
|
|
22215
23459
|
}
|
|
22216
23460
|
if (process.argv[2] !== "config") {
|
|
22217
23461
|
try {
|
|
22218
|
-
const cfgPath =
|
|
23462
|
+
const cfgPath = join35(homedir20(), ".config", "squadrant", "config.json");
|
|
22219
23463
|
if (existsSync14(cfgPath)) {
|
|
22220
23464
|
const cfg = JSON.parse(readConfigFileSync(cfgPath));
|
|
22221
23465
|
if (needsCheck(cfg, pkg.version)) {
|