@co0ontty/wand 4.88.0 → 4.89.0-beta.gde0b3cd

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 (79) hide show
  1. package/dist/agent-dispatch.d.ts +5 -0
  2. package/dist/agent-dispatch.js +8 -0
  3. package/dist/ai-team-runner.d.ts +1 -1
  4. package/dist/ai-team-runner.js +13 -5
  5. package/dist/ai-team-types.js +3 -2
  6. package/dist/build-info.json +4 -4
  7. package/dist/commit-task-archive.d.ts +3 -0
  8. package/dist/commit-task-archive.js +15 -0
  9. package/dist/config.js +1 -0
  10. package/dist/daemon-maintenance-state.d.ts +2 -0
  11. package/dist/daemon-maintenance-targets.d.ts +1 -0
  12. package/dist/daemon-maintenance-targets.js +26 -0
  13. package/dist/daemon-maintenance.d.ts +7 -0
  14. package/dist/daemon-maintenance.js +69 -0
  15. package/dist/decision-expert-employee.d.ts +1 -1
  16. package/dist/decision-expert-employee.js +3 -3
  17. package/dist/decision-expert-service.d.ts +6 -5
  18. package/dist/decision-expert-service.js +75 -49
  19. package/dist/decision-types.d.ts +2 -1
  20. package/dist/employee-text.d.ts +28 -0
  21. package/dist/employee-text.js +124 -0
  22. package/dist/git-quick-commit.d.ts +13 -4
  23. package/dist/git-quick-commit.js +112 -33
  24. package/dist/local-model-setup.d.ts +7 -1
  25. package/dist/local-model-setup.js +54 -7
  26. package/dist/local-model-types.d.ts +2 -1
  27. package/dist/process-manager.d.ts +4 -1
  28. package/dist/process-manager.js +5 -6
  29. package/dist/render-daemon-client.d.ts +2 -0
  30. package/dist/render-daemon-client.js +6 -0
  31. package/dist/server-daemon-maintenance-routes.d.ts +8 -0
  32. package/dist/server-daemon-maintenance-routes.js +19 -0
  33. package/dist/server-employee-routes.d.ts +2 -0
  34. package/dist/server-employee-routes.js +19 -45
  35. package/dist/server-file-routes.js +61 -25
  36. package/dist/server-local-model-routes.js +12 -0
  37. package/dist/server-session-routes.d.ts +2 -1
  38. package/dist/server-session-routes.js +13 -10
  39. package/dist/server-speech-routes.d.ts +2 -0
  40. package/dist/server-speech-routes.js +7 -1
  41. package/dist/server-task-routes.d.ts +3 -0
  42. package/dist/server-task-routes.js +13 -10
  43. package/dist/server.js +38 -11
  44. package/dist/session-ai-context.d.ts +6 -4
  45. package/dist/session-ai-context.js +10 -9
  46. package/dist/silicon-employee-dispatch.d.ts +4 -6
  47. package/dist/silicon-employee-dispatch.js +9 -6
  48. package/dist/speech-polisher-employee.d.ts +6 -0
  49. package/dist/speech-polisher-employee.js +23 -0
  50. package/dist/speech-polisher-identity.d.ts +4 -0
  51. package/dist/speech-polisher-identity.js +4 -0
  52. package/dist/speech-polisher-service.d.ts +31 -0
  53. package/dist/speech-polisher-service.js +88 -0
  54. package/dist/speech-service.d.ts +3 -1
  55. package/dist/speech-service.js +14 -3
  56. package/dist/speech-support.d.ts +6 -0
  57. package/dist/speech-support.js +26 -0
  58. package/dist/speech-types.d.ts +5 -0
  59. package/dist/storage.d.ts +2 -0
  60. package/dist/storage.js +18 -1
  61. package/dist/structured-session-manager.d.ts +3 -2
  62. package/dist/structured-session-manager.js +11 -7
  63. package/dist/system-employee.d.ts +6 -4
  64. package/dist/system-employee.js +8 -5
  65. package/dist/types.d.ts +3 -1
  66. package/dist/user-memory.d.ts +2 -0
  67. package/dist/user-memory.js +1 -1
  68. package/dist/web-ui/content/ai-teams.js +13 -13
  69. package/dist/web-ui/content/scripts.js +324 -212
  70. package/dist/web-ui/content/styles.css +1 -1
  71. package/dist/web-ui/content/tailwind.css +1 -1
  72. package/dist/web-ui/embedded-assets.d.ts +1 -0
  73. package/dist/web-ui/embedded-assets.js +4 -3
  74. package/dist/web-ui/index.js +3 -3
  75. package/dist/web-ui/provider-identity.d.ts +2 -0
  76. package/dist/web-ui/provider-identity.js +7 -1
  77. package/dist/web-ui/scripts.d.ts +2 -0
  78. package/dist/web-ui/scripts.js +5 -0
  79. package/package.json +1 -1
