@ouro.bot/cli 0.1.0-alpha.45 → 0.1.0-alpha.46

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.46",
6
+ "changes": [
7
+ "Inner dialog now knows which task triggered it: taskId flows from daemon poke through the worker into the turn, and the agent gets the full task file content instead of a generic heartbeat prompt.",
8
+ "Inner dialog boot message includes aspirations and state summary instead of a vacuous placeholder, so the agent wakes up with context about what matters and what's happening.",
9
+ "Vestigial `drainInbox` removed from inner dialog — pipeline already handles pending drain correctly.",
10
+ "Inner dialog nerves events now include assistant response preview, tool call names, token usage, and taskId for meaningful observability.",
11
+ "`ouro thoughts` command reads and formats inner dialog session turns with `--last`, `--json`, `--follow`, and `--agent` flags — humans can now see what the agent has been thinking.",
12
+ "`readTaskFile` searches collection subdirectories (one-shots, ongoing, habits) since the scheduler sends bare task stems without collection prefixes.",
13
+ "`ouro reminder create` accepts `--requester` to track who requested a reminder for notification round-trip.",
14
+ "Response extraction handles `tool_choice=required` models by falling back to `final_answer` tool call arguments when assistant message content is empty."
15
+ ]
16
+ },
4
17
  {
5
18
  "version": "0.1.0-alpha.45",
6
19
  "changes": [
@@ -62,6 +62,7 @@ const update_hooks_1 = require("./update-hooks");
62
62
  const bundle_meta_1 = require("./hooks/bundle-meta");
63
63
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
64
64
  const tasks_1 = require("../../repertoire/tasks");
65
+ const thoughts_1 = require("./thoughts");
65
66
  const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
66
67
  const launchd_1 = require("./launchd");
67
68
  function stringField(value) {
@@ -258,6 +259,7 @@ function usage() {
258
259
  " ouro friend list [--agent <name>]",
259
260
  " ouro friend show <id> [--agent <name>]",
260
261
  " ouro friend create --name <name> [--trust <level>] [--agent <name>]",
262
+ " ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
261
263
  " ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
262
264
  " ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
263
265
  " ouro whoami [--agent <name>]",
@@ -520,6 +522,7 @@ function parseReminderCommand(args) {
520
522
  let scheduledAt;
521
523
  let cadence;
522
524
  let category;
525
+ let requester;
523
526
  for (let i = 1; i < rest.length; i++) {
524
527
  if (rest[i] === "--body" && rest[i + 1]) {
525
528
  body = rest[i + 1];
@@ -537,6 +540,10 @@ function parseReminderCommand(args) {
537
540
  category = rest[i + 1];
538
541
  i += 1;
539
542
  }
543
+ else if (rest[i] === "--requester" && rest[i + 1]) {
544
+ requester = rest[i + 1];
545
+ i += 1;
546
+ }
540
547
  }
541
548
  if (!body)
542
549
  throw new Error(`Usage\n${usage()}`);
@@ -549,6 +556,7 @@ function parseReminderCommand(args) {
549
556
  ...(scheduledAt ? { scheduledAt } : {}),
550
557
  ...(cadence ? { cadence } : {}),
551
558
  ...(category ? { category } : {}),
559
+ ...(requester ? { requester } : {}),
552
560
  ...(agent ? { agent } : {}),
553
561
  };
554
562
  }
@@ -563,6 +571,23 @@ function parseSessionCommand(args) {
563
571
  return { kind: "session.list", ...(agent ? { agent } : {}) };
564
572
  throw new Error(`Usage\n${usage()}`);
565
573
  }
574
+ function parseThoughtsCommand(args) {
575
+ const { agent, rest: cleaned } = extractAgentFlag(args);
576
+ let last;
577
+ let json = false;
578
+ let follow = false;
579
+ for (let i = 0; i < cleaned.length; i++) {
580
+ if (cleaned[i] === "--last" && i + 1 < cleaned.length) {
581
+ last = Number.parseInt(cleaned[i + 1], 10);
582
+ i++;
583
+ }
584
+ if (cleaned[i] === "--json")
585
+ json = true;
586
+ if (cleaned[i] === "--follow" || cleaned[i] === "-f")
587
+ follow = true;
588
+ }
589
+ return { kind: "thoughts", ...(agent ? { agent } : {}), ...(last ? { last } : {}), ...(json ? { json } : {}), ...(follow ? { follow } : {}) };
590
+ }
566
591
  function parseFriendCommand(args) {
567
592
  const { agent, rest: cleaned } = extractAgentFlag(args);
568
593
  const [sub, ...rest] = cleaned;
@@ -630,6 +655,8 @@ function parseOuroCommand(args) {
630
655
  }
631
656
  if (head === "session")
632
657
  return parseSessionCommand(args.slice(1));
658
+ if (head === "thoughts")
659
+ return parseThoughtsCommand(args.slice(1));
633
660
  if (head === "chat") {
634
661
  if (!second)
635
662
  throw new Error(`Usage\n${usage()}`);
@@ -1338,6 +1365,7 @@ function executeReminderCommand(command, taskMod) {
1338
1365
  body: command.body,
1339
1366
  scheduledAt: command.scheduledAt,
1340
1367
  cadence: command.cadence,
1368
+ requester: command.requester,
1341
1369
  });
1342
1370
  return `created: ${created}`;
1343
1371
  }
@@ -1536,6 +1564,49 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1536
1564
  }
1537
1565
  /* v8 ignore stop */
1538
1566
  }
1567
+ // ── thoughts (local, no daemon socket needed) ──
1568
+ if (command.kind === "thoughts") {
1569
+ try {
1570
+ const agentName = command.agent ?? (0, identity_1.getAgentName)();
1571
+ const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`);
1572
+ const sessionFilePath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
1573
+ if (command.json) {
1574
+ try {
1575
+ const raw = fs.readFileSync(sessionFilePath, "utf-8");
1576
+ deps.writeStdout(raw);
1577
+ return raw;
1578
+ }
1579
+ catch {
1580
+ const message = "no inner dialog session found";
1581
+ deps.writeStdout(message);
1582
+ return message;
1583
+ }
1584
+ }
1585
+ const turns = (0, thoughts_1.parseInnerDialogSession)(sessionFilePath);
1586
+ const message = (0, thoughts_1.formatThoughtTurns)(turns, command.last ?? 10);
1587
+ deps.writeStdout(message);
1588
+ if (command.follow) {
1589
+ deps.writeStdout("\n\n--- following (ctrl+c to stop) ---\n");
1590
+ /* v8 ignore start -- callback tested via followThoughts unit tests @preserve */
1591
+ const stop = (0, thoughts_1.followThoughts)(sessionFilePath, (formatted) => {
1592
+ deps.writeStdout("\n" + formatted);
1593
+ });
1594
+ /* v8 ignore stop */
1595
+ // Block until process exit; cleanup watcher on SIGINT/SIGTERM
1596
+ return new Promise((resolve) => {
1597
+ const cleanup = () => { stop(); resolve(message); };
1598
+ process.once("SIGINT", cleanup);
1599
+ process.once("SIGTERM", cleanup);
1600
+ });
1601
+ }
1602
+ return message;
1603
+ }
1604
+ catch {
1605
+ const message = "error: no agent context — use --agent <name> to specify";
1606
+ deps.writeStdout(message);
1607
+ return message;
1608
+ }
1609
+ }
1539
1610
  // ── session list (local, no daemon socket needed) ──
1540
1611
  if (command.kind === "session.list") {
1541
1612
  /* v8 ignore start -- production default: requires full identity setup @preserve */
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ // Formats inner dialog session turns for human consumption.
3
+ // Used by `ouro thoughts` CLI command to show what the agent has been thinking.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.parseInnerDialogSession = parseInnerDialogSession;
39
+ exports.formatThoughtTurns = formatThoughtTurns;
40
+ exports.getInnerDialogSessionPath = getInnerDialogSessionPath;
41
+ exports.followThoughts = followThoughts;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const runtime_1 = require("../../nerves/runtime");
45
+ function contentToText(content) {
46
+ if (typeof content === "string")
47
+ return content;
48
+ if (!Array.isArray(content))
49
+ return "";
50
+ return content
51
+ .map((part) => {
52
+ if (typeof part === "string")
53
+ return part;
54
+ if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
55
+ return part.text;
56
+ }
57
+ return "";
58
+ })
59
+ .join("\n");
60
+ }
61
+ function classifyTurn(userText) {
62
+ if (userText.includes("waking up."))
63
+ return { type: "boot" };
64
+ const taskMatch = /## task: (.+)$/m.exec(userText);
65
+ if (taskMatch)
66
+ return { type: "task", taskId: taskMatch[1] };
67
+ return { type: "heartbeat" };
68
+ }
69
+ function extractToolNames(messages) {
70
+ const names = [];
71
+ for (const msg of messages) {
72
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
73
+ for (const tc of msg.tool_calls) {
74
+ if (tc.function?.name && tc.function.name !== "final_answer")
75
+ names.push(tc.function.name);
76
+ }
77
+ }
78
+ }
79
+ return names;
80
+ }
81
+ /** Extract text from a final_answer tool call's arguments. */
82
+ function extractFinalAnswer(messages) {
83
+ for (let k = messages.length - 1; k >= 0; k--) {
84
+ const msg = messages[k];
85
+ if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
86
+ continue;
87
+ for (const tc of msg.tool_calls) {
88
+ if (tc.function?.name !== "final_answer")
89
+ continue;
90
+ try {
91
+ const parsed = JSON.parse(tc.function.arguments ?? "{}");
92
+ if (typeof parsed.answer === "string" && parsed.answer.trim())
93
+ return parsed.answer.trim();
94
+ }
95
+ catch {
96
+ // malformed arguments — skip
97
+ }
98
+ }
99
+ }
100
+ return "";
101
+ }
102
+ function parseInnerDialogSession(sessionPath) {
103
+ (0, runtime_1.emitNervesEvent)({
104
+ component: "daemon",
105
+ event: "daemon.thoughts_parse",
106
+ message: "parsing inner dialog session",
107
+ meta: { sessionPath },
108
+ });
109
+ let raw;
110
+ try {
111
+ raw = fs.readFileSync(sessionPath, "utf-8");
112
+ }
113
+ catch {
114
+ return [];
115
+ }
116
+ let data;
117
+ try {
118
+ data = JSON.parse(raw);
119
+ }
120
+ catch {
121
+ return [];
122
+ }
123
+ if (data.version !== 1 || !Array.isArray(data.messages))
124
+ return [];
125
+ const turns = [];
126
+ const messages = data.messages;
127
+ // Walk messages, pairing user → (tool calls) → assistant sequences
128
+ let i = 0;
129
+ while (i < messages.length) {
130
+ const msg = messages[i];
131
+ if (msg.role === "system") {
132
+ i++;
133
+ continue;
134
+ }
135
+ if (msg.role !== "user") {
136
+ i++;
137
+ continue;
138
+ }
139
+ const userText = contentToText(msg.content);
140
+ const classification = classifyTurn(userText);
141
+ // Collect all messages until the next user message (or end)
142
+ const turnMessages = [];
143
+ let j = i + 1;
144
+ while (j < messages.length && messages[j].role !== "user") {
145
+ turnMessages.push(messages[j]);
146
+ j++;
147
+ }
148
+ // Find the last assistant text response in this turn.
149
+ // With tool_choice="required", the response may be inside a final_answer tool call.
150
+ const assistantMsgs = turnMessages.filter((m) => m.role === "assistant");
151
+ const lastAssistant = assistantMsgs.reverse().find((m) => contentToText(m.content).trim().length > 0);
152
+ const response = lastAssistant
153
+ ? contentToText(lastAssistant.content).trim()
154
+ : extractFinalAnswer(turnMessages);
155
+ const tools = extractToolNames(turnMessages);
156
+ turns.push({
157
+ type: classification.type,
158
+ prompt: userText.trim(),
159
+ response,
160
+ tools,
161
+ ...(classification.taskId ? { taskId: classification.taskId } : {}),
162
+ });
163
+ i = j;
164
+ }
165
+ return turns;
166
+ }
167
+ function formatThoughtTurns(turns, lastN) {
168
+ if (turns.length === 0)
169
+ return "no inner dialog activity";
170
+ const selected = lastN > 0 ? turns.slice(-lastN) : turns;
171
+ /* v8 ignore next -- unreachable: turns.length > 0 checked above, slice always returns ≥1 @preserve */
172
+ if (selected.length === 0)
173
+ return "no inner dialog activity";
174
+ const lines = [];
175
+ for (const turn of selected) {
176
+ const typeLabel = turn.type === "task" && turn.taskId
177
+ ? `task: ${turn.taskId}`
178
+ : turn.type;
179
+ lines.push(`--- ${typeLabel} ---`);
180
+ if (turn.tools.length > 0) {
181
+ lines.push(`tools: ${turn.tools.join(", ")}`);
182
+ }
183
+ if (turn.response) {
184
+ lines.push(turn.response);
185
+ }
186
+ else {
187
+ lines.push("(no response)");
188
+ }
189
+ lines.push("");
190
+ }
191
+ return lines.join("\n").trim();
192
+ }
193
+ function getInnerDialogSessionPath(agentRoot) {
194
+ return path.join(agentRoot, "state", "sessions", "self", "inner", "dialog.json");
195
+ }
196
+ /**
197
+ * Watch a session file and emit new turns as they appear.
198
+ * Returns a cleanup function that stops the watcher.
199
+ */
200
+ function followThoughts(sessionPath, onNewTurns, pollIntervalMs = 1000) {
201
+ let displayedCount = parseInnerDialogSession(sessionPath).length;
202
+ (0, runtime_1.emitNervesEvent)({
203
+ component: "daemon",
204
+ event: "daemon.thoughts_follow_start",
205
+ message: "started following inner dialog session",
206
+ meta: { sessionPath, initialTurns: displayedCount },
207
+ });
208
+ fs.watchFile(sessionPath, { interval: pollIntervalMs }, () => {
209
+ const turns = parseInnerDialogSession(sessionPath);
210
+ if (turns.length > displayedCount) {
211
+ const newTurns = turns.slice(displayedCount);
212
+ onNewTurns(formatThoughtTurns(newTurns, 0));
213
+ displayedCount = turns.length;
214
+ }
215
+ });
216
+ return () => {
217
+ fs.unwatchFile(sessionPath);
218
+ (0, runtime_1.emitNervesEvent)({
219
+ component: "daemon",
220
+ event: "daemon.thoughts_follow_stop",
221
+ message: "stopped following inner dialog session",
222
+ meta: { sessionPath, totalTurns: displayedCount },
223
+ });
224
+ };
225
+ }
@@ -6,12 +6,12 @@ const inner_dialog_1 = require("./inner-dialog");
6
6
  const runtime_1 = require("../nerves/runtime");
7
7
  function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runInnerDialogTurn)(options)) {
8
8
  let running = false;
9
- async function run(reason) {
9
+ async function run(reason, taskId) {
10
10
  if (running)
11
11
  return;
12
12
  running = true;
13
13
  try {
14
- await runTurn({ reason });
14
+ await runTurn({ reason, taskId });
15
15
  }
16
16
  catch (error) {
17
17
  (0, runtime_1.emitNervesEvent)({
@@ -37,8 +37,11 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
37
37
  await run("heartbeat");
38
38
  return;
39
39
  }
40
- if (maybeMessage.type === "poke" ||
41
- maybeMessage.type === "chat" ||
40
+ if (maybeMessage.type === "poke") {
41
+ await run("instinct", maybeMessage.taskId);
42
+ return;
43
+ }
44
+ if (maybeMessage.type === "chat" ||
42
45
  maybeMessage.type === "message") {
43
46
  await run("instinct");
44
47
  return;
@@ -37,6 +37,8 @@ exports.loadInnerDialogInstincts = loadInnerDialogInstincts;
37
37
  exports.buildInnerDialogBootstrapMessage = buildInnerDialogBootstrapMessage;
38
38
  exports.buildNonCanonicalCleanupNudge = buildNonCanonicalCleanupNudge;
39
39
  exports.buildInstinctUserMessage = buildInstinctUserMessage;
40
+ exports.readTaskFile = readTaskFile;
41
+ exports.buildTaskTriggeredMessage = buildTaskTriggeredMessage;
40
42
  exports.deriveResumeCheckpoint = deriveResumeCheckpoint;
41
43
  exports.innerDialogSessionPath = innerDialogSessionPath;
42
44
  exports.runInnerDialogTurn = runInnerDialogTurn;
@@ -73,8 +75,16 @@ function readAspirations(agentRoot) {
73
75
  function loadInnerDialogInstincts() {
74
76
  return [...DEFAULT_INNER_DIALOG_INSTINCTS];
75
77
  }
76
- function buildInnerDialogBootstrapMessage(_aspirations, _stateSummary) {
77
- return "waking up. settling in.\n\nwhat needs my attention?";
78
+ function buildInnerDialogBootstrapMessage(aspirations, stateSummary) {
79
+ const lines = ["waking up."];
80
+ if (aspirations) {
81
+ lines.push("", "## what matters to me", aspirations);
82
+ }
83
+ if (stateSummary) {
84
+ lines.push("", "## what i know so far", stateSummary);
85
+ }
86
+ lines.push("", "what needs my attention?");
87
+ return lines.join("\n");
78
88
  }
79
89
  function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
80
90
  if (nonCanonicalPaths.length === 0)
@@ -98,6 +108,33 @@ function buildInstinctUserMessage(instincts, _reason, state) {
98
108
  }
99
109
  return lines.join("\n");
100
110
  }
111
+ function readTaskFile(agentRoot, taskId) {
112
+ // Task files live in collection subdirectories (one-shots, ongoing, habits).
113
+ // Try each collection, then fall back to root tasks/ for legacy layout.
114
+ const collections = ["one-shots", "ongoing", "habits", ""];
115
+ for (const collection of collections) {
116
+ try {
117
+ return fs.readFileSync(path.join(agentRoot, "tasks", collection, `${taskId}.md`), "utf8").trim();
118
+ }
119
+ catch {
120
+ // not in this collection — try next
121
+ }
122
+ }
123
+ return "";
124
+ }
125
+ function buildTaskTriggeredMessage(taskId, taskContent, checkpoint) {
126
+ const lines = ["a task needs my attention."];
127
+ if (taskContent) {
128
+ lines.push("", `## task: ${taskId}`, taskContent);
129
+ }
130
+ else {
131
+ lines.push("", `## task: ${taskId}`, "(task file not found)");
132
+ }
133
+ if (checkpoint) {
134
+ lines.push("", `last i remember: ${checkpoint}`);
135
+ }
136
+ return lines.join("\n");
137
+ }
101
138
  function contentToText(content) {
102
139
  if (typeof content === "string")
103
140
  return content.trim();
@@ -143,6 +180,31 @@ function deriveResumeCheckpoint(messages) {
143
180
  return firstLine;
144
181
  return `${firstLine.slice(0, 217)}...`;
145
182
  }
183
+ function extractAssistantPreview(messages, maxLength = 120) {
184
+ const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
185
+ if (!lastAssistant)
186
+ return "";
187
+ const text = contentToText(lastAssistant.content);
188
+ if (!text)
189
+ return "";
190
+ /* v8 ignore next -- unreachable: contentToText().trim() guarantees a non-empty line @preserve */
191
+ const firstLine = text.split("\n").find((line) => line.trim().length > 0) ?? "";
192
+ if (firstLine.length <= maxLength)
193
+ return firstLine;
194
+ return `${firstLine.slice(0, maxLength - 3)}...`;
195
+ }
196
+ function extractToolCallNames(messages) {
197
+ const names = [];
198
+ for (const msg of messages) {
199
+ if (msg.role === "assistant" && "tool_calls" in msg && Array.isArray(msg.tool_calls)) {
200
+ for (const tc of msg.tool_calls) {
201
+ if ("function" in tc && tc.function?.name)
202
+ names.push(tc.function.name);
203
+ }
204
+ }
205
+ }
206
+ return [...new Set(names)];
207
+ }
146
208
  function createInnerDialogCallbacks() {
147
209
  return {
148
210
  onModelStart: () => { },
@@ -208,19 +270,17 @@ async function runInnerDialogTurn(options) {
208
270
  ].filter(Boolean).join("\n\n");
209
271
  }
210
272
  else {
211
- // Resumed session: instinct message with checkpoint context
273
+ // Resumed session: task-triggered or instinct message with checkpoint context
212
274
  const assistantTurns = existingMessages.filter((message) => message.role === "assistant").length;
213
275
  state.cycleCount = assistantTurns + 1;
214
276
  state.checkpoint = deriveResumeCheckpoint(existingMessages);
215
- userContent = buildInstinctUserMessage(instincts, reason, state);
216
- }
217
- // ── Adapter concern: inbox drain (inner-dialog-specific) ─────────
218
- const inboxMessages = options?.drainInbox?.() ?? [];
219
- if (inboxMessages.length > 0) {
220
- const section = inboxMessages
221
- .map((msg) => `- **${msg.from}**: ${msg.content}`)
222
- .join("\n");
223
- userContent = `${userContent}\n\n## incoming messages\n${section}`;
277
+ if (options?.taskId) {
278
+ const taskContent = readTaskFile((0, identity_1.getAgentRoot)(), options.taskId);
279
+ userContent = buildTaskTriggeredMessage(options.taskId, taskContent, state.checkpoint);
280
+ }
281
+ else {
282
+ userContent = buildInstinctUserMessage(instincts, reason, state);
283
+ }
224
284
  }
225
285
  const userMessage = { role: "user", content: userContent };
226
286
  // ── Session loader: wraps existing session logic ──────────────────
@@ -266,14 +326,28 @@ async function runInnerDialogTurn(options) {
266
326
  skipConfirmation: true,
267
327
  },
268
328
  });
329
+ const resultMessages = result.messages ?? [];
330
+ const assistantPreview = extractAssistantPreview(resultMessages);
331
+ const toolCalls = extractToolCallNames(resultMessages);
269
332
  (0, runtime_1.emitNervesEvent)({
270
333
  component: "senses",
271
334
  event: "senses.inner_dialog_turn",
272
335
  message: "inner dialog turn completed",
273
- meta: { reason, session: sessionFilePath },
336
+ meta: {
337
+ reason,
338
+ session: sessionFilePath,
339
+ ...(options?.taskId && { taskId: options.taskId }),
340
+ ...(assistantPreview && { assistantPreview }),
341
+ ...(toolCalls.length > 0 && { toolCalls }),
342
+ ...(result.usage && {
343
+ promptTokens: result.usage.input_tokens,
344
+ completionTokens: result.usage.output_tokens,
345
+ totalTokens: result.usage.total_tokens,
346
+ }),
347
+ },
274
348
  });
275
349
  return {
276
- messages: result.messages ?? [],
350
+ messages: resultMessages,
277
351
  usage: result.usage,
278
352
  sessionPath: result.sessionPath ?? sessionFilePath,
279
353
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.45",
3
+ "version": "0.1.0-alpha.46",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",