@yeaft/webchat-agent 1.0.325 → 1.0.328

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.
@@ -29,23 +29,18 @@ const DEFAULT_TIMEOUT_MS = 120_000;
29
29
 
30
30
  /** Max timeout in ms (10 minutes). */
31
31
  const MAX_TIMEOUT_MS = 600_000;
32
- /**
33
- * ToolRegistry must not preempt Bash's own process timeout. Bash terminates
34
- * the process tree and waits for close before returning exit 124; the grace
35
- * covers TERM/KILL escalation and the bounded close-confirmation window.
36
- */
37
- const REGISTRY_TIMEOUT_MS = MAX_TIMEOUT_MS + 15_000;
38
32
  const LINUX_NAMESPACE_HELPER = resolve(
39
33
  dirname(fileURLToPath(import.meta.url)),
40
34
  '..',
41
35
  'linux-process-namespace.js',
42
36
  );
37
+ const RUN_PROCESS_OVERRIDE = Symbol('runProcessOverride');
43
38
 
44
39
  /**
45
40
  * Run a command in a child process.
46
- * @returns {Promise<{ stdout: string, stderr: string, exitCode: number, timedOut: boolean }>}
41
+ * @returns {Promise<{ stdout: string, stderr: string, exitCode: number, timedOut: boolean, terminationError: string | null }>}
47
42
  */
