@co0ontty/wand 4.85.1 → 4.87.0-beta.gd2f0f8f

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 (108) hide show
  1. package/README.md +4 -0
  2. package/dist/agent-dispatch.js +5 -3
  3. package/dist/ai-team-runner.js +1 -0
  4. package/dist/ai-team-types.d.ts +1 -0
  5. package/dist/ai-team-types.js +6 -2
  6. package/dist/build-info.json +4 -4
  7. package/dist/cli.js +12 -6
  8. package/dist/config.d.ts +1 -1
  9. package/dist/config.js +33 -0
  10. package/dist/core-runner.js +1 -1
  11. package/dist/daemon-maintenance-state.d.ts +6 -0
  12. package/dist/daemon-maintenance-state.js +9 -0
  13. package/dist/daemon-maintenance-targets.d.ts +20 -0
  14. package/dist/daemon-maintenance-targets.js +183 -0
  15. package/dist/daemon-maintenance.d.ts +53 -0
  16. package/dist/daemon-maintenance.js +164 -0
  17. package/dist/decision-expert-employee.d.ts +6 -0
  18. package/dist/decision-expert-employee.js +22 -0
  19. package/dist/decision-expert-identity.d.ts +7 -0
  20. package/dist/decision-expert-identity.js +7 -0
  21. package/dist/decision-hardware.d.ts +19 -0
  22. package/dist/decision-hardware.js +19 -0
  23. package/dist/decision-service.d.ts +6 -1
  24. package/dist/decision-service.js +60 -1
  25. package/dist/decision-types.d.ts +1 -0
  26. package/dist/decision-types.js +12 -0
  27. package/dist/git-quick-commit.js +1 -1
  28. package/dist/laya-model-manifest.d.ts +11 -0
  29. package/dist/laya-model-manifest.js +18 -0
  30. package/dist/local-model-setup.d.ts +46 -0
  31. package/dist/local-model-setup.js +248 -0
  32. package/dist/local-model-types.d.ts +34 -0
  33. package/dist/local-model-types.js +1 -0
  34. package/dist/message-truncator.d.ts +4 -2
  35. package/dist/message-truncator.js +20 -6
  36. package/dist/missions.js +6 -3
  37. package/dist/model-file-store.d.ts +13 -0
  38. package/dist/model-file-store.js +108 -0
  39. package/dist/model-groups.js +2 -2
  40. package/dist/model-setup-process.d.ts +2 -0
  41. package/dist/model-setup-process.js +80 -0
  42. package/dist/models.js +3 -3
  43. package/dist/process-manager.js +21 -8
  44. package/dist/provider-catalog.d.ts +13 -1
  45. package/dist/provider-catalog.js +14 -2
  46. package/dist/render-binary.d.ts +1 -1
  47. package/dist/render-binary.js +4 -4
  48. package/dist/render-daemon-client.d.ts +12 -1
  49. package/dist/render-daemon-client.js +72 -4
  50. package/dist/render-host.d.ts +1 -0
  51. package/dist/render-host.js +9 -56
  52. package/dist/render-structured-host.d.ts +1 -0
  53. package/dist/render-structured-host.js +2 -2
  54. package/dist/server-employee-routes.js +3 -3
  55. package/dist/server-local-model-routes.d.ts +8 -0
  56. package/dist/server-local-model-routes.js +62 -0
  57. package/dist/server-resume-routes.js +1 -1
  58. package/dist/server-session-routes.js +34 -8
  59. package/dist/server-settings-routes.js +2 -0
  60. package/dist/server-speech-routes.d.ts +8 -0
  61. package/dist/server-speech-routes.js +87 -0
  62. package/dist/server-task-routes.js +3 -1
  63. package/dist/server-workspace-routes.js +2 -0
  64. package/dist/server.js +63 -18
  65. package/dist/session-completion-state.d.ts +1 -0
  66. package/dist/session-completion-state.js +1 -0
  67. package/dist/session-transport.js +1 -0
  68. package/dist/silicon-employee-dispatch.js +5 -2
  69. package/dist/silicon-employee-draft.js +1 -1
  70. package/dist/speech-models.d.ts +24 -0
  71. package/dist/speech-models.js +16 -0
  72. package/dist/speech-service.d.ts +57 -0
  73. package/dist/speech-service.js +399 -0
  74. package/dist/speech-types.d.ts +47 -0
  75. package/dist/speech-types.js +3 -0
  76. package/dist/storage.d.ts +2 -0
  77. package/dist/storage.js +13 -0
  78. package/dist/structured-failure.js +1 -1
  79. package/dist/structured-session-manager.d.ts +15 -2
  80. package/dist/structured-session-manager.js +58 -19
  81. package/dist/terminal-daemon-build.d.ts +4 -0
  82. package/dist/terminal-daemon-build.js +54 -0
  83. package/dist/terminal-daemon-protocol.d.ts +1 -1
  84. package/dist/terminal-daemon-server.js +20 -1
  85. package/dist/terminal-host.d.ts +4 -0
  86. package/dist/terminal-host.js +7 -4
  87. package/dist/tui/commands.js +1 -1
  88. package/dist/types.d.ts +14 -2
  89. package/dist/web-ui/chat-history-window.d.ts +23 -0
  90. package/dist/web-ui/chat-history-window.js +29 -0
  91. package/dist/web-ui/content/ai-teams.js +12 -12
  92. package/dist/web-ui/content/scripts.js +316 -233
  93. package/dist/web-ui/content/styles.css +1 -1
  94. package/dist/web-ui/content/tailwind.css +1 -1
  95. package/dist/web-ui/embedded-assets.js +3 -3
  96. package/dist/web-ui/index.js +3 -3
  97. package/dist/web-ui/provider-identity.d.ts +28 -1
  98. package/dist/web-ui/provider-identity.js +31 -2
  99. package/dist/web-ui/session-activity.d.ts +1 -0
  100. package/dist/web-ui/session-activity.js +5 -2
  101. package/dist/web-ui/speech-audio.d.ts +2 -0
  102. package/dist/web-ui/speech-audio.js +41 -0
  103. package/dist/web-ui/vendor-loader.d.ts +2 -0
  104. package/dist/web-ui/vendor-loader.js +50 -0
  105. package/dist/ws-broadcast.js +10 -4
  106. package/package.json +7 -3
  107. package/scripts/install-laya-runtime.js +67 -0
  108. package/scripts/install-speech-runtime.js +82 -0
