@co0ontty/wand 2.13.0 → 3.0.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/cert.js +0 -3
- package/dist/claude-pty-bridge.d.ts +1 -9
- package/dist/claude-pty-bridge.js +5 -22
- package/dist/middleware/path-safety.js +1 -4
- package/dist/npm-update-utils.d.ts +2 -1
- package/dist/npm-update-utils.js +8 -5
- package/dist/pidfile.d.ts +0 -2
- package/dist/pidfile.js +0 -18
- package/dist/process-manager.d.ts +1 -8
- package/dist/process-manager.js +3 -86
- package/dist/server-session-routes.d.ts +1 -3
- package/dist/server-session-routes.js +19 -32
- package/dist/server.js +5 -21
- package/dist/storage.d.ts +0 -1
- package/dist/storage.js +0 -13
- package/dist/structured-session-manager.d.ts +0 -1
- package/dist/structured-session-manager.js +0 -2
- package/dist/update-helper.js +31 -17
- package/dist/upload-routes.js +2 -4
- package/dist/version-utils.js +14 -10
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/ws-broadcast.d.ts +0 -1
- package/dist/ws-broadcast.js +0 -2
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "
|
|
2
|
+
"commit": "433a1530c708f09aa78cf17e229b0c372f63b246",
|
|
3
|
+
"builtAt": "2026-07-12T12:10:18.456Z",
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/cert.js
CHANGED
|
@@ -9,9 +9,6 @@ function getCertificatePaths(configDir) {
|
|
|
9
9
|
certPath: path.join(configDir, "server.crt")
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
function certificatesExist(paths) {
|
|
13
|
-
return existsSync(paths.keyPath) && existsSync(paths.certPath);
|
|
14
|
-
}
|
|
15
12
|
function readPair(keyPath, certPath) {
|
|
16
13
|
try {
|
|
17
14
|
if (!existsSync(keyPath) || !existsSync(certPath))
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. Structured messages for chat view (parsed)
|
|
8
8
|
*/
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
10
|
-
import type {
|
|
10
|
+
import type { ConversationTurn, EscalationScope } from "./types.js";
|
|
11
11
|
interface PermissionState {
|
|
12
12
|
/** Rolling window for prompt detection */
|
|
13
13
|
window: string;
|
|
@@ -48,8 +48,6 @@ export interface ClaudePtyBridgeOptions {
|
|
|
48
48
|
isClaudeCommand?: boolean;
|
|
49
49
|
/** Whether to auto-approve permission prompts */
|
|
50
50
|
autoApprove?: boolean;
|
|
51
|
-
/** Approval policy for permission handling */
|
|
52
|
-
approvalPolicy?: ApprovalPolicy;
|
|
53
51
|
/** PTY write function for sending approval input */
|
|
54
52
|
ptyWrite?: (input: string) => void;
|
|
55
53
|
}
|
|
@@ -63,7 +61,6 @@ export interface ClaudePtyBridgeOptions {
|
|
|
63
61
|
* - "permission.prompt" - Permission request detected
|
|
64
62
|
* - "permission.resolved" - Permission resolved
|
|
65
63
|
* - "session.id" - Claude session ID captured
|
|
66
|
-
* - "task" - Task info update
|
|
67
64
|
* - "ended" - Session ended
|
|
68
65
|
*/
|
|
69
66
|
export declare class ClaudePtyBridge extends EventEmitter {
|
|
@@ -74,12 +71,8 @@ export declare class ClaudePtyBridge extends EventEmitter {
|
|
|
74
71
|
private permissionState;
|
|
75
72
|
private sessionIdWindow;
|
|
76
73
|
private claudeSessionId;
|
|
77
|
-
private currentTask;
|
|
78
|
-
private taskDebounceTimer;
|
|
79
|
-
private lastEmittedTask;
|
|
80
74
|
private isClaudeCommand;
|
|
81
75
|
private autoApprove;
|
|
82
|
-
private approvalPolicy;
|
|
83
76
|
private ptyWrite;
|
|
84
77
|
/** Set to true once onExit() has been called; guards against post-exit method calls */
|
|
85
78
|
private _exited;
|
|
@@ -112,7 +105,6 @@ export declare class ClaudePtyBridge extends EventEmitter {
|
|
|
112
105
|
getClaudeSessionId(): string | null;
|
|
113
106
|
isPermissionBlocked(): boolean;
|
|
114
107
|
getPermissionState(): PermissionState;
|
|
115
|
-
getCurrentTask(): TaskData | null;
|
|
116
108
|
/**
|
|
117
109
|
* Set the PTY write function for sending approval input.
|
|
118
110
|
*/
|
|
@@ -40,7 +40,6 @@ function normalizePtyOutput(value) {
|
|
|
40
40
|
* - "permission.prompt" - Permission request detected
|
|
41
41
|
* - "permission.resolved" - Permission resolved
|
|
42
42
|
* - "session.id" - Claude session ID captured
|
|
43
|
-
* - "task" - Task info update
|
|
44
43
|
* - "ended" - Session ended
|
|
45
44
|
*/
|
|
46
45
|
export class ClaudePtyBridge extends EventEmitter {
|
|
@@ -55,14 +54,9 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
55
54
|
// Session ID capture
|
|
56
55
|
sessionIdWindow;
|
|
57
56
|
claudeSessionId = null;
|
|
58
|
-
// Task tracking
|
|
59
|
-
currentTask = null;
|
|
60
|
-
taskDebounceTimer = null;
|
|
61
|
-
lastEmittedTask = null;
|
|
62
57
|
// Options
|
|
63
58
|
isClaudeCommand;
|
|
64
59
|
autoApprove;
|
|
65
|
-
approvalPolicy;
|
|
66
60
|
ptyWrite;
|
|
67
61
|
/** Set to true once onExit() has been called; guards against post-exit method calls */
|
|
68
62
|
_exited = false;
|
|
@@ -84,7 +78,6 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
84
78
|
this.rawOutput = options.initialOutput ?? "";
|
|
85
79
|
this.isClaudeCommand = options.isClaudeCommand ?? false;
|
|
86
80
|
this.autoApprove = options.autoApprove ?? false;
|
|
87
|
-
this.approvalPolicy = options.approvalPolicy ?? "ask-every-time";
|
|
88
81
|
this.ptyWrite = options.ptyWrite ?? null;
|
|
89
82
|
this.chatState = {
|
|
90
83
|
phase: "idle",
|
|
@@ -200,11 +193,6 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
200
193
|
this.chatState.echoSkipped = true; // Force skip echo on exit
|
|
201
194
|
this.finalizeResponse();
|
|
202
195
|
}
|
|
203
|
-
// Clear task debounce timer
|
|
204
|
-
if (this.taskDebounceTimer) {
|
|
205
|
-
clearTimeout(this.taskDebounceTimer);
|
|
206
|
-
this.taskDebounceTimer = null;
|
|
207
|
-
}
|
|
208
196
|
// Flush pending chat emit
|
|
209
197
|
if (this.chatEmitTimer) {
|
|
210
198
|
clearTimeout(this.chatEmitTimer);
|
|
@@ -240,9 +228,6 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
240
228
|
getPermissionState() {
|
|
241
229
|
return this.permissionState;
|
|
242
230
|
}
|
|
243
|
-
getCurrentTask() {
|
|
244
|
-
return this.currentTask;
|
|
245
|
-
}
|
|
246
231
|
/**
|
|
247
232
|
* Set the PTY write function for sending approval input.
|
|
248
233
|
*/
|
|
@@ -445,11 +430,9 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
445
430
|
if (!blocked && !this.permissionState.isBlocked && this.autoApprove) {
|
|
446
431
|
const { score, matched } = scorePermissionLikelihood(normalized);
|
|
447
432
|
if (score >= FALLBACK_SCORE_THRESHOLD) {
|
|
448
|
-
const target = this.extractPermissionTarget(normalized);
|
|
449
|
-
const scope = this.inferScope(normalized, target);
|
|
450
433
|
const lines = normalized.split("\n");
|
|
451
434
|
const tailText = lines.slice(-8).join("\n");
|
|
452
|
-
this.scheduleFallbackAutoApprove(
|
|
435
|
+
this.scheduleFallbackAutoApprove({ score, matched, text: tailText });
|
|
453
436
|
return;
|
|
454
437
|
}
|
|
455
438
|
}
|
|
@@ -460,7 +443,7 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
460
443
|
if (prompt !== this.permissionState.lastPrompt) {
|
|
461
444
|
// New permission prompt while already blocked — update and re-process
|
|
462
445
|
const target = this.extractPermissionTarget(normalized);
|
|
463
|
-
const scope = this.inferScope(normalized
|
|
446
|
+
const scope = this.inferScope(normalized);
|
|
464
447
|
this.permissionState.lastPrompt = prompt;
|
|
465
448
|
this.permissionState.lastScope = scope;
|
|
466
449
|
this.permissionState.lastTarget = target ?? null;
|
|
@@ -484,7 +467,7 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
484
467
|
if (blocked) {
|
|
485
468
|
const prompt = this.extractPromptText(normalized);
|
|
486
469
|
const target = this.extractPermissionTarget(normalized);
|
|
487
|
-
const scope = this.inferScope(normalized
|
|
470
|
+
const scope = this.inferScope(normalized);
|
|
488
471
|
this.permissionState.lastPrompt = prompt;
|
|
489
472
|
this.permissionState.lastScope = scope;
|
|
490
473
|
this.permissionState.lastTarget = target ?? null;
|
|
@@ -569,7 +552,7 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
569
552
|
* Schedule a fallback auto-approve with false-positive verification.
|
|
570
553
|
* Similar to scheduleAutoApprove but sets up post-approve monitoring.
|
|
571
554
|
*/
|
|
572
|
-
scheduleFallbackAutoApprove(
|
|
555
|
+
scheduleFallbackAutoApprove(context) {
|
|
573
556
|
const now = Date.now();
|
|
574
557
|
if (now - this.permissionState.lastAutoConfirmAt < 500)
|
|
575
558
|
return;
|
|
@@ -702,7 +685,7 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
702
685
|
|| normalized.match(/execute\s+([^,.\n]+)/i);
|
|
703
686
|
return match?.[1]?.trim();
|
|
704
687
|
}
|
|
705
|
-
inferScope(normalized
|
|
688
|
+
inferScope(normalized) {
|
|
706
689
|
const lower = normalized.toLowerCase();
|
|
707
690
|
if (lower.includes("write") || lower.includes("redirection") || lower.includes("output redirection")) {
|
|
708
691
|
return "write_file";
|
|
@@ -9,10 +9,7 @@ export function isPathWithinBase(targetPath, basePath) {
|
|
|
9
9
|
const BLOCKED_FOLDER_PATHS = ["/etc", "/root", "/boot"];
|
|
10
10
|
/** Check if targetPath is inside any blocked system folder. */
|
|
11
11
|
export function isBlockedFolderPath(targetPath) {
|
|
12
|
-
return BLOCKED_FOLDER_PATHS.some((blockedPath) =>
|
|
13
|
-
const relativePath = path.relative(blockedPath, targetPath);
|
|
14
|
-
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
15
|
-
});
|
|
12
|
+
return BLOCKED_FOLDER_PATHS.some((blockedPath) => isPathWithinBase(targetPath, blockedPath));
|
|
16
13
|
}
|
|
17
14
|
/** Expand shell-style home shortcuts accepted by the UI. */
|
|
18
15
|
export function expandHomePath(inputPath) {
|
|
@@ -58,8 +58,9 @@ export declare function requiredUpdateFreeBytes(currentInstallBytes: number): nu
|
|
|
58
58
|
* @param pkg 包名带版本,例如 `@co0ontty/wand@latest`
|
|
59
59
|
* @param timeoutMs 单次 npm 调用超时
|
|
60
60
|
* @param log 可选 logger,用来把过程写入控制台或前端日志
|
|
61
|
+
* @returns 与本次安装使用同一个 npm root 校验出的 dist/cli.js 绝对路径
|
|
61
62
|
*/
|
|
62
|
-
export declare function installPackageGloballyAsync(pkg: string, timeoutMs: number, log?: (line: string) => void): Promise<
|
|
63
|
+
export declare function installPackageGloballyAsync(pkg: string, timeoutMs: number, log?: (line: string) => void): Promise<string>;
|
|
63
64
|
/**
|
|
64
65
|
* 同步版本,给 TUI installUpdate 用。
|
|
65
66
|
*
|
package/dist/npm-update-utils.js
CHANGED
|
@@ -402,6 +402,7 @@ function assertGlobalWandInstallComplete() {
|
|
|
402
402
|
if (!result.ok) {
|
|
403
403
|
throw new Error(result.message);
|
|
404
404
|
}
|
|
405
|
+
return path.join(result.packageDir, "dist", "cli.js");
|
|
405
406
|
}
|
|
406
407
|
function createGlobalInstallBackup(note) {
|
|
407
408
|
const packageDir = getGlobalPackageDir();
|
|
@@ -641,6 +642,7 @@ function releaseGlobalUpdateLock(lock) {
|
|
|
641
642
|
* @param pkg 包名带版本,例如 `@co0ontty/wand@latest`
|
|
642
643
|
* @param timeoutMs 单次 npm 调用超时
|
|
643
644
|
* @param log 可选 logger,用来把过程写入控制台或前端日志
|
|
645
|
+
* @returns 与本次安装使用同一个 npm root 校验出的 dist/cli.js 绝对路径
|
|
644
646
|
*/
|
|
645
647
|
export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
|
|
646
648
|
const note = (line) => {
|
|
@@ -658,9 +660,9 @@ export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
|
|
|
658
660
|
}
|
|
659
661
|
try {
|
|
660
662
|
await npmInstallGlobalAsync(pkg, timeoutMs);
|
|
661
|
-
assertGlobalWandInstallComplete();
|
|
663
|
+
const installedCli = assertGlobalWandInstallComplete();
|
|
662
664
|
success = true;
|
|
663
|
-
return;
|
|
665
|
+
return installedCli;
|
|
664
666
|
}
|
|
665
667
|
catch (error) {
|
|
666
668
|
const msg = getErrorMessage(error);
|
|
@@ -675,9 +677,9 @@ export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
|
|
|
675
677
|
cleanupNpmLeftovers();
|
|
676
678
|
try {
|
|
677
679
|
await npmInstallGlobalAsync(pkg, timeoutMs);
|
|
678
|
-
assertGlobalWandInstallComplete();
|
|
680
|
+
const installedCli = assertGlobalWandInstallComplete();
|
|
679
681
|
success = true;
|
|
680
|
-
return;
|
|
682
|
+
return installedCli;
|
|
681
683
|
}
|
|
682
684
|
catch (retryError) {
|
|
683
685
|
const retryMsg = getErrorMessage(retryError);
|
|
@@ -699,8 +701,9 @@ export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
|
|
|
699
701
|
}
|
|
700
702
|
cleanupNpmLeftovers();
|
|
701
703
|
await npmInstallGlobalAsync(pkg, timeoutMs, ["--force"]);
|
|
702
|
-
assertGlobalWandInstallComplete();
|
|
704
|
+
const installedCli = assertGlobalWandInstallComplete();
|
|
703
705
|
success = true;
|
|
706
|
+
return installedCli;
|
|
704
707
|
}
|
|
705
708
|
finally {
|
|
706
709
|
try {
|
package/dist/pidfile.d.ts
CHANGED
|
@@ -34,5 +34,3 @@ export declare function readPidfile(configPath: string): PidInfo | null;
|
|
|
34
34
|
export declare function isPidAlive(pid: number): boolean;
|
|
35
35
|
export declare function removePidfile(configPath: string): void;
|
|
36
36
|
export declare function removeSocketFile(configPath: string): void;
|
|
37
|
-
/** 读取 pidfile 并校验进程是否还活着。Stale 文件会自动清理。 */
|
|
38
|
-
export declare function readLiveInstance(configPath: string): PidInfo | null;
|
package/dist/pidfile.js
CHANGED
|
@@ -98,24 +98,6 @@ export function removeSocketFile(configPath) {
|
|
|
98
98
|
/* noop */
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
/** 读取 pidfile 并校验进程是否还活着。Stale 文件会自动清理。 */
|
|
102
|
-
export function readLiveInstance(configPath) {
|
|
103
|
-
const info = readPidfile(configPath);
|
|
104
|
-
if (!info)
|
|
105
|
-
return null;
|
|
106
|
-
if (info.pid === process.pid)
|
|
107
|
-
return null; // 自身(防御性)
|
|
108
|
-
if (!isPidAlive(info.pid)) {
|
|
109
|
-
// stale,顺手清理
|
|
110
|
-
removePidfile(configPath);
|
|
111
|
-
removeSocketFile(configPath);
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
// 进程活着但 socket 不在 → 多半是异常崩溃后被某种监控拉起。视为不可用。
|
|
115
|
-
if (info.socket && !existsSync(info.socket))
|
|
116
|
-
return null;
|
|
117
|
-
return info;
|
|
118
|
-
}
|
|
119
101
|
function applyConfigDirOwnership(filePath, configPath, mode) {
|
|
120
102
|
try {
|
|
121
103
|
const owner = statSync(path.dirname(configPath));
|
|
@@ -2,11 +2,6 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import { WandStorage } from "./storage.js";
|
|
3
3
|
import { ExecutionMode, ProcessEventHandler, SessionProvider, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
|
|
4
4
|
export type { ProcessEvent, ProcessEventHandler } from "./types.js";
|
|
5
|
-
/** Human-readable task information for the UI */
|
|
6
|
-
export interface TaskInfo {
|
|
7
|
-
title: string;
|
|
8
|
-
tool?: string;
|
|
9
|
-
}
|
|
10
5
|
export declare class SessionInputError extends Error {
|
|
11
6
|
readonly code: "SESSION_NOT_FOUND" | "SESSION_NOT_RUNNING" | "SESSION_NO_PTY";
|
|
12
7
|
readonly sessionId: string;
|
|
@@ -53,7 +48,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
53
48
|
private orphanRecoveredCount;
|
|
54
49
|
private readonly topicRequests;
|
|
55
50
|
constructor(config: WandConfig, storage: WandStorage, configDir?: string);
|
|
56
|
-
on(
|
|
51
|
+
on(_event: "process", listener: ProcessEventHandler): this;
|
|
57
52
|
/** 启动时被识别为孤儿 PTY 并标记为 exited 的旧会话数量(仅用于启动摘要展示)。 */
|
|
58
53
|
getOrphanRecoveredCount(): number;
|
|
59
54
|
private emitEvent;
|
|
@@ -110,8 +105,6 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
110
105
|
*/
|
|
111
106
|
setSessionMode(id: string, mode: ExecutionMode): SessionSnapshot;
|
|
112
107
|
sendInput(id: string, input: string, view?: "chat" | "terminal", shortcutKey?: string): SessionSnapshot;
|
|
113
|
-
/** Emit a task event for a session, debounced to avoid flooding */
|
|
114
|
-
private emitTask;
|
|
115
108
|
resize(id: string, cols: number, rows: number): SessionSnapshot;
|
|
116
109
|
stop(id: string): SessionSnapshot;
|
|
117
110
|
private cleanupRecord;
|
package/dist/process-manager.js
CHANGED
|
@@ -568,20 +568,7 @@ function snapshotMessages(record) {
|
|
|
568
568
|
const MAX_SESSIONS = 200;
|
|
569
569
|
const ARCHIVE_AFTER_MS = 1000 * 60 * 60 * 24;
|
|
570
570
|
const CONFIRM_WINDOW_SIZE = 800;
|
|
571
|
-
// Claude 会话 ID 格式:UUID v4
|
|
572
|
-
const CLAUDE_SESSION_ID_PATTERN = /"session_id"\s*:\s*"([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})"/i;
|
|
573
571
|
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
574
|
-
function listClaudeTaskIds() {
|
|
575
|
-
const tasksDir = path.join(os.homedir(), ".claude", "tasks");
|
|
576
|
-
try {
|
|
577
|
-
return readdirSync(tasksDir, { withFileTypes: true })
|
|
578
|
-
.filter((entry) => entry.isDirectory() && UUID_V4_PATTERN.test(entry.name))
|
|
579
|
-
.map((entry) => entry.name);
|
|
580
|
-
}
|
|
581
|
-
catch {
|
|
582
|
-
return [];
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
572
|
function getClaudeProjectDir(cwd) {
|
|
586
573
|
// Claude Code encodes the project dir by replacing every non-alphanumeric
|
|
587
574
|
// character (slash, dot, underscore, etc.) with "-", not just "/". Mirroring
|
|
@@ -590,23 +577,6 @@ function getClaudeProjectDir(cwd) {
|
|
|
590
577
|
const normalized = path.resolve(cwd).replace(/[^a-zA-Z0-9]/g, "-");
|
|
591
578
|
return path.join(os.homedir(), ".claude", "projects", normalized);
|
|
592
579
|
}
|
|
593
|
-
function getLatestClaudeTaskId(excludeIds) {
|
|
594
|
-
const tasksDir = path.join(os.homedir(), ".claude", "tasks");
|
|
595
|
-
try {
|
|
596
|
-
const candidates = readdirSync(tasksDir, { withFileTypes: true })
|
|
597
|
-
.filter((entry) => entry.isDirectory() && UUID_V4_PATTERN.test(entry.name) && !excludeIds.has(entry.name))
|
|
598
|
-
.map((entry) => {
|
|
599
|
-
const fullPath = path.join(tasksDir, entry.name);
|
|
600
|
-
const stats = statSync(fullPath);
|
|
601
|
-
return { id: entry.name, mtimeMs: stats.mtimeMs };
|
|
602
|
-
})
|
|
603
|
-
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
604
|
-
return candidates[0]?.id ?? null;
|
|
605
|
-
}
|
|
606
|
-
catch {
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
580
|
/** Derive a short summary for a session from user messages or current task. */
|
|
611
581
|
function getLastMessageSignature(messages) {
|
|
612
582
|
const last = messages[messages.length - 1];
|
|
@@ -634,7 +604,7 @@ function shouldPersistMessages(lastState, nextMessages) {
|
|
|
634
604
|
function recoverMessagesFromSnapshot(snapshot) {
|
|
635
605
|
return snapshot.messages ?? [];
|
|
636
606
|
}
|
|
637
|
-
function deriveSessionSummary(messages
|
|
607
|
+
function deriveSessionSummary(messages) {
|
|
638
608
|
// Prefer first user message as summary
|
|
639
609
|
for (const msg of messages) {
|
|
640
610
|
if (msg.role !== "user")
|
|
@@ -646,8 +616,6 @@ function deriveSessionSummary(messages, currentTaskTitle) {
|
|
|
646
616
|
}
|
|
647
617
|
break;
|
|
648
618
|
}
|
|
649
|
-
if (currentTaskTitle)
|
|
650
|
-
return currentTaskTitle.slice(0, 120);
|
|
651
619
|
return undefined;
|
|
652
620
|
}
|
|
653
621
|
export class ProcessManager extends EventEmitter {
|
|
@@ -739,9 +707,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
739
707
|
messages: snapshot.messages ?? [],
|
|
740
708
|
childProcess: null,
|
|
741
709
|
ptyBridge: null,
|
|
742
|
-
currentTask: null,
|
|
743
|
-
taskDebounceTimer: null,
|
|
744
|
-
lastEmittedTask: null,
|
|
745
710
|
knownClaudeTaskIds: undefined,
|
|
746
711
|
claudeTaskDiscoveryTimer: null,
|
|
747
712
|
knownClaudeProjectMtimes: isClaudeCmd ? listClaudeProjectSessionMtimes(updated.cwd) : undefined,
|
|
@@ -785,9 +750,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
785
750
|
messages: snapshot.messages ?? [],
|
|
786
751
|
childProcess: null,
|
|
787
752
|
ptyBridge: null,
|
|
788
|
-
currentTask: null,
|
|
789
|
-
taskDebounceTimer: null,
|
|
790
|
-
lastEmittedTask: null,
|
|
791
753
|
knownClaudeTaskIds: undefined,
|
|
792
754
|
claudeTaskDiscoveryTimer: null,
|
|
793
755
|
knownClaudeProjectMtimes: isClaudeCmd ? listClaudeProjectSessionMtimes(updated.cwd) : undefined,
|
|
@@ -811,7 +773,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
811
773
|
}, 60 * 1000);
|
|
812
774
|
this.archiveTimer.unref?.();
|
|
813
775
|
}
|
|
814
|
-
on(
|
|
776
|
+
on(_event, listener) {
|
|
815
777
|
return super.on("process", listener);
|
|
816
778
|
}
|
|
817
779
|
/** 启动时被识别为孤儿 PTY 并标记为 exited 的旧会话数量(仅用于启动摘要展示)。 */
|
|
@@ -956,9 +918,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
956
918
|
messages: priorMessages,
|
|
957
919
|
childProcess: null,
|
|
958
920
|
ptyBridge: null,
|
|
959
|
-
currentTask: null,
|
|
960
|
-
taskDebounceTimer: null,
|
|
961
|
-
lastEmittedTask: null,
|
|
962
921
|
knownClaudeTaskIds: knownClaudeTaskIds ?? undefined,
|
|
963
922
|
claudeTaskDiscoveryTimer: null,
|
|
964
923
|
knownClaudeProjectMtimes: knownClaudeProjectMtimes ?? undefined,
|
|
@@ -978,7 +937,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
978
937
|
sessionId: id,
|
|
979
938
|
isClaudeCommand: true,
|
|
980
939
|
autoApprove: record.autoApprovePermissions,
|
|
981
|
-
approvalPolicy: record.approvalPolicy,
|
|
982
940
|
initialMessages: priorMessages,
|
|
983
941
|
});
|
|
984
942
|
record.ptyBridge.on("event", (event) => {
|
|
@@ -1454,33 +1412,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
1454
1412
|
this.persist(record);
|
|
1455
1413
|
return this.snapshot(record);
|
|
1456
1414
|
}
|
|
1457
|
-
/** Emit a task event for a session, debounced to avoid flooding */
|
|
1458
|
-
emitTask(record, task) {
|
|
1459
|
-
// Clear existing debounce timer
|
|
1460
|
-
if (record.taskDebounceTimer) {
|
|
1461
|
-
clearTimeout(record.taskDebounceTimer);
|
|
1462
|
-
record.taskDebounceTimer = null;
|
|
1463
|
-
}
|
|
1464
|
-
// Don't re-emit the same task
|
|
1465
|
-
if (task && task.title === record.lastEmittedTask)
|
|
1466
|
-
return;
|
|
1467
|
-
if (task === null) {
|
|
1468
|
-
// Clear task after a delay — allows a brief display of "idle" state
|
|
1469
|
-
record.taskDebounceTimer = setTimeout(() => {
|
|
1470
|
-
record.currentTask = null;
|
|
1471
|
-
record.lastEmittedTask = null;
|
|
1472
|
-
this.emitEvent({ type: "task", sessionId: record.id, data: null });
|
|
1473
|
-
}, 2000);
|
|
1474
|
-
return;
|
|
1475
|
-
}
|
|
1476
|
-
// Debounce task changes by 100ms to avoid flickering on rapid tool switches
|
|
1477
|
-
record.taskDebounceTimer = setTimeout(() => {
|
|
1478
|
-
record.taskDebounceTimer = null;
|
|
1479
|
-
record.currentTask = task;
|
|
1480
|
-
record.lastEmittedTask = task.title;
|
|
1481
|
-
this.emitEvent({ type: "task", sessionId: record.id, data: task });
|
|
1482
|
-
}, 100);
|
|
1483
|
-
}
|
|
1484
1415
|
resize(id, cols, rows) {
|
|
1485
1416
|
const record = this.mustGet(id);
|
|
1486
1417
|
if (!record.ptyProcess || record.status !== "running") {
|
|
@@ -1509,11 +1440,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
1509
1440
|
if (record.status !== "running") {
|
|
1510
1441
|
return this.snapshot(record);
|
|
1511
1442
|
}
|
|
1512
|
-
// Clear any pending task debounce timer
|
|
1513
|
-
if (record.taskDebounceTimer) {
|
|
1514
|
-
clearTimeout(record.taskDebounceTimer);
|
|
1515
|
-
record.taskDebounceTimer = null;
|
|
1516
|
-
}
|
|
1517
1443
|
if (record.claudeTaskDiscoveryTimer) {
|
|
1518
1444
|
clearTimeout(record.claudeTaskDiscoveryTimer);
|
|
1519
1445
|
record.claudeTaskDiscoveryTimer = null;
|
|
@@ -1555,10 +1481,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
1555
1481
|
return this.snapshot(record);
|
|
1556
1482
|
}
|
|
1557
1483
|
cleanupRecord(record) {
|
|
1558
|
-
if (record.taskDebounceTimer) {
|
|
1559
|
-
clearTimeout(record.taskDebounceTimer);
|
|
1560
|
-
record.taskDebounceTimer = null;
|
|
1561
|
-
}
|
|
1562
1484
|
if (record.claudeTaskDiscoveryTimer) {
|
|
1563
1485
|
clearTimeout(record.claudeTaskDiscoveryTimer);
|
|
1564
1486
|
record.claudeTaskDiscoveryTimer = null;
|
|
@@ -1595,10 +1517,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
1595
1517
|
delete(id) {
|
|
1596
1518
|
const record = this.mustGet(id);
|
|
1597
1519
|
// Always clear pending timers
|
|
1598
|
-
if (record.taskDebounceTimer) {
|
|
1599
|
-
clearTimeout(record.taskDebounceTimer);
|
|
1600
|
-
record.taskDebounceTimer = null;
|
|
1601
|
-
}
|
|
1602
1520
|
if (record.claudeTaskDiscoveryTimer) {
|
|
1603
1521
|
clearTimeout(record.claudeTaskDiscoveryTimer);
|
|
1604
1522
|
record.claudeTaskDiscoveryTimer = null;
|
|
@@ -1721,10 +1639,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
1721
1639
|
autoRecovered: record.autoRecovered ?? false,
|
|
1722
1640
|
autoApprovePermissions: record.autoApprovePermissions || undefined,
|
|
1723
1641
|
approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined,
|
|
1724
|
-
summary: record.description ?? deriveSessionSummary(messages
|
|
1642
|
+
summary: record.description ?? deriveSessionSummary(messages),
|
|
1725
1643
|
title: record.title,
|
|
1726
1644
|
description: record.description,
|
|
1727
|
-
currentTaskTitle: record.status === "running" ? record.currentTask?.title ?? undefined : undefined,
|
|
1728
1645
|
selectedModel: record.selectedModel ?? null,
|
|
1729
1646
|
thinkingEffort: record.thinkingEffort ?? null,
|
|
1730
1647
|
ptyCols: record.ptyCols,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { Express } from "express";
|
|
1
|
+
import type { Express } from "express";
|
|
2
2
|
import { ProcessManager } from "./process-manager.js";
|
|
3
3
|
import { StructuredSessionManager } from "./structured-session-manager.js";
|
|
4
4
|
import { WandStorage } from "./storage.js";
|
|
5
5
|
import { ExecutionMode, SessionSource, WandConfig } from "./types.js";
|
|
6
|
-
import { getErrorMessage } from "./error-utils.js";
|
|
7
|
-
export { getErrorMessage };
|
|
8
6
|
export declare function parseSessionCreationOrigin(body: {
|
|
9
7
|
sessionSource?: unknown;
|
|
10
8
|
automationId?: unknown;
|