@ouro.bot/cli 0.1.0-alpha.741 → 0.1.0-alpha.743
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 +42 -0
- package/deploy/unraid/Dockerfile +7 -2
- package/deploy/unraid/README.txt +717 -106
- 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/agent.json +2 -2
- 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/provider-readiness.json +3 -10
- 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 +12 -9
- 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/context-loss-gauntlet.js +31 -2
- package/dist/heart/context-loss-sentinel.js +2 -0
- package/dist/heart/core.js +37 -7
- package/dist/heart/cross-chat-delivery.js +1 -0
- package/dist/heart/daemon/container-healthcheck.js +3 -1
- package/dist/heart/daemon/container-runtime.js +13 -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 +151 -87
- 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/daemon/sanctuary-scheduler-origin.js +11 -5
- package/dist/heart/daemon/supercronic-supervisor.js +16 -1
- package/dist/heart/external-events/router.js +1025 -43
- package/dist/heart/provider-models.js +10 -3
- 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 +51 -96
- package/dist/senses/sanctuary-health.js +54 -93
- 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 +236 -72
- package/dist/senses/telegram-effect-adapter.js +787 -0
- package/dist/senses/telegram-entry.js +4 -1
- package/dist/senses/telegram.js +728 -48
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
|
@@ -1,14 +1,216 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.continuityToolDefinitions = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
4
39
|
const identity_1 = require("../heart/identity");
|
|
40
|
+
const router_1 = require("../heart/external-events/router");
|
|
5
41
|
const runtime_1 = require("../nerves/runtime");
|
|
6
42
|
const episodes_1 = require("../arc/episodes");
|
|
7
43
|
const cares_1 = require("../arc/cares");
|
|
8
44
|
const presence_1 = require("../arc/presence");
|
|
9
45
|
const intentions_1 = require("../arc/intentions");
|
|
46
|
+
const steward_policy_1 = require("../heart/steward-policy");
|
|
47
|
+
const await_parser_1 = require("../heart/awaiting/await-parser");
|
|
10
48
|
exports.continuityToolDefinitions = [
|
|
11
49
|
// ── Continuity tools ──────────────────────────────────────────────
|
|
50
|
+
{
|
|
51
|
+
tool: {
|
|
52
|
+
type: "function",
|
|
53
|
+
function: {
|
|
54
|
+
name: "external_event_disposition",
|
|
55
|
+
description: "Classify the exact external-event generation I just investigated. An ask or report is the sole owner-delivery path and sends reason once for this receipt; silent and act only record the disposition.",
|
|
56
|
+
parameters: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
recordPath: { type: "string", description: "Exact receipt path from the external-event message" },
|
|
60
|
+
expectedGeneration: { type: "number", description: "Exact generation shown in the external-event turn" },
|
|
61
|
+
classifiedRevision: { type: "string", description: "Exact observation revision investigated in this turn" },
|
|
62
|
+
classification: { type: "string", enum: ["expected", "needs_attention", "adopted", "snoozed", "dismissed_until_change", "resolved"] },
|
|
63
|
+
stewardPolicyKind: { type: "string", enum: ["current", "none"], description: "Use current with the exact live policy key/version, or none only for a fresh observation with no applicable policy" },
|
|
64
|
+
stewardPolicyKey: { type: "string", description: "Exact current policy key used for this decision" },
|
|
65
|
+
stewardPolicyVersion: { type: "number", description: "Exact current policy version used for this decision" },
|
|
66
|
+
decision: { type: "string", enum: ["silent", "act", "ask", "report"] },
|
|
67
|
+
reason: { type: "string", description: "Short plain-language reason for the decision" },
|
|
68
|
+
nextWake: { type: "string", enum: ["on_change", "on_escalation", "on_recovery", "at"] },
|
|
69
|
+
wakeAt: { type: "string", description: "ISO time required when nextWake=at" },
|
|
70
|
+
awaitId: { type: "string", description: "Existing await receipt required when nextWake=at" },
|
|
71
|
+
careId: { type: "string", description: "Existing Care adopted for this incident, if any" },
|
|
72
|
+
actionRefs: { type: "array", items: { type: "string" } },
|
|
73
|
+
verificationRefs: { type: "array", items: { type: "string" } },
|
|
74
|
+
},
|
|
75
|
+
required: ["recordPath", "expectedGeneration", "classifiedRevision", "classification", "stewardPolicyKind", "decision", "reason", "nextWake"],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
handler: (a, ctx) => {
|
|
80
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
81
|
+
const agentEventRoot = path.resolve((0, router_1.getExternalEventRoot)(), agentName);
|
|
82
|
+
const recordPath = path.resolve(String(a.recordPath ?? ""));
|
|
83
|
+
const relative = path.relative(agentEventRoot, recordPath);
|
|
84
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative) || !recordPath.endsWith(".json")) {
|
|
85
|
+
throw new Error("External event receipt does not belong to the current agent");
|
|
86
|
+
}
|
|
87
|
+
const record = (0, router_1.readExternalEventRecord)(recordPath);
|
|
88
|
+
if (record.agent !== agentName)
|
|
89
|
+
throw new Error("External event receipt does not belong to the current agent");
|
|
90
|
+
const turnContext = ctx?.currentExternalEvent;
|
|
91
|
+
const turnEvent = turnContext
|
|
92
|
+
? [turnContext, ...(turnContext.relatedEvents ?? [])].find((event) => path.resolve(event.recordPath) === recordPath)
|
|
93
|
+
: undefined;
|
|
94
|
+
const expectedGeneration = Number(a.expectedGeneration);
|
|
95
|
+
const classifiedRevision = String(a.classifiedRevision ?? "");
|
|
96
|
+
if (!turnEvent || turnEvent.recordPath !== recordPath || turnEvent.agent !== agentName
|
|
97
|
+
|| turnEvent.generation !== expectedGeneration || turnEvent.observationRevision !== classifiedRevision
|
|
98
|
+
|| record.generation !== expectedGeneration || record.observationRevision !== classifiedRevision
|
|
99
|
+
|| record.executionState !== "running" || record.claimOwner !== turnEvent.claimOwner) {
|
|
100
|
+
throw new Error("External event disposition is not authorized for this exact turn lease");
|
|
101
|
+
}
|
|
102
|
+
const classification = String(a.classification);
|
|
103
|
+
const decision = String(a.decision);
|
|
104
|
+
const nextWake = String(a.nextWake);
|
|
105
|
+
const policyVersion = Number(a.stewardPolicyVersion);
|
|
106
|
+
const policyKind = String(a.stewardPolicyKind ?? "");
|
|
107
|
+
const reason = String(a.reason ?? "").trim();
|
|
108
|
+
if (!reason || (policyKind !== "none" && (!Number.isSafeInteger(policyVersion) || policyVersion < 1))
|
|
109
|
+
|| !["current", "none"].includes(policyKind)
|
|
110
|
+
|| !["expected", "needs_attention", "adopted", "snoozed", "dismissed_until_change", "resolved"].includes(classification)
|
|
111
|
+
|| !["silent", "act", "ask", "report"].includes(decision)
|
|
112
|
+
|| !["on_change", "on_escalation", "on_recovery", "at"].includes(nextWake)) {
|
|
113
|
+
throw new Error("External event disposition is invalid");
|
|
114
|
+
}
|
|
115
|
+
const wake = nextWake === "at" ? { kind: "at", at: String(a.wakeAt ?? "") } : { kind: nextWake };
|
|
116
|
+
const actionRefs = Array.isArray(a.actionRefs) ? a.actionRefs.map(String) : [];
|
|
117
|
+
const verificationRefs = Array.isArray(a.verificationRefs) ? a.verificationRefs.map(String) : [];
|
|
118
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
119
|
+
const policy = (0, steward_policy_1.readStewardPolicy)(agentRoot);
|
|
120
|
+
let stewardPolicy;
|
|
121
|
+
if (policyKind === "none") {
|
|
122
|
+
if (record.transition !== "opened")
|
|
123
|
+
throw new Error("External event no-policy disposition requires a fresh observation");
|
|
124
|
+
stewardPolicy = { kind: "none" };
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const key = String(a.stewardPolicyKey ?? "").trim();
|
|
128
|
+
if (!key)
|
|
129
|
+
throw new Error("External event disposition is invalid");
|
|
130
|
+
if (policy.version !== policyVersion || (!policy.desiredStates[key] && !policy.routineActionGrants[key])) {
|
|
131
|
+
throw new Error("External event steward policy is not the exact current key/version");
|
|
132
|
+
}
|
|
133
|
+
stewardPolicy = { kind: "current", key, version: policyVersion };
|
|
134
|
+
}
|
|
135
|
+
if (typeof a.careId === "string" && a.careId) {
|
|
136
|
+
const care = (0, cares_1.readCares)(agentRoot).find((candidate) => candidate.id === a.careId);
|
|
137
|
+
const binding = care?.incidentBindings?.find((candidate) => candidate.source === record.source && candidate.incidentKey === record.eventId && candidate.classifiedRevision === classifiedRevision);
|
|
138
|
+
if (!care || !binding)
|
|
139
|
+
throw new Error("External event Care does not belong to this agent and incident revision");
|
|
140
|
+
}
|
|
141
|
+
if (classification === "adopted" && !(typeof a.careId === "string" && a.careId)) {
|
|
142
|
+
throw new Error("External event adopted disposition requires a Care");
|
|
143
|
+
}
|
|
144
|
+
if (nextWake === "at") {
|
|
145
|
+
const awaitId = typeof a.awaitId === "string" ? a.awaitId : "";
|
|
146
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(awaitId))
|
|
147
|
+
throw new Error("External event timed disposition requires a current pending Await");
|
|
148
|
+
const awaitPath = path.join(agentRoot, "awaiting", `${awaitId}.md`);
|
|
149
|
+
let pendingAwait;
|
|
150
|
+
try {
|
|
151
|
+
const stat = fs.lstatSync(awaitPath);
|
|
152
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
153
|
+
throw new Error("unsafe Await");
|
|
154
|
+
pendingAwait = (0, await_parser_1.parseAwaitFile)(fs.readFileSync(awaitPath, "utf8"), awaitPath);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new Error("External event timed disposition requires a current pending Await");
|
|
158
|
+
}
|
|
159
|
+
const expectedOwnerId = ctx?.context?.friend.id;
|
|
160
|
+
if (pendingAwait.filed_from !== "external-event" || pendingAwait.filed_from_key !== record.recordPath
|
|
161
|
+
|| (expectedOwnerId && pendingAwait.filed_for_friend_id !== expectedOwnerId)) {
|
|
162
|
+
throw new Error("External event timed disposition Await is not owned by this exact external event");
|
|
163
|
+
}
|
|
164
|
+
if (pendingAwait.status !== "pending" || pendingAwait.wake_at !== String(a.wakeAt)) {
|
|
165
|
+
throw new Error("External event timed disposition Await does not match the exact wake time");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const authority = ctx?.externalEventAuthority?.authorizeDisposition({
|
|
169
|
+
event: turnEvent,
|
|
170
|
+
classification,
|
|
171
|
+
decision,
|
|
172
|
+
stewardPolicy,
|
|
173
|
+
nextWake,
|
|
174
|
+
wakeAt: nextWake === "at" ? String(a.wakeAt) : null,
|
|
175
|
+
awaitId: typeof a.awaitId === "string" && a.awaitId ? a.awaitId : null,
|
|
176
|
+
careId: typeof a.careId === "string" && a.careId ? a.careId : null,
|
|
177
|
+
actionRefs,
|
|
178
|
+
verificationRefs,
|
|
179
|
+
});
|
|
180
|
+
if (!authority?.allowed)
|
|
181
|
+
throw new Error(`External event disposition authority denied: ${authority?.reason ?? "authority unavailable"}`);
|
|
182
|
+
const finish = async () => {
|
|
183
|
+
if (decision === "ask" || decision === "report") {
|
|
184
|
+
if (Buffer.byteLength(reason, "utf8") > 1_200)
|
|
185
|
+
throw new Error("External event owner message must be phone-sized");
|
|
186
|
+
if (!ctx?.externalEventEffects)
|
|
187
|
+
throw new Error("External event owner delivery is unavailable");
|
|
188
|
+
await ctx.externalEventEffects.deliverOwnerDecision({ source: record.source, eventId: record.eventId, generation: record.generation, text: reason });
|
|
189
|
+
}
|
|
190
|
+
const handled = (0, router_1.commitExternalEventDisposition)(recordPath, {
|
|
191
|
+
owner: turnEvent.claimOwner,
|
|
192
|
+
expectedVersion: record.version,
|
|
193
|
+
expectedGeneration: record.generation,
|
|
194
|
+
disposition: {
|
|
195
|
+
classifiedRevision,
|
|
196
|
+
classification: classification,
|
|
197
|
+
stewardPolicy,
|
|
198
|
+
decision: decision,
|
|
199
|
+
reason,
|
|
200
|
+
nextWake: wake,
|
|
201
|
+
careId: typeof a.careId === "string" && a.careId ? a.careId : null,
|
|
202
|
+
awaitId: typeof a.awaitId === "string" && a.awaitId ? a.awaitId : null,
|
|
203
|
+
actionRefs,
|
|
204
|
+
verificationRefs,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
(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
|
+
return JSON.stringify(handled, null, 2);
|
|
209
|
+
};
|
|
210
|
+
return finish();
|
|
211
|
+
},
|
|
212
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "records the agent's classification on an existing external-event receipt" },
|
|
213
|
+
},
|
|
12
214
|
{
|
|
13
215
|
tool: {
|
|
14
216
|
type: "function",
|
|
@@ -115,17 +317,24 @@ exports.continuityToolDefinitions = [
|
|
|
115
317
|
type: "function",
|
|
116
318
|
function: {
|
|
117
319
|
name: "care_manage",
|
|
118
|
-
description: "Create, update, or resolve a care. Cares are things I watch over — people, projects, missions, system health.",
|
|
320
|
+
description: "Create, update, or resolve a care, or bind and resolve one machine incident within it. Cares are things I watch over — people, projects, missions, system health.",
|
|
119
321
|
parameters: {
|
|
120
322
|
type: "object",
|
|
121
323
|
properties: {
|
|
122
|
-
action: { type: "string", enum: ["create", "update", "resolve"], description: "What to do" },
|
|
324
|
+
action: { type: "string", enum: ["create", "update", "resolve", "bind_incident", "resolve_incident", "upsert_incident"], description: "What to do" },
|
|
123
325
|
id: { type: "string", description: "Care ID (required for update/resolve)" },
|
|
124
326
|
label: { type: "string", description: "Short label for the care" },
|
|
125
327
|
why: { type: "string", description: "Why this matters" },
|
|
126
328
|
salience: { type: "string", description: "low, medium, high, or critical" },
|
|
127
329
|
kind: { type: "string", description: "person, agent, project, mission, or system" },
|
|
128
330
|
stewardship: { type: "string", description: "mine, shared, or delegated" },
|
|
331
|
+
source: { type: "string", description: "Machine evidence source for an incident binding" },
|
|
332
|
+
incidentKey: { type: "string", description: "Stable incident key within the source" },
|
|
333
|
+
classifiedRevision: { type: "string", description: "Observation revision classified by the agent" },
|
|
334
|
+
correlationKey: { type: "string", description: "Optional key relating several incidents to one concern" },
|
|
335
|
+
expectedUpdatedAt: { type: "string", description: "Optional Care updatedAt value for CAS fencing" },
|
|
336
|
+
currentRisk: { type: "string", description: "Current material risk for this Care, or an empty string to clear it" },
|
|
337
|
+
nextCheckAt: { type: "string", description: "Exact ISO time of the next owned check" },
|
|
129
338
|
},
|
|
130
339
|
required: ["action"],
|
|
131
340
|
},
|
|
@@ -146,8 +355,8 @@ exports.continuityToolDefinitions = [
|
|
|
146
355
|
relatedAgentIds: [],
|
|
147
356
|
relatedObligationIds: [],
|
|
148
357
|
relatedEpisodeIds: [],
|
|
149
|
-
currentRisk: null,
|
|
150
|
-
nextCheckAt: null,
|
|
358
|
+
currentRisk: a.currentRisk ? String(a.currentRisk) : null,
|
|
359
|
+
nextCheckAt: a.nextCheckAt ? String(a.nextCheckAt) : null,
|
|
151
360
|
});
|
|
152
361
|
}
|
|
153
362
|
else if (a.action === "update") {
|
|
@@ -158,11 +367,57 @@ exports.continuityToolDefinitions = [
|
|
|
158
367
|
updates.why = a.why;
|
|
159
368
|
if (a.salience)
|
|
160
369
|
updates.salience = a.salience;
|
|
370
|
+
if (a.currentRisk !== undefined)
|
|
371
|
+
updates.currentRisk = a.currentRisk || null;
|
|
372
|
+
if (a.nextCheckAt !== undefined)
|
|
373
|
+
updates.nextCheckAt = a.nextCheckAt || null;
|
|
161
374
|
result = (0, cares_1.updateCare)(agentRoot, a.id, updates);
|
|
162
375
|
}
|
|
163
376
|
else if (a.action === "resolve") {
|
|
164
377
|
result = (0, cares_1.resolveCare)(agentRoot, a.id);
|
|
165
378
|
}
|
|
379
|
+
else if (a.action === "bind_incident") {
|
|
380
|
+
if (!a.expectedUpdatedAt)
|
|
381
|
+
throw new Error("Care incident binding requires expectedUpdatedAt");
|
|
382
|
+
result = (0, cares_1.bindCareIncident)(agentRoot, a.id, {
|
|
383
|
+
source: a.source,
|
|
384
|
+
incidentKey: a.incidentKey,
|
|
385
|
+
classifiedRevision: a.classifiedRevision,
|
|
386
|
+
...(a.correlationKey ? { correlationKey: a.correlationKey } : {}),
|
|
387
|
+
}, { expectedUpdatedAt: a.expectedUpdatedAt });
|
|
388
|
+
}
|
|
389
|
+
else if (a.action === "resolve_incident") {
|
|
390
|
+
if (!a.expectedUpdatedAt)
|
|
391
|
+
throw new Error("Care incident resolution requires expectedUpdatedAt");
|
|
392
|
+
result = (0, cares_1.resolveCareIncident)(agentRoot, a.id, {
|
|
393
|
+
source: a.source,
|
|
394
|
+
incidentKey: a.incidentKey,
|
|
395
|
+
expectedUpdatedAt: a.expectedUpdatedAt,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
else if (a.action === "upsert_incident") {
|
|
399
|
+
result = (0, cares_1.upsertCareForIncident)(agentRoot, {
|
|
400
|
+
label: a.label ?? "untitled",
|
|
401
|
+
why: a.why ?? "",
|
|
402
|
+
kind: a.kind ?? "system",
|
|
403
|
+
status: "active",
|
|
404
|
+
salience: a.salience ?? "medium",
|
|
405
|
+
steward: a.stewardship ?? "mine",
|
|
406
|
+
relatedFriendIds: [],
|
|
407
|
+
relatedAgentIds: [],
|
|
408
|
+
relatedObligationIds: [],
|
|
409
|
+
relatedEpisodeIds: [],
|
|
410
|
+
currentRisk: a.currentRisk ? String(a.currentRisk) : null,
|
|
411
|
+
nextCheckAt: a.nextCheckAt ? String(a.nextCheckAt) : null,
|
|
412
|
+
...(a.expectedUpdatedAt ? { expectedUpdatedAt: String(a.expectedUpdatedAt) } : {}),
|
|
413
|
+
incident: {
|
|
414
|
+
source: a.source,
|
|
415
|
+
incidentKey: a.incidentKey,
|
|
416
|
+
classifiedRevision: a.classifiedRevision,
|
|
417
|
+
...(a.correlationKey ? { correlationKey: a.correlationKey } : {}),
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
166
421
|
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.care_manage", message: `care ${a.action}`, meta: { action: a.action, id: a.id } });
|
|
167
422
|
return JSON.stringify(result, null, 2);
|
|
168
423
|
},
|
|
@@ -44,6 +44,7 @@ const friends_1 = require("@ouro.bot/friends");
|
|
|
44
44
|
const diary_1 = require("../mind/diary");
|
|
45
45
|
const record_paths_1 = require("../mind/record-paths");
|
|
46
46
|
const provenance_trust_1 = require("../mind/provenance-trust");
|
|
47
|
+
const relationship_authorization_1 = require("./relationship-authorization");
|
|
47
48
|
const CLAUDE_READ_ONLY_TOOLS = "Read,Grep,Glob,LS";
|
|
48
49
|
function bundleRelativeLocator(filePath, ctx) {
|
|
49
50
|
const agentRoot = ctx?.agentRoot ?? (0, identity_1.getAgentRoot)();
|
|
@@ -386,12 +387,13 @@ exports.notesToolDefinitions = [
|
|
|
386
387
|
type: "function",
|
|
387
388
|
function: {
|
|
388
389
|
name: "save_friend_note",
|
|
389
|
-
description: "save something i learned about my friend. use type 'name' to update their display name, 'tool_preference' for
|
|
390
|
+
description: "save something i learned about my friend. use type 'name' to update their display name, 'tool_preference' for presentation preferences, or 'note' for general context. For Sanctuary relationships, preference source is stated (the person said or confirmed it), observed (a revisable pattern i inferred), or default (a fallback until better evidence), and only communication or timing are accepted. Other agents retain their existing integration-preference behavior and do not persist this provenance field. desired state and action authority belong in steward policy. notes and preferences improve orientation but never grant authority. set override=true when replacing an existing value.",
|
|
390
391
|
parameters: {
|
|
391
392
|
type: "object",
|
|
392
393
|
properties: {
|
|
393
394
|
type: { type: "string", enum: ["name", "tool_preference", "note"], description: "what kind of information to save" },
|
|
394
|
-
key: { type: "string", description: "category key (required for tool_preference and note
|
|
395
|
+
key: { type: "string", description: "category key (required for tool_preference and note; Sanctuary relationship preferences use communication or timing)" },
|
|
396
|
+
source: { type: "string", enum: ["stated", "observed", "default"], description: "preference provenance; required for Sanctuary relationship preferences" },
|
|
395
397
|
content: { type: "string", description: "the value to save" },
|
|
396
398
|
override: { type: "string", enum: ["true", "false"], description: "set to 'true' to overwrite an existing value" },
|
|
397
399
|
},
|
|
@@ -415,6 +417,18 @@ exports.notesToolDefinitions = [
|
|
|
415
417
|
const friendId = ctx.context.friend?.id;
|
|
416
418
|
if (!friendId)
|
|
417
419
|
return "i can't save notes -- no friend identity available";
|
|
420
|
+
const actor = ctx.relationshipAuthorization?.actor;
|
|
421
|
+
const isSanctuaryRelationship = ctx.context.friend?.capabilityProfileId?.startsWith("sanctuary-") === true;
|
|
422
|
+
const preferenceSource = a.source;
|
|
423
|
+
if (isSanctuaryRelationship && a.type === "tool_preference" && !["stated", "observed", "default"].includes(String(preferenceSource))) {
|
|
424
|
+
return "i need exact preference provenance: source must be stated, observed, or default";
|
|
425
|
+
}
|
|
426
|
+
if (isSanctuaryRelationship && a.type === "tool_preference" && !["communication", "timing"].includes(a.key ?? "")) {
|
|
427
|
+
return "Sanctuary relationship preferences are limited to communication or timing; desired state and action authority belong in steward policy";
|
|
428
|
+
}
|
|
429
|
+
if (actor?.trustLevel === "friend" && (actor.friendId !== friendId || a.type !== "tool_preference" || !["communication", "timing"].includes(a.key ?? ""))) {
|
|
430
|
+
return "household members may only save their own communication or timing preferences; preferences never grant authority";
|
|
431
|
+
}
|
|
418
432
|
// Validate parameters (input-shape guards stay harness-side so we keep the
|
|
419
433
|
// first-person tool wording; applyFriendNote does the record mutation).
|
|
420
434
|
if (!a.content)
|
|
@@ -426,9 +440,21 @@ exports.notesToolDefinitions = [
|
|
|
426
440
|
return "i need a key for tool_preference or note type";
|
|
427
441
|
// Record-domain logic lives in @ouro.bot/friends; this wrapper maps the
|
|
428
442
|
// structured FriendOpResult back to the tool's first-person strings.
|
|
443
|
+
if (isSanctuaryRelationship && a.type === "tool_preference" && preferenceSource) {
|
|
444
|
+
const current = await ctx.friendStore.get(friendId);
|
|
445
|
+
if (!current)
|
|
446
|
+
return "i can't find the friend record on disk";
|
|
447
|
+
if (current.relationshipPolicy?.preferences[a.key] && a.override !== "true")
|
|
448
|
+
return `relationship preference '${a.key}' already exists; call again with override: true to replace it`;
|
|
449
|
+
const sourceKind = preferenceSource === "observed" ? "observed pattern" : preferenceSource === "default" ? "default fallback" : "explicit turn";
|
|
450
|
+
const turnSource = `${ctx.currentSession?.channel ?? "relationship"} ${sourceKind} ${actor?.sessionEventId ?? "unknown-event"}`;
|
|
451
|
+
await ctx.friendStore.put(friendId, (0, relationship_authorization_1.withRelationshipPreference)(current, { category: a.key, value: a.content, provenance: preferenceSource, source: turnSource }));
|
|
452
|
+
return `saved preference: source=${turnSource}; provenance=${preferenceSource}; category=${a.key}; value=${a.content}; this changes presentation only and grants no authority`;
|
|
453
|
+
}
|
|
454
|
+
const storedKey = a.key;
|
|
429
455
|
const result = await (0, friends_1.applyFriendNote)(ctx.friendStore, friendId, {
|
|
430
456
|
type: a.type,
|
|
431
|
-
...(
|
|
457
|
+
...(storedKey !== undefined ? { key: storedKey } : {}),
|
|
432
458
|
content: a.content,
|
|
433
459
|
override: a.override === "true",
|
|
434
460
|
});
|