@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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 persona is the canonical soul. Exact stock legacy bodies are mapped
496
- // to the current authored source so old seeded role.md files do not leak
497
- // bilingual/English souls into localized system prompts before top-up runs.
498
- if (body) {
499
- const migratedStockBody = selectMigratedStockPersonaBody(vpPersona, body, effectiveLang);
500
- if (migratedStockBody) return migratedStockBody;
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
 
@@ -45,6 +45,7 @@ function runCommand(command, { cwd, timeout, signal, runtimePlatform }) {
45
45
  env,
46
46
  stdio: ['ignore', 'pipe', 'pipe'],
47
47
  detached: !platform.isWindows,
48
+ windowsHide: true,
48
49
  });
49
50
 
50
51
  let stdout = '';
@@ -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
- execFileSync('git', ['rev-parse', '--git-dir'], { cwd, stdio: 'pipe' });
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
- execFileSync('git', ['worktree', 'add', '-b', branchName, worktreeDir, baseRef], { cwd, stdio: 'pipe' });
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 = execFileSync('git', ['status', '--porcelain'], {
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 = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
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
- execFileSync('git', ['worktree', 'remove', ...(input.discard_changes ? ['--force'] : []), worktreePath], {
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
- execFileSync('git', ['branch', '-D', branchName], {
132
+ gitExecFileSync(['branch', '-D', branchName], {
127
133
  cwd: mainCwd,
128
134
  stdio: 'pipe',
129
135
  });
@@ -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
 
@@ -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
- * two minimal, additive things:
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 the `area` frontmatter line** on existing seeded VPs whose
19
- * role.md predates the area field. The body is left BYTE-IDENTICAL —
20
- * we splice a single `area: <bucket>` line into the YAML frontmatter
21
- * and write nothing else. If the user has authored their own `area`,
22
- * we keep theirs.
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 that is on disk. The user might have edited
26
- * persona/role/traits; that is their truth, not ours.
27
- * - **Keep stock defaults available.** If a shipped stock VP is missing,
28
- * recreate it, but never overwrite an on-disk VP. The session/member picker
29
- * depends on these product-owned defaults being present.
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 is reserved for future "the default persona changed; offer the
51
- * user a migration" semantics. We do not auto-upgrade today.
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 — never overwrite. Possibly backfill `area` /
305
- // `nameZh`. Each backfill is independent: a role.md with `area`
306
- // already set but no `nameZh` should still get `nameZh`.
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
  }