@ouro.bot/cli 0.1.0-alpha.102 → 0.1.0-alpha.104

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,20 @@
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.104",
6
+ "changes": [
7
+ "Family-scoped status checks now keep the five-line header anchored on the current live conversation while appending an `other active sessions:` block for every other live lane the agent is actively working.",
8
+ "The inbound pipeline, active-work frame, prompt steering, and status retry logic now preserve non-current live coding lanes and obligations so family status answers stop hiding parallel work in other sessions."
9
+ ]
10
+ },
11
+ {
12
+ "version": "0.1.0-alpha.103",
13
+ "changes": [
14
+ "Status-check turns now answer in a fixed live-state shape that names the current conversation, active lane, artifact, latest checkpoint, and next action instead of drifting into broad mission summaries.",
15
+ "Those status turns now hold final streaming until the exact reply is ready and turn report-backs into same-thread obligation updates, so 'I'll report back here' comes back as a real in-thread follow-up instead of hidden background work."
16
+ ]
17
+ },
4
18
  {
5
19
  "version": "0.1.0-alpha.102",
6
20
  "changes": [
@@ -63,6 +63,24 @@ function formatObligationSurface(obligation) {
63
63
  return ` (${obligation.currentSurface.label})`;
64
64
  }
65
65
  }
66
+ function mergeArtifactFallback(obligation) {
67
+ const trimmed = obligation.content.trim();
68
+ if (!trimmed)
69
+ return "the fix";
70
+ const stripped = trimmed.replace(/^merge(?:\s+|$)/i, "").trim();
71
+ return stripped || "the fix";
72
+ }
73
+ function formatMergeArtifact(obligation) {
74
+ const currentArtifact = obligation.currentArtifact?.trim();
75
+ if (currentArtifact)
76
+ return currentArtifact;
77
+ if (obligation.currentSurface?.kind === "merge") {
78
+ const surfaceLabel = obligation.currentSurface.label.trim();
79
+ if (surfaceLabel)
80
+ return surfaceLabel;
81
+ }
82
+ return mergeArtifactFallback(obligation);
83
+ }
66
84
  function findPrimaryOpenObligation(frame) {
67
85
  return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled")
68
86
  ?? (frame.pendingObligations ?? []).find(obligations_1.isOpenObligation)
@@ -79,6 +97,9 @@ function formatActiveLane(frame, obligation) {
79
97
  if (frame.inner?.job?.status === "running") {
80
98
  return "inner dialog";
81
99
  }
100
+ if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0 && frame.currentSession) {
101
+ return "this same thread";
102
+ }
82
103
  return null;
83
104
  }
84
105
  function formatCurrentArtifact(frame, obligation) {
@@ -91,6 +112,9 @@ function formatCurrentArtifact(frame, obligation) {
91
112
  if ((frame.codingSessions ?? []).length > 0) {
92
113
  return "no PR or merge artifact yet";
93
114
  }
115
+ if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
116
+ return "no artifact yet";
117
+ }
94
118
  return null;
95
119
  }
96
120
  function formatNextAction(frame, obligation) {
@@ -108,8 +132,7 @@ function formatNextAction(frame, obligation) {
108
132
  return "finish the coding pass and bring the result back here";
109
133
  }
110
134
  if (obligation?.status === "waiting_for_merge") {
111
- const artifact = formatCurrentArtifact(frame, obligation) ?? "the fix";
112
- return `wait for checks, merge ${artifact}, then update runtime`;
135
+ return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
113
136
  }
114
137
  if (obligation?.status === "updating_runtime") {
115
138
  return "update runtime, verify version/changelog, then re-observe";
@@ -117,6 +140,9 @@ function formatNextAction(frame, obligation) {
117
140
  if (obligation) {
118
141
  return "continue the active loop and bring the result back here";
119
142
  }
143
+ if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
144
+ return `work on "${frame.currentObligation.trim()}" and bring back a concrete artifact`;
145
+ }
120
146
  return null;
121
147
  }
122
148
  function suggestBridgeForActiveWork(input) {
@@ -177,6 +203,9 @@ function buildActiveWorkFrame(input) {
177
203
  const activeBridgePresent = input.bridges.some(isActiveBridge);
178
204
  const openObligations = activeObligationCount(input.pendingObligations);
179
205
  const liveCodingSessions = input.codingSessions ?? [];
206
+ const allOtherLiveSessions = [...input.friendActivity].sort(compareActivity);
207
+ const otherCodingSessions = input.otherCodingSessions ?? [];
208
+ const pendingObligations = input.pendingObligations ?? [];
180
209
  const centerOfGravity = activeBridgePresent
181
210
  ? "shared-work"
182
211
  : (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff || openObligations > 0 || liveCodingSessions.length > 0)
@@ -197,9 +226,11 @@ function buildActiveWorkFrame(input) {
197
226
  friendActivity: {
198
227
  freshestForCurrentFriend: friendSessions[0] ?? null,
199
228
  otherLiveSessionsForCurrentFriend: friendSessions,
229
+ allOtherLiveSessions,
200
230
  },
201
231
  codingSessions: liveCodingSessions,
202
- pendingObligations: input.pendingObligations ?? [],
232
+ otherCodingSessions,
233
+ pendingObligations,
203
234
  targetCandidates: input.targetCandidates ?? [],
204
235
  bridgeSuggestion: suggestBridgeForActiveWork({
205
236
  currentSession: input.currentSession,
@@ -221,6 +252,8 @@ function buildActiveWorkFrame(input) {
221
252
  liveTasks: frame.taskPressure.liveTaskNames.length,
222
253
  liveSessions: frame.friendActivity.otherLiveSessionsForCurrentFriend.length,
223
254
  codingSessions: frame.codingSessions.length,
255
+ otherLiveSessions: allOtherLiveSessions.length,
256
+ otherCodingSessions: otherCodingSessions.length,
224
257
  pendingObligations: openObligations,
225
258
  hasBridgeSuggestion: frame.bridgeSuggestion !== null,
226
259
  },
@@ -233,6 +266,8 @@ function formatActiveWorkFrame(frame) {
233
266
  const activeLane = formatActiveLane(frame, primaryObligation);
234
267
  const currentArtifact = formatCurrentArtifact(frame, primaryObligation);
235
268
  const nextAction = formatNextAction(frame, primaryObligation);
269
+ const otherCodingSessions = frame.otherCodingSessions ?? [];
270
+ const otherLiveSessions = frame.friendActivity?.allOtherLiveSessions ?? [];
236
271
  // Session line
237
272
  if (frame.currentSession) {
238
273
  let sessionLine = `i'm in a conversation on ${formatSessionLabel(frame.currentSession)}.`;
@@ -305,6 +340,23 @@ function formatActiveWorkFrame(frame) {
305
340
  lines.push(`- [${session.status}] ${formatCodingLaneLabel(session)}${describeCodingSessionScope(session, frame.currentSession)}`);
306
341
  }
307
342
  }
343
+ if (otherCodingSessions.length > 0) {
344
+ lines.push("");
345
+ lines.push("## other live coding work");
346
+ for (const session of otherCodingSessions) {
347
+ const origin = session.originSession
348
+ ? `${session.originSession.friendId}/${session.originSession.channel}/${session.originSession.key}`
349
+ : "another session";
350
+ lines.push(`- [${session.status}] ${formatCodingLaneLabel(session)} for ${origin}`);
351
+ }
352
+ }
353
+ if (otherLiveSessions.length > 0) {
354
+ lines.push("");
355
+ lines.push("## other live sessions");
356
+ for (const session of otherLiveSessions) {
357
+ lines.push(`- ${session.friendName}/${session.channel}/${session.key}`);
358
+ }
359
+ }
308
360
  // Task pressure
309
361
  if ((frame.taskPressure?.liveTaskNames ?? []).length > 0) {
310
362
  lines.push("");
@@ -27,6 +27,7 @@ const azure_1 = require("./providers/azure");
27
27
  const minimax_1 = require("./providers/minimax");
28
28
  const openai_codex_1 = require("./providers/openai-codex");
29
29
  const github_copilot_1 = require("./providers/github-copilot");
30
+ const obligation_steering_1 = require("../mind/obligation-steering");
30
31
  const pending_1 = require("../mind/pending");
31
32
  const identity_2 = require("./identity");
32
33
  const socket_client_1 = require("./daemon/socket-client");
@@ -272,6 +273,45 @@ function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringF
272
273
  }
273
274
  return null;
274
275
  }
276
+ function hasExactStatusReplyShape(answer, statusCheckScope) {
277
+ const lines = answer.trimEnd().split(/\r?\n/);
278
+ const hasFiveLineHeader = (/^live conversation:\s+\S.+$/i.test(lines[0])
279
+ && /^active lane:\s+\S.+$/i.test(lines[1])
280
+ && /^current artifact:\s+\S.+$/i.test(lines[2])
281
+ && /^latest checkpoint:\s+\S.+$/i.test(lines[3])
282
+ && /^next action:\s+\S.+$/i.test(lines[4]));
283
+ if (!hasFiveLineHeader)
284
+ return false;
285
+ if (statusCheckScope !== "all-sessions-family") {
286
+ return lines.length === 5;
287
+ }
288
+ if (lines.length < 7)
289
+ return false;
290
+ if (!/^other active sessions:\s*$/i.test(lines[5]))
291
+ return false;
292
+ return lines.slice(6).every((line) => /^-\s+\S.+$/i.test(line));
293
+ }
294
+ function extractLatestCheckpoint(answer) {
295
+ const latestLine = answer.trimEnd().split(/\r?\n/)[3];
296
+ return latestLine.replace(/^latest checkpoint:\s*/i, "").trim();
297
+ }
298
+ function getStatusReplyRetryError(answer, statusCheckRequested, activeWorkFrame, statusCheckScope) {
299
+ if (!statusCheckRequested || answer == null)
300
+ return null;
301
+ if (hasExactStatusReplyShape(answer, statusCheckScope))
302
+ return null;
303
+ if (!activeWorkFrame) {
304
+ return `the user asked for current status. call final_answer again using exactly these five non-empty lines and nothing else:
305
+ live conversation: ...
306
+ active lane: ...
307
+ current artifact: ...
308
+ latest checkpoint: ...
309
+ next action: ...`;
310
+ }
311
+ return `the user asked for current status right now.
312
+ ${(0, obligation_steering_1.renderExactStatusReplyContract)(activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(activeWorkFrame), statusCheckScope)}
313
+ call final_answer again using that exact status shape and nothing else.`;
314
+ }
275
315
  // Re-export kick utilities for backward compat
276
316
  var kicks_1 = require("./kicks");
277
317
  Object.defineProperty(exports, "hasToolIntent", { enumerable: true, get: function () { return kicks_1.hasToolIntent; } });
@@ -551,6 +591,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
551
591
  traceId,
552
592
  toolChoiceRequired,
553
593
  reasoningEffort: currentReasoningEffort,
594
+ eagerFinalAnswerStreaming: !options?.statusCheckRequested,
554
595
  });
555
596
  // Track usage from the latest API call
556
597
  if (result.usage)
@@ -599,15 +640,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
599
640
  // Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
600
641
  const { answer, intent } = parseFinalAnswerPayload(result.toolCalls[0].arguments);
601
642
  const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
643
+ const statusReplyRetryError = getStatusReplyRetryError(answer, options?.statusCheckRequested, options?.activeWorkFrame, options?.statusCheckScope);
644
+ const exactStatusReplyAccepted = Boolean(options?.statusCheckRequested) && !statusReplyRetryError && answer != null;
645
+ const deliveredAnswer = exactStatusReplyAccepted && options?.activeWorkFrame
646
+ ? (0, obligation_steering_1.buildExactStatusReply)(options.activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(options.activeWorkFrame), extractLatestCheckpoint(answer), options?.statusCheckScope)
647
+ : answer;
602
648
  const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
603
649
  const validTerminalIntent = intent === "complete" || intent === "blocked";
604
- const validClosure = answer != null
605
- && !retryError
606
- && (!mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
650
+ const validClosure = deliveredAnswer != null
651
+ && (exactStatusReplyAccepted || !retryError)
652
+ && !statusReplyRetryError
653
+ && (exactStatusReplyAccepted || !mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
607
654
  if (validClosure) {
608
655
  completion = {
609
- answer,
610
- intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
656
+ answer: deliveredAnswer,
657
+ intent: validDirectReply && !exactStatusReplyAccepted ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
611
658
  };
612
659
  if (result.finalAnswerStreamed) {
613
660
  // The streaming layer already parsed and emitted the answer
@@ -619,10 +666,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
619
666
  callbacks.onClearText?.();
620
667
  // Emit the answer through the callback pipeline so channels receive it.
621
668
  // Never truncate -- channel adapters handle splitting long messages.
622
- callbacks.onTextChunk(answer);
669
+ callbacks.onTextChunk(deliveredAnswer);
623
670
  }
624
671
  messages.push(msg);
625
- if (validDirectReply) {
672
+ if (validDirectReply && !exactStatusReplyAccepted) {
626
673
  const resumeWork = "direct reply delivered. resume the unresolved obligation now and keep working until you can finish or clearly report that you are blocked.";
627
674
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
628
675
  providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
@@ -641,8 +688,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
641
688
  // assistant msg + error tool result and let the model try again.
642
689
  callbacks.onClearText?.();
643
690
  messages.push(msg);
644
- messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: retryError ?? "your final_answer was incomplete or malformed. call final_answer again with your complete response." });
645
- providerRuntime.appendToolOutput(result.toolCalls[0].id, retryError ?? "your final_answer was incomplete or malformed. call final_answer again with your complete response.");
691
+ const toolRetryMessage = statusReplyRetryError
692
+ ?? retryError
693
+ ?? "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
694
+ messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
695
+ providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
646
696
  }
647
697
  continue;
648
698
  }
@@ -238,7 +238,7 @@ async function streamAnthropicMessages(client, model, request) {
238
238
  const toolCalls = new Map();
239
239
  const thinkingBlocks = new Map();
240
240
  const redactedBlocks = new Map();
241
- const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks);
241
+ const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks, request.eagerFinalAnswerStreaming);
242
242
  try {
243
243
  for await (const event of response) {
244
244
  if (request.signal?.aborted)
@@ -136,7 +136,7 @@ function createAzureProviderRuntime() {
136
136
  params.metadata = { trace_id: request.traceId };
137
137
  if (request.toolChoiceRequired)
138
138
  params.tool_choice = "required";
139
- const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
139
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
140
140
  for (const item of result.outputItems)
141
141
  nativeInput.push(item);
142
142
  return result;
@@ -88,7 +88,7 @@ function createGithubCopilotProviderRuntime() {
88
88
  if (request.toolChoiceRequired)
89
89
  params.tool_choice = "required";
90
90
  try {
91
- return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal);
91
+ return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
92
92
  }
93
93
  catch (error) {
94
94
  throw withAuthGuidance(error);
@@ -135,7 +135,7 @@ function createGithubCopilotProviderRuntime() {
135
135
  if (request.toolChoiceRequired)
136
136
  params.tool_choice = "required";
137
137
  try {
138
- const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
138
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
139
139
  for (const item of result.outputItems)
140
140
  nativeInput.push(item);
141
141
  return result;
@@ -51,7 +51,7 @@ function createMinimaxProviderRuntime() {
51
51
  params.metadata = { trace_id: request.traceId };
52
52
  if (request.toolChoiceRequired)
53
53
  params.tool_choice = "required";
54
- return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal);
54
+ return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
55
55
  },
56
56
  };
57
57
  }
@@ -158,7 +158,7 @@ function createOpenAICodexProviderRuntime() {
158
158
  if (request.toolChoiceRequired)
159
159
  params.tool_choice = "required";
160
160
  try {
161
- const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
161
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
162
162
  for (const item of result.outputItems)
163
163
  nativeInput.push(item);
164
164
  return result;
@@ -84,13 +84,17 @@ class FinalAnswerStreamer {
84
84
  parser = new FinalAnswerParser();
85
85
  _detected = false;
86
86
  callbacks;
87
- constructor(callbacks) {
87
+ enabled;
88
+ constructor(callbacks, enabled = true) {
88
89
  this.callbacks = callbacks;
90
+ this.enabled = enabled;
89
91
  }
90
92
  get detected() { return this._detected; }
91
93
  get streamed() { return this.parser.active; }
92
94
  /** Mark final_answer as detected. Calls onClearText on the callbacks. */
93
95
  activate() {
96
+ if (!this.enabled)
97
+ return;
94
98
  if (this._detected)
95
99
  return;
96
100
  this._detected = true;
@@ -98,6 +102,8 @@ class FinalAnswerStreamer {
98
102
  }
99
103
  /** Feed an argument delta through the parser. Emits text via onTextChunk. */
100
104
  processDelta(delta) {
105
+ if (!this.enabled)
106
+ return;
101
107
  if (!this._detected)
102
108
  return;
103
109
  const text = this.parser.process(delta);
@@ -227,7 +233,7 @@ function toResponsesTools(ccTools) {
227
233
  strict: false,
228
234
  }));
229
235
  }
230
- async function streamChatCompletion(client, createParams, callbacks, signal) {
236
+ async function streamChatCompletion(client, createParams, callbacks, signal, eagerFinalAnswerStreaming = true) {
231
237
  (0, runtime_1.emitNervesEvent)({
232
238
  component: "engine",
233
239
  event: "engine.stream_start",
@@ -241,7 +247,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
241
247
  let toolCalls = {};
242
248
  let streamStarted = false;
243
249
  let usage;
244
- const answerStreamer = new FinalAnswerStreamer(callbacks);
250
+ const answerStreamer = new FinalAnswerStreamer(callbacks, eagerFinalAnswerStreaming);
245
251
  // State machine for parsing inline <think> tags (MiniMax pattern)
246
252
  let contentBuf = "";
247
253
  let inThinkTag = false;
@@ -387,7 +393,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
387
393
  finalAnswerStreamed: answerStreamer.streamed,
388
394
  };
389
395
  }
390
- async function streamResponsesApi(client, createParams, callbacks, signal) {
396
+ async function streamResponsesApi(client, createParams, callbacks, signal, eagerFinalAnswerStreaming = true) {
391
397
  (0, runtime_1.emitNervesEvent)({
392
398
  component: "engine",
393
399
  event: "engine.stream_start",
@@ -402,7 +408,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
402
408
  const outputItems = [];
403
409
  let currentToolCall = null;
404
410
  let usage;
405
- const answerStreamer = new FinalAnswerStreamer(callbacks);
411
+ const answerStreamer = new FinalAnswerStreamer(callbacks, eagerFinalAnswerStreaming);
406
412
  let functionCallCount = 0;
407
413
  for await (const event of response) {
408
414
  if (signal?.aborted)
@@ -1,14 +1,139 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.findActivePersistentObligation = findActivePersistentObligation;
4
+ exports.findStatusObligation = findStatusObligation;
4
5
  exports.renderActiveObligationSteering = renderActiveObligationSteering;
5
6
  exports.renderConcreteStatusGuidance = renderConcreteStatusGuidance;
7
+ exports.renderLiveThreadStatusShape = renderLiveThreadStatusShape;
8
+ exports.buildExactStatusReply = buildExactStatusReply;
9
+ exports.renderExactStatusReplyContract = renderExactStatusReplyContract;
6
10
  const runtime_1 = require("../nerves/runtime");
7
11
  function findActivePersistentObligation(frame) {
8
12
  if (!frame)
9
13
  return null;
10
14
  return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled") ?? null;
11
15
  }
16
+ function obligationTimestampMs(obligation) {
17
+ return Date.parse(obligation.updatedAt ?? obligation.createdAt);
18
+ }
19
+ function newestObligationFirst(left, right) {
20
+ return obligationTimestampMs(right) - obligationTimestampMs(left);
21
+ }
22
+ function matchesCurrentSession(frame, obligation) {
23
+ return matchesSessionOrigin(frame, obligation.origin);
24
+ }
25
+ function matchesSessionOrigin(frame, origin) {
26
+ return Boolean(frame.currentSession
27
+ && origin.friendId === frame.currentSession.friendId
28
+ && origin.channel === frame.currentSession.channel
29
+ && origin.key === frame.currentSession.key);
30
+ }
31
+ function sessionOriginKey(origin) {
32
+ return `${origin.friendId}/${origin.channel}/${origin.key}`;
33
+ }
34
+ function codingSessionTimestampMs(session) {
35
+ return Date.parse(session.lastActivityAt ?? session.startedAt);
36
+ }
37
+ function formatCodingLaneLabel(session) {
38
+ return `${session.runner} ${session.id}`;
39
+ }
40
+ function formatOtherSessionArtifact(obligation, codingSession) {
41
+ if (obligation?.currentArtifact?.trim())
42
+ return obligation.currentArtifact.trim();
43
+ if (obligation?.currentSurface?.kind === "merge" && obligation.currentSurface.label.trim()) {
44
+ return obligation.currentSurface.label.trim();
45
+ }
46
+ if (codingSession)
47
+ return "no PR or merge artifact yet";
48
+ return "no artifact yet";
49
+ }
50
+ function formatOtherSessionNextAction(obligation, codingSession) {
51
+ if (obligation?.nextAction?.trim())
52
+ return obligation.nextAction.trim();
53
+ if (obligation?.status === "waiting_for_merge") {
54
+ return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
55
+ }
56
+ if (obligation?.status === "updating_runtime") {
57
+ return "update runtime, verify version/changelog, then re-observe";
58
+ }
59
+ if (codingSession?.status === "waiting_input") {
60
+ return `answer ${formatCodingLaneLabel(codingSession)} and continue`;
61
+ }
62
+ if (codingSession?.status === "stalled") {
63
+ return `unstick ${formatCodingLaneLabel(codingSession)} and continue`;
64
+ }
65
+ if (codingSession) {
66
+ return "finish the coding pass and bring the result back there";
67
+ }
68
+ if (obligation) {
69
+ return "continue the active loop and bring the result back there";
70
+ }
71
+ return "check this session and bring back the latest concrete state";
72
+ }
73
+ function findFriendNameForOrigin(frame, origin) {
74
+ return (frame.friendActivity?.allOtherLiveSessions ?? []).find((entry) => entry.friendId === origin.friendId
75
+ && entry.channel === origin.channel
76
+ && entry.key === origin.key)?.friendName ?? origin.friendId;
77
+ }
78
+ function buildOtherActiveSessionLines(frame) {
79
+ const originMap = new Map();
80
+ for (const session of frame.friendActivity?.allOtherLiveSessions ?? []) {
81
+ originMap.set(sessionOriginKey(session), {
82
+ friendId: session.friendId,
83
+ channel: session.channel,
84
+ key: session.key,
85
+ });
86
+ }
87
+ for (const session of frame.otherCodingSessions ?? []) {
88
+ if (!session.originSession || matchesSessionOrigin(frame, session.originSession))
89
+ continue;
90
+ originMap.set(sessionOriginKey(session.originSession), session.originSession);
91
+ }
92
+ for (const obligation of frame.pendingObligations ?? []) {
93
+ if (obligation.status === "fulfilled" || matchesSessionOrigin(frame, obligation.origin))
94
+ continue;
95
+ originMap.set(sessionOriginKey(obligation.origin), obligation.origin);
96
+ }
97
+ const summaries = [...originMap.values()].map((origin) => {
98
+ const obligation = [...(frame.pendingObligations ?? [])]
99
+ .filter((candidate) => candidate.status !== "fulfilled" && sessionOriginKey(candidate.origin) === sessionOriginKey(origin))
100
+ .sort(newestObligationFirst)[0] ?? null;
101
+ const codingSession = [...(frame.otherCodingSessions ?? [])]
102
+ .filter((candidate) => candidate.originSession && sessionOriginKey(candidate.originSession) === sessionOriginKey(origin))
103
+ .sort((left, right) => codingSessionTimestampMs(right) - codingSessionTimestampMs(left))[0] ?? null;
104
+ const liveSession = (frame.friendActivity?.allOtherLiveSessions ?? []).find((candidate) => sessionOriginKey(candidate) === sessionOriginKey(origin)) ?? null;
105
+ const timestampMs = Math.max(liveSession?.lastActivityMs ?? 0, codingSession ? codingSessionTimestampMs(codingSession) : 0, obligation ? obligationTimestampMs(obligation) : 0);
106
+ const activeLane = codingSession
107
+ ? formatCodingLaneLabel(codingSession)
108
+ : obligation?.currentSurface?.label?.trim() || "this live thread";
109
+ const artifact = formatOtherSessionArtifact(obligation, codingSession);
110
+ const nextAction = formatOtherSessionNextAction(obligation, codingSession);
111
+ const status = obligation?.status ?? codingSession?.status ?? "active";
112
+ const friendName = findFriendNameForOrigin(frame, origin);
113
+ return {
114
+ timestampMs,
115
+ line: `- ${friendName}/${origin.channel}/${origin.key}: [${status}] ${activeLane}; artifact ${artifact}; next ${nextAction}`,
116
+ };
117
+ }).sort((left, right) => right.timestampMs - left.timestampMs);
118
+ return summaries.length > 0 ? summaries.map((entry) => entry.line) : ["- none"];
119
+ }
120
+ function findStatusObligation(frame) {
121
+ if (!frame)
122
+ return null;
123
+ const openObligations = [...(frame.pendingObligations ?? [])]
124
+ .filter((obligation) => obligation.status !== "fulfilled")
125
+ .sort(newestObligationFirst);
126
+ const sameSession = openObligations.find((obligation) => matchesCurrentSession(frame, obligation));
127
+ if (sameSession)
128
+ return sameSession;
129
+ return openObligations[0] ?? null;
130
+ }
131
+ function findCurrentSessionStatusObligation(frame) {
132
+ const openObligations = [...(frame.pendingObligations ?? [])]
133
+ .filter((obligation) => obligation.status !== "fulfilled")
134
+ .sort(newestObligationFirst);
135
+ return openObligations.find((obligation) => matchesCurrentSession(frame, obligation)) ?? null;
136
+ }
12
137
  function renderActiveObligationSteering(obligation) {
13
138
  (0, runtime_1.emitNervesEvent)({
14
139
  component: "mind",
@@ -30,6 +155,24 @@ i'm already working on something i owe ${name}.${surfaceLine}
30
155
 
31
156
  i should close that loop before i act like this is a fresh blank turn.`;
32
157
  }
158
+ function mergeArtifactFallback(obligation) {
159
+ const trimmed = obligation.content.trim();
160
+ if (!trimmed)
161
+ return "the fix";
162
+ const stripped = trimmed.replace(/^merge(?:\s+|$)/i, "").trim();
163
+ return stripped || "the fix";
164
+ }
165
+ function formatMergeArtifact(obligation) {
166
+ const currentArtifact = obligation.currentArtifact?.trim();
167
+ if (currentArtifact)
168
+ return currentArtifact;
169
+ if (obligation.currentSurface?.kind === "merge") {
170
+ const surfaceLabel = obligation.currentSurface.label.trim();
171
+ if (surfaceLabel)
172
+ return surfaceLabel;
173
+ }
174
+ return mergeArtifactFallback(obligation);
175
+ }
33
176
  function formatActiveLane(frame, obligation) {
34
177
  const liveCodingSession = frame.codingSessions?.[0];
35
178
  if (liveCodingSession) {
@@ -44,16 +187,31 @@ function formatActiveLane(frame, obligation) {
44
187
  ? `${liveCodingSession.runner} ${liveCodingSession.id} for ${liveCodingSession.originSession.channel}/${liveCodingSession.originSession.key}`
45
188
  : `${liveCodingSession.runner} ${liveCodingSession.id}`;
46
189
  }
47
- return obligation.currentSurface?.label || "this live loop";
190
+ return obligation.currentSurface?.label
191
+ || (frame.currentObligation?.trim() ? "this same thread" : "this live loop");
48
192
  }
49
193
  function formatCurrentArtifact(frame, obligation) {
50
- return obligation.currentArtifact
51
- || (obligation.currentSurface?.kind === "merge" ? obligation.currentSurface.label : "")
52
- || ((frame.codingSessions ?? []).length > 0 ? "no PR or merge artifact yet" : "no explicit artifact yet");
194
+ if (obligation?.currentArtifact)
195
+ return obligation.currentArtifact;
196
+ if (obligation?.currentSurface?.kind === "merge")
197
+ return obligation.currentSurface.label;
198
+ if (frame.currentObligation?.trim())
199
+ return "no artifact yet";
200
+ if ((frame.codingSessions ?? []).length > 0)
201
+ return "no PR or merge artifact yet";
202
+ return obligation ? "no explicit artifact yet" : "";
203
+ }
204
+ function isStatusCheckPrompt(text) {
205
+ const trimmed = text?.trim();
206
+ if (!trimmed)
207
+ return false;
208
+ return /^(what are you doing|what(?:'|’)s your status|status|status update|what changed|where are you at|where things stand)\??$/i.test(trimmed);
53
209
  }
54
210
  function formatNextAction(frame, obligation) {
55
- if (obligation.nextAction)
211
+ if (obligation?.nextAction)
56
212
  return obligation.nextAction;
213
+ const currentObligation = frame.currentObligation?.trim() ?? "";
214
+ const statusCheckPrompt = isStatusCheckPrompt(currentObligation);
57
215
  const liveCodingSession = frame.codingSessions?.[0];
58
216
  if (liveCodingSession?.status === "waiting_input") {
59
217
  return `answer ${liveCodingSession.runner} ${liveCodingSession.id} and continue`;
@@ -64,24 +222,91 @@ function formatNextAction(frame, obligation) {
64
222
  if (liveCodingSession) {
65
223
  return "finish the coding pass and bring the result back here";
66
224
  }
67
- if (obligation.status === "waiting_for_merge") {
68
- return `wait for checks, merge ${formatCurrentArtifact(frame, obligation)}, then update runtime`;
225
+ if (obligation?.status === "waiting_for_merge") {
226
+ return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
69
227
  }
70
- if (obligation.status === "updating_runtime") {
228
+ if (obligation?.status === "updating_runtime") {
71
229
  return "update runtime, verify version/changelog, then re-observe";
72
230
  }
73
- return "continue the active loop and bring the result back here";
231
+ if (currentObligation && !statusCheckPrompt) {
232
+ return `work on "${currentObligation}" and bring back a concrete artifact`;
233
+ }
234
+ return obligation ? "continue the active loop and bring the result back here" : "";
74
235
  }
75
236
  function renderConcreteStatusGuidance(frame, obligation) {
76
- if (!obligation)
77
- return "";
78
- const activeLane = formatActiveLane(frame, obligation);
237
+ const activeLane = obligation ? formatActiveLane(frame, obligation) : (frame.currentObligation?.trim() ? "this same thread" : "");
79
238
  const currentArtifact = formatCurrentArtifact(frame, obligation);
80
239
  const nextAction = formatNextAction(frame, obligation);
81
- return `if someone asks what i'm doing or for status, i answer from the concrete state:
82
- - active lane: ${activeLane}
83
- - current artifact: ${currentArtifact}
84
- - next action: ${nextAction}
240
+ const liveConversation = frame.currentSession
241
+ ? `${frame.currentSession.channel}/${frame.currentSession.key}`
242
+ : "";
243
+ if (!activeLane && !currentArtifact && !nextAction)
244
+ return "";
245
+ return `if someone asks what i'm doing or for status mid-task, i answer from these live facts instead of copying a canned block.
246
+ the live conversation is ${liveConversation || "not in a live conversation"}.
247
+ the active lane is ${activeLane}.
248
+ the current artifact is ${currentArtifact}.
249
+ if i just finished or verified something concrete in this live lane, i name that as the latest checkpoint.
250
+ the next action is ${nextAction}.
251
+
252
+ i use those facts to answer naturally unless this turn is an explicit direct status check, where the separate exact five-line contract applies.`;
253
+ }
254
+ function renderLiveThreadStatusShape(frame) {
255
+ if (!frame.currentSession)
256
+ return "";
257
+ return `if someone asks what i'm doing or for status mid-task in this live thread, i answer in these exact lines, in order, with no intro paragraph:
258
+ live conversation: ${frame.currentSession.channel}/${frame.currentSession.key}
259
+ active lane: this same thread
260
+ current artifact: <actual artifact or "no artifact yet">
261
+ latest checkpoint: <freshest concrete thing i just finished or verified>
262
+ next action: <smallest concrete next step i'm taking now>
85
263
 
86
- i don't replace that with a broad mission statement if this concrete state is available.`;
264
+ no recap paragraph before those lines.
265
+ no option list.
266
+ present tense only.
267
+ if a finished step matters, i label it "just finished" instead of presenting it as current work.`;
268
+ }
269
+ function buildExactStatusReply(frame, obligation, latestCheckpoint, statusCheckScope) {
270
+ const headerObligation = statusCheckScope === "all-sessions-family"
271
+ ? findCurrentSessionStatusObligation(frame)
272
+ : obligation;
273
+ const liveConversation = frame.currentSession
274
+ ? `${frame.currentSession.channel}/${frame.currentSession.key}`
275
+ : "not in a live conversation";
276
+ const activeLane = headerObligation
277
+ ? formatActiveLane(frame, headerObligation)
278
+ : (frame.currentSession ? "this same thread" : "this live loop");
279
+ const currentArtifact = formatCurrentArtifact(frame, headerObligation) || "no artifact yet";
280
+ const nextAction = formatNextAction(frame, headerObligation) || "continue the active loop and bring the result back here";
281
+ const latest = latestCheckpoint.trim() || "<freshest concrete thing i just finished or verified>";
282
+ const lines = [
283
+ `live conversation: ${liveConversation}`,
284
+ `active lane: ${activeLane}`,
285
+ `current artifact: ${currentArtifact}`,
286
+ `latest checkpoint: ${latest}`,
287
+ `next action: ${nextAction}`,
288
+ ];
289
+ if (statusCheckScope === "all-sessions-family") {
290
+ lines.push("other active sessions:");
291
+ lines.push(...buildOtherActiveSessionLines(frame));
292
+ }
293
+ return lines.join("\n");
294
+ }
295
+ function renderExactStatusReplyContract(frame, obligation, statusCheckScope) {
296
+ const headerObligation = statusCheckScope === "all-sessions-family"
297
+ ? findCurrentSessionStatusObligation(frame)
298
+ : obligation;
299
+ if (statusCheckScope === "all-sessions-family") {
300
+ return `reply using exactly this status shape and nothing else:
301
+ live conversation: ${frame.currentSession ? `${frame.currentSession.channel}/${frame.currentSession.key}` : "not in a live conversation"}
302
+ active lane: ${headerObligation ? formatActiveLane(frame, headerObligation) : (frame.currentSession ? "this same thread" : "this live loop")}
303
+ current artifact: ${formatCurrentArtifact(frame, headerObligation) || "no artifact yet"}
304
+ latest checkpoint: <freshest concrete thing i just finished or verified>
305
+ next action: ${formatNextAction(frame, headerObligation) || "continue the active loop and bring the result back here"}
306
+ other active sessions:
307
+ - <session label>: <what i'm doing there right now>`;
308
+ }
309
+ return `reply using exactly these five lines and nothing else:
310
+ ${buildExactStatusReply(frame, obligation, "<freshest concrete thing i just finished or verified>")}
311
+ `;
87
312
  }
@@ -464,15 +464,18 @@ function centerOfGravitySteeringSection(channel, options) {
464
464
  if (!frame)
465
465
  return "";
466
466
  const cog = frame.centerOfGravity;
467
- if (cog === "local-turn")
468
- return "";
469
467
  const job = frame.inner?.job;
470
468
  const activeObligation = (0, obligation_steering_1.findActivePersistentObligation)(frame);
469
+ const statusObligation = (0, obligation_steering_1.findStatusObligation)(frame);
470
+ const genericConcreteStatus = (0, obligation_steering_1.renderConcreteStatusGuidance)(frame, statusObligation);
471
+ if (cog === "local-turn") {
472
+ return genericConcreteStatus || (0, obligation_steering_1.renderLiveThreadStatusShape)(frame);
473
+ }
471
474
  if (cog === "inward-work") {
472
475
  if (activeObligation) {
473
476
  return `${(0, obligation_steering_1.renderActiveObligationSteering)(activeObligation)}
474
477
 
475
- ${(0, obligation_steering_1.renderConcreteStatusGuidance)(frame, activeObligation)}`;
478
+ ${genericConcreteStatus}`;
476
479
  }
477
480
  if (job?.status === "queued" || job?.status === "running") {
478
481
  const originClause = job.origin
@@ -514,6 +517,9 @@ i already have coding work running in ${liveCodingSession.runner} ${liveCodingSe
514
517
 
515
518
  i should orient around that live lane first, then decide what still needs to come back here.`;
516
519
  }
520
+ if (genericConcreteStatus) {
521
+ return genericConcreteStatus;
522
+ }
517
523
  return `## where my attention is
518
524
  i have unfinished work that needs attention before i move on.
519
525
 
@@ -529,6 +535,17 @@ i should keep the different sides aligned. what i learn here may matter there, a
529
535
  /* v8 ignore next -- unreachable: all center-of-gravity modes covered above @preserve */
530
536
  return "";
531
537
  }
538
+ function statusCheckSection(channel, options) {
539
+ if (channel === "inner" || !options?.statusCheckRequested)
540
+ return "";
541
+ const frame = options.activeWorkFrame;
542
+ if (!frame)
543
+ return "";
544
+ const activeObligation = (0, obligation_steering_1.findStatusObligation)(frame);
545
+ return `## status question on this turn
546
+ the user is asking for current status right now.
547
+ ${(0, obligation_steering_1.renderExactStatusReplyContract)(frame, activeObligation, options.statusCheckScope)}`;
548
+ }
532
549
  function commitmentsSection(options) {
533
550
  if (!options?.activeWorkFrame)
534
551
  return "";
@@ -736,6 +753,7 @@ async function buildSystem(channel = "cli", options, context) {
736
753
  skillsSection(),
737
754
  taskBoardSection(),
738
755
  activeWorkSection(options),
756
+ statusCheckSection(channel, options),
739
757
  centerOfGravitySteeringSection(channel, options),
740
758
  commitmentsSection(options),
741
759
  delegationHintSection(options),
@@ -115,16 +115,20 @@ function deriveObligationMilestone(update) {
115
115
  return null;
116
116
  }
117
117
  function isSafeProgressSnippet(snippet) {
118
+ const normalized = snippet.trim();
118
119
  const wordCount = snippet.split(/\s+/).filter(Boolean).length;
119
- return (snippet.length <= 80
120
+ return (normalized.length <= 80
121
+ && wordCount >= 2
120
122
  && wordCount <= 8
121
- && !snippet.includes(":")
122
- && !snippet.startsWith("**")
123
- && !/^Respond with\b/i.test(snippet)
124
- && !/^Coding session metadata\b/i.test(snippet)
125
- && !/^sessionId\b/i.test(snippet)
126
- && !/^taskRef\b/i.test(snippet)
127
- && !/^parentAgent\b/i.test(snippet));
123
+ && /[A-Za-z]{3,}/.test(normalized)
124
+ && !normalized.includes(":")
125
+ && !/[{}\[\]();]/.test(normalized)
126
+ && !normalized.startsWith("**")
127
+ && !/^Respond with\b/i.test(normalized)
128
+ && !/^Coding session metadata\b/i.test(normalized)
129
+ && !/^sessionId\b/i.test(normalized)
130
+ && !/^taskRef\b/i.test(normalized)
131
+ && !/^parentAgent\b/i.test(normalized));
128
132
  }
129
133
  function pickUpdateSnippet(update) {
130
134
  return (lastMeaningfulLine(update.text)
@@ -99,6 +99,9 @@ function selectCodingStatusSessions(sessions, currentSession) {
99
99
  }
100
100
  return [...sessions].sort(latestSessionFirst);
101
101
  }
102
+ function buildCodingObligationContent(taskRef) {
103
+ return `finish ${taskRef} and bring the result back`;
104
+ }
102
105
  const codingSpawnTool = {
103
106
  type: "function",
104
107
  function: {
@@ -231,6 +234,13 @@ exports.codingToolDefinitions = [
231
234
  }
232
235
  return JSON.stringify({ ...existingSession, reused: true });
233
236
  }
237
+ if (request.originSession && !request.obligationId) {
238
+ const created = (0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
239
+ origin: request.originSession,
240
+ content: buildCodingObligationContent(taskRef),
241
+ });
242
+ request.obligationId = created.id;
243
+ }
234
244
  const session = await manager.spawnSession(request);
235
245
  if (session.obligationId) {
236
246
  (0, obligations_1.advanceObligation)((0, identity_1.getAgentRoot)(), session.obligationId, {
@@ -38,6 +38,29 @@ function emptyTaskBoard() {
38
38
  activeBridges: [],
39
39
  };
40
40
  }
41
+ const STATUS_CHECK_PATTERNS = [
42
+ /^\s*what are you doing\??\s*$/i,
43
+ /^\s*what'?s your status\??\s*$/i,
44
+ /^\s*status\??\s*$/i,
45
+ /^\s*status update\??\s*$/i,
46
+ /^\s*what changed\??\s*$/i,
47
+ /^\s*where (?:are you at|things stand)\??\s*$/i,
48
+ ];
49
+ function isStatusCheckRequested(ingressTexts) {
50
+ const latest = ingressTexts
51
+ ?.map((text) => text.trim())
52
+ .filter((text) => text.length > 0)
53
+ .at(-1);
54
+ if (!latest)
55
+ return false;
56
+ return STATUS_CHECK_PATTERNS.some((pattern) => pattern.test(latest));
57
+ }
58
+ function isLiveCodingSessionStatus(status) {
59
+ return status === "spawning"
60
+ || status === "running"
61
+ || status === "waiting_input"
62
+ || status === "stalled";
63
+ }
41
64
  function readInnerWorkState() {
42
65
  const defaultJob = {
43
66
  status: "idle",
@@ -188,23 +211,21 @@ async function handleInboundTurn(input) {
188
211
  pendingObligations = [];
189
212
  }
190
213
  let codingSessions = [];
214
+ let otherCodingSessions = [];
191
215
  try {
192
- codingSessions = (0, coding_1.getCodingSessionManager)()
216
+ const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
193
217
  .listSessions()
194
- .filter((session) => {
195
- if (session.status !== "spawning" && session.status !== "running" && session.status !== "waiting_input" && session.status !== "stalled") {
196
- return false;
197
- }
198
- if (!session.originSession) {
199
- return false;
200
- }
201
- return session.originSession.friendId === currentSession.friendId
202
- && session.originSession.channel === currentSession.channel
203
- && session.originSession.key === currentSession.key;
204
- });
218
+ .filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
219
+ codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === currentSession.friendId
220
+ && session.originSession.channel === currentSession.channel
221
+ && session.originSession.key === currentSession.key);
222
+ otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === currentSession.friendId
223
+ && session.originSession.channel === currentSession.channel
224
+ && session.originSession.key === currentSession.key));
205
225
  }
206
226
  catch {
207
227
  codingSessions = [];
228
+ otherCodingSessions = [];
208
229
  }
209
230
  const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
210
231
  currentSession,
@@ -213,6 +234,7 @@ async function handleInboundTurn(input) {
213
234
  inner: readInnerWorkState(),
214
235
  bridges: activeBridges,
215
236
  codingSessions,
237
+ otherCodingSessions,
216
238
  pendingObligations,
217
239
  taskBoard: (() => {
218
240
  try {
@@ -271,6 +293,10 @@ async function handleInboundTurn(input) {
271
293
  }
272
294
  // Step 5: runAgent
273
295
  const existingToolContext = input.runAgentOptions?.toolContext;
296
+ const statusCheckRequested = isStatusCheckRequested(input.continuityIngressTexts);
297
+ const statusCheckScope = statusCheckRequested && resolvedContext.friend.trustLevel === "family"
298
+ ? "all-sessions-family"
299
+ : undefined;
274
300
  const runAgentOptions = {
275
301
  ...input.runAgentOptions,
276
302
  bridgeContext,
@@ -278,6 +304,9 @@ async function handleInboundTurn(input) {
278
304
  delegationDecision,
279
305
  currentSessionKey: currentSession.key,
280
306
  currentObligation,
307
+ statusCheckRequested,
308
+ statusCheckScope,
309
+ toolChoiceRequired: statusCheckRequested ? true : input.runAgentOptions?.toolChoiceRequired,
281
310
  mustResolveBeforeHandoff,
282
311
  setMustResolveBeforeHandoff: (value) => {
283
312
  mustResolveBeforeHandoff = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.102",
3
+ "version": "0.1.0-alpha.104",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",