pigpig-agent 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.
Files changed (64) hide show
  1. package/.skills/code-review/SKILL.md +42 -0
  2. package/dist/agent/loop-detection.js +135 -0
  3. package/dist/agent/loop.js +123 -0
  4. package/dist/agent/retry.js +39 -0
  5. package/dist/agents/registry.js +59 -0
  6. package/dist/agents/spawn.js +124 -0
  7. package/dist/agents/types.js +5 -0
  8. package/dist/commands/agent.js +33 -0
  9. package/dist/commands/context.js +27 -0
  10. package/dist/commands/cron.js +38 -0
  11. package/dist/commands/debug.js +44 -0
  12. package/dist/commands/dream.js +36 -0
  13. package/dist/commands/index.js +10 -0
  14. package/dist/commands/memory.js +40 -0
  15. package/dist/commands/plugin.js +73 -0
  16. package/dist/commands/rag.js +25 -0
  17. package/dist/commands/security.js +44 -0
  18. package/dist/commands/skill.js +84 -0
  19. package/dist/config/init.js +69 -0
  20. package/dist/config/loader.js +52 -0
  21. package/dist/config/schema.js +55 -0
  22. package/dist/context/compressor.js +165 -0
  23. package/dist/context/defense.js +201 -0
  24. package/dist/context/prompt-builder.js +56 -0
  25. package/dist/context/prompt-pipes.js +14 -0
  26. package/dist/context/tool-result-output.js +25 -0
  27. package/dist/context/view.js +185 -0
  28. package/dist/cron/parser.js +27 -0
  29. package/dist/cron/service.js +211 -0
  30. package/dist/cron/store.js +53 -0
  31. package/dist/cron/types.js +1 -0
  32. package/dist/index.js +9 -0
  33. package/dist/main.js +270 -0
  34. package/dist/memory/store.js +175 -0
  35. package/dist/memory/validator.js +62 -0
  36. package/dist/mock-model.js +534 -0
  37. package/dist/plugins/manager.js +97 -0
  38. package/dist/plugins/supabase-plugin.js +111 -0
  39. package/dist/plugins/types.js +1 -0
  40. package/dist/rag/chunker.js +54 -0
  41. package/dist/rag/embedder.js +53 -0
  42. package/dist/rag/search.js +123 -0
  43. package/dist/rag/sqlite-store.js +195 -0
  44. package/dist/rag/store.js +29 -0
  45. package/dist/security/bash-classifier.js +39 -0
  46. package/dist/security/hook.js +53 -0
  47. package/dist/security/roles.js +16 -0
  48. package/dist/session/store.js +60 -0
  49. package/dist/skills/loader.js +100 -0
  50. package/dist/tools/cron-tools.js +94 -0
  51. package/dist/tools/file-tools.js +115 -0
  52. package/dist/tools/index.js +17 -0
  53. package/dist/tools/mcp-client.js +100 -0
  54. package/dist/tools/memory-tools.js +81 -0
  55. package/dist/tools/rag-tools.js +54 -0
  56. package/dist/tools/registry.js +270 -0
  57. package/dist/tools/search-tools.js +116 -0
  58. package/dist/tools/shell-tools.js +39 -0
  59. package/dist/tools/spawn-tools.js +35 -0
  60. package/dist/tools/tool-search.js +17 -0
  61. package/dist/tools/web-search.js +150 -0
  62. package/dist/usage/tracker.js +83 -0
  63. package/package.json +55 -0
  64. package/readme.md +131 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * 优化记忆存储 -- 不能每次都加载所有记忆
