chainlesschain 0.45.75 → 0.45.76

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 CHANGED
@@ -183,7 +183,7 @@ chainlesschain llm models # List installed Ollama models
183
183
  chainlesschain llm models --json # JSON output
184
184
  chainlesschain llm test # Test Ollama connectivity
185
185
  chainlesschain llm test --provider openai --api-key sk-...
186
- chainlesschain llm providers # List 8 built-in LLM providers
186
+ chainlesschain llm providers # List 10 built-in LLM providers
187
187
  chainlesschain llm add-provider <name> # Add custom provider
188
188
  chainlesschain llm switch <name> # Switch active provider
189
189
  ```
@@ -198,7 +198,7 @@ chainlesschain a --model llama3 # Short alias
198
198
  chainlesschain agent --provider openai --api-key sk-...
199
199
  ```
200
200
 
201
- Built-in tools: `read_file`, `write_file`, `edit_file`, `run_shell`, `search_files`, `list_dir`, `run_skill`, `list_skills`, `run_code`
201
+ Built-in tools (12): `read_file`, `write_file`, `edit_file`, `edit_file_hashed`, `run_shell`, `git`, `search_files`, `list_dir`, `run_skill`, `list_skills`, `run_code`, `spawn_sub_agent`
202
202
 
203
203
  Agent slash commands: `/plan` (plan mode), `/plan interactive <request>` (LLM-driven planning with skill recommendations), `/model`, `/provider`, `/clear`, `/compact`, `/task`, `/session`, `/stats`, `/auto` (autonomous agent), `/cowork` (multi-agent collaboration), `/sub-agents` (show active/completed sub-agents)
204
204
 
@@ -701,6 +701,38 @@ chainlesschain evolution learn --domain nlp # Incremental learning
701
701
  chainlesschain evolution status # Evolution status
702
702
  ```
703
703
 
704
+ ### `chainlesschain learning <action>`
705
+
706
+ Autonomous learning loop — tracks execution trajectories, auto-synthesizes skills from successful patterns, and performs periodic self-reflection.
707
+
708
+ ```bash
709
+ chainlesschain learning stats # Learning loop statistics overview
710
+ chainlesschain learning stats --json # JSON output
711
+ chainlesschain learning trajectories # Recent 20 execution trajectories
712
+ chainlesschain learning trajectories -n 50 # Specify count
713
+ chainlesschain learning trajectories --session <id> # Filter by session
714
+ chainlesschain learning trajectories --json # JSON output
715
+ chainlesschain learning reflect # Manual self-reflection trigger
716
+ chainlesschain learning reflect --json # JSON reflection report
717
+ chainlesschain learning synthesize # Scan and synthesize new skills
718
+ chainlesschain learning synthesize --json # JSON output
719
+ chainlesschain learning cleanup # Clean up trajectories older than 90 days
720
+ chainlesschain learning cleanup --days 30 # Custom retention period
721
+ chainlesschain learning cleanup --json # JSON output
722
+ ```
723
+
724
+ **Core modules** (7 files in `src/lib/learning/`):
725
+
726
+ | Module | Description |
727
+ | -------------------- | ------------------------------------------------------------------------------------ |
728
+ | **TrajectoryStore** | Records complete execution trajectories (intent → tool chain → result → response) |
729
+ | **OutcomeFeedback** | Auto-scoring + user feedback + correction detection (Chinese & English) |
730
+ | **SkillSynthesizer** | Extracts reusable patterns from complex successful trajectories into SKILL.md |
731
+ | **SkillImprover** | Three improvement triggers: error repair, user correction, better trajectory compare |
732
+ | **ReflectionEngine** | Periodic self-review with tool stats, score trends, error-prone tool identification |
733
+ | **LearningHooks** | REPL lifecycle integration — captures prompts, tool calls, responses, session events |
734
+ | **LearningTables** | SQLite schema creation for trajectories, tags, and improvement logs |
735
+
704
736
  ---
705
737
 
706
738
  ## Phase 8: Blockchain & Enterprise Analytics
@@ -1139,17 +1171,19 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
1139
1171
 
1140
1172
  ### Supported LLM Providers
1141
1173
 
1142
- | Provider | Default Model | API Key Required |
1143
- | ---------------------- | ----------------- | ---------------- |
1144
- | Ollama (Local) | qwen2.5:7b | No |
1145
- | OpenAI | gpt-4o | Yes |
1146
- | Anthropic | claude-sonnet-4-6 | Yes |
1147
- | DashScope (Alibaba) | qwen-max | Yes |
1148
- | DeepSeek | deepseek-chat | Yes |
1149
- | Gemini (Google) | gemini-pro | Yes |
1150
- | Mistral | mistral-large | Yes |
1151
- | Volcengine (ByteDance) | doubao-1.5-pro | Yes |
1152
- | Custom | | Yes |
1174
+ | Provider | Default Model | API Key Required |
1175
+ | -------------------------- | ---------------------- | ---------------- |
1176
+ | Ollama (Local) | qwen2.5:7b | No |
1177
+ | OpenAI | gpt-4o | Yes |
1178
+ | Anthropic | claude-opus-4-6 | Yes |
1179
+ | DeepSeek | deepseek-chat | Yes |
1180
+ | DashScope (Alibaba) | qwen-turbo | Yes |
1181
+ | Google Gemini | gemini-2.0-flash | Yes |
1182
+ | Mistral AI | mistral-large-latest | Yes |
1183
+ | Volcengine (火山引擎/豆包) | doubao-seed-1-6-251015 | Yes |
1184
+ | Kimi (月之暗面) | moonshot-v1-auto | Yes |
1185
+ | MiniMax (海螺AI) | MiniMax-Text-01 | Yes |
1186
+ | Custom | — | Yes |
1153
1187
 
1154
1188
  ## File Structure
1155
1189
 
@@ -1168,7 +1202,7 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
1168
1202
  ```bash
