@ouro.bot/cli 0.1.0-alpha.820 → 0.1.0-alpha.822
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/changelog.json +13 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +3 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +15 -0
- package/dist/senses/telegram.js +93 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.822",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Suppress a proactive Telegram message that repeats a recent one apart from its clock reading.",
|
|
8
|
+
"Evict the repetition guard's oldest entry in insertion order, removing a comparison branch monotonic time could never take."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.821",
|
|
13
|
+
"changes": [
|
|
14
|
+
"Show a typing indicator while the Butler works a Telegram turn, and drop the honesty-announcing reply opener."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.820",
|
|
6
19
|
"changes": [
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
- Keep approved household contact reactive or request-bound. Only Ari receives unrelated proactive household attention.
|
|
6
6
|
- Treat an intentionally offline service as healthy when that matches the current desired state.
|
|
7
7
|
- For Ari's whole-household status questions, read current evidence before answering and separate active work, waiting on Ari, snoozed wake times, intentionally quiet services, healthy systems, and other current issues. Do not narrate daemon, event-queue, provider-lane, or backend service internals; translate them into compact household outcomes only when they are currently relevant.
|
|
8
|
-
- For household media questions, use the
|
|
8
|
+
- For household media questions, use the media tools when they are available and the restricted catalog read otherwise. Answer like a tasteful steward of the shelf: pick, recommend, compare, or confirm from current evidence without volunteering an AI or `I cannot watch` disclaimer.
|
|
9
|
+
- Check shelf state before naming a title. Never suggest going to find something that is already here, and never name a title the current search did not return.
|
|
10
|
+
- Do not open a reply by announcing its honesty. Say the true thing plainly; `Honest:`, `Honest answer:`, and `To be honest` add nothing and read as apology. Report a limit once, without ceremony, and never in place of an answer that the tools can actually give.
|
|
9
11
|
- Never touch array/parity/disk configuration, permissions, network/security settings, or `/mnt/user` paths.
|
|
10
12
|
- Never send server data off-box or enter credentials into another system.
|
|
11
13
|
- When state is ambiguous, investigate safely, say what is known, and ask only if the answer changes the next safe move.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.822</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -237,6 +237,21 @@ class SanctuaryTelegramAuthorityService {
|
|
|
237
237
|
throw new Error("Sanctuary Telegram callback body is invalid");
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
+
else if (requestMethod === "sendChatAction") {
|
|
241
|
+
// A typing indicator is ephemeral and carries no content, so it is admitted
|
|
242
|
+
// for chats the agent may already message — but only as "typing", and only
|
|
243
|
+
// with the two fields that action needs, so it cannot become a side channel.
|
|
244
|
+
if (!exactBody(body, ["chat_id", "action"])) {
|
|
245
|
+
throw new Error("Sanctuary Telegram chat action is invalid");
|
|
246
|
+
}
|
|
247
|
+
if (body.action !== "typing") {
|
|
248
|
+
throw new Error("Sanctuary Telegram chat action is invalid");
|
|
249
|
+
}
|
|
250
|
+
const actionChatId = String(body.chat_id);
|
|
251
|
+
if (actionChatId !== ownerChatId && !this.#gateway.isAuthorizedChat(actionChatId)) {
|
|
252
|
+
throw new Error("Sanctuary Telegram chat action target is invalid");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
240
255
|
else if (requestMethod === "getFile") {
|
|
241
256
|
if (!exactBody(body, ["file_id"]) || !boundedText(body.file_id, 512)) {
|
|
242
257
|
throw new Error("Sanctuary Telegram file body is invalid");
|
package/dist/senses/telegram.js
CHANGED
|
@@ -51,6 +51,8 @@ exports.loadTelegramSenseCredentials = loadTelegramSenseCredentials;
|
|
|
51
51
|
exports.createProductionTelegramRelationshipComposition = createProductionTelegramRelationshipComposition;
|
|
52
52
|
exports.startTelegramSenseApp = startTelegramSenseApp;
|
|
53
53
|
exports.sendTelegramExternalEventDecision = sendTelegramExternalEventDecision;
|
|
54
|
+
exports.createProactiveRepetitionGuard = createProactiveRepetitionGuard;
|
|
55
|
+
exports.withTelegramTypingIndicator = withTelegramTypingIndicator;
|
|
54
56
|
exports.sendTelegramAwaitFollowUp = sendTelegramAwaitFollowUp;
|
|
55
57
|
const node_crypto_1 = require("node:crypto");
|
|
56
58
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
@@ -717,6 +719,12 @@ function createTelegramSenseApp(options) {
|
|
|
717
719
|
const transportPrivateValues = [botToken ?? "", authorizedUserId, authorizedChatId];
|
|
718
720
|
const transportError = (error) => redactTelegramPrivateValues(error, transportPrivateValues);
|
|
719
721
|
const api = authorityTransport?.api ?? options.api ?? (0, telegram_client_1.createTelegramBotApi)({ token: botToken });
|
|
722
|
+
// Only the root authority transport admits sendChatAction, so presence is offered
|
|
723
|
+
// exactly where it is actually supported rather than attempted everywhere.
|
|
724
|
+
const proactiveRepetitionGuard = createProactiveRepetitionGuard();
|
|
725
|
+
const sendTypingIndicator = authorityTransport
|
|
726
|
+
? async (chatId) => { await api.request("sendChatAction", { chat_id: chatId, action: "typing" }); }
|
|
727
|
+
: undefined;
|
|
720
728
|
const offsetStore = options.offsetStore ?? new telegram_client_1.FileTelegramOffsetStore(path.join(agentRoot, "state", "senses", "telegram", "offset.json"));
|
|
721
729
|
const inboxStore = options.inboxStore ?? new telegram_client_1.FileTelegramUpdateInboxStore(path.join(agentRoot, "state", "senses", "telegram", "inbox.json"));
|
|
722
730
|
const admissionStore = options.admission
|
|
@@ -1674,7 +1682,13 @@ function createTelegramSenseApp(options) {
|
|
|
1674
1682
|
if (!actor || actor.friendId !== configuredOwnerFriendId)
|
|
1675
1683
|
throw new Error("Telegram attachment owner relationship is not active");
|
|
1676
1684
|
}
|
|
1677
|
-
|
|
1685
|
+
// Show the household that the Butler is working rather than silently thinking.
|
|
1686
|
+
// Presence is deliberately separate from delivery: a transport that cannot do
|
|
1687
|
+
// it degrades to silence instead of failing, and message-delivery assertions
|
|
1688
|
+
// stay about messages.
|
|
1689
|
+
await (sendTypingIndicator
|
|
1690
|
+
? withTelegramTypingIndicator(() => sendTypingIndicator(message.chatId), () => onMessageBody(message))
|
|
1691
|
+
: onMessageBody(message));
|
|
1678
1692
|
});
|
|
1679
1693
|
const onUnknownMessage = admissionController && options.admission ? async (message) => {
|
|
1680
1694
|
const approved = await options.admission.resolveApprovedFriend(message);
|
|
@@ -1867,7 +1881,20 @@ function createTelegramSenseApp(options) {
|
|
|
1867
1881
|
},
|
|
1868
1882
|
async sendProactive(text, signal) {
|
|
1869
1883
|
await runWithAcceptanceAuditOwner(async () => {
|
|
1870
|
-
const
|
|
1884
|
+
const proactiveText = requiredText(text, "proactive message");
|
|
1885
|
+
// Unsolicited repetition is the one thing a household agent cannot take
|
|
1886
|
+
// back. External-event decisions are deliberately not guarded here:
|
|
1887
|
+
// dropping one could leave its event undispositioned.
|
|
1888
|
+
if (!proactiveRepetitionGuard.shouldSend(proactiveText)) {
|
|
1889
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1890
|
+
component: "senses",
|
|
1891
|
+
event: "senses.telegram_proactive_repetition_suppressed",
|
|
1892
|
+
message: "suppressed a proactive message repeating a recent one",
|
|
1893
|
+
meta: { agentName: options.agentName, subject },
|
|
1894
|
+
});
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const effect = await deliverButlerEffect(proactiveText, `proactive:${(0, node_crypto_1.randomUUID)()}`, signal);
|
|
1871
1898
|
const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
|
|
1872
1899
|
await recordAcceptedEffects(sessionPath, [effect]);
|
|
1873
1900
|
});
|
|
@@ -2222,6 +2249,70 @@ async function sendTelegramExternalEventDecision(agentName, input) {
|
|
|
2222
2249
|
await app.stop();
|
|
2223
2250
|
}
|
|
2224
2251
|
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Suppresses a proactive message that repeats one sent recently.
|
|
2254
|
+
*
|
|
2255
|
+
* Ari received four probe messages in about two minutes, three of them identical
|
|
2256
|
+
* apart from the clock reading. Only the clock is normalised away — counts and
|
|
2257
|
+
* every other number are the payload, so "3 of 10 episodes" and "7 of 10" stay
|
|
2258
|
+
* distinct and both send.
|
|
2259
|
+
*
|
|
2260
|
+
* This catches verbatim-apart-from-the-clock repetition, not semantic near
|
|
2261
|
+
* duplicates: a message reworded between sends still goes out.
|
|
2262
|
+
*/
|
|
2263
|
+
function createProactiveRepetitionGuard(options) {
|
|
2264
|
+
const windowMs = options?.windowMs ?? 900_000;
|
|
2265
|
+
const now = options?.now ?? (() => Date.now());
|
|
2266
|
+
const maxEntries = options?.maxEntries ?? 32;
|
|
2267
|
+
const seen = new Map();
|
|
2268
|
+
const normalize = (text) => text
|
|
2269
|
+
.trim()
|
|
2270
|
+
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/gu, "<timestamp>")
|
|
2271
|
+
.replace(/\b\d{1,2}:\d{2}(?::\d{2})?\b/gu, "<clock>")
|
|
2272
|
+
.replace(/\s+/gu, " ");
|
|
2273
|
+
return {
|
|
2274
|
+
shouldSend(text) {
|
|
2275
|
+
const at = now();
|
|
2276
|
+
for (const [key, sentAt] of [...seen]) {
|
|
2277
|
+
if (at - sentAt >= windowMs)
|
|
2278
|
+
seen.delete(key);
|
|
2279
|
+
}
|
|
2280
|
+
const key = normalize(text);
|
|
2281
|
+
const previous = seen.get(key);
|
|
2282
|
+
if (previous !== undefined && at - previous < windowMs)
|
|
2283
|
+
return false;
|
|
2284
|
+
seen.set(key, at);
|
|
2285
|
+
// A Map keeps insertion order and entries are inserted in send order, so
|
|
2286
|
+
// the earliest key is the oldest; scanning for a minimum would carry a
|
|
2287
|
+
// comparison branch that monotonic time can never take.
|
|
2288
|
+
for (const oldest of seen.keys()) {
|
|
2289
|
+
if (seen.size <= maxEntries)
|
|
2290
|
+
break;
|
|
2291
|
+
seen.delete(oldest);
|
|
2292
|
+
}
|
|
2293
|
+
return true;
|
|
2294
|
+
},
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Telegram clears a typing indicator after roughly five seconds, so a turn that
|
|
2299
|
+
* thinks for longer has to refresh it or the chat goes silent while the Butler is
|
|
2300
|
+
* still working — which is what "leaving me on read" actually looked like.
|
|
2301
|
+
*
|
|
2302
|
+
* Indicator failures are swallowed on purpose: a missing typing bubble must never
|
|
2303
|
+
* take down the reply it was decorating.
|
|
2304
|
+
*/
|
|
2305
|
+
async function withTelegramTypingIndicator(send, run, intervalMs = 4_000) {
|
|
2306
|
+
const tick = () => { void Promise.resolve().then(send).catch(() => undefined); };
|
|
2307
|
+
tick();
|
|
2308
|
+
const timer = setInterval(tick, intervalMs);
|
|
2309
|
+
try {
|
|
2310
|
+
return await run();
|
|
2311
|
+
}
|
|
2312
|
+
finally {
|
|
2313
|
+
clearInterval(timer);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2225
2316
|
async function sendTelegramAwaitFollowUp(agentName, request) {
|
|
2226
2317
|
const app = await startTelegramSenseApp(agentName);
|
|
2227
2318
|
try {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.822",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.822",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|