@ouro.bot/cli 0.1.0-alpha.481 → 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 (34) hide show
  1. package/changelog.json +16 -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 +153 -19
  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/mailroom/blob-store.js +22 -1
  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 +2 -1
  34. package/package.json +1 -1
package/changelog.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.483",
6
+ "changes": [
7
+ "`ouro mail import-mbox --discover` can now find HEY exports downloaded through the browser MCP sandbox as well as `~/Downloads`, so delegated backfill no longer depends on a human hand-copying Playwright-normalized `.mbox` paths.",
8
+ "Inner-dialog truth surfaces now strip synthetic timestamp noise and understand live v2 session envelopes, which makes `ouro inner`, `ouro thoughts`, and shared-work status checks report the real recent state instead of stale or empty transcript tails.",
9
+ "Ouro Mailbox is now the human-facing name for the read-only mailbox surface, and stale dead coding lanes no longer masquerade as live return-ready work inside Mailbox obligation and needs-me views while the `ouro.bot` wrapper stays version-synced for the release."
10
+ ]
11
+ },
12
+ {
13
+ "version": "0.1.0-alpha.482",
14
+ "changes": [
15
+ "Tracked mail imports and hosted mail index repairs now queue completion and failure notices into the live MCP session when one exists, so `check_response` learns about background mail work immediately instead of silently showing no pending messages.",
16
+ "When no MCP session is live, background mail-operation notices now fall back cleanly to the freshest non-blocked outward session instead of depending on inner-dialog-only wakeups.",
17
+ "Hosted Blob duplicate checks now treat unreadable already-existing message blobs as degraded duplicates during import reruns, preventing large delegated HEY archive backfills from crashing on a transient duplicate-read timeout while keeping the `ouro.bot` wrapper version-synced for the release."
18
+ ]
19
+ },
4
20
  {
5
21
  "version": "0.1.0-alpha.481",
6
22
  "changes": [
@@ -231,6 +231,48 @@ function formatObligationContentNextAction(obligation) {
231
231
  return null;
232
232
  return `work on "${content}" and bring back a concrete artifact`;
233
233
  }
234
+ function backgroundOperationPriority(operation) {
235
+ switch (operation.status) {
236
+ case "failed": return 0;
237
+ case "queued": return 1;
238
+ case "running": return 2;
239
+ case "succeeded": return 3;
240
+ }
241
+ }
242
+ function selectPrimaryBackgroundOperation(frame) {
243
+ const operations = frame.backgroundOperations ?? [];
244
+ if (operations.length === 0)
245
+ return null;
246
+ return [...operations].sort((left, right) => {
247
+ const priorityDiff = backgroundOperationPriority(left) - backgroundOperationPriority(right);
248
+ if (priorityDiff !== 0)
249
+ return priorityDiff;
250
+ return Date.parse(right.updatedAt) - Date.parse(left.updatedAt);
251
+ })[0] ?? null;
252
+ }
253
+ function formatBackgroundOperationNextAction(operation) {
254
+ if (operation.kind === "mail.import-discovered") {
255
+ return "inspect the import-ready archive and start the matching mail import";
256
+ }
257
+ if (operation.kind === "mail.import-mbox") {
258
+ switch (operation.status) {
259
+ case "failed":
260
+ return "inspect the failed mail import, fix the issue, and retry it";
261
+ case "queued":
262
+ return "let the queued mail import start; failure or completion will wake me, so i only re-check if i need status or it looks stalled";
263
+ case "running":
264
+ return "let the background mail import run; failure or completion will wake me, so i only check again if i need status or it looks stalled";
265
+ case "succeeded":
266
+ return "review the completed mail import and continue from the updated mailbox state; i only rerun if a newer archive appears";
267
+ }
268
+ }
269
+ switch (operation.status) {
270
+ case "failed": return `repair the failed ${operation.title} operation and retry it`;
271
+ case "queued": return `let the queued ${operation.title} operation start, then re-check progress`;
272
+ case "running": return `monitor the ${operation.title} operation and react when it changes`;
273
+ case "succeeded": return `review the completed ${operation.title} operation and continue`;
274
+ }
275
+ }
234
276
  function formatNextAction(frame, obligation) {
235
277
  const obligationHasConcreteArtifact = Boolean(obligation?.currentArtifact?.trim())
236
278
  || obligation?.currentSurface?.kind === "merge";
@@ -258,6 +300,10 @@ function formatNextAction(frame, obligation) {
258
300
  if (obligation) {
259
301
  return formatObligationContentNextAction(obligation) || "continue the active loop and bring the result back here";
260
302
  }
303
+ const backgroundOperation = selectPrimaryBackgroundOperation(frame);
304
+ if (backgroundOperation) {
305
+ return formatBackgroundOperationNextAction(backgroundOperation);
306
+ }
261
307
  if (frame.mustResolveBeforeHandoff) {
262
308
  return "finish what i started here before moving on";
263
309
  }
@@ -128,6 +128,20 @@ function readRecord(filePath) {
128
128
  return null;
129
129
  }
130
130
  }
131
+ function specText(spec, key) {
132
+ const value = spec?.[key];
133
+ return typeof value === "string" ? value.trim() : "";
134
+ }
135
+ function visibleOperationKey(record) {
136
+ if (record.kind !== "mail.import-mbox")
137
+ return null;
138
+ const filePath = specText(record.spec, "filePath");
139
+ if (!filePath)
140
+ return null;
141
+ const ownerEmail = specText(record.spec, "ownerEmail").toLowerCase();
142
+ const source = specText(record.spec, "source").toLowerCase();
143
+ return `${record.agentName.toLowerCase()}|${record.kind}|${filePath}|${ownerEmail}|${source}`;
144
+ }
131
145
  function writeRecord(locator, record) {
132
146
  fs.mkdirSync(operationsDir(locator.agentName, locator.agentRoot), { recursive: true });
133
147
  fs.writeFileSync(operationPath(locator), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
@@ -160,12 +174,22 @@ function listBackgroundOperations(input) {
160
174
  catch {
161
175
  return [];
162
176
  }
163
- return entries
177
+ const records = entries
164
178
  .filter((entry) => entry.endsWith(".json"))
165
179
  .map((entry) => readRecord(path.join(dir, entry)))
166
180
  .filter((entry) => entry !== null)
167
- .sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))
168
- .slice(0, input.limit ?? 10);
181
+ .sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt));
182
+ const visibleKeys = new Set();
183
+ const visible = records.filter((record) => {
184
+ const key = visibleOperationKey(record);
185
+ if (!key)
186
+ return true;
187
+ if (visibleKeys.has(key))
188
+ return false;
189
+ visibleKeys.add(key);
190
+ return true;
191
+ });
192
+ return visible.slice(0, input.limit ?? 10);
169
193
  }
170
194
  function startBackgroundOperation(input) {
171
195
  return writeRecord(input, {
@@ -182,6 +182,13 @@ function getProviderDisplayLabel(facing = "human") {
182
182
  };
183
183
  return providerLabelBuilders[provider]();
184
184
  }
185
+ function hasFreshPendingWork(options) {
186
+ const pendingMessages = options?.pendingMessages;
187
+ if (!Array.isArray(pendingMessages))
188
+ return false;
189
+ return pendingMessages.some((message) => typeof message?.content === "string"
190
+ && message.content.trim().length > 0);
191
+ }
185
192
  // Sole-call tools must be the only tool call in a turn. When they appear
186
193
  // alongside other tools, the sole-call tool is rejected with this message.
187
194
  const SOLE_CALL_REJECTION = {
@@ -891,6 +898,14 @@ async function runAgent(messages, callbacks, channel, signal, options) {
891
898
  providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
892
899
  continue;
893
900
  }
901
+ if (hasFreshPendingWork(options)) {
902
+ callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
903
+ messages.push(msg);
904
+ const gateMessage = "fresh work arrived for me this turn — inspect the pending messages above and take the next concrete action before you rest.";
905
+ messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
906
+ providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
907
+ continue;
908
+ }
894
909
  callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), true);
895
910
  messages.push(msg);
896
911
  const ack = "(resting)";
@@ -44,6 +44,7 @@ exports.mergeStartupStability = mergeStartupStability;
44
44
  exports.ensureDaemonRunning = ensureDaemonRunning;
45
45
  exports.listGithubCopilotModels = listGithubCopilotModels;
46
46
  exports.checkManualCloneBundles = checkManualCloneBundles;
47
+ exports.resolveMailImportFilePath = resolveMailImportFilePath;
47
48
  exports.runOuroCli = runOuroCli;
48
49
  const child_process_1 = require("child_process");
49
50
  const crypto_1 = require("crypto");
@@ -84,6 +85,9 @@ const mbox_import_1 = require("../../mailroom/mbox-import");
84
85
  const reader_1 = require("../../mailroom/reader");
85
86
  const pending_1 = require("../../mind/pending");
86
87
  const background_operations_1 = require("../background-operations");
88
+ const mail_import_discovery_1 = require("../mail-import-discovery");
89
+ const session_activity_1 = require("../session-activity");
90
+ const target_resolution_1 = require("../target-resolution");
87
91
  const cli_parse_1 = require("./cli-parse");
88
92
  const cli_parse_2 = require("./cli-parse");
89
93
  const cli_help_1 = require("./cli-help");
@@ -3409,7 +3413,7 @@ function makeBackgroundOperationId(kind) {
3409
3413
  const prefix = kind === "mail.import-mbox" ? "op_mail_import" : "op_mail_backfill";
3410
3414
  return `${prefix}_${(0, crypto_1.randomUUID)().slice(0, 8)}`;
3411
3415
  }
3412
- function notifyMailOperation(agentName, record, deps) {
3416
+ async function notifyMailOperation(agentName, record, deps) {
3413
3417
  const lines = [
3414
3418
  "[Background Operation]",
3415
3419
  `${record.title} ${record.status === "failed" ? "failed" : "finished"}.`,
@@ -3419,22 +3423,68 @@ function notifyMailOperation(agentName, record, deps) {
3419
3423
  `detail: ${record.detail}`,
3420
3424
  ...(record.error?.message ? [`error: ${record.error.message}`] : []),
3421
3425
  ...(record.remediation && record.remediation.length > 0 ? ["", "next steps:", ...record.remediation.map((step) => `- ${step}`)] : []),
3426
+ ...(record.kind === "mail.import-mbox" && record.status === "succeeded"
3427
+ ? ["", "do not rerun the same archive unless a newer export appears."]
3428
+ : []),
3422
3429
  "",
3423
3430
  "Use query_active_work for the live background-work view before drilling into mail details.",
3424
3431
  ];
3425
- (0, pending_1.queuePendingMessage)((0, pending_1.getInnerDialogPendingDir)(agentName), {
3432
+ const agentRoot = providerCliAgentRoot({ agent: agentName }, deps);
3433
+ const content = lines.join("\n");
3434
+ (0, pending_1.queuePendingMessage)(path.join(agentRoot, "state", "pending", "self", "inner", "dialog"), {
3426
3435
  from: "mailroom",
3427
3436
  friendId: "self",
3428
3437
  channel: "inner",
3429
3438
  key: "dialog",
3430
- content: lines.join("\n"),
3439
+ content,
3431
3440
  timestamp: deps.now?.() ?? Date.now(),
3432
3441
  mode: "reflect",
3433
3442
  });
3434
- return deps.sendCommand(deps.socketPath, { kind: "inner.wake", agent: agentName })
3443
+ await queueMailOperationTargetNotification(agentName, content, deps).catch(() => undefined);
3444
+ await deps.sendCommand(deps.socketPath, { kind: "inner.wake", agent: agentName })
3435
3445
  .then(() => undefined)
3436
3446
  .catch(() => undefined);
3437
3447
  }
3448
+ async function queueMailOperationTargetNotification(agentName, content, deps) {
3449
+ const agentRoot = providerCliAgentRoot({ agent: agentName }, deps);
3450
+ const friendsDir = path.join(agentRoot, "friends");
3451
+ const sessionsDir = path.join(agentRoot, "state", "sessions");
3452
+ const mcpTarget = (0, session_activity_1.listSessionActivity)({
3453
+ sessionsDir,
3454
+ friendsDir,
3455
+ agentName,
3456
+ }).find((candidate) => candidate.channel === "mcp");
3457
+ if (mcpTarget) {
3458
+ (0, pending_1.queuePendingMessage)(path.join(agentRoot, "state", "pending", mcpTarget.friendId, mcpTarget.channel, mcpTarget.key), {
3459
+ from: "mailroom",
3460
+ friendId: mcpTarget.friendId,
3461
+ channel: mcpTarget.channel,
3462
+ key: mcpTarget.key,
3463
+ content,
3464
+ timestamp: deps.now?.() ?? Date.now(),
3465
+ mode: "relay",
3466
+ });
3467
+ return;
3468
+ }
3469
+ const candidates = await (0, target_resolution_1.listTargetSessionCandidates)({
3470
+ sessionsDir,
3471
+ friendsDir,
3472
+ agentName,
3473
+ friendStore: new store_file_1.FileFriendStore(friendsDir),
3474
+ });
3475
+ const target = candidates.find((candidate) => candidate.delivery.mode !== "blocked");
3476
+ if (!target)
3477
+ return;
3478
+ (0, pending_1.queuePendingMessage)(path.join(agentRoot, "state", "pending", target.friendId, target.channel, target.key), {
3479
+ from: "mailroom",
3480
+ friendId: target.friendId,
3481
+ channel: target.channel,
3482
+ key: target.key,
3483
+ content,
3484
+ timestamp: deps.now?.() ?? Date.now(),
3485
+ mode: "relay",
3486
+ });
3487
+ }
3438
3488
  function ensureTrackedMailOperation(input) {
3439
3489
  if (!input.operationId)
3440
3490
  return null;
@@ -3543,17 +3593,108 @@ async function launchBackgroundMailCommand(command, deps, args, title, queuedSum
3543
3593
  `operation: ${operationId}`,
3544
3594
  ...(spec ? Object.entries(spec).map(([key, value]) => `${key}: ${String(value)}`) : []),
3545
3595
  `${command.agent} will be woken on failure or completion.`,
3546
- "Use query_active_work to check live progress.",
3596
+ "Use query_active_work only when a live status check is actually needed.",
3547
3597
  ].join("\n");
3548
3598
  deps.writeStdout(message);
3549
3599
  return message;
3550
3600
  }
3601
+ function buildMailImportOperationSpec(filePath, ownerEmail, source) {
3602
+ const stats = fs.statSync(filePath);
3603
+ return {
3604
+ filePath,
3605
+ ...(ownerEmail ? { ownerEmail } : {}),
3606
+ ...(source ? { source } : {}),
3607
+ fileSizeBytes: stats.size,
3608
+ fileModifiedAt: new Date(stats.mtimeMs).toISOString(),
3609
+ };
3610
+ }
3611
+ function matchingMailImportOperation(record, command, filePath) {
3612
+ if (record.kind !== "mail.import-mbox")
3613
+ return false;
3614
+ if ((record.spec?.filePath ?? null) !== filePath)
3615
+ return false;
3616
+ const expectedOwnerEmail = command.ownerEmail?.trim() ?? "";
3617
+ const expectedSource = command.source?.trim() ?? "";
3618
+ const recordOwnerEmail = typeof record.spec?.ownerEmail === "string" ? record.spec.ownerEmail.trim() : "";
3619
+ const recordSource = typeof record.spec?.source === "string" ? record.spec.source.trim() : "";
3620
+ return recordOwnerEmail === expectedOwnerEmail && recordSource === expectedSource;
3621
+ }
3622
+ function latestComparableOperationTimestamp(record) {
3623
+ const candidates = [record.finishedAt, record.updatedAt];
3624
+ for (const candidate of candidates) {
3625
+ if (typeof candidate !== "string")
3626
+ continue;
3627
+ const parsed = Date.parse(candidate);
3628
+ if (Number.isFinite(parsed))
3629
+ return parsed;
3630
+ }
3631
+ return null;
3632
+ }
3633
+ function duplicateMailImportMessage(command, filePath, record) {
3634
+ const lines = [
3635
+ `Background mail import already ${record.status === "succeeded" ? "completed" : record.status} for ${command.agent}`,
3636
+ `operation: ${record.id}`,
3637
+ `filePath: ${filePath}`,
3638
+ ...(command.ownerEmail ? [`ownerEmail: ${command.ownerEmail}`] : []),
3639
+ ...(command.source ? [`source: ${command.source}`] : []),
3640
+ `summary: ${record.summary}`,
3641
+ ...(record.detail ? [`detail: ${record.detail}`] : []),
3642
+ ];
3643
+ if (record.status === "succeeded") {
3644
+ lines.push("This archive has not changed since that import. Download a newer export before rerunning.");
3645
+ }
3646
+ else {
3647
+ lines.push(`${command.agent} will be woken on failure or completion.`);
3648
+ }
3649
+ return lines.join("\n");
3650
+ }
3651
+ function findExistingMailImportOperation(command, deps, filePath) {
3652
+ const agentRoot = providerCliAgentRoot({ agent: command.agent }, deps);
3653
+ const currentFileMtimeMs = fs.statSync(filePath).mtimeMs;
3654
+ const operations = (0, background_operations_1.listBackgroundOperations)({ agentName: command.agent, agentRoot, limit: 50 });
3655
+ for (const record of operations) {
3656
+ if (!matchingMailImportOperation(record, command, filePath))
3657
+ continue;
3658
+ if (record.status === "queued" || record.status === "running") {
3659
+ return record;
3660
+ }
3661
+ if (record.status === "succeeded") {
3662
+ const recordTimestamp = latestComparableOperationTimestamp(record);
3663
+ if (recordTimestamp !== null && currentFileMtimeMs <= recordTimestamp) {
3664
+ return record;
3665
+ }
3666
+ }
3667
+ }
3668
+ return null;
3669
+ }
3670
+ function resolveMailImportFilePath(command, deps) {
3671
+ if (command.filePath) {
3672
+ return path.resolve(command.filePath);
3673
+ }
3674
+ if (!command.discover) {
3675
+ throw new Error("mail import requires either --file <path> or --discover");
3676
+ }
3677
+ return (0, mail_import_discovery_1.discoverMailImportFilePath)({
3678
+ agentName: command.agent,
3679
+ ownerEmail: command.ownerEmail,
3680
+ source: command.source,
3681
+ repoRoot: path.resolve(deps.getRepoCwd?.() ?? process.cwd()),
3682
+ homeDir: deps.homeDir ?? os.homedir(),
3683
+ });
3684
+ }
3551
3685
  async function executeMailImportMbox(command, deps) {
3552
- const filePath = path.resolve(command.filePath);
3686
+ const filePath = resolveMailImportFilePath(command, deps);
3553
3687
  if (!command.foreground) {
3554
3688
  if (!fs.existsSync(filePath)) {
3555
3689
  throw new Error(`no such file: ${filePath}`);
3556
3690
  }
3691
+ const duplicate = findExistingMailImportOperation(command, deps, filePath);
3692
+ if (duplicate) {
3693
+ const message = duplicateMailImportMessage(command, filePath, duplicate);
3694
+ deps.writeStdout(message);
3695
+ return message;
3696
+ }
3697
+ const spec = buildMailImportOperationSpec(filePath, command.ownerEmail, command.source);
3557
3698
  return launchBackgroundMailCommand(command, deps, [
3558
3699
  "mail",
3559
3700
  "import-mbox",
@@ -3563,13 +3704,10 @@ async function executeMailImportMbox(command, deps) {
3563
3704
  filePath,
3564
3705
  ...(command.ownerEmail ? ["--owner-email", command.ownerEmail] : []),
3565
3706
  ...(command.source ? ["--source", command.source] : []),
3566
- ], "mail import", "queued delegated mail import", {
3567
- filePath,
3568
- ...(command.ownerEmail ? { ownerEmail: command.ownerEmail } : {}),
3569
- ...(command.source ? { source: command.source } : {}),
3570
- });
3707
+ ], "mail import", "queued delegated mail import", spec);
3571
3708
  }
3572
3709
  const progress = createHumanCommandProgress(deps, "mail import");
3710
+ const spec = buildMailImportOperationSpec(filePath, command.ownerEmail, command.source);
3573
3711
  const trackedOperation = ensureTrackedMailOperation({
3574
3712
  agentName: command.agent,
3575
3713
  deps,
@@ -3577,11 +3715,7 @@ async function executeMailImportMbox(command, deps) {
3577
3715
  kind: "mail.import-mbox",
3578
3716
  title: "mail import",
3579
3717
  queuedSummary: "queued delegated mail import",
3580
- spec: {
3581
- filePath,
3582
- ...(command.ownerEmail ? { ownerEmail: command.ownerEmail } : {}),
3583
- ...(command.source ? { source: command.source } : {}),
3584
- },
3718
+ spec,
3585
3719
  });
3586
3720
  try {
3587
3721
  trackedOperation?.running("reading Mailroom config", `file: ${filePath}`, {
@@ -3643,6 +3777,7 @@ async function executeMailImportMbox(command, deps) {
3643
3777
  progress.completePhase("reading Mailroom config", "imported");
3644
3778
  progress.end();
3645
3779
  const message = [
3780
+ ...(command.discover ? [`Discovered MBOX: ${filePath}`] : []),
3646
3781
  `Imported MBOX for ${command.agent}`,
3647
3782
  `file: ${filePath}`,
3648
3783
  `grant: ${result.sourceGrant.grantId} (${result.sourceGrant.source}; ${result.sourceGrant.ownerEmail})`,
@@ -6538,14 +6673,13 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6538
6673
  /* v8 ignore start -- inner status handler: requires real agent state on disk @preserve */
6539
6674
  if (command.kind === "inner.status") {
6540
6675
  try {
6541
- const agentRoot = (0, identity_1.getAgentRoot)(command.agent);
6676
+ const agentRoot = deps.agentBundleRoot ?? (0, identity_1.getAgentRoot)(command.agent);
6542
6677
  const { buildInnerStatusOutput } = await Promise.resolve().then(() => __importStar(require("./inner-status")));
6543
- const { sessionPath: getSessionPath } = await Promise.resolve().then(() => __importStar(require("../config")));
6544
6678
  const { parseCadenceToMs: parseCadenceMs, DEFAULT_CADENCE_MS } = await Promise.resolve().then(() => __importStar(require("./cadence")));
6545
6679
  const { parseFrontmatter } = await Promise.resolve().then(() => __importStar(require("../../repertoire/tasks/parser")));
6546
6680
  const { listActiveReturnObligations } = await Promise.resolve().then(() => __importStar(require("../../arc/obligations")));
6547
6681
  // Read runtime state
6548
- const innerSessionPath = getSessionPath("inner-dialog", "inner", "session");
6682
+ const innerSessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
6549
6683
  const runtimeJsonPath = path.join(path.dirname(innerSessionPath), "runtime.json");
6550
6684
  let runtimeState = null;
6551
6685
  try {
@@ -79,9 +79,15 @@ exports.COMMAND_REGISTRY = {
79
79
  usage: "ouro doctor",
80
80
  example: "ouro doctor",
81
81
  },
82
+ mailbox: {
83
+ category: "Agents",
84
+ description: "Show the agent's current mailbox overview",
85
+ usage: "ouro mailbox [--json]",
86
+ example: "ouro mailbox --json",
87
+ },
82
88
  outlook: {
83
89
  category: "Agents",
84
- description: "Show the agent's current outlook",
90
+ description: "Deprecated alias for `ouro mailbox`",
85
91
  usage: "ouro outlook [--json]",
86
92
  example: "ouro outlook --json",
87
93
  },
@@ -181,7 +187,7 @@ exports.COMMAND_REGISTRY = {
181
187
  category: "Auth",
182
188
  description: "Import delegated mail and repair hosted Mailroom mailbox indexes",
183
189
  usage: "ouro mail <import-mbox|backfill-indexes> [--agent <name>]",
184
- example: "ouro mail import-mbox --file ~/Downloads/hey.mbox --owner-email ari@mendelow.me --source hey --agent slugger",
190
+ example: "ouro mail import-mbox --discover --owner-email ari@mendelow.me --source hey --agent slugger",
185
191
  subcommands: ["import-mbox", "backfill-indexes"],
186
192
  },
187
193
  use: {
@@ -323,8 +329,8 @@ const SUBCOMMAND_HELP = {
323
329
  },
324
330
  "mail import-mbox": {
325
331
  description: "Import a HEY or other MBOX export into an existing delegated Mailroom source grant",
326
- usage: "ouro mail import-mbox --file <path> [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]",
327
- example: "ouro mail import-mbox --file ~/Downloads/hey.mbox --owner-email ari@mendelow.me --source hey --agent slugger",
332
+ usage: "ouro mail import-mbox (--file <path>|--discover) [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]",
333
+ example: "ouro mail import-mbox --discover --owner-email ari@mendelow.me --source hey --agent slugger",
328
334
  },
329
335
  "mail backfill-indexes": {
330
336
  description: "Rebuild hosted blob mailbox indexes for faster recent-mail reads after large legacy imports or drift repair.",
@@ -79,7 +79,7 @@ function usage() {
79
79
  " ouro check [--agent <name>] --lane outward|inner",
80
80
  " ouro repair [--agent <name>]",
81
81
  " ouro provider refresh [--agent <name>]",
82
- " ouro outlook [--json]",
82
+ " ouro mailbox [--json]",
83
83
  " ouro -v|--version",
84
84
  " ouro config model [--agent <name>] <model-name>",
85
85
  " ouro config models [--agent <name>]",
@@ -885,7 +885,7 @@ function parseConnectCommand(args) {
885
885
  }
886
886
  function parseMailCommand(args) {
887
887
  const [sub, ...subArgs] = args;
888
- const usageText = "Usage: ouro mail import-mbox --file <path> [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]\n ouro mail backfill-indexes [--agent <name>] [--foreground]";
888
+ const usageText = "Usage: ouro mail import-mbox (--file <path>|--discover) [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]\n ouro mail backfill-indexes [--agent <name>] [--foreground]";
889
889
  if (sub === "backfill-indexes") {
890
890
  const { agent, rest } = extractAgentFlag(subArgs);
891
891
  let foreground = false;
@@ -914,6 +914,7 @@ function parseMailCommand(args) {
914
914
  }
915
915
  const { agent, rest } = extractAgentFlag(subArgs);
916
916
  let filePath;
917
+ let discover = false;
917
918
  let ownerEmail;
918
919
  let source;
919
920
  let foreground = false;
@@ -924,6 +925,10 @@ function parseMailCommand(args) {
924
925
  filePath = rest[++i];
925
926
  continue;
926
927
  }
928
+ if (token === "--discover") {
929
+ discover = true;
930
+ continue;
931
+ }
927
932
  if (token === "--owner-email" && rest[i + 1]) {
928
933
  ownerEmail = rest[++i];
929
934
  continue;
@@ -942,13 +947,14 @@ function parseMailCommand(args) {
942
947
  }
943
948
  throw new Error(usageText);
944
949
  }
945
- if (!filePath) {
950
+ if ((filePath ? 1 : 0) + (discover ? 1 : 0) !== 1) {
946
951
  throw new Error(usageText);
947
952
  }
948
953
  return {
949
954
  kind: "mail.import-mbox",
950
955
  ...(agent ? { agent } : {}),
951
- filePath,
956
+ ...(filePath ? { filePath } : {}),
957
+ ...(discover ? { discover: true } : {}),
952
958
  ...(ownerEmail ? { ownerEmail } : {}),
953
959
  ...(source ? { source } : {}),
954
960
  ...(foreground ? { foreground: true } : {}),
@@ -1452,7 +1458,7 @@ function parseOuroCommand(args) {
1452
1458
  return { kind: "daemon.logs.prune" };
1453
1459
  return { kind: "daemon.logs" };
1454
1460
  }
1455
- if (head === "outlook")
1461
+ if (head === "mailbox" || head === "outlook")
1456
1462
  return { kind: "outlook", ...(args.includes("--json") ? { json: true } : {}) };
1457
1463
  if (head === "hatch")
1458
1464
  return parseHatchCommand(args.slice(1));
@@ -308,7 +308,7 @@ function formatDaemonStatusOutput(response, fallback) {
308
308
  // ── Key-value overview ──
309
309
  const kvLine = (label, value) => ` ${teal(label.padEnd(11))} ${value}`;
310
310
  lines.push(kvLine("Socket", ov.socketPath));
311
- lines.push(kvLine("Outlook", ov.outlookUrl));
311
+ lines.push(kvLine("Mailbox", ov.outlookUrl));
312
312
  lines.push(kvLine("Health", `${statusDot(ov.health)} ${ov.health}`));
313
313
  lines.push(kvLine("Updated", ov.lastUpdated));
314
314
  lines.push("");
@@ -48,6 +48,7 @@ exports.readInnerDialogRawData = readInnerDialogRawData;
48
48
  exports.followThoughts = followThoughts;
49
49
  const fs = __importStar(require("fs"));
50
50
  const path = __importStar(require("path"));
51
+ const session_events_1 = require("../session-events");
51
52
  const runtime_1 = require("../../nerves/runtime");
52
53
  function contentToText(content) {
53
54
  if (typeof content === "string")
@@ -364,21 +365,34 @@ function parseInnerDialogSession(sessionPath) {
364
365
  catch {
365
366
  return [];
366
367
  }
367
- let data;
368
+ let parsedRaw;
368
369
  try {
369
- data = JSON.parse(raw);
370
+ parsedRaw = JSON.parse(raw);
370
371
  }
371
372
  catch {
372
373
  return [];
373
374
  }
374
- if (data.version !== 1 || !Array.isArray(data.messages))
375
+ const legacyRecord = parsedRaw && typeof parsedRaw === "object"
376
+ ? parsedRaw
377
+ : null;
378
+ const messages = legacyRecord?.version === 1 && Array.isArray(legacyRecord.messages)
379
+ ? legacyRecord.messages
380
+ : (() => {
381
+ const envelope = (0, session_events_1.parseSessionEnvelope)(parsedRaw, {
382
+ recordedAt: new Date().toISOString(),
383
+ });
384
+ if (!envelope)
385
+ return null;
386
+ return (0, session_events_1.projectProviderMessages)(envelope);
387
+ })();
388
+ if (!messages)
375
389
  return [];
390
+ const normalizedMessages = messages;
376
391
  const turns = [];
377
- const messages = data.messages;
378
392
  // Walk messages, pairing user → (tool calls) → assistant sequences
379
393
  let i = 0;
380
- while (i < messages.length) {
381
- const msg = messages[i];
394
+ while (i < normalizedMessages.length) {
395
+ const msg = normalizedMessages[i];
382
396
  if (msg.role === "system") {
383
397
  i++;
384
398
  continue;
@@ -392,8 +406,8 @@ function parseInnerDialogSession(sessionPath) {
392
406
  // Collect all messages until the next user message (or end)
393
407
  const turnMessages = [];
394
408
  let j = i + 1;
395
- while (j < messages.length && messages[j].role !== "user") {
396
- turnMessages.push(messages[j]);
409
+ while (j < normalizedMessages.length && normalizedMessages[j].role !== "user") {
410
+ turnMessages.push(normalizedMessages[j]);
397
411
  j++;
398
412
  }
399
413
  // Find the last assistant text response in this turn.