@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,31 +1,35 @@
1
1
  import { employeeAvatarValue, isFixedAvatarEmployee } from "./fixed-employee-avatar.js";
2
+ import { HR_EMPLOYEE_KEY } from "./hr-employee-identity.js";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { AI_TEAM_MAX_CANDIDATES, SILICON_EMPLOYEE_AVATAR_MAX_CHARS, agentKey, isBuiltinSiliconEmployee, parseSiliconEmployeeTags, siliconEmployeeTags, } from "./ai-team-types.js";
4
5
  import { getErrorMessage } from "./error-utils.js";
5
6
  import { asyncRoute } from "./express-async.js";
6
- import { bodyObject, sendRouteError, text } from "./server-request.js";
7
+ import { parseStoredAvatar } from "./avatar-input.js";
8
+ import { bodyObject, boundedText, sendRouteError, text } from "./server-request.js";
7
9
  import { parseTaskAgent } from "./server-task-routes.js";
8
10
  import { resolveSystemAiContext } from "./session-ai-context.js";
9
11
  import { employeeCliAvailable } from "./silicon-employee-dispatch.js";
10
12
  import { EMPLOYEE_CREATION_DEFAULTS_PREF, resolveSiliconEmployeeDefaults, } from "./silicon-employee-defaults.js";
11
13
  import { availableEmployeeProviders, generateSiliconEmployeeDraft, SiliconEmployeeDraftError, validateStructuredEmployeeDraft, } from "./silicon-employee-draft.js";
12
14
  import { SESSION_PROVIDERS } from "./session-provider.js";
13
- import { parsePlushAvatar, parsePlushCatAvatar } from "./plush-avatar.js";
14
15
  import { DEFAULT_WAND_TASK_AGENT_KIND } from "./task-types.js";
15
16
  import { EMPLOYEE_KNOWLEDGE_MAX_ENTRIES } from "./employee-knowledge-types.js";
