@openclawcity/become 0.2.0 → 1.0.0

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/dist/index.js CHANGED
@@ -2889,6 +2889,584 @@ var TrainScheduler = class {
2889
2889
  }
2890
2890
  };
2891
2891
 
2892
+ // src/proxy/server.ts
2893
+ import { createServer } from "http";
2894
+
2895
+ // src/skills/store.ts
2896
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync as readdirSync2, mkdirSync as mkdirSync2, renameSync, unlinkSync, existsSync as existsSync2 } from "fs";
2897
+ import { join as join3, basename } from "path";
2898
+ import { createHash } from "crypto";
2899
+ var FileSkillStore = class {
2900
+ skillsDir;
2901
+ pendingDir;
2902
+ rejectedDir;
2903
+ constructor(config) {
2904
+ this.skillsDir = join3(config.baseDir, "skills");
2905
+ this.pendingDir = join3(config.baseDir, "pending");
2906
+ this.rejectedDir = join3(config.baseDir, "rejected");
2907
+ mkdirSync2(this.skillsDir, { recursive: true });
2908
+ mkdirSync2(this.pendingDir, { recursive: true });
2909
+ mkdirSync2(this.rejectedDir, { recursive: true });
2910
+ }
2911
+ // ── Read ────────────────────────────────────────────────────────────────
2912
+ listApproved() {
2913
+ return this.readDir(this.skillsDir);
2914
+ }
2915
+ listPending() {
2916
+ return this.readDir(this.pendingDir);
2917
+ }
2918
+ listRejected() {
2919
+ return this.readDir(this.rejectedDir);
2920
+ }
2921
+ getApproved(id) {
2922
+ this.validateId(id);
2923
+ return this.readFile(join3(this.skillsDir, `${id}.md`));
2924
+ }
2925
+ // ── Write ───────────────────────────────────────────────────────────────
2926
+ savePending(lesson) {
2927
+ const normalized = lesson.instruction.toLowerCase().trim();
2928
+ const allExisting = [...this.listApproved(), ...this.listPending()];
2929
+ if (allExisting.some((s) => s.instruction.toLowerCase().trim() === normalized)) {
2930
+ return null;
2931
+ }
2932
+ const id = this.generateId(lesson.name);
2933
+ const file = { ...lesson, id, approved_at: void 0 };
2934
+ this.writeFile(join3(this.pendingDir, `${id}.md`), file);
2935
+ return file;
2936
+ }
2937
+ approve(id) {
2938
+ this.validateId(id);
2939
+ const src = join3(this.pendingDir, `${id}.md`);
2940
+ if (!existsSync2(src)) return false;
2941
+ const skill = this.readFile(src);
2942
+ if (!skill) return false;
2943
+ skill.approved_at = (/* @__PURE__ */ new Date()).toISOString();
2944
+ const dest = join3(this.skillsDir, `${id}.md`);
2945
+ this.writeFile(dest, skill);
2946
+ unlinkSync(src);
2947
+ return true;
2948
+ }
2949
+ reject(id) {
2950
+ this.validateId(id);
2951
+ const src = join3(this.pendingDir, `${id}.md`);
2952
+ if (!existsSync2(src)) return false;
2953
+ const dest = join3(this.rejectedDir, `${id}.md`);
2954
+ renameSync(src, dest);
2955
+ return true;
2956
+ }
2957
+ disable(id) {
2958
+ this.validateId(id);
2959
+ const src = join3(this.skillsDir, `${id}.md`);
2960
+ if (!existsSync2(src)) return false;
2961
+ const dest = join3(this.rejectedDir, `${id}.md`);
2962
+ renameSync(src, dest);
2963
+ return true;
2964
+ }
2965
+ remove(id) {
2966
+ this.validateId(id);
2967
+ for (const dir of [this.skillsDir, this.pendingDir, this.rejectedDir]) {
2968
+ const path = join3(dir, `${id}.md`);
2969
+ if (existsSync2(path)) {
2970
+ unlinkSync(path);
2971
+ return true;
2972
+ }
2973
+ }
2974
+ return false;
2975
+ }
2976
+ // ── Helpers ─────────────────────────────────────────────────────────────
2977
+ /**
2978
+ * Validate that an id does not contain path traversal characters.
2979
+ * Prevents attacks like id="../../.ssh/authorized_keys"
2980
+ */
2981
+ validateId(id) {
2982
+ if (!id || typeof id !== "string") throw new Error("Invalid id");
2983
+ if (id.includes("/") || id.includes("\\") || id.includes("..") || id.includes("\0")) {
2984
+ throw new Error("Invalid id: path traversal detected");
2985
+ }
2986
+ }
2987
+ readDir(dir) {
2988
+ if (!existsSync2(dir)) return [];
2989
+ const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
2990
+ const skills = [];
2991
+ for (const f of files) {
2992
+ const skill = this.readFile(join3(dir, f));
2993
+ if (skill) skills.push(skill);
2994
+ }
2995
+ return skills.sort((a, b) => b.created_at.localeCompare(a.created_at));
2996
+ }
2997
+ readFile(path) {
2998
+ if (!existsSync2(path)) return null;
2999
+ try {
3000
+ const content = readFileSync2(path, "utf-8");
3001
+ return this.parseSkillFile(content, basename(path, ".md"));
3002
+ } catch {
3003
+ return null;
3004
+ }
3005
+ }
3006
+ writeFile(path, skill) {
3007
+ const content = this.formatSkillFile(skill);
3008
+ writeFileSync2(path, content, "utf-8");
3009
+ }
3010
+ parseSkillFile(content, id) {
3011
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
3012
+ if (!match) return null;
3013
+ const [, frontmatter, body] = match;
3014
+ const meta = {};
3015
+ for (const line of frontmatter.split("\n")) {
3016
+ const colonIdx = line.indexOf(":");
3017
+ if (colonIdx === -1) continue;
3018
+ const key = line.slice(0, colonIdx).trim();
3019
+ const value = line.slice(colonIdx + 1).trim();
3020
+ if (key && value) meta[key] = value;
3021
+ }
3022
+ return {
3023
+ id,
3024
+ name: meta.name ?? id,
3025
+ instruction: body.trim(),
3026
+ learned_from: meta.learned_from ?? "unknown",
3027
+ source: meta.source ?? "conversation",
3028
+ confidence: parseFloat(meta.confidence ?? "0.5"),
3029
+ approved_at: meta.approved_at || void 0,
3030
+ created_at: meta.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
3031
+ };
3032
+ }
3033
+ formatSkillFile(skill) {
3034
+ const lines = [
3035
+ "---",
3036
+ `name: ${skill.name}`,
3037
+ `learned_from: ${skill.learned_from}`,
3038
+ `source: ${skill.source}`,
3039
+ `confidence: ${skill.confidence}`,
3040
+ `created_at: ${skill.created_at}`
3041
+ ];
3042
+ if (skill.approved_at) lines.push(`approved_at: ${skill.approved_at}`);
3043
+ lines.push("---");
3044
+ lines.push("");
3045
+ lines.push(skill.instruction);
3046
+ lines.push("");
3047
+ return lines.join("\n");
3048
+ }
3049
+ generateId(name) {
3050
+ const clean = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 40);
3051
+ const hash = createHash("sha256").update(`${name}${Date.now()}${Math.random()}`).digest("hex").slice(0, 6);
3052
+ return `${clean}_${hash}`;
3053
+ }
3054
+ };
3055
+
3056
+ // src/skills/trust.ts
3057
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
3058
+ import { join as join4, dirname } from "path";
3059
+ var DEFAULT_TRUST = {
3060
+ trusted: [],
3061
+ blocked: [],
3062
+ default: "pending"
3063
+ };
3064
+ var DEFAULT_RATE_LIMITS = {
3065
+ max_lessons_per_day: 20,
3066
+ max_lessons_per_agent: 10,
3067
+ max_skills_per_call: 15
3068
+ };
3069
+ var TrustManager = class {
3070
+ trustPath;
3071
+ statsPath;
3072
+ config;
3073
+ dailyCounts;
3074
+ constructor(baseDir) {
3075
+ this.trustPath = join4(baseDir, "trust.json");
3076
+ this.statsPath = join4(baseDir, "state", "daily_counts.json");
3077
+ mkdirSync3(join4(baseDir, "state"), { recursive: true });
3078
+ this.config = this.loadTrust();
3079
+ this.dailyCounts = this.loadDailyCounts();
3080
+ }
3081
+ getLevel(agentId) {
3082
+ if (this.config.trusted.includes(agentId)) return "trusted";
3083
+ if (this.config.blocked.includes(agentId)) return "blocked";
3084
+ return this.config.default;
3085
+ }
3086
+ setLevel(agentId, level) {
3087
+ this.config.trusted = this.config.trusted.filter((a) => a !== agentId);
3088
+ this.config.blocked = this.config.blocked.filter((a) => a !== agentId);
3089
+ if (level === "trusted") this.config.trusted.push(agentId);
3090
+ if (level === "blocked") this.config.blocked.push(agentId);
3091
+ this.saveTrust();
3092
+ }
3093
+ setDefault(level) {
3094
+ this.config.default = level;
3095
+ this.saveTrust();
3096
+ }
3097
+ getConfig() {
3098
+ return { ...this.config };
3099
+ }
3100
+ // ── Rate Limiting ───────────────────────────────────────────────────────
3101
+ canLearn(agentId, limits = DEFAULT_RATE_LIMITS) {
3102
+ this.refreshDailyCountsIfNewDay();
3103
+ if (this.dailyCounts.total >= limits.max_lessons_per_day) return false;
3104
+ const agentCount = this.dailyCounts.perAgent[agentId] ?? 0;
3105
+ if (agentCount >= limits.max_lessons_per_agent) return false;
3106
+ return true;
3107
+ }
3108
+ recordLesson(agentId) {
3109
+ this.refreshDailyCountsIfNewDay();
3110
+ this.dailyCounts.total++;
3111
+ this.dailyCounts.perAgent[agentId] = (this.dailyCounts.perAgent[agentId] ?? 0) + 1;
3112
+ this.saveDailyCounts();
3113
+ }
3114
+ getDailyCounts() {
3115
+ this.refreshDailyCountsIfNewDay();
3116
+ return { total: this.dailyCounts.total, perAgent: { ...this.dailyCounts.perAgent } };
3117
+ }
3118
+ // ── Private ─────────────────────────────────────────────────────────────
3119
+ loadTrust() {
3120
+ if (!existsSync3(this.trustPath)) return { ...DEFAULT_TRUST, trusted: [], blocked: [] };
3121
+ try {
3122
+ const raw = JSON.parse(readFileSync3(this.trustPath, "utf-8"));
3123
+ return {
3124
+ trusted: Array.isArray(raw.trusted) ? raw.trusted.filter((a) => typeof a === "string") : [],
3125
+ blocked: Array.isArray(raw.blocked) ? raw.blocked.filter((a) => typeof a === "string") : [],
3126
+ default: ["trusted", "pending", "blocked"].includes(raw.default) ? raw.default : "pending"
3127
+ };
3128
+ } catch {
3129
+ return { ...DEFAULT_TRUST, trusted: [], blocked: [] };
3130
+ }
3131
+ }
3132
+ saveTrust() {
3133
+ mkdirSync3(dirname(this.trustPath), { recursive: true });
3134
+ writeFileSync3(this.trustPath, JSON.stringify(this.config, null, 2), "utf-8");
3135
+ }
3136
+ loadDailyCounts() {
3137
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3138
+ if (!existsSync3(this.statsPath)) return { date: today, total: 0, perAgent: {} };
3139
+ try {
3140
+ const data = JSON.parse(readFileSync3(this.statsPath, "utf-8"));
3141
+ if (data.date !== today) return { date: today, total: 0, perAgent: {} };
3142
+ return data;
3143
+ } catch {
3144
+ return { date: today, total: 0, perAgent: {} };
3145
+ }
3146
+ }
3147
+ saveDailyCounts() {
3148
+ writeFileSync3(this.statsPath, JSON.stringify(this.dailyCounts, null, 2), "utf-8");
3149
+ }
3150
+ refreshDailyCountsIfNewDay() {
3151
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3152
+ if (this.dailyCounts.date !== today) {
3153
+ this.dailyCounts = { date: today, total: 0, perAgent: {} };
3154
+ }
3155
+ }
3156
+ };
3157
+
3158
+ // src/skills/format.ts
3159
+ function formatSkillsForInjection(skills) {
3160
+ if (skills.length === 0) return "";
3161
+ const lines = skills.map((s) => {
3162
+ const source = s.source === "peer_review" ? "from a peer review" : s.source === "collaboration" ? "from a collaboration" : s.source === "teaching" ? "from being taught" : "from a conversation";
3163
+ return `- ${s.instruction} (${source})`;
3164
+ });
3165
+ return [
3166
+ "## Lessons learned from other agents",
3167
+ "",
3168
+ "You have learned the following from interactions with other agents. Follow these instructions:",
3169
+ "",
3170
+ ...lines
3171
+ ].join("\n");
3172
+ }
3173
+ function injectSkillsIntoMessages(messages, skillText) {
3174
+ if (!skillText) return;
3175
+ const sysIdx = messages.findIndex((m) => m.role === "system");
3176
+ if (sysIdx >= 0) {
3177
+ messages[sysIdx].content = skillText + "\n\n---\n\n" + messages[sysIdx].content;
3178
+ } else {
3179
+ messages.unshift({ role: "system", content: skillText });
3180
+ }
3181
+ }
3182
+
3183
+ // src/proxy/detector.ts
3184
+ var CHANNEL_PATTERN = /^\[([^\]]+)\s+says?\]:\s*/;
3185
+ var DM_PATTERN = /^DM\s+from\s+([^:]+):\s*/;
3186
+ var BUILDING_PATTERN = /^([a-zA-Z0-9]+[-_][a-zA-Z0-9_.-]+)\s+in\s+[^:]+:\s*/;
3187
+ var REVIEW_KEYWORDS = ["strengths:", "weaknesses:", "verdict:", "assessment:", "suggestions:"];
3188
+ function detectAgentConversation(messages) {
3189
+ const negative = { isAgentToAgent: false };
3190
+ if (!messages || messages.length === 0) return negative;
3191
+ for (const msg of messages) {
3192
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
3193
+ const content = typeof msg.content === "string" ? msg.content : "";
3194
+ if (msg.name && msg.role === "user") {
3195
+ return {
3196
+ isAgentToAgent: true,
3197
+ otherAgentId: msg.name,
3198
+ exchangeType: "chat"
3199
+ };
3200
+ }
3201
+ const channelMatch = content.match(CHANNEL_PATTERN);
3202
+ if (channelMatch) {
3203
+ return {
3204
+ isAgentToAgent: true,
3205
+ otherAgentId: channelMatch[1].trim(),
3206
+ exchangeType: "channel"
3207
+ };
3208
+ }
3209
+ const dmMatch = content.match(DM_PATTERN);
3210
+ if (dmMatch) {
3211
+ return {
3212
+ isAgentToAgent: true,
3213
+ otherAgentId: dmMatch[1].trim(),
3214
+ exchangeType: "dm"
3215
+ };
3216
+ }
3217
+ const buildingMatch = content.match(BUILDING_PATTERN);
3218
+ if (buildingMatch) {
3219
+ return {
3220
+ isAgentToAgent: true,
3221
+ otherAgentId: buildingMatch[1].trim(),
3222
+ exchangeType: "chat"
3223
+ };
3224
+ }
3225
+ const lowerContent = content.toLowerCase();
3226
+ const reviewMatches = REVIEW_KEYWORDS.filter((kw) => lowerContent.includes(kw));
3227
+ if (reviewMatches.length >= 2) {
3228
+ return {
3229
+ isAgentToAgent: true,
3230
+ otherAgentId: void 0,
3231
+ exchangeType: "peer_review"
3232
+ };
3233
+ }
3234
+ }
3235
+ return negative;
3236
+ }
3237
+ function extractExchangeText(messages) {
3238
+ return messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => {
3239
+ const speaker = m.name ?? m.role;
3240
+ const content = typeof m.content === "string" ? m.content : "";
3241
+ return `[${speaker}]: ${content}`;
3242
+ }).join("\n").slice(0, 6e3);
3243
+ }
3244
+
3245
+ // src/proxy/extractor.ts
3246
+ var LessonExtractor = class {
3247
+ constructor(store, trust, analyzer) {
3248
+ this.store = store;
3249
+ this.trust = trust;
3250
+ this.analyzer = analyzer;
3251
+ }
3252
+ /**
3253
+ * Analyze a conversation and extract lessons. Fire-and-forget.
3254
+ */
3255
+ async extract(messages) {
3256
+ const detection = detectAgentConversation(messages);
3257
+ if (!detection.isAgentToAgent) return;
3258
+ const agentId = detection.otherAgentId ?? "unknown-agent";
3259
+ const trustLevel = this.trust.getLevel(agentId);
3260
+ if (trustLevel === "blocked") return;
3261
+ if (!this.trust.canLearn(agentId)) return;
3262
+ const exchangeText = extractExchangeText(messages);
3263
+ if (exchangeText.length < 20) return;
3264
+ const prompt = `Analyze this conversation between an AI agent and another agent. Extract concrete, actionable lessons that the first agent (the "assistant") can learn from the other agent.
3265
+
3266
+ CONVERSATION:
3267
+ ${exchangeText.slice(0, 4e3)}
3268
+
3269
+ Output valid JSON array:
3270
+ [{"skill": "skill_name_snake_case", "instruction": "concrete actionable lesson in 1-2 sentences", "confidence": 0.0-1.0}]
3271
+
3272
+ Rules:
3273
+ - Only extract lessons where the other agent clearly teaches, corrects, or shares useful knowledge
3274
+ - instruction must be concrete and actionable ("use X when Y" not "consider improving")
3275
+ - confidence: 0.9 = explicitly taught, 0.7 = clearly implied, 0.5 = suggested, below 0.5 = skip
3276
+ - Only include lessons with confidence >= 0.5
3277
+ - Max 3 lessons per conversation
3278
+ - If no real learning happened, return []`;
3279
+ try {
3280
+ const response = await this.analyzer.analyze(prompt);
3281
+ const lessons = this.parseLessons(response);
3282
+ for (const lesson of lessons.slice(0, 3)) {
3283
+ const saved = this.store.savePending({
3284
+ name: lesson.skill,
3285
+ instruction: lesson.instruction.slice(0, 500),
3286
+ learned_from: agentId,
3287
+ source: detection.exchangeType ?? "conversation",
3288
+ confidence: lesson.confidence,
3289
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3290
+ });
3291
+ if (saved) {
3292
+ this.trust.recordLesson(agentId);
3293
+ if (trustLevel === "trusted") {
3294
+ this.store.approve(saved.id);
3295
+ }
3296
+ }
3297
+ }
3298
+ } catch {
3299
+ }
3300
+ }
3301
+ parseLessons(response) {
3302
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
3303
+ if (!jsonMatch) return [];
3304
+ try {
3305
+ const parsed = JSON.parse(jsonMatch[0]);
3306
+ if (!Array.isArray(parsed)) return [];
3307
+ return parsed.filter(
3308
+ (r) => typeof r.skill === "string" && typeof r.instruction === "string" && typeof r.confidence === "number" && r.confidence >= 0.5 && r.skill.length > 0 && r.instruction.length > 0
3309
+ );
3310
+ } catch {
3311
+ return [];
3312
+ }
3313
+ }
3314
+ };
3315
+
3316
+ // src/proxy/server.ts
3317
+ var SKILL_CACHE_TTL_MS = 5e3;
3318
+ function createProxyServer(config, analyzer) {
3319
+ const store = new FileSkillStore({ baseDir: config.baseDir });
3320
+ const trust = new TrustManager(config.baseDir);
3321
+ const extractor = analyzer ? new LessonExtractor(store, trust, analyzer) : null;
3322
+ const stats = {
3323
+ requests_forwarded: 0,
3324
+ skills_injected: 0,
3325
+ lessons_extracted: 0,
3326
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
3327
+ };
3328
+ let cachedSkills = [];
3329
+ let cacheTimestamp = 0;
3330
+ function getSkills() {
3331
+ const now = Date.now();
3332
+ if (now - cacheTimestamp > SKILL_CACHE_TTL_MS) {
3333
+ cachedSkills = store.listApproved();
3334
+ cacheTimestamp = now;
3335
+ }
3336
+ return cachedSkills;
3337
+ }
3338
+ const server = createServer(async (req, res) => {
3339
+ if (req.url === "/health" && req.method === "GET") {
3340
+ res.writeHead(200, { "Content-Type": "application/json" });
3341
+ res.end(JSON.stringify({ status: "ok", ...stats }));
3342
+ return;
3343
+ }
3344
+ const isOpenAI = req.url === "/v1/chat/completions";
3345
+ const isAnthropic = req.url === "/v1/messages";
3346
+ if (req.method !== "POST" || !isOpenAI && !isAnthropic) {
3347
+ res.writeHead(404, { "Content-Type": "application/json" });
3348
+ res.end(JSON.stringify({ error: "Not found. Use POST /v1/chat/completions or /v1/messages" }));
3349
+ return;
3350
+ }
3351
+ try {
3352
+ const rawBody = await readBody(req);
3353
+ const body = JSON.parse(rawBody);
3354
+ const messages = body.messages;
3355
+ if (Array.isArray(messages)) {
3356
+ const skills = getSkills().slice(0, config.max_skills_per_call);
3357
+ if (skills.length > 0) {
3358
+ const skillText = formatSkillsForInjection(skills);
3359
+ injectSkillsIntoMessages(messages, skillText);
3360
+ stats.skills_injected++;
3361
+ }
3362
+ }
3363
+ const upstreamUrl = buildUpstreamUrl(config, req.url);
3364
+ const upstreamHeaders = buildUpstreamHeaders(config, req.headers);
3365
+ const isStreaming = body.stream === true;
3366
+ const modifiedBody = JSON.stringify(body);
3367
+ const upstreamRes = await fetch(upstreamUrl, {
3368
+ method: "POST",
3369
+ headers: upstreamHeaders,
3370
+ body: modifiedBody
3371
+ });
3372
+ stats.requests_forwarded++;
3373
+ const responseHeaders = {};
3374
+ upstreamRes.headers.forEach((value, key) => {
3375
+ if (key.toLowerCase() !== "transfer-encoding") {
3376
+ responseHeaders[key] = value;
3377
+ }
3378
+ });
3379
+ res.writeHead(upstreamRes.status, responseHeaders);
3380
+ if (isStreaming && upstreamRes.body) {
3381
+ const reader = upstreamRes.body.getReader();
3382
+ try {
3383
+ while (true) {
3384
+ const { done, value } = await reader.read();
3385
+ if (done) break;
3386
+ res.write(value);
3387
+ }
3388
+ } finally {
3389
+ res.end();
3390
+ }
3391
+ if (config.auto_extract && extractor && Array.isArray(messages)) {
3392
+ extractor.extract(messages).then(() => {
3393
+ stats.lessons_extracted++;
3394
+ }).catch(() => {
3395
+ });
3396
+ }
3397
+ } else {
3398
+ const responseBuffer = await upstreamRes.arrayBuffer();
3399
+ res.end(Buffer.from(responseBuffer));
3400
+ if (config.auto_extract && extractor && Array.isArray(messages)) {
3401
+ extractor.extract(messages).then(() => {
3402
+ stats.lessons_extracted++;
3403
+ }).catch(() => {
3404
+ });
3405
+ }
3406
+ }
3407
+ } catch (err) {
3408
+ const safeMessage = err instanceof Error && err.message === "Request body too large" ? "Request body too large" : "Failed to forward request to LLM";
3409
+ if (!res.headersSent) {
3410
+ res.writeHead(502, { "Content-Type": "application/json" });
3411
+ }
3412
+ res.end(JSON.stringify({ error: safeMessage }));
3413
+ }
3414
+ });
3415
+ return {
3416
+ server,
3417
+ stats,
3418
+ store,
3419
+ trust,
3420
+ listen: (port) => {
3421
+ const p = port ?? config.port;
3422
+ return new Promise((resolve2) => {
3423
+ server.listen(p, "127.0.0.1", () => resolve2());
3424
+ });
3425
+ },
3426
+ close: () => new Promise((resolve2) => server.close(() => resolve2()))
3427
+ };
3428
+ }
3429
+ function readBody(req) {
3430
+ return new Promise((resolve2, reject) => {
3431
+ const chunks = [];
3432
+ let size = 0;
3433
+ const MAX_BODY = 10 * 1024 * 1024;
3434
+ req.on("data", (chunk) => {
3435
+ size += chunk.length;
3436
+ if (size > MAX_BODY) {
3437
+ req.destroy();
3438
+ reject(new Error("Request body too large"));
3439
+ return;
3440
+ }
3441
+ chunks.push(chunk);
3442
+ });
3443
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
3444
+ req.on("error", reject);
3445
+ });
3446
+ }
3447
+ function buildUpstreamUrl(config, path) {
3448
+ const base = config.llm_base_url.replace(/\/+$/, "");
3449
+ return `${base}${path}`;
3450
+ }
3451
+ function buildUpstreamHeaders(config, incomingHeaders) {
3452
+ const headers = {
3453
+ "Content-Type": "application/json"
3454
+ };
3455
+ if (config.llm_provider === "anthropic") {
3456
+ headers["x-api-key"] = config.llm_api_key;
3457
+ headers["anthropic-version"] = "2023-06-01";
3458
+ const version = incomingHeaders["anthropic-version"];
3459
+ if (typeof version === "string") headers["anthropic-version"] = version;
3460
+ const beta = incomingHeaders["anthropic-beta"];
3461
+ if (typeof beta === "string") headers["anthropic-beta"] = beta;
3462
+ } else {
3463
+ headers["Authorization"] = `Bearer ${config.llm_api_key}`;
3464
+ }
3465
+ const accept = incomingHeaders["accept"];
3466
+ if (typeof accept === "string") headers["Accept"] = accept;
3467
+ return headers;
3468
+ }
3469
+
2892
3470
  // src/integrations/openclawcity.ts
