@co0ontty/wand 4.89.4 → 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.
- package/README.md +12 -1
- package/dist/ai-team-employee-binding.js +2 -1
- package/dist/archive-policy.d.ts +17 -0
- package/dist/archive-policy.js +8 -0
- package/dist/build-info.json +3 -3
- package/dist/fixed-employee-avatar.d.ts +6 -0
- package/dist/fixed-employee-avatar.js +4 -0
- package/dist/npm-update-utils.js +14 -12
- package/dist/plush-avatar.d.ts +2 -1
- package/dist/plush-avatar.js +4 -2
- package/dist/retention.js +8 -10
- package/dist/server-employee-routes.js +9 -3
- package/dist/server-session-routes.js +27 -23
- package/dist/server-task-routes.js +10 -0
- package/dist/server-update-routes.js +12 -3
- package/dist/server-workspace-routes.js +12 -0
- package/dist/server.js +4 -4
- package/dist/session-registry.js +7 -0
- package/dist/storage.js +12 -4
- package/dist/structured-session-manager.d.ts +5 -0
- package/dist/structured-session-manager.js +43 -9
- package/dist/task-archive-policy.d.ts +4 -0
- package/dist/task-archive-policy.js +16 -0
- package/dist/tui/commands.d.ts +1 -1
- package/dist/tui/commands.js +6 -3
- package/dist/web-ui/chat-message-meta.d.ts +3 -1
- package/dist/web-ui/chat-message-meta.js +21 -1
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/licenses/codex-LICENSE.txt +21 -0
- package/dist/web-ui/content/plush-avatar.js +34 -34
- package/dist/web-ui/content/scripts.js +302 -398
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -1
- package/dist/web-ui/embedded-assets.js +4 -4
- package/dist/web-ui/markdown.js +3 -1
- package/dist/web-ui/provider-identity.d.ts +1 -1
- package/dist/web-ui/provider-identity.js +4 -3
- package/package.json +1 -1
- package/dist/commit-task-archive.d.ts +0 -8
- 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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
+
}
|
package/dist/tui/commands.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
32
|
+
* 检查所选 npm dist-tag,再安装经过通道校验的固定版本。
|
|
33
33
|
*
|
|
34
34
|
* 此调用同步阻塞(TUI 上层应在另一线程的 setImmediate 调度,或直接 await)。
|
|
35
35
|
* 通过 npm-update-utils 自动处理 `.wand-XXXXXX` 残留目录和 ENOTEMPTY 回退,
|
package/dist/tui/commands.js
CHANGED
|
@@ -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,
|
|
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
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 [];
|