@openclaw/lobster 2026.3.13 → 2026.5.1-beta.1

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/README.md CHANGED
@@ -69,7 +69,6 @@ Notes:
69
69
 
70
70
  ## Security
71
71
 
72
- - Runs the `lobster` executable as a local subprocess.
72
+ - Runs Lobster in process via the published `@clawdbot/lobster/core` runtime.
73
73
  - Does not manage OAuth/tokens.
74
74
  - Uses timeouts, stdout caps, and strict JSON envelope parsing.
75
- - Ensure `lobster` is available on `PATH` for the gateway process.
package/index.ts CHANGED
@@ -1,18 +1,24 @@
1
- import type {
2
- AnyAgentTool,
3
- OpenClawPluginApi,
4
- OpenClawPluginToolFactory,
5
- } from "openclaw/plugin-sdk/lobster";
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import type { AnyAgentTool, OpenClawPluginApi, OpenClawPluginToolFactory } from "./runtime-api.js";
6
3
  import { createLobsterTool } from "./src/lobster-tool.js";
7
4
 
8
- export default function register(api: OpenClawPluginApi) {
9
- api.registerTool(
10
- ((ctx) => {
11
- if (ctx.sandboxed) {
12
- return null;
13
- }
14
- return createLobsterTool(api) as AnyAgentTool;
15
- }) as OpenClawPluginToolFactory,
16
- { optional: true },
17
- );
18
- }
5
+ export default definePluginEntry({
6
+ id: "lobster",
7
+ name: "Lobster",
8
+ description: "Optional local shell helper tools",
9
+ register(api: OpenClawPluginApi) {
10
+ api.registerTool(
11
+ ((ctx) => {
12
+ if (ctx.sandboxed) {
13
+ return null;
14
+ }
15
+ const taskFlow =
16
+ api.runtime?.tasks.managedFlows && ctx.sessionKey
17
+ ? api.runtime.tasks.managedFlows.fromToolContext(ctx)
18
+ : undefined;
19
+ return createLobsterTool(api, { taskFlow }) as AnyAgentTool;
20
+ }) as OpenClawPluginToolFactory,
21
+ { optional: true },
22
+ );
23
+ },
24
+ });
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "id": "lobster",
3
+ "activation": {
4
+ "onStartup": true
5
+ },
3
6
  "name": "Lobster",
4
7
  "description": "Typed workflow tool with resumable approvals.",
5
8
  "configSchema": {
package/package.json CHANGED
@@ -1,14 +1,33 @@
1
1
  {
2
2
  "name": "@openclaw/lobster",
3
- "version": "2026.3.13",
3
+ "version": "2026.5.1-beta.1",
4
4
  "description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/openclaw/openclaw"
8
+ },
5
9
  "type": "module",
6
10
  "dependencies": {
7
- "@sinclair/typebox": "0.34.48"
11
+ "@clawdbot/lobster": "2026.4.6",
12
+ "ajv": "^8.20.0",
13
+ "typebox": "1.1.37"
14
+ },
15
+ "devDependencies": {
16
+ "@openclaw/plugin-sdk": "workspace:*"
8
17
  },
9
18
  "openclaw": {
10
19
  "extensions": [
11
20
  "./index.ts"
12
- ]
21
+ ],
22
+ "compat": {
23
+ "pluginApi": ">=2026.4.25"
24
+ },
25
+ "build": {
26
+ "openclawVersion": "2026.5.1-beta.1"
27
+ },
28
+ "release": {
29
+ "publishToClawHub": true,
30
+ "publishToNpm": true
31
+ }
13
32
  }
14
33
  }
