agent-sin 0.1.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 (150) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +81 -0
  4. package/assets/logo.png +0 -0
  5. package/builtin-skills/_shared/_models_lib.py +227 -0
  6. package/builtin-skills/_shared/_profile_lib.py +98 -0
  7. package/builtin-skills/_shared/_schedules_lib.py +313 -0
  8. package/builtin-skills/_shared/_skill_settings_lib.py +153 -0
  9. package/builtin-skills/_shared/i18n.py +26 -0
  10. package/builtin-skills/memo-delete/main.py +155 -0
  11. package/builtin-skills/memo-delete/skill.yaml +57 -0
  12. package/builtin-skills/memo-index/main.py +178 -0
  13. package/builtin-skills/memo-index/skill.yaml +53 -0
  14. package/builtin-skills/memo-save/README.md +5 -0
  15. package/builtin-skills/memo-save/main.py +74 -0
  16. package/builtin-skills/memo-save/skill.yaml +52 -0
  17. package/builtin-skills/memo-search/README.md +10 -0
  18. package/builtin-skills/memo-search/main.py +97 -0
  19. package/builtin-skills/memo-search/skill.yaml +51 -0
  20. package/builtin-skills/memo-vector-search/main.py +121 -0
  21. package/builtin-skills/memo-vector-search/skill.yaml +53 -0
  22. package/builtin-skills/model-add/main.py +180 -0
  23. package/builtin-skills/model-add/skill.yaml +112 -0
  24. package/builtin-skills/model-list/main.py +93 -0
  25. package/builtin-skills/model-list/skill.yaml +48 -0
  26. package/builtin-skills/model-set/main.py +123 -0
  27. package/builtin-skills/model-set/skill.yaml +69 -0
  28. package/builtin-skills/profile-delete/_profile_lib.py +98 -0
  29. package/builtin-skills/profile-delete/main.py +98 -0
  30. package/builtin-skills/profile-delete/skill.yaml +64 -0
  31. package/builtin-skills/profile-edit/_profile_lib.py +98 -0
  32. package/builtin-skills/profile-edit/main.py +97 -0
  33. package/builtin-skills/profile-edit/skill.yaml +72 -0
  34. package/builtin-skills/profile-save/main.py +52 -0
  35. package/builtin-skills/profile-save/skill.yaml +69 -0
  36. package/builtin-skills/schedule-add/_schedules_lib.py +303 -0
  37. package/builtin-skills/schedule-add/main.py +137 -0
  38. package/builtin-skills/schedule-add/skill.yaml +94 -0
  39. package/builtin-skills/schedule-list/_schedules_lib.py +303 -0
  40. package/builtin-skills/schedule-list/main.py +86 -0
  41. package/builtin-skills/schedule-list/skill.yaml +45 -0
  42. package/builtin-skills/schedule-remove/_schedules_lib.py +303 -0
  43. package/builtin-skills/schedule-remove/main.py +69 -0
  44. package/builtin-skills/schedule-remove/skill.yaml +49 -0
  45. package/builtin-skills/schedule-toggle/_schedules_lib.py +303 -0
  46. package/builtin-skills/schedule-toggle/main.py +78 -0
  47. package/builtin-skills/schedule-toggle/skill.yaml +61 -0
  48. package/builtin-skills/skills-disable/main.py +63 -0
  49. package/builtin-skills/skills-disable/skill.yaml +52 -0
  50. package/builtin-skills/skills-enable/main.py +62 -0
  51. package/builtin-skills/skills-enable/skill.yaml +51 -0
  52. package/builtin-skills/todo-add/main.py +68 -0
  53. package/builtin-skills/todo-add/skill.yaml +53 -0
  54. package/builtin-skills/todo-delete/main.py +65 -0
  55. package/builtin-skills/todo-delete/skill.yaml +47 -0
  56. package/builtin-skills/todo-done/main.py +75 -0
  57. package/builtin-skills/todo-done/skill.yaml +47 -0
  58. package/builtin-skills/todo-list/main.py +91 -0
  59. package/builtin-skills/todo-list/skill.yaml +48 -0
  60. package/builtin-skills/todo-tick/main.py +125 -0
  61. package/builtin-skills/todo-tick/skill.yaml +48 -0
  62. package/dist/builder/build-action-classifier.d.ts +18 -0
  63. package/dist/builder/build-action-classifier.js +142 -0
  64. package/dist/builder/build-commands.d.ts +19 -0
  65. package/dist/builder/build-commands.js +133 -0
  66. package/dist/builder/build-flow.d.ts +72 -0
  67. package/dist/builder/build-flow.js +416 -0
  68. package/dist/builder/builder-session.d.ts +117 -0
  69. package/dist/builder/builder-session.js +1129 -0
  70. package/dist/builder/conversation-router.d.ts +22 -0
  71. package/dist/builder/conversation-router.js +69 -0
  72. package/dist/builder/intent-runtime-store.d.ts +7 -0
  73. package/dist/builder/intent-runtime-store.js +60 -0
  74. package/dist/builder/progress-format.d.ts +7 -0
  75. package/dist/builder/progress-format.js +46 -0
  76. package/dist/cli/index.d.ts +2 -0
  77. package/dist/cli/index.js +2835 -0
  78. package/dist/cli/spinner.d.ts +30 -0
  79. package/dist/cli/spinner.js +164 -0
  80. package/dist/core/ai-provider.d.ts +75 -0
  81. package/dist/core/ai-provider.js +678 -0
  82. package/dist/core/builtin-skills.d.ts +27 -0
  83. package/dist/core/builtin-skills.js +120 -0
  84. package/dist/core/chat-engine.d.ts +70 -0
  85. package/dist/core/chat-engine.js +812 -0
  86. package/dist/core/config.d.ts +127 -0
  87. package/dist/core/config.js +1379 -0
  88. package/dist/core/daily-memory-promotion.d.ts +21 -0
  89. package/dist/core/daily-memory-promotion.js +422 -0
  90. package/dist/core/i18n.d.ts +23 -0
  91. package/dist/core/i18n.js +167 -0
  92. package/dist/core/info-lines.d.ts +5 -0
  93. package/dist/core/info-lines.js +39 -0
  94. package/dist/core/input-schema.d.ts +2 -0
  95. package/dist/core/input-schema.js +156 -0
  96. package/dist/core/intent-router.d.ts +27 -0
  97. package/dist/core/intent-router.js +160 -0
  98. package/dist/core/logger.d.ts +60 -0
  99. package/dist/core/logger.js +240 -0
  100. package/dist/core/memory.d.ts +10 -0
  101. package/dist/core/memory.js +72 -0
  102. package/dist/core/message-utils.d.ts +13 -0
  103. package/dist/core/message-utils.js +104 -0
  104. package/dist/core/notifier.d.ts +17 -0
  105. package/dist/core/notifier.js +424 -0
  106. package/dist/core/output-writer.d.ts +13 -0
  107. package/dist/core/output-writer.js +100 -0
  108. package/dist/core/plan-decision.d.ts +16 -0
  109. package/dist/core/plan-decision.js +88 -0
  110. package/dist/core/profile-memory.d.ts +17 -0
  111. package/dist/core/profile-memory.js +142 -0
  112. package/dist/core/runtime.d.ts +50 -0
  113. package/dist/core/runtime.js +187 -0
  114. package/dist/core/scheduler.d.ts +28 -0
  115. package/dist/core/scheduler.js +155 -0
  116. package/dist/core/secrets.d.ts +31 -0
  117. package/dist/core/secrets.js +214 -0
  118. package/dist/core/service.d.ts +35 -0
  119. package/dist/core/service.js +479 -0
  120. package/dist/core/skill-planner.d.ts +24 -0
  121. package/dist/core/skill-planner.js +100 -0
  122. package/dist/core/skill-registry.d.ts +98 -0
  123. package/dist/core/skill-registry.js +319 -0
  124. package/dist/core/skill-scaffold.d.ts +33 -0
  125. package/dist/core/skill-scaffold.js +256 -0
  126. package/dist/core/skill-settings.d.ts +11 -0
  127. package/dist/core/skill-settings.js +63 -0
  128. package/dist/core/transfer.d.ts +31 -0
  129. package/dist/core/transfer.js +270 -0
  130. package/dist/core/update-notifier.d.ts +2 -0
  131. package/dist/core/update-notifier.js +140 -0
  132. package/dist/discord/bot.d.ts +96 -0
  133. package/dist/discord/bot.js +2424 -0
  134. package/dist/runtimes/codex-app-server.d.ts +53 -0
  135. package/dist/runtimes/codex-app-server.js +305 -0
  136. package/dist/runtimes/python-runner.d.ts +7 -0
  137. package/dist/runtimes/python-runner.js +302 -0
  138. package/dist/runtimes/typescript-runner.d.ts +5 -0
  139. package/dist/runtimes/typescript-runner.js +172 -0
  140. package/dist/skills-sdk/types.d.ts +38 -0
  141. package/dist/skills-sdk/types.js +1 -0
  142. package/dist/telegram/bot.d.ts +94 -0
  143. package/dist/telegram/bot.js +1219 -0
  144. package/install.ps1 +132 -0
  145. package/install.sh +130 -0
  146. package/package.json +60 -0
  147. package/templates/skill-python/main.py +74 -0
  148. package/templates/skill-python/skill.yaml +48 -0
  149. package/templates/skill-typescript/main.ts +87 -0
  150. package/templates/skill-typescript/skill.yaml +42 -0
