chatccc 0.2.286 → 0.2.288
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 +50 -46
- package/deepccc-agent/README.md +153 -151
- package/deepccc-agent/package.json +1 -1
- package/dist/deepccc-agent/src/cli.js +22 -2
- package/dist/deepccc-agent/src/index.js +211 -139
- package/dist/deepccc-agent/src/progress/reducer.js +6 -0
- package/dist/src/adapters/ccc-adapter.js +7 -1
- package/dist/src/adapters/codex-adapter.js +184 -179
- package/dist/src/adapters/codex-app-server.js +280 -0
- package/dist/src/agent-activity.js +3 -0
- package/dist/src/cards.js +38 -0
- package/dist/src/config-utils.js +36 -0
- package/dist/src/config.js +34 -1
- package/dist/src/execution-transcript.js +3 -0
- package/dist/src/index.js +2 -1
- package/dist/src/orchestrator.js +35 -4
- package/dist/src/progress/reducer.js +6 -0
- package/dist/src/session-chat-binding.js +43 -0
- package/dist/src/session.js +36 -1
- package/package.json +1 -1
|
@@ -459,6 +459,10 @@ async function runRepl(args) {
|
|
|
459
459
|
}
|
|
460
460
|
let currentAbort = null;
|
|
461
461
|
const ctrlCState = createCtrlCState();
|
|
462
|
+
// 协作式让位:agent 运行期间用户仍可输入,新行进入注入队列,由内核在每个
|
|
463
|
+
// model step 边界 drain 后吸收到当前 turn(而非等待整轮结束)。
|
|
464
|
+
let chatRunning = false;
|
|
465
|
+
const pendingInjections = [];
|
|
462
466
|
rl.prompt();
|
|
463
467
|
rl.on("line", async (line) => {
|
|
464
468
|
ctrlCState.reset();
|
|
@@ -467,6 +471,17 @@ async function runRepl(args) {
|
|
|
467
471
|
rl.prompt();
|
|
468
472
|
return;
|
|
469
473
|
}
|
|
474
|
+
if (chatRunning) {
|
|
475
|
+
// 运行期间不并发启动新 turn:把输入注入当前 turn 的 step 边界。
|
|
476
|
+
if (input === "exit" || input.startsWith("/")) {
|
|
477
|
+
process.stdout.write(`${C.dim}[running] 命令将在本轮结束后生效;或按两次 Ctrl+C 中断${C.reset}\n`);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
pendingInjections.push(buildAttachmentPrompt(input, pendingAttachments));
|
|
481
|
+
pendingAttachments = [];
|
|
482
|
+
process.stdout.write(`${C.dim}[queued] 消息将在当前步骤后注入本轮${C.reset}\n`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
470
485
|
if (input === "exit") {
|
|
471
486
|
process.stdout.write(`${C.dim}bye${C.reset}\n`);
|
|
472
487
|
rl.close();
|
|
@@ -510,10 +525,11 @@ async function runRepl(args) {
|
|
|
510
525
|
const chatInput = buildAttachmentPrompt(input, pendingAttachments);
|
|
511
526
|
pendingAttachments = [];
|
|
512
527
|
let lastAccumulated = "";
|
|
513
|
-
|
|
528
|
+
chatRunning = true;
|
|
529
|
+
for await (const event of session.chat(chatInput, signal, () => pendingInjections.shift())) {
|
|
514
530
|
if (renderer && view) {
|
|
515
531
|
view = reduceProgress(view, event);
|
|
516
|
-
if (event.type === "text" || event.type === "compact" || event.type === "status") {
|
|
532
|
+
if (event.type === "text" || event.type === "compact" || event.type === "status" || event.type === "input_injected") {
|
|
517
533
|
renderer.render(view);
|
|
518
534
|
}
|
|
519
535
|
else {
|
|
@@ -543,6 +559,9 @@ async function runRepl(args) {
|
|
|
543
559
|
const status = event.is_error ? "error" : "ok";
|
|
544
560
|
console.log(`${C.dim}[tool result] ${event.name ?? event.tool_use_id} ${status}${C.reset}`);
|
|
545
561
|
}
|
|
562
|
+
else if (event.type === "input_injected") {
|
|
563
|
+
console.log(`\n${C.dim}[injected] ${event.text}${C.reset}`);
|
|
564
|
+
}
|
|
546
565
|
else if (event.type === "error") {
|
|
547
566
|
console.log(`\n${C.yellow}[error] ${event.message}${C.reset}`);
|
|
548
567
|
}
|
|
@@ -561,6 +580,7 @@ async function runRepl(args) {
|
|
|
561
580
|
finally {
|
|
562
581
|
activeRenderer = null;
|
|
563
582
|
activeView = null;
|
|
583
|
+
chatRunning = false;
|
|
564
584
|
if (renderer && view && !rendererEnded) {
|
|
565
585
|
// 定型终态区块(完成/已停止/异常结束)留在屏幕上,恢复光标
|
|
566
586
|
renderer.end(view);
|
|
@@ -246,6 +246,17 @@ function normalizeAnthropicBaseURL(baseURL) {
|
|
|
246
246
|
// DeepSeek Anthropic 端点示例:https://api.deepseek.com/anthropic/v1。
|
|
247
247
|
return baseURL.trim().replace(/\/+$/, "");
|
|
248
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* 协作式让位的内部中断信号:在 prepareStep(每个模型 step 前)检测到外部
|
|
251
|
+
* 注入消息时抛出,触发 chat() 持久化当前中间态 → 注入 user 消息 → 以新
|
|
252
|
+
* messages 重启 streamText,让 agent 在当前 turn 内吸收新指令而非结束整轮。
|
|
253
|
+
*/
|
|
254
|
+
class InputInjectionInterrupt extends Error {
|
|
255
|
+
constructor() {
|
|
256
|
+
super("input injection requested at step boundary");
|
|
257
|
+
this.name = "InputInjectionInterrupt";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
249
260
|
export class ChatSession {
|
|
250
261
|
model;
|
|
251
262
|
/** 子模型实例;未配置 subModel 时与主模型同一实例 */
|
|
@@ -396,7 +407,7 @@ export class ChatSession {
|
|
|
396
407
|
}
|
|
397
408
|
return systemContent.join("\n");
|
|
398
409
|
}
|
|
399
|
-
async *chat(userMessage, signal) {
|
|
410
|
+
async *chat(userMessage, signal, drainInput) {
|
|
400
411
|
this.context.appendMessage({ role: "user", content: userMessage });
|
|
401
412
|
let fullText = "";
|
|
402
413
|
let safeAccumulated = "";
|
|
@@ -469,6 +480,13 @@ export class ChatSession {
|
|
|
469
480
|
? { [OPENAI_COMPATIBLE_PROVIDER_NAME]: { reasoningEffort: this.effort } }
|
|
470
481
|
: { anthropic: { effort: this.effort } };
|
|
471
482
|
}
|
|
483
|
+
// 协作式让位(仅 streaming 模式):外部可在每个 model step 边界 drain 一条
|
|
484
|
+
// 新消息注入当前 turn。non-streaming 下 generateText 是原子请求,无法在 step
|
|
485
|
+
// 边界取回中间工具结果,因此忽略 drainInput(消息留在上层队列,整轮结束后消费)。
|
|
486
|
+
const canInject = this.streaming && typeof drainInput === "function";
|
|
487
|
+
// pendingInjection 是 prepareStep 与 catch 之间的注入信号槽。用标志而非
|
|
488
|
+
// instanceof 判断,因为 provider SDK 可能包装抛出的错误。
|
|
489
|
+
let pendingInjection = null;
|
|
472
490
|
const baseGenerationOptions = {
|
|
473
491
|
model: this.model,
|
|
474
492
|
system,
|
|
@@ -483,6 +501,13 @@ export class ChatSession {
|
|
|
483
501
|
stopWhen: maxSteps !== undefined ? stepCountIs(maxSteps) : isLoopFinished(),
|
|
484
502
|
abortSignal: signal,
|
|
485
503
|
prepareStep: ({ messages, stepNumber }) => {
|
|
504
|
+
if (canInject) {
|
|
505
|
+
const injected = drainInput();
|
|
506
|
+
if (injected) {
|
|
507
|
+
pendingInjection = injected;
|
|
508
|
+
throw new InputInjectionInterrupt();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
486
511
|
const compacted = compactToolLoopMessages(messages);
|
|
487
512
|
if (compacted.compactedResults > 0) {
|
|
488
513
|
rawLog?.writeLine(safeRawStreamJson({
|
|
@@ -500,151 +525,198 @@ export class ChatSession {
|
|
|
500
525
|
: {}),
|
|
501
526
|
...(effortProviderOptions ? { providerOptions: effortProviderOptions } : {}),
|
|
502
527
|
};
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
for await (const part of stream) {
|
|
533
|
-
rawLog?.writeLine(safeRawStreamJson(part));
|
|
534
|
-
if (part.type === "finish") {
|
|
535
|
-
receivedFinish = true;
|
|
536
|
-
finishReason = part.finishReason;
|
|
537
|
-
}
|
|
538
|
-
if (part.type === "reasoning-start" || part.type === "reasoning-delta") {
|
|
539
|
-
// Reasoning content remains private. A throttled heartbeat is enough
|
|
540
|
-
// for ChatCCC to distinguish active inference from a stalled stream.
|
|
541
|
-
const now = Date.now();
|
|
542
|
-
if (lastReasoningProgressAt === undefined || now - lastReasoningProgressAt >= 1_000) {
|
|
543
|
-
lastReasoningProgressAt = now;
|
|
544
|
-
yield { type: "progress", phase: "reasoning" };
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
else if (part.type === "text-delta") {
|
|
548
|
-
fullText += part.text;
|
|
549
|
-
const previous = timeline[timeline.length - 1];
|
|
550
|
-
if (previous?.type === "text")
|
|
551
|
-
previous.text += part.text;
|
|
552
|
-
else
|
|
553
|
-
timeline.push({ type: "text", text: part.text });
|
|
554
|
-
// 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
|
|
555
|
-
// fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
|
|
556
|
-
const safeText = applyPrivacy(part.text);
|
|
557
|
-
safeAccumulated += safeText;
|
|
558
|
-
yield { type: "text", text: safeText, accumulated: safeAccumulated };
|
|
559
|
-
}
|
|
560
|
-
else if (part.type === "tool-call") {
|
|
561
|
-
const input = safeJson(part.input);
|
|
562
|
-
toolContext.push(`tool_call ${part.toolName}: ${input}`);
|
|
563
|
-
toolCallsById.set(part.toolCallId, { id: part.toolCallId, name: part.toolName, input });
|
|
564
|
-
toolCallOrder.push(part.toolCallId);
|
|
565
|
-
timeline.push({ type: "tool_use", id: part.toolCallId, name: part.toolName, input });
|
|
566
|
-
yield {
|
|
567
|
-
type: "tool_use",
|
|
568
|
-
id: part.toolCallId,
|
|
569
|
-
name: part.toolName,
|
|
570
|
-
input: applyPrivacyToJson(part.input),
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
else if (part.type === "tool-result") {
|
|
574
|
-
const output = truncateToolContext(safeJson(part.output));
|
|
575
|
-
toolContext.push(`tool_result ${part.toolName}: ${output}`);
|
|
576
|
-
const call = toolCallsById.get(part.toolCallId);
|
|
577
|
-
if (call)
|
|
578
|
-
call.output = output;
|
|
579
|
-
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output });
|
|
580
|
-
yield {
|
|
581
|
-
type: "tool_result",
|
|
582
|
-
tool_use_id: part.toolCallId,
|
|
583
|
-
name: part.toolName,
|
|
584
|
-
content: applyPrivacyToJson(part.output),
|
|
585
|
-
is_error: false,
|
|
528
|
+
// 注入重启后 context 已变(中间 assistant + 注入 user),需要重新构建并应用
|
|
529
|
+
// 与首次一致的 recovery-hint / anthropic 兼容提示。
|
|
530
|
+
const decorateMessages = (msgs) => {
|
|
531
|
+
const hinted = maybeAppendCompactionRecoveryHint(msgs, this.context.summary, appConfig.rawStreamLogs.enabled, this.context.sessionId);
|
|
532
|
+
return this.provider === "anthropic"
|
|
533
|
+
? addAnthropicToolJsonCompatibilityNote(hinted)
|
|
534
|
+
: hinted;
|
|
535
|
+
};
|
|
536
|
+
let currentMessages = modelMessages;
|
|
537
|
+
while (true) {
|
|
538
|
+
pendingInjection = null;
|
|
539
|
+
try {
|
|
540
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
541
|
+
fullText = "";
|
|
542
|
+
toolContext = [];
|
|
543
|
+
toolCallsById.clear();
|
|
544
|
+
toolCallOrder.length = 0;
|
|
545
|
+
timeline.length = 0;
|
|
546
|
+
// safeAccumulated 是整轮展示累积:注入延续时不重置,只有工具协议恢复
|
|
547
|
+
// (重新生成、已 yield text_reset)才重置。
|
|
548
|
+
if (attempt === 1)
|
|
549
|
+
safeAccumulated = "";
|
|
550
|
+
let lastReasoningProgressAt;
|
|
551
|
+
const attemptMessages = attempt === 0
|
|
552
|
+
? currentMessages
|
|
553
|
+
: [...currentMessages, { role: "user", content: TOOL_PROTOCOL_RECOVERY_PROMPT }];
|
|
554
|
+
const generationOptions = {
|
|
555
|
+
...baseGenerationOptions,
|
|
556
|
+
messages: attemptMessages,
|
|
586
557
|
};
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
558
|
+
let stream;
|
|
559
|
+
let requiresFinish = false;
|
|
560
|
+
let receivedFinish = false;
|
|
561
|
+
let finishReason;
|
|
562
|
+
if (this.streaming) {
|
|
563
|
+
const result = streamText(generationOptions);
|
|
564
|
+
requiresFinish = result.fullStream != null;
|
|
565
|
+
stream = result.fullStream ?? textStreamToFullStream(result.textStream);
|
|
595
566
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
567
|
+
else {
|
|
568
|
+
const result = await generateText(generationOptions);
|
|
569
|
+
finishReason = result.finishReason;
|
|
570
|
+
stream = generateResultToFullStream(result);
|
|
571
|
+
}
|
|
572
|
+
for await (const part of stream) {
|
|
573
|
+
rawLog?.writeLine(safeRawStreamJson(part));
|
|
574
|
+
if (part.type === "finish") {
|
|
575
|
+
receivedFinish = true;
|
|
576
|
+
finishReason = part.finishReason;
|
|
577
|
+
}
|
|
578
|
+
if (part.type === "reasoning-start" || part.type === "reasoning-delta") {
|
|
579
|
+
// Reasoning content remains private. A throttled heartbeat is enough
|
|
580
|
+
// for ChatCCC to distinguish active inference from a stalled stream.
|
|
581
|
+
const now = Date.now();
|
|
582
|
+
if (lastReasoningProgressAt === undefined || now - lastReasoningProgressAt >= 1_000) {
|
|
583
|
+
lastReasoningProgressAt = now;
|
|
584
|
+
yield { type: "progress", phase: "reasoning" };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else if (part.type === "text-delta") {
|
|
588
|
+
fullText += part.text;
|
|
589
|
+
const previous = timeline[timeline.length - 1];
|
|
590
|
+
if (previous?.type === "text")
|
|
591
|
+
previous.text += part.text;
|
|
592
|
+
else
|
|
593
|
+
timeline.push({ type: "text", text: part.text });
|
|
594
|
+
// 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
|
|
595
|
+
// fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
|
|
596
|
+
const safeText = applyPrivacy(part.text);
|
|
597
|
+
safeAccumulated += safeText;
|
|
598
|
+
yield { type: "text", text: safeText, accumulated: safeAccumulated };
|
|
599
|
+
}
|
|
600
|
+
else if (part.type === "tool-call") {
|
|
601
|
+
const input = safeJson(part.input);
|
|
602
|
+
toolContext.push(`tool_call ${part.toolName}: ${input}`);
|
|
603
|
+
toolCallsById.set(part.toolCallId, { id: part.toolCallId, name: part.toolName, input });
|
|
604
|
+
toolCallOrder.push(part.toolCallId);
|
|
605
|
+
timeline.push({ type: "tool_use", id: part.toolCallId, name: part.toolName, input });
|
|
606
|
+
yield {
|
|
607
|
+
type: "tool_use",
|
|
608
|
+
id: part.toolCallId,
|
|
609
|
+
name: part.toolName,
|
|
610
|
+
input: applyPrivacyToJson(part.input),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
else if (part.type === "tool-result") {
|
|
614
|
+
const output = truncateToolContext(safeJson(part.output));
|
|
615
|
+
toolContext.push(`tool_result ${part.toolName}: ${output}`);
|
|
616
|
+
const call = toolCallsById.get(part.toolCallId);
|
|
617
|
+
if (call)
|
|
618
|
+
call.output = output;
|
|
619
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output });
|
|
620
|
+
yield {
|
|
621
|
+
type: "tool_result",
|
|
622
|
+
tool_use_id: part.toolCallId,
|
|
623
|
+
name: part.toolName,
|
|
624
|
+
content: applyPrivacyToJson(part.output),
|
|
625
|
+
is_error: false,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
else if (part.type === "tool-error") {
|
|
629
|
+
const message = errorMessage(part.error);
|
|
630
|
+
toolContext.push(`tool_error ${part.toolName}: ${message}`);
|
|
631
|
+
const call = toolCallsById.get(part.toolCallId);
|
|
632
|
+
if (call) {
|
|
633
|
+
call.output = message;
|
|
634
|
+
call.is_error = true;
|
|
635
|
+
}
|
|
636
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output: message, is_error: true });
|
|
637
|
+
yield {
|
|
638
|
+
type: "tool_result",
|
|
639
|
+
tool_use_id: part.toolCallId,
|
|
640
|
+
name: part.toolName,
|
|
641
|
+
content: applyPrivacy(message),
|
|
642
|
+
is_error: true,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
else if (part.type === "error") {
|
|
646
|
+
if (pendingInjection !== null) {
|
|
647
|
+
// prepareStep 里的注入中断被 provider 转成了 error part。
|
|
648
|
+
throw new InputInjectionInterrupt();
|
|
649
|
+
}
|
|
650
|
+
const message = errorMessage(part.error);
|
|
651
|
+
yield { type: "error", message: applyPrivacy(message) };
|
|
652
|
+
throw new Error(message);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (!signal?.aborted) {
|
|
656
|
+
if (requiresFinish && !receivedFinish)
|
|
657
|
+
throw new Error("DeepCCC 输出流中断:未收到模型完成事件,回复可能不完整");
|
|
658
|
+
if (finishReason === "error" || finishReason === "length")
|
|
659
|
+
throw new Error(`DeepCCC 未正常完成:finishReason=${finishReason},回复可能不完整`);
|
|
660
|
+
if (!fullText.trim() && toolCallOrder.length === 0)
|
|
661
|
+
throw new Error("DeepCCC 本轮未产生有效回复");
|
|
662
|
+
}
|
|
663
|
+
if (hasMalformedToolProtocolText(fullText)) {
|
|
664
|
+
console.warn(`[DeepCCC] malformed tool protocol text detected for ${this.context.sessionId} `
|
|
665
|
+
+ `(attempt ${attempt + 1}/2, structuredToolCalls=${toolCallOrder.length})`);
|
|
666
|
+
rawLog?.writeLine(safeRawStreamJson({
|
|
667
|
+
type: "deepccc_tool_protocol_recovery",
|
|
668
|
+
attempt: attempt + 1,
|
|
669
|
+
structuredToolCalls: toolCallOrder.length,
|
|
670
|
+
}));
|
|
671
|
+
yield { type: "text_reset" };
|
|
672
|
+
if (attempt === 0 && toolCallOrder.length === 0) {
|
|
673
|
+
yield { type: "status", phase: "generating" };
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
throw new Error(toolCallOrder.length > 0
|
|
677
|
+
? "工具调用协议异常:检测到混合的结构化调用与伪造工具文本,为避免重复执行工具,本轮已安全终止"
|
|
678
|
+
: "工具调用协议异常:模型重试后仍输出了无效或伪造的工具调用文本");
|
|
679
|
+
}
|
|
680
|
+
completed = true;
|
|
681
|
+
const collectedToolCalls = toolCallOrder
|
|
682
|
+
.map((id) => toolCallsById.get(id))
|
|
683
|
+
.filter((call) => call !== undefined);
|
|
684
|
+
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
685
|
+
fullText,
|
|
686
|
+
transcriptLines: toolContext,
|
|
687
|
+
toolCalls: collectedToolCalls,
|
|
688
|
+
timeline,
|
|
689
|
+
}));
|
|
690
|
+
yield { type: "done", text: safeAccumulated };
|
|
691
|
+
return;
|
|
609
692
|
}
|
|
610
693
|
}
|
|
611
|
-
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
694
|
+
catch (err) {
|
|
695
|
+
if (pendingInjection !== null) {
|
|
696
|
+
// 协作式让位:持久化本段中间态 → 注入 user → 重建 messages → 继续当前 turn。
|
|
697
|
+
if (fullText.trim() || toolCallOrder.length > 0) {
|
|
698
|
+
const collectedToolCalls = toolCallOrder
|
|
699
|
+
.map((id) => toolCallsById.get(id))
|
|
700
|
+
.filter((call) => call !== undefined);
|
|
701
|
+
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
702
|
+
fullText,
|
|
703
|
+
transcriptLines: toolContext,
|
|
704
|
+
toolCalls: collectedToolCalls,
|
|
705
|
+
timeline: timeline.map((entry) => ({ ...entry })),
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
708
|
+
const injectedText = pendingInjection;
|
|
709
|
+
this.context.appendMessage({ role: "user", content: injectedText });
|
|
710
|
+
rawLog?.writeLine(safeRawStreamJson({
|
|
711
|
+
type: "deepccc_input_injected",
|
|
712
|
+
text: injectedText,
|
|
713
|
+
}));
|
|
714
|
+
yield { type: "input_injected", text: injectedText };
|
|
715
|
+
currentMessages = decorateMessages(this.context.buildModelMessages());
|
|
630
716
|
continue;
|
|
631
717
|
}
|
|
632
|
-
throw
|
|
633
|
-
? "工具调用协议异常:检测到混合的结构化调用与伪造工具文本,为避免重复执行工具,本轮已安全终止"
|
|
634
|
-
: "工具调用协议异常:模型重试后仍输出了无效或伪造的工具调用文本");
|
|
718
|
+
throw err;
|
|
635
719
|
}
|
|
636
|
-
completed = true;
|
|
637
|
-
const collectedToolCalls = toolCallOrder
|
|
638
|
-
.map((id) => toolCallsById.get(id))
|
|
639
|
-
.filter((call) => call !== undefined);
|
|
640
|
-
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
641
|
-
fullText,
|
|
642
|
-
transcriptLines: toolContext,
|
|
643
|
-
toolCalls: collectedToolCalls,
|
|
644
|
-
timeline,
|
|
645
|
-
}));
|
|
646
|
-
yield { type: "done", text: safeAccumulated };
|
|
647
|
-
return;
|
|
648
720
|
}
|
|
649
721
|
}
|
|
650
722
|
catch (err) {
|
|
@@ -102,5 +102,11 @@ export function reduceProgress(prev, event) {
|
|
|
102
102
|
case "compact":
|
|
103
103
|
// 旧上下文压缩不影响当前过程展示
|
|
104
104
|
return prev;
|
|
105
|
+
case "input_injected": {
|
|
106
|
+
// 协作式让位:在当前 turn 的 step 边界注入了新消息,仅在头部提示,
|
|
107
|
+
// 不改动正文与工具状态。
|
|
108
|
+
const preview = event.text.length > 40 ? `${event.text.slice(0, 40)}…` : event.text;
|
|
109
|
+
return withProgressView(prev, { headerTitle: `已注入新消息:${preview}` });
|
|
110
|
+
}
|
|
105
111
|
}
|
|
106
112
|
}
|
|
@@ -52,7 +52,7 @@ export function createCccAdapter(options = {}) {
|
|
|
52
52
|
const normalizedSessionId = normalizeBuiltinSessionId(sessionId);
|
|
53
53
|
const session = new ChatSession(chatConfig, toChatSessionOptions(normalizedSessionId, cwd, options));
|
|
54
54
|
const completion = createTurnCompletion("CCC Agent");
|
|
55
|
-
for await (const event of session.chat(userText, signal)) {
|
|
55
|
+
for await (const event of session.chat(userText, signal, _promptOptions?.drainInput)) {
|
|
56
56
|
if (event.type === "text")
|
|
57
57
|
completion.observe({ type: "assistant", blocks: [{ type: "text", text: event.text }] });
|
|
58
58
|
if (event.type === "text_reset")
|
|
@@ -78,6 +78,12 @@ export function createCccAdapter(options = {}) {
|
|
|
78
78
|
blocks: [{ type: "text_reset" }],
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
+
else if (event.type === "input_injected") {
|
|
82
|
+
yield {
|
|
83
|
+
type: "assistant",
|
|
84
|
+
blocks: [{ type: "input_injected", text: event.text }],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
81
87
|
else if (event.type === "text") {
|
|
82
88
|
yield {
|
|
83
89
|
type: "assistant",
|