tide-commander 1.43.0 → 1.44.0

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.
Files changed (45) hide show
  1. package/dist/assets/{BossLogsModal-CnIyv0O7.js → BossLogsModal-CTo4Bbm7.js} +1 -1
  2. package/dist/assets/BossSpawnModal-DOk9u7EW.js +1 -0
  3. package/dist/assets/{ControlsModal-DJ6kwJQg.js → ControlsModal-BzBzF0Cw.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-wLLPPw3N.js → DockerLogsModal-CrHd6OZ3.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-BZUBqqzE.js → EmbeddedEditor-h8IrAy4E.js} +1 -1
  6. package/dist/assets/{GmailOAuthSetup-DU-rYgPc.js → GmailOAuthSetup-io57q0N8.js} +1 -1
  7. package/dist/assets/{GoogleOAuthSetup-DUG794k5.js → GoogleOAuthSetup-YyNFVd9Y.js} +1 -1
  8. package/dist/assets/{IframeModal-BEaeYao1.js → IframeModal-DqmSDXCT.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-CrT3G7gD.js → IntegrationsPanel-DiW8UeG5.js} +2 -2
  10. package/dist/assets/{LogViewerModal-pmKvg2_3.js → LogViewerModal-CGW8yajP.js} +1 -1
  11. package/dist/assets/{MonitoringModal-BopLTYX0.js → MonitoringModal-BfsYrF1j.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-akdCsnNY.js → PM2LogsModal-BJytXxKY.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-BAe0BLNN.js → RestoreArchivedAreaModal-CtRiwwzT.js} +1 -1
  14. package/dist/assets/{SaveSnapshotModal-CCK5ncy-.js → SaveSnapshotModal-BHBdh1dK.js} +1 -1
  15. package/dist/assets/{Scene2DCanvas-CTrNCLiT.js → Scene2DCanvas-BYUuUYur.js} +1 -1
  16. package/dist/assets/{SceneManager-WQvF4y8-.js → SceneManager-u4hiSYdk.js} +1 -1
  17. package/dist/assets/{SkillsPanel-BpY-jIMP.js → SkillsPanel-D4IHFehE.js} +1 -1
  18. package/dist/assets/{SnapshotManager-DlJQAEC-.js → SnapshotManager-Cys1dllC.js} +1 -1
  19. package/dist/assets/{SpawnModal-Ci_uvH1F.js → SpawnModal-DUK7X8ql.js} +1 -1
  20. package/dist/assets/{SubordinateAssignmentModal-kFDllrhg.js → SubordinateAssignmentModal-DeH34tA3.js} +1 -1
  21. package/dist/assets/{SupervisorPanel-CPyoCa6i.js → SupervisorPanel-D0c7Kkzt.js} +1 -1
  22. package/dist/assets/{TriggerManagerPanel-B7qEVCh4.js → TriggerManagerPanel-RsbYDmIC.js} +1 -1
  23. package/dist/assets/{WorkflowEditorPanel-D8FwpAyj.js → WorkflowEditorPanel-lpYmT8iN.js} +1 -1
  24. package/dist/assets/{index-M90uUgxL.js → index-BOY5J4U4.js} +1 -1
  25. package/dist/assets/{index-C5IJ99Dq.js → index-BQdQ0iv_.js} +1 -1
  26. package/dist/assets/{index-BWCjlz0C.js → index-BexCA27h.js} +3 -3
  27. package/dist/assets/{index-OOJWJ_4Z.js → index-BqPgrat9.js} +1 -1
  28. package/dist/assets/{index-CRD8TN18.js → index-C6GdsxBK.js} +1 -1
  29. package/dist/assets/{index-DkhEv4qN.js → index-CpwhIgf8.js} +1 -1
  30. package/dist/assets/{index-Bu3B9Eke.js → index-X9qZZBXG.js} +2 -2
  31. package/dist/assets/{index-CjH90tsj.js → index-t5WzFf-W.js} +1 -1
  32. package/dist/assets/{main-DB67UbmQ.js → main-CrRMpn-n.js} +56 -56
  33. package/dist/assets/main-DjGrMNoB.css +1 -0
  34. package/dist/assets/{web-DRj84zWD.js → web-D94DaG2N.js} +1 -1
  35. package/dist/assets/{web-D4vvRMYa.js → web-XswRIovf.js} +1 -1
  36. package/dist/index.html +2 -2
  37. package/dist/src/packages/server/data/index.js +40 -0
  38. package/dist/src/packages/server/index.js +2 -0
  39. package/dist/src/packages/server/routes/agents.js +33 -0
  40. package/dist/src/packages/server/services/agent-service.js +42 -2
  41. package/dist/src/packages/server/websocket/handler.js +3 -1
  42. package/dist/src/packages/server/websocket/handlers/agent-handler.js +45 -0
  43. package/package.json +1 -1
  44. package/dist/assets/BossSpawnModal-C5JHnXoV.js +0 -1
  45. package/dist/assets/main-Chjl2UXl.css +0 -1
