squadrant 0.10.0 → 0.11.1
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 +21 -1
- package/dist/index.js +758 -334
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +487 -76
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- 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,9 +364,9 @@ 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
|
}
|
|
@@ -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");
|
|
@@ -939,12 +981,12 @@ function withProjectLock(project, fn) {
|
|
|
939
981
|
function appendEntry(stateRoot, project, build) {
|
|
940
982
|
return withProjectLock(project, async () => {
|
|
941
983
|
const dir = inboxDir(stateRoot);
|
|
942
|
-
await
|
|
984
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
943
985
|
const file = logPath(stateRoot, project);
|
|
944
986
|
const lastSeq = await readMaxSeq(stateRoot, project);
|
|
945
987
|
const seq = lastSeq + 1;
|
|
946
988
|
const entry = build(seq);
|
|
947
|
-
await
|
|
989
|
+
await fs7.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
|
|
948
990
|
return seq;
|
|
949
991
|
});
|
|
950
992
|
}
|
|
@@ -975,7 +1017,7 @@ function cursorPath(stateRoot, project, subscriber) {
|
|
|
975
1017
|
async function readCursor(opts) {
|
|
976
1018
|
let buf;
|
|
977
1019
|
try {
|
|
978
|
-
buf = await
|
|
1020
|
+
buf = await fs7.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
|
|
979
1021
|
} catch (e) {
|
|
980
1022
|
if (e.code === "ENOENT")
|
|
981
1023
|
return null;
|
|
@@ -990,7 +1032,7 @@ async function readCursor(opts) {
|
|
|
990
1032
|
}
|
|
991
1033
|
}
|
|
992
1034
|
async function writeCursor(opts) {
|
|
993
|
-
await
|
|
1035
|
+
await fs7.mkdir(inboxDir(opts.stateRoot), { recursive: true });
|
|
994
1036
|
const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);
|
|
995
1037
|
const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;
|
|
996
1038
|
const data = {
|
|
@@ -998,7 +1040,7 @@ async function writeCursor(opts) {
|
|
|
998
1040
|
subscriber: opts.subscriber,
|
|
999
1041
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1000
1042
|
};
|
|
1001
|
-
const handle = await
|
|
1043
|
+
const handle = await fs7.open(tmp, "w");
|
|
1002
1044
|
try {
|
|
1003
1045
|
await handle.writeFile(JSON.stringify(data), { encoding: "utf-8" });
|
|
1004
1046
|
await handle.sync();
|
|
@@ -1006,9 +1048,9 @@ async function writeCursor(opts) {
|
|
|
1006
1048
|
await handle.close();
|
|
1007
1049
|
}
|
|
1008
1050
|
try {
|
|
1009
|
-
await
|
|
1051
|
+
await fs7.rename(tmp, dest);
|
|
1010
1052
|
} catch (e) {
|
|
1011
|
-
await
|
|
1053
|
+
await fs7.unlink(tmp).catch(() => {
|
|
1012
1054
|
});
|
|
1013
1055
|
throw e;
|
|
1014
1056
|
}
|
|
@@ -1019,7 +1061,7 @@ async function* readFromCursor(opts) {
|
|
|
1019
1061
|
for (const file of files) {
|
|
1020
1062
|
let buf;
|
|
1021
1063
|
try {
|
|
1022
|
-
buf = await
|
|
1064
|
+
buf = await fs7.readFile(file, "utf-8");
|
|
1023
1065
|
} catch (e) {
|
|
1024
1066
|
if (e.code === "ENOENT")
|
|
1025
1067
|
continue;
|
|
@@ -1043,7 +1085,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
1043
1085
|
const file = logPath(stateRoot, project);
|
|
1044
1086
|
let sizeBytes = 0;
|
|
1045
1087
|
try {
|
|
1046
|
-
sizeBytes = (await
|
|
1088
|
+
sizeBytes = (await fs7.stat(file)).size;
|
|
1047
1089
|
} catch (e) {
|
|
1048
1090
|
if (e.code !== "ENOENT")
|
|
1049
1091
|
throw e;
|
|
@@ -1058,7 +1100,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
1058
1100
|
}
|
|
1059
1101
|
async function oldestEntryAgeMs(file) {
|
|
1060
1102
|
try {
|
|
1061
|
-
const buf = await
|
|
1103
|
+
const buf = await fs7.readFile(file, "utf-8");
|
|
1062
1104
|
const firstLine = buf.split("\n").find((l) => l.trim());
|
|
1063
1105
|
if (!firstLine)
|
|
1064
1106
|
return 0;
|
|
@@ -1073,7 +1115,7 @@ async function rotateIfNeeded(opts) {
|
|
|
1073
1115
|
const file = logPath(opts.stateRoot, opts.project);
|
|
1074
1116
|
let size = 0;
|
|
1075
1117
|
try {
|
|
1076
|
-
size = (await
|
|
1118
|
+
size = (await fs7.stat(file)).size;
|
|
1077
1119
|
} catch (e) {
|
|
1078
1120
|
if (e.code === "ENOENT")
|
|
1079
1121
|
return { rotated: false };
|
|
@@ -1089,22 +1131,22 @@ async function rotateIfNeeded(opts) {
|
|
|
1089
1131
|
const dst = `${file}.${n + 1}`;
|
|
1090
1132
|
if (n + 1 > opts.keepCount) {
|
|
1091
1133
|
try {
|
|
1092
|
-
await
|
|
1134
|
+
await fs7.unlink(src);
|
|
1093
1135
|
} catch (e) {
|
|
1094
1136
|
if (e.code !== "ENOENT")
|
|
1095
1137
|
throw e;
|
|
1096
1138
|
}
|
|
1097
1139
|
} else {
|
|
1098
1140
|
try {
|
|
1099
|
-
await
|
|
1141
|
+
await fs7.rename(src, dst);
|
|
1100
1142
|
} catch (e) {
|
|
1101
1143
|
if (e.code !== "ENOENT")
|
|
1102
1144
|
throw e;
|
|
1103
1145
|
}
|
|
1104
1146
|
}
|
|
1105
1147
|
}
|
|
1106
|
-
await
|
|
1107
|
-
await
|
|
1148
|
+
await fs7.rename(file, `${file}.1`);
|
|
1149
|
+
await fs7.writeFile(file, "", { encoding: "utf-8" });
|
|
1108
1150
|
return { rotated: true, from: file, to: `${file}.1` };
|
|
1109
1151
|
});
|
|
1110
1152
|
}
|
|
@@ -2078,10 +2120,10 @@ function distBuiltAt() {
|
|
|
2078
2120
|
return 0;
|
|
2079
2121
|
}
|
|
2080
2122
|
}
|
|
2081
|
-
function gatherLogStats(
|
|
2123
|
+
function gatherLogStats(path16, now, windowMs) {
|
|
2082
2124
|
let sizeBytes = 0;
|
|
2083
2125
|
try {
|
|
2084
|
-
sizeBytes = statSync2(
|
|
2126
|
+
sizeBytes = statSync2(path16).size;
|
|
2085
2127
|
} catch {
|
|
2086
2128
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
2087
2129
|
}
|
|
@@ -2092,7 +2134,7 @@ function gatherLogStats(path14, now, windowMs) {
|
|
|
2092
2134
|
const len = sizeBytes - start;
|
|
2093
2135
|
let text = "";
|
|
2094
2136
|
try {
|
|
2095
|
-
const fd = openSync2(
|
|
2137
|
+
const fd = openSync2(path16, "r");
|
|
2096
2138
|
try {
|
|
2097
2139
|
const buf = Buffer.alloc(len);
|
|
2098
2140
|
readSync(fd, buf, 0, len, start);
|
|
@@ -2417,8 +2459,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2417
2459
|
|
|
2418
2460
|
// packages/core/dist/session-freshness.js
|
|
2419
2461
|
import crypto from "crypto";
|
|
2420
|
-
import
|
|
2421
|
-
import
|
|
2462
|
+
import fs8 from "fs";
|
|
2463
|
+
import path7 from "path";
|
|
2422
2464
|
|
|
2423
2465
|
// packages/core/dist/crew-lifecycle.js
|
|
2424
2466
|
import { exec as nodeExec } from "child_process";
|
|
@@ -2498,19 +2540,30 @@ var REGISTRY = {
|
|
|
2498
2540
|
return usage("spawn", "usage: /spawn <project> <task...>");
|
|
2499
2541
|
return ok("spawn", ["crew", "spawn", project, task]);
|
|
2500
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>")
|
|
2501
2551
|
}
|
|
2502
2552
|
};
|
|
2503
2553
|
function helpText() {
|
|
2504
2554
|
const lines = Object.values(REGISTRY).map((e) => ` ${e.usage}`);
|
|
2505
2555
|
return ["Available commands:", ...lines, " /help"].join("\n");
|
|
2506
2556
|
}
|
|
2557
|
+
function stripBotMention(token) {
|
|
2558
|
+
return token.split("@")[0];
|
|
2559
|
+
}
|
|
2507
2560
|
function parseCommand(text) {
|
|
2508
2561
|
const trimmed = text.trim();
|
|
2509
2562
|
if (!trimmed.startsWith("/")) {
|
|
2510
2563
|
return { kind: "unknown", message: "unknown command \u2014 send /help" };
|
|
2511
2564
|
}
|
|
2512
2565
|
const tokens = trimmed.slice(1).split(/\s+/).filter((t) => t.length > 0);
|
|
2513
|
-
const name = (tokens[0] ?? "").toLowerCase();
|
|
2566
|
+
const name = stripBotMention(tokens[0] ?? "").toLowerCase();
|
|
2514
2567
|
const args = tokens.slice(1);
|
|
2515
2568
|
if (name === "help") {
|
|
2516
2569
|
return { kind: "usage", name: "help", message: helpText() };
|
|
@@ -2568,6 +2621,17 @@ ${ev.message}` : "");
|
|
|
2568
2621
|
${ev.question}`;
|
|
2569
2622
|
case "task.idle":
|
|
2570
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}`;
|
|
2571
2635
|
default:
|
|
2572
2636
|
return `\u2139\uFE0F [${project}] ${ev.type} \xB7 ${ev.id}`;
|
|
2573
2637
|
}
|
|
@@ -2577,35 +2641,49 @@ function formatInbound(text) {
|
|
|
2577
2641
|
}
|
|
2578
2642
|
|
|
2579
2643
|
// packages/core/dist/telegram/state.js
|
|
2580
|
-
import
|
|
2581
|
-
import
|
|
2644
|
+
import fs9 from "fs";
|
|
2645
|
+
import path8 from "path";
|
|
2582
2646
|
function statePath(stateRoot) {
|
|
2583
|
-
return
|
|
2647
|
+
return path8.join(stateRoot, "telegram-state.json");
|
|
2584
2648
|
}
|
|
2585
2649
|
function topicKey(project, scope = "project") {
|
|
2586
2650
|
return `${project}::${scope}`;
|
|
2587
2651
|
}
|
|
2588
2652
|
function loadState(stateRoot) {
|
|
2589
2653
|
try {
|
|
2590
|
-
const raw =
|
|
2654
|
+
const raw = fs9.readFileSync(statePath(stateRoot), "utf-8");
|
|
2591
2655
|
const data = JSON.parse(raw);
|
|
2592
|
-
|
|
2656
|
+
const result = {
|
|
2593
2657
|
offset: typeof data.offset === "number" ? data.offset : 0,
|
|
2594
|
-
topics: data.topics ?? {}
|
|
2658
|
+
topics: data.topics ?? {},
|
|
2659
|
+
notify: data.notify ?? {}
|
|
2595
2660
|
};
|
|
2661
|
+
if (typeof data.lastUserId === "number")
|
|
2662
|
+
result.lastUserId = data.lastUserId;
|
|
2663
|
+
return result;
|
|
2596
2664
|
} catch {
|
|
2597
|
-
return { offset: 0, topics: {} };
|
|
2665
|
+
return { offset: 0, topics: {}, notify: {} };
|
|
2598
2666
|
}
|
|
2599
2667
|
}
|
|
2600
2668
|
function saveState(stateRoot, s) {
|
|
2601
|
-
|
|
2602
|
-
|
|
2669
|
+
fs9.mkdirSync(stateRoot, { recursive: true });
|
|
2670
|
+
fs9.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
|
|
2603
2671
|
}
|
|
2604
2672
|
function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
2605
2673
|
const s = loadState(stateRoot);
|
|
2606
2674
|
s.topics[topicKey(project, scope)] = topicId;
|
|
2607
2675
|
saveState(stateRoot, s);
|
|
2608
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
|
+
}
|
|
2609
2687
|
function findProjectByThread(stateRoot, threadId) {
|
|
2610
2688
|
const s = loadState(stateRoot);
|
|
2611
2689
|
for (const [key, id] of Object.entries(s.topics)) {
|
|
@@ -2645,24 +2723,141 @@ function createTelegramClient(opts) {
|
|
|
2645
2723
|
getUpdates(offset, timeoutSec = 50) {
|
|
2646
2724
|
return call("getUpdates", { offset, timeout: timeoutSec });
|
|
2647
2725
|
},
|
|
2648
|
-
async sendMessage(chatId, threadId, text) {
|
|
2726
|
+
async sendMessage(chatId, threadId, text, replyMarkup) {
|
|
2649
2727
|
const body = { chat_id: chatId, text };
|
|
2650
2728
|
if (threadId !== void 0)
|
|
2651
2729
|
body.message_thread_id = threadId;
|
|
2730
|
+
if (replyMarkup !== void 0)
|
|
2731
|
+
body.reply_markup = replyMarkup;
|
|
2652
2732
|
await call("sendMessage", body);
|
|
2653
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
|
+
},
|
|
2654
2743
|
async createForumTopic(chatId, name) {
|
|
2655
2744
|
const r = await call("createForumTopic", { chat_id: chatId, name });
|
|
2656
2745
|
return r.message_thread_id;
|
|
2746
|
+
},
|
|
2747
|
+
async setMyCommands(commands) {
|
|
2748
|
+
await call("setMyCommands", { commands });
|
|
2657
2749
|
}
|
|
2658
2750
|
};
|
|
2659
2751
|
}
|
|
2660
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
|
+
|
|
2661
2829
|
// packages/core/dist/telegram/bridge.js
|
|
2662
2830
|
var LONG_POLL_SEC = 50;
|
|
2663
2831
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2832
|
+
var CREW_TIERS = ["all", "alert_only", "done_only", "none"];
|
|
2833
|
+
var RECOGNIZED_CHANNEL_COMMANDS = /* @__PURE__ */ new Set(["status", "projects", "crews", "launch", "effort", "spawn"]);
|
|
2834
|
+
function parseNotifyPref(text) {
|
|
2835
|
+
const parts = text.trim().split(/\s+/);
|
|
2836
|
+
if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
|
|
2837
|
+
return null;
|
|
2838
|
+
const dimension = parts[1]?.toLowerCase();
|
|
2839
|
+
if ((dimension === "crew" || dimension === "cap") && parts[2])
|
|
2840
|
+
return { dimension, value: parts[2].toLowerCase() };
|
|
2841
|
+
return null;
|
|
2842
|
+
}
|
|
2843
|
+
function isBareSpawn(text) {
|
|
2844
|
+
const trimmed = text.trim();
|
|
2845
|
+
if (!trimmed.startsWith("/"))
|
|
2846
|
+
return false;
|
|
2847
|
+
const tokens = trimmed.slice(1).split(/\s+/).filter((t) => t.length > 0);
|
|
2848
|
+
return stripBotMention(tokens[0] ?? "").toLowerCase() === "spawn" && tokens.length === 1;
|
|
2849
|
+
}
|
|
2850
|
+
function notifyToggle(text) {
|
|
2851
|
+
const first = stripBotMention(text.trim().split(/\s+/)[0] ?? "").toLowerCase();
|
|
2852
|
+
if (first === "/unmute")
|
|
2853
|
+
return true;
|
|
2854
|
+
if (first === "/mute")
|
|
2855
|
+
return false;
|
|
2856
|
+
return null;
|
|
2857
|
+
}
|
|
2664
2858
|
function createTelegramBridge(opts) {
|
|
2665
2859
|
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
2860
|
+
const configRoot = opts.configRoot ?? path9.join(os3.homedir(), ".config", "squadrant");
|
|
2666
2861
|
const pollMs = cfg.pollMs ?? 1e3;
|
|
2667
2862
|
let running = false;
|
|
2668
2863
|
function persistOffset(next) {
|
|
@@ -2671,6 +2866,13 @@ function createTelegramBridge(opts) {
|
|
|
2671
2866
|
saveState(stateRoot, s);
|
|
2672
2867
|
}
|
|
2673
2868
|
async function deliverOutbound(project, ev) {
|
|
2869
|
+
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
2870
|
+
const live = loadState(stateRoot).notify[project];
|
|
2871
|
+
const active = live ?? resolved.active;
|
|
2872
|
+
if (!active)
|
|
2873
|
+
return;
|
|
2874
|
+
if (!tierIncludes(resolved.crew, ev.type))
|
|
2875
|
+
return;
|
|
2674
2876
|
let threadId = loadState(stateRoot).topics[topicKey(project)];
|
|
2675
2877
|
if (threadId === void 0) {
|
|
2676
2878
|
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
@@ -2678,41 +2880,225 @@ function createTelegramBridge(opts) {
|
|
|
2678
2880
|
}
|
|
2679
2881
|
await client.sendMessage(cfg.supergroupId, threadId, formatLifecycle(project, ev));
|
|
2680
2882
|
}
|
|
2681
|
-
|
|
2883
|
+
function resolveLiveNotify(project) {
|
|
2884
|
+
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
2885
|
+
const live = loadState(stateRoot).notify[project];
|
|
2886
|
+
return { ...resolved, active: live ?? resolved.active };
|
|
2887
|
+
}
|
|
2888
|
+
async function editMarkup(chatId, messageId, markup) {
|
|
2889
|
+
try {
|
|
2890
|
+
await client.editMessageReplyMarkup(chatId, messageId, markup);
|
|
2891
|
+
} catch (e) {
|
|
2892
|
+
const msg = e.message;
|
|
2893
|
+
if (!/not modified/i.test(msg))
|
|
2894
|
+
throw e;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
async function handleCallback(cq) {
|
|
2898
|
+
try {
|
|
2899
|
+
if (!cq.data || !cq.message) {
|
|
2900
|
+
await client.answerCallbackQuery(cq.id);
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
if (!isControlEnabled(cfg) || !isAuthorized(cq.from?.id, cfg)) {
|
|
2904
|
+
await client.answerCallbackQuery(cq.id, "\u26D4 not authorized");
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
const action = parseCallback(cq.data);
|
|
2908
|
+
if (!action) {
|
|
2909
|
+
await client.answerCallbackQuery(cq.id);
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
const chatId = cq.message.chat.id;
|
|
2913
|
+
const messageId = cq.message.message_id;
|
|
2914
|
+
if (action.t === "notify") {
|
|
2915
|
+
const resolved = findProjectByThread(stateRoot, cq.message.message_thread_id ?? -1);
|
|
2916
|
+
if (!resolved) {
|
|
2917
|
+
await client.answerCallbackQuery(cq.id, "no project for this topic");
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
const project2 = resolved.project;
|
|
2921
|
+
if (action.dim === "active") {
|
|
2922
|
+
setNotify(stateRoot, project2, action.val === "on");
|
|
2923
|
+
} else if (action.dim === "cap") {
|
|
2924
|
+
saveProjectOverride(project2, { telegram: { notify: { cap: action.val === "on" } } }, configRoot);
|
|
2925
|
+
} else {
|
|
2926
|
+
saveProjectOverride(project2, { telegram: { notify: { crew: action.val } } }, configRoot);
|
|
2927
|
+
}
|
|
2928
|
+
await client.answerCallbackQuery(cq.id, `\u2705 ${action.dim} = ${action.val}`);
|
|
2929
|
+
await editMarkup(chatId, messageId, notifyPanel(resolveLiveNotify(project2)));
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
if (action.t === "effort") {
|
|
2933
|
+
if (runCommand)
|
|
2934
|
+
await runCommand(["effort", action.mode]);
|
|
2935
|
+
await client.answerCallbackQuery(cq.id, `\u2705 effort = ${action.mode}`);
|
|
2936
|
+
await editMarkup(chatId, messageId, effortPanel(action.mode));
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
if (action.t === "spawn") {
|
|
2940
|
+
await reply(cq.message.message_thread_id, buildSpawnPrompt(action.project), { force_reply: true, selective: true });
|
|
2941
|
+
await client.answerCallbackQuery(cq.id);
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
const { action: act, project } = action;
|
|
2945
|
+
if (act === "cr") {
|
|
2946
|
+
const out = runCommand ? await runCommand(["crew", "list", project]) : "(command runner unavailable)";
|
|
2947
|
+
await client.answerCallbackQuery(cq.id);
|
|
2948
|
+
await reply(void 0, out);
|
|
2949
|
+
} else if (act === "lc") {
|
|
2950
|
+
if (runCommand)
|
|
2951
|
+
await runCommand(["launch", project]);
|
|
2952
|
+
await client.answerCallbackQuery(cq.id, `launching ${project}`);
|
|
2953
|
+
} else if (act === "mu") {
|
|
2954
|
+
setNotify(stateRoot, project, false);
|
|
2955
|
+
await client.answerCallbackQuery(cq.id, `\u{1F515} muted ${project}`);
|
|
2956
|
+
} else {
|
|
2957
|
+
setNotify(stateRoot, project, true);
|
|
2958
|
+
await client.answerCallbackQuery(cq.id, `\u{1F514} unmuted ${project}`);
|
|
2959
|
+
}
|
|
2960
|
+
} catch (e) {
|
|
2961
|
+
log(`telegram callback failed data=${cq.data}: ${e.message}`);
|
|
2962
|
+
try {
|
|
2963
|
+
await client.answerCallbackQuery(cq.id, "\u26A0\uFE0F failed");
|
|
2964
|
+
} catch {
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
async function reply(threadId, text, replyMarkup) {
|
|
2682
2969
|
if (!sendReply)
|
|
2683
2970
|
return;
|
|
2684
2971
|
try {
|
|
2685
|
-
|
|
2972
|
+
if (replyMarkup !== void 0)
|
|
2973
|
+
await sendReply(threadId, text, replyMarkup);
|
|
2974
|
+
else
|
|
2975
|
+
await sendReply(threadId, text);
|
|
2686
2976
|
} catch (e) {
|
|
2687
2977
|
log(`telegram reply failed: ${e.message}`);
|
|
2688
2978
|
}
|
|
2689
2979
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2980
|
+
function currentEffort() {
|
|
2981
|
+
try {
|
|
2982
|
+
return loadConfig(path9.join(configRoot, "config.json")).defaults.effort ?? "balance";
|
|
2983
|
+
} catch {
|
|
2984
|
+
return "balance";
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
function projectNames() {
|
|
2988
|
+
try {
|
|
2989
|
+
return Object.keys(loadConfig(path9.join(configRoot, "config.json")).projects);
|
|
2990
|
+
} catch {
|
|
2991
|
+
return [];
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
async function replySpawnPicker(threadId) {
|
|
2995
|
+
const projects = projectNames();
|
|
2996
|
+
if (projects.length === 0) {
|
|
2997
|
+
await reply(threadId, "no projects registered");
|
|
2693
2998
|
return;
|
|
2694
2999
|
}
|
|
3000
|
+
await reply(threadId, "Pick a project to spawn a crew on:", spawnPicker(projects));
|
|
3001
|
+
}
|
|
3002
|
+
async function runChannelCommand(text, fromId, threadId) {
|
|
2695
3003
|
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
2696
|
-
await reply(
|
|
3004
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
const tokens = text.trim().slice(1).split(/\s+/).filter((t) => t.length > 0);
|
|
3008
|
+
const name = stripBotMention(tokens[0] ?? "").toLowerCase();
|
|
3009
|
+
const noArg = tokens.length === 1;
|
|
3010
|
+
if (noArg && name === "effort") {
|
|
3011
|
+
await reply(threadId, "Effort mode:", effortPanel(currentEffort()));
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
if (noArg && name === "spawn") {
|
|
3015
|
+
await replySpawnPicker(threadId);
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
const PICKERS = { crews: "cr", launch: "lc", mute: "mu", unmute: "um" };
|
|
3019
|
+
if (noArg && name in PICKERS) {
|
|
3020
|
+
const projects = projectNames();
|
|
3021
|
+
if (projects.length === 0) {
|
|
3022
|
+
await reply(threadId, "no projects registered");
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
await reply(threadId, `Pick a project:`, projectPicker(PICKERS[name], projects));
|
|
2697
3026
|
return;
|
|
2698
3027
|
}
|
|
2699
3028
|
const parsed = parseCommand(text);
|
|
2700
3029
|
if (parsed.kind !== "ok") {
|
|
2701
|
-
await reply(
|
|
3030
|
+
await reply(threadId, parsed.message);
|
|
2702
3031
|
return;
|
|
2703
3032
|
}
|
|
2704
3033
|
try {
|
|
2705
3034
|
const out = runCommand ? await runCommand(parsed.argv) : "(command runner unavailable)";
|
|
2706
|
-
await reply(
|
|
3035
|
+
await reply(threadId, out);
|
|
2707
3036
|
} catch (e) {
|
|
2708
|
-
await reply(
|
|
3037
|
+
await reply(threadId, `\u26A0\uFE0F command failed: ${e.message}`);
|
|
2709
3038
|
log(`telegram command failed argv=${JSON.stringify(parsed.argv)}: ${e.message}`);
|
|
2710
3039
|
}
|
|
2711
3040
|
}
|
|
3041
|
+
async function handleGeneral(text, fromId) {
|
|
3042
|
+
if (!text.startsWith("/")) {
|
|
3043
|
+
await reply(void 0, "Send /help for commands.");
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
await runChannelCommand(text, fromId, void 0);
|
|
3047
|
+
}
|
|
2712
3048
|
async function handleProjectTopic(text, threadId, fromId) {
|
|
2713
3049
|
const resolved = findProjectByThread(stateRoot, threadId);
|
|
2714
3050
|
if (!resolved)
|
|
2715
3051
|
return;
|
|
3052
|
+
if (isBareSpawn(text)) {
|
|
3053
|
+
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
3054
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
await replySpawnPicker(threadId);
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
const toggle = notifyToggle(text);
|
|
3061
|
+
if (toggle !== null) {
|
|
3062
|
+
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
3063
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
setNotify(stateRoot, resolved.project, toggle);
|
|
3067
|
+
await reply(threadId, toggle ? `\u{1F514} ${resolved.project} notifications ON` : `\u{1F515} ${resolved.project} notifications OFF`);
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
if (stripBotMention(text.trim().split(/\s+/)[0] ?? "").toLowerCase() === "/notify") {
|
|
3071
|
+
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
3072
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
const pref = parseNotifyPref(text);
|
|
3076
|
+
if (pref === null) {
|
|
3077
|
+
await reply(threadId, `\u{1F514} ${resolved.project} notifications`, notifyPanel(resolveLiveNotify(resolved.project)));
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
if (pref.dimension === "crew") {
|
|
3081
|
+
if (!CREW_TIERS.includes(pref.value)) {
|
|
3082
|
+
await reply(threadId, "crew must be all|alert_only|done_only|none");
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
saveProjectOverride(resolved.project, { telegram: { notify: { crew: pref.value } } }, configRoot);
|
|
3086
|
+
} else {
|
|
3087
|
+
if (pref.value !== "on" && pref.value !== "off") {
|
|
3088
|
+
await reply(threadId, "cap must be on|off");
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
saveProjectOverride(resolved.project, { telegram: { notify: { cap: pref.value === "on" } } }, configRoot);
|
|
3092
|
+
}
|
|
3093
|
+
await reply(threadId, `\u2705 ${pref.dimension} = ${pref.value}`);
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
const firstTok = stripBotMention(text.trim().split(/\s+/)[0] ?? "").toLowerCase();
|
|
3097
|
+
if (firstTok.startsWith("/") && RECOGNIZED_CHANNEL_COMMANDS.has(firstTok.slice(1))) {
|
|
3098
|
+
await runChannelCommand(text, fromId, threadId);
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
setNotify(stateRoot, resolved.project, true);
|
|
2716
3102
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
2717
3103
|
try {
|
|
2718
3104
|
const r = await ensureCaptainAlive(resolved.project);
|
|
@@ -2725,11 +3111,35 @@ function createTelegramBridge(opts) {
|
|
|
2725
3111
|
await appendCaptainMessage2({ stateRoot, project: resolved.project, text: formatInbound(text), source: "telegram" });
|
|
2726
3112
|
}
|
|
2727
3113
|
async function handleUpdate(u) {
|
|
3114
|
+
if (u.callback_query) {
|
|
3115
|
+
await handleCallback(u.callback_query);
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
2728
3118
|
const m = u.message;
|
|
2729
3119
|
if (!m || m.text === void 0)
|
|
2730
3120
|
return;
|
|
2731
3121
|
if (!cfg.chats.includes(m.chat.id))
|
|
2732
3122
|
return;
|
|
3123
|
+
if (m.from?.id !== void 0 && loadState(stateRoot).lastUserId !== m.from.id) {
|
|
3124
|
+
setLastUserId(stateRoot, m.from.id);
|
|
3125
|
+
}
|
|
3126
|
+
const spawnProject = parseSpawnPrompt(m.reply_to_message?.text);
|
|
3127
|
+
if (spawnProject) {
|
|
3128
|
+
const threadId = m.message_thread_id;
|
|
3129
|
+
if (!isControlEnabled(cfg) || !isAuthorized(m.from?.id, cfg)) {
|
|
3130
|
+
await reply(threadId, "\u26D4 not authorized");
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
const task = m.text.trim();
|
|
3134
|
+
if (!task) {
|
|
3135
|
+
await reply(threadId, "spawn cancelled \u2014 empty task");
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
if (runCommand)
|
|
3139
|
+
await runCommand(["crew", "spawn", spawnProject, task]);
|
|
3140
|
+
await reply(threadId, `\u{1F195} spawning a crew on ${spawnProject}\u2026`);
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
2733
3143
|
if (m.message_thread_id === void 0) {
|
|
2734
3144
|
await handleGeneral(m.text, m.from?.id);
|
|
2735
3145
|
return;
|
|
@@ -2771,7 +3181,7 @@ function createTelegramBridge(opts) {
|
|
|
2771
3181
|
}
|
|
2772
3182
|
|
|
2773
3183
|
// packages/core/dist/telegram/setup.js
|
|
2774
|
-
import
|
|
3184
|
+
import fs10 from "fs";
|
|
2775
3185
|
|
|
2776
3186
|
// packages/cli/src/control/telegram-control.ts
|
|
2777
3187
|
import { execFile } from "child_process";
|
|
@@ -2834,28 +3244,28 @@ import { execSync as execSync4 } from "child_process";
|
|
|
2834
3244
|
import { execSync as execSync5 } from "child_process";
|
|
2835
3245
|
|
|
2836
3246
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
2837
|
-
import
|
|
2838
|
-
import
|
|
3247
|
+
import fs11 from "fs";
|
|
3248
|
+
import path10 from "path";
|
|
2839
3249
|
|
|
2840
3250
|
// packages/agents/dist/projection/cursor.js
|
|
2841
3251
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2842
|
-
import
|
|
2843
|
-
import
|
|
3252
|
+
import path11 from "path";
|
|
3253
|
+
import os4 from "os";
|
|
2844
3254
|
|
|
2845
3255
|
// packages/agents/dist/projection/codex.js
|
|
2846
3256
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
2847
|
-
import
|
|
2848
|
-
import
|
|
3257
|
+
import path12 from "path";
|
|
3258
|
+
import os5 from "os";
|
|
2849
3259
|
|
|
2850
3260
|
// packages/agents/dist/projection/gemini.js
|
|
2851
3261
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
2852
|
-
import
|
|
2853
|
-
import
|
|
3262
|
+
import path13 from "path";
|
|
3263
|
+
import os6 from "os";
|
|
2854
3264
|
|
|
2855
3265
|
// packages/agents/dist/projection/opencode.js
|
|
2856
3266
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2857
|
-
import
|
|
2858
|
-
import
|
|
3267
|
+
import path14 from "path";
|
|
3268
|
+
import os7 from "os";
|
|
2859
3269
|
|
|
2860
3270
|
// packages/agents/dist/codex/app-server-client.js
|
|
2861
3271
|
import { EventEmitter } from "events";
|
|
@@ -4071,9 +4481,9 @@ function createCmuxDriver() {
|
|
|
4071
4481
|
import { execFileSync as execFileSync5, execSync as execSync7 } from "child_process";
|
|
4072
4482
|
|
|
4073
4483
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
4074
|
-
import
|
|
4484
|
+
import fs12 from "fs/promises";
|
|
4075
4485
|
import { existsSync as existsSync8 } from "fs";
|
|
4076
|
-
import
|
|
4486
|
+
import path15 from "path";
|
|
4077
4487
|
|
|
4078
4488
|
// packages/workspaces/dist/cmux/events-bridge.js
|
|
4079
4489
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
@@ -4290,10 +4700,11 @@ function buildTelegramBridge(cfg, stateRoot, log) {
|
|
|
4290
4700
|
launch: createLaunch(CLI_BIN)
|
|
4291
4701
|
});
|
|
4292
4702
|
const runCommand = createRunCommand(CLI_BIN);
|
|
4293
|
-
const sendReply = (threadId, text) => client.sendMessage(cfg.supergroupId, threadId, text);
|
|
4703
|
+
const sendReply = (threadId, text, replyMarkup) => client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);
|
|
4294
4704
|
return createTelegramBridge({
|
|
4295
4705
|
cfg,
|
|
4296
4706
|
stateRoot,
|
|
4707
|
+
configRoot: dirname5(stateRoot),
|
|
4297
4708
|
client,
|
|
4298
4709
|
appendCaptainMessage,
|
|
4299
4710
|
log,
|