@ouro.bot/cli 0.1.0-alpha.595 → 0.1.0-alpha.596

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.
@@ -0,0 +1,360 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.awaitingToolDefinitions = void 0;
37
+ exports.setAwaitToolDeps = setAwaitToolDeps;
38
+ exports.resetAwaitToolDeps = resetAwaitToolDeps;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const identity_1 = require("../heart/identity");
42
+ const runtime_1 = require("../nerves/runtime");
43
+ const await_parser_1 = require("../heart/awaiting/await-parser");
44
+ const await_alert_1 = require("../heart/awaiting/await-alert");
45
+ const pending_1 = require("../mind/pending");
46
+ /**
47
+ * Bundle-root-relative locations.
48
+ * - `awaiting/<name>.md` — active awaits (status: pending)
49
+ * - `awaiting/.done/<name>.md` — terminal awaits (resolved/expired/canceled)
50
+ */
51
+ function awaitingDir(agentRoot) {
52
+ return path.join(agentRoot, "awaiting");
53
+ }
54
+ function awaitingDoneDir(agentRoot) {
55
+ return path.join(awaitingDir(agentRoot), ".done");
56
+ }
57
+ function awaitFilePath(agentRoot, name) {
58
+ return path.join(awaitingDir(agentRoot), `${name}.md`);
59
+ }
60
+ function awaitDoneFilePath(agentRoot, name) {
61
+ return path.join(awaitingDoneDir(agentRoot), `${name}.md`);
62
+ }
63
+ const VALID_NAME = /^[A-Za-z0-9_-]+$/;
64
+ function validateName(name) {
65
+ if (!name)
66
+ return "name is required";
67
+ if (!VALID_NAME.test(name))
68
+ return "name must be alphanumeric, underscores, or hyphens";
69
+ return null;
70
+ }
71
+ function readAwaitDefinition(agentRoot, name) {
72
+ const filePath = awaitFilePath(agentRoot, name);
73
+ try {
74
+ const content = fs.readFileSync(filePath, "utf-8");
75
+ return (0, await_parser_1.parseAwaitFile)(content, filePath);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ /**
82
+ * Default delivery deps for the await alert path used from the tool.
83
+ * Mirrors the proactive-outreach pattern: queue to the inner-dialog pending
84
+ * dir when no live deliverer is registered.
85
+ */
86
+ function defaultDeliveryDeps(agentName) {
87
+ const pendingDir = (0, pending_1.getInnerDialogPendingDir)(agentName);
88
+ return {
89
+ agentName,
90
+ queuePending: (message) => {
91
+ // Mirror the write-as-pending convention from tools-session.
92
+ fs.mkdirSync(pendingDir, { recursive: true });
93
+ const filename = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
94
+ fs.writeFileSync(path.join(pendingDir, filename), JSON.stringify(message, null, 2), "utf-8");
95
+ },
96
+ };
97
+ }
98
+ let injected = {};
99
+ function setAwaitToolDeps(deps) {
100
+ injected = deps;
101
+ }
102
+ function resetAwaitToolDeps() {
103
+ injected = {};
104
+ }
105
+ function resolveDeliveryDeps(agentName) {
106
+ if (injected.buildDeliveryDeps)
107
+ return injected.buildDeliveryDeps(agentName);
108
+ return defaultDeliveryDeps(agentName);
109
+ }
110
+ function fileAwait(args, agentRoot, agentName, sessionFriendId, sessionChannel) {
111
+ const nameError = validateName(args.name);
112
+ if (nameError)
113
+ return JSON.stringify({ error: nameError });
114
+ if (!args.condition || !args.condition.trim()) {
115
+ return JSON.stringify({ error: "condition is required" });
116
+ }
117
+ if (!args.cadence || !args.cadence.trim()) {
118
+ return JSON.stringify({ error: "cadence is required" });
119
+ }
120
+ const filePath = awaitFilePath(agentRoot, args.name);
121
+ if (fs.existsSync(filePath)) {
122
+ return JSON.stringify({ error: `await "${args.name}" already exists` });
123
+ }
124
+ const mode = args.mode === "quick" ? "quick" : "full";
125
+ const alert = args.alert ?? sessionChannel ?? null;
126
+ const frontmatter = {
127
+ condition: args.condition.trim(),
128
+ cadence: args.cadence.trim(),
129
+ alert,
130
+ mode,
131
+ max_age: args.max_age ?? null,
132
+ status: "pending",
133
+ created_at: new Date().toISOString(),
134
+ filed_from: sessionChannel ?? "unknown",
135
+ filed_for_friend_id: sessionFriendId ?? null,
136
+ };
137
+ const rendered = (0, await_parser_1.renderAwaitFile)(frontmatter, args.body ?? "");
138
+ fs.mkdirSync(awaitingDir(agentRoot), { recursive: true });
139
+ fs.writeFileSync(filePath, rendered, "utf-8");
140
+ (0, runtime_1.emitNervesEvent)({
141
+ component: "repertoire",
142
+ event: "repertoire.await_filed",
143
+ message: "filed new await",
144
+ meta: { agent: agentName, name: args.name, cadence: args.cadence, alert },
145
+ });
146
+ return JSON.stringify({ filed: args.name, path: filePath });
147
+ }
148
+ function archiveAwait(agentRoot, name, updates) {
149
+ const source = awaitFilePath(agentRoot, name);
150
+ /* v8 ignore start -- defensive: callers (resolve/cancel) already verify the file exists via readAwaitDefinition; this guards the file-disappears-between-calls race @preserve */
151
+ if (!fs.existsSync(source)) {
152
+ return { ok: false, error: `await "${name}" not found in awaiting/` };
153
+ }
154
+ /* v8 ignore stop */
155
+ const content = fs.readFileSync(source, "utf-8");
156
+ const current = (0, await_parser_1.parseAwaitFile)(content, source);
157
+ // merge frontmatter from the parsed file with updates
158
+ const merged = {
159
+ condition: current.condition,
160
+ cadence: current.cadence,
161
+ alert: current.alert,
162
+ mode: current.mode,
163
+ max_age: current.max_age,
164
+ status: current.status,
165
+ created_at: current.created_at,
166
+ filed_from: current.filed_from,
167
+ filed_for_friend_id: current.filed_for_friend_id,
168
+ ...updates,
169
+ };
170
+ const rendered = (0, await_parser_1.renderAwaitFile)(merged, current.body);
171
+ fs.mkdirSync(awaitingDoneDir(agentRoot), { recursive: true });
172
+ fs.writeFileSync(awaitDoneFilePath(agentRoot, name), rendered, "utf-8");
173
+ fs.unlinkSync(source);
174
+ // re-parse the archived file so callers see merged fields (e.g. resolution_observation)
175
+ const archivedContent = fs.readFileSync(awaitDoneFilePath(agentRoot, name), "utf-8");
176
+ const archived = (0, await_parser_1.parseAwaitFile)(archivedContent, awaitDoneFilePath(agentRoot, name));
177
+ return { ok: true, file: archived };
178
+ }
179
+ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName) {
180
+ const nameError = validateName(name);
181
+ if (nameError)
182
+ return JSON.stringify({ error: nameError });
183
+ const existing = readAwaitDefinition(agentRoot, name);
184
+ if (!existing) {
185
+ return JSON.stringify({ error: `await "${name}" not found in awaiting/` });
186
+ }
187
+ if (existing.status !== "pending") {
188
+ return JSON.stringify({ error: `await "${name}" is not pending (status: ${existing.status})` });
189
+ }
190
+ if (verdict !== "yes" && verdict !== "no") {
191
+ return JSON.stringify({ error: "verdict must be 'yes' or 'no'" });
192
+ }
193
+ if (!observation || !observation.trim()) {
194
+ return JSON.stringify({ error: "observation is required" });
195
+ }
196
+ if (verdict === "no") {
197
+ // Update runtime state via recordAwaitCheck-style write
198
+ const { recordAwaitCheck } = await Promise.resolve().then(() => __importStar(require("../heart/awaiting/await-runtime-state")));
199
+ recordAwaitCheck(agentRoot, name, observation.trim(), new Date().toISOString());
200
+ (0, runtime_1.emitNervesEvent)({
201
+ component: "repertoire",
202
+ event: "repertoire.await_check_no",
203
+ message: "await checked, not yet ready",
204
+ meta: { agent: agentName, name },
205
+ });
206
+ return JSON.stringify({ verdict: "no", recorded: true });
207
+ }
208
+ // verdict === "yes" — archive and alert
209
+ const archive = archiveAwait(agentRoot, name, {
210
+ status: "resolved",
211
+ resolved_at: new Date().toISOString(),
212
+ resolution_observation: observation.trim(),
213
+ });
214
+ /* v8 ignore next -- defensive: archiveAwait only fails on the file-disappears-mid-call race already covered by v8 ignore inside archiveAwait @preserve */
215
+ if (!archive.ok)
216
+ return JSON.stringify({ error: archive.error });
217
+ (0, runtime_1.emitNervesEvent)({
218
+ component: "repertoire",
219
+ event: "repertoire.await_resolved",
220
+ message: "await resolved",
221
+ meta: { agent: agentName, name },
222
+ });
223
+ let alert = null;
224
+ try {
225
+ alert = await (0, await_alert_1.deliverAwaitAlert)({
226
+ awaitFile: archive.file,
227
+ reason: "resolved",
228
+ observation: observation.trim(),
229
+ agentRoot,
230
+ agentName,
231
+ deliveryDeps: resolveDeliveryDeps(agentName),
232
+ });
233
+ }
234
+ catch (error) {
235
+ (0, runtime_1.emitNervesEvent)({
236
+ level: "error",
237
+ component: "repertoire",
238
+ event: "repertoire.await_alert_error",
239
+ message: "await alert delivery threw",
240
+ meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) },
241
+ });
242
+ }
243
+ return JSON.stringify({
244
+ verdict: "yes",
245
+ archived: awaitDoneFilePath(agentRoot, name),
246
+ alert: alert ? { attempted: alert.attempted, status: alert.delivery?.status ?? null, skipped: alert.skipped ?? null } : null,
247
+ });
248
+ }
249
+ function cancelAwaitTool(name, reason, agentRoot, agentName) {
250
+ const nameError = validateName(name);
251
+ if (nameError)
252
+ return JSON.stringify({ error: nameError });
253
+ const existing = readAwaitDefinition(agentRoot, name);
254
+ if (!existing) {
255
+ return JSON.stringify({ error: `await "${name}" not found in awaiting/` });
256
+ }
257
+ if (existing.status !== "pending") {
258
+ return JSON.stringify({ error: `await "${name}" is not pending (status: ${existing.status})` });
259
+ }
260
+ const updates = {
261
+ status: "canceled",
262
+ canceled_at: new Date().toISOString(),
263
+ };
264
+ if (reason && reason.trim()) {
265
+ updates.cancel_reason = reason.trim();
266
+ }
267
+ const archive = archiveAwait(agentRoot, name, updates);
268
+ /* v8 ignore next -- defensive: archiveAwait only fails on the file-disappears-mid-call race already covered by v8 ignore inside archiveAwait @preserve */
269
+ if (!archive.ok)
270
+ return JSON.stringify({ error: archive.error });
271
+ (0, runtime_1.emitNervesEvent)({
272
+ component: "repertoire",
273
+ event: "repertoire.await_canceled",
274
+ message: "await canceled",
275
+ meta: { agent: agentName, name },
276
+ });
277
+ return JSON.stringify({ canceled: name, archived: awaitDoneFilePath(agentRoot, name) });
278
+ }
279
+ exports.awaitingToolDefinitions = [
280
+ {
281
+ tool: {
282
+ type: "function",
283
+ function: {
284
+ name: "await_condition",
285
+ description: "File a one-shot waiting condition. The daemon polls on cadence; on each tick I evaluate the condition and call resolve_await. When the condition becomes true, an alert fires via my outward channel.",
286
+ parameters: {
287
+ type: "object",
288
+ properties: {
289
+ name: { type: "string", description: "Filename stem (alphanumeric/underscore/hyphen). Must be unique." },
290
+ condition: { type: "string", description: "Natural-language condition to watch for." },
291
+ cadence: { type: "string", description: "Polling cadence (e.g. '5m', '1h')." },
292
+ alert: { type: "string", description: "Channel to alert on (e.g. 'bluebubbles', 'teams'). Defaults to filing session's channel." },
293
+ mode: { type: "string", description: "'full' or 'quick'. Defaults 'full'." },
294
+ max_age: { type: "string", description: "Optional auto-expiry (e.g. '24h')." },
295
+ body: { type: "string", description: "Optional notes: why I filed this, what 'ready' looks like." },
296
+ },
297
+ required: ["name", "condition", "cadence"],
298
+ },
299
+ },
300
+ },
301
+ handler: (a, ctx) => {
302
+ const agentRoot = (0, identity_1.getAgentRoot)();
303
+ const agentName = (0, identity_1.getAgentName)();
304
+ return fileAwait({
305
+ name: a.name,
306
+ condition: a.condition,
307
+ cadence: a.cadence,
308
+ alert: a.alert,
309
+ mode: a.mode,
310
+ max_age: a.max_age,
311
+ body: a.body,
312
+ }, agentRoot, agentName, ctx?.currentSession?.friendId ?? null, ctx?.currentSession?.channel ?? null);
313
+ },
314
+ },
315
+ {
316
+ tool: {
317
+ type: "function",
318
+ function: {
319
+ name: "resolve_await",
320
+ description: "Resolve a pending await with a verdict. verdict='yes' archives and fires the alert. verdict='no' records the observation and continues polling.",
321
+ parameters: {
322
+ type: "object",
323
+ properties: {
324
+ name: { type: "string", description: "Await name (filename stem)." },
325
+ verdict: { type: "string", description: "'yes' if the condition is met, 'no' otherwise." },
326
+ observation: { type: "string", description: "One-line summary of what I saw this tick." },
327
+ },
328
+ required: ["name", "verdict", "observation"],
329
+ },
330
+ },
331
+ },
332
+ handler: async (a) => {
333
+ const agentRoot = (0, identity_1.getAgentRoot)();
334
+ const agentName = (0, identity_1.getAgentName)();
335
+ return resolveAwaitTool(a.name, a.verdict, a.observation, agentRoot, agentName);
336
+ },
337
+ },
338
+ {
339
+ tool: {
340
+ type: "function",
341
+ function: {
342
+ name: "cancel_await",
343
+ description: "Cancel a pending await without alerting. Archives with status: canceled.",
344
+ parameters: {
345
+ type: "object",
346
+ properties: {
347
+ name: { type: "string", description: "Await name (filename stem)." },
348
+ reason: { type: "string", description: "Optional cancel reason." },
349
+ },
350
+ required: ["name"],
351
+ },
352
+ },
353
+ },
354
+ handler: (a) => {
355
+ const agentRoot = (0, identity_1.getAgentRoot)();
356
+ const agentName = (0, identity_1.getAgentName)();
357
+ return cancelAwaitTool(a.name, a.reason, agentRoot, agentName);
358
+ },
359
+ },
360
+ ];
@@ -18,6 +18,7 @@ const tools_flight_1 = require("./tools-flight");
18
18
  const tools_attachments_1 = require("./tools-attachments");
