@ouro.bot/cli 0.1.0-alpha.742 → 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 +26 -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
package/dist/repertoire/tools.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
|
|
4
4
|
exports.resetMcpDefinitions = resetMcpDefinitions;
|
|
5
|
+
exports.getSanctuaryRelationshipTools = getSanctuaryRelationshipTools;
|
|
5
6
|
exports.getToolsForChannel = getToolsForChannel;
|
|
6
7
|
exports.resolveToolDefinition = resolveToolDefinition;
|
|
7
8
|
exports.approvalPolicyForToolName = approvalPolicyForToolName;
|
|
9
|
+
exports.classifyApprovalForInvocation = classifyApprovalForInvocation;
|
|
10
|
+
exports.approvalPolicyForInvocation = approvalPolicyForInvocation;
|
|
8
11
|
exports.shellRiskProfile = shellRiskProfile;
|
|
9
12
|
exports.riskProfileForTool = riskProfileForTool;
|
|
10
13
|
exports.riskProfileForToolName = riskProfileForToolName;
|
|
@@ -27,6 +30,8 @@ const tools_voice_1 = require("./tools-voice");
|
|
|
27
30
|
const shell_sessions_1 = require("./shell-sessions");
|
|
28
31
|
const tools_unraid_1 = require("./tools-unraid");
|
|
29
32
|
const tools_flow_1 = require("./tools-flow");
|
|
33
|
+
const tools_steward_policy_1 = require("./tools-steward-policy");
|
|
34
|
+
const steward_policy_1 = require("../heart/steward-policy");
|
|
30
35
|
function safeGetAgentRoot() {
|
|
31
36
|
try {
|
|
32
37
|
return (0, identity_1.getAgentRoot)();
|
|
@@ -43,6 +48,33 @@ function isSanctuaryAgent() {
|
|
|
43
48
|
return false;
|
|
44
49
|
}
|
|
45
50
|
}
|
|
51
|
+
const SANCTUARY_RELATIONSHIP_BASE_TOOLS = new Set([
|
|
52
|
+
"external_event_disposition",
|
|
53
|
+
"query_active_work",
|
|
54
|
+
"save_friend_note",
|
|
55
|
+
"telegram_contact_manage",
|
|
56
|
+
"query_cares",
|
|
57
|
+
"care_manage",
|
|
58
|
+
"await_condition",
|
|
59
|
+
"resolve_await",
|
|
60
|
+
"cancel_await",
|
|
61
|
+
"send_message",
|
|
62
|
+
]);
|
|
63
|
+
const SANCTUARY_TELEGRAM_SURFACE = new Set([
|
|
64
|
+
"query_active_work",
|
|
65
|
+
"save_friend_note",
|
|
66
|
+
"telegram_contact_manage",
|
|
67
|
+
"query_cares",
|
|
68
|
+
"care_manage",
|
|
69
|
+
"await_condition",
|
|
70
|
+
"resolve_await",
|
|
71
|
+
"cancel_await",
|
|
72
|
+
...tools_unraid_1.unraidToolDefinitions.map((definition) => definition.tool.function.name),
|
|
73
|
+
"steward_policy_manage",
|
|
74
|
+
"ponder",
|
|
75
|
+
"settle",
|
|
76
|
+
"speak",
|
|
77
|
+
]);
|
|
46
78
|
// Re-export types and constants used by the rest of the codebase
|
|
47
79
|
var tools_base_2 = require("./tools-base");
|
|
48
80
|
Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_base_2.tools; } });
|
|
@@ -55,7 +87,7 @@ Object.defineProperty(exports, "speakTool", { enumerable: true, get: function ()
|
|
|
55
87
|
var tools_surface_2 = require("./tools-surface");
|
|
56
88
|
Object.defineProperty(exports, "surfaceToolDef", { enumerable: true, get: function () { return tools_surface_2.surfaceToolDef; } });
|
|
57
89
|
// All tool definitions in a single registry
|
|
58
|
-
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, ...tools_bundle_1.bundleToolDefinitions, ...tools_voice_1.voiceToolDefinitions, ...tools_unraid_1.unraidToolDefinitions, tools_surface_1.surfaceToolDefinition];
|
|
90
|
+
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, ...tools_bundle_1.bundleToolDefinitions, ...tools_voice_1.voiceToolDefinitions, ...tools_unraid_1.unraidToolDefinitions, tools_steward_policy_1.stewardPolicyToolDefinition, tools_surface_1.surfaceToolDefinition];
|
|
59
91
|
const COMMERCE_AUTHORITY_TOOLS = new Set(["stripe_create_card", "flight_hold", "flight_book"]);
|
|
60
92
|
// MCP tool definitions — populated each time getToolsForChannel() is called with an mcpManager.
|
|
61
93
|
// Kept separate from allDefinitions so execTool can find them.
|
|
@@ -68,6 +100,32 @@ function baseToolsForCapabilities() {
|
|
|
68
100
|
// Use baseToolDefinitions at call time so dynamically-added tools are included
|
|
69
101
|
return tools_base_1.baseToolDefinitions.map((d) => d.tool);
|
|
70
102
|
}
|
|
103
|
+
function uniqueToolSchemas(tools) {
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
return tools.filter((tool) => {
|
|
106
|
+
if (seen.has(tool.function.name))
|
|
107
|
+
return false;
|
|
108
|
+
seen.add(tool.function.name);
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve Sanctuary relationship tools from one canonical definition pool.
|
|
114
|
+
* Durable relationship profiles can only reduce this surface; they cannot
|
|
115
|
+
* introduce an arbitrary base tool by naming it in configuration.
|
|
116
|
+
*/
|
|
117
|
+
function getSanctuaryRelationshipTools(advertisedToolNames) {
|
|
118
|
+
const advertised = new Set(advertisedToolNames);
|
|
119
|
+
return uniqueToolSchemas([
|
|
120
|
+
...tools_base_1.baseToolDefinitions.filter((definition) => SANCTUARY_RELATIONSHIP_BASE_TOOLS.has(definition.tool.function.name)).map((definition) => definition.tool),
|
|
121
|
+
...tools_unraid_1.unraidToolDefinitions.map((definition) => definition.tool),
|
|
122
|
+
tools_steward_policy_1.stewardPolicyToolDefinition.tool,
|
|
123
|
+
tools_flow_1.ponderTool,
|
|
124
|
+
tools_flow_1.restTool,
|
|
125
|
+
tools_flow_1.settleTool,
|
|
126
|
+
tools_flow_1.speakTool,
|
|
127
|
+
]).filter((tool) => advertised.has(tool.function.name));
|
|
128
|
+
}
|
|
71
129
|
// Apply a single tool preference to a tool schema, returning a new object.
|
|
72
130
|
function applyPreference(tool, pref) {
|
|
73
131
|
return {
|
|
@@ -96,12 +154,7 @@ function filterByCapability(toolList, providerCapabilities) {
|
|
|
96
154
|
// When providerCapabilities is provided, tools with requiredCapability are filtered.
|
|
97
155
|
function getToolsForChannel(capabilities, toolPreferences, _context, providerCapabilities, mcpManager, _chatModel) {
|
|
98
156
|
if (capabilities?.channel === "telegram" && isSanctuaryAgent()) {
|
|
99
|
-
return [
|
|
100
|
-
...tools_unraid_1.unraidToolDefinitions.map((definition) => definition.tool),
|
|
101
|
-
tools_flow_1.ponderTool,
|
|
102
|
-
tools_flow_1.settleTool,
|
|
103
|
-
tools_flow_1.speakTool,
|
|
104
|
-
];
|
|
157
|
+
return getSanctuaryRelationshipTools([...SANCTUARY_TELEGRAM_SURFACE]);
|
|
105
158
|
}
|
|
106
159
|
const baseTools = baseToolsForCapabilities();
|
|
107
160
|
const bluebubblesTools = capabilities?.channel === "bluebubbles"
|
|
@@ -156,6 +209,36 @@ function resolveToolDefinition(toolName) {
|
|
|
156
209
|
function approvalPolicyForToolName(name, args) {
|
|
157
210
|
return resolveToolDefinition(name)?.approvalPolicy?.(args) ?? { kind: "not_required" };
|
|
158
211
|
}
|
|
212
|
+
async function routineActionInvocation(name, args, ctx) {
|
|
213
|
+
const target = typeof args.container === "string" ? args.container : "";
|
|
214
|
+
if (name !== "unraid_restart_container" || !ctx?.agentRoot || !(0, tools_base_1.routineActionRequester)(ctx) || !target)
|
|
215
|
+
return null;
|
|
216
|
+
const relationshipAuthorization = ctx.relationshipAuthorization;
|
|
217
|
+
if (!relationshipAuthorization)
|
|
218
|
+
return null;
|
|
219
|
+
let authorization;
|
|
220
|
+
try {
|
|
221
|
+
authorization = await relationshipAuthorization.authorizeTool(name, args);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
if (!authorization.allowed || !Number.isInteger(authorization.profileVersion) || Number(authorization.profileVersion) < 1)
|
|
227
|
+
return null;
|
|
228
|
+
const key = `unraid.restart:${target}`;
|
|
229
|
+
const decision = (0, steward_policy_1.inspectRoutineActionGrant)(ctx.agentRoot, { key, action: "unraid.container.restart", target });
|
|
230
|
+
return decision.allowed ? { key, target, expectedPolicyVersion: decision.policyVersion } : null;
|
|
231
|
+
}
|
|
232
|
+
async function classifyApprovalForInvocation(name, args, ctx) {
|
|
233
|
+
const fallback = approvalPolicyForToolName(name, args);
|
|
234
|
+
if (name !== "unraid_restart_container" || fallback.kind !== "required")
|
|
235
|
+
return { policy: fallback };
|
|
236
|
+
const routineActionSelection = await routineActionInvocation(name, args, ctx);
|
|
237
|
+
return routineActionSelection ? { policy: { kind: "not_required" }, routineActionSelection } : { policy: fallback };
|
|
238
|
+
}
|
|
239
|
+
async function approvalPolicyForInvocation(name, args, ctx) {
|
|
240
|
+
return (await classifyApprovalForInvocation(name, args, ctx)).policy;
|
|
241
|
+
}
|
|
159
242
|
const findDefinition = resolveToolDefinition;
|
|
160
243
|
function normalizeGuardArgs(_name, args) {
|
|
161
244
|
return args;
|
|
@@ -267,6 +350,17 @@ async function execTool(name, args, ctx) {
|
|
|
267
350
|
});
|
|
268
351
|
return `unknown: ${name}`;
|
|
269
352
|
}
|
|
353
|
+
const relationshipDecision = await ctx?.relationshipAuthorization?.authorizeTool(name, args);
|
|
354
|
+
if (relationshipDecision && !relationshipDecision.allowed) {
|
|
355
|
+
(0, runtime_1.emitNervesEvent)({
|
|
356
|
+
level: "warn",
|
|
357
|
+
event: "tool.relationship_authorization_block",
|
|
358
|
+
component: "tools",
|
|
359
|
+
message: "relationship authorization blocked tool execution",
|
|
360
|
+
meta: { name, reason: relationshipDecision.reason },
|
|
361
|
+
});
|
|
362
|
+
return `relationship authorization required: ${relationshipDecision.reason}`;
|
|
363
|
+
}
|
|
270
364
|
const riskProfile = riskProfileForTool(def, name, args);
|
|
271
365
|
const orientationPolicy = ctx?.orientationFrame?.actionPolicy;
|
|
272
366
|
if (orientationPolicy?.mode === "correction_hold"
|
|
@@ -323,16 +417,17 @@ async function execTool(name, args, ctx) {
|
|
|
323
417
|
});
|
|
324
418
|
return `commerce authority required: ${commerceReservation.reason}`;
|
|
325
419
|
}
|
|
420
|
+
const authorizedContext = ctx;
|
|
326
421
|
const toolContext = commerceReservation?.ok
|
|
327
422
|
? {
|
|
328
|
-
...
|
|
423
|
+
...authorizedContext,
|
|
329
424
|
agentRoot: guardContext.agentRoot,
|
|
330
425
|
commerceAuthority: {
|
|
331
426
|
checkoutId: commerceReservation.checkoutId,
|
|
332
427
|
reservationToken: commerceReservation.reservationToken,
|
|
333
428
|
},
|
|
334
429
|
}
|
|
335
|
-
:
|
|
430
|
+
: authorizedContext;
|
|
336
431
|
try {
|
|
337
432
|
const result = await def.handler(args, toolContext);
|
|
338
433
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -8,6 +8,12 @@ const unraid_client_1 = require("./unraid-client");
|
|
|
8
8
|
exports.SANCTUARY_RESTART_MUTATION = `mutation SanctuaryRestart($id: PrefixedID!) {
|
|
9
9
|
docker { restart(id: $id) { id names state status autoStart } }
|
|
10
10
|
}`;
|
|
11
|
+
class RoutineActionReceiptError extends Error {
|
|
12
|
+
constructor(cause) {
|
|
13
|
+
super("routine action receipt persistence failed", { cause });
|
|
14
|
+
this.name = "RoutineActionReceiptError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
function failure(code, message) {
|
|
12
18
|
return { ok: false, error: { code, message, degraded: true } };
|
|
13
19
|
}
|
|
@@ -40,7 +46,15 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
40
46
|
const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
41
47
|
const now = options.now ?? (() => new Date());
|
|
42
48
|
const observationTimeoutMs = options.observationTimeoutMs ?? 30_000;
|
|
43
|
-
|
|
49
|
+
const transitionRoutine = (input) => {
|
|
50
|
+
try {
|
|
51
|
+
options.transitionRoutineAction?.(input);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new RoutineActionReceiptError(error);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return async (args, execution) => {
|
|
44
58
|
if (!validArgument(args.container))
|
|
45
59
|
return failure("invalid_response", "container must be one bounded exact name");
|
|
46
60
|
const resolved = exactTarget(await options.listContainers(), args.container);
|
|
@@ -51,6 +65,22 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
51
65
|
return fresh;
|
|
52
66
|
if (fresh.id !== resolved.id)
|
|
53
67
|
return failure("stale_target", "container identity changed before restart");
|
|
68
|
+
let routineAuthorization = null;
|
|
69
|
+
if (execution?.routine) {
|
|
70
|
+
let authorization;
|
|
71
|
+
try {
|
|
72
|
+
authorization = await execution.routine.reauthorize();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return failure("stale_target", "routine relationship authorization is unavailable");
|
|
76
|
+
}
|
|
77
|
+
if (!authorization.allowed)
|
|
78
|
+
return failure("stale_target", authorization.reason);
|
|
79
|
+
if (!authorization.receiptId.trim() || !Number.isInteger(authorization.profileVersion) || authorization.profileVersion < 1) {
|
|
80
|
+
return failure("stale_target", "routine relationship authorization is not versioned");
|
|
81
|
+
}
|
|
82
|
+
routineAuthorization = { receiptId: authorization.receiptId, profileVersion: authorization.profileVersion };
|
|
83
|
+
}
|
|
54
84
|
const scenarioHandleDigest = options.acceptanceScenarioHandleDigest?.();
|
|
55
85
|
const approval = options.acceptanceApproval?.();
|
|
56
86
|
const attempt = {
|
|
@@ -65,13 +95,44 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
65
95
|
mutationAcknowledged: false,
|
|
66
96
|
afterState: null,
|
|
67
97
|
};
|
|
98
|
+
let routineReceipt = null;
|
|
99
|
+
let routineState = null;
|
|
100
|
+
if (execution?.routine) {
|
|
101
|
+
if (!options.reserveRoutineAction || !options.transitionRoutineAction)
|
|
102
|
+
return failure("invalid_response", "routine action ledger is unavailable");
|
|
103
|
+
try {
|
|
104
|
+
routineReceipt = options.reserveRoutineAction({
|
|
105
|
+
key: execution.routine.key,
|
|
106
|
+
expectedPolicyVersion: execution.routine.expectedPolicyVersion,
|
|
107
|
+
authorizationReceiptId: routineAuthorization.receiptId,
|
|
108
|
+
authorizationVersion: routineAuthorization.profileVersion,
|
|
109
|
+
action: "unraid.container.restart",
|
|
110
|
+
target: fresh.name,
|
|
111
|
+
attemptId: attempt.attemptId,
|
|
112
|
+
expectedBeforeState: fresh.state,
|
|
113
|
+
resolvedTarget: { id: fresh.id, name: fresh.name },
|
|
114
|
+
effect: { operation: "restart", targetId: fresh.id },
|
|
115
|
+
});
|
|
116
|
+
routineState = "reserved";
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return failure("stale_target", error instanceof Error ? error.message : "routine action authority changed");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
68
122
|
if (approval && approval.argumentDigest !== attempt.argumentDigest)
|
|
69
123
|
return failure("stale_target", "approval arguments changed before restart");
|
|
70
124
|
await options.persistAttempt?.({ ...attempt, observedAt: now().toISOString(), state: "attempt_not_started" });
|
|
71
125
|
const apiKey = await options.loadWriteApiKey();
|
|
72
|
-
if (!apiKey.trim())
|
|
126
|
+
if (!apiKey.trim()) {
|
|
127
|
+
if (routineReceipt && routineState === "reserved")
|
|
128
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: "reserved", state: "failed", recoveryState: { state: "completed", compensation: "none" } });
|
|
73
129
|
return failure("invalid_response", "Unraid write credential is unavailable");
|
|
130
|
+
}
|
|
74
131
|
const client = createClient({ endpoint: options.endpoint, apiKey });
|
|
132
|
+
if (routineReceipt && routineState === "reserved") {
|
|
133
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: "reserved", state: "attempting" });
|
|
134
|
+
routineState = "attempting";
|
|
135
|
+
}
|
|
75
136
|
await options.persistAttempt?.({ ...attempt, observedAt: now().toISOString(), state: "attempting" });
|
|
76
137
|
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.unraid_restart_start", message: "approved Unraid restart started", meta: { containerId: fresh.id, containerName: fresh.name } });
|
|
77
138
|
const persistTerminal = async (terminal) => {
|
|
@@ -85,8 +146,9 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
85
146
|
}
|
|
86
147
|
};
|
|
87
148
|
let acknowledged = false;
|
|
149
|
+
let mutation = null;
|
|
88
150
|
try {
|
|
89
|
-
|
|
151
|
+
mutation = await client.mutate(exports.SANCTUARY_RESTART_MUTATION, { id: fresh.id });
|
|
90
152
|
acknowledged = acknowledgedIdentity(mutation, fresh);
|
|
91
153
|
if (!acknowledged)
|
|
92
154
|
throw new Error("restart response identity was invalid");
|
|
@@ -94,6 +156,10 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
94
156
|
catch {
|
|
95
157
|
acknowledged = false;
|
|
96
158
|
}
|
|
159
|
+
if (acknowledged && mutation && routineReceipt && routineState === "attempting") {
|
|
160
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: "attempting", state: "effect_acknowledged", effectReceipt: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(mutation)).digest("hex") });
|
|
161
|
+
routineState = "effect_acknowledged";
|
|
162
|
+
}
|
|
97
163
|
const startedAt = now().getTime();
|
|
98
164
|
let sawRestarting = false;
|
|
99
165
|
try {
|
|
@@ -102,6 +168,8 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
102
168
|
if (!("ok" in observed)) {
|
|
103
169
|
if (observed.id !== fresh.id) {
|
|
104
170
|
await persistTerminal({ ...attempt, observedAt: now().toISOString(), state: "attempted_or_indeterminate", mutationAcknowledged: acknowledged });
|
|
171
|
+
if (routineReceipt && routineState)
|
|
172
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: routineState, state: "indeterminate", recoveryState: { state: "manual_inspection_required", compensation: "none" } });
|
|
105
173
|
return failure("ambiguous", "container identity changed after restart attempt");
|
|
106
174
|
}
|
|
107
175
|
sawRestarting ||= observed.state === "restarting";
|
|
@@ -109,6 +177,14 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
109
177
|
if (!await persistTerminal({ ...attempt, observedAt: now().toISOString(), state: "succeeded", mutationAcknowledged: acknowledged, afterState: observed.state })) {
|
|
110
178
|
return failure("ambiguous", "restart succeeded but its terminal receipt could not be persisted; it was not retried");
|
|
111
179
|
}
|
|
180
|
+
if (routineReceipt && routineState === "attempting") {
|
|
181
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: "attempting", state: "effect_acknowledged", effectReceipt: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ observation: "restarting", target: fresh.id })).digest("hex") });
|
|
182
|
+
routineState = "effect_acknowledged";
|
|
183
|
+
}
|
|
184
|
+
if (routineReceipt && routineState === "effect_acknowledged") {
|
|
185
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: "effect_acknowledged", state: "verified", verifiedAfterState: observed.state, recoveryState: { state: "completed", compensation: "none" } });
|
|
186
|
+
routineState = null;
|
|
187
|
+
}
|
|
112
188
|
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.unraid_restart_end", message: "approved Unraid restart completed", meta: { containerId: fresh.id, observedRestart: true } });
|
|
113
189
|
return { ok: true, data: { container: { id: fresh.id, name: fresh.name }, beforeState: fresh.state, afterState: observed.state, observedRestart: true, degraded: false } };
|
|
114
190
|
}
|
|
@@ -118,12 +194,18 @@ function createApprovedUnraidRestartExecutor(options) {
|
|
|
118
194
|
await sleep(1_000);
|
|
119
195
|
} while (true);
|
|
120
196
|
}
|
|
121
|
-
catch {
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof RoutineActionReceiptError)
|
|
199
|
+
throw error;
|
|
122
200
|
await persistTerminal({ ...attempt, observedAt: now().toISOString(), state: "attempted_or_indeterminate", mutationAcknowledged: acknowledged });
|
|
201
|
+
if (routineReceipt && routineState)
|
|
202
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: routineState, state: "indeterminate", recoveryState: { state: "manual_inspection_required", compensation: "none" } });
|
|
123
203
|
(0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.unraid_restart_error", message: "approved Unraid restart observation failed", meta: { containerId: fresh.id, acknowledged } });
|
|
124
204
|
return failure("ambiguous", "restart was attempted but observation failed; it was not retried");
|
|
125
205
|
}
|
|
126
206
|
await persistTerminal({ ...attempt, observedAt: now().toISOString(), state: "attempted_or_indeterminate", mutationAcknowledged: acknowledged });
|
|
207
|
+
if (routineReceipt && routineState)
|
|
208
|
+
transitionRoutine({ id: routineReceipt.id, expectedState: routineState, state: "indeterminate", recoveryState: { state: "manual_inspection_required", compensation: "none" } });
|
|
127
209
|
(0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.unraid_restart_error", message: "approved Unraid restart outcome was ambiguous", meta: { containerId: fresh.id, acknowledged } });
|
|
128
210
|
return failure("ambiguous", "restart was attempted but could not be proven; it was not retried");
|
|
129
211
|
};
|
|
@@ -39,6 +39,7 @@ exports.startPrivateRuntimeWorker = startPrivateRuntimeWorker;
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const private_runtime_1 = require("./private-runtime");
|
|
42
|
+
const router_1 = require("../heart/external-events/router");
|
|
42
43
|
const runtime_1 = require("../nerves/runtime");
|
|
43
44
|
const identity_1 = require("../heart/identity");
|
|
44
45
|
const pending_1 = require("../mind/pending");
|
|
@@ -471,9 +472,29 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
471
472
|
});
|
|
472
473
|
}
|
|
473
474
|
}
|
|
474
|
-
|
|
475
|
+
const startExternalEventHeartbeat = (event) => {
|
|
476
|
+
const renew = () => {
|
|
477
|
+
for (const member of [event, ...(event.relatedEvents ?? [])]) {
|
|
478
|
+
try {
|
|
479
|
+
(0, router_1.renewExternalEventClaim)(member.recordPath, { owner: member.claimOwner, expectedGeneration: member.generation, leaseMs: 30_000 });
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
(0, runtime_1.emitNervesEvent)({
|
|
483
|
+
level: "warn", component: "senses", event: "senses.external_event_lease_renew_error",
|
|
484
|
+
message: "external-event turn lease renewal failed",
|
|
485
|
+
meta: { recordPath: member.recordPath, generation: member.generation, error: error instanceof Error ? error.message : String(error) },
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
renew();
|
|
491
|
+
const timer = setInterval(renew, 10_000);
|
|
492
|
+
timer.unref?.();
|
|
493
|
+
return () => clearInterval(timer);
|
|
494
|
+
};
|
|
495
|
+
async function run(reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend, externalEvent) {
|
|
475
496
|
if (running) {
|
|
476
|
-
queue.push({ reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend });
|
|
497
|
+
queue.push({ reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend, externalEvent, ...(externalEvent ? { stopExternalEventHeartbeat: startExternalEventHeartbeat(externalEvent) } : {}) });
|
|
477
498
|
return;
|
|
478
499
|
}
|
|
479
500
|
running = true;
|
|
@@ -484,6 +505,8 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
484
505
|
let nextAwaitName = awaitName;
|
|
485
506
|
let nextTrigger = trigger;
|
|
486
507
|
let nextPrivateTurnDecision = privateTurnDecision;
|
|
508
|
+
let nextExternalEvent = externalEvent;
|
|
509
|
+
let nextExternalEventHeartbeat = externalEvent ? startExternalEventHeartbeat(externalEvent) : undefined;
|
|
487
510
|
let nextNoSend = reason === "habit" ? noSend : undefined;
|
|
488
511
|
let nextHabitRun = null;
|
|
489
512
|
let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
|
|
@@ -492,6 +515,8 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
492
515
|
const currentHabitName = nextHabitName;
|
|
493
516
|
const currentTrigger = nextTrigger ?? "overdue";
|
|
494
517
|
const currentNoSend = nextNoSend;
|
|
518
|
+
const currentExternalEventHeartbeat = nextExternalEventHeartbeat;
|
|
519
|
+
nextExternalEventHeartbeat = undefined;
|
|
495
520
|
const currentHabitRun = currentReason === "habit" && currentHabitName
|
|
496
521
|
? nextHabitRun && nextHabitRun.habit.name === currentHabitName
|
|
497
522
|
? nextHabitRun
|
|
@@ -545,6 +570,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
545
570
|
awaitName: nextAwaitName,
|
|
546
571
|
...(currentNoSend ? { noSend: true } : {}),
|
|
547
572
|
...(nextPrivateTurnDecision ? { privateTurnDecision: nextPrivateTurnDecision } : {}),
|
|
573
|
+
...(nextExternalEvent ? { externalEvent: nextExternalEvent } : {}),
|
|
548
574
|
...(currentHabitRun
|
|
549
575
|
? {
|
|
550
576
|
trigger: currentHabitRun.trigger,
|
|
@@ -587,6 +613,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
587
613
|
},
|
|
588
614
|
});
|
|
589
615
|
}
|
|
616
|
+
currentExternalEventHeartbeat?.();
|
|
590
617
|
if (currentReason === "habit" && currentHabitName === "heartbeat") {
|
|
591
618
|
heartbeatOkRestedAt = isHeartbeatOkRestResult(turnResult) ? nowSource() : null;
|
|
592
619
|
}
|
|
@@ -613,6 +640,8 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
613
640
|
nextAwaitName = next.awaitName;
|
|
614
641
|
nextTrigger = next.trigger;
|
|
615
642
|
nextPrivateTurnDecision = next.privateTurnDecision;
|
|
643
|
+
nextExternalEvent = next.externalEvent;
|
|
644
|
+
nextExternalEventHeartbeat = next.stopExternalEventHeartbeat;
|
|
616
645
|
nextNoSend = next.reason === "habit" ? next.noSend : undefined;
|
|
617
646
|
consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
|
|
618
647
|
continue runLoop;
|
|
@@ -650,6 +679,8 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
650
679
|
nextAwaitName = undefined;
|
|
651
680
|
nextTrigger = currentTrigger;
|
|
652
681
|
nextPrivateTurnDecision = undefined;
|
|
682
|
+
nextExternalEvent = undefined;
|
|
683
|
+
nextExternalEventHeartbeat = undefined;
|
|
653
684
|
nextNoSend = currentNoSend;
|
|
654
685
|
nextHabitRun = currentHabitRun;
|
|
655
686
|
}
|
|
@@ -662,6 +693,8 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
662
693
|
nextAwaitName = undefined;
|
|
663
694
|
nextTrigger = undefined;
|
|
664
695
|
nextPrivateTurnDecision = undefined;
|
|
696
|
+
nextExternalEvent = undefined;
|
|
697
|
+
nextExternalEventHeartbeat = undefined;
|
|
665
698
|
nextNoSend = undefined;
|
|
666
699
|
}
|
|
667
700
|
continue;
|
|
@@ -715,7 +748,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
715
748
|
if (maybeMessage.type === "chat" ||
|
|
716
749
|
maybeMessage.type === "message") {
|
|
717
750
|
clearHeartbeatRestShield();
|
|
718
|
-
await run("instinct", undefined, undefined, undefined, undefined, maybeMessage.privateTurnDecision);
|
|
751
|
+
await run("instinct", undefined, undefined, undefined, undefined, maybeMessage.privateTurnDecision, undefined, maybeMessage.externalEvent);
|
|
719
752
|
return;
|
|
720
753
|
}
|
|
721
754
|
if (maybeMessage.type === "shutdown") {
|