@ouro.bot/cli 0.1.0-alpha.665 → 0.1.0-alpha.666

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.
@@ -6,8 +6,9 @@
6
6
  <meta name="color-scheme" content="dark" />
7
7
  <title>Ouro Mailbox</title>
8
8
  <meta name="description" content="The daemon-hosted shared orientation surface for agents alive on this machine." />
9
- <script type="module" crossorigin src="/assets/index-BZ60na8O.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-DG6Xf5uL.css">
9
+ <script type="module" crossorigin src="/assets/index-CaTIFDmv.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-CcN1XpQ9.js">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-Du_9G9WO.css">
11
12
  </head>
12
13
  <body>
13
14
  <div id="app"></div>
@@ -1,13 +1,57 @@
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
  exports.notesToolDefinitions = void 0;
4
37
  const child_process_1 = require("child_process");
38
+ const path = __importStar(require("path"));
5
39
  const skills_1 = require("./skills");
6
40
  const config_1 = require("../heart/config");
7
41
  const runtime_1 = require("../nerves/runtime");
42
+ const identity_1 = require("../heart/identity");
8
43
  const diary_1 = require("../mind/diary");
44
+ const record_paths_1 = require("../mind/record-paths");
9
45
  const provenance_trust_1 = require("../mind/provenance-trust");
10
46
  const CLAUDE_READ_ONLY_TOOLS = "Read,Grep,Glob,LS";
47
+ function bundleRelativeLocator(filePath, ctx) {
48
+ const agentRoot = ctx?.agentRoot ?? (0, identity_1.getAgentRoot)();
49
+ const relativePath = path.relative(agentRoot, filePath);
50
+ if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
51
+ return relativePath.split(path.sep).join("/");
52
+ }
53
+ return filePath.split(path.sep).join("/");
54
+ }
11
55
  exports.notesToolDefinitions = [
12
56
  {
13
57
  tool: {
@@ -252,6 +296,12 @@ exports.notesToolDefinitions = [
252
296
  about: typeof a.about === "string" ? a.about : undefined,
253
297
  provenance,
254
298
  });
299
+ if (result.added > 0) {
300
+ ctx?.habitSession?.recordProducedRef?.({
301
+ kind: "desk_record",
302
+ locator: bundleRelativeLocator(path.join((0, record_paths_1.resolveRecordDiaryRoot)(), "facts.jsonl"), ctx),
303
+ });
304
+ }
255
305
  return `saved diary entry (added=${result.added}, skipped=${result.skipped})`;
256
306
  },
257
307
  summaryKeys: ["entry", "about"],
@@ -42,6 +42,7 @@ const note_search_1 = require("../mind/note-search");
42
42
  const record_paths_1 = require("../mind/record-paths");
43
43
  const types_1 = require("../mind/friends/types");
44
44
  const runtime_1 = require("../nerves/runtime");
45
+ const identity_1 = require("../heart/identity");
45
46
  const NOTES_INDEX_VERSION = 1;
46
47
  const NOTE_SLUG_MAX_CHARS = 40;
47
48
  const DEFAULT_LIMIT = 5;
@@ -61,6 +62,14 @@ function hasRecordReadTrust(ctx) {
61
62
  const friend = ctx?.context?.friend;
62
63
  return Boolean(friend) && (0, types_1.isTrustedLevel)(friend?.trustLevel);
63
64
  }
65
+ function bundleRelativeLocator(filePath, ctx) {
66
+ const agentRoot = ctx?.agentRoot ?? (0, identity_1.getAgentRoot)();
67
+ const relativePath = path.relative(agentRoot, filePath);
68
+ if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
69
+ return relativePath.split(path.sep).join("/");
70
+ }
71
+ return filePath.split(path.sep).join("/");
72
+ }
64
73
  function normalizeTags(value) {
65
74
  if (value === undefined || value === null)
66
75
  return undefined;
@@ -371,6 +380,10 @@ exports.recordToolDefinitions = [
371
380
  const savedPath = ensureUniquePath(notesDir, date, slugForContent(content));
372
381
  fs.writeFileSync(savedPath, renderNote(createdAt, cappedContent, tags), "utf8");
373
382
  await updateIndexForSavedNote(notesDir, indexPath, savedPath, provider);
383
+ ctx?.habitSession?.recordProducedRef?.({
384
+ kind: "desk_record",
385
+ locator: bundleRelativeLocator(savedPath, ctx),
386
+ });
374
387
  (0, runtime_1.emitNervesEvent)({
375
388
  component: "repertoire",
376
389
  event: "repertoire.record_note_saved",
@@ -127,6 +127,19 @@ function renderCrossChatDeliveryStatus(target, result) {
127
127
  outcomeText: `${lead}\n${result.detail}`,
128
128
  }));
129
129
  }
130
+ function normalizeHabitSendStatus(status) {
131
+ return status === "queued_for_later" ? "queued" : status;
132
+ }
133
+ function recordHabitSendAttempt(ctx, args, result) {
134
+ ctx?.habitSession?.recordSurfaceAttempt?.({
135
+ recipient: args.friendId,
136
+ channel: args.channel,
137
+ reason: result.status === "blocked" ? "blocked" : result.status === "failed" ? "other" : "status",
138
+ result: normalizeHabitSendStatus(result.status),
139
+ rawStatus: result.rawStatus ?? result.status,
140
+ ...(result.status === "blocked" || result.status === "failed" ? { error: result.detail } : {}),
141
+ });
142
+ }
130
143
  async function deliverVoiceChannelMessage(request, agentName, initialAudio) {
131
144
  const result = await (0, outbound_1.placeTrustedFriendVoiceOutboundCall)({
132
145
  agentName,
@@ -724,6 +737,7 @@ exports.sessionToolDefinitions = [
724
737
  voice: async (request) => deliverVoiceChannelMessage(request, agentName, voiceInitialAudio),
725
738
  },
726
739
  });
740
+ recordHabitSendAttempt(ctx, { friendId, channel, key }, deliveryResult);
727
741
  return renderCrossChatDeliveryStatus(`${friendId} on ${channel}/${key}`, deliveryResult);
728
742
  },
729
743
  riskProfile: sendMessageRiskProfile,
@@ -332,6 +332,17 @@ exports.surfaceToolDefinition = {
332
332
  }
333
333
  /* v8 ignore stop */
334
334
  },
