dsh-her 0.0.1 → 0.1.0
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/LICENSE +21 -0
- package/README.md +113 -0
- package/cordis.patch.yml +5 -0
- package/examples/workshop/README.md +20 -0
- package/lib/client.js +1602 -0
- package/lib/core/character.js +50 -0
- package/lib/core/cues.js +62 -0
- package/lib/core/echo.js +27 -0
- package/lib/core/index.js +10 -0
- package/lib/core/paths.js +14 -0
- package/lib/core/prompt.js +63 -0
- package/lib/core/sentences.js +115 -0
- package/lib/core/speakable.js +26 -0
- package/lib/core/voices.js +43 -0
- package/lib/core/wav.js +71 -0
- package/lib/core/workshop.js +330 -0
- package/lib/dsh/api.js +156 -0
- package/lib/dsh/body-install.js +137 -0
- package/lib/dsh/body-launcher.js +48 -0
- package/lib/dsh/config.js +27 -0
- package/lib/dsh/her-session.js +282 -0
- package/lib/dsh/import-status.js +6 -0
- package/lib/dsh/plugin.js +1010 -0
- package/lib/dsh/workshop.js +222 -0
- package/lib/index.js +11 -0
- package/lib/memory/digest.js +119 -0
- package/lib/memory/search.js +52 -0
- package/lib/memory/store.js +267 -0
- package/lib/protocol/index.js +39 -0
- package/lib/server/brain-server.js +219 -0
- package/lib/types/core/character.d.ts +32 -0
- package/lib/types/core/cues.d.ts +20 -0
- package/lib/types/core/echo.d.ts +10 -0
- package/lib/types/core/index.d.ts +10 -0
- package/lib/types/core/paths.d.ts +11 -0
- package/lib/types/core/prompt.d.ts +39 -0
- package/lib/types/core/sentences.d.ts +40 -0
- package/lib/types/core/speakable.d.ts +6 -0
- package/lib/types/core/voices.d.ts +23 -0
- package/lib/types/core/wav.d.ts +9 -0
- package/lib/types/core/workshop.d.ts +156 -0
- package/lib/types/dsh/api.d.ts +163 -0
- package/lib/types/dsh/body-install.d.ts +58 -0
- package/lib/types/dsh/body-launcher.d.ts +28 -0
- package/lib/types/dsh/config.d.ts +34 -0
- package/lib/types/dsh/her-session.d.ts +215 -0
- package/lib/types/dsh/import-status.d.ts +17 -0
- package/lib/types/dsh/plugin.d.ts +34 -0
- package/lib/types/dsh/workshop.d.ts +94 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/memory/digest.d.ts +33 -0
- package/lib/types/memory/search.d.ts +17 -0
- package/lib/types/memory/store.d.ts +132 -0
- package/lib/types/protocol/index.d.ts +434 -0
- package/lib/types/server/brain-server.d.ts +71 -0
- package/lib/types/voice/cloud-key.d.ts +10 -0
- package/lib/types/voice/cosyvoice.d.ts +47 -0
- package/lib/types/voice/edge.d.ts +52 -0
- package/lib/types/voice/engine.d.ts +30 -0
- package/lib/types/voice/models.d.ts +21 -0
- package/lib/types/voice/speech-queue.d.ts +55 -0
- package/lib/types/voice/voice.d.ts +75 -0
- package/lib/types/voice/worker.d.ts +37 -0
- package/lib/voice/cloud-key.js +23 -0
- package/lib/voice/cosyvoice.js +194 -0
- package/lib/voice/edge.js +199 -0
- package/lib/voice/engine.js +101 -0
- package/lib/voice/models.js +69 -0
- package/lib/voice/speech-queue.js +118 -0
- package/lib/voice/voice.js +171 -0
- package/lib/voice/worker.js +47 -0
- package/package.json +61 -4
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP endpoints for the "她" page, on DSH's own authenticated `/api` channel
|
|
3
|
+
* (`ctx.connection.fetch`), so every request has already passed DSH's browser
|
|
4
|
+
* authentication before it gets here.
|
|
5
|
+
*
|
|
6
|
+
* Every endpoint is POST with a JSON body and answers `{ ok: true, … }` or
|
|
7
|
+
* `{ ok: false, message }`. The page polls `/state`; changes answer with the
|
|
8
|
+
* fresh state, so the page never shows something the brain does not have.
|
|
9
|
+
*/
|
|
10
|
+
import type { Character } from '../core/index.ts';
|
|
11
|
+
import type { LookView, PackSummary, WindowMode } from '../protocol/index.ts';
|
|
12
|
+
import type { Fact, Episode } from '../memory/store.ts';
|
|
13
|
+
import type { ReportLine } from './import-status.ts';
|
|
14
|
+
import type { LookProject, WorkshopCard } from '../core/workshop.ts';
|
|
15
|
+
import type { ProjectStatus } from './workshop.ts';
|
|
16
|
+
import type { InstallState } from './body-install.ts';
|
|
17
|
+
export declare const API_BASE = "/api/dsh-her";
|
|
18
|
+
export interface StateView {
|
|
19
|
+
platform: string;
|
|
20
|
+
character: Character;
|
|
21
|
+
/** The model she uses now; `chosen` = picked on the "她" page (else the config's or DSH's default). */
|
|
22
|
+
model: {
|
|
23
|
+
provider: string;
|
|
24
|
+
model: string;
|
|
25
|
+
effort?: string;
|
|
26
|
+
chosen: boolean;
|
|
27
|
+
} | null;
|
|
28
|
+
/** status: listening (local recognition); speaking: her cloud voice. */
|
|
29
|
+
voice: {
|
|
30
|
+
status: string;
|
|
31
|
+
speaking: {
|
|
32
|
+
status: string;
|
|
33
|
+
service: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
};
|
|
36
|
+
id: string;
|
|
37
|
+
speed: number;
|
|
38
|
+
};
|
|
39
|
+
body: {
|
|
40
|
+
connected: boolean;
|
|
41
|
+
/** A body process is running (it may still be connecting). */
|
|
42
|
+
running: boolean;
|
|
43
|
+
/** There is a body to start (the development one, or the installed app). */
|
|
44
|
+
canStart: boolean;
|
|
45
|
+
/** The installed body app: installing, updating, a newer version waiting for a restart. */
|
|
46
|
+
install: InstallState;
|
|
47
|
+
info?: {
|
|
48
|
+
kind: string;
|
|
49
|
+
version: string;
|
|
50
|
+
os: string;
|
|
51
|
+
};
|
|
52
|
+
pack?: PackSummary;
|
|
53
|
+
packs: PackSummary[];
|
|
54
|
+
activePack?: string;
|
|
55
|
+
mode?: WindowMode;
|
|
56
|
+
/** The look on screen, with its scenes (when a look rather than a single pack is chosen). */
|
|
57
|
+
look?: LookView;
|
|
58
|
+
/** The body's screens (logical size, scale), for advice on sizes. */
|
|
59
|
+
screens?: Array<{
|
|
60
|
+
w: number;
|
|
61
|
+
h: number;
|
|
62
|
+
scale: number;
|
|
63
|
+
}>;
|
|
64
|
+
};
|
|
65
|
+
import: {
|
|
66
|
+
state: 'idle' | 'picking' | 'running' | 'done' | 'failed';
|
|
67
|
+
done: number;
|
|
68
|
+
total: number;
|
|
69
|
+
step: string;
|
|
70
|
+
lines: ReportLine[];
|
|
71
|
+
};
|
|
72
|
+
heard?: {
|
|
73
|
+
text: string;
|
|
74
|
+
at: number;
|
|
75
|
+
};
|
|
76
|
+
memory: {
|
|
77
|
+
facts: Fact[];
|
|
78
|
+
episodes: Episode[];
|
|
79
|
+
diary: string[];
|
|
80
|
+
lastDigestDay: string | null;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export interface ApiRuntime {
|
|
84
|
+
state(): StateView;
|
|
85
|
+
saveCharacter(patch: Partial<Character>): Character;
|
|
86
|
+
expandPersona(input: {
|
|
87
|
+
name: string;
|
|
88
|
+
idea: string;
|
|
89
|
+
callYou: string;
|
|
90
|
+
pronoun: string;
|
|
91
|
+
}): Promise<string>;
|
|
92
|
+
tryPersona(input: {
|
|
93
|
+
name: string;
|
|
94
|
+
persona: string;
|
|
95
|
+
callYou: string;
|
|
96
|
+
pronoun: string;
|
|
97
|
+
history: Array<{
|
|
98
|
+
who: 'you' | 'her';
|
|
99
|
+
text: string;
|
|
100
|
+
}>;
|
|
101
|
+
message: string;
|
|
102
|
+
}): Promise<string>;
|
|
103
|
+
previewVoice(id: string, speed: number, text: string): Promise<{
|
|
104
|
+
audio: string;
|
|
105
|
+
format: 'wav' | 'mp3';
|
|
106
|
+
durationMs: number;
|
|
107
|
+
}>;
|
|
108
|
+
startBody(): string;
|
|
109
|
+
/** Download and install the body app (or a newer one); progress shows in the state. */
|
|
110
|
+
installBody(): void;
|
|
111
|
+
/** Stop the running body and start it again (to use a newly installed version). */
|
|
112
|
+
restartBody(): Promise<void>;
|
|
113
|
+
setMode(mode: WindowMode): void;
|
|
114
|
+
pickPack(): void;
|
|
115
|
+
usePack(packId: string): void;
|
|
116
|
+
setLook(change: {
|
|
117
|
+
auto?: boolean;
|
|
118
|
+
locked?: boolean;
|
|
119
|
+
go?: string;
|
|
120
|
+
on?: Record<string, boolean>;
|
|
121
|
+
}): void;
|
|
122
|
+
/** 形象工坊: projects in the materials folder; create, save, open, frame a scene, import. */
|
|
123
|
+
workshopList(): Array<{
|
|
124
|
+
project: LookProject;
|
|
125
|
+
status: ProjectStatus;
|
|
126
|
+
}>;
|
|
127
|
+
workshopNew(name: string): {
|
|
128
|
+
project: LookProject;
|
|
129
|
+
status: ProjectStatus;
|
|
130
|
+
};
|
|
131
|
+
workshopSave(project: unknown): {
|
|
132
|
+
project: LookProject;
|
|
133
|
+
status: ProjectStatus;
|
|
134
|
+
};
|
|
135
|
+
/** Copy a project to a folder the user picks on the body (undefined: cancelled). */
|
|
136
|
+
workshopExport(id: string, media: boolean): Promise<{
|
|
137
|
+
dir: string;
|
|
138
|
+
files: number;
|
|
139
|
+
} | undefined>;
|
|
140
|
+
/** Bring in a project folder (or its 形象.json) the user picks on the body. */
|
|
141
|
+
workshopImportProject(): Promise<{
|
|
142
|
+
project: LookProject;
|
|
143
|
+
status: ProjectStatus;
|
|
144
|
+
} | undefined>;
|
|
145
|
+
workshopExamples(): Array<{
|
|
146
|
+
card: WorkshopCard;
|
|
147
|
+
file: string;
|
|
148
|
+
label: string;
|
|
149
|
+
ready: boolean;
|
|
150
|
+
}>;
|
|
151
|
+
workshopOpen(id: string): void;
|
|
152
|
+
workshopFrame(id: string, sceneId: string): void;
|
|
153
|
+
workshopImport(id: string): void;
|
|
154
|
+
memoryRemove(ids: string[]): number;
|
|
155
|
+
memoryConfirm(ids: string[]): number;
|
|
156
|
+
digest(): Promise<string>;
|
|
157
|
+
exportMemory(): string;
|
|
158
|
+
}
|
|
159
|
+
type Body = Record<string, unknown>;
|
|
160
|
+
type Handler = (body: Body) => Promise<Record<string, unknown>>;
|
|
161
|
+
export declare function apiHandlers(rt: ApiRuntime): Record<string, Handler>;
|
|
162
|
+
export declare function handle(handler: Handler, request: Request): Promise<Response>;
|
|
163
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The installed body: downloaded from the release (body/scripts/package.mjs
|
|
3
|
+
* makes it), checked, unpacked into `<home>/body/<version>/`, and kept up to
|
|
4
|
+
* date. The release has `body-latest.json`:
|
|
5
|
+
*
|
|
6
|
+
* { "format": "dsh-her-body@1", "version": "0.1.0",
|
|
7
|
+
* "assets": { "darwin-arm64": { "file": "dsh-her-body-0.1.0-darwin-arm64.zip", "size": …, "sha256": "…" } } }
|
|
8
|
+
*
|
|
9
|
+
* `<home>/body/current.json` says which version is installed. A download
|
|
10
|
+
* that does not match its SHA-256 is thrown away; the previous version stays
|
|
11
|
+
* until the new one is in place, and one older version is kept to go back to.
|
|
12
|
+
*/
|
|
13
|
+
import type { LaunchPlan } from './body-launcher.ts';
|
|
14
|
+
export declare const MANIFEST = "body-latest.json";
|
|
15
|
+
export interface BodyAsset {
|
|
16
|
+
file: string;
|
|
17
|
+
size: number;
|
|
18
|
+
sha256: string;
|
|
19
|
+
}
|
|
20
|
+
export interface BodyManifest {
|
|
21
|
+
format: 'dsh-her-body@1';
|
|
22
|
+
version: string;
|
|
23
|
+
assets: Record<string, BodyAsset>;
|
|
24
|
+
}
|
|
25
|
+
export interface Installed {
|
|
26
|
+
version: string;
|
|
27
|
+
/** The app (macOS: dsh-her.app) inside `<home>/body/<version>/`. */
|
|
28
|
+
app: string;
|
|
29
|
+
installedAt: string;
|
|
30
|
+
}
|
|
31
|
+
/** What the "她" page shows about the installed body. */
|
|
32
|
+
export interface InstallState {
|
|
33
|
+
state: 'idle' | 'checking' | 'downloading' | 'installing' | 'failed';
|
|
34
|
+
installed?: string;
|
|
35
|
+
/** The newest version in the release, when checked. */
|
|
36
|
+
latest?: string;
|
|
37
|
+
/** A newer version is installed but the running body is still the old one. */
|
|
38
|
+
restart?: boolean;
|
|
39
|
+
done?: number;
|
|
40
|
+
total?: number;
|
|
41
|
+
error?: string;
|
|
42
|
+
/** This install runs the development body from the repository instead. */
|
|
43
|
+
dev: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** This computer's asset key in the manifest. */
|
|
46
|
+
export declare const platformKey: (platform?: NodeJS.Platform, arch?: NodeJS.Architecture) => string;
|
|
47
|
+
/** -1, 0, 1: "0.1.0-dev.0" < "0.1.0" < "0.1.1" < "0.2.0". */
|
|
48
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
49
|
+
export declare function validateManifest(value: unknown): BodyManifest;
|
|
50
|
+
export declare function installedBody(home: string): Installed | undefined;
|
|
51
|
+
/** How to start the installed body. */
|
|
52
|
+
export declare function installedPlan(home: string): LaunchPlan | undefined;
|
|
53
|
+
export declare function fetchManifest(base: string): Promise<BodyManifest>;
|
|
54
|
+
/**
|
|
55
|
+
* Download this computer's asset of `manifest`, check it, unpack it next to
|
|
56
|
+
* the installed version and make it current. Progress in bytes.
|
|
57
|
+
*/
|
|
58
|
+
export declare function install(home: string, base: string, manifest: BodyManifest, onProgress: (done: number, total: number) => void): Promise<Installed>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start the body app when no body is connected: the development body (the
|
|
3
|
+
* repository's `body/` next to this package, built with `npm run build`
|
|
4
|
+
* there) when there is one, else the installed app (body-install.ts).
|
|
5
|
+
*
|
|
6
|
+
* The Electron binary is found through `node_modules/electron/path.txt`, not
|
|
7
|
+
* `require('electron')`: inside the DSH desktop app that require returns
|
|
8
|
+
* Electron's API instead of the binary path. The body is detached so it
|
|
9
|
+
* outlives DSH (she sleeps until DSH is back), and ELECTRON_RUN_AS_NODE is
|
|
10
|
+
* removed or the body would start as plain Node.
|
|
11
|
+
*/
|
|
12
|
+
export interface LaunchPlan {
|
|
13
|
+
binary: string;
|
|
14
|
+
args: string[];
|
|
15
|
+
cwd: string;
|
|
16
|
+
}
|
|
17
|
+
/** The development body next to this package, if it is built. */
|
|
18
|
+
export declare function findDevBody(packageRoot?: string): LaunchPlan | {
|
|
19
|
+
missing: string;
|
|
20
|
+
};
|
|
21
|
+
/** A body that is running but not connected yet (still backing off) has its pid in `<home>/body.json`. */
|
|
22
|
+
export declare function bodyRunning(home: string): boolean;
|
|
23
|
+
export declare function launchBody(plan: LaunchPlan, env: {
|
|
24
|
+
url: string;
|
|
25
|
+
token: string;
|
|
26
|
+
home: string;
|
|
27
|
+
pack: string;
|
|
28
|
+
}): number | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
2
|
+
import type { VoiceConfig } from '../voice/voice.ts';
|
|
3
|
+
export interface HerConfig {
|
|
4
|
+
/** Where dsh-her keeps characters, packs, models and the discovery file, outside DSH. `~` expands. */
|
|
5
|
+
home: string;
|
|
6
|
+
character: {
|
|
7
|
+
/** Stable id; her session and memory are keyed by it. */
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
persona: string;
|
|
11
|
+
};
|
|
12
|
+
/** Model route for her session; both empty = DSH's current default model. A fast, non-reasoning model keeps her quick to answer. */
|
|
13
|
+
provider: string;
|
|
14
|
+
model: string;
|
|
15
|
+
/** Reasoning effort for her model route; empty = the route's default. Lower answers sooner. */
|
|
16
|
+
reasoningEffort: string;
|
|
17
|
+
/** Preferred port of the body protocol server (the next free one is used if taken). */
|
|
18
|
+
port: number;
|
|
19
|
+
body: {
|
|
20
|
+
/** auto: start the body app when DSH starts and no body is connected; off: never. */
|
|
21
|
+
launch: 'auto' | 'off';
|
|
22
|
+
/** Appearance pack directory passed to the body; empty = the body's default. */
|
|
23
|
+
pack: string;
|
|
24
|
+
/** Where the body app is downloaded from: the release's address, ending with the folder that has body-latest.json. */
|
|
25
|
+
releases: string;
|
|
26
|
+
/** Start the development body in the repository when there is one (off: behave like a release install). */
|
|
27
|
+
useDev: boolean;
|
|
28
|
+
};
|
|
29
|
+
voice: VoiceConfig & {
|
|
30
|
+
/** How the body listens: one utterance per shortcut press, or all the time. */
|
|
31
|
+
mic: 'push-to-talk' | 'always';
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare const Config: Schema<HerConfig>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Her own session in DSH ("她 · <name>"): one ordinary persisted session per
|
|
3
|
+
* character. Created once, resumed after every restart; its id is kept under
|
|
4
|
+
* the dsh-her home, not in DSH. The creation follows dsh-vael's SelfSession.
|
|
5
|
+
*
|
|
6
|
+
* Inside her agent's scoped context she gets her own system prompt (DSH's
|
|
7
|
+
* coding-agent prompt and runtime chatter are replaced) and a listener on her
|
|
8
|
+
* live assistant stream, which turns text deltas into speakable sentences.
|
|
9
|
+
* Reasoning and tool-call deltas are never spoken.
|
|
10
|
+
*
|
|
11
|
+
* Her prompt is rebuilt at every model request, so its performance section
|
|
12
|
+
* always matches the body that is connected and what she remembers is current.
|
|
13
|
+
* Her tools: `play_action` (a longer performance by the body) and `remember`,
|
|
14
|
+
* `recall`, `forget` (her memory).
|
|
15
|
+
*/
|
|
16
|
+
import { type Character } from '../core/index.ts';
|
|
17
|
+
import type { MemoryProvider } from '../memory/store.ts';
|
|
18
|
+
export interface ModelRoute {
|
|
19
|
+
provider: string;
|
|
20
|
+
model: string;
|
|
21
|
+
reasoningEffort?: string;
|
|
22
|
+
}
|
|
23
|
+
interface UserMessageLike {
|
|
24
|
+
id: string;
|
|
25
|
+
role: 'user';
|
|
26
|
+
content: Array<{
|
|
27
|
+
type: 'text';
|
|
28
|
+
text: string;
|
|
29
|
+
}>;
|
|
30
|
+
source: {
|
|
31
|
+
kind: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
interface AgentLike {
|
|
36
|
+
readonly session: {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
};
|
|
39
|
+
followup(message: UserMessageLike): void;
|
|
40
|
+
/** Context for the next model request, without starting a turn. */
|
|
41
|
+
inject(message: UserMessageLike): void;
|
|
42
|
+
cancel(cause?: unknown): void;
|
|
43
|
+
whenIdle(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
interface AgentHandleLike {
|
|
46
|
+
readonly agent: AgentLike;
|
|
47
|
+
dispose(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/** One live publication of her assistant stream (`agent/assistant-stream`). */
|
|
50
|
+
export type StreamFrame = {
|
|
51
|
+
type: 'start';
|
|
52
|
+
attemptId: string;
|
|
53
|
+
turn: number;
|
|
54
|
+
step: number;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'chunk';
|
|
57
|
+
attemptId: string;
|
|
58
|
+
index: number;
|
|
59
|
+
time: number;
|
|
60
|
+
chunk: {
|
|
61
|
+
type: string;
|
|
62
|
+
text?: string;
|
|
63
|
+
};
|
|
64
|
+
} | {
|
|
65
|
+
type: 'end';
|
|
66
|
+
attemptId: string;
|
|
67
|
+
index: number;
|
|
68
|
+
outcome: {
|
|
69
|
+
kind: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export interface ScopedContext {
|
|
73
|
+
tools: {
|
|
74
|
+
register(definition: unknown): () => void;
|
|
75
|
+
/** Tool schemas as a scope sees them; no scope = the global surface. */
|
|
76
|
+
schemas(scope?: unknown): Array<{
|
|
77
|
+
name: string;
|
|
78
|
+
}>;
|
|
79
|
+
/** Hide global tools from the calling agent scope (scoped registrations stay visible). */
|
|
80
|
+
restrict(filter: {
|
|
81
|
+
allow?: readonly string[];
|
|
82
|
+
deny?: readonly string[];
|
|
83
|
+
}): () => void;
|
|
84
|
+
};
|
|
85
|
+
systemPrompt?: {
|
|
86
|
+
section(definition: {
|
|
87
|
+
name: string;
|
|
88
|
+
order: number;
|
|
89
|
+
text: string | (() => string);
|
|
90
|
+
complete?: boolean;
|
|
91
|
+
interpolate?: boolean;
|
|
92
|
+
}): unknown;
|
|
93
|
+
suppressRuntimeContext?(): unknown;
|
|
94
|
+
};
|
|
95
|
+
on(event: 'agent/assistant-stream', listener: (payload: {
|
|
96
|
+
frame: StreamFrame;
|
|
97
|
+
}) => void): () => void;
|
|
98
|
+
}
|
|
99
|
+
export interface DshServices {
|
|
100
|
+
agents: {
|
|
101
|
+
create(options: {
|
|
102
|
+
sessionId: string;
|
|
103
|
+
meta?: {
|
|
104
|
+
cwd?: string;
|
|
105
|
+
};
|
|
106
|
+
agentOptions?: ModelRoute;
|
|
107
|
+
setup?: (agentCtx: ScopedContext) => void | Promise<void>;
|
|
108
|
+
}): Promise<AgentHandleLike>;
|
|
109
|
+
resume(options: {
|
|
110
|
+
resumeSessionId: string;
|
|
111
|
+
agentOptions?: ModelRoute;
|
|
112
|
+
setup?: (agentCtx: ScopedContext) => void | Promise<void>;
|
|
113
|
+
}): Promise<AgentHandleLike>;
|
|
114
|
+
};
|
|
115
|
+
agentDefaultModel?: {
|
|
116
|
+
currentSelection(): {
|
|
117
|
+
provider: string;
|
|
118
|
+
model: string;
|
|
119
|
+
} | undefined;
|
|
120
|
+
};
|
|
121
|
+
workspaceRegistry?: {
|
|
122
|
+
create(path: string, title?: string): Promise<{
|
|
123
|
+
attachSession(id: string): Promise<void>;
|
|
124
|
+
}>;
|
|
125
|
+
};
|
|
126
|
+
sessionTitle?: {
|
|
127
|
+
rename(session: unknown, title: string): void;
|
|
128
|
+
};
|
|
129
|
+
sessionQuery?: {
|
|
130
|
+
readSession(id: string): Promise<{
|
|
131
|
+
events: Array<{
|
|
132
|
+
seq: number;
|
|
133
|
+
type: string;
|
|
134
|
+
data: unknown;
|
|
135
|
+
}>;
|
|
136
|
+
}>;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* What her stream produces, per reply: one `start`, any number of `sentence`s,
|
|
141
|
+
* one `end`. `replyId` is unique across turns.
|
|
142
|
+
*/
|
|
143
|
+
export interface ReplyEvents {
|
|
144
|
+
start(replyId: string): void;
|
|
145
|
+
sentence(replyId: string, text: string): void;
|
|
146
|
+
end(replyId: string, outcome: string): void;
|
|
147
|
+
}
|
|
148
|
+
export interface HerSessionDeps {
|
|
149
|
+
cid: string;
|
|
150
|
+
/** Who she is right now (the "她" page can change it at any time). */
|
|
151
|
+
character: () => Pick<Character, 'name' | 'persona' | 'callYou' | 'pronoun'>;
|
|
152
|
+
/** The performance section for the connected body (empty without one). */
|
|
153
|
+
performance: () => string;
|
|
154
|
+
/** Have the body perform an action; resolves when it is done. */
|
|
155
|
+
playAction: (action: string) => Promise<{
|
|
156
|
+
ok: boolean;
|
|
157
|
+
reason?: string;
|
|
158
|
+
transcript?: string;
|
|
159
|
+
}>;
|
|
160
|
+
/** "关于对方" for her prompt, from memory. */
|
|
161
|
+
aboutYou: () => string;
|
|
162
|
+
memory: MemoryProvider;
|
|
163
|
+
/** dsh-her home for this character: session.json and her room live here. */
|
|
164
|
+
dir: string;
|
|
165
|
+
route: ModelRoute | undefined;
|
|
166
|
+
services: () => DshServices | undefined;
|
|
167
|
+
logger: {
|
|
168
|
+
info(message: string): void;
|
|
169
|
+
warn(message: string): void;
|
|
170
|
+
};
|
|
171
|
+
events: ReplyEvents;
|
|
172
|
+
}
|
|
173
|
+
export declare class HerSession {
|
|
174
|
+
private handle;
|
|
175
|
+
private opening;
|
|
176
|
+
private readonly splitter;
|
|
177
|
+
/** Set by interrupt(): the rest of the current reply is not spoken. */
|
|
178
|
+
private dropping;
|
|
179
|
+
/** The reply being streamed, unique across turns. */
|
|
180
|
+
private replyId;
|
|
181
|
+
private readonly deps;
|
|
182
|
+
constructor(deps: HerSessionDeps);
|
|
183
|
+
get title(): string;
|
|
184
|
+
get sessionId(): string | undefined;
|
|
185
|
+
/** Her live agent: reuse, resume the recorded session, or create it the first time. */
|
|
186
|
+
ensure(): Promise<AgentHandleLike>;
|
|
187
|
+
/** Send what you said (typed or recognized) as a new turn. */
|
|
188
|
+
say(text: string, via: 'voice' | 'text' | 'command'): Promise<AgentLike>;
|
|
189
|
+
/** Tell her something for her next turn without making her answer (what was heard of a cut-off reply, …). */
|
|
190
|
+
note(text: string): Promise<void>;
|
|
191
|
+
/** Stop the reply in progress (barge-in). DSH keeps the delivered prefix marked `interrupted`. */
|
|
192
|
+
interrupt(): void;
|
|
193
|
+
dispose(): Promise<void>;
|
|
194
|
+
private recordFile;
|
|
195
|
+
private recorded;
|
|
196
|
+
private open;
|
|
197
|
+
private composeScope;
|
|
198
|
+
/**
|
|
199
|
+
* Her tools, in DSH's native tool shape (plain JSON Schema). Not through
|
|
200
|
+
* `defineTool`: a plugin linked from outside the profile cannot import
|
|
201
|
+
* dsh-tools, and an unconverted schema makes the model provider reject the
|
|
202
|
+
* whole request.
|
|
203
|
+
*/
|
|
204
|
+
private safe;
|
|
205
|
+
private registerTools;
|
|
206
|
+
/**
|
|
207
|
+
* P1: she only keeps you company, so DSH's work tools (shell, files, web…)
|
|
208
|
+
* are hidden from her session. Their schemas would also cost thousands of
|
|
209
|
+
* tokens on every turn. Tools she gets later are registered in her own scope,
|
|
210
|
+
* which a restriction never hides.
|
|
211
|
+
*/
|
|
212
|
+
private hideGlobalTools;
|
|
213
|
+
private onFrame;
|
|
214
|
+
}
|
|
215
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a pack import started from the "她" page stands. The import itself
|
|
3
|
+
* runs in the body (it reads your folder and has ffmpeg); the body reports
|
|
4
|
+
* progress and a report, which the page shows by polling the brain's state.
|
|
5
|
+
*/
|
|
6
|
+
export interface ReportLine {
|
|
7
|
+
level: 'ok' | 'warn' | 'error';
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ImportStatus {
|
|
11
|
+
state: 'idle' | 'picking' | 'running' | 'done' | 'failed';
|
|
12
|
+
done: number;
|
|
13
|
+
total: number;
|
|
14
|
+
step: string;
|
|
15
|
+
lines: ReportLine[];
|
|
16
|
+
}
|
|
17
|
+
export declare const IDLE_IMPORT: ImportStatus;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DSH shell: opens her session as soon as DSH's agent services are ready,
|
|
3
|
+
* serves the body protocol, starts the body, and exposes `/her`. Everything
|
|
4
|
+
* hangs off `ctx.effect`, so disabling or uninstalling the plugin closes her
|
|
5
|
+
* session handle and the server (the body stays up and waits, asleep).
|
|
6
|
+
*
|
|
7
|
+
* Every reply of her session, whoever prompted it (the body's input box or
|
|
8
|
+
* microphone, the DSH page, `/her`), goes to connected bodies as `speak.*`
|
|
9
|
+
* sentences, each with its synthesized audio (or, without a voice, timed by
|
|
10
|
+
* reading speed). Replies are live only: a body that connects later never gets
|
|
11
|
+
* earlier ones replayed.
|
|
12
|
+
*
|
|
13
|
+
* Listening: a body sends what you said as audio; it is recognized here and
|
|
14
|
+
* goes to her like typed text. When a body cuts her off (you started talking),
|
|
15
|
+
* it reports how far she got; her next turn is told what you actually heard.
|
|
16
|
+
*
|
|
17
|
+
* Performing: the connected body's pack decides what she can do. Her prompt
|
|
18
|
+
* lists it; inline tags in her reply become cues on the sentence they are in,
|
|
19
|
+
* and longer performances go through her `play_action` tool, which waits for
|
|
20
|
+
* the body's `act.done`.
|
|
21
|
+
*
|
|
22
|
+
* Memory: files under `<home>/characters/<cid>/memory/` (src/memory). She
|
|
23
|
+
* writes with `remember`, reads with `recall`, and her prompt carries what she
|
|
24
|
+
* knows about you, her last diary days and what has a date today or tomorrow.
|
|
25
|
+
* A minute after each talk, a short note picks out of the new turns what is
|
|
26
|
+
* worth keeping (as candidates), so it shows on the "她记得的事" page at once.
|
|
27
|
+
* Once a day (30 minutes after you last talked, or from 23:30) a digest reads
|
|
28
|
+
* the day back from her session and writes her diary page and candidates.
|
|
29
|
+
*
|
|
30
|
+
* Her replies are also logged with timings to `<home>/logs/stream.jsonl`.
|
|
31
|
+
*/
|
|
32
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
33
|
+
import type { HerConfig } from './config.ts';
|
|
34
|
+
export declare function apply(ctx: Context, config: HerConfig): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 形象工坊 on disk: each project is a folder under `~/Documents/dsh-her 素材`
|
|
3
|
+
* (where the user can see it and drop files in), with
|
|
4
|
+
*
|
|
5
|
+
* 形象.json the project (character card, scenes, clips)
|
|
6
|
+
* 参考图提示词.txt how to make 参考图.png, her "ID photo"
|
|
7
|
+
* 参考图.png (the user's) — also the desk pet's anchor
|
|
8
|
+
* 生成清单.md every clip: first frame, file, prompt
|
|
9
|
+
* MiniMax生成任务.md the same, as a task for MiniMax Code (when that is the tool)
|
|
10
|
+
* ComfyUI生成清单.json the same, for a ComfyUI script (when that is the tool)
|
|
11
|
+
* 桌宠/提示词.txt + the desk pet's clips
|
|
12
|
+
* 场景-卧室/提示词.txt + 照片提示词.txt, plate.png, scene.json, crop.png, clips
|
|
13
|
+
*
|
|
14
|
+
* The prompts are rewritten on every save; the user's pictures and clips are
|
|
15
|
+
* never touched. Importing hands the folders to the body (same computer).
|
|
16
|
+
*/
|
|
17
|
+
import { type LookProject, type WorkshopCard } from '../core/workshop.ts';
|
|
18
|
+
export declare const MATERIALS_DIR: string;
|
|
19
|
+
export interface PartStatus {
|
|
20
|
+
/** 桌宠, or the scene's name. */
|
|
21
|
+
name: string;
|
|
22
|
+
folder: string;
|
|
23
|
+
/** Clips planned, and how many of them are in the folder. */
|
|
24
|
+
planned: number;
|
|
25
|
+
made: number;
|
|
26
|
+
/** The minimum (idle and two talks) is there. */
|
|
27
|
+
enough: boolean;
|
|
28
|
+
/** Scenes: the photo, and her region framed on it (scene.json and crop.png). */
|
|
29
|
+
photo?: boolean;
|
|
30
|
+
framed?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ProjectStatus {
|
|
33
|
+
dir: string;
|
|
34
|
+
reference: boolean;
|
|
35
|
+
parts: PartStatus[];
|
|
36
|
+
/** Something can be imported: at least one part has its minimum (and a scene its frame). */
|
|
37
|
+
importable: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Every project in the materials folder, newest first. */
|
|
40
|
+
export declare function listProjects(root?: string): Array<{
|
|
41
|
+
project: LookProject;
|
|
42
|
+
status: ProjectStatus;
|
|
43
|
+
}>;
|
|
44
|
+
export declare function findProject(id: string, root?: string): {
|
|
45
|
+
project: LookProject;
|
|
46
|
+
dir: string;
|
|
47
|
+
} | undefined;
|
|
48
|
+
export declare function projectStatus(project: LookProject, dir: string): ProjectStatus;
|
|
49
|
+
/**
|
|
50
|
+
* Write a project: its folder (made on first save, named after it), 形象.json
|
|
51
|
+
* and every prompt file. Pictures and clips already there stay as they are.
|
|
52
|
+
*/
|
|
53
|
+
export declare function saveProject(input: unknown, screen?: {
|
|
54
|
+
width: number;
|
|
55
|
+
height: number;
|
|
56
|
+
}, root?: string): {
|
|
57
|
+
project: LookProject;
|
|
58
|
+
status: ProjectStatus;
|
|
59
|
+
};
|
|
60
|
+
/** Before importing: the desk pet's anchor is the reference picture. */
|
|
61
|
+
export declare function prepareImport(project: LookProject, dir: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Copy a project to another folder (`dest`/<its name>): its settings
|
|
64
|
+
* (形象.json, the prompts, scene.json), and with `media` its pictures and
|
|
65
|
+
* clips too. The copy can be imported anywhere with importProject.
|
|
66
|
+
*/
|
|
67
|
+
export declare function exportProject(id: string, dest: string, media: boolean, root?: string): {
|
|
68
|
+
dir: string;
|
|
69
|
+
files: number;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Bring a project in from elsewhere: a folder with 形象.json in it, or the
|
|
73
|
+
* 形象.json itself. It becomes a new project in the materials folder (a new
|
|
74
|
+
* id if one with its id is there already), with whatever pictures and clips
|
|
75
|
+
* came along; the prompts are written afresh.
|
|
76
|
+
*/
|
|
77
|
+
export declare function importProject(path: string, screen?: {
|
|
78
|
+
width: number;
|
|
79
|
+
height: number;
|
|
80
|
+
}, root?: string): {
|
|
81
|
+
project: LookProject;
|
|
82
|
+
status: ProjectStatus;
|
|
83
|
+
};
|
|
84
|
+
/** Where examples are looked for: the user's own first, then the plugin's. */
|
|
85
|
+
export declare function exampleDirs(home: string): string[];
|
|
86
|
+
/** The file to serve for an example slot, if it has been put in place. */
|
|
87
|
+
export declare function exampleFile(home: string, file: string): string | undefined;
|
|
88
|
+
/** Every card's example slots and whether each is there yet. */
|
|
89
|
+
export declare function examples(home: string): Array<{
|
|
90
|
+
card: WorkshopCard;
|
|
91
|
+
file: string;
|
|
92
|
+
label: string;
|
|
93
|
+
ready: boolean;
|
|
94
|
+
}>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-her — a desktop companion for DSH.
|
|
3
|
+
*
|
|
4
|
+
* The brain lives here: her own DSH session, voice, memory, and the body
|
|
5
|
+
* protocol server. The body (the Electron desktop app) lives in `body/`.
|
|
6
|
+
* `src/core` never imports DSH; `src/dsh` is the only layer that does.
|
|
7
|
+
*/
|
|
8
|
+
export declare const name = "dsh-her";
|
|
9
|
+
export declare const usage = "dsh-her: a desktop companion who lives on your real desktop, talks by voice and remembers you. /her talks to her.";
|
|
10
|
+
export { Config, type HerConfig } from './dsh/config.ts';
|
|
11
|
+
export { apply } from './dsh/plugin.ts';
|