@co0ontty/wand 4.89.2 → 4.89.5

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 (44) hide show
  1. package/README.md +12 -1
  2. package/browser-extension/icons/wand-128.png +0 -0
  3. package/browser-extension/icons/wand-16.png +0 -0
  4. package/browser-extension/icons/wand-32.png +0 -0
  5. package/browser-extension/icons/wand-48.png +0 -0
  6. package/dist/ai-team-employee-binding.js +2 -1
  7. package/dist/archive-policy.d.ts +17 -0
  8. package/dist/archive-policy.js +8 -0
  9. package/dist/build-info.json +3 -3
  10. package/dist/fixed-employee-avatar.d.ts +6 -0
  11. package/dist/fixed-employee-avatar.js +4 -0
  12. package/dist/npm-update-utils.js +14 -12
  13. package/dist/plush-avatar.d.ts +2 -1
  14. package/dist/plush-avatar.js +4 -2
  15. package/dist/retention.js +8 -10
  16. package/dist/server-employee-routes.js +9 -3
  17. package/dist/server-session-routes.js +27 -23
  18. package/dist/server-task-routes.js +10 -0
  19. package/dist/server-update-routes.js +12 -3
  20. package/dist/server-workspace-routes.js +12 -0
  21. package/dist/server.js +4 -4
  22. package/dist/session-registry.js +7 -0
  23. package/dist/storage.js +12 -4
  24. package/dist/structured-session-manager.d.ts +5 -0
  25. package/dist/structured-session-manager.js +43 -9
  26. package/dist/task-archive-policy.d.ts +4 -0
  27. package/dist/task-archive-policy.js +16 -0
  28. package/dist/tui/commands.d.ts +1 -1
  29. package/dist/tui/commands.js +6 -3
  30. package/dist/web-ui/chat-message-meta.d.ts +3 -1
  31. package/dist/web-ui/chat-message-meta.js +21 -1
  32. package/dist/web-ui/content/ai-teams.js +13 -13
  33. package/dist/web-ui/content/licenses/codex-LICENSE.txt +21 -0
  34. package/dist/web-ui/content/plush-avatar.js +34 -34
  35. package/dist/web-ui/content/scripts.js +304 -400
  36. package/dist/web-ui/content/styles.css +1 -1
  37. package/dist/web-ui/content/tailwind.css +1 -1
  38. package/dist/web-ui/embedded-assets.js +8 -8
  39. package/dist/web-ui/markdown.js +3 -1
  40. package/dist/web-ui/provider-identity.d.ts +1 -1
  41. package/dist/web-ui/provider-identity.js +4 -3
  42. package/package.json +1 -1
  43. package/dist/commit-task-archive.d.ts +0 -8
  44. package/dist/commit-task-archive.js +0 -65
@@ -11,6 +11,7 @@ import { buildChildEnv } from "./env-utils.js";
11
11
  import { getErrorMessage } from "./error-utils.js";
12
12
  import { getDefaultModelForProvider } from "./config.js";
13
13
  import { isLocalDecisionModel } from "./decision-expert-identity.js";
14
+ import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
14
15
  import { recordIterationPrompt } from "./iteration-log.js";
15
16
  import { startEmployeeKnowledgeRunner } from "./employee-knowledge.js";
16
17
  import { CoreRunner } from "./core-runner.js";
