@ouro.bot/cli 0.1.0-alpha.203 → 0.1.0-alpha.205

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.205",
6
+ "changes": [
7
+ "refactor: enforce subsystem boundaries — eliminate all heart/ and nerves/ static imports from senses/, move AttentionItem type to heart/, surfaceToolDef to repertoire/, SteeringFollowUpEffect to heart/turn-coordinator, inline BlueBubbles runtime state reader in daemon"
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.204",
12
+ "changes": [
13
+ "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"
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.203",
6
18
  "changes": [
@@ -1,4 +1,37 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  // Unified agent runtime entrypoint.
4
37
  // Requires --agent before importing runtime modules that rely on identity.
@@ -7,10 +40,12 @@ if (!process.argv.includes("--agent")) {
7
40
  console.error("Missing required --agent <name> argument.\nUsage: node dist/heart/agent-entry.js --agent ouroboros");
8
41
  process.exit(1);
9
42
  }
10
- const inner_dialog_worker_1 = require("../senses/inner-dialog-worker");
11
43
  const cli_logging_1 = require("../nerves/cli-logging");
12
44
  (0, cli_logging_1.configureCliRuntimeLogger)("self");
13
- (0, inner_dialog_worker_1.startInnerDialogWorker)().catch((error) => {
45
+ // Dynamic import: agent-entry is boot-time wiring that starts a sense process.
46
+ // Using dynamic import avoids a static heart/ -> senses/ dependency.
47
+ Promise.resolve().then(() => __importStar(require("../senses/inner-dialog-worker"))).then(({ startInnerDialogWorker }) => startInnerDialogWorker())
48
+ .catch((error) => {
14
49
  // eslint-disable-next-line no-console -- fatal startup guard for worker process
15
50
  console.error(error instanceof Error ? error.message : String(error));
16
51
  process.exit(1);
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Shared attention queue types.
4
+ *
5
+ * Canonical home for the AttentionItem interface — consumed by heart/, repertoire/, nerves/, and senses/.
6
+ * Placed in heart/ because attention is a turn-coordination concern.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -18,7 +18,7 @@ const config_1 = require("./config");
18
18
  const identity_1 = require("./identity");
19
19
  const tools_1 = require("../repertoire/tools");
20
20
  const channel_1 = require("../mind/friends/channel");
21
- const surface_tool_1 = require("../senses/surface-tool");
21
+ const tools_2 = require("../repertoire/tools");
22
22
  const runtime_1 = require("../nerves/runtime");
23
23
  const context_1 = require("../mind/context");
24
24
  const prompt_1 = require("../mind/prompt");
@@ -148,11 +148,11 @@ function getProviderDisplayLabel(facing = "human") {
148
148
  return providerLabelBuilders[provider]();
149
149
  }
150
150
  // Re-export tools, execTool, summarizeArgs from ./tools for backward compat
151
- var tools_2 = require("../repertoire/tools");
152
- Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_2.tools; } });
153
- Object.defineProperty(exports, "execTool", { enumerable: true, get: function () { return tools_2.execTool; } });
154
- Object.defineProperty(exports, "summarizeArgs", { enumerable: true, get: function () { return tools_2.summarizeArgs; } });
155
- Object.defineProperty(exports, "getToolsForChannel", { enumerable: true, get: function () { return tools_2.getToolsForChannel; } });
151
+ var tools_3 = require("../repertoire/tools");
152
+ Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_3.tools; } });
153
+ Object.defineProperty(exports, "execTool", { enumerable: true, get: function () { return tools_3.execTool; } });
154
+ Object.defineProperty(exports, "summarizeArgs", { enumerable: true, get: function () { return tools_3.summarizeArgs; } });
155
+ Object.defineProperty(exports, "getToolsForChannel", { enumerable: true, get: function () { return tools_3.getToolsForChannel; } });
156
156
  // Re-export streaming functions for backward compat
157
157
  var streaming_1 = require("./streaming");
158
158
  Object.defineProperty(exports, "streamChatCompletion", { enumerable: true, get: function () { return streaming_1.streamChatCompletion; } });
@@ -530,7 +530,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
530
530
  const activeTools = [
531
531
  ...filteredBaseTools,
532
532
  tools_1.ponderTool,
533
- ...(isInnerDialog ? [surface_tool_1.surfaceToolDef, tools_1.restTool] : []),
533
+ ...(isInnerDialog ? [tools_2.surfaceToolDef, tools_1.restTool] : []),
534
534
  ...((currentContext?.isGroupChat || options?.isReactionSignal) && !isInnerDialog ? [tools_1.observeTool] : []),
535
535
  ...(!isInnerDialog ? [tools_1.settleTool] : []),
536
536
  ];
@@ -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
  });
@@ -38,7 +38,6 @@ const fs = __importStar(require("fs"));
38
38
  const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
