wave-code 1.0.5 → 1.0.6
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/contexts/useChat.js +81 -19
- package/dist/stdio/agentBridge.d.ts +1 -0
- package/dist/stdio/agentBridge.js +7 -0
- package/dist/utils/rewindCheckpoints.d.ts +2 -2
- package/dist/utils/rewindCheckpoints.js +4 -2
- package/package.json +2 -2
- package/src/contexts/useChat.tsx +136 -70
- package/src/stdio/agentBridge.ts +8 -0
- package/src/utils/rewindCheckpoints.ts +3 -2
package/dist/contexts/useChat.js
CHANGED
|
@@ -15,6 +15,66 @@ export const useChat = () => {
|
|
|
15
15
|
}
|
|
16
16
|
return context;
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Window-concat throttle for pure-delta streaming updates: chunks arriving
|
|
20
|
+
* within the cooldown window are merged so no delta is lost (a dropped delta
|
|
21
|
+
* would permanently lose content, unlike the accumulated-payload throttle it
|
|
22
|
+
* replaces). Leading edge fires immediately; the trailing edge carries only
|
|
23
|
+
* chunks that arrived within the window. `end` flushes any pending deltas
|
|
24
|
+
* first, then forwards the end signal right away.
|
|
25
|
+
*/
|
|
26
|
+
function createStreamingWindowThrottle(fn, wait) {
|
|
27
|
+
let timer = null;
|
|
28
|
+
let pending = null;
|
|
29
|
+
const fire = (stage) => {
|
|
30
|
+
if (pending) {
|
|
31
|
+
fn({ ...pending, stage });
|
|
32
|
+
pending = null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const throttled = (params) => {
|
|
36
|
+
if (params.stage === "end") {
|
|
37
|
+
// Flush any deltas still pending inside the cooldown window first
|
|
38
|
+
if (timer) {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
timer = null;
|
|
41
|
+
}
|
|
42
|
+
fire("streaming");
|
|
43
|
+
fn(params);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (pending) {
|
|
47
|
+
pending.chunk += params.chunk;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
pending = { messageId: params.messageId, chunk: params.chunk };
|
|
51
|
+
}
|
|
52
|
+
if (!timer) {
|
|
53
|
+
// Leading edge: fire the current delta immediately, then reset pending so
|
|
54
|
+
// the trailing edge only carries chunks arriving within this window
|
|
55
|
+
fire("streaming");
|
|
56
|
+
timer = setTimeout(() => {
|
|
57
|
+
timer = null;
|
|
58
|
+
fire("streaming");
|
|
59
|
+
}, wait);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
throttled.cancel = () => {
|
|
63
|
+
if (timer) {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
timer = null;
|
|
66
|
+
}
|
|
67
|
+
pending = null;
|
|
68
|
+
};
|
|
69
|
+
throttled.flush = () => {
|
|
70
|
+
if (timer) {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
timer = null;
|
|
73
|
+
}
|
|
74
|
+
fire("streaming");
|
|
75
|
+
};
|
|
76
|
+
return throttled;
|
|
77
|
+
}
|
|
18
78
|
export const ChatProvider = ({ children, bypassPermissions, permissionMode: initialPermissionMode, pluginDirs, additionalDirectories, tools, allowedTools, disallowedTools, workdir, worktreeSession, originalCwd, version, model, mcpServers, }) => {
|
|
19
79
|
const { restoreSessionId, continueLastSession } = useAppConfig();
|
|
20
80
|
const { stdout } = useStdout();
|
|
@@ -26,11 +86,13 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
26
86
|
const [messages, setMessages] = useState([]);
|
|
27
87
|
const [latestTotalTokens, setLatestTotalTokens] = useState(0);
|
|
28
88
|
const [maxInputTokens, setMaxInputTokens] = useState(200000);
|
|
29
|
-
// Throttled incremental streaming updaters — 500ms
|
|
30
|
-
// as the pre-incremental throttledSetMessages.
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
89
|
+
// Throttled incremental streaming updaters — 500ms window-concat, the same
|
|
90
|
+
// interval as the pre-incremental throttledSetMessages. Chunks are pure
|
|
91
|
+
// deltas: within-window chunks are merged so none is dropped, and
|
|
92
|
+
// `stage === "end"` flushes pending deltas + applies the end signal
|
|
93
|
+
// immediately so completion results are never delayed.
|
|
94
|
+
const throttledContentUpdate = useMemo(() => createStreamingWindowThrottle((params) => {
|
|
95
|
+
const { messageId, chunk, stage } = params;
|
|
34
96
|
setMessages((prev) => prev.map((m) => {
|
|
35
97
|
if (m.id !== messageId)
|
|
36
98
|
return m;
|
|
@@ -38,22 +100,23 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
38
100
|
if (textBlockIndex === -1) {
|
|
39
101
|
return {
|
|
40
102
|
...m,
|
|
41
|
-
blocks: [
|
|
42
|
-
...m.blocks,
|
|
43
|
-
{ type: "text", content: accumulated, stage },
|
|
44
|
-
],
|
|
103
|
+
blocks: [...m.blocks, { type: "text", content: chunk, stage }],
|
|
45
104
|
};
|
|
46
105
|
}
|
|
47
106
|
return {
|
|
48
107
|
...m,
|
|
49
108
|
blocks: m.blocks.map((b, idx) => idx === textBlockIndex && b.type === "text"
|
|
50
|
-
? {
|
|
109
|
+
? {
|
|
110
|
+
...b,
|
|
111
|
+
content: (b.content || "") + chunk,
|
|
112
|
+
stage,
|
|
113
|
+
}
|
|
51
114
|
: b),
|
|
52
115
|
};
|
|
53
116
|
}));
|
|
54
117
|
}, 500), []);
|
|
55
|
-
const throttledReasoningUpdate = useMemo(() =>
|
|
56
|
-
const { messageId,
|
|
118
|
+
const throttledReasoningUpdate = useMemo(() => createStreamingWindowThrottle((params) => {
|
|
119
|
+
const { messageId, chunk, stage } = params;
|
|
57
120
|
setMessages((prev) => prev.map((m) => {
|
|
58
121
|
if (m.id !== messageId)
|
|
59
122
|
return m;
|
|
@@ -63,14 +126,18 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
63
126
|
...m,
|
|
64
127
|
blocks: [
|
|
65
128
|
...m.blocks,
|
|
66
|
-
{ type: "reasoning", content:
|
|
129
|
+
{ type: "reasoning", content: chunk, stage },
|
|
67
130
|
],
|
|
68
131
|
};
|
|
69
132
|
}
|
|
70
133
|
return {
|
|
71
134
|
...m,
|
|
72
135
|
blocks: m.blocks.map((b, idx) => idx === reasoningBlockIndex && b.type === "reasoning"
|
|
73
|
-
? {
|
|
136
|
+
? {
|
|
137
|
+
...b,
|
|
138
|
+
content: (b.content || "") + chunk,
|
|
139
|
+
stage,
|
|
140
|
+
}
|
|
74
141
|
: b),
|
|
75
142
|
};
|
|
76
143
|
}));
|
|
@@ -211,15 +278,11 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
211
278
|
if (isExpandedRef.current)
|
|
212
279
|
return;
|
|
213
280
|
throttledContentUpdate(params);
|
|
214
|
-
if (params.stage === "end")
|
|
215
|
-
throttledContentUpdate.flush();
|
|
216
281
|
},
|
|
217
282
|
onAssistantReasoningUpdated: (params) => {
|
|
218
283
|
if (isExpandedRef.current)
|
|
219
284
|
return;
|
|
220
285
|
throttledReasoningUpdate(params);
|
|
221
|
-
if (params.stage === "end")
|
|
222
|
-
throttledReasoningUpdate.flush();
|
|
223
286
|
},
|
|
224
287
|
onToolBlockUpdated: (params) => {
|
|
225
288
|
if (isExpandedRef.current)
|
|
@@ -437,7 +500,6 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
437
500
|
originalCwd,
|
|
438
501
|
model,
|
|
439
502
|
initialPermissionMode,
|
|
440
|
-
refreshMessages,
|
|
441
503
|
throttledContentUpdate,
|
|
442
504
|
throttledReasoningUpdate,
|
|
443
505
|
throttledToolBlockUpdate,
|
|
@@ -140,6 +140,8 @@ export class AgentBridge {
|
|
|
140
140
|
return this.getBackgroundTaskOutput(p.taskId, sessionId);
|
|
141
141
|
case "stopBackgroundTask":
|
|
142
142
|
return this.stopBackgroundTask(p.taskId, sessionId);
|
|
143
|
+
case "backgroundCurrentTask":
|
|
144
|
+
return this.backgroundCurrentTask(sessionId);
|
|
143
145
|
case "getWorkflowRuns":
|
|
144
146
|
return this.getWorkflowRuns(sessionId);
|
|
145
147
|
case "stopWorkflowRun":
|
|
@@ -539,6 +541,11 @@ export class AgentBridge {
|
|
|
539
541
|
const success = entry.agent.stopBackgroundTask(taskId);
|
|
540
542
|
return { success };
|
|
541
543
|
}
|
|
544
|
+
async backgroundCurrentTask(sessionId) {
|
|
545
|
+
const entry = this.requireSession(sessionId);
|
|
546
|
+
await entry.agent.backgroundCurrentTask();
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
542
549
|
async getWorkflowRuns(sessionId) {
|
|
543
550
|
const entry = this.requireSession(sessionId);
|
|
544
551
|
const runs = await entry.agent.getWorkflowRuns();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Message } from "wave-agent-sdk";
|
|
2
2
|
/**
|
|
3
3
|
* 判断一条 user 消息能否作为 /rewind 检查点。
|
|
4
|
-
* 后台任务通知(task_notification
|
|
5
|
-
*
|
|
4
|
+
* 后台任务通知(task_notification)、hook 注入的消息(source: "hook")
|
|
5
|
+
* 与 bang 命令消息都是系统生成、用户不可见的,不能作为回滚点。
|
|
6
6
|
* CLI 交互式选择器与 stdio listRewindCheckpoints 共用此判定,避免两处漂移。
|
|
7
7
|
*/
|
|
8
8
|
export declare function isUserCheckpointMessage(m: Message): boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 判断一条 user 消息能否作为 /rewind 检查点。
|
|
3
|
-
* 后台任务通知(task_notification
|
|
4
|
-
*
|
|
3
|
+
* 后台任务通知(task_notification)、hook 注入的消息(source: "hook")
|
|
4
|
+
* 与 bang 命令消息都是系统生成、用户不可见的,不能作为回滚点。
|
|
5
5
|
* CLI 交互式选择器与 stdio listRewindCheckpoints 共用此判定,避免两处漂移。
|
|
6
6
|
*/
|
|
7
7
|
export function isUserCheckpointMessage(m) {
|
|
@@ -11,5 +11,7 @@ export function isUserCheckpointMessage(m) {
|
|
|
11
11
|
return false;
|
|
12
12
|
if (m.blocks.some((b) => b.type === "text" && b.source === "hook"))
|
|
13
13
|
return false;
|
|
14
|
+
if (m.blocks.some((b) => b.type === "bang"))
|
|
15
|
+
return false;
|
|
14
16
|
return true;
|
|
15
17
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"wrap-ansi": "^10.0.0",
|
|
44
44
|
"yargs": "^17.7.2",
|
|
45
45
|
"zod": "^3.23.8",
|
|
46
|
-
"wave-agent-sdk": "1.0.
|
|
46
|
+
"wave-agent-sdk": "1.0.6"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/react": "^19.1.8",
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -158,6 +158,84 @@ export interface ChatProviderProps extends BaseAppProps {
|
|
|
158
158
|
children: React.ReactNode;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
interface StreamingUpdateParams {
|
|
162
|
+
messageId: string;
|
|
163
|
+
chunk: string;
|
|
164
|
+
stage: "streaming" | "end";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Window-concat throttle for pure-delta streaming updates: chunks arriving
|
|
169
|
+
* within the cooldown window are merged so no delta is lost (a dropped delta
|
|
170
|
+
* would permanently lose content, unlike the accumulated-payload throttle it
|
|
171
|
+
* replaces). Leading edge fires immediately; the trailing edge carries only
|
|
172
|
+
* chunks that arrived within the window. `end` flushes any pending deltas
|
|
173
|
+
* first, then forwards the end signal right away.
|
|
174
|
+
*/
|
|
175
|
+
function createStreamingWindowThrottle(
|
|
176
|
+
fn: (params: StreamingUpdateParams) => void,
|
|
177
|
+
wait: number,
|
|
178
|
+
): {
|
|
179
|
+
(params: StreamingUpdateParams): void;
|
|
180
|
+
cancel: () => void;
|
|
181
|
+
flush: () => void;
|
|
182
|
+
} {
|
|
183
|
+
let timer: NodeJS.Timeout | null = null;
|
|
184
|
+
let pending: { messageId: string; chunk: string } | null = null;
|
|
185
|
+
|
|
186
|
+
const fire = (stage: "streaming" | "end") => {
|
|
187
|
+
if (pending) {
|
|
188
|
+
fn({ ...pending, stage });
|
|
189
|
+
pending = null;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const throttled = (params: StreamingUpdateParams) => {
|
|
194
|
+
if (params.stage === "end") {
|
|
195
|
+
// Flush any deltas still pending inside the cooldown window first
|
|
196
|
+
if (timer) {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
timer = null;
|
|
199
|
+
}
|
|
200
|
+
fire("streaming");
|
|
201
|
+
fn(params);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (pending) {
|
|
205
|
+
pending.chunk += params.chunk;
|
|
206
|
+
} else {
|
|
207
|
+
pending = { messageId: params.messageId, chunk: params.chunk };
|
|
208
|
+
}
|
|
209
|
+
if (!timer) {
|
|
210
|
+
// Leading edge: fire the current delta immediately, then reset pending so
|
|
211
|
+
// the trailing edge only carries chunks arriving within this window
|
|
212
|
+
fire("streaming");
|
|
213
|
+
timer = setTimeout(() => {
|
|
214
|
+
timer = null;
|
|
215
|
+
fire("streaming");
|
|
216
|
+
}, wait);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
throttled.cancel = () => {
|
|
221
|
+
if (timer) {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
timer = null;
|
|
224
|
+
}
|
|
225
|
+
pending = null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
throttled.flush = () => {
|
|
229
|
+
if (timer) {
|
|
230
|
+
clearTimeout(timer);
|
|
231
|
+
timer = null;
|
|
232
|
+
}
|
|
233
|
+
fire("streaming");
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return throttled;
|
|
237
|
+
}
|
|
238
|
+
|
|
161
239
|
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
162
240
|
children,
|
|
163
241
|
bypassPermissions,
|
|
@@ -188,86 +266,77 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
188
266
|
const [latestTotalTokens, setLatestTotalTokens] = useState(0);
|
|
189
267
|
const [maxInputTokens, setMaxInputTokens] = useState(200000);
|
|
190
268
|
|
|
191
|
-
// Throttled incremental streaming updaters — 500ms
|
|
192
|
-
// as the pre-incremental throttledSetMessages.
|
|
193
|
-
//
|
|
269
|
+
// Throttled incremental streaming updaters — 500ms window-concat, the same
|
|
270
|
+
// interval as the pre-incremental throttledSetMessages. Chunks are pure
|
|
271
|
+
// deltas: within-window chunks are merged so none is dropped, and
|
|
272
|
+
// `stage === "end"` flushes pending deltas + applies the end signal
|
|
273
|
+
// immediately so completion results are never delayed.
|
|
194
274
|
const throttledContentUpdate = useMemo(
|
|
195
275
|
() =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
setMessages((prev) =>
|
|
204
|
-
prev.map((m) => {
|
|
205
|
-
if (m.id !== messageId) return m;
|
|
206
|
-
const textBlockIndex = m.blocks.findIndex(
|
|
207
|
-
(b) => b.type === "text",
|
|
208
|
-
);
|
|
209
|
-
if (textBlockIndex === -1) {
|
|
210
|
-
return {
|
|
211
|
-
...m,
|
|
212
|
-
blocks: [
|
|
213
|
-
...m.blocks,
|
|
214
|
-
{ type: "text", content: accumulated, stage },
|
|
215
|
-
],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
276
|
+
createStreamingWindowThrottle((params) => {
|
|
277
|
+
const { messageId, chunk, stage } = params;
|
|
278
|
+
setMessages((prev) =>
|
|
279
|
+
prev.map((m) => {
|
|
280
|
+
if (m.id !== messageId) return m;
|
|
281
|
+
const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
|
|
282
|
+
if (textBlockIndex === -1) {
|
|
218
283
|
return {
|
|
219
284
|
...m,
|
|
220
|
-
blocks: m.blocks
|
|
221
|
-
idx === textBlockIndex && b.type === "text"
|
|
222
|
-
? { ...b, content: accumulated, stage }
|
|
223
|
-
: b,
|
|
224
|
-
),
|
|
285
|
+
blocks: [...m.blocks, { type: "text", content: chunk, stage }],
|
|
225
286
|
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
...m,
|
|
290
|
+
blocks: m.blocks.map((b, idx) =>
|
|
291
|
+
idx === textBlockIndex && b.type === "text"
|
|
292
|
+
? {
|
|
293
|
+
...b,
|
|
294
|
+
content: (b.content || "") + chunk,
|
|
295
|
+
stage,
|
|
296
|
+
}
|
|
297
|
+
: b,
|
|
298
|
+
),
|
|
299
|
+
};
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
}, 500),
|
|
231
303
|
[],
|
|
232
304
|
);
|
|
233
305
|
|
|
234
306
|
const throttledReasoningUpdate = useMemo(
|
|
235
307
|
() =>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (m.id !== messageId) return m;
|
|
246
|
-
const reasoningBlockIndex = m.blocks.findIndex(
|
|
247
|
-
(b) => b.type === "reasoning",
|
|
248
|
-
);
|
|
249
|
-
if (reasoningBlockIndex === -1) {
|
|
250
|
-
return {
|
|
251
|
-
...m,
|
|
252
|
-
blocks: [
|
|
253
|
-
...m.blocks,
|
|
254
|
-
{ type: "reasoning", content: accumulated, stage },
|
|
255
|
-
],
|
|
256
|
-
};
|
|
257
|
-
}
|
|
308
|
+
createStreamingWindowThrottle((params) => {
|
|
309
|
+
const { messageId, chunk, stage } = params;
|
|
310
|
+
setMessages((prev) =>
|
|
311
|
+
prev.map((m) => {
|
|
312
|
+
if (m.id !== messageId) return m;
|
|
313
|
+
const reasoningBlockIndex = m.blocks.findIndex(
|
|
314
|
+
(b) => b.type === "reasoning",
|
|
315
|
+
);
|
|
316
|
+
if (reasoningBlockIndex === -1) {
|
|
258
317
|
return {
|
|
259
318
|
...m,
|
|
260
|
-
blocks:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
),
|
|
319
|
+
blocks: [
|
|
320
|
+
...m.blocks,
|
|
321
|
+
{ type: "reasoning", content: chunk, stage },
|
|
322
|
+
],
|
|
265
323
|
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
...m,
|
|
327
|
+
blocks: m.blocks.map((b, idx) =>
|
|
328
|
+
idx === reasoningBlockIndex && b.type === "reasoning"
|
|
329
|
+
? {
|
|
330
|
+
...b,
|
|
331
|
+
content: (b.content || "") + chunk,
|
|
332
|
+
stage,
|
|
333
|
+
}
|
|
334
|
+
: b,
|
|
335
|
+
),
|
|
336
|
+
};
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
}, 500),
|
|
271
340
|
[],
|
|
272
341
|
);
|
|
273
342
|
|
|
@@ -477,12 +546,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
477
546
|
onAssistantContentUpdated: (params) => {
|
|
478
547
|
if (isExpandedRef.current) return;
|
|
479
548
|
throttledContentUpdate(params);
|
|
480
|
-
if (params.stage === "end") throttledContentUpdate.flush();
|
|
481
549
|
},
|
|
482
550
|
onAssistantReasoningUpdated: (params) => {
|
|
483
551
|
if (isExpandedRef.current) return;
|
|
484
552
|
throttledReasoningUpdate(params);
|
|
485
|
-
if (params.stage === "end") throttledReasoningUpdate.flush();
|
|
486
553
|
},
|
|
487
554
|
onToolBlockUpdated: (params) => {
|
|
488
555
|
if (isExpandedRef.current) return;
|
|
@@ -730,7 +797,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
730
797
|
originalCwd,
|
|
731
798
|
model,
|
|
732
799
|
initialPermissionMode,
|
|
733
|
-
refreshMessages,
|
|
734
800
|
throttledContentUpdate,
|
|
735
801
|
throttledReasoningUpdate,
|
|
736
802
|
throttledToolBlockUpdate,
|
package/src/stdio/agentBridge.ts
CHANGED
|
@@ -335,6 +335,8 @@ export class AgentBridge {
|
|
|
335
335
|
return this.getBackgroundTaskOutput(p.taskId as string, sessionId);
|
|
336
336
|
case "stopBackgroundTask":
|
|
337
337
|
return this.stopBackgroundTask(p.taskId as string, sessionId);
|
|
338
|
+
case "backgroundCurrentTask":
|
|
339
|
+
return this.backgroundCurrentTask(sessionId);
|
|
338
340
|
|
|
339
341
|
case "getWorkflowRuns":
|
|
340
342
|
return this.getWorkflowRuns(sessionId);
|
|
@@ -905,6 +907,12 @@ export class AgentBridge {
|
|
|
905
907
|
return { success };
|
|
906
908
|
}
|
|
907
909
|
|
|
910
|
+
private async backgroundCurrentTask(sessionId?: string): Promise<null> {
|
|
911
|
+
const entry = this.requireSession(sessionId);
|
|
912
|
+
await entry.agent.backgroundCurrentTask();
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
|
|
908
916
|
private async getWorkflowRuns(
|
|
909
917
|
sessionId?: string,
|
|
910
918
|
): Promise<{ runs: SerializableWorkflowRun[] }> {
|
|
@@ -2,8 +2,8 @@ import type { Message } from "wave-agent-sdk";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 判断一条 user 消息能否作为 /rewind 检查点。
|
|
5
|
-
* 后台任务通知(task_notification
|
|
6
|
-
*
|
|
5
|
+
* 后台任务通知(task_notification)、hook 注入的消息(source: "hook")
|
|
6
|
+
* 与 bang 命令消息都是系统生成、用户不可见的,不能作为回滚点。
|
|
7
7
|
* CLI 交互式选择器与 stdio listRewindCheckpoints 共用此判定,避免两处漂移。
|
|
8
8
|
*/
|
|
9
9
|
export function isUserCheckpointMessage(m: Message): boolean {
|
|
@@ -11,5 +11,6 @@ export function isUserCheckpointMessage(m: Message): boolean {
|
|
|
11
11
|
if (m.blocks.some((b) => b.type === "task_notification")) return false;
|
|
12
12
|
if (m.blocks.some((b) => b.type === "text" && b.source === "hook"))
|
|
13
13
|
return false;
|
|
14
|
+
if (m.blocks.some((b) => b.type === "bang")) return false;
|
|
14
15
|
return true;
|
|
15
16
|
}
|