@lobu/worker 6.1.1 → 7.1.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 (124) hide show
  1. package/dist/core/error-handler.d.ts +0 -4
  2. package/dist/core/error-handler.d.ts.map +1 -1
  3. package/dist/core/error-handler.js +4 -15
  4. package/dist/core/error-handler.js.map +1 -1
  5. package/dist/core/types.d.ts +1 -19
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/core/types.js +0 -4
  8. package/dist/core/types.js.map +1 -1
  9. package/dist/core/workspace.d.ts +2 -11
  10. package/dist/core/workspace.d.ts.map +1 -1
  11. package/dist/core/workspace.js +14 -36
  12. package/dist/core/workspace.js.map +1 -1
  13. package/dist/embedded/just-bash-bootstrap.d.ts.map +1 -1
  14. package/dist/embedded/just-bash-bootstrap.js +60 -6
  15. package/dist/embedded/just-bash-bootstrap.js.map +1 -1
  16. package/dist/embedded/mcp-cli-commands.d.ts.map +1 -1
  17. package/dist/embedded/mcp-cli-commands.js +3 -38
  18. package/dist/embedded/mcp-cli-commands.js.map +1 -1
  19. package/dist/gateway/gateway-integration.js +4 -4
  20. package/dist/gateway/gateway-integration.js.map +1 -1
  21. package/dist/gateway/message-batcher.d.ts.map +1 -1
  22. package/dist/gateway/message-batcher.js +3 -5
  23. package/dist/gateway/message-batcher.js.map +1 -1
  24. package/dist/gateway/sse-client.d.ts +1 -0
  25. package/dist/gateway/sse-client.d.ts.map +1 -1
  26. package/dist/gateway/sse-client.js +52 -8
  27. package/dist/gateway/sse-client.js.map +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +7 -24
  30. package/dist/index.js.map +1 -1
  31. package/dist/instructions/builder.d.ts.map +1 -1
  32. package/dist/instructions/builder.js +2 -1
  33. package/dist/instructions/builder.js.map +1 -1
  34. package/dist/openclaw/plugin-loader.d.ts.map +1 -1
  35. package/dist/openclaw/plugin-loader.js +8 -19
  36. package/dist/openclaw/plugin-loader.js.map +1 -1
  37. package/dist/openclaw/processor.d.ts.map +1 -1
  38. package/dist/openclaw/processor.js +2 -0
  39. package/dist/openclaw/processor.js.map +1 -1
  40. package/dist/openclaw/sandbox-leak.d.ts.map +1 -1
  41. package/dist/openclaw/sandbox-leak.js +1 -6
  42. package/dist/openclaw/sandbox-leak.js.map +1 -1
  43. package/dist/openclaw/session-context.d.ts.map +1 -1
  44. package/dist/openclaw/session-context.js +3 -0
  45. package/dist/openclaw/session-context.js.map +1 -1
  46. package/dist/openclaw/tool-policy.d.ts.map +1 -1
  47. package/dist/openclaw/tool-policy.js +5 -11
  48. package/dist/openclaw/tool-policy.js.map +1 -1
  49. package/dist/openclaw/worker.d.ts +0 -1
  50. package/dist/openclaw/worker.d.ts.map +1 -1
  51. package/dist/openclaw/worker.js +19 -85
  52. package/dist/openclaw/worker.js.map +1 -1
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +3 -40
  55. package/dist/server.js.map +1 -1
  56. package/dist/shared/audio-provider-suggestions.d.ts.map +1 -1
  57. package/dist/shared/audio-provider-suggestions.js +4 -6
  58. package/dist/shared/audio-provider-suggestions.js.map +1 -1
  59. package/dist/shared/tool-implementations.d.ts.map +1 -1
  60. package/dist/shared/tool-implementations.js +99 -37
  61. package/dist/shared/tool-implementations.js.map +1 -1
  62. package/package.json +14 -4
  63. package/src/__tests__/audio-provider-suggestions.test.ts +199 -0
  64. package/src/__tests__/custom-tools.test.ts +92 -0
  65. package/src/__tests__/embedded-just-bash-bootstrap.test.ts +128 -0
  66. package/src/__tests__/embedded-mcp-cli-bash.test.ts +179 -0
  67. package/src/__tests__/embedded-tools.test.ts +744 -0
  68. package/src/__tests__/exec-sandbox-extra.test.ts +0 -0
  69. package/src/__tests__/exec-sandbox.test.ts +550 -0
  70. package/src/__tests__/generated-media.test.ts +142 -0
  71. package/src/__tests__/instructions.test.ts +60 -0
  72. package/src/__tests__/mcp-cli-commands-extra.test.ts +478 -0
  73. package/src/__tests__/mcp-cli-commands.test.ts +383 -0
  74. package/src/__tests__/mcp-tool-call.test.ts +423 -0
  75. package/src/__tests__/memory-flush-harden.test.ts +367 -0
  76. package/src/__tests__/memory-flush-runtime.test.ts +138 -0
  77. package/src/__tests__/memory-flush.test.ts +64 -0
  78. package/src/__tests__/message-batcher.test.ts +247 -0
  79. package/src/__tests__/model-resolver-harden.test.ts +197 -0
  80. package/src/__tests__/model-resolver.test.ts +156 -0
  81. package/src/__tests__/processor-harden.test.ts +259 -0
  82. package/src/__tests__/processor.test.ts +225 -0
  83. package/src/__tests__/replace-base-prompt-identity.test.ts +41 -0
  84. package/src/__tests__/sandbox-leak-harden.test.ts +200 -0
  85. package/src/__tests__/sandbox-leak.test.ts +167 -0
  86. package/src/__tests__/setup.ts +102 -0
  87. package/src/__tests__/sse-client-harden.test.ts +588 -0
  88. package/src/__tests__/sse-client.test.ts +90 -0
  89. package/src/__tests__/tool-implementations.test.ts +196 -0
  90. package/src/__tests__/tool-policy-edge-cases.test.ts +263 -0
  91. package/src/__tests__/tool-policy.test.ts +269 -0
  92. package/src/__tests__/worker.test.ts +89 -0
  93. package/src/core/error-handler.ts +47 -0
  94. package/src/core/project-scanner.ts +65 -0
  95. package/src/core/types.ts +94 -0
  96. package/src/core/workspace.ts +66 -0
  97. package/src/embedded/exec-sandbox.ts +372 -0
  98. package/src/embedded/just-bash-bootstrap.ts +575 -0
  99. package/src/embedded/mcp-cli-commands.ts +405 -0
  100. package/src/gateway/gateway-integration.ts +298 -0
  101. package/src/gateway/message-batcher.ts +123 -0
  102. package/src/gateway/sse-client.ts +988 -0
  103. package/src/gateway/types.ts +68 -0
  104. package/src/index.ts +123 -0
  105. package/src/instructions/builder.ts +44 -0
  106. package/src/instructions/providers.ts +27 -0
  107. package/src/modules/lifecycle.ts +92 -0
  108. package/src/openclaw/custom-tools.ts +315 -0
  109. package/src/openclaw/instructions.ts +36 -0
  110. package/src/openclaw/model-resolver.ts +150 -0
  111. package/src/openclaw/plugin-loader.ts +423 -0
  112. package/src/openclaw/processor.ts +199 -0
  113. package/src/openclaw/sandbox-leak.ts +100 -0
  114. package/src/openclaw/session-context.ts +323 -0
  115. package/src/openclaw/tool-policy.ts +241 -0
  116. package/src/openclaw/tools.ts +277 -0
  117. package/src/openclaw/worker.ts +1836 -0
  118. package/src/server.ts +330 -0
  119. package/src/shared/audio-provider-suggestions.ts +130 -0
  120. package/src/shared/processor-utils.ts +33 -0
  121. package/src/shared/provider-auth-hints.ts +68 -0
  122. package/src/shared/tool-display-config.ts +75 -0
  123. package/src/shared/tool-implementations.ts +981 -0
  124. package/src/shared/worker-env-keys.ts +8 -0
