@ouro.bot/cli 0.1.0-alpha.816 → 0.1.0-alpha.818
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/assets/sanctuary-host-launcher.sh +16 -0
- package/changelog.json +16 -0
- package/deploy/unraid/Dockerfile +6 -0
- package/deploy/unraid/README.txt +296 -62
- package/deploy/unraid/docker-man-template-transaction.mjs +192 -7
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +1 -1
- package/deploy/unraid/sanctuary-acceptance-contract.json +7 -7
- package/deploy/unraid/sanctuary-authority-installation.json +36 -0
- package/deploy/unraid/sanctuary-authority-service.sh +30 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +62 -9
- package/deploy/unraid/sanctuary-unit16-run.sh +15 -15
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +2 -1
- package/dist/heart/core.js +2 -1
- package/dist/heart/daemon/container-spec-auditor-main.js +8 -7
- package/dist/heart/daemon/container-spec-auditor.js +16 -9
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
- package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
- package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
- package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
- package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
- package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
- package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
- package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
- package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
- package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
- package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
- package/dist/heart/daemon/sense-manager.js +20 -10
- package/dist/repertoire/tools-sanctuary-host.js +202 -0
- package/dist/repertoire/tools.js +15 -6
- package/dist/senses/private-runtime.js +2 -2
- package/dist/senses/root-host-approval-port.js +345 -0
- package/dist/senses/root-host-approval-runtime.js +393 -0
- package/dist/senses/sanctuary-authority-resident.js +102 -0
- package/dist/senses/telegram-admission.js +7 -0
- package/dist/senses/telegram-attachments.js +5 -1
- package/dist/senses/telegram-authority-transport.js +231 -0
- package/dist/senses/telegram-client.js +31 -8
- package/dist/senses/telegram-entry.js +1 -1
- package/dist/senses/telegram.js +168 -28
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,393 @@
|
|
|
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.FileRootHostPendingApprovalStore = void 0;
|
|
37
|
+
exports.classifyRootHostPendingApproval = classifyRootHostPendingApproval;
|
|
38
|
+
exports.createRootHostApprovalRuntime = createRootHostApprovalRuntime;
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const node_fs_1 = require("node:fs");
|
|
41
|
+
const node_crypto_1 = require("node:crypto");
|
|
42
|
+
const node_util_1 = require("node:util");
|
|
43
|
+
const friends_1 = require("@ouro.bot/friends");
|
|
44
|
+
const core_1 = require("../heart/core");
|
|
45
|
+
const tool_approval_1 = require("../heart/tool-approval");
|
|
46
|
+
const context_1 = require("../mind/context");
|
|
47
|
+
const session_transaction_1 = require("../mind/session-transaction");
|
|
48
|
+
const runtime_1 = require("../nerves/runtime");
|
|
49
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
50
|
+
const tool_arguments_1 = require("../repertoire/tool-arguments");
|
|
51
|
+
const tools_1 = require("../repertoire/tools");
|
|
52
|
+
const tools_sanctuary_host_1 = require("../repertoire/tools-sanctuary-host");
|
|
53
|
+
const shared_turn_1 = require("./shared-turn");
|
|
54
|
+
class RootHostPendingApprovalError extends Error {
|
|
55
|
+
}
|
|
56
|
+
function requireValid(condition) {
|
|
57
|
+
if (!condition)
|
|
58
|
+
throw new RootHostPendingApprovalError("Root host pending approval is invalid");
|
|
59
|
+
}
|
|
60
|
+
function exact(value, keys) {
|
|
61
|
+
requireValid(value !== null && typeof value === "object" && !Array.isArray(value));
|
|
62
|
+
requireValid((0, node_util_1.isDeepStrictEqual)(Object.keys(value).sort(), keys.sort()));
|
|
63
|
+
}
|
|
64
|
+
function validate(value) {
|
|
65
|
+
exact(value, ["schemaVersion", "approvalId", "registration", "binding", "checkpoint", "executionRequested", "continuationState", "materializedSessionRevision", "rootStatusDigest", "reconciliationRequired"]);
|
|
66
|
+
requireValid(value.schemaVersion === 1 && typeof value.approvalId === "string" && /^root-host-[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u.test(value.approvalId));
|
|
67
|
+
requireValid(typeof value.executionRequested === "boolean" && typeof value.reconciliationRequired === "boolean"
|
|
68
|
+
&& typeof value.continuationState === "string" && ["pending", "materialized", "attempted", "complete"].includes(value.continuationState));
|
|
69
|
+
requireValid(value.rootStatusDigest === null || (typeof value.rootStatusDigest === "string" && /^sha256:[a-f0-9]{64}$/u.test(value.rootStatusDigest)));
|
|
70
|
+
requireValid(value.materializedSessionRevision === null || (typeof value.materializedSessionRevision === "string" && /^[a-f0-9]{64}$/u.test(value.materializedSessionRevision)));
|
|
71
|
+
requireValid((value.continuationState !== "materialized" && value.continuationState !== "attempted") || value.materializedSessionRevision !== null);
|
|
72
|
+
requireValid(value.continuationState !== "pending" || value.materializedSessionRevision === null);
|
|
73
|
+
exact(value.registration, ["registration", "registrationId", "telegramMessageId", "expiresAt"]);
|
|
74
|
+
requireValid(typeof value.registration.registrationId === "string" && /^hostreg-[A-Za-z0-9_-]{43}$/u.test(value.registration.registrationId)
|
|
75
|
+
&& Number.isSafeInteger(value.registration.telegramMessageId) && value.registration.telegramMessageId > 0
|
|
76
|
+
&& typeof value.registration.expiresAt === "string" && Number.isFinite(Date.parse(value.registration.expiresAt)));
|
|
77
|
+
exact(value.registration.registration, ["schemaVersion", "domain", "keyId", "payload", "signature"]);
|
|
78
|
+
const artifact = value.registration.registration;
|
|
79
|
+
requireValid(artifact.schemaVersion === 1 && artifact.domain === "ouro.sanctuary.host-registration.v1"
|
|
80
|
+
&& typeof artifact.keyId === "string" && artifact.keyId.length > 0 && typeof artifact.signature === "string" && artifact.signature.length > 0
|
|
81
|
+
&& artifact.payload !== null && typeof artifact.payload === "object" && !Array.isArray(artifact.payload));
|
|
82
|
+
exact(value.binding, ["agentRoot", "friendId", "requestId", "sessionKey", "sessionPath", "sessionEventId", "profileId", "profileVersion", "keyId", "publicKeyDigest", "targetHost"]);
|
|
83
|
+
const binding = value.binding;
|
|
84
|
+
requireValid(binding.profileId === "sanctuary-owner" && Number.isSafeInteger(binding.profileVersion) && binding.profileVersion >= 9);
|
|
85
|
+
requireValid(Object.entries(binding).every(([key, item]) => key === "profileVersion" || (typeof item === "string" && item.length > 0 && item.length <= 4096)));
|
|
86
|
+
requireValid(/^sha256:[a-f0-9]{64}$/u.test(binding.publicKeyDigest));
|
|
87
|
+
validateCheckpoint(value.checkpoint, value.approvalId, binding.targetHost);
|
|
88
|
+
}
|
|
89
|
+
function validateCheckpoint(value, approvalId, targetHost) {
|
|
90
|
+
exact(value, ["approvalId", "checkpointDigest", "baseSessionRevision", "suspendedSessionRevision", "argumentDigest", "schemaDigest", "toolDigest", "policyDigest", "preCallDigest", "preCallMessages", "frozenAssistantMessage"]);
|
|
91
|
+
const c = value;
|
|
92
|
+
requireValid(c.approvalId === approvalId && Array.isArray(c.preCallMessages));
|
|
93
|
+
requireValid(c.preCallMessages.every((message) => message !== null && typeof message === "object"
|
|
94
|
+
&& ["system", "developer", "user", "assistant", "tool", "function"].includes(message.role)));
|
|
95
|
+
requireValid(["checkpointDigest", "baseSessionRevision", "suspendedSessionRevision", "argumentDigest", "schemaDigest", "toolDigest", "policyDigest", "preCallDigest"]
|
|
96
|
+
.every((key) => typeof c[key] === "string" && /^[a-f0-9]{64}$/u.test(String(c[key]))));
|
|
97
|
+
requireValid(c.preCallDigest === (0, tool_arguments_1.digestJson)(c.preCallMessages) && c.checkpointDigest === (0, tool_approval_1.digestApprovalSuspensionCheckpointPayload)(c));
|
|
98
|
+
requireValid(c.suspendedSessionRevision === c.baseSessionRevision);
|
|
99
|
+
requireValid(c.frozenAssistantMessage !== null && typeof c.frozenAssistantMessage === "object" && !Array.isArray(c.frozenAssistantMessage));
|
|
100
|
+
const calls = c.frozenAssistantMessage.tool_calls;
|
|
101
|
+
requireValid(Array.isArray(calls) && calls.length === 1);
|
|
102
|
+
exact(calls[0], ["id", "type", "function"]);
|
|
103
|
+
requireValid(c.frozenAssistantMessage.role === "assistant" && calls[0].type === "function" && typeof calls[0].id === "string" && calls[0].id.length > 0);
|
|
104
|
+
exact(calls[0].function, ["name", "arguments"]);
|
|
105
|
+
requireValid(calls[0].function.name === "sanctuary_host_execute" && typeof calls[0].function.arguments === "string");
|
|
106
|
+
const args = JSON.parse(calls[0].function.arguments);
|
|
107
|
+
requireValid((0, tool_arguments_1.digestJson)(args) === c.argumentDigest);
|
|
108
|
+
(0, tools_sanctuary_host_1.validateRootHostToolArguments)(args, targetHost);
|
|
109
|
+
}
|
|
110
|
+
function checkDefinition(checkpoint, definition) {
|
|
111
|
+
const args = JSON.parse(checkpoint.frozenAssistantMessage.tool_calls[0].function.arguments);
|
|
112
|
+
const policy = definition.approvalPolicy?.(args);
|
|
113
|
+
requireValid(policy?.kind === "required");
|
|
114
|
+
requireValid(checkpoint.schemaDigest === (0, tool_arguments_1.digestJson)(definition.tool.function.parameters));
|
|
115
|
+
requireValid(checkpoint.toolDigest === (0, tool_approval_1.digestApprovalToolDefinition)(definition, checkpoint.schemaDigest, policy.policyId));
|
|
116
|
+
requireValid(checkpoint.policyDigest === (0, tool_arguments_1.digestJson)({ policyId: policy.policyId, actionClass: policy.actionClass, classification: "required" }));
|
|
117
|
+
return policy;
|
|
118
|
+
}
|
|
119
|
+
function classifyRootHostPendingApproval(value) {
|
|
120
|
+
validate(value);
|
|
121
|
+
if (value.continuationState !== "pending")
|
|
122
|
+
return value.continuationState;
|
|
123
|
+
if (value.reconciliationRequired)
|
|
124
|
+
return "reconciliation_required";
|
|
125
|
+
return value.executionRequested ? "executing" : "pending";
|
|
126
|
+
}
|
|
127
|
+
class FileRootHostPendingApprovalStore {
|
|
128
|
+
filePath;
|
|
129
|
+
constructor(agentRoot) { this.filePath = path.join(agentRoot, "state", "root-host-approvals", "v1", "pending.json"); }
|
|
130
|
+
list() {
|
|
131
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(this.filePath, (lease) => {
|
|
132
|
+
const transaction = (0, session_transaction_1.readSessionTransaction)(this.filePath, lease);
|
|
133
|
+
if (!transaction.bytes) {
|
|
134
|
+
requireValid(!(0, node_fs_1.existsSync)(this.filePath));
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
exact(transaction.value, ["schemaVersion", "records"]);
|
|
138
|
+
requireValid(transaction.value.schemaVersion === 1 && Array.isArray(transaction.value.records));
|
|
139
|
+
const records = transaction.value.records;
|
|
140
|
+
records.forEach(validate);
|
|
141
|
+
requireValid(new Set(records.map((record) => record.approvalId)).size === records.length
|
|
142
|
+
&& new Set(records.map((record) => record.registration.registrationId)).size === records.length
|
|
143
|
+
&& new Set(records.map((record) => record.registration.telegramMessageId)).size === records.length);
|
|
144
|
+
return structuredClone(records);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
put(record) {
|
|
148
|
+
validate(record);
|
|
149
|
+
(0, session_transaction_1.withImmediateSessionTurnLease)(this.filePath, (lease) => {
|
|
150
|
+
const revision = (0, session_transaction_1.readSessionTransaction)(this.filePath, lease).revision;
|
|
151
|
+
const records = this.list();
|
|
152
|
+
const previous = records.find((item) => item.approvalId === record.approvalId);
|
|
153
|
+
if (previous) {
|
|
154
|
+
requireValid((0, node_util_1.isDeepStrictEqual)(previous.binding, record.binding) && (0, node_util_1.isDeepStrictEqual)(previous.registration, record.registration)
|
|
155
|
+
&& (0, node_util_1.isDeepStrictEqual)(previous.checkpoint, record.checkpoint));
|
|
156
|
+
const phases = ["pending", "materialized", "attempted", "complete"];
|
|
157
|
+
requireValid(phases.indexOf(record.continuationState) >= phases.indexOf(previous.continuationState)
|
|
158
|
+
&& (!previous.executionRequested || record.executionRequested)
|
|
159
|
+
&& (previous.materializedSessionRevision === null || previous.materializedSessionRevision === record.materializedSessionRevision));
|
|
160
|
+
Object.assign(previous, structuredClone(record));
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
requireValid(!records.some((item) => item.registration.registrationId === record.registration.registrationId
|
|
164
|
+
|| item.registration.telegramMessageId === record.registration.telegramMessageId));
|
|
165
|
+
records.push(structuredClone(record));
|
|
166
|
+
}
|
|
167
|
+
(0, session_transaction_1.writeSessionTransaction)(this.filePath, { schemaVersion: 1, records }, { lease, expectedRevision: revision });
|
|
168
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_pending_saved", message: "root host continuation checkpoint saved", meta: { approvalId: record.approvalId, state: classifyRootHostPendingApproval(record) } });
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.FileRootHostPendingApprovalStore = FileRootHostPendingApprovalStore;
|
|
173
|
+
function terminalProjection(record, status) {
|
|
174
|
+
const call = record.checkpoint.frozenAssistantMessage.tool_calls[0];
|
|
175
|
+
const common = { approvalId: record.approvalId, toolCallId: call.id };
|
|
176
|
+
if (status.state === "denied" || status.state === "expired")
|
|
177
|
+
return { ...common, state: status.state, result: null };
|
|
178
|
+
if (status.state !== "executed")
|
|
179
|
+
return null;
|
|
180
|
+
requireValid(status.receipt && status.permit);
|
|
181
|
+
const state = status.receipt.payload.state;
|
|
182
|
+
requireValid(state === "verified" || state === "failed" || state === "ambiguous");
|
|
183
|
+
requireValid(status.receipt.payload.cleanup === "cgroup_empty");
|
|
184
|
+
return { ...common, state: state === "verified" ? "succeeded" : state === "failed" ? "failed" : "attempted_indeterminate", result: JSON.stringify(status.receipt) };
|
|
185
|
+
}
|
|
186
|
+
function correlation(record) {
|
|
187
|
+
const b = record.binding;
|
|
188
|
+
return {
|
|
189
|
+
registrationId: record.registration.registrationId, residentFriendId: b.friendId,
|
|
190
|
+
relationshipProfileId: b.profileId, relationshipProfileVersion: b.profileVersion, requestId: b.requestId,
|
|
191
|
+
sessionKey: b.sessionKey, sessionEventId: b.sessionEventId, residentApprovalId: record.approvalId, stewardPolicy: null,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function createRootHostApprovalRuntime(options) {
|
|
195
|
+
const store = new FileRootHostPendingApprovalStore(options.agentRoot);
|
|
196
|
+
const port = options.port;
|
|
197
|
+
const barrier = options.effectBarrier ?? (() => undefined);
|
|
198
|
+
const readStatus = async (record) => {
|
|
199
|
+
const status = await port.status(record.registration);
|
|
200
|
+
if (status.permit)
|
|
201
|
+
requireValid(Object.entries(correlation(record)).every(([key, value]) => (0, node_util_1.isDeepStrictEqual)(status.permit.payload[key], value)));
|
|
202
|
+
return status;
|
|
203
|
+
};
|
|
204
|
+
const current = async (record) => {
|
|
205
|
+
requireValid(record.binding.agentRoot === options.agentRoot);
|
|
206
|
+
if (!await port.refresh())
|
|
207
|
+
throw new Error("Root host authority is unavailable");
|
|
208
|
+
const context = await options.resolveContext(record.binding);
|
|
209
|
+
requireValid(context.rootHost?.port === port);
|
|
210
|
+
await (0, tools_sanctuary_host_1.authorizeRootHostContext)(context, record.binding);
|
|
211
|
+
const owner = { agentName: "sanctuary", agentRoot: options.agentRoot };
|
|
212
|
+
const runtime = await (options.dependencies?.getProviderRuntime ?? core_1.getProviderRuntime)("human", owner);
|
|
213
|
+
const mcp = await (options.dependencies?.getMcp ?? mcp_manager_1.getSharedMcpManager)(owner) ?? undefined;
|
|
214
|
+
if (!port.isHealthy() && !await port.refresh())
|
|
215
|
+
throw new Error("Root host authority is unavailable");
|
|
216
|
+
const select = () => (0, tools_1.selectToolsForChannel)((0, friends_1.getChannelCapabilities)("telegram"), context.context?.friend.toolPreferences, context.context, runtime.capabilities, mcp, runtime.model, context);
|
|
217
|
+
const toolContext = { ...context, toolSelection: select(), selectCurrentTools: select };
|
|
218
|
+
const call = record.checkpoint.frozenAssistantMessage.tool_calls[0];
|
|
219
|
+
requireValid(call.type === "function");
|
|
220
|
+
const args = JSON.parse(call.function.arguments);
|
|
221
|
+
const preflight = await (0, tools_1.preflightToolCall)("sanctuary_host_execute", args, toolContext);
|
|
222
|
+
if (preflight.kind === "rejected_before_handler" && preflight.error && !(preflight.error instanceof tools_sanctuary_host_1.RootHostAuthorizationError))
|
|
223
|
+
throw preflight.error;
|
|
224
|
+
requireValid(preflight.kind === "ready" && preflight.definition);
|
|
225
|
+
checkDefinition(record.checkpoint, preflight.definition);
|
|
226
|
+
const proposed = (0, tools_sanctuary_host_1.validateRootHostToolArguments)(args, record.binding.targetHost);
|
|
227
|
+
for (const [key, value] of Object.entries(proposed))
|
|
228
|
+
requireValid((0, node_util_1.isDeepStrictEqual)(record.registration.registration.payload[key], value));
|
|
229
|
+
return { toolContext, providerRuntimeOverride: runtime, mcpManager: mcp };
|
|
230
|
+
};
|
|
231
|
+
const coordinator = (context) => ({
|
|
232
|
+
async propose(request) {
|
|
233
|
+
barrier();
|
|
234
|
+
const live = request.liveToolContext;
|
|
235
|
+
const { liveToolContext: _live, ...proposal } = request;
|
|
236
|
+
request = structuredClone(proposal);
|
|
237
|
+
requireValid(live?.rootHost?.port === port && request.toolCall.type === "function" && request.toolCall.function.name === "sanctuary_host_execute");
|
|
238
|
+
requireValid(live.currentSession?.sessionPath === context.sessionPath && live.agentRoot === options.agentRoot);
|
|
239
|
+
const binding = await (0, tools_sanctuary_host_1.authorizeRootHostContext)(live);
|
|
240
|
+
const preflight = await (0, tools_1.preflightToolCall)("sanctuary_host_execute", request.arguments, live);
|
|
241
|
+
requireValid(preflight.kind === "ready" && preflight.definition);
|
|
242
|
+
requireValid((0, session_transaction_1.withImmediateSessionTurnLease)(context.sessionPath, (lease) => (0, session_transaction_1.readSessionTransaction)(context.sessionPath, lease).revision) === context.baseSessionRevision);
|
|
243
|
+
const args = (0, tools_sanctuary_host_1.validateRootHostToolArguments)(request.arguments, binding.targetHost);
|
|
244
|
+
const approvalId = `root-host-${(0, node_crypto_1.randomUUID)()}`;
|
|
245
|
+
const draft = {
|
|
246
|
+
approvalId, baseSessionRevision: context.baseSessionRevision, argumentDigest: (0, tool_arguments_1.digestJson)(request.arguments),
|
|
247
|
+
schemaDigest: request.schemaDigest, toolDigest: request.toolDigest, policyDigest: request.policyDigest,
|
|
248
|
+
preCallDigest: (0, tool_arguments_1.digestJson)(request.preCallMessages), preCallMessages: structuredClone(request.preCallMessages),
|
|
249
|
+
frozenAssistantMessage: structuredClone(request.frozenAssistantMessage),
|
|
250
|
+
};
|
|
251
|
+
const checkpoint = { ...draft, suspendedSessionRevision: context.baseSessionRevision, checkpointDigest: (0, tool_approval_1.digestApprovalSuspensionCheckpointPayload)(draft) };
|
|
252
|
+
validateCheckpoint(checkpoint, approvalId, binding.targetHost);
|
|
253
|
+
requireValid((0, node_util_1.isDeepStrictEqual)(request.frozenAssistantMessage.tool_calls[0], request.toolCall));
|
|
254
|
+
const policy = checkDefinition(checkpoint, preflight.definition);
|
|
255
|
+
requireValid(request.policyId === policy.policyId && request.actionClass === policy.actionClass);
|
|
256
|
+
const observation = live.rootHost.observation;
|
|
257
|
+
barrier();
|
|
258
|
+
const registration = await port.register({ ...args, ownerObservation: {
|
|
259
|
+
digest: observation.observationDigest, updateId: observation.updateId, userId: observation.userId, chatId: observation.chatId, messageId: observation.messageId,
|
|
260
|
+
} });
|
|
261
|
+
await (0, tools_sanctuary_host_1.authorizeRootHostContext)(live);
|
|
262
|
+
requireValid((0, session_transaction_1.withImmediateSessionTurnLease)(context.sessionPath, (lease) => (0, session_transaction_1.readSessionTransaction)(context.sessionPath, lease).revision) === context.baseSessionRevision);
|
|
263
|
+
barrier();
|
|
264
|
+
store.put({ schemaVersion: 1, approvalId, registration, binding, checkpoint, executionRequested: false, continuationState: "pending", materializedSessionRevision: null, rootStatusDigest: null, reconciliationRequired: false });
|
|
265
|
+
return { approvalId, checkpointDigest: checkpoint.checkpointDigest, suspendedSessionRevision: checkpoint.suspendedSessionRevision };
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
const reconcile = async (initial) => {
|
|
269
|
+
// Never choose a filesystem path from an unvalidated persisted binding.
|
|
270
|
+
requireValid(initial.binding.agentRoot === options.agentRoot
|
|
271
|
+
&& initial.binding.sessionPath === (0, shared_turn_1.getSenseSessionPath)("sanctuary", initial.binding.friendId, "telegram", initial.binding.sessionKey, options.agentRoot));
|
|
272
|
+
await (0, session_transaction_1.withSessionTurnLease)(initial.binding.sessionPath, async (lease) => {
|
|
273
|
+
const record = store.list().find((item) => item.approvalId === initial.approvalId);
|
|
274
|
+
if (record.continuationState === "complete")
|
|
275
|
+
return;
|
|
276
|
+
barrier();
|
|
277
|
+
let status = await readStatus(record);
|
|
278
|
+
record.rootStatusDigest = status.statusDigest;
|
|
279
|
+
if (status.state === "approved" && !record.executionRequested) {
|
|
280
|
+
await current(record);
|
|
281
|
+
requireValid((0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision === record.checkpoint.suspendedSessionRevision);
|
|
282
|
+
record.executionRequested = true;
|
|
283
|
+
barrier();
|
|
284
|
+
store.put(record);
|
|
285
|
+
try {
|
|
286
|
+
await port.execute(correlation(record));
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
record.reconciliationRequired = true;
|
|
290
|
+
store.put(record);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
status = await readStatus(record);
|
|
294
|
+
record.rootStatusDigest = status.statusDigest;
|
|
295
|
+
}
|
|
296
|
+
record.reconciliationRequired = status.execution === "reconciliation_required" || (record.executionRequested && status.state === "approved");
|
|
297
|
+
barrier();
|
|
298
|
+
store.put(record);
|
|
299
|
+
const terminal = terminalProjection(record, status);
|
|
300
|
+
if (!terminal)
|
|
301
|
+
return;
|
|
302
|
+
const expectedRevision = record.materializedSessionRevision ?? record.checkpoint.suspendedSessionRevision;
|
|
303
|
+
if (record.continuationState === "attempted"
|
|
304
|
+
|| (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision !== expectedRevision) {
|
|
305
|
+
record.continuationState = "complete";
|
|
306
|
+
barrier();
|
|
307
|
+
store.put(record);
|
|
308
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_terminal_control", message: "root outcome retained without retrying an attempted or superseded continuation", meta: { approvalId: record.approvalId } });
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
await current(record);
|
|
313
|
+
requireValid((0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision === expectedRevision);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (!(error instanceof tools_sanctuary_host_1.RootHostAuthorizationError) && !(error instanceof RootHostPendingApprovalError))
|
|
317
|
+
throw error;
|
|
318
|
+
record.continuationState = "complete";
|
|
319
|
+
barrier();
|
|
320
|
+
store.put(record);
|
|
321
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_authority_control", message: "root terminal outcome retained without a continuation because current owner authority changed", meta: { approvalId: record.approvalId, state: terminal.state } });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
let authorized = false;
|
|
325
|
+
const continuationCoordinator = {
|
|
326
|
+
propose: (request) => (options.approvalCoordinatorFactory ?? coordinator)({
|
|
327
|
+
sessionPath: record.binding.sessionPath, baseSessionRevision: (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision,
|
|
328
|
+
}).propose(request),
|
|
329
|
+
};
|
|
330
|
+
await (0, core_1.resumeApprovalContinuation)({
|
|
331
|
+
record: terminal, checkpoint: record.checkpoint, currentSessionRevision: (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision,
|
|
332
|
+
sessionMessages: record.continuationState === "pending" ? record.checkpoint.preCallMessages : (0, context_1.loadSession)(record.binding.sessionPath).messages,
|
|
333
|
+
callbacks: {}, channel: "telegram",
|
|
334
|
+
claimContinuation: () => ({ claimed: true, interruptedAfterAttempt: false, record: { continuationState: record.continuationState === "materialized" ? "materialized" : "claimed" } }),
|
|
335
|
+
markContinuationMaterialized: () => { barrier(); record.materializedSessionRevision = (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision; record.continuationState = "materialized"; store.put(record); },
|
|
336
|
+
markContinuationAttempted: () => { barrier(); record.continuationState = "attempted"; store.put(record); },
|
|
337
|
+
completeContinuation: () => { barrier(); record.continuationState = "complete"; store.put(record); },
|
|
338
|
+
runAgent: options.dependencies?.runProvider ?? core_1.runAgent,
|
|
339
|
+
revalidate: async () => {
|
|
340
|
+
try {
|
|
341
|
+
const live = await current(record);
|
|
342
|
+
requireValid((0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision === record.materializedSessionRevision);
|
|
343
|
+
authorized = true;
|
|
344
|
+
return { ...live, approvalCoordinator: continuationCoordinator };
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
if (!(error instanceof tools_sanctuary_host_1.RootHostAuthorizationError) && !(error instanceof RootHostPendingApprovalError))
|
|
348
|
+
throw error;
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
persist: (messages, result) => { barrier(); (0, context_1.saveSession)(record.binding.sessionPath, messages, result?.usage, undefined, lease); },
|
|
353
|
+
deliver: async (text) => {
|
|
354
|
+
barrier();
|
|
355
|
+
if (authorized)
|
|
356
|
+
await options.deliver(text, record.approvalId);
|
|
357
|
+
else
|
|
358
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_terminal_control", message: "root owns the terminal host approval card; no unauthorized resident message was sent", meta: { approvalId: record.approvalId, state: terminal.state } });
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
};
|
|
363
|
+
const recover = async (registrationId) => {
|
|
364
|
+
for (const record of store.list()) {
|
|
365
|
+
if (record.continuationState === "complete" || (registrationId && registrationId !== record.registration.registrationId))
|
|
366
|
+
continue;
|
|
367
|
+
try {
|
|
368
|
+
await reconcile(record);
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.root_host_reconciliation_required", message: "root host continuation remains pending reconciliation", meta: { approvalId: record.approvalId, category: error instanceof Error ? error.name : "unknown" } });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
return {
|
|
376
|
+
coordinator,
|
|
377
|
+
recover: () => recover(),
|
|
378
|
+
async handleUpdate(update) {
|
|
379
|
+
const callback = port.callbackForUpdate(update);
|
|
380
|
+
if (!callback.handled)
|
|
381
|
+
return false;
|
|
382
|
+
if (callback.registrationId) {
|
|
383
|
+
try {
|
|
384
|
+
await recover(callback.registrationId);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.root_host_reconciliation_required", message: "claimed root callback retained despite unavailable local pending state", meta: { registrationId: callback.registrationId } });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
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.openSanctuaryResidentAuthority = openSanctuaryResidentAuthority;
|
|
37
|
+
const node_crypto_1 = require("node:crypto");
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const sanctuary_telegram_authority_service_1 = require("../heart/daemon/sanctuary-telegram-authority-service");
|
|
41
|
+
const sanctuary_authority_codec_1 = require("../heart/daemon/sanctuary-authority-codec");
|
|
42
|
+
const runtime_1 = require("../nerves/runtime");
|
|
43
|
+
const telegram_authority_transport_1 = require("./telegram-authority-transport");
|
|
44
|
+
function openSanctuaryResidentAuthority(runtime, machineRuntime, options = {}) {
|
|
45
|
+
if ([runtime, machineRuntime].some((config) => Object.prototype.hasOwnProperty.call(config, "telegramBotToken"))) {
|
|
46
|
+
throw new Error("Sanctuary resident Telegram token residue must be removed by the root migration");
|
|
47
|
+
}
|
|
48
|
+
const configPath = options.configPath ?? "/run/ouro-authority/resident.json";
|
|
49
|
+
const expectedUid = options.expectedUid ?? 0;
|
|
50
|
+
const expectedGid = options.expectedGid ?? 10001;
|
|
51
|
+
const root = path.dirname(configPath);
|
|
52
|
+
const parent = fs.lstatSync(root);
|
|
53
|
+
if (!parent.isDirectory() || parent.uid !== expectedUid || parent.gid !== expectedGid || (parent.mode & 0o7777) !== 0o750 || fs.realpathSync(root) !== root) {
|
|
54
|
+
throw new Error("Sanctuary resident authority directory is unsafe");
|
|
55
|
+
}
|
|
56
|
+
const descriptor = fs.openSync(configPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
57
|
+
let value;
|
|
58
|
+
try {
|
|
59
|
+
const stat = fs.fstatSync(descriptor);
|
|
60
|
+
if (!stat.isFile() || stat.uid !== expectedUid || stat.gid !== expectedGid || (stat.mode & 0o7777) !== 0o640 || stat.nlink !== 1 || stat.size > 8192)
|
|
61
|
+
throw new Error("Sanctuary resident authority pins are unsafe");
|
|
62
|
+
value = JSON.parse(fs.readFileSync(descriptor, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
fs.closeSync(descriptor);
|
|
66
|
+
}
|
|
67
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
68
|
+
throw new Error("Sanctuary resident authority pins are invalid");
|
|
69
|
+
const config = value;
|
|
70
|
+
if (Object.keys(config).sort().join(",") !== "botId,keyId,ownerChatId,ownerUserId,publicKeyDigest,publicKeyPem,schemaVersion,targetHost"
|
|
71
|
+
|| config.schemaVersion !== 1 || config.targetHost !== "sanctuary"
|
|
72
|
+
|| !["botId", "ownerUserId", "ownerChatId"].every((key) => typeof config[key] === "string" && /^[1-9][0-9]*$/u.test(config[key]))
|
|
73
|
+
|| config.ownerUserId !== config.ownerChatId || typeof config.keyId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/u.test(config.keyId)
|
|
74
|
+
|| typeof config.publicKeyPem !== "string" || typeof config.publicKeyDigest !== "string")
|
|
75
|
+
throw new Error("Sanctuary resident authority identity is invalid");
|
|
76
|
+
const publicKey = (0, node_crypto_1.createPublicKey)(config.publicKeyPem);
|
|
77
|
+
if (publicKey.asymmetricKeyType !== "ed25519" || config.publicKeyDigest !== `sha256:${(0, node_crypto_1.createHash)("sha256").update(publicKey.export({ format: "der", type: "spki" })).digest("hex")}`)
|
|
78
|
+
throw new Error("Sanctuary resident authority issuer pin changed");
|
|
79
|
+
const client = (options.createClient ?? ((socketPath) => new sanctuary_telegram_authority_service_1.SocketSanctuaryTelegramAuthorityClient(socketPath)))(path.join(root, "authority.sock"));
|
|
80
|
+
const credentials = { botId: config.botId, authorizedUserId: config.ownerUserId, authorizedChatId: config.ownerChatId };
|
|
81
|
+
const authorityTransport = (0, telegram_authority_transport_1.createSanctuaryTelegramAuthorityTransport)(client, {
|
|
82
|
+
expectedTargetHost: config.targetHost, expectedBotId: credentials.botId, expectedOwnerUserId: credentials.authorizedUserId,
|
|
83
|
+
expectedOwnerChatId: credentials.authorizedChatId, expectedKeyId: config.keyId, expectedPublicKeyDigest: config.publicKeyDigest, publicKey,
|
|
84
|
+
});
|
|
85
|
+
async function cursorSnapshot(at) {
|
|
86
|
+
const snapshot = (0, sanctuary_authority_codec_1.verifyAuthorityPayload)({
|
|
87
|
+
artifact: await client.request("telegram.cursor.snapshot", {}), expectedDomain: "ouro.sanctuary.telegram-cursor.v1", expectedKeyId: config.keyId, publicKey,
|
|
88
|
+
});
|
|
89
|
+
const now = at ?? Date.now();
|
|
90
|
+
if (!snapshot || Object.keys(snapshot).sort().join(",") !== "botId,cursor,keyId,observedAt,ownerChatId,ownerUserId,pendingUpdateIds,progressDigest,publicKeyDigest,targetHost"
|
|
91
|
+
|| !["targetHost", "botId", "ownerUserId", "ownerChatId", "keyId", "publicKeyDigest"].every((key) => snapshot[key] === config[key])
|
|
92
|
+
|| !Number.isSafeInteger(snapshot.cursor) || snapshot.cursor < 0
|
|
93
|
+
|| !Array.isArray(snapshot.pendingUpdateIds) || !snapshot.pendingUpdateIds.every((id, index, ids) => Number.isSafeInteger(id) && id >= snapshot.cursor && (index === 0 || id > ids[index - 1]))
|
|
94
|
+
|| typeof snapshot.progressDigest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(snapshot.progressDigest)
|
|
95
|
+
|| typeof snapshot.observedAt !== "string" || !Number.isFinite(Date.parse(snapshot.observedAt)) || new Date(snapshot.observedAt).toISOString() !== snapshot.observedAt
|
|
96
|
+
|| Date.parse(snapshot.observedAt) > now + 1000 || now - Date.parse(snapshot.observedAt) > 30_000)
|
|
97
|
+
throw new Error("Sanctuary authority cursor snapshot is invalid or stale");
|
|
98
|
+
return snapshot;
|
|
99
|
+
}
|
|
100
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.sanctuary_authority_pins_loaded", message: "Sanctuary resident authority pins loaded", meta: { keyId: config.keyId } });
|
|
101
|
+
return { credentials, authorityTransport, cursorSnapshot };
|
|
102
|
+
}
|
|
@@ -524,6 +524,7 @@ function createTelegramAdmissionController(options) {
|
|
|
524
524
|
const compensateFriend = async (record) => {
|
|
525
525
|
if (!record.friendId)
|
|
526
526
|
return;
|
|
527
|
+
await options.revokeCommunication?.({ userId: record.userId, chatId: record.chatId });
|
|
527
528
|
const revocation = await options.revokeFriend({
|
|
528
529
|
provider: "telegram-user",
|
|
529
530
|
botId: record.botId,
|
|
@@ -562,6 +563,12 @@ function createTelegramAdmissionController(options) {
|
|
|
562
563
|
}
|
|
563
564
|
try {
|
|
564
565
|
if (record.status === "friend_bound") {
|
|
566
|
+
await options.registerCommunication?.({
|
|
567
|
+
id: record.id,
|
|
568
|
+
updateId: record.updateId,
|
|
569
|
+
userId: record.userId,
|
|
570
|
+
chatId: record.chatId,
|
|
571
|
+
});
|
|
565
572
|
if (record.quarantinedText === null || !record.friendId)
|
|
566
573
|
throw new Error("Telegram admission lost input before durable ingress");
|
|
567
574
|
const committedBase = await options.commitApprovedIngress({
|
|
@@ -150,7 +150,11 @@ async function ingestTelegramAttachments(input) {
|
|
|
150
150
|
throw new Error("invalid remote path");
|
|
151
151
|
if (remote.file_size !== undefined && (!Number.isSafeInteger(remote.file_size) || remote.file_size < 0 || remote.file_size > exports.MAX_TELEGRAM_ATTACHMENT_BYTES))
|
|
152
152
|
throw new Error("advertised size exceeds limit");
|
|
153
|
-
const response =
|
|
153
|
+
const response = input.downloadFile
|
|
154
|
+
? await input.downloadFile(remote.file_path)
|
|
155
|
+
: input.botToken
|
|
156
|
+
? await fetchImpl(`https://api.telegram.org/file/bot${input.botToken}/${remote.file_path}`, { signal: AbortSignal.timeout(30_000) })
|
|
157
|
+
: (() => { throw new Error("attachment transport is unavailable"); })();
|
|
154
158
|
if (!response.ok)
|
|
155
159
|
throw new Error(`download failed with HTTP ${response.status}`);
|
|
156
160
|
const buffer = await readBounded(response, exports.MAX_TELEGRAM_ATTACHMENT_BYTES);
|