@@ -1 +1 @@
1
- import{bx as a}from"./main-DB67UbmQ.js";import{ImpactStyle as i,NotificationType as r}from"./index-Bu3B9Eke.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class u extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{u as HapticsWeb};
1
+ import{bx as a}from"./main-CrRMpn-n.js";import{ImpactStyle as i,NotificationType as r}from"./index-X9qZZBXG.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class u extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{u as HapticsWeb};
@@ -1 +1 @@
1
- import{bx as s}from"./main-DB67UbmQ.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class f extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{f as LocalNotificationsWeb};
1
+ import{bx as s}from"./main-CrRMpn-n.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class f extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{f as LocalNotificationsWeb};
package/dist/index.html CHANGED
@@ -22,11 +22,11 @@
22
22
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
23
23
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
24
24
  <title>Tide Commander</title>
25
- <script type="module" crossorigin src="/assets/main-DB67UbmQ.js"></script>
25
+ <script type="module" crossorigin src="/assets/main-CrRMpn-n.js"></script>
26
26
  <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
28
28
  <link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
29
- <link rel="stylesheet" crossorigin href="/assets/main-Chjl2UXl.css">
29
+ <link rel="stylesheet" crossorigin href="/assets/main-DjGrMNoB.css">
30
30
  </head>
31
31
  <body>
32
32
  <div id="app"></div>
@@ -27,9 +27,11 @@ const SKILLS_FILE = path.join(DATA_DIR, 'skills.json');
27
27
  const CUSTOM_CLASSES_FILE = path.join(DATA_DIR, 'custom-agent-classes.json');
28
28
  const RUNNING_PROCESSES_FILE = path.join(DATA_DIR, 'running-processes.json');
29
29
  const SECRETS_FILE = path.join(DATA_DIR, 'secrets.json');
30
+ const SESSION_HISTORY_FILE = path.join(DATA_DIR, 'session-history.json');
30
31
  const AREA_LOGOS_DIR = path.join(DATA_DIR, 'area-logos');
31
32
  // Maximum history entries per agent
32
33
  const MAX_HISTORY_PER_AGENT = 50;
34
+ const MAX_SESSION_HISTORY_PER_AGENT = 100;
33
35
  const MAX_DELEGATION_HISTORY_PER_BOSS = 100;
34
36
  // Ensure data directory exists
35
37
  function ensureDataDir() {
@@ -780,3 +782,41 @@ export function deleteQueryHistory(buildingId) {
780
782
  log.error(` Failed to delete query history for building ${buildingId}:`, err);
781
783
  }
782
784
  }
