@xcanwin/manyoyo 7.0.13 → 7.0.15

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/lib/web/server.js CHANGED
@@ -2068,7 +2068,7 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
2068
2068
 
2069
2069
  function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, meta, result) {
2070
2070
  const turnUsage = extractTurnUsage(agentMeta.agentProgram, result.stdout);
2071
- appendWebSessionMessage(state.webHistoryDir, sessionRef, 'assistant', result.output, {
2071
+ const replyMessage = appendWebSessionMessage(state.webHistoryDir, sessionRef, 'assistant', result.output, {
2072
2072
  exitCode: result.exitCode,
2073
2073
  mode: 'agent',
2074
2074
  contextMode: meta.contextMode,
@@ -2090,6 +2090,7 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
2090
2090
  patch.usageTotal = accumulateUsageTotal(agentSession.usageTotal, turnUsage);
2091
2091
  }
2092
2092
  patchWebAgentSessionState(state.webHistoryDir, sessionRef, patch);
2093
+ return replyMessage;
2093
2094
  }
2094
2095
 
2095
2096
  function appendWebAgentTraceMessage(webHistoryDir, sessionRefOrContainerName, content, extra = {}) {
@@ -3799,11 +3800,37 @@ function stopWebAgentRun(state, containerName) {
3799
3800
  try {
3800
3801
  runState.process.kill('SIGTERM');
3801
3802
  } catch (e) {
3802
- return false;
3803
+ // 进程可能已自行退出,视为停止成功(close 事件会收尾)
3803
3804
  }
3804
3805
  return true;
3805
3806
  }
3806
3807
 
3808
+ // serve 重启等原因会丢失 agentRuns 运行登记,但消息里可能残留 pending 标记
3809
+ // (孤儿任务:容器里的进程或许还在跑,但再没有任何代码会去收尾这些消息)。
3810
+ // 前端把 pending 视为"任务进行中"会永久禁用输入,这里把它们标记为已中断
3811
+ function interruptOrphanPendingMessages(webHistoryDir, sessionRef) {
3812
+ const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
3813
+ const agentSession = getWebAgentSession(history, sessionRef.agentId, { includeArchived: true });
3814
+ if (!agentSession || !Array.isArray(agentSession.messages)) {
3815
+ return;
3816
+ }
3817
+ let touched = false;
3818
+ for (const message of agentSession.messages) {
3819
+ if (message && message.pending === true) {
3820
+ message.pending = false;
3821
+ message.interrupted = true;
3822
+ touched = true;
3823
+ }
3824
+ }
3825
+ if (!touched) {
3826
+ return;
3827
+ }
3828
+ const timestamp = new Date().toISOString();
3829
+ agentSession.updatedAt = timestamp;
3830
+ history.updatedAt = timestamp;
3831
+ saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
3832
+ }
3833
+
3807
3834
  function sendHtml(res, statusCode, html, extraHeaders = {}) {
3808
3835
  res.writeHead(statusCode, {
3809
3836
  'Content-Type': 'text/html; charset=utf-8',
@@ -5212,7 +5239,8 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5212
5239
  return;
5213
5240
  }
5214
5241
  if (state.agentRuns.has(sessionRef.containerName)) {
5215
- sendJson(res, 409, { error: '当前会话已有运行中的 agent 任务' });
5242
+ // 锁是容器级的:同一容器里其他 agent 会话在跑任务时同样命中这里
5243
+ sendJson(res, 409, { error: '当前容器已有运行中的 agent 任务,请等它结束或先停止' });
5216
5244
  return;
5217
5245
  }
5218
5246
 
@@ -5259,6 +5287,10 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5259
5287
  type: 'meta',
5260
5288
  containerName: sessionRef.containerName,
5261
5289
  sessionName: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId),
5290
+ // 前端用这两个 id 把本地乐观占位(local-* / __streaming_trace__)换成
5291
+ // 服务端持久化 id,轮询对账时 React key 才能对得上、DOM 不整体重建
5292
+ userMessageId: userMessage && userMessage.id,
5293
+ traceMessageId: traceMessage && traceMessage.id,
5262
5294
  contextMode,
5263
5295
  resumeAttempted,
5264
5296
  resumeSucceeded,
@@ -5342,7 +5374,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5342
5374
  if (streamingReplyMessageId) {
5343
5375
  removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
5344
5376
  }
5345
- finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, {
5377
+ const replyMessage = finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, {
5346
5378
  contextMode,
5347
5379
  resumeAttempted,
5348
5380
  resumeSucceeded,
@@ -5353,6 +5385,9 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5353
5385
  type: 'result',
5354
5386
  exitCode: result.exitCode,
5355
5387
  output: result.output,
5388
+ // 最终 assistant 消息的持久化 id:前端把 __streaming__ 占位换成它,
5389
+ // 发送后的首次对账才不会重建整个消息列表 DOM(滚动位置/选区保持)
5390
+ replyMessageId: replyMessage && replyMessage.id,
5356
5391
  contextMode,
5357
5392
  resumeAttempted,
5358
5393
  resumeSucceeded,
@@ -5397,6 +5432,9 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5397
5432
  }
5398
5433
  const stopped = stopWebAgentRun(state, sessionRef.containerName);
5399
5434
  if (!stopped) {
5435
+ // 运行登记不存在(serve 重启后常见的孤儿任务):把残留的
5436
+ // pending 消息标记为已中断,否则前端会因 pending 永久禁用输入
5437
+ interruptOrphanPendingMessages(state.webHistoryDir, sessionRef);
5400
5438
  sendJson(res, 404, { error: '当前会话没有运行中的 agent 任务' });
5401
5439
  return;
5402
5440
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "7.0.13",
3
+ "version": "7.0.15",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.18",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",