agentgui 1.0.648 → 1.0.650

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.
@@ -203,6 +203,10 @@ class AgentRunner {
203
203
 
204
204
  proc.on('close', (code) => {
205
205
  clearTimeout(timeoutHandle);
206
+ // Close stdin when process exits - it was kept open for steering during execution
207
+ if (proc.stdin && !proc.stdin.destroyed) {
208
+ try { proc.stdin.end(); } catch (e) {}
209
+ }
206
210
  if (timedOut) return;
207
211
 
208
212
  if (authError) {
@@ -609,9 +613,9 @@ registry.register({
609
613
  name: 'Claude Code',
610
614
  command: 'claude',
611
615
  protocol: 'direct',
612
- supportsStdin: true, // keep open for steering via JSON-RPC
613
- closeStdin: false, // must NOT close stdin to allow steering prompts
614
- useJsonRpcStdin: true, // support JSON-RPC steering input
616
+ supportsStdin: false, // prompt passed as positional arg, not stdin
617
+ closeStdin: false, // keep stdin open for JSON-RPC steering commands
618
+ useJsonRpcStdin: false,
615
619
  supportedFeatures: ['streaming', 'resume', 'system-prompt', 'permissions-skip', 'steering'],
616
620
  spawnEnv: { MAX_THINKING_TOKENS: '0' },
617
621
 
@@ -633,8 +637,7 @@ registry.register({
633
637
  if (model) flags.push('--model', model);
634
638
  if (resumeSessionId) flags.push('--resume', resumeSessionId);
635
639
  if (systemPrompt) flags.push('--append-system-prompt', systemPrompt);
636
- // Do NOT pass prompt as positional arg - send via stdin instead to allow steering
637
- // This allows stdin to stay open for receiving JSON-RPC steering commands
640
+ flags.push(prompt); // positional arg - stdin stays open separately for steering
638
641
 
639
642
  return flags;
640
643
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.648",
3
+ "version": "1.0.650",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -4643,89 +4643,41 @@ server.on('error', (err) => {
4643
4643
  }
4644
4644
  });
4645
4645
 
4646
+ // On startup: mark all active/pending sessions as interrupted (server was down, they didn't complete).
4647
+ // Then resumeInterruptedStreams will pick up recent ones for auto-resume.
4646
4648
  function recoverStaleSessions() {
4647
4649
  try {
4648
- const now = Date.now();
4649
- const RESUME_WINDOW_MS = 600000; // 10 minutes
4650
-
4651
- const resumable = new Set();
4652
- const resumableConvs = queries.getResumableConversations ? queries.getResumableConversations(RESUME_WINDOW_MS) : [];
4653
- for (const conv of resumableConvs) {
4654
- resumable.add(conv.id); // All agent types are resumable
4655
- }
4656
-
4657
- const staleSessions = queries.getActiveSessions ? queries.getActiveSessions() : [];
4658
- let markedCount = 0;
4650
+ const RESUME_WINDOW_MS = 600000;
4651
+ const cutoff = Date.now() - RESUME_WINDOW_MS;
4652
+ const staleSessions = queries.getActiveSessions();
4659
4653
  for (const session of staleSessions) {
4660
- if (activeExecutions.has(session.conversationId)) continue;
4661
- if (resumable.has(session.conversationId)) continue;
4662
4654
  queries.updateSession(session.id, {
4663
- status: 'error',
4655
+ status: session.started_at > cutoff ? 'interrupted' : 'error',
4664
4656
  error: 'Server restarted',
4665
- completed_at: now
4657
+ completed_at: Date.now()
4666
4658
  });
4667
- markedCount++;
4668
4659
  }
4669
- if (markedCount > 0) {
4670
- console.log(`[RECOVERY] Marked ${markedCount} stale session(s) as error`);
4671
- }
4672
-
4673
- const streamingConvs = queries.getStreamingConversations ? queries.getStreamingConversations() : [];
4674
- let clearedCount = 0;
4675
- for (const conv of streamingConvs) {
4676
- if (activeExecutions.has(conv.id)) continue;
4677
- if (resumable.has(conv.id)) continue;
4678
- queries.setIsStreaming(conv.id, false);
4679
- clearedCount++;
4680
- }
4681
- if (clearedCount > 0) {
4682
- console.log(`[RECOVERY] Cleared isStreaming flag on ${clearedCount} stale conversation(s)`);
4683
- }
4684
- if (resumable.size > 0) {
4685
- console.log(`[RECOVERY] Found ${resumable.size} resumable conversation(s)`);
4660
+ // Clear all isStreaming flags - nothing is running yet
4661
+ queries.clearAllStreamingFlags();
4662
+ if (staleSessions.length > 0) {
4663
+ console.log(`[RECOVERY] Marked ${staleSessions.length} stale session(s); cleared streaming flags`);
4686
4664
  }
4687
4665
  } catch (err) {
4688
- console.error('[RECOVERY] Stale session recovery error:', err.message);
4666
+ console.error('[RECOVERY] Error:', err.message);
4689
4667
  }
4690
4668
  }
4691
4669
 
4670
+ // Resume conversations with recently interrupted sessions (started within 10 min).
4692
4671
  async function resumeInterruptedStreams() {
4693
4672
  try {
4694
- const RESUME_WINDOW_MS = 600000; // Only resume sessions active within the last 10 minutes
4695
- const cutoff = Date.now() - RESUME_WINDOW_MS;
4696
-
4697
- // Get conversations marked as streaming in database (isStreaming=1)
4698
- // Fall back to getResumableConversations if isStreaming is not being used
4699
- let toResume = [];
4700
-
4701
- // Primary: Check database isStreaming flag for conversations still marked as active
4702
- // Exclude conversations whose last session completed or started more than 10 min ago
4703
- const streamingConvs = queries.getConversations().filter(c => {
4704
- if (c.isStreaming !== 1) return false;
4705
- const lastSession = queries.getLatestSession(c.id);
4706
- if (!lastSession) return false;
4707
- if (lastSession.status === 'complete') return false;
4708
- // Only resume if session started within the last 10 minutes
4709
- return lastSession.started_at > cutoff;
4710
- });
4711
-
4712
- if (streamingConvs.length > 0) {
4713
- toResume = streamingConvs;
4714
- } else {
4715
- // Fallback: Use session-based resumable conversations (already filtered by 10 min)
4716
- toResume = queries.getResumableConversations ? queries.getResumableConversations(RESUME_WINDOW_MS) : [];
4717
- }
4718
-
4673
+ const toResume = queries.getResumableConversations(600000);
4719
4674
  if (toResume.length === 0) return;
4720
-
4721
4675
  console.log(`[RESUME] Resuming ${toResume.length} interrupted conversation(s)`);
4722
-
4723
4676
  for (let i = 0; i < toResume.length; i++) {
4724
4677
  const conv = toResume[i];
4725
4678
  try {
4726
- const previousSessions = [...queries.getSessionsByStatus(conv.id, 'active'), ...queries.getSessionsByStatus(conv.id, 'pending')];
4727
- const previousSessionId = previousSessions.length > 0 ? previousSessions[0].id : null;
4728
- await resumeConversation(conv.id, previousSessionId, 'Server restarted, resuming');
4679
+ const lastSession = queries.getLatestSession(conv.id);
4680
+ await resumeConversation(conv.id, lastSession?.id || null, 'Server restarted');
4729
4681
  if (i < toResume.length - 1) await new Promise(r => setTimeout(r, 200));
4730
4682
  } catch (err) {
4731
4683
  console.error(`[RESUME] Failed to resume conv ${conv.id}: ${err.message}`);
@@ -4733,7 +4685,7 @@ async function resumeInterruptedStreams() {
4733
4685
  }
4734
4686
  }
4735
4687
  } catch (err) {
4736
- console.error('[RESUME] Error during stream resumption:', err.message);
4688
+ console.error('[RESUME] Error:', err.message);
4737
4689
  }
4738
4690
  }
4739
4691
 
@@ -1453,7 +1453,7 @@ class AgentGUIClient {
1453
1453
  this.enableControls();
1454
1454
  }
1455
1455
 
1456
- async handleAllConversationsDeleted(data) {
1456
+ handleAllConversationsDeleted(data) {
1457
1457
  window.ConversationState?.clear('all_deleted');
1458
1458
  this.state.currentConversation = null;
1459
1459
  this.state.conversations = [];
@@ -1462,10 +1462,6 @@ class AgentGUIClient {
1462
1462
  this.conversationListCache = { data: [], timestamp: 0, ttl: 30000 };
1463
1463
  this.draftPrompts.clear();
1464
1464
  window.dispatchEvent(new CustomEvent('conversation-deselected'));
1465
- if (window.conversationManager) {
1466
- this.state.currentConversation = null;
1467
- await window.conversationManager.loadConversations();
1468
- }
1469
1465
  const outputEl = document.getElementById('output');
1470
1466
  if (outputEl) outputEl.innerHTML = '';
1471
1467
  }