moltblock 0.8.0 → 0.11.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/dist/config.d.ts CHANGED
@@ -70,6 +70,8 @@ export declare const ModelBindingSchema: z.ZodObject<{
70
70
  baseUrl: z.ZodString;
71
71
  apiKey: z.ZodDefault<z.ZodNullable<z.ZodString>>;
72
72
  model: z.ZodDefault<z.ZodString>;
73
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
74
+ maxRetries: z.ZodOptional<z.ZodNumber>;
73
75
  }, z.core.$strip>;
74
76
  export type ModelBinding = z.infer<typeof ModelBindingSchema>;
75
77
  /** Track which config source was used */
package/dist/config.js CHANGED
@@ -60,6 +60,8 @@ export const ModelBindingSchema = z.object({
60
60
  baseUrl: z.string().describe("API base URL"),
61
61
  apiKey: z.string().nullable().default(null).describe("Bearer token; null for local"),
62
62
  model: z.string().default("default").describe("Model name for chat completion"),
63
+ timeoutMs: z.number().int().positive().optional().describe("Request timeout in ms (default 60000)"),
64
+ maxRetries: z.number().int().min(0).optional().describe("Max retries on transient errors (default 2)"),
63
65
  });
64
66
  /** Validate that a config path is within allowed directories (cwd, homedir, or tmpdir). */
65
67
  function isAllowedConfigPath(filePath) {
@@ -131,8 +133,8 @@ export function loadMoltblockConfig() {
131
133
  lastConfigSource = "moltblock";
132
134
  return config;
133
135
  }
134
- catch {
135
- // Parse error, try fallback
136
+ catch (err) {
137
+ console.warn(`Warning: Failed to parse config ${moltblockFile}: ${err instanceof Error ? err.message : String(err)}`);
136
138
  }
137
139
  }
138
140
  // Fallback to OpenClaw config
@@ -148,8 +150,8 @@ export function loadMoltblockConfig() {
148
150
  return config;
149
151
  }
150
152
  }
151
- catch {
152
- // Parse error
153
+ catch (err) {
154
+ console.warn(`Warning: Failed to parse config ${openclawFile}: ${err instanceof Error ? err.message : String(err)}`);
153
155
  }
154
156
  }
155
157
  lastConfigSource = "env";
