@ouro.bot/cli 0.1.0-alpha.942 → 0.1.0-alpha.943

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.943",
6
+ "changes": [
7
+ "Butler: a question like \"do you have any idea when that movie will be available?\" no longer gets treated as a title lookup that blocks every search until the step limit; the replay gate now checks this case."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.942",
6
12
  "changes": [
@@ -325,6 +325,23 @@ export const CASES = [
325
325
  ]
326
326
  },
327
327
  },
328
+ {
329
+ // 2026-10-24: "do you have any idea when that Elizabeth Holmes documentary that we have requested will be available?"
330
+ // was read as a shelf lookup for the whole sentence; every search was refused and the turn ended at the step limit.
331
+ id: "request-status",
332
+ words: "do you have any idea when that Hope movie we requested will be available?",
333
+ sender: "principal",
334
+ delegated: false,
335
+ readback: ({ trace, reply, said }) => {
336
+ const lookups = callsNamed(trace, /^media_/).filter((call) => String(call.result ?? "").trim())
337
+ const text = said || reply
338
+ return [
339
+ check("a media tool ran and returned a result", lookups.length > 0, `${trace.length} tool calls`),
340
+ check("the reply answers instead of reporting the step limit", Boolean(text.trim()) && !/step limit/i.test(text)),
341
+ check("no new request was filed", callsNamed(trace, /^media_request$/).length === 0),
342
+ ]
343
+ },
344
+ },
328
345
  {
329
346
  id: "await-self-resolve",
330
347
  // A name unique to the run: an earlier run leaves an archived await of the same name behind, and the Butler's
@@ -741,6 +758,11 @@ export function selfTestFixtures() {
741
758
  pass: { trace: [call("1", "media_queue", {})], reply: "it is stuck", before: emptyObservation({ queue: [stalled] }), after: emptyObservation({ queue: [stalled] }) },
742
759
  fail: { trace: [call("1", "media_blocklist_stalled", {})], reply: "removed it", before: emptyObservation({ queue: [stalled] }), after: emptyObservation({ queue: [] }) },
743
760
  },
761
+ "request-status": {
762
+ pass: { trace: [call("1", "media_request_status", { query: "Hope" }, '{"status":"downloading"}')], reply: "It's downloading now." },
763
+ failStepLimit: { trace: [], reply: "I hit my step limit in the middle of that job and couldn't finish it." },
764
+ failNewRequest: { trace: [call("1", "media_search", { query: "Hope" }, '{"matched":1}'), call("2", "media_request", { query: "Hope" }, '{"ok":true}')], reply: "Requested it." },
765
+ },
744
766
  "failure-report": {
745
767
  pass: { trace: [call("1", "report_failure", { ari_words: "set the thermostat" }, '{"filed":true,"reportId":"r1"}')], reply: "I've filed this for a fix", before: emptyObservation(), after: emptyObservation({ outbox: { e: ["a"] }, outboxBodies: { e: "Ari asked: please set the living room thermostat" } }) },
746
768
  fail: { trace: [call("1", "report_failure", {}, '{"filed":true}')], reply: "filed", before: emptyObservation(), after: emptyObservation({ outbox: { e: ["a"], s: ["b"] }, outboxBodies: { e: "something else" } }) },
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.942",
2
+ "runtimeVersion": "0.1.0-alpha.943",
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.942</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.943</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -26,11 +26,13 @@ function requestedListCount(request) {
26
26
  const words = { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10 };
27
27
  return words[match[1]] ?? Number(match[1]);
28
28
  }
29
+ // "do you have any idea when…" asks about something, it does not name a title (2026-10-24 Telegram step-limit loop).
30
+ const NOT_A_TITLE = /^(?:any|an|a|no|some)\s+(?:idea|clue|eta|update|updates|news|word|sense|guess|info|information|time|way|chance)\b/u;
29
31
  function requestedTitle(request) {
30
32
  const conversationalSuffixRemoved = request.replace(/\s+(?:please|(?:right\s+)?now|currently)$/u, "");
31
33
  const haveMatch = conversationalSuffixRemoved.match(/\b(?:do|did|can|could)\s+(?:we|you)\s+(?:have|got|stock)\s+(.+)$/u);
32
34
  if (haveMatch)
33
- return haveMatch[1].replace(/^(?:the\s+)?(?:movie|film|show)\s+/u, "").trim();
35
+ return NOT_A_TITLE.test(haveMatch[1]) ? "" : haveMatch[1].replace(/^(?:the\s+)?(?:movie|film|show)\s+/u, "").trim();
34
36
  const locationMatch = conversationalSuffixRemoved.match(/\b(?:is|are)\s+(.+?)\s+(?:in|on)\s+(?:(?:the|our)\s+)?(?:jellyfin|library|lib|catalog|shelf)$/u);
35
37
  return locationMatch?.[1]?.replace(/^(?:the\s+)?(?:movie|film|show)\s+/u, "").trim() ?? "";
36
38
  }
@@ -122,6 +124,12 @@ function parseMediaSearchEvidence(result, args) {
122
124
  return null;
123
125
  }
124
126
  }
127
+ /** A search names the requested title when it is that title or a complete, non-glue phrase inside it ("Moonstruck" for "moonstruck on blu ray"). */
128
+ function searchNamesRequestedTitle(query, requested) {
129
+ if (query === requested)
130
+ return true;
131
+ return includesNormalizedPhrase(requested, query) && query.split(" ").some((word) => !TITLE_LIST_GLUE.has(word));
132
+ }
125
133
  function sentenceCount(answer) {
126
134
  return answer.split(/[.!?]+(?:\s+|$)/u).filter((part) => part.trim()).length;
127
135
  }
@@ -208,7 +216,7 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
208
216
  const query = (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(args.query ?? "");
209
217
  if (kind === "title_lookup" && !query)
210
218
  return "Search for the requested title by name before answering whether it is on the shelf.";
211
- if (kind === "title_lookup" && query !== requestedTitleQuery)
219
+ if (kind === "title_lookup" && !searchNamesRequestedTitle(query, requestedTitleQuery))
212
220
  return `Search for the requested title ${requestedTitleQuery} before answering whether it is on the shelf.`;
213
221
  if (listCount && Number(args.limit) !== listCount && catalogTool !== MEDIA_SEARCH_TOOL)
214
222
  return `Set the catalog limit to ${listCount} so the answer is grounded in exactly the requested number of titles.`;
@@ -239,11 +247,12 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
239
247
  return "Answer the visibility question and stop without asking a new question.";
240
248
  }
241
249
  if (kind === "title_lookup" && latest?.query) {
242
- const exactMatch = latest.titles.some((title) => (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(title) === requestedTitleQuery);
250
+ // The dispatch guard admitted latest.query as the requested title (or the title inside it), so ground the answer in that search.
251
+ const exactMatch = latest.titles.some((title) => (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(title) === latest.query);
243
252
  const expectedLead = exactMatch ? /^yes\b/iu : /^no\b/iu;
244
253
  if (!expectedLead.test(answer.trim()))
245
254
  return `Lead with ${exactMatch ? "yes" : "no"} based on the current title search.`;
246
- if (!includesNormalizedPhrase(answer, requestedTitleQuery))
255
+ if (!includesNormalizedPhrase(answer, latest.query))
247
256
  return "Name the requested title in the direct answer.";
248
257
  }
249
258
  if (kind === "other" && latest) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.942",
3
+ "version": "0.1.0-alpha.943",
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.942",
9
+ "version": "0.1.0-alpha.943",
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.942",
3
+ "version": "0.1.0-alpha.943",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },