@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 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 },
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.795",
2
+ "runtimeVersion": "0.1.0-alpha.796",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -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.795</Repository>
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>
@@ -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 do not use markdown. i do not introduce myself on boot.");
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, signal, onMessageDelivered) {
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: escapeTelegramHtml(chunk),
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 telegramPlainText(text) {
80
- return text.replace(/(^|[\s([{])\*\*([\p{L}\p{N}][^*\n]*?)\*\*(?=$|[\s.,;!?)\]}])/gmu, "$1$2");
81
- }
82
- function presentTelegramEffect(authorClass, effect) {
83
- return authorClass === "butler" && effect.kind === "text" ? { ...effect, text: telegramPlainText(effect.text) } : effect;
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 ids = await (0, telegram_client_1.sendTelegramText)(api, chatId, part.text, signal);
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 presentedInput = { ...input, effect: presentTelegramEffect(input.authorClass, input.effect) };
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, { ...presentedInput, authorization });
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)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.795",
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.795",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.795",
3
+ "version": "0.1.0-alpha.796",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },