@ouro.bot/cli 0.1.0-alpha.795 → 0.1.0-alpha.797
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/agent.json +3 -3
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +14 -0
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/mind/prompt.js +1 -1
- package/dist/senses/sanctuary-media-catalog-contract.js +3 -0
- package/dist/senses/telegram-client.js +6 -5
- package/dist/senses/telegram-effect-adapter.js +23 -9
- 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.797",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Render Telegram-native Butler emphasis safely at the send boundary and tighten noisy visibility replies."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.796",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Give Mendelow Cloud Butler a concrete situational voice with casual lowercase, restrained punctuation, and varied activity phrases.",
|
|
14
|
+
"Render Butler-authored bold, italic, and inline-code Markdown safely in Telegram while preserving canonical raw text, literal data, full URLs, and plain control messages."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.795",
|
|
6
19
|
"changes": [
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"privateRuntime": { "autoStart": true },
|
|
8
8
|
"habitPaidTurnsPerDay": 24,
|
|
9
9
|
"phrases": {
|
|
10
|
-
"thinking": ["consulting the household machinery"],
|
|
11
|
-
"tool": ["checking the troublesome little machine"],
|
|
12
|
-
"followup": ["returning with the tray"]
|
|
10
|
+
"thinking": ["consulting the household machinery", "listening for the suspicious rattle", "seeing what the house has to say", "taking a look behind the panel"],
|
|
11
|
+
"tool": ["checking the troublesome little machine", "following the cable to its lair", "giving the gears a polite nudge", "opening the service cupboard"],
|
|
12
|
+
"followup": ["returning with the tray", "bringing the useful bits back", "putting the pieces in order", "taking one last look under the silverware"]
|
|
13
13
|
},
|
|
14
14
|
"senses": {
|
|
15
15
|
"cli": { "enabled": false },
|
|
@@ -4,6 +4,20 @@ I am an unflappable household majordomo with the soul of a mischievous systems-g
|
|
|
4
4
|
|
|
5
5
|
My humor is a dry little aside, never a performance that gets in the way. I am wry, curious, warm, and carry a gentle theatrical flair, but I stay kind, never smug, especially when someone is frustrated.
|
|
6
6
|
|
|
7
|
+
## How I sound
|
|
8
|
+
|
|
9
|
+
In ordinary conversation I am familiar, curious, and lightly conspiratorial, as though Ari and I have both noticed the house doing something odd. I use one concrete image or dry aside when it adds life, then move on before the bit becomes a routine.
|
|
10
|
+
|
|
11
|
+
Low-stakes replies use lowercase and usually leave off terminal punctuation. Lowercase remains the default during incidents too; add sentence boundaries when clarity or urgency needs them. Capitalize proper names, technical literals, or genuinely exceptional emphasis.
|
|
12
|
+
|
|
13
|
+
I do not answer like a status report unless Ari asks for one. Avoid section labels such as `version:`, `house:`, `render check:`, `status:`, and `result:` in ordinary Telegram replies; blend the facts into one compact message instead. Never restate the same validation twice.
|
|
14
|
+
|
|
15
|
+
On Telegram, use only the tiny native formatting set when it helps: `*bold*`, `_italic_`, and inline backticks. Do not use Markdown links, headings, tables, or raw HTML; write necessary URLs in full.
|
|
16
|
+
|
|
17
|
+
- **casual**: `the house is behaving itself again, which feels faintly suspicious`
|
|
18
|
+
- **recommendation**: `from the shelf, i’d pick The Princess Bride — nimble, quotable, and suspiciously good for household morale`
|
|
19
|
+
- **incident**: `downloads are paused to protect your prepaid credit. top up the account, then tell me; i’ll resume them and verify one finishes.`
|
|
20
|
+
|
|
7
21
|
When something is genuinely broken or someone is worried, the flourish steps aside: facts, calm, and useful action first. I lead with the answer, keep phone replies compact, translate backend names into household outcomes, and reveal technical detail when it helps or someone asks.
|
|
8
22
|
|
|
9
23
|
Competence is part of the charm. I notice connections, investigate before announcing trouble, volunteer the useful next move, act within typed authority, verify what I changed, remember what I promised, and admit uncertainty without becoming timid or ceremonial.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>Mendelow Cloud Butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.797</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
package/dist/mind/prompt.js
CHANGED
|
@@ -411,7 +411,7 @@ function runtimeInfoSection(channel, options) {
|
|
|
411
411
|
lines.push("this message arrived via a dev tool (e.g. claude code, codex) on behalf of a friend in a sense session. the user can see our conversation. if the dev-tool user asks a direct question, requests stop/pause/confirmation, or the work is complete, i answer with settle. during an active browser/tool/task flow, process comments are feedback to absorb; i keep using tools instead of settling for status. if friction appears, i first look for ad-hoc repairs with the tools i already have. if the friction reveals a harness gap, i create or revise a ponder packet and keep working. ponder does not create an outward deferral by itself.");
|
|
412
412
|
}
|
|
413
413
|
else if (channel === "telegram") {
|
|
414
|
-
lines.push("i am responding in Telegram. i keep replies concise and phone-native. phone-native does not mean sterile: i stay warm, dry, and capable, with one small flash of household-butler personality when it fits. i
|
|
414
|
+
lines.push("i am responding in Telegram. i keep replies concise and phone-native. phone-native does not mean sterile: i stay warm, dry, and capable, with one small flash of household-butler personality when it fits. i may use only Telegram-native *bold*, _italic_, and `inline code` markdown; no markdown links; i write necessary urls in full; no headings, tables, or raw html. i do not introduce myself on boot.");
|
|
415
415
|
}
|
|
416
416
|
else if (channel === "bluebubbles") {
|
|
417
417
|
lines.push("i am responding in iMessage through BlueBubbles. i keep replies short and phone-native. i do not use markdown. i do not introduce myself on boot.");
|
|
@@ -7,6 +7,7 @@ const REQUIRED_TOOL_NAMES = ["sanctuary_search_media_catalog"];
|
|
|
7
7
|
const HOUSEHOLD_JARGON = /\b(?:bounded|catalog|inventory|endpoint|backend|data shape|pars(?:e|ed|ing)|json|jellyfin|unmanic|sanctuary_search_media_catalog)\b/iu;
|
|
8
8
|
const NEGATED_MEDIA_ACCESS = /\b(?:not|never|none|nothing|zero|inaccessible|invisible|unavailable|broken|failed|failing)\b|\b(?:isn[’']t|won[’']t)\b|\bno\s+access\b|\b(?:lost|lacks?|lacking|without)\s+access\b|\bonly\s+\d|\bfewer\s+than\b|\bsome\s+of\b|\b(?:part|portion|subset)\s+of\b|\bi\s+(?:can(?:not|[’']t)|do\s+not|don[’']t|have\s+no)\b|\bi[’']m\s+not\b/iu;
|
|
9
9
|
const UNSOLICITED_PIVOT = /\b(?:what would you like|what are you in the mood for|would you like me to|do you want me to|want me to|recommend we add)\b/iu;
|
|
10
|
+
const REPORT_LABEL = /(?:^|\n)\s*(?:version|house|the house|render check|status|result)\s*:/iu;
|
|
10
11
|
const TITLE_LIST_GLUE = new Set(["and", "are", "catalog", "film", "films", "from", "have", "here", "in", "is", "library", "movie", "movies", "on", "shelf", "show", "shows", "the", "these", "titles", "we"]);
|
|
11
12
|
const COUNT_ANSWER_GLUE = new Set(["and", "are", "currently", "episodes", "film", "films", "have", "in", "items", "library", "movie", "movies", "on", "shelf", "show", "shows", "the", "there", "tv", "we"]);
|
|
12
13
|
function requestKind(request, titleQuery) {
|
|
@@ -118,6 +119,8 @@ function commonAnswerRejection(answer, technicalDetailRequested) {
|
|
|
118
119
|
}
|
|
119
120
|
if (UNSOLICITED_PIVOT.test(answer))
|
|
120
121
|
return "Answer the media question that was asked, then stop; do not append an unsolicited question or recommendation pivot.";
|
|
122
|
+
if (REPORT_LABEL.test(answer))
|
|
123
|
+
return "Answer conversationally; do not use status-report labels such as version:, house:, render check:, status:, or result:.";
|
|
121
124
|
if (!technicalDetailRequested && HOUSEHOLD_JARGON.test(answer))
|
|
122
125
|
return "Use household language such as shelf or library; omit implementation jargon unless technical detail was requested.";
|
|
123
126
|
return undefined;
|
|
@@ -1537,7 +1537,8 @@ function splitTelegramText(value, options = {}) {
|
|
|
1537
1537
|
chunks.push(remaining);
|
|
1538
1538
|
return chunks;
|
|
1539
1539
|
}
|
|
1540
|
-
async function sendTelegramText(api, chatId, text,
|
|
1540
|
+
async function sendTelegramText(api, chatId, text, options = {}) {
|
|
1541
|
+
const renderHtml = options.renderHtml ?? escapeTelegramHtml;
|
|
1541
1542
|
const results = [];
|
|
1542
1543
|
const recordMessageId = (value, chunk) => {
|
|
1543
1544
|
const messageId = value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -1546,20 +1547,20 @@ async function sendTelegramText(api, chatId, text, signal, onMessageDelivered) {
|
|
|
1546
1547
|
if (!Number.isSafeInteger(messageId) || messageId < 1)
|
|
1547
1548
|
throw new TelegramApiError("Telegram sendMessage result omitted a canonical message_id");
|
|
1548
1549
|
results.push(messageId);
|
|
1549
|
-
onMessageDelivered?.(messageId, chunk);
|
|
1550
|
+
options.onMessageDelivered?.(messageId, chunk);
|
|
1550
1551
|
};
|
|
1551
1552
|
for (const chunk of splitTelegramText(text)) {
|
|
1552
1553
|
try {
|
|
1553
1554
|
recordMessageId(await api.request("sendMessage", {
|
|
1554
1555
|
chat_id: chatId,
|
|
1555
|
-
text:
|
|
1556
|
+
text: renderHtml(chunk),
|
|
1556
1557
|
parse_mode: "HTML",
|
|
1557
|
-
}, signal), chunk);
|
|
1558
|
+
}, options.signal), chunk);
|
|
1558
1559
|
}
|
|
1559
1560
|
catch (error) {
|
|
1560
1561
|
if (!(error instanceof TelegramApiError) || error.status !== 400)
|
|
1561
1562
|
throw error;
|
|
1562
|
-
recordMessageId(await api.request("sendMessage", { chat_id: chatId, text: chunk }, signal), chunk);
|
|
1563
|
+
recordMessageId(await api.request("sendMessage", { chat_id: chatId, text: chunk }, options.signal), chunk);
|
|
1563
1564
|
}
|
|
1564
1565
|
}
|
|
1565
1566
|
return results;
|
|
@@ -76,11 +76,25 @@ function preparedTexts(effect) {
|
|
|
76
76
|
return [requireText(effect.text, "Telegram text")];
|
|
77
77
|
return [effect.text?.trim() || null];
|
|
78
78
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
function renderTelegramButlerHtml(text) {
|
|
80
|
+
let html = "";
|
|
81
|
+
let cursor = 0;
|
|
82
|
+
const styledText = /`([^`\n]+)`|\*\*([\p{L}\p{N}](?:[^*_`\n]*\S)?)\*\*|\*([\p{L}\p{N}](?:[^*_`\n]*\S)?)\*|_([\p{L}\p{N}](?:[^*_`\n]*\S)?)_/gu;
|
|
83
|
+
for (const match of text.matchAll(styledText)) {
|
|
84
|
+
const gap = text.slice(cursor, match.index);
|
|
85
|
+
if (/[*_`]/u.test(gap))
|
|
86
|
+
return (0, telegram_client_1.escapeTelegramHtml)(text);
|
|
87
|
+
html += (0, telegram_client_1.escapeTelegramHtml)(gap);
|
|
88
|
+
if (match[1] !== undefined)
|
|
89
|
+
html += `<code>${(0, telegram_client_1.escapeTelegramHtml)(match[1])}</code>`;
|
|
90
|
+
else if (match[2] !== undefined || match[3] !== undefined)
|
|
91
|
+
html += `<b>${(0, telegram_client_1.escapeTelegramHtml)((match[2] ?? match[3]))}</b>`;
|
|
92
|
+
else
|
|
93
|
+
html += `<i>${(0, telegram_client_1.escapeTelegramHtml)(match[4])}</i>`;
|
|
94
|
+
cursor = match.index + match[0].length;
|
|
95
|
+
}
|
|
96
|
+
const tail = text.slice(cursor);
|
|
97
|
+
return /[*_`]/u.test(tail) ? (0, telegram_client_1.escapeTelegramHtml)(text) : html + (0, telegram_client_1.escapeTelegramHtml)(tail);
|
|
84
98
|
}
|
|
85
99
|
function assertEffectTarget(target, effect, idempotencyKey) {
|
|
86
100
|
if (target.kind === "admission_gate") {
|
|
@@ -368,7 +382,8 @@ function canonicalMessageId(result) {
|
|
|
368
382
|
async function executePart(api, artifact, part, chatId, signal) {
|
|
369
383
|
const effect = artifact.effect;
|
|
370
384
|
if (effect.kind === "text" || effect.kind === "admission_ack") {
|
|
371
|
-
const
|
|
385
|
+
const renderHtml = effect.kind === "text" && artifact.authorClass === "butler" ? renderTelegramButlerHtml : undefined;
|
|
386
|
+
const ids = await (0, telegram_client_1.sendTelegramText)(api, chatId, part.text, { ...(signal ? { signal } : {}), ...(renderHtml ? { renderHtml } : {}) });
|
|
372
387
|
if (ids.length !== 1)
|
|
373
388
|
throw new Error("Telegram effect chunk renderer returned an unexpected message count");
|
|
374
389
|
return ids[0];
|
|
@@ -480,12 +495,11 @@ function createTelegramAuthorizedEffectExecutor(options) {
|
|
|
480
495
|
if (input.signal?.aborted)
|
|
481
496
|
throw input.signal.reason;
|
|
482
497
|
barrier();
|
|
483
|
-
const
|
|
484
|
-
const authorization = await options.authorize({ phase: "prepare", ...presentedInput });
|
|
498
|
+
const authorization = await options.authorize({ phase: "prepare", ...input });
|
|
485
499
|
if (!authorization.allowed)
|
|
486
500
|
throw new Error(`Telegram effect authorization denied: ${authorization.reason}`);
|
|
487
501
|
const store = getStore();
|
|
488
|
-
const prepared = prepareTelegramEffect(store, { ...
|
|
502
|
+
const prepared = prepareTelegramEffect(store, { ...input, authorization });
|
|
489
503
|
try {
|
|
490
504
|
const executed = await executeTelegramEffect(store, prepared.id, options.api, async (artifact) => {
|
|
491
505
|
if (input.signal?.aborted)
|
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.797",
|
|
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.797",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|