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

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.
@@ -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;
@@ -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
  }
@@ -178,8 +178,12 @@ function recordToolOutcome(state, toolName, args, result, success) {
178
178
  state.history.splice(0, state.history.length - exports.TOOL_LOOP_HISTORY_LIMIT);
179
179
  }
180
180
  }
181
+ // Tools that must never be blocked by the circuit breaker.
182
+ // settle = end the turn, surface = deliver results outward.
183
+ // Blocking these traps the agent: it can think all it wants but can never speak.
184
+ const CIRCUIT_BREAKER_EXEMPT = new Set(["settle", "surface"]);
181
185
  function detectToolLoop(state, toolName, args) {
182
- if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT) {
186
+ if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT && !CIRCUIT_BREAKER_EXEMPT.has(toolName)) {
183
187
  return emitDetection("global_circuit_breaker", toolName, state.history.length, `this turn has already made ${state.history.length} tool calls. stop thrashing, use the current evidence, and either change approach or answer truthfully with the best grounded status.`);
184
188
  }
185
189
  const callHash = digest(normalizeArgs(toolName, args));
@@ -36,7 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.trimMessages = trimMessages;
37
37
  exports.validateSessionMessages = validateSessionMessages;
38
38
  exports.repairSessionMessages = repairSessionMessages;
39
+ exports.migrateToolNames = migrateToolNames;
39
40
  exports.saveSession = saveSession;
41
+ exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
40
42
  exports.loadSession = loadSession;
41
43
  exports.postTurn = postTurn;
42
44
  exports.deleteSession = deleteSession;
@@ -255,6 +257,43 @@ function stripOrphanedToolResults(messages) {
255
257
  }
256
258
  return repaired;
257
259
  }