3
+ * 提取有效记忆手段: 文件 xxx.md | RAG
4
+ * xxx.md --- 专门用一个技能文件来教会Agent如何提取有效记忆
5
+ */
6
+ // ---
7
+ // name: 用户偏好 Typescript
8
+ // description: 用户对Typescript的偏好设置,不喜欢python
9
+ // type: user
10
+ // ---
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { lintAll, } from './validator.js';
14
+ const MEMORY_DIR = '.memory'; // 默认记忆目录名
15
+ const INDEX_FILE = 'MEMORY.md'; // 默认记忆文件名 === 一个 skill 文件
16
+ const MAX_INDEX_LINES = 200; // 一个索引文件最多200行
17
+ const MAX_FILE_CHARS = 4000; // 单个记忆文件最多4000个字符
18
+ export class MemoryStore {
19
+ baseDir = '.';
20
+ constructor(baseDir = '.') {
21
+ this.baseDir = baseDir;
22
+ }
23
+ get memoryDir() {
24
+ return path.join(this.baseDir, MEMORY_DIR);
25
+ }
26
+ get indexPath() {
27
+ return path.join(this.memoryDir, INDEX_FILE);
28
+ }
29
+ init() {
30
+ if (!fs.existsSync(this.memoryDir)) {
31
+ fs.mkdirSync(this.memoryDir, { recursive: true });
32
+ }
33
+ if (!fs.existsSync(this.indexPath)) {
34
+ fs.writeFileSync(this.indexPath, '# Memory Index\n', 'utf-8');
35
+ }
36
+ }
37
+ // 将有效记忆保存到文件,文件以 skill 的格式开头,然后拼接有效的记忆内容
38
+ save(entry) {
39
+ this.init();
40
+ const slug = entry.name
41
+ .toLowerCase()
42
+ .replace(/[^a-z0-9一-鿿]+/g, '-')
43
+ .replace(/^-|-$/g, '');
44
+ const filename = `${entry.type}_${slug}.md`;
45
+ const filePath = path.join(this.memoryDir, filename);
46
+ const fileContent = [
47
+ '---',
48
+ `name: ${entry.name}`,
49
+ `description: ${entry.description}`,
50
+ `type: ${entry.type}`,
51
+ '---',
52
+ '',
53
+ entry.content,
54
+ ].join('\n');
55
+ fs.writeFileSync(filePath, fileContent, 'utf-8');
56
+ this.updateIndex(entry.name, filename, entry.description);
57
+ return filename;
58
+ }
59
+ // 更新索引文件,判断记忆文件中的内容是否超过最大上限,超过则移除最早的条目
60
+ updateIndex(name, filename, description) {
61
+ const indexContent = fs.readFileSync(this.indexPath, 'utf-8');
62
+ const lines = indexContent.split('\n');
63
+ const existingIdx = lines.findIndex(l => l.includes(`(${filename})`));
64
+ const newLine = `- [${name}](${filename}) — ${description}`;
65
+ if (existingIdx >= 0) {
66
+ lines[existingIdx] = newLine;
67
+ }
68
+ else {
69
+ if (lines.length >= MAX_INDEX_LINES) {
70
+ console.log(`[memory] 索引已达 ${MAX_INDEX_LINES} 行上限,移除最早的条目`);
71
+ const firstEntry = lines.findIndex(l => l.startsWith('- '));
72
+ if (firstEntry >= 0)
73
+ lines.splice(firstEntry, 1);
74
+ }
75
+ lines.push(newLine);
76
+ }
77
+ fs.writeFileSync(this.indexPath, lines.join('\n'), 'utf-8');
78
+ }
79
+ // 从记忆文件中读取所有有效记忆
80
+ list() {
81
+ this.init();
82
+ const entries = []; // 记录所有的记忆文件的头部信息
83
+ const files = fs.readdirSync(this.memoryDir)
84
+ .filter(f => f.endsWith('.md') && f !== INDEX_FILE);
85
+ for (const file of files) {
86
+ const filePath = path.join(this.memoryDir, file);
87
+ const raw = fs.readFileSync(filePath, 'utf-8');
88
+ const parsed = this.parseFrontmatter(raw);
89
+ if (parsed) {
90
+ entries.push({ ...parsed, filePath });
91
+ }
92
+ }
93
+ return entries;
94
+ }
95
+ // 拿着关键词在所有的有效记忆文件的头部信息中搜索,返回所有匹配的记忆
96
+ search(query) {
97
+ const all = this.list();
98
+ const keywords = query.toLowerCase().split(/\s+/);
99
+ return all.filter(entry => {
100
+ const text = `${entry.name} ${entry.description} ${entry.content}`.toLowerCase();
101
+ return keywords.some(kw => text.includes(kw));
102
+ });
103
+ }
104
+ // 读取记忆文件中的内容
105
+ loadIndex() {
106
+ this.init();
107
+ const raw = fs.readFileSync(this.indexPath, 'utf-8');
108
+ return raw.length > MAX_FILE_CHARS ? raw.slice(0, MAX_FILE_CHARS) + '\n...(已截断)' : raw;
109
+ }
110
+ // 读取指定记忆文件的内容
111
+ loadFile(filename) {
112
+ const filePath = path.join(this.memoryDir, filename);
113
+ if (!fs.existsSync(filePath))
114
+ return null;
115
+ const raw = fs.readFileSync(filePath, 'utf-8');
116
+ return raw.length > MAX_FILE_CHARS ? raw.slice(0, MAX_FILE_CHARS) + '\n...(已截断)' : raw;
117
+ }
118
+ delete(filename) {
119
+ const filePath = path.join(this.memoryDir, filename);
120
+ if (!fs.existsSync(filePath))
121
+ return false;
122
+ fs.unlinkSync(filePath); // 删除记忆文件
123
+ const indexContent = fs.readFileSync(this.indexPath, 'utf-8');
124
+ const lines = indexContent.split('\n').filter(l => !l.includes(`(${filename})`));
125
+ fs.writeFileSync(this.indexPath, lines.join('\n'), 'utf-8');
126
+ return true;
127
+ }
128
+ // 负责将记忆系统中内容提取出来,构建一个提示段落
129
+ buildPromptSection() {
130
+ this.init();
131
+ const index = this.loadIndex();
132
+ const entries = this.list();
133
+ if (entries.length === 0) {
134
+ return '[记忆系统] 当前没有存储任何记忆。你可以使用 memory 工具来保存重要信息。';
135
+ }
136
+ const lines = [
137
+ `[记忆系统] 共 ${entries.length} 条记忆`,
138
+ '',
139
+ '记忆索引:',
140
+ index,
141
+ '',
142
+ '记忆使用原则:',
143
+ '- 记忆是线索,不是事实——使用前先用工具验证 (read_file,grep确认)',
144
+ '- 不存代码能推导的,git 能查的,文档已经写了的',
145
+ '- 只存对话中出现的,其他地方推导不出来的信息',
146
+ ];
147
+ return lines.join('\n');
148
+ }
149
+ // 解析记忆文件中的 frontmatter 部分,提取 name、description、type 等元数据
150
+ parseFrontmatter(raw) {
151
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
152
+ if (!match)
153
+ return null;
154
+ const meta = {};
155
+ for (const line of match[1].split('\n')) {
156
+ const idx = line.indexOf(':');
157
+ if (idx > 0) {
158
+ meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
159
+ }
160
+ }
161
+ const validTypes = ['user', 'feedback', 'project', 'reference'];
162
+ if (!meta.name || !meta.type || !validTypes.includes(meta.type))
163
+ return null;
164
+ return {
165
+ name: meta.name,
166
+ description: meta.description || '',
167
+ type: meta.type,
168
+ content: match[2].trim(),
169
+ };
170
+ }
171
+ // 记忆体检
172
+ lint() {
173
+ return lintAll(this.list(), this.baseDir);
174
+ }
175
+ }
@@ -0,0 +1,62 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ const TTL_BY_TYPE = {
4
+ user: 365, // 用户偏好
5
+ feedback: 90, // 纠正反馈
6
+ project: 30, // 项目决策
7
+ reference: 14, // 外部资源
8
+ };
9
+ // 解析内容中的路径
10
+ const PATH_RE = /(?<![\w/])([\w./-]+\.(?:ts|tsx|js|jsx|json|md|mdx|sql|yml|yaml|toml|env|sh|py))/g;
11
+ export function extractPaths(content) {
12
+ const paths = new Set();
13
+ for (const match of content.matchAll(PATH_RE)) {
14
+ paths.add(match[1]);
15
+ }
16
+ return Array.from(paths);
17
+ }
18
+ export function validateEntry(entry, baseDir = '.') {
19
+ const issues = [];
20
+ // 检查路径是否过期
21
+ const paths = extractPaths(entry.content);
22
+ for (const p of paths) {
23
+ const abs = path.isAbsolute(p) ? p : path.join(baseDir, p);
24
+ if (!fs.existsSync(abs)) {
25
+ issues.push({
26
+ kind: 'stale_path',
27
+ message: `引用的路径不存在: ${abs}`,
28
+ });
29
+ }
30
+ }
31
+ // 按类型 TTL 判断长期未用
32
+ if (entry.lastReadAt) {
33
+ const staleDays = TTL_BY_TYPE[entry.type] ?? 30;
34
+ const days = (Date.now() - entry.lastReadAt) / (1000 * 60 * 60 * 24);
35
+ if (days > staleDays) {
36
+ issues.push({
37
+ kind: 'never_used',
38
+ message: `已 ${Math.floor(days)} 天没被读过,超过 ${entry.type} 类型的 ${staleDays} 天保质期`,
39
+ });
40
+ }
41
+ }
42
+ return issues;
43
+ }
44
+ export function lintAll(entries, baseDir = '.') {
45
+ const reports = [];
46
+ const nameCount = new Map();
47
+ for (const e of entries) {
48
+ nameCount.set(e.name, (nameCount.get(e.name) || 0) + 1);
49
+ }
50
+ for (const entry of entries) {
51
+ const issues = validateEntry(entry, baseDir);
52
+ if ((nameCount.get(entry.name) || 0) > 1) {
53
+ issues.push({
54
+ kind: 'duplicate_name',
55
+ message: `存在 ${nameCount.get(entry.name)} 条同名记忆,可能需要合并`,
56
+ });
57
+ }
58
+ if (issues.length > 0)
59
+ reports.push({ entry, issues });
60
+ }
61
+ return reports;
62
+ }