create-walle 0.9.21 → 0.9.22
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 +5 -5
- package/package.json +2 -2
- package/template/claude-task-manager/api-prompts.js +13 -0
- package/template/claude-task-manager/api-reviews.js +5 -2
- package/template/claude-task-manager/db.js +348 -15
- package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
- package/template/claude-task-manager/docs/image-paste-ux.md +3 -0
- package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
- package/template/claude-task-manager/git-utils.js +146 -17
- package/template/claude-task-manager/lib/auth-rate-limit.js +23 -3
- package/template/claude-task-manager/lib/auth-rules.js +3 -0
- package/template/claude-task-manager/lib/document-review.js +33 -2
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +83 -0
- package/template/claude-task-manager/lib/mobile-auth-api.js +14 -0
- package/template/claude-task-manager/lib/restart-guard.js +68 -0
- package/template/claude-task-manager/lib/session-standup.js +36 -13
- package/template/claude-task-manager/lib/session-stream.js +11 -4
- package/template/claude-task-manager/lib/transport-security.js +50 -0
- package/template/claude-task-manager/lib/walle-transcript.js +16 -0
- package/template/claude-task-manager/lib/worktree-active-sync.js +6 -3
- package/template/claude-task-manager/public/css/reviews.css +10 -0
- package/template/claude-task-manager/public/css/setup.css +13 -0
- package/template/claude-task-manager/public/css/walle.css +145 -0
- package/template/claude-task-manager/public/index.html +539 -44
- package/template/claude-task-manager/public/ipad.html +363 -0
- package/template/claude-task-manager/public/js/document-review-links.js +196 -0
- package/template/claude-task-manager/public/js/message-renderer.js +14 -3
- package/template/claude-task-manager/public/js/reviews.js +30 -6
- package/template/claude-task-manager/public/js/setup.js +42 -2
- package/template/claude-task-manager/public/js/stream-view.js +20 -1
- package/template/claude-task-manager/public/js/walle.js +314 -18
- package/template/claude-task-manager/public/m/app.css +789 -11
- package/template/claude-task-manager/public/m/app.js +1070 -67
- package/template/claude-task-manager/public/m/claim.html +9 -2
- package/template/claude-task-manager/public/m/index.html +17 -10
- package/template/claude-task-manager/public/m/sw.js +1 -1
- package/template/claude-task-manager/server.js +365 -95
- package/template/claude-task-manager/session-integrity.js +4 -0
- package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +86 -35
- package/template/package.json +1 -1
- package/template/wall-e/api-walle.js +19 -1
- package/template/wall-e/brain.js +152 -6
- package/template/wall-e/chat.js +85 -0
- package/template/wall-e/coding-orchestrator.js +106 -12
- package/template/wall-e/http/model-admin.js +131 -0
- package/template/wall-e/lib/service-health.js +194 -0
- package/template/wall-e/llm/anthropic.js +7 -0
- package/template/wall-e/llm/client.js +46 -12
- package/template/wall-e/llm/openai.js +17 -2
- package/template/wall-e/llm/portkey-sync.js +201 -0
- package/template/wall-e/server.js +13 -0
- package/template/website/index.html +10 -10
|
@@ -2508,7 +2508,7 @@ function _microsoftProbeNoteText(probe, ms) {
|
|
|
2508
2508
|
return 'The Mac-side check reached CTM. If the phone still fails, refresh the phone page and check the CTM traffic list below.';
|
|
2509
2509
|
}
|
|
2510
2510
|
if (probe.blocked_by_tunnel_auth || probe.diagnosis === 'tunnel_auth_required') {
|
|
2511
|
-
return 'The tunnel is private. This Mac-side check is stopped by Microsoft because it is not a signed-in browser session; your phone should sign in with the same Microsoft/GitHub account, then CTM will load.';
|
|
2511
|
+
return 'The tunnel is private. This Mac-side check is stopped by Microsoft because it is not a signed-in browser session; your phone should sign in with the same Microsoft/GitHub account, then CTM will load. If the browser is stuck on the wrong account, use Different Account above and sign in with GitHub.';
|
|
2512
2512
|
}
|
|
2513
2513
|
return probe.message || 'Use Recover Now if the tunnel process should be running, then check the URL again.';
|
|
2514
2514
|
}
|
|
@@ -2544,6 +2544,7 @@ function _renderMicrosoftDevTunnelSetup(ms, d) {
|
|
|
2544
2544
|
var mobileUrl = document.getElementById('setup-ms-mobile-url');
|
|
2545
2545
|
var inspectUrl = document.getElementById('setup-ms-inspect-url');
|
|
2546
2546
|
var setupBtn = document.getElementById('setup-ms-setup');
|
|
2547
|
+
var switchLoginBtn = document.getElementById('setup-ms-switch-login');
|
|
2547
2548
|
var stopBtn = document.getElementById('setup-ms-stop');
|
|
2548
2549
|
var installCommand = document.getElementById('setup-ms-install-command');
|
|
2549
2550
|
var loginCommand = document.getElementById('setup-ms-login-command');
|
|
@@ -2620,6 +2621,12 @@ function _renderMicrosoftDevTunnelSetup(ms, d) {
|
|
|
2620
2621
|
: (_microsoftSetupInFlight ? 'Working...' : (!installed ? 'Set Up' : (!signedIn ? 'Open Sign-In' : 'Start Tunnel')));
|
|
2621
2622
|
setupBtn.title = ready ? 'Microsoft tunnel phone access is ready to use.' : '';
|
|
2622
2623
|
}
|
|
2624
|
+
if (switchLoginBtn) {
|
|
2625
|
+
switchLoginBtn.style.display = installed && signedIn ? '' : 'none';
|
|
2626
|
+
switchLoginBtn.disabled = _microsoftSetupInFlight;
|
|
2627
|
+
switchLoginBtn.textContent = 'Use Different Account';
|
|
2628
|
+
switchLoginBtn.title = 'Sign out of the current Dev Tunnels account and start GitHub device sign-in.';
|
|
2629
|
+
}
|
|
2623
2630
|
if (stopBtn) {
|
|
2624
2631
|
stopBtn.disabled = !ready;
|
|
2625
2632
|
}
|
|
@@ -3253,7 +3260,7 @@ async function setupMicrosoftTunnel() {
|
|
|
3253
3260
|
if (err) err.style.display = 'none';
|
|
3254
3261
|
if (btn) { btn.disabled = true; btn.textContent = 'Working...'; }
|
|
3255
3262
|
try {
|
|
3256
|
-
var d =
|
|
3263
|
+
var d = await loadNetworkSettings() || _lastNetworkSettings || {};
|
|
3257
3264
|
var ms = d.microsoft_dev_tunnel || {};
|
|
3258
3265
|
if (_microsoftTunnelReady(d)) {
|
|
3259
3266
|
_setMicrosoftActionStatus('Microsoft tunnel is already ready.', 'ok');
|
|
@@ -3312,6 +3319,38 @@ async function startMicrosoftTunnelLogin(deviceCode) {
|
|
|
3312
3319
|
}
|
|
3313
3320
|
}
|
|
3314
3321
|
|
|
3322
|
+
async function switchMicrosoftTunnelLogin() {
|
|
3323
|
+
var err = document.getElementById('setup-network-err');
|
|
3324
|
+
var btn = document.getElementById('setup-ms-switch-login');
|
|
3325
|
+
if (err) err.style.display = 'none';
|
|
3326
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Signing out...'; }
|
|
3327
|
+
_setMicrosoftActionStatus('Signing out of the current Dev Tunnels account...', '');
|
|
3328
|
+
try {
|
|
3329
|
+
var logoutRes = await fetch('/api/setup/network/microsoft-dev-tunnel/logout', {
|
|
3330
|
+
method: 'POST',
|
|
3331
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3332
|
+
body: '{}',
|
|
3333
|
+
});
|
|
3334
|
+
var logoutData = await logoutRes.json();
|
|
3335
|
+
if (!logoutRes.ok || !logoutData.ok) throw new Error(logoutData.error || 'Could not sign out of Dev Tunnels');
|
|
3336
|
+
if (_lastNetworkSettings) {
|
|
3337
|
+
_lastNetworkSettings.microsoft_dev_tunnel = logoutData.setup || _lastNetworkSettings.microsoft_dev_tunnel || {};
|
|
3338
|
+
}
|
|
3339
|
+
_renderMicrosoftDevTunnelSetup((_lastNetworkSettings || {}).microsoft_dev_tunnel || {}, _lastNetworkSettings || {});
|
|
3340
|
+
_setMicrosoftActionStatus('Signed out. Starting GitHub sign-in now...', '');
|
|
3341
|
+
if (btn) btn.textContent = 'Opening GitHub...';
|
|
3342
|
+
var loginData = await _postMicrosoftTunnelLogin(true);
|
|
3343
|
+
_renderMicrosoftProgress(loginData.progress || {});
|
|
3344
|
+
_setMicrosoftActionStatus('GitHub sign-in started. Enter the displayed code on the GitHub page, then click Set Up again after it finishes.', 'ok');
|
|
3345
|
+
setupToast('GitHub sign-in started');
|
|
3346
|
+
} catch (e) {
|
|
3347
|
+
if (err) { err.textContent = e.message; err.style.display = 'block'; }
|
|
3348
|
+
_setMicrosoftActionStatus(e.message || 'Could not switch Dev Tunnels account', 'error');
|
|
3349
|
+
} finally {
|
|
3350
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Use Different Account'; }
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3315
3354
|
async function startMicrosoftTunnel() {
|
|
3316
3355
|
var err = document.getElementById('setup-network-err');
|
|
3317
3356
|
var btn = document.getElementById('setup-ms-setup');
|
|
@@ -5263,6 +5302,7 @@ SETUP.setupMicrosoftTunnel = setupMicrosoftTunnel;
|
|
|
5263
5302
|
SETUP.startMicrosoftTunnel = startMicrosoftTunnel;
|
|
5264
5303
|
SETUP.stopMicrosoftTunnel = stopMicrosoftTunnel;
|
|
5265
5304
|
SETUP.startMicrosoftTunnelLogin = startMicrosoftTunnelLogin;
|
|
5305
|
+
SETUP.switchMicrosoftTunnelLogin = switchMicrosoftTunnelLogin;
|
|
5266
5306
|
SETUP.toggleMicrosoftKeepAwake = toggleMicrosoftKeepAwake;
|
|
5267
5307
|
SETUP.recoverMicrosoftTunnel = recoverMicrosoftTunnel;
|
|
5268
5308
|
SETUP.probeMicrosoftTunnel = probeMicrosoftTunnel;
|
|
@@ -496,6 +496,16 @@ function renderConversationEvent(evt) {
|
|
|
496
496
|
return MR.renderConversationEvent(evt);
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
function _linkConversationDocumentReferences(container, root) {
|
|
500
|
+
if (!container || !root || !window.CTMDocLinks || typeof window.CTMDocLinks.linkifyElement !== 'function') return;
|
|
501
|
+
try {
|
|
502
|
+
const sessionId = container.dataset?.sessionId || '';
|
|
503
|
+
window.CTMDocLinks.linkifyElement(root, window.CTMDocLinks.contextForSession(sessionId));
|
|
504
|
+
} catch (e) {
|
|
505
|
+
console.warn('[stream-view] document linkification failed:', e && e.message ? e.message : e);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
499
509
|
// When the new element AND the last existing child are compatible collapsible
|
|
500
510
|
// operational rows (`.conv-tool-group` or `.conv-warning-group`), fold the new
|
|
501
511
|
// one into the previous one's items list and bump the count + last-time.
|
|
@@ -718,6 +728,7 @@ function _appendConversationEventToTurns(container, evt, opts) {
|
|
|
718
728
|
const turn = MR.createConversationTurn(evt, { expanded: !!opts.expandPrompt });
|
|
719
729
|
_removeConversationState(container);
|
|
720
730
|
container.appendChild(turn);
|
|
731
|
+
_linkConversationDocumentReferences(container, turn);
|
|
721
732
|
if (typeof MR.refreshPromptTurnMeta === 'function') MR.refreshPromptTurnMeta(turn);
|
|
722
733
|
return turn;
|
|
723
734
|
}
|
|
@@ -730,7 +741,9 @@ function _appendConversationEventToTurns(container, evt, opts) {
|
|
|
730
741
|
if (!body) return null;
|
|
731
742
|
_removePromptTurnEmpty(body);
|
|
732
743
|
if (evt.data?.parentUuid) _assignConversationParentUuid(el, evt.data.parentUuid);
|
|
733
|
-
|
|
744
|
+
const merged = _mergeConsecutiveToolGroups(body, el);
|
|
745
|
+
if (!merged) body.appendChild(el);
|
|
746
|
+
_linkConversationDocumentReferences(container, merged ? body : el);
|
|
734
747
|
if (typeof MR.refreshPromptTurnMeta === 'function') MR.refreshPromptTurnMeta(turn);
|
|
735
748
|
return el;
|
|
736
749
|
}
|
|
@@ -754,6 +767,7 @@ function populateConversationView(container, events) {
|
|
|
754
767
|
if (_mergeConsecutiveToolGroups(container, el)) continue;
|
|
755
768
|
container.appendChild(el);
|
|
756
769
|
}
|
|
770
|
+
_linkConversationDocumentReferences(container, container);
|
|
757
771
|
if (_isPromptTurnContainer(container)) _refreshLatestPromptTurnStatus(container.dataset?.sessionId || '', container);
|
|
758
772
|
container.scrollTop = container.scrollHeight;
|
|
759
773
|
}
|
|
@@ -1364,6 +1378,7 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1364
1378
|
: renderConversationEvent(msg);
|
|
1365
1379
|
if (newEl) {
|
|
1366
1380
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
1381
|
+
_linkConversationDocumentReferences(container, newEl);
|
|
1367
1382
|
_replaceConversationParentEvent(existing, newEl);
|
|
1368
1383
|
} else {
|
|
1369
1384
|
_replaceConversationParentEvent(existing, null); // Event became empty after update
|
|
@@ -1385,6 +1400,7 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1385
1400
|
: renderConversationEvent(msg);
|
|
1386
1401
|
if (newEl) {
|
|
1387
1402
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
1403
|
+
_linkConversationDocumentReferences(container, newEl);
|
|
1388
1404
|
_replaceConversationParentEvent(existing, newEl);
|
|
1389
1405
|
}
|
|
1390
1406
|
refreshLatestPromptStatus();
|
|
@@ -1399,6 +1415,7 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1399
1415
|
: renderConversationEvent(msg);
|
|
1400
1416
|
if (newEl) {
|
|
1401
1417
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
1418
|
+
_linkConversationDocumentReferences(container, newEl);
|
|
1402
1419
|
_replaceConversationParentEvent(existing, newEl);
|
|
1403
1420
|
seen.add(parentUuid);
|
|
1404
1421
|
}
|
|
@@ -1425,6 +1442,7 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1425
1442
|
} else {
|
|
1426
1443
|
container.appendChild(el);
|
|
1427
1444
|
}
|
|
1445
|
+
_linkConversationDocumentReferences(container, container);
|
|
1428
1446
|
// Phase 4 reconciliation: respect the global "Hide tool calls" toggle
|
|
1429
1447
|
// for newly-streamed tool-only rows so the user's preference applies
|
|
1430
1448
|
// immediately to live updates, not just to the next full re-render.
|
|
@@ -1711,6 +1729,7 @@ async function _loadOlder(sessionId, convView) {
|
|
|
1711
1729
|
while (pageHost.firstChild) frag.appendChild(pageHost.firstChild);
|
|
1712
1730
|
// Prepend below the bar so the bar stays at the very top.
|
|
1713
1731
|
convView.insertBefore(frag, bar.nextSibling);
|
|
1732
|
+
_linkConversationDocumentReferences(convView, convView);
|
|
1714
1733
|
const newScrollHeight = convView.scrollHeight;
|
|
1715
1734
|
convView.scrollTop = prevScrollTop + (newScrollHeight - prevScrollHeight);
|
|
1716
1735
|
// Merge new parentUuids into the dedup set so live events for primed
|
|
@@ -8,6 +8,7 @@ let _brainReloadTimer = null;
|
|
|
8
8
|
let _providerStatus = null;
|
|
9
9
|
let _providerPollTimer = null;
|
|
10
10
|
let _cachedActiveModel = null; // { model, provider } from /api/setup/status
|
|
11
|
+
let _cachedSetupStatus = null;
|
|
11
12
|
let _activeProviderIssue = null;
|
|
12
13
|
|
|
13
14
|
const WalleCore = window.WalleCore || {};
|
|
@@ -1927,6 +1928,7 @@ WE.renderChat = function() {
|
|
|
1927
1928
|
// Fetch active model for badge (once, then cached)
|
|
1928
1929
|
if (!_cachedActiveModel) {
|
|
1929
1930
|
fetch('/api/setup/status').then(function(r) { return r.json(); }).then(function(d) {
|
|
1931
|
+
_cachedSetupStatus = d || null;
|
|
1930
1932
|
if (d.walle_model || d.walle_provider) {
|
|
1931
1933
|
_cachedActiveModel = { model: d.walle_model || '', provider: d.walle_provider || '' };
|
|
1932
1934
|
var badge = document.querySelector('.we-model-badge');
|
|
@@ -6464,22 +6466,15 @@ checkProviderStatus();
|
|
|
6464
6466
|
|
|
6465
6467
|
var _alertsPollTimer = null;
|
|
6466
6468
|
var _serviceAlerts = [];
|
|
6469
|
+
var _serviceHealth = null;
|
|
6467
6470
|
var _updatePromptShownVersions = {};
|
|
6468
6471
|
|
|
6469
6472
|
function checkServiceAlerts() {
|
|
6470
6473
|
api('/alerts').then(function(d) {
|
|
6471
6474
|
var alerts = d.alerts || [];
|
|
6472
6475
|
_serviceAlerts = alerts;
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
var issue = _normalizeProviderIssue(alerts[i]);
|
|
6476
|
-
if (issue) providerIssue = issue;
|
|
6477
|
-
}
|
|
6478
|
-
if (providerIssue) {
|
|
6479
|
-
_activeProviderIssue = providerIssue;
|
|
6480
|
-
if (currentView === 'chat') renderChatUI();
|
|
6481
|
-
}
|
|
6482
|
-
renderServiceAlerts(alerts);
|
|
6476
|
+
_serviceHealth = d.summary || buildClientServiceHealth(alerts);
|
|
6477
|
+
renderServiceAlerts(alerts, _serviceHealth);
|
|
6483
6478
|
maybeShowUpdateWizard(alerts);
|
|
6484
6479
|
clearTimeout(_alertsPollTimer);
|
|
6485
6480
|
_alertsPollTimer = setTimeout(checkServiceAlerts, 60000);
|
|
@@ -6489,6 +6484,131 @@ function checkServiceAlerts() {
|
|
|
6489
6484
|
});
|
|
6490
6485
|
}
|
|
6491
6486
|
|
|
6487
|
+
function alertCreatedAtMs(alert) {
|
|
6488
|
+
var t = Date.parse(alert && alert.created_at || '');
|
|
6489
|
+
return Number.isFinite(t) ? t : 0;
|
|
6490
|
+
}
|
|
6491
|
+
|
|
6492
|
+
function serviceAlertSkillName(alert) {
|
|
6493
|
+
if (!alert) return '';
|
|
6494
|
+
if (alert.skill) return String(alert.skill);
|
|
6495
|
+
var service = String(alert.service || '');
|
|
6496
|
+
if (service && service !== 'ai_provider' && service !== 'system') return service;
|
|
6497
|
+
var msg = String(alert.message || '');
|
|
6498
|
+
var quoted = msg.match(/Skill\s+"([^"]+)"/i);
|
|
6499
|
+
if (quoted) return quoted[1];
|
|
6500
|
+
var named = msg.match(/Skill\s+([A-Za-z0-9_.-]+)/i);
|
|
6501
|
+
return named ? named[1] : '';
|
|
6502
|
+
}
|
|
6503
|
+
|
|
6504
|
+
function serviceAlertProviderIssueType(issue) {
|
|
6505
|
+
return String(issue && issue.type || '').toLowerCase();
|
|
6506
|
+
}
|
|
6507
|
+
|
|
6508
|
+
function serviceAlertIsStickyProviderIssue(issue) {
|
|
6509
|
+
var text = [
|
|
6510
|
+
serviceAlertProviderIssueType(issue),
|
|
6511
|
+
issue && issue.title,
|
|
6512
|
+
issue && issue.message,
|
|
6513
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
6514
|
+
return /auth|quota|billing|credit|insufficient|invalid|forbidden|unauthorized|permission/.test(text);
|
|
6515
|
+
}
|
|
6516
|
+
|
|
6517
|
+
function serviceAlertMatchesActiveProvider(issue) {
|
|
6518
|
+
if (!issue) return false;
|
|
6519
|
+
var active = _cachedActiveModel || {};
|
|
6520
|
+
var activeProvider = String(active.provider || '').toLowerCase();
|
|
6521
|
+
var activeModel = String(active.model || '').toLowerCase();
|
|
6522
|
+
var provider = String(issue.provider || '').toLowerCase();
|
|
6523
|
+
var model = String(issue.model || '').toLowerCase();
|
|
6524
|
+
if (activeProvider && provider && activeProvider === provider) return true;
|
|
6525
|
+
if (activeModel && model && activeModel === model) return true;
|
|
6526
|
+
return !activeProvider && !activeModel;
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
function serviceAlertIsCurrentProviderIssue(issue) {
|
|
6530
|
+
if (!serviceAlertMatchesActiveProvider(issue)) return false;
|
|
6531
|
+
if (serviceAlertIsStickyProviderIssue(issue)) return true;
|
|
6532
|
+
var created = Date.parse(issue.createdAt || issue.created_at || '');
|
|
6533
|
+
if (!Number.isFinite(created)) return true;
|
|
6534
|
+
return Date.now() - created <= 6 * 60 * 60 * 1000;
|
|
6535
|
+
}
|
|
6536
|
+
|
|
6537
|
+
function publicServiceAlert(alert) {
|
|
6538
|
+
return {
|
|
6539
|
+
id: alert.id || '',
|
|
6540
|
+
service: alert.service || '',
|
|
6541
|
+
type: alert.type || '',
|
|
6542
|
+
title: alert.title || '',
|
|
6543
|
+
message: alert.message || '',
|
|
6544
|
+
severity: alert.severity || '',
|
|
6545
|
+
action: alert.action || '',
|
|
6546
|
+
action_label: alert.action_label || '',
|
|
6547
|
+
action_url: alert.action_url || '',
|
|
6548
|
+
created_at: alert.created_at || '',
|
|
6549
|
+
skill: serviceAlertSkillName(alert),
|
|
6550
|
+
};
|
|
6551
|
+
}
|
|
6552
|
+
|
|
6553
|
+
function summarizeProviderIssueForHealth(issue) {
|
|
6554
|
+
return {
|
|
6555
|
+
id: issue.id || '',
|
|
6556
|
+
type: issue.type || '',
|
|
6557
|
+
severity: issue.severity || 'error',
|
|
6558
|
+
title: issue.title || 'AI provider issue',
|
|
6559
|
+
message: issue.message || '',
|
|
6560
|
+
provider: issue.provider || '',
|
|
6561
|
+
model: issue.model || '',
|
|
6562
|
+
status: issue.status || '',
|
|
6563
|
+
action_url: issue.actionUrl || issue.action_url || '/setup.html',
|
|
6564
|
+
action_label: issue.actionLabel || issue.action_label || 'Open Setup',
|
|
6565
|
+
created_at: issue.createdAt || issue.created_at || '',
|
|
6566
|
+
};
|
|
6567
|
+
}
|
|
6568
|
+
|
|
6569
|
+
function buildClientServiceHealth(alerts) {
|
|
6570
|
+
alerts = Array.isArray(alerts) ? alerts : [];
|
|
6571
|
+
var providerIssues = alerts.map(_normalizeProviderIssue).filter(Boolean).sort(function(a, b) {
|
|
6572
|
+
return Date.parse(b.createdAt || b.created_at || '') - Date.parse(a.createdAt || a.created_at || '');
|
|
6573
|
+
});
|
|
6574
|
+
var currentIssue = null;
|
|
6575
|
+
for (var i = 0; i < providerIssues.length; i++) {
|
|
6576
|
+
if (serviceAlertIsCurrentProviderIssue(providerIssues[i])) {
|
|
6577
|
+
currentIssue = providerIssues[i];
|
|
6578
|
+
break;
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
var disabled = alerts.filter(function(a) { return String(a && a.type || '').toLowerCase() === 'skill_disabled'; }).map(publicServiceAlert);
|
|
6582
|
+
var integrations = alerts.filter(function(a) {
|
|
6583
|
+
return !_normalizeProviderIssue(a) && String(a && a.type || '').toLowerCase() !== 'skill_disabled' && isSessionsIntegrationAlert(a);
|
|
6584
|
+
}).map(publicServiceAlert);
|
|
6585
|
+
var systems = alerts.filter(function(a) {
|
|
6586
|
+
return !_normalizeProviderIssue(a) && String(a && a.type || '').toLowerCase() !== 'skill_disabled' && !isSessionsIntegrationAlert(a);
|
|
6587
|
+
}).map(publicServiceAlert);
|
|
6588
|
+
var history = providerIssues.filter(function(issue) {
|
|
6589
|
+
return !currentIssue || issue.id !== currentIssue.id;
|
|
6590
|
+
}).map(summarizeProviderIssueForHealth);
|
|
6591
|
+
var level = currentIssue ? (currentIssue.severity === 'warning' ? 'warning' : 'error') : ((disabled.length || integrations.length) ? 'warning' : ((history.length || systems.length) ? 'info' : 'ok'));
|
|
6592
|
+
return {
|
|
6593
|
+
level: level,
|
|
6594
|
+
title: currentIssue ? currentIssue.title : (level === 'ok' ? 'Wall-E services healthy' : (level === 'info' ? 'Older service history' : 'Background services need review')),
|
|
6595
|
+
message: currentIssue ? currentIssue.message : (level === 'ok' ? 'No current service blocker.' : 'Review provider history, disabled skills, or integrations below.'),
|
|
6596
|
+
current_issue: currentIssue ? summarizeProviderIssueForHealth(currentIssue) : null,
|
|
6597
|
+
disabled_skills: disabled,
|
|
6598
|
+
integration_alerts: integrations,
|
|
6599
|
+
provider_history: history,
|
|
6600
|
+
system_alerts: systems,
|
|
6601
|
+
counts: {
|
|
6602
|
+
total: alerts.length,
|
|
6603
|
+
current: currentIssue ? 1 : 0,
|
|
6604
|
+
disabled_skills: disabled.length,
|
|
6605
|
+
integration: integrations.length,
|
|
6606
|
+
provider_history: history.length,
|
|
6607
|
+
system: systems.length,
|
|
6608
|
+
},
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6611
|
+
|
|
6492
6612
|
function serviceAlertPresentation(alert) {
|
|
6493
6613
|
var providerIssue = _normalizeProviderIssue(alert);
|
|
6494
6614
|
var kind = providerIssue ? 'provider' : (alert.type === 'auth_expired' ? 'error' : alert.type === 'update_available' ? 'info' : 'warning');
|
|
@@ -6507,7 +6627,14 @@ function safeServiceAlertId(alert) {
|
|
|
6507
6627
|
function serviceAlertActionHtml(alert, safeId) {
|
|
6508
6628
|
var actionBtn = '';
|
|
6509
6629
|
var actionLabel = esc(alert.action_label || 'Fix');
|
|
6510
|
-
|
|
6630
|
+
var providerIssue = _normalizeProviderIssue(alert);
|
|
6631
|
+
if (providerIssue) {
|
|
6632
|
+
actionBtn = ' <button class="we-service-alert-action" type="button" onclick="WE._testProviderFromAlert(\'' + safeId + '\')">Test</button>'
|
|
6633
|
+
+ ' <button class="we-service-alert-action" type="button" onclick="navTo(\'setup\')">Setup</button>';
|
|
6634
|
+
} else if (String(alert.type || '').toLowerCase() === 'skill_disabled') {
|
|
6635
|
+
actionBtn = ' <button class="we-service-alert-action" type="button" onclick="WE._reviewSkillAlert(\'' + safeId + '\')">Review</button>'
|
|
6636
|
+
+ ' <button class="we-service-alert-action" type="button" onclick="WE._enableSkillFromAlert(\'' + safeId + '\')">Re-enable</button>';
|
|
6637
|
+
} else if (alert.action === 'repair_slack_owner') {
|
|
6511
6638
|
var actionName = esc(alert.action || 'custom').replace(/'/g, ''');
|
|
6512
6639
|
actionBtn = ' <button class="we-service-alert-action" type="button" onclick="WE._runAlertAction(\'' + safeId + '\', \'' + actionName + '\')">' + actionLabel + '</button>';
|
|
6513
6640
|
} else if (alert.action === 'gws_reauth') {
|
|
@@ -6530,6 +6657,96 @@ function serviceAlertItemHtml(alert) {
|
|
|
6530
6657
|
+ dismissBtn + '</div>';
|
|
6531
6658
|
}
|
|
6532
6659
|
|
|
6660
|
+
function serviceAlertById(alertId) {
|
|
6661
|
+
for (var i = 0; i < _serviceAlerts.length; i++) {
|
|
6662
|
+
if (String(_serviceAlerts[i].id) === String(alertId)) return _serviceAlerts[i];
|
|
6663
|
+
}
|
|
6664
|
+
return null;
|
|
6665
|
+
}
|
|
6666
|
+
|
|
6667
|
+
function serviceAlertSummaryMeta(health) {
|
|
6668
|
+
var counts = (health && health.counts) || {};
|
|
6669
|
+
var parts = [];
|
|
6670
|
+
if (health && health.current_issue) parts.push('current blocker');
|
|
6671
|
+
if (counts.disabled_skills) parts.push(counts.disabled_skills + ' disabled skill' + (counts.disabled_skills === 1 ? '' : 's'));
|
|
6672
|
+
if (counts.integration) parts.push(counts.integration + ' integration');
|
|
6673
|
+
if (counts.provider_history) parts.push(counts.provider_history + ' older provider');
|
|
6674
|
+
if (counts.system) parts.push(counts.system + ' system');
|
|
6675
|
+
return parts.length ? parts.join(' · ') : 'No alerts';
|
|
6676
|
+
}
|
|
6677
|
+
|
|
6678
|
+
function serviceAlertDisplayTime(value) {
|
|
6679
|
+
var t = Date.parse(value || '');
|
|
6680
|
+
if (!Number.isFinite(t)) return '';
|
|
6681
|
+
try {
|
|
6682
|
+
return new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }).format(new Date(t));
|
|
6683
|
+
} catch {
|
|
6684
|
+
return new Date(t).toLocaleString();
|
|
6685
|
+
}
|
|
6686
|
+
}
|
|
6687
|
+
|
|
6688
|
+
function renderHealthIssue(issue) {
|
|
6689
|
+
if (!issue) return '';
|
|
6690
|
+
var safeId = esc(issue.id || '').replace(/'/g, ''');
|
|
6691
|
+
var provider = [issue.provider, issue.model].filter(Boolean).join(' / ');
|
|
6692
|
+
var when = serviceAlertDisplayTime(issue.created_at);
|
|
6693
|
+
return '<div class="we-health-current">'
|
|
6694
|
+
+ '<div class="we-health-current-copy">'
|
|
6695
|
+
+ '<div class="we-health-current-title">' + esc(issue.title || 'Current provider issue') + '</div>'
|
|
6696
|
+
+ '<div class="we-health-current-body">' + esc(issue.message || '') + '</div>'
|
|
6697
|
+
+ '<div class="we-health-current-meta">' + esc([provider, when].filter(Boolean).join(' · ')) + '</div>'
|
|
6698
|
+
+ '</div>'
|
|
6699
|
+
+ '<div class="we-health-current-actions">'
|
|
6700
|
+
+ '<button class="walle-btn primary" type="button" onclick="WE._testProviderFromAlert(\'' + safeId + '\')">Test provider</button>'
|
|
6701
|
+
+ '<button class="walle-btn" type="button" onclick="navTo(\'setup\')">Open Setup</button>'
|
|
6702
|
+
+ '<button class="we-service-alert-dismiss" type="button" onclick="WE._dismissAlert(\'' + safeId + '\')" title="Dismiss">×</button>'
|
|
6703
|
+
+ '</div>'
|
|
6704
|
+
+ '</div>';
|
|
6705
|
+
}
|
|
6706
|
+
|
|
6707
|
+
function renderHealthGroup(title, alerts, opts) {
|
|
6708
|
+
opts = opts || {};
|
|
6709
|
+
if (!alerts || !alerts.length) return '';
|
|
6710
|
+
var open = opts.open ? ' open' : '';
|
|
6711
|
+
var rows = alerts.map(function(alert) {
|
|
6712
|
+
var source = serviceAlertById(alert.id) || alert;
|
|
6713
|
+
return serviceAlertItemHtml(source);
|
|
6714
|
+
}).join('');
|
|
6715
|
+
return '<details class="we-health-group"' + open + '>'
|
|
6716
|
+
+ '<summary><span>' + esc(title) + '</span><strong>' + alerts.length + '</strong></summary>'
|
|
6717
|
+
+ '<div class="we-health-group-body">' + rows + '</div>'
|
|
6718
|
+
+ '</details>';
|
|
6719
|
+
}
|
|
6720
|
+
|
|
6721
|
+
function renderServiceHealthPanel(alerts, health) {
|
|
6722
|
+
health = health || buildClientServiceHealth(alerts);
|
|
6723
|
+
if ((!alerts || !alerts.length) && (!health || health.level === 'ok')) return '';
|
|
6724
|
+
var level = health.level || 'info';
|
|
6725
|
+
var html = '<div id="walle-service-alerts" class="we-service-alerts we-health-card ' + esc(level) + '">';
|
|
6726
|
+
html += '<div class="we-health-header">';
|
|
6727
|
+
html += '<div class="we-health-status-dot" aria-hidden="true"></div>';
|
|
6728
|
+
html += '<div class="we-health-title-block"><div class="we-service-alerts-title">Service Health</div>'
|
|
6729
|
+
+ '<div class="we-health-title">' + esc(health.title || 'Wall-E services') + '</div>'
|
|
6730
|
+
+ '<div class="we-health-message">' + esc(health.message || '') + '</div></div>';
|
|
6731
|
+
html += '<div class="we-health-meta">' + esc(serviceAlertSummaryMeta(health)) + '</div>';
|
|
6732
|
+
html += '</div>';
|
|
6733
|
+
if (health.current_issue) html += renderHealthIssue(health.current_issue);
|
|
6734
|
+
if (health.integration_alerts && health.integration_alerts.length) {
|
|
6735
|
+
html += renderHealthGroup('Integrations', health.integration_alerts, { open: !health.current_issue });
|
|
6736
|
+
}
|
|
6737
|
+
if (health.disabled_skills && health.disabled_skills.length) {
|
|
6738
|
+
html += renderHealthGroup('Disabled background skills', health.disabled_skills, { open: !health.current_issue && !(health.integration_alerts || []).length });
|
|
6739
|
+
}
|
|
6740
|
+
if (health.provider_history && health.provider_history.length) {
|
|
6741
|
+
html += renderHealthGroup('Older provider history', health.provider_history, { open: false });
|
|
6742
|
+
}
|
|
6743
|
+
if (health.system_alerts && health.system_alerts.length) {
|
|
6744
|
+
html += renderHealthGroup('System alerts', health.system_alerts, { open: !health.current_issue && !(health.integration_alerts || []).length && !(health.disabled_skills || []).length });
|
|
6745
|
+
}
|
|
6746
|
+
html += '</div>';
|
|
6747
|
+
return html;
|
|
6748
|
+
}
|
|
6749
|
+
|
|
6533
6750
|
function serviceAlertSearchText(alert) {
|
|
6534
6751
|
return [
|
|
6535
6752
|
alert && alert.id,
|
|
@@ -6580,18 +6797,14 @@ function renderStandupServiceAlerts(alerts) {
|
|
|
6580
6797
|
+ integrationAlerts.map(serviceAlertItemHtml).join('');
|
|
6581
6798
|
}
|
|
6582
6799
|
|
|
6583
|
-
function renderServiceAlerts(alerts) {
|
|
6800
|
+
function renderServiceAlerts(alerts, health) {
|
|
6584
6801
|
var existing = document.getElementById('walle-service-alerts');
|
|
6585
6802
|
renderStandupServiceAlerts(alerts);
|
|
6586
|
-
|
|
6803
|
+
var html = renderServiceHealthPanel(alerts, health);
|
|
6804
|
+
if (!html) {
|
|
6587
6805
|
if (existing) existing.remove();
|
|
6588
6806
|
return;
|
|
6589
6807
|
}
|
|
6590
|
-
var items = alerts.map(serviceAlertItemHtml).join('');
|
|
6591
|
-
|
|
6592
|
-
var html = '<div id="walle-service-alerts" class="we-service-alerts">'
|
|
6593
|
-
+ '<div class="we-service-alerts-title">Service Alerts</div>' + items + '</div>';
|
|
6594
|
-
|
|
6595
6808
|
if (existing) {
|
|
6596
6809
|
existing.outerHTML = html;
|
|
6597
6810
|
} else {
|
|
@@ -6730,6 +6943,89 @@ WE._runAlertAction = function(alertId, actionName) {
|
|
|
6730
6943
|
});
|
|
6731
6944
|
};
|
|
6732
6945
|
|
|
6946
|
+
function providerForAlert(alert) {
|
|
6947
|
+
var issue = _normalizeProviderIssue(alert) || alert || {};
|
|
6948
|
+
return String(issue.provider || (_cachedActiveModel && _cachedActiveModel.provider) || '').trim();
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
function providerLabelForTest(provider) {
|
|
6952
|
+
if (!provider) return 'provider';
|
|
6953
|
+
if (provider === 'deepseek') return 'DeepSeek';
|
|
6954
|
+
if (provider === 'openai') return 'OpenAI';
|
|
6955
|
+
if (provider === 'moonshot') return 'Moonshot / Kimi';
|
|
6956
|
+
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
6957
|
+
}
|
|
6958
|
+
|
|
6959
|
+
function testSetupProvider(provider, alertId) {
|
|
6960
|
+
provider = String(provider || '').trim();
|
|
6961
|
+
if (!provider) {
|
|
6962
|
+
if (typeof showToast === 'function') showToast('No active provider to test', 'var(--yellow, #e0af68)', 2600);
|
|
6963
|
+
return;
|
|
6964
|
+
}
|
|
6965
|
+
var params = new URLSearchParams({ provider: provider });
|
|
6966
|
+
if (_cachedSetupStatus && _cachedSetupStatus.auth_method) params.set('auth_method', _cachedSetupStatus.auth_method);
|
|
6967
|
+
if (typeof showToast === 'function') showToast('Testing ' + providerLabelForTest(provider) + '...', 'var(--accent)', 1600);
|
|
6968
|
+
fetch('/api/setup/test-key?' + params.toString(), { cache: 'no-store' })
|
|
6969
|
+
.then(function(r) { return r.json().then(function(d) { return { ok: r.ok, data: d }; }); })
|
|
6970
|
+
.then(function(result) {
|
|
6971
|
+
var data = result.data || {};
|
|
6972
|
+
if (result.ok && data.ok) {
|
|
6973
|
+
if (typeof showToast === 'function') showToast(providerLabelForTest(provider) + ' is reachable', 'var(--green, #9ece6a)', 2600);
|
|
6974
|
+
if (alertId) WE._dismissAlert(alertId);
|
|
6975
|
+
else checkServiceAlerts();
|
|
6976
|
+
return;
|
|
6977
|
+
}
|
|
6978
|
+
var msg = data.error || 'Provider test failed';
|
|
6979
|
+
if (typeof showToast === 'function') showToast(msg, 'var(--red, #f7768e)', 4200);
|
|
6980
|
+
})
|
|
6981
|
+
.catch(function(err) {
|
|
6982
|
+
if (typeof showToast === 'function') showToast(err.message || 'Provider test failed', 'var(--red, #f7768e)', 4200);
|
|
6983
|
+
});
|
|
6984
|
+
}
|
|
6985
|
+
|
|
6986
|
+
WE._testProviderFromAlert = function(alertId) {
|
|
6987
|
+
var alert = serviceAlertById(alertId);
|
|
6988
|
+
if (!alert && _serviceHealth && _serviceHealth.current_issue && String(_serviceHealth.current_issue.id) === String(alertId)) {
|
|
6989
|
+
alert = _serviceHealth.current_issue;
|
|
6990
|
+
}
|
|
6991
|
+
testSetupProvider(providerForAlert(alert), alertId);
|
|
6992
|
+
};
|
|
6993
|
+
|
|
6994
|
+
WE._reviewSkillAlert = function(alertId) {
|
|
6995
|
+
var alert = serviceAlertById(alertId);
|
|
6996
|
+
var name = serviceAlertSkillName(alert);
|
|
6997
|
+
_skillsFilter.search = name || '';
|
|
6998
|
+
_skillsFilter.status = 'All';
|
|
6999
|
+
_skillsFilter.category = 'All';
|
|
7000
|
+
WE.showView('skills');
|
|
7001
|
+
};
|
|
7002
|
+
|
|
7003
|
+
WE._enableSkillFromAlert = function(alertId) {
|
|
7004
|
+
var alert = serviceAlertById(alertId);
|
|
7005
|
+
var name = serviceAlertSkillName(alert);
|
|
7006
|
+
if (!name) {
|
|
7007
|
+
if (typeof showToast === 'function') showToast('Could not identify the disabled skill', 'var(--red, #f7768e)', 3000);
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
7010
|
+
api('/skills?enabled=0').then(function(resp) {
|
|
7011
|
+
var skills = resp.data || [];
|
|
7012
|
+
var match = null;
|
|
7013
|
+
for (var i = 0; i < skills.length; i++) {
|
|
7014
|
+
if (String(skills[i].name || '').toLowerCase() === name.toLowerCase()) {
|
|
7015
|
+
match = skills[i];
|
|
7016
|
+
break;
|
|
7017
|
+
}
|
|
7018
|
+
}
|
|
7019
|
+
if (!match) throw new Error('Skill is not disabled or was not found: ' + name);
|
|
7020
|
+
return apiPut('/skills/' + encodeURIComponent(match.id), { enabled: 1 });
|
|
7021
|
+
}).then(function() {
|
|
7022
|
+
if (typeof showToast === 'function') showToast('Re-enabled ' + name, 'var(--green, #9ece6a)', 2600);
|
|
7023
|
+
WE._dismissAlert(alertId);
|
|
7024
|
+
}).catch(function(err) {
|
|
7025
|
+
if (typeof showToast === 'function') showToast(err.message || 'Could not re-enable skill', 'var(--red, #f7768e)', 4200);
|
|
7026
|
+
});
|
|
7027
|
+
};
|
|
7028
|
+
|
|
6733
7029
|
function findServiceAlert(alertId) {
|
|
6734
7030
|
for (var i = 0; i < _serviceAlerts.length; i++) {
|
|
6735
7031
|
if (String(_serviceAlerts[i].id) === String(alertId)) return _serviceAlerts[i];
|