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
package/dist/cli/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import path from "node:path";
3
+ import { randomBytes } from "node:crypto";
4
+ import { networkInterfaces } from "node:os";
3
5
  import { mkdir, rm, stat } from "node:fs/promises";
4
6
  import { createInterface } from "node:readline/promises";
5
7
  import { stdin as input, stdout as output } from "node:process";
6
8
  import { findAgentSinServiceProcesses, getServiceProvider, isSchedulerCommandLine, isSchedulerProcessRunning, serviceLabel, } from "../core/service.js";
7
- import { configPath, defaultWorkspace, ensureDailyMemoIndexSchedule, ensureWorkspaceDirs, loadConfig, loadModels, modelsPath, setRoleModel, setupWorkspace, SetupRequiredError, writeModelsYaml, detectAvailableProviders, deriveSetupChoicePairIds, PROVIDER_CATALOG, } from "../core/config.js";
9
+ import { agentSinInstallRoot, configPath, defaultWorkspace, ensureDailyMemoIndexSchedule, ensureNightlyTopicKnowledgeSchedule, ensureWorkspaceDirs, loadConfig, loadModels, modelsPath, setRoleModel, setupWorkspace, SetupRequiredError, writeModelsYaml, detectAvailableProviders, deriveSetupChoicePairIds, PROVIDER_CATALOG, } from "../core/config.js";
10
+ import { ensureBetterSqlite3 } from "../core/native-modules.js";
8
11
  import { migrateLegacyBuiltinCopies } from "../core/builtin-skills.js";
9
12
  import { setSkillEnabled } from "../core/skill-settings.js";
10
13
  import { exportWorkspace, formatBytes, importWorkspace, listArchiveEntries, pathExists, } from "../core/transfer.js";