package/runtime-api.ts ADDED
@@ -0,0 +1,12 @@
1
+ export { definePluginEntry } from "openclaw/plugin-sdk/core";
2
+ export type {
3
+ AnyAgentTool,
4
+ OpenClawPluginApi,
5
+ OpenClawPluginToolContext,
6
+ OpenClawPluginToolFactory,
7
+ } from "openclaw/plugin-sdk/core";
8
+ export {
9
+ applyWindowsSpawnProgramPolicy,
10
+ materializeWindowsSpawnProgram,
11
+ resolveWindowsSpawnProgramCandidate,
12
+ } from "openclaw/plugin-sdk/windows-spawn";
@@ -0,0 +1,142 @@
1
+ import { createHash } from "node:crypto";
2
+ import AjvPkg, { type AnySchema, type ValidateFunction } from "ajv";
3
+
4
+ const installedSymbol = Symbol.for("openclaw.lobster.ajv-compile-cache.installed");
5
+ const cacheSymbol = Symbol.for("openclaw.lobster.ajv-compile-cache.entries");
6
+ const maxEntries = 512;
7
+
8
+ type AjvInstance = import("ajv").default;
9
+
10
+ type CompileCacheEntry = {
11
+ schema: AnySchema;
12
+ validate: ValidateFunction;
13
+ };
14
+
15
+ const AjvCtor = AjvPkg as unknown as {
16
+ new (opts?: object): AjvInstance;
17
+ prototype: AjvInstance;
18
+ };
19
+
20
+ type AjvWithCompileCache = AjvInstance & {
21
+ [cacheSymbol]?: Map<string, CompileCacheEntry>;
22
+ };
23
+
24
+ type AjvPrototypePatch = {
25
+ [installedSymbol]?: boolean;
26
+ compile: (schema: AnySchema) => ValidateFunction;
27
+ removeSchema: (schemaKeyRef?: Parameters<AjvInstance["removeSchema"]>[0]) => AjvInstance;
28
+ };
29
+
30
+ type JsonLike = null | boolean | number | string | JsonLike[] | { [key: string]: JsonLike };
31
+
32
+ function stableJsonStringify(value: unknown, seen = new WeakSet<object>()): string {
33
+ if (value === null || typeof value !== "object") {
34
+ return JSON.stringify(value);
35
+ }
36
+ if (seen.has(value)) {
37
+ throw new TypeError("Cannot cache cyclic JSON schema");
38
+ }
39
+ seen.add(value);
40
+ if (Array.isArray(value)) {
41
+ const items = value.map((entry) => stableJsonStringify(entry, seen));
42
+ seen.delete(value);
43
+ return `[${items.join(",")}]`;
44
+ }
45
+ const record = value as Record<string, unknown>;
46
+ const keys = Object.keys(record).toSorted();
47
+ const properties = keys
48
+ .filter((key) => record[key] !== undefined)
49
+ .map((key) => `${JSON.stringify(key)}:${stableJsonStringify(record[key], seen)}`);
50
+ seen.delete(value);
51
+ return `{${properties.join(",")}}`;
52
+ }
53
+
54
+ function compileCacheKey(schema: unknown): string | null {
55
+ try {
56
+ return createHash("sha256").update(stableJsonStringify(schema)).digest("hex");
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ function readCompileCache(instance: AjvWithCompileCache): Map<string, CompileCacheEntry> {
63
+ let cache = instance[cacheSymbol];
64
+ if (!cache) {
65
+ cache = new Map<string, CompileCacheEntry>();
66
+ Object.defineProperty(instance, cacheSymbol, {
67
+ value: cache,
68
+ configurable: true,
69
+ });
70
+ }
71
+ return cache;
72
+ }
73
+
74
+ function rememberCompiledValidator(params: {
75
+ cache: Map<string, CompileCacheEntry>;
76
+ instance: AjvWithCompileCache;
77
+ key: string;
78
+ removeSchema: AjvPrototypePatch["removeSchema"];
79
+ schema: AnySchema;
80
+ validate: ValidateFunction;
81
+ }) {
82
+ const { cache, instance, key, removeSchema, schema, validate } = params;
83
+ if (!cache.has(key) && cache.size >= maxEntries) {
84
+ const oldest = cache.keys().next().value;
85
+ if (oldest !== undefined) {
86
+ const evicted = cache.get(oldest);
87
+ cache.delete(oldest);
88
+ if (evicted) {
89
+ removeSchema.call(instance, evicted.schema);
90
+ }
91
+ }
92
+ }
93
+ cache.set(key, { schema, validate });
94
+ }
95
+
96
+ export function installLobsterAjvCompileCache() {
97
+ const proto = AjvCtor.prototype as unknown as AjvPrototypePatch;
98
+ if (proto[installedSymbol]) {
99
+ return;
100
+ }
101
+
102
+ const originalCompile = proto.compile;
103
+ const originalRemoveSchema = proto.removeSchema;
104
+
105
+ Object.defineProperty(proto, installedSymbol, {
106
+ value: true,
107
+ configurable: true,
108
+ });
109
+
110
+ proto.compile = function compileWithContentCache(
111
+ this: AjvWithCompileCache,
112
+ schema: AnySchema,
113
+ ): ValidateFunction<JsonLike> {
114
+ const key = compileCacheKey(schema);
115
+ if (!key) {
116
+ return originalCompile.call(this, schema) as ValidateFunction<JsonLike>;
117
+ }
118
+ const cache = readCompileCache(this);
119
+ const cached = cache.get(key);
120
+ if (cached) {
121
+ return cached.validate as ValidateFunction<JsonLike>;
122
+ }
123
+ const validate = originalCompile.call(this, schema) as ValidateFunction<JsonLike>;
124
+ rememberCompiledValidator({
125
+ cache,
126
+ instance: this,
127
+ key,
128
+ removeSchema: originalRemoveSchema,
129
+ schema,
130
+ validate,
131
+ });
132
+ return validate;
133
+ };
134
+
135
+ proto.removeSchema = function removeSchemaAndClearContentCache(
136
+ this: AjvWithCompileCache,
137
+ schemaKeyRef?: Parameters<AjvInstance["removeSchema"]>[0],
138
+ ) {
139
+ this[cacheSymbol]?.clear();
140
+ return originalRemoveSchema.call(this, schemaKeyRef);
141
+ };
142
+ }
@@ -0,0 +1,60 @@
1
+ declare module "@clawdbot/lobster/core" {
2
+ type LobsterApprovalRequest = {
3
+ type: "approval_request";
4
+ prompt: string;
5
+ items: unknown[];
6
+ resumeToken?: string;
7
+ approvalId?: string;
8
+ } | null;
9
+
10
+ type LobsterToolContext = {
11
+ cwd?: string;
12
+ env?: Record<string, string | undefined>;
13
+ stdin?: NodeJS.ReadableStream;
14
+ stdout?: NodeJS.WritableStream;
15
+ stderr?: NodeJS.WritableStream;
16
+ signal?: AbortSignal;
17
+ registry?: unknown;
18
+ llmAdapters?: Record<string, unknown>;
19
+ };
20
+
21
+ type LobsterToolEnvelope =
22
+ | {
23
+ protocolVersion: 1;
24
+ ok: true;
25
+ status: "ok" | "needs_approval" | "needs_input" | "cancelled";
26
+ output: unknown[];
27
+ requiresApproval: LobsterApprovalRequest;
28
+ requiresInput?: {
29
+ prompt: string;
30
+ schema?: unknown;
31
+ items?: unknown[];
32
+ resumeToken?: string;
33
+ approvalId?: string;
34
+ } | null;
35
+ }
36
+ | {
37
+ protocolVersion: 1;
38
+ ok: false;
39
+ error: {
40
+ type: string;
41
+ message: string;
42
+ };
43
+ };
44
+
45
+ export function runToolRequest(params: {
46
+ pipeline?: string;
47
+ filePath?: string;
48
+ args?: Record<string, unknown>;
49
+ ctx?: LobsterToolContext;
50
+ }): Promise<LobsterToolEnvelope>;
51
+
52
+ export function resumeToolRequest(params: {
53
+ token?: string;
54
+ approvalId?: string;
55
+ approved?: boolean;
56
+ response?: unknown;
57
+ cancel?: boolean;
58
+ ctx?: LobsterToolContext;
59
+ }): Promise<LobsterToolEnvelope>;
60
+ }