@lobu/worker 3.0.9 → 3.0.13

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 (49) hide show
  1. package/dist/openclaw/session-context.d.ts.map +1 -1
  2. package/dist/openclaw/session-context.js +1 -1
  3. package/dist/openclaw/session-context.js.map +1 -1
  4. package/package.json +10 -9
  5. package/USAGE.md +0 -120
  6. package/docs/custom-base-image.md +0 -88
  7. package/scripts/worker-entrypoint.sh +0 -184
  8. package/src/__tests__/audio-provider-suggestions.test.ts +0 -198
  9. package/src/__tests__/embedded-just-bash-bootstrap.test.ts +0 -39
  10. package/src/__tests__/embedded-tools.test.ts +0 -558
  11. package/src/__tests__/instructions.test.ts +0 -59
  12. package/src/__tests__/memory-flush-runtime.test.ts +0 -138
  13. package/src/__tests__/memory-flush.test.ts +0 -64
  14. package/src/__tests__/model-resolver.test.ts +0 -156
  15. package/src/__tests__/processor.test.ts +0 -225
  16. package/src/__tests__/setup.ts +0 -109
  17. package/src/__tests__/sse-client.test.ts +0 -48
  18. package/src/__tests__/tool-policy.test.ts +0 -269
  19. package/src/__tests__/worker.test.ts +0 -89
  20. package/src/core/error-handler.ts +0 -70
  21. package/src/core/project-scanner.ts +0 -65
  22. package/src/core/types.ts +0 -125
  23. package/src/core/url-utils.ts +0 -9
  24. package/src/core/workspace.ts +0 -138
  25. package/src/embedded/just-bash-bootstrap.ts +0 -228
  26. package/src/gateway/gateway-integration.ts +0 -287
  27. package/src/gateway/message-batcher.ts +0 -128
  28. package/src/gateway/sse-client.ts +0 -955
  29. package/src/gateway/types.ts +0 -68
  30. package/src/index.ts +0 -144
  31. package/src/instructions/builder.ts +0 -80
  32. package/src/instructions/providers.ts +0 -27
  33. package/src/modules/lifecycle.ts +0 -92
  34. package/src/openclaw/custom-tools.ts +0 -290
  35. package/src/openclaw/instructions.ts +0 -38
  36. package/src/openclaw/model-resolver.ts +0 -150
  37. package/src/openclaw/plugin-loader.ts +0 -427
  38. package/src/openclaw/processor.ts +0 -216
  39. package/src/openclaw/session-context.ts +0 -277
  40. package/src/openclaw/tool-policy.ts +0 -212
  41. package/src/openclaw/tools.ts +0 -208
  42. package/src/openclaw/worker.ts +0 -1792
  43. package/src/server.ts +0 -329
  44. package/src/shared/audio-provider-suggestions.ts +0 -132
  45. package/src/shared/processor-utils.ts +0 -33
  46. package/src/shared/provider-auth-hints.ts +0 -64
  47. package/src/shared/tool-display-config.ts +0 -75
  48. package/src/shared/tool-implementations.ts +0 -768
  49. package/tsconfig.json +0 -21
