@ouro.bot/cli 0.1.0-alpha.795 → 0.1.0-alpha.796
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 +7 -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 +10 -0
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/mind/prompt.js +1 -1
- package/dist/senses/telegram-client.js +6 -5
- package/dist/senses/telegram-effect-adapter.js +22 -9
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.796",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Give Mendelow Cloud Butler a concrete situational voice with casual lowercase, restrained punctuation, and varied activity phrases.",
|
|
8
|
+
"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."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.795",
|
|
6
13
|
"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,16 @@ 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
|
+
- **casual**: `the house is behaving itself again, which feels faintly suspicious`
|
|
14
|
+
- **recommendation**: `from the shelf, i’d pick The Princess Bride — nimble, quotable, and suspiciously good for household morale`
|
|
15
|
+
- **incident**: `downloads are paused to protect your prepaid credit. top up the account, then tell me; i’ll resume them and verify one finishes.`
|
|
16
|
+
|
|
7
17
|
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
18
|
|
|
9
19
|
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.796</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 **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.");
|
|
@@ -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,24 @@ 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
|
+
for (const match of text.matchAll(/`([^`\n]+)`|\*\*([\p{L}\p{N}](?:[^*`\n]*\S)?)\*\*|\*([\p{L}\p{N}](?:[^*`\n]*\S)?)\*/gu)) {
|
|
83
|
+
const gap = text.slice(cursor, match.index);
|
|
84
|
+
if (/[*`]/u.test(gap))
|
|
85
|
+
return (0, telegram_client_1.escapeTelegramHtml)(text);
|
|
86
|
+
html += (0, telegram_client_1.escapeTelegramHtml)(gap);
|
|
87
|
+
if (match[1] !== undefined)
|
|
88
|
+
html += `<code>${(0, telegram_client_1.escapeTelegramHtml)(match[1])}</code>`;
|
|
89
|
+
else if (match[2] !== undefined)
|
|
90
|
+
html += `<b>${(0, telegram_client_1.escapeTelegramHtml)(match[2])}</b>`;
|
|
91
|
+
else
|
|
92
|
+
html += `<i>${(0, telegram_client_1.escapeTelegramHtml)(match[3])}</i>`;
|
|
93
|
+
cursor = match.index + match[0].length;
|
|
94
|
+
}
|
|
95
|
+
const tail = text.slice(cursor);
|
|
96
|
+
return /[*`]/u.test(tail) ? (0, telegram_client_1.escapeTelegramHtml)(text) : html + (0, telegram_client_1.escapeTelegramHtml)(tail);
|
|
84
97
|
}
|
|
85
98
|
function assertEffectTarget(target, effect, idempotencyKey) {
|
|
86
99
|
if (target.kind === "admission_gate") {
|
|
@@ -368,7 +381,8 @@ function canonicalMessageId(result) {
|
|
|
368
381
|
async function executePart(api, artifact, part, chatId, signal) {
|
|
369
382
|
const effect = artifact.effect;
|
|
370
383
|
if (effect.kind === "text" || effect.kind === "admission_ack") {
|
|
371
|
-
const
|
|
384
|
+
const renderHtml = effect.kind === "text" && artifact.authorClass === "butler" ? renderTelegramButlerHtml : undefined;
|
|
385
|
+
const ids = await (0, telegram_client_1.sendTelegramText)(api, chatId, part.text, { ...(signal ? { signal } : {}), ...(renderHtml ? { renderHtml } : {}) });
|
|
372
386
|
if (ids.length !== 1)
|
|
373
387
|
throw new Error("Telegram effect chunk renderer returned an unexpected message count");
|
|
374
388
|
return ids[0];
|
|
@@ -480,12 +494,11 @@ function createTelegramAuthorizedEffectExecutor(options) {
|
|
|
480
494
|
if (input.signal?.aborted)
|
|
481
495
|
throw input.signal.reason;
|
|
482
496
|
barrier();
|
|
483
|
-
const
|
|
484
|
-
const authorization = await options.authorize({ phase: "prepare", ...presentedInput });
|
|
497
|
+
const authorization = await options.authorize({ phase: "prepare", ...input });
|
|
485
498
|
if (!authorization.allowed)
|
|
486
499
|
throw new Error(`Telegram effect authorization denied: ${authorization.reason}`);
|
|
487
500
|
const store = getStore();
|
|
488
|
-
const prepared = prepareTelegramEffect(store, { ...
|
|
501
|
+
const prepared = prepareTelegramEffect(store, { ...input, authorization });
|
|
489
502
|
try {
|
|
490
503
|
const executed = await executeTelegramEffect(store, prepared.id, options.api, async (artifact) => {
|
|
491
504
|
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.796",
|
|
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.796",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|