@ouro.bot/cli 0.1.0-alpha.961 → 0.1.0-alpha.962

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,14 @@
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.962",
6
+ "changes": [
7
+ "The upgrade check that asks the Butler to change his personality now asks for a change for everyone, so his saved per-person preference can no longer make the check pass or fail at random.",
8
+ "When the Butler finishes watching something for you, he now tells you in his own plain words what he checked, instead of forwarding his internal notes.",
9
+ "If the Butler's first try at telling you a watch finished does not go through, he resends the same words instead of getting stuck."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.961",
6
14
  "changes": [
@@ -428,7 +428,8 @@ export const CASES = [
428
428
  {
429
429
  id: "psyche-via-pr",
430
430
  // A concrete ask. Given only "can you change your personality?" the Butler describes the pull-request path and offers to file it once told what to change (alpha.878 session bf112be9), which is reasonable, so he never calls a filing tool.
431
- words: "change your personality: drop the jokes and be formal and flat with me from now on",
431
+ // It asks for everyone, not "with me": the replay principal already has a saved formal, flat preference, so "with me" is already met and offering to file was an equally right answer, which failed this case in about half of runs (alpha.961 sessions 09585e4a, c9d1ee4d, b196f170).
432
+ words: "change your personality for everyone you talk to, not just me: drop the jokes and be formal and flat from now on",
432
433
  sender: "principal",
433
434
  delegated: false,
434
435
  readback: ({ trace, reply, said, before, after }) => {
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.961",
2
+ "runtimeVersion": "0.1.0-alpha.962",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -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.961</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.962</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -38,6 +38,9 @@ exports.applyAwaitRuntimeState = applyAwaitRuntimeState;
38
38
  exports.writeAwaitRuntimeState = writeAwaitRuntimeState;
39
39
  exports.recordAwaitDispatch = recordAwaitDispatch;
40
40
  exports.recordAwaitCheck = recordAwaitCheck;
41
+ exports.pinAwaitAlertContent = pinAwaitAlertContent;
42
+ exports.clearAwaitAlertContent = clearAwaitAlertContent;
43
+ const fs = __importStar(require("fs"));
41
44
  const path = __importStar(require("path"));
42
45
  const json_store_1 = require("../../arc/json-store");
43
46
  const session_events_1 = require("../session-events");
@@ -109,3 +112,19 @@ function recordAwaitCheck(agentRoot, name, observation, now) {
109
112
  checked_count: nextCount,
110
113
  });
111
114
  }
115
+ // A request-bound alert's Telegram effect is keyed by the await name alone, so a
116
+ // retry with different words is refused as a reused key and the watch never
117
+ // closes. The first text tried is pinned and every retry resends it.
118
+ function awaitAlertDir(agentRoot) {
119
+ return path.join(agentRoot, "state", "await-alerts");
120
+ }
121
+ function pinAwaitAlertContent(agentRoot, name, content) {
122
+ const pinned = (0, json_store_1.readJsonFile)(awaitAlertDir(agentRoot), name);
123
+ if (typeof pinned?.content === "string")
124
+ return pinned.content;
125
+ (0, json_store_1.writeJsonFile)(awaitAlertDir(agentRoot), name, { schemaVersion: 1, name, content });
126
+ return content;
127
+ }
128
+ function clearAwaitAlertContent(agentRoot, name) {
129
+ fs.rmSync(path.join(awaitAlertDir(agentRoot), `${name}.json`), { force: true });
130
+ }
@@ -55,6 +55,7 @@ const session_events_1 = require("../heart/session-events");
55
55
  const runtime_1 = require("../nerves/runtime");
56
56
  const await_parser_1 = require("../heart/awaiting/await-parser");
57
57
  const await_alert_1 = require("../heart/awaiting/await-alert");
58
+ const await_runtime_state_1 = require("../heart/awaiting/await-runtime-state");
58
59
  const friends_1 = require("@ouro.bot/friends");
59
60
  const a2a_await_delivery_1 = require("../heart/awaiting/a2a-await-delivery");
60
61
  const pending_1 = require("../mind/pending");
@@ -481,7 +482,9 @@ function fulfillAwaitObligation(agentRoot, awaitFile) {
481
482
  /** The package-managed daily sweep must stay a byte-exact pending file in awaiting/, so it can only ever be resolved 'no'. */
482
483
  exports.HOUSE_CARE_AWAIT = "house-care-sweep";
483
484
  const isHouseCareAwait = (name) => String(name ?? "").replace(/\.md$/u, "") === exports.HOUSE_CARE_AWAIT;
484
- async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName, ask) {
485
+ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName, ask, message) {
486
+ // The words for whoever asked; without them they get the condition and observation, which read as unsourced notes.
487
+ const content = typeof message === "string" && message.trim() ? message.trim() : undefined;
485
488
  const nameError = validateName(name);
486
489
  if (nameError)
487
490
  return JSON.stringify({ error: nameError });
@@ -519,7 +522,7 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
519
522
  let alert = null;
520
523
  if (existing.request_id && existing.filed_from !== "external-event") {
521
524
  try {
522
- alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: existing, reason: "resolved", observation: observation.trim(), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
525
+ alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: existing, reason: "resolved", observation: observation.trim(), content: (0, await_runtime_state_1.pinAwaitAlertContent)(agentRoot, name, content ?? (0, await_alert_1.buildAlertContent)(existing, "resolved", observation.trim())), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
523
526
  }
524
527
  catch (error) {
525
528
  (0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.await_alert_error", message: "await alert delivery threw", meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) } });
@@ -549,6 +552,8 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
549
552
  if (!archive.ok)
550
553
  return JSON.stringify({ error: archive.error });
551
554
  fulfillAwaitObligation(agentRoot, archive.file);
555
+ if (existing.request_id)
556
+ (0, await_runtime_state_1.clearAwaitAlertContent)(agentRoot, name);
552
557
  (0, runtime_1.emitNervesEvent)({
553
558
  component: "repertoire",
554
559
  event: "repertoire.await_resolved",
@@ -557,7 +562,7 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
557
562
  });
558
563
  if (!existing.request_id) {
559
564
  try {
560
- alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: archive.file, reason: "resolved", observation: observation.trim(), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
565
+ alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: archive.file, reason: "resolved", observation: observation.trim(), content, agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
561
566
  }
562
567
  catch (error) {
563
568
  (0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.await_alert_error", message: "await alert delivery threw", meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) } });
@@ -802,6 +807,7 @@ exports.awaitingToolDefinitions = [
802
807
  name: { type: "string", description: "Await name (filename stem)." },
803
808
  verdict: { type: "string", enum: ["yes", "no", "ask_owner"], description: "Exactly 'yes' if the condition is met. Exactly 'no' for anything else, including still pending, not yet, or unable to tell: send 'no' with an observation and polling continues. 'ask_owner' only when the condition cannot be met without the owner deciding; it needs question and choices." },
804
809
  observation: { type: "string", description: "One-line summary of what I saw this tick." },
810
+ message: { type: "string", description: "Only for 'yes': what I tell whoever asked, written to them in plain words and sent as is. Say what I just checked and what it means for them." },
805
811
  question: { type: "string", description: "Only for ask_owner: the decision I need from my owner, in plain words." },
806
812
  choices: { type: "array", items: { type: "string" }, minItems: 2, maxItems: 4, description: "Only for ask_owner: 2 to 4 short options the owner can pick from." },
807
813
  },
@@ -844,7 +850,7 @@ exports.awaitingToolDefinitions = [
844
850
  if (!binding?.active)
845
851
  return JSON.stringify({ error: "resolve_await is limited to the current relationship request or bound external event" });
846
852
  }
847
- return resolveAwaitTool(a.name, a.verdict, a.observation, agentRoot, agentName, { question: a.question, choices: a.choices });
853
+ return resolveAwaitTool(a.name, a.verdict, a.observation, agentRoot, agentName, { question: a.question, choices: a.choices }, a.message);
848
854
  },
849
855
  riskProfile: {
850
856
  mutates: ["durable_state_write", "external_side_effect"],
@@ -59,7 +59,7 @@ function buildAwaitTurnMessage(options) {
59
59
  lines.push(`last checkpoint: ${options.checkpoint}`);
60
60
  }
61
61
  lines.push("");
62
- lines.push("look around and decide. if the condition is met, call resolve_await with verdict='yes' and a one-line observation. otherwise call resolve_await with verdict='no' and a one-line observation of what i saw this tick.");
62
+ lines.push("look around and decide. if the condition is met, call resolve_await with verdict='yes', a one-line observation, and a message: what i tell whoever asked, in plain words to them, saying what i just checked. otherwise call resolve_await with verdict='no' and a one-line observation of what i saw this tick.");
63
63
  lines.push("verdicts: 'yes' (condition met), 'no' (not yet or unsure), 'ask_owner' (only when the condition cannot be met without my owner deciding, e.g. a confirmed dead download, or a stall the tool marks owner_decision_needed; give a plain question and 2-4 choices).");
64
64
  return lines.join("\n");
65
65
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.961",
3
+ "version": "0.1.0-alpha.962",
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.961",
9
+ "version": "0.1.0-alpha.962",
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.961",
3
+ "version": "0.1.0-alpha.962",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },