@co0ontty/wand 4.80.0-beta.gf8e0c44 → 4.80.1-beta.g1fbbefb

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 (51) hide show
  1. package/dist/ai-team-availability.js +0 -1
  2. package/dist/ai-team-live.d.ts +3 -1
  3. package/dist/ai-team-live.js +31 -2
  4. package/dist/ai-team-runner.d.ts +2 -1
  5. package/dist/ai-team-runner.js +17 -7
  6. package/dist/ai-team-types.d.ts +8 -0
  7. package/dist/ai-team-types.js +4 -0
  8. package/dist/build-info.json +3 -3
  9. package/dist/claude-pty-bridge.js +16 -1
  10. package/dist/cli.js +0 -9
  11. package/dist/config.d.ts +2 -10
  12. package/dist/config.js +1 -73
  13. package/dist/git-quick-commit.d.ts +3 -6
  14. package/dist/git-quick-commit.js +38 -75
  15. package/dist/language-prompt.d.ts +1 -1
  16. package/dist/language-prompt.js +1 -1
  17. package/dist/native-session-title.d.ts +17 -0
  18. package/dist/native-session-title.js +257 -0
  19. package/dist/process-manager.d.ts +3 -0
  20. package/dist/process-manager.js +37 -6
  21. package/dist/server-session-routes.js +4 -29
  22. package/dist/server-settings-routes.js +2 -177
  23. package/dist/server-task-routes.js +1 -1
  24. package/dist/server-workspace-routes.js +1 -1
  25. package/dist/server.js +1 -13
  26. package/dist/session-ai-context.d.ts +3 -7
  27. package/dist/session-ai-context.js +0 -25
  28. package/dist/session-topic.d.ts +19 -0
  29. package/dist/session-topic.js +53 -3
  30. package/dist/session-transport.js +0 -1
  31. package/dist/silicon-employee-draft.d.ts +1 -1
  32. package/dist/storage.js +12 -18
  33. package/dist/structured-claude-adapter.d.ts +0 -6
  34. package/dist/structured-claude-adapter.js +1 -5
  35. package/dist/structured-provider-common.d.ts +8 -4
  36. package/dist/structured-provider-common.js +14 -15
  37. package/dist/structured-session-manager.d.ts +4 -40
  38. package/dist/structured-session-manager.js +53 -773
  39. package/dist/types.d.ts +12 -27
  40. package/dist/web-ui/content/ai-teams.js +9 -9
  41. package/dist/web-ui/content/scripts.js +70 -70
  42. package/dist/web-ui/content/styles.css +1 -1
  43. package/dist/web-ui/embedded-assets.js +3 -3
  44. package/dist/ws-broadcast.js +19 -7
  45. package/package.json +1 -2
  46. package/dist/claude-sdk-runner.d.ts +0 -59
  47. package/dist/claude-sdk-runner.js +0 -188
  48. package/dist/claude-skills.d.ts +0 -6
  49. package/dist/claude-skills.js +0 -53
  50. package/dist/system-ai.d.ts +0 -27
  51. package/dist/system-ai.js +0 -424
