agent-sin 0.1.12 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +2 -1
- package/builtin-skills/_shared/_todo_lib.py +290 -0
- package/builtin-skills/even-g2-setup/main.ts +896 -0
- package/builtin-skills/even-g2-setup/skill.yaml +133 -0
- package/builtin-skills/memo-delete/main.py +28 -107
- package/builtin-skills/memo-delete/skill.yaml +10 -21
- package/builtin-skills/memo-index/main.py +96 -64
- package/builtin-skills/memo-index/skill.yaml +4 -10
- package/builtin-skills/memo-list/main.py +126 -72
- package/builtin-skills/memo-list/skill.yaml +8 -14
- package/builtin-skills/memo-save/main.py +191 -25
- package/builtin-skills/memo-save/skill.yaml +29 -5
- package/builtin-skills/memo-search/main.py +38 -18
- package/builtin-skills/memo-vector-search/main.py +11 -6
- package/builtin-skills/nightly-topic-knowledge/_feedback_lib.py +391 -0
- package/builtin-skills/nightly-topic-knowledge/_topics_lib.py +415 -0
- package/builtin-skills/nightly-topic-knowledge/main.py +403 -0
- package/builtin-skills/nightly-topic-knowledge/skill.yaml +88 -0
- package/builtin-skills/schedule-add/main.py +26 -0
- package/builtin-skills/service-restart/main.ts +249 -0
- package/builtin-skills/service-restart/skill.yaml +49 -0
- package/builtin-skills/todo-add/main.py +3 -1
- package/builtin-skills/todo-delete/main.py +3 -1
- package/builtin-skills/todo-done/main.py +3 -1
- package/builtin-skills/todo-list/main.py +4 -1
- package/builtin-skills/todo-tick/main.py +3 -1
- package/builtin-skills/topic-knowledge-read/main.py +118 -0
- package/builtin-skills/topic-knowledge-read/skill.yaml +49 -0
- package/dist/builder/build-action-classifier.d.ts +18 -0
- package/dist/builder/build-action-classifier.js +82 -1
- package/dist/builder/build-flow.d.ts +33 -4
- package/dist/builder/build-flow.js +251 -89
- package/dist/builder/builder-session.d.ts +1 -1
- package/dist/builder/builder-session.js +112 -7
- package/dist/builder/conversation-router.d.ts +4 -2
- package/dist/builder/conversation-router.js +19 -2
- package/dist/cli/index.js +323 -20
- package/dist/core/ai-provider.d.ts +1 -0
- package/dist/core/ai-provider.js +8 -3
- package/dist/core/chat-engine.d.ts +9 -3
- package/dist/core/chat-engine.js +1263 -146
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +82 -0
- package/dist/core/daily-memory-promotion.d.ts +7 -0
- package/dist/core/daily-memory-promotion.js +568 -14
- package/dist/core/image-attachments.d.ts +31 -0
- package/dist/core/image-attachments.js +237 -0
- package/dist/core/logger.d.ts +2 -1
- package/dist/core/logger.js +77 -1
- package/dist/core/memo-migration.d.ts +3 -0
- package/dist/core/memo-migration.js +422 -0
- package/dist/core/native-modules.d.ts +24 -0
- package/dist/core/native-modules.js +99 -0
- package/dist/core/notifier.d.ts +8 -3
- package/dist/core/notifier.js +191 -17
- package/dist/core/obsidian-vault.d.ts +19 -0
- package/dist/core/obsidian-vault.js +477 -0
- package/dist/core/operating-model.d.ts +2 -0
- package/dist/core/operating-model.js +15 -0
- package/dist/core/output-writer.d.ts +3 -2
- package/dist/core/output-writer.js +108 -7
- package/dist/core/profile-memory.js +22 -1
- package/dist/core/runtime.d.ts +2 -0
- package/dist/core/runtime.js +9 -1
- package/dist/core/secrets.d.ts +4 -0
- package/dist/core/secrets.js +34 -0
- package/dist/core/skill-history.d.ts +44 -0
- package/dist/core/skill-history.js +329 -0
- package/dist/core/skill-registry.d.ts +5 -0
- package/dist/core/skill-registry.js +11 -0
- package/dist/discord/bot.d.ts +1 -0
- package/dist/discord/bot.js +181 -10
- package/dist/even-g2/gateway.d.ts +15 -0
- package/dist/even-g2/gateway.js +868 -0
- package/dist/runtimes/codex-app-server.d.ts +5 -1
- package/dist/runtimes/codex-app-server.js +147 -8
- package/dist/runtimes/python-runner.js +82 -0
- package/dist/runtimes/typescript-runner.js +13 -1
- package/dist/skills-sdk/types.d.ts +19 -4
- package/dist/telegram/bot.d.ts +1 -0
- package/dist/telegram/bot.js +115 -7
- package/package.json +3 -1
- package/templates/even-g2-agent/README.md +83 -0
- package/templates/even-g2-agent/app.json +20 -0
- package/templates/even-g2-agent/index.html +31 -0
- package/templates/even-g2-agent/package-lock.json +1836 -0
- package/templates/even-g2-agent/package.json +22 -0
- package/templates/even-g2-agent/scripts/qr-auto.mjs +182 -0
- package/templates/even-g2-agent/src/embedded-config.ts +4 -0
- package/templates/even-g2-agent/src/main.ts +539 -0
- package/templates/even-g2-agent/src/style.css +70 -0
- package/templates/even-g2-agent/tsconfig.json +11 -0
- package/templates/skill-python/main.py +20 -2
- package/templates/skill-python/skill.yaml +9 -0
- package/templates/skill-typescript/main.ts +40 -5
- package/templates/skill-typescript/skill.yaml +9 -0
|
@@ -30,6 +30,10 @@ export interface CodexAppServerOptions {
|
|
|
30
30
|
turnTimeoutMs?: number;
|
|
31
31
|
onStderr?: (chunk: string) => void;
|
|
32
32
|
}
|
|
33
|
+
export interface CodexTurnResult {
|
|
34
|
+
text: string;
|
|
35
|
+
generatedImagePaths: string[];
|
|
36
|
+
}
|
|
33
37
|
export declare function getSharedCodexAppServer(model?: string): CodexAppServerSession;
|
|
34
38
|
export declare function shutdownSharedCodexAppServer(): Promise<void>;
|
|
35
39
|
export declare class CodexAppServerSession {
|
|
@@ -42,7 +46,7 @@ export declare class CodexAppServerSession {
|
|
|
42
46
|
private readonly options;
|
|
43
47
|
private exitReason;
|
|
44
48
|
constructor(options?: CodexAppServerOptions);
|
|
45
|
-
sendTurn(text: string, options?: CodexTurnOptions): Promise<
|
|
49
|
+
sendTurn(text: string, options?: CodexTurnOptions): Promise<CodexTurnResult>;
|
|
46
50
|
stop(): Promise<void>;
|
|
47
51
|
isRunning(): boolean;
|
|
48
52
|
private ensureStarted;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { detectImageMimeTypeFromBuffer, imageExtensionForMimeType, } from "../core/image-attachments.js";
|
|
2
7
|
import { l } from "../core/i18n.js";
|
|
3
8
|
const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
|
|
4
9
|
const DEFAULT_TURN_TIMEOUT_MS = 5 * 60_000;
|
|
10
|
+
const MAX_GENERATED_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
5
11
|
function snippet(value) {
|
|
6
12
|
if (typeof value !== "string") {
|
|
7
13
|
return undefined;
|
|
@@ -40,8 +46,8 @@ export class CodexAppServerSession {
|
|
|
40
46
|
constructor(options = {}) {
|
|
41
47
|
const baseArgs = options.args ? [...options.args] : ["app-server"];
|
|
42
48
|
const model = options.model || process.env.AGENT_SIN_CODEX_MODEL;
|
|
43
|
-
if (model && !baseArgs
|
|
44
|
-
baseArgs.push("
|
|
49
|
+
if (model && !hasConfigOverride(baseArgs, "model")) {
|
|
50
|
+
baseArgs.push("-c", `model=${JSON.stringify(model)}`);
|
|
45
51
|
}
|
|
46
52
|
this.options = {
|
|
47
53
|
bin: options.bin || process.env.AGENT_SIN_CODEX_BIN || "codex",
|
|
@@ -211,6 +217,9 @@ export class CodexAppServerSession {
|
|
|
211
217
|
return new Promise((resolve, reject) => {
|
|
212
218
|
let assistantText = "";
|
|
213
219
|
let settled = false;
|
|
220
|
+
const generatedImagePaths = [];
|
|
221
|
+
const generatedImageKeys = new Set();
|
|
222
|
+
const generatedImageTasks = [];
|
|
214
223
|
const emit = (event) => {
|
|
215
224
|
if (!options.onProgress) {
|
|
216
225
|
return;
|
|
@@ -259,6 +268,17 @@ export class CodexAppServerSession {
|
|
|
259
268
|
else if (item && (item.type === "command" || item.type === "tool_call")) {
|
|
260
269
|
emit({ kind: "tool", name: item.name || item.type, text: l("done", "完了") });
|
|
261
270
|
}
|
|
271
|
+
else if (item && item.type === "imageGeneration") {
|
|
272
|
+
generatedImageTasks.push(captureGeneratedImage(item, options.cwd, generatedImagePaths, generatedImageKeys));
|
|
273
|
+
emit({ kind: "tool", name: "image_generation", text: l("done", "完了") });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else if (method === "rawResponseItem/completed") {
|
|
277
|
+
const item = params.item;
|
|
278
|
+
if (item && item.type === "image_generation_call") {
|
|
279
|
+
generatedImageTasks.push(captureGeneratedImage(item, options.cwd, generatedImagePaths, generatedImageKeys));
|
|
280
|
+
emit({ kind: "tool", name: "image_generation", text: l("done", "完了") });
|
|
281
|
+
}
|
|
262
282
|
}
|
|
263
283
|
else if (method === "turn/completed") {
|
|
264
284
|
const turn = params.turn;
|
|
@@ -270,6 +290,11 @@ export class CodexAppServerSession {
|
|
|
270
290
|
if (fromTurn) {
|
|
271
291
|
assistantText = fromTurn;
|
|
272
292
|
}
|
|
293
|
+
for (const entry of turn.items) {
|
|
294
|
+
if (entry && entry.type === "imageGeneration") {
|
|
295
|
+
generatedImageTasks.push(captureGeneratedImage(entry, options.cwd, generatedImagePaths, generatedImageKeys));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
273
298
|
}
|
|
274
299
|
finish(true, undefined);
|
|
275
300
|
}
|
|
@@ -285,12 +310,23 @@ export class CodexAppServerSession {
|
|
|
285
310
|
settled = true;
|
|
286
311
|
clearTimeout(timeout);
|
|
287
312
|
this.notificationHandlers.delete(handler);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
313
|
+
Promise.all(generatedImageTasks)
|
|
314
|
+
.then(() => {
|
|
315
|
+
if (ok) {
|
|
316
|
+
resolve({ text: assistantText, generatedImagePaths });
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
reject(error || new Error("codex app-server: unknown failure"));
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
.catch((imageError) => {
|
|
323
|
+
if (ok) {
|
|
324
|
+
resolve({ text: assistantText, generatedImagePaths });
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
reject(error || imageError);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
294
330
|
};
|
|
295
331
|
const timeout = setTimeout(() => {
|
|
296
332
|
finish(false, new Error(`codex app-server: turn timed out after ${this.options.turnTimeoutMs}ms`));
|
|
@@ -306,3 +342,106 @@ export class CodexAppServerSession {
|
|
|
306
342
|
});
|
|
307
343
|
}
|
|
308
344
|
}
|
|
345
|
+
function hasConfigOverride(args, key) {
|
|
346
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
347
|
+
const arg = args[index];
|
|
348
|
+
if ((arg === "-c" || arg === "--config") && args[index + 1]?.startsWith(`${key}=`)) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
if (arg.startsWith("-c") && arg.slice(2).trimStart().startsWith(`${key}=`)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
if (arg.startsWith("--config=") && arg.slice("--config=".length).startsWith(`${key}=`)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
async function captureGeneratedImage(item, cwd, paths, keys) {
|
|
361
|
+
if (!item || typeof item !== "object")
|
|
362
|
+
return;
|
|
363
|
+
const record = item;
|
|
364
|
+
const id = typeof record.id === "string" ? record.id : undefined;
|
|
365
|
+
const savedPath = typeof record.savedPath === "string" ? record.savedPath : undefined;
|
|
366
|
+
const result = typeof record.result === "string" ? record.result : undefined;
|
|
367
|
+
const key = id || (result ? imageResultKey(result) : savedPath);
|
|
368
|
+
if (key && keys.has(key))
|
|
369
|
+
return;
|
|
370
|
+
if (key)
|
|
371
|
+
keys.add(key);
|
|
372
|
+
if (savedPath) {
|
|
373
|
+
const usable = await existingImagePath(savedPath);
|
|
374
|
+
if (usable && !paths.includes(usable)) {
|
|
375
|
+
paths.push(usable);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (!result)
|
|
380
|
+
return;
|
|
381
|
+
const resultPath = await persistImageGenerationResult(result, cwd, id);
|
|
382
|
+
if (resultPath && !paths.includes(resultPath)) {
|
|
383
|
+
paths.push(resultPath);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function existingImagePath(filePath) {
|
|
387
|
+
try {
|
|
388
|
+
const info = await stat(filePath);
|
|
389
|
+
return info.isFile() && info.size > 0 ? filePath : null;
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function persistImageGenerationResult(value, cwd, id) {
|
|
396
|
+
const parsed = parseImageGenerationResult(value);
|
|
397
|
+
if (!parsed || parsed.buffer.length === 0 || parsed.buffer.length > MAX_GENERATED_IMAGE_BYTES) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
const mimeType = parsed.mimeType || detectImageMimeTypeFromBuffer(parsed.buffer);
|
|
401
|
+
const ext = imageExtensionForMimeType(mimeType);
|
|
402
|
+
const safeId = (id || "image").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 40) || "image";
|
|
403
|
+
const hash = crypto.createHash("sha256").update(parsed.buffer).digest("hex").slice(0, 10);
|
|
404
|
+
const filename = `${new Date().toISOString().replace(/[:.]/g, "-")}-${safeId}-${hash}${ext}`;
|
|
405
|
+
for (const dir of candidateImageDirs(cwd)) {
|
|
406
|
+
try {
|
|
407
|
+
await mkdir(dir, { recursive: true });
|
|
408
|
+
const filePath = path.join(dir, filename);
|
|
409
|
+
await writeFile(filePath, parsed.buffer);
|
|
410
|
+
return filePath;
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Try the fallback directory.
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
function parseImageGenerationResult(value) {
|
|
419
|
+
const trimmed = value.trim();
|
|
420
|
+
const dataUrl = trimmed.match(/^data:(image\/[a-z0-9.+-]+);base64,([\s\S]+)$/i);
|
|
421
|
+
const mimeType = dataUrl?.[1];
|
|
422
|
+
const rawBase64 = dataUrl ? dataUrl[2] : trimmed;
|
|
423
|
+
if (rawBase64.length === 0 || rawBase64.length > Math.ceil(MAX_GENERATED_IMAGE_BYTES * 1.4)) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
if (!/^[A-Za-z0-9+/=\s_-]+$/.test(rawBase64)) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const normalized = rawBase64.replace(/[\s_-]/g, (char) => (char === "-" ? "+" : char === "_" ? "/" : ""));
|
|
430
|
+
try {
|
|
431
|
+
return { buffer: Buffer.from(normalized, "base64"), mimeType };
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function candidateImageDirs(cwd) {
|
|
438
|
+
const dirs = [];
|
|
439
|
+
if (cwd) {
|
|
440
|
+
dirs.push(path.join(cwd, "codex-images"));
|
|
441
|
+
}
|
|
442
|
+
dirs.push(path.join(os.tmpdir(), "agent-sin-codex-images"));
|
|
443
|
+
return dirs;
|
|
444
|
+
}
|
|
445
|
+
function imageResultKey(value) {
|
|
446
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
447
|
+
}
|
|
@@ -4,10 +4,13 @@ import path from "node:path";
|
|
|
4
4
|
import { resolveSkillEntryPath } from "../core/skill-registry.js";
|
|
5
5
|
import { getAiProvider } from "../core/ai-provider.js";
|
|
6
6
|
import { notify as runNotify } from "../core/notifier.js";
|
|
7
|
+
import { appendSkillHistory, listSkillHistory, readSkillHistoryRaw, } from "../core/skill-history.js";
|
|
7
8
|
const AI_REQUEST_PREFIX = "AGENT_SIN_AI_REQUEST::";
|
|
8
9
|
const AI_RESPONSE_PREFIX = "AGENT_SIN_AI_RESPONSE::";
|
|
9
10
|
const NOTIFY_REQUEST_PREFIX = "AGENT_SIN_NOTIFY_REQUEST::";
|
|
10
11
|
const NOTIFY_RESPONSE_PREFIX = "AGENT_SIN_NOTIFY_RESPONSE::";
|
|
12
|
+
const HISTORY_REQUEST_PREFIX = "AGENT_SIN_HISTORY_REQUEST::";
|
|
13
|
+
const HISTORY_RESPONSE_PREFIX = "AGENT_SIN_HISTORY_RESPONSE::";
|
|
11
14
|
const CTX_LOG_PATTERN = /^\[(info|warn|error)\]\s+([\s\S]*)$/;
|
|
12
15
|
export function candidatePythonInterpreters(config, platform = process.platform) {
|
|
13
16
|
const venvDir = path.join(config.workspace, ".venv");
|
|
@@ -83,6 +86,13 @@ export async function runPythonSkill(config, manifest, input) {
|
|
|
83
86
|
});
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
89
|
+
if (line.startsWith(HISTORY_REQUEST_PREFIX)) {
|
|
90
|
+
const payloadJson = line.slice(HISTORY_REQUEST_PREFIX.length);
|
|
91
|
+
handleHistoryRequest(payloadJson).catch((error) => {
|
|
92
|
+
stderrLines.push(`[history-error] ${error instanceof Error ? error.message : String(error)}`);
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
86
96
|
const ctxMatch = line.match(CTX_LOG_PATTERN);
|
|
87
97
|
if (ctxMatch) {
|
|
88
98
|
ctxLogs.push({ level: ctxMatch[1], message: ctxMatch[2] });
|
|
@@ -103,6 +113,7 @@ export async function runPythonSkill(config, manifest, input) {
|
|
|
103
113
|
const response = await getAiProvider()(config, {
|
|
104
114
|
model_id: request.model_id,
|
|
105
115
|
messages: request.messages,
|
|
116
|
+
cwd: config.workspace,
|
|
106
117
|
});
|
|
107
118
|
child.stdin.write(`${AI_RESPONSE_PREFIX}${JSON.stringify({ id: request.id, ok: true, response })}\n`);
|
|
108
119
|
}
|
|
@@ -114,6 +125,40 @@ export async function runPythonSkill(config, manifest, input) {
|
|
|
114
125
|
})}\n`);
|
|
115
126
|
}
|
|
116
127
|
}
|
|
128
|
+
async function handleHistoryRequest(payloadJson) {
|
|
129
|
+
let request;
|
|
130
|
+
try {
|
|
131
|
+
request = JSON.parse(payloadJson);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
child.stdin.write(`${HISTORY_RESPONSE_PREFIX}${JSON.stringify({ id: "?", ok: false, error: `Invalid history request JSON: ${String(error)}` })}\n`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const payload = request.payload || {};
|
|
139
|
+
let data = null;
|
|
140
|
+
if (request.op === "append") {
|
|
141
|
+
data = await appendSkillHistory(config, manifest, payload);
|
|
142
|
+
}
|
|
143
|
+
else if (request.op === "list") {
|
|
144
|
+
data = await listSkillHistory(config, manifest, payload);
|
|
145
|
+
}
|
|
146
|
+
else if (request.op === "read") {
|
|
147
|
+
data = await readSkillHistoryRaw(config, manifest, payload);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
throw new Error(`Unknown history op: ${request.op}`);
|
|
151
|
+
}
|
|
152
|
+
child.stdin.write(`${HISTORY_RESPONSE_PREFIX}${JSON.stringify({ id: request.id, ok: true, data })}\n`);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
child.stdin.write(`${HISTORY_RESPONSE_PREFIX}${JSON.stringify({
|
|
156
|
+
id: request.id,
|
|
157
|
+
ok: false,
|
|
158
|
+
error: error instanceof Error ? error.message : String(error),
|
|
159
|
+
})}\n`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
117
162
|
async function handleNotifyRequest(payloadJson) {
|
|
118
163
|
let request;
|
|
119
164
|
try {
|
|
@@ -134,6 +179,15 @@ export async function runPythonSkill(config, manifest, input) {
|
|
|
134
179
|
to: typeof args.to === "string" ? args.to : undefined,
|
|
135
180
|
discordThreadId: typeof args.discordThreadId === "string" ? args.discordThreadId : undefined,
|
|
136
181
|
telegramThreadId: typeof args.telegramThreadId === "string" ? args.telegramThreadId : undefined,
|
|
182
|
+
filePath: typeof args.filePath === "string" ? args.filePath : undefined,
|
|
183
|
+
filePaths: Array.isArray(args.filePaths)
|
|
184
|
+
? args.filePaths.filter((item) => typeof item === "string")
|
|
185
|
+
: undefined,
|
|
186
|
+
imagePath: typeof args.imagePath === "string" ? args.imagePath : undefined,
|
|
187
|
+
imagePaths: Array.isArray(args.imagePaths)
|
|
188
|
+
? args.imagePaths.filter((item) => typeof item === "string")
|
|
189
|
+
: undefined,
|
|
190
|
+
cwd: config.workspace,
|
|
137
191
|
});
|
|
138
192
|
child.stdin.write(`${NOTIFY_RESPONSE_PREFIX}${JSON.stringify({ id: request.id, ok: result.ok, channel: result.channel, detail: result.detail })}\n`);
|
|
139
193
|
}
|
|
@@ -238,6 +292,7 @@ class AI:
|
|
|
238
292
|
"model_id": response.get("model_id"),
|
|
239
293
|
"provider": response.get("provider"),
|
|
240
294
|
"text": response.get("text", ""),
|
|
295
|
+
"generated_images": response.get("generated_images") or [],
|
|
241
296
|
}
|
|
242
297
|
|
|
243
298
|
class Memory:
|
|
@@ -275,11 +330,38 @@ async def notify_call(args):
|
|
|
275
330
|
"detail": envelope.get("detail"),
|
|
276
331
|
}
|
|
277
332
|
|
|
333
|
+
async def history_call(op, payload):
|
|
334
|
+
request_id = str(uuid.uuid4())
|
|
335
|
+
request = {"id": request_id, "op": op, "payload": payload or {}}
|
|
336
|
+
print("AGENT_SIN_HISTORY_REQUEST::" + json.dumps(request, ensure_ascii=False, default=str), file=sys.stderr, flush=True)
|
|
337
|
+
line = sys.stdin.readline()
|
|
338
|
+
if not line:
|
|
339
|
+
raise RuntimeError("History channel closed")
|
|
340
|
+
marker = "AGENT_SIN_HISTORY_RESPONSE::"
|
|
341
|
+
idx = line.find(marker)
|
|
342
|
+
if idx < 0:
|
|
343
|
+
raise RuntimeError(f"Unexpected history response: {line.strip()}")
|
|
344
|
+
envelope = json.loads(line[idx + len(marker):])
|
|
345
|
+
if not envelope.get("ok"):
|
|
346
|
+
raise RuntimeError(envelope.get("error") or "History error")
|
|
347
|
+
return envelope.get("data")
|
|
348
|
+
|
|
349
|
+
class History:
|
|
350
|
+
async def append(self, args):
|
|
351
|
+
if not isinstance(args, dict):
|
|
352
|
+
raise RuntimeError("ctx.history.append requires a dict argument")
|
|
353
|
+
return await history_call("append", args)
|
|
354
|
+
async def list(self, options=None):
|
|
355
|
+
return await history_call("list", options or {})
|
|
356
|
+
async def read(self, options=None):
|
|
357
|
+
return await history_call("read", options or {})
|
|
358
|
+
|
|
278
359
|
class Ctx:
|
|
279
360
|
def __init__(self):
|
|
280
361
|
self.log = Log()
|
|
281
362
|
self.ai = AI()
|
|
282
363
|
self.memory = Memory()
|
|
364
|
+
self.history = History()
|
|
283
365
|
def now(self):
|
|
284
366
|
return datetime.now(timezone.utc).isoformat()
|
|
285
367
|
async def notify(self, args):
|
|
@@ -7,6 +7,7 @@ import ts from "typescript";
|
|
|
7
7
|
import { resolveSkillEntryPath } from "../core/skill-registry.js";
|
|
8
8
|
import { getAiProvider } from "../core/ai-provider.js";
|
|
9
9
|
import { notify as runNotify } from "../core/notifier.js";
|
|
10
|
+
import { appendSkillHistory, listSkillHistory, readSkillHistoryRaw, } from "../core/skill-history.js";
|
|
10
11
|
export async function runTypeScriptSkill(config, manifest, input) {
|
|
11
12
|
const entry = await resolveSkillEntryPath(manifest);
|
|
12
13
|
const skillDir = await realpath(manifest.dir);
|
|
@@ -113,13 +114,14 @@ function createContext(config, manifest, initialMemory) {
|
|
|
113
114
|
{ role: "system", content: `You are an AI step "${step.id}" of an Agent-Sin skill. Purpose: ${step.purpose}` },
|
|
114
115
|
{ role: "user", content: typeof payload === "string" ? payload : JSON.stringify(payload) },
|
|
115
116
|
];
|
|
116
|
-
const response = await getAiProvider()(config, { model_id: modelId, messages });
|
|
117
|
+
const response = await getAiProvider()(config, { model_id: modelId, messages, cwd: config.workspace });
|
|
117
118
|
return {
|
|
118
119
|
status: "ok",
|
|
119
120
|
step_id: stepId,
|
|
120
121
|
model_id: modelId,
|
|
121
122
|
provider: response.provider,
|
|
122
123
|
text: response.text,
|
|
124
|
+
generated_images: response.generated_images || [],
|
|
123
125
|
};
|
|
124
126
|
}
|
|
125
127
|
catch (error) {
|
|
@@ -145,6 +147,11 @@ function createContext(config, manifest, initialMemory) {
|
|
|
145
147
|
to: args.to,
|
|
146
148
|
discordThreadId: args.discordThreadId,
|
|
147
149
|
telegramThreadId: args.telegramThreadId,
|
|
150
|
+
filePath: args.filePath,
|
|
151
|
+
filePaths: args.filePaths,
|
|
152
|
+
imagePath: args.imagePath,
|
|
153
|
+
imagePaths: args.imagePaths,
|
|
154
|
+
cwd: config.workspace,
|
|
148
155
|
});
|
|
149
156
|
return { ok: result.ok, channel: result.channel, detail: result.detail };
|
|
150
157
|
},
|
|
@@ -164,6 +171,11 @@ function createContext(config, manifest, initialMemory) {
|
|
|
164
171
|
return true;
|
|
165
172
|
},
|
|
166
173
|
},
|
|
174
|
+
history: {
|
|
175
|
+
append: async (args) => appendSkillHistory(config, manifest, args),
|
|
176
|
+
list: async (options = {}) => listSkillHistory(config, manifest, options),
|
|
177
|
+
read: async (options = {}) => readSkillHistoryRaw(config, manifest, options),
|
|
178
|
+
},
|
|
167
179
|
now: () => new Date().toISOString(),
|
|
168
180
|
},
|
|
169
181
|
memoryUpdates,
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export interface SkillInputSources {
|
|
2
|
+
workspace?: string;
|
|
3
|
+
notes_dir?: string;
|
|
4
|
+
memory_dir?: string;
|
|
5
|
+
index_dir?: string;
|
|
6
|
+
logs_dir?: string;
|
|
7
|
+
skill_output_dir?: string;
|
|
8
|
+
skillOutputDir?: string;
|
|
9
|
+
locale?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
1
12
|
export interface SkillInput {
|
|
2
13
|
args: Record<string, unknown>;
|
|
3
14
|
trigger: {
|
|
@@ -5,19 +16,23 @@ export interface SkillInput {
|
|
|
5
16
|
id: string;
|
|
6
17
|
time: string;
|
|
7
18
|
};
|
|
8
|
-
sources:
|
|
19
|
+
sources: SkillInputSources;
|
|
9
20
|
memory: Record<string, unknown>;
|
|
10
21
|
}
|
|
11
|
-
export type NotifyChannel = "auto" | "macos" | "discord" | "telegram" | "slack" | "mail" | "stderr";
|
|
22
|
+
export type NotifyChannel = "auto" | "macos" | "discord" | "telegram" | "slack" | "mail" | "g2" | "stderr";
|
|
12
23
|
export interface NotifyArgs {
|
|
13
|
-
title
|
|
14
|
-
body
|
|
24
|
+
title?: string;
|
|
25
|
+
body?: string;
|
|
15
26
|
subtitle?: string;
|
|
16
27
|
sound?: boolean;
|
|
17
28
|
channel?: NotifyChannel;
|
|
18
29
|
to?: string;
|
|
19
30
|
discordThreadId?: string;
|
|
20
31
|
telegramThreadId?: string;
|
|
32
|
+
filePath?: string;
|
|
33
|
+
filePaths?: string[];
|
|
34
|
+
imagePath?: string;
|
|
35
|
+
imagePaths?: string[];
|
|
21
36
|
}
|
|
22
37
|
export interface NotifyOutcome {
|
|
23
38
|
ok: boolean;
|
package/dist/telegram/bot.d.ts
CHANGED
|
@@ -90,5 +90,6 @@ export declare function shouldUseTelegramDraftStream(message: TelegramMessage):
|
|
|
90
90
|
export declare function telegramSendPayload(chatId: string, content: string, options?: TelegramSendOptions): Record<string, unknown>;
|
|
91
91
|
export declare function telegramDraftPayload(message: TelegramMessage, draftId: number, text: string): Record<string, unknown>;
|
|
92
92
|
export declare function formatTelegramDraftProgress(event: AiProgressEvent): string | null;
|
|
93
|
+
export declare function formatTelegramChatDraftProgress(event: AiProgressEvent): string | null;
|
|
93
94
|
export declare function loadTelegramHistories(filePath: string): Promise<Map<string, ChatTurn[]>>;
|
|
94
95
|
export declare function loadTelegramIntentRuntimes(filePath: string): Promise<Map<string, IntentRuntime>>;
|
package/dist/telegram/bot.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createIntentRuntime, renderBuildFooter, shouldShowBuildFooter, } from "
|
|
|
7
7
|
import { routeConversationMessage, } from "../builder/conversation-router.js";
|
|
8
8
|
import { cleanProgressText, formatBuildProgress, progressIntervalMs } from "../builder/progress-format.js";
|
|
9
9
|
import { chunkText, cleanAttachmentText, formatAttachmentLabel, formatBytes, guessImageMimeType, indentAttachmentContent, isImageLikeFile, isTextLikeFile, } from "../core/message-utils.js";
|
|
10
|
+
import { collectLocalFileAttachments, collectLocalImageAttachments, } from "../core/image-attachments.js";
|
|
10
11
|
import { isEmptyIntentRuntime, loadIntentRuntimeMap, saveIntentRuntimeMap, } from "../builder/intent-runtime-store.js";
|
|
11
12
|
import { inferLocaleFromText, l, lLines, withLocale } from "../core/i18n.js";
|
|
12
13
|
import { consumeUpdateBanner, scheduleUpdateCheck } from "../core/update-notifier.js";
|
|
@@ -351,20 +352,20 @@ async function handleTelegramMessage(state, message) {
|
|
|
351
352
|
draft.update(l("Thinking", "考えています"), { force: true });
|
|
352
353
|
const prevMode = intentRuntime.mode;
|
|
353
354
|
try {
|
|
354
|
-
const
|
|
355
|
+
const routed = await routeTelegramMessage(state, userText, history, intentRuntime, chatKey, chatId, threadId, message.message_id, draft, userMessage.images);
|
|
355
356
|
typing.stop();
|
|
356
357
|
trimHistory(history);
|
|
357
358
|
void saveTelegramHistories(state);
|
|
358
359
|
void saveTelegramIntentRuntimes(state);
|
|
359
360
|
const isBuildEntry = prevMode !== "build" && intentRuntime.mode === "build";
|
|
360
|
-
const decorated = withTelegramModeBadge(intentRuntime, lines, { userText, isBuildEntry });
|
|
361
|
+
const decorated = withTelegramModeBadge(intentRuntime, routed.lines, { userText, isBuildEntry });
|
|
361
362
|
scheduleUpdateCheck(state.config.workspace);
|
|
362
363
|
const banner = await consumeUpdateBanner(state.config.workspace);
|
|
363
364
|
const finalLines = banner ? [banner, "", ...decorated] : decorated;
|
|
364
365
|
const reply = finalLines.filter((line) => line !== undefined && line !== null).join("\n").trim();
|
|
365
366
|
draft.update(l("Sending reply", "応答を送信しています"), { force: true });
|
|
366
367
|
await draft.finish();
|
|
367
|
-
await
|
|
368
|
+
await sendTelegramMessageWithLocalAttachments(state, chatId, reply || l("(no response)", "(応答なし)"), sendOptions, routed.localAttachmentPaths);
|
|
368
369
|
}
|
|
369
370
|
catch (error) {
|
|
370
371
|
typing.stop();
|
|
@@ -398,7 +399,8 @@ async function refreshTelegramStateConfig(state) {
|
|
|
398
399
|
}
|
|
399
400
|
}
|
|
400
401
|
async function routeTelegramMessage(state, text, history, intentRuntime, chatKey, chatId, threadId, replyToMessageId, draft, images = []) {
|
|
401
|
-
|
|
402
|
+
const localAttachmentPaths = [];
|
|
403
|
+
const lines = await routeConversationMessage({
|
|
402
404
|
config: state.config,
|
|
403
405
|
text,
|
|
404
406
|
history,
|
|
@@ -408,7 +410,11 @@ async function routeTelegramMessage(state, text, history, intentRuntime, chatKey
|
|
|
408
410
|
createBuildProgress: () => createTelegramBuildProgressReporter(state, chatKey, chatId, threadId, replyToMessageId, draft),
|
|
409
411
|
onChatProgress: (event) => draft.onChatProgress(event),
|
|
410
412
|
onAiProgress: (event) => draft.onAiProgress(event),
|
|
413
|
+
onLocalAttachments: (paths) => {
|
|
414
|
+
localAttachmentPaths.push(...paths);
|
|
415
|
+
},
|
|
411
416
|
});
|
|
417
|
+
return { lines, localAttachmentPaths };
|
|
412
418
|
}
|
|
413
419
|
function createTelegramBuildProgressReporter(state, chatKey, chatId, threadId, replyToMessageId, draft) {
|
|
414
420
|
const minIntervalMs = telegramProgressIntervalMs();
|
|
@@ -712,7 +718,7 @@ async function persistTelegramAttachmentBuffer(state, attachment, buffer, mimeTy
|
|
|
712
718
|
const HH = String(now.getHours()).padStart(2, "0");
|
|
713
719
|
const mm = String(now.getMinutes()).padStart(2, "0");
|
|
714
720
|
const ss = String(now.getSeconds()).padStart(2, "0");
|
|
715
|
-
const dir = path.join(state.config.
|
|
721
|
+
const dir = path.join(state.config.memory_dir, "attachments", yyyy, MM);
|
|
716
722
|
await mkdir(dir, { recursive: true });
|
|
717
723
|
const ext = pickAttachmentExtension(attachment.filename, mimeType);
|
|
718
724
|
const random = Math.random().toString(36).slice(2, 8);
|
|
@@ -903,6 +909,12 @@ export function formatTelegramDraftProgress(event) {
|
|
|
903
909
|
return null;
|
|
904
910
|
}
|
|
905
911
|
}
|
|
912
|
+
export function formatTelegramChatDraftProgress(event) {
|
|
913
|
+
if (event.kind === "message") {
|
|
914
|
+
return l("Preparing reply", "応答を整理しています");
|
|
915
|
+
}
|
|
916
|
+
return formatTelegramDraftProgress(event);
|
|
917
|
+
}
|
|
906
918
|
function createTelegramDraftStreamer(state, message) {
|
|
907
919
|
if (!shouldUseTelegramDraftStream(message)) {
|
|
908
920
|
return noopTelegramDraftStreamer();
|
|
@@ -969,9 +981,9 @@ function createTelegramDraftStreamer(state, message) {
|
|
|
969
981
|
}
|
|
970
982
|
},
|
|
971
983
|
onAiProgress(event) {
|
|
972
|
-
const text =
|
|
984
|
+
const text = formatTelegramChatDraftProgress(event);
|
|
973
985
|
if (text)
|
|
974
|
-
enqueue(text
|
|
986
|
+
enqueue(text);
|
|
975
987
|
},
|
|
976
988
|
onBuildProgress(event) {
|
|
977
989
|
const text = formatTelegramDraftProgress(event);
|
|
@@ -1071,6 +1083,60 @@ async function sendTelegramMessage(state, chatId, content, options = {}) {
|
|
|
1071
1083
|
}
|
|
1072
1084
|
}
|
|
1073
1085
|
}
|
|
1086
|
+
async function sendTelegramMessageWithLocalAttachments(state, chatId, content, options = {}, localAttachmentPaths = []) {
|
|
1087
|
+
const sanitized = stripInternalControlBlocks(content);
|
|
1088
|
+
const explicitAttachments = await collectLocalFileAttachments({
|
|
1089
|
+
paths: localAttachmentPaths,
|
|
1090
|
+
cwd: state.config.workspace,
|
|
1091
|
+
allowedRoots: [state.config.workspace, state.config.notes_dir],
|
|
1092
|
+
});
|
|
1093
|
+
const inlineImages = await collectLocalImageAttachments({
|
|
1094
|
+
text: sanitized,
|
|
1095
|
+
cwd: state.config.workspace,
|
|
1096
|
+
allowedRoots: [state.config.workspace, state.config.notes_dir],
|
|
1097
|
+
});
|
|
1098
|
+
const attachments = dedupeLocalAttachments([...explicitAttachments, ...inlineImages]);
|
|
1099
|
+
if (sanitized.trim()) {
|
|
1100
|
+
await sendTelegramMessage(state, chatId, sanitized, options);
|
|
1101
|
+
}
|
|
1102
|
+
for (let index = 0; index < attachments.length; index += 1) {
|
|
1103
|
+
await sendTelegramFileAttachment(state, chatId, attachments[index], index === 0 && !sanitized.trim() ? options : { threadId: options.threadId });
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
async function sendTelegramFileAttachment(state, chatId, attachment, options = {}) {
|
|
1107
|
+
const method = shouldSendTelegramAsPhoto(attachment) ? "sendPhoto" : "sendDocument";
|
|
1108
|
+
const fileField = method === "sendPhoto" ? "photo" : "document";
|
|
1109
|
+
const payload = telegramFilePayload(chatId, options);
|
|
1110
|
+
try {
|
|
1111
|
+
const buffer = await readFile(attachment.path);
|
|
1112
|
+
await telegramMultipartApi(state, method, payload, fileField, attachment.filename, attachment.mimeType, buffer);
|
|
1113
|
+
}
|
|
1114
|
+
catch (error) {
|
|
1115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1116
|
+
console.error(`agent-sin telegram: file send error: ${message}`);
|
|
1117
|
+
await appendEventLog(state.config, {
|
|
1118
|
+
level: "error",
|
|
1119
|
+
source: "telegram",
|
|
1120
|
+
event: "file_send_error",
|
|
1121
|
+
message,
|
|
1122
|
+
details: { chat_id: chatId, path: attachment.path },
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function shouldSendTelegramAsPhoto(attachment) {
|
|
1127
|
+
return attachment.isImage && ["image/png", "image/jpeg", "image/webp"].includes(attachment.mimeType.toLowerCase());
|
|
1128
|
+
}
|
|
1129
|
+
function dedupeLocalAttachments(attachments) {
|
|
1130
|
+
const seen = new Set();
|
|
1131
|
+
const out = [];
|
|
1132
|
+
for (const attachment of attachments) {
|
|
1133
|
+
if (seen.has(attachment.path))
|
|
1134
|
+
continue;
|
|
1135
|
+
seen.add(attachment.path);
|
|
1136
|
+
out.push(attachment);
|
|
1137
|
+
}
|
|
1138
|
+
return out;
|
|
1139
|
+
}
|
|
1074
1140
|
async function sendTelegramChatAction(state, chatId, threadId) {
|
|
1075
1141
|
try {
|
|
1076
1142
|
const payload = {
|
|
@@ -1086,6 +1152,21 @@ async function sendTelegramChatAction(state, chatId, threadId) {
|
|
|
1086
1152
|
// typing indicator is a hint; ignore failures
|
|
1087
1153
|
}
|
|
1088
1154
|
}
|
|
1155
|
+
function telegramFilePayload(chatId, options = {}) {
|
|
1156
|
+
const payload = {
|
|
1157
|
+
chat_id: chatId,
|
|
1158
|
+
};
|
|
1159
|
+
if (options.threadId) {
|
|
1160
|
+
payload.message_thread_id = options.threadId;
|
|
1161
|
+
}
|
|
1162
|
+
if (options.replyToMessageId && process.env.AGENT_SIN_TELEGRAM_REPLY_TO_MESSAGE !== "0") {
|
|
1163
|
+
payload.reply_parameters = {
|
|
1164
|
+
message_id: options.replyToMessageId,
|
|
1165
|
+
allow_sending_without_reply: true,
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
return payload;
|
|
1169
|
+
}
|
|
1089
1170
|
async function telegramApi(state, method, payload) {
|
|
1090
1171
|
const response = await fetch(`${TELEGRAM_API_BASE}/bot${state.token}/${method}`, {
|
|
1091
1172
|
method: "POST",
|
|
@@ -1102,6 +1183,33 @@ async function telegramApi(state, method, payload) {
|
|
|
1102
1183
|
}
|
|
1103
1184
|
return data.result;
|
|
1104
1185
|
}
|
|
1186
|
+
async function telegramMultipartApi(state, method, payload, fileField, filename, mimeType, buffer) {
|
|
1187
|
+
const form = new FormData();
|
|
1188
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
1189
|
+
if (value === undefined || value === null)
|
|
1190
|
+
continue;
|
|
1191
|
+
form.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
1192
|
+
}
|
|
1193
|
+
form.append(fileField, blobFromBuffer(buffer, mimeType), filename);
|
|
1194
|
+
const response = await fetch(`${TELEGRAM_API_BASE}/bot${state.token}/${method}`, {
|
|
1195
|
+
method: "POST",
|
|
1196
|
+
body: form,
|
|
1197
|
+
});
|
|
1198
|
+
if (!response.ok) {
|
|
1199
|
+
const detail = await response.text().catch(() => "");
|
|
1200
|
+
throw new Error(`HTTP ${response.status} ${detail.slice(0, 200)}`);
|
|
1201
|
+
}
|
|
1202
|
+
const data = (await response.json());
|
|
1203
|
+
if (!data.ok) {
|
|
1204
|
+
throw new Error(data.description || "Telegram API error");
|
|
1205
|
+
}
|
|
1206
|
+
return data.result;
|
|
1207
|
+
}
|
|
1208
|
+
function blobFromBuffer(buffer, type) {
|
|
1209
|
+
const bytes = new Uint8Array(buffer.length);
|
|
1210
|
+
bytes.set(buffer);
|
|
1211
|
+
return new Blob([bytes.buffer], { type });
|
|
1212
|
+
}
|
|
1105
1213
|
async function loadTelegramOffset(filePath) {
|
|
1106
1214
|
try {
|
|
1107
1215
|
const raw = await readFile(filePath, "utf8");
|