@ouro.bot/cli 0.1.0-alpha.482 → 0.1.0-alpha.483
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 +8 -0
- package/dist/heart/active-work.js +46 -0
- package/dist/heart/background-operations.js +27 -3
- package/dist/heart/core.js +15 -0
- package/dist/heart/daemon/cli-exec.js +104 -15
- package/dist/heart/daemon/cli-help.js +10 -4
- package/dist/heart/daemon/cli-parse.js +11 -5
- package/dist/heart/daemon/cli-render.js +1 -1
- package/dist/heart/daemon/thoughts.js +22 -8
- package/dist/heart/mail-import-discovery.js +318 -0
- package/dist/heart/outlook/outlook-http-routes.js +1 -1
- package/dist/heart/outlook/outlook-http-static.js +4 -0
- package/dist/heart/outlook/outlook-http.js +2 -2
- package/dist/heart/outlook/outlook-types.js +1 -1
- package/dist/heart/outlook/outlook-view.js +3 -3
- package/dist/heart/outlook/readers/agent-machine.js +34 -11
- package/dist/heart/outlook/readers/continuity-readers.js +5 -1
- package/dist/heart/outlook/readers/mail.js +3 -3
- package/dist/heart/session-events.js +91 -5
- package/dist/heart/turn-context.js +11 -0
- package/dist/mind/context.js +1 -1
- package/dist/mind/prompt.js +6 -3
- package/dist/nerves/coverage/file-completeness.js +1 -0
- package/dist/nerves/observation.js +2 -2
- package/dist/outlook-ui/assets/{index-CPfhbn13.js → index-Cm51CY9W.js} +1 -1
- package/dist/outlook-ui/index.html +2 -2
- package/dist/repertoire/tools-mail.js +2 -2
- package/dist/repertoire/tools-session.js +5 -2
- package/dist/senses/cli/ouro-tui.js +1 -1
- package/dist/senses/inner-dialog.js +5 -7
- package/dist/senses/mail.js +149 -18
- package/dist/senses/pipeline.js +2 -1
- 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.defaultMailImportDiscoveryDirs = defaultMailImportDiscoveryDirs;
|
|
37
|
+
exports.listDiscoveredMboxCandidates = listDiscoveredMboxCandidates;
|
|
38
|
+
exports.rankDiscoveredMboxCandidates = rankDiscoveredMboxCandidates;
|
|
39
|
+
exports.discoverMailImportFilePath = discoverMailImportFilePath;
|
|
40
|
+
exports.listAmbientMailImportOperations = listAmbientMailImportOperations;
|
|
41
|
+
exports.listVisibleBackgroundOperations = listVisibleBackgroundOperations;
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const os = __importStar(require("node:os"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const background_operations_1 = require("./background-operations");
|
|
46
|
+
const identity = __importStar(require("./identity"));
|
|
47
|
+
const DEFAULT_RECENT_IMPORT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
48
|
+
const DEFAULT_VISIBLE_BACKGROUND_OPERATION_LIMIT = 5;
|
|
49
|
+
const DEFAULT_VISIBLE_IMPORT_CANDIDATE_LIMIT = 3;
|
|
50
|
+
const DEFAULT_WORKTREE_POOL_SEARCH_DEPTH = 2;
|
|
51
|
+
const DEFAULT_AGENT_WORKSPACE_SCAN_LIMIT = 20;
|
|
52
|
+
const SKIPPED_HOME_SCAN_DIRS = new Set([
|
|
53
|
+
".Trash",
|
|
54
|
+
"Applications",
|
|
55
|
+
"Library",
|
|
56
|
+
"Movies",
|
|
57
|
+
"Music",
|
|
58
|
+
"Pictures",
|
|
59
|
+
"Public",
|
|
60
|
+
"node_modules",
|
|
61
|
+
]);
|
|
62
|
+
function sortBackgroundOperationsNewestFirst(left, right) {
|
|
63
|
+
return Date.parse(right.updatedAt) - Date.parse(left.updatedAt);
|
|
64
|
+
}
|
|
65
|
+
function recentImportFingerprint(candidates) {
|
|
66
|
+
return candidates
|
|
67
|
+
.map((candidate) => `${candidate.path}:${candidate.mtimeMs}`)
|
|
68
|
+
.sort((left, right) => left.localeCompare(right))
|
|
69
|
+
.join("|");
|
|
70
|
+
}
|
|
71
|
+
function specText(spec, key) {
|
|
72
|
+
const value = spec?.[key];
|
|
73
|
+
return typeof value === "string" ? value.trim() : "";
|
|
74
|
+
}
|
|
75
|
+
function latestComparableOperationTimestamp(record) {
|
|
76
|
+
const fileModifiedAt = specText(record.spec, "fileModifiedAt");
|
|
77
|
+
if (fileModifiedAt) {
|
|
78
|
+
const parsed = Date.parse(fileModifiedAt);
|
|
79
|
+
if (Number.isFinite(parsed))
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
const candidates = [record.finishedAt, record.updatedAt];
|
|
83
|
+
for (const candidate of candidates) {
|
|
84
|
+
if (typeof candidate !== "string")
|
|
85
|
+
continue;
|
|
86
|
+
const parsed = Date.parse(candidate);
|
|
87
|
+
if (Number.isFinite(parsed))
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function candidateAlreadyCoveredByOperation(candidate, operation) {
|
|
93
|
+
if (operation.kind !== "mail.import-mbox")
|
|
94
|
+
return false;
|
|
95
|
+
if (specText(operation.spec, "filePath") !== candidate.path)
|
|
96
|
+
return false;
|
|
97
|
+
if (operation.status !== "succeeded")
|
|
98
|
+
return false;
|
|
99
|
+
const operationTimestamp = latestComparableOperationTimestamp(operation);
|
|
100
|
+
return operationTimestamp !== null && candidate.mtimeMs <= operationTimestamp;
|
|
101
|
+
}
|
|
102
|
+
function listChildDirs(dir) {
|
|
103
|
+
if (!fs.existsSync(dir))
|
|
104
|
+
return [];
|
|
105
|
+
try {
|
|
106
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
107
|
+
.filter((entry) => entry.isDirectory())
|
|
108
|
+
.map((entry) => path.join(dir, entry.name));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function findWorktreePools(rootDir, maxDepth) {
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
const found = [];
|
|
117
|
+
function visit(currentDir, depth) {
|
|
118
|
+
if (depth > maxDepth || seen.has(currentDir))
|
|
119
|
+
return;
|
|
120
|
+
seen.add(currentDir);
|
|
121
|
+
let entries;
|
|
122
|
+
try {
|
|
123
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (!entry.isDirectory())
|
|
130
|
+
continue;
|
|
131
|
+
if (entry.name.startsWith(".") && entry.name !== ".playwright-mcp")
|
|
132
|
+
continue;
|
|
133
|
+
if (SKIPPED_HOME_SCAN_DIRS.has(entry.name))
|
|
134
|
+
continue;
|
|
135
|
+
const childDir = path.join(currentDir, entry.name);
|
|
136
|
+
if (entry.name === "_worktrees") {
|
|
137
|
+
found.push(childDir);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
visit(childDir, depth + 1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
visit(rootDir, 0);
|
|
144
|
+
return found;
|
|
145
|
+
}
|
|
146
|
+
function listAgentWorkspaceSandboxDirs(agentName) {
|
|
147
|
+
if (!agentName)
|
|
148
|
+
return [];
|
|
149
|
+
const getAgentRepoWorkspacesRoot = Object.getOwnPropertyDescriptor(identity, "getAgentRepoWorkspacesRoot")?.value;
|
|
150
|
+
const workspacesRoot = typeof getAgentRepoWorkspacesRoot === "function"
|
|
151
|
+
? getAgentRepoWorkspacesRoot(agentName)
|
|
152
|
+
: "";
|
|
153
|
+
if (!workspacesRoot)
|
|
154
|
+
return [];
|
|
155
|
+
return listChildDirs(workspacesRoot)
|
|
156
|
+
.slice(0, DEFAULT_AGENT_WORKSPACE_SCAN_LIMIT)
|
|
157
|
+
.map((workspaceDir) => path.join(workspaceDir, ".playwright-mcp"));
|
|
158
|
+
}
|
|
159
|
+
function listWorktreePoolSandboxDirs(homeDir) {
|
|
160
|
+
return findWorktreePools(homeDir, DEFAULT_WORKTREE_POOL_SEARCH_DEPTH)
|
|
161
|
+
.flatMap((poolDir) => listChildDirs(poolDir))
|
|
162
|
+
.map((worktreeDir) => path.join(worktreeDir, ".playwright-mcp"));
|
|
163
|
+
}
|
|
164
|
+
function defaultMailImportDiscoveryDirs(input = {}) {
|
|
165
|
+
const repoRoot = path.resolve(input.repoRoot ?? process.cwd());
|
|
166
|
+
const homeDir = path.resolve(input.homeDir ?? os.homedir());
|
|
167
|
+
return [...new Set([
|
|
168
|
+
path.join(repoRoot, ".playwright-mcp"),
|
|
169
|
+
...listAgentWorkspaceSandboxDirs(input.agentName),
|
|
170
|
+
...listWorktreePoolSandboxDirs(homeDir),
|
|
171
|
+
path.join(homeDir, ".playwright-mcp"),
|
|
172
|
+
path.join(homeDir, "Downloads"),
|
|
173
|
+
].map((dir) => path.resolve(dir)))];
|
|
174
|
+
}
|
|
175
|
+
function listDiscoveredMboxCandidates(dir) {
|
|
176
|
+
if (!fs.existsSync(dir))
|
|
177
|
+
return [];
|
|
178
|
+
try {
|
|
179
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
180
|
+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".mbox"))
|
|
181
|
+
.map((entry) => {
|
|
182
|
+
const candidatePath = path.join(dir, entry.name);
|
|
183
|
+
const stat = fs.statSync(candidatePath);
|
|
184
|
+
return { path: candidatePath, name: entry.name, mtimeMs: stat.mtimeMs };
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function normalizeMboxDiscoveryText(value) {
|
|
192
|
+
return value
|
|
193
|
+
.toLowerCase()
|
|
194
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
195
|
+
.trim();
|
|
196
|
+
}
|
|
197
|
+
function tokenizeMboxDiscoveryHint(value) {
|
|
198
|
+
if (!value)
|
|
199
|
+
return [];
|
|
200
|
+
return normalizeMboxDiscoveryText(value)
|
|
201
|
+
.split(" ")
|
|
202
|
+
.filter((token) => token.length > 0);
|
|
203
|
+
}
|
|
204
|
+
function scoreDiscoveredMboxCandidate(fileName, mtimeMs, ownerEmail, source) {
|
|
205
|
+
const normalized = normalizeMboxDiscoveryText(fileName);
|
|
206
|
+
const ownerTokens = tokenizeMboxDiscoveryHint(ownerEmail);
|
|
207
|
+
const sourceTokens = tokenizeMboxDiscoveryHint(source);
|
|
208
|
+
const ownerScore = ownerTokens.reduce((score, token) => score + (normalized.includes(token) ? 50 : 0), 0);
|
|
209
|
+
const sourceScore = sourceTokens.reduce((score, token) => score + (normalized.includes(token) ? 20 : 0), 0);
|
|
210
|
+
const recencyScore = Math.floor(mtimeMs / 1000);
|
|
211
|
+
return ownerScore + sourceScore + recencyScore;
|
|
212
|
+
}
|
|
213
|
+
function rankDiscoveredMboxCandidates(candidates, ownerEmail, source) {
|
|
214
|
+
return candidates
|
|
215
|
+
.map((candidate) => ({
|
|
216
|
+
path: candidate.path,
|
|
217
|
+
score: scoreDiscoveredMboxCandidate(candidate.name, candidate.mtimeMs, ownerEmail, source),
|
|
218
|
+
}))
|
|
219
|
+
.sort((left, right) => right.score - left.score);
|
|
220
|
+
}
|
|
221
|
+
function discoverMailImportFilePath(input) {
|
|
222
|
+
const searchDirs = defaultMailImportDiscoveryDirs({
|
|
223
|
+
agentName: input.agentName,
|
|
224
|
+
repoRoot: input.repoRoot,
|
|
225
|
+
homeDir: input.homeDir,
|
|
226
|
+
});
|
|
227
|
+
const candidates = searchDirs.flatMap((dir) => listDiscoveredMboxCandidates(dir));
|
|
228
|
+
const ranked = rankDiscoveredMboxCandidates(candidates, input.ownerEmail, input.source);
|
|
229
|
+
if (ranked.length === 0) {
|
|
230
|
+
throw new Error(`could not discover an MBOX file in ${searchDirs.join(", ")}`);
|
|
231
|
+
}
|
|
232
|
+
const topScore = ranked[0].score;
|
|
233
|
+
const topCandidates = ranked.filter((candidate) => candidate.score === topScore);
|
|
234
|
+
if (topCandidates.length > 1) {
|
|
235
|
+
throw new Error(`multiple candidate MBOX files found: ${topCandidates.map((candidate) => candidate.path).join(", ")}`);
|
|
236
|
+
}
|
|
237
|
+
return topCandidates[0].path;
|
|
238
|
+
}
|
|
239
|
+
function summarizeAmbientImportCandidates(candidates, candidateLimit) {
|
|
240
|
+
const visibleCandidates = candidates.slice(0, candidateLimit);
|
|
241
|
+
const hiddenCount = Math.max(0, candidates.length - visibleCandidates.length);
|
|
242
|
+
const summary = visibleCandidates.length === 1
|
|
243
|
+
? "recent MBOX archive ready for import"
|
|
244
|
+
: `${visibleCandidates.length} recent MBOX archives ready for import`;
|
|
245
|
+
const detailLines = [
|
|
246
|
+
"recent candidates:",
|
|
247
|
+
...visibleCandidates.map((candidate) => `- ${candidate.path}`),
|
|
248
|
+
...(hiddenCount > 0 ? [`- ...and ${hiddenCount} more recent archive${hiddenCount === 1 ? "" : "s"}`] : []),
|
|
249
|
+
"next: if one matches an outstanding mail backfill, run `ouro mail import-mbox --discover` with owner/source hints so Ouro can select the right archive or report ambiguity.",
|
|
250
|
+
];
|
|
251
|
+
return {
|
|
252
|
+
summary,
|
|
253
|
+
detail: detailLines.join("\n"),
|
|
254
|
+
spec: {
|
|
255
|
+
fingerprint: recentImportFingerprint(candidates),
|
|
256
|
+
candidatePaths: visibleCandidates.map((candidate) => candidate.path),
|
|
257
|
+
newestCandidatePath: visibleCandidates[0]?.path ?? null,
|
|
258
|
+
newestCandidateMtime: visibleCandidates[0] ? new Date(visibleCandidates[0].mtimeMs).toISOString() : null,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function listAmbientMailImportOperations(input) {
|
|
263
|
+
const existingOperations = input.existingOperations ?? [];
|
|
264
|
+
const hasLiveImport = existingOperations.some((operation) => operation.kind === "mail.import-mbox"
|
|
265
|
+
&& (operation.status === "queued" || operation.status === "running"));
|
|
266
|
+
if (hasLiveImport)
|
|
267
|
+
return [];
|
|
268
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
269
|
+
const recentWindowMs = input.recentWindowMs ?? DEFAULT_RECENT_IMPORT_WINDOW_MS;
|
|
270
|
+
const candidateLimit = input.candidateLimit ?? DEFAULT_VISIBLE_IMPORT_CANDIDATE_LIMIT;
|
|
271
|
+
const recentCandidates = defaultMailImportDiscoveryDirs({
|
|
272
|
+
agentName: input.agentName,
|
|
273
|
+
repoRoot: input.repoRoot,
|
|
274
|
+
homeDir: input.homeDir,
|
|
275
|
+
})
|
|
276
|
+
.flatMap((dir) => listDiscoveredMboxCandidates(dir))
|
|
277
|
+
.filter((candidate) => (nowMs - candidate.mtimeMs) <= recentWindowMs)
|
|
278
|
+
.filter((candidate) => !existingOperations.some((operation) => candidateAlreadyCoveredByOperation(candidate, operation)))
|
|
279
|
+
.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
280
|
+
if (recentCandidates.length === 0)
|
|
281
|
+
return [];
|
|
282
|
+
const newestCandidate = recentCandidates[0];
|
|
283
|
+
const rendered = summarizeAmbientImportCandidates(recentCandidates, candidateLimit);
|
|
284
|
+
return [{
|
|
285
|
+
schemaVersion: 1,
|
|
286
|
+
id: "ambient_mail_import_ready",
|
|
287
|
+
agentName: input.agentName,
|
|
288
|
+
kind: "mail.import-discovered",
|
|
289
|
+
title: "mail import ready",
|
|
290
|
+
status: "queued",
|
|
291
|
+
summary: rendered.summary,
|
|
292
|
+
detail: rendered.detail,
|
|
293
|
+
createdAt: new Date(newestCandidate.mtimeMs).toISOString(),
|
|
294
|
+
updatedAt: new Date(newestCandidate.mtimeMs).toISOString(),
|
|
295
|
+
spec: rendered.spec,
|
|
296
|
+
}];
|
|
297
|
+
}
|
|
298
|
+
function listVisibleBackgroundOperations(input) {
|
|
299
|
+
const limit = input.limit ?? DEFAULT_VISIBLE_BACKGROUND_OPERATION_LIMIT;
|
|
300
|
+
const persisted = (0, background_operations_1.listBackgroundOperations)({
|
|
301
|
+
agentName: input.agentName,
|
|
302
|
+
agentRoot: input.agentRoot,
|
|
303
|
+
limit,
|
|
304
|
+
});
|
|
305
|
+
const ambient = listAmbientMailImportOperations({
|
|
306
|
+
agentName: input.agentName,
|
|
307
|
+
agentRoot: input.agentRoot,
|
|
308
|
+
existingOperations: persisted,
|
|
309
|
+
repoRoot: input.repoRoot,
|
|
310
|
+
homeDir: input.homeDir,
|
|
311
|
+
nowMs: input.nowMs,
|
|
312
|
+
recentWindowMs: input.recentWindowMs,
|
|
313
|
+
candidateLimit: input.candidateLimit,
|
|
314
|
+
});
|
|
315
|
+
return [...persisted, ...ambient]
|
|
316
|
+
.sort(sortBackgroundOperationsNewestFirst)
|
|
317
|
+
.slice(0, limit);
|
|
318
|
+
}
|
|
@@ -53,7 +53,7 @@ function createOutlookHttpRequestHandler(options) {
|
|
|
53
53
|
(0, outlook_http_response_1.writeJson)(response, 404, { ok: false, error: "asset not found" });
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
if (pathname === "/outlook") {
|
|
56
|
+
if (pathname === "/outlook" || pathname === "/mailbox") {
|
|
57
57
|
response.writeHead(301, { location: "/" });
|
|
58
58
|
response.end();
|
|
59
59
|
return;
|
|
@@ -61,6 +61,10 @@ function normalizeLegacyOutlookApiPath(pathname) {
|
|
|
61
61
|
return pathname.slice("/outlook".length);
|
|
62
62
|
if (pathname === "/outlook/api")
|
|
63
63
|
return "/api";
|
|
64
|
+
if (pathname.startsWith("/mailbox/api/"))
|
|
65
|
+
return pathname.slice("/mailbox".length);
|
|
66
|
+
if (pathname === "/mailbox/api")
|
|
67
|
+
return "/api";
|
|
64
68
|
return pathname;
|
|
65
69
|
}
|
|
66
70
|
function defaultSpaDistCandidates() {
|
|
@@ -76,7 +76,7 @@ async function startOutlookHttpServer(options = {}) {
|
|
|
76
76
|
(0, runtime_1.emitNervesEvent)({
|
|
77
77
|
component: "daemon",
|
|
78
78
|
event: "daemon.outlook_http_started",
|
|
79
|
-
message: "started
|
|
79
|
+
message: "started Mailbox HTTP server",
|
|
80
80
|
meta: { origin },
|
|
81
81
|
});
|
|
82
82
|
return {
|
|
@@ -91,7 +91,7 @@ async function startOutlookHttpServer(options = {}) {
|
|
|
91
91
|
(0, runtime_1.emitNervesEvent)({
|
|
92
92
|
component: "daemon",
|
|
93
93
|
event: "daemon.outlook_http_stopped",
|
|
94
|
-
message: "stopped
|
|
94
|
+
message: "stopped Mailbox HTTP server",
|
|
95
95
|
meta: { origin },
|
|
96
96
|
});
|
|
97
97
|
},
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.OUTLOOK_DEFAULT_PORT = exports.OUTLOOK_DEFAULT_INNER_VISIBILITY = exports.OUTLOOK_RELEASE_INTERACTION_MODEL = exports.OUTLOOK_PRODUCT_NAME = void 0;
|
|
4
4
|
exports.getOutlookTranscriptMessageText = getOutlookTranscriptMessageText;
|
|
5
5
|
exports.getOutlookTranscriptTimestamp = getOutlookTranscriptTimestamp;
|
|
6
|
-
exports.OUTLOOK_PRODUCT_NAME = "Ouro
|
|
6
|
+
exports.OUTLOOK_PRODUCT_NAME = "Ouro Mailbox";
|
|
7
7
|
exports.OUTLOOK_RELEASE_INTERACTION_MODEL = "read-only";
|
|
8
8
|
exports.OUTLOOK_DEFAULT_INNER_VISIBILITY = "summary";
|
|
9
9
|
exports.OUTLOOK_DEFAULT_PORT = 6876;
|
|
@@ -94,8 +94,8 @@ function buildOutlookMachineView(input) {
|
|
|
94
94
|
totals,
|
|
95
95
|
mood: deriveMood(input.machine, input.daemon),
|
|
96
96
|
entrypoints: [
|
|
97
|
-
{ kind: "web", label: "Open
|
|
98
|
-
{ kind: "cli", label: "CLI JSON", target: "ouro
|
|
97
|
+
{ kind: "web", label: "Open Mailbox", target: input.daemon.outlookUrl },
|
|
98
|
+
{ kind: "cli", label: "CLI JSON", target: "ouro mailbox --json" },
|
|
99
99
|
],
|
|
100
100
|
},
|
|
101
101
|
agents,
|
|
@@ -162,7 +162,7 @@ function buildRecentActivity(agent) {
|
|
|
162
162
|
}
|
|
163
163
|
function buildOutlookAgentView(input) {
|
|
164
164
|
/* v8 ignore next */
|
|
165
|
-
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.outlook_view_agent", message: `building
|
|
165
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.outlook_view_agent", message: `building mailbox view for ${input.agent.agentName}`, meta: { agent: input.agent.agentName } });
|
|
166
166
|
const viewer = normalizeViewer(input.viewer);
|
|
167
167
|
return {
|
|
168
168
|
productName: outlook_types_1.OUTLOOK_PRODUCT_NAME,
|
|
@@ -115,19 +115,42 @@ function readTaskSummary(agentRoot) {
|
|
|
115
115
|
issues,
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
+
const STALE_CODING_SURFACE_WINDOW_MS = 60 * 60 * 1000;
|
|
119
|
+
function buildLiveCodingSurfaceLabels(agentRoot) {
|
|
120
|
+
return new Set(readCodingSummary(agentRoot).items
|
|
121
|
+
.filter((item) => shared_1.ACTIVE_CODING_STATUSES.has(item.status))
|
|
122
|
+
.map((item) => `${item.runner} ${item.id}`));
|
|
123
|
+
}
|
|
124
|
+
function normalizeObligationCurrentSurface(currentSurface, updatedAt, liveCodingSurfaceLabels) {
|
|
125
|
+
if (!currentSurface || currentSurface.kind !== "coding")
|
|
126
|
+
return currentSurface;
|
|
127
|
+
const liveLabel = currentSurface.label.trim();
|
|
128
|
+
if (!liveLabel)
|
|
129
|
+
return null;
|
|
130
|
+
if (liveCodingSurfaceLabels.has(liveLabel))
|
|
131
|
+
return currentSurface;
|
|
132
|
+
const updatedAtMs = Date.parse(updatedAt);
|
|
133
|
+
const recentlyTouched = Number.isFinite(updatedAtMs)
|
|
134
|
+
&& (Date.now() - updatedAtMs) <= STALE_CODING_SURFACE_WINDOW_MS;
|
|
135
|
+
return recentlyTouched ? currentSurface : null;
|
|
136
|
+
}
|
|
118
137
|
function readObligationSummary(agentRoot) {
|
|
138
|
+
const liveCodingSurfaceLabels = buildLiveCodingSurfaceLabels(agentRoot);
|
|
119
139
|
const items = (0, obligations_1.readPendingObligations)(agentRoot)
|
|
120
|
-
.map((obligation) =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
.map((obligation) => {
|
|
141
|
+
const updatedAt = obligation.updatedAt ?? obligation.createdAt;
|
|
142
|
+
return {
|
|
143
|
+
id: obligation.id,
|
|
144
|
+
status: obligation.status,
|
|
145
|
+
content: obligation.content,
|
|
146
|
+
updatedAt,
|
|
147
|
+
nextAction: obligation.nextAction ?? null,
|
|
148
|
+
/* v8 ignore start */
|
|
149
|
+
origin: obligation.origin ?? null,
|
|
150
|
+
currentSurface: normalizeObligationCurrentSurface(obligation.currentSurface ?? null, updatedAt, liveCodingSurfaceLabels),
|
|
151
|
+
/* v8 ignore stop */
|
|
152
|
+
};
|
|
153
|
+
})
|
|
131
154
|
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
132
155
|
return { items };
|
|
133
156
|
}
|
|
@@ -49,6 +49,7 @@ const presence_1 = require("../../../arc/presence");
|
|
|
49
49
|
const scanner_1 = require("../../../repertoire/tasks/scanner");
|
|
50
50
|
const active_work_1 = require("../../active-work");
|
|
51
51
|
const session_activity_1 = require("../../session-activity");
|
|
52
|
+
const agent_machine_1 = require("./agent-machine");
|
|
52
53
|
function sortOpenObligations(obligations) {
|
|
53
54
|
const statusPriority = {
|
|
54
55
|
returning: 0,
|
|
@@ -185,6 +186,7 @@ function readObligationDetailView(agentRoot) {
|
|
|
185
186
|
const openObligations = obligations.filter(obligations_1.isOpenObligation);
|
|
186
187
|
const sorted = sortOpenObligations(openObligations);
|
|
187
188
|
const primary = sorted[0] ?? null;
|
|
189
|
+
const normalizedSummary = new Map((0, agent_machine_1.readObligationSummary)(agentRoot).items.map((item) => [item.id, item.currentSurface]));
|
|
188
190
|
const items = openObligations.map((ob) => ({
|
|
189
191
|
id: ob.id,
|
|
190
192
|
status: ob.status,
|
|
@@ -192,7 +194,9 @@ function readObligationDetailView(agentRoot) {
|
|
|
192
194
|
updatedAt: ob.updatedAt ?? ob.createdAt,
|
|
193
195
|
nextAction: ob.nextAction ?? null,
|
|
194
196
|
origin: ob.origin ?? null,
|
|
195
|
-
currentSurface:
|
|
197
|
+
currentSurface: normalizedSummary.has(ob.id)
|
|
198
|
+
? (normalizedSummary.get(ob.id) ?? null)
|
|
199
|
+
: (ob.currentSurface ? { kind: ob.currentSurface.kind, label: ob.currentSurface.label } : null),
|
|
196
200
|
meaning: ob.meaning ? { waitingOn: ob.meaning.waitingOn?.detail ?? null } : null,
|
|
197
201
|
isPrimary: ob.id === primary.id,
|
|
198
202
|
}));
|
|
@@ -247,7 +247,7 @@ function emitMailRead(agentName, mode, status) {
|
|
|
247
247
|
(0, runtime_1.emitNervesEvent)({
|
|
248
248
|
component: "heart",
|
|
249
249
|
event: "heart.outlook_mail_read",
|
|
250
|
-
message: "reading
|
|
250
|
+
message: "reading Mailbox mail surface",
|
|
251
251
|
meta: { agentName, mode, status },
|
|
252
252
|
});
|
|
253
253
|
}
|
|
@@ -271,7 +271,7 @@ async function readMailView(agentName) {
|
|
|
271
271
|
await resolved.store.recordAccess({
|
|
272
272
|
agentId: agentName,
|
|
273
273
|
tool: "outlook_mail_list",
|
|
274
|
-
reason: "
|
|
274
|
+
reason: "mailbox read-only mailbox",
|
|
275
275
|
});
|
|
276
276
|
const accessLog = accessEntries(await resolved.store.listAccessLog(agentName));
|
|
277
277
|
emitMailRead(agentName, "list", "ready");
|
|
@@ -325,7 +325,7 @@ async function readMailMessageView(agentName, messageId) {
|
|
|
325
325
|
agentId: agentName,
|
|
326
326
|
messageId,
|
|
327
327
|
tool: "outlook_mail_message",
|
|
328
|
-
reason: "
|
|
328
|
+
reason: "mailbox read-only message body",
|
|
329
329
|
...accessProvenance(decrypted),
|
|
330
330
|
});
|
|
331
331
|
const body = decrypted.private.text.length > OUTLOOK_MAIL_BODY_LIMIT
|
|
@@ -112,6 +112,24 @@ function normalizeContent(content) {
|
|
|
112
112
|
.filter((part) => part != null && typeof part === "object")
|
|
113
113
|
.map((part) => ({ ...part }));
|
|
114
114
|
}
|
|
115
|
+
const SYNTHETIC_TIMESTAMP_PREFIX_RE = /^(?:(?:\[(?:just now|-\d+[mhd])\])\s*)+/i;
|
|
116
|
+
function stripSyntheticTimestampPrefix(text) {
|
|
117
|
+
return text.replace(SYNTHETIC_TIMESTAMP_PREFIX_RE, "");
|
|
118
|
+
}
|
|
119
|
+
function sanitizeConversationContent(role, content) {
|
|
120
|
+
if (role !== "user" && role !== "assistant")
|
|
121
|
+
return content;
|
|
122
|
+
if (typeof content === "string")
|
|
123
|
+
return stripSyntheticTimestampPrefix(content);
|
|
124
|
+
if (!Array.isArray(content))
|
|
125
|
+
return content;
|
|
126
|
+
return content.map((part) => {
|
|
127
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
128
|
+
return { ...part, text: stripSyntheticTimestampPrefix(part.text) };
|
|
129
|
+
}
|
|
130
|
+
return part;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
115
133
|
function normalizeToolCalls(rawToolCalls) {
|
|
116
134
|
if (!Array.isArray(rawToolCalls))
|
|
117
135
|
return [];
|
|
@@ -141,10 +159,11 @@ function normalizeRole(role) {
|
|
|
141
159
|
function normalizeMessage(message) {
|
|
142
160
|
const record = message;
|
|
143
161
|
const role = normalizeRole(record.role);
|
|
162
|
+
const normalizedContent = sanitizeConversationContent(role, normalizeContent(record.content));
|
|
144
163
|
if (role === "assistant") {
|
|
145
164
|
return {
|
|
146
165
|
role,
|
|
147
|
-
content:
|
|
166
|
+
content: normalizedContent,
|
|
148
167
|
name: typeof record.name === "string" ? record.name : null,
|
|
149
168
|
toolCallId: null,
|
|
150
169
|
toolCalls: normalizeToolCalls(record.tool_calls),
|
|
@@ -163,7 +182,7 @@ function normalizeMessage(message) {
|
|
|
163
182
|
}
|
|
164
183
|
return {
|
|
165
184
|
role,
|
|
166
|
-
content:
|
|
185
|
+
content: normalizedContent ?? "",
|
|
167
186
|
name: typeof record.name === "string" ? record.name : null,
|
|
168
187
|
toolCallId: null,
|
|
169
188
|
toolCalls: [],
|
|
@@ -286,7 +305,7 @@ function repairSessionMessages(messages) {
|
|
|
286
305
|
});
|
|
287
306
|
return result.map(toProviderMessage);
|
|
288
307
|
}
|
|
289
|
-
function
|
|
308
|
+
function repairToolCallSequences(messages) {
|
|
290
309
|
const normalized = messages.map(normalizeMessage);
|
|
291
310
|
const validCallIds = new Set();
|
|
292
311
|
for (const msg of normalized) {
|
|
@@ -313,8 +332,74 @@ function stripOrphanedToolResults(messages) {
|
|
|
313
332
|
meta: { removed },
|
|
314
333
|
});
|
|
315
334
|
}
|
|
335
|
+
let injected = 0;
|
|
336
|
+
for (let i = 0; i < repaired.length; i++) {
|
|
337
|
+
const msg = repaired[i];
|
|
338
|
+
if (msg.role !== "assistant" || msg.toolCalls.length === 0)
|
|
339
|
+
continue;
|
|
340
|
+
const resultIds = new Set();
|
|
341
|
+
for (let j = i + 1; j < repaired.length; j++) {
|
|
342
|
+
const following = repaired[j];
|
|
343
|
+
if (following.role === "tool" && following.toolCallId !== null) {
|
|
344
|
+
resultIds.add(following.toolCallId);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (following.role === "assistant" || following.role === "user")
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
const missing = msg.toolCalls.filter((toolCall) => !resultIds.has(toolCall.id));
|
|
351
|
+
if (missing.length === 0)
|
|
352
|
+
continue;
|
|
353
|
+
const syntheticResults = missing.map((toolCall) => ({
|
|
354
|
+
role: "tool",
|
|
355
|
+
content: "error: tool call was interrupted (previous turn timed out or was aborted)",
|
|
356
|
+
name: null,
|
|
357
|
+
toolCallId: toolCall.id,
|
|
358
|
+
toolCalls: [],
|
|
359
|
+
hadToolCallsField: false,
|
|
360
|
+
}));
|
|
361
|
+
let insertAt = i + 1;
|
|
362
|
+
while (insertAt < repaired.length && repaired[insertAt].role === "tool")
|
|
363
|
+
insertAt++;
|
|
364
|
+
repaired.splice(insertAt, 0, ...syntheticResults);
|
|
365
|
+
injected += syntheticResults.length;
|
|
366
|
+
}
|
|
367
|
+
if (injected > 0) {
|
|
368
|
+
(0, runtime_1.emitNervesEvent)({
|
|
369
|
+
level: "info",
|
|
370
|
+
event: "mind.session_orphan_tool_call_repair",
|
|
371
|
+
component: "mind",
|
|
372
|
+
message: "injected synthetic tool results for orphaned tool calls",
|
|
373
|
+
meta: { injected },
|
|
374
|
+
});
|
|
375
|
+
}
|
|
316
376
|
return repaired.map(toProviderMessage);
|
|
317
377
|
}
|
|
378
|
+
function canonicalizeSystemMessageSequence(messages) {
|
|
379
|
+
const normalized = messages.map(normalizeMessage);
|
|
380
|
+
const firstSystemIndex = normalized.findIndex((msg) => msg.role === "system");
|
|
381
|
+
if (firstSystemIndex === -1)
|
|
382
|
+
return normalized.map(toProviderMessage);
|
|
383
|
+
const extraSystemCount = normalized.filter((msg) => msg.role === "system").length - 1;
|
|
384
|
+
if (firstSystemIndex === 0 && extraSystemCount === 0) {
|
|
385
|
+
return normalized.map(toProviderMessage);
|
|
386
|
+
}
|
|
387
|
+
const primarySystem = normalized[firstSystemIndex];
|
|
388
|
+
const nonSystemMessages = normalized.filter((msg) => msg.role !== "system");
|
|
389
|
+
const repaired = [primarySystem, ...nonSystemMessages].map(toProviderMessage);
|
|
390
|
+
(0, runtime_1.emitNervesEvent)({
|
|
391
|
+
level: "info",
|
|
392
|
+
event: "mind.session_system_prompt_repair",
|
|
393
|
+
component: "mind",
|
|
394
|
+
message: "canonicalized session system prompt sequence",
|
|
395
|
+
meta: {
|
|
396
|
+
firstSystemIndex,
|
|
397
|
+
extraSystemCount,
|
|
398
|
+
finalMessageCount: repaired.length,
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
return repaired;
|
|
402
|
+
}
|
|
318
403
|
function migrateToolNames(messages) {
|
|
319
404
|
const safeMessages = messages.filter((message) => Boolean(message) && typeof message === "object");
|
|
320
405
|
let migrated = 0;
|
|
@@ -359,7 +444,7 @@ function sanitizeProviderMessages(messages) {
|
|
|
359
444
|
meta: { violations },
|
|
360
445
|
});
|
|
361
446
|
}
|
|
362
|
-
return migrateToolNames(
|
|
447
|
+
return canonicalizeSystemMessageSequence(migrateToolNames(repairToolCallSequences(repairSessionMessages(normalized.map(toProviderMessage)))));
|
|
363
448
|
}
|
|
364
449
|
function stampIngressTime(msg) {
|
|
365
450
|
msg._ingressAt = new Date().toISOString();
|
|
@@ -587,11 +672,12 @@ function parseSessionEnvelope(raw, options = {}) {
|
|
|
587
672
|
const time = event.time;
|
|
588
673
|
const relations = event.relations;
|
|
589
674
|
const provenance = event.provenance;
|
|
675
|
+
const content = sanitizeConversationContent(role, normalizeContent(event.content));
|
|
590
676
|
return {
|
|
591
677
|
id: typeof event.id === "string" ? event.id : makeEventId(index + 1),
|
|
592
678
|
sequence: typeof event.sequence === "number" ? event.sequence : index + 1,
|
|
593
679
|
role,
|
|
594
|
-
content
|
|
680
|
+
content,
|
|
595
681
|
name: typeof event.name === "string" ? event.name : null,
|
|
596
682
|
toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : null,
|
|
597
683
|
toolCalls: normalizeToolCalls(event.toolCalls),
|