@exreve/exk 1.0.49 → 1.0.51

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.
@@ -88,20 +88,59 @@ const CACHED_CLAUDE_PATH = (() => {
88
88
  // Promisify symlink for async use
89
89
  const symlinkAsync = promisify(fsSymlink);
90
90
  // Helper function to extract tool name from result structure
91
+ /**
92
+ * Detect tool name from the shape of tool_use_result.
93
+ *
94
+ * Uses discriminating keys from the SDK's own type definitions
95
+ * (sdk-tools.d.ts: BashOutput, GrepOutput, GlobOutput, FileReadOutput,
96
+ * FileEditOutput, FileWriteOutput, TodoWriteOutput, etc.)
97
+ */
91
98
  function extractToolName(toolResult) {
92
- if (toolResult.name)
93
- return toolResult.name;
94
- if (toolResult.type === 'text' && toolResult.file)
99
+ if (!toolResult || typeof toolResult !== 'object')
100
+ return 'unknown';
101
+ // ── Read (FileReadOutput): {type: 'text'|'image'|'notebook'|'pdf'|'parts'|'file_unchanged', file: {...}}
102
+ if (toolResult.file && typeof toolResult.file === 'object'
103
+ && ['text', 'image', 'notebook', 'pdf', 'parts', 'file_unchanged'].includes(toolResult.type)) {
95
104
  return 'Read';
96
- if (toolResult.file_path || toolResult.filePath) {
97
- // Has old_string/new_string → Edit; has content/create → Write; else Read
98
- if (toolResult.old_string !== undefined && toolResult.new_string !== undefined)
99
- return 'Edit';
100
- return (toolResult.content !== undefined || toolResult.type === 'create') ? 'Write' : 'Read';
101
105
  }
102
- if (toolResult.stdout !== undefined || toolResult.stderr !== undefined)
106
+ // ── Edit (FileEditOutput): {filePath, oldString, newString, structuredPatch}
107
+ if (toolResult.filePath && toolResult.oldString !== undefined && toolResult.newString !== undefined
108
+ && Array.isArray(toolResult.structuredPatch)) {
109
+ return 'Edit';
110
+ }
111
+ // ── Write (FileWriteOutput): {type: 'create'|'update', filePath, content, structuredPatch}
112
+ if (toolResult.filePath && (toolResult.type === 'create' || toolResult.type === 'update')
113
+ && toolResult.content !== undefined) {
114
+ return 'Write';
115
+ }
116
+ // ── Grep (GrepOutput): {mode, numFiles, filenames, content?, numLines?}
117
+ if (typeof toolResult.numFiles === 'number' && Array.isArray(toolResult.filenames)
118
+ && toolResult.mode !== undefined) {
119
+ return 'Grep';
120
+ }
121
+ // ── Glob (GlobOutput): {durationMs, numFiles, filenames, truncated}
122
+ if (typeof toolResult.numFiles === 'number' && Array.isArray(toolResult.filenames)
123
+ && typeof toolResult.durationMs === 'number' && toolResult.truncated !== undefined) {
124
+ return 'Glob';
125
+ }
126
+ // ── TodoWrite (TodoWriteOutput): {oldTodos, newTodos}
127
+ if (Array.isArray(toolResult.oldTodos) && Array.isArray(toolResult.newTodos)) {
128
+ return 'TodoWrite';
129
+ }
130
+ // ── Bash (BashOutput): {stdout, stderr, interrupted, ...}
131
+ if (toolResult.stdout !== undefined || toolResult.stderr !== undefined) {
103
132
  return 'Bash';
104
- // send_file tool: content is JSON with _type marker — detect before Bash fallback
133
+ }
134
+ // ── WebSearch (WebSearchOutput): {query, results}
135
+ if (typeof toolResult.query === 'string' && Array.isArray(toolResult.results)) {
136
+ return 'WebSearch';
137
+ }
138
+ // ── WebFetch (WebFetchOutput): {url, result, code, bytes}
139
+ if (typeof toolResult.url === 'string' && typeof toolResult.result === 'string'
140
+ && typeof toolResult.code === 'number') {
141
+ return 'WebFetch';
142
+ }
143
+ // ── send_file (custom MCP tool): content is JSON with _type marker
105
144
  if (toolResult.content && typeof toolResult.content === 'string' && toolResult.type === 'text') {
106
145
  try {
107
146
  const parsed = JSON.parse(toolResult.content);
@@ -112,6 +151,10 @@ function extractToolName(toolResult) {
112
151
  // SDK 0.2.x: content-only results from nested tool calls (no stdout/stderr wrapper)
113
152
  return 'Bash';
114
153
  }
154
+ // ── Agent/Task output: {agentId, content, status}
155
+ if (toolResult.agentId && Array.isArray(toolResult.content) && toolResult.status) {
156
+ return 'Task';
157
+ }
115
158
  return 'unknown';
116
159
  }
117
160
  // Look up tool name from the most recent assistant message's tool_use blocks by tool_use_id
@@ -122,7 +165,12 @@ function lookupToolNameFromHistory(messages, toolUseId) {
122
165
  const msg = messages[i];
123
166
  if (msg.role !== 'assistant')
124
167
  continue;
125
- const content = typeof msg.content === 'string' ? null : msg.content;
168
+ // msg.content is the SDK message object: {role: 'assistant', content: [{type: 'text',...}, {type: 'tool_use',...}]}
169
+ let content = typeof msg.content === 'string' ? null : msg.content;
170
+ // Unwrap nested content: {content: [...]} → [...]
171
+ if (content && !Array.isArray(content) && Array.isArray(content.content)) {
172
+ content = content.content;
173
+ }
126
174
  if (!Array.isArray(content))
127
175
  continue;
128
176
  const toolUse = content.find((c) => c.type === 'tool_use' && c.id === toolUseId);
@@ -1042,10 +1090,11 @@ export class AgentSessionManager {
1042
1090
  }
1043
1091
  }
1044
1092
  if (toolResult) {
1093
+ // History lookup is authoritative: the assistant's tool_use block names the tool.
1094
+ // Heuristic detection can't distinguish Bash/Grep/Glob/etc (all have {stdout,stderr}).
1095
+ const historyName = lookupToolNameFromHistory(session.messages, toolUseId);
1045
1096
  const detectedName = extractToolName(toolResult);
1046
- const resolvedName = detectedName !== 'unknown'
1047
- ? detectedName
1048
- : (lookupToolNameFromHistory(session.messages, toolUseId) || detectedName);
1097
+ const resolvedName = historyName || detectedName;
1049
1098
  onOutput({
1050
1099
  type: 'tool_result',
1051
1100
  data: toolResult,
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exreve/exk",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "description": "exk - Control Claude CLI with voice and programmable interfaces",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,4 +61,4 @@
61
61
  "engines": {
62
62
  "node": ">=20.0.0"
63
63
  }
64
- }
64
+ }