@ouro.bot/cli 0.1.0-alpha.489 → 0.1.0-alpha.490

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/README.md CHANGED
@@ -22,7 +22,9 @@ Current first-class senses:
22
22
  - `cli`
23
23
  - `teams`
24
24
  - `bluebubbles`
25
- - `mcp`
25
+ - `mail`
26
+
27
+ (MCP is a bridge for developer tools — a separate channel, not a sense. See `src/heart/mcp/` for the implementation.)
26
28
 
27
29
  Current provider ids:
28
30
 
@@ -45,7 +47,7 @@ The shared harness lives in `src/`:
45
47
  - `src/repertoire/`
46
48
  Tool registry (split into category modules: files, shell, notes, bridge, session, continuity, flow, surface, config, and sense-specific tools), coding orchestration, task tools, shared API client, and integration clients (Graph, ADO, GitHub).
47
49
  - `src/senses/`
48
- CLI (with TUI in senses/cli/), Teams, BlueBubbles (in senses/bluebubbles/), MCP, activity transport, inner-dialog orchestration, and contextual heartbeat.
50
+ CLI (with TUI in senses/cli/), Teams, BlueBubbles (in senses/bluebubbles/), Mail (in senses/mail.ts), activity transport, inner-dialog orchestration, and contextual heartbeat. The MCP bridge is at `src/heart/mcp/`, not here.
49
51
  - `src/nerves/`
50
52
  Structured runtime logging and coverage-audit infrastructure.
51
53
  - `src/__tests__/`
@@ -267,4 +269,3 @@ See `skills/configure-dev-tools.md` for the full tool inventory and troubleshoot
267
269
  ## A Note To Future Maintainers
268
270
 
269
271
  If you discover a doc that lies, fix it or remove it. Accuracy is a kindness. A future agent should not have to untangle a fossil record just to understand where their hands are.