@@ -0,0 +1,164 @@
1
+ /** One admission barrier for PTY and structured starts, not for ongoing input/output. */
2
+ export class DaemonAdmission {
3
+ barrier = null;
4
+ active = 0;
5
+ settled = null;
6
+ async run(operation) {
7
+ while (this.barrier)
8
+ await this.barrier;
9
+ this.active++;
10
+ try {
11
+ return await operation();
12
+ }
13
+ finally {
14
+ if (--this.active === 0)
15
+ this.settled?.();
16
+ }
17
+ }
18
+ async exclusive(operation) {
19
+ while (this.barrier)
20
+ await this.barrier;
21
+ let release;
22
+ this.barrier = new Promise((resolve) => { release = resolve; });
23
+ try {
24
+ if (this.active)
25
+ await new Promise((resolve) => { this.settled = resolve; });
26
+ return await operation();
27
+ }
28
+ finally {
29
+ this.settled = null;
30
+ this.barrier = null;
31
+ release();
32
+ }
33
+ }
34
+ terminal(host) {
35
+ return {
36
+ persistent: host.persistent,
37
+ attach: (id, seq) => host.attach(id, seq),
38
+ createOrAttach: (request, seq) => this.run(() => host.createOrAttach(request, seq)),
39
+ forget: (id) => host.forget(id),
40
+ flush: () => host.flush?.(),
41
+ disconnect: () => host.disconnect(),
42
+ };
43
+ }
44
+ structured(host) {
45
+ return {
46
+ persistent: host.persistent,
47
+ spawnStructured: (request) => this.run(() => host.spawnStructured(request)),
48
+ listRuns: () => host.listRuns(),
49
+ attachRun: (id) => host.attachRun(id),
50
+ adoptRun: (id) => host.adoptRun(id),
51
+ forgetRun: (id) => host.forgetRun(id),
52
+ };
53
+ }
54
+ }
55
+ /** Lifecycle-owned worker. GET only projects its state; it never performs maintenance. */
56
+ export class DaemonMaintenance {
57
+ options;
58
+ state = { pending: false, phase: "idle" };
59
+ timer = null;
60
+ flight = null;
61
+ stopped = false;
62
+ idleSince = null;
63
+ failures = 0;
64
+ constructor(options) {
65
+ this.options = options;
66
+ }
67
+ status() { return { ...this.state }; }
68
+ start() {
69
+ if (this.timer || this.flight || this.stopped || !this.options.targets.length)
70
+ return;
71
+ const poll = async () => {
72
+ await this.check();
73
+ if (this.stopped)
74
+ return;
75
+ this.timer = setTimeout(() => { this.timer = null; void poll(); }, Math.min(300_000, 15_000 * 2 ** Math.min(this.failures, 5)));
76
+ this.timer.unref();
77
+ };
78
+ void poll();
79
+ }
80
+ check() {
81
+ if (this.stopped)
82
+ return Promise.resolve();
83
+ if (this.flight)
84
+ return this.flight;
85
+ this.flight = this.inspectAndUpdate().catch((error) => {
86
+ this.idleSince = null;
87
+ if (this.state.pending)
88
+ this.state = { pending: true, phase: "retrying" };
89
+ this.failures++;
90
+ // Backoff plus rate-limited diagnostics, not a repeated user notification.
91
+ if (this.failures === 1 || this.failures % 10 === 0)
92
+ this.options.log(error);
93
+ }).finally(() => { this.flight = null; });
94
+ return this.flight;
95
+ }
96
+ async stop() {
97
+ this.stopped = true;
98
+ if (this.timer)
99
+ clearTimeout(this.timer);
100
+ this.timer = null;
101
+ await this.flight;
102
+ }
103
+ async inspectAndUpdate() {
104
+ const o = this.options;
105
+ if (!o.available()) {
106
+ this.idleSince = null;
107
+ return;
108
+ }
109
+ // Reading full replay inventories while a known session is busy only adds
110
+ // load. Versions still refresh so the waiting notice remains accurate.
111
+ const includeInventory = !o.busy();
112
+ const inspected = await Promise.allSettled(o.targets.map((target) => target.inspect(includeInventory)));
113
+ if (inspected.some((result) => result.status === "fulfilled" && result.value.pending)) {
114
+ this.state = { pending: true, phase: "waiting" };
115
+ }
116
+ const snapshots = inspected.map((result) => {
117
+ if (result.status === "rejected")
118
+ throw result.reason;
119
+ return result.value;
120
+ });
121
+ const pending = snapshots.some((snapshot) => snapshot.pending);
122
+ this.state = { pending, phase: pending ? "waiting" : "idle" };
123
+ if (!pending)
124
+ this.failures = 0;
125
+ if (!pending || o.busy() || snapshots.some((snapshot) => snapshot.running !== 0)) {
126
+ this.idleSince = null;
127
+ return;
128
+ }
129
+ const now = (o.now ?? Date.now)();
130
+ this.idleSince ??= now;
131
+ if (now - this.idleSince < (o.quietMs ?? 10_000))
132
+ return;
133
+ await o.admission.exclusive(async () => {
134
+ if (this.stopped || !o.available())
135
+ return;
136
+ const releaseCore = o.beginCoreDrain();
137
+ try {
138
+ // Close the check/start race, including unowned daemon runs and shells
139
+ // that are idle in the UI but still have a live PTY process.
140
+ const fresh = await Promise.all(o.targets.map((target) => target.inspect()));
141
+ if (this.stopped || !o.available() || o.busy() || fresh.some((snapshot) => snapshot.running !== 0))
142
+ return;
143
+ this.state = { pending: true, phase: "updating" };
144
+ for (let i = 0; i < fresh.length; i++) {
145
+ if (this.stopped || !o.available() || o.busy())
146
+ return;
147
+ if (fresh[i].pending)
148
+ await o.targets[i].restart(fresh[i]);
149
+ }
150
+ const after = await Promise.all(o.targets.map((target) => target.inspect()));
151
+ if (after.some((snapshot) => snapshot.pending))
152
+ throw new Error("Daemon replacement is not ready yet");
153
+ this.state = { pending: false, phase: "idle" };
154
+ this.failures = 0;
155
+ }
156
+ finally {
157
+ this.idleSince = null;
158
+ releaseCore();
159
+ if (this.state.phase === "updating")
160
+ this.state = { pending: true, phase: "waiting" };
161
+ }
162
+ });
163
+ }
164
+ }
@@ -0,0 +1,6 @@
1
+ import type { SiliconEmployee } from "./ai-team-types.js";
2
+ import type { WandTaskAgent } from "./task-types.js";
3
+ export declare const DECISION_EXPERT_DUTY = "Wand \u51B3\u7B56\u4E13\u5BB6\uFF1A\u6839\u636E\u6700\u5C11\u5FC5\u8981\u8BC1\u636E\u505A\u6709\u754C\u9009\u62E9\u3001\u8BC4\u5206\u4E0E\u662F\u975E\u5224\u65AD\uFF1B\u5148\u6838\u5BF9\u672C\u673A\u80FD\u529B\uFF0C\u4F18\u5148\u672C\u5730\u51B3\u7B56\uFF0C\u5907\u7528\u514D\u8D39\u6A21\u578B\u3002";
4
+ export declare const DECISION_EXPERT_PROMPT: string;
5
+ export declare function decisionExpertSeedAgents(): WandTaskAgent[];
6
+ export declare function decisionExpertDefinition(now: string, existing?: SiliconEmployee | null): SiliconEmployee;
@@ -0,0 +1,22 @@
1
+ import { SYSTEM_EMPLOYEE_TAG } from "./ai-team-types.js";
2
+ import { DECISION_EXPERT_ID, DECISION_EXPERT_KEY, DECISION_EXPERT_NAME, WAND_LOCAL_DECISION_MODEL } from "./decision-expert-identity.js";
3
+ import { OPENROUTER_FREE_SELECTOR } from "./openrouter-free-selection.js";
4
+ export const DECISION_EXPERT_DUTY = "Wand 决策专家:根据最少必要证据做有界选择、评分与是非判断;先核对本机能力,优先本地决策,备用免费模型。";
5
+ export const DECISION_EXPERT_PROMPT = [
6
+ "你是 Wand 内置系统员工「决策专家」。你的职责是基于用户提供的证据,做有界候选选择、评分与是非判断。",
7
+ "首选为 Wand 的 LAYA 本地决策模型,备用默认 Wand 免费分组。LAYA 不生成开放式文本,不伪装成聊天模型。",
8
+ "先核对机器的 CPU、内存、Apple Silicon / Metal 和运行时健康。性能/平台不足时明确说明原因,建议配置你的调用链,不能捏造可以运行。",
9
+ "只使用输入中已授权的最少证据,不读取别人的知识/私聊。概率不是正确性保证,更不是权限、删除、发布或付款批准。",
10
+ "严格输出任务要求的格式;不编造选项、解释、执行结果或事实。失败有明确原因,不盲目重复已执行/未知送达的工作。",
11
+ "免费分组在每次请求前重新核对价格;没有免费候选时停止并提示配置,不静默换付费模型。",
12
+ ].join("\n");
13
+ export function decisionExpertSeedAgents() {
14
+ return [WAND_LOCAL_DECISION_MODEL, OPENROUTER_FREE_SELECTOR].map(model => ({ provider: "pi", engine: "sdk", model,
15
+ thinkingEffort: "off", mode: "default", kind: "structured" }));
16
+ }
17
+ export function decisionExpertDefinition(now, existing) {
18
+ return { id: existing?.id ?? DECISION_EXPERT_ID, systemKey: DECISION_EXPERT_KEY, name: DECISION_EXPERT_NAME,
19
+ duty: DECISION_EXPERT_DUTY, prompt: DECISION_EXPERT_PROMPT, avatar: "", tags: [SYSTEM_EMPLOYEE_TAG],
20
+ agents: existing?.agents.length ? existing.agents : decisionExpertSeedAgents(),
21
+ createdAt: existing?.createdAt ?? now, updatedAt: now };
22
+ }
@@ -0,0 +1,7 @@
1
+ /** Pure identity shared by server and clients; LAYA is a bounded decision source, never a chat model. */
2
+ export declare const DECISION_EXPERT_KEY = "wand-decision-expert";
3
+ export declare const DECISION_EXPERT_ID = "e_wand_decision_expert";
4
+ export declare const DECISION_EXPERT_NAME = "\u51B3\u7B56\u4E13\u5BB6";
5
+ export declare const WAND_LOCAL_DECISION_MODEL = "wand-decision/laya";
6
+ export declare const WAND_LOCAL_DECISION_LABEL = "Wand \u672C\u5730\u51B3\u7B56\u6A21\u578B \u00B7 LAYA";
7
+ export declare function isLocalDecisionModel(value: string | null | undefined): boolean;
@@ -0,0 +1,7 @@
1
+ /** Pure identity shared by server and clients; LAYA is a bounded decision source, never a chat model. */
2
+ export const DECISION_EXPERT_KEY = "wand-decision-expert";
3
+ export const DECISION_EXPERT_ID = "e_wand_decision_expert";
4
+ export const DECISION_EXPERT_NAME = "决策专家";
5
+ export const WAND_LOCAL_DECISION_MODEL = "wand-decision/laya";
6
+ export const WAND_LOCAL_DECISION_LABEL = "Wand 本地决策模型 · LAYA";
7
+ export function isLocalDecisionModel(value) { return value === WAND_LOCAL_DECISION_MODEL; }
@@ -0,0 +1,19 @@
1
+ export interface DecisionHardwareSnapshot {
2
+ platform: string;
3
+ arch: string;
4
+ memoryBytes: number;
5
+ availableBytes: number;
6
+ cpuCount: number;
7
+ metal?: boolean;
8
+ }
9
+ export interface DecisionHardwareAssessment {
10
+ suitable: boolean;
11
+ code: "ready" | "platform" | "metal" | "memory" | "cpu";
12
+ message: string;
13
+ memoryGiB: number;
14
+ availableGiB: number;
15
+ cpuCount: number;
16
+ minimumMemoryGiB: number;
17
+ }
18
+ export declare function assessDecisionHardware(value: DecisionHardwareSnapshot): DecisionHardwareAssessment;
19
+ export declare function decisionHardware(): DecisionHardwareAssessment;
@@ -0,0 +1,19 @@
1
+ import os from "node:os";
2
+ export function assessDecisionHardware(value) {
3
+ const gib = 1024 ** 3, memoryGiB = Math.round(value.memoryBytes / gib * 10) / 10, availableGiB = Math.round(value.availableBytes / gib * 10) / 10;
4
+ const base = { memoryGiB, availableGiB, cpuCount: value.cpuCount, minimumMemoryGiB: 8 };
5
+ const insufficient = "当前机器性能或平台条件不足,建议配置「决策专家」员工的调用链,通过备用模型使用。";
6
+ if (value.platform !== "darwin" || value.arch !== "arm64")
7
+ return { ...base, suitable: false, code: "platform", message: `LAYA 本地决策需要 Apple Silicon macOS / Metal。${insufficient}` };
8
+ if (value.metal === false)
9
+ return { ...base, suitable: false, code: "metal", message: `当前 Metal 不可用。${insufficient}` };
10
+ if (value.memoryBytes < 7.5 * gib)
11
+ return { ...base, suitable: false, code: "memory", message: `当前机器性能不够(内存 ${memoryGiB} GiB,建议至少 8 GiB)。请配置「决策专家」员工调用链使用备用模型。` };
12
+ if (value.cpuCount < 4)
13
+ return { ...base, suitable: false, code: "cpu", message: `当前机器性能不够(可用 CPU ${value.cpuCount} 核,建议至少 4 核)。请配置「决策专家」员工调用链。` };
14
+ // Darwin's free memory excludes reclaimable cache. Don't falsely reject an otherwise capable Mac based on that number alone.
15
+ return { ...base, suitable: true, code: "ready", message: "本机基本配置满足 LAYA 本地决策要求;实际运行时与 Metal 能力仍需初始化验证。" };
16
+ }
17
+ export function decisionHardware() {
18
+ return assessDecisionHardware({ platform: process.platform, arch: process.arch, memoryBytes: os.totalmem(), availableBytes: os.freemem(), cpuCount: os.availableParallelism() });
19
+ }
@@ -7,7 +7,7 @@ interface DecisionServiceOptions {
7
7
  }
