clementine-agent 1.18.79 → 1.18.81

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.
Files changed (2) hide show
  1. package/dist/cli/dashboard.js +343 -91
  2. package/package.json +1 -1
@@ -15124,6 +15124,44 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
15124
15124
  0%, 100% { opacity: 0.4; transform: scale(0.85); }
15125
15125
  50% { opacity: 1; transform: scale(1); }
15126
15126
  }
15127
+ /* PRD Phase 1.3a: inner config-tab strip — switches which Configure
15128
+ section blocks are visible without changing the outer Configure /
15129
+ What-will-run / Last-run tabs. Visual: pill chips along the top of
15130
+ the Configure pane. */
15131
+ .cron-config-tabs {
15132
+ display: flex;
15133
+ gap: 4px;
15134
+ margin: 0 0 14px;
15135
+ border-bottom: 1px solid var(--border);
15136
+ padding-bottom: 0;
15137
+ }
15138
+ .cron-config-tab-btn {
15139
+ background: none;
15140
+ border: none;
15141
+ color: var(--text-muted);
15142
+ font-size: 12px;
15143
+ font-weight: 500;
15144
+ padding: 8px 12px;
15145
+ cursor: pointer;
15146
+ border-bottom: 2px solid transparent;
15147
+ transition: color 0.15s, border-color 0.15s;
15148
+ margin-bottom: -1px;
15149
+ }
15150
+ .cron-config-tab-btn:hover { color: var(--text-primary); }
15151
+ .cron-config-tab-btn.active {
15152
+ color: var(--accent);
15153
+ border-bottom-color: var(--accent);
15154
+ }
15155
+ /* Sections are tagged with data-config-tab; the active tab attr on the
15156
+ Configure pane controls visibility. .force-show overrides for power-user
15157
+ "show everything" later. */
15158
+ #cron-tab-configure[data-active-config-tab="basics"] [data-config-tab]:not([data-config-tab="basics"]),
15159
+ #cron-tab-configure[data-active-config-tab="prompt"] [data-config-tab]:not([data-config-tab="prompt"]),
15160
+ #cron-tab-configure[data-active-config-tab="tools"] [data-config-tab]:not([data-config-tab="tools"]),
15161
+ #cron-tab-configure[data-active-config-tab="scope"] [data-config-tab]:not([data-config-tab="scope"]),
15162
+ #cron-tab-configure[data-active-config-tab="limits"] [data-config-tab]:not([data-config-tab="limits"]) {
15163
+ display: none;
15164
+ }
15127
15165
  /* ── Trick capability strip (skills + MCP + tools at a glance) ─── */