@@ -2083,21 +2084,53 @@ export class StructuredSessionManager {
2083
2084
  throw new Error("执行引擎必须是 cli 或 sdk。");
2084
2085
  if (engine === "sdk" && provider !== "pi")
2085
2086
  throw new Error("Wand Agent 只支持 Pi 结构化会话。");
2087
+ this.requireBlankSessionSelection(session);
2088
+ const updated = this.blankSessionToolSelection(session, provider, engine);
2089
+ if (updated === session)
2090
+ return session;
2091
+ return this.saveBlankSessionSelection(updated);
2092
+ }
2093
+ /** 首次发送前显式选择真实员工;同一会话、目录和未发送输入的归属不变。 */
2094
+ setSessionEmployee(sessionId, employeeId) {
2095
+ const session = this.requireSession(sessionId);
2096
+ this.requireBlankSessionSelection(session);
2097
+ const employee = this.storage.getSiliconEmployee(employeeId);
2098
+ if (!employee || employee.archivedAt)
2099
+ throw new Error("硅基员工不存在或已归档。");
2100
+ const selected = selectEmployeeCandidate(employee);
2101
+ if (session.employeeId === employee.id)
2102
+ return session;
2103
+ const role = {
2104
+ ...session,
2105
+ employeeId: employee.id,
2106
+ employeeName: employee.name,
2107
+ employeeAvatar: employee.avatar,
2108
+ systemPrompt: employee.prompt,
2109
+ runtimeSystemPrompt: undefined,
2110
+ employeeCandidates: employee.agents,
2111
+ employeeCandidateIndex: selected.index,
2112
+ };
2113
+ return this.saveBlankSessionSelection(this.blankSessionToolSelection(role, selected.agent.provider, selected.agent.engine ?? "cli", selected.index));
2114
+ }
2115
+ requireBlankSessionSelection(session) {
2086
2116
  if (session.status !== "idle" || session.archived || session.structuredState?.inFlight ||
2087
- this.pendingRunnerExecutions.has(sessionId) || session.claudeSessionId ||
2117
+ session.structuredState?.activeRequestId ||
2118
+ this.pendingRunnerExecutions.has(session.id) || session.claudeSessionId ||
2088
2119
  session.resumedFromSessionId || session.autoRecovered ||
2089
2120
  (session.messages?.length ?? 0) > 0 || (session.queuedMessages?.length ?? 0) > 0 ||
2090
2121
  session.automationId || (session.sessionSource && session.sessionSource !== "interactive") ||
2091
2122
  this.relayHandlerFor(session)) {
2092
- throw new Error("只有尚未发送消息的新建空白对话可以更换工具。");
2123
+ throw new Error("只有尚未发送消息的新建空白对话可以更换员工或工具。");
2093
2124
  }
2125
+ }
2126
+ blankSessionToolSelection(session, provider, engine, selectedCandidateIndex) {
2094
2127
  const candidates = session.employeeCandidates ?? [];
2095
2128
  const currentCandidate = candidates[session.employeeCandidateIndex ?? 0];
2096
2129
  const currentEngine = session.provider === "pi" && (session.structuredState?.engine === "core"
2097
2130
  || (!session.structuredState?.engine && currentCandidate?.provider === "pi" && currentCandidate.engine === "sdk")) ? "sdk" : "cli";
2098
2131
  // 旧客户端只发 provider 时,同 provider 保持原引擎;显式 Pi 选项始终传 cli。
2099
2132
  const targetEngine = provider === "pi" ? engine ?? (session.provider === provider ? currentEngine : "cli") : "cli";
2100
- if (session.provider === provider && currentEngine === targetEngine)
2133
+ if (selectedCandidateIndex === undefined && session.provider === provider && currentEngine === targetEngine)
2101
2134
  return session;
2102
2135
  if (targetEngine === "sdk" && session.provider === "pi" && (session.piSettings?.resources?.skills.length
2103
2136
  || session.piSettings?.resources?.mcpServers.length || session.piSettings?.lockedSkills?.length
@@ -2105,7 +2138,7 @@ export class StructuredSessionManager {
2105
2138
  throw new Error("Wand Agent 不支持会话级 Skills / MCP、自动选择或 CodeMode 覆盖;请先在 Pi 设置中关闭这些选项再切换。");
2106
2139
  }
2107
2140
  const resolution = provider === "pi" ? this.resolveNewSessionPiEngine(targetEngine) : undefined;
2108
- const candidateIndex = candidates.findIndex((candidate) => candidate.provider === provider
2141
+ const candidateIndex = selectedCandidateIndex ?? candidates.findIndex((candidate) => candidate.provider === provider
2109
2142
  && (candidate.engine ?? "cli") === targetEngine);
2110
2143
  const candidate = candidateIndex >= 0 ? candidates[candidateIndex] : undefined;
2111
2144
  const runner = defaultStructuredRunner(provider);
@@ -2118,7 +2151,7 @@ export class StructuredSessionManager {
2118
2151
  (requestedMode === "auto-edit" && ["opencode", "grok", "pi"].includes(provider));
2119
2152
  const mode = provider === "codex" ? "full-access"
2120
2153
  : unsupportedMode ? "default" : requestedMode;
2121
- const updated = {
2154
+ return {
2122
2155
  ...session,
2123
2156
  provider,
2124
2157
  runner,
@@ -2136,14 +2169,15 @@ export class StructuredSessionManager {
2136
2169
  structuredState: { ...defaultStructuredState(provider, runner), model: model ?? undefined,
2137
2170
  ...(resolution ? { engine: resolution.engine, engineReason: resolution.reason } : {}) },
2138
2171
  };
2139
- // 身份、知识归属、任务/目录和候选快照不变;只更新本会话的执行参数。
2172
+ }
2173
+ saveBlankSessionSelection(updated) {
2140
2174
  this.storage.saveSession(updated);
2141
- this.sessions.set(sessionId, updated);
2175
+ this.sessions.set(updated.id, updated);
2142
2176
  this.emit({
2143
2177
  type: "status",
2144
- sessionId,
2178
+ sessionId: updated.id,
2145
2179
  data: { ...buildStructuredOutputPayload(updated), command: updated.command,
2146
- mode, autoApprovePermissions: updated.autoApprovePermissions },
2180
+ mode: updated.mode, autoApprovePermissions: updated.autoApprovePermissions },
2147
2181
  });
2148
2182
  return updated;
2149
2183
  }
@@ -0,0 +1,4 @@
1
+ import type { WandStorage } from "./storage.js";
2
+ import type { SessionRegistry } from "./session-registry.js";
3
+ /** Read live owners at the mutation boundary; a stale task label cannot hide active execution. */
4
+ export declare function canArchiveTask(storage: WandStorage, sessions: SessionRegistry | undefined, taskId: string): boolean;
@@ -0,0 +1,16 @@
1
+ import { isArchiveBlocked } from "./archive-policy.js";
2
+ import { AI_TEAM_ACTIVE_RUN_STATUSES } from "./ai-team-types.js";
3
+ /** Read live owners at the mutation boundary; a stale task label cannot hide active execution. */
4
+ export function canArchiveTask(storage, sessions, taskId) {
5
+ const task = storage.getWandTask(taskId);
6
+ if (!task || task.status === "doing")
7
+ return false;
8
+ if (storage.listWandTaskSessionIds(task.id).some(id => {
9
+ const session = sessions?.getLatest(id) ?? storage.getSession(id);
10
+ return session ? isArchiveBlocked(session) : false;
11
+ }))
12
+ return false;
13
+ return ![task.id, task.workspaceTaskId].some(id => id && storage.listAiTeamRuns({
14
+ taskId: id, statuses: AI_TEAM_ACTIVE_RUN_STATUSES, limit: 1,
15
+ }).length > 0);
16
+ }
@@ -29,7 +29,7 @@ export declare function readUpdateChannel(configPath: string): UpdateChannel;
29
29
  /** 通过 npm dist-tag 拿到当前通道最新版本号。失败返回 latest=null。 */
30
30
  export declare function checkUpdate(currentVersion: string, channel?: UpdateChannel): UpdateInfo;
31
31
  /**
32
- * 执行 `npm install -g @co0ontty/wand@<dist-tag>`。
32
+ * 检查所选 npm dist-tag,再安装经过通道校验的固定版本。
33
33
  *
34
34
  * 此调用同步阻塞(TUI 上层应在另一线程的 setImmediate 调度,或直接 await)。
35
35
  * 通过 npm-update-utils 自动处理 `.wand-XXXXXX` 残留目录和 ENOTEMPTY 回退,
@@ -9,7 +9,7 @@ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, unlinkSyn
9
9
  import os from "node:os";
10
10
  import path from "node:path";
11
11
  import process from "node:process";
12
- import { checkPackageUpdateSync, getInstallSpecForChannel, installPackageGloballySync, normalizeUpdateChannel, resolveGlobalWandBin, resolveGlobalWandCli, } from "../npm-update-utils.js";
12
+ import { checkPackageUpdateSync, installPackageGloballySync, normalizeUpdateChannel, resolveGlobalWandBin, resolveGlobalWandCli, } from "../npm-update-utils.js";
13
13
  import { whichSync } from "../path-repair.js";
14
14
  import { computeRelaunch } from "../relaunch.js";
15
15
  import { ensureDatabaseFile, resolveDatabasePath, WandStorage } from "../storage.js";
@@ -77,7 +77,7 @@ export function checkUpdate(currentVersion, channel = "stable") {
77
77
  };
78
78
  }
79
79
  /**
80
- * 执行 `npm install -g @co0ontty/wand@<dist-tag>`。
80
+ * 检查所选 npm dist-tag,再安装经过通道校验的固定版本。
81
81
  *
82
82
  * 此调用同步阻塞(TUI 上层应在另一线程的 setImmediate 调度,或直接 await)。
83
83
  * 通过 npm-update-utils 自动处理 `.wand-XXXXXX` 残留目录和 ENOTEMPTY 回退,
@@ -89,7 +89,10 @@ export function installUpdate(channel = "stable") {
89
89
  const serviceSafety = checkInstalledServiceEntrypointsForUpdate();
90
90
  if (!serviceSafety.ok)
91
91
  return serviceSafety;
92
- const res = installPackageGloballySync(getInstallSpecForChannel(channel), 180_000);
92
+ const info = checkPackageUpdateSync("dev", channel);
93
+ if (!info.latest)
94
+ return { ok: false, message: "npm 所选通道暂无有效版本,或 registry 暂时不可用。" };
95
+ const res = installPackageGloballySync(info.installSpec, 180_000);
93
96
  const out = (res.stdout || "") + (res.stderr ? "\n" + res.stderr : "");
94
97
  const trail = res.attempts.length > 1
95
98
  ? `\n\n尝试过的命令:\n ${res.attempts.join("\n ")}`
@@ -5,7 +5,9 @@ export interface ChatUsageMetric {
5
5
  value: string;
6
6
  title: string;
7
7
  }
8
- /** Display only reported counts. Round aggregation remains owned by the chat renderer. */
8
+ /** Latest reported round, retaining it while the next reply has no usage yet. */
9
+ export declare function latestChatUsage(messages: readonly Pick<ConversationTurn, "role" | "usage">[]): ConversationTurn["usage"];
10
+ /** Display only reported counts. */
9
11
  export declare function chatUsageMetrics(usage: ConversationTurn["usage"]): ChatUsageMetric[];
10
12
  /** Never infer a finished duration from wall time, a live turn, or invalid/reversed anchors. */
11
13
  export declare function chatReplyDuration(turn: Pick<ConversationTurn, "role" | "createdAt" | "completedAt">): string | null;
@@ -6,7 +6,27 @@ function compactNumber(value) {
6
6
  return `${(value / 1_000).toFixed(1).replace(/\.0$/, "")}k`;
7
7
  return String(Math.round(value));
8
8
  }
9
- /** Display only reported counts. Round aggregation remains owned by the chat renderer. */
9
+ /** Latest reported round, retaining it while the next reply has no usage yet. */
10
+ export function latestChatUsage(messages) {
11
+ let latest;
12
+ let round = {};
13
+ for (const message of messages) {
14
+ if (message.role === "user")
15
+ round = {};
16
+ if (message.role !== "assistant" || !message.usage)
17
+ continue;
18
+ for (const key of ["inputTokens", "outputTokens", "cacheReadInputTokens", "cacheCreationInputTokens", "reasoningOutputTokens", "totalCostUsd"]) {
19
+ const value = message.usage[key];
20
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0)
21
+ round[key] = (round[key] ?? 0) + value;
22
+ }
23
+ if (message.usage.estimated)
24
+ round.estimated = true;
25
+ latest = { ...round };
26
+ }
27
+ return latest;
28
+ }
29
+ /** Display only reported counts. */
10
30
  export function chatUsageMetrics(usage) {
11
31
  if (!usage)
12
32
  return [];