@ouro.bot/cli 0.1.0-alpha.49 → 0.1.0-alpha.50

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.
@@ -57,6 +57,9 @@ const tokens_1 = require("../mind/friends/tokens");
57
57
  const pipeline_1 = require("./pipeline");
58
58
  const nerves_1 = require("../nerves");
59
59
  const runtime_1 = require("../nerves/runtime");
60
+ const manager_1 = require("../heart/bridges/manager");
61
+ const session_activity_1 = require("../heart/session-activity");
62
+ const bluebubbles_1 = require("./bluebubbles");
60
63
  const DEFAULT_INNER_DIALOG_INSTINCTS = [
61
64
  {
62
65
  id: "heartbeat_checkin",
@@ -244,6 +247,83 @@ function writeInnerDialogRuntimeState(sessionFilePath, state) {
244
247
  });
245
248
  }
246
249
  }
250
+ function writePendingEnvelope(pendingDir, message) {
251
+ fs.mkdirSync(pendingDir, { recursive: true });
252
+ const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
253
+ const filePath = path.join(pendingDir, fileName);
254
+ fs.writeFileSync(filePath, JSON.stringify(message, null, 2), "utf8");
255
+ }
256
+ function sessionMatchesActivity(activity, session) {
257
+ return activity.friendId === session.friendId
258
+ && activity.channel === session.channel
259
+ && activity.key === session.key;
260
+ }
261
+ function resolveBridgePreferredSession(delegatedFrom, sessionActivity) {
262
+ if (!delegatedFrom.bridgeId)
263
+ return null;
264
+ const bridge = (0, manager_1.createBridgeManager)().getBridge(delegatedFrom.bridgeId);
265
+ if (!bridge || bridge.lifecycle === "completed" || bridge.lifecycle === "cancelled") {
266
+ return null;
267
+ }
268
+ return sessionActivity.find((activity) => activity.friendId === delegatedFrom.friendId
269
+ && activity.channel !== "inner"
270
+ && bridge.attachedSessions.some((session) => sessionMatchesActivity(activity, session))) ?? null;
271
+ }
272
+ async function tryDeliverDelegatedCompletion(target, outboundEnvelope) {
273
+ if (target.channel !== "bluebubbles") {
274
+ return false;
275
+ }
276
+ const result = await (0, bluebubbles_1.sendProactiveBlueBubblesMessageToSession)({
277
+ friendId: target.friendId,
278
+ sessionKey: target.key,
279
+ text: outboundEnvelope.content,
280
+ });
281
+ return result.delivered;
282
+ }
283
+ async function routeDelegatedCompletion(agentRoot, agentName, completion, drainedPending, timestamp) {
284
+ const delegated = (drainedPending ?? []).find((message) => message.delegatedFrom);
285
+ if (!delegated?.delegatedFrom || !completion?.answer?.trim()) {
286
+ return;
287
+ }
288
+ const delegatedFrom = delegated.delegatedFrom;
289
+ const outboundEnvelope = {
290
+ from: agentName,
291
+ friendId: delegatedFrom.friendId,
292
+ channel: delegatedFrom.channel,
293
+ key: delegatedFrom.key,
294
+ content: completion.answer.trim(),
295
+ timestamp,
296
+ delegatedFrom,
297
+ };
298
+ const sessionActivity = (0, session_activity_1.listSessionActivity)({
299
+ sessionsDir: path.join(agentRoot, "state", "sessions"),
300
+ friendsDir: path.join(agentRoot, "friends"),
301
+ agentName,
302
+ });
303
+ const bridgeTarget = resolveBridgePreferredSession(delegatedFrom, sessionActivity);
304
+ if (bridgeTarget) {
305
+ if (await tryDeliverDelegatedCompletion(bridgeTarget, outboundEnvelope)) {
306
+ return;
307
+ }
308
+ writePendingEnvelope((0, pending_1.getPendingDir)(agentName, bridgeTarget.friendId, bridgeTarget.channel, bridgeTarget.key), outboundEnvelope);
309
+ return;
310
+ }
311
+ const freshest = (0, session_activity_1.findFreshestFriendSession)({
312
+ sessionsDir: path.join(agentRoot, "state", "sessions"),
313
+ friendsDir: path.join(agentRoot, "friends"),
314
+ agentName,
315
+ friendId: delegatedFrom.friendId,
316
+ activeOnly: true,
317
+ });
318
+ if (freshest && freshest.channel !== "inner") {
319
+ if (await tryDeliverDelegatedCompletion(freshest, outboundEnvelope)) {
320
+ return;
321
+ }
322
+ writePendingEnvelope((0, pending_1.getPendingDir)(agentName, freshest.friendId, freshest.channel, freshest.key), outboundEnvelope);
323
+ return;
324
+ }
325
+ writePendingEnvelope((0, pending_1.getDeferredReturnDir)(agentName, delegatedFrom.friendId), outboundEnvelope);
326
+ }
247
327
  // Self-referencing friend record for inner dialog (agent talking to itself).
