@ouro.bot/cli 0.1.0-alpha.666 → 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 +7 -0
- package/dist/arc/flight-recorder.js +58 -2
- package/dist/heart/daemon/cli-exec.js +90 -1
- package/dist/heart/daemon/cli-help.js +12 -1
- package/dist/heart/daemon/cli-parse.js +102 -0
- package/dist/heart/habits/habit-parser.js +8 -0
- package/dist/heart/habits/habit-runtime-state.js +17 -3
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +62 -7
- package/dist/heart/mailbox/mailbox-http-hooks.js +27 -1
- package/dist/heart/mailbox/mailbox-http-routes.js +82 -1
- package/dist/heart/mailbox/mailbox-read.js +3 -1
- package/dist/heart/mailbox/readers/runtime-readers.js +31 -0
- package/dist/repertoire/tools-session.js +126 -0
- package/dist/senses/habit-turn-message.js +41 -3
- package/dist/senses/inner-dialog-worker.js +113 -1
- package/dist/senses/inner-dialog.js +24 -12
- package/dist/senses/pipeline.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
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.selectHabitRunReceipt = selectHabitRunReceipt;
|
|
37
|
+
exports.readHabitSessionSummary = readHabitSessionSummary;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const flight_recorder_1 = require("../../arc/flight-recorder");
|
|
41
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
42
|
+
const session_events_1 = require("../session-events");
|
|
43
|
+
const VALID_WHICH = new Set(["latest", "previous", "latest-success", "latest-failure"]);
|
|
44
|
+
const SUCCESS_OUTCOMES = new Set([
|
|
45
|
+
"no_change",
|
|
46
|
+
"wrote_arc",
|
|
47
|
+
"updated_desk",
|
|
48
|
+
"wrote_record",
|
|
49
|
+
"surfaced",
|
|
50
|
+
]);
|
|
51
|
+
const FAILURE_OUTCOMES = new Set(["blocked", "error"]);
|
|
52
|
+
const MAX_SUMMARY_CHARS = 1600;
|
|
53
|
+
const TRUNCATION_SUFFIX = "\n[truncated]";
|
|
54
|
+
function selectorError(code, message) {
|
|
55
|
+
return { ok: false, error: { code, message } };
|
|
56
|
+
}
|
|
57
|
+
function normalizeWhich(value) {
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return "latest";
|
|
60
|
+
return VALID_WHICH.has(value) ? value : null;
|
|
61
|
+
}
|
|
62
|
+
function sortNewestFirst(receipts) {
|
|
63
|
+
return [...receipts].sort((left, right) => right.endedAt.localeCompare(left.endedAt) || right.runId.localeCompare(left.runId));
|
|
64
|
+
}
|
|
65
|
+
function filterReceipts(receipts, selector) {
|
|
66
|
+
return receipts.filter((receipt) => {
|
|
67
|
+
if (selector.habitName !== undefined && receipt.habitName !== selector.habitName)
|
|
68
|
+
return false;
|
|
69
|
+
if (selector.operationId !== undefined && receipt.operationId !== selector.operationId)
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function filterByWhich(receipts, which) {
|
|
75
|
+
if (which === "latest" || which === "previous")
|
|
76
|
+
return receipts;
|
|
77
|
+
const outcomes = which === "latest-success" ? SUCCESS_OUTCOMES : FAILURE_OUTCOMES;
|
|
78
|
+
return receipts.filter((receipt) => outcomes.has(receipt.outcome));
|
|
79
|
+
}
|
|
80
|
+
function selectHabitRunReceipt(receipts, selector) {
|
|
81
|
+
if (selector.runId !== undefined) {
|
|
82
|
+
if (selector.habitName !== undefined || selector.operationId !== undefined || selector.which !== undefined) {
|
|
83
|
+
return selectorError("run_id_exclusive", "runId cannot be combined with habitName, operationId, or which");
|
|
84
|
+
}
|
|
85
|
+
const receipt = receipts.find((entry) => entry.runId === selector.runId);
|
|
86
|
+
return receipt ? { ok: true, receipt } : selectorError("not_found", "no habit run matched selector");
|
|
87
|
+
}
|
|
88
|
+
if (selector.habitName === undefined && selector.operationId === undefined) {
|
|
89
|
+
return selectorError("selector_required", "provide runId, habitName, or operationId");
|
|
90
|
+
}
|
|
91
|
+
const which = normalizeWhich(selector.which);
|
|
92
|
+
if (which === null) {
|
|
93
|
+
return selectorError("invalid_which", "which must be latest, previous, latest-success, or latest-failure");
|
|
94
|
+
}
|
|
95
|
+
const matches = filterByWhich(sortNewestFirst(filterReceipts([...receipts], selector)), which);
|
|
96
|
+
const index = which === "previous" ? 1 : 0;
|
|
97
|
+
const receipt = matches[index];
|
|
98
|
+
return receipt ? { ok: true, receipt } : selectorError("not_found", "no habit run matched selector");
|
|
99
|
+
}
|
|
100
|
+
function isRecord(value) {
|
|
101
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
102
|
+
}
|
|
103
|
+
function stringValue(value) {
|
|
104
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
105
|
+
}
|
|
106
|
+
function summarySnapshot(receipt) {
|
|
107
|
+
const snapshot = receipt.summarySnapshot;
|
|
108
|
+
return {
|
|
109
|
+
summary: snapshot.summary,
|
|
110
|
+
decisions: snapshot.decisions,
|
|
111
|
+
nextLikelyStep: snapshot.nextLikelyStep,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function relativeSource(receipt, key) {
|
|
115
|
+
if (key === "receipt")
|
|
116
|
+
return receipt.receiptLocator;
|
|
117
|
+
if (key === "session")
|
|
118
|
+
return receipt.sessionLocator;
|
|
119
|
+
if (key === "pending")
|
|
120
|
+
return receipt.pendingLocator;
|
|
121
|
+
return receipt.runtimeStateLocator;
|
|
122
|
+
}
|
|
123
|
+
function safeSourcePath(agentRoot, locator, key, expectedPrefix) {
|
|
124
|
+
const normalizedInput = locator.replace(/\\/g, "/");
|
|
125
|
+
const normalized = path.posix.normalize(normalizedInput);
|
|
126
|
+
const unsafe = path.isAbsolute(locator)
|
|
127
|
+
|| normalizedInput.startsWith("/")
|
|
128
|
+
|| normalized === "."
|
|
129
|
+
|| normalized === ".."
|
|
130
|
+
|| normalized.startsWith("../")
|
|
131
|
+
|| normalized !== normalizedInput
|
|
132
|
+
|| !normalized.startsWith(expectedPrefix);
|
|
133
|
+
if (unsafe) {
|
|
134
|
+
return { ok: false, warning: `${key} locator unsafe: ${locator}` };
|
|
135
|
+
}
|
|
136
|
+
const root = path.resolve(agentRoot);
|
|
137
|
+
const filePath = path.resolve(agentRoot, normalized);
|
|
138
|
+
/* v8 ignore next -- defensive containment check after normalized bundle-relative locator validation @preserve */
|
|
139
|
+
if (filePath !== root && !filePath.startsWith(`${root}${path.sep}`)) {
|
|
140
|
+
return { ok: false, warning: `${key} locator escaped bundle: ${locator}` };
|
|
141
|
+
}
|
|
142
|
+
return { ok: true, filePath };
|
|
143
|
+
}
|
|
144
|
+
function safeExistingRealPath(agentRoot, filePath, key, locator) {
|
|
145
|
+
try {
|
|
146
|
+
const root = fs.realpathSync.native(agentRoot);
|
|
147
|
+
const realPath = fs.realpathSync.native(filePath);
|
|
148
|
+
if (realPath !== root && !realPath.startsWith(`${root}${path.sep}`)) {
|
|
149
|
+
return { ok: false, warning: `${key} locator escaped bundle via symlink: ${locator}` };
|
|
150
|
+
}
|
|
151
|
+
return { ok: true, filePath: realPath };
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* v8 ignore next -- defensive realpath race/permission guard; existence is checked before callers invoke this @preserve */
|
|
155
|
+
return { ok: false, warning: `${key} locator unreadable: ${locator}` };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function sessionSummaryFromMessages(messages) {
|
|
159
|
+
const toolsUsed = new Set();
|
|
160
|
+
for (const message of messages) {
|
|
161
|
+
/* v8 ignore next -- defensive: session projection returns message records; this protects malformed direct projection callers @preserve */
|
|
162
|
+
if (!isRecord(message))
|
|
163
|
+
continue;
|
|
164
|
+
const name = stringValue(message.name) ?? stringValue(message.toolName);
|
|
165
|
+
/* v8 ignore next -- defensive: canonical projections expose tool names from assistant tool_calls; legacy direct tool-name projections still count if present @preserve */
|
|
166
|
+
if (name && message.role === "tool")
|
|
167
|
+
toolsUsed.add(name);
|
|
168
|
+
if (Array.isArray(message.tool_calls)) {
|
|
169
|
+
/* v8 ignore start -- defensive: projected provider messages normally expose structured tool calls; malformed direct envelopes are kept inert @preserve */
|
|
170
|
+
for (const toolCall of message.tool_calls) {
|
|
171
|
+
if (!isRecord(toolCall))
|
|
172
|
+
continue;
|
|
173
|
+
const fn = isRecord(toolCall.function) ? toolCall.function : null;
|
|
174
|
+
const toolName = fn ? stringValue(fn.name) : null;
|
|
175
|
+
if (toolName)
|
|
176
|
+
toolsUsed.add(toolName);
|
|
177
|
+
}
|
|
178
|
+
/* v8 ignore stop @preserve */
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const warnings = messages.length === 0 ? ["session file had no usable messages"] : [];
|
|
182
|
+
return {
|
|
183
|
+
decisions: [],
|
|
184
|
+
nextLikelyStep: null,
|
|
185
|
+
toolsUsed: [...toolsUsed].sort(),
|
|
186
|
+
warnings,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function readSessionEnrichment(agentRoot, receipt) {
|
|
190
|
+
const source = safeSourcePath(agentRoot, receipt.sessionLocator, "session", "state/habit-sessions/");
|
|
191
|
+
if (!source.ok) {
|
|
192
|
+
return { decisions: [], nextLikelyStep: null, toolsUsed: [], warnings: [source.warning] };
|
|
193
|
+
}
|
|
194
|
+
if (!fs.existsSync(source.filePath)) {
|
|
195
|
+
return { decisions: [], nextLikelyStep: null, toolsUsed: [], warnings: ["session file missing"] };
|
|
196
|
+
}
|
|
197
|
+
const realSource = safeExistingRealPath(agentRoot, source.filePath, "session", receipt.sessionLocator);
|
|
198
|
+
if (!realSource.ok) {
|
|
199
|
+
return { decisions: [], nextLikelyStep: null, toolsUsed: [], warnings: [realSource.warning] };
|
|
200
|
+
}
|
|
201
|
+
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(realSource.filePath);
|
|
202
|
+
if (!envelope) {
|
|
203
|
+
return { decisions: [], nextLikelyStep: null, toolsUsed: [], warnings: ["session file malformed"] };
|
|
204
|
+
}
|
|
205
|
+
return sessionSummaryFromMessages((0, session_events_1.projectProviderMessages)(envelope));
|
|
206
|
+
}
|
|
207
|
+
function readPending(agentRoot, receipt) {
|
|
208
|
+
const source = safeSourcePath(agentRoot, receipt.pendingLocator, "pending", "state/habit-sessions/");
|
|
209
|
+
if (!source.ok)
|
|
210
|
+
return { pending: { count: 0, files: [] }, warnings: [source.warning] };
|
|
211
|
+
try {
|
|
212
|
+
if (!fs.existsSync(source.filePath))
|
|
213
|
+
return { pending: { count: 0, files: [] }, warnings: [] };
|
|
214
|
+
const realSource = safeExistingRealPath(agentRoot, source.filePath, "pending", receipt.pendingLocator);
|
|
215
|
+
if (!realSource.ok)
|
|
216
|
+
return { pending: { count: 0, files: [] }, warnings: [realSource.warning] };
|
|
217
|
+
const entries = fs.readdirSync(realSource.filePath, { withFileTypes: true })
|
|
218
|
+
.filter((entry) => entry.isFile())
|
|
219
|
+
.map((entry) => entry.name)
|
|
220
|
+
.sort();
|
|
221
|
+
return { pending: { count: entries.length, files: entries }, warnings: [] };
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return { pending: { count: 0, files: [] }, warnings: [] };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function runtimeOperationId(agentRoot, receipt) {
|
|
228
|
+
const source = safeSourcePath(agentRoot, receipt.runtimeStateLocator, "runtimeState", "state/habits/");
|
|
229
|
+
if (!source.ok)
|
|
230
|
+
return { operationId: null, warnings: [source.warning] };
|
|
231
|
+
try {
|
|
232
|
+
if (!fs.existsSync(source.filePath))
|
|
233
|
+
return { operationId: null, warnings: [] };
|
|
234
|
+
const realSource = safeExistingRealPath(agentRoot, source.filePath, "runtimeState", receipt.runtimeStateLocator);
|
|
235
|
+
if (!realSource.ok)
|
|
236
|
+
return { operationId: null, warnings: [realSource.warning] };
|
|
237
|
+
const parsed = JSON.parse(fs.readFileSync(realSource.filePath, "utf-8"));
|
|
238
|
+
if (!isRecord(parsed))
|
|
239
|
+
return { operationId: null, warnings: [] };
|
|
240
|
+
if (parsed.schemaVersion !== 1)
|
|
241
|
+
return { operationId: null, warnings: [] };
|
|
242
|
+
if (stringValue(parsed.name) !== receipt.habitName)
|
|
243
|
+
return { operationId: null, warnings: [] };
|
|
244
|
+
if (stringValue(parsed.latestRunId) !== receipt.runId)
|
|
245
|
+
return { operationId: null, warnings: [] };
|
|
246
|
+
if (stringValue(parsed.latestReceiptLocator) !== receipt.receiptLocator)
|
|
247
|
+
return { operationId: null, warnings: [] };
|
|
248
|
+
return { operationId: stringValue(parsed.activeOperationId), warnings: [] };
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return { operationId: null, warnings: [] };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function uniqueStrings(values) {
|
|
255
|
+
return [...new Set(values)];
|
|
256
|
+
}
|
|
257
|
+
function truncateSummary(value) {
|
|
258
|
+
if (value.length <= MAX_SUMMARY_CHARS)
|
|
259
|
+
return value;
|
|
260
|
+
return `${value.slice(0, MAX_SUMMARY_CHARS - TRUNCATION_SUFFIX.length)}${TRUNCATION_SUFFIX}`;
|
|
261
|
+
}
|
|
262
|
+
function legacySummaryWarnings(receipt) {
|
|
263
|
+
return receipt.permissionEnvelope.warnings.some((warning) => warning.includes("legacy receipt normalized"))
|
|
264
|
+
? ["legacy receipt normalized"]
|
|
265
|
+
: [];
|
|
266
|
+
}
|
|
267
|
+
function readHabitSessionSummary(agentRoot, selector) {
|
|
268
|
+
const selection = selectHabitRunReceipt((0, flight_recorder_1.listHabitRunReceipts)(agentRoot), selector);
|
|
269
|
+
if (!selection.ok)
|
|
270
|
+
return null;
|
|
271
|
+
const receipt = selection.receipt;
|
|
272
|
+
const snapshot = summarySnapshot(receipt);
|
|
273
|
+
const session = readSessionEnrichment(agentRoot, receipt);
|
|
274
|
+
const pending = readPending(agentRoot, receipt);
|
|
275
|
+
const runtime = runtimeOperationId(agentRoot, receipt);
|
|
276
|
+
const operationId = receipt.operationId ?? runtime.operationId;
|
|
277
|
+
const decisions = uniqueStrings([
|
|
278
|
+
...snapshot.decisions,
|
|
279
|
+
...session.decisions,
|
|
280
|
+
]);
|
|
281
|
+
const summary = truncateSummary(snapshot.summary);
|
|
282
|
+
const nextLikelyStep = snapshot.nextLikelyStep ?? session.nextLikelyStep;
|
|
283
|
+
const result = {
|
|
284
|
+
runId: receipt.runId,
|
|
285
|
+
habitName: receipt.habitName,
|
|
286
|
+
operationId: operationId ?? null,
|
|
287
|
+
status: receipt.outcome,
|
|
288
|
+
triggeredAt: receipt.startedAt,
|
|
289
|
+
completedAt: receipt.endedAt,
|
|
290
|
+
summary,
|
|
291
|
+
decisions,
|
|
292
|
+
pending: pending.pending,
|
|
293
|
+
messagesSent: receipt.surfaceAttempts,
|
|
294
|
+
toolsUsed: session.toolsUsed,
|
|
295
|
+
producedRefs: receipt.producedRefs,
|
|
296
|
+
errors: receipt.errors,
|
|
297
|
+
nextLikelyStep: nextLikelyStep ?? null,
|
|
298
|
+
sources: {
|
|
299
|
+
receipt: relativeSource(receipt, "receipt"),
|
|
300
|
+
session: relativeSource(receipt, "session"),
|
|
301
|
+
pending: relativeSource(receipt, "pending"),
|
|
302
|
+
runtimeState: relativeSource(receipt, "runtimeState"),
|
|
303
|
+
},
|
|
304
|
+
warnings: [
|
|
305
|
+
...legacySummaryWarnings(receipt),
|
|
306
|
+
...session.warnings,
|
|
307
|
+
...pending.warnings,
|
|
308
|
+
...runtime.warnings,
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
(0, runtime_1.emitNervesEvent)({
|
|
312
|
+
component: "daemon",
|
|
313
|
+
event: "daemon.habit_session_summary_read",
|
|
314
|
+
message: "habit session summary read",
|
|
315
|
+
meta: { agentRoot, runId: receipt.runId, habitName: receipt.habitName, warningCount: result.warnings.length },
|
|
316
|
+
});
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
@@ -59,14 +59,29 @@ function isHabitRuntimeStateSnapshot(value, habitName) {
|
|
|
59
59
|
return record.schemaVersion === 1
|
|
60
60
|
&& record.name === habitName
|
|
61
61
|
&& typeof record.lastRun === "string"
|
|
62
|
-
&& typeof record.updatedAt === "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;
|
|
63
69
|
}
|
|
64
70
|
function readHabitRuntimeStateSnapshot(agentRoot, habitName) {
|
|
65
71
|
const runtimeStatePath = path.join(agentRoot, "state", "habits", `${habitName}.json`);
|
|
66
72
|
try {
|
|
67
73
|
const parsed = JSON.parse(fs.readFileSync(runtimeStatePath, "utf-8"));
|
|
68
|
-
if (isHabitRuntimeStateSnapshot(parsed, habitName))
|
|
69
|
-
return
|
|
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
|
+
}
|
|
70
85
|
(0, runtime_1.emitNervesEvent)({
|
|
71
86
|
level: "warn",
|
|
72
87
|
component: "daemon",
|
|
@@ -444,6 +459,37 @@ function computeNextRunAt(habit, endedAt) {
|
|
|
444
459
|
return null;
|
|
445
460
|
return new Date(endedMs + cadenceMs).toISOString();
|
|
446
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
|
+
}
|
|
447
493
|
function buildHabitRunReceipt(input) {
|
|
448
494
|
const paths = createHabitSessionPaths(input.agentRoot, input.runId, input.habit.name);
|
|
449
495
|
const receipt = {
|
|
@@ -460,9 +506,11 @@ function buildHabitRunReceipt(input) {
|
|
|
460
506
|
pendingLocator: paths.pendingLocator,
|
|
461
507
|
runtimeStateLocator: paths.runtimeStateLocator,
|
|
462
508
|
receiptLocator: paths.receiptLocator,
|
|
509
|
+
operationId: input.operationId ?? null,
|
|
463
510
|
nextRunAt: input.nextRunAt ?? computeNextRunAt(input.habit, input.endedAt),
|
|
464
511
|
permissionEnvelope: input.permissionEnvelope,
|
|
465
512
|
toolPolicy: input.toolPolicy,
|
|
513
|
+
summarySnapshot: input.summarySnapshot ?? defaultSummarySnapshot(input),
|
|
466
514
|
producedRefs: input.producedRefs ?? [],
|
|
467
515
|
surfaceAttempts: input.surfaceAttempts ?? [],
|
|
468
516
|
errors: input.errors ?? [],
|
|
@@ -510,8 +558,9 @@ function habitOutcomeForCompletion(input) {
|
|
|
510
558
|
}
|
|
511
559
|
function completeHabitRun(input) {
|
|
512
560
|
const { outcome, producedRefs } = habitOutcomeForCompletion(input);
|
|
561
|
+
let receipt;
|
|
513
562
|
try {
|
|
514
|
-
|
|
563
|
+
receipt = buildHabitRunReceipt({
|
|
515
564
|
agentRoot: input.agentRoot,
|
|
516
565
|
habit: input.habit,
|
|
517
566
|
runId: input.runId,
|
|
@@ -519,12 +568,15 @@ function completeHabitRun(input) {
|
|
|
519
568
|
startedAt: input.startedAt,
|
|
520
569
|
endedAt: input.endedAt,
|
|
521
570
|
outcome,
|
|
571
|
+
operationId: input.operationId ?? null,
|
|
522
572
|
permissionEnvelope: input.permissionEnvelope,
|
|
523
573
|
toolPolicy: input.toolPolicy,
|
|
524
574
|
producedRefs,
|
|
525
575
|
surfaceAttempts: input.surfaceAttempts,
|
|
526
576
|
errors: input.errors,
|
|
527
|
-
|
|
577
|
+
summarySnapshot: input.summarySnapshot,
|
|
578
|
+
});
|
|
579
|
+
(0, flight_recorder_1.writeHabitRunReceipt)(input.agentRoot, receipt);
|
|
528
580
|
}
|
|
529
581
|
catch (error) {
|
|
530
582
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -535,7 +587,7 @@ function completeHabitRun(input) {
|
|
|
535
587
|
meta: {
|
|
536
588
|
habitName: input.habit.name,
|
|
537
589
|
runId: input.runId,
|
|
538
|
-
error:
|
|
590
|
+
error: String(error),
|
|
539
591
|
},
|
|
540
592
|
});
|
|
541
593
|
return { outcome, producedRefs, receiptWritten: false, runtimeStateRecorded: false };
|
|
@@ -543,6 +595,9 @@ function completeHabitRun(input) {
|
|
|
543
595
|
try {
|
|
544
596
|
(0, habit_runtime_state_1.recordHabitRun)(input.agentRoot, input.habit.name, input.endedAt, {
|
|
545
597
|
definitionPath: path.join(input.agentRoot, "habits", `${input.habit.name}.md`),
|
|
598
|
+
activeOperationId: receipt.operationId ?? null,
|
|
599
|
+
latestRunId: receipt.runId,
|
|
600
|
+
latestReceiptLocator: receipt.receiptLocator,
|
|
546
601
|
});
|
|
547
602
|
return { outcome, producedRefs, receiptWritten: true, runtimeStateRecorded: true };
|
|
548
603
|
}
|
|
@@ -555,7 +610,7 @@ function completeHabitRun(input) {
|
|
|
555
610
|
meta: {
|
|
556
611
|
habitName: input.habit.name,
|
|
557
612
|
runId: input.runId,
|
|
558
|
-
error:
|
|
613
|
+
error: String(error),
|
|
559
614
|
},
|
|
560
615
|
});
|
|
561
616
|
return { outcome, producedRefs, receiptWritten: true, runtimeStateRecorded: false };
|
|
@@ -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)),
|
|
@@ -60,6 +84,8 @@ function createMailboxHttpReadHooks(options) {
|
|
|
60
84
|
readAgentHabits: options.readAgentHabits ?? ((agentName) => (0, mailbox_read_1.readHabitView)(agentRoot(agentName))),
|
|
61
85
|
readAgentHabitRuns: options.readAgentHabitRuns ?? ((agentName, habitOptions) => (0, mailbox_read_1.readHabitRunView)(agentRoot(agentName), habitOptions)),
|
|
62
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)),
|
|
63
89
|
readAgentMail: options.readAgentMail ?? ((agentName) => (0, mailbox_read_1.readMailView)(agentName)),
|
|
64
90
|
readAgentMailMessage: options.readAgentMailMessage ?? ((agentName, messageId) => (0, mailbox_read_1.readMailMessageView)(agentName, messageId)),
|
|
65
91
|
readDaemonHealth: options.readDaemonHealth ?? (() => (0, mailbox_read_1.readDaemonHealthDeep)(options.healthPath)),
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.createMailboxHttpRequestHandler = createMailboxHttpRequestHandler;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const mailbox_http_hooks_1 = require("./mailbox-http-hooks");
|
|
39
40
|
const mailbox_http_response_1 = require("./mailbox-http-response");
|
|
40
41
|
const mailbox_http_static_1 = require("./mailbox-http-static");
|
|
41
42
|
function decodePathSegment(value) {
|
|
@@ -46,6 +47,16 @@ function decodePathSegment(value) {
|
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
function rawRequestTargetsUnsafeAgent(urlValue = "/") {
|
|
51
|
+
const rawTarget = urlValue.split(/[?#]/, 1)[0];
|
|
52
|
+
const rawPath = rawTarget.replace(/\/+$/, "") || "/";
|
|
53
|
+
const pathname = (0, mailbox_http_static_1.normalizeLegacyMailboxApiPath)(rawPath);
|
|
54
|
+
const rawAgentMatch = /^\/api\/agents\/([^/]+)(?:\/|$)/.exec(pathname);
|
|
55
|
+
if (!rawAgentMatch)
|
|
56
|
+
return false;
|
|
57
|
+
const agent = decodePathSegment(rawAgentMatch[1]);
|
|
58
|
+
return !agent || !(0, mailbox_http_hooks_1.isSafeMailboxAgentName)(agent);
|
|
59
|
+
}
|
|
49
60
|
function parseHabitRunLimit(urlValue) {
|
|
50
61
|
const rawLimit = new URL(urlValue, "http://127.0.0.1").searchParams.get("limit");
|
|
51
62
|
if (rawLimit === null)
|
|
@@ -55,9 +66,47 @@ function parseHabitRunLimit(urlValue) {
|
|
|
55
66
|
const limit = Number.parseInt(rawLimit, 10);
|
|
56
67
|
return limit >= 1 && limit <= 100 ? limit : null;
|
|
57
68
|
}
|
|
69
|
+
const VALID_HABIT_SUMMARY_WHICH = new Set(["latest", "previous", "latest-success", "latest-failure"]);
|
|
70
|
+
function firstQueryValue(params, names) {
|
|
71
|
+
for (const name of names) {
|
|
72
|
+
const value = params.get(name);
|
|
73
|
+
if (value !== null && value.trim().length > 0)
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function parseHabitSummarySelector(urlValue) {
|
|
79
|
+
const params = new URL(urlValue, "http://127.0.0.1").searchParams;
|
|
80
|
+
const runId = firstQueryValue(params, ["runId", "run-id"]);
|
|
81
|
+
const habitName = firstQueryValue(params, ["habitName", "habit"]);
|
|
82
|
+
const operationId = firstQueryValue(params, ["operationId", "operation-id"]);
|
|
83
|
+
const which = firstQueryValue(params, ["which"]);
|
|
84
|
+
if (runId !== undefined && (habitName !== undefined || operationId !== undefined || which !== undefined)) {
|
|
85
|
+
return { ok: false, error: "--run-id cannot be combined with --habit, --operation-id, or --which" };
|
|
86
|
+
}
|
|
87
|
+
if (runId === undefined && habitName === undefined && operationId === undefined) {
|
|
88
|
+
return { ok: false, error: "provide runId, habitName, or operationId" };
|
|
89
|
+
}
|
|
90
|
+
if (which !== undefined && !VALID_HABIT_SUMMARY_WHICH.has(which)) {
|
|
91
|
+
return { ok: false, error: "which must be latest, previous, latest-success, or latest-failure" };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
selector: {
|
|
96
|
+
...(runId ? { runId } : {}),
|
|
97
|
+
...(habitName ? { habitName } : {}),
|
|
98
|
+
...(operationId ? { operationId } : {}),
|
|
99
|
+
...(which ? { which } : {}),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
58
103
|
function createMailboxHttpRequestHandler(options) {
|
|
59
104
|
const staticFiles = options.staticFiles ?? { resolveSpaDistDir: mailbox_http_static_1.resolveSpaDistDir, serveStaticFile: mailbox_http_static_1.serveStaticFile };
|
|
60
105
|
return (request, response) => {
|
|
106
|
+
if (rawRequestTargetsUnsafeAgent(request.url)) {
|
|
107
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: "agent name must be a safe bundle name" });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
61
110
|
let pathname = (0, mailbox_http_static_1.normalizeMailboxRequestPath)(request.url);
|
|
62
111
|
const origin = `http://${options.host}:${options.getPort()}`;
|
|
63
112
|
if (pathname.startsWith("/assets/")) {
|
|
@@ -99,8 +148,14 @@ function createMailboxHttpRequestHandler(options) {
|
|
|
99
148
|
}
|
|
100
149
|
const agentMatch = /^\/api\/agents\/([^/]+)(?:\/(.+))?$/.exec(pathname);
|
|
101
150
|
if (agentMatch) {
|
|
151
|
+
const agent = decodePathSegment(agentMatch[1]);
|
|
152
|
+
/* v8 ignore next -- rawRequestTargetsUnsafeAgent rejects unsafe/malformed agent segments before normalization @preserve */
|
|
153
|
+
if (!agent || !(0, mailbox_http_hooks_1.isSafeMailboxAgentName)(agent)) {
|
|
154
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: "agent name must be a safe bundle name" });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
102
157
|
void handleAgentRoute(request, response, {
|
|
103
|
-
agent
|
|
158
|
+
agent,
|
|
104
159
|
surface: agentMatch[2] ?? null,
|
|
105
160
|
options,
|
|
106
161
|
}).catch((error) => {
|
|
@@ -229,6 +284,32 @@ async function handleAgentRoute(request, response, context) {
|
|
|
229
284
|
(0, mailbox_http_response_1.writeJson)(response, 200, view);
|
|
230
285
|
return;
|
|
231
286
|
}
|
|
287
|
+
if (surface === "habit-run-summaries") {
|
|
288
|
+
const limit = parseHabitRunLimit(request.url);
|
|
289
|
+
if (limit === null) {
|
|
290
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: "limit must be an integer between 1 and 100" });
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const view = limit === undefined
|
|
294
|
+
? options.hooks.readAgentHabitRunSummaries(agent)
|
|
295
|
+
: options.hooks.readAgentHabitRunSummaries(agent, { limit });
|
|
296
|
+
(0, mailbox_http_response_1.writeJson)(response, 200, view);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (surface === "habit-run-summary") {
|
|
300
|
+
const parsed = parseHabitSummarySelector(request.url);
|
|
301
|
+
if (!parsed.ok) {
|
|
302
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: parsed.error });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const summary = options.hooks.readAgentHabitRunSummary(agent, parsed.selector);
|
|
306
|
+
if (!summary) {
|
|
307
|
+
(0, mailbox_http_response_1.writeJson)(response, 404, { ok: false, error: "habit summary not found" });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
(0, mailbox_http_response_1.writeJson)(response, 200, summary);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
232
313
|
if (surface.startsWith("habit-runs/")) {
|
|
233
314
|
const rawRunId = surface.slice("habit-runs/".length);
|
|
234
315
|
const runId = decodePathSegment(rawRunId);
|
|
@@ -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.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;
|
|
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.readHabitSessionSummaryView = exports.readHabitSessionSummaryListView = 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, "readHabitSessionSummaryListView", { enumerable: true, get: function () { return runtime_readers_1.readHabitSessionSummaryListView; } });
|
|
22
|
+
Object.defineProperty(exports, "readHabitSessionSummaryView", { enumerable: true, get: function () { return runtime_readers_1.readHabitSessionSummaryView; } });
|
|
21
23
|
Object.defineProperty(exports, "readHabitRunReceiptView", { enumerable: true, get: function () { return runtime_readers_1.readHabitRunReceiptView; } });
|
|
22
24
|
Object.defineProperty(exports, "readHabitRunView", { enumerable: true, get: function () { return runtime_readers_1.readHabitRunView; } });
|
|
23
25
|
Object.defineProperty(exports, "readHabitView", { enumerable: true, get: function () { return runtime_readers_1.readHabitView; } });
|