@@ -0,0 +1,479 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { mkdir, stat, writeFile } from "node:fs/promises";
6
+ import { l } from "./i18n.js";
7
+ const execFileAsync = promisify(execFile);
8
+ export const SERVICE_LABEL_DARWIN = "com.agent-sin.gateway";
9
+ export const SERVICE_TASK_NAME_WINDOWS = "Agent-Sin Gateway";
10
+ const SERVICE_STOP_TIMEOUT_MS = 10_000;
11
+ const SERVICE_BOOTSTRAP_RETRY_DELAYS_MS = [250, 500, 1000, 2000, 3000];
12
+ export function renderServiceManifestForPlatform(config, platform = process.platform) {
13
+ if (platform === "win32") {
14
+ return {
15
+ label: SERVICE_TASK_NAME_WINDOWS,
16
+ manifestKind: "schtasks",
17
+ text: windowsTaskXml(config),
18
+ };
19
+ }
20
+ return {
21
+ label: SERVICE_LABEL_DARWIN,
22
+ manifestKind: "plist",
23
+ text: launchdPlistText(config),
24
+ };
25
+ }
26
+ export function getServiceProvider() {
27
+ if (process.platform === "darwin") {
28
+ return darwinProvider;
29
+ }
30
+ if (process.platform === "win32") {
31
+ return windowsProvider;
32
+ }
33
+ return unsupportedProvider;
34
+ }
35
+ export function serviceLabel() {
36
+ if (process.platform === "win32")
37
+ return SERVICE_TASK_NAME_WINDOWS;
38
+ return SERVICE_LABEL_DARWIN;
39
+ }
40
+ function cliPath() {
41
+ return path.resolve(process.argv[1] || "agent-sin");
42
+ }
43
+ function envPath() {
44
+ if (process.platform === "win32") {
45
+ return process.env.PATH || "";
46
+ }
47
+ return process.env.PATH || "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
48
+ }
49
+ function execErrorText(error) {
50
+ if (typeof error === "object" && error !== null) {
51
+ const record = error;
52
+ const stderr = typeof record.stderr === "string" ? record.stderr.trim() : "";
53
+ const stdout = typeof record.stdout === "string" ? record.stdout.trim() : "";
54
+ const message = typeof record.message === "string" ? record.message.trim() : "";
55
+ return stderr || stdout || message || String(error);
56
+ }
57
+ return String(error);
58
+ }
59
+ function sleep(ms) {
60
+ return new Promise((resolve) => setTimeout(resolve, ms));
61
+ }
62
+ function xmlEscape(value) {
63
+ return value
64
+ .replace(/&/g, "&")
65
+ .replace(/</g, "&lt;")
66
+ .replace(/>/g, "&gt;")
67
+ .replace(/"/g, "&quot;")
68
+ .replace(/'/g, "&apos;");
69
+ }
70
+ function configLocaleEnv(config) {
71
+ const locale = config.defaults?.locale;
72
+ return locale === "ja" || locale === "en" ? locale : undefined;
73
+ }
74
+ // ---------- darwin (launchd) ----------
75
+ function launchdDomain() {
76
+ const uid = typeof process.getuid === "function" ? process.getuid() : os.userInfo().uid;
77
+ return `gui/${uid}`;
78
+ }
79
+ function launchdServiceTarget() {
80
+ return `${launchdDomain()}/${SERVICE_LABEL_DARWIN}`;
81
+ }
82
+ function launchdPlistPath() {
83
+ return path.join(os.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL_DARWIN}.plist`);
84
+ }
85
+ function launchdPlistText(config) {
86
+ const stdout = path.join(config.logs_dir, "service.out.log");
87
+ const stderr = path.join(config.logs_dir, "service.err.log");
88
+ return `<?xml version="1.0" encoding="UTF-8"?>
89
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
90
+ <plist version="1.0">
91
+ <dict>
92
+ <key>Label</key>
93
+ <string>${xmlEscape(SERVICE_LABEL_DARWIN)}</string>
94
+ <key>ProgramArguments</key>
95
+ <array>
96
+ <string>${xmlEscape(process.execPath)}</string>
97
+ <string>${xmlEscape(cliPath())}</string>
98
+ <string>service</string>
99
+ <string>run</string>
100
+ </array>
101
+ <key>EnvironmentVariables</key>
102
+ <dict>
103
+ <key>AGENT_SIN_HOME</key>
104
+ <string>${xmlEscape(config.workspace)}</string>
105
+ <key>PATH</key>
106
+ <string>${xmlEscape(envPath())}</string>${configLocaleEnv(config) ? `
107
+ <key>AGENT_SIN_LOCALE</key>
108
+ <string>${xmlEscape(configLocaleEnv(config))}</string>` : ""}
109
+ </dict>
110
+ <key>WorkingDirectory</key>
111
+ <string>${xmlEscape(config.workspace)}</string>
112
+ <key>RunAtLoad</key>
113
+ <true/>
114
+ <key>KeepAlive</key>
115
+ <true/>
116
+ <key>StandardOutPath</key>
117
+ <string>${xmlEscape(stdout)}</string>
118
+ <key>StandardErrorPath</key>
119
+ <string>${xmlEscape(stderr)}</string>
120
+ </dict>
121
+ </plist>`;
122
+ }
123
+ async function launchctl(args, options = {}) {
124
+ try {
125
+ await execFileAsync("launchctl", args);
126
+ }
127
+ catch (error) {
128
+ const detail = execErrorText(error);
129
+ if (options.allowAlreadyBootstrapped &&
130
+ /already exists|already bootstrapped|service already loaded|EEXIST/i.test(detail)) {
131
+ return;
132
+ }
133
+ if (options.allowNotFound && /No such process|Could not find service|service not found|113:/.test(detail)) {
134
+ return;
135
+ }
136
+ throw new Error(l(`launchctl ${args.join(" ")} failed: ${detail}`, `launchctl ${args.join(" ")} が失敗しました: ${detail}`));
137
+ }
138
+ }
139
+ async function isLaunchdServiceLoaded(target) {
140
+ try {
141
+ await execFileAsync("launchctl", ["print", target]);
142
+ return true;
143
+ }
144
+ catch {
145
+ return false;
146
+ }
147
+ }
148
+ function isTransientBootstrapFailure(detail) {
149
+ return /Bootstrap failed:\s*5:|Input\/output error/i.test(detail);
150
+ }
151
+ async function bootstrapLaunchAgent(domain, plistPath, target) {
152
+ let lastError = "";
153
+ for (let attempt = 0; attempt <= SERVICE_BOOTSTRAP_RETRY_DELAYS_MS.length; attempt += 1) {
154
+ try {
155
+ await launchctl(["bootstrap", domain, plistPath]);
156
+ return;
157
+ }
158
+ catch (error) {
159
+ lastError = error instanceof Error ? error.message : String(error);
160
+ if (await isLaunchdServiceLoaded(target)) {
161
+ return;
162
+ }
163
+ if (!isTransientBootstrapFailure(lastError) || attempt === SERVICE_BOOTSTRAP_RETRY_DELAYS_MS.length) {
164
+ throw error;
165
+ }
166
+ await sleep(SERVICE_BOOTSTRAP_RETRY_DELAYS_MS[attempt]);
167
+ }
168
+ }
169
+ throw new Error(lastError);
170
+ }
171
+ async function waitForLaunchdServiceUnloaded(target, timeoutMs) {
172
+ const deadline = Date.now() + timeoutMs;
173
+ while (await isLaunchdServiceLoaded(target)) {
174
+ if (Date.now() >= deadline) {
175
+ throw new Error(l(`launchctl bootout ${target} timed out waiting for service to unload`, `launchctl bootout ${target} は service 停止待ちでタイムアウトしました`));
176
+ }
177
+ await sleep(250);
178
+ }
179
+ }
180
+ const darwinProvider = {
181
+ platformId: "darwin",
182
+ label: SERVICE_LABEL_DARWIN,
183
+ supported: true,
184
+ async install(config, options) {
185
+ const plistPath = launchdPlistPath();
186
+ await mkdir(path.dirname(plistPath), { recursive: true });
187
+ await writeFile(plistPath, launchdPlistText(config), "utf8");
188
+ if (!options?.noStart) {
189
+ await this.start(config);
190
+ }
191
+ },
192
+ async start(_config) {
193
+ const plistPath = launchdPlistPath();
194
+ try {
195
+ await stat(plistPath);
196
+ }
197
+ catch {
198
+ throw new Error(l("service is not installed. Run: agent-sin service install", "service がインストールされていません。実行: agent-sin service install"));
199
+ }
200
+ const target = launchdServiceTarget();
201
+ await bootstrapLaunchAgent(launchdDomain(), plistPath, target);
202
+ await launchctl(["kickstart", "-k", target]);
203
+ },
204
+ async stop(options) {
205
+ const target = launchdServiceTarget();
206
+ await launchctl(["bootout", target], { allowNotFound: true });
207
+ if (options?.wait) {
208
+ await waitForLaunchdServiceUnloaded(target, SERVICE_STOP_TIMEOUT_MS);
209
+ }
210
+ },
211
+ manifestText(config) {
212
+ return launchdPlistText(config);
213
+ },
214
+ async status(_config) {
215
+ const plistPath = launchdPlistPath();
216
+ let installed = false;
217
+ try {
218
+ await stat(plistPath);
219
+ installed = true;
220
+ }
221
+ catch {
222
+ installed = false;
223
+ }
224
+ return { installed, manifestPath: plistPath, manifestKind: "plist" };
225
+ },
226
+ notSupportedReason() {
227
+ return "";
228
+ },
229
+ };
230
+ // ---------- windows (schtasks) ----------
231
+ function windowsManifestPath() {
232
+ return path.join(os.homedir(), ".agent-sin", "service", "agent-sin-gateway.xml");
233
+ }
234
+ function windowsTaskXml(config) {
235
+ const cli = cliPath();
236
+ const node = process.execPath;
237
+ const args = [cli, "service", "run"]
238
+ .map((value) => `"${value.replace(/"/g, '\\"')}"`)
239
+ .join(" ");
240
+ // Windows Task Scheduler uses UTF-16 LE XML. Build a logical XML; we'll save it as UTF-16 LE separately.
241
+ return `<?xml version="1.0" encoding="UTF-16"?>
242
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
243
+ <RegistrationInfo>
244
+ <Description>Agent-Sin Gateway: scheduler + Discord/Telegram bots</Description>
245
+ <Author>agent-sin</Author>
246
+ </RegistrationInfo>
247
+ <Triggers>
248
+ <LogonTrigger>
249
+ <Enabled>true</Enabled>
250
+ </LogonTrigger>
251
+ </Triggers>
252
+ <Principals>
253
+ <Principal id="Author">
254
+ <LogonType>InteractiveToken</LogonType>
255
+ <RunLevel>LeastPrivilege</RunLevel>
256
+ </Principal>
257
+ </Principals>
258
+ <Settings>
259
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
260
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
261
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
262
+ <AllowHardTerminate>true</AllowHardTerminate>
263
+ <StartWhenAvailable>true</StartWhenAvailable>
264
+ <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
265
+ <IdleSettings>
266
+ <StopOnIdleEnd>false</StopOnIdleEnd>
267
+ <RestartOnIdle>false</RestartOnIdle>
268
+ </IdleSettings>
269
+ <AllowStartOnDemand>true</AllowStartOnDemand>
270
+ <Enabled>true</Enabled>
271
+ <Hidden>false</Hidden>
272
+ <RunOnlyIfIdle>false</RunOnlyIfIdle>
273
+ <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
274
+ <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
275
+ <WakeToRun>false</WakeToRun>
276
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
277
+ <Priority>7</Priority>
278
+ <RestartOnFailure>
279
+ <Interval>PT1M</Interval>
280
+ <Count>5</Count>
281
+ </RestartOnFailure>
282
+ </Settings>
283
+ <Actions Context="Author">
284
+ <Exec>
285
+ <Command>${xmlEscape(node)}</Command>
286
+ <Arguments>${xmlEscape(args)}</Arguments>
287
+ <WorkingDirectory>${xmlEscape(config.workspace)}</WorkingDirectory>
288
+ </Exec>
289
+ </Actions>
290
+ </Task>`;
291
+ }
292
+ async function schtasks(args, options = {}) {
293
+ try {
294
+ const { stdout, stderr } = await execFileAsync("schtasks", args, { encoding: "utf8" });
295
+ return { stdout: String(stdout), stderr: String(stderr) };
296
+ }
297
+ catch (error) {
298
+ const detail = execErrorText(error);
299
+ if (options.allowMissing && /ERROR:\s*The system cannot find the file|cannot find the (?:file|task) specified/i.test(detail)) {
300
+ return { stdout: "", stderr: detail };
301
+ }
302
+ if (options.allowExists && /ERROR:\s*Cannot create a file when that file already exists|already exists/i.test(detail)) {
303
+ return { stdout: "", stderr: detail };
304
+ }
305
+ throw new Error(l(`schtasks ${args.join(" ")} failed: ${detail}`, `schtasks ${args.join(" ")} が失敗しました: ${detail}`));
306
+ }
307
+ }
308
+ async function schtasksTaskInstalled() {
309
+ try {
310
+ await execFileAsync("schtasks", ["/Query", "/TN", SERVICE_TASK_NAME_WINDOWS]);
311
+ return true;
312
+ }
313
+ catch {
314
+ return false;
315
+ }
316
+ }
317
+ async function writeUtf16LeWithBom(filePath, text) {
318
+ const bom = Buffer.from([0xff, 0xfe]);
319
+ const body = Buffer.from(text, "utf16le");
320
+ await mkdir(path.dirname(filePath), { recursive: true });
321
+ await writeFile(filePath, Buffer.concat([bom, body]));
322
+ }
323
+ const windowsProvider = {
324
+ platformId: "windows",
325
+ label: SERVICE_TASK_NAME_WINDOWS,
326
+ supported: true,
327
+ async install(config, options) {
328
+ const xmlPath = windowsManifestPath();
329
+ await writeUtf16LeWithBom(xmlPath, windowsTaskXml(config));
330
+ // Replace existing task if any.
331
+ if (await schtasksTaskInstalled()) {
332
+ await schtasks(["/Delete", "/TN", SERVICE_TASK_NAME_WINDOWS, "/F"], { allowMissing: true });
333
+ }
334
+ await schtasks(["/Create", "/TN", SERVICE_TASK_NAME_WINDOWS, "/XML", xmlPath, "/F"]);
335
+ if (!options?.noStart) {
336
+ await this.start(config);
337
+ }
338
+ },
339
+ async start(_config) {
340
+ if (!(await schtasksTaskInstalled())) {
341
+ throw new Error(l("service is not installed. Run: agent-sin service install", "service がインストールされていません。実行: agent-sin service install"));
342
+ }
343
+ await schtasks(["/Run", "/TN", SERVICE_TASK_NAME_WINDOWS]);
344
+ },
345
+ async stop(options) {
346
+ if (!(await schtasksTaskInstalled())) {
347
+ if (!options?.quiet) {
348
+ // No installed task is not an error for stop.
349
+ }
350
+ return;
351
+ }
352
+ await schtasks(["/End", "/TN", SERVICE_TASK_NAME_WINDOWS], { allowMissing: true });
353
+ if (options?.wait) {
354
+ const deadline = Date.now() + SERVICE_STOP_TIMEOUT_MS;
355
+ while (Date.now() < deadline) {
356
+ if (!(await isWindowsTaskRunning())) {
357
+ return;
358
+ }
359
+ await sleep(250);
360
+ }
361
+ }
362
+ },
363
+ manifestText(config) {
364
+ return windowsTaskXml(config);
365
+ },
366
+ async status(_config) {
367
+ const installed = await schtasksTaskInstalled();
368
+ return {
369
+ installed,
370
+ manifestPath: windowsManifestPath(),
371
+ manifestKind: "schtasks",
372
+ };
373
+ },
374
+ notSupportedReason() {
375
+ return "";
376
+ },
377
+ };
378
+ async function isWindowsTaskRunning() {
379
+ try {
380
+ const { stdout } = await execFileAsync("schtasks", ["/Query", "/TN", SERVICE_TASK_NAME_WINDOWS, "/FO", "CSV", "/NH"], { encoding: "utf8" });
381
+ const text = String(stdout).trim();
382
+ if (!text)
383
+ return false;
384
+ const lines = text.split(/\r?\n/);
385
+ return lines.some((line) => /"Running"/i.test(line));
386
+ }
387
+ catch {
388
+ return false;
389
+ }
390
+ }
391
+ // ---------- unsupported ----------
392
+ const unsupportedProvider = {
393
+ platformId: "unsupported",
394
+ label: SERVICE_LABEL_DARWIN,
395
+ supported: false,
396
+ async install() {
397
+ throw new Error(this.notSupportedReason());
398
+ },
399
+ async start() {
400
+ throw new Error(this.notSupportedReason());
401
+ },
402
+ async stop() {
403
+ throw new Error(this.notSupportedReason());
404
+ },
405
+ manifestText(_config) {
406
+ return "";
407
+ },
408
+ async status(_config) {
409
+ return { installed: false, manifestPath: "", manifestKind: "none" };
410
+ },
411
+ notSupportedReason() {
412
+ return l(`agent-sin service is not supported on ${process.platform}. Supported: macOS (launchd), Windows (Task Scheduler).`, `agent-sin service は ${process.platform} では未対応です。対応: macOS (launchd), Windows (Task Scheduler)。`);
413
+ },
414
+ };
415
+ // ---------- process discovery (cross-platform) ----------
416
+ export async function findAgentSinServiceProcesses() {
417
+ if (process.platform === "win32") {
418
+ return findAgentSinServiceProcessesWindows();
419
+ }
420
+ try {
421
+ const { stdout } = await execFileAsync("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
422
+ const currentPid = String(process.pid);
423
+ return String(stdout)
424
+ .split(/\r?\n/)
425
+ .map((line) => line.trim())
426
+ .filter(Boolean)
427
+ .filter((line) => !line.startsWith(currentPid))
428
+ .filter((line) => isServiceCommandLine(line));
429
+ }
430
+ catch {
431
+ return [];
432
+ }
433
+ }
434
+ async function findAgentSinServiceProcessesWindows() {
435
+ try {
436
+ const { stdout } = await execFileAsync("wmic", ["process", "where", "name='node.exe'", "get", "ProcessId,CommandLine", "/FORMAT:LIST"], { encoding: "utf8" });
437
+ const text = String(stdout);
438
+ const lines = [];
439
+ let currentCommand = "";
440
+ let currentPid = "";
441
+ for (const raw of text.split(/\r?\n/)) {
442
+ const line = raw.trim();
443
+ if (line.startsWith("CommandLine=")) {
444
+ currentCommand = line.slice("CommandLine=".length);
445
+ }
446
+ else if (line.startsWith("ProcessId=")) {
447
+ currentPid = line.slice("ProcessId=".length);
448
+ }
449
+ else if (line === "") {
450
+ if (currentCommand && currentPid && currentPid !== String(process.pid)) {
451
+ if (isServiceCommandLine(currentCommand)) {
452
+ lines.push(`${currentPid} ${currentCommand}`);
453
+ }
454
+ }
455
+ currentCommand = "";
456
+ currentPid = "";
457
+ }
458
+ }
459
+ if (currentCommand && currentPid && currentPid !== String(process.pid) && isServiceCommandLine(currentCommand)) {
460
+ lines.push(`${currentPid} ${currentCommand}`);
461
+ }
462
+ return lines;
463
+ }
464
+ catch {
465
+ return [];
466
+ }
467
+ }
468
+ export function isServiceCommandLine(line) {
469
+ return (isSchedulerCommandLine(line) ||
470
+ /\b(?:agent-sin|dist[\\/]cli[\\/]index\.js)\s+discord(?:\s|$)/.test(line) ||
471
+ /\b(?:agent-sin|dist[\\/]cli[\\/]index\.js)\s+telegram(?:\s|$)/.test(line));
472
+ }
473
+ export function isSchedulerCommandLine(line) {
474
+ return (/\b(?:agent-sin|dist[\\/]cli[\\/]index\.js)\s+(?:daemon|gateway)(?:\s|$)/.test(line) ||
475
+ /\b(?:agent-sin|dist[\\/]cli[\\/]index\.js)\s+service\s+run(?:\s|$)/.test(line));
476
+ }
477
+ export async function isSchedulerProcessRunning() {
478
+ return (await findAgentSinServiceProcesses()).some((line) => isSchedulerCommandLine(line));
479
+ }
@@ -0,0 +1,24 @@
1
+ import type { AppConfig } from "./config.js";
2
+ import { type AiPermissionMode, type AiProgressHandler } from "./ai-provider.js";
3
+ import type { SkillManifest } from "./skill-registry.js";
4
+ export interface PlannerHandoffTurn {
5
+ role: "user" | "assistant" | "tool";
6
+ content: string;
7
+ }
8
+ export interface PlanContext {
9
+ type: "create" | "edit";
10
+ skill_id: string;
11
+ history: PlannerHandoffTurn[];
12
+ original_text: string;
13
+ existing_manifest?: SkillManifest | null;
14
+ refine_input?: string;
15
+ previous_plan?: string;
16
+ onProgress?: AiProgressHandler;
17
+ permission_mode?: AiPermissionMode;
18
+ }
19
+ export interface PlanResult {
20
+ text: string;
21
+ model_id: string;
22
+ provider: string;
23
+ }
24
+ export declare function planSkill(config: AppConfig, ctx: PlanContext): Promise<PlanResult>;
@@ -0,0 +1,100 @@
1
+ import { getAiProvider } from "./ai-provider.js";
2
+ import { formatProfileMemoryPromptSection, readProfileMemoryForPrompt, } from "./profile-memory.js";
3
+ export async function planSkill(config, ctx) {
4
+ const profileMemory = await readProfileMemoryForPrompt(config);
5
+ const messages = buildPlannerMessages(ctx, profileMemory);
6
+ const response = await getAiProvider()(config, {
7
+ model_id: config.builder_model_id || config.chat_model_id,
8
+ messages,
9
+ temperature: 0.2,
10
+ role: "builder",
11
+ onProgress: ctx.onProgress,
12
+ permission_mode: ctx.permission_mode,
13
+ });
14
+ return {
15
+ text: extractPlanText(response.text),
16
+ model_id: response.model_id,
17
+ provider: response.provider,
18
+ };
19
+ }
20
+ function buildPlannerMessages(ctx, profileMemory) {
21
+ const system = [
22
+ "You are a skill design assistant.",
23
+ "The user may not be an engineer. From the conversation and the user's requirements, write a short plain-language plan that lets them immediately understand what the skill will do.",
24
+ "",
25
+ "Output language: write the plan in the same language as the user's most recent message. If the user wrote in Japanese, write the plan in Japanese; otherwise write in English.",
26
+ "",
27
+ "Goal: before implementation starts, the user should be able to decide 'this is fine' or 'I want to change this'.",
28
+ "Do not use technical terms such as manifest, runtime, manual/cron, or AI step. Use everyday language.",
29
+ "",
30
+ "Most important rules:",
31
+ "- Treat requirements already stated in <request> or <chat_history> as facts, not guesses, and carry them into the plan.",
32
+ "- If the user already said specific things such as 'every hour', 'summarize at 3pm', 'list spam/read/not urgent', or 'improve by learning', include them directly and do not ask again.",
33
+ "- For points the user has not mentioned, choose reasonable defaults and put them in the plan. It is fine to decide; the user can correct it.",
34
+ "- Only if you truly need confirmation, add at most 1 or 2 short questions at the end. Zero questions is preferred.",
35
+ "- Do not ask again about already-stated content by turning it into examples or multiple-choice questions.",
36
+ "- If <previous_plan> is provided, do not repeat the same questions already asked there. Reflect answers if present; otherwise choose defaults and proceed.",
37
+ "",
38
+ "Writing style:",
39
+ "- Free format. Do not force a fixed template or headings such as '## What to do'.",
40
+ "- Keep it short and focused, roughly a few lines to 10 lines.",
41
+ "- Put confirmation questions at the end only if needed. Omit them when not needed.",
42
+ "",
43
+ "Other:",
44
+ "- For edits (type=edit), respect the current settings and make clear what will change.",
45
+ "- The output is shown directly on screen. Do not add surrounding explanation or ``` fences.",
46
+ ].join("\n");
47
+ const messages = [{ role: "system", content: system }];
48
+ const contextLines = [
49
+ `<mode>${ctx.type}</mode>`,
50
+ `<skill_id>${ctx.skill_id}</skill_id>`,
51
+ ];
52
+ const profileLines = formatProfileMemoryPromptSection(profileMemory);
53
+ if (profileLines.length > 0) {
54
+ contextLines.push("<profile_memory>", ...profileLines, "</profile_memory>");
55
+ }
56
+ if (ctx.existing_manifest) {
57
+ const m = ctx.existing_manifest;
58
+ contextLines.push("<existing_skill>", ` id: ${m.id}`, ` name: ${m.name}`, ` description: ${m.description || ""}`, ` runtime: ${m.runtime}`, "</existing_skill>");
59
+ }
60
+ if (ctx.history.length > 0) {
61
+ contextLines.push("<chat_history>");
62
+ for (const turn of ctx.history.slice(-12)) {
63
+ const role = turn.role === "assistant" ? "assistant" : turn.role === "tool" ? "tool" : "user";
64
+ contextLines.push(` <${role}>${truncate(turn.content, 600)}</${role}>`);
65
+ }
66
+ contextLines.push("</chat_history>");
67
+ }
68
+ contextLines.push("<request>");
69
+ contextLines.push(truncate(ctx.original_text, 1200));
70
+ contextLines.push("</request>");
71
+ if (ctx.previous_plan) {
72
+ contextLines.push("<previous_plan>");
73
+ contextLines.push(truncate(ctx.previous_plan, 1500));
74
+ contextLines.push("</previous_plan>");
75
+ }
76
+ if (ctx.refine_input) {
77
+ contextLines.push("<refine_request>");
78
+ contextLines.push(truncate(ctx.refine_input, 600));
79
+ contextLines.push("</refine_request>");
80
+ contextLines.push("→ Update the plan by applying <refine_request> to <previous_plan>.");
81
+ }
82
+ else {
83
+ contextLines.push("→ Create the initial plan from the information above.");
84
+ }
85
+ messages.push({ role: "user", content: contextLines.join("\n") });
86
+ return messages;
87
+ }
88
+ function extractPlanText(raw) {
89
+ const fenced = raw.match(/```(?:markdown|md)?\s*([\s\S]*?)```/i);
90
+ if (fenced) {
91
+ return fenced[1].trim();
92
+ }
93
+ return raw.trim();
94
+ }
95
+ function truncate(value, max) {
96
+ if (value.length <= max) {
97
+ return value;
98
+ }
99
+ return `${value.slice(0, max)}…`;
100
+ }