1169
1203
  cd packages/cli
1170
1204
  npm install
1171
- npm test # Run all tests (5200+ tests across 133+ files)
1205
+ npm test # Run all tests (5400+ tests across 201+ files)
1172
1206
  npm run test:unit # Unit tests only
1173
1207
  npm run test:integration # Integration tests
1174
1208
  npm run test:e2e # End-to-end tests
@@ -1185,16 +1219,19 @@ npm run test:e2e # End-to-end tests
1185
1219
  | Unit — Skill Packs | 2 | 57+ | All passing |
1186
1220
  | Unit — AI Templates | 2 | 130+ | All passing |
1187
1221
  | Unit — Web UI | 1 | 46 | All passing |
1222
+ | Unit — Learning | 7 | 177 | All passing |
1188
1223
  | Integration | 13 | 230+ | All passing |
1189
1224
  | Integration — WS session | 1 | 12 | All passing |
1190
1225
  | Integration — AI Handlers | 2 | 100+ | All passing |
1191
1226
  | Integration — Web UI | 1 | 29 | All passing |
1227
+ | Integration — Learning | 1 | 12 | All passing |
1192
1228
  | E2E | 15 | 260+ | All passing |
1193
1229
  | E2E — Skill Packs | 1 | 23+ | All passing |
1194
1230
  | E2E — AI Templates | 4 | 65+ | All passing |
1195
1231
  | E2E — Web UI | 1 | 24 | All passing |
1232
+ | E2E — Learning | 1 | 16 | All passing |
1196
1233
  | Core packages (external) | — | 118 | All passing |
1197
- | **CLI Total** | **133** | **3155** | **All passing** |
1234
+ | **CLI Total** | **201** | **3360** | **All passing** |
1198
1235
 
1199
1236
  ## License
1200
1237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.45.75",
3
+ "version": "0.45.76",
4
4
  "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Autonomous Learning Loop commands
