Skip to content

Commit 8b7a1cb

Browse files
authored
Merge pull request #56 from ZhouChaunge/feat/auto-selection-chip
feat: 选中代码自动生成 attachment chip(#55
2 parents 283752b + 7465f31 commit 8b7a1cb

12 files changed

Lines changed: 838 additions & 144 deletions

File tree

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<p align="center">
1313
<a href="https://code.visualstudio.com/"><img src="https://img.shields.io/badge/VS%20Code-%E2%89%A51.95.0-blue" alt="VS Code"/></a>
14-
<a href="https://github.com/ZhouChaunge/DeepCopilot/releases"><img src="https://img.shields.io/badge/version-0.32.9-success" alt="Version"/></a>
14+
<a href="https://github.com/ZhouChaunge/DeepCopilot/releases"><img src="https://img.shields.io/badge/version-0.33.0-success" alt="Version"/></a>
1515
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"/></a>
1616
</p>
1717

@@ -469,6 +469,11 @@ The hook's stdout/stderr is appended to the tool result so the model can react
469469
<details>
470470
<summary>点击展开完整更新日志 · Click to expand full changelog</summary>
471471

472+
### v0.33.0 — Compact Tool UI · Sonar Spinner Redesign · 工具栏精简与进度动画重设计
473+
474+
- 中文:大幅精简工具调用的显示方式,隐藏图标、三角形与分栏,改为单行灰色文本,文件路径更淡化,点击后展开详情;移除所有中间"思考中"气泡的耗时显示;底部进度动画全面重设计——保留蓝色 sonar 光波,去除背景色与边框,加入 20 个英文动词随机轮播(每 3 秒切换,渐显动画)与实时计时器;修复长回复时 spinner 被输入框遮挡的问题(requestAnimationFrame 布局时序修正 + 每秒兜底滚动)。
475+
- English: Heavily simplified tool-call display — icons, chevrons, and column layout removed in favour of a single-line grey text row with faded file paths and click-to-expand detail. Removed elapsed-time labels from all intermediate "Thinking" chips. Completely redesigned the bottom progress indicator: keeps the blue sonar dot, removes background / border / shimmer, adds a 20-word English verb carousel (randomised every 3 s with a fade-in animation) and a live elapsed timer. Fixed the spinner being obscured by the input box during long streaming responses via `requestAnimationFrame`-deferred scrolling and a per-second scroll safety net.
476+
472477
### v0.32.9 — Autopilot 静默放行工作区外路径 · Silent pass-through for out-of-workspace paths
473478

474479
- 中文:在 `src/tools/utils.js``ensurePathAllowed()` 中新增审批模式检测。当 `approvalMode === 'autopilot'` 时,访问工作区外的路径(如 `~/.deepcopilot/memory.md`**静默放行**,不再弹出“访问工作区之外”对话框,与 autopilot 语义保持一致。其他模式(`manual` / `auto-edit`)行为不变。

media/chat.css

Lines changed: 100 additions & 55 deletions
Large diffs are not rendered by default.

media/chat.js

Lines changed: 325 additions & 70 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "deep-copilot",
33
"displayName": "Deep Copilot",
44
"description": "Deep Copilot — AI coding agent embedded in VS Code. Powered by DeepSeek V4, with file editing, terminal, search, and plan/todos. Standalone, no backend required.",
5-
"version": "0.32.9",
5+
"version": "0.33.0",
66
"publisher": "ZhouChaunge",
77
"icon": "imgs/logo.png",
88
"repository": {
@@ -122,6 +122,11 @@
122122
"command": "deepseekAgent.openDebugLog",
123123
"title": "Deep Copilot: Open Debug Log",
124124
"icon": "$(output)"
125+
},
126+
{
127+
"command": "deepseekAgent.attachSelection",
128+
"title": "附加到 Deep Copilot",
129+
"icon": "$(link)"
125130
}
126131
],
127132
"configuration": {
@@ -272,6 +277,16 @@
272277
},
273278
{
274279
"command": "deepseekAgent.moveToRight"
280+
},
281+
{
282+
"command": "deepseekAgent.attachSelection"
283+
}
284+
],
285+
"editor/context": [
286+
{
287+
"command": "deepseekAgent.attachSelection",
288+
"group": "deepcopilot@1",
289+
"when": "editorTextFocus"
275290
}
276291
]
277292
}
@@ -281,7 +296,8 @@
281296
"watch": "node esbuild.config.js --watch",
282297
"package": "npm run build && vsce package --out release/",
283298
"publish": "npm run build && vsce publish",
284-
"vscode:prepublish": "npm run build"
299+
"vscode:prepublish": "npm run build",
300+
"lint": "eslint src/ --max-warnings 0"
285301
},
286302
"devDependencies": {
287303
"@types/node": "^20.0.0",
@@ -291,6 +307,7 @@
291307
"danger": "^13.0.7",
292308
"dompurify": "^3.4.2",
293309
"esbuild": "^0.28.0",
310+
"eslint": "^8.57.0",
294311
"katex": "^0.16.45"
295312
}
296313
}

