@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 +1 -2
- package/index.ts +22 -16
- package/openclaw.plugin.json +3 -0
- package/package.json +22 -3
- package/runtime-api.ts +12 -0
- package/src/lobster-ajv-cache.ts +142 -0
- package/src/lobster-core.d.ts +60 -0
- package/src/lobster-runner.test.ts +572 -0
- package/src/lobster-runner.ts +395 -0
- package/src/lobster-taskflow.test.ts +227 -0
- package/src/lobster-taskflow.ts +279 -0
- package/src/lobster-tool.test.ts +250 -208
- package/src/lobster-tool.ts +245 -191
- package/src/taskflow-test-helpers.ts +48 -0
- package/tsconfig.json +16 -0
- package/src/test-helpers.ts +0 -43
- package/src/windows-spawn.test.ts +0 -118
- package/src/windows-spawn.ts +0 -36
package/README.md
CHANGED
|
@@ -69,7 +69,6 @@ Notes:
|
|
|
69
69
|
|
|
70
70
|
## Security
|
|
71
71
|
|
|
72
|
-
- Runs
|
|
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
|
|
2
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
});
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/lobster",
|
|
3
|
-
"version": "2026.
|
|
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
|
-
"@
|
|
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
|
+
}
|