chainlesschain 0.37.10 → 0.37.12

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.
Files changed (39) hide show
  1. package/README.md +166 -10
  2. package/package.json +1 -1
  3. package/src/commands/a2a.js +374 -0
  4. package/src/commands/bi.js +240 -0
  5. package/src/commands/cowork.js +317 -0
  6. package/src/commands/economy.js +375 -0
  7. package/src/commands/evolution.js +398 -0
  8. package/src/commands/hmemory.js +273 -0
  9. package/src/commands/hook.js +260 -0
  10. package/src/commands/init.js +184 -0
  11. package/src/commands/lowcode.js +320 -0
  12. package/src/commands/plugin.js +55 -2
  13. package/src/commands/sandbox.js +366 -0
  14. package/src/commands/skill.js +254 -201
  15. package/src/commands/workflow.js +359 -0
  16. package/src/commands/zkp.js +277 -0
  17. package/src/index.js +44 -0
  18. package/src/lib/a2a-protocol.js +371 -0
  19. package/src/lib/agent-coordinator.js +273 -0
  20. package/src/lib/agent-economy.js +369 -0
  21. package/src/lib/app-builder.js +377 -0
  22. package/src/lib/bi-engine.js +299 -0
  23. package/src/lib/cowork/ab-comparator-cli.js +180 -0
  24. package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
  25. package/src/lib/cowork/debate-review-cli.js +144 -0
  26. package/src/lib/cowork/decision-kb-cli.js +153 -0
  27. package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
  28. package/src/lib/cowork-adapter.js +106 -0
  29. package/src/lib/evolution-system.js +508 -0
  30. package/src/lib/hierarchical-memory.js +471 -0
  31. package/src/lib/hook-manager.js +387 -0
  32. package/src/lib/plugin-manager.js +118 -0
  33. package/src/lib/project-detector.js +53 -0
  34. package/src/lib/sandbox-v2.js +503 -0
  35. package/src/lib/service-container.js +183 -0
  36. package/src/lib/skill-loader.js +274 -0
  37. package/src/lib/workflow-engine.js +503 -0
  38. package/src/lib/zkp-engine.js +241 -0
  39. package/src/repl/agent-repl.js +117 -112
