@ouro.bot/cli 0.1.0-alpha.819 → 0.1.0-alpha.820
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.820",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Teach the Sanctuary media catalog contract about media_search so taste and browse questions ground in its evidence instead of rejecting every terminal answer."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.819",
|
|
6
12
|
"changes": [
|
|
@@ -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.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.820</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sanctuaryMediaCatalogRequiredToolCalls = sanctuaryMediaCatalogRequiredToolCalls;
|
|
4
4
|
const runtime_1 = require("../nerves/runtime");
|
|
5
5
|
const sanctuary_media_optimization_1 = require("./sanctuary-media-optimization");
|
|
6
|
-
const
|
|
6
|
+
const LEGACY_CATALOG_TOOL = "sanctuary_search_media_catalog";
|
|
7
|
+
const MEDIA_SEARCH_TOOL = "media_search";
|
|
8
|
+
const MEDIA_REQUEST_TOOL = "media_request";
|
|
7
9
|
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
10
|
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
11
|
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;
|
|
@@ -84,6 +86,43 @@ function parseCatalogEvidence(result, args) {
|
|
|
84
86
|
totalItems: parsed.data.totalItems,
|
|
85
87
|
matchedItems: parsed.data.matchedItems,
|
|
86
88
|
titles,
|
|
89
|
+
offShelfTitles: [],
|
|
90
|
+
fromMediaSearch: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* media_search reports shelf state per title, so it can ground both "what do we own"
|
|
99
|
+
* and "what is worth adding" from one read. The legacy catalog read can only prove
|
|
100
|
+
* absence by returning zero matches for an exact title query.
|
|
101
|
+
*/
|
|
102
|
+
function parseMediaSearchEvidence(result, args) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(result);
|
|
105
|
+
if (!Number.isSafeInteger(parsed.matched) || !Array.isArray(parsed.items))
|
|
106
|
+
return null;
|
|
107
|
+
const titles = [];
|
|
108
|
+
const offShelfTitles = [];
|
|
109
|
+
for (const entry of parsed.items) {
|
|
110
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
111
|
+
continue;
|
|
112
|
+
const item = entry;
|
|
113
|
+
if (typeof item.title !== "string" || !item.title.trim())
|
|
114
|
+
continue;
|
|
115
|
+
titles.push(item.title.trim());
|
|
116
|
+
if (item.on_shelf === false)
|
|
117
|
+
offShelfTitles.push(item.title.trim());
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
query: (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(args.query ?? ""),
|
|
121
|
+
totalItems: parsed.matched,
|
|
122
|
+
matchedItems: parsed.matched,
|
|
123
|
+
titles,
|
|
124
|
+
offShelfTitles,
|
|
125
|
+
fromMediaSearch: true,
|
|
87
126
|
};
|
|
88
127
|
}
|
|
89
128
|
catch {
|
|
@@ -126,7 +165,11 @@ function commonAnswerRejection(answer, technicalDetailRequested) {
|
|
|
126
165
|
return undefined;
|
|
127
166
|
}
|
|
128
167
|
function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
129
|
-
|
|
168
|
+
// Prefer media_search when the media surface is installed: it grounds both what is
|
|
169
|
+
// on the shelf and what is worth adding. Fall back to the legacy restricted read.
|
|
170
|
+
const catalogTool = advertisedToolNames.includes(MEDIA_SEARCH_TOOL) ? MEDIA_SEARCH_TOOL : LEGACY_CATALOG_TOOL;
|
|
171
|
+
const canRequestMedia = advertisedToolNames.includes(MEDIA_REQUEST_TOOL);
|
|
172
|
+
if (!advertisedToolNames.includes(catalogTool))
|
|
130
173
|
return undefined;
|
|
131
174
|
const normalized = (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(request);
|
|
132
175
|
const mentionsMedia = /\b(?:film|films|movie|movies|shows|tv|jellyfin|watch|shelf|stock|catalog|library|lib)\b/u.test(normalized);
|
|
@@ -143,7 +186,7 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
143
186
|
const listCount = requestedListCount(normalized);
|
|
144
187
|
const technicalDetailRequested = /\b(?:technical|technically|implementation|endpoint|backend|debug|detail|internals?)\b/u.test(normalized);
|
|
145
188
|
let latestEvidence;
|
|
146
|
-
const names = [
|
|
189
|
+
const names = [catalogTool];
|
|
147
190
|
(0, runtime_1.emitNervesEvent)({
|
|
148
191
|
component: "senses",
|
|
149
192
|
event: "senses.sanctuary_media_catalog_obligation",
|
|
@@ -154,36 +197,38 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
154
197
|
names,
|
|
155
198
|
requireSuccessfulResults: true,
|
|
156
199
|
retryMessage: asksForCount
|
|
157
|
-
?
|
|
200
|
+
? `Use ${catalogTool} before answering, then report only the current shelf count in ordinary household language.`
|
|
158
201
|
: kind === "visibility"
|
|
159
|
-
?
|
|
202
|
+
? `Use ${catalogTool} before answering. Lead with the direct answer from current catalog evidence, report the shelf count in ordinary household language, and stop without sampled titles or a follow-up question.`
|
|
160
203
|
: kind === "title_lookup"
|
|
161
|
-
?
|
|
204
|
+
? `Use ${catalogTool} with the requested title before answering. Confirm its presence or absence directly from current catalog evidence.`
|
|
162
205
|
: kind === "taste_recommendation"
|
|
163
|
-
?
|
|
206
|
+
? `Use ${catalogTool} before answering. Make one concise, decisive choice grounded in returned catalog evidence; do not volunteer an AI or 'I cannot watch' disclaimer.`
|
|
164
207
|
: listCount
|
|
165
|
-
? `Use
|
|
166
|
-
:
|
|
208
|
+
? `Use ${catalogTool} with limit ${listCount} before answering, then name exactly the returned catalog titles.`
|
|
209
|
+
: `Use ${catalogTool} before answering and base any named titles on the returned catalog evidence.`,
|
|
167
210
|
validateRequiredToolResult(name, result, args) {
|
|
168
|
-
if (name !==
|
|
211
|
+
if (name !== catalogTool)
|
|
169
212
|
return false;
|
|
170
|
-
const current = parseCatalogEvidence(result, args);
|
|
213
|
+
const current = catalogTool === MEDIA_SEARCH_TOOL ? parseMediaSearchEvidence(result, args) : parseCatalogEvidence(result, args);
|
|
171
214
|
if (!current)
|
|
172
215
|
return false;
|
|
173
216
|
latestEvidence = current;
|
|
174
217
|
return true;
|
|
175
218
|
},
|
|
176
219
|
validateToolCallBeforeDispatch(name, args) {
|
|
177
|
-
if (name !==
|
|
220
|
+
if (name !== catalogTool)
|
|
178
221
|
return undefined;
|
|
179
222
|
const query = (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(args.query ?? "");
|
|
180
223
|
if (kind === "title_lookup" && !query)
|
|
181
224
|
return "Search for the requested title by name before answering whether it is on the shelf.";
|
|
182
225
|
if (kind === "title_lookup" && query !== requestedTitleQuery)
|
|
183
226
|
return `Search for the requested title ${requestedTitleQuery} before answering whether it is on the shelf.`;
|
|
184
|
-
|
|
227
|
+
// media_search browses by genre, keyword and year and reports shelf state per
|
|
228
|
+
// title, so an addition question does not need a per-candidate title query.
|
|
229
|
+
if (asksForAddition && !query && catalogTool !== MEDIA_SEARCH_TOOL)
|
|
185
230
|
return "Search for one candidate title by name before recommending it as an addition.";
|
|
186
|
-
if (listCount && Number(args.limit) !== listCount)
|
|
231
|
+
if (listCount && Number(args.limit) !== listCount && catalogTool !== MEDIA_SEARCH_TOOL)
|
|
187
232
|
return `Set the catalog limit to ${listCount} so the answer is grounded in exactly the requested number of titles.`;
|
|
188
233
|
return undefined;
|
|
189
234
|
},
|
|
@@ -221,17 +266,31 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
221
266
|
}
|
|
222
267
|
if (kind === "taste_recommendation" && latest) {
|
|
223
268
|
if (asksForAddition) {
|
|
224
|
-
if (
|
|
269
|
+
if (latest.fromMediaSearch) {
|
|
270
|
+
// media_search states shelf membership per title, so a recommendation is
|
|
271
|
+
// grounded when the named candidate came back marked absent.
|
|
272
|
+
if (!latest.offShelfTitles.some((title) => includesNormalizedPhrase(answer, title))) {
|
|
273
|
+
return "Recommend only a title the current search returned as not on the shelf.";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else if (!latest.query || latest.matchedItems !== 0 || !includesNormalizedPhrase(answer, latest.query)) {
|
|
225
277
|
return "Recommend an addition only after an exact current catalog search shows that candidate is absent.";
|
|
226
|
-
|
|
278
|
+
}
|
|
279
|
+
if (!canRequestMedia && /\b(?:i|we)\s+(?:added|requested|submitted|queued)\b/iu.test(answer)) {
|
|
227
280
|
return "Do not claim the title was added or requested when no media-request action was available.";
|
|
281
|
+
}
|
|
228
282
|
}
|
|
229
283
|
else if (!latest.titles.some((title) => includesNormalizedPhrase(answer, title))) {
|
|
230
284
|
return "Make the choice from a title returned by the current catalog evidence.";
|
|
231
285
|
}
|
|
232
286
|
}
|
|
233
|
-
if (kind === "other" && latest)
|
|
234
|
-
|
|
287
|
+
if (kind === "other" && latest) {
|
|
288
|
+
if (!latest.fromMediaSearch)
|
|
289
|
+
return catalogTitleGroundingRejection(answer, latest, listCount);
|
|
290
|
+
if (latest.titles.length > 0 && !latest.titles.some((title) => includesNormalizedPhrase(answer, title))) {
|
|
291
|
+
return "Name titles the current search returned; do not add unverified ones.";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
235
294
|
return undefined;
|
|
236
295
|
},
|
|
237
296
|
};
|
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.820",
|
|
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.820",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|