@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.
@@ -88,7 +88,7 @@ function createGithubCopilotProviderRuntime(injectedConfig) {
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, request.eagerFinalAnswerStreaming);
91
+ return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
92
92
  }
93
93
  catch (error) {
94
94
  throw error instanceof Error ? error : new Error(String(error));
@@ -139,7 +139,7 @@ function createGithubCopilotProviderRuntime(injectedConfig) {
139
139
  if (request.toolChoiceRequired)
140
140
  params.tool_choice = "required";
141
141
  try {
142
- const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
142
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
143
143
  for (const item of result.outputItems)
144
144
  nativeInput.push(item);
145
145
  return result;
@@ -74,7 +74,7 @@ function createMinimaxProviderRuntime(config) {
74
74
  params.metadata = { trace_id: request.traceId };
75
75
  if (request.toolChoiceRequired)
76
76
  params.tool_choice = "required";
77
- return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
77
+ return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
78
78
  },
79
79
  classifyError(error) {
80
80
  return classifyMinimaxError(error);
@@ -180,7 +180,7 @@ function createOpenAICodexProviderRuntime(config) {
180
180
  if (request.toolChoiceRequired)
181
181
  params.tool_choice = "required";
182
182
  try {
183
- const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
183
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
184
184
  for (const item of result.outputItems)
185
185
  nativeInput.push(item);
186
186
  return result;
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FinalAnswerStreamer = exports.FinalAnswerParser = void 0;
3
+ exports.SettleStreamer = exports.SettleParser = void 0;
4
4
  exports.toResponsesInput = toResponsesInput;
5
5
  exports.toResponsesTools = toResponsesTools;
6
6
  exports.streamChatCompletion = streamChatCompletion;
7
7
  exports.streamResponsesApi = streamResponsesApi;
8
8
  const runtime_1 = require("../nerves/runtime");
9
9
  // Character-level state machine that extracts the answer value from
10
- // `final_answer` tool call JSON arguments as they stream in.
10
+ // `settle` tool call JSON arguments as they stream in.
11
11
  // Scans for prefix `"answer":"` or `"answer": "` in the character stream,
12
12
  // then emits text handling JSON escapes, stopping at unescaped closing `"`.
13
- class FinalAnswerParser {
13
+ class SettleParser {
14
14
  // Possible prefixes to match (with and without space after colon)
15
15
  static PREFIXES = ['"answer":"', '"answer": "'];
16
16
  // Buffer of characters seen so far (pre-activation only)
@@ -29,7 +29,7 @@ class FinalAnswerParser {
29
29
  if (!this._active) {
30
30
  this.buf += ch;
31
31
  // Check if any prefix has been fully matched in the buffer
32
- for (const prefix of FinalAnswerParser.PREFIXES) {
32
+ for (const prefix of SettleParser.PREFIXES) {
33
33
  if (this.buf.endsWith(prefix)) {
34
34
  this._active = true;
35
35
  break;
@@ -76,12 +76,12 @@ class FinalAnswerParser {
76
76
  return out;
77
77
  }
78
78
  }
79
- exports.FinalAnswerParser = FinalAnswerParser;
80
- // Shared helper: wraps FinalAnswerParser with onClearText + onTextChunk wiring.
79
+ exports.SettleParser = SettleParser;
80
+ // Shared helper: wraps SettleParser with onClearText + onTextChunk wiring.
81
81
  // Used by all streaming providers (Chat Completions, Responses API, Anthropic)
82
- // so the eager-match streaming pattern lives in one place.
83
- class FinalAnswerStreamer {
84
- parser = new FinalAnswerParser();
82
+ // so the eager-match settle streaming pattern lives in one place.
83
+ class SettleStreamer {
84
+ parser = new SettleParser();
85
85
  _detected = false;
86
86
  callbacks;
87
87
  enabled;
@@ -91,7 +91,7 @@ class FinalAnswerStreamer {
91
91
  }
92
92
  get detected() { return this._detected; }
93
93
  get streamed() { return this.parser.active; }
94
- /** Mark final_answer as detected. Calls onClearText on the callbacks. */
94
+ /** Mark settle as detected. Calls onClearText on the callbacks. */
95
95
  activate() {
96
96
  if (!this.enabled)
97
97
  return;
@@ -111,7 +111,7 @@ class FinalAnswerStreamer {
111
111
  this.callbacks.onTextChunk(text);
112
112
  }
113
113
  }
114
- exports.FinalAnswerStreamer = FinalAnswerStreamer;
114
+ exports.SettleStreamer = SettleStreamer;
115
115
  function toResponsesUserContent(content) {
116
116
  if (typeof content === "string") {
117
117
  return content;
@@ -233,7 +233,7 @@ function toResponsesTools(ccTools) {
233
233
  strict: false,
234
234
  }));
235
235
  }
236
- async function streamChatCompletion(client, createParams, callbacks, signal, eagerFinalAnswerStreaming = true) {
236
+ async function streamChatCompletion(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
237
237
  (0, runtime_1.emitNervesEvent)({
238
238
  component: "engine",
239
239
  event: "engine.stream_start",
@@ -247,7 +247,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
247
247
  let toolCalls = {};
248
248
  let streamStarted = false;
249
249
  let usage;
250
- const answerStreamer = new FinalAnswerStreamer(callbacks, eagerFinalAnswerStreaming);
250
+ const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
251
251
  // State machine for parsing inline <think> tags (MiniMax pattern)
252
252
  let contentBuf = "";
253
253
  let inThinkTag = false;
@@ -362,19 +362,19 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
362
362
  toolCalls[tc.index].id = tc.id;
363
363
  if (tc.function?.name) {
364
364
  toolCalls[tc.index].name = tc.function.name;
365
- // Detect final_answer tool call on first name delta.
365
+ // Detect settle tool call on first name delta.
366
366
  // Only activate streaming if this is the sole tool call (index 0
367
367
  // and no other indices seen). Mixed calls are rejected by core.ts.
368
- if (tc.function.name === "final_answer" && !answerStreamer.detected
368
+ if (tc.function.name === "settle" && !answerStreamer.detected
369
369
  && tc.index === 0 && Object.keys(toolCalls).length === 1) {
370
370
  answerStreamer.activate();
371
371
  }
372
372
  }
373
373
  if (tc.function?.arguments) {
374
374
  toolCalls[tc.index].arguments += tc.function.arguments;
375
- // Feed final_answer argument deltas to the parser for progressive
375
+ // Feed settle argument deltas to the parser for progressive
376
376
  // streaming, but only when it appears to be the sole tool call.
377
- if (answerStreamer.detected && toolCalls[tc.index].name === "final_answer"
377
+ if (answerStreamer.detected && toolCalls[tc.index].name === "settle"
378
378
  && Object.keys(toolCalls).length === 1) {
379
379
  answerStreamer.processDelta(tc.function.arguments);
380
380
  }
@@ -390,10 +390,10 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
390
390
  toolCalls: Object.values(toolCalls),
391
391
  outputItems: [],
392
392
  usage,
393
- finalAnswerStreamed: answerStreamer.streamed,
393
+ settleStreamed: answerStreamer.streamed,
394
394
  };
395
395
  }
396
- async function streamResponsesApi(client, createParams, callbacks, signal, eagerFinalAnswerStreaming = true) {
396
+ async function streamResponsesApi(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
397
397
  (0, runtime_1.emitNervesEvent)({
398
398
  component: "engine",
399
399
  event: "engine.stream_start",
@@ -408,7 +408,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
408
408
  const outputItems = [];
409
409
  let currentToolCall = null;
410
410
  let usage;
411
- const answerStreamer = new FinalAnswerStreamer(callbacks, eagerFinalAnswerStreaming);
411
+ const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
412
412
  let functionCallCount = 0;
413
413
  for await (const event of response) {
414
414
  if (signal?.aborted)
@@ -438,10 +438,10 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
438
438
  name: String(event.item.name),
439
439
  arguments: "",
440
440
  };
441
- // Detect final_answer function call -- clear any streamed noise.
441
+ // Detect settle function call -- clear any streamed noise.
442
442
  // Only activate when this is the first (and so far only) function call.
443
443
  // Mixed calls are rejected by core.ts; no need to stream their args.
444
- if (String(event.item.name) === "final_answer" && functionCallCount === 1) {
444
+ if (String(event.item.name) === "settle" && functionCallCount === 1) {
445
445
  answerStreamer.activate();
446
446
  }
447
447
  }
@@ -450,9 +450,9 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
450
450
  case "response.function_call_arguments.delta": {
451
451
  if (currentToolCall) {
452
452
  currentToolCall.arguments += event.delta;
453
- // Feed final_answer argument deltas to the parser for progressive
453
+ // Feed settle argument deltas to the parser for progressive
454
454
  // streaming, but only when it appears to be the sole function call.
455
- if (answerStreamer.detected && currentToolCall.name === "final_answer"
455
+ if (answerStreamer.detected && currentToolCall.name === "settle"
456
456
  && functionCallCount === 1) {
457
457
  answerStreamer.processDelta(String(event.delta));
458
458
  }
@@ -494,6 +494,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
494
494
  toolCalls,
495
495
  outputItems,
496
496
  usage,
497
- finalAnswerStreamed: answerStreamer.streamed,
497
+ settleStreamed: answerStreamer.streamed,
498
498
  };
499
499
  }
@@ -271,11 +271,11 @@ function runtimeInfoSection(channel) {
271
271
  lines.push("i introduce myself on boot with a fun random greeting.");
272
272
  }
273
273
  else if (channel === "inner") {
274
- // No boot greeting or channel-specific guidance for inner dialog
274
+ lines.push("this is my private thinking space. when a thought is ready to share, i surface it to whoever needs to hear it. i settle when i'm done thinking.");
275
275
  }
276
276
  else if (channel === "bluebubbles") {
277
277
  lines.push("i am responding in iMessage through BlueBubbles. i keep replies short and phone-native. i do not use markdown. i do not introduce myself on boot.");
278
- lines.push("when a bluebubbles turn arrives from a thread, the harness tells me the current lane and any recent active thread ids. if widening back to top-level or routing into a different active thread is the better move, i use bluebubbles_set_reply_target before final_answer.");
278
+ lines.push("when a bluebubbles turn arrives from a thread, the harness tells me the current lane and any recent active thread ids. if widening back to top-level or routing into a different active thread is the better move, i use bluebubbles_set_reply_target before settle.");
279
279
  }
280
280
  else {
281
281
  lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
@@ -356,7 +356,7 @@ function dateSection() {
356
356
  }
357
357
  function toolsSection(channel, options, context) {
358
358
  const channelTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel), undefined, context, options?.providerCapabilities);
359
- const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.finalAnswerTool] : channelTools;
359
+ const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.settleTool] : channelTools;
360
360
  const list = activeTools
361
361
  .map((t) => `- ${t.function.name}: ${t.function.description}`)
362
362
  .join("\n");
@@ -619,10 +619,10 @@ function toolBehaviorSection(options) {
619
619
  return `## tool behavior
620
620
  tool_choice is set to "required" -- i must call a tool on every turn.
621
621
  - need more information? i call a tool.
622
- - ready to respond to the user? i call \`final_answer\`.
623
- \`final_answer\` is a tool call -- it satisfies the tool_choice requirement.
624
- \`final_answer\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
625
- do NOT call no-op tools just before \`final_answer\`. if i am done, i call \`final_answer\` directly.`;
622
+ - ready to respond to the user? i call \`settle\`.
623
+ \`settle\` is a tool call -- it satisfies the tool_choice requirement.
624
+ \`settle\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
625
+ do NOT call no-op tools just before \`settle\`. if i am done, i call \`settle\` directly.`;
626
626
  }
627
627
  function workspaceDisciplineSection() {
628
628
  return `## repo workspace discipline
@@ -668,7 +668,7 @@ function contextSection(context, options) {
668
668
  lines.push("my conversation memory is ephemeral -- it resets between sessions. anything i learn about my friend, i save with save_friend_note so future me remembers.");
669
669
  lines.push("the conversation is my source of truth. my notes are a journal for future me -- they may be stale or incomplete.");
670
670
  lines.push("when i learn something that might invalidate an existing note, i check related notes and update or override any that are stale.");
671
- lines.push("i save ANYTHING i learn about my friend immediately with save_friend_note -- names, preferences, what they do, what they care about. when in doubt, save it. saving comes BEFORE responding: i call save_friend_note first, then final_answer on the next turn.");
671
+ lines.push("i save ANYTHING i learn about my friend immediately with save_friend_note -- names, preferences, what they do, what they care about. when in doubt, save it. saving comes BEFORE responding: i call save_friend_note first, then settle on the next turn.");
672
672
  // Onboarding instructions (only below token threshold -- drop once exceeded)
673
673
  const impressions = (0, first_impressions_1.getFirstImpressions)(friend, options);
674
674
  if (impressions) {
@@ -727,13 +727,13 @@ function groupChatParticipationSection(context) {
727
727
  group chats are conversations between people. i'm one participant, not the host.
728
728
 
729
729
  i don't need to respond to everything. most reactions, tapbacks, and side
730
- conversations between others aren't for me. i use no_response to stay quiet
730
+ conversations between others aren't for me. i use observe to stay quiet
731
731
  when the moment doesn't call for my voice — same as any person would.
732
732
 
733
733
  when a reaction or emoji says it better than words, i can react instead of
734
734
  typing a full reply. a thumbs-up is often the perfect response.
735
735
 
736
- no_response must be the sole tool call in the turn (same rule as final_answer).
736
+ observe must be the sole tool call in the turn (same rule as settle).
737
737
  when unsure whether to chime in, i lean toward silence rather than noise.`;
738
738
  }
739
739
  function mixedTrustGroupSection(context) {
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.finalAnswerTool = exports.noResponseTool = exports.goInwardTool = exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = void 0;
36
+ exports.settleTool = exports.observeTool = exports.goInwardTool = exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = void 0;
37
37
  exports.renderInnerProgressStatus = renderInnerProgressStatus;
38
38
  const fs = __importStar(require("fs"));
39
39
  const fg = __importStar(require("fast-glob"));
@@ -1354,9 +1354,9 @@ exports.goInwardTool = {
1354
1354
  parameters: {
1355
1355
  type: "object",
1356
1356
  properties: {
1357
- content: {
1357
+ topic: {
1358
1358
  type: "string",
1359
- description: "what i need to think about -- the question, the thread, the thing that needs private attention",
1359
+ description: "the question or topic that needs private thought brief framing, not your analysis. your inner dialog will do the actual thinking.",
1360
1360
  },
1361
1361
  answer: {
1362
1362
  type: "string",
@@ -1368,14 +1368,14 @@ exports.goInwardTool = {
1368
1368
  description: "reflect: something to sit with. plan: something to work through. relay: something to carry across.",
1369
1369
  },
1370
1370
  },
1371
- required: ["content"],
1371
+ required: ["topic"],
1372
1372
  },
1373
1373
  },
1374
1374
  };
1375
- exports.noResponseTool = {
1375
+ exports.observeTool = {
1376
1376
  type: "function",
1377
1377
  function: {
1378
- name: "no_response",
1378
+ name: "observe",
1379
1379
  description: "stay silent in this group chat — the moment doesn't call for a response. must be the only tool call in the turn.",
1380
1380
  parameters: {
1381
1381
  type: "object",
@@ -1385,10 +1385,10 @@ exports.noResponseTool = {
1385
1385
  },
1386
1386
  },
1387
1387
  };
1388
- exports.finalAnswerTool = {
1388
+ exports.settleTool = {
1389
1389
  type: "function",
1390
1390
  function: {
1391
- name: "final_answer",
1391
+ name: "settle",
1392
1392
  description: "respond to the user with your message. call this tool when you are ready to deliver your response.",
1393
1393
  parameters: {
1394
1394
  type: "object",
@@ -1,6 +1,39 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.goInwardTool = exports.noResponseTool = exports.finalAnswerTool = exports.tools = void 0;
36
+ exports.goInwardTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
4
37
  exports.getToolsForChannel = getToolsForChannel;
5
38
  exports.isConfirmationRequired = isConfirmationRequired;
6
39
  exports.execTool = execTool;
@@ -14,6 +47,10 @@ const runtime_1 = require("../nerves/runtime");
14
47
  const guardrails_1 = require("./guardrails");
15
48
  const identity_1 = require("../heart/identity");
16
49
  const safe_workspace_1 = require("../heart/safe-workspace");
50
+ const surface_tool_1 = require("../senses/surface-tool");
51
+ const obligations_1 = require("../mind/obligations");
52
+ const session_activity_1 = require("../heart/session-activity");
53
+ const path = __importStar(require("path"));
17
54
  function safeGetAgentRoot() {
18
55
  try {
19
56
  return (0, identity_1.getAgentRoot)();
@@ -25,11 +62,149 @@ function safeGetAgentRoot() {
25
62
  // Re-export types and constants used by the rest of the codebase
26
63
  var tools_base_2 = require("./tools-base");
27
64
  Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_base_2.tools; } });
28
- Object.defineProperty(exports, "finalAnswerTool", { enumerable: true, get: function () { return tools_base_2.finalAnswerTool; } });
29
- Object.defineProperty(exports, "noResponseTool", { enumerable: true, get: function () { return tools_base_2.noResponseTool; } });
65
+ Object.defineProperty(exports, "settleTool", { enumerable: true, get: function () { return tools_base_2.settleTool; } });
66
+ Object.defineProperty(exports, "observeTool", { enumerable: true, get: function () { return tools_base_2.observeTool; } });
30
67
  Object.defineProperty(exports, "goInwardTool", { enumerable: true, get: function () { return tools_base_2.goInwardTool; } });
68
+ // Surface tool handler: routes content to friend's freshest session
69
+ /* v8 ignore start -- surface handler wiring: core logic tested via surface-tool.test.ts; this wires identity/routing deps @preserve */
70
+ const surfaceToolDefinition = {
71
+ tool: surface_tool_1.surfaceToolDef,
72
+ handler: async (args, ctx) => {
73
+ const queue = ctx?.delegatedOrigins ?? [];
74
+ const agentName = (() => { try {
75
+ return (0, identity_1.getAgentName)();
76
+ }
77
+ catch {
78
+ return "unknown";
79
+ } })();
80
+ const routeToFriend = async (friendId, content, queueItem) => {
81
+ /* v8 ignore start -- routing: integration path tested via inner-dialog routing tests @preserve */
82
+ try {
83
+ const agentRoot = (0, identity_1.getAgentRoot)();
84
+ const sessionsDir = path.join(agentRoot, "state", "sessions");
85
+ const friendsDir = path.join(agentRoot, "friends");
86
+ // Priority 1: Bridge-preferred session (if queue item has a bridgeId)
87
+ if (queueItem?.bridgeId) {
88
+ const { createBridgeManager } = await Promise.resolve().then(() => __importStar(require("../heart/bridges/manager")));
89
+ const bridge = createBridgeManager().getBridge(queueItem.bridgeId);
90
+ if (bridge && bridge.lifecycle !== "completed" && bridge.lifecycle !== "cancelled") {
91
+ const allSessions = (0, session_activity_1.listSessionActivity)({ sessionsDir, friendsDir, agentName });
92
+ const bridgeTarget = allSessions.find((activity) => activity.friendId === friendId
93
+ && activity.channel !== "inner"
94
+ && bridge.attachedSessions.some((s) => s.friendId === activity.friendId && s.channel === activity.channel && s.key === activity.key));
95
+ if (bridgeTarget) {
96
+ // Attempt proactive BB delivery for bridge target
97
+ if (bridgeTarget.channel === "bluebubbles") {
98
+ const { sendProactiveBlueBubblesMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/bluebubbles")));
99
+ const proactiveResult = await sendProactiveBlueBubblesMessageToSession({
100
+ friendId: bridgeTarget.friendId,
101
+ sessionKey: bridgeTarget.key,
102
+ text: content,
103
+ });
104
+ if (proactiveResult.delivered) {
105
+ return { status: "delivered", detail: "via iMessage" };
106
+ }
107
+ }
108
+ // Fall back to pending queue for bridge target
109
+ const { queuePendingMessage, getPendingDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
110
+ const pendingDir = getPendingDir(agentName, bridgeTarget.friendId, bridgeTarget.channel, bridgeTarget.key);
111
+ queuePendingMessage(pendingDir, {
112
+ from: agentName,
113
+ friendId: bridgeTarget.friendId,
114
+ channel: bridgeTarget.channel,
115
+ key: bridgeTarget.key,
116
+ content,
117
+ timestamp: Date.now(),
118
+ });
119
+ return { status: "queued", detail: `for next interaction via ${bridgeTarget.channel}` };
120
+ }
121
+ }
122
+ }
123
+ // Priority 2: Freshest active friend session
124
+ const freshest = (0, session_activity_1.findFreshestFriendSession)({
125
+ sessionsDir,
126
+ friendsDir,
127
+ agentName,
128
+ friendId,
129
+ activeOnly: true,
130
+ });
131
+ if (freshest && freshest.channel !== "inner") {
132
+ // Attempt proactive BB delivery
133
+ if (freshest.channel === "bluebubbles") {
134
+ const { sendProactiveBlueBubblesMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/bluebubbles")));
135
+ const proactiveResult = await sendProactiveBlueBubblesMessageToSession({
136
+ friendId: freshest.friendId,
137
+ sessionKey: freshest.key,
138
+ text: content,
139
+ });
140
+ if (proactiveResult.delivered) {
141
+ return { status: "delivered", detail: "via iMessage" };
142
+ }
143
+ }
144
+ // Queue as pending for next interaction
145
+ const { queuePendingMessage, getPendingDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
146
+ const pendingDir = getPendingDir(agentName, freshest.friendId, freshest.channel, freshest.key);
147
+ queuePendingMessage(pendingDir, {
148
+ from: agentName,
149
+ friendId: freshest.friendId,
150
+ channel: freshest.channel,
151
+ key: freshest.key,
152
+ content,
153
+ timestamp: Date.now(),
154
+ });
155
+ return { status: "queued", detail: `for next interaction via ${freshest.channel}` };
156
+ }
157
+ // Priority 3: Deferred — no active session found
158
+ const { getDeferredReturnDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
159
+ const { queuePendingMessage: queueDeferred } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
160
+ const deferredDir = getDeferredReturnDir(agentName, friendId);
161
+ queueDeferred(deferredDir, {
162
+ from: agentName,
163
+ friendId,
164
+ channel: "deferred",
165
+ key: "return",
166
+ content,
167
+ timestamp: Date.now(),
168
+ });
169
+ return { status: "deferred", detail: "they'll see it next time" };
170
+ }
171
+ catch {
172
+ return { status: "failed" };
173
+ }
174
+ /* v8 ignore stop */
175
+ };
176
+ return (0, surface_tool_1.handleSurface)({
177
+ content: args.content ?? "",
178
+ delegationId: args.delegationId,
179
+ friendId: args.friendId,
180
+ queue,
181
+ routeToFriend,
182
+ advanceObligation: (obligationId, update) => {
183
+ /* v8 ignore start -- obligation advance: tested via attention-queue tests @preserve */
184
+ try {
185
+ const name = (() => { try {
186
+ return (0, identity_1.getAgentName)();
187
+ }
188
+ catch {
189
+ return "unknown";
190
+ } })();
191
+ (0, obligations_1.advanceObligation)(name, obligationId, {
192
+ status: update.status,
193
+ ...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
194
+ ...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
195
+ });
196
+ }
197
+ catch {
198
+ // swallowed — obligation advance must never break surface delivery
199
+ }
200
+ /* v8 ignore stop */
201
+ },
202
+ });
203
+ },
204
+ };
205
+ /* v8 ignore stop */
31
206
  // All tool definitions in a single registry
32
- const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
207
+ const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, surfaceToolDefinition];
33
208
  function baseToolsForCapabilities() {
34
209
  // Use baseToolDefinitions at call time so dynamically-added tools are included
35
210
  return tools_base_1.baseToolDefinitions.map((d) => d.tool);
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAttentionQueue = buildAttentionQueue;
4
+ exports.dequeueAttentionItem = dequeueAttentionItem;
5
+ exports.attentionQueueEmpty = attentionQueueEmpty;
6
+ exports.buildAttentionQueueSummary = buildAttentionQueueSummary;
7
+ const runtime_1 = require("../nerves/runtime");
8
+ // ── Queue construction ───────────────────────────────────────────
9
+ function generateItemId() {
10
+ return Math.random().toString(36).slice(2, 10);
11
+ }
12
+ function originKey(friendId, channel, key) {
13
+ return `${friendId}/${channel}/${key}`;
14
+ }
15
+ function buildAttentionQueue(input) {
16
+ const { drainedPending, outstandingObligations, friendNameResolver } = input;
17
+ const seen = new Set();
18
+ const items = [];
19
+ // Source 1: drained pending messages with delegatedFrom (current-turn delegations)
20
+ for (const msg of drainedPending) {
21
+ if (!msg.delegatedFrom)
22
+ continue;
23
+ const { friendId, channel, key, bridgeId } = msg.delegatedFrom;
24
+ const oKey = originKey(friendId, channel, key);
25
+ seen.add(oKey);
26
+ const resolvedName = friendNameResolver(friendId);
27
+ items.push({
28
+ id: msg.obligationId ?? generateItemId(),
29
+ friendId,
30
+ friendName: resolvedName ?? friendId,
31
+ channel,
32
+ key,
33
+ ...(bridgeId ? { bridgeId } : {}),
34
+ delegatedContent: msg.content,
35
+ ...(msg.obligationId ? { obligationId: msg.obligationId } : {}),
36
+ source: "drained",
37
+ timestamp: msg.timestamp,
38
+ });
39
+ }
40
+ // Source 2: outstanding obligations (crash recovery)
41
+ for (const obligation of outstandingObligations) {
42
+ const { friendId, channel, key, bridgeId } = obligation.origin;
43
+ const oKey = originKey(friendId, channel, key);
44
+ if (seen.has(oKey))
45
+ continue; // deduplicate: prefer drained version
46
+ seen.add(oKey);
47
+ const resolvedName = friendNameResolver(friendId);
48
+ items.push({
49
+ id: obligation.id,
50
+ friendId,
51
+ friendName: resolvedName ?? friendId,
52
+ channel,
53
+ key,
54
+ ...(bridgeId ? { bridgeId } : {}),
55
+ delegatedContent: obligation.delegatedContent,
56
+ obligationId: obligation.id,
57
+ source: "obligation-recovery",
58
+ timestamp: obligation.createdAt,
59
+ });
60
+ }
61
+ // Sort FIFO (oldest first)
62
+ items.sort((a, b) => a.timestamp - b.timestamp);
63
+ (0, runtime_1.emitNervesEvent)({
64
+ event: "senses.attention_queue_built",
65
+ component: "senses",
66
+ message: `attention queue built with ${items.length} item(s)`,
67
+ meta: {
68
+ drainedCount: items.filter((i) => i.source === "drained").length,
69
+ recoveredCount: items.filter((i) => i.source === "obligation-recovery").length,
70
+ },
71
+ });
72
+ return items;
73
+ }
74
+ // ── Queue operations ─────────────────────────────────────────────
75
+ function dequeueAttentionItem(queue, id) {
76
+ const index = queue.findIndex((item) => item.id === id);
77
+ if (index === -1)
78
+ return null;
79
+ return queue.splice(index, 1)[0];
80
+ }
81
+ function attentionQueueEmpty(queue) {
82
+ return queue.length === 0;
83
+ }
84
+ // ── Queue visibility ─────────────────────────────────────────────
85
+ const CONTENT_PREVIEW_MAX = 80;
86
+ function buildAttentionQueueSummary(queue) {
87
+ if (queue.length === 0)
88
+ return "";
89
+ const lines = ["you're holding:"];
90
+ for (const item of queue) {
91
+ const preview = item.delegatedContent.length > CONTENT_PREVIEW_MAX
92
+ ? `${item.delegatedContent.slice(0, CONTENT_PREVIEW_MAX - 3)}...`
93
+ : item.delegatedContent;
94
+ lines.push(`- [${item.id}] ${item.friendName} asked: "${preview}"`);
95
+ }
96
+ return lines.join("\n");
97
+ }
@@ -382,14 +382,14 @@ function createCliCallbacks() {
382
382
  onModelStreamStart: () => {
383
383
  // No-op: content callbacks (onTextChunk, onReasoningChunk) handle
384
384
  // stopping the spinner. onModelStreamStart fires too early and
385
- // doesn't fire at all for final_answer tool streaming.
385
+ // doesn't fire at all for settle tool streaming.
386
386
  },
387
387
  onClearText: () => {
388
388
  streamer.reset();
389
389
  wrapper.reset();
390
390
  },
391
391
  onTextChunk: (text) => {
392
- // Stop spinner if still running — final_answer streaming and Anthropic
392
+ // Stop spinner if still running — settle streaming and Anthropic
393
393
  // tool-only responses bypass onModelStreamStart, so the spinner would
394
394
  // otherwise keep running (and its \r writes overwrite response text).
395
395
  if (currentSpinner) {
@@ -558,7 +558,7 @@ async function runCliSession(options) {
558
558
  exitToolResult = result;
559
559
  exitToolFired = true;
560
560
  // Abort immediately so the model doesn't generate more output
561
- // (e.g. reasoning about calling final_answer after complete_adoption)
561
+ // (e.g. reasoning about calling settle after complete_adoption)
562
562
  currentAbort?.abort();
563
563
  }
564
564
  return result;