248
328
  // No real friend to resolve -- this satisfies the pipeline's friend resolver contract.
249
329
  function createSelfFriend(agentName) {
@@ -274,6 +354,8 @@ async function runInnerDialogTurn(options) {
274
354
  const now = options?.now ?? (() => new Date());
275
355
  const reason = options?.reason ?? "heartbeat";
276
356
  const sessionFilePath = innerDialogSessionPath();
357
+ const agentName = (0, identity_1.getAgentName)();
358
+ const agentRoot = (0, identity_1.getAgentRoot)();
277
359
  writeInnerDialogRuntimeState(sessionFilePath, {
278
360
  status: "running",
279
361
  reason,
@@ -316,8 +398,8 @@ async function runInnerDialogTurn(options) {
316
398
  const userMessage = { role: "user", content: userContent };
317
399
  // ── Session loader: wraps existing session logic ──────────────────
318
400
  const innerCapabilities = (0, channel_1.getChannelCapabilities)("inner");
319
- const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
320
- const selfFriend = createSelfFriend((0, identity_1.getAgentName)());
401
+ const pendingDir = (0, pending_1.getInnerDialogPendingDir)(agentName);
402
+ const selfFriend = createSelfFriend(agentName);
321
403
  const selfContext = { friend: selfFriend, channel: innerCapabilities };
322
404
  const sessionLoader = {
323
405
  loadOrCreate: async () => {
@@ -358,6 +440,7 @@ async function runInnerDialogTurn(options) {
358
440
  skipConfirmation: true,
359
441
  },
360
442
  });
443
+ await routeDelegatedCompletion(agentRoot, agentName, result.completion, result.drainedPending, now().getTime());
361
444
  const resultMessages = result.messages ?? [];
362
445
  const assistantPreview = extractAssistantPreview(resultMessages);
363
446
  const toolCalls = extractToolCallNames(resultMessages);
@@ -382,6 +465,7 @@ async function runInnerDialogTurn(options) {
382
465
  messages: resultMessages,
383
466
  usage: result.usage,
384
467
  sessionPath: result.sessionPath ?? sessionFilePath,
468
+ completion: result.completion,
385
469
  };
386
470
  }
387
471
  finally {
@@ -9,6 +9,50 @@ exports.handleInboundTurn = handleInboundTurn;
9
9
  const runtime_1 = require("../nerves/runtime");
10
10
  const continuity_1 = require("./continuity");
11
11
  const manager_1 = require("../heart/bridges/manager");
12
+ const identity_1 = require("../heart/identity");
13
+ const tasks_1 = require("../repertoire/tasks");
14
+ const session_activity_1 = require("../heart/session-activity");
15
+ const active_work_1 = require("../heart/active-work");
16
+ const delegation_1 = require("../heart/delegation");
17
+ const thoughts_1 = require("../heart/daemon/thoughts");
18
+ const pending_1 = require("../mind/pending");
19
+ function emptyTaskBoard() {
20
+ return {
21
+ compact: "",
22
+ full: "",
23
+ byStatus: {
24
+ drafting: [],
25
+ processing: [],
26
+ validating: [],
27
+ collaborating: [],
28
+ paused: [],
29
+ blocked: [],
30
+ done: [],
31
+ },
32
+ actionRequired: [],
33
+ unresolvedDependencies: [],
34
+ activeSessions: [],
35
+ activeBridges: [],
36
+ };
37
+ }
38
+ function readInnerWorkState() {
39
+ try {
40
+ const agentRoot = (0, identity_1.getAgentRoot)();
41
+ const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
42
+ const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
43
+ const status = (0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir);
44
+ return {
45
+ status: status.processing === "started" ? "running" : "idle",
46
+ hasPending: status.queue !== "clear",
47
+ };
48
+ }
49
+ catch {
50
+ return {
51
+ status: "idle",
52
+ hasPending: false,
53
+ };
54
+ }
55
+ }
12
56
  // ── Pipeline ──────────────────────────────────────────────────────
13
57
  async function handleInboundTurn(input) {
14
58
  // Step 1: Resolve friend
@@ -57,6 +101,9 @@ async function handleInboundTurn(input) {
57
101
  const session = await input.sessionLoader.loadOrCreate();
58
102
  const sessionMessages = session.messages;
59
103
  let mustResolveBeforeHandoff = (0, continuity_1.resolveMustResolveBeforeHandoff)(session.state?.mustResolveBeforeHandoff === true, input.continuityIngressTexts);
104
+ const lastFriendActivityAt = input.channel === "inner"
105
+ ? session.state?.lastFriendActivityAt
106
+ : new Date().toISOString();
60
107
  const currentObligation = input.continuityIngressTexts
61
108
  ?.map((text) => text.trim())
62
109
  .filter((text) => text.length > 0)
@@ -73,8 +120,51 @@ async function handleInboundTurn(input) {
73
120
  key: currentSession.key,
74
121
  });
75
122
  const bridgeContext = (0, manager_1.formatBridgeContext)(activeBridges) || undefined;
76
- // Step 4: Drain pending messages
77
- const pending = input.drainPending(input.pendingDir);
123
+ let sessionActivity = [];
124
+ try {
125
+ const agentRoot = (0, identity_1.getAgentRoot)();
126
+ sessionActivity = (0, session_activity_1.listSessionActivity)({
127
+ sessionsDir: `${agentRoot}/state/sessions`,
128
+ friendsDir: `${agentRoot}/friends`,
129
+ agentName: (0, identity_1.getAgentName)(),
130
+ currentSession: {
131
+ friendId: currentSession.friendId,
132
+ channel: currentSession.channel,
133
+ key: currentSession.key,
134
+ },
135
+ });
136
+ }
137
+ catch {
138
+ sessionActivity = [];
139
+ }
140
+ const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
141
+ currentSession,
142
+ currentObligation,
143
+ mustResolveBeforeHandoff,
144
+ inner: readInnerWorkState(),
145
+ bridges: activeBridges,
146
+ taskBoard: (() => {
147
+ try {
148
+ return (0, tasks_1.getTaskModule)().getBoard();
149
+ }
150
+ catch {
151
+ return emptyTaskBoard();
152
+ }
153
+ })(),
154
+ friendActivity: sessionActivity,
155
+ });
156
+ const delegationDecision = (0, delegation_1.decideDelegation)({
157
+ channel: input.channel,
158
+ ingressTexts: input.continuityIngressTexts ?? [],
159
+ activeWork: activeWorkFrame,
160
+ mustResolveBeforeHandoff,
161
+ });
162
+ // Step 4: Drain deferred friend returns, then ordinary per-session pending.
163
+ const deferredReturns = input.channel === "inner"
164
+ ? []
165
+ : (input.drainDeferredReturns?.(resolvedContext.friend.id) ?? []);
166
+ const sessionPending = input.drainPending(input.pendingDir);
167
+ const pending = [...deferredReturns, ...sessionPending];
78
168
  // Assemble messages: session messages + pending (formatted) + inbound user messages
79
169
  if (pending.length > 0) {
80
170
  // Format pending messages and prepend to the user content
@@ -112,6 +202,8 @@ async function handleInboundTurn(input) {
112
202
  const runAgentOptions = {
113
203
  ...input.runAgentOptions,
114
204
  bridgeContext,
205
+ activeWorkFrame,
206
+ delegationDecision,
115
207
  currentSessionKey: currentSession.key,
116
208
  currentObligation,
117
209
  mustResolveBeforeHandoff,
@@ -130,11 +222,15 @@ async function handleInboundTurn(input) {
130
222
  };
131
223
  const result = await input.runAgent(sessionMessages, input.callbacks, input.channel, input.signal, runAgentOptions);
132
224
  // Step 6: postTurn
225
+ const continuingState = {
226
+ ...(mustResolveBeforeHandoff ? { mustResolveBeforeHandoff: true } : {}),
227
+ ...(typeof lastFriendActivityAt === "string" ? { lastFriendActivityAt } : {}),
228
+ };
133
229
  const nextState = result.outcome === "complete" || result.outcome === "blocked" || result.outcome === "superseded"
134
- ? undefined
135
- : mustResolveBeforeHandoff
136
- ? { mustResolveBeforeHandoff: true }
137
- : undefined;
230
+ ? (typeof lastFriendActivityAt === "string"
231
+ ? { lastFriendActivityAt }
232
+ : undefined)
233
+ : (Object.keys(continuingState).length > 0 ? continuingState : undefined);
138
234
  input.postTurn(sessionMessages, session.sessionPath, result.usage, undefined, nextState);
139
235
  // Step 7: Token accumulation
140
236
  await input.accumulateFriendTokens(input.friendStore, resolvedContext.friend.id, result.usage);
@@ -152,7 +248,9 @@ async function handleInboundTurn(input) {
152
248
  gateResult,
153
249
  usage: result.usage,
154
250
  turnOutcome: result.outcome,
251
+ completion: result.completion,
155
252
  sessionPath: session.sessionPath,
156
253
  messages: sessionMessages,
254
+ drainedPending: pending,
157
255
  };
158
256
  }
@@ -63,6 +63,7 @@ const resolver_1 = require("../mind/friends/resolver");
63
63
  const tokens_1 = require("../mind/friends/tokens");
64
64
  const turn_coordinator_1 = require("../heart/turn-coordinator");
65
65
  const identity_1 = require("../heart/identity");
66
+ const progress_story_1 = require("../heart/progress-story");
66
67
  const http = __importStar(require("http"));
67
68
  const path = __importStar(require("path"));
68
69
  const trust_gate_1 = require("./trust-gate");
@@ -334,13 +335,21 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
334
335
  if (!streamHasContent)
335
336
  safeEmit("⏳");
336
337
  const argSummary = (0, tools_1.summarizeArgs)(name, args) || Object.keys(args).join(", ");
337
- safeUpdate(`running ${name} (${argSummary})...`);
338
+ safeUpdate((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
339
+ scope: "shared-work",
340
+ phase: "processing",
341
+ objective: `running ${name} (${argSummary})...`,
342
+ })));
338
343
  hadToolRun = true;
339
344
  },
340
345
  onToolEnd: (name, summary, success) => {
341
346
  stopPhraseRotation();
342
347
  const msg = (0, format_1.formatToolResult)(name, summary, success);
343
- safeUpdate(msg);
348
+ safeUpdate((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
349
+ scope: "shared-work",
350
+ phase: "processing",
351
+ objective: msg,
352
+ })));
344
353
  },
345
354
  onKick: () => {
346
355
  stopPhraseRotation();
@@ -351,7 +360,11 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
351
360
  stopPhraseRotation();
352
361
  if (stopped)
353
362
  return;
354
- const msg = (0, format_1.formatError)(error);
363
+ const msg = (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
364
+ scope: "shared-work",
365
+ phase: "errored",
366
+ outcomeText: (0, format_1.formatError)(error),
367
+ }));
355
368
  if (severity === "transient") {
356
369
  safeUpdate(msg);
357
370
  }
@@ -560,6 +573,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
560
573
  hasExistingGroupWithFamily: false,
561
574
  enforceTrustGate: trust_gate_1.enforceTrustGate,
562
575
  drainPending: pending_1.drainPending,
576
+ drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)((0, identity_1.getAgentName)(), deferredFriendId),
563
577
  runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
564
578
  ...opts,
565
579
  toolContext: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.49",
3
+ "version": "0.1.0-alpha.50",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",