Skip to content

Commit f8f0c46

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/app-control-plane-commands
2 parents e65954a + 1a17ac8 commit f8f0c46

29 files changed

Lines changed: 1392 additions & 175 deletions

‎docs/agents/error-hint-change.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ runtime/create-cli.ts: 每次调用创建 reporter,统一捕获初始化/解
4343
↓
4444
runtime/error-handler.ts:
4545
- 服务端错误(BailianError(GENERAL)) → text 直接打 message
46-
- 内部 AUTH/USAGE/NETWORK/TIMEOUT → 走 errorHint(只 AUTH 还有增强)
46+
- 内部 AUTH/USAGE/NETWORK/TIMEOUT → 走 errorHint(AUTH 增强;另可对已知可恢复服务端错误追加 hint)
4747
- TypeError("fetch failed") → 读 err.cause.code 翻成 NETWORK
4848
- Node fs errno → 翻成 GENERAL + errno hint
4949
- reporter:JSON 错误、warning、debug 各占 stderr 一行,无空行;stdout 仅保留最终业务结果
@@ -104,8 +104,13 @@ process.exit(err.exitCode)
104104

105105
### B. runtime 增强(`errorHint`)
106106

107-
- [ ] `packages/runtime/src/error-handler.ts:errorHint`:**当前只为 internal AUTH 增强**(因为 resolver / authStage 等内部位置会发 AUTH)
107+
- [ ] `packages/runtime/src/error-handler.ts:errorHint`:**主要为 internal AUTH 增强**(resolver / authStage 等内部位置会发 AUTH)
108+
- [ ] **允许的有限扩展**:对已知可恢复的服务端错误,**追加**恢复 hint(保留原 message / exitCode)。当前仅 `AllocationQuota.FreeTierOnly`(及同义英文 message 短语)。message / apiCode 匹配**只用于追加**,不参与分类、归并或替换 message(勿与下方反面 case 3 的关键词归并混淆)
109+
- [ ] 追加时若已有 hint(如 video task id),拼接并对**完整恢复文本**去重;不要 `hint` 非空就跳过,也不要用 `"usage freetier"` 子串匹配
110+
- [ ] 命令层不得自行拼 `usage free` / `usage freetier` 恢复指引;该指引只由 runtime `errorHint` 生成
111+
- [ ] 渲染产品命令前须经能力门控(`hasCommand(path)`),不要对 `kscli` 等缺命令的产品硬拼 `usage freetier`
108112
- [ ] 命令名使用 `commandPrefix`,不要硬编码 `bl`
113+
- [ ] 恢复命令经 `recoveryCommand` 渲染;Profile 名用 `resolveRecoveryConfigName` / `Settings.recoveryConfigName`(显式 `--config default` 保留,隐式 default 不加)。API Key capability fallback 到 `default` 时,runtime 须把恢复 hint 的 profile 改写为 `default`(当前命令自身的控制台会话 hint `consoleRecoveryHint` 不改)。理由:恢复命令要跟随实际报错的 API Key 所属 Profile——此时报错(如 `AllocationQuota.FreeTierOnly`)来自 `default` 的 API Key 所属账号,免费额度恢复虽是 Console 命令,也须钉 `default`;钉回原命名 Profile 可能用另一账号的 Console token 静默改错账号的计费设置。`default` 缺 Console token 时报 AUTH 并给出 `auth login --console --config default`,可恢复且账号正确。不要拆成 api-key / console 两套 Profile 名
109114
- [ ] URL 必须是 `import { API_KEY_PAGE } from "./urls.ts"`
110115

111116
### C. command / runtime 直接抛错

‎docs/agents/output-contract.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ OpenAgentPack SDK 没有 logger 注入接口:仅在 SDK 调用上下文内,
8282
- [ ] JSON 诊断每条一行;stdout 为单个业务 JSON 文档,stderr 单独解析
8383
- [ ] speech synthesize 的原始音频仅在 text 流式模式下直接写 stdout;JSON 流式需 --out 文件并返回元数据,未指定文件时在请求和文件操作前报用法错误;dry-run 仍允许 JSON 预览。--list-voices 的 JSON 返回音色条目与已有文档链接,内置列表未知时 voices/total 为 null
8484
- [ ] speech recognize 的同步 JSON 返回完整服务响应;异步完成后返回原始任务响应并附加 transcriptions 数组,保留失败子任务和 request_id。text 保留转写、说话人和批量分隔;--out 仍保存原有转写文件结构,下载及写盘失败不提前输出最终结果;部分子任务失败不改变原有退出码
85-
- [ ] usage freetier 的启用 JSON 保留原始响应数组;停用 JSON 返回 `{ processed: [{ model, result }], skipped: [model] }`,跳过项仍不发起停用请求。失败与超时用 warning 提示,保留原有退出策略,不把部分失败丢出结果。
85+
- [ ] usage freetier 的启用 JSON 保留原始响应数组(含零模型时的 `[]`);停用 JSON 返回 `{ processed: [{ model, result }], skipped: [model] }`(含空对象),跳过项仍不发起停用请求。按模型串行修改:业务失败 / 超时 / 未在 successModels(或 successCount)确认成功时 fail-fast,不再请求后续模型;HTTP/网络异常先输出已确认进度并用 warning 记录进度,再原样抛出原始错误(不二次包装,保留 cause.code / ERROR_STACK)。失败与超时先用 warning 提示并保留业务结果,再抛摘要:仅轮询超时 → `TIMEOUT(5)`(文案用「超时未确认」);服务端拒绝 / 未确认成功 → `GENERAL(1)`;成功文案(Enabled / Disabled)仅在该模型操作成功时输出
8686
- [ ] Token Plan 创建密钥的完整响应和一次性明文仍属于业务结果,quiet 不裁剪;不得通过 warning/debug 泄露明文。
8787
- [ ] 原始流与原生交互仅限上表;Command Pack API 1 由宿主缓存并提交一次结果,不再直接转发 stdout
8888
- [ ] 每个调用使用独立 reporter,不共享结果状态、不替换全局 stdout/stderr;SDK console 桥接只能读取调用上下文,不缓存上一个 host

