skyloom 1.12.0 → 1.13.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.
Files changed (135) hide show
  1. package/.github/workflows/ci.yml +36 -36
  2. package/README.md +142 -46
  3. package/config/default.yaml +43 -47
  4. package/config/models.yaml +155 -155
  5. package/config/providers.yaml +39 -39
  6. package/config/skills/api_integrator/SKILL.md +15 -15
  7. package/config/skills/arch_designer/SKILL.md +13 -13
  8. package/config/skills/ci_cd_manager/SKILL.md +14 -14
  9. package/config/skills/code_analysis/SKILL.md +13 -13
  10. package/config/skills/code_generator/SKILL.md +12 -12
  11. package/config/skills/code_reviewer/SKILL.md +13 -13
  12. package/config/skills/content_writer/SKILL.md +14 -14
  13. package/config/skills/data_transformer/SKILL.md +15 -15
  14. package/config/skills/document_analysis/SKILL.md +13 -13
  15. package/config/skills/emotional_companion/SKILL.md +15 -15
  16. package/config/skills/performance_checker/SKILL.md +14 -14
  17. package/config/skills/security_auditor/SKILL.md +14 -14
  18. package/config/skills/self_evolve/SKILL.md +13 -13
  19. package/config/skills/sys_operator/SKILL.md +15 -15
  20. package/config/skills/task_planner/SKILL.md +14 -14
  21. package/config/skills/web_research/SKILL.md +14 -14
  22. package/config/skills/workflow_designer/SKILL.md +13 -13
  23. package/dist/agents/dew.js +52 -52
  24. package/dist/agents/fair.js +84 -84
  25. package/dist/agents/fog.js +30 -30
  26. package/dist/agents/frost.js +32 -32
  27. package/dist/agents/rain.js +32 -32
  28. package/dist/agents/snow.js +68 -68
  29. package/dist/cli/main.js +103 -51
  30. package/dist/cli/main.js.map +1 -1
  31. package/dist/cli/tui.d.ts.map +1 -1
  32. package/dist/cli/tui.js +8 -1
  33. package/dist/cli/tui.js.map +1 -1
  34. package/dist/core/agent/task.d.ts +58 -0
  35. package/dist/core/agent/task.d.ts.map +1 -0
  36. package/dist/core/agent/task.js +83 -0
  37. package/dist/core/agent/task.js.map +1 -0
  38. package/dist/core/agent.d.ts +2 -45
  39. package/dist/core/agent.d.ts.map +1 -1
  40. package/dist/core/agent.js +61 -145
  41. package/dist/core/agent.js.map +1 -1
  42. package/dist/core/agent_helpers.d.ts +10 -0
  43. package/dist/core/agent_helpers.d.ts.map +1 -1
  44. package/dist/core/agent_helpers.js +39 -0
  45. package/dist/core/agent_helpers.js.map +1 -1
  46. package/dist/core/catalog.d.ts +71 -0
  47. package/dist/core/catalog.d.ts.map +1 -0
  48. package/dist/core/catalog.js +176 -0
  49. package/dist/core/catalog.js.map +1 -0
  50. package/dist/core/config.d.ts +8 -0
  51. package/dist/core/config.d.ts.map +1 -1
  52. package/dist/core/config.js +12 -4
  53. package/dist/core/config.js.map +1 -1
  54. package/dist/core/factory.js +16 -16
  55. package/dist/core/llm.d.ts +7 -0
  56. package/dist/core/llm.d.ts.map +1 -1
  57. package/dist/core/llm.js +139 -7
  58. package/dist/core/llm.js.map +1 -1
  59. package/dist/core/longdoc.js +5 -5
  60. package/dist/core/memory.d.ts.map +1 -1
  61. package/dist/core/memory.js +69 -62
  62. package/dist/core/memory.js.map +1 -1
  63. package/dist/core/theme.d.ts +46 -0
  64. package/dist/core/theme.d.ts.map +1 -0
  65. package/dist/core/theme.js +42 -0
  66. package/dist/core/theme.js.map +1 -0
  67. package/dist/web/server.js +542 -519
  68. package/dist/web/server.js.map +1 -1
  69. package/docs/AESTHETIC_DESIGN.md +144 -0
  70. package/docs/OPTIMIZATION_PLAN.md +178 -0
  71. package/package.json +60 -60
  72. package/scripts/install.js +48 -48
  73. package/scripts/link.js +10 -10
  74. package/setup.bat +79 -79
  75. package/skill-test-ty2fOA/test.md +10 -10
  76. package/src/agents/dew.ts +70 -70
  77. package/src/agents/fair.ts +102 -102
  78. package/src/agents/fog.ts +48 -48
  79. package/src/agents/frost.ts +50 -50
  80. package/src/agents/rain.ts +50 -50
  81. package/src/agents/snow.ts +239 -239
  82. package/src/cli/main.ts +425 -372
  83. package/src/cli/mode.ts +58 -58
  84. package/src/cli/tui.ts +272 -269
  85. package/src/core/agent/task.ts +100 -0
  86. package/src/core/agent.ts +1446 -1549
  87. package/src/core/agent_helpers.ts +496 -461
  88. package/src/core/arbitrate.ts +162 -162
  89. package/src/core/catalog.ts +178 -0
  90. package/src/core/checkpoint.ts +94 -94
  91. package/src/core/config.ts +20 -4
  92. package/src/core/estimate.ts +104 -104
  93. package/src/core/evolve.ts +191 -191
  94. package/src/core/factory.ts +627 -627
  95. package/src/core/filter.ts +103 -103
  96. package/src/core/graph.ts +156 -156
  97. package/src/core/icons.ts +53 -53
  98. package/src/core/index.ts +37 -37
  99. package/src/core/learn.ts +146 -146
  100. package/src/core/llm.ts +108 -5
  101. package/src/core/longdoc.ts +155 -155
  102. package/src/core/mcp_server.ts +176 -176
  103. package/src/core/memory.ts +1178 -1171
  104. package/src/core/profile.ts +255 -255
  105. package/src/core/router.ts +124 -124
  106. package/src/core/sandbox.ts +142 -142
  107. package/src/core/security.ts +243 -243
  108. package/src/core/skill.ts +342 -342
  109. package/src/core/theme.ts +65 -0
  110. package/src/core/tool_router.ts +193 -193
  111. package/src/core/vector.ts +152 -152
  112. package/src/core/workspace.ts +150 -150
  113. package/src/plugins/loader.ts +66 -66
  114. package/src/skills/loader.ts +46 -46
  115. package/src/sql.js.d.ts +29 -29
  116. package/src/tools/builtin.ts +380 -380
  117. package/src/tools/computer.ts +269 -269
  118. package/src/tools/delegate.ts +49 -49
  119. package/src/web/server.ts +660 -634
  120. package/src/web/tts.ts +93 -93
  121. package/tests/agent_helpers.test.ts +48 -0
  122. package/tests/bus.test.ts +121 -121
  123. package/tests/catalog.test.ts +86 -0
  124. package/tests/config.test.ts +41 -0
  125. package/tests/icons.test.ts +45 -45
  126. package/tests/memory.test.ts +147 -0
  127. package/tests/router.test.ts +86 -86
  128. package/tests/schemas.test.ts +51 -51
  129. package/tests/semantic.test.ts +83 -83
  130. package/tests/setup.ts +10 -10
  131. package/tests/skill.test.ts +172 -172
  132. package/tests/task.test.ts +60 -0
  133. package/tests/tool.test.ts +108 -108
  134. package/tests/tool_router.test.ts +71 -71
  135. package/vitest.config.ts +17 -17