335
+ onRouteResult: ({ targetFriendId, queueItem, result }) => {
336
+ ctx?.habitSession?.recordSurfaceAttempt?.({
337
+ recipient: targetFriendId,
338
+ channel: queueItem?.channel ?? args.channel ?? "surface",
339
+ reason: result.status === "failed" ? "blocked" : "answer",
340
+ result: result.status,
341
+ rawStatus: result.status,
342
+ ...(queueItem ? { routeKind: "originator" } : {}),
343
+ ...(result.status === "failed" ? { error: result.detail ?? "surface route failed" } : {}),
344
+ });
345
+ },
335
346
  });
336
347
  },
337
348
  summaryKeys: ["content", "delegationId", "friendId", "channel"],
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
4
4
  exports.resetMcpDefinitions = resetMcpDefinitions;
5
5
  exports.getToolsForChannel = getToolsForChannel;
6
+ exports.shellRiskProfile = shellRiskProfile;
7
+ exports.riskProfileForTool = riskProfileForTool;
8
+ exports.riskProfileForToolName = riskProfileForToolName;
6
9
  exports.execTool = execTool;
7
10
  exports.summarizeArgs = summarizeArgs;
8
11
  exports.buildToolResultSummary = buildToolResultSummary;
@@ -202,6 +205,10 @@ function riskProfileForTool(def, name, args) {
202
205
  return def.riskProfile(args);
203
206
  return def.riskProfile ?? { mutates: "none", risk: "low" };
204
207
  }
208
+ function riskProfileForToolName(name, args) {
209
+ const def = findDefinition(name);
210
+ return def ? riskProfileForTool(def, name, args) : null;
211
+ }
205
212
  function orientationHoldMessage(name, profile, reason) {
206
213
  return `orientation hold: ${reason} Available: orientation_get plus read-only inspection tools like trip_status, query_session, read_config, read_file, grep, search_facts, consult_diary, and consult_notes. Resolve the referent/correction, then retry ${name} if the action is still correct. Blocked ${mutationKindsFor(profile).join(", ")}. ${profile.reason}.`;
207
214
  }
