@co0ontty/wand 4.89.0 → 4.89.2-beta.g9f64cf7
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/dist/agent-dispatch.d.ts +5 -0
- package/dist/agent-dispatch.js +8 -0
- package/dist/ai-team-employee-binding.d.ts +1 -0
- package/dist/ai-team-employee-binding.js +1 -1
- package/dist/ai-team-runner.d.ts +1 -1
- package/dist/ai-team-runner.js +13 -5
- package/dist/ai-team-types.d.ts +2 -2
- package/dist/ai-team-types.js +3 -2
- package/dist/build-info.json +4 -4
- package/dist/commit-task-archive.d.ts +3 -0
- package/dist/commit-task-archive.js +15 -0
- package/dist/config.js +1 -0
- package/dist/daemon-maintenance-targets.js +1 -1
- package/dist/decision-expert-employee.d.ts +1 -1
- package/dist/decision-expert-employee.js +3 -3
- package/dist/decision-expert-service.d.ts +6 -5
- package/dist/decision-expert-service.js +75 -49
- package/dist/decision-types.d.ts +2 -1
- package/dist/default-employee.d.ts +5 -1
- package/dist/default-employee.js +17 -5
- package/dist/employee-text.d.ts +28 -0
- package/dist/employee-text.js +124 -0
- package/dist/fixed-employee-avatar.d.ts +19 -0
- package/dist/fixed-employee-avatar.js +13 -0
- package/dist/git-quick-commit.d.ts +13 -4
- package/dist/git-quick-commit.js +112 -33
- package/dist/local-model-setup.d.ts +7 -1
- package/dist/local-model-setup.js +54 -7
- package/dist/local-model-types.d.ts +2 -1
- package/dist/plush-avatar.d.ts +100 -0
- package/dist/plush-avatar.js +96 -0
- package/dist/process-manager.d.ts +4 -1
- package/dist/process-manager.js +9 -7
- package/dist/server-employee-routes.d.ts +6 -2
- package/dist/server-employee-routes.js +55 -53
- package/dist/server-file-routes.js +61 -25
- package/dist/server-local-model-routes.js +12 -0
- package/dist/server-session-routes.d.ts +2 -1
- package/dist/server-session-routes.js +13 -10
- package/dist/server-speech-routes.d.ts +2 -0
- package/dist/server-speech-routes.js +7 -1
- package/dist/server-task-routes.d.ts +3 -0
- package/dist/server-task-routes.js +19 -10
- package/dist/server.js +25 -8
- package/dist/session-ai-context.d.ts +6 -4
- package/dist/session-ai-context.js +10 -9
- package/dist/silicon-employee-dispatch.d.ts +4 -6
- package/dist/silicon-employee-dispatch.js +9 -6
- package/dist/speech-polisher-employee.d.ts +6 -0
- package/dist/speech-polisher-employee.js +23 -0
- package/dist/speech-polisher-identity.d.ts +4 -0
- package/dist/speech-polisher-identity.js +4 -0
- package/dist/speech-polisher-service.d.ts +31 -0
- package/dist/speech-polisher-service.js +88 -0
- package/dist/speech-service.d.ts +3 -1
- package/dist/speech-service.js +15 -4
- package/dist/speech-support.d.ts +6 -0
- package/dist/speech-support.js +26 -0
- package/dist/speech-types.d.ts +5 -0
- package/dist/storage.d.ts +13 -4
- package/dist/storage.js +84 -32
- package/dist/structured-session-manager.d.ts +3 -2
- package/dist/structured-session-manager.js +11 -7
- package/dist/system-employee.d.ts +6 -4
- package/dist/system-employee.js +13 -10
- package/dist/types.d.ts +3 -1
- package/dist/user-memory.d.ts +2 -0
- package/dist/user-memory.js +1 -1
- package/dist/wand-task-sync.js +13 -2
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/plush-avatar.js +4220 -0
- package/dist/web-ui/content/scripts.js +572 -209
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +2 -0
- package/dist/web-ui/embedded-assets.js +5 -3
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/provider-identity.d.ts +2 -0
- package/dist/web-ui/provider-identity.js +7 -1
- package/dist/web-ui/scripts.d.ts +7 -0
- package/dist/web-ui/scripts.js +30 -5
- package/package.json +3 -1
|
@@ -7,6 +7,7 @@ import { LAYA_FILES, LAYA_REPOSITORY, layaFileUrl } from "./laya-model-manifest.
|
|
|
7
7
|
import { ModelFileStore } from "./model-file-store.js";
|
|
8
8
|
import { runModelSetup } from "./model-setup-process.js";
|
|
9
9
|
import { speechModel } from "./speech-models.js";
|
|
10
|
+
import { speechSupport } from "./speech-support.js";
|
|
10
11
|
import { systemEnvValue } from "./env-utils.js";
|
|
11
12
|
import { decisionHardware } from "./decision-hardware.js";
|
|
12
13
|
import { DECISION_EXPERT_ID } from "./decision-expert-identity.js";
|
|
@@ -20,6 +21,8 @@ export class LocalModelSetupService {
|
|
|
20
21
|
jobs = new Map();
|
|
21
22
|
sequence = 0;
|
|
22
23
|
disposed = false;
|
|
24
|
+
speechEnableRevision = 0;
|
|
25
|
+
supportCache = null;
|
|
23
26
|
constructor(deps) {
|
|
24
27
|
this.deps = deps;
|
|
25
28
|
this.files = new ModelFileStore(deps.fetch);
|
|
@@ -55,6 +58,7 @@ export class LocalModelSetupService {
|
|
|
55
58
|
const hardware = decisionHardware();
|
|
56
59
|
const operation = this.operations.get("laya") ?? null;
|
|
57
60
|
const selected = speech.models.find((value) => value.id === speech.settings.model);
|
|
61
|
+
const support = await this.speechSupport(speech.runtime.available);
|
|
58
62
|
return {
|
|
59
63
|
laya: { kind: "laya", label: "LAYA 本地决策", supported,
|
|
60
64
|
reason: !supported ? hardware.message
|
|
@@ -62,12 +66,38 @@ export class LocalModelSetupService {
|
|
|
62
66
|
enabled: decision.enabled, model: LAYA_REPOSITORY, modelSize: this.manifest.reduce((sum, file) => sum + file.size, 0),
|
|
63
67
|
downloaded: model.downloaded, runtimeAvailable: !!python, initialized: decision.state === "ready", busy: this.jobs.has("laya") || decision.queued > 0 || decision.state === "loading",
|
|
64
68
|
operation: operation ? { ...operation } : null, hardware, decisionEmployeeId: DECISION_EXPERT_ID },
|
|
65
|
-
speech: { kind: "speech", label: "服务端语音识别", supported:
|
|
69
|
+
speech: { kind: "speech", label: "服务端语音识别", supported: support.supported, supportReason: support.reason, reason: speech.reason,
|
|
66
70
|
enabled: speech.settings.enabled, model: speech.settings.model, modelSize: selected?.size ?? 0,
|
|
67
71
|
downloaded: selected?.downloaded ?? false, runtimeAvailable: speech.runtime.available, initialized: speech.initialized === true,
|
|
68
72
|
busy: speech.busy || this.jobs.has("speech"), operation: this.operations.has("speech") ? { ...this.operations.get("speech") } : null },
|
|
69
73
|
};
|
|
70
74
|
}
|
|
75
|
+
speechSupport(runtimeAvailable) {
|
|
76
|
+
if (!this.supportCache || this.supportCache.expires < Date.now() || this.supportCache.runtimeAvailable !== runtimeAvailable) {
|
|
77
|
+
this.supportCache = { expires: Date.now() + 30_000, runtimeAvailable, value: (this.deps.speechSupport ?? speechSupport)(runtimeAvailable) };
|
|
78
|
+
}
|
|
79
|
+
return this.supportCache.value;
|
|
80
|
+
}
|
|
81
|
+
async setSpeechEnabled(enabled) {
|
|
82
|
+
const revision = ++this.speechEnableRevision;
|
|
83
|
+
const settingsRevision = this.deps.speech.settingsRevision();
|
|
84
|
+
if (this.disposed)
|
|
85
|
+
throw new DecisionError("UNAVAILABLE", "模型管理已关闭。", 503);
|
|
86
|
+
if (!enabled) {
|
|
87
|
+
this.cancel("speech");
|
|
88
|
+
this.deps.speech.configure({ enabled: false });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const status = await this.deps.speech.status();
|
|
92
|
+
const support = await this.speechSupport(status.runtime.available);
|
|
93
|
+
if (revision !== this.speechEnableRevision || this.disposed || settingsRevision !== this.deps.speech.settingsRevision())
|
|
94
|
+
return;
|
|
95
|
+
if (!support.supported)
|
|
96
|
+
throw new DecisionError("UNSUPPORTED", support.reason || "本机不支持语音识别。", 409);
|
|
97
|
+
if (status.busy || status.download?.phase === "downloading")
|
|
98
|
+
throw new DecisionError("BUSY", "语音识别或模型下载正在进行,请稍后启用。", 409);
|
|
99
|
+
this.start("speech", "activate", {});
|
|
100
|
+
}
|
|
71
101
|
parse(kind, raw) {
|
|
72
102
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
73
103
|
throw new DecisionError("INVALID_REQUEST", "模型操作参数无效。");
|
|
@@ -90,6 +120,8 @@ export class LocalModelSetupService {
|
|
|
90
120
|
throw new DecisionError("UNAVAILABLE", "模型管理已关闭。", 503);
|
|
91
121
|
if (kind === "laya" && !this.supported())
|
|
92
122
|
throw new DecisionError("UNSUPPORTED", `LAYA-MLX 需要 Apple Silicon macOS 与 Metal;${decisionHardware().message}`, 409);
|
|
123
|
+
if (kind === "laya" && action === "activate")
|
|
124
|
+
throw new DecisionError("INVALID_REQUEST", "一键启用仅用于语音识别。");
|
|
93
125
|
const input = this.parse(kind, raw);
|
|
94
126
|
if (this.jobs.has(kind))
|
|
95
127
|
throw new DecisionError("BUSY", "该模型已有安装任务,请等待或取消。", 409);
|
|
@@ -119,6 +151,8 @@ export class LocalModelSetupService {
|
|
|
119
151
|
timer.unref();
|
|
120
152
|
const signal = abort.signal;
|
|
121
153
|
const phase = (value, message) => this.update(kind, id, { phase: value, message });
|
|
154
|
+
let activationMaintenance = false;
|
|
155
|
+
const settingsRevision = this.deps.speech.settingsRevision();
|
|
122
156
|
try {
|
|
123
157
|
if (kind === "laya") {
|
|
124
158
|
let model = await this.layaModel();
|
|
@@ -170,7 +204,7 @@ export class LocalModelSetupService {
|
|
|
170
204
|
}
|
|
171
205
|
else {
|
|
172
206
|
const selected = input.model ?? this.deps.speech.settings().model;
|
|
173
|
-
if (action === "download") {
|
|
207
|
+
if (action === "download" || action === "activate") {
|
|
174
208
|
phase("downloading", "下载并校验语音模型");
|
|
175
209
|
await this.deps.speech.startDownload(selected);
|
|
176
210
|
while (true) {
|
|
@@ -188,7 +222,12 @@ export class LocalModelSetupService {
|
|
|
188
222
|
});
|
|
189
223
|
}
|
|
190
224
|
}
|
|
191
|
-
|
|
225
|
+
if (action !== "download") {
|
|
226
|
+
if (action === "activate") {
|
|
227
|
+
signal.throwIfAborted();
|
|
228
|
+
this.deps.speech.acquireMaintenance();
|
|
229
|
+
activationMaintenance = true;
|
|
230
|
+
}
|
|
192
231
|
const status = await this.deps.speech.status();
|
|
193
232
|
signal.throwIfAborted();
|
|
194
233
|
const backend = input.backend === "auto" || !input.backend ? process.platform === "darwin" ? "metal" : "cpu" : input.backend;
|
|
@@ -207,22 +246,30 @@ export class LocalModelSetupService {
|
|
|
207
246
|
await rm(work, { recursive: true, force: true }).catch(() => { });
|
|
208
247
|
}
|
|
209
248
|
}
|
|
210
|
-
phase("initializing", "加载所选语音模型并用合成静音检查,未启用识别或重启服务");
|
|
211
|
-
await this.deps.speech.initializeModel(selected, signal);
|
|
249
|
+
phase("initializing", action === "activate" ? "正在检查语音模型,完成后自动启用。" : "加载所选语音模型并用合成静音检查,未启用识别或重启服务");
|
|
250
|
+
await this.deps.speech.initializeModel(selected, signal, action === "activate" ? "auto" : undefined);
|
|
251
|
+
if (action === "activate") {
|
|
252
|
+
signal.throwIfAborted();
|
|
253
|
+
if (settingsRevision !== this.deps.speech.settingsRevision())
|
|
254
|
+
throw new DecisionError("SETTINGS_CHANGED", "语音设置已由其他操作修改,本次启用已取消。");
|
|
255
|
+
this.deps.speech.configure({ enabled: true, acceleration: "auto" });
|
|
256
|
+
}
|
|
212
257
|
}
|
|
213
258
|
}
|
|
214
259
|
signal.throwIfAborted();
|
|
215
|
-
phase("completed", action === "download" ? "模型下载及完整性校验完成;可继续初始化" : "运行时和模型初始化检查完成;启用状态保持不变");
|
|
260
|
+
phase("completed", action === "activate" ? "服务端语音识别已启用。" : action === "download" ? "模型下载及完整性校验完成;可继续初始化" : "运行时和模型初始化检查完成;启用状态保持不变");
|
|
216
261
|
}
|
|
217
262
|
catch (error) {
|
|
218
263
|
const cancelled = signal.aborted;
|
|
219
264
|
const message = cancelled ? "安装任务已取消,完整文件保留,可重试" : error instanceof DecisionError || (error instanceof Error && /请先|校验|自定义|独立/.test(error.message))
|
|
220
265
|
? error.message : "安装或初始化失败,请检查网络、编译工具/Python版本、磁盘空间和平台能力,再重试。";
|
|
221
266
|
this.update(kind, id, { phase: cancelled ? "cancelled" : "failed", message, ...(cancelled ? {} : { error: message }) });
|
|
222
|
-
if (kind === "speech" && action === "download" && cancelled)
|
|
267
|
+
if (kind === "speech" && (action === "download" || action === "activate") && cancelled)
|
|
223
268
|
this.deps.speech.cancelDownload();
|
|
224
269
|
}
|
|
225
270
|
finally {
|
|
271
|
+
if (activationMaintenance)
|
|
272
|
+
this.deps.speech.releaseMaintenance();
|
|
226
273
|
clearTimeout(timer);
|
|
227
274
|
}
|
|
228
275
|
}
|
|
@@ -3,7 +3,7 @@ export type LocalModelKind = "laya" | "speech";
|
|
|
3
3
|
export type ModelSetupPhase = "downloading" | "verifying" | "runtime" | "initializing" | "completed" | "failed" | "cancelled";
|
|
4
4
|
export interface ModelSetupOperation {
|
|
5
5
|
id: number;
|
|
6
|
-
action: "download" | "initialize";
|
|
6
|
+
action: "download" | "initialize" | "activate";
|
|
7
7
|
phase: ModelSetupPhase;
|
|
8
8
|
message: string;
|
|
9
9
|
received: number;
|
|
@@ -14,6 +14,7 @@ export interface LocalModelStatus {
|
|
|
14
14
|
kind: LocalModelKind;
|
|
15
15
|
label: string;
|
|
16
16
|
supported: boolean;
|
|
17
|
+
supportReason?: string | null;
|
|
17
18
|
reason: string | null;
|
|
18
19
|
enabled: boolean;
|
|
19
20
|
model: string;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** Shared, versioned employee avatar contract. Keep v1 option IDs and seed rules stable across clients. */
|
|
2
|
+
export declare const PLUSH_SHAPES: readonly [{
|
|
3
|
+
readonly id: "heart";
|
|
4
|
+
readonly label: "心形";
|
|
5
|
+
}, {
|
|
6
|
+
readonly id: "triangle";
|
|
7
|
+
readonly label: "圆角三角";
|
|
8
|
+
}, {
|
|
9
|
+
readonly id: "diamond";
|
|
10
|
+
readonly label: "菱形";
|
|
11
|
+
}, {
|
|
12
|
+
readonly id: "round";
|
|
13
|
+
readonly label: "圆形";
|
|
14
|
+
}, {
|
|
15
|
+
readonly id: "square";
|
|
16
|
+
readonly label: "圆角方形";
|
|
17
|
+
}, {
|
|
18
|
+
readonly id: "capsule";
|
|
19
|
+
readonly label: "胶囊";
|
|
20
|
+
}];
|
|
21
|
+
export declare const PLUSH_COLORS: readonly [{
|
|
22
|
+
readonly id: "coral";
|
|
23
|
+
readonly label: "珊瑚";
|
|
24
|
+
readonly color: "#E99589";
|
|
25
|
+
}, {
|
|
26
|
+
readonly id: "sage";
|
|
27
|
+
readonly label: "灰绿";
|
|
28
|
+
readonly color: "#A9BDA7";
|
|
29
|
+
}, {
|
|
30
|
+
readonly id: "lavender";
|
|
31
|
+
readonly label: "淡紫";
|
|
32
|
+
readonly color: "#BFB0D9";
|
|
33
|
+
}, {
|
|
34
|
+
readonly id: "cream";
|
|
35
|
+
readonly label: "奶油";
|
|
36
|
+
readonly color: "#E8D8B8";
|
|
37
|
+
}, {
|
|
38
|
+
readonly id: "blue";
|
|
39
|
+
readonly label: "雾蓝";
|
|
40
|
+
readonly color: "#A9C6D5";
|
|
41
|
+
}, {
|
|
42
|
+
readonly id: "ochre";
|
|
43
|
+
readonly label: "赭黄";
|
|
44
|
+
readonly color: "#D4AE65";
|
|
45
|
+
}];
|
|
46
|
+
export declare const PLUSH_GLASSES: readonly [{
|
|
47
|
+
readonly id: "none";
|
|
48
|
+
readonly label: "无眼镜";
|
|
49
|
+
}, {
|
|
50
|
+
readonly id: "gold";
|
|
51
|
+
readonly label: "金框眼镜";
|
|
52
|
+
}, {
|
|
53
|
+
readonly id: "ink";
|
|
54
|
+
readonly label: "黑框眼镜";
|
|
55
|
+
}];
|
|
56
|
+
export declare const PLUSH_HATS: readonly [{
|
|
57
|
+
readonly id: "none";
|
|
58
|
+
readonly label: "无帽子";
|
|
59
|
+
}, {
|
|
60
|
+
readonly id: "beanie";
|
|
61
|
+
readonly label: "针织帽";
|
|
62
|
+
}, {
|
|
63
|
+
readonly id: "beret";
|
|
64
|
+
readonly label: "贝雷帽";
|
|
65
|
+
}];
|
|
66
|
+
export type PlushShape = typeof PLUSH_SHAPES[number]["id"];
|
|
67
|
+
export type PlushColor = typeof PLUSH_COLORS[number]["id"];
|
|
68
|
+
export type PlushGlasses = typeof PLUSH_GLASSES[number]["id"];
|
|
69
|
+
export type PlushHat = typeof PLUSH_HATS[number]["id"];
|
|
70
|
+
export interface PlushAvatarConfig {
|
|
71
|
+
version: 1;
|
|
72
|
+
shape: PlushShape;
|
|
73
|
+
color: PlushColor;
|
|
74
|
+
glasses: PlushGlasses;
|
|
75
|
+
hat: PlushHat;
|
|
76
|
+
}
|
|
77
|
+
/** Two explicit source-derived cat heads; geometric defaults and legacy pixels stay unchanged. */
|
|
78
|
+
export interface PlushCatAvatarConfig {
|
|
79
|
+
version: 1;
|
|
80
|
+
kind: "cat";
|
|
81
|
+
coat: "silver" | "orange";
|
|
82
|
+
}
|
|
83
|
+
export type PlushRenderConfig = PlushAvatarConfig | PlushCatAvatarConfig;
|
|
84
|
+
export declare function isPlushCatAvatar(config: PlushRenderConfig): config is PlushCatAvatarConfig;
|
|
85
|
+
export declare function parsePlushCatAvatar(value: string): PlushCatAvatarConfig | null;
|
|
86
|
+
export declare function encodePlushCatAvatar(config: PlushCatAvatarConfig): string;
|
|
87
|
+
/** Common identity projection; only an explicit cat value opts into this exception. */
|
|
88
|
+
export declare function resolveEmployeeAvatar(identity: PlushAvatarIdentity): PlushRenderConfig | null;
|
|
89
|
+
export interface PlushAvatarIdentity {
|
|
90
|
+
id?: string | null;
|
|
91
|
+
name?: string | null;
|
|
92
|
+
avatar?: string | null;
|
|
93
|
+
}
|
|
94
|
+
/** Only the exact known v1 contract is accepted for explicit configuration; no URL or future-version guessing. */
|
|
95
|
+
export declare function parsePlushAvatar(value: string): PlushAvatarConfig | null;
|
|
96
|
+
export declare function encodePlushAvatar(config: PlushAvatarConfig): string;
|
|
97
|
+
/** Pure presentation default; never persist this from a GET, a render, or a preview. */
|
|
98
|
+
export declare function defaultPlushAvatar({ id, name }: Pick<PlushAvatarIdentity, "id" | "name">): PlushAvatarConfig;
|
|
99
|
+
/** Uploaded photos and explicit legacy cats keep their renderer; corrupt old values fall back without migration. */
|
|
100
|
+
export declare function resolvePlushAvatar(identity: PlushAvatarIdentity): PlushAvatarConfig | null;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Shared, versioned employee avatar contract. Keep v1 option IDs and seed rules stable across clients. */
|
|
2
|
+
export const PLUSH_SHAPES = [
|
|
3
|
+
{ id: "heart", label: "心形" },
|
|
4
|
+
{ id: "triangle", label: "圆角三角" },
|
|
5
|
+
{ id: "diamond", label: "菱形" },
|
|
6
|
+
{ id: "round", label: "圆形" },
|
|
7
|
+
{ id: "square", label: "圆角方形" },
|
|
8
|
+
{ id: "capsule", label: "胶囊" },
|
|
9
|
+
];
|
|
10
|
+
export const PLUSH_COLORS = [
|
|
11
|
+
{ id: "coral", label: "珊瑚", color: "#E99589" },
|
|
12
|
+
{ id: "sage", label: "灰绿", color: "#A9BDA7" },
|
|
13
|
+
{ id: "lavender", label: "淡紫", color: "#BFB0D9" },
|
|
14
|
+
{ id: "cream", label: "奶油", color: "#E8D8B8" },
|
|
15
|
+
{ id: "blue", label: "雾蓝", color: "#A9C6D5" },
|
|
16
|
+
{ id: "ochre", label: "赭黄", color: "#D4AE65" },
|
|
17
|
+
];
|
|
18
|
+
export const PLUSH_GLASSES = [
|
|
19
|
+
{ id: "none", label: "无眼镜" },
|
|
20
|
+
{ id: "gold", label: "金框眼镜" },
|
|
21
|
+
{ id: "ink", label: "黑框眼镜" },
|
|
22
|
+
];
|
|
23
|
+
export const PLUSH_HATS = [
|
|
24
|
+
{ id: "none", label: "无帽子" },
|
|
25
|
+
{ id: "beanie", label: "针织帽" },
|
|
26
|
+
{ id: "beret", label: "贝雷帽" },
|
|
27
|
+
];
|
|
28
|
+
export function isPlushCatAvatar(config) {
|
|
29
|
+
return "kind" in config && config.kind === "cat";
|
|
30
|
+
}
|
|
31
|
+
export function parsePlushCatAvatar(value) {
|
|
32
|
+
if (value === "plush-cat:v1:silver")
|
|
33
|
+
return { version: 1, kind: "cat", coat: "silver" };
|
|
34
|
+
if (value === "plush-cat:v1:orange")
|
|
35
|
+
return { version: 1, kind: "cat", coat: "orange" };
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
export function encodePlushCatAvatar(config) {
|
|
39
|
+
const value = `plush-cat:v${config.version}:${config.coat}`;
|
|
40
|
+
if (config.kind !== "cat" || !parsePlushCatAvatar(value))
|
|
41
|
+
throw new Error("毛绒猫头像配置无效。");
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
/** Common identity projection; only an explicit cat value opts into this exception. */
|
|
45
|
+
export function resolveEmployeeAvatar(identity) {
|
|
46
|
+
return parsePlushCatAvatar(identity.avatar ?? "") ?? resolvePlushAvatar(identity);
|
|
47
|
+
}
|
|
48
|
+
function hasId(options, id) {
|
|
49
|
+
return options.some(option => option.id === id);
|
|
50
|
+
}
|
|
51
|
+
/** Only the exact known v1 contract is accepted for explicit configuration; no URL or future-version guessing. */
|
|
52
|
+
export function parsePlushAvatar(value) {
|
|
53
|
+
const parts = value.split(":");
|
|
54
|
+
if (parts.length !== 6 || parts[0] !== "plush" || parts[1] !== "v1")
|
|
55
|
+
return null;
|
|
56
|
+
const [, , shape, color, glasses, hat] = parts;
|
|
57
|
+
if (!hasId(PLUSH_SHAPES, shape) || !hasId(PLUSH_COLORS, color)
|
|
58
|
+
|| !hasId(PLUSH_GLASSES, glasses) || !hasId(PLUSH_HATS, hat))
|
|
59
|
+
return null;
|
|
60
|
+
return { version: 1, shape, color, glasses, hat };
|
|
61
|
+
}
|
|
62
|
+
export function encodePlushAvatar(config) {
|
|
63
|
+
const value = `plush:v${config.version}:${config.shape}:${config.color}:${config.glasses}:${config.hat}`;
|
|
64
|
+
if (!parsePlushAvatar(value))
|
|
65
|
+
throw new Error("毛绒头像配置无效。");
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
/** FNV-1a followed by an avalanche: fixed 32-bit integer math, shared by Node and browsers. */
|
|
69
|
+
function seedHash(seed) {
|
|
70
|
+
let hash = 0x811c9dc5;
|
|
71
|
+
for (let at = 0; at < seed.length; at += 1) {
|
|
72
|
+
hash = Math.imul(hash ^ seed.charCodeAt(at), 0x01000193);
|
|
73
|
+
}
|
|
74
|
+
hash = Math.imul(hash ^ (hash >>> 16), 0x85ebca6b);
|
|
75
|
+
hash = Math.imul(hash ^ (hash >>> 13), 0xc2b2ae35);
|
|
76
|
+
return (hash ^ (hash >>> 16)) >>> 0;
|
|
77
|
+
}
|
|
78
|
+
/** Pure presentation default; never persist this from a GET, a render, or a preview. */
|
|
79
|
+
export function defaultPlushAvatar({ id, name }) {
|
|
80
|
+
const identity = (id ?? "").trim() || (name ?? "").trim() || "member";
|
|
81
|
+
const pick = (field, options) => options[seedHash(`plush:v1:${identity}:${field}`) % options.length].id;
|
|
82
|
+
return {
|
|
83
|
+
version: 1,
|
|
84
|
+
shape: pick("shape", PLUSH_SHAPES),
|
|
85
|
+
color: pick("color", PLUSH_COLORS),
|
|
86
|
+
glasses: pick("glasses", PLUSH_GLASSES),
|
|
87
|
+
hat: pick("hat", PLUSH_HATS),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Uploaded photos and explicit legacy cats keep their renderer; corrupt old values fall back without migration. */
|
|
91
|
+
export function resolvePlushAvatar(identity) {
|
|
92
|
+
const avatar = identity.avatar ?? "";
|
|
93
|
+
if (avatar.startsWith("data:image/") || /^cat:\d+$/.test(avatar))
|
|
94
|
+
return null;
|
|
95
|
+
return parsePlushAvatar(avatar) ?? defaultPlushAvatar(identity);
|
|
96
|
+
}
|
|
@@ -3,6 +3,7 @@ import { EventEmitter } from "node:events";
|
|
|
3
3
|
import { WandStorage } from "./storage.js";
|
|
4
4
|
import { ExecutionMode, ProcessEventHandler, SessionProvider, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
|
|
5
5
|
import { type PermissionResolution } from "./claude-pty-bridge.js";
|
|
6
|
+
import type { EmployeeTextDeps } from "./employee-text.js";
|
|
6
7
|
import { type PtyTerminalSnapshot, type PtyHistoryPage } from "./pty-terminal-state.js";
|
|
7
8
|
import { samplePtyForegrounds } from "./pty-foreground.js";
|
|
8
9
|
import { type TerminalHost } from "./terminal-host.js";
|
|
@@ -34,6 +35,7 @@ export declare const PTY_TURN_IDLE_MS = 3000;
|
|
|
34
35
|
*/
|
|
35
36
|
export declare const PTY_FOREGROUND_SAMPLE_MS = 1000;
|
|
36
37
|
export interface ProcessManagerOptions {
|
|
38
|
+
free?: EmployeeTextDeps["free"];
|
|
37
39
|
/** Overrides the non-Claude PTY turn quiet window (tests use a short one). */
|
|
38
40
|
ptyTurnIdleMs?: number;
|
|
39
41
|
/** Overrides the foreground-process-group sampling interval (tests use a short one). */
|
|
@@ -55,6 +57,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
55
57
|
private readonly ptyForegroundSampleMs;
|
|
56
58
|
/** Foreground probe; injectable so tests can drive it without a real process table. */
|
|
57
59
|
private readonly probePtyForegrounds;
|
|
60
|
+
private readonly free;
|
|
58
61
|
private readonly resolveDecisionEvaluate;
|
|
59
62
|
/** 24h archive scan timer */
|
|
60
63
|
private archiveTimer;
|
|
@@ -177,7 +180,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
177
180
|
*/
|
|
178
181
|
setSessionModel(id: string, model: string | null): SessionSnapshot;
|
|
179
182
|
/**
|
|
180
|
-
*
|
|
183
|
+
* 归档只写标记,保留正在运行的终端、会话 ID 和 provider session id。
|
|
181
184
|
* 取消归档只清标记;重新拉起由恢复入口负责。
|
|
182
185
|
*/
|
|
183
186
|
setSessionArchived(id: string, archived: boolean): SessionSnapshot;
|
package/dist/process-manager.js
CHANGED
|
@@ -26,7 +26,7 @@ import { recordIterationPrompt } from "./iteration-log.js";
|
|
|
26
26
|
import { describePtySpawnFailure } from "./ensure-node-pty-helper.js";
|
|
27
27
|
import { resolveSessionProvider, resolveSystemAiContext } from "./session-ai-context.js";
|
|
28
28
|
import { resolveSessionCwd } from "./session-cwd.js";
|
|
29
|
-
import { inferProviderFromCommand, providerCliCommand } from "./session-provider.js";
|
|
29
|
+
import { inferProviderFromCommand, isSessionProvider, providerCliCommand } from "./session-provider.js";
|
|
30
30
|
import { RETENTION_IDLE_MS } from "./retention.js";
|
|
31
31
|
import { PtyTerminalState } from "./pty-terminal-state.js";
|
|
32
32
|
import { isPtySubmitInput } from "./pty-turn-activity.js";
|
|
@@ -563,6 +563,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
563
563
|
ptyForegroundSampleMs;
|
|
564
564
|
/** Foreground probe; injectable so tests can drive it without a real process table. */
|
|
565
565
|
probePtyForegrounds;
|
|
566
|
+
free;
|
|
566
567
|
resolveDecisionEvaluate;
|
|
567
568
|
/** 24h archive scan timer */
|
|
568
569
|
archiveTimer = null;
|
|
@@ -595,6 +596,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
595
596
|
this.ptyTurnIdleMs = options.ptyTurnIdleMs ?? PTY_TURN_IDLE_MS;
|
|
596
597
|
this.ptyForegroundSampleMs = options.ptyForegroundSampleMs ?? PTY_FOREGROUND_SAMPLE_MS;
|
|
597
598
|
this.probePtyForegrounds = options.samplePtyForegrounds ?? samplePtyForegrounds;
|
|
599
|
+
this.free = options.free;
|
|
598
600
|
this.resolveDecisionEvaluate = options.resolveDecisionEvaluate ?? (() => undefined);
|
|
599
601
|
this.terminalHost = terminalHost ?? new InProcessTerminalHost();
|
|
600
602
|
this.logger = new SessionLogger(configDir || path.join(process.env.HOME || process.cwd(), ".wand"), config.shortcutLogMaxBytes);
|
|
@@ -1229,7 +1231,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1229
1231
|
ai: resolveSystemAiContext({
|
|
1230
1232
|
provider: groupProvider, runner: undefined, command: providerCliCommand(groupProvider),
|
|
1231
1233
|
structuredState: undefined, selectedModel: null, thinkingEffort: opts?.thinkingEffort,
|
|
1232
|
-
}, this.config, this.storage.getSystemSiliconEmployee()),
|
|
1234
|
+
}, this.config, this.storage.getSystemSiliconEmployee(), this.free),
|
|
1233
1235
|
cwd: resolveSessionCwd(cwd, this.config.defaultCwd),
|
|
1234
1236
|
language: this.config.language,
|
|
1235
1237
|
});
|
|
@@ -1775,13 +1777,10 @@ export class ProcessManager extends EventEmitter {
|
|
|
1775
1777
|
return this.snapshot(record);
|
|
1776
1778
|
}
|
|
1777
1779
|
/**
|
|
1778
|
-
*
|
|
1780
|
+
* 归档只写标记,保留正在运行的终端、会话 ID 和 provider session id。
|
|
1779
1781
|
* 取消归档只清标记;重新拉起由恢复入口负责。
|
|
1780
1782
|
*/
|
|
1781
1783
|
setSessionArchived(id, archived) {
|
|
1782
|
-
if (archived && this.mustGet(id).status === "running") {
|
|
1783
|
-
this.stop(id);
|
|
1784
|
-
}
|
|
1785
1784
|
const record = this.mustGet(id);
|
|
1786
1785
|
record.archived = archived;
|
|
1787
1786
|
record.archivedAt = archived ? (record.archivedAt ?? new Date().toISOString()) : null;
|
|
@@ -2339,6 +2338,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
2339
2338
|
return;
|
|
2340
2339
|
// 迭代提示词记录:和会话标题共用同一套「有没有信息量」判断,用户不用多做一步。
|
|
2341
2340
|
recordIterationPrompt(this.storage, record, prompt, "session");
|
|
2341
|
+
// 空白终端只执行 Shell 命令,不生成会话标题或调用系统员工总结。
|
|
2342
|
+
if (!isSessionProvider(record.provider))
|
|
2343
|
+
return;
|
|
2342
2344
|
const blockedTitles = sessionTopicBlocklistForSnapshot(record, this.storage);
|
|
2343
2345
|
// CLI 自己起了名字就归它,模型标题与输入首行都不再覆盖;取不到原生标题才走系统硅基员工。
|
|
2344
2346
|
const hasNativeTitle = this.nativeTitles.has(id);
|
|
@@ -2354,7 +2356,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
2354
2356
|
input: prompt,
|
|
2355
2357
|
cwd: record.cwd,
|
|
2356
2358
|
language: this.config.language,
|
|
2357
|
-
ai: resolveSystemAiContext(record, this.config, this.storage.getSystemSiliconEmployee()),
|
|
2359
|
+
ai: resolveSystemAiContext(record, this.config, this.storage.getSystemSiliconEmployee(), this.free),
|
|
2358
2360
|
readNativeTitle: () => readNativeSessionTitle(resolveSessionProvider(record), record.claudeSessionId, record.cwd),
|
|
2359
2361
|
hasNativeTitle,
|
|
2360
2362
|
onGenerating: (generating) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Express } from "express";
|
|
2
2
|
import { type SiliconEmployee, type SiliconEmployeeDraft } from "./ai-team-types.js";
|
|
3
|
+
import type { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
|
|
3
4
|
import type { WandStorage } from "./storage.js";
|
|
4
5
|
import type { WandTaskAgent } from "./task-types.js";
|
|
5
6
|
import type { WandConfig } from "./types.js";
|
|
@@ -8,15 +9,18 @@ export interface EmployeeDraftRequest {
|
|
|
8
9
|
existingNames: string[];
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* 其他内置员工只接受 agents;固定猫头员工另外接受资料的部分更新。
|
|
13
|
+
* 锁定字段即使由旧客户端或直接 API 发来,也必须一致;不一致明确拒绝。
|
|
13
14
|
*/
|
|
14
15
|
export declare function parseSystemEmployeeAgents(value: unknown, existing: SiliconEmployee): WandTaskAgent[];
|
|
16
|
+
/** Partial edits preserve omitted fields and the raw saved prompt, not a generated read projection. */
|
|
17
|
+
export declare function parseFixedAvatarEmployeeInput(value: unknown, existing: SiliconEmployee, now: string): SiliconEmployee;
|
|
15
18
|
export declare function parseSiliconEmployeeInput(value: unknown, existing: SiliconEmployee | null, now: string): SiliconEmployee;
|
|
16
19
|
export declare function registerSiliconEmployeeRoutes(app: Express, deps: {
|
|
17
20
|
storage: WandStorage;
|
|
18
21
|
notifyEmployeeChanged?: (employeeId: string) => void;
|
|
19
22
|
config?: WandConfig;
|
|
23
|
+
free?: Pick<OpenRouterFreeModelsService, "resolveForCall">;
|
|
20
24
|
/** 测试注入点:默认按系统 AI 配置真实调用模型。 */
|
|
21
25
|
generateDraft?: (request: EmployeeDraftRequest) => Promise<SiliconEmployeeDraft>;
|
|
22
26
|
/** 测试注入点:CLI 可用性探测。 */
|