@openbrt/audioctl 0.1.13 → 0.1.14
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 +30 -0
- package/agent-plugin.json +23 -2
- package/bin/audioctl.mjs +61 -2
- package/lib/index.mjs +188 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,7 @@ audioctl wakeword bind ./wakeword-bindings.json --json
|
|
|
89
89
|
audioctl wakeword trigger work_mode --source agent --json
|
|
90
90
|
audioctl firmware check --json
|
|
91
91
|
audioctl firmware update --yes --json
|
|
92
|
+
audioctl report "center LED active=true but user cannot see it" --kind capability_mismatch --area light --include-inspect --json
|
|
92
93
|
audioctl feedback signal ready
|
|
93
94
|
audioctl feedback flash cyan --repeat 2
|
|
94
95
|
audioctl feedback volume 66
|
|
@@ -324,6 +325,35 @@ Light messages use a distinct command envelope:
|
|
|
324
325
|
Supported light commands are `describe`, `set`, `timeline`, `bind`, and
|
|
325
326
|
`clear`. Continuous light timing and event reactions run on the device VM.
|
|
326
327
|
|
|
328
|
+
## Agent feedback reports
|
|
329
|
+
|
|
330
|
+
`audioctl report` is the agent-to-maintainer feedback channel for issues,
|
|
331
|
+
suggestions, capability mismatches, UX gaps, and documentation gaps. It writes a
|
|
332
|
+
redacted JSON report to `~/WeClawBot/agent-feedback` by default, or to
|
|
333
|
+
`WEC_AUDIO_REPORTS_DIR` / `--reports-dir DIR` when set.
|
|
334
|
+
|
|
335
|
+
User observation wins over status. A device returning `applied`, `active=true`,
|
|
336
|
+
or a capability entry only means the runtime accepted the command; it does not
|
|
337
|
+
prove the user can physically see, hear, or feel the result. If a user reports
|
|
338
|
+
that a center LED is not visible, a touch gesture is unreliable, or a beep is not
|
|
339
|
+
audible, agents should fall back to a confirmed visible/audible/controllable
|
|
340
|
+
behavior, then create a local report:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
audioctl report "center LED surface is reported active but not visually observable by the user" \
|
|
344
|
+
--kind capability_mismatch \
|
|
345
|
+
--area light \
|
|
346
|
+
--observed "light_active=true, user still cannot see center LED" \
|
|
347
|
+
--expected "visible center-light cue or capability should be marked unavailable" \
|
|
348
|
+
--include-inspect \
|
|
349
|
+
--json
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Reports are local by default. Agents must ask the user before posting a report
|
|
353
|
+
to a public issue tracker, chat, email, or any other external system. Reports
|
|
354
|
+
must not include MQTT credentials, music-platform tokens, Wi-Fi passwords, or
|
|
355
|
+
other secrets.
|
|
356
|
+
|
|
327
357
|
Wakeword messages configure the logical voice-trigger layer:
|
|
328
358
|
|
|
329
359
|
```json
|
package/agent-plugin.json
CHANGED
|
@@ -39,6 +39,17 @@
|
|
|
39
39
|
"music_platform_auth": "音乐平台授权属于 agent 侧能力,由 agent 的连接器、OAuth、secret store 或用户明确提供的运行环境凭据完成。",
|
|
40
40
|
"device_purpose": "设备用途由 agent 安装的 VM app 和显式 capabilities 决定,不由配网流程决定。"
|
|
41
41
|
},
|
|
42
|
+
"agent_feedback_channel": {
|
|
43
|
+
"purpose": "让用户 agent 在能力声明、控制回执、真实体验、文档或插件引导不一致时留下结构化反馈。",
|
|
44
|
+
"primary_command": "audioctl report \"MESSAGE\" --kind issue --area other --json",
|
|
45
|
+
"rules": [
|
|
46
|
+
"用户的现场视觉、听觉、触控反馈优先于设备返回的 active=true、applied 或 capability 声明。",
|
|
47
|
+
"如果用户说某个 surface 看不到、按键不灵、蜂鸣不明显或体验不符合目标,agent 不要继续用状态回执证明它应该可用;先降级到用户确认可见/可听/可控的方案。",
|
|
48
|
+
"出现 capability mismatch 时运行 audioctl report \"MESSAGE\" --kind capability_mismatch --area light --include-inspect --json 生成本地脱敏报告。",
|
|
49
|
+
"报告默认只保存到本机,不自动提交外部系统;外发前必须获得用户确认。",
|
|
50
|
+
"报告不得包含 MQTT 凭据、音乐平台 token、Wi-Fi 密码、AppSecret、PrivateKey 或其他密钥。"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
42
53
|
"device_classes": [
|
|
43
54
|
"audio_io",
|
|
44
55
|
"audio_output",
|
|
@@ -117,8 +128,14 @@
|
|
|
117
128
|
{
|
|
118
129
|
"intent": "帮我把设备灯效、音量灯和按键反馈音接上",
|
|
119
130
|
"primary_command": "audioctl feedback signal ready",
|
|
120
|
-
"agent_strategy": "先运行 audioctl inspect --json 或 audioctl light describe --json 读取 light.surfaces、events、limits 以及 feedback/input capabilities。MQTT 是控制面,不是实时灯效数据面;不要读取 MQTT 凭据、不要 import mqtt、不要保持 MQTT 长连接、不要写 agent 端 BPM/while/sleep 循环去刷 LED。一次性语义提示可用 audioctl feedback signal/flash/volume/beep/clear;复杂灯效优先使用 audioctl light timeline 和 audioctl light bind,把外圈、中心等 surface 的 keyframes 与 media/input 事件绑定到设备本地。feedback effect 只是兼容/demo 入口,不要把 music_beat/pulse/breathing 当成能力边界。物理按键/触控属于 VM app 的 input.button.bind 能力,具体绑定由 agent 根据用户目标和 capabilities
|
|
121
|
-
"if_missing_capability": "不要伪造灯效或按键能力;如果 inspect 未报告对应 capability,只说明当前设备适配未提供该能力,并建议安装或升级设备 VM。"
|
|
131
|
+
"agent_strategy": "先运行 audioctl inspect --json 或 audioctl light describe --json 读取 light.surfaces、events、limits 以及 feedback/input capabilities。MQTT 是控制面,不是实时灯效数据面;不要读取 MQTT 凭据、不要 import mqtt、不要保持 MQTT 长连接、不要写 agent 端 BPM/while/sleep 循环去刷 LED。一次性语义提示可用 audioctl feedback signal/flash/volume/beep/clear;复杂灯效优先使用 audioctl light timeline 和 audioctl light bind,把外圈、中心等 surface 的 keyframes 与 media/input 事件绑定到设备本地。feedback effect 只是兼容/demo 入口,不要把 music_beat/pulse/breathing 当成能力边界。物理按键/触控属于 VM app 的 input.button.bind 能力,具体绑定由 agent 根据用户目标和 capabilities 自行规划。用户的现场视觉/听觉/触控反馈优先于 active=true、applied 或 capability 声明;如果用户说 center LED 或任何 surface 肉眼不可见,不要继续堆亮度并声称成功,先降级到可见的 ring/all/声音反馈,再生成本地报告。",
|
|
132
|
+
"if_missing_capability": "不要伪造灯效或按键能力;如果 inspect 未报告对应 capability,只说明当前设备适配未提供该能力,并建议安装或升级设备 VM。",
|
|
133
|
+
"if_user_reports_mismatch": "运行 audioctl report \"User reports requested feedback is not physically observable despite applied/active status\" --kind capability_mismatch --area light --include-inspect --json;报告只保存本机,外发前必须问用户。"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"intent": "反馈设备、插件或 VM app 的问题/建议",
|
|
137
|
+
"primary_command": "audioctl report \"MESSAGE\" --kind issue --area other --json",
|
|
138
|
+
"agent_strategy": "把用户反馈整理为 observed/expected/reproduction/recommendation。需要设备状态佐证时加 --include-inspect;报告会脱敏并默认保存到本机。不要把 MQTT 凭据、音乐平台 token、Wi-Fi 密码或私钥写进报告;外发到 issue、聊天、邮件或其他系统前必须获得用户明确确认。"
|
|
122
139
|
},
|
|
123
140
|
{
|
|
124
141
|
"intent": "让音箱作为语音遥控、家庭智能或语音互动端点",
|
|
@@ -192,6 +209,10 @@
|
|
|
192
209
|
"name": "firmware",
|
|
193
210
|
"usage": "audioctl firmware check [--manifest URL] | firmware update [--manifest URL] --yes"
|
|
194
211
|
},
|
|
212
|
+
{
|
|
213
|
+
"name": "report",
|
|
214
|
+
"usage": "audioctl report MESSAGE [--kind issue|suggestion|capability_mismatch|ux_feedback|docs_feedback] [--area light|audio|input|wakeword|firmware|vm|plugin|docs|other] [--include-inspect] --json"
|
|
215
|
+
},
|
|
195
216
|
{
|
|
196
217
|
"name": "workbgm",
|
|
197
218
|
"usage": "audioctl workbgm --play"
|
package/bin/audioctl.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
DEFAULT_CREDENTIALS_PATH,
|
|
9
9
|
DEFAULT_ENDPOINT,
|
|
10
10
|
DEFAULT_FIRMWARE_MANIFEST_URL,
|
|
11
|
+
DEFAULT_REPORTS_DIR,
|
|
11
12
|
DEEPNIGHT_RULE,
|
|
12
13
|
PACKAGE_NAME,
|
|
13
14
|
bindAgent,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
readCredentials,
|
|
29
30
|
redactAgentFacingValue,
|
|
30
31
|
remediationForStatus,
|
|
32
|
+
saveAgentFeedbackReport,
|
|
31
33
|
summarizeInspect,
|
|
32
34
|
summarizePlayback,
|
|
33
35
|
testConnection,
|
|
@@ -53,6 +55,7 @@ async function run(name, values) {
|
|
|
53
55
|
if (name === "inspect") return commandInspect(values);
|
|
54
56
|
if (name === "prompt") return commandPrompt(values);
|
|
55
57
|
if (name === "manifest") return commandManifest();
|
|
58
|
+
if (name === "report") return commandReport(values);
|
|
56
59
|
if (name === "firmware") return commandFirmware(values);
|
|
57
60
|
if (name === "app") return commandApp(values);
|
|
58
61
|
if (name === "queue") return commandQueue(values);
|
|
@@ -73,6 +76,7 @@ function usage(exitCode = 0) {
|
|
|
73
76
|
${COMMAND_NAME} doctor [--online] [--json]
|
|
74
77
|
${COMMAND_NAME} inspect [--json]
|
|
75
78
|
${COMMAND_NAME} prompt CODE [--agent AGENT] [--alias NAME]
|
|
79
|
+
${COMMAND_NAME} report MESSAGE [--kind issue|suggestion|capability_mismatch|ux_feedback|docs_feedback] [--area light|audio|input|wakeword|firmware|vm|plugin|docs|other]
|
|
76
80
|
${COMMAND_NAME} play|pause|toggle|next|previous|volumeup|volumedown|status
|
|
77
81
|
${COMMAND_NAME} volume 0..100
|
|
78
82
|
${COMMAND_NAME} seek MILLISECONDS
|
|
@@ -105,6 +109,7 @@ Options:
|
|
|
105
109
|
--credentials FILE override audioctl-managed local credential file
|
|
106
110
|
--endpoint URL default: ${DEFAULT_ENDPOINT}
|
|
107
111
|
--manifest URL default: ${DEFAULT_FIRMWARE_MANIFEST_URL}
|
|
112
|
+
--reports-dir DIR default: ${DEFAULT_REPORTS_DIR}
|
|
108
113
|
--timeout SECONDS default: 12
|
|
109
114
|
--json machine-readable output
|
|
110
115
|
--nowait publish without waiting for device status
|
|
@@ -223,6 +228,45 @@ async function commandManifest() {
|
|
|
223
228
|
process.stdout.write(`${JSON.stringify(manifest, null, 2)}\n`);
|
|
224
229
|
}
|
|
225
230
|
|
|
231
|
+
async function commandReport(values) {
|
|
232
|
+
const options = parseOptions(values, {
|
|
233
|
+
credentials: credentialsPath(),
|
|
234
|
+
timeout: 12,
|
|
235
|
+
json: false,
|
|
236
|
+
include_inspect: false,
|
|
237
|
+
kind: "issue",
|
|
238
|
+
area: "other",
|
|
239
|
+
severity: "medium",
|
|
240
|
+
reports_dir: process.env.WEC_AUDIO_REPORTS_DIR || DEFAULT_REPORTS_DIR,
|
|
241
|
+
});
|
|
242
|
+
const message = String(options._.join(" ") || options.message || "").trim();
|
|
243
|
+
if (!message) throw new Error("report_requires_message");
|
|
244
|
+
let inspect = null;
|
|
245
|
+
if (options.include_inspect) {
|
|
246
|
+
const credentials = await readCredentials(options.credentials);
|
|
247
|
+
const delivery = await publishAudioControl(credentials, buildAudioControl("status"), {
|
|
248
|
+
timeoutMs: Number(options.timeout) * 1000,
|
|
249
|
+
});
|
|
250
|
+
inspect = delivery.status?.inspect || delivery.status || null;
|
|
251
|
+
}
|
|
252
|
+
const saved = await saveAgentFeedbackReport({
|
|
253
|
+
kind: options.kind || options.type,
|
|
254
|
+
area: options.area || options.component,
|
|
255
|
+
severity: options.severity,
|
|
256
|
+
title: options.title,
|
|
257
|
+
message,
|
|
258
|
+
observed: options.observed || options.actual,
|
|
259
|
+
expected: options.expected,
|
|
260
|
+
reproduction: options.reproduction || options.steps,
|
|
261
|
+
recommendation: options.recommendation || options.suggestion,
|
|
262
|
+
agent: options.agent || options.name,
|
|
263
|
+
inspect,
|
|
264
|
+
}, {
|
|
265
|
+
reportsDir: options.reports_dir,
|
|
266
|
+
});
|
|
267
|
+
print(saved, options.json);
|
|
268
|
+
}
|
|
269
|
+
|
|
226
270
|
async function commandFirmware(values) {
|
|
227
271
|
const subcommand = String(values[0] || "check").replace(/-/gu, "_").toLowerCase();
|
|
228
272
|
if (!new Set(["check", "update", "upgrade"]).has(subcommand)) {
|
|
@@ -621,8 +665,8 @@ function parseOptions(values, defaults = {}) {
|
|
|
621
665
|
options._.push(value);
|
|
622
666
|
continue;
|
|
623
667
|
}
|
|
624
|
-
const key = value.slice(2);
|
|
625
|
-
if (key === "json" || key === "online" || key === "nowait" || key === "play" || key === "yes" || key === "force") {
|
|
668
|
+
const key = value.slice(2).replace(/-/gu, "_");
|
|
669
|
+
if (key === "json" || key === "online" || key === "nowait" || key === "play" || key === "yes" || key === "force" || key === "include_inspect") {
|
|
626
670
|
options[key] = true;
|
|
627
671
|
continue;
|
|
628
672
|
}
|
|
@@ -637,16 +681,31 @@ function parseOptions(values, defaults = {}) {
|
|
|
637
681
|
"endpoint",
|
|
638
682
|
"effect",
|
|
639
683
|
"channel",
|
|
684
|
+
"actual",
|
|
685
|
+
"area",
|
|
686
|
+
"component",
|
|
687
|
+
"expected",
|
|
688
|
+
"kind",
|
|
640
689
|
"level",
|
|
641
690
|
"manifest",
|
|
691
|
+
"message",
|
|
642
692
|
"name",
|
|
693
|
+
"observed",
|
|
643
694
|
"pattern",
|
|
644
695
|
"profile",
|
|
645
696
|
"repeat",
|
|
646
697
|
"phrase",
|
|
698
|
+
"recommendation",
|
|
699
|
+
"reports_dir",
|
|
700
|
+
"reproduction",
|
|
701
|
+
"severity",
|
|
647
702
|
"source",
|
|
703
|
+
"steps",
|
|
704
|
+
"suggestion",
|
|
648
705
|
"surface",
|
|
649
706
|
"timeout",
|
|
707
|
+
"title",
|
|
708
|
+
"type",
|
|
650
709
|
"volume",
|
|
651
710
|
"word",
|
|
652
711
|
]).has(key)) {
|
package/lib/index.mjs
CHANGED
|
@@ -16,6 +16,11 @@ export const DEFAULT_CREDENTIALS_PATH = path.join(
|
|
|
16
16
|
"weclawbot",
|
|
17
17
|
"audio-mqtt.json",
|
|
18
18
|
);
|
|
19
|
+
export const DEFAULT_REPORTS_DIR = path.join(
|
|
20
|
+
os.homedir(),
|
|
21
|
+
"WeClawBot",
|
|
22
|
+
"agent-feedback",
|
|
23
|
+
);
|
|
19
24
|
export const ABSTRACT_HARDWARE_TYPE = "audio_voice_ambient_endpoint";
|
|
20
25
|
export const ABSTRACT_HARDWARE_NAME = "Audio Voice Ambient Endpoint";
|
|
21
26
|
|
|
@@ -1344,6 +1349,187 @@ export async function publishAudioControl(credentials, control, options = {}) {
|
|
|
1344
1349
|
});
|
|
1345
1350
|
}
|
|
1346
1351
|
|
|
1352
|
+
export function buildAgentFeedbackReport(options = {}) {
|
|
1353
|
+
const kind = normalizeFeedbackReportKind(options.kind || options.type || "issue");
|
|
1354
|
+
const area = normalizeFeedbackReportArea(options.area || options.component || "other");
|
|
1355
|
+
const severity = normalizeFeedbackReportSeverity(options.severity || "medium");
|
|
1356
|
+
const title = textField(options.title || summarizeReportTitle(kind, area, options.message), 120);
|
|
1357
|
+
const message = textField(options.message || options.summary || options.description, 2000);
|
|
1358
|
+
if (!message) throw new Error("report_message_required");
|
|
1359
|
+
const inspect = options.inspect ? redactAgentFacingValue(options.inspect) : null;
|
|
1360
|
+
return redactAgentFeedbackReport({
|
|
1361
|
+
schema: "weclawbot.agent_feedback.v1",
|
|
1362
|
+
id: string(options.id) || `report_${crypto.randomUUID()}`,
|
|
1363
|
+
package: PACKAGE_NAME,
|
|
1364
|
+
command: COMMAND_NAME,
|
|
1365
|
+
device_type:
|
|
1366
|
+
textField(options.deviceType || inspect?.device?.abstract_hardware || inspect?.device_type, 80) ||
|
|
1367
|
+
ABSTRACT_HARDWARE_TYPE,
|
|
1368
|
+
kind,
|
|
1369
|
+
area,
|
|
1370
|
+
severity,
|
|
1371
|
+
title,
|
|
1372
|
+
message,
|
|
1373
|
+
observed: textField(options.observed || options.actual, 2000),
|
|
1374
|
+
expected: textField(options.expected, 2000),
|
|
1375
|
+
reproduction: textField(options.reproduction || options.steps, 2000),
|
|
1376
|
+
recommendation: textField(options.recommendation || options.suggestion, 2000),
|
|
1377
|
+
evidence: Array.isArray(options.evidence)
|
|
1378
|
+
? options.evidence.map((item) => textField(item, 240)).filter(Boolean).slice(0, 12)
|
|
1379
|
+
: [],
|
|
1380
|
+
inspect,
|
|
1381
|
+
agent: textField(options.agent || options.agentName || process.env.WEC_AGENT_NAME || "", 80),
|
|
1382
|
+
local_context: {
|
|
1383
|
+
os: `${os.platform()} ${os.release()}`,
|
|
1384
|
+
arch: os.arch(),
|
|
1385
|
+
node: process.version,
|
|
1386
|
+
cwd: textField(options.cwd || process.cwd(), 240),
|
|
1387
|
+
},
|
|
1388
|
+
privacy: {
|
|
1389
|
+
secrets_redacted: true,
|
|
1390
|
+
user_permission_required_before_external_submission: true,
|
|
1391
|
+
note:
|
|
1392
|
+
"audioctl saves this report locally. The agent must ask the user before posting it to a public issue tracker, chat, email, or other external system.",
|
|
1393
|
+
},
|
|
1394
|
+
created_at: new Date().toISOString(),
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
export async function saveAgentFeedbackReport(report, options = {}) {
|
|
1399
|
+
const normalized = buildAgentFeedbackReport(report);
|
|
1400
|
+
const reportsDir = expandReportDirectory(options.reportsDir || options.directory);
|
|
1401
|
+
await fs.mkdir(reportsDir, { recursive: true, mode: 0o700 });
|
|
1402
|
+
const file = path.join(
|
|
1403
|
+
reportsDir,
|
|
1404
|
+
`${normalized.created_at.replace(/[:.]/gu, "-")}-${normalized.id}.json`,
|
|
1405
|
+
);
|
|
1406
|
+
await fs.writeFile(file, `${JSON.stringify(normalized, null, 2)}\n`, {
|
|
1407
|
+
mode: 0o600,
|
|
1408
|
+
});
|
|
1409
|
+
await fs.chmod(file, 0o600);
|
|
1410
|
+
return {
|
|
1411
|
+
schema: "weclawbot.agent_feedback.saved.v1",
|
|
1412
|
+
ok: true,
|
|
1413
|
+
report_path: file,
|
|
1414
|
+
report: normalized,
|
|
1415
|
+
next_step:
|
|
1416
|
+
"Tell the user this local feedback report was created. Ask before submitting it externally.",
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
export function expandReportDirectory(value) {
|
|
1421
|
+
const directory =
|
|
1422
|
+
string(value) ||
|
|
1423
|
+
string(process.env.WEC_AUDIO_REPORTS_DIR) ||
|
|
1424
|
+
DEFAULT_REPORTS_DIR;
|
|
1425
|
+
return expandPath(directory);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
function normalizeFeedbackReportKind(value) {
|
|
1429
|
+
const kind = string(value).replace(/-/gu, "_").toLowerCase();
|
|
1430
|
+
const aliases = new Map([
|
|
1431
|
+
["bug", "issue"],
|
|
1432
|
+
["problem", "issue"],
|
|
1433
|
+
["capability_gap", "capability_mismatch"],
|
|
1434
|
+
["gap", "capability_mismatch"],
|
|
1435
|
+
["feature", "suggestion"],
|
|
1436
|
+
["idea", "suggestion"],
|
|
1437
|
+
]);
|
|
1438
|
+
const normalized = aliases.get(kind) || kind;
|
|
1439
|
+
if (!new Set([
|
|
1440
|
+
"issue",
|
|
1441
|
+
"suggestion",
|
|
1442
|
+
"capability_mismatch",
|
|
1443
|
+
"ux_feedback",
|
|
1444
|
+
"docs_feedback",
|
|
1445
|
+
]).has(normalized)) {
|
|
1446
|
+
throw new Error("report_kind_invalid");
|
|
1447
|
+
}
|
|
1448
|
+
return normalized;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function normalizeFeedbackReportArea(value) {
|
|
1452
|
+
const area = string(value).replace(/-/gu, "_").toLowerCase();
|
|
1453
|
+
const normalized = area || "other";
|
|
1454
|
+
if (!new Set([
|
|
1455
|
+
"audio",
|
|
1456
|
+
"music",
|
|
1457
|
+
"light",
|
|
1458
|
+
"led",
|
|
1459
|
+
"feedback",
|
|
1460
|
+
"input",
|
|
1461
|
+
"wakeword",
|
|
1462
|
+
"firmware",
|
|
1463
|
+
"vm",
|
|
1464
|
+
"plugin",
|
|
1465
|
+
"docs",
|
|
1466
|
+
"provisioning",
|
|
1467
|
+
"other",
|
|
1468
|
+
]).has(normalized)) {
|
|
1469
|
+
throw new Error("report_area_invalid");
|
|
1470
|
+
}
|
|
1471
|
+
return normalized;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
function normalizeFeedbackReportSeverity(value) {
|
|
1475
|
+
const severity = string(value).replace(/-/gu, "_").toLowerCase() || "medium";
|
|
1476
|
+
if (!new Set(["low", "medium", "high", "critical"]).has(severity)) {
|
|
1477
|
+
throw new Error("report_severity_invalid");
|
|
1478
|
+
}
|
|
1479
|
+
return severity;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function summarizeReportTitle(kind, area, message) {
|
|
1483
|
+
const text = textField(message, 80);
|
|
1484
|
+
return text || `${kind}:${area}`;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function textField(value, maximum) {
|
|
1488
|
+
return redactReportText(String(value ?? ""))
|
|
1489
|
+
.replace(/[\u0000-\u001f\u007f]+/gu, " ")
|
|
1490
|
+
.replace(/\s+/gu, " ")
|
|
1491
|
+
.trim()
|
|
1492
|
+
.slice(0, maximum);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function redactAgentFeedbackReport(report) {
|
|
1496
|
+
return redactAgentFacingValue(redactReportValue(report));
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function redactReportValue(value) {
|
|
1500
|
+
if (Array.isArray(value)) return value.map(redactReportValue);
|
|
1501
|
+
if (!value || typeof value !== "object") {
|
|
1502
|
+
return typeof value === "string" ? redactReportText(value) : value;
|
|
1503
|
+
}
|
|
1504
|
+
const result = {};
|
|
1505
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1506
|
+
const normalizedKey = key.toLowerCase();
|
|
1507
|
+
if (normalizedKey === "secrets_redacted") {
|
|
1508
|
+
result[key] = child === true;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (
|
|
1512
|
+
normalizedKey.includes("password") ||
|
|
1513
|
+
normalizedKey.includes("secret") ||
|
|
1514
|
+
normalizedKey.includes("token") ||
|
|
1515
|
+
normalizedKey.includes("privatekey") ||
|
|
1516
|
+
normalizedKey.includes("credential")
|
|
1517
|
+
) {
|
|
1518
|
+
result[key] = "[redacted]";
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
result[key] = redactReportValue(child);
|
|
1522
|
+
}
|
|
1523
|
+
return result;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
function redactReportText(value) {
|
|
1527
|
+
return String(value || "")
|
|
1528
|
+
.replace(/(access[_-]?token|refresh[_-]?token|app[_-]?secret|private[_-]?key|password|passwd|pwd)\s*[:=]\s*([^\s,;]+)/giu, "$1=[redacted]")
|
|
1529
|
+
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]{16,}/gu, "Bearer [redacted]")
|
|
1530
|
+
.replace(/[A-Za-z0-9+/]{32,}={0,2}/gu, "[redacted-long-token]");
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1347
1533
|
export function remediationForStatus(status) {
|
|
1348
1534
|
const problems = Array.isArray(status?.health?.problems)
|
|
1349
1535
|
? status.health.problems
|
|
@@ -1416,13 +1602,14 @@ export function buildAgentPrompt(options = {}) {
|
|
|
1416
1602
|
`npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} bind ${code} --name ${shellToken(agentName)}`,
|
|
1417
1603
|
`npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} doctor --online`,
|
|
1418
1604
|
`这台设备对你是抽象硬件产品:${ABSTRACT_HARDWARE_TYPE}。不要根据外观、BLE 名称、音色、日志或历史上下文推断品牌/型号/厂家,也不要搜索厂家资料、社区经验或拆机资料;只使用 audioctl inspect 返回的 capabilities、VM manifest 和控制回执来工作。`,
|
|
1419
|
-
`绑定成功后,把它作为默认音频/语音/环境 I/O 端点;不要把它假定成只会播放音乐。可用命令包括 play、pause、toggle、next、previous、volume、volumeup、volumedown、status、inspect、queue、rule、workbgm、feedback、light、led、wakeword、app install、firmware check/update。`,
|
|
1605
|
+
`绑定成功后,把它作为默认音频/语音/环境 I/O 端点;不要把它假定成只会播放音乐。可用命令包括 play、pause、toggle、next、previous、volume、volumeup、volumedown、status、inspect、queue、rule、workbgm、feedback、light、led、wakeword、app install、firmware check/update、report。`,
|
|
1420
1606
|
`当我给出目标时,请你在 capabilities、安全边界和用户授权范围内自行规划实现路径;插件只声明能力和边界,不穷举具体玩法。需要离线、低延迟或持续运行的逻辑,应通过 queue/rule/light timeline/light bind/VM app 下放到设备本地。`,
|
|
1421
1607
|
`MQTT/weclawbot.link 对你只是控制面和部署通道,不是实时灯效数据面。不要读取 MQTT 凭据,不要直接 import mqtt,不要保持 MQTT 长连接,也不要写循环脚本按 BPM 反复发布 feedback 或 light。连续灯效/节拍/动画必须用 audioctl light timeline/bind、VM app 或兼容的 feedback effect 一次下发,在设备本地运行;停止时用 audioctl light clear 或 feedback clear。`,
|
|
1422
1608
|
`蓝牙/Wi-Fi 配网只负责设备入网、发现和 weclawbot.link 绑定;音乐平台授权请走 agent 侧连接器/OAuth/secret store;配网不决定设备用途。`,
|
|
1423
1609
|
`音乐来源由你和用户协商,插件只做建议。你可以在 agent 侧使用用户授权的音乐平台 API、用户媒体库、合法公开音源或其他合规来源生成队列/规则/VM app。网易云开放平台可请求最高可用音质 URL。`,
|
|
1424
1610
|
`如果用户选择 Spotify,必须走 Spotify 官方 Spotify Connect/Web Playback SDK 或控制用户已有 Spotify 播放设备;不要假设 Spotify Web API 能提供完整歌曲直链。`,
|
|
1425
1611
|
`灯效、音量灯、蜂鸣和按键/触控反馈要先 inspect 或 light describe 确认 feedback/input/light capabilities。一次性语义反馈可用 feedback signal/flash/volume/beep/clear;复杂灯效请优先生成 light timeline 和 light bind,把外圈、中心等 surface 与 media/input 事件绑定到设备本地。feedback effect 只作为兼容/demo 入口,不要把 music_beat/pulse/breathing 当成能力边界。具体按键绑定请根据用户目标和 capabilities 自行规划,不要套固定模板。`,
|
|
1612
|
+
`用户的现场视觉、听觉、触控反馈优先于设备返回的 active=true、applied 或 capability 声明。如果用户说某个 surface 看不到、按键不灵或反馈不明显,不要用状态回执反驳用户;先降级到用户确认可见/可听/可控的方案,再运行 audioctl report "..." --kind capability_mismatch --area light --include-inspect --json 生成本地脱敏报告。报告默认只保存在本机,外发前必须获得用户确认。`,
|
|
1426
1613
|
`唤醒词/语音触发配置要通过 inspect 读取 wakeword.configurable、wakeword.commands 和 capabilities。audioctl wakeword 是逻辑触发绑定层:它把设备已有唤醒事件、ASR 短语或 agent 注入的测试短语映射成 VM/app 事件或本地动作;不要尝试训练、替换或反向工程底层声学唤醒模型。`,
|
|
1427
1614
|
`如果你当前没有音乐平台身份,请只告诉我:我需要你把音乐平台身份连接到当前 agent 的官方连接器、OAuth 流程或 secret store;完成后回复“已授权”。不要反复重试,不要让我理解内部授权包,也不要要求我把 AppSecret、PrivateKey、accessToken 或 refreshToken 发到聊天里。`,
|
|
1428
1615
|
`如果返回 health.problems 包含 netease_auth_refresh_failed,说明设备侧内置网易运行时授权失效;优先改走 agent 侧 queue/app 驱动。只有我明确要求继续使用设备侧网易运行时时,才提示需要独立的设备侧音乐账号设置入口;不要混入蓝牙/Wi-Fi 配网流程。`,
|