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.
- package/README.md +166 -10
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/init.js +184 -0
- package/src/commands/lowcode.js +320 -0
- package/src/commands/plugin.js +55 -2
- package/src/commands/sandbox.js +366 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +44 -0
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -0
- package/src/lib/evolution-system.js +508 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -0
- package/src/lib/plugin-manager.js +118 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/skill-loader.js +274 -0
- package/src/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- 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
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -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
|
-
*
|
|
193
|
+
* Shared multi-layer skill loader
|
|
193
194
|
*/
|
|
194
|
-
const
|
|
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
|
|
398
|
-
if (
|
|
330
|
+
const allSkills = skillLoader.getResolvedSkills();
|
|
331
|
+
if (allSkills.length === 0) {
|
|
399
332
|
return {
|
|
400
333
|
error:
|
|
401
|
-
"
|
|
334
|
+
"No skills found. Make sure you're in the ChainlessChain project root or have skills installed.",
|
|
402
335
|
};
|
|
403
336
|
}
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
459
|
-
if (
|
|
460
|
-
return { error: "
|
|
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
|
|
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();
|