@ouro.bot/cli 0.1.0-alpha.755 → 0.1.0-alpha.757
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 +13 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/mind/session-transaction.js +58 -22
- package/dist/repertoire/tools-continuity.js +1 -0
- package/dist/repertoire/tools-steward-policy.js +5 -3
- package/dist/senses/private-runtime-worker.js +6 -0
- package/dist/senses/private-runtime.js +33 -1
- package/dist/senses/sanctuary-runtime.js +9 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
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.757",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Allow authenticated steward policy writes to use the current version when the agent omits an explicit concurrency token.",
|
|
8
|
+
"Keep private external-event turns bound to their exact leased batch, with prompt-safe lease context and deterministic disposition completeness."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.756",
|
|
13
|
+
"changes": [
|
|
14
|
+
"Recover private-runtime turns safely across container restarts and restore Sanctuary steward-policy context."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.755",
|
|
6
19
|
"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.757</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -51,6 +51,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
51
51
|
const path = __importStar(require("node:path"));
|
|
52
52
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
53
53
|
const runtime_1 = require("../nerves/runtime");
|
|
54
|
+
const habit_lifecycle_1 = require("../heart/habits/habit-lifecycle");
|
|
54
55
|
class SessionTurnBusyError extends Error {
|
|
55
56
|
retryable = true;
|
|
56
57
|
constructor(message = "session turn is busy") {
|
|
@@ -101,34 +102,71 @@ function openLeaseDatabase(lockPath, busyTimeoutMs = 0) {
|
|
|
101
102
|
owner_token TEXT NOT NULL
|
|
102
103
|
)
|
|
103
104
|
`);
|
|
105
|
+
const columns = new Set(database.prepare(`PRAGMA table_info(session_turn_lease)`).all().map((column) => column.name));
|
|
106
|
+
if (!columns.has("boot_identity"))
|
|
107
|
+
database.exec(`ALTER TABLE session_turn_lease ADD COLUMN boot_identity TEXT`);
|
|
108
|
+
if (!columns.has("process_started_at"))
|
|
109
|
+
database.exec(`ALTER TABLE session_turn_lease ADD COLUMN process_started_at TEXT`);
|
|
104
110
|
return database;
|
|
105
111
|
}
|
|
106
|
-
function
|
|
112
|
+
function acquisitionRecord(options) {
|
|
113
|
+
const pid = options.pid ?? process.pid;
|
|
114
|
+
const bootIdentity = (options.getBootIdentity ?? habit_lifecycle_1.probeHabitBootIdentity)();
|
|
115
|
+
const processStartedAt = (options.getProcessStartedAt ?? habit_lifecycle_1.probeHabitProcessStartedAt)(pid);
|
|
116
|
+
if (!bootIdentity || !processStartedAt)
|
|
117
|
+
throw new SessionTransactionError("current process identity is unavailable");
|
|
118
|
+
return {
|
|
119
|
+
pid,
|
|
120
|
+
ownerId: options.ownerId ?? (0, node_crypto_1.randomUUID)(),
|
|
121
|
+
ownerToken: options.ownerToken ?? (0, node_crypto_1.randomUUID)(),
|
|
122
|
+
bootIdentity,
|
|
123
|
+
processStartedAt,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function claimLeaseRecord(lockPath, record, isProcessAlive, getProcessStartedAt) {
|
|
107
127
|
const database = openLeaseDatabase(lockPath);
|
|
108
128
|
try {
|
|
109
129
|
return database.transaction(() => {
|
|
110
130
|
const current = database.prepare(`
|
|
111
|
-
SELECT pid, owner_id, owner_token FROM session_turn_lease WHERE singleton = 1
|
|
131
|
+
SELECT pid, owner_id, owner_token, boot_identity, process_started_at FROM session_turn_lease WHERE singleton = 1
|
|
112
132
|
`).get();
|
|
113
133
|
if (!current) {
|
|
114
134
|
database.prepare(`
|
|
115
|
-
INSERT INTO session_turn_lease (singleton, pid, owner_id, owner_token) VALUES (1, ?, ?, ?)
|
|
116
|
-
`).run(record.pid, record.ownerId, record.ownerToken);
|
|
135
|
+
INSERT INTO session_turn_lease (singleton, pid, owner_id, owner_token, boot_identity, process_started_at) VALUES (1, ?, ?, ?, ?, ?)
|
|
136
|
+
`).run(record.pid, record.ownerId, record.ownerToken, record.bootIdentity, record.processStartedAt);
|
|
117
137
|
return { acquired: true, stale: null };
|
|
118
138
|
}
|
|
119
139
|
if (!Number.isSafeInteger(current.pid) || current.pid <= 0
|
|
120
140
|
|| typeof current.owner_id !== "string" || current.owner_id.length === 0
|
|
121
|
-
|| typeof current.owner_token !== "string" || current.owner_token.length === 0
|
|
141
|
+
|| typeof current.owner_token !== "string" || current.owner_token.length === 0
|
|
142
|
+
|| !((current.boot_identity === null && current.process_started_at === null)
|
|
143
|
+
|| (typeof current.boot_identity === "string" && current.boot_identity.length > 0
|
|
144
|
+
&& typeof current.process_started_at === "string" && current.process_started_at.length > 0))) {
|
|
122
145
|
throw new SessionTransactionError("invalid session lease record");
|
|
123
146
|
}
|
|
124
|
-
const observed = {
|
|
125
|
-
|
|
147
|
+
const observed = {
|
|
148
|
+
pid: current.pid,
|
|
149
|
+
ownerId: current.owner_id,
|
|
150
|
+
ownerToken: current.owner_token,
|
|
151
|
+
bootIdentity: current.boot_identity,
|
|
152
|
+
processStartedAt: current.process_started_at,
|
|
153
|
+
};
|
|
154
|
+
const differentBoot = observed.bootIdentity !== null && observed.bootIdentity !== record.bootIdentity;
|
|
155
|
+
let differentProcess = false;
|
|
156
|
+
if (observed.bootIdentity !== null && !differentBoot) {
|
|
157
|
+
const liveStartedAt = getProcessStartedAt(observed.pid);
|
|
158
|
+
differentProcess = liveStartedAt !== null && liveStartedAt !== observed.processStartedAt;
|
|
159
|
+
if (liveStartedAt === observed.processStartedAt)
|
|
160
|
+
return { acquired: false, stale: null };
|
|
161
|
+
}
|
|
162
|
+
if (!differentBoot && !differentProcess && isProcessAlive(observed.pid))
|
|
126
163
|
return { acquired: false, stale: null };
|
|
127
|
-
database.prepare(`
|
|
128
|
-
UPDATE session_turn_lease SET pid = ?, owner_id = ?, owner_token = ?
|
|
164
|
+
const changed = database.prepare(`
|
|
165
|
+
UPDATE session_turn_lease SET pid = ?, owner_id = ?, owner_token = ?, boot_identity = ?, process_started_at = ?
|
|
129
166
|
WHERE singleton = 1 AND pid = ? AND owner_id = ? AND owner_token = ?
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
AND boot_identity IS ? AND process_started_at IS ?
|
|
168
|
+
`).run(record.pid, record.ownerId, record.ownerToken, record.bootIdentity, record.processStartedAt, observed.pid, observed.ownerId, observed.ownerToken, observed.bootIdentity, observed.processStartedAt).changes;
|
|
169
|
+
return changed === 1 ? { acquired: true, stale: observed } : { acquired: false, stale: null };
|
|
132
170
|
})();
|
|
133
171
|
}
|
|
134
172
|
finally {
|
|
@@ -140,7 +178,8 @@ function releaseLeaseRecord(lockPath, record) {
|
|
|
140
178
|
try {
|
|
141
179
|
return database.prepare(`
|
|
142
180
|
DELETE FROM session_turn_lease WHERE singleton = 1 AND pid = ? AND owner_id = ? AND owner_token = ?
|
|
143
|
-
|
|
181
|
+
AND boot_identity IS ? AND process_started_at IS ?
|
|
182
|
+
`).run(record.pid, record.ownerId, record.ownerToken, record.bootIdentity, record.processStartedAt).changes === 1;
|
|
144
183
|
}
|
|
145
184
|
finally {
|
|
146
185
|
database.close();
|
|
@@ -180,9 +219,8 @@ async function acquireSessionTurnLease(sessionPath, options = {}) {
|
|
|
180
219
|
return makeLease(existing);
|
|
181
220
|
}
|
|
182
221
|
}
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const pid = options.pid ?? process.pid;
|
|
222
|
+
const record = acquisitionRecord(options);
|
|
223
|
+
const { ownerId } = record;
|
|
186
224
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
187
225
|
const pollIntervalMs = options.pollIntervalMs ?? 10;
|
|
188
226
|
const isProcessAlive = options.isProcessAlive ?? processAlive;
|
|
@@ -192,14 +230,14 @@ async function acquireSessionTurnLease(sessionPath, options = {}) {
|
|
|
192
230
|
for (;;) {
|
|
193
231
|
let claim = null;
|
|
194
232
|
try {
|
|
195
|
-
claim = claimLeaseRecord(lockPath,
|
|
233
|
+
claim = claimLeaseRecord(lockPath, record, isProcessAlive, options.getProcessStartedAt ?? habit_lifecycle_1.probeHabitProcessStartedAt);
|
|
196
234
|
}
|
|
197
235
|
catch (error) {
|
|
198
236
|
if (!isSqliteBusy(error))
|
|
199
237
|
throw error;
|
|
200
238
|
}
|
|
201
239
|
if (claim?.acquired) {
|
|
202
|
-
const held = { sessionPath: canonical, lockPath,
|
|
240
|
+
const held = { sessionPath: canonical, lockPath, ...record, depth: 1, released: false };
|
|
203
241
|
heldLeases.set(canonical, held);
|
|
204
242
|
(0, runtime_1.emitNervesEvent)({
|
|
205
243
|
component: "mind",
|
|
@@ -246,15 +284,13 @@ function withImmediateSessionTurnLease(sessionPath, work, options = {}) {
|
|
|
246
284
|
const contextual = currentSessionTurnLease(canonical);
|
|
247
285
|
if (contextual)
|
|
248
286
|
return work(contextual);
|
|
249
|
-
const
|
|
250
|
-
const ownerToken = options.ownerToken ?? (0, node_crypto_1.randomUUID)();
|
|
251
|
-
const pid = options.pid ?? process.pid;
|
|
287
|
+
const record = acquisitionRecord(options);
|
|
252
288
|
const lockPath = `${canonical}.turn.lock`;
|
|
253
289
|
fs.mkdirSync(path.dirname(canonical), { recursive: true });
|
|
254
290
|
const tryAcquire = () => {
|
|
255
291
|
let claim;
|
|
256
292
|
try {
|
|
257
|
-
claim = claimLeaseRecord(lockPath,
|
|
293
|
+
claim = claimLeaseRecord(lockPath, record, options.isProcessAlive ?? processAlive, options.getProcessStartedAt ?? habit_lifecycle_1.probeHabitProcessStartedAt);
|
|
258
294
|
}
|
|
259
295
|
catch (error) {
|
|
260
296
|
if (isSqliteBusy(error))
|
|
@@ -262,7 +298,7 @@ function withImmediateSessionTurnLease(sessionPath, work, options = {}) {
|
|
|
262
298
|
throw error;
|
|
263
299
|
}
|
|
264
300
|
if (claim.acquired) {
|
|
265
|
-
const held = { sessionPath: canonical, lockPath,
|
|
301
|
+
const held = { sessionPath: canonical, lockPath, ...record, depth: 1, released: false };
|
|
266
302
|
heldLeases.set(canonical, held);
|
|
267
303
|
if (claim.stale) {
|
|
268
304
|
options.onStaleLease?.(claim.stale);
|
|
@@ -204,6 +204,7 @@ exports.continuityToolDefinitions = [
|
|
|
204
204
|
verificationRefs,
|
|
205
205
|
},
|
|
206
206
|
});
|
|
207
|
+
ctx?.externalEventAuthority?.recordCommittedDisposition?.(turnEvent);
|
|
207
208
|
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.external_event_disposition", message: "external event disposition recorded", meta: { agentName, eventId: record.eventId, generation: record.generation, classification, decision } });
|
|
208
209
|
return JSON.stringify(handled, null, 2);
|
|
209
210
|
};
|
|
@@ -16,12 +16,12 @@ exports.stewardPolicyToolDefinition = {
|
|
|
16
16
|
type: "function",
|
|
17
17
|
function: {
|
|
18
18
|
name: "steward_policy_manage",
|
|
19
|
-
description: "Read or update the household steward's typed desired-state and routine-action policy. Updates require the current authenticated family request
|
|
19
|
+
description: "Read or update the household steward's typed desired-state and routine-action policy. Updates require the current authenticated family request. expectedVersion is optional; omit it to apply against the current policy version, or supply a version from read for explicit stale-write detection.",
|
|
20
20
|
parameters: {
|
|
21
21
|
type: "object",
|
|
22
22
|
properties: {
|
|
23
23
|
action: { type: "string", enum: ["read", "set_desired_state", "grant_routine_action"] },
|
|
24
|
-
expectedVersion: { type: "integer", minimum: 0 },
|
|
24
|
+
expectedVersion: { type: "integer", minimum: 0, description: "Optional version returned by read. When omitted, the runtime uses the current policy version." },
|
|
25
25
|
key: { type: "string" },
|
|
26
26
|
value: { type: "string" },
|
|
27
27
|
provenance: { type: "string", enum: ["stated", "observed", "default", "installed_explicit_policy"] },
|
|
@@ -51,7 +51,9 @@ exports.stewardPolicyToolDefinition = {
|
|
|
51
51
|
const actor = ctx.relationshipAuthorization?.actor;
|
|
52
52
|
if (!actor)
|
|
53
53
|
throw new Error("steward policy mutation requires authenticated relationship authority");
|
|
54
|
-
const expectedVersion =
|
|
54
|
+
const expectedVersion = args.expectedVersion === undefined
|
|
55
|
+
? (0, steward_policy_1.readStewardPolicy)(ctx.agentRoot).version
|
|
56
|
+
: Number(args.expectedVersion);
|
|
55
57
|
if (!Number.isInteger(expectedVersion) || expectedVersion < 0)
|
|
56
58
|
throw new Error("expectedVersion must be a nonnegative integer");
|
|
57
59
|
let mutation;
|
|
@@ -515,6 +515,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
515
515
|
const currentHabitName = nextHabitName;
|
|
516
516
|
const currentTrigger = nextTrigger ?? "overdue";
|
|
517
517
|
const currentNoSend = nextNoSend;
|
|
518
|
+
const currentExternalEvent = nextExternalEvent;
|
|
518
519
|
const currentExternalEventHeartbeat = nextExternalEventHeartbeat;
|
|
519
520
|
nextExternalEventHeartbeat = undefined;
|
|
520
521
|
const currentHabitRun = currentReason === "habit" && currentHabitName
|
|
@@ -655,6 +656,11 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
655
656
|
// would cause hasPendingWork() to be true here, producing a
|
|
656
657
|
// self-sustaining "instinct" loop with no external input. Cap it.
|
|
657
658
|
if (hasPendingWork(currentHabitRun?.paths.pendingDir)) {
|
|
659
|
+
if (currentExternalEvent && (turnErrors.length > 0
|
|
660
|
+
|| (turnResult && typeof turnResult === "object" && "turnOutcome" in turnResult && turnResult.turnOutcome === "errored"))) {
|
|
661
|
+
finalizeCurrentHabitRun();
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
658
664
|
clearHeartbeatRestShield();
|
|
659
665
|
if (consecutiveInstinctTurns >= exports.MAX_CONSECUTIVE_INSTINCT_TURNS) {
|
|
660
666
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -835,6 +835,24 @@ function buildHeldReturnWakeMessage() {
|
|
|
835
835
|
"Return each listed item with surface(delegationId=...) before resting or settling.",
|
|
836
836
|
].join("\n");
|
|
837
837
|
}
|
|
838
|
+
function buildExternalEventLeaseMessage(event) {
|
|
839
|
+
const members = [event, ...(event.relatedEvents ?? [])];
|
|
840
|
+
return [
|
|
841
|
+
"[current external-event disposition contract]",
|
|
842
|
+
"This exact lease frame is authoritative for this turn. Ignore older receipts, failed tool arguments, held-work summaries, and checkpoints when choosing disposition arguments.",
|
|
843
|
+
"Investigate the current observation, then call external_event_disposition once for every listed lease before settling.",
|
|
844
|
+
...members.flatMap((member, index) => [
|
|
845
|
+
"",
|
|
846
|
+
`lease ${index + 1}:`,
|
|
847
|
+
`recordPath: ${JSON.stringify(member.recordPath)}`,
|
|
848
|
+
`expectedGeneration: ${member.generation}`,
|
|
849
|
+
`classifiedRevision: ${JSON.stringify(member.observationRevision)}`,
|
|
850
|
+
]),
|
|
851
|
+
].join("\n");
|
|
852
|
+
}
|
|
853
|
+
function externalEventLeaseKey(event) {
|
|
854
|
+
return JSON.stringify([event.recordPath, event.generation, event.observationRevision, event.claimOwner]);
|
|
855
|
+
}
|
|
838
856
|
function buildAlsoDueLine(agentRoot, currentHabitName, now) {
|
|
839
857
|
const habitsDir = path.join(agentRoot, "habits");
|
|
840
858
|
let files;
|
|
@@ -1126,7 +1144,10 @@ async function runPrivateRuntimeTurn(options) {
|
|
|
1126
1144
|
userContent = buildInstinctUserMessage(instincts, reason, state);
|
|
1127
1145
|
}
|
|
1128
1146
|
}
|
|
1129
|
-
if (
|
|
1147
|
+
if (options?.externalEvent) {
|
|
1148
|
+
userContent = buildExternalEventLeaseMessage(options.externalEvent);
|
|
1149
|
+
}
|
|
1150
|
+
else if (shouldUseHeldReturnWake) {
|
|
1130
1151
|
userContent = buildHeldReturnWakeMessage();
|
|
1131
1152
|
}
|
|
1132
1153
|
// ── Session loader: wraps existing session logic ──────────────────
|
|
@@ -1197,6 +1218,7 @@ async function runPrivateRuntimeTurn(options) {
|
|
|
1197
1218
|
};
|
|
1198
1219
|
})()
|
|
1199
1220
|
: undefined;
|
|
1221
|
+
const committedExternalEventLeases = new Set();
|
|
1200
1222
|
const externalEventRelationship = options?.externalEvent
|
|
1201
1223
|
? await (async () => {
|
|
1202
1224
|
const agentRoot = (0, identity_1.getAgentRoot)(agentName);
|
|
@@ -1231,6 +1253,9 @@ async function runPrivateRuntimeTurn(options) {
|
|
|
1231
1253
|
const decision = initial.authorizeTool("external_event_disposition");
|
|
1232
1254
|
return decision.allowed ? { allowed: true, reason: "relationship-authorized" } : { allowed: false, reason: decision.reason };
|
|
1233
1255
|
},
|
|
1256
|
+
recordCommittedDisposition: (event) => {
|
|
1257
|
+
committedExternalEventLeases.add(externalEventLeaseKey(event));
|
|
1258
|
+
},
|
|
1234
1259
|
},
|
|
1235
1260
|
externalEventEffects: {
|
|
1236
1261
|
deliverOwnerDecision: async (input) => {
|
|
@@ -1403,6 +1428,13 @@ async function runPrivateRuntimeTurn(options) {
|
|
|
1403
1428
|
...(effectiveHabitSession ? { habitSession: effectiveHabitSession } : {}),
|
|
1404
1429
|
},
|
|
1405
1430
|
});
|
|
1431
|
+
if (options?.externalEvent && ["settled", "observed", "rested"].includes(String(result.turnOutcome))) {
|
|
1432
|
+
const incomplete = [options.externalEvent, ...(options.externalEvent.relatedEvents ?? [])]
|
|
1433
|
+
.filter((event) => !committedExternalEventLeases.has(externalEventLeaseKey(event)));
|
|
1434
|
+
if (incomplete.length > 0) {
|
|
1435
|
+
throw new Error(`External-event turn did not commit dispositions for every exact lease (${incomplete.length} incomplete)`);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1406
1438
|
if (externalRunBase) {
|
|
1407
1439
|
const lifecycle = result.turnOutcome === "settled" || result.turnOutcome === "observed" || result.turnOutcome === "rested"
|
|
1408
1440
|
? "completed"
|
|
@@ -120,10 +120,10 @@ function persistAttempt(filePath, attempt) {
|
|
|
120
120
|
fs.writeFileSync(temporary, `${JSON.stringify(attempt)}\n`, { mode: 0o600 });
|
|
121
121
|
fs.renameSync(temporary, filePath);
|
|
122
122
|
}
|
|
123
|
-
async function appendAcceptanceAttempt(
|
|
123
|
+
async function appendAcceptanceAttempt(agentRoot, attempt) {
|
|
124
124
|
if (!attempt.scenarioHandleDigest)
|
|
125
125
|
return;
|
|
126
|
-
const filePath = path.join(
|
|
126
|
+
const filePath = path.join(agentRoot, "state", "acceptance", "restart-attempts.ndjson");
|
|
127
127
|
const previous = acceptanceLedgerTails.get(filePath) ?? Promise.resolve();
|
|
128
128
|
const current = previous.catch(() => undefined).then(() => {
|
|
129
129
|
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
@@ -187,6 +187,7 @@ function createSanctuaryToolContext(agentName) {
|
|
|
187
187
|
message: "creating typed Sanctuary tool context",
|
|
188
188
|
meta: { agentName },
|
|
189
189
|
});
|
|
190
|
+
const agentRoot = (0, identity_1.getAgentRoot)(agentName);
|
|
190
191
|
const initial = machineConfig(agentName);
|
|
191
192
|
const endpoint = required(initial, "unraidGraphqlUrl");
|
|
192
193
|
const readClient = new unraid_client_1.UnraidClient({ endpoint, apiKey: required(initial, "unraidReadApiKey") });
|
|
@@ -216,15 +217,16 @@ function createSanctuaryToolContext(agentName) {
|
|
|
216
217
|
listContainers: reads.listContainers,
|
|
217
218
|
loadWriteApiKey: async () => required(machineConfig(agentName), "unraidWriteApiKey"),
|
|
218
219
|
persistAttempt: async (attempt) => {
|
|
219
|
-
persistAttempt(path.join(
|
|
220
|
-
await appendAcceptanceAttempt(
|
|
220
|
+
persistAttempt(path.join(agentRoot, "state", "approvals", "unraid-restart-attempt.json"), attempt);
|
|
221
|
+
await appendAcceptanceAttempt(agentRoot, attempt);
|
|
221
222
|
},
|
|
222
223
|
acceptanceScenarioHandleDigest: () => (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(agentName)?.scenarioHandleDigest,
|
|
223
224
|
acceptanceApproval: sanctuary_acceptance_marker_1.readSanctuaryAcceptanceApproval,
|
|
224
|
-
reserveRoutineAction: (input) => (0, steward_policy_1.consumeRoutineActionGrant)(
|
|
225
|
-
transitionRoutineAction: (input) => (0, steward_policy_1.transitionRoutineActionReceipt)(
|
|
225
|
+
reserveRoutineAction: (input) => (0, steward_policy_1.consumeRoutineActionGrant)(agentRoot, input),
|
|
226
|
+
transitionRoutineAction: (input) => (0, steward_policy_1.transitionRoutineActionReceipt)(agentRoot, input),
|
|
226
227
|
});
|
|
227
228
|
return {
|
|
229
|
+
agentRoot,
|
|
228
230
|
sanctuary: {
|
|
229
231
|
listContainers: acceptanceRead("unraid_list_containers", reads.listContainers),
|
|
230
232
|
getContainerLogs: acceptanceRead("unraid_get_container_logs", reads.getContainerLogs),
|
|
@@ -255,7 +257,7 @@ function createSanctuaryToolContext(agentName) {
|
|
|
255
257
|
collectToolResult(result);
|
|
256
258
|
return result;
|
|
257
259
|
},
|
|
258
|
-
recoverRoutineActions: () => (0, steward_policy_1.recoverRoutineActionReceipts)(
|
|
260
|
+
recoverRoutineActions: () => (0, steward_policy_1.recoverRoutineActionReceipts)(agentRoot, {
|
|
259
261
|
observeTarget: async (target) => {
|
|
260
262
|
const observed = await reads.listContainers();
|
|
261
263
|
if (!observed.ok)
|
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.757",
|
|
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.757",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|