@yeaft/webchat-agent 0.1.314 → 0.1.316

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/crew/control.js CHANGED
@@ -187,6 +187,8 @@ async function resumeSession(session) {
187
187
  message: { role: 'assistant', content: [{ type: 'text', text: 'Session 已恢复' }] }
188
188
  });
189
189
  sendStatusUpdate(session);
190
+ // Status changed to running — persist
191
+ saveSessionMeta(session).catch(e => console.warn('[Crew] Failed to save after resume:', e.message));
190
192
 
191
193
  if (session.pendingRoutes.length > 0) {
192
194
  const pending = session.pendingRoutes.slice();
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { dispatchToRole } from './routing.js';
6
6
  import { sendStatusUpdate } from './ui-messages.js';
7
+ import { debouncedSaveSessionMeta } from './persistence.js';
7
8
 
8
9
  /**
9
10
  * 处理人的输入
@@ -50,6 +51,8 @@ export async function handleCrewHumanInput(msg) {
50
51
  session.status = 'running';
51
52
  session.waitingHumanContext = null;
52
53
  sendStatusUpdate(session);
54
+ // Status changed + new human message — persist (debounced, dispatch will follow)
55
+ debouncedSaveSessionMeta(session);
53
56
 
54
57
  const target = targetRole || waitingContext?.fromRole || session.decisionMaker;
55
58
  await dispatchToRole(session, target, buildHumanContent('人工回复:', content), 'human');
@@ -119,6 +119,24 @@ export async function unhideCrewSession(sessionId) {
119
119
  // =====================================================================
120
120
 
121
121
  const MESSAGE_SHARD_SIZE = 256 * 1024; // 256KB per shard
122
+ const SAVE_DEBOUNCE_MS = 10_000; // 10 seconds
123
+
124
+ /**
125
+ * Debounced version of saveSessionMeta.
126
+ * Coalesces rapid-fire calls (e.g. multiple role turn-ends) into a single
127
+ * disk write per session, at most once every SAVE_DEBOUNCE_MS.
128
+ */
129
+ export function debouncedSaveSessionMeta(session) {
130
+ if (session._saveDebounceTimer) {
131
+ clearTimeout(session._saveDebounceTimer);
132
+ }
133
+ session._saveDebounceTimer = setTimeout(() => {
134
+ session._saveDebounceTimer = null;
135
+ saveSessionMeta(session).catch(e =>
136
+ console.warn('[Crew] Debounced save failed:', e.message)
137
+ );
138
+ }, SAVE_DEBOUNCE_MS);
139
+ }
122
140
 
123
141
  export async function saveSessionMeta(session) {
124
142
  const meta = {
@@ -127,9 +145,11 @@ export async function saveSessionMeta(session) {
127
145
  sharedDir: session.sharedDir,
128
146
  name: session.name || '',
129
147
  status: session.status,
148
+ // claudeMd is intentionally excluded — it's large and already persisted
149
+ // in per-role CLAUDE.md files under .crew/roles/<name>/CLAUDE.md
130
150
  roles: Array.from(session.roles.values()).map(r => ({
131
151
  name: r.name, displayName: r.displayName, icon: r.icon,
132
- description: r.description, claudeMd: r.claudeMd || '',
152
+ description: r.description,
133
153
  isDecisionMaker: r.isDecisionMaker || false,
134
154
  groupIndex: r.groupIndex, roleType: r.roleType, model: r.model
135
155
  })),
@@ -5,6 +5,7 @@
5
5
  import { initRoleDir, updateSharedClaudeMd } from './shared-dir.js';
6
6
  import { saveRoleSessionId } from './role-query.js';
7
7
  import { sendCrewMessage, sendCrewOutput, sendStatusUpdate } from './ui-messages.js';
8
+ import { saveSessionMeta, upsertCrewIndex } from './persistence.js';
8
9
 
9
10
  /** Format role label */
10
11
  function roleLabel(r) {
@@ -94,6 +95,9 @@ export async function addRoleToSession(msg) {
94
95
  }
95
96
 
96
97
  sendStatusUpdate(session);
98
+ // Roles changed — persist
99
+ saveSessionMeta(session).catch(e => console.warn('[Crew] Failed to save after addRole:', e.message));
100
+ upsertCrewIndex(session).catch(e => console.warn('[Crew] Failed to update index after addRole:', e.message));
97
101
  }
98
102
 
99
103
  /**
@@ -152,4 +156,7 @@ export async function removeRoleFromSession(msg) {
152
156
  });
153
157
 
154
158
  sendStatusUpdate(session);
159
+ // Roles changed — persist
160
+ saveSessionMeta(session).catch(e => console.warn('[Crew] Failed to save after removeRole:', e.message));
161
+ upsertCrewIndex(session).catch(e => console.warn('[Crew] Failed to update index after removeRole:', e.message));
155
162
  }
@@ -6,6 +6,7 @@ import { sendCrewMessage, sendCrewOutput, sendStatusUpdate, endRoleStreaming } f
6
6
  import { saveRoleSessionId, clearRoleSessionId, classifyRoleError, createRoleQuery } from './role-query.js';
7
7
  import { parseRoutes, executeRoute, dispatchToRole } from './routing.js';
8
8
  import { parseCompletedTasks, updateFeatureIndex, appendChangelog, saveRoleWorkSummary, updateKanban } from './task-files.js';
9
+ import { debouncedSaveSessionMeta, saveSessionMeta } from './persistence.js';
9
10
  import ctx from '../context.js';
10
11
 
11
12
  // Context 使用率常量(运行时从 ctx.CONFIG 读取)
@@ -170,6 +171,8 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
170
171
  });
171
172
 
172
173
  sendStatusUpdate(session);
174
+ // Cost/tokens/messages updated — debounced persist (coalesces rapid turn-ends)
175
+ debouncedSaveSessionMeta(session);
173
176
 
174
177
  // 执行路由
175
178
  if (routes.length > 0) {
@@ -267,6 +270,8 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
267
270
  message: errDetail
268
271
  });
269
272
  sendStatusUpdate(session);
273
+ // Status changed to waiting_human — persist
274
+ saveSessionMeta(session).catch(e => console.warn('[Crew] Failed to save after error→human:', e.message));
270
275
  }
271
276
  return;
272
277
  }
package/crew/routing.js CHANGED
@@ -6,6 +6,7 @@ import { join } from 'path';
6
6
  import { sendCrewMessage, sendCrewOutput, sendStatusUpdate } from './ui-messages.js';
7
7
  import { ensureTaskFile, appendTaskRecord, readTaskFile, updateKanban, readKanban, saveRoleWorkSummary } from './task-files.js';
8
8
  import { createRoleQuery, clearRoleSessionId } from './role-query.js';
9
+ import { saveSessionMeta } from './persistence.js';
9
10
  import ctx from '../context.js';
10
11
 
11
12
  /** Format role label */
@@ -19,7 +20,8 @@ function roleLabel(r) {
19
20
  */
20
21
  export function parseRoutes(text) {
21
22
  const routes = [];
22
- const regex = /---ROUTE---\s*\n([\s\S]*?)---END_ROUTE---/g;
23
+ // ★ Tolerate both underscore and space variants: ---END_ROUTE--- or ---END ROUTE---
24
+ const regex = /---ROUTE---\s*\n([\s\S]*?)---END[_ ]ROUTE---/g;
23
25
  let match;
24
26
 
25
27
  while ((match = regex.exec(text)) !== null) {
@@ -168,6 +170,8 @@ export async function executeRoute(session, fromRole, route) {
168
170
  message: summary
169
171
  });
170
172
  sendStatusUpdate(session);
173
+ // Status changed to waiting_human — persist
174
+ saveSessionMeta(session).catch(e => console.warn('[Crew] Failed to save after →human:', e.message));
171
175
  return;
172
176
  }
173
177
 
package/crew/session.js CHANGED
@@ -488,6 +488,8 @@ export async function resumeCrewSession(msg) {
488
488
  }
489
489
 
490
490
  const roles = meta.roles || [];
491
+ // Migration: strip claudeMd from legacy session.json (now persisted in per-role CLAUDE.md files)
492
+ for (const r of roles) delete r.claudeMd;
491
493
  if (roles.length === 0) {
492
494
  console.warn(`[Crew] resumeCrewSession: session ${sessionId} has empty roles in session.json`);
493
495
  }
@@ -594,11 +596,11 @@ export async function handleUpdateCrewSession(msg) {
594
596
  console.log(`[Crew] Role added during update: ${r.name} (${r.displayName})`);
595
597
  } else {
596
598
  // Existing role — update metadata (displayName, icon, etc.) but preserve roleState
599
+ // claudeMd is NOT updated here — it's managed via CLAUDE.md files, not through edit session
597
600
  const existing = session.roles.get(r.name);
598
601
  existing.displayName = r.displayName;
599
602
  existing.icon = r.icon;
600
603
  existing.description = r.description;
601
- existing.claudeMd = r.claudeMd;
602
604
  existing.isDecisionMaker = r.isDecisionMaker;
603
605
  existing.roleType = r.roleType;
604
606
  existing.groupIndex = r.groupIndex;
@@ -3,7 +3,6 @@
3
3
  * sendCrewMessage, sendCrewOutput, sendStatusUpdate, endRoleStreaming, findActiveRole
4
4
  */
5
5
  import ctx from '../context.js';
6
- import { upsertCrewIndex, saveSessionMeta } from './persistence.js';
7
6
 
8
7
  /**
9
8
  * 发送 crew 消息到 server(透传到 Web)
@@ -257,8 +256,6 @@ export function sendStatusUpdate(session) {
257
256
  features: getActiveFeatures(session),
258
257
  initProgress: session.initProgress || null
259
258
  });
260
-
261
- // 异步更新持久化
262
- upsertCrewIndex(session).catch(e => console.warn('[Crew] Failed to update index:', e.message));
263
- saveSessionMeta(session).catch(e => console.warn('[Crew] Failed to save session meta:', e.message));
259
+ // Persist is NOT called here — callers that change persistent state
260
+ // (status, roles, cost, messages) must call saveSessionMeta explicitly.
264
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.314",
3
+ "version": "0.1.316",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",