@ouro.bot/cli 0.1.0-alpha.141 → 0.1.0-alpha.142

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,14 @@
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.142",
6
+ "changes": [
7
+ "Surface tool fulfills heart obligations on successful routing: findPendingObligationForOrigin + fulfillObligation called after inner obligation advance, wrapped in try/catch.",
8
+ "New fulfillHeartObligation callback on HandleSurfaceInput — origin-based lookup independent of inner obligationId.",
9
+ "ouro inner status command: reads runtime.json, journal dir, heartbeat cadence, attention count. Shows last turn, status, heartbeat health, journal listing, held thoughts."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.141",
6
14
  "changes": [
@@ -295,6 +295,7 @@ function usage() {
295
295
  " ouro friend create --name <name> [--trust <level>] [--agent <name>]",
296
296
  " ouro friend update <id> --trust <level> [--agent <name>]",
297
297
  " ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
298
+ " ouro inner [--agent <name>]",
298
299
  " ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
299
300
  " ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
300
301
  " ouro whoami [--agent <name>]",
@@ -987,6 +988,10 @@ function parseOuroCommand(args) {
987
988
  return parseThoughtsCommand(args.slice(1));
988
989
  if (head === "attention")
989
990
  return parseAttentionCommand(args.slice(1));
991
+ if (head === "inner") {
992
+ const { agent } = extractAgentFlag(args.slice(1));
993
+ return { kind: "inner.status", ...(agent ? { agent } : {}) };
994
+ }
990
995
  if (head === "chat") {
991
996
  if (!second)
992
997
  throw new Error(`Usage\n${usage()}`);
@@ -2607,6 +2612,89 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2607
2612
  }
2608
2613
  }
2609
2614
  /* v8 ignore stop */
2615
+ // ── inner dialog status (local, no daemon socket needed) ──
2616
+ /* v8 ignore start -- inner status handler: requires real agent state on disk @preserve */
2617
+ if (command.kind === "inner.status") {
2618
+ try {
2619
+ const agentName = command.agent ?? (0, identity_1.getAgentName)();
2620
+ const agentRoot = (0, identity_1.getAgentRoot)(agentName);
2621
+ const { buildInnerStatusOutput } = await Promise.resolve().then(() => __importStar(require("./inner-status")));
2622
+ const { sessionPath: getSessionPath } = await Promise.resolve().then(() => __importStar(require("../config")));
2623
+ const { parseCadenceMs, DEFAULT_CADENCE_MS } = await Promise.resolve().then(() => __importStar(require("./heartbeat-timer")));
2624
+ const { parseFrontmatter } = await Promise.resolve().then(() => __importStar(require("../../repertoire/tasks/parser")));
2625
+ const { listActiveObligations } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
2626
+ // Read runtime state
2627
+ const innerSessionPath = getSessionPath("inner-dialog", "inner", "session");
2628
+ const runtimeJsonPath = path.join(path.dirname(innerSessionPath), "runtime.json");
2629
+ let runtimeState = null;
2630
+ try {
2631
+ const raw = fs.readFileSync(runtimeJsonPath, "utf-8");
2632
+ runtimeState = JSON.parse(raw);
2633
+ }
2634
+ catch { /* missing or corrupt — will show "unknown" */ }
2635
+ // Read journal files
2636
+ const journalDir = path.join(agentRoot, "journal");
2637
+ let journalFiles = [];
2638
+ try {
2639
+ const entries = fs.readdirSync(journalDir, { withFileTypes: true });
2640
+ journalFiles = entries
2641
+ .filter((e) => e.isFile() && !e.name.startsWith("."))
2642
+ .map((e) => {
2643
+ const stat = fs.statSync(path.join(journalDir, e.name));
2644
+ return { name: e.name, mtimeMs: stat.mtimeMs };
2645
+ });
2646
+ }
2647
+ catch { /* missing dir — will show (empty) */ }
2648
+ // Read heartbeat cadence
2649
+ let heartbeat = null;
2650
+ try {
2651
+ const habitsDir = path.join(agentRoot, "habits");
2652
+ const habitFiles = fs.readdirSync(habitsDir);
2653
+ const heartbeatFile = habitFiles.find((f) => f.endsWith("-heartbeat.md"));
2654
+ let cadenceMs = DEFAULT_CADENCE_MS;
2655
+ if (heartbeatFile) {
2656
+ const content = fs.readFileSync(path.join(habitsDir, heartbeatFile), "utf-8");
2657
+ const lines = content.split(/\r?\n/);
2658
+ if (lines[0]?.trim() === "---") {
2659
+ const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
2660
+ if (closing !== -1) {
2661
+ const rawFrontmatter = lines.slice(1, closing).join("\n");
2662
+ const frontmatter = parseFrontmatter(rawFrontmatter);
2663
+ const parsed = parseCadenceMs(frontmatter.cadence);
2664
+ if (parsed !== null)
2665
+ cadenceMs = parsed;
2666
+ }
2667
+ }
2668
+ }
2669
+ let lastCompletedAt = null;
2670
+ if (runtimeState?.lastCompletedAt) {
2671
+ const ms = new Date(runtimeState.lastCompletedAt).getTime();
2672
+ if (!Number.isNaN(ms))
2673
+ lastCompletedAt = ms;
2674
+ }
2675
+ heartbeat = { cadenceMs, lastCompletedAt };
2676
+ }
2677
+ catch { /* no habits — heartbeat unknown */ }
2678
+ // Attention count
2679
+ const activeObligations = listActiveObligations(agentName);
2680
+ const message = buildInnerStatusOutput({
2681
+ agentName,
2682
+ runtimeState,
2683
+ journalFiles,
2684
+ heartbeat,
2685
+ attentionCount: activeObligations.length,
2686
+ now: Date.now(),
2687
+ });
2688
+ deps.writeStdout(message);
2689
+ return message;
2690
+ }
2691
+ catch {
2692
+ const message = "error: no agent context — use --agent <name> to specify";
2693
+ deps.writeStdout(message);
2694
+ return message;
2695
+ }
2696
+ }
2697
+ /* v8 ignore stop */
2610
2698
  // ── session list (local, no daemon socket needed) ──
2611
2699
  if (command.kind === "session.list") {
2612
2700
  /* v8 ignore start -- production default: requires full identity setup @preserve */
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildInnerStatusOutput = buildInnerStatusOutput;
4
+ const runtime_1 = require("../../nerves/runtime");
5
+ function formatRelativeTime(elapsedMs) {
6
+ const minutes = Math.floor(elapsedMs / (60 * 1000));
7
+ if (minutes < 1)
8
+ return "just now";
9
+ if (minutes === 1)
10
+ return "1 minute ago";
11
+ if (minutes < 60)
12
+ return `${minutes} minutes ago`;
13
+ const hours = Math.floor(minutes / 60);
14
+ if (hours === 1)
15
+ return "1 hour ago";
16
+ return `${hours} hours ago`;
17
+ }
18
+ function formatCadence(cadenceMs) {
19
+ const minutes = Math.round(cadenceMs / (60 * 1000));
20
+ if (minutes >= 60) {
21
+ const hours = Math.round(minutes / 60);
22
+ return `${hours}h`;
23
+ }
24
+ return `${minutes}m`;
25
+ }
26
+ function buildInnerStatusOutput(input) {
27
+ const { agentName, runtimeState, journalFiles, heartbeat, attentionCount, now } = input;
28
+ const lines = [];
29
+ lines.push(`inner dialog status: ${agentName}`);
30
+ // Last turn
31
+ if (runtimeState?.lastCompletedAt) {
32
+ const lastMs = new Date(runtimeState.lastCompletedAt).getTime();
33
+ const elapsed = now - lastMs;
34
+ const relativeTime = formatRelativeTime(elapsed);
35
+ const reasonSuffix = runtimeState.reason ? ` (${runtimeState.reason})` : "";
36
+ lines.push(` last turn: ${relativeTime}${reasonSuffix}`);
37
+ }
38
+ else {
39
+ lines.push(" last turn: unknown");
40
+ }
41
+ // Status
42
+ if (runtimeState) {
43
+ const reasonSuffix = runtimeState.status === "running" && runtimeState.reason ? ` (${runtimeState.reason})` : "";
44
+ lines.push(` status: ${runtimeState.status}${reasonSuffix}`);
45
+ }
46
+ else {
47
+ lines.push(" status: unknown");
48
+ }
49
+ // Heartbeat health
50
+ if (heartbeat && heartbeat.lastCompletedAt !== null) {
51
+ const elapsed = now - heartbeat.lastCompletedAt;
52
+ const threshold = heartbeat.cadenceMs * 1.5;
53
+ const health = elapsed < threshold ? "healthy" : "overdue";
54
+ const cadenceStr = formatCadence(heartbeat.cadenceMs);
55
+ const sinceStr = formatRelativeTime(elapsed);
56
+ lines.push(` heartbeat: ${health} (cadence ${cadenceStr}, ${sinceStr})`);
57
+ }
58
+ else {
59
+ lines.push(" heartbeat: unknown");
60
+ }
61
+ // Journal
62
+ if (journalFiles.length === 0) {
63
+ lines.push(" journal: (empty)");
64
+ }
65
+ else {
66
+ lines.push(" journal:");
67
+ const sorted = [...journalFiles].sort((a, b) => b.mtimeMs - a.mtimeMs);
68
+ for (const file of sorted) {
69
+ const elapsed = now - file.mtimeMs;
70
+ const relativeTime = formatRelativeTime(elapsed);
71
+ lines.push(` - ${file.name} (${relativeTime})`);
72
+ }
73
+ }
74
+ // Attention
75
+ const thoughtWord = attentionCount === 1 ? "thought" : "thoughts";
76
+ lines.push(` attention: ${attentionCount} held ${thoughtWord}`);
77
+ (0, runtime_1.emitNervesEvent)({
78
+ component: "daemon",
79
+ event: "daemon.inner_status_read",
80
+ message: "inner dialog status read",
81
+ meta: {
82
+ agentName,
83
+ status: runtimeState?.status ?? "unknown",
84
+ journalCount: journalFiles.length,
85
+ attentionCount,
86
+ },
87
+ });
88
+ return lines.join("\n");
89
+ }
@@ -48,6 +48,7 @@ const guardrails_1 = require("./guardrails");
48
48
  const identity_1 = require("../heart/identity");
49
49
  const surface_tool_1 = require("../senses/surface-tool");
50
50
  const obligations_1 = require("../mind/obligations");
51
+ const obligations_2 = require("../heart/obligations");
51
52
  const session_activity_1 = require("../heart/session-activity");
52
53
  const path = __importStar(require("path"));
53
54
  function safeGetAgentRoot() {
@@ -207,6 +208,20 @@ const surfaceToolDefinition = {
207
208
  }
208
209
  /* v8 ignore stop */
209
210
  },
211
+ fulfillHeartObligation: (origin) => {
212
+ /* v8 ignore start -- heart obligation fulfillment: tested via surface-tool.test.ts @preserve */
213
+ try {
214
+ const agentRoot = (0, identity_1.getAgentRoot)();
215
+ const heartObligation = (0, obligations_2.findPendingObligationForOrigin)(agentRoot, origin);
216
+ if (heartObligation) {
217
+ (0, obligations_2.fulfillObligation)(agentRoot, heartObligation.id);
218
+ }
219
+ }
220
+ catch {
221
+ // swallowed — heart obligation fulfillment must never break surface delivery
222
+ }
223
+ /* v8 ignore stop */
224
+ },
210
225
  });
211
226
  },
212
227
  summaryKeys: ["content", "delegationId"],
@@ -31,7 +31,7 @@ exports.surfaceToolDef = {
31
31
  },
32
32
  };
33
33
  async function handleSurface(input) {
34
- const { content, delegationId, friendId, queue, routeToFriend, advanceObligation } = input;
34
+ const { content, delegationId, friendId, queue, routeToFriend, advanceObligation, fulfillHeartObligation } = input;
35
35
  // Resolve target friend
36
36
  let targetFriendId;
37
37
  let queueItem;
@@ -74,6 +74,19 @@ async function handleSurface(input) {
74
74
  returnTarget: "surface",
75
75
  });
76
76
  }
77
+ // Fulfill the heart obligation for this origin (separate from inner/mind obligation)
78
+ if (fulfillHeartObligation) {
79
+ try {
80
+ fulfillHeartObligation({
81
+ friendId: queueItem.friendId,
82
+ channel: queueItem.channel,
83
+ key: queueItem.key,
84
+ });
85
+ }
86
+ catch {
87
+ // swallowed — heart obligation fulfillment must never break surface delivery
88
+ }
89
+ }
77
90
  (0, attention_queue_1.dequeueAttentionItem)(queue, delegationId);
78
91
  }
79
92
  // Return delivery status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.141",
3
+ "version": "0.1.0-alpha.142",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",