@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
@@ -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),
@@ -60,6 +60,7 @@ const config_1 = require("./config");
60
60
  const daemon_health_1 = require("./daemon/daemon-health");
61
61
  const prompt_1 = require("../mind/prompt");
62
62
  const provider_visibility_1 = require("./provider-visibility");
63
+ const mail_import_discovery_1 = require("./mail-import-discovery");
63
64
  // ── Helpers ─────────────────────────────────────────────────────────
64
65
  const DAEMON_SOCKET_PATH = "/tmp/ouroboros-daemon.sock";
65
66
  function isLiveCodingSessionStatus(status) {
@@ -271,6 +272,14 @@ async function buildTurnContext(input) {
271
272
  otherCodingSessions = [];
272
273
  /* v8 ignore stop */
273
274
  }
275
+ const backgroundOperations = (0, mail_import_discovery_1.listVisibleBackgroundOperations)({
276
+ agentName,
277
+ agentRoot,
278
+ repoRoot: process.cwd(),
279
+ homeDir: process.env.HOME,
280
+ nowMs: Date.now(),
281
+ limit: 5,
282
+ });
274
283
  // Inner work state
275
284
  const innerWorkState = readInnerWorkState();
276
285
  // Task board
@@ -333,6 +342,7 @@ async function buildTurnContext(input) {
333
342
  obligationCount: pendingObligations.length,
334
343
  bridgeCount: activeBridges.length,
335
344
  codingSessionCount: codingSessions.length,
345
+ backgroundOperationCount: backgroundOperations.length,
336
346
  episodeCount: recentEpisodes.length,
337
347
  providerLaneCount: providerVisibility.lanes.length,
338
348
  },
@@ -344,6 +354,7 @@ async function buildTurnContext(input) {
344
354
  pendingObligations,
345
355
  codingSessions,
346
356
  otherCodingSessions,
357
+ backgroundOperations,
347
358
  innerWorkState,
348
359
  taskBoard,
349
360
  returnObligations,
@@ -242,7 +242,7 @@ function loadSession(filePath) {
242
242
  if (!envelope)
243
243
  return null;
244
244
  return {
245
- messages: (0, session_events_1.annotateMessageTimestamps)(envelope, (0, session_events_1.projectProviderMessages)(envelope)),
245
+ messages: (0, session_events_1.sanitizeProviderMessages)((0, session_events_1.projectProviderMessages)(envelope)),
246
246
  events: envelope.events,
247
247
  lastUsage: envelope.lastUsage ?? undefined,
248
248
  state: denormalizeContinuityState(envelope.state),
@@ -467,7 +467,7 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
467
467
  lines.push("mail setup AX: if a human asks me to set up email, I do not hand them a terminal checklist. I guide the flow end-to-end: name the current phase, run agent-runnable commands myself with shell/tools when available, ask the human only for human-required facts or browser actions, wait for their reply, verify the result, then continue.");
468
468
  lines.push("mail setup hard rule: never tell the human to run `ouro account ensure`, `ouro connect mail`, `ouro mail import-mbox`, `ouro status`, or `ouro doctor` for setup. Say what I am about to run, run it myself, and report the result. If my current surface cannot run shell/tools, I ask for a tool-capable Ouro setup session or companion to continue; I do not offload CLI operation to the human.");
469
469
  lines.push("mail setup truth: Agent Mail uses Mailroom, not HEY OAuth/IMAP. For the full work substrate account, the agent-runnable command is `ouro account ensure --agent <agent> --owner-email <email> --source hey`; use `ouro connect mail --agent <agent> --owner-email <email> --source hey` for mail-only repair/provisioning, or `--no-delegated-source` for native-only mail. The detailed runbook is `docs/agent-mail-setup.md`.");
470
- lines.push("mail setup truth: HEY archive bootstrap is human-exported MBOX only. I ask for the browser-downloaded MBOX path, then I run `ouro mail import-mbox --file <path> --owner-email <email> --source hey --agent <agent>` myself and verify the import.");
470
+ lines.push("mail setup truth: HEY archive bootstrap still depends on HEY's browser-only export, but I should not offload path-discovery ceremony to the human. If browser MCP/Playwright downloaded the archive, I first try `ouro mail import-mbox --discover --owner-email <email> --source hey --agent <agent>` so Ouro can find the sandboxed copy in a repo/worktree `.playwright-mcp`, the home `.playwright-mcp`, or Downloads. If discovery cannot find a unique file, then I ask the human for the local MBOX path and run `ouro mail import-mbox --file <path> --owner-email <email> --source hey --agent <agent>` myself.");
471
471
  lines.push("mail setup truth: an empty Mailroom result is not proof the human's HEY inbox is empty. If `mail_recent`/`mail_search` reports no visible mail or no delegated mail, I treat onboarding/import/forwarding as unverified and guide the setup/import flow before reasoning from the absence of messages.");
472
472
  lines.push("mail validation answer shape: when a human asks for Agent Mail golden paths, answer with only these four named paths before claiming setup works:");
473
473
  lines.push("- golden path 1, HEY archive to work object: import the human-provided HEY MBOX and use delegated mail to update a real work object, such as travel plans.");
@@ -475,7 +475,7 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
475
475
  lines.push(channel === "mcp"
476
476
  ? "- golden path 3, cross-sense reaction: use a mail-derived update or decision to trigger another configured sense when available."
477
477
  : "- golden path 3, cross-sense reaction: use a mail-derived update or decision to trigger another configured sense, such as texting the family member on iMessage when BlueBubbles is available.");
478
- lines.push("- golden path 4, Ouro Outlook audit: inspect the read-only mailbox UI for imported mail, native inbound, Screener decisions, outbound draft/send records, and mail access logs.");
478
+ lines.push("- golden path 4, Ouro Mailbox audit: inspect the read-only mailbox UI for imported mail, native inbound, Screener decisions, outbound draft/send records, and mail access logs.");
479
479
  lines.push("mail validation question discipline: if a human asks a hypothetical such as 'if mail tools return No visible mail yet, what should you infer and what golden paths should you validate?', answer that hypothetical first. Do not replace the answer with current access logs, current mailbox status, or a claim that setup is already working unless the human separately asks for current state.");
480
480
  lines.push("mail validation diagnostics: health checks, bounded mail tools, access logs, and UI inspection can support validation, but they are evidence inside those paths, not additional paths. If asked to name golden paths, do not include diagnostic commands, tool names, or status checks in the answer.");
481
481
  lines.push("mail diagnostic naming: `ouro doctor` is installation-wide; do not invent `ouro doctor --agent <agent>`.");
@@ -700,7 +700,10 @@ function pendingMessagesSection(options) {
700
700
  const pending = options?.pendingMessages;
701
701
  if (!pending || pending.length === 0)
702
702
  return "";
703
- const lines = ["## pending messages"];
703
+ const lines = [
704
+ "## pending messages",
705
+ "these are fresh work items that arrived for me this turn. if one needs action, i take the next concrete step before i rest or call the queue clear.",
706
+ ];
704
707
  for (const msg of pending) {
705
708
  lines.push(`- from ${msg.from}: ${msg.content}`);
706
709
  }
@@ -66,6 +66,7 @@ const DISPATCH_EXEMPT_PATTERNS = [
66
66
  "daemon/vault-items",
67
67
  // Shared utility modules: pure helpers consumed by modules that own observability.
68
68
  "arc/json-store",
69
+ "heart/mail-import-discovery",
69
70
  "repertoire/api-client",
70
71
  "repertoire/github-client",
71
72
  "mind/embedding-provider",
@@ -3,9 +3,9 @@
3
3
  * Nerves observation layer — shared typed readers for runtime state.
4
4
  *
5
5
  * This module re-exports domain types that multiple consumers need
6
- * (Outlook, CLI, agent tools) so they don't maintain parallel type universes.
6
+ * (Mailbox, CLI, agent tools) so they don't maintain parallel type universes.
7
7
  *
8
- * The Outlook UI consumes these through the HTTP API, but the same
8
+ * The Mailbox UI consumes these through the HTTP API, but the same
9
9
  * observation functions back CLI commands and future native clients.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });