moltblock 0.7.8 → 0.11.0

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/cli.js CHANGED
@@ -7,10 +7,12 @@ import { program } from "commander";
7
7
  import { CodeEntity } from "./entity.js";
8
8
  import { defaultCodeEntityBindings } from "./config.js";
9
9
  import { validateTask } from "./validation.js";
10
+ import { VERSION } from "./index.js";
10
11
  async function main() {
11
12
  program
12
13
  .name("moltblock")
13
14
  .description("Moltblock Code Entity — one task through the loop.")
15
+ .version(VERSION, "-V, --version", "Output the current version")
14
16
  .argument("<task>", "Task description (e.g. 'Implement a function add(a,b) that returns a+b.')")
15
17
  .option("-t, --test <path>", "Path to file containing test code (e.g. vitest test module). If omitted, only syntax check.")
16
18
  .option("--json", "Output result as JSON (draft, critique, final, verification_passed, authoritative_artifact).")
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
  }
@@ -49,11 +65,21 @@ export class LLMGateway {
49
65
  this.model = await resolveLocalModel(this.client, this.binding.baseUrl, this.model);
50
66
  this.modelResolved = true;
51
67
  }
52
- const resp = await this.client.chat.completions.create({
53
- model: this.model,
54
- messages: messages.map((m) => ({ role: m.role, content: m.content })),
55
- max_tokens: maxTokens,
56
- });
68
+ let resp;
69
+ try {
70
+ resp = await this.client.chat.completions.create({
71
+ model: this.model,
72
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
73
+ max_tokens: maxTokens,
74
+ });
75
+ }
76
+ catch (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}`);
82
+ }
57
83
  const choice = resp.choices[0];
58
84
  if (!choice?.message) {
59
85
  return "";
@@ -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();
@@ -52,7 +52,13 @@ export async function runEval(runTask, evalTasks, store) {
52
52
  let passed = 0;
53
53
  for (const task of evalTasks) {
54
54
  const t0 = performance.now();
55
- const ok = await runTask(task);
55
+ let ok;
56
+ try {
57
+ ok = await runTask(task);
58
+ }
59
+ catch {
60
+ ok = false;
61
+ }
56
62
  const latency = (performance.now() - t0) / 1000;
57
63
  if (store) {
58
64
  recordOutcome(store, ok, latency, task.slice(0, 100));
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.6.0";
4
+ export declare const VERSION = "0.11.0";
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.6.0";
4
+ export const VERSION = "0.11.0";
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.7.8",
3
+ "version": "0.11.0",
4
4
  "description": "Framework for building evolving composite AI intelligences (Entities)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,7 +31,11 @@
31
31
  "moltblock"
32
32
  ],
33
33
  "author": "",
34
- "license": "Apache-2.0",
34
+ "license": "MIT",
35
+ "homepage": "https://github.com/moltblock/moltblock",
36
+ "bugs": {
37
+ "url": "https://github.com/moltblock/moltblock/issues"
38
+ },
35
39
  "repository": {
36
40
  "type": "git",
37
41
  "url": "https://github.com/moltblock/moltblock"
@@ -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.7.8
4
+ version: 0.8.0
5
5
  metadata:
6
6
  openclaw:
7
7
  requires:
@@ -15,7 +15,7 @@ metadata:
15
15
  homepage: https://github.com/moltblock/moltblock
16
16
  install:
17
17
  - kind: node
18
- package: moltblock@0.7.8
18
+ package: moltblock@0.8.0
19
19
  bins: [moltblock]
20
20
  ---
21
21
 
@@ -46,7 +46,7 @@ Verify a task before execution.
46
46
  ### Usage
47
47
 
48
48
  ```bash
49
- npx moltblock@0.7.8 "<task description>" --provider <provider> --json
49
+ npx moltblock@0.8.0 "<task description>" --provider <provider> --json
50
50
  ```
51
51
 
52
52
  ### Parameters
@@ -71,10 +71,10 @@ No API key is required — moltblock falls back to a local LLM (localhost:1234)
71
71
 
72
72
  ```bash
73
73
  # Verify a task
74
- npx moltblock@0.7.8 "implement a function that validates email addresses" --json
74
+ npx moltblock@0.8.0 "implement a function that validates email addresses" --json
75
75
 
76
76
  # Verify code with tests
77
- npx moltblock@0.7.8 "implement a markdown-to-html converter" --test ./tests/markdown.test.ts --json
77
+ npx moltblock@0.8.0 "implement a markdown-to-html converter" --test ./tests/markdown.test.ts --json
78
78
  ```
79
79
 
80
80
  ### Output (JSON mode)
@@ -95,13 +95,13 @@ npx moltblock@0.7.8 "implement a markdown-to-html converter" --test ./tests/mark
95
95
  Use directly with npx (recommended, no install needed):
96
96
 
97
97
  ```bash
98
- npx moltblock@0.7.8 "your task" --json
98
+ npx moltblock@0.8.0 "your task" --json
99
99
  ```
100
100
 
101
101
  Or install globally:
102
102
 
103
103
  ```bash
104
- npm install -g moltblock@0.7.8
104
+ npm install -g moltblock@0.8.0
105
105
  ```
106
106
 
107
107
  ## Configuration
@@ -128,7 +128,7 @@ See the [full configuration docs](https://github.com/moltblock/moltblock#configu
128
128
 
129
129
  - Repository: [github.com/moltblock/moltblock](https://github.com/moltblock/moltblock)
130
130
  - npm: [npmjs.com/package/moltblock](https://www.npmjs.com/package/moltblock)
131
- - License: Apache-2.0
131
+ - License: MIT
132
132
 
133
133
  ## Disclaimer
134
134