dsh-acp-enhanced 0.3.1 → 0.3.2
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-zh.md +2 -1
- package/README.md +4 -2
- package/lib/index.js +143 -5
- package/package.json +1 -1
package/README-zh.md
CHANGED
|
@@ -26,7 +26,8 @@ ACP 线上。
|
|
|
26
26
|
|
|
27
27
|
### Zed 深度集成
|
|
28
28
|
|
|
29
|
-
-
|
|
29
|
+
- **工具卡片**:折叠态即显示一行摘要——`Read <路径>`、执行的命令、`Search: <模式>`、
|
|
30
|
+
`Fetch: <URL>` 等;展开可见每次调用的完整参数与结果预览(`rawInput` / `rawOutput`),
|
|
30
31
|
按工具类型渲染图标
|
|
31
32
|
- **Zed 文件与终端**:`zed_read_text_file` / `zed_write_text_file` / `zed_terminal` 把
|
|
32
33
|
文件编辑放进 Zed 的"编辑文件"区(diff + 接受/拒绝)、命令跑在 Zed 真实终端
|
package/README.md
CHANGED
|
@@ -29,8 +29,10 @@ over the ACP wire.
|
|
|
29
29
|
|
|
30
30
|
### Zed deep integration
|
|
31
31
|
|
|
32
|
-
- **Tool cards**:
|
|
33
|
-
|
|
32
|
+
- **Tool cards**: one-line summary in the collapsed header — `Read <path>`, the
|
|
33
|
+
executed command, `Search: <pattern>`, `Fetch: <url>`, etc. — with the full
|
|
34
|
+
arguments and result preview (`rawInput` / `rawOutput`) one click away, plus
|
|
35
|
+
per-kind icons
|
|
34
36
|
- **Zed files & terminal**: `zed_read_text_file` / `zed_write_text_file` / `zed_terminal`
|
|
35
37
|
put file edits into Zed's "edited files" area (diff + accept/reject) and commands into a
|
|
36
38
|
real Zed terminal
|
package/lib/index.js
CHANGED
|
@@ -103,6 +103,138 @@ function parseToolArguments(raw) {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/** Collapse whitespace and bound a string to one display line (Zed shows the
|
|
107
|
+
* tool-call header from the title, truncating overflow with an ellipsis). */
|
|
108
|
+
function clipOneLine(text, max) {
|
|
109
|
+
const oneLine = String(text).replace(/\s+/g, ' ').trim()
|
|
110
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Escape markdown-significant characters so a dynamic title fragment (path,
|
|
114
|
+
* pattern, URL, query) renders literally inside Zed's markdown label. Zed
|
|
115
|
+
* applies no escaping to `other`/`read`/`search`/`fetch`/`think` kinds — it
|
|
116
|
+
* only escapes `edit` itself — so the bridge must keep emphasis/code/link
|
|
117
|
+
* syntax from being interpreted. Execute-kind titles render as plain text
|
|
118
|
+
* and are never escaped. */
|
|
119
|
+
function mdEscape(text) {
|
|
120
|
+
return String(text).replace(/([\\*_`[\]<>])/g, '\\$1')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One-line human-readable summary of a tool call for the ACP `title` field.
|
|
125
|
+
* Zed renders this as the collapsed tool-call header, so a bare tool name
|
|
126
|
+
* ("read", "bash") hides what actually happened until the card is expanded.
|
|
127
|
+
* Mirror Zed's own native-agent titles (`Read <path>`, `Fetch <url>`,
|
|
128
|
+
* `Search: <pattern>`, and the raw command for terminal tools) with the
|
|
129
|
+
* detail escaped and bounded to one line.
|
|
130
|
+
*/
|
|
131
|
+
function toolCallTitle(name, argumentsValue) {
|
|
132
|
+
const obj = argumentsValue !== null && typeof argumentsValue === 'object' && !Array.isArray(argumentsValue)
|
|
133
|
+
? argumentsValue
|
|
134
|
+
: {}
|
|
135
|
+
const str = (key) => (typeof obj[key] === 'string' ? obj[key].trim() : undefined)
|
|
136
|
+
const num = (key) => {
|
|
137
|
+
const value = obj[key]
|
|
138
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
139
|
+
if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {
|
|
140
|
+
return Number(value)
|
|
141
|
+
}
|
|
142
|
+
return undefined
|
|
143
|
+
}
|
|
144
|
+
const stringField = (value, key) => (
|
|
145
|
+
typeof value === 'object' && value !== null && typeof value[key] === 'string' ? value[key].trim() : undefined
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// Execute-kind tools (plain-text label): show the command itself.
|
|
149
|
+
if (name === 'zed_terminal' || /bash|shell|exec|run_code|execute|terminal/.test(name)) {
|
|
150
|
+
// Only zed_terminal maps to kind 'execute' (Zed renders its label as plain
|
|
151
|
+
// text); local executors like bash/run_code are kind 'other' and render as
|
|
152
|
+
// markdown, so their command must be escaped to stay literal.
|
|
153
|
+
const literal = (text) => (name === 'zed_terminal' ? text : mdEscape(text))
|
|
154
|
+
const command = str('command') ?? str('cmd') ?? str('script')
|
|
155
|
+
if (command) return clipOneLine(literal(command), 100)
|
|
156
|
+
const argv = obj.args
|
|
157
|
+
if (Array.isArray(argv) && argv.length > 0) return clipOneLine(literal(argv.map(String).join(' ')), 100)
|
|
158
|
+
const cwd = str('cwd')
|
|
159
|
+
if (cwd) return clipOneLine(literal(`Run in ${cwd}`), 100)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// File-content tools: `Read <path>` (incl. line range when present).
|
|
163
|
+
if (/read|cat|show|view/.test(name)) {
|
|
164
|
+
const path = str('path') ?? str('file_path') ?? str('filePath') ?? str('file')
|
|
165
|
+
if (path) {
|
|
166
|
+
const range = [num('line') ?? num('offset'), num('limit')].filter((value) => value !== undefined)
|
|
167
|
+
const suffix = range.length === 2 && Number.isFinite(range[0]) && Number.isFinite(range[1]) && range[1] > 0
|
|
168
|
+
? ` (lines ${range[0]}-${range[0] + range[1] - 1})`
|
|
169
|
+
: range.length === 1 && Number.isFinite(range[0]) && range[0] > 1
|
|
170
|
+
? ` (from line ${range[0]})`
|
|
171
|
+
: ''
|
|
172
|
+
return clipOneLine(`Read ${mdEscape(path)}${suffix}`, 120)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// File-modifying tools: `Write <path>` / `Edit <path>`.
|
|
177
|
+
if (/write|edit|patch|apply/.test(name)) {
|
|
178
|
+
const path = str('path') ?? str('file_path') ?? str('filePath') ?? str('file') ?? str('target')
|
|
179
|
+
if (path) return clipOneLine(`Write ${mdEscape(path)}`, 120)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Content search: `Search: <pattern>`.
|
|
183
|
+
if (/grep|rg|content_search|search_text/.test(name)) {
|
|
184
|
+
const pattern = str('pattern') ?? str('regex') ?? str('query')
|
|
185
|
+
if (pattern) return clipOneLine(`Search: ${mdEscape(pattern)}`, 120)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Path search: `Find: <pattern>`.
|
|
189
|
+
if (/glob|find/.test(name)) {
|
|
190
|
+
const pattern = str('pattern') ?? str('query') ?? str('path')
|
|
191
|
+
if (pattern) return clipOneLine(`Find: ${mdEscape(pattern)}`, 120)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Network fetch: `Fetch: <url>`.
|
|
195
|
+
if (/fetch|http/.test(name)) {
|
|
196
|
+
const url = str('url') ?? str('uri') ?? str('endpoint')
|
|
197
|
+
if (url) return clipOneLine(`Fetch: ${mdEscape(url)}`, 120)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Generic web/data search: `Search: <query>`.
|
|
201
|
+
if (/search/.test(name)) {
|
|
202
|
+
const query = str('query') ?? str('q') ?? str('keyword') ?? str('keywords')
|
|
203
|
+
if (query) return clipOneLine(`Search: ${mdEscape(query)}`, 120)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// User questions: `Ask: <first question>`.
|
|
207
|
+
if (/ask|question|elicit/.test(name) && Array.isArray(obj.questions)) {
|
|
208
|
+
const first = obj.questions[0]
|
|
209
|
+
const text = typeof first === 'object' && first !== null
|
|
210
|
+
? stringField(first, 'question') ?? stringField(first, 'header') ?? ''
|
|
211
|
+
: String(first ?? '')
|
|
212
|
+
if (text.trim().length > 0) return clipOneLine(`Ask: ${mdEscape(text)}`, 120)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Subagents / delegated work: show the objective.
|
|
216
|
+
if (/spawn_agent|subagent|spawn|delegate|task /.test(name)) {
|
|
217
|
+
const description = str('description') ?? str('objective') ?? str('prompt')
|
|
218
|
+
if (description) return clipOneLine(mdEscape(description), 100)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MCP tools: inline a single string-valued field (mirrors Zed's own MCP
|
|
222
|
+
// primary-argument heuristic); otherwise fall back to the tool name.
|
|
223
|
+
if (name.startsWith('mcp__')) {
|
|
224
|
+
const strings = Object.entries(obj)
|
|
225
|
+
.filter(([key, value]) => typeof value === 'string' && value.trim().length > 0)
|
|
226
|
+
if (strings.length === 1) {
|
|
227
|
+
return clipOneLine(mdEscape(strings[0][1].trim()), 120)
|
|
228
|
+
}
|
|
229
|
+
if (strings.length > 1) {
|
|
230
|
+
const [key, value] = strings[0]
|
|
231
|
+
return clipOneLine(`${key}=${mdEscape(value)}`, 120)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return name
|
|
236
|
+
}
|
|
237
|
+
|
|
106
238
|
/** Extract a bounded text preview of a dsh tool result for ACP rawOutput. */
|
|
107
239
|
function resultPreview(event) {
|
|
108
240
|
if (event.data.error !== undefined) {
|
|
@@ -237,14 +369,18 @@ export function apply(ctx, config) {
|
|
|
237
369
|
case 'tool/call': {
|
|
238
370
|
record.toolStats.lastCallAt = Date.now()
|
|
239
371
|
record.toolStats.lastName = event.data.name
|
|
372
|
+
const parsedArgs = parseToolArguments(event.data.arguments)
|
|
240
373
|
notify({
|
|
241
374
|
sessionId: session.header.id,
|
|
242
375
|
update: {
|
|
243
376
|
sessionUpdate: 'tool_call',
|
|
244
377
|
toolCallId: event.data.callId,
|
|
245
|
-
|
|
378
|
+
// A one-line summary (not just the tool name) so Zed's collapsed
|
|
379
|
+
// header shows what the call is doing — which file is read, which
|
|
380
|
+
// command runs, what is searched — before expanding the card.
|
|
381
|
+
title: toolCallTitle(event.data.name, parsedArgs),
|
|
246
382
|
kind: toolKindFor(event.data.name),
|
|
247
|
-
rawInput:
|
|
383
|
+
rawInput: parsedArgs,
|
|
248
384
|
_meta: {
|
|
249
385
|
turn: event.data.turn,
|
|
250
386
|
step: event.data.step,
|
|
@@ -1260,13 +1396,14 @@ export function apply(ctx, config) {
|
|
|
1260
1396
|
}
|
|
1261
1397
|
break
|
|
1262
1398
|
}
|
|
1263
|
-
case 'tool/call':
|
|
1399
|
+
case 'tool/call': {
|
|
1400
|
+
const parsedArgs = parseToolArguments(event.data.arguments)
|
|
1264
1401
|
await notifyNow(record, {
|
|
1265
1402
|
sessionUpdate: 'tool_call',
|
|
1266
1403
|
toolCallId: event.data.callId,
|
|
1267
|
-
title: event.data.name,
|
|
1404
|
+
title: toolCallTitle(event.data.name, parsedArgs),
|
|
1268
1405
|
kind: toolKindFor(event.data.name),
|
|
1269
|
-
rawInput:
|
|
1406
|
+
rawInput: parsedArgs,
|
|
1270
1407
|
_meta: {
|
|
1271
1408
|
turn: event.data.turn,
|
|
1272
1409
|
step: event.data.step,
|
|
@@ -1275,6 +1412,7 @@ export function apply(ctx, config) {
|
|
|
1275
1412
|
},
|
|
1276
1413
|
})
|
|
1277
1414
|
break
|
|
1415
|
+
}
|
|
1278
1416
|
case 'tool/result': {
|
|
1279
1417
|
const preview = resultPreview(event)
|
|
1280
1418
|
const callId = event.data.message?.content?.[0]?.toolCallId ?? event.data.callId
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-acp-enhanced",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Enhanced ACP server for DeepSeek Harness: block-level streaming, usage/stat telemetry (cache hit rate, token speed, input/output tokens, context length, turns, tool timing), model & reasoning-effort switching, and permission-preset control over the ACP wire (Zed-friendly)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|