@ouro.bot/cli 0.1.0-alpha.742 → 0.1.0-alpha.744
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 +32 -0
- package/deploy/unraid/Dockerfile +7 -2
- package/deploy/unraid/README.txt +704 -64
- package/deploy/unraid/migrate-sanctuary-bundle.mjs +21 -0
- package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
- package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
- package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
- package/deploy/unraid/ouro-events/install-usenet-guard.sh +434 -0
- package/deploy/unraid/ouro-events/usenet-health.sh +178 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +4 -3
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +5 -1
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +2 -2
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -1
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -1
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +4 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +6 -2
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +4 -2
- package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +19 -17
- package/deploy/unraid/sanctuary.xml +13 -10
- package/dist/arc/cares.js +171 -37
- package/dist/arc/obligations.js +38 -0
- package/dist/heart/awaiting/await-alert.js +4 -2
- package/dist/heart/awaiting/await-expiry.js +39 -4
- package/dist/heart/awaiting/await-parser.js +8 -0
- package/dist/heart/core.js +37 -7
- package/dist/heart/cross-chat-delivery.js +1 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +11 -7
- package/dist/heart/daemon/container-spec-auditor.js +38 -11
- package/dist/heart/daemon/daemon-entry.js +19 -5
- package/dist/heart/daemon/daemon.js +154 -22
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +126 -42
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +26 -29
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +42 -51
- package/dist/heart/daemon/sanctuary-bundle-migration.js +398 -0
- package/dist/heart/external-events/router.js +1025 -43
- package/dist/heart/session-events.js +23 -5
- package/dist/heart/steward-policy.js +302 -0
- package/dist/heart/tool-approval.js +3 -1
- package/dist/heart/turn-context.js +1 -0
- package/dist/mind/context.js +8 -3
- package/dist/mind/pending.js +25 -0
- package/dist/mind/prompt.js +49 -1
- package/dist/repertoire/guardrails.js +2 -1
- package/dist/repertoire/relationship-authorization.js +194 -0
- package/dist/repertoire/tools-awaiting.js +229 -26
- package/dist/repertoire/tools-base.js +15 -0
- package/dist/repertoire/tools-continuity.js +259 -4
- package/dist/repertoire/tools-notes.js +29 -3
- package/dist/repertoire/tools-session.js +219 -4
- package/dist/repertoire/tools-steward-policy.js +88 -0
- package/dist/repertoire/tools-telegram-contacts.js +37 -0
- package/dist/repertoire/tools-unraid.js +113 -6
- package/dist/repertoire/tools.js +104 -9
- package/dist/repertoire/unraid-restart.js +86 -4
- package/dist/senses/private-runtime-worker.js +36 -3
- package/dist/senses/private-runtime.js +310 -63
- package/dist/senses/sanctuary-health-acceptance-probe.js +8 -6
- package/dist/senses/sanctuary-health-runner.js +47 -103
- package/dist/senses/sanctuary-health.js +53 -105
- package/dist/senses/sanctuary-runtime.js +36 -2
- package/dist/senses/sanctuary-sab.js +80 -0
- package/dist/senses/shared-turn.js +64 -9
- package/dist/senses/telegram-admission.js +684 -0
- package/dist/senses/telegram-approval-runtime.js +46 -6
- package/dist/senses/telegram-client.js +212 -66
- package/dist/senses/telegram-effect-adapter.js +787 -0
- package/dist/senses/telegram.js +728 -48
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
|
@@ -50,6 +50,8 @@ exports.migrateToolNames = migrateToolNames;
|
|
|
50
50
|
exports.sanitizeProviderMessages = sanitizeProviderMessages;
|
|
51
51
|
exports.stampIngressTime = stampIngressTime;
|
|
52
52
|
exports.getIngressTime = getIngressTime;
|
|
53
|
+
exports.stampIngressRelations = stampIngressRelations;
|
|
54
|
+
exports.getIngressRelations = getIngressRelations;
|
|
53
55
|
exports.projectProviderMessages = projectProviderMessages;
|
|
54
56
|
exports.annotateMessageTimestamps = annotateMessageTimestamps;
|
|
55
57
|
exports.bestEventTimestamp = bestEventTimestamp;
|
|
@@ -860,6 +862,21 @@ function getIngressTime(msg) {
|
|
|
860
862
|
const value = msg._ingressAt;
|
|
861
863
|
return typeof value === "string" ? value : null;
|
|
862
864
|
}
|
|
865
|
+
function stampIngressRelations(msg, relations) {
|
|
866
|
+
;
|
|
867
|
+
msg._ingressRelations = structuredClone(relations);
|
|
868
|
+
}
|
|
869
|
+
function getIngressRelations(msg) {
|
|
870
|
+
const value = msg._ingressRelations;
|
|
871
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
872
|
+
return null;
|
|
873
|
+
const relations = value;
|
|
874
|
+
return {
|
|
875
|
+
replyToEventId: typeof relations.replyToEventId === "string" ? relations.replyToEventId : null,
|
|
876
|
+
threadRootEventId: typeof relations.threadRootEventId === "string" ? relations.threadRootEventId : null,
|
|
877
|
+
references: Array.isArray(relations.references) ? relations.references.filter((item) => typeof item === "string") : [],
|
|
878
|
+
};
|
|
879
|
+
}
|
|
863
880
|
function createEventTime(role, recordedAt, captureKind, ingressAt) {
|
|
864
881
|
if (captureKind === "migration") {
|
|
865
882
|
return {
|
|
@@ -890,7 +907,7 @@ function createEventTime(role, recordedAt, captureKind, ingressAt) {
|
|
|
890
907
|
recordedAtSource: "save",
|
|
891
908
|
};
|
|
892
909
|
}
|
|
893
|
-
function buildEventFromMessage(message, sequence, recordedAt, captureKind, sourceMessageIndex, legacyVersion, ingressAt) {
|
|
910
|
+
function buildEventFromMessage(message, sequence, recordedAt, captureKind, sourceMessageIndex, legacyVersion, ingressAt, ingressRelations) {
|
|
894
911
|
const normalized = normalizeMessage(message);
|
|
895
912
|
const role = normalized.role;
|
|
896
913
|
const cappedContent = truncateLargeEventContent(normalized.content, exports.EVENT_CONTENT_MAX_CHARS).content;
|
|
@@ -905,9 +922,9 @@ function buildEventFromMessage(message, sequence, recordedAt, captureKind, sourc
|
|
|
905
922
|
attachments: [],
|
|
906
923
|
time: createEventTime(role, recordedAt, captureKind, ingressAt),
|
|
907
924
|
relations: {
|
|
908
|
-
replyToEventId: null,
|
|
909
|
-
threadRootEventId: null,
|
|
910
|
-
references: [],
|
|
925
|
+
replyToEventId: ingressRelations?.replyToEventId ?? null,
|
|
926
|
+
threadRootEventId: ingressRelations?.threadRootEventId ?? null,
|
|
927
|
+
references: ingressRelations?.references ?? [],
|
|
911
928
|
toolCallId: role === "tool" ? normalized.toolCallId : null,
|
|
912
929
|
supersedesEventId: null,
|
|
913
930
|
redactsEventId: null,
|
|
@@ -1205,6 +1222,7 @@ function buildCanonicalSessionEnvelope(options) {
|
|
|
1205
1222
|
const existing = options.existing;
|
|
1206
1223
|
// Callers pass pre-sanitized messages + pre-captured ingress times.
|
|
1207
1224
|
const currentIngressTimes = options.currentIngressTimes ?? options.currentMessages.map(getIngressTime);
|
|
1225
|
+
const currentIngressRelations = options.currentIngressRelations ?? options.currentMessages.map(getIngressRelations);
|
|
1208
1226
|
const previousMessages = options.previousMessages;
|
|
1209
1227
|
const currentMessages = options.currentMessages;
|
|
1210
1228
|
const trimmedMessages = options.trimmedMessages;
|
|
@@ -1249,7 +1267,7 @@ function buildCanonicalSessionEnvelope(options) {
|
|
|
1249
1267
|
// Create a new event. Use nextEventSequence(events) instead of
|
|
1250
1268
|
// `events.length + 1` so that any gap (from pruning, archive replay,
|
|
1251
1269
|
// or self-heal dedup) cannot collide with an existing id.
|
|
1252
|
-
const event = buildEventFromMessage(currentMessages[i], nextEventSequence(events), options.recordedAt, "live", null, null, currentIngressTimes[i]);
|
|
1270
|
+
const event = buildEventFromMessage(currentMessages[i], nextEventSequence(events), options.recordedAt, "live", null, null, currentIngressTimes[i], currentIngressRelations[i]);
|
|
1253
1271
|
events.push(event);
|
|
1254
1272
|
currentEventIds.push(event.id);
|
|
1255
1273
|
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readStewardPolicy = readStewardPolicy;
|
|
37
|
+
exports.updateStewardPolicy = updateStewardPolicy;
|
|
38
|
+
exports.readRoutineActionReceipts = readRoutineActionReceipts;
|
|
39
|
+
exports.inspectRoutineActionGrant = inspectRoutineActionGrant;
|
|
40
|
+
exports.consumeRoutineActionGrant = consumeRoutineActionGrant;
|
|
41
|
+
exports.transitionRoutineActionReceipt = transitionRoutineActionReceipt;
|
|
42
|
+
exports.recoverRoutineActionReceipts = recoverRoutineActionReceipts;
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const node_crypto_1 = require("node:crypto");
|
|
46
|
+
const runtime_1 = require("../nerves/runtime");
|
|
47
|
+
const session_transaction_1 = require("../mind/session-transaction");
|
|
48
|
+
const EMPTY_POLICY = { schemaVersion: 1, version: 0, desiredStates: {}, routineActionGrants: {}, updatedAt: null };
|
|
49
|
+
function policyDir(agentRoot) {
|
|
50
|
+
return path.join(agentRoot, "state", "policy");
|
|
51
|
+
}
|
|
52
|
+
function policyPath(agentRoot) {
|
|
53
|
+
return path.join(policyDir(agentRoot), "steward.json");
|
|
54
|
+
}
|
|
55
|
+
function receiptsPath(agentRoot) {
|
|
56
|
+
return path.join(policyDir(agentRoot), "action-receipts.ndjson");
|
|
57
|
+
}
|
|
58
|
+
function auditPath(agentRoot) {
|
|
59
|
+
return path.join(policyDir(agentRoot), "policy-audit.ndjson");
|
|
60
|
+
}
|
|
61
|
+
function ensureDirectory(agentRoot) {
|
|
62
|
+
fs.mkdirSync(policyDir(agentRoot), { recursive: true, mode: 0o700 });
|
|
63
|
+
fs.chmodSync(policyDir(agentRoot), 0o700);
|
|
64
|
+
}
|
|
65
|
+
function appendReceipt(filePath, value) {
|
|
66
|
+
const creating = !fs.existsSync(filePath);
|
|
67
|
+
const fd = fs.openSync(filePath, "a", 0o600);
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(fd, `${JSON.stringify(value)}\n`, "utf8");
|
|
70
|
+
fs.fsyncSync(fd);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
fs.closeSync(fd);
|
|
74
|
+
}
|
|
75
|
+
fs.chmodSync(filePath, 0o600);
|
|
76
|
+
if (creating) {
|
|
77
|
+
const directory = fs.openSync(path.dirname(filePath), fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
78
|
+
try {
|
|
79
|
+
fs.fsyncSync(directory);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
fs.closeSync(directory);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function requireText(value, label) {
|
|
87
|
+
const result = value.trim();
|
|
88
|
+
if (!result)
|
|
89
|
+
throw new Error(`${label} must be nonempty`);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function optionalExpiry(value, now) {
|
|
93
|
+
if (!value)
|
|
94
|
+
return undefined;
|
|
95
|
+
const epoch = Date.parse(value);
|
|
96
|
+
if (!Number.isFinite(epoch) || new Date(epoch).toISOString() !== value || epoch <= Date.parse(now))
|
|
97
|
+
throw new Error("policy expiry must be a future canonical timestamp");
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function validateStewardPolicy(value) {
|
|
101
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
102
|
+
throw new Error("steward policy is invalid");
|
|
103
|
+
const candidate = value;
|
|
104
|
+
if (candidate.schemaVersion !== 1 || !Number.isInteger(candidate.version) || candidate.version < 0 || typeof candidate.desiredStates !== "object" || !candidate.desiredStates || typeof candidate.routineActionGrants !== "object" || !candidate.routineActionGrants) {
|
|
105
|
+
throw new Error("steward policy is invalid");
|
|
106
|
+
}
|
|
107
|
+
return candidate;
|
|
108
|
+
}
|
|
109
|
+
function readStewardPolicy(agentRoot) {
|
|
110
|
+
const filePath = policyPath(agentRoot);
|
|
111
|
+
if (!fs.existsSync(filePath))
|
|
112
|
+
return structuredClone(EMPTY_POLICY);
|
|
113
|
+
return validateStewardPolicy(JSON.parse(fs.readFileSync(filePath, "utf8")));
|
|
114
|
+
}
|
|
115
|
+
function updateStewardPolicy(agentRoot, input) {
|
|
116
|
+
if (input.actor.trustLevel !== "family")
|
|
117
|
+
throw new Error("steward policy mutation requires family authority");
|
|
118
|
+
const authorizingSessionEvent = requireText(input.actor.sessionEventId, "authorizing session event");
|
|
119
|
+
const issuer = requireText(input.actor.friendId, "issuer");
|
|
120
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(policyPath(agentRoot), (lease) => {
|
|
121
|
+
const snapshot = (0, session_transaction_1.readSessionTransaction)(policyPath(agentRoot), lease);
|
|
122
|
+
const current = snapshot.value === null ? structuredClone(EMPTY_POLICY) : validateStewardPolicy(snapshot.value);
|
|
123
|
+
if (current.version !== input.expectedVersion)
|
|
124
|
+
throw new Error(`steward policy version changed: expected ${input.expectedVersion}, got ${current.version}`);
|
|
125
|
+
const now = input.now ?? new Date().toISOString();
|
|
126
|
+
const expiresAt = optionalExpiry(input.mutation.expiresAt, now);
|
|
127
|
+
const version = current.version + 1;
|
|
128
|
+
const next = { ...current, version, desiredStates: { ...current.desiredStates }, routineActionGrants: { ...current.routineActionGrants }, updatedAt: now };
|
|
129
|
+
if (input.mutation.kind === "set_desired_state") {
|
|
130
|
+
next.desiredStates[requireText(input.mutation.key, "desired state key")] = {
|
|
131
|
+
value: requireText(input.mutation.value, "desired state value"),
|
|
132
|
+
provenance: input.mutation.provenance,
|
|
133
|
+
version,
|
|
134
|
+
source: requireText(input.mutation.source, "desired state source"),
|
|
135
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (input.mutation.provenance !== "stated" && input.mutation.provenance !== "installed_explicit_policy")
|
|
140
|
+
throw new Error("routine action grants require explicit authority");
|
|
141
|
+
if (!Number.isInteger(input.mutation.maxCount) || input.mutation.maxCount < 1 || !Number.isFinite(input.mutation.windowMs) || input.mutation.windowMs <= 0)
|
|
142
|
+
throw new Error("routine action grant bounds are invalid");
|
|
143
|
+
if (!input.mutation.verificationRequired)
|
|
144
|
+
throw new Error("routine action grants require post-action verification");
|
|
145
|
+
const targets = [...new Set(input.mutation.targets.map((value) => requireText(value, "routine action target")))];
|
|
146
|
+
if (targets.length === 0)
|
|
147
|
+
throw new Error("routine action grant requires a target");
|
|
148
|
+
next.routineActionGrants[requireText(input.mutation.key, "routine action key")] = {
|
|
149
|
+
action: requireText(input.mutation.action, "routine action"), targets, maxCount: input.mutation.maxCount, windowMs: input.mutation.windowMs,
|
|
150
|
+
verificationRequired: input.mutation.verificationRequired, exclusions: [...new Set(input.mutation.exclusions)], provenance: input.mutation.provenance,
|
|
151
|
+
issuer, authorizedAt: now, authorizingSessionEvent, version, ...(expiresAt ? { expiresAt } : {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
ensureDirectory(agentRoot);
|
|
155
|
+
(0, session_transaction_1.writeSessionTransaction)(policyPath(agentRoot), next, { lease, expectedRevision: snapshot.revision });
|
|
156
|
+
appendReceipt(auditPath(agentRoot), { schemaVersion: 1, policyVersion: version, issuer, authorizingSessionEvent, mutationKind: input.mutation.kind, at: now });
|
|
157
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "heart.steward_policy_updated", message: "updated steward policy", meta: { version, mutationKind: input.mutation.kind, issuer } });
|
|
158
|
+
return next;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function readActionReceiptHistory(agentRoot) {
|
|
162
|
+
const filePath = receiptsPath(agentRoot);
|
|
163
|
+
if (!fs.existsSync(filePath))
|
|
164
|
+
return [];
|
|
165
|
+
return fs.readFileSync(filePath, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
166
|
+
}
|
|
167
|
+
function readRoutineActionReceipts(agentRoot) {
|
|
168
|
+
const latest = new Map();
|
|
169
|
+
for (const receipt of readActionReceiptHistory(agentRoot))
|
|
170
|
+
latest.set(receipt.id, receipt);
|
|
171
|
+
return [...latest.values()];
|
|
172
|
+
}
|
|
173
|
+
function expectedOff(policy, target, now) {
|
|
174
|
+
const desired = policy.desiredStates[`container:${target}`];
|
|
175
|
+
if (!desired || (desired.expiresAt && Date.parse(desired.expiresAt) <= Date.parse(now)))
|
|
176
|
+
return false;
|
|
177
|
+
return /^(?:off|disabled|paused|intentionally_off|intentionally_paused)$/u.test(desired.value.trim().toLowerCase());
|
|
178
|
+
}
|
|
179
|
+
const UNRESOLVED_ACTION_STATES = new Set(["reserved", "attempting", "effect_acknowledged", "recovery_pending", "indeterminate"]);
|
|
180
|
+
function inspectRoutineActionGrantSnapshot(policy, receipts, input) {
|
|
181
|
+
if (input.expectedPolicyVersion !== undefined && policy.version !== input.expectedPolicyVersion)
|
|
182
|
+
return { allowed: false, reason: "routine action policy version changed" };
|
|
183
|
+
const grant = policy.routineActionGrants[input.key];
|
|
184
|
+
if (!grant)
|
|
185
|
+
return { allowed: false, reason: "routine action grant is missing" };
|
|
186
|
+
if (grant.action !== input.action)
|
|
187
|
+
return { allowed: false, reason: "routine action does not match the grant" };
|
|
188
|
+
if (!grant.targets.includes(input.target) || grant.exclusions.includes(input.target))
|
|
189
|
+
return { allowed: false, reason: "routine action target is not authorized" };
|
|
190
|
+
const now = input.now ?? new Date().toISOString();
|
|
191
|
+
if (grant.expiresAt && Date.parse(grant.expiresAt) <= Date.parse(now))
|
|
192
|
+
return { allowed: false, reason: "routine action grant expired" };
|
|
193
|
+
if (expectedOff(policy, input.target, now))
|
|
194
|
+
return { allowed: false, reason: "container is expected off" };
|
|
195
|
+
if (receipts.some((receipt) => receipt.action === input.action && receipt.target === input.target && UNRESOLVED_ACTION_STATES.has(receipt.state))) {
|
|
196
|
+
return { allowed: false, reason: "routine action has an unresolved receipt for this target" };
|
|
197
|
+
}
|
|
198
|
+
return { allowed: true, policyVersion: policy.version, grantVersion: grant.version, key: input.key, action: grant.action, target: input.target };
|
|
199
|
+
}
|
|
200
|
+
function inspectRoutineActionGrant(agentRoot, input) {
|
|
201
|
+
try {
|
|
202
|
+
const policy = readStewardPolicy(agentRoot);
|
|
203
|
+
return inspectRoutineActionGrantSnapshot(policy, readRoutineActionReceipts(agentRoot), input);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
return { allowed: false, reason: error instanceof Error ? error.message : "routine action policy is unavailable" };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function consumeRoutineActionGrant(agentRoot, input) {
|
|
210
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(policyPath(agentRoot), (lease) => {
|
|
211
|
+
const snapshot = (0, session_transaction_1.readSessionTransaction)(policyPath(agentRoot), lease);
|
|
212
|
+
const policy = snapshot.value === null ? structuredClone(EMPTY_POLICY) : validateStewardPolicy(snapshot.value);
|
|
213
|
+
const grant = policy.routineActionGrants[input.key];
|
|
214
|
+
const now = input.now ?? new Date().toISOString();
|
|
215
|
+
const action = input.action ?? grant?.action ?? "";
|
|
216
|
+
const receipts = readRoutineActionReceipts(agentRoot);
|
|
217
|
+
const decision = inspectRoutineActionGrantSnapshot(policy, receipts, { key: input.key, action, target: input.target, expectedPolicyVersion: input.expectedPolicyVersion, now });
|
|
218
|
+
if (!decision.allowed)
|
|
219
|
+
throw new Error(decision.reason);
|
|
220
|
+
ensureDirectory(agentRoot);
|
|
221
|
+
const windowStart = Date.parse(now) - grant.windowMs;
|
|
222
|
+
const used = receipts.filter((receipt) => receipt.key === input.key && Date.parse(receipt.reservedAt) > windowStart).length;
|
|
223
|
+
if (used >= grant.maxCount)
|
|
224
|
+
throw new Error("routine action rate limit reached");
|
|
225
|
+
const id = `action-${(0, node_crypto_1.randomUUID)()}`;
|
|
226
|
+
const receipt = {
|
|
227
|
+
schemaVersion: 2,
|
|
228
|
+
id,
|
|
229
|
+
state: "reserved",
|
|
230
|
+
key: input.key,
|
|
231
|
+
action,
|
|
232
|
+
target: input.target,
|
|
233
|
+
policyVersion: policy.version,
|
|
234
|
+
grantVersion: grant.version,
|
|
235
|
+
reservedAt: now,
|
|
236
|
+
updatedAt: now,
|
|
237
|
+
authorizationReceiptId: input.authorizationReceiptId ?? `policy-${grant.issuer}-${grant.authorizingSessionEvent}`,
|
|
238
|
+
authorizationVersion: input.authorizationVersion ?? grant.version,
|
|
239
|
+
attemptId: input.attemptId ?? `attempt-${(0, node_crypto_1.randomUUID)()}`,
|
|
240
|
+
attempt: 1,
|
|
241
|
+
expectedBeforeState: input.expectedBeforeState ?? null,
|
|
242
|
+
resolvedTarget: input.resolvedTarget ?? { id: "unresolved", name: input.target },
|
|
243
|
+
effect: input.effect ?? { operation: action, targetId: input.resolvedTarget?.id ?? "unresolved" },
|
|
244
|
+
effectReceipt: null,
|
|
245
|
+
verifiedAfterState: null,
|
|
246
|
+
recoveryState: { state: "not_needed", compensation: "none" },
|
|
247
|
+
};
|
|
248
|
+
appendReceipt(receiptsPath(agentRoot), receipt);
|
|
249
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "heart.routine_action_reserved", message: "reserved routine action grant", meta: { key: input.key, target: input.target, policyVersion: policy.version } });
|
|
250
|
+
return receipt;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function transitionRoutineActionReceipt(agentRoot, input) {
|
|
254
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(policyPath(agentRoot), () => {
|
|
255
|
+
const current = readRoutineActionReceipts(agentRoot).find((receipt) => receipt.id === input.id);
|
|
256
|
+
if (!current)
|
|
257
|
+
throw new Error("routine action receipt is missing");
|
|
258
|
+
if (current.state !== input.expectedState)
|
|
259
|
+
throw new Error(`routine action receipt state changed: expected ${input.expectedState}, got ${current.state}`);
|
|
260
|
+
const now = input.at ?? new Date().toISOString();
|
|
261
|
+
const recoveryState = input.recoveryState ?? current.recoveryState;
|
|
262
|
+
const next = {
|
|
263
|
+
...current,
|
|
264
|
+
state: input.state,
|
|
265
|
+
updatedAt: now,
|
|
266
|
+
...(input.effectReceipt !== undefined ? { effectReceipt: input.effectReceipt } : {}),
|
|
267
|
+
...(input.verifiedAfterState !== undefined ? { verifiedAfterState: input.verifiedAfterState } : {}),
|
|
268
|
+
recoveryState,
|
|
269
|
+
};
|
|
270
|
+
appendReceipt(receiptsPath(agentRoot), next);
|
|
271
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "heart.routine_action_transitioned", message: "transitioned routine action receipt", meta: { id: next.id, state: next.state, target: next.target } });
|
|
272
|
+
return next;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async function recoverRoutineActionReceipts(agentRoot, options) {
|
|
276
|
+
const recovered = [];
|
|
277
|
+
for (const receipt of readRoutineActionReceipts(agentRoot)) {
|
|
278
|
+
if (receipt.state === "reserved") {
|
|
279
|
+
recovered.push(transitionRoutineActionReceipt(agentRoot, { id: receipt.id, expectedState: "reserved", state: "recovered_no_effect", recoveryState: { state: "completed", compensation: "none" } }));
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (receipt.state === "attempting") {
|
|
283
|
+
recovered.push(transitionRoutineActionReceipt(agentRoot, { id: receipt.id, expectedState: "attempting", state: "indeterminate", recoveryState: { state: "manual_inspection_required", compensation: "none" } }));
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (receipt.state !== "effect_acknowledged" && receipt.state !== "recovery_pending")
|
|
287
|
+
continue;
|
|
288
|
+
const pending = receipt.state === "effect_acknowledged"
|
|
289
|
+
? transitionRoutineActionReceipt(agentRoot, { id: receipt.id, expectedState: "effect_acknowledged", state: "recovery_pending", recoveryState: { state: "pending", compensation: "none" } })
|
|
290
|
+
: receipt;
|
|
291
|
+
options.afterRecoveryClaim?.(pending);
|
|
292
|
+
const observed = await options.observeTarget(pending.resolvedTarget);
|
|
293
|
+
if (observed.id === pending.resolvedTarget.id && observed.name === pending.resolvedTarget.name && observed.state === "running") {
|
|
294
|
+
recovered.push(transitionRoutineActionReceipt(agentRoot, { id: pending.id, expectedState: "recovery_pending", state: "verified", verifiedAfterState: observed.state, recoveryState: { state: "completed", compensation: "none" } }));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
recovered.push(transitionRoutineActionReceipt(agentRoot, { id: pending.id, expectedState: "recovery_pending", state: "indeterminate", recoveryState: { state: "manual_inspection_required", compensation: "none" } }));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "heart.routine_action_recovery", message: "reconciled interrupted routine action receipts", meta: { recoveredCount: recovered.length } });
|
|
301
|
+
return recovered;
|
|
302
|
+
}
|
|
@@ -263,7 +263,9 @@ async function executeApprovalDecision(options) {
|
|
|
263
263
|
|| validated.value.schemaDigest !== decided.schemaDigest) {
|
|
264
264
|
return terminalizeClaimed(options.approvalStore, decided, "drifted", "tool arguments or schema drift");
|
|
265
265
|
}
|
|
266
|
-
const policy =
|
|
266
|
+
const policy = await options.resolveApprovalPolicy?.(decided.toolName, validated.value.arguments)
|
|
267
|
+
?? definition.approvalPolicy?.(validated.value.arguments)
|
|
268
|
+
?? { kind: "not_required" };
|
|
267
269
|
if (policy.kind !== "required") {
|
|
268
270
|
return terminalizeClaimed(options.approvalStore, decided, "drifted", "approval policy no longer requires approval");
|
|
269
271
|
}
|
|
@@ -41,6 +41,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
};
|
|
42
42
|
})();
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.readSenseStatusLines = readSenseStatusLines;
|
|
44
45
|
exports.buildTurnContext = buildTurnContext;
|
|
45
46
|
const fs = __importStar(require("fs"));
|
|
46
47
|
const path = __importStar(require("path"));
|
package/dist/mind/context.js
CHANGED
|
@@ -292,10 +292,11 @@ function writeSessionEnvelopeUnderLease(filePath, envelope, lease, expectedRevis
|
|
|
292
292
|
});
|
|
293
293
|
}
|
|
294
294
|
function saveSession(filePath, messages, lastUsage, state, lease) {
|
|
295
|
-
withMutationLease(filePath, lease, (owned) => {
|
|
295
|
+
return withMutationLease(filePath, lease, (owned) => {
|
|
296
296
|
const existing = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
|
|
297
297
|
const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
|
|
298
298
|
const currentIngressTimes = messages.map(session_events_1.getIngressTime);
|
|
299
|
+
const currentIngressRelations = messages.map(session_events_1.getIngressRelations);
|
|
299
300
|
const sanitized = (0, session_events_1.sanitizeProviderMessages)(messages);
|
|
300
301
|
const { envelope } = (0, session_events_1.buildCanonicalSessionEnvelope)({
|
|
301
302
|
existing,
|
|
@@ -303,6 +304,7 @@ function saveSession(filePath, messages, lastUsage, state, lease) {
|
|
|
303
304
|
currentMessages: sanitized,
|
|
304
305
|
trimmedMessages: sanitized,
|
|
305
306
|
currentIngressTimes,
|
|
307
|
+
currentIngressRelations,
|
|
306
308
|
recordedAt: new Date().toISOString(),
|
|
307
309
|
lastUsage: lastUsage ?? null,
|
|
308
310
|
state,
|
|
@@ -313,6 +315,7 @@ function saveSession(filePath, messages, lastUsage, state, lease) {
|
|
|
313
315
|
},
|
|
314
316
|
});
|
|
315
317
|
writeSessionEnvelopeUnderLease(filePath, envelope, owned);
|
|
318
|
+
return envelope.events;
|
|
316
319
|
});
|
|
317
320
|
}
|
|
318
321
|
function appendSyntheticAssistantMessage(filePath, content, lease) {
|
|
@@ -386,12 +389,13 @@ function postTurnTrim(messages, usage, hooks) {
|
|
|
386
389
|
}
|
|
387
390
|
}
|
|
388
391
|
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
392
|
+
const currentIngressTimes = preTrimMessages.map(session_events_1.getIngressTime);
|
|
393
|
+
const currentIngressRelations = preTrimMessages.map(session_events_1.getIngressRelations);
|
|
389
394
|
const currentMessages = (0, session_events_1.sanitizeProviderMessages)(messages);
|
|
390
|
-
const currentIngressTimes = currentMessages.map(session_events_1.getIngressTime);
|
|
391
395
|
const tokenTrimmedMessages = trimMessages(currentMessages, maxTokens, contextMargin, usage?.input_tokens);
|
|
392
396
|
const trimmedMessages = compactIdleRestOnlyTurns(tokenTrimmedMessages);
|
|
393
397
|
messages.splice(0, messages.length, ...trimmedMessages);
|
|
394
|
-
return { currentMessages, trimmedMessages, currentIngressTimes, maxTokens, contextMargin };
|
|
398
|
+
return { currentMessages, trimmedMessages, currentIngressTimes, currentIngressRelations, maxTokens, contextMargin };
|
|
395
399
|
}
|
|
396
400
|
/**
|
|
397
401
|
* Synchronous persist: load existing envelope, build canonical envelope, write to disk.
|
|
@@ -406,6 +410,7 @@ function postTurnPersist(sessPath, prepared, usage, state, lease, expectedRevisi
|
|
|
406
410
|
currentMessages: prepared.currentMessages,
|
|
407
411
|
trimmedMessages: prepared.trimmedMessages,
|
|
408
412
|
currentIngressTimes: prepared.currentIngressTimes,
|
|
413
|
+
currentIngressRelations: prepared.currentIngressRelations,
|
|
409
414
|
recordedAt: new Date().toISOString(),
|
|
410
415
|
lastUsage: usage ?? null,
|
|
411
416
|
state,
|
package/dist/mind/pending.js
CHANGED
|
@@ -39,11 +39,13 @@ exports.getDeferredReturnDir = getDeferredReturnDir;
|
|
|
39
39
|
exports.getPrivateRuntimePendingDir = getPrivateRuntimePendingDir;
|
|
40
40
|
exports.hasPendingMessages = hasPendingMessages;
|
|
41
41
|
exports.queuePendingMessage = queuePendingMessage;
|
|
42
|
+
exports.queuePendingMessageOnce = queuePendingMessageOnce;
|
|
42
43
|
exports.enqueueDeferredReturn = enqueueDeferredReturn;
|
|
43
44
|
exports.drainDeferredReturns = drainDeferredReturns;
|
|
44
45
|
exports.drainPending = drainPending;
|
|
45
46
|
const fs = __importStar(require("fs"));
|
|
46
47
|
const path = __importStar(require("path"));
|
|
48
|
+
const node_crypto_1 = require("node:crypto");
|
|
47
49
|
const identity_1 = require("../heart/identity");
|
|
48
50
|
const session_events_1 = require("../heart/session-events");
|
|
49
51
|
const runtime_1 = require("../nerves/runtime");
|
|
@@ -79,6 +81,29 @@ function writeQueueFile(queueDir, message) {
|
|
|
79
81
|
function queuePendingMessage(pendingDir, message) {
|
|
80
82
|
writeQueueFile(pendingDir, message);
|
|
81
83
|
}
|
|
84
|
+
function queuePendingMessageOnce(pendingDir, message) {
|
|
85
|
+
fs.mkdirSync(pendingDir, { recursive: true });
|
|
86
|
+
const identity = (0, node_crypto_1.createHash)("sha256").update(message.packetId).digest("hex");
|
|
87
|
+
const filePath = path.join(pendingDir, `${message.timestamp}-${identity}.json`);
|
|
88
|
+
const temporary = `${filePath}.tmp-${process.pid}`;
|
|
89
|
+
const content = JSON.stringify({ ...message, content: (0, session_events_1.capStructuredRecordString)(message.content) }, null, 2);
|
|
90
|
+
try {
|
|
91
|
+
fs.writeFileSync(temporary, content, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
92
|
+
try {
|
|
93
|
+
fs.renameSync(temporary, filePath);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error.code !== "EEXIST")
|
|
97
|
+
throw error;
|
|
98
|
+
fs.unlinkSync(temporary);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.code !== "EEXIST")
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
return filePath;
|
|
106
|
+
}
|
|
82
107
|
function drainQueue(queueDir) {
|
|
83
108
|
if (!fs.existsSync(queueDir))
|
|
84
109
|
return { messages: [], recovered: 0 };
|
package/dist/mind/prompt.js
CHANGED
|
@@ -71,6 +71,7 @@ const runtime_credentials_1 = require("../heart/runtime-credentials");
|
|
|
71
71
|
const runtime_mode_1 = require("../heart/daemon/runtime-mode");
|
|
72
72
|
const friends_1 = require("@ouro.bot/friends");
|
|
73
73
|
const runtime_1 = require("../nerves/runtime");
|
|
74
|
+
const relationship_authorization_1 = require("../repertoire/relationship-authorization");
|
|
74
75
|
const bundle_manifest_1 = require("./bundle-manifest");
|
|
75
76
|
const first_impressions_1 = require("./first-impressions");
|
|
76
77
|
const desk_section_1 = require("./desk-section");
|
|
@@ -633,7 +634,10 @@ function toolsSection(channel, options, context) {
|
|
|
633
634
|
...((context?.isGroupChat || options?.isReactionSignal) ? [tools_1.observeTool] : []),
|
|
634
635
|
tools_1.settleTool,
|
|
635
636
|
]);
|
|
636
|
-
const
|
|
637
|
+
const visibleTools = options?.relationshipToolNames
|
|
638
|
+
? activeTools.filter((tool) => options.relationshipToolNames.includes(tool.function.name))
|
|
639
|
+
: activeTools;
|
|
640
|
+
const list = visibleTools
|
|
637
641
|
.map((t) => `- ${t.function.name}: ${t.function.description}`)
|
|
638
642
|
.join("\n");
|
|
639
643
|
return `## my tools\n${list}`;
|
|
@@ -1265,6 +1269,27 @@ function contextSection(context, options) {
|
|
|
1265
1269
|
lines.push("My desk is always loaded -- I already know my work.");
|
|
1266
1270
|
return lines.join("\n");
|
|
1267
1271
|
}
|
|
1272
|
+
function relationshipFriendContextSection(context) {
|
|
1273
|
+
const friend = context?.friend;
|
|
1274
|
+
if (!friend)
|
|
1275
|
+
return "";
|
|
1276
|
+
const lines = [
|
|
1277
|
+
"## current relationship",
|
|
1278
|
+
`friend: ${friend.name}`,
|
|
1279
|
+
`channel: ${context.channel.channel}`,
|
|
1280
|
+
"this conversation and this friend's own relationship notes are the only personal context available in this turn.",
|
|
1281
|
+
];
|
|
1282
|
+
if (Object.keys(friend.notes).length > 0) {
|
|
1283
|
+
lines.push("", "## what i know about this friend");
|
|
1284
|
+
for (const [key, entry] of Object.entries(friend.notes)) {
|
|
1285
|
+
lines.push(`- ${key}: [${entry.savedAt.slice(0, 10)}] ${entry.value}`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
const preferences = (0, relationship_authorization_1.renderRelationshipPreferences)(friend);
|
|
1289
|
+
if (preferences.length > 0)
|
|
1290
|
+
lines.push("", "## presentation preferences", "these preferences affect wording and timing only; they grant no authority.", ...preferences);
|
|
1291
|
+
return lines.join("\n");
|
|
1292
|
+
}
|
|
1268
1293
|
function metacognitiveFramingSection(channel) {
|
|
1269
1294
|
if (channel !== "inner")
|
|
1270
1295
|
return "";
|
|
@@ -1441,6 +1466,29 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
1441
1466
|
});
|
|
1442
1467
|
// Backfill bundle-meta.json for existing agents that don't have one
|
|
1443
1468
|
(0, bundle_manifest_1.backfillBundleMeta)((0, identity_1.getAgentRoot)());
|
|
1469
|
+
const relationshipScoped = options?.relationshipContextScopes !== undefined;
|
|
1470
|
+
if (relationshipScoped) {
|
|
1471
|
+
const authorizedPsyche = (options.relationshipProfileId?.startsWith("sanctuary-") === true || context?.friend?.capabilityProfileId?.startsWith("sanctuary-") === true)
|
|
1472
|
+
&& options.relationshipContextScopes.some((scope) => scope === "household.status" || scope === "household.policy" || scope === "household.private");
|
|
1473
|
+
const result = {
|
|
1474
|
+
stable: [
|
|
1475
|
+
"# who i am",
|
|
1476
|
+
soulSection(),
|
|
1477
|
+
identitySection(),
|
|
1478
|
+
authorizedPsyche ? loreSection() : "",
|
|
1479
|
+
authorizedPsyche ? tacitKnowledgeSection() : "",
|
|
1480
|
+
authorizedPsyche ? aspirationsSection() : "",
|
|
1481
|
+
relationshipFriendContextSection(context),
|
|
1482
|
+
trustContextSection(context),
|
|
1483
|
+
"# my tools",
|
|
1484
|
+
toolsSection(channel, options, context),
|
|
1485
|
+
toolRestrictionSection(context),
|
|
1486
|
+
].filter(Boolean).join("\n\n"),
|
|
1487
|
+
volatile: [dateSection(), currentTriggerSection(channel, options)].filter(Boolean).join("\n\n"),
|
|
1488
|
+
};
|
|
1489
|
+
(0, runtime_1.emitNervesEvent)({ event: "mind.step_end", component: "mind", message: "buildSystem completed", meta: { channel } });
|
|
1490
|
+
return result;
|
|
1491
|
+
}
|
|
1444
1492
|
const stableParts = [
|
|
1445
1493
|
// Group 1: who i am
|
|
1446
1494
|
"# who i am",
|
|
@@ -61,8 +61,9 @@ const PROTECTED_PATH_SEGMENTS = [
|
|
|
61
61
|
".git/",
|
|
62
62
|
".ouro-cli/vault-unlock/",
|
|
63
63
|
".ouro-cli/vault-unlock-dpapi/",
|
|
64
|
+
"state/policy/",
|
|
64
65
|
];
|
|
65
|
-
const PROTECTED_FILENAMES = ["agent.json"];
|
|
66
|
+
const PROTECTED_FILENAMES = ["agent.json", "tool-profiles.json"];
|
|
66
67
|
function isProtectedPath(filePath) {
|
|
67
68
|
for (const segment of PROTECTED_PATH_SEGMENTS) {
|
|
68
69
|
if (filePath.includes(`/${segment}`) || filePath.startsWith(segment))
|