@ouro.bot/cli 0.1.0-alpha.810 → 0.1.0-alpha.811

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.
Files changed (38) hide show
  1. package/changelog.json +12 -0
  2. package/deploy/unraid/sanctuary-acceptance-contract.json +1 -1
  3. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +8 -7
  4. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
  5. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +1 -1
  6. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
  7. package/deploy/unraid/sanctuary.xml +1 -1
  8. package/dist/heart/config.js +32 -2
  9. package/dist/heart/core.js +167 -115
  10. package/dist/heart/daemon/daemon.js +19 -9
  11. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +88 -50
  12. package/dist/heart/daemon/sanctuary-acceptance-harness.js +53 -15
  13. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +6 -26
  14. package/dist/heart/frontend-approval-runtime.js +87 -13
  15. package/dist/heart/identity.js +17 -5
  16. package/dist/heart/session-events.js +8 -1
  17. package/dist/heart/tool-approval.js +64 -31
  18. package/dist/mind/pending.js +4 -4
  19. package/dist/repertoire/mcp-manager.js +365 -381
  20. package/dist/repertoire/mcp-tools.js +66 -25
  21. package/dist/repertoire/plugin-mcp.js +3 -3
  22. package/dist/repertoire/shell-sessions.js +8 -7
  23. package/dist/repertoire/tool-arguments.js +30 -8
  24. package/dist/repertoire/tools-session.js +30 -5
  25. package/dist/repertoire/tools-shell.js +30 -14
  26. package/dist/repertoire/tools-voice.js +6 -6
  27. package/dist/repertoire/tools.js +231 -205
  28. package/dist/senses/bluebubbles/index.js +4 -2
  29. package/dist/senses/cli.js +6 -5
  30. package/dist/senses/private-runtime.js +9 -3
  31. package/dist/senses/shared-turn.js +12 -9
  32. package/dist/senses/teams.js +15 -12
  33. package/dist/senses/telegram-approval-runtime.js +129 -20
  34. package/dist/senses/telegram-client.js +19 -2
  35. package/dist/senses/telegram.js +54 -15
  36. package/dist/senses/voice/twilio-phone.js +123 -173
  37. package/npm-shrinkwrap.json +2 -2
  38. package/package.json +1 -1
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MAX_PROVIDER_ITERATIONS = void 0;
4
4
  exports.validateToolCallBatchAtProductionBoundary = validateToolCallBatchAtProductionBoundary;
5
5
  exports.createProviderRegistry = createProviderRegistry;
6
+ exports.getProviderRuntime = getProviderRuntime;
6
7
  exports.resetProviderRuntime = resetProviderRuntime;
7
8
  exports.getModel = getModel;
8
9
  exports.getProvider = getProvider;
@@ -22,8 +23,8 @@ const identity_1 = require("./identity");
22
23
  const tools_1 = require("../repertoire/tools");
23
24
  const tool_arguments_1 = require("../repertoire/tool-arguments");
24
25
  const session_events_1 = require("./session-events");
26
+ const tool_approval_1 = require("./tool-approval");
25
27
  const friends_1 = require("@ouro.bot/friends");
26
- const tools_2 = require("../repertoire/tools");
27
28
  const streaming_1 = require("./streaming");
28
29
  const runtime_1 = require("../nerves/runtime");
