@mclawnet/agent 0.6.11 → 0.6.13
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/__tests__/skill-sync.test.d.ts +2 -0
- package/dist/__tests__/skill-sync.test.d.ts.map +1 -0
- package/dist/{chunk-HQMVKDYY.js → chunk-MSDIRBXF.js} +402 -142
- package/dist/chunk-MSDIRBXF.js.map +1 -0
- package/dist/hub-connection.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/skill-loader.d.ts +22 -3
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +1 -1
- package/package.json +7 -5
- package/dist/chunk-HQMVKDYY.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-sync.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/skill-sync.test.ts"],"names":[],"mappings":""}
|
|
@@ -2,6 +2,10 @@ import {
|
|
|
2
2
|
loadConfig
|
|
3
3
|
} from "./chunk-CBZIH6FY.js";
|
|
4
4
|
|
|
5
|
+
// src/start.ts
|
|
6
|
+
import { homedir as homedir3 } from "os";
|
|
7
|
+
import { join as join4 } from "path";
|
|
8
|
+
|
|
5
9
|
// src/hub-connection.ts
|
|
6
10
|
import { hostname as osHostname } from "os";
|
|
7
11
|
import WebSocket from "ws";
|
|
@@ -248,7 +252,7 @@ async function handleLoadSessionHistory(workDir, claudeSessionId, opts) {
|
|
|
248
252
|
}
|
|
249
253
|
|
|
250
254
|
// src/hub-connection.ts
|
|
251
|
-
import { createLogger } from "@mclawnet/logger";
|
|
255
|
+
import { createLogger, previewFields } from "@mclawnet/logger";
|
|
252
256
|
var log = createLogger({ module: "agent" });
|
|
253
257
|
var HubConnection = class {
|
|
254
258
|
ws = null;
|
|
@@ -610,6 +614,7 @@ var HubConnection = class {
|
|
|
610
614
|
const sm = this.sessionManager;
|
|
611
615
|
const spawnAndSend = (resumeId, label) => {
|
|
612
616
|
log.info({ sessionId, claudeSessionId: resumeId, workDir, label }, "claude.execute: spawning");
|
|
617
|
+
log.debug({ sessionId, ...previewFields(content) }, "claude.execute: input");
|
|
613
618
|
sm.createSession({ sessionId, workDir, resumeId, useBrainCore, roleId: "role-__assistant__" }).then(() => {
|
|
614
619
|
sm.sendInput(sessionId, content);
|
|
615
620
|
}).catch((err) => {
|
|
@@ -622,6 +627,7 @@ var HubConnection = class {
|
|
|
622
627
|
};
|
|
623
628
|
if (sm.isHealthy(sessionId)) {
|
|
624
629
|
log.info({ sessionId }, "claude.execute: reusing healthy session");
|
|
630
|
+
log.debug({ sessionId, ...previewFields(content) }, "claude.execute: input");
|
|
625
631
|
sm.sendInput(sessionId, content);
|
|
626
632
|
} else if (sm.hasSession(sessionId) && claudeSessionId) {
|
|
627
633
|
log.warn({ sessionId, claudeSessionId }, "claude.execute: session unhealthy, recreating with --resume");
|
|
@@ -689,7 +695,10 @@ var HubConnection = class {
|
|
|
689
695
|
return true;
|
|
690
696
|
}
|
|
691
697
|
if (msg.type === "claude.input") {
|
|
692
|
-
log.
|
|
698
|
+
log.debug(
|
|
699
|
+
{ sessionId: msg.sessionId, ...previewFields(msg.content) },
|
|
700
|
+
"claude.input"
|
|
701
|
+
);
|
|
693
702
|
this.sessionManager.sendInput(msg.sessionId, msg.content);
|
|
694
703
|
return true;
|
|
695
704
|
}
|
|
@@ -778,9 +787,299 @@ var HubConnection = class {
|
|
|
778
787
|
};
|
|
779
788
|
|
|
780
789
|
// src/session-manager.ts
|
|
781
|
-
import { createLogger as
|
|
790
|
+
import { createLogger as createLogger3, previewFields as previewFields2 } from "@mclawnet/logger";
|
|
782
791
|
import { buildMemorySection } from "@mclawnet/memory";
|
|
783
|
-
|
|
792
|
+
|
|
793
|
+
// src/skill-loader.ts
|
|
794
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
795
|
+
import { createHash } from "crypto";
|
|
796
|
+
import { join as join2, dirname } from "path";
|
|
797
|
+
import { homedir as homedir2 } from "os";
|
|
798
|
+
import { createRequire } from "module";
|
|
799
|
+
import { fileURLToPath } from "url";
|
|
800
|
+
import { createLogger as createLogger2 } from "@mclawnet/logger";
|
|
801
|
+
import { ManifestManager, mergeSkillSections } from "@mclawnet/skill-manager";
|
|
802
|
+
var log2 = createLogger2({ module: "agent/skill-loader" });
|
|
803
|
+
var CLAWNET_DIR = join2(homedir2(), ".clawnet");
|
|
804
|
+
var SKILLS_DIR = join2(CLAWNET_DIR, ".claude", "skills");
|
|
805
|
+
var MCP_CONFIG_PATH = join2(CLAWNET_DIR, "mcp.json");
|
|
806
|
+
async function initSkills() {
|
|
807
|
+
ensureSkillsDir();
|
|
808
|
+
syncBuiltinSkills(CLAWNET_DIR, defaultBuiltinSourceDir());
|
|
809
|
+
ensureMcpConfig();
|
|
810
|
+
scanSkills();
|
|
811
|
+
}
|
|
812
|
+
function defaultBuiltinSourceDir() {
|
|
813
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
814
|
+
return join2(dirname(thisFile), "..", "skills");
|
|
815
|
+
}
|
|
816
|
+
function sha256(s) {
|
|
817
|
+
return createHash("sha256").update(s).digest("hex");
|
|
818
|
+
}
|
|
819
|
+
function readBuiltinVersion(content) {
|
|
820
|
+
const match = content.match(/^version:\s*(.+)$/m);
|
|
821
|
+
return match ? match[1].trim().replace(/^["']|["']$/g, "") : "1.0.0";
|
|
822
|
+
}
|
|
823
|
+
function syncBuiltinSkills(rootDir, srcDir) {
|
|
824
|
+
const skillsDir = join2(rootDir, ".claude", "skills");
|
|
825
|
+
if (!existsSync2(skillsDir)) mkdirSync(skillsDir, { recursive: true });
|
|
826
|
+
if (!existsSync2(srcDir)) {
|
|
827
|
+
log2.debug({ srcDir }, "no built-in skills directory found, skipping");
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const manifest = new ManifestManager(rootDir);
|
|
831
|
+
let entries;
|
|
832
|
+
try {
|
|
833
|
+
entries = readdirSync2(srcDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
834
|
+
} catch {
|
|
835
|
+
log2.warn({ srcDir }, "failed to read built-in skills directory");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
for (const skillName of entries) {
|
|
839
|
+
const srcSkillMd = join2(srcDir, skillName, "SKILL.md");
|
|
840
|
+
if (!existsSync2(srcSkillMd)) continue;
|
|
841
|
+
const destDir = join2(skillsDir, skillName);
|
|
842
|
+
const destSkillMd = join2(destDir, "SKILL.md");
|
|
843
|
+
const baseSnapshotPath = join2(destDir, ".base.md");
|
|
844
|
+
let officialContent;
|
|
845
|
+
try {
|
|
846
|
+
officialContent = readFileSync2(srcSkillMd, "utf-8");
|
|
847
|
+
} catch (err) {
|
|
848
|
+
log2.warn({ skill: skillName, err }, "failed to read built-in skill");
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
const officialHash = sha256(officialContent);
|
|
852
|
+
const officialVersion = readBuiltinVersion(officialContent);
|
|
853
|
+
if (!existsSync2(destSkillMd)) {
|
|
854
|
+
try {
|
|
855
|
+
mkdirSync(destDir, { recursive: true });
|
|
856
|
+
writeFileSync(destSkillMd, officialContent);
|
|
857
|
+
writeFileSync(baseSnapshotPath, officialContent);
|
|
858
|
+
manifest.register(skillName, officialHash, officialVersion);
|
|
859
|
+
log2.info({ skill: skillName, version: officialVersion }, "installed built-in skill");
|
|
860
|
+
} catch (err) {
|
|
861
|
+
log2.warn({ skill: skillName, err }, "failed to install built-in skill");
|
|
862
|
+
}
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
const userContent = readFileSync2(destSkillMd, "utf-8");
|
|
866
|
+
const userHash = sha256(userContent);
|
|
867
|
+
manifest.refresh(skillName, userHash);
|
|
868
|
+
if (!existsSync2(baseSnapshotPath)) {
|
|
869
|
+
const entry = manifest.load().skills[skillName];
|
|
870
|
+
if (entry && !entry.userModified) {
|
|
871
|
+
try {
|
|
872
|
+
writeFileSync(baseSnapshotPath, userContent);
|
|
873
|
+
} catch (err) {
|
|
874
|
+
log2.debug({ skill: skillName, err }, "failed to write lazy .base.md");
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const action = manifest.determineSyncAction(skillName, officialHash, officialVersion);
|
|
879
|
+
switch (action) {
|
|
880
|
+
case "skip":
|
|
881
|
+
log2.debug({ skill: skillName }, "skill already up to date");
|
|
882
|
+
break;
|
|
883
|
+
case "direct-overwrite":
|
|
884
|
+
try {
|
|
885
|
+
writeFileSync(destSkillMd, officialContent);
|
|
886
|
+
writeFileSync(baseSnapshotPath, officialContent);
|
|
887
|
+
manifest.markSynced(skillName, officialHash, officialHash, officialVersion);
|
|
888
|
+
log2.info({ skill: skillName, version: officialVersion }, "upgraded built-in skill");
|
|
889
|
+
} catch (err) {
|
|
890
|
+
log2.warn({ skill: skillName, err }, "failed to overwrite skill");
|
|
891
|
+
}
|
|
892
|
+
break;
|
|
893
|
+
case "keep-user":
|
|
894
|
+
log2.debug({ skill: skillName }, "keeping user-modified skill");
|
|
895
|
+
break;
|
|
896
|
+
case "needs-merge": {
|
|
897
|
+
if (!existsSync2(baseSnapshotPath)) {
|
|
898
|
+
try {
|
|
899
|
+
writeFileSync(
|
|
900
|
+
destSkillMd + ".conflict",
|
|
901
|
+
renderConflictFile(skillName, officialContent, [
|
|
902
|
+
{
|
|
903
|
+
section: "(entire file)",
|
|
904
|
+
official: officialContent,
|
|
905
|
+
user: userContent
|
|
906
|
+
}
|
|
907
|
+
])
|
|
908
|
+
);
|
|
909
|
+
log2.warn(
|
|
910
|
+
{ skill: skillName },
|
|
911
|
+
"missing .base.md snapshot \u2014 skipping auto-merge, wrote .conflict advisory"
|
|
912
|
+
);
|
|
913
|
+
} catch (err) {
|
|
914
|
+
log2.warn({ skill: skillName, err }, "failed to write .conflict advisory");
|
|
915
|
+
}
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
const baseContent = readFileSync2(baseSnapshotPath, "utf-8");
|
|
919
|
+
const merged = mergeSkillSections(baseContent, officialContent, userContent);
|
|
920
|
+
if (merged.success && merged.content) {
|
|
921
|
+
try {
|
|
922
|
+
writeFileSync(destSkillMd, merged.content);
|
|
923
|
+
writeFileSync(baseSnapshotPath, officialContent);
|
|
924
|
+
const newCurrentHash = sha256(merged.content);
|
|
925
|
+
manifest.markSynced(skillName, officialHash, newCurrentHash, officialVersion);
|
|
926
|
+
log2.info({ skill: skillName }, "auto-merged skill upgrade");
|
|
927
|
+
} catch (err) {
|
|
928
|
+
log2.warn({ skill: skillName, err }, "failed to write merged skill");
|
|
929
|
+
}
|
|
930
|
+
} else {
|
|
931
|
+
try {
|
|
932
|
+
writeFileSync(
|
|
933
|
+
destSkillMd + ".conflict",
|
|
934
|
+
renderConflictFile(skillName, officialContent, merged.conflicts ?? [])
|
|
935
|
+
);
|
|
936
|
+
log2.warn(
|
|
937
|
+
{ skill: skillName, conflicts: merged.conflicts?.length },
|
|
938
|
+
"skill merge conflict \u2014 user version kept, .conflict file written"
|
|
939
|
+
);
|
|
940
|
+
} catch (err) {
|
|
941
|
+
log2.warn({ skill: skillName, err }, "failed to write .conflict file");
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function renderConflictFile(skillName, officialContent, conflicts) {
|
|
950
|
+
const lines = [
|
|
951
|
+
`# Skill upgrade conflict: ${skillName}`,
|
|
952
|
+
``,
|
|
953
|
+
`The official skill was updated, and your local copy was also modified.`,
|
|
954
|
+
`Please review each section below and reconcile manually.`,
|
|
955
|
+
``,
|
|
956
|
+
`## Conflicted sections`,
|
|
957
|
+
``
|
|
958
|
+
];
|
|
959
|
+
for (const c of conflicts) {
|
|
960
|
+
lines.push(`### ${c.section}`);
|
|
961
|
+
lines.push("");
|
|
962
|
+
lines.push("**Official:**");
|
|
963
|
+
lines.push("```");
|
|
964
|
+
lines.push(c.official);
|
|
965
|
+
lines.push("```");
|
|
966
|
+
lines.push("");
|
|
967
|
+
lines.push("**Your version:**");
|
|
968
|
+
lines.push("```");
|
|
969
|
+
lines.push(c.user);
|
|
970
|
+
lines.push("```");
|
|
971
|
+
lines.push("");
|
|
972
|
+
}
|
|
973
|
+
lines.push("## Full official content");
|
|
974
|
+
lines.push("```");
|
|
975
|
+
lines.push(officialContent);
|
|
976
|
+
lines.push("```");
|
|
977
|
+
return lines.join("\n");
|
|
978
|
+
}
|
|
979
|
+
function ensureSkillsDir() {
|
|
980
|
+
if (!existsSync2(SKILLS_DIR)) {
|
|
981
|
+
mkdirSync(SKILLS_DIR, { recursive: true });
|
|
982
|
+
log2.info({ dir: SKILLS_DIR }, "created skills directory");
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
var cachedSkills = [];
|
|
986
|
+
function scanSkills() {
|
|
987
|
+
if (!existsSync2(SKILLS_DIR)) return [];
|
|
988
|
+
let dirs;
|
|
989
|
+
try {
|
|
990
|
+
dirs = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
991
|
+
} catch {
|
|
992
|
+
log2.warn("failed to read skills directory for scanning");
|
|
993
|
+
return [];
|
|
994
|
+
}
|
|
995
|
+
const skills = [];
|
|
996
|
+
for (const dirName of dirs) {
|
|
997
|
+
const skillMd = join2(SKILLS_DIR, dirName, "SKILL.md");
|
|
998
|
+
if (!existsSync2(skillMd)) continue;
|
|
999
|
+
try {
|
|
1000
|
+
const content = readFileSync2(skillMd, "utf-8");
|
|
1001
|
+
const parsed = parseFrontmatter(content);
|
|
1002
|
+
if (parsed.name) {
|
|
1003
|
+
skills.push({
|
|
1004
|
+
name: parsed.name,
|
|
1005
|
+
description: parsed.description || dirName
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
log2.debug({ skill: dirName, err }, "failed to parse SKILL.md frontmatter");
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
cachedSkills = skills;
|
|
1013
|
+
log2.info({ count: skills.length }, "scanned skills");
|
|
1014
|
+
return skills;
|
|
1015
|
+
}
|
|
1016
|
+
function getSkillList() {
|
|
1017
|
+
return cachedSkills;
|
|
1018
|
+
}
|
|
1019
|
+
function getPendingNotification() {
|
|
1020
|
+
const pendingPath = join2(CLAWNET_DIR, ".claude", "pending-evolutions.json");
|
|
1021
|
+
if (!existsSync2(pendingPath)) return null;
|
|
1022
|
+
try {
|
|
1023
|
+
const raw = JSON.parse(readFileSync2(pendingPath, "utf-8"));
|
|
1024
|
+
const pending = Array.isArray(raw?.pending) ? raw.pending : [];
|
|
1025
|
+
if (pending.length === 0) return null;
|
|
1026
|
+
const lines = pending.map(
|
|
1027
|
+
(p) => `- [${p.signal ?? "?"}] ${p.skillName ?? "?"}: ${p.problem ?? ""} (confidence: ${p.confidence ?? "?"})`
|
|
1028
|
+
);
|
|
1029
|
+
const text = `\u6709 ${pending.length} \u6761 Skill \u8FDB\u5316\u63D0\u6848\u5F85\u5BA1\u6838\uFF1A
|
|
1030
|
+
${lines.join("\n")}
|
|
1031
|
+
\u4F7F\u7528 skill_pending_list \u67E5\u770B\u8BE6\u60C5\uFF0Cskill_pending_approve / skill_pending_reject \u5904\u7406\u3002`;
|
|
1032
|
+
return { count: pending.length, text };
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
log2.debug({ err }, "failed to read pending-evolutions.json");
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function parseFrontmatter(content) {
|
|
1039
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1040
|
+
if (!match) return { name: "", description: "" };
|
|
1041
|
+
const yaml = match[1];
|
|
1042
|
+
const nameMatch = yaml.match(/^name:\s*(.+)$/m);
|
|
1043
|
+
const descMatch = yaml.match(/^description:\s*(.+)$/m);
|
|
1044
|
+
const strip = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
1045
|
+
return {
|
|
1046
|
+
name: strip(nameMatch?.[1] ?? ""),
|
|
1047
|
+
description: strip(descMatch?.[1] ?? "")
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
function ensureMcpConfig() {
|
|
1051
|
+
if (existsSync2(MCP_CONFIG_PATH)) {
|
|
1052
|
+
log2.debug("mcp.json already exists, skipping");
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
let mcpServerPath;
|
|
1056
|
+
try {
|
|
1057
|
+
const req = createRequire(import.meta.url);
|
|
1058
|
+
const mcpPkgDir = dirname(req.resolve("@mclawnet/mcp-server/package.json"));
|
|
1059
|
+
mcpServerPath = join2(mcpPkgDir, "dist", "server.js");
|
|
1060
|
+
} catch {
|
|
1061
|
+
log2.warn("could not resolve @mclawnet/mcp-server package path, skipping mcp.json generation");
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const config = {
|
|
1065
|
+
mcpServers: {
|
|
1066
|
+
"clawnet-mcp": {
|
|
1067
|
+
command: "node",
|
|
1068
|
+
args: [mcpServerPath]
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
try {
|
|
1073
|
+
mkdirSync(CLAWNET_DIR, { recursive: true });
|
|
1074
|
+
writeFileSync(MCP_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
|
|
1075
|
+
log2.info({ path: MCP_CONFIG_PATH }, "generated default mcp.json");
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
log2.warn({ err }, "failed to generate mcp.json");
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/session-manager.ts
|
|
1082
|
+
var log3 = createLogger3({ module: "agent/session-manager" });
|
|
784
1083
|
var DEFAULT_MAX_PROCESSES = 10;
|
|
785
1084
|
var MAX_PROCESSES = Number(process.env.CLAWNET_MAX_PROCESSES) || DEFAULT_MAX_PROCESSES;
|
|
786
1085
|
var SessionManager = class {
|
|
@@ -821,10 +1120,26 @@ var SessionManager = class {
|
|
|
821
1120
|
options.systemPrompt = options.systemPrompt ? `${memorySection}${roleHint}
|
|
822
1121
|
|
|
823
1122
|
${options.systemPrompt}` : `${memorySection}${roleHint}`;
|
|
824
|
-
|
|
1123
|
+
log3.debug({ roleId: options.roleId, sessionId: options.sessionId }, "memory prompt + roleId hint injected");
|
|
825
1124
|
} catch (err) {
|
|
826
|
-
|
|
1125
|
+
log3.warn({ err, roleId: options.roleId }, "failed to build memory section, proceeding without");
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
try {
|
|
1129
|
+
const notice = getPendingNotification();
|
|
1130
|
+
if (notice) {
|
|
1131
|
+
const noticeBlock = `
|
|
1132
|
+
|
|
1133
|
+
[Skill \u8FDB\u5316\u63D0\u6848]
|
|
1134
|
+
${notice.text}`;
|
|
1135
|
+
options.systemPrompt = options.systemPrompt ? `${options.systemPrompt}${noticeBlock}` : noticeBlock.trimStart();
|
|
1136
|
+
log3.debug(
|
|
1137
|
+
{ sessionId: options.sessionId, pending: notice.count },
|
|
1138
|
+
"pending skill evolution notice injected"
|
|
1139
|
+
);
|
|
827
1140
|
}
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
log3.debug({ err }, "failed to inject pending notification");
|
|
828
1143
|
}
|
|
829
1144
|
try {
|
|
830
1145
|
const process2 = await this.adapter.spawn(options);
|
|
@@ -850,7 +1165,7 @@ ${options.systemPrompt}` : `${memorySection}${roleHint}`;
|
|
|
850
1165
|
if (this.onSessionStarted) {
|
|
851
1166
|
this.adapter.onSessionStarted?.(process2, (info) => {
|
|
852
1167
|
if (this.aborting.has(options.sessionId)) {
|
|
853
|
-
|
|
1168
|
+
log3.debug(
|
|
854
1169
|
{ sessionId: options.sessionId },
|
|
855
1170
|
"suppressing late session_started \u2014 session is aborting"
|
|
856
1171
|
);
|
|
@@ -866,7 +1181,7 @@ ${options.systemPrompt}` : `${memorySection}${roleHint}`;
|
|
|
866
1181
|
if (this.sessions.get(options.sessionId) === process2) {
|
|
867
1182
|
this.sessions.delete(options.sessionId);
|
|
868
1183
|
this.conversationBuffer.delete(options.sessionId);
|
|
869
|
-
|
|
1184
|
+
log3.warn({ sessionId: options.sessionId, exitCode: code }, "backend process exited unexpectedly, evicted from session map");
|
|
870
1185
|
this.onSessionError(options.sessionId, `backend process exited (code=${code ?? "null"})`);
|
|
871
1186
|
}
|
|
872
1187
|
});
|
|
@@ -883,6 +1198,10 @@ ${options.systemPrompt}` : `${memorySection}${roleHint}`;
|
|
|
883
1198
|
this.onSessionError(sessionId, `No active session: ${sessionId}`);
|
|
884
1199
|
return;
|
|
885
1200
|
}
|
|
1201
|
+
log3.debug(
|
|
1202
|
+
{ sessionId, ...previewFields2(input) },
|
|
1203
|
+
"sendInput \u2192 backend stdin"
|
|
1204
|
+
);
|
|
886
1205
|
this.adapter.send(process2, input);
|
|
887
1206
|
}
|
|
888
1207
|
async abortSession(sessionId) {
|
|
@@ -950,138 +1269,6 @@ ${options.systemPrompt}` : `${memorySection}${roleHint}`;
|
|
|
950
1269
|
// src/start.ts
|
|
951
1270
|
import { SwarmCoordinator, initRoles } from "@mclawnet/swarm";
|
|
952
1271
|
|
|
953
|
-
// src/skill-loader.ts
|
|
954
|
-
import { existsSync as existsSync2, mkdirSync, copyFileSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
955
|
-
import { join as join2, dirname } from "path";
|
|
956
|
-
import { homedir as homedir2 } from "os";
|
|
957
|
-
import { createRequire } from "module";
|
|
958
|
-
import { fileURLToPath } from "url";
|
|
959
|
-
import { createLogger as createLogger3 } from "@mclawnet/logger";
|
|
960
|
-
var log3 = createLogger3({ module: "agent/skill-loader" });
|
|
961
|
-
var CLAWNET_DIR = join2(homedir2(), ".clawnet");
|
|
962
|
-
var SKILLS_DIR = join2(CLAWNET_DIR, ".claude", "skills");
|
|
963
|
-
var MCP_CONFIG_PATH = join2(CLAWNET_DIR, "mcp.json");
|
|
964
|
-
async function initSkills() {
|
|
965
|
-
ensureSkillsDir();
|
|
966
|
-
copyBuiltinSkills();
|
|
967
|
-
ensureMcpConfig();
|
|
968
|
-
scanSkills();
|
|
969
|
-
}
|
|
970
|
-
function ensureSkillsDir() {
|
|
971
|
-
if (!existsSync2(SKILLS_DIR)) {
|
|
972
|
-
mkdirSync(SKILLS_DIR, { recursive: true });
|
|
973
|
-
log3.info({ dir: SKILLS_DIR }, "created skills directory");
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
function copyBuiltinSkills() {
|
|
977
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
978
|
-
const srcDir = join2(dirname(thisFile), "..", "skills");
|
|
979
|
-
if (!existsSync2(srcDir)) {
|
|
980
|
-
log3.debug({ srcDir }, "no built-in skills directory found, skipping");
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
let entries;
|
|
984
|
-
try {
|
|
985
|
-
entries = readdirSync2(srcDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
986
|
-
} catch {
|
|
987
|
-
log3.warn({ srcDir }, "failed to read built-in skills directory");
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
for (const skillName of entries) {
|
|
991
|
-
const srcSkillMd = join2(srcDir, skillName, "SKILL.md");
|
|
992
|
-
const destDir = join2(SKILLS_DIR, skillName);
|
|
993
|
-
const destSkillMd = join2(destDir, "SKILL.md");
|
|
994
|
-
if (!existsSync2(srcSkillMd)) continue;
|
|
995
|
-
if (existsSync2(destSkillMd)) {
|
|
996
|
-
log3.debug({ skill: skillName }, "skill already exists, skipping");
|
|
997
|
-
continue;
|
|
998
|
-
}
|
|
999
|
-
try {
|
|
1000
|
-
mkdirSync(destDir, { recursive: true });
|
|
1001
|
-
copyFileSync(srcSkillMd, destSkillMd);
|
|
1002
|
-
log3.info({ skill: skillName }, "copied built-in skill");
|
|
1003
|
-
} catch (err) {
|
|
1004
|
-
log3.warn({ skill: skillName, err }, "failed to copy built-in skill");
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
var cachedSkills = [];
|
|
1009
|
-
function scanSkills() {
|
|
1010
|
-
if (!existsSync2(SKILLS_DIR)) return [];
|
|
1011
|
-
let dirs;
|
|
1012
|
-
try {
|
|
1013
|
-
dirs = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1014
|
-
} catch {
|
|
1015
|
-
log3.warn("failed to read skills directory for scanning");
|
|
1016
|
-
return [];
|
|
1017
|
-
}
|
|
1018
|
-
const skills = [];
|
|
1019
|
-
for (const dirName of dirs) {
|
|
1020
|
-
const skillMd = join2(SKILLS_DIR, dirName, "SKILL.md");
|
|
1021
|
-
if (!existsSync2(skillMd)) continue;
|
|
1022
|
-
try {
|
|
1023
|
-
const content = readFileSync2(skillMd, "utf-8");
|
|
1024
|
-
const parsed = parseFrontmatter(content);
|
|
1025
|
-
if (parsed.name) {
|
|
1026
|
-
skills.push({
|
|
1027
|
-
name: parsed.name,
|
|
1028
|
-
description: parsed.description || dirName
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
} catch (err) {
|
|
1032
|
-
log3.debug({ skill: dirName, err }, "failed to parse SKILL.md frontmatter");
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
cachedSkills = skills;
|
|
1036
|
-
log3.info({ count: skills.length }, "scanned skills");
|
|
1037
|
-
return skills;
|
|
1038
|
-
}
|
|
1039
|
-
function getSkillList() {
|
|
1040
|
-
return cachedSkills;
|
|
1041
|
-
}
|
|
1042
|
-
function parseFrontmatter(content) {
|
|
1043
|
-
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1044
|
-
if (!match) return { name: "", description: "" };
|
|
1045
|
-
const yaml = match[1];
|
|
1046
|
-
const nameMatch = yaml.match(/^name:\s*(.+)$/m);
|
|
1047
|
-
const descMatch = yaml.match(/^description:\s*(.+)$/m);
|
|
1048
|
-
const strip = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
1049
|
-
return {
|
|
1050
|
-
name: strip(nameMatch?.[1] ?? ""),
|
|
1051
|
-
description: strip(descMatch?.[1] ?? "")
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
function ensureMcpConfig() {
|
|
1055
|
-
if (existsSync2(MCP_CONFIG_PATH)) {
|
|
1056
|
-
log3.debug("mcp.json already exists, skipping");
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
let mcpServerPath;
|
|
1060
|
-
try {
|
|
1061
|
-
const req = createRequire(import.meta.url);
|
|
1062
|
-
const memoryPkgDir = dirname(req.resolve("@mclawnet/memory/package.json"));
|
|
1063
|
-
mcpServerPath = join2(memoryPkgDir, "dist", "mcp", "server.js");
|
|
1064
|
-
} catch {
|
|
1065
|
-
log3.warn("could not resolve @mclawnet/memory package path, skipping mcp.json generation");
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
const config = {
|
|
1069
|
-
mcpServers: {
|
|
1070
|
-
"clawnet-memory": {
|
|
1071
|
-
command: "node",
|
|
1072
|
-
args: [mcpServerPath]
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
try {
|
|
1077
|
-
mkdirSync(CLAWNET_DIR, { recursive: true });
|
|
1078
|
-
writeFileSync(MCP_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
|
|
1079
|
-
log3.info({ path: MCP_CONFIG_PATH }, "generated default mcp.json");
|
|
1080
|
-
} catch (err) {
|
|
1081
|
-
log3.warn({ err }, "failed to generate mcp.json");
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
1272
|
// src/brain-bridge.ts
|
|
1086
1273
|
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
1087
1274
|
import { join as join3 } from "path";
|
|
@@ -1465,6 +1652,19 @@ var FsBridge = class {
|
|
|
1465
1652
|
|
|
1466
1653
|
// src/start.ts
|
|
1467
1654
|
import { createLogger as createLogger6 } from "@mclawnet/logger";
|
|
1655
|
+
import {
|
|
1656
|
+
initDatabase,
|
|
1657
|
+
MemoryStore,
|
|
1658
|
+
EmbeddingService,
|
|
1659
|
+
createEmbeddingProviders,
|
|
1660
|
+
distillConversation
|
|
1661
|
+
} from "@mclawnet/memory";
|
|
1662
|
+
import {
|
|
1663
|
+
SkillStore,
|
|
1664
|
+
EvolutionPipeline,
|
|
1665
|
+
AccumulationScanner,
|
|
1666
|
+
triggerFromAccumulation
|
|
1667
|
+
} from "@mclawnet/skill-manager";
|
|
1468
1668
|
var log6 = createLogger6({ module: "agent" });
|
|
1469
1669
|
async function startAgent(options) {
|
|
1470
1670
|
const config = loadConfig(options.config);
|
|
@@ -1496,6 +1696,58 @@ async function startAgent(options) {
|
|
|
1496
1696
|
}
|
|
1497
1697
|
});
|
|
1498
1698
|
let swarmCoordinator;
|
|
1699
|
+
const clawnetDir = process.env.CLAWNET_DIR ?? join4(homedir3(), ".clawnet");
|
|
1700
|
+
const dbPath = process.env.CLAWNET_MEMORY_DB ?? join4(clawnetDir, "memory.db");
|
|
1701
|
+
let memoryStore = null;
|
|
1702
|
+
let embeddingService = null;
|
|
1703
|
+
let evolutionPipeline = null;
|
|
1704
|
+
let skillStore = null;
|
|
1705
|
+
let memoryDb = null;
|
|
1706
|
+
try {
|
|
1707
|
+
memoryDb = initDatabase(dbPath);
|
|
1708
|
+
memoryStore = new MemoryStore(memoryDb);
|
|
1709
|
+
embeddingService = new EmbeddingService(memoryDb, createEmbeddingProviders());
|
|
1710
|
+
skillStore = new SkillStore(clawnetDir);
|
|
1711
|
+
evolutionPipeline = new EvolutionPipeline(clawnetDir);
|
|
1712
|
+
} catch (err) {
|
|
1713
|
+
log6.warn({ err }, "failed to init memory/skill infra; distillation disabled");
|
|
1714
|
+
}
|
|
1715
|
+
const onBeforeClose = async (sessionId, messages) => {
|
|
1716
|
+
if (messages.length === 0) return;
|
|
1717
|
+
if (!memoryStore || !embeddingService) return;
|
|
1718
|
+
try {
|
|
1719
|
+
await distillConversation(
|
|
1720
|
+
messages,
|
|
1721
|
+
"role-__assistant__",
|
|
1722
|
+
memoryStore,
|
|
1723
|
+
embeddingService
|
|
1724
|
+
);
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
log6.warn({ err, sessionId }, "distillation failed (non-fatal)");
|
|
1727
|
+
}
|
|
1728
|
+
try {
|
|
1729
|
+
if (!skillStore || !evolutionPipeline) return;
|
|
1730
|
+
const skills = skillStore.scan();
|
|
1731
|
+
if (skills.length === 0) return;
|
|
1732
|
+
const recent = memoryStore.getMemoriesByRole("role-__assistant__", "working");
|
|
1733
|
+
const refs = recent.map((m) => ({
|
|
1734
|
+
id: m.id,
|
|
1735
|
+
type: m.type,
|
|
1736
|
+
content: m.content,
|
|
1737
|
+
domain: m.domain
|
|
1738
|
+
}));
|
|
1739
|
+
const scanner = new AccumulationScanner();
|
|
1740
|
+
const signals = scanner.scan(
|
|
1741
|
+
refs,
|
|
1742
|
+
skills.map((s) => s.name)
|
|
1743
|
+
);
|
|
1744
|
+
if (signals.length > 0) {
|
|
1745
|
+
await triggerFromAccumulation(signals, evolutionPipeline);
|
|
1746
|
+
}
|
|
1747
|
+
} catch (err) {
|
|
1748
|
+
log6.warn({ err, sessionId }, "accumulation scan failed (non-fatal)");
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1499
1751
|
const sessionManager = new SessionManager({
|
|
1500
1752
|
adapter: options.adapter,
|
|
1501
1753
|
onOutput: (sessionId, data) => {
|
|
@@ -1530,7 +1782,8 @@ async function startAgent(options) {
|
|
|
1530
1782
|
sessionId,
|
|
1531
1783
|
error
|
|
1532
1784
|
});
|
|
1533
|
-
}
|
|
1785
|
+
},
|
|
1786
|
+
onBeforeClose
|
|
1534
1787
|
});
|
|
1535
1788
|
swarmCoordinator = new SwarmCoordinator(sessionManager, hub);
|
|
1536
1789
|
hub.setSessionManager(sessionManager);
|
|
@@ -1541,6 +1794,13 @@ async function startAgent(options) {
|
|
|
1541
1794
|
log6.info("shutting down");
|
|
1542
1795
|
await sessionManager.closeAll();
|
|
1543
1796
|
hub.destroy();
|
|
1797
|
+
if (memoryDb) {
|
|
1798
|
+
try {
|
|
1799
|
+
memoryDb.close();
|
|
1800
|
+
} catch (err) {
|
|
1801
|
+
log6.warn({ err }, "failed to close memory db");
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1544
1804
|
process.exit(0);
|
|
1545
1805
|
};
|
|
1546
1806
|
process.on("SIGINT", shutdown);
|
|
@@ -1556,4 +1816,4 @@ export {
|
|
|
1556
1816
|
FsBridge,
|
|
1557
1817
|
startAgent
|
|
1558
1818
|
};
|
|
1559
|
-
//# sourceMappingURL=chunk-
|
|
1819
|
+
//# sourceMappingURL=chunk-MSDIRBXF.js.map
|