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

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,13 @@
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.103",
6
+ "changes": [
7
+ "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.",
8
+ "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."
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.102",
6
13
  "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) {
@@ -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,37 @@ function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringF
272
273
  }
273
274
  return null;
274
275
  }
276
+ function hasExactStatusReplyShape(answer) {
277
+ const lines = answer.trimEnd().split(/\r?\n/);
278
+ if (lines.length !== 5)
279
+ return false;
280
+ return (/^live conversation:\s+\S.+$/i.test(lines[0])
281
+ && /^active lane:\s+\S.+$/i.test(lines[1])
282
+ && /^current artifact:\s+\S.+$/i.test(lines[2])
283
+ && /^latest checkpoint:\s+\S.+$/i.test(lines[3])
284
+ && /^next action:\s+\S.+$/i.test(lines[4]));
285
+ }
286
+ function extractLatestCheckpoint(answer) {
287
+ const latestLine = answer.trimEnd().split(/\r?\n/)[3];
288
+ return latestLine.replace(/^latest checkpoint:\s*/i, "").trim();
289
+ }
290
+ function getStatusReplyRetryError(answer, statusCheckRequested, activeWorkFrame) {
291
+ if (!statusCheckRequested || answer == null)
292
+ return null;
293
+ if (hasExactStatusReplyShape(answer))
294
+ return null;
295
+ if (!activeWorkFrame) {
296
+ return `the user asked for current status. call final_answer again using exactly these five non-empty lines and nothing else:
297
+ live conversation: ...
298
+ active lane: ...
299
+ current artifact: ...
300
+ latest checkpoint: ...
301
+ next action: ...`;
302
+ }
303
+ return `the user asked for current status right now.
304
+ ${(0, obligation_steering_1.renderExactStatusReplyContract)(activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(activeWorkFrame))}
305
+ call final_answer again using that exact five-line shape and nothing else.`;
306
+ }
275
307
  // Re-export kick utilities for backward compat
276
308
  var kicks_1 = require("./kicks");
277
309
  Object.defineProperty(exports, "hasToolIntent", { enumerable: true, get: function () { return kicks_1.hasToolIntent; } });
@@ -551,6 +583,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
551
583
  traceId,
552
584
  toolChoiceRequired,
553
585
  reasoningEffort: currentReasoningEffort,
586
+ eagerFinalAnswerStreaming: !options?.statusCheckRequested,
554
587
  });
555
588
  // Track usage from the latest API call
556
589
  if (result.usage)
@@ -599,15 +632,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
599
632
  // Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
600
633
  const { answer, intent } = parseFinalAnswerPayload(result.toolCalls[0].arguments);
601
634
  const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
635
+ const statusReplyRetryError = getStatusReplyRetryError(answer, options?.statusCheckRequested, options?.activeWorkFrame);
636
+ const exactStatusReplyAccepted = Boolean(options?.statusCheckRequested) && !statusReplyRetryError && answer != null;
637
+ const deliveredAnswer = exactStatusReplyAccepted && options?.activeWorkFrame
638
+ ? (0, obligation_steering_1.buildExactStatusReply)(options.activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(options.activeWorkFrame), extractLatestCheckpoint(answer))
639
+ : answer;
602
640
  const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
603
641
  const validTerminalIntent = intent === "complete" || intent === "blocked";
