@ouro.bot/cli 0.1.0-alpha.960 → 0.1.0-alpha.962
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 +14 -0
- package/deploy/unraid/sanctuary-replay-gate.mjs +2 -1
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/awaiting/await-runtime-state.js +19 -0
- package/dist/repertoire/tools-awaiting.js +30 -5
- package/dist/senses/await-turn-message.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
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.962",
|
|
6
|
+
"changes": [
|
|
7
|
+
"The upgrade check that asks the Butler to change his personality now asks for a change for everyone, so his saved per-person preference can no longer make the check pass or fail at random.",
|
|
8
|
+
"When the Butler finishes watching something for you, he now tells you in his own plain words what he checked, instead of forwarding his internal notes.",
|
|
9
|
+
"If the Butler's first try at telling you a watch finished does not go through, he resends the same words instead of getting stuck."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.961",
|
|
14
|
+
"changes": [
|
|
15
|
+
"When Butler finishes a watch you asked for in Telegram, he now actually tells you and closes it, instead of silently re-checking every few hours."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.960",
|
|
6
20
|
"changes": [
|
|
@@ -428,7 +428,8 @@ export const CASES = [
|
|
|
428
428
|
{
|
|
429
429
|
id: "psyche-via-pr",
|
|
430
430
|
// A concrete ask. Given only "can you change your personality?" the Butler describes the pull-request path and offers to file it once told what to change (alpha.878 session bf112be9), which is reasonable, so he never calls a filing tool.
|
|
431
|
-
|
|
431
|
+
// It asks for everyone, not "with me": the replay principal already has a saved formal, flat preference, so "with me" is already met and offering to file was an equally right answer, which failed this case in about half of runs (alpha.961 sessions 09585e4a, c9d1ee4d, b196f170).
|
|
432
|
+
words: "change your personality for everyone you talk to, not just me: drop the jokes and be formal and flat from now on",
|
|
432
433
|
sender: "principal",
|
|
433
434
|
delegated: false,
|
|
434
435
|
readback: ({ trace, reply, said, before, after }) => {
|
|
@@ -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.962</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -38,6 +38,9 @@ exports.applyAwaitRuntimeState = applyAwaitRuntimeState;
|
|
|
38
38
|
exports.writeAwaitRuntimeState = writeAwaitRuntimeState;
|
|
39
39
|
exports.recordAwaitDispatch = recordAwaitDispatch;
|
|
40
40
|
exports.recordAwaitCheck = recordAwaitCheck;
|
|
41
|
+
exports.pinAwaitAlertContent = pinAwaitAlertContent;
|
|
42
|
+
exports.clearAwaitAlertContent = clearAwaitAlertContent;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
41
44
|
const path = __importStar(require("path"));
|
|
42
45
|
const json_store_1 = require("../../arc/json-store");
|
|
43
46
|
const session_events_1 = require("../session-events");
|
|
@@ -109,3 +112,19 @@ function recordAwaitCheck(agentRoot, name, observation, now) {
|
|
|
109
112
|
checked_count: nextCount,
|
|
110
113
|
});
|
|
111
114
|
}
|
|
115
|
+
// A request-bound alert's Telegram effect is keyed by the await name alone, so a
|
|
116
|
+
// retry with different words is refused as a reused key and the watch never
|
|
117
|
+
// closes. The first text tried is pinned and every retry resends it.
|
|
118
|
+
function awaitAlertDir(agentRoot) {
|
|
119
|
+
return path.join(agentRoot, "state", "await-alerts");
|
|
120
|
+
}
|
|
121
|
+
function pinAwaitAlertContent(agentRoot, name, content) {
|
|
122
|
+
const pinned = (0, json_store_1.readJsonFile)(awaitAlertDir(agentRoot), name);
|
|
123
|
+
if (typeof pinned?.content === "string")
|
|
124
|
+
return pinned.content;
|
|
125
|
+
(0, json_store_1.writeJsonFile)(awaitAlertDir(agentRoot), name, { schemaVersion: 1, name, content });
|
|
126
|
+
return content;
|
|
127
|
+
}
|
|
128
|
+
function clearAwaitAlertContent(agentRoot, name) {
|
|
129
|
+
fs.rmSync(path.join(awaitAlertDir(agentRoot), `${name}.json`), { force: true });
|
|
130
|
+
}
|
|
@@ -55,6 +55,7 @@ const session_events_1 = require("../heart/session-events");
|
|
|
55
55
|
const runtime_1 = require("../nerves/runtime");
|
|
56
56
|
const await_parser_1 = require("../heart/awaiting/await-parser");
|
|
57
57
|
const await_alert_1 = require("../heart/awaiting/await-alert");
|
|
58
|
+
const await_runtime_state_1 = require("../heart/awaiting/await-runtime-state");
|
|
58
59
|
const friends_1 = require("@ouro.bot/friends");
|
|
59
60
|
const a2a_await_delivery_1 = require("../heart/awaiting/a2a-await-delivery");
|
|
60
61
|
const pending_1 = require("../mind/pending");
|
|
@@ -143,9 +144,28 @@ function setAwaitToolDeps(deps) {
|
|
|
143
144
|
function resetAwaitToolDeps() {
|
|
144
145
|
injected = {};
|
|
145
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* A tick runs in the private runtime, where no resident Telegram sense registered a deliverer. Without this one-shot
|
|
149
|
+
* sender a Telegram-bound watch queued its alert in the inner pending folder, never reached the owner, and never closed.
|
|
150
|
+
* A resident sense's own deliverer still wins when one is registered.
|
|
151
|
+
*/
|
|
152
|
+
async function sendTelegramAwaitAlert(agentName, request) {
|
|
153
|
+
// Only a request-bound watch has an exact chat to answer; any other alert keeps queueing for the agent's next turn.
|
|
154
|
+
if (!request.requestId)
|
|
155
|
+
return { status: "unavailable", detail: "no request-bound Telegram chat to answer" };
|
|
156
|
+
const { sendTelegramAwaitFollowUp } = await Promise.resolve().then(() => __importStar(require("../senses/telegram")));
|
|
157
|
+
return sendTelegramAwaitFollowUp(agentName, request);
|
|
158
|
+
}
|
|
146
159
|
function resolveDeliveryDeps(agentName) {
|
|
147
160
|
const deps = injected.buildDeliveryDeps ? injected.buildDeliveryDeps(agentName) : defaultDeliveryDeps(agentName);
|
|
148
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
...deps,
|
|
163
|
+
deliverers: {
|
|
164
|
+
a2a: (0, a2a_await_delivery_1.createA2AAwaitOwnerDeliverer)(agentName),
|
|
165
|
+
telegram: (request) => sendTelegramAwaitAlert(agentName, request),
|
|
166
|
+
...deps.deliverers,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
149
169
|
}
|
|
150
170
|
const ASK_OWNER_MIN_CHOICES = 2;
|
|
151
171
|
const ASK_OWNER_MAX_CHOICES = 4;
|
|
@@ -462,7 +482,9 @@ function fulfillAwaitObligation(agentRoot, awaitFile) {
|
|
|
462
482
|
/** The package-managed daily sweep must stay a byte-exact pending file in awaiting/, so it can only ever be resolved 'no'. */
|
|
463
483
|
exports.HOUSE_CARE_AWAIT = "house-care-sweep";
|
|
464
484
|
const isHouseCareAwait = (name) => String(name ?? "").replace(/\.md$/u, "") === exports.HOUSE_CARE_AWAIT;
|
|
465
|
-
async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName, ask) {
|
|
485
|
+
async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName, ask, message) {
|
|
486
|
+
// The words for whoever asked; without them they get the condition and observation, which read as unsourced notes.
|
|
487
|
+
const content = typeof message === "string" && message.trim() ? message.trim() : undefined;
|
|
466
488
|
const nameError = validateName(name);
|
|
467
489
|
if (nameError)
|
|
468
490
|
return JSON.stringify({ error: nameError });
|
|
@@ -500,7 +522,7 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
500
522
|
let alert = null;
|
|
501
523
|
if (existing.request_id && existing.filed_from !== "external-event") {
|
|
502
524
|
try {
|
|
503
|
-
alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: existing, reason: "resolved", observation: observation.trim(), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
|
|
525
|
+
alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: existing, reason: "resolved", observation: observation.trim(), content: (0, await_runtime_state_1.pinAwaitAlertContent)(agentRoot, name, content ?? (0, await_alert_1.buildAlertContent)(existing, "resolved", observation.trim())), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
|
|
504
526
|
}
|
|
505
527
|
catch (error) {
|
|
506
528
|
(0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.await_alert_error", message: "await alert delivery threw", meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) } });
|
|
@@ -530,6 +552,8 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
530
552
|
if (!archive.ok)
|
|
531
553
|
return JSON.stringify({ error: archive.error });
|
|
532
554
|
fulfillAwaitObligation(agentRoot, archive.file);
|
|
555
|
+
if (existing.request_id)
|
|
556
|
+
(0, await_runtime_state_1.clearAwaitAlertContent)(agentRoot, name);
|
|
533
557
|
(0, runtime_1.emitNervesEvent)({
|
|
534
558
|
component: "repertoire",
|
|
535
559
|
event: "repertoire.await_resolved",
|
|
@@ -538,7 +562,7 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
538
562
|
});
|
|
539
563
|
if (!existing.request_id) {
|
|
540
564
|
try {
|
|
541
|
-
alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: archive.file, reason: "resolved", observation: observation.trim(), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
|
|
565
|
+
alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: archive.file, reason: "resolved", observation: observation.trim(), content, agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
|
|
542
566
|
}
|
|
543
567
|
catch (error) {
|
|
544
568
|
(0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.await_alert_error", message: "await alert delivery threw", meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) } });
|
|
@@ -783,6 +807,7 @@ exports.awaitingToolDefinitions = [
|
|
|
783
807
|
name: { type: "string", description: "Await name (filename stem)." },
|
|
784
808
|
verdict: { type: "string", enum: ["yes", "no", "ask_owner"], description: "Exactly 'yes' if the condition is met. Exactly 'no' for anything else, including still pending, not yet, or unable to tell: send 'no' with an observation and polling continues. 'ask_owner' only when the condition cannot be met without the owner deciding; it needs question and choices." },
|
|
785
809
|
observation: { type: "string", description: "One-line summary of what I saw this tick." },
|
|
810
|
+
message: { type: "string", description: "Only for 'yes': what I tell whoever asked, written to them in plain words and sent as is. Say what I just checked and what it means for them." },
|
|
786
811
|
question: { type: "string", description: "Only for ask_owner: the decision I need from my owner, in plain words." },
|
|
787
812
|
choices: { type: "array", items: { type: "string" }, minItems: 2, maxItems: 4, description: "Only for ask_owner: 2 to 4 short options the owner can pick from." },
|
|
788
813
|
},
|
|
@@ -825,7 +850,7 @@ exports.awaitingToolDefinitions = [
|
|
|
825
850
|
if (!binding?.active)
|
|
826
851
|
return JSON.stringify({ error: "resolve_await is limited to the current relationship request or bound external event" });
|
|
827
852
|
}
|
|
828
|
-
return resolveAwaitTool(a.name, a.verdict, a.observation, agentRoot, agentName, { question: a.question, choices: a.choices });
|
|
853
|
+
return resolveAwaitTool(a.name, a.verdict, a.observation, agentRoot, agentName, { question: a.question, choices: a.choices }, a.message);
|
|
829
854
|
},
|
|
830
855
|
riskProfile: {
|
|
831
856
|
mutates: ["durable_state_write", "external_side_effect"],
|
|
@@ -59,7 +59,7 @@ function buildAwaitTurnMessage(options) {
|
|
|
59
59
|
lines.push(`last checkpoint: ${options.checkpoint}`);
|
|
60
60
|
}
|
|
61
61
|
lines.push("");
|
|
62
|
-
lines.push("look around and decide. if the condition is met, call resolve_await with verdict='yes'
|
|
62
|
+
lines.push("look around and decide. if the condition is met, call resolve_await with verdict='yes', a one-line observation, and a message: what i tell whoever asked, in plain words to them, saying what i just checked. otherwise call resolve_await with verdict='no' and a one-line observation of what i saw this tick.");
|
|
63
63
|
lines.push("verdicts: 'yes' (condition met), 'no' (not yet or unsure), 'ask_owner' (only when the condition cannot be met without my owner deciding, e.g. a confirmed dead download, or a stall the tool marks owner_decision_needed; give a plain question and 2-4 choices).");
|
|
64
64
|
return lines.join("\n");
|
|
65
65
|
}
|
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.962",
|
|
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.962",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|