@yeaft/webchat-agent 1.0.50 → 1.0.52
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/connection/upgrade.js +1 -1
- package/crew/worktree.js +5 -5
- package/history.js +4 -3
- package/package.json +1 -1
- package/providers/copilot-models.js +1 -1
- package/sdk/message-normalize.js +129 -0
- package/sdk/query.js +37 -18
- package/yeaft/mcp.js +1 -0
- package/yeaft/tools/bash.js +1 -0
- package/yeaft/tools/enter-worktree.js +8 -2
- package/yeaft/tools/exit-worktree.js +10 -4
- package/yeaft/tools/grep.js +2 -2
package/connection/upgrade.js
CHANGED
|
@@ -32,7 +32,7 @@ const isWin = platform() === 'win32';
|
|
|
32
32
|
// macOS/Linux: use absolute path — launchd/systemd have minimal PATH.
|
|
33
33
|
const npmPath = isWin ? 'npm' : join(nodeBinDir, 'npm');
|
|
34
34
|
const pm2Path = isWin ? 'pm2' : join(nodeBinDir, 'pm2');
|
|
35
|
-
const shellOpt = isWin ? { shell: true } : {};
|
|
35
|
+
const shellOpt = isWin ? { shell: true, windowsHide: true } : {};
|
|
36
36
|
|
|
37
37
|
// Ensure PATH includes nodeBinDir so that `#!/usr/bin/env node` shebangs
|
|
38
38
|
// can locate node in launchd/systemd environments with minimal PATH.
|
package/crew/worktree.js
CHANGED
|
@@ -27,7 +27,7 @@ export async function initWorktrees(projectDir, roles) {
|
|
|
27
27
|
// 获取 git 已知的 worktree 列表
|
|
28
28
|
let knownWorktrees = new Set();
|
|
29
29
|
try {
|
|
30
|
-
const { stdout } = await execFile('git', ['worktree', 'list', '--porcelain'], { cwd: projectDir });
|
|
30
|
+
const { stdout } = await execFile('git', ['worktree', 'list', '--porcelain'], { cwd: projectDir, windowsHide: true });
|
|
31
31
|
for (const line of stdout.split('\n')) {
|
|
32
32
|
if (line.startsWith('worktree ')) {
|
|
33
33
|
knownWorktrees.add(line.slice('worktree '.length).trim());
|
|
@@ -66,13 +66,13 @@ export async function initWorktrees(projectDir, roles) {
|
|
|
66
66
|
try {
|
|
67
67
|
// 创建分支(如果不存在)
|
|
68
68
|
try {
|
|
69
|
-
await execFile('git', ['branch', branch], { cwd: projectDir });
|
|
69
|
+
await execFile('git', ['branch', branch], { cwd: projectDir, windowsHide: true });
|
|
70
70
|
} catch {
|
|
71
71
|
// 分支已存在,忽略
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// 创建 worktree
|
|
75
|
-
await execFile('git', ['worktree', 'add', wtDir, branch], { cwd: projectDir });
|
|
75
|
+
await execFile('git', ['worktree', 'add', wtDir, branch], { cwd: projectDir, windowsHide: true });
|
|
76
76
|
console.log(`[Crew] Created worktree: ${wtDir} on branch ${branch}`);
|
|
77
77
|
worktreeMap.set(idx, wtDir);
|
|
78
78
|
} catch (e) {
|
|
@@ -104,14 +104,14 @@ export async function cleanupWorktrees(projectDir) {
|
|
|
104
104
|
const branch = `crew/${entry}`;
|
|
105
105
|
|
|
106
106
|
try {
|
|
107
|
-
await execFile('git', ['worktree', 'remove', wtDir, '--force'], { cwd: projectDir });
|
|
107
|
+
await execFile('git', ['worktree', 'remove', wtDir, '--force'], { cwd: projectDir, windowsHide: true });
|
|
108
108
|
console.log(`[Crew] Removed worktree: ${wtDir}`);
|
|
109
109
|
} catch (e) {
|
|
110
110
|
console.warn(`[Crew] Failed to remove worktree ${wtDir}:`, e.message);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
try {
|
|
114
|
-
await execFile('git', ['branch', '-D', branch], { cwd: projectDir });
|
|
114
|
+
await execFile('git', ['branch', '-D', branch], { cwd: projectDir, windowsHide: true });
|
|
115
115
|
console.log(`[Crew] Deleted branch: ${branch}`);
|
|
116
116
|
} catch (e) {
|
|
117
117
|
console.warn(`[Crew] Failed to delete branch ${branch}:`, e.message);
|
package/history.js
CHANGED
|
@@ -2,6 +2,7 @@ import { homedir } from 'os';
|
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync, openSync, readSync, closeSync, fstatSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import ctx from './context.js';
|
|
5
|
+
import { normalizeClaudeMessage } from './sdk/message-normalize.js';
|
|
5
6
|
import { getProvider, DEFAULT_PROVIDER } from './providers/index.js';
|
|
6
7
|
|
|
7
8
|
// Claude 项目目录
|
|
@@ -227,7 +228,7 @@ function readTailMessages(filePath, limit) {
|
|
|
227
228
|
const line = lines[i];
|
|
228
229
|
if (!line || !line.trim()) continue;
|
|
229
230
|
try {
|
|
230
|
-
const data = JSON.parse(line);
|
|
231
|
+
const data = normalizeClaudeMessage(JSON.parse(line));
|
|
231
232
|
if (data.type === 'user' || data.type === 'assistant') {
|
|
232
233
|
collected.push(data);
|
|
233
234
|
if (collected.length >= limit) break;
|
|
@@ -241,7 +242,7 @@ function readTailMessages(filePath, limit) {
|
|
|
241
242
|
const headLine = carry.toString('utf-8').trim();
|
|
242
243
|
if (headLine) {
|
|
243
244
|
try {
|
|
244
|
-
const data = JSON.parse(headLine);
|
|
245
|
+
const data = normalizeClaudeMessage(JSON.parse(headLine));
|
|
245
246
|
if (data.type === 'user' || data.type === 'assistant') {
|
|
246
247
|
collected.push(data);
|
|
247
248
|
}
|
|
@@ -287,7 +288,7 @@ export function loadSessionHistory(workDir, claudeSessionId, limit = 500) {
|
|
|
287
288
|
|
|
288
289
|
for (const line of lines) {
|
|
289
290
|
try {
|
|
290
|
-
const data = JSON.parse(line);
|
|
291
|
+
const data = normalizeClaudeMessage(JSON.parse(line));
|
|
291
292
|
if (data.type === 'user' || data.type === 'assistant') {
|
|
292
293
|
messages.push(data);
|
|
293
294
|
}
|
package/package.json
CHANGED
|
@@ -99,7 +99,7 @@ function _probeAcpModels() {
|
|
|
99
99
|
if (err) reject(err); else resolve(models || []);
|
|
100
100
|
};
|
|
101
101
|
try {
|
|
102
|
-
child = spawn('copilot', ['--acp'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
102
|
+
child = spawn('copilot', ['--acp'], { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
103
103
|
} catch (e) { return reject(e); }
|
|
104
104
|
const timer = setTimeout(() => finish(new Error('ACP probe timeout')), PROBE_TIMEOUT_MS);
|
|
105
105
|
child.on('error', (e) => finish(e));
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const TOOL_LIKE_BLOCK_TYPES = new Set([
|
|
2
|
+
'tool_use',
|
|
3
|
+
'tool_call',
|
|
4
|
+
'function_call',
|
|
5
|
+
'server_tool_use',
|
|
6
|
+
'call',
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Claude Code stream-json has changed its tool-call block spelling more than
|
|
11
|
+
* once. Normalize every known tool-like assistant content block to the
|
|
12
|
+
* Anthropic-compatible `tool_use` shape used by the rest of the app.
|
|
13
|
+
*
|
|
14
|
+
* @param {unknown} block
|
|
15
|
+
* @returns {unknown}
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeAssistantContentBlock(block) {
|
|
18
|
+
if (!block || typeof block !== 'object') return block;
|
|
19
|
+
if (block.type === 'tool_use') return block;
|
|
20
|
+
if (!TOOL_LIKE_BLOCK_TYPES.has(block.type)) return block;
|
|
21
|
+
|
|
22
|
+
const input = normalizeToolInput(block);
|
|
23
|
+
const name = block.name
|
|
24
|
+
|| block.tool_name
|
|
25
|
+
|| block.toolName
|
|
26
|
+
|| block.function?.name
|
|
27
|
+
|| block.action?.name
|
|
28
|
+
|| input?.name
|
|
29
|
+
|| 'tool';
|
|
30
|
+
const id = block.id
|
|
31
|
+
|| block.tool_use_id
|
|
32
|
+
|| block.toolUseId
|
|
33
|
+
|| block.tool_call_id
|
|
34
|
+
|| block.toolCallId
|
|
35
|
+
|| block.call_id
|
|
36
|
+
|| block.callId
|
|
37
|
+
|| stableFallbackToolId(name, block);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
type: 'tool_use',
|
|
41
|
+
id,
|
|
42
|
+
name,
|
|
43
|
+
input,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {unknown} content
|
|
49
|
+
* @returns {unknown}
|
|
50
|
+
*/
|
|
51
|
+
export function normalizeAssistantContent(content) {
|
|
52
|
+
if (!Array.isArray(content)) return content;
|
|
53
|
+
return content.map(normalizeAssistantContentBlock);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {unknown} message
|
|
58
|
+
* @returns {unknown}
|
|
59
|
+
*/
|
|
60
|
+
export function normalizeClaudeMessage(message) {
|
|
61
|
+
if (!message || typeof message !== 'object') return message;
|
|
62
|
+
if (message.type !== 'assistant' || !message.message) return message;
|
|
63
|
+
const content = message.message.content;
|
|
64
|
+
const normalized = normalizeAssistantContent(content);
|
|
65
|
+
if (normalized === content) return message;
|
|
66
|
+
return {
|
|
67
|
+
...message,
|
|
68
|
+
message: {
|
|
69
|
+
...message.message,
|
|
70
|
+
content: normalized,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Stream events should only emit text deltas from actual text blocks. Newer
|
|
77
|
+
* Claude Code builds may emit text-looking deltas while a tool-call block is
|
|
78
|
+
* being assembled (`call`, transient id, JSON argument text). Those bytes are
|
|
79
|
+
* transport detail, not assistant prose; the final complete assistant message
|
|
80
|
+
* carries the real tool_use block.
|
|
81
|
+
*
|
|
82
|
+
* @param {string|undefined|null} blockType
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
export function shouldForwardTextDeltaForBlockType(blockType) {
|
|
86
|
+
if (!blockType) return true; // Back-compat for older CLIs without start events.
|
|
87
|
+
return blockType === 'text';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function stableFallbackToolId(name, block) {
|
|
91
|
+
const raw = stableStringify(block);
|
|
92
|
+
let hash = 2166136261;
|
|
93
|
+
for (let i = 0; i < raw.length; i++) {
|
|
94
|
+
hash ^= raw.charCodeAt(i);
|
|
95
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
96
|
+
}
|
|
97
|
+
return `${name}-${hash.toString(36)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stableStringify(value) {
|
|
101
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
|
|
102
|
+
if (value && typeof value === 'object') {
|
|
103
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
|
|
104
|
+
}
|
|
105
|
+
return JSON.stringify(value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeToolInput(block) {
|
|
109
|
+
const raw = block.input
|
|
110
|
+
?? block.arguments
|
|
111
|
+
?? block.args
|
|
112
|
+
?? block.parameters
|
|
113
|
+
?? block.function?.arguments
|
|
114
|
+
?? block.action?.arguments
|
|
115
|
+
?? {};
|
|
116
|
+
|
|
117
|
+
if (typeof raw === 'string') {
|
|
118
|
+
const trimmed = raw.trim();
|
|
119
|
+
if (!trimmed) return {};
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(trimmed);
|
|
122
|
+
return parsed && typeof parsed === 'object' ? parsed : { value: parsed };
|
|
123
|
+
} catch {
|
|
124
|
+
return { arguments: raw };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (raw && typeof raw === 'object') return raw;
|
|
128
|
+
return {};
|
|
129
|
+
}
|
package/sdk/query.js
CHANGED
|
@@ -7,6 +7,7 @@ import { spawn } from 'child_process';
|
|
|
7
7
|
import { createInterface } from 'readline';
|
|
8
8
|
import { Stream } from './stream.js';
|
|
9
9
|
import { AbortError } from './types.js';
|
|
10
|
+
import { normalizeClaudeMessage, shouldForwardTextDeltaForBlockType } from './message-normalize.js';
|
|
10
11
|
import { getCleanEnv, logDebug, streamToStdin, resolveClaudeCommand } from './utils.js';
|
|
11
12
|
|
|
12
13
|
|
|
@@ -82,6 +83,7 @@ export class Query {
|
|
|
82
83
|
// Track whether we've forwarded text deltas for the current assistant turn.
|
|
83
84
|
// When true, the next complete `assistant` message's text blocks are redundant.
|
|
84
85
|
let hasStreamedTextDeltas = false;
|
|
86
|
+
const streamBlockTypes = new Map();
|
|
85
87
|
|
|
86
88
|
try {
|
|
87
89
|
for await (const line of rl) {
|
|
@@ -114,36 +116,52 @@ export class Query {
|
|
|
114
116
|
const event = message.event;
|
|
115
117
|
if (!event) continue;
|
|
116
118
|
|
|
117
|
-
|
|
119
|
+
if (event.type === 'content_block_start') {
|
|
120
|
+
streamBlockTypes.set(event.index, event.content_block?.type || null);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (event.type === 'content_block_stop') {
|
|
124
|
+
streamBlockTypes.delete(event.index);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// content_block_delta with text_delta → convert to assistant message for streaming,
|
|
128
|
+
// but only while the active block is actually text. Some Claude Code builds
|
|
129
|
+
// expose tool-call assembly as text-looking deltas; forwarding those leaks
|
|
130
|
+
// raw `call`, transient ids, and JSON arguments into the chat transcript.
|
|
118
131
|
if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta' && event.delta.text) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
const blockType = streamBlockTypes.get(event.index);
|
|
133
|
+
if (shouldForwardTextDeltaForBlockType(blockType)) {
|
|
134
|
+
hasStreamedTextDeltas = true;
|
|
135
|
+
this.inputStream.enqueue({
|
|
136
|
+
type: 'assistant',
|
|
137
|
+
message: {
|
|
138
|
+
role: 'assistant',
|
|
139
|
+
content: [{ type: 'text', text: event.delta.text }]
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
127
143
|
}
|
|
128
|
-
// All other stream events (message_start,
|
|
129
|
-
//
|
|
130
|
-
//
|
|
144
|
+
// All other stream events (message_start, input_json_delta,
|
|
145
|
+
// message_stop) are ignored. Tool use is handled via the complete
|
|
146
|
+
// assistant message after block normalization below.
|
|
131
147
|
continue;
|
|
132
148
|
}
|
|
133
149
|
|
|
150
|
+
const normalizedMessage = normalizeClaudeMessage(message);
|
|
151
|
+
|
|
134
152
|
// Deduplicate: when a complete assistant message arrives after we've
|
|
135
153
|
// already streamed text deltas, strip the text blocks (already sent).
|
|
136
154
|
// Keep tool_use blocks which are NOT sent incrementally.
|
|
137
|
-
if (
|
|
155
|
+
if (normalizedMessage.type === 'assistant' && hasStreamedTextDeltas) {
|
|
138
156
|
hasStreamedTextDeltas = false; // Reset for next assistant turn
|
|
139
157
|
|
|
140
|
-
const content =
|
|
158
|
+
const content = normalizedMessage.message?.content;
|
|
141
159
|
if (Array.isArray(content)) {
|
|
142
160
|
const nonTextBlocks = content.filter(b => b.type !== 'text');
|
|
143
161
|
if (nonTextBlocks.length > 0) {
|
|
144
162
|
// Forward only tool_use blocks (text already sent via deltas)
|
|
145
|
-
|
|
146
|
-
this.inputStream.enqueue(
|
|
163
|
+
normalizedMessage.message.content = nonTextBlocks;
|
|
164
|
+
this.inputStream.enqueue(normalizedMessage);
|
|
147
165
|
} else {
|
|
148
166
|
// Pure text message fully streamed — send finish-streaming signal
|
|
149
167
|
// so frontend clears isStreaming and typing dots can reappear
|
|
@@ -165,11 +183,12 @@ export class Query {
|
|
|
165
183
|
|
|
166
184
|
// Reset delta tracking on non-assistant messages
|
|
167
185
|
// (e.g., user, result, system — a new turn boundary)
|
|
168
|
-
if (
|
|
186
|
+
if (normalizedMessage.type !== 'assistant') {
|
|
169
187
|
hasStreamedTextDeltas = false;
|
|
188
|
+
streamBlockTypes.clear();
|
|
170
189
|
}
|
|
171
190
|
|
|
172
|
-
this.inputStream.enqueue(
|
|
191
|
+
this.inputStream.enqueue(normalizedMessage);
|
|
173
192
|
} catch (e) {
|
|
174
193
|
logDebug(`Non-JSON line: ${line.substring(0, 100)}`);
|
|
175
194
|
}
|
package/yeaft/mcp.js
CHANGED
|
@@ -81,6 +81,7 @@ class MCPServerConnection extends EventEmitter {
|
|
|
81
81
|
this.#process = spawn(this.config.command, this.config.args || [], {
|
|
82
82
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
83
|
env: { ...process.env, ...this.config.env },
|
|
84
|
+
windowsHide: true,
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
this.#process.stdout.on('data', (data) => {
|
package/yeaft/tools/bash.js
CHANGED
|
@@ -14,6 +14,12 @@ import { existsSync, mkdirSync } from 'fs';
|
|
|
14
14
|
import { join, resolve } from 'path';
|
|
15
15
|
import { randomUUID } from 'crypto';
|
|
16
16
|
|
|
17
|
+
const HIDDEN_PROCESS_OPTIONS = { windowsHide: true };
|
|
18
|
+
|
|
19
|
+
function gitExecFileSync(args, options = {}) {
|
|
20
|
+
return execFileSync('git', args, { ...options, ...HIDDEN_PROCESS_OPTIONS });
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
export default defineTool({
|
|
18
24
|
name: 'EnterWorktree',
|
|
19
25
|
description: {
|
|
@@ -61,7 +67,7 @@ Worktree 创建在 .yeaft/worktrees/ 中,基于 HEAD 创建新分支。返回
|
|
|
61
67
|
|
|
62
68
|
// Verify we're in a git repo
|
|
63
69
|
try {
|
|
64
|
-
|
|
70
|
+
gitExecFileSync(['rev-parse', '--git-dir'], { cwd, stdio: 'pipe' });
|
|
65
71
|
} catch {
|
|
66
72
|
return JSON.stringify({ error: 'Not in a git repository' });
|
|
67
73
|
}
|
|
@@ -93,7 +99,7 @@ Worktree 创建在 .yeaft/worktrees/ 中,基于 HEAD 创建新分支。返回
|
|
|
93
99
|
try {
|
|
94
100
|
// Create worktree with new branch. Use execFileSync args instead of
|
|
95
101
|
// shell quoting so Windows drive letters and spaces in paths survive.
|
|
96
|
-
|
|
102
|
+
gitExecFileSync(['worktree', 'add', '-b', branchName, worktreeDir, baseRef], { cwd, stdio: 'pipe' });
|
|
97
103
|
|
|
98
104
|
return JSON.stringify({
|
|
99
105
|
success: true,
|
|
@@ -12,6 +12,12 @@ import { execFileSync } from 'child_process';
|
|
|
12
12
|
import { existsSync } from 'fs';
|
|
13
13
|
import { resolve } from 'path';
|
|
14
14
|
|
|
15
|
+
const HIDDEN_PROCESS_OPTIONS = { windowsHide: true };
|
|
16
|
+
|
|
17
|
+
function gitExecFileSync(args, options = {}) {
|
|
18
|
+
return execFileSync('git', args, { ...options, ...HIDDEN_PROCESS_OPTIONS });
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
export default defineTool({
|
|
16
22
|
name: 'ExitWorktree',
|
|
17
23
|
description: {
|
|
@@ -84,7 +90,7 @@ unless discard_changes is set to true.`,
|
|
|
84
90
|
// Check for uncommitted changes
|
|
85
91
|
if (!input.discard_changes) {
|
|
86
92
|
try {
|
|
87
|
-
const status =
|
|
93
|
+
const status = gitExecFileSync(['status', '--porcelain'], {
|
|
88
94
|
cwd: worktreePath,
|
|
89
95
|
encoding: 'utf8',
|
|
90
96
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -104,7 +110,7 @@ unless discard_changes is set to true.`,
|
|
|
104
110
|
// Get branch name before removal
|
|
105
111
|
let branchName = null;
|
|
106
112
|
try {
|
|
107
|
-
branchName =
|
|
113
|
+
branchName = gitExecFileSync(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
108
114
|
cwd: worktreePath,
|
|
109
115
|
encoding: 'utf8',
|
|
110
116
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -115,7 +121,7 @@ unless discard_changes is set to true.`,
|
|
|
115
121
|
|
|
116
122
|
// Remove worktree. Use argv form so Windows paths with spaces or drive
|
|
117
123
|
// letters are passed to git unchanged.
|
|
118
|
-
|
|
124
|
+
gitExecFileSync(['worktree', 'remove', ...(input.discard_changes ? ['--force'] : []), worktreePath], {
|
|
119
125
|
cwd: mainCwd,
|
|
120
126
|
stdio: 'pipe',
|
|
121
127
|
});
|
|
@@ -123,7 +129,7 @@ unless discard_changes is set to true.`,
|
|
|
123
129
|
// Remove the branch if it was a yeaft worktree branch
|
|
124
130
|
if (branchName && branchName.startsWith('yeaft-wt/')) {
|
|
125
131
|
try {
|
|
126
|
-
|
|
132
|
+
gitExecFileSync(['branch', '-D', branchName], {
|
|
127
133
|
cwd: mainCwd,
|
|
128
134
|
stdio: 'pipe',
|
|
129
135
|
});
|
package/yeaft/tools/grep.js
CHANGED
|
@@ -30,7 +30,7 @@ const BINARY_EXTS = new Set([
|
|
|
30
30
|
*/
|
|
31
31
|
function hasRipgrep() {
|
|
32
32
|
return new Promise((resolve) => {
|
|
33
|
-
const proc = spawn('rg', ['--version'], { stdio: 'pipe' });
|
|
33
|
+
const proc = spawn('rg', ['--version'], { stdio: 'pipe', windowsHide: true });
|
|
34
34
|
proc.on('close', (code) => resolve(code === 0));
|
|
35
35
|
proc.on('error', () => resolve(false));
|
|
36
36
|
});
|
|
@@ -60,7 +60,7 @@ function runRipgrep(pattern, searchPath, options) {
|
|
|
60
60
|
if (options.multiline) args.push('-U', '--multiline-dotall');
|
|
61
61
|
args.push('--max-count', String(options.maxResults || 500));
|
|
62
62
|
|
|
63
|
-
const proc = spawn('rg', args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
63
|
+
const proc = spawn('rg', args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
64
64
|
let stdout = '';
|
|
65
65
|
let stderr = '';
|
|
66
66
|
|