@co0ontty/wand 4.80.1 → 4.81.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.
- package/dist/build-info.json +3 -3
- package/dist/iteration-log.d.ts +0 -4
- package/dist/iteration-log.js +1 -5
- package/dist/message-truncator.d.ts +6 -12
- package/dist/message-truncator.js +116 -31
- package/dist/server-resume-routes.d.ts +2 -2
- package/dist/server-resume-routes.js +1 -1
- package/dist/server-session-routes.js +67 -49
- package/dist/server.js +12 -1
- package/dist/structured-session-manager.js +13 -8
- package/dist/system-employee.d.ts +0 -2
- package/dist/system-employee.js +0 -6
- package/dist/task-types.d.ts +0 -2
- package/dist/task-types.js +0 -2
- package/dist/tool-use-timestamps.d.ts +3 -0
- package/dist/tool-use-timestamps.js +27 -0
- package/dist/types.d.ts +12 -0
- package/dist/web-ui/content/ai-teams.js +1 -1
- package/dist/web-ui/content/scripts.js +70 -68
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/dist/ws-broadcast.d.ts +1 -0
- package/dist/ws-broadcast.js +39 -4
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-09-
|
|
4
|
-
"version": "4.
|
|
2
|
+
"commit": "94cd7604eb5f5ffc01687755a23e337888cee038",
|
|
3
|
+
"builtAt": "2026-09-30T22:06:12.590Z",
|
|
4
|
+
"version": "4.81.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/iteration-log.d.ts
CHANGED
|
@@ -72,10 +72,6 @@ export declare function recordIterationPromptForTask(storage: WandStorage, input
|
|
|
72
72
|
* 已被上一轮提交用掉的条目会被标出来,方便模型少写重复内容。
|
|
73
73
|
*/
|
|
74
74
|
export declare function iterationPromptDigest(entries: readonly WandIterationPrompt[]): string;
|
|
75
|
-
/** 迭代为空时给模型的一句话,避免它把「没有输入」理解成「没有改动」。 */
|
|
76
|
-
export declare const EMPTY_ITERATION_DIGEST_HINT = "\uFF08\u672C\u8FED\u4EE3\u8FD8\u6CA1\u6709\u8BB0\u5F55\u5230\u63D0\u793A\u8BCD\uFF0C\u6539\u4E3A\u4F9D\u636E\u6587\u4EF6\u6539\u52A8\u5224\u65AD\u3002\uFF09";
|
|
77
|
-
/** 默认迭代的名字;UI 与文档共用,避免各处硬编码。 */
|
|
78
|
-
export declare const DEFAULT_ITERATION_LABEL = "\u9ED8\u8BA4\u8FED\u4EE3";
|
|
79
75
|
/** 面板里的一行:就是一条提示词记录 + 是否已被上一轮提交用掉。 */
|
|
80
76
|
export interface IterationCommitEntry {
|
|
81
77
|
id: string;
|
package/dist/iteration-log.js
CHANGED
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import { getErrorMessage } from "./error-utils.js";
|
|
10
10
|
import { runGitAsync } from "./git-utils.js";
|
|
11
11
|
import { shouldGenerateSessionTopicFromInput, summarizeSessionTitleFromInput } from "./session-topic.js";
|
|
12
|
-
import {
|
|
12
|
+
import { WAND_ITERATION_PROMPT_DETAIL_MAX_LENGTH, WAND_ITERATION_PROMPT_TITLE_MAX_LENGTH, } from "./task-types.js";
|
|
13
13
|
/** 用户在上次选择后记住的模式;两条互不影响,互相切不会被覆盖。 */
|
|
14
14
|
export const COMMIT_CONTEXT_MODE_PREF_KEY = "pref:commitContextMode";
|
|
15
15
|
export function isCommitContextMode(value) {
|
|
@@ -215,10 +215,6 @@ export function iterationPromptDigest(entries) {
|
|
|
215
215
|
}
|
|
216
216
|
return kept.map((line, index) => `${index + 1}. ${line}`).join("\n");
|
|
217
217
|
}
|
|
218
|
-
/** 迭代为空时给模型的一句话,避免它把「没有输入」理解成「没有改动」。 */
|
|
219
|
-
export const EMPTY_ITERATION_DIGEST_HINT = `(本迭代还没有记录到提示词,改为依据文件改动判断。)`;
|
|
220
|
-
/** 默认迭代的名字;UI 与文档共用,避免各处硬编码。 */
|
|
221
|
-
export const DEFAULT_ITERATION_LABEL = DEFAULT_ITERATION_NAME;
|
|
222
218
|
function toCommitEntry(entry) {
|
|
223
219
|
return {
|
|
224
220
|
id: entry.id,
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Truncates tool_result content in messages for WebSocket transport.
|
|
3
|
-
* Cards that are collapsed by default have their large results replaced
|
|
4
|
-
* with a summary, and clients fetch full content on-demand via API.
|
|
5
|
-
*/
|
|
1
|
+
/** Keep tool payloads out of all chat snapshots and fetch them only when opened. */
|
|
6
2
|
import type { CardExpandDefaults, ContentBlock, ConversationTurn } from "./types.js";
|
|
7
3
|
export interface WindowedMessages {
|
|
8
4
|
/** 已截断 + 窗口化后的 turn 列表(最近 windowSize 条)。 */
|
|
@@ -72,12 +68,10 @@ export declare function sliceTurnBlocksForTransport(turn: ConversationTurn, star
|
|
|
72
68
|
*/
|
|
73
69
|
export declare function windowMessagesForTransport(all: ConversationTurn[] | undefined, cardDefaults: CardExpandDefaults, windowSize?: number): WindowedMessages;
|
|
74
70
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* @param messages - Original messages array (not mutated)
|
|
79
|
-
* @param cardDefaults - Current card expand defaults from config
|
|
80
|
-
* @param streamingTurnIndex - Index of the currently streaming turn (-1 if none).
|
|
81
|
-
* Tool results in the streaming turn are never truncated.
|
|
71
|
+
* Project ordinary activity tools to a lean transport shape, regardless of
|
|
72
|
+
* result size or streaming/error state. Interactive questions, task lists,
|
|
73
|
+
* subagents and image cards retain their independent visible contracts.
|
|
82
74
|
*/
|
|
75
|
+
export declare function compactToolMessagesForTransport(messages: ConversationTurn[], knownToolNames?: ReadonlyMap<string, string>): ConversationTurn[];
|
|
76
|
+
/** Existing transport behavior for clients that have not opted into compact tool cards. */
|
|
83
77
|
export declare function truncateMessagesForTransport(messages: ConversationTurn[], cardDefaults: CardExpandDefaults, streamingTurnIndex?: number): ConversationTurn[];
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* Cards that are collapsed by default have their large results replaced
|
|
4
|
-
* with a summary, and clients fetch full content on-demand via API.
|
|
5
|
-
*/
|
|
1
|
+
/** Keep tool payloads out of all chat snapshots and fetch them only when opened. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
6
3
|
import { contentHasStructuredImage } from "./structured-content.js";
|
|
7
4
|
const TRUNCATION_THRESHOLD = 200;
|
|
8
5
|
const SUMMARY_LENGTH = 100;
|
|
@@ -69,6 +66,10 @@ function blockTransportBytes(block, cardDefaults, toolNameById) {
|
|
|
69
66
|
bytes += JSON.stringify(use.input)?.length ?? 0;
|
|
70
67
|
if (use.description)
|
|
71
68
|
bytes += use.description.length;
|
|
69
|
+
if (use.semantic)
|
|
70
|
+
bytes += JSON.stringify(use.semantic).length;
|
|
71
|
+
if (use.activity)
|
|
72
|
+
bytes += JSON.stringify(use.activity).length;
|
|
72
73
|
return bytes;
|
|
73
74
|
}
|
|
74
75
|
case "tool_result": {
|
|
@@ -346,54 +347,138 @@ function isToolDefaultCollapsed(toolName, defaults) {
|
|
|
346
347
|
return false;
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
350
|
+
function inputString(input, ...keys) {
|
|
351
|
+
for (const key of keys) {
|
|
352
|
+
const value = input[key];
|
|
353
|
+
if (typeof value !== "string")
|
|
354
|
+
continue;
|
|
355
|
+
const trimmed = value.trim();
|
|
356
|
+
if (trimmed)
|
|
357
|
+
return trimmed;
|
|
358
|
+
}
|
|
359
|
+
return "";
|
|
360
|
+
}
|
|
361
|
+
function toolActivity(use, hasImage) {
|
|
362
|
+
const name = use.name.toLowerCase();
|
|
363
|
+
const input = use.input ?? {};
|
|
364
|
+
const path = inputString(input, "file_path", "path", "filename", "file", "notebook_path");
|
|
365
|
+
const hasMultipleFiles = ["paths", "file_paths", "files"].some((key) => Array.isArray(input[key]) && input[key].length > 1);
|
|
366
|
+
const singleFileKey = !hasMultipleFiles && path
|
|
367
|
+
? createHash("sha256").update(path.replace(/\\/g, "/")).digest("hex").slice(0, 20)
|
|
368
|
+
: !hasMultipleFiles ? use.activity?.fileKey : undefined;
|
|
369
|
+
const kind = singleFileKey && /^(edit|write|multiedit|notebookedit|file_change|file_edit)$/.test(name)
|
|
370
|
+
? "edit_file"
|
|
371
|
+
: singleFileKey && /^(read|read_file)$/.test(name)
|
|
372
|
+
? "read_file"
|
|
373
|
+
: /^(bash|exec|exec_command|command_execution|shell_command|terminal|run_command)$/.test(name)
|
|
374
|
+
? "run_command"
|
|
375
|
+
: "other";
|
|
376
|
+
const fileKey = kind === "edit_file" || kind === "read_file" ? singleFileKey : undefined;
|
|
377
|
+
const imagePath = /\.(?:png|jpe?g|gif|webp|svg|bmp|avif)(?:[?#].*)?$/i.test(path);
|
|
378
|
+
const occurredAt = use.occurredAt ?? use.activity?.occurredAt;
|
|
379
|
+
return {
|
|
380
|
+
kind,
|
|
381
|
+
label: activityLabel(kind),
|
|
382
|
+
...(fileKey ? { fileKey } : {}),
|
|
383
|
+
...(hasImage || imagePath || use.activity?.hasImage ? { hasImage: true } : {}),
|
|
384
|
+
...(kind === "run_command" && occurredAt ? { occurredAt } : {}),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function activityLabel(kind) {
|
|
388
|
+
switch (kind) {
|
|
389
|
+
case "edit_file": return "修改文件";
|
|
390
|
+
case "read_file": return "查看文件";
|
|
391
|
+
case "run_command": return "运行命令";
|
|
392
|
+
default: return "使用工具";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const INDEPENDENT_TOOL_NAMES = new Set([
|
|
396
|
+
"AskUserQuestion", "TodoWrite", "TaskCreate", "TaskUpdate", "TaskList",
|
|
397
|
+
"Pi/todo", "Task", "Agent", "Pi/subagent",
|
|
398
|
+
]);
|
|
399
|
+
function toolInputShowsImage(input) {
|
|
400
|
+
const path = inputString(input, "file_path", "path", "url");
|
|
401
|
+
return /\.(?:png|jpe?g|gif|webp|svg|bmp|avif)(?:[?#].*)?$/i.test(path);
|
|
351
402
|
}
|
|
352
403
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* @param messages - Original messages array (not mutated)
|
|
357
|
-
* @param cardDefaults - Current card expand defaults from config
|
|
358
|
-
* @param streamingTurnIndex - Index of the currently streaming turn (-1 if none).
|
|
359
|
-
* Tool results in the streaming turn are never truncated.
|
|
404
|
+
* Project ordinary activity tools to a lean transport shape, regardless of
|
|
405
|
+
* result size or streaming/error state. Interactive questions, task lists,
|
|
406
|
+
* subagents and image cards retain their independent visible contracts.
|
|
360
407
|
*/
|
|
408
|
+
export function compactToolMessagesForTransport(messages, knownToolNames) {
|
|
409
|
+
const toolNameMap = new Map(knownToolNames);
|
|
410
|
+
const imageToolIds = new Set();
|
|
411
|
+
const independentToolIds = new Set();
|
|
412
|
+
for (const turn of messages) {
|
|
413
|
+
for (const block of turn.content) {
|
|
414
|
+
if (block.type === "tool_use") {
|
|
415
|
+
toolNameMap.set(block.id, block.name);
|
|
416
|
+
if (INDEPENDENT_TOOL_NAMES.has(block.name) || block.semantic || block.__subagent
|
|
417
|
+
|| toolInputShowsImage(block.input ?? {}))
|
|
418
|
+
independentToolIds.add(block.id);
|
|
419
|
+
}
|
|
420
|
+
else if (block.type === "tool_result" && contentHasStructuredImage(block.content)) {
|
|
421
|
+
imageToolIds.add(block.tool_use_id);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return messages.map((turn) => {
|
|
426
|
+
const truncatedContent = turn.content.map((block) => {
|
|
427
|
+
if (block.type === "tool_use") {
|
|
428
|
+
if (independentToolIds.has(block.id) || imageToolIds.has(block.id))
|
|
429
|
+
return block;
|
|
430
|
+
return {
|
|
431
|
+
...block,
|
|
432
|
+
description: undefined,
|
|
433
|
+
input: {},
|
|
434
|
+
activity: toolActivity(block, imageToolIds.has(block.id)),
|
|
435
|
+
__subagent: block.__subagent ? {
|
|
436
|
+
...block.__subagent,
|
|
437
|
+
taskDescription: undefined,
|
|
438
|
+
} : undefined,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (block.type === "tool_result" && toolNameMap.has(block.tool_use_id)
|
|
442
|
+
&& !INDEPENDENT_TOOL_NAMES.has(toolNameMap.get(block.tool_use_id))
|
|
443
|
+
&& !independentToolIds.has(block.tool_use_id)
|
|
444
|
+
&& !imageToolIds.has(block.tool_use_id)
|
|
445
|
+
&& !block.__subagent) {
|
|
446
|
+
return { ...block, content: "", _truncated: true };
|
|
447
|
+
}
|
|
448
|
+
return block;
|
|
449
|
+
});
|
|
450
|
+
return { ...turn, content: truncatedContent };
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
function getContentString(content) {
|
|
454
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
455
|
+
}
|
|
456
|
+
/** Existing transport behavior for clients that have not opted into compact tool cards. */
|
|
361
457
|
export function truncateMessagesForTransport(messages, cardDefaults, streamingTurnIndex = -1) {
|
|
362
458
|
return messages.map((turn, turnIndex) => {
|
|
363
|
-
// Never truncate the currently streaming turn
|
|
364
459
|
if (turnIndex === streamingTurnIndex)
|
|
365
460
|
return turn;
|
|
366
461
|
const toolNameMap = new Map();
|
|
367
462
|
for (const block of turn.content) {
|
|
368
|
-
if (block.type === "tool_use")
|
|
463
|
+
if (block.type === "tool_use")
|
|
369
464
|
toolNameMap.set(block.id, block.name);
|
|
370
|
-
}
|
|
371
465
|
}
|
|
372
466
|
let changed = false;
|
|
373
467
|
const truncatedContent = turn.content.map((block) => {
|
|
374
468
|
if (block.type !== "tool_result")
|
|
375
469
|
return block;
|
|
376
|
-
|
|
377
|
-
// Never truncate errors
|
|
378
|
-
if (result.is_error)
|
|
470
|
+
if (block.is_error)
|
|
379
471
|
return block;
|
|
380
|
-
const toolName = toolNameMap.get(
|
|
472
|
+
const toolName = toolNameMap.get(block.tool_use_id) ?? "";
|
|
381
473
|
if (!isToolDefaultCollapsed(toolName, cardDefaults))
|
|
382
474
|
return block;
|
|
383
|
-
|
|
384
|
-
// 抽不出 data URI(线上表现就是「读图不显示,只剩一串 base64 残文」)。图片要么
|
|
385
|
-
// 整块送达(客户端会强制展开渲染),要么由 _truncated + 懒加载单独取回。
|
|
386
|
-
if (contentHasStructuredImage(result.content))
|
|
475
|
+
if (contentHasStructuredImage(block.content))
|
|
387
476
|
return block;
|
|
388
|
-
const contentStr = getContentString(
|
|
477
|
+
const contentStr = getContentString(block.content);
|
|
389
478
|
if (contentStr.length <= TRUNCATION_THRESHOLD)
|
|
390
479
|
return block;
|
|
391
480
|
changed = true;
|
|
392
|
-
return {
|
|
393
|
-
...result,
|
|
394
|
-
content: contentStr.slice(0, SUMMARY_LENGTH) + "…",
|
|
395
|
-
_truncated: true,
|
|
396
|
-
};
|
|
481
|
+
return { ...block, content: `${contentStr.slice(0, SUMMARY_LENGTH)}…`, _truncated: true };
|
|
397
482
|
});
|
|
398
483
|
return changed ? { ...turn, content: truncatedContent } : turn;
|
|
399
484
|
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 以前每个 provider 各抄一份 handler(5 份几乎相同的 200 行);现在只有一个
|
|
9
9
|
* 恢复流程,provider 之间的差异收敛成 `StructuredResumeSpec` 里的几个字段。
|
|
10
10
|
*/
|
|
11
|
-
import type { Express } from "express";
|
|
11
|
+
import type { Express, Request } from "express";
|
|
12
12
|
import type { ProcessManager } from "./process-manager.js";
|
|
13
13
|
import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
14
14
|
import type { WandStorage } from "./storage.js";
|
|
@@ -20,6 +20,6 @@ export interface ResumeRouteDependencies {
|
|
|
20
20
|
defaultMode: ExecutionMode;
|
|
21
21
|
onSessionCreated?: (cwd: string | undefined | null) => void;
|
|
22
22
|
/** 会话快照 → 响应 DTO;由调用方注入以免这里再依赖消息截断层。 */
|
|
23
|
-
toDetailDTO: (snapshot: SessionSnapshot) => unknown;
|
|
23
|
+
toDetailDTO: (snapshot: SessionSnapshot, req: Request) => unknown;
|
|
24
24
|
}
|
|
25
25
|
export declare function registerStructuredResumeRoutes(app: Express, deps: ResumeRouteDependencies): void;
|
|
@@ -114,7 +114,7 @@ export function registerStructuredResumeRoutes(app, deps) {
|
|
|
114
114
|
...parseSessionCreationOrigin(body),
|
|
115
115
|
});
|
|
116
116
|
onSessionCreated?.(cwd);
|
|
117
|
-
res.status(201).json({ resumedClaudeSessionId: nativeId, ...toDetailDTO(snapshot) });
|
|
117
|
+
res.status(201).json({ resumedClaudeSessionId: nativeId, ...toDetailDTO(snapshot, req) });
|
|
118
118
|
}
|
|
119
119
|
catch (error) {
|
|
120
120
|
res.status(400).json({ error: getErrorMessage(error, `无法按 ${spec.label} 会话 ID 恢复会话。`) });
|