@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.
Files changed (33) hide show
  1. package/changelog.json +8 -0
  2. package/dist/heart/active-work.js +46 -0
  3. package/dist/heart/background-operations.js +27 -3
  4. package/dist/heart/core.js +15 -0
  5. package/dist/heart/daemon/cli-exec.js +104 -15
  6. package/dist/heart/daemon/cli-help.js +10 -4
  7. package/dist/heart/daemon/cli-parse.js +11 -5
  8. package/dist/heart/daemon/cli-render.js +1 -1
  9. package/dist/heart/daemon/thoughts.js +22 -8
  10. package/dist/heart/mail-import-discovery.js +318 -0
  11. package/dist/heart/outlook/outlook-http-routes.js +1 -1
  12. package/dist/heart/outlook/outlook-http-static.js +4 -0
  13. package/dist/heart/outlook/outlook-http.js +2 -2
  14. package/dist/heart/outlook/outlook-types.js +1 -1
  15. package/dist/heart/outlook/outlook-view.js +3 -3
  16. package/dist/heart/outlook/readers/agent-machine.js +34 -11
  17. package/dist/heart/outlook/readers/continuity-readers.js +5 -1
  18. package/dist/heart/outlook/readers/mail.js +3 -3
  19. package/dist/heart/session-events.js +91 -5
  20. package/dist/heart/turn-context.js +11 -0
  21. package/dist/mind/context.js +1 -1
  22. package/dist/mind/prompt.js +6 -3
  23. package/dist/nerves/coverage/file-completeness.js +1 -0
  24. package/dist/nerves/observation.js +2 -2
  25. package/dist/outlook-ui/assets/{index-CPfhbn13.js → index-Cm51CY9W.js} +1 -1
  26. package/dist/outlook-ui/index.html +2 -2
  27. package/dist/repertoire/tools-mail.js +2 -2
  28. package/dist/repertoire/tools-session.js +5 -2
  29. package/dist/senses/cli/ouro-tui.js +1 -1
  30. package/dist/senses/inner-dialog.js +5 -7
  31. package/dist/senses/mail.js +149 -18
  32. package/dist/senses/pipeline.js +2 -1
  33. 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 Outlook HTTP server",
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 Outlook HTTP server",
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 Outlook";
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 Outlook", target: input.daemon.outlookUrl },
98
- { kind: "cli", label: "CLI JSON", target: "ouro outlook --json" },
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 outlook view for ${input.agent.agentName}`, meta: { agent: input.agent.agentName } });
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
- id: obligation.id,
122
- status: obligation.status,
123
- content: obligation.content,
124
- updatedAt: obligation.updatedAt ?? obligation.createdAt,
125
- nextAction: obligation.nextAction ?? null,
126
- /* v8 ignore start */
127
- origin: obligation.origin ?? null,
128
- currentSurface: obligation.currentSurface ?? null,
129
- /* v8 ignore stop */
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: ob.currentSurface ? { kind: ob.currentSurface.kind, label: ob.currentSurface.label } : null,
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 Outlook mail surface",
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: "outlook read-only mailbox",
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: "outlook read-only message body",
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: normalizeContent(record.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: normalizeContent(record.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 stripOrphanedToolResults(messages) {
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(stripOrphanedToolResults(repairSessionMessages(normalized.map(toProviderMessage))));
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: normalizeContent(event.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),