@ouro.bot/cli 0.1.0-alpha.961 → 0.1.0-alpha.963
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/bitwarden-store.js +9 -11
- package/dist/repertoire/tools-awaiting.js +10 -4
- 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.963",
|
|
6
|
+
"changes": [
|
|
7
|
+
"The vault now syncs after unlock unless Ouro itself synced in the last minute. Before, `bw status` and `bw unlock` rewrote the profile's data file, which made a stale cache look fresh, so a running daemon could keep reading an old copy of a vault item (for example a machine config changed from another Bitwarden client) indefinitely."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.962",
|
|
12
|
+
"changes": [
|
|
13
|
+
"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.",
|
|
14
|
+
"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.",
|
|
15
|
+
"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."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.961",
|
|
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.963</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
|
+
}
|
|
@@ -1144,19 +1144,17 @@ class BitwardenCredentialStore {
|
|
|
1144
1144
|
}
|
|
1145
1145
|
return true;
|
|
1146
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* When Ouro last synced this profile. Only Ouro's own marker counts: `bw unlock` and `bw status`
|
|
1149
|
+
* rewrite data.json, so its mtime is always fresh right after unlock and a daemon would never sync.
|
|
1150
|
+
*/
|
|
1147
1151
|
latestLocalSyncTimestamp() {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
latest = latest === null ? mtimeMs : Math.max(latest, mtimeMs);
|
|
1154
|
-
}
|
|
1155
|
-
catch {
|
|
1156
|
-
// Missing freshness file is fine; use any other available timestamp.
|
|
1157
|
-
}
|
|
1152
|
+
try {
|
|
1153
|
+
return fs.statSync(path.join(this.appDataDir, BW_SYNC_MARKER_FILENAME)).mtimeMs;
|
|
1154
|
+
}
|
|
1155
|
+
catch {
|
|
1156
|
+
return null;
|
|
1158
1157
|
}
|
|
1159
|
-
return latest;
|
|
1160
1158
|
}
|
|
1161
1159
|
writeSyncMarker() {
|
|
1162
1160
|
if (!this.appDataDir)
|
|
@@ -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");
|
|
@@ -481,7 +482,9 @@ function fulfillAwaitObligation(agentRoot, awaitFile) {
|
|
|
481
482
|
/** The package-managed daily sweep must stay a byte-exact pending file in awaiting/, so it can only ever be resolved 'no'. */
|
|
482
483
|
exports.HOUSE_CARE_AWAIT = "house-care-sweep";
|
|
483
484
|
const isHouseCareAwait = (name) => String(name ?? "").replace(/\.md$/u, "") === exports.HOUSE_CARE_AWAIT;
|
|
484
|
-
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;
|
|
485
488
|
const nameError = validateName(name);
|
|
486
489
|
if (nameError)
|
|
487
490
|
return JSON.stringify({ error: nameError });
|
|
@@ -519,7 +522,7 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
519
522
|
let alert = null;
|
|
520
523
|
if (existing.request_id && existing.filed_from !== "external-event") {
|
|
521
524
|
try {
|
|
522
|
-
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) });
|
|
523
526
|
}
|
|
524
527
|
catch (error) {
|
|
525
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) } });
|
|
@@ -549,6 +552,8 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
549
552
|
if (!archive.ok)
|
|
550
553
|
return JSON.stringify({ error: archive.error });
|
|
551
554
|
fulfillAwaitObligation(agentRoot, archive.file);
|
|
555
|
+
if (existing.request_id)
|
|
556
|
+
(0, await_runtime_state_1.clearAwaitAlertContent)(agentRoot, name);
|
|
552
557
|
(0, runtime_1.emitNervesEvent)({
|
|
553
558
|
component: "repertoire",
|
|
554
559
|
event: "repertoire.await_resolved",
|
|
@@ -557,7 +562,7 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
557
562
|
});
|
|
558
563
|
if (!existing.request_id) {
|
|
559
564
|
try {
|
|
560
|
-
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) });
|
|
561
566
|
}
|
|
562
567
|
catch (error) {
|
|
563
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) } });
|
|
@@ -802,6 +807,7 @@ exports.awaitingToolDefinitions = [
|
|
|
802
807
|
name: { type: "string", description: "Await name (filename stem)." },
|
|
803
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." },
|
|
804
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." },
|
|
805
811
|
question: { type: "string", description: "Only for ask_owner: the decision I need from my owner, in plain words." },
|
|
806
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." },
|
|
807
813
|
},
|
|
@@ -844,7 +850,7 @@ exports.awaitingToolDefinitions = [
|
|
|
844
850
|
if (!binding?.active)
|
|
845
851
|
return JSON.stringify({ error: "resolve_await is limited to the current relationship request or bound external event" });
|
|
846
852
|
}
|
|
847
|
-
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);
|
|
848
854
|
},
|
|
849
855
|
riskProfile: {
|
|
850
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.963",
|
|
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.963",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|