@lobu/worker 3.0.5 → 3.0.6

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 (46) hide show
  1. package/USAGE.md +120 -0
  2. package/docs/custom-base-image.md +88 -0
  3. package/package.json +2 -2
  4. package/scripts/worker-entrypoint.sh +184 -0
  5. package/src/__tests__/audio-provider-suggestions.test.ts +198 -0
  6. package/src/__tests__/embedded-just-bash-bootstrap.test.ts +39 -0
  7. package/src/__tests__/embedded-tools.test.ts +558 -0
  8. package/src/__tests__/instructions.test.ts +59 -0
  9. package/src/__tests__/memory-flush-runtime.test.ts +138 -0
  10. package/src/__tests__/memory-flush.test.ts +64 -0
  11. package/src/__tests__/model-resolver.test.ts +156 -0
  12. package/src/__tests__/processor.test.ts +225 -0
  13. package/src/__tests__/setup.ts +109 -0
  14. package/src/__tests__/sse-client.test.ts +48 -0
  15. package/src/__tests__/tool-policy.test.ts +269 -0
  16. package/src/__tests__/worker.test.ts +89 -0
  17. package/src/core/error-handler.ts +70 -0
  18. package/src/core/project-scanner.ts +65 -0
  19. package/src/core/types.ts +125 -0
  20. package/src/core/url-utils.ts +9 -0
  21. package/src/core/workspace.ts +138 -0
  22. package/src/embedded/just-bash-bootstrap.ts +228 -0
  23. package/src/gateway/gateway-integration.ts +287 -0
  24. package/src/gateway/message-batcher.ts +128 -0
  25. package/src/gateway/sse-client.ts +955 -0
  26. package/src/gateway/types.ts +68 -0
  27. package/src/index.ts +146 -0
  28. package/src/instructions/builder.ts +80 -0
  29. package/src/instructions/providers.ts +27 -0
  30. package/src/modules/lifecycle.ts +92 -0
  31. package/src/openclaw/custom-tools.ts +290 -0
  32. package/src/openclaw/instructions.ts +38 -0
  33. package/src/openclaw/model-resolver.ts +150 -0
  34. package/src/openclaw/plugin-loader.ts +427 -0
  35. package/src/openclaw/processor.ts +216 -0
  36. package/src/openclaw/session-context.ts +277 -0
  37. package/src/openclaw/tool-policy.ts +212 -0
  38. package/src/openclaw/tools.ts +208 -0
  39. package/src/openclaw/worker.ts +1792 -0
  40. package/src/server.ts +329 -0
  41. package/src/shared/audio-provider-suggestions.ts +132 -0
  42. package/src/shared/processor-utils.ts +33 -0
  43. package/src/shared/provider-auth-hints.ts +64 -0
  44. package/src/shared/tool-display-config.ts +75 -0
  45. package/src/shared/tool-implementations.ts +768 -0
  46. package/tsconfig.json +21 -0