15128
15166
  .task-cap-strip {
15129
15167
  border-top: 1px solid var(--border-light);
@@ -16318,14 +16356,18 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16318
16356
 
16319
16357
  <!-- ═══ Tasks Page — single unified surface ═══ -->
16320
16358
  <div class="page" id="page-build">
16321
- <!-- Sub-tabs hidden by default. Cron is the single surface; multi-step
16322
- workflows (formerly "Tricks") are still accessible via deep-link
16323
- ?tab=workflows or by toggling .show-workflows-tabs on the page. -->
16324
- <div id="build-tabs" style="display:none;gap:4px;padding:8px 18px 0;background:var(--bg-secondary);border-bottom:1px solid var(--border);flex-shrink:0">
16359
+ <!-- PRD Phase 2: top-level tab strip within the Tasks domain.
16360
+ Tasks (default) + Tools & MCP catalog. Workflows still reachable
16361
+ via deep-link ?tab=workflows for power users with existing
16362
+ multi-step workflows. -->
16363
+ <div id="build-tabs" style="display:flex;gap:4px;padding:8px 18px 0;background:var(--bg-secondary);border-bottom:1px solid var(--border);flex-shrink:0">
16325
16364
  <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">
16326
16365
  <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>
16327
16366
  </button>
16328
- <button class="build-tab-btn" data-build-tab="workflows" onclick="switchBuildTab('workflows')" 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">
16367
+ <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
+ <span style="margin-right:6px">🧰</span>Tools &amp; 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
+ </button>
16370
+ <button class="build-tab-btn" data-build-tab="workflows" onclick="switchBuildTab('workflows')" style="display:none;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">
16329
16371
  <span style="margin-right:6px">🔧</span>Workflows <span id="build-tab-workflows-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>
16330
16372
  </button>
16331
16373
  </div>
@@ -16337,6 +16379,12 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16337
16379
  <div id="build-tab-crons" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
16338
16380
  <div id="panel-cron"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading scheduled tasks…</div></div>
16339
16381
  </div>
16382
+ <!-- ── PRD Phase 2: Tools & MCP catalog ────────────────────────────────
16383
+ Read-only foundation in 1.18.81. Future slices: per-tool bindings,
16384
+ Reconnect/Toggle/Edit actions, Approval Mode + Max-auto-runs config. -->
16385
+ <div id="build-tab-toolsmcp" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
16386
+ <div id="panel-toolsmcp"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading Tools &amp; MCP catalog…</div></div>
16387
+ </div>
16340
16388
  <!-- Tricks (workflows) tab — existing RoutinesUI surface ─────────────────── -->
16341
16389
  <div id="build-tab-workflows" style="display:none;flex:1;min-height:0;display:flex;flex-direction:column">
16342
16390
  <!-- Toolbar -->
@@ -19912,12 +19960,24 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19912
19960
  </div>
19913
19961
  <div class="modal-body">
19914
19962
  <!-- ── Tab: Configure ─────────────────────────────────────────── -->
19915
- <div class="cron-tab-pane active" id="cron-tab-configure">
19963
+ <!-- PRD §5.1 right-inspector pattern lives here as inner tabs (1.3a).
19964
+ data-active-config-tab controls which section blocks render via
19965
+ CSS — no JS show/hide needed beyond setting the attribute. -->
19966
+ <div class="cron-tab-pane active" id="cron-tab-configure" data-active-config-tab="basics">
19916
19967
  <!-- Legacy / mismatch banner (populated by openEditCronModal) -->
19917
19968
  <div id="cron-legacy-banner-host"></div>
19918
19969
 
19970
+ <!-- Inner tab strip — pill nav swapping which section blocks show -->
19971
+ <div class="cron-config-tabs" role="tablist">
19972
+ <button type="button" class="cron-config-tab-btn active" data-config-tab-btn="basics" onclick="switchCronConfigTab('basics')">Basics</button>
19973
+ <button type="button" class="cron-config-tab-btn" data-config-tab-btn="prompt" onclick="switchCronConfigTab('prompt')">Prompt</button>
19974
+ <button type="button" class="cron-config-tab-btn" data-config-tab-btn="tools" onclick="switchCronConfigTab('tools')">Tools &amp; MCP</button>
19975
+ <button type="button" class="cron-config-tab-btn" data-config-tab-btn="scope" onclick="switchCronConfigTab('scope')">Scope</button>
19976
+ <button type="button" class="cron-config-tab-btn" data-config-tab-btn="limits" onclick="switchCronConfigTab('limits')">Limits</button>
19977
+ </div>
19978
+
19919
19979
  <!-- Identity: name + category + tags -->
19920
- <div class="cron-section-card">
19980
+ <div class="cron-section-card" data-config-tab="basics">
19921
19981
  <h4>Identity</h4>
19922
19982
  <p class="cron-section-desc">A unique name and optional grouping for the dashboard.</p>
19923
19983
  <div class="form-group">
@@ -19933,7 +19993,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19933
19993
  </div>
19934
19994
 
19935
19995
  <!-- Schedule -->
19936
- <div class="cron-section-card">
19996
+ <div class="cron-section-card" data-config-tab="basics">
19937
19997
  <h4>Schedule</h4>
19938
19998
  <p class="cron-section-desc">When this task fires. Pick a frequency or write a cron expression.</p>
19939
19999
  <div class="form-group" style="margin:0">
@@ -20022,7 +20082,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20022
20082
  </div>
20023
20083
 
20024
20084
  <!-- What it does: prompt + context + reference files -->
20025
- <div class="cron-section-card">
20085
+ <div class="cron-section-card" data-config-tab="prompt">
20026
20086
  <h4>What it does</h4>
20027
20087
  <p class="cron-section-desc">The instruction the agent receives. Long prompts are fine — drag the corner to resize.</p>
20028
20088
  <div class="form-group">
@@ -20050,7 +20110,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20050
20110
  The single most important new field set. Without one of these, a
20051
20111
  run "finished"; with one, a run "accomplished what it was meant
20052
20112
  to". Banner warns (does not block) when neither is set. -->
20053
- <div class="cron-section-card">
20113
+ <div class="cron-section-card" data-config-tab="prompt">
20054
20114
  <h4>Goal <span style="color:var(--text-muted);font-weight:normal;font-size:12px">— how do you know this task succeeded?</span></h4>
20055
20115
  <p class="cron-section-desc">Optional but strongly recommended. Use plain English (an evaluator agent grades the run) or a JSON Schema (validated against the agent's structured output).</p>
20056
20116
  <div id="cron-goal-warning" style="display:none;margin-bottom:12px;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">
@@ -20073,7 +20133,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20073
20133
  </div>
20074
20134
 
20075
20135
  <!-- Skills & tools: pinned skills + MCP + tools + tags -->
20076
- <div class="cron-section-card">
20136
+ <div class="cron-section-card" data-config-tab="tools">
20077
20137
  <h4>Skills &amp; tools</h4>
20078
20138
  <p class="cron-section-desc">Pin the skills and tools this task should use. Switch to the <strong>What will run</strong> tab to see exactly what the agent receives.</p>
20079
20139
 
@@ -20124,85 +20184,90 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20124
20184
  </div>
20125
20185
  </div>
20126
20186
 
20127
- <!-- Advanced: tier + workdir + mode + max-hours + retries + after -->
20128
- <details class="cron-section-card" style="padding:0">
20129
- <summary style="padding:14px 16px;cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between">
20130
- <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>
20131
- <span style="color:var(--text-muted);font-size:11px">▾</span>
20132
- </summary>
20133
- <div style="padding:0 16px 14px">
20134
- <div class="form-row">
20135
- <div class="form-group">
20136
- <label class="form-label">Tier</label>
20137
- <select id="cron-tier">
20138
- <option value="1">Tier 1 — Read-only (vault, search, web)</option>
20139
- <option value="2">Tier 2 — Read + Write (Bash, files, sub-agents)</option>
20140
- <option value="3">Tier 3 — Full access (use with caution)</option>
20141
- </select>
20142
- </div>
20143
- <div class="form-group">
20144
- <label class="form-label">Project Context <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
20145
- <select id="cron-workdir">
20146
- <option value="">None — runs in default context</option>
20147
- </select>
20148
- <div class="form-hint">Run inside a project directory. Agent gets that project's CLAUDE.md.</div>
20149
- </div>
20187
+ <!-- ── Scope: where the task can read/write ── -->
20188
+ <div class="cron-section-card" data-config-tab="scope">
20189
+ <h4>Scope</h4>
20190
+ <p class="cron-section-desc">Where the agent runs and what files it can read.</p>
20191
+ <div class="form-row">
20192
+ <div class="form-group">
20193
+ <label class="form-label">Project Context <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
20194
+ <select id="cron-workdir">
20195
+ <option value="">None — runs in default context</option>
20196
+ </select>
20197
+ <div class="form-hint">Run inside a project directory. Agent gets that project's CLAUDE.md.</div>
20150
20198
  </div>
20151
- <!-- PRD Phase 1: read scope beyond cwd. One absolute path per line. -->
20152
- <div class="form-row">
20153
- <div class="form-group" style="flex:1">
20154
- <label class="form-label">Additional read directories <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
20155
- <textarea id="cron-add-dirs" rows="2" placeholder="/Users/me/notes&#10;/Users/me/clients/acme" style="font-family:'JetBrains Mono',monospace;font-size:11px"></textarea>
20156
- <div class="form-hint">One absolute path per line. The agent gets read access to these in addition to the Project Context cwd.</div>
20157
- </div>
20199
+ </div>
20200
+ <!-- PRD Phase 1: read scope beyond cwd. One absolute path per line. -->
20201
+ <div class="form-row">
20202
+ <div class="form-group" style="flex:1">
20203
+ <label class="form-label">Additional read directories <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
20204
+ <textarea id="cron-add-dirs" rows="2" placeholder="/Users/me/notes&#10;/Users/me/clients/acme" style="font-family:'JetBrains Mono',monospace;font-size:11px"></textarea>
20205
+ <div class="form-hint">One absolute path per line. The agent gets read access to these in addition to the Project Context cwd.</div>
20158
20206
  </div>
20159
- <div class="form-row">
20160
- <div class="form-group">
20161
- <label class="form-label">Mode</label>
20162
- <select id="cron-mode" onchange="toggleUnleashedOptions()">
20163
- <option value="standard">Standard</option>
20164
- <option value="unleashed">Unleashed (long-running)</option>
20165
- </select>
20166
- <div class="form-hint">Unleashed runs in phases with checkpointing for hour-scale tasks.</div>
20167
- </div>
20168
- <div class="form-group" id="cron-maxhours-group" style="display:none">
20169
- <label class="form-label">Max Hours</label>
20170
- <select id="cron-maxhours">
20171
- <option value="1">1 hour</option>
20172
- <option value="2">2 hours</option>
20173
- <option value="4">4 hours</option>
20174
- <option value="6" selected>6 hours (default)</option>
20175
- <option value="8">8 hours</option>
20176
- <option value="12">12 hours</option>
20177
- <option value="24">24 hours</option>
20178
- </select>
20179
- </div>
20207
+ </div>
20208
+ </div>
20209
+
20210
+ <!-- ── Limits: budgets, mode, retries, chain triggers ── -->
20211
+ <div class="cron-section-card" data-config-tab="limits">
20212
+ <h4>Limits</h4>
20213
+ <p class="cron-section-desc">Permission level, runtime mode, and retry/chain rules.</p>
20214
+ <div class="form-row">
20215
+ <div class="form-group">
20216
+ <label class="form-label">Tier</label>
20217
+ <select id="cron-tier">
20218
+ <option value="1">Tier 1 — Read-only (vault, search, web)</option>
20219
+ <option value="2">Tier 2 — Read + Write (Bash, files, sub-agents)</option>
20220
+ <option value="3">Tier 3 — Full access (use with caution)</option>
20221
+ </select>
20222
+ <div class="form-hint">Permission level. Higher tiers can do more but require trust.</div>
20180
20223
  </div>
20181
- <div class="form-row">
20182
- <div class="form-group">
20183
- <label class="form-label">Max Retries <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
20184
- <select id="cron-max-retries">
20185
- <option value="">Auto (based on error history)</option>
20186
- <option value="0">0 — No retries</option>
20187
- <option value="1">1</option>
20188
- <option value="2">2</option>
20189
- <option value="3">3</option>
20190
- <option value="5">5</option>
20191
- </select>
20192
- <div class="form-hint">Override automatic retry count for transient errors.</div>
20193
- </div>
20194
- <div class="form-group">
20195
- <label class="form-label">After Job <span style="color:var(--text-muted);font-weight:normal">(chain)</span></label>
20196
- <select id="cron-after">
20197
- <option value="">None — runs on schedule</option>
20198
- </select>
20199
- <div class="form-hint">Trigger after another job succeeds (ignores schedule).</div>
20200
- </div>
20224
+ <div class="form-group">
20225
+ <label class="form-label">Mode</label>
20226
+ <select id="cron-mode" onchange="toggleUnleashedOptions()">
20227
+ <option value="standard">Standard</option>
20228
+ <option value="unleashed">Unleashed (long-running)</option>
20229
+ </select>
20230
+ <div class="form-hint">Unleashed runs in phases with checkpointing for hour-scale tasks.</div>
20201
20231
  </div>
20202
20232
  </div>
20203
- </details>
20233
+ <div class="form-row">
20234
+ <div class="form-group" id="cron-maxhours-group" style="display:none">
20235
+ <label class="form-label">Max Hours</label>
20236
+ <select id="cron-maxhours">
20237
+ <option value="1">1 hour</option>
20238
+ <option value="2">2 hours</option>
20239
+ <option value="4">4 hours</option>
20240
+ <option value="6" selected>6 hours (default)</option>
20241
+ <option value="8">8 hours</option>
20242
+ <option value="12">12 hours</option>
20243
+ <option value="24">24 hours</option>
20244
+ </select>
20245
+ </div>
20246
+ <div class="form-group">
20247
+ <label class="form-label">Max Retries <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
20248
+ <select id="cron-max-retries">
20249
+ <option value="">Auto (based on error history)</option>
20250
+ <option value="0">0 — No retries</option>
20251
+ <option value="1">1</option>
20252
+ <option value="2">2</option>
20253
+ <option value="3">3</option>
20254
+ <option value="5">5</option>
20255
+ </select>
20256
+ <div class="form-hint">Override automatic retry count for transient errors.</div>
20257
+ </div>
20258
+ </div>
20259
+ <div class="form-row">
20260
+ <div class="form-group">
20261
+ <label class="form-label">After Job <span style="color:var(--text-muted);font-weight:normal">(chain)</span></label>
20262
+ <select id="cron-after">
20263
+ <option value="">None — runs on schedule</option>
20264
+ </select>
20265
+ <div class="form-hint">Trigger after another job succeeds (ignores schedule).</div>
20266
+ </div>
20267
+ </div>
20268
+ </div>
20204
20269
 
20205
- <!-- Training Chat -->
20270
+ <!-- Training Chat — visible across all config tabs (it's a tool, not a field group) -->
20206
20271
  <div class="form-group" id="cron-training-section" style="display:none">
20207
20272
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
20208
20273
  <label class="form-label" style="margin:0">Training Chat</label>
@@ -21093,12 +21158,27 @@ function switchBuildTab(tab) {
21093
21158
  var workPane = document.getElementById('build-tab-workflows');
21094
21159
  var cronPane = document.getElementById('build-tab-crons');
21095
21160
  var tplPane = document.getElementById('build-tab-templates');
21161
+ var toolsmcpPane = document.getElementById('build-tab-toolsmcp');
21096
21162
  var headerStrip = document.getElementById('build-header-strip');
21097
21163
  var usagePanel = document.getElementById('build-usage-panel');
21098
21164
  var newBtn = document.getElementById('builder-new-btn');
21099
21165
  // Always close any open workflow when changing tabs — switching context
21100
21166
  // is a clean slate, not a stale node hanging on the canvas.
21101
21167
  if (typeof closeBuilderCanvas === 'function') closeBuilderCanvas();
21168
+ // Default: hide the Tools & MCP pane unless we're explicitly on it.
21169
+ if (toolsmcpPane && tab !== 'toolsmcp') toolsmcpPane.style.display = 'none';
21170
+ if (tab === 'toolsmcp') {
21171
+ // PRD Phase 2: Tools & MCP catalog. Read-only foundation in 1.18.81.
21172
+ if (workPane) workPane.style.display = 'none';
21173
+ if (cronPane) cronPane.style.display = 'none';
21174
+ if (tplPane) tplPane.style.display = 'none';
21175
+ if (toolsmcpPane) toolsmcpPane.style.display = 'block';
21176
+ if (headerStrip) headerStrip.style.display = 'none';
21177
+ if (usagePanel) usagePanel.style.display = 'none';
21178
+ if (newBtn) newBtn.style.display = 'none';
21179
+ if (typeof refreshToolsMcpCatalog === 'function') refreshToolsMcpCatalog();
21180
+ return;
21181
+ }
21102
21182
  if (tab === 'templates') {
21103
21183
  if (workPane) workPane.style.display = 'none';
21104
21184
  if (cronPane) cronPane.style.display = 'none';
@@ -23477,6 +23557,156 @@ function renderRunningCard(item) {
23477
23557
  + '</div></div>';
23478
23558
  }
23479
23559
 
23560
+ // ── PRD Phase 2: Tools & MCP catalog ──────────────────────────────────
23561
+ // Read-only foundation in 1.18.81. Renders the four-card taxonomy:
23562
+ // • Built-in — Claude SDK native tools (Read/Write/Bash/etc.)
23563
+ // • Custom MCP — in-process SDK MCP servers
23564
+ // • Shell command — CLI wrappers
23565
+ // • External MCP — stdio / sse / http MCP servers
23566
+ // Pulls from /api/mcp-status (live status) + /api/mcp-servers (config).
23567
+ // Future slices wire Reconnect/Toggle/Edit + the McpToolBinding modal.
23568
+ async function refreshToolsMcpCatalog() {
23569
+ var panel = document.getElementById('panel-toolsmcp');
23570
+ if (!panel) return;
23571
+ panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading Tools &amp; MCP catalog…</div>';
23572
+ var statusMap = {};
23573
+ var servers = [];
23574
+ try {
23575
+ var sR = await apiFetch('/api/mcp-status');
23576
+ var statusJson = await sR.json();
23577
+ statusMap = statusJson || {};
23578
+ } catch (e) { /* status is optional — servers still render without it */ }
23579
+ try {
23580
+ var lR = await apiFetch('/api/mcp-servers');
23581
+ var lJson = await lR.json();
23582
+ servers = (lJson && lJson.servers) || [];
23583
+ } catch (e) {
23584
+ panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--red)">Failed to load MCP servers: ' + esc(String(e)) + '</div>';
23585
+ return;
23586
+ }
23587
+ var tabCount = document.getElementById('build-tab-toolsmcp-count');
23588
+ if (tabCount) {
23589
+ tabCount.textContent = servers.length;
23590
+ tabCount.style.display = servers.length > 0 ? '' : 'none';
23591
+ }
23592
+ // Bucket servers into the four PRD categories. The existing
23593
+ // ManagedMcpServer type doesn't have an explicit "kind" field, so we
23594
+ // infer: stdio with a known shell binary → 'shell', stdio bundled with
23595
+ // clementine → 'builtin', stdio external command → 'external_stdio',
23596
+ // http/sse → 'external_remote'. The bucket keys map to the PRD's four
23597
+ // taxonomy cards.
23598
+ var buckets = { builtin: [], custom: [], shell: [], external: [] };
23599
+ for (var i = 0; i < servers.length; i++) {
23600
+ var s = servers[i];
23601
+ var name = s.name || '';
23602
+ var type = s.type || 'stdio';
23603
+ var cmd = s.command || '';
23604
+ var kind;
23605
+ // The clementine-tools server is an in-process bundle
23606
+ if (name === 'clementine-tools' || name === 'kernel') kind = 'builtin';
23607
+ else if (type === 'http' || type === 'sse') kind = 'external';
23608
+ else if (/^(sf|gh|gcloud|kubectl|docker|aws|az|terraform)$/.test(cmd) || /\\b(sf|gh|gcloud|kubectl)$/.test(cmd)) kind = 'shell';
23609
+ else kind = 'external'; // default for stdio external MCP
23610
+ buckets[kind].push(s);
23611
+ }
23612
+ var html = '';
23613
+ // Header strip
23614
+ html += '<div style="margin-bottom:18px"><h2 style="margin:0 0 4px;font-size:18px;font-weight:600;color:var(--text-primary)">Tools &amp; MCP catalog</h2>'
23615
+ + '<div style="font-size:12px;color:var(--text-muted)">'+ esc(servers.length) +' MCP server' + (servers.length === 1 ? '' : 's') + ' configured. Click any task in the Tasks tab to bind specific tools to that task.</div></div>';
23616
+ // Four-card taxonomy. Each section is a labeled bucket of cards.
23617
+ var sections = [
23618
+ { key: 'builtin', label: 'Built-in', desc: 'Claude SDK native tools — always available to every task at the agent profile\\x27s permission tier.' },
23619
+ { key: 'custom', label: 'Custom in-process MCP', desc: 'MCP servers defined in clementine\\x27s code, loaded inside the daemon process.' },
23620
+ { key: 'shell', label: 'Shell commands', desc: 'Local CLI binaries (sf, gh, gcloud…) wrapped as MCP servers.' },
23621
+ { key: 'external', label: 'External MCP servers', desc: 'Third-party MCP servers reached over stdio, SSE, or HTTP.' },
23622
+ ];
23623
+ for (var k = 0; k < sections.length; k++) {
23624
+ var sec = sections[k];
23625
+ var bucket = buckets[sec.key] || [];
23626
+ html += '<div style="margin-bottom:24px">';
23627
+ html += '<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:10px">'
23628
+ + '<h3 style="margin:0;font-size:14px;font-weight:600;color:var(--text-primary)">' + esc(sec.label) + '</h3>'
23629
+ + '<span style="font-size:11px;color:var(--text-muted);font-weight:500">' + bucket.length + '</span>'
23630
+ + '</div>';
23631
+ html += '<div style="font-size:11px;color:var(--text-muted);margin-bottom:12px">' + esc(sec.desc) + '</div>';
23632
+ if (bucket.length === 0) {
23633
+ html += '<div class="empty-state" style="padding:14px;color:var(--text-muted);font-size:12px;background:var(--bg-secondary);border:1px dashed var(--border);border-radius:6px">No servers in this bucket.</div>';
23634
+ } else {
23635
+ html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px">';
23636
+ for (var b = 0; b < bucket.length; b++) html += renderMcpCatalogCard(bucket[b], statusMap);
23637
+ html += '</div>';
23638
+ }
23639
+ html += '</div>';
23640
+ }
23641
+ panel.innerHTML = html;
23642
+ }
23643
+
23644
+ // Render one MCP server card. Status pill colors mirror the PRD's five
23645
+ // states (connected / failed / needs-auth / pending / disabled). The
23646
+ // statusMap shape comes from gw.getMcpStatus() — varies a bit between
23647
+ // SDK versions; we defensively probe for connected/healthy fields.
23648
+ function renderMcpCatalogCard(server, statusMap) {
23649
+ var name = server.name || '(unnamed)';
23650
+ var transport = server.type || 'stdio';
23651
+ var enabled = server.enabled !== false;
23652
+ var status = statusMap && statusMap[name];
23653
+ var statusKind, statusLabel, statusColor;
23654
+ if (!enabled) { statusKind = 'disabled'; statusLabel = 'disabled'; statusColor = 'var(--text-muted)'; }
23655
+ else if (status && (status.connected === true || status.status === 'connected' || status.healthy === true)) { statusKind = 'connected'; statusLabel = 'connected'; statusColor = 'var(--green)'; }
23656
+ else if (status && (status.needsAuth === true || status.status === 'needs-auth')) { statusKind = 'needsauth'; statusLabel = 'needs auth'; statusColor = 'var(--yellow)'; }
23657
+ else if (status && (status.connected === false || status.status === 'failed' || status.error)) { statusKind = 'failed'; statusLabel = 'failed'; statusColor = 'var(--red)'; }
23658
+ else { statusKind = 'pending'; statusLabel = 'pending'; statusColor = 'var(--text-muted)'; }
23659
+ var toolCount = (status && (status.toolCount != null ? status.toolCount : (Array.isArray(status.tools) ? status.tools.length : null))) || (Array.isArray(server.exposedTools) ? server.exposedTools.length : null);
23660
+ var lastChecked = status && (status.lastCheckedAt || status.checkedAt || status.updatedAt);
23661
+ var src = server.source === 'auto-detected' ? 'auto-detected' : 'user-configured';
23662
+ var lastError = status && status.error;
23663
+ var html = ''
23664
+ + '<div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:8px">'
23665
+ + '<div style="display:flex;align-items:center;gap:8px">'
23666
+ + '<div style="font-size:13px;font-weight:600;color:var(--text-primary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + esc(name) + '">' + esc(name) + '</div>'
23667
+ + '<span style="display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:500;color:' + statusColor + ';background:' + statusColor + '20;padding:2px 8px;border-radius:999px;text-transform:uppercase;letter-spacing:0.04em">'
23668
+ + '<span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:' + statusColor + '"></span>' + esc(statusLabel)
23669
+ + '</span>'
23670
+ + '</div>'
23671
+ + '<div style="display:flex;align-items:center;gap:10px;font-size:11px;color:var(--text-muted)">'
23672
+ + '<span style="text-transform:uppercase;letter-spacing:0.04em">' + esc(transport) + '</span>'
23673
+ + '<span>·</span>'
23674
+ + '<span>' + esc(src) + '</span>'
23675
+ + (toolCount != null ? '<span>·</span><span>' + esc(toolCount) + ' tool' + (toolCount === 1 ? '' : 's') + '</span>' : '')
23676
+ + '</div>'
23677
+ + (server.description ? '<div style="font-size:12px;color:var(--text-secondary);line-height:1.45">' + esc(String(server.description).slice(0, 240)) + '</div>' : '')
23678
+ + (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
+ + (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">'
23681
+ + '<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" disabled title="Edit + Reconnect coming in the next slice" style="opacity:0.55;cursor:not-allowed">Edit</button>'
23683
+ + '</div>'
23684
+ + '</div>';
23685
+ return html;
23686
+ }
23687
+
23688
+ // PUT helper for the Toggle button. Lazy: re-fetches the catalog after
23689
+ // the round-trip so the new state is reflected. Future slice will swap
23690
+ // to optimistic update + rollback on error.
23691
+ async function toggleMcpServerEnabled(name, nextEnabled) {
23692
+ try {
23693
+ var r = await apiFetch('/api/mcp-servers/' + encodeURIComponent(name), {
23694
+ method: 'PUT',
23695
+ headers: { 'Content-Type': 'application/json' },
23696
+ body: JSON.stringify({ enabled: nextEnabled }),
23697
+ });
23698
+ var d = await r.json();
23699
+ if (!r.ok || d.error) {
23700
+ toast('Toggle failed: ' + (d.error || 'unknown'), 'error');
23701
+ return;
23702
+ }
23703
+ toast(name + ' is now ' + (nextEnabled ? 'enabled' : 'disabled'), 'success');
23704
+ refreshToolsMcpCatalog();
23705
+ } catch (e) {
23706
+ toast('Toggle failed: ' + String(e), 'error');
23707
+ }
23708
+ }
23709
+
23480
23710
  async function refreshCron() {
23481
23711
  try {
23482
23712
  // Fetch operations + cross-job recent runs in parallel for the three-zone
@@ -24809,6 +25039,18 @@ async function loadCronPreviewIntoTab(jobName) {
24809
25039
  // Mark the preview as stale (call after save so next tab visit refetches).
24810
25040
  function markCronPreviewDirty() { _cronPreviewLoadedFor = null; }
24811
25041
 
25042
+ // PRD Phase 1.3a: inner Configure pane tabs. Sets the data-active-config-tab
25043
+ // attribute on #cron-tab-configure; CSS handles section visibility via
25044
+ // data-config-tab attributes on each section. JS-light by design.
25045
+ function switchCronConfigTab(tab) {
25046
+ var pane = document.getElementById('cron-tab-configure');
25047
+ if (!pane) return;
25048
+ pane.setAttribute('data-active-config-tab', tab);
25049
+ document.querySelectorAll('.cron-config-tab-btn').forEach(function(b) {
25050
+ b.classList.toggle('active', b.getAttribute('data-config-tab-btn') === tab);
25051
+ });
25052
+ }
25053
+
24812
25054
  // ── PRD Phase 1.2: "Run task once" — inline run + Last run tab ───────────
24813
25055
  // Tracks an in-flight run triggered FROM the modal so the SSE listeners
24814
25056
  // know when a cron_complete event belongs to "the run I just kicked off"
@@ -25098,6 +25340,7 @@ function openCreateCronModal(agentSlug) {
25098
25340
  var schedLink = document.getElementById('sched-cron-link');
25099
25341
  if (schedLink) schedLink.style.display = '';
25100
25342
  switchCronTab('configure');
25343
+ switchCronConfigTab('basics'); // PRD 1.3a: always start on Basics for new tasks
25101
25344
  onPredictableChange();
25102
25345
  document.getElementById('cron-modal').classList.add('show');
25103
25346
  // Snapshot AFTER all defaults are populated so an immediate close with no
@@ -25196,6 +25439,7 @@ function openEditCronModal(jobName) {
25196
25439
  // dead empty pane). The pane updates live when Run task once fires.
25197
25440
  renderCronLastRunPane(job);
25198
25441
  switchCronTab('configure');
25442
+ switchCronConfigTab('basics'); // PRD 1.3a: always start on Basics
25199
25443
  document.getElementById('cron-modal').classList.add('show');
25200
25444
  setTimeout(captureCronModalSnapshot, 0);
25201
25445
  }
@@ -25542,26 +25786,34 @@ async function saveCronJob() {
25542
25786
  // Field-specific validation. Toasting one message per problem is more
25543
25787
  // actionable than the old "Please fill in all fields" — and we bail before
25544
25788
  // hitting the API so the user sees the issue without a round-trip.
25545
- if (!name) { toast('Task name is required', 'error'); document.getElementById('cron-name').focus(); return; }
25789
+ // Helper: focus a field on a possibly-hidden inner config tab. Switches
25790
+ // to the tab that owns the field first so the user sees the cursor land.
25791
+ function focusOnConfigTab(fieldId, configTab) {
25792
+ if (typeof switchCronConfigTab === 'function') switchCronConfigTab(configTab);
25793
+ var el = document.getElementById(fieldId);
25794
+ if (el) el.focus();
25795
+ }
25796
+ if (!name) { toast('Task name is required', 'error'); focusOnConfigTab('cron-name', 'basics'); return; }
25546
25797
  if (!/^[a-z][a-z0-9-]{0,63}$/.test(name)) {
25547
25798
  toast('Task name must start with a lowercase letter and contain only a–z, 0–9, and hyphens (max 64 chars)', 'error');
25548
- document.getElementById('cron-name').focus();
25799
+ focusOnConfigTab('cron-name', 'basics');
25549
25800
  return;
25550
25801
  }
25551
25802
  if (!editingCronJob && Array.isArray(cronJobsData)) {
25552
25803
  var dup = cronJobsData.find(function(j) { return String(j.name || '').toLowerCase() === name.toLowerCase(); });
25553
- if (dup) { toast('A task named "' + name + '" already exists', 'error'); document.getElementById('cron-name').focus(); return; }
25804
+ if (dup) { toast('A task named "' + name + '" already exists', 'error'); focusOnConfigTab('cron-name', 'basics'); return; }
25554
25805
  }
25555
- if (!schedule) { toast('Schedule is required', 'error'); return; }
25806
+ if (!schedule) { toast('Schedule is required', 'error'); switchCronConfigTab('basics'); return; }
25556
25807
  // Light client-side cron sanity check — server uses real cron-parser.
25557
25808
  // Catches obvious garbage like "foo bar" without making a round-trip.
25558
25809
  // Note: the hyphen sits at the END of the character class to avoid being
25559
25810
  // interpreted as a range. \\\\s in source → \\s in served JS → \s in regex.
25560
25811
  if (!/^([0-9*/, -]+|@(yearly|annually|monthly|weekly|daily|hourly|reboot))$/i.test(schedule) && schedule.split(/\\s+/).length < 5) {
25561
25812
  toast('Schedule does not look like a valid cron expression', 'error');
25813
+ switchCronConfigTab('basics');
25562
25814
  return;
25563
25815
  }
25564
- if (!prompt) { toast('Prompt is required — tell the agent what to do', 'error'); document.getElementById('cron-prompt').focus(); return; }
25816
+ if (!prompt) { toast('Prompt is required — tell the agent what to do', 'error'); focusOnConfigTab('cron-prompt', 'prompt'); return; }
25565
25817
 
25566
25818
  // PRD Phase 1 goal fields. successCriteriaText is freeform; successSchema
25567
25819
  // is parsed JSON. Validate JSON early so the user gets a clean error before
@@ -25574,12 +25826,12 @@ async function saveCronJob() {
25574
25826
  successSchema = JSON.parse(successSchemaRaw);
25575
25827
  if (!successSchema || typeof successSchema !== 'object' || Array.isArray(successSchema)) {
25576
25828
  toast('Success schema must be a JSON object', 'error');
25577
- document.getElementById('cron-success-schema').focus();
25829
+ focusOnConfigTab('cron-success-schema', 'prompt');
25578
25830
  return;
25579
25831
  }
25580
25832
  } catch (e) {
25581
25833
  toast('Success schema is not valid JSON: ' + (e.message || String(e)), 'error');
25582
- document.getElementById('cron-success-schema').focus();
25834
+ focusOnConfigTab('cron-success-schema', 'prompt');
25583
25835
  return;
25584
25836
  }
25585
25837
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.79",
3
+ "version": "1.18.81",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",