@tritard/waterbrother 0.16.74 → 0.16.76
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/gateway.js +217 -0
package/package.json
CHANGED
package/src/gateway.js
CHANGED
|
@@ -419,6 +419,28 @@ function chooseExecutorAgent(project, fallbackExecutor = {}) {
|
|
|
419
419
|
return null;
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
+
function getLatestBlockingReviewPolicy(project) {
|
|
423
|
+
const events = Array.isArray(project?.recentEvents) ? [...project.recentEvents] : [];
|
|
424
|
+
const ordered = events
|
|
425
|
+
.filter((event) => event?.createdAt)
|
|
426
|
+
.sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")));
|
|
427
|
+
const resolvedAgents = new Set();
|
|
428
|
+
for (const event of ordered) {
|
|
429
|
+
const type = String(event?.type || "").trim();
|
|
430
|
+
const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
|
|
431
|
+
const agentId = String(meta.agentId || "").trim();
|
|
432
|
+
if (!agentId) continue;
|
|
433
|
+
if (type === "review-policy-cleared" || type === "review-policy-override") {
|
|
434
|
+
resolvedAgents.add(agentId);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (type === "review-policy" && String(meta.policy || "").trim() === "blocking" && !resolvedAgents.has(agentId)) {
|
|
438
|
+
return event;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
422
444
|
function memberRoleWeight(role = "") {
|
|
423
445
|
const normalized = String(role || "").trim().toLowerCase();
|
|
424
446
|
if (normalized === "owner") return 3;
|
|
@@ -1778,6 +1800,124 @@ class TelegramGateway {
|
|
|
1778
1800
|
].join("\n")
|
|
1779
1801
|
};
|
|
1780
1802
|
}
|
|
1803
|
+
if (continuation.kind === "blocking-review-override") {
|
|
1804
|
+
const reply = this.stripBotMention(String(message?.text || "").trim()).toLowerCase();
|
|
1805
|
+
const actorId = String(message?.from?.id || "").trim();
|
|
1806
|
+
const actorName = peer?.username || [message?.from?.first_name, message?.from?.last_name].filter(Boolean).join(" ").trim() || actorId;
|
|
1807
|
+
const session = await loadSession(sessionId);
|
|
1808
|
+
if (/\boverride\b/.test(reply)) {
|
|
1809
|
+
await addSharedRoomNote(session.cwd || this.cwd, `${continuation.context?.ownerName || "Assigned"} blocking review overridden for now`, {
|
|
1810
|
+
actorId,
|
|
1811
|
+
actorName,
|
|
1812
|
+
type: "review-policy-override",
|
|
1813
|
+
meta: {
|
|
1814
|
+
agentId: String(continuation.context?.agentId || "").trim()
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
return {
|
|
1818
|
+
markup: [
|
|
1819
|
+
"<b>Blocking review overridden</b>",
|
|
1820
|
+
`reviewer: <code>${escapeTelegramHtml(continuation.context?.ownerName || "unknown")}</code>`,
|
|
1821
|
+
"Execution can proceed for now."
|
|
1822
|
+
].join("\n")
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
if (/\breviewed\b/.test(reply) || /\bdone\b/.test(reply) || /\bcomplete\b/.test(reply)) {
|
|
1826
|
+
await addSharedRoomNote(session.cwd || this.cwd, `${continuation.context?.ownerName || "Assigned"} blocking review marked complete`, {
|
|
1827
|
+
actorId,
|
|
1828
|
+
actorName,
|
|
1829
|
+
type: "review-policy-cleared",
|
|
1830
|
+
meta: {
|
|
1831
|
+
agentId: String(continuation.context?.agentId || "").trim()
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
return {
|
|
1835
|
+
markup: [
|
|
1836
|
+
"<b>Blocking review cleared</b>",
|
|
1837
|
+
`reviewer: <code>${escapeTelegramHtml(continuation.context?.ownerName || "unknown")}</code>`,
|
|
1838
|
+
"Execution can proceed."
|
|
1839
|
+
].join("\n")
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
return {
|
|
1843
|
+
markup: "Reply with <code>override</code> to proceed anyway, or <code>reviewed</code> after the blocking review is complete.",
|
|
1844
|
+
remember: {
|
|
1845
|
+
text: String(continuation.lastPrompt || "").trim() || "Reply with override or reviewed.",
|
|
1846
|
+
kind: continuation.kind,
|
|
1847
|
+
source: continuation.source,
|
|
1848
|
+
context: continuation.context || {},
|
|
1849
|
+
force: true
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
if (continuation.kind === "executor-handoff-override") {
|
|
1854
|
+
const reply = this.stripBotMention(String(message?.text || "").trim()).toLowerCase();
|
|
1855
|
+
const actorId = String(message?.from?.id || "").trim();
|
|
1856
|
+
const actorName = peer?.username || [message?.from?.first_name, message?.from?.last_name].filter(Boolean).join(" ").trim() || actorId;
|
|
1857
|
+
const session = await loadSession(sessionId);
|
|
1858
|
+
if (/\boverride\b/.test(reply)) {
|
|
1859
|
+
await addSharedRoomNote(session.cwd || this.cwd, `${continuation.context?.selectedOwnerName || "Selected executor"} handoff overridden; current terminal may execute`, {
|
|
1860
|
+
actorId,
|
|
1861
|
+
actorName,
|
|
1862
|
+
type: "executor-handoff-override",
|
|
1863
|
+
meta: {
|
|
1864
|
+
selectedAgentId: String(continuation.context?.selectedAgentId || "").trim(),
|
|
1865
|
+
currentOwnerId: String(continuation.context?.currentOwnerId || "").trim()
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
return {
|
|
1869
|
+
markup: [
|
|
1870
|
+
"<b>Executor handoff overridden</b>",
|
|
1871
|
+
`selected executor: <code>${escapeTelegramHtml(continuation.context?.selectedOwnerName || "unknown")}</code>`,
|
|
1872
|
+
"Execution can proceed on the current terminal for now."
|
|
1873
|
+
].join("\n")
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
if (/\buse this terminal\b/.test(reply) || /\bthis terminal\b/.test(reply) || /\buse current\b/.test(reply)) {
|
|
1877
|
+
await upsertSharedAgent(session.cwd || this.cwd, {
|
|
1878
|
+
id: String(continuation.context?.currentAgentId || "").trim(),
|
|
1879
|
+
ownerId: String(continuation.context?.currentOwnerId || "").trim(),
|
|
1880
|
+
ownerName: String(continuation.context?.currentOwnerName || "").trim(),
|
|
1881
|
+
label: String(continuation.context?.currentLabel || "").trim(),
|
|
1882
|
+
surface: String(continuation.context?.currentSurface || "live-tui").trim(),
|
|
1883
|
+
role: "executor",
|
|
1884
|
+
provider: String(continuation.context?.currentProvider || "").trim(),
|
|
1885
|
+
model: String(continuation.context?.currentModel || "").trim(),
|
|
1886
|
+
runtimeProfile: String(continuation.context?.currentRuntimeProfile || "").trim(),
|
|
1887
|
+
sessionId: String(continuation.context?.currentSessionId || "").trim(),
|
|
1888
|
+
cwd: String(session.cwd || this.cwd).trim(),
|
|
1889
|
+
chatId: String(message?.chat?.id || "").trim()
|
|
1890
|
+
}, {
|
|
1891
|
+
actorId,
|
|
1892
|
+
actorName
|
|
1893
|
+
});
|
|
1894
|
+
await addSharedRoomNote(session.cwd || this.cwd, `${continuation.context?.currentOwnerName || "Current terminal"} is now the selected executor`, {
|
|
1895
|
+
actorId,
|
|
1896
|
+
actorName,
|
|
1897
|
+
type: "executor-handoff-reassigned",
|
|
1898
|
+
meta: {
|
|
1899
|
+
currentAgentId: String(continuation.context?.currentAgentId || "").trim()
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
return {
|
|
1903
|
+
markup: [
|
|
1904
|
+
"<b>Executor reassigned</b>",
|
|
1905
|
+
`executor: <code>${escapeTelegramHtml(continuation.context?.currentOwnerName || "current terminal")}</code>`,
|
|
1906
|
+
"Execution can proceed on this terminal."
|
|
1907
|
+
].join("\n")
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
markup: "Reply with <code>override</code> to proceed anyway, or <code>use this terminal</code> to make the current terminal the executor.",
|
|
1912
|
+
remember: {
|
|
1913
|
+
text: String(continuation.lastPrompt || "").trim() || "Reply with override or use this terminal.",
|
|
1914
|
+
kind: continuation.kind,
|
|
1915
|
+
source: continuation.source,
|
|
1916
|
+
context: continuation.context || {},
|
|
1917
|
+
force: true
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1781
1921
|
return null;
|
|
1782
1922
|
}
|
|
1783
1923
|
|
|
@@ -3467,6 +3607,83 @@ Ask them to run <code>/whoami</code> and then <code>/accept-invite ${escapeTeleg
|
|
|
3467
3607
|
await this.sendMessage(message.chat.id, escapeTelegramHtml(gateReason), message.message_id, { parseMode: "HTML" });
|
|
3468
3608
|
return;
|
|
3469
3609
|
}
|
|
3610
|
+
const blockingReview = getLatestBlockingReviewPolicy(operatorGate.project);
|
|
3611
|
+
if (blockingReview) {
|
|
3612
|
+
const meta = blockingReview.meta && typeof blockingReview.meta === "object" ? blockingReview.meta : {};
|
|
3613
|
+
const reviewerName = String(meta.ownerName || meta.agentId || "assigned reviewer").trim();
|
|
3614
|
+
const followUp = `Blocking review is assigned to ${reviewerName}. Reply with override to proceed anyway, or reviewed when the review is done.`;
|
|
3615
|
+
await this.sendMarkup(
|
|
3616
|
+
message.chat.id,
|
|
3617
|
+
[
|
|
3618
|
+
"<b>Blocking review in effect</b>",
|
|
3619
|
+
`reviewer: <code>${escapeTelegramHtml(reviewerName)}</code>`,
|
|
3620
|
+
"Execution is paused until the blocking review is cleared or overridden.",
|
|
3621
|
+
followUp
|
|
3622
|
+
].join("\n"),
|
|
3623
|
+
message.message_id
|
|
3624
|
+
);
|
|
3625
|
+
await this.rememberContinuationWithOptions(message, followUp, {
|
|
3626
|
+
kind: "blocking-review-override",
|
|
3627
|
+
source: "review-policy-gate",
|
|
3628
|
+
context: {
|
|
3629
|
+
agentId: String(meta.agentId || "").trim(),
|
|
3630
|
+
ownerName: reviewerName
|
|
3631
|
+
},
|
|
3632
|
+
force: true
|
|
3633
|
+
});
|
|
3634
|
+
return;
|
|
3635
|
+
}
|
|
3636
|
+
const host = await this.getLiveBridgeHost();
|
|
3637
|
+
const activeExecutor = {
|
|
3638
|
+
surface: host?.surface || (host ? "live-tui" : "telegram-fallback"),
|
|
3639
|
+
provider: host?.provider || this.runtime.provider,
|
|
3640
|
+
model: host?.model || this.runtime.model,
|
|
3641
|
+
runtimeProfile: host?.runtimeProfile || operatorGate.project?.runtimeProfile || this.channel.defaultRuntimeProfile || this.gateway.defaultRuntimeProfile || "",
|
|
3642
|
+
hostSessionId: host?.sessionId || "",
|
|
3643
|
+
ownerId: host?.ownerId || "",
|
|
3644
|
+
ownerName: host?.ownerName || "",
|
|
3645
|
+
label: host?.label || ""
|
|
3646
|
+
};
|
|
3647
|
+
const selectedExecutor = chooseExecutorAgent(operatorGate.project, activeExecutor);
|
|
3648
|
+
if (
|
|
3649
|
+
selectedExecutor?.ownerId
|
|
3650
|
+
&& activeExecutor.ownerId
|
|
3651
|
+
&& String(selectedExecutor.ownerId).trim() !== String(activeExecutor.ownerId).trim()
|
|
3652
|
+
) {
|
|
3653
|
+
const selectedName = String(selectedExecutor.ownerName || selectedExecutor.label || selectedExecutor.ownerId || "selected executor").trim();
|
|
3654
|
+
const currentName = String(activeExecutor.ownerName || activeExecutor.label || activeExecutor.ownerId || "current terminal").trim();
|
|
3655
|
+
const followUp = `Selected executor is ${selectedName}. Reply with override to proceed anyway, or use this terminal to make ${currentName} the executor.`;
|
|
3656
|
+
await this.sendMarkup(
|
|
3657
|
+
message.chat.id,
|
|
3658
|
+
[
|
|
3659
|
+
"<b>Executor handoff required</b>",
|
|
3660
|
+
`selected executor: <code>${escapeTelegramHtml(selectedName)}</code>`,
|
|
3661
|
+
`current terminal: <code>${escapeTelegramHtml(currentName)}</code>`,
|
|
3662
|
+
"Execution is paused because this room selected a different terminal as executor.",
|
|
3663
|
+
followUp
|
|
3664
|
+
].join("\n"),
|
|
3665
|
+
message.message_id
|
|
3666
|
+
);
|
|
3667
|
+
await this.rememberContinuationWithOptions(message, followUp, {
|
|
3668
|
+
kind: "executor-handoff-override",
|
|
3669
|
+
source: "executor-handoff-gate",
|
|
3670
|
+
context: {
|
|
3671
|
+
selectedAgentId: String(selectedExecutor.id || "").trim(),
|
|
3672
|
+
selectedOwnerName: selectedName,
|
|
3673
|
+
currentAgentId: String(host?.sessionId ? `agent:telegram-bridge:${host.sessionId}` : "").trim(),
|
|
3674
|
+
currentOwnerId: String(activeExecutor.ownerId || "").trim(),
|
|
3675
|
+
currentOwnerName: currentName,
|
|
3676
|
+
currentLabel: String(activeExecutor.label || "").trim(),
|
|
3677
|
+
currentSurface: String(activeExecutor.surface || "").trim(),
|
|
3678
|
+
currentProvider: String(activeExecutor.provider || "").trim(),
|
|
3679
|
+
currentModel: String(activeExecutor.model || "").trim(),
|
|
3680
|
+
currentRuntimeProfile: String(activeExecutor.runtimeProfile || "").trim(),
|
|
3681
|
+
currentSessionId: String(activeExecutor.hostSessionId || "").trim()
|
|
3682
|
+
},
|
|
3683
|
+
force: true
|
|
3684
|
+
});
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3470
3687
|
previewMessage = await this.sendProgressMessage(message.chat.id, message.message_id);
|
|
3471
3688
|
const content = (await this.runPromptViaBridge(message, sessionId, promptText, { explicitExecution: shouldExecutePrompt }))
|
|
3472
3689
|
?? (await this.runPromptFallback(sessionId, promptText));
|