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,371 @@
1
+ /**
2
+ * A2A (Agent-to-Agent) Protocol for CLI
3
+ *
4
+ * Implements Google A2A protocol concepts: agent cards, task lifecycle,
5
+ * capability negotiation, and peer discovery.
6
+ */
7
+
8
+ // ─── Task statuses ───────────────────────────────────────────────
9
+ export const TASK_STATUS = {
10
+ SUBMITTED: "submitted",
11
+ WORKING: "working",
12
+ COMPLETED: "completed",
13
+ FAILED: "failed",
14
+ INPUT_REQUIRED: "input-required",
15
+ };
16
+
17
+ // ─── In-memory task subscriptions ────────────────────────────────
18
+ // Map<taskId, Set<callback>>
19
+ const _subscriptions = new Map();
20
+
21
+ // ─── Helpers ─────────────────────────────────────────────────────
22
+ function generateId(prefix = "a2a") {
23
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
24
+ }
25
+
26
+ function nowISO() {
27
+ return new Date().toISOString();
28
+ }
29
+
30
+ // ─── Table setup ─────────────────────────────────────────────────
31
+
32
+ /**
33
+ * Create A2A protocol tables
34
+ */
35
+ export function ensureA2ATables(db) {
36
+ db.exec(`
37
+ CREATE TABLE IF NOT EXISTS a2a_agent_cards (
38
+ id TEXT PRIMARY KEY,
39
+ name TEXT NOT NULL,
40
+ description TEXT DEFAULT '',
41
+ url TEXT DEFAULT '',
42
+ capabilities TEXT DEFAULT '[]',
43
+ skills TEXT DEFAULT '[]',
44
+ auth_type TEXT DEFAULT 'none',
45
+ status TEXT DEFAULT 'active',
46
+ created_at TEXT DEFAULT (datetime('now')),
47
+ updated_at TEXT DEFAULT (datetime('now'))
48
+ )
49
+ `);
50
+
51
+ db.exec(`
52
+ CREATE TABLE IF NOT EXISTS a2a_tasks (
53
+ id TEXT PRIMARY KEY,
54
+ agent_id TEXT NOT NULL,
55
+ status TEXT DEFAULT 'submitted',
56
+ input TEXT DEFAULT '',
57
+ output TEXT DEFAULT '',
58
+ artifacts TEXT DEFAULT '[]',
59
+ error TEXT DEFAULT '',
60
+ history TEXT DEFAULT '[]',
61
+ created_at TEXT DEFAULT (datetime('now')),
62
+ updated_at TEXT DEFAULT (datetime('now'))
63
+ )
64
+ `);
65
+ }
66
+
67
+ // ─── Agent Cards ─────────────────────────────────────────────────
68
+
69
+ /**
70
+ * Register an agent card
71
+ */
72
+ export function registerCard(db, card) {
73
+ ensureA2ATables(db);
74
+
75
+ if (!card || !card.name) {
76
+ throw new Error("Agent card must have a name");
77
+ }
78
+
79
+ const id = generateId("agent");
80
+ const now = nowISO();
81
+ const capabilities = JSON.stringify(card.capabilities || []);
82
+ const skills = JSON.stringify(card.skills || []);
83
+
84
+ db.prepare(
85
+ `INSERT INTO a2a_agent_cards (id, name, description, url, capabilities, skills, auth_type, created_at, updated_at)
86
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
87
+ ).run(
88
+ id,
89
+ card.name,
90
+ card.description || "",
91
+ card.url || "",
92
+ capabilities,
93
+ skills,
94
+ card.auth_type || "none",
95
+ now,
96
+ now,
97
+ );
98
+
99
+ return { id, name: card.name, status: "active" };
100
+ }
101
+
102
+ /**
103
+ * Update an existing agent card
104
+ */
105
+ export function updateCard(db, id, updates) {
106
+ ensureA2ATables(db);
107
+
108
+ if (!id) throw new Error("Card ID is required");
109
+
110
+ const fields = [];
111
+ const values = [];
112
+
113
+ if (updates.name !== undefined) {
114
+ fields.push("name = ?");
115
+ values.push(updates.name);
116
+ }
117
+ if (updates.description !== undefined) {
118
+ fields.push("description = ?");
119
+ values.push(updates.description);
120
+ }
121
+ if (updates.url !== undefined) {
122
+ fields.push("url = ?");
123
+ values.push(updates.url);
124
+ }
125
+ if (updates.capabilities !== undefined) {
126
+ fields.push("capabilities = ?");
127
+ values.push(JSON.stringify(updates.capabilities));
128
+ }
129
+ if (updates.skills !== undefined) {
130
+ fields.push("skills = ?");
131
+ values.push(JSON.stringify(updates.skills));
132
+ }
133
+ if (updates.auth_type !== undefined) {
134
+ fields.push("auth_type = ?");
135
+ values.push(updates.auth_type);
136
+ }
137
+ if (updates.status !== undefined) {
138
+ fields.push("status = ?");
139
+ values.push(updates.status);
140
+ }
141
+
142
+ if (fields.length === 0) return { id, updated: false };
143
+
144
+ fields.push("updated_at = ?");
145
+ values.push(nowISO());
146
+ values.push(id);
147
+
148
+ const result = db
149
+ .prepare(`UPDATE a2a_agent_cards SET ${fields.join(", ")} WHERE id = ?`)
150
+ .run(...values);
151
+
152
+ return { id, updated: result.changes > 0 };
153
+ }
154
+
155
+ /**
156
+ * Discover agents matching a filter
157
+ */
158
+ export function discoverAgents(db, filter = {}) {
159
+ ensureA2ATables(db);
160
+
161
+ let rows = db
162
+ .prepare(`SELECT * FROM a2a_agent_cards WHERE status = 'active'`)
163
+ .all();
164
+
165
+ // Parse JSON fields
166
+ rows = rows.map((r) => ({
167
+ ...r,
168
+ capabilities: JSON.parse(r.capabilities || "[]"),
169
+ skills: JSON.parse(r.skills || "[]"),
170
+ }));
171
+
172
+ if (filter.capability) {
173
+ rows = rows.filter((r) => r.capabilities.includes(filter.capability));
174
+ }
175
+ if (filter.skill) {
176
+ rows = rows.filter((r) => r.skills.includes(filter.skill));
177
+ }
178
+ if (filter.name) {
179
+ const pattern = filter.name.toLowerCase();
180
+ rows = rows.filter((r) => r.name.toLowerCase().includes(pattern));
181
+ }
182
+
183
+ return rows;
184
+ }
185
+
186
+ // ─── Task Lifecycle ──────────────────────────────────────────────
187
+
188
+ /**
189
+ * Send a task to an agent (creates with status: submitted)
190
+ */
191
+ export function sendTask(db, agentId, input) {
192
+ ensureA2ATables(db);
193
+
194
+ if (!agentId) throw new Error("Agent ID is required");
195
+ if (!input) throw new Error("Task input is required");
196
+
197
+ const taskId = generateId("task");
198
+ const now = nowISO();
199
+ const history = JSON.stringify([
200
+ { status: TASK_STATUS.SUBMITTED, timestamp: now },
201
+ ]);
202
+
203
+ db.prepare(
204
+ `INSERT INTO a2a_tasks (id, agent_id, status, input, history, created_at, updated_at)
205
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
206
+ ).run(taskId, agentId, TASK_STATUS.SUBMITTED, input, history, now, now);
207
+
208
+ _notifySubscribers(taskId, TASK_STATUS.SUBMITTED);
209
+
210
+ return { taskId, status: TASK_STATUS.SUBMITTED };
211
+ }
212
+
213
+ /**
214
+ * Mark a task as completed
215
+ */
216
+ export function completeTask(db, taskId, output, artifacts = []) {
217
+ ensureA2ATables(db);
218
+
219
+ if (!taskId) throw new Error("Task ID is required");
220
+
221
+ const now = nowISO();
222
+ const task = _getTask(db, taskId);
223
+ const history = JSON.parse(task.history || "[]");
224
+ history.push({ status: TASK_STATUS.COMPLETED, timestamp: now });
225
+
226
+ db.prepare(
227
+ `UPDATE a2a_tasks SET status = ?, output = ?, artifacts = ?, history = ?, updated_at = ? WHERE id = ?`,
228
+ ).run(
229
+ TASK_STATUS.COMPLETED,
230
+ output || "",
231
+ JSON.stringify(artifacts),
232
+ JSON.stringify(history),
233
+ now,
234
+ taskId,
235
+ );
236
+
237
+ _notifySubscribers(taskId, TASK_STATUS.COMPLETED);
238
+
239
+ return { taskId, status: TASK_STATUS.COMPLETED };
240
+ }
241
+
242
+ /**
243
+ * Mark a task as failed
244
+ */
245
+ export function failTask(db, taskId, error) {
246
+ ensureA2ATables(db);
247
+
248
+ if (!taskId) throw new Error("Task ID is required");
249
+
250
+ const now = nowISO();
251
+ const task = _getTask(db, taskId);
252
+ const history = JSON.parse(task.history || "[]");
253
+ history.push({ status: TASK_STATUS.FAILED, timestamp: now });
254
+
255
+ db.prepare(
256
+ `UPDATE a2a_tasks SET status = ?, error = ?, history = ?, updated_at = ? WHERE id = ?`,
257
+ ).run(
258
+ TASK_STATUS.FAILED,
259
+ error || "Unknown error",
260
+ JSON.stringify(history),
261
+ now,
262
+ taskId,
263
+ );
264
+
265
+ _notifySubscribers(taskId, TASK_STATUS.FAILED);
266
+
267
+ return { taskId, status: TASK_STATUS.FAILED };
268
+ }
269
+
270
+ /**
271
+ * Get task status with full history
272
+ */
273
+ export function getTaskStatus(db, taskId) {
274
+ ensureA2ATables(db);
275
+
276
+ const task = _getTask(db, taskId);
277
+ return {
278
+ ...task,
279
+ history: JSON.parse(task.history || "[]"),
280
+ artifacts: JSON.parse(task.artifacts || "[]"),
281
+ };
282
+ }
283
+
284
+ function _getTask(db, taskId) {
285
+ const task = db.prepare(`SELECT * FROM a2a_tasks WHERE id = ?`).get(taskId);
286
+ if (!task) throw new Error(`Task not found: ${taskId}`);
287
+ return task;
288
+ }
289
+
290
+ // ─── Capability Negotiation ──────────────────────────────────────
291
+
292
+ /**
293
+ * Check if an agent supports the required capabilities
294
+ */
295
+ export function negotiateCapability(db, agentId, requiredCapabilities) {
296
+ ensureA2ATables(db);
297
+
298
+ if (!agentId) throw new Error("Agent ID is required");
299
+ if (!Array.isArray(requiredCapabilities)) {
300
+ throw new Error("requiredCapabilities must be an array");
301
+ }
302
+
303
+ const card = db
304
+ .prepare(`SELECT * FROM a2a_agent_cards WHERE id = ?`)
305
+ .get(agentId);
306
+ if (!card) throw new Error(`Agent not found: ${agentId}`);
307
+
308
+ const agentCaps = JSON.parse(card.capabilities || "[]");
309
+ const supported = requiredCapabilities.filter((c) => agentCaps.includes(c));
310
+ const missing = requiredCapabilities.filter((c) => !agentCaps.includes(c));
311
+
312
+ return {
313
+ compatible: missing.length === 0,
314
+ supported,
315
+ missing,
316
+ };
317
+ }
318
+
319
+ // ─── Peers ───────────────────────────────────────────────────────
320
+
321
+ /**
322
+ * List all registered agents
323
+ */
324
+ export function listPeers(db) {
325
+ ensureA2ATables(db);
326
+
327
+ const rows = db
328
+ .prepare(`SELECT * FROM a2a_agent_cards ORDER BY created_at DESC`)
329
+ .all();
330
+ return rows.map((r) => ({
331
+ ...r,
332
+ capabilities: JSON.parse(r.capabilities || "[]"),
333
+ skills: JSON.parse(r.skills || "[]"),
334
+ }));
335
+ }
336
+
337
+ // ─── Subscriptions ───────────────────────────────────────────────
338
+
339
+ /**
340
+ * Subscribe to task status changes (in-memory)
341
+ */
342
+ export function subscribeToTask(taskId, callback) {
343
+ if (!_subscriptions.has(taskId)) {
344
+ _subscriptions.set(taskId, new Set());
345
+ }
346
+ _subscriptions.get(taskId).add(callback);
347
+
348
+ return () => {
349
+ const subs = _subscriptions.get(taskId);
350
+ if (subs) {
351
+ subs.delete(callback);
352
+ if (subs.size === 0) _subscriptions.delete(taskId);
353
+ }
354
+ };
355
+ }
356
+
357
+ function _notifySubscribers(taskId, status) {
358
+ const subs = _subscriptions.get(taskId);
359
+ if (subs) {
360
+ for (const cb of subs) {
361
+ try {
362
+ cb(taskId, status);
363
+ } catch (_err) {
364
+ // Subscriber error should not break task lifecycle
365
+ }
366
+ }
367
+ }
368
+ }
369
+
370
+ // Export for testing
371
+ export { _subscriptions };
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Multi-Agent Coordinator — decomposes tasks, selects agents, and aggregates results.
3
+ *
4
+ * Provides task decomposition based on keyword matching, agent selection by
5
+ * capability, subtask assignment tracking, and result aggregation.
6
+ */
7
+
8
+ import crypto from "crypto";
9
+
10
+ /**
11
+ * Keyword map for agent type detection.
12
+ */
13
+ export const AGENT_TYPE_KEYWORDS = {
14
+ "code-generation": [
15
+ "code",
16
+ "generate",
17
+ "implement",
18
+ "function",
19
+ "class",
20
+ "module",
21
+ "develop",
22
+ "build",
23
+ ],
24
+ "code-review": [
25
+ "review",
26
+ "audit",
27
+ "inspect",
28
+ "check",
29
+ "lint",
30
+ "quality",
31
+ "pull request",
32
+ ],
33
+ "data-analysis": [
34
+ "data",
35
+ "analyze",
36
+ "statistics",
37
+ "chart",
38
+ "graph",
39
+ "csv",
40
+ "report",
41
+ "dashboard",
42
+ "visualization",
43
+ ],
44
+ document: [
45
+ "document",
46
+ "documentation",
47
+ "readme",
48
+ "guide",
49
+ "write",
50
+ "article",
51
+ "tutorial",
52
+ "markdown",
53
+ ],
54
+ testing: [
55
+ "test",
56
+ "unit test",
57
+ "integration",
58
+ "e2e",
59
+ "jest",
60
+ "vitest",
61
+ "coverage",
62
+ "mock",
63
+ "spec",
64
+ ],
65
+ };
66
+
67
+ /**
68
+ * Generate a short unique id.
69
+ */
70
+ function generateId() {
71
+ return crypto.randomUUID().slice(0, 12);
72
+ }
73
+
74
+ /**
75
+ * Decompose a task description into subtasks based on keyword matching.
76
+ *
77
+ * @param {string} task - Task description
78
+ * @returns {{ taskId: string, subtasks: Array<{ id: string, agentType: string, description: string, status: string }> }}
79
+ */
80
+ export function decomposeTask(task) {
81
+ if (!task || typeof task !== "string") {
82
+ return { taskId: generateId(), subtasks: [] };
83
+ }
84
+
85
+ const lower = task.toLowerCase();
86
+ const subtasks = [];
87
+
88
+ for (const [agentType, keywords] of Object.entries(AGENT_TYPE_KEYWORDS)) {
89
+ const matched = keywords.filter((kw) => lower.includes(kw));
90
+ if (matched.length > 0) {
91
+ subtasks.push({
92
+ id: generateId(),
93
+ agentType,
94
+ description: `${agentType}: ${matched.join(", ")} — from "${task.substring(0, 80)}"`,
95
+ status: "pending",
96
+ });
97
+ }
98
+ }
99
+
100
+ // If nothing matched, create a generic subtask
101
+ if (subtasks.length === 0) {
102
+ subtasks.push({
103
+ id: generateId(),
104
+ agentType: "general",
105
+ description: task.substring(0, 200),
106
+ status: "pending",
107
+ });
108
+ }
109
+
110
+ return {
111
+ taskId: generateId(),
112
+ subtasks,
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Select the best matching agent for a subtask from available agents.
118
+ *
119
+ * @param {{ agentType: string }} subtask
120
+ * @param {Array<{ id: string, capabilities: string[] }>} availableAgents
121
+ * @returns {{ id: string, capabilities: string[] } | null}
122
+ */
123
+ export function selectAgent(subtask, availableAgents) {
124
+ if (
125
+ !subtask ||
126
+ !Array.isArray(availableAgents) ||
127
+ availableAgents.length === 0
128
+ ) {
129
+ return null;
130
+ }
131
+
132
+ // Direct capability match
133
+ for (const agent of availableAgents) {
134
+ if (
135
+ Array.isArray(agent.capabilities) &&
136
+ agent.capabilities.includes(subtask.agentType)
137
+ ) {
138
+ return agent;
139
+ }
140
+ }
141
+
142
+ // Partial match — check if any capability keyword overlaps
143
+ const keywords = AGENT_TYPE_KEYWORDS[subtask.agentType] || [];
144
+ let bestAgent = null;
145
+ let bestScore = 0;
146
+
147
+ for (const agent of availableAgents) {
148
+ if (!Array.isArray(agent.capabilities)) continue;
149
+ let score = 0;
150
+ for (const cap of agent.capabilities) {
151
+ if (keywords.some((kw) => cap.includes(kw) || kw.includes(cap))) {
152
+ score++;
153
+ }
154
+ }
155
+ if (score > bestScore) {
156
+ bestScore = score;
157
+ bestAgent = agent;
158
+ }
159
+ }
160
+
161
+ return bestAgent;
162
+ }
163
+
164
+ /**
165
+ * Assign a subtask to an agent and record in the database.
166
+ *
167
+ * @param {object} db - Database instance (better-sqlite3 API)
168
+ * @param {string} subtaskId
169
+ * @param {string} agentId
170
+ * @returns {{ subtaskId: string, agentId: string, status: string }}
171
+ */
172
+ export function assignSubtask(db, subtaskId, agentId) {
173
+ if (db) {
174
+ const stmt = db.prepare(
175
+ `INSERT OR REPLACE INTO agent_assignments (id, subtask_id, agent_id, status, assigned_at)
176
+ VALUES (?, ?, ?, ?, datetime('now'))`,
177
+ );
178
+ stmt.run(generateId(), subtaskId, agentId, "assigned");
179
+ }
180
+
181
+ return {
182
+ subtaskId,
183
+ agentId,
184
+ status: "assigned",
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Aggregate results from completed subtasks.
190
+ *
191
+ * @param {Array<{ id: string, agentType: string, status: string, result?: any }>} subtasks
192
+ * @returns {{ taskId: string, status: string, results: any[], summary: string }}
193
+ */
194
+ export function aggregateResults(subtasks) {
195
+ if (!Array.isArray(subtasks) || subtasks.length === 0) {
196
+ return {
197
+ taskId: "",
198
+ status: "empty",
199
+ results: [],
200
+ summary: "No subtasks to aggregate",
201
+ };
202
+ }
203
+
204
+ const results = subtasks.map((s) => ({
205
+ id: s.id,
206
+ agentType: s.agentType,
207
+ status: s.status,
208
+ result: s.result || null,
209
+ }));
210
+
211
+ const completed = subtasks.filter((s) => s.status === "completed").length;
212
+ const failed = subtasks.filter((s) => s.status === "failed").length;
213
+ const total = subtasks.length;
214
+
215
+ let status = "completed";
216
+ if (failed > 0 && completed === 0) status = "failed";
217
+ else if (failed > 0) status = "partial";
218
+ else if (completed < total) status = "in-progress";
219
+
220
+ return {
221
+ taskId: subtasks[0]?.taskId || generateId(),
222
+ status,
223
+ results,
224
+ summary: `${completed}/${total} subtasks completed, ${failed} failed`,
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Return the list of all supported agent types.
230
+ *
231
+ * @returns {string[]}
232
+ */
233
+ export function getAgentTypes() {
234
+ return Object.keys(AGENT_TYPE_KEYWORDS);
235
+ }
236
+
237
+ /**
238
+ * Estimate task complexity based on keyword count and description length.
239
+ *
240
+ * @param {string} task
241
+ * @returns {{ complexity: "low" | "medium" | "high", estimatedSubtasks: number }}
242
+ */
243
+ export function estimateComplexity(task) {
244
+ if (!task || typeof task !== "string") {
245
+ return { complexity: "low", estimatedSubtasks: 0 };
246
+ }
247
+
248
+ const lower = task.toLowerCase();
249
+ let matchedTypes = 0;
250
+ let totalKeywords = 0;
251
+
252
+ for (const [, keywords] of Object.entries(AGENT_TYPE_KEYWORDS)) {
253
+ const matched = keywords.filter((kw) => lower.includes(kw));
254
+ if (matched.length > 0) {
255
+ matchedTypes++;
256
+ totalKeywords += matched.length;
257
+ }
258
+ }
259
+
260
+ const lengthFactor = task.length > 200 ? 1 : task.length > 100 ? 0.5 : 0;
261
+
262
+ const score = matchedTypes + totalKeywords * 0.3 + lengthFactor;
263
+
264
+ let complexity;
265
+ if (score >= 5) complexity = "high";
266
+ else if (score >= 2) complexity = "medium";
267
+ else complexity = "low";
268
+
269
+ return {
270
+ complexity,
271
+ estimatedSubtasks: Math.max(1, matchedTypes),
272
+ };
273
+ }