@ouro.bot/cli 0.1.0-alpha.810 → 0.1.0-alpha.812
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 +18 -0
- package/deploy/unraid/README.txt +3 -6
- package/deploy/unraid/sanctuary-acceptance-contract.json +1 -1
- package/deploy/unraid/sanctuary-deployment-target.mjs +1 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +8 -7
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/config.js +32 -2
- package/dist/heart/core.js +167 -115
- package/dist/heart/daemon/daemon.js +19 -9
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +88 -50
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +53 -15
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +6 -26
- package/dist/heart/frontend-approval-runtime.js +87 -13
- package/dist/heart/identity.js +17 -5
- package/dist/heart/session-events.js +8 -1
- package/dist/heart/tool-approval.js +64 -31
- package/dist/mind/pending.js +4 -4
- package/dist/repertoire/mcp-manager.js +365 -381
- package/dist/repertoire/mcp-tools.js +66 -25
- package/dist/repertoire/plugin-mcp.js +3 -3
- package/dist/repertoire/shell-sessions.js +8 -7
- package/dist/repertoire/tool-arguments.js +30 -8
- package/dist/repertoire/tools-session.js +30 -5
- package/dist/repertoire/tools-shell.js +30 -14
- package/dist/repertoire/tools-voice.js +6 -6
- package/dist/repertoire/tools.js +231 -205
- package/dist/senses/bluebubbles/index.js +4 -2
- package/dist/senses/cli.js +6 -5
- package/dist/senses/private-runtime.js +9 -3
- package/dist/senses/shared-turn.js +12 -9
- package/dist/senses/teams.js +15 -12
- package/dist/senses/telegram-approval-runtime.js +129 -20
- package/dist/senses/telegram-client.js +19 -2
- package/dist/senses/telegram.js +54 -15
- package/dist/senses/voice/twilio-phone.js +123 -173
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/repertoire/tools.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
3
|
+
exports.ToolSelectionError = exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = exports.SANCTUARY_OWNER_ADDITIONS = void 0;
|
|
4
|
+
exports.toolSelectionSchemas = toolSelectionSchemas;
|
|
5
|
+
exports.selectToolsForChannel = selectToolsForChannel;
|
|
6
6
|
exports.getToolsForChannel = getToolsForChannel;
|
|
7
|
+
exports.reduceToolSelection = reduceToolSelection;
|
|
7
8
|
exports.resolveToolDefinition = resolveToolDefinition;
|
|
8
9
|
exports.approvalPolicyForToolName = approvalPolicyForToolName;
|
|
9
10
|
exports.classifyApprovalForInvocation = classifyApprovalForInvocation;
|
|
@@ -11,9 +12,12 @@ exports.approvalPolicyForInvocation = approvalPolicyForInvocation;
|
|
|
11
12
|
exports.shellRiskProfile = shellRiskProfile;
|
|
12
13
|
exports.riskProfileForTool = riskProfileForTool;
|
|
13
14
|
exports.riskProfileForToolName = riskProfileForToolName;
|
|
15
|
+
exports.preflightToolCall = preflightToolCall;
|
|
16
|
+
exports.executeTool = executeTool;
|
|
14
17
|
exports.execTool = execTool;
|
|
15
18
|
exports.summarizeArgs = summarizeArgs;
|
|
16
19
|
exports.buildToolResultSummary = buildToolResultSummary;
|
|
20
|
+
const node_util_1 = require("node:util");
|
|
17
21
|
const tools_base_1 = require("./tools-base");
|
|
18
22
|
const tools_teams_1 = require("./tools-teams");
|
|
19
23
|
const tools_bluebubbles_1 = require("./tools-bluebubbles");
|
|
@@ -26,12 +30,14 @@ const identity_1 = require("../heart/identity");
|
|
|
26
30
|
const store_1 = require("../commerce/store");
|
|
27
31
|
const tools_surface_1 = require("./tools-surface");
|
|
28
32
|
const mcp_tools_1 = require("./mcp-tools");
|
|
33
|
+
const tool_arguments_1 = require("./tool-arguments");
|
|
29
34
|
const tools_voice_1 = require("./tools-voice");
|
|
30
35
|
const shell_sessions_1 = require("./shell-sessions");
|
|
31
36
|
const tools_unraid_1 = require("./tools-unraid");
|
|
32
37
|
const tools_flow_1 = require("./tools-flow");
|
|
33
38
|
const tools_steward_policy_1 = require("./tools-steward-policy");
|
|
34
39
|
const steward_policy_1 = require("../heart/steward-policy");
|
|
40
|
+
const tool_approval_1 = require("../heart/tool-approval");
|
|
35
41
|
function safeGetAgentRoot() {
|
|
36
42
|
try {
|
|
37
43
|
return (0, identity_1.getAgentRoot)();
|
|
@@ -40,14 +46,6 @@ function safeGetAgentRoot() {
|
|
|
40
46
|
return undefined;
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
|
-
function isSanctuaryAgent() {
|
|
44
|
-
try {
|
|
45
|
-
return (0, identity_1.getAgentName)() === "sanctuary";
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
49
|
const SANCTUARY_RELATIONSHIP_BASE_TOOLS = new Set([
|
|
52
50
|
"external_event_disposition",
|
|
53
51
|
"query_active_work",
|
|
@@ -63,23 +61,10 @@ const SANCTUARY_RELATIONSHIP_BASE_TOOLS = new Set([
|
|
|
63
61
|
"describe_image",
|
|
64
62
|
"send_message",
|
|
65
63
|
]);
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"query_cares",
|
|
71
|
-
"care_manage",
|
|
72
|
-
"await_condition",
|
|
73
|
-
"resolve_await",
|
|
74
|
-
"cancel_await",
|
|
75
|
-
"list_recent_attachments",
|
|
76
|
-
"materialize_attachment",
|
|
77
|
-
"describe_image",
|
|
78
|
-
...tools_unraid_1.unraidToolDefinitions.map((definition) => definition.tool.function.name),
|
|
79
|
-
"steward_policy_manage",
|
|
80
|
-
"ponder",
|
|
81
|
-
"settle",
|
|
82
|
-
"speak",
|
|
64
|
+
exports.SANCTUARY_OWNER_ADDITIONS = new Set([
|
|
65
|
+
"shell", "shell_status", "shell_tail", "read_file", "write_file", "edit_file", "glob", "grep",
|
|
66
|
+
"web_search", "search_facts", "consult_diary", "consult_notes", "get_friend_note",
|
|
67
|
+
"session_summary", "query_session", "set_reasoning_effort", "restart_runtime", "revive_sense",
|
|
83
68
|
]);
|
|
84
69
|
// Re-export types and constants used by the rest of the codebase
|
|
85
70
|
var tools_base_2 = require("./tools-base");
|
|
@@ -93,44 +78,26 @@ Object.defineProperty(exports, "speakTool", { enumerable: true, get: function ()
|
|
|
93
78
|
var tools_surface_2 = require("./tools-surface");
|
|
94
79
|
Object.defineProperty(exports, "surfaceToolDef", { enumerable: true, get: function () { return tools_surface_2.surfaceToolDef; } });
|
|
95
80
|
// All tool definitions in a single registry
|
|
96
|
-
const
|
|
81
|
+
const additionalDefinitions = [...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, ...tools_bundle_1.bundleToolDefinitions, ...tools_voice_1.voiceToolDefinitions, ...tools_unraid_1.unraidToolDefinitions, tools_steward_policy_1.stewardPolicyToolDefinition, tools_surface_1.surfaceToolDefinition];
|
|
97
82
|
const COMMERCE_AUTHORITY_TOOLS = new Set(["stripe_create_card", "flight_hold", "flight_book"]);
|
|
98
|
-
|
|
99
|
-
// Kept separate from allDefinitions so execTool can find them.
|
|
100
|
-
let mcpDefinitions = [];
|
|
101
|
-
/** Exported for testing — reset the MCP definitions cache. */
|
|
102
|
-
function resetMcpDefinitions() {
|
|
103
|
-
mcpDefinitions = [];
|
|
104
|
-
}
|
|
105
|
-
function baseToolsForCapabilities() {
|
|
106
|
-
// Use baseToolDefinitions at call time so dynamically-added tools are included
|
|
107
|
-
return tools_base_1.baseToolDefinitions.map((d) => d.tool);
|
|
83
|
+
class ToolSelectionError extends Error {
|
|
108
84
|
}
|
|
109
|
-
|
|
85
|
+
exports.ToolSelectionError = ToolSelectionError;
|
|
86
|
+
function assertUniqueToolSchemas(tools) {
|
|
110
87
|
const seen = new Set();
|
|
111
|
-
|
|
112
|
-
if (seen.has(tool.function.name))
|
|
113
|
-
|
|
88
|
+
for (const tool of tools) {
|
|
89
|
+
if (seen.has(tool.function.name)) {
|
|
90
|
+
(0, runtime_1.emitNervesEvent)({
|
|
91
|
+
level: "error", component: "tools", event: "tool.selection_collision",
|
|
92
|
+
message: "tool selection contains an ambiguous name", meta: { name: tool.function.name },
|
|
93
|
+
});
|
|
94
|
+
throw new ToolSelectionError(`tool selection collision: ${tool.function.name}`);
|
|
95
|
+
}
|
|
114
96
|
seen.add(tool.function.name);
|
|
115
|
-
|
|
116
|
-
});
|
|
97
|
+
}
|
|
117
98
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
* Durable relationship profiles can only reduce this surface; they cannot
|
|
121
|
-
* introduce an arbitrary base tool by naming it in configuration.
|
|
122
|
-
*/
|
|
123
|
-
function getSanctuaryRelationshipTools(advertisedToolNames) {
|
|
124
|
-
const advertised = new Set(advertisedToolNames);
|
|
125
|
-
return uniqueToolSchemas([
|
|
126
|
-
...tools_base_1.baseToolDefinitions.filter((definition) => SANCTUARY_RELATIONSHIP_BASE_TOOLS.has(definition.tool.function.name)).map((definition) => definition.tool),
|
|
127
|
-
...tools_unraid_1.unraidToolDefinitions.map((definition) => definition.tool),
|
|
128
|
-
tools_steward_policy_1.stewardPolicyToolDefinition.tool,
|
|
129
|
-
tools_flow_1.ponderTool,
|
|
130
|
-
tools_flow_1.restTool,
|
|
131
|
-
tools_flow_1.settleTool,
|
|
132
|
-
tools_flow_1.speakTool,
|
|
133
|
-
]).filter((tool) => advertised.has(tool.function.name));
|
|
99
|
+
function toolSelectionSchemas(selection) {
|
|
100
|
+
return [...selection.ordinary.map((definition) => definition.tool), ...selection.engine];
|
|
134
101
|
}
|
|
135
102
|
// Apply a single tool preference to a tool schema, returning a new object.
|
|
136
103
|
function applyPreference(tool, pref) {
|
|
@@ -142,78 +109,96 @@ function applyPreference(tool, pref) {
|
|
|
142
109
|
},
|
|
143
110
|
};
|
|
144
111
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
? tools_bluebubbles_1.bluebubblesToolDefinitions.map((d) => d.tool)
|
|
168
|
-
: [];
|
|
169
|
-
const voiceTools = capabilities?.channel === "voice"
|
|
170
|
-
? tools_voice_1.voiceToolDefinitions.map((d) => d.tool)
|
|
171
|
-
: [];
|
|
172
|
-
let result;
|
|
173
|
-
if (!capabilities || capabilities.availableIntegrations.length === 0) {
|
|
174
|
-
result = [...baseTools, ...bluebubblesTools, ...voiceTools];
|
|
112
|
+
function selectToolsForChannel(capabilities, toolPreferences, _context, providerCapabilities, mcpManager, _chatModel, context) {
|
|
113
|
+
const relationship = context?.relationshipAuthorization;
|
|
114
|
+
const profileId = relationship?.profileId;
|
|
115
|
+
const sanctuary = (context?.agentName === "sanctuary" && (relationship !== undefined || capabilities?.channel === "telegram"))
|
|
116
|
+
|| profileId?.startsWith("sanctuary-") === true;
|
|
117
|
+
const owner = profileId === "sanctuary-owner";
|
|
118
|
+
const knownSanctuaryProfile = relationship !== undefined && (owner || profileId === "sanctuary-household" || profileId === "sanctuary-event");
|
|
119
|
+
const mcp = mcpManager ? (0, mcp_tools_1.mcpToolsAsDefinitions)(mcpManager) : [];
|
|
120
|
+
const native = [...tools_base_1.baseToolDefinitions, ...additionalDefinitions];
|
|
121
|
+
assertUniqueToolSchemas([
|
|
122
|
+
...native.map((definition) => definition.tool), ...mcp.map((definition) => definition.tool),
|
|
123
|
+
tools_flow_1.ponderTool, tools_flow_1.restTool, tools_flow_1.observeTool, tools_flow_1.settleTool, tools_flow_1.speakTool,
|
|
124
|
+
]);
|
|
125
|
+
let ordinary;
|
|
126
|
+
if (sanctuary) {
|
|
127
|
+
ordinary = knownSanctuaryProfile
|
|
128
|
+
? [
|
|
129
|
+
...tools_base_1.baseToolDefinitions.filter((definition) => SANCTUARY_RELATIONSHIP_BASE_TOOLS.has(definition.tool.function.name)
|
|
130
|
+
|| (owner && exports.SANCTUARY_OWNER_ADDITIONS.has(definition.tool.function.name))),
|
|
131
|
+
...tools_unraid_1.unraidToolDefinitions, tools_steward_policy_1.stewardPolicyToolDefinition, ...(owner ? mcp : []),
|
|
132
|
+
]
|
|
133
|
+
: [];
|
|
175
134
|
}
|
|
176
135
|
else {
|
|
177
|
-
const available = new Set(capabilities
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
136
|
+
const available = new Set(capabilities?.availableIntegrations ?? []);
|
|
137
|
+
const integrations = available.size === 0 ? [] : [...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions]
|
|
138
|
+
.filter((definition) => definition.integration ? available.has(definition.integration) : capabilities?.channel === "teams")
|
|
139
|
+
.map((definition) => {
|
|
140
|
+
const preference = definition.integration ? toolPreferences?.[definition.integration] : undefined;
|
|
141
|
+
return preference ? { ...definition, tool: applyPreference(definition.tool, preference) } : definition;
|
|
142
|
+
});
|
|
143
|
+
ordinary = [
|
|
144
|
+
...tools_base_1.baseToolDefinitions,
|
|
145
|
+
...(capabilities?.channel === "bluebubbles" ? tools_bluebubbles_1.bluebubblesToolDefinitions : []),
|
|
146
|
+
...(capabilities?.channel === "voice" ? tools_voice_1.voiceToolDefinitions : []),
|
|
147
|
+
...integrations, ...mcp,
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
const inner = capabilities?.channel === "inner";
|
|
151
|
+
const habit = context?.habitSession;
|
|
152
|
+
if (inner) {
|
|
153
|
+
const canSend = habit?.toolPolicy.outwardMessagingAllowed === true && habit.toolPolicy.grantedTools.includes("send_message");
|
|
154
|
+
ordinary = ordinary.filter((definition) => canSend || definition.tool.function.name !== "send_message");
|
|
155
|
+
if (!habit || (habit.toolPolicy.outwardMessagingAllowed && habit.toolPolicy.grantedTools.includes("surface"))) {
|
|
156
|
+
ordinary.push(tools_surface_1.surfaceToolDefinition);
|
|
183
157
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
158
|
+
}
|
|
159
|
+
ordinary = ordinary.filter((definition) => (!relationship || relationship.advertisedToolNames.includes(definition.tool.function.name))
|
|
160
|
+
&& (!definition.requiredCapability || providerCapabilities?.has(definition.requiredCapability) === true));
|
|
161
|
+
const engine = [
|
|
162
|
+
...(context?.noSend ? [] : [tools_flow_1.ponderTool]),
|
|
163
|
+
...(inner ? [tools_flow_1.restTool] : [tools_flow_1.observeTool, tools_flow_1.settleTool]),
|
|
164
|
+
...(capabilities?.chatStyle ? [tools_flow_1.speakTool] : []),
|
|
165
|
+
].filter((tool) => (!sanctuary || (knownSanctuaryProfile && tool.function.name !== "observe"))
|
|
166
|
+
&& (!relationship || relationship.advertisedToolNames.includes(tool.function.name)));
|
|
167
|
+
return Object.freeze({
|
|
168
|
+
ordinary: Object.freeze(ordinary.map((definition) => Object.freeze({
|
|
169
|
+
...definition,
|
|
170
|
+
tool: (0, tool_arguments_1.freezeToolValue)(structuredClone(definition.tool)),
|
|
171
|
+
...(definition.riskProfile && typeof definition.riskProfile !== "function"
|
|
172
|
+
? { riskProfile: (0, tool_arguments_1.freezeToolValue)(structuredClone(definition.riskProfile)) } : {}),
|
|
173
|
+
...(definition.terminalProjection ? { terminalProjection: (0, tool_arguments_1.freezeToolValue)(structuredClone(definition.terminalProjection)) } : {}),
|
|
174
|
+
}))),
|
|
175
|
+
engine: Object.freeze(engine.map((tool) => (0, tool_arguments_1.freezeToolValue)(tool))),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function getToolsForChannel(...args) {
|
|
179
|
+
return toolSelectionSchemas(selectToolsForChannel(...args));
|
|
180
|
+
}
|
|
181
|
+
function reduceToolSelection(selection, requested) {
|
|
182
|
+
assertUniqueToolSchemas(requested);
|
|
183
|
+
const canonical = toolSelectionSchemas(selection);
|
|
184
|
+
for (const tool of requested) {
|
|
185
|
+
const match = canonical.find((candidate) => candidate.function.name === tool.function.name);
|
|
186
|
+
if (!match || !(0, node_util_1.isDeepStrictEqual)(match, tool)) {
|
|
187
|
+
(0, runtime_1.emitNervesEvent)({
|
|
188
|
+
level: "warn", component: "tools", event: "tool.selection_rejected",
|
|
189
|
+
message: "tool override is not an exact canonical reduction", meta: { name: tool.function.name },
|
|
195
190
|
});
|
|
196
|
-
|
|
191
|
+
throw new ToolSelectionError(`tool selection is not a canonical reduction: ${tool.function.name}`);
|
|
197
192
|
}
|
|
198
193
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
mcpDefinitions = (0, mcp_tools_1.mcpToolsAsDefinitions)(mcpManager);
|
|
202
|
-
const mcpSchemas = mcpDefinitions.map((d) => d.tool);
|
|
203
|
-
result = [...result, ...mcpSchemas];
|
|
204
|
-
}
|
|
205
|
-
return filterByCapability(result, providerCapabilities);
|
|
194
|
+
const names = new Set(requested.map((tool) => tool.function.name));
|
|
195
|
+
return Object.freeze({ ordinary: Object.freeze(selection.ordinary.filter((definition) => names.has(definition.tool.function.name))), engine: selection.engine });
|
|
206
196
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// runtime extensions cannot leave execution metadata behind a stale snapshot.
|
|
210
|
-
function resolveToolDefinition(toolName) {
|
|
211
|
-
return tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === toolName)
|
|
212
|
-
?? allDefinitions.find((d) => d.tool.function.name === toolName)
|
|
213
|
-
?? mcpDefinitions.find((d) => d.tool.function.name === toolName);
|
|
197
|
+
function resolveToolDefinition(toolName, selection) {
|
|
198
|
+
return (selection?.ordinary ?? [...tools_base_1.baseToolDefinitions, ...additionalDefinitions]).find((definition) => definition.tool.function.name === toolName);
|
|
214
199
|
}
|
|
215
|
-
function approvalPolicyForToolName(name, args) {
|
|
216
|
-
return resolveToolDefinition(name)?.approvalPolicy?.(args) ?? { kind: "not_required" };
|
|
200
|
+
function approvalPolicyForToolName(name, args, selection) {
|
|
201
|
+
return resolveToolDefinition(name, selection)?.approvalPolicy?.(args) ?? { kind: "not_required" };
|
|
217
202
|
}
|
|
218
203
|
async function routineActionInvocation(name, args, ctx) {
|
|
219
204
|
const target = typeof args.container === "string" ? args.container : "";
|
|
@@ -236,7 +221,7 @@ async function routineActionInvocation(name, args, ctx) {
|
|
|
236
221
|
return decision.allowed ? { key, target, expectedPolicyVersion: decision.policyVersion } : null;
|
|
237
222
|
}
|
|
238
223
|
async function classifyApprovalForInvocation(name, args, ctx) {
|
|
239
|
-
const fallback = approvalPolicyForToolName(name, args);
|
|
224
|
+
const fallback = approvalPolicyForToolName(name, args, ctx?.toolSelection);
|
|
240
225
|
if (name !== "unraid_restart_container" || fallback.kind !== "required")
|
|
241
226
|
return { policy: fallback };
|
|
242
227
|
const routineActionSelection = await routineActionInvocation(name, args, ctx);
|
|
@@ -246,9 +231,6 @@ async function approvalPolicyForInvocation(name, args, ctx) {
|
|
|
246
231
|
return (await classifyApprovalForInvocation(name, args, ctx)).policy;
|
|
247
232
|
}
|
|
248
233
|
const findDefinition = resolveToolDefinition;
|
|
249
|
-
function normalizeGuardArgs(_name, args) {
|
|
250
|
-
return args;
|
|
251
|
-
}
|
|
252
234
|
const READ_ONLY_OURO_SHELL_COMMANDS = new Set([
|
|
253
235
|
"help",
|
|
254
236
|
"-h",
|
|
@@ -321,8 +303,8 @@ function riskProfileForTool(def, name, args) {
|
|
|
321
303
|
return def.riskProfile(args);
|
|
322
304
|
return def.riskProfile ?? { mutates: "none", risk: "low" };
|
|
323
305
|
}
|
|
324
|
-
function riskProfileForToolName(name, args) {
|
|
325
|
-
const def = findDefinition(name);
|
|
306
|
+
function riskProfileForToolName(name, args, selection) {
|
|
307
|
+
const def = findDefinition(name, selection);
|
|
326
308
|
return def ? riskProfileForTool(def, name, args) : null;
|
|
327
309
|
}
|
|
328
310
|
function orientationHoldMessage(name, profile, reason) {
|
|
@@ -337,73 +319,105 @@ function orientationPolicyBlocks(profile, blockedMutationKinds) {
|
|
|
337
319
|
return false;
|
|
338
320
|
return mutationKindsFor(profile).some((kind) => blockedMutationKinds.includes(kind));
|
|
339
321
|
}
|
|
340
|
-
async function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
322
|
+
async function preflightToolCall(name, args, ctx) {
|
|
323
|
+
try {
|
|
324
|
+
const selection = ctx?.toolSelection;
|
|
325
|
+
const def = findDefinition(name, selection);
|
|
326
|
+
const schema = def?.tool ?? selection?.engine.find((tool) => tool.function.name === name);
|
|
327
|
+
if (!schema) {
|
|
328
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", event: "tool.error", component: "tools", message: "unknown or unselected tool requested", meta: { name } });
|
|
329
|
+
return { kind: "rejected_before_handler", text: `unknown: ${name}` };
|
|
330
|
+
}
|
|
331
|
+
if (selection || ctx?.relationshipAuthorization) {
|
|
332
|
+
const validation = (0, tool_arguments_1.validateAdvertisedToolArguments)(JSON.stringify(args), schema.function.parameters ?? {});
|
|
333
|
+
if (!validation.ok)
|
|
334
|
+
return { kind: "rejected_before_handler", text: `invalid tool arguments: ${validation.reason}` };
|
|
335
|
+
}
|
|
336
|
+
(0, tool_arguments_1.assertRelationshipToolOwner)(ctx);
|
|
337
|
+
const relationship = ctx?.relationshipAuthorization;
|
|
338
|
+
const decision = await relationship?.authorizeTool(name, args);
|
|
339
|
+
if (decision && (!decision.allowed || !relationship.advertisedToolNames.includes(name)
|
|
340
|
+
|| (decision.profileId !== undefined && decision.profileId !== relationship.profileId))) {
|
|
341
|
+
const reason = decision.allowed ? "current relationship selection changed" : decision.reason;
|
|
342
|
+
(0, runtime_1.emitNervesEvent)({
|
|
343
|
+
level: "warn", event: "tool.relationship_authorization_block", component: "tools",
|
|
344
|
+
message: "relationship authorization blocked tool execution", meta: { name, reason },
|
|
345
|
+
});
|
|
346
|
+
return { kind: "rejected_before_handler", text: `relationship authorization required: ${reason}` };
|
|
347
|
+
}
|
|
348
|
+
const currentSelection = ctx?.selectCurrentTools?.()
|
|
349
|
+
?? (relationship?.profileId?.startsWith("sanctuary-") || (relationship && ctx?.agentName === "sanctuary")
|
|
350
|
+
? selectToolsForChannel(undefined, undefined, undefined, undefined, undefined, undefined, ctx)
|
|
351
|
+
: undefined);
|
|
352
|
+
const current = currentSelection ? findDefinition(name, currentSelection) : def?.mcpBinding ? def : findDefinition(name);
|
|
353
|
+
const currentSchema = current?.tool ?? currentSelection?.engine.find((tool) => tool.function.name === name)
|
|
354
|
+
?? (!currentSelection ? selection?.engine.find((tool) => tool.function.name === name) : undefined);
|
|
355
|
+
if (!currentSchema || !(0, node_util_1.isDeepStrictEqual)(schema, currentSchema)
|
|
356
|
+
|| (def && (!current || (def.mcpBinding
|
|
357
|
+
? !current.mcpBinding || current.mcpBinding.manager !== def.mcpBinding.manager || !(0, node_util_1.isDeepStrictEqual)(current.mcpBinding, def.mcpBinding)
|
|
358
|
+
: current.handler !== def.handler)))) {
|
|
359
|
+
(0, runtime_1.emitNervesEvent)({
|
|
360
|
+
level: "warn", event: "tool.selection_rejected", component: "tools",
|
|
361
|
+
message: "selected tool is no longer current", meta: { name },
|
|
362
|
+
});
|
|
363
|
+
return { kind: "rejected_before_handler", text: `rejected: ${name} changed or is no longer available in the current tool selection` };
|
|
364
|
+
}
|
|
365
|
+
if (def?.mcpBinding) {
|
|
366
|
+
if (!ctx?.agentName || !ctx.agentRoot)
|
|
367
|
+
throw new mcp_tools_1.McpCallRejectedError("MCP requires an explicit owner");
|
|
368
|
+
await def.mcpBinding.manager.validateToolBinding(def.mcpBinding, { agentName: ctx.agentName, agentRoot: ctx.agentRoot });
|
|
369
|
+
}
|
|
370
|
+
if (!def)
|
|
371
|
+
return { kind: "ready" };
|
|
372
|
+
const risk = riskProfileForTool(def, name, args);
|
|
373
|
+
const orientationPolicy = ctx?.orientationFrame?.actionPolicy;
|
|
374
|
+
if (orientationPolicy?.mode === "correction_hold" && orientationPolicyBlocks(risk, orientationPolicy.blockedMutationKinds)) {
|
|
375
|
+
(0, runtime_1.emitNervesEvent)({
|
|
376
|
+
level: "warn", event: "tool.orientation_hold_block", component: "tools",
|
|
377
|
+
message: "orientation hold blocked high-risk tool execution",
|
|
378
|
+
meta: { name, mutates: mutationKindsFor(risk), reason: orientationPolicy.reason },
|
|
379
|
+
});
|
|
380
|
+
return { kind: "rejected_before_handler", text: orientationHoldMessage(name, risk, orientationPolicy.reason) };
|
|
381
|
+
}
|
|
382
|
+
const group = ctx?.context && "isGroupChat" in ctx.context ? ctx.context.isGroupChat : undefined;
|
|
383
|
+
const guardContext = {
|
|
384
|
+
readPaths: tools_base_1.editFileReadTracker, trustLevel: ctx?.context?.friend?.trustLevel,
|
|
385
|
+
agentRoot: ctx?.agentRoot ?? safeGetAgentRoot(), friendId: ctx?.context?.friend?.id,
|
|
386
|
+
...(def?.mcpServer ? { mcpServerName: def.mcpServer } : {}),
|
|
387
|
+
...(typeof group === "boolean" ? { isGroupChat: group } : {}),
|
|
388
|
+
};
|
|
389
|
+
const guard = (0, guardrails_1.guardInvocation)(name, args, guardContext);
|
|
390
|
+
if (!guard.allowed) {
|
|
391
|
+
(0, runtime_1.emitNervesEvent)({
|
|
392
|
+
level: "warn", event: "tool.guardrail_block", component: "tools",
|
|
393
|
+
message: "guardrail blocked tool execution", meta: { name, reason: guard.reason },
|
|
394
|
+
});
|
|
395
|
+
return { kind: "rejected_before_handler", text: guard.reason };
|
|
396
|
+
}
|
|
397
|
+
return { kind: "ready", definition: def, agentRoot: guardContext.agentRoot, friendId: guardContext.friendId };
|
|
384
398
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const guardContext = {
|
|
388
|
-
readPaths: tools_base_1.editFileReadTracker,
|
|
389
|
-
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
390
|
-
agentRoot: ctx?.agentRoot ?? safeGetAgentRoot(),
|
|
391
|
-
friendId: ctx?.context?.friend?.id,
|
|
392
|
-
...(mcpDef?.mcpServer ? { mcpServerName: mcpDef.mcpServer } : {}),
|
|
393
|
-
...(ctx?.context?.isGroupChat !== undefined ? { isGroupChat: (ctx?.context).isGroupChat } : {}),
|
|
394
|
-
};
|
|
395
|
-
const guardArgs = normalizeGuardArgs(name, args);
|
|
396
|
-
const guardResult = (0, guardrails_1.guardInvocation)(name, guardArgs, guardContext);
|
|
397
|
-
if (!guardResult.allowed) {
|
|
399
|
+
catch (error) {
|
|
400
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
398
401
|
(0, runtime_1.emitNervesEvent)({
|
|
399
|
-
level: "warn",
|
|
400
|
-
|
|
401
|
-
component: "tools",
|
|
402
|
-
message: "guardrail blocked tool execution",
|
|
403
|
-
meta: { name, reason: guardResult.reason },
|
|
402
|
+
level: "warn", event: "tool.selection_rejected", component: "tools",
|
|
403
|
+
message: "tool preflight failed before execution", meta: { name, reason: text.slice(0, 2000) },
|
|
404
404
|
});
|
|
405
|
-
return
|
|
405
|
+
return { kind: "rejected_before_handler", text: `rejected: ${text.slice(0, 2000)}`, error };
|
|
406
406
|
}
|
|
407
|
+
}
|
|
408
|
+
async function executeTool(name, args, ctx, executor) {
|
|
409
|
+
(0, runtime_1.emitNervesEvent)({
|
|
410
|
+
event: "tool.start", component: "tools", message: "tool execution started",
|
|
411
|
+
meta: { name, ...(name === "shell" && args.command ? { command: args.command } : {}) },
|
|
412
|
+
});
|
|
413
|
+
const prepared = await preflightToolCall(name, args, ctx);
|
|
414
|
+
if (prepared.kind !== "ready")
|
|
415
|
+
return prepared;
|
|
416
|
+
const def = prepared.definition;
|
|
417
|
+
if (!def)
|
|
418
|
+
return { kind: "rejected_before_handler", text: `rejected: ${name} is owned by the engine` };
|
|
419
|
+
const guardContext = prepared;
|
|
420
|
+
const guardArgs = args;
|
|
407
421
|
const commerceReservation = COMMERCE_AUTHORITY_TOOLS.has(name) && guardContext.agentRoot
|
|
408
422
|
? (0, store_1.reserveCommerceAuthority)({
|
|
409
423
|
agentRoot: guardContext.agentRoot,
|
|
@@ -421,7 +435,7 @@ async function execTool(name, args, ctx) {
|
|
|
421
435
|
message: "guardrail blocked tool execution",
|
|
422
436
|
meta: { name, reason: commerceReservation.reason },
|
|
423
437
|
});
|
|
424
|
-
return `commerce authority required: ${commerceReservation.reason}
|
|
438
|
+
return { kind: "rejected_before_handler", text: `commerce authority required: ${commerceReservation.reason}` };
|
|
425
439
|
}
|
|
426
440
|
const authorizedContext = ctx;
|
|
427
441
|
const toolContext = commerceReservation?.ok
|
|
@@ -435,14 +449,14 @@ async function execTool(name, args, ctx) {
|
|
|
435
449
|
}
|
|
436
450
|
: authorizedContext;
|
|
437
451
|
try {
|
|
438
|
-
const result = await def.handler(args, toolContext);
|
|
452
|
+
const result = await (executor ? executor(name, args, toolContext) : def.handler(args, toolContext));
|
|
439
453
|
(0, runtime_1.emitNervesEvent)({
|
|
440
454
|
event: "tool.end",
|
|
441
455
|
component: "tools",
|
|
442
456
|
message: "tool execution finished",
|
|
443
457
|
meta: { name, success: true },
|
|
444
458
|
});
|
|
445
|
-
return result;
|
|
459
|
+
return { kind: "handler_succeeded", text: result };
|
|
446
460
|
}
|
|
447
461
|
catch (error) {
|
|
448
462
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -452,7 +466,14 @@ async function execTool(name, args, ctx) {
|
|
|
452
466
|
message: error instanceof Error ? error.message : String(error),
|
|
453
467
|
meta: { name },
|
|
454
468
|
});
|
|
455
|
-
|
|
469
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
470
|
+
return {
|
|
471
|
+
kind: error instanceof mcp_tools_1.McpCallRejectedError ? "rejected_before_handler"
|
|
472
|
+
: error instanceof mcp_tools_1.McpToolExecutionError ? error.kind
|
|
473
|
+
: error instanceof tool_approval_1.ApprovalExecutionFailedError ? "handler_failed"
|
|
474
|
+
: riskProfileForTool(def, name, args).risk === "high" ? "handler_indeterminate" : "handler_failed",
|
|
475
|
+
text: text.slice(0, 2000), error,
|
|
476
|
+
};
|
|
456
477
|
}
|
|
457
478
|
finally {
|
|
458
479
|
if (commerceReservation?.ok && guardContext.agentRoot) {
|
|
@@ -471,6 +492,13 @@ async function execTool(name, args, ctx) {
|
|
|
471
492
|
}
|
|
472
493
|
}
|
|
473
494
|
}
|
|
495
|
+
async function execTool(name, args, ctx, executor) {
|
|
496
|
+
// Keep the legacy handler type without coercing schema-validated JSON values.
|
|
497
|
+
const result = await executeTool(name, args, ctx, executor);
|
|
498
|
+
if ("error" in result && !(result.error instanceof mcp_tools_1.McpToolExecutionError))
|
|
499
|
+
throw result.error;
|
|
500
|
+
return result.text;
|
|
501
|
+
}
|
|
474
502
|
function summarizeKeyValues(args, keys, maxValueLength = 60) {
|
|
475
503
|
const parts = [];
|
|
476
504
|
for (const key of keys) {
|
|
@@ -491,8 +519,8 @@ function summarizeUnknownArgs(args) {
|
|
|
491
519
|
return "";
|
|
492
520
|
return summarizeKeyValues(args, keys);
|
|
493
521
|
}
|
|
494
|
-
function summarizeArgs(name, args) {
|
|
495
|
-
const def = findDefinition(name);
|
|
522
|
+
function summarizeArgs(name, args, selection) {
|
|
523
|
+
const def = findDefinition(name, selection);
|
|
496
524
|
if (def && def.summaryKeys !== undefined) {
|
|
497
525
|
return summarizeKeyValues(args, def.summaryKeys);
|
|
498
526
|
}
|
|
@@ -503,12 +531,11 @@ function summarizeArgs(name, args) {
|
|
|
503
531
|
* For recognized tools, includes result-derived info (diff stats, exit codes, etc.).
|
|
504
532
|
* Falls back to arg-based summary for unrecognized tools.
|
|
505
533
|
*/
|
|
506
|
-
function buildToolResultSummary(name, args, _result, success) {
|
|
507
|
-
/* v8 ignore start -- branches: ?? fallbacks and ternaries in tool summary formatting @preserve */
|
|
534
|
+
function buildToolResultSummary(name, args, _result, success, selection) {
|
|
508
535
|
switch (name) {
|
|
509
536
|
case "edit_file": {
|
|
510
537
|
if (!success)
|
|
511
|
-
return summarizeArgs(name, args);
|
|
538
|
+
return summarizeArgs(name, args, selection);
|
|
512
539
|
const addedLines = (args.new_string ?? "").split("\n").length;
|
|
513
540
|
const removedLines = (args.old_string ?? "").split("\n").length;
|
|
514
541
|
return `+${addedLines} -${removedLines} lines in ${args.path ?? "unknown"}`;
|
|
@@ -532,7 +559,6 @@ function buildToolResultSummary(name, args, _result, success) {
|
|
|
532
559
|
return `${taskRef} -> ${status}`;
|
|
533
560
|
}
|
|
534
561
|
default:
|
|
535
|
-
return summarizeArgs(name, args);
|
|
562
|
+
return summarizeArgs(name, args, selection);
|
|
536
563
|
}
|
|
537
|
-
/* v8 ignore stop */
|
|
538
564
|
}
|
|
@@ -1571,9 +1571,10 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1571
1571
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "superseded" };
|
|
1572
1572
|
}
|
|
1573
1573
|
options.lifecycleSignal?.throwIfAborted();
|
|
1574
|
+
const mcpOwner = { agentName, agentRoot: (0, identity_1.getAgentRoot)(agentName) };
|
|
1574
1575
|
const liveTurnId = (0, active_turns_1.beginBlueBubblesActiveTurn)(agentName, event);
|
|
1575
1576
|
activeTurnId = liveTurnId;
|
|
1576
|
-
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
1577
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)(mcpOwner) ?? undefined;
|
|
1577
1578
|
if (event.chat.isGroup) {
|
|
1578
1579
|
await (0, friends_1.upsertGroupContextParticipants)({
|
|
1579
1580
|
store,
|
|
@@ -1847,6 +1848,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1847
1848
|
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
1848
1849
|
signin: async () => undefined,
|
|
1849
1850
|
...safeToolContext,
|
|
1851
|
+
...mcpOwner,
|
|
1850
1852
|
summarize,
|
|
1851
1853
|
bluebubblesReplyTarget: {
|
|
1852
1854
|
setSelection: (selection) => replyTarget.setSelection(selection),
|
|
@@ -1880,7 +1882,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1880
1882
|
orientationFrame,
|
|
1881
1883
|
toolContext: {
|
|
1882
1884
|
signin: async () => undefined,
|
|
1883
|
-
|
|
1885
|
+
...mcpOwner,
|
|
1884
1886
|
currentIngressEvidence: options.currentIngressEvidence,
|
|
1885
1887
|
},
|
|
1886
1888
|
},
|