- const bluebubbles_runtime_state_1 = require("../../senses/bluebubbles-runtime-state");
42
41
  const identity_1 = require("../identity");
43
42
  const sense_truth_1 = require("../sense-truth");
44
43
  const process_manager_1 = require("./process-manager");
@@ -187,13 +186,35 @@ function blueBubblesRuntimeStateIsFresh(lastCheckedAt, now = Date.now()) {
187
186
  }
188
187
  return checkedAt >= now - BLUEBUBBLES_RUNTIME_FRESHNESS_WINDOW_MS;
189
188
  }
189
+ function readBlueBubblesRuntimeJson(runtimePath) {
190
+ try {
191
+ const raw = fs.readFileSync(runtimePath, "utf-8");
192
+ const parsed = JSON.parse(raw);
193
+ /* v8 ignore start -- branches: ternary fallbacks for missing/malformed BB runtime fields @preserve */
194
+ return {
195
+ upstreamStatus: parsed.upstreamStatus === "ok" || parsed.upstreamStatus === "error"
196
+ ? parsed.upstreamStatus
197
+ : "unknown",
198
+ detail: typeof parsed.detail === "string" && parsed.detail.trim()
199
+ ? parsed.detail
200
+ : "startup health probe pending",
201
+ lastCheckedAt: typeof parsed.lastCheckedAt === "string" ? parsed.lastCheckedAt : undefined,
202
+ };
203
+ /* v8 ignore stop */
204
+ /* v8 ignore start -- defensive: catch for missing/corrupt BB runtime state file @preserve */
205
+ }
206
+ catch {
207
+ return { upstreamStatus: "unknown", detail: "startup health probe pending" };
208
+ }
209
+ /* v8 ignore stop */
210
+ }
190
211
  function readBlueBubblesRuntimeFacts(agent, bundlesRoot, snapshot) {
191
212
  const agentRoot = path.join(bundlesRoot, `${agent}.ouro`);
192
213
  const runtimePath = path.join(agentRoot, "state", "senses", "bluebubbles", "runtime.json");
193
214
  if (!fs.existsSync(runtimePath)) {
194
215
  return { runtime: snapshot?.runtime };
195
216
  }
196
- const state = (0, bluebubbles_runtime_state_1.readBlueBubblesRuntimeState)(agent, agentRoot);
217
+ const state = readBlueBubblesRuntimeJson(runtimePath);
197
218
  if (!blueBubblesRuntimeStateIsFresh(state.lastCheckedAt)) {
198
219
  return { runtime: snapshot?.runtime };
199
220
  }
@@ -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.
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
36
+ exports.surfaceToolDef = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
37
37
  exports.getToolsForChannel = getToolsForChannel;
38
38
  exports.isConfirmationRequired = isConfirmationRequired;
39
39
  exports.execTool = execTool;
@@ -66,10 +66,36 @@ Object.defineProperty(exports, "settleTool", { enumerable: true, get: function (
66
66
  Object.defineProperty(exports, "observeTool", { enumerable: true, get: function () { return tools_base_2.observeTool; } });
67
67
  Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_base_2.ponderTool; } });
68
68
  Object.defineProperty(exports, "restTool", { enumerable: true, get: function () { return tools_base_2.restTool; } });
69
+ // Surface tool schema — canonical home. Handler lives in senses/surface-tool.ts.
70
+ exports.surfaceToolDef = {
71
+ type: "function",
72
+ function: {
73
+ name: "surface",
74
+ description: "share a thought outward — deliver an answer, ask a follow-up, or surface progress to whoever needs to hear it. pass delegationId to address a held thought (see your attention queue above), or friendId for spontaneous outreach. does not end your turn.",
75
+ parameters: {
76
+ type: "object",
77
+ properties: {
78
+ content: {
79
+ type: "string",
80
+ description: "the message to deliver",
81
+ },
82
+ delegationId: {
83
+ type: "string",
84
+ description: "ID from your attention queue — addresses a specific held thought",
85
+ },
86
+ friendId: {
87
+ type: "string",
88
+ description: "friend to reach out to spontaneously (when not addressing a held thought)",
89
+ },
90
+ },
91
+ required: ["content"],
92
+ },
93
+ },
94
+ };
69
95
  // Surface tool handler: routes content to friend's freshest session
70
96
  /* v8 ignore start -- surface handler wiring: core logic tested via surface-tool.test.ts; this wires identity/routing deps @preserve */
71
97
  const surfaceToolDefinition = {
72
- tool: surface_tool_1.surfaceToolDef,
98
+ tool: exports.surfaceToolDef,
73
99
  handler: async (args, ctx) => {
74
100
  const queue = ctx?.delegatedOrigins ?? [];
75
101
  const agentName = (() => { try {
@@ -47,15 +47,9 @@ 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
54
  const provider_failover_1 = require("../heart/provider-failover");
61
55
  const provider_ping_1 = require("../heart/provider-ping");
@@ -67,7 +61,7 @@ const sync_1 = require("../heart/sync");
67
61
  const config_1 = require("../heart/config");
68
62
  const presence_1 = require("../heart/presence");
69
63
  const episodes_1 = require("../mind/episodes");
70
- const cares_1 = require("../heart/cares");
64
+ const turn_context_1 = require("../heart/turn-context");
71
65
  /**
72
66
  * Emit episodes for obligation state transitions detected during a turn.
73
67
  * Exported for direct testability (avoids v8 coverage merge issues in multi-file test suites).
@@ -88,33 +82,6 @@ function emitObligationTransitionEpisodes(agentRoot, preTurnObligationIds, postT
88
82
  }
89
83
  }
90
84
  }
91
- function emptyTaskBoard() {
92
- return {
93
- compact: "",
94
- full: "",
95
- byStatus: {
96
- drafting: [],
97
- processing: [],
98
- validating: [],
99
- collaborating: [],
100
- paused: [],
101
- blocked: [],
102
- done: [],
103
- cancelled: [],
104
- },
105
- issues: [],
106
- actionRequired: [],
107
- unresolvedDependencies: [],
108
- activeSessions: [],
109
- activeBridges: [],
110
- };
111
- }
112
- function isLiveCodingSessionStatus(status) {
113
- return status === "spawning"
114
- || status === "running"
115
- || status === "waiting_input"
116
- || status === "stalled";
117
- }
118
85
  function prependTurnSections(message, sections) {
119
86
  /* v8 ignore next -- defensive: only user messages with non-empty sections reach here @preserve */
120
87
  if (message.role !== "user" || sections.length === 0)
@@ -136,44 +103,6 @@ function prependTurnSections(message, sections) {
136
103
  };
137
104
  /* v8 ignore stop */
138
105
  }
139
- function readInnerWorkState() {
140
- const defaultJob = {
141
- status: "idle",
142
- content: null,
143
- origin: null,
144
- mode: "reflect",
145
- obligationStatus: null,
146
- surfacedResult: null,
147
- queuedAt: null,
148
- startedAt: null,
149
- surfacedAt: null,
150
- };
151
- try {
152
- const agentRoot = (0, identity_1.getAgentRoot)();
153
- const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
154
- const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
155
- const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
156
- const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
157
- const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
158
- // Derive obligationPending from both the pending message field and the obligation store
159
- const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
160
- return {
161
- status: dialogStatus.processing === "started" ? "running" : "idle",
162
- hasPending: dialogStatus.queue !== "clear",
163
- origin: dialogStatus.origin,
164
- contentSnippet: dialogStatus.contentSnippet,
165
- obligationPending: dialogStatus.obligationPending || storeObligationPending,
166
- job,
167
- };
168
- }
169
- catch {
170
- return {
171
- status: "idle",
172
- hasPending: false,
173
- job: defaultJob,
174
- };
175
- }
176
- }
177
106
  // ── Pipeline ──────────────────────────────────────────────────────
178
107
  let _lastSessionKey = null;
179
108
  async function handleInboundTurn(input) {
@@ -377,100 +306,29 @@ async function handleInboundTurn(input) {
377
306
  }
378
307
  }
379
308
  /* v8 ignore stop */
380
- const activeBridges = (0, manager_1.createBridgeManager)().findBridgesForSession({
381
- friendId: currentSession.friendId,
382
- channel: currentSession.channel,
383
- 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,
384
314
  });
315
+ // Propagate sync failure from pre-turn pull
316
+ ctx.syncFailure = syncFailure;
317
+ const { activeBridges, sessionActivity, pendingObligations, codingSessions, otherCodingSessions } = ctx;
385
318
  const bridgeContext = (0, manager_1.formatBridgeContext)(activeBridges) || undefined;
386
- let sessionActivity = [];
387
- try {
388
- const agentRoot = (0, identity_1.getAgentRoot)();
389
- sessionActivity = (0, session_activity_1.listSessionActivity)({
390
- sessionsDir: `${agentRoot}/state/sessions`,
391
- friendsDir: `${agentRoot}/friends`,
392
- agentName: (0, identity_1.getAgentName)(),
393
- currentSession: {
394
- friendId: currentSession.friendId,
395
- channel: currentSession.channel,
396
- key: currentSession.key,
397
- },
398
- });
399
- }
400
- catch {
401
- sessionActivity = [];
402
- }
403
- let targetCandidates = [];
404
- try {
405
- if (input.channel !== "inner") {
406
- const agentRoot = (0, identity_1.getAgentRoot)();
407
- targetCandidates = await (0, target_resolution_1.listTargetSessionCandidates)({
408
- sessionsDir: `${agentRoot}/state/sessions`,
409
- friendsDir: `${agentRoot}/friends`,
410
- agentName: (0, identity_1.getAgentName)(),
411
- currentSession: {
412
- friendId: currentSession.friendId,
413
- channel: currentSession.channel,
414
- key: currentSession.key,
415
- },
416
- friendStore: input.friendStore,
417
- });
418
- }
419
- }
420
- catch {
421
- targetCandidates = [];
422
- }
423
- let pendingObligations = [];
424
- try {
425
- pendingObligations = (0, obligations_1.readPendingObligations)((0, identity_1.getAgentRoot)());
426
- }
427
- catch {
428
- pendingObligations = [];
429
- }
430
- let codingSessions = [];
431
- let otherCodingSessions = [];
432
- try {
433
- const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
434
- .listSessions()
435
- .filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
436
- codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === currentSession.friendId
437
- && session.originSession.channel === currentSession.channel
438
- && session.originSession.key === currentSession.key);
439
- otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === currentSession.friendId
440
- && session.originSession.channel === currentSession.channel
441
- && session.originSession.key === currentSession.key));
442
- }
443
- catch {
444
- codingSessions = [];
445
- otherCodingSessions = [];
446
- }
447
319
  const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
448
320
  currentSession,
449
321
  currentObligation,
450
322
  mustResolveBeforeHandoff,
451
- inner: readInnerWorkState(),
323
+ inner: ctx.innerWorkState,
452
324
  bridges: activeBridges,
453
325
  codingSessions,
454
326
  otherCodingSessions,
455
327
  pendingObligations,
456
- taskBoard: (() => {
457
- try {
458
- return (0, tasks_1.getTaskModule)().getBoard();
459
- }
460
- catch {
461
- return emptyTaskBoard();
462
- }
463
- })(),
328
+ taskBoard: ctx.taskBoard,
464
329
  friendActivity: sessionActivity,
465
- targetCandidates,
466
- innerReturnObligations: (() => {
467
- try {
468
- return (0, obligations_1.listActiveReturnObligations)((0, identity_1.getAgentName)());
469
- }
470
- catch {
471
- return [];
472
- }
473
- })(),
330
+ targetCandidates: ctx.targetCandidates,
331
+ innerReturnObligations: ctx.returnObligations,
474
332
  });
