tycono 0.1.96-beta.27 → 0.1.96-beta.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.1.96-beta.27",
3
+ "version": "0.1.96-beta.29",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tui/api.ts CHANGED
@@ -180,6 +180,14 @@ export async function fetchActiveSessions(): Promise<ActiveSessionsResponse> {
180
180
  return fetchJson<ActiveSessionsResponse>('/api/active-sessions');
181
181
  }
182
182
 
183
+ export async function killSession(sessionId: string): Promise<{ ok: boolean }> {
184
+ return fetchJson<{ ok: boolean }>(`/api/active-sessions/${sessionId}`, { method: 'DELETE' });
185
+ }
186
+
187
+ export async function cleanupSessions(): Promise<{ cleaned: number; remaining: number }> {
188
+ return fetchJson<{ cleaned: number; remaining: number }>('/api/active-sessions/cleanup', { method: 'POST' });
189
+ }
190
+
183
191
  /* ─── Setup API calls ─── */
184
192
 
185
193
  export interface TeamTemplate {
package/src/tui/app.tsx CHANGED
@@ -326,6 +326,28 @@ export const App: React.FC = () => {
326
326
  addSystemLines(lines);
327
327
  break;
328
328
  }
329
+ case 'sessions': {
330
+ if (api.activeSessions.length === 0) {
331
+ addSystemMessage('No active sessions.', 'gray');
332
+ } else {
333
+ addSystemMessage(`Sessions (${api.activeSessions.length}):`, 'cyan');
334
+ for (const s of api.activeSessions) {
335
+ const alive = s.alive === false ? ' DEAD' : s.pid ? ` PID:${s.pid}` : '';
336
+ const wave = s.waveId ? ` wave=${String(s.waveId).replace('wave-', 'W')}` : '';
337
+ addSystemMessage(
338
+ ` ${s.sessionId.slice(0, 25).padEnd(26)} ${s.roleId.padEnd(12)} API:${s.ports.api} ${s.status}${alive}${wave}`,
339
+ s.alive === false ? 'red' : s.status === 'active' ? 'green' : 'gray'
340
+ );
341
+ }
342
+ addSystemMessage(' /kill <sessionId> to stop | /cleanup to remove dead', 'gray');
343
+ }
344
+ break;
345
+ }
346
+ case 'cleanup': {
347
+ addSystemMessage(result.message, 'yellow');
348
+ api.refresh();
349
+ break;
350
+ }
329
351
  case 'error':
330
352
  addSystemMessage(result.message, 'red');
331
353
  break;
@@ -337,6 +359,9 @@ export const App: React.FC = () => {
337
359
  addSystemMessage(' /focus <n> Switch to wave n', 'white');
338
360
  addSystemMessage(' /agents Agent tree + resources', 'white');
339
361
  addSystemMessage(' /ports Port allocations', 'white');
362
+ addSystemMessage(' /sessions All sessions (kill/cleanup)', 'white');
363
+ addSystemMessage(' /kill <id> Kill a session', 'white');
364
+ addSystemMessage(' /cleanup Remove dead sessions', 'white');
340
365
  addSystemMessage(' /status Show current status', 'white');
341
366
  addSystemMessage(' /assign <role> <task> Assign task to role', 'white');
342
367
  addSystemMessage(' /roles Org tree (Panel Mode)', 'white');
@@ -364,17 +389,16 @@ export const App: React.FC = () => {
364
389
  }
365
390
  }, [execute, addSystemMessage, addSystemLines, focusedWaveId, focusedWaveIndex, derivedWaveStatus, api.sessions.length, activeCount, waves, api.activeSessions, api.portSummary]);
366
391
 
367
- // Global key handler: Tab to toggle mode, Ctrl+C handling
392
+ // Global key handler: Tab to toggle mode, Ctrl+C always exits
368
393
  useInput((input, key) => {
369
- if (mode === 'command' && key.tab) {
370
- setMode('panel');
371
- return;
372
- }
373
- // Ctrl+C in command mode: exit
374
394
  if (key.ctrl && input === 'c') {
375
395
  exit();
396
+ return;
376
397
  }
377
- }, { isActive: mode === 'command' });
398
+ if (mode === 'command' && key.tab) {
399
+ setMode('panel');
400
+ }
401
+ });
378
402
 
379
403
  // Loading state
380
404
  if (view === 'loading') {
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { useCallback } from 'react';
19
- import { dispatchWave, sendDirective, fetchJson } from '../api';
19
+ import { dispatchWave, sendDirective, fetchJson, killSession, cleanupSessions } from '../api';
20
20
 
21
21
  export interface WaveInfo {
22
22
  waveId: string;
@@ -25,7 +25,7 @@ export interface WaveInfo {
25
25
  }
26
26
 
27
27
  export interface CommandResult {
28
- type: 'success' | 'error' | 'info' | 'wave_started' | 'directive_sent' | 'stopped' | 'quit' | 'help' | 'panel' | 'waves_list' | 'focus_changed' | 'agents' | 'ports';
28
+ type: 'success' | 'error' | 'info' | 'wave_started' | 'directive_sent' | 'stopped' | 'quit' | 'help' | 'panel' | 'waves_list' | 'focus_changed' | 'agents' | 'ports' | 'sessions' | 'cleanup';
29
29
  message: string;
30
30
  waveId?: string;
31
31
  }
@@ -99,6 +99,30 @@ export function useCommand(options: UseCommandOptions) {
99
99
  case 'ports':
100
100
  return { type: 'ports', message: '__ports__' };
101
101
 
102
+ case 'sessions':
103
+ return { type: 'sessions', message: '__sessions__' };
104
+
105
+ case 'kill': {
106
+ if (!args) {
107
+ return { type: 'error', message: 'Usage: /kill <sessionId>' };
108
+ }
109
+ try {
110
+ await killSession(args);
111
+ return { type: 'success', message: `Session ${args} killed` };
112
+ } catch (err) {
113
+ return { type: 'error', message: `Kill failed: ${err instanceof Error ? err.message : 'unknown'}` };
114
+ }
115
+ }
116
+
117
+ case 'cleanup': {
118
+ try {
119
+ const result = await cleanupSessions();
120
+ return { type: 'cleanup', message: `Cleaned ${result.cleaned} dead sessions. ${result.remaining} remaining.` };
121
+ } catch (err) {
122
+ return { type: 'error', message: `Cleanup failed: ${err instanceof Error ? err.message : 'unknown'}` };
123
+ }
124
+ }
125
+
102
126
  case 'status':
103
127
  return { type: 'info', message: '__status__' };
104
128