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,100 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { l } from "./i18n.js";
4
+ export async function writeSkillOutputs(config, manifest, result, date = new Date()) {
5
+ const saved = [];
6
+ for (const output of manifest.outputs || []) {
7
+ const value = result.outputs?.[output.id];
8
+ if (!value) {
9
+ continue;
10
+ }
11
+ if (output.type !== "markdown") {
12
+ throw new Error(l(`Unsupported output type: ${output.type}`, `未対応の出力形式です: ${output.type}`));
13
+ }
14
+ const file = resolveOutputFile(config, output, date);
15
+ assertAllowedOutputPath(config, file);
16
+ await writeMarkdown(file, output, value, date);
17
+ saved.push({
18
+ id: output.id,
19
+ type: output.type,
20
+ path: file,
21
+ append: output.append ?? false,
22
+ show_saved: output.show_saved !== false,
23
+ });
24
+ }
25
+ return saved;
26
+ }
27
+ export function resolveOutputFile(config, output, date) {
28
+ const renderedDir = renderTemplate(output.path, date);
29
+ const renderedFilename = renderTemplate(output.filename, date);
30
+ const normalized = renderedDir.replace(/^\.?\//, "");
31
+ if (normalized === "notes" || normalized.startsWith("notes/")) {
32
+ const rest = normalized.replace(/^notes\/?/, "");
33
+ return path.join(config.notes_dir, rest, renderedFilename);
34
+ }
35
+ if (path.isAbsolute(normalized)) {
36
+ return path.join(normalized, renderedFilename);
37
+ }
38
+ return path.join(config.workspace, normalized, renderedFilename);
39
+ }
40
+ export function renderTemplate(template, date) {
41
+ const yyyy = String(date.getFullYear());
42
+ const MM = String(date.getMonth() + 1).padStart(2, "0");
43
+ const dd = String(date.getDate()).padStart(2, "0");
44
+ const isoDate = `${yyyy}-${MM}-${dd}`;
45
+ return template
46
+ .replaceAll("{{yyyy}}", yyyy)
47
+ .replaceAll("{{MM}}", MM)
48
+ .replaceAll("{{dd}}", dd)
49
+ .replaceAll("{{date}}", isoDate)
50
+ .replaceAll("{{datetime}}", date.toISOString());
51
+ }
52
+ async function writeMarkdown(file, output, value, date) {
53
+ const content = value.content || "";
54
+ await mkdir(path.dirname(file), { recursive: true });
55
+ if (output.append) {
56
+ const existing = await readTextIfExists(file);
57
+ const prefix = existing ? "" : initialMarkdown(value.frontmatter, date);
58
+ await writeFile(file, `${prefix}${existing}${ensureTrailingNewline(content)}`, "utf8");
59
+ return;
60
+ }
61
+ await writeFile(file, `${initialFrontmatter(value.frontmatter)}${content}`, "utf8");
62
+ }
63
+ function initialMarkdown(frontmatter, date) {
64
+ const title = renderTemplate("# {{date}}\n\n", date);
65
+ return `${initialFrontmatter(frontmatter)}${title}`;
66
+ }
67
+ function initialFrontmatter(frontmatter) {
68
+ if (!frontmatter || Object.keys(frontmatter).length === 0) {
69
+ return "";
70
+ }
71
+ const lines = ["---"];
72
+ for (const [key, value] of Object.entries(frontmatter)) {
73
+ if (Array.isArray(value)) {
74
+ lines.push(`${key}: [${value.map((item) => JSON.stringify(item)).join(", ")}]`);
75
+ }
76
+ else {
77
+ lines.push(`${key}: ${JSON.stringify(value)}`);
78
+ }
79
+ }
80
+ lines.push("---", "");
81
+ return `${lines.join("\n")}\n`;
82
+ }
83
+ function ensureTrailingNewline(value) {
84
+ return value.endsWith("\n") ? value : `${value}\n`;
85
+ }
86
+ async function readTextIfExists(file) {
87
+ try {
88
+ return await readFile(file, "utf8");
89
+ }
90
+ catch {
91
+ return "";
92
+ }
93
+ }
94
+ function assertAllowedOutputPath(config, file) {
95
+ const resolved = path.resolve(file);
96
+ const allowedRoots = [path.resolve(config.workspace), path.resolve(config.notes_dir)];
97
+ if (!allowedRoots.some((root) => resolved === root || resolved.startsWith(`${root}${path.sep}`))) {
98
+ throw new Error(l(`Output path is outside allowed directories: ${file}`, `出力先が許可された場所の外です: ${file}`));
99
+ }
100
+ }
@@ -0,0 +1,16 @@
1
+ import type { AppConfig } from "./config.js";
2
+ export type PlanDecision = "approve" | "refine";
3
+ export interface PlanDecisionInput {
4
+ userText: string;
5
+ currentPlan: string | null;
6
+ }
7
+ export interface ClassifyPlanDecisionOptions {
8
+ modelId?: string;
9
+ }
10
+ /**
11
+ * Classifies whether a user reply during the plan phase is approving the plan
12
+ * ("approve") or asking to refine/change it ("refine"). LLM-based classification
13
+ * keeps the call robust across colloquial and multilingual inputs. Slash
14
+ * commands (`/approve`, `/draft`) and empty input are handled upstream.
15
+ */
16
+ export declare function classifyPlanDecision(config: AppConfig, input: PlanDecisionInput, options?: ClassifyPlanDecisionOptions): Promise<PlanDecision>;
@@ -0,0 +1,88 @@
1
+ import { getAiProvider } from "./ai-provider.js";
2
+ /**
3
+ * Classifies whether a user reply during the plan phase is approving the plan
4
+ * ("approve") or asking to refine/change it ("refine"). LLM-based classification
5
+ * keeps the call robust across colloquial and multilingual inputs. Slash
6
+ * commands (`/approve`, `/draft`) and empty input are handled upstream.
7
+ */
8
+ export async function classifyPlanDecision(config, input, options = {}) {
9
+ const userText = input.userText.trim();
10
+ if (!userText) {
11
+ return "refine";
12
+ }
13
+ const system = buildPlanDecisionSystemPrompt();
14
+ const messages = [
15
+ { role: "system", content: system },
16
+ {
17
+ role: "user",
18
+ content: buildPlanDecisionUserPrompt({
19
+ userText,
20
+ currentPlan: input.currentPlan,
21
+ }),
22
+ },
23
+ ];
24
+ let response;
25
+ try {
26
+ response = await getAiProvider()(config, {
27
+ model_id: options.modelId || config.chat_model_id,
28
+ messages,
29
+ temperature: 0,
30
+ });
31
+ }
32
+ catch {
33
+ return "refine";
34
+ }
35
+ return parseDecision(response.text);
36
+ }
37
+ function buildPlanDecisionSystemPrompt() {
38
+ return [
39
+ "You classify Agent-Sin plan approval.",
40
+ "You will receive the plan just shown to the user and the user's reply to it.",
41
+ "Decide whether the user is approving the plan or asking to refine/change/add something.",
42
+ "",
43
+ "Rules:",
44
+ "- Short affirmations in any language (OK, yes, go, proceed, please do it, sounds good, leave it to you, 了解, 進めて, お願いします, いいよ, それで, 任せる, etc.) → approve",
45
+ "- Direct objections or added requirements (want more, also include, remove this, change that, もっと…, 〜も入れて, 〜は要らない, 〜を変えて, etc.) → refine",
46
+ "- Questions or confirmations → refine",
47
+ "- Ignore punctuation, emojis, politeness, and tone differences. Judge by meaning.",
48
+ "- If unsure, choose refine so the user's extra information is not lost.",
49
+ "",
50
+ "Return exactly one JSON object. No explanation and no ``` fences:",
51
+ '{"decision":"approve|refine","reason":"short reason"}',
52
+ ].join("\n");
53
+ }
54
+ function buildPlanDecisionUserPrompt(args) {
55
+ const lines = [];
56
+ if (args.currentPlan) {
57
+ lines.push("<current_plan>");
58
+ lines.push(args.currentPlan);
59
+ lines.push("</current_plan>");
60
+ }
61
+ else {
62
+ lines.push("<current_plan>(no plan generated)</current_plan>");
63
+ }
64
+ lines.push("<user_reply>");
65
+ lines.push(args.userText);
66
+ lines.push("</user_reply>");
67
+ lines.push("Return JSON only.");
68
+ return lines.join("\n");
69
+ }
70
+ function parseDecision(text) {
71
+ const stripped = text
72
+ .replace(/```json\s*/gi, "")
73
+ .replace(/```\s*$/g, "")
74
+ .trim();
75
+ const start = stripped.indexOf("{");
76
+ const end = stripped.lastIndexOf("}");
77
+ if (start < 0 || end <= start) {
78
+ return "refine";
79
+ }
80
+ try {
81
+ const parsed = JSON.parse(stripped.slice(start, end + 1));
82
+ const raw = String(parsed.decision || "").trim().toLowerCase();
83
+ return raw === "approve" ? "approve" : "refine";
84
+ }
85
+ catch {
86
+ return "refine";
87
+ }
88
+ }
@@ -0,0 +1,17 @@
1
+ import type { AppConfig } from "./config.js";
2
+ export type ProfileMemoryTarget = "soul" | "user" | "memory";
3
+ export interface ProfileMemoryFiles {
4
+ soul: string;
5
+ user: string;
6
+ memory: string;
7
+ paths: Record<ProfileMemoryTarget, string>;
8
+ }
9
+ export declare function profileMemoryDir(config: AppConfig): string;
10
+ export declare function profileMemoryPath(config: AppConfig, target: ProfileMemoryTarget): string;
11
+ export declare function ensureProfileMemoryFiles(config: AppConfig): Promise<ProfileMemoryFiles>;
12
+ export declare function readProfileMemoryFiles(config: AppConfig): Promise<ProfileMemoryFiles>;
13
+ export declare function readProfileMemoryForPrompt(config: AppConfig): Promise<ProfileMemoryFiles>;
14
+ export declare function appendProfileMemory(config: AppConfig, target: ProfileMemoryTarget, text: string, date?: Date): Promise<string>;
15
+ export declare function parseProfileMemoryTarget(value: string | undefined): ProfileMemoryTarget | undefined;
16
+ export declare function formatProfileEntry(text: string, date?: Date): string;
17
+ export declare function formatProfileMemoryPromptSection(profileMemory: ProfileMemoryFiles | undefined): string[];
@@ -0,0 +1,142 @@
1
+ import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { l } from "./i18n.js";
4
+ const SOUL_TEMPLATE_EN = `# soul.md
5
+
6
+ <!--
7
+ Long-term notes about the AI itself.
8
+ Write explicit user-provided guidance for role, tone, judgment criteria, and durable behavior here.
9
+ -->
10
+ `;
11
+ const SOUL_TEMPLATE_JA = `# soul.md
12
+
13
+ <!--
14
+ AI自身についての長期メモを書く場所です。
15
+ 役割、話し方、判断基準、継続したい姿勢などをユーザーが明示的に書きます。
16
+ -->
17
+ `;
18
+ const USER_TEMPLATE_EN = `# user.md
19
+
20
+ <!--
21
+ Long-term notes about the user.
22
+ Write explicit user-provided preferences, work context, things to avoid, and durable assumptions here.
23
+ -->
24
+ `;
25
+ const USER_TEMPLATE_JA = `# user.md
26
+
27
+ <!--
28
+ ユーザーについての長期メモを書く場所です。
29
+ 好み、作業文脈、避けたいこと、長期的な前提などをユーザーが明示的に書きます。
30
+ -->
31
+ `;
32
+ const MEMORY_TEMPLATE_EN = `# memory.md
33
+
34
+ <!--
35
+ Long-term notes for understanding the user deeply and keeping conversations smooth.
36
+ Unlike user.md (fixed profile), this file stores updateable observations such as preferences, values, and communication patterns.
37
+ Edit and update existing items when new information arrives instead of only appending.
38
+ Do not write operating rules, schedules, or skill behavior settings here.
39
+ -->
40
+ `;
41
+ const MEMORY_TEMPLATE_JA = `# memory.md
42
+
43
+ <!--
44
+ ユーザーを深く理解し会話をスムーズにするための長期メモ。
45
+ user.md(固定プロフィール)に対し、こちらは嗜好・価値観・コミュニケーション傾向など
46
+ 更新されうる観察を1行ずつ書く。新情報が入ったら追記ではなく既存項目を編集・更新していく。
47
+ 運用ルール・スケジュール・スキル動作の設定はここに書かない。
48
+ -->
49
+ `;
50
+ export function profileMemoryDir(config) {
51
+ return path.join(config.memory_dir, "profile");
52
+ }
53
+ export function profileMemoryPath(config, target) {
54
+ return path.join(profileMemoryDir(config), `${target}.md`);
55
+ }
56
+ export async function ensureProfileMemoryFiles(config) {
57
+ const paths = profileMemoryPaths(config);
58
+ await mkdir(profileMemoryDir(config), { recursive: true });
59
+ await writeIfMissing(paths.soul, l(SOUL_TEMPLATE_EN, SOUL_TEMPLATE_JA));
60
+ await writeIfMissing(paths.user, l(USER_TEMPLATE_EN, USER_TEMPLATE_JA));
61
+ await writeIfMissing(paths.memory, l(MEMORY_TEMPLATE_EN, MEMORY_TEMPLATE_JA));
62
+ return {
63
+ soul: await readFile(paths.soul, "utf8"),
64
+ user: await readFile(paths.user, "utf8"),
65
+ memory: await readFile(paths.memory, "utf8"),
66
+ paths,
67
+ };
68
+ }
69
+ export async function readProfileMemoryFiles(config) {
70
+ const files = await ensureProfileMemoryFiles(config);
71
+ return files;
72
+ }
73
+ export async function readProfileMemoryForPrompt(config) {
74
+ const files = await ensureProfileMemoryFiles(config);
75
+ return {
76
+ soul: promptContent(files.soul, "soul"),
77
+ user: promptContent(files.user, "user"),
78
+ memory: promptContent(files.memory, "memory"),
79
+ paths: files.paths,
80
+ };
81
+ }
82
+ export async function appendProfileMemory(config, target, text, date = new Date()) {
83
+ const value = text.trim();
84
+ if (!value) {
85
+ throw new Error(l("Profile text is empty", "プロフィールメモが空です"));
86
+ }
87
+ await ensureProfileMemoryFiles(config);
88
+ const file = profileMemoryPath(config, target);
89
+ await appendFile(file, formatProfileEntry(value, date), "utf8");
90
+ return file;
91
+ }
92
+ export function parseProfileMemoryTarget(value) {
93
+ if (value === "soul" || value === "user" || value === "memory") {
94
+ return value;
95
+ }
96
+ return undefined;
97
+ }
98
+ export function formatProfileEntry(text, date = new Date()) {
99
+ return `\n## ${date.toISOString()}\n\n${text.trim()}\n`;
100
+ }
101
+ export function formatProfileMemoryPromptSection(profileMemory) {
102
+ if (!profileMemory) {
103
+ return [];
104
+ }
105
+ const lines = [];
106
+ if (profileMemory.soul.trim()) {
107
+ lines.push("<soul.md>", profileMemory.soul.trim(), "</soul.md>");
108
+ }
109
+ if (profileMemory.user.trim()) {
110
+ lines.push("<user.md>", profileMemory.user.trim(), "</user.md>");
111
+ }
112
+ if (profileMemory.memory.trim()) {
113
+ lines.push("<memory.md>", profileMemory.memory.trim(), "</memory.md>");
114
+ }
115
+ return lines;
116
+ }
117
+ function profileMemoryPaths(config) {
118
+ return {
119
+ soul: profileMemoryPath(config, "soul"),
120
+ user: profileMemoryPath(config, "user"),
121
+ memory: profileMemoryPath(config, "memory"),
122
+ };
123
+ }
124
+ async function writeIfMissing(file, content) {
125
+ try {
126
+ await stat(file);
127
+ }
128
+ catch {
129
+ await writeFile(file, content, "utf8");
130
+ }
131
+ }
132
+ function promptContent(raw, target) {
133
+ const withoutComments = raw.replace(/<!--[\s\S]*?-->/g, "").trim();
134
+ if (!withoutComments) {
135
+ return "";
136
+ }
137
+ const lines = withoutComments.split(/\r?\n/);
138
+ if (lines[0]?.trim().toLowerCase() === `# ${target}.md`) {
139
+ lines.shift();
140
+ }
141
+ return lines.join("\n").trim();
142
+ }
@@ -0,0 +1,50 @@
1
+ import type { AppConfig } from "./config.js";
2
+ import { type SavedOutput } from "./output-writer.js";
3
+ import { type SkillManifest } from "./skill-registry.js";
4
+ import type { SkillResult } from "../skills-sdk/types.js";
5
+ export interface RunSkillResponse {
6
+ run_id: string;
7
+ manifest: SkillManifest;
8
+ result: SkillResult;
9
+ saved_outputs: SavedOutput[];
10
+ log_path: string;
11
+ memory_path?: string;
12
+ attempts: number;
13
+ }
14
+ export interface RunSkillOptions {
15
+ /** @deprecated Approval gating was removed; field kept for API compatibility. */
16
+ approved?: boolean;
17
+ dryRun?: boolean;
18
+ }
19
+ export interface SkillLogEntry {
20
+ level: "info" | "warn" | "error";
21
+ message: string;
22
+ ts?: string;
23
+ }
24
+ export interface SkillExecution {
25
+ result: SkillResult;
26
+ memory_updates?: Record<string, unknown>;
27
+ logs?: SkillLogEntry[];
28
+ }
29
+ export declare class SkillRunError extends Error {
30
+ readonly runId: string;
31
+ readonly skillId: string;
32
+ readonly logPath: string;
33
+ readonly originalMessage: string;
34
+ constructor(message: string, runId: string, skillId: string, logPath: string, originalMessage: string);
35
+ }
36
+ export declare function runSkill(config: AppConfig, skillId: string, args: Record<string, unknown>, options?: RunSkillOptions): Promise<RunSkillResponse>;
37
+ export declare class MissingEnvError extends Error {
38
+ readonly missing: Array<{
39
+ name: string;
40
+ description?: string;
41
+ }>;
42
+ constructor(missing: Array<{
43
+ name: string;
44
+ description?: string;
45
+ }>);
46
+ }
47
+ export declare function findMissingRequiredEnv(manifest: SkillManifest): Array<{
48
+ name: string;
49
+ description?: string;
50
+ }>;
@@ -0,0 +1,187 @@
1
+ import path from "node:path";
2
+ import { appendEventLog, createRunId, writeRunLog } from "./logger.js";
3
+ import { loadSkillMemory, saveSkillMemoryUpdates } from "./memory.js";
4
+ import { writeSkillOutputs } from "./output-writer.js";
5
+ import { findSkillManifest } from "./skill-registry.js";
6
+ import { validateSkillArgs } from "./input-schema.js";
7
+ import { loadDotenv } from "./secrets.js";
8
+ import { ensureProfileMemoryFiles } from "./profile-memory.js";
9
+ import { runPythonSkill } from "../runtimes/python-runner.js";
10
+ import { runTypeScriptSkill } from "../runtimes/typescript-runner.js";
11
+ import { detectLocale, l, t } from "./i18n.js";
12
+ export class SkillRunError extends Error {
13
+ runId;
14
+ skillId;
15
+ logPath;
16
+ originalMessage;
17
+ constructor(message, runId, skillId, logPath, originalMessage) {
18
+ super(message);
19
+ this.runId = runId;
20
+ this.skillId = skillId;
21
+ this.logPath = logPath;
22
+ this.originalMessage = originalMessage;
23
+ this.name = "SkillRunError";
24
+ }
25
+ }
26
+ export async function runSkill(config, skillId, args, options = {}) {
27
+ await loadDotenv(config.workspace);
28
+ await ensureProfileMemoryFiles(config);
29
+ const manifest = await findSkillManifest(config.skills_dir, skillId);
30
+ if (manifest.enabled === false) {
31
+ throw new Error(l(`Skill is disabled: ${skillId}`, `スキルが無効です: ${skillId}`));
32
+ }
33
+ assertRequiredEnv(manifest);
34
+ const runId = createRunId();
35
+ const started = new Date();
36
+ const memory = await loadSkillMemory(config, manifest);
37
+ const input = {
38
+ args,
39
+ trigger: {
40
+ type: "manual",
41
+ id: "manual",
42
+ time: started.toISOString(),
43
+ },
44
+ sources: {
45
+ workspace: config.workspace,
46
+ notes_dir: config.notes_dir,
47
+ memory_dir: config.memory_dir,
48
+ index_dir: config.index_dir,
49
+ logs_dir: config.logs_dir,
50
+ locale: detectLocale(),
51
+ },
52
+ memory,
53
+ };
54
+ input.args = validateSkillArgs(manifest, args);
55
+ let attempts = 0;
56
+ let lastError;
57
+ try {
58
+ const maxAttempts = Math.max(0, manifest.retry?.max_attempts || 0);
59
+ for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
60
+ attempts = attempt + 1;
61
+ try {
62
+ const execution = await executeSkill(config, manifest, input);
63
+ const result = normalizeResult(execution.result);
64
+ if (result.status === "error" && attempt < maxAttempts) {
65
+ await waitRetryDelay(manifest);
66
+ continue;
67
+ }
68
+ const savedOutputs = result.status === "ok" && !options.dryRun ? await writeSkillOutputs(config, manifest, result, started) : [];
69
+ const memoryPath = result.status === "ok" && !options.dryRun
70
+ ? await saveSkillMemoryUpdates(config, manifest, execution.memory_updates || {})
71
+ : undefined;
72
+ const finished = new Date().toISOString();
73
+ const skillLogs = execution.logs || [];
74
+ const logPath = await writeRunLog(config, {
75
+ run_id: runId,
76
+ skill_id: manifest.id,
77
+ status: result.status,
78
+ started_at: started.toISOString(),
79
+ finished_at: finished,
80
+ attempts,
81
+ input,
82
+ result,
83
+ saved_outputs: savedOutputs,
84
+ memory_path: memoryPath,
85
+ dry_run: options.dryRun ? true : undefined,
86
+ ctx_logs: skillLogs.length > 0 ? skillLogs : undefined,
87
+ });
88
+ for (const entry of skillLogs) {
89
+ await appendEventLog(config, {
90
+ ts: entry.ts,
91
+ level: entry.level,
92
+ source: "skill",
93
+ event: "ctx_log",
94
+ message: entry.message,
95
+ details: { skill_id: manifest.id, run_id: runId, dry_run: options.dryRun ? true : undefined },
96
+ });
97
+ }
98
+ return {
99
+ run_id: runId,
100
+ manifest,
101
+ result,
102
+ saved_outputs: savedOutputs,
103
+ log_path: logPath,
104
+ memory_path: memoryPath,
105
+ attempts,
106
+ };
107
+ }
108
+ catch (error) {
109
+ lastError = error;
110
+ if (attempt < maxAttempts) {
111
+ await waitRetryDelay(manifest);
112
+ continue;
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
118
+ }
119
+ catch (error) {
120
+ const finished = new Date().toISOString();
121
+ const logPath = await writeRunLog(config, {
122
+ run_id: runId,
123
+ skill_id: manifest.id,
124
+ status: "error",
125
+ started_at: started.toISOString(),
126
+ finished_at: finished,
127
+ attempts,
128
+ input,
129
+ error: error instanceof Error ? error.message : String(error),
130
+ });
131
+ const message = error instanceof Error ? error.message : String(error);
132
+ throw new SkillRunError(`${message} (log: ${path.basename(logPath)})`, runId, manifest.id, logPath, message);
133
+ }
134
+ }
135
+ async function executeSkill(config, manifest, input) {
136
+ if (manifest.runtime === "python") {
137
+ return runPythonSkill(config, manifest, input);
138
+ }
139
+ if (manifest.runtime === "typescript") {
140
+ return runTypeScriptSkill(config, manifest, input);
141
+ }
142
+ throw new Error(l(`Unsupported runtime: ${manifest.runtime}`, `未対応の runtime です: ${manifest.runtime}`));
143
+ }
144
+ export class MissingEnvError extends Error {
145
+ missing;
146
+ constructor(missing) {
147
+ super(l(`Missing required env vars: ${missing.map((entry) => entry.name).join(", ")}`, `必要な環境変数が未設定です: ${missing.map((entry) => entry.name).join(", ")}`));
148
+ this.missing = missing;
149
+ this.name = "MissingEnvError";
150
+ }
151
+ }
152
+ export function findMissingRequiredEnv(manifest) {
153
+ if (!manifest.required_env || manifest.required_env.length === 0)
154
+ return [];
155
+ const missing = [];
156
+ for (const entry of manifest.required_env) {
157
+ if (entry.optional)
158
+ continue;
159
+ const value = process.env[entry.name];
160
+ if (!value || value.trim() === "") {
161
+ missing.push({ name: entry.name, description: entry.description });
162
+ }
163
+ }
164
+ return missing;
165
+ }
166
+ function assertRequiredEnv(manifest) {
167
+ const missing = findMissingRequiredEnv(manifest);
168
+ if (missing.length > 0) {
169
+ throw new MissingEnvError(missing);
170
+ }
171
+ }
172
+ async function waitRetryDelay(manifest) {
173
+ const delay = manifest.retry?.delay_ms || 0;
174
+ if (delay > 0) {
175
+ await new Promise((resolve) => setTimeout(resolve, delay));
176
+ }
177
+ }
178
+ function normalizeResult(result) {
179
+ return {
180
+ status: result.status || "ok",
181
+ title: result.title || t("skill.default_done"),
182
+ summary: result.summary || "",
183
+ outputs: result.outputs || {},
184
+ data: result.data || {},
185
+ suggestions: result.suggestions || [],
186
+ };
187
+ }
@@ -0,0 +1,28 @@
1
+ export interface ScheduleEntry {
2
+ id: string;
3
+ cron: string;
4
+ skill: string;
5
+ enabled: boolean;
6
+ args: Record<string, unknown>;
7
+ approve: boolean;
8
+ description?: string;
9
+ expression: CronExpression;
10
+ }
11
+ export interface CronExpression {
12
+ raw: string;
13
+ minute: CronField;
14
+ hour: CronField;
15
+ dayOfMonth: CronField;
16
+ month: CronField;
17
+ dayOfWeek: CronField;
18
+ }
19
+ export interface CronField {
20
+ values: Set<number>;
21
+ isWildcard: boolean;
22
+ }
23
+ export declare function loadSchedules(workspace: string): Promise<ScheduleEntry[]>;
24
+ export declare function schedulesFile(workspace: string): string;
25
+ export declare function legacySchedulesFile(workspace: string): string;
26
+ export declare function parseCron(raw: string): CronExpression;
27
+ export declare function matchesCron(expression: CronExpression, date: Date): boolean;
28
+ export declare function nextRunAfter(expression: CronExpression, from: Date, lookaheadMinutes?: number): Date | null;