@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.
Files changed (82) hide show
  1. package/dist/agent-dispatch.d.ts +5 -0
  2. package/dist/agent-dispatch.js +8 -0
  3. package/dist/ai-team-employee-binding.d.ts +1 -0
  4. package/dist/ai-team-employee-binding.js +1 -1
  5. package/dist/ai-team-runner.d.ts +1 -1
  6. package/dist/ai-team-runner.js +13 -5
  7. package/dist/ai-team-types.d.ts +2 -2
  8. package/dist/ai-team-types.js +3 -2
  9. package/dist/build-info.json +4 -4
  10. package/dist/commit-task-archive.d.ts +3 -0
  11. package/dist/commit-task-archive.js +15 -0
  12. package/dist/config.js +1 -0
  13. package/dist/daemon-maintenance-targets.js +1 -1
  14. package/dist/decision-expert-employee.d.ts +1 -1
  15. package/dist/decision-expert-employee.js +3 -3
  16. package/dist/decision-expert-service.d.ts +6 -5
  17. package/dist/decision-expert-service.js +75 -49
  18. package/dist/decision-types.d.ts +2 -1
  19. package/dist/default-employee.d.ts +5 -1
  20. package/dist/default-employee.js +17 -5
  21. package/dist/employee-text.d.ts +28 -0
  22. package/dist/employee-text.js +124 -0
  23. package/dist/fixed-employee-avatar.d.ts +19 -0
  24. package/dist/fixed-employee-avatar.js +13 -0
  25. package/dist/git-quick-commit.d.ts +13 -4
  26. package/dist/git-quick-commit.js +112 -33
  27. package/dist/local-model-setup.d.ts +7 -1
  28. package/dist/local-model-setup.js +54 -7
  29. package/dist/local-model-types.d.ts +2 -1
  30. package/dist/plush-avatar.d.ts +100 -0
  31. package/dist/plush-avatar.js +96 -0
  32. package/dist/process-manager.d.ts +4 -1
  33. package/dist/process-manager.js +9 -7
  34. package/dist/server-employee-routes.d.ts +6 -2
  35. package/dist/server-employee-routes.js +55 -53
  36. package/dist/server-file-routes.js +61 -25
  37. package/dist/server-local-model-routes.js +12 -0
  38. package/dist/server-session-routes.d.ts +2 -1
  39. package/dist/server-session-routes.js +13 -10
  40. package/dist/server-speech-routes.d.ts +2 -0
  41. package/dist/server-speech-routes.js +7 -1
  42. package/dist/server-task-routes.d.ts +3 -0
  43. package/dist/server-task-routes.js +19 -10
  44. package/dist/server.js +25 -8
  45. package/dist/session-ai-context.d.ts +6 -4
  46. package/dist/session-ai-context.js +10 -9
  47. package/dist/silicon-employee-dispatch.d.ts +4 -6
  48. package/dist/silicon-employee-dispatch.js +9 -6
  49. package/dist/speech-polisher-employee.d.ts +6 -0
  50. package/dist/speech-polisher-employee.js +23 -0
  51. package/dist/speech-polisher-identity.d.ts +4 -0
  52. package/dist/speech-polisher-identity.js +4 -0
  53. package/dist/speech-polisher-service.d.ts +31 -0
  54. package/dist/speech-polisher-service.js +88 -0
  55. package/dist/speech-service.d.ts +3 -1
  56. package/dist/speech-service.js +15 -4
  57. package/dist/speech-support.d.ts +6 -0
  58. package/dist/speech-support.js +26 -0
  59. package/dist/speech-types.d.ts +5 -0
  60. package/dist/storage.d.ts +13 -4
  61. package/dist/storage.js +84 -32
  62. package/dist/structured-session-manager.d.ts +3 -2
  63. package/dist/structured-session-manager.js +11 -7
  64. package/dist/system-employee.d.ts +6 -4
  65. package/dist/system-employee.js +13 -10
  66. package/dist/types.d.ts +3 -1
  67. package/dist/user-memory.d.ts +2 -0
  68. package/dist/user-memory.js +1 -1
  69. package/dist/wand-task-sync.js +13 -2
  70. package/dist/web-ui/content/ai-teams.js +13 -13
  71. package/dist/web-ui/content/plush-avatar.js +4220 -0
  72. package/dist/web-ui/content/scripts.js +572 -209
  73. package/dist/web-ui/content/styles.css +1 -1
  74. package/dist/web-ui/content/tailwind.css +1 -1
  75. package/dist/web-ui/embedded-assets.d.ts +2 -0
  76. package/dist/web-ui/embedded-assets.js +5 -3
  77. package/dist/web-ui/index.js +3 -3
  78. package/dist/web-ui/provider-identity.d.ts +2 -0
  79. package/dist/web-ui/provider-identity.js +7 -1
  80. package/dist/web-ui/scripts.d.ts +7 -0
  81. package/dist/web-ui/scripts.js +30 -5
  82. 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: ["darwin", "linux", "win32"].includes(process.platform), reason: speech.reason,
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
- else {
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
- * 归档会先停掉还在跑的终端,但保留会话 ID 和 provider session id,恢复时用它们续上。
183
+ * 归档只写标记,保留正在运行的终端、会话 ID 和 provider session id。
181
184
  * 取消归档只清标记;重新拉起由恢复入口负责。
182
185
  */
183
186
  setSessionArchived(id: string, archived: boolean): SessionSnapshot;
@@ -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
- * 归档会先停掉还在跑的终端,但保留会话 ID 和 provider session id,恢复时用它们续上。
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
- * 内置员工只接受 agents;其余字段即使前端遗漏地发上来也必须与服务端一致,
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 可用性探测。 */