@openclawcity/become 0.1.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
@@ -1014,6 +1014,266 @@ function importSkillDirectory(dir, _depth = 0) {
1014
1014
  return skills;
1015
1015
  }
1016
1016
 
1017
+ // src/learn/agent-conversations.ts
1018
+ var MAX_INSTRUCTIONS_PER_SKILL = 20;
1019
+ var MAX_INSTRUCTION_LENGTH = 500;
1020
+ var LEARNED_PREFIX = "learned:";
1021
+ var AgentLearningEngine = class {
1022
+ constructor(store, analyzer) {
1023
+ this.store = store;
1024
+ this.analyzer = analyzer;
1025
+ }
1026
+ /**
1027
+ * Analyze a conversation between two agents and extract lessons for both.
1028
+ */
1029
+ async learnFromConversation(exchange) {
1030
+ validateAgentId(exchange.agent_a);
1031
+ validateAgentId(exchange.agent_b);
1032
+ if (exchange.messages.length === 0) {
1033
+ return { agent_a_learned: [], agent_b_learned: [] };
1034
+ }
1035
+ const conversationText = exchange.messages.map((m) => `[${sanitize2(m.from)}]: ${sanitize2(m.text)}`).join("\n");
1036
+ const prompt = `Analyze this conversation between two AI agents (Agent A and Agent B) and extract concrete, actionable lessons that each agent can learn from the other.
1037
+
1038
+ Agent A: ${sanitize2(exchange.agent_a)}
1039
+ Agent B: ${sanitize2(exchange.agent_b)}
1040
+
1041
+ CONVERSATION (context: ${sanitize2(exchange.context ?? "chat")}):
1042
+ ${conversationText.slice(0, 4e3)}
1043
+
1044
+ For each agent, identify what they learned from the OTHER agent. Only extract lessons where one agent clearly teaches, corrects, or shares knowledge with the other.
1045
+
1046
+ Output valid JSON:
1047
+ {
1048
+ "a_learned": [
1049
+ {"skill": "skill_name", "instruction": "concrete actionable lesson in 1-2 sentences", "confidence": 0.0-1.0}
1050
+ ],
1051
+ "b_learned": [
1052
+ {"skill": "skill_name", "instruction": "concrete actionable lesson in 1-2 sentences", "confidence": 0.0-1.0}
1053
+ ]
1054
+ }
1055
+
1056
+ Rules:
1057
+ - skill names must be snake_case
1058
+ - instruction must be concrete and actionable, not vague ("use X when Y" not "consider improving")
1059
+ - confidence: 0.9 = explicitly taught, 0.7 = clearly implied, 0.5 = suggested, below 0.5 = skip
1060
+ - Only include lessons with confidence >= 0.5
1061
+ - Max 3 lessons per agent per conversation
1062
+ - If no real learning happened, return empty arrays`;
1063
+ try {
1064
+ const response = await this.analyzer.analyze(prompt);
1065
+ return await this.parseAndSave(response, exchange);
1066
+ } catch {
1067
+ return { agent_a_learned: [], agent_b_learned: [] };
1068
+ }
1069
+ }
1070
+ /**
1071
+ * Analyze a peer review and extract lessons for the reviewee.
1072
+ */
1073
+ async learnFromPeerReview(review) {
1074
+ validateAgentId(review.reviewer);
1075
+ validateAgentId(review.reviewee);
1076
+ if (review.weaknesses.length === 0 && review.suggestions.length === 0) {
1077
+ return [];
1078
+ }
1079
+ const reviewText = [
1080
+ `Assessment: ${sanitize2(review.assessment)}`,
1081
+ `Strengths: ${review.strengths.map(sanitize2).join(", ")}`,
1082
+ `Weaknesses: ${review.weaknesses.map(sanitize2).join(", ")}`,
1083
+ `Suggestions: ${review.suggestions.map(sanitize2).join(", ")}`
1084
+ ].join("\n");
1085
+ const prompt = `A peer reviewer gave this feedback. Extract concrete lessons the reviewee should learn.
1086
+
1087
+ REVIEW (skill: ${sanitize2(review.skill ?? "general")}):
1088
+ ${reviewText.slice(0, 2e3)}
1089
+
1090
+ Output valid JSON array:
1091
+ [{"skill": "skill_name", "instruction": "concrete actionable lesson", "confidence": 0.0-1.0}]
1092
+
1093
+ Rules:
1094
+ - Focus on weaknesses and suggestions \u2014 those are the learning opportunities
1095
+ - instruction must be specific and actionable
1096
+ - Max 3 lessons
1097
+ - confidence 0.8+ for explicit suggestions, 0.6+ for implied improvements`;
1098
+ try {
1099
+ const response = await this.analyzer.analyze(prompt);
1100
+ const parsed = this.parseInstructions(response);
1101
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1102
+ const instructions = [];
1103
+ for (const raw of parsed.slice(0, 3)) {
1104
+ const instruction = await this.saveInstruction(
1105
+ review.reviewee,
1106
+ raw.skill,
1107
+ raw.instruction,
1108
+ review.reviewer,
1109
+ "peer_review",
1110
+ raw.confidence,
1111
+ now
1112
+ );
1113
+ if (instruction) instructions.push(instruction);
1114
+ }
1115
+ return instructions;
1116
+ } catch {
1117
+ return [];
1118
+ }
1119
+ }
1120
+ /**
1121
+ * Get the learning context for an agent — the text block that should be
1122
+ * injected into the agent's system prompt or conversation context.
1123
+ *
1124
+ * THIS is what actually makes the agent smarter.
1125
+ */
1126
+ async getContext(agentId, opts) {
1127
+ validateAgentId(agentId);
1128
+ const max = opts?.maxInstructions ?? 15;
1129
+ const instructions = await this.getInstructions(agentId, max);
1130
+ if (instructions.length === 0) return "";
1131
+ const lines = instructions.map((inst) => {
1132
+ const source = inst.source_context === "peer_review" ? "from a peer review" : inst.source_context === "collaboration" ? "from a collaboration" : inst.source_context === "teaching" ? "from being taught" : "from a conversation";
1133
+ return `- ${inst.instruction} (${source})`;
1134
+ });
1135
+ return `Based on your interactions with other agents, you have learned:
1136
+ ${lines.join("\n")}`;
1137
+ }
1138
+ /**
1139
+ * Get raw learned instructions for an agent, deduplicated by instruction text.
1140
+ */
1141
+ async getInstructions(agentId, limit = 15) {
1142
+ validateAgentId(agentId);
1143
+ const reflections = await this.store.getReflections(agentId, { limit: 500 });
1144
+ const seen = /* @__PURE__ */ new Set();
1145
+ const results = [];
1146
+ for (const r of reflections) {
1147
+ if (!r.skill.startsWith(LEARNED_PREFIX)) continue;
1148
+ if (results.length >= limit) break;
1149
+ const normalizedInstruction = r.reflection.toLowerCase().trim();
1150
+ if (seen.has(normalizedInstruction)) continue;
1151
+ seen.add(normalizedInstruction);
1152
+ const meta = tryParseJSON(r.artifact_id ?? "{}");
1153
+ results.push({
1154
+ id: r.id ?? "",
1155
+ agent_id: r.agent_id,
1156
+ skill: r.skill.slice(LEARNED_PREFIX.length),
1157
+ instruction: r.reflection,
1158
+ learned_from: meta.learned_from ?? "unknown",
1159
+ source_context: meta.source_context ?? "conversation",
1160
+ confidence: meta.confidence ?? 0.5,
1161
+ created_at: r.created_at
1162
+ });
1163
+ }
1164
+ return results;
1165
+ }
1166
+ /**
1167
+ * Get skills this agent has learned from others (distinct skill names).
1168
+ */
1169
+ async getLearnedSkills(agentId) {
1170
+ const instructions = await this.getInstructions(agentId, 100);
1171
+ return [...new Set(instructions.map((i) => i.skill))];
1172
+ }
1173
+ // ── Private ────────────────────────────────────────────────────────────
1174
+ async parseAndSave(response, exchange) {
1175
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
1176
+ if (!jsonMatch) return { agent_a_learned: [], agent_b_learned: [] };
1177
+ try {
1178
+ const parsed = JSON.parse(jsonMatch[0]);
1179
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1180
+ const aRaw = Array.isArray(parsed.a_learned) ? parsed.a_learned : [];
1181
+ const bRaw = Array.isArray(parsed.b_learned) ? parsed.b_learned : [];
1182
+ const aLearned = [];
1183
+ const bLearned = [];
1184
+ for (const raw of aRaw.slice(0, 3)) {
1185
+ const inst = await this.saveInstruction(
1186
+ exchange.agent_a,
1187
+ raw.skill,
1188
+ raw.instruction,
1189
+ exchange.agent_b,
1190
+ exchange.context ?? "conversation",
1191
+ raw.confidence,
1192
+ now
1193
+ );
1194
+ if (inst) aLearned.push(inst);
1195
+ }
1196
+ for (const raw of bRaw.slice(0, 3)) {
1197
+ const inst = await this.saveInstruction(
1198
+ exchange.agent_b,
1199
+ raw.skill,
1200
+ raw.instruction,
1201
+ exchange.agent_a,
1202
+ exchange.context ?? "conversation",
1203
+ raw.confidence,
1204
+ now
1205
+ );
1206
+ if (inst) bLearned.push(inst);
1207
+ }
1208
+ return { agent_a_learned: aLearned, agent_b_learned: bLearned };
1209
+ } catch {
1210
+ return { agent_a_learned: [], agent_b_learned: [] };
1211
+ }
1212
+ }
1213
+ parseInstructions(response) {
1214
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
1215
+ if (!jsonMatch) return [];
1216
+ try {
1217
+ const parsed = JSON.parse(jsonMatch[0]);
1218
+ if (!Array.isArray(parsed)) return [];
1219
+ return parsed.filter(
1220
+ (r) => typeof r.skill === "string" && typeof r.instruction === "string" && typeof r.confidence === "number" && r.confidence >= 0.5
1221
+ );
1222
+ } catch {
1223
+ return [];
1224
+ }
1225
+ }
1226
+ async saveInstruction(agentId, skill, instruction, learnedFrom, sourceContext, confidence, now) {
1227
+ if (typeof skill !== "string" || typeof instruction !== "string") return null;
1228
+ if (confidence < 0.5) return null;
1229
+ const cleanSkill = skill.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_-]/g, "").slice(0, 100);
1230
+ const cleanInstruction = instruction.slice(0, MAX_INSTRUCTION_LENGTH);
1231
+ if (!cleanSkill || !cleanInstruction) return null;
1232
+ const existing = await this.store.getReflections(agentId, { limit: 200 });
1233
+ const normalizedNew = cleanInstruction.toLowerCase().trim();
1234
+ const isDuplicate = existing.some(
1235
+ (r) => r.skill.startsWith(LEARNED_PREFIX) && r.reflection.toLowerCase().trim() === normalizedNew
1236
+ );
1237
+ if (isDuplicate) return null;
1238
+ const skillInstructions = existing.filter(
1239
+ (r) => r.skill === `${LEARNED_PREFIX}${cleanSkill}`
1240
+ );
1241
+ if (skillInstructions.length >= MAX_INSTRUCTIONS_PER_SKILL) return null;
1242
+ const meta = JSON.stringify({
1243
+ learned_from: learnedFrom,
1244
+ source_context: sourceContext,
1245
+ confidence
1246
+ });
1247
+ const saved = await this.store.saveReflection({
1248
+ agent_id: agentId,
1249
+ skill: `${LEARNED_PREFIX}${cleanSkill}`,
1250
+ artifact_id: meta,
1251
+ reflection: cleanInstruction,
1252
+ created_at: now
1253
+ });
1254
+ return {
1255
+ id: saved.id ?? "",
1256
+ agent_id: agentId,
1257
+ skill: cleanSkill,
1258
+ instruction: cleanInstruction,
1259
+ learned_from: learnedFrom,
1260
+ source_context: sourceContext,
1261
+ confidence,
1262
+ created_at: now
1263
+ };
1264
+ }
1265
+ };
1266
+ function sanitize2(text) {
1267
+ return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").replace(/```/g, "'''").slice(0, 2e3);
1268
+ }
1269
+ function tryParseJSON(text) {
1270
+ try {
1271
+ return JSON.parse(text);
1272
+ } catch {
1273
+ return {};
1274
+ }
1275
+ }
1276
+
1017
1277
  // src/social/peer-review.ts
1018
1278
  var MIN_ASSESSMENT_LENGTH = 100;
1019
1279
  var MAX_ASSESSMENT_LENGTH = 1e4;
@@ -1385,7 +1645,7 @@ var NormDetector = class {
1385
1645
  if (activity.length < 5) return [];
1386
1646
  const existingNorms = await this.adapter.getNorms({ limit: 30 });
1387
1647
  const existingTitles = new Set(existingNorms.map((n) => n.title.toLowerCase()));
1388
- const activitySummary = activity.slice(0, 200).map((a) => `[${sanitize2(a.agent_name)}] ${sanitize2(a.action)}: ${sanitize2(truncate2(a.content ?? "", 150))}${a.tags?.length ? ` (tags: ${a.tags.slice(0, 10).map(sanitize2).join(", ")})` : ""}`).join("\n");
1648
+ const activitySummary = activity.slice(0, 200).map((a) => `[${sanitize3(a.agent_name)}] ${sanitize3(a.action)}: ${sanitize3(truncate2(a.content ?? "", 150))}${a.tags?.length ? ` (tags: ${a.tags.slice(0, 10).map(sanitize3).join(", ")})` : ""}`).join("\n");
1389
1649
  const recentTitlesList = [...existingTitles].slice(0, 20).join(", ");
1390
1650
  const prompt = `Analyze these agent activities for emergent cultural norms, shared behaviors, or collective patterns.
1391
1651
 
@@ -1474,7 +1734,7 @@ function truncate2(text, max) {
1474
1734
  if (text.length <= max) return text;
1475
1735
  return text.slice(0, max - 3) + "...";
1476
1736
  }
1477
- function sanitize2(text) {
1737
+ function sanitize3(text) {
1478
1738
  return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").replace(/```/g, "'''");
1479
1739
  }