19
19
  const tools_mail_1 = require("./tools-mail");
20
20
  const tools_trip_1 = require("./tools-trip");
21
+ const tools_awaiting_1 = require("./tools-awaiting");
21
22
  // Re-export flow tools for consumers that import them from tools-base
22
23
  var tools_flow_1 = require("./tools-flow");
23
24
  Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_flow_1.ponderTool; } });
@@ -51,6 +52,7 @@ exports.baseToolDefinitions = [
51
52
  ...tools_attachments_1.attachmentToolDefinitions,
52
53
  ...tools_mail_1.mailToolDefinitions,
53
54
  ...tools_trip_1.tripToolDefinitions,
55
+ ...tools_awaiting_1.awaitingToolDefinitions,
54
56
  ];
55
57
  // Convenience array of just the tool schemas (no handler/integration metadata).
56
58
  // Used by consumers that need the OpenAI function-tool format.
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAwaitTurnMessage = buildAwaitTurnMessage;
4
+ const runtime_1 = require("../nerves/runtime");
5
+ function formatElapsed(ms) {
6
+ if (ms < 60_000)
7
+ return "<1m ago";
8
+ const minutes = Math.floor(ms / 60_000);
9
+ if (minutes < 60)
10
+ return `${minutes}m ago`;
11
+ const hours = Math.floor(minutes / 60);
12
+ if (hours < 24)
13
+ return `${hours}h ago`;
14
+ const days = Math.floor(hours / 24);
15
+ return `${days}d ago`;
16
+ }
17
+ function relativeAge(lastCheckedAt, now) {
18
+ if (!lastCheckedAt)
19
+ return null;
20
+ const lastMs = new Date(lastCheckedAt).getTime();
21
+ if (!Number.isFinite(lastMs))
22
+ return null;
23
+ return formatElapsed(now().getTime() - lastMs);
24
+ }
25
+ function buildAwaitTurnMessage(options) {
26
+ (0, runtime_1.emitNervesEvent)({
27
+ component: "senses",
28
+ event: "senses.await_turn_message_built",
29
+ message: "built await tick message",
30
+ meta: { awaitName: options.awaitName, checkedCount: options.checkedCount },
31
+ });
32
+ const lines = [];
33
+ lines.push(`await tick: ${options.awaitName} — ${options.condition}`);
34
+ if (options.body && options.body.trim().length > 0) {
35
+ lines.push("");
36
+ lines.push("what would count as ready:");
37
+ lines.push(options.body.trim());
38
+ }
39
+ const age = relativeAge(options.lastCheckedAt, options.now);
40
+ const obs = options.lastObservation && options.lastObservation.trim().length > 0
41
+ ? `last observation: "${options.lastObservation.trim()}"`
42
+ : "last observation: (none yet)";
43
+ if (options.checkedCount === 0) {
44
+ lines.push("");
45
+ lines.push("history: never checked. this is my first look.");
46
+ }
47
+ else {
48
+ lines.push("");
49
+ lines.push(`history: checked ${options.checkedCount}x so far. last checked ${age ?? "(unknown)"}. ${obs}.`);
50
+ }
51
+ if (options.checkpoint) {
52
+ lines.push("");
53
+ lines.push(`last checkpoint: ${options.checkpoint}`);
54
+ }
55
+ lines.push("");
56
+ lines.push("look around and decide. if the condition is met, call resolve_await with verdict='yes' and a one-line observation. otherwise call resolve_await with verdict='no' and a one-line observation of what i saw this tick.");
57
+ return lines.join("\n");
58
+ }
@@ -119,9 +119,9 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
119
119
  });