48
- function runCommand(command, { cwd, timeout, signal, runtimePlatform }) {
43
+ function runCommand(command, { cwd, timeout, signal, runtimePlatform, runProcessImpl = runProcess }) {
49
44
  const platform = runtimePlatform || getRuntimePlatformInfo();
50
45
  const env = { ...process.env, TERM: 'dumb', FORCE_COLOR: '0' };
51
46
  const baseInvocation = buildShellInvocation(command, { runtimePlatform: platform });
@@ -76,7 +71,7 @@ function runCommand(command, { cwd, timeout, signal, runtimePlatform }) {
76
71
  systemdControl: null,
77
72
  };
78
73
  }
79
- return runProcess(invocation.command, invocation.args, {
74
+ return runProcessImpl(invocation.command, invocation.args, {
80
75
  cwd,
81
76
  env,
82
77
  signal,
@@ -91,10 +86,11 @@ function runCommand(command, { cwd, timeout, signal, runtimePlatform }) {
91
86
  stderr: result.stderr,
92
87
  exitCode: result.code,
93
88
  timedOut: result.timedOut,
89
+ terminationError: result.terminationError || null,
94
90
  }));
95
91
  }
96
92
 
97
- export default defineTool({
93
+ const bashTool = defineTool({
98
94
  name: 'Bash',
99
95
  description: {
100
96
  en: `Execute a shell command and return its output.
@@ -111,6 +107,7 @@ Guidelines:
111
107
  - Use absolute paths when possible
112
108
  - Avoid interactive commands (no stdin support)
113
109
  - Use background=true for long-running or persistent tasks that should survive across turns
110
+ - Foreground timeout and cleanup status is returned to you; decide whether to inspect, retry, stop, or use a background task
114
111
  - stderr is captured separately and included in the result`,
115
112
  zh: `执行 Shell 命令并返回输出。
116
113
 
@@ -123,6 +120,7 @@ Guidelines:
123
120
  - 尽量使用绝对路径
124
121
  - 避免交互式命令(不支持 stdin)
125
122
  - 长时间或需要跨 turn 持续存在的任务使用 background=true
123
+ - 前台命令的超时和清理状态会返回给你;由你决定检查、重试、停止或改用后台任务
126
124
  - stderr 单独捕获并包含在结果中`
127
125
  },
128
126
  parameters: {
@@ -167,7 +165,10 @@ Guidelines:
167
165
  required: ['command'],
168
166
  },
169
167
  errorOutput: null,
170
- timeoutMs: REGISTRY_TIMEOUT_MS,
168
+ // Foreground Bash owns a bounded timeout and process-tree cleanup state
169
+ // machine. A second ToolRegistry timer can preempt that cleanup and turn an
170
+ // owned exit 124 into a fatal orphan, so it must stay disabled for this tool.
171
+ timeoutMs: 0,
171
172
  isConcurrencySafe: () => false,
172
173
  isReadOnly: () => false,
173
174
  isDestructive: (input) => {
@@ -224,6 +225,7 @@ Guidelines:
224
225
  timeout,
225
226
  signal: ctx?.signal,
226
227
  runtimePlatform,
228
+ runProcessImpl: ctx?.[RUN_PROCESS_OVERRIDE] || runProcess,
227
229
  });
228
230
 
229
231
  // Format output similar to Claude Code
@@ -231,6 +233,24 @@ Guidelines:
231
233
  if (result.stdout) parts.push(result.stdout);
232
234
  if (result.stderr) parts.push(`STDERR:\n${result.stderr}`);
233
235
  if (result.timedOut) parts.push(`\n(Command timed out after ${timeout}ms)`);
236
+ if (result.terminationError) {
237
+ parts.push([
238
+ `WARNING: ${result.terminationError}`,
239
+ 'The command timed out, but process-tree termination could not be confirmed. The command may still be running.',
240
+ 'Decide whether to inspect or stop it, retry, or use background=true.',
241
+ ].join('\n'));
242
+ }
243
+ if (result.timedOut) {
244
+ ctx?.requestToolBatchBarrier?.({
245
+ kind: 'owned_timeout',
246
+ message: [
247
+ result.terminationError
248
+ ? 'The foreground Bash command timed out and process-tree termination could not be confirmed.'
249
+ : 'The foreground Bash command timed out.',
250
+ 'Return this result to the model before executing another tool call from the same batch.',
251
+ ].join(' '),
252
+ });
253
+ }
234
254
 
235
255
  const output = parts.join('\n');
236
256
  if (result.exitCode !== 0) {
@@ -243,3 +263,14 @@ Guidelines:
243
263
  }
244
264
  },
245
265
  });
266
+
267
+ export function createBashTool({ runProcessImpl = runProcess } = {}) {
268
+ return {
269
+ ...bashTool,
270
+ execute(input, ctx) {
271
+ return bashTool.execute(input, { ...ctx, [RUN_PROCESS_OVERRIDE]: runProcessImpl });
272
+ },
273
+ };
274
+ }
275
+
276
+ export default bashTool;
@@ -102,6 +102,7 @@ function killProcessTree(proc, signalName, platform, spawnProcessSync, systemdSc
102
102
  * @param {string} command
103
103
  * @param {string[]} args
104
104
  * @param {{ cwd?: string, signal?: AbortSignal, timeoutMs?: number, maxBytes?: number, env?: NodeJS.ProcessEnv, preserveCarriageReturns?: boolean, killGraceMs?: number, forceSettleMs?: number, requireExitConfirmation?: boolean, systemdScope?: { unit: string, systemctlPath: string, env?: NodeJS.ProcessEnv } | null, onSettled?: (() => void) | null, platform?: NodeJS.Platform, spawnProcess?: typeof spawn, spawnProcessSync?: typeof spawnSync }} [options]
105
+ * @returns {Promise<{ code: number, stdout: string, stderr: string, truncated: boolean, timedOut: boolean, terminationError?: string }>}
105
106
  */
106
107
  export function runProcess(command, args, options = {}) {
107
108
  if (options.signal?.aborted) {
@@ -188,21 +189,30 @@ export function runProcess(command, args, options = {}) {
188
189
  if (settled) return;
189
190
  settled = true;
190
191
  cleanup();
191
- if (error) {
192
- reject(error);
193
- return;
194
- }
195
- if (aborted) {
196
- reject(abortError(options.signal));
197
- return;
198
- }
199
- resolve({
192
+ const result = {
200
193
  code: timedOut ? 124 : (code ?? 1),
201
194
  stdout: decode(stdout, stdoutTruncated, options.preserveCarriageReturns),
202
195
  stderr: decode(stderr, stderrTruncated),
203
196
  truncated,
204
197
  timedOut,
205
- });
198
+ };
199
+ if (aborted) {
200
+ reject(abortError(options.signal));
201
+ return;
202
+ }
203
+ if (error) {
204
+ // A command timeout is an owned, bounded outcome. Failure to observe
205
+ // the final child close is important context for the caller, but it
206
+ // must not turn the timeout into an infrastructure exception that
207
+ // prevents the model from deciding what to do next.
208
+ if (timedOut && error instanceof ProcessTerminationError) {
209
+ resolve({ ...result, terminationError: error.message });
210
+ return;
211
+ }
212
+ reject(error);
213
+ return;
214
+ }
215
+ resolve(result);
206
216
  };
207
217
  const terminationConfirmed = () => {
208
218
  if (!options.requireExitConfirmation) return directClosed;
@@ -334,6 +344,10 @@ export function runProcess(command, args, options = {}) {
334
344
 
335
345
  if (Number.isFinite(options.timeoutMs) && options.timeoutMs > 0) {
336
346
  timer = setTimeout(() => {
347
+ // Preserve the first stop reason. Output overflow and Abort may have
348
+ // already started termination; a later deadline must not relabel that
349
+ // cleanup failure as a recoverable command timeout.
350
+ if (settled || stopRequested) return;
337
351
  timedOut = true;
338
352
  stop();
339
353
  }, options.timeoutMs);
@@ -45,6 +45,11 @@
45
45
  * the supplied reason as `detail`. `reason` may be a structured object
46
46
  * `{kind, ...}` so downstream observers (web-bridge) can render UI hints
47
47
  * (e.g. "↪ 已转交给 @vp-b") without re-parsing strings.
48
+ * @property {(reason?: string|object) => void} [requestToolBatchBarrier]
49
+ * — stop executing the remaining calls in the current assistant tool batch
50
+ * while still pairing each call with an explicit skipped tool result. The
51
+ * engine then returns those results to the provider before accepting another
52
+ * plan. Used when a completed tool result invalidates the rest of the batch.
48
53
  * @property {string} [senderVpId] — id of the VP whose turn is currently
49
54
  * running. Used by `route_forward` to stamp the forwarded message and
50
55
  * by the loop guard to key per-sender throttling.
@@ -5277,12 +5277,11 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
5277
5277
  const projectSessionIds = projectContext?.sessionIds || [];
5278
5278
  queryOpts.projectSessionIds = projectSessionIds;
5279
5279
  queryOpts.projectInstruction = projectContext?.projectInstruction || '';
5280
- const projectSummaries = await sharedProjectContext(ctx.CONFIG?.yeaftDir, sessionId, {
5281
- sessionIds: projectSessionIds,
5282
- language: session?.config?.language,
5283
- tokenBudget: Math.max(512, Math.floor((session?.config?.messageTokenBudget || 32768) / 8)),
5284
- });
5285
- const sharedBlock = buildProjectSharedBlock(projectContext, projectSummaries);
5280
+ // Related Session summaries now enter through Engine's single AMS
5281
+ // memory outlet. Keep this announcement limited to Project identity and
5282
+ // sharing boundaries so parent VP prompts do not duplicate the same prose
5283
+ // that sub-agents receive through memory.
5284
+ const sharedBlock = buildProjectSharedBlock(projectContext);
5286
5285
  if (sharedBlock) {
5287
5286
  queryOpts.sessionAnnouncement = queryOpts.sessionAnnouncement
5288
5287
  ? `${queryOpts.sessionAnnouncement}\n\n${sharedBlock}`