arona-agent 1.0.2 → 1.0.4
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/README.md +16 -9
- package/assets/blue-archive/arona/voice_sovits.mp3 +0 -0
- package/assets/blue-archive/arona/voice_text.txt +1 -0
- package/assets/blue-archive/hoshino/voice_sovits.mp3 +0 -0
- package/assets/blue-archive/hoshino/voice_text.txt +1 -0
- package/assets/blue-archive/plana/voice_sovits.mp3 +0 -0
- package/assets/blue-archive/plana/voice_text.txt +1 -0
- package/assets/blue-archive/shiroko/voice_sovits.mp3 +0 -0
- package/assets/blue-archive/shiroko/voice_text.txt +1 -0
- package/bin/arona.mjs +3 -1
- package/bin/postinstall-fix.mjs +46 -0
- package/package.json +3 -2
- package/pet/renderer/gallery.js +2 -2
- package/pet/renderer/renderer.js +1 -1
- package/pet/renderer/style.css +16 -2
- package/pet/tools/gen_sway.cjs +1 -1
- package/pet/tools/visual_test.cjs +2 -2
- package/python/__pycache__/_i18n.cpython-314.pyc +0 -0
- package/python/__pycache__/computer_use.cpython-314.pyc +0 -0
- package/python/__pycache__/hotkey.cpython-314.pyc +0 -0
- package/python/__pycache__/oss_upload.cpython-314.pyc +0 -0
- package/python/__pycache__/stt.cpython-314.pyc +0 -0
- package/python/__pycache__/tts_say.cpython-314.pyc +0 -0
- package/python/hotkey.py +7 -1
- package/python/oss_upload.py +70 -0
- package/python/tts_say.py +182 -19
- package/src/agent.ts +155 -147
- package/src/commands.ts +17 -23
- package/src/config.ts +45 -2
- package/src/gesture_context.ts +48 -0
- package/src/gpt_sovits_local.ts +706 -0
- package/src/index.ts +13 -10
- package/src/memory.ts +3 -10
- package/src/oss_upload.ts +116 -0
- package/src/renderer.ts +17 -6
- package/src/repl.ts +70 -59
- package/src/setup.ts +349 -15
- package/src/skills.ts +32 -2
- package/src/slash_menu.ts +1 -1
- package/src/slash_registry.ts +6 -5
- package/src/tools/read_docs_tool.ts +84 -0
- package/src/tools/tavily_tools.ts +24 -10
- package/src/tools/voice_tools.ts +1 -1
- package/src/tts_provider.ts +501 -0
- package/src/tts_stream.ts +321 -31
- package/src/tui_select.ts +31 -2
- package/src/undo.ts +82 -18
- package/src/utils/python.ts +22 -0
- package/src/voice.ts +17 -7
- package/src/voice_cli.ts +117 -9
- package/src/voices.ts +197 -28
package/src/commands.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { execSync } from "child_process";
|
|
|
3
3
|
import { writeFileSync } from "fs";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import type { AgentSession, DefaultResourceLoader } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { config } from "./config.ts";
|
|
6
|
+
import { config, sttHotkeyLabel } from "./config.ts";
|
|
7
7
|
import * as memory from "./memory.ts";
|
|
8
8
|
import * as voice from "./voice.ts";
|
|
9
9
|
import * as mcp from "./mcp.ts";
|
|
@@ -37,11 +37,12 @@ export interface CommandContext {
|
|
|
37
37
|
undoManager?: UndoManager;
|
|
38
38
|
// /change-agent 切换角色后重启 TTS 进程(音色随主 Agent,spawn 时固化,需重拉)
|
|
39
39
|
restartTts?: () => void;
|
|
40
|
+
// /change-agent 只改子 Agent 组合时回收旧子 session 对象,下轮按新组合重新初始化
|
|
41
|
+
resetSubAgents?: () => void;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
const HELP_TEXT = t(
|
|
43
|
-
|
|
44
|
-
${chalk.bold.cyan("ARONA Agent - 命令列表")}
|
|
45
|
+
`${chalk.bold.cyan("ARONA Agent - 命令列表")}
|
|
45
46
|
|
|
46
47
|
${chalk.bold("会话")}
|
|
47
48
|
/new, /clear 开始新会话(清空上下文)
|
|
@@ -50,9 +51,9 @@ ${chalk.bold("会话")}
|
|
|
50
51
|
/export 导出当前会话为 Markdown
|
|
51
52
|
|
|
52
53
|
${chalk.bold("显示")}
|
|
53
|
-
/thinking
|
|
54
|
+
/thinking 开关推理块显示
|
|
54
55
|
/details 开关工具执行详情显示
|
|
55
|
-
/compact
|
|
56
|
+
/compact 压缩上下文
|
|
56
57
|
|
|
57
58
|
${chalk.bold("语音")}
|
|
58
59
|
/tts 开关文字转语音
|
|
@@ -67,13 +68,8 @@ ${chalk.bold("其他")}
|
|
|
67
68
|
/undo 撤销上一个回合的全部文件改动
|
|
68
69
|
/redo 重做已撤销的改动
|
|
69
70
|
/help 显示本帮助
|
|
70
|
-
|
|
71
|
-
${chalk.cyan("提示:")}
|
|
72
|
-
${chalk.cyan("• @文件名 把文件内容插入到消息中")}
|
|
73
|
-
${chalk.cyan("• !命令 执行 shell 命令并把输出加入消息")}
|
|
74
71
|
`,
|
|
75
|
-
|
|
76
|
-
${chalk.bold.cyan("ARONA Agent - Commands")}
|
|
72
|
+
`${chalk.bold.cyan("ARONA Agent - Commands")}
|
|
77
73
|
|
|
78
74
|
${chalk.bold("Session")}
|
|
79
75
|
/new, /clear Start a new session (clear context)
|
|
@@ -99,10 +95,6 @@ ${chalk.bold("Other")}
|
|
|
99
95
|
/undo Undo the previous turn's file changes
|
|
100
96
|
/redo Redo undone changes
|
|
101
97
|
/help Show this help
|
|
102
|
-
|
|
103
|
-
${chalk.cyan("Tips:")}
|
|
104
|
-
${chalk.cyan("• @filename inserts the file content into your message")}
|
|
105
|
-
${chalk.cyan("• !command runs a shell command and appends its output")}
|
|
106
98
|
`,
|
|
107
99
|
);
|
|
108
100
|
|
|
@@ -184,7 +176,7 @@ export async function handleCommand(input: string, ctx: CommandContext): Promise
|
|
|
184
176
|
voice.setSttEnabled(!voice.isSttEnabled());
|
|
185
177
|
console.log(chalk.cyan(t(`STT:${voice.isSttEnabled() ? "开" : "关"}`, `STT: ${voice.isSttEnabled() ? "on" : "off"}`)));
|
|
186
178
|
if (voice.isSttEnabled()) {
|
|
187
|
-
console.log(chalk.cyan(t(
|
|
179
|
+
console.log(chalk.cyan(t(`长按${sttHotkeyLabel()} ≥2秒录音。`, `Hold ${sttHotkeyLabel()} ≥2s to record.`)));
|
|
188
180
|
}
|
|
189
181
|
ctx.setSttHookEnabled?.(voice.isSttEnabled());
|
|
190
182
|
return true;
|
|
@@ -198,11 +190,11 @@ export async function handleCommand(input: string, ctx: CommandContext): Promise
|
|
|
198
190
|
return true;
|
|
199
191
|
|
|
200
192
|
case "undo":
|
|
201
|
-
handleUndo(ctx);
|
|
193
|
+
await handleUndo(ctx);
|
|
202
194
|
return true;
|
|
203
195
|
|
|
204
196
|
case "redo":
|
|
205
|
-
handleRedo(ctx);
|
|
197
|
+
await handleRedo(ctx);
|
|
206
198
|
return true;
|
|
207
199
|
|
|
208
200
|
case "skill":
|
|
@@ -409,22 +401,22 @@ function handleExport(ctx: CommandContext) {
|
|
|
409
401
|
}
|
|
410
402
|
}
|
|
411
403
|
|
|
412
|
-
function handleUndo(ctx: CommandContext) {
|
|
404
|
+
async function handleUndo(ctx: CommandContext) {
|
|
413
405
|
if (!ctx.undoManager) {
|
|
414
406
|
console.log(chalk.yellow(t("撤销系统未初始化。", "Undo system is not initialized.")));
|
|
415
407
|
return;
|
|
416
408
|
}
|
|
417
|
-
const r = ctx.undoManager.undo();
|
|
409
|
+
const r = await ctx.undoManager.undo();
|
|
418
410
|
if (r.ok) console.log(chalk.green(r.message));
|
|
419
411
|
else console.log(chalk.yellow(r.message));
|
|
420
412
|
}
|
|
421
413
|
|
|
422
|
-
function handleRedo(ctx: CommandContext) {
|
|
414
|
+
async function handleRedo(ctx: CommandContext) {
|
|
423
415
|
if (!ctx.undoManager) {
|
|
424
416
|
console.log(chalk.yellow(t("撤销系统未初始化。", "Undo system is not initialized.")));
|
|
425
417
|
return;
|
|
426
418
|
}
|
|
427
|
-
const r = ctx.undoManager.redo();
|
|
419
|
+
const r = await ctx.undoManager.redo();
|
|
428
420
|
if (r.ok) console.log(chalk.green(r.message));
|
|
429
421
|
else console.log(chalk.yellow(r.message));
|
|
430
422
|
}
|
|
@@ -535,8 +527,10 @@ async function handleChangeAgent(ctx: CommandContext) {
|
|
|
535
527
|
// 1. 持久化(settings.json mainAgent + subAgents 字段)
|
|
536
528
|
setMainAgent(main);
|
|
537
529
|
setSubAgents(subs);
|
|
538
|
-
// 2.
|
|
530
|
+
// 2. 桌宠多窗口按新组合重建
|
|
539
531
|
pet.restartWithSelection(main, subs);
|
|
532
|
+
// 2.5 子 Agent 组合变化:回收旧子 session 对象;主 Agent 切换时 newSession 也会回收
|
|
533
|
+
if (subsChanged) ctx.resetSubAgents?.();
|
|
540
534
|
// 3. 主 Agent 切换才需要重启 TTS(音色随主 Agent 变化)与重建会话(人格变化)
|
|
541
535
|
if (mainChanged) {
|
|
542
536
|
ctx.restartTts?.();
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir } from "os";
|
|
2
2
|
import { join, resolve } from "path";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
4
|
-
import type
|
|
4
|
+
import { t, type LanguageSetting } from "./locale.ts";
|
|
5
5
|
|
|
6
6
|
export const ARONA_DIR = join(homedir(), ".arona");
|
|
7
7
|
export const MEMORY_FILE = join(ARONA_DIR, "MEMORY.md");
|
|
@@ -35,6 +35,22 @@ export interface McpServerConfig {
|
|
|
35
35
|
headers?: Record<string, string>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** 已实现的 TTS Provider。 */
|
|
39
|
+
export type TtsProvider = "aliyun" | "gpt-sovits";
|
|
40
|
+
|
|
41
|
+
export const TTS_PROVIDER_IDS: readonly TtsProvider[] = ["aliyun", "gpt-sovits"];
|
|
42
|
+
|
|
43
|
+
/** settings.json 中 ttsProvider 的原始值解析:未知/未实现回退 aliyun 并警告一次。 */
|
|
44
|
+
function resolveTtsProvider(raw: unknown): TtsProvider {
|
|
45
|
+
if (typeof raw === "string" && (TTS_PROVIDER_IDS as readonly string[]).includes(raw)) {
|
|
46
|
+
return raw as TtsProvider;
|
|
47
|
+
}
|
|
48
|
+
if (typeof raw === "string" && raw) {
|
|
49
|
+
console.warn(t(`[tts] 未知 ttsProvider "${raw}",已回退 aliyun`, `[tts] unknown ttsProvider "${raw}", falling back to aliyun`));
|
|
50
|
+
}
|
|
51
|
+
return "aliyun";
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
interface AronaConfig {
|
|
39
55
|
// LLM
|
|
40
56
|
apiKey: string;
|
|
@@ -45,6 +61,10 @@ interface AronaConfig {
|
|
|
45
61
|
contextWindow: number;
|
|
46
62
|
// Voice (Qwen / 阿里云百炼 DashScope)
|
|
47
63
|
noVoice: boolean;
|
|
64
|
+
// TTS 后端:aliyun(默认,百炼) | gpt-sovits(本地 api_v2 服务)
|
|
65
|
+
ttsProvider: TtsProvider;
|
|
66
|
+
// 各 Provider 专属配置(键 = provider id)。百炼沿用顶层 ttsApiKey/ttsModel/workspaceId,不入此表。
|
|
67
|
+
ttsConfig: Record<string, unknown>;
|
|
48
68
|
// 百炼业务空间 ID(可选;留空走旧域名 dashscope.aliyuncs.com)
|
|
49
69
|
workspaceId: string;
|
|
50
70
|
ttsApiKey: string;
|
|
@@ -83,6 +103,10 @@ interface Settings {
|
|
|
83
103
|
sttEnabled?: boolean;
|
|
84
104
|
/** @deprecated 已合并进 ttsEnabled,仅读兼容(旧配置 ttsAuto:false 仍生效) */
|
|
85
105
|
ttsAuto?: boolean;
|
|
106
|
+
/** TTS 后端(aliyun | gpt-sovits) */
|
|
107
|
+
ttsProvider?: string;
|
|
108
|
+
/** 各 Provider 专属配置(键 = provider id;百炼沿用顶层 TTS 字段) */
|
|
109
|
+
ttsConfig?: Record<string, unknown>;
|
|
86
110
|
workspaceId?: string;
|
|
87
111
|
ttsApiKey?: string;
|
|
88
112
|
ttsModel?: string;
|
|
@@ -115,7 +139,7 @@ function loadSettings(): Settings {
|
|
|
115
139
|
try {
|
|
116
140
|
return JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
|
|
117
141
|
} catch {
|
|
118
|
-
console.warn("Failed to parse settings.json, using defaults");
|
|
142
|
+
console.warn(t("settings.json 解析失败,使用默认配置", "Failed to parse settings.json, using defaults"));
|
|
119
143
|
return {};
|
|
120
144
|
}
|
|
121
145
|
}
|
|
@@ -219,6 +243,9 @@ function loadConfig(): AronaConfig {
|
|
|
219
243
|
thinkingLevel: s.thinkingLevel || "medium",
|
|
220
244
|
contextWindow: typeof s.contextWindow === "number" && s.contextWindow > 0 ? s.contextWindow : 1000000,
|
|
221
245
|
|
|
246
|
+
ttsProvider: resolveTtsProvider(s.ttsProvider),
|
|
247
|
+
ttsConfig: s.ttsConfig && typeof s.ttsConfig === "object" && !Array.isArray(s.ttsConfig) ? s.ttsConfig : {},
|
|
248
|
+
|
|
222
249
|
noVoice,
|
|
223
250
|
// Skip TTS/STT fields when --no-voice is set
|
|
224
251
|
workspaceId: noVoice ? "" : (s.workspaceId || ""),
|
|
@@ -243,3 +270,19 @@ function loadConfig(): AronaConfig {
|
|
|
243
270
|
}
|
|
244
271
|
|
|
245
272
|
export const config = loadConfig();
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* STT 全局热键键名(pynput 命名,供 repl.ts 注入 ARONA_HOTKEY_KEY):
|
|
276
|
+
* macOS 右 Cmd(pynput 有 Key.cmd_r),Windows/Linux 右 Ctrl(pynput Key.ctrl_r 三平台皆有效)。
|
|
277
|
+
* Windows 没有 Key.cmd_r,默认 cmd_r 在那会是静默失效,故按平台自动选。
|
|
278
|
+
*/
|
|
279
|
+
export function sttHotkeyKey(): string {
|
|
280
|
+
return process.platform === "darwin" ? "cmd_r" : "ctrl_r";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** STT 全局热键显示名(用户可见,双语简洁)。右 Ctrl 提示一句话即可,不追加文案。 */
|
|
284
|
+
export function sttHotkeyLabel(): string {
|
|
285
|
+
return process.platform === "darwin"
|
|
286
|
+
? t("右 Cmd", "right Cmd")
|
|
287
|
+
: t("右 Ctrl", "right Ctrl");
|
|
288
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// 桌宠手势上下文注入扩展:摸头/dizzy 触发后,在**主 Agent** 的发送边界(context 事件)
|
|
2
|
+
// 追加一条 user 消息,描述"刚才发生了什么",引导主 Agent 在回复中自然流露出确实感受到了。
|
|
3
|
+
//
|
|
4
|
+
// 为什么用发送边界而不是拼进用户输入:repl.ts 的 processInput 会把文本写进主 session 的
|
|
5
|
+
// state.messages,会污染会话命名/存储;拼进输入仍可能被主 session 落盘/导出,且子 Agent 每轮
|
|
6
|
+
// 复制主 session 全量(stateless)时会一并看到。发送边界注入只改本次请求的消息数组、不进
|
|
7
|
+
// state.messages → 仅主 Agent 本轮看到、零存储污染、会话命名天然干净。
|
|
8
|
+
//
|
|
9
|
+
// 只注册在**主 Agent** 的 loader 扩展列表(agent.ts initAgent),子 Agent(initSubAgent)
|
|
10
|
+
// 的 loader 不含本扩展 → 不会收到。消费经 pet.takeGesture()(消费即清空,只注入最近一次)。
|
|
11
|
+
|
|
12
|
+
import type { ContextEvent, ExtensionAPI, InlineExtension } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { pet } from "./pet.ts";
|
|
14
|
+
import { t } from "./locale.ts";
|
|
15
|
+
|
|
16
|
+
/** 桌宠手势 → user 消息的上下文场景文本(双语)。 */
|
|
17
|
+
function petGestureScene(type: "pat" | "dizzy"): string {
|
|
18
|
+
if (type === "dizzy") {
|
|
19
|
+
return t(
|
|
20
|
+
"(Sensei刚才拖着你到处晃来晃去,你感觉有些头晕。请在回复里自然地体现出这份晕眩感。)",
|
|
21
|
+
"(Sensei just swung you around, and you feel a bit dizzy. Let that dizziness show naturally in your reply.)",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return t(
|
|
25
|
+
"(Sensei刚才摸了摸你的头。请在回复里自然地体现出被摸头的感受。)",
|
|
26
|
+
"(Sensei just petted your head. Let that show naturally in your reply.)",
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const gestureContextExtension: InlineExtension = {
|
|
31
|
+
name: "arona-gesture-context",
|
|
32
|
+
hidden: true, // 不显示在启动扩展列表
|
|
33
|
+
factory: (pi: ExtensionAPI) => {
|
|
34
|
+
pi.on("context", (event: ContextEvent) => {
|
|
35
|
+
// 有手势时才注入;takeGesture 消费即清空 → 一次 LLM 调用只注入一次、无手势时原样返回
|
|
36
|
+
const gesture = pet.takeGesture();
|
|
37
|
+
if (!gesture) return;
|
|
38
|
+
const scene = petGestureScene(gesture);
|
|
39
|
+
if (!scene) return;
|
|
40
|
+
return {
|
|
41
|
+
messages: [
|
|
42
|
+
...event.messages,
|
|
43
|
+
{ role: "user" as const, content: [{ type: "text" as const, text: scene }], timestamp: Date.now() },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|