8
8
  /** One bounded worker per server. It owns no sessions, permissions, tasks, or model downloads. */
9
9
  export declare class DecisionService {
10
- private readonly config;
10
+ private config;
11
11
  private readonly options;
12
12
  private child;
13
13
  private stopping;
@@ -22,6 +22,7 @@ export declare class DecisionService {
22
22
  private readonly rates;
23
23
  private completed;
24
24
  private failed;
25
+ private readonly initializations;
25
26
  constructor(config: LocalDecisionConfig | undefined, options?: DecisionServiceOptions);
26
27
  status(): {
27
28
  enabled: boolean;
@@ -33,6 +34,10 @@ export declare class DecisionService {
33
34
  failed: number;
34
35
  experimental: true;
35
36
  };
37
+ assertConfigurable(): void;
38
+ configure(config: LocalDecisionConfig): void;
39
+ /** Explicit admin warm-up; it does not evaluate a user's task or implicitly enable the model. */
40
+ initialize(signal?: AbortSignal): Promise<void>;
36
41
  evaluate(value: unknown, caller: string, signal?: AbortSignal): Promise<DecisionResult>;
37
42
  dispose(): void;
38
43
  private finish;
@@ -21,6 +21,7 @@ export class DecisionService {
21
21
  rates = new Map();
22
22
  completed = 0;
23
23
  failed = 0;
24
+ initializations = new Set();
24
25
  constructor(config, options = {}) {
25
26
  this.config = config;
26
27
  this.options = options;
@@ -33,6 +34,60 @@ export class DecisionService {
33
34
  state: this.disposed ? "closed" : this.stopping ? "stopping" : this.child ? this.ready ? "ready" : "loading" : "stopped",
34
35
  queued: this.queue.length + Number(!!this.active), completed: this.completed, failed: this.failed, experimental: true };
35
36
  }
37
+ assertConfigurable() {
38
+ if (this.active || this.queue.length || this.initializations.size || (this.child && !this.ready)) {
39
+ throw new DecisionError("BUSY", "本地决策正在执行或初始化,请等待后重试。", 409);
40
+ }
41
+ if (this.disposed)
42
+ throw new DecisionError("UNAVAILABLE", "决策服务已关闭。", 503);
43
+ }
44
+ configure(config) {
45
+ this.assertConfigurable();
46
+ const changedRuntime = config.pythonPath !== this.config?.pythonPath || config.modelPath !== this.config?.modelPath;
47
+ if (changedRuntime || !config.enabled)
48
+ this.stopWorker();
49
+ this.config = { ...config };
50
+ this.cooldownUntil = 0;
51
+ }
52
+ /** Explicit admin warm-up; it does not evaluate a user's task or implicitly enable the model. */
53
+ initialize(signal) {
54
+ const status = this.status();
55
+ if (this.disposed || !status.supported || !status.configured)
56
+ throw new DecisionError("UNAVAILABLE", "LAYA 平台不支持或运行环境/模型尚未安装。", 503);
57
+ if (signal?.aborted)
58
+ throw new DecisionError("CANCELLED", "初始化已取消。", 499);
59
+ if (this.ready)
60
+ return Promise.resolve();
61
+ this.clearIdle();
62
+ return new Promise((resolve, reject) => {
63
+ const finish = (error) => {
64
+ if (!this.initializations.delete(waiter))
65
+ return;
66
+ waiter.clear();
67
+ if (error)
68
+ reject(error);
69
+ else
70
+ resolve();
71
+ if (!this.initializations.size && !this.active && !this.queue.length) {
72
+ if (error)
73
+ this.stopWorker();
74
+ else
75
+ this.pump();
76
+ }
77
+ };
78
+ const abort = () => finish(new DecisionError("CANCELLED", "初始化已取消。", 499));
79
+ const timer = setTimeout(() => finish(new DecisionError("TIMEOUT", "LAYA 初始化超时,请检查运行环境和模型。", 504)), this.options.timeoutMs ?? 45_000);
80
+ const waiter = { resolve: () => finish(), reject: (error) => finish(error),
81
+ clear: () => { clearTimeout(timer); signal?.removeEventListener("abort", abort); } };
82
+ this.initializations.add(waiter);
83
+ signal?.addEventListener("abort", abort, { once: true });
84
+ if (signal?.aborted) {
85
+ abort();
86
+ return;
87
+ }
88
+ this.pump();
89
+ });
90
+ }
36
91
  evaluate(value, caller, signal) {
37
92
  const request = parseDecisionRequest(value);
38
93
  const status = this.status();
@@ -128,6 +183,8 @@ export class DecisionService {
128
183
  }
129
184
  }
130
185
  failWorker(error) {
186
+ for (const waiter of [...this.initializations])
187
+ waiter.reject(error);
131
188
  const jobs = [...(this.active ? [this.active] : []), ...this.queue];
132
189
  this.active = null;
133
190
  this.queue = [];
@@ -139,7 +196,7 @@ export class DecisionService {
139
196
  pump() {
140
197
  if (this.disposed || this.active || this.stopping)
141
198
  return;
142
- if (!this.queue.length) {
199
+ if (!this.queue.length && !this.initializations.size) {
143
200
  if (this.child && !this.idleTimer) {
144
201
  this.idleTimer = setTimeout(() => this.stopWorker(), this.options.idleMs ?? 5 * 60_000);
145
202
  this.idleTimer.unref();
@@ -200,6 +257,8 @@ export class DecisionService {
200
257
  if (message.type !== "ready" || message.protocol !== 1)
201
258
  throw new Error("Unexpected ready");
202
259
  this.ready = true;
260
+ for (const waiter of [...this.initializations])
261
+ waiter.resolve();
203
262
  this.pump();
204
263
  return;
205
264
  }
@@ -7,6 +7,7 @@ export interface LocalDecisionConfig {
7
7
  pythonPath: string;
8
8
  modelPath: string;
9
9
  }
10
+ export declare function parseLocalDecisionConfig(value: unknown): LocalDecisionConfig;
10
11
  export interface DecisionQuestion {
11
12
  type: "choice" | "score" | "noul";
12
13
  instructions: string;
@@ -1,7 +1,19 @@
1
+ import { isAbsolute } from "node:path";
1
2
  export const DECISION_MAX_BYTES = 32 * 1024;
2
3
  export const DECISION_MAX_QUESTIONS = 8;
3
4
  export const DECISION_MAX_OPTIONS = 8;
4
5
  export const DECISION_CHECKPOINT = "aac6fef/laya-multilingual-mlx";
6
+ export function parseLocalDecisionConfig(value) {
7
+ if (!value || typeof value !== "object" || Array.isArray(value))
8
+ throw new Error("本地决策配置无效。");
9
+ const input = value;
10
+ if (Object.keys(input).some((key) => !["enabled", "pythonPath", "modelPath"].includes(key))
11
+ || typeof input.enabled !== "boolean" || typeof input.pythonPath !== "string" || typeof input.modelPath !== "string"
12
+ || (input.pythonPath !== "" && !isAbsolute(input.pythonPath)) || (input.modelPath !== "" && !isAbsolute(input.modelPath))) {
13
+ throw new Error("本地决策需有效的启用状态及受信任的绝对路径。");
14
+ }
15
+ return { enabled: input.enabled, pythonPath: input.pythonPath, modelPath: input.modelPath };
16
+ }
5
17
  export class DecisionError extends Error {
6
18
  code;
7
19
  status;
@@ -780,7 +780,7 @@ async function callCliCandidates(request, cwd, language, opts, parseOutput) {
780
780
  .flatMap((candidate) => resolveModelGroupModels(opts.modelGroups, candidate.provider, candidate.model, { preferDefault: !candidate.model || candidate.model === "default" })
781
781
  .map((model) => ({ ...candidate, model: model || undefined })));
782
782
  if (opts.employeeChannelOnly && !opts.cliCandidates?.length) {
783
- throw new QuickCommitError("系统员工没有可用的 CLI 候选,已跳过 SDK 候选。", "AI_FALLBACK_FAILED");
783
+ throw new QuickCommitError("系统员工没有可用的 CLI 候选,已跳过 Wand Agent。", "AI_FALLBACK_FAILED");
784
784
  }
785
785
  const deadline = Date.now() + Math.max(1, opts.budgetMs ?? CLI_CHAIN_BUDGET_MS);
786
786
  if (chain.length === 1) {
@@ -0,0 +1,11 @@
1
+ export declare const LAYA_REPOSITORY = "aac6fef/laya-multilingual-mlx";
2
+ export declare const LAYA_REVISION = "f2b4faf51023039425946074e2cf1361d2db11d5";
3
+ export interface ModelFile {
4
+ path: string;
5
+ size: number;
6
+ sha256: string;
7
+ }
8
+ /** Exact upstream bytes, including tokenizer/config/identity/license, not an arbitrary HF repository. */
9
+ export declare const LAYA_FILES: readonly ModelFile[];
10
+ export declare const LAYA_MODEL_BYTES: number;
11
+ export declare function layaFileUrl(file: ModelFile): string;
@@ -0,0 +1,18 @@
1
+ export const LAYA_REPOSITORY = "aac6fef/laya-multilingual-mlx";
2
+ export const LAYA_REVISION = "f2b4faf51023039425946074e2cf1361d2db11d5";
3
+ /** Exact upstream bytes, including tokenizer/config/identity/license, not an arbitrary HF repository. */
4
+ export const LAYA_FILES = [
5
+ { path: "model.safetensors", size: 643835426, sha256: "7fc5834af4d8fdfb268d272a9d1a66e5819a0daac98241651c4c888cc43adff1" },
6
+ { path: "tokenizer/tokenizer.json", size: 34363188, sha256: "609d8f4c067cd3950f88594c5a802616cea245823836ef5848ee4fc40aab5b6f" },
7
+ { path: "encoder/config.json", size: 1938, sha256: "83f6916d13ef0f556ac461f28308dc2bffa7ebeadee8ec9e2db5812020ea5bb4" },
8
+ { path: "mlx_config.json", size: 316, sha256: "20ec723e1365a268597cc7e8cd39495c5e2eb58311b925f8e8c165ec5855cc28" },
9
+ { path: "rl_agent_config.json", size: 473, sha256: "9a669a70961064c3c6cc76d2afb8bc5fb10dcd8349bb66e5f7b9b1afb74440d5" },
10
+ { path: "tokenizer/tokenizer_config.json", size: 524, sha256: "6c6b2d8e3c84ce0e671c129cd6b374b235d6f9863042a5836358d00a89bbb5a1" },
11
+ { path: "manifest.json", size: 1773, sha256: "deb2de8ec6b1520b74866249bda632f40501440cdac29fdd90af80a3c422c778" },
12
+ { path: "LICENSE", size: 10173, sha256: "a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9" },
13
+ { path: "NOTICE", size: 698, sha256: "f23ae8ae701f5a43e186182e387aeef00b666daed18a946fc7c9ea827b00a8fa" },
14
+ ];
15
+ export const LAYA_MODEL_BYTES = LAYA_FILES.reduce((total, file) => total + file.size, 0);
16
+ export function layaFileUrl(file) {
17
+ return `https://huggingface.co/${LAYA_REPOSITORY}/resolve/${LAYA_REVISION}/${file.path}`;
18
+ }
@@ -0,0 +1,46 @@
1
+ import { type LocalDecisionConfig } from "./decision-types.js";
2
+ import type { DecisionService } from "./decision-service.js";
3
+ import { type ModelFile } from "./laya-model-manifest.js";
4
+ import { runModelSetup } from "./model-setup-process.js";
5
+ import type { SpeechService } from "./speech-service.js";
6
+ import type { LocalModelKind, LocalModelsStatus } from "./local-model-types.js";
7
+ export interface LocalModelSetupDeps {
8
+ configDir: string;
9
+ decisions: DecisionService;
10
+ speech: SpeechService;
11
+ decisionConfig(): LocalDecisionConfig;
12
+ /** Synchronous storage transaction + live service/config update, only after validation. */
13
+ configureDecision(config: LocalDecisionConfig): void;
14
+ fetch?: typeof fetch;
15
+ layaFiles?: readonly ModelFile[];
16
+ supportedLaya?: () => boolean;
17
+ run?: typeof runModelSetup;
18
+ checkRuntime?: (python: string, signal: AbortSignal) => Promise<boolean>;
19
+ timeoutMs?: number;
20
+ }
21
+ /** Deployment jobs only; existing inference owners retain their protocols, rate limits and capabilities. */
22
+ export declare class LocalModelSetupService {
23
+ private readonly deps;
24
+ private readonly files;
25
+ private readonly root;
26
+ private readonly manifest;
27
+ private readonly operations;
28
+ private readonly jobs;
29
+ private sequence;
30
+ private disposed;
31
+ constructor(deps: LocalModelSetupDeps);
32
+ private supported;
33
+ private managedModel;
34
+ private layaModel;
35
+ private python;
36
+ status(): Promise<LocalModelsStatus>;
37
+ private parse;
38
+ start(kind: LocalModelKind, action: "download" | "initialize", raw: unknown): void;
39
+ private update;
40
+ private work;
41
+ private checkRuntime;
42
+ private installer;
43
+ cancel(kind: LocalModelKind): void;
44
+ setLayaEnabled(enabled: boolean): void;
45
+ dispose(): void;
46
+ }