@co0ontty/wand 4.80.0 → 4.80.1

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
@@ -2,27 +2,16 @@ import { existsSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { buildInfoPayload } from "./build-info.js";
4
4
  import { buildChildEnv } from "./env-utils.js";
5
- import { getErrorMessage } from "./error-utils.js";
6
5
  import { asyncRoute } from "./express-async.js";
7
6
  import { sendRouteError } from "./server-request.js";
8
- import { getProviderDefaultModels, PREFERENCE_KEYS, saveConfig, validateCommitAiConfig, writePreferenceToStorage, } from "./config.js";
7
+ import { getProviderDefaultModels, PREFERENCE_KEYS, saveConfig, writePreferenceToStorage, } from "./config.js";
9
8
  import { withConfiguredDefaultModelLabels } from "./models.js";
10
9
  import { DEPLOYMENT_CONFIG_KEYS } from "./runtime-config.js";
11
- import { callSystemAiText, discoverCliSystemAiConfigs, mergeSystemAiConfigs, normalizeSystemAiConfig, } from "./system-ai.js";
12
- function systemAiRouteIdentity(profile) {
13
- return [
14
- profile.protocol,
15
- profile.baseUrl,
16
- profile.model,
17
- profile.authHeader ?? "bearer",
18
- ].join("\0");
19
- }
20
10
  function publicConfig(config) {
21
11
  const { password: _password, appSecret: _appSecret, ...safe } = config;
22
12
  const defaultModels = getProviderDefaultModels(config);
23
13
  return {
24
14
  ...safe,
25
- systemAi: publicSystemAi(safe.systemAi),
26
15
  defaultModel: defaultModels.claude,
27
16
  defaultCodexModel: defaultModels.codex,
28
17
  defaultOpenCodeModel: defaultModels.opencode,
@@ -33,21 +22,6 @@ function publicConfig(config) {
33
22
  defaultModels,
34
23
  };
35
24
  }
36
- function publicSystemAi(systemAi) {
37
- if (!systemAi)
38
- return undefined;
39
- return {
40
- ...systemAi,
41
- apiKey: "",
42
- hasApiKey: Boolean(systemAi.apiKey),
43
- fallbacks: systemAi.fallbacks?.map((profile) => ({
44
- ...profile,
45
- apiKey: "",
46
- hasApiKey: Boolean(profile.apiKey),
47
- fallbacks: undefined,
48
- })),
49
- };
50
- }
51
25
  function publicDistributionInfo(distribution) {
52
26
  const androidApk = { ...distribution.androidApk };
53
27
  const macosDmg = { ...distribution.macosDmg };
@@ -138,99 +112,6 @@ export function registerSettingsRoutes(app, deps) {
138
112
  res.json({ inheritEnv, total: entries.length, reveal, entries });
139
113
  }
140
114
  });
141
- app.post("/api/settings/system-ai/import", requireAdmin, (_req, res) => {
142
- const imported = discoverCliSystemAiConfigs();
143
- if (!imported.length) {
144
- res.status(404).json({ error: "没有在已配置的 CLI 文件中找到可直连的 API 地址、密钥和模型。" });
145
- return;
146
- }
147
- const candidate = runtimeConfig.createCandidate();
148
- const remainingImported = [...imported];
149
- const existingRoutes = candidate.systemAi
150
- ? [candidate.systemAi, ...(candidate.systemAi.fallbacks ?? [])]
151
- .map((profile) => ({ ...profile, fallbacks: undefined }))
152
- : [];
153
- const refreshedExisting = existingRoutes.map((profile) => {
154
- const importedIndex = remainingImported.findIndex((item) => systemAiRouteIdentity(item) === systemAiRouteIdentity(profile));
155
- if (importedIndex < 0)
156
- return profile;
157
- const [matchingImport] = remainingImported.splice(importedIndex, 1);
158
- return {
159
- ...profile,
160
- apiKey: matchingImport.apiKey,
161
- source: matchingImport.source,
162
- };
163
- });
164
- const merged = mergeSystemAiConfigs(refreshedExisting, remainingImported);
165
- if (!merged) {
166
- res.status(404).json({ error: "没有找到可用的系统 AI API 配置。" });
167
- return;
168
- }
169
- writePreferenceToStorage(candidate, storage, "systemAi", {
170
- ...merged,
171
- enabled: candidate.systemAi?.enabled === true,
172
- });
173
- runtimeConfig.commit(candidate, new Set(["systemAi"]));
174
- res.json({ ok: true, count: imported.length, systemAi: (publicConfig(candidate).systemAi) });
175
- });
176
- app.post("/api/settings/system-ai/test", requireAdmin, asyncRoute(async (req, res) => {
177
- const raw = req.body?.route;
178
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
179
- res.status(400).json({ error: "请提供要测试的系统 AI 线路。" });
180
- return;
181
- }
182
- const submitted = raw;
183
- const desired = runtimeConfig.desiredSnapshot().systemAi;
184
- const storedRoutes = desired
185
- ? [desired, ...(desired.fallbacks ?? [])].map((profile) => ({ ...profile, fallbacks: undefined }))
186
- : [];
187
- const submittedId = typeof submitted.id === "string" ? submitted.id.trim() : "";
188
- const stored = submittedId
189
- ? storedRoutes.find((profile) => profile.id === submittedId)
190
- : undefined;
191
- const apiKey = submitted.clearApiKey === true
192
- ? ""
193
- : typeof submitted.apiKey === "string" && submitted.apiKey.trim()
194
- ? submitted.apiKey.trim()
195
- : stored?.apiKey ?? "";
196
- let route;
197
- try {
198
- route = normalizeSystemAiConfig({
199
- ...stored,
200
- ...submitted,
201
- enabled: true,
202
- apiKey,
203
- fallbacks: undefined,
204
- }, stored);
205
- if (!route.baseUrl || !route.apiKey || !route.model) {
206
- throw new Error("测试线路需要完整的 API 地址、API Key 和模型。");
207
- }
208
- }
209
- catch (error) {
210
- sendRouteError(res, error, "测试线路配置无效。");
211
- return;
212
- }
213
- const startedAt = Date.now();
214
- try {
215
- const text = await callSystemAiText({ system: "你是 Wand 的系统 API 线路探针。", prompt: "这是 Wand 系统 API 线路验收。请只回复 WAND_API_OK。" }, route, 30_000);
216
- if (!text.trim())
217
- throw new Error("系统 AI API 返回了空结果。");
218
- res.json({
219
- ok: true,
220
- source: route.source ?? "custom",
221
- requestedModel: route.model,
222
- reasoningEffort: route.protocol === "openai" ? "low" : "disabled",
223
- latencyMs: Date.now() - startedAt,
224
- });
225
- }
226
- catch (error) {
227
- res.status(502).json({
228
- error: getErrorMessage(error, "系统 AI API 测试失败。"),
229
- requestedModel: route.model,
230
- latencyMs: Date.now() - startedAt,
231
- });
232
- }
233
- }));
234
115
  app.get("/api/app-connect-code", requireAdmin, (req, res) => {
235
116
  res.json(deps.resolveAppConnectCode(req));
236
117
  });
