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