squadrant 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1379 -323
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +783 -184
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/squadrantd.js
CHANGED
|
@@ -135,12 +135,7 @@ function getDefaultConfig() {
|
|
|
135
135
|
// resolver fallback is the safety net).
|
|
136
136
|
controlChannel: { claude: "on", opencode: "on" },
|
|
137
137
|
crewRouting: {
|
|
138
|
-
rules:
|
|
139
|
-
{ tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
|
|
140
|
-
{ tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
|
|
141
|
-
{ tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
|
|
142
|
-
{ tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
|
|
143
|
-
]
|
|
138
|
+
rules: DEFAULT_CREW_ROUTING_RULES.map((r) => ({ ...r }))
|
|
144
139
|
}
|
|
145
140
|
},
|
|
146
141
|
metrics: {
|
|
@@ -182,7 +177,7 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
182
177
|
function resolveHome(p) {
|
|
183
178
|
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
184
179
|
}
|
|
185
|
-
var GATE_MODES, GATE_POLICIES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH;
|
|
180
|
+
var GATE_MODES, GATE_POLICIES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
|
|
186
181
|
var init_config = __esm({
|
|
187
182
|
"packages/shared/dist/config.js"() {
|
|
188
183
|
init_config_io();
|
|
@@ -191,6 +186,19 @@ var init_config = __esm({
|
|
|
191
186
|
DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
|
|
192
187
|
CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
|
|
193
188
|
DAEMON_SOCK_PATH = path2.join(CONFIG_DIR, "squadrant.sock");
|
|
189
|
+
DEFAULT_CREW_ROUTING_RULES = [
|
|
190
|
+
{ tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
|
|
191
|
+
{ tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
|
|
192
|
+
{ tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
|
|
193
|
+
{ tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// packages/shared/dist/provider-preset.js
|
|
199
|
+
var init_provider_preset = __esm({
|
|
200
|
+
"packages/shared/dist/provider-preset.js"() {
|
|
201
|
+
init_config();
|
|
194
202
|
}
|
|
195
203
|
});
|
|
196
204
|
|
|
@@ -790,6 +798,7 @@ var init_daemon_keys = __esm({
|
|
|
790
798
|
var init_dist = __esm({
|
|
791
799
|
"packages/shared/dist/index.js"() {
|
|
792
800
|
init_config();
|
|
801
|
+
init_provider_preset();
|
|
793
802
|
init_project_config();
|
|
794
803
|
init_effort();
|
|
795
804
|
init_router_model();
|
|
@@ -2027,8 +2036,8 @@ function reconcileLiveness(prev, next) {
|
|
|
2027
2036
|
if (!prev)
|
|
2028
2037
|
return next;
|
|
2029
2038
|
if (next.source === "scan") {
|
|
2030
|
-
const
|
|
2031
|
-
return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
2039
|
+
const pidAlive2 = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;
|
|
2040
|
+
return { ...prev, pidAlive: pidAlive2, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
2032
2041
|
}
|
|
2033
2042
|
if (next.startedAt >= prev.startedAt || next.lastState === "end")
|
|
2034
2043
|
return next;
|
|
@@ -2774,6 +2783,68 @@ function createDirectSurfaceLivenessProbe(cmux2, getCaptainTitle) {
|
|
|
2774
2783
|
}
|
|
2775
2784
|
};
|
|
2776
2785
|
}
|
|
2786
|
+
function hasModalOptionList(screen) {
|
|
2787
|
+
if (!screen)
|
|
2788
|
+
return false;
|
|
2789
|
+
const lines = screen.split(/\r?\n/);
|
|
2790
|
+
let bottomHR = -1;
|
|
2791
|
+
let topHR = -1;
|
|
2792
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2793
|
+
if (HR_RE.test(lines[i])) {
|
|
2794
|
+
if (bottomHR === -1)
|
|
2795
|
+
bottomHR = i;
|
|
2796
|
+
else {
|
|
2797
|
+
topHR = i;
|
|
2798
|
+
break;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
if (topHR === -1)
|
|
2803
|
+
return false;
|
|
2804
|
+
return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
|
|
2805
|
+
}
|
|
2806
|
+
function classifyCaptainPane(screen, opts = {}) {
|
|
2807
|
+
if (screen == null || screen.trim() === "")
|
|
2808
|
+
return { status: "unreadable" };
|
|
2809
|
+
if (hasModalOptionList(screen))
|
|
2810
|
+
return { status: "locked" };
|
|
2811
|
+
if (WORKING_RE.test(screen))
|
|
2812
|
+
return { status: "mid-turn" };
|
|
2813
|
+
const idleAfterMs = opts.idleAfterMs ?? 0;
|
|
2814
|
+
const stale = opts.outputAgoMs !== void 0 && opts.outputAgoMs >= idleAfterMs;
|
|
2815
|
+
return stale ? { status: "no-session" } : { status: "mid-turn" };
|
|
2816
|
+
}
|
|
2817
|
+
function screenHash(screen) {
|
|
2818
|
+
let h = 2166136261;
|
|
2819
|
+
for (let i = 0; i < screen.length; i++) {
|
|
2820
|
+
h ^= screen.charCodeAt(i);
|
|
2821
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
2822
|
+
}
|
|
2823
|
+
return h.toString(16).padStart(8, "0");
|
|
2824
|
+
}
|
|
2825
|
+
function createCaptainPaneReader(cmux2, getCaptainTitle, opts = {}) {
|
|
2826
|
+
const idleAfterMs = opts.idleAfterMs ?? PANE_IDLE_AFTER_MS;
|
|
2827
|
+
return async (project) => {
|
|
2828
|
+
try {
|
|
2829
|
+
const wsId = await cmux2.findWorkspaceId(getCaptainTitle(project));
|
|
2830
|
+
if (!wsId)
|
|
2831
|
+
return { status: "no-session" };
|
|
2832
|
+
const surfaces = await cmux2.listSurfaces(wsId);
|
|
2833
|
+
const want = getCaptainTitle(project);
|
|
2834
|
+
const pane = surfaces.find((s) => s.title === want) ?? surfaces[0];
|
|
2835
|
+
if (!pane)
|
|
2836
|
+
return { status: "no-session" };
|
|
2837
|
+
const screen = await cmux2.readPaneScreen(pane);
|
|
2838
|
+
if (screen == null)
|
|
2839
|
+
return { status: "unreadable" };
|
|
2840
|
+
const outputAgoMs = opts.noteScreen?.(project, screenHash(screen));
|
|
2841
|
+
return classifyCaptainPane(screen, { outputAgoMs, idleAfterMs });
|
|
2842
|
+
} catch (e) {
|
|
2843
|
+
opts.log?.(`captain pane read failed project=${project}: ${e.message}`);
|
|
2844
|
+
return { status: "unreadable" };
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2777
2848
|
function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
|
|
2778
2849
|
return async (rec) => {
|
|
2779
2850
|
try {
|
|
@@ -2796,11 +2867,14 @@ function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
|
|
|
2796
2867
|
}
|
|
2797
2868
|
};
|
|
2798
2869
|
}
|
|
2799
|
-
var TAIL_LINES;
|
|
2870
|
+
var TAIL_LINES, HR_RE, WORKING_RE, PANE_IDLE_AFTER_MS;
|
|
2800
2871
|
var init_crew_pane_reader = __esm({
|
|
2801
2872
|
"packages/core/dist/crew-pane-reader.js"() {
|
|
2802
2873
|
init_dist();
|
|
2803
2874
|
TAIL_LINES = 25;
|
|
2875
|
+
HR_RE = /^\s*─{10,}\s*$/;
|
|
2876
|
+
WORKING_RE = /esc to interrupt|\btokens?\b.*\b(used|left)\b|↓\s*[\d.]+\s*k?\s*tokens?\b|\(\d+m?\s*\d+s\b/i;
|
|
2877
|
+
PANE_IDLE_AFTER_MS = 10 * 6e4;
|
|
2804
2878
|
}
|
|
2805
2879
|
});
|
|
2806
2880
|
|
|
@@ -4665,8 +4739,9 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4665
4739
|
if (mode !== "off")
|
|
4666
4740
|
throw new DeferDelivery(null, "no-channel");
|
|
4667
4741
|
}
|
|
4742
|
+
const paneOpts = { ...sendOpts, agent };
|
|
4668
4743
|
try {
|
|
4669
|
-
return await cmux2.send(surface, text,
|
|
4744
|
+
return await cmux2.send(surface, text, paneOpts);
|
|
4670
4745
|
} catch (e) {
|
|
4671
4746
|
if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
|
|
4672
4747
|
throw e;
|
|
@@ -4678,7 +4753,7 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4678
4753
|
}
|
|
4679
4754
|
log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
|
|
4680
4755
|
surface = next;
|
|
4681
|
-
return cmux2.send(next, text,
|
|
4756
|
+
return cmux2.send(next, text, paneOpts);
|
|
4682
4757
|
}
|
|
4683
4758
|
});
|
|
4684
4759
|
if ("delivered" in result) {
|
|
@@ -5609,16 +5684,26 @@ function createRunCommand(cliBin) {
|
|
|
5609
5684
|
}
|
|
5610
5685
|
};
|
|
5611
5686
|
}
|
|
5687
|
+
function captainAliveStateFromHealth(rows, project) {
|
|
5688
|
+
const row = rows.find((h) => h.kind === "captain" && h.project === project);
|
|
5689
|
+
if (!row)
|
|
5690
|
+
return "unknown";
|
|
5691
|
+
if (row.state === "alive")
|
|
5692
|
+
return "alive";
|
|
5693
|
+
if (row.state === "gone" || row.state === "stopped")
|
|
5694
|
+
return "dead";
|
|
5695
|
+
return "unknown";
|
|
5696
|
+
}
|
|
5612
5697
|
function isCaptainAliveFromHealth(rows, project) {
|
|
5613
|
-
return rows
|
|
5698
|
+
return captainAliveStateFromHealth(rows, project) === "alive";
|
|
5614
5699
|
}
|
|
5615
5700
|
function createIsCaptainAlive(sock) {
|
|
5616
5701
|
return async (project) => {
|
|
5617
5702
|
try {
|
|
5618
5703
|
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
5619
|
-
return
|
|
5704
|
+
return captainAliveStateFromHealth(health ?? [], project);
|
|
5620
5705
|
} catch {
|
|
5621
|
-
return
|
|
5706
|
+
return "unknown";
|
|
5622
5707
|
}
|
|
5623
5708
|
};
|
|
5624
5709
|
}
|
|
@@ -5655,16 +5740,19 @@ function createEnsureCaptainAlive(deps) {
|
|
|
5655
5740
|
const now = deps.now ?? (() => Date.now());
|
|
5656
5741
|
const inFlight = /* @__PURE__ */ new Map();
|
|
5657
5742
|
async function run(project) {
|
|
5658
|
-
|
|
5743
|
+
const first = await deps.isAlive(project);
|
|
5744
|
+
if (first === "alive")
|
|
5659
5745
|
return "alive";
|
|
5660
5746
|
await deps.launch(project);
|
|
5661
5747
|
const deadline = now() + warmupTimeoutMs;
|
|
5748
|
+
let last = first;
|
|
5662
5749
|
while (now() < deadline) {
|
|
5663
|
-
|
|
5750
|
+
last = await deps.isAlive(project);
|
|
5751
|
+
if (last === "alive")
|
|
5664
5752
|
return "launched";
|
|
5665
5753
|
await sleep3(pollMs);
|
|
5666
5754
|
}
|
|
5667
|
-
return "timeout";
|
|
5755
|
+
return last === "dead" ? "timeout" : "unknown";
|
|
5668
5756
|
}
|
|
5669
5757
|
return function ensure(project) {
|
|
5670
5758
|
const existing = inFlight.get(project);
|
|
@@ -5719,6 +5807,26 @@ ${ev.question}`;
|
|
|
5719
5807
|
function formatInbound(text) {
|
|
5720
5808
|
return `\u{1F4E9} [from Telegram] ${text}`;
|
|
5721
5809
|
}
|
|
5810
|
+
function mediaKind(m) {
|
|
5811
|
+
for (const [field, name] of MEDIA_KINDS) {
|
|
5812
|
+
if (m[field] !== void 0)
|
|
5813
|
+
return name;
|
|
5814
|
+
}
|
|
5815
|
+
return void 0;
|
|
5816
|
+
}
|
|
5817
|
+
function mediaMarker(kind) {
|
|
5818
|
+
return `[${kind} attached - not forwarded]`;
|
|
5819
|
+
}
|
|
5820
|
+
function inboundBody(text, kind) {
|
|
5821
|
+
if (kind === void 0)
|
|
5822
|
+
return text ?? "";
|
|
5823
|
+
const marker = mediaMarker(kind);
|
|
5824
|
+
return text ? `${text}
|
|
5825
|
+
${marker}` : marker;
|
|
5826
|
+
}
|
|
5827
|
+
function formatMediaReceipt(kind, hasCaption) {
|
|
5828
|
+
return hasCaption ? `\u{1F4CE} ${kind} not forwarded \u2014 your caption reached the captain` : `\u{1F4CE} ${kind} not forwarded \u2014 the captain was told it arrived, nothing else was sent`;
|
|
5829
|
+
}
|
|
5722
5830
|
function formatUsageLine(u) {
|
|
5723
5831
|
const reqs = u.requests === 1 ? "1 req" : `${u.requests} reqs`;
|
|
5724
5832
|
const named = Object.keys(u.models).filter((m) => m !== "unknown").sort((a, b) => u.models[b].costUsd - u.models[a].costUsd);
|
|
@@ -5730,8 +5838,19 @@ function maskToken(token) {
|
|
|
5730
5838
|
return token;
|
|
5731
5839
|
return "*".repeat(token.length - 4) + token.slice(-4);
|
|
5732
5840
|
}
|
|
5841
|
+
var MEDIA_KINDS;
|
|
5733
5842
|
var init_format = __esm({
|
|
5734
5843
|
"packages/core/dist/telegram/format.js"() {
|
|
5844
|
+
MEDIA_KINDS = [
|
|
5845
|
+
["photo", "photo"],
|
|
5846
|
+
["voice", "voice message"],
|
|
5847
|
+
["video_note", "video message"],
|
|
5848
|
+
["video", "video"],
|
|
5849
|
+
["audio", "audio file"],
|
|
5850
|
+
["document", "document"],
|
|
5851
|
+
["animation", "animation"],
|
|
5852
|
+
["sticker", "sticker"]
|
|
5853
|
+
];
|
|
5735
5854
|
}
|
|
5736
5855
|
});
|
|
5737
5856
|
|
|
@@ -5754,6 +5873,8 @@ function loadState(stateRoot) {
|
|
|
5754
5873
|
};
|
|
5755
5874
|
if (typeof data.lastUserId === "number")
|
|
5756
5875
|
result.lastUserId = data.lastUserId;
|
|
5876
|
+
if (data.pending && Object.keys(data.pending).length > 0)
|
|
5877
|
+
result.pending = data.pending;
|
|
5757
5878
|
return result;
|
|
5758
5879
|
} catch {
|
|
5759
5880
|
return { offset: 0, topics: {}, notify: {} };
|
|
@@ -5767,6 +5888,19 @@ function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
|
5767
5888
|
s.topics[topicKey(project, scope)] = topicId;
|
|
5768
5889
|
saveState(stateRoot, s);
|
|
5769
5890
|
}
|
|
5891
|
+
function pruneTopics(stateRoot, keep) {
|
|
5892
|
+
const s = loadState(stateRoot);
|
|
5893
|
+
const removed = Object.keys(s.topics).filter((key) => {
|
|
5894
|
+
const sep3 = key.indexOf("::");
|
|
5895
|
+
return !keep(sep3 === -1 ? key : key.slice(0, sep3));
|
|
5896
|
+
});
|
|
5897
|
+
if (removed.length === 0)
|
|
5898
|
+
return [];
|
|
5899
|
+
for (const key of removed)
|
|
5900
|
+
delete s.topics[key];
|
|
5901
|
+
saveState(stateRoot, s);
|
|
5902
|
+
return removed;
|
|
5903
|
+
}
|
|
5770
5904
|
function isNotifyActive(stateRoot, project) {
|
|
5771
5905
|
return loadState(stateRoot).notify[project] === true;
|
|
5772
5906
|
}
|
|
@@ -5780,6 +5914,42 @@ function setNotify(stateRoot, project, active) {
|
|
|
5780
5914
|
s.notify[project] = active;
|
|
5781
5915
|
saveState(stateRoot, s);
|
|
5782
5916
|
}
|
|
5917
|
+
function loadPending(stateRoot) {
|
|
5918
|
+
return loadState(stateRoot).pending ?? {};
|
|
5919
|
+
}
|
|
5920
|
+
function setPending(stateRoot, project, p) {
|
|
5921
|
+
const s = loadState(stateRoot);
|
|
5922
|
+
s.pending = { ...s.pending ?? {}, [project]: p };
|
|
5923
|
+
saveState(stateRoot, s);
|
|
5924
|
+
}
|
|
5925
|
+
function clearPending(stateRoot, project) {
|
|
5926
|
+
const s = loadState(stateRoot);
|
|
5927
|
+
if (!s.pending || !(project in s.pending))
|
|
5928
|
+
return;
|
|
5929
|
+
delete s.pending[project];
|
|
5930
|
+
if (Object.keys(s.pending).length === 0)
|
|
5931
|
+
delete s.pending;
|
|
5932
|
+
saveState(stateRoot, s);
|
|
5933
|
+
}
|
|
5934
|
+
function markPendingWarned(stateRoot, project, at) {
|
|
5935
|
+
const s = loadState(stateRoot);
|
|
5936
|
+
const p = s.pending?.[project];
|
|
5937
|
+
if (!p)
|
|
5938
|
+
return;
|
|
5939
|
+
s.pending[project] = { ...p, warnedAt: at };
|
|
5940
|
+
saveState(stateRoot, s);
|
|
5941
|
+
}
|
|
5942
|
+
function notePaneScreen(stateRoot, project, hash, at) {
|
|
5943
|
+
const s = loadState(stateRoot);
|
|
5944
|
+
const p = s.pending?.[project];
|
|
5945
|
+
if (!p)
|
|
5946
|
+
return void 0;
|
|
5947
|
+
if (p.paneHash === hash && p.paneChangedAt !== void 0)
|
|
5948
|
+
return at - p.paneChangedAt;
|
|
5949
|
+
s.pending[project] = { ...p, paneHash: hash, paneChangedAt: at };
|
|
5950
|
+
saveState(stateRoot, s);
|
|
5951
|
+
return void 0;
|
|
5952
|
+
}
|
|
5783
5953
|
function findProjectByThread(stateRoot, threadId) {
|
|
5784
5954
|
const s = loadState(stateRoot);
|
|
5785
5955
|
for (const [key, id] of Object.entries(s.topics)) {
|
|
@@ -5802,17 +5972,18 @@ var init_state = __esm({
|
|
|
5802
5972
|
function createTelegramClient(opts) {
|
|
5803
5973
|
const fetchImpl = opts.fetch ?? fetch;
|
|
5804
5974
|
const base = `https://api.telegram.org/bot${opts.token}`;
|
|
5805
|
-
async function call(method, body) {
|
|
5975
|
+
async function call(method, body, signal) {
|
|
5806
5976
|
const res = await fetchImpl(`${base}/${method}`, {
|
|
5807
5977
|
method: "POST",
|
|
5808
5978
|
headers: { "content-type": "application/json" },
|
|
5809
|
-
body: JSON.stringify(body)
|
|
5979
|
+
body: JSON.stringify(body),
|
|
5980
|
+
...signal ? { signal } : {}
|
|
5810
5981
|
});
|
|
5811
5982
|
const json = await res.json();
|
|
5812
5983
|
if (!res.ok || !json.ok) {
|
|
5813
5984
|
const code = json.error_code ?? res.status;
|
|
5814
5985
|
const desc = json.description ?? "unknown error";
|
|
5815
|
-
throw new
|
|
5986
|
+
throw new TelegramApiError(code, `telegram ${method} failed (${code}): ${desc}`, json.parameters?.retry_after);
|
|
5816
5987
|
}
|
|
5817
5988
|
return json.result;
|
|
5818
5989
|
}
|
|
@@ -5821,8 +5992,8 @@ function createTelegramClient(opts) {
|
|
|
5821
5992
|
const r = await call("getMe", {});
|
|
5822
5993
|
return { id: r.id, username: r.username };
|
|
5823
5994
|
},
|
|
5824
|
-
getUpdates(offset, timeoutSec = 50) {
|
|
5825
|
-
return call("getUpdates", { offset, timeout: timeoutSec });
|
|
5995
|
+
getUpdates(offset, timeoutSec = 50, signal) {
|
|
5996
|
+
return call("getUpdates", { offset, timeout: timeoutSec }, signal);
|
|
5826
5997
|
},
|
|
5827
5998
|
async sendMessage(chatId, threadId, text, replyMarkup) {
|
|
5828
5999
|
const body = { chat_id: chatId, text };
|
|
@@ -5845,6 +6016,9 @@ function createTelegramClient(opts) {
|
|
|
5845
6016
|
const r = await call("createForumTopic", { chat_id: chatId, name });
|
|
5846
6017
|
return r.message_thread_id;
|
|
5847
6018
|
},
|
|
6019
|
+
async deleteForumTopic(chatId, threadId) {
|
|
6020
|
+
await call("deleteForumTopic", { chat_id: chatId, message_thread_id: threadId });
|
|
6021
|
+
},
|
|
5848
6022
|
async setMyCommands(commands) {
|
|
5849
6023
|
await call("setMyCommands", { commands });
|
|
5850
6024
|
},
|
|
@@ -5853,11 +6027,241 @@ function createTelegramClient(opts) {
|
|
|
5853
6027
|
if (threadId !== void 0)
|
|
5854
6028
|
body.message_thread_id = threadId;
|
|
5855
6029
|
await call("sendChatAction", body);
|
|
6030
|
+
},
|
|
6031
|
+
async setMessageReaction(chatId, messageId, emoji) {
|
|
6032
|
+
await call("setMessageReaction", {
|
|
6033
|
+
chat_id: chatId,
|
|
6034
|
+
message_id: messageId,
|
|
6035
|
+
reaction: [{ type: "emoji", emoji }]
|
|
6036
|
+
});
|
|
5856
6037
|
}
|
|
5857
6038
|
};
|
|
5858
6039
|
}
|
|
6040
|
+
var TelegramApiError;
|
|
5859
6041
|
var init_client = __esm({
|
|
5860
6042
|
"packages/core/dist/telegram/client.js"() {
|
|
6043
|
+
TelegramApiError = class extends Error {
|
|
6044
|
+
code;
|
|
6045
|
+
retryAfterSec;
|
|
6046
|
+
constructor(code, message, retryAfterSec) {
|
|
6047
|
+
super(message);
|
|
6048
|
+
this.code = code;
|
|
6049
|
+
this.retryAfterSec = retryAfterSec;
|
|
6050
|
+
this.name = "TelegramApiError";
|
|
6051
|
+
}
|
|
6052
|
+
};
|
|
6053
|
+
}
|
|
6054
|
+
});
|
|
6055
|
+
|
|
6056
|
+
// packages/core/dist/telegram/inbound-lifecycle.js
|
|
6057
|
+
function defaultSchedule(fn, ms) {
|
|
6058
|
+
const t = setTimeout(fn, ms);
|
|
6059
|
+
t.unref?.();
|
|
6060
|
+
return () => clearTimeout(t);
|
|
6061
|
+
}
|
|
6062
|
+
function formatWatchdogWarning(project, read, minutes) {
|
|
6063
|
+
const head = `\u23F1\uFE0F [${project}] no reply for ${minutes} minutes`;
|
|
6064
|
+
switch (read.status) {
|
|
6065
|
+
case "locked":
|
|
6066
|
+
return `${head} \u2014 the captain is LOCKED on a prompt, waiting for a human. Answer it in the pane (or with \`squadrant crew answer\`) and it will continue.`;
|
|
6067
|
+
case "mid-turn":
|
|
6068
|
+
return `${head} \u2014 the captain is still working (a turn is in flight), just slow. No action needed yet.`;
|
|
6069
|
+
case "no-session":
|
|
6070
|
+
return `${head} \u2014 the captain looks GONE (no live session in its pane). Run \`squadrant launch ${project}\`.`;
|
|
6071
|
+
case "unreadable":
|
|
6072
|
+
return `${head} \u2014 couldn't read the captain's pane to classify it. Check it manually.`;
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
function createInboundLifecycle(opts) {
|
|
6076
|
+
const { stateRoot, cfg, sendChatAction, sendReply, readPane, log } = opts;
|
|
6077
|
+
const now = opts.now ?? Date.now;
|
|
6078
|
+
const schedule = opts.schedule ?? defaultSchedule;
|
|
6079
|
+
const typingMs = opts.typingMs ?? TYPING_KEEPALIVE_MS;
|
|
6080
|
+
const watchdogMs = opts.watchdogMs ?? WATCHDOG_MS;
|
|
6081
|
+
const warnAtMs = watchdogMs + (opts.watchdogMs === void 0 ? WATCHDOG_GRACE_MS : 0);
|
|
6082
|
+
const active = /* @__PURE__ */ new Set();
|
|
6083
|
+
let running = false;
|
|
6084
|
+
function issue(project, threadId) {
|
|
6085
|
+
void Promise.resolve(sendChatAction(cfg.supergroupId, threadId, "typing")).catch((e) => log(`telegram typing keep-alive failed project=${project}: ${e.message}`));
|
|
6086
|
+
}
|
|
6087
|
+
function drain(project) {
|
|
6088
|
+
active.add(project);
|
|
6089
|
+
const tick = () => {
|
|
6090
|
+
if (!running || !active.has(project))
|
|
6091
|
+
return;
|
|
6092
|
+
const p = loadPending(stateRoot)[project];
|
|
6093
|
+
if (!p) {
|
|
6094
|
+
active.delete(project);
|
|
6095
|
+
return;
|
|
6096
|
+
}
|
|
6097
|
+
if (now() - p.startedAt >= warnAtMs && p.warnedAt === void 0) {
|
|
6098
|
+
void warn(project, p.threadId);
|
|
6099
|
+
}
|
|
6100
|
+
issue(project, p.threadId);
|
|
6101
|
+
schedule(tick, typingMs);
|
|
6102
|
+
};
|
|
6103
|
+
schedule(tick, typingMs);
|
|
6104
|
+
}
|
|
6105
|
+
async function warn(project, threadId) {
|
|
6106
|
+
markPendingWarned(stateRoot, project, now());
|
|
6107
|
+
let read = { status: "unreadable" };
|
|
6108
|
+
try {
|
|
6109
|
+
if (readPane)
|
|
6110
|
+
read = await readPane(project);
|
|
6111
|
+
} catch (e) {
|
|
6112
|
+
log(`telegram watchdog pane read failed project=${project}: ${e.message}`);
|
|
6113
|
+
}
|
|
6114
|
+
try {
|
|
6115
|
+
await sendReply(threadId, formatWatchdogWarning(project, read, Math.floor(watchdogMs / 6e4)));
|
|
6116
|
+
} catch (e) {
|
|
6117
|
+
log(`telegram watchdog warning failed project=${project}: ${e.message}`);
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
return {
|
|
6121
|
+
start() {
|
|
6122
|
+
running = true;
|
|
6123
|
+
for (const [project, p] of Object.entries(loadPending(stateRoot))) {
|
|
6124
|
+
void (async () => {
|
|
6125
|
+
let read = { status: "unreadable" };
|
|
6126
|
+
try {
|
|
6127
|
+
if (readPane)
|
|
6128
|
+
read = await readPane(project);
|
|
6129
|
+
} catch (e) {
|
|
6130
|
+
log(`telegram resume pane read failed project=${project}: ${e.message}`);
|
|
6131
|
+
}
|
|
6132
|
+
const still = loadPending(stateRoot)[project];
|
|
6133
|
+
if (!still || still.startedAt !== p.startedAt)
|
|
6134
|
+
return;
|
|
6135
|
+
if (read.status === "no-session") {
|
|
6136
|
+
clearPending(stateRoot, project);
|
|
6137
|
+
log(`telegram typing lifecycle cleared for ${project}: captain session gone on boot`);
|
|
6138
|
+
return;
|
|
6139
|
+
}
|
|
6140
|
+
log(`telegram typing lifecycle resumed for ${project} (thread ${p.threadId})`);
|
|
6141
|
+
issue(project, p.threadId);
|
|
6142
|
+
drain(project);
|
|
6143
|
+
})();
|
|
6144
|
+
}
|
|
6145
|
+
},
|
|
6146
|
+
stop() {
|
|
6147
|
+
running = false;
|
|
6148
|
+
active.clear();
|
|
6149
|
+
},
|
|
6150
|
+
begin(project, threadId) {
|
|
6151
|
+
setPending(stateRoot, project, { threadId, startedAt: now() });
|
|
6152
|
+
issue(project, threadId);
|
|
6153
|
+
if (running && !active.has(project))
|
|
6154
|
+
drain(project);
|
|
6155
|
+
},
|
|
6156
|
+
pending() {
|
|
6157
|
+
return loadPending(stateRoot);
|
|
6158
|
+
}
|
|
6159
|
+
};
|
|
6160
|
+
}
|
|
6161
|
+
var TYPING_KEEPALIVE_MS, WATCHDOG_MS, WATCHDOG_GRACE_MS;
|
|
6162
|
+
var init_inbound_lifecycle = __esm({
|
|
6163
|
+
"packages/core/dist/telegram/inbound-lifecycle.js"() {
|
|
6164
|
+
init_state();
|
|
6165
|
+
TYPING_KEEPALIVE_MS = 4e3;
|
|
6166
|
+
WATCHDOG_MS = 15 * 6e4;
|
|
6167
|
+
WATCHDOG_GRACE_MS = 1;
|
|
6168
|
+
}
|
|
6169
|
+
});
|
|
6170
|
+
|
|
6171
|
+
// packages/core/dist/telegram/notify.js
|
|
6172
|
+
function runTelegramStatus(opts) {
|
|
6173
|
+
const tg = opts.config.telegram;
|
|
6174
|
+
const env = opts.env ?? process.env;
|
|
6175
|
+
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
6176
|
+
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
6177
|
+
const sep3 = key.indexOf("::");
|
|
6178
|
+
return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
|
|
6179
|
+
});
|
|
6180
|
+
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
6181
|
+
}
|
|
6182
|
+
function runTelegramNotifySet(opts) {
|
|
6183
|
+
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
6184
|
+
}
|
|
6185
|
+
function runTelegramNotifyPref(args) {
|
|
6186
|
+
const { project, dimension, value, root } = args;
|
|
6187
|
+
if (dimension === "crew") {
|
|
6188
|
+
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
6189
|
+
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
6190
|
+
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
6191
|
+
return { ok: true };
|
|
6192
|
+
}
|
|
6193
|
+
if (value !== "on" && value !== "off")
|
|
6194
|
+
return { ok: false, message: "cap must be on|off" };
|
|
6195
|
+
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
6196
|
+
return { ok: true };
|
|
6197
|
+
}
|
|
6198
|
+
function runTelegramNotifyStatus(opts) {
|
|
6199
|
+
const s = loadState(opts.stateRoot);
|
|
6200
|
+
const projects = /* @__PURE__ */ new Set();
|
|
6201
|
+
for (const key of Object.keys(s.topics)) {
|
|
6202
|
+
const sep3 = key.indexOf("::");
|
|
6203
|
+
projects.add(sep3 === -1 ? key : key.slice(0, sep3));
|
|
6204
|
+
}
|
|
6205
|
+
for (const p of Object.keys(s.notify))
|
|
6206
|
+
projects.add(p);
|
|
6207
|
+
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
6208
|
+
}
|
|
6209
|
+
function capAllowed(project, globalNotify, root) {
|
|
6210
|
+
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
6211
|
+
}
|
|
6212
|
+
function confirmationText(project, before, after, dim) {
|
|
6213
|
+
if (dim === "active")
|
|
6214
|
+
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
6215
|
+
if (dim === "cap")
|
|
6216
|
+
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
6217
|
+
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
6218
|
+
}
|
|
6219
|
+
async function runNotifyConfirmation(opts) {
|
|
6220
|
+
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
6221
|
+
if (!quieter || dim === null)
|
|
6222
|
+
return false;
|
|
6223
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6224
|
+
if (topicId === void 0)
|
|
6225
|
+
return false;
|
|
6226
|
+
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
6227
|
+
try {
|
|
6228
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
6229
|
+
return true;
|
|
6230
|
+
} catch {
|
|
6231
|
+
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
6232
|
+
return false;
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
async function runTelegramSend(opts) {
|
|
6236
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6237
|
+
if (topicId === void 0) {
|
|
6238
|
+
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
6239
|
+
}
|
|
6240
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
6241
|
+
clearPending(opts.stateRoot, opts.project);
|
|
6242
|
+
return { chatId: opts.cfg.supergroupId, topicId };
|
|
6243
|
+
}
|
|
6244
|
+
async function runTelegramLink(opts) {
|
|
6245
|
+
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6246
|
+
if (existing !== void 0)
|
|
6247
|
+
return { topicId: existing, created: false };
|
|
6248
|
+
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
6249
|
+
const winner = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6250
|
+
if (winner !== void 0) {
|
|
6251
|
+
try {
|
|
6252
|
+
await opts.client.deleteForumTopic?.(opts.cfg.supergroupId, topicId);
|
|
6253
|
+
} catch {
|
|
6254
|
+
}
|
|
6255
|
+
return { topicId: winner, created: false };
|
|
6256
|
+
}
|
|
6257
|
+
setTopic(opts.stateRoot, opts.project, topicId);
|
|
6258
|
+
return { topicId, created: true };
|
|
6259
|
+
}
|
|
6260
|
+
var init_notify = __esm({
|
|
6261
|
+
"packages/core/dist/telegram/notify.js"() {
|
|
6262
|
+
init_dist();
|
|
6263
|
+
init_state();
|
|
6264
|
+
init_format();
|
|
5861
6265
|
}
|
|
5862
6266
|
});
|
|
5863
6267
|
|
|
@@ -5945,8 +6349,10 @@ var init_tiers = __esm({
|
|
|
5945
6349
|
});
|
|
5946
6350
|
|
|
5947
6351
|
// packages/core/dist/telegram/bridge.js
|
|
6352
|
+
import fs11 from "fs";
|
|
5948
6353
|
import os5 from "os";
|
|
5949
6354
|
import path12 from "path";
|
|
6355
|
+
import crypto2 from "crypto";
|
|
5950
6356
|
function formatInboundReceipt(project, outcome) {
|
|
5951
6357
|
if (!outcome)
|
|
5952
6358
|
return void 0;
|
|
@@ -5962,6 +6368,85 @@ function formatInboundReceipt(project, outcome) {
|
|
|
5962
6368
|
return void 0;
|
|
5963
6369
|
}
|
|
5964
6370
|
}
|
|
6371
|
+
function telegramPollLockPath(token) {
|
|
6372
|
+
const hash = crypto2.createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
6373
|
+
return path12.join(os5.tmpdir(), `squadrant-telegram-poll-${hash}.lock`);
|
|
6374
|
+
}
|
|
6375
|
+
function pidAlive(pid) {
|
|
6376
|
+
try {
|
|
6377
|
+
process.kill(pid, 0);
|
|
6378
|
+
return true;
|
|
6379
|
+
} catch (e) {
|
|
6380
|
+
return e.code === "EPERM";
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
function lockIsHeld(file) {
|
|
6384
|
+
let pid;
|
|
6385
|
+
let ageMs;
|
|
6386
|
+
try {
|
|
6387
|
+
const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
|
|
6388
|
+
pid = Number.isFinite(parsed) ? parsed : void 0;
|
|
6389
|
+
ageMs = Date.now() - fs11.statSync(file).mtimeMs;
|
|
6390
|
+
} catch {
|
|
6391
|
+
return false;
|
|
6392
|
+
}
|
|
6393
|
+
if (pid !== void 0)
|
|
6394
|
+
return pidAlive(pid);
|
|
6395
|
+
return ageMs < POLL_LOCK_STALE_MS;
|
|
6396
|
+
}
|
|
6397
|
+
function acquirePollLock(token) {
|
|
6398
|
+
const file = telegramPollLockPath(token);
|
|
6399
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
6400
|
+
try {
|
|
6401
|
+
const fd = fs11.openSync(file, "wx");
|
|
6402
|
+
fs11.writeSync(fd, String(process.pid));
|
|
6403
|
+
fs11.closeSync(fd);
|
|
6404
|
+
return () => {
|
|
6405
|
+
try {
|
|
6406
|
+
fs11.unlinkSync(file);
|
|
6407
|
+
} catch {
|
|
6408
|
+
}
|
|
6409
|
+
};
|
|
6410
|
+
} catch (e) {
|
|
6411
|
+
if (e.code !== "EEXIST")
|
|
6412
|
+
return null;
|
|
6413
|
+
if (attempt === 0 && !lockIsHeld(file)) {
|
|
6414
|
+
try {
|
|
6415
|
+
fs11.unlinkSync(file);
|
|
6416
|
+
} catch {
|
|
6417
|
+
}
|
|
6418
|
+
continue;
|
|
6419
|
+
}
|
|
6420
|
+
return null;
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
return null;
|
|
6424
|
+
}
|
|
6425
|
+
function defaultSchedule2(fn, ms) {
|
|
6426
|
+
const t = setTimeout(fn, ms);
|
|
6427
|
+
t.unref?.();
|
|
6428
|
+
return () => clearTimeout(t);
|
|
6429
|
+
}
|
|
6430
|
+
function foreignPollLockHolder(token, selfPid = process.pid) {
|
|
6431
|
+
const file = telegramPollLockPath(token);
|
|
6432
|
+
let pid;
|
|
6433
|
+
try {
|
|
6434
|
+
const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
|
|
6435
|
+
if (!Number.isFinite(parsed))
|
|
6436
|
+
return null;
|
|
6437
|
+
pid = parsed;
|
|
6438
|
+
} catch {
|
|
6439
|
+
return null;
|
|
6440
|
+
}
|
|
6441
|
+
if (pid === selfPid)
|
|
6442
|
+
return null;
|
|
6443
|
+
return pidAlive(pid) ? pid : null;
|
|
6444
|
+
}
|
|
6445
|
+
function pollBackoffMs(err, pollMs) {
|
|
6446
|
+
if (err instanceof TelegramApiError && err.retryAfterSec !== void 0)
|
|
6447
|
+
return err.retryAfterSec * 1e3;
|
|
6448
|
+
return pollMs;
|
|
6449
|
+
}
|
|
5965
6450
|
function parseNotifyPref(text) {
|
|
5966
6451
|
const parts = text.trim().split(/\s+/);
|
|
5967
6452
|
if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
|
|
@@ -5988,9 +6473,17 @@ function notifyToggle(text) {
|
|
|
5988
6473
|
}
|
|
5989
6474
|
function createTelegramBridge(opts) {
|
|
5990
6475
|
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
6476
|
+
const lifecycle = opts.lifecycle;
|
|
5991
6477
|
const configRoot = opts.configRoot ?? path12.join(os5.homedir(), ".config", "squadrant");
|
|
5992
6478
|
const pollMs = cfg.pollMs ?? 1e3;
|
|
6479
|
+
const schedule = opts.schedule ?? defaultSchedule2;
|
|
6480
|
+
const lockToken = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
|
|
6481
|
+
let releasePollLock = null;
|
|
6482
|
+
let pollAbort = null;
|
|
5993
6483
|
let running = false;
|
|
6484
|
+
let pollStopped = false;
|
|
6485
|
+
let selfHealCancel = null;
|
|
6486
|
+
let conflictCount = 0;
|
|
5994
6487
|
let lastSuccessfulPollAt = null;
|
|
5995
6488
|
let lastError = null;
|
|
5996
6489
|
let lastErrorAt = null;
|
|
@@ -6000,12 +6493,8 @@ function createTelegramBridge(opts) {
|
|
|
6000
6493
|
saveState(stateRoot, s);
|
|
6001
6494
|
}
|
|
6002
6495
|
async function sendToTopic(project, text) {
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
6006
|
-
setTopic(stateRoot, project, threadId);
|
|
6007
|
-
}
|
|
6008
|
-
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
6496
|
+
const { topicId } = await runTelegramLink({ project, cfg, client, stateRoot });
|
|
6497
|
+
await client.sendMessage(cfg.supergroupId, topicId, text);
|
|
6009
6498
|
}
|
|
6010
6499
|
async function deliverOutbound(project, ev) {
|
|
6011
6500
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -6192,10 +6681,13 @@ ${formatUsageLine(usage2)}`;
|
|
|
6192
6681
|
}
|
|
6193
6682
|
await runChannelCommand(text, fromId, void 0);
|
|
6194
6683
|
}
|
|
6195
|
-
async function handleProjectTopic(text, threadId, fromId) {
|
|
6684
|
+
async function handleProjectTopic(text, threadId, fromId, messageId, media) {
|
|
6196
6685
|
const resolved = findProjectByThread(stateRoot, threadId);
|
|
6197
|
-
if (!resolved)
|
|
6686
|
+
if (!resolved) {
|
|
6687
|
+
log(`telegram inbound in unbound topic thread=${threadId} \u2014 no project linked, message dropped`);
|
|
6688
|
+
await reply(threadId, "\u26A0\uFE0F this topic isn't linked to a project \u2014 run: squadrant telegram link <project>");
|
|
6198
6689
|
return;
|
|
6690
|
+
}
|
|
6199
6691
|
if (isBareSpawn(text)) {
|
|
6200
6692
|
if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
|
|
6201
6693
|
await reply(threadId, "\u26D4 not authorized");
|
|
@@ -6245,21 +6737,16 @@ ${formatUsageLine(usage2)}`;
|
|
|
6245
6737
|
await runChannelCommand(text, fromId, threadId);
|
|
6246
6738
|
return;
|
|
6247
6739
|
}
|
|
6248
|
-
|
|
6249
|
-
log(`telegram
|
|
6250
|
-
}
|
|
6740
|
+
if (messageId !== void 0) {
|
|
6741
|
+
void Promise.resolve(client.setMessageReaction?.(cfg.supergroupId, messageId, "\u{1F44D}")).catch((e) => log(`telegram stage-1 reaction failed project=${resolved.project}: ${e.message}`));
|
|
6742
|
+
}
|
|
6251
6743
|
setNotify(stateRoot, resolved.project, true);
|
|
6252
6744
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
6253
|
-
|
|
6254
|
-
const r = await ensureCaptainAlive(resolved.project);
|
|
6745
|
+
void ensureCaptainAlive(resolved.project).then((r) => {
|
|
6255
6746
|
if (r === "timeout") {
|
|
6256
|
-
|
|
6257
|
-
} else {
|
|
6258
|
-
await reply(threadId, `\u{1F4E8} delivered to ${resolved.project} captain`);
|
|
6747
|
+
log(`telegram auto-launch: warmup timed out project=${resolved.project} \u2014 delivery verdict (not this probe) decides the receipt`);
|
|
6259
6748
|
}
|
|
6260
|
-
}
|
|
6261
|
-
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
6262
|
-
}
|
|
6749
|
+
}).catch((e) => log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`));
|
|
6263
6750
|
}
|
|
6264
6751
|
let handled = false;
|
|
6265
6752
|
let outcome;
|
|
@@ -6268,13 +6755,22 @@ ${formatUsageLine(usage2)}`;
|
|
|
6268
6755
|
handled = res.handled;
|
|
6269
6756
|
outcome = res.outcome;
|
|
6270
6757
|
}
|
|
6758
|
+
const deliveryHeld = outcome?.status === "held";
|
|
6271
6759
|
if (!handled) {
|
|
6272
6760
|
await appendCaptainMessage2({ stateRoot, project: resolved.project, text: formatInbound(text), source: "telegram" });
|
|
6761
|
+
outcome = void 0;
|
|
6273
6762
|
}
|
|
6274
6763
|
const receipt = formatInboundReceipt(resolved.project, outcome);
|
|
6275
6764
|
if (receipt && sendReply) {
|
|
6276
6765
|
await sendReply(threadId, receipt).catch((e) => log(`telegram receipt failed project=${resolved.project}: ${e.message}`));
|
|
6277
6766
|
}
|
|
6767
|
+
if (media) {
|
|
6768
|
+
await reply(threadId, formatMediaReceipt(media.kind, media.hasCaption));
|
|
6769
|
+
}
|
|
6770
|
+
if (deliveryHeld)
|
|
6771
|
+
return;
|
|
6772
|
+
await reply(threadId, CAPTAIN_RECEIVED_ACK);
|
|
6773
|
+
lifecycle?.begin(resolved.project, threadId);
|
|
6278
6774
|
}
|
|
6279
6775
|
async function handleUpdate(u) {
|
|
6280
6776
|
if (u.callback_query) {
|
|
@@ -6282,7 +6778,11 @@ ${formatUsageLine(usage2)}`;
|
|
|
6282
6778
|
return;
|
|
6283
6779
|
}
|
|
6284
6780
|
const m = u.message;
|
|
6285
|
-
if (!m
|
|
6781
|
+
if (!m)
|
|
6782
|
+
return;
|
|
6783
|
+
const kind = mediaKind(m);
|
|
6784
|
+
const text = m.text ?? m.caption;
|
|
6785
|
+
if (text === void 0 && kind === void 0)
|
|
6286
6786
|
return;
|
|
6287
6787
|
if (!cfg.chats.includes(m.chat.id))
|
|
6288
6788
|
return;
|
|
@@ -6296,7 +6796,7 @@ ${formatUsageLine(usage2)}`;
|
|
|
6296
6796
|
await reply(threadId, "\u26D4 not authorized");
|
|
6297
6797
|
return;
|
|
6298
6798
|
}
|
|
6299
|
-
const task =
|
|
6799
|
+
const task = (text ?? "").trim();
|
|
6300
6800
|
if (!task) {
|
|
6301
6801
|
await reply(threadId, "spawn cancelled \u2014 empty task");
|
|
6302
6802
|
return;
|
|
@@ -6307,39 +6807,122 @@ ${formatUsageLine(usage2)}`;
|
|
|
6307
6807
|
return;
|
|
6308
6808
|
}
|
|
6309
6809
|
if (m.message_thread_id === void 0) {
|
|
6810
|
+
if (m.text === void 0)
|
|
6811
|
+
return;
|
|
6310
6812
|
await handleGeneral(m.text, m.from?.id);
|
|
6311
6813
|
return;
|
|
6312
6814
|
}
|
|
6313
|
-
await handleProjectTopic(
|
|
6815
|
+
await handleProjectTopic(inboundBody(text, kind), m.message_thread_id, m.from?.id, m.message_id, kind === void 0 ? void 0 : { kind, hasCaption: text !== void 0 });
|
|
6816
|
+
}
|
|
6817
|
+
function acquirePollLockOrLog() {
|
|
6818
|
+
if (!lockToken)
|
|
6819
|
+
return true;
|
|
6820
|
+
releasePollLock = acquirePollLock(lockToken);
|
|
6821
|
+
if (releasePollLock)
|
|
6822
|
+
return true;
|
|
6823
|
+
const holder = foreignPollLockHolder(lockToken);
|
|
6824
|
+
log(`telegram inbound poll disabled: another consumer already polls this bot token (single-consumer guard, #830)${holder !== null ? ` \u2014 lock held by pid ${holder}` : ""}`);
|
|
6825
|
+
return false;
|
|
6826
|
+
}
|
|
6827
|
+
function scheduleSelfHeal() {
|
|
6828
|
+
selfHealCancel?.();
|
|
6829
|
+
selfHealCancel = schedule(() => {
|
|
6830
|
+
selfHealCancel = null;
|
|
6831
|
+
if (pollStopped || running)
|
|
6832
|
+
return;
|
|
6833
|
+
if (!acquirePollLockOrLog()) {
|
|
6834
|
+
scheduleSelfHeal();
|
|
6835
|
+
return;
|
|
6836
|
+
}
|
|
6837
|
+
running = true;
|
|
6838
|
+
conflictCount = 0;
|
|
6839
|
+
log("telegram inbound poll re-attempting after a 409 stop (#850)");
|
|
6840
|
+
void pollLoop();
|
|
6841
|
+
}, CONFLICT_409_SELF_HEAL_MS);
|
|
6314
6842
|
}
|
|
6315
6843
|
async function pollLoop() {
|
|
6316
6844
|
while (running) {
|
|
6845
|
+
const ac = new AbortController();
|
|
6846
|
+
pollAbort = ac;
|
|
6847
|
+
let waitMs = pollMs;
|
|
6317
6848
|
try {
|
|
6318
6849
|
const offset = loadState(stateRoot).offset;
|
|
6319
|
-
const updates = await client.getUpdates(offset, LONG_POLL_SEC);
|
|
6850
|
+
const updates = await client.getUpdates(offset, LONG_POLL_SEC, ac.signal);
|
|
6851
|
+
conflictCount = 0;
|
|
6320
6852
|
for (const u of updates) {
|
|
6321
6853
|
await handleUpdate(u);
|
|
6322
6854
|
persistOffset(u.update_id + 1);
|
|
6323
6855
|
}
|
|
6324
6856
|
lastSuccessfulPollAt = Date.now();
|
|
6325
6857
|
} catch (e) {
|
|
6326
|
-
|
|
6858
|
+
if (ac.signal.aborted)
|
|
6859
|
+
break;
|
|
6860
|
+
const msg = e.message;
|
|
6861
|
+
lastError = msg;
|
|
6327
6862
|
lastErrorAt = Date.now();
|
|
6328
|
-
|
|
6863
|
+
if (e instanceof TelegramApiError && e.code === 409) {
|
|
6864
|
+
conflictCount += 1;
|
|
6865
|
+
if (conflictCount >= CONFLICT_409_TERMINAL_AFTER) {
|
|
6866
|
+
log(`telegram inbound poll stopped: repeated 409 conflict (another getUpdates consumer holds this token) \u2014 ${msg}`);
|
|
6867
|
+
break;
|
|
6868
|
+
}
|
|
6869
|
+
} else {
|
|
6870
|
+
log(`telegram inbound poll failed: ${msg}`);
|
|
6871
|
+
}
|
|
6872
|
+
waitMs = pollBackoffMs(e, pollMs);
|
|
6873
|
+
} finally {
|
|
6874
|
+
pollAbort = null;
|
|
6329
6875
|
}
|
|
6330
6876
|
if (running)
|
|
6331
|
-
await sleep2(
|
|
6877
|
+
await sleep2(waitMs);
|
|
6332
6878
|
}
|
|
6879
|
+
running = false;
|
|
6880
|
+
releasePollLock?.();
|
|
6881
|
+
releasePollLock = null;
|
|
6882
|
+
if (!pollStopped)
|
|
6883
|
+
scheduleSelfHeal();
|
|
6333
6884
|
}
|
|
6334
6885
|
return {
|
|
6335
6886
|
start() {
|
|
6336
6887
|
if (running)
|
|
6337
6888
|
return;
|
|
6889
|
+
pollStopped = false;
|
|
6890
|
+
selfHealCancel?.();
|
|
6891
|
+
selfHealCancel = null;
|
|
6892
|
+
if (!acquirePollLockOrLog())
|
|
6893
|
+
return;
|
|
6894
|
+
const known = opts.registeredProjects?.();
|
|
6895
|
+
if (known !== void 0) {
|
|
6896
|
+
try {
|
|
6897
|
+
const removed = pruneTopics(stateRoot, (p) => known.includes(p));
|
|
6898
|
+
if (removed.length > 0)
|
|
6899
|
+
log(`telegram registry pruned ${removed.length} stale topic link(s): ${removed.join(", ")}`);
|
|
6900
|
+
} catch (e) {
|
|
6901
|
+
log(`telegram registry prune failed: ${e.message}`);
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6338
6904
|
running = true;
|
|
6905
|
+
conflictCount = 0;
|
|
6906
|
+
try {
|
|
6907
|
+
lifecycle?.start();
|
|
6908
|
+
} catch (e) {
|
|
6909
|
+
log(`telegram lifecycle start failed: ${e.message}`);
|
|
6910
|
+
}
|
|
6339
6911
|
void pollLoop();
|
|
6340
6912
|
},
|
|
6341
6913
|
stop() {
|
|
6342
6914
|
running = false;
|
|
6915
|
+
pollStopped = true;
|
|
6916
|
+
selfHealCancel?.();
|
|
6917
|
+
selfHealCancel = null;
|
|
6918
|
+
try {
|
|
6919
|
+
lifecycle?.stop();
|
|
6920
|
+
} catch (e) {
|
|
6921
|
+
log(`telegram lifecycle stop failed: ${e.message}`);
|
|
6922
|
+
}
|
|
6923
|
+
pollAbort?.abort();
|
|
6924
|
+
releasePollLock?.();
|
|
6925
|
+
releasePollLock = null;
|
|
6343
6926
|
},
|
|
6344
6927
|
pushLifecycle(project, ev) {
|
|
6345
6928
|
void deliverOutbound(project, ev).catch((e) => {
|
|
@@ -6356,17 +6939,23 @@ ${formatUsageLine(usage2)}`;
|
|
|
6356
6939
|
}
|
|
6357
6940
|
};
|
|
6358
6941
|
}
|
|
6359
|
-
var LONG_POLL_SEC, sleep2, CREW_TIERS, RECOGNIZED_CHANNEL_COMMANDS;
|
|
6942
|
+
var CAPTAIN_RECEIVED_ACK, LONG_POLL_SEC, POLL_LOCK_STALE_MS, CONFLICT_409_TERMINAL_AFTER, CONFLICT_409_SELF_HEAL_MS, sleep2, CREW_TIERS, RECOGNIZED_CHANNEL_COMMANDS;
|
|
6360
6943
|
var init_bridge = __esm({
|
|
6361
6944
|
"packages/core/dist/telegram/bridge.js"() {
|
|
6362
6945
|
init_dist();
|
|
6946
|
+
init_client();
|
|
6363
6947
|
init_auth();
|
|
6364
6948
|
init_commands();
|
|
6365
6949
|
init_format();
|
|
6950
|
+
init_notify();
|
|
6366
6951
|
init_panels();
|
|
6367
6952
|
init_state();
|
|
6368
6953
|
init_tiers();
|
|
6954
|
+
CAPTAIN_RECEIVED_ACK = "\u2705 captain received";
|
|
6369
6955
|
LONG_POLL_SEC = 50;
|
|
6956
|
+
POLL_LOCK_STALE_MS = 2 * 6e4;
|
|
6957
|
+
CONFLICT_409_TERMINAL_AFTER = 3;
|
|
6958
|
+
CONFLICT_409_SELF_HEAL_MS = 5 * 6e4;
|
|
6370
6959
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6371
6960
|
CREW_TIERS = ["all", "alert_only", "done_only", "none"];
|
|
6372
6961
|
RECOGNIZED_CHANNEL_COMMANDS = /* @__PURE__ */ new Set(["status", "projects", "crews", "launch", "effort", "spawn"]);
|
|
@@ -6411,7 +7000,7 @@ var init_restart_daemon = __esm({
|
|
|
6411
7000
|
});
|
|
6412
7001
|
|
|
6413
7002
|
// packages/core/dist/telegram/setup.js
|
|
6414
|
-
import
|
|
7003
|
+
import fs12 from "fs";
|
|
6415
7004
|
function resolveSetupGroup(existingSupergroupId, opts) {
|
|
6416
7005
|
if (existingSupergroupId !== void 0 && !opts.redetect)
|
|
6417
7006
|
return "reuse";
|
|
@@ -6462,7 +7051,7 @@ function writeTelegramConfig(configPath, opts) {
|
|
|
6462
7051
|
let config;
|
|
6463
7052
|
let raw = null;
|
|
6464
7053
|
try {
|
|
6465
|
-
raw =
|
|
7054
|
+
raw = fs12.readFileSync(configPath, "utf-8");
|
|
6466
7055
|
} catch (err) {
|
|
6467
7056
|
if (err.code !== "ENOENT") {
|
|
6468
7057
|
throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
|
|
@@ -6501,94 +7090,6 @@ var init_setup = __esm({
|
|
|
6501
7090
|
}
|
|
6502
7091
|
});
|
|
6503
7092
|
|
|
6504
|
-
// packages/core/dist/telegram/notify.js
|
|
6505
|
-
function runTelegramStatus(opts) {
|
|
6506
|
-
const tg = opts.config.telegram;
|
|
6507
|
-
const env = opts.env ?? process.env;
|
|
6508
|
-
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
6509
|
-
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
6510
|
-
const sep3 = key.indexOf("::");
|
|
6511
|
-
return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
|
|
6512
|
-
});
|
|
6513
|
-
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
6514
|
-
}
|
|
6515
|
-
function runTelegramNotifySet(opts) {
|
|
6516
|
-
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
6517
|
-
}
|
|
6518
|
-
function runTelegramNotifyPref(args) {
|
|
6519
|
-
const { project, dimension, value, root } = args;
|
|
6520
|
-
if (dimension === "crew") {
|
|
6521
|
-
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
6522
|
-
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
6523
|
-
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
6524
|
-
return { ok: true };
|
|
6525
|
-
}
|
|
6526
|
-
if (value !== "on" && value !== "off")
|
|
6527
|
-
return { ok: false, message: "cap must be on|off" };
|
|
6528
|
-
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
6529
|
-
return { ok: true };
|
|
6530
|
-
}
|
|
6531
|
-
function runTelegramNotifyStatus(opts) {
|
|
6532
|
-
const s = loadState(opts.stateRoot);
|
|
6533
|
-
const projects = /* @__PURE__ */ new Set();
|
|
6534
|
-
for (const key of Object.keys(s.topics)) {
|
|
6535
|
-
const sep3 = key.indexOf("::");
|
|
6536
|
-
projects.add(sep3 === -1 ? key : key.slice(0, sep3));
|
|
6537
|
-
}
|
|
6538
|
-
for (const p of Object.keys(s.notify))
|
|
6539
|
-
projects.add(p);
|
|
6540
|
-
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
6541
|
-
}
|
|
6542
|
-
function capAllowed(project, globalNotify, root) {
|
|
6543
|
-
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
6544
|
-
}
|
|
6545
|
-
function confirmationText(project, before, after, dim) {
|
|
6546
|
-
if (dim === "active")
|
|
6547
|
-
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
6548
|
-
if (dim === "cap")
|
|
6549
|
-
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
6550
|
-
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
6551
|
-
}
|
|
6552
|
-
async function runNotifyConfirmation(opts) {
|
|
6553
|
-
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
6554
|
-
if (!quieter || dim === null)
|
|
6555
|
-
return false;
|
|
6556
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6557
|
-
if (topicId === void 0)
|
|
6558
|
-
return false;
|
|
6559
|
-
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
6560
|
-
try {
|
|
6561
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
6562
|
-
return true;
|
|
6563
|
-
} catch {
|
|
6564
|
-
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
6565
|
-
return false;
|
|
6566
|
-
}
|
|
6567
|
-
}
|
|
6568
|
-
async function runTelegramSend(opts) {
|
|
6569
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6570
|
-
if (topicId === void 0) {
|
|
6571
|
-
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
6572
|
-
}
|
|
6573
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
6574
|
-
return { chatId: opts.cfg.supergroupId, topicId };
|
|
6575
|
-
}
|
|
6576
|
-
async function runTelegramLink(opts) {
|
|
6577
|
-
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
6578
|
-
if (existing !== void 0)
|
|
6579
|
-
return { topicId: existing, created: false };
|
|
6580
|
-
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
6581
|
-
setTopic(opts.stateRoot, opts.project, topicId);
|
|
6582
|
-
return { topicId, created: true };
|
|
6583
|
-
}
|
|
6584
|
-
var init_notify = __esm({
|
|
6585
|
-
"packages/core/dist/telegram/notify.js"() {
|
|
6586
|
-
init_dist();
|
|
6587
|
-
init_state();
|
|
6588
|
-
init_format();
|
|
6589
|
-
}
|
|
6590
|
-
});
|
|
6591
|
-
|
|
6592
7093
|
// packages/core/dist/telegram/index.js
|
|
6593
7094
|
var init_telegram = __esm({
|
|
6594
7095
|
"packages/core/dist/telegram/index.js"() {
|
|
@@ -6600,6 +7101,7 @@ var init_telegram = __esm({
|
|
|
6600
7101
|
init_format();
|
|
6601
7102
|
init_state();
|
|
6602
7103
|
init_client();
|
|
7104
|
+
init_inbound_lifecycle();
|
|
6603
7105
|
init_bridge();
|
|
6604
7106
|
init_setup();
|
|
6605
7107
|
init_notify();
|
|
@@ -7865,7 +8367,7 @@ var init_launch_workspace = __esm({
|
|
|
7865
8367
|
});
|
|
7866
8368
|
|
|
7867
8369
|
// packages/core/dist/side-session.js
|
|
7868
|
-
import
|
|
8370
|
+
import fs13 from "fs";
|
|
7869
8371
|
function sideTitleFor(project, name) {
|
|
7870
8372
|
return `\u{1F5D2} ${project}:${name}`;
|
|
7871
8373
|
}
|
|
@@ -7967,7 +8469,7 @@ async function runSideClose(runtime, workspaceId, project, name, projPath, workt
|
|
|
7967
8469
|
await runtime.closePane(pane);
|
|
7968
8470
|
if (projPath) {
|
|
7969
8471
|
const wtPath = worktreePath(projPath, worktreeDir, project, name);
|
|
7970
|
-
if (
|
|
8472
|
+
if (fs13.existsSync(wtPath)) {
|
|
7971
8473
|
try {
|
|
7972
8474
|
removeWorktree(projPath, wtPath);
|
|
7973
8475
|
} catch (e) {
|
|
@@ -8404,8 +8906,10 @@ __export(dist_exports, {
|
|
|
8404
8906
|
AGENT_BINS: () => AGENT_BINS,
|
|
8405
8907
|
ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
|
|
8406
8908
|
BOT_COMMANDS: () => BOT_COMMANDS,
|
|
8909
|
+
CAPTAIN_RECEIVED_ACK: () => CAPTAIN_RECEIVED_ACK,
|
|
8407
8910
|
CC_SOCKS_DIR: () => CC_SOCKS_DIR,
|
|
8408
8911
|
CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
|
|
8912
|
+
CONFLICT_409_SELF_HEAL_MS: () => CONFLICT_409_SELF_HEAL_MS,
|
|
8409
8913
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
8410
8914
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
8411
8915
|
CaptainDelivery: () => CaptainDelivery,
|
|
@@ -8425,6 +8929,7 @@ __export(dist_exports, {
|
|
|
8425
8929
|
LivenessRegistry: () => LivenessRegistry,
|
|
8426
8930
|
MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
|
|
8427
8931
|
OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
|
|
8932
|
+
PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
|
|
8428
8933
|
PROBE_QUIET_MS: () => PROBE_QUIET_MS,
|
|
8429
8934
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
8430
8935
|
READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
|
|
@@ -8437,6 +8942,10 @@ __export(dist_exports, {
|
|
|
8437
8942
|
TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
|
|
8438
8943
|
TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
|
|
8439
8944
|
TOOL_STALL_BUDGET_MS: () => TOOL_STALL_BUDGET_MS,
|
|
8945
|
+
TYPING_KEEPALIVE_MS: () => TYPING_KEEPALIVE_MS,
|
|
8946
|
+
TelegramApiError: () => TelegramApiError,
|
|
8947
|
+
WATCHDOG_GRACE_MS: () => WATCHDOG_GRACE_MS,
|
|
8948
|
+
WATCHDOG_MS: () => WATCHDOG_MS,
|
|
8440
8949
|
WORK_ITEM_TTL_MS: () => WORK_ITEM_TTL_MS,
|
|
8441
8950
|
WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
|
|
8442
8951
|
_resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
|
|
@@ -8458,15 +8967,19 @@ __export(dist_exports, {
|
|
|
8458
8967
|
callClassifier: () => callClassifier,
|
|
8459
8968
|
capAllowed: () => capAllowed,
|
|
8460
8969
|
capOutput: () => capOutput,
|
|
8970
|
+
captainAliveStateFromHealth: () => captainAliveStateFromHealth,
|
|
8461
8971
|
captainRecordPath: () => captainRecordPath,
|
|
8462
8972
|
captainSocketPath: () => captainSocketPath,
|
|
8463
8973
|
checkFact: () => checkFact,
|
|
8974
|
+
classifyCaptainPane: () => classifyCaptainPane,
|
|
8464
8975
|
classifyHealth: () => classifyHealth,
|
|
8465
8976
|
claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
|
|
8977
|
+
clearPending: () => clearPending,
|
|
8466
8978
|
closeWorkItem: () => closeWorkItem,
|
|
8467
8979
|
computeTemplateHash: () => computeTemplateHash,
|
|
8468
8980
|
consumeExitMarker: () => consumeExitMarker,
|
|
8469
8981
|
createAttach: () => createAttach,
|
|
8982
|
+
createCaptainPaneReader: () => createCaptainPaneReader,
|
|
8470
8983
|
createCrewPaneReader: () => createCrewPaneReader,
|
|
8471
8984
|
createDaemon: () => createDaemon,
|
|
8472
8985
|
createDecoder: () => createDecoder,
|
|
@@ -8475,6 +8988,7 @@ __export(dist_exports, {
|
|
|
8475
8988
|
createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
|
|
8476
8989
|
createEnsureCaptainAlive: () => createEnsureCaptainAlive,
|
|
8477
8990
|
createEventsSource: () => createEventsSource,
|
|
8991
|
+
createInboundLifecycle: () => createInboundLifecycle,
|
|
8478
8992
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
8479
8993
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
8480
8994
|
createLaunch: () => createLaunch,
|
|
@@ -8530,12 +9044,16 @@ __export(dist_exports, {
|
|
|
8530
9044
|
formatInbound: () => formatInbound,
|
|
8531
9045
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
8532
9046
|
formatLifecycle: () => formatLifecycle,
|
|
9047
|
+
formatMediaReceipt: () => formatMediaReceipt,
|
|
8533
9048
|
formatPermissionDecision: () => formatPermissionDecision,
|
|
8534
9049
|
formatUsageLine: () => formatUsageLine,
|
|
9050
|
+
formatWatchdogWarning: () => formatWatchdogWarning,
|
|
8535
9051
|
freshTrace: () => freshTrace,
|
|
8536
9052
|
gateCacheKey: () => gateCacheKey,
|
|
8537
9053
|
getDaemonPid: () => getDaemonPid,
|
|
9054
|
+
hasModalOptionList: () => hasModalOptionList,
|
|
8538
9055
|
healCmdFor: () => healCmdFor,
|
|
9056
|
+
inboundBody: () => inboundBody,
|
|
8539
9057
|
isAuthorized: () => isAuthorized,
|
|
8540
9058
|
isBareSpawn: () => isBareSpawn,
|
|
8541
9059
|
isCaptainAlive: () => isCaptainAlive,
|
|
@@ -8557,18 +9075,23 @@ __export(dist_exports, {
|
|
|
8557
9075
|
kickstartArgv: () => kickstartArgv,
|
|
8558
9076
|
launchOneWorkspace: () => launchOneWorkspace,
|
|
8559
9077
|
listSessions: () => listSessions,
|
|
9078
|
+
loadPending: () => loadPending,
|
|
8560
9079
|
loadSessions: () => loadSessions,
|
|
8561
9080
|
loadState: () => loadState,
|
|
8562
9081
|
mailboxStats: () => mailboxStats,
|
|
8563
9082
|
makeGate: () => makeGate,
|
|
9083
|
+
markPendingWarned: () => markPendingWarned,
|
|
8564
9084
|
maskToken: () => maskToken,
|
|
8565
9085
|
matchTier1Deny: () => matchTier1Deny,
|
|
9086
|
+
mediaKind: () => mediaKind,
|
|
9087
|
+
mediaMarker: () => mediaMarker,
|
|
8566
9088
|
mergeClaudeEnvRouterSettings: () => mergeClaudeEnvRouterSettings,
|
|
8567
9089
|
mintToken: () => mintToken,
|
|
8568
9090
|
nameFromTitle: () => nameFromTitle,
|
|
8569
9091
|
newestSessionInDirectory: () => newestSessionInDirectory,
|
|
8570
9092
|
nextAutoName: () => nextAutoName,
|
|
8571
9093
|
niceCrewCommand: () => niceCrewCommand,
|
|
9094
|
+
notePaneScreen: () => notePaneScreen,
|
|
8572
9095
|
notifyToggle: () => notifyToggle,
|
|
8573
9096
|
parseBearer: () => parseBearer,
|
|
8574
9097
|
parseClassifierVerdict: () => parseClassifierVerdict,
|
|
@@ -8577,6 +9100,7 @@ __export(dist_exports, {
|
|
|
8577
9100
|
parseNotifyPref: () => parseNotifyPref,
|
|
8578
9101
|
parseProgramArgs: () => parseProgramArgs,
|
|
8579
9102
|
plistPath: () => plistPath,
|
|
9103
|
+
pollBackoffMs: () => pollBackoffMs,
|
|
8580
9104
|
pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
|
|
8581
9105
|
prepareCaptainRoute: () => prepareCaptainRoute,
|
|
8582
9106
|
printForeignInstallError: () => printForeignInstallError,
|
|
@@ -8584,6 +9108,7 @@ __export(dist_exports, {
|
|
|
8584
9108
|
probeCaptainChannel: () => probeCaptainChannel,
|
|
8585
9109
|
programArgsBlock: () => programArgsBlock,
|
|
8586
9110
|
projectHealth: () => projectHealth,
|
|
9111
|
+
pruneTopics: () => pruneTopics,
|
|
8587
9112
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
8588
9113
|
readCaptainAddress: () => readCaptainAddress,
|
|
8589
9114
|
readCursor: () => readCursor,
|
|
@@ -8650,9 +9175,11 @@ __export(dist_exports, {
|
|
|
8650
9175
|
saveSessions: () => saveSessions,
|
|
8651
9176
|
saveState: () => saveState,
|
|
8652
9177
|
screenHasSplashMarker: () => screenHasSplashMarker,
|
|
9178
|
+
screenHash: () => screenHash,
|
|
8653
9179
|
sendRequest: () => sendRequest,
|
|
8654
9180
|
setLastUserId: () => setLastUserId,
|
|
8655
9181
|
setNotify: () => setNotify,
|
|
9182
|
+
setPending: () => setPending,
|
|
8656
9183
|
setTopic: () => setTopic,
|
|
8657
9184
|
shellQuote: () => shellQuote,
|
|
8658
9185
|
shouldBuildRouterService: () => shouldBuildRouterService,
|
|
@@ -8665,6 +9192,7 @@ __export(dist_exports, {
|
|
|
8665
9192
|
startServer: () => startServer,
|
|
8666
9193
|
stripBotMention: () => stripBotMention,
|
|
8667
9194
|
surfaceVerdict: () => surfaceVerdict,
|
|
9195
|
+
telegramPollLockPath: () => telegramPollLockPath,
|
|
8668
9196
|
timeoutGate: () => timeoutGate,
|
|
8669
9197
|
titleFor: () => titleFor,
|
|
8670
9198
|
toControlEvent: () => toControlEvent,
|
|
@@ -8758,7 +9286,7 @@ import { readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs"
|
|
|
8758
9286
|
import { join as join18 } from "path";
|
|
8759
9287
|
|
|
8760
9288
|
// packages/agents/dist/claude/registry.js
|
|
8761
|
-
import
|
|
9289
|
+
import fs14 from "fs";
|
|
8762
9290
|
import { join as join17 } from "path";
|
|
8763
9291
|
import { homedir as homedir7 } from "os";
|
|
8764
9292
|
var CLAUDE_SESSIONS_DIR = join17(homedir7(), ".claude", "sessions");
|
|
@@ -8803,11 +9331,11 @@ function toLifecycleSnapshot(entry, taskId, alive, now) {
|
|
|
8803
9331
|
function readClaudeStatusBySocketPath(socketPath) {
|
|
8804
9332
|
let files;
|
|
8805
9333
|
try {
|
|
8806
|
-
files =
|
|
9334
|
+
files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
|
|
8807
9335
|
} catch {
|
|
8808
9336
|
return void 0;
|
|
8809
9337
|
}
|
|
8810
|
-
const entries = parseRegistryDir(files, (name) =>
|
|
9338
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
8811
9339
|
const entry = entries.find((e) => e.messagingSocketPath === socketPath);
|
|
8812
9340
|
if (!entry)
|
|
8813
9341
|
return void 0;
|
|
@@ -8830,7 +9358,7 @@ import { homedir as homedir8 } from "os";
|
|
|
8830
9358
|
var SQUADRANT_STATE_DIR = join19(homedir8(), ".config", "squadrant", "state");
|
|
8831
9359
|
|
|
8832
9360
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
8833
|
-
import
|
|
9361
|
+
import fs15 from "fs";
|
|
8834
9362
|
import path13 from "path";
|
|
8835
9363
|
|
|
8836
9364
|
// packages/agents/dist/projection/cursor.js
|
|
@@ -8854,7 +9382,7 @@ import path17 from "path";
|
|
|
8854
9382
|
import os9 from "os";
|
|
8855
9383
|
|
|
8856
9384
|
// packages/agents/dist/projection/opencode-skills.js
|
|
8857
|
-
import
|
|
9385
|
+
import fs16 from "fs";
|
|
8858
9386
|
import os10 from "os";
|
|
8859
9387
|
import path18 from "path";
|
|
8860
9388
|
var OPENCODE_SKILL_MARKER = ".squadrant-managed";
|
|
@@ -8876,62 +9404,62 @@ function parseSkillName(raw) {
|
|
|
8876
9404
|
}
|
|
8877
9405
|
function listSourceSkills(sourceSkillsDir) {
|
|
8878
9406
|
const out = /* @__PURE__ */ new Map();
|
|
8879
|
-
if (!
|
|
9407
|
+
if (!fs16.existsSync(sourceSkillsDir))
|
|
8880
9408
|
return out;
|
|
8881
|
-
for (const entry of
|
|
9409
|
+
for (const entry of fs16.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
|
|
8882
9410
|
if (!entry.isDirectory())
|
|
8883
9411
|
continue;
|
|
8884
9412
|
const skillPath = path18.join(sourceSkillsDir, entry.name, "SKILL.md");
|
|
8885
|
-
if (!
|
|
9413
|
+
if (!fs16.existsSync(skillPath))
|
|
8886
9414
|
continue;
|
|
8887
|
-
const name = parseSkillName(
|
|
9415
|
+
const name = parseSkillName(fs16.readFileSync(skillPath, "utf-8"));
|
|
8888
9416
|
if (name && SAFE_NAME.test(name))
|
|
8889
9417
|
out.set(name, skillPath);
|
|
8890
9418
|
}
|
|
8891
9419
|
return out;
|
|
8892
9420
|
}
|
|
8893
9421
|
function hasMarker(dir) {
|
|
8894
|
-
return
|
|
9422
|
+
return fs16.existsSync(path18.join(dir, OPENCODE_SKILL_MARKER));
|
|
8895
9423
|
}
|
|
8896
9424
|
function syncOpencodeSkills(opts) {
|
|
8897
9425
|
const result = { written: [], unchanged: [], removed: [], skipped: [] };
|
|
8898
9426
|
const wanted = listSourceSkills(opts.sourceSkillsDir);
|
|
8899
|
-
if (wanted.size === 0 && !
|
|
9427
|
+
if (wanted.size === 0 && !fs16.existsSync(opts.sourceSkillsDir))
|
|
8900
9428
|
return result;
|
|
8901
9429
|
if (!opts.dryRun)
|
|
8902
|
-
|
|
9430
|
+
fs16.mkdirSync(opts.skillsRoot, { recursive: true });
|
|
8903
9431
|
for (const [name, srcPath] of wanted) {
|
|
8904
9432
|
const targetDir = path18.join(opts.skillsRoot, name);
|
|
8905
9433
|
const targetSkill = path18.join(targetDir, "SKILL.md");
|
|
8906
9434
|
const managed = hasMarker(targetDir);
|
|
8907
|
-
if (
|
|
9435
|
+
if (fs16.existsSync(targetSkill) && !managed) {
|
|
8908
9436
|
result.skipped.push({ name, reason: "foreign-collision" });
|
|
8909
9437
|
continue;
|
|
8910
9438
|
}
|
|
8911
|
-
const current =
|
|
8912
|
-
if (current && current.equals(
|
|
9439
|
+
const current = fs16.existsSync(targetSkill) ? fs16.readFileSync(targetSkill) : null;
|
|
9440
|
+
if (current && current.equals(fs16.readFileSync(srcPath))) {
|
|
8913
9441
|
if (!managed && !opts.dryRun) {
|
|
8914
|
-
|
|
9442
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
8915
9443
|
}
|
|
8916
9444
|
result.unchanged.push(name);
|
|
8917
9445
|
continue;
|
|
8918
9446
|
}
|
|
8919
9447
|
if (!opts.dryRun) {
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
9448
|
+
fs16.mkdirSync(targetDir, { recursive: true });
|
|
9449
|
+
fs16.copyFileSync(srcPath, targetSkill);
|
|
9450
|
+
fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
|
|
8923
9451
|
}
|
|
8924
9452
|
result.written.push(name);
|
|
8925
9453
|
}
|
|
8926
|
-
if (
|
|
8927
|
-
for (const entry of
|
|
9454
|
+
if (fs16.existsSync(opts.skillsRoot)) {
|
|
9455
|
+
for (const entry of fs16.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
|
|
8928
9456
|
if (!entry.isDirectory() || wanted.has(entry.name))
|
|
8929
9457
|
continue;
|
|
8930
9458
|
const dir = path18.join(opts.skillsRoot, entry.name);
|
|
8931
9459
|
if (!hasMarker(dir))
|
|
8932
9460
|
continue;
|
|
8933
9461
|
if (!opts.dryRun)
|
|
8934
|
-
|
|
9462
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
8935
9463
|
result.removed.push(entry.name);
|
|
8936
9464
|
}
|
|
8937
9465
|
}
|
|
@@ -10105,6 +10633,9 @@ var ClaudePeerChannel = class {
|
|
|
10105
10633
|
return { status: "accepted", via: this.name, confirmed: true };
|
|
10106
10634
|
}
|
|
10107
10635
|
}
|
|
10636
|
+
if (statusBefore === "busy" || statusBefore === "shell") {
|
|
10637
|
+
return { status: "queued", via: this.name };
|
|
10638
|
+
}
|
|
10108
10639
|
if (statusBefore === "idle") {
|
|
10109
10640
|
await this.deps.sleep(window);
|
|
10110
10641
|
const afterInfo = this.deps.statusFor(taskId);
|
|
@@ -10243,7 +10774,7 @@ var ClaudeReceiptListener = class {
|
|
|
10243
10774
|
};
|
|
10244
10775
|
|
|
10245
10776
|
// packages/agents/dist/claude/api-key-approval.js
|
|
10246
|
-
import
|
|
10777
|
+
import fs17 from "fs";
|
|
10247
10778
|
import os12 from "os";
|
|
10248
10779
|
import path20 from "path";
|
|
10249
10780
|
|
|
@@ -10485,11 +11016,11 @@ function parseDraftFromScreen(screen) {
|
|
|
10485
11016
|
if (!screen)
|
|
10486
11017
|
return null;
|
|
10487
11018
|
const lines = screen.split(/\r?\n/);
|
|
10488
|
-
const
|
|
11019
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10489
11020
|
let bottomHR = -1;
|
|
10490
11021
|
let topHR = -1;
|
|
10491
11022
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10492
|
-
if (
|
|
11023
|
+
if (HR_RE2.test(lines[i])) {
|
|
10493
11024
|
if (bottomHR === -1) {
|
|
10494
11025
|
bottomHR = i;
|
|
10495
11026
|
} else {
|
|
@@ -10527,11 +11058,11 @@ function hasCCInputBox(screen) {
|
|
|
10527
11058
|
if (!screen)
|
|
10528
11059
|
return false;
|
|
10529
11060
|
const lines = screen.split(/\r?\n/);
|
|
10530
|
-
const
|
|
11061
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10531
11062
|
let bottomHR = -1;
|
|
10532
11063
|
let topHR = -1;
|
|
10533
11064
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10534
|
-
if (
|
|
11065
|
+
if (HR_RE2.test(lines[i])) {
|
|
10535
11066
|
if (bottomHR === -1)
|
|
10536
11067
|
bottomHR = i;
|
|
10537
11068
|
else {
|
|
@@ -10544,15 +11075,15 @@ function hasCCInputBox(screen) {
|
|
|
10544
11075
|
return false;
|
|
10545
11076
|
return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));
|
|
10546
11077
|
}
|
|
10547
|
-
function
|
|
11078
|
+
function hasModalOptionList2(screen) {
|
|
10548
11079
|
if (!screen)
|
|
10549
11080
|
return false;
|
|
10550
11081
|
const lines = screen.split(/\r?\n/);
|
|
10551
|
-
const
|
|
11082
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10552
11083
|
let bottomHR = -1;
|
|
10553
11084
|
let topHR = -1;
|
|
10554
11085
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10555
|
-
if (
|
|
11086
|
+
if (HR_RE2.test(lines[i])) {
|
|
10556
11087
|
if (bottomHR === -1)
|
|
10557
11088
|
bottomHR = i;
|
|
10558
11089
|
else {
|
|
@@ -10569,11 +11100,11 @@ function readInputBoxRaw(screen, opts) {
|
|
|
10569
11100
|
if (!screen)
|
|
10570
11101
|
return null;
|
|
10571
11102
|
const lines = screen.split(/\r?\n/);
|
|
10572
|
-
const
|
|
11103
|
+
const HR_RE2 = /^\s*─{10,}\s*$/;
|
|
10573
11104
|
let bottomHR = -1;
|
|
10574
11105
|
let topHR = -1;
|
|
10575
11106
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10576
|
-
if (
|
|
11107
|
+
if (HR_RE2.test(lines[i])) {
|
|
10577
11108
|
if (bottomHR === -1)
|
|
10578
11109
|
bottomHR = i;
|
|
10579
11110
|
else {
|
|
@@ -10603,6 +11134,34 @@ function classifyStartupSurface(screen) {
|
|
|
10603
11134
|
return "idle";
|
|
10604
11135
|
return "loading";
|
|
10605
11136
|
}
|
|
11137
|
+
var OC_BOX_BOTTOM_RE = /^\s*╹[▀]{6,}\s*$/;
|
|
11138
|
+
var OC_BOX_SIDE_RE = /^\s*┃/;
|
|
11139
|
+
var OC_BOX_STATUS_RE = /^Build\s*·/i;
|
|
11140
|
+
var OC_BUSY_RE = /esc\s+interrupt/i;
|
|
11141
|
+
function parseOpencodeDraftFromScreen(screen) {
|
|
11142
|
+
if (!screen)
|
|
11143
|
+
return null;
|
|
11144
|
+
if (OC_BUSY_RE.test(screen))
|
|
11145
|
+
return null;
|
|
11146
|
+
const lines = screen.split(/\r?\n/);
|
|
11147
|
+
let bottom = -1;
|
|
11148
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
11149
|
+
if (OC_BOX_BOTTOM_RE.test(lines[i])) {
|
|
11150
|
+
bottom = i;
|
|
11151
|
+
break;
|
|
11152
|
+
}
|
|
11153
|
+
}
|
|
11154
|
+
if (bottom === -1)
|
|
11155
|
+
return null;
|
|
11156
|
+
const content = [];
|
|
11157
|
+
for (let i = bottom - 1; i >= 0 && OC_BOX_SIDE_RE.test(lines[i]); i--) {
|
|
11158
|
+
content.push(lines[i].replace(/^\s*┃\s*/, "").replace(/[\s┃│]+$/, ""));
|
|
11159
|
+
}
|
|
11160
|
+
if (content.length === 0)
|
|
11161
|
+
return null;
|
|
11162
|
+
const hasDraft = content.some((l) => l !== "" && !OC_BOX_STATUS_RE.test(l) && !screenHasSplashMarker(l, "Ask anything"));
|
|
11163
|
+
return hasDraft ? null : "";
|
|
11164
|
+
}
|
|
10606
11165
|
function sendDebugEnabled() {
|
|
10607
11166
|
return !!process.env.SQUADRANT_DEBUG_SEND;
|
|
10608
11167
|
}
|
|
@@ -10818,10 +11377,10 @@ function createCmuxDriver() {
|
|
|
10818
11377
|
`);
|
|
10819
11378
|
throw new DeferDelivery(null, "probe-failed");
|
|
10820
11379
|
}
|
|
10821
|
-
const draft = parseDraftFromScreen(screen);
|
|
11380
|
+
const draft = opts?.agent === "opencode" ? parseOpencodeDraftFromScreen(screen) : parseDraftFromScreen(screen);
|
|
10822
11381
|
if (draft === null)
|
|
10823
11382
|
throw new DeferDelivery(null, "no-box");
|
|
10824
|
-
if (
|
|
11383
|
+
if (hasModalOptionList2(screen))
|
|
10825
11384
|
throw new DeferDelivery(null, "modal");
|
|
10826
11385
|
if (draft === "") {
|
|
10827
11386
|
await deliver();
|
|
@@ -11015,7 +11574,7 @@ var NotifierRegistry = class {
|
|
|
11015
11574
|
};
|
|
11016
11575
|
|
|
11017
11576
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
11018
|
-
import
|
|
11577
|
+
import fs18 from "fs/promises";
|
|
11019
11578
|
import { existsSync as existsSync11 } from "fs";
|
|
11020
11579
|
import path21 from "path";
|
|
11021
11580
|
|
|
@@ -11811,7 +12370,7 @@ async function settleInputBox(runtime, pane) {
|
|
|
11811
12370
|
}
|
|
11812
12371
|
async function confirmedSendToPane(runtime, pane, message) {
|
|
11813
12372
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
11814
|
-
if (
|
|
12373
|
+
if (hasModalOptionList2(preSendScreen)) {
|
|
11815
12374
|
return { delivered: false, blockedByModal: true };
|
|
11816
12375
|
}
|
|
11817
12376
|
await runtime.pasteToPane(pane, message);
|
|
@@ -11914,7 +12473,7 @@ async function maybeBroadcastDaemonRestart(opts) {
|
|
|
11914
12473
|
|
|
11915
12474
|
// packages/cli/src/lib/captain-channel-factory.ts
|
|
11916
12475
|
import { createServer as createServer4, connect as netConnect } from "net";
|
|
11917
|
-
import
|
|
12476
|
+
import fs19 from "fs";
|
|
11918
12477
|
import { join as join26 } from "path";
|
|
11919
12478
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
11920
12479
|
import chalk2 from "chalk";
|
|
@@ -11923,15 +12482,15 @@ var shared;
|
|
|
11923
12482
|
var registryEntryPath = () => join26(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
11924
12483
|
function unregisterSenderIdentity() {
|
|
11925
12484
|
try {
|
|
11926
|
-
|
|
12485
|
+
fs19.unlinkSync(registryEntryPath());
|
|
11927
12486
|
} catch {
|
|
11928
12487
|
}
|
|
11929
12488
|
}
|
|
11930
12489
|
function registerSenderIdentity(socketPath) {
|
|
11931
12490
|
try {
|
|
11932
12491
|
unregisterSenderIdentity();
|
|
11933
|
-
|
|
11934
|
-
|
|
12492
|
+
fs19.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
12493
|
+
fs19.writeFileSync(
|
|
11935
12494
|
registryEntryPath(),
|
|
11936
12495
|
JSON.stringify({
|
|
11937
12496
|
pid: process.pid,
|
|
@@ -11957,7 +12516,7 @@ async function sharedReceiptListener() {
|
|
|
11957
12516
|
// must never be the reason we refuse to start.
|
|
11958
12517
|
unlinkStale: (p) => {
|
|
11959
12518
|
try {
|
|
11960
|
-
|
|
12519
|
+
fs19.unlinkSync(p);
|
|
11961
12520
|
} catch {
|
|
11962
12521
|
}
|
|
11963
12522
|
},
|
|
@@ -12054,6 +12613,33 @@ function buildTelegramBridge(cfg, stateRoot, log, deliverInbound, usageFor) {
|
|
|
12054
12613
|
});
|
|
12055
12614
|
const runCommand = createRunCommand(CLI_BIN);
|
|
12056
12615
|
const sendReply = (threadId, text, replyMarkup) => client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);
|
|
12616
|
+
const lifecycle = createInboundLifecycle({
|
|
12617
|
+
stateRoot,
|
|
12618
|
+
cfg: { supergroupId: cfg.supergroupId },
|
|
12619
|
+
sendChatAction: (chatId, threadId, action) => client.sendChatAction(chatId, threadId, action),
|
|
12620
|
+
sendReply: async (threadId, text) => {
|
|
12621
|
+
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
12622
|
+
},
|
|
12623
|
+
readPane: createCaptainPaneReader(
|
|
12624
|
+
new DaemonCmux(createCmuxDriver()),
|
|
12625
|
+
(project) => {
|
|
12626
|
+
try {
|
|
12627
|
+
return loadConfig().projects[project]?.captainName ?? `${project}-captain`;
|
|
12628
|
+
} catch {
|
|
12629
|
+
return `${project}-captain`;
|
|
12630
|
+
}
|
|
12631
|
+
},
|
|
12632
|
+
{
|
|
12633
|
+
log,
|
|
12634
|
+
// #839 staleness, measured for real: cmux has no pane activity
|
|
12635
|
+
// timestamp, so the reader hashes the screen and asks the persisted
|
|
12636
|
+
// pending store how long that exact screen has been up. Persisted, so a
|
|
12637
|
+
// daemon restart cannot hand a dead captain a fresh 15 minutes.
|
|
12638
|
+
noteScreen: (project, hash) => notePaneScreen(stateRoot, project, hash, Date.now())
|
|
12639
|
+
}
|
|
12640
|
+
),
|
|
12641
|
+
log
|
|
12642
|
+
});
|
|
12057
12643
|
return createTelegramBridge({
|
|
12058
12644
|
cfg,
|
|
12059
12645
|
stateRoot,
|
|
@@ -12065,7 +12651,20 @@ function buildTelegramBridge(cfg, stateRoot, log, deliverInbound, usageFor) {
|
|
|
12065
12651
|
runCommand,
|
|
12066
12652
|
sendReply,
|
|
12067
12653
|
deliverInbound,
|
|
12068
|
-
usageFor
|
|
12654
|
+
usageFor,
|
|
12655
|
+
lifecycle,
|
|
12656
|
+
// #321 registry prune. Read the file directly rather than via loadConfig():
|
|
12657
|
+
// loadConfig swallows every failure and returns defaults, so an unreadable
|
|
12658
|
+
// config would look like "no projects registered" and the prune would delete
|
|
12659
|
+
// every link on the machine. `undefined` correctly means "don't prune".
|
|
12660
|
+
registeredProjects: () => {
|
|
12661
|
+
try {
|
|
12662
|
+
const parsed = JSON.parse(readFileSync19(join27(dirname6(stateRoot), "config.json"), "utf8"));
|
|
12663
|
+
return Object.keys(parsed.projects ?? {});
|
|
12664
|
+
} catch {
|
|
12665
|
+
return void 0;
|
|
12666
|
+
}
|
|
12667
|
+
}
|
|
12069
12668
|
});
|
|
12070
12669
|
}
|
|
12071
12670
|
function buildNotifyFault(log) {
|