@yeaft/webchat-agent 0.1.161 → 0.1.163

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/claude.js CHANGED
@@ -5,17 +5,22 @@ import { sendConversationList, sendOutput, sendError, handleAskUserQuestion } fr
5
5
  /**
6
6
  * Determine maxContextTokens and autoCompactThreshold from model name.
7
7
  * Returns defaults suitable for the model's context window size.
8
+ *
9
+ * NOTE (2026-03): Opus 4.6 / Sonnet 4 have 200k context windows.
10
+ * Claude Code handles its own compaction internally, so we set the
11
+ * default threshold to 200k (effectively never triggers our custom compact).
12
+ * The thresholds are kept as parameters in case we need to re-enable later.
8
13
  */
9
14
  export function getModelContextConfig(modelName) {
10
- if (!modelName) return { maxContext: 128000, compactThreshold: 110000 };
15
+ if (!modelName) return { maxContext: 200000, compactThreshold: 200000 };
11
16
  const name = modelName.toLowerCase();
12
17
  // Explicit 1M context indicators
13
18
  if (name.includes('1m') || name.includes('1000k')) {
14
19
  return { maxContext: 1000000, compactThreshold: 256000 };
15
20
  }
16
- // Default: 128kCopilot API models (Sonnet 4, Opus 4, Claude 3.5 etc.)
17
- // report 200k context but actual usable window is 128k
18
- return { maxContext: 128000, compactThreshold: 110000 };
21
+ // Default: 200kOpus 4.6 / Sonnet 4 context window.
22
+ // Claude Code manages its own compaction; we no longer need custom compact logic.
23
+ return { maxContext: 200000, compactThreshold: 200000 };
19
24
  }
20
25
 
