@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.
- package/dist/agent/codex-mcp-process.d.ts.map +1 -1
- package/dist/agent/codex-mcp-process.js +14 -8
- package/dist/agent/codex-mcp-process.js.map +1 -1
- package/dist/agent/mama-tool-handlers.d.ts.map +1 -1
- package/dist/agent/mama-tool-handlers.js +7 -0
- package/dist/agent/mama-tool-handlers.js.map +1 -1
- package/dist/agent/persistent-cli-process.d.ts +5 -0
- package/dist/agent/persistent-cli-process.d.ts.map +1 -1
- package/dist/agent/persistent-cli-process.js +30 -10
- package/dist/agent/persistent-cli-process.js.map +1 -1
- package/dist/agent/skill-loader.d.ts.map +1 -1
- package/dist/agent/skill-loader.js +141 -5
- package/dist/agent/skill-loader.js.map +1 -1
- package/dist/api/graph-api-types.d.ts +1 -0
- package/dist/api/graph-api-types.d.ts.map +1 -1
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +56 -0
- package/dist/api/graph-api.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +7 -0
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/config/types.d.ts +4 -0
- package/dist/cli/config/types.d.ts.map +1 -1
- package/dist/cli/config/types.js +2 -0
- package/dist/cli/config/types.js.map +1 -1
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +51 -6
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/session-store.d.ts +15 -0
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +101 -0
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/multi-agent/agent-message-queue.d.ts +4 -2
- package/dist/multi-agent/agent-message-queue.d.ts.map +1 -1
- package/dist/multi-agent/agent-message-queue.js +76 -21
- package/dist/multi-agent/agent-message-queue.js.map +1 -1
- package/dist/multi-agent/agent-process-manager.d.ts +5 -3
- package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
- package/dist/multi-agent/agent-process-manager.js +16 -6
- package/dist/multi-agent/agent-process-manager.js.map +1 -1
- package/dist/multi-agent/multi-agent-base.d.ts +14 -1
- package/dist/multi-agent/multi-agent-base.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-base.js +95 -18
- package/dist/multi-agent/multi-agent-base.js.map +1 -1
- package/dist/multi-agent/multi-agent-discord.d.ts +0 -4
- package/dist/multi-agent/multi-agent-discord.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-discord.js +51 -26
- package/dist/multi-agent/multi-agent-discord.js.map +1 -1
- package/dist/multi-agent/multi-agent-slack.d.ts +11 -1
- package/dist/multi-agent/multi-agent-slack.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-slack.js +180 -34
- package/dist/multi-agent/multi-agent-slack.js.map +1 -1
- package/dist/multi-agent/system-reminder.d.ts +7 -1
- package/dist/multi-agent/system-reminder.d.ts.map +1 -1
- package/dist/multi-agent/system-reminder.js +35 -19
- package/dist/multi-agent/system-reminder.js.map +1 -1
- package/dist/multi-agent/workflow-engine.d.ts.map +1 -1
- package/dist/multi-agent/workflow-engine.js +10 -7
- package/dist/multi-agent/workflow-engine.js.map +1 -1
- package/dist/multi-agent/workflow-types.d.ts +3 -1
- package/dist/multi-agent/workflow-types.d.ts.map +1 -1
- package/package.json +1 -1
- package/public/viewer/js/modules/chat.js +43 -14
- package/public/viewer/js/modules/dashboard.js +55 -1
- package/public/viewer/js/modules/settings.js +28 -0
- package/public/viewer/js/utils/api.js +9 -0
- package/public/viewer/src/modules/chat.ts +57 -14
- package/public/viewer/src/modules/dashboard.ts +65 -1
- package/public/viewer/src/modules/settings.ts +38 -0
- package/public/viewer/src/utils/api.ts +27 -0
- 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
|
-
|
|
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
|
-
//
|
|
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
|
|
236
|
-
logger.info('
|
|
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.
|
|
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(
|
|
1783
|
+
loadHistory(_sessionId: string): ChatHistoryMessage[] | null {
|
|
1781
1784
|
try {
|
|
1782
|
-
const storageKey = this.
|
|
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 =
|
|
1900
|
+
this.history = boundedHistory;
|
|
1885
1901
|
|
|
1886
1902
|
// Use DocumentFragment for batch DOM insertion
|
|
1887
1903
|
const fragment = document.createDocumentFragment();
|
|
1888
|
-
|
|
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
|
-
|
|
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(
|
|
1967
|
+
clearHistory(_sessionId: string | null = null): void {
|
|
1925
1968
|
try {
|
|
1926
|
-
const storageKey = this.
|
|
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 {
|
|
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>
|