@co0ontty/wand 1.9.0 → 1.14.2
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 +48 -12
- package/dist/config.d.ts +2 -1
- package/dist/config.js +51 -40
- package/dist/message-truncator.d.ts +16 -0
- package/dist/message-truncator.js +76 -0
- package/dist/process-manager.d.ts +4 -0
- package/dist/process-manager.js +74 -21
- package/dist/resume-policy.d.ts +0 -77
- package/dist/resume-policy.js +0 -162
- package/dist/server-session-routes.js +29 -1
- package/dist/server.js +302 -45
- package/dist/storage.js +38 -112
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +10 -0
- package/dist/types.d.ts +27 -16
- package/dist/web-ui/content/scripts.js +1587 -780
- package/dist/web-ui/content/styles.css +677 -734
- package/dist/web-ui/scripts.js +3 -6
- package/dist/ws-broadcast.d.ts +3 -2
- package/dist/ws-broadcast.js +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# wand
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@co0ontty/wand)
|
|
4
|
+
[](https://github.com/co0ontty/wand/blob/master/LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://github.com/co0ontty/wand/commits/master)
|
|
7
|
+
|
|
8
|
+
通过浏览器远程访问和管理本地 CLI 工具的 Web 控制台。专为 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 和 [Codex](https://github.com/openai/codex) 设计,支持终端和结构化对话双视图、会话持久化与恢复、权限管控、文件浏览、Android 客户端等功能。
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img src="docs/screenshots/chat-view.png" width="800" alt="结构化对话视图" />
|
|
12
|
+
</p>
|
|
4
13
|
|
|
5
14
|
## 安装
|
|
6
15
|
|
|
@@ -22,6 +31,43 @@ wand web
|
|
|
22
31
|
|
|
23
32
|
安装完成后打开浏览器访问终端中提示的地址即可。
|
|
24
33
|
|
|
34
|
+
## 功能
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="docs/screenshots/home.png" width="800" alt="欢迎页" />
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
### 核心
|
|
41
|
+
|
|
42
|
+
- **双视图模式** — 终端原始输出和结构化对话视图可随时切换,同一会话两种呈现
|
|
43
|
+
- **多 Provider 支持** — 同时支持 Claude Code 和 Codex,可按需创建不同类型的会话
|
|
44
|
+
- **会话管理** — 创建、归档、恢复会话;支持从 Claude 原生历史记录恢复;会话列表显示摘要,懒加载
|
|
45
|
+
- **权限控制** — 可视化权限提示,支持逐次确认、单次批准、本轮记忆等策略;工具调用自动分组与审批统计
|
|
46
|
+
|
|
47
|
+
### 交互体验
|
|
48
|
+
|
|
49
|
+
- **结构化对话** — 代码块语法高亮、工具调用折叠/展开、多问题分组渲染、Token 用量按轮累计
|
|
50
|
+
- **个性化角色** — 像素风猫咪头像(赛博虎妞 / 勤劳初二),支持自定义对话角色名称
|
|
51
|
+
- **消息排队** — 在 AI 思考时可继续输入,消息自动排队发送
|
|
52
|
+
- **文件浏览器** — 内置路径浏览和搜索功能
|
|
53
|
+
|
|
54
|
+
### 部署与访问
|
|
55
|
+
|
|
56
|
+
- **PWA 支持** — 可添加到主屏幕作为独立应用使用
|
|
57
|
+
- **Android 客户端** — WebView 壳应用,支持加密连接码分发、APK 自动更新检查、原生通知推送、启动器图标切换
|
|
58
|
+
- **HTTPS** — 可选自签证书,适合远程或移动端访问
|
|
59
|
+
- **版本管理** — 内置更新检查与升级提示
|
|
60
|
+
|
|
61
|
+
## 截图
|
|
62
|
+
|
|
63
|
+
| 登录 | 对话视图 |
|
|
64
|
+
|:---:|:---:|
|
|
65
|
+
| <img src="docs/screenshots/login.png" width="400" alt="登录页" /> | <img src="docs/screenshots/chat-view.png" width="400" alt="对话视图" /> |
|
|
66
|
+
|
|
67
|
+
| 终端 PTY 视图 |
|
|
68
|
+
|:---:|
|
|
69
|
+
| <img src="docs/screenshots/terminal-pty.png" width="800" alt="终端 PTY 视图" /> |
|
|
70
|
+
|
|
25
71
|
## 配置
|
|
26
72
|
|
|
27
73
|
配置文件位于 `~/.wand/config.json`,首次 `wand init` 时自动生成。
|
|
@@ -43,17 +89,6 @@ wand config:set port 9443
|
|
|
43
89
|
| `password` | (随机生成) | 登录密码 |
|
|
44
90
|
| `language` | `""` | Claude 回复语言偏好 |
|
|
45
91
|
|
|
46
|
-
## 功能
|
|
47
|
-
|
|
48
|
-
- **双视图模式** — 终端原始输出和结构化对话视图可随时切换
|
|
49
|
-
- **会话管理** — 创建、归档、恢复会话;支持从 Claude 原生历史记录恢复;会话列表显示摘要
|
|
50
|
-
- **权限控制** — 可视化权限提示,支持逐次确认、单次批准、本轮记忆等策略;工具调用自动分组
|
|
51
|
-
- **文件浏览器** — 内置路径浏览和搜索功能
|
|
52
|
-
- **多种运行模式** — full-access / default / auto-edit 等 Claude 运行模式
|
|
53
|
-
- **个性化** — 像素风猫咪头像、回复语言偏好设置
|
|
54
|
-
- **PWA 支持** — 可添加到主屏幕作为独立应用使用
|
|
55
|
-
- **HTTPS** — 可选自签证书,适合远程或移动端访问
|
|
56
|
-
|
|
57
92
|
## 开发
|
|
58
93
|
|
|
59
94
|
```bash
|
|
@@ -84,6 +119,7 @@ src/
|
|
|
84
119
|
session-logger.ts # 文件日志 ~/.wand/sessions/
|
|
85
120
|
resume-policy.ts # Claude 历史绑定与恢复策略
|
|
86
121
|
web-ui/ # 服务端渲染的前端 HTML/CSS/JS
|
|
122
|
+
android/ # Android WebView 壳应用
|
|
87
123
|
```
|
|
88
124
|
|
|
89
125
|
数据存储在 `~/.wand/` 下:`config.json`(配置)、`wand.db`(SQLite)、`sessions/`(日志)。
|
package/dist/config.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ExecutionMode, WandConfig } from "./types.js";
|
|
1
|
+
import { CardExpandDefaults, ExecutionMode, WandConfig } from "./types.js";
|
|
2
2
|
export declare const defaultConfig: () => WandConfig;
|
|
3
3
|
export declare function resolveConfigPath(inputPath?: string): string;
|
|
4
4
|
export declare function resolveConfigDir(configPath: string): string;
|
|
5
5
|
export declare function hasConfigFile(configPath: string): boolean;
|
|
6
6
|
export declare function ensureConfig(configPath: string): Promise<WandConfig>;
|
|
7
7
|
export declare function saveConfig(configPath: string, config: WandConfig): Promise<void>;
|
|
8
|
+
export declare function normalizeCardDefaults(input: unknown): CardExpandDefaults;
|
|
8
9
|
export declare function isExecutionMode(value: unknown): value is ExecutionMode;
|
package/dist/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
@@ -16,20 +17,8 @@ export const defaultConfig = () => ({
|
|
|
16
17
|
allowedCommandPrefixes: [],
|
|
17
18
|
shortcutLogMaxBytes: 10 * 1024 * 1024,
|
|
18
19
|
language: "",
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
sessionsDrawerOpen: false,
|
|
22
|
-
filePanelOpen: false,
|
|
23
|
-
shortcutsExpanded: false,
|
|
24
|
-
claudeHistoryExpanded: true,
|
|
25
|
-
chatMessageExpanded: true,
|
|
26
|
-
structuredThinkingExpanded: true,
|
|
27
|
-
structuredToolGroupExpanded: false,
|
|
28
|
-
structuredInlineToolExpanded: false,
|
|
29
|
-
structuredTerminalExpanded: false,
|
|
30
|
-
structuredToolCardExpanded: false,
|
|
31
|
-
}
|
|
32
|
-
},
|
|
20
|
+
android: defaultAndroidApkConfig(),
|
|
21
|
+
cardDefaults: defaultCardExpandDefaults(),
|
|
33
22
|
commandPresets: [
|
|
34
23
|
{
|
|
35
24
|
label: "Claude",
|
|
@@ -93,6 +82,49 @@ export async function saveConfig(configPath, config) {
|
|
|
93
82
|
await mkdir(path.dirname(configPath), { recursive: true });
|
|
94
83
|
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
95
84
|
}
|
|
85
|
+
function defaultCardExpandDefaults() {
|
|
86
|
+
return {
|
|
87
|
+
editCards: false,
|
|
88
|
+
inlineTools: false,
|
|
89
|
+
terminal: false,
|
|
90
|
+
thinking: false,
|
|
91
|
+
toolGroup: false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function normalizeCardDefaults(input) {
|
|
95
|
+
if (!input || typeof input !== "object")
|
|
96
|
+
return defaultCardExpandDefaults();
|
|
97
|
+
const raw = input;
|
|
98
|
+
return {
|
|
99
|
+
editCards: typeof raw.editCards === "boolean" ? raw.editCards : false,
|
|
100
|
+
inlineTools: typeof raw.inlineTools === "boolean" ? raw.inlineTools : false,
|
|
101
|
+
terminal: typeof raw.terminal === "boolean" ? raw.terminal : false,
|
|
102
|
+
thinking: typeof raw.thinking === "boolean" ? raw.thinking : false,
|
|
103
|
+
toolGroup: typeof raw.toolGroup === "boolean" ? raw.toolGroup : false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function defaultAndroidApkConfig() {
|
|
107
|
+
return {
|
|
108
|
+
enabled: false,
|
|
109
|
+
apkDir: "android",
|
|
110
|
+
currentApkFile: "",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function normalizeAndroidApkConfig(input) {
|
|
114
|
+
if (!input || typeof input !== "object")
|
|
115
|
+
return undefined;
|
|
116
|
+
const defaults = defaultAndroidApkConfig();
|
|
117
|
+
const androidInput = input;
|
|
118
|
+
return {
|
|
119
|
+
enabled: typeof androidInput.enabled === "boolean" ? androidInput.enabled : defaults.enabled,
|
|
120
|
+
apkDir: typeof androidInput.apkDir === "string" && androidInput.apkDir.trim()
|
|
121
|
+
? androidInput.apkDir.trim()
|
|
122
|
+
: defaults.apkDir,
|
|
123
|
+
currentApkFile: typeof androidInput.currentApkFile === "string"
|
|
124
|
+
? androidInput.currentApkFile.trim()
|
|
125
|
+
: defaults.currentApkFile,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
96
128
|
function normalizeStructuredChatPersona(input) {
|
|
97
129
|
if (!input || typeof input !== "object")
|
|
98
130
|
return undefined;
|
|
@@ -115,26 +147,8 @@ function normalizeStructuredChatPersona(input) {
|
|
|
115
147
|
return undefined;
|
|
116
148
|
return { user, assistant };
|
|
117
149
|
}
|
|
118
|
-
function normalizeDefaultPanelState(input) {
|
|
119
|
-
if (!input || typeof input !== "object")
|
|
120
|
-
return undefined;
|
|
121
|
-
const state = input;
|
|
122
|
-
return {
|
|
123
|
-
sessionsDrawerOpen: typeof state.sessionsDrawerOpen === "boolean" ? state.sessionsDrawerOpen : undefined,
|
|
124
|
-
filePanelOpen: typeof state.filePanelOpen === "boolean" ? state.filePanelOpen : undefined,
|
|
125
|
-
shortcutsExpanded: typeof state.shortcutsExpanded === "boolean" ? state.shortcutsExpanded : undefined,
|
|
126
|
-
claudeHistoryExpanded: typeof state.claudeHistoryExpanded === "boolean" ? state.claudeHistoryExpanded : undefined,
|
|
127
|
-
chatMessageExpanded: typeof state.chatMessageExpanded === "boolean" ? state.chatMessageExpanded : undefined,
|
|
128
|
-
structuredThinkingExpanded: typeof state.structuredThinkingExpanded === "boolean" ? state.structuredThinkingExpanded : undefined,
|
|
129
|
-
structuredToolGroupExpanded: typeof state.structuredToolGroupExpanded === "boolean" ? state.structuredToolGroupExpanded : undefined,
|
|
130
|
-
structuredInlineToolExpanded: typeof state.structuredInlineToolExpanded === "boolean" ? state.structuredInlineToolExpanded : undefined,
|
|
131
|
-
structuredTerminalExpanded: typeof state.structuredTerminalExpanded === "boolean" ? state.structuredTerminalExpanded : undefined,
|
|
132
|
-
structuredToolCardExpanded: typeof state.structuredToolCardExpanded === "boolean" ? state.structuredToolCardExpanded : undefined,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
150
|
function mergeWithDefaults(input) {
|
|
136
151
|
const defaults = defaultConfig();
|
|
137
|
-
const normalizedPanelState = normalizeDefaultPanelState(input.uiPreferences?.defaultPanelState);
|
|
138
152
|
return {
|
|
139
153
|
...defaults,
|
|
140
154
|
...input,
|
|
@@ -164,14 +178,11 @@ function mergeWithDefaults(input) {
|
|
|
164
178
|
: defaults.commandPresets,
|
|
165
179
|
structuredChatPersona: normalizeStructuredChatPersona(input.structuredChatPersona),
|
|
166
180
|
language: typeof input.language === "string" ? input.language.trim() : defaults.language,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
...(normalizedPanelState || {}),
|
|
173
|
-
}
|
|
174
|
-
},
|
|
181
|
+
appSecret: typeof input.appSecret === "string" && input.appSecret.length >= 32
|
|
182
|
+
? input.appSecret
|
|
183
|
+
: crypto.randomBytes(32).toString("hex"),
|
|
184
|
+
android: normalizeAndroidApkConfig(input.android) ?? defaults.android,
|
|
185
|
+
cardDefaults: normalizeCardDefaults(input.cardDefaults),
|
|
175
186
|
};
|
|
176
187
|
}
|
|
177
188
|
export function isExecutionMode(value) {
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
*/
|
|
6
|
+
import type { CardExpandDefaults, ConversationTurn } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Truncate messages for WebSocket transport. Tool results for collapsed card
|
|
9
|
+
* types are replaced with a short summary when they exceed the threshold.
|
|
10
|
+
*
|
|
11
|
+
* @param messages - Original messages array (not mutated)
|
|
12
|
+
* @param cardDefaults - Current card expand defaults from config
|
|
13
|
+
* @param streamingTurnIndex - Index of the currently streaming turn (-1 if none).
|
|
14
|
+
* Tool results in the streaming turn are never truncated.
|
|
15
|
+
*/
|
|
16
|
+
export declare function truncateMessagesForTransport(messages: ConversationTurn[], cardDefaults: CardExpandDefaults, streamingTurnIndex?: number): ConversationTurn[];
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
*/
|
|
6
|
+
const TRUNCATION_THRESHOLD = 200;
|
|
7
|
+
const SUMMARY_LENGTH = 100;
|
|
8
|
+
/** Tool name → cardDefaults field mapping */
|
|
9
|
+
function isToolDefaultCollapsed(toolName, defaults) {
|
|
10
|
+
switch (toolName) {
|
|
11
|
+
case "Read":
|
|
12
|
+
case "Glob":
|
|
13
|
+
case "Grep":
|
|
14
|
+
case "WebFetch":
|
|
15
|
+
case "WebSearch":
|
|
16
|
+
case "TodoRead":
|
|
17
|
+
return defaults.inlineTools !== true;
|
|
18
|
+
case "Bash":
|
|
19
|
+
return defaults.terminal !== true;
|
|
20
|
+
case "Edit":
|
|
21
|
+
case "Write":
|
|
22
|
+
case "MultiEdit":
|
|
23
|
+
return defaults.editCards !== true;
|
|
24
|
+
default:
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function getContentString(content) {
|
|
29
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Truncate messages for WebSocket transport. Tool results for collapsed card
|
|
33
|
+
* types are replaced with a short summary when they exceed the threshold.
|
|
34
|
+
*
|
|
35
|
+
* @param messages - Original messages array (not mutated)
|
|
36
|
+
* @param cardDefaults - Current card expand defaults from config
|
|
37
|
+
* @param streamingTurnIndex - Index of the currently streaming turn (-1 if none).
|
|
38
|
+
* Tool results in the streaming turn are never truncated.
|
|
39
|
+
*/
|
|
40
|
+
export function truncateMessagesForTransport(messages, cardDefaults, streamingTurnIndex = -1) {
|
|
41
|
+
return messages.map((turn, turnIndex) => {
|
|
42
|
+
// Never truncate the currently streaming turn
|
|
43
|
+
if (turnIndex === streamingTurnIndex)
|
|
44
|
+
return turn;
|
|
45
|
+
// Build tool_use_id → tool_name map from this turn's blocks
|
|
46
|
+
const toolNameMap = new Map();
|
|
47
|
+
for (const block of turn.content) {
|
|
48
|
+
if (block.type === "tool_use") {
|
|
49
|
+
toolNameMap.set(block.id, block.name);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let changed = false;
|
|
53
|
+
const truncatedContent = turn.content.map((block) => {
|
|
54
|
+
if (block.type !== "tool_result")
|
|
55
|
+
return block;
|
|
56
|
+
const result = block;
|
|
57
|
+
// Never truncate errors
|
|
58
|
+
if (result.is_error)
|
|
59
|
+
return block;
|
|
60
|
+
const toolName = toolNameMap.get(result.tool_use_id) || "";
|
|
61
|
+
if (!isToolDefaultCollapsed(toolName, cardDefaults))
|
|
62
|
+
return block;
|
|
63
|
+
const contentStr = getContentString(result.content);
|
|
64
|
+
if (contentStr.length <= TRUNCATION_THRESHOLD)
|
|
65
|
+
return block;
|
|
66
|
+
changed = true;
|
|
67
|
+
return {
|
|
68
|
+
...result,
|
|
69
|
+
content: contentStr.slice(0, SUMMARY_LENGTH) + "…",
|
|
70
|
+
_truncated: true,
|
|
71
|
+
_originalSize: contentStr.length,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
return changed ? { ...turn, content: truncatedContent } : turn;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -45,6 +45,8 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
45
45
|
provider?: SessionProvider;
|
|
46
46
|
}): SessionSnapshot;
|
|
47
47
|
list(): SessionSnapshot[];
|
|
48
|
+
/** Return lightweight snapshots for the session list (no output/messages). */
|
|
49
|
+
listSlim(): SessionSnapshot[];
|
|
48
50
|
hasClaudeSessionFile(cwd: string, claudeSessionId: string): boolean;
|
|
49
51
|
private claudeHistoryCache;
|
|
50
52
|
private static readonly HISTORY_CACHE_TTL_MS;
|
|
@@ -64,6 +66,8 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
64
66
|
private deleteClaudeCache;
|
|
65
67
|
runStartupCommands(): SessionSnapshot[];
|
|
66
68
|
private snapshot;
|
|
69
|
+
/** Lightweight snapshot for list views — omits output and messages. */
|
|
70
|
+
private snapshotSlim;
|
|
67
71
|
private isPermissionBlocked;
|
|
68
72
|
private defaultAutonomyPolicy;
|
|
69
73
|
resolveEscalation(id: string, requestId: string, resolution?: "approve_once" | "approve_turn" | "deny"): SessionSnapshot;
|
package/dist/process-manager.js
CHANGED
|
@@ -8,6 +8,7 @@ import pty from "node-pty";
|
|
|
8
8
|
import { SessionLogger } from "./session-logger.js";
|
|
9
9
|
import { SessionLifecycleManager } from "./session-lifecycle.js";
|
|
10
10
|
import { ClaudePtyBridge } from "./claude-pty-bridge.js";
|
|
11
|
+
import { truncateMessagesForTransport } from "./message-truncator.js";
|
|
11
12
|
import { appendWindow, hasExplicitConfirmSyntax, hasPermissionActionContext, normalizePromptText } from "./pty-text-utils.js";
|
|
12
13
|
import { prepareSessionWorktree } from "./git-worktree.js";
|
|
13
14
|
import { getResumeCommandSessionId, hasRealConversationMessages, } from "./resume-policy.js";
|
|
@@ -863,6 +864,13 @@ export class ProcessManager extends EventEmitter {
|
|
|
863
864
|
.sort((a, b) => b.startedAt.localeCompare(a.startedAt))
|
|
864
865
|
.map((session) => this.snapshot(session));
|
|
865
866
|
}
|
|
867
|
+
/** Return lightweight snapshots for the session list (no output/messages). */
|
|
868
|
+
listSlim() {
|
|
869
|
+
this.archiveExpiredSessions();
|
|
870
|
+
return Array.from(this.sessions.values())
|
|
871
|
+
.sort((a, b) => b.startedAt.localeCompare(a.startedAt))
|
|
872
|
+
.map((session) => this.snapshotSlim(session));
|
|
873
|
+
}
|
|
866
874
|
hasClaudeSessionFile(cwd, claudeSessionId) {
|
|
867
875
|
return isClaudeSessionFileAvailable(cwd, claudeSessionId);
|
|
868
876
|
}
|
|
@@ -1171,6 +1179,43 @@ export class ProcessManager extends EventEmitter {
|
|
|
1171
1179
|
autoApprovePermissions: record.autoApprovePermissions || undefined,
|
|
1172
1180
|
approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined,
|
|
1173
1181
|
summary: deriveSessionSummary(messages, record.currentTask?.title ?? null),
|
|
1182
|
+
currentTaskTitle: record.status === "running" ? record.currentTask?.title ?? undefined : undefined,
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
/** Lightweight snapshot for list views — omits output and messages. */
|
|
1186
|
+
snapshotSlim(record) {
|
|
1187
|
+
const messages = record.ptyBridge?.getMessages() ?? record.messages;
|
|
1188
|
+
return {
|
|
1189
|
+
id: record.id,
|
|
1190
|
+
sessionKind: "pty",
|
|
1191
|
+
provider: record.provider,
|
|
1192
|
+
runner: "pty",
|
|
1193
|
+
command: record.command,
|
|
1194
|
+
cwd: record.cwd,
|
|
1195
|
+
mode: record.mode,
|
|
1196
|
+
worktreeEnabled: record.worktreeEnabled ?? false,
|
|
1197
|
+
worktree: record.worktree ?? null,
|
|
1198
|
+
autonomyPolicy: record.autonomyPolicy,
|
|
1199
|
+
approvalPolicy: record.approvalPolicy,
|
|
1200
|
+
allowedScopes: record.allowedScopes,
|
|
1201
|
+
status: record.status,
|
|
1202
|
+
exitCode: record.exitCode,
|
|
1203
|
+
startedAt: record.startedAt,
|
|
1204
|
+
endedAt: record.endedAt,
|
|
1205
|
+
output: "",
|
|
1206
|
+
archived: record.archived,
|
|
1207
|
+
archivedAt: record.archivedAt,
|
|
1208
|
+
permissionBlocked: this.isPermissionBlocked(record),
|
|
1209
|
+
pendingEscalation: record.pendingEscalation || undefined,
|
|
1210
|
+
lastEscalationResult: record.lastEscalationResult || undefined,
|
|
1211
|
+
claudeSessionId: record.claudeSessionId || null,
|
|
1212
|
+
resumedFromSessionId: record.resumedFromSessionId ?? undefined,
|
|
1213
|
+
resumedToSessionId: record.resumedToSessionId ?? undefined,
|
|
1214
|
+
autoRecovered: record.autoRecovered ?? false,
|
|
1215
|
+
autoApprovePermissions: record.autoApprovePermissions || undefined,
|
|
1216
|
+
approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined,
|
|
1217
|
+
summary: deriveSessionSummary(messages, record.currentTask?.title ?? null),
|
|
1218
|
+
currentTaskTitle: record.status === "running" ? record.currentTask?.title ?? undefined : undefined,
|
|
1174
1219
|
};
|
|
1175
1220
|
}
|
|
1176
1221
|
isPermissionBlocked(record) {
|
|
@@ -1438,31 +1483,39 @@ export class ProcessManager extends EventEmitter {
|
|
|
1438
1483
|
case "output.raw":
|
|
1439
1484
|
// Sync record.output from bridge before emitting so the event carries fresh data
|
|
1440
1485
|
record.output = record.ptyBridge?.getRawOutput() ?? record.output;
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1486
|
+
{
|
|
1487
|
+
const rawMessages = record.ptyBridge?.getMessages() ?? [];
|
|
1488
|
+
const messages = truncateMessagesForTransport(rawMessages, this.config.cardDefaults ?? {}, rawMessages.length - 1);
|
|
1489
|
+
// Emit output event for terminal view
|
|
1490
|
+
this.emitEvent({
|
|
1491
|
+
type: "output",
|
|
1492
|
+
sessionId: event.sessionId,
|
|
1493
|
+
data: {
|
|
1494
|
+
chunk: event.data.chunk,
|
|
1495
|
+
output: record.output,
|
|
1496
|
+
messages,
|
|
1497
|
+
permissionBlocked: this.isPermissionBlocked(record),
|
|
1498
|
+
},
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1452
1501
|
break;
|
|
1453
1502
|
case "output.chat":
|
|
1454
1503
|
// Sync record.output from bridge before emitting so the event carries fresh data
|
|
1455
1504
|
record.output = record.ptyBridge?.getRawOutput() ?? record.output;
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1505
|
+
{
|
|
1506
|
+
const rawMessages = record.ptyBridge?.getMessages() ?? [];
|
|
1507
|
+
const messages = truncateMessagesForTransport(rawMessages, this.config.cardDefaults ?? {}, rawMessages.length - 1);
|
|
1508
|
+
// Emit output event with updated messages for chat view
|
|
1509
|
+
this.emitEvent({
|
|
1510
|
+
type: "output",
|
|
1511
|
+
sessionId: event.sessionId,
|
|
1512
|
+
data: {
|
|
1513
|
+
output: record.output,
|
|
1514
|
+
messages,
|
|
1515
|
+
permissionBlocked: this.isPermissionBlocked(record),
|
|
1516
|
+
},
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1466
1519
|
break;
|
|
1467
1520
|
case "permission.prompt": {
|
|
1468
1521
|
const data = event.data;
|
package/dist/resume-policy.d.ts
CHANGED
|
@@ -1,80 +1,3 @@
|
|
|
1
1
|
import { ConversationTurn } from "./types.js";
|
|
2
2
|
export declare function hasRealConversationMessages(messages: ConversationTurn[] | undefined): boolean;
|
|
3
|
-
export declare function hasRuntimeConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
4
|
-
export declare function hasStoredConversationHistory(messages: ConversationTurn[] | undefined): boolean;
|
|
5
|
-
export declare function shouldBindClaudeSessionId(record: {
|
|
6
|
-
messages: ConversationTurn[] | undefined;
|
|
7
|
-
}): boolean;
|
|
8
|
-
export declare function shouldAllowResume(record: {
|
|
9
|
-
claudeSessionId: string | null | undefined;
|
|
10
|
-
messages: ConversationTurn[] | undefined;
|
|
11
|
-
}): boolean;
|
|
12
|
-
export declare function shouldBackfillFromStoredHistory(record: {
|
|
13
|
-
messages: ConversationTurn[] | undefined;
|
|
14
|
-
}): boolean;
|
|
15
|
-
export declare function shouldDisplayResumeAction(messages: ConversationTurn[] | undefined): boolean;
|
|
16
|
-
export declare function shouldAutoResumeMessages(messages: ConversationTurn[] | undefined): boolean;
|
|
17
|
-
export declare function shouldBackfillMessages(messages: ConversationTurn[] | undefined): boolean;
|
|
18
|
-
export declare function shouldPromoteProjectSessionId(record: {
|
|
19
|
-
messages: ConversationTurn[] | undefined;
|
|
20
|
-
}): boolean;
|
|
21
|
-
export declare function shouldPromoteStoredSessionId(record: {
|
|
22
|
-
messages: ConversationTurn[] | undefined;
|
|
23
|
-
}): boolean;
|
|
24
|
-
export declare function shouldPromoteUiSessionId(messages: ConversationTurn[] | undefined): boolean;
|
|
25
|
-
export declare function shouldPromoteResumeSessionId(messages: ConversationTurn[] | undefined): boolean;
|
|
26
|
-
export declare function hasBindableConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
27
|
-
export declare function hasBackfillableConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
28
|
-
export declare function hasUiConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
29
|
-
export declare function hasResumeConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
30
|
-
export declare function isRuntimeConversationReady(messages: ConversationTurn[] | undefined): boolean;
|
|
31
|
-
export declare function isStoredConversationReady(messages: ConversationTurn[] | undefined): boolean;
|
|
32
|
-
export declare function isResumeConversationReady(messages: ConversationTurn[] | undefined): boolean;
|
|
33
|
-
export declare function shouldBindFromRuntimeMessages(record: {
|
|
34
|
-
messages: ConversationTurn[] | undefined;
|
|
35
|
-
}): boolean;
|
|
36
|
-
export declare function shouldAllowUiResume(messages: ConversationTurn[] | undefined): boolean;
|
|
37
|
-
export declare function shouldPromoteResumeAction(record: {
|
|
38
|
-
claudeSessionId: string | null | undefined;
|
|
39
|
-
messages: ConversationTurn[] | undefined;
|
|
40
|
-
}): boolean;
|
|
41
|
-
export declare function shouldBackfillClaudeSessionIdFromDisk(record: {
|
|
42
|
-
messages: ConversationTurn[] | undefined;
|
|
43
|
-
}): boolean;
|
|
44
|
-
export declare function shouldUseProjectCandidate(record: {
|
|
45
|
-
messages: ConversationTurn[] | undefined;
|
|
46
|
-
}): boolean;
|
|
47
|
-
export declare function shouldResumeProjectCandidate(record: {
|
|
48
|
-
claudeSessionId: string | null | undefined;
|
|
49
|
-
messages: ConversationTurn[] | undefined;
|
|
50
|
-
}): boolean;
|
|
51
|
-
export declare function shouldBackfillProjectCandidate(record: {
|
|
52
|
-
messages: ConversationTurn[] | undefined;
|
|
53
|
-
}): boolean;
|
|
54
|
-
export declare function hasMinimumRuntimeConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
55
|
-
export declare function hasMinimumStoredConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
56
|
-
export declare function hasMinimumResumeConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
57
|
-
export declare function hasMinimumBackfillConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
58
|
-
export declare function hasProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
59
|
-
export declare function hasStoredProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
60
|
-
export declare function hasUiProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
61
|
-
export declare function hasResumeProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
62
|
-
export declare function canBindFromProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
63
|
-
export declare function canBackfillFromProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
64
|
-
export declare function canShowUiProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
65
|
-
export declare function canResumeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
66
|
-
export declare function shouldUseRuntimeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
67
|
-
export declare function shouldUseStoredProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
68
|
-
export declare function shouldUseUiProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
69
|
-
export declare function shouldUseResumeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
70
|
-
export declare function hasProjectConversationForBinding(messages: ConversationTurn[] | undefined): boolean;
|
|
71
|
-
export declare function hasProjectConversationForBackfill(messages: ConversationTurn[] | undefined): boolean;
|
|
72
|
-
export declare function hasProjectConversationForUi(messages: ConversationTurn[] | undefined): boolean;
|
|
73
|
-
export declare function hasProjectConversationForResume(messages: ConversationTurn[] | undefined): boolean;
|
|
74
|
-
export declare function isBindableProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
75
|
-
export declare function isBackfillableProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
76
|
-
export declare function isUiProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
77
|
-
export declare function isResumeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
78
|
-
export declare function hasLiveProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
79
|
-
export declare function hasStoredProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
80
3
|
export declare function getResumeCommandSessionId(command: string): string | null;
|