@co0ontty/wand 4.89.5 → 4.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +5 -3
  2. package/dist/agent-dispatch.js +4 -9
  3. package/dist/ai-team-employee-binding.js +10 -9
  4. package/dist/ai-team-runner.d.ts +2 -1
  5. package/dist/ai-team-runner.js +38 -2
  6. package/dist/ai-team-types.js +3 -2
  7. package/dist/ai-text-runner.d.ts +61 -0
  8. package/dist/ai-text-runner.js +653 -0
  9. package/dist/avatar-input.d.ts +2 -0
  10. package/dist/avatar-input.js +19 -0
  11. package/dist/bounded-process-output.d.ts +5 -0
  12. package/dist/bounded-process-output.js +9 -0
  13. package/dist/build-info.json +3 -3
  14. package/dist/command-error.d.ts +6 -0
  15. package/dist/command-error.js +8 -0
  16. package/dist/config.js +11 -42
  17. package/dist/conversation-service.d.ts +12 -2
  18. package/dist/conversation-service.js +104 -25
  19. package/dist/conversation-session-preview.d.ts +2 -0
  20. package/dist/conversation-session-preview.js +4 -0
  21. package/dist/decision-client.js +22 -6
  22. package/dist/decision-expert-employee.js +2 -2
  23. package/dist/decision-expert-identity.d.ts +1 -1
  24. package/dist/decision-expert-identity.js +1 -1
  25. package/dist/decision-expert-service.d.ts +1 -0
  26. package/dist/decision-expert-service.js +82 -76
  27. package/dist/decision-hardware.js +4 -3
  28. package/dist/decision-types.d.ts +4 -0
  29. package/dist/decision-types.js +4 -0
  30. package/dist/deployment-lock.d.ts +13 -0
  31. package/dist/deployment-lock.js +189 -0
  32. package/dist/employee-text.js +1 -1
  33. package/dist/fixed-employee-avatar.d.ts +7 -0
  34. package/dist/fixed-employee-avatar.js +13 -0
  35. package/dist/git-quick-commit.d.ts +5 -40
  36. package/dist/git-quick-commit.js +8 -636
  37. package/dist/hr-employee-identity.d.ts +5 -0
  38. package/dist/hr-employee-identity.js +5 -0
  39. package/dist/hr-employee.d.ts +5 -0
  40. package/dist/hr-employee.js +21 -0
  41. package/dist/middleware/path-safety.js +1 -1
  42. package/dist/milestone-scope.js +1 -1
  43. package/dist/model-auto-assign.d.ts +1 -1
  44. package/dist/model-auto-assign.js +1 -1
  45. package/dist/models.d.ts +4 -0
  46. package/dist/models.js +21 -1
  47. package/dist/npm-update-utils.js +5 -44
  48. package/dist/pi-auto-resources.js +6 -3
  49. package/dist/plush-avatar.d.ts +10 -1
  50. package/dist/plush-avatar.js +13 -1
  51. package/dist/plush-cat-appearance.d.ts +97 -0
  52. package/dist/plush-cat-appearance.js +118 -0
  53. package/dist/plush-cat-whiskers.d.ts +6 -0
  54. package/dist/plush-cat-whiskers.js +42 -0
  55. package/dist/process-manager.d.ts +22 -21
  56. package/dist/process-manager.js +49 -83
  57. package/dist/prompt-optimizer.d.ts +1 -1
  58. package/dist/prompt-optimizer.js +1 -1
  59. package/dist/render-host.js +1 -19
  60. package/dist/scheduled-task-service.d.ts +41 -0
  61. package/dist/scheduled-task-service.js +294 -0
  62. package/dist/scheduled-task-state.d.ts +11 -0
  63. package/dist/scheduled-task-state.js +65 -0
  64. package/dist/scheduled-task-time.d.ts +4 -0
  65. package/dist/scheduled-task-time.js +82 -0
  66. package/dist/scheduled-task-types.d.ts +71 -0
  67. package/dist/scheduled-task-types.js +1 -0
  68. package/dist/server-ai-team-routes.js +3 -17
  69. package/dist/server-employee-routes.d.ts +3 -0
  70. package/dist/server-employee-routes.js +20 -38
  71. package/dist/server-file-routes.js +44 -1
  72. package/dist/server-pi-recommendation-routes.js +13 -4
  73. package/dist/server-request.d.ts +2 -0
  74. package/dist/server-request.js +9 -0
  75. package/dist/server-resume-routes.js +1 -1
  76. package/dist/server-scheduled-task-routes.d.ts +4 -0
  77. package/dist/server-scheduled-task-routes.js +34 -0
  78. package/dist/server-session-routes.d.ts +5 -13
  79. package/dist/server-session-routes.js +35 -70
  80. package/dist/server-task-routes.d.ts +6 -21
  81. package/dist/server-task-routes.js +25 -120
  82. package/dist/server-team-dispatch-routes.js +12 -2
  83. package/dist/server-workspace-routes.d.ts +1 -1
  84. package/dist/server-workspace-routes.js +33 -78
  85. package/dist/server.js +55 -20
  86. package/dist/session-model.d.ts +15 -0
  87. package/dist/session-model.js +23 -0
  88. package/dist/session-registry.d.ts +5 -1
  89. package/dist/session-registry.js +20 -11
  90. package/dist/session-request.d.ts +9 -0
  91. package/dist/session-request.js +27 -0
  92. package/dist/session-topic.d.ts +1 -1
  93. package/dist/session-topic.js +1 -1
  94. package/dist/session-transport.d.ts +3 -1
  95. package/dist/session-transport.js +11 -5
  96. package/dist/silicon-employee-draft.d.ts +2 -2
  97. package/dist/silicon-employee-draft.js +3 -3
  98. package/dist/socket-probe.d.ts +2 -0
  99. package/dist/socket-probe.js +20 -0
  100. package/dist/storage-schedules.d.ts +20 -0
  101. package/dist/storage-schedules.js +78 -0
  102. package/dist/storage-schema.d.ts +3 -0
  103. package/dist/storage-schema.js +702 -0
  104. package/dist/storage.d.ts +42 -8
  105. package/dist/storage.js +182 -715
  106. package/dist/structured-claude-adapter.js +12 -0
  107. package/dist/structured-claude-protocol.d.ts +4 -0
  108. package/dist/structured-claude-protocol.js +18 -4
  109. package/dist/structured-codex-adapter.js +11 -0
  110. package/dist/structured-exec-pump.js +79 -38
  111. package/dist/structured-failure.d.ts +21 -0
  112. package/dist/structured-failure.js +14 -0
  113. package/dist/structured-gemini-adapter.js +12 -2
  114. package/dist/structured-grok-adapter.js +12 -2
  115. package/dist/structured-opencode-adapter.js +8 -1
  116. package/dist/structured-qoder-adapter.js +14 -0
  117. package/dist/structured-session-manager.d.ts +13 -18
  118. package/dist/structured-session-manager.js +147 -517
  119. package/dist/system-employee.d.ts +1 -1
  120. package/dist/system-employee.js +1 -1
  121. package/dist/task-archive-policy.js +1 -1
  122. package/dist/task-read-model.d.ts +27 -0
  123. package/dist/task-read-model.js +47 -0
  124. package/dist/task-retention.d.ts +1 -1
  125. package/dist/task-retention.js +1 -1
  126. package/dist/task-title-service.d.ts +30 -0
  127. package/dist/task-title-service.js +122 -0
  128. package/dist/task-title.d.ts +1 -1
  129. package/dist/task-title.js +1 -1
  130. package/dist/terminal-daemon-client.js +1 -18
  131. package/dist/user-memory.js +1 -1
  132. package/dist/web-ui/asset-runtime.d.ts +12 -0
  133. package/dist/web-ui/asset-runtime.js +6 -0
  134. package/dist/web-ui/composer-avatar-activity.d.ts +6 -0
  135. package/dist/web-ui/composer-avatar-activity.js +29 -0
  136. package/dist/web-ui/content/ai-teams.js +13 -13
  137. package/dist/web-ui/content/module-manifest.json +1 -0
  138. package/dist/web-ui/content/modules/5478be410b112154/chat-render-PY6DIXNZ.js +24 -0
  139. package/dist/web-ui/content/modules/5478be410b112154/chunk-2YZLCGYN.js +1 -0
  140. package/dist/web-ui/content/modules/5478be410b112154/chunk-6CFHW3FA.js +111 -0
  141. package/dist/web-ui/content/modules/5478be410b112154/chunk-6XQEBIEX.js +1 -0
  142. package/dist/web-ui/content/modules/5478be410b112154/chunk-DDSO7OX7.js +1 -0
  143. package/dist/web-ui/content/modules/5478be410b112154/chunk-G3CXNMA2.js +20 -0
  144. package/dist/web-ui/content/modules/5478be410b112154/chunk-LGVAPHRS.js +36 -0
  145. package/dist/web-ui/content/modules/5478be410b112154/chunk-NURCNMRU.js +1 -0
  146. package/dist/web-ui/content/modules/5478be410b112154/chunk-PCMFGZKX.js +1 -0
  147. package/dist/web-ui/content/modules/5478be410b112154/chunk-QRNA4D7E.js +1 -0
  148. package/dist/web-ui/content/modules/5478be410b112154/chunk-TMRPHNJO.js +1 -0
  149. package/dist/web-ui/content/modules/5478be410b112154/chunk-WC7RK7LP.js +1 -0
  150. package/dist/web-ui/content/modules/5478be410b112154/chunk-YOXXNXOX.js +2 -0
  151. package/dist/web-ui/content/modules/5478be410b112154/chunk-YX475LXO.js +1 -0
  152. package/dist/web-ui/content/modules/5478be410b112154/employee-card-MDWKU4V3.js +1 -0
  153. package/dist/web-ui/content/modules/5478be410b112154/host-56WPSON2.js +1 -0
  154. package/dist/web-ui/content/modules/5478be410b112154/host-5QOHKHNC.js +1 -0
  155. package/dist/web-ui/content/modules/5478be410b112154/host-AWIJ5L2K.js +1 -0
  156. package/dist/web-ui/content/modules/5478be410b112154/host-BOZLGIT5.js +69 -0
  157. package/dist/web-ui/content/modules/5478be410b112154/host-EZV22Q7Y.js +4 -0
  158. package/dist/web-ui/content/modules/5478be410b112154/host-IW43Z5ZF.js +1 -0
  159. package/dist/web-ui/content/modules/5478be410b112154/host-JT7NIUQL.js +1 -0
  160. package/dist/web-ui/content/modules/5478be410b112154/host-JWEIDB55.js +3 -0
  161. package/dist/web-ui/content/modules/5478be410b112154/host-OU5TRYH6.js +2 -0
  162. package/dist/web-ui/content/modules/5478be410b112154/host-PRSTCPVE.js +5 -0
  163. package/dist/web-ui/content/modules/5478be410b112154/host-TWPJI3HE.js +1 -0
  164. package/dist/web-ui/content/modules/5478be410b112154/host-WZGJSNML.js +1 -0
  165. package/dist/web-ui/content/modules/5478be410b112154/host-X6KQVFD6.js +1 -0
  166. package/dist/web-ui/content/modules/5478be410b112154/host-YKCQFMQR.js +1 -0
  167. package/dist/web-ui/content/modules/5478be410b112154/host-bridge-2LZX6UUZ.js +1 -0
  168. package/dist/web-ui/content/modules/5478be410b112154/library-bridge-MQFJGTGY.js +4 -0
  169. package/dist/web-ui/content/modules/5478be410b112154/page-T3VUCU2W.js +3 -0
  170. package/dist/web-ui/content/modules/5478be410b112154/panel-HJBGBBPY.js +1 -0
  171. package/dist/web-ui/content/modules/5478be410b112154/runtime.js +920 -0
  172. package/dist/web-ui/content/modules/5478be410b112154/sha256-ANXV72UC.js +1 -0
  173. package/dist/web-ui/content/modules/5478be410b112154/task-board-host-AVMDIHIV.js +2 -0
  174. package/dist/web-ui/content/modules/5478be410b112154/workspace-agent-picker-KI7QVSK3.js +1 -0
  175. package/dist/web-ui/content/modules/5478be410b112154/workspace-window-L542FGP2.js +1 -0
  176. package/dist/web-ui/content/plush-avatar.js +177 -177
  177. package/dist/web-ui/content/scripts.js +1 -994
  178. package/dist/web-ui/content/styles.css +1 -1
  179. package/dist/web-ui/content/tailwind.css +1 -1
  180. package/dist/web-ui/embedded-assets.d.ts +32 -0
  181. package/dist/web-ui/embedded-assets.js +10 -8
  182. package/dist/web-ui/markdown.d.ts +3 -2
  183. package/dist/web-ui/markdown.js +61 -48
  184. package/dist/web-ui/message-file-target.d.ts +13 -0
  185. package/dist/web-ui/message-file-target.js +85 -0
  186. package/dist/web-ui/provider-identity.d.ts +1 -1
  187. package/dist/web-ui/provider-identity.js +3 -4
  188. package/dist/web-ui/react/local-preview/controller.d.ts +2 -0
  189. package/dist/web-ui/react/local-preview/controller.js +12 -0
  190. package/dist/web-ui/scripts.d.ts +10 -0
  191. package/dist/web-ui/scripts.js +42 -5
  192. package/package.json +6 -3
  193. package/browser-extension/icons/wand-128.png +0 -0
  194. package/browser-extension/icons/wand-16.png +0 -0
  195. package/browser-extension/icons/wand-32.png +0 -0
  196. package/browser-extension/icons/wand-48.png +0 -0
  197. package/dist/web-ui/content/licenses/codex-LICENSE.txt +0 -21