@@ -0,0 +1,241 @@
1
+ /**
2
+ * ZKP Engine — Zero-knowledge proof circuit compilation, proof generation,
3
+ * verification, and selective identity disclosure.
4
+ */
5
+
6
+ import crypto from "crypto";
7
+
8
+ /* ── In-memory stores ──────────────────────────────────────── */
9
+ const _circuits = new Map();
10
+ const _proofs = new Map();
11
+ const _verificationKeys = new Map();
12
+
13
+ const DEFAULT_SCHEME = "groth16";
14
+
15
+ /* ── Schema ────────────────────────────────────────────────── */
16
+
17
+ export function ensureZKPTables(db) {
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS zkp_circuits (
20
+ id TEXT PRIMARY KEY,
21
+ name TEXT NOT NULL,
22
+ definition TEXT,
23
+ compiled TEXT,
24
+ verification_key TEXT,
25
+ created_at TEXT DEFAULT (datetime('now'))
26
+ )
27
+ `);
28
+ db.exec(`
29
+ CREATE TABLE IF NOT EXISTS zkp_proofs (
30
+ id TEXT PRIMARY KEY,
31
+ circuit_id TEXT NOT NULL,
32
+ proof TEXT,
33
+ public_inputs TEXT,
34
+ verified INTEGER DEFAULT 0,
35
+ scheme TEXT DEFAULT 'groth16',
36
+ created_at TEXT DEFAULT (datetime('now'))
37
+ )
38
+ `);
39
+ }
40
+
41
+ /* ── Circuit Compilation ───────────────────────────────────── */
42
+
43
+ export function compileCircuit(db, name, definition) {
44
+ const id = crypto.randomUUID();
45
+ const now = new Date().toISOString();
46
+
47
+ // Parse definition to extract constraints and I/O
48
+ let parsed;
49
+ if (typeof definition === "string") {
50
+ try {
51
+ parsed = JSON.parse(definition);
52
+ } catch (_err) {
53
+ parsed = { constraints: [], inputs: [], outputs: [] };
54
+ }
55
+ } else {
56
+ parsed = definition || {};
57
+ }
58
+
59
+ const constraints = parsed.constraints || [];
60
+ const inputs = parsed.inputs || [];
61
+ const outputs = parsed.outputs || [];
62
+
63
+ // Generate verification key
64
+ const vkData = `${id}:${name}:${now}`;
65
+ const verificationKey = crypto
66
+ .createHash("sha256")
67
+ .update(vkData)
68
+ .digest("hex");
69
+
70
+ const compiled = {
71
+ bytecode: crypto.randomBytes(32).toString("hex"),
72
+ constraintCount: constraints.length || 1,
73
+ compiledAt: now,
74
+ };
75
+
76
+ const circuit = {
77
+ id,
78
+ name,
79
+ definition: parsed,
80
+ compiled,
81
+ constraints: constraints.length || 1,
82
+ inputs,
83
+ outputs,
84
+ verificationKey,
85
+ createdAt: now,
86
+ };
87
+
88
+ _circuits.set(id, circuit);
89
+ _verificationKeys.set(id, verificationKey);
90
+
91
+ db.prepare(
92
+ `INSERT INTO zkp_circuits (id, name, definition, compiled, verification_key, created_at)
93
+ VALUES (?, ?, ?, ?, ?, ?)`,
94
+ ).run(
95
+ id,
96
+ name,
97
+ JSON.stringify(parsed),
98
+ JSON.stringify(compiled),
99
+ verificationKey,
100
+ now,
101
+ );
102
+
103
+ return circuit;
104
+ }
105
+
106
+ /* ── Proof Generation ──────────────────────────────────────── */
107
+
108
+ export function generateProof(db, circuitId, privateInputs, publicInputs) {
109
+ const circuit = _circuits.get(circuitId);
110
+ if (!circuit) throw new Error(`Circuit not found: ${circuitId}`);
111
+
112
+ const id = crypto.randomUUID();
113
+ const now = new Date().toISOString();
114
+
115
+ // Mock zk-SNARK proof with {a, b, c} components (Groth16 structure)
116
+ const proofData = {
117
+ a: crypto.randomBytes(32).toString("hex"),
118
+ b: crypto.randomBytes(64).toString("hex"),
119
+ c: crypto.randomBytes(32).toString("hex"),
120
+ };
121
+
122
+ const proof = {
123
+ id,
124
+ circuitId,
125
+ scheme: DEFAULT_SCHEME,
126
+ proof: proofData,
127
+ publicInputs: publicInputs || [],
128
+ verified: false,
129
+ createdAt: now,
130
+ };
131
+
132
+ _proofs.set(id, proof);
133
+
134
+ db.prepare(
135
+ `INSERT INTO zkp_proofs (id, circuit_id, proof, public_inputs, verified, scheme, created_at)
136
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
137
+ ).run(
138
+ id,
139
+ circuitId,
140
+ JSON.stringify(proofData),
141
+ JSON.stringify(publicInputs || []),
142
+ 0,
143
+ DEFAULT_SCHEME,
144
+ now,
145
+ );
146
+
147
+ return proof;
148
+ }
149
+
150
+ /* ── Proof Verification ────────────────────────────────────── */
151
+
152
+ export function verifyProof(db, proofId) {
153
+ const proof = _proofs.get(proofId);
154
+ if (!proof) throw new Error(`Proof not found: ${proofId}`);
155
+
156
+ const circuit = _circuits.get(proof.circuitId);
157
+ if (!circuit) throw new Error(`Circuit not found for proof: ${proofId}`);
158
+
159
+ // Mock verification — check proof structure is valid
160
+ const valid =
161
+ proof.proof &&
162
+ typeof proof.proof.a === "string" &&
163
+ typeof proof.proof.b === "string" &&
164
+ typeof proof.proof.c === "string" &&
165
+ _verificationKeys.has(proof.circuitId);
166
+
167
+ proof.verified = valid;
168
+
169
+ db.prepare(`UPDATE zkp_proofs SET verified = ? WHERE id = ?`).run(
170
+ valid ? 1 : 0,
171
+ proofId,
172
+ );
173
+
174
+ return { valid, proofId, circuitId: proof.circuitId, scheme: proof.scheme };
175
+ }
176
+
177
+ /* ── Identity Proof (Selective Disclosure) ─────────────────── */
178
+
179
+ export function createIdentityProof(claims, disclosedFields) {
180
+ if (!claims || typeof claims !== "object") {
181
+ throw new Error("Claims must be an object");
182
+ }
183
+
184
+ const allFields = Object.keys(claims);
185
+ const disclosed = {};
186
+ const hiddenFields = [];
187
+
188
+ for (const field of allFields) {
189
+ if (disclosedFields && disclosedFields.includes(field)) {
190
+ disclosed[field] = claims[field];
191
+ } else {
192
+ hiddenFields.push(field);
193
+ }
194
+ }
195
+
196
+ const commitment = crypto
197
+ .createHash("sha256")
198
+ .update(JSON.stringify(claims))
199
+ .digest("hex");
200
+
201
+ return {
202
+ id: crypto.randomUUID(),
203
+ type: "selective-disclosure",
204
+ disclosed,
205
+ hiddenCount: hiddenFields.length,
206
+ commitment,
207
+ };
208
+ }
209
+
210
+ /* ── Stats ─────────────────────────────────────────────────── */
211
+
212
+ export function getZKPStats() {
213
+ const proofList = [..._proofs.values()];
214
+ return {
215
+ circuits: _circuits.size,
216
+ proofs: proofList.length,
217
+ verifiedProofs: proofList.filter((p) => p.verified).length,
218
+ };
219
+ }
220
+
221
+ /* ── Listing ───────────────────────────────────────────────── */
222
+
223
+ export function listCircuits(db) {
224
+ return [..._circuits.values()];
225
+ }
226
+
227
+ export function listProofs(db, options) {
228
+ let proofs = [..._proofs.values()];
229
+ if (options && options.circuitId) {
230
+ proofs = proofs.filter((p) => p.circuitId === options.circuitId);
231
+ }
232
+ return proofs;
233
+ }
234
+
235
+ /* ── Reset (for testing) ───────────────────────────────────── */
236
+
237
+ export function _resetState() {
238
+ _circuits.clear();
239
+ _proofs.clear();
240
+ _verificationKeys.clear();
241
+ }
@@ -25,6 +25,7 @@ import { execSync } from "child_process";
25
25
  import { fileURLToPath } from "url";
