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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GatewayBase — shared foundation for messaging platform gateways.
|
|
3
|
+
*
|
|
4
|
+
* Each gateway (Telegram, Discord, etc.) extends this class.
|
|
5
|
+
* Provides: session-per-chat management, message mapping, rate limiting,
|
|
6
|
+
* and integration with the agent loop.
|
|
7
|
+
*
|
|
8
|
+
* @module gateway-base
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EventEmitter } from "node:events";
|
|
12
|
+
|
|
13
|
+
// ─── Constants ──────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const DEFAULT_MAX_RESPONSE_LENGTH = 4000;
|
|
16
|
+
const DEFAULT_RATE_LIMIT_WINDOW = 60000; // 1 minute
|
|
17
|
+
const DEFAULT_RATE_LIMIT_MAX = 20; // messages per window
|
|
18
|
+
|
|
19
|
+
// ─── GatewayBase ────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export class GatewayBase extends EventEmitter {
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} options
|
|
24
|
+
* @param {string} options.platform - Platform name (e.g. "telegram", "discord")
|
|
25
|
+
* @param {number} [options.maxResponseLength] - Max chars per response
|
|
26
|
+
* @param {number} [options.rateLimitWindow] - Rate limit window in ms
|
|
27
|
+
* @param {number} [options.rateLimitMax] - Max messages per window
|
|
28
|
+
*/
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
super();
|
|
31
|
+
this.platform = options.platform || "unknown";
|
|
32
|
+
this.maxResponseLength =
|
|
33
|
+
options.maxResponseLength || DEFAULT_MAX_RESPONSE_LENGTH;
|
|
34
|
+
this.rateLimitWindow = options.rateLimitWindow || DEFAULT_RATE_LIMIT_WINDOW;
|
|
35
|
+
this.rateLimitMax = options.rateLimitMax || DEFAULT_RATE_LIMIT_MAX;
|
|
36
|
+
|
|
37
|
+
/** @type {Map<string, { messages: object[], lastActivity: number }>} */
|
|
38
|
+
this.sessions = new Map();
|
|
39
|
+
|
|
40
|
+
/** @type {Map<string, number[]>} */
|
|
41
|
+
this._rateLimitBuckets = new Map();
|
|
42
|
+
|
|
43
|
+
this._running = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Lifecycle ───────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/** Start the gateway. Override in subclass. */
|
|
49
|
+
async start() {
|
|
50
|
+
this._running = true;
|
|
51
|
+
this.emit("started", { platform: this.platform });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Stop the gateway. Override in subclass. */
|
|
55
|
+
async stop() {
|
|
56
|
+
this._running = false;
|
|
57
|
+
this.sessions.clear();
|
|
58
|
+
this._rateLimitBuckets.clear();
|
|
59
|
+
this.emit("stopped", { platform: this.platform });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @returns {boolean} */
|
|
63
|
+
isRunning() {
|
|
64
|
+
return this._running;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Session management ──────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get or create a session for a chat.
|
|
71
|
+
* @param {string} chatId - Platform-specific chat identifier
|
|
72
|
+
* @returns {{ messages: object[], lastActivity: number, isNew: boolean }}
|
|
73
|
+
*/
|
|
74
|
+
getOrCreateSession(chatId) {
|
|
75
|
+
if (this.sessions.has(chatId)) {
|
|
76
|
+
const session = this.sessions.get(chatId);
|
|
77
|
+
session.lastActivity = Date.now();
|
|
78
|
+
return { ...session, isNew: false };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const session = {
|
|
82
|
+
messages: [],
|
|
83
|
+
lastActivity: Date.now(),
|
|
84
|
+
};
|
|
85
|
+
this.sessions.set(chatId, session);
|
|
86
|
+
return { ...session, isNew: true };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Add a message to a chat session.
|
|
91
|
+
* @param {string} chatId
|
|
92
|
+
* @param {string} role - "user" | "assistant"
|
|
93
|
+
* @param {string} content
|
|
94
|
+
*/
|
|
95
|
+
addMessage(chatId, role, content) {
|
|
96
|
+
const session = this.sessions.get(chatId);
|
|
97
|
+
if (session) {
|
|
98
|
+
session.messages.push({ role, content });
|
|
99
|
+
session.lastActivity = Date.now();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Clear a chat session.
|
|
105
|
+
* @param {string} chatId
|
|
106
|
+
*/
|
|
107
|
+
clearSession(chatId) {
|
|
108
|
+
this.sessions.delete(chatId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get active session count.
|
|
113
|
+
* @returns {number}
|
|
114
|
+
*/
|
|
115
|
+
getSessionCount() {
|
|
116
|
+
return this.sessions.size;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Rate limiting ───────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if a chat is rate-limited.
|
|
123
|
+
* @param {string} chatId
|
|
124
|
+
* @returns {boolean}
|
|
125
|
+
*/
|
|
126
|
+
isRateLimited(chatId) {
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
const bucket = this._rateLimitBuckets.get(chatId) || [];
|
|
129
|
+
// Clean old entries
|
|
130
|
+
const recent = bucket.filter((ts) => now - ts < this.rateLimitWindow);
|
|
131
|
+
this._rateLimitBuckets.set(chatId, recent);
|
|
132
|
+
return recent.length >= this.rateLimitMax;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Record a message for rate limiting.
|
|
137
|
+
* @param {string} chatId
|
|
138
|
+
*/
|
|
139
|
+
recordMessage(chatId) {
|
|
140
|
+
const bucket = this._rateLimitBuckets.get(chatId) || [];
|
|
141
|
+
bucket.push(Date.now());
|
|
142
|
+
this._rateLimitBuckets.set(chatId, bucket);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Message formatting ──────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Split a long response into chunks.
|
|
149
|
+
* @param {string} text
|
|
150
|
+
* @param {number} [maxLength]
|
|
151
|
+
* @returns {string[]}
|
|
152
|
+
*/
|
|
153
|
+
splitResponse(text, maxLength) {
|
|
154
|
+
const limit = maxLength || this.maxResponseLength;
|
|
155
|
+
if (!text || text.length <= limit) return [text || ""];
|
|
156
|
+
|
|
157
|
+
const chunks = [];
|
|
158
|
+
let remaining = text;
|
|
159
|
+
while (remaining.length > 0) {
|
|
160
|
+
if (remaining.length <= limit) {
|
|
161
|
+
chunks.push(remaining);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
// Try to split at last newline within limit
|
|
165
|
+
let splitIdx = remaining.lastIndexOf("\n", limit);
|
|
166
|
+
if (splitIdx < limit * 0.5) {
|
|
167
|
+
// No good newline split point — split at limit
|
|
168
|
+
splitIdx = limit;
|
|
169
|
+
}
|
|
170
|
+
chunks.push(remaining.substring(0, splitIdx));
|
|
171
|
+
remaining = remaining.substring(splitIdx).trimStart();
|
|
172
|
+
}
|
|
173
|
+
return chunks;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Stats ───────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get gateway statistics.
|
|
180
|
+
* @returns {{ platform: string, running: boolean, sessions: number }}
|
|
181
|
+
*/
|
|
182
|
+
getStats() {
|
|
183
|
+
return {
|
|
184
|
+
platform: this.platform,
|
|
185
|
+
running: this._running,
|
|
186
|
+
sessions: this.sessions.size,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Formatter — converts agent responses to Telegram-compatible markup.
|
|
3
|
+
*
|
|
4
|
+
* Telegram uses a subset of Markdown (MarkdownV2) with different escaping rules.
|
|
5
|
+
*
|
|
6
|
+
* @module telegram-formatter
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Characters that must be escaped in MarkdownV2
|
|
10
|
+
const ESCAPE_CHARS = /([_*[\]()~`>#+\-=|{}.!])/g;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Escape text for Telegram MarkdownV2.
|
|
14
|
+
* @param {string} text
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function escapeMarkdownV2(text) {
|
|
18
|
+
if (!text) return "";
|
|
19
|
+
return text.replace(ESCAPE_CHARS, "\\$1");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Convert standard Markdown to Telegram MarkdownV2.
|
|
24
|
+
* Handles: bold, italic, code blocks, inline code, links.
|
|
25
|
+
* @param {string} markdown
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function toTelegramMarkdown(markdown) {
|
|
29
|
+
if (!markdown) return "";
|
|
30
|
+
|
|
31
|
+
let result = markdown;
|
|
32
|
+
|
|
33
|
+
// Preserve code blocks (don't escape inside them)
|
|
34
|
+
const codeBlocks = [];
|
|
35
|
+
result = result.replace(/```[\s\S]*?```/g, (match) => {
|
|
36
|
+
codeBlocks.push(match);
|
|
37
|
+
return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Preserve inline code
|
|
41
|
+
const inlineCode = [];
|
|
42
|
+
result = result.replace(/`[^`]+`/g, (match) => {
|
|
43
|
+
inlineCode.push(match);
|
|
44
|
+
return `__INLINE_CODE_${inlineCode.length - 1}__`;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Escape special characters in text
|
|
48
|
+
result = escapeMarkdownV2(result);
|
|
49
|
+
|
|
50
|
+
// Restore code blocks and inline code (unescaped)
|
|
51
|
+
for (let i = codeBlocks.length - 1; i >= 0; i--) {
|
|
52
|
+
result = result.replace(
|
|
53
|
+
`__CODE_BLOCK_${i}__`.replace(ESCAPE_CHARS, "\\$1"),
|
|
54
|
+
codeBlocks[i],
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
for (let i = inlineCode.length - 1; i >= 0; i--) {
|
|
58
|
+
result = result.replace(
|
|
59
|
+
`__INLINE_CODE_${i}__`.replace(ESCAPE_CHARS, "\\$1"),
|
|
60
|
+
inlineCode[i],
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Format an agent response for Telegram (plain text fallback).
|
|
69
|
+
* Strips complex markdown, keeps it readable.
|
|
70
|
+
* @param {string} response
|
|
71
|
+
* @param {object} [options]
|
|
72
|
+
* @param {number} [options.maxLength=4000]
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
export function formatForTelegram(response, options = {}) {
|
|
76
|
+
if (!response) return "";
|
|
77
|
+
const maxLength = options.maxLength || 4000;
|
|
78
|
+
|
|
79
|
+
let text = response;
|
|
80
|
+
|
|
81
|
+
// Convert headers to bold
|
|
82
|
+
text = text.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
|
|
83
|
+
|
|
84
|
+
// Keep code blocks as-is (Telegram supports ```)
|
|
85
|
+
// Keep bold (**text** → *text*)
|
|
86
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "*$1*");
|
|
87
|
+
|
|
88
|
+
if (text.length > maxLength) {
|
|
89
|
+
text = text.substring(0, maxLength - 3) + "...";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return text;
|
|
93
|
+
}
|
package/src/index.js
CHANGED
|
@@ -47,6 +47,7 @@ import { registerA2aCommand } from "./commands/a2a.js";
|
|
|
47
47
|
// Phase 7: Security & Evolution
|
|
48
48
|
import { registerSandboxCommand } from "./commands/sandbox.js";
|
|
49
49
|
import { registerEvolutionCommand } from "./commands/evolution.js";
|
|
50
|
+
import { registerLearningCommand } from "./commands/learning.js";
|
|
50
51
|
|
|
51
52
|
// Phase 7: EvoMap Federation + DAO Governance
|
|
52
53
|
import { registerDaoCommand } from "./commands/dao.js";
|
|
@@ -168,6 +169,7 @@ export function createProgram() {
|
|
|
168
169
|
// Phase 7: Security & Evolution
|
|
169
170
|
registerSandboxCommand(program);
|
|
170
171
|
registerEvolutionCommand(program);
|
|
172
|
+
registerLearningCommand(program);
|
|
171
173
|
|
|
172
174
|
// Phase 7: EvoMap Federation + DAO Governance
|
|
173
175
|
registerDaoCommand(program);
|
package/src/lib/app-builder.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import crypto from "crypto";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
7
9
|
|
|
8
10
|
/** @type {Map<string, object>} In-memory app cache */
|
|
9
11
|
const _apps = new Map();
|
|
@@ -220,14 +222,12 @@ export function saveDesign(db, appId, design) {
|
|
|
220
222
|
).run(versionId, appId, newVersion, JSON.stringify(design));
|
|
221
223
|
|
|
222
224
|
if (!_versions.has(appId)) _versions.set(appId, []);
|
|
223
|
-
_versions
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
snapshot: design,
|
|
230
|
-
});
|
|
225
|
+
_versions.get(appId).push({
|
|
226
|
+
id: versionId,
|
|
227
|
+
app_id: appId,
|
|
228
|
+
version: newVersion,
|
|
229
|
+
snapshot: design,
|
|
230
|
+
});
|
|
231
231
|
|
|
232
232
|
if (_apps.has(appId)) {
|
|
233
233
|
_apps.get(appId).design = design;
|
|
@@ -375,3 +375,131 @@ export function listApps(db) {
|
|
|
375
375
|
updated_at: r.updated_at,
|
|
376
376
|
}));
|
|
377
377
|
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get a single application by ID from the database.
|
|
381
|
+
*
|
|
382
|
+
* @param {object} db
|
|
383
|
+
* @param {string} appId
|
|
384
|
+
* @returns {object|null}
|
|
385
|
+
*/
|
|
386
|
+
export function getApp(db, appId) {
|
|
387
|
+
const row = db.prepare(`SELECT * FROM lowcode_apps WHERE id = ?`).get(appId);
|
|
388
|
+
if (!row) return null;
|
|
389
|
+
return {
|
|
390
|
+
id: row.id,
|
|
391
|
+
name: row.name,
|
|
392
|
+
description: row.description,
|
|
393
|
+
design: row.design
|
|
394
|
+
? JSON.parse(row.design)
|
|
395
|
+
: { components: [], layout: {} },
|
|
396
|
+
status: row.status,
|
|
397
|
+
version: row.version,
|
|
398
|
+
platform: row.platform,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Generate a static HTML bundle string for an app's design.
|
|
404
|
+
*
|
|
405
|
+
* @param {object} app - App object with design
|
|
406
|
+
* @returns {{ "index.html": string, "app.js": string, "style.css": string }}
|
|
407
|
+
*/
|
|
408
|
+
function generateStaticBundle(app) {
|
|
409
|
+
const design = app.design || { components: [], layout: {} };
|
|
410
|
+
const components = design.components || [];
|
|
411
|
+
|
|
412
|
+
const componentHtml = components
|
|
413
|
+
.map(
|
|
414
|
+
(c, i) =>
|
|
415
|
+
` <div class="lc-component lc-${(c.type || c.name || "widget").toLowerCase()}" id="comp-${i}">${c.label || c.type || c.name || "Component"}</div>`,
|
|
416
|
+
)
|
|
417
|
+
.join("\n");
|
|
418
|
+
|
|
419
|
+
const html = `<!DOCTYPE html>
|
|
420
|
+
<html lang="en">
|
|
421
|
+
<head>
|
|
422
|
+
<meta charset="UTF-8">
|
|
423
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
424
|
+
<title>${app.name || "Low-Code App"}</title>
|
|
425
|
+
<link rel="stylesheet" href="style.css">
|
|
426
|
+
</head>
|
|
427
|
+
<body>
|
|
428
|
+
<div id="app">
|
|
429
|
+
<h1>${app.name || "Low-Code App"}</h1>
|
|
430
|
+
<p>${app.description || ""}</p>
|
|
431
|
+
<div class="lc-container">
|
|
432
|
+
${componentHtml || " <p>No components defined</p>"}
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
<script src="app.js"><\/script>
|
|
436
|
+
</body>
|
|
437
|
+
</html>`;
|
|
438
|
+
|
|
439
|
+
const css = `/* Generated by ChainlessChain Low-Code Platform */
|
|
440
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
441
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 24px; background: #f5f5f5; }
|
|
442
|
+
#app { max-width: 1200px; margin: 0 auto; }
|
|
443
|
+
h1 { margin-bottom: 8px; color: #1a1a1a; }
|
|
444
|
+
p { margin-bottom: 16px; color: #666; }
|
|
445
|
+
.lc-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
|
|
446
|
+
.lc-component { background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; }
|
|
447
|
+
`;
|
|
448
|
+
|
|
449
|
+
const js = `// Generated by ChainlessChain Low-Code Platform
|
|
450
|
+
// App: ${app.name || "Untitled"} v${app.version || 1}
|
|
451
|
+
(function() {
|
|
452
|
+
console.log("Low-Code App initialized: ${app.name || "Untitled"}");
|
|
453
|
+
var config = ${JSON.stringify({ id: app.id, name: app.name, version: app.version, platform: app.platform })};
|
|
454
|
+
document.querySelectorAll(".lc-component").forEach(function(el) {
|
|
455
|
+
el.addEventListener("click", function() { el.classList.toggle("active"); });
|
|
456
|
+
});
|
|
457
|
+
})();
|
|
458
|
+
`;
|
|
459
|
+
|
|
460
|
+
return { "index.html": html, "app.js": js, "style.css": css };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Deploy an application by generating a static bundle and writing it to disk.
|
|
465
|
+
*
|
|
466
|
+
* @param {object} db
|
|
467
|
+
* @param {string} appId
|
|
468
|
+
* @param {{ outputDir?: string }} options
|
|
469
|
+
* @returns {{ appId: string, outputDir: string, files: string[], deployedAt: string }}
|
|
470
|
+
*/
|
|
471
|
+
export function deployApp(db, appId, options = {}) {
|
|
472
|
+
const app = getApp(db, appId);
|
|
473
|
+
if (!app) {
|
|
474
|
+
throw new Error(`App '${appId}' not found`);
|
|
475
|
+
}
|
|
476
|
+
if (app.status !== "published") {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`App '${appId}' must be published before deploying (current status: ${app.status})`,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const outputDir =
|
|
483
|
+
options.outputDir ||
|
|
484
|
+
path.join(process.cwd(), ".chainlesschain", "deploys", appId);
|
|
485
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
486
|
+
|
|
487
|
+
const bundle = generateStaticBundle(app);
|
|
488
|
+
const fileNames = [];
|
|
489
|
+
for (const [fileName, content] of Object.entries(bundle)) {
|
|
490
|
+
fs.writeFileSync(path.join(outputDir, fileName), content, "utf-8");
|
|
491
|
+
fileNames.push(fileName);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Update app status to deployed
|
|
495
|
+
db.prepare(
|
|
496
|
+
`UPDATE lowcode_apps SET status = ?, updated_at = datetime('now') WHERE id = ?`,
|
|
497
|
+
).run("deployed", appId);
|
|
498
|
+
|
|
499
|
+
if (_apps.has(appId)) {
|
|
500
|
+
_apps.get(appId).status = "deployed";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const deployedAt = new Date().toISOString();
|
|
504
|
+
return { appId, outputDir, files: fileNames, deployedAt };
|
|
505
|
+
}
|
|
@@ -52,11 +52,18 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
52
52
|
/**
|
|
53
53
|
* Initialize with required dependencies.
|
|
54
54
|
*/
|
|
55
|
-
initialize({
|
|
55
|
+
initialize({
|
|
56
|
+
llmChat,
|
|
57
|
+
toolExecutor,
|
|
58
|
+
hookManager,
|
|
59
|
+
maxIterations,
|
|
60
|
+
iterationBudget,
|
|
61
|
+
} = {}) {
|
|
56
62
|
this._llmChat = llmChat || null;
|
|
57
63
|
this._toolExecutor = toolExecutor || null;
|
|
58
64
|
this._hookManager = hookManager || null;
|
|
59
65
|
if (maxIterations) this._maxIterations = maxIterations;
|
|
66
|
+
this._iterationBudget = iterationBudget || null; // shared budget from caller
|
|
60
67
|
this._initialized = true;
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -12,6 +12,7 @@ import { generateInstinctPrompt } from "./instinct-manager.js";
|
|
|
12
12
|
import { recallMemory } from "./hierarchical-memory.js";
|
|
13
13
|
import { BM25Search } from "./bm25-search.js";
|
|
14
14
|
import { createHash } from "crypto";
|
|
15
|
+
import { readUserProfile } from "./user-profile.js";
|
|
15
16
|
|
|
16
17
|
// Exported for test injection
|
|
17
18
|
export const _deps = {
|
|
@@ -19,6 +20,7 @@ export const _deps = {
|
|
|
19
20
|
recallMemory,
|
|
20
21
|
BM25Search,
|
|
21
22
|
createHash,
|
|
23
|
+
readUserProfile,
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
// ─── System prompt cleaning regexes (match desktop KV-Cache optimization) ───
|
|
@@ -105,6 +107,19 @@ export class CLIContextEngineering {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
// 2b. User profile injection (USER.md)
|
|
111
|
+
try {
|
|
112
|
+
const profile = _deps.readUserProfile();
|
|
113
|
+
if (profile) {
|
|
114
|
+
result.push({
|
|
115
|
+
role: "system",
|
|
116
|
+
content: `## User Profile\n${profile}`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
} catch (_err) {
|
|
120
|
+
// User profile injection failed — skip silently
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
// 3. Memory injection (scoped: higher threshold, namespace-aware)
|
|
109
124
|
if (this.db && userQuery) {
|
|
110
125
|
try {
|