@ouro.bot/cli 0.1.0-alpha.132 → 0.1.0-alpha.133

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.133",
6
+ "changes": [
7
+ "Inner return obligations: delegated inner dialog work now tracks a ReturnObligation through queued → running → returned/deferred lifecycle.",
8
+ "Exact-origin routing: inner dialog completions route back to the session that delegated the work, not just the freshest active session.",
9
+ "Active work frame surfaces pending inner return obligations so the agent knows what's outstanding."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.132",
6
14
  "changes": [
@@ -439,6 +439,7 @@ function buildActiveWorkFrame(input) {
439
439
  otherCodingSessions,
440
440
  pendingObligations,
441
441
  targetCandidates: input.targetCandidates ?? [],
442
+ innerReturnObligations: input.innerReturnObligations ?? [],
442
443
  bridgeSuggestion: suggestBridgeForActiveWork({
443
444
  currentSession: input.currentSession,
444
445
  currentObligation: input.currentObligation,
@@ -588,6 +589,16 @@ function formatActiveWorkFrame(frame) {
588
589
  lines.push(obligationLine);
589
590
  }
590
591
  }
592
+ if (frame.innerReturnObligations && frame.innerReturnObligations.length > 0) {
593
+ lines.push("");
594
+ lines.push("## inner return obligations");
595
+ for (const ob of frame.innerReturnObligations) {
596
+ const preview = ob.delegatedContent.length > 60
597
+ ? `${ob.delegatedContent.slice(0, 57)}...`
598
+ : ob.delegatedContent;
599
+ lines.push(`- [${ob.status}] ${ob.origin.friendId}/${ob.origin.channel}/${ob.origin.key}: ${preview}`);
600
+ }
601
+ }
591
602
  // Bridge suggestion
592
603
  if (frame.bridgeSuggestion) {
593
604
  lines.push("");
@@ -46,6 +46,11 @@ if [ ! -e "$ENTRY" ]; then
46
46
  fi
47
47
  exec node "$ENTRY" "$@"
48
48
  `;
49
+ function writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync) {
50
+ mkdirSync(path.dirname(scriptPath), { recursive: true });
51
+ writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
52
+ chmodSync(scriptPath, 0o755);
53
+ }
49
54
  function detectShellProfile(homeDir, shell) {
50
55
  if (!shell)
51
56
  return null;
@@ -151,9 +156,9 @@ function installOuroCommand(deps = {}) {
151
156
  meta: { scriptPath, binDir },
152
157
  });
153
158
  try {
154
- mkdirSync(binDir, { recursive: true });
155
- writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
156
- chmodSync(scriptPath, 0o755);
159
+ if (!modernCurrent) {
160
+ writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync);
161
+ }
157
162
  }
158
163
  catch (error) {
159
164
  (0, runtime_1.emitNervesEvent)({
@@ -166,8 +171,8 @@ function installOuroCommand(deps = {}) {
166
171
  return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error), repairedOldLauncher };
167
172
  }
168
173
  // Check if ~/.ouro-cli/bin is already in PATH
169
- let shellProfileUpdated = null;
170
174
  const pathReady = isBinDirInPath(binDir, envPath);
175
+ let shellProfileUpdated = null;
171
176
  if (!pathReady) {
172
177
  const profilePath = detectShellProfile(homeDir, shell);
173
178
  if (profilePath) {
@@ -199,7 +204,7 @@ function installOuroCommand(deps = {}) {
199
204
  component: "daemon",
200
205
  event: "daemon.ouro_path_install_end",
201
206
  message: "ouro command installed",
202
- meta: { scriptPath, pathReady, shellProfileUpdated },
207
+ meta: { scriptPath, pathReady, shellProfileUpdated, oldScriptPath: oldExists ? oldScriptPath : null },
203
208
  });
204
209
  return { installed: true, scriptPath, pathReady, shellProfileUpdated, repairedOldLauncher };
205
210
  }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getObligationsDir = getObligationsDir;
37
+ exports.generateObligationId = generateObligationId;
38
+ exports.createObligation = createObligation;
39
+ exports.readObligation = readObligation;
40
+ exports.advanceObligation = advanceObligation;
41
+ exports.listActiveObligations = listActiveObligations;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const identity_1 = require("../heart/identity");
45
+ const runtime_1 = require("../nerves/runtime");
46
+ // ── Paths ────────────────────────────────────────────────────────
47
+ function getObligationsDir(agentName) {
48
+ return path.join((0, identity_1.getAgentRoot)(agentName), "state", "obligations", "inner");
49
+ }
50
+ // ── ID generation ────────────────────────────────────────────────
51
+ function generateObligationId(timestamp) {
52
+ return `${timestamp}-${Math.random().toString(36).slice(2, 10)}`;
53
+ }
54
+ // ── Store operations ─────────────────────────────────────────────
55
+ function createObligation(agentName, obligation) {
56
+ const dir = getObligationsDir(agentName);
57
+ fs.mkdirSync(dir, { recursive: true });
58
+ const filePath = path.join(dir, `${obligation.id}.json`);
59
+ fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf8");
60
+ (0, runtime_1.emitNervesEvent)({
61
+ event: "mind.obligation_created",
62
+ component: "mind",
63
+ message: "return obligation created",
64
+ meta: {
65
+ obligationId: obligation.id,
66
+ origin: `${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}`,
67
+ status: obligation.status,
68
+ },
69
+ });
70
+ return filePath;
71
+ }
72
+ function readObligation(agentName, obligationId) {
73
+ const dir = getObligationsDir(agentName);
74
+ try {
75
+ const raw = fs.readFileSync(path.join(dir, `${obligationId}.json`), "utf-8");
76
+ return JSON.parse(raw);
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ function advanceObligation(agentName, obligationId, update) {
83
+ const existing = readObligation(agentName, obligationId);
84
+ if (!existing)
85
+ return null;
86
+ const updated = {
87
+ ...existing,
88
+ status: update.status,
89
+ ...(update.startedAt !== undefined ? { startedAt: update.startedAt } : {}),
90
+ ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
91
+ ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
92
+ };
93
+ const dir = getObligationsDir(agentName);
94
+ fs.writeFileSync(path.join(dir, `${obligationId}.json`), JSON.stringify(updated, null, 2), "utf8");
95
+ (0, runtime_1.emitNervesEvent)({
96
+ event: "mind.obligation_advanced",
97
+ component: "mind",
98
+ message: `obligation advanced to ${update.status}`,
99
+ meta: {
100
+ obligationId,
101
+ status: update.status,
102
+ ...(update.returnTarget ? { returnTarget: update.returnTarget } : {}),
103
+ },
104
+ });
105
+ return updated;
106
+ }
107
+ function listActiveObligations(agentName) {
108
+ const dir = getObligationsDir(agentName);
109
+ if (!fs.existsSync(dir))
110
+ return [];
111
+ let entries;
112
+ try {
113
+ entries = fs.readdirSync(dir);
114
+ }
115
+ catch {
116
+ return [];
117
+ }
118
+ const obligations = [];
119
+ for (const file of entries) {
120
+ if (!file.endsWith(".json"))
121
+ continue;
122
+ try {
123
+ const raw = fs.readFileSync(path.join(dir, file), "utf-8");
124
+ const parsed = JSON.parse(raw);
125
+ if (parsed.status === "queued" || parsed.status === "running") {
126
+ obligations.push(parsed);
127
+ }
128
+ }
129
+ catch {
130
+ // skip unparseable
131
+ }
132
+ }
133
+ return obligations.sort((a, b) => a.createdAt - b.createdAt);
134
+ }
@@ -55,9 +55,10 @@ const coding_1 = require("./coding");
55
55
  const memory_1 = require("../mind/memory");
56
56
  const tasks_1 = require("./tasks");
57
57
  const pending_1 = require("../mind/pending");
58
+ const obligations_1 = require("../mind/obligations");
58
59
  const progress_story_1 = require("../heart/progress-story");
59
60
  const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
60
- const obligations_1 = require("../heart/obligations");
61
+ const obligations_2 = require("../heart/obligations");
61
62
  // Tracks which file paths have been read via read_file in this session.
62
63
  // edit_file requires a file to be read first (must-read-first guard).
63
64
  exports.editFileReadTracker = new Set();
@@ -207,7 +208,7 @@ function readActiveWorkInnerState() {
207
208
  const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
208
209
  const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
209
210
  const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
210
- const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
211
+ const storeObligationPending = (0, obligations_2.readPendingObligations)(agentRoot).length > 0;
211
212
  return {
212
213
  status: dialogStatus.processing === "started" ? "running" : "idle",
213
214
  hasPending: dialogStatus.queue !== "clear",
@@ -256,7 +257,7 @@ async function buildToolActiveWorkFrame(ctx) {
256
257
  }
257
258
  const pendingObligations = (() => {
258
259
  try {
259
- return (0, obligations_1.readPendingObligations)(agentRoot);
260
+ return (0, obligations_2.readPendingObligations)(agentRoot);
260
261
  }
261
262
  catch {
262
263
  return [];
@@ -1122,6 +1123,7 @@ exports.baseToolDefinitions = [
1122
1123
  ...(delegatingBridgeId ? { bridgeId: delegatingBridgeId } : {}),
1123
1124
  }
1124
1125
  : undefined;
1126
+ const obligationId = delegatedFrom ? (0, obligations_1.generateObligationId)(now) : undefined;
1125
1127
  const envelope = {
1126
1128
  from: agentName,
1127
1129
  friendId,
@@ -1130,12 +1132,13 @@ exports.baseToolDefinitions = [
1130
1132
  content,
1131
1133
  timestamp: now,
1132
1134
  ...(delegatedFrom ? { delegatedFrom, obligationStatus: "pending" } : {}),
1135
+ ...(obligationId ? { obligationId } : {}),
1133
1136
  };
1134
1137
  if (isSelf) {
1135
1138
  writePendingEnvelope(pendingDir, envelope);
1136
1139
  if (delegatedFrom) {
1137
1140
  try {
1138
- (0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
1141
+ (0, obligations_2.createObligation)((0, identity_1.getAgentRoot)(), {
1139
1142
  origin: {
1140
1143
  friendId: delegatedFrom.friendId,
1141
1144
  channel: delegatedFrom.channel,
@@ -1148,6 +1151,16 @@ exports.baseToolDefinitions = [
1148
1151
  catch {
1149
1152
  /* v8 ignore next -- defensive: obligation store write failure should not break send_message @preserve */
1150
1153
  }
1154
+ /* v8 ignore next -- obligationId always set when delegatedFrom is set (see generateObligationId above) @preserve */
1155
+ if (obligationId) {
1156
+ (0, obligations_1.createObligation)(agentName, {
1157
+ id: obligationId,
1158
+ origin: delegatedFrom,
1159
+ status: "queued",
1160
+ delegatedContent: content.length > 120 ? `${content.slice(0, 117)}...` : content,
1161
+ createdAt: now,
1162
+ });
1163
+ }
1151
1164
  (0, runtime_1.emitNervesEvent)({
1152
1165
  event: "repertoire.obligation_created",
1153
1166
  component: "repertoire",
@@ -53,6 +53,7 @@ const prompt_1 = require("../mind/prompt");
53
53
  const mcp_manager_1 = require("../repertoire/mcp-manager");
54
54
  const bundle_manifest_1 = require("../mind/bundle-manifest");
55
55
  const pending_1 = require("../mind/pending");
56
+ const obligations_1 = require("../mind/obligations");
56
57
  const channel_1 = require("../mind/friends/channel");
57
58
  const trust_gate_1 = require("./trust-gate");
58
59
  const tokens_1 = require("../mind/friends/tokens");
@@ -62,7 +63,7 @@ const runtime_1 = require("../nerves/runtime");
62
63
  const manager_1 = require("../heart/bridges/manager");
63
64
  const session_activity_1 = require("../heart/session-activity");
64
65
  const bluebubbles_1 = require("./bluebubbles");
65
- const obligations_1 = require("../heart/obligations");
66
+ const obligations_2 = require("../heart/obligations");
66
67
  const DEFAULT_INNER_DIALOG_INSTINCTS = [
67
68
  {
68
69
  id: "heartbeat_checkin",
@@ -269,6 +270,12 @@ function sessionMatchesActivity(activity, session) {
269
270
  && activity.channel === session.channel
270
271
  && activity.key === session.key;
271
272
  }
273
+ function resolveExactOriginSession(delegatedFrom, sessionActivity) {
274
+ return sessionActivity.find((activity) => activity.friendId === delegatedFrom.friendId
275
+ && activity.channel === delegatedFrom.channel
276
+ && activity.key === delegatedFrom.key
277
+ && activity.channel !== "inner") ?? null;
278
+ }
272
279
  function resolveBridgePreferredSession(delegatedFrom, sessionActivity) {
273
280
  if (!delegatedFrom.bridgeId)
274
281
  return null;
@@ -307,23 +314,46 @@ function enrichDelegatedFromWithBridge(delegatedFrom) {
307
314
  }
308
315
  return delegatedFrom;
309
316
  }
317
+ function advanceObligationQuietly(agentName, obligationId, update) {
318
+ if (!obligationId)
319
+ return;
320
+ try {
321
+ (0, obligations_1.advanceObligation)(agentName, obligationId, update);
322
+ /* v8 ignore start -- best-effort: obligation fs errors must never block return routing @preserve */
323
+ }
324
+ catch {
325
+ // swallowed
326
+ }
327
+ /* v8 ignore stop */
328
+ }
310
329
  async function routeDelegatedCompletion(agentRoot, agentName, completion, drainedPending, timestamp) {
311
330
  const delegated = (drainedPending ?? []).find((message) => message.delegatedFrom);
312
331
  if (!delegated?.delegatedFrom || !completion?.answer?.trim()) {
313
332
  return;
314
333
  }
315
334
  const delegatedFrom = enrichDelegatedFromWithBridge(delegated.delegatedFrom);
335
+ const obligationId = delegated.obligationId;
336
+ // Advance any inner return obligations from queued -> running (they were drained this turn).
337
+ // drainedPending is guaranteed non-null here (we found delegated above).
338
+ for (const msg of drainedPending) {
339
+ if (msg.obligationId) {
340
+ advanceObligationQuietly(agentName, msg.obligationId, {
341
+ status: "running",
342
+ startedAt: timestamp,
343
+ });
344
+ }
345
+ }
316
346
  if (delegated.obligationStatus === "pending") {
317
347
  // Fulfill the persistent obligation in the store
318
348
  try {
319
- const pending = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, {
349
+ const pending = (0, obligations_2.findPendingObligationForOrigin)(agentRoot, {
320
350
  friendId: delegatedFrom.friendId,
321
351
  channel: delegatedFrom.channel,
322
352
  key: delegatedFrom.key,
323
353
  });
324
354
  /* v8 ignore next 2 -- obligation fulfillment tested via obligations.test.ts; integration requires real disk state @preserve */
325
355
  if (pending) {
326
- (0, obligations_1.fulfillObligation)(agentRoot, pending.id);
356
+ (0, obligations_2.fulfillObligation)(agentRoot, pending.id);
327
357
  }
328
358
  }
329
359
  catch {
@@ -348,20 +378,36 @@ async function routeDelegatedCompletion(agentRoot, agentName, completion, draine
348
378
  content: completion.answer.trim(),
349
379
  timestamp,
350
380
  delegatedFrom,
381
+ ...(obligationId ? { obligationId } : {}),
351
382
  };
352
383
  const sessionActivity = (0, session_activity_1.listSessionActivity)({
353
384
  sessionsDir: path.join(agentRoot, "state", "sessions"),
354
385
  friendsDir: path.join(agentRoot, "friends"),
355
386
  agentName,
356
387
  });
388
+ // Priority 1: Exact origin session (the session that delegated this work).
389
+ const exactOrigin = resolveExactOriginSession(delegatedFrom, sessionActivity);
390
+ if (exactOrigin) {
391
+ if (await tryDeliverDelegatedCompletion(exactOrigin, outboundEnvelope)) {
392
+ advanceObligationQuietly(agentName, obligationId, { status: "returned", returnedAt: timestamp, returnTarget: "exact-origin" });
393
+ return;
394
+ }
395
+ writePendingEnvelope((0, pending_1.getPendingDir)(agentName, exactOrigin.friendId, exactOrigin.channel, exactOrigin.key), outboundEnvelope);
396
+ advanceObligationQuietly(agentName, obligationId, { status: "returned", returnedAt: timestamp, returnTarget: "exact-origin" });
397
+ return;
398
+ }
399
+ // Priority 2: Bridge-preferred session (if delegation was within a bridge).
357
400
  const bridgeTarget = resolveBridgePreferredSession(delegatedFrom, sessionActivity);
358
401
  if (bridgeTarget) {
359
402
  if (await tryDeliverDelegatedCompletion(bridgeTarget, outboundEnvelope)) {
403
+ advanceObligationQuietly(agentName, obligationId, { status: "returned", returnedAt: timestamp, returnTarget: "bridge-session" });
360
404
  return;
361
405
  }
362
406
  writePendingEnvelope((0, pending_1.getPendingDir)(agentName, bridgeTarget.friendId, bridgeTarget.channel, bridgeTarget.key), outboundEnvelope);
407
+ advanceObligationQuietly(agentName, obligationId, { status: "returned", returnedAt: timestamp, returnTarget: "bridge-session" });
363
408
  return;
364
409
  }
410
+ // Priority 3: Freshest active friend session.
365
411
  const freshest = (0, session_activity_1.findFreshestFriendSession)({
366
412
  sessionsDir: path.join(agentRoot, "state", "sessions"),
367
413
  friendsDir: path.join(agentRoot, "friends"),
@@ -371,12 +417,16 @@ async function routeDelegatedCompletion(agentRoot, agentName, completion, draine
371
417
  });
372
418
  if (freshest && freshest.channel !== "inner") {
373
419
  if (await tryDeliverDelegatedCompletion(freshest, outboundEnvelope)) {
420
+ advanceObligationQuietly(agentName, obligationId, { status: "returned", returnedAt: timestamp, returnTarget: "freshest-session" });
374
421
  return;
375
422
  }
376
423
  writePendingEnvelope((0, pending_1.getPendingDir)(agentName, freshest.friendId, freshest.channel, freshest.key), outboundEnvelope);
424
+ advanceObligationQuietly(agentName, obligationId, { status: "returned", returnedAt: timestamp, returnTarget: "freshest-session" });
377
425
  return;
378
426
  }
427
+ // Priority 4: Deferred return queue.
379
428
  writePendingEnvelope((0, pending_1.getDeferredReturnDir)(agentName, delegatedFrom.friendId), outboundEnvelope);
429
+ advanceObligationQuietly(agentName, obligationId, { status: "deferred", returnedAt: timestamp, returnTarget: "deferred" });
380
430
  }
381
431
  // Self-referencing friend record for inner dialog (agent talking to itself).
382
432
  // No real friend to resolve -- this satisfies the pipeline's friend resolver contract.
@@ -19,6 +19,7 @@ const target_resolution_1 = require("../heart/target-resolution");
19
19
  const thoughts_1 = require("../heart/daemon/thoughts");
20
20
  const pending_1 = require("../mind/pending");
21
21
  const obligations_1 = require("../heart/obligations");
22
+ const obligations_2 = require("../mind/obligations");
22
23
  const provider_failover_1 = require("../heart/provider-failover");
23
24
  const provider_ping_1 = require("../heart/provider-ping");
24
25
  const auth_flow_1 = require("../heart/daemon/auth-flow");
@@ -310,6 +311,14 @@ async function handleInboundTurn(input) {
310
311
  })(),
311
312
  friendActivity: sessionActivity,
312
313
  targetCandidates,
314
+ innerReturnObligations: (() => {
315
+ try {
316
+ return (0, obligations_2.listActiveObligations)((0, identity_1.getAgentName)());
317
+ }
318
+ catch {
319
+ return [];
320
+ }
321
+ })(),
313
322
  });
314
323
  const delegationDecision = (0, delegation_1.decideDelegation)({
315
324
  channel: input.channel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.132",
3
+ "version": "0.1.0-alpha.133",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",