@co0ontty/wand 1.68.0 → 1.68.1-beta.g0c52991
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 +4 -4
- package/dist/config.js +7 -1
- package/dist/structured-session-manager.d.ts +6 -0
- package/dist/structured-session-manager.js +32 -9
- package/dist/web-ui/content/scripts.js +34 -34
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-06-
|
|
4
|
-
"version": "1.68.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "0c529913d424689b382ee54369899e84aec3be94",
|
|
3
|
+
"builtAt": "2026-06-16T23:28:18.438Z",
|
|
4
|
+
"version": "1.68.1-beta.g0c52991",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
package/dist/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
|
+
import { isRunningAsRoot } from "./env-utils.js";
|
|
6
7
|
const DEFAULT_CONFIG_DIR = ".wand";
|
|
7
8
|
const DEFAULT_CONFIG_FILE = "config.json";
|
|
8
9
|
/**
|
|
@@ -34,7 +35,12 @@ export const defaultConfig = () => ({
|
|
|
34
35
|
port: 8443,
|
|
35
36
|
https: false,
|
|
36
37
|
password: "change-me",
|
|
37
|
-
|
|
38
|
+
// 非 root 启动时才有资格用 Claude 的 permission-bypass(root 会被 Claude CLI 拒绝),
|
|
39
|
+
// 所以这种环境下把默认执行模式抬到「托管」——开箱即得自动确认权限的全自主体验。
|
|
40
|
+
// root 启动则保守回落到「default」(托管在 root 下也只能降级成 acceptEdits)。
|
|
41
|
+
// 注意:defaultMode 是偏好字段,只存 SQLite、不写 config.json(见 stripPreferenceFields),
|
|
42
|
+
// 这里仅作为「用户从未在设置里显式选过模式」时的回落值——显式选择始终优先。
|
|
43
|
+
defaultMode: isRunningAsRoot() ? "default" : "managed",
|
|
38
44
|
shell: process.env.SHELL || "/bin/bash",
|
|
39
45
|
defaultCwd: process.cwd(),
|
|
40
46
|
startupCommands: [],
|
|
@@ -48,6 +48,12 @@ export declare class StructuredSessionManager {
|
|
|
48
48
|
*/
|
|
49
49
|
private readonly pendingSdkQueries;
|
|
50
50
|
private readonly interruptedWith;
|
|
51
|
+
/**
|
|
52
|
+
* 用户主动点了「停止」的会话。异步收尾(claude -p / codex 的 close、SDK 的 abort)
|
|
53
|
+
* 据此跳过"结构化会话执行失败"路径——主动停止不是失败,按正常 idle 收尾、保留历史内容。
|
|
54
|
+
* 用 Set.delete 消费:读取的同时清除,下一轮真失败不会被旧标记误抑制。
|
|
55
|
+
*/
|
|
56
|
+
private readonly userStopped;
|
|
51
57
|
/**
|
|
52
58
|
* Sessions where the current interrupt is a "queue promote" (用户从排队条点了「立即」
|
|
53
59
|
* 把队首插队到 now)。退出处理三个分支默认会把 queuedMessages 清空——因为常规的
|
|
@@ -394,6 +394,12 @@ export class StructuredSessionManager {
|
|
|
394
394
|
*/
|
|
395
395
|
pendingSdkQueries = new Map();
|
|
396
396
|
interruptedWith = new Map();
|
|
397
|
+
/**
|
|
398
|
+
* 用户主动点了「停止」的会话。异步收尾(claude -p / codex 的 close、SDK 的 abort)
|
|
399
|
+
* 据此跳过"结构化会话执行失败"路径——主动停止不是失败,按正常 idle 收尾、保留历史内容。
|
|
400
|
+
* 用 Set.delete 消费:读取的同时清除,下一轮真失败不会被旧标记误抑制。
|
|
401
|
+
*/
|
|
402
|
+
userStopped = new Set();
|
|
397
403
|
/**
|
|
398
404
|
* Sessions where the current interrupt is a "queue promote" (用户从排队条点了「立即」
|
|
399
405
|
* 把队首插队到 now)。退出处理三个分支默认会把 queuedMessages 清空——因为常规的
|
|
@@ -967,6 +973,9 @@ export class StructuredSessionManager {
|
|
|
967
973
|
const session = this.requireSession(id);
|
|
968
974
|
this.interruptedWith.delete(id);
|
|
969
975
|
this.preserveQueueOnInterrupt.delete(id);
|
|
976
|
+
// 标记本会话为「用户主动停止」,让被 kill 的子进程 close / SDK abort 收尾时
|
|
977
|
+
// 走正常 idle 收尾而不是失败路径。
|
|
978
|
+
this.userStopped.add(id);
|
|
970
979
|
const child = this.pendingChildren.get(id);
|
|
971
980
|
if (child) {
|
|
972
981
|
child.kill();
|
|
@@ -984,22 +993,27 @@ export class StructuredSessionManager {
|
|
|
984
993
|
sdkAbort.abort();
|
|
985
994
|
this.pendingSdkAbort.delete(id);
|
|
986
995
|
}
|
|
987
|
-
|
|
996
|
+
// 主动停止只是取消「当前回合」,结构化会话本身并没有结束——置为 idle 而非 stopped。
|
|
997
|
+
// 这样前端不会进入"会话已结束/恢复会话"终止态,输入框保持可用,直接展示历史内容。
|
|
998
|
+
const cancelled = {
|
|
988
999
|
...session,
|
|
989
|
-
status: "
|
|
990
|
-
|
|
1000
|
+
status: "idle",
|
|
1001
|
+
exitCode: null,
|
|
1002
|
+
endedAt: null,
|
|
991
1003
|
pendingEscalation: null,
|
|
992
1004
|
permissionBlocked: false,
|
|
993
1005
|
structuredState: {
|
|
994
1006
|
...(session.structuredState ?? defaultStructuredState(session.provider ?? "claude", session.runner)),
|
|
995
1007
|
inFlight: false,
|
|
996
1008
|
activeRequestId: null,
|
|
1009
|
+
lastError: null,
|
|
997
1010
|
},
|
|
998
1011
|
};
|
|
999
|
-
this.sessions.set(id,
|
|
1000
|
-
this.storage.saveSession(
|
|
1001
|
-
|
|
1002
|
-
|
|
1012
|
+
this.sessions.set(id, cancelled);
|
|
1013
|
+
this.storage.saveSession(cancelled);
|
|
1014
|
+
// 仍发 "ended" 事件让各端停掉"回复中"指示 / 灵动岛,但携带的 status 是 idle。
|
|
1015
|
+
this.emitStructuredSnapshot(cancelled, "ended");
|
|
1016
|
+
return cancelled;
|
|
1003
1017
|
}
|
|
1004
1018
|
delete(id) {
|
|
1005
1019
|
const child = this.pendingChildren.get(id);
|
|
@@ -1148,6 +1162,7 @@ export class StructuredSessionManager {
|
|
|
1148
1162
|
// Streaming codex exec --json execution
|
|
1149
1163
|
// ---------------------------------------------------------------------------
|
|
1150
1164
|
runCodexStreaming(sessionId, session, prompt) {
|
|
1165
|
+
this.userStopped.delete(sessionId);
|
|
1151
1166
|
return new Promise((resolve, reject) => {
|
|
1152
1167
|
const args = this.buildCodexArgs(session);
|
|
1153
1168
|
const spawnedAt = new Date().toISOString();
|
|
@@ -1351,10 +1366,12 @@ export class StructuredSessionManager {
|
|
|
1351
1366
|
// 主动中断时(interruptedWith 里有新消息),不走失败路径
|
|
1352
1367
|
const interruptedByUser = this.interruptedWith.has(sessionId);
|
|
1353
1368
|
const interruptPrompt = this.interruptedWith.get(sessionId);
|
|
1369
|
+
// 用户点「停止」kill 掉 codex 后会非零退出,但这不是失败——按正常 idle 收尾。
|
|
1370
|
+
const userStopped = this.userStopped.delete(sessionId);
|
|
1354
1371
|
// codex 把模型/网络/沙箱等错误写到 stdout 的 NDJSON 流(type: error / turn.failed),
|
|
1355
1372
|
// 而不是 stderr。我们以 turn.failed 的 message 为准,其次是最后一个 error 事件。
|
|
1356
1373
|
const codexFailed = codexTurnFailed !== null;
|
|
1357
|
-
if ((codexFailed || (code !== 0 && code !== null) || signal) && !interruptedByUser) {
|
|
1374
|
+
if ((codexFailed || (code !== 0 && code !== null) || signal) && !interruptedByUser && !userStopped) {
|
|
1358
1375
|
const errorText = this.formatStructuredExitError("codex exec", code, signal, {
|
|
1359
1376
|
stderr,
|
|
1360
1377
|
primary: codexTurnFailed,
|
|
@@ -1448,6 +1465,7 @@ export class StructuredSessionManager {
|
|
|
1448
1465
|
* outside CWD). stdin is always "ignore" — no ACP bidirectional control.
|
|
1449
1466
|
*/
|
|
1450
1467
|
runClaudeStreaming(sessionId, session, prompt) {
|
|
1468
|
+
this.userStopped.delete(sessionId);
|
|
1451
1469
|
return new Promise((resolve, reject) => {
|
|
1452
1470
|
const args = ["-p", "--verbose", "--output-format", "stream-json"];
|
|
1453
1471
|
// 权限策略:决策规则与 SDK runner 共享 derivePermissionPolicy(),CLI 这边把
|
|
@@ -1909,8 +1927,10 @@ export class StructuredSessionManager {
|
|
|
1909
1927
|
// 可能以非零 exit code 退出(内部 handler 调了 exit(1))。这种情况属于正常
|
|
1910
1928
|
// 中断流程,不应走失败路径——后续 interruptedWith 逻辑会发送新消息。
|
|
1911
1929
|
const interruptedByUser = this.interruptedWith.has(sessionId);
|
|
1930
|
+
// 用户点「停止」kill 掉 claude -p 后会非零退出,但这不是失败——按正常 idle 收尾。
|
|
1931
|
+
const userStopped = this.userStopped.delete(sessionId);
|
|
1912
1932
|
const failedExit = (code !== null && code !== 0) || signal !== null;
|
|
1913
|
-
if (failedExit && !interruptedByUser) {
|
|
1933
|
+
if (failedExit && !interruptedByUser && !userStopped) {
|
|
1914
1934
|
const errorText = this.formatStructuredExitError("claude -p", code, signal, {
|
|
1915
1935
|
stderr,
|
|
1916
1936
|
// claude -p 没有 codex 那种独立的 turn.failed 事件,所以 primary 留空;
|
|
@@ -2060,6 +2080,7 @@ export class StructuredSessionManager {
|
|
|
2060
2080
|
* SDKAssistantMessage with the authoritative complete content.
|
|
2061
2081
|
*/
|
|
2062
2082
|
async runClaudeSdkStreaming(sessionId, session, prompt) {
|
|
2083
|
+
this.userStopped.delete(sessionId);
|
|
2063
2084
|
const abortController = new AbortController();
|
|
2064
2085
|
this.pendingSdkAbort.set(sessionId, abortController);
|
|
2065
2086
|
const isManaged = session.mode === "managed";
|
|
@@ -2450,6 +2471,8 @@ export class StructuredSessionManager {
|
|
|
2450
2471
|
this.pendingSdkAbort.delete(sessionId);
|
|
2451
2472
|
this.pendingSdkQueries.delete(sessionId);
|
|
2452
2473
|
this.lastStreamSaveAt.delete(sessionId);
|
|
2474
|
+
// SDK abort 收尾本就走 idle(不产生失败串),这里只是顺手清掉用户停止标记避免泄漏。
|
|
2475
|
+
this.userStopped.delete(sessionId);
|
|
2453
2476
|
if (emitTimer)
|
|
2454
2477
|
clearTimeout(emitTimer);
|
|
2455
2478
|
flushEmit();
|