@ouro.bot/cli 0.1.0-alpha.790 → 0.1.0-alpha.792
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 +20 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/agent-entry.js +1 -1
- package/dist/heart/config.js +14 -0
- package/dist/heart/daemon/daemon-entry.js +1 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +14 -1
- package/dist/repertoire/tools-session.js +12 -0
- package/dist/repertoire/tools-surface.js +47 -33
- package/dist/senses/pipeline.js +36 -7
- package/dist/senses/sanctuary-media-catalog-contract.js +8 -5
- package/dist/senses/sanctuary-media-optimization.js +2 -3
- package/dist/senses/sanctuary-storage-optimization-contract.js +1 -1
- package/dist/senses/shared-turn.js +7 -1
- package/dist/senses/telegram.js +5 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
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.792",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Catch asynchronous bundle watcher errors so transient pending-message processing files on live filesystems cannot tombstone the daemon or interrupt private-runtime wakes.",
|
|
8
|
+
"Warm runtime credentials before private-runtime bootstrap so Telegram-capable workers start with runtime config instead of racing the managed Telegram sense.",
|
|
9
|
+
"Treat simple Sanctuary library visibility asks like 'see the lib' as catalog reads, and block replies that send Ari to inspect Jellyfin, Unmanic, dashboards, or logs while safe evidence tools are available.",
|
|
10
|
+
"Degrade optional Unmanic file-size metrics, including failed detail reads, without hiding available media catalog evidence."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "0.1.0-alpha.791",
|
|
15
|
+
"changes": [
|
|
16
|
+
"Promote operator CLI pending messages into the active inner-turn user message so private-runtime wakes answer the text that woke them instead of stale heartbeat context.",
|
|
17
|
+
"Keep non-operator pending work in the background pending section so automated noise does not become the live user ask.",
|
|
18
|
+
"Deliver private-runtime Telegram surface and send_message returns through the existing Telegram effect journal instead of queuing ghost messages for the next chat.",
|
|
19
|
+
"Canonicalize Telegram session filename keys before live delivery so production sanitized session paths still authorize against the real Telegram binding.",
|
|
20
|
+
"Prefer the authoritative settle tool answer over incidental streamed prose for shared-turn delivery, preventing concatenated Telegram replies.",
|
|
21
|
+
"Expire stale generic Telegram outreach from the pending queue while preserving request-bound returns and trust/admission notices."
|
|
22
|
+
]
|
|
23
|
+
},
|
|
4
24
|
{
|
|
5
25
|
"version": "0.1.0-alpha.790",
|
|
6
26
|
"changes": [
|
|
@@ -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.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.792</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -172,7 +172,7 @@ Promise.resolve().then(() => __importStar(require("./runtime-credentials"))).the
|
|
|
172
172
|
drainBufferedRuntimeCredentialBootstrap(applyRuntimeCredentialBootstrapMessage);
|
|
173
173
|
}
|
|
174
174
|
if (!readRuntimeCredentialConfig(agentName).ok) {
|
|
175
|
-
|
|
175
|
+
await refreshRuntimeCredentialConfig(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
|
|
176
176
|
}
|
|
177
177
|
const providerPool = readProviderCredentialPool(agentName);
|
|
178
178
|
const providerTargets = await selectedProviderTargets(agentName);
|
package/dist/heart/config.js
CHANGED
|
@@ -57,6 +57,7 @@ exports.getSyncConfig = getSyncConfig;
|
|
|
57
57
|
exports.getOpenAIEmbeddingsApiKey = getOpenAIEmbeddingsApiKey;
|
|
58
58
|
exports.getLogsDir = getLogsDir;
|
|
59
59
|
exports.sanitizeKey = sanitizeKey;
|
|
60
|
+
exports.canonicalizeTelegramSessionKey = canonicalizeTelegramSessionKey;
|
|
60
61
|
exports.slugify = slugify;
|
|
61
62
|
exports.resolveSessionPath = resolveSessionPath;
|
|
62
63
|
exports.sessionPath = sessionPath;
|
|
@@ -351,6 +352,19 @@ function getLogsDir() {
|
|
|
351
352
|
function sanitizeKey(key) {
|
|
352
353
|
return key.replace(/[/:]/g, "_");
|
|
353
354
|
}
|
|
355
|
+
function canonicalizeTelegramSessionKey(key) {
|
|
356
|
+
if (key.startsWith("telegram:"))
|
|
357
|
+
return key;
|
|
358
|
+
if (!key.startsWith("telegram_"))
|
|
359
|
+
return key;
|
|
360
|
+
const suffix = key.slice("telegram_".length);
|
|
361
|
+
if (suffix.startsWith("tg_"))
|
|
362
|
+
return `telegram:${suffix}`;
|
|
363
|
+
const numericSession = suffix.match(/^([0-9]+)_(-?[0-9]+)$/u);
|
|
364
|
+
if (numericSession)
|
|
365
|
+
return `telegram:${numericSession[1]}:${numericSession[2]}`;
|
|
366
|
+
return key;
|
|
367
|
+
}
|
|
354
368
|
function slugify(value) {
|
|
355
369
|
return value
|
|
356
370
|
.trim()
|
|
@@ -591,6 +591,7 @@ async function prepareProviderRuntime() {
|
|
|
591
591
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
592
592
|
const readiness = await Promise.all(managedAgents.map(async (agent) => {
|
|
593
593
|
try {
|
|
594
|
+
await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
|
|
594
595
|
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
|
|
595
596
|
if (result.ok)
|
|
596
597
|
return null;
|
|
@@ -92,7 +92,20 @@ function createBundleWatcher(bundlesRoot, onChange, deps = DEFAULT_BUNDLE_WATCHE
|
|
|
92
92
|
}
|
|
93
93
|
try {
|
|
94
94
|
if (deps.existsSync(bundlesRoot)) {
|
|
95
|
-
|
|
95
|
+
const watcher = deps.watch(bundlesRoot, { recursive: true }, debouncedOnChange);
|
|
96
|
+
watcher.on?.("error", () => {
|
|
97
|
+
try {
|
|
98
|
+
watcher.close();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Already closed.
|
|
102
|
+
}
|
|
103
|
+
const index = watchers.indexOf(watcher);
|
|
104
|
+
if (index >= 0)
|
|
105
|
+
watchers.splice(index, 1);
|
|
106
|
+
debouncedOnChange();
|
|
107
|
+
});
|
|
108
|
+
watchers.push(watcher);
|
|
96
109
|
}
|
|
97
110
|
}
|
|
98
111
|
catch {
|
|
@@ -39,6 +39,7 @@ exports.formatButlerOperationalVisibility = formatButlerOperationalVisibility;
|
|
|
39
39
|
exports.renderInnerProgressStatus = renderInnerProgressStatus;
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const crypto_1 = require("crypto");
|
|
42
43
|
const config_1 = require("../heart/config");
|
|
43
44
|
const identity_1 = require("../heart/identity");
|
|
44
45
|
const session_events_1 = require("../heart/session-events");
|
|
@@ -1050,6 +1051,17 @@ exports.sessionToolDefinitions = [
|
|
|
1050
1051
|
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1051
1052
|
};
|
|
1052
1053
|
},
|
|
1054
|
+
telegram: async (request) => {
|
|
1055
|
+
const key = (0, config_1.canonicalizeTelegramSessionKey)(request.key);
|
|
1056
|
+
const hash = (0, crypto_1.createHash)("sha256").update(`${request.friendId}\0${key}\0${request.content}`).digest("hex");
|
|
1057
|
+
const { sendTelegramAwaitFollowUp } = await Promise.resolve().then(() => __importStar(require("../senses/telegram")));
|
|
1058
|
+
return sendTelegramAwaitFollowUp(agentName, {
|
|
1059
|
+
...request,
|
|
1060
|
+
key,
|
|
1061
|
+
requestId: request.requestId ?? `send-message:${hash}`,
|
|
1062
|
+
deliveryId: request.deliveryId ?? `send-message:${hash}`,
|
|
1063
|
+
});
|
|
1064
|
+
},
|
|
1053
1065
|
voice: async (request) => deliverVoiceChannelMessage(request, agentName, voiceInitialAudio),
|
|
1054
1066
|
},
|
|
1055
1067
|
});
|
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.surfaceToolDefinition = exports.surfaceToolDef = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
+
const crypto_1 = require("crypto");
|
|
39
|
+
const config_1 = require("../heart/config");
|
|
38
40
|
const identity_1 = require("../heart/identity");
|
|
39
41
|
const surface_tool_1 = require("../senses/surface-tool");
|
|
40
42
|
const obligations_1 = require("../arc/obligations");
|
|
@@ -173,6 +175,38 @@ exports.surfaceToolDefinition = {
|
|
|
173
175
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
174
176
|
const sessionsDir = path.join(agentRoot, "state", "sessions");
|
|
175
177
|
const friendsDir = path.join(agentRoot, "friends");
|
|
178
|
+
const queueToSession = async (target, detail) => {
|
|
179
|
+
const { queuePendingMessage, getPendingDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
|
|
180
|
+
const pendingDir = getPendingDir(agentName, target.friendId, target.channel, target.key);
|
|
181
|
+
queuePendingMessage(pendingDir, {
|
|
182
|
+
from: agentName,
|
|
183
|
+
friendId: target.friendId,
|
|
184
|
+
channel: target.channel,
|
|
185
|
+
key: target.key,
|
|
186
|
+
content,
|
|
187
|
+
timestamp: Date.now(),
|
|
188
|
+
});
|
|
189
|
+
return { status: "queued", detail };
|
|
190
|
+
};
|
|
191
|
+
const deliverTelegramOrQueue = async (target, detail) => {
|
|
192
|
+
const telegramKey = (0, config_1.canonicalizeTelegramSessionKey)(target.key);
|
|
193
|
+
const hash = (0, crypto_1.createHash)("sha256").update(`${target.friendId}\0${telegramKey}\0${content}`).digest("hex");
|
|
194
|
+
const requestId = queueItem?.obligationId ?? `surface:${hash}`;
|
|
195
|
+
const deliveryId = queueItem?.obligationId ? `surface:${queueItem.obligationId}:${hash}` : `surface:${hash}`;
|
|
196
|
+
const { sendTelegramAwaitFollowUp } = await Promise.resolve().then(() => __importStar(require("../senses/telegram")));
|
|
197
|
+
const delivered = await sendTelegramAwaitFollowUp(agentName, {
|
|
198
|
+
friendId: target.friendId,
|
|
199
|
+
channel: "telegram",
|
|
200
|
+
key: telegramKey,
|
|
201
|
+
content,
|
|
202
|
+
requestId,
|
|
203
|
+
deliveryId,
|
|
204
|
+
intent: "generic_outreach",
|
|
205
|
+
});
|
|
206
|
+
return delivered.status === "delivered_now"
|
|
207
|
+
? { status: "delivered", detail: "sent to Telegram now" }
|
|
208
|
+
: queueToSession(target, detail);
|
|
209
|
+
};
|
|
176
210
|
// Resolve friend name → UUID if needed (agents may pass name instead of UUID)
|
|
177
211
|
let resolvedFriendId = friendId;
|
|
178
212
|
if (!fs.existsSync(path.join(sessionsDir, friendId))) {
|
|
@@ -213,17 +247,10 @@ exports.surfaceToolDefinition = {
|
|
|
213
247
|
&& activity.channel !== "inner"
|
|
214
248
|
&& bridge.attachedSessions.some((s) => s.friendId === activity.friendId && s.channel === activity.channel && s.key === activity.key));
|
|
215
249
|
if (bridgeTarget) {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
friendId: bridgeTarget.friendId,
|
|
221
|
-
channel: bridgeTarget.channel,
|
|
222
|
-
key: bridgeTarget.key,
|
|
223
|
-
content,
|
|
224
|
-
timestamp: Date.now(),
|
|
225
|
-
});
|
|
226
|
-
return { status: "queued", detail: `for next interaction via ${bridgeTarget.channel}` };
|
|
250
|
+
const detail = `for next interaction via ${bridgeTarget.channel}`;
|
|
251
|
+
return bridgeTarget.channel === "telegram"
|
|
252
|
+
? deliverTelegramOrQueue(bridgeTarget, detail)
|
|
253
|
+
: queueToSession(bridgeTarget, detail);
|
|
227
254
|
}
|
|
228
255
|
}
|
|
229
256
|
}
|
|
@@ -232,17 +259,11 @@ exports.surfaceToolDefinition = {
|
|
|
232
259
|
// address before considering fresher sessions for the same friend,
|
|
233
260
|
// especially MCP sessions where check_response drains this path.
|
|
234
261
|
if (queueItem?.channel && queueItem.key && queueItem.channel !== "inner") {
|
|
235
|
-
const {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
channel: queueItem.channel,
|
|
241
|
-
key: queueItem.key,
|
|
242
|
-
content,
|
|
243
|
-
timestamp: Date.now(),
|
|
244
|
-
});
|
|
245
|
-
return { status: "queued", detail: `for originating ${queueItem.channel} session` };
|
|
262
|
+
const target = { friendId, channel: queueItem.channel, key: queueItem.key };
|
|
263
|
+
const detail = `for originating ${queueItem.channel} session`;
|
|
264
|
+
return queueItem.channel === "telegram"
|
|
265
|
+
? deliverTelegramOrQueue(target, detail)
|
|
266
|
+
: queueToSession(target, detail);
|
|
246
267
|
}
|
|
247
268
|
// Priority 2: Try proactive delivery first, then queue to freshest session
|
|
248
269
|
const allFriendSessions = (0, session_activity_1.listSessionActivity)({ sessionsDir, friendsDir, agentName, activeThresholdMs: Number.MAX_SAFE_INTEGER })
|
|
@@ -250,17 +271,10 @@ exports.surfaceToolDefinition = {
|
|
|
250
271
|
// Priority 2: Queue to freshest non-private-runtime session
|
|
251
272
|
const freshest = allFriendSessions[0];
|
|
252
273
|
if (freshest) {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
friendId: freshest.friendId,
|
|
258
|
-
channel: freshest.channel,
|
|
259
|
-
key: freshest.key,
|
|
260
|
-
content,
|
|
261
|
-
timestamp: Date.now(),
|
|
262
|
-
});
|
|
263
|
-
return { status: "queued", detail: `for next interaction via ${freshest.channel}` };
|
|
274
|
+
const detail = `for next interaction via ${freshest.channel}`;
|
|
275
|
+
return freshest.channel === "telegram"
|
|
276
|
+
? deliverTelegramOrQueue(freshest, detail)
|
|
277
|
+
: queueToSession(freshest, detail);
|
|
264
278
|
}
|
|
265
279
|
// Priority 3: Deferred — no active session found
|
|
266
280
|
const { getDeferredReturnDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -74,10 +74,21 @@ const flight_recorder_1 = require("../arc/flight-recorder");
|
|
|
74
74
|
const context_loss_sentinel_1 = require("../heart/context-loss-sentinel");
|
|
75
75
|
const cares_1 = require("../arc/cares");
|
|
76
76
|
const VOICE_PENDING_MAX_AGE_MS = 15 * 60 * 1_000;
|
|
77
|
-
|
|
77
|
+
const TELEGRAM_GENERIC_OUTREACH_PENDING_MAX_AGE_MS = 15 * 60 * 1_000;
|
|
78
|
+
function pendingExpirationReason(channel, message, now, agentName) {
|
|
78
79
|
/* v8 ignore start -- pending expiry edge permutations are covered by the stale voice queue tests; this helper keeps defensive non-voice fallbacks @preserve */
|
|
79
80
|
if (Number.isFinite(message.expiresAt) && Number(message.expiresAt) <= now)
|
|
80
81
|
return "explicit_expiry";
|
|
82
|
+
if (channel === "telegram"
|
|
83
|
+
&& agentName
|
|
84
|
+
&& message.from === agentName
|
|
85
|
+
&& !message.delegatedFrom
|
|
86
|
+
&& !message.obligationId
|
|
87
|
+
&& !message.requestId
|
|
88
|
+
&& Number.isFinite(message.timestamp)
|
|
89
|
+
&& now - message.timestamp > TELEGRAM_GENERIC_OUTREACH_PENDING_MAX_AGE_MS) {
|
|
90
|
+
return "telegram_generic_outreach_freshness_window";
|
|
91
|
+
}
|
|
81
92
|
if (channel !== "voice")
|
|
82
93
|
return null;
|
|
83
94
|
if (!Number.isFinite(message.timestamp))
|
|
@@ -139,10 +150,18 @@ function filterDeliverablePendingMessages(input) {
|
|
|
139
150
|
if (input.messages.length === 0)
|
|
140
151
|
return input.messages;
|
|
141
152
|
const now = input.now ?? Date.now();
|
|
153
|
+
const agentName = (() => {
|
|
154
|
+
try {
|
|
155
|
+
return (0, identity_1.getAgentName)();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
142
161
|
const deliverable = [];
|
|
143
162
|
const expired = [];
|
|
144
163
|
for (const message of input.messages) {
|
|
145
|
-
const reason = pendingExpirationReason(input.channel, message, now);
|
|
164
|
+
const reason = pendingExpirationReason(input.channel, message, now, agentName);
|
|
146
165
|
if (reason) {
|
|
147
166
|
expired.push({ message, reason });
|
|
148
167
|
}
|
|
@@ -786,6 +805,14 @@ async function handleInboundTurn(input) {
|
|
|
786
805
|
channel: input.channel,
|
|
787
806
|
messages: [...deferredReturns, ...sessionPending],
|
|
788
807
|
});
|
|
808
|
+
const promotedPendingUserMessages = input.channel === "inner"
|
|
809
|
+
? pending
|
|
810
|
+
.filter((message) => message.from === "ouro-cli" && message.content.trim().length > 0)
|
|
811
|
+
.map((message) => ({ role: "user", content: message.content }))
|
|
812
|
+
: [];
|
|
813
|
+
const backgroundPendingMessages = promotedPendingUserMessages.length > 0
|
|
814
|
+
? pending.filter((message) => message.from !== "ouro-cli")
|
|
815
|
+
: pending;
|
|
789
816
|
// Assemble messages: session messages + pending + inbound user messages
|
|
790
817
|
// NOTE: live world-state checkpoint and pending messages are rendered via buildSystem (system prompt sections)
|
|
791
818
|
const extraPrefixSections = input.onPendingDrained?.(pending) ?? [];
|
|
@@ -794,11 +821,11 @@ async function handleInboundTurn(input) {
|
|
|
794
821
|
input.messages[0] = prependTurnSections(input.messages[0], extraPrefixSections);
|
|
795
822
|
}
|
|
796
823
|
// Append user messages from the inbound turn
|
|
797
|
-
for (const msg of input.messages) {
|
|
824
|
+
for (const msg of [...input.messages, ...promotedPendingUserMessages]) {
|
|
798
825
|
(0, session_events_1.stampIngressTime)(msg);
|
|
799
826
|
sessionMessages.push(msg);
|
|
800
827
|
}
|
|
801
|
-
const currentUserMessages = input.messages.filter((message) => message.role === "user");
|
|
828
|
+
const currentUserMessages = [...input.messages, ...promotedPendingUserMessages].filter((message) => message.role === "user");
|
|
802
829
|
const orientationFrame = input.runAgentOptions?.orientationFrame
|
|
803
830
|
?? (currentUserMessages.length > 0
|
|
804
831
|
? (0, orientation_frame_1.buildOrientationFrame)({
|
|
@@ -891,8 +918,10 @@ async function handleInboundTurn(input) {
|
|
|
891
918
|
}
|
|
892
919
|
// Step 5: runAgent
|
|
893
920
|
const existingToolContext = input.runAgentOptions?.toolContext;
|
|
894
|
-
const
|
|
895
|
-
|
|
921
|
+
const promotedCurrentUserMessage = latestUserAuthoredText(promotedPendingUserMessages, undefined);
|
|
922
|
+
const currentUserMessage = promotedCurrentUserMessage
|
|
923
|
+
?? existingToolContext?.currentUserMessage
|
|
924
|
+
?? latestUserAuthoredText([...input.messages, ...promotedPendingUserMessages], input.continuityIngressTexts);
|
|
896
925
|
let runAgentOptions = {
|
|
897
926
|
...input.runAgentOptions,
|
|
898
927
|
resumePriorWork,
|
|
@@ -902,7 +931,7 @@ async function handleInboundTurn(input) {
|
|
|
902
931
|
activeWorkFrame,
|
|
903
932
|
delegationDecision,
|
|
904
933
|
startOfTurnPacket: renderedStartOfTurnPacket,
|
|
905
|
-
pendingMessages:
|
|
934
|
+
pendingMessages: backgroundPendingMessages.length > 0 ? backgroundPendingMessages.map((msg) => ({ from: msg.from, content: msg.content })) : undefined,
|
|
906
935
|
currentSessionKey: currentSession.key,
|
|
907
936
|
currentObligation,
|
|
908
937
|
mustResolveBeforeHandoff,
|
|
@@ -15,8 +15,8 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
15
15
|
if (!advertisedToolNames.includes("sanctuary_search_media_catalog"))
|
|
16
16
|
return undefined;
|
|
17
17
|
const normalized = normalizedRequest(request);
|
|
18
|
-
const mentionsMedia = /\b(?:film|films|movie|movies|show|shows|tv|jellyfin|watch|shelf|stock|catalog|library)\b/u.test(normalized);
|
|
19
|
-
const asksCatalog = /\b(?:have|got|stock|catalog|library|favorite|favourite|recommend|suggest|pick|watch)\b/u.test(normalized);
|
|
18
|
+
const mentionsMedia = /\b(?:film|films|movie|movies|show|shows|tv|jellyfin|watch|shelf|stock|catalog|library|lib)\b/u.test(normalized);
|
|
19
|
+
const asksCatalog = /\b(?:have|got|stock|catalog|library|lib|favorite|favourite|recommend|suggest|pick|watch|see)\b/u.test(normalized);
|
|
20
20
|
const titleInventoryQuestion = /\b(?:do|did|can)\s+(?:we|you)\s+(?:have|got|stock)\s+[a-z0-9'][a-z0-9' ]*\??$/u.test(normalized);
|
|
21
21
|
if ((!mentionsMedia && !titleInventoryQuestion) || !asksCatalog)
|
|
22
22
|
return undefined;
|
|
@@ -29,10 +29,13 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
29
29
|
});
|
|
30
30
|
return {
|
|
31
31
|
names,
|
|
32
|
-
retryMessage: "Use sanctuary_search_media_catalog before answering. If asked for taste or a favorite, form a light recommendation from returned catalog evidence instead of claiming you cannot have preferences. Keep it honest: say you cannot watch, but you can pick from the household shelf.",
|
|
32
|
+
retryMessage: "Use sanctuary_search_media_catalog before answering. If a broader media-optimization read fails or degrades, treat that as a diagnostic note and still use the catalog tool for ordinary library visibility questions. If asked for taste or a favorite, form a light recommendation from returned catalog evidence instead of claiming you cannot have preferences. Keep it honest: say you cannot watch, but you can pick from the household shelf.",
|
|
33
33
|
validateTerminalAnswer(answer) {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
if (/\b(?:just|only|actually|merely)\s+(?:a\s+)?(?:bot|ai|assistant)\b|\bi don'?t actually watch\b/iu.test(answer)) {
|
|
35
|
+
return "Answer from the catalog evidence with a truthful but personable recommendation; do not retreat into 'I am just a bot' framing.";
|
|
36
|
+
}
|
|
37
|
+
return /\b(?:you'?d|you\s+would|you\s+need\s+to|please)\b.*\b(?:check|look at|poke|nudge)\b.*\b(?:log|logs|dashboard|jellyfin|unmanic)\b/iu.test(answer)
|
|
38
|
+
? "Do not send Ari to check Jellyfin, Unmanic, dashboards, or logs for ordinary library visibility while safe catalog tools are available. Use the catalog evidence, or if the catalog read itself fails, say exactly what I could and could not verify."
|
|
36
39
|
: undefined;
|
|
37
40
|
},
|
|
38
41
|
};
|
|
@@ -314,9 +314,8 @@ function createSanctuaryMediaOptimizationClient(options) {
|
|
|
314
314
|
};
|
|
315
315
|
}
|
|
316
316
|
catch (error) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
history = { available: false, reason: "file_size_metrics panel returned invalid data" };
|
|
317
|
+
const code = error.code;
|
|
318
|
+
history = { available: false, reason: code === "invalid_response" ? "file_size_metrics panel returned invalid data" : "file_size_metrics panel is unavailable" };
|
|
320
319
|
}
|
|
321
320
|
return {
|
|
322
321
|
data: {
|
|
@@ -27,6 +27,6 @@ function sanctuaryStorageOptimizationRequiredToolCalls(request, _advertisedToolN
|
|
|
27
27
|
});
|
|
28
28
|
return {
|
|
29
29
|
names,
|
|
30
|
-
retryMessage: "Run both safe reads now, identify the largest measured evidence, report Unmanic and Jellyfin findings, and propose a sample encode without inventing future savings. Do not ask permission or send Ari to a shell or QDirStat while these typed reads are available.",
|
|
30
|
+
retryMessage: "Run both safe reads now, identify the largest measured evidence, report Unmanic and Jellyfin findings, and propose a sample encode without inventing future savings. If one read returns a degraded or partial result, continue with the other safe reads and bounded container/log tools before answering. Do not ask permission or send Ari to a shell, dashboard, logs, or QDirStat while these typed reads are available.",
|
|
31
31
|
};
|
|
32
32
|
}
|
|
@@ -418,7 +418,13 @@ async function runSenseTurn(options) {
|
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
420
|
const persistedEvents = persistPromise ? await persistPromise : [];
|
|
421
|
-
|
|
421
|
+
const finalDeliveryKind = terminalDeliveryKind;
|
|
422
|
+
if (finalDeliveryKind === "settle" && Array.isArray(turnResult.messages)) {
|
|
423
|
+
const settledText = extractOutwardSenseDeliveryText(turnResult.messages);
|
|
424
|
+
if (settledText)
|
|
425
|
+
pendingResponseText = settledText;
|
|
426
|
+
}
|
|
427
|
+
await deliverPending(finalDeliveryKind, { throwOnError: false });
|
|
422
428
|
const ponderDeferred = false;
|
|
423
429
|
// Build response
|
|
424
430
|
let finalResponse;
|
package/dist/senses/telegram.js
CHANGED
|
@@ -67,6 +67,7 @@ const sanctuary_runtime_1 = require("./sanctuary-runtime");
|
|
|
67
67
|
const sanctuary_sab_1 = require("./sanctuary-sab");
|
|
68
68
|
const sanctuary_download_credit_presentation_1 = require("./sanctuary-download-credit-presentation");
|
|
69
69
|
const shared_turn_1 = require("./shared-turn");
|
|
70
|
+
const config_1 = require("../heart/config");
|
|
70
71
|
const telegram_client_1 = require("./telegram-client");
|
|
71
72
|
const sanctuary_runtime_2 = require("./sanctuary-runtime");
|
|
72
73
|
const sanctuary_full_visibility_contract_1 = require("./sanctuary-full-visibility-contract");
|
|
@@ -1015,19 +1016,20 @@ function createTelegramSenseApp(options) {
|
|
|
1015
1016
|
const deliverAwaitFollowUp = async (request) => {
|
|
1016
1017
|
if (!request.requestId)
|
|
1017
1018
|
return { status: "blocked", detail: "Telegram follow-up is missing its request binding" };
|
|
1019
|
+
const sessionKey = (0, config_1.canonicalizeTelegramSessionKey)(request.key);
|
|
1018
1020
|
try {
|
|
1019
|
-
const obligation = (0, obligations_1.findPendingObligationForRequest)(agentRoot, { requestId: request.requestId, owedTo: { friendId: request.friendId, channel: "telegram", key:
|
|
1021
|
+
const obligation = (0, obligations_1.findPendingObligationForRequest)(agentRoot, { requestId: request.requestId, owedTo: { friendId: request.friendId, channel: "telegram", key: sessionKey } });
|
|
1020
1022
|
if (obligation && !obligation.returnReadyAt)
|
|
1021
1023
|
(0, obligations_1.markObligationReturnReady)(agentRoot, obligation.id, request.deliveryId ?? `telegram-return:${request.requestId}`);
|
|
1022
1024
|
const expiry = request.deliveryId?.endsWith(":expired") === true;
|
|
1023
1025
|
const artifact = await executeAuthorizedEffect({
|
|
1024
1026
|
idempotencyKey: `await-${expiry ? "expiry-" : ""}follow-up:${request.requestId}:${(0, node_crypto_1.createHash)("sha256").update(request.deliveryId ?? request.content).digest("hex")}`,
|
|
1025
|
-
target: { kind: "approved_relationship", friendId: request.friendId, sessionKey
|
|
1027
|
+
target: { kind: "approved_relationship", friendId: request.friendId, sessionKey, requestId: request.requestId },
|
|
1026
1028
|
authorClass: "butler",
|
|
1027
1029
|
effect: { kind: "text", text: request.content },
|
|
1028
1030
|
...(obligation ? { obligationReturnId: obligation.id } : {}),
|
|
1029
1031
|
});
|
|
1030
|
-
await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, request.friendId, "telegram",
|
|
1032
|
+
await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, request.friendId, "telegram", sessionKey, agentRoot), [artifact]);
|
|
1031
1033
|
return { status: "delivered_now", detail: "sent to the exact request-bound Telegram chat" };
|
|
1032
1034
|
}
|
|
1033
1035
|
catch (error) {
|
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.792",
|
|
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.792",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|