@ouro.bot/cli 0.1.0-alpha.665 → 0.1.0-alpha.667
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 +13 -0
- package/dist/arc/flight-recorder.js +324 -5
- package/dist/heart/core.js +167 -4
- package/dist/heart/cross-chat-delivery.js +3 -2
- package/dist/heart/daemon/cli-exec.js +139 -1
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +138 -2
- package/dist/heart/daemon/daemon-entry.js +24 -5
- package/dist/heart/daemon/daemon.js +10 -1
- package/dist/heart/habits/habit-parser.js +8 -0
- package/dist/heart/habits/habit-runtime-state.js +17 -3
- package/dist/heart/habits/habit-scheduler.js +24 -5
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +618 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +29 -1
- package/dist/heart/mailbox/mailbox-http-routes.js +122 -1
- package/dist/heart/mailbox/mailbox-read.js +5 -1
- package/dist/heart/mailbox/readers/runtime-readers.js +87 -0
- package/dist/mailbox-ui/assets/index-CaTIFDmv.js +1 -0
- package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
- package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
- package/dist/mailbox-ui/index.html +3 -2
- package/dist/repertoire/tools-notes.js +50 -0
- package/dist/repertoire/tools-record.js +13 -0
- package/dist/repertoire/tools-session.js +140 -0
- package/dist/repertoire/tools-surface.js +11 -0
- package/dist/repertoire/tools.js +7 -0
- package/dist/senses/habit-turn-message.js +41 -3
- package/dist/senses/inner-dialog-worker.js +264 -68
- package/dist/senses/inner-dialog.js +29 -15
- package/dist/senses/pipeline.js +2 -11
- package/dist/senses/surface-tool.js +2 -1
- package/package.json +1 -1
- package/dist/mailbox-ui/assets/index-BZ60na8O.js +0 -61
- package/dist/mailbox-ui/assets/index-DG6Xf5uL.css +0 -1
|
@@ -0,0 +1,618 @@
|
|
|
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.isSafeHabitRunId = void 0;
|
|
37
|
+
exports.readLatestHabitSessionState = readLatestHabitSessionState;
|
|
38
|
+
exports.createHabitSessionPaths = createHabitSessionPaths;
|
|
39
|
+
exports.normalizeHabitPermissionEnvelope = normalizeHabitPermissionEnvelope;
|
|
40
|
+
exports.resolveHabitReturnRoute = resolveHabitReturnRoute;
|
|
41
|
+
exports.filterHabitToolsForEnvelope = filterHabitToolsForEnvelope;
|
|
42
|
+
exports.buildHabitRunReceipt = buildHabitRunReceipt;
|
|
43
|
+
exports.completeHabitRun = completeHabitRun;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const flight_recorder_1 = require("../../arc/flight-recorder");
|
|
47
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
48
|
+
const cadence_1 = require("../daemon/cadence");
|
|
49
|
+
const habit_runtime_state_1 = require("./habit-runtime-state");
|
|
50
|
+
var flight_recorder_2 = require("../../arc/flight-recorder");
|
|
51
|
+
Object.defineProperty(exports, "isSafeHabitRunId", { enumerable: true, get: function () { return flight_recorder_2.isSafeHabitRunId; } });
|
|
52
|
+
function habitSessionRoot(agentRoot) {
|
|
53
|
+
return path.join(agentRoot, "state", "habit-sessions");
|
|
54
|
+
}
|
|
55
|
+
function isHabitRuntimeStateSnapshot(value, habitName) {
|
|
56
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
57
|
+
return false;
|
|
58
|
+
const record = value;
|
|
59
|
+
return record.schemaVersion === 1
|
|
60
|
+
&& record.name === habitName
|
|
61
|
+
&& typeof record.lastRun === "string"
|
|
62
|
+
&& typeof record.updatedAt === "string"
|
|
63
|
+
&& (record.activeOperationId === undefined || record.activeOperationId === null || typeof record.activeOperationId === "string")
|
|
64
|
+
&& (record.latestRunId === undefined || record.latestRunId === null || typeof record.latestRunId === "string")
|
|
65
|
+
&& (record.latestReceiptLocator === undefined || record.latestReceiptLocator === null || typeof record.latestReceiptLocator === "string");
|
|
66
|
+
}
|
|
67
|
+
function runtimeCursorValue(value) {
|
|
68
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
69
|
+
}
|
|
70
|
+
function readHabitRuntimeStateSnapshot(agentRoot, habitName) {
|
|
71
|
+
const runtimeStatePath = path.join(agentRoot, "state", "habits", `${habitName}.json`);
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(fs.readFileSync(runtimeStatePath, "utf-8"));
|
|
74
|
+
if (isHabitRuntimeStateSnapshot(parsed, habitName)) {
|
|
75
|
+
return {
|
|
76
|
+
schemaVersion: 1,
|
|
77
|
+
name: parsed.name,
|
|
78
|
+
lastRun: parsed.lastRun,
|
|
79
|
+
updatedAt: parsed.updatedAt,
|
|
80
|
+
activeOperationId: runtimeCursorValue(parsed.activeOperationId),
|
|
81
|
+
latestRunId: runtimeCursorValue(parsed.latestRunId),
|
|
82
|
+
latestReceiptLocator: runtimeCursorValue(parsed.latestReceiptLocator),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
(0, runtime_1.emitNervesEvent)({
|
|
86
|
+
level: "warn",
|
|
87
|
+
component: "daemon",
|
|
88
|
+
event: "daemon.habit_runtime_state_malformed",
|
|
89
|
+
message: "habit runtime state could not be used for recovery",
|
|
90
|
+
meta: { agentRoot, habitName, runtimeStatePath },
|
|
91
|
+
});
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function readLatestHabitSessionState(agentRoot, options = {}) {
|
|
99
|
+
const receipt = (0, flight_recorder_1.listHabitRunReceipts)(agentRoot)
|
|
100
|
+
.find((entry) => options.habitName === undefined || entry.habitName === options.habitName);
|
|
101
|
+
if (!receipt) {
|
|
102
|
+
(0, runtime_1.emitNervesEvent)({
|
|
103
|
+
component: "daemon",
|
|
104
|
+
event: "daemon.habit_session_recovery_state_empty",
|
|
105
|
+
message: "no habit session receipt available for recovery",
|
|
106
|
+
meta: { agentRoot, habitName: options.habitName ?? null },
|
|
107
|
+
});
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const state = {
|
|
111
|
+
receipt,
|
|
112
|
+
runtimeState: readHabitRuntimeStateSnapshot(agentRoot, receipt.habitName),
|
|
113
|
+
};
|
|
114
|
+
(0, runtime_1.emitNervesEvent)({
|
|
115
|
+
component: "daemon",
|
|
116
|
+
event: "daemon.habit_session_recovery_state_read",
|
|
117
|
+
message: "habit session recovery state read",
|
|
118
|
+
meta: { agentRoot, habitName: receipt.habitName, runId: receipt.runId, hasRuntimeState: state.runtimeState !== null },
|
|
119
|
+
});
|
|
120
|
+
return state;
|
|
121
|
+
}
|
|
122
|
+
function createHabitSessionPaths(agentRoot, runId, habitName = "heartbeat") {
|
|
123
|
+
if (!(0, flight_recorder_1.isSafeHabitRunId)(runId)) {
|
|
124
|
+
(0, runtime_1.emitNervesEvent)({
|
|
125
|
+
level: "warn",
|
|
126
|
+
component: "daemon",
|
|
127
|
+
event: "daemon.habit_session_unsafe_run_id",
|
|
128
|
+
message: "unsafe habit session run id rejected",
|
|
129
|
+
meta: { agentRoot, runId },
|
|
130
|
+
});
|
|
131
|
+
throw new Error(`unsafe habit run id: ${runId}`);
|
|
132
|
+
}
|
|
133
|
+
const sessionLocator = `state/habit-sessions/${runId}/session.json`;
|
|
134
|
+
const pendingLocator = `state/habit-sessions/${runId}/pending`;
|
|
135
|
+
const runtimeStateLocator = `state/habits/${habitName}.json`;
|
|
136
|
+
const receiptLocator = `arc/flight-recorder/habit-receipts/${runId}.json`;
|
|
137
|
+
return {
|
|
138
|
+
runDir: path.join(habitSessionRoot(agentRoot), runId),
|
|
139
|
+
sessionPath: path.join(habitSessionRoot(agentRoot), runId, "session.json"),
|
|
140
|
+
pendingDir: path.join(habitSessionRoot(agentRoot), runId, "pending"),
|
|
141
|
+
runtimeStatePath: path.join(agentRoot, "state", "habits", `${habitName}.json`),
|
|
142
|
+
receiptPath: path.join(agentRoot, "arc", "flight-recorder", "habit-receipts", `${runId}.json`),
|
|
143
|
+
sessionLocator,
|
|
144
|
+
pendingLocator,
|
|
145
|
+
runtimeStateLocator,
|
|
146
|
+
receiptLocator,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function normalizeIdentifier(value) {
|
|
150
|
+
return value.trim().toLowerCase();
|
|
151
|
+
}
|
|
152
|
+
function isSelfTarget(friendId, channel, key) {
|
|
153
|
+
return normalizeIdentifier(friendId) === "self"
|
|
154
|
+
|| normalizeIdentifier(channel ?? "") === "inner"
|
|
155
|
+
|| `${normalizeIdentifier(friendId)}/${normalizeIdentifier(channel ?? "")}/${normalizeIdentifier(key ?? "")}` === "self/inner/dialog";
|
|
156
|
+
}
|
|
157
|
+
function decodedSegment(value) {
|
|
158
|
+
try {
|
|
159
|
+
return decodeURIComponent(value);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function isUnsafePathSegment(value) {
|
|
166
|
+
const raw = value.trim();
|
|
167
|
+
const decoded = decodedSegment(raw);
|
|
168
|
+
const decodedTrimmed = decoded === null ? null : decoded.trim();
|
|
169
|
+
return raw.length === 0
|
|
170
|
+
|| decodedTrimmed === null
|
|
171
|
+
|| raw === "."
|
|
172
|
+
|| raw === ".."
|
|
173
|
+
|| decodedTrimmed === "."
|
|
174
|
+
|| decodedTrimmed === ".."
|
|
175
|
+
|| raw.includes("..")
|
|
176
|
+
|| decodedTrimmed.includes("..")
|
|
177
|
+
|| raw.includes("/")
|
|
178
|
+
|| raw.includes("\\")
|
|
179
|
+
|| decodedTrimmed.includes("/")
|
|
180
|
+
|| decodedTrimmed.includes("\\");
|
|
181
|
+
}
|
|
182
|
+
function unsafeRouteSegmentReason(friendId, channel, key) {
|
|
183
|
+
const segments = [
|
|
184
|
+
["friendId", friendId],
|
|
185
|
+
["channel", channel],
|
|
186
|
+
["key", key],
|
|
187
|
+
];
|
|
188
|
+
for (const [name, value] of segments) {
|
|
189
|
+
if (value.length > 0 && isUnsafePathSegment(value))
|
|
190
|
+
return `unsafe route ${name}: ${value}`;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
async function resolveFriend(friendStore, rawFriendIdOrName) {
|
|
195
|
+
const raw = rawFriendIdOrName.trim();
|
|
196
|
+
if (!raw)
|
|
197
|
+
return null;
|
|
198
|
+
const direct = await friendStore?.get(raw);
|
|
199
|
+
if (direct) {
|
|
200
|
+
return { id: direct.id, name: direct.name, trustLevel: direct.trustLevel, isSelf: isSelfTarget(direct.id) };
|
|
201
|
+
}
|
|
202
|
+
const all = await friendStore?.listAll?.();
|
|
203
|
+
const lowered = normalizeIdentifier(raw);
|
|
204
|
+
const matched = all?.find((record) => normalizeIdentifier(record.id) === lowered || normalizeIdentifier(record.name) === lowered);
|
|
205
|
+
if (matched) {
|
|
206
|
+
return { id: matched.id, name: matched.name, trustLevel: matched.trustLevel, isSelf: isSelfTarget(matched.id) };
|
|
207
|
+
}
|
|
208
|
+
if (!friendStore)
|
|
209
|
+
return { id: raw, name: raw, isSelf: false };
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
function unresolvedRoute(kind, recipient, reason) {
|
|
213
|
+
return { kind, recipient, status: "unresolved", reason };
|
|
214
|
+
}
|
|
215
|
+
function allowedRoute(kind, recipient, friendId, channel, key) {
|
|
216
|
+
return {
|
|
217
|
+
kind,
|
|
218
|
+
recipient,
|
|
219
|
+
status: "allowed",
|
|
220
|
+
...(friendId ? { friendId } : {}),
|
|
221
|
+
...(channel ? { channel } : {}),
|
|
222
|
+
...(key ? { key } : {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
async function resolveExactRoute(kind, recipient, channel, key, options) {
|
|
226
|
+
const routeRecipient = `${recipient}/${channel}/${key}`;
|
|
227
|
+
const unsafeReason = unsafeRouteSegmentReason(recipient, channel, key);
|
|
228
|
+
if (unsafeReason) {
|
|
229
|
+
return { route: unresolvedRoute(kind, routeRecipient, unsafeReason), warning: unsafeReason };
|
|
230
|
+
}
|
|
231
|
+
if (isSelfTarget(recipient, channel, key)) {
|
|
232
|
+
const warning = `${kind} route targets self/inner and cannot be used by a habit`;
|
|
233
|
+
return { route: unresolvedRoute(kind, routeRecipient, warning), warning };
|
|
234
|
+
}
|
|
235
|
+
const friend = await resolveFriend(options.friendStore, recipient);
|
|
236
|
+
if (!friend) {
|
|
237
|
+
const warning = `${kind} route recipient unresolved: ${recipient}`;
|
|
238
|
+
return { route: unresolvedRoute(kind, recipient, warning), warning };
|
|
239
|
+
}
|
|
240
|
+
const resolvedUnsafeReason = unsafeRouteSegmentReason(friend.id, channel, key);
|
|
241
|
+
if (resolvedUnsafeReason) {
|
|
242
|
+
return { route: unresolvedRoute(kind, routeRecipient, resolvedUnsafeReason), warning: resolvedUnsafeReason };
|
|
243
|
+
}
|
|
244
|
+
if (friend.isSelf || isSelfTarget(friend.id, channel, key)) {
|
|
245
|
+
const warning = `${kind} route targets self/inner and cannot be used by a habit`;
|
|
246
|
+
return { route: unresolvedRoute(kind, routeRecipient, warning), warning };
|
|
247
|
+
}
|
|
248
|
+
return { route: allowedRoute(kind, recipient, friend.id, channel, key) };
|
|
249
|
+
}
|
|
250
|
+
function parseExtraRouteSpec(spec) {
|
|
251
|
+
const parts = spec.split("/").map((part) => part.trim());
|
|
252
|
+
if (parts.length !== 3 || parts.some((part) => part.length === 0))
|
|
253
|
+
return null;
|
|
254
|
+
return { recipient: parts[0], channel: parts[1], key: parts[2] };
|
|
255
|
+
}
|
|
256
|
+
async function normalizeHabitPermissionEnvelope(habit, options) {
|
|
257
|
+
const returnRoutes = [];
|
|
258
|
+
const warnings = [];
|
|
259
|
+
if (habit.surface.family) {
|
|
260
|
+
returnRoutes.push(allowedRoute("family", "family"));
|
|
261
|
+
}
|
|
262
|
+
if (habit.surface.originator) {
|
|
263
|
+
if (habit.origin) {
|
|
264
|
+
const resolved = await resolveExactRoute("originator", habit.origin.friendId, habit.origin.channel, habit.origin.key, options);
|
|
265
|
+
returnRoutes.push(resolved.route);
|
|
266
|
+
if (resolved.warning)
|
|
267
|
+
warnings.push(resolved.warning);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const warning = "originator route requested but habit has no origin";
|
|
271
|
+
returnRoutes.push(unresolvedRoute("originator", "originator", warning));
|
|
272
|
+
warnings.push(warning);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
for (const spec of habit.surface.extra) {
|
|
276
|
+
const parsed = parseExtraRouteSpec(spec);
|
|
277
|
+
if (!parsed) {
|
|
278
|
+
const warning = `malformed extra route: ${spec}`;
|
|
279
|
+
returnRoutes.push(unresolvedRoute("extra", spec, warning));
|
|
280
|
+
warnings.push(warning);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const resolved = await resolveExactRoute("extra", parsed.recipient, parsed.channel, parsed.key, options);
|
|
284
|
+
returnRoutes.push(resolved.route);
|
|
285
|
+
if (resolved.warning)
|
|
286
|
+
warnings.push(resolved.warning);
|
|
287
|
+
}
|
|
288
|
+
const canMessageOutward = returnRoutes.some((route) => route.status === "allowed");
|
|
289
|
+
const envelope = {
|
|
290
|
+
schemaVersion: 1,
|
|
291
|
+
canMessageOutward,
|
|
292
|
+
returnRoutes,
|
|
293
|
+
deniedTools: canMessageOutward ? [] : ["send_message", "surface"],
|
|
294
|
+
warnings,
|
|
295
|
+
};
|
|
296
|
+
(0, runtime_1.emitNervesEvent)({
|
|
297
|
+
component: "daemon",
|
|
298
|
+
event: "daemon.habit_permission_envelope_normalized",
|
|
299
|
+
message: "habit permission envelope normalized",
|
|
300
|
+
meta: { agentRoot: options.agentRoot, habitName: habit.name, routes: returnRoutes.length, canMessageOutward },
|
|
301
|
+
});
|
|
302
|
+
return envelope;
|
|
303
|
+
}
|
|
304
|
+
function readString(value) {
|
|
305
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
306
|
+
}
|
|
307
|
+
function resolveRouteTarget(input) {
|
|
308
|
+
if (input.toolName === "send_message") {
|
|
309
|
+
const friendId = readString(input.args.friendId);
|
|
310
|
+
const channel = readString(input.args.channel);
|
|
311
|
+
const key = readString(input.args.key);
|
|
312
|
+
if (!friendId || !channel || !key)
|
|
313
|
+
return null;
|
|
314
|
+
return {
|
|
315
|
+
friendId,
|
|
316
|
+
channel,
|
|
317
|
+
key,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (input.toolName !== "surface")
|
|
321
|
+
return null;
|
|
322
|
+
const delegationId = readString(input.args.delegationId);
|
|
323
|
+
if (delegationId) {
|
|
324
|
+
const origin = input.delegatedOrigins?.find((item) => item.id === delegationId);
|
|
325
|
+
return origin ? { friendId: origin.friendId, channel: origin.channel, key: origin.key } : null;
|
|
326
|
+
}
|
|
327
|
+
const friendId = readString(input.args.friendId);
|
|
328
|
+
if (friendId) {
|
|
329
|
+
const channel = readString(input.args.channel);
|
|
330
|
+
const key = readString(input.args.key);
|
|
331
|
+
if (!channel || !key)
|
|
332
|
+
return null;
|
|
333
|
+
return {
|
|
334
|
+
friendId,
|
|
335
|
+
channel,
|
|
336
|
+
key,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (input.delegatedOrigins?.length === 1) {
|
|
340
|
+
const [origin] = input.delegatedOrigins;
|
|
341
|
+
return { friendId: origin.friendId, channel: origin.channel, key: origin.key };
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
function isLiveVoiceAttempt(args, target) {
|
|
346
|
+
const channel = normalizeIdentifier(readString(args.channel) ?? target?.channel ?? "");
|
|
347
|
+
return channel === "voice"
|
|
348
|
+
|| readString(args.phoneNumber) !== null
|
|
349
|
+
|| Object.keys(args).some((key) => normalizeIdentifier(key).includes("voice") || normalizeIdentifier(key).includes("audio"));
|
|
350
|
+
}
|
|
351
|
+
function exactRouteMatches(route, target) {
|
|
352
|
+
return route.status === "allowed"
|
|
353
|
+
&& route.kind !== "family"
|
|
354
|
+
&& route.friendId === target.friendId
|
|
355
|
+
&& route.channel === target.channel
|
|
356
|
+
&& route.key === target.key;
|
|
357
|
+
}
|
|
358
|
+
function deniedRoute(reason) {
|
|
359
|
+
return { allowed: false, reason };
|
|
360
|
+
}
|
|
361
|
+
async function resolveHabitReturnRoute(input) {
|
|
362
|
+
const target = resolveRouteTarget(input);
|
|
363
|
+
if (isLiveVoiceAttempt(input.args, target)) {
|
|
364
|
+
return deniedRoute("live voice routes are not allowed from habit sessions");
|
|
365
|
+
}
|
|
366
|
+
if (!target) {
|
|
367
|
+
return deniedRoute("habit tool call has no permitted return route target");
|
|
368
|
+
}
|
|
369
|
+
const unsafeReason = unsafeRouteSegmentReason(target.friendId, target.channel, target.key);
|
|
370
|
+
if (unsafeReason) {
|
|
371
|
+
return deniedRoute(`habit return route target is unsafe: ${unsafeReason}`);
|
|
372
|
+
}
|
|
373
|
+
if (isSelfTarget(target.friendId, target.channel, target.key)) {
|
|
374
|
+
return deniedRoute("habit sessions cannot route messages to self/inner");
|
|
375
|
+
}
|
|
376
|
+
const friend = await resolveFriend(input.friendStore, target.friendId);
|
|
377
|
+
if (!friend)
|
|
378
|
+
return deniedRoute(`habit return route recipient unresolved: ${target.friendId}`);
|
|
379
|
+
const resolvedUnsafeReason = unsafeRouteSegmentReason(friend.id, target.channel, target.key);
|
|
380
|
+
if (resolvedUnsafeReason) {
|
|
381
|
+
return deniedRoute(`habit return route target is unsafe: ${resolvedUnsafeReason}`);
|
|
382
|
+
}
|
|
383
|
+
if (friend.isSelf || isSelfTarget(friend.id, target.channel, target.key)) {
|
|
384
|
+
return deniedRoute("habit sessions cannot route messages to self/inner");
|
|
385
|
+
}
|
|
386
|
+
const normalizedTarget = { ...target, friendId: friend.id };
|
|
387
|
+
const exactRoute = input.envelope.returnRoutes.find((route) => exactRouteMatches(route, normalizedTarget));
|
|
388
|
+
if (exactRoute) {
|
|
389
|
+
return {
|
|
390
|
+
allowed: true,
|
|
391
|
+
routeKind: exactRoute.kind,
|
|
392
|
+
friendId: friend.id,
|
|
393
|
+
channel: target.channel,
|
|
394
|
+
key: target.key,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const familyRoute = input.envelope.returnRoutes.find((route) => route.kind === "family" && route.status === "allowed");
|
|
398
|
+
if (familyRoute) {
|
|
399
|
+
if (friend.trustLevel === "family") {
|
|
400
|
+
return { allowed: true, routeKind: "family", friendId: friend.id, channel: target.channel, key: target.key };
|
|
401
|
+
}
|
|
402
|
+
return deniedRoute(`habit family route requires family trust for ${friend.id}`);
|
|
403
|
+
}
|
|
404
|
+
(0, runtime_1.emitNervesEvent)({
|
|
405
|
+
level: "warn",
|
|
406
|
+
component: "daemon",
|
|
407
|
+
event: "daemon.habit_return_route_denied",
|
|
408
|
+
message: "habit return route denied",
|
|
409
|
+
meta: { agentRoot: input.agentRoot, toolName: input.toolName, friendId: friend.id },
|
|
410
|
+
});
|
|
411
|
+
return deniedRoute(`no habit return route allows ${friend.id}/${target.channel}/${target.key}`);
|
|
412
|
+
}
|
|
413
|
+
function toolName(definition) {
|
|
414
|
+
return definition.tool.function.name;
|
|
415
|
+
}
|
|
416
|
+
function isOutwardMessagingTool(name) {
|
|
417
|
+
return name === "send_message" || name === "surface";
|
|
418
|
+
}
|
|
419
|
+
function isHighRisk(profile) {
|
|
420
|
+
return profile.risk === "high";
|
|
421
|
+
}
|
|
422
|
+
function filterHabitToolsForEnvelope(definitions, requestedTools, envelope, riskClassifier) {
|
|
423
|
+
const requested = requestedTools ? new Set(requestedTools) : null;
|
|
424
|
+
const grantedTools = [];
|
|
425
|
+
const deniedTools = [];
|
|
426
|
+
for (const definition of definitions) {
|
|
427
|
+
const name = toolName(definition);
|
|
428
|
+
if (requested && !requested.has(name))
|
|
429
|
+
continue;
|
|
430
|
+
const deniedByEnvelope = envelope.deniedTools.includes(name);
|
|
431
|
+
const profile = riskClassifier(definition, name);
|
|
432
|
+
const allowedOutward = isOutwardMessagingTool(name) && envelope.canMessageOutward && !deniedByEnvelope;
|
|
433
|
+
if (deniedByEnvelope || (isHighRisk(profile) && !allowedOutward)) {
|
|
434
|
+
deniedTools.push(name);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
grantedTools.push(name);
|
|
438
|
+
}
|
|
439
|
+
const policy = {
|
|
440
|
+
requestedTools: requestedTools ? [...requestedTools] : null,
|
|
441
|
+
grantedTools,
|
|
442
|
+
deniedTools,
|
|
443
|
+
outwardMessagingAllowed: envelope.canMessageOutward,
|
|
444
|
+
};
|
|
445
|
+
(0, runtime_1.emitNervesEvent)({
|
|
446
|
+
component: "daemon",
|
|
447
|
+
event: "daemon.habit_tool_policy_filtered",
|
|
448
|
+
message: "habit tool policy filtered",
|
|
449
|
+
meta: { requested: requestedTools?.length ?? null, granted: grantedTools.length, denied: deniedTools.length },
|
|
450
|
+
});
|
|
451
|
+
return policy;
|
|
452
|
+
}
|
|
453
|
+
function computeNextRunAt(habit, endedAt) {
|
|
454
|
+
if (habit.status !== "active" || !habit.cadence)
|
|
455
|
+
return null;
|
|
456
|
+
const cadenceMs = (0, cadence_1.parseCadenceToMs)(habit.cadence);
|
|
457
|
+
const endedMs = Date.parse(endedAt);
|
|
458
|
+
if (cadenceMs === null || !Number.isFinite(endedMs))
|
|
459
|
+
return null;
|
|
460
|
+
return new Date(endedMs + cadenceMs).toISOString();
|
|
461
|
+
}
|
|
462
|
+
function defaultSummarySnapshot(input) {
|
|
463
|
+
const errors = input.errors ?? [];
|
|
464
|
+
if (errors.length > 0) {
|
|
465
|
+
return {
|
|
466
|
+
summary: `Habit ${input.habit.name} finished with errors: ${errors.join("; ")}`,
|
|
467
|
+
decisions: [],
|
|
468
|
+
nextLikelyStep: null,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
const surfaced = (input.surfaceAttempts ?? []).find((attempt) => attempt.result !== "blocked" && attempt.result !== "failed" && attempt.result !== "unavailable");
|
|
472
|
+
if (surfaced) {
|
|
473
|
+
return {
|
|
474
|
+
summary: `Habit ${input.habit.name} surfaced via ${surfaced.recipient}/${surfaced.channel}.`,
|
|
475
|
+
decisions: [],
|
|
476
|
+
nextLikelyStep: null,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const produced = (input.producedRefs ?? []).find((ref) => ref.kind !== "none");
|
|
480
|
+
if (produced) {
|
|
481
|
+
return {
|
|
482
|
+
summary: `Habit ${input.habit.name} produced ${produced.kind}: ${produced.locator}.`,
|
|
483
|
+
decisions: [],
|
|
484
|
+
nextLikelyStep: null,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
summary: `Habit ${input.habit.name} finished with ${input.outcome}.`,
|
|
489
|
+
decisions: [],
|
|
490
|
+
nextLikelyStep: null,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function buildHabitRunReceipt(input) {
|
|
494
|
+
const paths = createHabitSessionPaths(input.agentRoot, input.runId, input.habit.name);
|
|
495
|
+
const receipt = {
|
|
496
|
+
schemaVersion: 2,
|
|
497
|
+
runId: input.runId,
|
|
498
|
+
sessionId: input.runId,
|
|
499
|
+
habitName: input.habit.name,
|
|
500
|
+
trigger: input.trigger,
|
|
501
|
+
startedAt: input.startedAt,
|
|
502
|
+
endedAt: input.endedAt,
|
|
503
|
+
outcome: input.outcome,
|
|
504
|
+
definitionLocator: `habits/${input.habit.name}.md`,
|
|
505
|
+
sessionLocator: paths.sessionLocator,
|
|
506
|
+
pendingLocator: paths.pendingLocator,
|
|
507
|
+
runtimeStateLocator: paths.runtimeStateLocator,
|
|
508
|
+
receiptLocator: paths.receiptLocator,
|
|
509
|
+
operationId: input.operationId ?? null,
|
|
510
|
+
nextRunAt: input.nextRunAt ?? computeNextRunAt(input.habit, input.endedAt),
|
|
511
|
+
permissionEnvelope: input.permissionEnvelope,
|
|
512
|
+
toolPolicy: input.toolPolicy,
|
|
513
|
+
summarySnapshot: input.summarySnapshot ?? defaultSummarySnapshot(input),
|
|
514
|
+
producedRefs: input.producedRefs ?? [],
|
|
515
|
+
surfaceAttempts: input.surfaceAttempts ?? [],
|
|
516
|
+
errors: input.errors ?? [],
|
|
517
|
+
};
|
|
518
|
+
(0, runtime_1.emitNervesEvent)({
|
|
519
|
+
component: "daemon",
|
|
520
|
+
event: "daemon.habit_run_receipt_built",
|
|
521
|
+
message: "habit run receipt built",
|
|
522
|
+
meta: { agentRoot: input.agentRoot, habitName: input.habit.name, runId: input.runId, outcome: input.outcome },
|
|
523
|
+
});
|
|
524
|
+
return receipt;
|
|
525
|
+
}
|
|
526
|
+
function successfulSurfaceAttempt(attempt) {
|
|
527
|
+
return attempt.result !== "blocked"
|
|
528
|
+
&& attempt.result !== "failed"
|
|
529
|
+
&& attempt.result !== "unavailable";
|
|
530
|
+
}
|
|
531
|
+
function surfaceRefsFromAttempts(attempts) {
|
|
532
|
+
return attempts
|
|
533
|
+
.filter(successfulSurfaceAttempt)
|
|
534
|
+
.map((attempt) => ({
|
|
535
|
+
kind: "surface",
|
|
536
|
+
locator: `surface/${attempt.recipient}/${attempt.channel}`,
|
|
537
|
+
}));
|
|
538
|
+
}
|
|
539
|
+
function habitOutcomeForCompletion(input) {
|
|
540
|
+
const attempts = input.surfaceAttempts ?? [];
|
|
541
|
+
const producedRefs = input.producedRefs && input.producedRefs.length > 0
|
|
542
|
+
? [...input.producedRefs]
|
|
543
|
+
: surfaceRefsFromAttempts(attempts);
|
|
544
|
+
if ((input.errors ?? []).length > 0)
|
|
545
|
+
return { outcome: "error", producedRefs };
|
|
546
|
+
if (producedRefs.some((ref) => ref.kind === "surface"))
|
|
547
|
+
return { outcome: "surfaced", producedRefs };
|
|
548
|
+
if (producedRefs.some((ref) => ref.kind === "desk_record"))
|
|
549
|
+
return { outcome: "wrote_record", producedRefs };
|
|
550
|
+
if (producedRefs.some((ref) => ref.kind === "desk_task"))
|
|
551
|
+
return { outcome: "updated_desk", producedRefs };
|
|
552
|
+
if (producedRefs.some((ref) => ref.kind === "arc" || ref.kind === "claim"))
|
|
553
|
+
return { outcome: "wrote_arc", producedRefs };
|
|
554
|
+
if (attempts.length > 0 && attempts.every((attempt) => !successfulSurfaceAttempt(attempt))) {
|
|
555
|
+
return { outcome: "blocked", producedRefs };
|
|
556
|
+
}
|
|
557
|
+
return { outcome: "no_change", producedRefs };
|
|
558
|
+
}
|
|
559
|
+
function completeHabitRun(input) {
|
|
560
|
+
const { outcome, producedRefs } = habitOutcomeForCompletion(input);
|
|
561
|
+
let receipt;
|
|
562
|
+
try {
|
|
563
|
+
receipt = buildHabitRunReceipt({
|
|
564
|
+
agentRoot: input.agentRoot,
|
|
565
|
+
habit: input.habit,
|
|
566
|
+
runId: input.runId,
|
|
567
|
+
trigger: input.trigger,
|
|
568
|
+
startedAt: input.startedAt,
|
|
569
|
+
endedAt: input.endedAt,
|
|
570
|
+
outcome,
|
|
571
|
+
operationId: input.operationId ?? null,
|
|
572
|
+
permissionEnvelope: input.permissionEnvelope,
|
|
573
|
+
toolPolicy: input.toolPolicy,
|
|
574
|
+
producedRefs,
|
|
575
|
+
surfaceAttempts: input.surfaceAttempts,
|
|
576
|
+
errors: input.errors,
|
|
577
|
+
summarySnapshot: input.summarySnapshot,
|
|
578
|
+
});
|
|
579
|
+
(0, flight_recorder_1.writeHabitRunReceipt)(input.agentRoot, receipt);
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
(0, runtime_1.emitNervesEvent)({
|
|
583
|
+
level: "error",
|
|
584
|
+
component: "senses",
|
|
585
|
+
event: "senses.habit_receipt_write_error",
|
|
586
|
+
message: "habit receipt could not be written; runtime state was not advanced",
|
|
587
|
+
meta: {
|
|
588
|
+
habitName: input.habit.name,
|
|
589
|
+
runId: input.runId,
|
|
590
|
+
error: String(error),
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
return { outcome, producedRefs, receiptWritten: false, runtimeStateRecorded: false };
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
(0, habit_runtime_state_1.recordHabitRun)(input.agentRoot, input.habit.name, input.endedAt, {
|
|
597
|
+
definitionPath: path.join(input.agentRoot, "habits", `${input.habit.name}.md`),
|
|
598
|
+
activeOperationId: receipt.operationId ?? null,
|
|
599
|
+
latestRunId: receipt.runId,
|
|
600
|
+
latestReceiptLocator: receipt.receiptLocator,
|
|
601
|
+
});
|
|
602
|
+
return { outcome, producedRefs, receiptWritten: true, runtimeStateRecorded: true };
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
(0, runtime_1.emitNervesEvent)({
|
|
606
|
+
level: "warn",
|
|
607
|
+
component: "senses",
|
|
608
|
+
event: "senses.habit_runtime_state_record_error",
|
|
609
|
+
message: "habit runtime state could not be updated after receipt write",
|
|
610
|
+
meta: {
|
|
611
|
+
habitName: input.habit.name,
|
|
612
|
+
runId: input.runId,
|
|
613
|
+
error: String(error),
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
return { outcome, producedRefs, receiptWritten: true, runtimeStateRecorded: false };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
@@ -33,13 +33,37 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isSafeMailboxAgentName = isSafeMailboxAgentName;
|
|
36
37
|
exports.createMailboxHttpReadHooks = createMailboxHttpReadHooks;
|
|
37
38
|
const path = __importStar(require("path"));
|
|
38
39
|
const mailbox_read_1 = require("./mailbox-read");
|
|
40
|
+
function isSafeMailboxAgentName(agentName) {
|
|
41
|
+
const trimmed = agentName.trim();
|
|
42
|
+
return trimmed === agentName
|
|
43
|
+
&& /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(agentName)
|
|
44
|
+
&& agentName !== "."
|
|
45
|
+
&& agentName !== ".."
|
|
46
|
+
&& !agentName.includes("/")
|
|
47
|
+
&& !agentName.includes("\\");
|
|
48
|
+
}
|
|
49
|
+
function resolveAgentRoot(bundlesRoot, agentName) {
|
|
50
|
+
if (!isSafeMailboxAgentName(agentName)) {
|
|
51
|
+
throw new Error("mailbox API requires a safe agent name");
|
|
52
|
+
}
|
|
53
|
+
if (!bundlesRoot)
|
|
54
|
+
return path.join(`${agentName}.ouro`);
|
|
55
|
+
const root = path.resolve(bundlesRoot);
|
|
56
|
+
const agentRoot = path.resolve(root, `${agentName}.ouro`);
|
|
57
|
+
/* v8 ignore next -- defensive containment guard after strict safe bundle-name validation @preserve */
|
|
58
|
+
if (agentRoot !== root && !agentRoot.startsWith(`${root}${path.sep}`)) {
|
|
59
|
+
throw new Error("mailbox API agent root escaped bundles root");
|
|
60
|
+
}
|
|
61
|
+
return agentRoot;
|
|
62
|
+
}
|
|
39
63
|
function createMailboxHttpReadHooks(options) {
|
|
40
64
|
const bundlesRoot = options.bundlesRoot;
|
|
41
65
|
const readOptions = bundlesRoot ? { bundlesRoot } : undefined;
|
|
42
|
-
const agentRoot = (agentName) =>
|
|
66
|
+
const agentRoot = (agentName) => resolveAgentRoot(bundlesRoot, agentName);
|
|
43
67
|
return {
|
|
44
68
|
agentRoot,
|
|
45
69
|
readAgentSessions: options.readAgentSessions ?? ((agentName) => (0, mailbox_read_1.readSessionInventory)(agentName, readOptions)),
|
|
@@ -58,6 +82,10 @@ function createMailboxHttpReadHooks(options) {
|
|
|
58
82
|
readAgentSentinel: options.readAgentSentinel ?? ((agentName) => (0, mailbox_read_1.readSentinelView)(agentRoot(agentName))),
|
|
59
83
|
readAgentNoteDecisions: options.readAgentNoteDecisions ?? ((agentName) => (0, mailbox_read_1.readNoteDecisionView)(agentRoot(agentName))),
|
|
60
84
|
readAgentHabits: options.readAgentHabits ?? ((agentName) => (0, mailbox_read_1.readHabitView)(agentRoot(agentName))),
|
|
85
|
+
readAgentHabitRuns: options.readAgentHabitRuns ?? ((agentName, habitOptions) => (0, mailbox_read_1.readHabitRunView)(agentRoot(agentName), habitOptions)),
|
|
86
|
+
readAgentHabitRun: options.readAgentHabitRun ?? ((agentName, runId) => (0, mailbox_read_1.readHabitRunReceiptView)(agentRoot(agentName), runId)),
|
|
87
|
+
readAgentHabitRunSummaries: options.readAgentHabitRunSummaries ?? ((agentName, habitOptions) => (0, mailbox_read_1.readHabitSessionSummaryListView)(agentRoot(agentName), habitOptions)),
|
|
88
|
+
readAgentHabitRunSummary: options.readAgentHabitRunSummary ?? ((agentName, selector) => (0, mailbox_read_1.readHabitSessionSummaryView)(agentRoot(agentName), selector)),
|
|
61
89
|
readAgentMail: options.readAgentMail ?? ((agentName) => (0, mailbox_read_1.readMailView)(agentName)),
|
|
62
90
|
readAgentMailMessage: options.readAgentMailMessage ?? ((agentName, messageId) => (0, mailbox_read_1.readMailMessageView)(agentName, messageId)),
|
|
63
91
|
readDaemonHealth: options.readDaemonHealth ?? (() => (0, mailbox_read_1.readDaemonHealthDeep)(options.healthPath)),
|