@@ -1,138 +0,0 @@
1
- import { beforeEach, describe, expect, test } from "bun:test";
2
- import { SettingsManager } from "@mariozechner/pi-coding-agent";
3
- import { OpenClawWorker } from "../openclaw/worker";
4
- import { mockWorkerConfig } from "./setup";
5
-
6
- describe("pre-compaction memory flush runtime", () => {
7
- beforeEach(() => {
8
- process.env.DISPATCHER_URL = "https://test-dispatcher.example.com";
9
- process.env.WORKER_TOKEN = "test-worker-token";
10
- });
11
-
12
- test("runs silent flush once per compaction cycle and persists NO_REPLY outcome", async () => {
13
- const worker = new OpenClawWorker(mockWorkerConfig);
14
- const settingsManager = SettingsManager.inMemory();
15
-
16
- const branchEntries: Array<Record<string, unknown>> = [];
17
- const sessionManager = {
18
- getBranch: () => branchEntries as any,
19
- appendCustomEntry: (customType: string, data: unknown) => {
20
- branchEntries.push({
21
- type: "custom",
22
- id: crypto.randomUUID(),
23
- parentId: null,
24
- timestamp: new Date().toISOString(),
25
- customType,
26
- data,
27
- });
28
- },
29
- } as any;
30
-
31
- let silentCallCount = 0;
32
- const session = {
33
- getContextUsage: () => ({
34
- tokens: 90000,
35
- contextWindow: 100000,
36
- percent: 90,
37
- usageTokens: 90000,
38
- trailingTokens: 0,
39
- lastUsageIndex: 1,
40
- }),
41
- messages: [
42
- {
43
- role: "assistant",
44
- content: " no_reply ",
45
- },
46
- ],
47
- } as any;
48
-
49
- const invokeFlush = async () => {
50
- await (worker as any).maybeRunPreCompactionMemoryFlush({
51
- session,
52
- sessionManager,
53
- settingsManager,
54
- memoryFlushConfig: {
55
- enabled: true,
56
- softThresholdTokens: 4000,
57
- systemPrompt: "Session nearing compaction.",
58
- prompt: "Reply with NO_REPLY.",
59
- },
60
- incomingPromptText: "hello",
61
- incomingImageCount: 0,
62
- runSilentPrompt: async () => {
63
- silentCallCount += 1;
64
- },
65
- });
66
- };
67
-
68
- await invokeFlush();
69
- expect(silentCallCount).toBe(1);
70
-
71
- const flushStateEntry = branchEntries.find(
72
- (entry) => entry.type === "custom"
73
- ) as any;
74
- expect(flushStateEntry?.customType).toBe("lobu.memory_flush_state");
75
- expect(flushStateEntry?.data?.outcome).toBe("no_reply");
76
- expect(flushStateEntry?.data?.compactionCount).toBe(0);
77
-
78
- // Same compaction cycle: should not flush again.
79
- await invokeFlush();
80
- expect(silentCallCount).toBe(1);
81
-
82
- // New compaction entry means a new cycle: flush should run again.
83
- branchEntries.push({
84
- type: "compaction",
85
- id: crypto.randomUUID(),
86
- parentId: null,
87
- timestamp: new Date().toISOString(),
88
- summary: "compacted",
89
- firstKeptEntryId: "abc",
90
- tokensBefore: 123,
91
- });
92
-
93
- await invokeFlush();
94
- expect(silentCallCount).toBe(2);
95
- });
96
-
97
- test("skips flush when projected context is below threshold", async () => {
98
- const worker = new OpenClawWorker(mockWorkerConfig);
99
- const settingsManager = SettingsManager.inMemory();
100
-
101
- const sessionManager = {
102
- getBranch: () => [] as any,
103
- appendCustomEntry: () => undefined,
104
- } as any;
105
-
106
- let silentCallCount = 0;
107
- const session = {
108
- getContextUsage: () => ({
109
- tokens: 1000,
110
- contextWindow: 200000,
111
- percent: 0.5,
112
- usageTokens: 1000,
113
- trailingTokens: 0,
114
- lastUsageIndex: 1,
115
- }),
116
- messages: [],
117
- } as any;
118
-
119
- await (worker as any).maybeRunPreCompactionMemoryFlush({
120
- session,
121
- sessionManager,
122
- settingsManager,
123
- memoryFlushConfig: {
124
- enabled: true,
125
- softThresholdTokens: 4000,
126
- systemPrompt: "Session nearing compaction.",
127
- prompt: "Reply with NO_REPLY.",
128
- },
129
- incomingPromptText: "short prompt",
130
- incomingImageCount: 0,
131
- runSilentPrompt: async () => {
132
- silentCallCount += 1;
133
- },
134
- });
135
-
136
- expect(silentCallCount).toBe(0);
137
- });
138
- });
@@ -1,64 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import {
3
- estimatePromptTokenCost,
4
- resolveMemoryFlushConfig,
5
- } from "../openclaw/worker";
6
-
7
- describe("memory flush config", () => {
8
- test("uses defaults when config missing", () => {
9
- const cfg = resolveMemoryFlushConfig({});
10
- expect(cfg.enabled).toBe(true);
11
- expect(cfg.softThresholdTokens).toBe(4000);
12
- expect(cfg.systemPrompt).toBe(
13
- "Session nearing compaction. Store durable memories now."
14
- );
15
- expect(cfg.prompt).toContain("Reply with NO_REPLY");
16
- });
17
-
18
- test("uses configured memory flush options", () => {
19
- const cfg = resolveMemoryFlushConfig({
20
- compaction: {
21
- memoryFlush: {
22
- enabled: false,
23
- softThresholdTokens: 1234,
24
- systemPrompt: " custom system ",
25
- prompt: " custom prompt ",
26
- },
27
- },
28
- });
29
-
30
- expect(cfg).toEqual({
31
- enabled: false,
32
- softThresholdTokens: 1234,
33
- systemPrompt: "custom system",
34
- prompt: "custom prompt",
35
- });
36
- });
37
-
38
- test("falls back for invalid values", () => {
39
- const cfg = resolveMemoryFlushConfig({
40
- compaction: {
41
- memoryFlush: {
42
- enabled: "yes",
43
- softThresholdTokens: -10,
44
- systemPrompt: " ",
45
- prompt: 123,
46
- },
47
- },
48
- } as unknown as Record<string, unknown>);
49
-
50
- expect(cfg.enabled).toBe(true);
51
- expect(cfg.softThresholdTokens).toBe(4000);
52
- expect(cfg.systemPrompt).toBe(
53
- "Session nearing compaction. Store durable memories now."
54
- );
55
- expect(cfg.prompt).toContain("Reply with NO_REPLY");
56
- });
57
- });
58
-
59
- describe("estimatePromptTokenCost", () => {
60
- test("includes text and image token estimates", () => {
61
- expect(estimatePromptTokenCost("1234", 0)).toBe(1);
62
- expect(estimatePromptTokenCost("1234", 2)).toBe(2401);
63
- });
64
- });
@@ -1,156 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import {
3
- DEFAULT_PROVIDER_BASE_URL_ENV,
4
- DEFAULT_PROVIDER_MODELS,
5
- PROVIDER_REGISTRY_ALIASES,
6
- registerDynamicProvider,
7
- resolveModelRef,
8
- } from "../openclaw/model-resolver";
9
-
10
- describe("resolveModelRef", () => {
11
- let originalDefaultModel: string | undefined;
12
- let originalDefaultProvider: string | undefined;
13
-
14
- beforeEach(() => {
15
- originalDefaultModel = process.env.AGENT_DEFAULT_MODEL;
16
- originalDefaultProvider = process.env.AGENT_DEFAULT_PROVIDER;
17
- delete process.env.AGENT_DEFAULT_MODEL;
18
- delete process.env.AGENT_DEFAULT_PROVIDER;
19
- });
20
-
21
- afterEach(() => {
22
- if (originalDefaultModel !== undefined)
23
- process.env.AGENT_DEFAULT_MODEL = originalDefaultModel;
24
- else delete process.env.AGENT_DEFAULT_MODEL;
25
- if (originalDefaultProvider !== undefined)
26
- process.env.AGENT_DEFAULT_PROVIDER = originalDefaultProvider;
27
- else delete process.env.AGENT_DEFAULT_PROVIDER;
28
- });
29
-
30
- test("parses provider/model format", () => {
31
- const result = resolveModelRef("anthropic/claude-sonnet-4-20250514");
32
- expect(result.provider).toBe("anthropic");
33
- expect(result.modelId).toBe("claude-sonnet-4-20250514");
34
- });
35
-
36
- test("handles model with slashes (e.g. provider/org/model)", () => {
37
- const result = resolveModelRef("openai/gpt-4.1");
38
- expect(result.provider).toBe("openai");
39
- expect(result.modelId).toBe("gpt-4.1");
40
- });
41
-
42
- test("resolves 'auto' to provider default model", () => {
43
- const result = resolveModelRef("anthropic/auto");
44
- expect(result.provider).toBe("anthropic");
45
- expect(result.modelId).toBe(DEFAULT_PROVIDER_MODELS.anthropic);
46
- });
47
-
48
- test("uses AGENT_DEFAULT_PROVIDER for bare model ID", () => {
49
- process.env.AGENT_DEFAULT_PROVIDER = "openai";
50
- const result = resolveModelRef("gpt-4.1");
51
- expect(result.provider).toBe("openai");
52
- expect(result.modelId).toBe("gpt-4.1");
53
- });
54
-
55
- test("falls back to AGENT_DEFAULT_MODEL when rawModelRef is empty", () => {
56
- process.env.AGENT_DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514";
57
- const result = resolveModelRef("");
58
- expect(result.provider).toBe("anthropic");
59
- expect(result.modelId).toBe("claude-sonnet-4-20250514");
60
- });
61
-
62
- test("falls back to provider default when no model or AGENT_DEFAULT_MODEL", () => {
63
- process.env.AGENT_DEFAULT_PROVIDER = "google";
64
- const result = resolveModelRef("");
65
- expect(result.provider).toBe("google");
66
- expect(result.modelId).toBe(DEFAULT_PROVIDER_MODELS.google);
67
- });
68
-
69
- test("throws when no model can be determined", () => {
70
- expect(() => resolveModelRef("")).toThrow("No model configured");
71
- });
72
-
73
- test("throws when bare model ID and no default provider", () => {
74
- expect(() => resolveModelRef("some-model")).toThrow(
75
- 'No provider specified for model "some-model"'
76
- );
77
- });
78
-
79
- test("trims whitespace from rawModelRef", () => {
80
- const result = resolveModelRef(" anthropic/claude-sonnet-4-20250514 ");
81
- expect(result.provider).toBe("anthropic");
82
- });
83
- });
84
-
85
- describe("registerDynamicProvider", () => {
86
- const testProviderId = `test-provider-${Date.now()}`;
87
-
88
- afterEach(() => {
89
- // Clean up test provider entries
90
- delete DEFAULT_PROVIDER_BASE_URL_ENV[testProviderId];
91
- delete DEFAULT_PROVIDER_MODELS[testProviderId];
92
- delete PROVIDER_REGISTRY_ALIASES[testProviderId];
93
- });
94
-
95
- test("registers new provider with baseUrlEnvVar", () => {
96
- registerDynamicProvider(testProviderId, {
97
- baseUrlEnvVar: "TEST_BASE_URL",
98
- sdkCompat: "openai",
99
- });
100
- expect(DEFAULT_PROVIDER_BASE_URL_ENV[testProviderId]).toBe("TEST_BASE_URL");
101
- });
102
-
103
- test("registers default model when provided", () => {
104
- registerDynamicProvider(testProviderId, {
105
- baseUrlEnvVar: "TEST_BASE_URL",
106
- defaultModel: "test-model-v1",
107
- });
108
- expect(DEFAULT_PROVIDER_MODELS[testProviderId]).toBe("test-model-v1");
109
- });
110
-
111
- test("sets registry alias for openai-compatible providers", () => {
112
- registerDynamicProvider(testProviderId, {
113
- baseUrlEnvVar: "TEST_BASE_URL",
114
- sdkCompat: "openai",
115
- });
116
- expect(PROVIDER_REGISTRY_ALIASES[testProviderId]).toBe("openai");
117
- });
118
-
119
- test("uses explicit registryAlias over sdkCompat", () => {
120
- registerDynamicProvider(testProviderId, {
121
- baseUrlEnvVar: "TEST_BASE_URL",
122
- sdkCompat: "openai",
123
- registryAlias: "custom",
124
- });
125
- expect(PROVIDER_REGISTRY_ALIASES[testProviderId]).toBe("custom");
126
- });
127
-
128
- test("skips already-registered provider", () => {
129
- DEFAULT_PROVIDER_BASE_URL_ENV[testProviderId] = "EXISTING";
130
- registerDynamicProvider(testProviderId, {
131
- baseUrlEnvVar: "NEW_VALUE",
132
- });
133
- expect(DEFAULT_PROVIDER_BASE_URL_ENV[testProviderId]).toBe("EXISTING");
134
- });
135
-
136
- test("does not set alias when no sdkCompat or registryAlias", () => {
137
- registerDynamicProvider(testProviderId, {
138
- baseUrlEnvVar: "TEST_BASE_URL",
139
- });
140
- expect(PROVIDER_REGISTRY_ALIASES[testProviderId]).toBeUndefined();
141
- });
142
- });
143
-
144
- describe("DEFAULT_PROVIDER_MODELS", () => {
145
- test("contains expected providers", () => {
146
- expect(DEFAULT_PROVIDER_MODELS.anthropic).toBeDefined();
147
- expect(DEFAULT_PROVIDER_MODELS.openai).toBeDefined();
148
- expect(DEFAULT_PROVIDER_MODELS.google).toBeDefined();
149
- });
150
- });
151
-
152
- describe("DEFAULT_PROVIDER_BASE_URL_ENV", () => {
153
- test("maps anthropic to ANTHROPIC_BASE_URL", () => {
154
- expect(DEFAULT_PROVIDER_BASE_URL_ENV.anthropic).toBe("ANTHROPIC_BASE_URL");
155
- });
156
- });
@@ -1,225 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { OpenClawProgressProcessor } from "../openclaw/processor";
3
-
4
- function makeEvent(type: string, extra: Record<string, any> = {}): any {
5
- return { type, ...extra };
6
- }
7
-
8
- function makeMessageUpdate(
9
- assistantEventType: string,
10
- delta = "",
11
- role = "assistant"
12
- ): any {
13
- return makeEvent("message_update", {
14
- message: { role },
15
- assistantMessageEvent: { type: assistantEventType, delta },
16
- });
17
- }
18
-
19
- describe("OpenClawProgressProcessor", () => {
20
- test("processEvent returns false for non-assistant message_update", () => {
21
- const p = new OpenClawProgressProcessor();
22
- const event = makeMessageUpdate("text_delta", "hi", "user");
23
- expect(p.processEvent(event)).toBe(false);
24
- });
25
-
26
- test("text_delta appends to output", () => {
27
- const p = new OpenClawProgressProcessor();
28
- p.processEvent(makeMessageUpdate("text_delta", "Hello"));
29
- p.processEvent(makeMessageUpdate("text_delta", " world"));
30
- expect(p.getDelta()).toBe("Hello world");
31
- });
32
-
33
- test("thinking_delta does not append by default", () => {
34
- const p = new OpenClawProgressProcessor();
35
- const result = p.processEvent(
36
- makeMessageUpdate("thinking_delta", "thinking...")
37
- );
38
- expect(result).toBe(false);
39
- expect(p.getDelta()).toBeNull();
40
- });
41
-
42
- test("thinking_delta appends when verbose", () => {
43
- const p = new OpenClawProgressProcessor();
44
- p.setVerboseLogging(true);
45
- const result = p.processEvent(
46
- makeMessageUpdate("thinking_delta", "thinking...")
47
- );
48
- expect(result).toBe(true);
49
- expect(p.getDelta()).toContain("thinking...");
50
- });
51
-
52
- test("thinking_start/end output in verbose mode only", () => {
53
- const p = new OpenClawProgressProcessor();
54
- expect(p.processEvent(makeMessageUpdate("thinking_start"))).toBe(false);
55
- expect(p.processEvent(makeMessageUpdate("thinking_end"))).toBe(false);
56
-
57
- p.setVerboseLogging(true);
58
- expect(p.processEvent(makeMessageUpdate("thinking_start"))).toBe(true);
59
- expect(p.processEvent(makeMessageUpdate("thinking_end"))).toBe(true);
60
- });
61
-
62
- test("getCurrentThinking tracks thinking content", () => {
63
- const p = new OpenClawProgressProcessor();
64
- expect(p.getCurrentThinking()).toBeNull();
65
- p.processEvent(makeMessageUpdate("thinking_delta", "step 1"));
66
- expect(p.getCurrentThinking()).toBe("step 1");
67
- });
68
-
69
- test("message_end with error stores fatal error", () => {
70
- const p = new OpenClawProgressProcessor();
71
- const event = makeEvent("message_end", {
72
- message: {
73
- role: "assistant",
74
- stopReason: "error",
75
- errorMessage: "Something broke",
76
- },
77
- });
78
- expect(p.processEvent(event)).toBe(false);
79
- expect(p.consumeFatalErrorMessage()).toBe("Something broke");
80
- // Consumed, so second call returns null
81
- expect(p.consumeFatalErrorMessage()).toBeNull();
82
- });
83
-
84
- test("message_end extracts text when no streaming happened", () => {
85
- const p = new OpenClawProgressProcessor();
86
- const event = makeEvent("message_end", {
87
- message: {
88
- role: "assistant",
89
- content: [{ type: "text", text: "Final answer" }],
90
- },
91
- });
92
- expect(p.processEvent(event)).toBe(true);
93
- expect(p.getDelta()).toContain("Final answer");
94
- });
95
-
96
- test("message_end skips extraction if text already streamed", () => {
97
- const p = new OpenClawProgressProcessor();
98
- // Stream some text first
99
- p.processEvent(makeMessageUpdate("text_delta", "streamed"));
100
- p.getDelta(); // consume
101
- // Now message_end should skip
102
- const event = makeEvent("message_end", {
103
- message: {
104
- role: "assistant",
105
- content: [{ type: "text", text: "Final" }],
106
- },
107
- });
108
- expect(p.processEvent(event)).toBe(false);
109
- });
110
-
111
- test("auto_compaction_start appends message", () => {
112
- const p = new OpenClawProgressProcessor();
113
- expect(p.processEvent(makeEvent("auto_compaction_start"))).toBe(true);
114
- expect(p.getDelta()).toContain("Compacting context");
115
- });
116
-
117
- test("auto_compaction_end with aborted", () => {
118
- const p = new OpenClawProgressProcessor();
119
- expect(
120
- p.processEvent(makeEvent("auto_compaction_end", { aborted: true }))
121
- ).toBe(true);
122
- expect(p.getDelta()).toContain("Compaction aborted");
123
- });
124
-
125
- test("auto_compaction_end with result", () => {
126
- const p = new OpenClawProgressProcessor();
127
- expect(
128
- p.processEvent(makeEvent("auto_compaction_end", { result: {} }))
129
- ).toBe(true);
130
- expect(p.getDelta()).toContain("Context compacted");
131
- });
132
-
133
- test("auto_retry_start appends retry message", () => {
134
- const p = new OpenClawProgressProcessor();
135
- expect(
136
- p.processEvent(
137
- makeEvent("auto_retry_start", { attempt: 2, maxAttempts: 3 })
138
- )
139
- ).toBe(true);
140
- expect(p.getDelta()).toContain("Retrying (attempt 2/3)");
141
- });
142
-
143
- test("auto_retry_end with failure appends error", () => {
144
- const p = new OpenClawProgressProcessor();
145
- expect(
146
- p.processEvent(
147
- makeEvent("auto_retry_end", {
148
- success: false,
149
- finalError: "timeout",
150
- })
151
- )
152
- ).toBe(true);
153
- expect(p.getDelta()).toContain("Retry failed: timeout");
154
- });
155
-
156
- test("auto_retry_end with success returns false", () => {
157
- const p = new OpenClawProgressProcessor();
158
- expect(p.processEvent(makeEvent("auto_retry_end", { success: true }))).toBe(
159
- false
160
- );
161
- });
162
-
163
- test("unknown event type returns false", () => {
164
- const p = new OpenClawProgressProcessor();
165
- expect(p.processEvent(makeEvent("unknown_event"))).toBe(false);
166
- });
167
- });
168
-
169
- describe("getDelta", () => {
170
- test("returns null when no content", () => {
171
- const p = new OpenClawProgressProcessor();
172
- expect(p.getDelta()).toBeNull();
173
- });
174
-
175
- test("returns null when content unchanged", () => {
176
- const p = new OpenClawProgressProcessor();
177
- p.processEvent(makeMessageUpdate("text_delta", "hello"));
178
- p.getDelta(); // consume
179
- expect(p.getDelta()).toBeNull();
180
- });
181
-
182
- test("returns only the new suffix on incremental append", () => {
183
- const p = new OpenClawProgressProcessor();
184
- p.processEvent(makeMessageUpdate("text_delta", "Hello"));
185
- p.getDelta(); // consume "Hello"
186
- p.processEvent(makeMessageUpdate("text_delta", " world"));
187
- expect(p.getDelta()).toBe(" world");
188
- });
189
-
190
- test("returns full content on first call", () => {
191
- const p = new OpenClawProgressProcessor();
192
- p.processEvent(makeMessageUpdate("text_delta", "first"));
193
- expect(p.getDelta()).toBe("first");
194
- });
195
- });
196
-
197
- describe("finalResult lifecycle", () => {
198
- test("set and get final result", () => {
199
- const p = new OpenClawProgressProcessor();
200
- expect(p.getFinalResult()).toBeNull();
201
-
202
- p.setFinalResult({ text: "done", isFinal: true });
203
- const result = p.getFinalResult();
204
- expect(result).toEqual({ text: "done", isFinal: true });
205
-
206
- // Consumed, so next call returns null
207
- expect(p.getFinalResult()).toBeNull();
208
- });
209
- });
210
-
211
- describe("reset", () => {
212
- test("clears all state", () => {
213
- const p = new OpenClawProgressProcessor();
214
- p.processEvent(makeMessageUpdate("text_delta", "content"));
215
- p.processEvent(makeMessageUpdate("thinking_delta", "thought"));
216
- p.setFinalResult({ text: "done", isFinal: true });
217
-
218
- p.reset();
219
-
220
- expect(p.getDelta()).toBeNull();
221
- expect(p.getCurrentThinking()).toBeNull();
222
- expect(p.getFinalResult()).toBeNull();
223
- expect(p.consumeFatalErrorMessage()).toBeNull();
224
- });
225
- });
@@ -1,109 +0,0 @@
1
- /**
2
- * Test setup and configuration for worker tests.
3
- *
4
- * Shared mocks live in @lobu/core fixtures.
5
- * This file re-exports them and adds worker-specific helpers.
6
- */
7
-
8
- import { afterAll, beforeAll } from "bun:test";
9
- import {
10
- createWorkerConfig,
11
- mockFetch as sharedMockFetch,
12
- } from "@lobu/core/testing";
13
-
14
- export {
15
- createInstructionContext,
16
- createWorkerConfig,
17
- MockRedisClient,
18
- mockFetch,
19
- } from "@lobu/core/testing";
20
-
21
- export const mockWorkerConfig = createWorkerConfig();
22
-
23
- // Mock environment variables for testing
24
- const mockEnvVars: Record<string, string> = {
25
- DISPATCHER_URL: "https://test-dispatcher.example.com",
26
- WORKER_TOKEN: "test-worker-token-123",
27
- CONVERSATION_ID: "1234567890.123456",
28
- WORKER_SESSION_KEY: "test-session-key",
29
- WORKER_USER_ID: "U1234567890",
30
- WORKER_CHANNEL_ID: "C1234567890",
31
- WORKER_USER_PROMPT: Buffer.from("Test user prompt").toString("base64"),
32
- WORKER_RESPONSE_CHANNEL: "C1234567890",
33
- WORKER_RESPONSE_TS: "1234567890.123457",
34
- WORKER_CLAUDE_OPTIONS: JSON.stringify({
35
- model: "claude-sonnet-4-20250514",
36
- max_tokens: 8192,
37
- }),
38
- WORKER_TEAM_ID: "T1234567890",
39
- WORKER_WORKSPACE_BASE_DIRECTORY: "/tmp/test-workspace",
40
- };
41
-
42
- export class TestHelpers {
43
- static mockFetch(responses?: Record<string, any>): () => void {
44
- return sharedMockFetch(responses);
45
- }
46
-
47
- static createMockProgressUpdate(
48
- type: "output" | "completion" | "error",
49
- data: any
50
- ) {
51
- return { type, data, timestamp: Date.now() };
52
- }
53
-
54
- static async delay(ms: number): Promise<void> {
55
- return new Promise((resolve) => setTimeout(resolve, ms));
56
- }
57
-
58
- static mockEventSource() {
59
- class MockEventSource {
60
- url: string;
61
- readyState = 1;
62
- onopen: ((event: Event) => void) | null = null;
63
- onmessage: ((event: MessageEvent) => void) | null = null;
64
- onerror: ((event: Event) => void) | null = null;
65
-
66
- constructor(url: string) {
67
- this.url = url;
68
- setTimeout(() => {
69
- if (this.onopen) this.onopen(new Event("open"));
70
- }, 10);
71
- }
72
-
73
- close() {
74
- this.readyState = 2;
75
- }
76
-
77
- simulateMessage(data: any) {
78
- if (this.onmessage) {
79
- this.onmessage(
80
- new MessageEvent("message", { data: JSON.stringify(data) })
81
- );
82
- }
83
- }
84
-
85
- simulateError() {
86
- if (this.onerror) this.onerror(new Event("error"));
87
- }
88
- }
89
-
90
- const originalEventSource = globalThis.EventSource;
91
- globalThis.EventSource = MockEventSource as any;
92
- return () => {
93
- globalThis.EventSource = originalEventSource;
94
- };
95
- }
96
- }
97
-
98
- // Global test setup
99
- beforeAll(() => {
100
- for (const [key, value] of Object.entries(mockEnvVars)) {
101
- process.env[key] = value;
102
- }
103
- });
104
-
105
- afterAll(() => {
106
- for (const key of Object.keys(mockEnvVars)) {
107
- delete process.env[key];
108
- }
109
- });