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 +52 -15
- package/package.json +1 -1
- package/src/commands/learning.js +273 -0
- package/src/commands/lowcode.js +23 -8
- package/src/gateways/discord/discord-formatter.js +89 -0
- package/src/gateways/gateway-base.js +189 -0
- package/src/gateways/telegram/telegram-formatter.js +93 -0
- package/src/index.js +2 -0
- package/src/lib/app-builder.js +136 -8
- package/src/lib/autonomous-agent.js +8 -1
- package/src/lib/cli-context-engineering.js +15 -0
- package/src/lib/execution-backend.js +239 -0
- package/src/lib/hook-manager.js +2 -0
- package/src/lib/iteration-budget.js +175 -0
- package/src/lib/learning/learning-hooks.js +117 -0
- package/src/lib/learning/learning-tables.js +66 -0
- package/src/lib/learning/outcome-feedback.js +243 -0
- package/src/lib/learning/reflection-engine.js +323 -0
- package/src/lib/learning/skill-improver.js +536 -0
- package/src/lib/learning/skill-synthesizer.js +315 -0
- package/src/lib/learning/trajectory-store.js +409 -0
- package/src/lib/plugin-autodiscovery.js +224 -0
- package/src/lib/session-search.js +193 -0
- package/src/lib/sub-agent-context.js +7 -2
- package/src/lib/user-profile.js +172 -0
- package/src/lib/web-ui-server.js +1 -1
- package/src/repl/agent-repl.js +109 -0
- package/src/runtime/agent-core.js +75 -4
- package/src/runtime/coding-agent-contract-shared.cjs +35 -0
- package/src/runtime/coding-agent-policy.cjs +10 -0
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
|
|
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
|
|
1143
|
-
|
|
|
1144
|
-
| Ollama (Local)
|
|
1145
|
-
| OpenAI
|
|
1146
|
-
| Anthropic
|
|
1147
|
-
|
|
|
1148
|
-
|
|
|
1149
|
-
| Gemini
|
|
1150
|
-
| Mistral
|
|
1151
|
-
| Volcengine (
|
|
1152
|
-
|
|
|
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 (
|
|
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** | **
|
|
1234
|
+
| **CLI Total** | **201** | **3360** | **All passing** |
|
|
1198
1235
|
|
|
1199
1236
|
## License
|
|
1200
1237
|
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/commands/lowcode.js
CHANGED
|
@@ -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
|
|
311
|
+
.description("Deploy application as static HTML bundle")
|
|
311
312
|
.argument("<app-id>", "Application ID")
|
|
312
|
-
.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
+
}
|