@ouro.bot/cli 0.1.0-alpha.6 → 0.1.0-alpha.61

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