@jungjaehoon/mama-os 0.12.0 → 0.12.2

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 (71) hide show
  1. package/dist/agent/codex-mcp-process.d.ts.map +1 -1
  2. package/dist/agent/codex-mcp-process.js +14 -8
  3. package/dist/agent/codex-mcp-process.js.map +1 -1
  4. package/dist/agent/mama-tool-handlers.d.ts.map +1 -1
  5. package/dist/agent/mama-tool-handlers.js +7 -0
  6. package/dist/agent/mama-tool-handlers.js.map +1 -1
  7. package/dist/agent/persistent-cli-process.d.ts +5 -0
  8. package/dist/agent/persistent-cli-process.d.ts.map +1 -1
  9. package/dist/agent/persistent-cli-process.js +30 -10
  10. package/dist/agent/persistent-cli-process.js.map +1 -1
  11. package/dist/agent/skill-loader.d.ts.map +1 -1
  12. package/dist/agent/skill-loader.js +141 -5
  13. package/dist/agent/skill-loader.js.map +1 -1
  14. package/dist/api/graph-api-types.d.ts +1 -0
  15. package/dist/api/graph-api-types.d.ts.map +1 -1
  16. package/dist/api/graph-api.d.ts.map +1 -1
  17. package/dist/api/graph-api.js +56 -0
  18. package/dist/api/graph-api.js.map +1 -1
  19. package/dist/cli/commands/start.d.ts.map +1 -1
  20. package/dist/cli/commands/start.js +7 -0
  21. package/dist/cli/commands/start.js.map +1 -1
  22. package/dist/cli/config/types.d.ts +4 -0
  23. package/dist/cli/config/types.d.ts.map +1 -1
  24. package/dist/cli/config/types.js +2 -0
  25. package/dist/cli/config/types.js.map +1 -1
  26. package/dist/gateways/message-router.d.ts.map +1 -1
  27. package/dist/gateways/message-router.js +51 -6
  28. package/dist/gateways/message-router.js.map +1 -1
  29. package/dist/gateways/session-store.d.ts +15 -0
  30. package/dist/gateways/session-store.d.ts.map +1 -1
  31. package/dist/gateways/session-store.js +101 -0
  32. package/dist/gateways/session-store.js.map +1 -1
  33. package/dist/multi-agent/agent-message-queue.d.ts +4 -2
  34. package/dist/multi-agent/agent-message-queue.d.ts.map +1 -1
  35. package/dist/multi-agent/agent-message-queue.js +76 -21
  36. package/dist/multi-agent/agent-message-queue.js.map +1 -1
  37. package/dist/multi-agent/agent-process-manager.d.ts +5 -3
  38. package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
  39. package/dist/multi-agent/agent-process-manager.js +16 -6
  40. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  41. package/dist/multi-agent/multi-agent-base.d.ts +14 -1
  42. package/dist/multi-agent/multi-agent-base.d.ts.map +1 -1
  43. package/dist/multi-agent/multi-agent-base.js +95 -18
  44. package/dist/multi-agent/multi-agent-base.js.map +1 -1
  45. package/dist/multi-agent/multi-agent-discord.d.ts +0 -4
  46. package/dist/multi-agent/multi-agent-discord.d.ts.map +1 -1
  47. package/dist/multi-agent/multi-agent-discord.js +51 -26
  48. package/dist/multi-agent/multi-agent-discord.js.map +1 -1
  49. package/dist/multi-agent/multi-agent-slack.d.ts +11 -1
  50. package/dist/multi-agent/multi-agent-slack.d.ts.map +1 -1
  51. package/dist/multi-agent/multi-agent-slack.js +180 -34
  52. package/dist/multi-agent/multi-agent-slack.js.map +1 -1
  53. package/dist/multi-agent/system-reminder.d.ts +7 -1
  54. package/dist/multi-agent/system-reminder.d.ts.map +1 -1
  55. package/dist/multi-agent/system-reminder.js +35 -19
  56. package/dist/multi-agent/system-reminder.js.map +1 -1
  57. package/dist/multi-agent/workflow-engine.d.ts.map +1 -1
  58. package/dist/multi-agent/workflow-engine.js +10 -7
  59. package/dist/multi-agent/workflow-engine.js.map +1 -1
  60. package/dist/multi-agent/workflow-types.d.ts +3 -1
  61. package/dist/multi-agent/workflow-types.d.ts.map +1 -1
  62. package/package.json +1 -1
  63. package/public/viewer/js/modules/chat.js +43 -14
  64. package/public/viewer/js/modules/dashboard.js +55 -1
  65. package/public/viewer/js/modules/settings.js +28 -0
  66. package/public/viewer/js/utils/api.js +9 -0
  67. package/public/viewer/src/modules/chat.ts +57 -14
  68. package/public/viewer/src/modules/dashboard.ts +65 -1
  69. package/public/viewer/src/modules/settings.ts +38 -0
  70. package/public/viewer/src/utils/api.ts +27 -0
  71. package/public/viewer/viewer.html +36 -0
