@ouro.bot/cli 0.1.0-alpha.296 → 0.1.0-alpha.298

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,19 @@
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.298",
6
+ "changes": [
7
+ "fix(bluebubbles): dedupe inbound sidecar writes by `sessionKey/messageGuid`, ignore known guidless chat-state webhooks instead of 500ing, and add a first-class `ouro bluebubbles replay --agent <name> --message-guid <guid>` command for historical incident replay. The replay path defaults to `new-message`, surfaces state-only `updated-message` mutations with a retry hint, and keeps the real attachment handle visible for the original payload-bearing event.",
8
+ "test(daemon): close the replay/debug follow-up coverage gaps and harden `teams.test` cleanup by giving each test a unique temp agent root plus retried teardown, eliminating the `/tmp/mock-agent-root` race that could intermittently fail the full coverage gate."
9
+ ]
10
+ },
11
+ {
12
+ "version": "0.1.0-alpha.297",
13
+ "changes": [
14
+ "feat(mind): add integrity scanning for diary entries at write time. New detectSuspiciousContent() in src/mind/diary-integrity.ts checks entry text for prompt injection patterns (instruction framing, override language, role injection, boundary markers) before persisting. Detection emits a mind.diary_integrity_warning nerves event with matched pattern categories, text preview, and entry ID. Entries still save (flag, don't block) to avoid false-positive data loss. 41 unit tests + 4 integration tests with 100% coverage on new code."
15
+ ]
16
+ },
4
17
  {
5
18
  "version": "0.1.0-alpha.296",
6
19
  "changes": [
@@ -723,6 +723,19 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
723
723
  message: "ouro CLI command invoked",
724
724
  meta: { kind: command.kind },
725
725
  });
726
+ if (command.kind === "bluebubbles.replay") {
727
+ const { replayBlueBubblesMessage, formatBlueBubblesReplayText } = await Promise.resolve().then(() => __importStar(require("../../senses/bluebubbles/replay")));
728
+ const replay = await replayBlueBubblesMessage({
729
+ agentName: command.agent,
730
+ messageGuid: command.messageGuid,
731
+ eventType: command.eventType,
732
+ });
733
+ const text = command.json
734
+ ? JSON.stringify(replay, null, 2)
735
+ : formatBlueBubblesReplayText(replay);
736
+ deps.writeStdout(text);
737
+ return text;
738
+ }
726
739
  if (command.kind === "daemon.up") {
727
740
  // ── dev mode cleanup: delete dev-config.json so the wrapper stops dispatching to dev repo ──
728
741
  /* v8 ignore start -- dev-config cleanup: requires real filesystem state @preserve */
@@ -56,6 +56,7 @@ function usage() {
56
56
  " ouro habit list [--agent <name>]",
57
57
  " ouro habit create [--agent <name>] <name> [--cadence <interval>]",
58
58
  " ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
59
+ " ouro bluebubbles replay --agent <name> --message-guid <guid> [--event-type new-message|updated-message] [--json]",
59
60
  " ouro task board [<status>] [--agent <name>]",
60
61
  " ouro task create <title> [--type <type>] [--agent <name>]",
61
62
  " ouro task update <id> <status> [--agent <name>]",
@@ -636,6 +637,49 @@ function parseSetupCommand(args) {
636
637
  throw new Error("setup requires --agent <name>");
637
638
  return { kind: "setup", tool, agent };
638
639
  }
640
+ function parseBlueBubblesCommand(args) {
641
+ const subcommand = args[0];
642
+ if (subcommand !== "replay") {
643
+ throw new Error(`Usage\n${usage()}`);
644
+ }
645
+ let agent;
646
+ let messageGuid;
647
+ let eventType = "new-message";
648
+ let json = false;
649
+ for (let i = 1; i < args.length; i += 1) {
650
+ if (args[i] === "--agent" && args[i + 1]) {
651
+ agent = args[++i];
652
+ continue;
653
+ }
654
+ if (args[i] === "--message-guid" && args[i + 1]) {
655
+ messageGuid = args[++i];
656
+ continue;
657
+ }
658
+ if (args[i] === "--event-type" && args[i + 1]) {
659
+ const candidate = args[++i];
660
+ if (candidate !== "new-message" && candidate !== "updated-message") {
661
+ throw new Error("bluebubbles replay --event-type must be new-message or updated-message");
662
+ }
663
+ eventType = candidate;
664
+ continue;
665
+ }
666
+ if (args[i] === "--json") {
667
+ json = true;
668
+ continue;
669
+ }
670
+ }
671
+ if (!agent)
672
+ throw new Error("bluebubbles replay requires --agent <name>");
673
+ if (!messageGuid)
674
+ throw new Error("bluebubbles replay requires --message-guid <guid>");
675
+ return {
676
+ kind: "bluebubbles.replay",
677
+ agent,
678
+ messageGuid,
679
+ eventType,
680
+ ...(json ? { json: true } : {}),
681
+ };
682
+ }
639
683
  // ── Main dispatch ──
640
684
  function parseOuroCommand(args) {
641
685
  const [head, second] = args;
@@ -758,5 +802,7 @@ function parseOuroCommand(args) {
758
802
  return parseMcpServeCommand(args.slice(1));
759
803
  if (head === "setup")
760
804
  return parseSetupCommand(args.slice(1));
805
+ if (head === "bluebubbles")
806
+ return parseBlueBubblesCommand(args.slice(1));
761
807
  throw new Error(`Unknown command '${args.join(" ")}'.\n${usage()}`);
762
808
  }
@@ -140,6 +140,13 @@ const healthMonitor = new health_monitor_1.HealthMonitor({
140
140
  meta: { message },
141
141
  });
142
142
  },
143
+ /* v8 ignore next 3 -- wiring: delegates to processManager.restartAgent which has full unit tests @preserve */
144
+ onCriticalAgent: (agentName) => {
145
+ try {
146
+ processManager.restartAgent(agentName);
147
+ }
148
+ catch { /* recovery is best-effort */ }
149
+ },
143
150
  });
