@lobu/worker 6.1.1 → 7.0.0

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 (82) hide show
  1. package/dist/embedded/just-bash-bootstrap.d.ts.map +1 -1
  2. package/dist/embedded/just-bash-bootstrap.js +26 -2
  3. package/dist/embedded/just-bash-bootstrap.js.map +1 -1
  4. package/dist/gateway/gateway-integration.js +4 -4
  5. package/dist/gateway/gateway-integration.js.map +1 -1
  6. package/dist/gateway/message-batcher.d.ts.map +1 -1
  7. package/dist/gateway/message-batcher.js +3 -5
  8. package/dist/gateway/message-batcher.js.map +1 -1
  9. package/dist/gateway/sse-client.d.ts +1 -0
  10. package/dist/gateway/sse-client.d.ts.map +1 -1
  11. package/dist/gateway/sse-client.js +8 -0
  12. package/dist/gateway/sse-client.js.map +1 -1
  13. package/dist/openclaw/worker.d.ts +0 -1
  14. package/dist/openclaw/worker.d.ts.map +1 -1
  15. package/dist/openclaw/worker.js +18 -75
  16. package/dist/openclaw/worker.js.map +1 -1
  17. package/dist/shared/tool-implementations.d.ts.map +1 -1
  18. package/dist/shared/tool-implementations.js +37 -13
  19. package/dist/shared/tool-implementations.js.map +1 -1
  20. package/package.json +14 -4
  21. package/src/__tests__/audio-provider-suggestions.test.ts +199 -0
  22. package/src/__tests__/custom-tools.test.ts +92 -0
  23. package/src/__tests__/embedded-just-bash-bootstrap.test.ts +128 -0
  24. package/src/__tests__/embedded-mcp-cli-bash.test.ts +179 -0
  25. package/src/__tests__/embedded-tools.test.ts +744 -0
  26. package/src/__tests__/exec-sandbox-extra.test.ts +0 -0
  27. package/src/__tests__/exec-sandbox.test.ts +550 -0
  28. package/src/__tests__/generated-media.test.ts +142 -0
  29. package/src/__tests__/instructions.test.ts +60 -0
  30. package/src/__tests__/mcp-cli-commands-extra.test.ts +478 -0
  31. package/src/__tests__/mcp-cli-commands.test.ts +383 -0
  32. package/src/__tests__/mcp-tool-call.test.ts +423 -0
  33. package/src/__tests__/memory-flush-harden.test.ts +367 -0
  34. package/src/__tests__/memory-flush-runtime.test.ts +138 -0
  35. package/src/__tests__/memory-flush.test.ts +64 -0
  36. package/src/__tests__/message-batcher.test.ts +247 -0
  37. package/src/__tests__/model-resolver-harden.test.ts +197 -0
  38. package/src/__tests__/model-resolver.test.ts +156 -0
  39. package/src/__tests__/processor-harden.test.ts +269 -0
  40. package/src/__tests__/processor.test.ts +225 -0
  41. package/src/__tests__/replace-base-prompt-identity.test.ts +41 -0
  42. package/src/__tests__/sandbox-leak-harden.test.ts +200 -0
  43. package/src/__tests__/sandbox-leak.test.ts +167 -0
  44. package/src/__tests__/setup.ts +102 -0
  45. package/src/__tests__/sse-client-harden.test.ts +588 -0
  46. package/src/__tests__/sse-client.test.ts +90 -0
  47. package/src/__tests__/tool-implementations.test.ts +196 -0
  48. package/src/__tests__/tool-policy-edge-cases.test.ts +263 -0
  49. package/src/__tests__/tool-policy.test.ts +269 -0
  50. package/src/__tests__/worker.test.ts +89 -0
  51. package/src/core/error-handler.ts +62 -0
  52. package/src/core/project-scanner.ts +65 -0
  53. package/src/core/types.ts +128 -0
  54. package/src/core/workspace.ts +89 -0
  55. package/src/embedded/exec-sandbox.ts +372 -0
  56. package/src/embedded/just-bash-bootstrap.ts +543 -0
  57. package/src/embedded/mcp-cli-commands.ts +402 -0
  58. package/src/gateway/gateway-integration.ts +298 -0
  59. package/src/gateway/message-batcher.ts +123 -0
  60. package/src/gateway/sse-client.ts +951 -0
  61. package/src/gateway/types.ts +68 -0
  62. package/src/index.ts +141 -0
  63. package/src/instructions/builder.ts +45 -0
  64. package/src/instructions/providers.ts +27 -0
  65. package/src/modules/lifecycle.ts +92 -0
  66. package/src/openclaw/custom-tools.ts +315 -0
  67. package/src/openclaw/instructions.ts +36 -0
  68. package/src/openclaw/model-resolver.ts +150 -0
  69. package/src/openclaw/plugin-loader.ts +427 -0
  70. package/src/openclaw/processor.ts +198 -0
  71. package/src/openclaw/sandbox-leak.ts +105 -0
  72. package/src/openclaw/session-context.ts +320 -0
  73. package/src/openclaw/tool-policy.ts +248 -0
  74. package/src/openclaw/tools.ts +277 -0
  75. package/src/openclaw/worker.ts +1847 -0
  76. package/src/server.ts +334 -0
  77. package/src/shared/audio-provider-suggestions.ts +132 -0
  78. package/src/shared/processor-utils.ts +33 -0
  79. package/src/shared/provider-auth-hints.ts +68 -0
  80. package/src/shared/tool-display-config.ts +75 -0
  81. package/src/shared/tool-implementations.ts +940 -0
  82. package/src/shared/worker-env-keys.ts +8 -0