26
26
  import { logger } from "../lib/logger.js";
27
27
  import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
28
+ import { CLISkillLoader } from "../lib/skill-loader.js";
28
29
 
29
30
  /**
30
31
  * Tool definitions for function calling
@@ -189,77 +190,9 @@ const TOOLS = [
189
190
  ];
190
191
 
191
192
  /**
192
- * Find and load bundled skills (shared with skill command)
193
+ * Shared multi-layer skill loader
193
194
  */
194
- const __agentDirname = path.dirname(fileURLToPath(import.meta.url));
195
-
196
- function findSkillsDir() {
197
- const candidates = [
198
- path.resolve(
199
- __agentDirname,
200
- "../../../../../desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
201
- ),
202
- path.resolve(
203
- process.cwd(),
204
- "desktop-app-vue/src/main/ai-engine/cowork/skills/builtin",
205
- ),
206
- ];
207
- for (const dir of candidates) {
208
- if (fs.existsSync(dir)) return dir;
209
- }
210
- return null;
211
- }
212
-
213
- function loadSkillList(skillsDir) {
214
- const skills = [];
215
- try {
216
- const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });
217
- for (const dir of dirs) {
218
- if (!dir.isDirectory()) continue;
219
- const skillMd = path.join(skillsDir, dir.name, "SKILL.md");
220
- if (!fs.existsSync(skillMd)) continue;
221
- try {
222
- const content = fs.readFileSync(skillMd, "utf8");
223
- const lines = content.split("\n");
224
- if (lines[0].trim() !== "---") continue;
225
- let endIndex = -1;
226
- for (let i = 1; i < lines.length; i++) {
227
- if (lines[i].trim() === "---") {
228
- endIndex = i;
229
- break;
230
- }
231
- }
232
- if (endIndex === -1) continue;
233
- const data = {};
234
- for (const line of lines.slice(1, endIndex)) {
235
- const ci = line.indexOf(":");
236
- if (ci > 0) {
237
- const key = line.slice(0, ci).trim();
238
- const val = line
239
- .slice(ci + 1)
240
- .trim()
241
- .replace(/^['"]|['"]$/g, "");
242
- data[key] = val;
243
- }
244
- }
245
- skills.push({
246
- id: data.name || dir.name,
247
- description: data.description || "",
248
- category: data.category || "uncategorized",
249
- dirName: dir.name,
250
- hasHandler: fs.existsSync(
251
- path.join(skillsDir, dir.name, "handler.js"),
252
- ),
253
- });
254
- } catch {
255
- // Skip malformed skill files
256
- }
257
- }
258
- } catch {
259
- // Skills dir unreadable
260
- }
261
- return skills;
262
- }
195
+ const skillLoader = new CLISkillLoader();
263
196
 
