@ouro.bot/cli 0.1.0-alpha.76 → 0.1.0-alpha.77

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,15 @@
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.77",
6
+ "changes": [
7
+ "New: Persistent obligation store (state/obligations/*.json). Obligations survive pending message consumption so the agent always knows what it owes and to whom.",
8
+ "Obligations are created when send_message delegates to self or go_inward fires with a return address, and fulfilled when routeDelegatedCompletion delivers the answer.",
9
+ "The commitments frame now reads pending obligations from the store, giving the agent an accurate view of outstanding commitments across all sessions.",
10
+ "readInnerWorkState derives obligationPending from both pending messages and the obligation store, ensuring obligations are visible even after the inner dialog queue drains."
11
+ ]
12
+ },
4
13
  {
5
14
  "version": "0.1.0-alpha.76",
6
15
  "changes": [
@@ -108,6 +108,7 @@ function buildActiveWorkFrame(input) {
108
108
  freshestForCurrentFriend: friendSessions[0] ?? null,
109
109
  otherLiveSessionsForCurrentFriend: friendSessions,
110
110
  },
111
+ pendingObligations: input.pendingObligations ?? [],
111
112
  targetCandidates: input.targetCandidates ?? [],
112
113
  bridgeSuggestion: suggestBridgeForActiveWork({
113
114
  currentSession: input.currentSession,
@@ -3,11 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deriveCommitments = deriveCommitments;
4
4
  exports.formatCommitments = formatCommitments;
5
5
  const runtime_1 = require("../nerves/runtime");
6
- function deriveCommitments(activeWorkFrame, innerJob) {
6
+ function deriveCommitments(activeWorkFrame, innerJob, pendingObligations) {
7
7
  const committedTo = [];
8
8
  const completionCriteria = [];
9
9
  const safeToIgnore = [];
10
- // Obligation
10
+ // Persistent obligations from the obligation store
11
+ if (pendingObligations && pendingObligations.length > 0) {
12
+ for (const ob of pendingObligations) {
13
+ committedTo.push(`i owe ${ob.origin.friendId}: ${ob.content}`);
14
+ }
15
+ completionCriteria.push("fulfill my outstanding obligations");
16
+ }
17
+ // Obligation (from current turn -- kept for backward compat)
11
18
  if (typeof activeWorkFrame.currentObligation === "string" && activeWorkFrame.currentObligation.trim().length > 0) {
12
19
  committedTo.push(`i told them i'd ${activeWorkFrame.currentObligation.trim()}`);
13
20
  }
@@ -29,6 +29,7 @@ const github_copilot_1 = require("./providers/github-copilot");
29
29
  const pending_1 = require("../mind/pending");
30
30
  const identity_2 = require("./identity");
31
31
  const socket_client_1 = require("./daemon/socket-client");
32
+ const obligations_1 = require("./obligations");
32
33
  let _providerRuntime = null;
33
34
  function getProviderRuntimeFingerprint() {
34
35
  const provider = (0, identity_1.loadAgentConfig)().provider;
@@ -702,6 +703,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
702
703
  } : {}),
703
704
  };
704
705
  (0, pending_1.queuePendingMessage)(pendingDir, envelope);
706
+ if (currentSession && !isInnerChannel) {
707
+ try {
708
+ (0, obligations_1.createObligation)((0, identity_2.getAgentRoot)(), {
709
+ origin: {
710
+ friendId: currentSession.friendId,
711
+ channel: currentSession.channel,
712
+ key: currentSession.key,
713
+ },
714
+ content,
715
+ });
716
+ }
717
+ catch {
718
+ /* v8 ignore next -- defensive: obligation store write failure should not break go_inward @preserve */
719
+ }
720
+ }
705
721
  try {
706
722
  await (0, socket_client_1.requestInnerWake)((0, identity_2.getAgentName)());
707
723
  }
@@ -0,0 +1,141 @@
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.createObligation = createObligation;
37
+ exports.readObligations = readObligations;
38
+ exports.readPendingObligations = readPendingObligations;
39
+ exports.fulfillObligation = fulfillObligation;
40
+ exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const runtime_1 = require("../nerves/runtime");
44
+ function obligationsDir(agentRoot) {
45
+ return path.join(agentRoot, "state", "obligations");
46
+ }
47
+ function obligationFilePath(agentRoot, id) {
48
+ return path.join(obligationsDir(agentRoot), `${id}.json`);
49
+ }
50
+ function generateId() {
51
+ const timestamp = Date.now();
52
+ const random = Math.random().toString(36).slice(2, 10);
53
+ return `${timestamp}-${random}`;
54
+ }
55
+ function createObligation(agentRoot, input) {
56
+ const id = generateId();
57
+ const obligation = {
58
+ id,
59
+ origin: input.origin,
60
+ ...(input.bridgeId ? { bridgeId: input.bridgeId } : {}),
61
+ content: input.content,
62
+ status: "pending",
63
+ createdAt: new Date().toISOString(),
64
+ };
65
+ const dir = obligationsDir(agentRoot);
66
+ fs.mkdirSync(dir, { recursive: true });
67
+ fs.writeFileSync(obligationFilePath(agentRoot, id), JSON.stringify(obligation, null, 2), "utf-8");
68
+ (0, runtime_1.emitNervesEvent)({
69
+ component: "engine",
70
+ event: "engine.obligation_created",
71
+ message: "obligation created",
72
+ meta: {
73
+ obligationId: id,
74
+ friendId: input.origin.friendId,
75
+ channel: input.origin.channel,
76
+ key: input.origin.key,
77
+ },
78
+ });
79
+ return obligation;
80
+ }
81
+ function readObligations(agentRoot) {
82
+ const dir = obligationsDir(agentRoot);
83
+ if (!fs.existsSync(dir))
84
+ return [];
85
+ let entries;
86
+ try {
87
+ entries = fs.readdirSync(dir);
88
+ }
89
+ catch {
90
+ /* v8 ignore next -- defensive: readdirSync race after existsSync @preserve */
91
+ return [];
92
+ }
93
+ const jsonFiles = entries.filter((entry) => entry.endsWith(".json")).sort();
94
+ const obligations = [];
95
+ for (const file of jsonFiles) {
96
+ try {
97
+ const raw = fs.readFileSync(path.join(dir, file), "utf-8");
98
+ const parsed = JSON.parse(raw);
99
+ if (typeof parsed.id === "string" && typeof parsed.content === "string") {
100
+ obligations.push(parsed);
101
+ }
102
+ }
103
+ catch {
104
+ // skip malformed files
105
+ }
106
+ }
107
+ return obligations;
108
+ }
109
+ function readPendingObligations(agentRoot) {
110
+ return readObligations(agentRoot).filter((ob) => ob.status === "pending");
111
+ }
112
+ function fulfillObligation(agentRoot, obligationId) {
113
+ const filePath = obligationFilePath(agentRoot, obligationId);
114
+ let obligation;
115
+ try {
116
+ const raw = fs.readFileSync(filePath, "utf-8");
117
+ obligation = JSON.parse(raw);
118
+ }
119
+ catch {
120
+ return;
121
+ }
122
+ obligation.status = "fulfilled";
123
+ obligation.fulfilledAt = new Date().toISOString();
124
+ fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf-8");
125
+ (0, runtime_1.emitNervesEvent)({
126
+ component: "engine",
127
+ event: "engine.obligation_fulfilled",
128
+ message: "obligation fulfilled",
129
+ meta: {
130
+ obligationId,
131
+ friendId: obligation.origin.friendId,
132
+ channel: obligation.origin.channel,
133
+ key: obligation.origin.key,
134
+ },
135
+ });
136
+ }
137
+ function findPendingObligationForOrigin(agentRoot, origin) {
138
+ return readPendingObligations(agentRoot).find((ob) => ob.origin.friendId === origin.friendId
139
+ && ob.origin.channel === origin.channel
140
+ && ob.origin.key === origin.key);
141
+ }
@@ -503,7 +503,7 @@ function commitmentsSection(options) {
503
503
  const job = options.activeWorkFrame.inner?.job;
504
504
  if (!job)
505
505
  return "";
506
- const commitments = (0, commitments_1.deriveCommitments)(options.activeWorkFrame, job);
506
+ const commitments = (0, commitments_1.deriveCommitments)(options.activeWorkFrame, job, options.activeWorkFrame.pendingObligations);
507
507
  if (commitments.committedTo.length === 0)
508
508
  return "";
509
509
  return `## my commitments\n${commitments.committedTo.map((c) => `- ${c}`).join("\n")}`;
@@ -53,6 +53,7 @@ const memory_1 = require("../mind/memory");
53
53
  const pending_1 = require("../mind/pending");
54
54
  const progress_story_1 = require("../heart/progress-story");
55
55
  const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
56
+ const obligations_1 = require("../heart/obligations");
56
57
  // Tracks which file paths have been read via read_file in this session.
57
58
  // edit_file requires a file to be read first (must-read-first guard).
58
59
  exports.editFileReadTracker = new Set();
@@ -893,6 +894,20 @@ exports.baseToolDefinitions = [
893
894
  if (isSelf) {
894
895
  writePendingEnvelope(pendingDir, envelope);
895
896
  if (delegatedFrom) {
897
+ try {
898
+ (0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
899
+ origin: {
900
+ friendId: delegatedFrom.friendId,
901
+ channel: delegatedFrom.channel,
902
+ key: delegatedFrom.key,
903
+ },
904
+ ...(delegatedFrom.bridgeId ? { bridgeId: delegatedFrom.bridgeId } : {}),
905
+ content,
906
+ });
907
+ }
908
+ catch {
909
+ /* v8 ignore next -- defensive: obligation store write failure should not break send_message @preserve */
910
+ }
896
911
  (0, runtime_1.emitNervesEvent)({
897
912
  event: "repertoire.obligation_created",
898
913
  component: "repertoire",
@@ -62,6 +62,7 @@ const runtime_1 = require("../nerves/runtime");
62
62
  const manager_1 = require("../heart/bridges/manager");
63
63
  const session_activity_1 = require("../heart/session-activity");
64
64
  const bluebubbles_1 = require("./bluebubbles");
65
+ const obligations_1 = require("../heart/obligations");
65
66
  const DEFAULT_INNER_DIALOG_INSTINCTS = [
66
67
  {
67
68
  id: "heartbeat_checkin",
@@ -305,6 +306,21 @@ async function routeDelegatedCompletion(agentRoot, agentName, completion, draine
305
306
  }
306
307
  const delegatedFrom = enrichDelegatedFromWithBridge(delegated.delegatedFrom);
307
308
  if (delegated.obligationStatus === "pending") {
309
+ // Fulfill the persistent obligation in the store
310
+ try {
311
+ const pending = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, {
312
+ friendId: delegatedFrom.friendId,
313
+ channel: delegatedFrom.channel,
314
+ key: delegatedFrom.key,
315
+ });
316
+ /* v8 ignore next 2 -- obligation fulfillment tested via obligations.test.ts; integration requires real disk state @preserve */
317
+ if (pending) {
318
+ (0, obligations_1.fulfillObligation)(agentRoot, pending.id);
319
+ }
320
+ }
321
+ catch {
322
+ /* v8 ignore next -- defensive: obligation store read failure should not break delivery @preserve */
323
+ }
308
324
  (0, runtime_1.emitNervesEvent)({
309
325
  event: "senses.obligation_fulfilled",
310
326
  component: "senses",
@@ -17,6 +17,7 @@ const delegation_1 = require("../heart/delegation");
17
17
  const target_resolution_1 = require("../heart/target-resolution");
18
18
  const thoughts_1 = require("../heart/daemon/thoughts");
19
19
  const pending_1 = require("../mind/pending");
20
+ const obligations_1 = require("../heart/obligations");
20
21
  function emptyTaskBoard() {
21
22
  return {
22
23
  compact: "",
@@ -55,12 +56,14 @@ function readInnerWorkState() {
55
56
  const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
56
57
  const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
57
58
  const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
59
+ // Derive obligationPending from both the pending message field and the obligation store
60
+ const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
58
61
  return {
59
62
  status: dialogStatus.processing === "started" ? "running" : "idle",
60
63
  hasPending: dialogStatus.queue !== "clear",
61
64
  origin: dialogStatus.origin,
62
65
  contentSnippet: dialogStatus.contentSnippet,
63
- obligationPending: dialogStatus.obligationPending,
66
+ obligationPending: dialogStatus.obligationPending || storeObligationPending,
64
67
  job,
65
68
  };
66
69
  }
@@ -176,12 +179,20 @@ async function handleInboundTurn(input) {
176
179
  catch {
177
180
  targetCandidates = [];
178
181
  }
182
+ let pendingObligations = [];
183
+ try {
184
+ pendingObligations = (0, obligations_1.readPendingObligations)((0, identity_1.getAgentRoot)());
185
+ }
186
+ catch {
187
+ pendingObligations = [];
188
+ }
179
189
  const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
180
190
  currentSession,
181
191
  currentObligation,
182
192
  mustResolveBeforeHandoff,
183
193
  inner: readInnerWorkState(),
184
194
  bridges: activeBridges,
195
+ pendingObligations,
185
196
  taskBoard: (() => {
186
197
  try {
187
198
  return (0, tasks_1.getTaskModule)().getBoard();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.76",
3
+ "version": "0.1.0-alpha.77",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",