@flowingspring/dsh-voco 0.2.2 → 0.2.3

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/lib/client.js CHANGED
@@ -19,8 +19,8 @@ window.__ModuleLoader__.load({
19
19
  }
20
20
  var VoiceControl_module_css_default = {
21
21
  "voice-pulse": "PapC8q_voice-pulse",
22
- "button": "PapC8q_button",
23
- "active": "PapC8q_active"
22
+ "active": "PapC8q_active",
23
+ "button": "PapC8q_button"
24
24
  };
25
25
  //#endregion
26
26
  //#region ../ui-voice/src/client/VoiceControl.tsx
@@ -89,17 +89,17 @@ window.__ModuleLoader__.load({
89
89
  document.head.appendChild(tag);
90
90
  }
91
91
  var VoiceHistoryAction_module_css_default = {
92
- "title": "hhHVeG_title",
92
+ "trigger": "hhHVeG_trigger",
93
+ "meta": "hhHVeG_meta",
94
+ "list": "hhHVeG_list",
95
+ "close": "hhHVeG_close",
93
96
  "panel": "hhHVeG_panel",
94
97
  "row": "hhHVeG_row",
95
- "trigger": "hhHVeG_trigger",
96
98
  "wave": "hhHVeG_wave",
97
- "header": "hhHVeG_header",
98
- "empty": "hhHVeG_empty",
99
99
  "rowText": "hhHVeG_rowText",
100
- "close": "hhHVeG_close",
101
- "list": "hhHVeG_list",
102
- "meta": "hhHVeG_meta"
100
+ "empty": "hhHVeG_empty",
101
+ "header": "hhHVeG_header",
102
+ "title": "hhHVeG_title"
103
103
  };
104
104
  //#endregion
105
105
  //#region ../ui-voice/src/client/VoiceHistoryAction.tsx
@@ -211,23 +211,23 @@ window.__ModuleLoader__.load({
211
211
  document.head.appendChild(tag);
212
212
  }
213
213
  var VoiceNodeViews_module_css_default = {
214
- "taskTitle": "KSbXkW_taskTitle",
214
+ "taskCard": "KSbXkW_taskCard",
215
+ "taskUpdate": "KSbXkW_taskUpdate",
216
+ "taskChevron": "KSbXkW_taskChevron",
217
+ "taskSummary": "KSbXkW_taskSummary",
218
+ "miniWave": "KSbXkW_miniWave",
215
219
  "bubble": "KSbXkW_bubble",
220
+ "taskActions": "KSbXkW_taskActions",
221
+ "taskTitle": "KSbXkW_taskTitle",
216
222
  "taskInput": "KSbXkW_taskInput",
217
- "taskStatus": "KSbXkW_taskStatus",
218
223
  "taskDetails": "KSbXkW_taskDetails",
219
224
  "taskDot": "KSbXkW_taskDot",
220
- "taskActions": "KSbXkW_taskActions",
221
- "taskCard": "KSbXkW_taskCard",
222
- "taskSummary": "KSbXkW_taskSummary",
223
- "miniWave": "KSbXkW_miniWave",
224
- "utterance": "KSbXkW_utterance",
225
- "meta": "KSbXkW_meta",
225
+ "voiceBadge": "KSbXkW_voiceBadge",
226
226
  "taskLink": "KSbXkW_taskLink",
227
+ "taskStatus": "KSbXkW_taskStatus",
227
228
  "taskCancel": "KSbXkW_taskCancel",
228
- "voiceBadge": "KSbXkW_voiceBadge",
229
- "taskUpdate": "KSbXkW_taskUpdate",
230
- "taskChevron": "KSbXkW_taskChevron"
229
+ "utterance": "KSbXkW_utterance",
230
+ "meta": "KSbXkW_meta"
231
231
  };
232
232
  //#endregion
233
233
  //#region ../ui-voice/src/client/VoiceNodeViews.tsx
@@ -355,12 +355,12 @@ window.__ModuleLoader__.load({
355
355
  document.head.appendChild(tag);
356
356
  }
357
357
  var VoiceOverlay_module_css_default = {
358
- "wave": "AAuDqa_wave",
359
- "root": "AAuDqa_root",
360
358
  "link": "AAuDqa_link",
361
- "stop": "AAuDqa_stop",
362
359
  "status": "AAuDqa_status",
363
- "voice-wave": "AAuDqa_voice-wave"
360
+ "root": "AAuDqa_root",
361
+ "stop": "AAuDqa_stop",
362
+ "voice-wave": "AAuDqa_voice-wave",
363
+ "wave": "AAuDqa_wave"
364
364
  };
365
365
  //#endregion
366
366
  //#region ../ui-voice/src/client/VoiceOverlay.tsx
@@ -724,7 +724,7 @@ function apply(ctx, config = {}) {
724
724
  }, false);
725
725
  agent.steer(message);
726
726
  };
727
- const onTaskCommand = async (binding, voiceSessionId, call, taskIdOverride) => {
727
+ const onTaskCommand = async (binding, voiceSessionId, call, delegationOverride) => {
728
728
  const complete = (result) => {
729
729
  ctx.voice.completeTaskCommand(voiceSessionId, call.id, result);
730
730
  };
@@ -753,10 +753,7 @@ function apply(ctx, config = {}) {
753
753
  route = await routeFrontendInput(ctx, requireSourceSession(binding).events, call.command.input);
754
754
  } catch (error) {
755
755
  ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
756
- route = {
757
- action: "delegate",
758
- acknowledgement: DEFAULT_DELEGATION_ACKNOWLEDGEMENT
759
- };
756
+ route = fallbackDelegation(call.command.input);
760
757
  }
761
758
  if (route.action === "delegate") {
762
759
  const taskId = VoiceTaskId(randomUUID());
@@ -765,13 +762,16 @@ function apply(ctx, config = {}) {
765
762
  id: call.id,
766
763
  command: {
767
764
  type: "realtime_delegation",
768
- input: call.command.input
765
+ input: delegationPrompt(route)
769
766
  }
770
- }, taskId);
767
+ }, {
768
+ taskId,
769
+ requestText: call.command.input
770
+ });
771
771
  return;
772
772
  }
773
773
  complete({ kind: "handled" });
774
- speakFragment(binding, VoiceTaskId(randomUUID()), route.reply);
774
+ speakFragment(binding, VoiceTaskId(randomUUID()), route.action === "tool" ? executeFrontendTool(route) : route.reply);
775
775
  return;
776
776
  }