@@ -0,0 +1,383 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { McpToolDef } from "@lobu/core";
3
+ import {
4
+ buildMcpCliCommands,
5
+ buildMcpServerHandler,
6
+ isMcpIdReserved,
7
+ type McpRuntimeRef,
8
+ type McpRuntimeState,
9
+ parsePayload,
10
+ summariseAuthCheck,
11
+ summariseAuthStart,
12
+ } from "../embedded/mcp-cli-commands";
13
+ import type { GatewayParams } from "../shared/tool-implementations";
14
+
15
+ const gw: GatewayParams = {
16
+ gatewayUrl: "http://gateway",
17
+ workerToken: "worker-token",
18
+ channelId: "channel-1",
19
+ conversationId: "conversation-1",
20
+ platform: "telegram",
21
+ };
22
+
23
+ function makeRef(overrides: Partial<McpRuntimeState> = {}): McpRuntimeRef {
24
+ return {
25
+ current: {
26
+ mcpTools: overrides.mcpTools ?? {},
27
+ mcpStatus: overrides.mcpStatus ?? [],
28
+ mcpContext: overrides.mcpContext ?? {},
29
+ },
30
+ };
31
+ }
32
+
33
+ const lobuTool: McpToolDef = {
34
+ name: "search_memory",
35
+ description: "Search the memory store",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: { query: { type: "string" } },
39
+ required: ["query"],
40
+ },
41
+ };
42
+
43
+ describe("parsePayload", () => {
44
+ test("empty stdin and no inline arg yields empty object", () => {
45
+ expect(parsePayload(undefined, undefined)).toEqual({
46
+ ok: true,
47
+ payload: {},
48
+ });
49
+ });
50
+
51
+ test("whitespace stdin yields empty object", () => {
52
+ expect(parsePayload(" \n", undefined)).toEqual({
53
+ ok: true,
54
+ payload: {},
55
+ });
56
+ });
57
+
58
+ test("parses JSON from stdin", () => {
59
+ expect(parsePayload('{"q":"hello"}', undefined)).toEqual({
60
+ ok: true,
61
+ payload: { q: "hello" },
62
+ });
63
+ });
64
+
65
+ test("falls back to inline arg when stdin empty", () => {
66
+ expect(parsePayload("", '{"q":"x"}')).toEqual({
67
+ ok: true,
68
+ payload: { q: "x" },
69
+ });
70
+ });
71
+
72
+ test("rejects non-object JSON (array)", () => {
73
+ const result = parsePayload("[1, 2, 3]", undefined);
74
+ expect(result.ok).toBe(false);
75
+ });
76
+
77
+ test("rejects malformed JSON", () => {
78
+ const result = parsePayload("{not json", undefined);
79
+ expect(result.ok).toBe(false);
80
+ if (!result.ok) {
81
+ expect(result.error).toContain("invalid JSON");
82
+ }
83
+ });
84
+ });
85
+
86
+ describe("isMcpIdReserved", () => {
87
+ test("rejects bash builtins", () => {
88
+ expect(isMcpIdReserved("cd")).toContain("reserved");
89
+ expect(isMcpIdReserved("echo")).toContain("reserved");
90
+ });
91
+
92
+ test("rejects package-manager ids", () => {
93
+ expect(isMcpIdReserved("npm")).toContain("package-install");
94
+ expect(isMcpIdReserved("pip")).toContain("package-install");
95
+ });
96
+
97
+ test("allows normal MCP ids", () => {
98
+ expect(isMcpIdReserved("lobu")).toBeNull();
99
+ expect(isMcpIdReserved("gmail")).toBeNull();
100
+ });
101
+ });
102
+
103
+ describe("buildMcpServerHandler", () => {
104
+ test("--help lists tools and usage", async () => {
105
+ const ref = makeRef({
106
+ mcpTools: { lobu: [lobuTool] },
107
+ mcpStatus: [
108
+ {
109
+ id: "lobu",
110
+ name: "Lobu",
111
+ requiresAuth: true,
112
+ requiresInput: false,
113
+ authenticated: true,
114
+ configured: true,
115
+ },
116
+ ],
117
+ });
118
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
119
+ callTool: async () => ({ content: [] }),
120
+ });
121
+
122
+ const result = await handler(["--help"], {});
123
+ expect(result.exitCode).toBe(0);
124
+ expect(result.stdout).toContain("lobu — MCP server CLI");
125
+ expect(result.stdout).toContain("search_memory");
126
+ expect(result.stdout).toContain("auth login|check|logout");
127
+ });
128
+
129
+ test("--schema prints JSON schema for a known tool", async () => {
130
+ const ref = makeRef({ mcpTools: { lobu: [lobuTool] } });
131
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
132
+ callTool: async () => ({ content: [] }),
133
+ });
134
+
135
+ const result = await handler(["search_memory", "--schema"], {});
136
+ expect(result.exitCode).toBe(0);
137
+ const parsed = JSON.parse(result.stdout);
138
+ expect(parsed).toEqual(lobuTool.inputSchema);
139
+ });
140
+
141
+ test("--schema on unknown tool exits 2", async () => {
142
+ const ref = makeRef({ mcpTools: { lobu: [] } });
143
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
144
+ callTool: async () => ({ content: [] }),
145
+ });
146
+
147
+ const result = await handler(["nope", "--schema"], {});
148
+ expect(result.exitCode).toBe(2);
149
+ expect(result.stderr).toContain("unknown tool");
150
+ });
151
+
152
+ test("tool invocation parses JSON from stdin and routes to callTool", async () => {
153
+ const ref = makeRef({ mcpTools: { lobu: [lobuTool] } });
154
+ const calls: Array<{
155
+ mcpId: string;
156
+ toolName: string;
157
+ payload: Record<string, unknown>;
158
+ }> = [];
159
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
160
+ callTool: async (_gw, mcpId, toolName, payload) => {
161
+ calls.push({ mcpId, toolName, payload });
162
+ return { content: [{ type: "text", text: "search ok" }] };
163
+ },
164
+ });
165
+
166
+ const result = await handler(["search_memory"], {
167
+ stdin: '{"query":"architecture"}',
168
+ });
169
+ expect(result.exitCode).toBe(0);
170
+ expect(result.stdout.trim()).toBe("search ok");
171
+ expect(calls).toEqual([
172
+ {
173
+ mcpId: "lobu",
174
+ toolName: "search_memory",
175
+ payload: { query: "architecture" },
176
+ },
177
+ ]);
178
+ });
179
+
180
+ test("tool invocation falls back to args[1] when stdin is empty", async () => {
181
+ const ref = makeRef({ mcpTools: { lobu: [lobuTool] } });
182
+ const captured: Record<string, unknown>[] = [];
183
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
184
+ callTool: async (_gw, _mcpId, _toolName, payload) => {
185
+ captured.push(payload);
186
+ return { content: [] };
187
+ },
188
+ });
189
+
190
+ const result = await handler(["search_memory", '{"query":"inline"}'], {});
191
+ expect(result.exitCode).toBe(0);
192
+ expect(captured).toEqual([{ query: "inline" }]);
193
+ });
194
+
195
+ test("tool invocation defaults to empty object when no payload given", async () => {
196
+ const ref = makeRef({ mcpTools: { lobu: [lobuTool] } });
197
+ const captured: Record<string, unknown>[] = [];
198
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
199
+ callTool: async (_gw, _mcpId, _toolName, payload) => {
200
+ captured.push(payload);
201
+ return { content: [] };
202
+ },
203
+ });
204
+
205
+ const result = await handler(["search_memory"], {});
206
+ expect(result.exitCode).toBe(0);
207
+ expect(captured).toEqual([{}]);
208
+ });
209
+
210
+ test("invalid JSON payload exits 2", async () => {
211
+ const ref = makeRef({ mcpTools: { lobu: [lobuTool] } });
212
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
213
+ callTool: async () => {
214
+ throw new Error("should not be called");
215
+ },
216
+ });
217
+
218
+ const result = await handler(["search_memory"], { stdin: "{not json" });
219
+ expect(result.exitCode).toBe(2);
220
+ expect(result.stderr).toContain("invalid JSON");
221
+ });
222
+
223
+ test("unknown tool exits 2", async () => {
224
+ const ref = makeRef({ mcpTools: { lobu: [] } });
225
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
226
+ callTool: async () => ({ content: [] }),
227
+ });
228
+
229
+ const result = await handler(["mystery"], {});
230
+ expect(result.exitCode).toBe(2);
231
+ expect(result.stderr).toContain("unknown tool");
232
+ });
233
+
234
+ test("callTool throwing surfaces as exitCode 1", async () => {
235
+ const ref = makeRef({ mcpTools: { lobu: [lobuTool] } });
236
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
237
+ callTool: async () => {
238
+ throw new Error("network down");
239
+ },
240
+ });
241
+
242
+ const result = await handler(["search_memory"], { stdin: "{}" });
243
+ expect(result.exitCode).toBe(1);
244
+ expect(result.stderr).toContain("network down");
245
+ });
246
+
247
+ test("reads mcpTools through ref.current on each invocation", async () => {
248
+ // Start empty, then mutate the ref and confirm the handler picks up new tools.
249
+ const ref = makeRef({ mcpTools: { lobu: [] } });
250
+ const handler = buildMcpServerHandler("lobu", ref, gw, {
251
+ callTool: async () => ({ content: [{ type: "text", text: "ok" }] }),
252
+ });
253
+
254
+ const r1 = await handler(["search_memory"], {});
255
+ expect(r1.exitCode).toBe(2);
256
+
257
+ ref.current = {
258
+ ...ref.current,
259
+ mcpTools: { lobu: [lobuTool] },
260
+ };
261
+
262
+ const r2 = await handler(["search_memory"], { stdin: "{}" });
263
+ expect(r2.exitCode).toBe(0);
264
+ });
265
+ });
266
+
267
+ describe("buildMcpCliCommands", () => {
268
+ test("builds one command per MCP server", () => {
269
+ const ref = makeRef({
270
+ mcpTools: { lobu: [lobuTool] },
271
+ mcpStatus: [
272
+ {
273
+ id: "gmail",
274
+ name: "Gmail",
275
+ requiresAuth: true,
276
+ requiresInput: false,
277
+ authenticated: false,
278
+ configured: true,
279
+ },
280
+ ],
281
+ });
282
+ const commands = buildMcpCliCommands(ref, gw);
283
+ expect(commands.map((c) => c.name).sort()).toEqual(["gmail", "lobu"]);
284
+ });
285
+
286
+ test("skips MCP ids that collide with bash builtins", () => {
287
+ const ref = makeRef({
288
+ mcpStatus: [
289
+ {
290
+ id: "echo",
291
+ name: "Echo",
292
+ requiresAuth: false,
293
+ requiresInput: false,
294
+ authenticated: true,
295
+ configured: true,
296
+ },
297
+ {
298
+ id: "lobu",
299
+ name: "Lobu",
300
+ requiresAuth: false,
301
+ requiresInput: false,
302
+ authenticated: true,
303
+ configured: true,
304
+ },
305
+ ],
306
+ });
307
+ const commands = buildMcpCliCommands(ref, gw);
308
+ expect(commands.map((c) => c.name)).toEqual(["lobu"]);
309
+ });
310
+
311
+ test("skips MCP ids that collide with package-install denylist", () => {
312
+ const ref = makeRef({
313
+ mcpStatus: [
314
+ {
315
+ id: "npm",
316
+ name: "Not npm",
317
+ requiresAuth: false,
318
+ requiresInput: false,
319
+ authenticated: true,
320
+ configured: true,
321
+ },
322
+ ],
323
+ });
324
+ const commands = buildMcpCliCommands(ref, gw);
325
+ expect(commands).toEqual([]);
326
+ });
327
+ });
328
+
329
+ describe("summariseAuthStart / summariseAuthCheck", () => {
330
+ test("summariseAuthStart collapses login_started to a short status without URL", () => {
331
+ const out = summariseAuthStart(
332
+ JSON.stringify({
333
+ status: "login_started",
334
+ verification_url: "https://example.com/verify",
335
+ interaction_posted: true,
336
+ }),
337
+ "lobu"
338
+ );
339
+ const parsed = JSON.parse(out);
340
+ expect(parsed.status).toBe("login_started");
341
+ expect(parsed.mcp_id).toBe("lobu");
342
+ expect(parsed.interaction_posted).toBe(true);
343
+ expect(out).not.toContain("https://example.com/verify");
344
+ });
345
+
346
+ test("summariseAuthStart passes through already_authenticated", () => {
347
+ const out = summariseAuthStart(
348
+ JSON.stringify({ status: "already_authenticated" }),
349
+ "lobu"
350
+ );
351
+ expect(JSON.parse(out).status).toBe("already_authenticated");
352
+ });
353
+
354
+ test("summariseAuthStart falls through to raw when interaction_posted=false so URL stays reachable", () => {
355
+ const raw = JSON.stringify({
356
+ status: "login_started",
357
+ verification_url: "https://example.com/verify",
358
+ user_code: "ABCD-1234",
359
+ interaction_posted: false,
360
+ });
361
+ const out = summariseAuthStart(raw, "lobu");
362
+ expect(out).toBe(raw);
363
+ expect(out).toContain("https://example.com/verify");
364
+ expect(out).toContain("ABCD-1234");
365
+ });
366
+
367
+ test("summariseAuthCheck emits authenticated=true on success", () => {
368
+ const out = summariseAuthCheck(
369
+ { status: "authenticated", authenticated: true },
370
+ "lobu",
371
+ "raw"
372
+ );
373
+ expect(JSON.parse(out)).toEqual({
374
+ status: "authenticated",
375
+ mcp_id: "lobu",
376
+ authenticated: true,
377
+ });
378
+ });
379
+
380
+ test("summariseAuthCheck falls back to raw text when parse fails", () => {
381
+ expect(summariseAuthCheck(null, "lobu", "raw text")).toBe("raw text");
382
+ });
383
+ });