@@ -244,9 +125,7 @@ export function registerSettingsRoutes(app, deps) {
244
125
  setPreference(key, value) { stagedPreferences.push({ key, value }); },
245
126
  };
246
127
  const stagePreference = (field, value) => {
247
- writePreferenceToStorage(candidateConfig, stagingStorage, field, value, {
248
- deferCommitAiValidation: true,
249
- });
128
+ writePreferenceToStorage(candidateConfig, stagingStorage, field, value);
250
129
  stagedPreferenceFields.add(field);
251
130
  };
252
131
  let touchedDeployField = false;
@@ -296,66 +175,12 @@ export function registerSettingsRoutes(app, deps) {
296
175
  if (Object.hasOwn(body.defaultModels, "gemini"))
297
176
  stagePreference("defaultGeminiModel", body.defaultModels.gemini);
298
177
  }
299
- if (body.systemAi !== undefined) {
300
- if (!body.systemAi || typeof body.systemAi !== "object" || Array.isArray(body.systemAi)) {
301
- throw new Error("systemAi 必须是对象。");
302
- }
303
- const previous = candidateConfig.systemAi;
304
- const systemAiEnabled = body.systemAi.enabled === true;
305
- const previousRoutes = previous ? [previous, ...(previous.fallbacks ?? [])] : [];
306
- const previousById = new Map(previousRoutes.flatMap((profile) => profile.id ? [[profile.id, profile]] : []));
307
- const rawFallbacks = Array.isArray(body.systemAi.fallbacks) ? body.systemAi.fallbacks : [];
308
- const rawRoutes = [body.systemAi, ...rawFallbacks].map((item) => item && typeof item === "object" && !Array.isArray(item)
309
- ? item
310
- : {});
311
- const submittedIds = new Set();
312
- const normalizedRoutes = rawRoutes.map((raw, index) => {
313
- const submittedId = typeof raw.id === "string" ? raw.id.trim() : "";
314
- if (submittedId) {
315
- if (submittedIds.has(submittedId))
316
- throw new Error("系统 AI 路由 ID 不能重复。");
317
- submittedIds.add(submittedId);
318
- }
319
- // Older web clients did not submit route IDs. For those clients only,
320
- // match a unique route by its non-secret identity; never fall back to
321
- // the array index because routes may have been reordered.
322
- const identityMatches = submittedId ? [] : previousRoutes.filter((profile) => profile.protocol === (raw.protocol === "anthropic" ? "anthropic" : "openai")
323
- && profile.baseUrl === (typeof raw.baseUrl === "string" ? raw.baseUrl.trim().replace(/\/+$/, "") : "")
324
- && profile.model === (typeof raw.model === "string" ? raw.model.trim() : "")
325
- && (profile.authHeader ?? "bearer") === (raw.authHeader === "x-api-key" ? "x-api-key" : "bearer"));
326
- const prior = (submittedId ? previousById.get(submittedId) : undefined)
327
- ?? (identityMatches.length === 1 ? identityMatches[0] : undefined);
328
- const apiKey = raw.clearApiKey === true
329
- ? ""
330
- : typeof raw.apiKey === "string" && raw.apiKey.trim()
331
- ? raw.apiKey.trim()
332
- : prior?.apiKey ?? "";
333
- return normalizeSystemAiConfig({
334
- ...prior,
335
- ...raw,
336
- enabled: index === 0 ? systemAiEnabled : true,
337
- apiKey,
338
- fallbacks: undefined,
339
- }, prior);
340
- });
341
- const [primary, ...fallbacks] = normalizedRoutes;
342
- if (!primary)
343
- throw new Error("系统 AI 至少需要一个路由占位。");
344
- stagePreference("systemAi", {
345
- ...primary,
346
- enabled: systemAiEnabled,
347
- ...(fallbacks.length ? { fallbacks } : { fallbacks: [] }),
348
- });
349
- }
350
178
  for (const field of PREFERENCE_KEYS) {
351
- if (field === "systemAi")
352
- continue;
353
179
  const value = body[field];
354
180
  if (!(field in body) || value === undefined)
355
181
  continue;
356
182
  stagePreference(field, value);
357
183
  }
