@co0ontty/wand 1.30.0 → 1.31.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/README.md +39 -2
- package/dist/claude-sdk-runner.d.ts +31 -0
- package/dist/claude-sdk-runner.js +142 -0
- package/dist/cli.js +104 -0
- package/dist/git-quick-commit.js +18 -26
- package/dist/process-manager.d.ts +7 -0
- package/dist/process-manager.js +26 -2
- package/dist/prompt-optimizer.js +17 -26
- package/dist/server-session-routes.js +27 -1
- package/dist/server.js +1 -0
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +76 -8
- package/dist/tui/attach.js +7 -8
- package/dist/tui/commands.d.ts +24 -7
- package/dist/tui/commands.js +200 -86
- package/dist/tui/index.js +8 -8
- package/dist/tui/service-panel.js +3 -4
- package/dist/types.d.ts +2 -0
- package/dist/web-ui/content/scripts.js +687 -222
- package/dist/web-ui/content/styles.css +445 -106
- package/package.json +1 -1
|
@@ -117,6 +117,50 @@ function tagSubagentBlocks(blocks, parentToolUseId, registry) {
|
|
|
117
117
|
};
|
|
118
118
|
return blocks.map((block) => ({ ...block, __subagent: stamp }));
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* 给已被 captureTaskMeta 识别为 Task/Agent 的 tool_use block 本身也盖 __subagent 章。
|
|
122
|
+
* taskId 用自己的 block.id —— 与子消息的 parent_tool_use_id(也等于这个 id)保持一致,
|
|
123
|
+
* 前端 splitTurnBySubagent 按 taskId 分组时父 Task tool_use 和 SDK 转发的子消息能合并到同一段。
|
|
124
|
+
*/
|
|
125
|
+
function stampSelfTask(blocks, registry) {
|
|
126
|
+
return blocks.map((b) => {
|
|
127
|
+
if (b.type !== "tool_use")
|
|
128
|
+
return b;
|
|
129
|
+
if (b.__subagent)
|
|
130
|
+
return b; // 已盖章不重复(防止幂等问题)
|
|
131
|
+
const meta = registry.get(b.id);
|
|
132
|
+
if (!meta && b.name !== "Task" && b.name !== "Agent")
|
|
133
|
+
return b;
|
|
134
|
+
const stamp = {
|
|
135
|
+
taskId: b.id,
|
|
136
|
+
...(meta?.agentType ? { agentType: meta.agentType } : {}),
|
|
137
|
+
...(meta?.description ? { taskDescription: meta.description } : {}),
|
|
138
|
+
};
|
|
139
|
+
return { ...b, __subagent: stamp };
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 当父 assistant 在 parentToolUseId === null 的 user turn 里收到 Task 工具的 tool_result 时,
|
|
144
|
+
* tagSubagentBlocks 不会被调用(它只在 parentToolUseId 非空时盖章)。这里按 tool_use_id
|
|
145
|
+
* 反查 registry,给这条 tool_result 单独盖章,让前端能把它归到同一个 subagent 段。
|
|
146
|
+
*/
|
|
147
|
+
function stampParentTaskResults(blocks, registry) {
|
|
148
|
+
return blocks.map((b) => {
|
|
149
|
+
if (b.type !== "tool_result")
|
|
150
|
+
return b;
|
|
151
|
+
if (b.__subagent)
|
|
152
|
+
return b;
|
|
153
|
+
const meta = registry.get(b.tool_use_id);
|
|
154
|
+
if (!meta)
|
|
155
|
+
return b;
|
|
156
|
+
const stamp = {
|
|
157
|
+
taskId: b.tool_use_id,
|
|
158
|
+
...(meta.agentType ? { agentType: meta.agentType } : {}),
|
|
159
|
+
...(meta.description ? { taskDescription: meta.description } : {}),
|
|
160
|
+
};
|
|
161
|
+
return { ...b, __subagent: stamp };
|
|
162
|
+
});
|
|
163
|
+
}
|
|
120
164
|
const STREAM_EMIT_DEBOUNCE_MS = 16;
|
|
121
165
|
/** Min interval between full saveSession() calls for an in-progress streaming turn.
|
|
122
166
|
* saveSession serializes the entire messages array, so doing it on every NDJSON
|
|
@@ -505,6 +549,7 @@ export class StructuredSessionManager {
|
|
|
505
549
|
? prepareSessionWorktree({ cwd: options.cwd, sessionId: id })
|
|
506
550
|
: null;
|
|
507
551
|
const selectedModel = options.model?.trim() || null;
|
|
552
|
+
const initialThinkingEffort = normalizeThinkingEffort(options.thinkingEffort);
|
|
508
553
|
const snapshot = {
|
|
509
554
|
id,
|
|
510
555
|
sessionKind: "structured",
|
|
@@ -541,6 +586,7 @@ export class StructuredSessionManager {
|
|
|
541
586
|
autoApprovePermissions: shouldAutoApproveForMode(options.mode),
|
|
542
587
|
approvalStats: { tool: 0, command: 0, file: 0, total: 0 },
|
|
543
588
|
selectedModel,
|
|
589
|
+
thinkingEffort: initialThinkingEffort,
|
|
544
590
|
};
|
|
545
591
|
this.sessions.set(id, snapshot);
|
|
546
592
|
this.storage.saveSession(snapshot);
|
|
@@ -999,6 +1045,11 @@ export class StructuredSessionManager {
|
|
|
999
1045
|
if (modelChoice && modelChoice !== "default") {
|
|
1000
1046
|
args.push("--model", modelChoice);
|
|
1001
1047
|
}
|
|
1048
|
+
// 思考深度 → --reasoning-effort(off → minimal,standard → low,deep → medium,max → high)
|
|
1049
|
+
const reasoningFlag = thinkingEffortToCodexFlag(session.thinkingEffort);
|
|
1050
|
+
if (reasoningFlag) {
|
|
1051
|
+
args.push("--reasoning-effort", reasoningFlag);
|
|
1052
|
+
}
|
|
1002
1053
|
if (session.claudeSessionId) {
|
|
1003
1054
|
args.push("resume", session.claudeSessionId, "-");
|
|
1004
1055
|
}
|
|
@@ -1340,6 +1391,10 @@ export class StructuredSessionManager {
|
|
|
1340
1391
|
// variadic 参数贪婪吞掉(commander 的 <tools...> 会一直吃 positional 直到
|
|
1341
1392
|
// 下一个 flag)。表现为 claude 报 "Input must be provided either through
|
|
1342
1393
|
// stdin or as a prompt argument when using --print"。
|
|
1394
|
+
//
|
|
1395
|
+
// 思考深度通过给 prompt 前置魔法词触发(think / think hard / ultrathink)。
|
|
1396
|
+
// applyThinkingEffortToPrompt 自身已经做了"用户已写过就不重复加"的保护。
|
|
1397
|
+
const effectivePrompt = applyThinkingEffortToPrompt(prompt, session.thinkingEffort);
|
|
1343
1398
|
const spawnedAt = new Date().toISOString();
|
|
1344
1399
|
const child = spawn("claude", args, {
|
|
1345
1400
|
cwd: session.cwd,
|
|
@@ -1352,13 +1407,13 @@ export class StructuredSessionManager {
|
|
|
1352
1407
|
pid: child.pid ?? null,
|
|
1353
1408
|
cwd: session.cwd,
|
|
1354
1409
|
args,
|
|
1355
|
-
prompt:
|
|
1356
|
-
promptLength:
|
|
1410
|
+
prompt: effectivePrompt.slice(0, 2048),
|
|
1411
|
+
promptLength: effectivePrompt.length,
|
|
1357
1412
|
claudeSessionId: session.claudeSessionId,
|
|
1358
1413
|
spawnedAt,
|
|
1359
1414
|
});
|
|
1360
1415
|
this.pendingChildren.set(sessionId, child);
|
|
1361
|
-
child.stdin?.end(
|
|
1416
|
+
child.stdin?.end(effectivePrompt);
|
|
1362
1417
|
const turnState = {
|
|
1363
1418
|
blocks: [],
|
|
1364
1419
|
result: "",
|
|
@@ -1555,7 +1610,7 @@ export class StructuredSessionManager {
|
|
|
1555
1610
|
captureTaskMeta(extracted.content, taskMetaRegistry);
|
|
1556
1611
|
}
|
|
1557
1612
|
const stamped = parentToolUseId === null
|
|
1558
|
-
? extracted.content
|
|
1613
|
+
? stampSelfTask(extracted.content, taskMetaRegistry)
|
|
1559
1614
|
: tagSubagentBlocks(extracted.content, parentToolUseId, taskMetaRegistry);
|
|
1560
1615
|
if (stamped.length > 0) {
|
|
1561
1616
|
upsertBlocks(msgId, stamped);
|
|
@@ -1599,7 +1654,7 @@ export class StructuredSessionManager {
|
|
|
1599
1654
|
? parsed.parent_tool_use_id
|
|
1600
1655
|
: null;
|
|
1601
1656
|
const stamped = parentToolUseId === null
|
|
1602
|
-
? collected
|
|
1657
|
+
? stampParentTaskResults(collected, taskMetaRegistry)
|
|
1603
1658
|
: tagSubagentBlocks(collected, parentToolUseId, taskMetaRegistry);
|
|
1604
1659
|
if (stamped.length > 0) {
|
|
1605
1660
|
upsertBlocks(`tool_result:${toolResultSeq++}`, stamped);
|
|
@@ -1845,6 +1900,12 @@ export class StructuredSessionManager {
|
|
|
1845
1900
|
// SDK 默认会把整个 process.env 透传给 claude 子进程;这里显式按 inheritEnv 配置组装,
|
|
1846
1901
|
// 否则关闭"继承环境变量"开关时 SDK 路径会被静默忽略。
|
|
1847
1902
|
const sdkEnv = buildChildEnv(this.config.inheritEnv !== false);
|
|
1903
|
+
// 思考深度:off → 显式禁用 thinking,其他 → 给一个固定 budget。
|
|
1904
|
+
// SDK 类型用驼峰 budgetTokens(API 层是 budget_tokens,SDK 内部已做转换)。
|
|
1905
|
+
const sdkThinkingBudget = thinkingEffortToSdkBudget(session.thinkingEffort);
|
|
1906
|
+
const sdkThinking = sdkThinkingBudget > 0
|
|
1907
|
+
? { type: "enabled", budgetTokens: sdkThinkingBudget }
|
|
1908
|
+
: { type: "disabled" };
|
|
1848
1909
|
const sdkOptions = {
|
|
1849
1910
|
cwd: session.cwd,
|
|
1850
1911
|
abortController,
|
|
@@ -1853,6 +1914,7 @@ export class StructuredSessionManager {
|
|
|
1853
1914
|
...(permPolicy.permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
|
|
1854
1915
|
...(permPolicy.allowedTools ? { allowedTools: permPolicy.allowedTools } : {}),
|
|
1855
1916
|
...(isManaged ? { disallowedTools: ["AskUserQuestion"] } : {}),
|
|
1917
|
+
thinking: sdkThinking,
|
|
1856
1918
|
includePartialMessages: true,
|
|
1857
1919
|
// 把子 agent 的 text/thinking 也转发回来,UI 才能把"被 Task 召唤来的协作者"
|
|
1858
1920
|
// 渲染成独立角色的群聊消息。关掉这个开关时只会收到子 agent 的 tool_use/tool_result,
|
|
@@ -1983,7 +2045,13 @@ export class StructuredSessionManager {
|
|
|
1983
2045
|
streaming.push(block);
|
|
1984
2046
|
}
|
|
1985
2047
|
}
|
|
1986
|
-
|
|
2048
|
+
// 流式阶段就给 Task/Agent tool_use 本身盖章,防止"先显示工具卡片几秒再跳为
|
|
2049
|
+
// handoff 行"的闪烁。content_block_start 阶段就有 name=Task/Agent,
|
|
2050
|
+
// stampSelfTask 据此即可命中;agentType 字段藏在 input 里,delta 累计后再由
|
|
2051
|
+
// 后续 captureTaskMeta 回填 registry,下次 rebuild 自动补上更完整的 stamp。
|
|
2052
|
+
captureTaskMeta(streaming, taskMetaRegistry);
|
|
2053
|
+
const stampedStreaming = stampSelfTask(streaming, taskMetaRegistry);
|
|
2054
|
+
return [...finalizedBlocks, ...stampedStreaming];
|
|
1987
2055
|
};
|
|
1988
2056
|
const syncSnapshot = () => {
|
|
1989
2057
|
const current = this.sessions.get(sessionId);
|
|
@@ -2095,7 +2163,7 @@ export class StructuredSessionManager {
|
|
|
2095
2163
|
const parentToolUseId = assistantMsg.parent_tool_use_id ?? null;
|
|
2096
2164
|
if (parentToolUseId === null) {
|
|
2097
2165
|
captureTaskMeta(extracted.content, taskMetaRegistry);
|
|
2098
|
-
finalizedBlocks.push(...extracted.content);
|
|
2166
|
+
finalizedBlocks.push(...stampSelfTask(extracted.content, taskMetaRegistry));
|
|
2099
2167
|
}
|
|
2100
2168
|
else {
|
|
2101
2169
|
finalizedBlocks.push(...tagSubagentBlocks(extracted.content, parentToolUseId, taskMetaRegistry));
|
|
@@ -2150,7 +2218,7 @@ export class StructuredSessionManager {
|
|
|
2150
2218
|
}
|
|
2151
2219
|
}
|
|
2152
2220
|
if (parentToolUseId === null) {
|
|
2153
|
-
finalizedBlocks.push(...collected);
|
|
2221
|
+
finalizedBlocks.push(...stampParentTaskResults(collected, taskMetaRegistry));
|
|
2154
2222
|
}
|
|
2155
2223
|
else {
|
|
2156
2224
|
finalizedBlocks.push(...tagSubagentBlocks(collected, parentToolUseId, taskMetaRegistry));
|
package/dist/tui/attach.js
CHANGED
|
@@ -230,14 +230,13 @@ export function startAttachTui(deps) {
|
|
|
230
230
|
layout.showToast("服务已安装,按 Shift+S 卸载", "warn", 2500);
|
|
231
231
|
return;
|
|
232
232
|
}
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
233
|
+
const isRoot = typeof process.getuid === "function" ? process.getuid() === 0 : false;
|
|
234
|
+
const body = process.platform === "linux"
|
|
235
|
+
? `将写入 /etc/systemd/system/wand.service,systemctl enable --now,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root,可以 Ctrl+C 退出 TUI 后跑 sudo wand service:install。"}`
|
|
236
|
+
: process.platform === "darwin"
|
|
237
|
+
? `将写入 /Library/LaunchDaemons/com.wand.web.plist,launchctl load,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root,退出 TUI 跑 sudo wand service:install。"}`
|
|
238
|
+
: "当前平台暂不支持。";
|
|
239
|
+
const ok = await layout.confirm({ title: "注册为系统服务", body });
|
|
241
240
|
if (!ok)
|
|
242
241
|
return;
|
|
243
242
|
const r = await runOffMicrotask(() => installService({ configPath: deps.configPath }));
|
package/dist/tui/commands.d.ts
CHANGED
|
@@ -36,12 +36,29 @@ export declare function checkUpdate(currentVersion: string): UpdateInfo;
|
|
|
36
36
|
export declare function installUpdate(): CommandResult;
|
|
37
37
|
export declare function openInBrowser(url: string): CommandResult;
|
|
38
38
|
export declare function copyToClipboard(text: string): CommandResult;
|
|
39
|
+
/**
|
|
40
|
+
* 服务安装的作用域:
|
|
41
|
+
* - "system" = Linux 写 /etc/systemd/system/wand.service;macOS 写 /Library/LaunchDaemons/
|
|
42
|
+
* 需要 root,开机自启,所有用户可用,不依赖 login session。
|
|
43
|
+
* - "user" = Linux 写 ~/.config/systemd/user/wand.service;macOS 写 ~/Library/LaunchAgents/
|
|
44
|
+
* 不要 root,登出会被回收(除非 loginctl enable-linger)。
|
|
45
|
+
*
|
|
46
|
+
* 默认 system。
|
|
47
|
+
*/
|
|
48
|
+
export type ServiceScope = "system" | "user";
|
|
49
|
+
export declare const DEFAULT_SERVICE_SCOPE: ServiceScope;
|
|
39
50
|
export interface ServiceContext {
|
|
40
51
|
configPath: string;
|
|
41
52
|
/** wand 可执行文件路径。优先使用 process.argv[1],回退到 which wand。 */
|
|
42
53
|
wandBin?: string;
|
|
54
|
+
/** 显式指定作用域。不传走 DEFAULT_SERVICE_SCOPE。 */
|
|
55
|
+
scope?: ServiceScope;
|
|
56
|
+
}
|
|
57
|
+
export interface ServiceOpts {
|
|
58
|
+
/** 不传 = 自动检测已装的那个;都没装就用 default。 */
|
|
59
|
+
scope?: ServiceScope;
|
|
43
60
|
}
|
|
44
|
-
export declare function isServiceInstalled(): boolean;
|
|
61
|
+
export declare function isServiceInstalled(scope?: ServiceScope): boolean;
|
|
45
62
|
export interface ServiceStatus {
|
|
46
63
|
/** 是否已安装服务文件。 */
|
|
47
64
|
installed: boolean;
|
|
@@ -54,11 +71,11 @@ export interface ServiceStatus {
|
|
|
54
71
|
/** 平台。 */
|
|
55
72
|
platform: NodeJS.Platform;
|
|
56
73
|
}
|
|
57
|
-
export declare function serviceStatus(): ServiceStatus;
|
|
58
|
-
export declare function serviceStart(): CommandResult;
|
|
59
|
-
export declare function serviceStop(): CommandResult;
|
|
60
|
-
export declare function serviceRestart(): CommandResult;
|
|
74
|
+
export declare function serviceStatus(opts?: ServiceOpts): ServiceStatus;
|
|
75
|
+
export declare function serviceStart(opts?: ServiceOpts): CommandResult;
|
|
76
|
+
export declare function serviceStop(opts?: ServiceOpts): CommandResult;
|
|
77
|
+
export declare function serviceRestart(opts?: ServiceOpts): CommandResult;
|
|
61
78
|
/** 取最近 N 行服务日志。 */
|
|
62
|
-
export declare function serviceLogs(lines?: number): CommandResult;
|
|
79
|
+
export declare function serviceLogs(lines?: number, opts?: ServiceOpts): CommandResult;
|
|
63
80
|
export declare function installService(ctx: ServiceContext): CommandResult;
|
|
64
|
-
export declare function uninstallService(): CommandResult;
|
|
81
|
+
export declare function uninstallService(opts?: ServiceOpts): CommandResult;
|