dsh-agentic-pet 0.4.0 → 0.4.2

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/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { c as readAffectionRaw, d as toolCallsFile, f as writeAffectionRaw, i as installPetDriver, l as readJsonl, n as installPetService, p as writePetName, s as eventsFile, u as readPetName } from "./service-D-b8UzM2.js";
1
+ import { c as globalFilePath, d as readJsonl, f as readPetName, h as writePetName, i as installPetDriver, l as readAffectionRaw, m as writeGlobalJson, n as installPetService, p as writeAffectionRaw, r as petHome, s as eventsFile, u as readGlobalJson } from "./service-BjTawwUn.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { spawn } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
- import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
6
+ import { mkdir, readFile, rm, unlink, writeFile } from "node:fs/promises";
7
7
  import { homedir } from "node:os";
8
8
  import { dirname, extname, join, normalize, resolve, sep } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
@@ -38,6 +38,7 @@ const DEFAULT_SETTINGS = {
38
38
  touchReplyCooldownSeconds: 30,
39
39
  boundSessions: {}
40
40
  },
41
+ tts: { enabled: false },
41
42
  debug: { cdp: false }
42
43
  };
43
44
  const EMPTY_LEDGER = {
@@ -141,10 +142,30 @@ function mergeSettings(current, patch) {
141
142
  const d = patchDebug;
142
143
  if (typeof d.cdp === "boolean") debug.cdp = d.cdp;
143
144
  }
145
+ const tts = { ...current.tts };
146
+ const patchTts = patch.tts;
147
+ if (typeof patchTts === "object" && patchTts !== null) {
148
+ const t = patchTts;
149
+ if (typeof t.enabled === "boolean") tts.enabled = t.enabled;
150
+ const setText = (key) => {
151
+ const value = t[key];
152
+ if (typeof value !== "string") return;
153
+ const trimmed = value.trim();
154
+ if (trimmed === "") Reflect.deleteProperty(tts, key);
155
+ else tts[key] = trimmed;
156
+ };
157
+ setText("apiKey");
158
+ setText("voiceId");
159
+ setText("modelId");
160
+ setText("languageCode");
161
+ if (typeof t.speed === "number" && Number.isFinite(t.speed)) tts.speed = Math.min(2, Math.max(.5, t.speed));
162
+ if (typeof t.volume === "number" && Number.isFinite(t.volume)) tts.volume = Math.min(2, Math.max(.01, t.volume));
163
+ }
144
164
  return {
145
165
  ...current,
146
166
  proactivity,
147
167
  innerVoice,
168
+ tts,
148
169
  debug
149
170
  };
150
171
  }