@@ -12,7 +12,6 @@ export function isSessionGoneStatus(status) {
12
12
  * CLI 缺失类文案(spawn-missing)。来源样例(只引用不改):
13
13
  * - structured-session-manager.ts: codex exec ENOENT →「PATH 中找不到 … 可执行文件」
14
14
  * - git-quick-commit.ts: error.code === "ENOENT" →「未找到 <cmd> CLI。」
15
- * - claude-sdk-runner.ts:「Claude Code native binary not found」/ ENOENT →「未找到 claude CLI。」
16
15
  */
17
16
  const SPAWN_MISSING_PATTERNS = [
18
17
  /\bENOENT\b/i,
@@ -10,7 +10,9 @@ export interface LiveStepText {
10
10
  /**
11
11
  * 渲染「成员此刻干到哪儿了」的紧凑文本,给固定尺寸、内部滚动的 live 卡片用。
12
12
  * 口径:从最后一条 user turn(没有就取最后一条 assistant turn)渲染到末尾;
13
- * text 原样保留,tool_use 压成一行 `▸ 名称[ · 描述]`,tool_result 与 thinking 不渲染;
13
+ * text 原样保留,tool_use 压成一行 `▸ 名称[ · 描述]`(描述优先用 adapter 填的 `description`,
14
+ * 没有就从 input 里的命令/路径/搜索词派生,见 `toolInputDescription`),
15
+ * tool_result 与 thinking 不渲染;
14
16
  * 尾部保留最多 AI_TEAM_LIVE_TEXT_MAX_CHARS 字;渲染结果为空时回落原始 output 尾部
15
17
  * (先按尾部截窗再剥 ANSI、同样截尾,见 `outputTailText`),保证 PTY 会话也有内容可显示。
16
18
  * 纯函数、无 IO。
@@ -1,6 +1,33 @@
1
1
  import { AI_TEAM_LIVE_TEXT_MAX_CHARS } from "./ai-team-types.js";
2
2
  /** tool_use 行的 description 单行截断长度。 */
3
3
  const TOOL_DESCRIPTION_MAX_CHARS = 60;
4
+ /**
5
+ * 没有 `description` 时按序从这里挑一个 input 字段当描述。顺序 = 具体到笼统:
6
+ * 命令/路径/搜索词/网址这些一眼看出「在干什么」的排在前,待办与子代理的说明排在后。
7
+ * 只认字符串字段,不塞 JSON 尾巴:live 卡的目的是让人看懂进展,不是看参数。
8
+ */
9
+ const TOOL_INPUT_DESCRIPTION_KEYS = [
10
+ "command", "cmd",
11
+ "file_path", "filePath", "path", "notebook_path", "move_path",
12
+ "pattern", "query", "url",
13
+ "subject", "title", "explanation", "description", "prompt",
14
+ ];
15
+ /**
16
+ * adapter 给的 `description` 只有 grok / opencode / codex 少数几种工具会填,pi / claude / qoder
17
+ * 基本没有,卡片就只剩一个工具名(「▸ Pi/todo」重复刷屏)。这里从 input 里派生一行摘要,
18
+ * 与客户端会话里的工具卡同一个思路(路径 / 命令 / 搜索词)。取不到就返回空串,仍是裸工具名。
19
+ */
20
+ function toolInputDescription(input) {
21
+ if (!input || typeof input !== "object" || Array.isArray(input))
22
+ return "";
23
+ const record = input;
24
+ for (const key of TOOL_INPUT_DESCRIPTION_KEYS) {
25
+ const value = record[key];
26
+ if (typeof value === "string" && value.trim())
27
+ return value;
28
+ }
29
+ return "";
30
+ }
4
31
  /** CSI + OSC + 两字节转义,覆盖 PTY 输出里常见的控制序列。 */
5
32
  const ANSI_PATTERN = /\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[ @-Z\\-_])/g;
6
33
  function stripAnsi(text) {
@@ -84,7 +111,9 @@ function oneLine(text, max) {
84
111
  /**
85
112
  * 渲染「成员此刻干到哪儿了」的紧凑文本,给固定尺寸、内部滚动的 live 卡片用。
86
113
  * 口径:从最后一条 user turn(没有就取最后一条 assistant turn)渲染到末尾;
87
- * text 原样保留,tool_use 压成一行 `▸ 名称[ · 描述]`,tool_result 与 thinking 不渲染;
114
+ * text 原样保留,tool_use 压成一行 `▸ 名称[ · 描述]`(描述优先用 adapter 填的 `description`,
115
+ * 没有就从 input 里的命令/路径/搜索词派生,见 `toolInputDescription`),
116
+ * tool_result 与 thinking 不渲染;
88
117
  * 尾部保留最多 AI_TEAM_LIVE_TEXT_MAX_CHARS 字;渲染结果为空时回落原始 output 尾部
89
118
  * (先按尾部截窗再剥 ANSI、同样截尾,见 `outputTailText`),保证 PTY 会话也有内容可显示。
90
119
  * 纯函数、无 IO。
@@ -121,7 +150,7 @@ export function renderLiveStepText(messages, output, options = {}) {
121
150
  fragments.push(text);
122
151
  }
123
152
  else if (block.type === "tool_use") {
124
- const description = block.description?.trim();
153
+ const description = block.description?.trim() || toolInputDescription(block.input);
125
154
  fragments.push(description
126
155
  ? `▸ ${block.name} · ${oneLine(description, TOOL_DESCRIPTION_MAX_CHARS)}`
127
156
  : `▸ ${block.name}`);
@@ -91,7 +91,7 @@ export declare const CHAT_INVITE_PER_LINE = 4;
91
91
  * 每行最多 [CHAT_INVITE_PER_LINE] 人,第二行起用 `继续邀请 …`;除负责人外没人时给
92
92
  * `还没有邀请其他成员入群`(仍两条,不伪造成员)。
93
93
  */
94
- export declare function chatIntroLines(team: Pick<AiTeam, "name" | "members">): string[];
94
+ export declare function chatIntroLines(team: Pick<AiTeam, "members">, chatTitle: string): string[];
95
95
  /** 开工发言里的一条依据(来自 `step.dependsOn` 解析出的上游步骤)。 */
96
96
  export interface AiTeamStepBasis {
97
97
  seq: number;
@@ -232,6 +232,7 @@ export declare class AiTeamRunner {
232
232
  /**
233
233
  * 开工发言(S4):成员**自己发的真实发言**(头像 + 名字 + 气泡),不是居中 notice;
234
234
  * 文案由 [stepStartText] 拼(依据只来自 `step.dependsOn`,不引 `decision.message` / 报告正文 / 提示词文本)。
235
+ * 工作步骤与负责人步骤都走这里:负责人轮(制定计划 / 安排下一步 / 回应用户)同样要让人看见他开工了。
235
236
  */
236
237
  private postStepStart;
237
238
  /**
@@ -6,7 +6,7 @@ import path from "node:path";
6
6
  import { dispatchAgentForTask, resolveTaskDispatchTarget, sendToAgentSession, stopAgentSession, } from "./agent-dispatch.js";
7
7
  import { AI_TEAM_REPORT_DIR, aiTeamChatHistoryPath, aiTeamHandoffPath, aiTeamReportPath, buildLeaderFollowupPrompt, buildLeaderFormatRetryPrompt, buildAiTeamObjective, buildLeaderKickoffPrompt, buildMemberPrompt, leaderOf, parseLeaderDecision, renderChatHistoryFile, renderHandoffFile, } from "./ai-team-prompts.js";
8
8
  import { AI_TEAM_STARTUP_WINDOW_MS, classifyCandidateFailure, classifyStartupFailure, isDegradableWorkFailure, isModelUnknownBeforeDispatch, } from "./ai-team-availability.js";
9
- import { AI_TEAM_ACTIVE_RUN_STATUSES, AI_TEAM_DETAIL_CHAT_TURNS, AI_TEAM_TERMINAL_RUN_STATUSES, agentKey, memberAgents, } from "./ai-team-types.js";
9
+ import { AI_TEAM_ACTIVE_RUN_STATUSES, AI_TEAM_DETAIL_CHAT_TURNS, AI_TEAM_TERMINAL_RUN_STATUSES, agentKey, aiTeamChatTitle, memberAgents, } from "./ai-team-types.js";
10
10
  import { renderLiveStepText } from "./ai-team-live.js";
11
11
  import { getErrorMessage } from "./error-utils.js";
12
12
  import { activityState } from "./missions.js";
@@ -190,9 +190,9 @@ function chatStepLabel(seq, title) {
190
190
  * 每行最多 [CHAT_INVITE_PER_LINE] 人,第二行起用 `继续邀请 …`;除负责人外没人时给
191
191
  * `还没有邀请其他成员入群`(仍两条,不伪造成员)。
192
192
  */
193
- export function chatIntroLines(team) {
194
- const teamName = team.name.trim();
195
- const lines = [teamName ? `创建了团队群聊「${teamName}」` : "创建了团队群聊"];
193
+ export function chatIntroLines(team, chatTitle) {
194
+ const title = chatTitle.trim();
195
+ const lines = [title ? `创建了团队群聊「${title}」` : "创建了团队群聊"];
196
196
  const invitees = team.members.filter((member) => !member.isLeader)
197
197
  .map((member) => member.name.trim())
198
198
  .filter(Boolean);
@@ -575,8 +575,14 @@ export class AiTeamRunner {
575
575
  // 群聊回合只给最近这一段(§4.4);没有群聊会话的旧运行给空数组,前端不用判 null。
576
576
  const messages = run.chatSessionId ? this.ops.snapshot(run.chatSessionId)?.messages ?? [] : [];
577
577
  const currentTeam = this.storage.getAiTeam(run.teamId);
578
+ const binding = run.chatSessionId ? this.storage.getSessionWorkspace(run.chatSessionId) : null;
579
+ const task = (binding?.workspaceTaskId
580
+ ? this.storage.getWandTaskByWorkspaceTaskId(binding.workspaceTaskId) : null)
581
+ ?? this.storage.getWandTask(run.taskId);
578
582
  return {
579
583
  run,
584
+ chatTitle: aiTeamChatTitle(task?.title),
585
+ chatTitleUpdatedAt: task?.updatedAt ?? "",
580
586
  steps,
581
587
  memberStates,
582
588
  // 别把展示字段写回 run.team:运行快照里的执行候选/职责由 runner 独立管理。
@@ -1073,6 +1079,7 @@ export class AiTeamRunner {
1073
1079
  /**
1074
1080
  * 开工发言(S4):成员**自己发的真实发言**(头像 + 名字 + 气泡),不是居中 notice;
1075
1081
  * 文案由 [stepStartText] 拼(依据只来自 `step.dependsOn`,不引 `decision.message` / 报告正文 / 提示词文本)。
1082
+ * 工作步骤与负责人步骤都走这里:负责人轮(制定计划 / 安排下一步 / 回应用户)同样要让人看见他开工了。
1076
1083
  */
1077
1084
  postStepStart(run, step, member, agent, reusable) {
1078
1085
  this.postTurn(run, {
@@ -1241,7 +1248,10 @@ export class AiTeamRunner {
1241
1248
  // 开工发言(S4):就发在这个位置(原来那条居中 notice 的地方),所以候选起不来的那次尝试
1242
1249
  // 也会留一句「我正在开始工作」+ 随后的降级/失败出口;作者署名与报告同一套口径,
1243
1250
  // 复用会话时带真实 sessionId,新建在 open 前未知;若 open 失败,失败报告也可能无 ID。
1244
- if (member && step.kind === "work")
1251
+ // 负责人步骤同样要发:负责人一轮可能要读代码好几分钟,中途只在群里留一条 live 卡,
1252
+ // 群聊看上去就是「工位上写着工作中、消息里什么都没有」,等他交出计划才突然出现一屏文字。
1253
+ // 设计 v2 的口径本来就是「步骤开工时该成员自己发一条」,工作步骤那条门是从旧 notice 继承来的。
1254
+ if (member)
1245
1255
  this.postStepStart(run, running, member, agent, reusable);
1246
1256
  try {
1247
1257
  if (!member)
@@ -1545,7 +1555,7 @@ export class AiTeamRunner {
1545
1555
  const firstChat = !run.chatSessionId;
1546
1556
  try {
1547
1557
  if (firstChat) {
1548
- run.chatSessionId = this.chat.open({ task, run, title: `${run.team.name} · ${task.title}` });
1558
+ run.chatSessionId = this.chat.open({ task, run, title: aiTeamChatTitle(task.title) });
1549
1559
  }
1550
1560
  }
1551
1561
  catch (error) {
@@ -1564,7 +1574,7 @@ export class AiTeamRunner {
1564
1574
  // 首次建群:负责人先入群,再按名单邀请其余成员(2–3 条系统行,作者都是负责人)。
1565
1575
  const leader = leaderOf(run.team);
1566
1576
  const leaderAgent = leader ? memberAgents(leader)[0] ?? leader.agent : undefined;
1567
- for (const line of chatIntroLines(run.team)) {
1577
+ for (const line of chatIntroLines(run.team, aiTeamChatTitle(task.title))) {
1568
1578
  this.postNotice(run, line, run.chatSessionId, leader, leaderAgent);
1569
1579
  }
1570
1580
  }
@@ -129,6 +129,8 @@ export interface AiTeamRun {
129
129
  createdAt: string;
130
130
  updatedAt: string;
131
131
  }
132
+ /** 群聊名从任务标题派生;不修改团队定义,也不回写历史消息。 */
133
+ export declare function aiTeamChatTitle(taskTitle?: string | null): string;
132
134
  /**
133
135
  * 会话列表用的群聊标记:chat_session_id → 最近一次运行(`AiTeamRunChatMarker` 索引的 value)。
134
136
  * 只带列表徽标和点开 IM 视图所需的入口信息,不暴露运行细节。
@@ -137,6 +139,8 @@ export interface AiTeamRunChatMarker {
137
139
  runId: string;
138
140
  teamId: string;
139
141
  teamName: string;
142
+ /** 当前任务名派生的群名;旧服务端可能没有。 */
143
+ chatTitle?: string;
140
144
  memberCount: number;
141
145
  }
142
146
  /**
@@ -226,6 +230,10 @@ export interface AiTeamLiveUpdate {
226
230
  }
227
231
  export interface AiTeamRunDetail {
228
232
  run: AiTeamRun;
233
+ /** 仅供展示的群名,读取当前任务标题,不以团队名代替。 */
234
+ chatTitle?: string;
235
+ /** 任务标题的版本;任务改名不改变 run.updatedAt。 */
236
+ chatTitleUpdatedAt?: string;
229
237
  steps: AiTeamStep[];
230
238
  /** 运行中步骤所在会话的实时状态(等待授权 / 等待回答等)。 */
231
239
  memberStates: Record<string, AgentActivityState>;
@@ -41,6 +41,10 @@ export function isSystemSiliconEmployee(employee) {
41
41
  export const AI_TEAM_ACTIVE_RUN_STATUSES = ["running", "awaiting_approval", "waiting_user"];
42
42
  /** 终态:不会再有 live 变化,runner 据此清掉该 run 的推送指纹,避免长跑服务按 run 数攒字符串。 */
43
43
  export const AI_TEAM_TERMINAL_RUN_STATUSES = ["done", "failed", "stopped"];
44
+ /** 群聊名从任务标题派生;不修改团队定义,也不回写历史消息。 */
45
+ export function aiTeamChatTitle(taskTitle) {
46
+ return `${taskTitle?.trim() ?? ""}任务处理群`;
47
+ }
44
48
  /** detail() 里群聊回合的截尾条数(§4.4):面板只给最近这一段,完整会话走「打开群聊」。 */
45
49
  export const AI_TEAM_DETAIL_CHAT_TURNS = 200;
46
50
  /** live 卡片文本的尾部保留上限(§4.9),超出部分回报 omittedChars 给前端显示「已省略前面 N 字」。 */
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "f8e0c448bbfdf5623cc9dc79e12b46984d1d21c6",
3
- "builtAt": "2026-09-30T06:47:06.089Z",
4
- "version": "4.80.0-beta.gf8e0c44",
2
+ "commit": "1fbbefb6858936b5ae6af8fc19a86c66ad2a997c",
3
+ "builtAt": "2026-09-30T13:16:50.737Z",
4
+ "version": "4.80.1-beta.g1fbbefb",
5
5
  "channel": "beta"
6
6
  }
@@ -151,6 +151,10 @@ export class ClaudePtyBridge extends EventEmitter {
151
151
  if (!this.isRealChatInput(input)) {
152
152
  return;
153
153
  }
154
+ if (this.chatEmitTimer) {
155
+ clearTimeout(this.chatEmitTimer);
156
+ this.chatEmitTimer = null;
157
+ }
154
158
  const cleanInput = input.replace(/[\r\n]+$/, "").trim();
155
159
  const now = new Date().toISOString();
156
160
  // Add user message
@@ -754,8 +758,14 @@ export class ClaudePtyBridge extends EventEmitter {
754
758
  const now = Date.now();
755
759
  if (now - this.lastChatEmitAt < ClaudePtyBridge.CHAT_THROTTLE_MS) {
756
760
  if (!this.chatEmitTimer) {
757
- this.chatEmitTimer = setTimeout(() => {
761
+ const streamedIndex = idx;
762
+ const timer = setTimeout(() => {
763
+ if (this.chatEmitTimer !== timer)
764
+ return;
758
765
  this.chatEmitTimer = null;
766
+ if (this._exited || this.chatState.phase !== "responding"
767
+ || this.chatState.assistantIndex !== streamedIndex)
768
+ return;
759
769
  this.lastChatEmitAt = Date.now();
760
770
  this.emitEvent({
761
771
  type: "output.chat",
@@ -768,6 +778,7 @@ export class ClaudePtyBridge extends EventEmitter {
768
778
  },
769
779
  });
770
780
  }, ClaudePtyBridge.CHAT_THROTTLE_MS);
781
+ this.chatEmitTimer = timer;
771
782
  }
772
783
  return;
773
784
  }
@@ -784,6 +795,10 @@ export class ClaudePtyBridge extends EventEmitter {
784
795
  });
785
796
  }
786
797
  finalizeResponse() {
798
+ if (this.chatEmitTimer) {
799
+ clearTimeout(this.chatEmitTimer);
800
+ this.chatEmitTimer = null;
801
+ }
787
802
  const idx = this.chatState.assistantIndex;
788
803
  if (idx !== null) {
789
804
  const text = this.cleanForChat(this.chatState.buffer);
package/dist/cli.js CHANGED
@@ -115,15 +115,6 @@ async function main() {
115
115
  ...config,
116
116
  password: config.password === "change-me" ? "change-me" : "<set>",
117
117
  appSecret: config.appSecret ? "<set>" : "",
118
- systemAi: config.systemAi ? {
119
- ...config.systemAi,
120
- apiKey: config.systemAi.apiKey ? "<set>" : "",
121
- fallbacks: config.systemAi.fallbacks?.map((profile) => ({
122
- ...profile,
123
- apiKey: profile.apiKey ? "<set>" : "",
124
- fallbacks: undefined,
125
- })),
126
- } : undefined,
127
118
  };
128
119
  process.stdout.write(`${JSON.stringify(display, null, 2)}\n`);
129
120
  }
package/dist/config.d.ts CHANGED
@@ -7,7 +7,7 @@ import type { WandStorage } from "./storage.js";
7
7
  * 升级路径:老 JSON 里仍存有这些字段时,首次启动会被搬到 DB(见 migrateLegacyPreferencesToDb),
8
8
  * 然后下一次 saveConfig 写回 JSON 时它们会被剥离(见 stripPreferenceFields)。
9
9
  */
10
- export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "defaultGeminiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
10
+ export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "defaultGeminiModel", "commitCli", "commitModel", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "language", "cardDefaults", "inheritEnv"];
11
11
  export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
12
12
  export declare function isPreferenceKey(key: string): key is PreferenceKey;
13
13
  /**
@@ -37,15 +37,7 @@ export declare function loadConfigWithStorage(configPath: string, storage: WandS
37
37
  */
38
38
  export declare function applyStoragePreferences(config: WandConfig, storage: WandStorage): WandConfig;
39
39
  /** Write a single preference value to DB and (in-place) update the live config object. */
40
- export declare function writePreferenceToStorage(config: WandConfig, storage: WandStorage, key: PreferenceKey, value: unknown, options?: {
41
- deferCommitAiValidation?: boolean;
42
- }): void;
43
- /**
44
- * Kept as a compatibility seam for callers that validate preference batches.
45
- * Commit API profiles are discovered from the configured tools at request time,
46
- * so selecting API is valid even when no manual systemAi profile is stored.
47
- */
48
- export declare function validateCommitAiConfig(_config: Pick<WandConfig, "commitAiSource" | "systemAi">): void;
40
+ export declare function writePreferenceToStorage(config: WandConfig, storage: WandStorage, key: PreferenceKey, value: unknown): void;
49
41
  export declare function isExecutionMode(value: unknown): value is ExecutionMode;
50
42
  export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultGeminiModel">): {
51
43
  claude: string;
package/dist/config.js CHANGED
@@ -5,7 +5,6 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import process from "node:process";
7
7
  import { isRunningAsRoot } from "./env-utils.js";
8
- import { normalizeSystemAiConfig, systemAiProfiles } from "./system-ai.js";
9
8
  import { isSessionProvider } from "./session-provider.js";
10
9
  import { isThinkingEffort } from "./structured-provider-common.js";
11
10
  const DEFAULT_CONFIG_DIR = ".wand";
@@ -32,12 +31,9 @@ export const PREFERENCE_KEYS = [
32
31
  "defaultGeminiModel",
33
32
  "commitCli",
34
33
  "commitModel",
35
- "commitAiSource",
36
- "systemAi",
37
34
  "systemAiCli",
38
35
  "systemAiModel",
39
36
  "defaultThinkingEffort",
40
- "structuredRunner",
41
37
  "language",
42
38
  "cardDefaults",
43
39
  "inheritEnv",
@@ -106,20 +102,8 @@ export const defaultConfig = () => ({
106
102
  defaultGeminiModel: "",
107
103
  commitCli: "claude",
108
104
  commitModel: "",
109
- commitAiSource: "cli",
110
- systemAi: {
111
- id: crypto.randomUUID(),
112
- enabled: false,
113
- protocol: "openai",
114
- baseUrl: "",
115
- apiKey: "",
116
- model: "",
117
- authHeader: "bearer",
118
- source: "custom",
119
- },
120
105
  systemAiModel: "",
121
106
  defaultThinkingEffort: "off",
122
- structuredRunner: "cli",
123
107
  inheritEnv: true,
124
108
  commandPresets: [
125
109
  {
@@ -391,15 +375,6 @@ export function applyStoragePreferences(config, storage) {
391
375
  if (typeof v === "string")
392
376
  config.commitModel = v.trim();
393
377
  }
394
- if (storage.hasPreference(preferenceStorageKey("commitAiSource"))) {
395
- const v = storage.getPreference(preferenceStorageKey("commitAiSource"), defaults.commitAiSource ?? "cli");
396
- if (v === "cli" || v === "api")
397
- config.commitAiSource = v;
398
- }
399
- if (storage.hasPreference(preferenceStorageKey("systemAi"))) {
400
- const v = storage.getPreference(preferenceStorageKey("systemAi"), defaults.systemAi);
401
- config.systemAi = normalizeSystemAiConfig(v, defaults.systemAi);
402
- }
403
378
  if (storage.hasPreference(preferenceStorageKey("systemAiCli"))) {
404
379
  const v = storage.getPreference(preferenceStorageKey("systemAiCli"), defaults.systemAiCli);
405
380
  if (isSessionProvider(v))
@@ -415,11 +390,6 @@ export function applyStoragePreferences(config, storage) {
415
390
  if (isThinkingEffort(v))
416
391
  config.defaultThinkingEffort = v;
417
392
  }
418
- if (storage.hasPreference(preferenceStorageKey("structuredRunner"))) {
419
- const v = storage.getPreference(preferenceStorageKey("structuredRunner"), defaults.structuredRunner ?? "cli");
420
- if (v === "cli" || v === "sdk")
421
- config.structuredRunner = v;
422
- }
423
393
  if (storage.hasPreference(preferenceStorageKey("language"))) {
424
394
  const v = storage.getPreference(preferenceStorageKey("language"), defaults.language ?? "");
425
395
  if (typeof v === "string")
@@ -433,11 +403,10 @@ export function applyStoragePreferences(config, storage) {
433
403
  const v = storage.getPreference(preferenceStorageKey("inheritEnv"), defaults.inheritEnv ?? true);
434
404
  config.inheritEnv = v === false ? false : true;
435
405
  }
436
- validateCommitAiConfig(config);
437
406
  return config;
438
407
  }
439
408
  /** Write a single preference value to DB and (in-place) update the live config object. */
440
- export function writePreferenceToStorage(config, storage, key, value, options = {}) {
409
+ export function writePreferenceToStorage(config, storage, key, value) {
441
410
  const dbKey = preferenceStorageKey(key);
442
411
  switch (key) {
443
412
  case "defaultProvider": {
@@ -529,31 +498,6 @@ export function writePreferenceToStorage(config, storage, key, value, options =
529
498
  config.commitModel = v;
530
499
  break;
531
500
  }
532
- case "commitAiSource": {
533
- if (value !== "cli" && value !== "api")
534
- throw new Error(`无效 commit AI 来源: ${String(value)}`);
535
- if (!options.deferCommitAiValidation) {
536
- validateCommitAiConfig({ ...config, commitAiSource: value });
537
- }
538
- storage.setPreference(dbKey, value);
539
- config.commitAiSource = value;
540
- break;
541
- }
542
- case "systemAi": {
543
- if (!value || typeof value !== "object" || Array.isArray(value)) {
544
- throw new Error("systemAi 必须是对象。");
545
- }
546
- const normalized = normalizeSystemAiConfig(value, config.systemAi ?? defaultConfig().systemAi);
547
- if (normalized.enabled && !systemAiProfiles(normalized, true).length) {
548
- throw new Error("启用系统 AI API 时,至少需要一条地址、API Key 和模型完整的路由。");
549
- }
550
- if (!options.deferCommitAiValidation) {
551
- validateCommitAiConfig({ ...config, systemAi: normalized });
552
- }
553
- storage.setPreference(dbKey, normalized);
554
- config.systemAi = normalized;
555
- break;
556
- }
557
501
  case "systemAiCli": {
558
502
  if (!isSessionProvider(value))
559
503
  throw new Error(`无效系统 AI CLI: ${String(value)}`);
@@ -575,14 +519,6 @@ export function writePreferenceToStorage(config, storage, key, value, options =
575
519
  config.defaultThinkingEffort = v;
576
520
  break;
577
521
  }
578
- case "structuredRunner": {
579
- if (value !== "cli" && value !== "sdk")
580
- throw new Error(`无效 structured runner: ${String(value)}`);
581
- const v = value;
582
- storage.setPreference(dbKey, v);
583
- config.structuredRunner = v;
584
- break;
585
- }
586
522
  case "language": {
587
523
  const v = typeof value === "string" ? value.trim() : "";
588
524
  storage.setPreference(dbKey, v);
@@ -605,12 +541,6 @@ export function writePreferenceToStorage(config, storage, key, value, options =
605
541
  }
606
542
  }
607
543
  }
608
- /**
609
- * Kept as a compatibility seam for callers that validate preference batches.
610
- * Commit API profiles are discovered from the configured tools at request time,
611
- * so selecting API is valid even when no manual systemAi profile is stored.
612
- */
613
- export function validateCommitAiConfig(_config) { }
614
544
  function defaultCardExpandDefaults() {
615
545
  return {
616
546
  editCards: false,
@@ -815,11 +745,9 @@ function mergeWithDefaults(input) {
815
745
  defaultGeminiModel: typeof input.defaultGeminiModel === "string" ? input.defaultGeminiModel.trim() : defaults.defaultGeminiModel,
816
746
  commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
817
747
  commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
818
- commitAiSource: input.commitAiSource === "api" ? "api" : "cli",
819
748
  systemAiCli: isSessionProvider(input.systemAiCli) ? input.systemAiCli : undefined,
820
749
  systemAiModel: typeof input.systemAiModel === "string" ? input.systemAiModel.trim() : defaults.systemAiModel,
821
750
  defaultThinkingEffort: isThinkingEffort(input.defaultThinkingEffort) ? input.defaultThinkingEffort : "off",
822
- structuredRunner: (input.structuredRunner === "sdk" || input.structuredRunner === "cli") ? input.structuredRunner : defaults.structuredRunner,
823
751
  inheritEnv: typeof input.inheritEnv === "boolean" ? input.inheritEnv : (defaults.inheritEnv ?? true),
824
752
  };
825
753
  }
@@ -1,4 +1,4 @@
1
- import { type AiTextRequest } from "./system-ai.js";
1
+ import type { AiTextRequest } from "./types.js";
2
2
  import { GitStatusResult, PushResult, QuickCommitResult, SessionProvider, SessionSnapshot, TagHeadResult } from "./types.js";
3
3
  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";
4
4
  /** Non-blocking status implementation used by HTTP routes. */
@@ -23,7 +23,6 @@ export interface QuickCommitAiOptions {
23
23
  model?: string | null;
24
24
  thinkingEffort?: SessionSnapshot["thinkingEffort"];
25
25
  inheritEnv?: boolean;
26
- systemAi?: import("./types.js").SystemAiConfig;
27
26
  /** 内置「系统运维」员工的角色设定:作为系统提示前缀注入。 */
28
27
  opsPersona?: string;
29
28
  /** CLI 降级链(按顺序);未设置时只用 provider/model 这一次调用。 */
@@ -46,9 +45,8 @@ export interface CommitInputOptions {
46
45
  /** 角色设定走系统提示前缀;任务自己的输出格式与约束必须排在后面。 */
47
46
  export declare function withOpsPersona(request: AiTextRequest, persona?: string): AiTextRequest;
48
47
  /**
49
- * Run a lightweight AI request through the selected Commit source. API mode
50
- * exhausts its profile chain and then falls back once to the system employee's
51
- * CLI chain. CLI mode walks that chain in order.
48
+ * Run a lightweight AI request through the system employee's CLI chain.
49
+ * 所有 Wand 自有文本调用都走本机已安装的 CLI,没有任何直连 API 分支。
52
50
  */
53
51
  export declare function callConfiguredAiText(request: AiTextRequest, cwd: string, language: string, opts: QuickCommitAiOptions): Promise<string>;
54
52
  export interface GenerateCommitMessageResult {
@@ -64,7 +62,6 @@ interface TagHeadOptions {
64
62
  model?: string | null;
65
63
  thinkingEffort?: SessionSnapshot["thinkingEffort"];
66
64
  inheritEnv?: boolean;
67
- systemAi?: import("./types.js").SystemAiConfig;
68
65
  /** Explicit tag name. If empty and `autoTag` is true, ask the session provider to generate one. */
69
66
  tag?: string;
70
67
  autoTag?: boolean;