@ouro.bot/cli 0.1.0-alpha.845 → 0.1.0-alpha.847
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 +12 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/sync.js +56 -0
- package/dist/senses/sanctuary-install-state-contract.js +43 -0
- package/dist/senses/telegram.js +4 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
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.847",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Bundle sync: post-turn push now removes an empty git index.lock older than 10 minutes (left by a git process that died mid-write) before committing, and any other failed local commit is recorded in state/pending-sync.json so the start-of-turn packet surfaces it. One stale lock had silently blocked every bundle commit for three months while untracked files piled up; a lock that cannot be removed is reported as a warning."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.846",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Sanctuary: a natural question about the Butler's version ('what version are you on?') now requires a fresh install-state read, and any version it states must match that read. After an upgrade it had answered 'Just checked the live state: 0.1.0-alpha.841' from earlier conversation while running 845, with no tool call."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.845",
|
|
6
18
|
"changes": [
|
|
@@ -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.847</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/sync.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.STALE_INDEX_LOCK_MS = void 0;
|
|
36
37
|
exports.preTurnPull = preTurnPull;
|
|
37
38
|
exports.postTurnPush = postTurnPush;
|
|
38
39
|
exports.preTurnPullAsync = preTurnPullAsync;
|
|
@@ -87,6 +88,52 @@ function ensureGitRepo(agentRoot) {
|
|
|
87
88
|
const error = `bundle is not a git repo; run \`git init\` inside ${agentRoot} to enable sync (or disable sync in agent.json)`;
|
|
88
89
|
return { ok: false, error };
|
|
89
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Git holds `.git/index.lock` for milliseconds to seconds, and every git call
|
|
93
|
+
* in this module has a timeout of 30s or less. A lock older than this was left
|
|
94
|
+
* behind by a git process that died mid-write; while it exists every `git add`
|
|
95
|
+
* fails, which silently stopped one bundle's sync for three months.
|
|
96
|
+
*/
|
|
97
|
+
exports.STALE_INDEX_LOCK_MS = 10 * 60 * 1000;
|
|
98
|
+
// Only an empty lock is removed: git writes the new index into the lock right
|
|
99
|
+
// after creating it, while a live `git commit` waiting in an editor holds a
|
|
100
|
+
// full, unchanging lock that can be older than the threshold. When `.git` is a
|
|
101
|
+
// gitdir file (worktree), statSync fails and the lock is left alone.
|
|
102
|
+
function clearStaleIndexLock(agentRoot) {
|
|
103
|
+
const lockPath = path.join(agentRoot, ".git", "index.lock");
|
|
104
|
+
let ageMs;
|
|
105
|
+
try {
|
|
106
|
+
const stat = fs.statSync(lockPath);
|
|
107
|
+
if (stat.size !== 0)
|
|
108
|
+
return;
|
|
109
|
+
ageMs = Date.now() - stat.mtimeMs;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (ageMs < exports.STALE_INDEX_LOCK_MS)
|
|
115
|
+
return;
|
|
116
|
+
try {
|
|
117
|
+
fs.rmSync(lockPath, { force: true });
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
(0, runtime_1.emitNervesEvent)({
|
|
121
|
+
level: "warn",
|
|
122
|
+
component: "heart",
|
|
123
|
+
event: "heart.sync_stale_lock_clear_failed",
|
|
124
|
+
message: "post-turn push: could not remove stale git index.lock",
|
|
125
|
+
meta: { agentRoot, lockPath, error: err instanceof Error ? err.message : /* v8 ignore next -- defensive non-Error catch @preserve */ String(err) },
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
(0, runtime_1.emitNervesEvent)({
|
|
130
|
+
level: "warn",
|
|
131
|
+
component: "heart",
|
|
132
|
+
event: "heart.sync_stale_lock_cleared",
|
|
133
|
+
message: "post-turn push: removed stale git index.lock",
|
|
134
|
+
meta: { agentRoot, lockPath, ageMs },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
90
137
|
/**
|
|
91
138
|
* Pre-turn pull: sync the agent bundle from remote before assembling the start-of-turn packet.
|
|
92
139
|
*
|
|
@@ -224,6 +271,7 @@ function postTurnPush(agentRoot, config) {
|
|
|
224
271
|
return { ok: true };
|
|
225
272
|
}
|
|
226
273
|
const changedCount = statusOutput.split("\n").length;
|
|
274
|
+
clearStaleIndexLock(agentRoot);
|
|
227
275
|
try {
|
|
228
276
|
(0, child_process_1.execFileSync)("git", ["add", "-A"], {
|
|
229
277
|
cwd: agentRoot,
|
|
@@ -322,6 +370,14 @@ function postTurnPush(agentRoot, config) {
|
|
|
322
370
|
}
|
|
323
371
|
catch (err) {
|
|
324
372
|
const error = err instanceof Error ? err.message : String(err);
|
|
373
|
+
// Record it so the start-of-turn packet surfaces the failure; otherwise a
|
|
374
|
+
// failing local commit is visible only in nerves logs.
|
|
375
|
+
try {
|
|
376
|
+
writePendingSync(agentRoot, error, "unknown", []);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
// Best effort: the nerves event below still records the failure.
|
|
380
|
+
}
|
|
325
381
|
(0, runtime_1.emitNervesEvent)({
|
|
326
382
|
component: "heart",
|
|
327
383
|
event: "heart.sync_push_error",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sanctuaryInstallStateRequiredToolCalls = sanctuaryInstallStateRequiredToolCalls;
|
|
4
|
+
exports.sanctuaryVersionQuestionRequiredToolCalls = sanctuaryVersionQuestionRequiredToolCalls;
|
|
4
5
|
const runtime_1 = require("../nerves/runtime");
|
|
5
6
|
const TOOL_NAME = "sanctuary_get_install_state";
|
|
6
7
|
const REQUEST = /\b(?:use|run|call)\s+sanctuary_get_install_state\b/iu;
|
|
@@ -124,3 +125,45 @@ function sanctuaryInstallStateRequiredToolCalls(request, advertisedToolNames) {
|
|
|
124
125
|
},
|
|
125
126
|
};
|
|
126
127
|
}
|
|
128
|
+
const VERSION_QUESTION = /\b(?:what|which)(?:'s|\s+is)?\s+(?:your\s+|the\s+)?(?:current\s+|live\s+|installed\s+|running\s+)?version\b|\bversion\s+(?:are|r)\s+(?:you|u)\b|\bwhat\s+(?:are\s+you|you're)\s+(?:on|running)\b|\bare\s+you\s+(?:on|running)\s+(?:the\s+)?(?:latest|newest|new|current)\b/iu;
|
|
129
|
+
const VERSION_CLAIM = /\bv?\d+\.\d+\.\d+(?:-[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?\b/gu;
|
|
130
|
+
/**
|
|
131
|
+
* A natural question about the Butler's own version must be answered from a fresh
|
|
132
|
+
* install-state read. After an upgrade the Butler once answered "Just checked the live
|
|
133
|
+
* state: 0.1.0-alpha.841" from earlier conversation while it was running 845, with no
|
|
134
|
+
* tool call at all. Unlike the explicit install-state contract, the wording stays free:
|
|
135
|
+
* only the version it states is checked against the read.
|
|
136
|
+
*/
|
|
137
|
+
function sanctuaryVersionQuestionRequiredToolCalls(request, advertisedToolNames) {
|
|
138
|
+
const normalizedRequest = request.normalize("NFKC").replace(/[‘’]/gu, "'");
|
|
139
|
+
if (!advertisedToolNames.includes(TOOL_NAME) || REQUEST.test(normalizedRequest) || !VERSION_QUESTION.test(normalizedRequest))
|
|
140
|
+
return undefined;
|
|
141
|
+
let current;
|
|
142
|
+
(0, runtime_1.emitNervesEvent)({
|
|
143
|
+
component: "senses",
|
|
144
|
+
event: "senses.sanctuary_version_question_required",
|
|
145
|
+
message: "a question about the running version requires a fresh install-state read",
|
|
146
|
+
meta: { requiredToolName: TOOL_NAME },
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
names: [TOOL_NAME],
|
|
150
|
+
retryMessage: "Call sanctuary_get_install_state with empty arguments now, then state the version from that fresh result, not from earlier conversation.",
|
|
151
|
+
requireSuccessfulResults: true,
|
|
152
|
+
validateRequiredToolResult: (name, result, args) => {
|
|
153
|
+
if (name !== TOOL_NAME || Object.keys(args).length !== 0)
|
|
154
|
+
return false;
|
|
155
|
+
current = parseInstallState(result);
|
|
156
|
+
return current !== undefined;
|
|
157
|
+
},
|
|
158
|
+
validateTerminalAnswer: (answer) => {
|
|
159
|
+
if (!current)
|
|
160
|
+
return "Call sanctuary_get_install_state and state the version from that fresh result.";
|
|
161
|
+
const known = new Set([current.runtimePackageVersion, current.packagedBundleVersion, ...(current.liveBundleVersion ? [current.liveBundleVersion] : [])].map((version) => version.replace(/^v/u, "")));
|
|
162
|
+
const claimed = [...answer.normalize("NFKC").matchAll(VERSION_CLAIM)].map((match) => match[0].replace(/^v/u, ""));
|
|
163
|
+
if (claimed.length === 0)
|
|
164
|
+
return `State the running version from the fresh result: ${current.runtimePackageVersion}.`;
|
|
165
|
+
const stale = claimed.filter((version) => !known.has(version));
|
|
166
|
+
return stale.length === 0 ? undefined : `The fresh install-state result says ${current.runtimePackageVersion}; do not state ${stale.join(", ")} as the current version.`;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
package/dist/senses/telegram.js
CHANGED
|
@@ -1234,9 +1234,12 @@ function createTelegramSenseApp(options) {
|
|
|
1234
1234
|
const installState = isSanctuaryOwner
|
|
1235
1235
|
? (0, sanctuary_install_state_contract_1.sanctuaryInstallStateRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
|
|
1236
1236
|
: undefined;
|
|
1237
|
+
const versionQuestion = isSanctuaryOwner
|
|
1238
|
+
? (0, sanctuary_install_state_contract_1.sanctuaryVersionQuestionRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
|
|
1239
|
+
: undefined;
|
|
1237
1240
|
if (input.fullVisibilityProgress && fullVisibility)
|
|
1238
1241
|
input.fullVisibilityProgress.fallback = fullVisibility.emptyResponseFallback;
|
|
1239
|
-
const contracts = [storageOptimization, fullVisibility, staleDockerCare, mediaCatalog, installState].filter(Boolean);
|
|
1242
|
+
const contracts = [storageOptimization, fullVisibility, staleDockerCare, mediaCatalog, installState, versionQuestion].filter(Boolean);
|
|
1240
1243
|
const ownedNames = contracts.map((contract) => new Set(contract.names));
|
|
1241
1244
|
const requiredToolCalls = contracts.length === 0 ? undefined : {
|
|
1242
1245
|
names: [...new Set(contracts.flatMap((contract) => [...contract.names]))],
|
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.847",
|
|
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.847",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|