@ouro.bot/cli 0.1.0-alpha.482 → 0.1.0-alpha.484

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 (34) hide show
  1. package/changelog.json +14 -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/provider-failover.js +35 -0
  20. package/dist/heart/session-events.js +91 -5
  21. package/dist/heart/turn-context.js +11 -0
  22. package/dist/mind/context.js +1 -1
  23. package/dist/mind/prompt.js +6 -3
  24. package/dist/nerves/coverage/file-completeness.js +1 -0
  25. package/dist/nerves/observation.js +2 -2
  26. package/dist/outlook-ui/assets/{index-CPfhbn13.js → index-Cm51CY9W.js} +1 -1
  27. package/dist/outlook-ui/index.html +2 -2
  28. package/dist/repertoire/tools-mail.js +2 -2
  29. package/dist/repertoire/tools-session.js +5 -2
  30. package/dist/senses/cli/ouro-tui.js +1 -1
  31. package/dist/senses/inner-dialog.js +5 -7
  32. package/dist/senses/mail.js +149 -18
  33. package/dist/senses/pipeline.js +70 -7
  34. 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
@@ -5,6 +5,7 @@ exports.formatReadyProviderLabel = formatReadyProviderLabel;
5
5
  exports.buildFailoverContext = buildFailoverContext;
6
6
  exports.handleFailoverReply = handleFailoverReply;
7
7
  exports.runMachineProviderFailoverInventory = runMachineProviderFailoverInventory;
8
+ exports.validateFailoverSwitchCandidate = validateFailoverSwitchCandidate;
8
9
  const identity_1 = require("./identity");
9
10
  const provider_ping_1 = require("./provider-ping");
10
11
  const provider_models_1 = require("./provider-models");
@@ -170,6 +171,7 @@ function buildFailoverContext(errorMessage, classification, currentProvider, cur
170
171
  errorSummary,
171
172
  classification,
172
173
  currentProvider,
174
+ currentModel,
173
175
  currentLane,
174
176
  agentName,
175
177
  workingProviders,
@@ -264,3 +266,36 @@ async function runMachineProviderFailoverInventory(agentName, currentProvider, o
264
266
  });
265
267
  return inventory;
266
268
  }
269
+ /**
270
+ * Re-verify a failover candidate is actually reachable right before we mutate
271
+ * provider state. The inventory ping that produced the candidate may be stale
272
+ * (creds revoked between inventory and reply); without this preflight, an
273
+ * agent-driven "switch to <provider>" can move the lane onto an unreachable
274
+ * provider and brick the next turn.
275
+ */
276
+ async function validateFailoverSwitchCandidate(agentName, candidate, options = {}) {
277
+ const ping = options.ping ?? provider_ping_1.pingProvider;
278
+ const refreshPool = options.refreshPool ?? provider_credentials_1.refreshProviderCredentialPool;
279
+ const poolResult = await refreshPool(agentName);
280
+ if (!poolResult.ok) {
281
+ return {
282
+ ok: false,
283
+ classification: "auth-failure",
284
+ message: `provider credential pool unavailable (${poolResult.reason}): ${poolResult.error}`,
285
+ };
286
+ }
287
+ const record = poolResult.pool.providers[candidate.provider];
288
+ if (!record) {
289
+ return {
290
+ ok: false,
291
+ classification: "auth-failure",
292
+ message: `no credentials configured for ${candidate.provider}`,
293
+ };
294
+ }
295
+ const config = { ...record.credentials, ...record.config };
296
+ const result = await ping(candidate.provider, config, { model: candidate.model });
297
+ if (!result.ok) {
298
+ return { ok: false, classification: result.classification, message: result.message };
299
+ }
300
+ return { ok: true };
301
+ }