@ouro.bot/cli 0.1.0-alpha.13 → 0.1.0-alpha.131
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/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/changelog.json +814 -0
- package/dist/heart/active-work.js +622 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +105 -0
- package/dist/heart/config.js +66 -21
- package/dist/heart/core.js +518 -100
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +457 -0
- package/dist/heart/daemon/daemon-cli.js +1516 -195
- package/dist/heart/daemon/daemon-entry.js +43 -2
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +261 -1
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -72
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +57 -29
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +2 -2
- package/dist/heart/daemon/specialist-prompt.js +7 -4
- package/dist/heart/daemon/specialist-tools.js +52 -3
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +64 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +159 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +195 -34
- package/dist/heart/providers/azure.js +115 -9
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +49 -14
- package/dist/heart/safe-workspace.js +381 -0
- package/dist/heart/session-activity.js +173 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +108 -24
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/tool-loop.js +194 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +12 -0
- package/dist/mind/context.js +60 -14
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +456 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +210 -4
- package/dist/repertoire/coding/spawner.js +39 -9
- package/dist/repertoire/coding/tools.js +171 -4
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +925 -250
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +106 -53
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +915 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +374 -131
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +388 -83
- package/dist/senses/pipeline.js +444 -0
- package/dist/senses/teams.js +607 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +9 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -37,8 +37,11 @@ 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;
|
|
44
|
+
exports.enrichDelegatedFromWithBridge = enrichDelegatedFromWithBridge;
|
|
42
45
|
exports.runInnerDialogTurn = runInnerDialogTurn;
|
|
43
46
|
const fs = __importStar(require("fs"));
|
|
44
47
|
const path = __importStar(require("path"));
|
|
@@ -47,13 +50,23 @@ const core_1 = require("../heart/core");
|
|
|
47
50
|
const identity_1 = require("../heart/identity");
|
|
48
51
|
const context_1 = require("../mind/context");
|
|
49
52
|
const prompt_1 = require("../mind/prompt");
|
|
53
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
50
54
|
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
55
|
+
const pending_1 = require("../mind/pending");
|
|
56
|
+
const channel_1 = require("../mind/friends/channel");
|
|
57
|
+
const trust_gate_1 = require("./trust-gate");
|
|
58
|
+
const tokens_1 = require("../mind/friends/tokens");
|
|
59
|
+
const pipeline_1 = require("./pipeline");
|
|
51
60
|
const nerves_1 = require("../nerves");
|
|
52
61
|
const runtime_1 = require("../nerves/runtime");
|
|
62
|
+
const manager_1 = require("../heart/bridges/manager");
|
|
63
|
+
const session_activity_1 = require("../heart/session-activity");
|
|
64
|
+
const bluebubbles_1 = require("./bluebubbles");
|
|
65
|
+
const obligations_1 = require("../heart/obligations");
|
|
53
66
|
const DEFAULT_INNER_DIALOG_INSTINCTS = [
|
|
54
67
|
{
|
|
55
68
|
id: "heartbeat_checkin",
|
|
56
|
-
prompt: "
|
|
69
|
+
prompt: "...time passing. anything stirring?",
|
|
57
70
|
enabled: true,
|
|
58
71
|
},
|
|
59
72
|
];
|
|
@@ -69,18 +82,15 @@ function loadInnerDialogInstincts() {
|
|
|
69
82
|
return [...DEFAULT_INNER_DIALOG_INSTINCTS];
|
|
70
83
|
}
|
|
71
84
|
function buildInnerDialogBootstrapMessage(aspirations, stateSummary) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"",
|
|
82
|
-
"Orient yourself, decide what to do next, and make meaningful progress.",
|
|
83
|
-
].join("\n");
|
|
85
|
+
const lines = ["waking up."];
|
|
86
|
+
if (aspirations) {
|
|
87
|
+
lines.push("", "## what matters to me", aspirations);
|
|
88
|
+
}
|
|
89
|
+
if (stateSummary) {
|
|
90
|
+
lines.push("", "## what i know so far", stateSummary);
|
|
91
|
+
}
|
|
92
|
+
lines.push("", "what needs my attention?");
|
|
93
|
+
return lines.join("\n");
|
|
84
94
|
}
|
|
85
95
|
function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
|
|
86
96
|
if (nonCanonicalPaths.length === 0)
|
|
@@ -95,17 +105,49 @@ function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
|
|
|
95
105
|
...listed,
|
|
96
106
|
].join("\n");
|
|
97
107
|
}
|
|
98
|
-
function
|
|
108
|
+
function displayCheckpoint(checkpoint) {
|
|
109
|
+
const trimmed = checkpoint?.trim();
|
|
110
|
+
if (!trimmed || trimmed === "no prior checkpoint recorded") {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
return trimmed;
|
|
114
|
+
}
|
|
115
|
+
function buildInstinctUserMessage(instincts, _reason, state) {
|
|
99
116
|
const active = instincts.find((instinct) => instinct.enabled !== false) ?? DEFAULT_INNER_DIALOG_INSTINCTS[0];
|
|
100
|
-
const checkpoint = state.checkpoint
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
const checkpoint = displayCheckpoint(state.checkpoint);
|
|
118
|
+
const lines = [active.prompt];
|
|
119
|
+
if (checkpoint) {
|
|
120
|
+
lines.push(`\nlast i remember: ${checkpoint}`);
|
|
121
|
+
}
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
function readTaskFile(agentRoot, taskId) {
|
|
125
|
+
// Task files live in collection subdirectories (one-shots, ongoing, habits).
|
|
126
|
+
// Try each collection, then fall back to root tasks/ for legacy layout.
|
|
127
|
+
const collections = ["one-shots", "ongoing", "habits", ""];
|
|
128
|
+
for (const collection of collections) {
|
|
129
|
+
try {
|
|
130
|
+
return fs.readFileSync(path.join(agentRoot, "tasks", collection, `${taskId}.md`), "utf8").trim();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// not in this collection — try next
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
138
|
+
function buildTaskTriggeredMessage(taskId, taskContent, checkpoint) {
|
|
139
|
+
const lines = ["a task needs my attention."];
|
|
140
|
+
if (taskContent) {
|
|
141
|
+
lines.push("", `## task: ${taskId}`, taskContent);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
lines.push("", `## task: ${taskId}`, "(task file not found)");
|
|
145
|
+
}
|
|
146
|
+
const renderedCheckpoint = displayCheckpoint(checkpoint);
|
|
147
|
+
if (renderedCheckpoint) {
|
|
148
|
+
lines.push("", `last i remember: ${renderedCheckpoint}`);
|
|
149
|
+
}
|
|
150
|
+
return lines.join("\n");
|
|
109
151
|
}
|
|
110
152
|
function contentToText(content) {
|
|
111
153
|
if (typeof content === "string")
|
|
@@ -144,11 +186,39 @@ function deriveResumeCheckpoint(messages) {
|
|
|
144
186
|
const firstLine = assistantText
|
|
145
187
|
.split("\n")
|
|
146
188
|
.map((line) => line.trim())
|
|
147
|
-
.
|
|
189
|
+
.find((line) => line.length > 0);
|
|
190
|
+
/* v8 ignore next -- unreachable: contentToText().trim() guarantees a non-empty line @preserve */
|
|
191
|
+
if (!firstLine)
|
|
192
|
+
return "no prior checkpoint recorded";
|
|
148
193
|
if (firstLine.length <= 220)
|
|
149
194
|
return firstLine;
|
|
150
195
|
return `${firstLine.slice(0, 217)}...`;
|
|
151
196
|
}
|
|
197
|
+
function extractAssistantPreview(messages, maxLength = 120) {
|
|
198
|
+
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
|
|
199
|
+
if (!lastAssistant)
|
|
200
|
+
return "";
|
|
201
|
+
const text = contentToText(lastAssistant.content);
|
|
202
|
+
if (!text)
|
|
203
|
+
return "";
|
|
204
|
+
/* v8 ignore next -- unreachable: contentToText().trim() guarantees a non-empty line @preserve */
|
|
205
|
+
const firstLine = text.split("\n").find((line) => line.trim().length > 0) ?? "";
|
|
206
|
+
if (firstLine.length <= maxLength)
|
|
207
|
+
return firstLine;
|
|
208
|
+
return `${firstLine.slice(0, maxLength - 3)}...`;
|
|
209
|
+
}
|
|
210
|
+
function extractToolCallNames(messages) {
|
|
211
|
+
const names = [];
|
|
212
|
+
for (const msg of messages) {
|
|
213
|
+
if (msg.role === "assistant" && "tool_calls" in msg && Array.isArray(msg.tool_calls)) {
|
|
214
|
+
for (const tc of msg.tool_calls) {
|
|
215
|
+
if ("function" in tc && tc.function?.name)
|
|
216
|
+
names.push(tc.function.name);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return [...new Set(names)];
|
|
221
|
+
}
|
|
152
222
|
function createInnerDialogCallbacks() {
|
|
153
223
|
return {
|
|
154
224
|
onModelStart: () => { },
|
|
@@ -161,71 +231,306 @@ function createInnerDialogCallbacks() {
|
|
|
161
231
|
};
|
|
162
232
|
}
|
|
163
233
|
function innerDialogSessionPath() {
|
|
164
|
-
return (0, config_1.sessionPath)(
|
|
234
|
+
return (0, config_1.sessionPath)(pending_1.INNER_DIALOG_PENDING.friendId, pending_1.INNER_DIALOG_PENDING.channel, pending_1.INNER_DIALOG_PENDING.key);
|
|
165
235
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
cycleCount: 1,
|
|
175
|
-
resting: false,
|
|
176
|
-
lastHeartbeatAt: now().toISOString(),
|
|
177
|
-
};
|
|
178
|
-
if (messages.length === 0) {
|
|
179
|
-
const systemPrompt = await (0, prompt_1.buildSystem)("cli", { toolChoiceRequired: true });
|
|
180
|
-
messages.push({ role: "system", content: systemPrompt });
|
|
181
|
-
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
182
|
-
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
183
|
-
const cleanupNudge = buildNonCanonicalCleanupNudge(nonCanonical);
|
|
184
|
-
const bootstrapMessage = [
|
|
185
|
-
buildInnerDialogBootstrapMessage(aspirations, "No prior inner dialog session found."),
|
|
186
|
-
cleanupNudge,
|
|
187
|
-
].filter(Boolean).join("\n\n");
|
|
188
|
-
messages.push({ role: "user", content: bootstrapMessage });
|
|
236
|
+
function innerDialogRuntimeStatePath(sessionFilePath) {
|
|
237
|
+
return path.join(path.dirname(sessionFilePath), "runtime.json");
|
|
238
|
+
}
|
|
239
|
+
function writeInnerDialogRuntimeState(sessionFilePath, state) {
|
|
240
|
+
const filePath = innerDialogRuntimeStatePath(sessionFilePath);
|
|
241
|
+
try {
|
|
242
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
243
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
189
244
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
catch (error) {
|
|
246
|
+
(0, runtime_1.emitNervesEvent)({
|
|
247
|
+
level: "warn",
|
|
248
|
+
component: "senses",
|
|
249
|
+
event: "senses.inner_dialog_runtime_state_error",
|
|
250
|
+
message: "failed to write inner dialog runtime state",
|
|
251
|
+
meta: {
|
|
252
|
+
status: state.status,
|
|
253
|
+
reason: state.reason ?? null,
|
|
254
|
+
path: filePath,
|
|
255
|
+
/* v8 ignore next -- Node fs APIs throw Error objects for mkdirSync/writeFileSync failures @preserve */
|
|
256
|
+
error: error instanceof Error ? error.message : String(error),
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function writePendingEnvelope(pendingDir, message) {
|
|
262
|
+
fs.mkdirSync(pendingDir, { recursive: true });
|
|
263
|
+
const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
264
|
+
const filePath = path.join(pendingDir, fileName);
|
|
265
|
+
fs.writeFileSync(filePath, JSON.stringify(message, null, 2), "utf8");
|
|
266
|
+
}
|
|
267
|
+
function sessionMatchesActivity(activity, session) {
|
|
268
|
+
return activity.friendId === session.friendId
|
|
269
|
+
&& activity.channel === session.channel
|
|
270
|
+
&& activity.key === session.key;
|
|
271
|
+
}
|
|
272
|
+
function resolveBridgePreferredSession(delegatedFrom, sessionActivity) {
|
|
273
|
+
if (!delegatedFrom.bridgeId)
|
|
274
|
+
return null;
|
|
275
|
+
const bridge = (0, manager_1.createBridgeManager)().getBridge(delegatedFrom.bridgeId);
|
|
276
|
+
if (!bridge || bridge.lifecycle === "completed" || bridge.lifecycle === "cancelled") {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
return sessionActivity.find((activity) => activity.friendId === delegatedFrom.friendId
|
|
280
|
+
&& activity.channel !== "inner"
|
|
281
|
+
&& bridge.attachedSessions.some((session) => sessionMatchesActivity(activity, session))) ?? null;
|
|
282
|
+
}
|
|
283
|
+
async function tryDeliverDelegatedCompletion(target, outboundEnvelope) {
|
|
284
|
+
if (target.channel !== "bluebubbles") {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const result = await (0, bluebubbles_1.sendProactiveBlueBubblesMessageToSession)({
|
|
288
|
+
friendId: target.friendId,
|
|
289
|
+
sessionKey: target.key,
|
|
290
|
+
text: outboundEnvelope.content,
|
|
291
|
+
});
|
|
292
|
+
return result.delivered;
|
|
293
|
+
}
|
|
294
|
+
function enrichDelegatedFromWithBridge(delegatedFrom) {
|
|
295
|
+
if (delegatedFrom.bridgeId) {
|
|
296
|
+
return delegatedFrom;
|
|
297
|
+
}
|
|
298
|
+
const bridgeManager = (0, manager_1.createBridgeManager)();
|
|
299
|
+
const originBridges = bridgeManager.findBridgesForSession({
|
|
300
|
+
friendId: delegatedFrom.friendId,
|
|
301
|
+
channel: delegatedFrom.channel,
|
|
302
|
+
key: delegatedFrom.key,
|
|
303
|
+
});
|
|
304
|
+
const activeBridge = originBridges.find((b) => b.lifecycle === "active");
|
|
305
|
+
if (activeBridge) {
|
|
306
|
+
return { ...delegatedFrom, bridgeId: activeBridge.id };
|
|
307
|
+
}
|
|
308
|
+
return delegatedFrom;
|
|
309
|
+
}
|
|
310
|
+
async function routeDelegatedCompletion(agentRoot, agentName, completion, drainedPending, timestamp) {
|
|
311
|
+
const delegated = (drainedPending ?? []).find((message) => message.delegatedFrom);
|
|
312
|
+
if (!delegated?.delegatedFrom || !completion?.answer?.trim()) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const delegatedFrom = enrichDelegatedFromWithBridge(delegated.delegatedFrom);
|
|
316
|
+
if (delegated.obligationStatus === "pending") {
|
|
317
|
+
// Fulfill the persistent obligation in the store
|
|
318
|
+
try {
|
|
319
|
+
const pending = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, {
|
|
320
|
+
friendId: delegatedFrom.friendId,
|
|
321
|
+
channel: delegatedFrom.channel,
|
|
322
|
+
key: delegatedFrom.key,
|
|
323
|
+
});
|
|
324
|
+
/* v8 ignore next 2 -- obligation fulfillment tested via obligations.test.ts; integration requires real disk state @preserve */
|
|
325
|
+
if (pending) {
|
|
326
|
+
(0, obligations_1.fulfillObligation)(agentRoot, pending.id);
|
|
327
|
+
}
|
|
210
328
|
}
|
|
329
|
+
catch {
|
|
330
|
+
/* v8 ignore next -- defensive: obligation store read failure should not break delivery @preserve */
|
|
331
|
+
}
|
|
332
|
+
(0, runtime_1.emitNervesEvent)({
|
|
333
|
+
event: "senses.obligation_fulfilled",
|
|
334
|
+
component: "senses",
|
|
335
|
+
message: "obligation fulfilled via delegated completion",
|
|
336
|
+
meta: {
|
|
337
|
+
friendId: delegatedFrom.friendId,
|
|
338
|
+
channel: delegatedFrom.channel,
|
|
339
|
+
key: delegatedFrom.key,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
211
342
|
}
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
343
|
+
const outboundEnvelope = {
|
|
344
|
+
from: agentName,
|
|
345
|
+
friendId: delegatedFrom.friendId,
|
|
346
|
+
channel: delegatedFrom.channel,
|
|
347
|
+
key: delegatedFrom.key,
|
|
348
|
+
content: completion.answer.trim(),
|
|
349
|
+
timestamp,
|
|
350
|
+
delegatedFrom,
|
|
351
|
+
};
|
|
352
|
+
const sessionActivity = (0, session_activity_1.listSessionActivity)({
|
|
353
|
+
sessionsDir: path.join(agentRoot, "state", "sessions"),
|
|
354
|
+
friendsDir: path.join(agentRoot, "friends"),
|
|
355
|
+
agentName,
|
|
218
356
|
});
|
|
219
|
-
|
|
220
|
-
(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
357
|
+
const bridgeTarget = resolveBridgePreferredSession(delegatedFrom, sessionActivity);
|
|
358
|
+
if (bridgeTarget) {
|
|
359
|
+
if (await tryDeliverDelegatedCompletion(bridgeTarget, outboundEnvelope)) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
writePendingEnvelope((0, pending_1.getPendingDir)(agentName, bridgeTarget.friendId, bridgeTarget.channel, bridgeTarget.key), outboundEnvelope);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const freshest = (0, session_activity_1.findFreshestFriendSession)({
|
|
366
|
+
sessionsDir: path.join(agentRoot, "state", "sessions"),
|
|
367
|
+
friendsDir: path.join(agentRoot, "friends"),
|
|
368
|
+
agentName,
|
|
369
|
+
friendId: delegatedFrom.friendId,
|
|
370
|
+
activeOnly: true,
|
|
225
371
|
});
|
|
372
|
+
if (freshest && freshest.channel !== "inner") {
|
|
373
|
+
if (await tryDeliverDelegatedCompletion(freshest, outboundEnvelope)) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
writePendingEnvelope((0, pending_1.getPendingDir)(agentName, freshest.friendId, freshest.channel, freshest.key), outboundEnvelope);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
writePendingEnvelope((0, pending_1.getDeferredReturnDir)(agentName, delegatedFrom.friendId), outboundEnvelope);
|
|
380
|
+
}
|
|
381
|
+
// Self-referencing friend record for inner dialog (agent talking to itself).
|
|
382
|
+
// No real friend to resolve -- this satisfies the pipeline's friend resolver contract.
|
|
383
|
+
function createSelfFriend(agentName) {
|
|
226
384
|
return {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
385
|
+
id: "self",
|
|
386
|
+
name: agentName,
|
|
387
|
+
trustLevel: "family",
|
|
388
|
+
externalIds: [],
|
|
389
|
+
tenantMemberships: [],
|
|
390
|
+
toolPreferences: {},
|
|
391
|
+
notes: {},
|
|
392
|
+
totalTokens: 0,
|
|
393
|
+
createdAt: new Date().toISOString(),
|
|
394
|
+
updatedAt: new Date().toISOString(),
|
|
395
|
+
schemaVersion: 1,
|
|
230
396
|
};
|
|
231
397
|
}
|
|
398
|
+
// No-op friend store for inner dialog. Inner dialog doesn't track token usage per-friend.
|
|
399
|
+
function createNoOpFriendStore() {
|
|
400
|
+
return {
|
|
401
|
+
get: async () => null,
|
|
402
|
+
put: async () => { },
|
|
403
|
+
delete: async () => { },
|
|
404
|
+
findByExternalId: async () => null,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
async function runInnerDialogTurn(options) {
|
|
408
|
+
const now = options?.now ?? (() => new Date());
|
|
409
|
+
const reason = options?.reason ?? "heartbeat";
|
|
410
|
+
const sessionFilePath = innerDialogSessionPath();
|
|
411
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
412
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
413
|
+
writeInnerDialogRuntimeState(sessionFilePath, {
|
|
414
|
+
status: "running",
|
|
415
|
+
reason,
|
|
416
|
+
startedAt: now().toISOString(),
|
|
417
|
+
});
|
|
418
|
+
try {
|
|
419
|
+
const loaded = (0, context_1.loadSession)(sessionFilePath);
|
|
420
|
+
const existingMessages = loaded?.messages ? [...loaded.messages] : [];
|
|
421
|
+
const instincts = options?.instincts ?? loadInnerDialogInstincts();
|
|
422
|
+
const state = {
|
|
423
|
+
cycleCount: 1,
|
|
424
|
+
resting: false,
|
|
425
|
+
lastHeartbeatAt: now().toISOString(),
|
|
426
|
+
};
|
|
427
|
+
// ── Adapter concern: build user message ──────────────────────────
|
|
428
|
+
let userContent;
|
|
429
|
+
if (existingMessages.length === 0) {
|
|
430
|
+
// Fresh session: bootstrap message with non-canonical cleanup nudge
|
|
431
|
+
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
432
|
+
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
433
|
+
const cleanupNudge = buildNonCanonicalCleanupNudge(nonCanonical);
|
|
434
|
+
userContent = [
|
|
435
|
+
buildInnerDialogBootstrapMessage(aspirations, "No prior inner dialog session found."),
|
|
436
|
+
cleanupNudge,
|
|
437
|
+
].filter(Boolean).join("\n\n");
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
// Resumed session: task-triggered or instinct message with checkpoint context
|
|
441
|
+
const assistantTurns = existingMessages.filter((message) => message.role === "assistant").length;
|
|
442
|
+
state.cycleCount = assistantTurns + 1;
|
|
443
|
+
state.checkpoint = deriveResumeCheckpoint(existingMessages);
|
|
444
|
+
if (options?.taskId) {
|
|
445
|
+
const taskContent = readTaskFile((0, identity_1.getAgentRoot)(), options.taskId);
|
|
446
|
+
userContent = buildTaskTriggeredMessage(options.taskId, taskContent, state.checkpoint);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
userContent = buildInstinctUserMessage(instincts, reason, state);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const userMessage = { role: "user", content: userContent };
|
|
453
|
+
// ── Session loader: wraps existing session logic ──────────────────
|
|
454
|
+
const innerCapabilities = (0, channel_1.getChannelCapabilities)("inner");
|
|
455
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)(agentName);
|
|
456
|
+
const selfFriend = createSelfFriend(agentName);
|
|
457
|
+
const selfContext = { friend: selfFriend, channel: innerCapabilities };
|
|
458
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
459
|
+
const sessionLoader = {
|
|
460
|
+
loadOrCreate: async () => {
|
|
461
|
+
if (existingMessages.length > 0) {
|
|
462
|
+
return {
|
|
463
|
+
messages: existingMessages,
|
|
464
|
+
sessionPath: sessionFilePath,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
// Fresh session: build system prompt
|
|
468
|
+
const systemPrompt = await (0, prompt_1.buildSystem)("inner", { toolChoiceRequired: true, mcpManager });
|
|
469
|
+
return {
|
|
470
|
+
messages: [{ role: "system", content: systemPrompt }],
|
|
471
|
+
sessionPath: sessionFilePath,
|
|
472
|
+
};
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
// ── Call shared pipeline ──────────────────────────────────────────
|
|
476
|
+
const callbacks = createInnerDialogCallbacks();
|
|
477
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
478
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
479
|
+
channel: "inner",
|
|
480
|
+
sessionKey: "dialog",
|
|
481
|
+
capabilities: innerCapabilities,
|
|
482
|
+
messages: [userMessage],
|
|
483
|
+
continuityIngressTexts: [],
|
|
484
|
+
callbacks,
|
|
485
|
+
friendResolver: { resolve: () => Promise.resolve(selfContext) },
|
|
486
|
+
sessionLoader,
|
|
487
|
+
pendingDir,
|
|
488
|
+
friendStore: createNoOpFriendStore(),
|
|
489
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
490
|
+
drainPending: pending_1.drainPending,
|
|
491
|
+
runAgent: core_1.runAgent,
|
|
492
|
+
postTurn: context_1.postTurn,
|
|
493
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
494
|
+
signal: options?.signal,
|
|
495
|
+
runAgentOptions: {
|
|
496
|
+
traceId,
|
|
497
|
+
toolChoiceRequired: true,
|
|
498
|
+
skipConfirmation: true,
|
|
499
|
+
mcpManager,
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
await routeDelegatedCompletion(agentRoot, agentName, result.completion, result.drainedPending, now().getTime());
|
|
503
|
+
const resultMessages = result.messages ?? [];
|
|
504
|
+
const assistantPreview = extractAssistantPreview(resultMessages);
|
|
505
|
+
const toolCalls = extractToolCallNames(resultMessages);
|
|
506
|
+
(0, runtime_1.emitNervesEvent)({
|
|
507
|
+
component: "senses",
|
|
508
|
+
event: "senses.inner_dialog_turn",
|
|
509
|
+
message: "inner dialog turn completed",
|
|
510
|
+
meta: {
|
|
511
|
+
reason,
|
|
512
|
+
session: sessionFilePath,
|
|
513
|
+
...(options?.taskId && { taskId: options.taskId }),
|
|
514
|
+
...(assistantPreview && { assistantPreview }),
|
|
515
|
+
...(toolCalls.length > 0 && { toolCalls }),
|
|
516
|
+
...(result.usage && {
|
|
517
|
+
promptTokens: result.usage.input_tokens,
|
|
518
|
+
completionTokens: result.usage.output_tokens,
|
|
519
|
+
totalTokens: result.usage.total_tokens,
|
|
520
|
+
}),
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
return {
|
|
524
|
+
messages: resultMessages,
|
|
525
|
+
usage: result.usage,
|
|
526
|
+
sessionPath: result.sessionPath ?? sessionFilePath,
|
|
527
|
+
completion: result.completion,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
writeInnerDialogRuntimeState(sessionFilePath, {
|
|
532
|
+
status: "idle",
|
|
533
|
+
lastCompletedAt: now().toISOString(),
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|