@ouro.bot/cli 0.1.0-alpha.793 → 0.1.0-alpha.794
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 +7 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/core.js +1065 -1050
- package/dist/senses/sanctuary-media-catalog-contract.js +12 -3
- package/dist/senses/shared-turn.js +43 -17
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -4,7 +4,8 @@ exports.sanctuaryMediaCatalogRequiredToolCalls = sanctuaryMediaCatalogRequiredTo
|
|
|
4
4
|
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
|
-
const HOUSEHOLD_JARGON = /\b(?:bounded|catalog
|
|
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
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;
|
|
9
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"]);
|
|
10
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"]);
|
|
@@ -91,6 +92,14 @@ function parseCatalogEvidence(result, args) {
|
|
|
91
92
|
function sentenceCount(answer) {
|
|
92
93
|
return answer.split(/[.!?]+(?:\s+|$)/u).filter((part) => part.trim()).length;
|
|
93
94
|
}
|
|
95
|
+
function hasGroundedFirstPersonVisibility(answer, formattedCount) {
|
|
96
|
+
if (NEGATED_MEDIA_ACCESS.test(answer) || answer.split(formattedCount).length - 1 !== 1)
|
|
97
|
+
return false;
|
|
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)));
|
|
102
|
+
}
|
|
94
103
|
function commonAnswerRejection(answer, technicalDetailRequested) {
|
|
95
104
|
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)) {
|
|
96
105
|
return "Answer from the catalog evidence with a confident, personable choice; do not retreat into an AI or 'I cannot watch' disclaimer.";
|
|
@@ -175,14 +184,14 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
175
184
|
return "Answer library visibility in no more than two short sentences and 240 characters.";
|
|
176
185
|
if (!/^yes\b/iu.test(answer.trim()))
|
|
177
186
|
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.";
|
|
178
189
|
if (/\b(?:titles? like|such as|including)\b/iu.test(answer))
|
|
179
190
|
return "Do not sample titles for a visibility question; answer whether the shelf is visible and give the current count.";
|
|
180
191
|
if ([...answer.matchAll(/\byes\b/giu)].length > 1)
|
|
181
192
|
return "Give one answer once; do not repeat the yes or restate the response.";
|
|
182
193
|
if (answer.includes("?"))
|
|
183
194
|
return "Answer the visibility question and stop without asking a new question.";
|
|
184
|
-
if (latest && !answer.includes(new Intl.NumberFormat("en-US").format(latest.totalItems)))
|
|
185
|
-
return "Include the current shelf count from the successful catalog evidence.";
|
|
186
195
|
}
|
|
187
196
|
if (kind === "title_lookup" && latest?.query) {
|
|
188
197
|
const exactMatch = latest.titles.some((title) => (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(title) === requestedTitleQuery);
|
|
@@ -187,20 +187,27 @@ function hasAcceptedOutwardSessionAck(events, assistantIndex, toolCallId, toolNa
|
|
|
187
187
|
}
|
|
188
188
|
return false;
|
|
189
189
|
}
|
|
190
|
-
function newOutwardCoordinates(events, existingEventIds) {
|
|
190
|
+
function newOutwardCoordinates(events, existingEventIds, afterEventId) {
|
|
191
|
+
const boundaryIndex = events.findIndex((event) => event.id === afterEventId);
|
|
192
|
+
if (boundaryIndex < 0)
|
|
193
|
+
return [];
|
|
191
194
|
return events.flatMap((event, eventIndex) => {
|
|
192
|
-
if (existingEventIds.has(event.id) || event.role !== "assistant")
|
|
195
|
+
if (eventIndex <= boundaryIndex || existingEventIds.has(event.id) || event.role !== "assistant" || event.provenance?.captureKind === "synthetic")
|
|
193
196
|
return [];
|
|
194
|
-
const outwardTools = event.toolCalls.
|
|
197
|
+
const outwardTools = event.toolCalls.flatMap((call) => {
|
|
195
198
|
if (call.function.name !== "speak" && call.function.name !== "settle")
|
|
196
|
-
return
|
|
197
|
-
|
|
199
|
+
return [];
|
|
200
|
+
if (!hasAcceptedOutwardSessionAck(events, eventIndex, call.id, call.function.name))
|
|
201
|
+
return [];
|
|
202
|
+
const text = stripThinkBlocks(parseToolStringArg(call, call.function.name, call.function.name === "speak" ? "message" : "answer") ?? "");
|
|
203
|
+
return text ? [{ kind: call.function.name, eventId: event.id, text }] : [];
|
|
198
204
|
});
|
|
199
205
|
if (outwardTools.length > 0)
|
|
200
|
-
return outwardTools
|
|
206
|
+
return outwardTools;
|
|
201
207
|
if (event.toolCalls.length > 0)
|
|
202
208
|
return [];
|
|
203
|
-
|
|
209
|
+
const text = stripThinkBlocks(typeof event.content === "string" ? event.content : "");
|
|
210
|
+
return text ? [{ kind: "text", eventId: event.id, text }] : [];
|
|
204
211
|
});
|
|
205
212
|
}
|
|
206
213
|
function newestPlainAssistantText(messages) {
|
|
@@ -210,12 +217,30 @@ function newestPlainAssistantText(messages) {
|
|
|
210
217
|
&& candidate.content.trim().length > 0);
|
|
211
218
|
return message ? assistantContentText(message.content) : null;
|
|
212
219
|
}
|
|
213
|
-
function causalSessionEventIds(events, existingEventIds, attempts) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return attempts.flatMap((attempt
|
|
220
|
+
function causalSessionEventIds(events, existingEventIds, attempts, afterEventId) {
|
|
221
|
+
if (!afterEventId)
|
|
222
|
+
return attempts.flatMap((attempt) => attempt.delivered ? [null] : []);
|
|
223
|
+
const coordinates = newOutwardCoordinates(events, existingEventIds, afterEventId);
|
|
224
|
+
let nextCoordinate = 0;
|
|
225
|
+
return attempts.flatMap((attempt) => {
|
|
226
|
+
if (!attempt.delivered)
|
|
227
|
+
return [];
|
|
228
|
+
const coordinateIndex = coordinates.findIndex((coordinate, index) => index >= nextCoordinate && coordinate.kind === attempt.kind && coordinate.text === attempt.text);
|
|
229
|
+
if (coordinateIndex < 0)
|
|
230
|
+
return [null];
|
|
231
|
+
nextCoordinate = coordinateIndex + 1;
|
|
232
|
+
return [coordinates[coordinateIndex].eventId];
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function currentIngressEventId(events, existingEventIds, userMessage, precommittedIngress, ingressRelations) {
|
|
236
|
+
if (precommittedIngress)
|
|
237
|
+
return precommittedIngress.eventId;
|
|
238
|
+
const reference = ingressRelations?.references[0];
|
|
239
|
+
return events.findLast((event) => (!existingEventIds.has(event.id)
|
|
240
|
+
&& event.role === "user"
|
|
241
|
+
&& event.content === userMessage
|
|
242
|
+
&& event.provenance?.captureKind !== "synthetic"
|
|
243
|
+
&& (!reference || event.relations?.references.includes(reference))))?.id;
|
|
219
244
|
}
|
|
220
245
|
function getSenseSessionPath(agentName, friendId, channel, sessionKey, agentRootOverride) {
|
|
221
246
|
return path.join(agentRootOverride ?? (0, identity_1.getAgentRoot)(agentName), "state", "sessions", friendId, channel, `${(0, config_1.sanitizeKey)(sessionKey)}.json`);
|
|
@@ -329,7 +354,7 @@ async function runSenseTurn(options) {
|
|
|
329
354
|
if (!text)
|
|
330
355
|
return;
|
|
331
356
|
const delivery = { kind, text };
|
|
332
|
-
const attempt = { kind, delivered: false };
|
|
357
|
+
const attempt = { kind, text, delivered: false };
|
|
333
358
|
deliveryAttempts.push(attempt);
|
|
334
359
|
try {
|
|
335
360
|
await options.deliverySink?.onDelivery(delivery);
|
|
@@ -444,6 +469,7 @@ async function runSenseTurn(options) {
|
|
|
444
469
|
};
|
|
445
470
|
}
|
|
446
471
|
const persistedEvents = persistPromise ? await persistPromise : [];
|
|
472
|
+
const ingressEventId = currentIngressEventId(persistedEvents, existingEventIds, userMessage, options.precommittedIngress, options.ingressRelations);
|
|
447
473
|
const finalDeliveryKind = terminalDeliveryKind;
|
|
448
474
|
const acceptedTerminalOutcome = turnResult.turnOutcome === "settled" || turnResult.turnOutcome === "blocked";
|
|
449
475
|
const failoverText = turnResult.turnOutcome === "errored" ? turnResult.failoverMessage?.trim() : undefined;
|
|
@@ -463,7 +489,7 @@ async function runSenseTurn(options) {
|
|
|
463
489
|
else
|
|
464
490
|
pendingResponseText = "";
|
|
465
491
|
if (!hadPendingCallbackText && plainTerminalText)
|
|
466
|
-
recoveredTerminalEventId =
|
|
492
|
+
recoveredTerminalEventId = causalSessionEventIds(persistedEvents, existingEventIds, [{ kind: "text", text: stripThinkBlocks(plainTerminalText), delivered: true }], ingressEventId)[0] ?? undefined;
|
|
467
493
|
}
|
|
468
494
|
else if (turnResult.turnOutcome === "command") {
|
|
469
495
|
// Slash-command text is emitted directly by the pipeline and has no assistant event.
|
|
@@ -491,7 +517,7 @@ async function runSenseTurn(options) {
|
|
|
491
517
|
const recovered = extractOutwardSenseDeliveryText(postTurnSession.messages.slice(preTurnMessageCount));
|
|
492
518
|
finalResponse = recovered ?? emptyFallback ?? (hadReasoningChunk ? "" : "(agent responded but response was empty)");
|
|
493
519
|
if (recovered)
|
|
494
|
-
responseCausalSessionEventId =
|
|
520
|
+
responseCausalSessionEventId = causalSessionEventIds(persistedEvents, existingEventIds, [{ kind: finalDeliveryKind, text: stripThinkBlocks(recovered), delivered: true }], ingressEventId)[0] ?? undefined;
|
|
495
521
|
}
|
|
496
522
|
else {
|
|
497
523
|
finalResponse = emptyFallback ?? (hadReasoningChunk ? "" : "(agent responded but response was empty)");
|
|
@@ -538,7 +564,7 @@ async function runSenseTurn(options) {
|
|
|
538
564
|
providerInvocationCount,
|
|
539
565
|
toolInvocationCount,
|
|
540
566
|
sessionPath: sessPath,
|
|
541
|
-
...(deliveries.length > 0 ? { causalSessionEventIds: causalSessionEventIds(persistedEvents, existingEventIds, deliveryAttempts) } : {}),
|
|
567
|
+
...(deliveries.length > 0 ? { causalSessionEventIds: causalSessionEventIds(persistedEvents, existingEventIds, deliveryAttempts, ingressEventId) } : {}),
|
|
542
568
|
...(responseCausalSessionEventId ? { responseCausalSessionEventId } : {}),
|
|
543
569
|
};
|
|
544
570
|
});
|
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.794",
|
|
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.794",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|