@@ -0,0 +1,248 @@
1
+ import type { ToolsConfig } from "@lobu/core";
2
+
3
+ export type BashCommandPolicy = {
4
+ allowAll: boolean;
5
+ allowPrefixes: string[];
6
+ denyPrefixes: string[];
7
+ };
8
+
9
+ type ToolPolicy = {
10
+ toolsConfig?: ToolsConfig;
11
+ allowedPatterns: string[];
12
+ deniedPatterns: string[];
13
+ strictMode: boolean;
14
+ bashPolicy: BashCommandPolicy;
15
+ };
16
+
17
+ const DEFAULT_PACKAGE_MANAGER_DENY_PREFIXES = [
18
+ "apt ",
19
+ "apt-get ",
20
+ "yum ",
21
+ "dnf ",
22
+ "apk ",
23
+ "pacman ",
24
+ "zypper ",
25
+ "brew ",
26
+ "nix-shell ",
27
+ "nix-env ",
28
+ "nix profile ",
29
+ "sudo apt ",
30
+ "sudo apt-get ",
31
+ "sudo yum ",
32
+ "sudo dnf ",
33
+ "sudo apk ",
34
+ "sudo pacman ",
35
+ "sudo zypper ",
36
+ "sudo brew ",
37
+ "sudo nix-shell ",
38
+ "sudo nix-env ",
39
+ "sudo nix profile ",
40
+ "pip install ",
41
+ "pip3 install ",
42
+ "uv pip install ",
43
+ "npm install ",
44
+ "npm i ",
45
+ "pnpm install ",
46
+ "pnpm add ",
47
+ "yarn install ",
48
+ "yarn add ",
49
+ "bun install ",
50
+ "bun add ",
51
+ "cargo install ",
52
+ "go install ",
53
+ "gem install ",
54
+ "poetry add ",
55
+ "composer require ",
56
+ ];
57
+
58
+ const DIRECT_PACKAGE_INSTALL_PATTERNS = [
59
+ /(^|[\s"'`;|&()])(?:sudo\s+)?(?:apt|apt-get|yum|dnf|apk|pacman|zypper|brew)\s+(?:install|upgrade|add)\b/i,
60
+ /(^|[\s"'`;|&()])(?:sudo\s+)?(?:nix-shell|nix-env)\b/i,
61
+ /(^|[\s"'`;|&()])(?:sudo\s+)?nix\s+profile\b/i,
62
+ /(^|[\s"'`;|&()])(?:pip|pip3)\s+install\b/i,
63
+ /(^|[\s"'`;|&()])uv\s+pip\s+install\b/i,
64
+ /(^|[\s"'`;|&()])npm\s+(?:install|i)\b/i,
65
+ /(^|[\s"'`;|&()])pnpm\s+(?:install|add)\b/i,
66
+ /(^|[\s"'`;|&()])yarn\s+(?:install|add|global\s+add)\b/i,
67
+ /(^|[\s"'`;|&()])bun\s+(?:install|add)\b/i,
68
+ /(^|[\s"'`;|&()])cargo\s+install\b/i,
69
+ /(^|[\s"'`;|&()])go\s+install\b/i,
70
+ /(^|[\s"'`;|&()])gem\s+install\b/i,
71
+ /(^|[\s"'`;|&()])poetry\s+add\b/i,
72
+ /(^|[\s"'`;|&()])composer\s+require\b/i,
73
+ ];
74
+
75
+ export function isDirectPackageInstallCommand(command: string): boolean {
76
+ const trimmed = command.trim().toLowerCase();
77
+ if (!trimmed) {
78
+ return false;
79
+ }
80
+
81
+ return (
82
+ DEFAULT_PACKAGE_MANAGER_DENY_PREFIXES.some((prefix) =>
83
+ trimmed.startsWith(prefix.toLowerCase())
84
+ ) ||
85
+ DIRECT_PACKAGE_INSTALL_PATTERNS.some((pattern) => pattern.test(trimmed))
86
+ );
87
+ }
88
+
89
+ function normalizePattern(pattern: string): string {
90
+ return pattern.trim();
91
+ }
92
+
93
+ function normalizeToolName(name: string): string {
94
+ return name.trim().toLowerCase();
95
+ }
96
+
97
+ export function normalizeToolList(value?: string | string[]): string[] {
98
+ if (!value) {
99
+ return [];
100
+ }
101
+ const rawList = Array.isArray(value) ? value : value.split(/[,\n]/);
102
+ return rawList
103
+ .map((entry) =>
104
+ typeof entry === "string" ? entry.trim() : String(entry).trim()
105
+ )
106
+ .filter((entry) => entry.length > 0);
107
+ }
108
+
109
+ function parseBashFilter(pattern: string): string | null {
110
+ const match = pattern.match(/^Bash\(([^:]+):\*\)$/i);
111
+ if (!match) {
112
+ return null;
113
+ }
114
+ const prefix = match[1]?.trim();
115
+ return prefix ? prefix : null;
116
+ }
117
+
118
+ function matchesToolPattern(toolName: string, pattern: string): boolean {
119
+ const normalizedTool = normalizeToolName(toolName);
120
+ const normalizedPattern = normalizePattern(pattern);
121
+ const normalizedPatternLower = normalizedPattern.toLowerCase();
122
+
123
+ if (normalizedPattern === "*") {
124
+ return true;
125
+ }
126
+
127
+ if (normalizedPatternLower.endsWith("*")) {
128
+ const prefix = normalizedPatternLower.slice(0, -1);
129
+ return normalizedTool.startsWith(prefix);
130
+ }
131
+
132
+ return normalizedTool === normalizedPatternLower;
133
+ }
134
+
135
+ export function buildToolPolicy(params: {
136
+ toolsConfig?: ToolsConfig;
137
+ allowedTools?: string | string[];
138
+ disallowedTools?: string | string[];
139
+ }): ToolPolicy {
140
+ const allowedPatterns = normalizeToolList(params.allowedTools);
141
+ const deniedPatterns = normalizeToolList(params.disallowedTools);
142
+ const toolsConfig = params.toolsConfig;
143
+ const strictMode = toolsConfig?.strictMode === true;
144
+
145
+ const mergedAllowed = [
146
+ ...(toolsConfig?.allowedTools ?? []),
147
+ ...allowedPatterns,
148
+ ].map(normalizePattern);
149
+ const mergedDenied = [
150
+ ...(toolsConfig?.deniedTools ?? []),
151
+ ...deniedPatterns,
152
+ ].map(normalizePattern);
153
+
154
+ const bashAllowPrefixes = mergedAllowed
155
+ .map((pattern) => parseBashFilter(pattern))
156
+ .filter((prefix): prefix is string => Boolean(prefix));
157
+
158
+ const bashDenyPrefixes = mergedDenied
159
+ .map((pattern) => parseBashFilter(pattern))
160
+ .filter((prefix): prefix is string => Boolean(prefix));
161
+
162
+ const bashAllowAll = mergedAllowed.some((pattern) =>
163
+ matchesToolPattern("bash", pattern)
164
+ );
165
+
166
+ return {
167
+ toolsConfig,
168
+ allowedPatterns: mergedAllowed,
169
+ deniedPatterns: mergedDenied,
170
+ strictMode,
171
+ bashPolicy: {
172
+ allowAll: bashAllowAll,
173
+ allowPrefixes: bashAllowPrefixes,
174
+ denyPrefixes: [
175
+ ...DEFAULT_PACKAGE_MANAGER_DENY_PREFIXES,
176
+ ...bashDenyPrefixes,
177
+ ],
178
+ },
179
+ };
180
+ }
181
+
182
+ export function isToolAllowedByPolicy(
183
+ toolName: string,
184
+ policy: ToolPolicy
185
+ ): boolean {
186
+ const normalizedName = normalizeToolName(toolName);
187
+ const { allowedPatterns, deniedPatterns, strictMode } = policy;
188
+
189
+ const explicitDenied = deniedPatterns.some(
190
+ (pattern) =>
191
+ !parseBashFilter(pattern) && matchesToolPattern(normalizedName, pattern)
192
+ );
193
+ if (explicitDenied) {
194
+ return false;
195
+ }
196
+
197
+ if (normalizedName === "bash") {
198
+ if (strictMode) {
199
+ const explicitlyAllowed = allowedPatterns.some((pattern) =>
200
+ matchesToolPattern(normalizedName, pattern)
201
+ );
202
+ const hasCommandAllowlist = policy.bashPolicy.allowPrefixes.length > 0;
203
+ return explicitlyAllowed || hasCommandAllowlist;
204
+ }
205
+ return true;
206
+ }
207
+
208
+ if (!strictMode) {
209
+ return true;
210
+ }
211
+
212
+ return allowedPatterns.some((pattern) =>
213
+ matchesToolPattern(normalizedName, pattern)
214
+ );
215
+ }
216
+
217
+ export function enforceBashCommandPolicy(
218
+ command: string,
219
+ policy: BashCommandPolicy
220
+ ): void {
221
+ const trimmed = command.trim();
222
+ if (!trimmed) {
223
+ return;
224
+ }
225
+
226
+ const normalizedCommand = trimmed.toLowerCase();
227
+ const denyMatchPrefix = policy.denyPrefixes.find((prefix) =>
228
+ normalizedCommand.startsWith(prefix.toLowerCase())
229
+ );
230
+ if (denyMatchPrefix) {
231
+ throw new Error("Bash command denied by policy");
232
+ }
233
+
234
+ if (policy.allowAll) {
235
+ return;
236
+ }
237
+
238
+ if (policy.allowPrefixes.length === 0) {
239
+ return;
240
+ }
241
+
242
+ const allowMatch = policy.allowPrefixes.some((prefix) =>
243
+ normalizedCommand.startsWith(prefix.toLowerCase())
244
+ );
245
+ if (!allowMatch) {
246
+ throw new Error("Bash command not allowed by policy");
247
+ }
248
+ }
@@ -0,0 +1,277 @@
1
+ import type { AgentTool } from "@mariozechner/pi-agent-core";
2
+ import {
3
+ type BashOperations,
4
+ createBashTool,
5
+ createEditTool,
6
+ createFindTool,
7
+ createGrepTool,
8
+ createLsTool,
9
+ createReadTool,
10
+ createWriteTool,
11
+ } from "@mariozechner/pi-coding-agent";
12
+ import { Type } from "@sinclair/typebox";
13
+ import { stripEnv } from "@lobu/core";
14
+ import { isDirectPackageInstallCommand } from "./tool-policy";
15
+ import { SENSITIVE_WORKER_ENV_KEYS } from "../shared/worker-env-keys";
16
+
17
+ type RequiredParamGroup = {
18
+ keys: readonly string[];
19
+ allowEmpty?: boolean;
20
+ label?: string;
21
+ };
22
+
23
+ const CLAUDE_PARAM_GROUPS: Record<
24
+ "read" | "write" | "edit",
25
+ RequiredParamGroup[]
26
+ > = {
27
+ read: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }],
28
+ write: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }],
29
+ edit: [
30
+ { keys: ["path", "file_path"], label: "path (path or file_path)" },
31
+ {
32
+ keys: ["oldText", "old_string"],
33
+ label: "oldText (oldText or old_string)",
34
+ },
35
+ {
36
+ keys: ["newText", "new_string"],
37
+ label: "newText (newText or new_string)",
38
+ },
39
+ ],
40
+ };
41
+
42
+ function normalizeToolParams(
43
+ params: unknown
44
+ ): Record<string, unknown> | undefined {
45
+ if (!params || typeof params !== "object") {
46
+ return undefined;
47
+ }
48
+ const record = params as Record<string, unknown>;
49
+ const normalized = { ...record };
50
+
51
+ if ("file_path" in normalized && !("path" in normalized)) {
52
+ normalized.path = normalized.file_path;
53
+ delete normalized.file_path;
54
+ }
55
+ if ("old_string" in normalized && !("oldText" in normalized)) {
56
+ normalized.oldText = normalized.old_string;
57
+ delete normalized.old_string;
58
+ }
59
+ if ("new_string" in normalized && !("newText" in normalized)) {
60
+ normalized.newText = normalized.new_string;
61
+ delete normalized.new_string;
62
+ }
63
+ return normalized;
64
+ }
65
+
66
+ function assertRequiredParams(
67
+ params: Record<string, unknown>,
68
+ groups: RequiredParamGroup[]
69
+ ): void {
70
+ for (const group of groups) {
71
+ const hasValue = group.keys.some((key) => {
72
+ const value = params[key];
73
+ if (value === undefined || value === null) {
74
+ return false;
75
+ }
76
+ if (
77
+ !group.allowEmpty &&
78
+ typeof value === "string" &&
79
+ value.trim() === ""
80
+ ) {
81
+ return false;
82
+ }
83
+ return true;
84
+ });
85
+ if (!hasValue) {
86
+ const label = group.label ?? group.keys.join(" or ");
87
+ throw new Error(`Missing required parameter: ${label}`);
88
+ }
89
+ }
90
+ }
91
+
92
+ function wrapToolWithNormalization(params: {
93
+ tool: AgentTool<any>;
94
+ required: RequiredParamGroup[];
95
+ schema: unknown;
96
+ }): AgentTool<any> {
97
+ const { tool, required, schema } = params;
98
+ return {
99
+ ...tool,
100
+ parameters: schema as any,
101
+ execute: async (toolCallId, rawParams, signal, onUpdate) => {
102
+ const normalized = normalizeToolParams(rawParams) ?? {};
103
+ assertRequiredParams(normalized, required);
104
+ return tool.execute(toolCallId, normalized as any, signal, onUpdate);
105
+ },
106
+ };
107
+ }
108
+
109
+ function buildReadSchema() {
110
+ return Type.Object({
111
+ path: Type.Optional(Type.String({ description: "Path to the file" })),
112
+ file_path: Type.Optional(Type.String({ description: "Path to the file" })),
113
+ offset: Type.Optional(
114
+ Type.Number({ description: "Start reading at this byte offset" })
115
+ ),
116
+ limit: Type.Optional(Type.Number({ description: "Maximum bytes to read" })),
117
+ });
118
+ }
119
+
120
+ function buildWriteSchema() {
121
+ return Type.Object({
122
+ path: Type.Optional(Type.String({ description: "Path to the file" })),
123
+ file_path: Type.Optional(Type.String({ description: "Path to the file" })),
124
+ content: Type.String({ description: "Content to write" }),
125
+ });
126
+ }
127
+
128
+ function buildEditSchema() {
129
+ return Type.Object({
130
+ path: Type.Optional(Type.String({ description: "Path to the file" })),
131
+ file_path: Type.Optional(Type.String({ description: "Path to the file" })),
132
+ oldText: Type.Optional(Type.String({ description: "Text to replace" })),
133
+ old_string: Type.Optional(Type.String({ description: "Text to replace" })),
134
+ newText: Type.Optional(Type.String({ description: "Replacement text" })),
135
+ new_string: Type.Optional(Type.String({ description: "Replacement text" })),
136
+ });
137
+ }
138
+
139
+ export function createOpenClawTools(
140
+ cwd: string,
141
+ options?: { bashOperations?: BashOperations }
142
+ ): AgentTool<any>[] {
143
+ const read = wrapToolWithNormalization({
144
+ tool: createReadTool(cwd),
145
+ required: CLAUDE_PARAM_GROUPS.read,
146
+ schema: buildReadSchema(),
147
+ });
148
+
149
+ const write = wrapToolWithNormalization({
150
+ tool: createWriteTool(cwd),
151
+ required: CLAUDE_PARAM_GROUPS.write,
152
+ schema: buildWriteSchema(),
153
+ });
154
+
155
+ const edit = wrapToolWithNormalization({
156
+ tool: createEditTool(cwd),
157
+ required: CLAUDE_PARAM_GROUPS.edit,
158
+ schema: buildEditSchema(),
159
+ });
160
+
161
+ const bashToolOpts = {
162
+ ...(options?.bashOperations ? { operations: options.bashOperations } : {}),
163
+ spawnHook: (params: {
164
+ command: string;
165
+ cwd: string;
166
+ env: Record<string, string | undefined>;
167
+ }) => ({
168
+ command: params.command,
169
+ cwd: params.cwd,
170
+ env: stripEnv(params.env, SENSITIVE_WORKER_ENV_KEYS) as NodeJS.ProcessEnv,
171
+ }),
172
+ };
173
+ const bash = wrapBashWithProxyHint(createBashTool(cwd, bashToolOpts));
174
+
175
+ return [
176
+ read,
177
+ write,
178
+ edit,
179
+ bash,
180
+ createGrepTool(cwd),
181
+ createFindTool(cwd),
182
+ createLsTool(cwd),
183
+ ];
184
+ }
185
+
186
+ function isDirectGatewayApiAccessCommand(command: string): boolean {
187
+ const trimmed = command.trim();
188
+ if (!trimmed) {
189
+ return false;
190
+ }
191
+
192
+ if (/\$(?:\{)?(?:DISPATCHER_URL|WORKER_TOKEN)\b/.test(trimmed)) {
193
+ return true;
194
+ }
195
+
196
+ if (!/\b(?:curl|wget|http|httpie|fetch)\b/i.test(trimmed)) {
197
+ return false;
198
+ }
199
+
200
+ if (!/\/(?:internal|mcp)(?:\/|\b)/i.test(trimmed)) {
201
+ return false;
202
+ }
203
+
204
+ const gatewayTargets = new Set<string>([
205
+ "http://gateway",
206
+ "https://gateway",
207
+ "gateway:",
208
+ "http://dispatcher",
209
+ "https://dispatcher",
210
+ "dispatcher:",
211
+ "http://localhost",
212
+ "https://localhost",
213
+ "localhost:",
214
+ "http://127.0.0.1",
215
+ "https://127.0.0.1",
216
+ "127.0.0.1:",
217
+ ]);
218
+
219
+ const dispatcherUrl = process.env.DISPATCHER_URL?.trim();
220
+ if (dispatcherUrl) {
221
+ gatewayTargets.add(dispatcherUrl);
222
+ gatewayTargets.add(dispatcherUrl.replace(/\/+$/, ""));
223
+ try {
224
+ const parsed = new URL(dispatcherUrl);
225
+ gatewayTargets.add(`${parsed.protocol}//${parsed.host}`);
226
+ gatewayTargets.add(parsed.host);
227
+ gatewayTargets.add(parsed.hostname);
228
+ } catch {
229
+ // Ignore invalid dispatcher URLs and rely on static aliases.
230
+ }
231
+ }
232
+
233
+ const normalized = trimmed.toLowerCase();
234
+ return [...gatewayTargets].some((target) =>
235
+ normalized.includes(target.toLowerCase())
236
+ );
237
+ }
238
+
239
+ /**
240
+ * Wrap bash tool to detect proxy CONNECT 403 errors and append a hint.
241
+ * curl doesn't display the proxy response body for CONNECT failures,
242
+ * so the model never sees "Domain not allowed" — only exit code 56.
243
+ */
244
+ function wrapBashWithProxyHint(tool: AgentTool<any>): AgentTool<any> {
245
+ const PROXY_403_PATTERN = /Received HTTP code 403 from proxy after CONNECT/i;
246
+
247
+ return {
248
+ ...tool,
249
+ execute: async (toolCallId, params, signal, onUpdate) => {
250
+ const command =
251
+ params && typeof params === "object" && "command" in params
252
+ ? String((params as { command?: unknown }).command ?? "")
253
+ : "";
254
+ if (isDirectGatewayApiAccessCommand(command)) {
255
+ throw new Error(
256
+ "DIRECT GATEWAY API ACCESS BLOCKED. Use the registered MCP/auth tools instead of calling gateway /mcp or /internal endpoints from Bash."
257
+ );
258
+ }
259
+ if (isDirectPackageInstallCommand(command)) {
260
+ throw new Error(
261
+ "DIRECT PACKAGE INSTALL BLOCKED. Install system packages with nixPackages in lobu.toml or agent settings instead of using package managers inside the worker."
262
+ );
263
+ }
264
+ try {
265
+ return await tool.execute(toolCallId, params, signal, onUpdate);
266
+ } catch (err: any) {
267
+ const msg = err?.message ?? String(err);
268
+ if (PROXY_403_PATTERN.test(msg)) {
269
+ throw new Error(
270
+ `DOMAIN BLOCKED BY PROXY. The domain is blocked at the network level. Network access is configured via lobu.toml or the gateway configuration APIs — do NOT retry the request.\n\n${msg}`
271
+ );
272
+ }
273
+ throw err;
274
+ }
275
+ },
276
+ };
277
+ }