120
120
  }
121
121
  }
122
- async function run(reason, taskId, habitName) {
122
+ async function run(reason, taskId, habitName, awaitName) {
123
123
  if (running) {
124
- queue.push({ reason, taskId, habitName });
124
+ queue.push({ reason, taskId, habitName, awaitName });
125
125
  return;
126
126
  }
127
127
  running = true;
@@ -129,10 +129,11 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
129
129
  let nextReason = reason;
130
130
  let nextTaskId = taskId;
131
131
  let nextHabitName = habitName;
132
+ let nextAwaitName = awaitName;
132
133
  let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
133
134
  do {
134
135
  try {
135
- await runTurn({ reason: nextReason, taskId: nextTaskId, habitName: nextHabitName });
136
+ await runTurn({ reason: nextReason, taskId: nextTaskId, habitName: nextHabitName, awaitName: nextAwaitName });
136
137
  }
137
138
  catch (error) {
138
139
  (0, runtime_1.emitNervesEvent)({
@@ -165,6 +166,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
165
166
  nextReason = next.reason;
166
167
  nextTaskId = next.taskId;
167
168
  nextHabitName = next.habitName;
169
+ nextAwaitName = next.awaitName;
168
170
  consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
169
171
  continue;
170
172
  }
@@ -191,6 +193,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
191
193
  nextReason = "instinct";
192
194
  nextTaskId = undefined;
193
195
  nextHabitName = undefined;
196
+ nextAwaitName = undefined;
194
197
  continue;
195
198
  }
196
199
  break;
@@ -211,6 +214,13 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
211
214
  await run("habit", undefined, maybeMessage.habitName);
212
215
  return;
213
216
  }
217
+ if (maybeMessage.type === "await") {
218
+ /* v8 ignore next -- defensive fallback: live await dispatch always sets awaitName @preserve */
219
+ const awaitName = maybeMessage.awaitName ?? "(unnamed)";
220
+ recordHabitFireForRecursion(`await:${awaitName}`);
221
+ await run("await", undefined, undefined, maybeMessage.awaitName);
222
+ return;
223
+ }
214
224
  if (maybeMessage.type === "heartbeat") {
215
225
  // Backward compatibility: heartbeat -> habit/heartbeat
216
226
  recordHabitFireForRecursion("heartbeat");
@@ -69,6 +69,9 @@ const manager_1 = require("../heart/bridges/manager");
69
69
  const session_activity_1 = require("../heart/session-activity");
70
70
  const bluebubbles_1 = require("./bluebubbles");
71
71
  const habit_turn_message_1 = require("./habit-turn-message");
72
+ const await_turn_message_1 = require("./await-turn-message");
73
+ const await_parser_1 = require("../heart/awaiting/await-parser");
74
+ const await_runtime_state_1 = require("../heart/awaiting/await-runtime-state");
72
75
  const journal_index_1 = require("../mind/journal-index");
73
76
  const habit_parser_1 = require("../heart/habits/habit-parser");
74
77
  const habit_runtime_state_1 = require("../heart/habits/habit-runtime-state");
@@ -625,6 +628,45 @@ async function runInnerDialogTurn(options) {
625
628
  /* v8 ignore stop */
626
629
  }
627
630
  }
631
+ else if (reason === "await" && options?.awaitName) {
632
+ const agentRoot = (0, identity_1.getAgentRoot)();
633
+ const awaitName = options.awaitName;
634
+ const awaitFilePath = path.join(agentRoot, "awaiting", `${awaitName}.md`);
635
+ let awaitBody;
636
+ let condition = null;
637
+ let lastCheckedAt = null;
638
+ let lastObservation = null;
639
+ let checkedCount = 0;
640
+ let awaitFound = false;
641
+ try {
642
+ const awaitContent = fs.readFileSync(awaitFilePath, "utf-8");
643
+ const parsed = (0, await_runtime_state_1.applyAwaitRuntimeState)(agentRoot, (0, await_parser_1.parseAwaitFile)(awaitContent, awaitFilePath));
644
+ awaitFound = true;
645
+ awaitBody = parsed.body || undefined;
646
+ condition = parsed.condition;
647
+ lastCheckedAt = parsed.last_checked ?? null;
648
+ lastObservation = parsed.last_observation ?? null;
649
+ checkedCount = parsed.checked_count ?? 0;
650
+ }
651
+ catch {
652
+ // file missing — fall through to error message
653
+ }
654
+ if (!awaitFound || !condition) {
655
+ userContent = `await "${awaitName}" could not be read (file not found or no condition). check awaiting/${awaitName}.md.`;
656
+ }
657
+ else {
658
+ userContent = (0, await_turn_message_1.buildAwaitTurnMessage)({
659
+ awaitName,
660
+ condition,
661
+ body: awaitBody,
662
+ lastCheckedAt,
663
+ lastObservation,
664
+ checkedCount,
665
+ checkpoint: displayCheckpoint(state.checkpoint),
666
+ now,
667
+ });
668
+ }
669
+ }
628
670
  else {
629
671
  userContent = buildInstinctUserMessage(instincts, reason, state);
630
672
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.595",
3
+ "version": "0.1.0-alpha.596",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",