@@ -0,0 +1,88 @@
1
+ import { abortableEmployeeText, EmployeeCandidateRejected, employeeTextCandidates, prepareEmployeeTextCandidate } from "./employee-text.js";
2
+ import { SPEECH_POLISHER_ID, SPEECH_POLISHER_KEY } from "./speech-polisher-identity.js";
3
+ export function parseSpeechPolishOutput(raw) {
4
+ if (raw.length > 32_768)
5
+ throw new Error("口述整理结果过长。");
6
+ const body = JSON.parse(raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, ""));
7
+ if (!body || typeof body.text !== "string" || !body.text.trim() || body.text.length > 8000 || body.text.includes("\0")) {
8
+ throw new Error("口述整理结果无效。");
9
+ }
10
+ return body.text.trim();
11
+ }
12
+ /** Text-only SDK inference; configured CLIs use the shared one-shot text helper. */
13
+ export class SpeechPolisherService {
14
+ deps;
15
+ lifetime = new AbortController();
16
+ active = 0;
17
+ constructor(deps) {
18
+ this.deps = deps;
19
+ }
20
+ async polish(text, signal, budgetMs = 45_000) {
21
+ const original = { text, originalText: text, optimized: false, employeeId: SPEECH_POLISHER_ID };
22
+ const employee = this.deps.employee();
23
+ if (!text.trim())
24
+ return original;
25
+ if (text.length > 8000 || text.includes("\0"))
26
+ throw new Error("语音文字无效或超过8000字符。");
27
+ signal?.throwIfAborted();
28
+ if (!employee || employee.systemKey !== SPEECH_POLISHER_KEY || this.active >= 2 || budgetMs < 1000) {
29
+ return { ...original, optimizationError: "口述整理暂不可用,已保留原始转写。" };
30
+ }
31
+ this.active += 1;
32
+ const deadline = AbortSignal.timeout(Math.max(1, budgetMs));
33
+ const combined = AbortSignal.any([this.lifetime.signal, deadline, ...(signal ? [signal] : [])]);
34
+ const request = { system: `${employee.prompt}\n输出一个 JSON 对象:{"text":"整理后的完整文字"},不加其他字段或说明。`,
35
+ prompt: JSON.stringify({ transcript: text }) };
36
+ try {
37
+ for (const candidate of employeeTextCandidates(employee.agents, this.deps.config)) {
38
+ combined.throwIfAborted();
39
+ const agent = candidate.agent;
40
+ let generate;
41
+ // An unavailable candidate has accepted no user input: continue the configured order.
42
+ try {
43
+ generate = await this.prepareCandidate(agent, request, combined);
44
+ }
45
+ catch {
46
+ combined.throwIfAborted();
47
+ continue;
48
+ }
49
+ combined.throwIfAborted();
50
+ // Only a structured, verified pre-execution refusal permits another candidate.
51
+ let raw;
52
+ try {
53
+ raw = await abortableEmployeeText(generate(), combined);
54
+ }
55
+ catch (error) {
56
+ combined.throwIfAborted();
57
+ if (error instanceof EmployeeCandidateRejected && error.failure.retryable)
58
+ continue;
59
+ throw error;
60
+ }
61
+ combined.throwIfAborted();
62
+ return { text: parseSpeechPolishOutput(raw), originalText: text, optimized: true, employeeId: employee.id,
63
+ candidate: candidate.index };
64
+ }
65
+ return { ...original, optimizationError: "口述整理师的候选均不可用,已保留原始转写。" };
66
+ }
67
+ catch {
68
+ signal?.throwIfAborted();
69
+ this.lifetime.signal.throwIfAborted();
70
+ return { ...original, optimizationError: "口述整理失败或超时,已保留原始转写。" };
71
+ }
72
+ finally {
73
+ this.active -= 1;
74
+ }
75
+ }
76
+ async prepareCandidate(agent, request, signal) {
77
+ const deadline = new AbortController();
78
+ const timer = setTimeout(() => deadline.abort(new Error("候选可用性检查超时。")), 8000);
79
+ const checkedSignal = AbortSignal.any([signal, deadline.signal]);
80
+ try {
81
+ return await abortableEmployeeText((this.deps.prepare ?? ((candidate, input, checked) => prepareEmployeeTextCandidate(this.deps, candidate, input, checked)))(agent, request, checkedSignal), checkedSignal);
82
+ }
83
+ finally {
84
+ clearTimeout(timer);
85
+ }
86
+ }
87
+ dispose() { this.lifetime.abort(); }
88
+ }
@@ -36,13 +36,15 @@ export declare class SpeechService {
36
36
  private disposed;
37
37
  private maintenance;
38
38
  private readonly initialized;
39
+ private configurationRevision;
39
40
  constructor(storage: Pick<WandStorage, "getPreference" | "setPreference">, configDir: string, options?: SpeechServiceOptions);
40
41
  settings(): SpeechSettings;
41
42
  configure(patch: unknown): SpeechSettings;
43
+ settingsRevision(): number;
42
44
  acquireMaintenance(): void;
43
45
  releaseMaintenance(): void;
44
46
  /** Explicit admin health check loads the selected model using synthetic silence, without enabling it. */
45
- initializeModel(id: string, signal: AbortSignal): Promise<void>;
47
+ initializeModel(id: string, signal: AbortSignal, acceleration?: import("./speech-types.js").SpeechAcceleration): Promise<void>;
46
48
  private modelPath;
47
49
  private modelReady;
48
50
  private verifyModel;
@@ -72,6 +72,7 @@ export class SpeechService {
72
72
  disposed = false;
73
73
  maintenance = false;
74
74
  initialized = new Set();
75
+ configurationRevision = 0;
75
76
  constructor(storage, configDir, options = {}) {
76
77
  this.storage = storage;
77
78
  this.options = options;
@@ -89,8 +90,10 @@ export class SpeechService {
89
90
  configure(patch) {
90
91
  const settings = parseSpeechSettings(patch, this.settings());
91
92
  this.storage.setPreference(SPEECH_SETTINGS_KEY, settings);
93
+ this.configurationRevision += 1;
92
94
  return settings;
93
95
  }
96
+ settingsRevision() { return this.configurationRevision; }
94
97
  acquireMaintenance() {
95
98
  if (this.disposed)
96
99
  throw new SpeechError("UNAVAILABLE", "语音服务已关闭。", 503);
@@ -100,7 +103,7 @@ export class SpeechService {
100
103
  }
101
104
  releaseMaintenance() { this.maintenance = false; }
102
105
  /** Explicit admin health check loads the selected model using synthetic silence, without enabling it. */
103
- async initializeModel(id, signal) {
106
+ async initializeModel(id, signal, acceleration = this.settings().acceleration) {
104
107
  const model = this.models.find((value) => value.id === id);
105
108
  if (!model)
106
109
  throw new SpeechError("INVALID_MODEL", "模型不存在。");
@@ -126,8 +129,16 @@ export class SpeechService {
126
129
  wav.writeUInt32LE(8000, 40);
127
130
  const input = path.join(directory, "check.wav"), output = path.join(directory, "check");
128
131
  await writeFile(input, wav, { mode: 0o600 });
129
- await this.run(runtime.executable, ["-m", this.modelPath(model), "-f", input, "-of", output, "-otxt", "-nt", "-l", "en", "-t", String(this.settings().threads),
130
- ...(this.settings().acceleration === "cpu" || runtime.backend === "cpu" ? ["-ng"] : [])], directory, signal, this.options.timeoutMs ?? 120_000);
132
+ const args = ["-m", this.modelPath(model), "-f", input, "-of", output, "-otxt", "-nt", "-l", "en", "-t", String(this.settings().threads)];
133
+ const cpu = acceleration === "cpu" || runtime.backend === "cpu";
134
+ try {
135
+ await this.run(runtime.executable, [...args, ...(cpu ? ["-ng"] : [])], directory, signal, this.options.timeoutMs ?? 120_000);
136
+ }
137
+ catch (error) {
138
+ if (acceleration !== "auto" || cpu || signal.aborted || !(error instanceof SpeechError) || error.code !== "ENGINE_FAILED")
139
+ throw error;
140
+ await this.run(runtime.executable, [...args, "-ng"], directory, signal, this.options.timeoutMs ?? 120_000);
141
+ }
131
142
  signal.throwIfAborted();
132
143
  if (this.disposed)
133
144
  throw new SpeechError("UNAVAILABLE", "语音服务已关闭。", 503);
@@ -0,0 +1,6 @@
1
+ export interface SpeechSupport {
2
+ supported: boolean;
3
+ reason: string | null;
4
+ }
5
+ /** Fixed, read-only tool probes; never install anything during status polling. */
6
+ export declare function speechSupport(runtimeAvailable: boolean): Promise<SpeechSupport>;
@@ -0,0 +1,26 @@
1
+ import { execFile } from "node:child_process";
2
+ import { buildChildEnv, systemEnvValue } from "./env-utils.js";
3
+ function available(command, args = ["--version"]) {
4
+ return new Promise((resolve) => {
5
+ execFile(command, args, { env: buildChildEnv(true), timeout: 5000, maxBuffer: 16_384, windowsHide: true }, (error) => resolve(!error));
6
+ });
7
+ }
8
+ /** Fixed, read-only tool probes; never install anything during status polling. */
9
+ export async function speechSupport(runtimeAvailable) {
10
+ if (!["darwin", "linux", "win32"].includes(process.platform)) {
11
+ return { supported: false, reason: "服务端语音识别仅支持 macOS、Linux 和 Windows。" };
12
+ }
13
+ if (runtimeAvailable)
14
+ return { supported: true, reason: null };
15
+ if (systemEnvValue("WAND_WHISPER_BIN"))
16
+ return { supported: false, reason: "配置的语音运行时不可用,请管理员检查 WAND_WHISPER_BIN。" };
17
+ const tools = [
18
+ ["Git", available("git")], ["CMake", available("cmake")],
19
+ ["C++ 编译器", process.platform === "win32" ? available("cl", ["/help"]) : available("c++")],
20
+ ...(process.platform === "win32" ? [] : [["Make", available("make")]]),
21
+ ];
22
+ const results = await Promise.all(tools.map(async ([label, probe]) => await probe ? null : label));
23
+ const missing = results.filter(Boolean);
24
+ return missing.length ? { supported: false, reason: `缺少可用的 ${missing.join("、")},安装后重新打开此页即可启用。` }
25
+ : { supported: true, reason: null };
26
+ }
@@ -41,6 +41,11 @@ export interface SpeechResult {
41
41
  text: string;
42
42
  model: string;
43
43
  backend: SpeechBackend;
44
+ originalText?: string;
45
+ optimized?: boolean;
46
+ optimizationError?: string;
47
+ employeeId?: string;
48
+ candidate?: number;
44
49
  }
45
50
  export declare const SPEECH_SAMPLE_RATE = 16000;
46
51
  export declare const SPEECH_MAX_SECONDS = 60;
package/dist/storage.d.ts CHANGED
@@ -398,6 +398,8 @@ export declare class WandStorage {
398
398
  ensureSystemSiliconEmployee(seed?: SystemEmployeeSeed): SiliconEmployee;
399
399
  /** Stable built-in decision employee; seed once and retain the user's ordered call chain. */
400
400
  ensureDecisionExpertEmployee(): SiliconEmployee;
401
+ /** Seed the SDK free group once, then preserve independent user choices. */
402
+ ensureSpeechPolisherEmployee(): SiliconEmployee;
401
403
  saveSiliconEmployee(employee: SiliconEmployee): void;
402
404
  archiveSiliconEmployee(id: string, archivedAt?: string): void;
403
405
  unarchiveSiliconEmployee(id: string): void;
package/dist/storage.js CHANGED
@@ -20,6 +20,8 @@ import { isThinkingEffort } from "./structured-provider-common.js";
20
20
  import { DEFAULT_EMPLOYEE_KEY } from "./ai-team-types.js";
21
21
  import { defaultEmployeeDefinition, legacyPtyRoleIdentity } from "./default-employee.js";
22
22
  import { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
23
+ import { SPEECH_POLISHER_KEY } from "./speech-polisher-identity.js";
24
+ import { speechPolisherDefinition } from "./speech-polisher-employee.js";
23
25
  import { decisionExpertDefinition } from "./decision-expert-employee.js";
24
26
  import { normalizeEmployeeKnowledge } from "./employee-knowledge-content.js";
25
27
  import { EMPLOYEE_KNOWLEDGE_MAX_ENTRIES } from "./employee-knowledge-types.js";
@@ -322,7 +324,11 @@ function parseQueuedMessages(raw) {
322
324
  function parseWorktreeInfo(raw) {
323
325
  const parsed = safeJsonParse(raw);
324
326
  if (parsed && typeof parsed.branch === "string" && typeof parsed.path === "string") {
325
- return { branch: parsed.branch, path: parsed.path };
327
+ return {
328
+ branch: parsed.branch, path: parsed.path,
329
+ ...(typeof parsed.baseRef === "string" ? { baseRef: parsed.baseRef } : {}),
330
+ ...(typeof parsed.repoRoot === "string" ? { repoRoot: parsed.repoRoot } : {}),
331
+ };
326
332
  }
327
333
  return undefined;
328
334
  }
@@ -2773,6 +2779,17 @@ export class WandStorage {
2773
2779
  }
2774
2780
  return existing;
2775
2781
  }
2782
+ /** Seed the SDK free group once, then preserve independent user choices. */
2783
+ ensureSpeechPolisherEmployee() {
2784
+ const existing = this.getSystemSiliconEmployee(SPEECH_POLISHER_KEY);
2785
+ const definition = speechPolisherDefinition(new Date().toISOString(), existing);
2786
+ if (!existing || existing.name !== definition.name || existing.duty !== definition.duty || existing.prompt !== definition.prompt
2787
+ || existing.avatar !== definition.avatar || existing.archivedAt || !existing.agents.length) {
2788
+ this.saveSiliconEmployee(definition);
2789
+ return definition;
2790
+ }
2791
+ return existing;
2792
+ }
2776
2793
  saveSiliconEmployee(employee) {
2777
2794
  const tags = isBuiltinSiliconEmployee(employee)
2778
2795
  ? siliconEmployeeTags(employee)
@@ -86,6 +86,7 @@ export declare class StructuredSessionManager {
86
86
  private readonly logger;
87
87
  private readonly execHost?;
88
88
  private readonly decisionRuntime;
89
+ private readonly openRouter?;
89
90
  /** 「智能分配」专用的本地决策入口:只有真正能跑时才注入,没注入则用系统员工回退。 */
90
91
  private readonly autoAssignEvaluate;
91
92
  private readonly sessions;
@@ -140,7 +141,7 @@ export declare class StructuredSessionManager {
140
141
  private detachedRecoveryRetryTimer;
141
142
  private detachedRecoveryFailureLogged;
142
143
  private disposed;
143
- constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined, decisionRuntime?: () => DecisionRuntimeAccess | null, openRouter?: OpenRouterFreeModelsService,
144
+ constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined, decisionRuntime?: () => DecisionRuntimeAccess | null, openRouter?: OpenRouterFreeModelsService | undefined,
144
145
  /** 「智能分配」专用的本地决策入口:只有真正能跑时才注入,没注入则用系统员工回退。 */
145
146
  autoAssignEvaluate?: () => DecisionRuntimeAccess["evaluate"]);
146
147
  private archiveExpiredSessions;
@@ -173,7 +174,7 @@ export declare class StructuredSessionManager {
173
174
  get(id: string): SessionSnapshot | null;
174
175
  setSessionWorkspace(id: string, membership: Pick<SessionSnapshot, "workspaceId" | "workspaceTaskId">): void;
175
176
  setSessionTopic(id: string, title: string, description: string): SessionSnapshot;
176
- /** 归档会先停掉还在跑的回合,会话 ID 和 provider session id 都留着。 */
177
+ /** 归档只写标记,正在执行的回合和历史继续保留。 */
177
178
  setSessionArchived(id: string, archived: boolean): SessionSnapshot;
178
179
  private setSessionTopicGenerating;
179
180
  /**
@@ -10,6 +10,7 @@ import { stampNewToolUseTimes } from "./tool-use-timestamps.js";
10
10
  import { buildChildEnv } from "./env-utils.js";
11
11
  import { getErrorMessage } from "./error-utils.js";
12
12
  import { getDefaultModelForProvider } from "./config.js";
13
+ import { isLocalDecisionModel } from "./decision-expert-identity.js";
13
14
  import { recordIterationPrompt } from "./iteration-log.js";
14
15
  import { startEmployeeKnowledgeRunner } from "./employee-knowledge.js";
15
16
  import { CoreRunner } from "./core-runner.js";
@@ -436,6 +437,7 @@ export class StructuredSessionManager {
436
437
  logger;
437
438
  execHost;
438
439
  decisionRuntime;
440
+ openRouter;
439
441
  autoAssignEvaluate;
440
442
  sessions = new Map();
441
443
  // Both freshly started runners and adopted daemon runs own the same input gate.
@@ -500,6 +502,7 @@ export class StructuredSessionManager {
500
502
  this.logger = logger;
501
503
  this.execHost = execHost;
502
504
  this.decisionRuntime = decisionRuntime;
505
+ this.openRouter = openRouter;
503
506
  this.autoAssignEvaluate = autoAssignEvaluate;
504
507
  const wrap = (runner) => withModelGroups(withDecisionAccess(runner, storage, decisionRuntime), config);
505
508
  this.claudeCliRunner = wrap(runners.claudeCli ?? new ClaudeCliRunner({ language: () => this.config.language }, this.execHost));
@@ -1238,11 +1241,9 @@ export class StructuredSessionManager {
1238
1241
  this.emitStructuredSnapshot(updated);
1239
1242
  return updated;
1240
1243
  }
1241
- /** 归档会先停掉还在跑的回合,会话 ID 和 provider session id 都留着。 */
1244
+ /** 归档只写标记,正在执行的回合和历史继续保留。 */
1242
1245
  setSessionArchived(id, archived) {
1243
- const current = archived && this.requireSession(id).status === "running"
1244
- ? this.stop(id)
1245
- : this.requireSession(id);
1246
+ const current = this.requireSession(id);
1246
1247
  const archivedAt = archived ? new Date().toISOString() : null;
1247
1248
  const updated = { ...current, archived, archivedAt };
1248
1249
  this.sessions.set(id, updated);
@@ -1309,7 +1310,7 @@ export class StructuredSessionManager {
1309
1310
  input,
1310
1311
  cwd: session.cwd,
1311
1312
  language: this.config.language,
1312
- ai: resolveSystemAiContext(session, this.config, this.storage.getSystemSiliconEmployee()),
1313
+ ai: resolveSystemAiContext(session, this.config, this.storage.getSystemSiliconEmployee(), this.openRouter),
1313
1314
  readNativeTitle: () => readNativeSessionTitle(resolveSessionProvider(session), session.claudeSessionId, session.cwd),
1314
1315
  hasNativeTitle,
1315
1316
  onGenerating: (generating) => {
@@ -1499,7 +1500,10 @@ export class StructuredSessionManager {
1499
1500
  return null;
1500
1501
  const candidates = original.employeeCandidates ?? [];
1501
1502
  const index = original.employeeCandidateIndex ?? 0;
1502
- const nextIndex = index + 1;
1503
+ let nextIndex = index + 1;
1504
+ // LAYA has no chat adapter; keep original candidate indices while skipping bounded-only models.
1505
+ while (nextIndex < candidates.length && isLocalDecisionModel(candidates[nextIndex].model))
1506
+ nextIndex += 1;
1503
1507
  if (!original.employeeId || (original.messages?.length ?? 0) !== 0 || original.claudeSessionId
1504
1508
  || !error.failure.retryable || nextIndex >= candidates.length)
1505
1509
  return null;
@@ -1688,7 +1692,7 @@ export class StructuredSessionManager {
1688
1692
  prompt,
1689
1693
  groups: this.config.modelGroups,
1690
1694
  evaluate: this.autoAssignEvaluate(),
1691
- ai: resolveSystemAiContext(session, this.config, this.storage.getSystemSiliconEmployee()),
1695
+ ai: resolveSystemAiContext(session, this.config, this.storage.getSystemSiliconEmployee(), this.openRouter),
1692
1696
  cwd: session.cwd,
1693
1697
  language: this.config.language,
1694
1698
  });
@@ -6,8 +6,8 @@ export { SYSTEM_EMPLOYEE_ID, SYSTEM_EMPLOYEE_KEY, SYSTEM_EMPLOYEE_NAME, SYSTEM_E
6
6
  * 内置的「系统运维」员工。
7
7
  *
8
8
  * Wand 自己的 AI 调用(commit message / tag、提示词优化、会话与任务标题、
9
- * 员工起草)都通过这位员工的 CLI 候选链执行:名字、职责与人设写死在代码里,不可编辑、不可归档、
10
- * 不可删除;只有执行候选(CLI 工具 + 模型 + 思考深度,按顺序降级)由用户维护。候选中的 SDK 会被系统应用跳过。
9
+ * 员工起草)都通过这位员工的候选链执行:名字、职责与人设写死在代码里,不可编辑、不可归档、
10
+ * 不可删除;只有执行候选(工具 + 引擎 + 模型 + 思考深度,按顺序降级)由用户维护。
11
11
  */
12
12
  export declare const SYSTEM_EMPLOYEE_DUTY = "Wand \u7CFB\u7EDF\u8FD0\u7EF4\uFF1A\u670D\u52A1\u4E0E CLI \u7EBF\u8DEF\u3001\u66F4\u65B0\u5206\u53D1\u3001\u4ED3\u5E93\u4E0E\u4F1A\u8BDD\u64CD\u4F5C\uFF0CWand \u81EA\u6709 AI \u4EFB\u52A1\u7684\u6267\u884C\u8005\u3002";
13
13
  /**
@@ -29,6 +29,8 @@ export declare function systemEmployeeSeedAgents(seed?: SystemEmployeeSeed): Wan
29
29
  export declare function systemEmployeeDefinition(agents: WandTaskAgent[], now: string, existing?: SiliconEmployee | null): SiliconEmployee;
30
30
  export declare function isSdkEmployeeAgent(agent: Pick<WandTaskAgent, "engine">): boolean;
31
31
  /**
32
- * 系统应用只走员工的 CLI 渠道:员工候选里可以保存 SDK,但这里明确跳过。
32
+ * 系统应用沿用员工配置顺序和引擎;不按触发来源过滤工具。
33
33
  */
34
- export declare function systemEmployeeCliCandidates(employee: Pick<SiliconEmployee, "agents"> | null | undefined): AiCliCandidate[];
34
+ export declare function systemEmployeeCandidates(employee: Pick<SiliconEmployee, "agents"> | null | undefined): AiCliCandidate[];
35
+ /** Compatibility name for existing callers; the chain also includes Wand Agent. */
36
+ export declare const systemEmployeeCliCandidates: typeof systemEmployeeCandidates;
@@ -7,8 +7,8 @@ export { SYSTEM_EMPLOYEE_ID, SYSTEM_EMPLOYEE_KEY, SYSTEM_EMPLOYEE_NAME, SYSTEM_E
7
7
  * 内置的「系统运维」员工。
8
8
  *
9
9
  * Wand 自己的 AI 调用(commit message / tag、提示词优化、会话与任务标题、
10
- * 员工起草)都通过这位员工的 CLI 候选链执行:名字、职责与人设写死在代码里,不可编辑、不可归档、
11
- * 不可删除;只有执行候选(CLI 工具 + 模型 + 思考深度,按顺序降级)由用户维护。候选中的 SDK 会被系统应用跳过。
10
+ * 员工起草)都通过这位员工的候选链执行:名字、职责与人设写死在代码里,不可编辑、不可归档、
11
+ * 不可删除;只有执行候选(工具 + 引擎 + 模型 + 思考深度,按顺序降级)由用户维护。
12
12
  */
13
13
  export const SYSTEM_EMPLOYEE_DUTY = "Wand 系统运维:服务与 CLI 线路、更新分发、仓库与会话操作,Wand 自有 AI 任务的执行者。";
14
14
  /**
@@ -61,19 +61,22 @@ export function isSdkEmployeeAgent(agent) {
61
61
  return agent.engine === "sdk";
62
62
  }
63
63
  /**
64
- * 系统应用只走员工的 CLI 渠道:员工候选里可以保存 SDK,但这里明确跳过。
64
+ * 系统应用沿用员工配置顺序和引擎;不按触发来源过滤工具。
65
65
  */
66
- export function systemEmployeeCliCandidates(employee) {
66
+ export function systemEmployeeCandidates(employee) {
67
67
  if (!employee)
68
68
  return [];
69
69
  return employee.agents
70
- .filter((agent) => agent.kind === "structured" && !isSdkEmployeeAgent(agent))
70
+ .filter((agent) => agent.kind === "structured")
71
71
  .map((agent) => {
72
72
  const model = (agent.model ?? "").trim();
73
73
  return {
74
74
  provider: agent.provider,
75
+ ...(agent.engine ? { engine: agent.engine } : {}),
75
76
  model: !model || model === "default" ? undefined : model,
76
77
  thinkingEffort: agent.thinkingEffort,
77
78
  };
78
79
  });
79
80
  }
81
+ /** Compatibility name for existing callers; the chain also includes Wand Agent. */
82
+ export const systemEmployeeCliCandidates = systemEmployeeCandidates;
package/dist/types.d.ts CHANGED
@@ -226,11 +226,13 @@ export interface AiTextRequest {
226
226
  prompt: string;
227
227
  }
228
228
  /**
229
- * Wand 自有 AI 调用(commit message、标题、提示词优化…)的一条 CLI 执行候选。
229
+ * Wand 自有 AI 调用(commit message、标题、提示词优化…)的一条执行候选。
230
230
  * 候选顺序即降级顺序;由内置「系统运维」员工维护。
231
231
  */
232
232
  export interface AiCliCandidate {
233
233
  provider: SessionProvider;
234
+ /** 缺省为 CLI;显式 sdk 保持 Wand Agent 引擎身份。 */
235
+ engine?: "cli" | "sdk";
234
236
  /** 具体模型 ID;未设置表示跟随 provider 默认模型。 */
235
237
  model?: string;
236
238
  thinkingEffort?: ThinkingEffort;
@@ -1,3 +1,4 @@
1
+ import type { EmployeeTextDeps } from "./employee-text.js";
1
2
  import type { WandStorage } from "./storage.js";
2
3
  import type { WandConfig } from "./types.js";
3
4
  import { type UserMemoryEvent, type UserMemoryPreference, type UserMemoryView } from "./user-memory-types.js";
@@ -18,6 +19,7 @@ export declare class UserMemoryService {
18
19
  constructor(deps: {
19
20
  storage: WandStorage;
20
21
  config: WandConfig;
22
+ free?: EmployeeTextDeps["free"];
21
23
  notifyChanged?: (id: string) => void;
22
24
  now?: () => number;
23
25
  generate?: (events: UserMemoryEvent[]) => Promise<UserMemoryPreference[]>;
@@ -160,7 +160,7 @@ export class UserMemoryService {
160
160
  }, config.defaultCwd, config.language ?? "", resolveSystemAiContext({
161
161
  provider: config.defaultProvider ?? "claude", command: config.defaultProvider ?? "claude",
162
162
  selectedModel: null, thinkingEffort: config.defaultThinkingEffort,
163
- }, config, storage.getSystemSiliconEmployee()), (raw) => parseUserMemoryPreferences(raw, events));
163
+ }, config, storage.getSystemSiliconEmployee(), this.deps.free), (raw) => parseUserMemoryPreferences(raw, events));
164
164
  // Test seam and production share the exact same validator.
165
165
  const validated = parseUserMemoryPreferences(JSON.stringify({ preferences }), events);
166
166
  const referenced = new Set(validated.flatMap((entry) => entry.evidenceIds));