@@ -36,13 +36,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.HEARTBEAT_OK_REST_SUPPRESSION_MS = exports.HABIT_RECURSION_BURST_THRESHOLD = exports.HABIT_RECURSION_BURST_WINDOW_MS = exports.HABIT_RECURSION_MIN_INTERVAL_MS = exports.MAX_CONSECUTIVE_INSTINCT_TURNS = void 0;
37
37
  exports.createInnerDialogWorker = createInnerDialogWorker;
38
38
  exports.startInnerDialogWorker = startInnerDialogWorker;
39
+ const fs = __importStar(require("fs"));
39
40
  const path = __importStar(require("path"));
40
41
  const inner_dialog_1 = require("./inner-dialog");
41
42
  const runtime_1 = require("../nerves/runtime");
42
43
  const identity_1 = require("../heart/identity");
43
44
  const pending_1 = require("../mind/pending");
44
- const habit_runtime_state_1 = require("../heart/habits/habit-runtime-state");
45
+ const habit_parser_1 = require("../heart/habits/habit-parser");
46
+ const habit_session_1 = require("../heart/habits/habit-session");
45
47
  const flight_recorder_1 = require("../arc/flight-recorder");
48
+ const store_file_1 = require("../mind/friends/store-file");
49
+ const tools_base_1 = require("../repertoire/tools-base");
50
+ const tools_surface_1 = require("../repertoire/tools-surface");
51
+ const tools_1 = require("../repertoire/tools");
46
52
  /**
47
53
  * Cap on consecutive `instinct` follow-on turns triggered by `hasPendingWork()`
48
54
  * with no externally-queued work in between. Without this cap, a turn that
@@ -83,62 +89,87 @@ function isHeartbeatOkRestResult(result) {
83
89
  const maybeResult = result;
84
90
  return maybeResult.turnOutcome === "rested" && maybeResult.restStatus === "HEARTBEAT_OK";
85
91
  }
86
- function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runInnerDialogTurn)(options), hasPendingWork = () => (0, pending_1.hasPendingMessages)((0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)())), nowSource = () => Date.now()) {
92
+ function fallbackHabitFile(habitName) {
93
+ return {
94
+ name: habitName,
95
+ title: habitName,
96
+ cadence: null,
97
+ status: "active",
98
+ lastRun: null,
99
+ created: null,
100
+ tools: [],
101
+ origin: null,
102
+ surface: { family: false, originator: false, extra: [] },
103
+ body: "",
104
+ };
105
+ }
106
+ function readHabitForRun(agentRoot, habitName, errors) {
107
+ const habitPath = path.join(agentRoot, "habits", `${habitName}.md`);
108
+ try {
109
+ return (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath);
110
+ }
111
+ catch (error) {
112
+ const reason = error instanceof Error ? error.message : String(error);
113
+ errors.push(`habit file could not be read: ${reason}`);
114
+ (0, runtime_1.emitNervesEvent)({
115
+ level: "warn",
116
+ component: "senses",
117
+ event: "senses.habit_file_read_error",
118
+ message: "habit file could not be read for habit session",
119
+ meta: { habitName, habitPath, reason },
120
+ });
121
+ return fallbackHabitFile(habitName);
122
+ }
123
+ }
124
+ async function prepareHabitRun(habitName, trigger, startedAt) {
125
+ const agentRoot = (0, identity_1.getAgentRoot)();
126
+ const errors = [];
127
+ const habit = readHabitForRun(agentRoot, habitName, errors);
128
+ const runId = (0, flight_recorder_1.createHabitRunId)(habitName, new Date(startedAt));
129
+ const paths = (0, habit_session_1.createHabitSessionPaths)(agentRoot, runId, habit.name);
130
+ const friendStore = new store_file_1.FileFriendStore(path.join(agentRoot, "friends"));
131
+ const permissionEnvelope = await (0, habit_session_1.normalizeHabitPermissionEnvelope)(habit, { agentRoot, friendStore });
132
+ const toolPolicy = (0, habit_session_1.filterHabitToolsForEnvelope)([...tools_base_1.baseToolDefinitions, tools_surface_1.surfaceToolDefinition], habit.tools ?? null, permissionEnvelope, riskProfileForHabitPolicy);
133
+ return {
134
+ agentRoot,
135
+ habit,
136
+ runId,
137
+ trigger,
138
+ startedAt,
139
+ paths,
140
+ permissionEnvelope,
141
+ toolPolicy,
142
+ friendStore,
143
+ results: [],
144
+ errors,
145
+ producedRefs: [],
146
+ surfaceAttempts: [],
147
+ };
148
+ }
149
+ function riskProfileForHabitPolicy(definition, name) {
150
+ const probeArgs = name === "shell" ? { command: "touch /tmp/habit-policy-probe" } : {};
151
+ return (0, tools_1.riskProfileForTool)(definition, name, probeArgs);
152
+ }
153
+ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runInnerDialogTurn)(options), hasPendingWork = (pendingDir) => (0, pending_1.hasPendingMessages)(pendingDir ?? (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)())), nowSource = () => Date.now()) {
87
154
  let running = false;
88
155
  const queue = [];
89
156
  const lastFireByHabit = new Map();
90
157
  const recentHabitFires = [];
91
158
  let heartbeatOkRestedAt = null;
92
- function habitOutcomeForTurn(result, errors) {
93
- if (errors.length > 0)
94
- return { outcome: "error", producedRefs: [] };
95
- const toolNames = new Set();
96
- if (result && typeof result === "object" && Array.isArray(result.messages)) {
97
- for (const message of result.messages) {
98
- const toolCalls = message.tool_calls;
99
- if (!Array.isArray(toolCalls))
100
- continue;
101
- for (const call of toolCalls) {
102
- const functionName = call.function?.name;
103
- if (typeof functionName === "string")
104
- toolNames.add(functionName);
105
- }
106
- }
107
- }
108
- if (toolNames.has("send_message") || toolNames.has("surface")) {
109
- return { outcome: "surfaced", producedRefs: [{ kind: "surface", locator: "tool:send_message_or_surface" }] };
110
- }
111
- if (toolNames.has("diary_write") || toolNames.has("note")) {
112
- return { outcome: "wrote_record", producedRefs: [{ kind: "desk_record", locator: "desk/_record" }] };
113
- }
114
- if ([...toolNames].some((name) => name.startsWith("mcp__desk__"))) {
115
- return { outcome: "updated_desk", producedRefs: [{ kind: "desk_task", locator: "desk/" }] };
116
- }
117
- return { outcome: "no_change", producedRefs: [] };
118
- }
119
- function recordHabitCompletion(habitName, startedAt = new Date(nowSource()).toISOString(), endedAt = startedAt, trigger = "overdue", result, errors = []) {
120
- try {
121
- const agentRoot = (0, identity_1.getAgentRoot)();
122
- (0, habit_runtime_state_1.recordHabitRun)(agentRoot, habitName, endedAt, {
123
- definitionPath: path.join(agentRoot, "habits", `${habitName}.md`),
124
- });
125
- const { outcome, producedRefs } = habitOutcomeForTurn(result, errors);
126
- (0, flight_recorder_1.writeHabitRunReceipt)(agentRoot, {
127
- schemaVersion: 1,
128
- runId: (0, flight_recorder_1.createHabitRunId)(habitName, new Date(startedAt)),
129
- habitName,
130
- trigger,
131
- startedAt,
132
- endedAt,
133
- outcome,
134
- producedRefs,
135
- surfaceAttempts: [],
136
- errors,
137
- });
138
- }
139
- catch {
140
- // Habit file/state may be unavailable during the turn — skip gracefully
141
- }
159
+ function recordHabitCompletion(habitRun, endedAt = habitRun.startedAt) {
160
+ (0, habit_session_1.completeHabitRun)({
161
+ agentRoot: habitRun.agentRoot,
162
+ habit: habitRun.habit,
163
+ runId: habitRun.runId,
164
+ trigger: habitRun.trigger,
165
+ startedAt: habitRun.startedAt,
166
+ endedAt,
167
+ permissionEnvelope: habitRun.permissionEnvelope,
168
+ toolPolicy: habitRun.toolPolicy,
169
+ producedRefs: habitRun.producedRefs,
170
+ surfaceAttempts: habitRun.surfaceAttempts,
171
+ errors: habitRun.errors,
172
+ });
142
173
  }
143
174
  function clearHeartbeatRestShield() {
144
175
  heartbeatOkRestedAt = null;
@@ -154,9 +185,11 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
154
185
  }
155
186
  return true;
156
187
  }
157
- function reuseHeartbeatOkRest(habitName) {
188
+ async function reuseHeartbeatOkRest(habitName) {
158
189
  const nowIso = new Date(nowSource()).toISOString();
159
- recordHabitCompletion(habitName, nowIso, nowIso, "overdue", { turnOutcome: "rested", restStatus: "HEARTBEAT_OK" });
190
+ const habitRun = await prepareHabitRun(habitName, "overdue", nowIso);
191
+ habitRun.results.push({ turnOutcome: "rested", restStatus: "HEARTBEAT_OK" });
192
+ recordHabitCompletion(habitRun, nowIso);
160
193
  (0, runtime_1.emitNervesEvent)({
161
194
  level: "info",
162
195
  component: "senses",
@@ -220,19 +253,54 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
220
253
  let nextHabitName = habitName;
221
254
  let nextAwaitName = awaitName;
222
255
  let nextTrigger = trigger;
256
+ let nextHabitRun = null;
223
257
  let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
224
258
  runLoop: do {
225
259
  const currentReason = nextReason;
226
260
  const currentHabitName = nextHabitName;
227
261
  const currentTrigger = nextTrigger ?? "overdue";
228
- const habitStartedAt = currentReason === "habit" && currentHabitName ? new Date(nowSource()).toISOString() : null;
262
+ const currentHabitRun = currentReason === "habit" && currentHabitName
263
+ ? nextHabitRun && nextHabitRun.habit.name === currentHabitName
264
+ ? nextHabitRun
265
+ : await prepareHabitRun(currentHabitName, currentTrigger, new Date(nowSource()).toISOString())
266
+ : null;
267
+ nextHabitRun = null;
268
+ let currentHabitRunFinalized = false;
269
+ const finalizeCurrentHabitRun = () => {
270
+ if (!currentHabitRun || currentHabitRunFinalized)
271
+ return;
272
+ recordHabitCompletion(currentHabitRun, new Date(nowSource()).toISOString());
273
+ currentHabitRunFinalized = true;
274
+ };
229
275
  const turnErrors = [];
230
276
  if (!(currentReason === "habit" && currentHabitName === "heartbeat")) {
231
277
  clearHeartbeatRestShield();
232
278
  }
233
279
  let turnResult;
234
280
  try {
235
- turnResult = await runTurn({ reason: nextReason, taskId: nextTaskId, habitName: nextHabitName, awaitName: nextAwaitName });
281
+ const turnOptions = {
282
+ reason: nextReason,
283
+ taskId: nextTaskId,
284
+ habitName: nextHabitName,
285
+ awaitName: nextAwaitName,
286
+ ...(currentHabitRun
287
+ ? {
288
+ trigger: currentHabitRun.trigger,
289
+ habitSession: {
290
+ runId: currentHabitRun.runId,
291
+ sessionPath: currentHabitRun.paths.sessionPath,
292
+ pendingDir: currentHabitRun.paths.pendingDir,
293
+ permissionEnvelope: currentHabitRun.permissionEnvelope,
294
+ toolPolicy: currentHabitRun.toolPolicy,
295
+ friendStore: currentHabitRun.friendStore,
296
+ recordProducedRef: (ref) => { currentHabitRun.producedRefs.push(ref); },
297
+ recordSurfaceAttempt: (attempt) => { currentHabitRun.surfaceAttempts.push(attempt); },
298
+ recordError: (error) => { currentHabitRun.errors.push(error); },
299
+ },
300
+ }
301
+ : {}),
302
+ };
303
+ turnResult = await runTurn(turnOptions);
236
304
  }
237
305
  catch (error) {
238
306
  clearHeartbeatRestShield();
@@ -251,21 +319,23 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
251
319
  if (currentReason === "habit" && currentHabitName === "heartbeat") {
252
320
  heartbeatOkRestedAt = isHeartbeatOkRestResult(turnResult) ? nowSource() : null;
253
321
  }
254
- // Record lastRun after a habit turn without dirtying the tracked habit file.
255
- if (currentReason === "habit" && currentHabitName && habitStartedAt) {
256
- recordHabitCompletion(currentHabitName, habitStartedAt, new Date(nowSource()).toISOString(), currentTrigger, turnResult, turnErrors);
322
+ if (currentHabitRun) {
323
+ currentHabitRun.results.push(turnResult);
324
+ currentHabitRun.errors.push(...turnErrors);
257
325
  }
258
326
  // Drain queue first. Externally-queued work resets the instinct cap
259
327
  // because a real outside trigger arrived between turns.
260
328
  while (queue.length > 0) {
261
329
  const next = queue.shift();
262
330
  if (next.reason === "habit" && next.habitName === "heartbeat" && shouldReuseHeartbeatOkRest(next.habitName)) {
263
- reuseHeartbeatOkRest(next.habitName);
331
+ finalizeCurrentHabitRun();
332
+ await reuseHeartbeatOkRest(next.habitName);
264
333
  continue;
265
334
  }
266
335
  if (!(next.reason === "habit" && next.habitName === "heartbeat")) {
267
336
  clearHeartbeatRestShield();
268
337
  }
338
+ finalizeCurrentHabitRun();
269
339
  nextReason = next.reason;
270
340
  nextTaskId = next.taskId;
271
341
  nextHabitName = next.habitName;
@@ -278,7 +348,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
278
348
  // tool that writes to the inner-dialog pending dir during a turn
279
349
  // would cause hasPendingWork() to be true here, producing a
280
350
  // self-sustaining "instinct" loop with no external input. Cap it.
281
- if (hasPendingWork()) {
351
+ if (hasPendingWork(currentHabitRun?.paths.pendingDir)) {
282
352
  clearHeartbeatRestShield();
283
353
  if (consecutiveInstinctTurns >= exports.MAX_CONSECUTIVE_INSTINCT_TURNS) {
284
354
  (0, runtime_1.emitNervesEvent)({
@@ -292,16 +362,30 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
292
362
  lastReason: nextReason,
293
363
  },
294
364
  });
365
+ finalizeCurrentHabitRun();
295
366
  break;
296
367
  }
297
- consecutiveInstinctTurns += 1;
298
- nextReason = "instinct";
299
- nextTaskId = undefined;
300
- nextHabitName = undefined;
301
- nextAwaitName = undefined;
302
- nextTrigger = undefined;
368
+ if (currentReason === "habit" && currentHabitName && currentHabitRun) {
369
+ consecutiveInstinctTurns += 1;
370
+ nextReason = "habit";
371
+ nextTaskId = undefined;
372
+ nextHabitName = currentHabitName;
373
+ nextAwaitName = undefined;
374
+ nextTrigger = currentTrigger;
375
+ nextHabitRun = currentHabitRun;
376
+ }
377
+ else {
378
+ finalizeCurrentHabitRun();
379
+ consecutiveInstinctTurns += 1;
380
+ nextReason = "instinct";
381
+ nextTaskId = undefined;
382
+ nextHabitName = undefined;
383
+ nextAwaitName = undefined;
384
+ nextTrigger = undefined;
385
+ }
303
386
  continue;
304
387
  }
388
+ finalizeCurrentHabitRun();
305
389
  break;
306
390
  } while (true);
307
391
  }
@@ -317,7 +401,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
317
401
  /* v8 ignore next -- defensive fallback: live habit dispatch always sets habitName @preserve */
