clementine-agent 1.18.81 → 1.18.83
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/assistant.d.ts +15 -0
- package/dist/agent/assistant.js +15 -0
- package/dist/cli/dashboard.js +514 -3
- package/dist/gateway/router.d.ts +9 -0
- package/dist/gateway/router.js +4 -0
- package/package.json +1 -1
|
@@ -100,6 +100,21 @@ export declare class PersonalAssistant {
|
|
|
100
100
|
}>;
|
|
101
101
|
updatedAt: string;
|
|
102
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* PRD Phase 2.1: clear the cached status for one server so the next query
|
|
105
|
+
* repopulates it from a fresh handshake. The SDK manages connections
|
|
106
|
+
* internally; we don't have a direct "reconnect now" hook, but invalidating
|
|
107
|
+
* the cached entry tells the dashboard to render 'pending' and resets any
|
|
108
|
+
* stale error/auth state. Returns the post-clear cached snapshot.
|
|
109
|
+
*/
|
|
110
|
+
invalidateMcpStatus(serverName: string): {
|
|
111
|
+
servers: Array<{
|
|
112
|
+
name: string;
|
|
113
|
+
status: string;
|
|
114
|
+
}>;
|
|
115
|
+
updatedAt: string;
|
|
116
|
+
cleared: boolean;
|
|
117
|
+
};
|
|
103
118
|
/** Inject a background work result into the session as silent follow-up context. */
|
|
104
119
|
injectPendingContext(sessionKey: string, userPrompt: string, result: string): void;
|
|
105
120
|
private initMemoryStore;
|
package/dist/agent/assistant.js
CHANGED
|
@@ -810,6 +810,21 @@ export class PersonalAssistant {
|
|
|
810
810
|
getMcpStatus() {
|
|
811
811
|
return { servers: this._lastMcpStatus, updatedAt: this._lastMcpStatusTime };
|
|
812
812
|
}
|
|
813
|
+
/**
|
|
814
|
+
* PRD Phase 2.1: clear the cached status for one server so the next query
|
|
815
|
+
* repopulates it from a fresh handshake. The SDK manages connections
|
|
816
|
+
* internally; we don't have a direct "reconnect now" hook, but invalidating
|
|
817
|
+
* the cached entry tells the dashboard to render 'pending' and resets any
|
|
818
|
+
* stale error/auth state. Returns the post-clear cached snapshot.
|
|
819
|
+
*/
|
|
820
|
+
invalidateMcpStatus(serverName) {
|
|
821
|
+
const beforeLen = this._lastMcpStatus.length;
|
|
822
|
+
this._lastMcpStatus = this._lastMcpStatus.filter((s) => s.name !== serverName);
|
|
823
|
+
const cleared = this._lastMcpStatus.length < beforeLen;
|
|
824
|
+
if (cleared)
|
|
825
|
+
this._lastMcpStatusTime = new Date().toISOString();
|
|
826
|
+
return { servers: this._lastMcpStatus, updatedAt: this._lastMcpStatusTime, cleared };
|
|
827
|
+
}
|
|
813
828
|
/** Inject a background work result into the session as silent follow-up context. */
|
|
814
829
|
injectPendingContext(sessionKey, userPrompt, result) {
|
|
815
830
|
const pending = this.pendingContext.get(sessionKey) ?? [];
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -9266,6 +9266,31 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
9266
9266
|
app.get('/api/mcp-status', gwHandler(async (gw, _req, res) => {
|
|
9267
9267
|
res.json(gw.getMcpStatus());
|
|
9268
9268
|
}));
|
|
9269
|
+
// PRD Phase 2.1: Reconnect a single MCP server. Clears the cached status so
|
|
9270
|
+
// the next query handshake repopulates it. The SDK doesn't expose a direct
|
|
9271
|
+
// reconnect call, so this is the closest equivalent: kick the cache.
|
|
9272
|
+
app.post('/api/mcp-servers/:name/reconnect', gwHandler(async (gw, req, res) => {
|
|
9273
|
+
const rawName = req.params.name;
|
|
9274
|
+
const name = Array.isArray(rawName) ? rawName[0] : rawName;
|
|
9275
|
+
if (!name) {
|
|
9276
|
+
res.status(400).json({ ok: false, error: 'name required' });
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
try {
|
|
9280
|
+
const result = gw.invalidateMcpStatus(String(name));
|
|
9281
|
+
res.json({
|
|
9282
|
+
ok: true,
|
|
9283
|
+
cleared: result.cleared,
|
|
9284
|
+
message: result.cleared
|
|
9285
|
+
? `Reconnect queued for "${name}" — status will refresh on the next query.`
|
|
9286
|
+
: `"${name}" had no cached status to clear; next query will populate it.`,
|
|
9287
|
+
status: result,
|
|
9288
|
+
});
|
|
9289
|
+
}
|
|
9290
|
+
catch (err) {
|
|
9291
|
+
res.status(500).json({ ok: false, error: String(err) });
|
|
9292
|
+
}
|
|
9293
|
+
}));
|
|
9269
9294
|
// ── Self-Improvement API ─────────────────────────────────────────
|
|
9270
9295
|
// ── MCP Server Management API ───────────────────────────────────────
|
|
9271
9296
|
app.get('/api/mcp-servers', (_req, res) => {
|
|
@@ -16364,6 +16389,9 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
16364
16389
|
<button class="build-tab-btn active" data-build-tab="crons" onclick="switchBuildTab('crons')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-primary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
|
|
16365
16390
|
<span style="margin-right:6px">📅</span>Tasks <span id="build-tab-cron-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
|
|
16366
16391
|
</button>
|
|
16392
|
+
<button class="build-tab-btn" data-build-tab="runs" onclick="switchBuildTab('runs')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
|
|
16393
|
+
<span style="margin-right:6px">🕒</span>Runs <span id="build-tab-runs-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
|
|
16394
|
+
</button>
|
|
16367
16395
|
<button class="build-tab-btn" data-build-tab="toolsmcp" onclick="switchBuildTab('toolsmcp')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
|
|
16368
16396
|
<span style="margin-right:6px">🧰</span>Tools & MCP <span id="build-tab-toolsmcp-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
|
|
16369
16397
|
</button>
|
|
@@ -16379,6 +16407,12 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
16379
16407
|
<div id="build-tab-crons" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
|
|
16380
16408
|
<div id="panel-cron"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading scheduled tasks…</div></div>
|
|
16381
16409
|
</div>
|
|
16410
|
+
<!-- ── PRD Phase 3: Run list ───────────────────────────────────────────
|
|
16411
|
+
Single table of every run across all tasks, with filters + saved
|
|
16412
|
+
views. Default view is "Failures (last 24h)". -->
|
|
16413
|
+
<div id="build-tab-runs" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
|
|
16414
|
+
<div id="panel-runs"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading run history…</div></div>
|
|
16415
|
+
</div>
|
|
16382
16416
|
<!-- ── PRD Phase 2: Tools & MCP catalog ────────────────────────────────
|
|
16383
16417
|
Read-only foundation in 1.18.81. Future slices: per-tool bindings,
|
|
16384
16418
|
Reconnect/Toggle/Edit actions, Approval Mode + Max-auto-runs config. -->
|
|
@@ -20319,6 +20353,77 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
20319
20353
|
|
|
20320
20354
|
<!-- (legacy standalone Preview modal removed in 1.18.70 — preview now lives as a tab inside the cron modal) -->
|
|
20321
20355
|
|
|
20356
|
+
<!-- ═══ MCP Server Edit Modal — PRD Phase 2.1 ═══ -->
|
|
20357
|
+
<div class="modal-overlay" id="mcp-edit-modal">
|
|
20358
|
+
<div class="modal" style="max-width:640px;width:96vw">
|
|
20359
|
+
<div class="modal-header">
|
|
20360
|
+
<h3 id="mcp-edit-title">Edit MCP Server</h3>
|
|
20361
|
+
<button class="btn-ghost btn-sm" onclick="closeMcpServerEditModal()">×</button>
|
|
20362
|
+
</div>
|
|
20363
|
+
<div class="modal-body" style="padding:18px">
|
|
20364
|
+
<div id="mcp-edit-readonly-note" style="display:none;margin-bottom:14px;padding:10px 12px;border-radius:6px;background:rgba(245,158,11,0.10);border:1px solid rgba(245,158,11,0.30);color:var(--yellow);font-size:12px">
|
|
20365
|
+
⚠ Auto-detected server. Edits to this config aren't persisted by Clementine — change it in the source file (Claude Desktop config, Claude Code settings, or extensions). Use the toggle on the catalog card to enable/disable.
|
|
20366
|
+
</div>
|
|
20367
|
+
<div class="form-group">
|
|
20368
|
+
<label class="form-label">Name</label>
|
|
20369
|
+
<input type="text" id="mcp-edit-name">
|
|
20370
|
+
<div class="form-hint">Identifier — cannot be renamed via this dialog.</div>
|
|
20371
|
+
</div>
|
|
20372
|
+
<div class="form-row">
|
|
20373
|
+
<div class="form-group">
|
|
20374
|
+
<label class="form-label">Transport</label>
|
|
20375
|
+
<select id="mcp-edit-type" onchange="syncMcpEditTransportRows()">
|
|
20376
|
+
<option value="stdio">stdio (local process)</option>
|
|
20377
|
+
<option value="http">http</option>
|
|
20378
|
+
<option value="sse">sse</option>
|
|
20379
|
+
</select>
|
|
20380
|
+
</div>
|
|
20381
|
+
<div class="form-group">
|
|
20382
|
+
<label class="form-label" style="display:flex;align-items:center;gap:8px">
|
|
20383
|
+
<input type="checkbox" id="mcp-edit-enabled"> Enabled
|
|
20384
|
+
</label>
|
|
20385
|
+
<div class="form-hint">Disable to remove this server from every task without deleting it.</div>
|
|
20386
|
+
</div>
|
|
20387
|
+
</div>
|
|
20388
|
+
<div class="form-group">
|
|
20389
|
+
<label class="form-label">Description</label>
|
|
20390
|
+
<input type="text" id="mcp-edit-description" placeholder="What this server does">
|
|
20391
|
+
</div>
|
|
20392
|
+
<!-- stdio-only fields -->
|
|
20393
|
+
<div id="mcp-edit-stdio-rows">
|
|
20394
|
+
<div class="form-group">
|
|
20395
|
+
<label class="form-label">Command</label>
|
|
20396
|
+
<input type="text" id="mcp-edit-command" placeholder="e.g. npx, python, ./bin/server">
|
|
20397
|
+
</div>
|
|
20398
|
+
<div class="form-group">
|
|
20399
|
+
<label class="form-label">Args <span style="color:var(--text-muted);font-weight:normal">(one per line)</span></label>
|
|
20400
|
+
<textarea id="mcp-edit-args" rows="3" placeholder="--port 3001" style="font-family:'JetBrains Mono',monospace;font-size:11px"></textarea>
|
|
20401
|
+
</div>
|
|
20402
|
+
<div class="form-group">
|
|
20403
|
+
<label class="form-label">Env <span style="color:var(--text-muted);font-weight:normal">(JSON object, optional)</span></label>
|
|
20404
|
+
<textarea id="mcp-edit-env" rows="3" placeholder='{ "API_KEY": "..." }' style="font-family:'JetBrains Mono',monospace;font-size:11px"></textarea>
|
|
20405
|
+
</div>
|
|
20406
|
+
</div>
|
|
20407
|
+
<!-- http/sse fields -->
|
|
20408
|
+
<div id="mcp-edit-http-rows" style="display:none">
|
|
20409
|
+
<div class="form-group">
|
|
20410
|
+
<label class="form-label">URL</label>
|
|
20411
|
+
<input type="text" id="mcp-edit-url" placeholder="https://example.com/mcp">
|
|
20412
|
+
</div>
|
|
20413
|
+
<div class="form-group">
|
|
20414
|
+
<label class="form-label">Headers <span style="color:var(--text-muted);font-weight:normal">(JSON object, optional)</span></label>
|
|
20415
|
+
<textarea id="mcp-edit-headers" rows="3" placeholder='{ "Authorization": "Bearer ..." }' style="font-family:'JetBrains Mono',monospace;font-size:11px"></textarea>
|
|
20416
|
+
</div>
|
|
20417
|
+
</div>
|
|
20418
|
+
</div>
|
|
20419
|
+
<div class="modal-footer">
|
|
20420
|
+
<span style="flex:1"></span>
|
|
20421
|
+
<button onclick="closeMcpServerEditModal()">Close</button>
|
|
20422
|
+
<button class="btn-primary" id="mcp-edit-save" onclick="saveMcpServerEdit()">Save</button>
|
|
20423
|
+
</div>
|
|
20424
|
+
</div>
|
|
20425
|
+
</div>
|
|
20426
|
+
|
|
20322
20427
|
<!-- ═══ Goal Modal ═══ -->
|
|
20323
20428
|
<div class="modal-overlay" id="goal-modal">
|
|
20324
20429
|
<div class="modal" style="width:520px">
|
|
@@ -21165,8 +21270,10 @@ function switchBuildTab(tab) {
|
|
|
21165
21270
|
// Always close any open workflow when changing tabs — switching context
|
|
21166
21271
|
// is a clean slate, not a stale node hanging on the canvas.
|
|
21167
21272
|
if (typeof closeBuilderCanvas === 'function') closeBuilderCanvas();
|
|
21168
|
-
|
|
21273
|
+
var runsPane = document.getElementById('build-tab-runs');
|
|
21274
|
+
// Default: hide the Tools & MCP + Runs panes unless we're explicitly on them.
|
|
21169
21275
|
if (toolsmcpPane && tab !== 'toolsmcp') toolsmcpPane.style.display = 'none';
|
|
21276
|
+
if (runsPane && tab !== 'runs') runsPane.style.display = 'none';
|
|
21170
21277
|
if (tab === 'toolsmcp') {
|
|
21171
21278
|
// PRD Phase 2: Tools & MCP catalog. Read-only foundation in 1.18.81.
|
|
21172
21279
|
if (workPane) workPane.style.display = 'none';
|
|
@@ -21179,6 +21286,18 @@ function switchBuildTab(tab) {
|
|
|
21179
21286
|
if (typeof refreshToolsMcpCatalog === 'function') refreshToolsMcpCatalog();
|
|
21180
21287
|
return;
|
|
21181
21288
|
}
|
|
21289
|
+
if (tab === 'runs') {
|
|
21290
|
+
// PRD Phase 3: Run list — every run across every task.
|
|
21291
|
+
if (workPane) workPane.style.display = 'none';
|
|
21292
|
+
if (cronPane) cronPane.style.display = 'none';
|
|
21293
|
+
if (tplPane) tplPane.style.display = 'none';
|
|
21294
|
+
if (runsPane) runsPane.style.display = 'block';
|
|
21295
|
+
if (headerStrip) headerStrip.style.display = 'none';
|
|
21296
|
+
if (usagePanel) usagePanel.style.display = 'none';
|
|
21297
|
+
if (newBtn) newBtn.style.display = 'none';
|
|
21298
|
+
if (typeof refreshRunList === 'function') refreshRunList();
|
|
21299
|
+
return;
|
|
21300
|
+
}
|
|
21182
21301
|
if (tab === 'templates') {
|
|
21183
21302
|
if (workPane) workPane.style.display = 'none';
|
|
21184
21303
|
if (cronPane) cronPane.style.display = 'none';
|
|
@@ -23557,6 +23676,245 @@ function renderRunningCard(item) {
|
|
|
23557
23676
|
+ '</div></div>';
|
|
23558
23677
|
}
|
|
23559
23678
|
|
|
23679
|
+
// ── PRD Phase 3: Run list ──────────────────────────────────────────────
|
|
23680
|
+
// Single sortable/filterable table of every CronRunEntry across all tasks.
|
|
23681
|
+
// Filters: status, task name, time window. Browser-local saved views.
|
|
23682
|
+
// Default view: "Failures (last 24h)". No new endpoints — reuses
|
|
23683
|
+
// /api/cron/runs (CronRunLog.readAllRecent).
|
|
23684
|
+
|
|
23685
|
+
var _runListState = {
|
|
23686
|
+
filterStatus: 'all', // 'all' | 'failed' | 'ok'
|
|
23687
|
+
filterWindow: '24h', // '24h' | '7d' | 'all'
|
|
23688
|
+
filterText: '', // free-text task name match
|
|
23689
|
+
data: [], // raw runs from /api/cron/runs
|
|
23690
|
+
};
|
|
23691
|
+
|
|
23692
|
+
function _runListLoadDefaultView() {
|
|
23693
|
+
// First-time visit: PRD §5.3 — default Saved View is "Failures (last 24h)".
|
|
23694
|
+
try {
|
|
23695
|
+
var raw = localStorage.getItem('runListView');
|
|
23696
|
+
if (raw) {
|
|
23697
|
+
var saved = JSON.parse(raw);
|
|
23698
|
+
_runListState.filterStatus = saved.filterStatus || 'all';
|
|
23699
|
+
_runListState.filterWindow = saved.filterWindow || '24h';
|
|
23700
|
+
_runListState.filterText = saved.filterText || '';
|
|
23701
|
+
return;
|
|
23702
|
+
}
|
|
23703
|
+
} catch (e) { /* ignore */ }
|
|
23704
|
+
// Default: failures, last 24h.
|
|
23705
|
+
_runListState.filterStatus = 'failed';
|
|
23706
|
+
_runListState.filterWindow = '24h';
|
|
23707
|
+
_runListState.filterText = '';
|
|
23708
|
+
}
|
|
23709
|
+
|
|
23710
|
+
function _runListSaveView() {
|
|
23711
|
+
try {
|
|
23712
|
+
localStorage.setItem('runListView', JSON.stringify({
|
|
23713
|
+
filterStatus: _runListState.filterStatus,
|
|
23714
|
+
filterWindow: _runListState.filterWindow,
|
|
23715
|
+
filterText: _runListState.filterText,
|
|
23716
|
+
}));
|
|
23717
|
+
} catch (e) { /* ignore */ }
|
|
23718
|
+
}
|
|
23719
|
+
|
|
23720
|
+
function _runListApplyFilters(runs) {
|
|
23721
|
+
var now = Date.now();
|
|
23722
|
+
var windowMs = _runListState.filterWindow === '24h' ? 24 * 60 * 60 * 1000
|
|
23723
|
+
: _runListState.filterWindow === '7d' ? 7 * 24 * 60 * 60 * 1000
|
|
23724
|
+
: Infinity;
|
|
23725
|
+
var query = (_runListState.filterText || '').trim().toLowerCase();
|
|
23726
|
+
return runs.filter(function(r) {
|
|
23727
|
+
if (_runListState.filterStatus === 'failed') {
|
|
23728
|
+
if (r.status !== 'error' && r.status !== 'timeout' && r.status !== 'lost') return false;
|
|
23729
|
+
} else if (_runListState.filterStatus === 'ok') {
|
|
23730
|
+
if (r.status !== 'ok') return false;
|
|
23731
|
+
}
|
|
23732
|
+
if (query && String(r.jobName || '').toLowerCase().indexOf(query) === -1) return false;
|
|
23733
|
+
if (windowMs !== Infinity && r.startedAt) {
|
|
23734
|
+
var age = now - new Date(r.startedAt).getTime();
|
|
23735
|
+
if (age > windowMs) return false;
|
|
23736
|
+
}
|
|
23737
|
+
return true;
|
|
23738
|
+
});
|
|
23739
|
+
}
|
|
23740
|
+
|
|
23741
|
+
async function refreshRunList() {
|
|
23742
|
+
var panel = document.getElementById('panel-runs');
|
|
23743
|
+
if (!panel) return;
|
|
23744
|
+
if (!_runListState.data.length) {
|
|
23745
|
+
_runListLoadDefaultView();
|
|
23746
|
+
}
|
|
23747
|
+
panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading run history…</div>';
|
|
23748
|
+
try {
|
|
23749
|
+
var r = await apiFetch('/api/cron/runs?limit=200');
|
|
23750
|
+
var d = await r.json();
|
|
23751
|
+
_runListState.data = (d && d.runs) || [];
|
|
23752
|
+
} catch (e) {
|
|
23753
|
+
panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--red)">Failed to load runs: ' + esc(String(e)) + '</div>';
|
|
23754
|
+
return;
|
|
23755
|
+
}
|
|
23756
|
+
panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23757
|
+
// Update tab count badge with total runs (not filtered count — that's
|
|
23758
|
+
// shown alongside the filter chips).
|
|
23759
|
+
var tabCount = document.getElementById('build-tab-runs-count');
|
|
23760
|
+
if (tabCount) {
|
|
23761
|
+
tabCount.textContent = _runListState.data.length;
|
|
23762
|
+
tabCount.style.display = _runListState.data.length > 0 ? '' : 'none';
|
|
23763
|
+
}
|
|
23764
|
+
}
|
|
23765
|
+
|
|
23766
|
+
function renderRunListBody(allRuns) {
|
|
23767
|
+
var filtered = _runListApplyFilters(allRuns);
|
|
23768
|
+
var html = '';
|
|
23769
|
+
// Header
|
|
23770
|
+
html += '<div style="margin-bottom:18px"><h2 style="margin:0 0 4px;font-size:18px;font-weight:600;color:var(--text-primary)">Runs</h2>'
|
|
23771
|
+
+ '<div style="font-size:12px;color:var(--text-muted)">'+ filtered.length +' of '+ allRuns.length +' total runs · default view: <strong>Failures (last 24h)</strong></div></div>';
|
|
23772
|
+
// Filter row — saved automatically to localStorage on change.
|
|
23773
|
+
html += '<div style="display:flex;gap:10px;align-items:center;margin-bottom:14px;flex-wrap:wrap">';
|
|
23774
|
+
html += _runListChip('Status', [
|
|
23775
|
+
{ value: 'all', label: 'All' },
|
|
23776
|
+
{ value: 'ok', label: 'OK' },
|
|
23777
|
+
{ value: 'failed', label: 'Failed' },
|
|
23778
|
+
], 'filterStatus');
|
|
23779
|
+
html += _runListChip('Window', [
|
|
23780
|
+
{ value: '24h', label: 'Last 24h' },
|
|
23781
|
+
{ value: '7d', label: 'Last 7 days' },
|
|
23782
|
+
{ value: 'all', label: 'All time' },
|
|
23783
|
+
], 'filterWindow');
|
|
23784
|
+
html += '<input type="search" placeholder="Filter by task name…" value="' + esc(_runListState.filterText) + '" oninput="onRunListSearch(this.value)" style="flex:1;min-width:200px;max-width:320px;padding:6px 10px;font-size:12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary)">';
|
|
23785
|
+
html += '<button class="btn-sm" onclick="resetRunListFilters()" style="font-size:11px">Reset to default</button>';
|
|
23786
|
+
html += '</div>';
|
|
23787
|
+
if (filtered.length === 0) {
|
|
23788
|
+
html += '<div class="empty-state" style="padding:36px 24px;text-align:center;color:var(--text-muted)"><div style="font-size:14px;margin-bottom:6px">No runs match the current filter.</div><div style="font-size:12px">Try widening the time window or clearing the task-name filter.</div></div>';
|
|
23789
|
+
return html;
|
|
23790
|
+
}
|
|
23791
|
+
// Table — same shape as the Recent History list on the Tasks page,
|
|
23792
|
+
// but sortable and with a Trigger column.
|
|
23793
|
+
html += '<div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius)">';
|
|
23794
|
+
html += '<div style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr) 90px minmax(180px,1fr) 90px auto;gap:10px;padding:8px 14px;border-bottom:1px solid var(--border);font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:500">'
|
|
23795
|
+
+ '<div></div><div title="Goal verdict">Goal</div><div>Task</div><div>Trigger</div><div>Started</div><div>Duration</div><div></div>'
|
|
23796
|
+
+ '</div>';
|
|
23797
|
+
for (var i = 0; i < filtered.length; i++) {
|
|
23798
|
+
var entry = filtered[i] || {};
|
|
23799
|
+
var status = entry.status || 'unknown';
|
|
23800
|
+
var statusColor, statusIcon;
|
|
23801
|
+
if (status === 'ok') { statusColor = 'var(--green)'; statusIcon = '✓'; }
|
|
23802
|
+
else if (status === 'error') { statusColor = 'var(--red)'; statusIcon = '✗'; }
|
|
23803
|
+
else if (status === 'retried') { statusColor = 'var(--yellow)'; statusIcon = '↻'; }
|
|
23804
|
+
else if (status === 'timeout') { statusColor = 'var(--yellow)'; statusIcon = '⏳'; }
|
|
23805
|
+
else if (status === 'lost') { statusColor = 'var(--red)'; statusIcon = '?'; }
|
|
23806
|
+
else if (status === 'running') { statusColor = 'var(--accent)'; statusIcon = '●'; }
|
|
23807
|
+
else if (status === 'skipped') { statusColor = 'var(--text-muted)'; statusIcon = '−'; }
|
|
23808
|
+
else { statusColor = 'var(--text-muted)'; statusIcon = '·'; }
|
|
23809
|
+
var jobName = entry.jobName || '(unknown)';
|
|
23810
|
+
var safeName = jsStr(jobName);
|
|
23811
|
+
var startedAt = entry.startedAt ? new Date(entry.startedAt) : null;
|
|
23812
|
+
var startedLabel = startedAt ? startedAt.toLocaleString() : '—';
|
|
23813
|
+
var durationLabel = entry.durationMs != null ? formatDurationMs(entry.durationMs) : '—';
|
|
23814
|
+
// Trigger heuristic until we persist it for real (Phase 3.1):
|
|
23815
|
+
// 'unleashed' mode + manual cron run = manual; otherwise scheduled.
|
|
23816
|
+
// The cron-running.json sidecar already carries pid which can hint at
|
|
23817
|
+
// manual but isn't on terminal entries. Best-effort label only.
|
|
23818
|
+
var triggerLabel = entry.attempt > 1 ? 'retry' : 'scheduled';
|
|
23819
|
+
var triggerColor = 'var(--text-muted)';
|
|
23820
|
+
// Goal cell
|
|
23821
|
+
var goalCell = '<div></div>';
|
|
23822
|
+
if (entry.goalCheck) {
|
|
23823
|
+
var gc = entry.goalCheck;
|
|
23824
|
+
var gIcon = gc.status === 'pass' ? '🎯' : gc.status === 'fail' ? '✗' : gc.status === 'error' ? '⚠' : '';
|
|
23825
|
+
var gColor = gc.status === 'pass' ? 'var(--green)' : gc.status === 'fail' ? 'var(--red)' : 'var(--yellow)';
|
|
23826
|
+
var gTip = gc.evaluatorReason || (Array.isArray(gc.schemaErrors) ? gc.schemaErrors.join('; ') : gc.status);
|
|
23827
|
+
goalCell = '<div style="color:' + gColor + ';font-size:13px;line-height:18px;text-align:center" title="' + esc(gTip) + '">' + gIcon + '</div>';
|
|
23828
|
+
}
|
|
23829
|
+
var preview = '';
|
|
23830
|
+
if (status === 'error' && entry.error) {
|
|
23831
|
+
preview = '<div style="font-size:11px;color:var(--red);margin-top:2px;word-break:break-word">' + esc(String(entry.error).slice(0, 140)) + '</div>';
|
|
23832
|
+
} else if (entry.outputPreview) {
|
|
23833
|
+
preview = '<div style="font-size:11px;color:var(--text-muted);margin-top:2px;word-break:break-word">' + esc(String(entry.outputPreview).slice(0, 120)) + '</div>';
|
|
23834
|
+
}
|
|
23835
|
+
html += '<div class="history-row" data-trace-job="' + esc(jobName) + '" style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr) 90px minmax(180px,1fr) 90px auto;gap:10px;align-items:start;padding:8px 14px;border-bottom:1px solid var(--border);cursor:pointer">'
|
|
23836
|
+
+ '<div style="color:' + statusColor + ';font-size:14px;line-height:18px;text-align:center" title="' + esc(status) + '">' + statusIcon + '</div>'
|
|
23837
|
+
+ goalCell
|
|
23838
|
+
+ '<div style="min-width:0">'
|
|
23839
|
+
+ '<div style="font-weight:500;color:var(--text-primary);font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + esc(jobName) + '">' + esc(jobName) + (entry.attempt > 1 ? ' · attempt ' + esc(entry.attempt) : '') + '</div>'
|
|
23840
|
+
+ preview
|
|
23841
|
+
+ '</div>'
|
|
23842
|
+
+ '<div style="font-size:11px;color:' + triggerColor + ';line-height:18px">' + esc(triggerLabel) + '</div>'
|
|
23843
|
+
+ '<div style="font-size:12px;color:var(--text-secondary);line-height:18px">' + esc(startedLabel) + '</div>'
|
|
23844
|
+
+ '<div style="font-size:12px;color:var(--text-muted);line-height:18px">' + esc(durationLabel) + '</div>'
|
|
23845
|
+
+ '<div style="display:flex;gap:6px;align-items:center"><button class="btn-sm" onclick="event.stopPropagation();openTraceViewer(\\x27' + safeName + '\\x27)" style="font-size:11px;padding:3px 8px">Trace</button></div>'
|
|
23846
|
+
+ '</div>';
|
|
23847
|
+
}
|
|
23848
|
+
html += '</div>';
|
|
23849
|
+
return html;
|
|
23850
|
+
}
|
|
23851
|
+
|
|
23852
|
+
function _runListChip(label, options, stateKey) {
|
|
23853
|
+
var current = _runListState[stateKey];
|
|
23854
|
+
var html = '<span style="display:inline-flex;align-items:center;gap:4px">';
|
|
23855
|
+
html += '<span style="font-size:11px;color:var(--text-muted);font-weight:500;text-transform:uppercase;letter-spacing:0.04em;margin-right:2px">' + esc(label) + '</span>';
|
|
23856
|
+
for (var i = 0; i < options.length; i++) {
|
|
23857
|
+
var o = options[i];
|
|
23858
|
+
var active = o.value === current;
|
|
23859
|
+
var bg = active ? 'var(--accent)' : 'var(--bg-secondary)';
|
|
23860
|
+
var fg = active ? '#fff' : 'var(--text-primary)';
|
|
23861
|
+
html += '<button class="btn-sm" onclick="onRunListChipClick(\\x27' + jsStr(stateKey) + '\\x27,\\x27' + jsStr(o.value) + '\\x27)" style="font-size:11px;padding:4px 10px;background:' + bg + ';color:' + fg + ';border:1px solid var(--border);border-radius:999px">' + esc(o.label) + '</button>';
|
|
23862
|
+
}
|
|
23863
|
+
html += '</span>';
|
|
23864
|
+
return html;
|
|
23865
|
+
}
|
|
23866
|
+
|
|
23867
|
+
function onRunListChipClick(key, value) {
|
|
23868
|
+
_runListState[key] = value;
|
|
23869
|
+
_runListSaveView();
|
|
23870
|
+
var panel = document.getElementById('panel-runs');
|
|
23871
|
+
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23872
|
+
}
|
|
23873
|
+
|
|
23874
|
+
function onRunListSearch(value) {
|
|
23875
|
+
_runListState.filterText = value;
|
|
23876
|
+
_runListSaveView();
|
|
23877
|
+
// Debounce-by-render: just re-render. Filtering is in-memory + cheap.
|
|
23878
|
+
var panel = document.getElementById('panel-runs');
|
|
23879
|
+
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23880
|
+
}
|
|
23881
|
+
|
|
23882
|
+
function resetRunListFilters() {
|
|
23883
|
+
_runListState.filterStatus = 'failed';
|
|
23884
|
+
_runListState.filterWindow = '24h';
|
|
23885
|
+
_runListState.filterText = '';
|
|
23886
|
+
_runListSaveView();
|
|
23887
|
+
var panel = document.getElementById('panel-runs');
|
|
23888
|
+
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23889
|
+
}
|
|
23890
|
+
|
|
23891
|
+
// Wire the panel's click handler so clicking anywhere on a row opens the
|
|
23892
|
+
// trace viewer (the row's data-trace-job attribute is what the existing
|
|
23893
|
+
// global panel-cron click handler reads).
|
|
23894
|
+
function _runListAttachClickHandler() {
|
|
23895
|
+
var pane = document.getElementById('build-tab-runs');
|
|
23896
|
+
if (!pane || pane._handlerAttached) return;
|
|
23897
|
+
pane.addEventListener('click', function(ev) {
|
|
23898
|
+
var t = ev.target;
|
|
23899
|
+
while (t && t !== pane) {
|
|
23900
|
+
if (t.dataset && t.dataset.traceJob) {
|
|
23901
|
+
openTraceViewer(t.dataset.traceJob);
|
|
23902
|
+
return;
|
|
23903
|
+
}
|
|
23904
|
+
t = t.parentElement;
|
|
23905
|
+
}
|
|
23906
|
+
});
|
|
23907
|
+
pane._handlerAttached = true;
|
|
23908
|
+
}
|
|
23909
|
+
// Attach once on first DOM ready — runs idempotent thanks to the flag.
|
|
23910
|
+
if (typeof document !== 'undefined') {
|
|
23911
|
+
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
23912
|
+
setTimeout(_runListAttachClickHandler, 0);
|
|
23913
|
+
} else {
|
|
23914
|
+
document.addEventListener('DOMContentLoaded', _runListAttachClickHandler);
|
|
23915
|
+
}
|
|
23916
|
+
}
|
|
23917
|
+
|
|
23560
23918
|
// ── PRD Phase 2: Tools & MCP catalog ──────────────────────────────────
|
|
23561
23919
|
// Read-only foundation in 1.18.81. Renders the four-card taxonomy:
|
|
23562
23920
|
// • Built-in — Claude SDK native tools (Read/Write/Bash/etc.)
|
|
@@ -23677,14 +24035,159 @@ function renderMcpCatalogCard(server, statusMap) {
|
|
|
23677
24035
|
+ (server.description ? '<div style="font-size:12px;color:var(--text-secondary);line-height:1.45">' + esc(String(server.description).slice(0, 240)) + '</div>' : '')
|
|
23678
24036
|
+ (lastError ? '<div style="font-size:11px;color:var(--red);background:rgba(239,68,68,0.06);padding:6px 8px;border-radius:4px;word-break:break-word">' + esc(String(lastError).slice(0, 200)) + '</div>' : '')
|
|
23679
24037
|
+ (lastChecked ? '<div style="font-size:11px;color:var(--text-muted)">Checked ' + esc(timeAgo(lastChecked)) + '</div>' : '')
|
|
23680
|
-
+ '<div style="display:flex;gap:6px;margin-top:4px">'
|
|
24038
|
+
+ '<div style="display:flex;gap:6px;margin-top:4px;flex-wrap:wrap">'
|
|
23681
24039
|
+ '<button class="btn-sm" onclick="toggleMcpServerEnabled(\\x27' + jsStr(name) + '\\x27,' + (enabled ? 'false' : 'true') + ')" title="' + (enabled ? 'Disable this MCP server for all tasks' : 'Enable this MCP server') + '">' + (enabled ? 'Disable' : 'Enable') + '</button>'
|
|
23682
|
-
+ '<button class="btn-sm"
|
|
24040
|
+
+ '<button class="btn-sm" onclick="reconnectMcpServer(\\x27' + jsStr(name) + '\\x27)" title="Clear cached status — next query will reconnect">Reconnect</button>'
|
|
24041
|
+
+ '<button class="btn-sm" onclick="openMcpServerEditModal(\\x27' + jsStr(name) + '\\x27)" title="View or edit this server\\x27s config">Edit</button>'
|
|
23683
24042
|
+ '</div>'
|
|
23684
24043
|
+ '</div>';
|
|
23685
24044
|
return html;
|
|
23686
24045
|
}
|
|
23687
24046
|
|
|
24047
|
+
// PRD Phase 2.1: Reconnect — invalidate cached status server-side, refresh
|
|
24048
|
+
// the catalog so the user sees the pending pill until the next query
|
|
24049
|
+
// handshake repopulates it.
|
|
24050
|
+
async function reconnectMcpServer(name) {
|
|
24051
|
+
try {
|
|
24052
|
+
var r = await apiFetch('/api/mcp-servers/' + encodeURIComponent(name) + '/reconnect', { method: 'POST' });
|
|
24053
|
+
var d = await r.json();
|
|
24054
|
+
if (!r.ok || d.ok === false) {
|
|
24055
|
+
toast('Reconnect failed: ' + (d.error || 'unknown'), 'error');
|
|
24056
|
+
return;
|
|
24057
|
+
}
|
|
24058
|
+
toast(d.message || 'Reconnect queued.', 'info');
|
|
24059
|
+
refreshToolsMcpCatalog();
|
|
24060
|
+
} catch (e) {
|
|
24061
|
+
toast('Reconnect failed: ' + String(e), 'error');
|
|
24062
|
+
}
|
|
24063
|
+
}
|
|
24064
|
+
|
|
24065
|
+
// PRD Phase 2.1: Edit modal. User-managed servers get an editable config
|
|
24066
|
+
// form; auto-detected servers render the same fields read-only with a note
|
|
24067
|
+
// pointing the user at the underlying config file.
|
|
24068
|
+
async function openMcpServerEditModal(name) {
|
|
24069
|
+
// Pull the latest server config — don't trust whatever was on the rendered card.
|
|
24070
|
+
var server;
|
|
24071
|
+
try {
|
|
24072
|
+
var r = await apiFetch('/api/mcp-servers');
|
|
24073
|
+
var d = await r.json();
|
|
24074
|
+
server = (d && d.servers || []).find(function(s) { return s.name === name; });
|
|
24075
|
+
} catch (e) {
|
|
24076
|
+
toast('Failed to load server: ' + String(e), 'error');
|
|
24077
|
+
return;
|
|
24078
|
+
}
|
|
24079
|
+
if (!server) { toast('Server "' + name + '" not found', 'error'); return; }
|
|
24080
|
+
var modal = document.getElementById('mcp-edit-modal');
|
|
24081
|
+
if (!modal) { toast('Edit modal missing from DOM', 'error'); return; }
|
|
24082
|
+
document.getElementById('mcp-edit-title').textContent = 'Edit: ' + name;
|
|
24083
|
+
var roNote = document.getElementById('mcp-edit-readonly-note');
|
|
24084
|
+
var isReadOnly = server.source === 'auto-detected';
|
|
24085
|
+
if (roNote) roNote.style.display = isReadOnly ? '' : 'none';
|
|
24086
|
+
// Set fields
|
|
24087
|
+
document.getElementById('mcp-edit-name').value = server.name || '';
|
|
24088
|
+
document.getElementById('mcp-edit-name').disabled = true; // never rename via this path
|
|
24089
|
+
document.getElementById('mcp-edit-type').value = server.type || 'stdio';
|
|
24090
|
+
document.getElementById('mcp-edit-type').disabled = isReadOnly;
|
|
24091
|
+
document.getElementById('mcp-edit-description').value = server.description || '';
|
|
24092
|
+
document.getElementById('mcp-edit-description').disabled = isReadOnly;
|
|
24093
|
+
document.getElementById('mcp-edit-enabled').checked = server.enabled !== false;
|
|
24094
|
+
document.getElementById('mcp-edit-enabled').disabled = isReadOnly;
|
|
24095
|
+
document.getElementById('mcp-edit-command').value = server.command || '';
|
|
24096
|
+
document.getElementById('mcp-edit-command').disabled = isReadOnly;
|
|
24097
|
+
document.getElementById('mcp-edit-args').value = Array.isArray(server.args) ? server.args.join('\\n') : '';
|
|
24098
|
+
document.getElementById('mcp-edit-args').disabled = isReadOnly;
|
|
24099
|
+
document.getElementById('mcp-edit-url').value = server.url || '';
|
|
24100
|
+
document.getElementById('mcp-edit-url').disabled = isReadOnly;
|
|
24101
|
+
document.getElementById('mcp-edit-headers').value = server.headers && Object.keys(server.headers).length ? JSON.stringify(server.headers, null, 2) : '';
|
|
24102
|
+
document.getElementById('mcp-edit-headers').disabled = isReadOnly;
|
|
24103
|
+
document.getElementById('mcp-edit-env').value = server.env && Object.keys(server.env).length ? JSON.stringify(server.env, null, 2) : '';
|
|
24104
|
+
document.getElementById('mcp-edit-env').disabled = isReadOnly;
|
|
24105
|
+
// Show the right transport fields
|
|
24106
|
+
syncMcpEditTransportRows();
|
|
24107
|
+
// Save button hidden for read-only auto-detected servers.
|
|
24108
|
+
var saveBtn = document.getElementById('mcp-edit-save');
|
|
24109
|
+
if (saveBtn) saveBtn.style.display = isReadOnly ? 'none' : '';
|
|
24110
|
+
modal.classList.add('show');
|
|
24111
|
+
}
|
|
24112
|
+
|
|
24113
|
+
function closeMcpServerEditModal() {
|
|
24114
|
+
var modal = document.getElementById('mcp-edit-modal');
|
|
24115
|
+
if (modal) modal.classList.remove('show');
|
|
24116
|
+
}
|
|
24117
|
+
|
|
24118
|
+
// Show only the row matching the selected transport (stdio vs http/sse).
|
|
24119
|
+
function syncMcpEditTransportRows() {
|
|
24120
|
+
var t = (document.getElementById('mcp-edit-type') || {}).value || 'stdio';
|
|
24121
|
+
var stdioRow = document.getElementById('mcp-edit-stdio-rows');
|
|
24122
|
+
var httpRow = document.getElementById('mcp-edit-http-rows');
|
|
24123
|
+
if (stdioRow) stdioRow.style.display = (t === 'stdio') ? '' : 'none';
|
|
24124
|
+
if (httpRow) httpRow.style.display = (t === 'http' || t === 'sse') ? '' : 'none';
|
|
24125
|
+
}
|
|
24126
|
+
|
|
24127
|
+
async function saveMcpServerEdit() {
|
|
24128
|
+
var name = (document.getElementById('mcp-edit-name') || {}).value;
|
|
24129
|
+
if (!name) return;
|
|
24130
|
+
var type = document.getElementById('mcp-edit-type').value;
|
|
24131
|
+
var description = document.getElementById('mcp-edit-description').value.trim();
|
|
24132
|
+
var enabled = !!document.getElementById('mcp-edit-enabled').checked;
|
|
24133
|
+
var commandRaw = document.getElementById('mcp-edit-command').value.trim();
|
|
24134
|
+
var argsRaw = document.getElementById('mcp-edit-args').value;
|
|
24135
|
+
var url = document.getElementById('mcp-edit-url').value.trim();
|
|
24136
|
+
var headersRaw = document.getElementById('mcp-edit-headers').value.trim();
|
|
24137
|
+
var envRaw = document.getElementById('mcp-edit-env').value.trim();
|
|
24138
|
+
|
|
24139
|
+
var headers, env;
|
|
24140
|
+
if (headersRaw) {
|
|
24141
|
+
try {
|
|
24142
|
+
headers = JSON.parse(headersRaw);
|
|
24143
|
+
if (!headers || typeof headers !== 'object' || Array.isArray(headers)) throw new Error('Headers must be a JSON object');
|
|
24144
|
+
} catch (e) {
|
|
24145
|
+
toast('Headers JSON invalid: ' + (e.message || String(e)), 'error');
|
|
24146
|
+
document.getElementById('mcp-edit-headers').focus();
|
|
24147
|
+
return;
|
|
24148
|
+
}
|
|
24149
|
+
}
|
|
24150
|
+
if (envRaw) {
|
|
24151
|
+
try {
|
|
24152
|
+
env = JSON.parse(envRaw);
|
|
24153
|
+
if (!env || typeof env !== 'object' || Array.isArray(env)) throw new Error('Env must be a JSON object');
|
|
24154
|
+
} catch (e) {
|
|
24155
|
+
toast('Env JSON invalid: ' + (e.message || String(e)), 'error');
|
|
24156
|
+
document.getElementById('mcp-edit-env').focus();
|
|
24157
|
+
return;
|
|
24158
|
+
}
|
|
24159
|
+
}
|
|
24160
|
+
var args = argsRaw.split(/\\r?\\n/).map(function(s){ return s.trim(); }).filter(Boolean);
|
|
24161
|
+
var body = { type: type, description: description, enabled: enabled };
|
|
24162
|
+
if (type === 'stdio') {
|
|
24163
|
+
if (!commandRaw) { toast('Command is required for stdio transport', 'error'); document.getElementById('mcp-edit-command').focus(); return; }
|
|
24164
|
+
body.command = commandRaw;
|
|
24165
|
+
body.args = args;
|
|
24166
|
+
if (env) body.env = env;
|
|
24167
|
+
} else {
|
|
24168
|
+
if (!url) { toast('URL is required for ' + type + ' transport', 'error'); document.getElementById('mcp-edit-url').focus(); return; }
|
|
24169
|
+
body.url = url;
|
|
24170
|
+
if (headers) body.headers = headers;
|
|
24171
|
+
}
|
|
24172
|
+
try {
|
|
24173
|
+
var r = await apiFetch('/api/mcp-servers/' + encodeURIComponent(name), {
|
|
24174
|
+
method: 'PUT',
|
|
24175
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24176
|
+
body: JSON.stringify(body),
|
|
24177
|
+
});
|
|
24178
|
+
var d = await r.json();
|
|
24179
|
+
if (!r.ok || d.error) {
|
|
24180
|
+
toast('Save failed: ' + (d.error || 'unknown'), 'error');
|
|
24181
|
+
return;
|
|
24182
|
+
}
|
|
24183
|
+
toast('Saved.', 'success');
|
|
24184
|
+
closeMcpServerEditModal();
|
|
24185
|
+
refreshToolsMcpCatalog();
|
|
24186
|
+
} catch (e) {
|
|
24187
|
+
toast('Save failed: ' + String(e), 'error');
|
|
24188
|
+
}
|
|
24189
|
+
}
|
|
24190
|
+
|
|
23688
24191
|
// PUT helper for the Toggle button. Lazy: re-fetches the catalog after
|
|
23689
24192
|
// the round-trip so the new state is reflected. Future slice will swap
|
|
23690
24193
|
// to optimistic update + rollback on error.
|
|
@@ -35354,6 +35857,14 @@ try {
|
|
|
35354
35857
|
if (evt.type === 'cron_complete' && evt.data && evt.data.job && typeof handleCronRunOnceComplete === 'function') {
|
|
35355
35858
|
try { handleCronRunOnceComplete(evt.data.job); } catch (err) { /* non-fatal */ }
|
|
35356
35859
|
}
|
|
35860
|
+
// PRD Phase 3: if the Runs tab is visible, refresh it too so a new
|
|
35861
|
+
// run appears at the top without a manual reload.
|
|
35862
|
+
if (currentPage === 'build' && typeof refreshRunList === 'function') {
|
|
35863
|
+
var runsPane = document.getElementById('build-tab-runs');
|
|
35864
|
+
if (runsPane && runsPane.style.display !== 'none') {
|
|
35865
|
+
try { refreshRunList(); } catch (err) { /* non-fatal */ }
|
|
35866
|
+
}
|
|
35867
|
+
}
|
|
35357
35868
|
}
|
|
35358
35869
|
// A delete on one tab should drop the card from every open dashboard
|
|
35359
35870
|
// without waiting for the next poll. cron_toggled is similar but lighter.
|
package/dist/gateway/router.d.ts
CHANGED
|
@@ -255,6 +255,15 @@ export declare class Gateway {
|
|
|
255
255
|
}>;
|
|
256
256
|
updatedAt: string;
|
|
257
257
|
};
|
|
258
|
+
/** PRD Phase 2.1: thin pass-through for the dashboard's Reconnect button. */
|
|
259
|
+
invalidateMcpStatus(serverName: string): {
|
|
260
|
+
servers: Array<{
|
|
261
|
+
name: string;
|
|
262
|
+
status: string;
|
|
263
|
+
}>;
|
|
264
|
+
updatedAt: string;
|
|
265
|
+
cleared: boolean;
|
|
266
|
+
};
|
|
258
267
|
getPresenceInfo(sessionKey: string): {
|
|
259
268
|
model: string;
|
|
260
269
|
project: string | null;
|
package/dist/gateway/router.js
CHANGED
|
@@ -2241,6 +2241,10 @@ export class Gateway {
|
|
|
2241
2241
|
getMcpStatus() {
|
|
2242
2242
|
return this.assistant.getMcpStatus();
|
|
2243
2243
|
}
|
|
2244
|
+
/** PRD Phase 2.1: thin pass-through for the dashboard's Reconnect button. */
|
|
2245
|
+
invalidateMcpStatus(serverName) {
|
|
2246
|
+
return this.assistant.invalidateMcpStatus(serverName);
|
|
2247
|
+
}
|
|
2244
2248
|
getPresenceInfo(sessionKey) {
|
|
2245
2249
|
const sess = this.sessions.get(sessionKey);
|
|
2246
2250
|
const modelName = sess?.model
|