squadrant 0.21.1 → 0.23.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 +1666 -343
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +1004 -193
- package/dist/squadrantd.js.map +1 -1
- package/package.json +2 -1
package/dist/squadrantd.js
CHANGED
|
@@ -94,6 +94,9 @@ function isGateMode(v2) {
|
|
|
94
94
|
function isGatePolicy(v2) {
|
|
95
95
|
return GATE_POLICIES.includes(v2);
|
|
96
96
|
}
|
|
97
|
+
function isGateEngine(v2) {
|
|
98
|
+
return GATE_ENGINES.includes(v2);
|
|
99
|
+
}
|
|
97
100
|
function getDefaultConfig() {
|
|
98
101
|
return {
|
|
99
102
|
commandName: "\u{1F3DB}\uFE0F command",
|
|
@@ -135,12 +138,7 @@ function getDefaultConfig() {
|
|
|
135
138
|
// resolver fallback is the safety net).
|
|
136
139
|
controlChannel: { claude: "on", opencode: "on" },
|
|
137
140
|
crewRouting: {
|
|
138
|
-
rules:
|
|
139
|
-
{ tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
|
|
140
|
-
{ tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
|
|
141
|
-
{ tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
|
|
142
|
-
{ tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
|
|
143
|
-
]
|
|
141
|
+
rules: DEFAULT_CREW_ROUTING_RULES.map((r) => ({ ...r }))
|
|
144
142
|
}
|
|
145
143
|
},
|
|
146
144
|
metrics: {
|
|
@@ -182,15 +180,29 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
182
180
|
function resolveHome(p) {
|
|
183
181
|
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
184
182
|
}
|
|
185
|
-
var GATE_MODES, GATE_POLICIES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH;
|
|
183
|
+
var GATE_MODES, GATE_POLICIES, GATE_ENGINES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
|
|
186
184
|
var init_config = __esm({
|
|
187
185
|
"packages/shared/dist/config.js"() {
|
|
188
186
|
init_config_io();
|
|
189
187
|
GATE_MODES = ["on", "off", "auto"];
|
|
190
188
|
GATE_POLICIES = ["deny-dangerous", "ask-on-doubt"];
|
|
189
|
+
GATE_ENGINES = ["router", "auto-gate"];
|
|
191
190
|
DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
|
|
192
191
|
CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
|
|
193
192
|
DAEMON_SOCK_PATH = path2.join(CONFIG_DIR, "squadrant.sock");
|
|
193
|
+
DEFAULT_CREW_ROUTING_RULES = [
|
|
194
|
+
{ tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
|
|
195
|
+
{ tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
|
|
196
|
+
{ tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
|
|
197
|
+
{ tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// packages/shared/dist/provider-preset.js
|
|
203
|
+
var init_provider_preset = __esm({
|
|
204
|
+
"packages/shared/dist/provider-preset.js"() {
|
|
205
|
+
init_config();
|
|
194
206
|
}
|
|
195
207
|
});
|
|
196
208
|
|
|
@@ -790,6 +802,7 @@ var init_daemon_keys = __esm({
|
|
|
790
802
|
var init_dist = __esm({
|
|
791
803
|
"packages/shared/dist/index.js"() {
|
|
792
804
|
init_config();
|
|
805
|
+
init_provider_preset();
|
|
793
806
|
init_project_config();
|
|
794
807
|
init_effort();
|
|
795
808
|
init_router_model();
|
|
@@ -1061,10 +1074,11 @@ function formatDelegationReport(rec, originProject, targetProject) {
|
|
|
1061
1074
|
return null;
|
|
1062
1075
|
}
|
|
1063
1076
|
}
|
|
1064
|
-
function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
|
|
1077
|
+
function firePush(deps, project, prev, next, event, lastCaptainTurnAt, prevRec) {
|
|
1065
1078
|
if (!deps.notify)
|
|
1066
1079
|
return;
|
|
1067
|
-
|
|
1080
|
+
const questionChanged = prev === "blocked" && next.state === "blocked" && prevRec != null && (prevRec.question ?? "").trim() !== (next.question ?? "").trim();
|
|
1081
|
+
if (prev === next.state && !questionChanged)
|
|
1068
1082
|
return;
|
|
1069
1083
|
if (!ATTENTION_STATES.has(next.state))
|
|
1070
1084
|
return;
|
|
@@ -1102,6 +1116,7 @@ function createDaemon(deps) {
|
|
|
1102
1116
|
const { store, now } = deps;
|
|
1103
1117
|
const lastCaptainTurnAt = /* @__PURE__ */ new Map();
|
|
1104
1118
|
const quietNotifiedAt = /* @__PURE__ */ new Map();
|
|
1119
|
+
const blockedRenudgedFor = /* @__PURE__ */ new Map();
|
|
1105
1120
|
const resendAttemptedAt = /* @__PURE__ */ new Map();
|
|
1106
1121
|
const firstTurnUndeliveredBudgetMs = deps.firstTurnUndeliveredBudgetMs ?? DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS;
|
|
1107
1122
|
const firstTurnResendCooldownMs = deps.firstTurnResendCooldownMs ?? DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS;
|
|
@@ -1122,7 +1137,7 @@ function createDaemon(deps) {
|
|
|
1122
1137
|
const next = reduce(cur, event, now());
|
|
1123
1138
|
if (next !== cur) {
|
|
1124
1139
|
store.put(next);
|
|
1125
|
-
firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id));
|
|
1140
|
+
firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id), cur);
|
|
1126
1141
|
}
|
|
1127
1142
|
return next;
|
|
1128
1143
|
}
|
|
@@ -1274,6 +1289,8 @@ function createDaemon(deps) {
|
|
|
1274
1289
|
}
|
|
1275
1290
|
const recoveryPromises = [];
|
|
1276
1291
|
for (const r of store.listAll()) {
|
|
1292
|
+
if (r.state !== "blocked")
|
|
1293
|
+
blockedRenudgedFor.delete(r.id);
|
|
1277
1294
|
if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {
|
|
1278
1295
|
store.delete(r.project, r.id);
|
|
1279
1296
|
continue;
|
|
@@ -1336,6 +1353,27 @@ function createDaemon(deps) {
|
|
|
1336
1353
|
continue;
|
|
1337
1354
|
}
|
|
1338
1355
|
}
|
|
1356
|
+
if (r.state === "blocked" && !r.operatorHold) {
|
|
1357
|
+
const threshold = deps.blockedRenudgeMs ?? DEFAULT_BLOCKED_RENUDGE_MS;
|
|
1358
|
+
const blockedFor = t - r.lastHeartbeat;
|
|
1359
|
+
const episodeKey = r.question ?? "";
|
|
1360
|
+
if (blockedFor > threshold && blockedRenudgedFor.get(r.id) !== episodeKey) {
|
|
1361
|
+
blockedRenudgedFor.set(r.id, episodeKey);
|
|
1362
|
+
if (deps.notify) {
|
|
1363
|
+
const tag = crewTag(r);
|
|
1364
|
+
const mins = Math.max(1, Math.round(blockedFor / 6e4));
|
|
1365
|
+
const message = `CREW BLOCKED REMINDER ${tag}: still blocked ~${mins}min (reminder \u2014 same question, not a new one): ${(r.question ?? "(no question)").trim()}`;
|
|
1366
|
+
const synthEvent = { type: "task.blocked", id: r.id, reason: "renudge", question: r.question ?? "" };
|
|
1367
|
+
try {
|
|
1368
|
+
const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
|
|
1369
|
+
if (p && typeof p.catch === "function")
|
|
1370
|
+
p.catch(() => {
|
|
1371
|
+
});
|
|
1372
|
+
} catch {
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1339
1377
|
if (r.mode === "interactive" && r.state === "submitted" && !r.firstTurnConfirmedAt) {
|
|
1340
1378
|
const undeliveredMs = t - r.createdAt;
|
|
1341
1379
|
if (undeliveredMs > firstTurnUndeliveredBudgetMs) {
|
|
@@ -1425,7 +1463,7 @@ function createDaemon(deps) {
|
|
|
1425
1463
|
}
|
|
1426
1464
|
};
|
|
1427
1465
|
}
|
|
1428
|
-
var DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS, DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS, DEFAULT_TASK_TIMEOUT_MS, TERMINAL_RECORD_TTL_MS, TERMINAL_RECORD_KEEP_PER_PROJECT, ATTENTION_STATES, REAPABLE_SURFACE_STATES, IDLE_DEBOUNCE_MS, KNOWN_EVENT_TYPES;
|
|
1466
|
+
var DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS, DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS, DEFAULT_BLOCKED_RENUDGE_MS, DEFAULT_TASK_TIMEOUT_MS, TERMINAL_RECORD_TTL_MS, TERMINAL_RECORD_KEEP_PER_PROJECT, ATTENTION_STATES, REAPABLE_SURFACE_STATES, IDLE_DEBOUNCE_MS, KNOWN_EVENT_TYPES;
|
|
1429
1467
|
var init_reduce = __esm({
|
|
1430
1468
|
"packages/core/dist/daemon/reduce.js"() {
|
|
1431
1469
|
init_dist();
|
|
@@ -1433,6 +1471,7 @@ var init_reduce = __esm({
|
|
|
1433
1471
|
init_watchdog();
|
|
1434
1472
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS = 12e4;
|
|
1435
1473
|
DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS = 6e4;
|
|
1474
|
+
DEFAULT_BLOCKED_RENUDGE_MS = 30 * 60 * 1e3;
|
|
1436
1475
|
DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
|
|
1437
1476
|
TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1438
1477
|
TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
|
|
@@ -2027,8 +2066,8 @@ function reconcileLiveness(prev, next) {
|
|
|
2027
2066
|
if (!prev)
|
|
2028
2067
|
return next;
|
|
2029
2068
|
if (next.source === "scan") {
|
|
2030
|
-
const
|
|
2031
|
-
return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
2069
|
+
const pidAlive2 = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;
|
|
2070
|
+
return { ...prev, pidAlive: pidAlive2, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
2032
2071
|
}
|
|
2033
2072
|
if (next.startedAt >= prev.startedAt || next.lastState === "end")
|
|
2034
2073
|
return next;
|
|
@@ -2774,6 +2813,68 @@ function createDirectSurfaceLivenessProbe(cmux2, getCaptainTitle) {
|
|
|
2774
2813
|
}
|
|
2775
2814
|
};
|
|
2776
2815
|
}
|
|
2816
|
+
function hasModalOptionList(screen) {
|
|
2817
|
+
if (!screen)
|
|
2818
|
+
return false;
|
|
2819
|
+
const lines = screen.split(/\r?\n/);
|
|
2820
|
+
let bottomHR = -1;
|
|
2821
|
+
let topHR = -1;
|
|
2822
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2823
|
+
if (HR_RE.test(lines[i])) {
|
|
2824
|
+
if (bottomHR === -1)
|
|
2825
|
+
bottomHR = i;
|
|
2826
|
+
else {
|
|
2827
|
+
topHR = i;
|
|
2828
|
+
break;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
if (topHR === -1)
|
|
2833
|
+
return false;
|
|
2834
|
+
return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
|
|
2835
|
+
}
|
|
2836
|
+
function classifyCaptainPane(screen, opts = {}) {
|
|
2837
|
+
if (screen == null || screen.trim() === "")
|
|
2838
|
+
return { status: "unreadable" };
|
|
2839
|
+
if (hasModalOptionList(screen))
|
|
2840
|
+
return { status: "locked" };
|
|
2841
|
+
if (WORKING_RE.test(screen))
|
|
2842
|
+
return { status: "mid-turn" };
|
|
2843
|
+
const idleAfterMs = opts.idleAfterMs ?? 0;
|
|
2844
|
+
const stale = opts.outputAgoMs !== void 0 && opts.outputAgoMs >= idleAfterMs;
|
|
2845
|
+
return stale ? { status: "no-session" } : { status: "mid-turn" };
|
|
2846
|
+
}
|
|
2847
|
+
function screenHash(screen) {
|
|
2848
|
+
let h = 2166136261;
|
|
2849
|
+
for (let i = 0; i < screen.length; i++) {
|
|
2850
|
+
h ^= screen.charCodeAt(i);
|
|
2851
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
2852
|
+
}
|
|
2853
|
+
return h.toString(16).padStart(8, "0");
|
|
2854
|
+
}
|
|
2855
|
+
function createCaptainPaneReader(cmux2, getCaptainTitle, opts = {}) {
|
|
2856
|
+
const idleAfterMs = opts.idleAfterMs ?? PANE_IDLE_AFTER_MS;
|
|
2857
|
+
return async (project) => {
|
|
2858
|
+
try {
|
|
2859
|
+
const wsId = await cmux2.findWorkspaceId(getCaptainTitle(project));
|
|
2860
|
+
if (!wsId)
|
|
2861
|
+
return { status: "no-session" };
|
|
2862
|
+
const surfaces = await cmux2.listSurfaces(wsId);
|
|
2863
|
+
const want = getCaptainTitle(project);
|
|
2864
|
+
const pane = surfaces.find((s) => s.title === want) ?? surfaces[0];
|
|
2865
|
+
if (!pane)
|
|
2866
|
+
return { status: "no-session" };
|
|
2867
|
+
const screen = await cmux2.readPaneScreen(pane);
|
|
2868
|
+
if (screen == null)
|
|
2869
|
+
return { status: "unreadable" };
|
|
2870
|
+
const outputAgoMs = opts.noteScreen?.(project, screenHash(screen));
|
|
2871
|
+
return classifyCaptainPane(screen, { outputAgoMs, idleAfterMs });
|
|
2872
|
+
} catch (e) {
|
|
2873
|
+
opts.log?.(`captain pane read failed project=${project}: ${e.message}`);
|
|
2874
|
+
return { status: "unreadable" };
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2777
2878
|
function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
|
|
2778
2879
|
return async (rec) => {
|
|
2779
2880
|
try {
|
|
@@ -2796,11 +2897,14 @@ function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
|
|
|
2796
2897
|
}
|
|
2797
2898
|
};
|
|
2798
2899
|
}
|
|
2799
|
-
var TAIL_LINES;
|
|
2900
|
+
var TAIL_LINES, HR_RE, WORKING_RE, PANE_IDLE_AFTER_MS;
|
|
2800
2901
|
var init_crew_pane_reader = __esm({
|
|
2801
2902
|
"packages/core/dist/crew-pane-reader.js"() {
|
|
2802
2903
|
init_dist();
|
|
2803
2904
|
TAIL_LINES = 25;
|
|
2905
|
+
HR_RE = /^\s*─{10,}\s*$/;
|
|
2906
|
+
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;
|
|
2907
|
+
PANE_IDLE_AFTER_MS = 10 * 6e4;
|
|
2804
2908
|
}
|
|
2805
2909
|
});
|
|
2806
2910
|
|
|
@@ -3498,8 +3602,11 @@ function buildRouterEnv(creds, model) {
|
|
|
3498
3602
|
if (headers)
|
|
3499
3603
|
env.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
3500
3604
|
}
|
|
3501
|
-
if (model)
|
|
3605
|
+
if (model) {
|
|
3502
3606
|
env.ANTHROPIC_MODEL = model;
|
|
3607
|
+
env.CLAUDE_CODE_SUBAGENT_MODEL = model;
|
|
3608
|
+
env.ANTHROPIC_SMALL_FAST_MODEL = model;
|
|
3609
|
+
}
|
|
3503
3610
|
return env;
|
|
3504
3611
|
}
|
|
3505
3612
|
function mergeClaudeEnvRouterSettings(routerEnv, claudeEnv) {
|
|
@@ -4665,8 +4772,9 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4665
4772
|
if (mode !== "off")
|
|
4666
4773
|
throw new DeferDelivery(null, "no-channel");
|
|
4667
4774
|
}
|
|
4775
|
+
const paneOpts = { ...sendOpts, agent };
|
|
4668
4776
|
try {
|
|
4669
|
-
return await cmux2.send(surface, text,
|
|
4777
|
+
return await cmux2.send(surface, text, paneOpts);
|
|
4670
4778
|
} catch (e) {
|
|
4671
4779
|
if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
|
|
4672
4780
|
throw e;
|
|
@@ -4678,7 +4786,7 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4678
4786
|
}
|
|
4679
4787
|
log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
|
|
4680
4788
|
surface = next;
|
|
4681
|
-
return cmux2.send(next, text,
|
|
4789
|
+
return cmux2.send(next, text, paneOpts);
|
|
4682
4790
|
}
|
|
4683
4791
|
});
|
|
4684
4792
|
if ("delivered" in result) {
|
|
@@ -5609,16 +5717,26 @@ function createRunCommand(cliBin) {
|
|
|
5609
5717
|
}
|
|
5610
5718
|
};
|
|
5611
5719
|
}
|
|
5720
|
+
function captainAliveStateFromHealth(rows, project) {
|
|
5721
|
+
const row = rows.find((h) => h.kind === "captain" && h.project === project);
|
|
5722
|
+
if (!row)
|
|
5723
|
+
return "unknown";
|
|
5724
|
+
if (row.state === "alive")
|
|
5725
|
+
return "alive";
|
|
5726
|
+
if (row.state === "gone" || row.state === "stopped")
|
|
5727
|
+
return "dead";
|
|
5728
|
+
return "unknown";
|
|
5729
|
+
}
|
|
5612
5730
|
function isCaptainAliveFromHealth(rows, project) {
|
|
5613
|
-
return rows
|
|
5731
|
+
return captainAliveStateFromHealth(rows, project) === "alive";
|
|
5614
5732
|
}
|
|
5615
5733
|
function createIsCaptainAlive(sock) {
|
|
5616
5734
|
return async (project) => {
|
|
5617
5735
|
try {
|
|
5618
5736
|
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
5619
|
-
return
|
|
5737
|
+
return captainAliveStateFromHealth(health ?? [], project);
|
|
5620
5738
|
} catch {
|
|
5621
|
-
return
|
|
5739
|
+
return "unknown";
|
|
5622
5740
|
}
|
|
5623
5741
|
};
|
|
5624
5742
|
}
|
|
@@ -5655,16 +5773,19 @@ function createEnsureCaptainAlive(deps) {
|
|
|
5655
5773
|
const now = deps.now ?? (() => Date.now());
|
|
5656
5774
|
const inFlight = /* @__PURE__ */ new Map();
|
|
5657
5775
|
async function run(project) {
|
|
5658
|
-
|
|
5776
|
+
const first = await deps.isAlive(project);
|
|
5777
|
+
if (first === "alive")
|
|
5659
5778
|
return "alive";
|
|
5660
5779
|
await deps.launch(project);
|
|
5661
5780
|
const deadline = now() + warmupTimeoutMs;
|
|
5781
|
+
let last = first;
|
|
5662
5782
|
while (now() < deadline) {
|
|
5663
|
-
|
|
5783
|
+
last = await deps.isAlive(project);
|
|
5784
|
+
if (last === "alive")
|
|
5664
5785
|
return "launched";
|
|
5665
5786
|
await sleep3(pollMs);
|
|
5666
5787
|
}
|
|
5667
|
-
return "timeout";
|
|
5788
|
+
return last === "dead" ? "timeout" : "unknown";
|
|
5668
5789
|
}
|
|
5669
5790
|
return function ensure(project) {
|
|
5670
5791
|
const existing = inFlight.get(project);
|
|
@@ -5719,6 +5840,26 @@ ${ev.question}`;
|
|
|
5719
5840
|
function formatInbound(text) {
|
|
5720
5841
|
return `\u{1F4E9} [from Telegram] ${text}`;
|
|
5721
5842
|
}
|
|
5843
|
+
function mediaKind(m) {
|
|
5844
|
+
for (const [field, name] of MEDIA_KINDS) {
|
|
5845
|
+
if (m[field] !== void 0)
|
|
5846
|
+
return name;
|
|
5847
|
+
}
|
|
5848
|
+
return void 0;
|
|
5849
|
+
}
|
|
5850
|
+
function mediaMarker(kind) {
|
|
5851
|
+
return `[${kind} attached - not forwarded]`;
|
|
5852
|
+
}
|
|
5853
|
+
function inboundBody(text, kind) {
|
|
5854
|
+
if (kind === void 0)
|
|
5855
|
+
return text ?? "";
|
|
5856
|
+
const marker = mediaMarker(kind);
|
|
5857
|
+
return text ? `${text}
|
|
5858
|
+
${marker}` : marker;
|
|
5859
|
+
}
|
|
5860
|
+
function formatMediaReceipt(kind, hasCaption) {
|
|
5861
|
+
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`;
|
|
5862
|
+
}
|
|
5722
5863
|
function formatUsageLine(u) {
|
|
5723
5864
|
const reqs = u.requests === 1 ? "1 req" : `${u.requests} reqs`;
|
|
5724
5865
|
const named = Object.keys(u.models).filter((m) => m !== "unknown").sort((a, b) => u.models[b].costUsd - u.models[a].costUsd);
|
|
@@ -5730,8 +5871,19 @@ function maskToken(token) {
|
|
|
5730
5871
|
return token;
|
|
5731
5872
|
return "*".repeat(token.length - 4) + token.slice(-4);
|
|
5732
5873
|
}
|
|
5874
|
+
var MEDIA_KINDS;
|
|
5733
5875
|
var init_format = __esm({
|
|
5734
5876
|
"packages/core/dist/telegram/format.js"() {
|
|
5877
|
+
MEDIA_KINDS = [
|
|
5878
|
+
["photo", "photo"],
|
|
5879
|
+
["voice", "voice message"],
|
|
5880
|
+
["video_note", "video message"],
|
|
5881
|
+
["video", "video"],
|
|
5882
|
+
["audio", "audio file"],
|
|
5883
|
+
["document", "document"],
|
|
5884
|
+
["animation", "animation"],
|
|
5885
|
+
["sticker", "sticker"]
|
|
5886
|
+
];
|
|
5735
5887
|
}
|
|
5736
5888
|
});
|
|
5737
5889
|
|
|
@@ -5754,6 +5906,8 @@ function loadState(stateRoot) {
|
|
|
5754
5906
|
};
|
|
5755
5907
|
if (typeof data.lastUserId === "number")
|
|
5756
5908
|
result.lastUserId = data.lastUserId;
|
|
5909
|
+
if (data.pending && Object.keys(data.pending).length > 0)
|
|
5910
|
+
result.pending = data.pending;
|
|
5757
5911
|
return result;
|
|
5758
5912
|
} catch {
|
|
5759
5913
|
return { offset: 0, topics: {}, notify: {} };
|
|
@@ -5767,6 +5921,19 @@ function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
|
5767
5921
|
s.topics[topicKey(project, scope)] = topicId;
|
|
5768
5922
|
saveState(stateRoot, s);
|
|
5769
5923
|
}
|
|
5924
|
+
function pruneTopics(stateRoot, keep) {
|
|
5925
|
+
const s = loadState(stateRoot);
|
|
5926
|
+
const removed = Object.keys(s.topics).filter((key) => {
|
|
5927
|
+
const sep3 = key.indexOf("::");
|
|
5928
|
+
return !keep(sep3 === -1 ? key : key.slice(0, sep3));
|
|
5929
|
+
});
|
|
5930
|
+
if (removed.length === 0)
|
|
5931
|
+
return [];
|
|
5932
|
+
for (const key of removed)
|
|
5933
|
+
delete s.topics[key];
|
|
5934
|
+
saveState(stateRoot, s);
|
|
5935
|
+
return removed;
|
|
5936
|
+
}
|
|
5770
5937
|
function isNotifyActive(stateRoot, project) {
|
|
5771
5938
|
return loadState(stateRoot).notify[project] === true;
|
|
5772
5939
|
}
|
|
@@ -5780,6 +5947,42 @@ function setNotify(stateRoot, project, active) {
|
|
|
5780
5947
|
s.notify[project] = active;
|
|
5781
5948
|
saveState(stateRoot, s);
|
|
5782
5949
|
}
|
|
5950
|
+
function loadPending(stateRoot) {
|
|
5951
|
+
return loadState(stateRoot).pending ?? {};
|
|
5952
|
+
}
|
|
5953
|
+
function setPending(stateRoot, project, p) {
|
|
5954
|
+
const s = loadState(stateRoot);
|
|
5955
|
+
s.pending = { ...s.pending ?? {}, [project]: p };
|
|
5956
|
+
saveState(stateRoot, s);
|
|
5957
|
+
}
|
|
5958
|
+
function clearPending(stateRoot, project) {
|
|
5959
|
+
const s = loadState(stateRoot);
|
|
5960
|
+
if (!s.pending || !(project in s.pending))
|
|
5961
|
+
return;
|
|
5962
|
+
delete s.pending[project];
|
|
5963
|
+
if (Object.keys(s.pending).length === 0)
|
|
5964
|
+
delete s.pending;
|
|
5965
|
+
saveState(stateRoot, s);
|
|
5966
|
+
}
|
|
5967
|
+
function markPendingWarned(stateRoot, project, at) {
|
|
5968
|
+
const s = loadState(stateRoot);
|
|
5969
|
+
const p = s.pending?.[project];
|
|
5970
|
+
if (!p)
|
|
5971
|
+
return;
|
|
5972
|
+
s.pending[project] = { ...p, warnedAt: at };
|
|
5973
|
+
saveState(stateRoot, s);
|
|
5974
|
+
}
|
|
5975
|
+
function notePaneScreen(stateRoot, project, hash, at) {
|
|
5976
|
+
const s = loadState(stateRoot);
|
|
5977
|
+
const p = s.pending?.[project];
|
|
5978
|
+
if (!p)
|
|
5979
|
+
return void 0;
|
|
5980
|
+
if (p.paneHash === hash && p.paneChangedAt !== void 0)
|
|
5981
|
+
return at - p.paneChangedAt;
|
|
5982
|
+
s.pending[project] = { ...p, paneHash: hash, paneChangedAt: at };
|
|
5983
|
+
saveState(stateRoot, s);
|
|
5984
|
+
return void 0;
|
|
5985
|
+
}
|
|
5783
5986
|
function findProjectByThread(stateRoot, threadId) {
|
|
5784
5987
|
const s = loadState(stateRoot);
|
|
5785
5988
|
for (const [key, id] of Object.entries(s.topics)) {
|
|
@@ -5802,17 +6005,18 @@ var init_state = __esm({
|
|
|
5802
6005
|
function createTelegramClient(opts) {
|
|
5803
6006
|
const fetchImpl = opts.fetch ?? fetch;
|
|
5804
6007
|
const base = `https://api.telegram.org/bot${opts.token}`;
|
|
5805
|
-
async function call(method, body) {
|
|
6008
|
+
async function call(method, body, signal) {
|
|
5806
6009
|
const res = await fetchImpl(`${base}/${method}`, {
|
|
5807
6010
|
method: "POST",
|
|
5808
6011
|
headers: { "content-type": "application/json" },
|
|
5809
|
-
body: JSON.stringify(body)
|
|
6012
|
+
body: JSON.stringify(body),
|
|
6013
|
+
...signal ? { signal } : {}
|
|
5810
6014
|
});
|
|
5811
6015
|
const json = await res.json();
|
|
5812
6016
|
if (!res.ok || !json.ok) {
|
|
5813
6017
|
const code = json.error_code ?? res.status;
|
|
5814
6018
|
const desc = json.description ?? "unknown error";
|
|
5815
|
-
throw new
|
|
6019
|
+
throw new TelegramApiError(code, `telegram ${method} failed (${code}): ${desc}`, json.parameters?.retry_after);
|
|
5816
6020
|
}
|
|
5817
6021
|
return json.result;
|
|
5818
6022
|
}
|
|
@@ -5821,8 +6025,8 @@ function createTelegramClient(opts) {
|
|
|
5821
6025
|
const r = await call("getMe", {});
|
|
5822
6026
|
return { id: r.id, username: r.username };
|
|
5823
6027
|
},
|
|
5824
|
-
getUpdates(offset, timeoutSec = 50) {
|
|
5825
|
-
return call("getUpdates", { offset, timeout: timeoutSec });
|
|
6028
|
+
getUpdates(offset, timeoutSec = 50, signal) {
|
|
6029
|
+
return call("getUpdates", { offset, timeout: timeoutSec }, signal);
|
|
5826
6030
|
},
|
|
5827
6031
|
async sendMessage(chatId, threadId, text, replyMarkup) {
|
|
5828
6032
|
const body = { chat_id: chatId, text };
|
|
@@ -5845,6 +6049,9 @@ function createTelegramClient(opts) {
|
|
|
5845
6049
|
const r = await call("createForumTopic", { chat_id: chatId, name });
|
|
5846
6050
|
return r.message_thread_id;
|
|
5847
6051
|
},
|
|
6052
|
+
async deleteForumTopic(chatId, threadId) {
|
|
6053
|
+
await call("deleteForumTopic", { chat_id: chatId, message_thread_id: threadId });
|
|
6054
|
+
},
|
|
5848
6055
|
async setMyCommands(commands) {
|
|
5849
6056
|
await call("setMyCommands", { commands });
|
|
5850
6057
|
},
|
|
@@ -5853,11 +6060,241 @@ function createTelegramClient(opts) {
|
|
|
5853
6060
|
if (threadId !== void 0)
|
|
5854
6061
|
body.message_thread_id = threadId;
|
|
5855
6062
|
await call("sendChatAction", body);
|
|
6063
|
+
},
|
|
6064
|
+
async setMessageReaction(chatId, messageId, emoji) {
|
|
6065
|
+
await call("setMessageReaction", {
|
|
6066
|
+
chat_id: chatId,
|
|
6067
|
+
message_id: messageId,
|
|
6068
|
+
reaction: [{ type: "emoji", emoji }]
|
|
6069
|
+
});
|
|
5856
6070
|
}
|
|
5857
6071
|
};
|
|
5858
6072
|
}
|
|
6073
|
+
var TelegramApiError;
|
|
5859
6074
|
var init_client = __esm({
|
|
5860
6075
|
"packages/core/dist/telegram/client.js"() {
|
|
6076
|
+
TelegramApiError = class extends Error {
|
|
6077
|
+
code;
|
|
6078
|
+
retryAfterSec;
|
|
6079
|
+
constructor(code, message, retryAfterSec) {
|
|
6080
|
+
super(message);
|
|
6081
|
+
this.code = code;
|
|
6082
|
+
this.retryAfterSec = retryAfterSec;
|
|
6083
|
+
this.name = "TelegramApiError";
|
|
6084
|
+
}
|
|
6085
|
+
};
|
|
6086
|
+
}
|
|
6087
|
+
});
|
|
6088
|
+
|
|
6089
|
+
// packages/core/dist/telegram/inbound-lifecycle.js
|
|
6090
|
+
function defaultSchedule(fn, ms) {
|
|
6091
|
+
const t = setTimeout(fn, ms);
|
|
6092
|
+
t.unref?.();
|
|
6093
|
+
return () => clearTimeout(t);
|
|
6094
|
+
}
|
|
6095
|
+
function formatWatchdogWarning(project, read, minutes) {
|
|
6096
|
+
const head = `\u23F1\uFE0F [${project}] no reply for ${minutes} minutes`;
|
|
6097
|
+
switch (read.status) {
|
|
6098
|
+
case "locked":
|
|
6099
|
+
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.`;
|
|
6100
|
+
case "mid-turn":
|
|
6101
|
+
return `${head} \u2014 the captain is still working (a turn is in flight), just slow. No action needed yet.`;
|
|
6102
|
+
case "no-session":
|
|
6103
|
+
return `${head} \u2014 the captain looks GONE (no live session in its pane). Run \`squadrant launch ${project}\`.`;
|
|
6104
|
+
case "unreadable":
|
|
6105
|
+
return `${head} \u2014 couldn't read the captain's pane to classify it. Check it manually.`;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
function createInboundLifecycle(opts) {
|
|
6109
|
+
const { stateRoot, cfg, sendChatAction, sendReply, readPane, log } = opts;
|
|
6110
|
+
const now = opts.now ?? Date.now;
|
|
6111
|
+
const schedule = opts.schedule ?? defaultSchedule;
|
|
6112
|
+
const typingMs = opts.typingMs ?? TYPING_KEEPALIVE_MS;
|
|
6113
|
+
const watchdogMs = opts.watchdogMs ?? WATCHDOG_MS;
|
|
6114
|
+
const warnAtMs = watchdogMs + (opts.watchdogMs === void 0 ? WATCHDOG_GRACE_MS : 0);
|
|
6115
|
+
const active = /* @__PURE__ */ new Set();
|
|
6116
|
+
let running = false;
|
|
6117
|
+
function issue(project, threadId) {
|
|
6118
|
+
void Promise.resolve(sendChatAction(cfg.supergroupId, threadId, "typing")).catch((e) => log(`telegram typing keep-alive failed project=${project}: ${e.message}`));
|
|
6119
|
+
}
|
|
6120
|
+
function drain(project) {
|
|
6121
|
+
active.add(project);
|
|
6122
|
+
const tick = () => {
|
|
6123
|
+
if (!running || !active.has(project))
|
|
6124
|
+
return;
|
|
6125
|
+
const p = loadPending(stateRoot)[project];
|
|
6126
|
+
if (!p) {
|
|
6127
|
+
active.delete(project);
|
|
6128
|
+
return;
|
|
6129
|
+
}
|
|
6130
|
+
if (now() - p.startedAt >= warnAtMs && p.warnedAt === void 0) {
|
|
6131
|
+
void warn(project, p.threadId);
|
|
6132
|
+
}
|
|
6133
|
+
issue(project, p.threadId);
|
|
6134
|
+
schedule(tick, typingMs);
|
|
6135
|
+
};
|
|
6136
|
+
schedule(tick, typingMs);
|
|
6137
|
+
}
|
|
6138
|
+
async function warn(project, threadId) {
|
|
6139
|
+
markPendingWarned(stateRoot, project, now());
|
|
6140
|
+
let read = { status: "unreadable" };
|
|
6141
|
+
try {
|
|
6142
|
+
if (readPane)
|
|
6143
|
+
read = await readPane(project);
|
|
6144
|
+
} catch (e) {
|
|
6145
|
+
log(`telegram watchdog pane read failed project=${project}: ${e.message}`);
|
|
6146
|
+
}
|
|
6147
|
+
try {
|
|
6148
|
+
await sendReply(threadId, formatWatchdogWarning(project, read, Math.floor(watchdogMs / 6e4)));
|
|
6149
|
+
} catch (e) {
|
|
6150
|
+
log(`telegram watchdog warning failed project=${project}: ${e.message}`);
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
return {
|
|
6154
|
+
start() {
|
|
6155
|
+
running = true;
|
|
6156
|
+
for (const [project, p] of Object.entries(loadPending(stateRoot))) {
|
|
6157
|
+
void (async () => {
|
|
6158
|
+
let read = { status: "unreadable" };
|
|
6159
|
+
try {
|
|
6160
|
+
if (readPane)
|
|
6161
|
+
read = await readPane(project);
|
|
6162
|
+
} catch (e) {
|
|
6163
|
+
log(`telegram resume pane read failed project=${project}: ${e.message}`);
|
|
6164
|
+
}
|
|
6165
|
+
const still = loadPending(stateRoot)[project];
|
|
6166
|
+
if (!still || still.startedAt !== p.startedAt)
|
|
6167
|
+
return;
|
|
6168
|
+
if (read.status === "no-session") {
|
|
6169
|
+
clearPending(stateRoot, project);
|
|
6170
|
+
log(`telegram typing lifecycle cleared for ${project}: captain session gone on boot`);
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
6173
|
+
log(`telegram typing lifecycle resumed for ${project} (thread ${p.threadId})`);
|
|
6174
|
+
issue(project, p.threadId);
|
|
6175
|
+
drain(project);
|
|
6176
|
+
})();
|
|
6177
|
+
}
|
|
6178
|
+
},
|
|
6179
|
+
stop() {
|
|
6180
|
+
running = false;
|
|
6181
|
+
active.clear();
|
|
6182
|
+
},
|
|
6183
|
+
begin(project, threadId) {
|
|
6184
|
+
setPending(stateRoot, project, { threadId, startedAt: now() });
|
|
6185
|
+
issue(project, threadId);
|
|
6186
|
+
if (running && !active.has(project))
|
|
6187
|
+
drain(project);
|
|
6188
|
+
},
|
|
6189
|
+
pending() {
|
|
6190
|
+
return loadPending(stateRoot);
|
|
6191
|
+
}
|
|
6192
|
+
};
|
|
6193
|
+
}
|
|
6194
|
+
var TYPING_KEEPALIVE_MS, WATCHDOG_MS, WATCHDOG_GRACE_MS;
|
|
6195
|
+
var init_inbound_lifecycle = __esm({
|
|
6196
|
+
"packages/core/dist/telegram/inbound-lifecycle.js"() {
|
|
6197
|
+
init_state();
|
|
6198
|
+
TYPING_KEEPALIVE_MS = 4e3;
|
|
6199
|
+
WATCHDOG_MS = 15 * 6e4;
|
|
6200
|
+
WATCHDOG_GRACE_MS = 1;
|
|
6201
|
+
}
|
|
6202
|
+
});
|
|
6203
|
+
|
|
6204
|
+
// packages/core/dist/telegram/notify.js
|
|
6205
|
+
function runTelegramStatus(opts) {
|
|
6206
|
+
const tg = opts.config.telegram;
|
|
6207
|
+
const env = opts.env ?? process.env;
|
|
6208
|
+
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
6209
|
+
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
6210
|
+
const sep3 = key.indexOf("::");
|
|
6211
|
+
return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
|
|
6212
|
+
});
|
|
6213
|
+
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
6214
|
+
}
|
|
6215
|
+
function runTelegramNotifySet(opts) {
|
|
6216
|
+
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
6217
|
+
}
|
|
6218
|
+
function runTelegramNotifyPref(args) {
|
|
6219
|
+
const { project, dimension, value, root } = args;
|
|
6220
|
+
if (dimension === "crew") {
|
|
6221
|
+
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
6222
|
+
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
6223
|
+
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
6224
|
+
return { ok: true };
|
|
6225
|
+
}
|
|
6226
|
+
if (value !== "on" && value !== "off")
|
|
6227
|
+
return { ok: false, message: "cap must be on|off" };
|
|
6228
|
+
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
6229
|
+
return { ok: true };
|
|
6230
|
+
}
|
|
6231
|
+
function runTelegramNotifyStatus(opts) {
|
|
6232
|
+
const s = loadState(opts.stateRoot);
|
|
6233
|
+
const projects = /* @__PURE__ */ new Set();
|
|
6234
|
+
for (const key of Object.keys(s.topics)) {
|
|
6235
|
+
const sep3 = key.indexOf("::");
|
|
6236
|
+
projects.add(sep3 === -1 ? key : key.slice(0, sep3));
|
|
6237
|
+
}
|
|
6238
|
+
for (const p of Object.keys(s.notify))
|
|
6239
|
+
projects.add(p);
|
|
6240
|
+
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
6241
|
+
}
|
|
6242
|
+
function capAllowed(project, globalNotify, root) {
|
|
6243
|
+
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
6244
|
+
}
|
|
6245
|
+
function confirmationText(project, before, after, dim) {
|
|
6246
|
+
if (dim === "active")
|
|
6247
|
+
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
6248
|
+
if (dim === "cap")
|
|
6249
|
+
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
6250
|
+
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
6251
|
+
}
|
|
6252
|
+
async function runNotifyConfirmation(opts) {
|
|
6253
|
+
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
6254
|
+
if (!quieter || dim === null)
|
|
6255
|
+
return false;
|
|
6256
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6257
|
+
if (topicId === void 0)
|
|
6258
|
+
return false;
|
|
6259
|
+
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
6260
|
+
try {
|
|
6261
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
6262
|
+
return true;
|
|
6263
|
+
} catch {
|
|
6264
|
+
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
6265
|
+
return false;
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
async function runTelegramSend(opts) {
|
|
6269
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6270
|
+
if (topicId === void 0) {
|
|
6271
|
+
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
6272
|
+
}
|
|
6273
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
6274
|
+
clearPending(opts.stateRoot, opts.project);
|
|
6275
|
+
return { chatId: opts.cfg.supergroupId, topicId };
|
|
6276
|
+
}
|
|
6277
|
+
async function runTelegramLink(opts) {
|
|
6278
|
+
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6279
|
+
if (existing !== void 0)
|
|
6280
|
+
return { topicId: existing, created: false };
|
|
6281
|
+
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
6282
|
+
const winner = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6283
|
+
if (winner !== void 0) {
|
|
6284
|
+
try {
|
|
6285
|
+
await opts.client.deleteForumTopic?.(opts.cfg.supergroupId, topicId);
|
|
6286
|
+
} catch {
|
|
6287
|
+
}
|
|
6288
|
+
return { topicId: winner, created: false };
|
|
6289
|
+
}
|
|
6290
|
+
setTopic(opts.stateRoot, opts.project, topicId);
|
|
6291
|
+
return { topicId, created: true };
|
|
6292
|
+
}
|
|
6293
|
+
var init_notify = __esm({
|
|
6294
|
+
"packages/core/dist/telegram/notify.js"() {
|
|
6295
|
+
init_dist();
|
|
6296
|
+
init_state();
|
|
6297
|
+
init_format();
|
|
5861
6298
|
}
|
|
5862
6299
|
});
|
|
5863
6300
|
|
|
@@ -5945,8 +6382,10 @@ var init_tiers = __esm({
|
|
|
5945
6382
|
});
|
|
5946
6383
|
|
|
5947
6384
|
// packages/core/dist/telegram/bridge.js
|
|
6385
|
+
import fs11 from "fs";
|
|
5948
6386
|
import os5 from "os";
|
|
5949
6387
|
import path12 from "path";
|
|
6388
|
+
import crypto2 from "crypto";
|
|
5950
6389
|
function formatInboundReceipt(project, outcome) {
|
|
5951
6390
|
if (!outcome)
|
|
5952
6391
|
return void 0;
|
|
@@ -5962,6 +6401,85 @@ function formatInboundReceipt(project, outcome) {
|
|
|
5962
6401
|
return void 0;
|
|
5963
6402
|
}
|
|
5964
6403
|
}
|
|
6404
|
+
function telegramPollLockPath(token) {
|
|
6405
|
+
const hash = crypto2.createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
6406
|
+
return path12.join(os5.tmpdir(), `squadrant-telegram-poll-${hash}.lock`);
|
|
6407
|
+
}
|
|
6408
|
+
function pidAlive(pid) {
|
|
6409
|
+
try {
|
|
6410
|
+
process.kill(pid, 0);
|
|
6411
|
+
return true;
|
|
6412
|
+
} catch (e) {
|
|
6413
|
+
return e.code === "EPERM";
|
|
6414
|
+
}
|
|
6415
|
+
}
|
|
6416
|
+
function lockIsHeld(file) {
|
|
6417
|
+
let pid;
|
|
6418
|
+
let ageMs;
|
|
6419
|
+
try {
|
|
6420
|
+
const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
|
|
6421
|
+
pid = Number.isFinite(parsed) ? parsed : void 0;
|
|
6422
|
+
ageMs = Date.now() - fs11.statSync(file).mtimeMs;
|
|
6423
|
+
} catch {
|
|
6424
|
+
return false;
|
|
6425
|
+
}
|
|
6426
|
+
if (pid !== void 0)
|
|
6427
|
+
return pidAlive(pid);
|
|
6428
|
+
return ageMs < POLL_LOCK_STALE_MS;
|
|
6429
|
+
}
|
|
6430
|
+
function acquirePollLock(token) {
|
|
6431
|
+
const file = telegramPollLockPath(token);
|
|
6432
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
6433
|
+
try {
|
|
6434
|
+
const fd = fs11.openSync(file, "wx");
|
|
6435
|
+
fs11.writeSync(fd, String(process.pid));
|
|
6436
|
+
fs11.closeSync(fd);
|
|
6437
|
+
return () => {
|
|
6438
|
+
try {
|
|
6439
|
+
fs11.unlinkSync(file);
|
|
6440
|
+
} catch {
|
|
6441
|
+
}
|
|
6442
|
+
};
|
|
6443
|
+
} catch (e) {
|
|
6444
|
+
if (e.code !== "EEXIST")
|
|
6445
|
+
return null;
|
|
6446
|
+
if (attempt === 0 && !lockIsHeld(file)) {
|
|
6447
|
+
try {
|
|
6448
|
+
fs11.unlinkSync(file);
|
|
6449
|
+
} catch {
|
|
6450
|
+
}
|
|
6451
|
+
continue;
|
|
6452
|
+
}
|
|
6453
|
+
return null;
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6456
|
+
return null;
|
|
6457
|
+
}
|
|
6458
|
+
function defaultSchedule2(fn, ms) {
|
|
6459
|
+
const t = setTimeout(fn, ms);
|
|
6460
|
+
t.unref?.();
|
|
6461
|
+
return () => clearTimeout(t);
|
|
6462
|
+
}
|
|
6463
|
+
function foreignPollLockHolder(token, selfPid = process.pid) {
|
|
6464
|
+
const file = telegramPollLockPath(token);
|
|
6465
|
+
let pid;
|
|
6466
|
+
try {
|
|
6467
|
+
const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
|
|
6468
|
+
if (!Number.isFinite(parsed))
|
|
6469
|
+
return null;
|
|
6470
|
+
pid = parsed;
|
|
6471
|
+
} catch {
|
|
6472
|
+
return null;
|
|
6473
|
+
}
|
|
6474
|
+
if (pid === selfPid)
|
|
6475
|
+
return null;
|
|
6476
|
+
return pidAlive(pid) ? pid : null;
|
|
6477
|
+
}
|
|
6478
|
+
function pollBackoffMs(err, pollMs) {
|
|
6479
|
+
if (err instanceof TelegramApiError && err.retryAfterSec !== void 0)
|
|
6480
|
+
return err.retryAfterSec * 1e3;
|
|
6481
|
+
return pollMs;
|
|
6482
|
+
}
|
|
5965
6483
|
function parseNotifyPref(text) {
|
|
5966
6484
|
const parts = text.trim().split(/\s+/);
|
|
5967
6485
|
if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
|
|
@@ -5988,9 +6506,17 @@ function notifyToggle(text) {
|
|
|
5988
6506
|
}
|
|
5989
6507
|
function createTelegramBridge(opts) {
|
|
5990
6508
|
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
6509
|
+
const lifecycle = opts.lifecycle;
|
|
5991
6510
|
const configRoot = opts.configRoot ?? path12.join(os5.homedir(), ".config", "squadrant");
|
|
5992
6511
|
const pollMs = cfg.pollMs ?? 1e3;
|
|
6512
|
+
const schedule = opts.schedule ?? defaultSchedule2;
|
|
6513
|
+
const lockToken = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
6514
|
+
let releasePollLock = null;
|
|
6515
|
+
let pollAbort = null;
|
|
5993
6516
|
let running = false;
|
|
6517
|
+
let pollStopped = false;
|
|
6518
|
+
let selfHealCancel = null;
|
|
6519
|
+
let conflictCount = 0;
|
|
5994
6520
|
let lastSuccessfulPollAt = null;
|
|
5995
6521
|
let lastError = null;
|
|
5996
6522
|
let lastErrorAt = null;
|
|
@@ -6000,12 +6526,8 @@ function createTelegramBridge(opts) {
|
|
|
6000
6526
|
saveState(stateRoot, s);
|
|
6001
6527
|
}
|
|
6002
6528
|
async function sendToTopic(project, text) {
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
6006
|
-
setTopic(stateRoot, project, threadId);
|
|
6007
|
-
}
|
|
6008
|
-
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
6529
|
+
const { topicId } = await runTelegramLink({ project, cfg, client, stateRoot });
|
|
6530
|
+
await client.sendMessage(cfg.supergroupId, topicId, text);
|
|
6009
6531
|
}
|
|
6010
6532
|
async function deliverOutbound(project, ev) {
|
|
6011
6533
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -6192,10 +6714,13 @@ ${formatUsageLine(usage2)}`;
|
|
|
6192
6714
|
}
|
|
6193
6715
|
await runChannelCommand(text, fromId, void 0);
|
|
6194
6716
|
}
|
|
6195
|
-
async function handleProjectTopic(text, threadId, fromId) {
|
|
6717
|
+
async function handleProjectTopic(text, threadId, fromId, messageId, media) {
|
|
6196
6718
|
const resolved = findProjectByThread(stateRoot, threadId);
|
|
6197
|
-
if (!resolved)
|
|
6719
|
+
if (!resolved) {
|
|
6720
|
+
log(`telegram inbound in unbound topic thread=${threadId} \u2014 no project linked, message dropped`);
|
|
6721
|
+
await reply(threadId, "\u26A0\uFE0F this topic isn't linked to a project \u2014 run: squadrant telegram link <project>");
|
|
6198
6722
|
return;
|
|
6723
|
+
}
|
|
6199
6724
|
if (isBareSpawn(text)) {
|
|
6200
6725
|
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
6201
6726
|
await reply(threadId, "\u26D4 not authorized");
|
|
@@ -6245,21 +6770,16 @@ ${formatUsageLine(usage2)}`;
|
|
|
6245
6770
|
await runChannelCommand(text, fromId, threadId);
|
|
6246
6771
|
return;
|
|
6247
6772
|
}
|
|
6248
|
-
|
|
6249
|
-
log(`telegram
|
|
6250
|
-
}
|
|
6773
|
+
if (messageId !== void 0) {
|
|
6774
|
+
void Promise.resolve(client.setMessageReaction?.(cfg.supergroupId, messageId, "\u{1F44D}")).catch((e) => log(`telegram stage-1 reaction failed project=${resolved.project}: ${e.message}`));
|
|
6775
|
+
}
|
|
6251
6776
|
setNotify(stateRoot, resolved.project, true);
|
|
6252
6777
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
6253
|
-
|
|
6254
|
-
const r = await ensureCaptainAlive(resolved.project);
|
|
6778
|
+
void ensureCaptainAlive(resolved.project).then((r) => {
|
|
6255
6779
|
if (r === "timeout") {
|
|
6256
|
-
|
|
6257
|
-
} else {
|
|
6258
|
-
await reply(threadId, `\u{1F4E8} delivered to ${resolved.project} captain`);
|
|
6780
|
+
log(`telegram auto-launch: warmup timed out project=${resolved.project} \u2014 delivery verdict (not this probe) decides the receipt`);
|
|
6259
6781
|
}
|
|
6260
|
-
}
|
|
6261
|
-
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
6262
|
-
}
|
|
6782
|
+
}).catch((e) => log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`));
|
|
6263
6783
|
}
|
|
6264
6784
|
let handled = false;
|
|
6265
6785
|
let outcome;
|
|
@@ -6268,13 +6788,22 @@ ${formatUsageLine(usage2)}`;
|
|
|
6268
6788
|
handled = res.handled;
|
|
6269
6789
|
outcome = res.outcome;
|
|
6270
6790
|
}
|
|
6791
|
+
const deliveryHeld = outcome?.status === "held";
|
|
6271
6792
|
if (!handled) {
|
|
6272
6793
|
await appendCaptainMessage2({ stateRoot, project: resolved.project, text: formatInbound(text), source: "telegram" });
|
|
6794
|
+
outcome = void 0;
|
|
6273
6795
|
}
|
|
6274
6796
|
const receipt = formatInboundReceipt(resolved.project, outcome);
|
|
6275
6797
|
if (receipt && sendReply) {
|
|
6276
6798
|
await sendReply(threadId, receipt).catch((e) => log(`telegram receipt failed project=${resolved.project}: ${e.message}`));
|
|
6277
6799
|
}
|
|
6800
|
+
if (media) {
|
|
6801
|
+
await reply(threadId, formatMediaReceipt(media.kind, media.hasCaption));
|
|
6802
|
+
}
|
|
6803
|
+
if (deliveryHeld)
|
|
6804
|
+
return;
|
|
6805
|
+
await reply(threadId, CAPTAIN_RECEIVED_ACK);
|
|
6806
|
+
lifecycle?.begin(resolved.project, threadId);
|
|
6278
6807
|
}
|
|
6279
6808
|
async function handleUpdate(u) {
|
|
6280
6809
|
if (u.callback_query) {
|
|
@@ -6282,7 +6811,11 @@ ${formatUsageLine(usage2)}`;
|
|
|
6282
6811
|
return;
|
|
6283
6812
|
}
|
|
6284
6813
|
const m = u.message;
|
|
6285
|
-
if (!m
|
|
6814
|
+
if (!m)
|
|
6815
|
+
return;
|
|
6816
|
+
const kind = mediaKind(m);
|
|
6817
|
+
const text = m.text ?? m.caption;
|
|
6818
|
+
if (text === void 0 && kind === void 0)
|
|
6286
6819
|
return;
|
|
6287
6820
|
if (!cfg.chats.includes(m.chat.id))
|
|
6288
6821
|
return;
|
|
@@ -6296,7 +6829,7 @@ ${formatUsageLine(usage2)}`;
|
|
|
6296
6829
|
await reply(threadId, "\u26D4 not authorized");
|
|
6297
6830
|
return;
|
|
6298
6831
|
}
|
|
6299
|
-
const task =
|
|
6832
|
+
const task = (text ?? "").trim();
|
|
6300
6833
|
if (!task) {
|
|
6301
6834
|
await reply(threadId, "spawn cancelled \u2014 empty task");
|
|
6302
6835
|
return;
|
|
@@ -6307,39 +6840,122 @@ ${formatUsageLine(usage2)}`;
|
|
|
6307
6840
|
return;
|
|
6308
6841
|
}
|
|
6309
6842
|
if (m.message_thread_id === void 0) {
|
|
6843
|
+
if (m.text === void 0)
|
|
6844
|
+
return;
|
|
6310
6845
|
await handleGeneral(m.text, m.from?.id);
|
|
6311
6846
|
return;
|
|
6312
6847
|
}
|
|
6313
|
-
await handleProjectTopic(
|
|
6848
|
+
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 });
|
|
6849
|
+
}
|
|
6850
|
+
function acquirePollLockOrLog() {
|
|
6851
|
+
if (!lockToken)
|
|
6852
|
+
return true;
|
|
6853
|
+
releasePollLock = acquirePollLock(lockToken);
|
|
6854
|
+
if (releasePollLock)
|
|
6855
|
+
return true;
|
|
6856
|
+
const holder = foreignPollLockHolder(lockToken);
|
|
6857
|
+
log(`telegram inbound poll disabled: another consumer already polls this bot token (single-consumer guard, #830)${holder !== null ? ` \u2014 lock held by pid ${holder}` : ""}`);
|
|
6858
|
+
return false;
|
|
6859
|
+
}
|
|
6860
|
+
function scheduleSelfHeal() {
|
|
6861
|
+
selfHealCancel?.();
|
|
6862
|
+
selfHealCancel = schedule(() => {
|
|
6863
|
+
selfHealCancel = null;
|
|
6864
|
+
if (pollStopped || running)
|
|
6865
|
+
return;
|
|
6866
|
+
if (!acquirePollLockOrLog()) {
|
|
6867
|
+
scheduleSelfHeal();
|
|
6868
|
+
return;
|
|
6869
|
+
}
|
|
6870
|
+
running = true;
|
|
6871
|
+
conflictCount = 0;
|
|
6872
|
+
log("telegram inbound poll re-attempting after a 409 stop (#850)");
|
|
6873
|
+
void pollLoop();
|
|
6874
|
+
}, CONFLICT_409_SELF_HEAL_MS);
|
|
6314
6875
|
}
|
|
6315
6876
|
async function pollLoop() {
|
|
6316
6877
|
while (running) {
|
|
6878
|
+
const ac = new AbortController();
|
|
6879
|
+
pollAbort = ac;
|
|
6880
|
+
let waitMs = pollMs;
|
|
6317
6881
|
try {
|
|
6318
6882
|
const offset = loadState(stateRoot).offset;
|
|
6319
|
-
const updates = await client.getUpdates(offset, LONG_POLL_SEC);
|
|
6883
|
+
const updates = await client.getUpdates(offset, LONG_POLL_SEC, ac.signal);
|
|
6884
|
+
conflictCount = 0;
|
|
6320
6885
|
for (const u of updates) {
|
|
6321
6886
|
await handleUpdate(u);
|
|
6322
6887
|
persistOffset(u.update_id + 1);
|
|
6323
6888
|
}
|
|
6324
6889
|
lastSuccessfulPollAt = Date.now();
|
|
6325
6890
|
} catch (e) {
|
|
6326
|
-
|
|
6891
|
+
if (ac.signal.aborted)
|
|
6892
|
+
break;
|
|
6893
|
+
const msg = e.message;
|
|
6894
|
+
lastError = msg;
|
|
6327
6895
|
lastErrorAt = Date.now();
|
|
6328
|
-
|
|
6896
|
+
if (e instanceof TelegramApiError && e.code === 409) {
|
|
6897
|
+
conflictCount += 1;
|
|
6898
|
+
if (conflictCount >= CONFLICT_409_TERMINAL_AFTER) {
|
|
6899
|
+
log(`telegram inbound poll stopped: repeated 409 conflict (another getUpdates consumer holds this token) \u2014 ${msg}`);
|
|
6900
|
+
break;
|
|
6901
|
+
}
|
|
6902
|
+
} else {
|
|
6903
|
+
log(`telegram inbound poll failed: ${msg}`);
|
|
6904
|
+
}
|
|
6905
|
+
waitMs = pollBackoffMs(e, pollMs);
|
|
6906
|
+
} finally {
|
|
6907
|
+
pollAbort = null;
|
|
6329
6908
|
}
|
|
6330
6909
|
if (running)
|
|
6331
|
-
await sleep2(
|
|
6910
|
+
await sleep2(waitMs);
|
|
6332
6911
|
}
|
|
6912
|
+
running = false;
|
|
6913
|
+
releasePollLock?.();
|
|
6914
|
+
releasePollLock = null;
|
|
6915
|
+
if (!pollStopped)
|
|
6916
|
+
scheduleSelfHeal();
|
|
6333
6917
|
}
|
|
6334
6918
|
return {
|
|
6335
6919
|
start() {
|
|
6336
6920
|
if (running)
|
|
6337
6921
|
return;
|
|
6922
|
+
pollStopped = false;
|
|
6923
|
+
selfHealCancel?.();
|
|
6924
|
+
selfHealCancel = null;
|
|
6925
|
+
if (!acquirePollLockOrLog())
|
|
6926
|
+
return;
|
|
6927
|
+
const known = opts.registeredProjects?.();
|
|
6928
|
+
if (known !== void 0) {
|
|
6929
|
+
try {
|
|
6930
|
+
const removed = pruneTopics(stateRoot, (p) => known.includes(p));
|
|
6931
|
+
if (removed.length > 0)
|
|
6932
|
+
log(`telegram registry pruned ${removed.length} stale topic link(s): ${removed.join(", ")}`);
|
|
6933
|
+
} catch (e) {
|
|
6934
|
+
log(`telegram registry prune failed: ${e.message}`);
|
|
6935
|
+
}
|
|
6936
|
+
}
|
|
6338
6937
|
running = true;
|
|
6938
|
+
conflictCount = 0;
|
|
6939
|
+
try {
|
|
6940
|
+
lifecycle?.start();
|
|
6941
|
+
} catch (e) {
|
|
6942
|
+
log(`telegram lifecycle start failed: ${e.message}`);
|
|
6943
|
+
}
|
|
6339
6944
|
void pollLoop();
|
|
6340
6945
|
},
|
|
6341
6946
|
stop() {
|
|
6342
6947
|
running = false;
|
|
6948
|
+
pollStopped = true;
|
|
6949
|
+
selfHealCancel?.();
|
|
6950
|
+
selfHealCancel = null;
|
|
6951
|
+
try {
|
|
6952
|
+
lifecycle?.stop();
|
|
6953
|
+
} catch (e) {
|
|
6954
|
+
log(`telegram lifecycle stop failed: ${e.message}`);
|
|
6955
|
+
}
|
|
6956
|
+
pollAbort?.abort();
|
|
6957
|
+
releasePollLock?.();
|
|
6958
|
+
releasePollLock = null;
|
|
6343
6959
|
},
|
|
6344
6960
|
pushLifecycle(project, ev) {
|
|
6345
6961
|
void deliverOutbound(project, ev).catch((e) => {
|
|
@@ -6356,17 +6972,23 @@ ${formatUsageLine(usage2)}`;
|
|
|
6356
6972
|
}
|
|
6357
6973
|
};
|
|
6358
6974
|
}
|
|
6359
|
-
var LONG_POLL_SEC, sleep2, CREW_TIERS, RECOGNIZED_CHANNEL_COMMANDS;
|
|
6975
|
+
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;
|
|
6360
6976
|
var init_bridge = __esm({
|
|
6361
6977
|
"packages/core/dist/telegram/bridge.js"() {
|
|
6362
6978
|
init_dist();
|
|
6979
|
+
init_client();
|
|
6363
6980
|
init_auth();
|
|
6364
6981
|
init_commands();
|
|
6365
6982
|
init_format();
|
|
6983
|
+
init_notify();
|
|
6366
6984
|
init_panels();
|
|
6367
6985
|
init_state();
|
|
6368
6986
|
init_tiers();
|
|
6987
|
+
CAPTAIN_RECEIVED_ACK = "\u2705 captain received";
|
|
6369
6988
|
LONG_POLL_SEC = 50;
|
|
6989
|
+
POLL_LOCK_STALE_MS = 2 * 6e4;
|
|
6990
|
+
CONFLICT_409_TERMINAL_AFTER = 3;
|
|
6991
|
+
CONFLICT_409_SELF_HEAL_MS = 5 * 6e4;
|
|
6370
6992
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6371
6993
|
CREW_TIERS = ["all", "alert_only", "done_only", "none"];
|
|
6372
6994
|
RECOGNIZED_CHANNEL_COMMANDS = /* @__PURE__ */ new Set(["status", "projects", "crews", "launch", "effort", "spawn"]);
|
|
@@ -6411,7 +7033,7 @@ var init_restart_daemon = __esm({
|
|
|
6411
7033
|
});
|
|
6412
7034
|
|
|
6413
7035
|
// packages/core/dist/telegram/setup.js
|
|
6414
|
-
import
|
|
7036
|
+
import fs12 from "fs";
|
|
6415
7037
|
function resolveSetupGroup(existingSupergroupId, opts) {
|
|
6416
7038
|
if (existingSupergroupId !== void 0 && !opts.redetect)
|
|
6417
7039
|
return "reuse";
|
|
@@ -6462,7 +7084,7 @@ function writeTelegramConfig(configPath, opts) {
|
|
|
6462
7084
|
let config;
|
|
6463
7085
|
let raw = null;
|
|
6464
7086
|
try {
|
|
6465
|
-
raw =
|
|
7087
|
+
raw = fs12.readFileSync(configPath, "utf-8");
|
|
6466
7088
|
} catch (err) {
|
|
6467
7089
|
if (err.code !== "ENOENT") {
|
|
6468
7090
|
throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
|
|
@@ -6501,94 +7123,6 @@ var init_setup = __esm({
|
|
|
6501
7123
|
}
|
|
6502
7124
|
});
|
|
6503
7125
|
|
|
6504
|
-
// packages/core/dist/telegram/notify.js
|
|
6505
|
-
function runTelegramStatus(opts) {
|
|
6506
|
-
const tg = opts.config.telegram;
|
|
6507
|
-
const env = opts.env ?? process.env;
|
|
6508
|
-
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
6509
|
-
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
6510
|
-
const sep3 = key.indexOf("::");
|
|
6511
|
-
return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
|
|
6512
|
-
});
|
|
6513
|
-
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
6514
|
-
}
|
|
6515
|
-
function runTelegramNotifySet(opts) {
|
|
6516
|
-
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
6517
|
-
}
|
|
6518
|
-
function runTelegramNotifyPref(args) {
|
|
6519
|
-
const { project, dimension, value, root } = args;
|
|
6520
|
-
if (dimension === "crew") {
|
|
6521
|
-
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
6522
|
-
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
6523
|
-
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
6524
|
-
return { ok: true };
|
|
6525
|
-
}
|
|
6526
|
-
if (value !== "on" && value !== "off")
|
|
6527
|
-
return { ok: false, message: "cap must be on|off" };
|
|
6528
|
-
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
6529
|
-
return { ok: true };
|
|
6530
|
-
}
|
|
6531
|
-
function runTelegramNotifyStatus(opts) {
|
|
6532
|
-
const s = loadState(opts.stateRoot);
|
|
6533
|
-
const projects = /* @__PURE__ */ new Set();
|
|
6534
|
-
for (const key of Object.keys(s.topics)) {
|
|
6535
|
-
const sep3 = key.indexOf("::");
|
|
6536
|
-
projects.add(sep3 === -1 ? key : key.slice(0, sep3));
|
|
6537
|
-
}
|
|
6538
|
-
for (const p of Object.keys(s.notify))
|
|
6539
|
-
projects.add(p);
|
|
6540
|
-
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
6541
|
-
}
|
|
6542
|
-
function capAllowed(project, globalNotify, root) {
|
|
6543
|
-
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
6544
|
-
}
|
|
6545
|
-
function confirmationText(project, before, after, dim) {
|
|
6546
|
-
if (dim === "active")
|
|
6547
|
-
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
6548
|
-
if (dim === "cap")
|
|
6549
|
-
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
6550
|
-
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
6551
|
-
}
|
|
6552
|
-
async function runNotifyConfirmation(opts) {
|
|
6553
|
-
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
6554
|
-
if (!quieter || dim === null)
|
|
6555
|
-
return false;
|
|
6556
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6557
|
-
if (topicId === void 0)
|
|
6558
|
-
return false;
|
|
6559
|
-
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
6560
|
-
try {
|
|
6561
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
6562
|
-
return true;
|
|
6563
|
-
} catch {
|
|
6564
|
-
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
6565
|
-
return false;
|
|
6566
|
-
}
|
|
6567
|
-
}
|
|
6568
|
-
async function runTelegramSend(opts) {
|
|
6569
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6570
|
-
if (topicId === void 0) {
|
|
6571
|
-
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
6572
|
-
}
|
|
6573
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
6574
|
-
return { chatId: opts.cfg.supergroupId, topicId };
|
|
6575
|
-
}
|
|
6576
|
-
async function runTelegramLink(opts) {
|
|
6577
|
-
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6578
|
-
if (existing !== void 0)
|
|
6579
|
-
return { topicId: existing, created: false };
|
|
6580
|
-
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
6581
|
-
setTopic(opts.stateRoot, opts.project, topicId);
|
|
6582
|
-
return { topicId, created: true };
|
|
6583
|
-
}
|
|
6584
|
-
var init_notify = __esm({
|
|
6585
|
-
"packages/core/dist/telegram/notify.js"() {
|
|
6586
|
-
init_dist();
|
|
6587
|
-
init_state();
|
|
6588
|
-
init_format();
|
|
6589
|
-
}
|
|
6590
|
-
});
|
|
6591
|
-
|
|
6592
7126
|
// packages/core/dist/telegram/index.js
|
|
6593
7127
|
var init_telegram = __esm({
|
|
6594
7128
|
"packages/core/dist/telegram/index.js"() {
|
|
@@ -6600,6 +7134,7 @@ var init_telegram = __esm({
|
|
|
6600
7134
|
init_format();
|
|
6601
7135
|
init_state();
|
|
6602
7136
|
init_client();
|
|
7137
|
+
init_inbound_lifecycle();
|
|
6603
7138
|
init_bridge();
|
|
6604
7139
|
init_setup();
|
|
6605
7140
|
init_notify();
|
|
@@ -7234,6 +7769,12 @@ function resolveGateMode(env, gate) {
|
|
|
7234
7769
|
return fromEnv;
|
|
7235
7770
|
return gate?.mode && isGateMode(gate.mode) ? gate.mode : "auto";
|
|
7236
7771
|
}
|
|
7772
|
+
function resolveGateEngine(env, gate) {
|
|
7773
|
+
const fromEnv = env.SQUADRANT_GATE_ENGINE;
|
|
7774
|
+
if (fromEnv !== void 0 && isGateEngine(fromEnv))
|
|
7775
|
+
return fromEnv;
|
|
7776
|
+
return gate?.engine && isGateEngine(gate.engine) ? gate.engine : "router";
|
|
7777
|
+
}
|
|
7237
7778
|
function resolveGatePolicy(env, gate) {
|
|
7238
7779
|
const fromEnv = env.SQUADRANT_GATE_POLICY;
|
|
7239
7780
|
if (fromEnv !== void 0 && isGatePolicy(fromEnv))
|
|
@@ -7618,6 +8159,59 @@ var init_permission_gate = __esm({
|
|
|
7618
8159
|
}
|
|
7619
8160
|
});
|
|
7620
8161
|
|
|
8162
|
+
// packages/core/dist/auto-gate.js
|
|
8163
|
+
import { createAutoGate, projectGateConfig } from "@squadrant-ai/auto-gate";
|
|
8164
|
+
function gateForProjection(config) {
|
|
8165
|
+
const gate = config.defaults.gate;
|
|
8166
|
+
if (!gate)
|
|
8167
|
+
return {};
|
|
8168
|
+
const out = {};
|
|
8169
|
+
if (gate.mode)
|
|
8170
|
+
out.mode = gate.mode;
|
|
8171
|
+
if (gate.tools)
|
|
8172
|
+
out.tools = gate.tools;
|
|
8173
|
+
if (gate.deny)
|
|
8174
|
+
out.deny = gate.deny;
|
|
8175
|
+
return out;
|
|
8176
|
+
}
|
|
8177
|
+
function createSquadrantAutoGate(deps) {
|
|
8178
|
+
const env = deps.env ?? process.env;
|
|
8179
|
+
const taskId = env.SQUADRANT_CREW_TASK_ID;
|
|
8180
|
+
const project = deps.project ?? env.SQUADRANT_CREW_PROJECT;
|
|
8181
|
+
const mode = resolveGateMode(env, deps.config.defaults.gate);
|
|
8182
|
+
const projected = projectGateConfig({ gate: { ...gateForProjection(deps.config), mode } });
|
|
8183
|
+
const enabled = mode === "on";
|
|
8184
|
+
const blockedSignal = async (ctx) => {
|
|
8185
|
+
if (!taskId || !project || !deps.sendBlocked)
|
|
8186
|
+
return;
|
|
8187
|
+
const event = {
|
|
8188
|
+
type: "task.blocked",
|
|
8189
|
+
id: taskId,
|
|
8190
|
+
reason: ctx.reason,
|
|
8191
|
+
question: `crew needs permission to run ${ctx.tool}`
|
|
8192
|
+
};
|
|
8193
|
+
await deps.sendBlocked(project, event);
|
|
8194
|
+
};
|
|
8195
|
+
const host = createAutoGate({
|
|
8196
|
+
config: projected,
|
|
8197
|
+
readIntent: (p) => readUserIntent(p) ?? void 0,
|
|
8198
|
+
blockedSignal,
|
|
8199
|
+
...deps.decide ? { decide: deps.decide } : {},
|
|
8200
|
+
...project ? { project } : {},
|
|
8201
|
+
env,
|
|
8202
|
+
log: deps.log
|
|
8203
|
+
});
|
|
8204
|
+
if (!enabled) {
|
|
8205
|
+
return { config: host.config, decideClaudeHookPayload: async () => void 0 };
|
|
8206
|
+
}
|
|
8207
|
+
return host;
|
|
8208
|
+
}
|
|
8209
|
+
var init_auto_gate = __esm({
|
|
8210
|
+
"packages/core/dist/auto-gate.js"() {
|
|
8211
|
+
init_permission_gate();
|
|
8212
|
+
}
|
|
8213
|
+
});
|
|
8214
|
+
|
|
7621
8215
|
// packages/core/dist/group-dispatch.js
|
|
7622
8216
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
7623
8217
|
import { createConnection as createConnection2 } from "net";
|
|
@@ -7865,7 +8459,7 @@ var init_launch_workspace = __esm({
|
|
|
7865
8459
|
});
|
|
7866
8460
|
|
|
7867
8461
|
// packages/core/dist/side-session.js
|
|
7868
|
-
import
|
|
8462
|
+
import fs13 from "fs";
|
|
7869
8463
|
function sideTitleFor(project, name) {
|
|
7870
8464
|
return `\u{1F5D2} ${project}:${name}`;
|
|
7871
8465
|
}
|
|
@@ -7967,7 +8561,7 @@ async function runSideClose(runtime, workspaceId, project, name, projPath, workt
|
|
|
7967
8561
|
await runtime.closePane(pane);
|
|
7968
8562
|
if (projPath) {
|
|
7969
8563
|
const wtPath = worktreePath(projPath, worktreeDir, project, name);
|
|
7970
|
-
if (
|
|
8564
|
+
if (fs13.existsSync(wtPath)) {
|
|
7971
8565
|
try {
|
|
7972
8566
|
removeWorktree(projPath, wtPath);
|
|
7973
8567
|
} catch (e) {
|
|
@@ -8024,10 +8618,11 @@ async function runCrewAnswer(project, name, option, runtime, workspaceId, deps,
|
|
|
8024
8618
|
if (!crew) {
|
|
8025
8619
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
8026
8620
|
}
|
|
8027
|
-
const
|
|
8028
|
-
if (!options) {
|
|
8621
|
+
const modal = await deps.readModalOptions(crew);
|
|
8622
|
+
if (!modal || modal.options.length === 0) {
|
|
8029
8623
|
throw new Error(`Crew '${name}' has no interactive option prompt visible right now \u2014 nothing to answer. Read its screen with 'squadrant crew read ${project} ${name}' to check its state.`);
|
|
8030
8624
|
}
|
|
8625
|
+
const options = modal.options;
|
|
8031
8626
|
const target = resolveOption(options, option);
|
|
8032
8627
|
if (opts?.expect && !target.label.toLowerCase().includes(opts.expect.toLowerCase())) {
|
|
8033
8628
|
throw new Error(`Refusing: option ${target.index} is "${target.label}", which does not contain expected text "${opts.expect}". Option order is model-generated and can shift between renders \u2014 re-check with 'squadrant crew read ${project} ${name}'.
|
|
@@ -8039,7 +8634,9 @@ ${describeOptions(options)}`);
|
|
|
8039
8634
|
log(`\u2192 selecting ${target.index}. "${target.label}"`);
|
|
8040
8635
|
const current = options.find((o) => o.highlighted) ?? options[0];
|
|
8041
8636
|
const steps = target.index - current.index;
|
|
8042
|
-
const
|
|
8637
|
+
const forwardKey = modal.axis === "horizontal" ? "Right" : "Down";
|
|
8638
|
+
const backwardKey = modal.axis === "horizontal" ? "Left" : "Up";
|
|
8639
|
+
const key = steps >= 0 ? forwardKey : backwardKey;
|
|
8043
8640
|
for (let i = 0; i < Math.abs(steps); i++) {
|
|
8044
8641
|
await runtime.sendKeyToPane(crew, key);
|
|
8045
8642
|
}
|
|
@@ -8404,11 +9001,14 @@ __export(dist_exports, {
|
|
|
8404
9001
|
AGENT_BINS: () => AGENT_BINS,
|
|
8405
9002
|
ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
|
|
8406
9003
|
BOT_COMMANDS: () => BOT_COMMANDS,
|
|
9004
|
+
CAPTAIN_RECEIVED_ACK: () => CAPTAIN_RECEIVED_ACK,
|
|
8407
9005
|
CC_SOCKS_DIR: () => CC_SOCKS_DIR,
|
|
8408
9006
|
CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
|
|
9007
|
+
CONFLICT_409_SELF_HEAL_MS: () => CONFLICT_409_SELF_HEAL_MS,
|
|
8409
9008
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
8410
9009
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
8411
9010
|
CaptainDelivery: () => CaptainDelivery,
|
|
9011
|
+
DEFAULT_BLOCKED_RENUDGE_MS: () => DEFAULT_BLOCKED_RENUDGE_MS,
|
|
8412
9012
|
DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS: () => DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS,
|
|
8413
9013
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
|
|
8414
9014
|
DEFAULT_GATE_DENY_RULES: () => DEFAULT_GATE_DENY_RULES,
|
|
@@ -8425,6 +9025,7 @@ __export(dist_exports, {
|
|
|
8425
9025
|
LivenessRegistry: () => LivenessRegistry,
|
|
8426
9026
|
MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
|
|
8427
9027
|
OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
|
|
9028
|
+
PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
|
|
8428
9029
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
8429
9030
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
8430
9031
|
READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
|
|
@@ -8437,6 +9038,10 @@ __export(dist_exports, {
|
|
|
8437
9038
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
8438
9039
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
8439
9040
|
TOOL_STALL_BUDGET_MS: () => TOOL_STALL_BUDGET_MS,
|
|
9041
|
+
TYPING_KEEPALIVE_MS: () => TYPING_KEEPALIVE_MS,
|
|
9042
|
+
TelegramApiError: () => TelegramApiError,
|
|
9043
|
+
WATCHDOG_GRACE_MS: () => WATCHDOG_GRACE_MS,
|
|
9044
|
+
WATCHDOG_MS: () => WATCHDOG_MS,
|
|
8440
9045
|
WORK_ITEM_TTL_MS: () => WORK_ITEM_TTL_MS,
|
|
8441
9046
|
WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
|
|
8442
9047
|
_resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
|
|
@@ -8458,15 +9063,19 @@ __export(dist_exports, {
|
|
|
8458
9063
|
callClassifier: () => callClassifier,
|
|
8459
9064
|
capAllowed: () => capAllowed,
|
|
8460
9065
|
capOutput: () => capOutput,
|
|
9066
|
+
captainAliveStateFromHealth: () => captainAliveStateFromHealth,
|
|
8461
9067
|
captainRecordPath: () => captainRecordPath,
|
|
8462
9068
|
captainSocketPath: () => captainSocketPath,
|
|
8463
9069
|
checkFact: () => checkFact,
|
|
9070
|
+
classifyCaptainPane: () => classifyCaptainPane,
|
|
8464
9071
|
classifyHealth: () => classifyHealth,
|
|
8465
9072
|
claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
|
|
9073
|
+
clearPending: () => clearPending,
|
|
8466
9074
|
closeWorkItem: () => closeWorkItem,
|
|
8467
9075
|
computeTemplateHash: () => computeTemplateHash,
|
|
8468
9076
|
consumeExitMarker: () => consumeExitMarker,
|
|
8469
9077
|
createAttach: () => createAttach,
|
|
9078
|
+
createCaptainPaneReader: () => createCaptainPaneReader,
|
|
8470
9079
|
createCrewPaneReader: () => createCrewPaneReader,
|
|
8471
9080
|
createDaemon: () => createDaemon,
|
|
8472
9081
|
createDecoder: () => createDecoder,
|
|
@@ -8475,12 +9084,14 @@ __export(dist_exports, {
|
|
|
8475
9084
|
createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
|
|
8476
9085
|
createEnsureCaptainAlive: () => createEnsureCaptainAlive,
|
|
8477
9086
|
createEventsSource: () => createEventsSource,
|
|
9087
|
+
createInboundLifecycle: () => createInboundLifecycle,
|
|
8478
9088
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
8479
9089
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
8480
9090
|
createLaunch: () => createLaunch,
|
|
8481
9091
|
createRouterService: () => createRouterService,
|
|
8482
9092
|
createRouterShim: () => createRouterShim,
|
|
8483
9093
|
createRunCommand: () => createRunCommand,
|
|
9094
|
+
createSquadrantAutoGate: () => createSquadrantAutoGate,
|
|
8484
9095
|
createStore: () => createStore,
|
|
8485
9096
|
createSurfaceLivenessProbe: () => createSurfaceLivenessProbe,
|
|
8486
9097
|
createTelegramBridge: () => createTelegramBridge,
|
|
@@ -8530,12 +9141,16 @@ __export(dist_exports, {
|
|
|
8530
9141
|
formatInbound: () => formatInbound,
|
|
8531
9142
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
8532
9143
|
formatLifecycle: () => formatLifecycle,
|
|
9144
|
+
formatMediaReceipt: () => formatMediaReceipt,
|
|
8533
9145
|
formatPermissionDecision: () => formatPermissionDecision,
|
|
8534
9146
|
formatUsageLine: () => formatUsageLine,
|
|
9147
|
+
formatWatchdogWarning: () => formatWatchdogWarning,
|
|
8535
9148
|
freshTrace: () => freshTrace,
|
|
8536
9149
|
gateCacheKey: () => gateCacheKey,
|
|
8537
9150
|
getDaemonPid: () => getDaemonPid,
|
|
9151
|
+
hasModalOptionList: () => hasModalOptionList,
|
|
8538
9152
|
healCmdFor: () => healCmdFor,
|
|
9153
|
+
inboundBody: () => inboundBody,
|
|
8539
9154
|
isAuthorized: () => isAuthorized,
|
|
8540
9155
|
isBareSpawn: () => isBareSpawn,
|
|
8541
9156
|
isCaptainAlive: () => isCaptainAlive,
|
|
@@ -8557,18 +9172,23 @@ __export(dist_exports, {
|
|
|
8557
9172
|
kickstartArgv: () => kickstartArgv,
|
|
8558
9173
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
8559
9174
|
listSessions: () => listSessions,
|
|
9175
|
+
loadPending: () => loadPending,
|
|
8560
9176
|
loadSessions: () => loadSessions,
|
|
8561
9177
|
loadState: () => loadState,
|
|
8562
9178
|
mailboxStats: () => mailboxStats,
|
|
8563
9179
|
makeGate: () => makeGate,
|
|
9180
|
+
markPendingWarned: () => markPendingWarned,
|
|
8564
9181
|
maskToken: () => maskToken,
|
|
8565
9182
|
matchTier1Deny: () => matchTier1Deny,
|
|
9183
|
+
mediaKind: () => mediaKind,
|
|
9184
|
+
mediaMarker: () => mediaMarker,
|
|
8566
9185
|
mergeClaudeEnvRouterSettings: () => mergeClaudeEnvRouterSettings,
|
|
8567
9186
|
mintToken: () => mintToken,
|
|
8568
9187
|
nameFromTitle: () => nameFromTitle,
|
|
8569
9188
|
newestSessionInDirectory: () => newestSessionInDirectory,
|
|
8570
9189
|
nextAutoName: () => nextAutoName,
|
|
8571
9190
|
niceCrewCommand: () => niceCrewCommand,
|
|
9191
|
+
notePaneScreen: () => notePaneScreen,
|
|
8572
9192
|
notifyToggle: () => notifyToggle,
|
|
8573
9193
|
parseBearer: () => parseBearer,
|
|
8574
9194
|
parseClassifierVerdict: () => parseClassifierVerdict,
|
|
@@ -8577,6 +9197,7 @@ __export(dist_exports, {
|
|
|
8577
9197
|
parseNotifyPref: () => parseNotifyPref,
|
|
8578
9198
|
parseProgramArgs: () => parseProgramArgs,
|
|
8579
9199
|
plistPath: () => plistPath,
|
|
9200
|
+
pollBackoffMs: () => pollBackoffMs,
|
|
8580
9201
|
pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
|
|
8581
9202
|
prepareCaptainRoute: () => prepareCaptainRoute,
|
|
8582
9203
|
printForeignInstallError: () => printForeignInstallError,
|
|
@@ -8584,6 +9205,7 @@ __export(dist_exports, {
|
|
|
8584
9205
|
probeCaptainChannel: () => probeCaptainChannel,
|
|
8585
9206
|
programArgsBlock: () => programArgsBlock,
|
|
8586
9207
|
projectHealth: () => projectHealth,
|
|
9208
|
+
pruneTopics: () => pruneTopics,
|
|
8587
9209
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
8588
9210
|
readCaptainAddress: () => readCaptainAddress,
|
|
8589
9211
|
readCursor: () => readCursor,
|
|
@@ -8611,6 +9233,7 @@ __export(dist_exports, {
|
|
|
8611
9233
|
resolveCurrentProject: () => resolveCurrentProject,
|
|
8612
9234
|
resolveGate: () => resolveGate,
|
|
8613
9235
|
resolveGateDenyRules: () => resolveGateDenyRules,
|
|
9236
|
+
resolveGateEngine: () => resolveGateEngine,
|
|
8614
9237
|
resolveGateMode: () => resolveGateMode,
|
|
8615
9238
|
resolveGatePolicy: () => resolveGatePolicy,
|
|
8616
9239
|
resolveGateTools: () => resolveGateTools,
|
|
@@ -8650,9 +9273,11 @@ __export(dist_exports, {
|
|
|
8650
9273
|
saveSessions: () => saveSessions,
|
|
8651
9274
|
saveState: () => saveState,
|
|
8652
9275
|
screenHasSplashMarker: () => screenHasSplashMarker,
|
|
9276
|
+
screenHash: () => screenHash,
|
|
8653
9277
|
sendRequest: () => sendRequest,
|
|
8654
9278
|
setLastUserId: () => setLastUserId,
|
|
8655
9279
|
setNotify: () => setNotify,
|
|
9280
|
+
setPending: () => setPending,
|
|
8656
9281
|
setTopic: () => setTopic,
|
|
8657
9282
|
shellQuote: () => shellQuote,
|
|
8658
9283
|
shouldBuildRouterService: () => shouldBuildRouterService,
|
|
@@ -8665,6 +9290,7 @@ __export(dist_exports, {
|
|
|
8665
9290
|
startServer: () => startServer,
|
|
8666
9291
|
stripBotMention: () => stripBotMention,
|
|
8667
9292
|
surfaceVerdict: () => surfaceVerdict,
|
|
9293
|
+
telegramPollLockPath: () => telegramPollLockPath,
|
|
8668
9294
|
timeoutGate: () => timeoutGate,
|
|
8669
9295
|
titleFor: () => titleFor,
|
|
8670
9296
|
toControlEvent: () => toControlEvent,
|
|
@@ -8713,6 +9339,7 @@ var init_dist2 = __esm({
|
|
|
8713
9339
|
init_router_resolution();
|
|
8714
9340
|
init_captain_router();
|
|
8715
9341
|
init_permission_gate();
|
|
9342
|
+
init_auto_gate();
|
|
8716
9343
|
init_restart_daemon();
|
|
8717
9344
|
init_group_dispatch();
|
|
8718
9345
|
init_launch_workspace();
|
|
@@ -8758,7 +9385,7 @@ import { readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs"
|
|
|
8758
9385
|
import { join as join18 } from "path";
|
|
8759
9386
|
|
|
8760
9387
|
// packages/agents/dist/claude/registry.js
|
|
8761
|
-
import
|
|
9388
|
+
import fs14 from "fs";
|
|
8762
9389
|
import { join as join17 } from "path";
|
|
8763
9390
|
import { homedir as homedir7 } from "os";
|
|
8764
9391
|
var CLAUDE_SESSIONS_DIR = join17(homedir7(), ".claude", "sessions");
|
|
@@ -8803,11 +9430,11 @@ function toLifecycleSnapshot(entry, taskId, alive, now) {
|
|
|
8803
9430
|
function readClaudeStatusBySocketPath(socketPath) {
|
|
8804
9431
|
let files;
|
|
8805
9432
|
try {
|
|
8806
|
-
files =
|
|
9433
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
8807
9434
|
} catch {
|
|
8808
9435
|
return void 0;
|
|
8809
9436
|
}
|
|
8810
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
9437
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
8811
9438
|
const entry = entries.find((e) => e.messagingSocketPath === socketPath);
|
|
8812
9439
|
if (!entry)
|
|
8813
9440
|
return void 0;
|
|
@@ -8830,7 +9457,7 @@ import { homedir as homedir8 } from "os";
|
|
|
8830
9457
|
var SQUADRANT_STATE_DIR = join19(homedir8(), ".config", "squadrant", "state");
|
|
8831
9458
|
|
|
8832
9459
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
8833
|
-
import
|
|
9460
|
+
import fs15 from "fs";
|
|
8834
9461
|
import path13 from "path";
|
|
8835
9462
|
|
|
8836
9463
|
// packages/agents/dist/projection/cursor.js
|
|
@@ -8854,7 +9481,7 @@ import path17 from "path";
|
|
|
8854
9481
|
import os9 from "os";
|
|
8855
9482
|
|
|
8856
9483
|
// packages/agents/dist/projection/opencode-skills.js
|
|
8857
|
-
import
|
|
9484
|
+
import fs16 from "fs";
|
|
8858
9485
|
import os10 from "os";
|
|
8859
9486
|
import path18 from "path";
|
|
8860
9487
|
var OPENCODE_SKILL_MARKER = ".squadrant-managed";
|
|
@@ -8876,62 +9503,62 @@ function parseSkillName(raw) {
|
|
|
8876
9503
|
}
|
|
8877
9504
|
function listSourceSkills(sourceSkillsDir) {
|
|
8878
9505
|
const out = /* @__PURE__ */ new Map();
|
|
8879
|
-
if (!
|
|
9506
|
+
if (!fs16.existsSync(sourceSkillsDir))
|
|
8880
9507
|
return out;
|
|
8881
|
-
for (const entry of
|
|
9508
|
+
for (const entry of fs16.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
|
|
8882
9509
|
if (!entry.isDirectory())
|
|
8883
9510
|
continue;
|
|
8884
9511
|
const skillPath = path18.join(sourceSkillsDir, entry.name, "SKILL.md");
|
|
8885
|
-
if (!
|
|
9512
|
+
if (!fs16.existsSync(skillPath))
|
|
8886
9513
|
continue;
|
|
8887
|
-
const name = parseSkillName(
|
|
9514
|
+
const name = parseSkillName(fs16.readFileSync(skillPath, "utf-8"));
|
|
8888
9515
|
if (name && SAFE_NAME.test(name))
|
|
8889
9516
|
out.set(name, skillPath);
|
|
8890
9517
|
}
|
|
8891
9518
|
return out;
|
|
8892
9519
|
}
|
|
8893
9520
|
function hasMarker(dir) {
|
|
8894
|
-
return
|
|
9521
|
+
return fs16.existsSync(path18.join(dir, OPENCODE_SKILL_MARKER));
|
|
8895
9522
|
}
|
|
8896
9523
|
function syncOpencodeSkills(opts) {
|
|
8897
9524
|
const result = { written: [], unchanged: [], removed: [], skipped: [] };
|
|
8898
9525
|
const wanted = listSourceSkills(opts.sourceSkillsDir);
|
|
8899
|
-
if (wanted.size === 0 && !
|
|
9526
|
+
if (wanted.size === 0 && !fs16.existsSync(opts.sourceSkillsDir))
|
|
8900
9527
|
return result;
|
|
8901
9528
|
if (!opts.dryRun)
|
|
8902
|
-
|
|
9529
|
+
fs16.mkdirSync(opts.skillsRoot, { recursive: true });
|
|
8903
9530
|
for (const [name, srcPath] of wanted) {
|
|
8904
9531
|
const targetDir = path18.join(opts.skillsRoot, name);
|
|
8905
9532
|
const targetSkill = path18.join(targetDir, "SKILL.md");
|
|
8906
9533
|
const managed = hasMarker(targetDir);
|
|
8907
|
-
if (
|
|
9534
|
+
if (fs16.existsSync(targetSkill) && !managed) {
|
|
8908
9535
|
result.skipped.push({ name, reason: "foreign-collision" });
|
|
8909
9536
|
continue;
|
|
8910
9537
|
}
|
|
8911
|
-
const current =
|
|
8912
|
-
if (current && current.equals(
|
|
9538
|
+
const current = fs16.existsSync(targetSkill) ? fs16.readFileSync(targetSkill) : null;
|
|
9539
|
+
if (current && current.equals(fs16.readFileSync(srcPath))) {
|
|
8913
9540
|
if (!managed && !opts.dryRun) {
|
|
8914
|
-
|
|
9541
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
8915
9542
|
}
|
|
8916
9543
|
result.unchanged.push(name);
|
|
8917
9544
|
continue;
|
|
8918
9545
|
}
|
|
8919
9546
|
if (!opts.dryRun) {
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
9547
|
+
fs16.mkdirSync(targetDir, { recursive: true });
|
|
9548
|
+
fs16.copyFileSync(srcPath, targetSkill);
|
|
9549
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
8923
9550
|
}
|
|
8924
9551
|
result.written.push(name);
|
|
8925
9552
|
}
|
|
8926
|
-
if (
|
|
8927
|
-
for (const entry of
|
|
9553
|
+
if (fs16.existsSync(opts.skillsRoot)) {
|
|
9554
|
+
for (const entry of fs16.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
|
|
8928
9555
|
if (!entry.isDirectory() || wanted.has(entry.name))
|
|
8929
9556
|
continue;
|
|
8930
9557
|
const dir = path18.join(opts.skillsRoot, entry.name);
|
|
8931
9558
|
if (!hasMarker(dir))
|
|
8932
9559
|
continue;
|
|
8933
9560
|
if (!opts.dryRun)
|
|
8934
|
-
|
|
9561
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
8935
9562
|
result.removed.push(entry.name);
|
|
8936
9563
|
}
|
|
8937
9564
|
}
|
|
@@ -10105,6 +10732,9 @@ var ClaudePeerChannel = class {
|
|
|
10105
10732
|
return { status: "accepted", via: this.name, confirmed: true };
|
|
10106
10733
|
}
|
|
10107
10734
|
}
|
|
10735
|
+
if (statusBefore === "busy" || statusBefore === "shell") {
|
|
10736
|
+
return { status: "queued", via: this.name };
|
|
10737
|
+
}
|
|
10108
10738
|
if (statusBefore === "idle") {
|
|
10109
10739
|
await this.deps.sleep(window);
|
|
10110
10740
|
const afterInfo = this.deps.statusFor(taskId);
|
|
@@ -10243,7 +10873,7 @@ var ClaudeReceiptListener = class {
|
|
|
10243
10873
|
};
|
|
10244
10874
|
|
|
10245
10875
|
// packages/agents/dist/claude/api-key-approval.js
|
|
10246
|
-
import
|
|
10876
|
+
import fs17 from "fs";
|
|
10247
10877
|
import os12 from "os";
|
|
10248
10878
|
import path20 from "path";
|
|
10249
10879
|
|
|
@@ -10485,11 +11115,11 @@ function parseDraftFromScreen(screen) {
|
|
|
10485
11115
|
if (!screen)
|
|
10486
11116
|
return null;
|
|
10487
11117
|
const lines = screen.split(/\r?\n/);
|
|
10488
|
-
const
|
|
11118
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10489
11119
|
let bottomHR = -1;
|
|
10490
11120
|
let topHR = -1;
|
|
10491
11121
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10492
|
-
if (
|
|
11122
|
+
if (HR_RE2.test(lines[i])) {
|
|
10493
11123
|
if (bottomHR === -1) {
|
|
10494
11124
|
bottomHR = i;
|
|
10495
11125
|
} else {
|
|
@@ -10527,11 +11157,11 @@ function hasCCInputBox(screen) {
|
|
|
10527
11157
|
if (!screen)
|
|
10528
11158
|
return false;
|
|
10529
11159
|
const lines = screen.split(/\r?\n/);
|
|
10530
|
-
const
|
|
11160
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10531
11161
|
let bottomHR = -1;
|
|
10532
11162
|
let topHR = -1;
|
|
10533
11163
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10534
|
-
if (
|
|
11164
|
+
if (HR_RE2.test(lines[i])) {
|
|
10535
11165
|
if (bottomHR === -1)
|
|
10536
11166
|
bottomHR = i;
|
|
10537
11167
|
else {
|
|
@@ -10544,15 +11174,15 @@ function hasCCInputBox(screen) {
|
|
|
10544
11174
|
return false;
|
|
10545
11175
|
return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));
|
|
10546
11176
|
}
|
|
10547
|
-
function
|
|
11177
|
+
function hasModalOptionList2(screen) {
|
|
10548
11178
|
if (!screen)
|
|
10549
11179
|
return false;
|
|
10550
11180
|
const lines = screen.split(/\r?\n/);
|
|
10551
|
-
const
|
|
11181
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10552
11182
|
let bottomHR = -1;
|
|
10553
11183
|
let topHR = -1;
|
|
10554
11184
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10555
|
-
if (
|
|
11185
|
+
if (HR_RE2.test(lines[i])) {
|
|
10556
11186
|
if (bottomHR === -1)
|
|
10557
11187
|
bottomHR = i;
|
|
10558
11188
|
else {
|
|
@@ -10569,11 +11199,11 @@ function readInputBoxRaw(screen, opts) {
|
|
|
10569
11199
|
if (!screen)
|
|
10570
11200
|
return null;
|
|
10571
11201
|
const lines = screen.split(/\r?\n/);
|
|
10572
|
-
const
|
|
11202
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10573
11203
|
let bottomHR = -1;
|
|
10574
11204
|
let topHR = -1;
|
|
10575
11205
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10576
|
-
if (
|
|
11206
|
+
if (HR_RE2.test(lines[i])) {
|
|
10577
11207
|
if (bottomHR === -1)
|
|
10578
11208
|
bottomHR = i;
|
|
10579
11209
|
else {
|
|
@@ -10603,6 +11233,34 @@ function classifyStartupSurface(screen) {
|
|
|
10603
11233
|
return "idle";
|
|
10604
11234
|
return "loading";
|
|
10605
11235
|
}
|
|
11236
|
+
var OC_BOX_BOTTOM_RE = /^\s*╹[▀]{6,}\s*$/;
|
|
11237
|
+
var OC_BOX_SIDE_RE = /^\s*┃/;
|
|
11238
|
+
var OC_BOX_STATUS_RE = /^Build\s*·/i;
|
|
11239
|
+
var OC_BUSY_RE = /esc\s+interrupt/i;
|
|
11240
|
+
function parseOpencodeDraftFromScreen(screen) {
|
|
11241
|
+
if (!screen)
|
|
11242
|
+
return null;
|
|
11243
|
+
if (OC_BUSY_RE.test(screen))
|
|
11244
|
+
return null;
|
|
11245
|
+
const lines = screen.split(/\r?\n/);
|
|
11246
|
+
let bottom = -1;
|
|
11247
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
11248
|
+
if (OC_BOX_BOTTOM_RE.test(lines[i])) {
|
|
11249
|
+
bottom = i;
|
|
11250
|
+
break;
|
|
11251
|
+
}
|
|
11252
|
+
}
|
|
11253
|
+
if (bottom === -1)
|
|
11254
|
+
return null;
|
|
11255
|
+
const content = [];
|
|
11256
|
+
for (let i = bottom - 1; i >= 0 && OC_BOX_SIDE_RE.test(lines[i]); i--) {
|
|
11257
|
+
content.push(lines[i].replace(/^\s*┃\s*/, "").replace(/[\s┃│]+$/, ""));
|
|
11258
|
+
}
|
|
11259
|
+
if (content.length === 0)
|
|
11260
|
+
return null;
|
|
11261
|
+
const hasDraft = content.some((l) => l !== "" && !OC_BOX_STATUS_RE.test(l) && !screenHasSplashMarker(l, "Ask anything"));
|
|
11262
|
+
return hasDraft ? null : "";
|
|
11263
|
+
}
|
|
10606
11264
|
function sendDebugEnabled() {
|
|
10607
11265
|
return !!process.env.SQUADRANT_DEBUG_SEND;
|
|
10608
11266
|
}
|
|
@@ -10818,10 +11476,10 @@ function createCmuxDriver() {
|
|
|
10818
11476
|
`);
|
|
10819
11477
|
throw new DeferDelivery(null, "probe-failed");
|
|
10820
11478
|
}
|
|
10821
|
-
const draft = parseDraftFromScreen(screen);
|
|
11479
|
+
const draft = opts?.agent === "opencode" ? parseOpencodeDraftFromScreen(screen) : parseDraftFromScreen(screen);
|
|
10822
11480
|
if (draft === null)
|
|
10823
11481
|
throw new DeferDelivery(null, "no-box");
|
|
10824
|
-
if (
|
|
11482
|
+
if (hasModalOptionList2(screen))
|
|
10825
11483
|
throw new DeferDelivery(null, "modal");
|
|
10826
11484
|
if (draft === "") {
|
|
10827
11485
|
await deliver();
|
|
@@ -11015,7 +11673,7 @@ var NotifierRegistry = class {
|
|
|
11015
11673
|
};
|
|
11016
11674
|
|
|
11017
11675
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
11018
|
-
import
|
|
11676
|
+
import fs18 from "fs/promises";
|
|
11019
11677
|
import { existsSync as existsSync11 } from "fs";
|
|
11020
11678
|
import path21 from "path";
|
|
11021
11679
|
|
|
@@ -11590,6 +12248,48 @@ function removeCommandHandlers(entries, command) {
|
|
|
11590
12248
|
}
|
|
11591
12249
|
return removed;
|
|
11592
12250
|
}
|
|
12251
|
+
var FOREIGN_AUTO_GATE_MARKER = "auto-gate-managed";
|
|
12252
|
+
function isForeignAutoGateCommand(command) {
|
|
12253
|
+
if (typeof command !== "string")
|
|
12254
|
+
return false;
|
|
12255
|
+
if (command.includes(FOREIGN_AUTO_GATE_MARKER))
|
|
12256
|
+
return true;
|
|
12257
|
+
const first = command.trim().split(/\s+/)[0] ?? "";
|
|
12258
|
+
return /(^|[/\\])auto-gate$/.test(first);
|
|
12259
|
+
}
|
|
12260
|
+
function findForeignGateHandlers(entries) {
|
|
12261
|
+
const found = [];
|
|
12262
|
+
for (const entry of entries) {
|
|
12263
|
+
const hooks = entry?.hooks;
|
|
12264
|
+
if (!Array.isArray(hooks))
|
|
12265
|
+
continue;
|
|
12266
|
+
for (const h of hooks) {
|
|
12267
|
+
const cmd = h?.command;
|
|
12268
|
+
if (isForeignAutoGateCommand(cmd))
|
|
12269
|
+
found.push(cmd);
|
|
12270
|
+
}
|
|
12271
|
+
}
|
|
12272
|
+
return found;
|
|
12273
|
+
}
|
|
12274
|
+
function removeForeignGateHandlers(entries) {
|
|
12275
|
+
const removed = [];
|
|
12276
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
12277
|
+
const entry = entries[i];
|
|
12278
|
+
if (!Array.isArray(entry?.hooks))
|
|
12279
|
+
continue;
|
|
12280
|
+
entry.hooks = entry.hooks.filter((h) => {
|
|
12281
|
+
const cmd = h?.command;
|
|
12282
|
+
if (isForeignAutoGateCommand(cmd)) {
|
|
12283
|
+
removed.push(cmd);
|
|
12284
|
+
return false;
|
|
12285
|
+
}
|
|
12286
|
+
return true;
|
|
12287
|
+
});
|
|
12288
|
+
if (entry.hooks.length === 0)
|
|
12289
|
+
entries.splice(i, 1);
|
|
12290
|
+
}
|
|
12291
|
+
return removed;
|
|
12292
|
+
}
|
|
11593
12293
|
function installClaudeHooks(opts = {}) {
|
|
11594
12294
|
const settingsPath = opts.settingsPath ?? join25(homedir13(), ".claude", "settings.json");
|
|
11595
12295
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
@@ -11627,6 +12327,23 @@ function installClaudeHooks(opts = {}) {
|
|
|
11627
12327
|
changed = true;
|
|
11628
12328
|
migrated.push(`${eventName}/${sub}`);
|
|
11629
12329
|
}
|
|
12330
|
+
const foreign = findForeignGateHandlers(entries);
|
|
12331
|
+
if (foreign.length > 0) {
|
|
12332
|
+
if (opts.gateMode === "on") {
|
|
12333
|
+
const removed = removeForeignGateHandlers(entries);
|
|
12334
|
+
if (removed.length > 0)
|
|
12335
|
+
changed = true;
|
|
12336
|
+
for (const command2 of removed) {
|
|
12337
|
+
log(`native-hook: removed foreign auto-gate PermissionRequest handler in ${settingsPath} [#P6 \xA715#5]: ${command2}`);
|
|
12338
|
+
opts.recordGateAction?.({ action: "removed-foreign-gate", command: command2 });
|
|
12339
|
+
}
|
|
12340
|
+
} else {
|
|
12341
|
+
for (const command2 of foreign) {
|
|
12342
|
+
log(`native-hook: foreign auto-gate PermissionRequest handler present in ${settingsPath} but squadrant's gate is '${opts.gateMode ?? "auto"}' (not 'on') \u2014 foreign entry left untouched [#P6 \xA715#5]: ${command2}`);
|
|
12343
|
+
opts.recordGateAction?.({ action: "left-foreign-gate", command: command2 });
|
|
12344
|
+
}
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
11630
12347
|
}
|
|
11631
12348
|
const alreadyPresent = entries.some((m) => Array.isArray(m.hooks) && m.hooks.some((h) => typeof h.command === "string" && h.command === command));
|
|
11632
12349
|
if (!alreadyPresent) {
|
|
@@ -11657,11 +12374,57 @@ function installClaudeHooks(opts = {}) {
|
|
|
11657
12374
|
changed = true;
|
|
11658
12375
|
}
|
|
11659
12376
|
}
|
|
12377
|
+
if (typeof settings.env === "object" && settings.env !== null && !Array.isArray(settings.env)) {
|
|
12378
|
+
if (reconcileSubagentModels(settings.env, log))
|
|
12379
|
+
changed = true;
|
|
12380
|
+
}
|
|
11660
12381
|
if (changed) {
|
|
11661
12382
|
writeFile6(settingsPath, JSON.stringify(settings, null, 2));
|
|
11662
12383
|
}
|
|
11663
12384
|
return settingsPath;
|
|
11664
12385
|
}
|
|
12386
|
+
var SUBAGENT_MODEL_KEY = "CLAUDE_CODE_SUBAGENT_MODEL";
|
|
12387
|
+
var SMALL_FAST_MODEL_KEY = "ANTHROPIC_SMALL_FAST_MODEL";
|
|
12388
|
+
function isCustomUpstream(baseUrl) {
|
|
12389
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0)
|
|
12390
|
+
return false;
|
|
12391
|
+
try {
|
|
12392
|
+
return new URL(baseUrl).hostname.toLowerCase() !== "api.anthropic.com";
|
|
12393
|
+
} catch {
|
|
12394
|
+
return false;
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
function isAnthropicModelId(value) {
|
|
12398
|
+
if (typeof value !== "string")
|
|
12399
|
+
return false;
|
|
12400
|
+
const v2 = value.trim().toLowerCase();
|
|
12401
|
+
return v2 === "sonnet" || v2 === "opus" || v2 === "haiku" || v2.startsWith("claude-");
|
|
12402
|
+
}
|
|
12403
|
+
function reconcileSubagentModels(env, log) {
|
|
12404
|
+
if (!isCustomUpstream(env.ANTHROPIC_BASE_URL))
|
|
12405
|
+
return false;
|
|
12406
|
+
const model = env.ANTHROPIC_MODEL;
|
|
12407
|
+
if (typeof model !== "string" || model.length === 0)
|
|
12408
|
+
return false;
|
|
12409
|
+
let changed = false;
|
|
12410
|
+
for (const key of [SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY]) {
|
|
12411
|
+
const current = env[key];
|
|
12412
|
+
if (current === void 0) {
|
|
12413
|
+
env[key] = model;
|
|
12414
|
+
changed = true;
|
|
12415
|
+
log(`native-hook: set ${key}='${model}' to match ANTHROPIC_MODEL on a custom upstream`);
|
|
12416
|
+
} else if (isAnthropicModelId(current)) {
|
|
12417
|
+
if (current !== model) {
|
|
12418
|
+
env[key] = model;
|
|
12419
|
+
changed = true;
|
|
12420
|
+
log(`native-hook: aligned ${key} '${String(current)}' \u2192 '${model}' for a custom upstream`);
|
|
12421
|
+
}
|
|
12422
|
+
} else {
|
|
12423
|
+
log(`native-hook: ${key} '${String(current)}' is a custom model id \u2014 left untouched`);
|
|
12424
|
+
}
|
|
12425
|
+
}
|
|
12426
|
+
return changed;
|
|
12427
|
+
}
|
|
11665
12428
|
function mapSubToLifecycle(sub) {
|
|
11666
12429
|
switch (sub) {
|
|
11667
12430
|
case "session-start":
|
|
@@ -11811,7 +12574,7 @@ async function settleInputBox(runtime, pane) {
|
|
|
11811
12574
|
}
|
|
11812
12575
|
async function confirmedSendToPane(runtime, pane, message) {
|
|
11813
12576
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
11814
|
-
if (
|
|
12577
|
+
if (hasModalOptionList2(preSendScreen)) {
|
|
11815
12578
|
return { delivered: false, blockedByModal: true };
|
|
11816
12579
|
}
|
|
11817
12580
|
await runtime.pasteToPane(pane, message);
|
|
@@ -11914,7 +12677,7 @@ async function maybeBroadcastDaemonRestart(opts) {
|
|
|
11914
12677
|
|
|
11915
12678
|
// packages/cli/src/lib/captain-channel-factory.ts
|
|
11916
12679
|
import { createServer as createServer4, connect as netConnect } from "net";
|
|
11917
|
-
import
|
|
12680
|
+
import fs19 from "fs";
|
|
11918
12681
|
import { join as join26 } from "path";
|
|
11919
12682
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
11920
12683
|
import chalk2 from "chalk";
|
|
@@ -11923,15 +12686,15 @@ var shared;
|
|
|
11923
12686
|
var registryEntryPath = () => join26(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
11924
12687
|
function unregisterSenderIdentity() {
|
|
11925
12688
|
try {
|
|
11926
|
-
|
|
12689
|
+
fs19.unlinkSync(registryEntryPath());
|
|
11927
12690
|
} catch {
|
|
11928
12691
|
}
|
|
11929
12692
|
}
|
|
11930
12693
|
function registerSenderIdentity(socketPath) {
|
|
11931
12694
|
try {
|
|
11932
12695
|
unregisterSenderIdentity();
|
|
11933
|
-
|
|
11934
|
-
|
|
12696
|
+
fs19.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
12697
|
+
fs19.writeFileSync(
|
|
11935
12698
|
registryEntryPath(),
|
|
11936
12699
|
JSON.stringify({
|
|
11937
12700
|
pid: process.pid,
|
|
@@ -11957,7 +12720,7 @@ async function sharedReceiptListener() {
|
|
|
11957
12720
|
// must never be the reason we refuse to start.
|
|
11958
12721
|
unlinkStale: (p) => {
|
|
11959
12722
|
try {
|
|
11960
|
-
|
|
12723
|
+
fs19.unlinkSync(p);
|
|
11961
12724
|
} catch {
|
|
11962
12725
|
}
|
|
11963
12726
|
},
|
|
@@ -12054,6 +12817,33 @@ function buildTelegramBridge(cfg, stateRoot, log, deliverInbound, usageFor) {
|
|
|
12054
12817
|
});
|
|
12055
12818
|
const runCommand = createRunCommand(CLI_BIN);
|
|
12056
12819
|
const sendReply = (threadId, text, replyMarkup) => client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);
|
|
12820
|
+
const lifecycle = createInboundLifecycle({
|
|
12821
|
+
stateRoot,
|
|
12822
|
+
cfg: { supergroupId: cfg.supergroupId },
|
|
12823
|
+
sendChatAction: (chatId, threadId, action) => client.sendChatAction(chatId, threadId, action),
|
|
12824
|
+
sendReply: async (threadId, text) => {
|
|
12825
|
+
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
12826
|
+
},
|
|
12827
|
+
readPane: createCaptainPaneReader(
|
|
12828
|
+
new DaemonCmux(createCmuxDriver()),
|
|
12829
|
+
(project) => {
|
|
12830
|
+
try {
|
|
12831
|
+
return loadConfig().projects[project]?.captainName ?? `${project}-captain`;
|
|
12832
|
+
} catch {
|
|
12833
|
+
return `${project}-captain`;
|
|
12834
|
+
}
|
|
12835
|
+
},
|
|
12836
|
+
{
|
|
12837
|
+
log,
|
|
12838
|
+
// #839 staleness, measured for real: cmux has no pane activity
|
|
12839
|
+
// timestamp, so the reader hashes the screen and asks the persisted
|
|
12840
|
+
// pending store how long that exact screen has been up. Persisted, so a
|
|
12841
|
+
// daemon restart cannot hand a dead captain a fresh 15 minutes.
|
|
12842
|
+
noteScreen: (project, hash) => notePaneScreen(stateRoot, project, hash, Date.now())
|
|
12843
|
+
}
|
|
12844
|
+
),
|
|
12845
|
+
log
|
|
12846
|
+
});
|
|
12057
12847
|
return createTelegramBridge({
|
|
12058
12848
|
cfg,
|
|
12059
12849
|
stateRoot,
|
|
@@ -12065,7 +12855,20 @@ function buildTelegramBridge(cfg, stateRoot, log, deliverInbound, usageFor) {
|
|
|
12065
12855
|
runCommand,
|
|
12066
12856
|
sendReply,
|
|
12067
12857
|
deliverInbound,
|
|
12068
|
-
usageFor
|
|
12858
|
+
usageFor,
|
|
12859
|
+
lifecycle,
|
|
12860
|
+
// #321 registry prune. Read the file directly rather than via loadConfig():
|
|
12861
|
+
// loadConfig swallows every failure and returns defaults, so an unreadable
|
|
12862
|
+
// config would look like "no projects registered" and the prune would delete
|
|
12863
|
+
// every link on the machine. `undefined` correctly means "don't prune".
|
|
12864
|
+
registeredProjects: () => {
|
|
12865
|
+
try {
|
|
12866
|
+
const parsed = JSON.parse(readFileSync19(join27(dirname6(stateRoot), "config.json"), "utf8"));
|
|
12867
|
+
return Object.keys(parsed.projects ?? {});
|
|
12868
|
+
} catch {
|
|
12869
|
+
return void 0;
|
|
12870
|
+
}
|
|
12871
|
+
}
|
|
12069
12872
|
});
|
|
12070
12873
|
}
|
|
12071
12874
|
function buildNotifyFault(log) {
|
|
@@ -12187,7 +12990,15 @@ function startSquadrantd(opts = {}) {
|
|
|
12187
12990
|
log
|
|
12188
12991
|
});
|
|
12189
12992
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
12190
|
-
const nativeHookSource = new NativeHookSource({
|
|
12993
|
+
const nativeHookSource = new NativeHookSource({
|
|
12994
|
+
log,
|
|
12995
|
+
hookInstall: {
|
|
12996
|
+
claudeEnv: loadConfig().defaults.claudeEnv,
|
|
12997
|
+
// #P6 (§15#5): only an explicit gate mode "on" lets squadrant remove a
|
|
12998
|
+
// foreign auto-gate PermissionRequest handler; auto/absent leaves it alone.
|
|
12999
|
+
gateMode: loadConfig().defaults.gate?.mode
|
|
13000
|
+
}
|
|
13001
|
+
});
|
|
12191
13002
|
ctx.codexDriver = codexDriver;
|
|
12192
13003
|
ctx.opencodeBridge = opencodeBridge;
|
|
12193
13004
|
ctx.cmuxEventsBridge = cmuxEventsBridge;
|