@ouro.bot/cli 0.1.0-alpha.794 → 0.1.0-alpha.795

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,12 @@
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.795",
6
+ "changes": [
7
+ "Accept natural one- or two-sentence Butler library visibility replies while keeping exact count, grounding, contradiction, repetition, jargon, and follow-up guards."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.794",
6
12
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.794",
2
+ "runtimeVersion": "0.1.0-alpha.795",
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>Mendelow Cloud Butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.794</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.795</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -5,7 +5,7 @@ const runtime_1 = require("../nerves/runtime");
5
5
  const sanctuary_media_optimization_1 = require("./sanctuary-media-optimization");
6
6
  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
- const NEGATED_MEDIA_ACCESS = /\b(?:not|never|none|nothing|zero|inaccessible|invisible|unavailable)\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;
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
10
  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
11
  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"]);
@@ -96,9 +96,18 @@ function hasGroundedFirstPersonVisibility(answer, formattedCount) {
96
96
  if (NEGATED_MEDIA_ACCESS.test(answer) || answer.split(formattedCount).length - 1 !== 1)
97
97
  return false;
98
98
  const escapedCount = formattedCount.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
99
- const groundedCollection = new RegExp(`\\b(?:i\\s+can\\s+(?:(?:absolutely|clearly|currently|definitely|now|plainly|readily)\\s+)?(?:see|browse|access|view)|i\\s+(?:(?:currently|now)\\s+)?(?:see|browse|access|view)|i[’']m\\s+(?:(?:currently|now)\\s+)?looking\\s+at|i(?:\\s+have|[’']ve\\s+got)(?:\\s+access\\s+to)?)\\s+(?:all\\s+|about\\s+|around\\s+|exactly\\s+|roughly\\s+)?${escapedCount}\\s+(?:titles?|movies?|films?|shows?|episodes?)(?:\\s+(?:on\\s+the\\s+shelf|in\\s+the\\s+library))?\\b`, "iu");
100
- return answer.split(/[.!?]+(?:\s+|$)/u).some((sentence) => (sentence.includes(formattedCount)
101
- && groundedCollection.test(sentence)));
99
+ const mediaNoun = "(?:titles?|movies?|films?|shows?|episodes?|items?)";
100
+ const countedObject = `(?:all\\s+|about\\s+|around\\s+|exactly\\s+|roughly\\s+)?${escapedCount}\\s+${mediaNoun}\\b`;
101
+ const contextObject = `(?:it|(?:(?:the|our)\\s+)?(?:full\\s+)?(?:library|shelf))(?:\\s+(?:now|again))?(?=\\s*(?:[.!?;,:—-]|$))`;
102
+ const accessObject = `(?:${contextObject}|${countedObject})`;
103
+ const firstPersonAccess = [
104
+ new RegExp(`\\bi\\s+(?:(?:can\\s+(?:(?:absolutely|clearly|currently|definitely|now|plainly|readily)\\s+)?|(?:currently|now)\\s+)?(?:see|browse|access|view))\\s+${accessObject}`, "iu"),
105
+ new RegExp(`\\bi(?:\\s+have|[’']ve\\s+got)\\s+(?:(?:full\\s+)?access\\s+to\\s+${accessObject}|${countedObject})`, "iu"),
106
+ new RegExp(`\\bi(?:\\s+am|[’']m)\\s+(?:(?:currently|now)\\s+)?(?:looking\\s+at|able\\s+to\\s+(?:see|browse|access|view))\\s+${accessObject}`, "iu"),
107
+ /\b(?:(?:the|our)\s+)?(?:full\s+)?(?:library|shelf)\s+is\s+(?:fully\s+)?(?:visible|accessible)\s+to\s+me\b/iu,
108
+ ];
109
+ const groundedCount = new RegExp(`\\b${escapedCount}\\s+${mediaNoun}\\b`, "iu");
110
+ return firstPersonAccess.some((pattern) => pattern.test(answer)) && groundedCount.test(answer);
102
111
  }
103
112
  function commonAnswerRejection(answer, technicalDetailRequested) {
104
113
  if (/\b(?:just|only|actually|merely)\s+(?:a\s+)?(?:bot|ai|assistant)\b|\bi\s+(?:can[’']?t|cannot|don[’']?t|do not)\s+(?:actually\s+)?watch\b|\bif i had to pick\b/iu.test(answer)) {
@@ -184,8 +193,11 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
184
193
  return "Answer library visibility in no more than two short sentences and 240 characters.";
185
194
  if (!/^yes\b/iu.test(answer.trim()))
186
195
  return "Lead with a direct yes when the current catalog read succeeds.";
187
- if (latest && !hasGroundedFirstPersonVisibility(answer, new Intl.NumberFormat("en-US").format(latest.totalItems)))
188
- return "Answer in your own voice: say what you can see or access on the household shelf or in the library.";
196
+ if (latest) {
197
+ const formattedCount = new Intl.NumberFormat("en-US").format(latest.totalItems);
198
+ if (!hasGroundedFirstPersonVisibility(answer, formattedCount))
199
+ return `Reply in one or two sentences. A valid shape is: Yes—I can see the library now. It contains ${formattedCount} titles.`;
200
+ }
189
201
  if (/\b(?:titles? like|such as|including)\b/iu.test(answer))
190
202
  return "Do not sample titles for a visibility question; answer whether the shelf is visible and give the current count.";
191
203
  if ([...answer.matchAll(/\byes\b/giu)].length > 1)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.794",
3
+ "version": "0.1.0-alpha.795",
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.794",
9
+ "version": "0.1.0-alpha.795",
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.794",
3
+ "version": "0.1.0-alpha.795",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },