create-walle 0.9.0 → 0.9.3
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/README.md +35 -31
- package/package.json +3 -3
- package/template/CLAUDE.md +23 -1
- package/template/claude-task-manager/bin/restart-ctm.sh +3 -2
- package/template/claude-task-manager/db.js +38 -0
- package/template/claude-task-manager/public/css/walle.css +123 -0
- package/template/claude-task-manager/public/index.html +962 -69
- package/template/claude-task-manager/public/js/walle.js +374 -121
- package/template/claude-task-manager/public/prompts.html +84 -26
- package/template/claude-task-manager/public/walle-icon.svg +45 -0
- package/template/claude-task-manager/server.js +69 -4
- package/template/docs/openclaw-vs-walle-comparison.md +103 -0
- package/template/package.json +1 -1
- package/template/wall-e/agent.js +63 -3
- package/template/wall-e/api-walle.js +42 -0
- package/template/wall-e/brain.js +182 -5
- package/template/wall-e/channels/imessage-channel.js +4 -1
- package/template/wall-e/channels/slack-channel.js +3 -1
- package/template/wall-e/chat.js +106 -224
- package/template/wall-e/context/compactor.js +163 -0
- package/template/wall-e/context/context-builder.js +355 -0
- package/template/wall-e/context/state-snapshot.js +209 -0
- package/template/wall-e/context/token-counter.js +55 -0
- package/template/wall-e/context/topic-matcher.js +79 -0
- package/template/wall-e/core-tasks.js +24 -0
- package/template/wall-e/events/event-bus.js +23 -0
- package/template/wall-e/loops/ingest.js +4 -0
- package/template/wall-e/loops/initiative.js +316 -0
- package/template/wall-e/loops/tasks.js +55 -5
- package/template/wall-e/skills/_bundled/email-sync/run.js +3 -1
- package/template/wall-e/skills/_bundled/morning-briefing/run.js +41 -0
- package/template/wall-e/skills/_bundled/proactive-alerts/SKILL.md +20 -0
- package/template/wall-e/skills/_bundled/proactive-alerts/run.js +144 -0
- package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +18 -0
- package/template/wall-e/skills/_bundled/slack-mentions/.watermark.json +4 -0
- package/template/wall-e/skills/_bundled/slack-mentions/SKILL.md +52 -0
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +470 -0
- package/template/wall-e/skills/_bundled/weekly-reflection/SKILL.md +69 -0
- package/template/wall-e/tests/brain.test.js +4 -4
- package/template/wall-e/tests/compactor.test.js +323 -0
- package/template/wall-e/tests/context-builder.test.js +215 -0
- package/template/wall-e/tests/event-bus.test.js +74 -0
- package/template/wall-e/tests/initiative.test.js +354 -0
- package/template/wall-e/tests/proactive-alerts.test.js +140 -0
- package/template/wall-e/tests/session-persistence.test.js +335 -0
|
@@ -1016,7 +1016,7 @@ function renderChatUI() {
|
|
|
1016
1016
|
if (i + 1 < chatHistory.length && chatHistory[i + 1].role === 'assistant') {
|
|
1017
1017
|
i++;
|
|
1018
1018
|
html += '<div class="walle-chat-msg assistant">';
|
|
1019
|
-
html += '<div class="walle-chat-msg-role assistant">WALL-E</div>';
|
|
1019
|
+
html += '<div class="walle-chat-msg-role assistant"><img src="/walle-icon.svg" width="16" height="16" class="walle-avatar"> WALL-E</div>';
|
|
1020
1020
|
html += '<div class="walle-chat-msg-text we-markdown">' + renderMarkdown(chatHistory[i].text) + '</div>';
|
|
1021
1021
|
html += '</div>';
|
|
1022
1022
|
}
|
|
@@ -1030,7 +1030,7 @@ function renderChatUI() {
|
|
|
1030
1030
|
html += '<div class="we-turn-checkbox">' + (isSelSA ? '☑' : '☐') + '</div>';
|
|
1031
1031
|
}
|
|
1032
1032
|
html += '<div class="walle-chat-msg assistant">';
|
|
1033
|
-
html += '<div class="walle-chat-msg-role assistant">WALL-E</div>';
|
|
1033
|
+
html += '<div class="walle-chat-msg-role assistant"><img src="/walle-icon.svg" width="16" height="16" class="walle-avatar"> WALL-E</div>';
|
|
1034
1034
|
html += '<div class="walle-chat-msg-text we-markdown">' + renderMarkdown(msg.text) + '</div>';
|
|
1035
1035
|
html += '</div>';
|
|
1036
1036
|
html += '</div>';
|
|
@@ -1843,6 +1843,10 @@ function _highlightSearchTerms(html, query) {
|
|
|
1843
1843
|
}
|
|
1844
1844
|
|
|
1845
1845
|
// ---- Tasks View ----
|
|
1846
|
+
var _taskViewTab = 'active'; // active | inbox | automation | done | all
|
|
1847
|
+
var _expandedTasks = new Set(); // track which task cards are expanded
|
|
1848
|
+
var _collapsedGroups = new Set(); // track which groups user explicitly collapsed
|
|
1849
|
+
var _expandedGroups = new Set(); // track which groups user explicitly expanded
|
|
1846
1850
|
var _taskFilter = { search: '', statusSet: new Set(), type: 'all' }; // empty statusSet = all
|
|
1847
1851
|
|
|
1848
1852
|
WE.renderTasks = function() {
|
|
@@ -1874,8 +1878,22 @@ WE.renderTasks = function() {
|
|
|
1874
1878
|
});
|
|
1875
1879
|
};
|
|
1876
1880
|
|
|
1881
|
+
function _matchesViewTab(t) {
|
|
1882
|
+
if (_taskViewTab === 'all') return true;
|
|
1883
|
+
if (_taskViewTab === 'active') return t.status === 'running' || t.status === 'pending' || t.status === 'failed';
|
|
1884
|
+
if (_taskViewTab === 'inbox') return t.source === 'slack';
|
|
1885
|
+
if (_taskViewTab === 'automation') return t.type === 'recurring' && t.source !== 'slack';
|
|
1886
|
+
if (_taskViewTab === 'done') return t.status === 'completed' || t.status === 'cancelled';
|
|
1887
|
+
return true;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
function _countInbox(tasks) {
|
|
1891
|
+
return tasks.filter(function(t) { return t.source === 'slack' && t.status !== 'completed' && t.status !== 'cancelled' && t.status !== 'dismissed'; }).length;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1877
1894
|
function filterTasks(tasks) {
|
|
1878
1895
|
return tasks.filter(function(t) {
|
|
1896
|
+
if (!_matchesViewTab(t)) return false;
|
|
1879
1897
|
if (_taskFilter.statusSet.size > 0 && !_taskFilter.statusSet.has(t.status)) return false;
|
|
1880
1898
|
if (_taskFilter.type === 'recurring' && t.type !== 'recurring') return false;
|
|
1881
1899
|
if (_taskFilter.type === 'once' && t.type === 'recurring') return false;
|
|
@@ -1926,6 +1944,32 @@ WE._reconnectSlack = function() {
|
|
|
1926
1944
|
});
|
|
1927
1945
|
};
|
|
1928
1946
|
|
|
1947
|
+
WE._switchTaskView = function(tab) {
|
|
1948
|
+
_taskViewTab = tab;
|
|
1949
|
+
_taskFilter.statusSet.clear();
|
|
1950
|
+
_collapsedGroups.clear();
|
|
1951
|
+
_expandedGroups.clear();
|
|
1952
|
+
if (cache.allTasks) _renderTasksDirect(filterTasks(cache.allTasks));
|
|
1953
|
+
};
|
|
1954
|
+
|
|
1955
|
+
WE._toggleTaskExpand = function(id) {
|
|
1956
|
+
if (_expandedTasks.has(id)) _expandedTasks.delete(id);
|
|
1957
|
+
else _expandedTasks.add(id);
|
|
1958
|
+
_snapshotGroupState();
|
|
1959
|
+
if (cache.allTasks) _renderTasksDirect(filterTasks(cache.allTasks));
|
|
1960
|
+
};
|
|
1961
|
+
|
|
1962
|
+
// Capture current open/closed state of all <details> groups before re-render
|
|
1963
|
+
function _snapshotGroupState() {
|
|
1964
|
+
var groups = document.querySelectorAll('.we-task-group[data-group-key]');
|
|
1965
|
+
groups.forEach(function(el) {
|
|
1966
|
+
var key = el.getAttribute('data-group-key');
|
|
1967
|
+
if (!key) return;
|
|
1968
|
+
if (el.open) { _expandedGroups.add(key); _collapsedGroups.delete(key); }
|
|
1969
|
+
else { _collapsedGroups.add(key); _expandedGroups.delete(key); }
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1929
1973
|
WE._clearAllFilters = function() {
|
|
1930
1974
|
_taskFilter.statusSet.clear();
|
|
1931
1975
|
_taskFilter.search = '';
|
|
@@ -1995,42 +2039,62 @@ function _renderTasksContentInner(tasks) {
|
|
|
1995
2039
|
var body = document.getElementById('walle-body');
|
|
1996
2040
|
if (!body) return;
|
|
1997
2041
|
|
|
1998
|
-
var
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
html
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2042
|
+
var allTasks = cache.allTasks || tasks;
|
|
2043
|
+
var inboxCount = _countInbox(allTasks);
|
|
2044
|
+
|
|
2045
|
+
// ── View Tabs ──
|
|
2046
|
+
var html = '<div class="we-view-tabs">';
|
|
2047
|
+
var tabs = [
|
|
2048
|
+
{ id: 'active', label: 'Active' },
|
|
2049
|
+
{ id: 'inbox', label: 'Inbox', badge: inboxCount },
|
|
2050
|
+
{ id: 'automation', label: 'Automation' },
|
|
2051
|
+
{ id: 'done', label: 'Done' },
|
|
2052
|
+
{ id: 'all', label: 'All' }
|
|
2053
|
+
];
|
|
2054
|
+
tabs.forEach(function(tab) {
|
|
2055
|
+
var cls = 'we-view-tab' + (_taskViewTab === tab.id ? ' active' : '');
|
|
2056
|
+
html += '<button class="' + cls + '" onclick="WE._switchTaskView(\'' + tab.id + '\')">' + tab.label;
|
|
2057
|
+
if (tab.badge) html += '<span class="we-view-tab-badge">' + tab.badge + '</span>';
|
|
2058
|
+
html += '</button>';
|
|
2059
|
+
});
|
|
2060
|
+
html += '<button class="walle-btn primary" style="margin-left:auto" onclick="WE._newTaskForm()">+ New</button>';
|
|
2015
2061
|
html += '</div>';
|
|
2016
2062
|
|
|
2017
|
-
//
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2063
|
+
// ── Toolbar: search + filters (only in active/all/done views) ──
|
|
2064
|
+
if (_taskViewTab !== 'automation') {
|
|
2065
|
+
html += '<div class="we-task-toolbar">';
|
|
2066
|
+
html += '<input class="we-chat-search-input" id="we-task-search" placeholder="Search tasks..." value="' + esc(_taskFilter.search) + '" oninput="WE._onTaskFilter()" style="flex:1">';
|
|
2067
|
+
if (_taskViewTab === 'all') {
|
|
2068
|
+
html += '<select class="walle-filter-select" id="we-task-type-filter" onchange="WE._onTaskFilter()">';
|
|
2069
|
+
html += '<option value="all"' + (_taskFilter.type === 'all' ? ' selected' : '') + '>All types</option>';
|
|
2070
|
+
html += '<option value="recurring"' + (_taskFilter.type === 'recurring' ? ' selected' : '') + '>Recurring</option>';
|
|
2071
|
+
html += '<option value="once"' + (_taskFilter.type === 'once' ? ' selected' : '') + '>One-time</option>';
|
|
2072
|
+
html += '<option value="script"' + (_taskFilter.type === 'script' ? ' selected' : '') + '>Script</option>';
|
|
2073
|
+
html += '<option value="ai"' + (_taskFilter.type === 'ai' ? ' selected' : '') + '>AI</option>';
|
|
2074
|
+
html += '</select>';
|
|
2075
|
+
}
|
|
2076
|
+
html += '</div>';
|
|
2077
|
+
}
|
|
2031
2078
|
|
|
2032
|
-
//
|
|
2033
|
-
|
|
2079
|
+
// ── Context pills (for active/all views) ──
|
|
2080
|
+
if (_taskViewTab === 'active' || _taskViewTab === 'all') {
|
|
2081
|
+
var counts = { running: 0, pending: 0, paused: 0, completed: 0, failed: 0 };
|
|
2082
|
+
allTasks.forEach(function(t) { if (counts[t.status] !== undefined) counts[t.status]++; });
|
|
2083
|
+
var statusLabels = { running: 'running', pending: 'pending', paused: 'paused', completed: 'done', failed: 'failed' };
|
|
2084
|
+
var visibleStatuses = _taskViewTab === 'active' ? ['running', 'pending', 'failed'] : ['running', 'pending', 'paused', 'completed', 'failed'];
|
|
2085
|
+
html += '<div class="we-task-summary">';
|
|
2086
|
+
visibleStatuses.forEach(function(s) {
|
|
2087
|
+
var active = _taskFilter.statusSet.has(s);
|
|
2088
|
+
var cls = 'we-task-summary-item ' + s + (active ? ' active' : '');
|
|
2089
|
+
html += '<span class="' + cls + '" onclick="WE._toggleStatusFilter(\'' + s + '\')">' + counts[s] + ' ' + statusLabels[s] + '</span>';
|
|
2090
|
+
});
|
|
2091
|
+
var hasAnyFilter = _taskFilter.statusSet.size > 0 || _taskFilter.search || _taskFilter.type !== 'all';
|
|
2092
|
+
if (hasAnyFilter) html += '<span class="we-task-clear-btn" onclick="WE._clearAllFilters()" title="Clear all filters">×</span>';
|
|
2093
|
+
html += '</div>';
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// Slack auth banner
|
|
2097
|
+
var hasSlackAuthError = allTasks.some(function(t) {
|
|
2034
2098
|
return t.error && (t.error.includes('Slack token expired') || t.error.includes('invalid_auth'));
|
|
2035
2099
|
});
|
|
2036
2100
|
if (hasSlackAuthError) {
|
|
@@ -2044,60 +2108,157 @@ function _renderTasksContentInner(tasks) {
|
|
|
2044
2108
|
html += '<div id="we-new-task-form" style="display:none"></div>';
|
|
2045
2109
|
|
|
2046
2110
|
if (tasks.length === 0) {
|
|
2047
|
-
|
|
2111
|
+
var emptyMsg = _taskViewTab === 'inbox' ? 'No Slack requests yet. Mention @walle in Slack to assign tasks.' : 'No tasks match your filters.';
|
|
2112
|
+
html += '<div class="walle-empty" style="padding:20px">' + emptyMsg + '</div>';
|
|
2048
2113
|
safeSetHtml(body, html);
|
|
2049
2114
|
return;
|
|
2050
2115
|
}
|
|
2051
2116
|
|
|
2052
|
-
// ──
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
items.forEach(function(t) { h += renderTaskCard(t); });
|
|
2065
|
-
h += '</details>';
|
|
2066
|
-
return h;
|
|
2117
|
+
// ── Automation table view ──
|
|
2118
|
+
if (_taskViewTab === 'automation') {
|
|
2119
|
+
html += _renderAutomationTable(tasks);
|
|
2120
|
+
safeSetHtml(body, html);
|
|
2121
|
+
// Still poll running tasks
|
|
2122
|
+
var runningTasks = tasks.filter(function(t) { return t.status === 'running'; });
|
|
2123
|
+
var runningIds = {};
|
|
2124
|
+
runningTasks.forEach(function(t) { runningIds[t.id] = true; _pollTaskLogs(t.id); });
|
|
2125
|
+
Object.keys(_logPollers).forEach(function(id) { if (!runningIds[id]) _stopLogPoller(id); });
|
|
2126
|
+
if (runningTasks.length > 0) _ensureRunningTimer();
|
|
2127
|
+
_setupTaskRefreshPoller(tasks, runningTasks);
|
|
2128
|
+
return;
|
|
2067
2129
|
}
|
|
2068
2130
|
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
var
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
var
|
|
2131
|
+
// ── Inbox view — group by lifecycle ──
|
|
2132
|
+
if (_taskViewTab === 'inbox') {
|
|
2133
|
+
var inboxNew = tasks.filter(function(t) { return t.status === 'pending'; });
|
|
2134
|
+
var inboxProgress = tasks.filter(function(t) { return t.status === 'running'; });
|
|
2135
|
+
var inboxFailed = tasks.filter(function(t) { return t.status === 'failed'; });
|
|
2136
|
+
var inboxActiveThread = tasks.filter(function(t) { return (t.status === 'completed' || t.status === 'cancelled') && t.slack_thread && t.slack_thread.active; });
|
|
2137
|
+
var activeThreadIds = {};
|
|
2138
|
+
inboxActiveThread.forEach(function(t) { activeThreadIds[t.id] = true; });
|
|
2139
|
+
var inboxDone = tasks.filter(function(t) { return (t.status === 'completed' || t.status === 'cancelled') && !activeThreadIds[t.id]; });
|
|
2140
|
+
if (inboxActiveThread.length > 0) html += _taskGroup('Active Conversations', inboxActiveThread, '#4ade80', 'active-thread');
|
|
2141
|
+
if (inboxProgress.length > 0) html += _taskGroup('In Progress', inboxProgress, '#228be6', 'running');
|
|
2142
|
+
if (inboxNew.length > 0) html += _taskGroup('New', inboxNew, '#fab005', 'pending');
|
|
2143
|
+
if (inboxFailed.length > 0) html += _taskGroup('Failed', inboxFailed, '#e03131', 'failed');
|
|
2144
|
+
if (inboxDone.length > 0) html += _taskGroup('Done', inboxDone, '#5c940d', 'completed', true);
|
|
2145
|
+
}
|
|
2146
|
+
// ── Active / Done / All views — group by status ──
|
|
2147
|
+
else {
|
|
2148
|
+
var statusOrder = _taskViewTab === 'done'
|
|
2149
|
+
? ['completed', 'cancelled']
|
|
2150
|
+
: ['running', 'pending', 'paused', 'failed', 'completed'];
|
|
2076
2151
|
var groups = {};
|
|
2077
2152
|
statusOrder.forEach(function(s) { groups[s] = []; });
|
|
2078
2153
|
tasks.forEach(function(t) { (groups[t.status] || groups.pending).push(t); });
|
|
2079
|
-
|
|
2080
|
-
var colors = { running: '#228be6', pending: '#fab005', paused: '#888', completed: '#5c940d', failed: '#e03131' };
|
|
2154
|
+
var colors = { running: '#228be6', pending: '#fab005', paused: '#888', completed: '#5c940d', failed: '#e03131', cancelled: '#666' };
|
|
2081
2155
|
statusOrder.forEach(function(status) {
|
|
2082
2156
|
var g = groups[status];
|
|
2083
2157
|
if (g.length === 0) return;
|
|
2084
|
-
|
|
2158
|
+
var collapsed = (status === 'paused' || status === 'completed' || status === 'cancelled');
|
|
2159
|
+
html += _taskGroup(status.charAt(0).toUpperCase() + status.slice(1), g, colors[status], status, collapsed);
|
|
2085
2160
|
});
|
|
2086
2161
|
}
|
|
2087
2162
|
|
|
2088
2163
|
safeSetHtml(body, html);
|
|
2089
2164
|
|
|
2090
|
-
// Poll logs for running tasks
|
|
2165
|
+
// Poll logs for running tasks
|
|
2091
2166
|
var runningTasks = tasks.filter(function(t) { return t.status === 'running'; });
|
|
2092
2167
|
var runningIds = {};
|
|
2093
2168
|
runningTasks.forEach(function(t) { runningIds[t.id] = true; _pollTaskLogs(t.id); });
|
|
2094
|
-
// Stop pollers for tasks that are no longer running
|
|
2095
2169
|
Object.keys(_logPollers).forEach(function(id) { if (!runningIds[id]) _stopLogPoller(id); });
|
|
2096
|
-
|
|
2097
|
-
// Start/stop live elapsed-time ticking for running tasks
|
|
2098
2170
|
if (runningTasks.length > 0) _ensureRunningTimer();
|
|
2171
|
+
_setupTaskRefreshPoller(tasks, runningTasks);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
function _taskGroup(label, items, color, key, defaultCollapsed) {
|
|
2175
|
+
// User explicit state takes priority, then search forces open, then default
|
|
2176
|
+
var isOpen;
|
|
2177
|
+
if (_taskFilter.search) {
|
|
2178
|
+
isOpen = true;
|
|
2179
|
+
} else if (_expandedGroups.has(key)) {
|
|
2180
|
+
isOpen = true;
|
|
2181
|
+
} else if (_collapsedGroups.has(key)) {
|
|
2182
|
+
isOpen = false;
|
|
2183
|
+
} else {
|
|
2184
|
+
isOpen = !defaultCollapsed;
|
|
2185
|
+
}
|
|
2186
|
+
var h = '<details class="we-task-group"' + (isOpen ? ' open' : '') + ' data-group-key="' + esc(key) + '">';
|
|
2187
|
+
h += '<summary class="we-task-group-header" style="color:' + (color || '#aaa') + '">' + label + ' (' + items.length + ')</summary>';
|
|
2188
|
+
items.forEach(function(t) { h += renderTaskCard(t); });
|
|
2189
|
+
h += '</details>';
|
|
2190
|
+
return h;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
|
|
2194
|
+
function _renderAutomationTable(tasks) {
|
|
2195
|
+
var h = '';
|
|
2196
|
+
// Health summary
|
|
2197
|
+
var active = tasks.filter(function(t) { return t.status === 'pending' || t.status === 'running'; }).length;
|
|
2198
|
+
var paused = tasks.filter(function(t) { return t.status === 'paused'; }).length;
|
|
2199
|
+
var failed = tasks.filter(function(t) { return t.status === 'failed'; }).length;
|
|
2200
|
+
h += '<div class="we-auto-health">';
|
|
2201
|
+
h += '<span class="we-auto-health-item ok">' + active + ' active</span>';
|
|
2202
|
+
if (paused > 0) h += '<span class="we-auto-health-item paused">' + paused + ' paused</span>';
|
|
2203
|
+
if (failed > 0) h += '<span class="we-auto-health-item failed">' + failed + ' failed</span>';
|
|
2204
|
+
h += '</div>';
|
|
2205
|
+
|
|
2206
|
+
h += '<table class="we-auto-table"><thead><tr>';
|
|
2207
|
+
h += '<th>Task</th><th>Schedule</th><th>Last Run</th><th>Status</th><th></th>';
|
|
2208
|
+
h += '</tr></thead><tbody>';
|
|
2209
|
+
tasks.forEach(function(t) {
|
|
2210
|
+
var statusColors = { running: '#228be6', pending: '#5c940d', completed: '#5c940d', failed: '#e03131', paused: '#888', cancelled: '#666' };
|
|
2211
|
+
var statusIcons = { running: '\u25C9', pending: '\u25CF', completed: '\u2713', failed: '\u2717', paused: '\u23F8', cancelled: '\u2014' };
|
|
2212
|
+
var color = statusColors[t.status] || '#888';
|
|
2213
|
+
var icon = statusIcons[t.status] || '';
|
|
2214
|
+
var lastRun = t.last_run_at ? timeAgo(t.last_run_at) : 'never';
|
|
2215
|
+
var isExpanded = _expandedTasks.has(t.id);
|
|
2216
|
+
h += '<tr class="we-auto-row' + (isExpanded ? ' expanded' : '') + '" onclick="WE._toggleTaskExpand(\'' + esc(t.id) + '\')">';
|
|
2217
|
+
h += '<td class="we-auto-title">' + esc(t.title);
|
|
2218
|
+
if (t.skill) h += ' <span class="walle-tag" style="background:#1a3a1a;color:#4ade80;font-size:9px">' + esc(t.skill) + '</span>';
|
|
2219
|
+
h += '</td>';
|
|
2220
|
+
h += '<td class="we-auto-sched">' + esc(t.schedule || (t.type === 'once' ? 'one-time' : '-')) + '</td>';
|
|
2221
|
+
h += '<td class="we-auto-last">' + esc(lastRun) + '</td>';
|
|
2222
|
+
h += '<td style="color:' + color + '">' + icon + ' ' + esc(t.status) + '</td>';
|
|
2223
|
+
h += '<td class="we-auto-actions">';
|
|
2224
|
+
if (t.status === 'running') h += '<button class="we-auto-btn" onclick="event.stopPropagation();WE._stopTask(\'' + esc(t.id) + '\')">Stop</button>';
|
|
2225
|
+
else h += '<button class="we-auto-btn" onclick="event.stopPropagation();WE._runTaskNow(\'' + esc(t.id) + '\',this)">Run</button>';
|
|
2226
|
+
h += '</td>';
|
|
2227
|
+
h += '</tr>';
|
|
2228
|
+
if (isExpanded) {
|
|
2229
|
+
h += '<tr class="we-auto-detail"><td colspan="5">' + _renderExpandedCardContent(t) + '</td></tr>';
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
h += '</tbody></table>';
|
|
2233
|
+
return h;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
function _renderExpandedCardContent(t) {
|
|
2237
|
+
var h = '';
|
|
2238
|
+
if (t.description) h += '<div class="we-task-card-desc">' + esc(t.description) + '</div>';
|
|
2239
|
+
var taskItems = _getBriefingItemsForTask(t);
|
|
2240
|
+
if (taskItems.length > 0) h += renderBriefingItemsTable(taskItems, t);
|
|
2241
|
+
if (t.status === 'running') {
|
|
2242
|
+
h += '<div class="we-task-log-panel" id="we-task-log-' + esc(t.id) + '">';
|
|
2243
|
+
h += '<div class="we-task-log-header"><span class="we-task-log-pulse"></span> Live output</div>';
|
|
2244
|
+
h += '<pre class="we-task-log-content">Waiting for output...</pre>';
|
|
2245
|
+
h += '</div>';
|
|
2246
|
+
} else if (t.result || t.error) {
|
|
2247
|
+
h += _renderTaskResultPanel(t);
|
|
2248
|
+
}
|
|
2249
|
+
h += '<div class="we-task-card-actions" style="margin-top:8px">';
|
|
2250
|
+
if (t.status !== 'running') h += '<button class="walle-btn primary" onclick="WE._runTaskNow(\'' + esc(t.id) + '\',this)">Run Now</button>';
|
|
2251
|
+
if (t.type === 'recurring') {
|
|
2252
|
+
if (t.status === 'paused') h += '<button class="walle-btn" onclick="WE._pauseTask(\'' + esc(t.id) + '\',0)">Resume</button>';
|
|
2253
|
+
else if (t.status !== 'running') h += '<button class="walle-btn" onclick="WE._pauseTask(\'' + esc(t.id) + '\',1)">Pause</button>';
|
|
2254
|
+
}
|
|
2255
|
+
h += '<button class="walle-btn" onclick="WE._editTask(\'' + esc(t.id) + '\')">Edit</button>';
|
|
2256
|
+
h += '</div>';
|
|
2257
|
+
h += '<div id="we-edit-task-' + esc(t.id) + '" style="display:none"></div>';
|
|
2258
|
+
return h;
|
|
2259
|
+
}
|
|
2099
2260
|
|
|
2100
|
-
|
|
2261
|
+
function _setupTaskRefreshPoller(tasks, runningTasks) {
|
|
2101
2262
|
if (runningTasks.length > 0 && !_taskRefreshTimer) {
|
|
2102
2263
|
_taskRefreshTimer = setInterval(function() {
|
|
2103
2264
|
api('/tasks?limit=100').then(function(data) {
|
|
@@ -2109,6 +2270,7 @@ function _renderTasksContentInner(tasks) {
|
|
|
2109
2270
|
if (displayed && displayed.status !== nt.status) changed = true;
|
|
2110
2271
|
});
|
|
2111
2272
|
if (changed) {
|
|
2273
|
+
_snapshotGroupState();
|
|
2112
2274
|
Object.keys(_logPollers).forEach(_stopLogPoller);
|
|
2113
2275
|
_renderTasksDirect(filterTasks(newTasks));
|
|
2114
2276
|
}
|
|
@@ -2413,15 +2575,25 @@ WE._copyTaskLink = function(taskId) {
|
|
|
2413
2575
|
});
|
|
2414
2576
|
};
|
|
2415
2577
|
|
|
2578
|
+
function _sourceIcon(t) {
|
|
2579
|
+
var src = t.source || 'system';
|
|
2580
|
+
if (src === 'slack') return '<span class="we-src-icon slack" title="From Slack">slack</span>';
|
|
2581
|
+
if (src === 'chat') return '<span class="we-src-icon chat" title="From Chat">chat</span>';
|
|
2582
|
+
if (src === 'initiative') return '<span class="we-src-icon init" title="Auto-created">auto</span>';
|
|
2583
|
+
return '';
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2416
2586
|
function renderTaskCard(t) {
|
|
2417
2587
|
var q = _taskFilter.search || '';
|
|
2418
2588
|
var statusColors = { running: '#228be6', pending: '#fab005', completed: '#5c940d', failed: '#e03131', paused: '#888', cancelled: '#666' };
|
|
2419
2589
|
var borderColor = statusColors[t.status] || '#333';
|
|
2590
|
+
var isRunning = t.status === 'running';
|
|
2591
|
+
var isExpanded = _expandedTasks.has(t.id) || isRunning;
|
|
2420
2592
|
|
|
2421
|
-
var html = '<div class="we-task-card" id="task-' + esc(t.id) + '" style="border-left-color:' + borderColor + '">';
|
|
2593
|
+
var html = '<div class="we-task-card' + (isExpanded ? ' expanded' : ' compact') + '" id="task-' + esc(t.id) + '" style="border-left-color:' + borderColor + '">';
|
|
2422
2594
|
|
|
2423
|
-
// Row 1: title + badges + status
|
|
2424
|
-
html += '<div class="we-task-card-header">';
|
|
2595
|
+
// Row 1: title + badges + status (always visible)
|
|
2596
|
+
html += '<div class="we-task-card-header" onclick="WE._toggleTaskExpand(\'' + esc(t.id) + '\')" style="cursor:pointer">';
|
|
2425
2597
|
html += '<span class="we-task-card-title">' + _hl(t.title, q);
|
|
2426
2598
|
if (t.priority !== 'normal') html += ' <span class="walle-tag ' + esc(t.priority) + '">' + esc(t.priority) + '</span>';
|
|
2427
2599
|
if (t.execution === 'script') html += ' <span class="walle-tag" style="background:#333;color:#aaa">script</span>';
|
|
@@ -2431,26 +2603,63 @@ function renderTaskCard(t) {
|
|
|
2431
2603
|
html += '<span class="we-task-card-status" style="color:' + borderColor + '"><span class="we-status-dot we-status-dot--' + esc(t.status) + '"></span>' + esc(t.status) + '</span>';
|
|
2432
2604
|
html += '</div>';
|
|
2433
2605
|
|
|
2434
|
-
// Row 2:
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
// Row 3: metadata chips — primary (schedule + last run) always visible
|
|
2438
|
-
html += '<div class="we-task-meta">';
|
|
2606
|
+
// Row 2: compact metadata line (always visible)
|
|
2607
|
+
html += '<div class="we-task-meta-compact">';
|
|
2608
|
+
html += _sourceIcon(t);
|
|
2439
2609
|
if (t.type === 'recurring' && t.schedule) html += '<span>\uD83D\uDD01 ' + esc(t.schedule) + '</span>';
|
|
2440
|
-
if (t.last_run_at) html += '<span
|
|
2441
|
-
if (
|
|
2610
|
+
if (t.last_run_at) html += '<span>Last: ' + esc(timeAgo(t.last_run_at)) + '</span>';
|
|
2611
|
+
if (isRunning && t.started_at) {
|
|
2442
2612
|
var startMs = new Date(t.started_at + (t.started_at.includes('Z') ? '' : 'Z')).getTime();
|
|
2443
2613
|
var elapsedSec = Math.max(0, Math.floor((Date.now() - startMs) / 1000));
|
|
2444
2614
|
var elStr = elapsedSec < 60 ? elapsedSec + 's' : Math.floor(elapsedSec / 60) + 'm ' + (elapsedSec % 60) + 's';
|
|
2445
|
-
html += '<span class="we-task-running-timer" data-start-ms="' + startMs + '" style="color:#228be6">\u23F3
|
|
2615
|
+
html += '<span class="we-task-running-timer" data-start-ms="' + startMs + '" style="color:#228be6">\u23F3 ' + elStr + '</span>';
|
|
2616
|
+
}
|
|
2617
|
+
if (t.run_count > 0 && !isExpanded) html += '<span>' + t.run_count + ' runs</span>';
|
|
2618
|
+
// Slack source_ref link (rewrite to enterprise domain if needed)
|
|
2619
|
+
if (t.source === 'slack' && t.source_ref) {
|
|
2620
|
+
var slackUrl = t.source_ref.replace(/^(https:\/\/\w+)\.slack\.com/, '$1.enterprise.slack.com');
|
|
2621
|
+
html += '<a class="we-src-link" href="' + esc(slackUrl) + '" target="_blank" onclick="event.stopPropagation()" title="Open Slack thread">\u2197 thread</a>';
|
|
2622
|
+
}
|
|
2623
|
+
// Compact inline actions
|
|
2624
|
+
if (!isExpanded) {
|
|
2625
|
+
html += '<span class="we-compact-actions">';
|
|
2626
|
+
if (isRunning) html += '<button class="we-compact-btn stop" onclick="event.stopPropagation();WE._stopTask(\'' + esc(t.id) + '\')" title="Stop">\u25A0</button>';
|
|
2627
|
+
else html += '<button class="we-compact-btn run" onclick="event.stopPropagation();WE._runTaskNow(\'' + esc(t.id) + '\',this)" title="Run">\u25B6</button>';
|
|
2628
|
+
html += '<button class="we-compact-btn more" onclick="event.stopPropagation();WE._taskMenu(\'' + esc(t.id) + '\',this)" title="More actions">\u22EE</button>';
|
|
2629
|
+
html += '</span>';
|
|
2446
2630
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
if
|
|
2450
|
-
|
|
2451
|
-
|
|
2631
|
+
html += '</div>';
|
|
2632
|
+
|
|
2633
|
+
// ── Expanded content (only if expanded or running) ──
|
|
2634
|
+
if (isExpanded) {
|
|
2635
|
+
// Slack thread info panel
|
|
2636
|
+
if (t.source === 'slack') {
|
|
2637
|
+
html += '<div class="we-slack-info">';
|
|
2638
|
+
// Clean description: strip the ---\n**IMPORTANT** instructions block
|
|
2639
|
+
var cleanDesc = (t.description || '').replace(/\n---\n\*\*IMPORTANT\*\*[\s\S]*$/, '').replace(/^(Question from .+? in Slack:|Slack (?:task|mention) from .+?:)\n\n/, '').trim();
|
|
2640
|
+
if (cleanDesc) html += '<div class="we-task-card-desc">' + _hl(cleanDesc, q) + '</div>';
|
|
2641
|
+
// Thread watch status
|
|
2642
|
+
if (t.slack_thread) {
|
|
2643
|
+
var isActive = t.slack_thread.active;
|
|
2644
|
+
var expiresAt = new Date(t.slack_thread.expires_at);
|
|
2645
|
+
var msLeft = expiresAt - Date.now();
|
|
2646
|
+
var timeLeft = msLeft > 0 ? (msLeft > 3600000 ? Math.floor(msLeft / 3600000) + 'h ' + Math.floor((msLeft % 3600000) / 60000) + 'm' : Math.floor(msLeft / 60000) + 'm') : '';
|
|
2647
|
+
html += '<div class="we-slack-thread-status">';
|
|
2648
|
+
html += isActive
|
|
2649
|
+
? '<span class="we-thread-badge active">\uD83D\uDFE2 Thread active</span><span class="we-thread-expires">Reply in Slack to continue \u00B7 expires in ' + timeLeft + '</span>'
|
|
2650
|
+
: '<span class="we-thread-badge expired">\u26AA Thread expired</span>';
|
|
2651
|
+
html += '</div>';
|
|
2652
|
+
}
|
|
2653
|
+
html += '</div>';
|
|
2654
|
+
} else {
|
|
2655
|
+
// Non-slack description
|
|
2656
|
+
if (t.description) html += '<div class="we-task-card-desc">' + _hl(t.description, q) + '</div>';
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// Extra metadata
|
|
2660
|
+
html += '<div class="we-task-meta">';
|
|
2452
2661
|
if (t.run_count > 0) html += '<span>\u25B6 ' + t.run_count + ' runs</span>';
|
|
2453
|
-
if (t.last_duration_ms && t.last_duration_ms > 0 &&
|
|
2662
|
+
if (t.last_duration_ms && t.last_duration_ms > 0 && !isRunning) {
|
|
2454
2663
|
var durSec = Math.round(t.last_duration_ms / 1000);
|
|
2455
2664
|
var durStr = durSec < 60 ? durSec + 's' : Math.floor(durSec / 60) + 'm ' + (durSec % 60) + 's';
|
|
2456
2665
|
html += '<span>\u23F1 Took: ' + durStr + '</span>';
|
|
@@ -2468,48 +2677,42 @@ function renderTaskCard(t) {
|
|
|
2468
2677
|
}
|
|
2469
2678
|
}
|
|
2470
2679
|
}
|
|
2471
|
-
html += '</span>';
|
|
2472
|
-
}
|
|
2473
|
-
html += '</div>';
|
|
2474
|
-
|
|
2475
|
-
// Row 4: briefing items (if any), live logs (running), or result (completed/failed)
|
|
2476
|
-
var taskItems = _getBriefingItemsForTask(t);
|
|
2477
|
-
if (taskItems.length > 0) {
|
|
2478
|
-
html += renderBriefingItemsTable(taskItems, t);
|
|
2479
|
-
}
|
|
2480
|
-
if (t.status === 'running') {
|
|
2481
|
-
html += '<div class="we-task-log-panel" id="we-task-log-' + esc(t.id) + '">';
|
|
2482
|
-
html += '<div class="we-task-log-header"><span class="we-task-log-pulse"></span> Live output</div>';
|
|
2483
|
-
html += '<pre class="we-task-log-content">Waiting for output...</pre>';
|
|
2484
2680
|
html += '</div>';
|
|
2485
|
-
} else if (t.result || t.error) {
|
|
2486
|
-
html += _renderTaskResultPanel(t);
|
|
2487
|
-
}
|
|
2488
2681
|
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
else if (t.
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2682
|
+
// Briefing items
|
|
2683
|
+
var taskItems = _getBriefingItemsForTask(t);
|
|
2684
|
+
if (taskItems.length > 0) html += renderBriefingItemsTable(taskItems, t);
|
|
2685
|
+
|
|
2686
|
+
// Live logs or result
|
|
2687
|
+
if (isRunning) {
|
|
2688
|
+
html += '<div class="we-task-log-panel" id="we-task-log-' + esc(t.id) + '">';
|
|
2689
|
+
html += '<div class="we-task-log-header"><span class="we-task-log-pulse"></span> Live output</div>';
|
|
2690
|
+
html += '<pre class="we-task-log-content">Waiting for output...</pre>';
|
|
2691
|
+
html += '</div>';
|
|
2692
|
+
} else if (t.result || t.error) {
|
|
2693
|
+
html += _renderTaskResultPanel(t);
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
// Full action row
|
|
2697
|
+
html += '<div class="we-task-card-actions">';
|
|
2698
|
+
if (isRunning) {
|
|
2699
|
+
html += '<button class="walle-btn" style="color:#e03131;border-color:#e03131" onclick="WE._stopTask(\'' + esc(t.id) + '\')">Stop</button>';
|
|
2700
|
+
} else {
|
|
2701
|
+
html += '<button class="walle-btn primary" id="we-run-btn-' + esc(t.id) + '" onclick="WE._runTaskNow(\'' + esc(t.id) + '\',this)">Run Now</button>';
|
|
2702
|
+
}
|
|
2703
|
+
if (t.status === 'paused') {
|
|
2704
|
+
html += '<button class="walle-btn" onclick="WE._pauseTask(\'' + esc(t.id) + '\',0)">Resume</button>';
|
|
2705
|
+
} else if (!isRunning && t.status === 'pending') {
|
|
2706
|
+
html += '<button class="walle-btn" onclick="WE._pauseTask(\'' + esc(t.id) + '\',1)">Pause</button>';
|
|
2707
|
+
}
|
|
2708
|
+
if (!isRunning) {
|
|
2709
|
+
html += '<button class="walle-btn" style="color:#e03131;border-color:#e03131" onclick="WE._deleteTaskUI(\'' + esc(t.id) + '\')">Delete</button>';
|
|
2710
|
+
}
|
|
2711
|
+
html += '<button class="walle-btn" onclick="WE._editTask(\'' + esc(t.id) + '\')">Edit</button>';
|
|
2712
|
+
html += '</div>';
|
|
2713
|
+
html += '<div id="we-edit-task-' + esc(t.id) + '" style="display:none"></div>';
|
|
2508
2714
|
}
|
|
2509
|
-
html += '<button class="walle-btn" onclick="WE._editTask(\'' + esc(t.id) + '\')">Edit</button>';
|
|
2510
|
-
html += '</div>';
|
|
2511
2715
|
|
|
2512
|
-
html += '<div id="we-edit-task-' + esc(t.id) + '" style="display:none"></div>';
|
|
2513
2716
|
html += '</div>';
|
|
2514
2717
|
return html;
|
|
2515
2718
|
}
|
|
@@ -2800,6 +3003,9 @@ WE._cancelTask = function(id) {
|
|
|
2800
3003
|
};
|
|
2801
3004
|
|
|
2802
3005
|
WE._deleteTaskUI = function(id) {
|
|
3006
|
+
var t = (cache.allTasks || []).find(function(x) { return x.id === id; });
|
|
3007
|
+
var title = t ? t.title : id.slice(0, 8);
|
|
3008
|
+
if (!confirm('Delete task "' + title + '"?\n\nThis cannot be undone.')) return;
|
|
2803
3009
|
var token = window._ctmState?.token || '';
|
|
2804
3010
|
fetch(WALLE_BASE + '/api/wall-e/tasks/' + id + '?token=' + token, { method: 'DELETE' }).then(function() { WE.renderTasks(); });
|
|
2805
3011
|
};
|
|
@@ -2808,6 +3014,53 @@ WE._retryTask = function(id) {
|
|
|
2808
3014
|
apiPut('/tasks/' + id, { status: 'pending', error: null, result: null }).then(function() { WE.renderTasks(); });
|
|
2809
3015
|
};
|
|
2810
3016
|
|
|
3017
|
+
WE._taskMenu = function(id, btn) {
|
|
3018
|
+
// Close any existing menu
|
|
3019
|
+
var old = document.querySelector('.we-task-menu');
|
|
3020
|
+
if (old) old.remove();
|
|
3021
|
+
var t = (cache.allTasks || []).find(function(x) { return x.id === id; });
|
|
3022
|
+
if (!t) return;
|
|
3023
|
+
|
|
3024
|
+
var menu = document.createElement('div');
|
|
3025
|
+
menu.className = 'we-task-menu';
|
|
3026
|
+
var items = [];
|
|
3027
|
+
|
|
3028
|
+
// Context-appropriate actions
|
|
3029
|
+
if (t.status === 'running') {
|
|
3030
|
+
items.push({ label: '\u25A0 Stop', fn: function() { WE._stopTask(id); } });
|
|
3031
|
+
} else {
|
|
3032
|
+
items.push({ label: '\u25B6 Run Now', fn: function() { WE._runTaskNow(id); } });
|
|
3033
|
+
}
|
|
3034
|
+
if (t.status === 'paused') {
|
|
3035
|
+
items.push({ label: '\u25B6 Resume', fn: function() { WE._pauseTask(id, 0); } });
|
|
3036
|
+
} else if (t.status !== 'running') {
|
|
3037
|
+
items.push({ label: '\u23F8 Pause', fn: function() { WE._pauseTask(id, 1); } });
|
|
3038
|
+
}
|
|
3039
|
+
items.push({ label: '\u270E Edit', fn: function() { WE._toggleTaskExpand(id); setTimeout(function() { WE._editTask(id); }, 100); } });
|
|
3040
|
+
items.push({ label: '\u2716 Delete', fn: function() { WE._deleteTaskUI(id); }, danger: true });
|
|
3041
|
+
|
|
3042
|
+
items.forEach(function(item) {
|
|
3043
|
+
var row = document.createElement('div');
|
|
3044
|
+
row.className = 'we-task-menu-item' + (item.danger ? ' danger' : '');
|
|
3045
|
+
row.textContent = item.label;
|
|
3046
|
+
row.onclick = function(e) { e.stopPropagation(); menu.remove(); item.fn(); };
|
|
3047
|
+
menu.appendChild(row);
|
|
3048
|
+
});
|
|
3049
|
+
|
|
3050
|
+
// Position relative to button
|
|
3051
|
+
var rect = btn.getBoundingClientRect();
|
|
3052
|
+
menu.style.position = 'fixed';
|
|
3053
|
+
menu.style.top = (rect.bottom + 4) + 'px';
|
|
3054
|
+
menu.style.left = (rect.right - 140) + 'px';
|
|
3055
|
+
document.body.appendChild(menu);
|
|
3056
|
+
|
|
3057
|
+
// Close on outside click
|
|
3058
|
+
setTimeout(function() {
|
|
3059
|
+
function close(e) { if (!menu.contains(e.target)) { menu.remove(); document.removeEventListener('click', close); } }
|
|
3060
|
+
document.addEventListener('click', close);
|
|
3061
|
+
}, 0);
|
|
3062
|
+
};
|
|
3063
|
+
|
|
2811
3064
|
WE._editTask = function(id) {
|
|
2812
3065
|
var el = document.getElementById('we-edit-task-' + id);
|
|
2813
3066
|
if (!el) return;
|