deepccc 0.2.7 → 0.2.8
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 +1 -1
- package/dist/context.d.ts +19 -0
- package/dist/context.js +80 -0
- package/dist/index.js +35 -8
- package/dist/web-page.js +7 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ deepccc
|
|
|
155
155
|
|
|
156
156
|
Web UI 支持新建、恢复、重命名和删除多会话,多个会话可以同时运行,即使它们指向同一个工作目录。每个会话可独立选择 model、subModel 和 effort,并持续复用 CLI 已保存的历史。注意:当前版本不自动创建 Git worktree;同目录的多个运行中 Agent 直接修改同一组文件,页面会提示覆盖与冲突风险。
|
|
157
157
|
|
|
158
|
-
模型文本、reasoning 心跳和工具事件通过 SSE
|
|
158
|
+
模型文本、reasoning 心跳和工具事件通过 SSE 实时更新。每轮消息按真实发生顺序持久化和回放,因此刷新后仍会保持“阶段说明 → 工具调用 → 后续结论”的交错时间线;旧版会话没有顺序数据时,会兼容显示为“工具调用 → 最终回答”。每次工具调用与对应结果合并成一张卡片,折叠态显示 emoji、工具名、状态和关键参数;展开后调用参数默认保留前 8/后 4 行,工具结果保留前 12/后 6 行,省略内容可继续展开。工具卡及省略行的展开状态保存在当前浏览器标签页的 `sessionStorage`,持续生成、切换会话和刷新页面均不会自动收起。消息正文支持标题、表格、列表、引用、链接和代码块等常用 Markdown。
|
|
159
159
|
|
|
160
160
|
图片始终按本地附件处理,不转换为 Provider 原生多模态消息。Web 支持文件选择、剪贴板粘贴和拖拽,每条消息最多 10 张 PNG/JPEG/WebP、单张最大 20 MB;附件复制到 `~/.deepccc/attachments/<session-id>/`,Agent 收到本地绝对路径后使用可用工具自行处理。Agent 可调用 `present_file` 把当前工作目录或会话附件目录中的图片直接展示在会话中;删除会话时对应附件一并清理。
|
|
161
161
|
|
package/dist/context.d.ts
CHANGED
|
@@ -13,11 +13,28 @@ export interface BuiltinContextToolCall {
|
|
|
13
13
|
output?: string;
|
|
14
14
|
is_error?: boolean;
|
|
15
15
|
}
|
|
16
|
+
export type BuiltinContextTimelineEntry = {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: "tool_use";
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
input?: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "tool_result";
|
|
26
|
+
tool_use_id: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
output?: string;
|
|
29
|
+
is_error?: boolean;
|
|
30
|
+
};
|
|
16
31
|
export interface BuiltinContextMessage {
|
|
17
32
|
role: BuiltinContextRole;
|
|
18
33
|
content: string;
|
|
19
34
|
/** 可选结构化工具调用记录(assistant 消息专用) */
|
|
20
35
|
toolCalls?: BuiltinContextToolCall[];
|
|
36
|
+
/** 文本与工具事件的真实发生顺序,仅供 UI 回放;模型上下文仍使用 content。 */
|
|
37
|
+
timeline?: BuiltinContextTimelineEntry[];
|
|
21
38
|
}
|
|
22
39
|
export interface BuiltinContextState {
|
|
23
40
|
version: 1;
|
|
@@ -90,6 +107,8 @@ export declare function buildPersistedAssistantMessage(params: {
|
|
|
90
107
|
transcriptLines: readonly string[];
|
|
91
108
|
/** 结构化工具调用(按调用顺序);有则写入消息的 toolCalls 字段 */
|
|
92
109
|
toolCalls?: readonly BuiltinContextToolCall[];
|
|
110
|
+
/** 文本和工具事件的有序时间线;不参与模型输入。 */
|
|
111
|
+
timeline?: readonly BuiltinContextTimelineEntry[];
|
|
93
112
|
maxAssistantChars?: number;
|
|
94
113
|
maxTranscriptChars?: number;
|
|
95
114
|
}): BuiltinContextMessage;
|
package/dist/context.js
CHANGED
|
@@ -49,8 +49,42 @@ function normalizeMessage(value) {
|
|
|
49
49
|
const toolCalls = normalizeToolCalls(raw.toolCalls);
|
|
50
50
|
if (toolCalls.length > 0)
|
|
51
51
|
message.toolCalls = toolCalls;
|
|
52
|
+
const timeline = normalizeTimeline(raw.timeline);
|
|
53
|
+
if (timeline.length > 0)
|
|
54
|
+
message.timeline = timeline;
|
|
52
55
|
return message;
|
|
53
56
|
}
|
|
57
|
+
function normalizeTimeline(value) {
|
|
58
|
+
if (!Array.isArray(value))
|
|
59
|
+
return [];
|
|
60
|
+
const entries = [];
|
|
61
|
+
for (const item of value.slice(0, 1000)) {
|
|
62
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
63
|
+
continue;
|
|
64
|
+
const raw = item;
|
|
65
|
+
if (raw.type === "text" && typeof raw.text === "string") {
|
|
66
|
+
entries.push({ type: "text", text: raw.text });
|
|
67
|
+
}
|
|
68
|
+
else if (raw.type === "tool_use" && typeof raw.id === "string" && typeof raw.name === "string") {
|
|
69
|
+
entries.push({
|
|
70
|
+
type: "tool_use",
|
|
71
|
+
id: raw.id,
|
|
72
|
+
name: raw.name,
|
|
73
|
+
...(typeof raw.input === "string" ? { input: raw.input } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else if (raw.type === "tool_result" && typeof raw.tool_use_id === "string") {
|
|
77
|
+
entries.push({
|
|
78
|
+
type: "tool_result",
|
|
79
|
+
tool_use_id: raw.tool_use_id,
|
|
80
|
+
...(typeof raw.name === "string" ? { name: raw.name } : {}),
|
|
81
|
+
...(typeof raw.output === "string" ? { output: raw.output } : {}),
|
|
82
|
+
...(typeof raw.is_error === "boolean" ? { is_error: raw.is_error } : {}),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return entries;
|
|
87
|
+
}
|
|
54
88
|
function normalizeToolCalls(value) {
|
|
55
89
|
if (!Array.isArray(value))
|
|
56
90
|
return [];
|
|
@@ -217,8 +251,54 @@ export function buildPersistedAssistantMessage(params) {
|
|
|
217
251
|
const toolCalls = normalizeToolCalls(params.toolCalls);
|
|
218
252
|
if (toolCalls.length > 0)
|
|
219
253
|
message.toolCalls = toolCalls;
|
|
254
|
+
const timeline = truncatePersistedTimeline(normalizeTimeline(params.timeline));
|
|
255
|
+
if (timeline.length > 0)
|
|
256
|
+
message.timeline = timeline;
|
|
220
257
|
return message;
|
|
221
258
|
}
|
|
259
|
+
function truncatePersistedTimeline(entries) {
|
|
260
|
+
const maxTextChars = DEFAULT_PERSISTED_ASSISTANT_TEXT_CHARS;
|
|
261
|
+
const maxToolChars = DEFAULT_PERSISTED_TOOL_TRANSCRIPT_CHARS;
|
|
262
|
+
const totalText = entries.reduce((sum, entry) => sum + (entry.type === "text" ? entry.text.length : 0), 0);
|
|
263
|
+
const textPayloads = entries.filter((entry) => entry.type === "text" && entry.text.length > 0).length;
|
|
264
|
+
const totalTool = entries.reduce((sum, entry) => {
|
|
265
|
+
if (entry.type === "tool_use")
|
|
266
|
+
return sum + (entry.input?.length ?? 0);
|
|
267
|
+
if (entry.type === "tool_result")
|
|
268
|
+
return sum + (entry.output?.length ?? 0);
|
|
269
|
+
return sum;
|
|
270
|
+
}, 0);
|
|
271
|
+
const toolPayloads = entries.filter((entry) => (entry.type === "tool_use" && (entry.input?.length ?? 0) > 0)
|
|
272
|
+
|| (entry.type === "tool_result" && (entry.output?.length ?? 0) > 0)).length;
|
|
273
|
+
const payloadBudget = (length, total, count, maximum) => {
|
|
274
|
+
if (total <= maximum)
|
|
275
|
+
return length;
|
|
276
|
+
const sharedBudget = Math.max(0, maximum - count);
|
|
277
|
+
return length > 0 ? 1 + Math.floor(sharedBudget * length / Math.max(1, total)) : 0;
|
|
278
|
+
};
|
|
279
|
+
const truncatePayload = (value, budget, marker) => {
|
|
280
|
+
if (value.length <= budget)
|
|
281
|
+
return value;
|
|
282
|
+
if (budget <= marker.length + 2)
|
|
283
|
+
return value.slice(0, budget);
|
|
284
|
+
return truncateMiddle(value, budget, marker);
|
|
285
|
+
};
|
|
286
|
+
return entries.map((entry) => {
|
|
287
|
+
if (entry.type === "text") {
|
|
288
|
+
const budget = payloadBudget(entry.text.length, totalText, textPayloads, maxTextChars);
|
|
289
|
+
return { ...entry, text: truncatePayload(entry.text, budget, ASSISTANT_TRUNCATED_MARKER) };
|
|
290
|
+
}
|
|
291
|
+
if (entry.type === "tool_use" && entry.input !== undefined) {
|
|
292
|
+
const budget = payloadBudget(entry.input.length, totalTool, toolPayloads, maxToolChars);
|
|
293
|
+
return { ...entry, input: truncatePayload(entry.input, budget, TOOL_TRANSCRIPT_TRUNCATED_MARKER) };
|
|
294
|
+
}
|
|
295
|
+
if (entry.type === "tool_result" && entry.output !== undefined) {
|
|
296
|
+
const budget = payloadBudget(entry.output.length, totalTool, toolPayloads, maxToolChars);
|
|
297
|
+
return { ...entry, output: truncatePayload(entry.output, budget, TOOL_TRANSCRIPT_TRUNCATED_MARKER) };
|
|
298
|
+
}
|
|
299
|
+
return entry;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
222
302
|
function serializeMessagesForCompaction(messages) {
|
|
223
303
|
const sections = [];
|
|
224
304
|
let remaining = MAX_COMPACTION_SOURCE_CHARS;
|
package/dist/index.js
CHANGED
|
@@ -393,6 +393,8 @@ export class ChatSession {
|
|
|
393
393
|
// assistant 消息 toolCalls 字段;[Tool transcript] 文本视图仍按原格式生成。
|
|
394
394
|
const toolCallsById = new Map();
|
|
395
395
|
const toolCallOrder = [];
|
|
396
|
+
const timeline = [];
|
|
397
|
+
let toolContext = [];
|
|
396
398
|
try {
|
|
397
399
|
if (this.context.planCompaction()) {
|
|
398
400
|
yield { type: "status", phase: "compacting" };
|
|
@@ -460,9 +462,10 @@ export class ChatSession {
|
|
|
460
462
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
461
463
|
fullText = "";
|
|
462
464
|
safeAccumulated = "";
|
|
463
|
-
|
|
465
|
+
toolContext = [];
|
|
464
466
|
toolCallsById.clear();
|
|
465
467
|
toolCallOrder.length = 0;
|
|
468
|
+
timeline.length = 0;
|
|
466
469
|
let lastReasoningProgressAt;
|
|
467
470
|
const attemptMessages = attempt === 0
|
|
468
471
|
? modelMessages
|
|
@@ -493,6 +496,11 @@ export class ChatSession {
|
|
|
493
496
|
}
|
|
494
497
|
else if (part.type === "text-delta") {
|
|
495
498
|
fullText += part.text;
|
|
499
|
+
const previous = timeline[timeline.length - 1];
|
|
500
|
+
if (previous?.type === "text")
|
|
501
|
+
previous.text += part.text;
|
|
502
|
+
else
|
|
503
|
+
timeline.push({ type: "text", text: part.text });
|
|
496
504
|
// 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
|
|
497
505
|
// fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
|
|
498
506
|
const safeText = applyPrivacy(part.text);
|
|
@@ -500,9 +508,11 @@ export class ChatSession {
|
|
|
500
508
|
yield { type: "text", text: safeText, accumulated: safeAccumulated };
|
|
501
509
|
}
|
|
502
510
|
else if (part.type === "tool-call") {
|
|
503
|
-
|
|
504
|
-
|
|
511
|
+
const input = safeJson(part.input);
|
|
512
|
+
toolContext.push(`tool_call ${part.toolName}: ${input}`);
|
|
513
|
+
toolCallsById.set(part.toolCallId, { id: part.toolCallId, name: part.toolName, input });
|
|
505
514
|
toolCallOrder.push(part.toolCallId);
|
|
515
|
+
timeline.push({ type: "tool_use", id: part.toolCallId, name: part.toolName, input });
|
|
506
516
|
yield {
|
|
507
517
|
type: "tool_use",
|
|
508
518
|
id: part.toolCallId,
|
|
@@ -511,10 +521,12 @@ export class ChatSession {
|
|
|
511
521
|
};
|
|
512
522
|
}
|
|
513
523
|
else if (part.type === "tool-result") {
|
|
514
|
-
|
|
524
|
+
const output = truncateToolContext(safeJson(part.output));
|
|
525
|
+
toolContext.push(`tool_result ${part.toolName}: ${output}`);
|
|
515
526
|
const call = toolCallsById.get(part.toolCallId);
|
|
516
527
|
if (call)
|
|
517
|
-
call.output =
|
|
528
|
+
call.output = output;
|
|
529
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output });
|
|
518
530
|
yield {
|
|
519
531
|
type: "tool_result",
|
|
520
532
|
tool_use_id: part.toolCallId,
|
|
@@ -531,6 +543,7 @@ export class ChatSession {
|
|
|
531
543
|
call.output = message;
|
|
532
544
|
call.is_error = true;
|
|
533
545
|
}
|
|
546
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output: message, is_error: true });
|
|
534
547
|
yield {
|
|
535
548
|
type: "tool_result",
|
|
536
549
|
tool_use_id: part.toolCallId,
|
|
@@ -570,6 +583,7 @@ export class ChatSession {
|
|
|
570
583
|
fullText,
|
|
571
584
|
transcriptLines: toolContext,
|
|
572
585
|
toolCalls: collectedToolCalls,
|
|
586
|
+
timeline,
|
|
573
587
|
}));
|
|
574
588
|
yield { type: "done", text: safeAccumulated };
|
|
575
589
|
return;
|
|
@@ -581,9 +595,22 @@ export class ChatSession {
|
|
|
581
595
|
if (malformedProtocolOutput)
|
|
582
596
|
yield { type: "text_reset" };
|
|
583
597
|
if (err.name === "AbortError" || signal?.aborted) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
598
|
+
if ((fullText || toolCallOrder.length > 0) && !malformedProtocolOutput) {
|
|
599
|
+
const collectedToolCalls = toolCallOrder
|
|
600
|
+
.map((id) => toolCallsById.get(id))
|
|
601
|
+
.filter((call) => call !== undefined);
|
|
602
|
+
const interruptedTimeline = timeline.map((entry) => ({ ...entry }));
|
|
603
|
+
const previous = interruptedTimeline[interruptedTimeline.length - 1];
|
|
604
|
+
if (previous?.type === "text")
|
|
605
|
+
previous.text += "\n[interrupted]";
|
|
606
|
+
else
|
|
607
|
+
interruptedTimeline.push({ type: "text", text: "[interrupted]" });
|
|
608
|
+
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
609
|
+
fullText: `${fullText}\n[interrupted]`,
|
|
610
|
+
transcriptLines: toolContext,
|
|
611
|
+
toolCalls: collectedToolCalls,
|
|
612
|
+
timeline: interruptedTimeline,
|
|
613
|
+
}));
|
|
587
614
|
}
|
|
588
615
|
yield { type: "done", text: safeAccumulated };
|
|
589
616
|
return;
|
package/dist/web-page.js
CHANGED
|
@@ -325,7 +325,10 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
325
325
|
function setToolState(key,value){try{sessionStorage.setItem(toolStorageKey(key),value?'1':'0');}catch(error){}}
|
|
326
326
|
function clearToolSessionState(sessionId){try{var prefix='deepccc:tool-ui:v1:'+sessionId+':';for(var index=sessionStorage.length-1;index>=0;index--){var key=sessionStorage.key(index);if(key&&key.indexOf(prefix)===0)sessionStorage.removeItem(key);}}catch(error){}}
|
|
327
327
|
function toolSectionHtml(call,kind,value,headLines,tailLines){if(value===undefined)return'';var result=truncateToolPayload(value,headLines,tailLines);var stateKey=call.uiKey+':'+kind+'-full';var expanded=toolState(stateKey);var text=expanded?result.full:result.preview;var label=kind==='input'?'调用参数':'工具结果';var button=result.truncated?'<button type="button" class="tool-overflow-toggle" data-tool-overflow="'+escapeHtml(stateKey)+'">'+escapeHtml(expanded?'收起省略内容':(result.omittedLines>0?'展开省略的 '+result.omittedLines+' 行':'展开完整内容'))+'</button>':'';return'<section class="tool-section"><div class="tool-section-title">'+label+'</div><pre>'+escapeHtml(text)+'</pre>'+button+'</section>';}
|
|
328
|
-
function
|
|
328
|
+
function timelineHtml(timeline,messageIndex){var results={};(timeline||[]).forEach(function(item){if(item&&item.type==='tool_result'&&item.tool_use_id)results[item.tool_use_id]=item;});var renderedCalls={};var html='';(timeline||[]).forEach(function(item,index){if(!item)return;if(item.type==='text'){if(item.text)html+='<div class="markdown">'+markdown(item.text)+'</div>';return;}if(item.type==='tool_use'){var id=item.id||('timeline-'+messageIndex+'-'+index);var result=results[id];renderedCalls[id]=true;html+=toolCardHtml({uiKey:'tool:'+id,name:item.name||'tool',input:item.input,output:result?result.output:undefined,isError:!!(result&&result.is_error),pending:!result});return;}if(item.type==='tool_result'&&!renderedCalls[item.tool_use_id]){var fallbackId=item.tool_use_id||('result-'+messageIndex+'-'+index);renderedCalls[fallbackId]=true;html+=toolCardHtml({uiKey:'tool:'+fallbackId,name:item.name||'tool',output:item.output,isError:!!item.is_error,pending:false});}});return html;}
|
|
329
|
+
function liveTimeline(events){var timeline=[];(events||[]).forEach(function(event){if(event.type!=='agent'||!event.data)return;var item=event.data;if(item.type==='text_reset'){timeline.length=0;return;}if(item.type==='text'){var previous=timeline[timeline.length-1];if(previous&&previous.type==='text')previous.text+=(item.text||'');else timeline.push({type:'text',text:item.text||''});return;}if(item.type==='tool_use')timeline.push({type:'tool_use',id:item.id,name:item.name,input:item.input});else if(item.type==='tool_result')timeline.push({type:'tool_result',tool_use_id:item.tool_use_id,name:item.name,output:item.content,is_error:!!item.is_error});});return timeline;}
|
|
330
|
+
function legacyToolsHtml(toolCalls,messageIndex){var tools='';(toolCalls||[]).forEach(function(call,index){tools+=toolCardHtml({uiKey:call.id?'tool:'+call.id:'message:'+messageIndex+':tool:'+index,name:call.name,input:call.input,output:call.output,isError:!!call.is_error,pending:false});});return tools;}
|
|
331
|
+
function messageHtml(role,content,toolCalls,messageIndex,timeline){var decoded=role==='user'?decodeAttachmentMessage(content):{text:withoutToolTranscript(content),attachments:[]};var body;if(role==='user')body='<div class="markdown">'+markdown(decoded.text)+'</div>'+attachmentGallery(decoded.attachments,state.detail.sessionId);else if(timeline&&timeline.length)body=timelineHtml(timeline,messageIndex);else body=legacyToolsHtml(toolCalls,messageIndex)+'<div class="markdown">'+markdown(decoded.text)+'</div>';return '<article class="message '+role+'"><div class="avatar">'+(role==='user'?'你':'D')+'</div><div class="bubble"><div class="role">'+(role==='user'?'YOU':'DEEPCCC')+'</div>'+body+'</div></article>';}
|
|
329
332
|
function buildToolGroups(events){var groups=[];var byId={};var order=[];(events||[]).forEach(function(event){if(event.type!=='agent'||!event.data)return;var item=event.data;if(item.type==='tool_use'){var id=item.id||('call-'+order.length);var group={id:id,uiKey:'tool:'+id,name:item.name||'tool',input:item.input,output:undefined,isError:false,pending:true};byId[id]=group;order.push(id);groups.push(group);}else if(item.type==='tool_result'){var key=item.tool_use_id;var target=key?byId[key]:null;if(target){target.output=item.content;target.isError=!!item.is_error;target.pending=false;}}});return groups;}
|
|
330
333
|
function toolCardHtml(call){var open=toolState(call.uiKey+':card');var present=call.name==='present_file'&&!call.pending?artifactFromOutput(call.output):null;var inputHtml=toolSectionHtml(call,'input',call.input,8,4);var outputHtml=call.pending?'':(present?'<section class="tool-section"><div class="tool-section-title">工具结果</div>'+artifactGallery([{name:'present_file',output:call.output}],state.detail.sessionId)+'</section>':toolSectionHtml(call,'output',call.output==null?'(无输出)':call.output,12,6));return'<details class="tool-card" data-tool-state="'+escapeHtml(call.uiKey+':card')+'"'+(open?' open':'')+'><summary><span class="tool-summary-text">'+escapeHtml(toolSummary(call))+'</span></summary>'+inputHtml+outputHtml+'</details>';}
|
|
331
334
|
function toolCardsHtml(calls){return (calls||[]).map(toolCardHtml).join('');}
|
|
@@ -356,11 +359,10 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
356
359
|
renderAttachmentDrafts();
|
|
357
360
|
var html='<div class="thread">';
|
|
358
361
|
if(!(detail.messages||[]).length&&detail.status!=='running')html+='<div class="empty-thread"><strong>准备开始这个会话</strong><span>在下方输入任务,可附加截图。DeepCCC 会读取当前项目指令、调用工具并持续保存上下文。</span></div>';
|
|
359
|
-
(detail.messages||[]).forEach(function(message,index){html+=messageHtml(message.role,message.content,message.toolCalls,index);});
|
|
362
|
+
(detail.messages||[]).forEach(function(message,index){html+=messageHtml(message.role,message.content,message.toolCalls,index,message.timeline);});
|
|
360
363
|
if(detail.status==='running'){
|
|
361
|
-
var
|
|
362
|
-
if(
|
|
363
|
-
html+=toolCardsHtml(buildToolGroups(detail.events));
|
|
364
|
+
var currentTimeline=liveTimeline(detail.events);
|
|
365
|
+
if(currentTimeline.length)html+=messageHtml('assistant','',[],'live',currentTimeline);
|
|
364
366
|
}
|
|
365
367
|
var approvals=(detail.approvals||[]).slice();
|
|
366
368
|
if(detail.pendingApproval&&!approvals.some(function(item){return item.approvalId===detail.pendingApproval.approvalId;}))approvals.push(Object.assign({status:'pending'},detail.pendingApproval));
|