1480
1740
  function generateId() {
@@ -2629,6 +2889,856 @@ var TrainScheduler = class {
2629
2889
  }
2630
2890
  };
2631
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
+
3470
+ // src/integrations/openclawcity.ts
3471
+ function emptySkillEvidence() {
3472
+ return {
3473
+ artifact_count: 0,
3474
+ artifact_types: /* @__PURE__ */ new Set(),
3475
+ reactions: [],
3476
+ collab_count: 0,
3477
+ peer_reviews_received: 0,
3478
+ peer_reviews_given: 0,
3479
+ teaching_events: 0
3480
+ };
3481
+ }
3482
+ function toScoreInput(ev, globalFollowers) {
3483
+ const totalReactions = ev.reactions.reduce((a, b) => a + b, 0);
3484
+ const recent = ev.reactions.slice(0, 3);
3485
+ const older = ev.reactions.slice(3);
3486
+ return {
3487
+ artifact_count: ev.artifact_count,
3488
+ total_reactions: totalReactions,
3489
+ recent_reaction_avg: recent.length > 0 ? recent.reduce((a, b) => a + b, 0) / recent.length : 0,
3490
+ older_reaction_avg: older.length > 0 ? older.reduce((a, b) => a + b, 0) / older.length : 0,
3491
+ unique_types: ev.artifact_types.size,
3492
+ collab_count: ev.collab_count,
3493
+ peer_reviews_given: ev.peer_reviews_given,
3494
+ peer_reviews_received: ev.peer_reviews_received,
3495
+ follower_count: globalFollowers,
3496
+ teaching_events: ev.teaching_events
3497
+ };
3498
+ }
3499
+ var OBCBridge = class {
3500
+ become;
3501
+ peerReview;
3502
+ teaching;
3503
+ graph;
3504
+ conversation;
3505
+ growth;
3506
+ trends;
3507
+ awareness;
3508
+ agentId;
3509
+ store;
3510
+ skillEvidence = /* @__PURE__ */ new Map();
3511
+ knownSkills = /* @__PURE__ */ new Set();
3512
+ totalArtifacts = 0;
3513
+ allArtifactTypes = [];
3514
+ followerCount = 0;
3515
+ collabsStarted = 0;
3516
+ collabsCompleted = 0;
3517
+ totalQuestCompletions = 0;
3518
+ constructor(config) {
3519
+ validateAgentId(config.agentId);
3520
+ this.agentId = config.agentId;
3521
+ this.store = config.store;
3522
+ this.become = new Become({ store: config.store });
3523
+ this.peerReview = new PeerReviewProtocol(config.store);
3524
+ this.teaching = new TeachingProtocol(config.store);
3525
+ this.graph = new LearningGraph(config.store);
3526
+ this.conversation = new ConversationLearner(config.store);
3527
+ this.growth = new GrowthTracker(config.store);
3528
+ this.trends = new TrendTracker(config.store);
3529
+ this.awareness = new AwarenessIndex();
3530
+ }
3531
+ // ── Tier 1: Every Heartbeat ──────────────────────────────────────────
3532
+ async onHeartbeat(heartbeat) {
3533
+ const signals = [];
3534
+ let skillsSynced = 0;
3535
+ let reactionsProcessed = 0;
3536
+ if (heartbeat.your_skills?.length) {
3537
+ for (const s of heartbeat.your_skills) {
3538
+ if (!this.knownSkills.has(s.skill)) {
3539
+ await this.become.skills.upsert(this.agentId, { name: s.skill });
3540
+ this.knownSkills.add(s.skill);
3541
+ skillsSynced++;
3542
+ }
3543
+ signals.push(`skill:${s.skill}:${s.stage}`);
3544
+ }
3545
+ }
3546
+ if (heartbeat.your_artifact_reactions?.length) {
3547
+ const reactions = heartbeat.your_artifact_reactions;
3548
+ reactionsProcessed = reactions.length;
3549
+ const humanReactions = reactions.filter((r) => r.is_human);
3550
+ if (humanReactions.length > 0) {
3551
+ signals.push(`human_reactions:${humanReactions.length}`);
3552
+ }
3553
+ signals.push(`reactions:${reactions.length}`);
3554
+ }
3555
+ if (heartbeat.owner_messages?.length) {
3556
+ for (const msg of heartbeat.owner_messages) {
3557
+ await this.conversation.afterTurn({
3558
+ agent_id: this.agentId,
3559
+ user_message: msg.message,
3560
+ agent_response: "",
3561
+ context: { active_skills: [...this.knownSkills] }
3562
+ // No feedback — owner message received, agent hasn't responded yet
3563
+ });
3564
+ signals.push("owner_message");
3565
+ }
3566
+ }
3567
+ if (heartbeat.your_completed_quests?.length) {
3568
+ this.totalQuestCompletions = heartbeat.your_completed_quests.length;
3569
+ }
3570
+ const observations = this.become.reflector.observe({
3571
+ agent_id: this.agentId,
3572
+ artifacts: this.allArtifactTypes,
3573
+ collabs_started: this.collabsStarted,
3574
+ collabs_completed: this.collabsCompleted,
3575
+ skills: [...this.knownSkills],
3576
+ quest_completions: this.totalQuestCompletions,
3577
+ follower_count: this.followerCount
3578
+ });
3579
+ return { signals, observations, skills_synced: skillsSynced, reactions_processed: reactionsProcessed };
3580
+ }
3581
+ // ── Tier 2: Agent Actions ────────────────────────────────────────────
3582
+ async onArtifactCreated(artifact) {
3583
+ this.totalArtifacts++;
3584
+ this.allArtifactTypes.push({ type: artifact.type });
3585
+ if (artifact.skill_used) {
3586
+ this.knownSkills.add(artifact.skill_used);
3587
+ const ev = this.getSkillEvidence(artifact.skill_used);
3588
+ ev.artifact_count++;
3589
+ ev.artifact_types.add(artifact.type);
3590
+ ev.reactions.unshift(0);
3591
+ const input = toScoreInput(ev, this.followerCount);
3592
+ const score = computeFullScore(artifact.skill_used, input);
3593
+ await this.store.saveScore(this.agentId, score);
3594
+ await this.become.milestones.check(this.agentId, [score]);
3595
+ return score;
3596
+ }
3597
+ return null;
3598
+ }
3599
+ async onCollaborationCompleted(data) {
3600
+ validateAgentId(data.partner_id);
3601
+ this.collabsCompleted++;
3602
+ const skill = data.skill ?? "collaboration";
3603
+ const ev = this.getSkillEvidence(skill);
3604
+ ev.collab_count++;
3605
+ await this.store.saveLearningEdge({
3606
+ from_agent: data.partner_id,
3607
+ to_agent: this.agentId,
3608
+ skill,
3609
+ event_type: "collaboration",
3610
+ score_delta: 0,
3611
+ metadata: { proposal_type: data.proposal_type },
3612
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3613
+ });
3614
+ }
3615
+ /** Track that a collaboration was proposed (started but not yet completed) */
3616
+ onCollaborationStarted() {
3617
+ this.collabsStarted++;
3618
+ }
3619
+ async onQuestCompleted(questId, skill) {
3620
+ this.totalQuestCompletions++;
3621
+ if (skill) {
3622
+ const ev = this.getSkillEvidence(skill);
3623
+ const input = toScoreInput(ev, this.followerCount);
3624
+ const score = computeFullScore(skill, input);
3625
+ await this.become.milestones.check(this.agentId, [score]);
3626
+ }
3627
+ }
3628
+ async onReflection(skill, text) {
3629
+ await this.become.reflector.reflect(this.agentId, { skill, reflection: text });
3630
+ }
3631
+ async onSkillsRegistered(skills) {
3632
+ for (const skill of skills) {
3633
+ await this.become.skills.upsert(this.agentId, { name: skill });
3634
+ this.knownSkills.add(skill);
3635
+ }
3636
+ }
3637
+ /** Record that an artifact received reactions (call with per-artifact data) */
3638
+ onArtifactReaction(skill, reactionCount) {
3639
+ const ev = this.getSkillEvidence(skill);
3640
+ if (ev.reactions.length > 0) {
3641
+ ev.reactions[0] += reactionCount;
3642
+ }
3643
+ }
3644
+ // ── Tier 3: Peer Interactions ────────────────────────────────────────
3645
+ async onPeerReviewReceived(review) {
3646
+ validateAgentId(review.reviewer_id);
3647
+ const ev = this.getSkillEvidence(review.skill ?? "general");
3648
+ ev.peer_reviews_received++;
3649
+ await this.peerReview.submitReview({
3650
+ reviewer_agent_id: review.reviewer_id,
3651
+ submission_agent_id: this.agentId,
3652
+ submission_id: review.submission_id,
3653
+ skill: review.skill,
3654
+ verdict: review.verdict,
3655
+ overall_assessment: review.assessment,
3656
+ strengths: review.strengths,
3657
+ weaknesses: review.weaknesses,
3658
+ suggestions: review.suggestions
3659
+ });
3660
+ }
3661
+ async onPeerReviewGiven(review) {
3662
+ validateAgentId(review.submission_agent_id);
3663
+ const ev = this.getSkillEvidence(review.skill ?? "general");
3664
+ ev.peer_reviews_given++;
3665
+ await this.peerReview.submitReview({
3666
+ reviewer_agent_id: this.agentId,
3667
+ submission_agent_id: review.submission_agent_id,
3668
+ submission_id: review.submission_id,
3669
+ skill: review.skill,
3670
+ verdict: review.verdict,
3671
+ overall_assessment: review.assessment,
3672
+ strengths: review.strengths,
3673
+ weaknesses: review.weaknesses,
3674
+ suggestions: review.suggestions
3675
+ });
3676
+ }
3677
+ async onTaughtBy(teacherId, skill) {
3678
+ validateAgentId(teacherId);
3679
+ await this.teaching.teach(teacherId, this.agentId, skill);
3680
+ }
3681
+ async onTeaching(studentId, skill) {
3682
+ validateAgentId(studentId);
3683
+ const ev = this.getSkillEvidence(skill);
3684
+ ev.teaching_events++;
3685
+ await this.teaching.teach(this.agentId, studentId, skill);
3686
+ }
3687
+ onNewFollower() {
3688
+ this.followerCount++;
3689
+ }
3690
+ // ── Tier 4: Periodic / Summary ───────────────────────────────────────
3691
+ /** Compute scores for all known skills with per-skill evidence */
3692
+ async computeScores() {
3693
+ const scores = [];
3694
+ for (const skill of this.knownSkills) {
3695
+ const ev = this.getSkillEvidence(skill);
3696
+ const input = toScoreInput(ev, this.followerCount);
3697
+ const score = computeFullScore(skill, input);
3698
+ await this.store.saveScore(this.agentId, score);
3699
+ scores.push(score);
3700
+ }
3701
+ await this.become.milestones.check(this.agentId, scores);
3702
+ return scores;
3703
+ }
3704
+ async snapshot() {
3705
+ return this.growth.snapshot(this.agentId);
3706
+ }
3707
+ async analyzeTrends() {
3708
+ return this.trends.analyze(this.agentId);
3709
+ }
3710
+ async learningNetwork() {
3711
+ const [mentors, students] = await Promise.all([
3712
+ this.graph.topMentors(this.agentId),
3713
+ this.graph.topStudents(this.agentId)
3714
+ ]);
3715
+ return { mentors, students };
3716
+ }
3717
+ /** Get per-skill evidence (for debugging/inspection) */
3718
+ getSkillEvidence(skill) {
3719
+ let ev = this.skillEvidence.get(skill);
3720
+ if (!ev) {
3721
+ ev = emptySkillEvidence();
3722
+ this.skillEvidence.set(skill, ev);
3723
+ }
3724
+ return ev;
3725
+ }
3726
+ /** Get global stats */
3727
+ getStats() {
3728
+ return {
3729
+ total_artifacts: this.totalArtifacts,
3730
+ follower_count: this.followerCount,
3731
+ collabs_started: this.collabsStarted,
3732
+ collabs_completed: this.collabsCompleted,
3733
+ quest_completions: this.totalQuestCompletions,
3734
+ skills_count: this.knownSkills.size
3735
+ };
3736
+ }
3737
+ getSkills() {
3738
+ return [...this.knownSkills];
3739
+ }
3740
+ };
3741
+
2632
3742
  // src/index.ts
