dsh-agentic-pet 0.4.5 → 0.4.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.
- package/README.zh.md +7 -1
- package/lib/index.js +201 -28
- package/lib/{service-R8qwDx1Q.js → service-ZRl_L6yW.js} +108 -128
- package/lib/tools.js +1 -1
- package/lib/types/registry.d.ts +27 -20
- package/lib/types/service.d.ts +11 -13
- package/package.json +1 -1
package/README.zh.md
CHANGED
|
@@ -9,10 +9,16 @@ kind: "package-bundle"
|
|
|
9
9
|
|
|
10
10
|
## 简介
|
|
11
11
|
|
|
12
|
-
`dsh-agentic-pet` 是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 生态的桌面宠物插件:宠物以透明置顶窗常驻桌面,跟随 agent 会话状态(idle / thinking / working / error),与 web 界面竞速应答审批与提问(先答者赢),维护每宠独立的触摸好感账本,并向模型暴露表达工具(`pet_say`、`pet_do`、`pet_digest`、`pet_interactions`、`pet_state
|
|
12
|
+
`dsh-agentic-pet` 是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 生态的桌面宠物插件:宠物以透明置顶窗常驻桌面,跟随 agent 会话状态(idle / thinking / working / error),与 web 界面竞速应答审批与提问(先答者赢),维护每宠独立的触摸好感账本,并向模型暴露表达工具(`pet_say`、`pet_do`、`pet_digest`、`pet_interactions`、`pet_state`、`pet_schedule_wake`、`pet_cancel_wake`)——模型可以借宠物说话、做动作、感知被触摸、给自己定作息。说话内容可通过 [BreezeBlue](https://breezeblue.ai) TTS 朗读——carrier 侧合成每条 `/say` 文本,语音就绪后与气泡同时下发;合成失败或超时静默降级为只显示(桌面设置页可配)。多只宠物实现可以并存:目录注册表(`~/.dsh/whale598/pet-http.d/`)发现全部在线宠物,状态、交互、账本按宠路由。
|
|
13
13
|
|
|
14
14
|
宠物本身是零 DSH 知识的纯显示端。理解运行时的一切——事件收敛、交互瀑布、多宠路由、模型工具——都在插件的 driver 里,宠物只实现一个小型本地 HTTP 控制接口。任何讲同一 HTTP 契约的宠物实现都能被采纳,插件零改动。
|
|
15
15
|
|
|
16
|
+
### 多宠会话路由(session ↔ 宠物)
|
|
17
|
+
|
|
18
|
+
- **宠自己的内心戏会话**(每宠一个:boot greeting 自动创建,或设置页「指定会话」手动绑定):driver 用 `voiceOwners` 映射表记录会话→宠物,模型在会话里的 `pet_say`/`pet_do` 不点名也自动落回这只宠;这些回合不画状态灯(呼吸不是工作状态)。
|
|
19
|
+
- **工作会话**:有绑定宠时状态灯只亮在它身上(宠物在交互竞速里答过题即赢得绑定);无绑定或绑定宠离线时,状态广播到全部在线宠。审批/问答永远全员广播。工作会话没有宠物工具,不涉及说话/动作的分发。
|
|
20
|
+
- **手动在侧栏开的宠物模式会话**:driver 不认识它(不在映射表里),按普通工作会话对待——状态灯上身,表演类工具(`pet_say`/`pet_do`/定撤闹钟)直接拒答、不分发。这不是预期用法:想跟宠物说话,请用宠物表面的输入卡,直达她自己的会话。
|
|
21
|
+
|
|
16
22
|
## 仓库结构
|
|
17
23
|
|
|
18
24
|
| 路径 | 内容 |
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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-
|
|
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-ZRl_L6yW.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
3
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { appendFileSync, existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
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";
|
|
@@ -618,49 +618,207 @@ function apply(ctx) {
|
|
|
618
618
|
console.error(`pet-surface: could not write the locator file: ${error instanceof Error ? error.message : String(error)}`);
|
|
619
619
|
});
|
|
620
620
|
};
|
|
621
|
+
/** Carrier spawn diagnostics; the packaged host's stderr is not visible. */
|
|
622
|
+
const logCarrier = (line) => {
|
|
623
|
+
try {
|
|
624
|
+
appendFileSync(join(dirname(petLocatorPath()), "carrier-spawn.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${line}\n`, "utf8");
|
|
625
|
+
} catch {}
|
|
626
|
+
};
|
|
621
627
|
/**
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
* `electron` npm dependency (its module resolves to the binary path).
|
|
628
|
-
* The carrier's single-instance lock collapses duplicates.
|
|
628
|
+
* The desktop profile directory is Low-integrity (inheritable), so an
|
|
629
|
+
* electron.exe installed into it by pnpm inherits Low and Chromium crashes
|
|
630
|
+
* on spawn (exit 0x80000003). The host itself runs at Medium, so raising
|
|
631
|
+
* the label in place is enough; a later pnpm install that re-creates the
|
|
632
|
+
* files is healed the same way on the next activation.
|
|
629
633
|
*/
|
|
630
|
-
const
|
|
631
|
-
if (process.
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
634
|
+
const ensureElectronUsable = (binary) => {
|
|
635
|
+
if (process.platform !== "win32") return binary;
|
|
636
|
+
try {
|
|
637
|
+
const acl = execFileSync("icacls", [binary], {
|
|
638
|
+
encoding: "utf8",
|
|
639
|
+
windowsHide: true
|
|
640
|
+
});
|
|
641
|
+
if (!/Low Mandatory Level/i.test(acl)) return binary;
|
|
642
|
+
execFileSync("icacls", [
|
|
643
|
+
dirname(binary),
|
|
644
|
+
"/setintegritylevel",
|
|
645
|
+
"(OI)(CI)M",
|
|
646
|
+
"/T"
|
|
647
|
+
], {
|
|
648
|
+
windowsHide: true,
|
|
649
|
+
stdio: "ignore",
|
|
650
|
+
timeout: 3e4
|
|
651
|
+
});
|
|
652
|
+
logCarrier(`raised electron integrity to Medium: ${dirname(binary)}`);
|
|
653
|
+
} catch (error) {
|
|
654
|
+
logCarrier(`could not raise electron integrity: ${error instanceof Error ? error.message : String(error)}`);
|
|
655
|
+
}
|
|
656
|
+
return binary;
|
|
657
|
+
};
|
|
658
|
+
/**
|
|
659
|
+
* Locate the npm `electron` package WITHOUT loading it. Loading the module
|
|
660
|
+
* (`require('electron')`) runs its lazy installer synchronously — a ~250 MB
|
|
661
|
+
* download inside the host's event loop — so only the manifest is resolved
|
|
662
|
+
* here; installing is a separate, background step.
|
|
663
|
+
*/
|
|
664
|
+
const findNpmElectronPackage = () => {
|
|
635
665
|
try {
|
|
636
666
|
const require = createRequire(import.meta.url);
|
|
637
|
-
const
|
|
638
|
-
if (existsSync(
|
|
667
|
+
const pkgDir = dirname(require.resolve("electron/package.json"));
|
|
668
|
+
if (existsSync(join(pkgDir, "package.json"))) return pkgDir;
|
|
639
669
|
} catch {}
|
|
640
670
|
try {
|
|
641
671
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
642
672
|
for (let i = 0; i < 8; i += 1) {
|
|
643
673
|
const pkgDir = join(dir, "node_modules", "electron");
|
|
644
|
-
|
|
645
|
-
const binary = join(pkgDir, "dist", exe);
|
|
646
|
-
if (existsSync(binary)) return binary;
|
|
674
|
+
if (existsSync(join(pkgDir, "package.json"))) return pkgDir;
|
|
647
675
|
const parent = dirname(dir);
|
|
648
676
|
if (parent === dir) break;
|
|
649
677
|
dir = parent;
|
|
650
678
|
}
|
|
651
679
|
} catch {}
|
|
652
|
-
return process.versions.electron !== void 0 ? process.execPath : void 0;
|
|
653
680
|
};
|
|
654
|
-
|
|
655
|
-
const logCarrier = (line) => {
|
|
681
|
+
const npmElectronBinary = (pkgDir) => {
|
|
656
682
|
try {
|
|
657
|
-
|
|
683
|
+
const exe = readFileSync(join(pkgDir, "path.txt"), "utf8").trim();
|
|
684
|
+
const binary = join(pkgDir, "dist", exe);
|
|
685
|
+
return existsSync(binary) ? binary : void 0;
|
|
686
|
+
} catch {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
const INSTALL_LOCK_STALE_MS = 18e5;
|
|
691
|
+
const installLockPath = (pkgDir) => join(pkgDir, ".dsh-pet-install.lock");
|
|
692
|
+
const acquireInstallLock = (pkgDir) => {
|
|
693
|
+
const lock = installLockPath(pkgDir);
|
|
694
|
+
const write = () => {
|
|
695
|
+
writeFileSync(lock, JSON.stringify({
|
|
696
|
+
pid: process.pid,
|
|
697
|
+
at: Date.now()
|
|
698
|
+
}), { flag: "wx" });
|
|
699
|
+
};
|
|
700
|
+
try {
|
|
701
|
+
write();
|
|
702
|
+
return true;
|
|
703
|
+
} catch {
|
|
704
|
+
try {
|
|
705
|
+
if (Date.now() - statSync(lock).mtimeMs > INSTALL_LOCK_STALE_MS) {
|
|
706
|
+
unlinkSync(lock);
|
|
707
|
+
write();
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
} catch {}
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
const releaseInstallLock = (pkgDir) => {
|
|
715
|
+
try {
|
|
716
|
+
unlinkSync(installLockPath(pkgDir));
|
|
658
717
|
} catch {}
|
|
659
718
|
};
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
719
|
+
/**
|
|
720
|
+
* Install the npm electron binary off the activation path. The package's
|
|
721
|
+
* lazy installer is a ~250 MB download; running it synchronously (which
|
|
722
|
+
* `require('electron')` does) freezes the host, so it runs as a child with
|
|
723
|
+
* progress in the spawn log and a mirror hint on failure.
|
|
724
|
+
*/
|
|
725
|
+
const installNpmElectron = async (pkgDir) => {
|
|
726
|
+
const installer = join(pkgDir, "install.js");
|
|
727
|
+
if (!existsSync(installer)) {
|
|
728
|
+
logCarrier(`electron binary missing and install.js not found: ${pkgDir}`);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (!acquireInstallLock(pkgDir)) {
|
|
732
|
+
logCarrier(`electron install already in progress (lock present): ${pkgDir}`);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
logCarrier(`electron binary missing; installing in background: ${pkgDir}`);
|
|
736
|
+
return await new Promise((resolveInstall) => {
|
|
737
|
+
let settled = false;
|
|
738
|
+
const finish = (value) => {
|
|
739
|
+
if (settled) return;
|
|
740
|
+
settled = true;
|
|
741
|
+
releaseInstallLock(pkgDir);
|
|
742
|
+
resolveInstall(value);
|
|
743
|
+
};
|
|
744
|
+
let stderr = "";
|
|
745
|
+
let stdout = "";
|
|
746
|
+
let lastProgress = 0;
|
|
747
|
+
const child = spawn(process.execPath, [installer], {
|
|
748
|
+
cwd: pkgDir,
|
|
749
|
+
env: {
|
|
750
|
+
...process.env,
|
|
751
|
+
ELECTRON_RUN_AS_NODE: "1"
|
|
752
|
+
},
|
|
753
|
+
stdio: [
|
|
754
|
+
"ignore",
|
|
755
|
+
"pipe",
|
|
756
|
+
"pipe"
|
|
757
|
+
],
|
|
758
|
+
windowsHide: true
|
|
759
|
+
});
|
|
760
|
+
child.stdout?.setEncoding("utf8");
|
|
761
|
+
child.stdout?.on("data", (chunk) => {
|
|
762
|
+
stdout = `${stdout}${chunk}`.slice(-2e3);
|
|
763
|
+
const now = Date.now();
|
|
764
|
+
if (now - lastProgress > 5e3) {
|
|
765
|
+
lastProgress = now;
|
|
766
|
+
const line = stdout.split(/\r?\n/).map((part) => part.trim()).filter(Boolean).pop();
|
|
767
|
+
if (line !== void 0) logCarrier(`electron install: ${line}`);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
child.stderr?.setEncoding("utf8");
|
|
771
|
+
child.stderr?.on("data", (chunk) => {
|
|
772
|
+
stderr = `${stderr}${chunk}`.slice(-2e3);
|
|
773
|
+
});
|
|
774
|
+
child.on("error", (error) => {
|
|
775
|
+
logCarrier(`electron install failed to start: ${error.message}`);
|
|
776
|
+
finish(void 0);
|
|
777
|
+
});
|
|
778
|
+
child.on("exit", (code) => {
|
|
779
|
+
if (code !== 0) {
|
|
780
|
+
logCarrier(`electron install failed (exit ${String(code)}): ${stderr.trim() || "no stderr"}. Set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ (or electron_mirror in .npmrc) and restart DSH; or run "node ${installer}" manually.`);
|
|
781
|
+
finish(void 0);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const binary = npmElectronBinary(pkgDir);
|
|
785
|
+
if (binary === void 0) {
|
|
786
|
+
logCarrier(`electron install exited 0 but the binary is still missing: ${pkgDir}`);
|
|
787
|
+
finish(void 0);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
logCarrier(`electron installed: ${binary}`);
|
|
791
|
+
finish(ensureElectronUsable(binary));
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
};
|
|
795
|
+
/**
|
|
796
|
+
* Auto-start the pet window. A bare Electron binary runs any app directory
|
|
797
|
+
* (`electron.exe <dir>`), but a PACKAGED one (the installed desktop) loads
|
|
798
|
+
* its own app and dies on its single-instance lock instead — so the
|
|
799
|
+
* resolution order is an explicit env override, a binary staged beside this
|
|
800
|
+
* package, then the `electron` npm dependency. The carrier's single-instance
|
|
801
|
+
* lock collapses duplicates.
|
|
802
|
+
*/
|
|
803
|
+
const resolveElectron = () => {
|
|
804
|
+
const override = process.env.DSH_PET_ELECTRON;
|
|
805
|
+
if (override !== void 0 && override !== "") {
|
|
806
|
+
if (existsSync(override)) return { binary: override };
|
|
807
|
+
logCarrier(`DSH_PET_ELECTRON is set but missing, falling back: ${override}`);
|
|
808
|
+
}
|
|
809
|
+
const pkgRoot = resolve(join(dirname(fileURLToPath(import.meta.url)), ".."));
|
|
810
|
+
const staged = join(pkgRoot, "electron-bin", process.platform === "win32" ? "electron.exe" : process.platform === "darwin" ? join("Electron.app", "Contents", "MacOS", "Electron") : "electron");
|
|
811
|
+
if (existsSync(staged)) return { binary: staged };
|
|
812
|
+
const npmPackageDir = findNpmElectronPackage();
|
|
813
|
+
if (npmPackageDir !== void 0) {
|
|
814
|
+
const binary = npmElectronBinary(npmPackageDir);
|
|
815
|
+
if (binary !== void 0) return { binary: ensureElectronUsable(binary) };
|
|
816
|
+
return { npmPackageDir };
|
|
817
|
+
}
|
|
818
|
+
logCarrier("no electron binary: no staged electron-bin, no npm electron dependency, and DSH_PET_ELECTRON is unset");
|
|
819
|
+
return {};
|
|
820
|
+
};
|
|
821
|
+
const launchCarrier = (url, carrierDir, electronBinary) => {
|
|
664
822
|
const launchArgs = [carrierDir];
|
|
665
823
|
if (process.env.DSH_PET_NO_SANDBOX === "1") launchArgs.push("--no-sandbox");
|
|
666
824
|
logCarrier(`spawn binary=${electronBinary} args=${JSON.stringify(launchArgs)} pid=${String(process.pid)}`);
|
|
@@ -707,6 +865,21 @@ function apply(ctx) {
|
|
|
707
865
|
child.kill();
|
|
708
866
|
}, "pet-surface: carrier shutdown");
|
|
709
867
|
};
|
|
868
|
+
const spawnPetCarrier = (url) => {
|
|
869
|
+
const carrierDir = resolve(join(dirname(fileURLToPath(import.meta.url)), "..", "carrier"));
|
|
870
|
+
if (process.env.DSH_PET_NO_SPAWN === "1" || !existsSync(join(carrierDir, "package.json"))) return;
|
|
871
|
+
const resolution = resolveElectron();
|
|
872
|
+
if (resolution.binary !== void 0) {
|
|
873
|
+
launchCarrier(url, carrierDir, resolution.binary);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (resolution.npmPackageDir === void 0) return;
|
|
877
|
+
installNpmElectron(resolution.npmPackageDir).then((binary) => {
|
|
878
|
+
if (binary !== void 0) launchCarrier(url, carrierDir, binary);
|
|
879
|
+
}).catch((error) => {
|
|
880
|
+
logCarrier(`electron install failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
881
|
+
});
|
|
882
|
+
};
|
|
710
883
|
const settledReady = () => {
|
|
711
884
|
const url = authenticatedRoot();
|
|
712
885
|
if (url === void 0) return;
|
|
@@ -5395,19 +5395,16 @@ function buildChatPrompt(text, mergedPlan) {
|
|
|
5395
5395
|
}
|
|
5396
5396
|
// lib/types/registry.js
|
|
5397
5397
|
/**
|
|
5398
|
-
* The pet registry: a directory of per-pet control manifests
|
|
5399
|
-
*
|
|
5400
|
-
*
|
|
5401
|
-
*
|
|
5402
|
-
* pet
|
|
5403
|
-
*
|
|
5404
|
-
*
|
|
5405
|
-
* session's bound pet if alive, else the default pet (first alive entry),
|
|
5406
|
-
* plus an all-notify broadcast mode for interactions.
|
|
5398
|
+
* The pet registry: a directory of per-pet control manifests. Each pet
|
|
5399
|
+
* implementation owns its own file under whale598/pet-http.d/ (named by its
|
|
5400
|
+
* pet id) and re-asserts it on a cadence; entries whose publisher pid is dead
|
|
5401
|
+
* expire by omission. The driver reads the whole table and routes per the
|
|
5402
|
+
* multi-pet design: a session's state rides its bound pet when that pet is
|
|
5403
|
+
* alive and broadcasts to every live pet otherwise; interactions broadcast to
|
|
5404
|
+
* all; the default pet (earliest-booted) resolves unnamed expression targets.
|
|
5407
5405
|
* @module dsh-agentic-pet/registry
|
|
5408
5406
|
*/
|
|
5409
5407
|
const REGISTRY_DIR = `${WHALE_DIR}/pet-http.d`;
|
|
5410
|
-
const LEGACY_REGISTRY_DIR = "pet-http.d";
|
|
5411
5408
|
/**
|
|
5412
5409
|
* A live pet is one whose control plane ANSWERS, not merely one whose pid
|
|
5413
5410
|
* exists — and not merely one whose port accepts TCP: a frozen pet (stuck
|
|
@@ -5438,94 +5435,63 @@ async function probeControlPlane(port) {
|
|
|
5438
5435
|
return false;
|
|
5439
5436
|
}
|
|
5440
5437
|
}
|
|
5441
|
-
/**
|
|
5442
|
-
*
|
|
5443
|
-
*
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5438
|
+
/**
|
|
5439
|
+
* The default-pet order: earliest booted first (`since` ascending). A missing
|
|
5440
|
+
* or mistyped `since` (0) reads as "unknown boot time" and sorts LAST — it
|
|
5441
|
+
* must never outrank a publisher that stated its boot time. This turns the
|
|
5442
|
+
* multi-pet design's de-facto rule ("the default pet is the first STARTED")
|
|
5443
|
+
* into an actual guarantee: discovery output IS boot order, so every
|
|
5444
|
+
* `pets[0]` consumer (defaultPet, pet_state's defaultPet) reads the
|
|
5445
|
+
* same pet. Stable sort: equal `since` values keep registry listing order.
|
|
5446
|
+
*/
|
|
5447
|
+
function bootOrder(pets) {
|
|
5448
|
+
return [...pets].sort((a, b) => (a.since > 0 ? a.since : Number.POSITIVE_INFINITY) - (b.since > 0 ? b.since : Number.POSITIVE_INFINITY));
|
|
5449
|
+
}
|
|
5450
|
+
/** Every live pet in boot order. Dead-pid entries are skipped — they expire
|
|
5451
|
+
* by omission the next time their file is rewritten — and so are silent
|
|
5452
|
+
* entries (pid alive, control plane unresponsive). */
|
|
5453
|
+
async function readAllPets(home) {
|
|
5454
|
+
let names = [];
|
|
5455
|
+
try {
|
|
5456
|
+
names = (await readdir(join(home, REGISTRY_DIR))).filter((name) => name.endsWith(".json"));
|
|
5457
|
+
} catch {
|
|
5458
|
+
return [];
|
|
5457
5459
|
}
|
|
5458
5460
|
const live = [];
|
|
5459
|
-
for (const
|
|
5460
|
-
const
|
|
5461
|
-
|
|
5462
|
-
const pid = typeof raw.pid === "number" ? raw.pid : NaN;
|
|
5463
|
-
if (!Number.isFinite(port) || !Number.isFinite(pid)) continue;
|
|
5464
|
-
try {
|
|
5465
|
-
process.kill(pid, 0);
|
|
5466
|
-
} catch {
|
|
5467
|
-
continue;
|
|
5468
|
-
}
|
|
5469
|
-
if (!await probeControlPlane(port)) continue;
|
|
5470
|
-
live.push({
|
|
5471
|
-
id,
|
|
5472
|
-
port,
|
|
5473
|
-
pid,
|
|
5474
|
-
since: typeof raw.since === "number" ? raw.since : 0
|
|
5475
|
-
});
|
|
5476
|
-
} catch {}
|
|
5477
|
-
return live;
|
|
5478
|
-
}
|
|
5479
|
-
/** Legacy single-file manifests folded into the registry: the electron
|
|
5480
|
-
* carrier's dedicated file and the pure pet's shared file, identity-validated
|
|
5481
|
-
* and probe-validated, so pre-registry publishers stay drivable. */
|
|
5482
|
-
const LEGACY_SOURCES = [{
|
|
5483
|
-
id: "electron",
|
|
5484
|
-
file: "pet-http-electron.json",
|
|
5485
|
-
identity: "electron"
|
|
5486
|
-
}, {
|
|
5487
|
-
id: "pure",
|
|
5488
|
-
file: "pet-http.json",
|
|
5489
|
-
identity: "python"
|
|
5490
|
-
}];
|
|
5491
|
-
/** Every live pet: registry entries plus legacy single-file manifests folded
|
|
5492
|
-
* under their pet ids (registry entries win on id collision). */
|
|
5493
|
-
async function readAllPets(home) {
|
|
5494
|
-
const pets = await readLivePets(home);
|
|
5495
|
-
for (const { id, file, identity } of LEGACY_SOURCES) {
|
|
5496
|
-
if (pets.some((pet) => pet.id === id)) continue;
|
|
5461
|
+
for (const name of names) {
|
|
5462
|
+
const id = name.slice(0, -5);
|
|
5463
|
+
if (id === "") continue;
|
|
5497
5464
|
try {
|
|
5498
|
-
const
|
|
5499
|
-
|
|
5500
|
-
|
|
5465
|
+
const raw = JSON.parse(await readFile(join(home, REGISTRY_DIR, name), "utf8"));
|
|
5466
|
+
const port = typeof raw.port === "number" ? raw.port : NaN;
|
|
5467
|
+
const pid = typeof raw.pid === "number" ? raw.pid : NaN;
|
|
5468
|
+
if (!Number.isFinite(port) || !Number.isFinite(pid)) continue;
|
|
5501
5469
|
try {
|
|
5502
|
-
process.kill(
|
|
5470
|
+
process.kill(pid, 0);
|
|
5503
5471
|
} catch {
|
|
5504
5472
|
continue;
|
|
5505
5473
|
}
|
|
5506
|
-
if (!await probeControlPlane(
|
|
5507
|
-
|
|
5474
|
+
if (!await probeControlPlane(port)) continue;
|
|
5475
|
+
live.push({
|
|
5508
5476
|
id,
|
|
5509
|
-
port
|
|
5510
|
-
pid
|
|
5511
|
-
since: 0
|
|
5477
|
+
port,
|
|
5478
|
+
pid,
|
|
5479
|
+
since: typeof raw.since === "number" ? raw.since : 0
|
|
5512
5480
|
});
|
|
5513
5481
|
} catch {}
|
|
5514
5482
|
}
|
|
5515
|
-
return
|
|
5483
|
+
return bootOrder(live);
|
|
5516
5484
|
}
|
|
5517
5485
|
/**
|
|
5518
|
-
*
|
|
5519
|
-
*
|
|
5520
|
-
*
|
|
5521
|
-
*
|
|
5486
|
+
* The default pet for UNNAMED targets: the earliest-booted live entry
|
|
5487
|
+
* (`pets[0]` — discovery's bootOrder guarantees start order, not listing
|
|
5488
|
+
* accident). State routing does NOT go through here anymore: an unbound
|
|
5489
|
+
* (or bound-but-offline) session broadcasts its state to every live pet —
|
|
5490
|
+
* the driver computes that audience itself. Used by the tools'
|
|
5491
|
+
* resolveTarget when neither a named pet nor the owning session resolves.
|
|
5522
5492
|
* @returns the entry, or undefined when no pet is alive.
|
|
5523
5493
|
*/
|
|
5524
|
-
function
|
|
5525
|
-
if (boundPetId !== void 0) {
|
|
5526
|
-
const bound = pets.find((pet) => pet.id === boundPetId);
|
|
5527
|
-
if (bound !== void 0) return bound;
|
|
5528
|
-
}
|
|
5494
|
+
function defaultPet(pets) {
|
|
5529
5495
|
return pets[0];
|
|
5530
5496
|
}
|
|
5531
5497
|
/** The pets that receive an INTERACTION (all-notify mode): every live pet,
|
|
@@ -5834,34 +5800,39 @@ function installPetDriver(ctx, home, pet) {
|
|
|
5834
5800
|
};
|
|
5835
5801
|
const pushState = async (state) => {
|
|
5836
5802
|
currentState = state;
|
|
5837
|
-
const
|
|
5838
|
-
|
|
5803
|
+
const pets = await pet.list();
|
|
5804
|
+
const bound = pets.filter((pet) => pet.id === boundPetId);
|
|
5805
|
+
const audience = bound.length > 0 ? bound : pets;
|
|
5806
|
+
if (audience.length === 0) return;
|
|
5839
5807
|
const autonomy = await readAutonomy();
|
|
5840
5808
|
const autonomyKey = JSON.stringify(autonomy);
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
try {
|
|
5849
|
-
const response = await fetch(`http://127.0.0.1:${String(target.port)}/state`, {
|
|
5850
|
-
method: "POST",
|
|
5851
|
-
headers: { "content-type": "application/json" },
|
|
5852
|
-
body: JSON.stringify({
|
|
5853
|
-
state,
|
|
5854
|
-
autonomy
|
|
5855
|
-
})
|
|
5809
|
+
await Promise.all(audience.map(async (target) => {
|
|
5810
|
+
const last = lastPushedByPet.get(target.id);
|
|
5811
|
+
if (last !== void 0 && last.state === state && last.since === target.since && last.autonomy === autonomyKey) return;
|
|
5812
|
+
lastPushedByPet.set(target.id, {
|
|
5813
|
+
state,
|
|
5814
|
+
since: target.since,
|
|
5815
|
+
autonomy: autonomyKey
|
|
5856
5816
|
});
|
|
5857
|
-
|
|
5858
|
-
|
|
5817
|
+
try {
|
|
5818
|
+
const response = await fetch(`http://127.0.0.1:${String(target.port)}/state`, {
|
|
5819
|
+
method: "POST",
|
|
5820
|
+
headers: { "content-type": "application/json" },
|
|
5821
|
+
body: JSON.stringify({
|
|
5822
|
+
state,
|
|
5823
|
+
autonomy
|
|
5824
|
+
}),
|
|
5825
|
+
signal: AbortSignal.timeout(5e3)
|
|
5826
|
+
});
|
|
5827
|
+
if (!response.ok) {
|
|
5828
|
+
console.error(`pet-surface driver: /state answered HTTP ${String(response.status)}`);
|
|
5829
|
+
lastPushedByPet.delete(target.id);
|
|
5830
|
+
} else if (state === "error") lastPushedByPet.delete(target.id);
|
|
5831
|
+
} catch {
|
|
5832
|
+
console.error(`pet-surface driver: /state to ${target.id} failed`);
|
|
5859
5833
|
lastPushedByPet.delete(target.id);
|
|
5860
|
-
}
|
|
5861
|
-
}
|
|
5862
|
-
console.error(`pet-surface driver: /state to ${target.id} failed`);
|
|
5863
|
-
lastPushedByPet.delete(target.id);
|
|
5864
|
-
}
|
|
5834
|
+
}
|
|
5835
|
+
}));
|
|
5865
5836
|
};
|
|
5866
5837
|
events.on("api-session/status", (sessionId, running) => {
|
|
5867
5838
|
if (isVoiceSession(sessionId)) return;
|
|
@@ -6471,7 +6442,8 @@ ${text}`;
|
|
|
6471
6442
|
if (target !== void 0) fetch(`http://127.0.0.1:${String(target.port)}/action`, {
|
|
6472
6443
|
method: "POST",
|
|
6473
6444
|
headers: { "content-type": "application/json" },
|
|
6474
|
-
body: JSON.stringify({ action: "celebrate" })
|
|
6445
|
+
body: JSON.stringify({ action: "celebrate" }),
|
|
6446
|
+
signal: AbortSignal.timeout(5e3)
|
|
6475
6447
|
}).catch(() => {});
|
|
6476
6448
|
}
|
|
6477
6449
|
triggerTouchReply(petId, part);
|
|
@@ -6582,10 +6554,14 @@ function registerPetTools(ctx, home, pet) {
|
|
|
6582
6554
|
}
|
|
6583
6555
|
}
|
|
6584
6556
|
const focus = await readFocus();
|
|
6585
|
-
const target = (focus !== void 0 ? pets.find((entry) => entry.id === focus) : void 0) ??
|
|
6557
|
+
const target = (focus !== void 0 ? pets.find((entry) => entry.id === focus) : void 0) ?? defaultPet(pets);
|
|
6586
6558
|
if (target === void 0) throw new Error("no pet is running");
|
|
6587
6559
|
return target;
|
|
6588
6560
|
};
|
|
6561
|
+
const requireOwnedSession = (exec) => {
|
|
6562
|
+
const sessionId = exec?.agent?.id;
|
|
6563
|
+
if (typeof sessionId === "string" && !voiceOwners.has(sessionId)) throw new Error("本会话没有绑定宠物(不是宠物自己的内心戏会话),pet 工具不分发。想和宠物说话,请用宠物表面的输入卡。");
|
|
6564
|
+
};
|
|
6589
6565
|
const classify = (error) => {
|
|
6590
6566
|
if (error instanceof Error) {
|
|
6591
6567
|
if (/fetch failed|ECONNREFUSED|ENOTFOUND/i.test(error.message)) return /* @__PURE__ */ new Error(`offline: the pet is not reachable (${error.message})`);
|
|
@@ -6616,6 +6592,7 @@ function registerPetTools(ctx, home, pet) {
|
|
|
6616
6592
|
}]
|
|
6617
6593
|
},
|
|
6618
6594
|
async execute(args, exec) {
|
|
6595
|
+
requireOwnedSession(exec);
|
|
6619
6596
|
const text = String(args.text).slice(0, 200);
|
|
6620
6597
|
console.log(`pet-surface driver: pet_say ${JSON.stringify(text).slice(0, 60)}`);
|
|
6621
6598
|
try {
|
|
@@ -6661,6 +6638,7 @@ function registerPetTools(ctx, home, pet) {
|
|
|
6661
6638
|
}]
|
|
6662
6639
|
},
|
|
6663
6640
|
async execute(args, exec) {
|
|
6641
|
+
requireOwnedSession(exec);
|
|
6664
6642
|
try {
|
|
6665
6643
|
const entry = await resolveTarget(args.pet, exec?.agent?.id);
|
|
6666
6644
|
const requested = String(args.action);
|
|
@@ -6973,6 +6951,7 @@ function registerPetTools(ctx, home, pet) {
|
|
|
6973
6951
|
}]
|
|
6974
6952
|
},
|
|
6975
6953
|
async execute(args, exec) {
|
|
6954
|
+
requireOwnedSession(exec);
|
|
6976
6955
|
try {
|
|
6977
6956
|
const entry = await resolveTarget(args.pet, exec?.agent?.id);
|
|
6978
6957
|
const id = typeof args.id === "string" ? args.id.trim().slice(0, WAKE_PLAN_ID_MAX) : "";
|
|
@@ -7057,6 +7036,7 @@ function registerPetTools(ctx, home, pet) {
|
|
|
7057
7036
|
}]
|
|
7058
7037
|
},
|
|
7059
7038
|
async execute(args, exec) {
|
|
7039
|
+
requireOwnedSession(exec);
|
|
7060
7040
|
try {
|
|
7061
7041
|
const entry = await resolveTarget(args.pet, exec?.agent?.id);
|
|
7062
7042
|
const list = voiceWakePlans.get(entry.id) ?? [];
|
|
@@ -7168,8 +7148,8 @@ function registerPetTools(ctx, home, pet) {
|
|
|
7168
7148
|
* The `ctx.petService` capability seam — the dsh three-roles pattern applied
|
|
7169
7149
|
* inside the process boundary. Pets are separate processes by design, so this
|
|
7170
7150
|
* service is NOT the pet itself: it is the typed in-process seam over the
|
|
7171
|
-
* out-of-process HTTP control plane. Discovery (pet-http.d/
|
|
7172
|
-
*
|
|
7151
|
+
* out-of-process HTTP control plane. Discovery (the whale598/pet-http.d/
|
|
7152
|
+
* registry), liveness (pid + two-layer probe), retry policy, and the
|
|
7173
7153
|
* /status field matrix (per-implementation extras) are normalized HERE —
|
|
7174
7154
|
* consumers (the driver, the model tools, the settings page, the upcoming
|
|
7175
7155
|
* proactivity scheduler) never see registry files, ports, or raw payloads.
|
|
@@ -7189,11 +7169,12 @@ function petHome() {
|
|
|
7189
7169
|
*
|
|
7190
7170
|
* Implementations must honor these semantics:
|
|
7191
7171
|
* - {@link list} returns only pets whose control plane ANSWERS: a dead pid
|
|
7192
|
-
* and a frozen publisher (listening socket, silent HTTP) are both skipped
|
|
7193
|
-
*
|
|
7194
|
-
*
|
|
7195
|
-
*
|
|
7196
|
-
*
|
|
7172
|
+
* and a frozen publisher (listening socket, silent HTTP) are both skipped,
|
|
7173
|
+
* in boot order (earliest `since` first).
|
|
7174
|
+
* - Interaction BROADCAST is not a route: see `notifyPets` in the registry
|
|
7175
|
+
* module for the all-notify view, and `defaultPet` for unnamed targets.
|
|
7176
|
+
* State audiences are computed by the driver (bound pet alive → that pet,
|
|
7177
|
+
* otherwise every live pet).
|
|
7197
7178
|
* - {@link snapshot} never throws on an unreachable pet — it resolves with
|
|
7198
7179
|
* `online: false`. Field-matrix differences stay here.
|
|
7199
7180
|
* - {@link call} retries once through a fresh discovery pass, so a pet
|
|
@@ -7291,7 +7272,7 @@ function normalizeLastAction(raw) {
|
|
|
7291
7272
|
}
|
|
7292
7273
|
/**
|
|
7293
7274
|
* HTTP control-plane provider: talks to pet processes over the frozen wire
|
|
7294
|
-
* contract,
|
|
7275
|
+
* contract, discovering pets through the whale598/pet-http.d/ registry. A new
|
|
7295
7276
|
* pet implementation is adopted by publishing the same contract — this
|
|
7296
7277
|
* class does not change for it.
|
|
7297
7278
|
*/
|
|
@@ -7306,9 +7287,6 @@ var PetHttpService = class PetHttpService extends PetService {
|
|
|
7306
7287
|
list() {
|
|
7307
7288
|
return readAllPets(this.home);
|
|
7308
7289
|
}
|
|
7309
|
-
async route(boundPetId) {
|
|
7310
|
-
return routePet(await this.list(), boundPetId);
|
|
7311
|
-
}
|
|
7312
7290
|
async snapshot(entry) {
|
|
7313
7291
|
try {
|
|
7314
7292
|
const response = await fetch(`http://127.0.0.1:${String(entry.port)}/status`, { signal: AbortSignal.timeout(1500) });
|
|
@@ -7348,25 +7326,27 @@ var PetHttpService = class PetHttpService extends PetService {
|
|
|
7348
7326
|
async call(entry, path, init) {
|
|
7349
7327
|
const { timeoutMs, retry } = callBudget(path);
|
|
7350
7328
|
const attempts = retry ? 2 : 1;
|
|
7329
|
+
const callerSignal = init?.signal ?? void 0;
|
|
7351
7330
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
7352
7331
|
const current = attempt === 0 ? entry : (await readAllPets(this.home)).find((pet) => pet.id === entry.id);
|
|
7353
7332
|
if (current === void 0) throw new Error(`pet "${entry.id}" is no longer running`);
|
|
7333
|
+
let response;
|
|
7354
7334
|
try {
|
|
7355
7335
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
7356
|
-
const callerSignal = init?.signal ?? void 0;
|
|
7357
7336
|
const signal = callerSignal !== void 0 ? AbortSignal.any([callerSignal, timeout]) : timeout;
|
|
7358
|
-
|
|
7337
|
+
response = await fetch(`http://127.0.0.1:${String(current.port)}${path}`, {
|
|
7359
7338
|
...init,
|
|
7360
7339
|
signal
|
|
7361
7340
|
});
|
|
7362
|
-
if (!response.ok) {
|
|
7363
|
-
const detail = await response.text().catch(() => "");
|
|
7364
|
-
throw new Error(`pet control ${path} answered HTTP ${String(response.status)}${detail ? `: ${detail.slice(0, 300)}` : ""}`);
|
|
7365
|
-
}
|
|
7366
|
-
return await response.json();
|
|
7367
7341
|
} catch (error) {
|
|
7368
|
-
if (attempt === attempts - 1) throw error;
|
|
7342
|
+
if (attempt === attempts - 1 || callerSignal?.aborted === true) throw error;
|
|
7343
|
+
continue;
|
|
7344
|
+
}
|
|
7345
|
+
if (!response.ok) {
|
|
7346
|
+
const detail = await response.text().catch(() => "");
|
|
7347
|
+
throw new Error(`pet control ${path} answered HTTP ${String(response.status)}${detail ? `: ${detail.slice(0, 300)}` : ""}`);
|
|
7369
7348
|
}
|
|
7349
|
+
return await response.json();
|
|
7370
7350
|
}
|
|
7371
7351
|
throw new Error("unreachable");
|
|
7372
7352
|
}
|
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-ZRl_L6yW.js";
|
|
2
2
|
import { appendFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
// lib/types/tools.js
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The pet registry: a directory of per-pet control manifests
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* pet
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* session's bound pet if alive, else the default pet (first alive entry),
|
|
10
|
-
* plus an all-notify broadcast mode for interactions.
|
|
2
|
+
* The pet registry: a directory of per-pet control manifests. Each pet
|
|
3
|
+
* implementation owns its own file under whale598/pet-http.d/ (named by its
|
|
4
|
+
* pet id) and re-asserts it on a cadence; entries whose publisher pid is dead
|
|
5
|
+
* expire by omission. The driver reads the whole table and routes per the
|
|
6
|
+
* multi-pet design: a session's state rides its bound pet when that pet is
|
|
7
|
+
* alive and broadcasts to every live pet otherwise; interactions broadcast to
|
|
8
|
+
* all; the default pet (earliest-booted) resolves unnamed expression targets.
|
|
11
9
|
* @module dsh-agentic-pet/registry
|
|
12
10
|
*/
|
|
13
11
|
/** One pet's published control facts. */
|
|
@@ -30,21 +28,30 @@ export interface PetEntry {
|
|
|
30
28
|
* HTTP probe catches the frozen-but-listening zombies.
|
|
31
29
|
*/
|
|
32
30
|
export declare function probeControlPlane(port: number): Promise<boolean>;
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
31
|
+
/**
|
|
32
|
+
* The default-pet order: earliest booted first (`since` ascending). A missing
|
|
33
|
+
* or mistyped `since` (0) reads as "unknown boot time" and sorts LAST — it
|
|
34
|
+
* must never outrank a publisher that stated its boot time. This turns the
|
|
35
|
+
* multi-pet design's de-facto rule ("the default pet is the first STARTED")
|
|
36
|
+
* into an actual guarantee: discovery output IS boot order, so every
|
|
37
|
+
* `pets[0]` consumer (defaultPet, pet_state's defaultPet) reads the
|
|
38
|
+
* same pet. Stable sort: equal `since` values keep registry listing order.
|
|
39
|
+
*/
|
|
40
|
+
export declare function bootOrder(pets: PetEntry[]): PetEntry[];
|
|
41
|
+
/** Every live pet in boot order. Dead-pid entries are skipped — they expire
|
|
42
|
+
* by omission the next time their file is rewritten — and so are silent
|
|
43
|
+
* entries (pid alive, control plane unresponsive). */
|
|
39
44
|
export declare function readAllPets(home: string): Promise<PetEntry[]>;
|
|
40
45
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
46
|
+
* The default pet for UNNAMED targets: the earliest-booted live entry
|
|
47
|
+
* (`pets[0]` — discovery's bootOrder guarantees start order, not listing
|
|
48
|
+
* accident). State routing does NOT go through here anymore: an unbound
|
|
49
|
+
* (or bound-but-offline) session broadcasts its state to every live pet —
|
|
50
|
+
* the driver computes that audience itself. Used by the tools'
|
|
51
|
+
* resolveTarget when neither a named pet nor the owning session resolves.
|
|
45
52
|
* @returns the entry, or undefined when no pet is alive.
|
|
46
53
|
*/
|
|
47
|
-
export declare function
|
|
54
|
+
export declare function defaultPet(pets: PetEntry[]): PetEntry | undefined;
|
|
48
55
|
/** The pets that receive an INTERACTION (all-notify mode): every live pet,
|
|
49
56
|
* the session-bound one included. The binding routes STATE — it must never
|
|
50
57
|
* mute interactions: excluding the bound pet would silence the only body
|
package/lib/types/service.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* The `ctx.petService` capability seam — the dsh three-roles pattern applied
|
|
3
3
|
* inside the process boundary. Pets are separate processes by design, so this
|
|
4
4
|
* service is NOT the pet itself: it is the typed in-process seam over the
|
|
5
|
-
* out-of-process HTTP control plane. Discovery (pet-http.d/
|
|
6
|
-
*
|
|
5
|
+
* out-of-process HTTP control plane. Discovery (the whale598/pet-http.d/
|
|
6
|
+
* registry), liveness (pid + two-layer probe), retry policy, and the
|
|
7
7
|
* /status field matrix (per-implementation extras) are normalized HERE —
|
|
8
8
|
* consumers (the driver, the model tools, the settings page, the upcoming
|
|
9
9
|
* proactivity scheduler) never see registry files, ports, or raw payloads.
|
|
@@ -77,11 +77,12 @@ export interface PetLastAction {
|
|
|
77
77
|
*
|
|
78
78
|
* Implementations must honor these semantics:
|
|
79
79
|
* - {@link list} returns only pets whose control plane ANSWERS: a dead pid
|
|
80
|
-
* and a frozen publisher (listening socket, silent HTTP) are both skipped
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
80
|
+
* and a frozen publisher (listening socket, silent HTTP) are both skipped,
|
|
81
|
+
* in boot order (earliest `since` first).
|
|
82
|
+
* - Interaction BROADCAST is not a route: see `notifyPets` in the registry
|
|
83
|
+
* module for the all-notify view, and `defaultPet` for unnamed targets.
|
|
84
|
+
* State audiences are computed by the driver (bound pet alive → that pet,
|
|
85
|
+
* otherwise every live pet).
|
|
85
86
|
* - {@link snapshot} never throws on an unreachable pet — it resolves with
|
|
86
87
|
* `online: false`. Field-matrix differences stay here.
|
|
87
88
|
* - {@link call} retries once through a fresh discovery pass, so a pet
|
|
@@ -90,11 +91,9 @@ export interface PetLastAction {
|
|
|
90
91
|
*/
|
|
91
92
|
export declare abstract class PetService extends Service {
|
|
92
93
|
constructor(ctx: Context);
|
|
93
|
-
/** Every live pet (
|
|
94
|
-
*
|
|
94
|
+
/** Every live pet in boot order (earliest `since` first — the default-pet
|
|
95
|
+
* rule is a discovery guarantee, not a listing accident). */
|
|
95
96
|
abstract list(): Promise<PetEntry[]>;
|
|
96
|
-
/** The STATE carrier: the bound pet when alive, else the default pet. */
|
|
97
|
-
abstract route(boundPetId?: string): Promise<PetEntry | undefined>;
|
|
98
97
|
/** The normalized mirror of one pet, or `online: false` when unreachable. */
|
|
99
98
|
abstract snapshot(entry: PetEntry): Promise<PetSnapshot>;
|
|
100
99
|
/** One JSON call to a pet endpoint, with a single fresh-discovery retry. */
|
|
@@ -112,7 +111,7 @@ export declare abstract class PetService extends Service {
|
|
|
112
111
|
export declare function normalizeStatus(id: string, raw: Record<string, unknown>, online: boolean): PetSnapshot;
|
|
113
112
|
/**
|
|
114
113
|
* HTTP control-plane provider: talks to pet processes over the frozen wire
|
|
115
|
-
* contract,
|
|
114
|
+
* contract, discovering pets through the whale598/pet-http.d/ registry. A new
|
|
116
115
|
* pet implementation is adopted by publishing the same contract — this
|
|
117
116
|
* class does not change for it.
|
|
118
117
|
*/
|
|
@@ -122,7 +121,6 @@ export declare class PetHttpService extends PetService {
|
|
|
122
121
|
private static readonly CAPS_TTL_MS;
|
|
123
122
|
constructor(ctx: Context, home?: string);
|
|
124
123
|
list(): Promise<PetEntry[]>;
|
|
125
|
-
route(boundPetId?: string): Promise<PetEntry | undefined>;
|
|
126
124
|
snapshot(entry: PetEntry): Promise<PetSnapshot>;
|
|
127
125
|
capabilities(entry: PetEntry): Promise<PetCapabilities>;
|
|
128
126
|
call(entry: PetEntry, path: string, init?: RequestInit): Promise<unknown>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agentic-pet",
|
|
3
3
|
"description": "The pet-surface bundle: serves the desktop pet renderer over the web composition, turning dsh into a surface the desktop pet attaches to as an authenticated client",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.7",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|