@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/README.md +146 -107
- package/dist/cli.cjs +1550 -63
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +8 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +1536 -65
- package/dist/cli.js.map +1 -1
- package/dist/dashboard.d.cts +1 -1
- package/dist/dashboard.d.ts +1 -1
- package/dist/index.cjs +594 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -3
- package/dist/index.d.ts +160 -3
- package/dist/index.js +586 -0
- package/dist/index.js.map +1 -1
- package/dist/{types-DzOc15AL.d.cts → types-BnbaKMTo.d.cts} +1 -1
- package/dist/{types-DzOc15AL.d.ts → types-BnbaKMTo.d.ts} +1 -1
- package/package.json +4 -1
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,
|