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

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,21 @@
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.134",
6
+ "changes": [
7
+ "Metacognitive tool redesign: final_answer renamed to settle, no_response renamed to observe. RunAgentOutcome values now 'settled' and 'observed'.",
8
+ "New surface tool: inner-dialog-only tool for routing thoughts outward to friends' freshest active sessions. Replaces post-turn routeDelegatedCompletion with inline delivery.",
9
+ "Attention queue: FIFO queue of delegated work items seeded from drained pending messages and recovered obligations. Visible to the model at inner dialog turn start.",
10
+ "Surface-before-settle gate: settle rejected in inner dialog until all attention queue items are surfaced.",
11
+ "Channel-based tool filtering: observe excluded from 1:1 and inner dialog; go_inward and send_message excluded from inner dialog; surface included only in inner dialog.",
12
+ "go_inward content param renamed to topic with metacognitive handoff framing.",
13
+ "Shared sole-call interception pattern extracted from duplicated core.ts logic.",
14
+ "Exact-origin routing removed from routeDelegatedCompletion; routing now bridge, freshest, deferred only.",
15
+ "ouro attention CLI: list held items, show details by ID, view surfacing history.",
16
+ "System prompts updated with metacognitive framing for all new tool names."
17
+ ]
18
+ },
4
19
  {
5
20
  "version": "0.1.0-alpha.133",
6
21
  "changes": [
@@ -8,7 +8,7 @@ exports.getProvider = getProvider;
8
8
  exports.createSummarize = createSummarize;
9
9
  exports.getProviderDisplayLabel = getProviderDisplayLabel;
10
10
  exports.isExternalStateQuery = isExternalStateQuery;
11
- exports.getFinalAnswerRetryError = getFinalAnswerRetryError;
11
+ exports.getSettleRetryError = getSettleRetryError;
12
12
  exports.stripLastToolCalls = stripLastToolCalls;
13
13
  exports.repairOrphanedToolCalls = repairOrphanedToolCalls;
14
14
  exports.isTransientError = isTransientError;
@@ -18,6 +18,7 @@ const config_1 = require("./config");
18
18
  const identity_1 = require("./identity");
19
19
  const tools_1 = require("../repertoire/tools");
20
20
  const channel_1 = require("../mind/friends/channel");
21
+ const surface_tool_1 = require("../senses/surface-tool");
21
22
  const runtime_1 = require("../nerves/runtime");
22
23
  const context_1 = require("../mind/context");
23
24
  const prompt_1 = require("../mind/prompt");
@@ -170,6 +171,13 @@ Object.defineProperty(exports, "toResponsesTools", { enumerable: true, get: func
170
171
  // Re-export prompt functions for backward compat
171
172
  var prompt_2 = require("../mind/prompt");
172
173
  Object.defineProperty(exports, "buildSystem", { enumerable: true, get: function () { return prompt_2.buildSystem; } });
174
+ // Sole-call tools must be the only tool call in a turn. When they appear
175
+ // alongside other tools, the sole-call tool is rejected with this message.
176
+ const SOLE_CALL_REJECTION = {
177
+ settle: "rejected: settle must be the only tool call. finish your work first, then call settle alone.",
178
+ observe: "rejected: observe must be the only tool call. call observe alone when you want to stay silent.",
179
+ go_inward: "rejected: go_inward must be the only tool call. finish your other work first, then call go_inward alone.",
180
+ };
173
181
  const DELEGATION_REASON_PROSE_HANDOFF = {
174
182
  explicit_reflection: "something in the conversation called for reflection",
175
183
  cross_session: "this touches other conversations",
@@ -183,34 +191,34 @@ function buildGoInwardHandoffPacket(params) {
183
191
  const reasonProse = reasons.length > 0
184
192
  ? reasons.map((r) => DELEGATION_REASON_PROSE_HANDOFF[r]).join("; ")
185
193
  : "this felt like it needed more thought";
186
- const returnAddress = params.currentSession
187
- ? `${params.currentSession.friendId}/${params.currentSession.channel}/${params.currentSession.key}`
188
- : "no specific return -- just thinking";
189
- let obligationLine;
194
+ const whoAsked = params.currentSession
195
+ ? params.currentSession.friendId
196
+ : "no one -- just thinking";
197
+ let holdingLine;
190
198
  if (params.outwardClosureRequired && params.currentSession) {
191
- obligationLine = `i need to come back to ${params.currentSession.friendId} with something`;
199
+ holdingLine = `i'm holding something for ${params.currentSession.friendId}`;
192
200
  }
193
201
  else {
194
- obligationLine = "no obligation -- just thinking";
202
+ holdingLine = "nothing -- just thinking";
195
203
  }
196
204
  return [
197
205
  "## what i need to think about",
198
- params.content,
206
+ params.topic,
199
207
  "",
200
208
  "## why this came up",
201
209
  reasonProse,
202
210
  "",
203
- "## where to bring it back",
204
- returnAddress,
211
+ "## who asked",
212
+ whoAsked,
205
213
  "",
206
- "## what i owe",
207
- obligationLine,
214
+ "## what i'm holding",
215
+ holdingLine,
208
216
  "",
209
217
  "## thinking mode",
210
218
  params.mode,
211
219
  ].join("\n");
212
220
  }
213
- function parseFinalAnswerPayload(argumentsText) {
221
+ function parseSettlePayload(argumentsText) {
214
222
  try {
215
223
  const parsed = JSON.parse(argumentsText);
216
224
  if (typeof parsed === "string") {
@@ -237,9 +245,9 @@ function isExternalStateQuery(toolName, args) {
237
245
  const cmd = String(args.command ?? "");
238
246
  return /\bgh\s+(pr|run|api|issue)\b/.test(cmd) || /\bnpm\s+(view|info|show)\b/.test(cmd);
239
247
  }
240
- function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp, _delegationDecision, sawSendMessageSelf, sawGoInward, _sawQuerySession, currentObligation, innerJob, sawExternalStateQuery) {
248
+ function getSettleRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp, _delegationDecision, sawSendMessageSelf, sawGoInward, _sawQuerySession, currentObligation, innerJob, sawExternalStateQuery) {
241
249
  // Delegation adherence removed: the delegation decision is surfaced in the
242
- // system prompt as a suggestion. Hard-gating final_answer caused infinite
250
+ // system prompt as a suggestion. Hard-gating settle caused infinite
243
251
  // rejection loops where the agent couldn't respond to the user at all.
244
252
  // The agent is free to follow or ignore the delegation hint.
245
253
  // 2. Pending obligation not addressed
@@ -248,11 +256,11 @@ function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringF
248
256
  }
249
257
  // 3. mustResolveBeforeHandoff + missing intent
250
258
  if (mustResolveBeforeHandoff && !intent) {
251
- return "your final_answer is missing required intent. when you must keep going until done or blocked, call final_answer again with answer plus intent=complete, blocked, or direct_reply.";
259
+ return "your settle is missing required intent. when you must keep going until done or blocked, call settle again with answer plus intent=complete, blocked, or direct_reply.";
252
260
  }
253
261
  // 4. mustResolveBeforeHandoff + direct_reply without follow-up
254
262
  if (mustResolveBeforeHandoff && intent === "direct_reply" && !sawSteeringFollowUp) {
255
- return "your final_answer used intent=direct_reply without a newer steering follow-up. continue the unresolved work, or call final_answer again with intent=complete or blocked when appropriate.";
263
+ return "your settle used intent=direct_reply without a newer steering follow-up. continue the unresolved work, or call settle again with intent=complete or blocked when appropriate.";
256
264
  }
257
265
  // 5. mustResolveBeforeHandoff + complete while a live return loop is still active
258
266
  if (mustResolveBeforeHandoff && intent === "complete" && currentObligation && !sawSteeringFollowUp) {
@@ -260,7 +268,7 @@ function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringF
260
268
  }
261
269
  // 6. External-state grounding: obligation + complete requires fresh external verification
262
270
  if (intent === "complete" && currentObligation && !sawExternalStateQuery && !sawSteeringFollowUp) {
263
- return "you're claiming this work is complete, but the external state hasn't been verified this turn. ground your claim with a fresh check (gh pr view, npm view, gh run view, etc.) before calling final_answer.";
271
+ return "you're claiming this work is complete, but the external state hasn't been verified this turn. ground your claim with a fresh check (gh pr view, npm view, gh run view, etc.) before calling settle.";
264
272
  }
265
273
  return null;
266
274
  }
@@ -480,7 +488,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
480
488
  let lastUsage;
481
489
  let overflowRetried = false;
482
490
  let retryCount = 0;
483
- let outcome = "complete";
491
+ let outcome = "settled";
484
492
  let completion;
485
493
  let terminalError;
486
494
  let terminalErrorClassification;
@@ -513,13 +521,24 @@ async function runAgent(messages, callbacks, channel, signal, options) {
513
521
  // This prevents stale provider caches from replaying prior-turn context.
514
522
  providerRuntime.resetTurnState(messages);
515
523
  while (!done) {
516
- // When toolChoiceRequired is true (the default), include final_answer
517
- // so the model can signal completion. With tool_choice: required, the
518
- // model must call a tool every turn final_answer is how it exits.
519
- // Overridable via options.toolChoiceRequired = false (e.g. CLI).
520
- const activeTools = toolChoiceRequired
521
- ? [...baseTools, tools_1.goInwardTool, ...(currentContext?.isGroupChat ? [tools_1.noResponseTool] : []), tools_1.finalAnswerTool]
524
+ // Channel-based tool filtering:
525
+ // - Inner dialog: exclude go_inward (already inward), send_message (delivery via surface), observe (no one to observe)
526
+ // - 1:1 sessions: exclude observe (can't ignore someone talking directly to you)
527
+ // - Group chats: observe available
528
+ //
529
+ // go_inward, settle, surface, and observe are always assembled based on channel context.
530
+ // toolChoiceRequired only controls whether tool_choice: "required" is set in the API call.
531
+ const isInnerDialog = channel === "inner";
532
+ const filteredBaseTools = isInnerDialog
533
+ ? baseTools.filter((t) => t.function.name !== "send_message")
522
534
  : baseTools;
535
+ const activeTools = [
536
+ ...filteredBaseTools,
537
+ ...(!isInnerDialog ? [tools_1.goInwardTool] : []),
538
+ ...(isInnerDialog ? [surface_tool_1.surfaceToolDef] : []),
539
+ ...(currentContext?.isGroupChat && !isInnerDialog ? [tools_1.observeTool] : []),
540
+ tools_1.settleTool,
541
+ ];
523
542
  const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
524
543
  if (steeringFollowUps.length > 0) {
525
544
  const hasSupersedingFollowUp = steeringFollowUps.some((followUp) => followUp.effect === "clear_and_supersede");
@@ -555,7 +574,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
555
574
  traceId,
556
575
  toolChoiceRequired,
557
576
  reasoningEffort: currentReasoningEffort,
558
- eagerFinalAnswerStreaming: true,
577
+ eagerSettleStreaming: true,
559
578
  });
560
579
  // Track usage from the latest API call
561
580
  if (result.usage)
@@ -586,24 +605,44 @@ async function runAgent(messages, callbacks, channel, signal, options) {
586
605
  }
587
606
  // Phase annotation for Codex provider
588
607
  const hasPhaseAnnotation = providerRuntime.capabilities.has("phase-annotation");
589
- const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
608
+ const isSoleSettle = result.toolCalls.length === 1 && result.toolCalls[0].name === "settle";
590
609
  if (hasPhaseAnnotation) {
591
- msg.phase = isSoleFinalAnswer ? "final_answer" : "commentary";
610
+ msg.phase = isSoleSettle ? "settle" : "commentary";
592
611
  }
593
612
  if (!result.toolCalls.length) {
594
613
  // No tool calls — accept response as-is.
595
- // (Kick detection disabled; tool_choice: required + final_answer
614
+ // (Kick detection disabled; tool_choice: required + settle
596
615
  // is the primary loop control. See src/heart/kicks.ts to re-enable.)
597
616
  messages.push(msg);
598
617
  done = true;
599
618
  }
600
619
  else {
601
- // Check for final_answer sole call: intercept before tool execution
602
- if (isSoleFinalAnswer) {
620
+ // Check for settle sole call: intercept before tool execution
621
+ if (isSoleSettle) {
622
+ // Inner dialog attention queue gate: reject settle if items remain
623
+ const attentionQueue = (augmentedToolContext ?? options?.toolContext)?.delegatedOrigins;
624
+ if (isInnerDialog && attentionQueue && attentionQueue.length > 0) {
625
+ callbacks.onClearText?.();
626
+ messages.push(msg);
627
+ const gateMessage = "you're holding thoughts someone is waiting for — surface them before you settle.";
628
+ messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
629
+ providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
630
+ continue;
631
+ }
603
632
  // Extract answer from the tool call arguments.
604
633
  // Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
605
- const { answer, intent } = parseFinalAnswerPayload(result.toolCalls[0].arguments);
606
- const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
634
+ const { answer, intent } = parseSettlePayload(result.toolCalls[0].arguments);
635
+ // Inner dialog settle: no CompletionMetadata, "(settled)" ack
636
+ if (isInnerDialog) {
637
+ messages.push(msg);
638
+ const settled = "(settled)";
639
+ messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: settled });
640
+ providerRuntime.appendToolOutput(result.toolCalls[0].id, settled);
641
+ outcome = "settled";
642
+ done = true;
643
+ continue;
644
+ }
645
+ const retryError = getSettleRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
607
646
  const deliveredAnswer = answer;
608
647
  const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
609
648
  const validTerminalIntent = intent === "complete" || intent === "blocked";
@@ -615,9 +654,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
615
654
  answer: deliveredAnswer,
616
655
  intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
617
656
  };
618
- if (result.finalAnswerStreamed) {
657
+ if (result.settleStreamed) {
619
658
  // The streaming layer already parsed and emitted the answer
620
- // progressively via FinalAnswerParser. Skip clearing and
659
+ // progressively via SettleParser. Skip clearing and
621
660
  // re-emitting to avoid double-delivery.
622
661
  }
623
662
  else {
@@ -637,26 +676,26 @@ async function runAgent(messages, callbacks, channel, signal, options) {
637
676
  const delivered = "(delivered)";
638
677
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: delivered });
639
678
  providerRuntime.appendToolOutput(result.toolCalls[0].id, delivered);
640
- outcome = intent === "blocked" ? "blocked" : "complete";
679
+ outcome = intent === "blocked" ? "blocked" : "settled";
641
680
  done = true;
642
681
  }
643
682
  }
644
683
  else {
645
- // Answer is undefined -- the model's final_answer was incomplete or
684
+ // Answer is undefined -- the model's settle was incomplete or
646
685
  // malformed. Clear any partial streamed text or noise, then push the
647
686
  // assistant msg + error tool result and let the model try again.
648
687
  callbacks.onClearText?.();
649
688
  messages.push(msg);
650
689
  const toolRetryMessage = retryError
651
- ?? "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
690
+ ?? "your settle was incomplete or malformed. call settle again with your complete response.";
652
691
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
653
692
  providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
654
693
  }
655
694
  continue;
656
695
  }
657
- // Check for no_response sole call: intercept before tool execution
658
- const isSoleNoResponse = result.toolCalls.length === 1 && result.toolCalls[0].name === "no_response";
659
- if (isSoleNoResponse) {
696
+ // Check for observe sole call: intercept before tool execution
697
+ const isSoleObserve = result.toolCalls.length === 1 && result.toolCalls[0].name === "observe";
698
+ if (isSoleObserve) {
660
699
  let reason;
661
700
  try {
662
701
  const parsed = JSON.parse(result.toolCalls[0].arguments);
@@ -666,7 +705,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
666
705
  catch { /* ignore */ }
667
706
  (0, runtime_1.emitNervesEvent)({
668
707
  component: "engine",
669
- event: "engine.no_response",
708
+ event: "engine.observe",
670
709
  message: "agent declined to respond in group chat",
671
710
  meta: { ...(reason ? { reason } : {}) },
672
711
  });
@@ -674,7 +713,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
674
713
  const silenced = "(silenced)";
675
714
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: silenced });
676
715
  providerRuntime.appendToolOutput(result.toolCalls[0].id, silenced);
677
- outcome = "no_response";
716
+ outcome = "observed";
678
717
  done = true;
679
718
  continue;
680
719
  }
@@ -686,8 +725,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
686
725
  parsedArgs = JSON.parse(result.toolCalls[0].arguments);
687
726
  }
688
727
  catch { /* ignore */ }
689
- /* v8 ignore next -- defensive: content always string from model @preserve */
690
- const content = typeof parsedArgs.content === "string" ? parsedArgs.content : "";
728
+ /* v8 ignore next -- defensive: topic always string from model @preserve */
729
+ const topic = typeof parsedArgs.topic === "string" ? parsedArgs.topic : "";
691
730
  const answer = typeof parsedArgs.answer === "string" ? parsedArgs.answer : undefined;
692
731
  const parsedMode = parsedArgs.mode === "reflect" || parsedArgs.mode === "plan" || parsedArgs.mode === "relay"
693
732
  ? parsedArgs.mode
@@ -700,7 +739,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
700
739
  }
701
740
  // Build handoff packet and enqueue
702
741
  const handoffContent = buildGoInwardHandoffPacket({
703
- content,
742
+ topic,
704
743
  mode,
705
744
  delegationDecision: options?.delegationDecision,
706
745
  currentSession: options?.toolContext?.currentSession ?? null,
@@ -736,7 +775,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
736
775
  channel: currentSession.channel,
737
776
  key: currentSession.key,
738
777
  },
739
- content,
778
+ content: topic,
740
779
  });
741
780
  }
742
781
  catch {
@@ -756,36 +795,22 @@ async function runAgent(messages, callbacks, channel, signal, options) {
756
795
  component: "engine",
757
796
  event: "engine.go_inward",
758
797
  message: "taking thread inward",
759
- meta: { mode, hasAnswer: answer !== undefined, contentSnippet: content.slice(0, 80) },
798
+ meta: { mode, hasAnswer: answer !== undefined, contentSnippet: topic.slice(0, 80) },
760
799
  });
761
800
  outcome = "go_inward";
762
801
  done = true;
763
802
  continue;
764
803
  }
765
804
  messages.push(msg);
766
- // SHARED: execute tools (final_answer, no_response, go_inward in mixed calls are rejected inline)
805
+ // Execute tools (sole-call tools in mixed calls are rejected inline)
767
806
  for (const tc of result.toolCalls) {
768
807
  if (signal?.aborted)
769
808
  break;
770
- // Intercept final_answer in mixed call: reject it
771
- if (tc.name === "final_answer") {
772
- const rejection = "rejected: final_answer must be the only tool call. Finish your work first, then call final_answer alone.";
773
- messages.push({ role: "tool", tool_call_id: tc.id, content: rejection });
774
- providerRuntime.appendToolOutput(tc.id, rejection);
775
- continue;
776
- }
777
- // Intercept no_response in mixed call: reject it
778
- if (tc.name === "no_response") {
779
- const rejection = "rejected: no_response must be the only tool call. call no_response alone when you want to stay silent.";
780
- messages.push({ role: "tool", tool_call_id: tc.id, content: rejection });
781
- providerRuntime.appendToolOutput(tc.id, rejection);
782
- continue;
783
- }
784
- // Intercept go_inward in mixed call: reject it
785
- if (tc.name === "go_inward") {
786
- const rejection = "rejected: go_inward must be the only tool call. finish your other work first, then call go_inward alone.";
787
- messages.push({ role: "tool", tool_call_id: tc.id, content: rejection });
788
- providerRuntime.appendToolOutput(tc.id, rejection);
809
+ // Reject sole-call tools when mixed with other tool calls
810
+ const soleCallRejection = SOLE_CALL_REJECTION[tc.name];
811
+ if (soleCallRejection) {
812
+ messages.push({ role: "tool", tool_call_id: tc.id, content: soleCallRejection });
813
+ providerRuntime.appendToolOutput(tc.id, soleCallRejection);
789
814
  continue;
790
815
  }
791
816
  let args = {};
@@ -760,6 +760,17 @@ function parseSessionCommand(args) {
760
760
  return { kind: "session.list", ...(agent ? { agent } : {}) };
761
761
  throw new Error(`Usage\n${usage()}`);
762
762
  }
763
+ function parseAttentionCommand(args) {
764
+ const { agent, rest: cleaned } = extractAgentFlag(args);
765
+ const sub = cleaned[0];
766
+ if (sub === "show" && cleaned[1]) {
767
+ return { kind: "attention.show", id: cleaned[1], ...(agent ? { agent } : {}) };
768
+ }
769
+ if (sub === "history") {
770
+ return { kind: "attention.history", ...(agent ? { agent } : {}) };
771
+ }
772
+ return { kind: "attention.list", ...(agent ? { agent } : {}) };
773
+ }
763
774
  function parseThoughtsCommand(args) {
764
775
  const { agent, rest: cleaned } = extractAgentFlag(args);
765
776
  let last;
@@ -930,6 +941,8 @@ function parseOuroCommand(args) {
930
941
  }
931
942
  if (head === "thoughts")
932
943
  return parseThoughtsCommand(args.slice(1));
944
+ if (head === "attention")
945
+ return parseAttentionCommand(args.slice(1));
933
946
  if (head === "chat") {
934
947
  if (!second)
935
948
  throw new Error(`Usage\n${usage()}`);
@@ -2284,6 +2297,74 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2284
2297
  return message;
2285
2298
  }
2286
2299
  }
2300
+ // ── attention queue (local, no daemon socket needed) ──
2301
+ /* v8 ignore start -- CLI attention handler: requires real obligation store on disk @preserve */
2302
+ if (command.kind === "attention.list" || command.kind === "attention.show" || command.kind === "attention.history") {
2303
+ try {
2304
+ const agentName = command.agent ?? (0, identity_1.getAgentName)();
2305
+ const { listActiveObligations, readObligation } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
2306
+ if (command.kind === "attention.list") {
2307
+ const obligations = listActiveObligations(agentName);
2308
+ if (obligations.length === 0) {
2309
+ const message = "nothing held — attention queue is empty";
2310
+ deps.writeStdout(message);
2311
+ return message;
2312
+ }
2313
+ const lines = obligations.map((o) => `[${o.id}] ${o.origin.friendId} via ${o.origin.channel}/${o.origin.key} — ${o.delegatedContent.slice(0, 60)}${o.delegatedContent.length > 60 ? "..." : ""} (${o.status})`);
2314
+ const message = lines.join("\n");
2315
+ deps.writeStdout(message);
2316
+ return message;
2317
+ }
2318
+ if (command.kind === "attention.show") {
2319
+ const obligation = readObligation(agentName, command.id);
2320
+ if (!obligation) {
2321
+ const message = `no obligation found with id ${command.id}`;
2322
+ deps.writeStdout(message);
2323
+ return message;
2324
+ }
2325
+ const message = JSON.stringify(obligation, null, 2);
2326
+ deps.writeStdout(message);
2327
+ return message;
2328
+ }
2329
+ // attention.history: show returned obligations
2330
+ const { getObligationsDir } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
2331
+ const obligationsDir = getObligationsDir(agentName);
2332
+ let entries = [];
2333
+ try {
2334
+ entries = fs.readdirSync(obligationsDir);
2335
+ }
2336
+ catch { /* empty */ }
2337
+ const returned = entries
2338
+ .filter((e) => e.endsWith(".json"))
2339
+ .map((e) => { try {
2340
+ return JSON.parse(fs.readFileSync(path.join(obligationsDir, e), "utf-8"));
2341
+ }
2342
+ catch {
2343
+ return null;
2344
+ } })
2345
+ .filter((o) => o?.status === "returned")
2346
+ .sort((a, b) => (b.returnedAt ?? 0) - (a.returnedAt ?? 0))
2347
+ .slice(0, 20);
2348
+ if (returned.length === 0) {
2349
+ const message = "no surfacing history yet";
2350
+ deps.writeStdout(message);
2351
+ return message;
2352
+ }
2353
+ const lines = returned.map((o) => {
2354
+ const when = o.returnedAt ? new Date(o.returnedAt).toISOString() : "unknown";
2355
+ return `[${o.id}] → ${o.origin.friendId} via ${o.returnTarget ?? "unknown"} at ${when}`;
2356
+ });
2357
+ const message = lines.join("\n");
2358
+ deps.writeStdout(message);
2359
+ return message;
2360
+ }
2361
+ catch {
2362
+ const message = "error: no agent context — use --agent <name> to specify";
2363
+ deps.writeStdout(message);
2364
+ return message;
2365
+ }
2366
+ }
2367
+ /* v8 ignore stop */
2287
2368
  // ── session list (local, no daemon socket needed) ──
2288
2369
  if (command.kind === "session.list") {
2289
2370
  /* v8 ignore start -- production default: requires full identity setup @preserve */
@@ -82,7 +82,7 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
82
82
  "2. I write agent.json to the temp directory using write_file",
83
83
  "3. I suggest a PascalCase name for the hatchling and confirm with the human",
84
84
  "4. I call complete_adoption with the name and a warm handoff message",
85
- "5. I call final_answer to end the session",
85
+ "5. I call settle to end the session",
86
86
  ].join("\n"));
87
87
  sections.push([
88
88
  "## Tools",
@@ -91,9 +91,9 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
91
91
  "- `list_directory`: List directory contents. Useful for exploring existing agent bundles.",
92
92
  "- I also have the normal local harness tools when useful here, including `shell`, `ouro task create`, `ouro reminder create`, memory tools, coding tools, and repo helpers.",
93
93
  "- `complete_adoption`: Finalize the bundle. Validates, scaffolds structural dirs, moves to ~/AgentBundles/, writes secrets, plays hatch animation. I call this with `name` (PascalCase) and `handoff_message` (warm message for the human).",
94
- "- `final_answer`: End the conversation with a final message. I call this after complete_adoption succeeds.",
94
+ "- `settle`: End the conversation with a final message. I call this after complete_adoption succeeds.",
95
95
  "",
96
- "I must call `final_answer` when I am done to end the session cleanly.",
96
+ "I must call `settle` when I am done to end the session cleanly.",
97
97
  ].join("\n"));
98
98
  return sections.join("\n\n");
99
99
  }
@@ -90,7 +90,7 @@ const listDirToolSchema = {
90
90
  * Returns the specialist's tool schema array.
91
91
  */
92
92
  function getSpecialistTools() {
93
- return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool, listDirToolSchema];
93
+ return [completeAdoptionTool, tools_base_1.settleTool, readFileTool.tool, writeFileTool.tool, listDirToolSchema];
94
94
  }
95
95
  const PSYCHE_FILES = ["SOUL.md", "IDENTITY.md", "LORE.md", "TACIT.md", "ASPIRATIONS.md"];
96
96
  function isPascalCase(name) {
@@ -93,7 +93,7 @@ function extractToolNames(messages) {
93
93
  if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
94
94
  for (const tc of msg.tool_calls) {
95
95
  const toolFunction = extractToolFunction(tc);
96
- if (toolFunction?.name && toolFunction.name !== "final_answer")
96
+ if (toolFunction?.name && toolFunction.name !== "settle")
97
97
  names.push(toolFunction.name);
98
98
  }
99
99
  }
@@ -321,15 +321,15 @@ function formatInnerDialogStatus(status) {
321
321
  }
322
322
  return lines.join("\n");
323
323
  }
324
- /** Extract text from a final_answer tool call's arguments. */
325
- function extractFinalAnswer(messages) {
324
+ /** Extract text from a settle tool call's arguments. */
325
+ function extractSettleAnswer(messages) {
326
326
  for (let k = messages.length - 1; k >= 0; k--) {
327
327
  const msg = messages[k];
328
328
  if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
329
329
  continue;
330
330
  for (const tc of msg.tool_calls) {
331
331
  const toolFunction = extractToolFunction(tc);
332
- if (toolFunction?.name !== "final_answer")
332
+ if (toolFunction?.name !== "settle")
333
333
  continue;
334
334
  try {
335
335
  const parsed = JSON.parse(toolFunction.arguments ?? "{}");
@@ -348,7 +348,7 @@ function extractThoughtResponseFromMessages(messages) {
348
348
  const lastAssistant = assistantMsgs.reverse().find((message) => contentToText(message.content).trim().length > 0);
349
349
  return lastAssistant
350
350
  ? contentToText(lastAssistant.content).trim()
351
- : extractFinalAnswer(messages);
351
+ : extractSettleAnswer(messages);
352
352
  }
353
353
  function parseInnerDialogSession(sessionPath) {
354
354
  (0, runtime_1.emitNervesEvent)({
@@ -397,7 +397,7 @@ function parseInnerDialogSession(sessionPath) {
397
397
  j++;
398
398
  }
399
399
  // Find the last assistant text response in this turn.
400
- // With tool_choice="required", the response may be inside a final_answer tool call.
400
+ // With tool_choice="required", the response may be inside a settle tool call.
401
401
  const response = extractThoughtResponseFromMessages(turnMessages);
402
402
  const tools = extractToolNames(turnMessages);
403
403
  turns.push({
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.decideDelegation = decideDelegation;
4
4
  const runtime_1 = require("../nerves/runtime");
5
5
  const CROSS_SESSION_TOOLS = new Set(["query_session", "send_message", "bridge_manage"]);
6
- const FAST_PATH_TOOLS = new Set(["final_answer"]);
6
+ const FAST_PATH_TOOLS = new Set(["settle"]);
7
7
  const REFLECTION_PATTERN = /\b(think|reflect|ponder|surface|surfaces|surfaced|sit with|metaboli[sz]e)\b/i;
8
8
  const CROSS_SESSION_PATTERN = /\b(other chat|other session|across chats?|across sessions?|keep .* aligned|relay|carry .* across)\b/i;
9
9
  function hasExplicitReflection(ingressTexts) {
@@ -5,7 +5,7 @@ exports.detectKick = detectKick;
5
5
  const runtime_1 = require("../nerves/runtime");
6
6
  const KICK_MESSAGES = {
7
7
  empty: "I sent an empty message by accident — let me try again.",
8
- narration: "I narrated instead of acting. Using the tool now -- if done, calling final_answer.",
8
+ narration: "I narrated instead of acting. Using the tool now -- if done, calling settle.",
9
9
  tool_required: "tool-required is on — I need to call a tool. use /tool-required to turn it off.",
10
10
  };
11
11
  const TOOL_INTENT_PATTERNS = [
@@ -301,7 +301,7 @@ async function streamAnthropicMessages(client, model, request) {
301
301
  const toolCalls = new Map();
302
302
  const thinkingBlocks = new Map();
303
303
  const redactedBlocks = new Map();
304
- const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks, request.eagerFinalAnswerStreaming);
304
+ const answerStreamer = new streaming_1.SettleStreamer(request.callbacks, request.eagerSettleStreaming);
305
305
  try {
306
306
  for await (const event of response) {
307
307
  if (request.signal?.aborted)
@@ -327,9 +327,9 @@ async function streamAnthropicMessages(client, model, request) {
327
327
  name,
328
328
  arguments: input,
329
329
  });
330
- // Activate eager streaming for sole final_answer tool call
331
- /* v8 ignore next -- final_answer streaming activation, tested via FinalAnswerStreamer unit tests @preserve */
332
- if (name === "final_answer" && toolCalls.size === 1) {
330
+ // Activate eager streaming for sole settle tool call
331
+ /* v8 ignore next -- settle streaming activation, tested via SettleStreamer unit tests @preserve */
332
+ if (name === "settle" && toolCalls.size === 1) {
333
333
  answerStreamer.activate();
334
334
  }
335
335
  }
@@ -374,8 +374,8 @@ async function streamAnthropicMessages(client, model, request) {
374
374
  if (existing) {
375
375
  const partialJson = String(delta?.partial_json ?? "");
376
376
  existing.arguments = mergeAnthropicToolArguments(existing.arguments, partialJson);
377
- /* v8 ignore next -- final_answer delta streaming, tested via FinalAnswerStreamer unit tests @preserve */
378
- if (existing.name === "final_answer" && toolCalls.size === 1) {
377
+ /* v8 ignore next -- settle delta streaming, tested via SettleStreamer unit tests @preserve */
378
+ if (existing.name === "settle" && toolCalls.size === 1) {
379
379
  answerStreamer.processDelta(partialJson);
380
380
  }
381
381
  }
@@ -414,7 +414,7 @@ async function streamAnthropicMessages(client, model, request) {
414
414
  toolCalls: [...toolCalls.values()],
415
415
  outputItems,
416
416
  usage,
417
- finalAnswerStreamed: answerStreamer.streamed,
417
+ settleStreamed: answerStreamer.streamed,
418
418
  };
419
419
  }
420
420
  function createAnthropicProviderRuntime(config) {
@@ -159,7 +159,7 @@ function createAzureProviderRuntime(config) {
159
159
  params.metadata = { trace_id: request.traceId };
160
160
  if (request.toolChoiceRequired)
161
161
  params.tool_choice = "required";
162
- const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
162
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
163
163
  for (const item of result.outputItems)
164
164
  nativeInput.push(item);
165
165
  return result;