clementine-agent 1.18.69 → 1.18.70
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/cli/dashboard.js +493 -219
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -6594,6 +6594,11 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6594
6594
|
tags: job.tags ?? [],
|
|
6595
6595
|
category: job.category ?? null,
|
|
6596
6596
|
predictable: typeof job.predictable === 'boolean' ? job.predictable : null,
|
|
6597
|
+
// Configured allowlists — included so the dashboard can chip-color
|
|
6598
|
+
// "pinned vs auto-injected" without re-fetching the YAML.
|
|
6599
|
+
allowedMcpServers: Array.isArray(job.allowedMcpServers) ? job.allowedMcpServers : null,
|
|
6600
|
+
allowedTools: Array.isArray(job.allowedTools) ? job.allowedTools : null,
|
|
6601
|
+
pinnedSkills: Array.isArray(job.skills) ? job.skills : null,
|
|
6597
6602
|
},
|
|
6598
6603
|
predictable: plan.predictable,
|
|
6599
6604
|
profile: profile ? { slug: profile.slug, name: profile.name } : null,
|
|
@@ -15028,6 +15033,99 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15028
15033
|
color: var(--text-secondary); white-space: pre-wrap; word-break: break-word;
|
|
15029
15034
|
max-height: 180px; overflow-y: auto;
|
|
15030
15035
|
}
|
|
15036
|
+
/* ── Cron modal: tabs + section cards + legacy banner ─────────── */
|
|
15037
|
+
.cron-modal { width: 820px !important; max-width: 96vw; }
|
|
15038
|
+
.cron-tabs {
|
|
15039
|
+
display: flex; gap: 0; padding: 0 22px;
|
|
15040
|
+
border-bottom: 1px solid var(--border); background: var(--bg-secondary);
|
|
15041
|
+
flex-shrink: 0;
|
|
15042
|
+
}
|
|
15043
|
+
.cron-tab-btn {
|
|
15044
|
+
padding: 12px 18px; background: transparent; border: none;
|
|
15045
|
+
color: var(--text-secondary); font-size: 13px; font-weight: 500;
|
|
15046
|
+
cursor: pointer; border-bottom: 2px solid transparent;
|
|
15047
|
+
margin-bottom: -1px;
|
|
15048
|
+
}
|
|
15049
|
+
.cron-tab-btn.active {
|
|
15050
|
+
color: var(--accent); border-bottom-color: var(--accent);
|
|
15051
|
+
}
|
|
15052
|
+
.cron-tab-btn:hover:not(.active) { color: var(--text-primary); }
|
|
15053
|
+
.cron-tab-btn[disabled] { opacity: 0.4; cursor: not-allowed; }
|
|
15054
|
+
.cron-tab-pane { display: none; }
|
|
15055
|
+
.cron-tab-pane.active { display: block; }
|
|
15056
|
+
.cron-section-card {
|
|
15057
|
+
background: var(--bg-secondary);
|
|
15058
|
+
border: 1px solid var(--border);
|
|
15059
|
+
border-radius: 10px;
|
|
15060
|
+
padding: 14px 16px;
|
|
15061
|
+
margin-bottom: 14px;
|
|
15062
|
+
}
|
|
15063
|
+
.cron-section-card > h4 {
|
|
15064
|
+
font-size: 11px; font-weight: 700; color: var(--text-muted);
|
|
15065
|
+
text-transform: uppercase; letter-spacing: 0.6px;
|
|
15066
|
+
margin: 0 0 4px 0;
|
|
15067
|
+
}
|
|
15068
|
+
.cron-section-desc {
|
|
15069
|
+
font-size: 11px; color: var(--text-muted);
|
|
15070
|
+
margin: 0 0 10px 0; line-height: 1.5;
|
|
15071
|
+
}
|
|
15072
|
+
.cron-banner {
|
|
15073
|
+
border-radius: 8px; padding: 12px 14px;
|
|
15074
|
+
margin-bottom: 14px; font-size: 12px; line-height: 1.5;
|
|
15075
|
+
}
|
|
15076
|
+
.cron-banner.warn {
|
|
15077
|
+
background: rgba(245, 158, 11, 0.10);
|
|
15078
|
+
border: 1px solid rgba(245, 158, 11, 0.32);
|
|
15079
|
+
color: var(--text-primary);
|
|
15080
|
+
}
|
|
15081
|
+
.cron-banner.warn h5 {
|
|
15082
|
+
color: var(--yellow); font-size: 12px; font-weight: 700;
|
|
15083
|
+
margin: 0 0 4px 0; text-transform: uppercase; letter-spacing: 0.4px;
|
|
15084
|
+
}
|
|
15085
|
+
.cron-banner.ok {
|
|
15086
|
+
background: rgba(16, 185, 129, 0.08);
|
|
15087
|
+
border: 1px solid rgba(16, 185, 129, 0.28);
|
|
15088
|
+
color: var(--text-primary);
|
|
15089
|
+
}
|
|
15090
|
+
.cron-banner.ok h5 {
|
|
15091
|
+
color: var(--green); font-size: 12px; font-weight: 700;
|
|
15092
|
+
margin: 0 0 4px 0; text-transform: uppercase; letter-spacing: 0.4px;
|
|
15093
|
+
}
|
|
15094
|
+
.cron-banner .banner-actions { margin-top: 10px; display: flex; gap: 8px; }
|
|
15095
|
+
.cron-banner .banner-actions button {
|
|
15096
|
+
font-size: 12px; padding: 6px 12px; border-radius: 6px; cursor: pointer;
|
|
15097
|
+
}
|
|
15098
|
+
.cron-prompt-textarea {
|
|
15099
|
+
width: 100%; min-height: 180px;
|
|
15100
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
15101
|
+
font-size: 12px; line-height: 1.55;
|
|
15102
|
+
padding: 10px 12px; border: 1px solid var(--border); border-radius: 6px;
|
|
15103
|
+
background: var(--bg-input); color: var(--text-primary);
|
|
15104
|
+
resize: vertical;
|
|
15105
|
+
}
|
|
15106
|
+
.cron-predictable-card {
|
|
15107
|
+
display: flex; align-items: flex-start; gap: 12px;
|
|
15108
|
+
padding: 12px 14px; border-radius: 8px;
|
|
15109
|
+
background: var(--bg-secondary); border: 1px solid var(--border);
|
|
15110
|
+
}
|
|
15111
|
+
.cron-predictable-card.on { border-color: rgba(16, 185, 129, 0.32); background: rgba(16, 185, 129, 0.06); }
|
|
15112
|
+
.cron-predictable-card.off { border-color: rgba(245, 158, 11, 0.32); background: rgba(245, 158, 11, 0.06); }
|
|
15113
|
+
.cron-predictable-card .pred-icon { font-size: 18px; line-height: 1; padding-top: 2px; }
|
|
15114
|
+
.cron-predictable-card .pred-title { font-weight: 600; font-size: 13px; color: var(--text-primary); }
|
|
15115
|
+
.cron-predictable-card .pred-sub { font-size: 11px; color: var(--text-muted); margin-top: 3px; line-height: 1.5; }
|
|
15116
|
+
/* Effective-vs-configured chip coloring on Preview tab */
|
|
15117
|
+
.preview-chip {
|
|
15118
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
15119
|
+
padding: 3px 9px; margin: 2px 4px 2px 0; border-radius: 999px;
|
|
15120
|
+
font-size: 11px; line-height: 1.6;
|
|
15121
|
+
border: 1px solid var(--border);
|
|
15122
|
+
}
|
|
15123
|
+
.preview-chip.pinned { background: rgba(124, 58, 237, 0.10); color: var(--purple); border-color: rgba(124, 58, 237, 0.30); }
|
|
15124
|
+
.preview-chip.auto { background: rgba(245, 158, 11, 0.10); color: var(--yellow); border-color: rgba(245, 158, 11, 0.30); }
|
|
15125
|
+
.preview-chip-group-label {
|
|
15126
|
+
font-size: 10px; text-transform: uppercase; letter-spacing: 0.4px;
|
|
15127
|
+
color: var(--text-muted); margin-right: 4px;
|
|
15128
|
+
}
|
|
15031
15129
|
.preview-warn {
|
|
15032
15130
|
padding: 6px 10px; border-radius: 6px; background: rgba(245, 158, 11, 0.10);
|
|
15033
15131
|
color: var(--yellow); font-size: 12px; margin-bottom: 6px;
|
|
@@ -19511,22 +19609,45 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
19511
19609
|
</div><!-- /content -->
|
|
19512
19610
|
</div><!-- /layout -->
|
|
19513
19611
|
|
|
19514
|
-
<!-- ═══ Create/Edit Cron Modal ═══ -->
|
|
19612
|
+
<!-- ═══ Create/Edit Cron Modal — tabbed (Configure | What will run) ═══ -->
|
|
19515
19613
|
<div class="modal-overlay" id="cron-modal">
|
|
19516
|
-
<div class="modal">
|
|
19614
|
+
<div class="modal cron-modal">
|
|
19517
19615
|
<div class="modal-header">
|
|
19518
19616
|
<h3 id="cron-modal-title">New Scheduled Task</h3>
|
|
19519
19617
|
<button class="btn-ghost btn-sm" onclick="closeCronModal()">×</button>
|
|
19520
19618
|
</div>
|
|
19619
|
+
<div class="cron-tabs" role="tablist">
|
|
19620
|
+
<button type="button" class="cron-tab-btn active" data-cron-tab="configure" onclick="switchCronTab('configure')">Configure</button>
|
|
19621
|
+
<button type="button" class="cron-tab-btn" id="cron-tab-btn-preview" data-cron-tab="preview" onclick="switchCronTab('preview')" title="See exactly what the agent will receive at fire-time">What will run</button>
|
|
19622
|
+
</div>
|
|
19521
19623
|
<div class="modal-body">
|
|
19522
|
-
|
|
19523
|
-
|
|
19524
|
-
|
|
19525
|
-
<div
|
|
19526
|
-
|
|
19527
|
-
|
|
19528
|
-
<
|
|
19529
|
-
|
|
19624
|
+
<!-- ── Tab: Configure ─────────────────────────────────────────── -->
|
|
19625
|
+
<div class="cron-tab-pane active" id="cron-tab-configure">
|
|
19626
|
+
<!-- Legacy / mismatch banner (populated by openEditCronModal) -->
|
|
19627
|
+
<div id="cron-legacy-banner-host"></div>
|
|
19628
|
+
|
|
19629
|
+
<!-- Identity: name + category + tags -->
|
|
19630
|
+
<div class="cron-section-card">
|
|
19631
|
+
<h4>Identity</h4>
|
|
19632
|
+
<p class="cron-section-desc">A unique name and optional grouping for the dashboard.</p>
|
|
19633
|
+
<div class="form-group">
|
|
19634
|
+
<label class="form-label">Task Name</label>
|
|
19635
|
+
<input type="text" id="cron-name" placeholder="e.g. morning-briefing">
|
|
19636
|
+
<div class="form-hint">Unique identifier. Use lowercase with dashes.</div>
|
|
19637
|
+
</div>
|
|
19638
|
+
<div class="form-group">
|
|
19639
|
+
<label class="form-label">Category <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
19640
|
+
<input type="text" id="cron-category" placeholder="e.g. ops, research, morning">
|
|
19641
|
+
<div class="form-hint">Used for grouping in the dashboard.</div>
|
|
19642
|
+
</div>
|
|
19643
|
+
</div>
|
|
19644
|
+
|
|
19645
|
+
<!-- Schedule -->
|
|
19646
|
+
<div class="cron-section-card">
|
|
19647
|
+
<h4>Schedule</h4>
|
|
19648
|
+
<p class="cron-section-desc">When this task fires. Pick a frequency or write a cron expression.</p>
|
|
19649
|
+
<div class="form-group" style="margin:0">
|
|
19650
|
+
<div class="schedule-builder" id="schedule-builder">
|
|
19530
19651
|
<div class="form-row">
|
|
19531
19652
|
<select id="sched-freq" onchange="updateScheduleBuilder()">
|
|
19532
19653
|
<option value="daily">Every day</option>
|
|
@@ -19601,158 +19722,179 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
19601
19722
|
<input type="text" id="cron-schedule" placeholder="0 9 * * *" oninput="updateScheduleHint()">
|
|
19602
19723
|
</div>
|
|
19603
19724
|
<div class="form-hint" id="cron-schedule-hint" style="margin-top:6px"></div>
|
|
19725
|
+
</div>
|
|
19726
|
+
</div>
|
|
19604
19727
|
</div>
|
|
19605
|
-
|
|
19606
|
-
|
|
19607
|
-
<div class="
|
|
19608
|
-
<
|
|
19609
|
-
<
|
|
19610
|
-
|
|
19611
|
-
<
|
|
19612
|
-
<
|
|
19613
|
-
|
|
19614
|
-
|
|
19615
|
-
|
|
19616
|
-
|
|
19617
|
-
|
|
19618
|
-
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19629
|
-
<div class="form-group">
|
|
19630
|
-
<label class="form-label">Mode</label>
|
|
19631
|
-
<select id="cron-mode" onchange="toggleUnleashedOptions()">
|
|
19632
|
-
<option value="standard">Standard</option>
|
|
19633
|
-
<option value="unleashed">Unleashed (long-running)</option>
|
|
19634
|
-
</select>
|
|
19635
|
-
<div class="form-hint">Unleashed mode runs in phases with checkpointing for tasks that take hours.</div>
|
|
19636
|
-
</div>
|
|
19637
|
-
<div class="form-group" id="cron-maxhours-group" style="display:none">
|
|
19638
|
-
<label class="form-label">Max Hours</label>
|
|
19639
|
-
<select id="cron-maxhours">
|
|
19640
|
-
<option value="1">1 hour</option>
|
|
19641
|
-
<option value="2">2 hours</option>
|
|
19642
|
-
<option value="4">4 hours</option>
|
|
19643
|
-
<option value="6" selected>6 hours (default)</option>
|
|
19644
|
-
<option value="8">8 hours</option>
|
|
19645
|
-
<option value="12">12 hours</option>
|
|
19646
|
-
<option value="24">24 hours</option>
|
|
19647
|
-
</select>
|
|
19648
|
-
</div>
|
|
19649
|
-
</div>
|
|
19650
|
-
<div class="form-row">
|
|
19651
|
-
<div class="form-group">
|
|
19652
|
-
<label class="form-label">Max Retries <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
19653
|
-
<select id="cron-max-retries">
|
|
19654
|
-
<option value="">Auto (based on error history)</option>
|
|
19655
|
-
<option value="0">0 — No retries</option>
|
|
19656
|
-
<option value="1">1</option>
|
|
19657
|
-
<option value="2">2</option>
|
|
19658
|
-
<option value="3">3</option>
|
|
19659
|
-
<option value="5">5</option>
|
|
19660
|
-
</select>
|
|
19661
|
-
<div class="form-hint">Override automatic retry count for transient errors.</div>
|
|
19662
|
-
</div>
|
|
19663
|
-
<div class="form-group">
|
|
19664
|
-
<label class="form-label">After Job <span style="color:var(--text-muted);font-weight:normal">(chain)</span></label>
|
|
19665
|
-
<select id="cron-after">
|
|
19666
|
-
<option value="">None — runs on schedule</option>
|
|
19667
|
-
</select>
|
|
19668
|
-
<div class="form-hint">Trigger this job after another succeeds (ignores schedule).</div>
|
|
19728
|
+
|
|
19729
|
+
<!-- What it does: prompt + context + reference files -->
|
|
19730
|
+
<div class="cron-section-card">
|
|
19731
|
+
<h4>What it does</h4>
|
|
19732
|
+
<p class="cron-section-desc">The instruction the agent receives. Long prompts are fine — drag the corner to resize.</p>
|
|
19733
|
+
<div class="form-group">
|
|
19734
|
+
<label class="form-label">Prompt</label>
|
|
19735
|
+
<textarea id="cron-prompt" class="cron-prompt-textarea" placeholder="What should the AI do when this task runs?"></textarea>
|
|
19736
|
+
<div class="form-hint">The instruction sent to the AI agent when this task fires.</div>
|
|
19737
|
+
</div>
|
|
19738
|
+
<div class="form-group">
|
|
19739
|
+
<label class="form-label">Context <span style="color:var(--text-muted);font-weight:normal">(optional — injected at runtime)</span></label>
|
|
19740
|
+
<textarea id="cron-context" rows="4" placeholder="Guidelines, examples, formatting rules, data sources, or any context the agent should know when running this task..."></textarea>
|
|
19741
|
+
<div class="form-hint">Freeform notes injected into the prompt at execution time. Use this for training data, style guides, or standing instructions.</div>
|
|
19742
|
+
</div>
|
|
19743
|
+
<div class="form-group" style="margin-bottom:0">
|
|
19744
|
+
<label class="form-label">Reference Files <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
19745
|
+
<div id="cron-attachments-list" style="margin-bottom:8px"></div>
|
|
19746
|
+
<label class="btn btn-sm" style="cursor:pointer;display:inline-flex;align-items:center;gap:4px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;padding:6px 12px;font-size:12px;color:var(--text-primary)">
|
|
19747
|
+
+ Attach File
|
|
19748
|
+
<input type="file" multiple accept=".csv,.md,.txt,.json,.docx,.xlsx" style="display:none" onchange="handleCronFileUpload(event)">
|
|
19749
|
+
</label>
|
|
19750
|
+
<div class="form-hint">CSV, Markdown, or text files the agent can reference during execution. Max 10MB per file.</div>
|
|
19751
|
+
</div>
|
|
19669
19752
|
</div>
|
|
19670
|
-
|
|
19671
|
-
|
|
19672
|
-
<
|
|
19673
|
-
|
|
19674
|
-
|
|
19675
|
-
|
|
19676
|
-
|
|
19677
|
-
|
|
19678
|
-
|
|
19679
|
-
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
<label class="form-label">Capabilities <span style="color:var(--text-muted);font-weight:normal">(optional — pin skills + scope tools/MCP)</span></label>
|
|
19684
|
-
<div class="form-hint" style="margin-bottom:6px">Pin learned procedures and constrain which tools / MCP servers this trick can use. Empty = inherit defaults. Use Preview on the card to see exactly what gets sent.</div>
|
|
19685
|
-
<div class="cap-section">
|
|
19686
|
-
<label class="cap-section-label">Predictable Mode</label>
|
|
19687
|
-
<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;font-size:12px;color:var(--text-primary)">
|
|
19688
|
-
<input type="checkbox" id="cron-predictable" checked style="margin-top:3px">
|
|
19689
|
-
<span>
|
|
19690
|
-
<strong>Run with only what's attached</strong> (recommended)
|
|
19691
|
-
<div style="font-size:11px;color:var(--text-muted);margin-top:3px;line-height:1.5">
|
|
19692
|
-
ON: MEMORY.md, team comms, delegation queue, and auto-matched skills are SKIPPED at fire-time. The trick runs ONLY with the prompt + pinned skills + tools you see here. No drift, no surprise.<br>
|
|
19693
|
-
OFF: legacy mode — runner injects MEMORY.md and other live context. Use only when the trick legitimately needs to re-read memory each fire (e.g. "summarize yesterday's daily note").
|
|
19694
|
-
</div>
|
|
19753
|
+
|
|
19754
|
+
<!-- Capabilities: predictable mode + skills + MCP + tools + tags -->
|
|
19755
|
+
<div class="cron-section-card">
|
|
19756
|
+
<h4>Capabilities</h4>
|
|
19757
|
+
<p class="cron-section-desc">Predictable mode locks the run to exactly what's pinned here. Switch to the <strong>What will run</strong> tab to see the resolved final state.</p>
|
|
19758
|
+
|
|
19759
|
+
<!-- Predictable Mode (prominent at top) -->
|
|
19760
|
+
<label id="cron-predictable-card" class="cron-predictable-card on" style="cursor:pointer;margin-bottom:12px">
|
|
19761
|
+
<span class="pred-icon" id="cron-predictable-icon">🔒</span>
|
|
19762
|
+
<input type="checkbox" id="cron-predictable" checked style="margin-top:3px" onchange="onPredictableChange()">
|
|
19763
|
+
<span style="flex:1">
|
|
19764
|
+
<span class="pred-title" id="cron-predictable-title">Predictable mode — ON</span>
|
|
19765
|
+
<div class="pred-sub" id="cron-predictable-sub">Runs with ONLY the prompt + pinned skills/MCP below. MEMORY.md, team comms, and auto-matched skills are skipped at fire-time. Recommended.</div>
|
|
19695
19766
|
</span>
|
|
19696
19767
|
</label>
|
|
19697
|
-
|
|
19698
|
-
|
|
19699
|
-
|
|
19700
|
-
|
|
19701
|
-
|
|
19702
|
-
|
|
19703
|
-
|
|
19704
|
-
|
|
19768
|
+
|
|
19769
|
+
<div class="cap-section">
|
|
19770
|
+
<label class="cap-section-label">Pinned Skills</label>
|
|
19771
|
+
<div class="cap-picker-chips" id="cron-skills-chips"></div>
|
|
19772
|
+
<button type="button" class="cap-toggle-link" id="cron-skills-add-btn" onclick="toggleSkillsPickerSearch()">+ Add skill</button>
|
|
19773
|
+
<div id="cron-skills-search-panel" style="display:none;margin-top:6px">
|
|
19774
|
+
<input type="text" class="cap-picker-search" id="cron-skills-search" placeholder="Search skills…" oninput="renderSkillsPickerList()">
|
|
19775
|
+
<div class="cap-picker-list" id="cron-skills-list"><div class="cap-picker-empty-state">Loading skills…</div></div>
|
|
19776
|
+
</div>
|
|
19705
19777
|
</div>
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
|
|
19713
|
-
|
|
19778
|
+
<div class="cap-section">
|
|
19779
|
+
<label class="cap-section-label">Allowed MCP Servers</label>
|
|
19780
|
+
<div class="cap-picker-chips" id="cron-mcp-chips"></div>
|
|
19781
|
+
<button type="button" class="cap-toggle-link" id="cron-mcp-add-btn" onclick="toggleMcpPickerSearch()">+ Add MCP server</button>
|
|
19782
|
+
<div id="cron-mcp-search-panel" style="display:none;margin-top:6px">
|
|
19783
|
+
<input type="text" class="cap-picker-search" id="cron-mcp-search" placeholder="Search MCP servers…" oninput="renderMcpPickerList()">
|
|
19784
|
+
<div class="cap-picker-list" id="cron-mcp-list"><div class="cap-picker-empty-state">Loading MCP servers…</div></div>
|
|
19785
|
+
</div>
|
|
19714
19786
|
</div>
|
|
19715
|
-
|
|
19716
|
-
|
|
19717
|
-
|
|
19718
|
-
|
|
19719
|
-
|
|
19720
|
-
|
|
19721
|
-
|
|
19787
|
+
<div class="cap-section">
|
|
19788
|
+
<label class="cap-section-label">Allowed Tools (raw allowlist · power users)</label>
|
|
19789
|
+
<button type="button" class="cap-toggle-link" id="cron-tools-toggle-btn" onclick="toggleAllowedToolsPanel()">▾ Show</button>
|
|
19790
|
+
<div id="cron-tools-panel" style="display:none;margin-top:6px">
|
|
19791
|
+
<textarea id="cron-allowed-tools" class="cap-tools-textarea" rows="2" placeholder="Read, Edit, Bash, mcp__slack__send_message, ..."></textarea>
|
|
19792
|
+
<div class="form-hint">Comma-separated tool names. Empty = inherit from agent profile / default. 'Agent' is always force-included.</div>
|
|
19793
|
+
</div>
|
|
19794
|
+
</div>
|
|
19795
|
+
<div class="cap-section">
|
|
19796
|
+
<label class="cap-section-label">Tags</label>
|
|
19797
|
+
<div class="cap-picker-chips" id="cron-tags-chips"></div>
|
|
19798
|
+
<input type="text" class="cap-tag-input" id="cron-tags-input" placeholder="Type a tag and press Enter (e.g. morning, ops)" onkeydown="handleTagInputKeydown(event)">
|
|
19722
19799
|
</div>
|
|
19723
19800
|
</div>
|
|
19724
|
-
<div class="cap-section">
|
|
19725
|
-
<label class="cap-section-label">Tags</label>
|
|
19726
|
-
<div class="cap-picker-chips" id="cron-tags-chips"></div>
|
|
19727
|
-
<input type="text" class="cap-tag-input" id="cron-tags-input" placeholder="Type a tag and press Enter (e.g. morning, ops)" onkeydown="handleTagInputKeydown(event)">
|
|
19728
|
-
</div>
|
|
19729
|
-
</div>
|
|
19730
|
-
<div class="form-group">
|
|
19731
|
-
<label class="form-label">Reference Files <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
19732
|
-
<div id="cron-attachments-list" style="margin-bottom:8px"></div>
|
|
19733
|
-
<label class="btn btn-sm" style="cursor:pointer;display:inline-flex;align-items:center;gap:4px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;padding:6px 12px;font-size:12px;color:var(--text-primary)">
|
|
19734
|
-
+ Attach File
|
|
19735
|
-
<input type="file" multiple accept=".csv,.md,.txt,.json,.docx,.xlsx" style="display:none" onchange="handleCronFileUpload(event)">
|
|
19736
|
-
</label>
|
|
19737
|
-
<div class="form-hint">CSV, Markdown, or text files the agent can reference during execution. Max 10MB per file.</div>
|
|
19738
|
-
</div>
|
|
19739
19801
|
|
|
19740
|
-
|
|
19741
|
-
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
|
|
19802
|
+
<!-- Advanced: tier + workdir + mode + max-hours + retries + after -->
|
|
19803
|
+
<details class="cron-section-card" style="padding:0">
|
|
19804
|
+
<summary style="padding:14px 16px;cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between">
|
|
19805
|
+
<span><strong style="font-size:11px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.6px">Advanced</strong> <span style="color:var(--text-muted);font-size:11px;margin-left:6px">tier, mode, retries, project context</span></span>
|
|
19806
|
+
<span style="color:var(--text-muted);font-size:11px">▾</span>
|
|
19807
|
+
</summary>
|
|
19808
|
+
<div style="padding:0 16px 14px">
|
|
19809
|
+
<div class="form-row">
|
|
19810
|
+
<div class="form-group">
|
|
19811
|
+
<label class="form-label">Tier</label>
|
|
19812
|
+
<select id="cron-tier">
|
|
19813
|
+
<option value="1">Tier 1 — Read-only (vault, search, web)</option>
|
|
19814
|
+
<option value="2">Tier 2 — Read + Write (Bash, files, sub-agents)</option>
|
|
19815
|
+
<option value="3">Tier 3 — Full access (use with caution)</option>
|
|
19816
|
+
</select>
|
|
19817
|
+
</div>
|
|
19818
|
+
<div class="form-group">
|
|
19819
|
+
<label class="form-label">Project Context <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
19820
|
+
<select id="cron-workdir">
|
|
19821
|
+
<option value="">None — runs in default context</option>
|
|
19822
|
+
</select>
|
|
19823
|
+
<div class="form-hint">Run inside a project directory. Agent gets that project's CLAUDE.md.</div>
|
|
19824
|
+
</div>
|
|
19825
|
+
</div>
|
|
19826
|
+
<div class="form-row">
|
|
19827
|
+
<div class="form-group">
|
|
19828
|
+
<label class="form-label">Mode</label>
|
|
19829
|
+
<select id="cron-mode" onchange="toggleUnleashedOptions()">
|
|
19830
|
+
<option value="standard">Standard</option>
|
|
19831
|
+
<option value="unleashed">Unleashed (long-running)</option>
|
|
19832
|
+
</select>
|
|
19833
|
+
<div class="form-hint">Unleashed runs in phases with checkpointing for hour-scale tasks.</div>
|
|
19834
|
+
</div>
|
|
19835
|
+
<div class="form-group" id="cron-maxhours-group" style="display:none">
|
|
19836
|
+
<label class="form-label">Max Hours</label>
|
|
19837
|
+
<select id="cron-maxhours">
|
|
19838
|
+
<option value="1">1 hour</option>
|
|
19839
|
+
<option value="2">2 hours</option>
|
|
19840
|
+
<option value="4">4 hours</option>
|
|
19841
|
+
<option value="6" selected>6 hours (default)</option>
|
|
19842
|
+
<option value="8">8 hours</option>
|
|
19843
|
+
<option value="12">12 hours</option>
|
|
19844
|
+
<option value="24">24 hours</option>
|
|
19845
|
+
</select>
|
|
19846
|
+
</div>
|
|
19847
|
+
</div>
|
|
19848
|
+
<div class="form-row">
|
|
19849
|
+
<div class="form-group">
|
|
19850
|
+
<label class="form-label">Max Retries <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
19851
|
+
<select id="cron-max-retries">
|
|
19852
|
+
<option value="">Auto (based on error history)</option>
|
|
19853
|
+
<option value="0">0 — No retries</option>
|
|
19854
|
+
<option value="1">1</option>
|
|
19855
|
+
<option value="2">2</option>
|
|
19856
|
+
<option value="3">3</option>
|
|
19857
|
+
<option value="5">5</option>
|
|
19858
|
+
</select>
|
|
19859
|
+
<div class="form-hint">Override automatic retry count for transient errors.</div>
|
|
19860
|
+
</div>
|
|
19861
|
+
<div class="form-group">
|
|
19862
|
+
<label class="form-label">After Job <span style="color:var(--text-muted);font-weight:normal">(chain)</span></label>
|
|
19863
|
+
<select id="cron-after">
|
|
19864
|
+
<option value="">None — runs on schedule</option>
|
|
19865
|
+
</select>
|
|
19866
|
+
<div class="form-hint">Trigger after another job succeeds (ignores schedule).</div>
|
|
19867
|
+
</div>
|
|
19868
|
+
</div>
|
|
19869
|
+
</div>
|
|
19870
|
+
</details>
|
|
19871
|
+
|
|
19872
|
+
<!-- Training Chat -->
|
|
19873
|
+
<div class="form-group" id="cron-training-section" style="display:none">
|
|
19874
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
|
19875
|
+
<label class="form-label" style="margin:0">Training Chat</label>
|
|
19876
|
+
<button class="btn btn-sm" onclick="toggleCronTraining()" style="font-size:11px" id="cron-training-toggle">Hide</button>
|
|
19877
|
+
</div>
|
|
19878
|
+
<div id="cron-training-chat" style="border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);overflow:hidden">
|
|
19879
|
+
<div id="cron-training-messages" style="max-height:240px;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:8px">
|
|
19880
|
+
<div class="empty-state" style="padding:16px;font-size:12px;color:var(--text-muted)">Chat with the agent to refine this task. Ask for help with the prompt, suggest improvements, or add context.</div>
|
|
19881
|
+
</div>
|
|
19882
|
+
<div style="display:flex;gap:6px;padding:8px;border-top:1px solid var(--border);background:var(--bg-primary)">
|
|
19883
|
+
<input type="text" id="cron-training-input" placeholder="Ask for help refining this task..." style="flex:1;font-size:12px;padding:6px 10px" onkeydown="if(event.key==='Enter')sendCronTraining()">
|
|
19884
|
+
<button class="btn btn-sm btn-primary" id="cron-training-send" onclick="sendCronTraining()" style="font-size:11px;padding:4px 12px">Send</button>
|
|
19885
|
+
</div>
|
|
19749
19886
|
</div>
|
|
19750
|
-
|
|
19751
|
-
|
|
19752
|
-
|
|
19887
|
+
</div>
|
|
19888
|
+
</div><!-- /cron-tab-configure -->
|
|
19889
|
+
|
|
19890
|
+
<!-- ── Tab: What will run ─────────────────────────────────────── -->
|
|
19891
|
+
<div class="cron-tab-pane" id="cron-tab-preview">
|
|
19892
|
+
<div id="cron-preview-body" style="padding:0">
|
|
19893
|
+
<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">
|
|
19894
|
+
Save the task first, then switch back here to see exactly what the agent will receive.
|
|
19753
19895
|
</div>
|
|
19754
19896
|
</div>
|
|
19755
|
-
</div
|
|
19897
|
+
</div><!-- /cron-tab-preview -->
|
|
19756
19898
|
</div>
|
|
19757
19899
|
<div class="modal-footer">
|
|
19758
19900
|
<div style="display:flex;align-items:center;gap:8px;flex:1">
|
|
@@ -19764,21 +19906,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
19764
19906
|
</div>
|
|
19765
19907
|
</div>
|
|
19766
19908
|
|
|
19767
|
-
<!--
|
|
19768
|
-
<div class="modal-overlay" id="cron-preview-modal">
|
|
19769
|
-
<div class="modal" style="width:760px;max-width:96vw">
|
|
19770
|
-
<div class="modal-header">
|
|
19771
|
-
<h3 id="cron-preview-title">Preview</h3>
|
|
19772
|
-
<button class="btn-ghost btn-sm" onclick="closeCronPreviewModal()">×</button>
|
|
19773
|
-
</div>
|
|
19774
|
-
<div class="modal-body" id="cron-preview-body" style="padding:0;max-height:75vh;overflow-y:auto">
|
|
19775
|
-
<div style="padding:24px;color:var(--text-muted);text-align:center">Loading…</div>
|
|
19776
|
-
</div>
|
|
19777
|
-
<div class="modal-footer">
|
|
19778
|
-
<button onclick="closeCronPreviewModal()">Close</button>
|
|
19779
|
-
</div>
|
|
19780
|
-
</div>
|
|
19781
|
-
</div>
|
|
19909
|
+
<!-- (legacy standalone Preview modal removed in 1.18.70 — preview now lives as a tab inside the cron modal) -->
|
|
19782
19910
|
|
|
19783
19911
|
<!-- ═══ Goal Modal ═══ -->
|
|
19784
19912
|
<div class="modal-overlay" id="goal-modal">
|
|
@@ -24064,9 +24192,109 @@ function toggleUnleashedOptions() {
|
|
|
24064
24192
|
document.getElementById('cron-maxhours-group').style.display = mode === 'unleashed' ? '' : 'none';
|
|
24065
24193
|
}
|
|
24066
24194
|
|
|
24195
|
+
// ── Cron modal: tab switching ────────────────────────────────────
|
|
24196
|
+
// Tracks whether the current modal session has fetched preview data, so
|
|
24197
|
+
// switching tabs doesn't refetch on every flip but a save-then-flip will.
|
|
24198
|
+
var _cronPreviewLoadedFor = null;
|
|
24199
|
+
var _cronActiveTab = 'configure';
|
|
24200
|
+
|
|
24201
|
+
function switchCronTab(tab) {
|
|
24202
|
+
_cronActiveTab = tab;
|
|
24203
|
+
document.querySelectorAll('.cron-tab-btn').forEach(function(b) {
|
|
24204
|
+
b.classList.toggle('active', b.getAttribute('data-cron-tab') === tab);
|
|
24205
|
+
});
|
|
24206
|
+
var configurePane = document.getElementById('cron-tab-configure');
|
|
24207
|
+
var previewPane = document.getElementById('cron-tab-preview');
|
|
24208
|
+
if (configurePane) configurePane.classList.toggle('active', tab === 'configure');
|
|
24209
|
+
if (previewPane) previewPane.classList.toggle('active', tab === 'preview');
|
|
24210
|
+
if (tab === 'preview') {
|
|
24211
|
+
var name = editingCronJob;
|
|
24212
|
+
if (!name) {
|
|
24213
|
+
var body = document.getElementById('cron-preview-body');
|
|
24214
|
+
if (body) body.innerHTML = '<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">Save the task first, then switch back here to see exactly what the agent will receive.</div>';
|
|
24215
|
+
return;
|
|
24216
|
+
}
|
|
24217
|
+
if (_cronPreviewLoadedFor !== name) loadCronPreviewIntoTab(name);
|
|
24218
|
+
}
|
|
24219
|
+
}
|
|
24220
|
+
|
|
24221
|
+
async function loadCronPreviewIntoTab(jobName) {
|
|
24222
|
+
var body = document.getElementById('cron-preview-body');
|
|
24223
|
+
if (!body) return;
|
|
24224
|
+
body.innerHTML = '<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">Loading preview…</div>';
|
|
24225
|
+
try {
|
|
24226
|
+
var r = await apiFetch('/api/cron/' + encodeURIComponent(jobName) + '/preview');
|
|
24227
|
+
var d = await r.json();
|
|
24228
|
+
if (!r.ok || d.error) {
|
|
24229
|
+
body.innerHTML = '<div class="preview-section" style="color:var(--red)">Preview failed: ' + esc(d.error || 'unknown') + '</div>';
|
|
24230
|
+
return;
|
|
24231
|
+
}
|
|
24232
|
+
body.innerHTML = renderCronPreview(d);
|
|
24233
|
+
_cronPreviewLoadedFor = jobName;
|
|
24234
|
+
} catch(e) {
|
|
24235
|
+
body.innerHTML = '<div class="preview-section" style="color:var(--red)">Preview failed: ' + esc(String(e)) + '</div>';
|
|
24236
|
+
}
|
|
24237
|
+
}
|
|
24238
|
+
|
|
24239
|
+
// Mark the preview as stale (call after save so next tab visit refetches).
|
|
24240
|
+
function markCronPreviewDirty() { _cronPreviewLoadedFor = null; }
|
|
24241
|
+
|
|
24242
|
+
// ── Predictable mode: visual card sync + legacy banner ───────────
|
|
24243
|
+
function onPredictableChange() {
|
|
24244
|
+
var predEl = document.getElementById('cron-predictable');
|
|
24245
|
+
var card = document.getElementById('cron-predictable-card');
|
|
24246
|
+
var icon = document.getElementById('cron-predictable-icon');
|
|
24247
|
+
var title = document.getElementById('cron-predictable-title');
|
|
24248
|
+
var sub = document.getElementById('cron-predictable-sub');
|
|
24249
|
+
if (!predEl || !card) return;
|
|
24250
|
+
var on = !!predEl.checked;
|
|
24251
|
+
card.classList.toggle('on', on);
|
|
24252
|
+
card.classList.toggle('off', !on);
|
|
24253
|
+
if (icon) icon.textContent = on ? '🔒' : '🔄';
|
|
24254
|
+
if (title) title.textContent = on ? 'Predictable mode — ON' : 'Predictable mode — OFF (legacy)';
|
|
24255
|
+
if (sub) {
|
|
24256
|
+
sub.innerHTML = on
|
|
24257
|
+
? "Runs with ONLY the prompt + pinned skills/MCP below. MEMORY.md, team comms, and auto-matched skills are skipped at fire-time. Recommended."
|
|
24258
|
+
: "Legacy mode — fire-time injects MEMORY.md, recent team activity, and auto-matched skills/MCP servers based on prompt text. Use only if the task legitimately needs to re-read memory each fire (e.g. \\"summarize yesterday's daily note\\").";
|
|
24259
|
+
}
|
|
24260
|
+
// Hide legacy banner if user just turned predictable on (won't persist
|
|
24261
|
+
// until they Save, but the visual feedback matters).
|
|
24262
|
+
var host = document.getElementById('cron-legacy-banner-host');
|
|
24263
|
+
if (on && host) host.innerHTML = '';
|
|
24264
|
+
}
|
|
24265
|
+
|
|
24266
|
+
function renderCronLegacyBanner(job) {
|
|
24267
|
+
var host = document.getElementById('cron-legacy-banner-host');
|
|
24268
|
+
if (!host) return;
|
|
24269
|
+
// Banner only when predictable is undefined or explicitly false. Jobs
|
|
24270
|
+
// saved with predictable: true are migrated and skip the banner.
|
|
24271
|
+
if (job && job.predictable === true) { host.innerHTML = ''; return; }
|
|
24272
|
+
var msg = (job && job.predictable === false)
|
|
24273
|
+
? "This task is set to legacy mode. At fire-time the runner injects MEMORY.md, recent team activity, the delegation queue, and auto-matches MCP servers based on prompt text — even if your prompt forbids them. The <strong>What will run</strong> tab shows what actually gets attached."
|
|
24274
|
+
: "This task was created before Predictable Mode existed. At fire-time the runner still injects MEMORY.md, recent team activity, and auto-matches MCP servers based on prompt text. Open the <strong>What will run</strong> tab to see what actually gets attached.";
|
|
24275
|
+
host.innerHTML =
|
|
24276
|
+
'<div class="cron-banner warn">'
|
|
24277
|
+
+ '<h5>⚠ Legacy mode — output may not match what you see here</h5>'
|
|
24278
|
+
+ '<div>' + msg + '</div>'
|
|
24279
|
+
+ '<div class="banner-actions">'
|
|
24280
|
+
+ '<button class="btn-primary btn-sm" onclick="enablePredictableFromBanner()">Switch to Predictable Mode</button>'
|
|
24281
|
+
+ '<button class="btn-sm" onclick="switchCronTab(\\x27preview\\x27)" style="background:transparent;border:1px solid var(--border);color:var(--text-primary)">See what will run</button>'
|
|
24282
|
+
+ '</div>'
|
|
24283
|
+
+ '</div>';
|
|
24284
|
+
}
|
|
24285
|
+
|
|
24286
|
+
function enablePredictableFromBanner() {
|
|
24287
|
+
var predEl = document.getElementById('cron-predictable');
|
|
24288
|
+
if (!predEl) return;
|
|
24289
|
+
predEl.checked = true;
|
|
24290
|
+
onPredictableChange();
|
|
24291
|
+
toast('Predictable Mode enabled — click Save Changes to lock it in.', 'success');
|
|
24292
|
+
}
|
|
24293
|
+
|
|
24067
24294
|
function openCreateCronModal(agentSlug) {
|
|
24068
24295
|
_cronAgentContext = agentSlug || '';
|
|
24069
24296
|
editingCronJob = null;
|
|
24297
|
+
_cronPreviewLoadedFor = null;
|
|
24070
24298
|
document.getElementById('cron-modal-title').textContent = 'New Scheduled Task';
|
|
24071
24299
|
document.getElementById('cron-modal-save').textContent = 'Create Task';
|
|
24072
24300
|
document.getElementById('cron-name').value = '';
|
|
@@ -24089,6 +24317,13 @@ function openCreateCronModal(agentSlug) {
|
|
|
24089
24317
|
document.getElementById('cron-train-btn').style.display = '';
|
|
24090
24318
|
resetCronTrainingChat();
|
|
24091
24319
|
resetTrickCapabilityState();
|
|
24320
|
+
// No saved state to preview when creating — disable the Preview tab.
|
|
24321
|
+
var previewBtn = document.getElementById('cron-tab-btn-preview');
|
|
24322
|
+
if (previewBtn) previewBtn.setAttribute('disabled', 'disabled');
|
|
24323
|
+
var host = document.getElementById('cron-legacy-banner-host');
|
|
24324
|
+
if (host) host.innerHTML = '';
|
|
24325
|
+
switchCronTab('configure');
|
|
24326
|
+
onPredictableChange();
|
|
24092
24327
|
document.getElementById('cron-modal').classList.add('show');
|
|
24093
24328
|
}
|
|
24094
24329
|
|
|
@@ -24097,6 +24332,7 @@ function openEditCronModal(jobName) {
|
|
|
24097
24332
|
if (!job) return;
|
|
24098
24333
|
_cronAgentContext = job.agent || '';
|
|
24099
24334
|
editingCronJob = jobName;
|
|
24335
|
+
_cronPreviewLoadedFor = null;
|
|
24100
24336
|
document.getElementById('cron-modal-title').textContent = 'Edit: ' + jobName;
|
|
24101
24337
|
document.getElementById('cron-modal-save').textContent = 'Save Changes';
|
|
24102
24338
|
document.getElementById('cron-name').value = job.name;
|
|
@@ -24130,64 +24366,59 @@ function openEditCronModal(jobName) {
|
|
|
24130
24366
|
var catEl = document.getElementById('cron-category');
|
|
24131
24367
|
if (catEl) catEl.value = job.category || '';
|
|
24132
24368
|
// Predictable: respect saved value; if undefined (legacy trick), keep
|
|
24133
|
-
// unchecked so we don't silently change runner behavior.
|
|
24369
|
+
// unchecked so we don't silently change runner behavior. The legacy
|
|
24370
|
+
// banner surfaces the migration choice instead.
|
|
24134
24371
|
var predEl = document.getElementById('cron-predictable');
|
|
24135
24372
|
if (predEl) predEl.checked = (job.predictable === true);
|
|
24373
|
+
onPredictableChange();
|
|
24374
|
+
renderCronLegacyBanner(job);
|
|
24136
24375
|
renderSkillsPickerChips();
|
|
24137
24376
|
renderMcpPickerChips();
|
|
24138
24377
|
renderTagsPickerChips();
|
|
24139
24378
|
_pendingAttachments = [];
|
|
24140
24379
|
loadCronAttachments(jobName);
|
|
24380
|
+
// Existing job has saved state, enable Preview tab.
|
|
24381
|
+
var previewBtn = document.getElementById('cron-tab-btn-preview');
|
|
24382
|
+
if (previewBtn) previewBtn.removeAttribute('disabled');
|
|
24383
|
+
switchCronTab('configure');
|
|
24141
24384
|
document.getElementById('cron-modal').classList.add('show');
|
|
24142
24385
|
}
|
|
24143
24386
|
|
|
24144
24387
|
/**
|
|
24145
|
-
* Open the
|
|
24146
|
-
*
|
|
24147
|
-
*
|
|
24148
|
-
* "configured via chat → surprise at fire time" before the surprise.
|
|
24388
|
+
* Open the unified cron modal directly on the "What will run" tab —
|
|
24389
|
+
* what used to be a separate Preview modal in 1.18.69 and earlier.
|
|
24390
|
+
* Loads both Configure data (so editing works) and the live preview.
|
|
24149
24391
|
*/
|
|
24150
24392
|
async function openCronPreview(jobName) {
|
|
24151
|
-
|
|
24152
|
-
|
|
24153
|
-
body.innerHTML = '<div style="padding:24px;color:var(--text-muted);text-align:center">Loading preview…</div>';
|
|
24154
|
-
document.getElementById('cron-preview-modal').classList.add('show');
|
|
24155
|
-
try {
|
|
24156
|
-
var r = await apiFetch('/api/cron/' + encodeURIComponent(jobName) + '/preview');
|
|
24157
|
-
var d = await r.json();
|
|
24158
|
-
if (!r.ok || d.error) {
|
|
24159
|
-
body.innerHTML = '<div class="preview-section" style="color:var(--red)">Preview failed: ' + esc(d.error || 'unknown') + '</div>';
|
|
24160
|
-
return;
|
|
24161
|
-
}
|
|
24162
|
-
body.innerHTML = renderCronPreview(d);
|
|
24163
|
-
} catch(e) {
|
|
24164
|
-
body.innerHTML = '<div class="preview-section" style="color:var(--red)">Preview failed: ' + esc(String(e)) + '</div>';
|
|
24165
|
-
}
|
|
24393
|
+
openEditCronModal(jobName);
|
|
24394
|
+
switchCronTab('preview');
|
|
24166
24395
|
}
|
|
24167
24396
|
|
|
24168
|
-
|
|
24169
|
-
|
|
24170
|
-
}
|
|
24397
|
+
// Backwards-compat alias — kept in case external buttons still reference
|
|
24398
|
+
// it. The standalone preview modal was removed in 1.18.70.
|
|
24399
|
+
function closeCronPreviewModal() { closeCronModal(); }
|
|
24171
24400
|
|
|
24172
24401
|
function renderCronPreview(d) {
|
|
24173
24402
|
var html = '';
|
|
24174
|
-
//
|
|
24403
|
+
// Headline: this is THE place to surface the predictable verdict, since
|
|
24404
|
+
// this whole tab exists to answer "what will the agent actually receive?"
|
|
24175
24405
|
html += '<div class="preview-section">';
|
|
24176
24406
|
if (d.predictable === true) {
|
|
24177
24407
|
html += '<div style="padding:10px 12px;border-radius:6px;background:rgba(16,185,129,0.12);color:var(--green);font-size:13px;font-weight:500">'
|
|
24178
24408
|
+ '🔒 <strong>Predictable</strong> — what you see here is exactly what will run. No MEMORY.md, no team comms, no auto-matched skills injected at fire-time.'
|
|
24179
24409
|
+ '</div>';
|
|
24180
|
-
} else if (d.predictable === false) {
|
|
24181
|
-
html += '<div style="padding:10px 12px;border-radius:6px;background:rgba(245,158,11,0.12);color:var(--yellow);font-size:13px;font-weight:500">'
|
|
24182
|
-
+ '⚠ <strong>Reads memory at fire-time</strong> — fire-time will ALSO inject MEMORY.md, recent team comms, delegation queue, and auto-matched skills. Output may differ from this preview if those drift between now and fire.'
|
|
24183
|
-
+ '</div>';
|
|
24184
24410
|
} else {
|
|
24185
|
-
|
|
24186
|
-
|
|
24411
|
+
var head = (d.predictable === false)
|
|
24412
|
+
? '⚠ <strong>Legacy mode (explicit)</strong>'
|
|
24413
|
+
: '⚠ <strong>Legacy mode (default)</strong>';
|
|
24414
|
+
html += '<div style="padding:10px 12px;border-radius:6px;background:rgba(245,158,11,0.12);color:var(--yellow);font-size:13px;font-weight:500">'
|
|
24415
|
+
+ head + ' — fire-time ALSO injects MEMORY.md, team activity, the delegation queue, and auto-matches MCP servers based on prompt text. '
|
|
24416
|
+
+ '<a href="#" onclick="switchCronTab(\\x27configure\\x27);enablePredictableFromBanner();return false" style="color:var(--accent);text-decoration:underline">Switch to Predictable Mode →</a>'
|
|
24187
24417
|
+ '</div>';
|
|
24188
24418
|
}
|
|
24189
24419
|
html += '</div>';
|
|
24190
|
-
|
|
24420
|
+
|
|
24421
|
+
// Warnings band (e.g. pinned skill missing)
|
|
24191
24422
|
if (Array.isArray(d.warnings) && d.warnings.length > 0) {
|
|
24192
24423
|
html += '<div class="preview-section">';
|
|
24193
24424
|
for (var w = 0; w < d.warnings.length; w++) {
|
|
@@ -24195,17 +24426,55 @@ function renderCronPreview(d) {
|
|
|
24195
24426
|
}
|
|
24196
24427
|
html += '</div>';
|
|
24197
24428
|
}
|
|
24198
|
-
|
|
24429
|
+
|
|
24430
|
+
// Summary
|
|
24199
24431
|
html += '<div class="preview-section">';
|
|
24200
24432
|
html += '<h4>Summary</h4>';
|
|
24201
|
-
html += '<div style="font-size:12px;color:var(--text-secondary);line-height:1.
|
|
24433
|
+
html += '<div style="font-size:12px;color:var(--text-secondary);line-height:1.7">';
|
|
24202
24434
|
html += '<div><strong>Schedule:</strong> ' + esc(d.job.schedule) + '</div>';
|
|
24203
24435
|
html += '<div><strong>Tier:</strong> ' + esc(d.tier) + ' (effort: ' + esc(d.effort) + (d.maxBudgetUsd ? ', budget: $' + d.maxBudgetUsd : '') + ')</div>';
|
|
24204
24436
|
html += '<div><strong>Agent:</strong> ' + esc(d.profile ? (d.profile.name + ' (' + d.profile.slug + ')') : 'Clementine (global)') + '</div>';
|
|
24205
24437
|
if (d.job.tags && d.job.tags.length) html += '<div><strong>Tags:</strong> ' + d.job.tags.map(function(t) { return '#' + esc(t); }).join(', ') + '</div>';
|
|
24206
24438
|
if (d.job.category) html += '<div><strong>Category:</strong> ' + esc(d.job.category) + '</div>';
|
|
24207
24439
|
html += '</div></div>';
|
|
24208
|
-
|
|
24440
|
+
|
|
24441
|
+
// ── MCP servers — split into pinned (allowlist) vs auto-injected ─
|
|
24442
|
+
// The headline mismatch: a job's prompt may forbid kernel, but legacy
|
|
24443
|
+
// mode auto-injects it because the bundle router matched on prompt
|
|
24444
|
+
// text. Surface that explicitly with chip color so the user can act.
|
|
24445
|
+
var configuredMcp = (d.job && Array.isArray(d.job.allowedMcpServers)) ? d.job.allowedMcpServers : null;
|
|
24446
|
+
var pinnedSet = new Set(configuredMcp || []);
|
|
24447
|
+
var hasAllowlist = configuredMcp !== null && configuredMcp.length > 0;
|
|
24448
|
+
html += '<div class="preview-section">';
|
|
24449
|
+
html += '<h4>MCP servers attached at fire-time (' + d.mcpServers.length + ')</h4>';
|
|
24450
|
+
if (d.mcpServers.length === 0) {
|
|
24451
|
+
html += '<div style="color:var(--text-muted);font-size:12px;font-style:italic">No additional MCP servers (clementine-tools always available).</div>';
|
|
24452
|
+
} else {
|
|
24453
|
+
var anyAuto = false;
|
|
24454
|
+
var chips = '';
|
|
24455
|
+
for (var k = 0; k < d.mcpServers.length; k++) {
|
|
24456
|
+
var m = d.mcpServers[k];
|
|
24457
|
+
var isPinned = hasAllowlist && pinnedSet.has(m.name);
|
|
24458
|
+
var cls = (hasAllowlist && isPinned) ? 'preview-chip pinned' : (hasAllowlist ? 'preview-chip' : 'preview-chip auto');
|
|
24459
|
+
if (!hasAllowlist || !isPinned) anyAuto = true;
|
|
24460
|
+
chips += '<span class="' + cls + '" title="' + esc(m.description || '') + '">' + esc(m.name) + '</span>';
|
|
24461
|
+
}
|
|
24462
|
+
if (hasAllowlist) {
|
|
24463
|
+
html += '<div style="margin-bottom:6px"><span class="preview-chip-group-label">Pinned by your allowlist:</span></div>';
|
|
24464
|
+
} else if (anyAuto) {
|
|
24465
|
+
html += '<div style="margin-bottom:6px"><span class="preview-chip-group-label" style="color:var(--yellow)">Auto-injected by prompt match (no allowlist set):</span></div>';
|
|
24466
|
+
}
|
|
24467
|
+
html += '<div>' + chips + '</div>';
|
|
24468
|
+
if (!hasAllowlist && d.mcpServers.length > 0 && d.predictable !== true) {
|
|
24469
|
+
html += '<div style="margin-top:8px;padding:8px 10px;border-radius:6px;background:var(--bg-tertiary);font-size:11px;color:var(--text-muted);line-height:1.5">'
|
|
24470
|
+
+ 'These servers were auto-attached because the prompt text matched bundle keywords. '
|
|
24471
|
+
+ 'To restrict the surface, switch to Predictable Mode and add an explicit MCP allowlist on the Configure tab.'
|
|
24472
|
+
+ '</div>';
|
|
24473
|
+
}
|
|
24474
|
+
}
|
|
24475
|
+
html += '</div>';
|
|
24476
|
+
|
|
24477
|
+
// ── Skills — pinned vs auto-matched, split visually ─
|
|
24209
24478
|
html += '<div class="preview-section">';
|
|
24210
24479
|
html += '<h4>Skills injected (' + d.skillsApplied.length + ')</h4>';
|
|
24211
24480
|
if (d.skillsApplied.length === 0) {
|
|
@@ -24214,8 +24483,8 @@ function renderCronPreview(d) {
|
|
|
24214
24483
|
for (var i = 0; i < d.skillsApplied.length; i++) {
|
|
24215
24484
|
var s = d.skillsApplied[i];
|
|
24216
24485
|
var sourceTag = s.source === 'pinned'
|
|
24217
|
-
? '<span style="color:var(--
|
|
24218
|
-
: '<span style="color:var(--
|
|
24486
|
+
? '<span style="color:var(--purple);font-size:10px;text-transform:uppercase;letter-spacing:0.4px;font-weight:600">PINNED</span>'
|
|
24487
|
+
: '<span style="color:var(--yellow);font-size:10px;text-transform:uppercase;letter-spacing:0.4px;font-weight:600">AUTO-MATCHED</span>';
|
|
24219
24488
|
html += '<div class="preview-skill">';
|
|
24220
24489
|
html += '<div class="preview-skill-title">' + esc(s.title) + ' <span style="color:var(--text-muted);font-size:10px;font-weight:normal">(' + esc(s.name) + ')</span> ' + sourceTag + '</div>';
|
|
24221
24490
|
if (s.toolsUsed && s.toolsUsed.length) {
|
|
@@ -24226,21 +24495,7 @@ function renderCronPreview(d) {
|
|
|
24226
24495
|
}
|
|
24227
24496
|
}
|
|
24228
24497
|
html += '</div>';
|
|
24229
|
-
|
|
24230
|
-
html += '<div class="preview-section">';
|
|
24231
|
-
html += '<h4>MCP servers (' + d.mcpServers.length + ')</h4>';
|
|
24232
|
-
if (d.mcpServers.length === 0) {
|
|
24233
|
-
html += '<div style="color:var(--text-muted);font-size:12px;font-style:italic">No additional MCP servers (clementine-tools always available).</div>';
|
|
24234
|
-
} else {
|
|
24235
|
-
for (var k = 0; k < d.mcpServers.length; k++) {
|
|
24236
|
-
var m = d.mcpServers[k];
|
|
24237
|
-
html += '<div style="padding:6px 0;border-bottom:1px dashed var(--border-light)">';
|
|
24238
|
-
html += '<div style="font-weight:600;font-size:12px;color:var(--purple)">' + esc(m.name) + '</div>';
|
|
24239
|
-
if (m.description) html += '<div style="font-size:11px;color:var(--text-muted)">' + esc(m.description) + '</div>';
|
|
24240
|
-
html += '</div>';
|
|
24241
|
-
}
|
|
24242
|
-
}
|
|
24243
|
-
html += '</div>';
|
|
24498
|
+
|
|
24244
24499
|
// Tool allowlist
|
|
24245
24500
|
html += '<div class="preview-section">';
|
|
24246
24501
|
html += '<h4>Tool allowlist</h4>';
|
|
@@ -24252,9 +24507,10 @@ function renderCronPreview(d) {
|
|
|
24252
24507
|
html += '</div>';
|
|
24253
24508
|
}
|
|
24254
24509
|
html += '</div>';
|
|
24255
|
-
|
|
24510
|
+
|
|
24511
|
+
// Built prompt (what the agent literally receives)
|
|
24256
24512
|
html += '<div class="preview-section">';
|
|
24257
|
-
html += '<h4>Built prompt <span style="font-weight:normal;color:var(--text-muted)">(' + d.builtPrompt.length + ' chars — what the agent receives)</span></h4>';
|
|
24513
|
+
html += '<h4>Built prompt <span style="font-weight:normal;color:var(--text-muted)">(' + d.builtPrompt.length + ' chars — what the agent receives verbatim)</span></h4>';
|
|
24258
24514
|
html += '<div class="preview-prompt-box">' + esc(d.builtPrompt) + '</div>';
|
|
24259
24515
|
html += '</div>';
|
|
24260
24516
|
return html;
|
|
@@ -24263,8 +24519,15 @@ function renderCronPreview(d) {
|
|
|
24263
24519
|
function closeCronModal() {
|
|
24264
24520
|
document.getElementById('cron-modal').classList.remove('show');
|
|
24265
24521
|
editingCronJob = null;
|
|
24522
|
+
_cronPreviewLoadedFor = null;
|
|
24266
24523
|
var attachList = document.getElementById('cron-attachments-list');
|
|
24267
24524
|
if (attachList) attachList.innerHTML = '';
|
|
24525
|
+
var bannerHost = document.getElementById('cron-legacy-banner-host');
|
|
24526
|
+
if (bannerHost) bannerHost.innerHTML = '';
|
|
24527
|
+
// Reset preview pane content so a stale view from a previous job doesn't
|
|
24528
|
+
// flash on the next open.
|
|
24529
|
+
var previewBody = document.getElementById('cron-preview-body');
|
|
24530
|
+
if (previewBody) previewBody.innerHTML = '<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">Save the task first, then switch back here to see exactly what the agent will receive.</div>';
|
|
24268
24531
|
resetCronTrainingChat();
|
|
24269
24532
|
}
|
|
24270
24533
|
|
|
@@ -24414,6 +24677,7 @@ async function saveCronJob() {
|
|
|
24414
24677
|
predictable,
|
|
24415
24678
|
};
|
|
24416
24679
|
|
|
24680
|
+
var wasEditing = !!editingCronJob;
|
|
24417
24681
|
if (editingCronJob) {
|
|
24418
24682
|
await apiJson('PUT', '/api/cron/' + encodeURIComponent(editingCronJob), body);
|
|
24419
24683
|
if (_pendingAttachments.length > 0) await uploadPendingAttachments(editingCronJob);
|
|
@@ -24423,8 +24687,18 @@ async function saveCronJob() {
|
|
|
24423
24687
|
var attachJobName = _cronAgentContext ? (_cronAgentContext + ':' + name) : name;
|
|
24424
24688
|
if (_pendingAttachments.length > 0) await uploadPendingAttachments(attachJobName);
|
|
24425
24689
|
}
|
|
24426
|
-
|
|
24690
|
+
// Refresh card list so the data is fresh for any subsequent edit.
|
|
24427
24691
|
refreshCron();
|
|
24692
|
+
// After save: stay open and flip to "What will run" so the user can
|
|
24693
|
+
// confirm what they just saved actually runs the way they intended.
|
|
24694
|
+
// This is the close-the-loop UX move that makes Predictable Mode visible.
|
|
24695
|
+
markCronPreviewDirty();
|
|
24696
|
+
if (wasEditing) {
|
|
24697
|
+
toast('Saved. Showing what will run…', 'success');
|
|
24698
|
+
switchCronTab('preview');
|
|
24699
|
+
} else {
|
|
24700
|
+
closeCronModal();
|
|
24701
|
+
}
|
|
24428
24702
|
}
|
|
24429
24703
|
|
|
24430
24704
|
// ── Cron Training Chat ───────────────────
|