21
26
  /**
@@ -484,39 +489,41 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
484
489
  } else if (resultHandled) {
485
490
  // Turn 已正常完成,进程退出产生的 error 不发送给用户
486
491
  console.warn(`[SDK] Ignoring post-result error for ${conversationId}: ${error.message}`);
487
- } else if (isPromptTokenOverflow(error.message) && state.claudeSessionId && !state._compactRetried) {
488
- // 兜底:prompt token 溢出 自动 compact + 重试(而非暴露 raw API error 给用户)
489
- console.warn(`[SDK] Prompt token overflow for ${conversationId}, auto-compact + retry`);
490
- const savedSessionId = state.claudeSessionId;
491
- const savedLastMsg = state._lastUserMessage;
492
-
493
- ctx.sendToServer({
494
- type: 'compact_status',
495
- conversationId,
496
- status: 'compacting',
497
- message: 'Context too long, auto-compacting and retrying...'
498
- });
499
-
500
- // 重启 SDK(startClaudeQuery 会先 abort 当前 state,使 finally 中 isStale=true)
501
- try {
502
- const newState = await startClaudeQuery(conversationId, state.workDir, savedSessionId);
503
- newState._compactRetried = true; // 防止无限重试
504
- newState.turnActive = true;
505
- newState.turnResultReceived = false;
506
-
507
- // compact,再重试原始消息(如果有的话)
508
- if (savedLastMsg) {
509
- newState._pendingUserMessage = savedLastMsg;
510
- }
511
- newState.inputStream.enqueue({
512
- type: 'user',
513
- message: { role: 'user', content: '/compact' }
514
- });
515
- sendConversationList();
516
- } catch (retryError) {
517
- console.error(`[SDK] Compact-retry failed for ${conversationId}:`, retryError.message);
518
- sendError(conversationId, `Context too long. Auto-compact failed: ${retryError.message}`);
519
- }
492
+ // DISABLED (2026-03): Opus 4.6 has 200k context. Claude Code handles its own compaction.
493
+ // Keeping code for reference; re-enable if we ever need custom overflow recovery.
494
+ // } else if (isPromptTokenOverflow(error.message) && state.claudeSessionId && !state._compactRetried) {
495
+ // // 兜底:prompt token 溢出 → 自动 compact + 重试(而非暴露 raw API error 给用户)
496
+ // console.warn(`[SDK] Prompt token overflow for ${conversationId}, auto-compact + retry`);
497
+ // const savedSessionId = state.claudeSessionId;
498
+ // const savedLastMsg = state._lastUserMessage;
499
+ //
500
+ // ctx.sendToServer({
501
+ // type: 'compact_status',
502
+ // conversationId,
503
+ // status: 'compacting',
504
+ // message: 'Context too long, auto-compacting and retrying...'
505
+ // });
506
+ //
507
+ // // 重启 SDK(startClaudeQuery 会先 abort 当前 state,使 finally 中 isStale=true)
508
+ // try {
509
+ // const newState = await startClaudeQuery(conversationId, state.workDir, savedSessionId);
510
+ // newState._compactRetried = true; // 防止无限重试
511
+ // newState.turnActive = true;
512
+ // newState.turnResultReceived = false;
513
+ //
514
+ // // compact,再重试原始消息(如果有的话)
515
+ // if (savedLastMsg) {
516
+ // newState._pendingUserMessage = savedLastMsg;
517
+ // }
518
+ // newState.inputStream.enqueue({
519
+ // type: 'user',
520
+ // message: { role: 'user', content: '/compact' }
521
+ // });
522
+ // sendConversationList();
523
+ // } catch (retryError) {
524
+ // console.error(`[SDK] Compact-retry failed for ${conversationId}:`, retryError.message);
525
+ // sendError(conversationId, `Context too long. Auto-compact failed: ${retryError.message}`);
526
+ // }
520
527
  } else {
521
528
  console.error(`[SDK] Error for ${conversationId}:`, error.message);
522
529
  sendError(conversationId, error.message);
package/conversation.js CHANGED
@@ -6,6 +6,20 @@ import { crewSessions, loadCrewIndex } from './crew.js';
6
6
  // 不支持的斜杠命令(真正需要交互式 CLI 的命令)
7
7
  const UNSUPPORTED_SLASH_COMMANDS = ['/help', '/bug', '/login', '/logout', '/terminal-setup', '/vim', '/config'];
8
8
 
9
+ /**
10
+ * Prestart Claude CLI process in background (fire-and-forget).
11
+ * When the query starts, processClaudeOutput will receive the system init message
12
+ * containing skills/tools/model and push them to the frontend immediately.
13
+ * This eliminates the delay where users had to send a message first.
14
+ *
15
+ * Errors are silently caught — failure just degrades to lazy-start behavior.
16
+ */
17
+ function prestartClaude(conversationId, workDir, resumeSessionId) {
18
+ startClaudeQuery(conversationId, workDir, resumeSessionId).catch(err => {
19
+ console.warn(`[Prestart] Failed for ${conversationId}: ${err.message}`);
20
+ });
21
+ }
22
+
9
23
  /**
10
24
  * 解析斜杠命令
11
25
  * @param {string} message - 用户消息
@@ -163,6 +177,10 @@ export async function createConversation(msg) {
163
177
  }
164
178
 
165
179
  sendConversationList();
180
+
181
+ // ★ Prestart Claude CLI in background to eagerly fetch skills/tools/model
182
+ // Fire-and-forget: failure just degrades to lazy-start behavior
183
+ prestartClaude(conversationId, effectiveWorkDir, null);
166
184
  }
167
185
 
168
186
  // Resume 历史 conversation (延迟启动 Claude,等待用户发送第一条消息)
@@ -242,6 +260,13 @@ export async function resumeConversation(msg) {
242
260
  }
243
261
 
244
262
  sendConversationList();
263
+
264
+ // ★ Prestart Claude CLI in background to eagerly fetch skills/tools/model
265
+ // Skip if conversation already has an active query (shouldn't happen, but safety check)
266
+ const resumeState = ctx.conversations.get(conversationId);
267
+ if (!resumeState?.query) {
268
+ prestartClaude(conversationId, effectiveWorkDir, claudeSessionId);
269
+ }
245
270
  }
246
271
 
247
272
  // 删除 conversation
@@ -443,35 +468,37 @@ export async function handleUserInput(msg) {
443
468
 
444
469
  console.log(`[${conversationId}] Sending: ${prompt.substring(0, 100)}...`);
445
470
 
471
+ // DISABLED (2026-03): Opus 4.6 has 200k context. Claude Code handles its own compaction.
472
+ // Keeping code for reference; re-enable if we ever need custom pre-send compact.
446
473
  // ★ Pre-send compact check: estimate total tokens and compact before sending if needed
447
- const autoCompactThreshold = state.autoCompactThreshold || ctx.CONFIG?.autoCompactThreshold || 110000;
448
- const lastInputTokens = state.lastResultInputTokens || 0;
449
- const lastOutputTokens = state.lastResultOutputTokens || 0;
450
- const estimatedNewTokens = Math.ceil(effectivePrompt.length / 3); // conservative: ~3 chars per token
451
- // Include output_tokens: the assistant's last output becomes part of context for the next turn
452
- const estimatedTotal = lastInputTokens + lastOutputTokens + estimatedNewTokens;
453
-
454
- if (estimatedTotal > autoCompactThreshold && state.inputStream) {
455
- console.log(`[${conversationId}] Pre-send compact: estimated ${estimatedTotal} tokens (input: ${lastInputTokens} + output: ${lastOutputTokens} + new: ~${estimatedNewTokens}) exceeds threshold ${autoCompactThreshold}`);
456
- ctx.sendToServer({
457
- type: 'compact_status',
458
- conversationId,
459
- status: 'compacting',
460
- message: `Auto-compacting before send: estimated ${estimatedTotal} tokens (threshold: ${autoCompactThreshold})`
461
- });
462
- // Send /compact first, then the user message will be sent after compact completes
463
- // by storing it as a pending message
464
- state._pendingUserMessage = userMessage;
465
- state._pendingDisplayMessage = displayMessage;
466
- state.turnActive = true;
467
- state.turnResultReceived = false;
468
- sendConversationList();
469
- state.inputStream.enqueue({
470
- type: 'user',
471
- message: { role: 'user', content: '/compact' }
472
- });
473
- return;
474
- }
474
+ // const autoCompactThreshold = state.autoCompactThreshold || ctx.CONFIG?.autoCompactThreshold || 110000;
475
+ // const lastInputTokens = state.lastResultInputTokens || 0;
476
+ // const lastOutputTokens = state.lastResultOutputTokens || 0;
477
+ // const estimatedNewTokens = Math.ceil(effectivePrompt.length / 3); // conservative: ~3 chars per token
478
+ // // Include output_tokens: the assistant's last output becomes part of context for the next turn
479
+ // const estimatedTotal = lastInputTokens + lastOutputTokens + estimatedNewTokens;
480
+ //
481
+ // if (estimatedTotal > autoCompactThreshold && state.inputStream) {
482
+ // console.log(`[${conversationId}] Pre-send compact: estimated ${estimatedTotal} tokens (input: ${lastInputTokens} + output: ${lastOutputTokens} + new: ~${estimatedNewTokens}) exceeds threshold ${autoCompactThreshold}`);
483
+ // ctx.sendToServer({
484
+ // type: 'compact_status',
485
+ // conversationId,
486
+ // status: 'compacting',
487
+ // message: `Auto-compacting before send: estimated ${estimatedTotal} tokens (threshold: ${autoCompactThreshold})`
488
+ // });
489
+ // // Send /compact first, then the user message will be sent after compact completes
490
+ // // by storing it as a pending message
491
+ // state._pendingUserMessage = userMessage;
492
+ // state._pendingDisplayMessage = displayMessage;
493
+ // state.turnActive = true;
494
+ // state.turnResultReceived = false;
495
+ // sendConversationList();
496
+ // state.inputStream.enqueue({
497
+ // type: 'user',
498
+ // message: { role: 'user', content: '/compact' }
499
+ // });
500
+ // return;
501
+ // }
475
502
 
476
503
  state.turnActive = true;
477
504
  state.turnResultReceived = false; // 重置 per-turn 去重标志
package/crew/routing.js CHANGED
@@ -256,38 +256,40 @@ export async function dispatchToRole(session, roleName, content, fromSource, tas
256
256
  timestamp: Date.now()
257
257
  });
258
258
 
259
+ // DISABLED (2026-03): Opus 4.6 has 200k context. Claude Code handles its own compaction.
260
+ // Keeping code for reference; re-enable if we ever need custom crew pre-send compact.
259
261
  // ★ Pre-send compact check: estimate total tokens and clear+rebuild if needed
260
- const autoCompactThreshold = ctx.CONFIG?.autoCompactThreshold || 100000;
261
- const lastInputTokens = roleState.lastInputTokens || 0;
262
- const estimatedNewTokens = Math.ceil((typeof content === 'string' ? content.length : 0) / 3);
263
- const estimatedTotal = lastInputTokens + estimatedNewTokens;
264
-
265
- if (lastInputTokens > 0 && estimatedTotal > autoCompactThreshold) {
266
- console.log(`[Crew] Pre-send compact for ${roleName}: estimated ${estimatedTotal} tokens (last: ${lastInputTokens} + new: ~${estimatedNewTokens}) exceeds threshold ${autoCompactThreshold}`);
267
-
268
- // Save work summary before clearing (use lastTurnText since accumulatedText is cleared after result)
269
- await saveRoleWorkSummary(session, roleName, roleState.lastTurnText || roleState.accumulatedText || '').catch(e =>
270
- console.warn(`[Crew] Failed to save work summary for ${roleName}:`, e.message));
271
-
272
- // Clear role session and rebuild
273
- await clearRoleSessionId(session.sharedDir, roleName);
274
- roleState.claudeSessionId = null;
275
-
276
- if (roleState.abortController) roleState.abortController.abort();
277
- roleState.query = null;
278
- roleState.inputStream = null;
279
-
280
- sendCrewMessage({
281
- type: 'crew_role_cleared',
282
- sessionId: session.id,
283
- role: roleName,
284
- contextPercentage: Math.round((lastInputTokens / (ctx.CONFIG?.maxContextTokens || 128000)) * 100),
285
- reason: 'pre_send_compact'
286
- });
287
-
288
- // Recreate the query (fresh Claude process)
289
- roleState = await createRoleQuery(session, roleName);
290
- }
262
+ // const autoCompactThreshold = ctx.CONFIG?.autoCompactThreshold || 100000;
263
+ // const lastInputTokens = roleState.lastInputTokens || 0;
264
+ // const estimatedNewTokens = Math.ceil((typeof content === 'string' ? content.length : 0) / 3);
265
+ // const estimatedTotal = lastInputTokens + estimatedNewTokens;
266
+ //
267
+ // if (lastInputTokens > 0 && estimatedTotal > autoCompactThreshold) {
268
+ // console.log(`[Crew] Pre-send compact for ${roleName}: estimated ${estimatedTotal} tokens (last: ${lastInputTokens} + new: ~${estimatedNewTokens}) exceeds threshold ${autoCompactThreshold}`);
269
+ //
270
+ // // Save work summary before clearing (use lastTurnText since accumulatedText is cleared after result)
271
+ // await saveRoleWorkSummary(session, roleName, roleState.lastTurnText || roleState.accumulatedText || '').catch(e =>
272
+ // console.warn(`[Crew] Failed to save work summary for ${roleName}:`, e.message));
273
+ //
274
+ // // Clear role session and rebuild
275
+ // await clearRoleSessionId(session.sharedDir, roleName);
276
+ // roleState.claudeSessionId = null;
277
+ //
278
+ // if (roleState.abortController) roleState.abortController.abort();
279
+ // roleState.query = null;
280
+ // roleState.inputStream = null;
281
+ //
282
+ // sendCrewMessage({
283
+ // type: 'crew_role_cleared',
284
+ // sessionId: session.id,
285
+ // role: roleName,
286
+ // contextPercentage: Math.round((lastInputTokens / (ctx.CONFIG?.maxContextTokens || 128000)) * 100),
287
+ // reason: 'pre_send_compact'
288
+ // });
289
+ //
290
+ // // Recreate the query (fresh Claude process)
291
+ // roleState = await createRoleQuery(session, roleName);
292
+ // }
291
293
 
292
294
  // P1-4: 守卫 stream.enqueue — stream 可能已被 abort 关闭
293
295
  roleState.lastDispatchContent = content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.161",
3
+ "version": "0.1.163",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",