@@ -1,103 +1,103 @@
1
- /**
2
- * 输出过滤模块 — sensitive information sanitization.
3
- *
4
- * Before agent responses reach the user (or are persisted),
5
- * scan for and redact sensitive patterns like API keys,
6
- * tokens, passwords, PII, and internal paths.
7
- */
8
-
9
- /* ═══════════════════════════════════════
10
- Detection patterns — compiled once at module load
11
- ═══════════════════════════════════════ */
12
- const SENSITIVE_PATTERNS: Array<[RegExp, string]> = [
13
- // API keys & tokens
14
- [/sk-[a-zA-Z0-9]{32,}/g, "[REDACTED:API_KEY]"],
15
- [/(?:api_key|apikey|secret_key|access_token|auth_token)\s*[:=]\s*["']?[^\s"']{8,}["']?/gi, "$1: [REDACTED]"],
16
- [/ghp_[a-zA-Z0-9]{36}/g, "[REDACTED:GITHUB_TOKEN]"],
17
- [/gho_[a-zA-Z0-9]{36}/g, "[REDACTED:GITHUB_TOKEN]"],
18
-
19
- // AWS credentials
20
- [/AKIA[0-9A-Z]{16}/g, "[REDACTED:AWS_KEY]"],
21
- [/(?:aws_access_key_id|aws_secret_access_key)\s*[:=]\s*["']?[^\s"']+/gi, "$1: [REDACTED]"],
22
-
23
- // Passwords
24
- [/(?:password|passwd|pwd)\s*[:=]\s*["']?[^\s"']{4,}["']?/gi, "$1: [REDACTED]"],
25
- [/(?:密码|口令)\s*[:=]\s*["']?[^\s"']{2,}["']?/g, "$1: [已脱敏]"],
26
-
27
- // Connection strings
28
- [/(?:mongodb|postgres|mysql|redis):\/\/[^\s]+/g, "[REDACTED:DB_URI]"],
29
- [/(?:jdbc|odbc):[^\s]+/g, "[REDACTED:DB_URI]"],
30
-
31
- // Private keys
32
- [/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END .*?PRIVATE KEY-----/g, "[REDACTED:PRIVATE_KEY]"],
33
-
34
- // IP addresses (local only)
35
- [/192\.168\.\d{1,3}\.\d{1,3}/g, "[REDACTED:LAN_IP]"],
36
- [/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/g, "[REDACTED:LAN_IP]"],
37
- [/172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}/g, "[REDACTED:LAN_IP]"],
38
-
39
- // File paths
40
- [/(?:\/etc\/(?:passwd|shadow|hosts|sudoers))/g, "[REDACTED:SYSTEM_PATH]"],
41
- ];
42
-
43
- /* Email masking (function-based, handled separately) */
44
- const EMAIL_RE = /([a-zA-Z0-9._%+-]{3,})@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
45
-
46
-
47
- /* ═══════════════════════════════════════
48
- Filter function
49
- ═══════════════════════════════════════ */
50
- export interface FilterResult {
51
- clean: string;
52
- redacted: boolean;
53
- count: number;
54
- details: string[];
55
- }
56
-
57
- export function filterOutput(text: string): FilterResult {
58
- if (!text) return { clean: "", redacted: false, count: 0, details: [] };
59
-
60
- let clean = text;
61
- let count = 0;
62
- const details: string[] = [];
63
-
64
- // Email masking (function-based replacement)
65
- let emailCount = 0;
66
- clean = clean.replace(EMAIL_RE, (full, user, domain) => {
67
- emailCount++;
68
- return (user as string).slice(0, 2) + "***@" + (domain as string);
69
- });
70
- if (emailCount > 0) {
71
- count += emailCount;
72
- details.push(`Masked ${emailCount}x email addresses`);
73
- }
74
-
75
- for (const [pattern, replacement] of SENSITIVE_PATTERNS) {
76
- const matches = clean.match(pattern);
77
- if (matches) {
78
- count += matches.length;
79
- if (typeof replacement === "string") {
80
- details.push(`Redacted ${matches.length}x ${pattern.source.slice(0, 30)}`);
81
- } else {
82
- details.push(`Masked ${matches.length}x email addresses`);
83
- }
84
- clean = clean.replace(pattern, replacement as string);
85
- }
86
- }
87
-
88
- return { clean, redacted: count > 0, count, details };
89
- }
90
-
91
- /* ═══════════════════════════════════════
92
- Quick check — is filtering needed?
93
- ═══════════════════════════════════════ */
94
- export function needsFiltering(text: string): boolean {
95
- if (!text) return false;
96
- // Quick scan with the most common patterns
97
- if (/sk-[a-zA-Z0-9]{32,}/.test(text)) return true;
98
- if (/api_key.*[:=]/.test(text)) return true;
99
- if (/password.*[:=]/.test(text)) return true;
100
- if (/-----BEGIN.*PRIVATE KEY-----/.test(text)) return true;
101
- if (EMAIL_RE.test(text)) return true;
102
- return false;
103
- }
1
+ /**
2
+ * 输出过滤模块 — sensitive information sanitization.
3
+ *
4
+ * Before agent responses reach the user (or are persisted),
5
+ * scan for and redact sensitive patterns like API keys,
6
+ * tokens, passwords, PII, and internal paths.
7
+ */
8
+
9
+ /* ═══════════════════════════════════════
10
+ Detection patterns — compiled once at module load
11
+ ═══════════════════════════════════════ */
12
+ const SENSITIVE_PATTERNS: Array<[RegExp, string]> = [
13
+ // API keys & tokens
14
+ [/sk-[a-zA-Z0-9]{32,}/g, "[REDACTED:API_KEY]"],
15
+ [/(?:api_key|apikey|secret_key|access_token|auth_token)\s*[:=]\s*["']?[^\s"']{8,}["']?/gi, "$1: [REDACTED]"],
16
+ [/ghp_[a-zA-Z0-9]{36}/g, "[REDACTED:GITHUB_TOKEN]"],
17
+ [/gho_[a-zA-Z0-9]{36}/g, "[REDACTED:GITHUB_TOKEN]"],
18
+
19
+ // AWS credentials
20
+ [/AKIA[0-9A-Z]{16}/g, "[REDACTED:AWS_KEY]"],
21
+ [/(?:aws_access_key_id|aws_secret_access_key)\s*[:=]\s*["']?[^\s"']+/gi, "$1: [REDACTED]"],
22
+
23
+ // Passwords
24
+ [/(?:password|passwd|pwd)\s*[:=]\s*["']?[^\s"']{4,}["']?/gi, "$1: [REDACTED]"],
25
+ [/(?:密码|口令)\s*[:=]\s*["']?[^\s"']{2,}["']?/g, "$1: [已脱敏]"],
26
+
27
+ // Connection strings
28
+ [/(?:mongodb|postgres|mysql|redis):\/\/[^\s]+/g, "[REDACTED:DB_URI]"],
29
+ [/(?:jdbc|odbc):[^\s]+/g, "[REDACTED:DB_URI]"],
30
+
31
+ // Private keys
32
+ [/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END .*?PRIVATE KEY-----/g, "[REDACTED:PRIVATE_KEY]"],
33
+
34
+ // IP addresses (local only)
35
+ [/192\.168\.\d{1,3}\.\d{1,3}/g, "[REDACTED:LAN_IP]"],
36
+ [/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/g, "[REDACTED:LAN_IP]"],
37
+ [/172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}/g, "[REDACTED:LAN_IP]"],
38
+
39
+ // File paths
40
+ [/(?:\/etc\/(?:passwd|shadow|hosts|sudoers))/g, "[REDACTED:SYSTEM_PATH]"],
41
+ ];
42
+
43
+ /* Email masking (function-based, handled separately) */
44
+ const EMAIL_RE = /([a-zA-Z0-9._%+-]{3,})@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
45
+
46
+
47
+ /* ═══════════════════════════════════════
48
+ Filter function
49
+ ═══════════════════════════════════════ */
50
+ export interface FilterResult {
51
+ clean: string;
52
+ redacted: boolean;
53
+ count: number;
54
+ details: string[];
55
+ }
56
+
57
+ export function filterOutput(text: string): FilterResult {
58
+ if (!text) return { clean: "", redacted: false, count: 0, details: [] };
59
+
60
+ let clean = text;
61
+ let count = 0;
62
+ const details: string[] = [];
63
+
64
+ // Email masking (function-based replacement)
65
+ let emailCount = 0;
66
+ clean = clean.replace(EMAIL_RE, (full, user, domain) => {
67
+ emailCount++;
68
+ return (user as string).slice(0, 2) + "***@" + (domain as string);
69
+ });
70
+ if (emailCount > 0) {
71
+ count += emailCount;
72
+ details.push(`Masked ${emailCount}x email addresses`);
73
+ }
74
+
75
+ for (const [pattern, replacement] of SENSITIVE_PATTERNS) {
76
+ const matches = clean.match(pattern);
77
+ if (matches) {
78
+ count += matches.length;
79
+ if (typeof replacement === "string") {
80
+ details.push(`Redacted ${matches.length}x ${pattern.source.slice(0, 30)}`);
81
+ } else {
82
+ details.push(`Masked ${matches.length}x email addresses`);
83
+ }
84
+ clean = clean.replace(pattern, replacement as string);
85
+ }
86
+ }
87
+
88
+ return { clean, redacted: count > 0, count, details };
89
+ }
90
+
91
+ /* ═══════════════════════════════════════
92
+ Quick check — is filtering needed?
93
+ ═══════════════════════════════════════ */
94
+ export function needsFiltering(text: string): boolean {
95
+ if (!text) return false;
96
+ // Quick scan with the most common patterns
97
+ if (/sk-[a-zA-Z0-9]{32,}/.test(text)) return true;
98
+ if (/api_key.*[:=]/.test(text)) return true;
99
+ if (/password.*[:=]/.test(text)) return true;
100
+ if (/-----BEGIN.*PRIVATE KEY-----/.test(text)) return true;
101
+ if (EMAIL_RE.test(text)) return true;
102
+ return false;
103
+ }
package/src/core/graph.ts CHANGED
@@ -1,156 +1,156 @@
1
- /**
2
- * 简易知识图谱 — entity-relation storage in SQLite.
3
- *
4
- * Lightweight triple store: (subject, predicate, object) with metadata.
5
- * Used for: project info, tool preferences, dependency relationships.
6
- *
7
- * Schema:
8
- * CREATE TABLE triples (subj, pred, obj, agent, ts, meta)
9
- *
10
- * Queries:
11
- * - Find all relations for an entity
12
- * - Find all entities matching a predicate
13
- * - Transitive closure (2-hop max for performance)
14
- */
15
-
16
- import * as fs from "fs";
17
- import * as path from "path";
18
- import { USER_CONFIG_DIR } from "./config";
19
- import { getLogger } from "./logger";
20
-
21
- const log = getLogger("graph");
22
-
23
- /* ═══════════════════════════════════════
24
- Triple store — in-memory + optional persistence
25
- ═══════════════════════════════════════ */
26
- interface Triple {
27
- subj: string;
28
- pred: string;
29
- obj: string;
30
- agent: string;
31
- ts: string;
32
- meta?: Record<string, string>;
33
- }
34
-
35
- export class KnowledgeGraph {
36
- private triples: Triple[] = [];
37
- private indexPath: string;
38
-
39
- constructor(name: string = "default") {
40
- this.indexPath = path.join(USER_CONFIG_DIR, `kg_${name}.json`);
41
- this.load();
42
- }
43
-
44
- /** Add a fact: (subject, predicate, object). */
45
- add(subj: string, pred: string, obj: string, agent: string = "system", meta?: Record<string, string>): void {
46
- // Deduplicate
47
- const exists = this.triples.find(t => t.subj === subj && t.pred === pred && t.obj === obj);
48
- if (exists) { exists.ts = new Date().toISOString(); if (meta) exists.meta = { ...exists.meta, ...meta }; return; }
49
-
50
- this.triples.push({ subj, pred, obj, agent, ts: new Date().toISOString(), meta });
51
- if (this.triples.length > 5000) this.triples.splice(0, this.triples.length - 5000);
52
- this.save();
53
- }
54
-
55
- /** Find all facts about an entity. */
56
- about(entity: string, limit: number = 20): Triple[] {
57
- return this.triples.filter(t => t.subj === entity || t.obj === entity).slice(-limit);
58
- }
59
-
60
- /** Find all subjects matching a predicate. */
61
- byPredicate(pred: string): Triple[] {
62
- return this.triples.filter(t => t.pred === pred);
63
- }
64
-
65
- /** Find all objects for a subject-predicate pair. */
66
- find(subj: string, pred: string): Triple[] {
67
- return this.triples.filter(t => t.subj === subj && t.pred === pred);
68
- }
69
-
70
- /** Transitive expansion: 2-hop from a starting entity. */
71
- expand(entity: string, maxDepth: number = 2): Triple[] {
72
- const seen = new Set<Triple>();
73
- const queue = [entity];
74
- for (let depth = 0; depth < maxDepth && queue.length > 0; depth++) {
75
- const current = queue.shift()!;
76
- const facts = this.about(current, 10);
77
- for (const f of facts) {
78
- if (seen.has(f)) continue;
79
- seen.add(f);
80
- if (f.subj === current && !queue.includes(f.obj)) queue.push(f.obj);
81
- if (f.obj === current && !queue.includes(f.subj)) queue.push(f.subj);
82
- }
83
- }
84
- return Array.from(seen);
85
- }
86
-
87
- /** Remove a fact. */
88
- remove(subj: string, pred: string, obj: string): void {
89
- this.triples = this.triples.filter(t => !(t.subj === subj && t.pred === pred && t.obj === obj));
90
- this.save();
91
- }
92
-
93
- /** Search for entities or predicates containing a keyword. */
94
- search(keyword: string, limit: number = 15): Triple[] {
95
- const k = keyword.toLowerCase();
96
- return this.triples.filter(t => t.subj.toLowerCase().includes(k) || t.pred.toLowerCase().includes(k) || t.obj.toLowerCase().includes(k)).slice(-limit);
97
- }
98
-
99
- /** Format facts as readable text. */
100
- format(entity?: string): string {
101
- const facts = entity ? this.about(entity) : this.triples.slice(-30);
102
- if (facts.length === 0) return "(no facts)";
103
- const bySubj = new Map<string, string[]>();
104
- for (const f of facts) {
105
- if (!bySubj.has(f.subj)) bySubj.set(f.subj, []);
106
- bySubj.get(f.subj)!.push(`${f.pred} → ${f.obj}`);
107
- }
108
- const lines: string[] = [];
109
- for (const [subj, preds] of bySubj) {
110
- lines.push(`**${subj}**: ${preds.join(", ")}`);
111
- }
112
- return lines.join("\n");
113
- }
114
-
115
- get size(): number { return this.triples.length; }
116
-
117
- private save(): void {
118
- try {
119
- const dir = path.dirname(this.indexPath);
120
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
121
- fs.writeFileSync(this.indexPath, JSON.stringify(this.triples.slice(-2000)), "utf-8");
122
- } catch (e) { log.warn("kg_save_failed", { error: String(e) }); }
123
- }
124
-
125
- private load(): void {
126
- try {
127
- if (fs.existsSync(this.indexPath)) {
128
- this.triples = JSON.parse(fs.readFileSync(this.indexPath, "utf-8"));
129
- }
130
- } catch { this.triples = []; }
131
- }
132
- }
133
-
134
- /* ── Auto-extract facts from conversation ── */
135
- const RELATION_PATTERNS: Array<[RegExp, string]> = [
136
- [/(\w+) (?:是|为|属于|使用|用|用到了|采用) (.+?)(?:[。,,.\n]|$)/g, "is"],
137
- [/(\w+) (?:版本|version|v) (?:是|为)? ?(\d[\d.]*)/gi, "version"],
138
- [/(\w+) (?:depends|依赖|需要|requires) (\w+)/gi, "depends_on"],
139
- [/(\w+) (?:config|配置) (?:为|是)? (.+?)(?:[。,,.\n]|$)/gi, "config"],
140
- [/(\w+) (?:file|path|文件|路径) (?:在|为|at) (.+?)(?:[。,,.\n]|$)/gi, "located_at"],
141
- ];
142
-
143
- export function extractFacts(text: string, agent: string): Array<[string, string, string]> {
144
- const facts: Array<[string, string, string]> = [];
145
- for (const [pattern, pred] of RELATION_PATTERNS) {
146
- let match;
147
- while ((match = pattern.exec(text)) !== null) {
148
- const subj = match[1].trim().toLowerCase();
149
- const obj = match[2].trim();
150
- if (subj.length >= 2 && obj.length >= 2 && subj !== obj) {
151
- facts.push([subj, pred, obj]);
152
- }
153
- }
154
- }
155
- return facts;
156
- }
1
+ /**
2
+ * 简易知识图谱 — entity-relation storage in SQLite.
3
+ *
4
+ * Lightweight triple store: (subject, predicate, object) with metadata.
5
+ * Used for: project info, tool preferences, dependency relationships.
6
+ *
7
+ * Schema:
8
+ * CREATE TABLE triples (subj, pred, obj, agent, ts, meta)
9
+ *
10
+ * Queries:
11
+ * - Find all relations for an entity
12
+ * - Find all entities matching a predicate
13
+ * - Transitive closure (2-hop max for performance)
14
+ */
15
+
16
+ import * as fs from "fs";
17
+ import * as path from "path";
18
+ import { USER_CONFIG_DIR } from "./config";
19
+ import { getLogger } from "./logger";
20
+
21
+ const log = getLogger("graph");
22
+
23
+ /* ═══════════════════════════════════════
24
+ Triple store — in-memory + optional persistence
25
+ ═══════════════════════════════════════ */
26
+ interface Triple {
27
+ subj: string;
28
+ pred: string;
29
+ obj: string;
30
+ agent: string;
31
+ ts: string;
32
+ meta?: Record<string, string>;
33
+ }
34
+
35
+ export class KnowledgeGraph {
36
+ private triples: Triple[] = [];
37
+ private indexPath: string;
38
+
39
+ constructor(name: string = "default") {
40
+ this.indexPath = path.join(USER_CONFIG_DIR, `kg_${name}.json`);
41
+ this.load();
42
+ }
43
+
44
+ /** Add a fact: (subject, predicate, object). */
45
+ add(subj: string, pred: string, obj: string, agent: string = "system", meta?: Record<string, string>): void {
46
+ // Deduplicate
47
+ const exists = this.triples.find(t => t.subj === subj && t.pred === pred && t.obj === obj);
48
+ if (exists) { exists.ts = new Date().toISOString(); if (meta) exists.meta = { ...exists.meta, ...meta }; return; }
49
+
50
+ this.triples.push({ subj, pred, obj, agent, ts: new Date().toISOString(), meta });
51
+ if (this.triples.length > 5000) this.triples.splice(0, this.triples.length - 5000);
52
+ this.save();
53
+ }
54
+
55
+ /** Find all facts about an entity. */
56
+ about(entity: string, limit: number = 20): Triple[] {
57
+ return this.triples.filter(t => t.subj === entity || t.obj === entity).slice(-limit);
58
+ }
59
+
60
+ /** Find all subjects matching a predicate. */
61
+ byPredicate(pred: string): Triple[] {
62
+ return this.triples.filter(t => t.pred === pred);
63
+ }
64
+
65
+ /** Find all objects for a subject-predicate pair. */
66
+ find(subj: string, pred: string): Triple[] {
67
+ return this.triples.filter(t => t.subj === subj && t.pred === pred);
68
+ }
69
+
70
+ /** Transitive expansion: 2-hop from a starting entity. */
71
+ expand(entity: string, maxDepth: number = 2): Triple[] {
72
+ const seen = new Set<Triple>();
73
+ const queue = [entity];
74
+ for (let depth = 0; depth < maxDepth && queue.length > 0; depth++) {
75
+ const current = queue.shift()!;
76
+ const facts = this.about(current, 10);
77
+ for (const f of facts) {
78
+ if (seen.has(f)) continue;
79
+ seen.add(f);
80
+ if (f.subj === current && !queue.includes(f.obj)) queue.push(f.obj);
81
+ if (f.obj === current && !queue.includes(f.subj)) queue.push(f.subj);
82
+ }
83
+ }
84
+ return Array.from(seen);
85
+ }
86
+
87
+ /** Remove a fact. */
88
+ remove(subj: string, pred: string, obj: string): void {
89
+ this.triples = this.triples.filter(t => !(t.subj === subj && t.pred === pred && t.obj === obj));
90
+ this.save();
91
+ }
92
+
93
+ /** Search for entities or predicates containing a keyword. */
94
+ search(keyword: string, limit: number = 15): Triple[] {
95
+ const k = keyword.toLowerCase();
96
+ return this.triples.filter(t => t.subj.toLowerCase().includes(k) || t.pred.toLowerCase().includes(k) || t.obj.toLowerCase().includes(k)).slice(-limit);
97
+ }
98
+
99
+ /** Format facts as readable text. */
100
+ format(entity?: string): string {
101
+ const facts = entity ? this.about(entity) : this.triples.slice(-30);
102
+ if (facts.length === 0) return "(no facts)";
103
+ const bySubj = new Map<string, string[]>();
104
+ for (const f of facts) {
105
+ if (!bySubj.has(f.subj)) bySubj.set(f.subj, []);
106
+ bySubj.get(f.subj)!.push(`${f.pred} → ${f.obj}`);
107
+ }
108
+ const lines: string[] = [];
109
+ for (const [subj, preds] of bySubj) {
110
+ lines.push(`**${subj}**: ${preds.join(", ")}`);
111
+ }
112
+ return lines.join("\n");
113
+ }
114
+
115
+ get size(): number { return this.triples.length; }
116
+
117
+ private save(): void {
118
+ try {
119
+ const dir = path.dirname(this.indexPath);
120
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
121
+ fs.writeFileSync(this.indexPath, JSON.stringify(this.triples.slice(-2000)), "utf-8");
122
+ } catch (e) { log.warn("kg_save_failed", { error: String(e) }); }
123
+ }
124
+
125
+ private load(): void {
126
+ try {
127
+ if (fs.existsSync(this.indexPath)) {
128
+ this.triples = JSON.parse(fs.readFileSync(this.indexPath, "utf-8"));
129
+ }
130
+ } catch { this.triples = []; }
131
+ }
132
+ }
133
+
134
+ /* ── Auto-extract facts from conversation ── */
135
+ const RELATION_PATTERNS: Array<[RegExp, string]> = [
136
+ [/(\w+) (?:是|为|属于|使用|用|用到了|采用) (.+?)(?:[。,,.\n]|$)/g, "is"],
137
+ [/(\w+) (?:版本|version|v) (?:是|为)? ?(\d[\d.]*)/gi, "version"],
138
+ [/(\w+) (?:depends|依赖|需要|requires) (\w+)/gi, "depends_on"],
139
+ [/(\w+) (?:config|配置) (?:为|是)? (.+?)(?:[。,,.\n]|$)/gi, "config"],
140
+ [/(\w+) (?:file|path|文件|路径) (?:在|为|at) (.+?)(?:[。,,.\n]|$)/gi, "located_at"],
141
+ ];
142
+
143
+ export function extractFacts(text: string, agent: string): Array<[string, string, string]> {
144
+ const facts: Array<[string, string, string]> = [];
145
+ for (const [pattern, pred] of RELATION_PATTERNS) {
146
+ let match;
147
+ while ((match = pattern.exec(text)) !== null) {
148
+ const subj = match[1].trim().toLowerCase();
149
+ const obj = match[2].trim();
150
+ if (subj.length >= 2 && obj.length >= 2 && subj !== obj) {
151
+ facts.push([subj, pred, obj]);
152
+ }
153
+ }
154
+ }
155
+ return facts;
156
+ }
package/src/core/icons.ts CHANGED
@@ -1,53 +1,53 @@
1
- /**
2
- * Agent icon system — dynamic status indicators.
3
- *
4
- * Each agent is identified by its display name with color styling.
5
- * During processing, agent spinners provide dynamic status.
6
- */
7
-
8
- import * as path from 'path';
9
-
10
- const ICONS_DIR = path.resolve(__dirname, '..', 'assets', 'icons');
11
-
12
- export const AGENT_COLORS: Record<string, string> = {
13
- fog: 'bright_white',
14
- rain: 'blue',
15
- frost: 'cyan',
16
- snow: 'bright_white',
17
- dew: 'green',
18
- fair: '#FFD700',
19
- };
20
-
21
- export const AGENT_EMOJI: Record<string, string> = {
22
- fog: '≋',
23
- rain: '⸽',
24
- frost: '✱',
25
- snow: '❉',
26
- dew: '∘',
27
- fair: '☼',
28
- };
29
-
30
- /**
31
- * Return the filesystem path to an agent's SVG icon file.
32
- */
33
- export function svgPath(name: string): string {
34
- return path.join(ICONS_DIR, `${name}.svg`);
35
- }
36
-
37
- /**
38
- * Return the plain-text icon for an agent.
39
- *
40
- * Used in dashboards, prompts, logs, and any UI where SVG can't render.
41
- * The glyphs are deliberately chosen from Unicode blocks that render
42
- * as monochrome text on virtually every terminal.
43
- *
44
- * fog ≋ three wavy lines — drifting mist
45
- * rain ⸽ six vertical dots — falling rain streaks
46
- * frost ✱ pointed asterisk — frost crystal
47
- * snow ❉ balloon-spoked star — snowflake
48
- * dew ∘ ring — dewdrop
49
- * fair ☼ sun with rays — clear sky
50
- */
51
- export function iconText(name: string): string {
52
- return AGENT_EMOJI[name] ?? name;
53
- }
1
+ /**
2
+ * Agent icon system — dynamic status indicators.
3
+ *
4
+ * Each agent is identified by its display name with color styling.
5
+ * During processing, agent spinners provide dynamic status.
6
+ */
7
+
8
+ import * as path from 'path';
9
+
10
+ const ICONS_DIR = path.resolve(__dirname, '..', 'assets', 'icons');
11
+
12
+ export const AGENT_COLORS: Record<string, string> = {
13
+ fog: 'bright_white',
14
+ rain: 'blue',
15
+ frost: 'cyan',
16
+ snow: 'bright_white',
17
+ dew: 'green',
18
+ fair: '#FFD700',
19
+ };
20
+
21
+ export const AGENT_EMOJI: Record<string, string> = {
22
+ fog: '≋',
23
+ rain: '⸽',
24
+ frost: '✱',
25
+ snow: '❉',
26
+ dew: '∘',
27
+ fair: '☼',
28
+ };
29
+
30
+ /**
31
+ * Return the filesystem path to an agent's SVG icon file.
32
+ */
33
+ export function svgPath(name: string): string {
34
+ return path.join(ICONS_DIR, `${name}.svg`);
35
+ }
36
+
37
+ /**
38
+ * Return the plain-text icon for an agent.
39
+ *
40
+ * Used in dashboards, prompts, logs, and any UI where SVG can't render.
41
+ * The glyphs are deliberately chosen from Unicode blocks that render
42
+ * as monochrome text on virtually every terminal.
43
+ *
44
+ * fog ≋ three wavy lines — drifting mist
45
+ * rain ⸽ six vertical dots — falling rain streaks
46
+ * frost ✱ pointed asterisk — frost crystal
47
+ * snow ❉ balloon-spoked star — snowflake
48
+ * dew ∘ ring — dewdrop
49
+ * fair ☼ sun with rays — clear sky
50
+ */
51
+ export function iconText(name: string): string {
52
+ return AGENT_EMOJI[name] ?? name;
53
+ }