@ouro.bot/cli 0.1.0-alpha.485 → 0.1.0-alpha.488

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 CHANGED
@@ -1,6 +1,21 @@
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.488",
6
+ "changes": [
7
+ "BlueBubbles group chats stay fully silent on `observe` turns. The engine emits `onToolStart(\"observe\")` / `onToolEnd(\"observe\")` even when the resulting outcome is `observed` (no reply), and the BlueBubbles adapter previously treated every tool start as reply commitment — so groups would briefly show typing or mark-read before the silent path completed. Both callbacks now short-circuit for `observe`: no startTypingNow, no toolCallbacks dispatch, just an observability event. Real reply-commit semantics (typing, mark-read, status messages on real tools) are preserved. Regression test reproduces the real callback sequence from the engine and asserts the lane stays quiet."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.486",
12
+ "changes": [
13
+ "Mail convergence pass 1-5 hardens hosted-mail truth surfaces under live HEY ingest: import truth + audit resilience, accurate archive freshness and identity surfaces, sharper recovery and archive truth, delegated search resilience, and natural anchor-list retrieval; imported archive content is now searched on parsed message text rather than raw archive bytes so quoted-printable / HTML-heavy booking mail is reachable.",
14
+ "`mail_search` now ranks by booking-aware relevance instead of pure recency. Score signals weight query-term hits by field (subject +6 / from +4 / body +2), booking-intent tokens (`booking confirmation`, `your stay`, e-ticket, etc.), confirmation-number-shaped tokens, currency amounts, and known travel-sender domains; recency stays as a tiebreaker. Recall is unchanged — noise still appears in results, just below the decisive message. Each rendered result also surfaces a `matched on:` line listing fields, booking signals, status (confirmed / cancelled / changed / refunded / etc.), confirmation token, amount, dates, attachment count, and sender hint, so the agent can triage without paying for a body open.",
15
+ "BlueBubbles sense no longer sticks in `error` status when a single message is permanently unrecoverable. `upstreamStatus` now tracks upstream health and pending work only — per-cycle recovery failures stay informational in `detail` for transparency without contradicting `ouro doctor`'s healthy verdict, so a malformed payload that fails repairEvent on every retry can no longer brick the visible sense state until operator intervention.",
16
+ "Heart streaming caps oversized Responses-API `function_call_output` history items both when rebuilding provider input from session history and when appending fresh tool output mid-turn, preventing a giant tool result on resume from blowing the model context."
17
+ ]
18
+ },
4
19
  {
5
20
  "version": "0.1.0-alpha.485",
6
21
  "changes": [
@@ -239,6 +239,10 @@ function backgroundOperationPriority(operation) {
239
239
  case "succeeded": return 3;
240
240
  }
241
241
  }
242
+ function backgroundOperationSpecText(spec, key) {
243
+ const value = spec?.[key];
244
+ return typeof value === "string" ? value.trim() : "";
245
+ }
242
246
  function selectPrimaryBackgroundOperation(frame) {
243
247
  const operations = frame.backgroundOperations ?? [];
244
248
  if (operations.length === 0)
@@ -257,13 +261,34 @@ function formatBackgroundOperationNextAction(operation) {
257
261
  if (operation.kind === "mail.import-mbox") {
258
262
  switch (operation.status) {
259
263
  case "failed":
264
+ switch (operation.failure?.class?.trim()) {
265
+ case "transient-storage-read":
266
+ return "retry-safe once the transient storage/network issue clears";
267
+ case "mailroom-auth":
268
+ case "mailroom-config":
269
+ return "repair mail auth/config access, then retry";
270
+ case "source-grant-missing":
271
+ case "source-grant-ambiguous":
272
+ return "repair the delegated owner/source lane, then retry";
273
+ case "archive-discovery":
274
+ case "archive-ambiguity":
275
+ case "archive-missing":
276
+ case "archive-access":
277
+ return "materialize or point at the correct local archive, then retry";
278
+ }
279
+ if (operation.failure?.retryDisposition === "retry-safe") {
280
+ return "retry the failed mail import once the transient issue clears or the dependency answers again";
281
+ }
282
+ if (operation.failure?.retryDisposition === "investigate-first") {
283
+ return "inspect the failed mail import carefully, confirm the root cause, and then decide whether to retry";
284
+ }
260
285
  return "inspect the failed mail import, fix the issue, and retry it";
261
286
  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";
287
+ return "queued no action unless it stalls or i need live status";
263
288
  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";
289
+ return "in flight no action unless it stalls or i need live status";
265
290
  case "succeeded":
266
- return "review the completed mail import and continue from the updated mailbox state; i only rerun if a newer archive appears";
291
+ return "caught up no rerun needed unless a newer archive appears";
267
292
  }
268
293
  }
269
294
  switch (operation.status) {
@@ -273,6 +298,66 @@ function formatBackgroundOperationNextAction(operation) {
273
298
  case "succeeded": return `review the completed ${operation.title} operation and continue`;
274
299
  }
275
300
  }
301
+ function formatMailImportRecoveryUniverse(operation) {
302
+ const failureClass = operation.failure?.class?.trim();
303
+ if (!failureClass)
304
+ return null;
305
+ switch (failureClass) {
306
+ case "transient-storage-read":
307
+ return "transient dependency/read issue — safe to retry once storage/network answers again";
308
+ case "mailroom-auth":
309
+ case "mailroom-config":
310
+ return "mail auth/config issue — repair mail access or runtime config before retrying";
311
+ case "source-grant-missing":
312
+ case "source-grant-ambiguous":
313
+ return "delegated lane/registry issue — inspect owner/source linking before retrying";
314
+ case "archive-discovery":
315
+ case "archive-ambiguity":
316
+ case "archive-missing":
317
+ case "archive-access":
318
+ return "local archive/file issue — materialize or point at the right archive before retrying";
319
+ default:
320
+ return "unclassified import issue — inspect the recorded error before retrying";
321
+ }
322
+ }
323
+ function formatBackgroundOperationMeta(operation) {
324
+ const lines = [`operation: ${operation.id}`];
325
+ const filePath = backgroundOperationSpecText(operation.spec, "filePath")
326
+ || backgroundOperationSpecText(operation.spec, "newestCandidatePath");
327
+ if (filePath)
328
+ lines.push(`file: ${filePath}`);
329
+ const originLabel = backgroundOperationSpecText(operation.spec, "fileOriginLabel")
330
+ || backgroundOperationSpecText(operation.spec, "newestCandidateOriginLabel");
331
+ if (originLabel)
332
+ lines.push(`origin: ${originLabel}`);
333
+ const ownerEmail = backgroundOperationSpecText(operation.spec, "ownerEmail");
334
+ const source = backgroundOperationSpecText(operation.spec, "source");
335
+ if (ownerEmail || source) {
336
+ lines.push(`owner/source: ${ownerEmail || "unknown"} / ${source || "unknown"}`);
337
+ }
338
+ if (operation.failure?.class?.trim())
339
+ lines.push(`failure class: ${operation.failure.class}`);
340
+ if (operation.failure?.retryDisposition?.trim())
341
+ lines.push(`retry: ${operation.failure.retryDisposition}`);
342
+ if (operation.failure?.hint?.trim())
343
+ lines.push(`recovery: ${operation.failure.hint}`);
344
+ const recoveryUniverse = operation.kind === "mail.import-mbox" ? formatMailImportRecoveryUniverse(operation) : null;
345
+ if (recoveryUniverse)
346
+ lines.push(`recovery universe: ${recoveryUniverse}`);
347
+ if (operation.startedAt?.trim())
348
+ lines.push(`started: ${operation.startedAt}`);
349
+ if (operation.finishedAt?.trim())
350
+ lines.push(`finished: ${operation.finishedAt}`);
351
+ else if (operation.updatedAt?.trim())
352
+ lines.push(`updated: ${operation.updatedAt}`);
353
+ const nextAction = formatBackgroundOperationNextAction(operation);
354
+ if (nextAction.trim().length > 0)
355
+ lines.push(`next: ${nextAction}`);
356
+ if (operation.remediation?.length) {
357
+ lines.push(...operation.remediation.map((step) => `remediation: ${step}`));
358
+ }
359
+ return lines;
360
+ }
276
361
  function formatNextAction(frame, obligation) {
277
362
  const obligationHasConcreteArtifact = Boolean(obligation?.currentArtifact?.trim())
278
363
  || obligation?.currentSurface?.kind === "merge";
@@ -696,6 +781,7 @@ function formatActiveWorkFrame(frame, options) {
696
781
  if (operation.summary.trim().length > 0) {
697
782
  line += `: ${operation.summary}`;
698
783
  }
784
+ line += `\n ${formatBackgroundOperationMeta(operation).join("\n ")}`;
699
785
  if (operation.detail?.trim()) {
700
786
  line += `\n ${operation.detail.trim()}`;
701
787
  }
@@ -112,6 +112,20 @@ function normalizeRecord(record) {
112
112
  if (record.error && typeof record.error.message === "string" && record.error.message.trim().length > 0) {
113
113
  normalized.error = { message: record.error.message.trim() };
114
114
  }
115
+ if (record.failure && typeof record.failure === "object" && !Array.isArray(record.failure)) {
116
+ const failureClass = typeof record.failure.class === "string" ? record.failure.class.trim() : "";
117
+ const retryDisposition = record.failure.retryDisposition;
118
+ const hint = typeof record.failure.hint === "string" ? record.failure.hint.trim() : "";
119
+ if (failureClass) {
120
+ normalized.failure = {
121
+ class: failureClass,
122
+ ...(retryDisposition === "retry-safe" || retryDisposition === "fix-before-retry" || retryDisposition === "investigate-first"
123
+ ? { retryDisposition }
124
+ : {}),
125
+ ...(hint ? { hint } : {}),
126
+ };
127
+ }
128
+ }
115
129
  if (Array.isArray(record.remediation)) {
116
130
  const remediation = record.remediation.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
117
131
  if (remediation.length > 0)
@@ -143,15 +157,19 @@ function visibleOperationKey(record) {
143
157
  return `${record.agentName.toLowerCase()}|${record.kind}|${filePath}|${ownerEmail}|${source}`;
144
158
  }
145
159
  function writeRecord(locator, record) {
160
+ const normalized = normalizeRecord(record);
161
+ if (!normalized) {
162
+ throw new Error(`invalid background operation record: ${locator.id}`);
163
+ }
146
164
  fs.mkdirSync(operationsDir(locator.agentName, locator.agentRoot), { recursive: true });
147
- fs.writeFileSync(operationPath(locator), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
165
+ fs.writeFileSync(operationPath(locator), `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
148
166
  (0, runtime_1.emitNervesEvent)({
149
167
  component: "engine",
150
168
  event: "engine.background_operation_written",
151
169
  message: "background operation state written",
152
- meta: { agentName: locator.agentName, id: locator.id, kind: record.kind, status: record.status },
170
+ meta: { agentName: locator.agentName, id: locator.id, kind: normalized.kind, status: normalized.status },
153
171
  });
154
- return record;
172
+ return normalized;
155
173
  }
156
174
  function requireRecord(locator) {
157
175
  const record = readBackgroundOperation(locator);
@@ -226,6 +244,7 @@ function updateBackgroundOperation(input) {
226
244
  summary: input.summary ?? current.summary,
227
245
  ...(input.detail ? { detail: input.detail } : {}),
228
246
  ...(normalizeProgress(input.progress) ? { progress: normalizeProgress(input.progress) } : {}),
247
+ ...(input.spec ? { spec: input.spec } : {}),
229
248
  updatedAt: input.updatedAt ?? current.updatedAt,
230
249
  });
231
250
  }
@@ -238,6 +257,9 @@ function completeBackgroundOperation(input) {
238
257
  ...(input.detail ? { detail: input.detail } : {}),
239
258
  ...(normalizeProgress(input.progress) ? { progress: normalizeProgress(input.progress) } : {}),
240
259
  ...(input.result ? { result: input.result } : {}),
260
+ error: undefined,
261
+ remediation: undefined,
262
+ failure: undefined,
241
263
  finishedAt: input.finishedAt,
242
264
  updatedAt: input.updatedAt ?? input.finishedAt,
243
265
  });
@@ -251,6 +273,7 @@ function failBackgroundOperation(input) {
251
273
  ...(input.detail ? { detail: input.detail } : {}),
252
274
  ...(normalizeProgress(input.progress) ? { progress: normalizeProgress(input.progress) } : {}),
253
275
  error: { message: input.error },
276
+ ...(input.failure ? { failure: input.failure } : {}),
254
277
  ...(input.remediation && input.remediation.length > 0 ? { remediation: input.remediation } : {}),
255
278
  finishedAt: input.finishedAt,
256
279
  updatedAt: input.updatedAt ?? input.finishedAt,
@@ -3421,6 +3421,9 @@ async function notifyMailOperation(agentName, record, deps) {
3421
3421
  `status: ${record.status}`,
3422
3422
  `summary: ${record.summary}`,
3423
3423
  `detail: ${record.detail}`,
3424
+ ...(record.failure?.class ? [`failure class: ${record.failure.class}`] : []),
3425
+ ...(record.failure?.retryDisposition ? [`retry: ${record.failure.retryDisposition}`] : []),
3426
+ ...(record.failure?.hint ? [`recovery: ${record.failure.hint}`] : []),
3424
3427
  ...(record.error?.message ? [`error: ${record.error.message}`] : []),
3425
3428
  ...(record.remediation && record.remediation.length > 0 ? ["", "next steps:", ...record.remediation.map((step) => `- ${step}`)] : []),
3426
3429
  ...(record.kind === "mail.import-mbox" && record.status === "succeeded"
@@ -3518,7 +3521,7 @@ function ensureTrackedMailOperation(input) {
3518
3521
  ...(progress ? { progress } : {}),
3519
3522
  });
3520
3523
  },
3521
- update: (summary, detail, progress) => {
3524
+ update: (summary, detail, progress, spec) => {
3522
3525
  (0, background_operations_1.updateBackgroundOperation)({
3523
3526
  agentName: input.agentName,
3524
3527
  agentRoot,
@@ -3527,6 +3530,7 @@ function ensureTrackedMailOperation(input) {
3527
3530
  updatedAt: cliNowIso(input.deps),
3528
3531
  ...(detail ? { detail } : {}),
3529
3532
  ...(progress ? { progress } : {}),
3533
+ ...(spec ? { spec } : {}),
3530
3534
  });
3531
3535
  },
3532
3536
  succeed: async (summary, detail, result) => {
@@ -3541,7 +3545,7 @@ function ensureTrackedMailOperation(input) {
3541
3545
  });
3542
3546
  await notifyMailOperation(input.agentName, { ...completed, detail }, input.deps);
3543
3547
  },
3544
- fail: async (error, summary, detail, remediation) => {
3548
+ fail: async (error, summary, detail, remediation, failure) => {
3545
3549
  const failed = (0, background_operations_1.failBackgroundOperation)({
3546
3550
  agentName: input.agentName,
3547
3551
  agentRoot,
@@ -3550,6 +3554,7 @@ function ensureTrackedMailOperation(input) {
3550
3554
  summary,
3551
3555
  detail,
3552
3556
  error: error instanceof Error ? error.message : String(error),
3557
+ ...(failure ? { failure } : {}),
3553
3558
  remediation,
3554
3559
  });
3555
3560
  await notifyMailOperation(input.agentName, { ...failed, detail }, input.deps);
@@ -3608,6 +3613,143 @@ function buildMailImportOperationSpec(filePath, ownerEmail, source) {
3608
3613
  fileModifiedAt: new Date(stats.mtimeMs).toISOString(),
3609
3614
  };
3610
3615
  }
3616
+ function classifyMailImportFailure(error) {
3617
+ const message = error instanceof Error ? error.message : String(error);
3618
+ const normalized = message.toLowerCase();
3619
+ if (normalized.includes("auth_required:mailroom") || normalized.includes("cannot read mailroom config")) {
3620
+ return {
3621
+ failure: {
3622
+ class: "mailroom-auth",
3623
+ retryDisposition: "fix-before-retry",
3624
+ hint: "mailroom credentials or vault access are unavailable; unlock or repair mail auth first",
3625
+ },
3626
+ remediation: [
3627
+ "unlock or repair the owning agent vault/runtime config, then rerun the import",
3628
+ "use query_active_work to confirm the failed operation has settled before retrying",
3629
+ ],
3630
+ };
3631
+ }
3632
+ if (normalized.includes("missing mailroom config")
3633
+ || normalized.includes("missing registrypath/storepath")
3634
+ || normalized.includes("missing hosted registry coordinates")) {
3635
+ return {
3636
+ failure: {
3637
+ class: "mailroom-config",
3638
+ retryDisposition: "fix-before-retry",
3639
+ hint: "mailroom runtime config is incomplete for this agent",
3640
+ },
3641
+ remediation: [
3642
+ `rerun 'ouro connect mail --agent <agent>' or repair the stored mailroom runtime config`,
3643
+ "retry the import only after the config repair is complete",
3644
+ ],
3645
+ };
3646
+ }
3647
+ if (normalized.includes("no enabled mailroom source grant found")) {
3648
+ return {
3649
+ failure: {
3650
+ class: "source-grant-missing",
3651
+ retryDisposition: "fix-before-retry",
3652
+ hint: "the requested owner/source lane is not provisioned yet",
3653
+ },
3654
+ remediation: [
3655
+ "create or repair the delegated source grant for the intended owner/source, then rerun the import",
3656
+ "confirm the import is pointed at the intended owner/source before retrying",
3657
+ ],
3658
+ };
3659
+ }
3660
+ if (normalized.includes("multiple source grants found")) {
3661
+ return {
3662
+ failure: {
3663
+ class: "source-grant-ambiguous",
3664
+ retryDisposition: "fix-before-retry",
3665
+ hint: "more than one source grant matches; the import needs a narrower owner/source target",
3666
+ },
3667
+ remediation: [
3668
+ "rerun the import with explicit --owner-email and/or --source hints",
3669
+ "confirm which delegated lane should receive this archive before retrying",
3670
+ ],
3671
+ };
3672
+ }
3673
+ if (normalized.includes("could not discover an mbox file")) {
3674
+ return {
3675
+ failure: {
3676
+ class: "archive-discovery",
3677
+ retryDisposition: "fix-before-retry",
3678
+ hint: "no matching local archive was discovered in the known browser/download locations",
3679
+ },
3680
+ remediation: [
3681
+ "check the recent browser/download artifact locations or pass --file with the exact archive path",
3682
+ "retry only after the correct archive is visible locally",
3683
+ ],
3684
+ };
3685
+ }
3686
+ if (normalized.includes("multiple candidate mbox files found")) {
3687
+ return {
3688
+ failure: {
3689
+ class: "archive-ambiguity",
3690
+ retryDisposition: "fix-before-retry",
3691
+ hint: "more than one plausible archive matches this import request",
3692
+ },
3693
+ remediation: [
3694
+ "rerun with --file <path> or narrower owner/source hints so the archive choice is unambiguous",
3695
+ "retry after confirming which archive is the intended one",
3696
+ ],
3697
+ };
3698
+ }
3699
+ if (normalized.includes("no such file:") || normalized.includes("enoent")) {
3700
+ return {
3701
+ failure: {
3702
+ class: "archive-missing",
3703
+ retryDisposition: "fix-before-retry",
3704
+ hint: "the chosen archive path is not readable on disk anymore",
3705
+ },
3706
+ remediation: [
3707
+ "re-check the local archive path or re-download the export before retrying",
3708
+ "retry after the archive exists locally again",
3709
+ ],
3710
+ };
3711
+ }
3712
+ if (normalized.includes("permission denied") || normalized.includes("eacces") || normalized.includes("eperm")) {
3713
+ return {
3714
+ failure: {
3715
+ class: "archive-access",
3716
+ retryDisposition: "fix-before-retry",
3717
+ hint: "the archive or backing store could not be read with current filesystem permissions",
3718
+ },
3719
+ remediation: [
3720
+ "repair filesystem access to the archive or backing store, then rerun the import",
3721
+ "retry after confirming the path is readable by Ouro",
3722
+ ],
3723
+ };
3724
+ }
3725
+ if (normalized.includes("timed out")
3726
+ || normalized.includes("socket closed early")
3727
+ || normalized.includes("econnreset")
3728
+ || normalized.includes("eai_again")) {
3729
+ return {
3730
+ failure: {
3731
+ class: "transient-storage-read",
3732
+ retryDisposition: "retry-safe",
3733
+ hint: "likely transient hosted read failure",
3734
+ },
3735
+ remediation: [
3736
+ "retry the import from the same archive after the transient storage/network issue clears",
3737
+ "use query_active_work to confirm the failed operation has settled before retrying",
3738
+ ],
3739
+ };
3740
+ }
3741
+ return {
3742
+ failure: {
3743
+ class: "unknown-mail-import-failure",
3744
+ retryDisposition: "investigate-first",
3745
+ hint: "the import failed for a reason that does not match a known recovery class yet",
3746
+ },
3747
+ remediation: [
3748
+ "inspect the recorded error and surrounding Mailroom/runtime state before retrying",
3749
+ "retry only after the likely cause is understood or ruled out",
3750
+ ],
3751
+ };
3752
+ }
3611
3753
  function matchingMailImportOperation(record, command, filePath) {
3612
3754
  if (record.kind !== "mail.import-mbox")
3613
3755
  return false;
@@ -3683,8 +3825,8 @@ function resolveMailImportFilePath(command, deps) {
3683
3825
  });
3684
3826
  }
3685
3827
  async function executeMailImportMbox(command, deps) {
3686
- const filePath = resolveMailImportFilePath(command, deps);
3687
3828
  if (!command.foreground) {
3829
+ const filePath = resolveMailImportFilePath(command, deps);
3688
3830
  if (!fs.existsSync(filePath)) {
3689
3831
  throw new Error(`no such file: ${filePath}`);
3690
3832
  }
@@ -3707,7 +3849,7 @@ async function executeMailImportMbox(command, deps) {
3707
3849
  ], "mail import", "queued delegated mail import", spec);
3708
3850
  }
3709
3851
  const progress = createHumanCommandProgress(deps, "mail import");
3710
- const spec = buildMailImportOperationSpec(filePath, command.ownerEmail, command.source);
3852
+ let filePath = command.filePath ? path.resolve(command.filePath) : null;
3711
3853
  const trackedOperation = ensureTrackedMailOperation({
3712
3854
  agentName: command.agent,
3713
3855
  deps,
@@ -3715,13 +3857,28 @@ async function executeMailImportMbox(command, deps) {
3715
3857
  kind: "mail.import-mbox",
3716
3858
  title: "mail import",
3717
3859
  queuedSummary: "queued delegated mail import",
3718
- spec,
3860
+ spec: {
3861
+ ...(filePath ? { filePath } : {}),
3862
+ ...(command.ownerEmail ? { ownerEmail: command.ownerEmail } : {}),
3863
+ ...(command.source ? { source: command.source } : {}),
3864
+ ...(command.discover ? { discovery: true } : {}),
3865
+ },
3719
3866
  });
3720
3867
  try {
3868
+ trackedOperation?.running("resolving mail archive", filePath ? `file: ${filePath}` : "mode: discover", {
3869
+ current: 0,
3870
+ unit: "messages",
3871
+ });
3872
+ filePath = resolveMailImportFilePath(command, deps);
3873
+ const spec = buildMailImportOperationSpec(filePath, command.ownerEmail, command.source);
3721
3874
  trackedOperation?.running("reading Mailroom config", `file: ${filePath}`, {
3722
3875
  current: 0,
3723
3876
  unit: "messages",
3724
3877
  });
3878
+ trackedOperation?.update("reading Mailroom config", `file: ${filePath}`, {
3879
+ current: 0,
3880
+ unit: "messages",
3881
+ }, spec);
3725
3882
  progress.startPhase("reading Mailroom config");
3726
3883
  const runtime = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(command.agent, { preserveCachedOnFailure: true });
3727
3884
  if (!runtime.ok) {
@@ -3799,10 +3956,15 @@ async function executeMailImportMbox(command, deps) {
3799
3956
  }
3800
3957
  catch (error) {
3801
3958
  progress.end();
3802
- await trackedOperation?.fail(error, "delegated mail import failed", `file: ${filePath}`, [
3803
- "fix the reported Mailroom or MBOX problem, then rerun the import",
3804
- "use query_active_work to confirm whether the operation has cleared",
3805
- ]);
3959
+ const classifiedFailure = classifyMailImportFailure(error);
3960
+ const failureDetail = filePath
3961
+ ? `file: ${filePath}`
3962
+ : [
3963
+ "mode: discover",
3964
+ ...(command.ownerEmail ? [`owner: ${command.ownerEmail}`] : []),
3965
+ ...(command.source ? [`source: ${command.source}`] : []),
3966
+ ].join("; ");
3967
+ await trackedOperation?.fail(error, "delegated mail import failed", failureDetail, classifiedFailure.remediation, classifiedFailure.failure);
3806
3968
  throw error;
3807
3969
  }
3808
3970
  }
@@ -111,6 +111,26 @@ function listChildDirs(dir) {
111
111
  return [];
112
112
  }
113
113
  }
114
+ function classifyDiscoveredMboxOrigin(dir) {
115
+ const normalizedDir = path.resolve(dir);
116
+ const parts = normalizedDir.split(path.sep).filter(Boolean);
117
+ if (parts.includes(".playwright-mcp")) {
118
+ return {
119
+ originKind: "playwright-sandbox",
120
+ originLabel: "browser sandbox (.playwright-mcp)",
121
+ };
122
+ }
123
+ if (path.basename(normalizedDir) === "Downloads") {
124
+ return {
125
+ originKind: "downloads",
126
+ originLabel: "Downloads",
127
+ };
128
+ }
129
+ return {
130
+ originKind: "filesystem",
131
+ originLabel: "filesystem",
132
+ };
133
+ }
114
134
  function findWorktreePools(rootDir, maxDepth) {
115
135
  const seen = new Set();
116
136
  const found = [];
@@ -176,12 +196,19 @@ function listDiscoveredMboxCandidates(dir) {
176
196
  if (!fs.existsSync(dir))
177
197
  return [];
178
198
  try {
199
+ const origin = classifyDiscoveredMboxOrigin(dir);
179
200
  return fs.readdirSync(dir, { withFileTypes: true })
180
201
  .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".mbox"))
181
202
  .map((entry) => {
182
203
  const candidatePath = path.join(dir, entry.name);
183
204
  const stat = fs.statSync(candidatePath);
184
- return { path: candidatePath, name: entry.name, mtimeMs: stat.mtimeMs };
205
+ return {
206
+ path: candidatePath,
207
+ name: entry.name,
208
+ mtimeMs: stat.mtimeMs,
209
+ originKind: origin.originKind,
210
+ originLabel: origin.originLabel,
211
+ };
185
212
  });
186
213
  }
187
214
  catch {
@@ -244,7 +271,7 @@ function summarizeAmbientImportCandidates(candidates, candidateLimit) {
244
271
  : `${visibleCandidates.length} recent MBOX archives ready for import`;
245
272
  const detailLines = [
246
273
  "recent candidates:",
247
- ...visibleCandidates.map((candidate) => `- ${candidate.path}`),
274
+ ...visibleCandidates.map((candidate) => `- [${candidate.originLabel}] ${candidate.path}`),
248
275
  ...(hiddenCount > 0 ? [`- ...and ${hiddenCount} more recent archive${hiddenCount === 1 ? "" : "s"}`] : []),
249
276
  "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
277
  ];
@@ -254,8 +281,16 @@ function summarizeAmbientImportCandidates(candidates, candidateLimit) {
254
281
  spec: {
255
282
  fingerprint: recentImportFingerprint(candidates),
256
283
  candidatePaths: visibleCandidates.map((candidate) => candidate.path),
284
+ candidateDescriptors: visibleCandidates.map((candidate) => ({
285
+ path: candidate.path,
286
+ originKind: candidate.originKind,
287
+ originLabel: candidate.originLabel,
288
+ modifiedAt: new Date(candidate.mtimeMs).toISOString(),
289
+ })),
257
290
  newestCandidatePath: visibleCandidates[0]?.path ?? null,
258
291
  newestCandidateMtime: visibleCandidates[0] ? new Date(visibleCandidates[0].mtimeMs).toISOString() : null,
292
+ newestCandidateOriginKind: visibleCandidates[0]?.originKind ?? null,
293
+ newestCandidateOriginLabel: visibleCandidates[0]?.originLabel ?? null,
259
294
  },
260
295
  };
261
296
  }
@@ -120,7 +120,7 @@ function createAzureProviderRuntime(model, azureConfig = (0, config_1.getAzureCo
120
120
  appendToolOutput(callId, output) {
121
121
  if (!nativeInput)
122
122
  return;
123
- nativeInput.push({ type: "function_call_output", call_id: callId, output });
123
+ nativeInput.push({ type: "function_call_output", call_id: callId, output: (0, streaming_1.truncateResponsesFunctionCallOutput)(output) });
124
124
  },
125
125
  async streamTurn(request) {
126
126
  if (!nativeInput)
@@ -102,7 +102,7 @@ function createGithubCopilotProviderRuntime(model, config = (0, config_1.getGith
102
102
  appendToolOutput(callId, output) {
103
103
  if (!nativeInput)
104
104
  return;
105
- nativeInput.push({ type: "function_call_output", call_id: callId, output });
105
+ nativeInput.push({ type: "function_call_output", call_id: callId, output: (0, streaming_1.truncateResponsesFunctionCallOutput)(output) });
106
106
  },
107
107
  async streamTurn(request) {
108
108
  if (!nativeInput)
@@ -154,7 +154,7 @@ function createOpenAICodexProviderRuntime(model, codexConfig = (0, config_1.getO
154
154
  appendToolOutput(callId, output) {
155
155
  if (!nativeInput)
156
156
  return;
157
- nativeInput.push({ type: "function_call_output", call_id: callId, output });
157
+ nativeInput.push({ type: "function_call_output", call_id: callId, output: (0, streaming_1.truncateResponsesFunctionCallOutput)(output) });
158
158
  },
159
159
  async streamTurn(request) {
160
160
  if (!nativeInput)
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SettleStreamer = exports.SettleParser = void 0;
3
+ exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP = exports.SettleStreamer = exports.SettleParser = void 0;
4
+ exports.truncateResponsesFunctionCallOutput = truncateResponsesFunctionCallOutput;
4
5
  exports.toResponsesInput = toResponsesInput;
5
6
  exports.toResponsesTools = toResponsesTools;
6
7
  exports.streamChatCompletion = streamChatCompletion;
@@ -112,6 +113,16 @@ class SettleStreamer {
112
113
  }
113
114
  }
114
115
  exports.SettleStreamer = SettleStreamer;
116
+ exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP = 200_000;
117
+ function truncateResponsesFunctionCallOutput(output, maxChars = exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP) {
118
+ if (output.length <= maxChars)
119
+ return output;
120
+ const marker = `\n\n[truncated — function_call_output exceeded ${maxChars} chars; original length ${output.length} chars]\n\n`;
121
+ const remainingBudget = Math.max(0, maxChars - marker.length);
122
+ const headLength = Math.ceil(remainingBudget * 0.75);
123
+ const tailLength = Math.max(0, remainingBudget - headLength);
124
+ return `${output.slice(0, headLength)}${marker}${output.slice(-tailLength)}`;
125
+ }
115
126
  function toResponsesUserContent(content) {
116
127
  if (typeof content === "string") {
117
128
  return content;
@@ -217,7 +228,7 @@ function toResponsesInput(messages) {
217
228
  input.push({
218
229
  type: "function_call_output",
219
230
  call_id: t.tool_call_id,
220
- output: typeof t.content === "string" ? t.content : "",
231
+ output: truncateResponsesFunctionCallOutput(typeof t.content === "string" ? t.content : ""),
221
232
  });
222
233
  continue;
223
234
  }