@lobu/worker 3.0.9 → 3.0.12
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/dist/openclaw/session-context.d.ts.map +1 -1
- package/dist/openclaw/session-context.js +1 -1
- package/dist/openclaw/session-context.js.map +1 -1
- package/package.json +10 -9
- package/USAGE.md +0 -120
- package/docs/custom-base-image.md +0 -88
- package/scripts/worker-entrypoint.sh +0 -184
- package/src/__tests__/audio-provider-suggestions.test.ts +0 -198
- package/src/__tests__/embedded-just-bash-bootstrap.test.ts +0 -39
- package/src/__tests__/embedded-tools.test.ts +0 -558
- package/src/__tests__/instructions.test.ts +0 -59
- package/src/__tests__/memory-flush-runtime.test.ts +0 -138
- package/src/__tests__/memory-flush.test.ts +0 -64
- package/src/__tests__/model-resolver.test.ts +0 -156
- package/src/__tests__/processor.test.ts +0 -225
- package/src/__tests__/setup.ts +0 -109
- package/src/__tests__/sse-client.test.ts +0 -48
- package/src/__tests__/tool-policy.test.ts +0 -269
- package/src/__tests__/worker.test.ts +0 -89
- package/src/core/error-handler.ts +0 -70
- package/src/core/project-scanner.ts +0 -65
- package/src/core/types.ts +0 -125
- package/src/core/url-utils.ts +0 -9
- package/src/core/workspace.ts +0 -138
- package/src/embedded/just-bash-bootstrap.ts +0 -228
- package/src/gateway/gateway-integration.ts +0 -287
- package/src/gateway/message-batcher.ts +0 -128
- package/src/gateway/sse-client.ts +0 -955
- package/src/gateway/types.ts +0 -68
- package/src/index.ts +0 -144
- package/src/instructions/builder.ts +0 -80
- package/src/instructions/providers.ts +0 -27
- package/src/modules/lifecycle.ts +0 -92
- package/src/openclaw/custom-tools.ts +0 -290
- package/src/openclaw/instructions.ts +0 -38
- package/src/openclaw/model-resolver.ts +0 -150
- package/src/openclaw/plugin-loader.ts +0 -427
- package/src/openclaw/processor.ts +0 -216
- package/src/openclaw/session-context.ts +0 -277
- package/src/openclaw/tool-policy.ts +0 -212
- package/src/openclaw/tools.ts +0 -208
- package/src/openclaw/worker.ts +0 -1792
- package/src/server.ts +0 -329
- package/src/shared/audio-provider-suggestions.ts +0 -132
- package/src/shared/processor-utils.ts +0 -33
- package/src/shared/provider-auth-hints.ts +0 -64
- package/src/shared/tool-display-config.ts +0 -75
- package/src/shared/tool-implementations.ts +0 -768
- package/tsconfig.json +0 -21
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type InstructionContext,
|
|
3
|
-
type InstructionProvider,
|
|
4
|
-
renderAlwaysOnToolPolicyRules,
|
|
5
|
-
renderBaselineAgentPolicy,
|
|
6
|
-
renderDetectedToolIntentRules,
|
|
7
|
-
} from "@lobu/core";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* OpenClaw core instructions
|
|
11
|
-
*/
|
|
12
|
-
export class OpenClawCoreInstructionProvider implements InstructionProvider {
|
|
13
|
-
name = "core";
|
|
14
|
-
priority = 10;
|
|
15
|
-
|
|
16
|
-
getInstructions(context: InstructionContext): string {
|
|
17
|
-
return [
|
|
18
|
-
`You are a Lobu agent for user ${context.userId}.`,
|
|
19
|
-
`Working directory: ${context.workingDirectory}`,
|
|
20
|
-
renderBaselineAgentPolicy(),
|
|
21
|
-
renderAlwaysOnToolPolicyRules(),
|
|
22
|
-
`## Image Analysis
|
|
23
|
-
|
|
24
|
-
If the user asks to analyze an uploaded image, use the image content already attached to the prompt and provide direct analysis.`,
|
|
25
|
-
].join("\n\n");
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class OpenClawPromptIntentInstructionProvider
|
|
30
|
-
implements InstructionProvider
|
|
31
|
-
{
|
|
32
|
-
name = "prompt-intent";
|
|
33
|
-
priority = 15;
|
|
34
|
-
|
|
35
|
-
getInstructions(context: InstructionContext): string {
|
|
36
|
-
return renderDetectedToolIntentRules(context.userPrompt || "");
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model resolution and session management helpers.
|
|
3
|
-
* Extracted from worker.ts for clarity.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as fs from "node:fs/promises";
|
|
7
|
-
import * as path from "node:path";
|
|
8
|
-
import { type ConfigProviderMeta, createLogger } from "@lobu/core";
|
|
9
|
-
import { SessionManager } from "@mariozechner/pi-coding-agent";
|
|
10
|
-
|
|
11
|
-
const logger = createLogger("model-resolver");
|
|
12
|
-
|
|
13
|
-
/** Hardcoded fallback map for provider base URL env vars. */
|
|
14
|
-
export const DEFAULT_PROVIDER_BASE_URL_ENV: Record<string, string> = {
|
|
15
|
-
anthropic: "ANTHROPIC_BASE_URL",
|
|
16
|
-
openai: "OPENAI_BASE_URL",
|
|
17
|
-
"openai-codex": "OPENAI_BASE_URL",
|
|
18
|
-
google: "GEMINI_API_BASE_URL",
|
|
19
|
-
nvidia: "NVIDIA_API_BASE_URL",
|
|
20
|
-
"z-ai": "Z_AI_API_BASE_URL",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** Default model IDs per provider, used when no explicit model is configured. */
|
|
24
|
-
export const DEFAULT_PROVIDER_MODELS: Record<string, string> = {
|
|
25
|
-
anthropic: "claude-sonnet-4-20250514",
|
|
26
|
-
openai: "gpt-4.1",
|
|
27
|
-
"openai-codex": "gpt-5.1-codex-max",
|
|
28
|
-
google: "gemini-2.5-pro",
|
|
29
|
-
nvidia: "nvidia/moonshotai/kimi-k2.5",
|
|
30
|
-
"z-ai": "glm-4.7",
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Map gateway provider slugs to model-registry provider names.
|
|
35
|
-
* The gateway uses slugs like "z-ai" while the model registry uses "zai".
|
|
36
|
-
*/
|
|
37
|
-
export const PROVIDER_REGISTRY_ALIASES: Record<string, string> = {
|
|
38
|
-
"z-ai": "zai",
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Register a config-driven provider at runtime.
|
|
43
|
-
* Extends the base URL env, default model, and registry alias maps
|
|
44
|
-
* so resolveModelRef() and the worker can handle the provider.
|
|
45
|
-
*/
|
|
46
|
-
export function registerDynamicProvider(
|
|
47
|
-
id: string,
|
|
48
|
-
config: ConfigProviderMeta
|
|
49
|
-
): void {
|
|
50
|
-
const alreadyRegistered = !!DEFAULT_PROVIDER_BASE_URL_ENV[id];
|
|
51
|
-
|
|
52
|
-
if (!alreadyRegistered) {
|
|
53
|
-
DEFAULT_PROVIDER_BASE_URL_ENV[id] = config.baseUrlEnvVar;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Always update default model and alias even for pre-registered providers
|
|
57
|
-
if (config.defaultModel && !DEFAULT_PROVIDER_MODELS[id]) {
|
|
58
|
-
DEFAULT_PROVIDER_MODELS[id] = config.defaultModel;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Map to model registry name: explicit alias, or "openai" for sdkCompat providers
|
|
62
|
-
if (!PROVIDER_REGISTRY_ALIASES[id]) {
|
|
63
|
-
const alias =
|
|
64
|
-
config.registryAlias ||
|
|
65
|
-
(config.sdkCompat === "openai" ? "openai" : undefined);
|
|
66
|
-
if (alias) {
|
|
67
|
-
PROVIDER_REGISTRY_ALIASES[id] = alias;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (alreadyRegistered) return;
|
|
72
|
-
|
|
73
|
-
logger.info(
|
|
74
|
-
`Registered dynamic provider: ${id} (baseUrlEnv=${config.baseUrlEnvVar}, sdkCompat=${config.sdkCompat || "none"})`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function resolveModelRef(
|
|
79
|
-
rawModelRef: string,
|
|
80
|
-
overrides?: { defaultModel?: string; defaultProvider?: string }
|
|
81
|
-
): {
|
|
82
|
-
provider: string;
|
|
83
|
-
modelId: string;
|
|
84
|
-
} {
|
|
85
|
-
const defaultModelRef =
|
|
86
|
-
overrides?.defaultModel || process.env.AGENT_DEFAULT_MODEL || "";
|
|
87
|
-
const defaultProvider =
|
|
88
|
-
overrides?.defaultProvider || process.env.AGENT_DEFAULT_PROVIDER || "";
|
|
89
|
-
|
|
90
|
-
const normalizedRaw = rawModelRef?.trim();
|
|
91
|
-
let modelRef = normalizedRaw || defaultModelRef;
|
|
92
|
-
|
|
93
|
-
// When no model is configured but a provider is known, use the provider's
|
|
94
|
-
// default model so auto-mode provider selection works end-to-end.
|
|
95
|
-
if (!modelRef && defaultProvider) {
|
|
96
|
-
const fallbackModel = DEFAULT_PROVIDER_MODELS[defaultProvider];
|
|
97
|
-
if (fallbackModel) {
|
|
98
|
-
logger.info(
|
|
99
|
-
`No model configured, using default for ${defaultProvider}: ${fallbackModel}`
|
|
100
|
-
);
|
|
101
|
-
modelRef = fallbackModel;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!modelRef) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
"No model configured. Ask an admin to connect a provider for the base agent."
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const parts = modelRef.split("/").filter(Boolean);
|
|
112
|
-
if (parts.length >= 2) {
|
|
113
|
-
const provider = parts[0]!;
|
|
114
|
-
let modelId = parts.slice(1).join("/");
|
|
115
|
-
// Resolve "auto" to the provider's default model
|
|
116
|
-
if (modelId === "auto") {
|
|
117
|
-
const fallback = DEFAULT_PROVIDER_MODELS[provider];
|
|
118
|
-
if (fallback) {
|
|
119
|
-
logger.info(`Resolved auto model for ${provider}: ${fallback}`);
|
|
120
|
-
modelId = fallback;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return { provider, modelId };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!defaultProvider) {
|
|
127
|
-
throw new Error(
|
|
128
|
-
`No provider specified for model "${modelRef}". Use "provider/model" format or set AGENT_DEFAULT_PROVIDER.`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return { provider: defaultProvider, modelId: modelRef };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export async function openOrCreateSessionManager(
|
|
136
|
-
sessionFile: string,
|
|
137
|
-
workspaceDir: string
|
|
138
|
-
): Promise<SessionManager> {
|
|
139
|
-
try {
|
|
140
|
-
await fs.stat(sessionFile);
|
|
141
|
-
return SessionManager.open(sessionFile);
|
|
142
|
-
} catch {
|
|
143
|
-
const sessionManager = SessionManager.create(
|
|
144
|
-
workspaceDir,
|
|
145
|
-
path.dirname(sessionFile)
|
|
146
|
-
);
|
|
147
|
-
sessionManager.setSessionFile(sessionFile);
|
|
148
|
-
return sessionManager;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenClaw plugin loader.
|
|
3
|
-
*
|
|
4
|
-
* Loads plugin modules by dynamic import and provides a compatibility shim.
|
|
5
|
-
* Supports both legacy function-style plugins and object-style plugins with
|
|
6
|
-
* a `register(api)` method.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
createLogger,
|
|
11
|
-
type PluginConfig,
|
|
12
|
-
type PluginManifest,
|
|
13
|
-
type PluginsConfig,
|
|
14
|
-
type ProviderRegistration,
|
|
15
|
-
} from "@lobu/core";
|
|
16
|
-
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
17
|
-
|
|
18
|
-
const logger = createLogger("openclaw-plugin-loader");
|
|
19
|
-
|
|
20
|
-
type PluginHookName = "before_agent_start" | "agent_end";
|
|
21
|
-
|
|
22
|
-
export type PluginHookHandler = (
|
|
23
|
-
event: Record<string, unknown>,
|
|
24
|
-
ctx: Record<string, unknown>
|
|
25
|
-
) => unknown | Promise<unknown>;
|
|
26
|
-
|
|
27
|
-
export interface PluginService {
|
|
28
|
-
id: string;
|
|
29
|
-
start?: () => unknown | Promise<unknown>;
|
|
30
|
-
stop?: () => unknown | Promise<unknown>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Result of loading a single plugin */
|
|
34
|
-
export interface LoadedPlugin {
|
|
35
|
-
manifest: PluginManifest;
|
|
36
|
-
/** Raw ToolDefinition objects captured from registerTool() — no bridging needed */
|
|
37
|
-
tools: ToolDefinition[];
|
|
38
|
-
providers: ProviderRegistration[];
|
|
39
|
-
hooks: Record<PluginHookName, PluginHookHandler[]>;
|
|
40
|
-
services: PluginService[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Load all enabled plugins from config.
|
|
45
|
-
*/
|
|
46
|
-
export async function loadPlugins(
|
|
47
|
-
config: PluginsConfig | undefined,
|
|
48
|
-
cwd?: string
|
|
49
|
-
): Promise<LoadedPlugin[]> {
|
|
50
|
-
if (!config?.plugins?.length) {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const enabledPlugins = config.plugins.filter((p) => p.enabled !== false);
|
|
55
|
-
if (enabledPlugins.length === 0) {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
logger.info(`Loading ${enabledPlugins.length} plugin(s)`);
|
|
60
|
-
|
|
61
|
-
const results: LoadedPlugin[] = [];
|
|
62
|
-
|
|
63
|
-
for (const pluginConfig of enabledPlugins) {
|
|
64
|
-
try {
|
|
65
|
-
const loaded = await loadSinglePlugin(pluginConfig, cwd);
|
|
66
|
-
if (loaded) {
|
|
67
|
-
results.push(loaded);
|
|
68
|
-
const parts = [];
|
|
69
|
-
if (loaded.tools.length > 0)
|
|
70
|
-
parts.push(`${loaded.tools.length} tool(s)`);
|
|
71
|
-
if (loaded.providers.length > 0)
|
|
72
|
-
parts.push(`${loaded.providers.length} provider(s)`);
|
|
73
|
-
const hookCount =
|
|
74
|
-
loaded.hooks.before_agent_start.length +
|
|
75
|
-
loaded.hooks.agent_end.length;
|
|
76
|
-
if (hookCount > 0) parts.push(`${hookCount} hook(s)`);
|
|
77
|
-
if (loaded.services.length > 0)
|
|
78
|
-
parts.push(`${loaded.services.length} service(s)`);
|
|
79
|
-
logger.info(
|
|
80
|
-
`Loaded plugin "${loaded.manifest.name}" with ${parts.join(", ") || "no registrations"}`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
} catch (err) {
|
|
84
|
-
logger.error(
|
|
85
|
-
`Failed to load plugin "${pluginConfig.source}": ${err instanceof Error ? err.message : String(err)}`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return results;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Load a single plugin by resolving its module and invoking its factory.
|
|
95
|
-
*/
|
|
96
|
-
async function loadSinglePlugin(
|
|
97
|
-
config: PluginConfig,
|
|
98
|
-
cwd?: string
|
|
99
|
-
): Promise<LoadedPlugin | null> {
|
|
100
|
-
const { source, slot, config: pluginConfig } = config;
|
|
101
|
-
|
|
102
|
-
let mod: Record<string, unknown>;
|
|
103
|
-
try {
|
|
104
|
-
mod = (await import(source)) as Record<string, unknown>;
|
|
105
|
-
} catch (err) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Cannot import "${source}": ${err instanceof Error ? err.message : String(err)}`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const pluginEntrypoint = resolvePluginEntrypoint(mod);
|
|
112
|
-
if (!pluginEntrypoint) {
|
|
113
|
-
logger.warn(`Plugin "${source}" has no registerable entrypoint - skipping`);
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const capturedTools: ToolDefinition[] = [];
|
|
118
|
-
const capturedProviders: ProviderRegistration[] = [];
|
|
119
|
-
const capturedHooks: Record<PluginHookName, PluginHookHandler[]> = {
|
|
120
|
-
before_agent_start: [],
|
|
121
|
-
agent_end: [],
|
|
122
|
-
};
|
|
123
|
-
const capturedServices: PluginService[] = [];
|
|
124
|
-
const shimApi = createShimApi({
|
|
125
|
-
source,
|
|
126
|
-
pluginConfig: pluginConfig ?? {},
|
|
127
|
-
capturedTools,
|
|
128
|
-
capturedProviders,
|
|
129
|
-
capturedHooks,
|
|
130
|
-
capturedServices,
|
|
131
|
-
cwd,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
await Promise.resolve(pluginEntrypoint.register(shimApi));
|
|
135
|
-
const pluginName =
|
|
136
|
-
readStringProperty(pluginEntrypoint.metadata, "name") ||
|
|
137
|
-
extractPluginName(source);
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
manifest: {
|
|
141
|
-
source,
|
|
142
|
-
slot,
|
|
143
|
-
name: pluginName,
|
|
144
|
-
},
|
|
145
|
-
tools: capturedTools,
|
|
146
|
-
providers: capturedProviders,
|
|
147
|
-
hooks: capturedHooks,
|
|
148
|
-
services: capturedServices,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Resolve plugin entrypoint from module exports.
|
|
154
|
-
* Supports:
|
|
155
|
-
* - default export function (legacy)
|
|
156
|
-
* - default export object with register(api)
|
|
157
|
-
* - named register/init functions
|
|
158
|
-
*/
|
|
159
|
-
function resolvePluginEntrypoint(mod: Record<string, unknown>): {
|
|
160
|
-
register: (api: unknown) => void | Promise<void>;
|
|
161
|
-
metadata?: Record<string, unknown>;
|
|
162
|
-
} | null {
|
|
163
|
-
const defaultExport = mod.default;
|
|
164
|
-
if (typeof defaultExport === "function") {
|
|
165
|
-
return {
|
|
166
|
-
register: defaultExport as (api: unknown) => void | Promise<void>,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (isRecord(defaultExport) && typeof defaultExport.register === "function") {
|
|
171
|
-
return {
|
|
172
|
-
register: defaultExport.register as (
|
|
173
|
-
api: unknown
|
|
174
|
-
) => void | Promise<void>,
|
|
175
|
-
metadata: defaultExport,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
for (const name of ["register", "init"]) {
|
|
180
|
-
const fn = mod[name];
|
|
181
|
-
if (typeof fn === "function") {
|
|
182
|
-
return {
|
|
183
|
-
register: fn as (api: unknown) => void | Promise<void>,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
192
|
-
return typeof value === "object" && value !== null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function readStringProperty(
|
|
196
|
-
obj: Record<string, unknown> | undefined,
|
|
197
|
-
key: string
|
|
198
|
-
): string | undefined {
|
|
199
|
-
if (!obj) return undefined;
|
|
200
|
-
const value = obj[key];
|
|
201
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Create a shim API that captures tool/provider/hook/service registrations.
|
|
206
|
-
* Non-worker capabilities are no-oped for compatibility.
|
|
207
|
-
*/
|
|
208
|
-
function createShimApi(params: {
|
|
209
|
-
source: string;
|
|
210
|
-
pluginConfig: Record<string, unknown>;
|
|
211
|
-
capturedTools: ToolDefinition[];
|
|
212
|
-
capturedProviders: ProviderRegistration[];
|
|
213
|
-
capturedHooks: Record<PluginHookName, PluginHookHandler[]>;
|
|
214
|
-
capturedServices: PluginService[];
|
|
215
|
-
cwd?: string;
|
|
216
|
-
}): Record<string, unknown> {
|
|
217
|
-
const {
|
|
218
|
-
source,
|
|
219
|
-
pluginConfig,
|
|
220
|
-
capturedTools,
|
|
221
|
-
capturedProviders,
|
|
222
|
-
capturedHooks,
|
|
223
|
-
capturedServices,
|
|
224
|
-
cwd,
|
|
225
|
-
} = params;
|
|
226
|
-
const noop = () => {
|
|
227
|
-
/* intentional no-op */
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const shimLogger = {
|
|
231
|
-
info(message: string, ...args: unknown[]) {
|
|
232
|
-
logger.info(`[plugin:${extractPluginName(source)}] ${message}`, ...args);
|
|
233
|
-
},
|
|
234
|
-
warn(message: string, ...args: unknown[]) {
|
|
235
|
-
logger.warn(`[plugin:${extractPluginName(source)}] ${message}`, ...args);
|
|
236
|
-
},
|
|
237
|
-
error(message: string, ...args: unknown[]) {
|
|
238
|
-
logger.error(`[plugin:${extractPluginName(source)}] ${message}`, ...args);
|
|
239
|
-
},
|
|
240
|
-
debug(message: string, ...args: unknown[]) {
|
|
241
|
-
logger.debug(`[plugin:${extractPluginName(source)}] ${message}`, ...args);
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
pluginConfig,
|
|
247
|
-
logger: shimLogger,
|
|
248
|
-
|
|
249
|
-
on(eventName: unknown, handler: unknown) {
|
|
250
|
-
if (
|
|
251
|
-
(eventName === "before_agent_start" || eventName === "agent_end") &&
|
|
252
|
-
typeof handler === "function"
|
|
253
|
-
) {
|
|
254
|
-
capturedHooks[eventName].push(handler as PluginHookHandler);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
logger.debug(
|
|
258
|
-
`Plugin "${source}" registered unsupported hook "${String(eventName)}"`
|
|
259
|
-
);
|
|
260
|
-
},
|
|
261
|
-
|
|
262
|
-
// Capture tool registrations as-is (full ToolDefinition passthrough)
|
|
263
|
-
registerTool(toolDef: Record<string, unknown>) {
|
|
264
|
-
if (
|
|
265
|
-
typeof toolDef.name !== "string" ||
|
|
266
|
-
typeof toolDef.description !== "string" ||
|
|
267
|
-
typeof toolDef.execute !== "function"
|
|
268
|
-
) {
|
|
269
|
-
logger.warn(
|
|
270
|
-
"Plugin registered invalid tool - missing name, description, or execute"
|
|
271
|
-
);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Store the full ToolDefinition object — name, label, description,
|
|
276
|
-
// parameters, execute, renderCall, renderResult all preserved.
|
|
277
|
-
capturedTools.push(toolDef as unknown as ToolDefinition);
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
// Capture provider registrations (passed through to ModelRegistry)
|
|
281
|
-
registerProvider(name: unknown, config: unknown) {
|
|
282
|
-
if (typeof name !== "string" || !name.trim()) {
|
|
283
|
-
logger.warn("Plugin registered provider with invalid name");
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
if (typeof config !== "object" || config === null) {
|
|
287
|
-
logger.warn(`Plugin registered provider "${name}" with invalid config`);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
capturedProviders.push({
|
|
292
|
-
name: name.trim(),
|
|
293
|
-
config: config as Record<string, unknown>,
|
|
294
|
-
});
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
registerService(service: unknown) {
|
|
298
|
-
if (!isRecord(service)) {
|
|
299
|
-
logger.warn(`Plugin "${source}" registered invalid service`);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
const id = readStringProperty(service, "id");
|
|
303
|
-
if (!id) {
|
|
304
|
-
logger.warn(`Plugin "${source}" registered service without valid id`);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const start =
|
|
308
|
-
typeof service.start === "function"
|
|
309
|
-
? (service.start as () => unknown | Promise<unknown>)
|
|
310
|
-
: undefined;
|
|
311
|
-
const stop =
|
|
312
|
-
typeof service.stop === "function"
|
|
313
|
-
? (service.stop as () => unknown | Promise<unknown>)
|
|
314
|
-
: undefined;
|
|
315
|
-
capturedServices.push({ id, start, stop });
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
// No-op compatibility methods (worker runtime does not expose these surfaces)
|
|
319
|
-
registerCli: noop,
|
|
320
|
-
registerCommand: noop,
|
|
321
|
-
registerShortcut: noop,
|
|
322
|
-
registerFlag: noop,
|
|
323
|
-
registerChannel: noop,
|
|
324
|
-
registerMessageRenderer: noop,
|
|
325
|
-
sendMessage: noop,
|
|
326
|
-
sendUserMessage: noop,
|
|
327
|
-
appendEntry: noop,
|
|
328
|
-
setSessionName: noop,
|
|
329
|
-
getSessionName: () => undefined,
|
|
330
|
-
setLabel: noop,
|
|
331
|
-
exec: async () => ({
|
|
332
|
-
exitCode: 1,
|
|
333
|
-
stdout: "",
|
|
334
|
-
stderr: "exec is not supported in Lobu worker plugin shim",
|
|
335
|
-
}),
|
|
336
|
-
getActiveTools: () => [] as string[],
|
|
337
|
-
getAllTools: () => [] as Array<{ name: string; description: string }>,
|
|
338
|
-
setActiveTools: noop,
|
|
339
|
-
getCommands: () => [] as unknown[],
|
|
340
|
-
setModel: async () => false,
|
|
341
|
-
getThinkingLevel: () => "medium",
|
|
342
|
-
setThinkingLevel: noop,
|
|
343
|
-
events: {
|
|
344
|
-
on: noop,
|
|
345
|
-
off: noop,
|
|
346
|
-
emit: noop,
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
// Expose minimal context that plugins might read
|
|
350
|
-
cwd: cwd || process.cwd(),
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
export async function runPluginHooks(params: {
|
|
355
|
-
plugins: LoadedPlugin[];
|
|
356
|
-
hook: PluginHookName;
|
|
357
|
-
event: Record<string, unknown>;
|
|
358
|
-
ctx: Record<string, unknown>;
|
|
359
|
-
}): Promise<unknown[]> {
|
|
360
|
-
const { plugins, hook, event, ctx } = params;
|
|
361
|
-
const results: unknown[] = [];
|
|
362
|
-
for (const plugin of plugins) {
|
|
363
|
-
const handlers = plugin.hooks[hook];
|
|
364
|
-
if (handlers.length === 0) continue;
|
|
365
|
-
|
|
366
|
-
for (const handler of handlers) {
|
|
367
|
-
try {
|
|
368
|
-
const result = await Promise.resolve(handler(event, ctx));
|
|
369
|
-
results.push(result);
|
|
370
|
-
} catch (err) {
|
|
371
|
-
logger.error(
|
|
372
|
-
`Plugin hook "${hook}" failed for "${plugin.manifest.name}": ${err instanceof Error ? err.message : String(err)}`
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return results;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export async function startPluginServices(
|
|
381
|
-
plugins: LoadedPlugin[]
|
|
382
|
-
): Promise<void> {
|
|
383
|
-
for (const plugin of plugins) {
|
|
384
|
-
for (const service of plugin.services) {
|
|
385
|
-
if (!service.start) continue;
|
|
386
|
-
try {
|
|
387
|
-
await Promise.resolve(service.start());
|
|
388
|
-
} catch (err) {
|
|
389
|
-
logger.error(
|
|
390
|
-
`Plugin service "${service.id}" failed to start (${plugin.manifest.name}): ${err instanceof Error ? err.message : String(err)}`
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export async function stopPluginServices(
|
|
398
|
-
plugins: LoadedPlugin[]
|
|
399
|
-
): Promise<void> {
|
|
400
|
-
for (const plugin of [...plugins].reverse()) {
|
|
401
|
-
for (const service of [...plugin.services].reverse()) {
|
|
402
|
-
if (!service.stop) continue;
|
|
403
|
-
try {
|
|
404
|
-
await Promise.resolve(service.stop());
|
|
405
|
-
} catch (err) {
|
|
406
|
-
logger.error(
|
|
407
|
-
`Plugin service "${service.id}" failed to stop (${plugin.manifest.name}): ${err instanceof Error ? err.message : String(err)}`
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Extract a display name from a plugin source string.
|
|
416
|
-
* "@openclaw/voice-call" -> "voice-call"
|
|
417
|
-
* "./my-plugin" -> "my-plugin"
|
|
418
|
-
*/
|
|
419
|
-
function extractPluginName(source: string): string {
|
|
420
|
-
const scopeMatch = source.match(/^@[^/]+\/(.+)$/);
|
|
421
|
-
if (scopeMatch?.[1]) {
|
|
422
|
-
return scopeMatch[1];
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const parts = source.split("/");
|
|
426
|
-
return parts[parts.length - 1] || source;
|
|
427
|
-
}
|