@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.
Files changed (71) hide show
  1. package/changelog.json +26 -0
  2. package/deploy/unraid/Dockerfile +7 -2
  3. package/deploy/unraid/README.txt +704 -64
  4. package/deploy/unraid/migrate-sanctuary-bundle.mjs +21 -0
  5. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  6. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  7. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  8. package/deploy/unraid/ouro-events/install-usenet-guard.sh +434 -0
  9. package/deploy/unraid/ouro-events/usenet-health.sh +178 -0
  10. package/deploy/unraid/sanctuary-acceptance-contract.json +4 -3
  11. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +5 -1
  12. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +2 -2
  13. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +1 -1
  14. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -1
  15. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -1
  16. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +4 -0
  17. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +6 -2
  18. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +4 -2
  19. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  20. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +19 -17
  21. package/deploy/unraid/sanctuary.xml +13 -10
  22. package/dist/arc/cares.js +171 -37
  23. package/dist/arc/obligations.js +38 -0
  24. package/dist/heart/awaiting/await-alert.js +4 -2
  25. package/dist/heart/awaiting/await-expiry.js +39 -4
  26. package/dist/heart/awaiting/await-parser.js +8 -0
  27. package/dist/heart/core.js +37 -7
  28. package/dist/heart/cross-chat-delivery.js +1 -0
  29. package/dist/heart/daemon/container-spec-auditor-main.js +11 -7
  30. package/dist/heart/daemon/container-spec-auditor.js +38 -11
  31. package/dist/heart/daemon/daemon-entry.js +19 -5
  32. package/dist/heart/daemon/daemon.js +154 -22
  33. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +126 -42
  34. package/dist/heart/daemon/sanctuary-acceptance-harness.js +26 -29
  35. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +42 -51
  36. package/dist/heart/daemon/sanctuary-bundle-migration.js +398 -0
  37. package/dist/heart/external-events/router.js +1025 -43
  38. package/dist/heart/session-events.js +23 -5
  39. package/dist/heart/steward-policy.js +302 -0
  40. package/dist/heart/tool-approval.js +3 -1
  41. package/dist/heart/turn-context.js +1 -0
  42. package/dist/mind/context.js +8 -3
  43. package/dist/mind/pending.js +25 -0
  44. package/dist/mind/prompt.js +49 -1
  45. package/dist/repertoire/guardrails.js +2 -1
  46. package/dist/repertoire/relationship-authorization.js +194 -0
  47. package/dist/repertoire/tools-awaiting.js +229 -26
  48. package/dist/repertoire/tools-base.js +15 -0
  49. package/dist/repertoire/tools-continuity.js +259 -4
  50. package/dist/repertoire/tools-notes.js +29 -3
  51. package/dist/repertoire/tools-session.js +219 -4
  52. package/dist/repertoire/tools-steward-policy.js +88 -0
  53. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  54. package/dist/repertoire/tools-unraid.js +113 -6
  55. package/dist/repertoire/tools.js +104 -9
  56. package/dist/repertoire/unraid-restart.js +86 -4
  57. package/dist/senses/private-runtime-worker.js +36 -3
  58. package/dist/senses/private-runtime.js +310 -63
  59. package/dist/senses/sanctuary-health-acceptance-probe.js +8 -6
  60. package/dist/senses/sanctuary-health-runner.js +47 -103
  61. package/dist/senses/sanctuary-health.js +53 -105
  62. package/dist/senses/sanctuary-runtime.js +36 -2
  63. package/dist/senses/sanctuary-sab.js +80 -0
  64. package/dist/senses/shared-turn.js +64 -9
  65. package/dist/senses/telegram-admission.js +684 -0
  66. package/dist/senses/telegram-approval-runtime.js +46 -6
  67. package/dist/senses/telegram-client.js +212 -66
  68. package/dist/senses/telegram-effect-adapter.js +787 -0
  69. package/dist/senses/telegram.js +728 -48
  70. package/npm-shrinkwrap.json +6 -6
  71. package/package.json +2 -2
@@ -56,6 +56,9 @@ const context_1 = require("../mind/context");
56
56
  const prompt_1 = require("../mind/prompt");
57
57
  const mcp_manager_1 = require("../repertoire/mcp-manager");
58
58
  const tools_1 = require("../repertoire/tools");
59
+ const tools_awaiting_1 = require("../repertoire/tools-awaiting");
60
+ const relationship_authorization_1 = require("../repertoire/relationship-authorization");
61
+ const run_ledger_1 = require("../heart/run-ledger");
59
62
  const bundle_manifest_1 = require("../mind/bundle-manifest");