‎docs/agents/telemetry-change.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,16 @@ source-config 只用于百炼 / DashScope API 侧消费,不发送到通用网
116116

117117
`page` 仅记录有限数值页码;Webhook 等命令使用的不透明字符串游标不得进入本地日志或远端 AEM,即使字符串内容看起来是数字。
118118

119-
事件同时写入两处:
119+
事件写入两处:
120120

121-
1. 本地 `~/.bailian/telemetry.jsonl`:权限 `0600`,超过 5 MB 后重建
121+
1. 本地 `~/.bailian/telemetry.jsonl`:权限 `0600`,超过 5 MB 后重建;每行附带 `uid`(只读已有文件,不创建)
122122
2. AEM:`pid=bailian-cli-node`,源码运行自动使用 `env=dev`,npm 安装或编译二进制使用 `env=prod`
123123

124-
底层 Node tracker 还会附加公共设备字段:OS 类型/版本、Node 应用名与版本、平台,以及由本机网络标识计算的 MD5 `device_id`。
124+
旁路状态:`~/.bailian/telemetry-uid`(遵循 `BAILIAN_CONFIG_DIR`)由 `ensureUid` 在首次远端上报时生成,不是事件落盘目标。
125+
126+
底层 Node tracker 还会附加公共设备字段:OS 类型/版本、Node 应用名与版本、平台,以及内置的 `device_id`(本机网卡 MAC 的 MD5,保持不变)。稳定匿名 install id 写入 AEM 的 `uid`:来自 `~/.bailian/telemetry-uid`,首次远端上报时生成随机 UUID v4,以 0600 权限原子发布;生成失败时不上报 `uid`。遥测关闭时不生成该文件。本地 `telemetry.jsonl` 每行附带同名 `uid`。注意:这不是账号用户 ID,只是上报到 AEM `uid` 字段的匿名标识;与 Tracker 内置 `device_id` 无关。
127+
128+
`ensureUid` 无锁(对齐 qwen-code `InstallationManager`):稳态只读;缺失或损坏时同目录 temp 写满后 `rename` 发布(内容原子替换;并发首次创建时后写覆盖先写,磁盘最终为一个 UUID)。失败则省略 `uid`,不阻塞业务退出。
125129

126130
从 2.8.2 起,每条命令事件通过 `commandPrefix` 显式记录调用入口,三种入口仍共用 `pid=bailian-cli-node`:
127131

