squadrant 0.9.2 → 0.11.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/README.md +64 -0
- package/dist/index.js +1169 -289
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +969 -83
- package/dist/squadrantd.js.map +1 -1
- package/package.json +4 -3
- package/plugin/skills/captain-ops/SKILL.md +69 -0
- package/plugin/skills/telegram/SKILL.md +94 -0
package/dist/squadrantd.js
CHANGED
|
@@ -152,6 +152,48 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
152
152
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// packages/shared/dist/project-config.js
|
|
156
|
+
import fs2 from "fs";
|
|
157
|
+
import os2 from "os";
|
|
158
|
+
import path2 from "path";
|
|
159
|
+
function defaultRoot() {
|
|
160
|
+
return path2.join(os2.homedir(), ".config", "squadrant");
|
|
161
|
+
}
|
|
162
|
+
function projectConfigPath(name, root = defaultRoot()) {
|
|
163
|
+
return path2.join(root, "projects", `${name}.json`);
|
|
164
|
+
}
|
|
165
|
+
function loadProjectOverride(name, root = defaultRoot()) {
|
|
166
|
+
try {
|
|
167
|
+
return JSON.parse(fs2.readFileSync(projectConfigPath(name, root), "utf-8"));
|
|
168
|
+
} catch {
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function deepMerge(base, patch) {
|
|
173
|
+
if (patch === null || typeof patch !== "object" || Array.isArray(patch))
|
|
174
|
+
return patch ?? base;
|
|
175
|
+
const out = { ...base };
|
|
176
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
177
|
+
out[k] = deepMerge(out[k], v);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function saveProjectOverride(name, patch, root = defaultRoot()) {
|
|
182
|
+
const merged = deepMerge(loadProjectOverride(name, root), patch);
|
|
183
|
+
const file = projectConfigPath(name, root);
|
|
184
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
185
|
+
fs2.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
|
|
186
|
+
}
|
|
187
|
+
var DEFAULT_NOTIFY = { active: false, cap: true, crew: "alert_only" };
|
|
188
|
+
function resolveNotify(globalNotify, override) {
|
|
189
|
+
let n = { ...DEFAULT_NOTIFY };
|
|
190
|
+
if (globalNotify)
|
|
191
|
+
n = deepMerge(n, globalNotify);
|
|
192
|
+
if (override.telegram?.notify)
|
|
193
|
+
n = deepMerge(n, override.telegram.notify);
|
|
194
|
+
return n;
|
|
195
|
+
}
|
|
196
|
+
|
|
155
197
|
// packages/shared/dist/types/control.js
|
|
156
198
|
var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
157
199
|
"done",
|
|
@@ -185,22 +227,22 @@ var MINIMAL_TEMPLATE = [
|
|
|
185
227
|
``
|
|
186
228
|
].join("\n");
|
|
187
229
|
function ensureSocketAutomation(opts = {}) {
|
|
188
|
-
const
|
|
189
|
-
if (!existsSync(
|
|
190
|
-
mkdirSync(dirname(
|
|
191
|
-
writeFileSync(
|
|
192
|
-
return { path:
|
|
230
|
+
const path16 = opts.path ?? defaultCmuxConfigPath();
|
|
231
|
+
if (!existsSync(path16)) {
|
|
232
|
+
mkdirSync(dirname(path16), { recursive: true });
|
|
233
|
+
writeFileSync(path16, MINIMAL_TEMPLATE);
|
|
234
|
+
return { path: path16, changed: true, alreadySet: false };
|
|
193
235
|
}
|
|
194
|
-
const text = readFileSync(
|
|
236
|
+
const text = readFileSync(path16, "utf-8");
|
|
195
237
|
const current = parse(text)?.automation?.socketControlMode;
|
|
196
238
|
if (current === AUTOMATION_MODE) {
|
|
197
|
-
return { path:
|
|
239
|
+
return { path: path16, changed: false, alreadySet: true };
|
|
198
240
|
}
|
|
199
241
|
const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
|
|
200
242
|
formattingOptions: { insertSpaces: true, tabSize: 2 }
|
|
201
243
|
});
|
|
202
|
-
writeFileSync(
|
|
203
|
-
return { path:
|
|
244
|
+
writeFileSync(path16, applyEdits(text, edits));
|
|
245
|
+
return { path: path16, changed: true, alreadySet: false };
|
|
204
246
|
}
|
|
205
247
|
|
|
206
248
|
// packages/shared/dist/lib/cmux-probe.js
|
|
@@ -322,15 +364,15 @@ function sleep(ms) {
|
|
|
322
364
|
function defaultStatePath() {
|
|
323
365
|
return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
|
|
324
366
|
}
|
|
325
|
-
function readState(
|
|
367
|
+
function readState(path16) {
|
|
326
368
|
try {
|
|
327
|
-
return JSON.parse(readFileSync4(
|
|
369
|
+
return JSON.parse(readFileSync4(path16, "utf-8"));
|
|
328
370
|
} catch {
|
|
329
371
|
return {};
|
|
330
372
|
}
|
|
331
373
|
}
|
|
332
374
|
async function ensureCmuxAutoConfig(opts = {}) {
|
|
333
|
-
const
|
|
375
|
+
const statePath2 = opts.statePath ?? defaultStatePath();
|
|
334
376
|
const ensureConfig = opts.ensureConfig ?? ensureSocketAutomation;
|
|
335
377
|
const probe = opts.probe ?? probeCmuxDaemonDirect;
|
|
336
378
|
const cfg = ensureConfig({ path: opts.configPath });
|
|
@@ -338,15 +380,15 @@ async function ensureCmuxAutoConfig(opts = {}) {
|
|
|
338
380
|
const needsRestart = verdict === "denied";
|
|
339
381
|
let promptedThisRun = false;
|
|
340
382
|
if (needsRestart) {
|
|
341
|
-
const already = readState(
|
|
383
|
+
const already = readState(statePath2).promptedRestart === true;
|
|
342
384
|
if (!already) {
|
|
343
|
-
mkdirSync2(dirname2(
|
|
344
|
-
writeFileSync3(
|
|
385
|
+
mkdirSync2(dirname2(statePath2), { recursive: true });
|
|
386
|
+
writeFileSync3(statePath2, JSON.stringify({ promptedRestart: true }));
|
|
345
387
|
promptedThisRun = true;
|
|
346
388
|
}
|
|
347
389
|
} else if (verdict === "reachable") {
|
|
348
|
-
if (existsSync4(
|
|
349
|
-
rmSync2(
|
|
390
|
+
if (existsSync4(statePath2))
|
|
391
|
+
rmSync2(statePath2, { force: true });
|
|
350
392
|
}
|
|
351
393
|
return {
|
|
352
394
|
configPath: cfg.path,
|
|
@@ -373,14 +415,14 @@ var compatManifest = {
|
|
|
373
415
|
|
|
374
416
|
// packages/shared/dist/lib/git-worktree.js
|
|
375
417
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
376
|
-
import
|
|
418
|
+
import path3 from "path";
|
|
377
419
|
|
|
378
420
|
// packages/shared/dist/lib/resolve-text-input.js
|
|
379
|
-
import
|
|
421
|
+
import fs3 from "fs";
|
|
380
422
|
|
|
381
423
|
// packages/shared/dist/lib/runtime-sync.js
|
|
382
|
-
import
|
|
383
|
-
import
|
|
424
|
+
import fs4 from "fs";
|
|
425
|
+
import path4 from "path";
|
|
384
426
|
|
|
385
427
|
// packages/shared/dist/lib/tool-compat.js
|
|
386
428
|
function parseSemVer(v) {
|
|
@@ -414,13 +456,13 @@ function checkToolCompat(name, rawVersion, entry) {
|
|
|
414
456
|
}
|
|
415
457
|
|
|
416
458
|
// packages/shared/dist/lib/canonical-source.js
|
|
417
|
-
import
|
|
418
|
-
import
|
|
459
|
+
import fs5 from "fs";
|
|
460
|
+
import path5 from "path";
|
|
419
461
|
|
|
420
462
|
// packages/shared/dist/lib/daily-logs.js
|
|
421
463
|
import { execSync } from "child_process";
|
|
422
|
-
import
|
|
423
|
-
import
|
|
464
|
+
import fs6 from "fs";
|
|
465
|
+
import path6 from "path";
|
|
424
466
|
import matter from "gray-matter";
|
|
425
467
|
|
|
426
468
|
// packages/core/dist/state-machine.js
|
|
@@ -871,7 +913,7 @@ function createDaemon(deps) {
|
|
|
871
913
|
}
|
|
872
914
|
|
|
873
915
|
// packages/core/dist/mailbox.js
|
|
874
|
-
import { promises as
|
|
916
|
+
import { promises as fs7 } from "fs";
|
|
875
917
|
import { join as join5 } from "path";
|
|
876
918
|
import { randomUUID } from "crypto";
|
|
877
919
|
function inboxDir(stateRoot) {
|
|
@@ -888,7 +930,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
|
|
|
888
930
|
const dir = inboxDir(stateRoot);
|
|
889
931
|
let entries;
|
|
890
932
|
try {
|
|
891
|
-
entries = await
|
|
933
|
+
entries = await fs7.readdir(dir);
|
|
892
934
|
} catch {
|
|
893
935
|
return [];
|
|
894
936
|
}
|
|
@@ -897,7 +939,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
|
|
|
897
939
|
}
|
|
898
940
|
async function readMaxSeqFromFile(file) {
|
|
899
941
|
try {
|
|
900
|
-
const buf = await
|
|
942
|
+
const buf = await fs7.readFile(file, "utf-8");
|
|
901
943
|
if (!buf.trim())
|
|
902
944
|
return 0;
|
|
903
945
|
const lines = buf.trim().split("\n");
|
|
@@ -936,34 +978,46 @@ function withProjectLock(project, fn) {
|
|
|
936
978
|
projectLocks.set(project, next.catch(() => void 0));
|
|
937
979
|
return next;
|
|
938
980
|
}
|
|
939
|
-
|
|
940
|
-
return withProjectLock(
|
|
941
|
-
const dir = inboxDir(
|
|
942
|
-
await
|
|
943
|
-
const file = logPath(
|
|
944
|
-
const lastSeq = await readMaxSeq(
|
|
981
|
+
function appendEntry(stateRoot, project, build) {
|
|
982
|
+
return withProjectLock(project, async () => {
|
|
983
|
+
const dir = inboxDir(stateRoot);
|
|
984
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
985
|
+
const file = logPath(stateRoot, project);
|
|
986
|
+
const lastSeq = await readMaxSeq(stateRoot, project);
|
|
945
987
|
const seq = lastSeq + 1;
|
|
946
|
-
const entry =
|
|
947
|
-
|
|
948
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
949
|
-
taskId: opts.taskRecord.id,
|
|
950
|
-
...opts.taskRecord.name !== void 0 ? { name: opts.taskRecord.name } : {},
|
|
951
|
-
kind: opts.event.type,
|
|
952
|
-
provider: opts.taskRecord.provider,
|
|
953
|
-
payload: extractPayload(opts.event),
|
|
954
|
-
message: opts.message ?? null
|
|
955
|
-
};
|
|
956
|
-
await fs6.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
|
|
988
|
+
const entry = build(seq);
|
|
989
|
+
await fs7.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
|
|
957
990
|
return seq;
|
|
958
991
|
});
|
|
959
992
|
}
|
|
993
|
+
async function appendToMailbox(opts) {
|
|
994
|
+
return appendEntry(opts.stateRoot, opts.project, (seq) => ({
|
|
995
|
+
seq,
|
|
996
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
997
|
+
taskId: opts.taskRecord.id,
|
|
998
|
+
...opts.taskRecord.name !== void 0 ? { name: opts.taskRecord.name } : {},
|
|
999
|
+
kind: opts.event.type,
|
|
1000
|
+
provider: opts.taskRecord.provider,
|
|
1001
|
+
payload: extractPayload(opts.event),
|
|
1002
|
+
message: opts.message ?? null
|
|
1003
|
+
}));
|
|
1004
|
+
}
|
|
1005
|
+
async function appendCaptainMessage(opts) {
|
|
1006
|
+
await appendEntry(opts.stateRoot, opts.project, (seq) => ({
|
|
1007
|
+
seq,
|
|
1008
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1009
|
+
kind: "captain.message",
|
|
1010
|
+
payload: { source: opts.source },
|
|
1011
|
+
message: opts.text
|
|
1012
|
+
}));
|
|
1013
|
+
}
|
|
960
1014
|
function cursorPath(stateRoot, project, subscriber) {
|
|
961
1015
|
return join5(inboxDir(stateRoot), `${project}.${subscriber}.cursor`);
|
|
962
1016
|
}
|
|
963
1017
|
async function readCursor(opts) {
|
|
964
1018
|
let buf;
|
|
965
1019
|
try {
|
|
966
|
-
buf = await
|
|
1020
|
+
buf = await fs7.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
|
|
967
1021
|
} catch (e) {
|
|
968
1022
|
if (e.code === "ENOENT")
|
|
969
1023
|
return null;
|
|
@@ -978,7 +1032,7 @@ async function readCursor(opts) {
|
|
|
978
1032
|
}
|
|
979
1033
|
}
|
|
980
1034
|
async function writeCursor(opts) {
|
|
981
|
-
await
|
|
1035
|
+
await fs7.mkdir(inboxDir(opts.stateRoot), { recursive: true });
|
|
982
1036
|
const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);
|
|
983
1037
|
const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;
|
|
984
1038
|
const data = {
|
|
@@ -986,7 +1040,7 @@ async function writeCursor(opts) {
|
|
|
986
1040
|
subscriber: opts.subscriber,
|
|
987
1041
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
988
1042
|
};
|
|
989
|
-
const handle = await
|
|
1043
|
+
const handle = await fs7.open(tmp, "w");
|
|
990
1044
|
try {
|
|
991
1045
|
await handle.writeFile(JSON.stringify(data), { encoding: "utf-8" });
|
|
992
1046
|
await handle.sync();
|
|
@@ -994,9 +1048,9 @@ async function writeCursor(opts) {
|
|
|
994
1048
|
await handle.close();
|
|
995
1049
|
}
|
|
996
1050
|
try {
|
|
997
|
-
await
|
|
1051
|
+
await fs7.rename(tmp, dest);
|
|
998
1052
|
} catch (e) {
|
|
999
|
-
await
|
|
1053
|
+
await fs7.unlink(tmp).catch(() => {
|
|
1000
1054
|
});
|
|
1001
1055
|
throw e;
|
|
1002
1056
|
}
|
|
@@ -1007,7 +1061,7 @@ async function* readFromCursor(opts) {
|
|
|
1007
1061
|
for (const file of files) {
|
|
1008
1062
|
let buf;
|
|
1009
1063
|
try {
|
|
1010
|
-
buf = await
|
|
1064
|
+
buf = await fs7.readFile(file, "utf-8");
|
|
1011
1065
|
} catch (e) {
|
|
1012
1066
|
if (e.code === "ENOENT")
|
|
1013
1067
|
continue;
|
|
@@ -1031,7 +1085,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
1031
1085
|
const file = logPath(stateRoot, project);
|
|
1032
1086
|
let sizeBytes = 0;
|
|
1033
1087
|
try {
|
|
1034
|
-
sizeBytes = (await
|
|
1088
|
+
sizeBytes = (await fs7.stat(file)).size;
|
|
1035
1089
|
} catch (e) {
|
|
1036
1090
|
if (e.code !== "ENOENT")
|
|
1037
1091
|
throw e;
|
|
@@ -1046,7 +1100,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
1046
1100
|
}
|
|
1047
1101
|
async function oldestEntryAgeMs(file) {
|
|
1048
1102
|
try {
|
|
1049
|
-
const buf = await
|
|
1103
|
+
const buf = await fs7.readFile(file, "utf-8");
|
|
1050
1104
|
const firstLine = buf.split("\n").find((l) => l.trim());
|
|
1051
1105
|
if (!firstLine)
|
|
1052
1106
|
return 0;
|
|
@@ -1061,7 +1115,7 @@ async function rotateIfNeeded(opts) {
|
|
|
1061
1115
|
const file = logPath(opts.stateRoot, opts.project);
|
|
1062
1116
|
let size = 0;
|
|
1063
1117
|
try {
|
|
1064
|
-
size = (await
|
|
1118
|
+
size = (await fs7.stat(file)).size;
|
|
1065
1119
|
} catch (e) {
|
|
1066
1120
|
if (e.code === "ENOENT")
|
|
1067
1121
|
return { rotated: false };
|
|
@@ -1077,22 +1131,22 @@ async function rotateIfNeeded(opts) {
|
|
|
1077
1131
|
const dst = `${file}.${n + 1}`;
|
|
1078
1132
|
if (n + 1 > opts.keepCount) {
|
|
1079
1133
|
try {
|
|
1080
|
-
await
|
|
1134
|
+
await fs7.unlink(src);
|
|
1081
1135
|
} catch (e) {
|
|
1082
1136
|
if (e.code !== "ENOENT")
|
|
1083
1137
|
throw e;
|
|
1084
1138
|
}
|
|
1085
1139
|
} else {
|
|
1086
1140
|
try {
|
|
1087
|
-
await
|
|
1141
|
+
await fs7.rename(src, dst);
|
|
1088
1142
|
} catch (e) {
|
|
1089
1143
|
if (e.code !== "ENOENT")
|
|
1090
1144
|
throw e;
|
|
1091
1145
|
}
|
|
1092
1146
|
}
|
|
1093
1147
|
}
|
|
1094
|
-
await
|
|
1095
|
-
await
|
|
1148
|
+
await fs7.rename(file, `${file}.1`);
|
|
1149
|
+
await fs7.writeFile(file, "", { encoding: "utf-8" });
|
|
1096
1150
|
return { rotated: true, from: file, to: `${file}.1` };
|
|
1097
1151
|
});
|
|
1098
1152
|
}
|
|
@@ -1241,6 +1295,36 @@ function isDaemonSocketLive(sockPath, timeoutMs = 500) {
|
|
|
1241
1295
|
});
|
|
1242
1296
|
});
|
|
1243
1297
|
}
|
|
1298
|
+
function sendRequest(sockPath, msg, timeoutMs = 5e3) {
|
|
1299
|
+
return new Promise((resolve2, reject) => {
|
|
1300
|
+
const conn = createConnection(sockPath);
|
|
1301
|
+
const dec = createDecoder();
|
|
1302
|
+
const timer = setTimeout(() => {
|
|
1303
|
+
conn.destroy();
|
|
1304
|
+
reject(new Error("control plane unavailable: request timed out"));
|
|
1305
|
+
}, timeoutMs);
|
|
1306
|
+
conn.setEncoding("utf-8");
|
|
1307
|
+
conn.on("connect", () => conn.write(encodeMsg({ ...msg, _v: PROTOCOL_VERSION })));
|
|
1308
|
+
conn.on("data", (chunk) => {
|
|
1309
|
+
for (const m of dec.push(chunk)) {
|
|
1310
|
+
clearTimeout(timer);
|
|
1311
|
+
conn.destroy();
|
|
1312
|
+
if (m._v !== void 0 && m._v !== PROTOCOL_VERSION) {
|
|
1313
|
+
reject(new Error(`squadrantd protocol v${m._v}, this client expects v${PROTOCOL_VERSION} \u2014 upgrade squadrantd or this CLI`));
|
|
1314
|
+
} else if (m.ok) {
|
|
1315
|
+
resolve2(m.reply);
|
|
1316
|
+
} else {
|
|
1317
|
+
reject(new Error(m.error));
|
|
1318
|
+
}
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
conn.on("error", () => {
|
|
1323
|
+
clearTimeout(timer);
|
|
1324
|
+
reject(new Error("control plane unavailable: cannot reach squadrantd socket"));
|
|
1325
|
+
});
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1244
1328
|
function encodeFrame(f) {
|
|
1245
1329
|
return JSON.stringify(f) + "\n";
|
|
1246
1330
|
}
|
|
@@ -1508,6 +1592,7 @@ function buildContext(opts) {
|
|
|
1508
1592
|
codexDriver: null,
|
|
1509
1593
|
opencodeBridge: null,
|
|
1510
1594
|
cmuxEventsBridge: null,
|
|
1595
|
+
telegramBridge: void 0,
|
|
1511
1596
|
broadcast: () => {
|
|
1512
1597
|
},
|
|
1513
1598
|
schedulePromotion: () => {
|
|
@@ -2035,10 +2120,10 @@ function distBuiltAt() {
|
|
|
2035
2120
|
return 0;
|
|
2036
2121
|
}
|
|
2037
2122
|
}
|
|
2038
|
-
function gatherLogStats(
|
|
2123
|
+
function gatherLogStats(path16, now, windowMs) {
|
|
2039
2124
|
let sizeBytes = 0;
|
|
2040
2125
|
try {
|
|
2041
|
-
sizeBytes = statSync2(
|
|
2126
|
+
sizeBytes = statSync2(path16).size;
|
|
2042
2127
|
} catch {
|
|
2043
2128
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
2044
2129
|
}
|
|
@@ -2049,7 +2134,7 @@ function gatherLogStats(path13, now, windowMs) {
|
|
|
2049
2134
|
const len = sizeBytes - start;
|
|
2050
2135
|
let text = "";
|
|
2051
2136
|
try {
|
|
2052
|
-
const fd = openSync2(
|
|
2137
|
+
const fd = openSync2(path16, "r");
|
|
2053
2138
|
try {
|
|
2054
2139
|
const buf = Buffer.alloc(len);
|
|
2055
2140
|
readSync(fd, buf, 0, len, start);
|
|
@@ -2126,7 +2211,11 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2126
2211
|
const { daemonCmux } = ctx;
|
|
2127
2212
|
const probes = createProbes(ctx);
|
|
2128
2213
|
const { defaultNotify, deliveryTick: initialDeliveryTick } = createDelivery(ctx, daemonCmux);
|
|
2129
|
-
const
|
|
2214
|
+
const baseNotify = opts.notify ?? defaultNotify;
|
|
2215
|
+
const notify = ctx.telegramBridge ? async (args) => {
|
|
2216
|
+
await baseNotify(args);
|
|
2217
|
+
ctx.telegramBridge.pushLifecycle(args.project, args.event);
|
|
2218
|
+
} : baseNotify;
|
|
2130
2219
|
const surfaceProbe = buildSurfaceProbe(ctx, probes, daemonCmux);
|
|
2131
2220
|
const ingest = (project) => (e) => void ctx.d.handle({ kind: "event", project, event: e });
|
|
2132
2221
|
const d = createDaemon({
|
|
@@ -2252,6 +2341,13 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2252
2341
|
log(`cmux events bridge start failed: ${e.message}`);
|
|
2253
2342
|
}
|
|
2254
2343
|
}
|
|
2344
|
+
if (ctx.telegramBridge) {
|
|
2345
|
+
try {
|
|
2346
|
+
ctx.telegramBridge.start();
|
|
2347
|
+
} catch (e) {
|
|
2348
|
+
log(`telegram bridge start failed: ${e.message}`);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2255
2351
|
const autoConfigSafe = !!opts.runCmuxAutoConfig || !process.env.VITEST;
|
|
2256
2352
|
if (autoConfigSafe) {
|
|
2257
2353
|
try {
|
|
@@ -2341,6 +2437,10 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2341
2437
|
ctx.cmuxEventsBridge.stop();
|
|
2342
2438
|
} catch {
|
|
2343
2439
|
}
|
|
2440
|
+
try {
|
|
2441
|
+
ctx.telegramBridge?.stop();
|
|
2442
|
+
} catch {
|
|
2443
|
+
}
|
|
2344
2444
|
try {
|
|
2345
2445
|
ctx.codexDriver.stop?.();
|
|
2346
2446
|
} catch {
|
|
@@ -2359,12 +2459,769 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2359
2459
|
|
|
2360
2460
|
// packages/core/dist/session-freshness.js
|
|
2361
2461
|
import crypto from "crypto";
|
|
2362
|
-
import
|
|
2363
|
-
import
|
|
2462
|
+
import fs8 from "fs";
|
|
2463
|
+
import path7 from "path";
|
|
2364
2464
|
|
|
2365
2465
|
// packages/core/dist/crew-lifecycle.js
|
|
2366
2466
|
import { exec as nodeExec } from "child_process";
|
|
2367
2467
|
|
|
2468
|
+
// packages/core/dist/telegram/auth.js
|
|
2469
|
+
function isControlEnabled(cfg) {
|
|
2470
|
+
return cfg.remoteControl === true;
|
|
2471
|
+
}
|
|
2472
|
+
function isAuthorized(fromId, cfg) {
|
|
2473
|
+
if (fromId === void 0)
|
|
2474
|
+
return false;
|
|
2475
|
+
return Array.isArray(cfg.users) && cfg.users.includes(fromId);
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// packages/core/dist/telegram/commands.js
|
|
2479
|
+
var WRITABLE_CONFIG_KEYS = ["defaults.effort"];
|
|
2480
|
+
var EFFORT_MODES = /* @__PURE__ */ new Set(["max", "balance", "low"]);
|
|
2481
|
+
function ok(name, argv) {
|
|
2482
|
+
return { kind: "ok", name, argv };
|
|
2483
|
+
}
|
|
2484
|
+
function usage(name, message) {
|
|
2485
|
+
return { kind: "usage", name, message };
|
|
2486
|
+
}
|
|
2487
|
+
var REGISTRY = {
|
|
2488
|
+
status: { usage: "/status", build: () => ok("status", ["status"]) },
|
|
2489
|
+
projects: { usage: "/projects", build: () => ok("projects", ["projects", "list"]) },
|
|
2490
|
+
crews: {
|
|
2491
|
+
usage: "/crews <project>",
|
|
2492
|
+
build: (a) => a[0] ? ok("crews", ["crew", "list", a[0]]) : usage("crews", "usage: /crews <project>")
|
|
2493
|
+
},
|
|
2494
|
+
launch: {
|
|
2495
|
+
usage: "/launch <project>",
|
|
2496
|
+
build: (a) => a[0] ? ok("launch", ["launch", a[0]]) : usage("launch", "usage: /launch <project>")
|
|
2497
|
+
},
|
|
2498
|
+
effort: {
|
|
2499
|
+
usage: "/effort [max|balance|low]",
|
|
2500
|
+
build: (a) => {
|
|
2501
|
+
if (a.length === 0)
|
|
2502
|
+
return ok("effort", ["effort"]);
|
|
2503
|
+
if (!EFFORT_MODES.has(a[0]))
|
|
2504
|
+
return usage("effort", "usage: /effort [max|balance|low]");
|
|
2505
|
+
return ok("effort", ["effort", a[0]]);
|
|
2506
|
+
}
|
|
2507
|
+
},
|
|
2508
|
+
config: {
|
|
2509
|
+
usage: "/config get <key> | /config set <key> <value>",
|
|
2510
|
+
build: (a) => {
|
|
2511
|
+
const sub = a[0];
|
|
2512
|
+
if (sub === "get") {
|
|
2513
|
+
const key = a[1];
|
|
2514
|
+
if (!key)
|
|
2515
|
+
return usage("config", "usage: /config get <key>");
|
|
2516
|
+
return ok("config", ["config", "get", key]);
|
|
2517
|
+
}
|
|
2518
|
+
if (sub === "set") {
|
|
2519
|
+
const key = a[1];
|
|
2520
|
+
const value = a.slice(2).join(" ");
|
|
2521
|
+
if (!key || value === "")
|
|
2522
|
+
return usage("config", "usage: /config set <key> <value>");
|
|
2523
|
+
if (!WRITABLE_CONFIG_KEYS.includes(key)) {
|
|
2524
|
+
return {
|
|
2525
|
+
kind: "denied",
|
|
2526
|
+
message: `\u26D4 '${key}' is not writable over Telegram. Allowed: ${WRITABLE_CONFIG_KEYS.join(", ")}`
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
return ok("config", ["config", "set", key, value]);
|
|
2530
|
+
}
|
|
2531
|
+
return usage("config", "usage: /config get <key> | /config set <key> <value>");
|
|
2532
|
+
}
|
|
2533
|
+
},
|
|
2534
|
+
spawn: {
|
|
2535
|
+
usage: "/spawn <project> <task...>",
|
|
2536
|
+
build: (a) => {
|
|
2537
|
+
const project = a[0];
|
|
2538
|
+
const task = a.slice(1).join(" ");
|
|
2539
|
+
if (!project || task === "")
|
|
2540
|
+
return usage("spawn", "usage: /spawn <project> <task...>");
|
|
2541
|
+
return ok("spawn", ["crew", "spawn", project, task]);
|
|
2542
|
+
}
|
|
2543
|
+
},
|
|
2544
|
+
mute: {
|
|
2545
|
+
usage: "/mute <project>",
|
|
2546
|
+
build: (a) => a[0] ? ok("mute", ["telegram", "notify", a[0], "off"]) : usage("mute", "usage: /mute <project>")
|
|
2547
|
+
},
|
|
2548
|
+
unmute: {
|
|
2549
|
+
usage: "/unmute <project>",
|
|
2550
|
+
build: (a) => a[0] ? ok("unmute", ["telegram", "notify", a[0], "on"]) : usage("unmute", "usage: /unmute <project>")
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2553
|
+
function helpText() {
|
|
2554
|
+
const lines = Object.values(REGISTRY).map((e) => ` ${e.usage}`);
|
|
2555
|
+
return ["Available commands:", ...lines, " /help"].join("\n");
|
|
2556
|
+
}
|
|
2557
|
+
function stripBotMention(token) {
|
|
2558
|
+
return token.split("@")[0];
|
|
2559
|
+
}
|
|
2560
|
+
function parseCommand(text) {
|
|
2561
|
+
const trimmed = text.trim();
|
|
2562
|
+
if (!trimmed.startsWith("/")) {
|
|
2563
|
+
return { kind: "unknown", message: "unknown command \u2014 send /help" };
|
|
2564
|
+
}
|
|
2565
|
+
const tokens = trimmed.slice(1).split(/\s+/).filter((t) => t.length > 0);
|
|
2566
|
+
const name = stripBotMention(tokens[0] ?? "").toLowerCase();
|
|
2567
|
+
const args = tokens.slice(1);
|
|
2568
|
+
if (name === "help") {
|
|
2569
|
+
return { kind: "usage", name: "help", message: helpText() };
|
|
2570
|
+
}
|
|
2571
|
+
const entry = REGISTRY[name];
|
|
2572
|
+
if (!entry) {
|
|
2573
|
+
return { kind: "unknown", message: `unknown command '/${name}' \u2014 send /help` };
|
|
2574
|
+
}
|
|
2575
|
+
return entry.build(args);
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
// packages/core/dist/telegram/ensure-captain.js
|
|
2579
|
+
var DEFAULT_WARMUP_TIMEOUT_MS = 12e4;
|
|
2580
|
+
var DEFAULT_POLL_MS = 1e3;
|
|
2581
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2582
|
+
function createEnsureCaptainAlive(deps) {
|
|
2583
|
+
const warmupTimeoutMs = deps.warmupTimeoutMs ?? DEFAULT_WARMUP_TIMEOUT_MS;
|
|
2584
|
+
const pollMs = deps.pollMs ?? DEFAULT_POLL_MS;
|
|
2585
|
+
const sleep3 = deps.sleep ?? defaultSleep;
|
|
2586
|
+
const now = deps.now ?? (() => Date.now());
|
|
2587
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
2588
|
+
async function run(project) {
|
|
2589
|
+
if (await deps.isAlive(project))
|
|
2590
|
+
return "alive";
|
|
2591
|
+
await deps.launch(project);
|
|
2592
|
+
const deadline = now() + warmupTimeoutMs;
|
|
2593
|
+
while (now() < deadline) {
|
|
2594
|
+
if (await deps.isAlive(project))
|
|
2595
|
+
return "launched";
|
|
2596
|
+
await sleep3(pollMs);
|
|
2597
|
+
}
|
|
2598
|
+
return "timeout";
|
|
2599
|
+
}
|
|
2600
|
+
return function ensure(project) {
|
|
2601
|
+
const existing = inFlight.get(project);
|
|
2602
|
+
if (existing)
|
|
2603
|
+
return existing;
|
|
2604
|
+
const p = run(project).finally(() => inFlight.delete(project));
|
|
2605
|
+
inFlight.set(project, p);
|
|
2606
|
+
return p;
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
// packages/core/dist/telegram/format.js
|
|
2611
|
+
function topicName(project) {
|
|
2612
|
+
return project;
|
|
2613
|
+
}
|
|
2614
|
+
function formatLifecycle(project, ev) {
|
|
2615
|
+
switch (ev.type) {
|
|
2616
|
+
case "task.done":
|
|
2617
|
+
return `\u2705 [${project}] CREW DONE \xB7 ${ev.id}` + (ev.message ? `
|
|
2618
|
+
${ev.message}` : "");
|
|
2619
|
+
case "task.blocked":
|
|
2620
|
+
return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev.id}
|
|
2621
|
+
${ev.question}`;
|
|
2622
|
+
case "task.idle":
|
|
2623
|
+
return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev.id}`;
|
|
2624
|
+
case "task.failed":
|
|
2625
|
+
return `\u274C [${project}] CREW FAILED \xB7 ${ev.id}
|
|
2626
|
+
${ev.error}`;
|
|
2627
|
+
case "task.approval.requested":
|
|
2628
|
+
return `\u{1F510} [${project}] APPROVAL NEEDED \xB7 ${ev.id}
|
|
2629
|
+
${ev.question}`;
|
|
2630
|
+
case "task.input.requested":
|
|
2631
|
+
return `\u2753 [${project}] INPUT NEEDED \xB7 ${ev.id}
|
|
2632
|
+
${ev.question}`;
|
|
2633
|
+
case "task.timeout":
|
|
2634
|
+
return `\u23F1\uFE0F [${project}] CREW TIMEOUT \xB7 ${ev.id}`;
|
|
2635
|
+
default:
|
|
2636
|
+
return `\u2139\uFE0F [${project}] ${ev.type} \xB7 ${ev.id}`;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
function formatInbound(text) {
|
|
2640
|
+
return `\u{1F4E9} [from Telegram] ${text}`;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
// packages/core/dist/telegram/state.js
|
|
2644
|
+
import fs9 from "fs";
|
|
2645
|
+
import path8 from "path";
|
|
2646
|
+
function statePath(stateRoot) {
|
|
2647
|
+
return path8.join(stateRoot, "telegram-state.json");
|
|
2648
|
+
}
|
|
2649
|
+
function topicKey(project, scope = "project") {
|
|
2650
|
+
return `${project}::${scope}`;
|
|
2651
|
+
}
|
|
2652
|
+
function loadState(stateRoot) {
|
|
2653
|
+
try {
|
|
2654
|
+
const raw = fs9.readFileSync(statePath(stateRoot), "utf-8");
|
|
2655
|
+
const data = JSON.parse(raw);
|
|
2656
|
+
const result = {
|
|
2657
|
+
offset: typeof data.offset === "number" ? data.offset : 0,
|
|
2658
|
+
topics: data.topics ?? {},
|
|
2659
|
+
notify: data.notify ?? {}
|
|
2660
|
+
};
|
|
2661
|
+
if (typeof data.lastUserId === "number")
|
|
2662
|
+
result.lastUserId = data.lastUserId;
|
|
2663
|
+
return result;
|
|
2664
|
+
} catch {
|
|
2665
|
+
return { offset: 0, topics: {}, notify: {} };
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
function saveState(stateRoot, s) {
|
|
2669
|
+
fs9.mkdirSync(stateRoot, { recursive: true });
|
|
2670
|
+
fs9.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
|
|
2671
|
+
}
|
|
2672
|
+
function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
2673
|
+
const s = loadState(stateRoot);
|
|
2674
|
+
s.topics[topicKey(project, scope)] = topicId;
|
|
2675
|
+
saveState(stateRoot, s);
|
|
2676
|
+
}
|
|
2677
|
+
function setLastUserId(stateRoot, id) {
|
|
2678
|
+
const s = loadState(stateRoot);
|
|
2679
|
+
s.lastUserId = id;
|
|
2680
|
+
saveState(stateRoot, s);
|
|
2681
|
+
}
|
|
2682
|
+
function setNotify(stateRoot, project, active) {
|
|
2683
|
+
const s = loadState(stateRoot);
|
|
2684
|
+
s.notify[project] = active;
|
|
2685
|
+
saveState(stateRoot, s);
|
|
2686
|
+
}
|
|
2687
|
+
function findProjectByThread(stateRoot, threadId) {
|
|
2688
|
+
const s = loadState(stateRoot);
|
|
2689
|
+
for (const [key, id] of Object.entries(s.topics)) {
|
|
2690
|
+
if (id !== threadId)
|
|
2691
|
+
continue;
|
|
2692
|
+
const sep2 = key.indexOf("::");
|
|
2693
|
+
if (sep2 === -1)
|
|
2694
|
+
continue;
|
|
2695
|
+
return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2) };
|
|
2696
|
+
}
|
|
2697
|
+
return null;
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
// packages/core/dist/telegram/client.js
|
|
2701
|
+
function createTelegramClient(opts) {
|
|
2702
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
2703
|
+
const base = `https://api.telegram.org/bot${opts.token}`;
|
|
2704
|
+
async function call(method, body) {
|
|
2705
|
+
const res = await fetchImpl(`${base}/${method}`, {
|
|
2706
|
+
method: "POST",
|
|
2707
|
+
headers: { "content-type": "application/json" },
|
|
2708
|
+
body: JSON.stringify(body)
|
|
2709
|
+
});
|
|
2710
|
+
const json = await res.json();
|
|
2711
|
+
if (!res.ok || !json.ok) {
|
|
2712
|
+
const code = json.error_code ?? res.status;
|
|
2713
|
+
const desc = json.description ?? "unknown error";
|
|
2714
|
+
throw new Error(`telegram ${method} failed (${code}): ${desc}`);
|
|
2715
|
+
}
|
|
2716
|
+
return json.result;
|
|
2717
|
+
}
|
|
2718
|
+
return {
|
|
2719
|
+
async getMe() {
|
|
2720
|
+
const r = await call("getMe", {});
|
|
2721
|
+
return { id: r.id, username: r.username };
|
|
2722
|
+
},
|
|
2723
|
+
getUpdates(offset, timeoutSec = 50) {
|
|
2724
|
+
return call("getUpdates", { offset, timeout: timeoutSec });
|
|
2725
|
+
},
|
|
2726
|
+
async sendMessage(chatId, threadId, text, replyMarkup) {
|
|
2727
|
+
const body = { chat_id: chatId, text };
|
|
2728
|
+
if (threadId !== void 0)
|
|
2729
|
+
body.message_thread_id = threadId;
|
|
2730
|
+
if (replyMarkup !== void 0)
|
|
2731
|
+
body.reply_markup = replyMarkup;
|
|
2732
|
+
await call("sendMessage", body);
|
|
2733
|
+
},
|
|
2734
|
+
async answerCallbackQuery(callbackQueryId, text) {
|
|
2735
|
+
const body = { callback_query_id: callbackQueryId };
|
|
2736
|
+
if (text !== void 0)
|
|
2737
|
+
body.text = text;
|
|
2738
|
+
await call("answerCallbackQuery", body);
|
|
2739
|
+
},
|
|
2740
|
+
async editMessageReplyMarkup(chatId, messageId, replyMarkup) {
|
|
2741
|
+
await call("editMessageReplyMarkup", { chat_id: chatId, message_id: messageId, reply_markup: replyMarkup });
|
|
2742
|
+
},
|
|
2743
|
+
async createForumTopic(chatId, name) {
|
|
2744
|
+
const r = await call("createForumTopic", { chat_id: chatId, name });
|
|
2745
|
+
return r.message_thread_id;
|
|
2746
|
+
},
|
|
2747
|
+
async setMyCommands(commands) {
|
|
2748
|
+
await call("setMyCommands", { commands });
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
// packages/core/dist/telegram/bridge.js
|
|
2754
|
+
import os3 from "os";
|
|
2755
|
+
import path9 from "path";
|
|
2756
|
+
|
|
2757
|
+
// packages/core/dist/telegram/panels.js
|
|
2758
|
+
var mark = (on, label) => on ? `\u2022 ${label}` : label;
|
|
2759
|
+
var TIERS = ["none", "alert_only", "all"];
|
|
2760
|
+
function notifyPanel(s) {
|
|
2761
|
+
return {
|
|
2762
|
+
inline_keyboard: [
|
|
2763
|
+
[{ text: `Captain: ${s.cap ? "ON" : "OFF"}`, callback_data: `n:cap:${s.cap ? "off" : "on"}` }],
|
|
2764
|
+
TIERS.map((t) => ({ text: mark(s.crew === t, `crew:${t}`), callback_data: `n:crew:${t}` })),
|
|
2765
|
+
[{ text: s.active ? "\u{1F515} Mute topic" : "\u{1F514} Unmute", callback_data: `n:active:${s.active ? "off" : "on"}` }]
|
|
2766
|
+
]
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
function effortPanel(current) {
|
|
2770
|
+
const modes = ["max", "balance", "low"];
|
|
2771
|
+
return {
|
|
2772
|
+
inline_keyboard: [modes.map((m) => ({ text: mark(current === m, m), callback_data: `e:${m}` }))]
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
function projectPicker(action, projects) {
|
|
2776
|
+
return { inline_keyboard: projects.map((p) => [{ text: p, callback_data: `${action}:${p}` }]) };
|
|
2777
|
+
}
|
|
2778
|
+
var SPAWN_PROMPT_PREFIX = "\u{1F195} Reply with the task for a crew on: ";
|
|
2779
|
+
function buildSpawnPrompt(project) {
|
|
2780
|
+
return `${SPAWN_PROMPT_PREFIX}${project}`;
|
|
2781
|
+
}
|
|
2782
|
+
function parseSpawnPrompt(text) {
|
|
2783
|
+
if (!text || !text.startsWith(SPAWN_PROMPT_PREFIX))
|
|
2784
|
+
return null;
|
|
2785
|
+
const project = text.slice(SPAWN_PROMPT_PREFIX.length).trim();
|
|
2786
|
+
return project.length > 0 ? project : null;
|
|
2787
|
+
}
|
|
2788
|
+
function spawnPicker(projects) {
|
|
2789
|
+
return { inline_keyboard: projects.map((p) => [{ text: p, callback_data: `sp:${p}` }]) };
|
|
2790
|
+
}
|
|
2791
|
+
var PICK_ACTIONS = ["cr", "lc", "mu", "um"];
|
|
2792
|
+
function parseCallback(data) {
|
|
2793
|
+
const parts = data.split(":");
|
|
2794
|
+
if (parts[0] === "n" && (parts[1] === "cap" || parts[1] === "crew" || parts[1] === "active") && parts[2]) {
|
|
2795
|
+
return { t: "notify", dim: parts[1], val: parts[2] };
|
|
2796
|
+
}
|
|
2797
|
+
if (parts[0] === "e" && parts[1])
|
|
2798
|
+
return { t: "effort", mode: parts[1] };
|
|
2799
|
+
if (PICK_ACTIONS.includes(parts[0]) && parts[1]) {
|
|
2800
|
+
return { t: "pick", action: parts[0], project: parts.slice(1).join(":") };
|
|
2801
|
+
}
|
|
2802
|
+
if (parts[0] === "sp" && parts[1])
|
|
2803
|
+
return { t: "spawn", project: parts.slice(1).join(":") };
|
|
2804
|
+
return null;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
// packages/core/dist/telegram/tiers.js
|
|
2808
|
+
var DONE_ONLY = /* @__PURE__ */ new Set(["task.done", "task.failed"]);
|
|
2809
|
+
var ALERTS = /* @__PURE__ */ new Set([
|
|
2810
|
+
...DONE_ONLY,
|
|
2811
|
+
"task.blocked",
|
|
2812
|
+
"task.approval.requested",
|
|
2813
|
+
"task.input.requested",
|
|
2814
|
+
"task.timeout"
|
|
2815
|
+
]);
|
|
2816
|
+
function tierIncludes(tier, eventType) {
|
|
2817
|
+
switch (tier) {
|
|
2818
|
+
case "none":
|
|
2819
|
+
return false;
|
|
2820
|
+
case "done_only":
|
|
2821
|
+
return DONE_ONLY.has(eventType);
|
|
2822
|
+
case "alert_only":
|
|
2823
|
+
return ALERTS.has(eventType);
|
|
2824
|
+
case "all":
|
|
2825
|
+
return true;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// packages/core/dist/telegram/bridge.js
|
|
2830
|
+
var LONG_POLL_SEC = 50;
|
|
2831
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2832
|
+
var CREW_TIERS = ["all", "alert_only", "done_only", "none"];
|
|
2833
|
+
function parseNotifyPref(text) {
|
|
2834
|
+
const parts = text.trim().split(/\s+/);
|
|
2835
|
+
if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
|
|
2836
|
+
return null;
|
|
2837
|
+
const dimension = parts[1]?.toLowerCase();
|
|
2838
|
+
if ((dimension === "crew" || dimension === "cap") && parts[2])
|
|
2839
|
+
return { dimension, value: parts[2].toLowerCase() };
|
|
2840
|
+
return null;
|
|
2841
|
+
}
|
|
2842
|
+
function isBareSpawn(text) {
|
|
2843
|
+
const trimmed = text.trim();
|
|
2844
|
+
if (!trimmed.startsWith("/"))
|
|
2845
|
+
return false;
|
|
2846
|
+
const tokens = trimmed.slice(1).split(/\s+/).filter((t) => t.length > 0);
|
|
2847
|
+
return stripBotMention(tokens[0] ?? "").toLowerCase() === "spawn" && tokens.length === 1;
|
|
2848
|
+
}
|
|
2849
|
+
function notifyToggle(text) {
|
|
2850
|
+
const first = stripBotMention(text.trim().split(/\s+/)[0] ?? "").toLowerCase();
|
|
2851
|
+
if (first === "/unmute")
|
|
2852
|
+
return true;
|
|
2853
|
+
if (first === "/mute")
|
|
2854
|
+
return false;
|
|
2855
|
+
return null;
|
|
2856
|
+
}
|
|
2857
|
+
function createTelegramBridge(opts) {
|
|
2858
|
+
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
2859
|
+
const configRoot = opts.configRoot ?? path9.join(os3.homedir(), ".config", "squadrant");
|
|
2860
|
+
const pollMs = cfg.pollMs ?? 1e3;
|
|
2861
|
+
let running = false;
|
|
2862
|
+
function persistOffset(next) {
|
|
2863
|
+
const s = loadState(stateRoot);
|
|
2864
|
+
s.offset = next;
|
|
2865
|
+
saveState(stateRoot, s);
|
|
2866
|
+
}
|
|
2867
|
+
async function deliverOutbound(project, ev) {
|
|
2868
|
+
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
2869
|
+
const live = loadState(stateRoot).notify[project];
|
|
2870
|
+
const active = live ?? resolved.active;
|
|
2871
|
+
if (!active)
|
|
2872
|
+
return;
|
|
2873
|
+
if (!tierIncludes(resolved.crew, ev.type))
|
|
2874
|
+
return;
|
|
2875
|
+
let threadId = loadState(stateRoot).topics[topicKey(project)];
|
|
2876
|
+
if (threadId === void 0) {
|
|
2877
|
+
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
2878
|
+
setTopic(stateRoot, project, threadId);
|
|
2879
|
+
}
|
|
2880
|
+
await client.sendMessage(cfg.supergroupId, threadId, formatLifecycle(project, ev));
|
|
2881
|
+
}
|
|
2882
|
+
function resolveLiveNotify(project) {
|
|
2883
|
+
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
2884
|
+
const live = loadState(stateRoot).notify[project];
|
|
2885
|
+
return { ...resolved, active: live ?? resolved.active };
|
|
2886
|
+
}
|
|
2887
|
+
async function editMarkup(chatId, messageId, markup) {
|
|
2888
|
+
try {
|
|
2889
|
+
await client.editMessageReplyMarkup(chatId, messageId, markup);
|
|
2890
|
+
} catch (e) {
|
|
2891
|
+
const msg = e.message;
|
|
2892
|
+
if (!/not modified/i.test(msg))
|
|
2893
|
+
throw e;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
async function handleCallback(cq) {
|
|
2897
|
+
try {
|
|
2898
|
+
if (!cq.data || !cq.message) {
|
|
2899
|
+
await client.answerCallbackQuery(cq.id);
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
if (!isControlEnabled(cfg) || !isAuthorized(cq.from?.id, cfg)) {
|
|
2903
|
+
await client.answerCallbackQuery(cq.id, "\u26D4 not authorized");
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
const action = parseCallback(cq.data);
|
|
2907
|
+
if (!action) {
|
|
2908
|
+
await client.answerCallbackQuery(cq.id);
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
const chatId = cq.message.chat.id;
|
|
2912
|
+
const messageId = cq.message.message_id;
|
|
2913
|
+
if (action.t === "notify") {
|
|
2914
|
+
const resolved = findProjectByThread(stateRoot, cq.message.message_thread_id ?? -1);
|
|
2915
|
+
if (!resolved) {
|
|
2916
|
+
await client.answerCallbackQuery(cq.id, "no project for this topic");
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
const project2 = resolved.project;
|
|
2920
|
+
if (action.dim === "active") {
|
|
2921
|
+
setNotify(stateRoot, project2, action.val === "on");
|
|
2922
|
+
} else if (action.dim === "cap") {
|
|
2923
|
+
saveProjectOverride(project2, { telegram: { notify: { cap: action.val === "on" } } }, configRoot);
|
|
2924
|
+
} else {
|
|
2925
|
+
saveProjectOverride(project2, { telegram: { notify: { crew: action.val } } }, configRoot);
|
|
2926
|
+
}
|
|
2927
|
+
await client.answerCallbackQuery(cq.id, `\u2705 ${action.dim} = ${action.val}`);
|
|
2928
|
+
await editMarkup(chatId, messageId, notifyPanel(resolveLiveNotify(project2)));
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
if (action.t === "effort") {
|
|
2932
|
+
if (runCommand)
|
|
2933
|
+
await runCommand(["effort", action.mode]);
|
|
2934
|
+
await client.answerCallbackQuery(cq.id, `\u2705 effort = ${action.mode}`);
|
|
2935
|
+
await editMarkup(chatId, messageId, effortPanel(action.mode));
|
|
2936
|
+
return;
|
|
2937
|
+
}
|
|
2938
|
+
if (action.t === "spawn") {
|
|
2939
|
+
await reply(cq.message.message_thread_id, buildSpawnPrompt(action.project), { force_reply: true, selective: true });
|
|
2940
|
+
await client.answerCallbackQuery(cq.id);
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
const { action: act, project } = action;
|
|
2944
|
+
if (act === "cr") {
|
|
2945
|
+
const out = runCommand ? await runCommand(["crew", "list", project]) : "(command runner unavailable)";
|
|
2946
|
+
await client.answerCallbackQuery(cq.id);
|
|
2947
|
+
await reply(void 0, out);
|
|
2948
|
+
} else if (act === "lc") {
|
|
2949
|
+
if (runCommand)
|
|
2950
|
+
await runCommand(["launch", project]);
|
|
2951
|
+
await client.answerCallbackQuery(cq.id, `launching ${project}`);
|
|
2952
|
+
} else if (act === "mu") {
|
|
2953
|
+
setNotify(stateRoot, project, false);
|
|
2954
|
+
await client.answerCallbackQuery(cq.id, `\u{1F515} muted ${project}`);
|
|
2955
|
+
} else {
|
|
2956
|
+
setNotify(stateRoot, project, true);
|
|
2957
|
+
await client.answerCallbackQuery(cq.id, `\u{1F514} unmuted ${project}`);
|
|
2958
|
+
}
|
|
2959
|
+
} catch (e) {
|
|
2960
|
+
log(`telegram callback failed data=${cq.data}: ${e.message}`);
|
|
2961
|
+
try {
|
|
2962
|
+
await client.answerCallbackQuery(cq.id, "\u26A0\uFE0F failed");
|
|
2963
|
+
} catch {
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
async function reply(threadId, text, replyMarkup) {
|
|
2968
|
+
if (!sendReply)
|
|
2969
|
+
return;
|
|
2970
|
+
try {
|
|
2971
|
+
if (replyMarkup !== void 0)
|
|
2972
|
+
await sendReply(threadId, text, replyMarkup);
|
|
2973
|
+
else
|
|
2974
|
+
await sendReply(threadId, text);
|
|
2975
|
+
} catch (e) {
|
|
2976
|
+
log(`telegram reply failed: ${e.message}`);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
function currentEffort() {
|
|
2980
|
+
try {
|
|
2981
|
+
return loadConfig(path9.join(configRoot, "config.json")).defaults.effort ?? "balance";
|
|
2982
|
+
} catch {
|
|
2983
|
+
return "balance";
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
function projectNames() {
|
|
2987
|
+
try {
|
|
2988
|
+
return Object.keys(loadConfig(path9.join(configRoot, "config.json")).projects);
|
|
2989
|
+
} catch {
|
|
2990
|
+
return [];
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
async function replySpawnPicker(threadId) {
|
|
2994
|
+
const projects = projectNames();
|
|
2995
|
+
if (projects.length === 0) {
|
|
2996
|
+
await reply(threadId, "no projects registered");
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
await reply(threadId, "Pick a project to spawn a crew on:", spawnPicker(projects));
|
|
3000
|
+
}
|
|
3001
|
+
async function handleGeneral(text, fromId) {
|
|
3002
|
+
if (!text.startsWith("/")) {
|
|
3003
|
+
await reply(void 0, "Send /help for commands.");
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
3007
|
+
await reply(void 0, "\u26D4 not authorized");
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
const tokens = text.trim().slice(1).split(/\s+/).filter((t) => t.length > 0);
|
|
3011
|
+
const name = stripBotMention(tokens[0] ?? "").toLowerCase();
|
|
3012
|
+
const noArg = tokens.length === 1;
|
|
3013
|
+
if (noArg && name === "effort") {
|
|
3014
|
+
await reply(void 0, "Effort mode:", effortPanel(currentEffort()));
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
if (noArg && name === "spawn") {
|
|
3018
|
+
await replySpawnPicker(void 0);
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
const PICKERS = { crews: "cr", launch: "lc", mute: "mu", unmute: "um" };
|
|
3022
|
+
if (noArg && name in PICKERS) {
|
|
3023
|
+
const projects = projectNames();
|
|
3024
|
+
if (projects.length === 0) {
|
|
3025
|
+
await reply(void 0, "no projects registered");
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
await reply(void 0, `Pick a project:`, projectPicker(PICKERS[name], projects));
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
const parsed = parseCommand(text);
|
|
3032
|
+
if (parsed.kind !== "ok") {
|
|
3033
|
+
await reply(void 0, parsed.message);
|
|
3034
|
+
return;
|
|
3035
|
+
}
|
|
3036
|
+
try {
|
|
3037
|
+
const out = runCommand ? await runCommand(parsed.argv) : "(command runner unavailable)";
|
|
3038
|
+
await reply(void 0, out);
|
|
3039
|
+
} catch (e) {
|
|
3040
|
+
await reply(void 0, `\u26A0\uFE0F command failed: ${e.message}`);
|
|
3041
|
+
log(`telegram command failed argv=${JSON.stringify(parsed.argv)}: ${e.message}`);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
async function handleProjectTopic(text, threadId, fromId) {
|
|
3045
|
+
const resolved = findProjectByThread(stateRoot, threadId);
|
|
3046
|
+
if (!resolved)
|
|
3047
|
+
return;
|
|
3048
|
+
if (isBareSpawn(text)) {
|
|
3049
|
+
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
3050
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
await replySpawnPicker(threadId);
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
const toggle = notifyToggle(text);
|
|
3057
|
+
if (toggle !== null) {
|
|
3058
|
+
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
3059
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
setNotify(stateRoot, resolved.project, toggle);
|
|
3063
|
+
await reply(threadId, toggle ? `\u{1F514} ${resolved.project} notifications ON` : `\u{1F515} ${resolved.project} notifications OFF`);
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
if (stripBotMention(text.trim().split(/\s+/)[0] ?? "").toLowerCase() === "/notify") {
|
|
3067
|
+
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
3068
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
const pref = parseNotifyPref(text);
|
|
3072
|
+
if (pref === null) {
|
|
3073
|
+
await reply(threadId, `\u{1F514} ${resolved.project} notifications`, notifyPanel(resolveLiveNotify(resolved.project)));
|
|
3074
|
+
return;
|
|
3075
|
+
}
|
|
3076
|
+
if (pref.dimension === "crew") {
|
|
3077
|
+
if (!CREW_TIERS.includes(pref.value)) {
|
|
3078
|
+
await reply(threadId, "crew must be all|alert_only|done_only|none");
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
saveProjectOverride(resolved.project, { telegram: { notify: { crew: pref.value } } }, configRoot);
|
|
3082
|
+
} else {
|
|
3083
|
+
if (pref.value !== "on" && pref.value !== "off") {
|
|
3084
|
+
await reply(threadId, "cap must be on|off");
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
saveProjectOverride(resolved.project, { telegram: { notify: { cap: pref.value === "on" } } }, configRoot);
|
|
3088
|
+
}
|
|
3089
|
+
await reply(threadId, `\u2705 ${pref.dimension} = ${pref.value}`);
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
setNotify(stateRoot, resolved.project, true);
|
|
3093
|
+
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
3094
|
+
try {
|
|
3095
|
+
const r = await ensureCaptainAlive(resolved.project);
|
|
3096
|
+
if (r === "timeout")
|
|
3097
|
+
await reply(threadId, "\u26A0\uFE0F captain didn't warm up; message queued.");
|
|
3098
|
+
} catch (e) {
|
|
3099
|
+
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
await appendCaptainMessage2({ stateRoot, project: resolved.project, text: formatInbound(text), source: "telegram" });
|
|
3103
|
+
}
|
|
3104
|
+
async function handleUpdate(u) {
|
|
3105
|
+
if (u.callback_query) {
|
|
3106
|
+
await handleCallback(u.callback_query);
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
const m = u.message;
|
|
3110
|
+
if (!m || m.text === void 0)
|
|
3111
|
+
return;
|
|
3112
|
+
if (!cfg.chats.includes(m.chat.id))
|
|
3113
|
+
return;
|
|
3114
|
+
if (m.from?.id !== void 0 && loadState(stateRoot).lastUserId !== m.from.id) {
|
|
3115
|
+
setLastUserId(stateRoot, m.from.id);
|
|
3116
|
+
}
|
|
3117
|
+
const spawnProject = parseSpawnPrompt(m.reply_to_message?.text);
|
|
3118
|
+
if (spawnProject) {
|
|
3119
|
+
const threadId = m.message_thread_id;
|
|
3120
|
+
if (!isControlEnabled(cfg) || !isAuthorized(m.from?.id, cfg)) {
|
|
3121
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
const task = m.text.trim();
|
|
3125
|
+
if (!task) {
|
|
3126
|
+
await reply(threadId, "spawn cancelled \u2014 empty task");
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
if (runCommand)
|
|
3130
|
+
await runCommand(["crew", "spawn", spawnProject, task]);
|
|
3131
|
+
await reply(threadId, `\u{1F195} spawning a crew on ${spawnProject}\u2026`);
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
if (m.message_thread_id === void 0) {
|
|
3135
|
+
await handleGeneral(m.text, m.from?.id);
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
await handleProjectTopic(m.text, m.message_thread_id, m.from?.id);
|
|
3139
|
+
}
|
|
3140
|
+
async function pollLoop() {
|
|
3141
|
+
while (running) {
|
|
3142
|
+
try {
|
|
3143
|
+
const offset = loadState(stateRoot).offset;
|
|
3144
|
+
const updates = await client.getUpdates(offset, LONG_POLL_SEC);
|
|
3145
|
+
for (const u of updates) {
|
|
3146
|
+
await handleUpdate(u);
|
|
3147
|
+
persistOffset(u.update_id + 1);
|
|
3148
|
+
}
|
|
3149
|
+
} catch (e) {
|
|
3150
|
+
log(`telegram inbound poll failed: ${e.message}`);
|
|
3151
|
+
}
|
|
3152
|
+
if (running)
|
|
3153
|
+
await sleep2(pollMs);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
return {
|
|
3157
|
+
start() {
|
|
3158
|
+
if (running)
|
|
3159
|
+
return;
|
|
3160
|
+
running = true;
|
|
3161
|
+
void pollLoop();
|
|
3162
|
+
},
|
|
3163
|
+
stop() {
|
|
3164
|
+
running = false;
|
|
3165
|
+
},
|
|
3166
|
+
pushLifecycle(project, ev) {
|
|
3167
|
+
void deliverOutbound(project, ev).catch((e) => {
|
|
3168
|
+
log(`telegram outbound failed project=${project}: ${e.message}`);
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
// packages/core/dist/telegram/setup.js
|
|
3175
|
+
import fs10 from "fs";
|
|
3176
|
+
|
|
3177
|
+
// packages/cli/src/control/telegram-control.ts
|
|
3178
|
+
import { execFile } from "child_process";
|
|
3179
|
+
import { promisify } from "util";
|
|
3180
|
+
var pExecFile = promisify(execFile);
|
|
3181
|
+
var MAX_OUTPUT = 3500;
|
|
3182
|
+
function capOutput(stdout, stderr, max = MAX_OUTPUT) {
|
|
3183
|
+
const out = stdout.trim();
|
|
3184
|
+
const err = stderr.trim();
|
|
3185
|
+
let combined = out;
|
|
3186
|
+
if (err) combined = combined ? `${combined}
|
|
3187
|
+
[stderr] ${err}` : `[stderr] ${err}`;
|
|
3188
|
+
if (!combined) combined = "(no output)";
|
|
3189
|
+
if (combined.length > max) combined = combined.slice(0, max) + "\n\u2026[truncated]";
|
|
3190
|
+
return combined;
|
|
3191
|
+
}
|
|
3192
|
+
var COMMAND_TIMEOUT_MS = 6e4;
|
|
3193
|
+
function createRunCommand(cliBin) {
|
|
3194
|
+
return async (argv) => {
|
|
3195
|
+
try {
|
|
3196
|
+
const { stdout, stderr } = await pExecFile(
|
|
3197
|
+
process.execPath,
|
|
3198
|
+
[cliBin, ...argv],
|
|
3199
|
+
{ timeout: COMMAND_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 }
|
|
3200
|
+
);
|
|
3201
|
+
return capOutput(stdout ?? "", stderr ?? "");
|
|
3202
|
+
} catch (e) {
|
|
3203
|
+
const err = e;
|
|
3204
|
+
return capOutput(err.stdout ?? "", err.stderr ?? err.message ?? "command failed");
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
function createIsCaptainAlive(sock) {
|
|
3209
|
+
return async (project) => {
|
|
3210
|
+
try {
|
|
3211
|
+
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
3212
|
+
const captain = health?.find((h) => h.kind === "captain" && h.project === project);
|
|
3213
|
+
return captain != null && captain.state !== "gone" && captain.state !== "unknown";
|
|
3214
|
+
} catch {
|
|
3215
|
+
return false;
|
|
3216
|
+
}
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
function createLaunch(cliBin) {
|
|
3220
|
+
return async (project) => {
|
|
3221
|
+
await pExecFile(process.execPath, [cliBin, "launch", project], { timeout: 3e4 });
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
|
|
2368
3225
|
// packages/agents/dist/drivers/claude.js
|
|
2369
3226
|
import { execSync as execSync2 } from "child_process";
|
|
2370
3227
|
|
|
@@ -2378,28 +3235,28 @@ import { execSync as execSync4 } from "child_process";
|
|
|
2378
3235
|
import { execSync as execSync5 } from "child_process";
|
|
2379
3236
|
|
|
2380
3237
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
2381
|
-
import
|
|
2382
|
-
import
|
|
3238
|
+
import fs11 from "fs";
|
|
3239
|
+
import path10 from "path";
|
|
2383
3240
|
|
|
2384
3241
|
// packages/agents/dist/projection/cursor.js
|
|
2385
3242
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2386
|
-
import
|
|
2387
|
-
import
|
|
3243
|
+
import path11 from "path";
|
|
3244
|
+
import os4 from "os";
|
|
2388
3245
|
|
|
2389
3246
|
// packages/agents/dist/projection/codex.js
|
|
2390
3247
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
2391
|
-
import
|
|
2392
|
-
import
|
|
3248
|
+
import path12 from "path";
|
|
3249
|
+
import os5 from "os";
|
|
2393
3250
|
|
|
2394
3251
|
// packages/agents/dist/projection/gemini.js
|
|
2395
3252
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
2396
|
-
import
|
|
2397
|
-
import
|
|
3253
|
+
import path13 from "path";
|
|
3254
|
+
import os6 from "os";
|
|
2398
3255
|
|
|
2399
3256
|
// packages/agents/dist/projection/opencode.js
|
|
2400
3257
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2401
|
-
import
|
|
2402
|
-
import
|
|
3258
|
+
import path14 from "path";
|
|
3259
|
+
import os7 from "os";
|
|
2403
3260
|
|
|
2404
3261
|
// packages/agents/dist/codex/app-server-client.js
|
|
2405
3262
|
import { EventEmitter } from "events";
|
|
@@ -2979,7 +3836,7 @@ var OpencodeSseBridge = class {
|
|
|
2979
3836
|
}
|
|
2980
3837
|
async run(taskId, port, ac) {
|
|
2981
3838
|
const fetchImpl = this.deps.fetchImpl ?? fetch;
|
|
2982
|
-
const
|
|
3839
|
+
const sleep3 = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
2983
3840
|
const reconnectMs = this.deps.reconnectMs ?? 500;
|
|
2984
3841
|
const maxBoot = this.deps.maxBootAttempts ?? 60;
|
|
2985
3842
|
const url = `http://127.0.0.1:${port}/event`;
|
|
@@ -3007,7 +3864,7 @@ var OpencodeSseBridge = class {
|
|
|
3007
3864
|
return;
|
|
3008
3865
|
}
|
|
3009
3866
|
}
|
|
3010
|
-
await
|
|
3867
|
+
await sleep3(reconnectMs);
|
|
3011
3868
|
}
|
|
3012
3869
|
}
|
|
3013
3870
|
this.controllers.delete(taskId);
|
|
@@ -3243,7 +4100,7 @@ function runHeadless(opts) {
|
|
|
3243
4100
|
}
|
|
3244
4101
|
|
|
3245
4102
|
// packages/workspaces/dist/runtimes/cmux.js
|
|
3246
|
-
import { execFile, execFileSync as execFileSync4 } from "child_process";
|
|
4103
|
+
import { execFile as execFile2, execFileSync as execFileSync4 } from "child_process";
|
|
3247
4104
|
var CMUX_TIMEOUT = 15e3;
|
|
3248
4105
|
var CmuxTimeoutError = class extends Error {
|
|
3249
4106
|
constructor(cmd) {
|
|
@@ -3253,7 +4110,7 @@ var CmuxTimeoutError = class extends Error {
|
|
|
3253
4110
|
};
|
|
3254
4111
|
function cmux(args) {
|
|
3255
4112
|
return new Promise((resolve2, reject) => {
|
|
3256
|
-
|
|
4113
|
+
execFile2(
|
|
3257
4114
|
resolveCmuxBin(),
|
|
3258
4115
|
args,
|
|
3259
4116
|
// CMUX_QUIET=1 silences cmux 0.64's one-time deprecation hints (e.g. the
|
|
@@ -3615,9 +4472,9 @@ function createCmuxDriver() {
|
|
|
3615
4472
|
import { execFileSync as execFileSync5, execSync as execSync7 } from "child_process";
|
|
3616
4473
|
|
|
3617
4474
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
3618
|
-
import
|
|
4475
|
+
import fs12 from "fs/promises";
|
|
3619
4476
|
import { existsSync as existsSync8 } from "fs";
|
|
3620
|
-
import
|
|
4477
|
+
import path15 from "path";
|
|
3621
4478
|
|
|
3622
4479
|
// packages/workspaces/dist/cmux/events-bridge.js
|
|
3623
4480
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
@@ -3661,7 +4518,7 @@ var CmuxEventsBridge = class {
|
|
|
3661
4518
|
async run() {
|
|
3662
4519
|
const spawnImpl = this.deps.spawnImpl ?? ((bin2, args2) => nodeSpawn2(bin2, args2, { stdio: ["ignore", "pipe", "ignore"] }));
|
|
3663
4520
|
const bin = this.deps.cmuxBin ?? resolveCmuxBin();
|
|
3664
|
-
const
|
|
4521
|
+
const sleep3 = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
3665
4522
|
const reconnectMs = this.deps.reconnectMs ?? 1e3;
|
|
3666
4523
|
const args = [
|
|
3667
4524
|
"events",
|
|
@@ -3681,7 +4538,7 @@ var CmuxEventsBridge = class {
|
|
|
3681
4538
|
this.deps.log?.(`cmux events spawn failed: ${e.message}`);
|
|
3682
4539
|
if (this.deps.stopAfterFirstRun)
|
|
3683
4540
|
return;
|
|
3684
|
-
await
|
|
4541
|
+
await sleep3(reconnectMs);
|
|
3685
4542
|
continue;
|
|
3686
4543
|
}
|
|
3687
4544
|
this.child = child;
|
|
@@ -3704,7 +4561,7 @@ var CmuxEventsBridge = class {
|
|
|
3704
4561
|
this.child = null;
|
|
3705
4562
|
if (this.stopped || this.deps.stopAfterFirstRun)
|
|
3706
4563
|
break;
|
|
3707
|
-
await
|
|
4564
|
+
await sleep3(reconnectMs);
|
|
3708
4565
|
}
|
|
3709
4566
|
}
|
|
3710
4567
|
onData(chunk) {
|
|
@@ -3811,6 +4668,8 @@ import net from "net";
|
|
|
3811
4668
|
|
|
3812
4669
|
// packages/cli/src/control/squadrantd.ts
|
|
3813
4670
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
4671
|
+
var CLI_BIN = join13(dirname5(SELF_PATH2), "index.js");
|
|
4672
|
+
var DAEMON_SOCK = join13(homedir8(), ".config", "squadrant", "squadrant.sock");
|
|
3814
4673
|
function readPkgVersion() {
|
|
3815
4674
|
try {
|
|
3816
4675
|
const pkgPath = join13(dirname5(SELF_PATH2), "..", "package.json");
|
|
@@ -3820,6 +4679,31 @@ function readPkgVersion() {
|
|
|
3820
4679
|
}
|
|
3821
4680
|
}
|
|
3822
4681
|
var PKG_VERSION = readPkgVersion();
|
|
4682
|
+
function buildTelegramBridge(cfg, stateRoot, log) {
|
|
4683
|
+
const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
4684
|
+
if (!token) {
|
|
4685
|
+
log("telegram: config present but no botToken / TELEGRAM_BOT_TOKEN set \u2014 bridge disabled");
|
|
4686
|
+
return void 0;
|
|
4687
|
+
}
|
|
4688
|
+
const client = createTelegramClient({ token });
|
|
4689
|
+
const ensureCaptainAlive = createEnsureCaptainAlive({
|
|
4690
|
+
isAlive: createIsCaptainAlive(DAEMON_SOCK),
|
|
4691
|
+
launch: createLaunch(CLI_BIN)
|
|
4692
|
+
});
|
|
4693
|
+
const runCommand = createRunCommand(CLI_BIN);
|
|
4694
|
+
const sendReply = (threadId, text, replyMarkup) => client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);
|
|
4695
|
+
return createTelegramBridge({
|
|
4696
|
+
cfg,
|
|
4697
|
+
stateRoot,
|
|
4698
|
+
configRoot: dirname5(stateRoot),
|
|
4699
|
+
client,
|
|
4700
|
+
appendCaptainMessage,
|
|
4701
|
+
log,
|
|
4702
|
+
ensureCaptainAlive,
|
|
4703
|
+
runCommand,
|
|
4704
|
+
sendReply
|
|
4705
|
+
});
|
|
4706
|
+
}
|
|
3823
4707
|
function startSquadrantd(opts = {}) {
|
|
3824
4708
|
const ctx = buildContext(opts);
|
|
3825
4709
|
const { stateRoot, store, log, spawn: spawn2, writeResult, inFlightHeadlessIds, activeHeadlessKills } = ctx;
|
|
@@ -3876,6 +4760,8 @@ function startSquadrantd(opts = {}) {
|
|
|
3876
4760
|
ctx.codexDriver = codexDriver;
|
|
3877
4761
|
ctx.opencodeBridge = opencodeBridge;
|
|
3878
4762
|
ctx.cmuxEventsBridge = cmuxEventsBridge;
|
|
4763
|
+
const tgCfg = loadConfig().telegram;
|
|
4764
|
+
ctx.telegramBridge = opts.telegramBridge ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(tgCfg, stateRoot, log) : void 0);
|
|
3879
4765
|
ctx.daemonCmux = opts.daemonCmux ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();
|
|
3880
4766
|
const launchHeadless = opts.launchHeadless ?? (async (rec) => {
|
|
3881
4767
|
const ingest = (e) => void ctx.d.handle({ kind: "event", project: rec.project, event: e });
|