clementine-agent 1.18.82 → 1.18.84
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 +0 -2
- package/dist/agent/assistant.js +23 -9
- package/dist/agent/run-agent.d.ts +15 -0
- package/dist/agent/run-agent.js +54 -0
- package/dist/cli/cron.js +7 -0
- package/dist/cli/dashboard.js +282 -3
- package/dist/gateway/cron-scheduler.js +5 -2
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
|
@@ -70,8 +70,6 @@ export declare class PersonalAssistant {
|
|
|
70
70
|
private memoryStore;
|
|
71
71
|
private _lastUserMessage?;
|
|
72
72
|
onSkillProposed: ((skill: import('../types.js').SkillDocument) => void) | null;
|
|
73
|
-
private _lastMcpStatus;
|
|
74
|
-
private _lastMcpStatusTime;
|
|
75
73
|
/** Terminal reason from the last SDK query — consumed by cron scheduler for precise error classification. */
|
|
76
74
|
private _lastTerminalReason?;
|
|
77
75
|
/** Per-session stall nudge — set after a query shows stall signals, consumed on the next query. */
|
package/dist/agent/assistant.js
CHANGED
|
@@ -17,6 +17,7 @@ import { BASE_DIR, PKG_DIR, VAULT_DIR, DAILY_NOTES_DIR, SOUL_FILE, AGENTS_FILE,
|
|
|
17
17
|
import { summarizeIntegrationStatus } from '../config/integrations-registry.js';
|
|
18
18
|
import { loadToolPreferences, computeAvailability, buildPromptInstruction, buildComposioStatusBlock, KNOWN_SERVICES, } from '../integrations/tool-preferences.js';
|
|
19
19
|
import { loadClaudeIntegrations } from './mcp-bridge.js';
|
|
20
|
+
import { getLatestMcpStatusSnapshot, recordMcpStatusFromSystemInit, invalidateMcpStatusEntry } from './run-agent.js';
|
|
20
21
|
import { detectFrustrationSignals, detectRepeatedTopics } from './insight-engine.js';
|
|
21
22
|
import { DEFAULT_CHANNEL_CAPABILITIES } from '../types.js';
|
|
22
23
|
import { enforceToolPermissions, getSecurityPrompt, getHeartbeatSecurityPrompt, getCronSecurityPrompt, getHeartbeatDisallowedTools, logToolUse, logAuditJsonl, } from './hooks.js';
|
|
@@ -710,8 +711,10 @@ export class PersonalAssistant {
|
|
|
710
711
|
memoryStore = null; // Typed as any — MemoryStore may not be available yet
|
|
711
712
|
_lastUserMessage;
|
|
712
713
|
onSkillProposed = null;
|
|
713
|
-
|
|
714
|
-
|
|
714
|
+
// PRD Phase 2 / 1.18.84: superseded by the shared module-level cache in
|
|
715
|
+
// run-agent.ts (getLatestMcpStatusSnapshot / recordMcpStatusFromSystemInit).
|
|
716
|
+
// The fields below were declared but never written pre-1.18.84; the
|
|
717
|
+
// module cache populates from every system/init message instead.
|
|
715
718
|
/** Terminal reason from the last SDK query — consumed by cron scheduler for precise error classification. */
|
|
716
719
|
_lastTerminalReason;
|
|
717
720
|
/** Per-session stall nudge — set after a query shows stall signals, consumed on the next query. */
|
|
@@ -808,7 +811,12 @@ export class PersonalAssistant {
|
|
|
808
811
|
this.onSkillProposed = cb;
|
|
809
812
|
}
|
|
810
813
|
getMcpStatus() {
|
|
811
|
-
|
|
814
|
+
// 1.18.84 correctness fix: delegate to the shared module-level cache.
|
|
815
|
+
// Pre-1.18.84 we returned this._lastMcpStatus, which was declared but
|
|
816
|
+
// never written — getMcpStatus() always returned empty. Now run-agent
|
|
817
|
+
// and assistant query streams record into a shared snapshot via
|
|
818
|
+
// recordMcpStatusFromSystemInit when the SDK init message lands.
|
|
819
|
+
return getLatestMcpStatusSnapshot();
|
|
812
820
|
}
|
|
813
821
|
/**
|
|
814
822
|
* PRD Phase 2.1: clear the cached status for one server so the next query
|
|
@@ -818,12 +826,9 @@ export class PersonalAssistant {
|
|
|
818
826
|
* stale error/auth state. Returns the post-clear cached snapshot.
|
|
819
827
|
*/
|
|
820
828
|
invalidateMcpStatus(serverName) {
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (cleared)
|
|
825
|
-
this._lastMcpStatusTime = new Date().toISOString();
|
|
826
|
-
return { servers: this._lastMcpStatus, updatedAt: this._lastMcpStatusTime, cleared };
|
|
829
|
+
const result = invalidateMcpStatusEntry(serverName);
|
|
830
|
+
const snapshot = getLatestMcpStatusSnapshot();
|
|
831
|
+
return { servers: snapshot.servers, updatedAt: snapshot.updatedAt, cleared: result.cleared };
|
|
827
832
|
}
|
|
828
833
|
/** Inject a background work result into the session as silent follow-up context. */
|
|
829
834
|
injectPendingContext(sessionKey, userPrompt, result) {
|
|
@@ -2999,6 +3004,15 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2999
3004
|
const trace = [];
|
|
3000
3005
|
const stream = query({ prompt, options: sdkOptions });
|
|
3001
3006
|
for await (const message of stream) {
|
|
3007
|
+
// 1.18.84 correctness: capture MCP server status from SDK init.
|
|
3008
|
+
if (message.type === 'system' && message.subtype === 'init') {
|
|
3009
|
+
const mcpServersRaw = message.mcp_servers;
|
|
3010
|
+
if (mcpServersRaw)
|
|
3011
|
+
try {
|
|
3012
|
+
recordMcpStatusFromSystemInit(mcpServersRaw);
|
|
3013
|
+
}
|
|
3014
|
+
catch { /* non-fatal */ }
|
|
3015
|
+
}
|
|
3002
3016
|
if (message.type === 'assistant') {
|
|
3003
3017
|
const blocks = getContentBlocks(message);
|
|
3004
3018
|
for (const block of blocks) {
|
|
@@ -22,6 +22,21 @@
|
|
|
22
22
|
* long-task preflight, NO mode=unleashed wrapper.
|
|
23
23
|
*/
|
|
24
24
|
import { type AgentDefinition } from '@anthropic-ai/claude-agent-sdk';
|
|
25
|
+
/** Read the latest MCP status snapshot. Safe to call from any module. */
|
|
26
|
+
export declare function getLatestMcpStatusSnapshot(): {
|
|
27
|
+
servers: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
status: string;
|
|
30
|
+
}>;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
};
|
|
33
|
+
/** Write a fresh snapshot. Called from system/init handlers. */
|
|
34
|
+
export declare function recordMcpStatusFromSystemInit(rawMcpServers: unknown): void;
|
|
35
|
+
/** Drop one server from the cache so the next query repopulates it. */
|
|
36
|
+
export declare function invalidateMcpStatusEntry(name: string): {
|
|
37
|
+
cleared: boolean;
|
|
38
|
+
updatedAt: string;
|
|
39
|
+
};
|
|
25
40
|
import type { AgentProfile } from '../types.js';
|
|
26
41
|
import type { AgentManager } from './agent-manager.js';
|
|
27
42
|
import type { MemoryStore } from '../memory/store.js';
|
package/dist/agent/run-agent.js
CHANGED
|
@@ -24,6 +24,49 @@
|
|
|
24
24
|
import path from 'node:path';
|
|
25
25
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
26
26
|
import pino from 'pino';
|
|
27
|
+
/**
|
|
28
|
+
* Module-level cache of MCP server statuses from the most recent SDK
|
|
29
|
+
* init message. Populated by every runAgent / PersonalAssistant query
|
|
30
|
+
* stream that captures `system/init`. Read by Assistant.getMcpStatus()
|
|
31
|
+
* and the dashboard's Tools & MCP catalog page.
|
|
32
|
+
*
|
|
33
|
+
* Pre-1.18.84 the assistant declared a private _lastMcpStatus but no
|
|
34
|
+
* code wrote to it — getMcpStatus() always returned empty, making the
|
|
35
|
+
* catalog status pills misleading. The shared module cache fixes that
|
|
36
|
+
* without coupling assistant.ts to runAgent's stream loop.
|
|
37
|
+
*/
|
|
38
|
+
let _lastMcpStatusSnapshot = {
|
|
39
|
+
servers: [],
|
|
40
|
+
updatedAt: '',
|
|
41
|
+
};
|
|
42
|
+
/** Read the latest MCP status snapshot. Safe to call from any module. */
|
|
43
|
+
export function getLatestMcpStatusSnapshot() {
|
|
44
|
+
return { servers: [..._lastMcpStatusSnapshot.servers], updatedAt: _lastMcpStatusSnapshot.updatedAt };
|
|
45
|
+
}
|
|
46
|
+
/** Write a fresh snapshot. Called from system/init handlers. */
|
|
47
|
+
export function recordMcpStatusFromSystemInit(rawMcpServers) {
|
|
48
|
+
if (!Array.isArray(rawMcpServers))
|
|
49
|
+
return;
|
|
50
|
+
const servers = [];
|
|
51
|
+
for (const entry of rawMcpServers) {
|
|
52
|
+
if (!entry || typeof entry !== 'object')
|
|
53
|
+
continue;
|
|
54
|
+
const e = entry;
|
|
55
|
+
if (typeof e.name !== 'string' || !e.name)
|
|
56
|
+
continue;
|
|
57
|
+
servers.push({ name: e.name, status: typeof e.status === 'string' ? e.status : 'unknown' });
|
|
58
|
+
}
|
|
59
|
+
_lastMcpStatusSnapshot = { servers, updatedAt: new Date().toISOString() };
|
|
60
|
+
}
|
|
61
|
+
/** Drop one server from the cache so the next query repopulates it. */
|
|
62
|
+
export function invalidateMcpStatusEntry(name) {
|
|
63
|
+
const before = _lastMcpStatusSnapshot.servers.length;
|
|
64
|
+
_lastMcpStatusSnapshot = {
|
|
65
|
+
servers: _lastMcpStatusSnapshot.servers.filter((s) => s.name !== name),
|
|
66
|
+
updatedAt: new Date().toISOString(),
|
|
67
|
+
};
|
|
68
|
+
return { cleared: _lastMcpStatusSnapshot.servers.length < before, updatedAt: _lastMcpStatusSnapshot.updatedAt };
|
|
69
|
+
}
|
|
27
70
|
import { BASE_DIR, PKG_DIR, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY as CONFIG_ANTHROPIC_API_KEY, normalizeClaudeSdkOptionsForOneMillionContext, } from '../config.js';
|
|
28
71
|
import { buildAgentMap } from './agent-definitions.js';
|
|
29
72
|
const MCP_SERVER_SCRIPT = path.join(PKG_DIR, 'dist', 'tools', 'mcp-server.js');
|
|
@@ -229,6 +272,17 @@ export async function runAgent(prompt, opts) {
|
|
|
229
272
|
for await (const message of stream) {
|
|
230
273
|
if (message.type === 'system' && message.subtype === 'init') {
|
|
231
274
|
sessionId = message.session_id ?? '';
|
|
275
|
+
// PRD Phase 2 / 1.18.84 correctness: capture the SDK-reported MCP
|
|
276
|
+
// server status so getMcpStatus() (and the dashboard's Tools & MCP
|
|
277
|
+
// catalog) actually has data. The init message includes
|
|
278
|
+
// mcp_servers: Array<{ name, status }> per the SDK protocol.
|
|
279
|
+
const mcpServersRaw = message.mcp_servers;
|
|
280
|
+
if (mcpServersRaw) {
|
|
281
|
+
try {
|
|
282
|
+
recordMcpStatusFromSystemInit(mcpServersRaw);
|
|
283
|
+
}
|
|
284
|
+
catch { /* non-fatal */ }
|
|
285
|
+
}
|
|
232
286
|
logger.debug({ sessionKey: opts.sessionKey, sdkSessionId: sessionId }, 'runAgent: SDK session initialized');
|
|
233
287
|
continue;
|
|
234
288
|
}
|
package/dist/cli/cron.js
CHANGED
|
@@ -140,6 +140,10 @@ export async function cmdCronRun(jobName) {
|
|
|
140
140
|
try {
|
|
141
141
|
const response = await gateway.handleCronJob(job.name, job.prompt, job.tier, job.maxTurns, job.model, job.workDir, job.mode, job.maxHours);
|
|
142
142
|
const finishedAt = new Date();
|
|
143
|
+
// 1.18.84: trigger source comes from the CRON_RUN_TRIGGER env var set
|
|
144
|
+
// by the dashboard's manual-run endpoint. Defaults to 'scheduled' for
|
|
145
|
+
// any other invocation path (CLI, daemon-internal callsites).
|
|
146
|
+
const trigger = process.env.CRON_RUN_TRIGGER || 'scheduled';
|
|
143
147
|
const entry = {
|
|
144
148
|
jobName: job.name,
|
|
145
149
|
startedAt: startedAt.toISOString(),
|
|
@@ -148,6 +152,7 @@ export async function cmdCronRun(jobName) {
|
|
|
148
152
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
149
153
|
attempt: 1,
|
|
150
154
|
outputPreview: response ? response.slice(0, 200) : undefined,
|
|
155
|
+
trigger,
|
|
151
156
|
};
|
|
152
157
|
// PRD Phase 1.1: goal-orientation evaluator (mirrors the daemon path).
|
|
153
158
|
if (job.successSchema || (job.successCriteriaText && job.successCriteriaText.trim())) {
|
|
@@ -170,6 +175,7 @@ export async function cmdCronRun(jobName) {
|
|
|
170
175
|
}
|
|
171
176
|
catch (err) {
|
|
172
177
|
const finishedAt = new Date();
|
|
178
|
+
const trigger = process.env.CRON_RUN_TRIGGER || 'scheduled';
|
|
173
179
|
runLog.append({
|
|
174
180
|
jobName: job.name,
|
|
175
181
|
startedAt: startedAt.toISOString(),
|
|
@@ -179,6 +185,7 @@ export async function cmdCronRun(jobName) {
|
|
|
179
185
|
error: String(err).slice(0, 500),
|
|
180
186
|
errorType: classifyError(err),
|
|
181
187
|
attempt: 1,
|
|
188
|
+
trigger,
|
|
182
189
|
});
|
|
183
190
|
console.error(`Error: ${err}`);
|
|
184
191
|
process.exit(1);
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -4613,7 +4613,8 @@ export async function cmdDashboard(opts) {
|
|
|
4613
4613
|
detached: true,
|
|
4614
4614
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4615
4615
|
cwd: BASE_DIR,
|
|
4616
|
-
|
|
4616
|
+
// 1.18.84: pass the trigger source so cron.ts stamps it on the run entry.
|
|
4617
|
+
env: { ...process.env, CLEMENTINE_HOME: BASE_DIR, CRON_RUN_TRIGGER: 'manual' },
|
|
4617
4618
|
});
|
|
4618
4619
|
// Capture stderr for error reporting
|
|
4619
4620
|
let stderr = '';
|
|
@@ -16389,6 +16390,9 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
16389
16390
|
<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">
|
|
16390
16391
|
<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>
|
|
16391
16392
|
</button>
|
|
16393
|
+
<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">
|
|
16394
|
+
<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>
|
|
16395
|
+
</button>
|
|
16392
16396
|
<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">
|
|
16393
16397
|
<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>
|
|
16394
16398
|
</button>
|
|
@@ -16404,6 +16408,12 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
16404
16408
|
<div id="build-tab-crons" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
|
|
16405
16409
|
<div id="panel-cron"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading scheduled tasks…</div></div>
|
|
16406
16410
|
</div>
|
|
16411
|
+
<!-- ── PRD Phase 3: Run list ───────────────────────────────────────────
|
|
16412
|
+
Single table of every run across all tasks, with filters + saved
|
|
16413
|
+
views. Default view is "Failures (last 24h)". -->
|
|
16414
|
+
<div id="build-tab-runs" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
|
|
16415
|
+
<div id="panel-runs"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading run history…</div></div>
|
|
16416
|
+
</div>
|
|
16407
16417
|
<!-- ── PRD Phase 2: Tools & MCP catalog ────────────────────────────────
|
|
16408
16418
|
Read-only foundation in 1.18.81. Future slices: per-tool bindings,
|
|
16409
16419
|
Reconnect/Toggle/Edit actions, Approval Mode + Max-auto-runs config. -->
|
|
@@ -21261,8 +21271,10 @@ function switchBuildTab(tab) {
|
|
|
21261
21271
|
// Always close any open workflow when changing tabs — switching context
|
|
21262
21272
|
// is a clean slate, not a stale node hanging on the canvas.
|
|
21263
21273
|
if (typeof closeBuilderCanvas === 'function') closeBuilderCanvas();
|
|
21264
|
-
|
|
21274
|
+
var runsPane = document.getElementById('build-tab-runs');
|
|
21275
|
+
// Default: hide the Tools & MCP + Runs panes unless we're explicitly on them.
|
|
21265
21276
|
if (toolsmcpPane && tab !== 'toolsmcp') toolsmcpPane.style.display = 'none';
|
|
21277
|
+
if (runsPane && tab !== 'runs') runsPane.style.display = 'none';
|
|
21266
21278
|
if (tab === 'toolsmcp') {
|
|
21267
21279
|
// PRD Phase 2: Tools & MCP catalog. Read-only foundation in 1.18.81.
|
|
21268
21280
|
if (workPane) workPane.style.display = 'none';
|
|
@@ -21275,6 +21287,18 @@ function switchBuildTab(tab) {
|
|
|
21275
21287
|
if (typeof refreshToolsMcpCatalog === 'function') refreshToolsMcpCatalog();
|
|
21276
21288
|
return;
|
|
21277
21289
|
}
|
|
21290
|
+
if (tab === 'runs') {
|
|
21291
|
+
// PRD Phase 3: Run list — every run across every task.
|
|
21292
|
+
if (workPane) workPane.style.display = 'none';
|
|
21293
|
+
if (cronPane) cronPane.style.display = 'none';
|
|
21294
|
+
if (tplPane) tplPane.style.display = 'none';
|
|
21295
|
+
if (runsPane) runsPane.style.display = 'block';
|
|
21296
|
+
if (headerStrip) headerStrip.style.display = 'none';
|
|
21297
|
+
if (usagePanel) usagePanel.style.display = 'none';
|
|
21298
|
+
if (newBtn) newBtn.style.display = 'none';
|
|
21299
|
+
if (typeof refreshRunList === 'function') refreshRunList();
|
|
21300
|
+
return;
|
|
21301
|
+
}
|
|
21278
21302
|
if (tab === 'templates') {
|
|
21279
21303
|
if (workPane) workPane.style.display = 'none';
|
|
21280
21304
|
if (cronPane) cronPane.style.display = 'none';
|
|
@@ -23653,6 +23677,246 @@ function renderRunningCard(item) {
|
|
|
23653
23677
|
+ '</div></div>';
|
|
23654
23678
|
}
|
|
23655
23679
|
|
|
23680
|
+
// ── PRD Phase 3: Run list ──────────────────────────────────────────────
|
|
23681
|
+
// Single sortable/filterable table of every CronRunEntry across all tasks.
|
|
23682
|
+
// Filters: status, task name, time window. Browser-local saved views.
|
|
23683
|
+
// Default view: "Failures (last 24h)". No new endpoints — reuses
|
|
23684
|
+
// /api/cron/runs (CronRunLog.readAllRecent).
|
|
23685
|
+
|
|
23686
|
+
var _runListState = {
|
|
23687
|
+
filterStatus: 'all', // 'all' | 'failed' | 'ok'
|
|
23688
|
+
filterWindow: '24h', // '24h' | '7d' | 'all'
|
|
23689
|
+
filterText: '', // free-text task name match
|
|
23690
|
+
data: [], // raw runs from /api/cron/runs
|
|
23691
|
+
};
|
|
23692
|
+
|
|
23693
|
+
function _runListLoadDefaultView() {
|
|
23694
|
+
// First-time visit: PRD §5.3 — default Saved View is "Failures (last 24h)".
|
|
23695
|
+
try {
|
|
23696
|
+
var raw = localStorage.getItem('runListView');
|
|
23697
|
+
if (raw) {
|
|
23698
|
+
var saved = JSON.parse(raw);
|
|
23699
|
+
_runListState.filterStatus = saved.filterStatus || 'all';
|
|
23700
|
+
_runListState.filterWindow = saved.filterWindow || '24h';
|
|
23701
|
+
_runListState.filterText = saved.filterText || '';
|
|
23702
|
+
return;
|
|
23703
|
+
}
|
|
23704
|
+
} catch (e) { /* ignore */ }
|
|
23705
|
+
// Default: failures, last 24h.
|
|
23706
|
+
_runListState.filterStatus = 'failed';
|
|
23707
|
+
_runListState.filterWindow = '24h';
|
|
23708
|
+
_runListState.filterText = '';
|
|
23709
|
+
}
|
|
23710
|
+
|
|
23711
|
+
function _runListSaveView() {
|
|
23712
|
+
try {
|
|
23713
|
+
localStorage.setItem('runListView', JSON.stringify({
|
|
23714
|
+
filterStatus: _runListState.filterStatus,
|
|
23715
|
+
filterWindow: _runListState.filterWindow,
|
|
23716
|
+
filterText: _runListState.filterText,
|
|
23717
|
+
}));
|
|
23718
|
+
} catch (e) { /* ignore */ }
|
|
23719
|
+
}
|
|
23720
|
+
|
|
23721
|
+
function _runListApplyFilters(runs) {
|
|
23722
|
+
var now = Date.now();
|
|
23723
|
+
var windowMs = _runListState.filterWindow === '24h' ? 24 * 60 * 60 * 1000
|
|
23724
|
+
: _runListState.filterWindow === '7d' ? 7 * 24 * 60 * 60 * 1000
|
|
23725
|
+
: Infinity;
|
|
23726
|
+
var query = (_runListState.filterText || '').trim().toLowerCase();
|
|
23727
|
+
return runs.filter(function(r) {
|
|
23728
|
+
if (_runListState.filterStatus === 'failed') {
|
|
23729
|
+
if (r.status !== 'error' && r.status !== 'timeout' && r.status !== 'lost') return false;
|
|
23730
|
+
} else if (_runListState.filterStatus === 'ok') {
|
|
23731
|
+
if (r.status !== 'ok') return false;
|
|
23732
|
+
}
|
|
23733
|
+
if (query && String(r.jobName || '').toLowerCase().indexOf(query) === -1) return false;
|
|
23734
|
+
if (windowMs !== Infinity && r.startedAt) {
|
|
23735
|
+
var age = now - new Date(r.startedAt).getTime();
|
|
23736
|
+
if (age > windowMs) return false;
|
|
23737
|
+
}
|
|
23738
|
+
return true;
|
|
23739
|
+
});
|
|
23740
|
+
}
|
|
23741
|
+
|
|
23742
|
+
async function refreshRunList() {
|
|
23743
|
+
var panel = document.getElementById('panel-runs');
|
|
23744
|
+
if (!panel) return;
|
|
23745
|
+
if (!_runListState.data.length) {
|
|
23746
|
+
_runListLoadDefaultView();
|
|
23747
|
+
}
|
|
23748
|
+
panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading run history…</div>';
|
|
23749
|
+
try {
|
|
23750
|
+
var r = await apiFetch('/api/cron/runs?limit=200');
|
|
23751
|
+
var d = await r.json();
|
|
23752
|
+
_runListState.data = (d && d.runs) || [];
|
|
23753
|
+
} catch (e) {
|
|
23754
|
+
panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--red)">Failed to load runs: ' + esc(String(e)) + '</div>';
|
|
23755
|
+
return;
|
|
23756
|
+
}
|
|
23757
|
+
panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23758
|
+
// Update tab count badge with total runs (not filtered count — that's
|
|
23759
|
+
// shown alongside the filter chips).
|
|
23760
|
+
var tabCount = document.getElementById('build-tab-runs-count');
|
|
23761
|
+
if (tabCount) {
|
|
23762
|
+
tabCount.textContent = _runListState.data.length;
|
|
23763
|
+
tabCount.style.display = _runListState.data.length > 0 ? '' : 'none';
|
|
23764
|
+
}
|
|
23765
|
+
}
|
|
23766
|
+
|
|
23767
|
+
function renderRunListBody(allRuns) {
|
|
23768
|
+
var filtered = _runListApplyFilters(allRuns);
|
|
23769
|
+
var html = '';
|
|
23770
|
+
// Header
|
|
23771
|
+
html += '<div style="margin-bottom:18px"><h2 style="margin:0 0 4px;font-size:18px;font-weight:600;color:var(--text-primary)">Runs</h2>'
|
|
23772
|
+
+ '<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>';
|
|
23773
|
+
// Filter row — saved automatically to localStorage on change.
|
|
23774
|
+
html += '<div style="display:flex;gap:10px;align-items:center;margin-bottom:14px;flex-wrap:wrap">';
|
|
23775
|
+
html += _runListChip('Status', [
|
|
23776
|
+
{ value: 'all', label: 'All' },
|
|
23777
|
+
{ value: 'ok', label: 'OK' },
|
|
23778
|
+
{ value: 'failed', label: 'Failed' },
|
|
23779
|
+
], 'filterStatus');
|
|
23780
|
+
html += _runListChip('Window', [
|
|
23781
|
+
{ value: '24h', label: 'Last 24h' },
|
|
23782
|
+
{ value: '7d', label: 'Last 7 days' },
|
|
23783
|
+
{ value: 'all', label: 'All time' },
|
|
23784
|
+
], 'filterWindow');
|
|
23785
|
+
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)">';
|
|
23786
|
+
html += '<button class="btn-sm" onclick="resetRunListFilters()" style="font-size:11px">Reset to default</button>';
|
|
23787
|
+
html += '</div>';
|
|
23788
|
+
if (filtered.length === 0) {
|
|
23789
|
+
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>';
|
|
23790
|
+
return html;
|
|
23791
|
+
}
|
|
23792
|
+
// Table — same shape as the Recent History list on the Tasks page,
|
|
23793
|
+
// but sortable and with a Trigger column.
|
|
23794
|
+
html += '<div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius)">';
|
|
23795
|
+
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">'
|
|
23796
|
+
+ '<div></div><div title="Goal verdict">Goal</div><div>Task</div><div>Trigger</div><div>Started</div><div>Duration</div><div></div>'
|
|
23797
|
+
+ '</div>';
|
|
23798
|
+
for (var i = 0; i < filtered.length; i++) {
|
|
23799
|
+
var entry = filtered[i] || {};
|
|
23800
|
+
var status = entry.status || 'unknown';
|
|
23801
|
+
var statusColor, statusIcon;
|
|
23802
|
+
if (status === 'ok') { statusColor = 'var(--green)'; statusIcon = '✓'; }
|
|
23803
|
+
else if (status === 'error') { statusColor = 'var(--red)'; statusIcon = '✗'; }
|
|
23804
|
+
else if (status === 'retried') { statusColor = 'var(--yellow)'; statusIcon = '↻'; }
|
|
23805
|
+
else if (status === 'timeout') { statusColor = 'var(--yellow)'; statusIcon = '⏳'; }
|
|
23806
|
+
else if (status === 'lost') { statusColor = 'var(--red)'; statusIcon = '?'; }
|
|
23807
|
+
else if (status === 'running') { statusColor = 'var(--accent)'; statusIcon = '●'; }
|
|
23808
|
+
else if (status === 'skipped') { statusColor = 'var(--text-muted)'; statusIcon = '−'; }
|
|
23809
|
+
else { statusColor = 'var(--text-muted)'; statusIcon = '·'; }
|
|
23810
|
+
var jobName = entry.jobName || '(unknown)';
|
|
23811
|
+
var safeName = jsStr(jobName);
|
|
23812
|
+
var startedAt = entry.startedAt ? new Date(entry.startedAt) : null;
|
|
23813
|
+
var startedLabel = startedAt ? startedAt.toLocaleString() : '—';
|
|
23814
|
+
var durationLabel = entry.durationMs != null ? formatDurationMs(entry.durationMs) : '—';
|
|
23815
|
+
// 1.18.84: real persisted trigger field. Falls back to a heuristic for
|
|
23816
|
+
// pre-1.18.84 run entries that don't have the field set.
|
|
23817
|
+
var triggerLabel = entry.trigger || (entry.attempt > 1 ? 'retry' : 'scheduled');
|
|
23818
|
+
var triggerColor = entry.trigger === 'manual' ? 'var(--accent)'
|
|
23819
|
+
: entry.trigger === 'after' ? 'var(--purple)'
|
|
23820
|
+
: entry.trigger === 'discord' ? 'var(--blue)'
|
|
23821
|
+
: 'var(--text-muted)';
|
|
23822
|
+
// Goal cell
|
|
23823
|
+
var goalCell = '<div></div>';
|
|
23824
|
+
if (entry.goalCheck) {
|
|
23825
|
+
var gc = entry.goalCheck;
|
|
23826
|
+
var gIcon = gc.status === 'pass' ? '🎯' : gc.status === 'fail' ? '✗' : gc.status === 'error' ? '⚠' : '';
|
|
23827
|
+
var gColor = gc.status === 'pass' ? 'var(--green)' : gc.status === 'fail' ? 'var(--red)' : 'var(--yellow)';
|
|
23828
|
+
var gTip = gc.evaluatorReason || (Array.isArray(gc.schemaErrors) ? gc.schemaErrors.join('; ') : gc.status);
|
|
23829
|
+
goalCell = '<div style="color:' + gColor + ';font-size:13px;line-height:18px;text-align:center" title="' + esc(gTip) + '">' + gIcon + '</div>';
|
|
23830
|
+
}
|
|
23831
|
+
var preview = '';
|
|
23832
|
+
if (status === 'error' && entry.error) {
|
|
23833
|
+
preview = '<div style="font-size:11px;color:var(--red);margin-top:2px;word-break:break-word">' + esc(String(entry.error).slice(0, 140)) + '</div>';
|
|
23834
|
+
} else if (entry.outputPreview) {
|
|
23835
|
+
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>';
|
|
23836
|
+
}
|
|
23837
|
+
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">'
|
|
23838
|
+
+ '<div style="color:' + statusColor + ';font-size:14px;line-height:18px;text-align:center" title="' + esc(status) + '">' + statusIcon + '</div>'
|
|
23839
|
+
+ goalCell
|
|
23840
|
+
+ '<div style="min-width:0">'
|
|
23841
|
+
+ '<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>'
|
|
23842
|
+
+ preview
|
|
23843
|
+
+ '</div>'
|
|
23844
|
+
+ '<div style="font-size:11px;color:' + triggerColor + ';line-height:18px">' + esc(triggerLabel) + '</div>'
|
|
23845
|
+
+ '<div style="font-size:12px;color:var(--text-secondary);line-height:18px">' + esc(startedLabel) + '</div>'
|
|
23846
|
+
+ '<div style="font-size:12px;color:var(--text-muted);line-height:18px">' + esc(durationLabel) + '</div>'
|
|
23847
|
+
+ '<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>'
|
|
23848
|
+
+ '</div>';
|
|
23849
|
+
}
|
|
23850
|
+
html += '</div>';
|
|
23851
|
+
return html;
|
|
23852
|
+
}
|
|
23853
|
+
|
|
23854
|
+
function _runListChip(label, options, stateKey) {
|
|
23855
|
+
var current = _runListState[stateKey];
|
|
23856
|
+
var html = '<span style="display:inline-flex;align-items:center;gap:4px">';
|
|
23857
|
+
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>';
|
|
23858
|
+
for (var i = 0; i < options.length; i++) {
|
|
23859
|
+
var o = options[i];
|
|
23860
|
+
var active = o.value === current;
|
|
23861
|
+
var bg = active ? 'var(--accent)' : 'var(--bg-secondary)';
|
|
23862
|
+
var fg = active ? '#fff' : 'var(--text-primary)';
|
|
23863
|
+
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>';
|
|
23864
|
+
}
|
|
23865
|
+
html += '</span>';
|
|
23866
|
+
return html;
|
|
23867
|
+
}
|
|
23868
|
+
|
|
23869
|
+
function onRunListChipClick(key, value) {
|
|
23870
|
+
_runListState[key] = value;
|
|
23871
|
+
_runListSaveView();
|
|
23872
|
+
var panel = document.getElementById('panel-runs');
|
|
23873
|
+
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23874
|
+
}
|
|
23875
|
+
|
|
23876
|
+
function onRunListSearch(value) {
|
|
23877
|
+
_runListState.filterText = value;
|
|
23878
|
+
_runListSaveView();
|
|
23879
|
+
// Debounce-by-render: just re-render. Filtering is in-memory + cheap.
|
|
23880
|
+
var panel = document.getElementById('panel-runs');
|
|
23881
|
+
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23882
|
+
}
|
|
23883
|
+
|
|
23884
|
+
function resetRunListFilters() {
|
|
23885
|
+
_runListState.filterStatus = 'failed';
|
|
23886
|
+
_runListState.filterWindow = '24h';
|
|
23887
|
+
_runListState.filterText = '';
|
|
23888
|
+
_runListSaveView();
|
|
23889
|
+
var panel = document.getElementById('panel-runs');
|
|
23890
|
+
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
23891
|
+
}
|
|
23892
|
+
|
|
23893
|
+
// Wire the panel's click handler so clicking anywhere on a row opens the
|
|
23894
|
+
// trace viewer (the row's data-trace-job attribute is what the existing
|
|
23895
|
+
// global panel-cron click handler reads).
|
|
23896
|
+
function _runListAttachClickHandler() {
|
|
23897
|
+
var pane = document.getElementById('build-tab-runs');
|
|
23898
|
+
if (!pane || pane._handlerAttached) return;
|
|
23899
|
+
pane.addEventListener('click', function(ev) {
|
|
23900
|
+
var t = ev.target;
|
|
23901
|
+
while (t && t !== pane) {
|
|
23902
|
+
if (t.dataset && t.dataset.traceJob) {
|
|
23903
|
+
openTraceViewer(t.dataset.traceJob);
|
|
23904
|
+
return;
|
|
23905
|
+
}
|
|
23906
|
+
t = t.parentElement;
|
|
23907
|
+
}
|
|
23908
|
+
});
|
|
23909
|
+
pane._handlerAttached = true;
|
|
23910
|
+
}
|
|
23911
|
+
// Attach once on first DOM ready — runs idempotent thanks to the flag.
|
|
23912
|
+
if (typeof document !== 'undefined') {
|
|
23913
|
+
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
23914
|
+
setTimeout(_runListAttachClickHandler, 0);
|
|
23915
|
+
} else {
|
|
23916
|
+
document.addEventListener('DOMContentLoaded', _runListAttachClickHandler);
|
|
23917
|
+
}
|
|
23918
|
+
}
|
|
23919
|
+
|
|
23656
23920
|
// ── PRD Phase 2: Tools & MCP catalog ──────────────────────────────────
|
|
23657
23921
|
// Read-only foundation in 1.18.81. Renders the four-card taxonomy:
|
|
23658
23922
|
// • Built-in — Claude SDK native tools (Read/Write/Bash/etc.)
|
|
@@ -23670,7 +23934,14 @@ async function refreshToolsMcpCatalog() {
|
|
|
23670
23934
|
try {
|
|
23671
23935
|
var sR = await apiFetch('/api/mcp-status');
|
|
23672
23936
|
var statusJson = await sR.json();
|
|
23673
|
-
|
|
23937
|
+
// /api/mcp-status returns { servers: [{name, status}], updatedAt }.
|
|
23938
|
+
// Build a name → entry lookup so renderMcpCatalogCard can probe by name.
|
|
23939
|
+
if (statusJson && Array.isArray(statusJson.servers)) {
|
|
23940
|
+
for (var si = 0; si < statusJson.servers.length; si++) {
|
|
23941
|
+
var entry = statusJson.servers[si];
|
|
23942
|
+
if (entry && entry.name) statusMap[entry.name] = entry;
|
|
23943
|
+
}
|
|
23944
|
+
}
|
|
23674
23945
|
} catch (e) { /* status is optional — servers still render without it */ }
|
|
23675
23946
|
try {
|
|
23676
23947
|
var lR = await apiFetch('/api/mcp-servers');
|
|
@@ -35595,6 +35866,14 @@ try {
|
|
|
35595
35866
|
if (evt.type === 'cron_complete' && evt.data && evt.data.job && typeof handleCronRunOnceComplete === 'function') {
|
|
35596
35867
|
try { handleCronRunOnceComplete(evt.data.job); } catch (err) { /* non-fatal */ }
|
|
35597
35868
|
}
|
|
35869
|
+
// PRD Phase 3: if the Runs tab is visible, refresh it too so a new
|
|
35870
|
+
// run appears at the top without a manual reload.
|
|
35871
|
+
if (currentPage === 'build' && typeof refreshRunList === 'function') {
|
|
35872
|
+
var runsPane = document.getElementById('build-tab-runs');
|
|
35873
|
+
if (runsPane && runsPane.style.display !== 'none') {
|
|
35874
|
+
try { refreshRunList(); } catch (err) { /* non-fatal */ }
|
|
35875
|
+
}
|
|
35876
|
+
}
|
|
35598
35877
|
}
|
|
35599
35878
|
// A delete on one tab should drop the card from every open dashboard
|
|
35600
35879
|
// without waiting for the next poll. cron_toggled is similar but lighter.
|
|
@@ -888,7 +888,7 @@ export class CronScheduler {
|
|
|
888
888
|
ctx.agentSlug = wf.agentSlug;
|
|
889
889
|
return ctx;
|
|
890
890
|
}
|
|
891
|
-
async runJob(job) {
|
|
891
|
+
async runJob(job, trigger = 'scheduled') {
|
|
892
892
|
const creditBlock = getBackgroundCreditBlock();
|
|
893
893
|
if (creditBlock) {
|
|
894
894
|
logger.warn({ job: job.name, until: creditBlock.until }, 'Cron job skipped — Claude credit block active');
|
|
@@ -1206,6 +1206,8 @@ export class CronScheduler {
|
|
|
1206
1206
|
outputPreview: response ? response.slice(0, 200) : undefined,
|
|
1207
1207
|
advisorApplied,
|
|
1208
1208
|
terminalReason,
|
|
1209
|
+
// 1.18.84: persist the actual trigger source for the Run list filter.
|
|
1210
|
+
trigger,
|
|
1209
1211
|
// Trick capability metadata — surfaced by the dashboard's
|
|
1210
1212
|
// "ran with: …" line. Omit empty arrays to keep the JSONL light.
|
|
1211
1213
|
...(cronMetadata?.skillsApplied?.length ? { skillsApplied: cronMetadata.skillsApplied } : {}),
|
|
@@ -1268,7 +1270,8 @@ export class CronScheduler {
|
|
|
1268
1270
|
const dependents = this.jobs.filter(j => j.after === job.name && j.enabled && !this.disabledJobs.has(j.name));
|
|
1269
1271
|
for (const dep of dependents) {
|
|
1270
1272
|
logger.info(`Chain: '${job.name}' succeeded — triggering '${dep.name}'`);
|
|
1271
|
-
|
|
1273
|
+
// 1.18.84: chained-after triggers carry trigger='after' for the Run list filter.
|
|
1274
|
+
this.runJob(dep, 'after').catch((err) => {
|
|
1272
1275
|
logger.error({ err, job: dep.name }, `Chained job '${dep.name}' failed`);
|
|
1273
1276
|
});
|
|
1274
1277
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -447,6 +447,11 @@ export interface CronRunEntry {
|
|
|
447
447
|
allowedToolsApplied?: string[];
|
|
448
448
|
/** MCP servers live for this run (post profile + trick allowlist intersection). */
|
|
449
449
|
mcpServersApplied?: string[];
|
|
450
|
+
/** PRD §6 / 1.18.84: how this run was triggered. Persisted by the
|
|
451
|
+
* scheduler (cron tick / chained 'after' / manual-run endpoint /
|
|
452
|
+
* Discord) so the Run list can filter by source instead of guessing
|
|
453
|
+
* via heuristics on attempt count. */
|
|
454
|
+
trigger?: 'manual' | 'scheduled' | 'webhook' | 'api' | 'fork' | 'resume' | 'discord' | 'after';
|
|
450
455
|
/** PRD Phase 1: did the run accomplish what it was supposed to?
|
|
451
456
|
* Computed at run-end when the Task has successSchema or successCriteriaText.
|
|
452
457
|
* - status='pass' both configured checks passed (or the only one configured did)
|