@ouro.bot/cli 0.1.0-alpha.45 → 0.1.0-alpha.47
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 +22 -0
- package/dist/heart/daemon/daemon-cli.js +75 -76
- package/dist/heart/daemon/daemon.js +7 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/thoughts.js +371 -0
- package/dist/repertoire/tools-base.js +52 -2
- package/dist/senses/inner-dialog-worker.js +7 -4
- package/dist/senses/inner-dialog.js +202 -90
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
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.47",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Self-messages now wake inner dialog now/soon instead of only queueing: the harness requests a daemon-managed inner wake when available and falls back to an inline inner turn when it is not.",
|
|
8
|
+
"`send_message(friendId=self)` and `query_session(friendId=self, channel=inner, mode=status)` now share a truthful four-line status contract: `queue`, `wake`, `processing`, and `surfaced`.",
|
|
9
|
+
"Inner dialog persists live runtime activity beside the transcript, so status checks can report active processing instead of stale last-turn history.",
|
|
10
|
+
"Inline fallback surfaced previews now extract `final_answer` text too, so completed inner turns no longer show `no outward result` when the response lived in the final tool call."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "0.1.0-alpha.46",
|
|
15
|
+
"changes": [
|
|
16
|
+
"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.",
|
|
17
|
+
"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.",
|
|
18
|
+
"Vestigial `drainInbox` removed from inner dialog — pipeline already handles pending drain correctly.",
|
|
19
|
+
"Inner dialog nerves events now include assistant response preview, tool call names, token usage, and taskId for meaningful observability.",
|
|
20
|
+
"`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.",
|
|
21
|
+
"`readTaskFile` searches collection subdirectories (one-shots, ongoing, habits) since the scheduler sends bare task stems without collection prefixes.",
|
|
22
|
+
"`ouro reminder create` accepts `--requester` to track who requested a reminder for notification round-trip.",
|
|
23
|
+
"Response extraction handles `tool_choice=required` models by falling back to `final_answer` tool call arguments when assistant message content is empty."
|
|
24
|
+
]
|
|
25
|
+
},
|
|
4
26
|
{
|
|
5
27
|
"version": "0.1.0-alpha.45",
|
|
6
28
|
"changes": [
|
|
@@ -41,7 +41,6 @@ exports.runOuroCli = runOuroCli;
|
|
|
41
41
|
const child_process_1 = require("child_process");
|
|
42
42
|
const crypto_1 = require("crypto");
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
|
-
const net = __importStar(require("net"));
|
|
45
44
|
const os = __importStar(require("os"));
|
|
46
45
|
const path = __importStar(require("path"));
|
|
47
46
|
const identity_1 = require("../identity");
|
|
@@ -62,8 +61,10 @@ const update_hooks_1 = require("./update-hooks");
|
|
|
62
61
|
const bundle_meta_1 = require("./hooks/bundle-meta");
|
|
63
62
|
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
64
63
|
const tasks_1 = require("../../repertoire/tasks");
|
|
64
|
+
const thoughts_1 = require("./thoughts");
|
|
65
65
|
const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
|
|
66
66
|
const launchd_1 = require("./launchd");
|
|
67
|
+
const socket_client_1 = require("./socket-client");
|
|
67
68
|
function stringField(value) {
|
|
68
69
|
return typeof value === "string" ? value : null;
|
|
69
70
|
}
|
|
@@ -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()}`);
|
|
@@ -643,38 +670,6 @@ function parseOuroCommand(args) {
|
|
|
643
670
|
return parseLinkCommand(args.slice(1));
|
|
644
671
|
throw new Error(`Unknown command '${args.join(" ")}'.\n${usage()}`);
|
|
645
672
|
}
|
|
646
|
-
function defaultSendCommand(socketPath, command) {
|
|
647
|
-
return new Promise((resolve, reject) => {
|
|
648
|
-
const client = net.createConnection(socketPath);
|
|
649
|
-
let raw = "";
|
|
650
|
-
client.on("connect", () => {
|
|
651
|
-
client.write(JSON.stringify(command));
|
|
652
|
-
client.end();
|
|
653
|
-
});
|
|
654
|
-
client.on("data", (chunk) => {
|
|
655
|
-
raw += chunk.toString("utf-8");
|
|
656
|
-
});
|
|
657
|
-
client.on("error", reject);
|
|
658
|
-
client.on("end", () => {
|
|
659
|
-
const trimmed = raw.trim();
|
|
660
|
-
if (trimmed.length === 0 && command.kind === "daemon.stop") {
|
|
661
|
-
resolve({ ok: true, message: "daemon stopped" });
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
if (trimmed.length === 0) {
|
|
665
|
-
reject(new Error("Daemon returned empty response."));
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
try {
|
|
669
|
-
const parsed = JSON.parse(trimmed);
|
|
670
|
-
resolve(parsed);
|
|
671
|
-
}
|
|
672
|
-
catch (error) {
|
|
673
|
-
reject(error);
|
|
674
|
-
}
|
|
675
|
-
});
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
673
|
function defaultStartDaemonProcess(socketPath) {
|
|
679
674
|
const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
680
675
|
const child = (0, child_process_1.spawn)("node", [entry, "--socket", socketPath], {
|
|
@@ -688,46 +683,6 @@ function defaultWriteStdout(text) {
|
|
|
688
683
|
// eslint-disable-next-line no-console -- terminal UX: CLI command output
|
|
689
684
|
console.log(text);
|
|
690
685
|
}
|
|
691
|
-
function defaultCheckSocketAlive(socketPath) {
|
|
692
|
-
return new Promise((resolve) => {
|
|
693
|
-
const client = net.createConnection(socketPath);
|
|
694
|
-
let raw = "";
|
|
695
|
-
let done = false;
|
|
696
|
-
const finalize = (alive) => {
|
|
697
|
-
if (done)
|
|
698
|
-
return;
|
|
699
|
-
done = true;
|
|
700
|
-
resolve(alive);
|
|
701
|
-
};
|
|
702
|
-
if ("setTimeout" in client && typeof client.setTimeout === "function") {
|
|
703
|
-
client.setTimeout(800, () => {
|
|
704
|
-
client.destroy();
|
|
705
|
-
finalize(false);
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
client.on("connect", () => {
|
|
709
|
-
client.write(JSON.stringify({ kind: "daemon.status" }));
|
|
710
|
-
client.end();
|
|
711
|
-
});
|
|
712
|
-
client.on("data", (chunk) => {
|
|
713
|
-
raw += chunk.toString("utf-8");
|
|
714
|
-
});
|
|
715
|
-
client.on("error", () => finalize(false));
|
|
716
|
-
client.on("end", () => {
|
|
717
|
-
if (raw.trim().length === 0) {
|
|
718
|
-
finalize(false);
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
try {
|
|
722
|
-
JSON.parse(raw);
|
|
723
|
-
finalize(true);
|
|
724
|
-
}
|
|
725
|
-
catch {
|
|
726
|
-
finalize(false);
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
686
|
function defaultCleanupStaleSocket(socketPath) {
|
|
732
687
|
if (fs.existsSync(socketPath)) {
|
|
733
688
|
fs.unlinkSync(socketPath);
|
|
@@ -1048,13 +1003,13 @@ async function defaultRunAdoptionSpecialist() {
|
|
|
1048
1003
|
}
|
|
1049
1004
|
}
|
|
1050
1005
|
/* v8 ignore stop */
|
|
1051
|
-
function createDefaultOuroCliDeps(socketPath =
|
|
1006
|
+
function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SOCKET_PATH) {
|
|
1052
1007
|
return {
|
|
1053
1008
|
socketPath,
|
|
1054
|
-
sendCommand:
|
|
1009
|
+
sendCommand: socket_client_1.sendDaemonCommand,
|
|
1055
1010
|
startDaemonProcess: defaultStartDaemonProcess,
|
|
1056
1011
|
writeStdout: defaultWriteStdout,
|
|
1057
|
-
checkSocketAlive:
|
|
1012
|
+
checkSocketAlive: socket_client_1.checkDaemonSocketAlive,
|
|
1058
1013
|
cleanupStaleSocket: defaultCleanupStaleSocket,
|
|
1059
1014
|
fallbackPendingMessage: defaultFallbackPendingMessage,
|
|
1060
1015
|
installSubagents: defaultInstallSubagents,
|
|
@@ -1338,6 +1293,7 @@ function executeReminderCommand(command, taskMod) {
|
|
|
1338
1293
|
body: command.body,
|
|
1339
1294
|
scheduledAt: command.scheduledAt,
|
|
1340
1295
|
cadence: command.cadence,
|
|
1296
|
+
requester: command.requester,
|
|
1341
1297
|
});
|
|
1342
1298
|
return `created: ${created}`;
|
|
1343
1299
|
}
|
|
@@ -1536,6 +1492,49 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1536
1492
|
}
|
|
1537
1493
|
/* v8 ignore stop */
|
|
1538
1494
|
}
|
|
1495
|
+
// ── thoughts (local, no daemon socket needed) ──
|
|
1496
|
+
if (command.kind === "thoughts") {
|
|
1497
|
+
try {
|
|
1498
|
+
const agentName = command.agent ?? (0, identity_1.getAgentName)();
|
|
1499
|
+
const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`);
|
|
1500
|
+
const sessionFilePath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
|
|
1501
|
+
if (command.json) {
|
|
1502
|
+
try {
|
|
1503
|
+
const raw = fs.readFileSync(sessionFilePath, "utf-8");
|
|
1504
|
+
deps.writeStdout(raw);
|
|
1505
|
+
return raw;
|
|
1506
|
+
}
|
|
1507
|
+
catch {
|
|
1508
|
+
const message = "no inner dialog session found";
|
|
1509
|
+
deps.writeStdout(message);
|
|
1510
|
+
return message;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
const turns = (0, thoughts_1.parseInnerDialogSession)(sessionFilePath);
|
|
1514
|
+
const message = (0, thoughts_1.formatThoughtTurns)(turns, command.last ?? 10);
|
|
1515
|
+
deps.writeStdout(message);
|
|
1516
|
+
if (command.follow) {
|
|
1517
|
+
deps.writeStdout("\n\n--- following (ctrl+c to stop) ---\n");
|
|
1518
|
+
/* v8 ignore start -- callback tested via followThoughts unit tests @preserve */
|
|
1519
|
+
const stop = (0, thoughts_1.followThoughts)(sessionFilePath, (formatted) => {
|
|
1520
|
+
deps.writeStdout("\n" + formatted);
|
|
1521
|
+
});
|
|
1522
|
+
/* v8 ignore stop */
|
|
1523
|
+
// Block until process exit; cleanup watcher on SIGINT/SIGTERM
|
|
1524
|
+
return new Promise((resolve) => {
|
|
1525
|
+
const cleanup = () => { stop(); resolve(message); };
|
|
1526
|
+
process.once("SIGINT", cleanup);
|
|
1527
|
+
process.once("SIGTERM", cleanup);
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
return message;
|
|
1531
|
+
}
|
|
1532
|
+
catch {
|
|
1533
|
+
const message = "error: no agent context — use --agent <name> to specify";
|
|
1534
|
+
deps.writeStdout(message);
|
|
1535
|
+
return message;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1539
1538
|
// ── session list (local, no daemon socket needed) ──
|
|
1540
1539
|
if (command.kind === "session.list") {
|
|
1541
1540
|
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
@@ -443,6 +443,13 @@ class OuroDaemon {
|
|
|
443
443
|
data: messages,
|
|
444
444
|
};
|
|
445
445
|
}
|
|
446
|
+
case "inner.wake":
|
|
447
|
+
await this.processManager.startAgent(command.agent);
|
|
448
|
+
this.processManager.sendToAgent?.(command.agent, { type: "message" });
|
|
449
|
+
return {
|
|
450
|
+
ok: true,
|
|
451
|
+
message: `woke inner dialog for ${command.agent}`,
|
|
452
|
+
};
|
|
446
453
|
case "chat.connect":
|
|
447
454
|
await this.processManager.startAgent(command.agent);
|
|
448
455
|
return {
|
|
@@ -0,0 +1,202 @@
|
|
|
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.DEFAULT_DAEMON_SOCKET_PATH = void 0;
|
|
37
|
+
exports.sendDaemonCommand = sendDaemonCommand;
|
|
38
|
+
exports.checkDaemonSocketAlive = checkDaemonSocketAlive;
|
|
39
|
+
exports.requestInnerWake = requestInnerWake;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const net = __importStar(require("net"));
|
|
42
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
+
exports.DEFAULT_DAEMON_SOCKET_PATH = "/tmp/ouroboros-daemon.sock";
|
|
44
|
+
function sendDaemonCommand(socketPath, command) {
|
|
45
|
+
(0, runtime_1.emitNervesEvent)({
|
|
46
|
+
component: "daemon",
|
|
47
|
+
event: "daemon.socket_command_start",
|
|
48
|
+
message: "sending daemon command over socket",
|
|
49
|
+
meta: { socketPath, kind: command.kind },
|
|
50
|
+
});
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const client = net.createConnection(socketPath);
|
|
53
|
+
let raw = "";
|
|
54
|
+
client.on("connect", () => {
|
|
55
|
+
client.write(JSON.stringify(command));
|
|
56
|
+
client.end();
|
|
57
|
+
});
|
|
58
|
+
client.on("data", (chunk) => {
|
|
59
|
+
raw += chunk.toString("utf-8");
|
|
60
|
+
});
|
|
61
|
+
client.on("error", (error) => {
|
|
62
|
+
(0, runtime_1.emitNervesEvent)({
|
|
63
|
+
level: "error",
|
|
64
|
+
component: "daemon",
|
|
65
|
+
event: "daemon.socket_command_error",
|
|
66
|
+
message: "daemon socket command failed",
|
|
67
|
+
meta: {
|
|
68
|
+
socketPath,
|
|
69
|
+
kind: command.kind,
|
|
70
|
+
error: error.message,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
reject(error);
|
|
74
|
+
});
|
|
75
|
+
client.on("end", () => {
|
|
76
|
+
const trimmed = raw.trim();
|
|
77
|
+
if (trimmed.length === 0 && command.kind === "daemon.stop") {
|
|
78
|
+
(0, runtime_1.emitNervesEvent)({
|
|
79
|
+
component: "daemon",
|
|
80
|
+
event: "daemon.socket_command_end",
|
|
81
|
+
message: "daemon socket command completed",
|
|
82
|
+
meta: { socketPath, kind: command.kind, ok: true },
|
|
83
|
+
});
|
|
84
|
+
resolve({ ok: true, message: "daemon stopped" });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (trimmed.length === 0) {
|
|
88
|
+
const error = new Error("Daemon returned empty response.");
|
|
89
|
+
(0, runtime_1.emitNervesEvent)({
|
|
90
|
+
level: "error",
|
|
91
|
+
component: "daemon",
|
|
92
|
+
event: "daemon.socket_command_error",
|
|
93
|
+
message: "daemon socket command returned empty response",
|
|
94
|
+
meta: {
|
|
95
|
+
socketPath,
|
|
96
|
+
kind: command.kind,
|
|
97
|
+
error: error.message,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
reject(error);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(trimmed);
|
|
105
|
+
(0, runtime_1.emitNervesEvent)({
|
|
106
|
+
component: "daemon",
|
|
107
|
+
event: "daemon.socket_command_end",
|
|
108
|
+
message: "daemon socket command completed",
|
|
109
|
+
meta: {
|
|
110
|
+
socketPath,
|
|
111
|
+
kind: command.kind,
|
|
112
|
+
ok: parsed.ok,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
resolve(parsed);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
(0, runtime_1.emitNervesEvent)({
|
|
119
|
+
level: "error",
|
|
120
|
+
component: "daemon",
|
|
121
|
+
event: "daemon.socket_command_error",
|
|
122
|
+
message: "daemon socket command returned invalid JSON",
|
|
123
|
+
meta: {
|
|
124
|
+
socketPath,
|
|
125
|
+
kind: command.kind,
|
|
126
|
+
error: error instanceof Error ? error.message : String(error),
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
reject(error);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function checkDaemonSocketAlive(socketPath) {
|
|
135
|
+
(0, runtime_1.emitNervesEvent)({
|
|
136
|
+
component: "daemon",
|
|
137
|
+
event: "daemon.socket_alive_check_start",
|
|
138
|
+
message: "checking daemon socket health",
|
|
139
|
+
meta: { socketPath },
|
|
140
|
+
});
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const client = net.createConnection(socketPath);
|
|
143
|
+
let raw = "";
|
|
144
|
+
let done = false;
|
|
145
|
+
const finalize = (alive) => {
|
|
146
|
+
if (done)
|
|
147
|
+
return;
|
|
148
|
+
done = true;
|
|
149
|
+
(0, runtime_1.emitNervesEvent)({
|
|
150
|
+
component: "daemon",
|
|
151
|
+
event: "daemon.socket_alive_check_end",
|
|
152
|
+
message: "daemon socket health check completed",
|
|
153
|
+
meta: { socketPath, alive },
|
|
154
|
+
});
|
|
155
|
+
resolve(alive);
|
|
156
|
+
};
|
|
157
|
+
if ("setTimeout" in client && typeof client.setTimeout === "function") {
|
|
158
|
+
client.setTimeout(800, () => {
|
|
159
|
+
client.destroy();
|
|
160
|
+
finalize(false);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
client.on("connect", () => {
|
|
164
|
+
client.write(JSON.stringify({ kind: "daemon.status" }));
|
|
165
|
+
client.end();
|
|
166
|
+
});
|
|
167
|
+
client.on("data", (chunk) => {
|
|
168
|
+
raw += chunk.toString("utf-8");
|
|
169
|
+
});
|
|
170
|
+
client.on("error", () => finalize(false));
|
|
171
|
+
client.on("end", () => {
|
|
172
|
+
if (raw.trim().length === 0) {
|
|
173
|
+
finalize(false);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
JSON.parse(raw);
|
|
178
|
+
finalize(true);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
finalize(false);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async function requestInnerWake(agent, socketPath = exports.DEFAULT_DAEMON_SOCKET_PATH) {
|
|
187
|
+
const socketAvailable = fs.existsSync(socketPath);
|
|
188
|
+
(0, runtime_1.emitNervesEvent)({
|
|
189
|
+
component: "daemon",
|
|
190
|
+
event: "daemon.inner_wake_request",
|
|
191
|
+
message: "requesting daemon-managed inner wake",
|
|
192
|
+
meta: {
|
|
193
|
+
agent,
|
|
194
|
+
socketPath,
|
|
195
|
+
socketAvailable,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
if (!socketAvailable) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return sendDaemonCommand(socketPath, { kind: "inner.wake", agent });
|
|
202
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
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.formatSurfacedValue = formatSurfacedValue;
|
|
39
|
+
exports.deriveInnerDialogStatus = deriveInnerDialogStatus;
|
|
40
|
+
exports.formatInnerDialogStatus = formatInnerDialogStatus;
|
|
41
|
+
exports.extractThoughtResponseFromMessages = extractThoughtResponseFromMessages;
|
|
42
|
+
exports.parseInnerDialogSession = parseInnerDialogSession;
|
|
43
|
+
exports.formatThoughtTurns = formatThoughtTurns;
|
|
44
|
+
exports.getInnerDialogSessionPath = getInnerDialogSessionPath;
|
|
45
|
+
exports.readInnerDialogStatus = readInnerDialogStatus;
|
|
46
|
+
exports.followThoughts = followThoughts;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
50
|
+
function contentToText(content) {
|
|
51
|
+
if (typeof content === "string")
|
|
52
|
+
return content;
|
|
53
|
+
if (!Array.isArray(content))
|
|
54
|
+
return "";
|
|
55
|
+
return content
|
|
56
|
+
.map((part) => {
|
|
57
|
+
if (typeof part === "string")
|
|
58
|
+
return part;
|
|
59
|
+
if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
|
|
60
|
+
return part.text;
|
|
61
|
+
}
|
|
62
|
+
return "";
|
|
63
|
+
})
|
|
64
|
+
.join("\n");
|
|
65
|
+
}
|
|
66
|
+
function extractToolFunction(toolCall) {
|
|
67
|
+
if (!toolCall || typeof toolCall !== "object" || !("function" in toolCall))
|
|
68
|
+
return null;
|
|
69
|
+
const maybeFunction = toolCall.function;
|
|
70
|
+
if (!maybeFunction || typeof maybeFunction !== "object")
|
|
71
|
+
return null;
|
|
72
|
+
const name = "name" in maybeFunction && typeof maybeFunction.name === "string"
|
|
73
|
+
? maybeFunction.name
|
|
74
|
+
: undefined;
|
|
75
|
+
const argumentsValue = "arguments" in maybeFunction && typeof maybeFunction.arguments === "string"
|
|
76
|
+
? maybeFunction.arguments
|
|
77
|
+
: undefined;
|
|
78
|
+
return { name, arguments: argumentsValue };
|
|
79
|
+
}
|
|
80
|
+
function classifyTurn(userText) {
|
|
81
|
+
if (userText.includes("waking up."))
|
|
82
|
+
return { type: "boot" };
|
|
83
|
+
const taskMatch = /## task: (.+)$/m.exec(userText);
|
|
84
|
+
if (taskMatch)
|
|
85
|
+
return { type: "task", taskId: taskMatch[1] };
|
|
86
|
+
return { type: "heartbeat" };
|
|
87
|
+
}
|
|
88
|
+
function extractToolNames(messages) {
|
|
89
|
+
const names = [];
|
|
90
|
+
for (const msg of messages) {
|
|
91
|
+
if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
|
|
92
|
+
for (const tc of msg.tool_calls) {
|
|
93
|
+
const toolFunction = extractToolFunction(tc);
|
|
94
|
+
if (toolFunction?.name && toolFunction.name !== "final_answer")
|
|
95
|
+
names.push(toolFunction.name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return names;
|
|
100
|
+
}
|
|
101
|
+
function extractPendingPromptMessages(prompt) {
|
|
102
|
+
return prompt
|
|
103
|
+
.split("\n")
|
|
104
|
+
.map((line) => line.trim())
|
|
105
|
+
.filter((line) => line.startsWith("[pending from "))
|
|
106
|
+
.map((line) => {
|
|
107
|
+
const separator = line.indexOf("]: ");
|
|
108
|
+
return separator >= 0 ? line.slice(separator + 3).trim() : "";
|
|
109
|
+
})
|
|
110
|
+
.filter((line) => line.length > 0);
|
|
111
|
+
}
|
|
112
|
+
function readPendingMessagesForStatus(pendingDir) {
|
|
113
|
+
if (!fs.existsSync(pendingDir))
|
|
114
|
+
return [];
|
|
115
|
+
let entries;
|
|
116
|
+
try {
|
|
117
|
+
entries = fs.readdirSync(pendingDir);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const files = [
|
|
123
|
+
...entries.filter((entry) => entry.endsWith(".json.processing")),
|
|
124
|
+
...entries.filter((entry) => entry.endsWith(".json") && !entry.endsWith(".json.processing")),
|
|
125
|
+
].sort((a, b) => a.localeCompare(b));
|
|
126
|
+
const messages = [];
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
try {
|
|
129
|
+
const raw = fs.readFileSync(path.join(pendingDir, file), "utf-8");
|
|
130
|
+
const parsed = JSON.parse(raw);
|
|
131
|
+
if (typeof parsed.content === "string") {
|
|
132
|
+
messages.push(parsed);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// unreadable pending files should not break status queries
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return messages;
|
|
140
|
+
}
|
|
141
|
+
function formatSurfacedValue(text, maxLength = 120) {
|
|
142
|
+
const firstLine = text
|
|
143
|
+
.split("\n")
|
|
144
|
+
.map((line) => line.trim())
|
|
145
|
+
.find((line) => line.length > 0);
|
|
146
|
+
if (!firstLine)
|
|
147
|
+
return "no outward result";
|
|
148
|
+
if (firstLine.length <= maxLength)
|
|
149
|
+
return `"${firstLine}"`;
|
|
150
|
+
return `"${firstLine.slice(0, maxLength - 3)}..."`;
|
|
151
|
+
}
|
|
152
|
+
function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
|
|
153
|
+
if (runtimeState?.status === "running") {
|
|
154
|
+
return {
|
|
155
|
+
queue: pendingMessages.length > 0 ? "queued to inner/dialog" : "clear",
|
|
156
|
+
wake: "in progress",
|
|
157
|
+
processing: "started",
|
|
158
|
+
surfaced: "nothing yet",
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (pendingMessages.length > 0) {
|
|
162
|
+
return {
|
|
163
|
+
queue: "queued to inner/dialog",
|
|
164
|
+
wake: "awaiting inner session",
|
|
165
|
+
processing: "pending",
|
|
166
|
+
surfaced: "nothing yet",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const latestProcessedPendingTurn = [...turns]
|
|
170
|
+
.reverse()
|
|
171
|
+
.find((turn) => extractPendingPromptMessages(turn.prompt).length > 0);
|
|
172
|
+
if (!latestProcessedPendingTurn) {
|
|
173
|
+
return {
|
|
174
|
+
queue: "clear",
|
|
175
|
+
wake: "idle",
|
|
176
|
+
processing: "idle",
|
|
177
|
+
surfaced: "nothing recent",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
queue: "clear",
|
|
182
|
+
wake: "completed",
|
|
183
|
+
processing: "processed",
|
|
184
|
+
surfaced: formatSurfacedValue(latestProcessedPendingTurn.response),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function formatInnerDialogStatus(status) {
|
|
188
|
+
return [
|
|
189
|
+
`queue: ${status.queue}`,
|
|
190
|
+
`wake: ${status.wake}`,
|
|
191
|
+
`processing: ${status.processing}`,
|
|
192
|
+
`surfaced: ${status.surfaced}`,
|
|
193
|
+
].join("\n");
|
|
194
|
+
}
|
|
195
|
+
/** Extract text from a final_answer tool call's arguments. */
|
|
196
|
+
function extractFinalAnswer(messages) {
|
|
197
|
+
for (let k = messages.length - 1; k >= 0; k--) {
|
|
198
|
+
const msg = messages[k];
|
|
199
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
|
|
200
|
+
continue;
|
|
201
|
+
for (const tc of msg.tool_calls) {
|
|
202
|
+
const toolFunction = extractToolFunction(tc);
|
|
203
|
+
if (toolFunction?.name !== "final_answer")
|
|
204
|
+
continue;
|
|
205
|
+
try {
|
|
206
|
+
const parsed = JSON.parse(toolFunction.arguments ?? "{}");
|
|
207
|
+
if (typeof parsed.answer === "string" && parsed.answer.trim())
|
|
208
|
+
return parsed.answer.trim();
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// malformed arguments — skip
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return "";
|
|
216
|
+
}
|
|
217
|
+
function extractThoughtResponseFromMessages(messages) {
|
|
218
|
+
const assistantMsgs = messages.filter((message) => message.role === "assistant");
|
|
219
|
+
const lastAssistant = assistantMsgs.reverse().find((message) => contentToText(message.content).trim().length > 0);
|
|
220
|
+
return lastAssistant
|
|
221
|
+
? contentToText(lastAssistant.content).trim()
|
|
222
|
+
: extractFinalAnswer(messages);
|
|
223
|
+
}
|
|
224
|
+
function parseInnerDialogSession(sessionPath) {
|
|
225
|
+
(0, runtime_1.emitNervesEvent)({
|
|
226
|
+
component: "daemon",
|
|
227
|
+
event: "daemon.thoughts_parse",
|
|
228
|
+
message: "parsing inner dialog session",
|
|
229
|
+
meta: { sessionPath },
|
|
230
|
+
});
|
|
231
|
+
let raw;
|
|
232
|
+
try {
|
|
233
|
+
raw = fs.readFileSync(sessionPath, "utf-8");
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
let data;
|
|
239
|
+
try {
|
|
240
|
+
data = JSON.parse(raw);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
if (data.version !== 1 || !Array.isArray(data.messages))
|
|
246
|
+
return [];
|
|
247
|
+
const turns = [];
|
|
248
|
+
const messages = data.messages;
|
|
249
|
+
// Walk messages, pairing user → (tool calls) → assistant sequences
|
|
250
|
+
let i = 0;
|
|
251
|
+
while (i < messages.length) {
|
|
252
|
+
const msg = messages[i];
|
|
253
|
+
if (msg.role === "system") {
|
|
254
|
+
i++;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (msg.role !== "user") {
|
|
258
|
+
i++;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const userText = contentToText(msg.content);
|
|
262
|
+
const classification = classifyTurn(userText);
|
|
263
|
+
// Collect all messages until the next user message (or end)
|
|
264
|
+
const turnMessages = [];
|
|
265
|
+
let j = i + 1;
|
|
266
|
+
while (j < messages.length && messages[j].role !== "user") {
|
|
267
|
+
turnMessages.push(messages[j]);
|
|
268
|
+
j++;
|
|
269
|
+
}
|
|
270
|
+
// Find the last assistant text response in this turn.
|
|
271
|
+
// With tool_choice="required", the response may be inside a final_answer tool call.
|
|
272
|
+
const response = extractThoughtResponseFromMessages(turnMessages);
|
|
273
|
+
const tools = extractToolNames(turnMessages);
|
|
274
|
+
turns.push({
|
|
275
|
+
type: classification.type,
|
|
276
|
+
prompt: userText.trim(),
|
|
277
|
+
response,
|
|
278
|
+
tools,
|
|
279
|
+
...(classification.taskId ? { taskId: classification.taskId } : {}),
|
|
280
|
+
});
|
|
281
|
+
i = j;
|
|
282
|
+
}
|
|
283
|
+
return turns;
|
|
284
|
+
}
|
|
285
|
+
function formatThoughtTurns(turns, lastN) {
|
|
286
|
+
if (turns.length === 0)
|
|
287
|
+
return "no inner dialog activity";
|
|
288
|
+
const selected = lastN > 0 ? turns.slice(-lastN) : turns;
|
|
289
|
+
/* v8 ignore next -- unreachable: turns.length > 0 checked above, slice always returns ≥1 @preserve */
|
|
290
|
+
if (selected.length === 0)
|
|
291
|
+
return "no inner dialog activity";
|
|
292
|
+
const lines = [];
|
|
293
|
+
for (const turn of selected) {
|
|
294
|
+
const typeLabel = turn.type === "task" && turn.taskId
|
|
295
|
+
? `task: ${turn.taskId}`
|
|
296
|
+
: turn.type;
|
|
297
|
+
lines.push(`--- ${typeLabel} ---`);
|
|
298
|
+
if (turn.tools.length > 0) {
|
|
299
|
+
lines.push(`tools: ${turn.tools.join(", ")}`);
|
|
300
|
+
}
|
|
301
|
+
if (turn.response) {
|
|
302
|
+
lines.push(turn.response);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
lines.push("(no response)");
|
|
306
|
+
}
|
|
307
|
+
lines.push("");
|
|
308
|
+
}
|
|
309
|
+
return lines.join("\n").trim();
|
|
310
|
+
}
|
|
311
|
+
function getInnerDialogSessionPath(agentRoot) {
|
|
312
|
+
return path.join(agentRoot, "state", "sessions", "self", "inner", "dialog.json");
|
|
313
|
+
}
|
|
314
|
+
function getInnerDialogRuntimeStatePath(sessionPath) {
|
|
315
|
+
return path.join(path.dirname(sessionPath), "runtime.json");
|
|
316
|
+
}
|
|
317
|
+
function readInnerDialogRuntimeState(runtimePath) {
|
|
318
|
+
try {
|
|
319
|
+
const raw = fs.readFileSync(runtimePath, "utf-8");
|
|
320
|
+
const parsed = JSON.parse(raw);
|
|
321
|
+
if (parsed.status !== "running" && parsed.status !== "idle")
|
|
322
|
+
return null;
|
|
323
|
+
return {
|
|
324
|
+
status: parsed.status,
|
|
325
|
+
reason: parsed.reason === "boot" || parsed.reason === "heartbeat" || parsed.reason === "instinct"
|
|
326
|
+
? parsed.reason
|
|
327
|
+
: undefined,
|
|
328
|
+
startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : undefined,
|
|
329
|
+
lastCompletedAt: typeof parsed.lastCompletedAt === "string" ? parsed.lastCompletedAt : undefined,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function readInnerDialogStatus(sessionPath, pendingDir, runtimePath = getInnerDialogRuntimeStatePath(sessionPath)) {
|
|
337
|
+
const pendingMessages = readPendingMessagesForStatus(pendingDir);
|
|
338
|
+
const turns = parseInnerDialogSession(sessionPath);
|
|
339
|
+
const runtimeState = readInnerDialogRuntimeState(runtimePath);
|
|
340
|
+
return deriveInnerDialogStatus(pendingMessages, turns, runtimeState);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Watch a session file and emit new turns as they appear.
|
|
344
|
+
* Returns a cleanup function that stops the watcher.
|
|
345
|
+
*/
|
|
346
|
+
function followThoughts(sessionPath, onNewTurns, pollIntervalMs = 1000) {
|
|
347
|
+
let displayedCount = parseInnerDialogSession(sessionPath).length;
|
|
348
|
+
(0, runtime_1.emitNervesEvent)({
|
|
349
|
+
component: "daemon",
|
|
350
|
+
event: "daemon.thoughts_follow_start",
|
|
351
|
+
message: "started following inner dialog session",
|
|
352
|
+
meta: { sessionPath, initialTurns: displayedCount },
|
|
353
|
+
});
|
|
354
|
+
fs.watchFile(sessionPath, { interval: pollIntervalMs }, () => {
|
|
355
|
+
const turns = parseInnerDialogSession(sessionPath);
|
|
356
|
+
if (turns.length > displayedCount) {
|
|
357
|
+
const newTurns = turns.slice(displayedCount);
|
|
358
|
+
onNewTurns(formatThoughtTurns(newTurns, 0));
|
|
359
|
+
displayedCount = turns.length;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
return () => {
|
|
363
|
+
fs.unwatchFile(sessionPath);
|
|
364
|
+
(0, runtime_1.emitNervesEvent)({
|
|
365
|
+
component: "daemon",
|
|
366
|
+
event: "daemon.thoughts_follow_stop",
|
|
367
|
+
message: "stopped following inner dialog session",
|
|
368
|
+
meta: { sessionPath, totalTurns: displayedCount },
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
}
|
|
@@ -42,6 +42,8 @@ const skills_1 = require("./skills");
|
|
|
42
42
|
const config_1 = require("../heart/config");
|
|
43
43
|
const runtime_1 = require("../nerves/runtime");
|
|
44
44
|
const identity_1 = require("../heart/identity");
|
|
45
|
+
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
46
|
+
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
45
47
|
const tools_1 = require("./coding/tools");
|
|
46
48
|
const memory_1 = require("../mind/memory");
|
|
47
49
|
const pending_1 = require("../mind/pending");
|
|
@@ -596,6 +598,7 @@ exports.baseToolDefinitions = [
|
|
|
596
598
|
channel: { type: "string", description: "the channel: cli, teams, or inner" },
|
|
597
599
|
key: { type: "string", description: "session key (defaults to 'session')" },
|
|
598
600
|
messageCount: { type: "string", description: "how many recent messages to return (default 20)" },
|
|
601
|
+
mode: { type: "string", enum: ["transcript", "status"], description: "transcript (default) or lightweight status for self/inner checks" },
|
|
599
602
|
},
|
|
600
603
|
required: ["friendId", "channel"],
|
|
601
604
|
},
|
|
@@ -607,6 +610,15 @@ exports.baseToolDefinitions = [
|
|
|
607
610
|
const channel = args.channel;
|
|
608
611
|
const key = args.key || "session";
|
|
609
612
|
const count = parseInt(args.messageCount || "20", 10);
|
|
613
|
+
const mode = args.mode || "transcript";
|
|
614
|
+
if (mode === "status") {
|
|
615
|
+
if (friendId !== "self" || channel !== "inner") {
|
|
616
|
+
return "status mode is only available for self/inner dialog.";
|
|
617
|
+
}
|
|
618
|
+
const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)((0, identity_1.getAgentRoot)());
|
|
619
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
620
|
+
return (0, thoughts_1.formatInnerDialogStatus)((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
|
|
621
|
+
}
|
|
610
622
|
const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
611
623
|
const raw = fs.readFileSync(sessFile, "utf-8");
|
|
612
624
|
const data = JSON.parse(raw);
|
|
@@ -650,7 +662,7 @@ exports.baseToolDefinitions = [
|
|
|
650
662
|
},
|
|
651
663
|
},
|
|
652
664
|
},
|
|
653
|
-
handler: async (args) => {
|
|
665
|
+
handler: async (args, ctx) => {
|
|
654
666
|
const friendId = args.friendId;
|
|
655
667
|
const channel = args.channel;
|
|
656
668
|
const key = args.key || "session";
|
|
@@ -675,8 +687,46 @@ exports.baseToolDefinitions = [
|
|
|
675
687
|
timestamp: now,
|
|
676
688
|
};
|
|
677
689
|
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
690
|
+
if (isSelf) {
|
|
691
|
+
let wakeResponse = null;
|
|
692
|
+
try {
|
|
693
|
+
wakeResponse = await (0, socket_client_1.requestInnerWake)(agentName);
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
wakeResponse = null;
|
|
697
|
+
}
|
|
698
|
+
if (!wakeResponse?.ok) {
|
|
699
|
+
const { runInnerDialogTurn } = await Promise.resolve().then(() => __importStar(require("../senses/inner-dialog")));
|
|
700
|
+
if (ctx?.context?.channel.channel === "inner") {
|
|
701
|
+
queueMicrotask(() => {
|
|
702
|
+
void runInnerDialogTurn({ reason: "instinct" });
|
|
703
|
+
});
|
|
704
|
+
return (0, thoughts_1.formatInnerDialogStatus)({
|
|
705
|
+
queue: "queued to inner/dialog",
|
|
706
|
+
wake: "inline scheduled",
|
|
707
|
+
processing: "pending",
|
|
708
|
+
surfaced: "nothing yet",
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
const turnResult = await runInnerDialogTurn({ reason: "instinct" });
|
|
713
|
+
return (0, thoughts_1.formatInnerDialogStatus)({
|
|
714
|
+
queue: "queued to inner/dialog",
|
|
715
|
+
wake: "inline fallback",
|
|
716
|
+
processing: "processed",
|
|
717
|
+
surfaced: (0, thoughts_1.formatSurfacedValue)((0, thoughts_1.extractThoughtResponseFromMessages)(turnResult?.messages ?? [])),
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return (0, thoughts_1.formatInnerDialogStatus)({
|
|
722
|
+
queue: "queued to inner/dialog",
|
|
723
|
+
wake: "daemon requested",
|
|
724
|
+
processing: "pending",
|
|
725
|
+
surfaced: "nothing yet",
|
|
726
|
+
});
|
|
727
|
+
}
|
|
678
728
|
const preview = content.length > 80 ? content.slice(0, 80) + "…" : content;
|
|
679
|
-
const target =
|
|
729
|
+
const target = `${channel}/${key}`;
|
|
680
730
|
return `message queued for delivery to ${friendId} on ${target}. preview: "${preview}". it will be delivered when their session is next active.`;
|
|
681
731
|
},
|
|
682
732
|
},
|
|
@@ -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: () => { },
|
|
@@ -157,6 +219,31 @@ function createInnerDialogCallbacks() {
|
|
|
157
219
|
function innerDialogSessionPath() {
|
|
158
220
|
return (0, config_1.sessionPath)(pending_1.INNER_DIALOG_PENDING.friendId, pending_1.INNER_DIALOG_PENDING.channel, pending_1.INNER_DIALOG_PENDING.key);
|
|
159
221
|
}
|
|
222
|
+
function innerDialogRuntimeStatePath(sessionFilePath) {
|
|
223
|
+
return path.join(path.dirname(sessionFilePath), "runtime.json");
|
|
224
|
+
}
|
|
225
|
+
function writeInnerDialogRuntimeState(sessionFilePath, state) {
|
|
226
|
+
const filePath = innerDialogRuntimeStatePath(sessionFilePath);
|
|
227
|
+
try {
|
|
228
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
229
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
(0, runtime_1.emitNervesEvent)({
|
|
233
|
+
level: "warn",
|
|
234
|
+
component: "senses",
|
|
235
|
+
event: "senses.inner_dialog_runtime_state_error",
|
|
236
|
+
message: "failed to write inner dialog runtime state",
|
|
237
|
+
meta: {
|
|
238
|
+
status: state.status,
|
|
239
|
+
reason: state.reason ?? null,
|
|
240
|
+
path: filePath,
|
|
241
|
+
/* v8 ignore next -- Node fs APIs throw Error objects for mkdirSync/writeFileSync failures @preserve */
|
|
242
|
+
error: error instanceof Error ? error.message : String(error),
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
160
247
|
// Self-referencing friend record for inner dialog (agent talking to itself).
|
|
161
248
|
// No real friend to resolve -- this satisfies the pipeline's friend resolver contract.
|
|
162
249
|
function createSelfFriend(agentName) {
|
|
@@ -187,94 +274,119 @@ async function runInnerDialogTurn(options) {
|
|
|
187
274
|
const now = options?.now ?? (() => new Date());
|
|
188
275
|
const reason = options?.reason ?? "heartbeat";
|
|
189
276
|
const sessionFilePath = innerDialogSessionPath();
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
userContent
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
277
|
+
writeInnerDialogRuntimeState(sessionFilePath, {
|
|
278
|
+
status: "running",
|
|
279
|
+
reason,
|
|
280
|
+
startedAt: now().toISOString(),
|
|
281
|
+
});
|
|
282
|
+
try {
|
|
283
|
+
const loaded = (0, context_1.loadSession)(sessionFilePath);
|
|
284
|
+
const existingMessages = loaded?.messages ? [...loaded.messages] : [];
|
|
285
|
+
const instincts = options?.instincts ?? loadInnerDialogInstincts();
|
|
286
|
+
const state = {
|
|
287
|
+
cycleCount: 1,
|
|
288
|
+
resting: false,
|
|
289
|
+
lastHeartbeatAt: now().toISOString(),
|
|
290
|
+
};
|
|
291
|
+
// ── Adapter concern: build user message ──────────────────────────
|
|
292
|
+
let userContent;
|
|
293
|
+
if (existingMessages.length === 0) {
|
|
294
|
+
// Fresh session: bootstrap message with non-canonical cleanup nudge
|
|
295
|
+
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
296
|
+
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
297
|
+
const cleanupNudge = buildNonCanonicalCleanupNudge(nonCanonical);
|
|
298
|
+
userContent = [
|
|
299
|
+
buildInnerDialogBootstrapMessage(aspirations, "No prior inner dialog session found."),
|
|
300
|
+
cleanupNudge,
|
|
301
|
+
].filter(Boolean).join("\n\n");
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// Resumed session: task-triggered or instinct message with checkpoint context
|
|
305
|
+
const assistantTurns = existingMessages.filter((message) => message.role === "assistant").length;
|
|
306
|
+
state.cycleCount = assistantTurns + 1;
|
|
307
|
+
state.checkpoint = deriveResumeCheckpoint(existingMessages);
|
|
308
|
+
if (options?.taskId) {
|
|
309
|
+
const taskContent = readTaskFile((0, identity_1.getAgentRoot)(), options.taskId);
|
|
310
|
+
userContent = buildTaskTriggeredMessage(options.taskId, taskContent, state.checkpoint);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
userContent = buildInstinctUserMessage(instincts, reason, state);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const userMessage = { role: "user", content: userContent };
|
|
317
|
+
// ── Session loader: wraps existing session logic ──────────────────
|
|
318
|
+
const innerCapabilities = (0, channel_1.getChannelCapabilities)("inner");
|
|
319
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
320
|
+
const selfFriend = createSelfFriend((0, identity_1.getAgentName)());
|
|
321
|
+
const selfContext = { friend: selfFriend, channel: innerCapabilities };
|
|
322
|
+
const sessionLoader = {
|
|
323
|
+
loadOrCreate: async () => {
|
|
324
|
+
if (existingMessages.length > 0) {
|
|
325
|
+
return { messages: existingMessages, sessionPath: sessionFilePath };
|
|
326
|
+
}
|
|
327
|
+
// Fresh session: build system prompt
|
|
328
|
+
const systemPrompt = await (0, prompt_1.buildSystem)("inner", { toolChoiceRequired: true });
|
|
329
|
+
return {
|
|
330
|
+
messages: [{ role: "system", content: systemPrompt }],
|
|
331
|
+
sessionPath: sessionFilePath,
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
// ── Call shared pipeline ──────────────────────────────────────────
|
|
336
|
+
const callbacks = createInnerDialogCallbacks();
|
|
337
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
338
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
339
|
+
channel: "inner",
|
|
340
|
+
capabilities: innerCapabilities,
|
|
341
|
+
messages: [userMessage],
|
|
342
|
+
continuityIngressTexts: [],
|
|
343
|
+
callbacks,
|
|
344
|
+
friendResolver: { resolve: () => Promise.resolve(selfContext) },
|
|
345
|
+
sessionLoader,
|
|
346
|
+
pendingDir,
|
|
347
|
+
friendStore: createNoOpFriendStore(),
|
|
348
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
349
|
+
drainPending: pending_1.drainPending,
|
|
350
|
+
runAgent: core_1.runAgent,
|
|
351
|
+
postTurn: context_1.postTurn,
|
|
352
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
353
|
+
signal: options?.signal,
|
|
354
|
+
runAgentOptions: {
|
|
355
|
+
traceId,
|
|
356
|
+
toolChoiceRequired: true,
|
|
357
|
+
skipConfirmation: true,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
const resultMessages = result.messages ?? [];
|
|
361
|
+
const assistantPreview = extractAssistantPreview(resultMessages);
|
|
362
|
+
const toolCalls = extractToolCallNames(resultMessages);
|
|
363
|
+
(0, runtime_1.emitNervesEvent)({
|
|
364
|
+
component: "senses",
|
|
365
|
+
event: "senses.inner_dialog_turn",
|
|
366
|
+
message: "inner dialog turn completed",
|
|
367
|
+
meta: {
|
|
368
|
+
reason,
|
|
369
|
+
session: sessionFilePath,
|
|
370
|
+
...(options?.taskId && { taskId: options.taskId }),
|
|
371
|
+
...(assistantPreview && { assistantPreview }),
|
|
372
|
+
...(toolCalls.length > 0 && { toolCalls }),
|
|
373
|
+
...(result.usage && {
|
|
374
|
+
promptTokens: result.usage.input_tokens,
|
|
375
|
+
completionTokens: result.usage.output_tokens,
|
|
376
|
+
totalTokens: result.usage.total_tokens,
|
|
377
|
+
}),
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
return {
|
|
381
|
+
messages: resultMessages,
|
|
382
|
+
usage: result.usage,
|
|
383
|
+
sessionPath: result.sessionPath ?? sessionFilePath,
|
|
384
|
+
};
|
|
216
385
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
.join("\n");
|
|
223
|
-
userContent = `${userContent}\n\n## incoming messages\n${section}`;
|
|
386
|
+
finally {
|
|
387
|
+
writeInnerDialogRuntimeState(sessionFilePath, {
|
|
388
|
+
status: "idle",
|
|
389
|
+
lastCompletedAt: now().toISOString(),
|
|
390
|
+
});
|
|
224
391
|
}
|
|
225
|
-
const userMessage = { role: "user", content: userContent };
|
|
226
|
-
// ── Session loader: wraps existing session logic ──────────────────
|
|
227
|
-
const innerCapabilities = (0, channel_1.getChannelCapabilities)("inner");
|
|
228
|
-
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
229
|
-
const selfFriend = createSelfFriend((0, identity_1.getAgentName)());
|
|
230
|
-
const selfContext = { friend: selfFriend, channel: innerCapabilities };
|
|
231
|
-
const sessionLoader = {
|
|
232
|
-
loadOrCreate: async () => {
|
|
233
|
-
if (existingMessages.length > 0) {
|
|
234
|
-
return { messages: existingMessages, sessionPath: sessionFilePath };
|
|
235
|
-
}
|
|
236
|
-
// Fresh session: build system prompt
|
|
237
|
-
const systemPrompt = await (0, prompt_1.buildSystem)("inner", { toolChoiceRequired: true });
|
|
238
|
-
return {
|
|
239
|
-
messages: [{ role: "system", content: systemPrompt }],
|
|
240
|
-
sessionPath: sessionFilePath,
|
|
241
|
-
};
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
// ── Call shared pipeline ──────────────────────────────────────────
|
|
245
|
-
const callbacks = createInnerDialogCallbacks();
|
|
246
|
-
const traceId = (0, nerves_1.createTraceId)();
|
|
247
|
-
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
248
|
-
channel: "inner",
|
|
249
|
-
capabilities: innerCapabilities,
|
|
250
|
-
messages: [userMessage],
|
|
251
|
-
continuityIngressTexts: [],
|
|
252
|
-
callbacks,
|
|
253
|
-
friendResolver: { resolve: () => Promise.resolve(selfContext) },
|
|
254
|
-
sessionLoader,
|
|
255
|
-
pendingDir,
|
|
256
|
-
friendStore: createNoOpFriendStore(),
|
|
257
|
-
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
258
|
-
drainPending: pending_1.drainPending,
|
|
259
|
-
runAgent: core_1.runAgent,
|
|
260
|
-
postTurn: context_1.postTurn,
|
|
261
|
-
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
262
|
-
signal: options?.signal,
|
|
263
|
-
runAgentOptions: {
|
|
264
|
-
traceId,
|
|
265
|
-
toolChoiceRequired: true,
|
|
266
|
-
skipConfirmation: true,
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
(0, runtime_1.emitNervesEvent)({
|
|
270
|
-
component: "senses",
|
|
271
|
-
event: "senses.inner_dialog_turn",
|
|
272
|
-
message: "inner dialog turn completed",
|
|
273
|
-
meta: { reason, session: sessionFilePath },
|
|
274
|
-
});
|
|
275
|
-
return {
|
|
276
|
-
messages: result.messages ?? [],
|
|
277
|
-
usage: result.usage,
|
|
278
|
-
sessionPath: result.sessionPath ?? sessionFilePath,
|
|
279
|
-
};
|
|
280
392
|
}
|