@xdfnet/ispeak 1.6.4 → 1.6.5

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.
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # Stop Hook: transcript 文件中提取本次会话所有 Claude 回复文本
2
+ # Stop Hook: 只播报本次停止时的最后一条 assistant 回复
3
3
  # iAgent 调用 Claude 时设 ISPEAK_SKIP=1,此时跳过(iAgent 自己播)
4
4
  [[ "$ISPEAK_SKIP" == "1" ]] && exit 0
5
5
 
@@ -32,87 +32,79 @@ json_value() {
32
32
  printf "%s" "$input" | sed -n "s/.*\"$key\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p"
33
33
  }
34
34
 
35
- extract_recent_assistant_text() {
35
+ extract_last_assistant_text() {
36
36
  local transcript="$1"
37
- local cutoff="$2"
38
37
 
39
38
  if command -v node >/dev/null 2>&1; then
40
39
  node -e '
41
40
  const fs = require("fs");
42
41
  const file = process.argv[1];
43
- const cutoff = Number(process.argv[2]);
44
- const out = [];
42
+ let last = "";
45
43
 
46
44
  function collectText(content) {
45
+ const out = [];
47
46
  if (typeof content === "string") {
48
- out.push(content);
49
- return;
47
+ return content;
50
48
  }
51
- if (!Array.isArray(content)) return;
49
+ if (!Array.isArray(content)) return "";
52
50
  for (const item of content) {
53
51
  if (item && typeof item.text === "string") out.push(item.text);
54
52
  }
53
+ return out.join(" ");
55
54
  }
56
55
 
57
56
  for (const line of fs.readFileSync(file, "utf8").split(/\r?\n/)) {
58
57
  if (!line.trim()) continue;
59
58
  try {
60
59
  const event = JSON.parse(line);
61
- if (typeof event.timestamp === "number" && event.timestamp < cutoff) continue;
62
- if (event.role === "assistant") collectText(event.content);
63
- if (event.message && event.message.role === "assistant") collectText(event.message.content);
60
+ if (event.role === "assistant") last = collectText(event.content) || last;
61
+ if (event.message && event.message.role === "assistant") last = collectText(event.message.content) || last;
64
62
  } catch (_) {}
65
63
  }
66
- process.stdout.write([...new Set(out.filter(Boolean))].join(" "));
67
- ' "$transcript" "$cutoff" 2>/dev/null
64
+ process.stdout.write(last);
65
+ ' "$transcript" 2>/dev/null
68
66
  return
69
67
  fi
70
68
 
71
- awk -v cutoff="$cutoff" '
69
+ awk '
72
70
  {
73
- if (match($0, /"timestamp"[[:space:]]*:[[:space:]]*[0-9]+/)) {
74
- ts = substr($0, RSTART, RLENGTH)
75
- gsub(/[^0-9]/, "", ts)
76
- ts = int(ts)
77
- if (ts < cutoff) next
78
- }
79
-
80
71
  if (match($0, /"role"[[:space:]]*:[[:space:]]*"assistant"/)) {
81
72
  if (match($0, /"content"[[:space:]]*:[[:space:]]*\[/)) {
82
73
  gsub(/[^{]*\[/, "", $0)
83
74
  gsub(/\].*/, "", $0)
75
+ msg = ""
84
76
  while (match($0, /"text"[[:space:]]*:[[:space:]]*"[^"]*"/)) {
85
77
  t = substr($0, RSTART, RLENGTH)
86
78
  gsub(/"text"[[:space:]]*:[[:space:]]*"/, "", t)
87
79
  gsub(/"$/, "", t)
88
- if (t != "") print t
80
+ if (t != "") msg = msg " " t
89
81
  $0 = substr($0, RSTART + RLENGTH)
90
82
  }
83
+ if (msg != "") last = msg
91
84
  } else if (match($0, /"content"[[:space:]]*:[[:space:]]*"[^"]*"/)) {
92
85
  t = substr($0, RSTART, RLENGTH)
93
86
  gsub(/"content"[[:space:]]*:[[:space:]]*"/, "", t)
94
87
  gsub(/"$/, "", t)
95
- if (t != "") print t
88
+ if (t != "") last = t
96
89
  }
97
90
  }
98
91
  }
99
- ' "$transcript" 2>/dev/null | sort -u | tr '\n' ' '
92
+ END {
93
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", last)
94
+ if (last != "") print last
95
+ }
96
+ ' "$transcript" 2>/dev/null
100
97
  }
101
98
 
102
- # 从 stdin JSON 提取 transcript 路径和最后一条消息
99
+ # 从 stdin JSON 提取最后一条消息;没有时再从 transcript 取最后一条 assistant
103
100
  transcript=$(json_value "transcript_path")
104
101
  last_msg=$(json_value "last_assistant_message")
105
102
 
106
103
  all_text="$last_msg"
107
104
 
108
- # 如果有 transcript 文件,提取最近 30 秒内的所有 assistant 消息
109
- if [[ -n "$transcript" && -f "$transcript" ]]; then
110
- # 计算 30 秒前的时间戳(毫秒)
111
- cutoff=$(($(date +%s) * 1000 - 30000))
112
-
113
- # 优先用 JSON parser,Node 不存在时回退到简易 awk。
114
- extra=$(extract_recent_assistant_text "$transcript" "$cutoff")
115
-
105
+ # Claude Code 部分版本不提供 last_assistant_message,此时只取 transcript 最后一条 assistant
106
+ if [[ -z "$all_text" && -n "$transcript" && -f "$transcript" ]]; then
107
+ extra=$(extract_last_assistant_text "$transcript")
116
108
  if [[ -n "$extra" ]]; then
117
109
  all_text="$extra"
118
110
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdfnet/ispeak",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "Local macOS TTS daemon for AI coding assistants, powered by Volcengine streaming TTS.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/xdfnet/iSpeak#readme",