@flowingspring/dsh-voco 0.3.5 → 0.3.7

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.
@@ -5720,6 +5720,31 @@ function apply(ctx, config = {}) {
5720
5720
  ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
5721
5721
  });
5722
5722
  };
5723
+ const submitTaskMemory = (task) => {
5724
+ if (task.interactionMode !== "frontend-agent") return;
5725
+ const memory = optionalWorkspaceMemory(ctx);
5726
+ if (memory === void 0) return;
5727
+ const request = task.requestText.trim();
5728
+ const result = (task.completionDetail ?? task.lastAssistantMessage?.text ?? "").trim();
5729
+ const prefix = `voice-task:${String(task.id)}`;
5730
+ const messages = [...request === "" ? [] : [{
5731
+ id: `${prefix}:request`,
5732
+ role: "user",
5733
+ text: request
5734
+ }], ...result === "" ? [] : [{
5735
+ id: `${prefix}:result`,
5736
+ role: "assistant",
5737
+ text: result
5738
+ }]];
5739
+ if (messages.length === 0) return;
5740
+ memory.checkpoint({
5741
+ sessionId: task.taskSessionId,
5742
+ messages,
5743
+ reason: "task-end"
5744
+ }).catch((error) => {
5745
+ ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
5746
+ });
5747
+ };
5723
5748
  const markVoiceTurn = (binding) => {
5724
5749
  if (binding.interactionMode !== "frontend-agent" || binding.voiceTurnMarked) return;
5725
5750
  const session = requireSourceSession(binding);
@@ -6325,12 +6350,14 @@ function apply(ctx, config = {}) {
6325
6350
  }
6326
6351
  const deliverAssistantSpeech = !hasLlm || !ctx.voice.supportsSpeechText(binding.voiceSessionId);
6327
6352
  enqueue(binding, () => {
6353
+ const terminalTask = event.type === "turn/end" && event.data.reason.kind === "completed" && binding.active?.taskTurn === event.data.turn ? binding.active : void 0;
6328
6354
  if (observeSessionEvent(binding, event, append, config, (error) => {
6329
6355
  ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
6330
6356
  }, (task, text) => {
6331
6357
  if (deliverAssistantSpeech) ctx.voice.requestResponse(binding.voiceSessionId, { kind: "automatic" });
6332
6358
  else rewriteAndSpeak(binding, task.id, task.requestText, text);
6333
6359
  }, deliverAssistantSpeech)) {
6360
+ if (terminalTask !== void 0) submitTaskMemory(terminalTask);
6334
6361
  if ((config.taskSessionPolicy ?? "isolated") === "isolated") taskBindings.delete(session.id);
6335
6362
  }
6336
6363
  });
@@ -5,6 +5,8 @@ import { existsSync } from "node:fs";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import { loadEnvFile } from "node:process";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
9
+ import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
8
10
  import { mkdtemp, readFile, rm } from "node:fs/promises";
9
11
  import { tmpdir } from "node:os";
10
12
  import { EdgeTTS } from "node-edge-tts";
@@ -539,8 +541,10 @@ function errorMessage(error) {
539
541
  /** Silence-gated SiliconFlow cloud ASR with Edge TTS. */
540
542
  const name = "voice-local";
541
543
  const inject = ["voice"];
544
+ const VOICE_LOCAL_SETTINGS_NAMESPACE = settingsNamespace("voice-local");
545
+ const SILICONFLOW_API_KEY_REF = credentialRef("SILICONFLOW_API_KEY");
542
546
  const Config = z.object({
543
- apiKey: z.string(),
547
+ apiKey: z.string().role("secret"),
544
548
  endpoint: z.string().default("https://api.siliconflow.cn/v1/audio/transcriptions"),
545
549
  model: z.string().default("XingChenAGI/XingChenASR-V3.2-Ultra"),
546
550
  interactionMode: z.union(["speech-shell", "frontend-agent"]).default("speech-shell"),
@@ -575,26 +579,34 @@ function loadProjectEnv() {
575
579
  }
576
580
  }
577
581
  function apply(ctx, config = {}) {
578
- loadProjectEnv();
582
+ if (ctx.get("credentials") === void 0) loadProjectEnv();
583
+ let current = () => config;
584
+ installSettingsSection(ctx, VOICE_LOCAL_SETTINGS_NAMESPACE, Config, config, {
585
+ setSource: (source) => {
586
+ current = source;
587
+ },
588
+ onChange: () => {}
589
+ });
579
590
  return ctx.voice.registerProvider({
580
591
  id: "local",
581
592
  available: () => true,
582
593
  connect: async ({ voiceSessionId, emit }) => {
594
+ const active = current();
583
595
  const session = new LocalSession(new NodeSpeechBackend({
584
- apiKey: config.apiKey ?? process.env.SILICONFLOW_API_KEY ?? "",
585
- endpoint: config.endpoint ?? "https://api.siliconflow.cn/v1/audio/transcriptions",
586
- model: config.model ?? "XingChenAGI/XingChenASR-V3.2-Ultra",
587
- requestTimeoutMs: config.requestTimeoutMs ?? 6e4,
588
- inputSampleRate: config.inputSampleRate ?? 16e3,
589
- outputSampleRate: config.outputSampleRate ?? 48e3,
590
- ttsRate: config.ttsRate ?? "+20%",
591
- silenceDurationMs: config.silenceDurationMs ?? 1500,
592
- speechThreshold: config.speechThreshold ?? .015,
593
- minSpeechDurationMs: config.minSpeechDurationMs ?? 250,
594
- preRollMs: config.preRollMs ?? 400,
595
- trailingSilenceMs: config.trailingSilenceMs ?? 200,
596
- maxUtteranceMs: config.maxUtteranceMs ?? 6e4
597
- }), emit, voiceSessionId, config.interactionMode ?? "speech-shell");
596
+ apiKey: await resolveSiliconFlowApiKey(ctx, active),
597
+ endpoint: active.endpoint ?? "https://api.siliconflow.cn/v1/audio/transcriptions",
598
+ model: active.model ?? "XingChenAGI/XingChenASR-V3.2-Ultra",
599
+ requestTimeoutMs: active.requestTimeoutMs ?? 6e4,
600
+ inputSampleRate: active.inputSampleRate ?? 16e3,
601
+ outputSampleRate: active.outputSampleRate ?? 48e3,
602
+ ttsRate: active.ttsRate ?? "+20%",
603
+ silenceDurationMs: active.silenceDurationMs ?? 1500,
604
+ speechThreshold: active.speechThreshold ?? .015,
605
+ minSpeechDurationMs: active.minSpeechDurationMs ?? 250,
606
+ preRollMs: active.preRollMs ?? 400,
607
+ trailingSilenceMs: active.trailingSilenceMs ?? 200,
608
+ maxUtteranceMs: active.maxUtteranceMs ?? 6e4
609
+ }), emit, voiceSessionId, active.interactionMode ?? "speech-shell");
598
610
  try {
599
611
  await session.start();
600
612
  return session;
@@ -605,5 +617,12 @@ function apply(ctx, config = {}) {
605
617
  }
606
618
  });
607
619
  }
620
+ /** Resolve the key per connection so a settings-page write reaches the next attempt. */
621
+ async function resolveSiliconFlowApiKey(ctx, config) {
622
+ if (config.apiKey !== void 0 && config.apiKey.trim() !== "") return config.apiKey;
623
+ const credentials = ctx.get("credentials");
624
+ if (credentials !== void 0) return (await credentials.resolve(SILICONFLOW_API_KEY_REF))?.value ?? "";
625
+ return process.env.SILICONFLOW_API_KEY ?? "";
626
+ }
608
627
  //#endregion
609
- export { Config, LocalSession, NodeSpeechBackend, SiliconFlowAsr, apply, inject, name, pcm16MonoWav };
628
+ export { Config, LocalSession, NodeSpeechBackend, SILICONFLOW_API_KEY_REF, SiliconFlowAsr, VOICE_LOCAL_SETTINGS_NAMESPACE, apply, inject, name, pcm16MonoWav, resolveSiliconFlowApiKey };
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.3.5",
4
+ "version": "0.3.7",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",
@@ -65,6 +65,8 @@
65
65
  "@deepseek-ai/dsh-client-connection",
66
66
  "@deepseek-ai/dsh-client-locale",
67
67
  "@deepseek-ai/dsh-client-runtime",
68
+ "@deepseek-ai/dsh-client-ui-settings",
69
+ "@deepseek-ai/dsh-client-ui-settings-plugins",
68
70
  "@deepseek-ai/dsh-client-ui-conversation",
69
71
  "@deepseek-ai/dsh-client-ui-layout",
70
72
  "@deepseek-ai/dsh-client-ui-sidebar"
@@ -86,15 +88,19 @@
86
88
  "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
87
89
  "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.1",
88
90
  "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.1",
91
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
92
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.1-rc.2",
89
93
  "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.1",
90
94
  "@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.1",
91
95
  "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.1-rc.1",
92
96
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.1",
93
97
  "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
94
98
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
99
+ "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
95
100
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
96
101
  "@deepseek-ai/dsh-session": "^0.1.1-rc.1",
97
102
  "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.1",
103
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
98
104
  "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
99
105
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
100
106
  "@deepseek-ai/dsh-workspace": "^0.1.1-rc.1",
@@ -109,15 +115,19 @@
109
115
  "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
110
116
  "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.1",
111
117
  "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.1",
118
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
119
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.1-rc.2",
112
120
  "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.1",
113
121
  "@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.1",
114
122
  "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.1-rc.1",
115
123
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.1",
116
124
  "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
117
125
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
126
+ "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
118
127
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
119
128
  "@deepseek-ai/dsh-session": "^0.1.1-rc.1",
120
129
  "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.1",
130
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
121
131
  "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
122
132
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
123
133
  "@deepseek-ai/dsh-workspace": "^0.1.1-rc.1",