@ouro.bot/cli 0.1.0-alpha.134 → 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.
package/changelog.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.135",
6
+ "changes": [
7
+ "Metacognitive tool vocabulary complete: go_inward renamed to descend. RunAgentOutcome value is now 'descended', event is 'engine.descended'.",
8
+ "summarizeArgs rewritten: reads summaryKeys from ToolDefinition instead of per-tool switch branches. summarizeTeamsArgs and summarizeGithubArgs eliminated."
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.134",
6
13
  "changes": [
@@ -176,7 +176,7 @@ Object.defineProperty(exports, "buildSystem", { enumerable: true, get: function
176
176
  const SOLE_CALL_REJECTION = {
177
177
  settle: "rejected: settle must be the only tool call. finish your work first, then call settle alone.",
178
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.",
179
+ descend: "rejected: descend must be the only tool call. finish your other work first, then call descend alone.",
180
180
  };
181
181
  const DELEGATION_REASON_PROSE_HANDOFF = {
182
182
  explicit_reflection: "something in the conversation called for reflection",
@@ -186,7 +186,7 @@ const DELEGATION_REASON_PROSE_HANDOFF = {
186
186
  non_fast_path_tool: "this needs tools beyond a simple reply",
187
187
  unresolved_obligation: "there's an unresolved commitment from an earlier conversation",
188
188
  };
189
- function buildGoInwardHandoffPacket(params) {
189
+ function buildDescendHandoffPacket(params) {
190
190
  const reasons = params.delegationDecision?.reasons ?? [];
191
191
  const reasonProse = reasons.length > 0
192
192
  ? reasons.map((r) => DELEGATION_REASON_PROSE_HANDOFF[r]).join("; ")
@@ -245,14 +245,14 @@ function isExternalStateQuery(toolName, args) {
245
245
  const cmd = String(args.command ?? "");
246
246
  return /\bgh\s+(pr|run|api|issue)\b/.test(cmd) || /\bnpm\s+(view|info|show)\b/.test(cmd);
247
247
  }
248
- function getSettleRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp, _delegationDecision, sawSendMessageSelf, sawGoInward, _sawQuerySession, currentObligation, innerJob, sawExternalStateQuery) {
248
+ function getSettleRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp, _delegationDecision, sawSendMessageSelf, sawDescend, _sawQuerySession, currentObligation, innerJob, sawExternalStateQuery) {
249
249
  // Delegation adherence removed: the delegation decision is surfaced in the
250
250
  // system prompt as a suggestion. Hard-gating settle caused infinite
251
251
  // rejection loops where the agent couldn't respond to the user at all.
252
252
  // The agent is free to follow or ignore the delegation hint.
253
253
  // 2. Pending obligation not addressed
254
- if (innerJob?.obligationStatus === "pending" && !sawSendMessageSelf && !sawGoInward) {
255
- return "you're still holding something from an earlier conversation -- someone is waiting for your answer. finish the thought first, or go_inward to keep working on it privately.";
254
+ if (innerJob?.obligationStatus === "pending" && !sawSendMessageSelf && !sawDescend) {
255
+ return "you're still holding something from an earlier conversation -- someone is waiting for your answer. finish the thought first, or descend to keep working on it privately.";
256
256
  }
257
257
  // 3. mustResolveBeforeHandoff + missing intent
258
258
  if (mustResolveBeforeHandoff && !intent) {
@@ -496,7 +496,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
496
496
  let mustResolveBeforeHandoffActive = options?.mustResolveBeforeHandoff === true;
497
497
  let currentReasoningEffort = "medium";
498
498
  let sawSendMessageSelf = false;
499
- let sawGoInward = false;
499
+ let sawDescend = false;
500
500
  let sawQuerySession = false;
501
501
  let sawBridgeManage = false;
502
502
  let sawExternalStateQuery = false;
@@ -522,11 +522,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
522
522
  providerRuntime.resetTurnState(messages);
523
523
  while (!done) {
524
524
  // Channel-based tool filtering:
525
- // - Inner dialog: exclude go_inward (already inward), send_message (delivery via surface), observe (no one to observe)
525
+ // - Inner dialog: exclude descend (already inward), send_message (delivery via surface), observe (no one to observe)
526
526
  // - 1:1 sessions: exclude observe (can't ignore someone talking directly to you)
527
527
  // - Group chats: observe available
528
528
  //
529
- // go_inward, settle, surface, and observe are always assembled based on channel context.
529
+ // descend, settle, surface, and observe are always assembled based on channel context.
530
530
  // toolChoiceRequired only controls whether tool_choice: "required" is set in the API call.
531
531
  const isInnerDialog = channel === "inner";
532
532
  const filteredBaseTools = isInnerDialog
@@ -534,7 +534,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
534
534
  : baseTools;
535
535
  const activeTools = [
536
536
  ...filteredBaseTools,
537
- ...(!isInnerDialog ? [tools_1.goInwardTool] : []),
537
+ ...(!isInnerDialog ? [tools_1.descendTool] : []),
538
538
  ...(isInnerDialog ? [surface_tool_1.surfaceToolDef] : []),
539
539
  ...(currentContext?.isGroupChat && !isInnerDialog ? [tools_1.observeTool] : []),
540
540
  tools_1.settleTool,
@@ -619,9 +619,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
619
619
  else {
620
620
  // Check for settle sole call: intercept before tool execution
621
621
  if (isSoleSettle) {
622
+ /* v8 ignore next -- defensive: JSON.parse catch for malformed settle args @preserve */
623
+ const settleArgs = (() => { try {
624
+ return JSON.parse(result.toolCalls[0].arguments);
625
+ }
626
+ catch {
627
+ return {};
628
+ } })();
629
+ callbacks.onToolStart("settle", settleArgs);
622
630
  // Inner dialog attention queue gate: reject settle if items remain
623
631
  const attentionQueue = (augmentedToolContext ?? options?.toolContext)?.delegatedOrigins;
624
632
  if (isInnerDialog && attentionQueue && attentionQueue.length > 0) {
633
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
625
634
  callbacks.onClearText?.();
626
635
  messages.push(msg);
627
636
  const gateMessage = "you're holding thoughts someone is waiting for — surface them before you settle.";
@@ -634,6 +643,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
634
643
  const { answer, intent } = parseSettlePayload(result.toolCalls[0].arguments);
635
644
  // Inner dialog settle: no CompletionMetadata, "(settled)" ack
636
645
  if (isInnerDialog) {
646
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
637
647
  messages.push(msg);
638
648
  const settled = "(settled)";
639
649
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: settled });
@@ -642,7 +652,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
642
652
  done = true;
643
653
  continue;
644
654
  }
645
- const retryError = getSettleRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
655
+ const retryError = getSettleRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawDescend, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
646
656
  const deliveredAnswer = answer;
647
657
  const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
648
658
  const validTerminalIntent = intent === "complete" || intent === "blocked";
@@ -650,6 +660,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
650
660
  && !retryError
651
661
  && (!mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
652
662
  if (validClosure) {
663
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
653
664
  completion = {
654
665
  answer: deliveredAnswer,
655
666
  intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
@@ -684,6 +695,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
684
695
  // Answer is undefined -- the model's settle was incomplete or
685
696
  // malformed. Clear any partial streamed text or noise, then push the
686
697
  // assistant msg + error tool result and let the model try again.
698
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
687
699
  callbacks.onClearText?.();
688
700
  messages.push(msg);
689
701
  const toolRetryMessage = retryError
@@ -696,19 +708,24 @@ async function runAgent(messages, callbacks, channel, signal, options) {
696
708
  // Check for observe sole call: intercept before tool execution
697
709
  const isSoleObserve = result.toolCalls.length === 1 && result.toolCalls[0].name === "observe";
698
710
  if (isSoleObserve) {
699
- let reason;
700
- try {
701
- const parsed = JSON.parse(result.toolCalls[0].arguments);
702
- if (typeof parsed?.reason === "string")
703
- reason = parsed.reason;
711
+ /* v8 ignore next -- defensive: JSON.parse catch for malformed observe args @preserve */
712
+ const observeArgs = (() => { try {
713
+ return JSON.parse(result.toolCalls[0].arguments);
704
714
  }
705
- catch { /* ignore */ }
715
+ catch {
716
+ return {};
717
+ } })();
718
+ let reason;
719
+ if (typeof observeArgs?.reason === "string")
720
+ reason = observeArgs.reason;
721
+ callbacks.onToolStart("observe", observeArgs);
706
722
  (0, runtime_1.emitNervesEvent)({
707
723
  component: "engine",
708
724
  event: "engine.observe",
709
725
  message: "agent declined to respond in group chat",
710
726
  meta: { ...(reason ? { reason } : {}) },
711
727
  });
728
+ callbacks.onToolEnd("observe", (0, tools_1.summarizeArgs)("observe", observeArgs), true);
712
729
  messages.push(msg);
713
730
  const silenced = "(silenced)";
714
731
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: silenced });
@@ -717,14 +734,15 @@ async function runAgent(messages, callbacks, channel, signal, options) {
717
734
  done = true;
718
735
  continue;
719
736
  }
720
- // Check for go_inward sole call: intercept before tool execution
721
- const isSoleGoInward = result.toolCalls.length === 1 && result.toolCalls[0].name === "go_inward";
722
- if (isSoleGoInward) {
737
+ // Check for descend sole call: intercept before tool execution
738
+ const isSoleDescend = result.toolCalls.length === 1 && result.toolCalls[0].name === "descend";
739
+ if (isSoleDescend) {
723
740
  let parsedArgs = {};
724
741
  try {
725
742
  parsedArgs = JSON.parse(result.toolCalls[0].arguments);
726
743
  }
727
744
  catch { /* ignore */ }
745
+ callbacks.onToolStart("descend", parsedArgs);
728
746
  /* v8 ignore next -- defensive: topic always string from model @preserve */
729
747
  const topic = typeof parsedArgs.topic === "string" ? parsedArgs.topic : "";
730
748
  const answer = typeof parsedArgs.answer === "string" ? parsedArgs.answer : undefined;
@@ -738,7 +756,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
738
756
  callbacks.onTextChunk(answer);
739
757
  }
740
758
  // Build handoff packet and enqueue
741
- const handoffContent = buildGoInwardHandoffPacket({
759
+ const handoffContent = buildDescendHandoffPacket({
742
760
  topic,
743
761
  mode,
744
762
  delegationDecision: options?.delegationDecision,
@@ -749,6 +767,24 @@ async function runAgent(messages, callbacks, channel, signal, options) {
749
767
  const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_2.getAgentName)());
750
768
  const currentSession = options?.toolContext?.currentSession;
751
769
  const isInnerChannel = currentSession?.friendId === "self" && currentSession?.channel === "inner";
770
+ // Create obligation FIRST so we can attach its ID to the pending message
771
+ let createdObligationId;
772
+ if (currentSession && !isInnerChannel) {
773
+ try {
774
+ const obligation = (0, obligations_1.createObligation)((0, identity_2.getAgentRoot)(), {
775
+ origin: {
776
+ friendId: currentSession.friendId,
777
+ channel: currentSession.channel,
778
+ key: currentSession.key,
779
+ },
780
+ content: topic,
781
+ });
782
+ createdObligationId = obligation.id;
783
+ }
784
+ catch {
785
+ /* v8 ignore next -- defensive: obligation store write failure should not break descend @preserve */
786
+ }
787
+ }
752
788
  const envelope = {
753
789
  from: (0, identity_2.getAgentName)(),
754
790
  friendId: "self",
@@ -764,40 +800,28 @@ async function runAgent(messages, callbacks, channel, signal, options) {
764
800
  key: currentSession.key,
765
801
  },
766
802
  obligationStatus: "pending",
803
+ /* v8 ignore next -- defensive: createdObligationId is undefined only when obligation store write fails @preserve */
804
+ ...(createdObligationId ? { obligationId: createdObligationId } : {}),
767
805
  } : {}),
768
806
  };
769
807
  (0, pending_1.queuePendingMessage)(pendingDir, envelope);
770
- if (currentSession && !isInnerChannel) {
771
- try {
772
- (0, obligations_1.createObligation)((0, identity_2.getAgentRoot)(), {
773
- origin: {
774
- friendId: currentSession.friendId,
775
- channel: currentSession.channel,
776
- key: currentSession.key,
777
- },
778
- content: topic,
779
- });
780
- }
781
- catch {
782
- /* v8 ignore next -- defensive: obligation store write failure should not break go_inward @preserve */
783
- }
784
- }
785
808
  try {
786
809
  await (0, socket_client_1.requestInnerWake)((0, identity_2.getAgentName)());
787
810
  }
788
811
  catch { /* daemon may not be running */ }
789
- sawGoInward = true;
812
+ callbacks.onToolEnd("descend", (0, tools_1.summarizeArgs)("descend", parsedArgs), true);
813
+ sawDescend = true;
790
814
  messages.push(msg);
791
815
  const ack = "(going inward)";
792
816
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
793
817
  providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
794
818
  (0, runtime_1.emitNervesEvent)({
795
819
  component: "engine",
796
- event: "engine.go_inward",
820
+ event: "engine.descended",
797
821
  message: "taking thread inward",
798
822
  meta: { mode, hasAnswer: answer !== undefined, contentSnippet: topic.slice(0, 80) },
799
823
  });
800
- outcome = "go_inward";
824
+ outcome = "descended";
801
825
  done = true;
802
826
  continue;
803
827
  }
@@ -957,7 +981,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
957
981
  trace_id: traceId,
958
982
  component: "engine",
959
983
  message: "runAgent turn completed",
960
- meta: { done, sawGoInward, sawQuerySession, sawBridgeManage },
984
+ meta: { done, sawDescend, sawQuerySession, sawBridgeManage },
961
985
  });
962
986
  return {
963
987
  usage: lastUsage,
@@ -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}`);
@@ -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 */
@@ -626,12 +630,13 @@ do NOT call no-op tools just before \`settle\`. if i am done, i call \`settle\`
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) {
@@ -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
  ];