@romiluz/clawmongo 0.1.0-rc.2 → 0.1.0-rc.3

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/main.js CHANGED
@@ -19,7 +19,7 @@ import { cronCommand } from "./cli/commands/cron.js";
19
19
  import { backupCommand } from "./cli/commands/backup.js";
20
20
  import { securityCommand } from "./cli/commands/security.js";
21
21
  import { benchmarkCommand } from "./cli/commands/benchmark.js";
22
- const VERSION = "0.1.0-rc.2";
22
+ const VERSION = "0.1.0-rc.3";
23
23
  /**
24
24
  * Build the command registry.
25
25
  */
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Bash Session Tool
3
+ *
4
+ * Persistent bash shell sessions with restart capability.
5
+ * Ported from OpenClaw's computer-use-demo/tools/bash.py.
6
+ *
7
+ * Features:
8
+ * - Persistent sessions that maintain state
9
+ * - Automatic session creation
10
+ * - Restart capability for stuck sessions
11
+ * - Timeout handling
12
+ * - Tenant-scoped session management
13
+ *
14
+ * @module tool-runtime/executors/bash
15
+ */
16
+ import { spawn } from "node:child_process";
17
+ import { randomUUID } from "node:crypto";
18
+ const DEFAULT_TIMEOUT_MS = 120_000; // 2 minutes
19
+ const OUTPUT_DELAY_MS = 200;
20
+ const SENTINEL = "<<exit>>";
21
+ /**
22
+ * Session registry - maps tenant to their active bash session
23
+ */
24
+ const sessionRegistry = new Map();
25
+ /**
26
+ * Get or create a bash session for a tenant
27
+ */
28
+ function getSession(tenantId) {
29
+ return sessionRegistry.get(tenantId);
30
+ }
31
+ /**
32
+ * Create a new bash session
33
+ */
34
+ async function createSession(tenantId) {
35
+ // Kill existing session if any
36
+ const existing = sessionRegistry.get(tenantId);
37
+ if (existing?.process) {
38
+ existing.process.kill();
39
+ }
40
+ const sessionId = `bash-${randomUUID().slice(0, 8)}`;
41
+ const proc = spawn("/bin/bash", [], {
42
+ shell: false,
43
+ stdio: ["pipe", "pipe", "pipe"]
44
+ });
45
+ const session = {
46
+ id: sessionId,
47
+ process: proc,
48
+ tenantId,
49
+ started: true,
50
+ timedOut: false,
51
+ stdout: "",
52
+ stderr: "",
53
+ createdAt: new Date()
54
+ };
55
+ sessionRegistry.set(tenantId, session);
56
+ return session;
57
+ }
58
+ /**
59
+ * Run a command in a bash session
60
+ */
61
+ async function runCommand(session, command, timeoutMs = DEFAULT_TIMEOUT_MS) {
62
+ if (!session.started) {
63
+ return { stdout: "", stderr: "", error: "Session has not started" };
64
+ }
65
+ if (session.process.exitCode !== null) {
66
+ return {
67
+ stdout: "",
68
+ stderr: "",
69
+ error: `Bash has exited with return code ${session.process.exitCode}. Tool must be restarted.`
70
+ };
71
+ }
72
+ if (session.timedOut) {
73
+ return {
74
+ stdout: "",
75
+ stderr: "",
76
+ error: `Session timed out and must be restarted`
77
+ };
78
+ }
79
+ // Clear buffers
80
+ session.stdout = "";
81
+ session.stderr = "";
82
+ // Setup output collection
83
+ const stdoutHandler = (data) => {
84
+ session.stdout += data.toString();
85
+ };
86
+ const stderrHandler = (data) => {
87
+ session.stderr += data.toString();
88
+ };
89
+ session.process.stdout?.on("data", stdoutHandler);
90
+ session.process.stderr?.on("data", stderrHandler);
91
+ // Send command with sentinel
92
+ session.process.stdin?.write(`${command}; echo '${SENTINEL}'\n`);
93
+ // Wait for output with sentinel
94
+ try {
95
+ await new Promise((resolve, reject) => {
96
+ const timeout = setTimeout(() => {
97
+ session.timedOut = true;
98
+ reject(new Error(`Bash timed out after ${timeoutMs}ms and must be restarted`));
99
+ }, timeoutMs);
100
+ const checkInterval = setInterval(() => {
101
+ if (session.stdout.includes(SENTINEL)) {
102
+ clearTimeout(timeout);
103
+ clearInterval(checkInterval);
104
+ resolve();
105
+ }
106
+ }, OUTPUT_DELAY_MS);
107
+ });
108
+ }
109
+ catch (err) {
110
+ session.process.stdout?.removeListener("data", stdoutHandler);
111
+ session.process.stderr?.removeListener("data", stderrHandler);
112
+ return {
113
+ stdout: "",
114
+ stderr: "",
115
+ error: err instanceof Error ? err.message : String(err)
116
+ };
117
+ }
118
+ session.process.stdout?.removeListener("data", stdoutHandler);
119
+ session.process.stderr?.removeListener("data", stderrHandler);
120
+ // Strip sentinel from output
121
+ let output = session.stdout;
122
+ const sentinelIdx = output.indexOf(SENTINEL);
123
+ if (sentinelIdx !== -1) {
124
+ output = output.slice(0, sentinelIdx);
125
+ }
126
+ if (output.endsWith("\n")) {
127
+ output = output.slice(0, -1);
128
+ }
129
+ let stderr = session.stderr;
130
+ if (stderr.endsWith("\n")) {
131
+ stderr = stderr.slice(0, -1);
132
+ }
133
+ return { stdout: output, stderr };
134
+ }
135
+ /**
136
+ * Validate bash tool parameters
137
+ */
138
+ function validateBashParams(params) {
139
+ const command = params.command;
140
+ const restart = params.restart;
141
+ if (restart === true) {
142
+ return { restart: true };
143
+ }
144
+ if (typeof command !== "string" || command.trim().length === 0) {
145
+ return { error: "command is required (or set restart: true)" };
146
+ }
147
+ return { command: command.trim(), restart: false };
148
+ }
149
+ /**
150
+ * Bash tool executor
151
+ */
152
+ export async function bashToolExecutor(params, context) {
153
+ const startTime = performance.now();
154
+ const tenantId = context.tenantId ?? "default";
155
+ // Handle restart
156
+ if (params.restart) {
157
+ await createSession(tenantId);
158
+ return {
159
+ ok: true,
160
+ output: { system: "Tool has been restarted.", stdout: "", stderr: "" },
161
+ durationMs: Math.round(performance.now() - startTime),
162
+ sideEffects: [`bash:restart:${context.toolCallId}`]
163
+ };
164
+ }
165
+ // Get or create session
166
+ let session = getSession(tenantId);
167
+ if (!session) {
168
+ session = await createSession(tenantId);
169
+ }
170
+ // Run command
171
+ const result = await runCommand(session, params.command ?? "");
172
+ if (result.error) {
173
+ return {
174
+ ok: false,
175
+ output: null,
176
+ error: result.error,
177
+ durationMs: Math.round(performance.now() - startTime),
178
+ sideEffects: []
179
+ };
180
+ }
181
+ return {
182
+ ok: true,
183
+ output: { stdout: result.stdout, stderr: result.stderr },
184
+ durationMs: Math.round(performance.now() - startTime),
185
+ sideEffects: [`bash:exec:${context.toolCallId}`]
186
+ };
187
+ }
188
+ /**
189
+ * Create bash executor with validated params
190
+ */
191
+ export function createBashExecutor(rawParams, context) {
192
+ const validation = validateBashParams(rawParams);
193
+ if ("error" in validation) {
194
+ return {
195
+ ok: false,
196
+ output: null,
197
+ error: validation.error,
198
+ durationMs: 0,
199
+ sideEffects: []
200
+ };
201
+ }
202
+ return bashToolExecutor(validation, context);
203
+ }
204
+ /**
205
+ * Cleanup bash sessions for a tenant
206
+ */
207
+ export function cleanupBashSessions(tenantId) {
208
+ const session = sessionRegistry.get(tenantId);
209
+ if (session?.process) {
210
+ session.process.kill();
211
+ }
212
+ sessionRegistry.delete(tenantId);
213
+ }
214
+ /**
215
+ * Tool definition for the bash tool
216
+ */
217
+ export const bashToolDefinition = {
218
+ name: "bash",
219
+ description: "Run commands in a persistent bash shell. The session persists between calls. " +
220
+ "Set restart: true to restart the shell if it times out or exits.",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ command: {
225
+ type: "string",
226
+ description: "The bash command to run."
227
+ },
228
+ restart: {
229
+ type: "boolean",
230
+ description: "Set to true to restart the bash session."
231
+ }
232
+ }
233
+ }
234
+ };
@@ -8,3 +8,7 @@ export { createExecExecutor, execToolExecutor } from "./exec.js";
8
8
  export { cleanupTenantProcesses, createProcessExecutor, getProcessCount, processToolExecutor } from "./process.js";