@@ -1,8 +1,8 @@
1
- import { DECISION_EXPERT_ID, DECISION_EXPERT_KEY, WAND_LOCAL_DECISION_MODEL } from "./decision-expert-identity.js";
1
+ import { DECISION_EXPERT_ID, DECISION_EXPERT_KEY, DECISION_EXPERT_NAME, WAND_LOCAL_DECISION_MODEL } from "./decision-expert-identity.js";
2
2
  import { decisionHardware } from "./decision-hardware.js";
3
- import { DecisionError, parseDecisionRequest, parseDecisionResult } from "./decision-types.js";
3
+ import { DECISION_CANDIDATE_TIMEOUT_MS, DecisionError, parseDecisionRequest, parseDecisionResult } from "./decision-types.js";
4
4
  import { isOpenRouterFreeSelector } from "./openrouter-free-models.js";
5
- import { abortableEmployeeText, EmployeeCandidateRejected, employeeTextCandidates, prepareEmployeeTextCandidate } from "./employee-text.js";
5
+ import { abortableEmployeeText, employeeTextCandidates, prepareEmployeeTextCandidate } from "./employee-text.js";
6
6
  /** Bounded decisions use the employee's ordered adapters; LAYA stays a local evaluator. */
7
7
  export class DecisionExpertService {
8
8
  deps;
@@ -23,108 +23,114 @@ export class DecisionExpertService {
23
23
  });
