@ouro.bot/cli 0.1.0-alpha.297 → 0.1.0-alpha.299
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 +175 -162
- package/dist/heart/daemon/cli-exec.js +13 -0
- package/dist/heart/daemon/cli-parse.js +46 -0
- package/dist/heart/daemon/daemon-entry.js +10 -0
- package/dist/heart/daemon/health-monitor.js +38 -0
- package/dist/senses/bluebubbles/inbound-log.js +3 -0
- package/dist/senses/bluebubbles/index.js +27 -10
- package/dist/senses/bluebubbles/model.js +16 -0
- package/dist/senses/bluebubbles/replay.js +129 -0
- package/package.json +1 -1
|
@@ -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" });
|
|
@@ -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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
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
|
+
}
|