29
30
  function validateToolCallBatchAtProductionBoundary(calls, activeTools) {
@@ -73,15 +74,15 @@ const _providerRuntimeFactories = {
73
74
  function providerLaneForFacing(facing) {
74
75
  return facing === "human" ? "outward" : "inner";
75
76
  }
76
- function resolveRuntimeProviderBinding(facing) {
77
+ function resolveRuntimeProviderBinding(facing, owner) {
77
78
  const lane = providerLaneForFacing(facing);
78
- const config = (0, identity_1.loadAgentConfig)();
79
+ const config = (0, identity_1.loadAgentConfig)(owner);
79
80
  const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
80
81
  return { lane, provider: facingConfig.provider, model: facingConfig.model };
81
82
  }
82
- async function getProviderRuntimeFingerprint(facing) {
83
- const agentName = (0, identity_2.getAgentName)();
84
- const binding = resolveRuntimeProviderBinding(facing);
83
+ async function getProviderRuntimeFingerprint(facing, owner) {
84
+ const agentName = owner?.agentName ?? (0, identity_2.getAgentName)();
85
+ const binding = resolveRuntimeProviderBinding(facing, owner);
85
86
  const credential = await (0, provider_credentials_1.readProviderCredentialRecord)(agentName, binding.provider);
86
87
  if (!credential.ok) {
87
88
  throw new Error([
@@ -103,6 +104,7 @@ async function getProviderRuntimeFingerprint(facing) {
103
104
  return {
104
105
  binding,
105
106
  fingerprint: JSON.stringify({
107
+ ...(owner ? { agentName: owner.agentName, agentRoot: owner.agentRoot } : {}),
106
108
  lane: binding.lane,
107
109
  provider: binding.provider,
108
110
  model: binding.model,
@@ -133,10 +135,11 @@ function createProviderRegistry() {
133
135
  },
134
136
  };
135
137
  }
136
- async function getProviderRuntime(facing = "human") {
138
+ async function getProviderRuntime(facing = "human", owner) {
139
+ const scope = owner ? { agentName: owner.agentName, agentRoot: owner.agentRoot } : undefined;
137
140
  let runtime = null;
138
141
  try {
139
- const { binding, fingerprint, credential } = await getProviderRuntimeFingerprint(facing);
142
+ const { binding, fingerprint, credential } = await getProviderRuntimeFingerprint(facing, scope);
140
143
  const cached = _providerRuntimeFactories[facing];
141
144
  if (!cached || cached.fingerprint !== fingerprint) {
142
145
  const create = () => createProviderRegistry().resolve(binding.provider, binding.model, credential);
@@ -323,15 +326,25 @@ async function resumeApprovalContinuation(options) {
323
326
  await options.persist(materialized.messages);
324
327
  await options.markContinuationMaterialized();
325
328
  }
326
- if (!materialized.resumeProvider) {
327
- await options.deliver(materialized.directNotice);
329
+ const currentOptions = options.revalidate ? await options.revalidate()
330
+ : options.runAgentOptions?.toolContext?.relationshipAuthorization ? null : options.runAgentOptions;
331
+ if (!materialized.resumeProvider || currentOptions === null) {
332
+ if (currentOptions === null)
333
+ (0, runtime_1.emitNervesEvent)({
334
+ level: "warn", component: "engine", event: "engine.approval_continuation_authority_block",
335
+ message: "approval continuation has no current relationship authority",
336
+ meta: { approvalId: options.record.approvalId, state: options.record.state },
337
+ });
338
+ const effect = options.record.state === "succeeded" ? "the approved action completed"
339
+ : options.record.state === "failed" ? "the approved action failed" : "the protected action was not executed";
340
+ await options.deliver(materialized.directNotice ?? `${effect}; current relationship access is unavailable, so no model continuation was run`);
328
341
  await options.completeContinuation();
329
342
  return { outcome: "terminal_notice", messages: materialized.messages };
330
343
  }
331
344
  const outward = { value: "" };
332
345
  const callbacks = continuationCallbacks(options.callbacks, outward);
333
346
  await options.markContinuationAttempted();
334
- const result = await options.runAgent(materialized.messages, callbacks, options.channel, options.signal, options.runAgentOptions);
347
+ const result = await options.runAgent(materialized.messages, callbacks, options.channel, options.signal, currentOptions);
335
348
  if (result.outcome === "suspended") {
336
349
  await options.completeContinuation();
337
350
  return { outcome: result.outcome, messages: materialized.messages, suspension: result.suspension };
@@ -406,7 +419,7 @@ function recordBlockedHabitSurfaceAttempts(habitSession, toolCalls, reason) {
406
419
  });
407
420
  }
408
421
  }
409
- async function habitToolBatchBlockReason(habitSession, toolCalls, delegatedOrigins) {
422
+ async function habitToolBatchBlockReason(habitSession, toolCalls, delegatedOrigins, selection) {
410
423
  if (!habitSession)
411
424
  return null;
412
425
  const granted = new Set(habitSession.toolPolicy.grantedTools);
@@ -420,7 +433,7 @@ async function habitToolBatchBlockReason(habitSession, toolCalls, delegatedOrigi
420
433
  return `habit tool '${call.name}' was not granted to this habit session`;
421
434
  // The canonical pre-batch schema gate guarantees object arguments here.
422
435
  const args = JSON.parse(call.arguments);
423
- const riskProfile = (0, tools_1.riskProfileForToolName)(call.name, args);
436
+ const riskProfile = (0, tools_1.riskProfileForToolName)(call.name, args, selection);
424
437
  if (!riskProfile)
425
438
  return `habit tool '${call.name}' does not have a known executable risk profile`;
426
439
  const externalMutation = highRiskExternalMutation(riskProfile);
@@ -569,7 +582,7 @@ function requiredToolResultSucceeded(name, content, args, validate) {
569
582
  return false;
570
583
  return validate?.(name, content, args) ?? true;
571
584
  }
572
- function effectFingerprint(name, rawArguments) {
585
+ function effectFingerprint(name, rawArguments, selection) {
573
586
  let args;
574
587
  try {
575
588
  const parsed = JSON.parse(rawArguments);
@@ -580,13 +593,13 @@ function effectFingerprint(name, rawArguments) {
580
593
  catch {
581
594
  return null;
582
595
  }
583
- const profile = (0, tools_1.riskProfileForToolName)(name, args);
596
+ const profile = (0, tools_1.riskProfileForToolName)(name, args, selection);
584
597
  if (!profile || profile.mutates === "none")
585
598
  return null;
586
599
  delete args.expectedVersion;
587
600
  return (0, tool_arguments_1.digestJson)({ name, args });
588
601
  }
589
- function historicalFailedEffectsForExactRepeatedRequest(messages) {
602
+ function historicalFailedEffectsForExactRepeatedRequest(messages, selection) {
590
603
  const userRequests = [];
591
604
  for (let index = 0; index < messages.length; index += 1) {
592
605
  const message = messages[index];
@@ -611,7 +624,7 @@ function historicalFailedEffectsForExactRepeatedRequest(messages) {
611
624
  for (const call of message.tool_calls) {
612
625
  if (call.type !== "function" || !call.id || !call.function.name)
613
626
  continue;
614
- const fingerprint = effectFingerprint(call.function.name, call.function.arguments);
627
+ const fingerprint = effectFingerprint(call.function.name, call.function.arguments, selection);
615
628
  if (fingerprint)
616
629
  effectsByCallId.set(call.id, { name: call.function.name, fingerprint });
617
630
  }
@@ -964,7 +977,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
964
977
  }
965
978
  };
966
979
  const facing = (0, friends_1.channelToFacing)(channel);
967
- let providerRuntime = options?.providerRuntimeOverride ?? await getProviderRuntime(facing);
980
+ const owner = options?.toolContext?.agentName && options.toolContext.agentRoot
981
+ ? { agentName: options.toolContext.agentName, agentRoot: options.toolContext.agentRoot }
982
+ : undefined;
983
+ let providerRuntime = options?.providerRuntimeOverride ?? await getProviderRuntime(facing, owner);
968
984
  const provider = providerRuntime.id;
969
985
  const toolChoiceRequired = options?.hardDisableTools ? false : options?.toolChoiceRequired ?? true;
970
986
  const traceId = options?.traceId;
@@ -1085,7 +1101,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1085
1101
  await (0, kept_notes_1.injectKeptNotes)(messages, {
1086
1102
  channel,
1087
1103
  friend: currentContext?.friend,
1088
- judge: async (input) => (0, kept_notes_1.createKeptNotesJudge)(await getProviderRuntime("agent"), signal)(input),
1104
+ judge: async (input) => (0, kept_notes_1.createKeptNotesJudge)(await getProviderRuntime("agent", owner), signal)(input),
1089
1105
  signal,
1090
1106
  traceId,
1091
1107
  });
@@ -1121,7 +1137,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1121
1137
  // a ponder packet created the return obligation in this turn.
1122
1138
  let noToolCallRetries = 0;
1123
1139
  const NO_TOOL_CALL_MAX_RETRIES = 2;
1124
- let unresolvedHistoricalEffects = historicalFailedEffectsForExactRepeatedRequest(messages);
1140
+ let unresolvedHistoricalEffects = [];
1125
1141
  let historicalToolFailureRetries = 0;
1126
1142
  let providerIterations = 0;
1127
1143
  const requiredToolCallNames = [...new Set(options?.requiredToolCalls?.names ?? [])];
@@ -1189,38 +1205,62 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1189
1205
  require("events").setMaxListeners(50, signal);
1190
1206
  }
1191
1207
  catch { /* unsupported */ }
1192
- const toolPreferences = currentContext?.friend?.toolPreferences;
1193
- const unboundBaseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, friends_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext, providerRuntime.capabilities, options?.mcpManager, providerRuntime.model);
1194
- const relationshipToolNames = options?.toolContext?.relationshipAuthorization?.advertisedToolNames;
1195
- const relationshipScopedTools = relationshipToolNames
1196
- ? unboundBaseTools.filter((tool) => relationshipToolNames.includes(tool.function.name))
1197
- : unboundBaseTools;
1198
- const baseTools = bindCurrentIngressEvidenceLocator(relationshipScopedTools, options?.toolContext?.currentIngressEvidence);
1199
- // Augment tool context with reasoning effort controls from provider
1200
- const baseToolContext = options?.toolContext
1201
- ?? (turnOrientationFrame ? { signin: async () => undefined, orientationFrame: turnOrientationFrame } : undefined);
1202
- const habitSession = options?.habitSession ?? baseToolContext?.habitSession;
1203
- const augmentedToolContext = baseToolContext
1204
- ? {
1208
+ try {
1209
+ const toolPreferences = currentContext?.friend?.toolPreferences;
1210
+ const filterTrustedTools = (tools, selected) => tools.filter((tool) => !selected.engine.some((engine) => engine.function.name === tool.function.name)
1211
+ && (channel !== "inner" || options?.toolProfile === "sanctuary-health-private" || !["send_message", "surface"].includes(tool.function.name)
1212
+ || selected.ordinary.some((definition) => definition.tool.function.name === tool.function.name)));
1213
+ const selectCurrentTools = () => {
1214
+ if (options?.hardDisableTools)
1215
+ return Object.freeze({ ordinary: Object.freeze([]), engine: Object.freeze([]) });
1216
+ const selected = (0, tools_1.selectToolsForChannel)(channel ? (0, friends_1.getChannelCapabilities)(channel) : undefined, toolPreferences, currentContext, providerRuntime.capabilities, options?.mcpManager, providerRuntime.model, { ...options?.toolContext, ...(options?.habitSession ? { habitSession: options.habitSession } : {}) });
1217
+ const ordinary = options?.tools !== undefined && !options.toolContext?.relationshipAuthorization
1218
+ ? filterTrustedTools(options.tools, selected).flatMap((tool) => {
1219
+ const definition = (0, tools_1.resolveToolDefinition)(tool.function.name, selected) ?? (0, tools_1.resolveToolDefinition)(tool.function.name);
1220
+ return definition ? [{ ...definition, tool }] : [];
1221
+ })
1222
+ : selected.ordinary;
1223
+ const bound = bindCurrentIngressEvidenceLocator(ordinary.map((definition) => definition.tool), options?.toolContext?.currentIngressEvidence);
1224
+ return Object.freeze({
1225
+ ordinary: Object.freeze(ordinary.map((definition, index) => definition.tool === bound[index]
1226
+ ? definition : Object.freeze({ ...definition, tool: bound[index] }))),
1227
+ engine: selected.engine,
1228
+ });
1229
+ };
1230
+ let toolSelection = selectCurrentTools();
1231
+ const relationship = options?.toolContext?.relationshipAuthorization;
1232
+ if (!options?.hardDisableTools && options?.tools !== undefined) {
1233
+ if (relationship) {
1234
+ toolSelection = (0, tools_1.reduceToolSelection)(toolSelection, options.tools);
1235
+ }
1236
+ }
1237
+ const unboundBaseTools = !options?.hardDisableTools && !relationship && options?.tools
1238
+ ? filterTrustedTools(options.tools, toolSelection)
1239
+ : toolSelection.ordinary.map((definition) => definition.tool);
1240
+ const relationshipToolNames = options?.toolContext?.relationshipAuthorization?.advertisedToolNames;
1241
+ const relationshipScopedTools = relationshipToolNames
1242
+ ? unboundBaseTools.filter((tool) => relationshipToolNames.includes(tool.function.name))
1243
+ : unboundBaseTools;
1244
+ const baseTools = bindCurrentIngressEvidenceLocator(relationshipScopedTools, options?.toolContext?.currentIngressEvidence);
1245
+ // Augment tool context with reasoning effort controls from provider
1246
+ const baseToolContext = options?.toolContext
1247
+ ?? { signin: async () => undefined, ...(turnOrientationFrame ? { orientationFrame: turnOrientationFrame } : {}) };
1248
+ const habitSession = options?.habitSession ?? baseToolContext?.habitSession;
1249
+ const augmentedToolContext = {
1205
1250
  ...baseToolContext,
1206
1251
  supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
1207
1252
  setReasoningEffort: (level) => { currentReasoningEffort = level; },
1208
1253
  activeWorkFrame: options?.activeWorkFrame,
1209
1254
  orientationFrame: turnOrientationFrame ?? baseToolContext.orientationFrame,
1210
1255
  ...(habitSession ? { habitSession } : {}),
1211
- }
1212
- : habitSession
1213
- ? {
1214
- signin: async () => undefined,
1215
- habitSession,
1216
- supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
1217
- setReasoningEffort: (level) => { currentReasoningEffort = level; },
1218
- }
1219
- : undefined;
1220
- // Rebase provider-owned turn state from canonical messages at user-turn start.
1221
- // This prevents stale provider caches from replaying prior-turn context.
1222
- providerRuntime.resetTurnState(messages);
1223
- try {
1256
+ toolSelection,
1257
+ selectCurrentTools,
1258
+ };
1259
+ const trustedToolContext = options?.toolContext || turnOrientationFrame || habitSession ? augmentedToolContext : undefined;
1260
+ unresolvedHistoricalEffects = historicalFailedEffectsForExactRepeatedRequest(messages, toolSelection);
1261
+ // Rebase provider-owned turn state from canonical messages at user-turn start.
1262
+ // This prevents stale provider caches from replaying prior-turn context.
1263
+ providerRuntime.resetTurnState(messages);
1224
1264
  while (!done) {
1225
1265
  // Channel-based tool filtering:
1226
1266
  // - Private runtime: exclude send_message (delivery via surface), observe (no one to observe)
@@ -1231,23 +1271,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1231
1271
  // Private runtime gets restTool instead of settleTool (rest = end turn, gated by attention queue).
1232
1272
  // toolChoiceRequired only controls whether tool_choice: "required" is set in the API call.
1233
1273
  const isPrivateRuntimeChannel = channel === "inner";
1234
- const privateRuntimeHabitCanSendMessage = isPrivateRuntimeChannel
1235
- && habitSession?.toolPolicy.outwardMessagingAllowed === true
1236
- && habitSession.toolPolicy.grantedTools.includes("send_message");
1237
- const privateRuntimeHabitCanSurface = isPrivateRuntimeChannel
1238
- && (!habitSession || (habitSession.toolPolicy.outwardMessagingAllowed === true
1239
- && habitSession.toolPolicy.grantedTools.includes("surface")));
1240
- const filteredBaseTools = isPrivateRuntimeChannel
1241
- ? baseTools.filter((t) => privateRuntimeHabitCanSendMessage || t.function.name !== "send_message")
1242
- : baseTools;
1243
1274
  const unscopedOrdinaryActiveTools = [
1244
- ...filteredBaseTools,
1245
- ...(augmentedToolContext?.noSend === true ? [] : [tools_1.ponderTool]),
1246
- ...(isPrivateRuntimeChannel && privateRuntimeHabitCanSurface ? [tools_2.surfaceToolDef] : []),
1247
- ...(isPrivateRuntimeChannel ? [tools_1.restTool] : []),
1248
- ...(!isPrivateRuntimeChannel ? [tools_1.observeTool] : []),
1249
- ...(!isPrivateRuntimeChannel ? [tools_1.settleTool] : []),
1250
- ...(isChatStyleChannel(channel ?? "") ? [tools_1.speakTool] : []),
1275
+ ...baseTools,
1276
+ ...toolSelection.engine,
1251
1277
  ];
1252
1278
  const ordinaryActiveTools = relationshipToolNames
1253
1279
  ? unscopedOrdinaryActiveTools.filter((tool) => relationshipToolNames.includes(tool.function.name))
@@ -1325,7 +1351,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1325
1351
  callbacks.onModelStart();
1326
1352
  turnCallbackBufferRef.current = habitSession
1327
1353
  ? createHabitCallbackBuffer(callbacks)
1328
- : callbacks.settleOutputMode === "final_only"
1354
+ : callbacks.settleOutputMode === "final_only" || relationship !== undefined
1329
1355
  ? createFinalOnlyTextBuffer(callbacks)
1330
1356
  : null;
1331
1357
  try {
@@ -1390,18 +1416,19 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1390
1416
  const seconds = delayMs / 1000;
1391
1417
  const cause = RETRY_LABELS[record.classification];
1392
1418
  try {
1419
+ const agentName = owner?.agentName ?? (0, identity_2.getAgentName)();
1393
1420
  if (record.provider === "openai-codex" && record.classification === "auth-failure") {
1394
- await (0, openai_codex_token_1.refreshOpenAICodexProviderCredentials)((0, identity_2.getAgentName)(), {
1421
+ await (0, openai_codex_token_1.refreshOpenAICodexProviderCredentials)(agentName, {
1395
1422
  force: true,
1396
1423
  reason: "turn-auth-failure",
1397
1424
  });
1398
1425
  }
1399
- await (0, provider_credentials_1.refreshProviderCredentialPool)((0, identity_2.getAgentName)(), {
1426
+ await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, {
1400
1427
  preserveCachedOnFailure: true,
1401
1428
  providers: [record.provider],
1402
1429
  });
1403
1430
  _providerRuntimeFactories[facing] = null;
1404
- providerRuntime = await getProviderRuntime(facing);
1431
+ providerRuntime = await getProviderRuntime(facing, owner);
1405
1432
  providerRuntime.resetTurnState(attemptMessages);
1406
1433
  }
1407
1434
  catch (refreshError) {
@@ -1712,7 +1739,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1712
1739
  entry.call,
1713
1740
  entry.validated.arguments,
1714
1741
  ]));
1715
- const habitBlockReason = await habitToolBatchBlockReason(habitSession, result.toolCalls, augmentedToolContext?.delegatedOrigins);
1742
+ const preflightRejections = new Map();
1743
+ for (const entry of validCalls) {
1744
+ if (!relationship && !toolSelection.engine.some((tool) => tool.function.name === entry.call.name))
1745
+ continue;
1746
+ const prepared = await (0, tools_1.preflightToolCall)(entry.call.name, validatedCallArguments.get(entry.call), augmentedToolContext);
1747
+ if (prepared.kind !== "ready")
1748
+ preflightRejections.set(entry.call.id, prepared.text);
1749
+ }
1750
+ if (preflightRejections.size > 0) {
1751
+ streamCallbackBuffer?.discard();
1752
+ rejectToolBatch(msg, result.toolCalls.map((call) => preflightRejections.get(call.id)
1753
+ ?? "rejected: another call in this batch failed current authorization; no handler was executed."));
1754
+ continue;
1755
+ }
1756
+ const habitBlockReason = await habitToolBatchBlockReason(habitSession, result.toolCalls, augmentedToolContext?.delegatedOrigins, toolSelection);
1716
1757
  if (habitBlockReason) {
1717
1758
  streamCallbackBuffer?.discard();
1718
1759
  recordBlockedHabitSurfaceAttempts(habitSession, result.toolCalls, habitBlockReason);
@@ -1728,7 +1769,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1728
1769
  continue;
1729
1770
  }
1730
1771
  const soleViolation = result.toolCalls.length > 1
1731
- ? result.toolCalls.find((call) => SOLE_CALL_REJECTION[call.name] !== undefined || (0, tools_1.resolveToolDefinition)(call.name)?.terminalProjection?.requiresSoleCall === true)
1772
+ ? result.toolCalls.find((call) => SOLE_CALL_REJECTION[call.name] !== undefined || (0, tools_1.resolveToolDefinition)(call.name, toolSelection)?.terminalProjection?.requiresSoleCall === true)
1732
1773
  : undefined;
1733
1774
  if (soleViolation) {
1734
1775
  streamCallbackBuffer?.discard();
@@ -1740,7 +1781,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1740
1781
  ? result.toolCalls[0]
1741
1782
  : null;
1742
1783
  const soleTerminalProjection = soleTerminalCall
1743
- ? (0, tools_1.resolveToolDefinition)(soleTerminalCall.name)?.terminalProjection
1784
+ ? (0, tools_1.resolveToolDefinition)(soleTerminalCall.name, toolSelection)?.terminalProjection
1744
1785
  : undefined;
1745
1786
  if (soleTerminalCall && soleTerminalProjection?.mode === "verbatim") {
1746
1787
  const terminalArgs = validatedCallArguments.get(soleTerminalCall);
@@ -1755,29 +1796,30 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1755
1796
  callbacks.onClearText?.();
1756
1797
  callbacks.onToolStart(soleTerminalCall.name, terminalArgs);
1757
1798
  let terminalResult;
1799
+ let terminalSucceeded = false;
1758
1800
  try {
1759
- const execToolFn = options?.execTool ?? tools_1.execTool;
1760
- terminalResult = await execToolFn(soleTerminalCall.name, terminalArgs, augmentedToolContext);
1801
+ if (options?.execTool && !relationship) {
1802
+ terminalResult = await options.execTool(soleTerminalCall.name, terminalArgs, trustedToolContext);
1803
+ terminalSucceeded = true;
1804
+ }
1805
+ else {
1806
+ const execution = await (0, tools_1.executeTool)(soleTerminalCall.name, terminalArgs, augmentedToolContext, options?.execTool);
1807
+ terminalResult = "error" in execution
1808
+ ? `error: ${execution.error instanceof Error ? execution.error.message : String(execution.error)}`
1809
+ : execution.text;
1810
+ terminalSucceeded = execution.kind === "handler_succeeded";
1811
+ }
1761
1812
  }
1762
1813
  catch (error) {
1763
- callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), false);
1764
- pushGenerated(msg);
1765
- const failure = error instanceof Error ? `error: ${error.message}` : `error: ${String(error)}`;
1766
- pushGenerated({ role: "tool", tool_call_id: soleTerminalCall.id, content: failure });
1767
- providerRuntime.appendToolOutput(soleTerminalCall.id, failure);
1768
- callbacks.onTextChunk(failure);
1769
- completion = { answer: failure, intent: "blocked" };
1770
- outcome = "blocked";
1771
- done = true;
1772
- continue;
1814
+ terminalResult = error instanceof Error ? `error: ${error.message}` : `error: ${String(error)}`;
1773
1815
  }
1774
- callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), true);
1816
+ callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs, toolSelection), terminalSucceeded);
1775
1817
  pushGenerated(msg);
1776
1818
  pushGenerated({ role: "tool", tool_call_id: soleTerminalCall.id, content: terminalResult });
1777
1819
  providerRuntime.appendToolOutput(soleTerminalCall.id, terminalResult);
1778
1820
  callbacks.onTextChunk(terminalResult);
1779
- completion = { answer: terminalResult, intent: "complete" };
1780
- outcome = "settled";
1821
+ completion = { answer: terminalResult, intent: terminalSucceeded ? "complete" : "blocked" };
1822
+ outcome = terminalSucceeded ? "settled" : "blocked";
1781
1823
  done = true;
1782
1824
  continue;
1783
1825
  }
@@ -1788,7 +1830,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1788
1830
  const requiredToolCallsGate = pendingRequiredToolCalls();
1789
1831
  if (requiredToolCallsGate) {
1790
1832
  streamCallbackBuffer?.discard();
1791
- callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
1833
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs, toolSelection), false);
1792
1834
  callbacks.onClearText?.();
1793
1835
  queueRequiredCorrection(msg, requiredToolCallsGate.message, "before required tool calls completed");
1794
1836
  (0, runtime_1.emitNervesEvent)({
@@ -1804,7 +1846,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1804
1846
  const attentionQueue = augmentedToolContext?.delegatedOrigins;
1805
1847
  if (isPrivateRuntimeChannel && attentionQueue && attentionQueue.length > 0) {
1806
1848
  streamCallbackBuffer?.discard();
1807
- callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
1849
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs, toolSelection), false);
1808
1850
  callbacks.onClearText?.();
1809
1851
  const gateMessage = "current held-work frame still has unsurfaced items — return each listed item with surface(delegationId=...) before you settle. Older transcript claims are historical; only the current held-work frame is the gate.";
1810
1852
  rejectToolBatch(msg, [gateMessage]);
@@ -1816,7 +1858,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1816
1858
  const requiredAnswerRejection = options?.requiredToolCalls?.validateTerminalAnswer?.(answer);
1817
1859
  if (requiredAnswerRejection) {
1818
1860
  streamCallbackBuffer?.discard();
1819
- callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
1861
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs, toolSelection), false);
1820
1862
  callbacks.onClearText?.();
1821
1863
  queueRequiredCorrection(msg, requiredAnswerRejection, "before required terminal answer validation completed");
1822
1864
  (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.required_tool_answer_rejected", message: "unsupported settle answer rejected after required reads", meta: { answerLength: answer.length } });
@@ -1825,7 +1867,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1825
1867
  // Private-runtime settle: no CompletionMetadata, "(settled)" ack
1826
1868
  if (isPrivateRuntimeChannel) {
1827
1869
  streamCallbackBuffer?.discard();
1828
- callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
1870
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs, toolSelection), true);
1829
1871
  pushGenerated(msg);
1830
1872
  const settled = "(settled)";
1831
1873
  pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: settled });
@@ -1854,12 +1896,12 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1854
1896
  await streamCallbackBuffer?.flush();
1855
1897
  }
1856
1898
  catch (error) {
1857
- callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
1899
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs, toolSelection), false);
1858
1900
  streamCallbackBuffer?.discard();
1859
1901
  finishTerminalProviderError(new streaming_1.SettleFinalizationCallbackError(error), "unknown");
1860
1902
  continue;
1861
1903
  }
1862
- callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
1904
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs, toolSelection), true);
1863
1905
  completion = {
1864
1906
  answer: deliveredAnswer,
1865
1907
  intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
@@ -1884,7 +1926,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1884
1926
  // The payload is structurally final, but a semantic continuation
1885
1927
  // gate rejected it. Return that exact gate reason to the model.
1886
1928
  streamCallbackBuffer?.discard();
1887
- callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
1929
+ callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs, toolSelection), false);
1888
1930
  callbacks.onClearText?.();
1889
1931
  rejectToolBatch(msg, [retryError]);
1890
1932
  }
@@ -1905,7 +1947,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1905
1947
  message: "agent observed without responding",
1906
1948
  meta: { ...(reason ? { reason } : {}) },
1907
1949
  });
1908
- callbacks.onToolEnd("observe", (0, tools_1.summarizeArgs)("observe", observeArgs), true);
1950
+ callbacks.onToolEnd("observe", (0, tools_1.summarizeArgs)("observe", observeArgs, toolSelection), true);
1909
1951
  pushGenerated(msg);
1910
1952
  const silenced = "(silenced)";
1911
1953
  pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: silenced });
@@ -1923,14 +1965,14 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1923
1965
  // Attention queue gate: reject rest if items remain
1924
1966
  const attentionQueue = augmentedToolContext?.delegatedOrigins;
1925
1967
  if (attentionQueue && attentionQueue.length > 0) {
1926
- callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
1968
+ callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs, toolSelection), false);
1927
1969
  const gateMessage = "current held-work frame still has unsurfaced items — return each listed item with surface(delegationId=...) before you rest. Older transcript claims are historical; only the current held-work frame is the gate.";
1928
1970
  rejectToolBatch(msg, [gateMessage]);
1929
1971
  continue;
1930
1972
  }
1931
1973
  if (hasFreshPendingWork(options) && !freshWorkGateFired) {
1932
1974
  freshWorkGateFired = true;
1933
- callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
1975
+ callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs, toolSelection), false);
1934
1976
  const gateMessage = "fresh work arrived for me this turn — inspect the pending messages above and take the next concrete action before you rest.";
1935
1977
  rejectToolBatch(msg, [gateMessage]);
1936
1978
  (0, runtime_1.emitNervesEvent)({
@@ -1942,7 +1984,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1942
1984
  });
1943
1985
  continue;
1944
1986
  }
1945
- callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), true);
1987
+ callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs, toolSelection), true);
1946
1988
  pushGenerated(msg);
