@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
|
-
|
|
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(
|
|
77
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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: {
|
|
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:
|
|
350
|
+
messages: resultMessages,
|
|
277
351
|
usage: result.usage,
|
|
278
352
|
sessionPath: result.sessionPath ?? sessionFilePath,
|
|
279
353
|
};
|