@yeaft/webchat-agent 1.0.505 → 1.0.506

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/conversation.js CHANGED
@@ -423,10 +423,12 @@ export async function resumeConversation(msg) {
423
423
  console.log(`[Resume] claudeSessionId: ${claudeSessionId}`);
424
424
  console.log(`[Resume] workDir: ${effectiveWorkDir} (lazy start)`);
425
425
 
426
- // 清理旧条目:同 conversationId 或同 claudeSessionId 的条目(避免重复恢复同一个 session 累积)
426
+ // 清理旧条目:CLI session ID 只在当前实例的同 owner/provider 内去重。
427
427
  let priorProviderOptions = null;
428
428
  for (const [id, conv] of ctx.conversations) {
429
- if (id === conversationId || (claudeSessionId && conv.claudeSessionId === claudeSessionId)) {
429
+ if (id === conversationId || (claudeSessionId && conv.claudeSessionId === claudeSessionId &&
430
+ (conv.providerName || DEFAULT_PROVIDER) === provider &&
431
+ (conv.userId || null) === (userId || null))) {
430
432
  console.log(`[Resume] Cleaning up old conversation: ${id} (claudeSessionId: ${conv.claudeSessionId})`);
431
433
  if (conv.providerOptions && !priorProviderOptions) priorProviderOptions = conv.providerOptions;
432
434
  let cleanupDriver = null;
@@ -87,15 +87,6 @@ export async function handleAgentConversation(agentId, agent, msg) {
87
87
 
88
88
  case 'conversation_created':
89
89
  case 'conversation_resumed': {
90
- // 清理同 claudeSessionId 的旧条目(避免重复恢复同一个 session 累积)
91
- if (msg.type === 'conversation_resumed' && msg.claudeSessionId) {
92
- for (const [id, conv] of agent.conversations) {
93
- if (id !== msg.conversationId && conv.claudeSessionId === msg.claudeSessionId) {
94
- agent.conversations.delete(id);
95
- }
96
- }
97
- }
98
-
99
90
  // fix-session-dup: if this conv is currently held in another
100
91
  // agent's in-memory Map (e.g. user resumed it against a
101
92
  // different machine after the original agent went offline),
@@ -141,6 +132,17 @@ export async function handleAgentConversation(agentId, agent, msg) {
141
132
  const reportedProvider = (msg.provider && msg.provider !== 'claude-code') ? msg.provider : null;
142
133
  const resolvedProvider = reportedProvider || existingConvData?.provider || dbSessionData?.provider || null;
143
134
 
135
+ // CLI IDs are scoped to this Agent, provider and owner, not globally unique.
136
+ if (msg.type === 'conversation_resumed' && msg.claudeSessionId) {
137
+ for (const [id, conv] of agent.conversations) {
138
+ if (id !== msg.conversationId && conv.claudeSessionId === msg.claudeSessionId &&
139
+ (conv.provider || 'claude-code') === (resolvedProvider || 'claude-code') &&
140
+ (conv.userId || agent.ownerId || null) === trustedUserId) {
141
+ agent.conversations.delete(id);
142
+ }
143
+ }
144
+ }
145
+
144
146
  agent.conversations.set(msg.conversationId, {
145
147
  id: msg.conversationId,
146
148
  workDir: msg.workDir,
@@ -509,7 +509,6 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
509
509
  await sendToWebClient(client, { type: 'error', message: 'Agent is still syncing, please wait...' });
510
510
  return;
511
511
  }
512
- client.currentAgent = resumeAgentId;
513
512
  // fix-copilot-provider-persist: the web's auto-restore / recovery
514
513
  // resume paths (web/stores/helpers/session.js) send no provider, and
515
514
  // the agent's resume handler defaults an absent provider to
@@ -518,7 +517,34 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
518
517
  // 'claude-code'} and the persist path would CLOBBER the stored
519
518
  // 'copilot' binding — reintroducing the bug, permanently. Inject the
520
519
  // persisted provider so a provider-less resume keeps the real one.
521
- const resumeProvider = msg.provider || sessionDb.get(msg.conversationId)?.provider || undefined;
520
+ const persistedResume = msg.conversationId ? sessionDb.get(msg.conversationId) : null;
521
+ const liveResume = resumeAgent.conversations?.get(msg.conversationId);
522
+ const resumeProvider = msg.provider || persistedResume?.provider || liveResume?.provider || 'claude-code';
523
+ // A supplied Web ID may only be reused within its existing identity.
524
+ // Check both persisted and live rows before forwarding any destructive resume.
525
+ const bindings = [];
526
+ if (persistedResume) bindings.push({
527
+ agentId: persistedResume.agent_id,
528
+ userId: persistedResume.user_id || agents.get(persistedResume.agent_id)?.ownerId,
529
+ provider: persistedResume.provider,
530
+ });
531
+ if (msg.conversationId) {
532
+ for (const [agentId, agent] of agents) {
533
+ const conversation = agent.conversations?.get(msg.conversationId);
534
+ if (conversation) bindings.push({
535
+ agentId,
536
+ userId: conversation.userId || agent.ownerId,
537
+ provider: conversation.provider || persistedResume?.provider,
538
+ });
539
+ }
540
+ }
541
+ if (bindings.some(binding => binding.agentId !== resumeAgentId ||
542
+ (binding.provider || 'claude-code') !== resumeProvider ||
543
+ (!CONFIG.skipAuth && binding.userId !== client.userId))) {
544
+ await sendToWebClient(client, { type: 'error', message: 'Permission denied' });
545
+ return;
546
+ }
547
+ client.currentAgent = resumeAgentId;
522
548
  await forwardToAgent(resumeAgentId, {
523
549
  type: 'resume_conversation',
524
550
  conversationId: msg.conversationId || randomUUID(),
@@ -1 +1 @@
1
- {"version":"1.0.505"}
1
+ {"version":"1.0.506"}