agent-sin 0.1.12 → 0.1.16

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 (97) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +2 -1
  3. package/builtin-skills/_shared/_todo_lib.py +290 -0
  4. package/builtin-skills/even-g2-setup/main.ts +896 -0
  5. package/builtin-skills/even-g2-setup/skill.yaml +133 -0
  6. package/builtin-skills/memo-delete/main.py +28 -107
  7. package/builtin-skills/memo-delete/skill.yaml +10 -21
  8. package/builtin-skills/memo-index/main.py +96 -64
  9. package/builtin-skills/memo-index/skill.yaml +4 -10
  10. package/builtin-skills/memo-list/main.py +126 -72
  11. package/builtin-skills/memo-list/skill.yaml +8 -14
  12. package/builtin-skills/memo-save/main.py +191 -25
  13. package/builtin-skills/memo-save/skill.yaml +29 -5
  14. package/builtin-skills/memo-search/main.py +38 -18
  15. package/builtin-skills/memo-vector-search/main.py +11 -6
  16. package/builtin-skills/nightly-topic-knowledge/_feedback_lib.py +391 -0
  17. package/builtin-skills/nightly-topic-knowledge/_topics_lib.py +415 -0
  18. package/builtin-skills/nightly-topic-knowledge/main.py +403 -0
  19. package/builtin-skills/nightly-topic-knowledge/skill.yaml +88 -0
  20. package/builtin-skills/schedule-add/main.py +26 -0
  21. package/builtin-skills/service-restart/main.ts +249 -0
  22. package/builtin-skills/service-restart/skill.yaml +49 -0
  23. package/builtin-skills/todo-add/main.py +3 -1
  24. package/builtin-skills/todo-delete/main.py +3 -1
  25. package/builtin-skills/todo-done/main.py +3 -1
  26. package/builtin-skills/todo-list/main.py +4 -1
  27. package/builtin-skills/todo-tick/main.py +3 -1
  28. package/builtin-skills/topic-knowledge-read/main.py +118 -0
  29. package/builtin-skills/topic-knowledge-read/skill.yaml +49 -0
  30. package/dist/builder/build-action-classifier.d.ts +18 -0
  31. package/dist/builder/build-action-classifier.js +82 -1
  32. package/dist/builder/build-flow.d.ts +33 -4
  33. package/dist/builder/build-flow.js +251 -89
  34. package/dist/builder/builder-session.d.ts +1 -1
  35. package/dist/builder/builder-session.js +112 -7
  36. package/dist/builder/conversation-router.d.ts +4 -2
  37. package/dist/builder/conversation-router.js +19 -2
  38. package/dist/cli/index.js +323 -20
  39. package/dist/core/ai-provider.d.ts +1 -0
  40. package/dist/core/ai-provider.js +8 -3
  41. package/dist/core/chat-engine.d.ts +9 -3
  42. package/dist/core/chat-engine.js +1263 -146
  43. package/dist/core/config.d.ts +4 -0
  44. package/dist/core/config.js +82 -0
  45. package/dist/core/daily-memory-promotion.d.ts +7 -0
  46. package/dist/core/daily-memory-promotion.js +596 -18
  47. package/dist/core/image-attachments.d.ts +31 -0
  48. package/dist/core/image-attachments.js +237 -0
  49. package/dist/core/logger.d.ts +2 -1
  50. package/dist/core/logger.js +77 -1
  51. package/dist/core/memo-migration.d.ts +3 -0
  52. package/dist/core/memo-migration.js +422 -0
  53. package/dist/core/native-modules.d.ts +24 -0
  54. package/dist/core/native-modules.js +99 -0
  55. package/dist/core/notifier.d.ts +8 -3
  56. package/dist/core/notifier.js +191 -17
  57. package/dist/core/obsidian-vault.d.ts +19 -0
  58. package/dist/core/obsidian-vault.js +477 -0
  59. package/dist/core/operating-model.d.ts +2 -0
  60. package/dist/core/operating-model.js +15 -0
  61. package/dist/core/output-writer.d.ts +3 -2
  62. package/dist/core/output-writer.js +108 -7
  63. package/dist/core/profile-memory.js +22 -1
  64. package/dist/core/runtime.d.ts +2 -0
  65. package/dist/core/runtime.js +9 -1
  66. package/dist/core/secrets.d.ts +4 -0
  67. package/dist/core/secrets.js +34 -0
  68. package/dist/core/skill-history.d.ts +44 -0
  69. package/dist/core/skill-history.js +329 -0
  70. package/dist/core/skill-registry.d.ts +5 -0
  71. package/dist/core/skill-registry.js +11 -0
  72. package/dist/discord/bot.d.ts +1 -0
  73. package/dist/discord/bot.js +181 -10
  74. package/dist/even-g2/gateway.d.ts +15 -0
  75. package/dist/even-g2/gateway.js +868 -0
  76. package/dist/runtimes/codex-app-server.d.ts +5 -1
  77. package/dist/runtimes/codex-app-server.js +147 -8
  78. package/dist/runtimes/python-runner.js +82 -0
  79. package/dist/runtimes/typescript-runner.js +13 -1
  80. package/dist/skills-sdk/types.d.ts +19 -4
  81. package/dist/telegram/bot.d.ts +1 -0
  82. package/dist/telegram/bot.js +115 -7
  83. package/package.json +3 -1
  84. package/templates/even-g2-agent/README.md +83 -0
  85. package/templates/even-g2-agent/app.json +20 -0
  86. package/templates/even-g2-agent/index.html +31 -0
  87. package/templates/even-g2-agent/package-lock.json +1836 -0
  88. package/templates/even-g2-agent/package.json +22 -0
  89. package/templates/even-g2-agent/scripts/qr-auto.mjs +182 -0
  90. package/templates/even-g2-agent/src/embedded-config.ts +4 -0
  91. package/templates/even-g2-agent/src/main.ts +539 -0
  92. package/templates/even-g2-agent/src/style.css +70 -0
  93. package/templates/even-g2-agent/tsconfig.json +11 -0
  94. package/templates/skill-python/main.py +20 -2
  95. package/templates/skill-python/skill.yaml +9 -0
  96. package/templates/skill-typescript/main.ts +40 -5
  97. package/templates/skill-typescript/skill.yaml +9 -0