785
+ export function loadSessionHistory() {
786
+ ensureDataDir();
787
+ const data = safeReadJsonSync(SESSION_HISTORY_FILE, 'Session history');
788
+ if (data?.histories) {
789
+ log.log(` Loaded session history for ${Object.keys(data.histories).length} agents`);
790
+ return new Map(Object.entries(data.histories));
791
+ }
792
+ return new Map();
793
+ }
794
+ export function saveSessionHistory(histories) {
795
+ ensureDataDir();
796
+ try {
797
+ atomicWriteJsonSync(SESSION_HISTORY_FILE, {
798
+ histories: Object.fromEntries(histories),
799
+ savedAt: Date.now(),
800
+ version: '1.0.0',
801
+ });
802
+ }
803
+ catch (err) {
804
+ log.error(' Failed to save session history:', err);
805
+ }
806
+ }
807
+ export function addSessionHistoryEntry(histories, agentId, entry) {
808
+ let agentHistory = histories.get(agentId);
809
+ if (!agentHistory) {
810
+ agentHistory = [];
811
+ histories.set(agentId, agentHistory);
812
+ }
813
+ // Add to beginning (most recent first)
814
+ agentHistory.unshift(entry);
815
+ // Trim to max entries
816
+ if (agentHistory.length > MAX_SESSION_HISTORY_PER_AGENT) {
817
+ agentHistory.pop();
818
+ }
819
+ }
820
+ export function getSessionHistoryForAgent(histories, agentId) {
821
+ return histories.get(agentId) || [];
822
+ }
@@ -59,6 +59,7 @@ async function main() {
59
59
  eventRetentionService.init();
60
60
  // Initialize services
61
61
  agentService.initAgents();
62
+ agentService.initSessionHistory();
62
63
  runtimeService.init();
63
64
  supervisorService.init();
64
65
  bossService.init();
@@ -209,6 +210,7 @@ async function main() {
209
210
  buildingService.stopTerminalStatusPolling();
210
211
  buildingService.cleanupAllTerminals();
211
212
  await runtimeService.shutdown();
213
+ agentService.shutdownSessionHistory();
212
214
  agentService.flushPersistAgents();
213
215
  closeEventDb();
214
216
  wss.clients.forEach((client) => client.terminate());
@@ -9,6 +9,7 @@ import * as path from 'path';
9
9
  import * as os from 'os';
10
10
  import { agentService, runtimeService, bossMessageService } from '../services/index.js';
11
11
  import { getClaudeProjectDir, loadAreas, saveAreas } from '../data/index.js';
12
+ import { loadSession } from '../claude/session-loader.js';
12
13
  import { getAllCustomClasses } from '../services/custom-class-service.js';
13
14
  // Session listing is done inline for performance
14
15
  import { createLogger } from '../utils/logger.js';
@@ -586,6 +587,38 @@ router.get('/:id/history', async (req, res) => {
586
587
  res.status(500).json({ error: err.message });
587
588
  }
588
589
  });
590
+ // GET /api/agents/:id/session-history - Get archived session history for an agent
591
+ router.get('/:id/session-history', (_req, res) => {
592
+ try {
593
+ const entries = agentService.getAgentSessionHistory(_req.params.id);
594
+ res.json({ entries });
595
+ }
596
+ catch (err) {
597
+ log.error(' Failed to load session history:', err);
598
+ res.status(500).json({ error: err.message });
599
+ }
600
+ });
601
+ // GET /api/agents/:id/session-preview/:sessionId - Preview messages from an archived session
602
+ router.get('/:id/session-preview/:sessionId', async (req, res) => {
603
+ try {
604
+ const agent = agentService.getAgent(req.params.id);
605
+ if (!agent) {
606
+ res.status(404).json({ error: 'Agent not found' });
607
+ return;
608
+ }
609
+ const limit = parseInt(req.query.limit) || 30;
610
+ const history = await loadSession(agent.cwd, req.params.sessionId, limit, 0);
611
+ if (!history) {
612
+ res.status(404).json({ error: 'Session not found' });
613
+ return;
614
+ }
615
+ res.json({ messages: history.messages, totalCount: history.totalCount });
616
+ }
617
+ catch (err) {
618
+ log.error(' Failed to load session preview:', err);
619
+ res.status(500).json({ error: err.message });
620
+ }
621
+ });
589
622
  // GET /api/agents/:id/search - Search conversation history
590
623
  router.get('/:id/search', async (req, res) => {
591
624
  try {
@@ -5,8 +5,8 @@
5
5
  import * as fs from 'fs';
6
6
  import * as os from 'os';
7
7
  import * as path from 'path';
8
- import { loadAgents, saveAgents, saveAgentsAsync, getDataDir, loadAreas, saveAreas } from '../data/index.js';
9
- import { listSessions, getSessionSummary, loadSession, loadToolHistory, searchSession, } from '../claude/session-loader.js';
8
+ import { loadAgents, saveAgents, saveAgentsAsync, getDataDir, loadAreas, saveAreas, loadSessionHistory, saveSessionHistory, addSessionHistoryEntry, getSessionHistoryForAgent } from '../data/index.js';
9
+ import { listSessions, getSessionSummary, getProjectDir, loadSession, loadToolHistory, searchSession, } from '../claude/session-loader.js';
10
10
  import { loadSubagentHistory } from '../claude/subagent-history-loader.js';
11
11
  import { logger, generateId } from '../utils/index.js';
12
12
  const log = logger.agent;
@@ -650,5 +650,45 @@ Please acknowledge this update and continue with your work.
650
650
  `;
651
651
  }
652
652
  // ============================================================================
653
+ // Session History
654
+ // ============================================================================
655
+ let sessionHistories = new Map();
656
+ export function initSessionHistory() {
657
+ sessionHistories = loadSessionHistory();
658
+ log.log?.(` Loaded session history for ${sessionHistories.size} agents`);
659
+ }
660
+ export function shutdownSessionHistory() {
661
+ saveSessionHistory(sessionHistories);
662
+ }
663
+ /**
664
+ * Archive the current session for an agent before it gets cleared.
665
+ * Call this before setting sessionId to undefined.
666
+ */
667
+ export function archiveCurrentSession(agentId) {
668
+ const agent = agents.get(agentId);
669
+ if (!agent || !agent.sessionId)
670
+ return;
671
+ const entry = {
672
+ sessionId: agent.sessionId,
673
+ summary: agent.taskLabel || agent.lastAssignedTask || agent.currentTask || 'No description',
674
+ startedAt: agent.createdAt,
675
+ endedAt: Date.now(),
676
+ };
677
+ addSessionHistoryEntry(sessionHistories, agentId, entry);
678
+ saveSessionHistory(sessionHistories);
679
+ log.log?.(`Archived session ${agent.sessionId} for agent ${agent.name}`);
680
+ }
681
+ export function getAgentSessionHistory(agentId) {
682
+ const agent = agents.get(agentId);
683
+ const entries = getSessionHistoryForAgent(sessionHistories, agentId);
684
+ if (!agent)
685
+ return entries;
686
+ const projectDir = getProjectDir(agent.cwd);
687
+ return entries.map((entry) => ({
688
+ ...entry,
689
+ fileExists: fs.existsSync(path.join(projectDir, `${entry.sessionId}.jsonl`)),
690
+ }));
691
+ }
692
+ // ============================================================================
653
693
  // Utilities
654
694
  // ============================================================================
@@ -9,7 +9,7 @@ import { logger } from '../utils/index.js';
9
9
  import { setNotificationBroadcast, setExecBroadcast, setFocusAgentBroadcast, setAgentsBroadcast, setTriggerBroadcast } from '../routes/index.js';
10
10
  import { validateWebSocketAuth, isAuthEnabled } from '../auth/index.js';
11
11
  import { incrementWsSent, incrementWsReceived, setWsClientsCount } from '../routes/perf.js';
12
- import { handleSpawnAgent, handleKillAgent, handleStopAgent, handleClearContext, handleCollapseContext, handleRequestContextStats, handleMoveAgent, handleRemoveAgent, handleRenameAgent, handleUpdateAgentProperties, handleCreateDirectory, handleReattachAgent, } from './handlers/agent-handler.js';
12
+ import { handleSpawnAgent, handleKillAgent, handleStopAgent, handleClearContext, handleRestoreSession, handleRequestSessionHistory, handleCollapseContext, handleRequestContextStats, handleMoveAgent, handleRemoveAgent, handleRenameAgent, handleUpdateAgentProperties, handleCreateDirectory, handleReattachAgent, } from './handlers/agent-handler.js';
13
13
  import { handleCreateSkill, handleUpdateSkill, handleDeleteSkill, handleAssignSkill, handleUnassignSkill, handleRequestAgentSkills, } from './handlers/skill-handler.js';
14
14
  import { handleSpawnBossAgent, handleAssignSubordinates, handleRemoveSubordinate, handleSendBossCommand, handleRequestDelegationHistory, } from './handlers/boss-handler.js';
15
15
  import { handleCreateCustomAgentClass, handleUpdateCustomAgentClass, handleDeleteCustomAgentClass, } from './handlers/custom-class-handler.js';
@@ -138,6 +138,8 @@ const messageHandlers = {
138
138
  kill_agent: handleKillAgent,
139
139
  stop_agent: handleStopAgent,
140
140
  clear_context: handleClearContext,
141
+ restore_session: handleRestoreSession,
142
+ request_session_history: handleRequestSessionHistory,
141
143
  collapse_context: handleCollapseContext,
142
144
  create_directory: handleCreateDirectory,
143
145
  remove_agent: handleRemoveAgent,
@@ -151,6 +151,8 @@ export async function handleStopAgent(ctx, payload) {
151
151
  export async function handleClearContext(ctx, payload) {
152
152
  const agent = agentService.getAgent(payload.agentId);
153
153
  log.log(`Agent ${agent?.name || payload.agentId}: User requested context clear`);
154
+ // Archive the current session before clearing
155
+ agentService.archiveCurrentSession(payload.agentId);
154
156
  await runtimeService.stopAgent(payload.agentId);
155
157
  agentService.updateAgent(payload.agentId, {
156
158
  status: 'idle',
@@ -170,6 +172,47 @@ export async function handleClearContext(ctx, payload) {
170
172
  ctx.sendActivity(payload.agentId, 'Context cleared - new session on next command');
171
173
  log.log(`Agent ${agent?.name || payload.agentId}: Context cleared, session reset`);
172
174
  }
175
+ /**
176
+ * Handle restore_session message - restores a previous session for an agent
177
+ */
178
+ export async function handleRestoreSession(ctx, payload) {
179
+ const agent = agentService.getAgent(payload.agentId);
180
+ if (!agent) {
181
+ ctx.sendError(`Agent not found: ${payload.agentId}`);
182
+ return;
183
+ }
184
+ log.log(`Agent ${agent.name}: Restoring session ${payload.sessionId}`);
185
+ // Archive the current session first (if any)
186
+ agentService.archiveCurrentSession(payload.agentId);
187
+ // Stop any running process
188
+ await runtimeService.stopAgent(payload.agentId);
189
+ // Set the restored session ID
190
+ agentService.updateAgent(payload.agentId, {
191
+ status: 'idle',
192
+ currentTask: undefined,
193
+ taskLabel: undefined,
194
+ currentTool: undefined,
195
+ sessionId: payload.sessionId,
196
+ tokensUsed: 0,
197
+ contextUsed: 0,
198
+ contextStats: undefined,
199
+ });
200
+ ctx.sendActivity(payload.agentId, `Session restored - will resume on next command`);
201
+ log.log(`Agent ${agent.name}: Session restored to ${payload.sessionId}`);
202
+ }
203
+ /**
204
+ * Handle request_session_history - returns archived sessions for an agent
205
+ */
206
+ export function handleRequestSessionHistory(ctx, payload) {
207
+ const entries = agentService.getAgentSessionHistory(payload.agentId);
208
+ ctx.sendToClient({
209
+ type: 'session_history',
210
+ payload: {
211
+ agentId: payload.agentId,
212
+ entries,
213
+ },
214
+ });
215
+ }
173
216
  /**
174
217
  * Handle collapse_context message - sends /compact command to collapse context
175
218
  */
@@ -806,6 +849,8 @@ export async function handleUpdateAgentProperties(ctx, payload) {
806
849
  // Claude sessions are tied to the directory they were created in
807
850
  if (cwdChanged && sessionId) {
808
851
  log.log(`Agent ${agent.name}: Working directory changed to ${updates.cwd}, clearing session (cwd change requires new session)`);
852
+ // Archive the current session before clearing
853
+ agentService.archiveCurrentSession(agentId);
809
854
  try {
810
855
  // Stop the current Claude process
811
856
  await runtimeService.stopAgent(agentId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.43.0",
3
+ "version": "1.44.0",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- import{u as $e,f as Ae,M as Le,L as Ee,r as t,P as _e,S as se,w as De,U as O,A as ae,a2 as P,a3 as j,j as s,W as Ie,Y as Be,Z as T,_ as A,$ as L,a1 as Re,s as Oe}from"./main-DB67UbmQ.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";function ne(m){const h=j.filter(S=>!m.has(`Boss ${S}`));return h.length===0?`Boss ${j[Math.floor(Math.random()*j.length)]}-${Date.now()%1e3}`:`Boss ${h[Math.floor(Math.random()*h.length)]}`}function Fe({isOpen:m,onClose:h,onSpawnStart:S,onSpawnEnd:E,spawnPosition:le}){const{t:n}=$e(["terminal","common"]),{agents:g}=Ae(),o=Le(),C=Ee(),[r,p]=t.useState(""),[k,te]=t.useState(()=>_e(se.LAST_CWD)),[c,_]=t.useState("boss"),[U,q]=t.useState(!1),[ie,M]=t.useState(!1),[F,ce]=t.useState(!0),[W,oe]=t.useState("bypass"),[d,D]=t.useState("claude"),[b,N]=t.useState({fullAuto:!0,sandbox:"workspace-write",approvalMode:"on-request",search:!1}),[G,re]=t.useState("haiku"),[K,de]=t.useState("gpt-5.3-codex"),[v,y]=t.useState(new Set),[z,I]=t.useState(new Set),[u,pe]=t.useState(""),[f,ue]=t.useState(""),[H,me]=t.useState(""),$=t.useRef(null),B=t.useRef(!1),J=t.useRef(!1),x=t.useMemo(()=>C.filter(e=>e.enabled),[C]),Y=t.useMemo(()=>{if(!f.trim())return x;const e=f.toLowerCase();return x.filter(a=>a.name.toLowerCase().includes(e)||a.description.toLowerCase().includes(e)||a.slug.toLowerCase().includes(e))},[x,f]),he=t.useMemo(()=>{var a;const e=o.find(l=>l.id===c);return(a=e==null?void 0:e.defaultSkillIds)!=null&&a.length?C.filter(l=>e.defaultSkillIds.includes(l.id)):[]},[o,c,C]),we=["full-notifications","streaming-exec","task-label","report-task-to-boss"];t.useEffect(()=>{if(m&&!J.current&&x.length>0){const a=x.filter(l=>we.includes(l.slug)).map(l=>l.id);a.length>0&&I(new Set(a))}J.current=m},[m,x]);const xe=t.useCallback(e=>{I(a=>{const l=new Set(a);return l.has(e)?l.delete(e):l.add(e),l})},[]),i=t.useMemo(()=>o.find(e=>e.id===c),[o,c]),be=t.useMemo(()=>{if(i!=null&&i.model)return i.model},[i]),fe=t.useMemo(()=>{if(i!=null&&i.customModelPath)return De(`/api/custom-models/${i.id}`)},[i]),je=i==null?void 0:i.modelScale,Ne=t.useMemo(()=>i?"scout":c==="boss"?"architect":c,[c,i]),V=Array.from(g.values()).filter(e=>!e.isBoss&&e.class!=="boss"&&!e.bossId),X=t.useMemo(()=>{if(!u.trim())return o;const e=u.toLowerCase();return o.filter(a=>a.name.toLowerCase().includes(e)||a.description.toLowerCase().includes(e)||a.id.toLowerCase().includes(e))},[o,u]),Z=t.useMemo(()=>{if(!u.trim())return O;const e=u.toLowerCase();return O.filter(a=>{const l=ae[a.id];return l?a.name.toLowerCase().includes(e)||a.id.toLowerCase().includes(e)||l.description.toLowerCase().includes(e):!1})},[u]),Q=t.useMemo(()=>{if(!u.trim())return!0;const e=u.toLowerCase(),a=P.boss;return"boss".includes(e)||a.description.toLowerCase().includes(e)},[u]);t.useEffect(()=>{if(m&&!B.current){B.current=!0;const e=new Set(Array.from(g.values()).map(l=>l.name)),a=o.find(l=>l.id===c);if(a){const l=j.filter(w=>!e.has(`${a.name} ${w}`));if(l.length===0){const w=j[Math.floor(Math.random()*j.length)];p(`${a.name} ${w}-${Date.now()%1e3}`)}else p(`${a.name} ${l[Math.floor(Math.random()*l.length)]}`)}else p(ne(e));y(new Set),$.current&&($.current.focus(),$.current.select())}else m||(B.current=!1)},[m,g]),t.useEffect(()=>{if(!m)return;const e=o.find(a=>a.id===c);if(e)if(r.startsWith("Boss ")){const a=r.substring(5);p(`${e.name} ${a}`)}else{const a=o.find(l=>r.startsWith(l.name+" "));if(a){const l=r.substring(a.name.length+1);p(`${e.name} ${l}`)}else p(`${e.name} ${r}`)}else{const a=o.find(l=>r.startsWith(l.name+" "));if(a){const l=r.substring(a.name.length+1);p(`Boss ${l}`)}else r.startsWith("Boss ")||p(`Boss ${r}`)}},[c]);const ve=()=>{if(M(!1),!k.trim()){M(!0);return}if(!r.trim()){const l=new Set(Array.from(g.values()).map(w=>w.name));p(ne(l));return}Re(se.LAST_CWD,k),S();const e=H.trim()||void 0,a=Array.from(z);Oe.spawnBossAgent(r.trim(),c,k.trim(),le||void 0,Array.from(v),d==="claude"?F:!1,W,d,d==="codex"?b:void 0,d==="codex"?K:void 0,d==="claude"?G:void 0,e,a),p(""),y(new Set),I(new Set),E(),h()},Se=()=>{q(!1),p(""),y(new Set),E(),h()},ge=()=>{q(!1),M(!0),E()};t.useEffect(()=>(window.__bossSpawnModalSuccess=Se,window.__bossSpawnModalError=ge,()=>{delete window.__bossSpawnModalSuccess,delete window.__bossSpawnModalError}),[r]);const Ce=e=>{e.target===e.currentTarget&&h()},ke=e=>{e.key==="Escape"&&h()},Me=e=>{const a=new Set(v);a.has(e)?a.delete(e):a.add(e),y(a)};if(!m)return null;const R=P.boss;return s.jsx("div",{className:`modal-overlay ${m?"visible":""}`,onClick:Ce,onKeyDown:ke,children:s.jsxs("div",{className:"modal boss-spawn-modal",children:[s.jsxs("div",{className:"modal-header",children:[s.jsx("span",{className:"boss-header-icon",children:R.icon}),n("terminal:spawn.deployBossTitle")]}),s.jsxs("div",{className:"modal-body spawn-modal-body",children:[s.jsxs("div",{className:"spawn-top-section",children:[s.jsx("div",{className:"spawn-preview-compact",children:s.jsx(Ie,{agentClass:Ne,modelFile:be,customModelUrl:fe,modelScale:je,width:100,height:120})}),s.jsxs("div",{className:"spawn-class-section",children:[s.jsx("div",{className:"spawn-class-label",children:n("terminal:spawn.bossClass")}),o.length+O.length+1>6&&s.jsx("input",{type:"text",className:"spawn-input class-search-input",placeholder:n("terminal:spawn.filterClasses"),value:u,onChange:e=>pe(e.target.value)}),s.jsxs("div",{className:"class-selector-inline",children:[X.map(e=>s.jsxs("button",{className:`class-chip ${c===e.id?"selected":""}`,onClick:()=>_(e.id),title:e.description,children:[s.jsx("span",{className:"class-chip-icon",children:e.icon}),s.jsx("span",{className:"class-chip-name",children:e.name})]},e.id)),Q&&s.jsxs("button",{className:`class-chip ${c==="boss"?"selected":""}`,onClick:()=>_("boss"),title:R.description,children:[s.jsx("span",{className:"class-chip-icon",children:R.icon}),s.jsx("span",{className:"class-chip-name",children:n("terminal:spawn.bossClassName")})]}),Z.map(e=>{const a=ae[e.id];return a?s.jsxs("button",{className:`class-chip ${c===e.id?"selected":""}`,onClick:()=>_(e.id),title:a.description,children:[s.jsx("span",{className:"class-chip-icon",children:a.icon}),s.jsx("span",{className:"class-chip-name",children:e.name})]},e.id):null}),u&&X.length===0&&!Q&&Z.length===0&&s.jsx("div",{className:"class-search-empty",children:n("terminal:spawn.noClassesMatch",{query:u})})]})]})]}),s.jsxs("div",{className:"spawn-form-section",children:[s.jsxs("div",{className:"spawn-form-row",children:[s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.name")}),s.jsx("input",{ref:$,type:"text",className:"spawn-input",placeholder:n("terminal:spawn.bossNamePlaceholder"),value:r,onChange:e=>p(e.target.value)})]}),s.jsxs("div",{className:"spawn-field spawn-field-wide",children:[s.jsx("label",{className:"spawn-label",children:n("terminal:spawn.workingDir")}),s.jsx(Be,{value:k,onChange:e=>{te(e),M(!1)},placeholder:n("terminal:spawn.workingDirPlaceholder"),className:"spawn-input",hasError:ie,directoriesOnly:!0})]})]}),s.jsxs("div",{className:"spawn-form-row",children:[s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.runtime")}),s.jsxs("div",{className:"spawn-select-row",children:[s.jsxs("button",{className:`spawn-select-btn ${d==="claude"?"selected":""}`,onClick:()=>D("claude"),title:n("terminal:spawn.useClaudeCli"),children:[s.jsx("img",{src:"/assets/claude.ico",alt:"Claude",className:"spawn-provider-icon"}),s.jsx("span",{children:"Claude"})]}),s.jsxs("button",{className:`spawn-select-btn ${d==="codex"?"selected":""}`,onClick:()=>D("codex"),title:n("terminal:spawn.useCodexCli"),children:[s.jsx("img",{src:"/assets/codex.ico",alt:"Codex",className:"spawn-provider-icon"}),s.jsx("span",{children:"Codex"})]}),s.jsxs("button",{className:`spawn-select-btn spawn-select-btn--opencode ${d==="opencode"?"selected":""}`,onClick:()=>D("opencode"),title:"Use OpenCode CLI (multi-provider)",children:[s.jsx("span",{children:"🟢"}),s.jsx("span",{children:"OpenCode"})]})]})]}),s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.permissions")}),s.jsx("div",{className:"spawn-select-row",children:Object.keys(T).map(e=>s.jsxs("button",{className:`spawn-select-btn ${W===e?"selected":""}`,onClick:()=>oe(e),title:T[e].description,children:[s.jsx("span",{children:e==="bypass"?"⚡":"🔐"}),s.jsx("span",{children:T[e].label})]},e))})]})]}),s.jsxs("div",{className:"spawn-form-row",children:[s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.model")}),d==="claude"?s.jsx("div",{className:"spawn-select-row",children:Object.keys(A).map(e=>s.jsxs("button",{className:`spawn-select-btn ${G===e?"selected":""}`,onClick:()=>re(e),title:A[e].description,children:[s.jsx("span",{children:A[e].icon}),s.jsx("span",{children:A[e].label})]},e))}):d==="codex"?s.jsx("div",{className:"spawn-select-row spawn-select-row--codex-models",children:Object.keys(L).map(e=>s.jsxs("button",{className:`spawn-select-btn ${K===e?"selected":""}`,onClick:()=>de(e),title:L[e].description,children:[s.jsx("span",{children:L[e].icon}),s.jsx("span",{children:L[e].label})]},e))}):d==="opencode"?s.jsx("input",{type:"text",className:"spawn-input",defaultValue:"minimax/MiniMax-M1-80k",placeholder:"provider/model (e.g., minimax/MiniMax-M1-80k)"}):s.jsx("div",{className:"spawn-inline-hint",children:n("terminal:spawn.codex.configuration")})]}),s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.browser")}),s.jsx("div",{className:"spawn-form-row spawn-options-row",children:s.jsxs("label",{className:"spawn-checkbox",children:[s.jsx("input",{type:"checkbox",checked:F,onChange:e=>ce(e.target.checked),disabled:d!=="claude"}),s.jsx("span",{children:n("terminal:spawn.chromeBrowser")})]})})]})]}),d==="codex"&&s.jsx("div",{className:"spawn-form-row",children:s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("terminal:spawn.codex.config")}),s.jsxs("div",{className:"spawn-options-row",style:{display:"flex",flexDirection:"column",gap:8},children:[s.jsxs("label",{className:"spawn-checkbox",children:[s.jsx("input",{type:"checkbox",checked:b.fullAuto!==!1,onChange:e=>N(a=>({...a,fullAuto:e.target.checked}))}),s.jsx("span",{children:n("terminal:spawn.codex.useFullAuto")})]}),s.jsxs("label",{className:"spawn-checkbox",children:[s.jsx("input",{type:"checkbox",checked:!!b.search,onChange:e=>N(a=>({...a,search:e.target.checked}))}),s.jsx("span",{children:n("terminal:spawn.codex.enableSearch")})]}),b.fullAuto===!1&&s.jsxs(s.Fragment,{children:[s.jsxs("select",{className:"spawn-input",value:b.sandbox||"workspace-write",onChange:e=>N(a=>({...a,sandbox:e.target.value})),children:[s.jsx("option",{value:"read-only",children:n("terminal:spawn.codex.sandboxReadOnly")}),s.jsx("option",{value:"workspace-write",children:n("terminal:spawn.codex.sandboxWorkspaceWrite")}),s.jsx("option",{value:"danger-full-access",children:n("terminal:spawn.codex.sandboxDangerFullAccess")})]}),s.jsxs("select",{className:"spawn-input",value:b.approvalMode||"on-request",onChange:e=>N(a=>({...a,approvalMode:e.target.value})),children:[s.jsx("option",{value:"untrusted",children:n("terminal:spawn.codex.approvalsUntrusted")}),s.jsx("option",{value:"on-failure",children:n("terminal:spawn.codex.approvalsOnFailure")}),s.jsx("option",{value:"on-request",children:n("terminal:spawn.codex.approvalsOnRequest")}),s.jsx("option",{value:"never",children:n("terminal:spawn.codex.approvalsNever")})]})]}),s.jsx("input",{type:"text",className:"spawn-input",placeholder:n("terminal:spawn.codex.profileOptional"),value:b.profile||"",onChange:e=>N(a=>({...a,profile:e.target.value||void 0}))})]})]})}),x.length>0&&s.jsxs("div",{className:"spawn-skills-section",children:[s.jsxs("label",{className:"spawn-label",children:[n("terminal:spawn.skills")," ",s.jsxs("span",{className:"spawn-label-hint",children:["(",n("common:labels.optional"),")"]})]}),x.length>6&&s.jsx("input",{type:"text",className:"spawn-input skill-search-input",placeholder:n("terminal:spawn.filterSkills"),value:f,onChange:e=>ue(e.target.value)}),s.jsxs("div",{className:"spawn-skills-inline",children:[Y.map(e=>{const a=z.has(e.id);return he.some(w=>w.id===e.id)?null:s.jsxs("button",{className:`spawn-skill-chip ${a?"selected":""}`,onClick:()=>xe(e.id),title:e.description,children:[a&&s.jsx("span",{className:"spawn-skill-check",children:"✓"}),s.jsx("span",{children:e.name}),e.builtin&&s.jsx("span",{className:"spawn-skill-builtin",children:"TC"})]},e.id)}),f&&Y.length===0&&s.jsx("div",{className:"skill-search-empty",children:n("terminal:spawn.noSkillsMatch",{query:f})})]})]}),s.jsxs("div",{className:"spawn-custom-instructions-section",children:[s.jsxs("label",{className:"spawn-label",children:[n("terminal:spawn.customInstructions")," ",s.jsxs("span",{className:"spawn-label-hint",children:["(",n("common:labels.optional"),")"]})]}),s.jsx("textarea",{className:"spawn-input spawn-textarea",placeholder:n("terminal:spawn.customInstructionsBossPlaceholder"),value:H,onChange:e=>me(e.target.value),rows:3})]}),s.jsxs("div",{className:"spawn-subordinates-section",children:[s.jsxs("label",{className:"spawn-label",children:[n("terminal:spawn.initialSubordinates")," ",s.jsxs("span",{className:"spawn-label-hint",children:["(",n("common:labels.optional"),")"]})]}),s.jsx("div",{className:"subordinates-selector-compact",children:V.length===0?s.jsx("div",{className:"subordinates-empty",children:n("terminal:spawn.noAvailableAgents")}):V.map(e=>{const a=v.has(e.id),l=P[e.class],w=o.find(ye=>ye.id===e.class),ee=l||w||{icon:"🤖",color:"#888888"};return s.jsxs("button",{className:`subordinate-chip ${a?"selected":""}`,onClick:()=>Me(e.id),children:[a&&s.jsx("span",{className:"subordinate-check",children:"✓"}),s.jsx("span",{className:"subordinate-chip-icon",style:{color:ee.color},children:ee.icon}),s.jsx("span",{className:"subordinate-chip-name",children:e.name})]},e.id)})}),v.size>0&&s.jsxs("div",{className:"subordinates-count",children:[v.size," ",n("common:labels.selected").toLowerCase()]})]})]})]}),s.jsxs("div",{className:"modal-footer",children:[s.jsx("button",{className:"btn btn-secondary",onClick:h,children:n("common:buttons.cancel")}),s.jsx("button",{className:"btn btn-boss",onClick:ve,disabled:U,children:n(U?"common:buttons.deploying":"common:buttons2.deployBoss")})]})]})})}export{Fe as BossSpawnModal};