@openclawcity/become 0.2.0 → 1.0.1

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.cjs CHANGED
@@ -28,8 +28,10 @@ __export(src_exports, {
28
28
  Become: () => Become,
29
29
  ConversationLearner: () => ConversationLearner,
30
30
  DREYFUS_THRESHOLDS: () => DREYFUS_THRESHOLDS,
31
+ FileSkillStore: () => FileSkillStore,
31
32
  GrowthTracker: () => GrowthTracker,
32
33
  LearningGraph: () => LearningGraph,
34
+ LessonExtractor: () => LessonExtractor,
33
35
  MemoryStore: () => MemoryStore,
34
36
  MilestoneDetector: () => MilestoneDetector,
35
37
  NormDetector: () => NormDetector,
@@ -45,11 +47,14 @@ __export(src_exports, {
45
47
  TeachingProtocol: () => TeachingProtocol,
46
48
  TrainScheduler: () => TrainScheduler,
47
49
  TrendTracker: () => TrendTracker,
50
+ TrustManager: () => TrustManager,
48
51
  WEIGHTS: () => WEIGHTS,
49
52
  checkGate: () => checkGate,
50
53
  computeFullScore: () => computeFullScore,
51
54
  computeScore: () => computeScore,
55
+ createProxyServer: () => createProxyServer,
52
56
  datasetStats: () => datasetStats,
57
+ detectAgentConversation: () => detectAgentConversation,
53
58
  detectBloomsLevel: () => detectBloomsLevel,
54
59
  detectCollaborationGap: () => detectCollaborationGap,
55
60
  detectCollectiveMemory: () => detectCollectiveMemory,
@@ -62,9 +67,12 @@ __export(src_exports, {
62
67
  detectSoloCreator: () => detectSoloCreator,
63
68
  detectSymbolicVocabulary: () => detectSymbolicVocabulary,
64
69
  dreyfusStage: () => dreyfusStage,
70
+ extractExchangeText: () => extractExchangeText,
65
71
  filterHighQuality: () => filterHighQuality,
72
+ formatSkillsForInjection: () => formatSkillsForInjection,
66
73
  getReputationLevel: () => getReputationLevel,
67
74
  importSkillDirectory: () => importSkillDirectory,
75
+ injectSkillsIntoMessages: () => injectSkillsIntoMessages,
68
76
  nextMilestone: () => nextMilestone,
69
77
  normalizeCategory: () => normalizeCategory,
70
78
  parseSkillFile: () => parseSkillFile,
@@ -2954,6 +2962,584 @@ var TrainScheduler = class {
2954
2962
  }
2955
2963
  };
2956
2964
 
2965
+ // src/proxy/server.ts
2966
+ var import_node_http = require("http");
2967
+
2968
+ // src/skills/store.ts
2969
+ var import_node_fs3 = require("fs");
2970
+ var import_node_path3 = require("path");
2971
+ var import_node_crypto = require("crypto");
2972
+ var FileSkillStore = class {
2973
+ skillsDir;
2974
+ pendingDir;
2975
+ rejectedDir;
2976
+ constructor(config) {
2977
+ this.skillsDir = (0, import_node_path3.join)(config.baseDir, "skills");
2978
+ this.pendingDir = (0, import_node_path3.join)(config.baseDir, "pending");
2979
+ this.rejectedDir = (0, import_node_path3.join)(config.baseDir, "rejected");
2980
+ (0, import_node_fs3.mkdirSync)(this.skillsDir, { recursive: true });
2981
+ (0, import_node_fs3.mkdirSync)(this.pendingDir, { recursive: true });
2982
+ (0, import_node_fs3.mkdirSync)(this.rejectedDir, { recursive: true });
2983
+ }
2984
+ // ── Read ────────────────────────────────────────────────────────────────
2985
+ listApproved() {
2986
+ return this.readDir(this.skillsDir);
2987
+ }
2988
+ listPending() {
2989
+ return this.readDir(this.pendingDir);
2990
+ }
2991
+ listRejected() {
2992
+ return this.readDir(this.rejectedDir);
2993
+ }
2994
+ getApproved(id) {
2995
+ this.validateId(id);
2996
+ return this.readFile((0, import_node_path3.join)(this.skillsDir, `${id}.md`));
2997
+ }
2998
+ // ── Write ───────────────────────────────────────────────────────────────
2999
+ savePending(lesson) {
3000
+ const normalized = lesson.instruction.toLowerCase().trim();
3001
+ const allExisting = [...this.listApproved(), ...this.listPending()];
3002
+ if (allExisting.some((s) => s.instruction.toLowerCase().trim() === normalized)) {
3003
+ return null;
3004
+ }
3005
+ const id = this.generateId(lesson.name);
3006
+ const file = { ...lesson, id, approved_at: void 0 };
3007
+ this.writeFile((0, import_node_path3.join)(this.pendingDir, `${id}.md`), file);
3008
+ return file;
3009
+ }
3010
+ approve(id) {
3011
+ this.validateId(id);
3012
+ const src = (0, import_node_path3.join)(this.pendingDir, `${id}.md`);
3013
+ if (!(0, import_node_fs3.existsSync)(src)) return false;
3014
+ const skill = this.readFile(src);
3015
+ if (!skill) return false;
3016
+ skill.approved_at = (/* @__PURE__ */ new Date()).toISOString();
3017
+ const dest = (0, import_node_path3.join)(this.skillsDir, `${id}.md`);
3018
+ this.writeFile(dest, skill);
3019
+ (0, import_node_fs3.unlinkSync)(src);
3020
+ return true;
3021
+ }
3022
+ reject(id) {
3023
+ this.validateId(id);
3024
+ const src = (0, import_node_path3.join)(this.pendingDir, `${id}.md`);
3025
+ if (!(0, import_node_fs3.existsSync)(src)) return false;
3026
+ const dest = (0, import_node_path3.join)(this.rejectedDir, `${id}.md`);
3027
+ (0, import_node_fs3.renameSync)(src, dest);
3028
+ return true;
3029
+ }
3030
+ disable(id) {
3031
+ this.validateId(id);
3032
+ const src = (0, import_node_path3.join)(this.skillsDir, `${id}.md`);
3033
+ if (!(0, import_node_fs3.existsSync)(src)) return false;
3034
+ const dest = (0, import_node_path3.join)(this.rejectedDir, `${id}.md`);
3035
+ (0, import_node_fs3.renameSync)(src, dest);
3036
+ return true;
3037
+ }
3038
+ remove(id) {
3039
+ this.validateId(id);
3040
+ for (const dir of [this.skillsDir, this.pendingDir, this.rejectedDir]) {
3041
+ const path = (0, import_node_path3.join)(dir, `${id}.md`);
3042
+ if ((0, import_node_fs3.existsSync)(path)) {
3043
+ (0, import_node_fs3.unlinkSync)(path);
3044
+ return true;
3045
+ }
3046
+ }
3047
+ return false;
3048
+ }
3049
+ // ── Helpers ─────────────────────────────────────────────────────────────
3050
+ /**
3051
+ * Validate that an id does not contain path traversal characters.
3052
+ * Prevents attacks like id="../../.ssh/authorized_keys"
3053
+ */
3054
+ validateId(id) {
3055
+ if (!id || typeof id !== "string") throw new Error("Invalid id");
3056
+ if (id.includes("/") || id.includes("\\") || id.includes("..") || id.includes("\0")) {
3057
+ throw new Error("Invalid id: path traversal detected");
3058
+ }
3059
+ }
3060
+ readDir(dir) {
3061
+ if (!(0, import_node_fs3.existsSync)(dir)) return [];
3062
+ const files = (0, import_node_fs3.readdirSync)(dir).filter((f) => f.endsWith(".md"));
3063
+ const skills = [];
3064
+ for (const f of files) {
3065
+ const skill = this.readFile((0, import_node_path3.join)(dir, f));
3066
+ if (skill) skills.push(skill);
3067
+ }
3068
+ return skills.sort((a, b) => b.created_at.localeCompare(a.created_at));
3069
+ }
3070
+ readFile(path) {
3071
+ if (!(0, import_node_fs3.existsSync)(path)) return null;
3072
+ try {
3073
+ const content = (0, import_node_fs3.readFileSync)(path, "utf-8");
3074
+ return this.parseSkillFile(content, (0, import_node_path3.basename)(path, ".md"));
3075
+ } catch {
3076
+ return null;
3077
+ }
3078
+ }
3079
+ writeFile(path, skill) {
3080
+ const content = this.formatSkillFile(skill);
3081
+ (0, import_node_fs3.writeFileSync)(path, content, "utf-8");
3082
+ }
3083
+ parseSkillFile(content, id) {
3084
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
3085
+ if (!match) return null;
3086
+ const [, frontmatter, body] = match;
3087
+ const meta = {};
3088
+ for (const line of frontmatter.split("\n")) {
3089
+ const colonIdx = line.indexOf(":");
3090
+ if (colonIdx === -1) continue;
3091
+ const key = line.slice(0, colonIdx).trim();
3092
+ const value = line.slice(colonIdx + 1).trim();
3093
+ if (key && value) meta[key] = value;
3094
+ }
3095
+ return {
3096
+ id,
3097
+ name: meta.name ?? id,
3098
+ instruction: body.trim(),
3099
+ learned_from: meta.learned_from ?? "unknown",
3100
+ source: meta.source ?? "conversation",
3101
+ confidence: parseFloat(meta.confidence ?? "0.5"),
3102
+ approved_at: meta.approved_at || void 0,
3103
+ created_at: meta.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
3104
+ };
3105
+ }
3106
+ formatSkillFile(skill) {
3107
+ const lines = [
3108
+ "---",
3109
+ `name: ${skill.name}`,
3110
+ `learned_from: ${skill.learned_from}`,
3111
+ `source: ${skill.source}`,
3112
+ `confidence: ${skill.confidence}`,
3113
+ `created_at: ${skill.created_at}`
3114
+ ];
3115
+ if (skill.approved_at) lines.push(`approved_at: ${skill.approved_at}`);
3116
+ lines.push("---");
3117
+ lines.push("");
3118
+ lines.push(skill.instruction);
3119
+ lines.push("");
3120
+ return lines.join("\n");
3121
+ }
3122
+ generateId(name) {
3123
+ const clean = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 40);
3124
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${name}${Date.now()}${Math.random()}`).digest("hex").slice(0, 6);
3125
+ return `${clean}_${hash}`;
3126
+ }
3127
+ };
3128
+
3129
+ // src/skills/trust.ts
3130
+ var import_node_fs4 = require("fs");
3131
+ var import_node_path4 = require("path");
3132
+ var DEFAULT_TRUST = {
3133
+ trusted: [],
3134
+ blocked: [],
3135
+ default: "pending"
3136
+ };
3137
+ var DEFAULT_RATE_LIMITS = {
3138
+ max_lessons_per_day: 20,
3139
+ max_lessons_per_agent: 10,
3140
+ max_skills_per_call: 15
3141
+ };
3142
+ var TrustManager = class {
3143
+ trustPath;
3144
+ statsPath;
3145
+ config;
3146
+ dailyCounts;
3147
+ constructor(baseDir) {
3148
+ this.trustPath = (0, import_node_path4.join)(baseDir, "trust.json");
3149
+ this.statsPath = (0, import_node_path4.join)(baseDir, "state", "daily_counts.json");
3150
+ (0, import_node_fs4.mkdirSync)((0, import_node_path4.join)(baseDir, "state"), { recursive: true });
3151
+ this.config = this.loadTrust();
3152
+ this.dailyCounts = this.loadDailyCounts();
3153
+ }
3154
+ getLevel(agentId) {
3155
+ if (this.config.trusted.includes(agentId)) return "trusted";
3156
+ if (this.config.blocked.includes(agentId)) return "blocked";
3157
+ return this.config.default;
3158
+ }
3159
+ setLevel(agentId, level) {
3160
+ this.config.trusted = this.config.trusted.filter((a) => a !== agentId);
3161
+ this.config.blocked = this.config.blocked.filter((a) => a !== agentId);
3162
+ if (level === "trusted") this.config.trusted.push(agentId);
3163
+ if (level === "blocked") this.config.blocked.push(agentId);
3164
+ this.saveTrust();
3165
+ }
3166
+ setDefault(level) {
3167
+ this.config.default = level;
3168
+ this.saveTrust();
3169
+ }
3170
+ getConfig() {
3171
+ return { ...this.config };
3172
+ }
3173
+ // ── Rate Limiting ───────────────────────────────────────────────────────
3174
+ canLearn(agentId, limits = DEFAULT_RATE_LIMITS) {
3175
+ this.refreshDailyCountsIfNewDay();
3176
+ if (this.dailyCounts.total >= limits.max_lessons_per_day) return false;
3177
+ const agentCount = this.dailyCounts.perAgent[agentId] ?? 0;
3178
+ if (agentCount >= limits.max_lessons_per_agent) return false;
3179
+ return true;
3180
+ }
3181
+ recordLesson(agentId) {
3182
+ this.refreshDailyCountsIfNewDay();
3183
+ this.dailyCounts.total++;
3184
+ this.dailyCounts.perAgent[agentId] = (this.dailyCounts.perAgent[agentId] ?? 0) + 1;
3185
+ this.saveDailyCounts();
3186
+ }
3187
+ getDailyCounts() {
3188
+ this.refreshDailyCountsIfNewDay();
3189
+ return { total: this.dailyCounts.total, perAgent: { ...this.dailyCounts.perAgent } };
3190
+ }
3191
+ // ── Private ─────────────────────────────────────────────────────────────
3192
+ loadTrust() {
3193
+ if (!(0, import_node_fs4.existsSync)(this.trustPath)) return { ...DEFAULT_TRUST, trusted: [], blocked: [] };
3194
+ try {
3195
+ const raw = JSON.parse((0, import_node_fs4.readFileSync)(this.trustPath, "utf-8"));
3196
+ return {
3197
+ trusted: Array.isArray(raw.trusted) ? raw.trusted.filter((a) => typeof a === "string") : [],
3198
+ blocked: Array.isArray(raw.blocked) ? raw.blocked.filter((a) => typeof a === "string") : [],
3199
+ default: ["trusted", "pending", "blocked"].includes(raw.default) ? raw.default : "pending"
3200
+ };
3201
+ } catch {
3202
+ return { ...DEFAULT_TRUST, trusted: [], blocked: [] };
3203
+ }
3204
+ }
3205
+ saveTrust() {
3206
+ (0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(this.trustPath), { recursive: true });
3207
+ (0, import_node_fs4.writeFileSync)(this.trustPath, JSON.stringify(this.config, null, 2), "utf-8");
3208
+ }
3209
+ loadDailyCounts() {
3210
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3211
+ if (!(0, import_node_fs4.existsSync)(this.statsPath)) return { date: today, total: 0, perAgent: {} };
3212
+ try {
3213
+ const data = JSON.parse((0, import_node_fs4.readFileSync)(this.statsPath, "utf-8"));
3214
+ if (data.date !== today) return { date: today, total: 0, perAgent: {} };
3215
+ return data;
3216
+ } catch {
3217
+ return { date: today, total: 0, perAgent: {} };
3218
+ }
3219
+ }
3220
+ saveDailyCounts() {
3221
+ (0, import_node_fs4.writeFileSync)(this.statsPath, JSON.stringify(this.dailyCounts, null, 2), "utf-8");
3222
+ }
3223
+ refreshDailyCountsIfNewDay() {
3224
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3225
+ if (this.dailyCounts.date !== today) {
3226
+ this.dailyCounts = { date: today, total: 0, perAgent: {} };
3227
+ }
3228
+ }
3229
+ };
3230
+
3231
+ // src/skills/format.ts
3232
+ function formatSkillsForInjection(skills) {
3233
+ if (skills.length === 0) return "";
3234
+ const lines = skills.map((s) => {
3235
+ 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";
3236
+ return `- ${s.instruction} (${source})`;
3237
+ });
3238
+ return [
3239
+ "## Lessons learned from other agents",
3240
+ "",
3241
+ "You have learned the following from interactions with other agents. Follow these instructions:",
3242
+ "",
3243
+ ...lines
3244
+ ].join("\n");
3245
+ }
3246
+ function injectSkillsIntoMessages(messages, skillText) {
3247
+ if (!skillText) return;
3248
+ const sysIdx = messages.findIndex((m) => m.role === "system");
3249
+ if (sysIdx >= 0) {
3250
+ messages[sysIdx].content = skillText + "\n\n---\n\n" + messages[sysIdx].content;
3251
+ } else {
3252
+ messages.unshift({ role: "system", content: skillText });
3253
+ }
3254
+ }
3255
+
3256
+ // src/proxy/detector.ts
3257
+ var CHANNEL_PATTERN = /^\[([^\]]+)\s+says?\]:\s*/;
3258
+ var DM_PATTERN = /^DM\s+from\s+([^:]+):\s*/;
3259
+ var BUILDING_PATTERN = /^([a-zA-Z0-9]+[-_][a-zA-Z0-9_.-]+)\s+in\s+[^:]+:\s*/;
3260
+ var REVIEW_KEYWORDS = ["strengths:", "weaknesses:", "verdict:", "assessment:", "suggestions:"];
3261
+ function detectAgentConversation(messages) {
3262
+ const negative = { isAgentToAgent: false };
3263
+ if (!messages || messages.length === 0) return negative;
3264
+ for (const msg of messages) {
3265
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
3266
+ const content = typeof msg.content === "string" ? msg.content : "";
3267
+ if (msg.name && msg.role === "user") {
3268
+ return {
3269
+ isAgentToAgent: true,
3270
+ otherAgentId: msg.name,
3271
+ exchangeType: "chat"
3272
+ };
3273
+ }
3274
+ const channelMatch = content.match(CHANNEL_PATTERN);
3275
+ if (channelMatch) {
3276
+ return {
3277
+ isAgentToAgent: true,
3278
+ otherAgentId: channelMatch[1].trim(),
3279
+ exchangeType: "channel"
3280
+ };
3281
+ }
3282
+ const dmMatch = content.match(DM_PATTERN);
3283
+ if (dmMatch) {
3284
+ return {
3285
+ isAgentToAgent: true,
3286
+ otherAgentId: dmMatch[1].trim(),
3287
+ exchangeType: "dm"
3288
+ };
3289
+ }
3290
+ const buildingMatch = content.match(BUILDING_PATTERN);
3291
+ if (buildingMatch) {
3292
+ return {
3293
+ isAgentToAgent: true,
3294
+ otherAgentId: buildingMatch[1].trim(),
3295
+ exchangeType: "chat"
3296
+ };
3297
+ }
3298
+ const lowerContent = content.toLowerCase();
3299
+ const reviewMatches = REVIEW_KEYWORDS.filter((kw) => lowerContent.includes(kw));
3300
+ if (reviewMatches.length >= 2) {
3301
+ return {
3302
+ isAgentToAgent: true,
3303
+ otherAgentId: void 0,
3304
+ exchangeType: "peer_review"
3305
+ };
3306
+ }
3307
+ }
3308
+ return negative;
3309
+ }
3310
+ function extractExchangeText(messages) {
3311
+ return messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => {
3312
+ const speaker = m.name ?? m.role;
3313
+ const content = typeof m.content === "string" ? m.content : "";
3314
+ return `[${speaker}]: ${content}`;
3315
+ }).join("\n").slice(0, 6e3);
3316
+ }
3317
+
3318
+ // src/proxy/extractor.ts
3319
+ var LessonExtractor = class {
3320
+ constructor(store, trust, analyzer) {
3321
+ this.store = store;
3322
+ this.trust = trust;
3323
+ this.analyzer = analyzer;
3324
+ }
3325
+ /**
3326
+ * Analyze a conversation and extract lessons. Fire-and-forget.
3327
+ */
3328
+ async extract(messages) {
3329
+ const detection = detectAgentConversation(messages);
3330
+ if (!detection.isAgentToAgent) return;
3331
+ const agentId = detection.otherAgentId ?? "unknown-agent";
3332
+ const trustLevel = this.trust.getLevel(agentId);
3333
+ if (trustLevel === "blocked") return;
3334
+ if (!this.trust.canLearn(agentId)) return;
3335
+ const exchangeText = extractExchangeText(messages);
3336
+ if (exchangeText.length < 20) return;
3337
+ 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.
3338
+
3339
+ CONVERSATION:
3340
+ ${exchangeText.slice(0, 4e3)}
3341
+
3342
+ Output valid JSON array:
3343
+ [{"skill": "skill_name_snake_case", "instruction": "concrete actionable lesson in 1-2 sentences", "confidence": 0.0-1.0}]
3344
+
3345
+ Rules:
3346
+ - Only extract lessons where the other agent clearly teaches, corrects, or shares useful knowledge
3347
+ - instruction must be concrete and actionable ("use X when Y" not "consider improving")
3348
+ - confidence: 0.9 = explicitly taught, 0.7 = clearly implied, 0.5 = suggested, below 0.5 = skip
3349
+ - Only include lessons with confidence >= 0.5
3350
+ - Max 3 lessons per conversation
3351
+ - If no real learning happened, return []`;
3352
+ try {
3353
+ const response = await this.analyzer.analyze(prompt);
3354
+ const lessons = this.parseLessons(response);
3355
+ for (const lesson of lessons.slice(0, 3)) {
3356
+ const saved = this.store.savePending({
3357
+ name: lesson.skill,
3358
+ instruction: lesson.instruction.slice(0, 500),
3359
+ learned_from: agentId,
3360
+ source: detection.exchangeType ?? "conversation",
3361
+ confidence: lesson.confidence,
3362
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3363
+ });
3364
+ if (saved) {
3365
+ this.trust.recordLesson(agentId);
3366
+ if (trustLevel === "trusted") {
3367
+ this.store.approve(saved.id);
3368
+ }
3369
+ }
3370
+ }
3371
+ } catch {
3372
+ }
3373
+ }
3374
+ parseLessons(response) {
3375
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
3376
+ if (!jsonMatch) return [];
3377
+ try {
3378
+ const parsed = JSON.parse(jsonMatch[0]);
3379
+ if (!Array.isArray(parsed)) return [];
3380
+ return parsed.filter(
3381
+ (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
3382
+ );
3383
+ } catch {
3384
+ return [];
3385
+ }
3386
+ }
3387
+ };
3388
+
3389
+ // src/proxy/server.ts
3390
+ var SKILL_CACHE_TTL_MS = 5e3;
3391
+ function createProxyServer(config, analyzer) {
3392
+ const store = new FileSkillStore({ baseDir: config.baseDir });
3393
+ const trust = new TrustManager(config.baseDir);
3394
+ const extractor = analyzer ? new LessonExtractor(store, trust, analyzer) : null;
3395
+ const stats = {
3396
+ requests_forwarded: 0,
3397
+ skills_injected: 0,
3398
+ lessons_extracted: 0,
3399
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
3400
+ };
3401
+ let cachedSkills = [];
3402
+ let cacheTimestamp = 0;
3403
+ function getSkills() {
3404
+ const now = Date.now();
3405
+ if (now - cacheTimestamp > SKILL_CACHE_TTL_MS) {
3406
+ cachedSkills = store.listApproved();
3407
+ cacheTimestamp = now;
3408
+ }
3409
+ return cachedSkills;
3410
+ }
3411
+ const server = (0, import_node_http.createServer)(async (req, res) => {
3412
+ if (req.url === "/health" && req.method === "GET") {
3413
+ res.writeHead(200, { "Content-Type": "application/json" });
3414
+ res.end(JSON.stringify({ status: "ok", ...stats }));
3415
+ return;
3416
+ }
3417
+ const isOpenAI = req.url === "/v1/chat/completions";
3418
+ const isAnthropic = req.url === "/v1/messages";
3419
+ if (req.method !== "POST" || !isOpenAI && !isAnthropic) {
3420
+ res.writeHead(404, { "Content-Type": "application/json" });
3421
+ res.end(JSON.stringify({ error: "Not found. Use POST /v1/chat/completions or /v1/messages" }));
3422
+ return;
3423
+ }
3424
+ try {
3425
+ const rawBody = await readBody(req);
3426
+ const body = JSON.parse(rawBody);
3427
+ const messages = body.messages;
3428
+ if (Array.isArray(messages)) {
3429
+ const skills = getSkills().slice(0, config.max_skills_per_call);
3430
+ if (skills.length > 0) {
3431
+ const skillText = formatSkillsForInjection(skills);
3432
+ injectSkillsIntoMessages(messages, skillText);
3433
+ stats.skills_injected++;
3434
+ }
3435
+ }
3436
+ const upstreamUrl = buildUpstreamUrl(config, req.url);
3437
+ const upstreamHeaders = buildUpstreamHeaders(config, req.headers);
3438
+ const isStreaming = body.stream === true;
3439
+ const modifiedBody = JSON.stringify(body);
3440
+ const upstreamRes = await fetch(upstreamUrl, {
3441
+ method: "POST",
3442
+ headers: upstreamHeaders,
3443
+ body: modifiedBody
3444
+ });
3445
+ stats.requests_forwarded++;
3446
+ const responseHeaders = {};
3447
+ upstreamRes.headers.forEach((value, key) => {
3448
+ if (key.toLowerCase() !== "transfer-encoding") {
3449
+ responseHeaders[key] = value;
3450
+ }
3451
+ });
3452
+ res.writeHead(upstreamRes.status, responseHeaders);
3453
+ if (isStreaming && upstreamRes.body) {
3454
+ const reader = upstreamRes.body.getReader();
3455
+ try {
3456
+ while (true) {
3457
+ const { done, value } = await reader.read();
3458
+ if (done) break;
3459
+ res.write(value);
3460
+ }
3461
+ } finally {
3462
+ res.end();
3463
+ }
3464
+ if (config.auto_extract && extractor && Array.isArray(messages)) {
3465
+ extractor.extract(messages).then(() => {
3466
+ stats.lessons_extracted++;
3467
+ }).catch(() => {
3468
+ });
3469
+ }
3470
+ } else {
3471
+ const responseBuffer = await upstreamRes.arrayBuffer();
3472
+ res.end(Buffer.from(responseBuffer));
3473
+ if (config.auto_extract && extractor && Array.isArray(messages)) {
3474
+ extractor.extract(messages).then(() => {
3475
+ stats.lessons_extracted++;
3476
+ }).catch(() => {
3477
+ });
3478
+ }
3479
+ }
3480
+ } catch (err) {
3481
+ const safeMessage = err instanceof Error && err.message === "Request body too large" ? "Request body too large" : "Failed to forward request to LLM";
3482
+ if (!res.headersSent) {
3483
+ res.writeHead(502, { "Content-Type": "application/json" });
3484
+ }
3485
+ res.end(JSON.stringify({ error: safeMessage }));
3486
+ }
3487
+ });
3488
+ return {
3489
+ server,
3490
+ stats,
3491
+ store,
3492
+ trust,
3493
+ listen: (port) => {
3494
+ const p = port ?? config.port;
3495
+ return new Promise((resolve2) => {
3496
+ server.listen(p, "127.0.0.1", () => resolve2());
3497
+ });
3498
+ },
3499
+ close: () => new Promise((resolve2) => server.close(() => resolve2()))
3500
+ };
3501
+ }
3502
+ function readBody(req) {
3503
+ return new Promise((resolve2, reject) => {
3504
+ const chunks = [];
3505
+ let size = 0;
3506
+ const MAX_BODY = 10 * 1024 * 1024;
3507
+ req.on("data", (chunk) => {
3508
+ size += chunk.length;
3509
+ if (size > MAX_BODY) {
3510
+ req.destroy();
3511
+ reject(new Error("Request body too large"));
3512
+ return;
3513
+ }
3514
+ chunks.push(chunk);
3515
+ });
3516
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
3517
+ req.on("error", reject);
3518
+ });
3519
+ }
3520
+ function buildUpstreamUrl(config, path) {
3521
+ const base = config.llm_base_url.replace(/\/+$/, "");
3522
+ return `${base}${path}`;
3523
+ }
3524
+ function buildUpstreamHeaders(config, incomingHeaders) {
3525
+ const headers = {
3526
+ "Content-Type": "application/json"
3527
+ };
3528
+ if (config.llm_provider === "anthropic") {
3529
+ headers["x-api-key"] = config.llm_api_key;
3530
+ headers["anthropic-version"] = "2023-06-01";
3531
+ const version = incomingHeaders["anthropic-version"];
3532
+ if (typeof version === "string") headers["anthropic-version"] = version;
3533
+ const beta = incomingHeaders["anthropic-beta"];
3534
+ if (typeof beta === "string") headers["anthropic-beta"] = beta;
3535
+ } else {
3536
+ headers["Authorization"] = `Bearer ${config.llm_api_key}`;
3537
+ }
3538
+ const accept = incomingHeaders["accept"];
3539
+ if (typeof accept === "string") headers["Accept"] = accept;
3540
+ return headers;
3541
+ }
3542
+
2957
3543
  // src/integrations/openclawcity.ts
2958
3544
  function emptySkillEvidence() {
2959
3545
  return {
@@ -3248,8 +3834,10 @@ var Become = class {
3248
3834
  Become,
3249
3835
  ConversationLearner,
3250
3836
  DREYFUS_THRESHOLDS,
3837
+ FileSkillStore,
3251
3838
  GrowthTracker,
3252
3839
  LearningGraph,
3840
+ LessonExtractor,
3253
3841
  MemoryStore,
3254
3842
  MilestoneDetector,
3255
3843
  NormDetector,
@@ -3265,11 +3853,14 @@ var Become = class {
3265
3853
  TeachingProtocol,
3266
3854
  TrainScheduler,
3267
3855
  TrendTracker,
3856
+ TrustManager,
3268
3857
  WEIGHTS,
3269
3858
  checkGate,
3270
3859
  computeFullScore,
3271
3860
  computeScore,
3861
+ createProxyServer,
3272
3862
  datasetStats,
3863
+ detectAgentConversation,
3273
3864
  detectBloomsLevel,
3274
3865
  detectCollaborationGap,
3275
3866
  detectCollectiveMemory,
@@ -3282,9 +3873,12 @@ var Become = class {
3282
3873
  detectSoloCreator,
3283
3874
  detectSymbolicVocabulary,
3284
3875
  dreyfusStage,
3876
+ extractExchangeText,
3285
3877
  filterHighQuality,
3878
+ formatSkillsForInjection,
3286
3879
  getReputationLevel,
3287
3880
  importSkillDirectory,
3881
+ injectSkillsIntoMessages,
3288
3882
  nextMilestone,
3289
3883
  normalizeCategory,
3290
3884
  parseSkillFile,