@ouro.bot/cli 0.1.0-alpha.202 → 0.1.0-alpha.204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.json CHANGED
@@ -1,6 +1,18 @@
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.204",
6
+ "changes": [
7
+ "refactor: introduce TurnContext snapshot — centralize state assembly from pipeline.ts into buildTurnContext(), thread pre-read state through prompt assembly to eliminate ad-hoc filesystem reads"
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.203",
12
+ "changes": [
13
+ "refactor: unify obligation systems — mind/obligations.ts merged into heart/obligations.ts with prefixed ReturnObligation API"
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.202",
6
18
  "changes": [
@@ -3116,9 +3116,9 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3116
3116
  if (command.kind === "attention.list" || command.kind === "attention.show" || command.kind === "attention.history") {
3117
3117
  try {
3118
3118
  const agentName = command.agent ?? (0, identity_1.getAgentName)();
3119
- const { listActiveObligations, readObligation } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
3119
+ const { listActiveReturnObligations, readReturnObligation } = await Promise.resolve().then(() => __importStar(require("../obligations")));
3120
3120
  if (command.kind === "attention.list") {
3121
- const obligations = listActiveObligations(agentName);
3121
+ const obligations = listActiveReturnObligations(agentName);
3122
3122
  if (obligations.length === 0) {
3123
3123
  const message = "nothing held — attention queue is empty";
3124
3124
  deps.writeStdout(message);
@@ -3130,7 +3130,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3130
3130
  return message;
3131
3131
  }
3132
3132
  if (command.kind === "attention.show") {
3133
- const obligation = readObligation(agentName, command.id);
3133
+ const obligation = readReturnObligation(agentName, command.id);
3134
3134
  if (!obligation) {
3135
3135
  const message = `no obligation found with id ${command.id}`;
3136
3136
  deps.writeStdout(message);
@@ -3141,8 +3141,8 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3141
3141
  return message;
3142
3142
  }
3143
3143
  // attention.history: show returned obligations
3144
- const { getObligationsDir } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
3145
- const obligationsDir = getObligationsDir(agentName);
3144
+ const { getReturnObligationsDir } = await Promise.resolve().then(() => __importStar(require("../obligations")));
3145
+ const obligationsDir = getReturnObligationsDir(agentName);
3146
3146
  let entries = [];
3147
3147
  try {
3148
3148
  entries = fs.readdirSync(obligationsDir);
@@ -3189,7 +3189,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3189
3189
  const { sessionPath: getSessionPath } = await Promise.resolve().then(() => __importStar(require("../config")));
3190
3190
  const { parseCadenceToMs: parseCadenceMs, DEFAULT_CADENCE_MS } = await Promise.resolve().then(() => __importStar(require("./cadence")));
3191
3191
  const { parseFrontmatter } = await Promise.resolve().then(() => __importStar(require("../../repertoire/tasks/parser")));
3192
- const { listActiveObligations } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
3192
+ const { listActiveReturnObligations } = await Promise.resolve().then(() => __importStar(require("../obligations")));
3193
3193
  // Read runtime state
3194
3194
  const innerSessionPath = getSessionPath("inner-dialog", "inner", "session");
3195
3195
  const runtimeJsonPath = path.join(path.dirname(innerSessionPath), "runtime.json");
@@ -3242,7 +3242,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3242
3242
  }
3243
3243
  catch { /* no habits — heartbeat unknown */ }
3244
3244
  // Attention count
3245
- const activeObligations = listActiveObligations(agentName);
3245
+ const activeObligations = listActiveReturnObligations(agentName);
3246
3246
  const message = buildInnerStatusOutput({
3247
3247
  agentName,
3248
3248
  runtimeState,
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.OuroDaemon = void 0;
37
37
  exports.killOrphanProcesses = killOrphanProcesses;
38
38
  exports.writePidfile = writePidfile;
39
+ exports.handleAgentSenseTurn = handleAgentSenseTurn;
39
40
  const fs = __importStar(require("fs"));
40
41
  const net = __importStar(require("net"));
41
42
  const os = __importStar(require("os"));
@@ -177,6 +178,33 @@ function parseIncomingCommand(raw) {
177
178
  }
178
179
  return parsed;
179
180
  }
181
+ /**
182
+ * Handle agent.senseTurn command: runs a full agent turn via the daemon process.
183
+ * Dynamic import lazy-loads shared-turn. Hot-reload works because ouro dev
184
+ * restarts the daemon process (fresh module cache).
185
+ */
186
+ async function handleAgentSenseTurn(command) {
187
+ try {
188
+ const { runSenseTurn } = await Promise.resolve().then(() => __importStar(require("../../senses/shared-turn")));
189
+ const result = await runSenseTurn({
190
+ agentName: command.agent,
191
+ channel: command.channel,
192
+ sessionKey: command.sessionKey,
193
+ friendId: command.friendId,
194
+ userMessage: command.message,
195
+ });
196
+ return {
197
+ ok: true,
198
+ message: result.response,
199
+ data: { ponderDeferred: result.ponderDeferred },
200
+ };
201
+ }
202
+ catch (error) {
203
+ /* v8 ignore next -- branch: String(error) fallback only for non-Error throws @preserve */
204
+ const errorMessage = error instanceof Error ? error.message : String(error);
205
+ return { ok: false, error: `sense turn failed: ${errorMessage}` };
206
+ }
207
+ }
180
208
  class OuroDaemon {
181
209
  socketPath;
182
210
  processManager;
@@ -683,6 +711,9 @@ class OuroDaemon {
683
711
  return (0, agent_service_1.handleAgentReportBlocker)(command);
684
712
  case "agent.reportComplete":
685
713
  return (0, agent_service_1.handleAgentReportComplete)(command);
714
+ case "agent.senseTurn":
715
+ return handleAgentSenseTurn(command);
716
+ /* v8 ignore stop */
686
717
  case "cron.list": {
687
718
  const jobs = this.scheduler.listJobs();
688
719
  const summary = jobs.length === 0
@@ -38,7 +38,6 @@ exports.getToolSchemas = getToolSchemas;
38
38
  const socket_client_1 = require("./socket-client");
39
39
  const agentService = __importStar(require("./agent-service"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
- const shared_turn_1 = require("../../senses/shared-turn");
42
41
  const session_id_resolver_1 = require("./session-id-resolver");
43
42
  const pending_1 = require("../../mind/pending");
44
43
  /**
@@ -249,19 +248,22 @@ function createMcpServer(options) {
249
248
  const message = toolArgs.message ?? "";
250
249
  /* v8 ignore stop */
251
250
  try {
252
- const result = await (0, shared_turn_1.runSenseTurn)({
253
- agentName: agent,
251
+ const response = await (0, socket_client_1.sendDaemonCommand)(socketPath, {
252
+ kind: "agent.senseTurn",
253
+ agent,
254
+ friendId,
254
255
  channel: "mcp",
255
256
  sessionKey: sessionId,
256
- friendId,
257
- userMessage: message,
257
+ message,
258
258
  });
259
+ /* v8 ignore next -- branch: ?? fallback for empty daemon response @preserve */
260
+ const text = response.message ?? "(empty response)";
259
261
  writeResponse({
260
262
  jsonrpc: "2.0",
261
263
  id: request.id,
262
264
  result: {
263
- content: [{ type: "text", text: result.response }],
264
- isError: false,
265
+ content: [{ type: "text", text }],
266
+ isError: !response.ok,
265
267
  },
266
268
  });
267
269
  }
@@ -269,11 +271,17 @@ function createMcpServer(options) {
269
271
  /* v8 ignore start — instanceof guard defensive; thrown errors are always Error */
270
272
  const errorMessage = error instanceof Error ? error.message : String(error);
271
273
  /* v8 ignore stop */
274
+ /* v8 ignore start -- daemon-down detection: only triggers with real socket I/O @preserve */
275
+ const isDaemonDown = errorMessage.includes("ECONNREFUSED") || errorMessage.includes("ENOENT");
276
+ const userMessage = isDaemonDown
277
+ ? "The daemon is not running. Start it with `ouro up` (production) or `ouro dev` (development), then retry."
278
+ : `Error: ${errorMessage}`;
279
+ /* v8 ignore stop */
272
280
  writeResponse({
273
281
  jsonrpc: "2.0",
274
282
  id: request.id,
275
283
  result: {
276
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
284
+ content: [{ type: "text", text: userMessage }],
277
285
  isError: true,
278
286
  },
279
287
  });
@@ -306,7 +314,7 @@ function createMcpServer(options) {
306
314
  }
307
315
  return;
308
316
  }
309
- // ── delegate: full conversation turn via runSenseTurn ──
317
+ // ── delegate: full conversation turn via daemon ──
310
318
  if (toolName === "delegate") {
311
319
  /* v8 ignore start — ?? fallback defensive; MCP clients always send task */
312
320
  const task = toolArgs.task ?? "";
@@ -314,19 +322,22 @@ function createMcpServer(options) {
314
322
  const context = toolArgs.context;
315
323
  const delegateMessage = context ? `[delegate] ${task}\n\ncontext: ${context}` : `[delegate] ${task}`;
316
324
  try {
317
- const result = await (0, shared_turn_1.runSenseTurn)({
318
- agentName: agent,
325
+ const response = await (0, socket_client_1.sendDaemonCommand)(socketPath, {
326
+ kind: "agent.senseTurn",
327
+ agent,
328
+ friendId,
319
329
  channel: "mcp",
320
330
  sessionKey: sessionId,
321
- friendId,
322
- userMessage: delegateMessage,
331
+ message: delegateMessage,
323
332
  });
333
+ /* v8 ignore next -- branch: ?? fallback for empty daemon response @preserve */
334
+ const text = response.message ?? "(empty response)";
324
335
  writeResponse({
325
336
  jsonrpc: "2.0",
326
337
  id: request.id,
327
338
  result: {
328
- content: [{ type: "text", text: result.response }],
329
- isError: false,
339
+ content: [{ type: "text", text }],
340
+ isError: !response.ok,
330
341
  },
331
342
  });
332
343
  }
@@ -334,11 +345,17 @@ function createMcpServer(options) {
334
345
  /* v8 ignore start — instanceof guard defensive; thrown errors are always Error */
335
346
  const errorMessage = error instanceof Error ? error.message : String(error);
336
347
  /* v8 ignore stop */
348
+ /* v8 ignore start -- daemon-down detection: only triggers with real socket I/O @preserve */
349
+ const isDaemonDown = errorMessage.includes("ECONNREFUSED") || errorMessage.includes("ENOENT");
350
+ const userMessage = isDaemonDown
351
+ ? "The daemon is not running. Start it with `ouro up` (production) or `ouro dev` (development), then retry."
352
+ : `Error: ${errorMessage}`;
353
+ /* v8 ignore stop */
337
354
  writeResponse({
338
355
  jsonrpc: "2.0",
339
356
  id: request.id,
340
357
  result: {
341
- content: [{ type: "text", text: `Error: ${errorMessage}` }],
358
+ content: [{ type: "text", text: userMessage }],
342
359
  isError: true,
343
360
  },
344
361
  });
@@ -42,8 +42,15 @@ exports.advanceObligation = advanceObligation;
42
42
  exports.fulfillObligation = fulfillObligation;
43
43
  exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
44
44
  exports.enrichObligation = enrichObligation;
45
+ exports.generateObligationId = generateObligationId;
46
+ exports.getReturnObligationsDir = getReturnObligationsDir;
47
+ exports.createReturnObligation = createReturnObligation;
48
+ exports.readReturnObligation = readReturnObligation;
49
+ exports.advanceReturnObligation = advanceReturnObligation;
50
+ exports.listActiveReturnObligations = listActiveReturnObligations;
45
51
  const fs = __importStar(require("fs"));
46
52
  const path = __importStar(require("path"));
53
+ const identity_1 = require("./identity");
47
54
  const runtime_1 = require("../nerves/runtime");
48
55
  const sync_1 = require("./sync");
49
56
  function obligationsDir(agentRoot) {
@@ -223,3 +230,92 @@ function enrichObligation(agentRoot, id, meaning) {
223
230
  });
224
231
  return obligation;
225
232
  }
233
+ function generateObligationId(timestamp) {
234
+ return `${timestamp}-${Math.random().toString(36).slice(2, 10)}`;
235
+ }
236
+ function getReturnObligationsDir(agentName) {
237
+ return path.join((0, identity_1.getAgentRoot)(agentName), "arc", "obligations", "inner");
238
+ }
239
+ function createReturnObligation(agentName, obligation) {
240
+ const dir = getReturnObligationsDir(agentName);
241
+ fs.mkdirSync(dir, { recursive: true });
242
+ const filePath = path.join(dir, `${obligation.id}.json`);
243
+ fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf8");
244
+ (0, sync_1.trackSyncWrite)(filePath);
245
+ (0, runtime_1.emitNervesEvent)({
246
+ event: "mind.obligation_created",
247
+ component: "mind",
248
+ message: "return obligation created",
249
+ meta: {
250
+ obligationId: obligation.id,
251
+ origin: `${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}`,
252
+ status: obligation.status,
253
+ },
254
+ });
255
+ return filePath;
256
+ }
257
+ function readReturnObligation(agentName, obligationId) {
258
+ const dir = getReturnObligationsDir(agentName);
259
+ try {
260
+ const raw = fs.readFileSync(path.join(dir, `${obligationId}.json`), "utf-8");
261
+ return JSON.parse(raw);
262
+ }
263
+ catch {
264
+ return null;
265
+ }
266
+ }
267
+ function advanceReturnObligation(agentName, obligationId, update) {
268
+ const existing = readReturnObligation(agentName, obligationId);
269
+ if (!existing)
270
+ return null;
271
+ const updated = {
272
+ ...existing,
273
+ status: update.status,
274
+ ...(update.startedAt !== undefined ? { startedAt: update.startedAt } : {}),
275
+ ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
276
+ ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
277
+ };
278
+ const dir = getReturnObligationsDir(agentName);
279
+ const advancedFilePath = path.join(dir, `${obligationId}.json`);
280
+ fs.writeFileSync(advancedFilePath, JSON.stringify(updated, null, 2), "utf8");
281
+ (0, sync_1.trackSyncWrite)(advancedFilePath);
282
+ (0, runtime_1.emitNervesEvent)({
283
+ event: "mind.obligation_advanced",
284
+ component: "mind",
285
+ message: `obligation advanced to ${update.status}`,
286
+ meta: {
287
+ obligationId,
288
+ status: update.status,
289
+ ...(update.returnTarget ? { returnTarget: update.returnTarget } : {}),
290
+ },
291
+ });
292
+ return updated;
293
+ }
294
+ function listActiveReturnObligations(agentName) {
295
+ const dir = getReturnObligationsDir(agentName);
296
+ if (!fs.existsSync(dir))
297
+ return [];
298
+ let entries;
299
+ try {
300
+ entries = fs.readdirSync(dir);
301
+ }
302
+ catch {
303
+ return [];
304
+ }
305
+ const obligations = [];
306
+ for (const file of entries) {
307
+ if (!file.endsWith(".json"))
308
+ continue;
309
+ try {
310
+ const raw = fs.readFileSync(path.join(dir, file), "utf-8");
311
+ const parsed = JSON.parse(raw);
312
+ if (parsed.status === "queued" || parsed.status === "running") {
313
+ obligations.push(parsed);
314
+ }
315
+ }
316
+ catch {
317
+ // skip unparseable
318
+ }
319
+ }
320
+ return obligations.sort((a, b) => a.createdAt - b.createdAt);
321
+ }
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ /**
3
+ * TurnContext: a single pre-read snapshot of all state sources needed
4
+ * by the inbound-turn pipeline and prompt assembly.
5
+ *
6
+ * Before this module existed, pipeline.ts read ~10 state sources inline
7
+ * and prompt.ts did several ad-hoc filesystem reads. This centralizes
8
+ * all reads into one place so the rest of the turn is pure derivation.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.buildTurnContext = buildTurnContext;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const runtime_1 = require("../nerves/runtime");
48
+ const manager_1 = require("./bridges/manager");
49
+ const identity_1 = require("./identity");
50
+ const tasks_1 = require("../repertoire/tasks");
51
+ const coding_1 = require("../repertoire/coding");
52
+ const session_activity_1 = require("./session-activity");
53
+ const thoughts_1 = require("./daemon/thoughts");
54
+ const pending_1 = require("../mind/pending");
55
+ const obligations_1 = require("./obligations");
56
+ const target_resolution_1 = require("./target-resolution");
57
+ const episodes_1 = require("../mind/episodes");
58
+ const cares_1 = require("./cares");
59
+ const config_1 = require("./config");
60
+ const daemon_health_1 = require("./daemon/daemon-health");
61
+ const prompt_1 = require("../mind/prompt");
62
+ // ── Helpers ─────────────────────────────────────────────────────────
63
+ const DAEMON_SOCKET_PATH = "/tmp/ouroboros-daemon.sock";
64
+ function isLiveCodingSessionStatus(status) {
65
+ return status === "spawning"
66
+ || status === "running"
67
+ || status === "waiting_input"
68
+ || status === "stalled";
69
+ }
70
+ function emptyTaskBoard() {
71
+ return {
72
+ compact: "",
73
+ full: "",
74
+ byStatus: {
75
+ drafting: [],
76
+ processing: [],
77
+ validating: [],
78
+ collaborating: [],
79
+ paused: [],
80
+ blocked: [],
81
+ done: [],
82
+ cancelled: [],
83
+ },
84
+ issues: [],
85
+ actionRequired: [],
86
+ unresolvedDependencies: [],
87
+ activeSessions: [],
88
+ activeBridges: [],
89
+ };
90
+ }
91
+ function readInnerWorkState() {
92
+ const defaultJob = {
93
+ status: "idle",
94
+ content: null,
95
+ origin: null,
96
+ mode: "reflect",
97
+ obligationStatus: null,
98
+ surfacedResult: null,
99
+ queuedAt: null,
100
+ startedAt: null,
101
+ surfacedAt: null,
102
+ };
103
+ try {
104
+ const agentRoot = (0, identity_1.getAgentRoot)();
105
+ const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
106
+ const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
107
+ const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
108
+ const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
109
+ const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
110
+ const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
111
+ return {
112
+ status: dialogStatus.processing === "started" ? "running" : "idle",
113
+ hasPending: dialogStatus.queue !== "clear",
114
+ origin: dialogStatus.origin,
115
+ contentSnippet: dialogStatus.contentSnippet,
116
+ obligationPending: dialogStatus.obligationPending || storeObligationPending,
117
+ job,
118
+ };
119
+ }
120
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
121
+ return {
122
+ status: "idle",
123
+ hasPending: false,
124
+ job: defaultJob,
125
+ /* v8 ignore stop */
126
+ };
127
+ }
128
+ }
129
+ function checkDaemonRunning() {
130
+ try {
131
+ return fs.existsSync(DAEMON_SOCKET_PATH);
132
+ }
133
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
134
+ return false;
135
+ /* v8 ignore stop */
136
+ }
137
+ }
138
+ function hasTextField(record, key) {
139
+ return typeof record?.[key] === "string" && record[key].trim().length > 0;
140
+ }
141
+ function readSenseStatusLines() {
142
+ const config = (0, identity_1.loadAgentConfig)();
143
+ const senses = config.senses ?? {
144
+ cli: { enabled: true },
145
+ teams: { enabled: false },
146
+ bluebubbles: { enabled: false },
147
+ };
148
+ let payload = {};
149
+ try {
150
+ const raw = fs.readFileSync((0, identity_1.getAgentSecretsPath)(), "utf-8");
151
+ const parsed = JSON.parse(raw);
152
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
153
+ payload = parsed;
154
+ }
155
+ }
156
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
157
+ payload = {};
158
+ /* v8 ignore stop */
159
+ }
160
+ const teams = payload.teams;
161
+ const bluebubbles = payload.bluebubbles;
162
+ const configured = {
163
+ cli: true,
164
+ teams: hasTextField(teams, "clientId") && hasTextField(teams, "clientSecret") && hasTextField(teams, "tenantId"),
165
+ bluebubbles: hasTextField(bluebubbles, "serverUrl") && hasTextField(bluebubbles, "password"),
166
+ };
167
+ const rows = [
168
+ { label: "CLI", status: "interactive" },
169
+ {
170
+ label: "Teams",
171
+ status: !senses.teams.enabled ? "disabled" : configured.teams ? "ready" : "needs_config",
172
+ },
173
+ {
174
+ label: "BlueBubbles",
175
+ status: !senses.bluebubbles.enabled ? "disabled" : configured.bluebubbles ? "ready" : "needs_config",
176
+ },
177
+ ];
178
+ return rows.map((row) => `- ${row.label}: ${row.status}`);
179
+ }
180
+ function readBundleMetaFile() {
181
+ try {
182
+ const metaPath = path.join((0, identity_1.getAgentRoot)(), "bundle-meta.json");
183
+ const raw = fs.readFileSync(metaPath, "utf-8");
184
+ return JSON.parse(raw);
185
+ }
186
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
187
+ return null;
188
+ /* v8 ignore stop */
189
+ }
190
+ }
191
+ // ── Builder ─────────────────────────────────────────────────────────
192
+ async function buildTurnContext(input) {
193
+ const agentRoot = (0, identity_1.getAgentRoot)();
194
+ const agentName = (0, identity_1.getAgentName)();
195
+ // Sync config
196
+ let syncConfig = { enabled: false, remote: "origin" };
197
+ try {
198
+ syncConfig = (0, config_1.getSyncConfig)();
199
+ }
200
+ catch { /* v8 ignore next -- defensive @preserve */ /* config not available */ }
201
+ // Bridges
202
+ const activeBridges = (0, manager_1.createBridgeManager)().findBridgesForSession({
203
+ friendId: input.currentSession.friendId,
204
+ channel: input.currentSession.channel,
205
+ key: input.currentSession.key,
206
+ });
207
+ // Session activity
208
+ let sessionActivity = [];
209
+ try {
210
+ sessionActivity = (0, session_activity_1.listSessionActivity)({
211
+ sessionsDir: `${agentRoot}/state/sessions`,
212
+ friendsDir: `${agentRoot}/friends`,
213
+ agentName,
214
+ currentSession: {
215
+ friendId: input.currentSession.friendId,
216
+ channel: input.currentSession.channel,
217
+ key: input.currentSession.key,
218
+ },
219
+ });
220
+ }
221
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
222
+ sessionActivity = [];
223
+ /* v8 ignore stop */
224
+ }
225
+ // Target candidates
226
+ let targetCandidates = [];
227
+ try {
228
+ if (input.channel !== "inner") {
229
+ targetCandidates = await (0, target_resolution_1.listTargetSessionCandidates)({
230
+ sessionsDir: `${agentRoot}/state/sessions`,
231
+ friendsDir: `${agentRoot}/friends`,
232
+ agentName,
233
+ currentSession: {
234
+ friendId: input.currentSession.friendId,
235
+ channel: input.currentSession.channel,
236
+ key: input.currentSession.key,
237
+ },
238
+ friendStore: input.friendStore,
239
+ });
240
+ }
241
+ }
242
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
243
+ targetCandidates = [];
244
+ /* v8 ignore stop */
245
+ }
246
+ // Pending obligations
247
+ let pendingObligations = [];
248
+ try {
249
+ pendingObligations = (0, obligations_1.readPendingObligations)(agentRoot);
250
+ }
251
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
252
+ pendingObligations = [];
253
+ /* v8 ignore stop */
254
+ }
255
+ // Coding sessions
256
+ let codingSessions = [];
257
+ let otherCodingSessions = [];
258
+ try {
259
+ const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
260
+ .listSessions()
261
+ .filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
262
+ codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === input.currentSession.friendId
263
+ && session.originSession.channel === input.currentSession.channel
264
+ && session.originSession.key === input.currentSession.key);
265
+ otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === input.currentSession.friendId
266
+ && session.originSession.channel === input.currentSession.channel
267
+ && session.originSession.key === input.currentSession.key));
268
+ }
269
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
270
+ codingSessions = [];
271
+ otherCodingSessions = [];
272
+ /* v8 ignore stop */
273
+ }
274
+ // Inner work state
275
+ const innerWorkState = readInnerWorkState();
276
+ // Task board
277
+ let taskBoard;
278
+ try {
279
+ taskBoard = (0, tasks_1.getTaskModule)().getBoard();
280
+ }
281
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
282
+ taskBoard = emptyTaskBoard();
283
+ /* v8 ignore stop */
284
+ }
285
+ // Return obligations
286
+ let returnObligations = [];
287
+ try {
288
+ returnObligations = (0, obligations_1.listActiveReturnObligations)(agentName);
289
+ }
290
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
291
+ returnObligations = [];
292
+ /* v8 ignore stop */
293
+ }
294
+ // Recent episodes
295
+ const recentEpisodes = (0, episodes_1.readRecentEpisodes)(agentRoot, { limit: 20 });
296
+ // Active cares
297
+ const activeCares = (0, cares_1.readActiveCares)(agentRoot);
298
+ // ── Prompt-assembly pre-reads ──────────────────────────────────────
299
+ const daemonRunning = checkDaemonRunning();
300
+ let senseStatusLines = [];
301
+ try {
302
+ senseStatusLines = readSenseStatusLines();
303
+ }
304
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
305
+ senseStatusLines = [];
306
+ /* v8 ignore stop */
307
+ }
308
+ const bundleMeta = readBundleMetaFile();
309
+ let daemonHealth = null;
310
+ try {
311
+ daemonHealth = (0, daemon_health_1.readHealth)((0, daemon_health_1.getDefaultHealthPath)());
312
+ }
313
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
314
+ daemonHealth = null;
315
+ /* v8 ignore stop */
316
+ }
317
+ let journalFiles = [];
318
+ try {
319
+ const journalDir = path.join(agentRoot, "journal");
320
+ journalFiles = (0, prompt_1.readJournalFiles)(journalDir);
321
+ }
322
+ catch { /* v8 ignore start -- defensive: fallback on read failure @preserve */
323
+ journalFiles = [];
324
+ /* v8 ignore stop */
325
+ }
326
+ (0, runtime_1.emitNervesEvent)({
327
+ component: "senses",
328
+ event: "senses.turn_context_built",
329
+ message: "turn context snapshot assembled",
330
+ meta: {
331
+ channel: input.channel,
332
+ obligationCount: pendingObligations.length,
333
+ bridgeCount: activeBridges.length,
334
+ codingSessionCount: codingSessions.length,
335
+ episodeCount: recentEpisodes.length,
336
+ },
337
+ });
338
+ return {
339
+ activeBridges,
340
+ sessionActivity,
341
+ targetCandidates,
342
+ pendingObligations,
343
+ codingSessions,
344
+ otherCodingSessions,
345
+ innerWorkState,
346
+ taskBoard,
347
+ returnObligations,
348
+ recentEpisodes,
349
+ activeCares,
350
+ syncConfig,
351
+ syncFailure: undefined, // Set by pipeline after preTurnPull
352
+ daemonRunning,
353
+ senseStatusLines,
354
+ bundleMeta,
355
+ daemonHealth,
356
+ journalFiles,
357
+ };
358
+ }
@@ -257,7 +257,11 @@ function processTypeLabel(channel) {
257
257
  return PROCESS_TYPE_LABELS[channel];
258
258
  }
259
259
  const DAEMON_SOCKET_PATH = "/tmp/ouroboros-daemon.sock";
260
- function daemonStatus() {
260
+ function daemonStatus(preRead) {
261
+ /* v8 ignore next 2 -- pre-read branch: exercised via pipeline TurnContext path, not unit-testable in isolation @preserve */
262
+ if (preRead !== undefined) {
263
+ return preRead ? "running" : "not running";
264
+ }
261
265
  try {
262
266
  return fs.existsSync(DAEMON_SOCKET_PATH) ? "running" : "not running";
263
267
  }
@@ -265,14 +269,15 @@ function daemonStatus() {
265
269
  return "unknown";
266
270
  }
267
271
  }
268
- function runtimeInfoSection(channel) {
272
+ function runtimeInfoSection(channel, options) {
269
273
  const lines = [];
270
274
  const agentName = (0, identity_1.getAgentName)();
271
275
  const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
272
276
  lines.push(`## runtime`);
273
277
  lines.push(`agent: ${agentName}`);
274
278
  lines.push(`runtime version: ${currentVersion}`);
275
- const bundleMeta = readBundleMeta();
279
+ /* v8 ignore next -- branch: pre-read path exercised via pipeline TurnContext, not unit-testable in isolation @preserve */
280
+ const bundleMeta = options?.bundleMeta !== undefined ? options.bundleMeta : readBundleMeta();
276
281
  if (bundleMeta?.previousRuntimeVersion && bundleMeta.previousRuntimeVersion !== currentVersion) {
277
282
  lines.push(`previously: ${bundleMeta.previousRuntimeVersion}`);
278
283
  const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(bundleMeta.previousRuntimeVersion, currentVersion);
@@ -289,7 +294,7 @@ function runtimeInfoSection(channel) {
289
294
  lines.push(`channel: ${channel}`);
290
295
  lines.push(`current sense: ${channel}`);
291
296
  lines.push(`process type: ${processTypeLabel(channel)}`);
292
- lines.push(`daemon: ${daemonStatus()}`);
297
+ lines.push(`daemon: ${daemonStatus(options?.daemonRunning)}`);
293
298
  lines.push(`mcp serve: i can expose my tools to dev tools via \`ouro mcp-serve\`. see the configure-dev-tools skill for setup.`);
294
299
  if (channel === "cli") {
295
300
  lines.push("i introduce myself on boot with a fun random greeting.");
@@ -308,7 +313,7 @@ function runtimeInfoSection(channel) {
308
313
  lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
309
314
  }
310
315
  lines.push("");
311
- lines.push(...senseRuntimeGuidance(channel));
316
+ lines.push(...senseRuntimeGuidance(channel, options?.senseStatusLines));
312
317
  return lines.join("\n");
313
318
  }
314
319
  function hasTextField(record, key) {
@@ -356,9 +361,9 @@ function localSenseStatusLines() {
356
361
  _senseStatusLinesCache = rows.map((row) => `- ${row.label}: ${row.status}`);
357
362
  return [..._senseStatusLinesCache];
358
363
  }
359
- function senseRuntimeGuidance(channel) {
364
+ function senseRuntimeGuidance(channel, preReadStatusLines) {
360
365
  const lines = ["available senses:"];
361
- lines.push(...localSenseStatusLines());
366
+ lines.push(...(preReadStatusLines ?? localSenseStatusLines()));
362
367
  lines.push("sense states:");
363
368
  lines.push("- interactive = available when opened by the user instead of kept running by the daemon");
364
369
  lines.push("- disabled = turned off in agent.json");
@@ -876,9 +881,8 @@ function formatRelativeTime(nowMs, mtimeMs) {
876
881
  const days = Math.floor(hours / 24);
877
882
  return `${days} day${days === 1 ? "" : "s"} ago`;
878
883
  }
879
- function journalSection(agentRoot, now) {
880
- const journalDir = path.join(agentRoot, "journal");
881
- const files = readJournalFiles(journalDir);
884
+ function journalSection(agentRoot, now, preReadFiles) {
885
+ const files = preReadFiles ?? readJournalFiles(path.join(agentRoot, "journal"));
882
886
  if (files.length === 0)
883
887
  return "";
884
888
  const nowMs = (now ?? new Date()).getTime();
@@ -977,9 +981,10 @@ function formatElapsedBrief(ms) {
977
981
  const hours = Math.floor(minutes / 60);
978
982
  return `${hours}h ago`;
979
983
  }
980
- function rhythmStatusSection() {
984
+ function rhythmStatusSection(preReadHealth) {
981
985
  try {
982
- const health = (0, daemon_health_1.readHealth)((0, daemon_health_1.getDefaultHealthPath)());
986
+ /* v8 ignore next -- branch: pre-read path exercised via pipeline TurnContext @preserve */
987
+ const health = preReadHealth !== undefined ? preReadHealth : (0, daemon_health_1.readHealth)((0, daemon_health_1.getDefaultHealthPath)());
983
988
  if (!health)
984
989
  return "";
985
990
  const habitNames = Object.keys(health.habits);
@@ -1032,8 +1037,8 @@ async function buildSystem(channel = "cli", options, context) {
1032
1037
  // Group 2: my body & environment
1033
1038
  "# my body & environment",
1034
1039
  bodyMapSection((0, identity_1.getAgentName)()),
1035
- runtimeInfoSection(channel),
1036
- rhythmStatusSection(),
1040
+ runtimeInfoSection(channel, options),
1041
+ rhythmStatusSection(options?.daemonHealth),
1037
1042
  channelNatureSection((0, channel_1.getChannelCapabilities)(channel)),
1038
1043
  providerSection(channel),
1039
1044
  dateSection(),
@@ -1055,7 +1060,7 @@ async function buildSystem(channel = "cli", options, context) {
1055
1060
  ...(channel === "inner" ? [
1056
1061
  "# my inner life",
1057
1062
  metacognitiveFramingSection(channel),
1058
- journalSection((0, identity_1.getAgentRoot)()),
1063
+ journalSection((0, identity_1.getAgentRoot)(), undefined, options?.journalFiles),
1059
1064
  ] : []),
1060
1065
  // Group 6: social context (non-local, non-inner channels)
1061
1066
  // Individual sections self-gate on isRemoteChannel/channel checks.
@@ -58,10 +58,9 @@ const coding_1 = require("./coding");
58
58
  const diary_1 = require("../mind/diary");
59
59
  const tasks_1 = require("./tasks");
60
60
  const pending_1 = require("../mind/pending");
61
- const obligations_1 = require("../mind/obligations");
61
+ const obligations_1 = require("../heart/obligations");
62
62
  const progress_story_1 = require("../heart/progress-story");
63
63
  const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
64
- const obligations_2 = require("../heart/obligations");
65
64
  const episodes_1 = require("../mind/episodes");
66
65
  const cares_1 = require("../heart/cares");
67
66
  const presence_1 = require("../heart/presence");
@@ -220,7 +219,7 @@ function readActiveWorkInnerState() {
220
219
  const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
221
220
  const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
222
221
  const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
223
- const storeObligationPending = (0, obligations_2.readPendingObligations)(agentRoot).length > 0;
222
+ const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
224
223
  return {
225
224
  status: dialogStatus.processing === "started" ? "running" : "idle",
226
225
  hasPending: dialogStatus.queue !== "clear",
@@ -269,7 +268,7 @@ async function buildToolActiveWorkFrame(ctx) {
269
268
  }
270
269
  const pendingObligations = (() => {
271
270
  try {
272
- return (0, obligations_2.readPendingObligations)(agentRoot);
271
+ return (0, obligations_1.readPendingObligations)(agentRoot);
273
272
  }
274
273
  catch {
275
274
  return [];
@@ -1296,7 +1295,7 @@ exports.baseToolDefinitions = [
1296
1295
  writePendingEnvelope(pendingDir, envelope);
1297
1296
  if (delegatedFrom) {
1298
1297
  try {
1299
- (0, obligations_2.createObligation)((0, identity_1.getAgentRoot)(), {
1298
+ (0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
1300
1299
  origin: {
1301
1300
  friendId: delegatedFrom.friendId,
1302
1301
  channel: delegatedFrom.channel,
@@ -1311,7 +1310,7 @@ exports.baseToolDefinitions = [
1311
1310
  }
1312
1311
  /* v8 ignore next -- obligationId always set when delegatedFrom is set (see generateObligationId above) @preserve */
1313
1312
  if (obligationId) {
1314
- (0, obligations_1.createObligation)(agentName, {
1313
+ (0, obligations_1.createReturnObligation)(agentName, {
1315
1314
  id: obligationId,
1316
1315
  origin: delegatedFrom,
1317
1316
  status: "queued",
@@ -48,8 +48,7 @@ const runtime_1 = require("../nerves/runtime");
48
48
  const guardrails_1 = require("./guardrails");
49
49
  const identity_1 = require("../heart/identity");
50
50
  const surface_tool_1 = require("../senses/surface-tool");
51
- const obligations_1 = require("../mind/obligations");
52
- const obligations_2 = require("../heart/obligations");
51
+ const obligations_1 = require("../heart/obligations");
53
52
  const session_activity_1 = require("../heart/session-activity");
54
53
  const path = __importStar(require("path"));
55
54
  function safeGetAgentRoot() {
@@ -198,7 +197,7 @@ const surfaceToolDefinition = {
198
197
  catch {
199
198
  return "unknown";
200
199
  } })();
201
- (0, obligations_1.advanceObligation)(name, obligationId, {
200
+ (0, obligations_1.advanceReturnObligation)(name, obligationId, {
202
201
  status: update.status,
203
202
  ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
204
203
  ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
@@ -213,9 +212,9 @@ const surfaceToolDefinition = {
213
212
  /* v8 ignore start -- heart obligation fulfillment: tested via surface-tool.test.ts @preserve */
214
213
  try {
215
214
  const agentRoot = (0, identity_1.getAgentRoot)();
216
- const heartObligation = (0, obligations_2.findPendingObligationForOrigin)(agentRoot, origin);
215
+ const heartObligation = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, origin);
217
216
  if (heartObligation) {
218
- (0, obligations_2.fulfillObligation)(agentRoot, heartObligation.id);
217
+ (0, obligations_1.fulfillObligation)(agentRoot, heartObligation.id);
219
218
  }
220
219
  }
221
220
  catch {
@@ -55,7 +55,7 @@ const prompt_1 = require("../mind/prompt");
55
55
  const mcp_manager_1 = require("../repertoire/mcp-manager");
56
56
  const bundle_manifest_1 = require("../mind/bundle-manifest");
57
57
  const pending_1 = require("../mind/pending");
58
- const obligations_1 = require("../mind/obligations");
58
+ const obligations_1 = require("../heart/obligations");
59
59
  const attention_queue_1 = require("./attention-queue");
60
60
  const channel_1 = require("../mind/friends/channel");
61
61
  const trust_gate_1 = require("./trust-gate");
@@ -66,7 +66,6 @@ const runtime_1 = require("../nerves/runtime");
66
66
  const manager_1 = require("../heart/bridges/manager");
67
67
  const session_activity_1 = require("../heart/session-activity");
68
68
  const bluebubbles_1 = require("./bluebubbles");
69
- const obligations_2 = require("../heart/obligations");
70
69
  const habit_turn_message_1 = require("./habit-turn-message");
71
70
  const journal_index_1 = require("../mind/journal-index");
72
71
  const habit_parser_1 = require("../heart/daemon/habit-parser");
@@ -324,7 +323,7 @@ function advanceObligationQuietly(agentName, obligationId, update) {
324
323
  if (!obligationId)
325
324
  return;
326
325
  try {
327
- (0, obligations_1.advanceObligation)(agentName, obligationId, update);
326
+ (0, obligations_1.advanceReturnObligation)(agentName, obligationId, update);
328
327
  /* v8 ignore start -- best-effort: obligation fs errors must never block return routing @preserve */
329
328
  }
330
329
  catch {
@@ -352,14 +351,14 @@ async function routeDelegatedCompletion(agentRoot, agentName, completion, draine
352
351
  if (delegated.obligationStatus === "pending") {
353
352
  // Fulfill the persistent obligation in the store
354
353
  try {
355
- const pending = (0, obligations_2.findPendingObligationForOrigin)(agentRoot, {
354
+ const pending = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, {
356
355
  friendId: delegatedFrom.friendId,
357
356
  channel: delegatedFrom.channel,
358
357
  key: delegatedFrom.key,
359
358
  });
360
359
  /* v8 ignore next 2 -- obligation fulfillment tested via obligations.test.ts; integration requires real disk state @preserve */
361
360
  if (pending) {
362
- (0, obligations_2.fulfillObligation)(agentRoot, pending.id);
361
+ (0, obligations_1.fulfillObligation)(agentRoot, pending.id);
363
362
  }
364
363
  }
365
364
  catch {
@@ -577,7 +576,7 @@ async function runInnerDialogTurn(options) {
577
576
  }
578
577
  else {
579
578
  // Unified path: gather context for ALL habits (heartbeat included)
580
- const obligations = (0, obligations_1.listActiveObligations)(agentName);
579
+ const obligations = (0, obligations_1.listActiveReturnObligations)(agentName);
581
580
  const nowMs = now().getTime();
582
581
  const staleObligations = obligations.map((o) => ({
583
582
  friendName: o.origin.friendId,
@@ -670,7 +669,7 @@ async function runInnerDialogTurn(options) {
670
669
  signal: options?.signal,
671
670
  /* v8 ignore start -- attention queue: callback invoked by pipeline during pending drain; tested via attention-queue unit tests @preserve */
672
671
  onPendingDrained: (drained) => {
673
- const outstandingObligations = (0, obligations_1.listActiveObligations)(agentName);
672
+ const outstandingObligations = (0, obligations_1.listActiveReturnObligations)(agentName);
674
673
  attentionQueue = (0, attention_queue_1.buildAttentionQueue)({
675
674
  drainedPending: drained,
676
675
  outstandingObligations,
@@ -47,17 +47,10 @@ const commands_1 = require("./commands");
47
47
  const continuity_1 = require("./continuity");
48
48
  const manager_1 = require("../heart/bridges/manager");
49
49
  const identity_1 = require("../heart/identity");
50
- const tasks_1 = require("../repertoire/tasks");
51
- const coding_1 = require("../repertoire/coding");
52
- const session_activity_1 = require("../heart/session-activity");
53
50
  const socket_client_1 = require("../heart/daemon/socket-client");
54
51
  const active_work_1 = require("../heart/active-work");
55
52
  const delegation_1 = require("../heart/delegation");
56
- const target_resolution_1 = require("../heart/target-resolution");
57
- const thoughts_1 = require("../heart/daemon/thoughts");
58
- const pending_1 = require("../mind/pending");
59
53
  const obligations_1 = require("../heart/obligations");
60
- const obligations_2 = require("../mind/obligations");
61
54
  const provider_failover_1 = require("../heart/provider-failover");
62
55
  const provider_ping_1 = require("../heart/provider-ping");
63
56
  const auth_flow_1 = require("../heart/daemon/auth-flow");
@@ -68,7 +61,7 @@ const sync_1 = require("../heart/sync");
68
61
  const config_1 = require("../heart/config");
69
62
  const presence_1 = require("../heart/presence");
70
63
  const episodes_1 = require("../mind/episodes");
71
- const cares_1 = require("../heart/cares");
64
+ const turn_context_1 = require("../heart/turn-context");
72
65
  /**
73
66
  * Emit episodes for obligation state transitions detected during a turn.
74
67
  * Exported for direct testability (avoids v8 coverage merge issues in multi-file test suites).
@@ -89,33 +82,6 @@ function emitObligationTransitionEpisodes(agentRoot, preTurnObligationIds, postT
89
82
  }
90
83
  }
91
84
  }
92
- function emptyTaskBoard() {
93
- return {
94
- compact: "",
95
- full: "",
96
- byStatus: {
97
- drafting: [],
98
- processing: [],
99
- validating: [],
100
- collaborating: [],
101
- paused: [],
102
- blocked: [],
103
- done: [],
104
- cancelled: [],
105
- },
106
- issues: [],
107
- actionRequired: [],
108
- unresolvedDependencies: [],
109
- activeSessions: [],
110
- activeBridges: [],
111
- };
112
- }
113
- function isLiveCodingSessionStatus(status) {
114
- return status === "spawning"
115
- || status === "running"
116
- || status === "waiting_input"
117
- || status === "stalled";
118
- }
119
85
  function prependTurnSections(message, sections) {
120
86
  /* v8 ignore next -- defensive: only user messages with non-empty sections reach here @preserve */
121
87
  if (message.role !== "user" || sections.length === 0)
@@ -137,44 +103,6 @@ function prependTurnSections(message, sections) {
137
103
  };
138
104
  /* v8 ignore stop */
139
105
  }
140
- function readInnerWorkState() {
141
- const defaultJob = {
142
- status: "idle",
143
- content: null,
144
- origin: null,
145
- mode: "reflect",
146
- obligationStatus: null,
147
- surfacedResult: null,
148
- queuedAt: null,
149
- startedAt: null,
150
- surfacedAt: null,
151
- };
152
- try {
153
- const agentRoot = (0, identity_1.getAgentRoot)();
154
- const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
155
- const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
156
- const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
157
- const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
158
- const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
159
- // Derive obligationPending from both the pending message field and the obligation store
160
- const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
161
- return {
162
- status: dialogStatus.processing === "started" ? "running" : "idle",
163
- hasPending: dialogStatus.queue !== "clear",
164
- origin: dialogStatus.origin,
165
- contentSnippet: dialogStatus.contentSnippet,
166
- obligationPending: dialogStatus.obligationPending || storeObligationPending,
167
- job,
168
- };
169
- }
170
- catch {
171
- return {
172
- status: "idle",
173
- hasPending: false,
174
- job: defaultJob,
175
- };
176
- }
177
- }
178
106
  // ── Pipeline ──────────────────────────────────────────────────────
179
107
  let _lastSessionKey = null;
180
108
  async function handleInboundTurn(input) {
@@ -378,100 +306,29 @@ async function handleInboundTurn(input) {
378
306
  }
379
307
  }
380
308
  /* v8 ignore stop */
381
- const activeBridges = (0, manager_1.createBridgeManager)().findBridgesForSession({
382
- friendId: currentSession.friendId,
383
- channel: currentSession.channel,
384
- key: currentSession.key,
309
+ // Build the turn context snapshot — centralizes all state reads
310
+ const ctx = await (0, turn_context_1.buildTurnContext)({
311
+ currentSession,
312
+ channel: input.channel,
313
+ friendStore: input.friendStore,
385
314
  });
315
+ // Propagate sync failure from pre-turn pull
316
+ ctx.syncFailure = syncFailure;
317
+ const { activeBridges, sessionActivity, pendingObligations, codingSessions, otherCodingSessions } = ctx;
386
318
  const bridgeContext = (0, manager_1.formatBridgeContext)(activeBridges) || undefined;
387
- let sessionActivity = [];
388
- try {
389
- const agentRoot = (0, identity_1.getAgentRoot)();
390
- sessionActivity = (0, session_activity_1.listSessionActivity)({
391
- sessionsDir: `${agentRoot}/state/sessions`,
392
- friendsDir: `${agentRoot}/friends`,
393
- agentName: (0, identity_1.getAgentName)(),
394
- currentSession: {
395
- friendId: currentSession.friendId,
396
- channel: currentSession.channel,
397
- key: currentSession.key,
398
- },
399
- });
400
- }
401
- catch {
402
- sessionActivity = [];
403
- }
404
- let targetCandidates = [];
405
- try {
406
- if (input.channel !== "inner") {
407
- const agentRoot = (0, identity_1.getAgentRoot)();
408
- targetCandidates = await (0, target_resolution_1.listTargetSessionCandidates)({
409
- sessionsDir: `${agentRoot}/state/sessions`,
410
- friendsDir: `${agentRoot}/friends`,
411
- agentName: (0, identity_1.getAgentName)(),
412
- currentSession: {
413
- friendId: currentSession.friendId,
414
- channel: currentSession.channel,
415
- key: currentSession.key,
416
- },
417
- friendStore: input.friendStore,
418
- });
419
- }
420
- }
421
- catch {
422
- targetCandidates = [];
423
- }
424
- let pendingObligations = [];
425
- try {
426
- pendingObligations = (0, obligations_1.readPendingObligations)((0, identity_1.getAgentRoot)());
427
- }
428
- catch {
429
- pendingObligations = [];
430
- }
431
- let codingSessions = [];
432
- let otherCodingSessions = [];
433
- try {
434
- const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
435
- .listSessions()
436
- .filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
437
- codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === currentSession.friendId
438
- && session.originSession.channel === currentSession.channel
439
- && session.originSession.key === currentSession.key);
440
- otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === currentSession.friendId
441
- && session.originSession.channel === currentSession.channel
442
- && session.originSession.key === currentSession.key));
443
- }
444
- catch {
445
- codingSessions = [];
446
- otherCodingSessions = [];
447
- }
448
319
  const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
449
320
  currentSession,
450
321
  currentObligation,
451
322
  mustResolveBeforeHandoff,
452
- inner: readInnerWorkState(),
323
+ inner: ctx.innerWorkState,
453
324
  bridges: activeBridges,
454
325
  codingSessions,
455
326
  otherCodingSessions,
456
327
  pendingObligations,
457
- taskBoard: (() => {
458
- try {
459
- return (0, tasks_1.getTaskModule)().getBoard();
460
- }
461
- catch {
462
- return emptyTaskBoard();
463
- }
464
- })(),
328
+ taskBoard: ctx.taskBoard,
465
329
  friendActivity: sessionActivity,
466
- targetCandidates,
467
- innerReturnObligations: (() => {
468
- try {
469
- return (0, obligations_2.listActiveObligations)((0, identity_1.getAgentName)());
470
- }
471
- catch {
472
- return [];
473
- }
474
- })(),
330
+ targetCandidates: ctx.targetCandidates,
331
+ innerReturnObligations: ctx.returnObligations,
475
332
  });
476
333
  const delegationDecision = (0, delegation_1.decideDelegation)({
477
334
  channel: input.channel,
@@ -502,8 +359,7 @@ async function handleInboundTurn(input) {
502
359
  try {
503
360
  const agentRoot = (0, identity_1.getAgentRoot)();
504
361
  const agentName = (0, identity_1.getAgentName)();
505
- const recentEpisodes = (0, episodes_1.readRecentEpisodes)(agentRoot, { limit: 20 });
506
- const activeCares = (0, cares_1.readActiveCares)(agentRoot);
362
+ const { recentEpisodes, activeCares } = ctx;
507
363
  const tempoState = (0, tempo_1.deriveTempo)({
508
364
  activeSessions: sessionActivity.length + 1,
509
365
  openObligations: pendingObligations.length,
@@ -571,6 +427,12 @@ async function handleInboundTurn(input) {
571
427
  setMustResolveBeforeHandoff: (value) => {
572
428
  mustResolveBeforeHandoff = value;
573
429
  },
430
+ // Pre-read state from TurnContext for prompt assembly
431
+ daemonRunning: ctx.daemonRunning,
432
+ senseStatusLines: ctx.senseStatusLines,
433
+ bundleMeta: ctx.bundleMeta,
434
+ daemonHealth: ctx.daemonHealth,
435
+ journalFiles: ctx.journalFiles,
574
436
  toolContext: {
575
437
  /* v8 ignore next -- default no-op signin satisfies interface; real signin injected by sense adapter @preserve */
576
438
  signin: async () => undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.202",
3
+ "version": "0.1.0-alpha.204",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -1,138 +0,0 @@
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.getObligationsDir = getObligationsDir;
37
- exports.generateObligationId = generateObligationId;
38
- exports.createObligation = createObligation;
39
- exports.readObligation = readObligation;
40
- exports.advanceObligation = advanceObligation;
41
- exports.listActiveObligations = listActiveObligations;
42
- const fs = __importStar(require("fs"));
43
- const path = __importStar(require("path"));
44
- const identity_1 = require("../heart/identity");
45
- const runtime_1 = require("../nerves/runtime");
46
- const sync_1 = require("../heart/sync");
47
- // ── Paths ────────────────────────────────────────────────────────
48
- function getObligationsDir(agentName) {
49
- return path.join((0, identity_1.getAgentRoot)(agentName), "arc", "obligations", "inner");
50
- }
51
- // ── ID generation ────────────────────────────────────────────────
52
- function generateObligationId(timestamp) {
53
- return `${timestamp}-${Math.random().toString(36).slice(2, 10)}`;
54
- }
55
- // ── Store operations ─────────────────────────────────────────────
56
- function createObligation(agentName, obligation) {
57
- const dir = getObligationsDir(agentName);
58
- fs.mkdirSync(dir, { recursive: true });
59
- const filePath = path.join(dir, `${obligation.id}.json`);
60
- fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf8");
61
- (0, sync_1.trackSyncWrite)(filePath);
62
- (0, runtime_1.emitNervesEvent)({
63
- event: "mind.obligation_created",
64
- component: "mind",
65
- message: "return obligation created",
66
- meta: {
67
- obligationId: obligation.id,
68
- origin: `${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}`,
69
- status: obligation.status,
70
- },
71
- });
72
- return filePath;
73
- }
74
- function readObligation(agentName, obligationId) {
75
- const dir = getObligationsDir(agentName);
76
- try {
77
- const raw = fs.readFileSync(path.join(dir, `${obligationId}.json`), "utf-8");
78
- return JSON.parse(raw);
79
- }
80
- catch {
81
- return null;
82
- }
83
- }
84
- function advanceObligation(agentName, obligationId, update) {
85
- const existing = readObligation(agentName, obligationId);
86
- if (!existing)
87
- return null;
88
- const updated = {
89
- ...existing,
90
- status: update.status,
91
- ...(update.startedAt !== undefined ? { startedAt: update.startedAt } : {}),
92
- ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
93
- ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
94
- };
95
- const dir = getObligationsDir(agentName);
96
- const advancedFilePath = path.join(dir, `${obligationId}.json`);
97
- fs.writeFileSync(advancedFilePath, JSON.stringify(updated, null, 2), "utf8");
98
- (0, sync_1.trackSyncWrite)(advancedFilePath);
99
- (0, runtime_1.emitNervesEvent)({
100
- event: "mind.obligation_advanced",
101
- component: "mind",
102
- message: `obligation advanced to ${update.status}`,
103
- meta: {
104
- obligationId,
105
- status: update.status,
106
- ...(update.returnTarget ? { returnTarget: update.returnTarget } : {}),
107
- },
108
- });
109
- return updated;
110
- }
111
- function listActiveObligations(agentName) {
112
- const dir = getObligationsDir(agentName);
113
- if (!fs.existsSync(dir))
114
- return [];
115
- let entries;
116
- try {
117
- entries = fs.readdirSync(dir);
118
- }
119
- catch {
120
- return [];
121
- }
122
- const obligations = [];
123
- for (const file of entries) {
124
- if (!file.endsWith(".json"))
125
- continue;
126
- try {
127
- const raw = fs.readFileSync(path.join(dir, file), "utf-8");
128
- const parsed = JSON.parse(raw);
129
- if (parsed.status === "queued" || parsed.status === "running") {
130
- obligations.push(parsed);
131
- }
132
- }
133
- catch {
134
- // skip unparseable
135
- }
136
- }
137
- return obligations.sort((a, b) => a.createdAt - b.createdAt);
138
- }