@ouro.bot/cli 0.1.0-alpha.765 → 0.1.0-alpha.766
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 +8 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/core.js +34 -9
- package/dist/repertoire/tools-continuity.js +19 -1
- package/dist/senses/sanctuary-full-visibility-contract.js +52 -2
- package/dist/senses/telegram-effect-adapter.js +9 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
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.766",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Keep Sanctuary status summaries grounded in successful current reads and stale-care-safe evidence.",
|
|
8
|
+
"Render Butler Telegram replies without literal bold markers while preserving commands and data.",
|
|
9
|
+
"Keep the packaged visibility contract clear of retired module markers."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.765",
|
|
6
14
|
"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.766</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
package/dist/heart/core.js
CHANGED
|
@@ -569,6 +569,11 @@ function toolResultIndicatesFailure(content) {
|
|
|
569
569
|
|| normalized.startsWith("blocked:")
|
|
570
570
|
|| normalized.startsWith("rejected:");
|
|
571
571
|
}
|
|
572
|
+
function requiredToolResultSucceeded(name, content, validate) {
|
|
573
|
+
if (toolResultIndicatesFailure(content))
|
|
574
|
+
return false;
|
|
575
|
+
return validate?.(name, content) ?? true;
|
|
576
|
+
}
|
|
572
577
|
function effectFingerprint(name, rawArguments) {
|
|
573
578
|
let args;
|
|
574
579
|
try {
|
|
@@ -1128,6 +1133,12 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1128
1133
|
? { missing, message: `${options.requiredToolCalls.retryMessage} Missing required tool calls: ${missing.join(", ")}.` }
|
|
1129
1134
|
: null;
|
|
1130
1135
|
};
|
|
1136
|
+
const queueRequiredCorrection = (message, limitContext) => {
|
|
1137
|
+
if (providerIterations >= exports.MAX_PROVIDER_ITERATIONS)
|
|
1138
|
+
throw new Error(`provider iteration limit exhausted at response ${exports.MAX_PROVIDER_ITERATIONS} ${limitContext}`);
|
|
1139
|
+
messages.push({ role: "user", content: message });
|
|
1140
|
+
providerRuntime.resetTurnState(messages);
|
|
1141
|
+
};
|
|
1131
1142
|
const toolLoopState = (0, tool_loop_1.createToolLoopState)();
|
|
1132
1143
|
const toolFrictionLedger = (0, tool_friction_1.createToolFrictionLedger)();
|
|
1133
1144
|
const finishTerminalProviderError = (error, classification) => {
|
|
@@ -1530,11 +1541,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1530
1541
|
if (requiredToolCallsGate) {
|
|
1531
1542
|
streamCallbackBuffer?.discard();
|
|
1532
1543
|
callbacks.onClearText?.();
|
|
1533
|
-
|
|
1534
|
-
throw new Error(`provider iteration limit exhausted at response ${exports.MAX_PROVIDER_ITERATIONS} before required tool calls completed`);
|
|
1535
|
-
}
|
|
1536
|
-
pushGenerated(msg);
|
|
1537
|
-
messages.push({ role: "user", content: requiredToolCallsGate.message });
|
|
1544
|
+
queueRequiredCorrection(requiredToolCallsGate.message, "before required tool calls completed");
|
|
1538
1545
|
(0, runtime_1.emitNervesEvent)({
|
|
1539
1546
|
level: "warn",
|
|
1540
1547
|
component: "engine",
|
|
@@ -1544,6 +1551,14 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1544
1551
|
});
|
|
1545
1552
|
continue;
|
|
1546
1553
|
}
|
|
1554
|
+
const requiredAnswerRejection = options?.requiredToolCalls?.validateTerminalAnswer?.(String(msg.content ?? ""));
|
|
1555
|
+
if (requiredAnswerRejection) {
|
|
1556
|
+
streamCallbackBuffer?.discard();
|
|
1557
|
+
callbacks.onClearText?.();
|
|
1558
|
+
queueRequiredCorrection(requiredAnswerRejection, "before required terminal answer validation completed");
|
|
1559
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.required_tool_answer_rejected", message: "unsupported terminal answer rejected after required reads", meta: { answerLength: String(msg.content ?? "").length } });
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1547
1562
|
if (privateReturnTextAckRetryError) {
|
|
1548
1563
|
streamCallbackBuffer?.discard();
|
|
1549
1564
|
callbacks.onClearText?.();
|
|
@@ -1751,9 +1766,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1751
1766
|
streamCallbackBuffer?.discard();
|
|
1752
1767
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1753
1768
|
callbacks.onClearText?.();
|
|
1754
|
-
|
|
1755
|
-
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: requiredToolCallsGate.message });
|
|
1756
|
-
providerRuntime.appendToolOutput(result.toolCalls[0].id, requiredToolCallsGate.message);
|
|
1769
|
+
queueRequiredCorrection(requiredToolCallsGate.message, "before required tool calls completed");
|
|
1757
1770
|
(0, runtime_1.emitNervesEvent)({
|
|
1758
1771
|
level: "warn",
|
|
1759
1772
|
component: "engine",
|
|
@@ -1778,6 +1791,15 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1778
1791
|
// Extract answer from the tool call arguments.
|
|
1779
1792
|
// Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
|
|
1780
1793
|
const { answer, intent } = parseSettlePayload(result.toolCalls[0].arguments);
|
|
1794
|
+
const requiredAnswerRejection = options?.requiredToolCalls?.validateTerminalAnswer?.(answer);
|
|
1795
|
+
if (requiredAnswerRejection) {
|
|
1796
|
+
streamCallbackBuffer?.discard();
|
|
1797
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1798
|
+
callbacks.onClearText?.();
|
|
1799
|
+
queueRequiredCorrection(requiredAnswerRejection, "before required terminal answer validation completed");
|
|
1800
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.required_tool_answer_rejected", message: "unsupported settle answer rejected after required reads", meta: { answerLength: answer.length } });
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1781
1803
|
// Private-runtime settle: no CompletionMetadata, "(settled)" ack
|
|
1782
1804
|
if (isPrivateRuntimeChannel) {
|
|
1783
1805
|
streamCallbackBuffer?.discard();
|
|
@@ -2289,7 +2311,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
2289
2311
|
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
2290
2312
|
const routineActionSelection = approvalCalls.find((entry) => entry.call.id === tc.id)?.routineActionSelection;
|
|
2291
2313
|
const executionToolContext = routineActionSelection && augmentedToolContext ? { ...augmentedToolContext, routineActionSelection } : augmentedToolContext;
|
|
2292
|
-
if (requiredToolCallNames.includes(tc.name))
|
|
2314
|
+
if (requiredToolCallNames.includes(tc.name) && !options?.requiredToolCalls?.requireSuccessfulResults)
|
|
2293
2315
|
dispatchedRequiredToolCalls.add(tc.name);
|
|
2294
2316
|
toolResult = await execToolFn(tc.name, args, executionToolContext);
|
|
2295
2317
|
success = true;
|
|
@@ -2299,6 +2321,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
2299
2321
|
success = false;
|
|
2300
2322
|
augmentedToolContext?.habitSession?.recordError?.(toolResult);
|
|
2301
2323
|
}
|
|
2324
|
+
if (success && requiredToolCallNames.includes(tc.name) && options?.requiredToolCalls?.requireSuccessfulResults
|
|
2325
|
+
&& requiredToolResultSucceeded(tc.name, toolResult, options.requiredToolCalls.validateRequiredToolResult))
|
|
2326
|
+
dispatchedRequiredToolCalls.add(tc.name);
|
|
2302
2327
|
if (success && currentEffectFingerprint && !toolResultIndicatesFailure(toolResult)) {
|
|
2303
2328
|
unresolvedHistoricalEffects = unresolvedHistoricalEffects.filter((effect) => effect.fingerprint !== currentEffectFingerprint);
|
|
2304
2329
|
}
|
|
@@ -45,6 +45,24 @@ const presence_1 = require("../arc/presence");
|
|
|
45
45
|
const intentions_1 = require("../arc/intentions");
|
|
46
46
|
const steward_policy_1 = require("../heart/steward-policy");
|
|
47
47
|
const await_parser_1 = require("../heart/awaiting/await-parser");
|
|
48
|
+
function presentCare(care) {
|
|
49
|
+
const staleAt = care.nextCheckAt ? Date.parse(care.nextCheckAt) : Number.NaN;
|
|
50
|
+
if (care.kind !== "system" || !["active", "watching"].includes(care.status) || care.nextCheckAt === null)
|
|
51
|
+
return care;
|
|
52
|
+
if (Number.isFinite(staleAt) && staleAt >= Date.now())
|
|
53
|
+
return care;
|
|
54
|
+
return {
|
|
55
|
+
id: care.id,
|
|
56
|
+
kind: care.kind,
|
|
57
|
+
status: care.status,
|
|
58
|
+
salience: care.salience,
|
|
59
|
+
steward: care.steward,
|
|
60
|
+
evidenceStatus: "stale",
|
|
61
|
+
recheckRequired: true,
|
|
62
|
+
staleAt: care.nextCheckAt,
|
|
63
|
+
lastAssessedAt: care.updatedAt,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
48
66
|
exports.continuityToolDefinitions = [
|
|
49
67
|
// ── Continuity tools ──────────────────────────────────────────────
|
|
50
68
|
{
|
|
@@ -308,7 +326,7 @@ exports.continuityToolDefinitions = [
|
|
|
308
326
|
},
|
|
309
327
|
handler: (a) => {
|
|
310
328
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
311
|
-
const cares = a.status === "all" ? (0, cares_1.readCares)(agentRoot) : (0, cares_1.readActiveCares)(agentRoot);
|
|
329
|
+
const cares = (a.status === "all" ? (0, cares_1.readCares)(agentRoot) : (0, cares_1.readActiveCares)(agentRoot)).map(presentCare);
|
|
312
330
|
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_cares", message: `queried ${cares.length} cares`, meta: { count: cares.length } });
|
|
313
331
|
return JSON.stringify(cares, null, 2);
|
|
314
332
|
},
|
|
@@ -2,8 +2,55 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sanctuaryFullVisibilityRequiredToolCalls = sanctuaryFullVisibilityRequiredToolCalls;
|
|
4
4
|
const runtime_1 = require("../nerves/runtime");
|
|
5
|
-
const REQUIRED_TOOL_NAMES = ["query_active_work", "query_cares", "unraid_get_system", "unraid_list_containers"];
|
|
5
|
+
const REQUIRED_TOOL_NAMES = ["query_active_work", "query_cares", "unraid_get_system", "unraid_list_containers", "unraid_get_storage", "sanctuary_get_download_queue"];
|
|
6
6
|
const WHOLE_STATUS_REQUESTS = new Set(["what are you working on", "what's going on with sanctuary"]);
|
|
7
|
+
function unsupportedCurrentClaim(answer) {
|
|
8
|
+
const unsupported = answer.replace(/docker\.img/giu, "docker image").split(/[,;!?\n]|(?<!\d)\.(?!\d)/u).some((sentence) => {
|
|
9
|
+
if (!/docker image(?: disk)?/iu.test(sentence))
|
|
10
|
+
return false;
|
|
11
|
+
const uncertaintyOnly = /\b(?:cannot|can't|unable to) (?:currently )?(?:verify|measure)\b|\b(?:unknown|unverified)\b|\bneeds? (?:a )?(?:fresh |authoritative )*(?:check|measurement)\b/iu.test(sentence);
|
|
12
|
+
const stateClaim = /\b\d+(?:\.\d+)?\s*%|\bfull\b|\b(?:no|out of) space\b|\bwrites? (?:will |may )?fail\b|\b(?:healthy|unhealthy|running|stopped)\b/iu.test(sentence);
|
|
13
|
+
return stateClaim || !uncertaintyOnly;
|
|
14
|
+
});
|
|
15
|
+
if (unsupported)
|
|
16
|
+
return "No current Butler tool measures Docker image utilization. Do not report a Docker image percentage or full state as current; say that the stale care needs a fresh authoritative check.";
|
|
17
|
+
const unsupportedProviderClaim = answer.split(/[,;!?\n]|(?<!\d)\.(?!\d)/u).some((clause) => {
|
|
18
|
+
const explicitlyUnverified = /\b(?:cannot|can't|unable to) (?:currently )?(?:verify|confirm)|\b(?:unknown|unverified|stale|historical|previous|prior)\b|\bneeds? (?:a )?(?:fresh |authoritative )*(?:check|verification)\b/iu.test(clause);
|
|
19
|
+
if (explicitlyUnverified)
|
|
20
|
+
return false;
|
|
21
|
+
if (/\b(?:Astraweb|Usenet provider|download provider|(?:block|prepaid|download|provider) credit)\b/iu.test(clause))
|
|
22
|
+
return true;
|
|
23
|
+
return /\bSABnzbd\b/iu.test(clause) && /\b(?:auth(?:entication)?|authenticated|credentials?)\b/iu.test(clause);
|
|
24
|
+
});
|
|
25
|
+
return unsupportedProviderClaim ? "The current queue read does not prove provider credit or authentication status. Do not report Astraweb, block-credit, or authentication failure as current; say that the provider needs a fresh authoritative check." : undefined;
|
|
26
|
+
}
|
|
27
|
+
function successfulCurrentResult(name, result) {
|
|
28
|
+
if (Buffer.byteLength(result, "utf8") > 1_000_000)
|
|
29
|
+
return false;
|
|
30
|
+
if (name === "query_active_work")
|
|
31
|
+
return result.trimStart().startsWith("this is my current top-level live world-state.");
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(result);
|
|
34
|
+
if (name === "query_cares")
|
|
35
|
+
return Array.isArray(parsed);
|
|
36
|
+
if (name === "sanctuary_get_download_queue") {
|
|
37
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
38
|
+
return false;
|
|
39
|
+
const queue = parsed;
|
|
40
|
+
const exactKeys = ["observedAt", "paused", "queuedJobs", "stateDigest", "status"];
|
|
41
|
+
return JSON.stringify(Object.keys(queue).sort()) === JSON.stringify(exactKeys)
|
|
42
|
+
&& typeof queue.paused === "boolean"
|
|
43
|
+
&& typeof queue.status === "string" && Buffer.byteLength(queue.status, "utf8") <= 64
|
|
44
|
+
&& Number.isSafeInteger(queue.queuedJobs) && Number(queue.queuedJobs) >= 0 && Number(queue.queuedJobs) <= 1_000_000
|
|
45
|
+
&& typeof queue.observedAt === "string" && Number.isFinite(Date.parse(queue.observedAt)) && new Date(queue.observedAt).toISOString() === queue.observedAt
|
|
46
|
+
&& typeof queue.stateDigest === "string" && /^[a-f0-9]{64}$/u.test(queue.stateDigest);
|
|
47
|
+
}
|
|
48
|
+
return !!parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.ok === true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
7
54
|
function sanctuaryFullVisibilityRequiredToolCalls(request, advertisedToolNames) {
|
|
8
55
|
const normalized = request.normalize("NFKC").trim().toLocaleLowerCase("en-US").replaceAll("’", "'").replace(/[?!.\s]+$/gu, "");
|
|
9
56
|
if (!WHOLE_STATUS_REQUESTS.has(normalized) || !REQUIRED_TOOL_NAMES.every((name) => advertisedToolNames.includes(name)))
|
|
@@ -11,6 +58,9 @@ function sanctuaryFullVisibilityRequiredToolCalls(request, advertisedToolNames)
|
|
|
11
58
|
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.sanctuary_full_visibility_reads_required", message: "required current Sanctuary visibility reads", meta: { toolCount: REQUIRED_TOOL_NAMES.length } });
|
|
12
59
|
return {
|
|
13
60
|
names: REQUIRED_TOOL_NAMES,
|
|
14
|
-
retryMessage: "Before answering, read current active work, cares, system health, and
|
|
61
|
+
retryMessage: "Before answering, read current active work, cares, system health, service state, storage, and the download queue. Current tool facts outrank care history; a stale care is a recheck item, not a present-tense fact. Then give Ari one compact household summary; do not ask him to choose a status slice.",
|
|
62
|
+
requireSuccessfulResults: true,
|
|
63
|
+
validateRequiredToolResult: successfulCurrentResult,
|
|
64
|
+
validateTerminalAnswer: unsupportedCurrentClaim,
|
|
15
65
|
};
|
|
16
66
|
}
|
|
@@ -76,6 +76,12 @@ function preparedTexts(effect) {
|
|
|
76
76
|
return [requireText(effect.text, "Telegram text")];
|
|
77
77
|
return [effect.text?.trim() || null];
|
|
78
78
|
}
|
|
79
|
+
function telegramPlainText(text) {
|
|
80
|
+
return text.replace(/(^|[\s([{])\*\*([\p{L}\p{N}][^*\n]*?)\*\*(?=$|[\s.,;!?)\]}])/gmu, "$1$2");
|
|
81
|
+
}
|
|
82
|
+
function presentTelegramEffect(authorClass, effect) {
|
|
83
|
+
return authorClass === "butler" && effect.kind === "text" ? { ...effect, text: telegramPlainText(effect.text) } : effect;
|
|
84
|
+
}
|
|
79
85
|
function assertEffectTarget(target, effect, idempotencyKey) {
|
|
80
86
|
if (target.kind === "admission_gate") {
|
|
81
87
|
if (effect.kind !== "admission_ack" || effect.text !== exports.FIXED_ADMISSION_ACKNOWLEDGEMENT || idempotencyKey !== `ack:${target.admissionId}`) {
|
|
@@ -425,11 +431,12 @@ function createTelegramAuthorizedEffectExecutor(options) {
|
|
|
425
431
|
if (input.signal?.aborted)
|
|
426
432
|
throw input.signal.reason;
|
|
427
433
|
barrier();
|
|
428
|
-
const
|
|
434
|
+
const presentedInput = { ...input, effect: presentTelegramEffect(input.authorClass, input.effect) };
|
|
435
|
+
const authorization = await options.authorize({ phase: "prepare", ...presentedInput });
|
|
429
436
|
if (!authorization.allowed)
|
|
430
437
|
throw new Error(`Telegram effect authorization denied: ${authorization.reason}`);
|
|
431
438
|
const store = getStore();
|
|
432
|
-
const prepared = prepareTelegramEffect(store, { ...
|
|
439
|
+
const prepared = prepareTelegramEffect(store, { ...presentedInput, authorization });
|
|
433
440
|
try {
|
|
434
441
|
const executed = await executeTelegramEffect(store, prepared.id, options.api, async (artifact) => {
|
|
435
442
|
if (input.signal?.aborted)
|
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.766",
|
|
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.766",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|