318
402
  const habitName = maybeMessage.habitName ?? "(unnamed)";
319
403
  if (shouldReuseHeartbeatOkRest(habitName)) {
320
- reuseHeartbeatOkRest(habitName);
404
+ await reuseHeartbeatOkRest(habitName);
321
405
  return;
322
406
  }
323
407
  recordHabitFireForRecursion(habitName);
@@ -335,7 +419,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
335
419
  if (maybeMessage.type === "heartbeat") {
336
420
  // Backward compatibility: heartbeat -> habit/heartbeat
337
421
  if (shouldReuseHeartbeatOkRest("heartbeat")) {
338
- reuseHeartbeatOkRest("heartbeat");
422
+ await reuseHeartbeatOkRest("heartbeat");
339
423
  return;
340
424
  }
341
425
  recordHabitFireForRecursion("heartbeat");
@@ -645,7 +645,7 @@ function buildHabitSurfacePolicy(origin, surface) {
645
645
  async function runInnerDialogTurn(options) {
646
646
  const now = options?.now ?? (() => new Date());
647
647
  const reason = options?.reason ?? "instinct";
648
- const sessionFilePath = innerDialogSessionPath();
648
+ const sessionFilePath = options?.habitSession?.sessionPath ?? innerDialogSessionPath();
649
649
  const agentName = (0, identity_1.getAgentName)();
650
650
  writeInnerDialogRuntimeState(sessionFilePath, {
651
651
  status: "running",
@@ -661,7 +661,7 @@ async function runInnerDialogTurn(options) {
661
661
  resting: false,
662
662
  lastHeartbeatAt: now().toISOString(),
663
663
  };
664
- const pendingDir = (0, pending_1.getInnerDialogPendingDir)(agentName);
664
+ const pendingDir = options?.habitSession?.pendingDir ?? (0, pending_1.getInnerDialogPendingDir)(agentName);
665
665
  const shouldUseHeldReturnWake = !options?.taskId && reason !== "habit" && reason !== "await"
666
666
  ? (0, obligations_1.listActiveReturnObligations)(agentName).length > 0
667
667
  : false;
@@ -669,7 +669,7 @@ async function runInnerDialogTurn(options) {
669
669
  let userContent;
670
670
  let habitTools;
671
671
  let habitParsedSuccessfully = false;
672
- if (existingMessages.length === 0) {
672
+ if (existingMessages.length === 0 && !(reason === "habit" && options?.habitName)) {
673
673
  // Fresh session: bootstrap message with non-canonical cleanup nudge
674
674
  const aspirations = readAspirations((0, identity_1.getAgentRoot)());
675
675
  const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
@@ -917,7 +917,9 @@ async function runInnerDialogTurn(options) {
917
917
  toolContext: {
918
918
  signin: async () => undefined,
919
919
  delegatedOrigins: attentionQueue,
920
+ ...(options?.habitSession ? { habitSession: options.habitSession } : {}),
920
921
  },
922
+ ...(options?.habitSession ? { habitSession: options.habitSession } : {}),
921
923
  },
922
924
  });
923
925
  // Post-turn routeDelegatedCompletion removed: delivery is now inline via surface tool.
@@ -48,7 +48,6 @@ const continuity_1 = require("./continuity");
48
48
  const manager_1 = require("../heart/bridges/manager");
49
49
  const identity_1 = require("../heart/identity");
50
50
  const auth_flow_1 = require("../heart/auth/auth-flow");
51
- const socket_client_1 = require("../heart/daemon/socket-client");
52
51
  const active_work_1 = require("../heart/active-work");
53
52
  const delegation_1 = require("../heart/delegation");
54
53
  const obligations_1 = require("../arc/obligations");
@@ -949,14 +948,6 @@ async function handleInboundTurn(input) {
949
948
  friendId: resolvedContext.friend.id,
950
949
  },
951
950
  });
952
- // DRY cross-session awareness: notify inner dialog that activity happened on another channel
953
- // Inner dialog's next checkpoint will include this session's state
954
- if (input.channel !== "inner") {
955
- try {
956
- (0, socket_client_1.requestInnerWake)((0, identity_1.getAgentName)(), existingToolContext?.daemonSocketPath).catch(/* v8 ignore next */ () => { });
957
- }
958
- catch { /* getAgentName may fail in test environments */ }
959
- }
960
951
  return {
961
952
  resolvedContext,
962
953
  gateResult,
@@ -4,7 +4,7 @@ exports.handleSurface = handleSurface;
4
4
  const attention_queue_1 = require("./attention-queue");
5
5
  const runtime_1 = require("../nerves/runtime");
6
6
  async function handleSurface(input) {
7
- const { content, delegationId, friendId, deliveryHint, queue, routeToFriend, advanceObligation, completePonderPacket, fulfillHeartObligation, } = input;
7
+ const { content, delegationId, friendId, deliveryHint, queue, routeToFriend, advanceObligation, completePonderPacket, fulfillHeartObligation, onRouteResult, } = input;
8
8
  // Resolve target friend
9
9
  let targetFriendId;
10
10
  let queueItem;
@@ -68,6 +68,7 @@ async function handleSurface(input) {
68
68
  ...(result.detail ? { detail: result.detail } : {}),
69
69
  },
70
70
  });
71
+ onRouteResult?.({ targetFriendId, queueItem, result });
71
72
  // On successful routing with delegationId:
72
73
  // 1. Advance obligation to "returned" (disk FIRST — crash safety)
73
74
  // 2. Dequeue from process-local queue (AFTER obligation advance)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.665",
3
+ "version": "0.1.0-alpha.666",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",