@@ -168,8 +189,7 @@ function openPathInFileManager(path) {
168
189
  * @param home - the dsh home holding the settings/ledger/manifest files.
169
190
  */
170
191
  function installSettingsPage(ctx, home, pet) {
171
- const settingsPath = join(home, "pet-settings.json");
172
- const windowPath = join(home, "pet-window.json");
192
+ const windowPath = globalFilePath(home, "pet-window.json");
173
193
  /** Summarize EVERY live pet (id, online flag, normalized /status). */
174
194
  const petStatus = async () => {
175
195
  return { pets: (await Promise.all((await pet.list()).map((entry) => pet.snapshot(entry)))).map(({ id, online, ...rest }) => ({
@@ -179,11 +199,7 @@ function installSettingsPage(ctx, home, pet) {
179
199
  })) };
180
200
  };
181
201
  const readSettings = async () => {
182
- try {
183
- return mergeSettings(JSON.parse(await readFile(settingsPath, "utf8")), {});
184
- } catch {
185
- return mergeSettings({}, {});
186
- }
202
+ return mergeSettings(await readGlobalJson(home, "pet-settings.json"), {});
187
203
  };
188
204
  const routeByPath = /* @__PURE__ */ new Map();
189
205
  const route = (method, path, handler) => {
@@ -219,7 +235,7 @@ function installSettingsPage(ctx, home, pet) {
219
235
  route("GET", "/api/pet-settings/read", async (_req, res) => {
220
236
  const [settings, windowPosition, status] = await Promise.all([
221
237
  readSettings(),
222
- readFile(windowPath, "utf8").then((raw) => JSON.parse(raw), () => null),
238
+ readGlobalJson(home, "pet-window.json").then((raw) => Object.keys(raw).length > 0 ? raw : null),
223
239
  petStatus()
224
240
  ]);
225
241
  const affection = {};
@@ -238,7 +254,6 @@ function installSettingsPage(ctx, home, pet) {
238
254
  ...settings,
239
255
  names
240
256
  };
241
- const history = [];
242
257
  const touchTotals = {};
243
258
  for (const id of bookIds) {
244
259
  const events = await readJsonl(eventsFile(home, id), 600);
@@ -253,18 +268,12 @@ function installSettingsPage(ctx, home, pet) {
253
268
  totals.byPart[part] = (totals.byPart[part] ?? 0) + 1;
254
269
  }
255
270
  if (totals.total > 0) touchTotals[id] = totals;
256
- for (const call of await readJsonl(toolCallsFile(home, id), 12)) history.push({
257
- ...call,
258
- petId: id
259
- });
260
271
  }
261
- history.sort((a, b) => Number(b.at ?? 0) - Number(a.at ?? 0));
262
272
  writeJson(res, 200, {
263
273
  settings: composed,
264
274
  affection,
265
275
  windowPosition,
266
276
  pets: status.pets,
267
- history: history.slice(0, 12),
268
277
  touchTotals
269
278
  });
270
279
  });
@@ -281,7 +290,7 @@ function installSettingsPage(ctx, home, pet) {
281
290
  delete patch.names;
282
291
  }
283
292
  const next = mergeSettings(await readSettings(), patch);
284
- await writeFile(settingsPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
293
+ await writeGlobalJson(home, "pet-settings.json", next);
285
294
  ctx.emit("pet-settings/changed");
286
295
  writeJson(res, 200, {
287
296
  ok: true,
@@ -296,7 +305,7 @@ function installSettingsPage(ctx, home, pet) {
296
305
  });
297
306
  });
298
307
  route("POST", "/api/pet-settings/reset-position", async (_req, res) => {
299
- if (existsSync(windowPath)) await unlink(windowPath);
308
+ for (const p of [windowPath, join(home, "pet-window.json")]) await rm(p, { force: true });
300
309
  writeJson(res, 200, { ok: true });
301
310
  });
302
311
  route("POST", "/api/pet-settings/open-logs", async (_req, res) => {
@@ -308,6 +317,59 @@ function installSettingsPage(ctx, home, pet) {
308
317
  opened: target
309
318
  });
310
319
  });
320
+ route("GET", "/api/pet-settings/tts/voices", async (req, res) => {
321
+ const settings = await readSettings();
322
+ const apiKey = typeof settings.tts?.apiKey === "string" ? settings.tts.apiKey : "";
323
+ if (apiKey === "") {
324
+ writeJson(res, 400, { error: "尚未配置 BreezeBlue API key" });
325
+ return;
326
+ }
327
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
328
+ const params = new URLSearchParams();
329
+ for (const key of [
330
+ "search",
331
+ "language_code",
332
+ "gender",
333
+ "accent"
334
+ ]) {
335
+ const value = url.searchParams.get(key);
336
+ if (value !== null && value !== "") params.set(key, value);
337
+ }
338
+ const rawPageSize = url.searchParams.get("page_size");
339
+ const pageSize = rawPageSize !== null ? clampNumber(Number(rawPageSize), 1, 100) : void 0;
340
+ params.set("page_size", String(pageSize ?? 30));
341
+ try {
342
+ const response = await fetch(`https://api.breeze.blue/v1/voices?${params.toString()}`, {
343
+ headers: { "xi-api-key": apiKey },
344
+ signal: AbortSignal.timeout(1e4)
345
+ });
346
+ if (!response.ok) {
347
+ const detail = await response.text().catch(() => "");
348
+ writeJson(res, 502, { error: response.status === 401 ? "BreezeBlue 拒绝了这个 key(401):请到 breezeblue.ai 的 Developer Console 复制 brz_ 开头的 API key" : `BreezeBlue HTTP ${String(response.status)}${detail ? `: ${detail.slice(0, 200)}` : ""}` });
349
+ return;
350
+ }
351
+ const raw = await response.json();
352
+ const voices = (Array.isArray(raw.voices) ? raw.voices : []).map((entry) => {
353
+ const voice = entry;
354
+ const str = (key) => typeof voice[key] === "string" ? voice[key] : null;
355
+ return {
356
+ voice_id: str("voice_id") ?? "",
357
+ name: str("name") ?? "",
358
+ language_code: str("language_code"),
359
+ gender: str("gender"),
360
+ tone: Array.isArray(voice.tone) ? voice.tone.filter((item) => typeof item === "string").slice(0, 3) : [],
361
+ accent: str("accent"),
362
+ preview_url: str("preview_url")
363
+ };
364
+ }).filter((voice) => voice.voice_id !== "");
365
+ writeJson(res, 200, {
366
+ voices,
367
+ total: typeof raw.total === "number" ? raw.total : voices.length
368
+ });
369
+ } catch (error) {
370
+ writeJson(res, 502, { error: `声音列表请求失败: ${error instanceof Error ? error.message : String(error)}` });
371
+ }
372
+ });
311
373
  }
312
374
  //#endregion
313
375
  //#region lib/types/index.js
@@ -355,7 +417,7 @@ const PET_PATH_PREFIX = "/whale598";
355
417
  * publisher, then falls back to spawning its own profile.
356
418
  */
357
419
  function petLocatorPath() {
358
- return join(process.env.DSH_HOME ?? join(homedir(), ".dsh"), "pet-surface-url.json");
420
+ return join(process.env.DSH_HOME ?? join(homedir(), ".dsh"), "whale598", "pet-surface-url.json");
359
421
  }
360
422
  const HTML_MIME = "text/html; charset=utf-8";
361
423
  const MIME = {
@@ -623,9 +685,10 @@ function apply(ctx) {
623
685
  const settled = ctx.get("loader")?.await();
624
686
  if (settled === void 0) settledReady();
625
687
  else settled.then(settledReady).catch(() => {});
626
- const pet = installPetService(ctx, dirname(petLocatorPath()));
627
- installPetDriver(ctx, dirname(petLocatorPath()), pet);
628
- installSettingsPage(ctx, dirname(petLocatorPath()), pet);
688
+ const home = petHome();
689
+ const pet = installPetService(ctx, home);
690
+ installPetDriver(ctx, home, pet);
691
+ installSettingsPage(ctx, home, pet);
629
692
  ctx.effect(() => () => {
630
693
  readFile(petLocatorPath(), "utf8").then((raw) => {
631
694
  if (JSON.parse(raw).pid === process.pid) return unlink(petLocatorPath());