@yeaft/webchat-agent 1.0.51 → 1.0.53
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/package.json +1 -1
- package/providers/copilot-models.js +1 -1
- package/yeaft/mcp.js +1 -0
- package/yeaft/prompts.js +7 -41
- 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/yeaft/vp/seed-topup.js +25 -17
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/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));
|
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/prompts.js
CHANGED
|
@@ -26,7 +26,6 @@ import { readFileSync, existsSync } from 'fs';
|
|
|
26
26
|
import { join, dirname } from 'path';
|
|
27
27
|
import { fileURLToPath } from 'url';
|
|
28
28
|
import { getRuntimePlatformInfo, renderRuntimePlatformPrompt } from './runtime-platform.js';
|
|
29
|
-
import { DEFAULT_VPS } from './vp/seed-defaults.js';
|
|
30
29
|
|
|
31
30
|
// ─── Template Loading (one-time at startup) ──────────────────────
|
|
32
31
|
|
|
@@ -463,50 +462,17 @@ function selectVpPersonaName(vpPersona, effectiveLang) {
|
|
|
463
462
|
return typeof vpPersona.displayName === 'string' ? vpPersona.displayName.trim() : '';
|
|
464
463
|
}
|
|
465
464
|
|
|
466
|
-
function normalizePersonaBodyForMatch(value) {
|
|
467
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function selectMigratedStockPersonaBody(vpPersona, body, effectiveLang) {
|
|
471
|
-
const vpId = typeof vpPersona?.vpId === 'string' ? vpPersona.vpId.trim() : '';
|
|
472
|
-
const persistedBody = normalizePersonaBodyForMatch(body);
|
|
473
|
-
if (!vpId || !persistedBody) return null;
|
|
474
|
-
|
|
475
|
-
const stock = DEFAULT_VPS.find(vp => vp.vpId === vpId);
|
|
476
|
-
if (!stock) return null;
|
|
477
|
-
|
|
478
|
-
const acceptedBodies = [
|
|
479
|
-
stock.legacyPersonaEn,
|
|
480
|
-
stock.legacyPersona,
|
|
481
|
-
stock.personaEn,
|
|
482
|
-
...(Array.isArray(stock.legacyPersonas) ? stock.legacyPersonas : []),
|
|
483
|
-
]
|
|
484
|
-
.map(normalizePersonaBodyForMatch)
|
|
485
|
-
.filter(Boolean);
|
|
486
|
-
|
|
487
|
-
if (!acceptedBodies.includes(persistedBody)) return null;
|
|
488
|
-
const selected = effectiveLang === 'zh' ? stock.personaZh : stock.personaEn;
|
|
489
|
-
return normalizePersonaBodyForMatch(selected) || null;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
465
|
function selectVpPersonaBody(vpPersona, effectiveLang) {
|
|
493
466
|
const body = typeof vpPersona.persona === 'string' ? vpPersona.persona.trim() : '';
|
|
494
467
|
|
|
495
|
-
// role.md
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
if (body) {
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (body.includes('<!-- lang:')) {
|
|
503
|
-
const selected = extractExactLangSection(body, effectiveLang);
|
|
504
|
-
return selected !== null ? selected : body;
|
|
505
|
-
}
|
|
506
|
-
return body;
|
|
468
|
+
// role.md is the canonical soul source. Stock legacy migration belongs in
|
|
469
|
+
// seed-topup, not in prompt rendering; prompt rendering should only select
|
|
470
|
+
// authored language sections when they are present.
|
|
471
|
+
if (body && body.includes('<!-- lang:')) {
|
|
472
|
+
const selected = extractExactLangSection(body, effectiveLang);
|
|
473
|
+
return selected !== null ? selected : body;
|
|
507
474
|
}
|
|
508
|
-
|
|
509
|
-
return '';
|
|
475
|
+
return body;
|
|
510
476
|
}
|
|
511
477
|
|
|
512
478
|
|
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
|
|
package/yeaft/vp/seed-topup.js
CHANGED
|
@@ -9,24 +9,30 @@
|
|
|
9
9
|
* (b) a forced overwrite that would clobber their hand edits.
|
|
10
10
|
*
|
|
11
11
|
* This module runs on every agent start alongside `seedDefaultVps` and does
|
|
12
|
-
*
|
|
12
|
+
* three safe stock-maintenance things:
|
|
13
13
|
*
|
|
14
14
|
* 1. **Top-up missing stock VPs**. If a vpId from `DEFAULT_VPS` is not
|
|
15
15
|
* on disk, `createVp()` it. This keeps product-owned defaults such as
|
|
16
16
|
* Omni and the expanded role roster visible in session/member pickers.
|
|
17
17
|
*
|
|
18
|
-
* 2. **Backfill
|
|
19
|
-
* role.md predates
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* 2. **Backfill missing stock frontmatter** (`area`, `nameZh`, `roleZh`) on
|
|
19
|
+
* existing seeded VPs whose role.md predates those fields. The persona body
|
|
20
|
+
* is left BYTE-IDENTICAL for these metadata backfills. If the user has
|
|
21
|
+
* authored their own value, we keep theirs.
|
|
22
|
+
*
|
|
23
|
+
* 3. **Migrate exact historical stock bodies to canonical localized bodies**.
|
|
24
|
+
* This is intentionally a write-time migration, not a prompt-rendering
|
|
25
|
+
* fallback: once top-up sees an old shipped Omni/Linus/etc. body, role.md is
|
|
26
|
+
* rewritten to the current `<!-- lang:en -->` / `<!-- lang:zh -->` stock
|
|
27
|
+
* persona. User-edited bodies are not touched.
|
|
23
28
|
*
|
|
24
29
|
* Hard rules:
|
|
25
|
-
* - **Never** overwrite a VP
|
|
26
|
-
*
|
|
27
|
-
* - **Keep stock defaults available.** If a shipped stock VP is
|
|
28
|
-
* recreate it
|
|
29
|
-
*
|
|
30
|
+
* - **Never** overwrite a user-edited VP. A persona body is only rewritten when
|
|
31
|
+
* it exactly matches a historical body shipped by us.
|
|
32
|
+
* - **Keep stock defaults canonical and available.** If a shipped stock VP is
|
|
33
|
+
* missing, recreate it. If it exists with an old exact stock body, rewrite
|
|
34
|
+
* that body to the current canonical localized body. The session/member
|
|
35
|
+
* picker depends on these product-owned defaults being present and current.
|
|
30
36
|
* - Best-effort: any failure is logged, never thrown.
|
|
31
37
|
*
|
|
32
38
|
* Pre-ledger deletion caveat: on the very first top-up against an existing
|
|
@@ -47,8 +53,9 @@
|
|
|
47
53
|
* }
|
|
48
54
|
* }
|
|
49
55
|
*
|
|
50
|
-
* The hash
|
|
51
|
-
*
|
|
56
|
+
* The hash tracks the canonical stock persona currently written by top-up. Exact
|
|
57
|
+
* historical stock bodies are auto-migrated to this canonical localized body;
|
|
58
|
+
* user-edited bodies are deliberately left alone.
|
|
52
59
|
*/
|
|
53
60
|
|
|
54
61
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'fs';
|
|
@@ -231,7 +238,7 @@ function replaceRoleBody(source, body) {
|
|
|
231
238
|
return `${match[1]}${String(body || '').trim()}\n`;
|
|
232
239
|
}
|
|
233
240
|
|
|
234
|
-
function backfillLocalizedPersonaBody(source, vp) {
|
|
241
|
+
export function backfillLocalizedPersonaBody(source, vp) {
|
|
235
242
|
const body = roleBodyOf(source).trim();
|
|
236
243
|
const nextBody = typeof vp.persona === 'string' ? vp.persona.trim() : '';
|
|
237
244
|
if (!body || !nextBody || body === nextBody) return null;
|
|
@@ -301,9 +308,9 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
301
308
|
const inLedger = Object.prototype.hasOwnProperty.call(versions.seeded, vpId);
|
|
302
309
|
|
|
303
310
|
if (onDisk) {
|
|
304
|
-
// Already there —
|
|
305
|
-
//
|
|
306
|
-
//
|
|
311
|
+
// Already there — preserve user-authored content, but keep shipped stock
|
|
312
|
+
// defaults canonical. Metadata backfills only add missing frontmatter;
|
|
313
|
+
// persona migration only rewrites exact historical stock bodies.
|
|
307
314
|
skippedExisting.push(vpId);
|
|
308
315
|
let currentSrc = null;
|
|
309
316
|
const rolePath = join(libDir, vpId, 'role.md');
|
|
@@ -388,6 +395,7 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
388
395
|
if (patched != null && patched !== src) {
|
|
389
396
|
writeFileSync(rolePath, patched, 'utf-8');
|
|
390
397
|
currentSrc = patched;
|
|
398
|
+
versions.seeded[vpId] = personaHash(vp.persona);
|
|
391
399
|
personaBackfilled.push(vpId);
|
|
392
400
|
}
|
|
393
401
|
}
|