260
+ // Tool renames that have shipped. Old names in session history confuse the
261
+ // model into calling tools that no longer exist. Applied on session load so
262
+ // the transcript uses the current vocabulary.
263
+ const TOOL_NAME_MIGRATIONS = {
264
+ final_answer: "settle",
265
+ no_response: "observe",
266
+ go_inward: "descend",
267
+ };
268
+ function migrateToolNames(messages) {
269
+ let migrated = 0;
270
+ const result = messages.map((msg) => {
271
+ if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0)
272
+ return msg;
273
+ let changed = false;
274
+ const updatedCalls = msg.tool_calls.map((tc) => {
275
+ if (tc.type !== "function")
276
+ return tc;
277
+ const newName = TOOL_NAME_MIGRATIONS[tc.function.name];
278
+ if (!newName)
279
+ return tc;
280
+ changed = true;
281
+ migrated++;
282
+ return { ...tc, function: { ...tc.function, name: newName } };
283
+ });
284
+ return changed ? { ...msg, tool_calls: updatedCalls } : msg;
285
+ });
286
+ if (migrated > 0) {
287
+ (0, runtime_1.emitNervesEvent)({
288
+ level: "info",
289
+ event: "mind.session_tool_name_migration",
290
+ component: "mind",
291
+ message: "migrated deprecated tool names in session history",
292
+ meta: { migrated },
293
+ });
294
+ }
295
+ return result;
296
+ }
258
297
  function saveSession(filePath, messages, lastUsage, state) {
259
298
  const violations = validateSessionMessages(messages);
260
299
  if (violations.length > 0) {
@@ -280,6 +319,28 @@ function saveSession(filePath, messages, lastUsage, state) {
280
319
  }
281
320
  fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
282
321
  }
322
+ function appendSyntheticAssistantMessage(filePath, content) {
323
+ try {
324
+ if (!fs.existsSync(filePath))
325
+ return false;
326
+ const raw = fs.readFileSync(filePath, "utf-8");
327
+ const data = JSON.parse(raw);
328
+ if (data.version !== 1 || !Array.isArray(data.messages))
329
+ return false;
330
+ data.messages.push({ role: "assistant", content });
331
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
332
+ (0, runtime_1.emitNervesEvent)({
333
+ component: "mind",
334
+ event: "mind.session_synthetic_message_appended",
335
+ message: "appended synthetic assistant message to session",
336
+ meta: { path: filePath, contentLength: content.length },
337
+ });
338
+ return true;
339
+ }
340
+ catch {
341
+ return false;
342
+ }
343
+ }
283
344
  function loadSession(filePath) {
284
345
  try {
285
346
  const raw = fs.readFileSync(filePath, "utf-8");
@@ -299,6 +360,7 @@ function loadSession(filePath) {
299
360
  messages = repairSessionMessages(messages);
300
361
  }
301
362
  messages = stripOrphanedToolResults(messages);
363
+ messages = migrateToolNames(messages);
302
364
  const rawState = data?.state && typeof data.state === "object" && data.state !== null
303
365
  ? data.state
304
366
  : undefined;
@@ -56,6 +56,7 @@ const ouro_version_manager_1 = require("../heart/daemon/ouro-version-manager");
56
56
  const tools_1 = require("../repertoire/tools");
57
57
  const skills_1 = require("../repertoire/skills");
58
58
  const identity_1 = require("../heart/identity");
59
+ const runtime_mode_1 = require("../heart/daemon/runtime-mode");
59
60
  const types_1 = require("./friends/types");
60
61
  const trust_explanation_1 = require("./friends/trust-explanation");
61
62
  const channel_1 = require("./friends/channel");
@@ -262,6 +263,9 @@ function runtimeInfoSection(channel) {
262
263
  }
263
264
  }
264
265
  lines.push(`changelog available at: ${(0, bundle_manifest_1.getChangelogPath)()}`);
266
+ const sourceRoot = (0, identity_1.getRepoRoot)();
267
+ lines.push(`source root: ${sourceRoot}`);
268
+ lines.push(`runtime mode: ${(0, runtime_mode_1.detectRuntimeMode)(sourceRoot)}`);
265
269
  lines.push(`cwd: ${process.cwd()}`);
266
270
  lines.push(`channel: ${channel}`);
267
271
  lines.push(`current sense: ${channel}`);
@@ -271,11 +275,11 @@ function runtimeInfoSection(channel) {
271
275
  lines.push("i introduce myself on boot with a fun random greeting.");
272
276
  }
273
277
  else if (channel === "inner") {
274
- // No boot greeting or channel-specific guidance for inner dialog
278
+ 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
279
  }
276
280
  else if (channel === "bluebubbles") {
277
281
  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.");
282
+ 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
283
  }
280
284
  else {
281
285
  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 +360,7 @@ function dateSection() {
356
360
  }
357
361
  function toolsSection(channel, options, context) {
358
362
  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;
363
+ const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.settleTool] : channelTools;
360
364
  const list = activeTools
361
365
  .map((t) => `- ${t.function.name}: ${t.function.description}`)
362
366
  .join("\n");
@@ -557,7 +561,7 @@ i should orient around that live lane first, then decide what still needs to com
557
561
  return `## where my attention is
558
562
  i have unfinished work that needs attention before i move on.
559
563
 
560
- i can take it inward with go_inward to think privately, or address it directly here.`;
564
+ i can take it inward with descend to think privately, or address it directly here.`;
561
565
  }
562
566
  if (cog === "shared-work") {
563
567
  /* v8 ignore stop */
@@ -619,19 +623,20 @@ function toolBehaviorSection(options) {
619
623
  return `## tool behavior
620
624
  tool_choice is set to "required" -- i must call a tool on every turn.
621
625
  - 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.`;
626
+ - ready to respond to the user? i call \`settle\`.
627
+ \`settle\` is a tool call -- it satisfies the tool_choice requirement.
628
+ \`settle\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
629
+ do NOT call no-op tools just before \`settle\`. if i am done, i call \`settle\` directly.`;
626
630
  }
627
631
  function workspaceDisciplineSection() {
628
632
  return `## repo workspace discipline
629
- when a shared-harness or local code fix needs repo work, i get the real workspace first with \`safe_workspace\`.
630
- \`read_file\`, \`write_file\`, and \`edit_file\` already map repo paths into that workspace. shell commands that target the harness run there too.
633
+ my source code lives at the path shown in \`source root\` above. that always matches my running version.
634
+ when i need to read my own code to understand my tools or debug behavior, i read from source root.
635
+ when i need to EDIT harness code (self-fix, feature work), i create a git worktree first so i don't dirty the working tree.
631
636
 
632
637
  before the first repo edit, i tell the user in 1-2 short lines:
633
638
  - the friction i'm fixing
634
- - the workspace path/branch i'm using
639
+ - the worktree path/branch i'm using
635
640
  - the first concrete action i'm taking`;
636
641
  }
637
642
  function contextSection(context, options) {
@@ -668,7 +673,7 @@ function contextSection(context, options) {
668
673
  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
674
  lines.push("the conversation is my source of truth. my notes are a journal for future me -- they may be stale or incomplete.");
670
675
  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.");
676
+ 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
677
  // Onboarding instructions (only below token threshold -- drop once exceeded)
673
678
  const impressions = (0, first_impressions_1.getFirstImpressions)(friend, options);
674
679
  if (impressions) {
@@ -727,13 +732,13 @@ function groupChatParticipationSection(context) {
727
732
  group chats are conversations between people. i'm one participant, not the host.
728
733
 
729
734
  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
735
+ conversations between others aren't for me. i use observe to stay quiet
731
736
  when the moment doesn't call for my voice — same as any person would.
732
737
 
733
738
  when a reaction or emoji says it better than words, i can react instead of
734
739
  typing a full reply. a thumbs-up is often the perfect response.
735
740
 
736
- no_response must be the sole tool call in the turn (same rule as final_answer).
741
+ observe must be the sole tool call in the turn (same rule as settle).
737
742
  when unsure whether to chime in, i lean toward silence rather than noise.`;
738
743
  }
739
744
  function mixedTrustGroupSection(context) {
@@ -279,6 +279,7 @@ exports.adoSemanticToolDefinitions = [
279
279
  return formatForChannel(items, ctx, organization, project);
280
280
  },
281
281
  integration: "ado",
282
+ summaryKeys: ["organization", "project"],
282
283
  },
283
284
  // -- ado_create_epic --
284
285
  {
@@ -318,6 +319,7 @@ exports.adoSemanticToolDefinitions = [
318
319
  },
319
320
  integration: "ado",
320
321
  confirmationRequired: true,
322
+ summaryKeys: ["organization", "project", "title"],
321
323
  },
322
324
  // -- ado_create_issue --
323
325
  {
@@ -359,6 +361,7 @@ exports.adoSemanticToolDefinitions = [
359
361
  },
360
362
  integration: "ado",
361
363
  confirmationRequired: true,
364
+ summaryKeys: ["organization", "project", "title"],
362
365
  },
363
366
  // -- ado_move_items --
364
367
  {
@@ -413,6 +416,7 @@ exports.adoSemanticToolDefinitions = [
413
416
  },
414
417
  integration: "ado",
415
418
  confirmationRequired: true,
419
+ summaryKeys: ["organization", "project", "workItemIds"],
416
420
  },
417
421
  // -- ado_restructure_backlog --
418
422
  {
@@ -471,6 +475,7 @@ exports.adoSemanticToolDefinitions = [
471
475
  },
472
476
  integration: "ado",
473
477
  confirmationRequired: true,
478
+ summaryKeys: ["organization", "project"],
474
479
  },
475
480
  // -- ado_validate_structure --
476
481
  {
@@ -672,6 +677,7 @@ exports.adoSemanticToolDefinitions = [
672
677
  },
673
678
  integration: "ado",
674
679
  confirmationRequired: true,
680
+ summaryKeys: ["organization", "project"],
675
681
  },
676
682
  // -- ado_detect_orphans --
677
683
  {
@@ -279,6 +279,7 @@ exports.codingToolDefinitions = [
279
279
  }
280
280
  return JSON.stringify(session);
281
281
  },
282
+ summaryKeys: ["runner", "workdir", "taskRef"],
282
283
  },
283
284
  {
284
285
  tool: codingStatusTool,
@@ -294,6 +295,7 @@ exports.codingToolDefinitions = [
294
295
  return `session not found: ${sessionId}`;
295
296
  return JSON.stringify(session);
296
297
  },
298
+ summaryKeys: ["sessionId"],
297
299
  },
298
300
  {
299
301
  tool: codingTailTool,
@@ -307,6 +309,7 @@ exports.codingToolDefinitions = [
307
309
  return `session not found: ${sessionId}`;
308
310
  return (0, index_1.formatCodingTail)(session);
309
311
  },
312
+ summaryKeys: ["sessionId"],
310
313
  },
311
314
  {
312
315
  tool: codingSendInputTool,
@@ -320,6 +323,7 @@ exports.codingToolDefinitions = [
320
323
  return "input is required";
321
324
  return JSON.stringify((0, index_1.getCodingSessionManager)().sendInput(sessionId, input));
322
325
  },
326
+ summaryKeys: ["sessionId", "input"],
323
327
  },
324
328
  {
325
329
  tool: codingKillTool,
@@ -330,5 +334,6 @@ exports.codingToolDefinitions = [
330
334
  return "sessionId is required";
331
335
  return JSON.stringify((0, index_1.getCodingSessionManager)().killSession(sessionId));
332
336
  },
337
+ summaryKeys: ["sessionId"],
333
338
  },
334
339
  ];