60
63
  const pending_1 = require("../mind/pending");
61
64
  const obligations_1 = require("../arc/obligations");
@@ -83,6 +86,9 @@ const habit_policy_1 = require("../rsvp/habit-policy");
83
86
  const daemon_health_1 = require("../heart/daemon/daemon-health");
84
87
  const flight_recorder_1 = require("../arc/flight-recorder");
85
88
  const desk_section_1 = require("../mind/desk-section");
89
+ const sanctuary_runtime_1 = require("./sanctuary-runtime");
90
+ const shared_turn_1 = require("./shared-turn");
91
+ const relationship_authorization_2 = require("../repertoire/relationship-authorization");
86
92
  const private_runtime_1 = require("../heart/private-runtime");
87
93
  exports.PRIVATE_TURN_DECISION_MAX_AGE_MS = 15 * 60_000;
88
94
  const DUPLICATE_PRIVATE_TURN_STATUS = "DUPLICATE_PRIVATE_TURN";
@@ -93,6 +99,74 @@ const DEFAULT_PRIVATE_RUNTIME_INSTINCTS = [
93
99
  enabled: true,
94
100
  },
95
101
  ];
102
+ class StaleRelationshipAwaitError extends Error {
103
+ reason;
104
+ constructor(reason) {
105
+ super(`Relationship await authority ${reason}`);
106
+ this.reason = reason;
107
+ }
108
+ }
109
+ function exactFriendFileIsMissing(agentRoot, friendId) {
110
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(friendId))
111
+ throw new Error("Relationship await authority friend id is invalid");
112
+ try {
113
+ fs.lstatSync(path.join(agentRoot, "friends", `${friendId}.json`));
114
+ return false;
115
+ }
116
+ catch (error) {
117
+ if (error.code === "ENOENT")
118
+ return true;
119
+ throw error;
120
+ }
121
+ }
122
+ function relationshipAwaitCoordinates(awaitFile) {
123
+ const values = [awaitFile.filed_for_friend_id, awaitFile.filed_from, awaitFile.filed_from_key];
124
+ if (!awaitFile.request_id) {
125
+ if (awaitFile.filed_from === "external-event" && awaitFile.filed_for_friend_id && awaitFile.filed_from_key) {
126
+ if (Buffer.byteLength(awaitFile.filed_for_friend_id) > 256 || Buffer.byteLength(awaitFile.filed_from_key) > 1_024)
127
+ throw new Error("Relationship await provenance is invalid");
128
+ return { friendId: awaitFile.filed_for_friend_id, channel: awaitFile.filed_from, key: awaitFile.filed_from_key, requestId: null };
129
+ }
130
+ const isSystemAwait = awaitFile.filed_for_friend_id === null
131
+ && awaitFile.filed_from_key === null
132
+ && (awaitFile.filed_from === null || awaitFile.filed_from === "unknown" || awaitFile.filed_from === "cli");
133
+ if (isSystemAwait)
134
+ return null;
135
+ throw new Error("Relationship await provenance is incomplete or legacy");
136
+ }
137
+ if (values.some((value) => value === null))
138
+ throw new Error("Relationship await provenance is incomplete or legacy");
139
+ const [friendId, channel, key] = values;
140
+ if (channel !== "telegram" || Buffer.byteLength(friendId) > 256 || Buffer.byteLength(channel) > 64 || Buffer.byteLength(key) > 1_024 || Buffer.byteLength(awaitFile.request_id) > 256) {
141
+ throw new Error("Relationship await provenance is invalid");
142
+ }
143
+ return { friendId, channel, key, requestId: awaitFile.request_id };
144
+ }
145
+ async function resolveRelationshipAwaitAuthority(agentRoot, store, registry, coordinates) {
146
+ const friend = await store.get(coordinates.friendId);
147
+ if (!friend) {
148
+ if (exactFriendFileIsMissing(agentRoot, coordinates.friendId))
149
+ throw new StaleRelationshipAwaitError("friend is missing");
150
+ throw new Error("Relationship await authority friend exists but could not be read");
151
+ }
152
+ if (friend.id !== coordinates.friendId)
153
+ throw new Error("Relationship await authority friend record is ambiguous");
154
+ if (friend.admissionState !== "active" || !friend.capabilityProfileId)
155
+ throw new StaleRelationshipAwaitError("admission or profile is not active");
156
+ if (!registry.profiles[friend.capabilityProfileId])
157
+ throw new StaleRelationshipAwaitError("admission or profile is not active");
158
+ if (coordinates.channel === "external-event" && (friend.capabilityProfileId !== "sanctuary-owner" || !registry.profiles["sanctuary-event"])) {
159
+ throw new StaleRelationshipAwaitError("admission or profile is not active");
160
+ }
161
+ const authorization = coordinates.channel === "external-event"
162
+ ? (0, relationship_authorization_2.createRelationshipAuthorizationEvaluator)({ friend, registry, profileId: "sanctuary-event", requestPhase: "follow_up" })
163
+ : (0, relationship_authorization_2.createRelationshipAuthorizationEvaluator)({ friend, registry, requestId: coordinates.requestId, requestPhase: "follow_up" });
164
+ if (authorization.subject.friendId !== coordinates.friendId)
165
+ throw new Error("Relationship await authority friend is ambiguous");
166
+ if (!authorization.authorizeTool("resolve_await").allowed)
167
+ throw new StaleRelationshipAwaitError("admission or profile is not active");
168
+ return { authorization, friend };
169
+ }
96
170
  function readAspirations(agentRoot) {
97
171
  try {
98
172
  return fs.readFileSync(path.join(agentRoot, "psyche", "ASPIRATIONS.md"), "utf8").trim();
@@ -728,6 +802,16 @@ function createSelfFriend(agentName) {
728
802
  schemaVersion: 1,
729
803
  };
730
804
  }
805
+ function buildOwnerPresentationPreferences(friend) {
806
+ const lines = [
807
+ `## ${friend.name}'s presentation preferences`,
808
+ "Use this relationship context to decide whether and how to contact the owner. It is presentation-only and grants no mutation authority.",
809
+ ];
810
+ lines.push(...(0, relationship_authorization_1.renderRelationshipPreferences)(friend));
811
+ if (lines.length === 2)
812
+ lines.push("- No presentation preferences have been learned yet; be calm, concise, and useful.");
813
+ return lines.join("\n");
814
+ }
731
815
  // No-op friend store for the private runtime. It doesn't track token usage per-friend.
732
816
  function createNoOpFriendStore() {
733
817
  return {
@@ -877,6 +961,7 @@ async function runPrivateRuntimeTurn(options) {
877
961
  let userContent;
878
962
  let habitTools;
879
963
  let habitParsedSuccessfully = false;
964
+ let parsedAwait = null;
880
965
  const isPayloadSpecificWake = !!options?.taskId
881
966
  || (reason === "habit" && !!options?.habitName)
882
967
  || (reason === "await" && !!options?.awaitName);
@@ -1010,6 +1095,7 @@ async function runPrivateRuntimeTurn(options) {
1010
1095
  try {
1011
1096
  const awaitContent = fs.readFileSync(awaitFilePath, "utf-8");
1012
1097
  const parsed = (0, await_runtime_state_1.applyAwaitRuntimeState)(agentRoot, (0, await_parser_1.parseAwaitFile)(awaitContent, awaitFilePath));
1098
+ parsedAwait = parsed;
1013
1099
  awaitFound = true;
1014
1100
  awaitBody = parsed.body || undefined;
1015
1101
  condition = parsed.condition;
@@ -1043,12 +1129,121 @@ async function runPrivateRuntimeTurn(options) {
1043
1129
  if (shouldUseHeldReturnWake) {
1044
1130
  userContent = buildHeldReturnWakeMessage();
1045
1131
  }
1046
- const userMessage = { role: "user", content: userContent };
1047
1132
  // ── Session loader: wraps existing session logic ──────────────────
1048
1133
  const innerCapabilities = (0, friends_1.getChannelCapabilities)("inner");
1049
1134
  const selfFriend = createSelfFriend(agentName);
1050
1135
  const selfContext = { friend: selfFriend, channel: innerCapabilities };
1051
1136
  const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
1137
+ const relationshipAwaitCoordinatesValue = parsedAwait ? relationshipAwaitCoordinates(parsedAwait) : null;
1138
+ const relationshipAwait = relationshipAwaitCoordinatesValue
1139
+ ? await (async () => {
1140
+ const agentRoot = (0, identity_1.getAgentRoot)(agentName);
1141
+ const assertLiveBinding = () => {
1142
+ const binding = relationshipAwaitCoordinatesValue.channel === "external-event"
1143
+ ? parsedAwait.wake_at && (0, tools_awaiting_1.hasActiveExternalEventAwait)(agentName, { recordPath: relationshipAwaitCoordinatesValue.key, awaitName: parsedAwait.name, wakeAt: parsedAwait.wake_at })
1144
+ ? { active: true }
1145
+ : { active: false, reason: "external event disposition is no longer active" }
1146
+ : (0, tools_awaiting_1.inspectRelationshipFollowUp)(agentRoot, {
1147
+ friendId: relationshipAwaitCoordinatesValue.friendId,
1148
+ channel: relationshipAwaitCoordinatesValue.channel,
1149
+ key: relationshipAwaitCoordinatesValue.key,
1150
+ requestId: relationshipAwaitCoordinatesValue.requestId,
1151
+ awaitName: parsedAwait.name,
1152
+ now: now().getTime(),
1153
+ });
1154
+ if (!binding.active) {
1155
+ (0, tools_awaiting_1.cancelStaleAwait)(agentRoot, agentName, parsedAwait.name, binding.reason);
1156
+ throw new Error(`Relationship await authority binding is missing, stale, or ambiguous: ${binding.reason}`);
1157
+ }
1158
+ };
1159
+ assertLiveBinding();
1160
+ const store = new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
1161
+ const resolve = () => resolveRelationshipAwaitAuthority(agentRoot, store, (0, relationship_authorization_2.loadRelationshipCapabilityRegistry)(agentRoot), relationshipAwaitCoordinatesValue);
1162
+ const resolveOrCancel = async () => {
1163
+ try {
1164
+ return await resolve();
1165
+ }
1166
+ catch (error) {
1167
+ if (error instanceof StaleRelationshipAwaitError)
1168
+ (0, tools_awaiting_1.cancelStaleAwait)(agentRoot, agentName, parsedAwait.name, error.reason);
1169
+ throw error;
1170
+ }
1171
+ };
1172
+ const initial = await resolveOrCancel();
1173
+ return {
1174
+ store,
1175
+ context: { friend: initial.friend, channel: innerCapabilities },
1176
+ currentSession: {
1177
+ friendId: relationshipAwaitCoordinatesValue.friendId,
1178
+ channel: relationshipAwaitCoordinatesValue.channel,
1179
+ key: relationshipAwaitCoordinatesValue.key,
1180
+ sessionPath: relationshipAwaitCoordinatesValue.channel === "telegram"
1181
+ ? (0, shared_turn_1.getSenseSessionPath)(agentName, relationshipAwaitCoordinatesValue.friendId, "telegram", relationshipAwaitCoordinatesValue.key, agentRoot)
1182
+ : sessionFilePath,
1183
+ },
1184
+ relationshipAuthorization: {
1185
+ ...(relationshipAwaitCoordinatesValue.requestId ? { requestId: relationshipAwaitCoordinatesValue.requestId } : {}),
1186
+ profileId: initial.authorization.profileId,
1187
+ authorizedContextScopes: initial.authorization.authorizedContextScopes,
1188
+ advertisedToolNames: relationshipAwaitCoordinatesValue.channel === "external-event" ? ["resolve_await"] : initial.authorization.advertisedToolNames,
1189
+ authorizeTool: async (name, args) => {
1190
+ assertLiveBinding();
1191
+ const decision = (await resolveOrCancel()).authorization.authorizeTool(name, args);
1192
+ return relationshipAwaitCoordinatesValue.channel !== "external-event" || name === "resolve_await"
1193
+ ? decision
1194
+ : { ...decision, allowed: false, reason: "external event await wake is resolve-only" };
1195
+ },
1196
+ },
1197
+ };
1198
+ })()
1199
+ : undefined;
1200
+ const externalEventRelationship = options?.externalEvent
1201
+ ? await (async () => {
1202
+ const agentRoot = (0, identity_1.getAgentRoot)(agentName);
1203
+ const store = new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
1204
+ const registry = (0, relationship_authorization_2.loadRelationshipCapabilityRegistry)(agentRoot);
1205
+ const resolve = () => (0, relationship_authorization_2.resolveProfileScopedRelationshipAuthorization)({
1206
+ store,
1207
+ registry,
1208
+ relationshipProfileId: "sanctuary-owner",
1209
+ profileId: "sanctuary-event",
1210
+ });
1211
+ const initial = await resolve();
1212
+ const initialDisposition = initial.authorizeTool("external_event_disposition");
1213
+ if (!initialDisposition.allowed)
1214
+ throw new Error(`external event relationship authority denied: ${initialDisposition.reason}`);
1215
+ const owners = (await store.listAll()).filter((friend) => friend.capabilityProfileId === "sanctuary-owner");
1216
+ const [owner] = owners;
1217
+ if (owners.length !== 1 || !owner)
1218
+ throw new Error("external event owner presentation context must resolve to exactly one Friend");
1219
+ return {
1220
+ store,
1221
+ context: { friend: owner, channel: innerCapabilities },
1222
+ ownerPresentationPreferences: buildOwnerPresentationPreferences(owner),
1223
+ relationshipAuthorization: {
1224
+ profileId: initial.profileId,
1225
+ authorizedContextScopes: initial.authorizedContextScopes,
1226
+ advertisedToolNames: initial.advertisedToolNames,
1227
+ authorizeTool: async (name) => (await resolve()).authorizeTool(name),
1228
+ },
1229
+ externalEventAuthority: {
1230
+ authorizeDisposition: () => {
1231
+ const decision = initial.authorizeTool("external_event_disposition");
1232
+ return decision.allowed ? { allowed: true, reason: "relationship-authorized" } : { allowed: false, reason: decision.reason };
1233
+ },
1234
+ },
1235
+ externalEventEffects: {
1236
+ deliverOwnerDecision: async (input) => {
1237
+ const { sendTelegramExternalEventDecision } = await Promise.resolve().then(() => __importStar(require("./telegram")));
1238
+ await sendTelegramExternalEventDecision(agentName, input);
1239
+ },
1240
+ },
1241
+ };
1242
+ })()
1243
+ : undefined;
1244
+ if (externalEventRelationship)
1245
+ userContent = `${userContent}\n\n${externalEventRelationship.ownerPresentationPreferences}`;
1246
+ const userMessage = { role: "user", content: userContent };
1052
1247
  // ── Habit tool enforcement ───────────────────────────────────────
1053
1248
  let habitToolsResolved;
1054
1249
  if (habitTools !== undefined) {
@@ -1076,6 +1271,12 @@ async function runPrivateRuntimeTurn(options) {
1076
1271
  if (options?.noSend === true) {
1077
1272
  habitToolsResolved = [];
1078
1273
  }
1274
+ const externalEventToolsResolved = options?.externalEvent
1275
+ ? (0, tools_1.getSanctuaryRelationshipTools)(externalEventRelationship.relationshipAuthorization.advertisedToolNames)
1276
+ : undefined;
1277
+ const relationshipAwaitToolsResolved = relationshipAwait
1278
+ ? (0, tools_1.getSanctuaryRelationshipTools)(relationshipAwait.relationshipAuthorization.advertisedToolNames)
1279
+ : undefined;
1079
1280
  const effectiveHabitSession = options?.noSend === true
1080
1281
  ? reduceHabitSessionToNoSend(options.habitSession)
1081
1282
  : options?.habitSession;
@@ -1105,71 +1306,117 @@ async function runPrivateRuntimeTurn(options) {
1105
1306
  const traceId = (0, nerves_1.createTraceId)();
1106
1307
  // Attention queue: built when pending messages are drained, shared with tool context
1107
1308
  let attentionQueue = [];
1108
- const result = await (0, pipeline_1.handleInboundTurn)({
1109
- channel: "inner",
1110
- sessionKey: "dialog",
1111
- capabilities: innerCapabilities,
1112
- messages: [userMessage],
1113
- continuityIngressTexts: [],
1114
- callbacks,
1115
- sessionTurnLease,
1116
- friendResolver: { resolve: () => Promise.resolve(selfContext) },
1117
- sessionLoader,
1118
- pendingDir,
1119
- friendStore: createNoOpFriendStore(),
1120
- enforceTrustGate: trust_gate_1.enforceTrustGate,
1121
- drainPending: pending_1.drainPending,
1122
- runAgent: core_1.runAgent,
1123
- postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
1124
- const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
1125
- return (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
1126
- },
1127
- accumulateFriendTokens: friends_1.accumulateFriendTokens,
1128
- signal: options?.signal,
1129
- /* v8 ignore start -- attention queue: callback invoked by pipeline during pending drain; tested via attention-queue unit tests @preserve */
1130
- onPendingDrained: (drained) => {
1131
- const outstandingObligations = (0, obligations_1.listActiveReturnObligations)(agentName);
1132
- const builtAttentionQueue = (0, attention_queue_1.buildAttentionQueue)({
1133
- drainedPending: drained,
1134
- outstandingObligations,
1135
- friendNameResolver: (friendId) => {
1136
- try {
1137
- const raw = fs.readFileSync(path.join((0, identity_1.getAgentRoot)(agentName), "friends", friendId + ".json"), "utf-8");
1138
- const parsed = JSON.parse(raw);
1139
- return typeof parsed.name === "string" ? parsed.name : null;
1140
- }
1141
- catch {
1142
- return null;
1143
- }
1144
- },
1145
- packetResolver: (packetId) => {
1146
- try {
1147
- return (0, packets_1.readPonderPacket)((0, identity_1.getAgentRoot)(agentName), packetId);
1148
- }
1149
- catch {
1150
- return null;
1151
- }
1309
+ const externalRunStartedAt = now().toISOString();
1310
+ const externalRunBase = options?.externalEvent ? {
1311
+ agent: agentName,
1312
+ triggerType: "inbound",
1313
+ sourceKind: "private-runtime",
1314
+ senseOrHabit: "external-event",
1315
+ target: { source: options.externalEvent.source, eventId: options.externalEvent.eventId, generation: options.externalEvent.generation, observationRevision: options.externalEvent.observationRevision },
1316
+ idempotencyScope: { recordPath: options.externalEvent.recordPath, generation: options.externalEvent.generation, observationRevision: options.externalEvent.observationRevision },
1317
+ startedAt: externalRunStartedAt,
1318
+ } : null;
1319
+ if (externalRunBase)
1320
+ (0, run_ledger_1.appendRunLedgerRecordNonFatal)((0, identity_1.getAgentRoot)(agentName), (0, run_ledger_1.createRunLedgerRecord)({ ...externalRunBase, lifecycle: "started" }));
1321
+ let result;
1322
+ try {
1323
+ result = await (0, pipeline_1.handleInboundTurn)({
1324
+ channel: "inner",
1325
+ sessionKey: "dialog",
1326
+ capabilities: innerCapabilities,
1327
+ messages: [userMessage],
1328
+ continuityIngressTexts: [],
1329
+ callbacks,
1330
+ sessionTurnLease,
1331
+ friendResolver: { resolve: () => Promise.resolve(selfContext) },
1332
+ sessionLoader,
1333
+ pendingDir,
1334
+ friendStore: createNoOpFriendStore(),
1335
+ enforceTrustGate: trust_gate_1.enforceTrustGate,
1336
+ drainPending: pending_1.drainPending,
1337
+ runAgent: core_1.runAgent,
1338
+ postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
1339
+ const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
1340
+ return (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
1341
+ },
1342
+ accumulateFriendTokens: friends_1.accumulateFriendTokens,
1343
+ signal: options?.signal,
1344
+ /* v8 ignore start -- attention queue: callback invoked by pipeline during pending drain; tested via attention-queue unit tests @preserve */
1345
+ onPendingDrained: (drained) => {
1346
+ const outstandingObligations = (0, obligations_1.listActiveReturnObligations)(agentName);
1347
+ const builtAttentionQueue = (0, attention_queue_1.buildAttentionQueue)({
1348
+ drainedPending: drained,
1349
+ outstandingObligations,
1350
+ friendNameResolver: (friendId) => {
1351
+ try {
1352
+ const raw = fs.readFileSync(path.join((0, identity_1.getAgentRoot)(agentName), "friends", friendId + ".json"), "utf-8");
1353
+ const parsed = JSON.parse(raw);
1354
+ return typeof parsed.name === "string" ? parsed.name : null;
1355
+ }
1356
+ catch {
1357
+ return null;
1358
+ }
1359
+ },
1360
+ packetResolver: (packetId) => {
1361
+ try {
1362
+ return (0, packets_1.readPonderPacket)((0, identity_1.getAgentRoot)(agentName), packetId);
1363
+ }
1364
+ catch {
1365
+ return null;
1366
+ }
1367
+ },
1368
+ });
1369
+ attentionQueue.splice(0, attentionQueue.length, ...builtAttentionQueue);
1370
+ const attentionFrame = (0, attention_queue_1.buildAttentionQueueStatusFrame)(attentionQueue);
1371
+ return attentionFrame ? [attentionFrame] : [];
1372
+ },
1373
+ /* v8 ignore stop */
1374
+ runAgentOptions: {
1375
+ traceId,
1376
+ toolChoiceRequired: true,
1377
+ mcpManager,
1378
+ ...(options?.externalEvent
1379
+ ? { tools: externalEventToolsResolved }
1380
+ : relationshipAwaitToolsResolved !== undefined ? { tools: relationshipAwaitToolsResolved }
1381
+ : habitToolsResolved !== undefined ? { tools: habitToolsResolved } : {}),
1382
+ toolContext: {
1383
+ signin: async () => undefined,
1384
+ delegatedOrigins: attentionQueue,
1385
+ ...(relationshipAwait ? {
1386
+ friendStore: relationshipAwait.store,
1387
+ context: relationshipAwait.context,
1388
+ currentSession: relationshipAwait.currentSession,
1389
+ relationshipAuthorization: relationshipAwait.relationshipAuthorization,
1390
+ } : {}),
1391
+ ...(options?.externalEvent ? {
1392
+ currentExternalEvent: Object.freeze({ ...options.externalEvent }),
1393
+ friendStore: externalEventRelationship.store,
1394
+ context: externalEventRelationship.context,
1395
+ relationshipAuthorization: externalEventRelationship.relationshipAuthorization,
1396
+ externalEventAuthority: externalEventRelationship.externalEventAuthority,
1397
+ externalEventEffects: externalEventRelationship.externalEventEffects,
1398
+ ...(["sanctuary-health", "sanctuary-usenet"].includes(options.externalEvent.source) ? (0, sanctuary_runtime_1.createSanctuaryToolContext)(agentName) : {}),
1399
+ } : {}),
1400
+ ...(options?.noSend ? { noSend: true } : {}),
1401
+ ...(effectiveHabitSession ? { habitSession: effectiveHabitSession } : {}),
1152
1402
  },
1153
- });
1154
- attentionQueue.splice(0, attentionQueue.length, ...builtAttentionQueue);
1155
- const attentionFrame = (0, attention_queue_1.buildAttentionQueueStatusFrame)(attentionQueue);
1156
- return attentionFrame ? [attentionFrame] : [];
1157
- },
1158
- /* v8 ignore stop */
1159
- runAgentOptions: {
1160
- traceId,
1161
- toolChoiceRequired: true,
1162
- mcpManager,
1163
- ...(habitToolsResolved !== undefined && { tools: habitToolsResolved }),
1164
- toolContext: {
1165
- signin: async () => undefined,
1166
- delegatedOrigins: attentionQueue,
1167
- ...(options?.noSend ? { noSend: true } : {}),
1168
1403
  ...(effectiveHabitSession ? { habitSession: effectiveHabitSession } : {}),
1169
1404
  },
1170
- ...(effectiveHabitSession ? { habitSession: effectiveHabitSession } : {}),
1171
- },
1172
- });
1405
+ });
1406
+ if (externalRunBase) {
1407
+ const lifecycle = result.turnOutcome === "settled" || result.turnOutcome === "observed" || result.turnOutcome === "rested"
1408
+ ? "completed"
1409
+ : result.turnOutcome === "errored" ? "error"
1410
+ : result.turnOutcome === "blocked" || result.turnOutcome === "suspended" ? "blocked"
1411
+ : "skipped";
1412
+ (0, run_ledger_1.appendRunLedgerRecordNonFatal)((0, identity_1.getAgentRoot)(agentName), (0, run_ledger_1.createRunLedgerRecord)({ ...externalRunBase, lifecycle, endedAt: now().toISOString(), usage: (0, run_ledger_1.usageMetadataFromUsageData)(result.usage, result.usage ? "provider" : "reported-unavailable"), ...(result.turnOutcome === "errored" ? { errorName: "AgentTurnError" } : {}) }));
1413
+ }
1414
+ }
1415
+ catch (error) {
1416
+ if (externalRunBase)
1417
+ (0, run_ledger_1.appendRunLedgerRecordNonFatal)((0, identity_1.getAgentRoot)(agentName), (0, run_ledger_1.createRunLedgerRecord)({ ...externalRunBase, lifecycle: "error", endedAt: now().toISOString(), errorName: error instanceof Error ? error.name : "UnknownError" }));
1418
+ throw error;
1419
+ }
1173
1420
  // Post-turn routeDelegatedCompletion removed: delivery is now inline via surface tool.
1174
1421
  // settle in the private runtime produces no CompletionMetadata, so routeDelegatedCompletion
1175
1422
  // would be a no-op. The routing infrastructure is reused by the surface handler.
@@ -65,7 +65,7 @@ const TARGET_ENDPOINT = "https://books.mendelow.cloud/";
65
65
  const CRON_MARKER = "# ouro:habit:sanctuary:sanctuary:sanctuary-health";
66
66
  const CRON_COMMAND = "*/15 * * * * /usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js poke sanctuary --habit sanctuary-health --trigger cron";
67
67
  const PROCESS_ENTRY = "/opt/ouro/dist/senses/sanctuary-health-acceptance-probe-entry.js";
68
- const LABELS = ["unit-16f-cron-fingerprint", "unit-16g-health-transition", "unit-16h-daily-digest"];
68
+ const LABELS = ["unit-16f-cron-fingerprint", "unit-16g-health-transition", "unit-16h-acceptance-delivery-probe"];
69
69
  function digestBytes(value) {
70
70
  return (0, node_crypto_1.createHash)("sha256").update(value).digest("hex");
71
71
  }
@@ -505,7 +505,7 @@ async function runSanctuaryHealthAcceptanceProbe(input, dependencies = createSan
505
505
  const cronBefore = fs.readFileSync(cronPath, "utf8");
506
506
  const cronFingerprintBefore = digestBytes(cronBefore);
507
507
  const cronRegisteredBefore = canonicalCron(cronBefore);
508
- const effectiveNow = input.label === "unit-16h-daily-digest" ? exactLocalDailyBoundary(dependencies.now()) : dependencies.now();
508
+ const effectiveNow = input.label === "unit-16h-acceptance-delivery-probe" ? exactLocalDailyBoundary(dependencies.now()) : dependencies.now();
509
509
  let fixture;
510
510
  let expectedIncidentDigest = "";
511
511
  let realCheckEquivalent = false;
@@ -539,7 +539,7 @@ async function runSanctuaryHealthAcceptanceProbe(input, dependencies = createSan
539
539
  else
540
540
  await (0, sanctuary_health_1.withSanctuaryHealthStateLease)(statePath, async (lease) => {
541
541
  expectedIncidentDigest = await normalizeWorkingState({ workspace, statePath, deps: dependencies, effectiveNow });
542
- if (input.label === "unit-16h-daily-digest") {
542
+ if (input.label === "unit-16h-acceptance-delivery-probe") {
543
543
  const state = readHealthState(statePath);
544
544
  state.incidents[`endpoint:${TARGET_ENDPOINT}`] = { id: `endpoint:${TARGET_ENDPOINT}`, summary: `${TARGET_ENDPOINT} returned 503` };
545
545
  state.lastDigestDay = localDay(new Date(effectiveNow.getTime() - 36 * 60 * 60 * 1_000));
@@ -570,8 +570,8 @@ async function runSanctuaryHealthAcceptanceProbe(input, dependencies = createSan
570
570
  }
571
571
  else {
572
572
  fixture = await createSanctuaryHealthAcceptanceLoopbackFixture([503, 503], dependencies.ambientFetch, dependencies.createLoopbackServer);
573
- await runPhase("digest-boundary", "acceptance", 503, fixture.fetch, effectiveNow);
574
- await runPhase("digest-repeat", "acceptance", 503, fixture.fetch, effectiveNow);
573
+ await runPhase("delivery-probe-first", "acceptance", 503, fixture.fetch, effectiveNow);
574
+ await runPhase("delivery-probe-repeat", "acceptance", 503, fixture.fetch, effectiveNow);
575
575
  }
576
576
  await closeServer(fixture.server);
577
577
  socketAbsent = fixture.server.address() === null;
@@ -602,7 +602,7 @@ async function runSanctuaryHealthAcceptanceProbe(input, dependencies = createSan
602
602
  cronDegradedBefore: !cronRegisteredBefore,
603
603
  cronDegradedAfter: !cronRegisteredAfter,
604
604
  fixtureSequenceDigest: digestJson(observedFixtureSequence),
605
- clockMode: input.label === "unit-16h-daily-digest" ? "local-daily-boundary" : "ambient",
605
+ clockMode: input.label === "unit-16h-acceptance-delivery-probe" ? "local-daily-boundary" : "ambient",
606
606
  effectiveNow: effectiveNow.toISOString(),
607
607
  phases,
608
608
  providerInvocationCount,
@@ -615,6 +615,8 @@ async function runSanctuaryHealthAcceptanceProbe(input, dependencies = createSan
615
615
  productionRestored: beforeStateDigest === restoredStateDigest && cronFingerprintBefore === cronFingerprintAfter
616
616
  && cronRegisteredBefore && cronRegisteredAfter && socketAbsent && realCheckEquivalent,
617
617
  schedulerReceipt,
618
+ acceptanceOnly: true,
619
+ productionScheduleChanged: false,
618
620
  };
619
621
  if (input.label === "unit-16f-cron-fingerprint")
620
622
  receipt.productionRestored = cronFingerprintBefore === cronFingerprintAfter