@@ -151,7 +151,10 @@ export class ChatModule {
151
151
  rafPending = false;
152
152
  history: ChatHistoryMessage[] = [];
153
153
  historyPrefix = 'mama_chat_history_';
154
- maxHistoryMessages = 50;
154
+ get historyStorageKey(): string {
155
+ return this.historyPrefix + 'viewer_mama_os_main';
156
+ }
157
+ maxHistoryMessages = 200;
155
158
  maxDomMessages = 100; // Limit DOM elements for performance
156
159
  historyExpiryMs = 24 * 60 * 60 * 1000;
157
160
  checkpointCooldown = false;
@@ -230,11 +233,11 @@ export class ChatModule {
230
233
  // Check for resumable session first
231
234
  await this.checkForResumableSession();
232
235
 
233
- // Try to get last active server session first
236
+ // Always try to get the last active viewer session from server
237
+ // Use it regardless of isAlive — server history is the source of truth
234
238
  const lastActiveSession = await API.getLastActiveSession().catch(() => null);
235
- if (lastActiveSession && lastActiveSession.id && lastActiveSession.isAlive) {
236
- logger.info('Resuming last active session:', lastActiveSession.id);
237
- this.addSystemMessage('Resuming previous session...');
239
+ if (lastActiveSession && lastActiveSession.id) {
240
+ logger.info('Using server session:', lastActiveSession.id);
238
241
  localStorage.setItem('mama_chat_session_id', lastActiveSession.id);
239
242
  this.initWebSocket(lastActiveSession.id);
240
243
  return;
@@ -1763,7 +1766,7 @@ export class ChatModule {
1763
1766
  }
1764
1767
 
1765
1768
  try {
1766
- const storageKey = this.historyPrefix + this.sessionId;
1769
+ const storageKey = this.historyStorageKey;
1767
1770
  const storageData = {
1768
1771
  history: this.history,
1769
1772
  savedAt: Date.now(),
@@ -1777,9 +1780,9 @@ export class ChatModule {
1777
1780
  /**
1778
1781
  * Load history from localStorage
1779
1782
  */
1780
- loadHistory(sessionId: string): ChatHistoryMessage[] | null {
1783
+ loadHistory(_sessionId: string): ChatHistoryMessage[] | null {
1781
1784
  try {
1782
- const storageKey = this.historyPrefix + sessionId;
1785
+ const storageKey = this.historyStorageKey;
1783
1786
  const stored = localStorage.getItem(storageKey);
1784
1787
 
1785
1788
  if (!stored) {
@@ -1880,13 +1883,25 @@ export class ChatModule {
1880
1883
  return;
1881
1884
  }
1882
1885
 
1886
+ // Merge: use server messages as base, append any local-only messages
1887
+ // that are newer than the last server message
1888
+ const serverTimestamp =
1889
+ messages.length > 0 ? new Date(messages[messages.length - 1].timestamp || 0).getTime() : 0;
1890
+
1891
+ const localOnlyMessages = this.history.filter((msg) => {
1892
+ const msgTime = new Date(msg.timestamp).getTime();
1893
+ return msgTime > serverTimestamp && msg.role !== 'system';
1894
+ });
1895
+
1896
+ const merged = [...messages, ...localOnlyMessages];
1897
+ const boundedHistory = merged.slice(-this.maxHistoryMessages);
1898
+
1883
1899
  container.innerHTML = '';
1884
- this.history = messages;
1900
+ this.history = boundedHistory;
1885
1901
 
1886
1902
  // Use DocumentFragment for batch DOM insertion
1887
1903
  const fragment = document.createDocumentFragment();
1888
- // Limit to last N messages for DOM performance
1889
- const messagesToRender = messages.slice(-this.maxDomMessages);
1904
+ const messagesToRender = boundedHistory.slice(-this.maxDomMessages);
1890
1905
 
1891
1906
  messagesToRender.forEach((msg) => {
1892
1907
  const msgEl = document.createElement('div');
@@ -1915,15 +1930,43 @@ export class ChatModule {
1915
1930
 
1916
1931
  container.appendChild(fragment);
1917
1932
  scrollToBottom(container);
1918
- logger.info('Displayed', messagesToRender.length, 'history messages');
1933
+
1934
+ // Save merged history to localStorage
1935
+ this.saveCurrentHistory();
1936
+
1937
+ logger.info(
1938
+ 'Displayed',
1939
+ messagesToRender.length,
1940
+ 'messages (server:',
1941
+ messages.length,
1942
+ '+ local:',
1943
+ localOnlyMessages.length,
1944
+ ')'
1945
+ );
1946
+ }
1947
+
1948
+ private saveCurrentHistory(): void {
1949
+ if (!this.sessionId) {
1950
+ return;
1951
+ }
1952
+ try {
1953
+ const storageKey = this.historyStorageKey;
1954
+ const storageData = {
1955
+ history: this.history,
1956
+ savedAt: Date.now(),
1957
+ };
1958
+ localStorage.setItem(storageKey, JSON.stringify(storageData));
1959
+ } catch (e) {
1960
+ logger.warn('Failed to save history:', e);
1961
+ }
1919
1962
  }
1920
1963
 
1921
1964
  /**
1922
1965
  * Clear chat history
1923
1966
  */
1924
- clearHistory(sessionId: string | null = null): void {
1967
+ clearHistory(_sessionId: string | null = null): void {
1925
1968
  try {
1926
- const storageKey = this.historyPrefix + (sessionId || this.sessionId);
1969
+ const storageKey = this.historyStorageKey;
1927
1970
  localStorage.removeItem(storageKey);
1928
1971
  this.history = [];
1929
1972
  } catch (e) {
@@ -12,7 +12,13 @@
12
12
 
13
13
  /* eslint-env browser */
14
14
 
15
- import { escapeAttr, escapeHtml, getElementByIdOrNull, getErrorMessage } from '../utils/dom.js';
15
+ import {
16
+ escapeAttr,
17
+ escapeHtml,
18
+ getElementByIdOrNull,
19
+ getErrorMessage,
20
+ showToast,
21
+ } from '../utils/dom.js';
16
22
  import { formatModelName } from '../utils/format.js';
17
23
  import {
18
24
  API,
@@ -168,6 +174,24 @@ export class DashboardModule {
168
174
  if (settingsTab) {
169
175
  settingsTab.click();
170
176
  }
177
+ return;
178
+ }
179
+
180
+ const restartBtn = target.closest<HTMLElement>('[data-action="restart-agent"]');
181
+ if (restartBtn) {
182
+ const agentId = restartBtn.getAttribute('data-agent-id');
183
+ if (agentId) {
184
+ this.restartAgent(agentId);
185
+ }
186
+ return;
187
+ }
188
+
189
+ const stopBtn = target.closest<HTMLElement>('[data-action="stop-agent"]');
190
+ if (stopBtn) {
191
+ const agentId = stopBtn.getAttribute('data-agent-id');
192
+ if (agentId) {
193
+ this.stopAgent(agentId);
194
+ }
171
195
  }
172
196
  };
173
197
  document.addEventListener('click', this.onCronClick);
@@ -717,6 +741,24 @@ export class DashboardModule {
717
741
  ? `<p class="text-[10px] text-gray-400 mt-1">Last: ${this.formatRelativeTime(agent.lastActivity)}</p>`
718
742
  : ''
719
743
  }
744
+ <div class="flex gap-1 mt-2 pt-2 border-t border-gray-100">
745
+ <button
746
+ data-action="restart-agent"
747
+ data-agent-id="${escapeAttr(agent.id)}"
748
+ class="text-[10px] px-2 py-0.5 rounded bg-blue-50 text-blue-600 hover:bg-blue-100 transition-colors"
749
+ title="Restart agent"
750
+ >🔄 Restart</button>
751
+ ${
752
+ agent.status === 'busy'
753
+ ? `<button
754
+ data-action="stop-agent"
755
+ data-agent-id="${escapeAttr(agent.id)}"
756
+ class="text-[10px] px-2 py-0.5 rounded bg-red-50 text-red-600 hover:bg-red-100 transition-colors"
757
+ title="Stop agent"
758
+ >⏹ Stop</button>`
759
+ : ''
760
+ }
761
+ </div>
720
762
  </div>
721
763
  `;
722
764
  })
@@ -921,6 +963,28 @@ export class DashboardModule {
921
963
  }
922
964
  }
923
965
 
966
+ async restartAgent(agentId: string): Promise<void> {
967
+ try {
968
+ await API.restartAgent(agentId);
969
+ showToast(`Agent "${agentId}" restarted`);
970
+ await this.loadStatus();
971
+ } catch (e) {
972
+ logger.error('[Dashboard] Failed to restart agent:', e);
973
+ showToast(`Restart failed: ${getErrorMessage(e)}`);
974
+ }
975
+ }
976
+
977
+ async stopAgent(agentId: string): Promise<void> {
978
+ try {
979
+ await API.stopAgent(agentId);
980
+ showToast(`Agent "${agentId}" stopped`);
981
+ await this.loadStatus();
982
+ } catch (e) {
983
+ logger.error('[Dashboard] Failed to stop agent:', e);
984
+ showToast(`Stop failed: ${getErrorMessage(e)}`);
985
+ }
986
+ }
987
+
924
988
  /**
925
989
  * Render system health section
926
990
  */
@@ -81,6 +81,14 @@ type SettingsPayload = {
81
81
  enabled: boolean;
82
82
  retention_days: number;
83
83
  };
84
+ timeouts: {
85
+ request_ms: number;
86
+ agent_ms: number;
87
+ session_ms: number;
88
+ workflow_step_ms: number;
89
+ workflow_max_ms: number;
90
+ ultrawork_ms: number;
91
+ };
84
92
  };
85
93
 
86
94
  // Model options by backend (single source of truth)
@@ -432,6 +440,9 @@ export class SettingsModule {
432
440
  Math.round((this.config.agent?.timeout || 300000) / 1000)
433
441
  );
434
442
 
443
+ // Timeouts
444
+ this.populateTimeouts();
445
+
435
446
  // Tool Mode
436
447
  this.populateToolMode();
437
448
 
@@ -450,6 +461,25 @@ export class SettingsModule {
450
461
  this.populateCronSection();
451
462
  }
452
463
 
464
+ /**
465
+ * Populate timeouts section from config
466
+ */
467
+ populateTimeouts(): void {
468
+ if (!this.config) {
469
+ return;
470
+ }
471
+ const t = this.config.timeouts;
472
+ if (!t) {
473
+ return;
474
+ }
475
+ this.setValue('settings-timeout-request', Math.round((t.request_ms ?? 120000) / 1000));
476
+ this.setValue('settings-timeout-agent', Math.round((t.agent_ms ?? 300000) / 1000));
477
+ this.setValue('settings-timeout-session', Math.round((t.session_ms ?? 1800000) / 60000));
478
+ this.setValue('settings-timeout-wf-step', Math.round((t.workflow_step_ms ?? 300000) / 1000));
479
+ this.setValue('settings-timeout-wf-max', Math.round((t.workflow_max_ms ?? 1800000) / 60000));
480
+ this.setValue('settings-timeout-ultrawork', Math.round((t.ultrawork_ms ?? 300000) / 1000));
481
+ }
482
+
453
483
  /**
454
484
  * Populate role permissions from config
455
485
  */
@@ -841,6 +871,14 @@ export class SettingsModule {
841
871
  enabled: this.getCheckbox('settings-metrics-enabled'),
842
872
  retention_days: this.parseIntegerInput('settings-metrics-retention', 1, 90, 7),
843
873
  },
874
+ timeouts: {
875
+ request_ms: this.parseIntegerInput('settings-timeout-request', 0, 600, 120) * 1000,
876
+ agent_ms: this.parseIntegerInput('settings-timeout-agent', 0, 3600, 300) * 1000,
877
+ session_ms: this.parseIntegerInput('settings-timeout-session', 0, 1440, 30) * 60000,
878
+ workflow_step_ms: this.parseIntegerInput('settings-timeout-wf-step', 0, 7200, 300) * 1000,
879
+ workflow_max_ms: this.parseIntegerInput('settings-timeout-wf-max', 0, 1440, 30) * 60000,
880
+ ultrawork_ms: this.parseIntegerInput('settings-timeout-ultrawork', 0, 7200, 300) * 1000,
881
+ },
844
882
  };
845
883
  }
846
884
 
@@ -91,6 +91,20 @@ export interface ApiMetricsConfig {
91
91
  retention_days?: number;
92
92
  }
93
93
 
94
+ export interface ApiTimeoutsConfig {
95
+ request_ms?: number;
96
+ codex_request_ms?: number;
97
+ initialize_ms?: number;
98
+ session_ms?: number;
99
+ session_cleanup_ms?: number;
100
+ agent_ms?: number;
101
+ ultrawork_ms?: number;
102
+ workflow_step_ms?: number;
103
+ workflow_max_ms?: number;
104
+ busy_retry_ms?: number;
105
+ [key: string]: unknown;
106
+ }
107
+
94
108
  export interface ApiConfigResponse {
95
109
  discord?: ApiGatewayConfig;
96
110
  slack?: ApiGatewayConfig;
@@ -101,6 +115,7 @@ export interface ApiConfigResponse {
101
115
  roles?: ApiRolesConfig;
102
116
  token_budget?: ApiTokenBudgetConfig;
103
117
  metrics?: ApiMetricsConfig;
118
+ timeouts?: ApiTimeoutsConfig;
104
119
  [key: string]: unknown;
105
120
  }
106
121
 
@@ -695,6 +710,18 @@ export class API {
695
710
  return this.put(`/api/skills/${encodeURIComponent(name)}/content`, { content, source });
696
711
  }
697
712
 
713
+ // =============================================
714
+ // Multi-Agent Control API
715
+ // =============================================
716
+
717
+ static async restartAgent(agentId: string): Promise<JsonRecord> {
718
+ return this.post(`/api/multi-agent/agents/${encodeURIComponent(agentId)}/restart`, {});
719
+ }
720
+
721
+ static async stopAgent(agentId: string): Promise<JsonRecord> {
722
+ return this.post(`/api/multi-agent/agents/${encodeURIComponent(agentId)}/stop`, {});
723
+ }
724
+
698
725
  // =============================================
699
726
  // Metrics / Health API
700
727
  // =============================================
@@ -658,6 +658,42 @@
658
658
  </div>
659
659
  </section>
660
660
 
661
+ <!-- Timeouts -->
662
+ <section>
663
+ <h2 class="text-xs font-semibold text-gray-700 mb-1.5 flex items-center gap-1">⏱️ Timeouts</h2>
664
+ <div class="bg-white border border-gray-200 rounded-lg p-2 shadow-sm">
665
+ <div class="grid grid-cols-3 gap-1 mb-1">
666
+ <div>
667
+ <label class="block text-[9px] text-gray-500 mb-0.5">Request (s)</label>
668
+ <input type="number" id="settings-timeout-request" value="120" min="10" max="600" class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50">
669
+ </div>
670
+ <div>
671
+ <label class="block text-[9px] text-gray-500 mb-0.5">Agent (s)</label>
672
+ <input type="number" id="settings-timeout-agent" value="300" min="30" max="3600" class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50">
673
+ </div>
674
+ <div>
675
+ <label class="block text-[9px] text-gray-500 mb-0.5">Session (min)</label>
676
+ <input type="number" id="settings-timeout-session" value="30" min="5" max="1440" class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50">
677
+ </div>
678
+ </div>
679
+ <div class="grid grid-cols-3 gap-1 mb-1">
680
+ <div>
681
+ <label class="block text-[9px] text-gray-500 mb-0.5">WF Step (s)</label>
682
+ <input type="number" id="settings-timeout-wf-step" value="300" min="0" max="7200" class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50" title="0 = unlimited">
683
+ </div>
684
+ <div>
685
+ <label class="block text-[9px] text-gray-500 mb-0.5">WF Max (min)</label>
686
+ <input type="number" id="settings-timeout-wf-max" value="30" min="0" max="1440" class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50" title="0 = unlimited">
687
+ </div>
688
+ <div>
689
+ <label class="block text-[9px] text-gray-500 mb-0.5">UltraWork (s)</label>
690
+ <input type="number" id="settings-timeout-ultrawork" value="300" min="0" max="7200" class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50" title="0 = unlimited">
691
+ </div>
692
+ </div>
693
+ <p class="text-[8px] text-gray-400 mt-0.5">0 = unlimited (WF Step, WF Max, UltraWork)</p>
694
+ </div>
695
+ </section>
696
+
661
697
  <!-- Role Permissions -->
662
698
  <section>
663
699
  <h2 class="text-xs font-semibold text-gray-700 mb-1.5 flex items-center gap-1">🔐 Roles</h2>