@@ -0,0 +1,31 @@
1
+ export interface LocalFileAttachment {
2
+ path: string;
3
+ filename: string;
4
+ mimeType: string;
5
+ size: number;
6
+ isImage: boolean;
7
+ }
8
+ export interface LocalImageAttachment extends LocalFileAttachment {
9
+ isImage: true;
10
+ }
11
+ export interface CollectLocalFileAttachmentOptions {
12
+ paths?: string[];
13
+ cwd?: string;
14
+ allowedRoots?: string[];
15
+ maxFiles?: number;
16
+ maxBytes?: number;
17
+ }
18
+ export interface CollectLocalImageAttachmentOptions extends CollectLocalFileAttachmentOptions {
19
+ text?: string;
20
+ }
21
+ interface LocalCandidate {
22
+ value: string;
23
+ trusted: boolean;
24
+ }
25
+ export declare function collectLocalFileAttachments(options: CollectLocalFileAttachmentOptions): Promise<LocalFileAttachment[]>;
26
+ export declare function collectLocalImageAttachments(options: CollectLocalImageAttachmentOptions): Promise<LocalImageAttachment[]>;
27
+ export declare function extractLocalImageCandidates(text: string): LocalCandidate[];
28
+ export declare function detectImageMimeTypeFromBuffer(buffer: Buffer, fallback?: string): string;
29
+ export declare function imageExtensionForMimeType(mimeType: string): string;
30
+ export declare function guessFileMimeType(filePath: string): string;
31
+ export {};
@@ -0,0 +1,237 @@
1
+ import { realpath, stat } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { guessImageMimeType, isImageLikeFile } from "./message-utils.js";
6
+ const DEFAULT_MAX_FILES = 4;
7
+ const DEFAULT_MAX_BYTES = 20 * 1024 * 1024;
8
+ const IMAGE_EXT_PATTERN = /\.(png|jpe?g|gif|webp|bmp|heic|heif)(?=$|[?#])/i;
9
+ const MARKDOWN_IMAGE_PATTERN = /!\[[^\]\n]*\]\(([^)\n]+)\)/g;
10
+ const MARKDOWN_LINK_PATTERN = /\[[^\]\n]*\]\(([^)\n]+)\)/g;
11
+ const CODE_PATH_PATTERN = /`([^`\n]+\.(?:png|jpe?g|gif|webp|bmp|heic|heif)(?:[?#][^`\n]*)?)`/gi;
12
+ const RAW_PATH_PATTERN = /(^|[\s(["'])((?:~\/|\.{1,2}\/|\/|[A-Za-z]:[\\/])[^<>"'`()\s]+\.(?:png|jpe?g|gif|webp|bmp|heic|heif)(?:[?#][^<>"'`()\s]*)?)/gi;
13
+ const FILE_MIME_TYPES = {
14
+ ".txt": "text/plain",
15
+ ".text": "text/plain",
16
+ ".md": "text/markdown",
17
+ ".markdown": "text/markdown",
18
+ ".json": "application/json",
19
+ ".jsonl": "application/x-ndjson",
20
+ ".csv": "text/csv",
21
+ ".tsv": "text/tab-separated-values",
22
+ ".yaml": "application/yaml",
23
+ ".yml": "application/yaml",
24
+ ".xml": "application/xml",
25
+ ".html": "text/html",
26
+ ".htm": "text/html",
27
+ ".css": "text/css",
28
+ ".js": "text/javascript",
29
+ ".mjs": "text/javascript",
30
+ ".ts": "text/typescript",
31
+ ".pdf": "application/pdf",
32
+ ".zip": "application/zip",
33
+ ".gz": "application/gzip",
34
+ ".tar": "application/x-tar",
35
+ ".mp3": "audio/mpeg",
36
+ ".wav": "audio/wav",
37
+ ".mp4": "video/mp4",
38
+ ".mov": "video/quicktime",
39
+ };
40
+ export async function collectLocalFileAttachments(options) {
41
+ return collectLocalAttachments((options.paths || []).map((value) => ({ value, trusted: true })), options, { requireImage: false });
42
+ }
43
+ export async function collectLocalImageAttachments(options) {
44
+ const candidates = [
45
+ ...(options.paths || []).map((value) => ({ value, trusted: true })),
46
+ ...extractLocalImageCandidates(options.text || ""),
47
+ ];
48
+ const attachments = await collectLocalAttachments(candidates, options, { requireImage: true });
49
+ return attachments.filter((attachment) => attachment.isImage);
50
+ }
51
+ async function collectLocalAttachments(candidates, options, collectOptions) {
52
+ const cwd = options.cwd || process.cwd();
53
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
54
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
55
+ const allowedRoots = await resolveAllowedRoots(options.allowedRoots || []);
56
+ const attachments = [];
57
+ const seen = new Set();
58
+ for (const candidate of candidates) {
59
+ if (attachments.length >= maxFiles)
60
+ break;
61
+ const resolved = resolveLocalPathCandidate(candidate.value, cwd);
62
+ if (!resolved)
63
+ continue;
64
+ const attachment = await inspectLocalAttachment(resolved, {
65
+ trusted: candidate.trusted,
66
+ allowedRoots,
67
+ maxBytes,
68
+ requireImage: collectOptions.requireImage,
69
+ });
70
+ if (!attachment || seen.has(attachment.path))
71
+ continue;
72
+ seen.add(attachment.path);
73
+ attachments.push(attachment);
74
+ }
75
+ return attachments;
76
+ }
77
+ export function extractLocalImageCandidates(text) {
78
+ const candidates = [];
79
+ const add = (value) => {
80
+ const cleaned = cleanCandidateRef(value);
81
+ if (!cleaned || !IMAGE_EXT_PATTERN.test(cleaned))
82
+ return;
83
+ candidates.push({ value: cleaned, trusted: false });
84
+ };
85
+ for (const match of text.matchAll(MARKDOWN_IMAGE_PATTERN)) {
86
+ add(match[1]);
87
+ }
88
+ for (const match of text.matchAll(MARKDOWN_LINK_PATTERN)) {
89
+ add(match[1]);
90
+ }
91
+ for (const match of text.matchAll(CODE_PATH_PATTERN)) {
92
+ add(match[1]);
93
+ }
94
+ for (const match of text.matchAll(RAW_PATH_PATTERN)) {
95
+ add(match[2]);
96
+ }
97
+ return candidates;
98
+ }
99
+ export function detectImageMimeTypeFromBuffer(buffer, fallback = "image/png") {
100
+ if (buffer.length >= 8 && buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
101
+ return "image/png";
102
+ }
103
+ if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
104
+ return "image/jpeg";
105
+ }
106
+ if (buffer.length >= 6 && (buffer.subarray(0, 6).toString("ascii") === "GIF87a" || buffer.subarray(0, 6).toString("ascii") === "GIF89a")) {
107
+ return "image/gif";
108
+ }
109
+ if (buffer.length >= 12 &&
110
+ buffer.subarray(0, 4).toString("ascii") === "RIFF" &&
111
+ buffer.subarray(8, 12).toString("ascii") === "WEBP") {
112
+ return "image/webp";
113
+ }
114
+ if (buffer.length >= 2 && buffer[0] === 0x42 && buffer[1] === 0x4d) {
115
+ return "image/bmp";
116
+ }
117
+ return fallback;
118
+ }
119
+ export function imageExtensionForMimeType(mimeType) {
120
+ const normalized = mimeType.toLowerCase().split(";")[0].trim();
121
+ if (normalized === "image/jpeg")
122
+ return ".jpg";
123
+ if (normalized === "image/gif")
124
+ return ".gif";
125
+ if (normalized === "image/webp")
126
+ return ".webp";
127
+ if (normalized === "image/bmp")
128
+ return ".bmp";
129
+ if (normalized === "image/heic")
130
+ return ".heic";
131
+ if (normalized === "image/heif")
132
+ return ".heif";
133
+ return ".png";
134
+ }
135
+ export function guessFileMimeType(filePath) {
136
+ if (isImageLikeFile(undefined, filePath)) {
137
+ return guessImageMimeType(filePath);
138
+ }
139
+ const ext = path.extname(filePath).toLowerCase();
140
+ return FILE_MIME_TYPES[ext] || "application/octet-stream";
141
+ }
142
+ function cleanCandidateRef(value) {
143
+ let ref = value.trim();
144
+ const titleMatch = ref.match(/^<([^>]+)>/);
145
+ if (titleMatch) {
146
+ ref = titleMatch[1];
147
+ }
148
+ else {
149
+ ref = ref.replace(/\s+["'][^"']*["']\s*$/, "");
150
+ }
151
+ ref = ref.replace(/^['"<]+/, "").replace(/[)'">.,;:]+$/g, "");
152
+ return ref.trim();
153
+ }
154
+ function resolveLocalPathCandidate(value, cwd) {
155
+ let ref = cleanCandidateRef(value);
156
+ if (!ref)
157
+ return null;
158
+ if (/^(https?|data|attachment):/i.test(ref))
159
+ return null;
160
+ if (ref.startsWith("sandbox:")) {
161
+ ref = ref.slice("sandbox:".length);
162
+ }
163
+ if (ref.startsWith("file://")) {
164
+ try {
165
+ ref = fileURLToPath(ref);
166
+ }
167
+ catch {
168
+ return null;
169
+ }
170
+ }
171
+ try {
172
+ ref = decodeURIComponent(ref);
173
+ }
174
+ catch {
175
+ // Keep the original string if it was not URI-encoded.
176
+ }
177
+ if (ref.startsWith("~/")) {
178
+ ref = path.join(os.homedir(), ref.slice(2));
179
+ }
180
+ if (!path.isAbsolute(ref)) {
181
+ ref = path.resolve(cwd, ref);
182
+ }
183
+ return ref;
184
+ }
185
+ async function resolveAllowedRoots(roots) {
186
+ const out = [];
187
+ for (const root of roots) {
188
+ if (!root)
189
+ continue;
190
+ try {
191
+ out.push(await realpath(root));
192
+ }
193
+ catch {
194
+ out.push(path.resolve(root));
195
+ }
196
+ }
197
+ return out;
198
+ }
199
+ async function inspectLocalAttachment(filePath, options) {
200
+ let real;
201
+ try {
202
+ real = await realpath(filePath);
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ if (!options.trusted && options.allowedRoots.length > 0 && !isUnderAllowedRoot(real, options.allowedRoots)) {
208
+ return null;
209
+ }
210
+ let info;
211
+ try {
212
+ info = await stat(real);
213
+ }
214
+ catch {
215
+ return null;
216
+ }
217
+ if (!info.isFile() || info.size <= 0 || info.size > options.maxBytes) {
218
+ return null;
219
+ }
220
+ const isImage = isImageLikeFile(undefined, real);
221
+ if (options.requireImage && !isImage) {
222
+ return null;
223
+ }
224
+ return {
225
+ path: real,
226
+ filename: path.basename(real),
227
+ mimeType: guessFileMimeType(real),
228
+ size: info.size,
229
+ isImage,
230
+ };
231
+ }
232
+ function isUnderAllowedRoot(filePath, roots) {
233
+ return roots.some((root) => {
234
+ const relative = path.relative(root, filePath);
235
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
236
+ });
237
+ }
@@ -19,7 +19,7 @@ export interface RunLogRecord {
19
19
  }>;
20
20
  }
21
21
  export type EventLogLevel = "info" | "warn" | "error";
22
- export type EventLogSource = "cli" | "chat" | "skill" | "setup" | "build" | "schedule" | "discord" | "telegram";
22
+ export type EventLogSource = "cli" | "chat" | "skill" | "setup" | "build" | "schedule" | "discord" | "telegram" | "g2";
23
23
  export interface EventLogEntry {
24
24
  ts?: string;
25
25
  level: EventLogLevel;
@@ -55,6 +55,7 @@ export interface ConversationLogEntry {
55
55
  export declare function appendConversationLog(config: AppConfig, entry: ConversationLogEntry): Promise<void>;
56
56
  export declare function dailyConversationMemoryFile(config: AppConfig, date?: Date): string;
57
57
  export declare function pruneOldLogs(config: AppConfig): Promise<void>;
58
+ export declare function pruneOrphanAttachments(config: AppConfig): Promise<void>;
58
59
  export declare function readEventLog(config: AppConfig, options?: ReadEventLogOptions): Promise<ReadEventLogResult[]>;
59
60
  export declare function listRunLogs(config: AppConfig, skillId?: string): Promise<RunLogRecord[]>;
60
61
  export declare function readRunLog(config: AppConfig, runId: string): Promise<RunLogRecord>;
@@ -64,7 +64,16 @@ async function appendDailyConversationMemory(config, entry) {
64
64
  function formatConversationMarkdownEntry(entry) {
65
65
  const label = [entry.source, entry.role].filter(Boolean).join(" ");
66
66
  const content = entry.content.trim() || "(empty)";
67
- return [`## ${entry.ts} ${label}`, "", content].join("\n");
67
+ return [`## ${formatConversationDisplayTime(entry.ts)} ${label}`, "", content].join("\n");
68
+ }
69
+ function formatConversationDisplayTime(timestamp) {
70
+ const date = new Date(timestamp);
71
+ if (Number.isNaN(date.getTime())) {
72
+ return timestamp;
73
+ }
74
+ const HH = String(date.getHours()).padStart(2, "0");
75
+ const mm = String(date.getMinutes()).padStart(2, "0");
76
+ return `${HH}:${mm}`;
68
77
  }
69
78
  async function readTextIfExists(file) {
70
79
  try {
@@ -111,6 +120,73 @@ export async function pruneOldLogs(config) {
111
120
  return head ? Date.parse(head) : NaN;
112
121
  });
113
122
  }
123
+ await pruneOrphanAttachments(config);
124
+ }
125
+ const ATTACHMENT_RETENTION_DAYS = 7;
126
+ const ATTACHMENT_REFERENCE_PATTERN = /!\[[^\]]*\]\(([^)]+)\)/g;
127
+ export async function pruneOrphanAttachments(config) {
128
+ const attachmentsDir = path.join(config.memory_dir, "attachments");
129
+ let attachmentEntries;
130
+ try {
131
+ attachmentEntries = (await readdir(attachmentsDir, { recursive: true }));
132
+ }
133
+ catch {
134
+ return;
135
+ }
136
+ const cutoff = Date.now() - ATTACHMENT_RETENTION_DAYS * 24 * 60 * 60 * 1000;
137
+ // 参照元は daily memory (memory_dir) と user-facing memo (notes_dir) の両方を見る。
138
+ const referenced = new Set();
139
+ for (const base of [config.memory_dir, config.notes_dir]) {
140
+ for (const name of await collectReferencedAttachmentBasenames(base)) {
141
+ referenced.add(name);
142
+ }
143
+ }
144
+ for (const rel of attachmentEntries) {
145
+ const full = path.join(attachmentsDir, rel);
146
+ try {
147
+ const info = await stat(full);
148
+ if (!info.isFile())
149
+ continue;
150
+ if (info.mtimeMs >= cutoff)
151
+ continue;
152
+ if (referenced.has(path.basename(full)))
153
+ continue;
154
+ await rm(full, { force: true });
155
+ }
156
+ catch {
157
+ // ignore
158
+ }
159
+ }
160
+ }
161
+ async function collectReferencedAttachmentBasenames(rootDir) {
162
+ const referenced = new Set();
163
+ let entries;
164
+ try {
165
+ entries = (await readdir(rootDir, { recursive: true }));
166
+ }
167
+ catch {
168
+ return referenced;
169
+ }
170
+ for (const rel of entries) {
171
+ if (!rel.endsWith(".md"))
172
+ continue;
173
+ if (rel === "attachments" || rel.startsWith(`attachments${path.sep}`))
174
+ continue;
175
+ let raw;
176
+ try {
177
+ raw = await readFile(path.join(rootDir, rel), "utf8");
178
+ }
179
+ catch {
180
+ continue;
181
+ }
182
+ for (const match of raw.matchAll(ATTACHMENT_REFERENCE_PATTERN)) {
183
+ const ref = match[1].trim();
184
+ const base = path.basename(ref);
185
+ if (base)
186
+ referenced.add(base);
187
+ }
188
+ }
189
+ return referenced;
114
190
  }
115
191
  async function pruneLinesByLeadingTimestamp(file, cutoff, extractTime) {
116
192
  let raw;
@@ -0,0 +1,3 @@
1
+ import type { AppConfig } from "./config.js";
2
+ export declare function migrateMemoFlatStructure(config: AppConfig): Promise<void>;
3
+ export declare function formatLocalDateTime(date: Date): string;