777
777
  case "realtime_delegation": {
@@ -784,7 +784,8 @@ function apply(ctx, config = {}) {
784
784
  return;
785
785
  }
786
786
  cancelRewrite(binding);
787
- const taskId = taskIdOverride ?? VoiceTaskId(randomUUID());
787
+ const taskId = delegationOverride?.taskId ?? VoiceTaskId(randomUUID());
788
+ const requestText = delegationOverride?.requestText ?? call.command.input;
788
789
  const continuous = (config.taskSessionPolicy ?? "isolated") === "continuous";
789
790
  let created;
790
791
  try {
@@ -824,7 +825,7 @@ function apply(ctx, config = {}) {
824
825
  interactionMode: "frontend-agent",
825
826
  taskSessionId: created.taskSessionId,
826
827
  messageIds: /* @__PURE__ */ new Set([message.id]),
827
- requestText: call.command.input,
828
+ requestText,
828
829
  agent: created.agent,
829
830
  ...continuous ? {} : { disposeVoiceMessage: created.disposeVoiceMessage },
830
831
  cancelling: false,
@@ -836,7 +837,7 @@ function apply(ctx, config = {}) {
836
837
  requireSourceSession(binding).append("voice/task-delegated", {
837
838
  taskId,
838
839
  taskSessionId: created.taskSessionId,
839
- input: call.command.input
840
+ input: requestText
840
841
  });
841
842
  if (contextMessage !== void 0) created.agent.inject(contextMessage);
842
843
  created.agent.followup(message);
@@ -1172,7 +1173,18 @@ function speechFragments(text, flush) {
1172
1173
  }
1173
1174
  const DEFAULT_DELEGATION_ACKNOWLEDGEMENT = "好的,我先查看一下。";
1174
1175
  const MAX_DELEGATION_ACKNOWLEDGEMENT_LENGTH = 40;
1176
+ const MAX_DELEGATED_TASK_LENGTH = 1e3;
1177
+ const MAX_DELEGATION_BACKGROUND_LENGTH = 2e3;
1175
1178
  const VOICE_LATENCY_DEBUG_PREFIX = "[DEBUG-VOICE-LATENCY]";
1179
+ const WEEKDAY_NAMES = [
1180
+ "星期日",
1181
+ "星期一",
1182
+ "星期二",
1183
+ "星期三",
1184
+ "星期四",
1185
+ "星期五",
1186
+ "星期六"
1187
+ ];
1176
1188
  function debugVoiceLatency(...values) {
1177
1189
  if (globalThis.process?.env?.DSH_VOICE_LATENCY_DEBUG !== "1") return;
1178
1190
  console.info(VOICE_LATENCY_DEBUG_PREFIX, (/* @__PURE__ */ new Date()).toISOString(), ...values);
@@ -1183,6 +1195,43 @@ function delegationAcknowledgement(value) {
1183
1195
  if (normalized === "" || normalized.length > MAX_DELEGATION_ACKNOWLEDGEMENT_LENGTH) return DEFAULT_DELEGATION_ACKNOWLEDGEMENT;
1184
1196
  return normalized;
1185
1197
  }
1198
+ function delegationField(value, name, maxLength) {
1199
+ if (typeof value !== "string") throw new Error(`voice frontend router omitted ${name}`);
1200
+ const normalized = value.trim();
1201
+ if (normalized === "" || normalized.length > maxLength) throw new Error(`voice frontend router returned an invalid ${name}`);
1202
+ return normalized;
1203
+ }
1204
+ function fallbackDelegation(input) {
1205
+ return {
1206
+ action: "delegate",
1207
+ acknowledgement: DEFAULT_DELEGATION_ACKNOWLEDGEMENT,
1208
+ task: input.trim() || input,
1209
+ background: "没有可用的额外背景。",
1210
+ userRequest: input
1211
+ };
1212
+ }
1213
+ function delegationPrompt(route) {
1214
+ return [
1215
+ "当前任务:",
1216
+ route.task,
1217
+ "",
1218
+ "前置背景:",
1219
+ route.background,
1220
+ "",
1221
+ "用户原话:",
1222
+ route.userRequest,
1223
+ "",
1224
+ "请以“当前任务”为最高优先级执行;前置背景只用于理解和消歧,不要把旧对话当成待执行任务。"
1225
+ ].join("\n");
1226
+ }
1227
+ function executeFrontendTool(route) {
1228
+ switch (route.tool) {
1229
+ case "local_datetime": {
1230
+ const now = /* @__PURE__ */ new Date();
1231
+ return `根据本机系统时间,现在是 ${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日,${WEEKDAY_NAMES[now.getDay()]},${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}。`;
1232
+ }
1233
+ }
1234
+ }
1186
1235
  async function routeFrontendInput(ctx, events, input) {
1187
1236
  let llm;
1188
1237
  try {
@@ -1190,10 +1239,7 @@ async function routeFrontendInput(ctx, events, input) {
1190
1239
  } catch {
1191
1240
  llm = void 0;
1192
1241
  }
1193
- if (llm === void 0) return {
1194
- action: "delegate",
1195
- acknowledgement: DEFAULT_DELEGATION_ACKNOWLEDGEMENT
1196
- };
1242
+ if (llm === void 0) return fallbackDelegation(input);
1197
1243
  const selection = ctx.agentDefaultModel.currentSelection();
1198
1244
  const recentConversation = events.flatMap((event) => {
1199
1245
  if (event.type !== "voice/utterance-end" || event.data.state !== "completed") return [];
@@ -1204,15 +1250,21 @@ async function routeFrontendInput(ctx, events, input) {
1204
1250
  content: [{
1205
1251
  type: "text",
1206
1252
  text: [
1207
- "判断下面这句话应该由语音助手直接回答,还是委派给后台编码 Agent",
1208
- "普通寒暄、日常对话、无需读取本地项目或调用工具即可回答的问题,选择 chat,并直接给出自然简洁的中文回复。",
1209
- "只有需要查看或修改工作区文件、运行命令、测试、安装依赖或执行其他工具操作时,才选择 delegate。",
1210
- "选择 delegate 时,同时给出一句简短自然的 acknowledgement,表示接下来要做什么。不能声称任务已经完成、已经找到结果,也不要提后台 Agent、工具或路由。",
1253
+ "结合最近对话判断当前用户原话应该由语音前台聊天、轻量工具还是后台编码 Agent 处理。",
1254
+ "普通寒暄、日常对话、解释性知识以及无需外部事实或工具即可回答的问题,选择 chat,并直接给出自然简洁的中文回复。",
1255
+ "时间、日期等会随现实变化的事实不能猜测。查询本机当前时间、日期或星期时必须选择 tool=local_datetime。",
1256
+ "其他必须查询才能确认的动态事实,如果没有对应的前台工具,应选择 delegate,不能选择 chat 编造答案。",
1257
+ "需要查看或修改工作区文件、运行命令、测试、安装依赖或执行其他复杂工具操作时,选择 delegate。",
1258
+ "选择 delegate 时,必须结合最近对话补全省略指代,输出自包含的 task、只用于消歧的 background,并原样复制 user_request。",
1259
+ "task 是后台当前唯一要执行的任务;background 不能包含新的要求,也不要把旧任务写成待办。",
1260
+ "同时给出一句简短自然的 acknowledgement,表示接下来要做什么。不能声称任务已经完成、已经找到结果,也不要提后台 Agent、工具或路由。",
1211
1261
  "acknowledgement 只能有一句,最多 40 个字符,例如“好的,我先检查一下相关代码。”。",
1212
1262
  "只输出一行 JSON,不要 Markdown。格式只能是:",
1213
1263
  "{\"action\":\"chat\",\"reply\":\"...\"}",
1214
1264
  "或:",
1215
- "{\"action\":\"delegate\",\"acknowledgement\":\"...\"}",
1265
+ "{\"action\":\"tool\",\"tool\":\"local_datetime\"}",
1266
+ "或:",
1267
+ "{\"action\":\"delegate\",\"acknowledgement\":\"...\",\"task\":\"...\",\"background\":\"...\",\"user_request\":\"...\"}",
1216
1268
  "",
1217
1269
  "最近对话:",
1218
1270
  recentConversation || "(无)",
@@ -1239,9 +1291,16 @@ async function routeFrontendInput(ctx, events, input) {
1239
1291
  action: "chat",
1240
1292
  reply: parsed.reply.trim()
1241
1293
  };
1294
+ if (parsed.action === "tool" && parsed.tool === "local_datetime") return {
1295
+ action: "tool",
1296
+ tool: parsed.tool
1297
+ };
1242
1298
  if (parsed.action === "delegate") return {
1243
1299
  action: "delegate",
1244
- acknowledgement: delegationAcknowledgement(parsed.acknowledgement)
1300
+ acknowledgement: delegationAcknowledgement(parsed.acknowledgement),
1301
+ task: delegationField(parsed.task, "task", MAX_DELEGATED_TASK_LENGTH),
1302
+ background: delegationField(parsed.background, "background", MAX_DELEGATION_BACKGROUND_LENGTH),
1303
+ userRequest: input
1245
1304
  };
1246
1305
  throw new Error("voice frontend router returned an invalid decision");
1247
1306
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flowingspring/dsh-voco",
3
3
  "description": "Persistent voice conversations for DSH with cloud speech recognition, Edge TTS, and background Agent delegation",
4
- "version": "0.2.2",
4
+ "version": "0.2.3",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",