arona-agent 1.0.8 → 1.0.9

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 CHANGED
@@ -19,6 +19,9 @@ npm i -g arona-agent
19
19
 
20
20
  # 首次运行自动进入初始化向导
21
21
  arona
22
+
23
+ # 或着如果想先上手玩玩
24
+ npx arona-agent
22
25
  ```
23
26
 
24
27
  ---
package/README_en.md CHANGED
@@ -17,6 +17,9 @@ npm i -g arona-agent
17
17
 
18
18
  # First run auto-starts the setup wizard, then launches ARONA
19
19
  arona
20
+
21
+ # Quick Start
22
+ npx arona-agent
20
23
  ```
21
24
 
22
25
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arona-agent",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Terminal AI Agent with desktop pet Arona — eye-tracking pupils, voice cloning, Computer Use, TTS/STT, MCP.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,8 +17,22 @@ stdout:
17
17
 
18
18
  import json
19
19
  import os
20
+ import socket
20
21
  import sys
21
22
 
23
+ # ---- IPv4 优先解析(同 tts_say.py / voice_clone.py)----
24
+ # macOS 家宽坑:有全局 IPv6 但出口路由不通,python 无 Happy Eyeballs →
25
+ # getaddrinfo v6 在前,每次请求先卡满 v6 超时再回落 v4。重排 AF_INET 在前根治。
26
+ _orig_getaddrinfo = socket.getaddrinfo
27
+
28
+
29
+ def _ipv4_first_getaddrinfo(*args, **kwargs):
30
+ res = _orig_getaddrinfo(*args, **kwargs)
31
+ return sorted(res, key=lambda r: 0 if r[0] == socket.AF_INET else 1)
32
+
33
+
34
+ socket.getaddrinfo = _ipv4_first_getaddrinfo
35
+
22
36
 
23
37
  def fail(msg):
24
38
  print(json.dumps({"error": msg}))
@@ -16,9 +16,32 @@ All logging goes to stderr so stdout stays clean JSON.
16
16
  import json
17
17
  import os
18
18
  import re
19
+ import socket
19
20
  import sys
20
21
  import time
21
22
 
23
+ from _i18n import t
24
+
25
+ # ---- IPv4 优先解析(同 tts_say.py)----
26
+ # macOS 家宽坑:系统有全局 IPv6 地址但出口路由不通,python 无 Happy Eyeballs,
27
+ # getaddrinfo 返回 v6 在前 → 每次请求先卡满 v6 超时才回落 v4(上传/轮询多请求叠加=分钟级卡死,
28
+ # 表现为"正在克隆"卡住;开代理可用是因为代理直连 127.0.0.1 恰好绕开 v6)。
29
+ # 重排 AF_INET 在前根治;IPv4 不可用时仍按序回落 IPv6。
30
+ _orig_getaddrinfo = socket.getaddrinfo
31
+
32
+
33
+ def _ipv4_first_getaddrinfo(*args, **kwargs):
34
+ res = _orig_getaddrinfo(*args, **kwargs)
35
+ return sorted(res, key=lambda r: 0 if r[0] == socket.AF_INET else 1)
36
+
37
+
38
+ socket.getaddrinfo = _ipv4_first_getaddrinfo
39
+
40
+
41
+ def log(msg_zh, msg_en):
42
+ # 阶段诊断只走 stderr:Node 侧 --verbose 会逐行转发实时可见,stdout 保持纯 JSON。
43
+ print(f"[voice_clone] {t(msg_zh, msg_en)}", file=sys.stderr, flush=True)
44
+
22
45
 
23
46
  def fail(msg):
24
47
  print(json.dumps({"error": msg}))
@@ -52,29 +75,39 @@ def main():
52
75
 
53
76
  # 1. Upload audio to DashScope hosted OSS
54
77
  try:
78
+ log(f"上传音频 {os.path.basename(audio_file)} 到 DashScope 托管 OSS...",
79
+ f"Uploading {os.path.basename(audio_file)} to DashScope OSS...")
55
80
  resp = Files.upload(file_path=audio_file, purpose="voice_clone")
56
81
  file_id = resp.output["uploaded_files"][0]["file_id"]
82
+ log(f"上传完成 file_id={file_id}", f"Upload complete file_id={file_id}")
57
83
  except Exception as e:
58
84
  fail(f"Upload failed: {e}")
59
85
 
60
86
  # 2. Get the Alibaba Cloud internal URL (must use internal address)
61
87
  try:
88
+ log("获取 OSS 内部地址...", "Getting OSS internal URL...")
62
89
  oss_url = Files.get(file_id).output["url"]
90
+ log("OSS 内部地址获取成功", "OSS internal URL acquired")
63
91
  except Exception as e:
64
92
  fail(f"Failed to get OSS URL: {e}")
65
93
 
66
94
  # 3. Submit voice cloning
67
95
  try:
96
+ log(f"提交音色克隆(model={target_model},prefix={prefix})...",
97
+ f"Submitting voice creation (model={target_model}, prefix={prefix})...")
68
98
  svc = VoiceEnrollmentService()
69
99
  voice_id = svc.create_voice(target_model=target_model, prefix=prefix, url=oss_url)
100
+ log(f"已提交,voice_id={voice_id}", f"Submitted, voice_id={voice_id}")
70
101
  except Exception as e:
71
102
  fail(f"create_voice failed: {e}")
72
103
 
73
104
  # 4. Wait for voice to be ready (max 5 minutes)
74
105
  try:
75
- for _ in range(30):
106
+ for i in range(30):
76
107
  info = svc.query_voice(voice_id=voice_id)
77
108
  status = info.get("status")
109
+ log(f"查询克隆状态:{status}(第 {i + 1}/30 次,每 10s)",
110
+ f"Voice status: {status} ({i + 1}/30, every 10s)")
78
111
  if status == "OK":
79
112
  break
80
113
  if status == "UNDEPLOYED":
@@ -87,10 +120,12 @@ def main():
87
120
 
88
121
  # 5. Delete the uploaded file (best-effort cleanup)
89
122
  try:
123
+ log("删除已上传文件(尽力清理)...", "Deleting uploaded file (best-effort)...")
90
124
  Files.delete(file_id)
91
125
  except Exception:
92
126
  pass # Cleanup is best-effort
93
127
 
128
+ log(f"音色克隆完成 voice_id={voice_id}", f"Voice clone complete voice_id={voice_id}")
94
129
  print(json.dumps({"voice_id": voice_id}))
95
130
 
96
131
 
@@ -1,6 +1,6 @@
1
1
  import type { ChildProcessWithoutNullStreams } from "child_process";
2
2
  import { join } from "path";
3
- import { PYTHON_DIR, config } from "../config.ts";
3
+ import { PYTHON_DIR, config, verbose } from "../config.ts";
4
4
  import { t, getLang } from "../locale.ts";
5
5
  import { spawnCompat, stripProxyEnv } from "./spawn.ts";
6
6
 
@@ -42,6 +42,13 @@ export async function runPython(
42
42
  });
43
43
  proc.stderr.on("data", (data) => {
44
44
  stderr += data.toString();
45
+ // --verbose:逐行实时转发 python stderr,可见 voice_clone 上传/轮询等阶段进度
46
+ if (verbose) {
47
+ for (const line of data.toString().split(/[\r\n]+/)) {
48
+ const msg = line.trim();
49
+ if (msg) console.error(`[python:${scriptName}]`, msg);
50
+ }
51
+ }
45
52
  });
46
53
  proc.on("close", (code) => {
47
54
  clearTimeout(timer);