src/chat/agent-loop.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ class AgentLoop {
8686
const MAX_TOTAL = 256 * 1024;
8787
let totalSize = 0;
8888
for (const a of textAttachments) {
89-
const block = `<attachment path="${a.path}">\n${a.content || '(empty)'}\n</attachment>`;
89+
// Include line range attribute when the attachment is a selection
90+
const lineAttr = (a.startLine && a.endLine) ? ` lines="${a.startLine}-${a.endLine}"` : '';
91+
const block = `<attachment path="${a.path}"${lineAttr}>\n${a.content || '(empty)'}\n</attachment>`;
9092
if (totalSize + block.length > MAX_TOTAL) {
9193
attachmentBlocks += `<attachment path="${a.path}">(truncated — total attachment budget exceeded)</attachment>\n\n`;
9294
break;
@@ -146,6 +148,21 @@ class AgentLoop {
146148

147149
run.abortCtrl = new AbortController();
148150
const signal = run.abortCtrl.signal;
151+
const runT0 = Date.now();
152+
153+
// Helper: throw immediately if user pressed Stop. Used at every
154+
// await-boundary so the loop can unwind on the first stop click
155+
// (issue #58 P0-2).
156+
const checkAbort = () => { if (signal.aborted) throw new Error('aborted'); };
157+
const postProgress = (phase, extra = {}) => {
158+
this._postToRun(run, {
159+
type: 'progress',
160+
phase,
161+
iter: run._iter || 0,
162+
elapsedMs: Date.now() - runT0,
163+
...extra,
164+
});
165+
};
149166

150167
const sysPrompt = buildSystemPrompt({ includeWorkspaceInstructions: true });
151168
const _itersRaw = Number(cfg.get('maxIterations'));
@@ -179,14 +196,17 @@ class AgentLoop {
179196
try {
180197
while (iter++ < MAX_ITERS) {
181198
run._iter = iter;
199+
checkAbort();
182200

183201
// Auto-compact
184202
const compactRes = autoCompactIfNeeded(run.messages, COMPACT_BUDGET);
185203
if (compactRes.compacted) {
186204
run.messages = compactRes.messages;
187205
Logger.info('AUTOCOMPACT', { sid, iter, dropped: compactRes.dropped });
188206
this._postToRun(run, { type: 'status', text: isZh() ? '🗜 压缩历史…' : 'Compacting history…' });
207+
postProgress('compacting');
189208
}
209+
checkAbort();
190210

191211
// Plan nudge
192212
if (run.plan && Array.isArray(run.plan.steps) && run.plan.steps.length) {
@@ -254,6 +274,7 @@ class AgentLoop {
254274
}
255275
}
256276
if (ctxLimitHit) break; // break while loop — do not call the API
277+
checkAbort();
257278

258279
// Rebuild msgs in case pre-flight compaction modified run.messages
259280
const finalMsgs = [{ role: 'system', content: effectiveSysPrompt }, ...run.messages];
@@ -268,10 +289,14 @@ class AgentLoop {
268289
const STREAMABLE_TOOLS = new Set(['write_file', 'str_replace_in_file', 'apply_patch']);
269290
const allTools = getToolDefs(mcpManager.getToolDefs());
270291

292+
postProgress('waiting_first_token');
293+
294+
let _gotFirstToken = false;
271295
const { toolCalls, usage } = await streamDeepSeek(
272296
{ apiKey, baseUrl, messages: finalMsgs, model, noTools: askMode, tools: allTools },
273297
{
274298
onDelta: (delta) => {
299+
if (!_gotFirstToken) { _gotFirstToken = true; postProgress('streaming'); }
275300
assistantText += delta; run.reply.asst += delta;
276301
pendingDelta += delta;
277302
const now = Date.now();
@@ -281,6 +306,7 @@ class AgentLoop {
281306
}
282307
},
283308
onThinking: (delta) => {
309+
if (!_gotFirstToken) { _gotFirstToken = true; postProgress('thinking'); }
284310
reasoningText += delta; run.reply.thoughts += delta;
285311
Logger.thinking(delta);
286312
this._postToRun(run, { type: 'thinkingDelta', text: delta });
@@ -339,6 +365,7 @@ class AgentLoop {
339365

340366
// ── Parallel read / serial mutating dispatch ──────────────────
341367
const results = new Array(toolCalls.length);
368+
checkAbort();
342369

343370
// Phase 1: read-only tools in parallel
344371
const parallelTasks = [];
@@ -347,6 +374,7 @@ class AgentLoop {
347374
if (!READ_ONLY.has(tc.name)) continue;
348375
const args = this._exec.logToolStart(run, tc);
349376
const tT0 = Date.now();
377+
postProgress('tool_running', { activeTool: tc.name });
350378
parallelTasks.push(
351379
this._exec.execute(tc.name, args, mode, run, signal, tc.id)
352380
.catch(e => `Error: ${e.message}`)
@@ -356,18 +384,22 @@ class AgentLoop {
356384
);
357385
}
358386
if (parallelTasks.length) await Promise.all(parallelTasks);
387+
checkAbort();
359388

360389
// Phase 2: mutating tools serially
361390
for (let i = 0; i < toolCalls.length; i++) {
362391
const tc = toolCalls[i];
363392
if (READ_ONLY.has(tc.name)) continue;
393+
checkAbort();
364394
const args = this._exec.logToolStart(run, tc);
365395
const tT0 = Date.now();
396+
postProgress('tool_running', { activeTool: tc.name });
366397
let rawResult = '';
367398
try { rawResult = await this._exec.execute(tc.name, args, mode, run, signal, tc.id); }
368399
catch (e) { rawResult = `Error: ${e.message}`; }
369400
results[i] = { tc, args, result: this._exec.logToolResult(run, tc, rawResult, Date.now() - tT0) };
370401
}
402+
checkAbort();
371403

372404
// Phase 3: push tool messages + loop-guard checks
373405
// IMPORTANT: The API requires all tool result messages to form a contiguous
@@ -498,8 +530,10 @@ class AgentLoop {
498530
Logger.flush();
499531

500532
try { flushDelta(); } catch {}
501-
this._postToRun(run, { type: 'replyEnd', empty: false });
533+
const wasAborted = signal.aborted;
534+
this._postToRun(run, { type: 'replyEnd', empty: false, aborted: wasAborted });
502535
this._postToRun(run, { type: 'status', text: '' });
536+
if (wasAborted) this._postToRun(run, { type: 'stopped' });
503537
run.abortCtrl = null;
504538
run.busy = false;
505539

0 commit comments

Comments
 (0)