2893
3471
  function emptySkillEvidence() {
2894
3472
  return {
@@ -3182,8 +3760,10 @@ export {
3182
3760
  Become,
3183
3761
  ConversationLearner,
3184
3762
  DREYFUS_THRESHOLDS,
3763
+ FileSkillStore,
3185
3764
  GrowthTracker,
3186
3765
  LearningGraph,
3766
+ LessonExtractor,
3187
3767
  MemoryStore,
3188
3768
  MilestoneDetector,
3189
3769
  NormDetector,
@@ -3199,11 +3779,14 @@ export {
3199
3779
  TeachingProtocol,
3200
3780
  TrainScheduler,
3201
3781
  TrendTracker,
3782
+ TrustManager,
3202
3783
  WEIGHTS,
3203
3784
  checkGate,
3204
3785
  computeFullScore,
3205
3786
  computeScore,
3787
+ createProxyServer,
3206
3788
  datasetStats,
3789
+ detectAgentConversation,
3207
3790
  detectBloomsLevel,
3208
3791
  detectCollaborationGap,
3209
3792
  detectCollectiveMemory,
@@ -3216,9 +3799,12 @@ export {
3216
3799
  detectSoloCreator,
3217
3800
  detectSymbolicVocabulary,
3218
3801
  dreyfusStage,
3802
+ extractExchangeText,
3219
3803
  filterHighQuality,
3804
+ formatSkillsForInjection,
3220
3805
  getReputationLevel,
3221
3806
  importSkillDirectory,
3807
+ injectSkillsIntoMessages,
3222
3808
  nextMilestone,
3223
3809
  normalizeCategory,
3224
3810
  parseSkillFile,