270
- # Production SPA serving
package/changelog.json CHANGED
@@ -1,6 +1,12 @@
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.490",
6
+ "changes": [
7
+ "BlueBubbles group echo self-talk fix. The BB ingest path previously relied solely on the payload's `isFromMe` flag to detect the agent's own outbound messages — but in groups, BlueBubbles sometimes broadcasts the echo back through the webhook with that flag missing or false. Without a fallback, the agent would ingest its own message and reply to it (the user reported this in a group with their friend Rach: 'Slugger talking to himself'). New `bluebubbles.ownHandles` config field accepts the agent's known iMessage handles (phone numbers in any formatting, or email addresses); a fallback guard at the head of `handleBlueBubblesNormalizedEvent` filters any event whose `sender.externalId` matches a configured handle (case-insensitive, with phone-number normalization across +/space/paren/dash differences) and emits a `senses.bluebubbles_self_handle_filtered` warn-level nerve event so the case is observable. Direct chats are unaffected (their echoes already carry `isFromMe: true` reliably). Also folds in three trivial cleanups surfaced by the full-system audit: removed a stray `# Production SPA serving` heading from the README, removed a vestigial `// getPhrases removed` comment in bluebubbles/index.ts, and removed two unreachable `throw new Error('unreachable')` statements after `process.exit(1)` in heart/core.ts (process.exit returns `never` so TS already knows control doesn't continue)."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.489",
6
12
  "changes": [
@@ -92,6 +92,7 @@ const DEFAULT_LOCAL_RUNTIME_CONFIG = {
92
92
  serverUrl: "",
93
93
  password: "",
94
94
  accountId: "default",
95
+ ownHandles: [],
95
96
  },
96
97
  bluebubblesChannel: {
97
98
  port: 18790,
@@ -284,17 +285,24 @@ function getTeamsChannelConfig() {
284
285
  }
285
286
  function getBlueBubblesConfig() {
286
287
  const config = loadConfig();
287
- const { serverUrl, password, accountId } = config.bluebubbles;
288
+ const { serverUrl, password, accountId, ownHandles } = config.bluebubbles;
288
289
  if (!serverUrl.trim()) {
289
290
  throw new Error("bluebubbles.serverUrl is required in this machine's agent-vault runtime config. Run `ouro connect bluebubbles --agent <agent>`.");
290
291
  }
291
292
  if (!password.trim()) {
292
293
  throw new Error("bluebubbles.password is required in this machine's agent-vault runtime config. Run `ouro connect bluebubbles --agent <agent>`.");
293
294
  }
295
+ const normalizedHandles = [];
296
+ for (const handle of ownHandles) {
297
+ const trimmed = handle.trim();
298
+ if (trimmed.length > 0)
299
+ normalizedHandles.push(trimmed);
300
+ }
294
301
  return {
295
302
  serverUrl: serverUrl.trim(),
296
303
  password: password.trim(),
297
304
  accountId: accountId.trim() || "default",
305
+ ownHandles: normalizedHandles,
298
306
  };
299
307
  }
300
308
  function getBlueBubblesChannelConfig() {
@@ -121,7 +121,6 @@ async function getProviderRuntime(facing = "human") {
121
121
  // eslint-disable-next-line no-console -- pre-boot guard: provider init failure
122
122
  console.error(`\n[fatal] ${msg}\n`);
123
123
  process.exit(1);
124
- throw new Error("unreachable");
125
124
  }
126
125
  if (!_providerRuntimes[facing]) {
127
126
  (0, runtime_1.emitNervesEvent)({
@@ -132,7 +131,6 @@ async function getProviderRuntime(facing = "human") {
132
131
  meta: {},
133
132
  });
134
133
  process.exit(1);
135
- throw new Error("unreachable");
136
134
  }
137
135
  return _providerRuntimes[facing].runtime;
138
136
  }
@@ -963,7 +963,10 @@ function appendEvictedToArchive(sessPath, evictedEvents) {
963
963
  }
964
964
  }
965
965
  function appendSyntheticAssistantEvent(envelope, content, recordedAt) {
966
- const sequence = envelope.events.length + 1;
966
+ // Use nextEventSequence(events) instead of `events.length + 1` so any gap
967
+ // (from pruning, archive replay, or self-heal dedup) cannot collide with
968
+ // an existing event id. Same fix pattern as line 1046.
969
+ const sequence = nextEventSequence(envelope.events);
967
970
  const event = buildEventFromMessage({ role: "assistant", content }, sequence, recordedAt, "synthetic", null, null);
968
971
  return {
969
972
  ...envelope,
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.enrichReactionText = enrichReactionText;
37
37
  exports.createStatusBatcher = createStatusBatcher;
38
+ exports.isAgentSelfHandle = isAgentSelfHandle;
38
39
  exports.handleBlueBubblesEvent = handleBlueBubblesEvent;
39
40
  exports.catchUpMissedBlueBubblesMessages = catchUpMissedBlueBubblesMessages;
40
41
  exports.recoverCapturedBlueBubblesInboundMessages = recoverCapturedBlueBubblesInboundMessages;
@@ -60,7 +61,6 @@ const channel_1 = require("../../mind/friends/channel");
60
61
  const pending_1 = require("../../mind/pending");
61
62
  const prompt_1 = require("../../mind/prompt");
62
63
  const mcp_manager_1 = require("../../repertoire/mcp-manager");
63
- // getPhrases removed — no longer needed after debug-activity cleanup
64
64
  const runtime_1 = require("../../nerves/runtime");
65
65
  const proactive_content_guard_1 = require("../proactive-content-guard");
66
66
  const model_1 = require("./model");
@@ -159,6 +159,7 @@ const defaultDeps = {
159
159
  createFriendStore: () => new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends")),
160
160
  createFriendResolver: (store, params) => new resolver_1.FriendResolver(store, params),
161
161
  createServer: http.createServer,
162
+ getOwnHandles: () => (0, config_1.getBlueBubblesConfig)().ownHandles,
162
163
  };
163
164
  const BLUEBUBBLES_RUNTIME_SYNC_INTERVAL_MS = 30_000;
164
165
  const BLUEBUBBLES_CATCHUP_PAGE_SIZE = 50;
@@ -607,6 +608,32 @@ function isWebhookPasswordValid(url, expectedPassword) {
607
608
  const provided = url.searchParams.get("password");
608
609
  return !provided || provided === expectedPassword;
609
610
  }
611
+ function normalizeHandleForSelfMatch(handle) {
612
+ const trimmed = handle.trim().toLowerCase();
613
+ if (!trimmed)
614
+ return "";
615
+ // Phone-shaped: strip everything but digits so +1 (415) 555-... matches 14155550000.
616
+ if (/[+\d]/.test(trimmed) && !trimmed.includes("@")) {
617
+ const digits = trimmed.replace(/\D/g, "");
618
+ if (digits.length >= 7)
619
+ return digits;
620
+ }
621
+ return trimmed;
622
+ }
623
+ function isAgentSelfHandle(senderExternalId, ownHandles) {
624
+ if (!senderExternalId || !senderExternalId.trim())
625
+ return false;
626
+ const target = normalizeHandleForSelfMatch(senderExternalId);
627
+ /* v8 ignore start -- target is non-empty by construction since senderExternalId was just verified non-whitespace */
628
+ if (!target)
629
+ return false;
630
+ /* v8 ignore stop */
631
+ for (const own of ownHandles) {
632
+ if (normalizeHandleForSelfMatch(own) === target)
633
+ return true;
634
+ }
635
+ return false;
636
+ }
610
637
  async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
611
638
  const client = resolvedDeps.createClient();
612
639
  const agentName = resolvedDeps.getAgentName();
@@ -622,6 +649,25 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
622
649
  });
623
650
  return { handled: true, notifiedAgent: false, kind: event.kind, reason: "from_me" };
624
651
  }
652
+ // Fallback self-detection: BlueBubbles sometimes broadcasts a group-chat
653
+ // outbound message back through the webhook with `isFromMe` missing/false.
654
+ // Without this guard the agent ingests its own message and replies to it
655
+ // ("Slugger talking to himself"). Compare the sender's externalId against
656
+ // the agent's known iMessage handles.
657
+ if (isAgentSelfHandle(event.sender.externalId, resolvedDeps.getOwnHandles())) {
658
+ (0, runtime_1.emitNervesEvent)({
659
+ level: "warn",
660
+ component: "senses",
661
+ event: "senses.bluebubbles_self_handle_filtered",
662
+ message: "filtered bluebubbles event whose sender matched an agent-owned handle (isFromMe was missing/false)",
663
+ meta: {
664
+ messageGuid: event.messageGuid,
665
+ kind: event.kind,
666
+ senderExternalId: event.sender.externalId,
667
+ },
668
+ });
669
+ return { handled: true, notifiedAgent: false, kind: event.kind, reason: "from_me" };
670
+ }
625
671
  if (event.kind === "mutation") {
626
672
  try {
627
673
  resolvedDeps.recordMutation(resolvedDeps.getAgentName(), event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.489",
3
+ "version": "0.1.0-alpha.490",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",