agent-sin 0.1.11 → 0.1.15

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 +179 -0
  11. package/builtin-skills/memo-list/skill.yaml +51 -0
  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 +10 -3
  42. package/dist/core/chat-engine.js +1563 -197
  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 +568 -14
  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 +13 -0
  73. package/dist/discord/bot.js +542 -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 +122 -31
  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
@@ -1,15 +1,18 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import { collectLocalFileAttachments, } from "./image-attachments.js";
2
4
  import { l } from "./i18n.js";
3
5
  const DISCORD_API_BASE = "https://discord.com/api/v10";
4
6
  const DISCORD_MESSAGE_LIMIT = 1900;
5
7
  const TELEGRAM_API_BASE = "https://api.telegram.org";
6
8
  const TELEGRAM_MESSAGE_LIMIT = 3900;
7
9
  export async function notify(options) {
8
- const title = sanitize(options.title);
9
- const body = sanitize(options.body);
10
+ const title = sanitize(options.title || "");
11
+ const body = sanitize(options.body || "");
10
12
  const subtitle = options.subtitle ? sanitize(options.subtitle) : undefined;
11
- if (!title && !body) {
12
- throw new Error(l("notify: title or body is required", "notify: title または body が必要です"));
13
+ const attachmentPaths = normalizeAttachmentPaths(options);
14
+ if (!title && !body && attachmentPaths.length === 0) {
15
+ throw new Error(l("notify: title, body, or filePath is required", "notify: title、body、または filePath が必要です"));
13
16
  }
14
17
  const requested = options.channel || "auto";
15
18
  const channel = requested === "auto" ? resolveAutoChannel() : requested;
@@ -22,19 +25,30 @@ export async function notify(options) {
22
25
  case "windows":
23
26
  return notifyWindows(title, body, subtitle);
24
27
  case "discord":
25
- return notifyDiscord(title, body, subtitle, options.discordThreadId);
28
+ return notifyDiscord(title, body, subtitle, options.discordThreadId, attachmentPaths, options.cwd);
26
29
  case "telegram":
27
- return notifyTelegram(title, body, subtitle, options.telegramThreadId);
30
+ return notifyTelegram(title, body, subtitle, options.telegramThreadId, attachmentPaths, options.cwd);
28
31
  case "slack":
29
32
  return notifySlack(title, body, subtitle);
30
33
  case "mail":
31
34
  return notifyMail(title, body, subtitle, options.to);
35
+ case "g2":
36
+ return notifyG2(title, body, subtitle);
32
37
  case "stderr":
33
38
  return notifyStderr(title, body, subtitle);
34
39
  default:
35
40
  return notifyStderr(title, body, subtitle);
36
41
  }
37
42
  }
43
+ function normalizeAttachmentPaths(options) {
44
+ const paths = [
45
+ options.filePath,
46
+ options.imagePath,
47
+ ...(Array.isArray(options.filePaths) ? options.filePaths : []),
48
+ ...(Array.isArray(options.imagePaths) ? options.imagePaths : []),
49
+ ].filter((item) => typeof item === "string" && item.trim().length > 0);
50
+ return [...new Set(paths)];
51
+ }
38
52
  function resolveAutoChannel() {
39
53
  if (process.env.AGENT_SIN_NOTIFY_BACKEND === "stderr") {
40
54
  return "stderr";
@@ -105,10 +119,14 @@ async function notifyWindows(title, body, subtitle) {
105
119
  // Fallback: stderr so the message is not lost on systems without WinRT toast support.
106
120
  return notifyStderr(title, body, subtitle);
107
121
  }
108
- async function notifyDiscord(title, body, subtitle, threadIdOverride) {
122
+ async function notifyDiscord(title, body, subtitle, threadIdOverride, attachmentPaths, attachmentCwd) {
109
123
  const url = process.env.AGENT_SIN_DISCORD_WEBHOOK_URL;
110
124
  const content = formatPlainTextMessage(title, body, subtitle);
111
- const chunks = splitMessage(content, DISCORD_MESSAGE_LIMIT);
125
+ const chunks = content.trim() ? splitMessage(content, DISCORD_MESSAGE_LIMIT) : [];
126
+ const attachments = await collectLocalFileAttachments({ paths: attachmentPaths, cwd: attachmentCwd });
127
+ if (attachmentPaths.length > 0 && attachments.length === 0) {
128
+ return { channel: "discord", ok: false, detail: "No readable attachment file was found" };
129
+ }
112
130
  const rawThreadId = discordNotifyThreadId(threadIdOverride);
113
131
  const threadId = normalizeDiscordSnowflake(rawThreadId);
114
132
  if (rawThreadId && !threadId) {
@@ -116,12 +134,18 @@ async function notifyDiscord(title, body, subtitle, threadIdOverride) {
116
134
  }
117
135
  if (url) {
118
136
  const webhookUrl = discordWebhookUrl(url, threadId);
119
- return postDiscordChunks(chunks, (chunk) => postWebhook(webhookUrl, { content: chunk }, "discord"));
137
+ const textResult = await postDiscordChunks(chunks, (chunk) => postWebhook(webhookUrl, { content: chunk }, "discord"));
138
+ if (!textResult.ok)
139
+ return textResult;
140
+ return postDiscordAttachments(attachments, (attachment) => postDiscordWebhookAttachment(webhookUrl, attachment));
120
141
  }
121
142
  const token = process.env.AGENT_SIN_DISCORD_BOT_TOKEN;
122
143
  const targetId = threadId || discordNotifyChannelId();
123
144
  if (token && targetId) {
124
- return postDiscordChunks(chunks, (chunk) => postDiscordBotMessage(token, targetId, chunk));
145
+ const textResult = await postDiscordChunks(chunks, (chunk) => postDiscordBotMessage(token, targetId, chunk));
146
+ if (!textResult.ok)
147
+ return textResult;
148
+ return postDiscordAttachments(attachments, (attachment) => postDiscordBotAttachment(token, targetId, attachment));
125
149
  }
126
150
  return {
127
151
  channel: "discord",
@@ -137,7 +161,7 @@ async function notifySlack(title, body, subtitle) {
137
161
  const text = formatPlainTextMessage(title, body, subtitle);
138
162
  return postWebhook(url, { text }, "slack");
139
163
  }
140
- async function notifyTelegram(title, body, subtitle, threadIdOverride) {
164
+ async function notifyTelegram(title, body, subtitle, threadIdOverride, attachmentPaths, attachmentCwd) {
141
165
  const token = process.env.AGENT_SIN_TELEGRAM_BOT_TOKEN;
142
166
  const chatId = telegramNotifyChatId();
143
167
  if (!token) {
@@ -152,8 +176,15 @@ async function notifyTelegram(title, body, subtitle, threadIdOverride) {
152
176
  return { channel: "telegram", ok: false, detail: `invalid Telegram thread id: ${rawThreadId}` };
153
177
  }
154
178
  const content = formatPlainTextMessage(title, body, subtitle);
155
- const chunks = splitMessage(content, TELEGRAM_MESSAGE_LIMIT);
156
- return postTelegramChunks(chunks, (chunk) => postTelegramBotMessage(token, chatId, chunk, threadId));
179
+ const chunks = content.trim() ? splitMessage(content, TELEGRAM_MESSAGE_LIMIT) : [];
180
+ const attachments = await collectLocalFileAttachments({ paths: attachmentPaths, cwd: attachmentCwd });
181
+ if (attachmentPaths.length > 0 && attachments.length === 0) {
182
+ return { channel: "telegram", ok: false, detail: "No readable attachment file was found" };
183
+ }
184
+ const textResult = await postTelegramChunks(chunks, (chunk) => postTelegramBotMessage(token, chatId, chunk, threadId));
185
+ if (!textResult.ok)
186
+ return textResult;
187
+ return postTelegramAttachments(attachments, (attachment) => postTelegramBotAttachment(token, chatId, attachment, threadId));
157
188
  }
158
189
  async function notifyMail(title, body, subtitle, toOverride) {
159
190
  const host = process.env.AGENT_SIN_SMTP_HOST;
@@ -211,14 +242,47 @@ async function notifyMail(title, body, subtitle, toOverride) {
211
242
  };
212
243
  }
213
244
  }
245
+ async function notifyG2(title, body, subtitle) {
246
+ const url = process.env.AGENT_SIN_G2_NOTIFY_URL || defaultG2NotifyUrl();
247
+ const token = process.env.AGENT_SIN_G2_TOKEN || "";
248
+ const headers = { "content-type": "application/json" };
249
+ if (token) {
250
+ headers.authorization = `Bearer ${token}`;
251
+ }
252
+ try {
253
+ const response = await fetch(url, {
254
+ method: "POST",
255
+ headers,
256
+ body: JSON.stringify({ title, body, subtitle }),
257
+ });
258
+ if (!response.ok) {
259
+ return { channel: "g2", ok: false, detail: `HTTP ${response.status} ${response.statusText}`.trim() };
260
+ }
261
+ return { channel: "g2", ok: true };
262
+ }
263
+ catch (error) {
264
+ return {
265
+ channel: "g2",
266
+ ok: false,
267
+ detail: error instanceof Error ? error.message : String(error),
268
+ };
269
+ }
270
+ }
271
+ function defaultG2NotifyUrl() {
272
+ const port = process.env.AGENT_SIN_G2_PORT || "8765";
273
+ return `http://127.0.0.1:${port}/api/notify`;
274
+ }
214
275
  function notifyStderr(title, body, subtitle) {
215
- const head = subtitle ? `${title} - ${subtitle}` : title;
216
- process.stderr.write(`[notify] ${head}: ${body}\n`);
276
+ const head = [title, subtitle].filter(Boolean).join(" - ");
277
+ const text = head && body ? `${head}: ${body}` : (body || head);
278
+ process.stderr.write(`[notify] ${text}\n`);
217
279
  return { channel: "stderr", ok: true };
218
280
  }
219
281
  function formatPlainTextMessage(title, body, subtitle) {
220
- const head = subtitle ? `*${title}* ${subtitle}` : `*${title}*`;
221
- return body ? `${head}\n${body}` : head;
282
+ const head = title ? (subtitle ? `*${title}* - ${subtitle}` : `*${title}*`) : (subtitle || "");
283
+ if (head && body)
284
+ return `${head}\n${body}`;
285
+ return body || head;
222
286
  }
223
287
  function hasDiscordBotNotifyConfig() {
224
288
  return Boolean(process.env.AGENT_SIN_DISCORD_BOT_TOKEN && (discordNotifyThreadId() || discordNotifyChannelId()));
@@ -317,6 +381,19 @@ async function postDiscordChunks(chunks, send) {
317
381
  }
318
382
  return { channel: "discord", ok: true, detail: chunks.length > 1 ? `${chunks.length} messages` : undefined };
319
383
  }
384
+ async function postDiscordAttachments(attachments, send) {
385
+ let sent = 0;
386
+ for (const attachment of attachments) {
387
+ const result = await send(attachment);
388
+ if (!result.ok) {
389
+ return sent > 0
390
+ ? { channel: "discord", ok: false, detail: `sent ${sent}/${attachments.length} attachments: ${result.detail || "failed"}` }
391
+ : result;
392
+ }
393
+ sent += 1;
394
+ }
395
+ return { channel: "discord", ok: true, detail: attachments.length > 1 ? `${attachments.length} attachments` : undefined };
396
+ }
320
397
  async function postDiscordBotMessage(token, channelId, content) {
321
398
  try {
322
399
  const response = await fetch(`${DISCORD_API_BASE}/channels/${channelId}/messages`, {
@@ -341,6 +418,53 @@ async function postDiscordBotMessage(token, channelId, content) {
341
418
  };
342
419
  }
343
420
  }
421
+ async function postDiscordBotAttachment(token, channelId, attachment) {
422
+ try {
423
+ const form = new FormData();
424
+ form.append("payload_json", JSON.stringify({ content: "" }));
425
+ form.append("files[0]", await blobFromLocalAttachment(attachment), attachment.filename);
426
+ const response = await fetch(`${DISCORD_API_BASE}/channels/${channelId}/messages`, {
427
+ method: "POST",
428
+ headers: { authorization: `Bot ${token}` },
429
+ body: form,
430
+ });
431
+ if (!response.ok) {
432
+ const detail = `HTTP ${response.status} ${response.statusText}`.trim();
433
+ return { channel: "discord", ok: false, detail };
434
+ }
435
+ return { channel: "discord", ok: true };
436
+ }
437
+ catch (error) {
438
+ return {
439
+ channel: "discord",
440
+ ok: false,
441
+ detail: error instanceof Error ? error.message : String(error),
442
+ };
443
+ }
444
+ }
445
+ async function postDiscordWebhookAttachment(url, attachment) {
446
+ try {
447
+ const form = new FormData();
448
+ form.append("payload_json", JSON.stringify({ content: "" }));
449
+ form.append("files[0]", await blobFromLocalAttachment(attachment), attachment.filename);
450
+ const response = await fetch(url, {
451
+ method: "POST",
452
+ body: form,
453
+ });
454
+ if (!response.ok) {
455
+ const detail = `HTTP ${response.status} ${response.statusText}`.trim();
456
+ return { channel: "discord", ok: false, detail };
457
+ }
458
+ return { channel: "discord", ok: true };
459
+ }
460
+ catch (error) {
461
+ return {
462
+ channel: "discord",
463
+ ok: false,
464
+ detail: error instanceof Error ? error.message : String(error),
465
+ };
466
+ }
467
+ }
344
468
  async function postTelegramChunks(chunks, send) {
345
469
  let sent = 0;
346
470
  for (const chunk of chunks) {
@@ -354,6 +478,19 @@ async function postTelegramChunks(chunks, send) {
354
478
  }
355
479
  return { channel: "telegram", ok: true, detail: chunks.length > 1 ? `${chunks.length} messages` : undefined };
356
480
  }
481
+ async function postTelegramAttachments(attachments, send) {
482
+ let sent = 0;
483
+ for (const attachment of attachments) {
484
+ const result = await send(attachment);
485
+ if (!result.ok) {
486
+ return sent > 0
487
+ ? { channel: "telegram", ok: false, detail: `sent ${sent}/${attachments.length} attachments: ${result.detail || "failed"}` }
488
+ : result;
489
+ }
490
+ sent += 1;
491
+ }
492
+ return { channel: "telegram", ok: true, detail: attachments.length > 1 ? `${attachments.length} attachments` : undefined };
493
+ }
357
494
  async function postTelegramBotMessage(token, chatId, text, threadId) {
358
495
  try {
359
496
  const payload = {
@@ -383,6 +520,43 @@ async function postTelegramBotMessage(token, chatId, text, threadId) {
383
520
  };
384
521
  }
385
522
  }
523
+ async function postTelegramBotAttachment(token, chatId, attachment, threadId) {
524
+ const method = shouldSendTelegramAsPhoto(attachment) ? "sendPhoto" : "sendDocument";
525
+ const fileField = method === "sendPhoto" ? "photo" : "document";
526
+ try {
527
+ const form = new FormData();
528
+ form.append("chat_id", chatId);
529
+ if (threadId) {
530
+ form.append("message_thread_id", threadId);
531
+ }
532
+ form.append(fileField, await blobFromLocalAttachment(attachment), attachment.filename);
533
+ const response = await fetch(`${TELEGRAM_API_BASE}/bot${token}/${method}`, {
534
+ method: "POST",
535
+ body: form,
536
+ });
537
+ if (!response.ok) {
538
+ const detail = `HTTP ${response.status} ${response.statusText}`.trim();
539
+ return { channel: "telegram", ok: false, detail };
540
+ }
541
+ return { channel: "telegram", ok: true };
542
+ }
543
+ catch (error) {
544
+ return {
545
+ channel: "telegram",
546
+ ok: false,
547
+ detail: error instanceof Error ? error.message : String(error),
548
+ };
549
+ }
550
+ }
551
+ function shouldSendTelegramAsPhoto(attachment) {
552
+ return attachment.isImage && ["image/png", "image/jpeg", "image/webp"].includes(attachment.mimeType.toLowerCase());
553
+ }
554
+ async function blobFromLocalAttachment(attachment) {
555
+ const buffer = await readFile(attachment.path);
556
+ const bytes = new Uint8Array(buffer.length);
557
+ bytes.set(buffer);
558
+ return new Blob([bytes.buffer], { type: attachment.mimeType });
559
+ }
386
560
  async function postWebhook(url, payload, channel) {
387
561
  try {
388
562
  const response = await fetch(url, {
@@ -0,0 +1,19 @@
1
+ import type { AppConfig } from "./config.js";
2
+ export interface ObsidianVaultLink {
3
+ name: string;
4
+ target: string;
5
+ created: boolean;
6
+ ok: boolean;
7
+ }
8
+ export interface ObsidianVaultSetupResult {
9
+ enabled: boolean;
10
+ vaultDir: string;
11
+ created: boolean;
12
+ links: ObsidianVaultLink[];
13
+ homeCreated: boolean;
14
+ configCreated: boolean;
15
+ symlinkError?: string;
16
+ }
17
+ export declare function defaultObsidianVaultDir(): string;
18
+ export declare function resolvedObsidianVaultDir(config: AppConfig): string;
19
+ export declare function ensureObsidianVault(config: AppConfig): Promise<ObsidianVaultSetupResult>;