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 CHANGED
@@ -26,7 +26,8 @@ ACP 线上。
26
26
 
27
27
  ### Zed 深度集成
28
28
 
29
- - **工具卡片**:展开可见每次调用的完整参数与结果预览(`rawInput` / `rawOutput`),
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**: expand to see each call's full arguments and result preview
33
- (`rawInput` / `rawOutput`), with per-kind icons
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
- title: event.data.name,
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: parseToolArguments(event.data.arguments),
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: parseToolArguments(event.data.arguments),
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.1",
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",