475
333
  const delegationDecision = (0, delegation_1.decideDelegation)({
476
334
  channel: input.channel,
@@ -501,8 +359,7 @@ async function handleInboundTurn(input) {
501
359
  try {
502
360
  const agentRoot = (0, identity_1.getAgentRoot)();
503
361
  const agentName = (0, identity_1.getAgentName)();
504
- const recentEpisodes = (0, episodes_1.readRecentEpisodes)(agentRoot, { limit: 20 });
505
- const activeCares = (0, cares_1.readActiveCares)(agentRoot);
362
+ const { recentEpisodes, activeCares } = ctx;
506
363
  const tempoState = (0, tempo_1.deriveTempo)({
507
364
  activeSessions: sessionActivity.length + 1,
508
365
  openObligations: pendingObligations.length,
@@ -570,6 +427,12 @@ async function handleInboundTurn(input) {
570
427
  setMustResolveBeforeHandoff: (value) => {
571
428
  mustResolveBeforeHandoff = value;
572
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,
573
436
  toolContext: {
574
437
  /* v8 ignore next -- default no-op signin satisfies interface; real signin injected by sense adapter @preserve */
575
438
  signin: async () => undefined,
@@ -1,35 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.surfaceToolDef = void 0;
4
3
  exports.handleSurface = handleSurface;
5
4
  const attention_queue_1 = require("./attention-queue");
6
5
  const runtime_1 = require("../nerves/runtime");
7
- // ── Tool definition ──────────────────────────────────────────────
8
- exports.surfaceToolDef = {
9
- type: "function",
10
- function: {
11
- name: "surface",
12
- description: "share a thought outward — deliver an answer, ask a follow-up, or surface progress to whoever needs to hear it. pass delegationId to address a held thought (see your attention queue above), or friendId for spontaneous outreach. does not end your turn.",
13
- parameters: {
14
- type: "object",
15
- properties: {
16
- content: {
17
- type: "string",
18
- description: "the message to deliver",
19
- },
20
- delegationId: {
21
- type: "string",
22
- description: "ID from your attention queue — addresses a specific held thought",
23
- },
24
- friendId: {
25
- type: "string",
26
- description: "friend to reach out to spontaneously (when not addressing a held thought)",
27
- },
28
- },
29
- required: ["content"],
30
- },
31
- },
32
- };
33
6
  async function handleSurface(input) {
34
7
  const { content, delegationId, friendId, queue, routeToFriend, advanceObligation, fulfillHeartObligation } = input;
35
8
  // Resolve target friend
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.203",
3
+ "version": "0.1.0-alpha.205",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",