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/README.md +17 -2
- package/README.zh.md +17 -2
- package/carrier/dist-renderer/assets/{index-C61Q_dyJ.js → index-BMH7auTh.js} +1 -1
- package/carrier/dist-renderer/index.html +1 -1
- package/carrier/lib/main.js +38 -20
- package/carrier/lib/preload.js +3 -1
- package/carrier/lib/tts.js +212 -0
- package/client/pet-settings.js +102 -1
- package/lib/index.js +86 -23
- package/lib/{service-D-b8UzM2.js → service-BjTawwUn.js} +184 -246
- package/lib/tools.js +2 -2
- package/lib/types/petdata.d.ts +16 -1
- package/package.json +12 -4
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
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/
|
|
4611
|
+
//#region lib/types/petdata.js
|
|
4611
4612
|
/**
|
|
4612
|
-
* The
|
|
4613
|
-
*
|
|
4614
|
-
*
|
|
4615
|
-
*
|
|
4616
|
-
*
|
|
4617
|
-
*
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
6307
|
-
const
|
|
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:
|
|
6317
|
-
mood: moodOf(
|
|
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
|
-
|
|
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,
|
|
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-
|
|
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.
|