2633
3743
  var Become = class {
2634
3744
  skills;
@@ -2642,6 +3752,7 @@ var Become = class {
2642
3752
  }
2643
3753
  };
2644
3754
  export {
3755
+ AgentLearningEngine,
2645
3756
  AnthropicAdapter,
2646
3757
  AwarenessIndex,
2647
3758
  BLOOMS_ORDER,
@@ -2649,11 +3760,14 @@ export {
2649
3760
  Become,
2650
3761
  ConversationLearner,
2651
3762
  DREYFUS_THRESHOLDS,
3763
+ FileSkillStore,
2652
3764
  GrowthTracker,
2653
3765
  LearningGraph,
3766
+ LessonExtractor,
2654
3767
  MemoryStore,
2655
3768
  MilestoneDetector,
2656
3769
  NormDetector,
3770
+ OBCBridge,
2657
3771
  OllamaAdapter,
2658
3772
  OpenAIAdapter,
2659
3773
  PeerReviewProtocol,
@@ -2665,11 +3779,14 @@ export {
2665
3779
  TeachingProtocol,
2666
3780
  TrainScheduler,
2667
3781
  TrendTracker,
3782
+ TrustManager,
2668
3783
  WEIGHTS,
2669
3784
  checkGate,
2670
3785
  computeFullScore,
2671
3786
  computeScore,
3787
+ createProxyServer,
2672
3788
  datasetStats,
3789
+ detectAgentConversation,
2673
3790
  detectBloomsLevel,
2674
3791
  detectCollaborationGap,
2675
3792
  detectCollectiveMemory,
@@ -2682,9 +3799,12 @@ export {
2682
3799
  detectSoloCreator,
2683
3800
  detectSymbolicVocabulary,
2684
3801
  dreyfusStage,
3802
+ extractExchangeText,
2685
3803
  filterHighQuality,
3804
+ formatSkillsForInjection,
2686
3805
  getReputationLevel,
2687
3806
  importSkillDirectory,
3807
+ injectSkillsIntoMessages,
2688
3808
  nextMilestone,
2689
3809
  normalizeCategory,
2690
3810
  parseSkillFile,