@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
package/src/gateway/types.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared types for gateway communication
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { AgentOptions, ThreadResponsePayload } from "@lobu/core";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Platform-specific metadata (e.g., Slack team_id, channel, thread_ts)
|
|
9
|
-
*/
|
|
10
|
-
interface PlatformMetadata {
|
|
11
|
-
team_id?: string;
|
|
12
|
-
channel?: string;
|
|
13
|
-
ts?: string;
|
|
14
|
-
thread_ts?: string;
|
|
15
|
-
files?: unknown[];
|
|
16
|
-
traceId?: string; // Trace ID for end-to-end observability
|
|
17
|
-
[key: string]: string | number | boolean | unknown[] | undefined;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Job type for queue messages
|
|
22
|
-
* - message: Standard agent message execution
|
|
23
|
-
* - exec: Direct command execution in sandbox
|
|
24
|
-
*/
|
|
25
|
-
export type JobType = "message" | "exec";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Message payload for agent execution
|
|
29
|
-
*/
|
|
30
|
-
export interface MessagePayload {
|
|
31
|
-
botId: string;
|
|
32
|
-
userId: string;
|
|
33
|
-
agentId: string;
|
|
34
|
-
conversationId: string;
|
|
35
|
-
platform: string;
|
|
36
|
-
channelId: string;
|
|
37
|
-
messageId: string;
|
|
38
|
-
messageText: string;
|
|
39
|
-
platformMetadata: PlatformMetadata;
|
|
40
|
-
agentOptions: AgentOptions;
|
|
41
|
-
jobId?: string; // Optional job ID from gateway
|
|
42
|
-
teamId?: string; // Optional team ID (WhatsApp uses top-level, Slack uses platformMetadata)
|
|
43
|
-
|
|
44
|
-
// Job type (default: "message")
|
|
45
|
-
jobType?: JobType;
|
|
46
|
-
|
|
47
|
-
// Exec-specific fields (only used when jobType === "exec")
|
|
48
|
-
execId?: string; // Unique ID for exec job (for response routing)
|
|
49
|
-
execCommand?: string; // Command to execute
|
|
50
|
-
execCwd?: string; // Working directory for command
|
|
51
|
-
execEnv?: Record<string, string>; // Additional environment variables
|
|
52
|
-
execTimeout?: number; // Timeout in milliseconds
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Queued message with timestamp
|
|
57
|
-
*/
|
|
58
|
-
export interface QueuedMessage {
|
|
59
|
-
payload: MessagePayload;
|
|
60
|
-
timestamp: number;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Response data sent back to gateway
|
|
65
|
-
*/
|
|
66
|
-
export type ResponseData = ThreadResponsePayload & {
|
|
67
|
-
originalMessageId: string;
|
|
68
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
createLogger,
|
|
5
|
-
initSentry,
|
|
6
|
-
initTracing,
|
|
7
|
-
moduleRegistry,
|
|
8
|
-
} from "@lobu/core";
|
|
9
|
-
|
|
10
|
-
const logger = createLogger("worker");
|
|
11
|
-
|
|
12
|
-
import { setupWorkspaceEnv } from "./core/workspace";
|
|
13
|
-
import { GatewayClient } from "./gateway/sse-client";
|
|
14
|
-
import { startWorkerHttpServer, stopWorkerHttpServer } from "./server";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Main entry point for gateway-based persistent worker
|
|
18
|
-
*/
|
|
19
|
-
async function main() {
|
|
20
|
-
// Register global rejection/exception handlers early
|
|
21
|
-
process.on("unhandledRejection", (reason) => {
|
|
22
|
-
logger.error("Unhandled rejection:", reason);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
process.on("uncaughtException", (error) => {
|
|
27
|
-
logger.error("Uncaught exception:", error);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
logger.info("Starting worker...");
|
|
32
|
-
|
|
33
|
-
// Initialize Sentry for error tracking
|
|
34
|
-
await initSentry();
|
|
35
|
-
|
|
36
|
-
// Initialize OpenTelemetry tracing for distributed tracing
|
|
37
|
-
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
38
|
-
if (otlpEndpoint) {
|
|
39
|
-
initTracing({
|
|
40
|
-
serviceName: "lobu-worker",
|
|
41
|
-
otlpEndpoint,
|
|
42
|
-
});
|
|
43
|
-
logger.info(`Tracing initialized: lobu-worker -> ${otlpEndpoint}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Discover and register available modules
|
|
47
|
-
await moduleRegistry.registerAvailableModules();
|
|
48
|
-
|
|
49
|
-
// Initialize all registered modules
|
|
50
|
-
await moduleRegistry.initAll();
|
|
51
|
-
logger.info("✅ Modules initialized");
|
|
52
|
-
|
|
53
|
-
logger.info("🔄 Starting in gateway mode (SSE/HTTP-based persistent worker)");
|
|
54
|
-
|
|
55
|
-
// Get user ID from environment
|
|
56
|
-
const userId = process.env.USER_ID;
|
|
57
|
-
|
|
58
|
-
if (!userId) {
|
|
59
|
-
logger.error(
|
|
60
|
-
"❌ USER_ID environment variable is required for gateway mode"
|
|
61
|
-
);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
// Get required environment variables
|
|
67
|
-
const deploymentName = process.env.DEPLOYMENT_NAME;
|
|
68
|
-
const dispatcherUrl = process.env.DISPATCHER_URL;
|
|
69
|
-
const workerToken = process.env.WORKER_TOKEN;
|
|
70
|
-
|
|
71
|
-
if (!deploymentName) {
|
|
72
|
-
logger.error("❌ DEPLOYMENT_NAME environment variable is required");
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
if (!dispatcherUrl) {
|
|
76
|
-
logger.error("❌ DISPATCHER_URL environment variable is required");
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
if (!workerToken) {
|
|
80
|
-
logger.error("❌ WORKER_TOKEN environment variable is required");
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
setupWorkspaceEnv(deploymentName);
|
|
85
|
-
|
|
86
|
-
// Start HTTP server before connecting to gateway
|
|
87
|
-
const httpPort = await startWorkerHttpServer();
|
|
88
|
-
logger.info(`Worker HTTP server started on port ${httpPort}`);
|
|
89
|
-
|
|
90
|
-
// Initialize gateway client directly
|
|
91
|
-
logger.info(`🚀 Starting Gateway-based Persistent Worker`);
|
|
92
|
-
logger.info(`- User ID: ${userId}`);
|
|
93
|
-
logger.info(`- Deployment: ${deploymentName}`);
|
|
94
|
-
logger.info(`- Dispatcher URL: ${dispatcherUrl}`);
|
|
95
|
-
|
|
96
|
-
const gatewayClient = new GatewayClient(
|
|
97
|
-
dispatcherUrl,
|
|
98
|
-
workerToken,
|
|
99
|
-
userId,
|
|
100
|
-
deploymentName,
|
|
101
|
-
httpPort
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
// Register signal handlers before async operations
|
|
105
|
-
let isShuttingDown = false;
|
|
106
|
-
|
|
107
|
-
process.on("SIGTERM", async () => {
|
|
108
|
-
if (isShuttingDown) return;
|
|
109
|
-
isShuttingDown = true;
|
|
110
|
-
logger.info("Received SIGTERM, shutting down gateway worker...");
|
|
111
|
-
await gatewayClient.stop();
|
|
112
|
-
await stopWorkerHttpServer();
|
|
113
|
-
process.exit(0);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
process.on("SIGINT", async () => {
|
|
117
|
-
if (isShuttingDown) return;
|
|
118
|
-
isShuttingDown = true;
|
|
119
|
-
logger.info("Received SIGINT, shutting down gateway worker...");
|
|
120
|
-
await gatewayClient.stop();
|
|
121
|
-
await stopWorkerHttpServer();
|
|
122
|
-
process.exit(0);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
logger.info("🔌 Connecting to dispatcher...");
|
|
126
|
-
await gatewayClient.start();
|
|
127
|
-
logger.info("✅ Gateway worker started successfully");
|
|
128
|
-
|
|
129
|
-
// Keep process alive
|
|
130
|
-
await new Promise(() => {
|
|
131
|
-
// Keep process running indefinitely so we can listen messages from the queue
|
|
132
|
-
}); // Wait forever
|
|
133
|
-
} catch (error) {
|
|
134
|
-
logger.error("❌ Gateway worker failed:", error);
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export type { WorkerConfig } from "./core/types";
|
|
140
|
-
|
|
141
|
-
main().catch((error) => {
|
|
142
|
-
logger.error("Fatal error in main:", error);
|
|
143
|
-
process.exit(1);
|
|
144
|
-
});
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createLogger,
|
|
3
|
-
type InstructionContext,
|
|
4
|
-
type InstructionProvider,
|
|
5
|
-
} from "@lobu/core";
|
|
6
|
-
|
|
7
|
-
const logger = createLogger("instruction-generator");
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Build custom instructions by collecting from multiple providers
|
|
11
|
-
* @param providers - Array of instruction providers
|
|
12
|
-
* @param context - Context information for instruction generation
|
|
13
|
-
* @returns Complete instruction text
|
|
14
|
-
*/
|
|
15
|
-
async function buildInstructions(
|
|
16
|
-
providers: InstructionProvider[],
|
|
17
|
-
context: InstructionContext
|
|
18
|
-
): Promise<string> {
|
|
19
|
-
// Sort by priority (lower priority = earlier in output)
|
|
20
|
-
const sortedProviders = [...providers].sort(
|
|
21
|
-
(a, b) => a.priority - b.priority
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
logger.debug(
|
|
25
|
-
`Building instructions with ${sortedProviders.length} providers`
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const sections: string[] = [];
|
|
29
|
-
|
|
30
|
-
// Collect instructions from all providers
|
|
31
|
-
for (const provider of sortedProviders) {
|
|
32
|
-
try {
|
|
33
|
-
const instructions = await provider.getInstructions(context);
|
|
34
|
-
if (instructions?.trim()) {
|
|
35
|
-
sections.push(instructions.trim());
|
|
36
|
-
logger.debug(
|
|
37
|
-
`Provider ${provider.name} contributed ${instructions.length} characters`
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
} catch (error) {
|
|
41
|
-
logger.error(
|
|
42
|
-
`Failed to get instructions from provider ${provider.name}:`,
|
|
43
|
-
error
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const finalInstructions = sections.join("\n\n");
|
|
49
|
-
logger.info(
|
|
50
|
-
`Built custom instructions: ${finalInstructions.length} characters from ${sections.length} providers`
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
return finalInstructions;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Generate custom instructions using modular providers
|
|
58
|
-
* Only generates worker-local instructions (core, projects)
|
|
59
|
-
* Platform and MCP instructions are provided by the gateway
|
|
60
|
-
*/
|
|
61
|
-
export async function generateCustomInstructions(
|
|
62
|
-
providers: InstructionProvider[],
|
|
63
|
-
context: InstructionContext
|
|
64
|
-
): Promise<string> {
|
|
65
|
-
try {
|
|
66
|
-
const instructions = await buildInstructions(providers, context);
|
|
67
|
-
|
|
68
|
-
logger.info(
|
|
69
|
-
`[WORKER-INSTRUCTIONS] Generated ${instructions.length} characters from ${providers.length} local providers`
|
|
70
|
-
);
|
|
71
|
-
logger.debug(`[WORKER-INSTRUCTIONS] \n${instructions}`);
|
|
72
|
-
|
|
73
|
-
return instructions;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
logger.error("Failed to generate worker instructions:", error);
|
|
76
|
-
const fallback = `You are a helpful AI agent for user ${context.userId}.`;
|
|
77
|
-
logger.warn(`[WORKER-INSTRUCTIONS] Using fallback: ${fallback}`);
|
|
78
|
-
return fallback;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Instruction providers for worker
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { InstructionContext, InstructionProvider } from "@lobu/core";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Provides information about available projects in the workspace
|
|
9
|
-
*/
|
|
10
|
-
export class ProjectsInstructionProvider implements InstructionProvider {
|
|
11
|
-
name = "projects";
|
|
12
|
-
priority = 30;
|
|
13
|
-
|
|
14
|
-
getInstructions(context: InstructionContext): string {
|
|
15
|
-
if (!context.availableProjects || context.availableProjects.length === 0) {
|
|
16
|
-
return `**Available projects:**
|
|
17
|
-
- none`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const projectList = context.availableProjects
|
|
21
|
-
.map((project: string) => ` - ${project}`)
|
|
22
|
-
.join("\n");
|
|
23
|
-
|
|
24
|
-
return `**Available projects:**
|
|
25
|
-
${projectList}`;
|
|
26
|
-
}
|
|
27
|
-
}
|
package/src/modules/lifecycle.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createLogger,
|
|
3
|
-
type ModuleSessionContext,
|
|
4
|
-
moduleRegistry,
|
|
5
|
-
type SessionContext,
|
|
6
|
-
type WorkerModule,
|
|
7
|
-
} from "@lobu/core";
|
|
8
|
-
|
|
9
|
-
const logger = createLogger("worker");
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Execute an operation on all worker modules with consistent error handling.
|
|
13
|
-
* Errors in individual modules are logged but do not halt iteration.
|
|
14
|
-
*/
|
|
15
|
-
async function executeForAllModules<T>(
|
|
16
|
-
operation: (module: WorkerModule) => Promise<T>,
|
|
17
|
-
operationName: string
|
|
18
|
-
): Promise<T[]> {
|
|
19
|
-
const workerModules = moduleRegistry.getWorkerModules();
|
|
20
|
-
const results: T[] = [];
|
|
21
|
-
for (const module of workerModules) {
|
|
22
|
-
try {
|
|
23
|
-
results.push(await operation(module));
|
|
24
|
-
} catch (error) {
|
|
25
|
-
logger.error(
|
|
26
|
-
`Failed to execute ${operationName} for module ${module.name}:`,
|
|
27
|
-
error
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return results;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function onSessionStart(
|
|
35
|
-
context: SessionContext
|
|
36
|
-
): Promise<SessionContext> {
|
|
37
|
-
// Convert to module session context
|
|
38
|
-
const moduleContext: ModuleSessionContext = {
|
|
39
|
-
userId: context.userId,
|
|
40
|
-
conversationId: context.conversationId || "",
|
|
41
|
-
systemPrompt: context.customInstructions || "",
|
|
42
|
-
workspace: undefined,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
let updatedContext = moduleContext;
|
|
46
|
-
|
|
47
|
-
await executeForAllModules(async (module) => {
|
|
48
|
-
updatedContext = await module.onSessionStart(updatedContext);
|
|
49
|
-
}, "onSessionStart");
|
|
50
|
-
|
|
51
|
-
// Merge back into original context, mapping systemPrompt back to customInstructions
|
|
52
|
-
return {
|
|
53
|
-
...context,
|
|
54
|
-
customInstructions:
|
|
55
|
-
updatedContext.systemPrompt || context.customInstructions,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Configuration for module workspace initialization
|
|
61
|
-
*/
|
|
62
|
-
interface ModuleWorkspaceConfig {
|
|
63
|
-
workspaceDir: string;
|
|
64
|
-
username: string;
|
|
65
|
-
sessionKey: string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function initModuleWorkspace(
|
|
69
|
-
config: ModuleWorkspaceConfig
|
|
70
|
-
): Promise<void> {
|
|
71
|
-
await executeForAllModules(
|
|
72
|
-
(module) => module.initWorkspace(config),
|
|
73
|
-
"initWorkspace"
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function collectModuleData(context: {
|
|
78
|
-
workspaceDir: string;
|
|
79
|
-
userId: string;
|
|
80
|
-
conversationId: string;
|
|
81
|
-
}): Promise<Record<string, unknown>> {
|
|
82
|
-
const moduleData: Record<string, unknown> = {};
|
|
83
|
-
|
|
84
|
-
await executeForAllModules(async (module) => {
|
|
85
|
-
const data = await module.onBeforeResponse(context);
|
|
86
|
-
if (data !== null) {
|
|
87
|
-
moduleData[module.name] = data;
|
|
88
|
-
}
|
|
89
|
-
}, "onBeforeResponse");
|
|
90
|
-
|
|
91
|
-
return moduleData;
|
|
92
|
-
}
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import { getCustomToolDescription, type McpToolDef } from "@lobu/core";
|
|
2
|
-
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
3
|
-
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
4
|
-
import type { Static } from "@sinclair/typebox";
|
|
5
|
-
import { type TSchema, Type } from "@sinclair/typebox";
|
|
6
|
-
import type { GatewayParams, TextResult } from "../shared/tool-implementations";
|
|
7
|
-
import {
|
|
8
|
-
askUserQuestion,
|
|
9
|
-
callMcpTool,
|
|
10
|
-
cancelReminder,
|
|
11
|
-
generateAudio,
|
|
12
|
-
generateImage,
|
|
13
|
-
getChannelHistory,
|
|
14
|
-
listReminders,
|
|
15
|
-
scheduleReminder,
|
|
16
|
-
uploadUserFile,
|
|
17
|
-
} from "../shared/tool-implementations";
|
|
18
|
-
|
|
19
|
-
type ToolResult = AgentToolResult<Record<string, unknown>>;
|
|
20
|
-
|
|
21
|
-
/** Adapt shared TextResult to OpenClaw's ToolResult (adds details field) */
|
|
22
|
-
function toToolResult(result: TextResult): ToolResult {
|
|
23
|
-
return { content: result.content, details: {} };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Create a ToolDefinition with proper type bridging between TypeBox schemas
|
|
28
|
-
* and the shared tool implementation functions. Eliminates per-tool `as` casts
|
|
29
|
-
* by casting once at the boundary.
|
|
30
|
-
*/
|
|
31
|
-
function defineTool<T extends TSchema>(config: {
|
|
32
|
-
name: string;
|
|
33
|
-
description: string;
|
|
34
|
-
parameters: T;
|
|
35
|
-
run: (args: Static<T>) => Promise<TextResult>;
|
|
36
|
-
}): ToolDefinition {
|
|
37
|
-
return {
|
|
38
|
-
name: config.name,
|
|
39
|
-
label: config.name,
|
|
40
|
-
description: config.description,
|
|
41
|
-
parameters: config.parameters,
|
|
42
|
-
execute: async (_toolCallId, args) =>
|
|
43
|
-
toToolResult(await config.run(args as Static<T>)),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function createOpenClawCustomTools(params: {
|
|
48
|
-
gatewayUrl: string;
|
|
49
|
-
workerToken: string;
|
|
50
|
-
channelId: string;
|
|
51
|
-
conversationId: string;
|
|
52
|
-
platform?: string;
|
|
53
|
-
}): ToolDefinition[] {
|
|
54
|
-
const gw: GatewayParams = {
|
|
55
|
-
gatewayUrl: params.gatewayUrl,
|
|
56
|
-
workerToken: params.workerToken,
|
|
57
|
-
channelId: params.channelId,
|
|
58
|
-
conversationId: params.conversationId,
|
|
59
|
-
platform: params.platform || "slack",
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const tools: ToolDefinition[] = [
|
|
63
|
-
defineTool({
|
|
64
|
-
name: "UploadUserFile",
|
|
65
|
-
description: getCustomToolDescription("UploadUserFile"),
|
|
66
|
-
parameters: Type.Object({
|
|
67
|
-
file_path: Type.String({
|
|
68
|
-
description:
|
|
69
|
-
"Path to the file to show (absolute or relative to workspace)",
|
|
70
|
-
}),
|
|
71
|
-
description: Type.Optional(
|
|
72
|
-
Type.String({
|
|
73
|
-
description:
|
|
74
|
-
"Optional description of what the file contains or shows",
|
|
75
|
-
})
|
|
76
|
-
),
|
|
77
|
-
}),
|
|
78
|
-
run: (args) => uploadUserFile(gw, args),
|
|
79
|
-
}),
|
|
80
|
-
|
|
81
|
-
defineTool({
|
|
82
|
-
name: "ScheduleReminder",
|
|
83
|
-
description: getCustomToolDescription("ScheduleReminder"),
|
|
84
|
-
parameters: Type.Object({
|
|
85
|
-
task: Type.String({
|
|
86
|
-
description: "Description of what you need to do when reminded",
|
|
87
|
-
}),
|
|
88
|
-
delayMinutes: Type.Optional(
|
|
89
|
-
Type.Number({
|
|
90
|
-
description:
|
|
91
|
-
"Minutes from now to trigger (1-1440, max 24 hours). Use this OR cron, not both.",
|
|
92
|
-
})
|
|
93
|
-
),
|
|
94
|
-
cron: Type.Optional(
|
|
95
|
-
Type.String({
|
|
96
|
-
description:
|
|
97
|
-
"Cron expression for recurring schedule (e.g., '*/30 * * * *' for every 30 min). Use this OR delayMinutes, not both.",
|
|
98
|
-
})
|
|
99
|
-
),
|
|
100
|
-
maxIterations: Type.Optional(
|
|
101
|
-
Type.Number({
|
|
102
|
-
description:
|
|
103
|
-
"Maximum iterations for recurring schedules (default: 10, max: 100). Only used with cron.",
|
|
104
|
-
})
|
|
105
|
-
),
|
|
106
|
-
}),
|
|
107
|
-
run: (args) => scheduleReminder(gw, args),
|
|
108
|
-
}),
|
|
109
|
-
|
|
110
|
-
defineTool({
|
|
111
|
-
name: "CancelReminder",
|
|
112
|
-
description: getCustomToolDescription("CancelReminder"),
|
|
113
|
-
parameters: Type.Object({
|
|
114
|
-
scheduleId: Type.String({
|
|
115
|
-
description: "The schedule ID returned from ScheduleReminder",
|
|
116
|
-
}),
|
|
117
|
-
}),
|
|
118
|
-
run: (args) => cancelReminder(gw, args),
|
|
119
|
-
}),
|
|
120
|
-
|
|
121
|
-
defineTool({
|
|
122
|
-
name: "ListReminders",
|
|
123
|
-
description: getCustomToolDescription("ListReminders"),
|
|
124
|
-
parameters: Type.Object({}),
|
|
125
|
-
run: () => listReminders(gw),
|
|
126
|
-
}),
|
|
127
|
-
|
|
128
|
-
defineTool({
|
|
129
|
-
name: "GenerateImage",
|
|
130
|
-
description: getCustomToolDescription("GenerateImage"),
|
|
131
|
-
parameters: Type.Object({
|
|
132
|
-
prompt: Type.String({
|
|
133
|
-
description: "The image prompt to generate",
|
|
134
|
-
}),
|
|
135
|
-
size: Type.Optional(
|
|
136
|
-
Type.Union(
|
|
137
|
-
[
|
|
138
|
-
Type.Literal("1024x1024"),
|
|
139
|
-
Type.Literal("1024x1536"),
|
|
140
|
-
Type.Literal("1536x1024"),
|
|
141
|
-
Type.Literal("auto"),
|
|
142
|
-
],
|
|
143
|
-
{
|
|
144
|
-
description: "Output image size (default: 1024x1024)",
|
|
145
|
-
}
|
|
146
|
-
)
|
|
147
|
-
),
|
|
148
|
-
quality: Type.Optional(
|
|
149
|
-
Type.Union(
|
|
150
|
-
[
|
|
151
|
-
Type.Literal("low"),
|
|
152
|
-
Type.Literal("medium"),
|
|
153
|
-
Type.Literal("high"),
|
|
154
|
-
Type.Literal("auto"),
|
|
155
|
-
],
|
|
156
|
-
{
|
|
157
|
-
description: "Image quality (default: auto)",
|
|
158
|
-
}
|
|
159
|
-
)
|
|
160
|
-
),
|
|
161
|
-
background: Type.Optional(
|
|
162
|
-
Type.Union(
|
|
163
|
-
[
|
|
164
|
-
Type.Literal("transparent"),
|
|
165
|
-
Type.Literal("opaque"),
|
|
166
|
-
Type.Literal("auto"),
|
|
167
|
-
],
|
|
168
|
-
{
|
|
169
|
-
description: "Background style (default: auto)",
|
|
170
|
-
}
|
|
171
|
-
)
|
|
172
|
-
),
|
|
173
|
-
format: Type.Optional(
|
|
174
|
-
Type.Union(
|
|
175
|
-
[Type.Literal("png"), Type.Literal("jpeg"), Type.Literal("webp")],
|
|
176
|
-
{
|
|
177
|
-
description: "Output image format (default: png)",
|
|
178
|
-
}
|
|
179
|
-
)
|
|
180
|
-
),
|
|
181
|
-
}),
|
|
182
|
-
run: (args) => generateImage(gw, args),
|
|
183
|
-
}),
|
|
184
|
-
|
|
185
|
-
defineTool({
|
|
186
|
-
name: "GenerateAudio",
|
|
187
|
-
description: getCustomToolDescription("GenerateAudio"),
|
|
188
|
-
parameters: Type.Object({
|
|
189
|
-
text: Type.String({
|
|
190
|
-
description: "The text to convert to speech (max 4096 characters)",
|
|
191
|
-
}),
|
|
192
|
-
voice: Type.Optional(
|
|
193
|
-
Type.String({
|
|
194
|
-
description:
|
|
195
|
-
"Voice ID (provider-specific). OpenAI: alloy, echo, fable, onyx, nova, shimmer. Leave empty for default.",
|
|
196
|
-
})
|
|
197
|
-
),
|
|
198
|
-
speed: Type.Optional(
|
|
199
|
-
Type.Number({
|
|
200
|
-
description: "Speech speed (0.5-2.0, default 1.0).",
|
|
201
|
-
})
|
|
202
|
-
),
|
|
203
|
-
}),
|
|
204
|
-
run: (args) => generateAudio(gw, args),
|
|
205
|
-
}),
|
|
206
|
-
|
|
207
|
-
defineTool({
|
|
208
|
-
name: "GetChannelHistory",
|
|
209
|
-
description: getCustomToolDescription("GetChannelHistory"),
|
|
210
|
-
parameters: Type.Object({
|
|
211
|
-
limit: Type.Optional(
|
|
212
|
-
Type.Number({
|
|
213
|
-
description: "Number of messages to fetch (default 50, max 100)",
|
|
214
|
-
})
|
|
215
|
-
),
|
|
216
|
-
before: Type.Optional(
|
|
217
|
-
Type.String({
|
|
218
|
-
description:
|
|
219
|
-
"ISO timestamp cursor - fetch messages before this time (for pagination)",
|
|
220
|
-
})
|
|
221
|
-
),
|
|
222
|
-
}),
|
|
223
|
-
run: (args) => getChannelHistory(gw, args),
|
|
224
|
-
}),
|
|
225
|
-
|
|
226
|
-
defineTool({
|
|
227
|
-
name: "AskUserQuestion",
|
|
228
|
-
description: getCustomToolDescription("AskUserQuestion"),
|
|
229
|
-
parameters: Type.Object({
|
|
230
|
-
question: Type.String({
|
|
231
|
-
description: "The question to ask the user",
|
|
232
|
-
}),
|
|
233
|
-
options: Type.Array(Type.String(), {
|
|
234
|
-
description: "Array of button labels for the user to choose from",
|
|
235
|
-
}),
|
|
236
|
-
}),
|
|
237
|
-
run: (args) => askUserQuestion(gw, args),
|
|
238
|
-
}),
|
|
239
|
-
];
|
|
240
|
-
|
|
241
|
-
return tools;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Convert MCP tool definitions from session context into first-class
|
|
246
|
-
* OpenClaw ToolDefinition objects that call the MCP proxy directly.
|
|
247
|
-
* Tools are dynamically discovered from each MCP server (e.g. owletto).
|
|
248
|
-
*/
|
|
249
|
-
export function createMcpToolDefinitions(
|
|
250
|
-
mcpTools: Record<string, McpToolDef[]>,
|
|
251
|
-
gw: GatewayParams,
|
|
252
|
-
mcpContext?: Record<string, string>
|
|
253
|
-
): ToolDefinition[] {
|
|
254
|
-
const tools: ToolDefinition[] = [];
|
|
255
|
-
|
|
256
|
-
for (const [mcpId, defs] of Object.entries(mcpTools)) {
|
|
257
|
-
const contextPrefix = mcpContext?.[mcpId];
|
|
258
|
-
for (const def of defs) {
|
|
259
|
-
if (!def.name || typeof def.name !== "string" || !def.name.trim()) {
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
const schema = def.inputSchema
|
|
263
|
-
? Type.Unsafe(def.inputSchema)
|
|
264
|
-
: Type.Object({});
|
|
265
|
-
|
|
266
|
-
const baseDescription = def.description || `MCP tool from ${mcpId}`;
|
|
267
|
-
const description = contextPrefix
|
|
268
|
-
? `[${contextPrefix}] ${baseDescription}`
|
|
269
|
-
: baseDescription;
|
|
270
|
-
|
|
271
|
-
tools.push({
|
|
272
|
-
name: def.name,
|
|
273
|
-
label: `${mcpId}/${def.name}`,
|
|
274
|
-
description,
|
|
275
|
-
parameters: schema,
|
|
276
|
-
execute: async (_toolCallId, args) =>
|
|
277
|
-
toToolResult(
|
|
278
|
-
await callMcpTool(
|
|
279
|
-
gw,
|
|
280
|
-
mcpId,
|
|
281
|
-
def.name,
|
|
282
|
-
(args || {}) as Record<string, unknown>
|
|
283
|
-
)
|
|
284
|
-
),
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return tools;
|
|
290
|
-
}
|