@@ -12,7 +15,7 @@ import { appendEventLog, dailyConversationMemoryFile, listRunLogs, readEventLog,
12
15
  import { runSkill } from "../core/runtime.js";
13
16
  import { listSkillManifests } from "../core/skill-registry.js";
14
17
  import { buildChatLines, buildLines, buildListLines, buildStatusLines, buildTestLines, } from "../builder/build-commands.js";
15
- import { classifyPendingHandoff, enterBuildMode, handleBuildModeMessage, renderBuildFooter, } from "../builder/build-flow.js";
18
+ import { classifyPendingHandoff, composeBuildSuggestionReply, enterBuildMode, enterEditModeForSkill, parseSlashBuildDirect, handleBuildModeMessage, isExplicitBuildModeStartRequest, renderBuildFooter, } from "../builder/build-flow.js";
16
19
  import { scaffoldSkill, validateInstalledSkill } from "../core/skill-scaffold.js";
17
20
  import { ensureDotenvSkeleton, getApiKeyResolution, loadDotenv, maskKey, readDotenvKeys, upsertDotenv } from "../core/secrets.js";
18
21
  import { loadSchedules, matchesCron, nextRunAfter } from "../core/scheduler.js";
@@ -23,6 +26,7 @@ import { consumeUpdateBanner, scheduleUpdateCheck } from "../core/update-notifie
23
26
  import { shutdownSharedCodexAppServer } from "../runtimes/codex-app-server.js";
24
27
  import { runDiscordBot } from "../discord/bot.js";
25
28
  import { extractTelegramIdentityCandidates, runTelegramBot, } from "../telegram/bot.js";
29
+ import { runEvenG2Gateway } from "../even-g2/gateway.js";
26
30
  import { Spinner } from "./spinner.js";
27
31
  import { formatModelRow, modelSummary, modelsLines, skillsLines, } from "../core/info-lines.js";
28
32
  import { inferLocaleFromText, l, lLines, t, withLocale } from "../core/i18n.js";
@@ -83,6 +87,8 @@ async function main() {
83
87
  return await cmdDiscord(args);
84
88
  case "telegram":
85
89
  return await cmdTelegram(args);
90
+ case "g2":
91
+ return await cmdG2(args);
86
92
  case "export":
87
93
  return await cmdExport(args);
88
94
  case "import":
@@ -132,6 +138,14 @@ async function ensureWorkspaceInitialized() {
132
138
  await migrateLegacyBuiltinCopies(config.skills_dir);
133
139
  await ensureProfileMemoryFiles(config);
134
140
  await ensureDailyMemoIndexSchedule(config);
141
+ await ensureNightlyTopicKnowledgeSchedule(config);
142
+ try {
143
+ const { migrateMemoFlatStructure } = await import("../core/memo-migration.js");
144
+ await migrateMemoFlatStructure(config);
145
+ }
146
+ catch (error) {
147
+ console.warn(`memo-migration: skipped: ${error.message}`);
148
+ }
135
149
  return;
136
150
  }
137
151
  catch (error) {
@@ -151,6 +165,59 @@ function isMissingFileError(error) {
151
165
  "code" in error &&
152
166
  error.code === "ENOENT");
153
167
  }
168
+ async function ensureNativeModulesReady(label) {
169
+ const result = await ensureBetterSqlite3({
170
+ packageRoot: agentSinInstallRoot(),
171
+ logger: (line) => console.log(`[${label}] ${line}`),
172
+ });
173
+ if (result.ok) {
174
+ if (result.rebuilt) {
175
+ console.log(l(`[${label}] better-sqlite3 was rebuilt to match the current Node.js (${process.execPath}).`, `[${label}] better-sqlite3 を現在の Node.js (${process.execPath}) 用に再ビルドしました。`));
176
+ }
177
+ return true;
178
+ }
179
+ console.error(l(`[${label}] better-sqlite3 native module is unusable: ${result.detail || "unknown error"}`, `[${label}] better-sqlite3 ネイティブモジュールが使えません: ${result.detail || "原因不明"}`));
180
+ return false;
181
+ }
182
+ async function ensureNativeModulesReadyForGateway(config) {
183
+ const result = await ensureBetterSqlite3({
184
+ packageRoot: agentSinInstallRoot(),
185
+ logger: (line) => console.log(`[gateway] ${line}`),
186
+ });
187
+ if (result.ok && !result.rebuilt) {
188
+ return "ok";
189
+ }
190
+ if (result.ok && result.rebuilt) {
191
+ try {
192
+ await appendEventLog(config, {
193
+ level: "warn",
194
+ source: "schedule",
195
+ event: "native_module_rebuilt",
196
+ message: "better-sqlite3 was rebuilt to match the current Node.js",
197
+ details: { node_exec_path: process.execPath },
198
+ });
199
+ }
200
+ catch {
201
+ /* ignore */
202
+ }
203
+ console.log(l("[gateway] better-sqlite3 was rebuilt. Exiting so launchd restarts the service with the fresh binary.", "[gateway] better-sqlite3 を再ビルドしました。launchd が新しいバイナリで再起動できるように一度終了します。"));
204
+ return "restart-required";
205
+ }
206
+ try {
207
+ await appendEventLog(config, {
208
+ level: "error",
209
+ source: "schedule",
210
+ event: "native_module_broken",
211
+ message: "better-sqlite3 native module is unusable",
212
+ details: { detail: result.detail || "unknown", node_exec_path: process.execPath },
213
+ });
214
+ }
215
+ catch {
216
+ /* ignore */
217
+ }
218
+ console.error(l(`[gateway] better-sqlite3 native module is unusable: ${result.detail || "unknown error"}. Continuing in degraded mode.`, `[gateway] better-sqlite3 ネイティブモジュールが使えません: ${result.detail || "原因不明"}。劣化モードで継続します。`));
219
+ return "broken";
220
+ }
154
221
  async function tryLogCliError(command, args, message) {
155
222
  try {
156
223
  const config = await loadConfig();
@@ -203,6 +270,7 @@ async function cmdSetup(args) {
203
270
  }
204
271
  const config = await setupWorkspace(setupOptions);
205
272
  await applyCodexCliOptions(config, options);
273
+ await ensureNativeModulesReady("setup");
206
274
  console.log(l(`Workspace ready: ${config.workspace}`, `ワークスペース準備完了: ${config.workspace}`));
207
275
  console.log(`Config: ${configPath(config.workspace)}`);
208
276
  console.log(`Notes: ${config.notes_dir}`);
@@ -466,6 +534,23 @@ async function cmdChat(args) {
466
534
  }
467
535
  continue;
468
536
  }
537
+ if (text === "/build" || text === "build") {
538
+ await refreshChatState(state);
539
+ const lines = await interactiveBuildPicker(state, rl, intentRuntime);
540
+ for (const line of lines) {
541
+ console.log(formatChatLine(line));
542
+ }
543
+ continue;
544
+ }
545
+ const buildDirect = parseSlashBuildDirect(text);
546
+ if (buildDirect) {
547
+ await refreshChatState(state);
548
+ const lines = await enterBuildModeForSlashSkill(state, intentRuntime, buildDirect, rl);
549
+ for (const line of lines) {
550
+ console.log(formatChatLine(line));
551
+ }
552
+ continue;
553
+ }
469
554
  const lines = await handleChatMessage(state.config, text, history, intentRuntime);
470
555
  scheduleUpdateCheck(state.config.workspace);
471
556
  const banner = await consumeUpdateBanner(state.config.workspace);
@@ -655,6 +740,44 @@ async function interactiveSkillsPicker(state, rl, options = {}) {
655
740
  return [l(`Delete failed: ${error instanceof Error ? error.message : String(error)}`, `削除に失敗しました: ${error instanceof Error ? error.message : String(error)}`)];
656
741
  }
657
742
  }
743
+ async function enterBuildModeForSlashSkill(state, intentRuntime, skillId, rl) {
744
+ const userSkills = state.skills.filter((skill) => skill.source !== "builtin");
745
+ const exists = userSkills.some((skill) => skill.id === skillId);
746
+ if (!exists) {
747
+ if (userSkills.length === 0) {
748
+ return [
749
+ l(`No user-created skills yet, so "${skillId}" cannot be opened. Send a request in chat to create one.`, `編集できるスキルがまだないので「${skillId}」を開けません。チャットで作成を依頼してください。`),
750
+ ];
751
+ }
752
+ const head = l(`"${skillId}" is not a registered user skill. Pick from the list, or send /build to see options.`, `「${skillId}」は登録済みのユーザースキルではありません。下のリストから番号で選んでください (またはもう一度 /build):`);
753
+ const picker = await interactiveBuildPicker(state, rl, intentRuntime);
754
+ return [head, ...picker];
755
+ }
756
+ return enterEditModeForSkill(state.config, skillId, intentRuntime, "cli");
757
+ }
758
+ async function interactiveBuildPicker(state, rl, intentRuntime) {
759
+ const userSkills = state.skills.filter((skill) => skill.source !== "builtin");
760
+ if (userSkills.length === 0) {
761
+ return [
762
+ l("No user-created skills yet. Send a request in chat to create one, or run: agent-sin build <skill-id>", "編集できるスキルはまだありません。チャットで作成依頼するか、`agent-sin build <skill-id>` で新規作成してください。"),
763
+ ];
764
+ }
765
+ console.log(l("Pick a skill to edit:", "編集するスキルを選んでください:"));
766
+ userSkills.forEach((skill, index) => {
767
+ const enabled = skill.enabled === false ? l("disabled", "無効") : l("enabled", "有効");
768
+ console.log(` ${String(index + 1).padStart(2)}) ${skill.id}\t${skill.name}\t${enabled}`);
769
+ });
770
+ const pick = (await rl.question(l("Enter a number (Enter to cancel): ", "番号を入力 (Enterでキャンセル): "))).trim();
771
+ if (!pick) {
772
+ return [];
773
+ }
774
+ const num = Number.parseInt(pick, 10);
775
+ if (!Number.isInteger(num) || num < 1 || num > userSkills.length) {
776
+ return [l("Invalid selection.", "無効な選択です。")];
777
+ }
778
+ const skill = userSkills[num - 1];
779
+ return enterEditModeForSkill(state.config, skill.id, intentRuntime, "cli");
780
+ }
658
781
  async function cmdBuild(args) {
659
782
  const config = await loadConfig();
660
783
  if (args[0] === "list") {
@@ -1554,13 +1677,26 @@ function chatRunPayload(skillId, text) {
1554
1677
  return { text };
1555
1678
  }
1556
1679
  async function chatWithModel(config, userText, history, preferredSkillId, intentRuntime) {
1557
- return chatRespond(config, userText, history, {
1680
+ let buildSuggestion = null;
1681
+ const lines = await chatRespond(config, userText, history, {
1558
1682
  formatNarrative: formatAssistantNarrative,
1559
1683
  spinner: new Spinner(),
1560
1684
  eventSource: "chat",
1561
1685
  preferredSkillId,
1562
- onBuildSuggestion: (suggestion) => setPendingBuildSuggestion(intentRuntime, suggestion, userText),
1686
+ onBuildSuggestion: (suggestion) => {
1687
+ buildSuggestion = suggestion;
1688
+ setPendingBuildSuggestion(intentRuntime, suggestion, userText);
1689
+ },
1563
1690
  });
1691
+ const suggestion = buildSuggestion;
1692
+ if (suggestion && intentRuntime?.pending && isExplicitBuildModeStartRequest(userText)) {
1693
+ const buildLines = await withCliBuildHooks(config, userText, (hooks) => enterBuildMode(config, history, intentRuntime, hooks));
1694
+ return appendCliBuildFooter(intentRuntime, buildLines, userText);
1695
+ }
1696
+ if (suggestion && intentRuntime?.pending) {
1697
+ return composeBuildSuggestionReply(lines, suggestion.type);
1698
+ }
1699
+ return lines;
1564
1700
  }
1565
1701
  function parseOptions(args) {
1566
1702
  const options = { _: [] };
@@ -2069,16 +2205,18 @@ Usage:
2069
2205
  agent-sin model set [chat|builder] <model-id>
2070
2206
  agent-sin logs [--skill skill-id]
2071
2207
  agent-sin logs --run <run-id>
2072
- agent-sin logs --events [--tail 50] [--source chat|skill|cli|setup|build|schedule|discord|telegram] [--level info|warn|error]
2208
+ agent-sin logs --events [--tail 50] [--source chat|skill|cli|setup|build|schedule|discord|telegram|g2] [--level info|warn|error]
2073
2209
  agent-sin daemon [--once]
2074
- agent-sin gateway [--no-discord] [--no-telegram]
2210
+ agent-sin gateway [--no-discord] [--no-telegram] [--g2]
2211
+ agent-sin g2 [--host 127.0.0.1] [--port 8765] [--token ...]
2075
2212
  agent-sin service status|install|start|stop|restart
2076
2213
  agent-sin schedules
2077
2214
  agent-sin schedules trigger <id>
2078
- agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]
2215
+ agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|g2|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]
2079
2216
  agent-sin discord
2080
2217
  agent-sin telegram
2081
2218
  agent-sin telegram id [--save]
2219
+ agent-sin g2
2082
2220
  agent-sin export [--out <file>]
2083
2221
  agent-sin import <archive> [--force] [--dry-run] [--no-backup]
2084
2222
  agent-sin config`, `Agent-Sin CLI
@@ -2107,16 +2245,18 @@ Usage:
2107
2245
  agent-sin model set [chat|builder] <model-id>
2108
2246
  agent-sin logs [--skill skill-id]
2109
2247
  agent-sin logs --run <run-id>
2110
- agent-sin logs --events [--tail 50] [--source chat|skill|cli|setup|build|schedule|discord|telegram] [--level info|warn|error]
2248
+ agent-sin logs --events [--tail 50] [--source chat|skill|cli|setup|build|schedule|discord|telegram|g2] [--level info|warn|error]
2111
2249
  agent-sin daemon [--once]
2112
- agent-sin gateway [--no-discord] [--no-telegram]
2250
+ agent-sin gateway [--no-discord] [--no-telegram] [--g2]
2251
+ agent-sin g2 [--host 127.0.0.1] [--port 8765] [--token ...]
2113
2252
  agent-sin service status|install|start|stop|restart
2114
2253
  agent-sin schedules
2115
2254
  agent-sin schedules trigger <id>
2116
- agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]
2255
+ agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|g2|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]
2117
2256
  agent-sin discord
2118
2257
  agent-sin telegram
2119
2258
  agent-sin telegram id [--save]
2259
+ agent-sin g2
2120
2260
  agent-sin export [--out <file>]
2121
2261
  agent-sin import <archive> [--force] [--dry-run] [--no-backup]
2122
2262
  agent-sin config`));
@@ -2134,10 +2274,10 @@ async function cmdNotify(args) {
2134
2274
  const discordThreadId = threadId || stringOption(options.discord_thread_id);
2135
2275
  const telegramThreadId = threadId || stringOption(options.telegram_thread_id);
2136
2276
  if (!title && !body) {
2137
- console.error(l("Usage: agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]", "使い方: agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]"));
2277
+ console.error(l("Usage: agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|g2|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]", "使い方: agent-sin notify --title <title> --body <body> [--channel macos|discord|telegram|slack|mail|g2|auto] [--thread-id <id>] [--to <addr>] [--subtitle <s>] [--sound]"));
2138
2278
  return 1;
2139
2279
  }
2140
- const allowed = ["auto", "macos", "discord", "telegram", "slack", "mail", "stderr"];
2280
+ const allowed = ["auto", "macos", "discord", "telegram", "slack", "mail", "g2", "stderr"];
2141
2281
  if (!allowed.includes(channelRaw)) {
2142
2282
  console.error(l(`Invalid --channel: ${channelRaw} (allowed: ${allowed.join(", ")})`, `--channel が不正です: ${channelRaw} (使用可: ${allowed.join(", ")})`));
2143
2283
  return 1;
@@ -2362,39 +2502,190 @@ async function cmdDaemon(args) {
2362
2502
  const config = await loadConfig();
2363
2503
  return runScheduleDaemon(config, { once: Boolean(options.once) });
2364
2504
  }
2505
+ async function cmdG2(args) {
2506
+ const [sub, ...rest] = args;
2507
+ if (sub === "setup") {
2508
+ return cmdG2Setup(rest);
2509
+ }
2510
+ const options = parseOptions(args);
2511
+ if (options.help) {
2512
+ console.log(l(`Usage:
2513
+ agent-sin g2 [--host 127.0.0.1] [--port 8765] [--token ...] [--history-channel telegram|discord|none|auto]
2514
+ agent-sin g2 setup [--history-channel telegram|discord|none|auto] [--discord-thread auto|off|<thread-id>]
2515
+
2516
+ Run the Even G2 gateway. Use --host 0.0.0.0 plus AGENT_SIN_G2_TOKEN
2517
+ when connecting from a phone on the same LAN. Use --history-channel to mirror
2518
+ G2 conversations to one external chat log.`, `使い方:
2519
+ agent-sin g2 [--host 127.0.0.1] [--port 8765] [--token ...] [--history-channel telegram|discord|none|auto]
2520
+ agent-sin g2 setup [--history-channel telegram|discord|none|auto] [--discord-thread auto|off|<thread-id>]
2521
+
2522
+ Even G2 gateway を起動します。同じLANのスマホから接続する場合は
2523
+ --host 0.0.0.0 と AGENT_SIN_G2_TOKEN を使ってください。
2524
+ --history-channel で G2 の会話履歴を外部チャットに1つだけ残せます。`));
2525
+ return 0;
2526
+ }
2527
+ const config = await loadConfig();
2528
+ const fallbackPort = Number.parseInt(process.env.AGENT_SIN_G2_PORT || "8765", 10) || 8765;
2529
+ return runEvenG2Gateway(config, {
2530
+ host: stringOption(options.host),
2531
+ port: parsePositiveIntOption(options.port, fallbackPort),
2532
+ token: stringOption(options.token),
2533
+ historyChannel: stringOption(options.history_channel),
2534
+ });
2535
+ }
2536
+ async function cmdG2Setup(args) {
2537
+ const options = parseOptions(args);
2538
+ if (options.help) {
2539
+ console.log(l(`Usage:
2540
+ agent-sin g2 setup [--host 0.0.0.0] [--port 8765] [--token ...] [--history-channel telegram|discord|none|auto] [--discord-thread auto|off|<thread-id>]
2541
+
2542
+ Writes the G2 gateway settings to ~/.agent-sin/.env.
2543
+ Use --discord-thread auto to create one Discord thread for G2 history.`, `使い方:
2544
+ agent-sin g2 setup [--host 0.0.0.0] [--port 8765] [--token ...] [--history-channel telegram|discord|none|auto] [--discord-thread auto|off|<thread-id>]
2545
+
2546
+ G2 gateway の設定を ~/.agent-sin/.env に保存します。
2547
+ --discord-thread auto で G2 履歴用の Discord スレッドを自動作成します。`));
2548
+ return 0;
2549
+ }
2550
+ const config = await loadConfig();
2551
+ const host = stringOption(options.host) || process.env.AGENT_SIN_G2_HOST || "0.0.0.0";
2552
+ const port = String(parsePositiveIntOption(options.port, Number.parseInt(process.env.AGENT_SIN_G2_PORT || "8765", 10) || 8765));
2553
+ const token = stringOption(options.token) || process.env.AGENT_SIN_G2_TOKEN || randomBytes(16).toString("hex");
2554
+ const historyChannel = await resolveG2HistoryChannel(options);
2555
+ const discordThread = resolveG2DiscordThreadOption(options);
2556
+ const dotenvEntries = [
2557
+ { key: "AGENT_SIN_G2_ENABLED", value: "1" },
2558
+ { key: "AGENT_SIN_G2_HOST", value: host },
2559
+ { key: "AGENT_SIN_G2_PORT", value: port },
2560
+ { key: "AGENT_SIN_G2_TOKEN", value: token },
2561
+ { key: "AGENT_SIN_G2_HISTORY_CHANNEL", value: historyChannel },
2562
+ ];
2563
+ if (discordThread !== null) {
2564
+ dotenvEntries.push({ key: "AGENT_SIN_G2_DISCORD_THREAD", value: discordThread });
2565
+ }
2566
+ const result = await upsertDotenv(config.workspace, dotenvEntries);
2567
+ const address = host === "0.0.0.0" ? localNetworkAddress() || "<PC-IP>" : host;
2568
+ console.log(l(`Saved G2 settings to ${result.path}`, `G2 設定を保存しました: ${result.path}`));
2569
+ console.log(l(`Server URL: http://${address}:${port}`, `Server URL: http://${address}:${port}`));
2570
+ console.log(l(`Token: ${token}`, `Token: ${token}`));
2571
+ console.log(l(`History channel: ${historyChannel}`, `履歴先: ${historyChannel}`));
2572
+ if (discordThread !== null) {
2573
+ console.log(l(`Discord thread: ${discordThread}`, `Discord thread: ${discordThread}`));
2574
+ }
2575
+ console.log(l("Apply with: agent-sin service restart", "反映: agent-sin service restart"));
2576
+ return 0;
2577
+ }
2578
+ function resolveG2DiscordThreadOption(options) {
2579
+ const explicit = stringOption(options.discord_thread) || stringOption(options.g2_discord_thread);
2580
+ if (!explicit)
2581
+ return null;
2582
+ const normalized = explicit.trim().toLowerCase();
2583
+ if (/^(auto|create|1|true|yes)$/.test(normalized))
2584
+ return "auto";
2585
+ if (/^(none|off|false|0)$/.test(normalized))
2586
+ return "off";
2587
+ if (/^\d+$/.test(explicit.trim()))
2588
+ return explicit.trim();
2589
+ throw new Error(l(`Invalid --discord-thread: ${explicit} (allowed: auto, off, or Discord thread id)`, `--discord-thread が不正です: ${explicit} (使用可: auto, off, Discord thread id)`));
2590
+ }
2591
+ async function resolveG2HistoryChannel(options) {
2592
+ const explicit = stringOption(options.history_channel) || stringOption(options.g2_history_channel);
2593
+ if (explicit) {
2594
+ const normalized = normalizeG2HistoryChannel(explicit);
2595
+ if (!normalized) {
2596
+ throw new Error(l(`Invalid --history-channel: ${explicit} (allowed: telegram, discord, none, auto)`, `--history-channel が不正です: ${explicit} (使用可: telegram, discord, none, auto)`));
2597
+ }
2598
+ return normalized;
2599
+ }
2600
+ const existing = normalizeG2HistoryChannel(process.env.AGENT_SIN_G2_HISTORY_CHANNEL || "");
2601
+ const fallback = existing || "none";
2602
+ if (!input.isTTY || process.env.CI) {
2603
+ return fallback;
2604
+ }
2605
+ const choices = ["none", "telegram", "discord", "auto"];
2606
+ console.log(l("Where should G2 conversation history be mirrored?", "G2 の会話履歴をどこに残しますか?"));
2607
+ choices.forEach((choice, index) => {
2608
+ console.log(` ${index + 1}) ${choice}`);
2609
+ });
2610
+ const rl = createInterface({ input, output });
2611
+ try {
2612
+ const answer = (await rl.question(l(`Enter a number (Enter for ${fallback}): `, `番号を入力 (Enterで ${fallback}): `))).trim();
2613
+ if (!answer)
2614
+ return fallback;
2615
+ const index = Number.parseInt(answer, 10);
2616
+ if (Number.isInteger(index) && index >= 1 && index <= choices.length) {
2617
+ return choices[index - 1];
2618
+ }
2619
+ const byName = normalizeG2HistoryChannel(answer);
2620
+ if (byName)
2621
+ return byName;
2622
+ throw new Error(l("Invalid selection.", "無効な選択です。"));
2623
+ }
2624
+ finally {
2625
+ rl.close();
2626
+ }
2627
+ }
2628
+ function normalizeG2HistoryChannel(value) {
2629
+ const normalized = value.trim().toLowerCase();
2630
+ if (normalized === "telegram" || normalized === "discord" || normalized === "none" || normalized === "auto") {
2631
+ return normalized;
2632
+ }
2633
+ if (normalized === "off" || normalized === "false" || normalized === "0") {
2634
+ return "none";
2635
+ }
2636
+ return null;
2637
+ }
2638
+ function localNetworkAddress() {
2639
+ const nets = networkInterfaces();
2640
+ for (const entries of Object.values(nets)) {
2641
+ for (const entry of entries || []) {
2642
+ if (entry.family === "IPv4" && !entry.internal) {
2643
+ return entry.address;
2644
+ }
2645
+ }
2646
+ }
2647
+ return null;
2648
+ }
2365
2649
  async function cmdGateway(args) {
2366
2650
  const options = parseOptions(args);
2367
2651
  if (options.help) {
2368
2652
  console.log(l(`Usage:
2369
- agent-sin gateway [--no-discord] [--no-telegram] [--once]
2653
+ agent-sin gateway [--no-discord] [--no-telegram] [--g2] [--once] [--g2-history-channel telegram|discord|none|auto]
2370
2654
  agent-sin start --daemon
2371
2655
  agent-sin service run
2372
2656
 
2373
2657
  Run Agent-Sin as a long-lived gateway. It starts the scheduler when enabled
2374
2658
  schedules exist, and starts Discord / Telegram bots when their environment variables
2375
- are configured. Use agent-sin service install on macOS to keep it running.`, `使い方:
2376
- agent-sin gateway [--no-discord] [--no-telegram] [--once]
2659
+ are configured. Use --g2 to also start the Even G2 gateway.
2660
+ Use agent-sin service install on macOS to keep it running.`, `使い方:
2661
+ agent-sin gateway [--no-discord] [--no-telegram] [--g2] [--once] [--g2-history-channel telegram|discord|none|auto]
2377
2662
  agent-sin start --daemon
2378
2663
  agent-sin service run
2379
2664
 
2380
2665
  Agent-Sin を常駐 gateway として起動します。有効なスケジュールがあれば scheduler を、
2381
2666
  環境変数が設定済みなら Discord / Telegram bot を起動します。
2667
+ --g2 で Even G2 gateway も起動します。
2382
2668
  macOS で常駐させるには agent-sin service install を使ってください。`));
2383
2669
  return 0;
2384
2670
  }
2385
2671
  const config = await loadConfig();
2672
+ const nativeStatus = await ensureNativeModulesReadyForGateway(config);
2673
+ if (nativeStatus === "restart-required") {
2674
+ return 0;
2675
+ }
2386
2676
  await maybePromoteDailyMemory(config, { eventSource: "schedule" });
2387
2677
  const schedules = await loadSchedules(config.workspace);
2388
2678
  const enabled = schedules.filter((schedule) => schedule.enabled);
2389
2679
  const startScheduler = enabled.length > 0;
2390
2680
  const startDiscord = !options.no_discord && hasDiscordConfig();
2391
2681
  const startTelegram = !options.no_telegram && hasTelegramConfig();
2392
- if (!startScheduler && !startDiscord && !startTelegram) {
2682
+ const startG2 = Boolean(options.g2) || process.env.AGENT_SIN_G2_ENABLED === "1";
2683
+ if (!startScheduler && !startDiscord && !startTelegram && !startG2) {
2393
2684
  console.log(l("agent-sin gateway: no enabled schedules and chat bots are not configured.", "agent-sin gateway: 有効なスケジュールがなく、チャット bot も未設定です。"));
2394
- console.log(l("Add schedules, or set Discord allowed user IDs / Telegram bot token.", "スケジュールを追加するか、Discord allowed user IDs / Telegram bot token を設定してください。"));
2685
+ console.log(l("Add schedules, set Discord/Telegram config, or run with --g2.", "スケジュールを追加するか、Discord/Telegram を設定するか、--g2 を付けてください。"));
2395
2686
  return 0;
2396
2687
  }
2397
- console.log(l(`agent-sin gateway: starting (${startScheduler ? `${enabled.length} schedule(s)` : "scheduler idle"}, ${startDiscord ? "discord on" : "discord off"}, ${startTelegram ? "telegram on" : "telegram off"})`, `agent-sin gateway: 起動します (${startScheduler ? `${enabled.length}件のスケジュール` : "scheduler 待機"}, ${startDiscord ? "discord 有効" : "discord 無効"}, ${startTelegram ? "telegram 有効" : "telegram 無効"})`));
2688
+ console.log(l(`agent-sin gateway: starting (${startScheduler ? `${enabled.length} schedule(s)` : "scheduler idle"}, ${startDiscord ? "discord on" : "discord off"}, ${startTelegram ? "telegram on" : "telegram off"}, ${startG2 ? "g2 on" : "g2 off"})`, `agent-sin gateway: 起動します (${startScheduler ? `${enabled.length}件のスケジュール` : "scheduler 待機"}, ${startDiscord ? "discord 有効" : "discord 無効"}, ${startTelegram ? "telegram 有効" : "telegram 無効"}, ${startG2 ? "g2 有効" : "g2 無効"})`));
2398
2689
  const startupVersion = agentSinVersionFresh();
2399
2690
  const versionCheckInterval = setInterval(() => {
2400
2691
  const current = agentSinVersionFresh();
@@ -2421,6 +2712,14 @@ macOS で常駐させるには agent-sin service install を使ってくださ
2421
2712
  else if (!options.no_telegram && !startDiscord) {
2422
2713
  console.log(l("agent-sin gateway: Telegram not configured.", "agent-sin gateway: Telegram は未設定です。"));
2423
2714
  }
2715
+ if (startG2) {
2716
+ tasks.push(runEvenG2Gateway(config, {
2717
+ host: stringOption(options.g2_host) || stringOption(options.host),
2718
+ port: parsePositiveIntOption(options.g2_port || options.port, Number.parseInt(process.env.AGENT_SIN_G2_PORT || "8765", 10) || 8765),
2719
+ token: stringOption(options.g2_token) || stringOption(options.token),
2720
+ historyChannel: stringOption(options.g2_history_channel) || stringOption(options.history_channel),
2721
+ }));
2722
+ }
2424
2723
  const results = await Promise.all(tasks);
2425
2724
  clearInterval(versionCheckInterval);
2426
2725
  return Math.max(...results);
@@ -2624,7 +2923,7 @@ Usage:
2624
2923
  agent-sin service run
2625
2924
 
2626
2925
  ${platformLine}
2627
- The service runs the scheduler and, when configured, Discord / Telegram bots.`, `Agent-Sin service
2926
+ The service runs the scheduler, configured Discord / Telegram bots, and G2 when AGENT_SIN_G2_ENABLED=1.`, `Agent-Sin service
2628
2927
 
2629
2928
  使い方:
2630
2929
  agent-sin service status
@@ -2636,7 +2935,7 @@ The service runs the scheduler and, when configured, Discord / Telegram bots.`,
2636
2935
  agent-sin service run
2637
2936
 
2638
2937
  ${platformLine}
2639
- service は scheduler と、設定済みの場合は Discord / Telegram bot を起動します。`));
2938
+ service は scheduler、設定済みの Discord / Telegram bot、AGENT_SIN_G2_ENABLED=1 のときは G2 を起動します。`));
2640
2939
  }
2641
2940
  async function installService(config, options) {
2642
2941
  const provider = getServiceProvider();
@@ -2644,6 +2943,10 @@ async function installService(config, options) {
2644
2943
  console.error(provider.notSupportedReason());
2645
2944
  return 1;
2646
2945
  }
2946
+ const nativeOk = await ensureNativeModulesReady("service install");
2947
+ if (!nativeOk) {
2948
+ return 1;
2949
+ }
2647
2950
  try {
2648
2951
  await provider.install(config, { noStart: Boolean(options.no_start) });
2649
2952
  }
@@ -51,6 +51,7 @@ export interface AiProviderResponse {
51
51
  text: string;
52
52
  model_id: string;
53
53
  provider: string;
54
+ generated_images?: string[];
54
55
  }
55
56
  export declare class AiProviderError extends Error {
56
57
  readonly model_id: string;
@@ -430,14 +430,19 @@ async function dispatchCodex(request, entry) {
430
430
  const effort = resolveCodexEffort(request, entry);
431
431
  try {
432
432
  const session = getSharedCodexAppServer(entry.model);
433
- const text = await session.sendTurn(prompt, {
433
+ const result = await session.sendTurn(prompt, {
434
434
  effort,
435
435
  cwd: request.cwd || process.cwd(),
436
436
  sandbox,
437
437
  approvalPolicy,
438
438
  onProgress: request.onProgress,
439
439
  });
440
- return { text, model_id: request.model_id, provider: "codex" };
440
+ return {
441
+ text: result.text,
442
+ model_id: request.model_id,
443
+ provider: "codex",
444
+ generated_images: result.generatedImagePaths,
445
+ };
441
446
  }
442
447
  catch (error) {
443
448
  if (mode === "appserver") {
@@ -563,7 +568,7 @@ function messagesToPrompt(messages) {
563
568
  }
564
569
  // OpenAI の chat/completions は `role: "tool"` を直前の assistant メッセージの
565
570
  // `tool_calls` への返答としてしか許容しない(無いと HTTP 400)。
566
- // agent-sin は markdown skill-call ブロックで独自に skill 呼び出しを行うため
571
+ // agent-sin は JSON envelope で独自に skill 呼び出しを行うため
567
572
  // tool_calls プロトコルには乗らない。よって OpenAI に投げる際だけ
568
573
  // tool ロールは [tool-result] プレフィックス付きの user メッセージに畳む。
569
574
  function toOpenAIChatMessages(request) {
@@ -47,23 +47,29 @@ export interface ChatRespondOptions {
47
47
  onChatProgress?: (event: ChatProgressEvent) => void;
48
48
  onAiProgress?: AiProgressHandler;
49
49
  onBuildSuggestion?: (suggestion: ChatBuildSuggestion) => void;
50
+ onLocalAttachments?: (paths: string[]) => void;
51
+ onGeneratedImages?: (paths: string[]) => void;
50
52
  preferredSkillId?: string;
51
53
  userImages?: AiImagePart[];
52
54
  }
53
55
  export declare const HISTORY_LIMIT = 20;
54
- export declare const TOOL_CALL_MAX_ITERATIONS = 3;
56
+ export declare const TOOL_CALL_MAX_ITERATIONS = 8;
55
57
  export declare function chatRespond(config: AppConfig, userText: string, history: ChatTurn[], options?: ChatRespondOptions): Promise<string[]>;
56
58
  export declare function makeSpinnerProgress(spinner: SpinnerLike, baseLabel: string): (event: AiProgressEvent) => void;
57
59
  export declare function formatProgressLabel(baseLabel: string, event: AiProgressEvent): string | null;
58
60
  export declare function isToolEligible(skill: SkillManifest): boolean;
59
- export declare function buildSystemPrompt(skills: SkillManifest[], preferredSkillId?: string, profileMemory?: ProfileMemoryFiles): string;
61
+ export declare function buildSystemPrompt(skills: SkillManifest[], preferredSkillId?: string, profileMemory?: ProfileMemoryFiles, config?: AppConfig): string;
62
+ export declare function buildTopicKnowledgeIndexContext(config: AppConfig): Promise<string>;
60
63
  export declare function parseSkillCalls(text: string): SkillCall[];
61
64
  export declare function stripSkillCalls(text: string): string;
62
65
  export declare function extractSkillCallBlocks(text: string): string;
63
66
  export declare function parseBuildSuggestion(text: string): ChatBuildSuggestion | null;
64
67
  export declare function stripBuildSuggestions(text: string): string;
65
68
  export declare function stripInternalControlBlocks(text: string): string;
66
- export declare function toolResultJson(id: string, status: string, summary: string, saved?: string[], data?: unknown): string;
69
+ export declare function toolResultJson(id: string, status: string, summary: string, saved?: string[], data?: unknown, notes?: Array<{
70
+ level: "warn" | "error";
71
+ message: string;
72
+ }>): string;
67
73
  export declare function toAiMessages(history: ChatTurn[], multimodalTurn?: {
68
74
  index: number;
69
75
  images: AiImagePart[];