3
+ * chainlesschain learning stats|trajectories|reflect|synthesize|improve|cleanup
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import { logger } from "../lib/logger.js";
9
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
10
+
11
+ export function registerLearningCommand(program) {
12
+ const learning = program
13
+ .command("learning")
14
+ .description(
15
+ "Autonomous learning loop — trajectories, reflection, skill synthesis",
16
+ );
17
+
18
+ // learning stats
19
+ learning
20
+ .command("stats")
21
+ .description("Show learning loop statistics")
22
+ .option("--json", "Output as JSON")
23
+ .action(async (options) => {
24
+ try {
25
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
26
+ if (!ctx.db) {
27
+ logger.error("Database not available");
28
+ process.exit(1);
29
+ }
30
+ const db = ctx.db.getDatabase();
31
+ const { TrajectoryStore } =
32
+ await import("../lib/learning/trajectory-store.js");
33
+ const store = new TrajectoryStore(db);
34
+ const stats = store.getStats();
35
+
36
+ if (options.json) {
37
+ console.log(JSON.stringify(stats, null, 2));
38
+ } else {
39
+ logger.log(chalk.bold("Learning Loop Statistics"));
40
+ logger.log(` Total trajectories: ${chalk.cyan(stats.total)}`);
41
+ logger.log(` Complex (6+ tools): ${chalk.cyan(stats.complex)}`);
42
+ logger.log(` Scored: ${chalk.cyan(stats.scored)}`);
43
+ logger.log(
44
+ ` Skills synthesized: ${chalk.cyan(stats.synthesized)}`,
45
+ );
46
+ }
47
+
48
+ await shutdown();
49
+ } catch (err) {
50
+ logger.error(`Failed: ${err.message}`);
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ // learning trajectories
56
+ learning
57
+ .command("trajectories")
58
+ .description("List recent trajectories")
59
+ .option("-n, --limit <n>", "Number of trajectories", "20")
60
+ .option("--session <id>", "Filter by session ID")
61
+ .option("--json", "Output as JSON")
62
+ .action(async (options) => {
63
+ try {
64
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
65
+ if (!ctx.db) {
66
+ logger.error("Database not available");
67
+ process.exit(1);
68
+ }
69
+ const db = ctx.db.getDatabase();
70
+ const { TrajectoryStore } =
71
+ await import("../lib/learning/trajectory-store.js");
72
+ const store = new TrajectoryStore(db);
73
+
74
+ const trajs = store.getRecent({
75
+ limit: parseInt(options.limit, 10),
76
+ sessionId: options.session,
77
+ });
78
+
79
+ if (options.json) {
80
+ console.log(JSON.stringify(trajs, null, 2));
81
+ } else {
82
+ if (trajs.length === 0) {
83
+ logger.log(chalk.gray("No trajectories recorded yet."));
84
+ } else {
85
+ logger.log(chalk.bold(`Recent Trajectories (${trajs.length})`));
86
+ for (const t of trajs) {
87
+ const scoreStr =
88
+ t.outcomeScore != null
89
+ ? chalk.cyan(t.outcomeScore.toFixed(2))
90
+ : chalk.gray("unscored");
91
+ const synthStr = t.synthesizedSkill
92
+ ? chalk.green(` → ${t.synthesizedSkill}`)
93
+ : "";
94
+ logger.log(
95
+ ` ${chalk.dim(t.id.slice(0, 8))} | ${t.complexityLevel.padEnd(8)} | ` +
96
+ `${t.toolCount} tools | score: ${scoreStr}${synthStr}`,
97
+ );
98
+ if (t.userIntent) {
99
+ logger.log(` ${chalk.dim(t.userIntent.slice(0, 80))}`);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ await shutdown();
106
+ } catch (err) {
107
+ logger.error(`Failed: ${err.message}`);
108
+ process.exit(1);
109
+ }
110
+ });
111
+
112
+ // learning reflect
113
+ learning
114
+ .command("reflect")
115
+ .description("Run a reflection cycle and generate report")
116
+ .option("--json", "Output as JSON")
117
+ .action(async (options) => {
118
+ try {
119
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
120
+ if (!ctx.db) {
121
+ logger.error("Database not available");
122
+ process.exit(1);
123
+ }
124
+ const db = ctx.db.getDatabase();
125
+ const { TrajectoryStore } =
126
+ await import("../lib/learning/trajectory-store.js");
127
+ const { ReflectionEngine } =
128
+ await import("../lib/learning/reflection-engine.js");
129
+ const store = new TrajectoryStore(db);
130
+ const engine = new ReflectionEngine(db, null, store);
131
+
132
+ const spinner = ora("Running reflection...").start();
133
+ const report = await engine.reflect();
134
+ spinner.succeed("Reflection complete");
135
+
136
+ if (options.json) {
137
+ console.log(JSON.stringify(report, null, 2));
138
+ } else {
139
+ logger.log(chalk.bold("Reflection Report"));
140
+ logger.log(` Period: ${chalk.dim(report.timestamp)}`);
141
+ logger.log(
142
+ ` Trajectories: ${chalk.cyan(report.totalTrajectories)}`,
143
+ );
144
+ logger.log(
145
+ ` Avg score: ${chalk.cyan(report.avgScore?.toFixed(2) || "N/A")}`,
146
+ );
147
+
148
+ const trendColor =
149
+ report.trend === "improving"
150
+ ? chalk.green
151
+ : report.trend === "declining"
152
+ ? chalk.red
153
+ : chalk.gray;
154
+ logger.log(` Trend: ${trendColor(report.trend)}`);
155
+
156
+ if (report.topTools && report.topTools.length > 0) {
157
+ logger.log(chalk.bold("\n Top Tools:"));
158
+ for (const t of report.topTools.slice(0, 5)) {
159
+ logger.log(
160
+ ` ${t.tool}: ${t.count}x (${(t.errorRate * 100).toFixed(0)}% error)`,
161
+ );
162
+ }
163
+ }
164
+
165
+ if (report.errorProneTools && report.errorProneTools.length > 0) {
166
+ logger.log(chalk.bold("\n Error-prone Tools:"));
167
+ for (const t of report.errorProneTools) {
168
+ logger.log(
169
+ ` ${chalk.red(t.tool)}: ${(t.errorRate * 100).toFixed(0)}% error rate`,
170
+ );
171
+ }
172
+ }
173
+
174
+ if (report.note) {
175
+ logger.log(chalk.gray(`\n Note: ${report.note}`));
176
+ }
177
+ }
178
+
179
+ await shutdown();
180
+ } catch (err) {
181
+ logger.error(`Failed: ${err.message}`);
182
+ process.exit(1);
183
+ }
184
+ });
185
+
186
+ // learning synthesize
187
+ learning
188
+ .command("synthesize")
189
+ .description("Synthesize new skills from eligible trajectories")
190
+ .option("--json", "Output as JSON")
191
+ .action(async (options) => {
192
+ try {
193
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
194
+ if (!ctx.db) {
195
+ logger.error("Database not available");
196
+ process.exit(1);
197
+ }
198
+ const db = ctx.db.getDatabase();
199
+ const { TrajectoryStore } =
200
+ await import("../lib/learning/trajectory-store.js");
201
+ const { SkillSynthesizer } =
202
+ await import("../lib/learning/skill-synthesizer.js");
203
+ const store = new TrajectoryStore(db);
204
+ const synthesizer = new SkillSynthesizer(db, null, store);
205
+
206
+ const spinner = ora("Scanning for synthesizable patterns...").start();
207
+ const result = await synthesizer.synthesize();
208
+ spinner.succeed("Synthesis complete");
209
+
210
+ if (options.json) {
211
+ console.log(JSON.stringify(result, null, 2));
212
+ } else {
213
+ if (result.created.length > 0) {
214
+ logger.log(
215
+ chalk.green(`Created ${result.created.length} skill(s):`),
216
+ );
217
+ for (const name of result.created) {
218
+ logger.log(` ${chalk.cyan(name)}`);
219
+ }
220
+ } else {
221
+ logger.log(chalk.gray("No new skills synthesized."));
222
+ }
223
+
224
+ if (result.skipped.length > 0) {
225
+ logger.log(
226
+ chalk.dim(`\nSkipped ${result.skipped.length} candidate(s)`),
227
+ );
228
+ }
229
+ }
230
+
231
+ await shutdown();
232
+ } catch (err) {
233
+ logger.error(`Failed: ${err.message}`);
234
+ process.exit(1);
235
+ }
236
+ });
237
+
238
+ // learning cleanup
239
+ learning
240
+ .command("cleanup")
241
+ .description("Delete old trajectories beyond retention period")
242
+ .option("--days <n>", "Retention days", "90")
243
+ .option("--json", "Output as JSON")
244
+ .action(async (options) => {
245
+ try {
246
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
247
+ if (!ctx.db) {
248
+ logger.error("Database not available");
249
+ process.exit(1);
250
+ }
251
+ const db = ctx.db.getDatabase();
252
+ const { TrajectoryStore } =
253
+ await import("../lib/learning/trajectory-store.js");
254
+ const store = new TrajectoryStore(db);
255
+
256
+ const days = parseInt(options.days, 10);
257
+ const spinner = ora(
258
+ `Cleaning up trajectories older than ${days} days...`,
259
+ ).start();
260
+ const deleted = store.cleanup(days);
261
+ spinner.succeed(`Cleanup complete: ${deleted} trajectories deleted`);
262
+
263
+ if (options.json) {
264
+ console.log(JSON.stringify({ deleted, retentionDays: days }));
265
+ }
266
+
267
+ await shutdown();
268
+ } catch (err) {
269
+ logger.error(`Failed: ${err.message}`);
270
+ process.exit(1);
271
+ }
272
+ });
273
+ }
@@ -19,6 +19,7 @@ import {
19
19
  rollbackApp,
20
20
  exportApp,
21
21
  listApps,
22
+ deployApp,
22
23
  } from "../lib/app-builder.js";
23
24
 
24
25
  export function registerLowcodeCommand(program) {
@@ -307,14 +308,28 @@ export function registerLowcodeCommand(program) {
307
308
  // lowcode deploy <app-id>
308
309
  lowcode
309
310
  .command("deploy")
310
- .description("Deploy application (placeholder)")
311
+ .description("Deploy application as static HTML bundle")
311
312
  .argument("<app-id>", "Application ID")
312
- .action(async (appId) => {
313
- logger.log(
314
- chalk.yellow(
315
- `Deploy for app ${chalk.cyan(appId)} is a placeholder. ` +
316
- "Full deployment support coming in a future release.",
317
- ),
318
- );
313
+ .option("-o, --output <dir>", "Output directory for the deploy bundle")
314
+ .action(async (appId, options) => {
315
+ const spinner = ora("Deploying application...").start();
316
+ let ctx;
317
+ try {
318
+ ctx = await bootstrap();
319
+ ensureLowcodeTables(ctx.db);
320
+ const result = deployApp(ctx.db, appId, {
321
+ outputDir: options.output || undefined,
322
+ });
323
+ spinner.succeed(
324
+ chalk.green(`App ${chalk.cyan(appId)} deployed successfully`),
325
+ );
326
+ logger.log(` Output: ${chalk.cyan(result.outputDir)}`);
327
+ logger.log(` Files: ${result.files.join(", ")}`);
328
+ logger.log(` Deployed: ${result.deployedAt}`);
329
+ } catch (err) {
330
+ spinner.fail(chalk.red(`Deploy failed: ${err.message}`));
331
+ } finally {
332
+ if (ctx) await shutdown(ctx);
333
+ }
319
334
  });
320
335
  }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Discord Formatter — converts agent responses to Discord-compatible markup.
3
+ *
4
+ * Discord supports a broad subset of Markdown. Main limit is 2000 chars per message.
5
+ *
6
+ * @module discord-formatter
7
+ */
8
+
9
+ const DISCORD_MAX_LENGTH = 2000;
10
+
11
+ /**
12
+ * Format an agent response for Discord.
13
+ * Discord natively supports Markdown, so minimal conversion needed.
14
+ * @param {string} response
15
+ * @param {object} [options]
16
+ * @param {number} [options.maxLength=2000]
17
+ * @returns {string}
18
+ */
19
+ export function formatForDiscord(response, options = {}) {
20
+ if (!response) return "";
21
+ const maxLength = options.maxLength || DISCORD_MAX_LENGTH;
22
+
23
+ let text = response;
24
+
25
+ if (text.length > maxLength) {
26
+ text = text.substring(0, maxLength - 3) + "...";
27
+ }
28
+
29
+ return text;
30
+ }
31
+
32
+ /**
33
+ * Split a response into Discord-sized chunks.
34
+ * Tries to split at code block or newline boundaries.
35
+ * @param {string} text
36
+ * @param {number} [maxLength=2000]
37
+ * @returns {string[]}
38
+ */
39
+ export function splitForDiscord(text, maxLength = DISCORD_MAX_LENGTH) {
40
+ if (!text || text.length <= maxLength) return [text || ""];
41
+
42
+ const chunks = [];
43
+ let remaining = text;
44
+
45
+ while (remaining.length > 0) {
46
+ if (remaining.length <= maxLength) {
47
+ chunks.push(remaining);
48
+ break;
49
+ }
50
+
51
+ // Try to split at code block boundary
52
+ let splitIdx = remaining.lastIndexOf("\n```", maxLength);
53
+ if (splitIdx > maxLength * 0.3) {
54
+ splitIdx += 1; // Include the newline
55
+ } else {
56
+ // Try to split at newline
57
+ splitIdx = remaining.lastIndexOf("\n", maxLength);
58
+ if (splitIdx < maxLength * 0.3) {
59
+ splitIdx = maxLength;
60
+ }
61
+ }
62
+
63
+ chunks.push(remaining.substring(0, splitIdx));
64
+ remaining = remaining.substring(splitIdx).trimStart();
65
+ }
66
+
67
+ return chunks;
68
+ }
69
+
70
+ /**
71
+ * Format a code block for Discord with syntax highlighting.
72
+ * @param {string} code
73
+ * @param {string} [language=""]
74
+ * @returns {string}
75
+ */
76
+ export function codeBlock(code, language = "") {
77
+ return `\`\`\`${language}\n${code}\n\`\`\``;
78
+ }
79
+
80
+ /**
81
+ * Format an embed-like block for Discord (using quote blocks).
82
+ * @param {string} title
83
+ * @param {string} content
84
+ * @returns {string}
85
+ */
86
+ export function quoteBlock(title, content) {
87
+ const lines = content.split("\n").map((l) => `> ${l}`);
88
+ return `**${title}**\n${lines.join("\n")}`;
89
+ }