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.
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { randomUUID } from "node:crypto";
3
- import { appendFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { appendFile, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
4
5
  import { homedir } from "node:os";
5
6
  import { dirname, join } from "node:path";
6
7
  import { Context, Service } from "@deepseek-ai/cordis";
@@ -4607,15 +4608,161 @@ function formatAvailableActions(available, cap = 24) {
4607
4608
  return rest > 0 ? `${shown} …(+${String(rest)} more — pet_state {actions:true} lists all)` : shown;
4608
4609
  }
4609
4610
  //#endregion
4610
- //#region lib/types/inner-voice.js
4611
+ //#region lib/types/petdata.js
4611
4612
  /**
4612
- * The inner voice (docs/inner-voice.md): the pet's self-initiated expression
4613
- * loop. This module owns the pure, testable half — config parsing with
4614
- * defaults and clamps, the durable petId→sessionId store, and the wake/touch
4615
- * prompt builders. The driver (driver.ts) owns the wiring: timers, gates,
4616
- * SessionController calls, and the per-pet pending locks.
4617
- * @module dsh-agentic-pet/inner-voice
4613
+ * The per-pet data store: everything the DRIVER persists about a pet lives
4614
+ * under `~/.dsh/whale598/<petId>/` — affection ledger, display name, event
4615
+ * history, tool-call history. One directory per pet, small files, single
4616
+ * writer (the driver), JSONL for append-only streams with bounded retention.
4617
+ *
4618
+ * Legacy layouts (`pet-affection.d/<id>.json`, the single legacy electron
4619
+ * book, names inside pet-settings.json) are READ as fallbacks and migrate to
4620
+ * the new layout on the next write — lazy migration, no data-loss window.
4621
+ * @module dsh-agentic-pet/petdata
4618
4622
  */
4623
+ /** The consolidated per-pet data root under the dsh home. */
4624
+ const WHALE_DIR = "whale598";
4625
+ function petDataDir(home, petId) {
4626
+ return join(home, WHALE_DIR, petId);
4627
+ }
4628
+ /** New-layout path first; legacy layouts as read fallbacks (per-pet legacy
4629
+ * dir → the single legacy electron book). The ledger SHAPE is the driver's
4630
+ * (TouchLedger); this module only owns paths and raw IO. */
4631
+ function affectionPaths(home, petId) {
4632
+ return {
4633
+ current: join(petDataDir(home, petId), "pet-affection.json"),
4634
+ legacy: [join(home, "pet-affection.d", `${petId}.json`), ...petId === "electron" ? [join(home, "pet-affection.json")] : []]
4635
+ };
4636
+ }
4637
+ /** Read the affection ledger raw: the current file wins; legacy files answer
4638
+ * only while the current one is absent (first write migrates the full
4639
+ * ledger). Returns null when no layout holds data. */
4640
+ async function readAffectionRaw(home, petId) {
4641
+ const { current, legacy } = affectionPaths(home, petId);
4642
+ for (const file of [current, ...legacy]) try {
4643
+ const parsed = JSON.parse(await readFile(file, "utf8"));
4644
+ if (parsed !== null && typeof parsed === "object" && Object.keys(parsed).length > 0) return parsed;
4645
+ } catch {}
4646
+ return null;
4647
+ }
4648
+ async function writeAffectionRaw(home, petId, ledger) {
4649
+ const dir = petDataDir(home, petId);
4650
+ await mkdir(dir, { recursive: true });
4651
+ await writeFile(join(dir, "pet-affection.json"), `${JSON.stringify(ledger)}\n`, "utf8");
4652
+ }
4653
+ function petNameFile(home, petId) {
4654
+ return join(petDataDir(home, petId), "pet-name.json");
4655
+ }
4656
+ /** The user's custom display name: new-layout file → legacy
4657
+ * pet-settings.json `names` map → undefined (caller applies the built-in
4658
+ * default). */
4659
+ async function readPetName(home, petId) {
4660
+ try {
4661
+ const parsed = JSON.parse(await readFile(petNameFile(home, petId), "utf8"));
4662
+ if (typeof parsed.name === "string" && parsed.name.trim() !== "") return parsed.name.trim();
4663
+ } catch {}
4664
+ try {
4665
+ const settings = JSON.parse(await readFile(join(home, "pet-settings.json"), "utf8"));
4666
+ const label = (typeof settings.names === "object" && settings.names !== null ? settings.names : {})[petId];
4667
+ if (typeof label === "string" && label.trim() !== "") return label.trim();
4668
+ } catch {}
4669
+ }
4670
+ /** Persist a custom name; `null` reverts to the built-in default (drops the
4671
+ * override file). Also strips the petId key from the legacy pet-settings
4672
+ * names map so the old layout cannot resurrect a cleared name. */
4673
+ async function writePetName(home, petId, name) {
4674
+ await mkdir(petDataDir(home, petId), { recursive: true });
4675
+ const file = petNameFile(home, petId);
4676
+ if (name === null || name.trim() === "") await rm(file, { force: true });
4677
+ else await writeFile(file, `${JSON.stringify({ name: name.trim() })}\n`, "utf8");
4678
+ try {
4679
+ const settingsPath = join(home, "pet-settings.json");
4680
+ const settings = JSON.parse(await readFile(settingsPath, "utf8"));
4681
+ if (typeof settings.names === "object" && settings.names !== null && petId in settings.names) {
4682
+ Reflect.deleteProperty(settings.names, petId);
4683
+ if (Object.keys(settings.names).length === 0) delete settings.names;
4684
+ await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
4685
+ }
4686
+ } catch {}
4687
+ }
4688
+ const JSONL_MAX_LINES = 600;
4689
+ const JSONL_KEEP_LINES = 500;
4690
+ function eventsFile(home, petId) {
4691
+ return join(petDataDir(home, petId), "events.jsonl");
4692
+ }
4693
+ /** Append one record; bounded retention trims the file when it outgrows the
4694
+ * cap (best-effort — a failed trim never loses the append). */
4695
+ async function appendJsonl(file, entry) {
4696
+ await mkdir(dirname(file), { recursive: true });
4697
+ await appendFile(file, `${JSON.stringify(entry)}\n`, "utf8");
4698
+ try {
4699
+ const lines = (await readFile(file, "utf8")).split("\n").filter((line) => line.trim() !== "");
4700
+ if (lines.length > JSONL_MAX_LINES) await writeFile(file, `${lines.slice(lines.length - JSONL_KEEP_LINES).join("\n")}\n`, "utf8");
4701
+ } catch {}
4702
+ }
4703
+ /** The last `limit` records (oldest last); malformed lines degrade to
4704
+ * `{raw}` instead of dropping the file. */
4705
+ async function readJsonl(file, limit) {
4706
+ try {
4707
+ return (await readFile(file, "utf8")).split("\n").filter((line) => line.trim() !== "").slice(-limit).map((line) => {
4708
+ try {
4709
+ return JSON.parse(line);
4710
+ } catch {
4711
+ return { raw: line };
4712
+ }
4713
+ });
4714
+ } catch {
4715
+ return [];
4716
+ }
4717
+ }
4718
+ const GLOBAL_STATE_FILES = [
4719
+ "pet-settings.json",
4720
+ "pet-inner-voice.json",
4721
+ "pet-interactions.json",
4722
+ "pet-world.json",
4723
+ "pet-window.json"
4724
+ ];
4725
+ function globalPaths(home, name) {
4726
+ return {
4727
+ current: join(home, WHALE_DIR, name),
4728
+ legacy: join(home, name)
4729
+ };
4730
+ }
4731
+ function globalFilePath(home, name) {
4732
+ return globalPaths(home, name).current;
4733
+ }
4734
+ /** Rename legacy → current for every global file that hasn't moved yet.
4735
+ * Best-effort: a failed rename leaves the legacy copy in place and the read
4736
+ * fallback still answers. Called once at driver install. */
4737
+ async function migrateGlobals(home) {
4738
+ await mkdir(join(home, WHALE_DIR), { recursive: true });
4739
+ for (const name of GLOBAL_STATE_FILES) {
4740
+ const { current, legacy } = globalPaths(home, name);
4741
+ try {
4742
+ if (existsSync(current) || !existsSync(legacy)) continue;
4743
+ await rename(legacy, current);
4744
+ } catch {}
4745
+ }
4746
+ }
4747
+ /** Read a global state file: current → legacy → {} (callers' defensive
4748
+ * field checks treat {} exactly like a missing file). */
4749
+ async function readGlobalJson(home, name) {
4750
+ const { current, legacy } = globalPaths(home, name);
4751
+ for (const file of [current, legacy]) try {
4752
+ const parsed = JSON.parse(await readFile(file, "utf8"));
4753
+ if (parsed && typeof parsed === "object") return parsed;
4754
+ } catch {}
4755
+ return {};
4756
+ }
4757
+ /** Write a global state file (current location only; pretty-printed — these
4758
+ * files are user-inspectable and hand-editable). */
4759
+ async function writeGlobalJson(home, name, obj) {
4760
+ const { current } = globalPaths(home, name);
4761
+ await mkdir(dirname(current), { recursive: true });
4762
+ await writeFile(current, `${JSON.stringify(obj, null, 2)}\n`, "utf8");
4763
+ }
4764
+ //#endregion
4765
+ //#region lib/types/inner-voice.js
4619
4766
  const VOICE_SESSION_PREFIX = "whale598-";
4620
4767
  /**
4621
4768
  * Saved-setting migration for preset id renames: pet-settings.json holds the
@@ -4636,7 +4783,7 @@ const finite01 = (value, fallback) => typeof value === "number" && Number.isFini
4636
4783
  * folds to its default (defensive parse, `readAutonomy` precedent). */
4637
4784
  async function readInnerVoiceConfig(home) {
4638
4785
  try {
4639
- const raw = JSON.parse(await readFile(join(home, "pet-settings.json"), "utf8"));
4786
+ const raw = await readGlobalJson(home, "pet-settings.json");
4640
4787
  const v = typeof raw.innerVoice === "object" && raw.innerVoice !== null ? raw.innerVoice : {};
4641
4788
  const legacyMinutes = typeof v.touchReplyCooldownMinutes === "number" && Number.isFinite(v.touchReplyCooldownMinutes) ? v.touchReplyCooldownMinutes : void 0;
4642
4789
  const secondsRaw = typeof v.touchReplyCooldownSeconds === "number" && Number.isFinite(v.touchReplyCooldownSeconds) ? v.touchReplyCooldownSeconds : legacyMinutes !== void 0 ? legacyMinutes * 60 : void 0;
@@ -4665,9 +4812,6 @@ async function readInnerVoiceConfig(home) {
4665
4812
  };
4666
4813
  }
4667
4814
  }
4668
- function innerVoiceStorePath(home) {
4669
- return join(home, "pet-inner-voice.json");
4670
- }
4671
4815
  /** Defensive extraction of the persisted generation counters (petId → n). */
4672
4816
  function generationsOf(raw) {
4673
4817
  const out = {};
@@ -4678,7 +4822,7 @@ function generationsOf(raw) {
4678
4822
  }
4679
4823
  async function loadInnerVoiceStore(home) {
4680
4824
  try {
4681
- const raw = JSON.parse(await readFile(innerVoiceStorePath(home), "utf8"));
4825
+ const raw = await readGlobalJson(home, "pet-inner-voice.json");
4682
4826
  const sessions = {};
4683
4827
  for (const [petId, entry] of Object.entries(typeof raw.sessions === "object" && raw.sessions !== null ? raw.sessions : {})) {
4684
4828
  const record = entry;
@@ -4700,7 +4844,7 @@ async function loadInnerVoiceStore(home) {
4700
4844
  }
4701
4845
  async function saveInnerVoiceStore(home, store) {
4702
4846
  try {
4703
- await writeFile(innerVoiceStorePath(home), `${JSON.stringify(store)}\n`, "utf8");
4847
+ await writeGlobalJson(home, "pet-inner-voice.json", store);
4704
4848
  } catch (error) {
4705
4849
  console.error(`pet-surface inner-voice: could not write the store: ${error instanceof Error ? error.message : String(error)}`);
4706
4850
  }
@@ -4817,117 +4961,6 @@ function buildChatPrompt(text) {
4817
4961
  return `[用户对你说] ${text.slice(0, 2e3)}`;
4818
4962
  }
4819
4963
  //#endregion
4820
- //#region lib/types/petdata.js
4821
- /**
4822
- * The per-pet data store: everything the DRIVER persists about a pet lives
4823
- * under `~/.dsh/whale598/<petId>/` — affection ledger, display name, event
4824
- * history, tool-call history. One directory per pet, small files, single
4825
- * writer (the driver), JSONL for append-only streams with bounded retention.
4826
- *
4827
- * Legacy layouts (`pet-affection.d/<id>.json`, the single legacy electron
4828
- * book, names inside pet-settings.json) are READ as fallbacks and migrate to
4829
- * the new layout on the next write — lazy migration, no data-loss window.
4830
- * @module dsh-agentic-pet/petdata
4831
- */
4832
- /** The consolidated per-pet data root under the dsh home. */
4833
- const WHALE_DIR = "whale598";
4834
- function petDataDir(home, petId) {
4835
- return join(home, WHALE_DIR, petId);
4836
- }
4837
- /** New-layout path first; legacy layouts as read fallbacks (per-pet legacy
4838
- * dir → the single legacy electron book). The ledger SHAPE is the driver's
4839
- * (TouchLedger); this module only owns paths and raw IO. */
4840
- function affectionPaths(home, petId) {
4841
- return {
4842
- current: join(petDataDir(home, petId), "pet-affection.json"),
4843
- legacy: [join(home, "pet-affection.d", `${petId}.json`), ...petId === "electron" ? [join(home, "pet-affection.json")] : []]
4844
- };
4845
- }
4846
- /** Read the affection ledger raw: the current file wins; legacy files answer
4847
- * only while the current one is absent (first write migrates the full
4848
- * ledger). Returns null when no layout holds data. */
4849
- async function readAffectionRaw(home, petId) {
4850
- const { current, legacy } = affectionPaths(home, petId);
4851
- for (const file of [current, ...legacy]) try {
4852
- const parsed = JSON.parse(await readFile(file, "utf8"));
4853
- if (parsed !== null && typeof parsed === "object" && Object.keys(parsed).length > 0) return parsed;
4854
- } catch {}
4855
- return null;
4856
- }
4857
- async function writeAffectionRaw(home, petId, ledger) {
4858
- const dir = petDataDir(home, petId);
4859
- await mkdir(dir, { recursive: true });
4860
- await writeFile(join(dir, "pet-affection.json"), `${JSON.stringify(ledger)}\n`, "utf8");
4861
- }
4862
- function petNameFile(home, petId) {
4863
- return join(petDataDir(home, petId), "pet-name.json");
4864
- }
4865
- /** The user's custom display name: new-layout file → legacy
4866
- * pet-settings.json `names` map → undefined (caller applies the built-in
4867
- * default). */
4868
- async function readPetName(home, petId) {
4869
- try {
4870
- const parsed = JSON.parse(await readFile(petNameFile(home, petId), "utf8"));
4871
- if (typeof parsed.name === "string" && parsed.name.trim() !== "") return parsed.name.trim();
4872
- } catch {}
4873
- try {
4874
- const settings = JSON.parse(await readFile(join(home, "pet-settings.json"), "utf8"));
4875
- const label = (typeof settings.names === "object" && settings.names !== null ? settings.names : {})[petId];
4876
- if (typeof label === "string" && label.trim() !== "") return label.trim();
4877
- } catch {}
4878
- }
4879
- /** Persist a custom name; `null` reverts to the built-in default (drops the
4880
- * override file). Also strips the petId key from the legacy pet-settings
4881
- * names map so the old layout cannot resurrect a cleared name. */
4882
- async function writePetName(home, petId, name) {
4883
- await mkdir(petDataDir(home, petId), { recursive: true });
4884
- const file = petNameFile(home, petId);
4885
- if (name === null || name.trim() === "") await rm(file, { force: true });
4886
- else await writeFile(file, `${JSON.stringify({ name: name.trim() })}\n`, "utf8");
4887
- try {
4888
- const settingsPath = join(home, "pet-settings.json");
4889
- const settings = JSON.parse(await readFile(settingsPath, "utf8"));
4890
- if (typeof settings.names === "object" && settings.names !== null && petId in settings.names) {
4891
- delete settings.names[petId];
4892
- if (Object.keys(settings.names).length === 0) delete settings.names;
4893
- await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
4894
- }
4895
- } catch {}
4896
- }
4897
- const JSONL_MAX_LINES = 600;
4898
- const JSONL_KEEP_LINES = 500;
4899
- function eventsFile(home, petId) {
4900
- return join(petDataDir(home, petId), "events.jsonl");
4901
- }
4902
- function toolCallsFile(home, petId) {
4903
- return join(petDataDir(home, petId), "toolcalls.jsonl");
4904
- }
4905
- /** Append one record; bounded retention trims the file when it outgrows the
4906
- * cap (best-effort — a failed trim never loses the append). */
4907
- async function appendJsonl(file, entry) {
4908
- await mkdir(dirname(file), { recursive: true });
4909
- await appendFile(file, `${JSON.stringify(entry)}\n`, "utf8");
4910
- try {
4911
- const lines = (await readFile(file, "utf8")).split("\n").filter((line) => line.trim() !== "");
4912
- if (lines.length > JSONL_MAX_LINES) await writeFile(file, `${lines.slice(lines.length - JSONL_KEEP_LINES).join("\n")}\n`, "utf8");
4913
- } catch {}
4914
- }
4915
- /** The last `limit` records (oldest last); malformed lines degrade to
4916
- * `{raw}` instead of dropping the file. */
4917
- async function readJsonl(file, limit) {
4918
- try {
4919
- return (await readFile(file, "utf8")).split("\n").filter((line) => line.trim() !== "").slice(-limit).map((line) => {
4920
- try {
4921
- return JSON.parse(line);
4922
- } catch {
4923
- return { raw: line };
4924
- }
4925
- });
4926
- } catch {
4927
- return [];
4928
- }
4929
- }
4930
- //#endregion
4931
4964
  //#region lib/types/registry.js
4932
4965
  /**
4933
4966
  * The pet registry: a directory of per-pet control manifests, replacing the
@@ -5093,8 +5126,6 @@ const ADJUST_MAX_DELTA = 3;
5093
5126
  const ADJUST_COOLDOWN_MS = 5 * 6e4;
5094
5127
  const ADJUST_DAILY_LIMIT = 8;
5095
5128
  const ADJUST_DAILY_SUM = 10;
5096
- const DECAY_START_DAYS = 3;
5097
- const DECAY_STEP_DAYS = 3;
5098
5129
  async function readLedger(home, petId) {
5099
5130
  try {
5100
5131
  const raw = await readAffectionRaw(home, petId);
@@ -5111,7 +5142,6 @@ async function readLedger(home, petId) {
5111
5142
  recent: raw.recent ?? [],
5112
5143
  adjustments: Array.isArray(raw.adjustments) ? raw.adjustments.filter((a) => typeof a === "object" && a !== null && typeof a.delta === "number" && typeof a.reason === "string" && typeof a.at === "number") : void 0,
5113
5144
  quota: typeof raw.quota === "object" && raw.quota !== null ? raw.quota : void 0,
5114
- lastDecayAt: typeof raw.lastDecayAt === "number" ? raw.lastDecayAt : void 0,
5115
5145
  bond: typeof raw.bond === "object" && raw.bond !== null && typeof raw.bond.lastCountedDate === "string" ? {
5116
5146
  value: typeof raw.bond.value === "number" ? raw.bond.value : 0,
5117
5147
  days: typeof raw.bond.days === "number" ? raw.bond.days : 0,
@@ -5136,7 +5166,7 @@ const logStamp = () => {
5136
5166
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
5137
5167
  };
5138
5168
  const driverLog = (home, line) => {
5139
- appendFile(join(home, "pet-driver.log"), `${logStamp()} ${line}
5169
+ appendFile(join(home, WHALE_DIR, "pet-driver.log"), `${logStamp()} ${line}
5140
5170
  `, "utf8").catch(() => {});
5141
5171
  };
5142
5172
  /** The one /events read both consumers share (§6 v4): the ledger loop
@@ -5218,6 +5248,7 @@ function markVoiceSpoke(petId) {
5218
5248
  }
5219
5249
  function installPetDriver(ctx, home, pet) {
5220
5250
  driverLog(home, "installPetDriver entered");
5251
+ migrateGlobals(home);
5221
5252
  voiceOwners.clear();
5222
5253
  voicePendingUntil.clear();
5223
5254
  voiceLastSpokeAt.clear();
@@ -5338,7 +5369,7 @@ function installPetDriver(ctx, home, pet) {
5338
5369
  */
5339
5370
  const readAutonomy = async () => {
5340
5371
  try {
5341
- const p = JSON.parse(await readFile(join(home, "pet-settings.json"), "utf8")).proactivity;
5372
+ const p = (await readGlobalJson(home, "pet-settings.json")).proactivity;
5342
5373
  const gap = typeof p?.idleAnimationGapMinutes === "number" && Number.isFinite(p.idleAnimationGapMinutes) ? Math.min(120, Math.max(1, Math.round(p.idleAnimationGapMinutes))) : 10;
5343
5374
  return {
5344
5375
  idleAnimation: p?.idleAnimation !== false,
@@ -5434,7 +5465,7 @@ function installPetDriver(ctx, home, pet) {
5434
5465
  }
5435
5466
  } catch {}
5436
5467
  try {
5437
- const raw = JSON.parse(await readFile(join(home, "pet-settings.json"), "utf8"));
5468
+ const raw = await readGlobalJson(home, "pet-settings.json");
5438
5469
  if (typeof raw.names === "object" && raw.names !== null) {
5439
5470
  for (const [petId, label] of Object.entries(raw.names)) if (petId !== "" && typeof label === "string" && label.trim() !== "" && !voiceNames.has(petId)) voiceNames.set(petId, label.trim());
5440
5471
  }
@@ -5501,7 +5532,7 @@ function installPetDriver(ctx, home, pet) {
5501
5532
  abort
5502
5533
  };
5503
5534
  };
5504
- const interactionLedgerFile = join(home, "pet-interactions.json");
5535
+ const interactionLedgerFile = globalFilePath(home, "pet-interactions.json");
5505
5536
  const recordInteraction = (entry) => {
5506
5537
  const at = Date.now();
5507
5538
  (async () => {
@@ -6001,12 +6032,13 @@ ${text}`;
6001
6032
  function registerPetTools(ctx, home, pet) {
6002
6033
  ctx.inject(["tools"], (scope) => {
6003
6034
  console.log("pet-surface driver: pet tools registered");
6035
+ const dshLoginAt = Date.now();
6004
6036
  const sleep = (ms) => new Promise((resolve) => {
6005
6037
  setTimeout(resolve, ms);
6006
6038
  });
6007
6039
  const readFocus = async () => {
6008
6040
  try {
6009
- const raw = JSON.parse(await readFile(join(home, "pet-settings.json"), "utf8"));
6041
+ const raw = await readGlobalJson(home, "pet-settings.json");
6010
6042
  return typeof raw.focus === "string" && raw.focus !== "" ? raw.focus : void 0;
6011
6043
  } catch {
6012
6044
  return;
@@ -6061,14 +6093,10 @@ function registerPetTools(ctx, home, pet) {
6061
6093
  }]
6062
6094
  },
6063
6095
  async execute(args, exec) {
6064
- const callStartedAt = Date.now();
6065
- let callPet = typeof args.pet === "string" && args.pet !== "" ? args.pet : "default";
6066
- let callDetail;
6067
6096
  const text = String(args.text).slice(0, 200);
6068
6097
  console.log(`pet-surface driver: pet_say ${JSON.stringify(text).slice(0, 60)}`);
6069
6098
  try {
6070
6099
  const entry = await resolveTarget(args.pet, exec?.agent?.id);
6071
- callPet = entry.id;
6072
6100
  await pet.call(entry, "/say", {
6073
6101
  method: "POST",
6074
6102
  headers: { "content-type": "application/json" },
@@ -6076,17 +6104,7 @@ function registerPetTools(ctx, home, pet) {
6076
6104
  });
6077
6105
  if (markVoiceSpoke(entry.id)) driverLog(home, `VOICE pending released by say pet=${entry.id} (inner-voice turn complete)`);
6078
6106
  } catch (error) {
6079
- callDetail = error instanceof Error ? error.message : String(error);
6080
6107
  throw classify(error);
6081
- } finally {
6082
- appendJsonl(toolCallsFile(home, callPet), {
6083
- tool: "pet_say",
6084
- petId: callPet,
6085
- ok: callDetail === void 0,
6086
- ...callDetail !== void 0 ? { detail: callDetail.slice(0, 120) } : {},
6087
- durationMs: Date.now() - callStartedAt,
6088
- at: Date.now()
6089
- });
6090
6108
  }
6091
6109
  return JSON.stringify({
6092
6110
  shown: true,
@@ -6120,12 +6138,8 @@ function registerPetTools(ctx, home, pet) {
6120
6138
  }]
6121
6139
  },
6122
6140
  async execute(args, exec) {
6123
- const callStartedAt = Date.now();
6124
- let callPet = typeof args.pet === "string" && args.pet !== "" ? args.pet : "default";
6125
- let callDetail;
6126
6141
  try {
6127
6142
  const entry = await resolveTarget(args.pet, exec?.agent?.id);
6128
- callPet = entry.id;
6129
6143
  const requested = String(args.action);
6130
6144
  const resolution = resolveActionRequest(requested, (await pet.capabilities(entry))?.actions ?? null);
6131
6145
  if (resolution.kind === "unknown") throw new Error(`unknown action "${requested}" for pet "${entry.id}". Available: ${formatAvailableActions(resolution.available)}`);
@@ -6168,17 +6182,7 @@ function registerPetTools(ctx, home, pet) {
6168
6182
  waitTimedOut: true
6169
6183
  });
6170
6184
  } catch (error) {
6171
- callDetail = error instanceof Error ? error.message : String(error);
6172
6185
  throw classify(error);
6173
- } finally {
6174
- appendJsonl(toolCallsFile(home, callPet), {
6175
- tool: "pet_do",
6176
- petId: callPet,
6177
- ok: callDetail === void 0,
6178
- ...callDetail !== void 0 ? { detail: callDetail.slice(0, 120) } : {},
6179
- durationMs: Date.now() - callStartedAt,
6180
- at: Date.now()
6181
- });
6182
6186
  }
6183
6187
  }
6184
6188
  }));
@@ -6197,12 +6201,8 @@ function registerPetTools(ctx, home, pet) {
6197
6201
  }]
6198
6202
  },
6199
6203
  async execute(args, exec) {
6200
- const callStartedAt = Date.now();
6201
- let callPet = typeof args.pet === "string" && args.pet !== "" ? args.pet : "default";
6202
- let callDetail;
6203
6204
  try {
6204
6205
  const entry = await resolveTarget(args.pet, exec?.agent?.id);
6205
- callPet = entry.id;
6206
6206
  const cursor = digestCursors.get(entry.id) ?? 0;
6207
6207
  let payload = await readEvents(pet, entry, cursor, 0);
6208
6208
  if (payload.latest < cursor) payload = await readEvents(pet, entry, 0, 0);
@@ -6231,17 +6231,7 @@ function registerPetTools(ctx, home, pet) {
6231
6231
  cursor: newest
6232
6232
  });
6233
6233
  } catch (error) {
6234
- callDetail = error instanceof Error ? error.message : String(error);
6235
6234
  throw classify(error);
6236
- } finally {
6237
- appendJsonl(toolCallsFile(home, callPet), {
6238
- tool: "pet_digest",
6239
- petId: callPet,
6240
- ok: callDetail === void 0,
6241
- ...callDetail !== void 0 ? { detail: callDetail.slice(0, 120) } : {},
6242
- durationMs: Date.now() - callStartedAt,
6243
- at: Date.now()
6244
- });
6245
6235
  }
6246
6236
  }
6247
6237
  }));
@@ -6263,27 +6253,15 @@ function registerPetTools(ctx, home, pet) {
6263
6253
  const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.round(args.limit))) : 10;
6264
6254
  let list = [];
6265
6255
  try {
6266
- const raw = JSON.parse(await readFile(join(home, "pet-interactions.json"), "utf8"));
6256
+ const raw = await readGlobalJson(home, "pet-interactions.json");
6267
6257
  if (Array.isArray(raw)) list = raw;
6268
6258
  } catch {}
6269
- appendJsonl(toolCallsFile(home, "all"), {
6270
- tool: "pet_interactions",
6271
- petId: "all",
6272
- ok: true,
6273
- durationMs: 0,
6274
- at: Date.now()
6275
- });
6276
6259
  return JSON.stringify({ recent: list.slice(0, limit) });
6277
6260
  }
6278
6261
  }));
6279
- const settleDecay = (ledger, now) => {
6280
- const silentDays = (now - Math.max(ledger.lastTouchAt ?? 0, ledger.adjustments?.[0]?.at ?? 0, ledger.lastDecayAt ?? 0)) / 864e5;
6281
- if (silentDays <= DECAY_START_DAYS) return 0;
6282
- return Math.min(ledger.affection, Math.floor((silentDays - DECAY_START_DAYS) / DECAY_STEP_DAYS) + 1);
6283
- };
6284
6262
  scope.tools.register(defineTool({
6285
6263
  name: "pet_affection_get",
6286
- description: "Read the FULL affection context before adjusting it: the current value you maintain (and its mood), the touch FACTS the system records (counts by part — cause, not feeling), your recent adjustments with their reasons, the remaining daily quota, and any pending decay. Call this whenever you are deciding whether and how to adjust.",
6264
+ description: "Read the FULL affection context before adjusting it: the current value you maintain (and its mood), the touch FACTS the system records (counts by part — cause, not feeling), your recent adjustments with their reasons, the remaining daily quota, and the time facts (now / lastInteractionAt / lastLoginAt, epoch ms — the system only records and reports facts, it never moves affection or bond). Judge honestly: if the silence since lastInteractionAt has made the current value no longer true, adjust it yourself with a concrete reason.",
6287
6265
  parameters: { pet: {
6288
6266
  type: "string",
6289
6267
  description: "Which pet: its id from pet_state. Omit for the default pet."
@@ -6296,15 +6274,11 @@ function registerPetTools(ctx, home, pet) {
6296
6274
  }]
6297
6275
  },
6298
6276
  async execute(args, exec) {
6299
- const callStartedAt = Date.now();
6300
- let callPet = typeof args.pet === "string" && args.pet !== "" ? args.pet : "default";
6301
- let callDetail;
6302
6277
  try {
6303
6278
  const entry = await resolveTarget(args.pet, exec?.agent?.id);
6304
- callPet = entry.id;
6305
6279
  const ledger = await readLedger(home, entry.id);
6306
- const pendingDecay = settleDecay(ledger, Date.now());
6307
- const effective = Math.max(0, ledger.affection - pendingDecay);
6280
+ const now = Date.now();
6281
+ const lastInteractionAt = Math.max(ledger.lastTouchAt ?? 0, ledger.adjustments?.[0]?.at ?? 0);
6308
6282
  const today = (/* @__PURE__ */ new Date()).toDateString();
6309
6283
  const quota = ledger.quota?.date === today ? ledger.quota : {
6310
6284
  date: today,
@@ -6313,8 +6287,8 @@ function registerPetTools(ctx, home, pet) {
6313
6287
  };
6314
6288
  return JSON.stringify({
6315
6289
  pet: entry.id,
6316
- affection: effective,
6317
- mood: moodOf(effective),
6290
+ affection: ledger.affection,
6291
+ mood: moodOf(ledger.affection),
6318
6292
  touch: {
6319
6293
  total: ledger.total,
6320
6294
  byPart: ledger.byPart,
@@ -6333,20 +6307,14 @@ function registerPetTools(ctx, home, pet) {
6333
6307
  sum: quota.sum,
6334
6308
  sumLimit: ADJUST_DAILY_SUM
6335
6309
  },
6336
- pendingDecay
6310
+ facts: {
6311
+ now,
6312
+ lastInteractionAt: lastInteractionAt > 0 ? lastInteractionAt : null,
6313
+ lastLoginAt: dshLoginAt
6314
+ }
6337
6315
  });
6338
6316
  } catch (error) {
6339
- callDetail = error instanceof Error ? error.message : String(error);
6340
6317
  throw classify(error);
6341
- } finally {
6342
- appendJsonl(toolCallsFile(home, callPet), {
6343
- tool: "pet_affection_get",
6344
- petId: callPet,
6345
- ok: callDetail === void 0,
6346
- ...callDetail !== void 0 ? { detail: callDetail.slice(0, 120) } : {},
6347
- durationMs: Date.now() - callStartedAt,
6348
- at: Date.now()
6349
- });
6350
6318
  }
6351
6319
  }
6352
6320
  }));
@@ -6385,25 +6353,10 @@ function registerPetTools(ctx, home, pet) {
6385
6353
  }]
6386
6354
  },
6387
6355
  async execute(args, exec) {
6388
- const callStartedAt = Date.now();
6389
- let callPet = typeof args.pet === "string" && args.pet !== "" ? args.pet : "default";
6390
- let callDetail;
6391
6356
  try {
6392
6357
  const entry = await resolveTarget(args.pet, exec?.agent?.id);
6393
- callPet = entry.id;
6394
6358
  const ledger = await readLedger(home, entry.id);
6395
6359
  const now = Date.now();
6396
- const pendingDecay = settleDecay(ledger, now);
6397
- if (pendingDecay > 0) {
6398
- ledger.affection = Math.max(0, ledger.affection - pendingDecay);
6399
- ledger.adjustments = [{
6400
- delta: -pendingDecay,
6401
- reason: `system: decay(${String(pendingDecay)} 天未互动)`,
6402
- at: now,
6403
- system: true
6404
- }, ...ledger.adjustments ?? []];
6405
- ledger.lastDecayAt = now;
6406
- }
6407
6360
  const delta = typeof args.delta === "number" && Number.isInteger(args.delta) ? args.delta : NaN;
6408
6361
  const reason = typeof args.reason === "string" ? args.reason.trim() : "";
6409
6362
  if (Number.isNaN(delta) || delta === 0 || Math.abs(delta) > ADJUST_MAX_DELTA) throw new Error(`delta 必须是 -${String(ADJUST_MAX_DELTA)}..+${String(ADJUST_MAX_DELTA)} 的非零整数(收到 ${String(args.delta)})`);
@@ -6461,17 +6414,7 @@ function registerPetTools(ctx, home, pet) {
6461
6414
  }
6462
6415
  });
6463
6416
  } catch (error) {
6464
- callDetail = error instanceof Error ? error.message : String(error);
6465
6417
  throw classify(error);
6466
- } finally {
6467
- appendJsonl(toolCallsFile(home, callPet), {
6468
- tool: "pet_affection_adjust",
6469
- petId: callPet,
6470
- ok: callDetail === void 0,
6471
- ...callDetail !== void 0 ? { detail: callDetail.slice(0, 120) } : {},
6472
- durationMs: Date.now() - callStartedAt,
6473
- at: Date.now()
6474
- });
6475
6418
  }
6476
6419
  }
6477
6420
  }));
@@ -6506,14 +6449,16 @@ function registerPetTools(ctx, home, pet) {
6506
6449
  for (const id of knownIds) {
6507
6450
  const ledger = await readLedger(home, id);
6508
6451
  const mood = moodOf(ledger.affection);
6452
+ const tier = bondTier(ledger.bond?.value ?? 0);
6453
+ const bond = {
6454
+ value: ledger.bond?.value ?? 0,
6455
+ days: ledger.bond?.days ?? 0,
6456
+ tier
6457
+ };
6509
6458
  ledgers[id] = {
6510
6459
  ...ledger,
6511
6460
  mood,
6512
- bond: {
6513
- value: ledger.bond?.value ?? 0,
6514
- days: ledger.bond?.days ?? 0,
6515
- tier: bondTier(ledger.bond?.value ?? 0)
6516
- }
6461
+ bond
6517
6462
  };
6518
6463
  }
6519
6464
  const petsOut = states.map((state) => {
@@ -6526,13 +6471,6 @@ function registerPetTools(ctx, home, pet) {
6526
6471
  name: voiceNames.get(state.id) ?? DEFAULT_PET_NAME[state.id] ?? state.id
6527
6472
  };
6528
6473
  });
6529
- appendJsonl(toolCallsFile(home, "all"), {
6530
- tool: "pet_state",
6531
- petId: "all",
6532
- ok: true,
6533
- durationMs: 0,
6534
- at: Date.now()
6535
- });
6536
6474
  return JSON.stringify({
6537
6475
  defaultPet: pets[0]?.id ?? null,
6538
6476
  pets: petsOut,
@@ -6805,4 +6743,4 @@ function installPetService(ctx, home) {
6805
6743
  return new PetHttpService(ctx, home);
6806
6744
  }
6807
6745
  //#endregion
6808
- export { logStamp as a, readAffectionRaw as c, toolCallsFile as d, writeAffectionRaw as f, installPetDriver as i, readJsonl as l, installPetService as n, registerPetTools as o, writePetName as p, petHome as r, eventsFile as s, createUnregisteredPetHttp as t, readPetName as u };
6746
+ export { logStamp as a, globalFilePath as c, readJsonl as d, readPetName as f, writePetName as h, installPetDriver as i, readAffectionRaw as l, writeGlobalJson as m, installPetService as n, registerPetTools as o, writeAffectionRaw as p, petHome as r, eventsFile as s, createUnregisteredPetHttp as t, readGlobalJson as u };
package/lib/tools.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as logStamp, o as registerPetTools, r as petHome, t as createUnregisteredPetHttp } from "./service-D-b8UzM2.js";
1
+ import { a as logStamp, o as registerPetTools, r as petHome, t as createUnregisteredPetHttp } from "./service-BjTawwUn.js";
2
2
  import { appendFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  //#region lib/types/tools.js
@@ -18,7 +18,7 @@ import { join } from "node:path";
18
18
  /** Diagnostic trace into the driver log: the desktop host swallows stdout, so
19
19
  * the preset-row activation path is otherwise invisible. */
20
20
  const trace = (line) => {
21
- appendFile(join(petHome(), "pet-driver.log"), `${logStamp()} pet-tools ${line}\n`, "utf8").catch(() => {});
21
+ appendFile(join(petHome(), "whale598", "pet-driver.log"), `${logStamp()} pet-tools ${line}\n`, "utf8").catch(() => {});
22
22
  };
23
23
  /**
24
24
  * Register the five pet tools for sessions mounted with the pet preset.