1947
1989
  const ack = "(resting)";
1948
1990
  pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
@@ -1968,7 +2010,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1968
2010
  streamCallbackBuffer?.discard();
1969
2011
  for (const rejection of requiredDispatchRejections.values()) {
1970
2012
  callbacks.onToolStart(rejection.name, rejection.args);
1971
- callbacks.onToolEnd(rejection.name, (0, tools_1.summarizeArgs)(rejection.name, rejection.args), false);
2013
+ callbacks.onToolEnd(rejection.name, (0, tools_1.summarizeArgs)(rejection.name, rejection.args, toolSelection), false);
1972
2014
  options?.toolBoundaryObserver?.({
1973
2015
  name: rejection.name, reason: "dependency_rejected",
1974
2016
  globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(rejection.name)?.handler === "function",
@@ -1987,7 +2029,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1987
2029
  const executionRejections = new Map();
1988
2030
  for (const entry of validCalls) {
1989
2031
  const args = validatedCallArguments.get(entry.call);
1990
- const fingerprint = effectFingerprint(entry.call.name, entry.call.arguments);
2032
+ const fingerprint = effectFingerprint(entry.call.name, entry.call.arguments, toolSelection);
1991
2033
  if (forcingHistoricalEffect && fingerprint && !unresolvedHistoricalEffects.some((effect) => effect.fingerprint === fingerprint)) {
1992
2034
  executionRejections.set(entry.call.id, "rejected: this turn is retrying an unresolved historical effect, and these mutation arguments do not match it. Read current state or retry the exact failed effect.");
1993
2035
  }
@@ -2007,7 +2049,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2007
2049
  continue;
2008
2050
  const args = validatedCallArguments.get(entry.call);
2009
2051
  callbacks.onToolStart(entry.call.name, args);
2010
- callbacks.onToolEnd(entry.call.name, (0, tools_1.summarizeArgs)(entry.call.name, args), false);
2052
+ callbacks.onToolEnd(entry.call.name, (0, tools_1.summarizeArgs)(entry.call.name, args, toolSelection), false);
2011
2053
  }
2012
2054
  rejectToolBatch(msg, result.toolCalls.map((call) => executionRejections.get(call.id)
2013
2055
  ?? "rejected: another call in this batch was inadmissible; no handler was executed."));
@@ -2048,11 +2090,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2048
2090
  continue;
2049
2091
  }
2050
2092
  streamCallbackBuffer?.discard();
2051
- const toolDigest = (0, tool_arguments_1.digestJson)({
2052
- name: protectedCall.call.name,
2053
- schemaDigest: protectedCall.validated.schemaDigest,
2054
- policyId: protectedCall.policy.policyId,
2055
- });
2093
+ const toolDigest = (0, tool_approval_1.digestApprovalToolDefinition)((0, tools_1.resolveToolDefinition)(protectedCall.call.name, toolSelection), protectedCall.validated.schemaDigest, protectedCall.policy.policyId);
2056
2094
  const policyDigest = (0, tool_arguments_1.digestJson)({
2057
2095
  policyId: protectedCall.policy.policyId,
2058
2096
  actionClass: protectedCall.policy.actionClass,
@@ -2094,7 +2132,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2094
2132
  if (signal?.aborted)
2095
2133
  break;
2096
2134
  const args = validatedCallArguments.get(tc);
2097
- const currentEffectFingerprint = effectFingerprint(tc.name, tc.arguments);
2135
+ const currentEffectFingerprint = effectFingerprint(tc.name, tc.arguments, toolSelection);
2098
2136
  if (tc.name === "send_message" && args.friendId === "self") {
2099
2137
  sawSendMessageSelf = true;
2100
2138
  }
@@ -2102,7 +2140,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2102
2140
  // The canonical pre-batch schema gate guarantees a required string.
2103
2141
  const speakArgs = JSON.parse(tc.arguments);
2104
2142
  const speakMessage = speakArgs.message;
2105
- const argSummary = (0, tools_1.summarizeArgs)("speak", { message: speakMessage });
2143
+ const argSummary = (0, tools_1.summarizeArgs)("speak", { message: speakMessage }, toolSelection);
2106
2144
  callbacks.onToolStart("speak", { message: speakMessage });
2107
2145
  if (speakMessage.trim().length === 0) {
2108
2146
  const err = "speak requires a non-empty `message` string.";
@@ -2154,7 +2192,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2154
2192
  }
2155
2193
  if (tc.name === "ponder") {
2156
2194
  const parsedArgs = normalizeLegacyPonderArgs(parsePonderPayload(tc.arguments));
2157
- const argSummary = (0, tools_1.summarizeArgs)(tc.name, parsedArgs);
2195
+ const argSummary = (0, tools_1.summarizeArgs)(tc.name, parsedArgs, toolSelection);
2158
2196
  callbacks.onToolStart(tc.name, parsedArgs);
2159
2197
  let toolResult;
2160
2198
  let success = false;
@@ -2320,19 +2358,28 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2320
2358
  callbacks.onToolStart(tc.name, args);
2321
2359
  let toolResult;
2322
2360
  let success;
2361
+ let invoked = false;
2323
2362
  try {
2324
- const execToolFn = options?.execTool ?? tools_1.execTool;
2325
2363
  const routineActionSelection = approvalCalls.find((entry) => entry.call.id === tc.id)?.routineActionSelection;
2326
2364
  const executionToolContext = routineActionSelection && augmentedToolContext ? { ...augmentedToolContext, routineActionSelection } : augmentedToolContext;
2327
- if (requiredToolCallNames.includes(tc.name) && !options?.requiredToolCalls?.requireSuccessfulResults)
2328
- dispatchedRequiredToolCalls.add(tc.name);
2329
- toolResult = await execToolFn(tc.name, args, executionToolContext);
2330
- success = true;
2365
+ if (options?.execTool && !relationship) {
2366
+ invoked = true;
2367
+ toolResult = await options.execTool(tc.name, args, trustedToolContext);
2368
+ success = true;
2369
+ }
2370
+ else {
2371
+ const execution = await (0, tools_1.executeTool)(tc.name, args, executionToolContext, options?.execTool);
2372
+ invoked = execution.kind !== "rejected_before_handler";
2373
+ success = execution.kind === "handler_succeeded";
2374
+ toolResult = "error" in execution ? `error: ${execution.error}` : execution.text;
2375
+ }
2331
2376
  }
2332
2377
  catch (e) {
2333
2378
  toolResult = `error: ${e}`;
2334
2379
  success = false;
2335
2380
  }
2381
+ if (invoked && requiredToolCallNames.includes(tc.name) && !options?.requiredToolCalls?.requireSuccessfulResults)
2382
+ dispatchedRequiredToolCalls.add(tc.name);
2336
2383
  const modelResult = (0, tool_friction_1.rewriteToolResultForModel)(tc.name, toolResult, toolFrictionLedger);
2337
2384
  pushGenerated({ role: "tool", tool_call_id: tc.id, content: modelResult });
2338
2385
  providerRuntime.appendToolOutput(tc.id, modelResult);
@@ -2355,17 +2402,17 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2355
2402
  if (success && currentEffectFingerprint && !toolResultIndicatesFailure(toolResult)) {
2356
2403
  unresolvedHistoricalEffects = unresolvedHistoricalEffects.filter((effect) => effect.fingerprint !== currentEffectFingerprint);
2357
2404
  }
2358
- const resolvedRiskProfile = (0, tools_1.resolveToolDefinition)(tc.name)?.riskProfile;
2405
+ const resolvedRiskProfile = (0, tools_1.resolveToolDefinition)(tc.name, toolSelection)?.riskProfile;
2359
2406
  const toolRiskProfile = typeof resolvedRiskProfile === "function" ? resolvedRiskProfile(args) : resolvedRiskProfile;
2360
2407
  options?.toolBoundaryObserver?.({
2361
2408
  name: tc.name,
2362
2409
  reason: "dispatched",
2363
- globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(tc.name)?.handler === "function",
2364
- invoked: true,
2410
+ globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(tc.name, toolSelection)?.handler === "function",
2411
+ invoked,
2365
2412
  sideEffect: success && toolRiskProfile?.mutates !== "none",
2366
2413
  });
2367
2414
  (0, tool_loop_1.recordToolOutcome)(toolLoopState, tc.name, args, modelResult, success);
2368
- callbacks.onToolEnd(tc.name, (0, tools_1.buildToolResultSummary)(tc.name, args, modelResult, success), success);
2415
+ callbacks.onToolEnd(tc.name, (0, tools_1.buildToolResultSummary)(tc.name, args, modelResult, success, toolSelection), success);
2369
2416
  callbacks.onToolResult?.(messages);
2370
2417
  }
2371
2418
  }
@@ -2391,6 +2438,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2391
2438
  }
2392
2439
  }
2393
2440
  }
2441
+ catch (error) {
2442
+ if (!(error instanceof tools_1.ToolSelectionError))
2443
+ throw error;
2444
+ finishTerminalProviderError(error, "unknown");
2445
+ }
2394
2446
  finally {
2395
2447
  nextAttemptControls = [];
2396
2448
  rejectedAttempt = [];