squadrant 0.21.1 → 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 +1379 -323
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +783 -184
- 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
|
|
|
@@ -5431,8 +5696,9 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
5431
5696
|
if (mode !== "off")
|
|
5432
5697
|
throw new DeferDelivery(null, "no-channel");
|
|
5433
5698
|
}
|
|
5699
|
+
const paneOpts = { ...sendOpts, agent };
|
|
5434
5700
|
try {
|
|
5435
|
-
return await cmux2.send(surface, text,
|
|
5701
|
+
return await cmux2.send(surface, text, paneOpts);
|
|
5436
5702
|
} catch (e) {
|
|
5437
5703
|
if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
|
|
5438
5704
|
throw e;
|
|
@@ -5444,7 +5710,7 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
5444
5710
|
}
|
|
5445
5711
|
log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
|
|
5446
5712
|
surface = next;
|
|
5447
|
-
return cmux2.send(next, text,
|
|
5713
|
+
return cmux2.send(next, text, paneOpts);
|
|
5448
5714
|
}
|
|
5449
5715
|
});
|
|
5450
5716
|
if ("delivered" in result) {
|
|
@@ -6375,16 +6641,26 @@ function createRunCommand(cliBin) {
|
|
|
6375
6641
|
}
|
|
6376
6642
|
};
|
|
6377
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
|
+
}
|
|
6378
6654
|
function isCaptainAliveFromHealth(rows, project) {
|
|
6379
|
-
return rows
|
|
6655
|
+
return captainAliveStateFromHealth(rows, project) === "alive";
|
|
6380
6656
|
}
|
|
6381
6657
|
function createIsCaptainAlive(sock) {
|
|
6382
6658
|
return async (project) => {
|
|
6383
6659
|
try {
|
|
6384
6660
|
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
6385
|
-
return
|
|
6661
|
+
return captainAliveStateFromHealth(health ?? [], project);
|
|
6386
6662
|
} catch {
|
|
6387
|
-
return
|
|
6663
|
+
return "unknown";
|
|
6388
6664
|
}
|
|
6389
6665
|
};
|
|
6390
6666
|
}
|
|
@@ -6421,16 +6697,19 @@ function createEnsureCaptainAlive(deps) {
|
|
|
6421
6697
|
const now = deps.now ?? (() => Date.now());
|
|
6422
6698
|
const inFlight = /* @__PURE__ */ new Map();
|
|
6423
6699
|
async function run(project) {
|
|
6424
|
-
|
|
6700
|
+
const first = await deps.isAlive(project);
|
|
6701
|
+
if (first === "alive")
|
|
6425
6702
|
return "alive";
|
|
6426
6703
|
await deps.launch(project);
|
|
6427
6704
|
const deadline = now() + warmupTimeoutMs;
|
|
6705
|
+
let last = first;
|
|
6428
6706
|
while (now() < deadline) {
|
|
6429
|
-
|
|
6707
|
+
last = await deps.isAlive(project);
|
|
6708
|
+
if (last === "alive")
|
|
6430
6709
|
return "launched";
|
|
6431
6710
|
await sleep3(pollMs);
|
|
6432
6711
|
}
|
|
6433
|
-
return "timeout";
|
|
6712
|
+
return last === "dead" ? "timeout" : "unknown";
|
|
6434
6713
|
}
|
|
6435
6714
|
return function ensure(project) {
|
|
6436
6715
|
const existing = inFlight.get(project);
|
|
@@ -6485,6 +6764,26 @@ ${ev.question}`;
|
|
|
6485
6764
|
function formatInbound(text) {
|
|
6486
6765
|
return `\u{1F4E9} [from Telegram] ${text}`;
|
|
6487
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
|
+
}
|
|
6488
6787
|
function formatUsageLine(u) {
|
|
6489
6788
|
const reqs = u.requests === 1 ? "1 req" : `${u.requests} reqs`;
|
|
6490
6789
|
const named = Object.keys(u.models).filter((m) => m !== "unknown").sort((a, b) => u.models[b].costUsd - u.models[a].costUsd);
|
|
@@ -6496,8 +6795,19 @@ function maskToken(token) {
|
|
|
6496
6795
|
return token;
|
|
6497
6796
|
return "*".repeat(token.length - 4) + token.slice(-4);
|
|
6498
6797
|
}
|
|
6798
|
+
var MEDIA_KINDS;
|
|
6499
6799
|
var init_format = __esm({
|
|
6500
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
|
+
];
|
|
6501
6811
|
}
|
|
6502
6812
|
});
|
|
6503
6813
|
|
|
@@ -6520,6 +6830,8 @@ function loadState(stateRoot) {
|
|
|
6520
6830
|
};
|
|
6521
6831
|
if (typeof data.lastUserId === "number")
|
|
6522
6832
|
result.lastUserId = data.lastUserId;
|
|
6833
|
+
if (data.pending && Object.keys(data.pending).length > 0)
|
|
6834
|
+
result.pending = data.pending;
|
|
6523
6835
|
return result;
|
|
6524
6836
|
} catch {
|
|
6525
6837
|
return { offset: 0, topics: {}, notify: {} };
|
|
@@ -6533,6 +6845,19 @@ function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
|
6533
6845
|
s.topics[topicKey(project, scope)] = topicId;
|
|
6534
6846
|
saveState(stateRoot, s);
|
|
6535
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
|
+
}
|
|
6536
6861
|
function isNotifyActive(stateRoot, project) {
|
|
6537
6862
|
return loadState(stateRoot).notify[project] === true;
|
|
6538
6863
|
}
|
|
@@ -6546,6 +6871,42 @@ function setNotify(stateRoot, project, active) {
|
|
|
6546
6871
|
s.notify[project] = active;
|
|
6547
6872
|
saveState(stateRoot, s);
|
|
6548
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
|
+
}
|
|
6549
6910
|
function findProjectByThread(stateRoot, threadId) {
|
|
6550
6911
|
const s = loadState(stateRoot);
|
|
6551
6912
|
for (const [key, id] of Object.entries(s.topics)) {
|
|
@@ -6568,17 +6929,18 @@ var init_state = __esm({
|
|
|
6568
6929
|
function createTelegramClient(opts) {
|
|
6569
6930
|
const fetchImpl = opts.fetch ?? fetch;
|
|
6570
6931
|
const base = `https://api.telegram.org/bot${opts.token}`;
|
|
6571
|
-
async function call(method, body) {
|
|
6932
|
+
async function call(method, body, signal) {
|
|
6572
6933
|
const res = await fetchImpl(`${base}/${method}`, {
|
|
6573
6934
|
method: "POST",
|
|
6574
6935
|
headers: { "content-type": "application/json" },
|
|
6575
|
-
body: JSON.stringify(body)
|
|
6936
|
+
body: JSON.stringify(body),
|
|
6937
|
+
...signal ? { signal } : {}
|
|
6576
6938
|
});
|
|
6577
6939
|
const json = await res.json();
|
|
6578
6940
|
if (!res.ok || !json.ok) {
|
|
6579
6941
|
const code = json.error_code ?? res.status;
|
|
6580
6942
|
const desc = json.description ?? "unknown error";
|
|
6581
|
-
throw new
|
|
6943
|
+
throw new TelegramApiError(code, `telegram ${method} failed (${code}): ${desc}`, json.parameters?.retry_after);
|
|
6582
6944
|
}
|
|
6583
6945
|
return json.result;
|
|
6584
6946
|
}
|
|
@@ -6587,8 +6949,8 @@ function createTelegramClient(opts) {
|
|
|
6587
6949
|
const r = await call("getMe", {});
|
|
6588
6950
|
return { id: r.id, username: r.username };
|
|
6589
6951
|
},
|
|
6590
|
-
getUpdates(offset, timeoutSec = 50) {
|
|
6591
|
-
return call("getUpdates", { offset, timeout: timeoutSec });
|
|
6952
|
+
getUpdates(offset, timeoutSec = 50, signal) {
|
|
6953
|
+
return call("getUpdates", { offset, timeout: timeoutSec }, signal);
|
|
6592
6954
|
},
|
|
6593
6955
|
async sendMessage(chatId, threadId, text, replyMarkup) {
|
|
6594
6956
|
const body = { chat_id: chatId, text };
|
|
@@ -6611,6 +6973,9 @@ function createTelegramClient(opts) {
|
|
|
6611
6973
|
const r = await call("createForumTopic", { chat_id: chatId, name });
|
|
6612
6974
|
return r.message_thread_id;
|
|
6613
6975
|
},
|
|
6976
|
+
async deleteForumTopic(chatId, threadId) {
|
|
6977
|
+
await call("deleteForumTopic", { chat_id: chatId, message_thread_id: threadId });
|
|
6978
|
+
},
|
|
6614
6979
|
async setMyCommands(commands) {
|
|
6615
6980
|
await call("setMyCommands", { commands });
|
|
6616
6981
|
},
|
|
@@ -6619,11 +6984,241 @@ function createTelegramClient(opts) {
|
|
|
6619
6984
|
if (threadId !== void 0)
|
|
6620
6985
|
body.message_thread_id = threadId;
|
|
6621
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
|
+
});
|
|
6622
6994
|
}
|
|
6623
6995
|
};
|
|
6624
6996
|
}
|
|
6997
|
+
var TelegramApiError;
|
|
6625
6998
|
var init_client = __esm({
|
|
6626
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();
|
|
6627
7222
|
}
|
|
6628
7223
|
});
|
|
6629
7224
|
|
|
@@ -6711,8 +7306,10 @@ var init_tiers = __esm({
|
|
|
6711
7306
|
});
|
|
6712
7307
|
|
|
6713
7308
|
// packages/core/dist/telegram/bridge.js
|
|
7309
|
+
import fs11 from "fs";
|
|
6714
7310
|
import os5 from "os";
|
|
6715
7311
|
import path12 from "path";
|
|
7312
|
+
import crypto2 from "crypto";
|
|
6716
7313
|
function formatInboundReceipt(project, outcome) {
|
|
6717
7314
|
if (!outcome)
|
|
6718
7315
|
return void 0;
|
|
@@ -6728,6 +7325,85 @@ function formatInboundReceipt(project, outcome) {
|
|
|
6728
7325
|
return void 0;
|
|
6729
7326
|
}
|
|
6730
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
|
+
}
|
|
6731
7407
|
function parseNotifyPref(text) {
|
|
6732
7408
|
const parts = text.trim().split(/\s+/);
|
|
6733
7409
|
if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
|
|
@@ -6754,9 +7430,17 @@ function notifyToggle(text) {
|
|
|
6754
7430
|
}
|
|
6755
7431
|
function createTelegramBridge(opts) {
|
|
6756
7432
|
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
7433
|
+
const lifecycle = opts.lifecycle;
|
|
6757
7434
|
const configRoot = opts.configRoot ?? path12.join(os5.homedir(), ".config", "squadrant");
|
|
6758
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;
|
|
6759
7440
|
let running = false;
|
|
7441
|
+
let pollStopped = false;
|
|
7442
|
+
let selfHealCancel = null;
|
|
7443
|
+
let conflictCount = 0;
|
|
6760
7444
|
let lastSuccessfulPollAt = null;
|
|
6761
7445
|
let lastError = null;
|
|
6762
7446
|
let lastErrorAt = null;
|
|
@@ -6766,12 +7450,8 @@ function createTelegramBridge(opts) {
|
|
|
6766
7450
|
saveState(stateRoot, s);
|
|
6767
7451
|
}
|
|
6768
7452
|
async function sendToTopic(project, text) {
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
6772
|
-
setTopic(stateRoot, project, threadId);
|
|
6773
|
-
}
|
|
6774
|
-
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
7453
|
+
const { topicId } = await runTelegramLink({ project, cfg, client, stateRoot });
|
|
7454
|
+
await client.sendMessage(cfg.supergroupId, topicId, text);
|
|
6775
7455
|
}
|
|
6776
7456
|
async function deliverOutbound(project, ev) {
|
|
6777
7457
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -6958,10 +7638,13 @@ ${formatUsageLine(usage2)}`;
|
|
|
6958
7638
|
}
|
|
6959
7639
|
await runChannelCommand(text, fromId, void 0);
|
|
6960
7640
|
}
|
|
6961
|
-
async function handleProjectTopic(text, threadId, fromId) {
|
|
7641
|
+
async function handleProjectTopic(text, threadId, fromId, messageId, media) {
|
|
6962
7642
|
const resolved = findProjectByThread(stateRoot, threadId);
|
|
6963
|
-
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>");
|
|
6964
7646
|
return;
|
|
7647
|
+
}
|
|
6965
7648
|
if (isBareSpawn(text)) {
|
|
6966
7649
|
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
6967
7650
|
await reply(threadId, "\u26D4 not authorized");
|
|
@@ -7011,21 +7694,16 @@ ${formatUsageLine(usage2)}`;
|
|
|
7011
7694
|
await runChannelCommand(text, fromId, threadId);
|
|
7012
7695
|
return;
|
|
7013
7696
|
}
|
|
7014
|
-
|
|
7015
|
-
log(`telegram
|
|
7016
|
-
}
|
|
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
|
+
}
|
|
7017
7700
|
setNotify(stateRoot, resolved.project, true);
|
|
7018
7701
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
7019
|
-
|
|
7020
|
-
const r = await ensureCaptainAlive(resolved.project);
|
|
7702
|
+
void ensureCaptainAlive(resolved.project).then((r) => {
|
|
7021
7703
|
if (r === "timeout") {
|
|
7022
|
-
|
|
7023
|
-
} else {
|
|
7024
|
-
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`);
|
|
7025
7705
|
}
|
|
7026
|
-
}
|
|
7027
|
-
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
7028
|
-
}
|
|
7706
|
+
}).catch((e) => log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`));
|
|
7029
7707
|
}
|
|
7030
7708
|
let handled = false;
|
|
7031
7709
|
let outcome;
|
|
@@ -7034,13 +7712,22 @@ ${formatUsageLine(usage2)}`;
|
|
|
7034
7712
|
handled = res.handled;
|
|
7035
7713
|
outcome = res.outcome;
|
|
7036
7714
|
}
|
|
7715
|
+
const deliveryHeld = outcome?.status === "held";
|
|
7037
7716
|
if (!handled) {
|
|
7038
7717
|
await appendCaptainMessage2({ stateRoot, project: resolved.project, text: formatInbound(text), source: "telegram" });
|
|
7718
|
+
outcome = void 0;
|
|
7039
7719
|
}
|
|
7040
7720
|
const receipt = formatInboundReceipt(resolved.project, outcome);
|
|
7041
7721
|
if (receipt && sendReply) {
|
|
7042
7722
|
await sendReply(threadId, receipt).catch((e) => log(`telegram receipt failed project=${resolved.project}: ${e.message}`));
|
|
7043
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);
|
|
7044
7731
|
}
|
|
7045
7732
|
async function handleUpdate(u) {
|
|
7046
7733
|
if (u.callback_query) {
|
|
@@ -7048,7 +7735,11 @@ ${formatUsageLine(usage2)}`;
|
|
|
7048
7735
|
return;
|
|
7049
7736
|
}
|
|
7050
7737
|
const m = u.message;
|
|
7051
|
-
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)
|
|
7052
7743
|
return;
|
|
7053
7744
|
if (!cfg.chats.includes(m.chat.id))
|
|
7054
7745
|
return;
|
|
@@ -7062,7 +7753,7 @@ ${formatUsageLine(usage2)}`;
|
|
|
7062
7753
|
await reply(threadId, "\u26D4 not authorized");
|
|
7063
7754
|
return;
|
|
7064
7755
|
}
|
|
7065
|
-
const task =
|
|
7756
|
+
const task = (text ?? "").trim();
|
|
7066
7757
|
if (!task) {
|
|
7067
7758
|
await reply(threadId, "spawn cancelled \u2014 empty task");
|
|
7068
7759
|
return;
|
|
@@ -7073,39 +7764,122 @@ ${formatUsageLine(usage2)}`;
|
|
|
7073
7764
|
return;
|
|
7074
7765
|
}
|
|
7075
7766
|
if (m.message_thread_id === void 0) {
|
|
7767
|
+
if (m.text === void 0)
|
|
7768
|
+
return;
|
|
7076
7769
|
await handleGeneral(m.text, m.from?.id);
|
|
7077
7770
|
return;
|
|
7078
7771
|
}
|
|
7079
|
-
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);
|
|
7080
7799
|
}
|
|
7081
7800
|
async function pollLoop() {
|
|
7082
7801
|
while (running) {
|
|
7802
|
+
const ac = new AbortController();
|
|
7803
|
+
pollAbort = ac;
|
|
7804
|
+
let waitMs = pollMs;
|
|
7083
7805
|
try {
|
|
7084
7806
|
const offset = loadState(stateRoot).offset;
|
|
7085
|
-
const updates = await client.getUpdates(offset, LONG_POLL_SEC);
|
|
7807
|
+
const updates = await client.getUpdates(offset, LONG_POLL_SEC, ac.signal);
|
|
7808
|
+
conflictCount = 0;
|
|
7086
7809
|
for (const u of updates) {
|
|
7087
7810
|
await handleUpdate(u);
|
|
7088
7811
|
persistOffset(u.update_id + 1);
|
|
7089
7812
|
}
|
|
7090
7813
|
lastSuccessfulPollAt = Date.now();
|
|
7091
7814
|
} catch (e) {
|
|
7092
|
-
|
|
7815
|
+
if (ac.signal.aborted)
|
|
7816
|
+
break;
|
|
7817
|
+
const msg = e.message;
|
|
7818
|
+
lastError = msg;
|
|
7093
7819
|
lastErrorAt = Date.now();
|
|
7094
|
-
|
|
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;
|
|
7095
7832
|
}
|
|
7096
7833
|
if (running)
|
|
7097
|
-
await sleep2(
|
|
7834
|
+
await sleep2(waitMs);
|
|
7098
7835
|
}
|
|
7836
|
+
running = false;
|
|
7837
|
+
releasePollLock?.();
|
|
7838
|
+
releasePollLock = null;
|
|
7839
|
+
if (!pollStopped)
|
|
7840
|
+
scheduleSelfHeal();
|
|
7099
7841
|
}
|
|
7100
7842
|
return {
|
|
7101
7843
|
start() {
|
|
7102
7844
|
if (running)
|
|
7103
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
|
+
}
|
|
7104
7861
|
running = true;
|
|
7862
|
+
conflictCount = 0;
|
|
7863
|
+
try {
|
|
7864
|
+
lifecycle?.start();
|
|
7865
|
+
} catch (e) {
|
|
7866
|
+
log(`telegram lifecycle start failed: ${e.message}`);
|
|
7867
|
+
}
|
|
7105
7868
|
void pollLoop();
|
|
7106
7869
|
},
|
|
7107
7870
|
stop() {
|
|
7108
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;
|
|
7109
7883
|
},
|
|
7110
7884
|
pushLifecycle(project, ev) {
|
|
7111
7885
|
void deliverOutbound(project, ev).catch((e) => {
|
|
@@ -7122,17 +7896,23 @@ ${formatUsageLine(usage2)}`;
|
|
|
7122
7896
|
}
|
|
7123
7897
|
};
|
|
7124
7898
|
}
|
|
7125
|
-
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;
|
|
7126
7900
|
var init_bridge = __esm({
|
|
7127
7901
|
"packages/core/dist/telegram/bridge.js"() {
|
|
7128
7902
|
init_dist();
|
|
7903
|
+
init_client();
|
|
7129
7904
|
init_auth();
|
|
7130
7905
|
init_commands();
|
|
7131
7906
|
init_format();
|
|
7907
|
+
init_notify();
|
|
7132
7908
|
init_panels();
|
|
7133
7909
|
init_state();
|
|
7134
7910
|
init_tiers();
|
|
7911
|
+
CAPTAIN_RECEIVED_ACK = "\u2705 captain received";
|
|
7135
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;
|
|
7136
7916
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7137
7917
|
CREW_TIERS = ["all", "alert_only", "done_only", "none"];
|
|
7138
7918
|
RECOGNIZED_CHANNEL_COMMANDS = /* @__PURE__ */ new Set(["status", "projects", "crews", "launch", "effort", "spawn"]);
|
|
@@ -7177,7 +7957,7 @@ var init_restart_daemon = __esm({
|
|
|
7177
7957
|
});
|
|
7178
7958
|
|
|
7179
7959
|
// packages/core/dist/telegram/setup.js
|
|
7180
|
-
import
|
|
7960
|
+
import fs12 from "fs";
|
|
7181
7961
|
function resolveSetupGroup(existingSupergroupId, opts) {
|
|
7182
7962
|
if (existingSupergroupId !== void 0 && !opts.redetect)
|
|
7183
7963
|
return "reuse";
|
|
@@ -7228,7 +8008,7 @@ function writeTelegramConfig(configPath, opts) {
|
|
|
7228
8008
|
let config;
|
|
7229
8009
|
let raw = null;
|
|
7230
8010
|
try {
|
|
7231
|
-
raw =
|
|
8011
|
+
raw = fs12.readFileSync(configPath, "utf-8");
|
|
7232
8012
|
} catch (err) {
|
|
7233
8013
|
if (err.code !== "ENOENT") {
|
|
7234
8014
|
throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
|
|
@@ -7267,94 +8047,6 @@ var init_setup = __esm({
|
|
|
7267
8047
|
}
|
|
7268
8048
|
});
|
|
7269
8049
|
|
|
7270
|
-
// packages/core/dist/telegram/notify.js
|
|
7271
|
-
function runTelegramStatus(opts) {
|
|
7272
|
-
const tg = opts.config.telegram;
|
|
7273
|
-
const env = opts.env ?? process.env;
|
|
7274
|
-
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
7275
|
-
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
7276
|
-
const sep3 = key.indexOf("::");
|
|
7277
|
-
return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
|
|
7278
|
-
});
|
|
7279
|
-
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
7280
|
-
}
|
|
7281
|
-
function runTelegramNotifySet(opts) {
|
|
7282
|
-
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
7283
|
-
}
|
|
7284
|
-
function runTelegramNotifyPref(args) {
|
|
7285
|
-
const { project, dimension, value, root } = args;
|
|
7286
|
-
if (dimension === "crew") {
|
|
7287
|
-
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
7288
|
-
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
7289
|
-
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
7290
|
-
return { ok: true };
|
|
7291
|
-
}
|
|
7292
|
-
if (value !== "on" && value !== "off")
|
|
7293
|
-
return { ok: false, message: "cap must be on|off" };
|
|
7294
|
-
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
7295
|
-
return { ok: true };
|
|
7296
|
-
}
|
|
7297
|
-
function runTelegramNotifyStatus(opts) {
|
|
7298
|
-
const s = loadState(opts.stateRoot);
|
|
7299
|
-
const projects = /* @__PURE__ */ new Set();
|
|
7300
|
-
for (const key of Object.keys(s.topics)) {
|
|
7301
|
-
const sep3 = key.indexOf("::");
|
|
7302
|
-
projects.add(sep3 === -1 ? key : key.slice(0, sep3));
|
|
7303
|
-
}
|
|
7304
|
-
for (const p of Object.keys(s.notify))
|
|
7305
|
-
projects.add(p);
|
|
7306
|
-
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
7307
|
-
}
|
|
7308
|
-
function capAllowed(project, globalNotify, root) {
|
|
7309
|
-
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
7310
|
-
}
|
|
7311
|
-
function confirmationText(project, before, after, dim) {
|
|
7312
|
-
if (dim === "active")
|
|
7313
|
-
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
7314
|
-
if (dim === "cap")
|
|
7315
|
-
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
7316
|
-
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
7317
|
-
}
|
|
7318
|
-
async function runNotifyConfirmation(opts) {
|
|
7319
|
-
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
7320
|
-
if (!quieter || dim === null)
|
|
7321
|
-
return false;
|
|
7322
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7323
|
-
if (topicId === void 0)
|
|
7324
|
-
return false;
|
|
7325
|
-
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
7326
|
-
try {
|
|
7327
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
7328
|
-
return true;
|
|
7329
|
-
} catch {
|
|
7330
|
-
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
7331
|
-
return false;
|
|
7332
|
-
}
|
|
7333
|
-
}
|
|
7334
|
-
async function runTelegramSend(opts) {
|
|
7335
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7336
|
-
if (topicId === void 0) {
|
|
7337
|
-
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
7338
|
-
}
|
|
7339
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
7340
|
-
return { chatId: opts.cfg.supergroupId, topicId };
|
|
7341
|
-
}
|
|
7342
|
-
async function runTelegramLink(opts) {
|
|
7343
|
-
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
7344
|
-
if (existing !== void 0)
|
|
7345
|
-
return { topicId: existing, created: false };
|
|
7346
|
-
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
7347
|
-
setTopic(opts.stateRoot, opts.project, topicId);
|
|
7348
|
-
return { topicId, created: true };
|
|
7349
|
-
}
|
|
7350
|
-
var init_notify = __esm({
|
|
7351
|
-
"packages/core/dist/telegram/notify.js"() {
|
|
7352
|
-
init_dist();
|
|
7353
|
-
init_state();
|
|
7354
|
-
init_format();
|
|
7355
|
-
}
|
|
7356
|
-
});
|
|
7357
|
-
|
|
7358
8050
|
// packages/core/dist/telegram/index.js
|
|
7359
8051
|
var init_telegram = __esm({
|
|
7360
8052
|
"packages/core/dist/telegram/index.js"() {
|
|
@@ -7366,6 +8058,7 @@ var init_telegram = __esm({
|
|
|
7366
8058
|
init_format();
|
|
7367
8059
|
init_state();
|
|
7368
8060
|
init_client();
|
|
8061
|
+
init_inbound_lifecycle();
|
|
7369
8062
|
init_bridge();
|
|
7370
8063
|
init_setup();
|
|
7371
8064
|
init_notify();
|
|
@@ -8631,7 +9324,7 @@ var init_launch_workspace = __esm({
|
|
|
8631
9324
|
});
|
|
8632
9325
|
|
|
8633
9326
|
// packages/core/dist/side-session.js
|
|
8634
|
-
import
|
|
9327
|
+
import fs13 from "fs";
|
|
8635
9328
|
function sideTitleFor(project, name) {
|
|
8636
9329
|
return `\u{1F5D2} ${project}:${name}`;
|
|
8637
9330
|
}
|
|
@@ -8733,7 +9426,7 @@ async function runSideClose(runtime, workspaceId, project, name, projPath, workt
|
|
|
8733
9426
|
await runtime.closePane(pane);
|
|
8734
9427
|
if (projPath) {
|
|
8735
9428
|
const wtPath = worktreePath(projPath, worktreeDir, project, name);
|
|
8736
|
-
if (
|
|
9429
|
+
if (fs13.existsSync(wtPath)) {
|
|
8737
9430
|
try {
|
|
8738
9431
|
removeWorktree(projPath, wtPath);
|
|
8739
9432
|
} catch (e) {
|
|
@@ -9170,8 +9863,10 @@ __export(dist_exports2, {
|
|
|
9170
9863
|
AGENT_BINS: () => AGENT_BINS,
|
|
9171
9864
|
ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
|
|
9172
9865
|
BOT_COMMANDS: () => BOT_COMMANDS,
|
|
9866
|
+
CAPTAIN_RECEIVED_ACK: () => CAPTAIN_RECEIVED_ACK,
|
|
9173
9867
|
CC_SOCKS_DIR: () => CC_SOCKS_DIR,
|
|
9174
9868
|
CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
|
|
9869
|
+
CONFLICT_409_SELF_HEAL_MS: () => CONFLICT_409_SELF_HEAL_MS,
|
|
9175
9870
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
9176
9871
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
9177
9872
|
CaptainDelivery: () => CaptainDelivery,
|
|
@@ -9191,6 +9886,7 @@ __export(dist_exports2, {
|
|
|
9191
9886
|
LivenessRegistry: () => LivenessRegistry,
|
|
9192
9887
|
MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
|
|
9193
9888
|
OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
|
|
9889
|
+
PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
|
|
9194
9890
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
9195
9891
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
9196
9892
|
READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
|
|
@@ -9203,6 +9899,10 @@ __export(dist_exports2, {
|
|
|
9203
9899
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
9204
9900
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
9205
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,
|
|
9206
9906
|
WORK_ITEM_TTL_MS: () => WORK_ITEM_TTL_MS,
|
|
9207
9907
|
WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
|
|
9208
9908
|
_resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
|
|
@@ -9224,15 +9924,19 @@ __export(dist_exports2, {
|
|
|
9224
9924
|
callClassifier: () => callClassifier,
|
|
9225
9925
|
capAllowed: () => capAllowed,
|
|
9226
9926
|
capOutput: () => capOutput,
|
|
9927
|
+
captainAliveStateFromHealth: () => captainAliveStateFromHealth,
|
|
9227
9928
|
captainRecordPath: () => captainRecordPath,
|
|
9228
9929
|
captainSocketPath: () => captainSocketPath,
|
|
9229
9930
|
checkFact: () => checkFact,
|
|
9931
|
+
classifyCaptainPane: () => classifyCaptainPane,
|
|
9230
9932
|
classifyHealth: () => classifyHealth,
|
|
9231
9933
|
claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
|
|
9934
|
+
clearPending: () => clearPending,
|
|
9232
9935
|
closeWorkItem: () => closeWorkItem,
|
|
9233
9936
|
computeTemplateHash: () => computeTemplateHash,
|
|
9234
9937
|
consumeExitMarker: () => consumeExitMarker,
|
|
9235
9938
|
createAttach: () => createAttach,
|
|
9939
|
+
createCaptainPaneReader: () => createCaptainPaneReader,
|
|
9236
9940
|
createCrewPaneReader: () => createCrewPaneReader,
|
|
9237
9941
|
createDaemon: () => createDaemon,
|
|
9238
9942
|
createDecoder: () => createDecoder,
|
|
@@ -9241,6 +9945,7 @@ __export(dist_exports2, {
|
|
|
9241
9945
|
createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
|
|
9242
9946
|
createEnsureCaptainAlive: () => createEnsureCaptainAlive,
|
|
9243
9947
|
createEventsSource: () => createEventsSource,
|
|
9948
|
+
createInboundLifecycle: () => createInboundLifecycle,
|
|
9244
9949
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
9245
9950
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
9246
9951
|
createLaunch: () => createLaunch,
|
|
@@ -9296,12 +10001,16 @@ __export(dist_exports2, {
|
|
|
9296
10001
|
formatInbound: () => formatInbound,
|
|
9297
10002
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
9298
10003
|
formatLifecycle: () => formatLifecycle,
|
|
10004
|
+
formatMediaReceipt: () => formatMediaReceipt,
|
|
9299
10005
|
formatPermissionDecision: () => formatPermissionDecision,
|
|
9300
10006
|
formatUsageLine: () => formatUsageLine,
|
|
10007
|
+
formatWatchdogWarning: () => formatWatchdogWarning,
|
|
9301
10008
|
freshTrace: () => freshTrace,
|
|
9302
10009
|
gateCacheKey: () => gateCacheKey,
|
|
9303
10010
|
getDaemonPid: () => getDaemonPid,
|
|
10011
|
+
hasModalOptionList: () => hasModalOptionList,
|
|
9304
10012
|
healCmdFor: () => healCmdFor,
|
|
10013
|
+
inboundBody: () => inboundBody,
|
|
9305
10014
|
isAuthorized: () => isAuthorized,
|
|
9306
10015
|
isBareSpawn: () => isBareSpawn,
|
|
9307
10016
|
isCaptainAlive: () => isCaptainAlive,
|
|
@@ -9323,18 +10032,23 @@ __export(dist_exports2, {
|
|
|
9323
10032
|
kickstartArgv: () => kickstartArgv,
|
|
9324
10033
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
9325
10034
|
listSessions: () => listSessions,
|
|
10035
|
+
loadPending: () => loadPending,
|
|
9326
10036
|
loadSessions: () => loadSessions,
|
|
9327
10037
|
loadState: () => loadState,
|
|
9328
10038
|
mailboxStats: () => mailboxStats,
|
|
9329
10039
|
makeGate: () => makeGate,
|
|
10040
|
+
markPendingWarned: () => markPendingWarned,
|
|
9330
10041
|
maskToken: () => maskToken,
|
|
9331
10042
|
matchTier1Deny: () => matchTier1Deny,
|
|
10043
|
+
mediaKind: () => mediaKind,
|
|
10044
|
+
mediaMarker: () => mediaMarker,
|
|
9332
10045
|
mergeClaudeEnvRouterSettings: () => mergeClaudeEnvRouterSettings,
|
|
9333
10046
|
mintToken: () => mintToken,
|
|
9334
10047
|
nameFromTitle: () => nameFromTitle,
|
|
9335
10048
|
newestSessionInDirectory: () => newestSessionInDirectory,
|
|
9336
10049
|
nextAutoName: () => nextAutoName,
|
|
9337
10050
|
niceCrewCommand: () => niceCrewCommand,
|
|
10051
|
+
notePaneScreen: () => notePaneScreen,
|
|
9338
10052
|
notifyToggle: () => notifyToggle,
|
|
9339
10053
|
parseBearer: () => parseBearer,
|
|
9340
10054
|
parseClassifierVerdict: () => parseClassifierVerdict,
|
|
@@ -9343,6 +10057,7 @@ __export(dist_exports2, {
|
|
|
9343
10057
|
parseNotifyPref: () => parseNotifyPref,
|
|
9344
10058
|
parseProgramArgs: () => parseProgramArgs,
|
|
9345
10059
|
plistPath: () => plistPath,
|
|
10060
|
+
pollBackoffMs: () => pollBackoffMs,
|
|
9346
10061
|
pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
|
|
9347
10062
|
prepareCaptainRoute: () => prepareCaptainRoute,
|
|
9348
10063
|
printForeignInstallError: () => printForeignInstallError,
|
|
@@ -9350,6 +10065,7 @@ __export(dist_exports2, {
|
|
|
9350
10065
|
probeCaptainChannel: () => probeCaptainChannel,
|
|
9351
10066
|
programArgsBlock: () => programArgsBlock,
|
|
9352
10067
|
projectHealth: () => projectHealth,
|
|
10068
|
+
pruneTopics: () => pruneTopics,
|
|
9353
10069
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
9354
10070
|
readCaptainAddress: () => readCaptainAddress,
|
|
9355
10071
|
readCursor: () => readCursor,
|
|
@@ -9416,9 +10132,11 @@ __export(dist_exports2, {
|
|
|
9416
10132
|
saveSessions: () => saveSessions,
|
|
9417
10133
|
saveState: () => saveState,
|
|
9418
10134
|
screenHasSplashMarker: () => screenHasSplashMarker,
|
|
10135
|
+
screenHash: () => screenHash,
|
|
9419
10136
|
sendRequest: () => sendRequest,
|
|
9420
10137
|
setLastUserId: () => setLastUserId,
|
|
9421
10138
|
setNotify: () => setNotify,
|
|
10139
|
+
setPending: () => setPending,
|
|
9422
10140
|
setTopic: () => setTopic,
|
|
9423
10141
|
shellQuote: () => shellQuote,
|
|
9424
10142
|
shouldBuildRouterService: () => shouldBuildRouterService,
|
|
@@ -9431,6 +10149,7 @@ __export(dist_exports2, {
|
|
|
9431
10149
|
startServer: () => startServer,
|
|
9432
10150
|
stripBotMention: () => stripBotMention,
|
|
9433
10151
|
surfaceVerdict: () => surfaceVerdict,
|
|
10152
|
+
telegramPollLockPath: () => telegramPollLockPath,
|
|
9434
10153
|
timeoutGate: () => timeoutGate,
|
|
9435
10154
|
titleFor: () => titleFor,
|
|
9436
10155
|
toControlEvent: () => toControlEvent,
|
|
@@ -9500,7 +10219,7 @@ var init_dist2 = __esm({
|
|
|
9500
10219
|
});
|
|
9501
10220
|
|
|
9502
10221
|
// packages/agents/dist/claude/registry.js
|
|
9503
|
-
import
|
|
10222
|
+
import fs14 from "fs";
|
|
9504
10223
|
import { join as join17 } from "path";
|
|
9505
10224
|
import { homedir as homedir7 } from "os";
|
|
9506
10225
|
function parseRegistryDir(files, readFile7) {
|
|
@@ -9545,11 +10264,11 @@ function readClaudeStatus(task) {
|
|
|
9545
10264
|
return void 0;
|
|
9546
10265
|
let files;
|
|
9547
10266
|
try {
|
|
9548
|
-
files =
|
|
10267
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
9549
10268
|
} catch {
|
|
9550
10269
|
return void 0;
|
|
9551
10270
|
}
|
|
9552
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
10271
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
9553
10272
|
let entry;
|
|
9554
10273
|
if (task.messagingSocketPath) {
|
|
9555
10274
|
entry = entries.find((e) => e.messagingSocketPath === task.messagingSocketPath);
|
|
@@ -9564,11 +10283,11 @@ function readClaudeStatus(task) {
|
|
|
9564
10283
|
function readClaudeStatusByCwd(cwd) {
|
|
9565
10284
|
let files;
|
|
9566
10285
|
try {
|
|
9567
|
-
files =
|
|
10286
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
9568
10287
|
} catch {
|
|
9569
10288
|
return void 0;
|
|
9570
10289
|
}
|
|
9571
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
10290
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
9572
10291
|
const entry = entries.find((e) => e.cwd === cwd);
|
|
9573
10292
|
if (!entry)
|
|
9574
10293
|
return void 0;
|
|
@@ -9577,11 +10296,11 @@ function readClaudeStatusByCwd(cwd) {
|
|
|
9577
10296
|
function readClaudeStatusBySocketPath(socketPath2) {
|
|
9578
10297
|
let files;
|
|
9579
10298
|
try {
|
|
9580
|
-
files =
|
|
10299
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
9581
10300
|
} catch {
|
|
9582
10301
|
return void 0;
|
|
9583
10302
|
}
|
|
9584
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
10303
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
9585
10304
|
const entry = entries.find((e) => e.messagingSocketPath === socketPath2);
|
|
9586
10305
|
if (!entry)
|
|
9587
10306
|
return void 0;
|
|
@@ -10004,7 +10723,7 @@ var init_registry2 = __esm({
|
|
|
10004
10723
|
});
|
|
10005
10724
|
|
|
10006
10725
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
10007
|
-
import
|
|
10726
|
+
import fs15 from "fs";
|
|
10008
10727
|
import path13 from "path";
|
|
10009
10728
|
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot, settingsPath) {
|
|
10010
10729
|
const driver = registry.getDriver(agentName);
|
|
@@ -10034,12 +10753,12 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
10034
10753
|
if (templatesDir) {
|
|
10035
10754
|
const roleFile2 = path13.join(templatesDir, `${role}.claude.md`);
|
|
10036
10755
|
const legacyRoleFile = path13.join(templatesDir, `${role}.CLAUDE.md`);
|
|
10037
|
-
const actualRoleFile =
|
|
10756
|
+
const actualRoleFile = fs15.existsSync(roleFile2) ? roleFile2 : fs15.existsSync(legacyRoleFile) ? legacyRoleFile : null;
|
|
10038
10757
|
if (actualRoleFile) {
|
|
10039
10758
|
cmd += ` --append-system-prompt-file ${actualRoleFile}`;
|
|
10040
10759
|
}
|
|
10041
10760
|
const pluginDir = path13.join(templatesDir, "..", "plugin");
|
|
10042
|
-
if (
|
|
10761
|
+
if (fs15.existsSync(pluginDir)) {
|
|
10043
10762
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
10044
10763
|
}
|
|
10045
10764
|
}
|
|
@@ -10055,7 +10774,7 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
10055
10774
|
role,
|
|
10056
10775
|
model,
|
|
10057
10776
|
autoApprove: true,
|
|
10058
|
-
promptFile: roleFile &&
|
|
10777
|
+
promptFile: roleFile && fs15.existsSync(roleFile) ? roleFile : void 0,
|
|
10059
10778
|
...captainBoot && role === "captain" ? {
|
|
10060
10779
|
interactive: true,
|
|
10061
10780
|
...captainBoot.port ? { port: captainBoot.port } : {},
|
|
@@ -10427,7 +11146,7 @@ var init_opencode2 = __esm({
|
|
|
10427
11146
|
});
|
|
10428
11147
|
|
|
10429
11148
|
// packages/agents/dist/projection/opencode-skills.js
|
|
10430
|
-
import
|
|
11149
|
+
import fs16 from "fs";
|
|
10431
11150
|
import os10 from "os";
|
|
10432
11151
|
import path18 from "path";
|
|
10433
11152
|
function defaultOpencodeSkillsRoot(env = process.env) {
|
|
@@ -10446,62 +11165,62 @@ function parseSkillName(raw) {
|
|
|
10446
11165
|
}
|
|
10447
11166
|
function listSourceSkills(sourceSkillsDir) {
|
|
10448
11167
|
const out = /* @__PURE__ */ new Map();
|
|
10449
|
-
if (!
|
|
11168
|
+
if (!fs16.existsSync(sourceSkillsDir))
|
|
10450
11169
|
return out;
|
|
10451
|
-
for (const entry of
|
|
11170
|
+
for (const entry of fs16.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
|
|
10452
11171
|
if (!entry.isDirectory())
|
|
10453
11172
|
continue;
|
|
10454
11173
|
const skillPath = path18.join(sourceSkillsDir, entry.name, "SKILL.md");
|
|
10455
|
-
if (!
|
|
11174
|
+
if (!fs16.existsSync(skillPath))
|
|
10456
11175
|
continue;
|
|
10457
|
-
const name = parseSkillName(
|
|
11176
|
+
const name = parseSkillName(fs16.readFileSync(skillPath, "utf-8"));
|
|
10458
11177
|
if (name && SAFE_NAME.test(name))
|
|
10459
11178
|
out.set(name, skillPath);
|
|
10460
11179
|
}
|
|
10461
11180
|
return out;
|
|
10462
11181
|
}
|
|
10463
11182
|
function hasMarker(dir) {
|
|
10464
|
-
return
|
|
11183
|
+
return fs16.existsSync(path18.join(dir, OPENCODE_SKILL_MARKER));
|
|
10465
11184
|
}
|
|
10466
11185
|
function syncOpencodeSkills(opts) {
|
|
10467
11186
|
const result = { written: [], unchanged: [], removed: [], skipped: [] };
|
|
10468
11187
|
const wanted = listSourceSkills(opts.sourceSkillsDir);
|
|
10469
|
-
if (wanted.size === 0 && !
|
|
11188
|
+
if (wanted.size === 0 && !fs16.existsSync(opts.sourceSkillsDir))
|
|
10470
11189
|
return result;
|
|
10471
11190
|
if (!opts.dryRun)
|
|
10472
|
-
|
|
11191
|
+
fs16.mkdirSync(opts.skillsRoot, { recursive: true });
|
|
10473
11192
|
for (const [name, srcPath] of wanted) {
|
|
10474
11193
|
const targetDir = path18.join(opts.skillsRoot, name);
|
|
10475
11194
|
const targetSkill = path18.join(targetDir, "SKILL.md");
|
|
10476
11195
|
const managed = hasMarker(targetDir);
|
|
10477
|
-
if (
|
|
11196
|
+
if (fs16.existsSync(targetSkill) && !managed) {
|
|
10478
11197
|
result.skipped.push({ name, reason: "foreign-collision" });
|
|
10479
11198
|
continue;
|
|
10480
11199
|
}
|
|
10481
|
-
const current =
|
|
10482
|
-
if (current && current.equals(
|
|
11200
|
+
const current = fs16.existsSync(targetSkill) ? fs16.readFileSync(targetSkill) : null;
|
|
11201
|
+
if (current && current.equals(fs16.readFileSync(srcPath))) {
|
|
10483
11202
|
if (!managed && !opts.dryRun) {
|
|
10484
|
-
|
|
11203
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
10485
11204
|
}
|
|
10486
11205
|
result.unchanged.push(name);
|
|
10487
11206
|
continue;
|
|
10488
11207
|
}
|
|
10489
11208
|
if (!opts.dryRun) {
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
11209
|
+
fs16.mkdirSync(targetDir, { recursive: true });
|
|
11210
|
+
fs16.copyFileSync(srcPath, targetSkill);
|
|
11211
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
10493
11212
|
}
|
|
10494
11213
|
result.written.push(name);
|
|
10495
11214
|
}
|
|
10496
|
-
if (
|
|
10497
|
-
for (const entry of
|
|
11215
|
+
if (fs16.existsSync(opts.skillsRoot)) {
|
|
11216
|
+
for (const entry of fs16.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
|
|
10498
11217
|
if (!entry.isDirectory() || wanted.has(entry.name))
|
|
10499
11218
|
continue;
|
|
10500
11219
|
const dir = path18.join(opts.skillsRoot, entry.name);
|
|
10501
11220
|
if (!hasMarker(dir))
|
|
10502
11221
|
continue;
|
|
10503
11222
|
if (!opts.dryRun)
|
|
10504
|
-
|
|
11223
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
10505
11224
|
result.removed.push(entry.name);
|
|
10506
11225
|
}
|
|
10507
11226
|
}
|
|
@@ -12310,6 +13029,9 @@ var init_peer_channel = __esm({
|
|
|
12310
13029
|
return { status: "accepted", via: this.name, confirmed: true };
|
|
12311
13030
|
}
|
|
12312
13031
|
}
|
|
13032
|
+
if (statusBefore === "busy" || statusBefore === "shell") {
|
|
13033
|
+
return { status: "queued", via: this.name };
|
|
13034
|
+
}
|
|
12313
13035
|
if (statusBefore === "idle") {
|
|
12314
13036
|
await this.deps.sleep(window);
|
|
12315
13037
|
const afterInfo = this.deps.statusFor(taskId);
|
|
@@ -12455,7 +13177,7 @@ var init_receipt_listener = __esm({
|
|
|
12455
13177
|
});
|
|
12456
13178
|
|
|
12457
13179
|
// packages/agents/dist/claude/api-key-approval.js
|
|
12458
|
-
import
|
|
13180
|
+
import fs17 from "fs";
|
|
12459
13181
|
import os12 from "os";
|
|
12460
13182
|
import path20 from "path";
|
|
12461
13183
|
function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
|
|
@@ -12465,12 +13187,12 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
|
|
|
12465
13187
|
const file = opts.claudeJsonPath ?? path20.join(os12.homedir(), ".claude.json");
|
|
12466
13188
|
const log = opts.log ?? (() => {
|
|
12467
13189
|
});
|
|
12468
|
-
if (!
|
|
13190
|
+
if (!fs17.existsSync(file)) {
|
|
12469
13191
|
return { changed: false, reason: "no ~/.claude.json yet \u2014 skipping routed-key pre-approval" };
|
|
12470
13192
|
}
|
|
12471
13193
|
let doc;
|
|
12472
13194
|
try {
|
|
12473
|
-
doc = JSON.parse(
|
|
13195
|
+
doc = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
12474
13196
|
} catch {
|
|
12475
13197
|
return {
|
|
12476
13198
|
changed: false,
|
|
@@ -12492,11 +13214,11 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
|
|
|
12492
13214
|
};
|
|
12493
13215
|
const tmp = `${file}.squadrant-tmp`;
|
|
12494
13216
|
try {
|
|
12495
|
-
|
|
12496
|
-
|
|
13217
|
+
fs17.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs17.statSync(file).mode & 511 });
|
|
13218
|
+
fs17.renameSync(tmp, file);
|
|
12497
13219
|
} catch (e) {
|
|
12498
13220
|
try {
|
|
12499
|
-
|
|
13221
|
+
fs17.unlinkSync(tmp);
|
|
12500
13222
|
} catch {
|
|
12501
13223
|
}
|
|
12502
13224
|
return {
|
|
@@ -12852,11 +13574,11 @@ function parseDraftFromScreen(screen) {
|
|
|
12852
13574
|
if (!screen)
|
|
12853
13575
|
return null;
|
|
12854
13576
|
const lines = screen.split(/\r?\n/);
|
|
12855
|
-
const
|
|
13577
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12856
13578
|
let bottomHR = -1;
|
|
12857
13579
|
let topHR = -1;
|
|
12858
13580
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12859
|
-
if (
|
|
13581
|
+
if (HR_RE2.test(lines[i])) {
|
|
12860
13582
|
if (bottomHR === -1) {
|
|
12861
13583
|
bottomHR = i;
|
|
12862
13584
|
} else {
|
|
@@ -12894,11 +13616,11 @@ function hasCCInputBox(screen) {
|
|
|
12894
13616
|
if (!screen)
|
|
12895
13617
|
return false;
|
|
12896
13618
|
const lines = screen.split(/\r?\n/);
|
|
12897
|
-
const
|
|
13619
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12898
13620
|
let bottomHR = -1;
|
|
12899
13621
|
let topHR = -1;
|
|
12900
13622
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12901
|
-
if (
|
|
13623
|
+
if (HR_RE2.test(lines[i])) {
|
|
12902
13624
|
if (bottomHR === -1)
|
|
12903
13625
|
bottomHR = i;
|
|
12904
13626
|
else {
|
|
@@ -12911,15 +13633,15 @@ function hasCCInputBox(screen) {
|
|
|
12911
13633
|
return false;
|
|
12912
13634
|
return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));
|
|
12913
13635
|
}
|
|
12914
|
-
function
|
|
13636
|
+
function hasModalOptionList2(screen) {
|
|
12915
13637
|
if (!screen)
|
|
12916
13638
|
return false;
|
|
12917
13639
|
const lines = screen.split(/\r?\n/);
|
|
12918
|
-
const
|
|
13640
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12919
13641
|
let bottomHR = -1;
|
|
12920
13642
|
let topHR = -1;
|
|
12921
13643
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12922
|
-
if (
|
|
13644
|
+
if (HR_RE2.test(lines[i])) {
|
|
12923
13645
|
if (bottomHR === -1)
|
|
12924
13646
|
bottomHR = i;
|
|
12925
13647
|
else {
|
|
@@ -12933,14 +13655,14 @@ function hasModalOptionList(screen) {
|
|
|
12933
13655
|
return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
|
|
12934
13656
|
}
|
|
12935
13657
|
function parseModalOptions(screen) {
|
|
12936
|
-
if (!
|
|
13658
|
+
if (!hasModalOptionList2(screen))
|
|
12937
13659
|
return null;
|
|
12938
13660
|
const lines = screen.split(/\r?\n/);
|
|
12939
|
-
const
|
|
13661
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12940
13662
|
let bottomHR = -1;
|
|
12941
13663
|
let topHR = -1;
|
|
12942
13664
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12943
|
-
if (
|
|
13665
|
+
if (HR_RE2.test(lines[i])) {
|
|
12944
13666
|
if (bottomHR === -1)
|
|
12945
13667
|
bottomHR = i;
|
|
12946
13668
|
else {
|
|
@@ -12965,11 +13687,11 @@ function readInputBoxRaw(screen, opts) {
|
|
|
12965
13687
|
if (!screen)
|
|
12966
13688
|
return null;
|
|
12967
13689
|
const lines = screen.split(/\r?\n/);
|
|
12968
|
-
const
|
|
13690
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
12969
13691
|
let bottomHR = -1;
|
|
12970
13692
|
let topHR = -1;
|
|
12971
13693
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12972
|
-
if (
|
|
13694
|
+
if (HR_RE2.test(lines[i])) {
|
|
12973
13695
|
if (bottomHR === -1)
|
|
12974
13696
|
bottomHR = i;
|
|
12975
13697
|
else {
|
|
@@ -13002,6 +13724,30 @@ function classifyOpencodeStartupSurface(screen) {
|
|
|
13002
13724
|
return "loading";
|
|
13003
13725
|
return OC_IDLE_MARKERS.some((m) => screenHasSplashMarker(screen, m)) ? "idle" : "loading";
|
|
13004
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
|
+
}
|
|
13005
13751
|
function sendDebugEnabled() {
|
|
13006
13752
|
return !!process.env.SQUADRANT_DEBUG_SEND;
|
|
13007
13753
|
}
|
|
@@ -13217,10 +13963,10 @@ function createCmuxDriver() {
|
|
|
13217
13963
|
`);
|
|
13218
13964
|
throw new DeferDelivery(null, "probe-failed");
|
|
13219
13965
|
}
|
|
13220
|
-
const draft = parseDraftFromScreen(screen);
|
|
13966
|
+
const draft = opts?.agent === "opencode" ? parseOpencodeDraftFromScreen(screen) : parseDraftFromScreen(screen);
|
|
13221
13967
|
if (draft === null)
|
|
13222
13968
|
throw new DeferDelivery(null, "no-box");
|
|
13223
|
-
if (
|
|
13969
|
+
if (hasModalOptionList2(screen))
|
|
13224
13970
|
throw new DeferDelivery(null, "modal");
|
|
13225
13971
|
if (draft === "") {
|
|
13226
13972
|
await deliver();
|
|
@@ -13324,7 +14070,7 @@ function createCmuxDriver() {
|
|
|
13324
14070
|
}
|
|
13325
14071
|
};
|
|
13326
14072
|
}
|
|
13327
|
-
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;
|
|
13328
14074
|
var init_cmux = __esm({
|
|
13329
14075
|
"packages/workspaces/dist/runtimes/cmux.js"() {
|
|
13330
14076
|
init_dist();
|
|
@@ -13342,6 +14088,10 @@ var init_cmux = __esm({
|
|
|
13342
14088
|
CC_INITIALIZED_RE = /⏵⏵|Ctx Used|for shortcuts|accept edits/i;
|
|
13343
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;
|
|
13344
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;
|
|
13345
14095
|
}
|
|
13346
14096
|
});
|
|
13347
14097
|
|
|
@@ -13466,7 +14216,7 @@ var init_notifiers = __esm({
|
|
|
13466
14216
|
});
|
|
13467
14217
|
|
|
13468
14218
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
13469
|
-
import
|
|
14219
|
+
import fs18 from "fs/promises";
|
|
13470
14220
|
import { existsSync as existsSync11 } from "fs";
|
|
13471
14221
|
import path21 from "path";
|
|
13472
14222
|
function resolveInRoot(root, relative) {
|
|
@@ -13491,16 +14241,16 @@ function createObsidianDriver(scope) {
|
|
|
13491
14241
|
};
|
|
13492
14242
|
},
|
|
13493
14243
|
async read(rel) {
|
|
13494
|
-
return
|
|
14244
|
+
return fs18.readFile(resolveInRoot(root, rel), "utf-8");
|
|
13495
14245
|
},
|
|
13496
14246
|
async write(rel, content) {
|
|
13497
14247
|
const abs = resolveInRoot(root, rel);
|
|
13498
|
-
await
|
|
13499
|
-
await
|
|
14248
|
+
await fs18.mkdir(path21.dirname(abs), { recursive: true });
|
|
14249
|
+
await fs18.writeFile(abs, content);
|
|
13500
14250
|
},
|
|
13501
14251
|
async exists(rel) {
|
|
13502
14252
|
try {
|
|
13503
|
-
await
|
|
14253
|
+
await fs18.access(resolveInRoot(root, rel));
|
|
13504
14254
|
return true;
|
|
13505
14255
|
} catch {
|
|
13506
14256
|
return false;
|
|
@@ -13508,13 +14258,13 @@ function createObsidianDriver(scope) {
|
|
|
13508
14258
|
},
|
|
13509
14259
|
async list(rel) {
|
|
13510
14260
|
try {
|
|
13511
|
-
return await
|
|
14261
|
+
return await fs18.readdir(resolveInRoot(root, rel));
|
|
13512
14262
|
} catch {
|
|
13513
14263
|
return [];
|
|
13514
14264
|
}
|
|
13515
14265
|
},
|
|
13516
14266
|
async mkdir(rel) {
|
|
13517
|
-
await
|
|
14267
|
+
await fs18.mkdir(resolveInRoot(root, rel), { recursive: true });
|
|
13518
14268
|
}
|
|
13519
14269
|
};
|
|
13520
14270
|
}
|
|
@@ -14412,7 +15162,7 @@ async function resolveCaptainWorkspace(project) {
|
|
|
14412
15162
|
}
|
|
14413
15163
|
async function paneHasOpenModal(runtime, pane) {
|
|
14414
15164
|
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
14415
|
-
return
|
|
15165
|
+
return hasModalOptionList2(screen);
|
|
14416
15166
|
}
|
|
14417
15167
|
async function readModalOptions(runtime, pane) {
|
|
14418
15168
|
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
@@ -14420,7 +15170,7 @@ async function readModalOptions(runtime, pane) {
|
|
|
14420
15170
|
}
|
|
14421
15171
|
async function confirmedSendToPane(runtime, pane, message) {
|
|
14422
15172
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
14423
|
-
if (
|
|
15173
|
+
if (hasModalOptionList2(preSendScreen)) {
|
|
14424
15174
|
return { delivered: false, blockedByModal: true };
|
|
14425
15175
|
}
|
|
14426
15176
|
await runtime.pasteToPane(pane, message);
|
|
@@ -14673,9 +15423,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
|
14673
15423
|
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
14674
15424
|
await needRef(resolved);
|
|
14675
15425
|
const finalProject = opts.command ? config.commandName : target;
|
|
14676
|
-
const { join:
|
|
15426
|
+
const { join: join36, dirname: dirname12 } = await import("path");
|
|
14677
15427
|
const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
14678
|
-
const stateRoot =
|
|
15428
|
+
const stateRoot = join36(dirname12(DEFAULT_CONFIG_PATH2), "state");
|
|
14679
15429
|
const seq = await appendCaptainMessage2({
|
|
14680
15430
|
stateRoot,
|
|
14681
15431
|
project: finalProject,
|
|
@@ -14784,7 +15534,7 @@ init_dist3();
|
|
|
14784
15534
|
import { Command as Command38 } from "commander";
|
|
14785
15535
|
import { existsSync as existsSync14, readFileSync as readFileSync21 } from "fs";
|
|
14786
15536
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
14787
|
-
import { dirname as
|
|
15537
|
+
import { dirname as dirname11, join as join35 } from "path";
|
|
14788
15538
|
import { homedir as homedir20 } from "os";
|
|
14789
15539
|
|
|
14790
15540
|
// packages/cli/src/commands/doctor.ts
|
|
@@ -14794,7 +15544,7 @@ init_dist();
|
|
|
14794
15544
|
init_dist4();
|
|
14795
15545
|
import { Command } from "commander";
|
|
14796
15546
|
import { execSync as execSync8 } from "child_process";
|
|
14797
|
-
import
|
|
15547
|
+
import fs19 from "fs";
|
|
14798
15548
|
import { stat } from "fs/promises";
|
|
14799
15549
|
import path22 from "path";
|
|
14800
15550
|
import chalk3 from "chalk";
|
|
@@ -14898,7 +15648,7 @@ function settingsHaveAgentTeams() {
|
|
|
14898
15648
|
try {
|
|
14899
15649
|
const home = process.env.HOME || "";
|
|
14900
15650
|
const settings = JSON.parse(
|
|
14901
|
-
|
|
15651
|
+
fs19.readFileSync(`${home}/.claude/settings.json`, "utf-8")
|
|
14902
15652
|
);
|
|
14903
15653
|
return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
14904
15654
|
} catch {
|
|
@@ -14909,7 +15659,7 @@ function pluginInstalled(pluginKey) {
|
|
|
14909
15659
|
try {
|
|
14910
15660
|
const home = process.env.HOME || "";
|
|
14911
15661
|
const plugins = JSON.parse(
|
|
14912
|
-
|
|
15662
|
+
fs19.readFileSync(
|
|
14913
15663
|
`${home}/.claude/plugins/installed_plugins.json`,
|
|
14914
15664
|
"utf-8"
|
|
14915
15665
|
)
|
|
@@ -14955,7 +15705,7 @@ ${lines.join("\n")}
|
|
|
14955
15705
|
}
|
|
14956
15706
|
function readPackageVersion(packageJsonPath) {
|
|
14957
15707
|
try {
|
|
14958
|
-
const pkg2 = JSON.parse(
|
|
15708
|
+
const pkg2 = JSON.parse(fs19.readFileSync(packageJsonPath, "utf-8"));
|
|
14959
15709
|
return typeof pkg2.version === "string" ? pkg2.version : null;
|
|
14960
15710
|
} catch {
|
|
14961
15711
|
return null;
|
|
@@ -14991,7 +15741,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
14991
15741
|
));
|
|
14992
15742
|
results.push(check(
|
|
14993
15743
|
"Obsidian installed",
|
|
14994
|
-
commandExists("obsidian") ||
|
|
15744
|
+
commandExists("obsidian") || fs19.existsSync("/Applications/Obsidian.app"),
|
|
14995
15745
|
"Install from: https://obsidian.md"
|
|
14996
15746
|
));
|
|
14997
15747
|
results.push(check(
|
|
@@ -15099,7 +15849,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
15099
15849
|
results.push(
|
|
15100
15850
|
check(
|
|
15101
15851
|
"Squadrant config exists",
|
|
15102
|
-
|
|
15852
|
+
fs19.existsSync(
|
|
15103
15853
|
process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
|
|
15104
15854
|
),
|
|
15105
15855
|
"Run: squadrant init"
|
|
@@ -15183,7 +15933,7 @@ init_dist();
|
|
|
15183
15933
|
init_dist4();
|
|
15184
15934
|
init_dist();
|
|
15185
15935
|
import { Command as Command2 } from "commander";
|
|
15186
|
-
import
|
|
15936
|
+
import fs20 from "fs";
|
|
15187
15937
|
import path23 from "path";
|
|
15188
15938
|
import os13 from "os";
|
|
15189
15939
|
import readline from "readline";
|
|
@@ -15400,25 +16150,57 @@ function writePerCrewOpencodeConfig(o) {
|
|
|
15400
16150
|
return file;
|
|
15401
16151
|
}
|
|
15402
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
|
+
|
|
15403
16185
|
// packages/cli/src/commands/init.ts
|
|
15404
16186
|
init_dist3();
|
|
15405
16187
|
function findPackageRoot() {
|
|
15406
16188
|
let dir = path23.dirname(new URL(import.meta.url).pathname);
|
|
15407
16189
|
while (dir !== "/") {
|
|
15408
|
-
if (
|
|
16190
|
+
if (fs20.existsSync(path23.join(dir, "package.json"))) return dir;
|
|
15409
16191
|
dir = path23.dirname(dir);
|
|
15410
16192
|
}
|
|
15411
16193
|
return process.cwd();
|
|
15412
16194
|
}
|
|
15413
16195
|
function copyDirRecursive(src, dest) {
|
|
15414
|
-
|
|
15415
|
-
for (const entry of
|
|
16196
|
+
fs20.mkdirSync(dest, { recursive: true });
|
|
16197
|
+
for (const entry of fs20.readdirSync(src, { withFileTypes: true })) {
|
|
15416
16198
|
const srcPath = path23.join(src, entry.name);
|
|
15417
16199
|
const destPath = path23.join(dest, entry.name);
|
|
15418
16200
|
if (entry.isDirectory()) {
|
|
15419
16201
|
copyDirRecursive(srcPath, destPath);
|
|
15420
16202
|
} else {
|
|
15421
|
-
|
|
16203
|
+
fs20.copyFileSync(srcPath, destPath);
|
|
15422
16204
|
}
|
|
15423
16205
|
}
|
|
15424
16206
|
}
|
|
@@ -15435,16 +16217,70 @@ function promptLine(question) {
|
|
|
15435
16217
|
});
|
|
15436
16218
|
});
|
|
15437
16219
|
}
|
|
15438
|
-
|
|
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) => {
|
|
15439
16264
|
const hubPath = resolveHome(opts.hub);
|
|
15440
16265
|
const pkgRoot = findPackageRoot();
|
|
15441
16266
|
const configDir = path23.join(os13.homedir(), ".config", "squadrant");
|
|
15442
16267
|
const isTTY = process.stdin.isTTY === true;
|
|
15443
16268
|
console.log(chalk4.bold("\nSquadrant Init\n"));
|
|
15444
|
-
|
|
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) {
|
|
15445
16280
|
console.log(" Run these steps to get started:\n");
|
|
15446
|
-
console.log(chalk4.bold(" 1/5 Hub vault"));
|
|
16281
|
+
console.log(chalk4.bold(" 1/5 Hub vault + provider"));
|
|
15447
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"));
|
|
15448
16284
|
console.log(chalk4.bold("\n 2/5 Agent + projection setup"));
|
|
15449
16285
|
console.log(" (handled automatically by: " + chalk4.cyan("squadrant init") + ")");
|
|
15450
16286
|
console.log(chalk4.bold("\n 3/5 Plugins \u2014 open Claude Code and run:"));
|
|
@@ -15464,8 +16300,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
15464
16300
|
}
|
|
15465
16301
|
const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
15466
16302
|
try {
|
|
15467
|
-
if (
|
|
15468
|
-
const existing = JSON.parse(
|
|
16303
|
+
if (fs20.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
16304
|
+
const existing = JSON.parse(fs20.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
|
|
15469
16305
|
wsRegistry.get(existing.workspace ?? "obsidian");
|
|
15470
16306
|
}
|
|
15471
16307
|
} catch (err) {
|
|
@@ -15473,45 +16309,76 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
15473
16309
|
return;
|
|
15474
16310
|
}
|
|
15475
16311
|
stepHeader(1, 5, "Hub vault");
|
|
15476
|
-
|
|
16312
|
+
const configExists = fs20.existsSync(DEFAULT_CONFIG_PATH);
|
|
16313
|
+
let config;
|
|
16314
|
+
if (configExists) {
|
|
15477
16315
|
console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
|
|
16316
|
+
config = loadConfig();
|
|
15478
16317
|
} else {
|
|
15479
|
-
|
|
16318
|
+
config = getDefaultConfig();
|
|
15480
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) {
|
|
15481
16345
|
saveConfig(config);
|
|
15482
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}`));
|
|
15483
16350
|
}
|
|
15484
16351
|
const hubTemplate = path23.join(pkgRoot, "obsidian", "hub");
|
|
15485
|
-
if (
|
|
16352
|
+
if (fs20.existsSync(hubPath)) {
|
|
15486
16353
|
console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
|
|
15487
|
-
} else if (
|
|
16354
|
+
} else if (fs20.existsSync(hubTemplate)) {
|
|
15488
16355
|
copyDirRecursive(hubTemplate, hubPath);
|
|
15489
16356
|
console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
|
|
15490
16357
|
} else {
|
|
15491
|
-
|
|
16358
|
+
fs20.mkdirSync(hubPath, { recursive: true });
|
|
15492
16359
|
console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
|
|
15493
16360
|
}
|
|
15494
16361
|
const hubDashboardSrc = path23.join(pkgRoot, "obsidian", "hub", "dashboard.md");
|
|
15495
16362
|
const hubDashboardDest = path23.join(hubPath, "dashboard.md");
|
|
15496
|
-
if (
|
|
15497
|
-
|
|
16363
|
+
if (fs20.existsSync(hubDashboardSrc)) {
|
|
16364
|
+
fs20.copyFileSync(hubDashboardSrc, hubDashboardDest);
|
|
15498
16365
|
console.log(chalk4.green(` \u2714 Dashboard refreshed`));
|
|
15499
16366
|
}
|
|
15500
|
-
|
|
16367
|
+
fs20.mkdirSync(path23.join(hubPath, "projects"), { recursive: true });
|
|
15501
16368
|
ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
|
|
15502
16369
|
console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
|
|
15503
16370
|
stepHeader(2, 5, "Agent + projection setup");
|
|
15504
16371
|
const settingsPath = path23.join(os13.homedir(), ".claude", "settings.json");
|
|
15505
16372
|
try {
|
|
15506
16373
|
let settings = {};
|
|
15507
|
-
if (
|
|
15508
|
-
settings = JSON.parse(
|
|
16374
|
+
if (fs20.existsSync(settingsPath)) {
|
|
16375
|
+
settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
|
|
15509
16376
|
}
|
|
15510
16377
|
const env = settings.env || {};
|
|
15511
16378
|
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
15512
16379
|
settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
15513
|
-
|
|
15514
|
-
|
|
16380
|
+
fs20.mkdirSync(path23.dirname(settingsPath), { recursive: true });
|
|
16381
|
+
fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
15515
16382
|
console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
|
|
15516
16383
|
} else {
|
|
15517
16384
|
console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
|
|
@@ -15591,7 +16458,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
15591
16458
|
init_dist();
|
|
15592
16459
|
init_dist2();
|
|
15593
16460
|
import { Command as Command3 } from "commander";
|
|
15594
|
-
import
|
|
16461
|
+
import fs21 from "fs";
|
|
15595
16462
|
import path24 from "path";
|
|
15596
16463
|
import chalk5 from "chalk";
|
|
15597
16464
|
function restartAfterProjectsAdd(opts) {
|
|
@@ -15606,20 +16473,20 @@ function restartAfterProjectsAdd(opts) {
|
|
|
15606
16473
|
function findPackageRoot2() {
|
|
15607
16474
|
let dir = path24.dirname(new URL(import.meta.url).pathname);
|
|
15608
16475
|
while (dir !== "/") {
|
|
15609
|
-
if (
|
|
16476
|
+
if (fs21.existsSync(path24.join(dir, "package.json"))) return dir;
|
|
15610
16477
|
dir = path24.dirname(dir);
|
|
15611
16478
|
}
|
|
15612
16479
|
return process.cwd();
|
|
15613
16480
|
}
|
|
15614
16481
|
function copyDirRecursive2(src, dest) {
|
|
15615
|
-
|
|
15616
|
-
for (const entry of
|
|
16482
|
+
fs21.mkdirSync(dest, { recursive: true });
|
|
16483
|
+
for (const entry of fs21.readdirSync(src, { withFileTypes: true })) {
|
|
15617
16484
|
const srcPath = path24.join(src, entry.name);
|
|
15618
16485
|
const destPath = path24.join(dest, entry.name);
|
|
15619
16486
|
if (entry.isDirectory()) {
|
|
15620
16487
|
copyDirRecursive2(srcPath, destPath);
|
|
15621
16488
|
} else {
|
|
15622
|
-
|
|
16489
|
+
fs21.copyFileSync(srcPath, destPath);
|
|
15623
16490
|
}
|
|
15624
16491
|
}
|
|
15625
16492
|
}
|
|
@@ -15655,7 +16522,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
15655
16522
|
process.exit(1);
|
|
15656
16523
|
}
|
|
15657
16524
|
const resolvedPath = resolveHome(projectPath);
|
|
15658
|
-
if (!
|
|
16525
|
+
if (!fs21.existsSync(path24.join(resolvedPath, ".git"))) {
|
|
15659
16526
|
console.log(chalk5.yellow(`
|
|
15660
16527
|
\u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
|
|
15661
16528
|
`));
|
|
@@ -15724,19 +16591,19 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
15724
16591
|
restartAfterProjectsAdd({ noRestart: opts.restart === false });
|
|
15725
16592
|
const pkgRoot = findPackageRoot2();
|
|
15726
16593
|
const spokeTemplate = path24.join(pkgRoot, "obsidian", "spoke");
|
|
15727
|
-
if (
|
|
16594
|
+
if (fs21.existsSync(spokeVault)) {
|
|
15728
16595
|
console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
|
|
15729
|
-
} else if (
|
|
16596
|
+
} else if (fs21.existsSync(spokeTemplate)) {
|
|
15730
16597
|
copyDirRecursive2(spokeTemplate, spokeVault);
|
|
15731
16598
|
const statusPath = path24.join(spokeVault, "status.md");
|
|
15732
|
-
if (
|
|
15733
|
-
const content =
|
|
16599
|
+
if (fs21.existsSync(statusPath)) {
|
|
16600
|
+
const content = fs21.readFileSync(statusPath, "utf-8");
|
|
15734
16601
|
const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
|
|
15735
|
-
|
|
16602
|
+
fs21.writeFileSync(statusPath, updated);
|
|
15736
16603
|
}
|
|
15737
16604
|
console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
|
|
15738
16605
|
} else {
|
|
15739
|
-
|
|
16606
|
+
fs21.mkdirSync(spokeVault, { recursive: true });
|
|
15740
16607
|
console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
|
|
15741
16608
|
}
|
|
15742
16609
|
console.log("");
|
|
@@ -15804,7 +16671,7 @@ init_dist4();
|
|
|
15804
16671
|
init_dist3();
|
|
15805
16672
|
init_dist();
|
|
15806
16673
|
import { Command as Command5 } from "commander";
|
|
15807
|
-
import { execSync as
|
|
16674
|
+
import { execSync as execSync10 } from "child_process";
|
|
15808
16675
|
import path25 from "path";
|
|
15809
16676
|
import os14 from "os";
|
|
15810
16677
|
import chalk7 from "chalk";
|
|
@@ -15815,7 +16682,7 @@ var TASK_PROMPTS = {
|
|
|
15815
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."
|
|
15816
16683
|
};
|
|
15817
16684
|
function detectCurrentWorkspace() {
|
|
15818
|
-
const out =
|
|
16685
|
+
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
15819
16686
|
const match = out.match(/workspace:\d+/);
|
|
15820
16687
|
if (!match) {
|
|
15821
16688
|
throw new Error("Could not detect current cmux workspace. Run `squadrant command` from inside a cmux workspace.");
|
|
@@ -17133,7 +18000,7 @@ init_dist4();
|
|
|
17133
18000
|
init_dist();
|
|
17134
18001
|
init_dist2();
|
|
17135
18002
|
import { Command as Command12 } from "commander";
|
|
17136
|
-
import
|
|
18003
|
+
import fs22 from "fs";
|
|
17137
18004
|
import path26 from "path";
|
|
17138
18005
|
import os15 from "os";
|
|
17139
18006
|
import chalk12 from "chalk";
|
|
@@ -17170,7 +18037,7 @@ async function runSideSpawn2(input) {
|
|
|
17170
18037
|
prompt: input.topic,
|
|
17171
18038
|
workdir: spawnCwd,
|
|
17172
18039
|
role: "side",
|
|
17173
|
-
promptFile:
|
|
18040
|
+
promptFile: fs22.existsSync(promptFile) ? promptFile : void 0,
|
|
17174
18041
|
interactive: true,
|
|
17175
18042
|
permissionMode: config.defaults.permissions?.crew ?? "auto",
|
|
17176
18043
|
...sideModel ? { model: sideModel } : {},
|
|
@@ -17277,7 +18144,7 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
|
|
|
17277
18144
|
init_dist();
|
|
17278
18145
|
init_dist4();
|
|
17279
18146
|
import { Command as Command13 } from "commander";
|
|
17280
|
-
import { execSync as
|
|
18147
|
+
import { execSync as execSync11 } from "child_process";
|
|
17281
18148
|
import chalk14 from "chalk";
|
|
17282
18149
|
|
|
17283
18150
|
// packages/web/dist/read-status.js
|
|
@@ -17433,7 +18300,7 @@ function renderDashboard(rows, opts) {
|
|
|
17433
18300
|
|
|
17434
18301
|
// packages/web/dist/sync-hub.js
|
|
17435
18302
|
init_dist();
|
|
17436
|
-
import
|
|
18303
|
+
import fs23 from "fs";
|
|
17437
18304
|
import path27 from "path";
|
|
17438
18305
|
function buildMirrorMarkdown(s) {
|
|
17439
18306
|
const fenced = "```";
|
|
@@ -17460,8 +18327,8 @@ function buildMirrorMarkdown(s) {
|
|
|
17460
18327
|
function syncHub(deps) {
|
|
17461
18328
|
if (!deps.config.hubVault)
|
|
17462
18329
|
return [];
|
|
17463
|
-
const writeFile6 = deps.writeFile ?? ((p, c) =>
|
|
17464
|
-
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 }));
|
|
17465
18332
|
const projectsDir = path27.join(resolveHome(deps.config.hubVault), "projects");
|
|
17466
18333
|
mkdir6(projectsDir);
|
|
17467
18334
|
const out = [];
|
|
@@ -17481,6 +18348,7 @@ function syncHub(deps) {
|
|
|
17481
18348
|
// packages/web/dist/web-server.js
|
|
17482
18349
|
init_dist2();
|
|
17483
18350
|
import { createServer as createServer5 } from "http";
|
|
18351
|
+
import { dirname as dirname7, join as join29 } from "path";
|
|
17484
18352
|
|
|
17485
18353
|
// packages/web/dist/snapshot-merge.js
|
|
17486
18354
|
function mergeSnapshot(daemon, external, now) {
|
|
@@ -17946,6 +18814,24 @@ function renderEnv(ext) {
|
|
|
17946
18814
|
out.push(`</section>`);
|
|
17947
18815
|
return out.join("");
|
|
17948
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
|
+
}
|
|
17949
18835
|
var ATTN_ORDER = { stale: 0, gone: 1, alive: 2, unknown: 3, stopped: 4 };
|
|
17950
18836
|
var STATE_LABEL = { alive: "running", stale: "blocked", gone: "errored", unknown: "idle", stopped: "offline" };
|
|
17951
18837
|
var STATE_CLS = { alive: "s-alive", stale: "s-stale", gone: "s-gone", unknown: "s-unknown", stopped: "s-stopped" };
|
|
@@ -18074,7 +18960,8 @@ var TABS = [
|
|
|
18074
18960
|
["overview", "Overview"],
|
|
18075
18961
|
["projects", "Projects"],
|
|
18076
18962
|
["daemon", "Daemon"],
|
|
18077
|
-
["environment", "Environment"]
|
|
18963
|
+
["environment", "Environment"],
|
|
18964
|
+
["logs", "Logs"]
|
|
18078
18965
|
];
|
|
18079
18966
|
function tabNav() {
|
|
18080
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("");
|
|
@@ -18123,6 +19010,7 @@ function renderContent(snap) {
|
|
|
18123
19010
|
out.push(renderProjects(snap, col.now));
|
|
18124
19011
|
out.push(renderDaemon(snap));
|
|
18125
19012
|
out.push(renderEnv(snap.external));
|
|
19013
|
+
out.push(renderLogs());
|
|
18126
19014
|
out.push(`</div>`);
|
|
18127
19015
|
out.push(metricsBlob(col, linkLost));
|
|
18128
19016
|
return out.join("\n");
|
|
@@ -18364,6 +19252,17 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
|
|
|
18364
19252
|
|
|
18365
19253
|
.empty{border:1px dashed var(--bezel);border-radius:12px;padding:24px;color:var(--ink-dim);text-align:center;background:var(--panel)}
|
|
18366
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
|
+
|
|
18367
19266
|
:focus-visible{outline:2px solid var(--hud);outline-offset:2px;border-radius:4px}
|
|
18368
19267
|
|
|
18369
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)}}
|
|
@@ -18377,6 +19276,7 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
|
|
|
18377
19276
|
var CLIENT_JS = `
|
|
18378
19277
|
(function(){
|
|
18379
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;
|
|
18380
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();}
|
|
18381
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);}
|
|
18382
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"/>';}
|
|
@@ -18389,9 +19289,19 @@ var CLIENT_JS = `
|
|
|
18389
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;});}
|
|
18390
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);});}
|
|
18391
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);}});}
|
|
18392
|
-
function
|
|
18393
|
-
|
|
18394
|
-
|
|
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();}});
|
|
18395
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();}});
|
|
18396
19306
|
var led=document.getElementById('led');var conn=document.getElementById('conn');var updated=document.getElementById('updated');
|
|
18397
19307
|
function tickAge(){if(!generatedAt)return;var s=Math.max(0,Math.round((Date.now()-generatedAt)/1000));updated.textContent='updated '+s+'s ago';}
|
|
@@ -18400,6 +19310,11 @@ var CLIENT_JS = `
|
|
|
18400
19310
|
es.onopen=function(){conn.textContent='live';conn.className='conn live';led.className='led live';};
|
|
18401
19311
|
es.onerror=function(){conn.textContent='reconnecting';conn.className='conn down';led.className='led down';};
|
|
18402
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(_){}};
|
|
18403
19318
|
refresh();tickAge();
|
|
18404
19319
|
})();
|
|
18405
19320
|
`.trim();
|
|
@@ -18422,7 +19337,100 @@ function renderHtml(snap, opts = {}) {
|
|
|
18422
19337
|
].join("");
|
|
18423
19338
|
}
|
|
18424
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
|
+
|
|
18425
19432
|
// packages/web/dist/web-server.js
|
|
19433
|
+
var LOG_POLL_MS = 1e3;
|
|
18426
19434
|
async function querySnapshot(sockPath) {
|
|
18427
19435
|
try {
|
|
18428
19436
|
const reply = await sendRequest(sockPath, { kind: "snapshot" });
|
|
@@ -18435,6 +19443,9 @@ async function startWebServer(opts) {
|
|
|
18435
19443
|
const host = opts.host ?? "127.0.0.1";
|
|
18436
19444
|
const now = opts.now ?? (() => Date.now());
|
|
18437
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);
|
|
18438
19449
|
let latest = null;
|
|
18439
19450
|
async function tick() {
|
|
18440
19451
|
const daemon = await querySnapshot(opts.sockPath);
|
|
@@ -18450,6 +19461,19 @@ async function startWebServer(opts) {
|
|
|
18450
19461
|
}
|
|
18451
19462
|
}
|
|
18452
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
|
+
}
|
|
18453
19477
|
const server = createServer5((req, res) => {
|
|
18454
19478
|
if (req.method !== "GET") {
|
|
18455
19479
|
res.writeHead(405).end();
|
|
@@ -18475,6 +19499,22 @@ async function startWebServer(opts) {
|
|
|
18475
19499
|
req.on("close", () => clients.delete(res));
|
|
18476
19500
|
return;
|
|
18477
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
|
+
}
|
|
18478
19518
|
res.writeHead(404).end();
|
|
18479
19519
|
});
|
|
18480
19520
|
await new Promise((resolve4, reject) => {
|
|
@@ -18489,17 +19529,33 @@ async function startWebServer(opts) {
|
|
|
18489
19529
|
});
|
|
18490
19530
|
}, opts.intervalMs);
|
|
18491
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?.();
|
|
18492
19540
|
return {
|
|
18493
19541
|
port: boundPort,
|
|
18494
19542
|
close: () => new Promise((resolve4) => {
|
|
18495
19543
|
clearInterval(timer);
|
|
19544
|
+
clearInterval(logTimer);
|
|
18496
19545
|
for (const res of clients) {
|
|
18497
19546
|
try {
|
|
18498
19547
|
res.end();
|
|
18499
19548
|
} catch {
|
|
18500
19549
|
}
|
|
18501
19550
|
}
|
|
19551
|
+
for (const res of logClients) {
|
|
19552
|
+
try {
|
|
19553
|
+
res.end();
|
|
19554
|
+
} catch {
|
|
19555
|
+
}
|
|
19556
|
+
}
|
|
18502
19557
|
clients.clear();
|
|
19558
|
+
logClients.clear();
|
|
18503
19559
|
server.close(() => resolve4());
|
|
18504
19560
|
})
|
|
18505
19561
|
};
|
|
@@ -18509,7 +19565,7 @@ async function startWebServer(opts) {
|
|
|
18509
19565
|
init_dist();
|
|
18510
19566
|
var SOCK3 = DAEMON_SOCK_PATH;
|
|
18511
19567
|
function detectCurrentWorkspace2() {
|
|
18512
|
-
const out =
|
|
19568
|
+
const out = execSync11(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
18513
19569
|
const match = out.match(/workspace:\d+/);
|
|
18514
19570
|
if (!match) {
|
|
18515
19571
|
throw new Error("Could not detect current cmux workspace. Run `squadrant dashboard --pane` from inside a cmux workspace.");
|
|
@@ -18592,8 +19648,8 @@ init_dist3();
|
|
|
18592
19648
|
init_dist4();
|
|
18593
19649
|
init_dist2();
|
|
18594
19650
|
import { Command as Command14 } from "commander";
|
|
18595
|
-
import { execSync as
|
|
18596
|
-
import
|
|
19651
|
+
import { execSync as execSync12, execFileSync as execFileSync10 } from "child_process";
|
|
19652
|
+
import fs24 from "fs";
|
|
18597
19653
|
import path28 from "path";
|
|
18598
19654
|
import os16 from "os";
|
|
18599
19655
|
import chalk15 from "chalk";
|
|
@@ -18724,7 +19780,7 @@ function resolveAnthropicRefusal(role, workspaceName, agentName, model, readOpen
|
|
|
18724
19780
|
function ensureCmuxReady(headless) {
|
|
18725
19781
|
if (headless || isInsideCmux()) return;
|
|
18726
19782
|
console.log(chalk15.yellow("\n Not running inside cmux. Opening cmux app...\n"));
|
|
18727
|
-
|
|
19783
|
+
execSync12(`open "${CMUX_APP}"`, { stdio: "inherit" });
|
|
18728
19784
|
console.log(chalk15.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
18729
19785
|
process.exit(0);
|
|
18730
19786
|
}
|
|
@@ -18921,12 +19977,12 @@ var launchCommand = new Command14("launch").description(
|
|
|
18921
19977
|
}
|
|
18922
19978
|
if (opts.all) {
|
|
18923
19979
|
const hubPath = resolveHome(config.hubVault);
|
|
18924
|
-
|
|
19980
|
+
fs24.mkdirSync(hubPath, { recursive: true });
|
|
18925
19981
|
console.log(chalk15.bold("\nLaunching all captain workspaces\n"));
|
|
18926
19982
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
18927
19983
|
const projPath = resolveHome(proj.path);
|
|
18928
19984
|
const spokePath = resolveHome(proj.spokeVault);
|
|
18929
|
-
if (!
|
|
19985
|
+
if (!fs24.existsSync(spokePath)) {
|
|
18930
19986
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
18931
19987
|
await ensureSpokeLayout(spokeDriver);
|
|
18932
19988
|
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -18964,7 +20020,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
18964
20020
|
const proj = config.projects[name];
|
|
18965
20021
|
const projPath = resolveHome(proj.path);
|
|
18966
20022
|
const spokePath = resolveHome(proj.spokeVault);
|
|
18967
|
-
if (!
|
|
20023
|
+
if (!fs24.existsSync(spokePath)) {
|
|
18968
20024
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
18969
20025
|
await ensureSpokeLayout(spokeDriver);
|
|
18970
20026
|
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -18988,7 +20044,7 @@ Launching ${selected.length} captain workspace(s) in parallel
|
|
|
18988
20044
|
const proj = config.projects[project];
|
|
18989
20045
|
const projPath = resolveHome(proj.path);
|
|
18990
20046
|
const spokePath = resolveHome(proj.spokeVault);
|
|
18991
|
-
if (!
|
|
20047
|
+
if (!fs24.existsSync(spokePath)) {
|
|
18992
20048
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
18993
20049
|
await ensureSpokeLayout(spokeDriver);
|
|
18994
20050
|
console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -19124,24 +20180,24 @@ Shutting down captain workspace for '${project}'...
|
|
|
19124
20180
|
// packages/cli/src/commands/feedback.ts
|
|
19125
20181
|
init_dist();
|
|
19126
20182
|
import { Command as Command16 } from "commander";
|
|
19127
|
-
import
|
|
20183
|
+
import fs25 from "fs";
|
|
19128
20184
|
import os17 from "os";
|
|
19129
20185
|
import path29 from "path";
|
|
19130
20186
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
19131
|
-
import { execSync as
|
|
20187
|
+
import { execSync as execSync13 } from "child_process";
|
|
19132
20188
|
import chalk17 from "chalk";
|
|
19133
20189
|
var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
19134
20190
|
function readPkgVersion() {
|
|
19135
20191
|
try {
|
|
19136
20192
|
const pkgPath = path29.join(path29.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
19137
|
-
return JSON.parse(
|
|
20193
|
+
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
19138
20194
|
} catch {
|
|
19139
20195
|
return "unknown";
|
|
19140
20196
|
}
|
|
19141
20197
|
}
|
|
19142
20198
|
function readMetrics(metricsPath) {
|
|
19143
20199
|
try {
|
|
19144
|
-
return JSON.parse(
|
|
20200
|
+
return JSON.parse(fs25.readFileSync(metricsPath, "utf-8"));
|
|
19145
20201
|
} catch {
|
|
19146
20202
|
return {};
|
|
19147
20203
|
}
|
|
@@ -19187,7 +20243,7 @@ var feedbackCommand = new Command16("feedback").description("Open a pre-filled G
|
|
|
19187
20243
|
console.log(chalk17.dim(` URL: ${issueUrl.substring(0, 80)}...
|
|
19188
20244
|
`));
|
|
19189
20245
|
try {
|
|
19190
|
-
|
|
20246
|
+
execSync13(`open "${issueUrl}"`, { stdio: "ignore" });
|
|
19191
20247
|
console.log(chalk17.green(" \u2714 Browser opened\n"));
|
|
19192
20248
|
} catch {
|
|
19193
20249
|
console.log(chalk17.yellow(" \u26A0 Could not open browser automatically."));
|
|
@@ -19589,7 +20645,7 @@ init_dist4();
|
|
|
19589
20645
|
init_dist();
|
|
19590
20646
|
import { Command as Command21 } from "commander";
|
|
19591
20647
|
import chalk22 from "chalk";
|
|
19592
|
-
import
|
|
20648
|
+
import fs26 from "fs";
|
|
19593
20649
|
import path30 from "path";
|
|
19594
20650
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
19595
20651
|
function parseScope(v2) {
|
|
@@ -19601,7 +20657,7 @@ function parseScope(v2) {
|
|
|
19601
20657
|
function findPackageRoot3() {
|
|
19602
20658
|
let dir = path30.dirname(fileURLToPath4(import.meta.url));
|
|
19603
20659
|
while (dir !== "/" && dir !== "") {
|
|
19604
|
-
if (
|
|
20660
|
+
if (fs26.existsSync(path30.join(dir, "package.json"))) return dir;
|
|
19605
20661
|
dir = path30.dirname(dir);
|
|
19606
20662
|
}
|
|
19607
20663
|
return process.cwd();
|
|
@@ -19798,12 +20854,12 @@ init_dist();
|
|
|
19798
20854
|
init_dist();
|
|
19799
20855
|
init_dist2();
|
|
19800
20856
|
import { Command as Command23 } from "commander";
|
|
19801
|
-
import
|
|
20857
|
+
import fs27 from "fs";
|
|
19802
20858
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
19803
|
-
import { dirname as
|
|
20859
|
+
import { dirname as dirname8, join as join30 } from "path";
|
|
19804
20860
|
import chalk23 from "chalk";
|
|
19805
20861
|
function runConfigCheck(opts) {
|
|
19806
|
-
const raw = JSON.parse(
|
|
20862
|
+
const raw = JSON.parse(fs27.readFileSync(opts.configPath, "utf-8"));
|
|
19807
20863
|
const def = getDefaultConfig();
|
|
19808
20864
|
const items = detectDrift(raw, def);
|
|
19809
20865
|
let working = raw;
|
|
@@ -19891,7 +20947,7 @@ function printItems(items) {
|
|
|
19891
20947
|
var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
|
|
19892
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) => {
|
|
19893
20949
|
const pkgVersion = readPkgVersion2();
|
|
19894
|
-
if (!
|
|
20950
|
+
if (!fs27.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
19895
20951
|
console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
|
|
19896
20952
|
return;
|
|
19897
20953
|
}
|
|
@@ -19936,14 +20992,14 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
19936
20992
|
}
|
|
19937
20993
|
});
|
|
19938
20994
|
function readPkgVersion2() {
|
|
19939
|
-
const pkgPath =
|
|
19940
|
-
return JSON.parse(
|
|
20995
|
+
const pkgPath = join30(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
20996
|
+
return JSON.parse(fs27.readFileSync(pkgPath, "utf-8")).version;
|
|
19941
20997
|
}
|
|
19942
20998
|
|
|
19943
20999
|
// packages/cli/src/commands/heal.ts
|
|
19944
21000
|
init_dist();
|
|
19945
21001
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
19946
|
-
import { join as
|
|
21002
|
+
import { join as join31 } from "path";
|
|
19947
21003
|
import { homedir as homedir17 } from "os";
|
|
19948
21004
|
import { Command as Command24 } from "commander";
|
|
19949
21005
|
import chalk24 from "chalk";
|
|
@@ -20183,8 +21239,8 @@ var healCommand = new Command24("heal").description("Targeted, idempotent remedi
|
|
|
20183
21239
|
}
|
|
20184
21240
|
const config = loadConfig();
|
|
20185
21241
|
const projects = opts.all ? Object.keys(config.projects) : [project];
|
|
20186
|
-
const stateRoot =
|
|
20187
|
-
const registry = new LivenessRegistry({ path:
|
|
21242
|
+
const stateRoot = join31(homedir17(), ".config", "squadrant", "state");
|
|
21243
|
+
const registry = new LivenessRegistry({ path: join31(stateRoot, "liveness.json") });
|
|
20188
21244
|
registry.load();
|
|
20189
21245
|
const code = await runHealCaptain(projects, {
|
|
20190
21246
|
liveness: readCmuxLiveness,
|
|
@@ -20216,7 +21272,7 @@ import chalk26 from "chalk";
|
|
|
20216
21272
|
init_dist();
|
|
20217
21273
|
init_dist2();
|
|
20218
21274
|
import { Command as Command25 } from "commander";
|
|
20219
|
-
import { execSync as
|
|
21275
|
+
import { execSync as execSync14 } from "child_process";
|
|
20220
21276
|
import chalk25 from "chalk";
|
|
20221
21277
|
async function runDispatch(toProject, task, opts) {
|
|
20222
21278
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
@@ -20232,7 +21288,7 @@ async function runDispatch(toProject, task, opts) {
|
|
|
20232
21288
|
warmupTimeoutMs: opts.warmupTimeout,
|
|
20233
21289
|
bootCaptain: async (project) => {
|
|
20234
21290
|
try {
|
|
20235
|
-
|
|
21291
|
+
execSync14(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
|
|
20236
21292
|
} catch {
|
|
20237
21293
|
throw new Error(`failed to launch captain for '${project}' \u2014 is squadrant installed?`);
|
|
20238
21294
|
}
|
|
@@ -20268,7 +21324,7 @@ init_dist();
|
|
|
20268
21324
|
init_dist2();
|
|
20269
21325
|
init_runtime2();
|
|
20270
21326
|
init_require_daemon();
|
|
20271
|
-
import { join as
|
|
21327
|
+
import { join as join33, dirname as dirname9 } from "path";
|
|
20272
21328
|
import { homedir as homedir18 } from "os";
|
|
20273
21329
|
import { Command as Command27 } from "commander";
|
|
20274
21330
|
import chalk28 from "chalk";
|
|
@@ -20277,23 +21333,23 @@ import chalk28 from "chalk";
|
|
|
20277
21333
|
init_dist3();
|
|
20278
21334
|
init_dist2();
|
|
20279
21335
|
import { createServer as createServer6, connect as netConnect2 } from "net";
|
|
20280
|
-
import
|
|
20281
|
-
import { join as
|
|
21336
|
+
import fs28 from "fs";
|
|
21337
|
+
import { join as join32 } from "path";
|
|
20282
21338
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
20283
21339
|
import chalk27 from "chalk";
|
|
20284
21340
|
var shared;
|
|
20285
|
-
var registryEntryPath = () =>
|
|
21341
|
+
var registryEntryPath = () => join32(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
20286
21342
|
function unregisterSenderIdentity() {
|
|
20287
21343
|
try {
|
|
20288
|
-
|
|
21344
|
+
fs28.unlinkSync(registryEntryPath());
|
|
20289
21345
|
} catch {
|
|
20290
21346
|
}
|
|
20291
21347
|
}
|
|
20292
21348
|
function registerSenderIdentity(socketPath2) {
|
|
20293
21349
|
try {
|
|
20294
21350
|
unregisterSenderIdentity();
|
|
20295
|
-
|
|
20296
|
-
|
|
21351
|
+
fs28.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
21352
|
+
fs28.writeFileSync(
|
|
20297
21353
|
registryEntryPath(),
|
|
20298
21354
|
JSON.stringify({
|
|
20299
21355
|
pid: process.pid,
|
|
@@ -20319,7 +21375,7 @@ async function sharedReceiptListener() {
|
|
|
20319
21375
|
// must never be the reason we refuse to start.
|
|
20320
21376
|
unlinkStale: (p) => {
|
|
20321
21377
|
try {
|
|
20322
|
-
|
|
21378
|
+
fs28.unlinkSync(p);
|
|
20323
21379
|
} catch {
|
|
20324
21380
|
}
|
|
20325
21381
|
},
|
|
@@ -20389,7 +21445,7 @@ async function runPing(project, message) {
|
|
|
20389
21445
|
await requireDaemon();
|
|
20390
21446
|
await needRef(resolved);
|
|
20391
21447
|
const mode = resolveCaptainChannelMode(config.defaults);
|
|
20392
|
-
const stateRoot =
|
|
21448
|
+
const stateRoot = join33(homedir18(), ".config", "squadrant", "state");
|
|
20393
21449
|
const { channels, agentFor } = mode === "off" ? { channels: {}, agentFor: () => void 0 } : await buildCaptainChannels({ stateRoot, configAgent: config.defaults.roles?.captain?.agent });
|
|
20394
21450
|
const channel = channels[agentFor(project) ?? ""];
|
|
20395
21451
|
const { handled, outcome } = await deliverToCaptain(project, message, {
|
|
@@ -20398,7 +21454,7 @@ async function runPing(project, message) {
|
|
|
20398
21454
|
log: (m) => console.error(chalk28.dim(m))
|
|
20399
21455
|
});
|
|
20400
21456
|
if (!handled) {
|
|
20401
|
-
const stateRoot2 =
|
|
21457
|
+
const stateRoot2 = join33(dirname9(DEFAULT_CONFIG_PATH), "state");
|
|
20402
21458
|
await appendCaptainMessage({
|
|
20403
21459
|
stateRoot: stateRoot2,
|
|
20404
21460
|
project,
|
|
@@ -20477,7 +21533,7 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
|
|
|
20477
21533
|
// packages/cli/src/commands/effort.ts
|
|
20478
21534
|
init_dist();
|
|
20479
21535
|
init_dist2();
|
|
20480
|
-
import
|
|
21536
|
+
import fs29 from "fs";
|
|
20481
21537
|
import path31 from "path";
|
|
20482
21538
|
import { Command as Command29 } from "commander";
|
|
20483
21539
|
import chalk30 from "chalk";
|
|
@@ -20521,7 +21577,7 @@ function effortScopeLabel(projectName) {
|
|
|
20521
21577
|
}
|
|
20522
21578
|
function canonical(p) {
|
|
20523
21579
|
try {
|
|
20524
|
-
return
|
|
21580
|
+
return fs29.realpathSync(p);
|
|
20525
21581
|
} catch {
|
|
20526
21582
|
return path31.resolve(p);
|
|
20527
21583
|
}
|
|
@@ -20583,7 +21639,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
|
|
|
20583
21639
|
|
|
20584
21640
|
// packages/cli/src/commands/tokens.ts
|
|
20585
21641
|
init_dist();
|
|
20586
|
-
import
|
|
21642
|
+
import fs30 from "fs";
|
|
20587
21643
|
import path32 from "path";
|
|
20588
21644
|
import os18 from "os";
|
|
20589
21645
|
import readline3 from "readline";
|
|
@@ -20639,7 +21695,7 @@ function foldTranscriptLine(agg, rawLine, state) {
|
|
|
20639
21695
|
async function aggregateTranscriptFile(filePath) {
|
|
20640
21696
|
const agg = emptySessionAggregate();
|
|
20641
21697
|
const state = { lastCacheRead: null };
|
|
20642
|
-
const rl = readline3.createInterface({ input:
|
|
21698
|
+
const rl = readline3.createInterface({ input: fs30.createReadStream(filePath), crlfDelay: Infinity });
|
|
20643
21699
|
for await (const line of rl) {
|
|
20644
21700
|
foldTranscriptLine(agg, line, state);
|
|
20645
21701
|
}
|
|
@@ -20707,7 +21763,7 @@ function buildRoleReport(role, sessions) {
|
|
|
20707
21763
|
}
|
|
20708
21764
|
async function readdirSafe(dir) {
|
|
20709
21765
|
try {
|
|
20710
|
-
return await
|
|
21766
|
+
return await fs30.promises.readdir(dir);
|
|
20711
21767
|
} catch {
|
|
20712
21768
|
return [];
|
|
20713
21769
|
}
|
|
@@ -20874,12 +21930,12 @@ var tokensCommand = new Command30("tokens").description(
|
|
|
20874
21930
|
// packages/cli/src/commands/telegram.ts
|
|
20875
21931
|
init_dist();
|
|
20876
21932
|
init_dist2();
|
|
20877
|
-
import { join as
|
|
21933
|
+
import { join as join34, dirname as dirname10 } from "path";
|
|
20878
21934
|
import { emitKeypressEvents } from "readline";
|
|
20879
21935
|
import { Command as Command31 } from "commander";
|
|
20880
21936
|
import chalk32 from "chalk";
|
|
20881
21937
|
function defaultStateRoot() {
|
|
20882
|
-
return
|
|
21938
|
+
return join34(dirname10(DEFAULT_CONFIG_PATH), "state");
|
|
20883
21939
|
}
|
|
20884
21940
|
async function questionMasked() {
|
|
20885
21941
|
return new Promise((resolve4) => {
|
|
@@ -21208,7 +22264,7 @@ import { Command as Command33 } from "commander";
|
|
|
21208
22264
|
import { homedir as homedir19 } from "os";
|
|
21209
22265
|
|
|
21210
22266
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
21211
|
-
import
|
|
22267
|
+
import fs31 from "fs";
|
|
21212
22268
|
import path33 from "path";
|
|
21213
22269
|
|
|
21214
22270
|
// packages/cli/src/lib/handoff-facts.ts
|
|
@@ -21260,15 +22316,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
|
|
|
21260
22316
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
21261
22317
|
var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
|
|
21262
22318
|
function appendCaptainSession(spokeVault, record) {
|
|
21263
|
-
|
|
22319
|
+
fs31.mkdirSync(spokeVault, { recursive: true });
|
|
21264
22320
|
const file = path33.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
21265
|
-
|
|
22321
|
+
fs31.appendFileSync(file, JSON.stringify(record) + "\n");
|
|
21266
22322
|
}
|
|
21267
22323
|
function readCaptainSessionRegistry(spokeVault) {
|
|
21268
22324
|
const file = path33.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
21269
|
-
if (!
|
|
22325
|
+
if (!fs31.existsSync(file)) return [];
|
|
21270
22326
|
const records = [];
|
|
21271
|
-
for (const line of
|
|
22327
|
+
for (const line of fs31.readFileSync(file, "utf-8").split("\n")) {
|
|
21272
22328
|
if (!line.trim()) continue;
|
|
21273
22329
|
try {
|
|
21274
22330
|
records.push(JSON.parse(line));
|
|
@@ -21645,7 +22701,7 @@ import os19 from "os";
|
|
|
21645
22701
|
// packages/cli/src/lib/handoff-live-repo.ts
|
|
21646
22702
|
init_dist();
|
|
21647
22703
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
21648
|
-
import
|
|
22704
|
+
import fs32 from "fs";
|
|
21649
22705
|
import path35 from "path";
|
|
21650
22706
|
|
|
21651
22707
|
// packages/cli/src/lib/handoff-branch-state.ts
|
|
@@ -21785,7 +22841,7 @@ function localAheadOfBase(runner, projectPath, base) {
|
|
|
21785
22841
|
}
|
|
21786
22842
|
function readFetchAgeMs(projectPath, now) {
|
|
21787
22843
|
try {
|
|
21788
|
-
const stat2 =
|
|
22844
|
+
const stat2 = fs32.statSync(path35.join(projectPath, ".git", "FETCH_HEAD"));
|
|
21789
22845
|
return Math.max(0, now - stat2.mtime.getTime());
|
|
21790
22846
|
} catch {
|
|
21791
22847
|
return null;
|
|
@@ -21856,7 +22912,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
|
|
|
21856
22912
|
|
|
21857
22913
|
// packages/cli/src/lib/handoff-claude-mem.ts
|
|
21858
22914
|
import { createRequire } from "module";
|
|
21859
|
-
import
|
|
22915
|
+
import fs33 from "fs";
|
|
21860
22916
|
var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
|
|
21861
22917
|
var CLAUDE_MEM_RECENCY_LIMIT = 20;
|
|
21862
22918
|
function decisionText(row) {
|
|
@@ -21870,7 +22926,7 @@ function decisionText(row) {
|
|
|
21870
22926
|
return row.narrative ?? "";
|
|
21871
22927
|
}
|
|
21872
22928
|
function queryClaudeMem(dbPath, project) {
|
|
21873
|
-
if (!
|
|
22929
|
+
if (!fs33.existsSync(dbPath)) return null;
|
|
21874
22930
|
let db;
|
|
21875
22931
|
try {
|
|
21876
22932
|
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
@@ -21913,7 +22969,7 @@ function queryClaudeMem(dbPath, project) {
|
|
|
21913
22969
|
}
|
|
21914
22970
|
|
|
21915
22971
|
// packages/cli/src/lib/handoff-transcript.ts
|
|
21916
|
-
import
|
|
22972
|
+
import fs34 from "fs";
|
|
21917
22973
|
var TRANSCRIPT_BYTE_CAP = 2e5;
|
|
21918
22974
|
var DIGEST_BYTE_CAP = 8 * 1024;
|
|
21919
22975
|
var PROMPT_TRUNCATE_CHARS = 300;
|
|
@@ -22022,27 +23078,27 @@ function extractMessages(tailText) {
|
|
|
22022
23078
|
return { lastUserMessage, lastAssistantText, digest };
|
|
22023
23079
|
}
|
|
22024
23080
|
function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
|
|
22025
|
-
if (!
|
|
22026
|
-
const content =
|
|
23081
|
+
if (!fs34.existsSync(transcriptPath)) return null;
|
|
23082
|
+
const content = fs34.readFileSync(transcriptPath, "utf-8");
|
|
22027
23083
|
const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
|
|
22028
|
-
const mtimeIso =
|
|
23084
|
+
const mtimeIso = fs34.statSync(transcriptPath).mtime.toISOString();
|
|
22029
23085
|
return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
|
|
22030
23086
|
}
|
|
22031
23087
|
|
|
22032
23088
|
// packages/cli/src/lib/handoff-archive.ts
|
|
22033
|
-
import
|
|
23089
|
+
import fs35 from "fs";
|
|
22034
23090
|
import path36 from "path";
|
|
22035
23091
|
function readNewestArchivedHandoff(spokeVault, now) {
|
|
22036
23092
|
const dir = path36.join(spokeVault, "handoffs");
|
|
22037
|
-
if (!
|
|
22038
|
-
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) => {
|
|
22039
23095
|
const full = path36.join(dir, e.name);
|
|
22040
|
-
return { name: e.name, full, mtime:
|
|
23096
|
+
return { name: e.name, full, mtime: fs35.statSync(full).mtime };
|
|
22041
23097
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
22042
23098
|
for (const candidate of candidates) {
|
|
22043
23099
|
let content;
|
|
22044
23100
|
try {
|
|
22045
|
-
content = JSON.parse(
|
|
23101
|
+
content = JSON.parse(fs35.readFileSync(candidate.full, "utf-8"));
|
|
22046
23102
|
} catch {
|
|
22047
23103
|
continue;
|
|
22048
23104
|
}
|
|
@@ -22385,15 +23441,15 @@ init_dist();
|
|
|
22385
23441
|
init_dist();
|
|
22386
23442
|
init_dist();
|
|
22387
23443
|
init_dist();
|
|
22388
|
-
var __dirname =
|
|
22389
|
-
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"));
|
|
22390
23446
|
ensureRuntimeSynced({
|
|
22391
|
-
sourceRoot:
|
|
22392
|
-
runtimeRoot:
|
|
23447
|
+
sourceRoot: join35(__dirname, ".."),
|
|
23448
|
+
runtimeRoot: join35(homedir20(), ".config", "squadrant")
|
|
22393
23449
|
});
|
|
22394
23450
|
if (!process.env.VITEST) {
|
|
22395
23451
|
try {
|
|
22396
|
-
syncShippedOpencodeSkills({ pkgRoot:
|
|
23452
|
+
syncShippedOpencodeSkills({ pkgRoot: join35(__dirname, "..") });
|
|
22397
23453
|
} catch (err) {
|
|
22398
23454
|
process.stderr.write(
|
|
22399
23455
|
`squadrant: opencode skills sync skipped: ${err.message}
|
|
@@ -22403,7 +23459,7 @@ if (!process.env.VITEST) {
|
|
|
22403
23459
|
}
|
|
22404
23460
|
if (process.argv[2] !== "config") {
|
|
22405
23461
|
try {
|
|
22406
|
-
const cfgPath =
|
|
23462
|
+
const cfgPath = join35(homedir20(), ".config", "squadrant", "config.json");
|
|
22407
23463
|
if (existsSync14(cfgPath)) {
|
|
22408
23464
|
const cfg = JSON.parse(readConfigFileSync(cfgPath));
|
|
22409
23465
|
if (needsCheck(cfg, pkg.version)) {
|