358
- validateCommitAiConfig(candidateConfig);
359
184
  }
360
185
  catch (error) {
361
186
  sendRouteError(res, error, "配置校验失败。");
@@ -308,7 +308,7 @@ function taskTitleAiOptions(config, storage) {
308
308
  selectedModel: null,
309
309
  thinkingEffort: config.defaultThinkingEffort,
310
310
  };
311
- // resolveSystemAiContext 会在直连 API 可用且已启用时优先走 API,否则走系统运维员工的 CLI 候选链。
311
+ // resolveSystemAiContext 按系统运维员工的 CLI 候选链解析(首个已安装的优先)。
312
312
  return resolveSystemAiContext(defaultSession, config, storage?.getSystemSiliconEmployee() ?? null);
313
313
  }
314
314
  export function registerTaskRoutes(app, deps) {
@@ -33,7 +33,7 @@ function workspaceSessionSummary(session, names = {}, registry, team = {}) {
33
33
  sessionKind: live.sessionKind,
34
34
  runner: live.runner,
35
35
  command: live.command,
36
- title: workspaceSessionTitle(live, names),
36
+ title: team.chat?.chatTitle ?? workspaceSessionTitle(live, names),
37
37
  status: live.status,
38
38
  ptyBusy: live.ptyBusy === true,
39
39
  providerCliActive: live.providerCliActive,
package/dist/server.js CHANGED
@@ -11,7 +11,6 @@ import process from "node:process";
11
11
  import { WebSocketServer } from "ws";
12
12
  import { AuthService, BROWSER_ADMIN_PRINCIPAL, CONNECTED_APP_PRINCIPAL, principalHasScope, readSessionCookie, SESSION_COOKIE_HTTP, SESSION_COOKIE_HTTPS, SESSION_COOKIE_LEGACY, } from "./auth.js";
13
13
  import { ensureCertificates } from "./cert.js";
14
- import { listClaudeSkills } from "./claude-skills.js";
15
14
  import { getDefaultModelForProvider, getProviderDefaultModels, isExecutionMode, resolveConfigDir, } from "./config.js";
16
15
  import { ModelCatalogService } from "./models.js";
17
16
  import { ProcessManager } from "./process-manager.js";
@@ -346,7 +345,7 @@ export async function startServer(config, configPath, options = {}) {
346
345
  const legacyStructuredHost = ptyHosts.legacyHost
347
346
  ?? (terminalHost instanceof TerminalDaemonClient ? terminalHost : null);
348
347
  const structuredHosts = await createUpgradeAwareStructuredHost(configPath, legacyStructuredHost, config.structured?.processHost);
349
- const structuredSessions = new StructuredSessionManager(storage, config, structuredLogger, undefined, {}, structuredHosts.host);
348
+ const structuredSessions = new StructuredSessionManager(storage, config, structuredLogger, {}, structuredHosts.host);
350
349
  const sessionRegistry = new SessionRegistry(processes, structuredSessions, storage);
351
350
  const missions = new Missions(storage, structuredSessions, sessionRegistry);
352
351
  // wsManager 在后面才建:团队运行的变更通知经由这个转发口,接好之前静默丢弃。
@@ -598,7 +597,6 @@ export async function startServer(config, configPath, options = {}) {
598
597
  "/api/session-directories",
599
598
  "/api/structured-sessions",
600
599
  "/api/commands",
601
- "/api/claude-skills",
602
600
  "/api/claude-history",
603
601
  "/api/codex-history",
604
602
  "/api/opencode-history",
@@ -656,7 +654,6 @@ export async function startServer(config, configPath, options = {}) {
656
654
  defaultModels,
657
655
  defaultThinkingEffort: config.defaultThinkingEffort ?? "off",
658
656
  commandPresets: config.commandPresets,
659
- structuredRunner: config.structuredRunner ?? "cli",
660
657
  structuredRunners: [
661
658
  { label: "Claude Structured", runner: "claude-cli-print" },
662
659
  { label: "Codex Structured", runner: "codex-cli-exec" },
@@ -679,15 +676,6 @@ export async function startServer(config, configPath, options = {}) {
679
676
  });
680
677
  }));
681
678
  // ── Claude skills & browser extension vault ──
682
- app.get("/api/claude-skills", (req, res) => {
683
- try {
684
- const cwd = resolveSessionCwd(typeof req.query.cwd === "string" ? req.query.cwd : undefined, config.defaultCwd);
685
- res.json({ skills: listClaudeSkills(cwd) });
686
- }
687
- catch (error) {
688
- sendRouteError(res, error, "无法读取 skills。");
689
- }
690
- });
691
679
  registerVaultRoutes(app, { storage, config, useHttps });
692
680
  // ── Settings endpoints ──
693
681
  const getDistributionSettings = () => distributionManager.getSettings();
@@ -1,16 +1,14 @@
1
- import { discoverCliSystemAiConfigs } from "./system-ai.js";
2
1
  import type { SiliconEmployee } from "./ai-team-types.js";
3
- import type { SessionProvider, SessionSnapshot, SystemAiConfig, WandConfig } from "./types.js";
2
+ import type { AiCliCandidate, SessionProvider, SessionSnapshot, WandConfig } from "./types.js";
4
3
  export interface SessionAiContext {
5
4
  provider: SessionProvider;
6
5
  model?: string;
7
6
  thinkingEffort: SessionSnapshot["thinkingEffort"];
8
7
  inheritEnv?: boolean;
9
- systemAi?: SystemAiConfig;
10
8
  /** 内置「系统运维」员工的人设;作为系统提示前缀注入 Wand 自有 AI 调用。 */
11
9
  opsPersona?: string;
12
10
  /** CLI 降级链(按顺序)。为空时只用 provider/model 这一次调用。 */
13
- cliCandidates?: import("./types.js").AiCliCandidate[];
11
+ cliCandidates?: AiCliCandidate[];
14
12
  }
15
13
  /**
16
14
  * Resolve the provider from every representation used by current and legacy
@@ -21,6 +19,4 @@ export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "
21
19
  /** Build the provider-specific settings used by session-adjacent AI actions. */
22
20
  export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
23
21
  /** Build the source order for Wand-owned AI features such as titles. */
24
- export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAi" | "systemAiCli" | "systemAiModel">, systemEmployee?: SiliconEmployee | null): SessionAiContext;
25
- /** Build the AI context for quick-commit actions from their global preferences. */
26
- export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv" | "commitAiSource" | "systemAi" | "systemAiCli" | "systemAiModel">, discoverApis?: typeof discoverCliSystemAiConfigs, systemEmployee?: SiliconEmployee | null): SessionAiContext;
22
+ export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAiCli" | "systemAiModel">, systemEmployee?: SiliconEmployee | null): SessionAiContext;
@@ -1,5 +1,4 @@
1
1
  import { getDefaultModelForProvider } from "./config.js";
2
- import { discoverCliSystemAiConfigs, mergeSystemAiConfigs, systemAiProfiles, } from "./system-ai.js";
3
2
  import { providerCliInstalled, isSessionProvider } from "./session-provider.js";
4
3
  import { isSystemSiliconEmployee, systemEmployeeCliCandidates } from "./system-employee.js";
5
4
  import { inferProviderFromCommand, inferProviderFromRunner } from "./session-provider.js";
@@ -21,11 +20,6 @@ function normalizeModel(value) {
21
20
  const model = value?.trim();
22
21
  return model && model !== "default" ? model : undefined;
23
22
  }
24
- function usableSystemAi(config) {
25
- if (!systemAiProfiles(config, true).length)
26
- return undefined;
27
- return { ...config, enabled: true };
28
- }
29
23
  /** Build the provider-specific settings used by session-adjacent AI actions. */
30
24
  export function resolveSessionAiContext(snapshot, config) {
31
25
  const provider = resolveSessionProvider(snapshot);
@@ -60,10 +54,6 @@ export function resolveSystemAiContext(snapshot, config, systemEmployee) {
60
54
  owned.model = preferred.model ?? normalizeModel(getDefaultModelForProvider(config, preferred.provider));
61
55
  owned.thinkingEffort = preferred.thinkingEffort ?? config.defaultThinkingEffort;
62
56
  }
63
- if (config.systemAi?.enabled) {
64
- const directApi = usableSystemAi(config.systemAi);
65
- return directApi ? { ...owned, systemAi: directApi } : owned;
66
- }
67
57
  if (chain.length)
68
58
  return owned;
69
59
  // Older installations without a system AI CLI preference still follow the
@@ -78,18 +68,3 @@ export function resolveSystemAiContext(snapshot, config, systemEmployee) {
78
68
  thinkingEffort: config.defaultThinkingEffort,
79
69
  };
80
70
  }
81
- /** Build the AI context for quick-commit actions from their global preferences. */
82
- export function resolveCommitAiContext(snapshot, config, discoverApis = discoverCliSystemAiConfigs, systemEmployee) {
83
- const sessionContext = resolveSystemAiContext(snapshot, config, systemEmployee);
84
- if (config.commitAiSource !== "api") {
85
- // 快捷提交的直连来源只看 commitAiSource;系统 AI 的 enabled 不能顺手把它打开。
86
- const cliContext = { ...sessionContext };
87
- delete cliContext.systemAi;
88
- return cliContext;
89
- }
90
- const directApi = mergeSystemAiConfigs(config.systemAi, discoverApis(sessionContext.provider));
91
- return {
92
- ...sessionContext,
93
- ...(directApi ? { systemAi: directApi } : {}),
94
- };
95
- }
@@ -20,6 +20,8 @@ export declare function consumePtyInputForTopic(buffer: PtyTopicLineBuffer, inpu
20
20
  export interface SessionTopic {
21
21
  title: string;
22
22
  description: string;
23
+ /** native = CLI 自己生成的标题;缺省表示由系统硅基员工总结出来的候选。 */
24
+ source?: "native";
23
25
  }
24
26
  export interface SessionTopicRequest {
25
27
  messages?: readonly ConversationTurn[];
@@ -27,6 +29,10 @@ export interface SessionTopicRequest {
27
29
  cwd?: string;
28
30
  language?: string;
29
31
  ai?: QuickCommitAiOptions;
32
+ /** provider 自己在运行期写下的标题;命中即优先使用,不再调用模型。 */
33
+ readNativeTitle?: () => Promise<string>;
34
+ /** 已确认过原生标题:本轮没有新名字时不要让模型覆盖它。 */
35
+ hasNativeTitle?: boolean;
30
36
  onGenerating(generating: boolean): void;
31
37
  onTopic(topic: SessionTopic): void;
32
38
  onError(error: unknown): void;
@@ -68,6 +74,18 @@ export declare function generateSessionTopic(userMessages: readonly string[], cw
68
74
  * in-flight result and is summarized together with every earlier user turn.
69
75
  * Short inputs (single commands, choices) are dropped before any model call.
70
76
  */
77
+ /**
78
+ * 记住哪些会话的标题来自 provider 原生 CLI。原生标题优先于模型标题:
79
+ * 一旦记下,后续输入不再发起模型总结;provider 改名时直接覆盖。
80
+ */
81
+ export declare class SessionNativeTitleTracker {
82
+ private readonly titles;
83
+ record(sessionId: string, title: string): void;
84
+ get(sessionId: string): string;
85
+ has(sessionId: string): boolean;
86
+ delete(sessionId: string): void;
87
+ clear(): void;
88
+ }
71
89
  export declare class SessionTopicCoordinator {
72
90
  private readonly generate;
73
91
  private readonly states;
@@ -76,4 +94,5 @@ export declare class SessionTopicCoordinator {
76
94
  request(sessionId: string, request: SessionTopicRequest): void;
77
95
  clear(): void;
78
96
  private run;
97
+ private readNativeTitle;
79
98
  }
@@ -268,6 +268,30 @@ export async function generateSessionTopic(userMessages, cwd, language, ai = {})
268
268
  * in-flight result and is summarized together with every earlier user turn.
269
269
  * Short inputs (single commands, choices) are dropped before any model call.
270
270
  */
271
+ /**
272
+ * 记住哪些会话的标题来自 provider 原生 CLI。原生标题优先于模型标题:
273
+ * 一旦记下,后续输入不再发起模型总结;provider 改名时直接覆盖。
274
+ */
275
+ export class SessionNativeTitleTracker {
276
+ titles = new Map();
277
+ record(sessionId, title) {
278
+ const cleaned = title.replace(/\s+/g, " ").trim();
279
+ if (cleaned)
280
+ this.titles.set(sessionId, cleaned);
281
+ }
282
+ get(sessionId) {
283
+ return this.titles.get(sessionId) ?? "";
284
+ }
285
+ has(sessionId) {
286
+ return this.titles.has(sessionId);
287
+ }
288
+ delete(sessionId) {
289
+ this.titles.delete(sessionId);
290
+ }
291
+ clear() {
292
+ this.titles.clear();
293
+ }
294
+ }
271
295
  export class SessionTopicCoordinator {
272
296
  generate;
273
297
  states = new Map();
@@ -311,9 +335,24 @@ export class SessionTopicCoordinator {
311
335
  const request = state.request;
312
336
  const userMessages = state.userMessages.slice();
313
337
  try {
314
- const topic = await this.generate(userMessages, request.cwd, request.language, request.ai);
315
- if (!this.disposed && this.states.get(sessionId) === state && state.revision === revision) {
316
- state.request.onTopic(topic);
338
+ const pendingNativeTitle = this.readNativeTitle(request);
339
+ // 没有原生标题源时保持同步短路,不给普通会话增加一次等待。
340
+ const nativeTitle = typeof pendingNativeTitle === "string"
341
+ ? pendingNativeTitle
342
+ : pendingNativeTitle ? await pendingNativeTitle : "";
343
+ if (nativeTitle) {
344
+ if (!this.disposed && this.states.get(sessionId) === state && state.revision === revision) {
345
+ request.onTopic({ title: nativeTitle, description: nativeTitle, source: "native" });
346
+ }
347
+ }
348
+ else if (request.hasNativeTitle) {
349
+ // CLI 这次没读到名字(锁库、改名中),也不能让模型盖掉已确认的原生标题。
350
+ }
351
+ else {
352
+ const topic = await this.generate(userMessages, request.cwd, request.language, request.ai);
353
+ if (!this.disposed && this.states.get(sessionId) === state && state.revision === revision) {
354
+ state.request.onTopic(topic);
355
+ }
317
356
  }
318
357
  }
319
358
  catch (error) {
@@ -330,4 +369,15 @@ export class SessionTopicCoordinator {
330
369
  state.running = false;
331
370
  state.request.onGenerating(false);
332
371
  }
372
+ readNativeTitle(request) {
373
+ if (!request.readNativeTitle)
374
+ return null;
375
+ try {
376
+ // 读取失败按「没有原生标题」处理,回退系统硅基员工。
377
+ return request.readNativeTitle().catch(() => "");
378
+ }
379
+ catch {
380
+ return Promise.resolve("");
381
+ }
382
+ }
333
383
  }
@@ -58,7 +58,6 @@ function sessionBase(snapshot) {
58
58
  claudeSessionId: snapshot.claudeSessionId,
59
59
  providerSessionId: snapshot.claudeSessionId,
60
60
  queuedMessages: snapshot.queuedMessages,
61
- queuedMessageSkills: snapshot.queuedMessageSkills,
62
61
  structuredState: snapshot.structuredState,
63
62
  resumedFromSessionId: snapshot.resumedFromSessionId,
64
63
  autoRecovered: snapshot.autoRecovered,
@@ -1,6 +1,6 @@
1
1
  import { type QuickCommitAiOptions } from "./git-quick-commit.js";
2
2
  import type { SiliconEmployeeDraft } from "./ai-team-types.js";
3
- import type { AiTextRequest } from "./system-ai.js";
3
+ import type { AiTextRequest } from "./types.js";
4
4
  import type { WandTaskAgent } from "./task-types.js";
5
5
  import type { SessionProvider } from "./types.js";
6
6
  export declare class SiliconEmployeeDraftError extends Error {
package/dist/storage.js CHANGED
@@ -8,7 +8,7 @@ import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider, S
8
8
  import { DEFAULT_ITERATION_NAME, DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentKind, normalizeWandTaskAgentMode } from "./task-types.js";
9
9
  import { firstLayoutTabId } from "./layout-tree.js";
10
10
  import { isUnnamedWorkspaceTaskName } from "./wand-task-sync.js";
11
- import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, isTeamMemberRole, memberAgents } from "./ai-team-types.js";
11
+ import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, aiTeamChatTitle, isTeamMemberRole, memberAgents } from "./ai-team-types.js";
12
12
  import { isTaskExecutionSubject } from "./task-types.js";
13
13
  import { SYSTEM_EMPLOYEE_KEY, systemEmployeeDefinition, systemEmployeeSeedAgents, } from "./system-employee.js";
14
14
  import { isThinkingEffort } from "./structured-provider-common.js";
@@ -267,17 +267,6 @@ function parseQueuedMessages(raw) {
267
267
  const parsed = safeJsonParse(raw);
268
268
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : undefined;
269
269
  }
270
- function parseQueuedMessageSkills(raw, queueLength) {
271
- const parsed = safeJsonParse(raw);
272
- if (!Array.isArray(parsed))
273
- return undefined;
274
- return Array.from({ length: queueLength }, (_, index) => {
275
- const skills = parsed[index];
276
- return Array.isArray(skills)
277
- ? skills.filter((skill) => typeof skill === "string")
278
- : [];
279
- });
280
- }
281
270
  function parseWorktreeInfo(raw) {
282
271
  const parsed = safeJsonParse(raw);
283
272
  if (parsed && typeof parsed.branch === "string" && typeof parsed.path === "string") {
@@ -391,7 +380,6 @@ function sessionPersistAssignments() {
391
380
  runner = excluded.runner,
392
381
  messages = excluded.messages,
393
382
  queued_messages = excluded.queued_messages,
394
- queued_message_skills = excluded.queued_message_skills,
395
383
  structured_state = excluded.structured_state,
396
384
  resumed_from_session_id = excluded.resumed_from_session_id,
397
385
  auto_recovered = excluded.auto_recovered,
@@ -410,7 +398,7 @@ function sessionRuntimeMetadataAssignments() {
410
398
  command = ?, cwd = ?, mode = ?, status = ?, exit_code = ?,
411
399
  started_at = ?, ended_at = ?,
412
400
  archived = ?, archived_at = ?, claude_session_id = ?,
413
- provider = ?, session_kind = ?, runner = ?, queued_messages = ?, queued_message_skills = ?, structured_state = ?,
401
+ provider = ?, session_kind = ?, runner = ?, queued_messages = ?, structured_state = ?,
414
402
  resumed_from_session_id = ?, auto_recovered = ?,
415
403
  worktree_enabled = ?, worktree_info = ?, worktree_merge_status = ?, worktree_merge_info = ?,
416
404
  title = ?, description = ?, session_options = ?`;
@@ -437,7 +425,8 @@ function sessionPersistValues(snapshot) {
437
425
  snapshot.runner ?? null,
438
426
  snapshot.messages ? JSON.stringify(snapshot.messages) : null,
439
427
  snapshot.queuedMessages ? JSON.stringify(snapshot.queuedMessages) : null,
440
- snapshot.queuedMessageSkills ? JSON.stringify(snapshot.queuedMessageSkills) : null,
428
+ // queued_message_skills 列按「只加不删」保留,但 SDK skills 功能已移除,不再写入。
429
+ null,
441
430
  snapshot.structuredState ? JSON.stringify(snapshot.structuredState) : null,
442
431
  snapshot.resumedFromSessionId ?? null,
443
432
  snapshot.autoRecovered ? 1 : 0,
@@ -470,7 +459,6 @@ function sessionRuntimeMetadataValues(snapshot) {
470
459
  snapshot.sessionKind ?? "pty",
471
460
  snapshot.runner ?? null,
472
461
  snapshot.queuedMessages ? JSON.stringify(snapshot.queuedMessages) : null,
473
- snapshot.queuedMessageSkills ? JSON.stringify(snapshot.queuedMessageSkills) : null,
474
462
  snapshot.structuredState ? JSON.stringify(snapshot.structuredState) : null,
475
463
  snapshot.resumedFromSessionId ?? null,
476
464
  snapshot.autoRecovered ? 1 : 0,
@@ -511,7 +499,6 @@ function mapSessionCore(row) {
511
499
  claudeSessionId: row.claude_session_id,
512
500
  messages: safeJsonParse(row.messages),
513
501
  queuedMessages,
514
- queuedMessageSkills: parseQueuedMessageSkills(row.queued_message_skills, queuedMessages?.length ?? 0),
515
502
  structuredState: safeJsonParse(row.structured_state),
516
503
  resumedFromSessionId: row.resumed_from_session_id ?? undefined,
517
504
  autoRecovered: Boolean(row.auto_recovered),
@@ -2430,8 +2417,14 @@ export class WandStorage {
2430
2417
  * 一次查询建整张表,供 /api/tasks 每请求一次构建;不逐会话查询。
2431
2418
  */
2432
2419
  listAiTeamRunChatMarkers() {
2433
- const rows = this.db.prepare(`SELECT r.id, r.team_id, r.team_json, r.chat_session_id, t.name AS current_team_name
2420
+ const rows = this.db.prepare(`SELECT r.id, r.team_id, r.team_json, r.chat_session_id, t.name AS current_team_name,
2421
+ task.title AS task_title
2434
2422
  FROM ai_team_runs r LEFT JOIN ai_teams t ON t.id = r.team_id
2423
+ LEFT JOIN command_sessions s ON s.id = r.chat_session_id
2424
+ LEFT JOIN wand_tasks task ON task.id = COALESCE((
2425
+ SELECT id FROM wand_tasks WHERE workspace_task_id = s.workspace_task_id
2426
+ ORDER BY updated_at DESC, rowid DESC LIMIT 1
2427
+ ), r.task_id)
2435
2428
  WHERE r.chat_session_id IS NOT NULL AND r.chat_session_id <> '' ORDER BY r.created_at DESC`).all();
2436
2429
  const markers = new Map();
2437
2430
  for (const row of rows) {
@@ -2443,6 +2436,7 @@ export class WandStorage {
2443
2436
  runId: String(row.id),
2444
2437
  teamId: String(row.team_id),
2445
2438
  teamName: typeof row.current_team_name === "string" ? row.current_team_name : String(rawTeam?.name ?? ""),
2439
+ chatTitle: aiTeamChatTitle(typeof row.task_title === "string" ? row.task_title : ""),
2446
2440
  memberCount: Array.isArray(rawTeam?.members) ? rawTeam.members.length : 0,
2447
2441
  });
2448
2442
  }
@@ -18,12 +18,6 @@ export interface ClaudeCliArgsOptions {
18
18
  systemPromptParts?: string[];
19
19
  }
20
20
  export declare function buildClaudeCliArgs(session: SessionSnapshot, options: ClaudeCliArgsOptions): string[];
21
- export declare function buildClaudeSdkThinking(effort: SessionSnapshot["thinkingEffort"]): {
22
- type: "enabled";
23
- budgetTokens: number;
24
- } | {
25
- type: "disabled";
26
- };
27
21
  export interface ClaudeCliRunnerOptions {
28
22
  language?: () => string | undefined;
29
23
  }
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { isRunningAsRoot } from "./env-utils.js";
5
5
  import { startStructuredCli } from "./structured-exec-pump.js";
6
6
  import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
7
- import { thinkingEffortToClaudeCliEffort, thinkingEffortToSdkBudget } from "./structured-provider-common.js";
7
+ import { thinkingEffortToClaudeCliEffort } from "./structured-provider-common.js";
8
8
  import { ClaudeCliProtocolReducer } from "./structured-claude-protocol.js";
9
9
  const ROOT_FALLBACK_ALLOWED_TOOLS = [
10
10
  "Bash", "Edit", "Write", "Read", "Glob", "Grep", "NotebookEdit", "WebFetch", "WebSearch",
@@ -124,10 +124,6 @@ export function buildClaudeCliArgs(session, options) {
124
124
  args.push("--resume", session.claudeSessionId);
125
125
  return args;
126
126
  }
127
- export function buildClaudeSdkThinking(effort) {
128
- const budgetTokens = thinkingEffortToSdkBudget(effort);
129
- return budgetTokens > 0 ? { type: "enabled", budgetTokens } : { type: "disabled" };
130
- }
131
127
  /** Owns the Claude print-mode process and its stream-json protocol. */
132
128
  export class ClaudeCliRunner {
133
129
  options;