16
- /** 员工起草没有会话上下文,按「默认 provider」解析,与任务标题 / 提示词优化一致。 */
17
- function employeeDraftAiOptions(storage, config, free) {
17
+ /** Recruitment uses HR’s own ordered candidates and persona, never the ops chain. */
18
+ export function employeeDraftAiOptions(storage, config, free) {
18
19
  if (!config)
19
- return {};
20
+ throw new Error("招聘需要服务端 AI 配置。");
21
+ const hr = storage.getSystemSiliconEmployee(HR_EMPLOYEE_KEY);
22
+ if (!hr)
23
+ throw new Error("系统 HR 嘟嘟尚未初始化,请重启服务后重试。");
20
24
  const provider = config.defaultProvider ?? "claude";
21
- return resolveSystemAiContext({
22
- provider,
23
- structuredState: undefined,
24
- runner: undefined,
25
- command: provider === "qoder" ? "qodercli" : provider,
26
- selectedModel: null,
27
- thinkingEffort: config.defaultThinkingEffort,
28
- }, config, storage.getSystemSiliconEmployee(), free);
25
+ return { ...resolveSystemAiContext({
26
+ provider,
27
+ structuredState: undefined,
28
+ runner: undefined,
29
+ command: provider === "qoder" ? "qodercli" : provider,
30
+ selectedModel: null,
31
+ thinkingEffort: config.defaultThinkingEffort,
32
+ }, config, hr, free), opsPersona: hr.prompt };
29
33
  }
30
34
  function sendEmployeeError(res, error, context) {
31
35
  const message = getErrorMessage(error, "硅基员工操作失败。");
@@ -50,30 +54,8 @@ function rejectSystemEmployeeMutation(existing, action) {
50
54
  return;
51
55
  throw new Error(`「${existing.name}」是 Wand 内置员工,不能${action}。`);
52
56
  }
53
- function boundedText(value, label, min, max) {
54
- const result = text(value);
55
- if (result.length < min)
56
- throw new Error(`${label}不能为空。`);
57
- if (result.length > max)
58
- throw new Error(`${label}不能超过 ${max} 个字符。`);
59
- return result;
60
- }
61
57
  function parseAvatar(value, name) {
62
- if (value !== undefined && value !== null && typeof value !== "string") {
63
- throw new Error(`员工「${name}」的头像格式无效。`);
64
- }
65
- const avatar = text(value);
66
- if (!avatar || /^cat:\d{1,2}$/.test(avatar))
67
- return avatar;
68
- if (parsePlushAvatar(avatar) || parsePlushCatAvatar(avatar))
69
- return avatar;
70
- if (!/^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/=]+$/.test(avatar)) {
71
- throw new Error(`员工「${name}」的头像格式无效。`);
72
- }
73
- if (avatar.length > SILICON_EMPLOYEE_AVATAR_MAX_CHARS) {
74
- throw new Error(`员工「${name}」的头像太大。`);
75
- }
76
- return avatar;
58
+ return parseStoredAvatar(value, `员工「${name}」`, SILICON_EMPLOYEE_AVATAR_MAX_CHARS);
77
59
  }
78
60
  /** Other built-ins keep profile locks; the two stable cat identities lock only avatar. */
79
61
  const SYSTEM_EMPLOYEE_LOCKED_FIELDS = [
@@ -90,7 +72,7 @@ export function parseSystemEmployeeAgents(value, existing) {
90
72
  const body = bodyObject(value);
91
73
  for (const field of SYSTEM_EMPLOYEE_LOCKED_FIELDS.filter((field) => !isFixedAvatarEmployee(existing) || field.key === "avatar")) {
92
74
  const incoming = body[field.key];
93
- if (incoming === undefined || (incoming === null && !isFixedAvatarEmployee(existing)))
75
+ if (incoming === undefined)
94
76
  continue;
95
77
  const current = existing[field.key];
96
78
  const isSame = typeof incoming === "string" && (incoming === current
@@ -102,7 +84,7 @@ export function parseSystemEmployeeAgents(value, existing) {
102
84
  if (body.tags !== undefined && JSON.stringify(body.tags) !== JSON.stringify(siliconEmployeeTags(existing))) {
103
85
  throw new Error("该员工是内置的,标签不可修改。");
104
86
  }
105
- if (isFixedAvatarEmployee(existing) && body.id !== undefined && body.id !== existing.id)
87
+ if (body.id !== undefined && body.id !== existing.id)
106
88
  throw new Error("该员工是内置的,身份不可修改。");
107
89
  if (body.systemKey !== undefined && body.systemKey !== existing.systemKey) {
108
90
  throw new Error("该员工是内置的,身份不可修改。");
@@ -1,7 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import { exec } from "node:child_process";
3
3
  import { createReadStream } from "node:fs";
4
- import { lstat, mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { lstat, mkdir, readdir, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import process from "node:process";
7
7
  import { promisify } from "node:util";
@@ -44,6 +44,40 @@ export function recordRecentPath(storage, cwd) {
44
44
  }
45
45
  export function registerFileRoutes(app, deps) {
46
46
  const { storage, defaultCwd } = deps;
47
+ // Resolve relative artifacts against the stored source session, never a client-supplied cwd.
48
+ app.get("/api/session-file/:sessionId", asyncRoute(async (req, res) => {
49
+ const session = storage.getSessionSlim(String(req.params.sessionId));
50
+ if (!session?.cwd) {
51
+ res.status(404).json({ error: "文件所属会话不存在。" });
52
+ return;
53
+ }
54
+ const relative = typeof req.query.path === "string" ? req.query.path : "";
55
+ if (!relative || path.isAbsolute(relative) || /[\u0000-\u001f]/.test(relative) || /^[a-z][a-z\d+.-]*:/i.test(relative)) {
56
+ res.status(400).json({ error: "文件相对路径无效。" });
57
+ return;
58
+ }
59
+ const resolved = path.resolve(session.cwd, relative);
60
+ if (!isPathWithinBase(resolved, path.resolve(session.cwd)) || isBlockedFolderPath(resolved)) {
61
+ res.status(403).json({ error: "文件路径超出会话目录。" });
62
+ return;
63
+ }
64
+ const canonical = await realpath(resolved).catch(() => null);
65
+ const base = await realpath(session.cwd).catch(() => null);
66
+ if (canonical && base && (!isPathWithinBase(canonical, base) || isBlockedFolderPath(canonical))) {
67
+ res.status(403).json({ error: "文件链接目标超出会话目录。" });
68
+ return;
69
+ }
70
+ if (req.query.mode === "preview") {
71
+ if (!/\.html?$/i.test(resolved)) {
72
+ res.status(400).json({ error: "目标不是 HTML 文件。" });
73
+ return;
74
+ }
75
+ res.redirect(307, "/api/local-file/" + Buffer.from(resolved).toString("base64url") + "/");
76
+ }
77
+ else {
78
+ res.redirect(307, "/api/file-raw?path=" + encodeURIComponent(resolved) + (req.query.mode === "download" ? "&download=1" : ""));
79
+ }
80
+ }));
47
81
  app.get("/api/path-suggestions", asyncRoute(async (req, res) => {
48
82
  const query = typeof req.query.q === "string" ? req.query.q : "";
49
83
  try {
@@ -397,6 +431,7 @@ export function registerFileRoutes(app, deps) {
397
431
  headers: {
398
432
  "Cache-Control": "private, max-age=60",
399
433
  "X-Content-Type-Options": "nosniff",
434
+ ...(/\.html?$/i.test(ext) ? { "Content-Security-Policy": "sandbox allow-scripts; connect-src 'none'; form-action 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'" } : {}),
400
435
  },
401
436
  readErrorMessage: "Failed to read file",
402
437
  });
@@ -463,6 +498,12 @@ export function registerFileRoutes(app, deps) {
463
498
  res.status(400).json({ error: "目标不是普通文件。" });
464
499
  return;
465
500
  }
501
+ const canonicalBase = await realpath(baseRoot);
502
+ const canonicalFile = await realpath(filePath);
503
+ if (!isPathWithinBase(canonicalFile, canonicalBase) || isBlockedFolderPath(canonicalFile)) {
504
+ res.status(403).json({ error: "访问被拒绝:资源链接目标越界。" });
505
+ return;
506
+ }
466
507
  const ext = path.extname(filePath).toLowerCase();
467
508
  const baseName = path.basename(filePath);
468
509
  const encodedName = encodeURIComponent(baseName);
@@ -474,6 +515,8 @@ export function registerFileRoutes(app, deps) {
474
515
  headers: {
475
516
  "Cache-Control": "private, no-cache",
476
517
  "X-Content-Type-Options": "nosniff",
518
+ // Even opening the artifact directly must not grant the Wand origin.
519
+ ...(/\.html?$/i.test(ext) ? { "Content-Security-Policy": "sandbox allow-scripts; connect-src 'none'; form-action 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'" } : {}),
477
520
  },
478
521
  readErrorMessage: "读取本地文件失败。",
479
522
  });
@@ -1,4 +1,5 @@
1
- import { DecisionError } from "./decision-types.js";
1
+ import { DECISION_EXPERT_NAME } from "./decision-expert-identity.js";
2
+ import { DECISION_CHAIN_WAIT_MS, DecisionError } from "./decision-types.js";
2
3
  import { discoverPiResources, resolvePiResourceSelection } from "./pi-resource-catalog.js";
3
4
  import { parsePiRecommendationRequest, recommendPiResources } from "./pi-resource-recommendation.js";
4
5
  /** Uses the parent's login + sessions-scope middleware; inference-only tokens cannot enter this route. */
@@ -26,7 +27,7 @@ export function registerPiRecommendationRoute(app, deps) {
26
27
  }
27
28
  const status = deps.decisions?.status();
28
29
  if (!deps.decisions || !status?.enabled || !status.supported || !status.configured || settings.localDecision === false) {
29
- const notice = status && "notice" in status && typeof status.notice === "string" ? status.notice : "本地决策未启用或不可用,原选择未改变;请配置「决策专家」调用链。";
30
+ const notice = status && "notice" in status && typeof status.notice === "string" ? status.notice : `本地决策未启用或不可用,原选择未改变;请配置「${DECISION_EXPERT_NAME}」调用链。`;
30
31
  throw new DecisionError("UNAVAILABLE", notice, 503);
31
32
  }
32
33
  const input = parsePiRecommendationRequest(req.body);
@@ -34,11 +35,19 @@ export function registerPiRecommendationRoute(app, deps) {
34
35
  throw new DecisionError("BUSY", "本会话正在推荐,请等待当前请求完成。", 409);
35
36
  active.add(id);
36
37
  owns = true;
37
- timer = setTimeout(() => abort.abort(), 15_000);
38
38
  const inventory = await discoverPiResources(deps.config, deps.structured.get(id).cwd);
39
39
  resolvePiResourceSelection(inventory, input.candidates);
40
40
  const result = await recommendPiResources({ ...input, catalog: inventory.catalog,
41
- evaluate: (value, caller, signal) => deps.decisions.evaluate(value, caller, signal),
41
+ evaluate: async (value, caller, signal) => {
42
+ const decisionTimer = setTimeout(() => abort.abort(), DECISION_CHAIN_WAIT_MS);
43
+ timer = decisionTimer;
44
+ try {
45
+ return await deps.decisions.evaluate(value, caller, signal);
46
+ }
47
+ finally {
48
+ clearTimeout(decisionTimer);
49
+ }
50
+ },
42
51
  caller: `pi-recommend:${id}`, signal: abort.signal });
43
52
  const latest = deps.structured.get(id);
44
53
  if (!latest || latest.archived || latest.provider !== "pi" || latest.piSettings?.localDecision === false
@@ -7,6 +7,8 @@
7
7
  */
8
8
  /** 取字符串并 trim;非字符串一律当缺省处理。 */
9
9
  export declare function text(value: unknown, fallback?: string): string;
10
+ /** Shared length check for employee and team name fields. */
11
+ export declare function boundedText(value: unknown, label: string, min: number, max: number): string;
10
12
  /** Express query 值可能是数组,取第一个字符串。 */
11
13
  export declare function firstQueryStringValue(value: unknown): string | undefined;
12
14
  /** 请求头可能是数组(重复头),取第一个值。 */
@@ -10,6 +10,15 @@ import { getErrorMessage } from "./error-utils.js";
10
10
  export function text(value, fallback = "") {
11
11
  return typeof value === "string" ? value.trim() : fallback;
12
12
  }
13
+ /** Shared length check for employee and team name fields. */
14
+ export function boundedText(value, label, min, max) {
15
+ const result = text(value);
16
+ if (result.length < min)
17
+ throw new Error(`${label}不能为空。`);
18
+ if (result.length > max)
19
+ throw new Error(`${label}不能超过 ${max} 个字符。`);
20
+ return result;
21
+ }
13
22
  /** Express query 值可能是数组,取第一个字符串。 */
14
23
  export function firstQueryStringValue(value) {
15
24
  if (typeof value === "string")
@@ -11,7 +11,7 @@
11
11
  import { asyncRoute } from "./express-async.js";
12
12
  import { getErrorMessage } from "./error-utils.js";
13
13
  import { isProviderSessionId, isSafeProviderSessionId } from "./resume-policy.js";
14
- import { parseExecutionMode, parseSessionCreationOrigin } from "./server-session-routes.js";
14
+ import { parseExecutionMode, parseSessionCreationOrigin } from "./session-request.js";
15
15
  import { resolveWorkspaceIdForNewSession } from "./workspace-binding.js";
16
16
  const STRUCTURED_RESUME_SPECS = [
17
17
  {
@@ -0,0 +1,4 @@
1
+ import type { Express } from "express";
2
+ import { type ScheduledTaskService } from "./scheduled-task-service.js";
3
+ /** Authentication and the sessions capability are enforced by the server route family. */
4
+ export declare function registerScheduledTaskRoutes(app: Express, service: ScheduledTaskService): void;
@@ -0,0 +1,34 @@
1
+ import { ScheduledTaskConflictError, ScheduledTaskNotFoundError } from "./scheduled-task-service.js";
2
+ import { sendRouteError } from "./server-request.js";
3
+ function failure(res, error) {
4
+ sendRouteError(res, error, "定时任务操作失败。", error instanceof ScheduledTaskNotFoundError ? 404
5
+ : error instanceof ScheduledTaskConflictError ? 409 : 400);
6
+ }
7
+ /** Authentication and the sessions capability are enforced by the server route family. */
8
+ export function registerScheduledTaskRoutes(app, service) {
9
+ app.get("/api/scheduled-tasks", (req, res) => { res.json({ tasks: service.list(req.query.includeCancelled === "1") }); });
10
+ app.get("/api/scheduled-tasks/:id", (req, res) => {
11
+ try {
12
+ res.json(service.get(req.params.id));
13
+ }
14
+ catch (error) {
15
+ failure(res, error);
16
+ }
17
+ });
18
+ app.post("/api/scheduled-tasks", (req, res) => {
19
+ try {
20
+ res.status(201).json({ task: service.create(req.body) });
21
+ }
22
+ catch (error) {
23
+ failure(res, error);
24
+ }
25
+ });
26
+ app.patch("/api/scheduled-tasks/:id", (req, res) => {
27
+ try {
28
+ res.json({ task: service.update(req.params.id, req.body) });
29
+ }
30
+ catch (error) {
31
+ failure(res, error);
32
+ }
33
+ });
34
+ }
@@ -1,21 +1,14 @@
1
1
  import type { Express } from "express";
2
+ import type { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
2
3
  import { ProcessManager } from "./process-manager.js";
3
- import { StructuredSessionManager } from "./structured-session-manager.js";
4
4
  import { WandStorage } from "./storage.js";
5
- import { ExecutionMode, ProcessEvent, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
6
- import type { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
5
+ import { StructuredSessionManager } from "./structured-session-manager.js";
6
+ import { ExecutionMode, ProcessEvent, SessionSnapshot, WandConfig } from "./types.js";
7
7
  import { toSessionListItemDTO } from "./session-transport.js";
8
- import { type ProviderHistoryDeleter, SessionRegistry } from "./session-registry.js";
9
8
  import { type PiRecommendationRuntime } from "./server-pi-recommendation-routes.js";
10
9
  import { type SessionDirectoryNode } from "./session-directory-tree.js";
11
- export declare function parseExecutionMode(value: unknown, fallback: ExecutionMode): ExecutionMode;
12
- export declare function parseSessionCreationOrigin(body: {
13
- sessionSource?: unknown;
14
- automationId?: unknown;
15
- } | null | undefined): {
16
- sessionSource: SessionSource;
17
- automationId?: string;
18
- };
10
+ import { SessionRegistry, type ProviderHistoryDeleter } from "./session-registry.js";
11
+ export { parseExecutionMode, parseSessionCreationOrigin } from "./session-request.js";
19
12
  type SessionDeletionProcesses = Pick<ProcessManager, "get" | "delete"> & ProviderHistoryDeleter;
20
13
  type SessionDeletionStructured = Pick<StructuredSessionManager, "get" | "delete">;
21
14
  /**
@@ -49,4 +42,3 @@ export declare function buildSessionListPage(sessions: SessionSnapshot[], offset
49
42
  export declare function buildSessionDirectoryTree(sessions: SessionSnapshot[], customNames?: ReadonlyMap<string, string>): SessionDirectoryTreeResponse;
50
43
  export declare function registerSessionRoutes(app: Express, processes: ProcessManager, structured: StructuredSessionManager, storage: WandStorage, defaultMode: ExecutionMode, config: WandConfig, sessions: SessionRegistry, onSessionCreated?: (cwd: string | undefined | null) => void, onCompletionViewed?: (event: ProcessEvent) => void, decisions?: PiRecommendationRuntime, free?: Pick<OpenRouterFreeModelsService, "resolveForCall">): void;
51
44
  export declare function registerClaudeHistoryRoutes(app: Express, processes: ProcessManager, storage: WandStorage): void;
52
- export {};
@@ -1,41 +1,36 @@
1
1
  import { createHash } from "node:crypto";
2
- import { PtyInputDeliveryError, SessionInputError } from "./process-manager.js";
3
- import { getDefaultModelForProvider, isExecutionMode } from "./config.js";
2
+ import { parseExecutionMode, parseSessionCreationOrigin } from "./session-request.js";
3
+ import { isExecutionMode } from "./config.js";
4
+ import { DECISION_EXPERT_NAME } from "./decision-expert-identity.js";
4
5
  import { defaultRoleForCli } from "./default-employee.js";
5
- import { defaultModelGroupSelector } from "./model-groups.js";
6
- import { inspectPiExecution } from "./pi-execution.js";
7
- import { alignedBlockStart, blockWindowMessagesForTransport, compactToolMessagesForTransport, messageWindowByteBudget, sliceTurnBlocksForTransport, truncateMessagesForTransport, visibleBlockCount, windowMessagesForTransport } from "./message-truncator.js";
8
- import { toSessionDetailDTO, toSessionListItemDTO } from "./session-transport.js";
6
+ import { fallbackProviderModel, resolveExplicitOrFallbackModel, sessionEmployeeFields } from "./session-model.js";
7
+ import { PtyInputDeliveryError, SessionInputError } from "./process-manager.js";
8
+ import { generateCommitMessageOnly, getGitStatusAsync, QuickCommitError, runPush, runQuickCommitWithFallback, runTagHead, } from "./git-quick-commit.js";
9
9
  import { checkSessionWorktreeMergeabilityAsync, cleanupSessionWorktreeAsync, getWorktreeMergeErrorCode, mergeSessionWorktreeAsync, WorktreeMergeError, } from "./git-worktree.js";
10
- import { resolveSessionCwd } from "./session-cwd.js";
10
+ import { alignedBlockStart, blockWindowMessagesForTransport, compactToolMessagesForTransport, messageWindowByteBudget, sliceTurnBlocksForTransport, truncateMessagesForTransport, visibleBlockCount, windowMessagesForTransport } from "./message-truncator.js";
11
+ import { inspectPiExecution } from "./pi-execution.js";
11
12
  import { resolveSystemAiContext } from "./session-ai-context.js";
12
- import { getGitStatusAsync, QuickCommitError, runQuickCommitWithFallback, runTagHead, runPush, generateCommitMessageOnly, } from "./git-quick-commit.js";
13
+ import { resolveSessionCwd } from "./session-cwd.js";
14
+ import { toSessionDetailDTO, toSessionListItemDTO } from "./session-transport.js";
13
15
  import { getErrorMessage } from "./error-utils.js";
16
+ import { asyncRoute } from "./express-async.js";
14
17
  import { buildIterationCommitContext, COMMIT_CONTEXT_MODE_PREF_KEY, isCommitContextMode, readCommitContextMode, resolveCommitContextInput, } from "./iteration-log.js";
15
- import { inferProviderFromCommand, isSessionProvider, providerCliCommand, SESSION_PROVIDERS } from "./session-provider.js";
16
- import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
17
- import { buildProviderResumeCommand, isProviderSessionId } from "./resume-policy.js";
18
+ import { installedPiExtensions } from "./pi-extension-resources.js";
19
+ import { discoverPiResources, resolvePiResourceSelection } from "./pi-resource-catalog.js";
20
+ import { effectivePiSessionSettings, patchPiSessionSettings } from "./pi-session-settings.js";
18
21
  import { parseBoundedInteger } from "./request-limits.js";
19
- import { asyncRoute } from "./express-async.js";
22
+ import { buildProviderResumeCommand, isProviderSessionId } from "./resume-policy.js";
23
+ import { registerPiRecommendationRoute } from "./server-pi-recommendation-routes.js";
20
24
  import { sendRouteError } from "./server-request.js";
21
25
  import { registerStructuredResumeRoutes } from "./server-resume-routes.js";
22
- import { addHiddenSessionIds, deleteProviderNativeHistory, removeHiddenSessionIds } from "./session-registry.js";
26
+ import { buildDirectoryTree, normalizeSessionDirectory, } from "./session-directory-tree.js";
27
+ import { inferProviderFromCommand, isSessionProvider, providerCliCommand, SESSION_PROVIDERS } from "./session-provider.js";
28
+ import { addHiddenSessionIds, deleteOwnedSessionWithHistory, removeHiddenSessionIds } from "./session-registry.js";
29
+ import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
23
30
  import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
24
31
  import { asRecord, isStructuredImagePart } from "./structured-content.js";
25
- import { installedPiExtensions } from "./pi-extension-resources.js";
26
- import { registerPiRecommendationRoute } from "./server-pi-recommendation-routes.js";
27
- import { discoverPiResources, resolvePiResourceSelection } from "./pi-resource-catalog.js";
28
- import { effectivePiSessionSettings, patchPiSessionSettings } from "./pi-session-settings.js";
29
- import { buildDirectoryTree, normalizeSessionDirectory, } from "./session-directory-tree.js";
30
32
  import { projectCwdForSession, renameWorkspaceDirectory, resolveWorkspaceIdForNewSession, } from "./workspace-binding.js";
31
- export function parseExecutionMode(value, fallback) {
32
- if (value === undefined)
33
- return fallback;
34
- if (!isExecutionMode(value)) {
35
- throw new Error(`无效执行模式: ${String(value)}`);
36
- }
37
- return value;
38
- }
33
+ export { parseExecutionMode, parseSessionCreationOrigin } from "./session-request.js";
39
34
  /** 提交输入模式:query 覆盖 > 记住的偏好 > 默认(有提示词就优先用提示词)。 */
40
35
  function commitModeForRead(storage, override) {
41
36
  return readCommitContextMode(storage, override);
@@ -53,24 +48,6 @@ async function iterationCommitInput(storage, snapshot, body) {
53
48
  function commitMessageFromIteration(body) {
54
49
  return body.autoMessage !== false && !body.customMessage?.trim();
55
50
  }
56
- export function parseSessionCreationOrigin(body) {
57
- const rawSource = body?.sessionSource;
58
- if (rawSource !== undefined && rawSource !== "interactive" && rawSource !== "automation" && rawSource !== "startup") {
59
- throw new Error("sessionSource 必须是 interactive、automation 或 startup。");
60
- }
61
- const rawAutomationId = body?.automationId;
62
- if (rawAutomationId !== undefined && typeof rawAutomationId !== "string") {
63
- throw new Error("automationId 必须是非空字符串。");
64
- }
65
- const automationId = rawAutomationId?.trim();
66
- if (rawAutomationId !== undefined && !automationId) {
67
- throw new Error("automationId 必须是非空字符串。");
68
- }
69
- return {
70
- sessionSource: rawSource ?? "interactive",
71
- ...(automationId ? { automationId } : {}),
72
- };
73
- }
74
51
  function getInputErrorResponse(error, sessionId) {
75
52
  if (error instanceof PtyInputDeliveryError) {
76
53
  return {
@@ -129,24 +106,14 @@ function getInputDebugMeta(error) {
129
106
  * immediately reappear as a "non-Wand" session after the Wand row is removed.
130
107
  */
131
108
  export function deleteSessionWithProviderHistory(processes, structured, storage, id) {
132
- const snapshot = structured.get(id) ?? processes.get(id);
133
- if (snapshot && (snapshot.sessionKind ?? "pty") === "structured") {
134
- structured.delete(id);
135
- }
136
- else {
137
- processes.delete(id);
138
- }
139
- const providerSessionId = snapshot?.claudeSessionId?.trim();
140
- if (!snapshot || !providerSessionId)
141
- return;
142
- const provider = snapshot.provider
143
- ?? snapshot.structuredState?.provider
144
- ?? inferProviderFromCommand(snapshot.command)
145
- ?? "claude";
146
- deleteProviderNativeHistory(processes, provider, providerSessionId, snapshot.cwd);
147
- // History deletion is intentionally best-effort at the filesystem layer.
148
- // Keep a tombstone as a fallback for permission errors or process tail writes.
149
- addHiddenSessionIds(storage, [providerSessionId]);
109
+ deleteOwnedSessionWithHistory(() => {
110
+ const snapshot = structured.get(id) ?? processes.get(id);
111
+ if (snapshot?.sessionKind === "structured")
112
+ structured.delete(id);
113
+ else
114
+ processes.delete(id);
115
+ return snapshot;
116
+ }, processes, storage);
150
117
  }
151
118
  function sessionSortTimestamp(snapshot) {
152
119
  const timestamp = Date.parse(snapshot.startedAt);
@@ -157,7 +124,7 @@ function buildSessionListEntries(sessions) {
157
124
  type: "managed",
158
125
  key: `session-${session.id}`,
159
126
  sortTimestamp: sessionSortTimestamp(session),
160
- session: toSessionListItemDTO(session),
127
+ session: toSessionListItemDTO(session, { avatar: "compact" }),
161
128
  })).sort((left, right) => {
162
129
  const timestampOrder = right.sortTimestamp - left.sortTimestamp;
163
130
  return timestampOrder || left.key.localeCompare(right.key);
@@ -454,7 +421,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
454
421
  }
455
422
  });
456
423
  app.get("/api/sessions", (_req, res) => {
457
- res.json(sessions.listSlim().map(toSessionListItemDTO));
424
+ res.json(sessions.listSlim().map((session) => toSessionListItemDTO(session)));
458
425
  });
459
426
  app.get("/api/sessions/provider-usage", (_req, res) => {
460
427
  res.json(storage.countInteractiveSessionsByProvider());
@@ -524,8 +491,8 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
524
491
  model: rawModel && rawModel !== "default"
525
492
  ? rawModel
526
493
  : (employeeAgent && !requestedProvider
527
- ? employeeAgent.model === "default" ? defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider)) || getDefaultModelForProvider(config, provider) || undefined : employeeAgent.model
528
- : rawModel || defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider)) || getDefaultModelForProvider(config, provider) || undefined),
494
+ ? resolveExplicitOrFallbackModel(config, provider, employeeAgent.model)
495
+ : rawModel || fallbackProviderModel(config, provider)),
529
496
  thinkingEffort: employeeAgent?.thinkingEffort ?? (typeof body.thinkingEffort === "string"
530
497
  ? body.thinkingEffort
531
498
  : config.defaultThinkingEffort),
@@ -533,9 +500,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
533
500
  workspaceTaskId: body.workspaceTaskId,
534
501
  // 角色与规则走系统提示通道,不拼进 prompt(见 AGENTS.md「Session 输入契约」)。
535
502
  systemPrompt: role?.prompt ?? body.systemPrompt,
536
- employeeId: role?.id,
537
- employeeName: role?.name,
538
- employeeAvatar: role?.avatar,
503
+ ...sessionEmployeeFields(role),
539
504
  employeeCandidates: employee?.agents,
540
505
  employeeCandidateIndex: requestedProvider ? undefined : selectedEmployeeCandidate?.index,
541
506
  ...(engineDecision ? { engine: engineDecision.engine } : {}),
@@ -679,7 +644,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
679
644
  && status?.enabled === true && status.supported && status.configured;
680
645
  const recommendationReason = !resourceCatalog.supported ? resourceCatalog.reason
681
646
  : settings.localDecision === false ? "本会话已关闭本地决策。"
682
- : !recommendationAvailable ? status && "notice" in status && typeof status.notice === "string" ? status.notice : "本地决策未启用或运行环境不可用;请配置「决策专家」调用链。" : "";
647
+ : !recommendationAvailable ? status && "notice" in status && typeof status.notice === "string" ? status.notice : `本地决策未启用或运行环境不可用;请配置「${DECISION_EXPERT_NAME}」调用链。` : "";
683
648
  return { recommendationAvailable, recommendationReason,
684
649
  autoResourcesAvailable: recommendationAvailable, autoResourcesReason: recommendationReason,
685
650
  autoCodemodeAvailable: recommendationAvailable, skillLocksAvailable: resourceCatalog.supported,
@@ -1,11 +1,11 @@
1
1
  import type { Express } from "express";
2
- import { type WandStorage } from "./storage.js";
3
- import { generateWandTaskTitle } from "./task-title.js";
2
+ import type { AiTeamRunner } from "./ai-team-runner.js";
4
3
  import type { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
4
+ import type { ProcessManager } from "./process-manager.js";
5
5
  import type { SessionRegistry } from "./session-registry.js";
6
+ import { type WandStorage } from "./storage.js";
6
7
  import type { StructuredSessionManager } from "./structured-session-manager.js";
7
- import type { ProcessManager } from "./process-manager.js";
8
- import type { AiTeamRunner } from "./ai-team-runner.js";
8
+ import { generateWandTaskTitle } from "./task-title.js";
9
9
  import type { WandTaskAgent, WandTaskAgentKind, WandTaskAgentMode } from "./task-types.js";
10
10
  import type { WandConfig } from "./types.js";
11
11
  /** 任务面板上次选用的 CLI 工具 / 模型 / 思考深度;从未保存过时回落到 Claude 默认。 */
@@ -31,21 +31,6 @@ export declare function parentTaskIdFrom(storage: WandStorage, value: unknown, w
31
31
  * 避免把用户在 Web 上选的工作模式悄悄复位成标准。
32
32
  */
33
33
  export declare function parseTaskAgent(value: unknown, fallbackMode?: WandTaskAgentMode, fallbackKind?: WandTaskAgentKind): WandTaskAgent | null;
34
- /** 等待排队中的标题生成全部结束;只有测试用,避免用例轮询。 */
35
- export declare function whenWandTaskTitlesSettled(): Promise<void>;
36
- export interface AutoTaskTitleOptions {
37
- config?: WandConfig;
38
- free?: Pick<OpenRouterFreeModelsService, "resolveForCall">;
39
- /** 可注入的标题生成器;测试里换成同步桩,避免真的起 CLI。 */
40
- generateTitle?: typeof generateWandTaskTitle;
41
- /** 未从任务所属工作区解析到目录时的兜底 cwd。 */
42
- cwd?: string;
43
- }
44
- /**
45
- * 扫描标题仍可自动命名的看板任务(titleSource=auto,或仍是占位名的历史卡片):
46
- * 先用任务描述 + 所有绑定会话的内容立即改掉占位名,再交给后台模型总结更贴切的标题。
47
- * 同一个输入指纹只处理一次:内容没变就跳过,也避免自动标题与模型结果来回震荡。
48
- * 用户建任务时写了名字、或事后改过名(titleSource=user 且不是占位名)的任务永不进入这里。
49
- */
50
- export declare function refreshAutoBoardTaskTitles(storage: WandStorage, options?: AutoTaskTitleOptions): void;
34
+ export { refreshAutoBoardTaskTitles, whenWandTaskTitlesSettled } from "./task-title-service.js";
35
+ export type { AutoTaskTitleOptions } from "./task-title-service.js";
51
36
  export declare function registerTaskRoutes(app: Express, deps: TaskRouteDependencies): void;