264
197
  /**
265
198
  * Execute a tool call (with plan mode filtering)
@@ -394,50 +327,28 @@ async function executeTool(name, args) {
394
327
  }
395
328
 
396
329
  case "run_skill": {
397
- const skillsDir = findSkillsDir();
398
- if (!skillsDir) {
330
+ const allSkills = skillLoader.getResolvedSkills();
331
+ if (allSkills.length === 0) {
399
332
  return {
400
333
  error:
401
- "Skills directory not found. Make sure you're in the ChainlessChain project root.",
334
+ "No skills found. Make sure you're in the ChainlessChain project root or have skills installed.",
402
335
  };
403
336
  }
404
- const handlerPath = path.join(skillsDir, args.skill_name, "handler.js");
405
- if (!fs.existsSync(handlerPath)) {
406
- // Try fuzzy match
407
- const skills = loadSkillList(skillsDir);
408
- const match = skills.find(
409
- (s) => s.id === args.skill_name || s.dirName === args.skill_name,
410
- );
411
- if (match && match.hasHandler) {
412
- const matchedPath = path.join(skillsDir, match.dirName, "handler.js");
413
- try {
414
- const handler = (
415
- await import(`file://${matchedPath.replace(/\\/g, "/")}`)
416
- ).default;
417
- const task = {
418
- params: { input: args.input },
419
- input: args.input,
420
- action: args.input,
421
- };
422
- const context = {
423
- projectRoot: process.cwd(),
424
- workspacePath: process.cwd(),
425
- };
426
- const result = await handler.execute(task, context);
427
- return result;
428
- } catch (err) {
429
- return { error: `Skill execution failed: ${err.message}` };
430
- }
431
- }
337
+ const match = allSkills.find(
338
+ (s) => s.id === args.skill_name || s.dirName === args.skill_name,
339
+ );
340
+ if (!match || !match.hasHandler) {
432
341
  return {
433
342
  error: `Skill "${args.skill_name}" not found or has no handler. Use list_skills to see available skills.`,
434
343
  };
435
344
  }
436
345
  try {
437
- const handler = (
438
- await import(`file://${handlerPath.replace(/\\/g, "/")}`)
439
- ).default;
440
- if (handler.init) await handler.init({});
346
+ const handlerPath = path.join(match.skillDir, "handler.js");
347
+ const imported = await import(
348
+ `file://${handlerPath.replace(/\\/g, "/")}`
349
+ );
350
+ const handler = imported.default || imported;
351
+ if (handler.init) await handler.init(match);
441
352
  const task = {
442
353
  params: { input: args.input },
443
354
  input: args.input,
@@ -447,7 +358,7 @@ async function executeTool(name, args) {
447
358
  projectRoot: process.cwd(),
448
359
  workspacePath: process.cwd(),
449
360
  };
450
- const result = await handler.execute(task, context);
361
+ const result = await handler.execute(task, context, match);
451
362
  return result;
452
363
  } catch (err) {
453
364
  return { error: `Skill execution failed: ${err.message}` };
@@ -455,11 +366,10 @@ async function executeTool(name, args) {
455
366
  }
456
367
 
457
368
  case "list_skills": {
458
- const skillsDir = findSkillsDir();
459
- if (!skillsDir) {
460
- return { error: "Skills directory not found." };
369
+ let skills = skillLoader.getResolvedSkills();
370
+ if (skills.length === 0) {
371
+ return { error: "No skills found." };
461
372
  }
462
- let skills = loadSkillList(skillsDir);
463
373
  if (args.category) {
464
374
  skills = skills.filter(
465
375
  (s) => s.category.toLowerCase() === args.category.toLowerCase(),
@@ -479,8 +389,9 @@ async function executeTool(name, args) {
479
389
  skills: skills.map((s) => ({
480
390
  id: s.id,
481
391
  category: s.category,
392
+ source: s.source,
482
393
  hasHandler: s.hasHandler,
483
- description: s.description.substring(0, 80),
394
+ description: (s.description || "").substring(0, 80),
484
395
  })),
485
396
  };
486
397
  }
@@ -654,7 +565,7 @@ Key behaviors:
654
565
  - When asked to create something, use write_file to create it
655
566
  - When asked to run/test something, use run_shell to execute it
656
567
  - When asked about files or code, use read_file and search_files to find information
657
- - You have 138 built-in skills (code-review, summarize, translate, refactor, etc.) — use list_skills to discover them and run_skill to execute them
568
+ - You have multi-layer skills (built-in, marketplace, global, project-level) — use list_skills to discover them and run_skill to execute them
658
569
  - Always explain what you're doing and show results
659
570
  - Be concise but thorough
660
571
 
@@ -730,6 +641,9 @@ export async function startAgentRepl(options = {}) {
730
641
  logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
731
642
  logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
732
643
  logger.log(` ${chalk.cyan("/compact")} Keep only last 4 messages`);
644
+ logger.log(
645
+ ` ${chalk.cyan("/cowork")} Multi-agent collaboration (debate, compare)`,
646
+ );
733
647
  logger.log(
734
648
  ` ${chalk.cyan("/plan")} Enter plan mode (read-only analysis first)`,
735
649
  );
@@ -792,6 +706,97 @@ export async function startAgentRepl(options = {}) {
792
706
  return;
793
707
  }
794
708
 
709
+ // Cowork commands
710
+ if (trimmed.startsWith("/cowork")) {
711
+ const coworkArgs = trimmed.slice(7).trim();
712
+ const [subCmd, ...rest] = coworkArgs.split(/\s+/);
713
+ const coworkInput = rest.join(" ");
714
+
715
+ if (!subCmd || subCmd === "help") {
716
+ logger.log(chalk.bold("\nCowork Commands:"));
717
+ logger.log(
718
+ ` ${chalk.cyan("/cowork debate <file>")} Multi-perspective code review`,
719
+ );
720
+ logger.log(
721
+ ` ${chalk.cyan("/cowork compare <prompt>")} A/B solution comparison`,
722
+ );
723
+ logger.log("");
724
+ } else if (subCmd === "debate" && coworkInput) {
725
+ try {
726
+ const { startDebate } =
727
+ await import("../lib/cowork/debate-review-cli.js");
728
+ let code = coworkInput;
729
+ let targetLabel = coworkInput;
730
+ const resolved = path.resolve(coworkInput);
731
+ if (fs.existsSync(resolved)) {
732
+ code = fs.readFileSync(resolved, "utf-8");
733
+ targetLabel = resolved;
734
+ if (code.length > 15000) {
735
+ code = code.substring(0, 15000) + "\n... (truncated)";
736
+ }
737
+ }
738
+ process.stdout.write(chalk.gray("\n Running debate review...\n"));
739
+ const result = await startDebate({
740
+ target: targetLabel,
741
+ code,
742
+ llmOptions: { provider, model, baseUrl, apiKey },
743
+ });
744
+ for (const review of result.reviews) {
745
+ const vc =
746
+ review.verdict === "APPROVE"
747
+ ? chalk.green
748
+ : review.verdict === "REJECT"
749
+ ? chalk.red
750
+ : chalk.yellow;
751
+ process.stdout.write(
752
+ ` ${chalk.bold(review.role)}: ${vc(review.verdict)}\n`,
753
+ );
754
+ }
755
+ process.stdout.write(
756
+ `\n ${chalk.bold("Verdict:")} ${result.verdict} Consensus: ${result.consensusScore}%\n\n`,
757
+ );
758
+ // Add summary to conversation for context
759
+ messages.push({
760
+ role: "assistant",
761
+ content: `[Cowork Debate Result] ${result.verdict} (consensus: ${result.consensusScore}%)\n${result.summary.substring(0, 500)}`,
762
+ });
763
+ } catch (err) {
764
+ logger.error(`Debate failed: ${err.message}`);
765
+ }
766
+ } else if (subCmd === "compare" && coworkInput) {
767
+ try {
768
+ const { compare } =
769
+ await import("../lib/cowork/ab-comparator-cli.js");
770
+ process.stdout.write(chalk.gray("\n Generating variants...\n"));
771
+ const result = await compare({
772
+ prompt: coworkInput,
773
+ llmOptions: { provider, model, baseUrl, apiKey },
774
+ });
775
+ for (const v of result.variants) {
776
+ process.stdout.write(
777
+ ` ${chalk.cyan(v.name)}: score ${v.totalScore}\n`,
778
+ );
779
+ }
780
+ process.stdout.write(
781
+ `\n ${chalk.bold("Winner:")} ${chalk.green(result.winner)}\n\n`,
782
+ );
783
+ messages.push({
784
+ role: "assistant",
785
+ content: `[Cowork Compare Result] Winner: ${result.winner}. ${result.reason}`,
786
+ });
787
+ } catch (err) {
788
+ logger.error(`Compare failed: ${err.message}`);
789
+ }
790
+ } else {
791
+ logger.info(
792
+ "Usage: /cowork debate <file-or-topic> or /cowork compare <prompt>",
793
+ );
794
+ }
795
+
796
+ prompt();
797
+ return;
798
+ }
799
+
795
800
  // Plan mode commands
796
801
  if (trimmed.startsWith("/plan")) {
797
802
  const planManager = getPlanModeManager();