agentgui 1.0.552 → 1.0.553

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.
@@ -95,6 +95,10 @@ class AgentRunner {
95
95
  try { config.onPid(proc.pid); } catch (e) {}
96
96
  }
97
97
 
98
+ if (config.onProcess) {
99
+ try { config.onProcess(proc); } catch (e) {}
100
+ }
101
+
98
102
  let jsonBuffer = '';
99
103
  const outputs = [];
100
104
  let timedOut = false;
@@ -274,6 +278,10 @@ class AgentRunner {
274
278
  try { config.onPid(proc.pid); } catch (e) {}
275
279
  }
276
280
 
281
+ if (config.onProcess) {
282
+ try { config.onProcess(proc); } catch (e) {}
283
+ }
284
+
277
285
  const outputs = [];
278
286
  let timedOut = false;
279
287
  let sessionId = null;
@@ -5,7 +5,7 @@ function notFound(msg = 'Not found') { fail(404, msg); }
5
5
 
6
6
  export function register(router, deps) {
7
7
  const { queries, activeExecutions, messageQueues, rateLimitState,
8
- broadcastSync, processMessageWithStreaming } = deps;
8
+ broadcastSync, processMessageWithStreaming, activeProcessesByConvId } = deps;
9
9
 
10
10
  router.handle('conv.ls', () => {
11
11
  const conversations = queries.getConversationsList();
@@ -114,6 +114,28 @@ export function register(router, deps) {
114
114
  return { ok: true, injected: true, conversationId: p.id, messageId: message.id };
115
115
  });
116
116
 
117
+ router.handle('conv.steer', (p) => {
118
+ const conv = queries.getConversation(p.id);
119
+ if (!conv) notFound('Conversation not found');
120
+ if (!p.content) fail(400, 'Missing content');
121
+ const entry = activeExecutions.get(p.id);
122
+ if (!entry) fail(409, 'No active execution to steer');
123
+ const proc = activeProcessesByConvId.get(p.id);
124
+ if (!proc || !proc.stdin) fail(409, 'Process not available for steering');
125
+
126
+ const message = queries.createMessage(p.id, 'user', p.content);
127
+ queries.createEvent('message.created', { role: 'user', messageId: message.id }, p.id);
128
+ broadcastSync({ type: 'message_created', conversationId: p.id, message, timestamp: Date.now() });
129
+
130
+ try {
131
+ // Write directly to stdin for steering
132
+ proc.stdin.write(p.content + '\n');
133
+ return { ok: true, steered: true, conversationId: p.id, messageId: message.id };
134
+ } catch (err) {
135
+ fail(500, `Failed to steer: ${err.message}`);
136
+ }
137
+ });
138
+
117
139
  router.handle('msg.ls', (p) => {
118
140
  return queries.getPaginatedMessages(p.id, Math.min(p.limit || 50, 100), Math.max(p.offset || 0, 0));
119
141
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.552",
3
+ "version": "1.0.553",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -292,6 +292,7 @@ const activeScripts = new Map();
292
292
  const messageQueues = new Map();
293
293
  const rateLimitState = new Map();
294
294
  const activeProcessesByRunId = new Map();
295
+ const activeProcessesByConvId = new Map(); // Store process handles by conversationId for steering
295
296
  const acpQueries = queries;
296
297
  const STUCK_AGENT_THRESHOLD_MS = 600000;
297
298
  const NO_PID_GRACE_PERIOD_MS = 60000;
@@ -3806,18 +3807,24 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
3806
3807
  onPid: (pid) => {
3807
3808
  const entry = activeExecutions.get(conversationId);
3808
3809
  if (entry) entry.pid = pid;
3810
+ },
3811
+ onProcess: (proc) => {
3812
+ // Store process handle for steering
3813
+ activeProcessesByConvId.set(conversationId, proc);
3809
3814
  }
3810
3815
  };
3811
3816
 
3812
3817
  const { outputs, sessionId: claudeSessionId } = await runClaudeWithStreaming(content, cwd, agentId || 'claude-code', config);
3813
-
3818
+
3814
3819
  // Check if rate limit was already handled in stream detection
3815
3820
  if (rateLimitState.get(conversationId)?.isStreamDetected) {
3816
3821
  debugLog(`[rate-limit] Rate limit already handled in stream for conv ${conversationId}, skipping success handler`);
3822
+ activeProcessesByConvId.delete(conversationId);
3817
3823
  return;
3818
3824
  }
3819
-
3825
+
3820
3826
  activeExecutions.delete(conversationId);
3827
+ activeProcessesByConvId.delete(conversationId);
3821
3828
  batcher.drain();
3822
3829
  debugLog(`[stream] Claude returned ${outputs.length} outputs, sessionId=${claudeSessionId}`);
3823
3830
 
@@ -4134,7 +4141,7 @@ const wsRouter = new WsRouter();
4134
4141
 
4135
4142
  registerConvHandlers(wsRouter, {
4136
4143
  queries, activeExecutions, messageQueues, rateLimitState,
4137
- broadcastSync, processMessageWithStreaming
4144
+ broadcastSync, processMessageWithStreaming, activeProcessesByConvId
4138
4145
  });
4139
4146
 
4140
4147
  registerSessionHandlers(wsRouter, {
package/static/index.html CHANGED
@@ -948,6 +948,26 @@
948
948
  .inject-btn:disabled { opacity: 0.4; cursor: not-allowed; }
949
949
  .inject-btn.visible { display: flex; }
950
950
 
951
+ .steer-btn {
952
+ display: none;
953
+ align-items: center;
954
+ justify-content: center;
955
+ width: 40px;
956
+ height: 40px;
957
+ background-color: #06b6d4;
958
+ color: white;
959
+ border: none;
960
+ border-radius: 0.5rem;
961
+ cursor: pointer;
962
+ flex-shrink: 0;
963
+ transition: background-color 0.15s;
964
+ margin-right: 0.25rem;
965
+ }
966
+
967
+ .steer-btn:hover:not(:disabled) { background-color: #0891b2; }
968
+ .steer-btn:disabled { opacity: 0.4; cursor: not-allowed; }
969
+ .steer-btn.visible { display: flex; }
970
+
951
971
  /* ===== OVERLAY for mobile sidebar ===== */
952
972
  .sidebar-overlay {
953
973
  display: none;
@@ -3216,6 +3236,11 @@
3216
3236
  <path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/>
3217
3237
  </svg>
3218
3238
  </button>
3239
+ <button class="steer-btn" id="steerBtn" title="Steer running agent with message" aria-label="Steer agent">
3240
+ <svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
3241
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"/>
3242
+ </svg>
3243
+ </button>
3219
3244
  <button class="stop-btn" id="stopBtn" title="Stop running agent (emergency)" aria-label="Stop agent">
3220
3245
  <svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
3221
3246
  <path d="M6 6h12v12H6z"/>
@@ -458,6 +458,7 @@ class AgentGUIClient {
458
458
 
459
459
  this.ui.stopButton = document.getElementById('stopBtn');
460
460
  this.ui.injectButton = document.getElementById('injectBtn');
461
+ this.ui.steerButton = document.getElementById('steerBtn');
461
462
 
462
463
  if (this.ui.stopButton) {
463
464
  this.ui.stopButton.addEventListener('click', async () => {
@@ -485,6 +486,28 @@ class AgentGUIClient {
485
486
  });
486
487
  }
487
488
 
489
+ if (this.ui.steerButton) {
490
+ this.ui.steerButton.addEventListener('click', async () => {
491
+ if (!this.state.currentConversation) return;
492
+ const message = this.ui.messageInput?.value || '';
493
+ if (!message.trim()) {
494
+ this.showError('Please enter a message to steer');
495
+ return;
496
+ }
497
+ try {
498
+ const data = await window.wsClient.rpc('conv.steer', { id: this.state.currentConversation.id, content: message });
499
+ console.log('Steer response:', data);
500
+ if (this.ui.messageInput) {
501
+ this.ui.messageInput.value = '';
502
+ this.ui.messageInput.style.height = 'auto';
503
+ }
504
+ } catch (err) {
505
+ console.error('Failed to steer:', err);
506
+ this.showError('Failed to steer: ' + err.message);
507
+ }
508
+ });
509
+ }
510
+
488
511
  if (this.ui.messageInput) {
489
512
  this.ui.messageInput.addEventListener('keydown', (e) => {
490
513
  if (e.key === 'Enter' && e.ctrlKey) {
@@ -708,9 +731,10 @@ class AgentGUIClient {
708
731
  return;
709
732
  }
710
733
 
711
- // Show stop and inject buttons when streaming starts for current conversation
734
+ // Show stop, steer, and inject buttons when streaming starts for current conversation
712
735
  if (this.ui.stopButton) this.ui.stopButton.classList.add('visible');
713
736
  if (this.ui.injectButton) this.ui.injectButton.classList.add('visible');
737
+ if (this.ui.steerButton) this.ui.steerButton.classList.add('visible');
714
738
  if (this.ui.sendButton) this.ui.sendButton.style.display = 'none';
715
739
 
716
740
  this.state.streamingConversations.set(data.conversationId, true);
@@ -937,9 +961,10 @@ class AgentGUIClient {
937
961
  console.error('Streaming error:', data);
938
962
  this._clearThinkingCountdown();
939
963
 
940
- // Hide stop and inject buttons on error
964
+ // Hide stop, steer, and inject buttons on error
941
965
  if (this.ui.stopButton) this.ui.stopButton.classList.remove('visible');
942
966
  if (this.ui.injectButton) this.ui.injectButton.classList.remove('visible');
967
+ if (this.ui.steerButton) this.ui.steerButton.classList.remove('visible');
943
968
  if (this.ui.sendButton) this.ui.sendButton.style.display = '';
944
969
 
945
970
  const conversationId = data.conversationId || this.state.currentSession?.conversationId;
@@ -988,9 +1013,10 @@ class AgentGUIClient {
988
1013
  console.log('Streaming completed:', data);
989
1014
  this._clearThinkingCountdown();
990
1015
 
991
- // Hide stop and inject buttons when streaming completes
1016
+ // Hide stop, steer, and inject buttons when streaming completes
992
1017
  if (this.ui.stopButton) this.ui.stopButton.classList.remove('visible');
993
1018
  if (this.ui.injectButton) this.ui.injectButton.classList.remove('visible');
1019
+ if (this.ui.steerButton) this.ui.steerButton.classList.remove('visible');
994
1020
  if (this.ui.sendButton) this.ui.sendButton.style.display = '';
995
1021
 
996
1022
  const conversationId = data.conversationId || this.state.currentSession?.conversationId;
@@ -2437,6 +2463,7 @@ class AgentGUIClient {
2437
2463
 
2438
2464
  if (this.ui.stopButton) this.ui.stopButton.classList.remove('visible');
2439
2465
  if (this.ui.injectButton) this.ui.injectButton.classList.remove('visible');
2466
+ if (this.ui.steerButton) this.ui.steerButton.classList.remove('visible');
2440
2467
  if (this.ui.sendButton) this.ui.sendButton.style.display = '';
2441
2468
 
2442
2469
  var prevId = this.state.currentConversation?.id;