@@ -18,6 +18,7 @@ export interface EntityOptions {
18
18
  /**
19
19
  * Generic Entity: same Generator -> Critic -> Judge pipeline as CodeEntity,
20
20
  * but with a pluggable verifier and domain-aware prompts.
21
+ * Supports degraded fallback: if critic/judge fails, execution continues with available data.
21
22
  */
22
23
  export declare class Entity {
23
24
  private gateways;
@@ -27,6 +28,11 @@ export declare class Entity {
27
28
  /**
28
29
  * One full loop: task -> Generator -> Critic -> Judge -> Verifier -> gating.
29
30
  * Returns working memory with authoritative_artifact set only if verification passed.
31
+ *
32
+ * Degraded fallback:
33
+ * - Generator fails -> return immediately with verification failed + error evidence
34
+ * - Critic fails -> proceed to judge with empty critique
35
+ * - Judge fails -> use draft as final candidate
30
36
  */
31
37
  run(task: string, options?: {
32
38
  testCode?: string;
@@ -12,6 +12,7 @@ import { validateTask } from "./validation.js";
12
12
  /**
13
13
  * Generic Entity: same Generator -> Critic -> Judge pipeline as CodeEntity,
14
14
  * but with a pluggable verifier and domain-aware prompts.
15
+ * Supports degraded fallback: if critic/judge fails, execution continues with available data.
15
16
  */
16
17
  export class Entity {
17
18
  gateways;
@@ -30,6 +31,11 @@ export class Entity {
30
31
  /**
31
32
  * One full loop: task -> Generator -> Critic -> Judge -> Verifier -> gating.
32
33
  * Returns working memory with authoritative_artifact set only if verification passed.
34
+ *
35
+ * Degraded fallback:
36
+ * - Generator fails -> return immediately with verification failed + error evidence
37
+ * - Critic fails -> proceed to judge with empty critique
38
+ * - Judge fails -> use draft as final candidate
33
39
  */
34
40
  async run(task, options = {}) {
35
41
  const { testCode, store, entityVersion = "0.5.0", writeCheckpointAfter = false, } = options;
@@ -55,10 +61,34 @@ export class Entity {
55
61
  }
56
62
  memory.longTermContext = parts.length > 0 ? parts.join("\n---\n") : "";
57
63
  }
58
- // Run the agent pipeline with domain-aware prompts
59
- await runGenerator(this.gateways["generator"], memory, store ?? null, this.domain);
60
- await runCritic(this.gateways["critic"], memory, store ?? null, this.domain);
61
- await runJudge(this.gateways["judge"], memory, store ?? null, this.domain);
64
+ // Generator if it fails, we have nothing to work with
65
+ try {
66
+ await runGenerator(this.gateways["generator"], memory, store ?? null, this.domain);
67
+ }
68
+ catch (err) {
69
+ const errMsg = err instanceof Error ? err.message : String(err);
70
+ memory.meta["generatorError"] = errMsg;
71
+ memory.setVerification(false, `Generator failed: ${errMsg}`);
72
+ return memory;
73
+ }
74
+ // Critic — if it fails, proceed to judge with empty critique (degraded)
75
+ try {
76
+ await runCritic(this.gateways["critic"], memory, store ?? null, this.domain);
77
+ }
78
+ catch (err) {
79
+ const errMsg = err instanceof Error ? err.message : String(err);
80
+ memory.meta["criticError"] = errMsg;
81
+ memory.setCritique("");
82
+ }
83
+ // Judge — if it fails, use draft as final candidate (degraded)
84
+ try {
85
+ await runJudge(this.gateways["judge"], memory, store ?? null, this.domain);
86
+ }
87
+ catch (err) {
88
+ const errMsg = err instanceof Error ? err.message : String(err);
89
+ memory.meta["judgeError"] = errMsg;
90
+ memory.setFinalCandidate(memory.draft);
91
+ }
62
92
  // Run pluggable verifier
63
93
  const ctx = {
64
94
  task,
package/dist/entity.d.ts CHANGED
@@ -8,6 +8,7 @@ import { Store } from "./persistence.js";
8
8
  /**
9
9
  * Code Entity: Generator -> Critic -> Judge -> Verifier.
10
10
  * Uses working memory and per-role LLM gateways.
11
+ * Supports degraded fallback: if critic/judge fails, execution continues with available data.
11
12
  */
12
13
  export declare class CodeEntity {
13
14
  private gateways;
@@ -16,6 +17,11 @@ export declare class CodeEntity {
16
17
  * One full loop: task in -> Generator -> Critic -> Judge -> Verifier -> gating.
17
18
  * If store is provided and verification passed: admit to verified memory; optionally write checkpoint.
18
19
  * Returns working memory with authoritative_artifact set only if verification passed.
20
+ *
21
+ * Degraded fallback:
22
+ * - Generator fails -> return immediately with verification failed + error evidence
23
+ * - Critic fails -> proceed to judge with empty critique
24
+ * - Judge fails -> use draft as final candidate
19
25
  */
20
26
  run(task: string, options?: {
21
27
  testCode?: string;
package/dist/entity.js CHANGED
@@ -13,6 +13,7 @@ import { runVerifier } from "./verifier.js";
13
13
  /**
14
14
  * Code Entity: Generator -> Critic -> Judge -> Verifier.
15
15
  * Uses working memory and per-role LLM gateways.
16
+ * Supports degraded fallback: if critic/judge fails, execution continues with available data.
16
17
  */
17
18
  export class CodeEntity {
18
19
  gateways;
@@ -28,6 +29,11 @@ export class CodeEntity {
28
29
  * One full loop: task in -> Generator -> Critic -> Judge -> Verifier -> gating.
29
30
  * If store is provided and verification passed: admit to verified memory; optionally write checkpoint.
30
31
  * Returns working memory with authoritative_artifact set only if verification passed.
32
+ *
33
+ * Degraded fallback:
34
+ * - Generator fails -> return immediately with verification failed + error evidence
35
+ * - Critic fails -> proceed to judge with empty critique
36
+ * - Judge fails -> use draft as final candidate
31
37
  */
32
38
  async run(task, options = {}) {
33
39
  const { testCode, store, entityVersion = "0.2.0", writeCheckpointAfter = false, } = options;
@@ -59,10 +65,34 @@ export class CodeEntity {
59
65
  }
60
66
  memory.longTermContext = parts.length > 0 ? parts.join("\n---\n") : "";
61
67
  }
62
- // Run the agent pipeline
63
- await runGenerator(this.gateways["generator"], memory, store ?? null);
64
- await runCritic(this.gateways["critic"], memory, store ?? null);
65
- await runJudge(this.gateways["judge"], memory, store ?? null);
68
+ // Generator if it fails, we have nothing to work with
69
+ try {
70
+ await runGenerator(this.gateways["generator"], memory, store ?? null);
71
+ }
72
+ catch (err) {
73
+ const errMsg = err instanceof Error ? err.message : String(err);
74
+ memory.meta["generatorError"] = errMsg;
75
+ memory.setVerification(false, `Generator failed: ${errMsg}`);
76
+ return memory;
77
+ }
78
+ // Critic — if it fails, proceed with empty critique (degraded)
79
+ try {
80
+ await runCritic(this.gateways["critic"], memory, store ?? null);
81
+ }
82
+ catch (err) {
83
+ const errMsg = err instanceof Error ? err.message : String(err);
84
+ memory.meta["criticError"] = errMsg;
85
+ memory.setCritique("");
86
+ }
87
+ // Judge — if it fails, use draft as final candidate (degraded)
88
+ try {
89
+ await runJudge(this.gateways["judge"], memory, store ?? null);
90
+ }
91
+ catch (err) {
92
+ const errMsg = err instanceof Error ? err.message : String(err);
93
+ memory.meta["judgeError"] = errMsg;
94
+ memory.setFinalCandidate(memory.draft);
95
+ }
66
96
  await runVerifier(memory, testCode);
67
97
  // Record outcome and persist if verification passed
68
98
  const latencySec = (performance.now() - t0) / 1000;
package/dist/gateway.d.ts CHANGED
@@ -3,8 +3,14 @@
3
3
  * Supports OpenAI, Anthropic Claude, Google Gemini, local LLMs (LM Studio, Ollama), and more.
4
4
  */
5
5
  import type { ModelBinding, ChatMessage } from "./types.js";
6
+ /**
7
+ * Extract only the hostname from a URL for safe error messages.
8
+ * Strips path, query params, credentials, and port.
9
+ */
10
+ export declare function sanitizeBaseUrl(baseUrl: string): string;
6
11
  /**
7
12
  * One client per role; uses OpenAI-compatible API with base_url and optional api_key.
13
+ * Supports configurable timeout and retry via the OpenAI SDK.
8
14
  */
9
15
  export declare class LLMGateway {
10
16
  private client;
package/dist/gateway.js CHANGED
@@ -3,6 +3,19 @@
3
3
  * Supports OpenAI, Anthropic Claude, Google Gemini, local LLMs (LM Studio, Ollama), and more.
4
4
  */
5
5
  import OpenAI from "openai";
6
+ /**
7
+ * Extract only the hostname from a URL for safe error messages.
8
+ * Strips path, query params, credentials, and port.
9
+ */
10
+ export function sanitizeBaseUrl(baseUrl) {
11
+ try {
12
+ const url = new URL(baseUrl);
13
+ return url.hostname;
14
+ }
15
+ catch {
16
+ return "<invalid-url>";
17
+ }
18
+ }
6
19
  /**
7
20
  * If model is 'local' or empty and base_url is localhost, use first available model from API.
8
21
  */
@@ -27,6 +40,7 @@ async function resolveLocalModel(client, baseUrl, configured) {
27
40
  }
28
41
  /**
29
42
  * One client per role; uses OpenAI-compatible API with base_url and optional api_key.
43
+ * Supports configurable timeout and retry via the OpenAI SDK.
30
44
  */
31
45
  export class LLMGateway {
32
46
  client;
@@ -38,6 +52,8 @@ export class LLMGateway {
38
52
  this.client = new OpenAI({
39
53
  baseURL: binding.baseUrl,
40
54
  apiKey: binding.apiKey ?? "not-needed",
55
+ timeout: binding.timeoutMs ?? 60_000,
56
+ maxRetries: binding.maxRetries ?? 2,
41
57
  });
42
58
  this.model = binding.model;
43
59
  }
@@ -58,8 +74,11 @@ export class LLMGateway {
58
74
  });
59
75
  }
60
76
  catch (err) {
61
- const base = this.binding.baseUrl;
62
- throw new Error(`LLM request failed (model=${this.model}, baseUrl=${base}): ${err instanceof Error ? err.message : String(err)}`);
77
+ const host = sanitizeBaseUrl(this.binding.baseUrl);
78
+ const msg = err instanceof Error ? err.message : String(err);
79
+ // Strip any key-like strings from the error message
80
+ const safeMsg = msg.replace(/[A-Za-z0-9_\-]{20,}/g, "[REDACTED]");
81
+ throw new Error(`LLM request failed (model=${this.model}, host=${host}): ${safeMsg}`);
63
82
  }
64
83
  const choice = resp.choices[0];
65
84
  if (!choice?.message) {
@@ -51,11 +51,16 @@ export function triggerMolt(store, entityVersion, config, options = {}) {
51
51
  if (!allowed) {
52
52
  return { success: false, message: reason };
53
53
  }
54
- store.writeCheckpoint(entityVersion, graphHash || "molt", memoryHash || "", artifactRefs);
55
- const now = Date.now() / 1000;
56
- setGovernanceValue(store, "last_molt_at", now.toString());
57
- setGovernanceValue(store, "entity_version", entityVersion);
58
- auditLog(store, "molt", `version=${entityVersion} graph_hash=${graphHash}`);
54
+ // Wrap checkpoint + governance state + audit in a transaction for atomicity
55
+ const db = store.getDb();
56
+ const txn = db.transaction(() => {
57
+ store.writeCheckpoint(entityVersion, graphHash || "molt", memoryHash || "", artifactRefs);
58
+ const now = Date.now() / 1000;
59
+ setGovernanceValue(store, "last_molt_at", now.toString());
60
+ setGovernanceValue(store, "entity_version", entityVersion);
61
+ auditLog(store, "molt", `version=${entityVersion} graph_hash=${graphHash}`);
62
+ });
63
+ txn();
59
64
  return { success: true, message: "Molt completed" };
60
65
  }
61
66
  /**
@@ -27,11 +27,15 @@ export declare class GraphRunner {
27
27
  * Execute graph: task in -> run nodes in topo order -> run verifier on final node -> gating.
28
28
  * If store is provided and verification passed: admit to verified memory; optionally write checkpoint.
29
29
  * Returns working memory with slots filled and authoritative_artifact set iff verification passed.
30
+ *
31
+ * @param options.continueOnError - If true, node failures are recorded but execution continues.
32
+ * Failed nodes produce empty output. Default false (throws on first failure).
30
33
  */
31
34
  run(task: string, options?: {
32
35
  testCode?: string;
33
36
  store?: Store;
34
37
  entityVersion?: string;
35
38
  writeCheckpointAfter?: boolean;
39
+ continueOnError?: boolean;
36
40
  }): Promise<WorkingMemory>;
37
41
  }
@@ -39,9 +39,12 @@ export class GraphRunner {
39
39
  * Execute graph: task in -> run nodes in topo order -> run verifier on final node -> gating.
40
40
  * If store is provided and verification passed: admit to verified memory; optionally write checkpoint.
41
41
  * Returns working memory with slots filled and authoritative_artifact set iff verification passed.
42
+ *
43
+ * @param options.continueOnError - If true, node failures are recorded but execution continues.
44
+ * Failed nodes produce empty output. Default false (throws on first failure).
42
45
  */
43
46
  async run(task, options = {}) {
44
- const { testCode, store, entityVersion = "0.2.0", writeCheckpointAfter = false, } = options;
47
+ const { testCode, store, entityVersion = "0.2.0", writeCheckpointAfter = false, continueOnError = false, } = options;
45
48
  const t0 = performance.now();
46
49
  const memory = new WorkingMemory();
47
50
  memory.setTask(task);
@@ -73,10 +76,32 @@ export class GraphRunner {
73
76
  }
74
77
  const gateway = this.gateways.get(node.binding);
75
78
  if (!gateway) {
76
- throw new Error(`No gateway for binding '${node.binding}'`);
79
+ const err = `No gateway for binding '${node.binding}'`;
80
+ if (continueOnError) {
81
+ if (!memory.meta["nodeErrors"]) {
82
+ memory.meta["nodeErrors"] = {};
83
+ }
84
+ memory.meta["nodeErrors"][nodeId] = err;
85
+ memory.setSlot(nodeId, "");
86
+ continue;
87
+ }
88
+ throw new Error(err);
89
+ }
90
+ try {
91
+ const out = await runRole(node.role, gateway, task, inputs, memory.longTermContext, store ?? null, this.domain);
92
+ memory.setSlot(nodeId, out);
93
+ }
94
+ catch (err) {
95
+ const errMsg = err instanceof Error ? err.message : String(err);
96
+ if (!continueOnError) {
97
+ throw err;
98
+ }
99
+ if (!memory.meta["nodeErrors"]) {
100
+ memory.meta["nodeErrors"] = {};
101
+ }
102
+ memory.meta["nodeErrors"][nodeId] = errMsg;
103
+ memory.setSlot(nodeId, "");
77
104
  }
78
- const out = await runRole(node.role, gateway, task, inputs, memory.longTermContext, store ?? null, this.domain);
79
- memory.setSlot(nodeId, out);
80
105
  }
81
106
  // Set final candidate from final node
82
107
  const finalId = this.graph.getFinalNodeId();
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Moltblock — framework for evolving composite intelligences (Entities).
3
3
  */
4
- export declare const VERSION = "0.8.0";
4
+ export declare const VERSION = "0.11.1";
5
5
  export type { ModelBinding, BindingEntry, AgentConfig, MoltblockConfig, ChatMessage, VerifiedMemoryEntry, CheckpointEntry, OutcomeEntry, InboxEntry, StrategySuggestion, ReceivedArtifact, GovernanceConfig, } from "./types.js";
6
6
  export { WorkingMemory } from "./memory.js";
7
7
  export { signArtifact, verifyArtifact, artifactHash } from "./signing.js";
8
8
  export { loadMoltblockConfig, defaultCodeEntityBindings, detectProvider, getConfigSource, loadPolicyRules, BindingEntrySchema, AgentConfigSchema, MoltblockConfigSchema, ModelBindingSchema, PolicyRuleSchema, type BindingOverrides, type ConfigSource, type PolicyRuleConfig, } from "./config.js";
9
9
  export { Store, hashGraph, hashMemory, auditLog, getGovernanceValue, setGovernanceValue, putInbox, getInbox, recordOutcome, getRecentOutcomes, getStrategy, setStrategy, } from "./persistence.js";
10
- export { LLMGateway } from "./gateway.js";
10
+ export { LLMGateway, sanitizeBaseUrl } from "./gateway.js";
11
11
  export { runGenerator, runCritic, runJudge, runRole, } from "./agents.js";
12
12
  export { AgentGraph, GraphNodeSchema, GraphEdgeSchema, AgentGraphSchema, type GraphNode, type GraphEdge, type AgentGraphData, } from "./graph-schema.js";
13
13
  export { GraphRunner, type GraphRunnerOptions } from "./graph-runner.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Moltblock — framework for evolving composite intelligences (Entities).
3
3
  */
4
- export const VERSION = "0.8.0";
4
+ export const VERSION = "0.11.1";
5
5
  // Memory
6
6
  export { WorkingMemory } from "./memory.js";
7
7
  // Signing
@@ -11,7 +11,7 @@ export { loadMoltblockConfig, defaultCodeEntityBindings, detectProvider, getConf
11
11
  // Persistence
12
12
  export { Store, hashGraph, hashMemory, auditLog, getGovernanceValue, setGovernanceValue, putInbox, getInbox, recordOutcome, getRecentOutcomes, getStrategy, setStrategy, } from "./persistence.js";
13
13
  // Gateway
14
- export { LLMGateway } from "./gateway.js";
14
+ export { LLMGateway, sanitizeBaseUrl } from "./gateway.js";
15
15
  // Agents
16
16
  export { runGenerator, runCritic, runJudge, runRole, } from "./agents.js";
17
17
  // Graph
@@ -35,6 +35,11 @@ export declare class Store {
35
35
  /** Get internal db for helper functions */
36
36
  getDb(): Database.Database;
37
37
  close(): void;
38
+ /**
39
+ * Explicit resource cleanup via Symbol.dispose.
40
+ * Enables `using store = new Store(...)` with TypeScript 5.2+ explicit resource management.
41
+ */
42
+ [Symbol.dispose](): void;
38
43
  }
39
44
  /**
40
45
  * Stable hash for graph config (for checkpoint).
@@ -25,6 +25,8 @@ export class Store {
25
25
  }
26
26
  }
27
27
  this.db = new Database(p);
28
+ // Enable WAL mode for better concurrent read/write performance
29
+ this.db.pragma("journal_mode = WAL");
28
30
  if (p !== ":memory:") {
29
31
  try {
30
32
  fs.chmodSync(p, 0o600);
@@ -174,6 +176,13 @@ export class Store {
174
176
  close() {
175
177
  this.db.close();
176
178
  }
179
+ /**
180
+ * Explicit resource cleanup via Symbol.dispose.
181
+ * Enables `using store = new Store(...)` with TypeScript 5.2+ explicit resource management.
182
+ */
183
+ [Symbol.dispose]() {
184
+ this.close();
185
+ }
177
186
  }
178
187
  /**
179
188
  * Stable hash for graph config (for checkpoint).
@@ -292,12 +301,15 @@ export function getStrategy(store, role) {
292
301
  */
293
302
  export function setStrategy(store, role, content) {
294
303
  const db = store.getDb();
295
- const versionStmt = db.prepare("SELECT COALESCE(MAX(version), 0) as maxVersion FROM strategies WHERE entity_id = ? AND role = ?");
296
- const versionRow = versionStmt.get(store.entityId, role);
297
- const version = (versionRow?.maxVersion ?? 0) + 1;
298
- const stmt = db.prepare(`
299
- INSERT INTO strategies (entity_id, role, version, content, created_at)
300
- VALUES (?, ?, ?, ?, ?)
301
- `);
302
- stmt.run(store.entityId, role, version, content, Date.now() / 1000);
304
+ const txn = db.transaction(() => {
305
+ const versionStmt = db.prepare("SELECT COALESCE(MAX(version), 0) as maxVersion FROM strategies WHERE entity_id = ? AND role = ?");
306
+ const versionRow = versionStmt.get(store.entityId, role);
307
+ const version = (versionRow?.maxVersion ?? 0) + 1;
308
+ const stmt = db.prepare(`
309
+ INSERT INTO strategies (entity_id, role, version, content, created_at)
310
+ VALUES (?, ?, ?, ?, ?)
311
+ `);
312
+ stmt.run(store.entityId, role, version, content, Date.now() / 1000);
313
+ });
314
+ txn();
303
315
  }
@@ -19,10 +19,12 @@ export interface PolicyRule {
19
19
  /**
20
20
  * Rule-based policy verifier. Checks artifacts and tasks against deny/allow rules.
21
21
  * Allow rules in the same category override deny rules.
22
+ * Regexes are pre-compiled in the constructor for performance.
22
23
  */
23
24
  export declare class PolicyVerifier implements Verifier {
24
25
  readonly name = "PolicyVerifier";
25
26
  private rules;
27
+ private compiledRules;
26
28
  constructor(customRules?: PolicyRule[]);
27
29
  verify(memory: WorkingMemory, context?: VerifierContext): Promise<VerificationResult>;
28
30
  private getTargetText;
@@ -30,15 +30,22 @@ const BUILTIN_RULES = [
30
30
  /**
31
31
  * Rule-based policy verifier. Checks artifacts and tasks against deny/allow rules.
32
32
  * Allow rules in the same category override deny rules.
33
+ * Regexes are pre-compiled in the constructor for performance.
33
34
  */
34
35
  export class PolicyVerifier {
35
36
  name = "PolicyVerifier";
36
37
  rules;
38
+ compiledRules;
37
39
  constructor(customRules) {
38
40
  this.rules = [...BUILTIN_RULES];
39
41
  if (customRules) {
40
42
  this.rules.push(...customRules);
41
43
  }
44
+ // Pre-compile all regexes once
45
+ this.compiledRules = this.rules.map((rule) => ({
46
+ rule,
47
+ regex: new RegExp(rule.pattern, "i"),
48
+ }));
42
49
  }
43
50
  async verify(memory, context) {
44
51
  const artifact = memory.finalCandidate || "";
@@ -46,23 +53,21 @@ export class PolicyVerifier {
46
53
  const violations = [];
47
54
  const allowedCategories = new Set();
48
55
  // First pass: collect allowed categories
49
- for (const rule of this.rules) {
56
+ for (const { rule, regex } of this.compiledRules) {
50
57
  if (!rule.enabled || rule.action !== "allow")
51
58
  continue;
52
59
  const text = this.getTargetText(rule.target, artifact, task);
53
- const regex = new RegExp(rule.pattern, "i");
54
60
  if (regex.test(text)) {
55
61
  allowedCategories.add(rule.category);
56
62
  }
57
63
  }
58
64
  // Second pass: check deny rules (skip allowed categories)
59
- for (const rule of this.rules) {
65
+ for (const { rule, regex } of this.compiledRules) {
60
66
  if (!rule.enabled || rule.action !== "deny")
61
67
  continue;
62
68
  if (allowedCategories.has(rule.category))
63
69
  continue;
64
70
  const text = this.getTargetText(rule.target, artifact, task);
65
- const regex = new RegExp(rule.pattern, "i");
66
71
  if (regex.test(text)) {
67
72
  violations.push(`[${rule.id}] ${rule.description}`);
68
73
  }
package/dist/signing.js CHANGED
@@ -42,7 +42,11 @@ function getSecret(entityId) {
42
42
  return key;
43
43
  }
44
44
  catch {
45
- // Fallback if filesystem is unavailable (e.g. in tests)
45
+ // Weak deterministic fallback requires explicit opt-in
46
+ if (process.env["MOLTBLOCK_INSECURE_DEV_SIGNING"] !== "1") {
47
+ throw new Error(`No MOLTBLOCK_SIGNING_KEY set and filesystem unavailable. ` +
48
+ `Set MOLTBLOCK_SIGNING_KEY for signing, or set MOLTBLOCK_INSECURE_DEV_SIGNING=1 to allow weak dev fallback.`);
49
+ }
46
50
  console.warn(`Warning: Using weak default signing key for entity "${entityId}". ` +
47
51
  `Set MOLTBLOCK_SIGNING_KEY for secure artifact signing.`);
48
52
  return Buffer.from(`dev-only-insecure-key-${entityId}`, "utf-8");
package/dist/types.d.ts CHANGED
@@ -7,6 +7,10 @@ export interface ModelBinding {
7
7
  baseUrl: string;
8
8
  apiKey: string | null;
9
9
  model: string;
10
+ /** Request timeout in milliseconds (default 60000). */
11
+ timeoutMs?: number;
12
+ /** Max retries on transient errors (default 2). */
13
+ maxRetries?: number;
10
14
  }
11
15
  /** JSON config binding entry (from moltblock.json) */
12
16
  export interface BindingEntry {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moltblock",
3
- "version": "0.8.0",
3
+ "version": "0.11.1",
4
4
  "description": "Framework for building evolving composite AI intelligences (Entities)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -297,6 +297,11 @@ const codeEntity = new Entity({
297
297
  - v0.2 — MVP Entity implementation (spec + Code Entity loop + graph, memory, improvement, governance, handoff)
298
298
  - v0.3 — Multi-Entity collaboration (orchestration and tooling)
299
299
  - v0.6 — Pluggable verification, policy rules, generic entity, risk classification, OpenClaw skill
300
+ - v0.8 — Quick wins, error handling, test coverage, license fix
301
+ - v0.9 — Core reliability: gateway retry/timeout, graph-runner error handling, entity degraded fallback
302
+ - v0.10 — Test coverage: agents, graph-runner, handoff, integration tests; test helpers
303
+ - v0.10.1 — Persistence hardening: WAL mode, transactions, regex caching, signing key opt-in
304
+ - v0.11 — CI coverage enforcement, stricter tsconfig, documentation fixes
300
305
 
301
306
  ---
302
307
 
package/skill/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: moltblock - Trust Layer for AI Agents
3
3
  description: Verification gating for AI-generated artifacts. Policy checks and code verification to catch dangerous patterns before execution.
4
- version: 0.8.0
4
+ version: 0.11.0
5
5
  metadata:
6
6
  openclaw:
7
7
  requires:
@@ -12,10 +12,14 @@ metadata:
12
12
  - moltblock.json
13
13
  - ~/.moltblock/moltblock.json
14
14
  primaryEnv: OPENAI_API_KEY
15
+ optionalEnv:
16
+ - ANTHROPIC_API_KEY
17
+ - GOOGLE_API_KEY
18
+ - ZAI_API_KEY
15
19
  homepage: https://github.com/moltblock/moltblock
16
20
  install:
17
21
  - kind: node
18
- package: moltblock@0.8.0
22
+ package: moltblock@0.11.0
19
23
  bins: [moltblock]
20
24
  ---
21
25
 
@@ -27,9 +31,9 @@ Moltblock provides verification gating for AI-generated artifacts. It runs polic
27
31
 
28
32
  **What moltblock does:**
29
33
  - Generates code via LLM API calls, then runs policy checks against the output
30
- - When `--test` is provided, executes vitest to verify generated code against the test file
34
+ - When `--test` is provided, executes vitest to verify generated code against a user-provided test file (see **Security: Test Execution** below)
31
35
  - Reads its own config files (`moltblock.json`, `~/.moltblock/moltblock.json`) if present
32
- - API keys are read from environment variables at runtime never stored or transmitted beyond the configured LLM provider
36
+ - API keys are read from environment variables at runtime and sent only to the configured LLM provider endpoint
33
37
 
34
38
  ## When to Use
35
39
 
@@ -46,7 +50,7 @@ Verify a task before execution.
46
50
  ### Usage
47
51
 
48
52
  ```bash
49
- npx moltblock@0.8.0 "<task description>" --provider <provider> --json
53
+ npx moltblock@0.11.0 "<task description>" --provider <provider> --json
50
54
  ```
51
55
 
52
56
  ### Parameters
@@ -61,20 +65,20 @@ npx moltblock@0.8.0 "<task description>" --provider <provider> --json
61
65
 
62
66
  ### Environment Variables
63
67
 
64
- No API key is required moltblock falls back to a local LLM (localhost:1234) if no key is set. To use a cloud provider, set **one** of these:
65
- - `OPENAI_API_KEY` — OpenAI
66
- - `ANTHROPIC_API_KEY` — Anthropic/Claude
67
- - `GOOGLE_API_KEY` — Google/Gemini
68
- - `ZAI_API_KEY` — ZAI
68
+ Moltblock auto-detects the LLM provider from whichever API key is set. If no key is set, it falls back to a local LLM at `localhost:1234`. Set **one** of these for a cloud provider:
69
+ - `OPENAI_API_KEY` — OpenAI (primary)
70
+ - `ANTHROPIC_API_KEY` — Anthropic/Claude (optional)
71
+ - `GOOGLE_API_KEY` — Google/Gemini (optional)
72
+ - `ZAI_API_KEY` — ZAI (optional)
69
73
 
70
74
  ### Example
71
75
 
72
76
  ```bash
73
77
  # Verify a task
74
- npx moltblock@0.8.0 "implement a function that validates email addresses" --json
78
+ npx moltblock@0.11.0 "implement a function that validates email addresses" --json
75
79
 
76
80
  # Verify code with tests
77
- npx moltblock@0.8.0 "implement a markdown-to-html converter" --test ./tests/markdown.test.ts --json
81
+ npx moltblock@0.11.0 "implement a markdown-to-html converter" --test ./tests/markdown.test.ts --json
78
82
  ```
79
83
 
80
84
  ### Output (JSON mode)
@@ -95,13 +99,13 @@ npx moltblock@0.8.0 "implement a markdown-to-html converter" --test ./tests/mark
95
99
  Use directly with npx (recommended, no install needed):
96
100
 
97
101
  ```bash
98
- npx moltblock@0.8.0 "your task" --json
102
+ npx moltblock@0.11.0 "your task" --json
99
103
  ```
100
104
 
101
105
  Or install globally:
102
106
 
103
107
  ```bash
104
- npm install -g moltblock@0.8.0
108
+ npm install -g moltblock@0.11.0
105
109
  ```
106
110
 
107
111
  ## Configuration
@@ -130,6 +134,17 @@ See the [full configuration docs](https://github.com/moltblock/moltblock#configu
130
134
  - npm: [npmjs.com/package/moltblock](https://www.npmjs.com/package/moltblock)
131
135
  - License: MIT
132
136
 
137
+ ## Security: Test Execution
138
+
139
+ When `--test` is used, moltblock writes LLM-generated code to a temporary file and runs vitest against it using the user-provided test file. **This executes LLM-generated code in a Node.js process on the host machine.** Mitigations:
140
+
141
+ - The test file path must be provided explicitly by the user — moltblock does not select or generate test files
142
+ - Generated code is written to `os.tmpdir()` and cleaned up after execution
143
+ - Policy rules run **before** test execution to deny known dangerous patterns (e.g. `rm -rf`, `eval`, `child_process`, filesystem writes)
144
+ - Without `--test`, no code execution occurs — only policy checks run against the generated artifact
145
+
146
+ **Residual risk:** Policy rules are pattern-based and cannot catch all dangerous code. LLM-generated code executed via `--test` may perform arbitrary actions within the permissions of the Node.js process. Users should review generated code or run moltblock in a sandboxed environment when verifying untrusted tasks.
147
+
133
148
  ## Disclaimer
134
149
 
135
150
  Moltblock reduces risk but does not eliminate it. Verification is best-effort — policy rules and LLM-based checks can miss dangerous patterns. Always review generated artifacts before executing them. The authors and contributors are not responsible for any damage, data loss, or security incidents resulting from the use of this tool. Use at your own risk.