24
24
  return { enabled: !!employee, supported: true, configured, state: local.state, queued: this.active,
25
25
  completed: local.completed, failed: local.failed, experimental: true,
26
- hardware, employeeId: employee?.id ?? DECISION_EXPERT_ID, employeeName: employee?.name ?? "决策专家",
26
+ hardware, employeeId: employee?.id ?? DECISION_EXPERT_ID, employeeName: employee?.name ?? DECISION_EXPERT_NAME,
27
27
  notice: !hardware.suitable ? hardware.message : !local.enabled || !local.configured
28
- ? "本地决策未就绪,可初始化 LAYA 或配置「决策专家」的工具与模型调用链。" : null };
28
+ ? `本地决策未就绪,可初始化 LAYA 或配置「${DECISION_EXPERT_NAME}」的工具与模型调用链。` : null };
29
29
  }
30
30
  async evaluate(value, caller, signal) {
31
31
  const request = parseDecisionRequest(value), employee = this.deps.employee();
32
32
  if (!employee || employee.systemKey !== DECISION_EXPERT_KEY)
33
- throw new DecisionError("UNAVAILABLE", "请先配置「决策专家」员工调用链。", 503);
33
+ throw new DecisionError("UNAVAILABLE", `请先配置「${DECISION_EXPERT_NAME}」员工调用链。`, 503);
34
34
  if (signal?.aborted)
35
35
  throw new DecisionError("CANCELLED", "决策已取消。", 499);
36
36
  if (this.active >= 8)
37
- throw new DecisionError("BUSY", "决策专家队列已满,请稍后重试。", 429);
37
+ throw new DecisionError("BUSY", `${DECISION_EXPERT_NAME}的决策队列已满,请稍后重试。`, 429);
38
38
  this.active += 1;
39
- const deadline = new AbortController();
40
- const timeout = new DecisionError("TIMEOUT", "决策专家请求超时,请稍后重试或调整该员工调用链。", 504);
41
- const timer = setTimeout(() => deadline.abort(timeout), this.deps.timeoutMs ?? 45_000);
42
- signal = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal;
39
+ const cancelled = () => {
40
+ if (signal?.aborted)
41
+ throw new DecisionError("CANCELLED", `${employee.name}的决策请求已取消。`, 499);
42
+ };
43
+ const candidates = employeeTextCandidates(employee.agents, this.deps.config);
44
+ const failures = [];
45
+ let executed = false, sawTimeout = false, otherExecutionFailure = false;
43
46
  const prompt = {
44
47
  system: `${employee.prompt}\n只输出JSON对象 {"answers":{...}}。对每题保留type;choice给choice和所有criteria的probabilities(总和1),score给score和以0开始索引的probabilities(总和1),noul给0到1的noul。不得执行操作或增加候选。概率是实验性辅助而非授权。`,
45
48
  prompt: JSON.stringify(request),
46
49
  };
47
50
  try {
48
- const hardware = (this.deps.hardware ?? decisionHardware)();
49
- for (const { agent, index: candidate } of employeeTextCandidates(employee.agents, this.deps.config)) {
50
- signal.throwIfAborted();
51
- if (agent.model === WAND_LOCAL_DECISION_MODEL) {
52
- if (agent.provider !== "pi" || agent.engine !== "sdk" || agent.kind !== "structured")
53
- continue;
54
- const local = this.deps.local.status();
55
- if (!hardware.suitable || !local.enabled || !local.configured || !local.supported)
56
- continue;
57
- try {
58
- const result = await abortableEmployeeText(this.deps.local.evaluate(request, caller, signal), signal);
51
+ for (const { agent, index: candidate } of candidates) {
52
+ cancelled();
53
+ const deadline = new AbortController();
54
+ const members = candidates.filter(entry => entry.index === candidate).length;
55
+ const timer = setTimeout(() => deadline.abort(new DecisionError("TIMEOUT", "候选决策超时。", 504)), Math.max(1, Math.floor((this.deps.timeoutMs ?? DECISION_CANDIDATE_TIMEOUT_MS) / members)));
56
+ const attempt = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal;
57
+ let stage = "availability";
58
+ try {
59
+ if (agent.model === WAND_LOCAL_DECISION_MODEL) {
60
+ const hardware = (this.deps.hardware ?? decisionHardware)(), local = this.deps.local.status();
61
+ if (agent.provider !== "pi" || agent.engine !== "sdk" || agent.kind !== "structured"
62
+ || !hardware.suitable || !local.enabled || !local.configured || !local.supported) {
63
+ throw new DecisionError("UNAVAILABLE", "LAYA 不可用。", 503);
64
+ }
65
+ stage = "execution";
66
+ executed = true;
67
+ const localResult = await abortableEmployeeText(this.deps.local.evaluate(request, caller, attempt), attempt);
68
+ stage = "result";
69
+ const result = parseDecisionResult(localResult, request, { model: localResult.model, runtime: "laya-mlx" });
70
+ cancelled();
71
+ attempt.throwIfAborted();
59
72
  return { ...result, executor: { employeeId: employee.id, candidate, source: "local" } };
60
73
  }
61
- catch (error) {
62
- signal.throwIfAborted();
63
- if (error instanceof DecisionError && ["CANCELLED", "INVALID_REQUEST", "RATE_LIMIT", "BUSY"].includes(error.code))
64
- throw error;
65
- // Existing LAYA fallback is read-only inference, never tool/task replay.
66
- continue;
67
- }
68
- }
69
- let run;
70
- if (this.deps.generate) {
71
- run = () => this.deps.generate(agent, request, employee, signal);
72
- }
73
- else {
74
- const availability = new AbortController();
75
- const checkTimer = setTimeout(() => availability.abort(new Error("候选可用性检查超时。")), 8000);
76
- const check = AbortSignal.any([signal, availability.signal]);
77
- let generate;
78
- let usage;
79
- try {
80
- generate = await abortableEmployeeText((this.deps.prepare ?? ((next, text, checkedSignal) => prepareEmployeeTextCandidate({
81
- config: this.deps.config, free: this.deps.free, onUsage: value => { usage = value; },
82
- }, next, text, checkedSignal)))(agent, prompt, check), check);
83
- }
84
- catch {
85
- signal.throwIfAborted();
86
- continue;
74
+ let run;
75
+ if (this.deps.generate) {
76
+ run = () => this.deps.generate(agent, request, employee, attempt);
87
77
  }
88
- finally {
89
- clearTimeout(checkTimer);
78
+ else {
79
+ const availability = new AbortController();
80
+ const checkTimer = setTimeout(() => availability.abort(new Error("候选可用性检查超时。")), 8000);
81
+ const check = AbortSignal.any([attempt, availability.signal]);
82
+ let generate;
83
+ let usage;
84
+ try {
85
+ generate = await abortableEmployeeText((this.deps.prepare ?? ((next, text, checkedSignal) => prepareEmployeeTextCandidate({
86
+ config: this.deps.config, free: this.deps.free, onUsage: value => { usage = value; },
87
+ }, next, text, checkedSignal)))(agent, prompt, check), check);
88
+ }
89
+ finally {
90
+ clearTimeout(checkTimer);
91
+ }
92
+ run = async () => {
93
+ const raw = await abortableEmployeeText(generate(), attempt);
94
+ stage = "result";
95
+ if (raw.length > 32768)
96
+ throw new Error("决策结果超过32KiB。");
97
+ const body = JSON.parse(raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, ""));
98
+ return { answers: body?.answers, inputTokens: usage?.inputTokens ?? 0, outputTokens: usage?.outputTokens ?? 0,
99
+ model: usage?.model ?? agent.model, available: !!usage };
100
+ };
90
101
  }
91
- run = async () => {
92
- const raw = await abortableEmployeeText(generate(), signal);
93
- if (raw.length > 32768)
94
- throw new Error("决策结果超过32KiB。");
95
- const body = JSON.parse(raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, ""));
96
- return { answers: body?.answers, inputTokens: usage?.inputTokens ?? 0, outputTokens: usage?.outputTokens ?? 0,
97
- model: usage?.model ?? agent.model, available: !!usage };
98
- };
99
- }
100
- try {
101
- const generated = await abortableEmployeeText(run(), signal);
102
- signal.throwIfAborted();
102
+ stage = "execution";
103
+ executed = true;
104
+ const generated = await abortableEmployeeText(run(), attempt);
105
+ stage = "result";
103
106
  const result = parseDecisionResult({ answers: generated.answers, usage: { input_tokens: generated.inputTokens, output_tokens: generated.outputTokens, truncated: false } }, request, { model: generated.model, runtime: "decision-expert" });
107
+ cancelled();
108
+ attempt.throwIfAborted();
104
109
  if (generated.available === false)
105
110
  result.usage.available = false;
106
111
  return { ...result, executor: { employeeId: employee.id, candidate,
107
112
  source: isOpenRouterFreeSelector(agent.model) ? "free-group" : agent.engine === "sdk" ? "sdk" : "cli" } };
108
113
  }
109
114
  catch (error) {
110
- signal.throwIfAborted();
111
- if (error instanceof EmployeeCandidateRejected && error.failure.retryable)
112
- continue;
113
- // Accepted/unknown delivery and invalid results never silently repeat a request.
114
- throw new DecisionError("EXECUTION_FAILED", "决策专家候选调用失败或结果无效,未自动重放;请检查该员工调用链。", 503);
115
+ cancelled();
116
+ const timedOut = deadline.signal.aborted || error instanceof DecisionError && error.code === "TIMEOUT";
117
+ sawTimeout ||= timedOut;
118
+ otherExecutionFailure ||= !timedOut && stage !== "availability";
119
+ failures.push(`候选${candidate + 1}:${timedOut ? "超时" : stage === "availability" ? "不可用" : stage === "result" ? "结果无效" : "调用失败"}`);
120
+ // Explicit decision-only fallback: no tools or task replay, and no unconfigured models.
121
+ // Even unknown delivery / malformed answers advance; only caller cancellation stops the chain.
122
+ }
123
+ finally {
124
+ clearTimeout(timer);
125
+ deadline.abort();
115
126
  }
116
127
  }
117
- throw new DecisionError("CONFIGURATION_REQUIRED", "决策专家没有当前可用的候选;请检查已配置工具、模型或初始化 LAYA。", 503);
118
- }
119
- catch (error) {
120
- if (deadline.signal.aborted)
121
- throw timeout;
122
- if (signal.aborted)
123
- throw new DecisionError("CANCELLED", "决策专家请求已取消。", 499);
124
- throw error;
128
+ cancelled();
129
+ const timedOut = sawTimeout && !otherExecutionFailure;
130
+ const code = timedOut ? "TIMEOUT" : executed ? "EXECUTION_FAILED" : "CONFIGURATION_REQUIRED";
131
+ throw new DecisionError(code, `${employee.name}已尝试全部配置的决策候选,均未成功${failures.length ? `(${failures.join(";").slice(0, 2048)})` : ""};请检查该员工调用链。`, timedOut ? 504 : 503);
125
132
  }
126
133
  finally {
127
- clearTimeout(timer);
128
134
  this.active -= 1;
129
135
  }
130
136
  }
@@ -1,16 +1,17 @@
1
1
  import os from "node:os";
2
+ import { DECISION_EXPERT_NAME } from "./decision-expert-identity.js";
2
3
  export function assessDecisionHardware(value) {
3
4
  const gib = 1024 ** 3, memoryGiB = Math.round(value.memoryBytes / gib * 10) / 10, availableGiB = Math.round(value.availableBytes / gib * 10) / 10;
4
5
  const base = { memoryGiB, availableGiB, cpuCount: value.cpuCount, minimumMemoryGiB: 8 };
5
- const insufficient = "当前机器性能或平台条件不足,建议配置「决策专家」员工的调用链,通过备用模型使用。";
6
+ const insufficient = `当前机器性能或平台条件不足,建议配置「${DECISION_EXPERT_NAME}」员工的调用链,通过备用模型使用。`;
6
7
  if (value.platform !== "darwin" || value.arch !== "arm64")
7
8
  return { ...base, suitable: false, code: "platform", message: `LAYA 本地决策需要 Apple Silicon macOS / Metal。${insufficient}` };
8
9
  if (value.metal === false)
9
10
  return { ...base, suitable: false, code: "metal", message: `当前 Metal 不可用。${insufficient}` };
10
11
  if (value.memoryBytes < 7.5 * gib)
11
- return { ...base, suitable: false, code: "memory", message: `当前机器性能不够(内存 ${memoryGiB} GiB,建议至少 8 GiB)。请配置「决策专家」员工调用链使用备用模型。` };
12
+ return { ...base, suitable: false, code: "memory", message: `当前机器性能不够(内存 ${memoryGiB} GiB,建议至少 8 GiB)。请配置「${DECISION_EXPERT_NAME}」员工调用链使用备用模型。` };
12
13
  if (value.cpuCount < 4)
13
- return { ...base, suitable: false, code: "cpu", message: `当前机器性能不够(可用 CPU ${value.cpuCount} 核,建议至少 4 核)。请配置「决策专家」员工调用链。` };
14
+ return { ...base, suitable: false, code: "cpu", message: `当前机器性能不够(可用 CPU ${value.cpuCount} 核,建议至少 4 核)。请配置「${DECISION_EXPERT_NAME}」员工调用链。` };
14
15
  // Darwin's free memory excludes reclaimable cache. Don't falsely reject an otherwise capable Mac based on that number alone.
15
16
  return { ...base, suitable: true, code: "ready", message: "本机基本配置满足 LAYA 本地决策要求;实际运行时与 Metal 能力仍需初始化验证。" };
16
17
  }
@@ -2,6 +2,10 @@ export declare const DECISION_MAX_BYTES: number;
2
2
  export declare const DECISION_MAX_QUESTIONS = 8;
3
3
  export declare const DECISION_MAX_OPTIONS = 8;
4
4
  export declare const DECISION_CHECKPOINT = "aac6fef/laya-multilingual-mlx";
5
+ /** Each saved employee slot gets its own budget; group leaves share that slot. */
6
+ export declare const DECISION_CANDIDATE_TIMEOUT_MS = 45000;
7
+ /** At most four saved slots, plus transport/cleanup overhead. */
8
+ export declare const DECISION_CHAIN_WAIT_MS: number;
5
9
  export interface LocalDecisionConfig {
6
10
  enabled: boolean;
7
11
  pythonPath: string;
@@ -3,6 +3,10 @@ export const DECISION_MAX_BYTES = 32 * 1024;
3
3
  export const DECISION_MAX_QUESTIONS = 8;
4
4
  export const DECISION_MAX_OPTIONS = 8;
5
5
  export const DECISION_CHECKPOINT = "aac6fef/laya-multilingual-mlx";
6
+ /** Each saved employee slot gets its own budget; group leaves share that slot. */
7
+ export const DECISION_CANDIDATE_TIMEOUT_MS = 45_000;
8
+ /** At most four saved slots, plus transport/cleanup overhead. */
9
+ export const DECISION_CHAIN_WAIT_MS = DECISION_CANDIDATE_TIMEOUT_MS * 4 + 5_000;
6
10
  export function parseLocalDecisionConfig(value) {
7
11
  if (!value || typeof value !== "object" || Array.isArray(value))
8
12
  throw new Error("本地决策配置无效。");
@@ -0,0 +1,13 @@
1
+ export interface DeploymentLock {
2
+ lockPath: string;
3
+ token: string;
4
+ ownerPid: number;
5
+ owned: boolean;
6
+ }
7
+ /** Existing npm updaters and local deployment use the same atomic directory protocol. */
8
+ export declare function acquireDeploymentLock(value: string, options?: {
9
+ ownerPid?: number;
10
+ inherited?: readonly DeploymentLock[];
11
+ }): DeploymentLock;
12
+ export declare function releaseDeploymentLock(lock: DeploymentLock): void;
13
+ export declare function acquireDeploymentLocks(paths: readonly string[], ownerPid?: number): DeploymentLock[];
@@ -0,0 +1,189 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ function canonicalLockPath(value) {
7
+ const absolute = path.resolve(value);
8
+ mkdirSync(path.dirname(absolute), { recursive: true });
9
+ return path.join(realpathSync(path.dirname(absolute)), path.basename(absolute));
10
+ }
11
+ function ownerOf(lockPath) {
12
+ try {
13
+ return JSON.parse(readFileSync(path.join(lockPath, "owner.json"), "utf8"));
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ function pidAlive(pid) {
20
+ try {
21
+ process.kill(pid, 0);
22
+ return true;
23
+ }
24
+ catch (error) {
25
+ return error.code === "EPERM";
26
+ }
27
+ }
28
+ /** Existing npm updaters and local deployment use the same atomic directory protocol. */
29
+ export function acquireDeploymentLock(value, options = {}) {
30
+ const lockPath = canonicalLockPath(value);
31
+ const ownerPid = options.ownerPid ?? process.pid;
32
+ if (!Number.isSafeInteger(ownerPid) || ownerPid <= 0)
33
+ throw new Error("无效的部署锁进程。");
34
+ const owner = ownerOf(lockPath);
35
+ const inherited = options.inherited?.find(lock => lock.lockPath === lockPath
36
+ && lock.token === owner?.token && lock.ownerPid === owner?.pid && pidAlive(lock.ownerPid));
37
+ if (inherited)
38
+ return { ...inherited, owned: false };
39
+ const token = randomUUID();
40
+ try {
41
+ mkdirSync(lockPath, { mode: 0o700 });
42
+ }
43
+ catch (error) {
44
+ if (error.code !== "EEXIST")
45
+ throw error;
46
+ throw new Error(`另一个 wand 更新正在进行中(锁: ${lockPath}${typeof owner?.pid === "number" ? `, PID ${owner.pid}` : ""})。若确认没有更新进程,请手动处理该锁目录。`);
47
+ }
48
+ try {
49
+ writeFileSync(path.join(lockPath, "owner.json"), `${JSON.stringify({ pid: ownerPid, token, createdAt: Date.now() })}\n`, { mode: 0o600 });
50
+ }
51
+ catch (error) {
52
+ // No other cooperative writer can have acquired the directory we just created.
53
+ rmSync(lockPath, { recursive: true, force: true });
54
+ throw error;
55
+ }
56
+ return { lockPath, token, ownerPid, owned: true };
57
+ }
58
+ export function releaseDeploymentLock(lock) {
59
+ if (!lock.owned)
60
+ return;
61
+ const owner = ownerOf(lock.lockPath);
62
+ if (owner?.token === lock.token && owner.pid === lock.ownerPid) {
63
+ rmSync(lock.lockPath, { recursive: true, force: true });
64
+ }
65
+ }
66
+ export function acquireDeploymentLocks(paths, ownerPid = process.pid) {
67
+ const acquired = [];
68
+ try {
69
+ for (const lockPath of [...new Set(paths)])
70
+ acquired.push(acquireDeploymentLock(lockPath, { ownerPid }));
71
+ return acquired;
72
+ }
73
+ catch (error) {
74
+ for (const lock of acquired.reverse())
75
+ releaseDeploymentLock(lock);
76
+ throw error;
77
+ }
78
+ }
79
+ function receipts(raw) {
80
+ const parsed = JSON.parse(raw);
81
+ if (!Array.isArray(parsed))
82
+ throw new Error("无效的部署锁回执。");
83
+ return parsed.map((item) => {
84
+ if (!item || typeof item !== "object")
85
+ throw new Error("无效的部署锁回执。");
86
+ const lock = item;
87
+ if (typeof lock.lockPath !== "string" || !path.isAbsolute(lock.lockPath)
88
+ || typeof lock.token !== "string" || !Number.isSafeInteger(lock.ownerPid) || lock.ownerPid <= 0
89
+ || typeof lock.owned !== "boolean")
90
+ throw new Error("无效的部署锁回执。");
91
+ return lock;
92
+ });
93
+ }
94
+ async function runProjectCommand(command) {
95
+ if (!command.length)
96
+ throw new Error("部署锁缺少执行命令。");
97
+ const inherited = process.env.WAND_DEPLOYMENT_LOCKS ? receipts(process.env.WAND_DEPLOYMENT_LOCKS) : [];
98
+ const lock = acquireDeploymentLock(path.join(process.cwd(), ".wand-build-lock"), { inherited });
99
+ try {
100
+ const env = { ...process.env, WAND_DEPLOYMENT_LOCKS: JSON.stringify([...inherited, lock]) };
101
+ const child = spawn(command[0], command.slice(1), { stdio: "inherit", env, detached: process.platform !== "win32" });
102
+ let interrupted = null;
103
+ const interrupt = (signal) => {
104
+ interrupted = signal;
105
+ try {
106
+ if (process.platform !== "win32" && child.pid)
107
+ process.kill(-child.pid, signal);
108
+ else
109
+ child.kill(signal);
110
+ }
111
+ catch (error) {
112
+ if (error.code !== "ESRCH")
113
+ throw error;
114
+ }
115
+ };
116
+ const onInt = () => interrupt("SIGINT");
117
+ const onTerm = () => interrupt("SIGTERM");
118
+ process.on("SIGINT", onInt);
119
+ process.on("SIGTERM", onTerm);
120
+ try {
121
+ const status = await new Promise((resolve, reject) => {
122
+ child.once("error", reject);
123
+ child.once("close", code => resolve(code));
124
+ });
125
+ // npm can exit before a grandchild finishes handling the signal. Keep the lease
126
+ // until every writer in this owned build/check process group has actually gone.
127
+ if (process.platform !== "win32" && child.pid) {
128
+ while (true) {
129
+ try {
130
+ process.kill(-child.pid, 0);
131
+ }
132
+ catch (error) {
133
+ if (error.code === "ESRCH")
134
+ break;
135
+ throw error;
136
+ }
137
+ await new Promise(resolve => setTimeout(resolve, 50));
138
+ }
139
+ }
140
+ process.exitCode = interrupted === "SIGINT" ? 130 : interrupted === "SIGTERM" ? 143 : status ?? 1;
141
+ }
142
+ finally {
143
+ process.off("SIGINT", onInt);
144
+ process.off("SIGTERM", onTerm);
145
+ }
146
+ }
147
+ finally {
148
+ releaseDeploymentLock(lock);
149
+ }
150
+ }
151
+ async function main() {
152
+ const [mode, ...args] = process.argv.slice(2);
153
+ if (mode === "--run-project") {
154
+ if (args.shift() !== "--")
155
+ throw new Error("部署锁命令需用 -- 分隔。");
156
+ await runProjectCommand(args);
157
+ return;
158
+ }
159
+ if (args[0] !== "--owner-pid" || !/^\d+$/.test(args[1] ?? ""))
160
+ throw new Error("部署锁需要 owner-pid。");
161
+ const pid = Number(args[1]);
162
+ args.splice(0, 2);
163
+ if (mode === "--acquire") {
164
+ const paths = [];
165
+ while (args.length) {
166
+ if (args.shift() !== "--lock" || !args.length)
167
+ throw new Error("无效的部署锁路径参数。");
168
+ paths.push(args.shift());
169
+ }
170
+ if (!paths.length)
171
+ throw new Error("部署锁路径不能为空。");
172
+ process.stdout.write(JSON.stringify(acquireDeploymentLocks(paths, pid)));
173
+ }
174
+ else if (mode === "--release") {
175
+ if (args.length)
176
+ throw new Error("释放部署锁不接受多余参数。");
177
+ const locks = receipts(readFileSync(0, "utf8"));
178
+ if (locks.some(lock => lock.ownerPid !== pid))
179
+ throw new Error("部署锁归属不匹配。");
180
+ for (const lock of locks.reverse())
181
+ releaseDeploymentLock(lock);
182
+ }
183
+ else {
184
+ throw new Error("未知部署锁操作。");
185
+ }
186
+ }
187
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
188
+ main().catch(error => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });
189
+ }
@@ -75,7 +75,7 @@ export async function prepareEmployeeTextCandidate(deps, agent, request, signal)
75
75
  if (agent.engine !== "sdk") {
76
76
  if (!providerCliInstalled(agent.provider) || isOpenRouterFreeSelector(agent.model))
77
77
  throw new Error("CLI候选不可用。");
78
- const { callCliAiText, assertGeminiTextCapability } = await import("./git-quick-commit.js");
78
+ const { callCliAiText, assertGeminiTextCapability } = await import("./ai-text-runner.js");
79
79
  if (agent.provider === "gemini")
80
80
  assertGeminiTextCapability();
81
81
  // One adapter call, never re-enter the candidate chain.
@@ -18,6 +18,13 @@ export declare function employeeAvatarValue(identity: {
18
18
  systemKey?: string | null;
19
19
  avatar?: string | null;
20
20
  }): string;
21
+ /** Uploaded photos are data URLs up to 60KB. Short tokens are the only avatar bytes lists should repeat. */
22
+ export declare function isUploadedAvatar(value: string | null | undefined): boolean;
23
+ /**
24
+ * List cards, nested session summaries and routine events keep plush/cat tokens.
25
+ * Uploaded bytes stay on the employee record, session detail and the first init packet.
26
+ */
27
+ export declare function compactTransportAvatar(value: string | null | undefined): string | undefined;
21
28
  /** The avatar exception does not change any other built-in employee's edit policy. */
22
29
  export declare function isFixedAvatarEmployee(identity: {
23
30
  id?: string | null;
@@ -11,6 +11,19 @@ export function fixedEmployeeAvatar(identity) {
11
11
  export function employeeAvatarValue(identity) {
12
12
  return fixedEmployeeAvatar(identity) ?? identity.avatar ?? "";
13
13
  }
14
+ /** Uploaded photos are data URLs up to 60KB. Short tokens are the only avatar bytes lists should repeat. */
15
+ export function isUploadedAvatar(value) {
16
+ return typeof value === "string" && value.startsWith("data:");
17
+ }
18
+ /**
19
+ * List cards, nested session summaries and routine events keep plush/cat tokens.
20
+ * Uploaded bytes stay on the employee record, session detail and the first init packet.
21
+ */
22
+ export function compactTransportAvatar(value) {
23
+ if (!value || isUploadedAvatar(value))
24
+ return undefined;
25
+ return value;
26
+ }
14
27
  /** The avatar exception does not change any other built-in employee's edit policy. */
15
28
  export function isFixedAvatarEmployee(identity) {
16
29
  return fixedEmployeeAvatar(identity) !== null;
@@ -1,7 +1,9 @@
1
- import { type EmployeeTextDeps } from "./employee-text.js";
2
- import type { AiTextRequest } from "./types.js";
3
1
  import { GitStatusResult, PushResult, QuickCommitResult, SessionProvider, SessionSnapshot, TagHeadResult } from "./types.js";
4
- export type QuickCommitErrorCode = "CWD_MISSING" | "NO_CWD" | "NOT_A_GIT_REPO" | "NO_COMMIT" | "NOTHING_TO_COMMIT" | "NOTHING_TO_PUSH" | "EMPTY_MESSAGE" | "EMPTY_TAG" | "EMPTY_AI_MESSAGE" | "AI_FALLBACK_FAILED" | "INVALID_AI_TAG" | "TAG_EXISTS" | "GIT_ADD_FAILED" | "GIT_DIFF_FAILED" | "GIT_COMMIT_FAILED" | "GIT_TAG_FAILED" | "CLAUDE_CLI_MISSING" | "CLAUDE_CLI_FAILED" | "CLAUDE_TIMEOUT";
2
+ /** provider 缺省值:无法识别时回落到 Claude。 */
3
+ import { type QuickCommitAiOptions } from "./ai-text-runner.js";
4
+ export { assertGeminiTextCapability, callCliAiText, callConfiguredAiText, withOpsPersona } from "./ai-text-runner.js";
5
+ export type { QuickCommitAiOptions } from "./ai-text-runner.js";
6
+ export { QuickCommitError, type QuickCommitErrorCode } from "./command-error.js";
5
7
  /** Non-blocking status implementation used by HTTP routes. */
6
8
  export declare function getGitStatusAsync(cwd: string): Promise<GitStatusResult>;
7
9
  interface QuickCommitOptions extends CommitInputOptions, QuickCommitAiOptions {
@@ -19,28 +21,6 @@ interface QuickCommitOptions extends CommitInputOptions, QuickCommitAiOptions {
19
21
  */
20
22
  submodule?: boolean;
21
23
  }
22
- export interface QuickCommitAiOptions {
23
- modelGroups?: import("./model-groups.js").ModelGroup[];
24
- provider?: SessionProvider;
25
- model?: string | null;
26
- thinkingEffort?: SessionSnapshot["thinkingEffort"];
27
- inheritEnv?: boolean;
28
- /** 内置「系统运维」员工的角色设定:作为系统提示前缀注入。 */
29
- opsPersona?: string;
30
- /** 员工候选链(按顺序);未设置时只用 provider/model 这一次调用。 */
31
- cliCandidates?: import("./types.js").AiCliCandidate[];
32
- /** SDK 使用当前实例配置与现有免费分组服务,不创建其他执行渠道。 */
33
- employeeText?: EmployeeTextDeps;
34
- /** 系统应用必须经员工渠道;没有可用候选时直接失败,不退回默认 provider。 */
35
- employeeChannelOnly?: boolean;
36
- /** 整条候选链的时间上限;默认 CLI_CHAIN_BUDGET_MS。第一条消息里的调用要短得多。 */
37
- budgetMs?: number;
38
- signal?: AbortSignal;
39
- }
40
- export declare class QuickCommitError extends Error {
41
- readonly code: QuickCommitErrorCode;
42
- constructor(message: string, code: QuickCommitErrorCode);
43
- }
44
24
  /**
45
25
  * 生成 commit message / tag 的输入源。
46
26
  * - iterationDigest:本轮迭代的提示词标题清单(默认输入,省 token、不读代码);
@@ -51,20 +31,6 @@ export interface CommitInputOptions {
51
31
  iterationDigest?: string;
52
32
  includeDiff?: boolean;
53
33
  }
54
- type CliAiTextOptions = QuickCommitAiOptions & {
55
- deadline?: number;
56
- };
57
- export declare function assertGeminiTextCapability(): void;
58
- export declare function callCliAiText(request: AiTextRequest, cwd: string, language: string, opts: CliAiTextOptions): Promise<string>;
59
- /** 角色设定走系统提示前缀;任务自己的输出格式与约束必须排在后面。 */
60
- export declare function withOpsPersona(request: AiTextRequest, persona?: string): AiTextRequest;
61
- /**
62
- * Run a lightweight AI request through the system employee's configured chain.
63
- * provider 与引擎由候选决定;SDK 与 CLI 均保留各自适配和身份。
64
- * parseOutput 在候选链内解析/校验结果;失败后继续下一条,而非提前宣布成功。
65
- */
66
- export declare function callConfiguredAiText(request: AiTextRequest, cwd: string, language: string, opts: QuickCommitAiOptions): Promise<string>;
67
- export declare function callConfiguredAiText<T>(request: AiTextRequest, cwd: string, language: string, opts: QuickCommitAiOptions, parseOutput: (raw: string) => T): Promise<T>;
68
34
  export interface GenerateCommitMessageResult {
69
35
  message: string;
70
36
  /** AI-suggested next tag derived from the staged diff and the latest existing tag. */
@@ -97,4 +63,3 @@ interface PushOptions {
97
63
  export declare function runPush(opts: PushOptions): Promise<PushResult>;
98
64
  export declare function runQuickCommitWithFallback(opts: QuickCommitOptions): Promise<QuickCommitResult>;
99
65
  export declare function runQuickCommit(opts: QuickCommitOptions): Promise<QuickCommitResult>;
100
- export {};