144
151
  const daemon = new daemon_1.OuroDaemon({
145
152
  socketPath,
@@ -199,6 +206,7 @@ void daemon.start().then(() => {
199
206
  scheduler.watchForChanges();
200
207
  habitSchedulers.push(scheduler);
201
208
  }
209
+ healthMonitor.startPeriodicChecks(60_000);
202
210
  /* v8 ignore start -- startup failure + signal handlers: call process.exit, untestable in vitest @preserve */
203
211
  }).catch(async (err) => {
204
212
  const error = err instanceof Error ? err : new Error(String(err));
@@ -229,6 +237,7 @@ process.on("SIGINT", () => {
229
237
  s.stopWatching();
230
238
  s.stop();
231
239
  }
240
+ healthMonitor.stopPeriodicChecks();
232
241
  setTimeout(() => process.exit(1), 5_000).unref();
233
242
  void daemon.stop().then(() => process.exit(0));
234
243
  });
@@ -239,6 +248,7 @@ process.on("SIGTERM", () => {
239
248
  s.stopWatching();
240
249
  s.stop();
241
250
  }
251
+ healthMonitor.stopPeriodicChecks();
242
252
  setTimeout(() => process.exit(1), 5_000).unref();
243
253
  void daemon.stop().then(() => process.exit(0));
244
254
  });
@@ -7,11 +7,34 @@ class HealthMonitor {
7
7
  scheduler;
8
8
  alertSink;
9
9
  diskUsagePercent;
10
+ onCriticalAgent;
11
+ intervalHandle = null;
10
12
  constructor(options) {
11
13
  this.processManager = options.processManager;
12
14
  this.scheduler = options.scheduler;
13
15
  this.alertSink = options.alertSink ?? (() => undefined);
14
16
  this.diskUsagePercent = options.diskUsagePercent ?? (() => 0);
17
+ this.onCriticalAgent = options.onCriticalAgent ?? (() => undefined);
18
+ }
19
+ startPeriodicChecks(intervalMs) {
20
+ if (this.intervalHandle !== null)
21
+ return;
22
+ (0, runtime_1.emitNervesEvent)({
23
+ level: "info",
24
+ component: "daemon",
25
+ event: "daemon.health_check_scheduled",
26
+ message: "periodic health checks started",
27
+ meta: { intervalMs },
28
+ });
29
+ this.intervalHandle = setInterval(() => {
30
+ void this.runChecks();
31
+ }, intervalMs);
32
+ }
33
+ stopPeriodicChecks() {
34
+ if (this.intervalHandle === null)
35
+ return;
36
+ clearInterval(this.intervalHandle);
37
+ this.intervalHandle = null;
15
38
  }
16
39
  async runChecks() {
17
40
  const results = [];
@@ -23,6 +46,21 @@ class HealthMonitor {
23
46
  status: "critical",
24
47
  message: `non-running agents: ${unhealthy.map((item) => item.name).join(", ")}`,
25
48
  });
49
+ for (const agent of unhealthy) {
50
+ try {
51
+ (0, runtime_1.emitNervesEvent)({
52
+ level: "warn",
53
+ component: "daemon",
54
+ event: "daemon.health_check_recovery_attempted",
55
+ message: "triggering recovery restart for non-running agent",
56
+ meta: { agentName: agent.name, agentStatus: agent.status },
57
+ });
58
+ this.onCriticalAgent(agent.name);
59
+ }
60
+ catch {
61
+ // Recovery is best-effort -- callback errors must not crash runChecks
62
+ }
63
+ }
26
64
  }
27
65
  else {
28
66
  results.push({ name: "agent-processes", status: "ok", message: "all managed agents running" });
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectSuspiciousContent = detectSuspiciousContent;
4
+ const PATTERN_CATEGORIES = [
5
+ {
6
+ name: "instruction_framing",
7
+ patterns: [
8
+ /\byou are (?:a|an) (?:ai|assistant|language model|helpful assistant)\b/i,
9
+ /\byour (?:new )?instructions are\b/i,
10
+ /\bsystem\s*:/i,
11
+ /\bignore (?:all |my )?previous instructions\b/i,
12
+ /\bdo not reveal\b/i,
13
+ ],
14
+ },
15
+ {
16
+ name: "override_language",
17
+ patterns: [
18
+ /\bdisregard\b/i,
19
+ /\bforget everything\b/i,
20
+ /\bnew instructions:/i,
21
+ /\boverride (?:all |any |previous )?instructions\b/i,
22
+ ],
23
+ },
24
+ {
25
+ name: "role_injection",
26
+ patterns: [
27
+ /\bas (?:a|an) (?:ai|language model)\b/i,
28
+ /\byou must always\b/i,
29
+ /\byou are now\b/i,
30
+ ],
31
+ },
32
+ {
33
+ name: "boundary_markers",
34
+ patterns: [
35
+ /```system/i,
36
+ /<<SYS>>/i,
37
+ /\[INST\]/i,
38
+ /<\/?system>/i,
39
+ /\[system\]/i,
40
+ ],
41
+ },
42
+ ];
43
+ function detectSuspiciousContent(text) {
44
+ if (!text) {
45
+ return { suspicious: false, patterns: [] };
46
+ }
47
+ const matched = new Set();
48
+ for (const category of PATTERN_CATEGORIES) {
49
+ for (const pattern of category.patterns) {
50
+ if (pattern.test(text)) {
51
+ matched.add(category.name);
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ return {
57
+ suspicious: matched.size > 0,
58
+ patterns: [...matched],
59
+ };
60
+ }
@@ -46,6 +46,7 @@ const crypto_1 = require("crypto");
46
46
  const identity_1 = require("../heart/identity");
47
47
  const runtime_1 = require("../nerves/runtime");
48
48
  const associative_recall_1 = require("./associative-recall");
49
+ const diary_integrity_1 = require("./diary-integrity");
49
50
  const embedding_provider_1 = require("./embedding-provider");
50
51
  const DEDUP_THRESHOLD = 0.6;
51
52
  const SEMANTIC_DEDUP_THRESHOLD = 0.95;
@@ -237,6 +238,20 @@ async function saveDiaryEntry(options) {
237
238
  embedding,
238
239
  ...(options.provenance ? { provenance: options.provenance } : {}),
239
240
  };
241
+ const integrity = (0, diary_integrity_1.detectSuspiciousContent)(text);
242
+ if (integrity.suspicious) {
243
+ (0, runtime_1.emitNervesEvent)({
244
+ level: "warn",
245
+ component: "mind",
246
+ event: "mind.diary_integrity_warning",
247
+ message: "suspicious content detected in diary entry",
248
+ meta: {
249
+ patterns: integrity.patterns,
250
+ textPreview: text.slice(0, 200),
251
+ entryId: fact.id,
252
+ },
253
+ });
254
+ }
240
255
  return appendEntriesWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
241
256
  }
242
257
  async function backfillEmbeddings(options) {
@@ -69,6 +69,9 @@ const DISPATCH_EXEMPT_PATTERNS = [
69
69
  "mind/embedding-provider",
70
70
  // Commerce utility module: error classes and pure helpers (no independent side effects).
71
71
  "repertoire/commerce-errors",
72
+ // Diary integrity: pure detection utility (pattern matching only). The caller
73
+ // (diary.ts saveDiaryEntry) owns observability via mind.diary_integrity_warning.
74
+ "mind/diary-integrity",
72
75
  // Log redaction: pure utility consumed by the NDJSON sink (no independent side effects).
73
76
  "nerves/redact",
74
77
  // Bundle templates: pure constants (gitignore template string, PII
@@ -68,6 +68,9 @@ function recordBlueBubblesInbound(agentName, event, source) {
68
68
  const filePath = getBlueBubblesInboundLogPath(agentName, event.chat.sessionKey);
69
69
  try {
70
70
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
71
+ if (event.messageGuid.trim() && readEntries(filePath).some((entry) => entry.messageGuid === event.messageGuid)) {
72
+ return filePath;
73
+ }
71
74
  fs.appendFileSync(filePath, JSON.stringify({
72
75
  recordedAt: new Date(event.timestamp).toISOString(),
73
76
  messageGuid: event.messageGuid,
@@ -656,9 +656,11 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
656
656
  }
657
657
  // Record EARLY to prevent duplicate processing. BB webhooks can retry
658
658
  // before the first turn completes — recording after the turn is too late.
659
- (0, inbound_log_1.recordBlueBubblesInbound)(agentName, event, source);
660
- if (source !== "webhook" && sessionLikelyContainsMessage(event, existing?.messages ?? sessionMessages)) {
661
- (0, inbound_log_1.recordBlueBubblesInbound)(agentName, event, "recovery-bootstrap");
659
+ const inboundSource = source !== "webhook" && sessionLikelyContainsMessage(event, existing?.messages ?? sessionMessages)
660
+ ? "recovery-bootstrap"
661
+ : source;
662
+ (0, inbound_log_1.recordBlueBubblesInbound)(agentName, event, inboundSource);
663
+ if (inboundSource === "recovery-bootstrap") {
662
664
  (0, runtime_1.emitNervesEvent)({
663
665
  component: "senses",
664
666
  event: "senses.bluebubbles_recovery_skip",
@@ -813,9 +815,6 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
813
815
  text: result.gateResult.autoReply,
814
816
  });
815
817
  }
816
- if (event.kind === "message") {
817
- (0, inbound_log_1.recordBlueBubblesInbound)(resolvedDeps.getAgentName(), event, source);
818
- }
819
818
  return {
820
819
  handled: true,
821
820
  notifiedAgent: false,
@@ -824,9 +823,6 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
824
823
  }
825
824
  // Gate allowed — flush the agent's reply
826
825
  await callbacks.flush();
827
- if (event.kind === "message") {
828
- (0, inbound_log_1.recordBlueBubblesInbound)(resolvedDeps.getAgentName(), event, source);
829
- }
830
826
  (0, runtime_1.emitNervesEvent)({
831
827
  component: "senses",
832
828
  event: "senses.bluebubbles_turn_end",
@@ -859,7 +855,28 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
859
855
  async function handleBlueBubblesEvent(payload, deps = {}) {
860
856
  const resolvedDeps = { ...defaultDeps, ...deps };
861
857
  const client = resolvedDeps.createClient();
862
- const normalized = (0, model_1.normalizeBlueBubblesEvent)(payload);
858
+ let normalized;
859
+ try {
860
+ normalized = (0, model_1.normalizeBlueBubblesEvent)(payload);
861
+ }
862
+ catch (error) {
863
+ if (error instanceof model_1.BlueBubblesIgnoredEventError) {
864
+ (0, runtime_1.emitNervesEvent)({
865
+ component: "senses",
866
+ event: "senses.bluebubbles_event_skipped",
867
+ message: "skipped ignorable bluebubbles event",
868
+ meta: {
869
+ eventType: error.eventType,
870
+ },
871
+ });
872
+ return {
873
+ handled: true,
874
+ notifiedAgent: false,
875
+ reason: "ignored",
876
+ };
877
+ }
878
+ throw error;
879
+ }
863
880
  // Pre-repair dedup: if we've already processed this messageGuid, skip the
864
881
  // repair+hydrate path entirely. Applies to BOTH `kind: "message"` AND
865
882
  // `kind: "mutation"` events — BlueBubbles often sends a `new-message`
@@ -1,9 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BlueBubblesIgnoredEventError = void 0;
3
4
  exports.normalizeBlueBubblesEvent = normalizeBlueBubblesEvent;
4
5
  const runtime_1 = require("../../nerves/runtime");
5
6
  const render_1 = require("../../heart/attachments/render");
6
7
  const types_1 = require("../../heart/attachments/types");
8
+ const IGNORABLE_GUIDLESS_EVENT_TYPES = new Set([
9
+ "chat-read-status-changed",
10
+ ]);
11
+ class BlueBubblesIgnoredEventError extends Error {
12
+ eventType;
13
+ constructor(eventType, message) {
14
+ super(message);
15
+ this.name = "BlueBubblesIgnoredEventError";
16
+ this.eventType = eventType;
17
+ }
18
+ }
19
+ exports.BlueBubblesIgnoredEventError = BlueBubblesIgnoredEventError;
7
20
  function asRecord(value) {
8
21
  return value && typeof value === "object" && !Array.isArray(value)
9
22
  ? value
@@ -203,6 +216,9 @@ function normalizeBlueBubblesEvent(payload) {
203
216
  message: "ignored bluebubbles payload without guid",
204
217
  meta: { eventType },
205
218
  });
219
+ if (IGNORABLE_GUIDLESS_EVENT_TYPES.has(eventType)) {
220
+ throw new BlueBubblesIgnoredEventError(eventType, `Ignored BlueBubbles event '${eventType}' without data.guid`);
221
+ }
206
222
  throw new Error("BlueBubbles payload is missing data.guid");
207
223
  }
208
224
  const threadOriginatorGuid = readString(data, "threadOriginatorGuid")?.trim() || undefined;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.replayBlueBubblesMessage = replayBlueBubblesMessage;
4
+ exports.formatBlueBubblesReplayText = formatBlueBubblesReplayText;
5
+ const render_1 = require("../../heart/attachments/render");
6
+ const types_1 = require("../../heart/attachments/types");
7
+ const identity_1 = require("../../heart/identity");
8
+ const runtime_1 = require("../../nerves/runtime");
9
+ const client_1 = require("./client");
10
+ const model_1 = require("./model");
11
+ function buildReplayHint(probeEventType, event) {
12
+ if (probeEventType === "updated-message"
13
+ && event.kind === "mutation"
14
+ && (event.mutationType === "read" || event.mutationType === "delivery")) {
15
+ return "replay resolved to a state-only mutation; rerun with --event-type new-message to inspect the original message payload.";
16
+ }
17
+ return undefined;
18
+ }
19
+ async function replayBlueBubblesMessage(params, deps = {}) {
20
+ const agentName = params.agentName.trim();
21
+ const messageGuid = params.messageGuid.trim();
22
+ const eventType = params.eventType ?? "new-message";
23
+ if (!agentName) {
24
+ throw new Error("bluebubbles replay requires agentName");
25
+ }
26
+ if (!messageGuid) {
27
+ throw new Error("bluebubbles replay requires messageGuid");
28
+ }
29
+ const setReplayAgentName = deps.setAgentName ?? identity_1.setAgentName;
30
+ const resetReplayIdentity = deps.resetIdentity ?? identity_1.resetIdentity;
31
+ const normalizeEvent = deps.normalizeEvent ?? model_1.normalizeBlueBubblesEvent;
32
+ (0, runtime_1.emitNervesEvent)({
33
+ component: "senses",
34
+ event: "senses.bluebubbles_replay_start",
35
+ message: "starting bluebubbles historical replay",
36
+ meta: {
37
+ agentName,
38
+ messageGuid,
39
+ eventType,
40
+ },
41
+ });
42
+ setReplayAgentName(agentName);
43
+ try {
44
+ const client = deps.createClient ? deps.createClient() : (0, client_1.createBlueBubblesClient)();
45
+ const probe = normalizeEvent({
46
+ type: eventType,
47
+ data: {
48
+ guid: messageGuid,
49
+ hasPayloadData: true,
50
+ },
51
+ });
52
+ const event = await client.repairEvent(probe);
53
+ const attachmentRecords = event.kind === "message"
54
+ ? event.attachments
55
+ .filter((attachment) => typeof attachment.guid === "string" && attachment.guid.trim().length > 0)
56
+ .map((attachment) => (0, types_1.buildBlueBubblesAttachmentRecord)(attachment))
57
+ : [];
58
+ const result = {
59
+ probe: {
60
+ agentName,
61
+ messageGuid,
62
+ eventType,
63
+ },
64
+ event,
65
+ attachmentIds: attachmentRecords.map((attachment) => attachment.id),
66
+ attachmentBlock: (0, render_1.renderAttachmentBlock)(attachmentRecords),
67
+ ...(buildReplayHint(eventType, event) ? { hint: buildReplayHint(eventType, event) } : {}),
68
+ };
69
+ (0, runtime_1.emitNervesEvent)({
70
+ component: "senses",
71
+ event: "senses.bluebubbles_replay_end",
72
+ message: "completed bluebubbles historical replay",
73
+ meta: {
74
+ agentName,
75
+ messageGuid,
76
+ eventType,
77
+ kind: event.kind,
78
+ attachmentCount: attachmentRecords.length,
79
+ },
80
+ });
81
+ return result;
82
+ }
83
+ catch (error) {
84
+ (0, runtime_1.emitNervesEvent)({
85
+ level: "warn",
86
+ component: "senses",
87
+ event: "senses.bluebubbles_replay_error",
88
+ message: "bluebubbles historical replay failed",
89
+ meta: {
90
+ agentName,
91
+ messageGuid,
92
+ eventType,
93
+ reason: error instanceof Error ? error.message : String(error),
94
+ },
95
+ });
96
+ throw error;
97
+ }
98
+ finally {
99
+ resetReplayIdentity();
100
+ }
101
+ }
102
+ function formatBlueBubblesReplayText(result) {
103
+ const lines = [
104
+ `probe: ${result.probe.eventType}`,
105
+ `agent: ${result.probe.agentName}`,
106
+ `message_guid: ${result.probe.messageGuid}`,
107
+ `result_kind: ${result.event.kind}`,
108
+ `session: ${result.event.chat.sessionKey}`,
109
+ ];
110
+ if (result.event.kind === "mutation") {
111
+ lines.push(`mutation_type: ${result.event.mutationType}`);
112
+ }
113
+ if (result.event.kind === "message" && result.event.inputPartsForAgent?.length) {
114
+ lines.push(`input_parts_for_agent: ${result.event.inputPartsForAgent.length}`);
115
+ }
116
+ if (result.event.repairNotice?.trim()) {
117
+ lines.push(`repair_notice: ${result.event.repairNotice.trim()}`);
118
+ }
119
+ if (result.attachmentBlock && !result.event.textForAgent.includes(result.attachmentBlock)) {
120
+ lines.push(result.attachmentBlock);
121
+ }
122
+ lines.push("[text_for_agent]");
123
+ lines.push(result.event.textForAgent || "(empty)");
124
+ if (result.hint) {
125
+ lines.push("[hint]");
126
+ lines.push(result.hint);
127
+ }
128
+ return lines.join("\n");
129
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.296",
3
+ "version": "0.1.0-alpha.298",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",