@tritard/waterbrother 0.16.77 → 0.16.78
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/package.json +1 -1
- package/src/cli.js +17 -1
- package/src/gateway-state.js +26 -16
- package/src/gateway.js +76 -4
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -3023,6 +3023,15 @@ function createTelegramBridgeHostRecord({ sessionId, cwd, label = "", surface =
|
|
|
3023
3023
|
};
|
|
3024
3024
|
}
|
|
3025
3025
|
|
|
3026
|
+
function upsertTelegramBridgeHostEntry(hosts = [], nextHost = {}) {
|
|
3027
|
+
const list = Array.isArray(hosts) ? [...hosts] : [];
|
|
3028
|
+
const pid = Number(nextHost?.pid || 0);
|
|
3029
|
+
if (!pid) return list;
|
|
3030
|
+
const filtered = list.filter((host) => Number(host?.pid || 0) !== pid);
|
|
3031
|
+
filtered.unshift(nextHost);
|
|
3032
|
+
return filtered.slice(0, 20);
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3026
3035
|
async function syncSharedTelegramBridgeAgent({ cwd, host = {}, actor = {} }) {
|
|
3027
3036
|
try {
|
|
3028
3037
|
const project = await loadSharedProject(cwd);
|
|
@@ -3052,6 +3061,7 @@ async function syncSharedTelegramBridgeAgent({ cwd, host = {}, actor = {} }) {
|
|
|
3052
3061
|
async function registerTelegramBridgeHost({ sessionId, cwd, label = "", surface = "live-tui", ownerId = "", ownerName = "", provider = "", model = "", runtimeProfile = "", actor = null }) {
|
|
3053
3062
|
const bridge = await loadGatewayBridge(TELEGRAM_BRIDGE_SERVICE);
|
|
3054
3063
|
bridge.activeHost = createTelegramBridgeHostRecord({ sessionId, cwd, label, surface, ownerId, ownerName, provider, model, runtimeProfile });
|
|
3064
|
+
bridge.hosts = upsertTelegramBridgeHostEntry(bridge.hosts, bridge.activeHost);
|
|
3055
3065
|
bridge.deliveredReplies = Array.isArray(bridge.deliveredReplies) ? bridge.deliveredReplies.slice(-50) : [];
|
|
3056
3066
|
bridge.pendingRequests = Array.isArray(bridge.pendingRequests) ? bridge.pendingRequests : [];
|
|
3057
3067
|
await saveGatewayBridge(TELEGRAM_BRIDGE_SERVICE, bridge);
|
|
@@ -3079,6 +3089,7 @@ async function touchTelegramBridgeHost({ sessionId, cwd, label = "", surface = "
|
|
|
3079
3089
|
updatedAt: new Date().toISOString()
|
|
3080
3090
|
};
|
|
3081
3091
|
}
|
|
3092
|
+
bridge.hosts = upsertTelegramBridgeHostEntry(bridge.hosts, bridge.activeHost);
|
|
3082
3093
|
await saveGatewayBridge(TELEGRAM_BRIDGE_SERVICE, bridge);
|
|
3083
3094
|
await syncSharedTelegramBridgeAgent({ cwd, host: bridge.activeHost, actor: actor || { id: ownerId, name: ownerName } });
|
|
3084
3095
|
return bridge.activeHost;
|
|
@@ -3088,8 +3099,9 @@ async function clearTelegramBridgeHost() {
|
|
|
3088
3099
|
const bridge = await loadGatewayBridge(TELEGRAM_BRIDGE_SERVICE);
|
|
3089
3100
|
if (Number(bridge.activeHost?.pid || 0) === process.pid) {
|
|
3090
3101
|
bridge.activeHost = { pid: 0, sessionId: "", cwd: "", label: "", surface: "", ownerId: "", ownerName: "", provider: "", model: "", runtimeProfile: "", startedAt: "", updatedAt: "" };
|
|
3091
|
-
await saveGatewayBridge(TELEGRAM_BRIDGE_SERVICE, bridge);
|
|
3092
3102
|
}
|
|
3103
|
+
bridge.hosts = Array.isArray(bridge.hosts) ? bridge.hosts.filter((host) => Number(host?.pid || 0) !== process.pid) : [];
|
|
3104
|
+
await saveGatewayBridge(TELEGRAM_BRIDGE_SERVICE, bridge);
|
|
3093
3105
|
}
|
|
3094
3106
|
|
|
3095
3107
|
async function dequeueTelegramBridgeRequest({ cwd }) {
|
|
@@ -3111,6 +3123,7 @@ async function dequeueTelegramBridgeRequest({ cwd }) {
|
|
|
3111
3123
|
...activeHost,
|
|
3112
3124
|
updatedAt: new Date().toISOString()
|
|
3113
3125
|
};
|
|
3126
|
+
bridge.hosts = upsertTelegramBridgeHostEntry(bridge.hosts, bridge.activeHost);
|
|
3114
3127
|
await saveGatewayBridge(TELEGRAM_BRIDGE_SERVICE, bridge);
|
|
3115
3128
|
return next;
|
|
3116
3129
|
}
|
|
@@ -3135,6 +3148,9 @@ async function deliverTelegramBridgeReply(request, { sessionId, content = "", er
|
|
|
3135
3148
|
sessionId: String(sessionId || "").trim() || String(bridge.activeHost.sessionId || "")
|
|
3136
3149
|
};
|
|
3137
3150
|
}
|
|
3151
|
+
if (Number(bridge.activeHost?.pid || 0) === process.pid) {
|
|
3152
|
+
bridge.hosts = upsertTelegramBridgeHostEntry(bridge.hosts, bridge.activeHost);
|
|
3153
|
+
}
|
|
3138
3154
|
await saveGatewayBridge(TELEGRAM_BRIDGE_SERVICE, bridge);
|
|
3139
3155
|
}
|
|
3140
3156
|
|
package/src/gateway-state.js
CHANGED
|
@@ -75,29 +75,39 @@ function normalizeBridgeReply(parsed = {}) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function normalizeGatewayBridge(parsed = {}) {
|
|
78
|
+
const normalizeHost = (host = {}) => ({
|
|
79
|
+
pid: Number.isFinite(Number(host.pid)) ? Math.floor(Number(host.pid)) : 0,
|
|
80
|
+
sessionId: String(host.sessionId || "").trim(),
|
|
81
|
+
cwd: String(host.cwd || "").trim(),
|
|
82
|
+
label: String(host.label || "").trim(),
|
|
83
|
+
surface: String(host.surface || "").trim(),
|
|
84
|
+
ownerId: String(host.ownerId || "").trim(),
|
|
85
|
+
ownerName: String(host.ownerName || "").trim(),
|
|
86
|
+
provider: String(host.provider || "").trim(),
|
|
87
|
+
model: String(host.model || "").trim(),
|
|
88
|
+
runtimeProfile: String(host.runtimeProfile || "").trim(),
|
|
89
|
+
startedAt: String(host.startedAt || "").trim(),
|
|
90
|
+
updatedAt: String(host.updatedAt || "").trim()
|
|
91
|
+
});
|
|
78
92
|
const pendingRequests = Array.isArray(parsed?.pendingRequests)
|
|
79
93
|
? parsed.pendingRequests.map(normalizeBridgeRequest).filter((item) => item.id)
|
|
80
94
|
: [];
|
|
81
95
|
const deliveredReplies = Array.isArray(parsed?.deliveredReplies)
|
|
82
96
|
? parsed.deliveredReplies.map(normalizeBridgeReply).filter((item) => item.requestId)
|
|
83
97
|
: [];
|
|
98
|
+
const hosts = Array.isArray(parsed?.hosts)
|
|
99
|
+
? parsed.hosts.map(normalizeHost).filter((host) => Number(host.pid || 0) > 0)
|
|
100
|
+
: [];
|
|
101
|
+
const activeHost = parsed?.activeHost && typeof parsed.activeHost === "object"
|
|
102
|
+
? normalizeHost(parsed.activeHost)
|
|
103
|
+
: normalizeHost();
|
|
104
|
+
const mergedHosts = [...hosts];
|
|
105
|
+
if (Number(activeHost.pid || 0) > 0 && !mergedHosts.some((host) => Number(host.pid || 0) === Number(activeHost.pid || 0))) {
|
|
106
|
+
mergedHosts.unshift(activeHost);
|
|
107
|
+
}
|
|
84
108
|
return {
|
|
85
|
-
activeHost
|
|
86
|
-
|
|
87
|
-
pid: Number.isFinite(Number(parsed.activeHost.pid)) ? Math.floor(Number(parsed.activeHost.pid)) : 0,
|
|
88
|
-
sessionId: String(parsed.activeHost.sessionId || "").trim(),
|
|
89
|
-
cwd: String(parsed.activeHost.cwd || "").trim(),
|
|
90
|
-
label: String(parsed.activeHost.label || "").trim(),
|
|
91
|
-
surface: String(parsed.activeHost.surface || "").trim(),
|
|
92
|
-
ownerId: String(parsed.activeHost.ownerId || "").trim(),
|
|
93
|
-
ownerName: String(parsed.activeHost.ownerName || "").trim(),
|
|
94
|
-
provider: String(parsed.activeHost.provider || "").trim(),
|
|
95
|
-
model: String(parsed.activeHost.model || "").trim(),
|
|
96
|
-
runtimeProfile: String(parsed.activeHost.runtimeProfile || "").trim(),
|
|
97
|
-
startedAt: String(parsed.activeHost.startedAt || "").trim(),
|
|
98
|
-
updatedAt: String(parsed.activeHost.updatedAt || "").trim()
|
|
99
|
-
}
|
|
100
|
-
: { pid: 0, sessionId: "", cwd: "", label: "", surface: "", ownerId: "", ownerName: "", provider: "", model: "", runtimeProfile: "", startedAt: "", updatedAt: "" },
|
|
109
|
+
activeHost,
|
|
110
|
+
hosts: mergedHosts.slice(-20),
|
|
101
111
|
pendingRequests,
|
|
102
112
|
deliveredReplies
|
|
103
113
|
};
|
package/src/gateway.js
CHANGED
|
@@ -466,6 +466,13 @@ function summarizeRuntimeConflict(project, fallbackExecutor = {}) {
|
|
|
466
466
|
};
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
+
function formatBridgeHostLabel(host = {}) {
|
|
470
|
+
const owner = String(host?.ownerName || host?.ownerId || "").trim();
|
|
471
|
+
const label = String(host?.label || "").trim();
|
|
472
|
+
const runtime = host?.provider && host?.model ? `${host.provider}/${host.model}` : "";
|
|
473
|
+
return [owner || label, label && label !== owner ? `(${label})` : "", runtime ? `[${runtime}]` : ""].filter(Boolean).join(" ").trim();
|
|
474
|
+
}
|
|
475
|
+
|
|
469
476
|
function getLatestBlockingReviewPolicy(project) {
|
|
470
477
|
const events = Array.isArray(project?.recentEvents) ? [...project.recentEvents] : [];
|
|
471
478
|
const ordered = events
|
|
@@ -730,6 +737,9 @@ function parseTelegramStateIntent(text = "") {
|
|
|
730
737
|
if (/\bwho are the bots\b/.test(lower) || /\bwho are the agents\b/.test(lower) || /\bwhat bots are here\b/.test(lower) || /\bwhat agents are here\b/.test(lower)) {
|
|
731
738
|
return { action: "agent-list" };
|
|
732
739
|
}
|
|
740
|
+
if (/\bwhich terminals are live\b/.test(lower) || /\bwhich bots are live\b/.test(lower) || /\bwho is live\b/.test(lower) || /\bwhat terminals are live\b/.test(lower)) {
|
|
741
|
+
return { action: "live-hosts" };
|
|
742
|
+
}
|
|
733
743
|
if (/\bmodel conflict\b/.test(lower) || /\bruntime conflict\b/.test(lower) || /\bmodel split\b/.test(lower) || /\bruntime split\b/.test(lower) || /\bcompare models\b/.test(lower) || /\bcompare bots\b/.test(lower)) {
|
|
734
744
|
return { action: "model-conflict" };
|
|
735
745
|
}
|
|
@@ -854,6 +864,7 @@ function formatTelegramRoomMarkup(project, options = {}) {
|
|
|
854
864
|
? agents.map((agent) => `• ${escapeTelegramHtml(formatAgentLabel(agent) || agent.id || "agent")} <code>${escapeTelegramHtml(agent.surface || "unknown")}</code>`)
|
|
855
865
|
: ["• none"];
|
|
856
866
|
const runtimeConflict = summarizeRuntimeConflict(project, executor);
|
|
867
|
+
const liveHosts = Array.isArray(options.liveHosts) ? options.liveHosts : [];
|
|
857
868
|
const executorBits = [
|
|
858
869
|
`surface: <code>${escapeTelegramHtml(executor.surface || "telegram")}</code>`,
|
|
859
870
|
`provider: <code>${escapeTelegramHtml(executor.provider || "unknown")}</code>`,
|
|
@@ -888,6 +899,11 @@ function formatTelegramRoomMarkup(project, options = {}) {
|
|
|
888
899
|
.filter(Boolean);
|
|
889
900
|
return `• <code>${escapeTelegramHtml(group.runtime)}</code>${owners.length ? ` — ${escapeTelegramHtml(owners.join(", "))}` : ""}`;
|
|
890
901
|
}),
|
|
902
|
+
"<b>Live Terminals</b>",
|
|
903
|
+
`count: <code>${escapeTelegramHtml(String(liveHosts.length))}</code>`,
|
|
904
|
+
...(liveHosts.length
|
|
905
|
+
? liveHosts.map((host) => `• ${escapeTelegramHtml(formatBridgeHostLabel(host) || host.sessionId || "terminal")} <code>${escapeTelegramHtml(host.surface || "live-tui")}</code>`)
|
|
906
|
+
: ["• none"]),
|
|
891
907
|
"<b>Task Summary</b>",
|
|
892
908
|
`total: <code>${tasks.length}</code>`,
|
|
893
909
|
...[...byState.entries()].map(([state, count]) => `${escapeTelegramHtml(state)}: <code>${count}</code>`),
|
|
@@ -2067,6 +2083,20 @@ class TelegramGateway {
|
|
|
2067
2083
|
].join("\n");
|
|
2068
2084
|
}
|
|
2069
2085
|
|
|
2086
|
+
if (intent.action === "live-hosts") {
|
|
2087
|
+
const hosts = await this.getLiveBridgeHosts({ cwd });
|
|
2088
|
+
if (!hosts.length) {
|
|
2089
|
+
return "<b>Live terminals</b>\n• none";
|
|
2090
|
+
}
|
|
2091
|
+
return [
|
|
2092
|
+
"<b>Live terminals</b>",
|
|
2093
|
+
...hosts.map((host) => {
|
|
2094
|
+
const roleAgent = listProjectAgents(project).find((agent) => String(agent?.sessionId || "").trim() === String(host?.sessionId || "").trim()) || null;
|
|
2095
|
+
return `• ${escapeTelegramHtml(formatBridgeHostLabel(host) || host.sessionId || "terminal")} <i>(${escapeTelegramHtml(roleAgent?.role || "live")})</i> <code>${escapeTelegramHtml(host.surface || "live-tui")}</code>`;
|
|
2096
|
+
})
|
|
2097
|
+
].join("\n");
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2070
2100
|
if (intent.action === "model-conflict") {
|
|
2071
2101
|
if (!project?.enabled) {
|
|
2072
2102
|
return "This project is not shared.";
|
|
@@ -2141,12 +2171,20 @@ class TelegramGateway {
|
|
|
2141
2171
|
const operatorLabel = project.activeOperator?.id
|
|
2142
2172
|
? `${project.activeOperator.name || project.activeOperator.id}`
|
|
2143
2173
|
: "none";
|
|
2174
|
+
const liveHosts = await this.getLiveBridgeHosts({ cwd });
|
|
2175
|
+
const isLive = liveHosts.some((host) => {
|
|
2176
|
+
const ownerId = String(host?.ownerId || "").trim();
|
|
2177
|
+
const sessionId = String(host?.sessionId || "").trim();
|
|
2178
|
+
return (ownerId && ownerId === String(agent.ownerId || "").trim())
|
|
2179
|
+
|| (sessionId && sessionId === String(agent.sessionId || "").trim());
|
|
2180
|
+
});
|
|
2144
2181
|
return [
|
|
2145
2182
|
"<b>Execution recommendation</b>",
|
|
2146
2183
|
`take this on: <code>${escapeTelegramHtml(agent.ownerName || agent.label || agent.ownerId || agent.id || "unknown")}</code>`,
|
|
2147
2184
|
`role: <code>${escapeTelegramHtml(agent.role || "executor")}</code>`,
|
|
2148
2185
|
`surface: <code>${escapeTelegramHtml(agent.surface || "unknown")}</code>`,
|
|
2149
2186
|
`runtime: <code>${escapeTelegramHtml(agent.provider && agent.model ? `${agent.provider}/${agent.model}` : "unknown")}</code>`,
|
|
2187
|
+
`live now: <code>${escapeTelegramHtml(isLive ? "yes" : "no")}</code>`,
|
|
2150
2188
|
`active operator: <code>${escapeTelegramHtml(operatorLabel)}</code>`
|
|
2151
2189
|
].join("\n");
|
|
2152
2190
|
}
|
|
@@ -2493,6 +2531,7 @@ class TelegramGateway {
|
|
|
2493
2531
|
async buildRoomMarkup(message, sessionId) {
|
|
2494
2532
|
const { project } = await this.bindSharedRoomForMessage(message, sessionId);
|
|
2495
2533
|
const host = await this.getLiveBridgeHost();
|
|
2534
|
+
const liveHosts = await this.getLiveBridgeHosts({ cwd: project?.cwd || this.cwd });
|
|
2496
2535
|
const executor = {
|
|
2497
2536
|
surface: host?.surface || (host ? "live-tui" : "telegram-fallback"),
|
|
2498
2537
|
provider: host?.provider || this.runtime.provider,
|
|
@@ -2504,10 +2543,10 @@ class TelegramGateway {
|
|
|
2504
2543
|
? `${formatTelegramSummaryMarkup({
|
|
2505
2544
|
cwd: project.cwd || this.cwd,
|
|
2506
2545
|
project,
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
})}\n\n${formatTelegramRoomMarkup(project, { executor })}`
|
|
2546
|
+
chatId: String(message.chat.id),
|
|
2547
|
+
title: String(message.chat.title || "").trim(),
|
|
2548
|
+
executor
|
|
2549
|
+
})}\n\n${formatTelegramRoomMarkup(project, { executor, liveHosts })}`
|
|
2511
2550
|
: "<b>Shared room</b>\nThis project is not shared.";
|
|
2512
2551
|
}
|
|
2513
2552
|
|
|
@@ -2558,6 +2597,39 @@ class TelegramGateway {
|
|
|
2558
2597
|
return host;
|
|
2559
2598
|
}
|
|
2560
2599
|
|
|
2600
|
+
async getLiveBridgeHosts({ cwd = "" } = {}) {
|
|
2601
|
+
const bridge = await loadGatewayBridge("telegram");
|
|
2602
|
+
const hosts = Array.isArray(bridge.hosts) ? bridge.hosts : [];
|
|
2603
|
+
const nextHosts = [];
|
|
2604
|
+
let changed = false;
|
|
2605
|
+
for (const host of hosts) {
|
|
2606
|
+
const pid = Number(host?.pid || 0);
|
|
2607
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
2608
|
+
changed = true;
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
try {
|
|
2612
|
+
process.kill(pid, 0);
|
|
2613
|
+
} catch {
|
|
2614
|
+
changed = true;
|
|
2615
|
+
continue;
|
|
2616
|
+
}
|
|
2617
|
+
if (cwd && host.cwd && String(host.cwd) !== String(cwd || "")) {
|
|
2618
|
+
continue;
|
|
2619
|
+
}
|
|
2620
|
+
nextHosts.push(host);
|
|
2621
|
+
}
|
|
2622
|
+
if (changed || nextHosts.length !== hosts.length) {
|
|
2623
|
+
bridge.hosts = nextHosts;
|
|
2624
|
+
const activePid = Number(bridge.activeHost?.pid || 0);
|
|
2625
|
+
if (activePid > 0 && !nextHosts.some((host) => Number(host?.pid || 0) === activePid)) {
|
|
2626
|
+
bridge.activeHost = { pid: 0, sessionId: "", cwd: "", label: "", surface: "", ownerId: "", ownerName: "", provider: "", model: "", runtimeProfile: "", startedAt: "", updatedAt: "" };
|
|
2627
|
+
}
|
|
2628
|
+
await saveGatewayBridge("telegram", bridge);
|
|
2629
|
+
}
|
|
2630
|
+
return nextHosts;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2561
2633
|
async runPromptViaBridge(message, sessionId, promptText, options = {}) {
|
|
2562
2634
|
const host = await this.getLiveBridgeHost();
|
|
2563
2635
|
if (!host) {
|