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.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/assets/logo.png +0 -0
- package/builtin-skills/_shared/_models_lib.py +227 -0
- package/builtin-skills/_shared/_profile_lib.py +98 -0
- package/builtin-skills/_shared/_schedules_lib.py +313 -0
- package/builtin-skills/_shared/_skill_settings_lib.py +153 -0
- package/builtin-skills/_shared/i18n.py +26 -0
- package/builtin-skills/memo-delete/main.py +155 -0
- package/builtin-skills/memo-delete/skill.yaml +57 -0
- package/builtin-skills/memo-index/main.py +178 -0
- package/builtin-skills/memo-index/skill.yaml +53 -0
- package/builtin-skills/memo-save/README.md +5 -0
- package/builtin-skills/memo-save/main.py +74 -0
- package/builtin-skills/memo-save/skill.yaml +52 -0
- package/builtin-skills/memo-search/README.md +10 -0
- package/builtin-skills/memo-search/main.py +97 -0
- package/builtin-skills/memo-search/skill.yaml +51 -0
- package/builtin-skills/memo-vector-search/main.py +121 -0
- package/builtin-skills/memo-vector-search/skill.yaml +53 -0
- package/builtin-skills/model-add/main.py +180 -0
- package/builtin-skills/model-add/skill.yaml +112 -0
- package/builtin-skills/model-list/main.py +93 -0
- package/builtin-skills/model-list/skill.yaml +48 -0
- package/builtin-skills/model-set/main.py +123 -0
- package/builtin-skills/model-set/skill.yaml +69 -0
- package/builtin-skills/profile-delete/_profile_lib.py +98 -0
- package/builtin-skills/profile-delete/main.py +98 -0
- package/builtin-skills/profile-delete/skill.yaml +64 -0
- package/builtin-skills/profile-edit/_profile_lib.py +98 -0
- package/builtin-skills/profile-edit/main.py +97 -0
- package/builtin-skills/profile-edit/skill.yaml +72 -0
- package/builtin-skills/profile-save/main.py +52 -0
- package/builtin-skills/profile-save/skill.yaml +69 -0
- package/builtin-skills/schedule-add/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-add/main.py +137 -0
- package/builtin-skills/schedule-add/skill.yaml +94 -0
- package/builtin-skills/schedule-list/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-list/main.py +86 -0
- package/builtin-skills/schedule-list/skill.yaml +45 -0
- package/builtin-skills/schedule-remove/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-remove/main.py +69 -0
- package/builtin-skills/schedule-remove/skill.yaml +49 -0
- package/builtin-skills/schedule-toggle/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-toggle/main.py +78 -0
- package/builtin-skills/schedule-toggle/skill.yaml +61 -0
- package/builtin-skills/skills-disable/main.py +63 -0
- package/builtin-skills/skills-disable/skill.yaml +52 -0
- package/builtin-skills/skills-enable/main.py +62 -0
- package/builtin-skills/skills-enable/skill.yaml +51 -0
- package/builtin-skills/todo-add/main.py +68 -0
- package/builtin-skills/todo-add/skill.yaml +53 -0
- package/builtin-skills/todo-delete/main.py +65 -0
- package/builtin-skills/todo-delete/skill.yaml +47 -0
- package/builtin-skills/todo-done/main.py +75 -0
- package/builtin-skills/todo-done/skill.yaml +47 -0
- package/builtin-skills/todo-list/main.py +91 -0
- package/builtin-skills/todo-list/skill.yaml +48 -0
- package/builtin-skills/todo-tick/main.py +125 -0
- package/builtin-skills/todo-tick/skill.yaml +48 -0
- package/dist/builder/build-action-classifier.d.ts +18 -0
- package/dist/builder/build-action-classifier.js +142 -0
- package/dist/builder/build-commands.d.ts +19 -0
- package/dist/builder/build-commands.js +133 -0
- package/dist/builder/build-flow.d.ts +72 -0
- package/dist/builder/build-flow.js +416 -0
- package/dist/builder/builder-session.d.ts +117 -0
- package/dist/builder/builder-session.js +1129 -0
- package/dist/builder/conversation-router.d.ts +22 -0
- package/dist/builder/conversation-router.js +69 -0
- package/dist/builder/intent-runtime-store.d.ts +7 -0
- package/dist/builder/intent-runtime-store.js +60 -0
- package/dist/builder/progress-format.d.ts +7 -0
- package/dist/builder/progress-format.js +46 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2835 -0
- package/dist/cli/spinner.d.ts +30 -0
- package/dist/cli/spinner.js +164 -0
- package/dist/core/ai-provider.d.ts +75 -0
- package/dist/core/ai-provider.js +678 -0
- package/dist/core/builtin-skills.d.ts +27 -0
- package/dist/core/builtin-skills.js +120 -0
- package/dist/core/chat-engine.d.ts +70 -0
- package/dist/core/chat-engine.js +812 -0
- package/dist/core/config.d.ts +127 -0
- package/dist/core/config.js +1379 -0
- package/dist/core/daily-memory-promotion.d.ts +21 -0
- package/dist/core/daily-memory-promotion.js +422 -0
- package/dist/core/i18n.d.ts +23 -0
- package/dist/core/i18n.js +167 -0
- package/dist/core/info-lines.d.ts +5 -0
- package/dist/core/info-lines.js +39 -0
- package/dist/core/input-schema.d.ts +2 -0
- package/dist/core/input-schema.js +156 -0
- package/dist/core/intent-router.d.ts +27 -0
- package/dist/core/intent-router.js +160 -0
- package/dist/core/logger.d.ts +60 -0
- package/dist/core/logger.js +240 -0
- package/dist/core/memory.d.ts +10 -0
- package/dist/core/memory.js +72 -0
- package/dist/core/message-utils.d.ts +13 -0
- package/dist/core/message-utils.js +104 -0
- package/dist/core/notifier.d.ts +17 -0
- package/dist/core/notifier.js +424 -0
- package/dist/core/output-writer.d.ts +13 -0
- package/dist/core/output-writer.js +100 -0
- package/dist/core/plan-decision.d.ts +16 -0
- package/dist/core/plan-decision.js +88 -0
- package/dist/core/profile-memory.d.ts +17 -0
- package/dist/core/profile-memory.js +142 -0
- package/dist/core/runtime.d.ts +50 -0
- package/dist/core/runtime.js +187 -0
- package/dist/core/scheduler.d.ts +28 -0
- package/dist/core/scheduler.js +155 -0
- package/dist/core/secrets.d.ts +31 -0
- package/dist/core/secrets.js +214 -0
- package/dist/core/service.d.ts +35 -0
- package/dist/core/service.js +479 -0
- package/dist/core/skill-planner.d.ts +24 -0
- package/dist/core/skill-planner.js +100 -0
- package/dist/core/skill-registry.d.ts +98 -0
- package/dist/core/skill-registry.js +319 -0
- package/dist/core/skill-scaffold.d.ts +33 -0
- package/dist/core/skill-scaffold.js +256 -0
- package/dist/core/skill-settings.d.ts +11 -0
- package/dist/core/skill-settings.js +63 -0
- package/dist/core/transfer.d.ts +31 -0
- package/dist/core/transfer.js +270 -0
- package/dist/core/update-notifier.d.ts +2 -0
- package/dist/core/update-notifier.js +140 -0
- package/dist/discord/bot.d.ts +96 -0
- package/dist/discord/bot.js +2424 -0
- package/dist/runtimes/codex-app-server.d.ts +53 -0
- package/dist/runtimes/codex-app-server.js +305 -0
- package/dist/runtimes/python-runner.d.ts +7 -0
- package/dist/runtimes/python-runner.js +302 -0
- package/dist/runtimes/typescript-runner.d.ts +5 -0
- package/dist/runtimes/typescript-runner.js +172 -0
- package/dist/skills-sdk/types.d.ts +38 -0
- package/dist/skills-sdk/types.js +1 -0
- package/dist/telegram/bot.d.ts +94 -0
- package/dist/telegram/bot.js +1219 -0
- package/install.ps1 +132 -0
- package/install.sh +130 -0
- package/package.json +60 -0
- package/templates/skill-python/main.py +74 -0
- package/templates/skill-python/skill.yaml +48 -0
- package/templates/skill-typescript/main.ts +87 -0
- package/templates/skill-typescript/skill.yaml +42 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { classifyIntent } from "../core/intent-router.js";
|
|
2
|
+
import { listSkillManifests } from "../core/skill-registry.js";
|
|
3
|
+
import { buildDraftWithAgent, prepareEditDraft, } from "./builder-session.js";
|
|
4
|
+
import { buildRegisterLines } from "./build-commands.js";
|
|
5
|
+
import { loadDotenv, upsertDotenv } from "../core/secrets.js";
|
|
6
|
+
import { createBuildSession } from "./builder-session.js";
|
|
7
|
+
import { findMissingRequiredEnv } from "../core/runtime.js";
|
|
8
|
+
import { loadSkillManifest } from "../core/skill-registry.js";
|
|
9
|
+
import { classifyBuildModeAction, classifyHandoffApproval, } from "./build-action-classifier.js";
|
|
10
|
+
import { detectLocale, l } from "../core/i18n.js";
|
|
11
|
+
export function createIntentRuntime(enabled = true) {
|
|
12
|
+
return {
|
|
13
|
+
pending: null,
|
|
14
|
+
pending_exit: null,
|
|
15
|
+
preferred_skill_id: null,
|
|
16
|
+
progress_detail: false,
|
|
17
|
+
enabled,
|
|
18
|
+
mode: "chat",
|
|
19
|
+
build: null,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const SLASH_EXIT_COMMANDS = new Set([
|
|
23
|
+
"/back",
|
|
24
|
+
"/chat",
|
|
25
|
+
"/exit-build",
|
|
26
|
+
"!back",
|
|
27
|
+
"!chat",
|
|
28
|
+
"!exit-build",
|
|
29
|
+
]);
|
|
30
|
+
const SLASH_REGISTER_COMMANDS = new Set(["/register", "!register"]);
|
|
31
|
+
const SLASH_TEST_COMMANDS = new Set(["/test", "!test"]);
|
|
32
|
+
function isSlashExitCommand(text) {
|
|
33
|
+
return SLASH_EXIT_COMMANDS.has(text.trim().toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
function isSlashRegisterCommand(text) {
|
|
36
|
+
return SLASH_REGISTER_COMMANDS.has(text.trim().toLowerCase());
|
|
37
|
+
}
|
|
38
|
+
function isSlashTestCommand(text) {
|
|
39
|
+
return SLASH_TEST_COMMANDS.has(text.trim().toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
export function parseEnvDirective(text) {
|
|
42
|
+
const trimmed = text.trim();
|
|
43
|
+
const match = trimmed.match(/^env\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*)$/i);
|
|
44
|
+
if (!match)
|
|
45
|
+
return null;
|
|
46
|
+
return { name: match[1], value: match[2].trim() };
|
|
47
|
+
}
|
|
48
|
+
export function isReservedAgentSinEnv(name) {
|
|
49
|
+
return /^AGENT_SIN_/i.test(name);
|
|
50
|
+
}
|
|
51
|
+
export function looksLikeRawSecretValue(text) {
|
|
52
|
+
const trimmed = text.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
return false;
|
|
55
|
+
if (/\s/.test(trimmed))
|
|
56
|
+
return false;
|
|
57
|
+
if (trimmed.length < 16 || trimmed.length > 512)
|
|
58
|
+
return false;
|
|
59
|
+
if (!/^[A-Za-z0-9._\-:/+=~]+$/.test(trimmed))
|
|
60
|
+
return false;
|
|
61
|
+
if (!/[0-9]/.test(trimmed) && !/[A-Z]/.test(trimmed))
|
|
62
|
+
return false;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
export function extractAutoSaveSecretValue(text, envName) {
|
|
66
|
+
const candidates = collectAutoSaveSecretCandidates(text);
|
|
67
|
+
const values = Array.from(new Set(candidates
|
|
68
|
+
.map((candidate) => normalizeAutoSaveSecretCandidate(candidate, envName))
|
|
69
|
+
.filter((value) => Boolean(value))));
|
|
70
|
+
return values.length === 1 ? values[0] : null;
|
|
71
|
+
}
|
|
72
|
+
function collectAutoSaveSecretCandidates(text) {
|
|
73
|
+
const candidates = [];
|
|
74
|
+
const assignmentPattern = /\b[A-Za-z_][A-Za-z0-9_]*\s*=\s*([A-Za-z0-9._\-:/+=~]{16,512})/g;
|
|
75
|
+
for (const match of text.matchAll(assignmentPattern)) {
|
|
76
|
+
candidates.push(match[0], match[1]);
|
|
77
|
+
}
|
|
78
|
+
const tokenPattern = /[A-Za-z0-9_][A-Za-z0-9._\-:/+=~]{15,511}/g;
|
|
79
|
+
for (const match of text.matchAll(tokenPattern)) {
|
|
80
|
+
candidates.push(match[0]);
|
|
81
|
+
}
|
|
82
|
+
return candidates;
|
|
83
|
+
}
|
|
84
|
+
function normalizeAutoSaveSecretCandidate(raw, envName) {
|
|
85
|
+
let value = raw
|
|
86
|
+
.trim()
|
|
87
|
+
.replace(/^[`"'「『]+/g, "")
|
|
88
|
+
.replace(/[`"'」』、。,.]+$/g, "");
|
|
89
|
+
const inlineAssignment = value.match(/^[A-Za-z_][A-Za-z0-9_]*=(.+)$/);
|
|
90
|
+
if (inlineAssignment) {
|
|
91
|
+
value = inlineAssignment[1].trim();
|
|
92
|
+
}
|
|
93
|
+
if (/^https?:\/\//i.test(value) && !allowsUrlSecret(envName)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return looksLikeRawSecretValue(value) ? value : null;
|
|
97
|
+
}
|
|
98
|
+
function allowsUrlSecret(envName) {
|
|
99
|
+
return Boolean(envName && /(?:URL|URI|ENDPOINT|WEBHOOK)/i.test(envName));
|
|
100
|
+
}
|
|
101
|
+
export async function tryAutoSaveBuildEnv(config, build, text) {
|
|
102
|
+
if (collectAutoSaveSecretCandidates(text).length === 0)
|
|
103
|
+
return null;
|
|
104
|
+
let manifest;
|
|
105
|
+
try {
|
|
106
|
+
const session = await createBuildSession(config, build.skill_id);
|
|
107
|
+
manifest = await loadSkillManifest(session.draft_dir);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
await loadDotenv(config.workspace);
|
|
113
|
+
const missing = findMissingRequiredEnv(manifest).filter((entry) => !isReservedAgentSinEnv(entry.name));
|
|
114
|
+
if (missing.length !== 1)
|
|
115
|
+
return null;
|
|
116
|
+
const envName = missing[0].name;
|
|
117
|
+
const value = extractAutoSaveSecretValue(text, envName);
|
|
118
|
+
if (!value)
|
|
119
|
+
return null;
|
|
120
|
+
try {
|
|
121
|
+
const result = await upsertDotenv(config.workspace, [{ key: envName, value }]);
|
|
122
|
+
return [l(`Saved ${envName} to ${result.path}.`, `${envName} を ${result.path} に保存しました。`)];
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
return [l(`Failed to save environment variable: ${message}`, `環境変数の保存に失敗: ${message}`)];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export async function classifyPendingHandoff(config, text, history, intentRuntime) {
|
|
130
|
+
const pending = intentRuntime.pending;
|
|
131
|
+
if (!pending) {
|
|
132
|
+
return { decision: "discuss" };
|
|
133
|
+
}
|
|
134
|
+
const result = await classifyHandoffApproval(config, text, history, pending);
|
|
135
|
+
return { decision: result.decision, carry_over_text: result.carry_over_text };
|
|
136
|
+
}
|
|
137
|
+
async function renderEditModeFailureMessage(config, skillId, error) {
|
|
138
|
+
const candidates = await findEditCandidates(config, skillId);
|
|
139
|
+
if (candidates.length > 0) {
|
|
140
|
+
const ids = candidates.map((skill) => skill.id).join(", ");
|
|
141
|
+
return l(`Could not enter edit mode: "${skillId}" is not a registered skill id. Close matches: ${ids}. Tell me the exact id to edit.`, `編集モードに入れませんでした: "${skillId}" は登録済みスキルIDではありません。近い候補: ${ids}。直す対象を正確なIDで指定してください。`);
|
|
142
|
+
}
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
return l(`Could not enter edit mode: ${message}`, `編集モードに入れませんでした: ${message}`);
|
|
145
|
+
}
|
|
146
|
+
async function findEditCandidates(config, skillId) {
|
|
147
|
+
let skills;
|
|
148
|
+
try {
|
|
149
|
+
skills = await listSkillManifests(config.skills_dir);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
const normalized = skillId.trim().toLowerCase();
|
|
155
|
+
if (!normalized)
|
|
156
|
+
return [];
|
|
157
|
+
const prefixMatches = skills.filter((skill) => skill.id.toLowerCase().startsWith(`${normalized}-`));
|
|
158
|
+
if (prefixMatches.length > 0) {
|
|
159
|
+
return prefixMatches.slice(0, 8);
|
|
160
|
+
}
|
|
161
|
+
const source = tokenizeText(normalized);
|
|
162
|
+
const scored = skills
|
|
163
|
+
.map((skill) => {
|
|
164
|
+
const phrases = skill.invocation?.phrases?.join(" ") || "";
|
|
165
|
+
const target = tokenizeText(`${skill.id} ${skill.name} ${skill.description || ""} ${phrases}`);
|
|
166
|
+
return { skill, score: jaccard(source, target) };
|
|
167
|
+
})
|
|
168
|
+
.filter((item) => item.score >= 0.35)
|
|
169
|
+
.sort((a, b) => b.score - a.score || a.skill.id.localeCompare(b.skill.id));
|
|
170
|
+
return scored.slice(0, 5).map((item) => item.skill);
|
|
171
|
+
}
|
|
172
|
+
function tokenizeText(text) {
|
|
173
|
+
return new Set(text
|
|
174
|
+
.toLowerCase()
|
|
175
|
+
.replace(/[ \s]+/g, "-")
|
|
176
|
+
.split(/[^a-z0-9ぁ-んァ-ヶ一-龠]+/)
|
|
177
|
+
.map((token) => stem(token))
|
|
178
|
+
.filter((token) => token.length > 0));
|
|
179
|
+
}
|
|
180
|
+
function stem(token) {
|
|
181
|
+
const suffixes = ["izer", "izing", "ization", "ize", "er", "ing", "ed", "es", "s"];
|
|
182
|
+
for (const suffix of suffixes) {
|
|
183
|
+
if (token.length > suffix.length + 2 && token.endsWith(suffix)) {
|
|
184
|
+
return token.slice(0, -suffix.length);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return token;
|
|
188
|
+
}
|
|
189
|
+
function jaccard(a, b) {
|
|
190
|
+
if (a.size === 0 && b.size === 0)
|
|
191
|
+
return 0;
|
|
192
|
+
let intersection = 0;
|
|
193
|
+
for (const item of a) {
|
|
194
|
+
if (b.has(item))
|
|
195
|
+
intersection += 1;
|
|
196
|
+
}
|
|
197
|
+
const union = new Set([...a, ...b]).size;
|
|
198
|
+
return union === 0 ? 0 : intersection / union;
|
|
199
|
+
}
|
|
200
|
+
export function renderBuildFooter(intentRuntime, options) {
|
|
201
|
+
if (intentRuntime.mode !== "build" || !intentRuntime.build)
|
|
202
|
+
return "";
|
|
203
|
+
const label = intentRuntime.build.skill_name?.trim() || intentRuntime.build.skill_id;
|
|
204
|
+
const locale = inferFooterLocale(options.languageHint);
|
|
205
|
+
if (options.exitPrefix === "!") {
|
|
206
|
+
return chooseFooterText(locale, `(Currently in build mode for "${label}". Reply "back" to leave)`, `(現在:「${label}」のビルドモードです。抜けるには「戻る」と返事してください)`);
|
|
207
|
+
}
|
|
208
|
+
return chooseFooterText(locale, `(Currently in build mode for "${label}". Reply /back to leave)`, `(現在:「${label}」のビルドモードです。抜けるには /back と返事してください)`);
|
|
209
|
+
}
|
|
210
|
+
function chooseFooterText(locale, en, ja) {
|
|
211
|
+
return locale === "ja" ? ja : en;
|
|
212
|
+
}
|
|
213
|
+
function inferFooterLocale(languageHint) {
|
|
214
|
+
const text = Array.isArray(languageHint)
|
|
215
|
+
? languageHint.join("\n")
|
|
216
|
+
: languageHint || "";
|
|
217
|
+
if (/[\u3040-\u30ff\u3400-\u9fff]/.test(text)) {
|
|
218
|
+
return "ja";
|
|
219
|
+
}
|
|
220
|
+
if (/[A-Za-z]/.test(text)) {
|
|
221
|
+
return "en";
|
|
222
|
+
}
|
|
223
|
+
return detectLocale();
|
|
224
|
+
}
|
|
225
|
+
async function resolveSkillDisplayName(config, skillId) {
|
|
226
|
+
try {
|
|
227
|
+
const skills = await listSkillManifests(config.skills_dir);
|
|
228
|
+
const manifest = skills.find((item) => item.id === skillId);
|
|
229
|
+
const name = manifest?.name?.trim();
|
|
230
|
+
return name && name.length > 0 ? name : null;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Decide whether to append the build-mode footer to a reply.
|
|
238
|
+
*/
|
|
239
|
+
export function shouldShowBuildFooter(opts) {
|
|
240
|
+
return opts.intentRuntime.mode === "build" && Boolean(opts.intentRuntime.build);
|
|
241
|
+
}
|
|
242
|
+
async function detectBuildAutoExit(config, text, history) {
|
|
243
|
+
const skills = await listSkillManifests(config.skills_dir);
|
|
244
|
+
if (skills.length === 0)
|
|
245
|
+
return null;
|
|
246
|
+
let result;
|
|
247
|
+
try {
|
|
248
|
+
result = await classifyIntent(config, text, skills, {
|
|
249
|
+
history: (history || []).map((turn) => ({ role: turn.role, content: turn.content })),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
// Only auto-exit when the user clearly wants to run an existing skill.
|
|
256
|
+
// We intentionally do NOT auto-exit on plain "chat" intent because the
|
|
257
|
+
// builder agent legitimately receives free-form requests that look like chat.
|
|
258
|
+
if (result.confidence === "low")
|
|
259
|
+
return null;
|
|
260
|
+
if (result.intent === "skill_run" && result.matched_skill_id) {
|
|
261
|
+
return { preferred_skill_id: result.matched_skill_id, reason: result.reason };
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
export async function enterBuildMode(config, history, intentRuntime, hooks = {}, extraText, eventSource) {
|
|
266
|
+
const pending = intentRuntime.pending;
|
|
267
|
+
if (!pending) {
|
|
268
|
+
return [l("There is no pending proposal.", "進行中の提案はありません。")];
|
|
269
|
+
}
|
|
270
|
+
intentRuntime.pending = null;
|
|
271
|
+
intentRuntime.pending_exit = null;
|
|
272
|
+
const seed = history.map((turn) => ({
|
|
273
|
+
role: turn.role === "tool" ? "tool" : turn.role,
|
|
274
|
+
content: turn.content,
|
|
275
|
+
}));
|
|
276
|
+
if (pending.type === "edit") {
|
|
277
|
+
try {
|
|
278
|
+
await prepareEditDraft(config, pending.skill_id);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
return [await renderEditModeFailureMessage(config, pending.skill_id, error)];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
intentRuntime.mode = "build";
|
|
285
|
+
intentRuntime.build = {
|
|
286
|
+
type: pending.type,
|
|
287
|
+
skill_id: pending.skill_id,
|
|
288
|
+
skill_name: await resolveSkillDisplayName(config, pending.skill_id),
|
|
289
|
+
context_seed: seed,
|
|
290
|
+
context_consumed: false,
|
|
291
|
+
original_text: pending.original_text,
|
|
292
|
+
event_source: eventSource,
|
|
293
|
+
};
|
|
294
|
+
const initialText = extraText && extraText.trim().length > 0
|
|
295
|
+
? `${pending.original_text}\n\n[追加要件]\n${extraText.trim()}`
|
|
296
|
+
: pending.original_text;
|
|
297
|
+
return forwardToBuilder(config, intentRuntime.build, initialText, hooks);
|
|
298
|
+
}
|
|
299
|
+
export async function handleBuildModeMessage(config, text, intentRuntime, hooks = {}, eventSource) {
|
|
300
|
+
const build = intentRuntime.build;
|
|
301
|
+
if (!build) {
|
|
302
|
+
intentRuntime.mode = "chat";
|
|
303
|
+
intentRuntime.pending_exit = null;
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
if (eventSource && build.event_source !== eventSource) {
|
|
307
|
+
build.event_source = eventSource;
|
|
308
|
+
}
|
|
309
|
+
// Deterministic slash-command exit.
|
|
310
|
+
if (isSlashExitCommand(text)) {
|
|
311
|
+
intentRuntime.mode = "chat";
|
|
312
|
+
intentRuntime.build = null;
|
|
313
|
+
intentRuntime.pending_exit = null;
|
|
314
|
+
return [l("Back to chat.", "◀︎ チャットに戻りました。")];
|
|
315
|
+
}
|
|
316
|
+
// Save env values silently if user voluntarily provides them.
|
|
317
|
+
const envDirective = parseEnvDirective(text);
|
|
318
|
+
if (envDirective) {
|
|
319
|
+
if (isReservedAgentSinEnv(envDirective.name)) {
|
|
320
|
+
return [
|
|
321
|
+
l(`${envDirective.name} is an agent-sin runtime setting, so it was not saved as a skill environment variable.`, `${envDirective.name} は agent-sin 本体の設定です。スキル用の環境変数としては保存しませんでした。`),
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const result = await upsertDotenv(config.workspace, [
|
|
326
|
+
{ key: envDirective.name, value: envDirective.value },
|
|
327
|
+
]);
|
|
328
|
+
return [l(`Saved ${envDirective.name} to ${result.path}.`, `${envDirective.name} を ${result.path} に保存しました。`)];
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
332
|
+
return [l(`Failed to save environment variable: ${message}`, `環境変数の保存に失敗: ${message}`)];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// User pasted a raw API key value — auto-save into the only missing required_env if any.
|
|
336
|
+
const autoSaved = await tryAutoSaveBuildEnv(config, build, text);
|
|
337
|
+
if (autoSaved) {
|
|
338
|
+
return autoSaved;
|
|
339
|
+
}
|
|
340
|
+
// Deterministic slash-command shortcuts for register / test.
|
|
341
|
+
if (isSlashRegisterCommand(text)) {
|
|
342
|
+
return handleRegisterAction(config, build, intentRuntime);
|
|
343
|
+
}
|
|
344
|
+
if (isSlashTestCommand(text)) {
|
|
345
|
+
return handleTestAction(build, intentRuntime);
|
|
346
|
+
}
|
|
347
|
+
// Classify the user's intent inside build mode (exit / register / test / continue).
|
|
348
|
+
const action = await classifyBuildModeAction(config, text, hooks.history || [], build);
|
|
349
|
+
if (action.action === "exit") {
|
|
350
|
+
intentRuntime.mode = "chat";
|
|
351
|
+
intentRuntime.build = null;
|
|
352
|
+
intentRuntime.pending_exit = null;
|
|
353
|
+
return [l("Back to chat.", "◀︎ チャットに戻りました。")];
|
|
354
|
+
}
|
|
355
|
+
if (action.action === "register") {
|
|
356
|
+
return handleRegisterAction(config, build, intentRuntime);
|
|
357
|
+
}
|
|
358
|
+
if (action.action === "test") {
|
|
359
|
+
return handleTestAction(build, intentRuntime);
|
|
360
|
+
}
|
|
361
|
+
// If the user's message looks like a routine chat / existing-skill request
|
|
362
|
+
// (e.g. "メール調べて…"), quietly leave build mode so the normal chat engine
|
|
363
|
+
// can run skills. Return null so the router falls through to chatRespond.
|
|
364
|
+
const autoExit = await detectBuildAutoExit(config, text, hooks.history);
|
|
365
|
+
if (autoExit) {
|
|
366
|
+
intentRuntime.mode = "chat";
|
|
367
|
+
intentRuntime.build = null;
|
|
368
|
+
intentRuntime.pending_exit = null;
|
|
369
|
+
intentRuntime.preferred_skill_id = autoExit.preferred_skill_id;
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
return forwardToBuilder(config, build, text, hooks);
|
|
373
|
+
}
|
|
374
|
+
function handleTestAction(build, intentRuntime) {
|
|
375
|
+
intentRuntime.mode = "chat";
|
|
376
|
+
intentRuntime.preferred_skill_id = build.skill_id;
|
|
377
|
+
intentRuntime.build = null;
|
|
378
|
+
intentRuntime.pending_exit = null;
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
async function handleRegisterAction(config, build, intentRuntime) {
|
|
382
|
+
try {
|
|
383
|
+
const lines = await buildRegisterLines(config, build.skill_id, {
|
|
384
|
+
overwrite: build.type === "edit",
|
|
385
|
+
});
|
|
386
|
+
intentRuntime.mode = "chat";
|
|
387
|
+
intentRuntime.build = null;
|
|
388
|
+
intentRuntime.pending_exit = null;
|
|
389
|
+
return lines;
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
393
|
+
return [
|
|
394
|
+
l(`Could not use this skill yet: ${message}`, `登録できませんでした: ${message}`),
|
|
395
|
+
l("After fixing it, ask to register/use it again.", "修正が終わりましたら、もう一度「登録して」とお伝えください。"),
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async function forwardToBuilder(config, build, text, hooks) {
|
|
400
|
+
const seed = build.context_consumed ? [] : build.context_seed;
|
|
401
|
+
build.context_consumed = true;
|
|
402
|
+
try {
|
|
403
|
+
const result = await buildDraftWithAgent(config, build.skill_id, text, {
|
|
404
|
+
handoff: seed,
|
|
405
|
+
onProgress: hooks.onProgress,
|
|
406
|
+
accessMode: "full",
|
|
407
|
+
eventSource: build.event_source,
|
|
408
|
+
});
|
|
409
|
+
const reply = result.summary?.trim();
|
|
410
|
+
return reply ? [reply] : [l("(no response)", "(応答なし)")];
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
414
|
+
return [l(`builder agent error: ${message}`, `builder agent エラー: ${message}`)];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type AppConfig } from "../core/config.js";
|
|
2
|
+
import { type SkillManifest } from "../core/skill-registry.js";
|
|
3
|
+
import { type AiProgressHandler } from "../core/ai-provider.js";
|
|
4
|
+
import { type ValidateSkillResult } from "../core/skill-scaffold.js";
|
|
5
|
+
export interface BuildSession {
|
|
6
|
+
skill_id: string;
|
|
7
|
+
workspace: string;
|
|
8
|
+
draft_dir: string;
|
|
9
|
+
logs_dir: string;
|
|
10
|
+
review_path: string;
|
|
11
|
+
session_path: string;
|
|
12
|
+
result_path: string;
|
|
13
|
+
}
|
|
14
|
+
export interface RegisteredBuildSkill {
|
|
15
|
+
skill_id: string;
|
|
16
|
+
source_dir: string;
|
|
17
|
+
target_dir: string;
|
|
18
|
+
}
|
|
19
|
+
export interface BuildMessage {
|
|
20
|
+
role: "user" | "assistant" | "system";
|
|
21
|
+
content: string;
|
|
22
|
+
ts: string;
|
|
23
|
+
}
|
|
24
|
+
export type BuilderAccessMode = "full" | "approval";
|
|
25
|
+
export interface BuildSessionState {
|
|
26
|
+
version: 1;
|
|
27
|
+
skill_id: string;
|
|
28
|
+
builder: string;
|
|
29
|
+
status: "drafting" | "testing" | "ready" | "failed" | "needs_config";
|
|
30
|
+
runtime: "python" | "typescript";
|
|
31
|
+
access_mode: BuilderAccessMode;
|
|
32
|
+
messages: BuildMessage[];
|
|
33
|
+
updated_at: string;
|
|
34
|
+
}
|
|
35
|
+
export interface BuildDraftResult {
|
|
36
|
+
session: BuildSession;
|
|
37
|
+
state: BuildSessionState;
|
|
38
|
+
files_written: string[];
|
|
39
|
+
summary: string;
|
|
40
|
+
model_id: string;
|
|
41
|
+
provider: string;
|
|
42
|
+
}
|
|
43
|
+
export interface BuilderHandoffTurn {
|
|
44
|
+
role: "user" | "assistant" | "tool";
|
|
45
|
+
content: string;
|
|
46
|
+
}
|
|
47
|
+
export type BuilderEventSource = "discord" | "telegram" | "cli";
|
|
48
|
+
export interface BuildTestResult {
|
|
49
|
+
session: BuildSession;
|
|
50
|
+
validation: ValidateSkillResult;
|
|
51
|
+
payload: Record<string, unknown>;
|
|
52
|
+
safe_to_register: boolean;
|
|
53
|
+
status: "ready" | "failed" | "needs_config";
|
|
54
|
+
summary: string;
|
|
55
|
+
/** Multi-line tester agent output (traceback / shell output etc.). */
|
|
56
|
+
details?: string;
|
|
57
|
+
missing_env?: Array<{
|
|
58
|
+
name: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
export type BuildReadinessStatus = "ready" | "failed" | "needs_config" | "incomplete";
|
|
63
|
+
export interface BuildReadinessResult {
|
|
64
|
+
session: BuildSession;
|
|
65
|
+
validation: ValidateSkillResult;
|
|
66
|
+
payload: Record<string, unknown>;
|
|
67
|
+
safe_to_register: boolean;
|
|
68
|
+
status: BuildReadinessStatus;
|
|
69
|
+
summary: string;
|
|
70
|
+
details?: string;
|
|
71
|
+
missing_env?: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
export declare function createBuildSession(config: AppConfig, skillId?: string, options?: {
|
|
77
|
+
runtime?: "python" | "typescript";
|
|
78
|
+
accessMode?: BuilderAccessMode;
|
|
79
|
+
/** Override the resolved session root. */
|
|
80
|
+
root?: string;
|
|
81
|
+
}): Promise<BuildSession>;
|
|
82
|
+
export declare function buildDraftWithAgent(config: AppConfig, skillId: string, userMessage: string, options?: {
|
|
83
|
+
runtime?: "python" | "typescript";
|
|
84
|
+
builder?: string;
|
|
85
|
+
handoff?: BuilderHandoffTurn[];
|
|
86
|
+
onProgress?: AiProgressHandler;
|
|
87
|
+
accessMode?: BuilderAccessMode;
|
|
88
|
+
eventSource?: BuilderEventSource;
|
|
89
|
+
}): Promise<BuildDraftResult>;
|
|
90
|
+
export declare function testBuildDraft(config: AppConfig, skillId: string, payload?: Record<string, unknown>): Promise<BuildTestResult>;
|
|
91
|
+
export declare function inspectBuildReadiness(config: AppConfig, skillId: string, payload?: Record<string, unknown>): Promise<BuildReadinessResult>;
|
|
92
|
+
export interface BuildDraftSummary {
|
|
93
|
+
skill_id: string;
|
|
94
|
+
status: BuildSessionState["status"];
|
|
95
|
+
runtime: BuildSessionState["runtime"];
|
|
96
|
+
builder: string;
|
|
97
|
+
message_count: number;
|
|
98
|
+
updated_at: string;
|
|
99
|
+
draft_dir: string;
|
|
100
|
+
has_skill_yaml: boolean;
|
|
101
|
+
safe_to_register: boolean;
|
|
102
|
+
summary?: string;
|
|
103
|
+
}
|
|
104
|
+
export declare function listBuildDrafts(config: AppConfig): Promise<BuildDraftSummary[]>;
|
|
105
|
+
export declare function readBuildStatus(config: AppConfig, skillId: string): Promise<{
|
|
106
|
+
session: BuildSession;
|
|
107
|
+
state: BuildSessionState;
|
|
108
|
+
result: Record<string, unknown> | null;
|
|
109
|
+
}>;
|
|
110
|
+
export declare function registerBuildSkill(config: AppConfig, skillId: string, _options?: {
|
|
111
|
+
overwrite?: boolean;
|
|
112
|
+
}): Promise<RegisteredBuildSkill>;
|
|
113
|
+
export declare function prepareEditDraft(config: AppConfig, skillId: string): Promise<BuildSession>;
|
|
114
|
+
export declare function prepareRepairDraft(config: AppConfig, manifest: SkillManifest): Promise<BuildSession>;
|
|
115
|
+
export declare function formatDiscordSlashGuidance(context: {
|
|
116
|
+
event_source?: BuilderEventSource;
|
|
117
|
+
} | undefined): string[];
|