@@ -180,6 +184,8 @@ AEM 映射:
180184
- [ ] 成功与失败均覆盖;遥测异常必须静默且不改变业务退出码
181185
- [ ] runtime 本地语义错误应记录 `exitCode`;新增字段默认随 AEM `ext` 上报,无需占用新的 `c1`—`c5`
182186
- [ ] 检查 `DO_NOT_TRACK=1` 与 `telemetry: false` 两个关闭入口
187+
- [ ] `remoteSink` 从调用到 `remoteSendEvent` 入队之间不得出现 `await`(flush 只排空已入队事件)
188+
- [ ] 测试中调用 `remoteSink` 的用例必须使用 `enterIsolatedHome()`,避免写入真实配置目录
183189
- [ ] 遥测发送的 DNS/HTTP/响应体必须受可取消预算控制;flush 超时必须取消在途操作,不能只停止等待,也不能修改全局 DNS/dispatcher 或强制退出宿主
184190
- [ ] 退出生命周期变更保留真实子进程回归:挂起响应头/响应体、DNS 预查询和关闭遥测;检查实际退出及进程仍存活时的连接释放,不以 flush Promise 返回代替验收
185191
- [ ] 错误字段不得额外拼接 token、请求体、prompt 或本地路径
@@ -190,7 +196,7 @@ AEM 映射:
190196
rg -n "trackingHeaders|x-dashscope-source-config|User-Agent" packages --glob '*.ts'
191197
rg -n "trackCommandExecution|PARAM_ALLOWLIST|buildRemoteAemOptions|readCaller" packages/core packages/runtime --glob '*.ts'
192198
vp check
193-
vp test packages/core/tests packages/runtime/tests/telemetry-stage.test.ts packages/commands/tests/e2e/auth.e2e.test.ts
199+
vp test packages/core/tests/telemetry-uid.test.ts packages/core/tests/telemetry-sink.test.ts packages/core/tests/telemetry-tracker.test.ts packages/core/tests/telemetry-send.test.ts packages/runtime/tests/telemetry-stage.test.ts packages/commands/tests/e2e/auth.e2e.test.ts
194200
```
195201

196202
## 常见漏点

‎packages/commands/src/commands/config/shared.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export const UI_MODEL_DEFAULTS: Record<string, string> = {
8080
default_reference_to_video_model: "wan3.0-video",
8181
default_speech_model: "cosyvoice-v3-flash",
8282
default_speech_recognition_model: "fun-asr",
83-
default_omni_model: "qwen3.5-omni-plus",
83+
default_omni_model: "qwen3.8-omni-flash",
8484
};
8585

8686
/** One selectable model plus a short note on where the CLI uses it. */
@@ -131,7 +131,8 @@ export const UI_MODEL_CATALOG: Record<string, ModelOption[]> = {
131131
{ id: "qwen-audio-3.0-asr-flash", role: "Token Plan speech/recognize (ASR)" },
132132
],
133133
default_omni_model: [
134-
{ id: "qwen3.5-omni-plus", role: "omni/chat default" },
134+
{ id: "qwen3.8-omni-flash", role: "omni/chat default · --text-only" },
135+
{ id: "qwen3.5-omni-plus", role: "omni/chat · audio output path (without --text-only)" },
135136
{ id: "qwen3-vl-plus", role: "vision/describe · multimodal input" },
136137
],
137138
};

‎packages/commands/src/commands/knowledge/chunk-delete.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ export default defineCommand({
103103
deletedCount += batchIds.length;
104104
completedBatches.push(batchIds);
105105
} catch (error) {
106-
const original = normalizeError(error, formatCommand(ctx.identity), ctx.localize);
106+
const original = normalizeError(
107+
error,
108+
formatCommand(ctx.identity),
109+
ctx.localize,
110+
ctx.settings.recoveryConfigName,
111+
);
107112
const failedBatch = completedBatches.length + 1;
108113
const progress = [
109114
ctx.localize({

‎packages/commands/src/commands/omni/chat.ts‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,10 @@ export default defineCommand({
141141
type: "string",
142142
valueHint: "<model>",
143143
description: {
144-
"en-US": "Model ID (default: qwen3.5-omni-plus)",
145-
"zh-CN": "模型 ID(默认:qwen3.5-omni-plus)",
144+
"en-US":
145+
"Model ID (default: qwen3.8-omni-flash; when --model is omitted and --text-only is absent, auto-switches to qwen3.5-omni-plus for audio output)",
146+
"zh-CN":
147+
"模型 ID(默认:qwen3.8-omni-flash;省略 --model 且未带 --text-only 时,自动切换为支持音频输出的 qwen3.5-omni-plus)",
146148
},
147149
},
148150
system: {
@@ -275,12 +277,26 @@ export default defineCommand({
275277
// --- Parse messages ---
276278
const userMessages = flags.message ?? [];
277279

278-
const model = flags.model || settings.defaultOmniModel || "qwen3.5-omni-plus";
280+
// Default to flash; switch to plus when --model is omitted and --text-only is absent (flash has no audio out).
281+
const textOnly = flags.textOnly === true;
282+
let model = flags.model || settings.defaultOmniModel || "qwen3.8-omni-flash";
283+
if (!flags.model && !textOnly && model === "qwen3.8-omni-flash") {
284+
model = "qwen3.5-omni-plus";
285+
}
279286
const voice = flags.voice || "Tina";
280287
const audioFormat = flags.audioFormat || "wav";
281-
const textOnly = flags.textOnly === true;
282288
const format = settings.output;
283289

290+
// Explicit flash + audio path will be rejected by the server; warn locally without overriding --model.
291+
if (flags.model === "qwen3.8-omni-flash" && !textOnly) {
292+
ctx.output.warning({
293+
"en-US":
294+
"qwen3.8-omni-flash does not support audio output. Add --text-only, or omit --model to auto-switch to qwen3.5-omni-plus.",
295+
"zh-CN":
296+
"qwen3.8-omni-flash 不支持音频输出。请添加 --text-only,或省略 --model 以自动切换为 qwen3.5-omni-plus。",
297+
});
298+
}
299+
284300
// --- Build messages array ---
285301
const allMessages: ChatMessage[] = [];
286302
if (flags.system) {

0 commit comments

Comments
 (0)