604
- const validClosure = answer != null
605
- && !retryError
606
- && (!mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
642
+ const validClosure = deliveredAnswer != null
643
+ && (exactStatusReplyAccepted || !retryError)
644
+ && !statusReplyRetryError
645
+ && (exactStatusReplyAccepted || !mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
607
646
  if (validClosure) {
608
647
  completion = {
609
- answer,
610
- intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
648
+ answer: deliveredAnswer,
649
+ intent: validDirectReply && !exactStatusReplyAccepted ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
611
650
  };
612
651
  if (result.finalAnswerStreamed) {
613
652
  // The streaming layer already parsed and emitted the answer
@@ -619,10 +658,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
619
658
  callbacks.onClearText?.();
620
659
  // Emit the answer through the callback pipeline so channels receive it.
621
660
  // Never truncate -- channel adapters handle splitting long messages.
622
- callbacks.onTextChunk(answer);
661
+ callbacks.onTextChunk(deliveredAnswer);
623
662
  }
624
663
  messages.push(msg);
625
- if (validDirectReply) {
664
+ if (validDirectReply && !exactStatusReplyAccepted) {
626
665
  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
666
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
628
667
  providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
@@ -641,8 +680,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
641
680
  // assistant msg + error tool result and let the model try again.
642
681
  callbacks.onClearText?.();
643
682
  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.");
683
+ const toolRetryMessage = statusReplyRetryError
684
+ ?? retryError
685
+ ?? "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
686
+ messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
687
+ providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
646
688
  }
647
689
  continue;
648
690
  }
@@ -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,41 @@
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 Boolean(frame.currentSession
24
+ && obligation.origin.friendId === frame.currentSession.friendId
25
+ && obligation.origin.channel === frame.currentSession.channel
26
+ && obligation.origin.key === frame.currentSession.key);
27
+ }
28
+ function findStatusObligation(frame) {
29
+ if (!frame)
30
+ return null;
31
+ const openObligations = [...(frame.pendingObligations ?? [])]
32
+ .filter((obligation) => obligation.status !== "fulfilled")
33
+ .sort(newestObligationFirst);
34
+ const sameSession = openObligations.find((obligation) => matchesCurrentSession(frame, obligation));
35
+ if (sameSession)
36
+ return sameSession;
37
+ return openObligations[0] ?? null;
38
+ }
12
39
  function renderActiveObligationSteering(obligation) {
13
40
  (0, runtime_1.emitNervesEvent)({
14
41
  component: "mind",
@@ -30,6 +57,24 @@ i'm already working on something i owe ${name}.${surfaceLine}
30
57
 
31
58
  i should close that loop before i act like this is a fresh blank turn.`;
32
59
  }
60
+ function mergeArtifactFallback(obligation) {
61
+ const trimmed = obligation.content.trim();
62
+ if (!trimmed)
63
+ return "the fix";
64
+ const stripped = trimmed.replace(/^merge(?:\s+|$)/i, "").trim();
65
+ return stripped || "the fix";
66
+ }
67
+ function formatMergeArtifact(obligation) {
68
+ const currentArtifact = obligation.currentArtifact?.trim();
69
+ if (currentArtifact)
70
+ return currentArtifact;
71
+ if (obligation.currentSurface?.kind === "merge") {
72
+ const surfaceLabel = obligation.currentSurface.label.trim();
73
+ if (surfaceLabel)
74
+ return surfaceLabel;
75
+ }
76
+ return mergeArtifactFallback(obligation);
77
+ }
33
78
  function formatActiveLane(frame, obligation) {
34
79
  const liveCodingSession = frame.codingSessions?.[0];
35
80
  if (liveCodingSession) {
@@ -44,16 +89,31 @@ function formatActiveLane(frame, obligation) {
44
89
  ? `${liveCodingSession.runner} ${liveCodingSession.id} for ${liveCodingSession.originSession.channel}/${liveCodingSession.originSession.key}`
45
90
  : `${liveCodingSession.runner} ${liveCodingSession.id}`;
46
91
  }
47
- return obligation.currentSurface?.label || "this live loop";
92
+ return obligation.currentSurface?.label
93
+ || (frame.currentObligation?.trim() ? "this same thread" : "this live loop");
48
94
  }
49
95
  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");
96
+ if (obligation?.currentArtifact)
97
+ return obligation.currentArtifact;
98
+ if (obligation?.currentSurface?.kind === "merge")
99
+ return obligation.currentSurface.label;
100
+ if (frame.currentObligation?.trim())
101
+ return "no artifact yet";
102
+ if ((frame.codingSessions ?? []).length > 0)
103
+ return "no PR or merge artifact yet";
104
+ return obligation ? "no explicit artifact yet" : "";
105
+ }
106
+ function isStatusCheckPrompt(text) {
107
+ const trimmed = text?.trim();
108
+ if (!trimmed)
109
+ return false;
110
+ 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
111
  }
54
112
  function formatNextAction(frame, obligation) {
55
- if (obligation.nextAction)
113
+ if (obligation?.nextAction)
56
114
  return obligation.nextAction;
115
+ const currentObligation = frame.currentObligation?.trim() ?? "";
116
+ const statusCheckPrompt = isStatusCheckPrompt(currentObligation);
57
117
  const liveCodingSession = frame.codingSessions?.[0];
58
118
  if (liveCodingSession?.status === "waiting_input") {
59
119
  return `answer ${liveCodingSession.runner} ${liveCodingSession.id} and continue`;
@@ -64,24 +124,70 @@ function formatNextAction(frame, obligation) {
64
124
  if (liveCodingSession) {
65
125
  return "finish the coding pass and bring the result back here";
66
126
  }
67
- if (obligation.status === "waiting_for_merge") {
68
- return `wait for checks, merge ${formatCurrentArtifact(frame, obligation)}, then update runtime`;
127
+ if (obligation?.status === "waiting_for_merge") {
128
+ return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
69
129
  }
70
- if (obligation.status === "updating_runtime") {
130
+ if (obligation?.status === "updating_runtime") {
71
131
  return "update runtime, verify version/changelog, then re-observe";
72
132
  }
73
- return "continue the active loop and bring the result back here";
133
+ if (currentObligation && !statusCheckPrompt) {
134
+ return `work on "${currentObligation}" and bring back a concrete artifact`;
135
+ }
136
+ return obligation ? "continue the active loop and bring the result back here" : "";
74
137
  }
75
138
  function renderConcreteStatusGuidance(frame, obligation) {
76
- if (!obligation)
77
- return "";
78
- const activeLane = formatActiveLane(frame, obligation);
139
+ const activeLane = obligation ? formatActiveLane(frame, obligation) : (frame.currentObligation?.trim() ? "this same thread" : "");
79
140
  const currentArtifact = formatCurrentArtifact(frame, obligation);
80
141
  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}
142
+ const liveConversation = frame.currentSession
143
+ ? `${frame.currentSession.channel}/${frame.currentSession.key}`
144
+ : "";
145
+ if (!activeLane && !currentArtifact && !nextAction)
146
+ return "";
147
+ 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.
148
+ the live conversation is ${liveConversation || "not in a live conversation"}.
149
+ the active lane is ${activeLane}.
150
+ the current artifact is ${currentArtifact}.
151
+ if i just finished or verified something concrete in this live lane, i name that as the latest checkpoint.
152
+ the next action is ${nextAction}.
85
153
 
86
- i don't replace that with a broad mission statement if this concrete state is available.`;
154
+ i use those facts to answer naturally unless this turn is an explicit direct status check, where the separate exact five-line contract applies.`;
155
+ }
156
+ function renderLiveThreadStatusShape(frame) {
157
+ if (!frame.currentSession)
158
+ return "";
159
+ 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:
160
+ live conversation: ${frame.currentSession.channel}/${frame.currentSession.key}
161
+ active lane: this same thread
162
+ current artifact: <actual artifact or "no artifact yet">
163
+ latest checkpoint: <freshest concrete thing i just finished or verified>
164
+ next action: <smallest concrete next step i'm taking now>
165
+
166
+ no recap paragraph before those lines.
167
+ no option list.
168
+ present tense only.
169
+ if a finished step matters, i label it "just finished" instead of presenting it as current work.`;
170
+ }
171
+ function buildExactStatusReply(frame, obligation, latestCheckpoint) {
172
+ const liveConversation = frame.currentSession
173
+ ? `${frame.currentSession.channel}/${frame.currentSession.key}`
174
+ : "not in a live conversation";
175
+ const activeLane = obligation
176
+ ? formatActiveLane(frame, obligation)
177
+ : (frame.currentSession ? "this same thread" : "this live loop");
178
+ const currentArtifact = formatCurrentArtifact(frame, obligation) || 'no artifact yet';
179
+ const nextAction = formatNextAction(frame, obligation) || "continue the active loop and bring the result back here";
180
+ const latest = latestCheckpoint.trim() || "<freshest concrete thing i just finished or verified>";
181
+ return [
182
+ `live conversation: ${liveConversation}`,
183
+ `active lane: ${activeLane}`,
184
+ `current artifact: ${currentArtifact}`,
185
+ `latest checkpoint: ${latest}`,
186
+ `next action: ${nextAction}`,
187
+ ].join("\n");
188
+ }
189
+ function renderExactStatusReplyContract(frame, obligation) {
190
+ return `reply using exactly these five lines and nothing else:
191
+ ${buildExactStatusReply(frame, obligation, "<freshest concrete thing i just finished or verified>")}
192
+ `;
87
193
  }
@@ -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)}`;
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,23 @@ 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
+ }
41
58
  function readInnerWorkState() {
42
59
  const defaultJob = {
43
60
  status: "idle",
@@ -271,6 +288,7 @@ async function handleInboundTurn(input) {
271
288
  }
272
289
  // Step 5: runAgent
273
290
  const existingToolContext = input.runAgentOptions?.toolContext;
291
+ const statusCheckRequested = isStatusCheckRequested(input.continuityIngressTexts);
274
292
  const runAgentOptions = {
275
293
  ...input.runAgentOptions,
276
294
  bridgeContext,
@@ -278,6 +296,8 @@ async function handleInboundTurn(input) {
278
296
  delegationDecision,
279
297
  currentSessionKey: currentSession.key,
280
298
  currentObligation,
299
+ statusCheckRequested,
300
+ toolChoiceRequired: statusCheckRequested ? true : input.runAgentOptions?.toolChoiceRequired,
281
301
  mustResolveBeforeHandoff,
282
302
  setMustResolveBeforeHandoff: (value) => {
283
303
  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.103",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",