9
9
  export { createEditExecutor, createReadExecutor, createWriteExecutor, editToolExecutor, readToolExecutor, writeToolExecutor } from "./filesystem.js";
10
10
  export { createSessionsListExecutor, createSessionStatusExecutor, sessionsListExecutor, sessionStatusExecutor } from "./session.js";
11
+ // New tools for OpenClaw parity
12
+ export { createThinkExecutor, thinkToolExecutor, thinkToolDefinition } from "./think.js";
13
+ export { createBashExecutor, bashToolExecutor, bashToolDefinition, cleanupBashSessions } from "./bash.js";
14
+ export { createCodeExecutionServerTool, createWebSearchServerTool, getDefaultServerTools, getServerToolBetaHeader, isServerTool, SERVER_TOOL_TYPES } from "./server-tools.js";
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Server Tools - Anthropic Server-Side Tool Definitions
3
+ *
4
+ * These are special tools executed by Anthropic's servers, not locally.
5
+ * They use a different registration format and don't need local executors.
6
+ *
7
+ * Ported from OpenClaw's anthropic-quickstarts:
8
+ * - code_execution.py → CodeExecutionServerTool
9
+ * - web_search.py → WebSearchServerTool
10
+ *
11
+ * @module tool-runtime/executors/server-tools
12
+ */
13
+ export function createCodeExecutionServerTool(options = {}) {
14
+ const name = options.name ?? "code_execution";
15
+ return {
16
+ type: "code_execution_20250522",
17
+ name
18
+ };
19
+ }
20
+ export function createWebSearchServerTool(options = {}) {
21
+ const name = options.name ?? "web_search";
22
+ const tool = {
23
+ type: "web_search_20250305",
24
+ name
25
+ };
26
+ if (options.maxUses !== undefined) {
27
+ tool.max_uses = options.maxUses;
28
+ }
29
+ if (options.allowedDomains !== undefined) {
30
+ tool.allowed_domains = options.allowedDomains;
31
+ }
32
+ if (options.blockedDomains !== undefined) {
33
+ tool.blocked_domains = options.blockedDomains;
34
+ }
35
+ if (options.userLocation !== undefined) {
36
+ tool.user_location = options.userLocation;
37
+ }
38
+ return tool;
39
+ }
40
+ /**
41
+ * Server tool type constants
42
+ */
43
+ export const SERVER_TOOL_TYPES = {
44
+ CODE_EXECUTION: "code_execution_20250522",
45
+ WEB_SEARCH: "web_search_20250305"
46
+ };
47
+ /**
48
+ * Check if a tool definition is a server tool
49
+ */
50
+ export function isServerTool(toolDef) {
51
+ const type = toolDef.type;
52
+ return (type === SERVER_TOOL_TYPES.CODE_EXECUTION ||
53
+ type === SERVER_TOOL_TYPES.WEB_SEARCH);
54
+ }
55
+ /**
56
+ * Get required beta header for server tools
57
+ */
58
+ export function getServerToolBetaHeader() {
59
+ // The code execution tool requires this beta header
60
+ return "code-execution-2025-05-22";
61
+ }
62
+ /**
63
+ * Default server tools preset - both code execution and web search
64
+ */
65
+ export function getDefaultServerTools() {
66
+ return [
67
+ createCodeExecutionServerTool(),
68
+ createWebSearchServerTool()
69
+ ];
70
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Think Tool Executor
3
+ *
4
+ * Internal reasoning tool for the agent to think through problems
5
+ * without executing external actions. Ported from OpenClaw's think.py.
6
+ *
7
+ * This tool allows the model to:
8
+ * - Reason about complex problems step by step
9
+ * - Store intermediate thoughts in conversation history
10
+ * - Plan before taking actions
11
+ *
12
+ * @module tool-runtime/executors/think
13
+ */
14
+ /**
15
+ * Validate think tool parameters
16
+ */
17
+ function validateThinkParams(params) {
18
+ const thought = params.thought;
19
+ if (typeof thought !== "string" || thought.trim().length === 0) {
20
+ return { error: "thought is required and must be a non-empty string" };
21
+ }
22
+ return { thought: thought.trim() };
23
+ }
24
+ /**
25
+ * Think tool executor
26
+ *
27
+ * Simply acknowledges the thought, allowing the model to use it
28
+ * for chain-of-thought reasoning without external side effects.
29
+ */
30
+ export const thinkToolExecutor = async (params, _context) => {
31
+ const startTime = performance.now();
32
+ // The think tool doesn't do anything external -
33
+ // it just acknowledges the thought so the model can reason
34
+ const result = {
35
+ message: "Thinking complete!",
36
+ thought: params.thought
37
+ };
38
+ return {
39
+ ok: true,
40
+ output: result,
41
+ durationMs: Math.round(performance.now() - startTime),
42
+ sideEffects: [] // No side effects - purely internal reasoning
43
+ };
44
+ };
45
+ /**
46
+ * Create think executor with validated params
47
+ */
48
+ export function createThinkExecutor(rawParams, context) {
49
+ const validation = validateThinkParams(rawParams);
50
+ if ("error" in validation) {
51
+ return {
52
+ ok: false,
53
+ output: null,
54
+ error: validation.error,
55
+ durationMs: 0,
56
+ sideEffects: []
57
+ };
58
+ }
59
+ return thinkToolExecutor(validation, context);
60
+ }
61
+ /**
62
+ * Tool definition for the think tool (for registration)
63
+ */
64
+ export const thinkToolDefinition = {
65
+ name: "think",
66
+ description: "Use this tool to think about something. It will not obtain new information " +
67
+ "or change the database, but just append the thought to the log. " +
68
+ "Use it when complex reasoning or some cache memory is needed.",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ thought: {
73
+ type: "string",
74
+ description: "A thought to think about."
75
+ }
76
+ },
77
+ required: ["thought"]
78
+ }
79
+ };
@@ -0,0 +1,178 @@
1
+ /**
2
+ * MCP Connection Manager
3
+ *
4
+ * Manages connections to MCP (Model Context Protocol) tool servers.
5
+ * Supports both STDIO and SSE connection types.
6
+ *
7
+ * @module tool-runtime/mcp/connection
8
+ */
9
+ import { spawn } from "node:child_process";
10
+ import { randomUUID } from "node:crypto";
11
+ /**
12
+ * MCP Connection - manages a single MCP server connection
13
+ */
14
+ export class MCPConnection {
15
+ id;
16
+ config;
17
+ process = null;
18
+ connected = false;
19
+ tools = [];
20
+ error;
21
+ constructor(config) {
22
+ this.id = `mcp-${randomUUID().slice(0, 8)}`;
23
+ this.config = config;
24
+ }
25
+ /**
26
+ * Connect to the MCP server
27
+ */
28
+ async connect() {
29
+ if (this.connected)
30
+ return;
31
+ try {
32
+ if (this.config.type === "stdio") {
33
+ await this.connectStdio();
34
+ }
35
+ else if (this.config.type === "sse") {
36
+ await this.connectSSE();
37
+ }
38
+ this.connected = true;
39
+ this.error = undefined;
40
+ }
41
+ catch (err) {
42
+ this.error = err instanceof Error ? err.message : String(err);
43
+ throw err;
44
+ }
45
+ }
46
+ /**
47
+ * Connect via STDIO
48
+ */
49
+ async connectStdio() {
50
+ const config = this.config;
51
+ this.process = spawn(config.command, config.args ?? [], {
52
+ env: { ...process.env, ...config.env },
53
+ stdio: ["pipe", "pipe", "pipe"]
54
+ });
55
+ // Wait for connection to be established
56
+ await new Promise((resolve, reject) => {
57
+ const timeout = setTimeout(() => {
58
+ reject(new Error("MCP connection timeout"));
59
+ }, 10000);
60
+ this.process?.stdout?.once("data", () => {
61
+ clearTimeout(timeout);
62
+ resolve();
63
+ });
64
+ this.process?.on("error", (err) => {
65
+ clearTimeout(timeout);
66
+ reject(err);
67
+ });
68
+ });
69
+ }
70
+ /**
71
+ * Connect via SSE (placeholder - full implementation needs SSE client)
72
+ */
73
+ async connectSSE() {
74
+ // SSE implementation would use EventSource or similar
75
+ // For now, throw not implemented
76
+ throw new Error("SSE MCP connections not yet implemented - use STDIO");
77
+ }
78
+ /**
79
+ * Disconnect from the MCP server
80
+ */
81
+ disconnect() {
82
+ if (this.process) {
83
+ this.process.kill();
84
+ this.process = null;
85
+ }
86
+ this.connected = false;
87
+ }
88
+ /**
89
+ * List available tools from the server
90
+ */
91
+ async listTools() {
92
+ if (!this.connected) {
93
+ throw new Error("Not connected to MCP server");
94
+ }
95
+ // In full implementation, this would send list_tools request
96
+ // For now, return cached tools
97
+ return this.tools;
98
+ }
99
+ /**
100
+ * Call a tool on the MCP server
101
+ */
102
+ async callTool(name, args) {
103
+ if (!this.connected) {
104
+ throw new Error("Not connected to MCP server");
105
+ }
106
+ // In full implementation, this would:
107
+ // 1. Send JSON-RPC request to server
108
+ // 2. Wait for response
109
+ // 3. Parse and return result
110
+ // Placeholder response
111
+ return {
112
+ content: [{
113
+ type: "text",
114
+ text: `MCP tool ${name} called with args: ${JSON.stringify(args)}`
115
+ }]
116
+ };
117
+ }
118
+ /**
119
+ * Get current connection state
120
+ */
121
+ getState() {
122
+ return {
123
+ id: this.id,
124
+ config: this.config,
125
+ connected: this.connected,
126
+ tools: this.tools,
127
+ error: this.error
128
+ };
129
+ }
130
+ }
131
+ /**
132
+ * MCP Connection Manager - manages multiple MCP connections
133
+ */
134
+ export class MCPConnectionManager {
135
+ connections = new Map();
136
+ /**
137
+ * Create and connect to an MCP server
138
+ */
139
+ async connect(config) {
140
+ const connection = new MCPConnection(config);
141
+ await connection.connect();
142
+ this.connections.set(connection.id, connection);
143
+ return connection;
144
+ }
145
+ /**
146
+ * Disconnect from an MCP server
147
+ */
148
+ disconnect(connectionId) {
149
+ const connection = this.connections.get(connectionId);
150
+ if (connection) {
151
+ connection.disconnect();
152
+ this.connections.delete(connectionId);
153
+ }
154
+ }
155
+ /**
156
+ * Disconnect all MCP servers
157
+ */
158
+ disconnectAll() {
159
+ for (const connection of this.connections.values()) {
160
+ connection.disconnect();
161
+ }
162
+ this.connections.clear();
163
+ }
164
+ /**
165
+ * Get a connection by ID
166
+ */
167
+ get(connectionId) {
168
+ return this.connections.get(connectionId);
169
+ }
170
+ /**
171
+ * List all connections
172
+ */
173
+ list() {
174
+ return Array.from(this.connections.values()).map(c => c.getState());
175
+ }
176
+ }
177
+ // Singleton manager instance
178
+ export const mcpManager = new MCPConnectionManager();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Module
3
+ *
4
+ * Enables ClawMongo to connect to external MCP tool servers.
5
+ * Provides full parity with OpenClaw's MCP tool integration.
6
+ *
7
+ * @module tool-runtime/mcp
8
+ */
9
+ export * from "./types.js";
10
+ export { MCPConnection, MCPConnectionManager, mcpManager } from "./connection.js";
11
+ export { createMCPTool, createMCPToolExecutor, mcpToolToDefinition } from "./tool.js";
@@ -0,0 +1,72 @@
1
+ /**
2
+ * MCP Tool - Wrapper for MCP server tools
3
+ *
4
+ * Creates executable tool instances from MCP server tool definitions.
5
+ * Ported from OpenClaw's mcp_tool.py.
6
+ *
7
+ * @module tool-runtime/mcp/tool
8
+ */
9
+ /**
10
+ * Create an MCP tool from a server tool definition
11
+ */
12
+ export function createMCPTool(definition, connection) {
13
+ return {
14
+ name: definition.name,
15
+ description: definition.description ?? `MCP tool: ${definition.name}`,
16
+ inputSchema: definition.inputSchema,
17
+ connection,
18
+ async execute(args) {
19
+ try {
20
+ const result = await connection.callTool(definition.name, args);
21
+ // Extract text content from result
22
+ if (result.content && result.content.length > 0) {
23
+ for (const item of result.content) {
24
+ if (item.type === "text" && item.text) {
25
+ return item.text;
26
+ }
27
+ }
28
+ }
29
+ return "No text content in MCP tool response";
30
+ }
31
+ catch (err) {
32
+ return `Error executing MCP tool ${definition.name}: ${err instanceof Error ? err.message : String(err)}`;
33
+ }
34
+ }
35
+ };
36
+ }
37
+ /**
38
+ * Create an executor for an MCP tool
39
+ */
40
+ export function createMCPToolExecutor(tool) {
41
+ return async (params, context) => {
42
+ const startTime = performance.now();
43
+ try {
44
+ const result = await tool.execute(params);
45
+ return {
46
+ ok: true,
47
+ output: result,
48
+ durationMs: Math.round(performance.now() - startTime),
49
+ sideEffects: [`mcp:${tool.name}:${context.toolCallId}`]
50
+ };
51
+ }
52
+ catch (err) {
53
+ return {
54
+ ok: false,
55
+ output: null,
56
+ error: err instanceof Error ? err.message : String(err),
57
+ durationMs: Math.round(performance.now() - startTime),
58
+ sideEffects: []
59
+ };
60
+ }
61
+ };
62
+ }
63
+ /**
64
+ * Convert MCP tool to standard tool definition format
65
+ */
66
+ export function mcpToolToDefinition(tool) {
67
+ return {
68
+ name: tool.name,
69
+ description: tool.description,
70
+ input_schema: tool.inputSchema
71
+ };
72
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Types
3
+ *
4
+ * Type definitions for MCP tool integration.
5
+ * Enables ClawMongo to connect to external MCP tool servers.
6
+ *
7
+ * @module tool-runtime/mcp/types
8
+ */
9
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@romiluz/clawmongo",
3
- "version": "0.1.0-rc.2",
3
+ "version": "0.1.0-rc.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "MongoDB-native OpenClaw-inspired assistant runtime",