@@ -0,0 +1,208 @@
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 { isDirectPackageInstallCommand } from "./tool-policy";
14
+
15
+ type RequiredParamGroup = {
16
+ keys: readonly string[];
17
+ allowEmpty?: boolean;
18
+ label?: string;
19
+ };
20
+
21
+ const CLAUDE_PARAM_GROUPS: Record<
22
+ "read" | "write" | "edit",
23
+ RequiredParamGroup[]
24
+ > = {
25
+ read: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }],
26
+ write: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }],
27
+ edit: [
28
+ { keys: ["path", "file_path"], label: "path (path or file_path)" },
29
+ {
30
+ keys: ["oldText", "old_string"],
31
+ label: "oldText (oldText or old_string)",
32
+ },
33
+ {
34
+ keys: ["newText", "new_string"],
35
+ label: "newText (newText or new_string)",
36
+ },
37
+ ],
38
+ };
39
+
40
+ function normalizeToolParams(
41
+ params: unknown
42
+ ): Record<string, unknown> | undefined {
43
+ if (!params || typeof params !== "object") {
44
+ return undefined;
45
+ }
46
+ const record = params as Record<string, unknown>;
47
+ const normalized = { ...record };
48
+
49
+ if ("file_path" in normalized && !("path" in normalized)) {
50
+ normalized.path = normalized.file_path;
51
+ delete normalized.file_path;
52
+ }
53
+ if ("old_string" in normalized && !("oldText" in normalized)) {
54
+ normalized.oldText = normalized.old_string;
55
+ delete normalized.old_string;
56
+ }
57
+ if ("new_string" in normalized && !("newText" in normalized)) {
58
+ normalized.newText = normalized.new_string;
59
+ delete normalized.new_string;
60
+ }
61
+ return normalized;
62
+ }
63
+
64
+ function assertRequiredParams(
65
+ params: Record<string, unknown>,
66
+ groups: RequiredParamGroup[]
67
+ ): void {
68
+ for (const group of groups) {
69
+ const hasValue = group.keys.some((key) => {
70
+ const value = params[key];
71
+ if (value === undefined || value === null) {
72
+ return false;
73
+ }
74
+ if (
75
+ !group.allowEmpty &&
76
+ typeof value === "string" &&
77
+ value.trim() === ""
78
+ ) {
79
+ return false;
80
+ }
81
+ return true;
82
+ });
83
+ if (!hasValue) {
84
+ const label = group.label ?? group.keys.join(" or ");
85
+ throw new Error(`Missing required parameter: ${label}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ function wrapToolWithNormalization(params: {
91
+ tool: AgentTool<any>;
92
+ required: RequiredParamGroup[];
93
+ schema: unknown;
94
+ }): AgentTool<any> {
95
+ const { tool, required, schema } = params;
96
+ return {
97
+ ...tool,
98
+ parameters: schema as any,
99
+ execute: async (toolCallId, rawParams, signal, onUpdate) => {
100
+ const normalized = normalizeToolParams(rawParams) ?? {};
101
+ assertRequiredParams(normalized, required);
102
+ return tool.execute(toolCallId, normalized as any, signal, onUpdate);
103
+ },
104
+ };
105
+ }
106
+
107
+ function buildReadSchema() {
108
+ return Type.Object({
109
+ path: Type.Optional(Type.String({ description: "Path to the file" })),
110
+ file_path: Type.Optional(Type.String({ description: "Path to the file" })),
111
+ offset: Type.Optional(
112
+ Type.Number({ description: "Start reading at this byte offset" })
113
+ ),
114
+ limit: Type.Optional(Type.Number({ description: "Maximum bytes to read" })),
115
+ });
116
+ }
117
+
118
+ function buildWriteSchema() {
119
+ return Type.Object({
120
+ path: Type.Optional(Type.String({ description: "Path to the file" })),
121
+ file_path: Type.Optional(Type.String({ description: "Path to the file" })),
122
+ content: Type.String({ description: "Content to write" }),
123
+ });
124
+ }
125
+
126
+ function buildEditSchema() {
127
+ return Type.Object({
128
+ path: Type.Optional(Type.String({ description: "Path to the file" })),
129
+ file_path: Type.Optional(Type.String({ description: "Path to the file" })),
130
+ oldText: Type.Optional(Type.String({ description: "Text to replace" })),
131
+ old_string: Type.Optional(Type.String({ description: "Text to replace" })),
132
+ newText: Type.Optional(Type.String({ description: "Replacement text" })),
133
+ new_string: Type.Optional(Type.String({ description: "Replacement text" })),
134
+ });
135
+ }
136
+
137
+ export function createOpenClawTools(
138
+ cwd: string,
139
+ options?: { bashOperations?: BashOperations }
140
+ ): AgentTool<any>[] {
141
+ const read = wrapToolWithNormalization({
142
+ tool: createReadTool(cwd),
143
+ required: CLAUDE_PARAM_GROUPS.read,
144
+ schema: buildReadSchema(),
145
+ });
146
+
147
+ const write = wrapToolWithNormalization({
148
+ tool: createWriteTool(cwd),
149
+ required: CLAUDE_PARAM_GROUPS.write,
150
+ schema: buildWriteSchema(),
151
+ });
152
+
153
+ const edit = wrapToolWithNormalization({
154
+ tool: createEditTool(cwd),
155
+ required: CLAUDE_PARAM_GROUPS.edit,
156
+ schema: buildEditSchema(),
157
+ });
158
+
159
+ const bashToolOpts = options?.bashOperations
160
+ ? { operations: options.bashOperations }
161
+ : undefined;
162
+ const bash = wrapBashWithProxyHint(createBashTool(cwd, bashToolOpts));
163
+
164
+ return [
165
+ read,
166
+ write,
167
+ edit,
168
+ bash,
169
+ createGrepTool(cwd),
170
+ createFindTool(cwd),
171
+ createLsTool(cwd),
172
+ ];
173
+ }
174
+
175
+ /**
176
+ * Wrap bash tool to detect proxy CONNECT 403 errors and append a hint.
177
+ * curl doesn't display the proxy response body for CONNECT failures,
178
+ * so the model never sees "Domain not allowed" — only exit code 56.
179
+ */
180
+ function wrapBashWithProxyHint(tool: AgentTool<any>): AgentTool<any> {
181
+ const PROXY_403_PATTERN = /Received HTTP code 403 from proxy after CONNECT/i;
182
+
183
+ return {
184
+ ...tool,
185
+ execute: async (toolCallId, params, signal, onUpdate) => {
186
+ const command =
187
+ params && typeof params === "object" && "command" in params
188
+ ? String((params as { command?: unknown }).command ?? "")
189
+ : "";
190
+ if (isDirectPackageInstallCommand(command)) {
191
+ throw new Error(
192
+ "DIRECT PACKAGE INSTALL BLOCKED. Install system packages with nixPackages in lobu.toml or agent settings instead of using package managers inside the worker."
193
+ );
194
+ }
195
+ try {
196
+ return await tool.execute(toolCallId, params, signal, onUpdate);
197
+ } catch (err: any) {
198
+ const msg = err?.message ?? String(err);
199
+ if (PROXY_403_PATTERN.test(msg)) {
200
+ throw new Error(
201
+ `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}`
202
+ );
203
+ }
204
+ throw err;
205
+ }
206
+ },
207
+ };
208
+ }