chatccc 0.2.258 → 0.2.259
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/deepccc-agent/package.json +1 -1
- package/dist/deepccc-agent/src/context.js +12 -3
- package/dist/deepccc-agent/src/index.js +116 -73
- package/dist/deepccc-agent/src/progress/reducer.js +4 -0
- package/dist/deepccc-agent/src/tool-protocol.js +17 -0
- package/dist/src/adapters/ccc-adapter.js +12 -0
- package/dist/src/agent-activity.js +4 -0
- package/dist/src/progress/reducer.js +4 -0
- package/dist/src/response-stall.js +2 -2
- package/dist/src/session.js +20 -4
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { hasMalformedToolProtocolText } from "./tool-protocol.js";
|
|
5
6
|
export const DEFAULT_BUILTIN_CONTEXT_DIR = join(homedir(), ".deepccc", "sessions");
|
|
6
7
|
/** 默认模型上下文窗口:1M(DeepSeek V4 Pro/Flash 原生规格)。 */
|
|
7
8
|
export const DEFAULT_CONTEXT_WINDOW_TOKENS = 1_048_576;
|
|
@@ -290,14 +291,19 @@ export class BuiltinContextManager {
|
|
|
290
291
|
].join("\n"),
|
|
291
292
|
});
|
|
292
293
|
}
|
|
293
|
-
|
|
294
|
+
// Keep malformed provider output on disk for diagnosis, but quarantine it
|
|
295
|
+
// from future prompts so one protocol failure cannot teach the model to
|
|
296
|
+
// repeat the same invalid tool syntax on every later turn.
|
|
297
|
+
messages.push(...this.modelSafeMessages());
|
|
294
298
|
return messages;
|
|
295
299
|
}
|
|
296
300
|
planCompaction() {
|
|
297
|
-
|
|
301
|
+
// The compaction model is still a model input: exclude quarantined protocol
|
|
302
|
+
// leaks here too, otherwise a later summary could reintroduce the bad syntax.
|
|
303
|
+
const messages = this.modelSafeMessages();
|
|
304
|
+
const estimated = estimateBuiltinContextTokens(this.state.summary, messages);
|
|
298
305
|
if (estimated <= this.compactAtTokens)
|
|
299
306
|
return null;
|
|
300
|
-
const messages = this.state.messages;
|
|
301
307
|
if (messages.length <= 1)
|
|
302
308
|
return null;
|
|
303
309
|
const earliestAllowed = Math.max(0, messages.length - this.keepRecentMessages);
|
|
@@ -340,6 +346,9 @@ export class BuiltinContextManager {
|
|
|
340
346
|
writeFileSync(tmp, content, "utf8");
|
|
341
347
|
renameSync(tmp, this.contextFilePath);
|
|
342
348
|
}
|
|
349
|
+
modelSafeMessages() {
|
|
350
|
+
return this.state.messages.filter((message) => message.role !== "assistant" || !hasMalformedToolProtocolText(message.content));
|
|
351
|
+
}
|
|
343
352
|
load() {
|
|
344
353
|
if (!this.persist || !existsSync(this.contextFilePath))
|
|
345
354
|
return emptyState(this.sessionId, this.cwd);
|
|
@@ -15,6 +15,7 @@ import { createRawStreamLog, } from "./raw-stream-log.js";
|
|
|
15
15
|
import { buildPersistedAssistantMessage, buildSummaryPrompt, BuiltinContextManager, defaultBuiltinSessionId, } from "./context.js";
|
|
16
16
|
import { createBuiltinFileTools, MAX_TASK_OUTPUT_CHARS } from "./file-tools.js";
|
|
17
17
|
import { PermissionGate } from "./permissions.js";
|
|
18
|
+
import { hasMalformedToolProtocolText, TOOL_PROTOCOL_RECOVERY_PROMPT, } from "./tool-protocol.js";
|
|
18
19
|
import { buildDefaultSkillDirs, buildSkillsIndexPrompt, scanSkillsDirs, } from "./skills.js";
|
|
19
20
|
import { applyPrivacy, applyPrivacyToJson } from "./privacy.js";
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -400,7 +401,6 @@ export class ChatSession {
|
|
|
400
401
|
catch (err) {
|
|
401
402
|
console.error(`[DeepCCC raw stream log] create failed: ${errorMessage(err)}`);
|
|
402
403
|
}
|
|
403
|
-
const toolContext = [];
|
|
404
404
|
const maxSteps = this.maxSteps;
|
|
405
405
|
// 每次对话前重新扫描技能索引(并行 + mtime 缓存,开销极小):
|
|
406
406
|
// 新技能/修改的技能在下一次对话自动生效(热加载)。
|
|
@@ -423,10 +423,9 @@ export class ChatSession {
|
|
|
423
423
|
? { deepseek: { reasoningEffort: this.effort } }
|
|
424
424
|
: { anthropic: { effort: this.effort } };
|
|
425
425
|
}
|
|
426
|
-
const
|
|
426
|
+
const baseGenerationOptions = {
|
|
427
427
|
model: this.model,
|
|
428
428
|
system,
|
|
429
|
-
messages: modelMessages,
|
|
430
429
|
tools: createBuiltinFileTools(this.cwd, {
|
|
431
430
|
permissionGate: this.permissionGate,
|
|
432
431
|
runTask: this.runTask,
|
|
@@ -435,88 +434,132 @@ export class ChatSession {
|
|
|
435
434
|
abortSignal: signal,
|
|
436
435
|
...(effortProviderOptions ? { providerOptions: effortProviderOptions } : {}),
|
|
437
436
|
};
|
|
438
|
-
let
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
437
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
438
|
+
fullText = "";
|
|
439
|
+
safeAccumulated = "";
|
|
440
|
+
const toolContext = [];
|
|
441
|
+
toolCallsById.clear();
|
|
442
|
+
toolCallOrder.length = 0;
|
|
443
|
+
let lastReasoningProgressAt;
|
|
444
|
+
const attemptMessages = attempt === 0
|
|
445
|
+
? modelMessages
|
|
446
|
+
: [...modelMessages, { role: "user", content: TOOL_PROTOCOL_RECOVERY_PROMPT }];
|
|
447
|
+
const generationOptions = {
|
|
448
|
+
...baseGenerationOptions,
|
|
449
|
+
messages: attemptMessages,
|
|
450
|
+
};
|
|
451
|
+
let stream;
|
|
452
|
+
if (appConfig.streaming) {
|
|
453
|
+
const result = streamText(generationOptions);
|
|
454
|
+
stream = result.fullStream ?? textStreamToFullStream(result.textStream);
|
|
456
455
|
}
|
|
457
|
-
else
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
toolCallOrder.push(part.toolCallId);
|
|
461
|
-
yield {
|
|
462
|
-
type: "tool_use",
|
|
463
|
-
id: part.toolCallId,
|
|
464
|
-
name: part.toolName,
|
|
465
|
-
input: applyPrivacyToJson(part.input),
|
|
466
|
-
};
|
|
456
|
+
else {
|
|
457
|
+
const result = await generateText(generationOptions);
|
|
458
|
+
stream = generateResultToFullStream(result);
|
|
467
459
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
460
|
+
for await (const part of stream) {
|
|
461
|
+
rawLog?.writeLine(safeRawStreamJson(part));
|
|
462
|
+
if (part.type === "reasoning-start" || part.type === "reasoning-delta") {
|
|
463
|
+
// Reasoning content remains private. A throttled heartbeat is enough
|
|
464
|
+
// for ChatCCC to distinguish active inference from a stalled stream.
|
|
465
|
+
const now = Date.now();
|
|
466
|
+
if (lastReasoningProgressAt === undefined || now - lastReasoningProgressAt >= 1_000) {
|
|
467
|
+
lastReasoningProgressAt = now;
|
|
468
|
+
yield { type: "progress", phase: "reasoning" };
|
|
469
|
+
}
|
|
473
470
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
471
|
+
else if (part.type === "text-delta") {
|
|
472
|
+
fullText += part.text;
|
|
473
|
+
// 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
|
|
474
|
+
// fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
|
|
475
|
+
const safeText = applyPrivacy(part.text);
|
|
476
|
+
safeAccumulated += safeText;
|
|
477
|
+
yield { type: "text", text: safeText, accumulated: safeAccumulated };
|
|
478
|
+
}
|
|
479
|
+
else if (part.type === "tool-call") {
|
|
480
|
+
toolContext.push(`tool_call ${part.toolName}: ${safeJson(part.input)}`);
|
|
481
|
+
toolCallsById.set(part.toolCallId, { name: part.toolName, input: safeJson(part.input) });
|
|
482
|
+
toolCallOrder.push(part.toolCallId);
|
|
483
|
+
yield {
|
|
484
|
+
type: "tool_use",
|
|
485
|
+
id: part.toolCallId,
|
|
486
|
+
name: part.toolName,
|
|
487
|
+
input: applyPrivacyToJson(part.input),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
else if (part.type === "tool-result") {
|
|
491
|
+
toolContext.push(`tool_result ${part.toolName}: ${truncateToolContext(safeJson(part.output))}`);
|
|
492
|
+
const call = toolCallsById.get(part.toolCallId);
|
|
493
|
+
if (call)
|
|
494
|
+
call.output = truncateToolContext(safeJson(part.output));
|
|
495
|
+
yield {
|
|
496
|
+
type: "tool_result",
|
|
497
|
+
tool_use_id: part.toolCallId,
|
|
498
|
+
name: part.toolName,
|
|
499
|
+
content: applyPrivacyToJson(part.output),
|
|
500
|
+
is_error: false,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
else if (part.type === "tool-error") {
|
|
504
|
+
const message = errorMessage(part.error);
|
|
505
|
+
toolContext.push(`tool_error ${part.toolName}: ${message}`);
|
|
506
|
+
const call = toolCallsById.get(part.toolCallId);
|
|
507
|
+
if (call) {
|
|
508
|
+
call.output = message;
|
|
509
|
+
call.is_error = true;
|
|
510
|
+
}
|
|
511
|
+
yield {
|
|
512
|
+
type: "tool_result",
|
|
513
|
+
tool_use_id: part.toolCallId,
|
|
514
|
+
name: part.toolName,
|
|
515
|
+
content: applyPrivacy(message),
|
|
516
|
+
is_error: true,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
else if (part.type === "error") {
|
|
520
|
+
const message = errorMessage(part.error);
|
|
521
|
+
yield { type: "error", message: applyPrivacy(message) };
|
|
522
|
+
throw new Error(message);
|
|
489
523
|
}
|
|
490
|
-
yield {
|
|
491
|
-
type: "tool_result",
|
|
492
|
-
tool_use_id: part.toolCallId,
|
|
493
|
-
name: part.toolName,
|
|
494
|
-
content: applyPrivacy(message),
|
|
495
|
-
is_error: true,
|
|
496
|
-
};
|
|
497
524
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
525
|
+
if (hasMalformedToolProtocolText(fullText)) {
|
|
526
|
+
console.warn(`[DeepCCC] malformed DSML tool output detected for ${this.context.sessionId} `
|
|
527
|
+
+ `(attempt ${attempt + 1}/2, structuredToolCalls=${toolCallOrder.length})`);
|
|
528
|
+
rawLog?.writeLine(safeRawStreamJson({
|
|
529
|
+
type: "deepccc_tool_protocol_recovery",
|
|
530
|
+
attempt: attempt + 1,
|
|
531
|
+
structuredToolCalls: toolCallOrder.length,
|
|
532
|
+
}));
|
|
533
|
+
yield { type: "text_reset" };
|
|
534
|
+
if (attempt === 0 && toolCallOrder.length === 0) {
|
|
535
|
+
yield { type: "status", phase: "generating" };
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
throw new Error(toolCallOrder.length > 0
|
|
539
|
+
? "工具调用协议异常:检测到混合的结构化与 DSML 文本调用,为避免重复执行工具,本轮已安全终止"
|
|
540
|
+
: "工具调用协议异常:模型重试后仍输出了无效的 DSML 工具调用");
|
|
502
541
|
}
|
|
542
|
+
completed = true;
|
|
543
|
+
const collectedToolCalls = toolCallOrder
|
|
544
|
+
.map((id) => toolCallsById.get(id))
|
|
545
|
+
.filter((call) => call !== undefined);
|
|
546
|
+
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
547
|
+
fullText,
|
|
548
|
+
transcriptLines: toolContext,
|
|
549
|
+
toolCalls: collectedToolCalls,
|
|
550
|
+
}));
|
|
551
|
+
yield { type: "done", text: safeAccumulated };
|
|
552
|
+
return;
|
|
503
553
|
}
|
|
504
|
-
completed = true;
|
|
505
|
-
const collectedToolCalls = toolCallOrder
|
|
506
|
-
.map((id) => toolCallsById.get(id))
|
|
507
|
-
.filter((call) => call !== undefined);
|
|
508
|
-
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
509
|
-
fullText,
|
|
510
|
-
transcriptLines: toolContext,
|
|
511
|
-
toolCalls: collectedToolCalls,
|
|
512
|
-
}));
|
|
513
|
-
yield { type: "done", text: safeAccumulated };
|
|
514
554
|
}
|
|
515
555
|
catch (err) {
|
|
516
556
|
const message = err instanceof Error ? err.message : String(err);
|
|
557
|
+
const malformedProtocolOutput = hasMalformedToolProtocolText(fullText);
|
|
558
|
+
if (malformedProtocolOutput)
|
|
559
|
+
yield { type: "text_reset" };
|
|
517
560
|
if (err.name === "AbortError" || signal?.aborted) {
|
|
518
561
|
// 被中断时,不保存不完整的助手消息
|
|
519
|
-
if (fullText) {
|
|
562
|
+
if (fullText && !malformedProtocolOutput) {
|
|
520
563
|
this.context.appendMessage({ role: "assistant", content: `${fullText}\n[interrupted]` });
|
|
521
564
|
}
|
|
522
565
|
yield { type: "done", text: safeAccumulated };
|
|
@@ -57,6 +57,10 @@ export function reduceProgress(prev, event) {
|
|
|
57
57
|
case "text":
|
|
58
58
|
// accumulated 是全文累积,直接全量替换,天然幂等
|
|
59
59
|
return withProgressView(prev, { text: event.accumulated });
|
|
60
|
+
case "progress":
|
|
61
|
+
return withProgressView(prev, { headerTitle: "思考中..." });
|
|
62
|
+
case "text_reset":
|
|
63
|
+
return withProgressView(prev, { text: "", tools: [] });
|
|
60
64
|
case "tool_use": {
|
|
61
65
|
const tool = {
|
|
62
66
|
id: event.id ?? `tool-${prev.tools.length + 1}`,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects provider output that leaked DeepSeek's internal DSML tool-call syntax
|
|
3
|
+
* into normal assistant text. A trailing closing tag is intentionally required:
|
|
4
|
+
* mentioning DSML in prose or a code sample must not trigger recovery.
|
|
5
|
+
*/
|
|
6
|
+
const MALFORMED_DSML_TRAILER = /<\/[||]{2}DSML[||]{2}(?:parameter|invoke)>\s*$/iu;
|
|
7
|
+
const RENDERED_TOOL_CALL = /\[调用\s+[A-Za-z_][\w.-]*\]/u;
|
|
8
|
+
const DSML_INVOKE_TAG = /<[||]{2}DSML[||]{2}invoke\b/iu;
|
|
9
|
+
export function hasMalformedToolProtocolText(text) {
|
|
10
|
+
return MALFORMED_DSML_TRAILER.test(text)
|
|
11
|
+
&& (RENDERED_TOOL_CALL.test(text) || DSML_INVOKE_TAG.test(text));
|
|
12
|
+
}
|
|
13
|
+
export const TOOL_PROTOCOL_RECOVERY_PROMPT = [
|
|
14
|
+
"[系统恢复提示] 上一次响应泄漏了内部工具调用协议,因此已被丢弃。",
|
|
15
|
+
"请重新完成当前用户请求。需要调用工具时,只能使用 API 提供的结构化工具调用;不要把 DSML 或工具参数作为普通文本输出。",
|
|
16
|
+
"不要提及本恢复提示,也不要假装工具已经执行。",
|
|
17
|
+
].join("\n");
|
|
@@ -55,6 +55,18 @@ export function createCccAdapter(options = {}) {
|
|
|
55
55
|
}],
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
+
else if (event.type === "progress") {
|
|
59
|
+
yield {
|
|
60
|
+
type: "assistant",
|
|
61
|
+
blocks: [{ type: "agent_progress", phase: event.phase }],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
else if (event.type === "text_reset") {
|
|
65
|
+
yield {
|
|
66
|
+
type: "assistant",
|
|
67
|
+
blocks: [{ type: "text_reset" }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
58
70
|
else if (event.type === "text") {
|
|
59
71
|
yield {
|
|
60
72
|
type: "assistant",
|
|
@@ -67,6 +67,10 @@ export function updateAgentActivity(tracker, block, now = Date.now()) {
|
|
|
67
67
|
if (tracker.activeTools.size > 0)
|
|
68
68
|
return false;
|
|
69
69
|
switch (block.type) {
|
|
70
|
+
case "agent_progress":
|
|
71
|
+
return setActivity(tracker, { kind: "thinking", startedAt: now });
|
|
72
|
+
case "text_reset":
|
|
73
|
+
return setActivity(tracker, { kind: "responding", startedAt: now });
|
|
70
74
|
case "agent_status":
|
|
71
75
|
return setActivity(tracker, {
|
|
72
76
|
kind: block.status === "compacting" ? "compacting" : "responding",
|
|
@@ -57,6 +57,10 @@ export function reduceProgress(prev, event) {
|
|
|
57
57
|
case "text":
|
|
58
58
|
// accumulated 是全文累积,直接全量替换,天然幂等
|
|
59
59
|
return withProgressView(prev, { text: event.accumulated });
|
|
60
|
+
case "progress":
|
|
61
|
+
return withProgressView(prev, { headerTitle: "思考中..." });
|
|
62
|
+
case "text_reset":
|
|
63
|
+
return withProgressView(prev, { text: "", tools: [] });
|
|
60
64
|
case "tool_use": {
|
|
61
65
|
const tool = {
|
|
62
66
|
id: event.id ?? `tool-${prev.tools.length + 1}`,
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Tracks how long the displayed output character count has remained unchanged.
|
|
3
3
|
* Leaving a monitored phase clears the window; returning starts a fresh one.
|
|
4
4
|
*/
|
|
5
|
-
export function observeResponseProgress(previous, isMonitoredPhase, totalChars, now = Date.now()) {
|
|
5
|
+
export function observeResponseProgress(previous, isMonitoredPhase, totalChars, now = Date.now(), heartbeat = false) {
|
|
6
6
|
if (!isMonitoredPhase)
|
|
7
7
|
return undefined;
|
|
8
|
-
if (previous?.totalChars === totalChars)
|
|
8
|
+
if (!heartbeat && previous?.totalChars === totalChars)
|
|
9
9
|
return previous;
|
|
10
10
|
return { totalChars, unchangedSince: now };
|
|
11
11
|
}
|
package/dist/src/session.js
CHANGED
|
@@ -233,7 +233,7 @@ function formatAutoEndedReply(finalReply) {
|
|
|
233
233
|
* 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
|
|
234
234
|
*/
|
|
235
235
|
function monitorsOutputProgress(kind) {
|
|
236
|
-
return kind === "responding";
|
|
236
|
+
return kind === "responding" || kind === "thinking";
|
|
237
237
|
}
|
|
238
238
|
function formatTerminalReply(status, finalReply, terminalError) {
|
|
239
239
|
if (status === "auto_ended")
|
|
@@ -528,9 +528,9 @@ export function getAdapterForTool(tool, sessionId) {
|
|
|
528
528
|
apiKey: config.ccc.DEEPSEEK_API_KEY,
|
|
529
529
|
baseURL: config.ccc.DEEPSEEK_BASE_URL,
|
|
530
530
|
model: effectiveModel || undefined,
|
|
531
|
-
effort: effectiveEffort || undefined,
|
|
532
531
|
compactionTimeoutMs: config.ccc.compactionTimeoutMs,
|
|
533
532
|
contextWindow: config.ccc.contextWindow,
|
|
533
|
+
...(effectiveEffort ? { effort: effectiveEffort } : {}),
|
|
534
534
|
// 留空("")不传 → ChatSession 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER)
|
|
535
535
|
...(config.ccc.provider ? { provider: config.ccc.provider } : {}),
|
|
536
536
|
...(config.ccc.subModel ? { subModel: config.ccc.subModel } : {}),
|
|
@@ -761,6 +761,14 @@ export function accumulateBlockContent(block, state, toolCallMap) {
|
|
|
761
761
|
// 覆盖而非追加:适配器已保证这是一段完整最终文本(如 Cursor 流末快照)
|
|
762
762
|
state.finalCompleteText = block.text;
|
|
763
763
|
break;
|
|
764
|
+
case "text_reset":
|
|
765
|
+
state.accumulatedContent = "";
|
|
766
|
+
state.finalText = "";
|
|
767
|
+
state.finalCompleteText = "";
|
|
768
|
+
state.chunkCount = 0;
|
|
769
|
+
break;
|
|
770
|
+
case "agent_progress":
|
|
771
|
+
break;
|
|
764
772
|
case "compact_boundary": {
|
|
765
773
|
const triggerLabel = block.trigger === "manual" ? "手动" : "自动"; // 手动 / 自动
|
|
766
774
|
state.accumulatedContent +=
|
|
@@ -1238,7 +1246,15 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
1238
1246
|
}
|
|
1239
1247
|
}
|
|
1240
1248
|
let activityChanged = false;
|
|
1249
|
+
let progressHeartbeat = false;
|
|
1250
|
+
let outputReset = false;
|
|
1241
1251
|
for (const block of unifiedMsg.blocks) {
|
|
1252
|
+
if (block.type === "agent_progress")
|
|
1253
|
+
progressHeartbeat = true;
|
|
1254
|
+
if (block.type === "text_reset") {
|
|
1255
|
+
outputReset = true;
|
|
1256
|
+
toolCallMap.clear();
|
|
1257
|
+
}
|
|
1242
1258
|
if (updateAgentActivity(activityTracker, block))
|
|
1243
1259
|
activityChanged = true;
|
|
1244
1260
|
accumulateBlockContent(block, state, toolCallMap);
|
|
@@ -1263,11 +1279,11 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
1263
1279
|
prompt.responseProgress = observeResponseProgress(
|
|
1264
1280
|
// starting → responding 本身是一次有效进展,即便首个文本块仍为空也应
|
|
1265
1281
|
// 重新计时;其它活动阶段会清空观察窗口。
|
|
1266
|
-
activityChanged ? undefined : prompt.responseProgress, monitorsOutputProgress(activityTracker.activity.kind), totalChars, Date.now());
|
|
1282
|
+
activityChanged ? undefined : prompt.responseProgress, monitorsOutputProgress(activityTracker.activity.kind), totalChars, Date.now(), progressHeartbeat);
|
|
1267
1283
|
}
|
|
1268
1284
|
// 定时写入文件
|
|
1269
1285
|
const now2 = Date.now();
|
|
1270
|
-
if (activityChanged || now2 - lastFileWrite >= FILE_WRITE_INTERVAL_MS) {
|
|
1286
|
+
if (activityChanged || outputReset || now2 - lastFileWrite >= FILE_WRITE_INTERVAL_MS) {
|
|
1271
1287
|
lastFileWrite = now2;
|
|
1272
1288
|
await writeStreamState({
|
|
1273
1289
|
sessionId,
|